{"_id":"@balena/jellyfish-core","_rev":"6294-b4a84ae9b95305b0c608edc7b5df9679","name":"@balena/jellyfish-core","dist-tags":{"latest":"16.0.13"},"versions":{"0.0.2-copy-over-code-f165a242db858fecd4814346025bb67ef774ab89":{"name":"@balena/jellyfish-core","version":"0.0.2-copy-over-code-f165a242db858fecd4814346025bb67ef774ab89","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.2-copy-over-code-f165a242db858fecd4814346025bb67ef774ab89","maintainers":[{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"35cb97be35fd18f0dd7b23e11c2f7263b6e99f04","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.2-copy-over-code-f165a242db858fecd4814346025bb67ef774ab89.tgz","fileCount":86,"integrity":"sha512-cNyVZcGIpV482lfHB/NZrGVLeQdFwe0ncSQ8vDbAYrzjNJIqB0F8R4ga7DpAdjrmNZTUuPyvPxCDlu5Ox8t+Yw==","signatures":[{"sig":"MEUCIQCWR0FilNwQtpRTsrg5wYMihHCppiMinnVA0NK6hV3azQIgAIt+G2bnStMlxrFcViz7IZcGxYkYPh4fduVo/2G9AR0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":519559,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfSKTVCRA9TVsSAnZWagAAhiAQAJ3MBFRmcs/ShSJt3z34\ne/JtkhucyURlkJWABZ+DRusf+N5LwrSHipM94B5veCyYUpqyDTsWyxBtDRGl\nbROM7tWojDXfpooWlnKrSDZ56VETTpUkA1VhWz4KHVF2+bAFafx9NTIwBq1Q\n9uaor+xr5U49RorV4yZntBm367CdX32gDmocsj5n7TvRtSNmmBRmi7Inq3je\nTbm9ZFcin1zBlbZnRnxt3dFpLCX62L2DNCds/Lsx1nNzU2xcIilqcr7w8nOl\nz1ynvc149899cBb6UukfzMwqD85gTEB6HA4SUkPnN5QuRkq2qlEH0P2ugrqV\nABOPP7CJtmlLOWyBwDIc2xKXxDSy2CWS+VKWDqgHBq11EMVcEBsNp2EAenbF\n64bZ38taUfWNx0mFLqqYI1AoRbzI75rv6gZ+yjGQoL5jPeD2tTwhbROTgAnr\nhVWxE1G5RHU/XgxK7ykITstV2R3vMcgiwFC7GeSXZYCzlDubbxoYY8jqKXQd\nivTN38TMPNhfAo01FZsTb5h6kVa4SFZd236uMI7f5v1aKPwtlL+lbWYSTk4V\nRUy82PhA8Id5QZXCMfIiz/JJjKQWeg5XaBu4S2m2R6/iaMJ9+LI1M44zEFIX\nIpFL2IxswbJeowHGrXs8G7AkSJsW7aDisF0fkH87Lifls5XmDezBrl/uwOxW\n2TTr\r\n=4EBX\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"f165a242db858fecd4814346025bb67ef774ab89","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes && make lint && make test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"_nodeVersion":"10.20.1","dependencies":{"json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^3.0.0-1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.2","json-schema-deref-sync":"^0.13.0","@balena/jellyfish-assert":"^1.0.2","@balena/jellyfish-logger":"0.0.49","@balena/jellyfish-metrics":"0.0.53","@balena/jellyfish-environment":"^2.2.9"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","eslint":"^7.7.0","deplint":"^1.1.2","depcheck":"^1.2.0","shellcheck":"^0.4.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.0","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@json-schema-org/tests":"^2.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.2-copy-over-code-f165a242db858fecd4814346025bb67ef774ab89_1598596309228_0.014897186534600815","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.2-add-githook-8837f9d46864840a697c34b473fc6d07f8ba9654":{"name":"@balena/jellyfish-core","version":"0.0.2-add-githook-8837f9d46864840a697c34b473fc6d07f8ba9654","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.2-add-githook-8837f9d46864840a697c34b473fc6d07f8ba9654","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4b9a55df8b1b8094578197a1057fba864163bfbd","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.2-add-githook-8837f9d46864840a697c34b473fc6d07f8ba9654.tgz","fileCount":85,"integrity":"sha512-XTFwO7xjuvvZVhVZUJespVkloJwk7fw6C3h+oJOnK9RhcLLw3E0Ql1kohEX5nCsZieTvtXDNdM6Tjd2ZohqPww==","signatures":[{"sig":"MEYCIQC+itQeCJ6BxUJbBLoy+v1cxmBBgfqDMb0445V9FF4i7AIhALwXmvMZNzLHVsqyFsaeV7EiUl1c3bpqJxFonDZ27Pen","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":512800,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfSKaWCRA9TVsSAnZWagAACXkP/2fK9Aa6pCI9dTvej4u0\nhLCx4IXMGHsjzEqgzDRpmoukSAcbfFA932dwHUOy7p65jqnvrtvmpNo9eeIA\nkDfLHDqn1VoJF1w0yIYhW4BVjCeD+A3fm8d/P05SKtvSYhSN8VcZtV8twiy6\nj4EhgwSA4mY/a2I2v3KbV7XpV9qM/n9kvp/w8m0B6+0t2JBEQJO/PXSctpx3\np5DBNZdisnSzgc0PDGwWIxnx4Gqiazbbz1R4RZjmJt2k25MyaWdS/75MYdaq\npojfW/xV7TjrvuV8zXtGqcdo2Z1cd6FAXW8zEhZYfJZa1fAybSD5fqOkd5R5\nfbpMw05AOVjTJBKvheUGBznigrj9G6obHqqkGHCf7tz+qPzfPQiOEN4Q8Zvr\n4Rs8kXFrQbsmLVJ+tvfOK+9U6JtmFAGwD6AEkq6/EcKBv+WgdaObq07bE6c7\nk5zP2apP8TiPuQz2zW1iug8soz/dSVv35DnRi2DmB9g0sVQwaeFqSKpylAA0\n5RfB0+BYjC6g9qCAegoWz6kU6rJ0UDJLkBmY5S7VHX6akLvVo6S0GssGjWKL\n9BncEZq3Dz9ogFcyiQjZnKkw+KVYoauXGTu5iW5gf7XpvbHEl0MFnZVNdBty\n29GmnabqpuRJwsZulNz2A16Pr4nG9Nc8vcIh8J5eRxH6CP7OwweECUlo5FU7\nAqOT\r\n=LZk0\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"8837f9d46864840a697c34b473fc6d07f8ba9654","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes && make lint && make test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^3.0.0-1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.2","json-schema-deref-sync":"^0.13.0","@balena/jellyfish-assert":"^1.0.2","@balena/jellyfish-logger":"0.0.49","@balena/jellyfish-metrics":"0.0.53","@balena/jellyfish-environment":"^2.2.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.2.5","eslint":"^7.7.0","deplint":"^1.1.2","depcheck":"^1.2.0","shellcheck":"^0.4.4","lint-staged":"^10.2.13","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.0","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@json-schema-org/tests":"^2.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.2-add-githook-8837f9d46864840a697c34b473fc6d07f8ba9654_1598596757679_0.13615078702193628","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.2":{"name":"@balena/jellyfish-core","version":"0.0.2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.2","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"be0be4a02bf822a7d16094415fba59ea08d0b25e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.2.tgz","fileCount":85,"integrity":"sha512-Y15909T82ZHPHAjSfxRyBsov6WJ4Zq15QDXkWzZQcP9ENul/PtD6yjTs/wjis2IpmQXgz5FudniQLATa08HVmw==","signatures":[{"sig":"MEQCICqn7oPIzuazwGXCzxG9IaF3IO7N+1xhErc1sk9sfC9bAiB3qHJZOlo9JUDxKY/pD0Nr2oLn/OmMO6bBFBZf/u7X2w==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":512747,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfSKesCRA9TVsSAnZWagAApmUP/1OJxFUzwp8aI3Z6nd/A\nskA2259TcnRwMEzskjgrwm8qOuRR1X9f1X+KyCHKV1XepLdlYvuNtILXhT+z\nW5/le72DYjYABxNvd+ZRjZZdURXDhbcBuh7oxRP97uAx7G+avNbCzbFD5eYq\ncp5h6Fz3DPVEVIzriY+xhgtmCJ5uWXtvtEXPjEuoaYhyI3bB2ofMKzTP0/9y\noOFn1WN9Pi4eLU2fB/RN4SzkGzOyIf7G30shS+aNqRwrw4uGtFGV+vkmxnU/\nBVOmC9DzCtj1okHZIH4n2ODBxWLLzzPFgEHXYWvmpG1CjJfpt2H8NYGIxCDE\nkC4WhlccgVMbiQv0NVsC6P5t/50Uennd8pC1MtZe8JsvkbnFgLdYSOyaSkTp\nje83zl6LQaNAgP8mhv1mooEJZAyaJ3pSiPw/p9S5FIBxyAd07hFKNAoo7QAf\n/8URWW4t97tFWPDuz4khPTlF3XIna5bSP3zm2QEOMeEzWshbtwu1R8tu6s6h\nwQqRh3o61Etl8SZorSkhz+42M/6ttW8vRBve2dJgfvMUa2z3vgaxkRcKBh1d\npdIvanFglRvl2gF5+VOvFfDybMCJtUUWh8oJrBVRAmQyOG6XSAuvzhi1eTp5\nvlFpEdtuoN+Fu0uoEYtsoEYk8CF6CGpfJzqIUIlIdfRF23dN5qyvf4bBH/B9\n0a7U\r\n=laSy\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"a946802d4aa3edd71b91c6eb0ac40e1cad261367","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes && make lint && make test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^3.0.0-1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.2","json-schema-deref-sync":"^0.13.0","@balena/jellyfish-assert":"^1.0.2","@balena/jellyfish-logger":"0.0.49","@balena/jellyfish-metrics":"0.0.53","@balena/jellyfish-environment":"^2.2.9"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.2.5","eslint":"^7.7.0","deplint":"^1.1.2","depcheck":"^1.2.0","shellcheck":"^0.4.4","lint-staged":"^10.2.13","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.0","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@json-schema-org/tests":"^2.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.2_1598597036166_0.8277113031341621","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.3-add-ci-docker-f76696aeb492c6d093a89bc98455d61111a0c830":{"name":"@balena/jellyfish-core","version":"0.0.3-add-ci-docker-f76696aeb492c6d093a89bc98455d61111a0c830","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.3-add-ci-docker-f76696aeb492c6d093a89bc98455d61111a0c830","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"25c2d5807cf786525943367f33c140d49b7c4a0f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.3-add-ci-docker-f76696aeb492c6d093a89bc98455d61111a0c830.tgz","fileCount":90,"integrity":"sha512-qt/rtIutQ8K5n5mhwuBIFE7pdHeUJTkT3D+OB6mtDcbYxZX4KCnXb79tnTLBteaLLOBenGNGObNvhspCh6QpCA==","signatures":[{"sig":"MEUCICXqrCz9Lyr3TkpjkSXJ2wRpGTNMdZH7HWlToDAEPY67AiEAt/e3IcFKvvhEtr/algbTtytrZfpIaWm9wnuQgG5tBKs=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":515307,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfSNSwCRA9TVsSAnZWagAAG3AQAJRCF3pJA2b5B5xAUfQS\ny/gze/cRDEAMT5TyDyN3e5ep4TFFSiwCb60vtOwTA0aLDTbJvPgeWMaNmqVd\nSQyefuetjSqzLAvjx8j5Q+FrGrOuGakMb9tdbr3TDH+WF4PaR2nCoy6OfS+C\nBBx0/t8/lzsPqK7ePjyc7o/mM+efPqg+kdUi4msULHNMTgwjoERQHsV5mF0H\nABMH8GE9kfJf+/r+tQjQXA3V8c395Rnw8OLkpoOXl9yyidJGldlwT2sXTjEK\nZwhQZoTDFiiGthcKBw+Nb9lXlTBAbSfKqPM7myTVNNvxyJ7AQYlPUERaLflU\nAS1tCYtYEG9YRqRKKJ99P+/mwMD256CNeyzn1svmDUq+Qpnyw6MNVrJ++mnM\nj74vtOzL7bThylm/pHLJVND8BqP3CoqQ0P8pcpeLJsjVN8dqQEmI4p/Ak02o\nPDXHinzruAbiw8+b9PPQpFQ/o7XQYAR7Y2kSK11ohi/rRBsmiuZjmRksojhq\nuEqCrji6LiKEzsIWDFniw0L3J6C8yOqUcI4doAilncDOIvQj4HcJKXT+MPjL\nYl4Nxd/4nS/MNNxZIPN2oHyR4d6lVkokR9vsQBlmertsuv2nHLkGLD4ylyG9\nlHYgg4wWrqQ0Gjn0GnPX1PMp0axJ86cc3BnFp7bEsLhMmH7o2pB54KAlYGnv\neWFn\r\n=WGUy\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"f76696aeb492c6d093a89bc98455d61111a0c830","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes && make lint && make test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^3.0.0-1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.2","json-schema-deref-sync":"^0.13.0","@balena/jellyfish-assert":"^1.0.2","@balena/jellyfish-logger":"0.0.49","@balena/jellyfish-metrics":"0.0.53","@balena/jellyfish-environment":"^2.2.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.2.5","redis":"^3.0.2","eslint":"^7.7.0","deplint":"^1.1.2","depcheck":"^1.2.0","shellcheck":"^0.4.4","lint-staged":"^10.2.13","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.0","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@json-schema-org/tests":"^2.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.3-add-ci-docker-f76696aeb492c6d093a89bc98455d61111a0c830_1598608559790_0.41674451609092444","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.3-add-ci-docker-a3de8f7a9fc811d89948403e67af6a94b82a205b":{"name":"@balena/jellyfish-core","version":"0.0.3-add-ci-docker-a3de8f7a9fc811d89948403e67af6a94b82a205b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.3-add-ci-docker-a3de8f7a9fc811d89948403e67af6a94b82a205b","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e120d115a65a34b42d23e5af7a107fb6f54b2ead","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.3-add-ci-docker-a3de8f7a9fc811d89948403e67af6a94b82a205b.tgz","fileCount":90,"integrity":"sha512-CIaw1oEENb0f7H94nrgEaDEro3U2BK7QYBobiJXeOGTfXev/0IkPs3uTWk+qFYbNvzpyTPDbli5GLyyVNXrtOw==","signatures":[{"sig":"MEUCIQCFfIAxhKO19hiJuWXw6sQENl7JGbN0+UcfHBjtoHgxgQIgIJncLbnuY98PbFl/5F4DxDK9MB30UuHidVZbPKuvnoc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":515301,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfSQvoCRA9TVsSAnZWagAAAeQQAJeHWQu63vXvSl/EGCjo\nLdDtI4dLC5vVquxlYJ2vBIrKYmGGM3mpBrEPcU4C9/gVh3z+kk1hBxURxxat\n889FeA+pHjWwketlWFnDDnPzxKYow975mENp5i7djWWjcteAIFgO1GsYT8zw\nWayKxPNTzYlTPnYH5xyoHTrVGNytS8crHOI8eU2rXYcAZzb6jUH8ikFmYxMI\nCIyE+zrDmDcvyE2wSpgP4Wq4uaI5pWqExxgato1j8MyWGYna249aJimcyll2\nDIrCR931dx0xG0EjOWnMtbGfgJU37dur8IyryxBi0eHdKr4QWMgF9AFUTEl2\naIfaC1J/8MF1iqqVLWBmswc8Ku0Vjq9/jZu/Iv3EN6XA77bt9ZaMn8zNeUiD\nzeJe0u55G/78OMsz3NcMdVhF1FAcvMpAGEzvsMi0AmsPcOS4s3mJnKYXoTkz\nr4KzK04deUtC5Lu9CiAbvePVccxqch0NvbSZ+1quncCvAUVlC7dDNgfirZjS\nYtKwZu3vrI2wFLQiFcR+c0c8ohEgGfwcDK+wEhFyCoYwaIY3BOOEVrD6pEkQ\noKqGJc5mRFn1Bnw8lXEnS6E79UR0hfqLM+vThQpYDfl6UXFS3LzVlAeZPHBe\n1rdhTBmeVHaUYKU+phRzOuP7puoKqktZElyXXtjFa1sE2AUX0tbDEFX4jrLo\n1g1g\r\n=nmvs\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"a3de8f7a9fc811d89948403e67af6a94b82a205b","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes && make lint && make test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^3.0.0-1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.2","json-schema-deref-sync":"^0.13.0","@balena/jellyfish-assert":"^1.0.2","@balena/jellyfish-logger":"0.0.49","@balena/jellyfish-metrics":"0.0.53","@balena/jellyfish-environment":"^2.2.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.2.5","redis":"^3.0.2","eslint":"^7.7.0","deplint":"^1.1.2","depcheck":"^1.2.0","shellcheck":"^0.4.4","lint-staged":"^10.2.13","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.0","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@json-schema-org/tests":"^2.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.3-add-ci-docker-a3de8f7a9fc811d89948403e67af6a94b82a205b_1598622694791_0.3041171441181705","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.3-add-ci-docker-f3b4985112a0517400b1e8b0917723da95ac2592":{"name":"@balena/jellyfish-core","version":"0.0.3-add-ci-docker-f3b4985112a0517400b1e8b0917723da95ac2592","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.3-add-ci-docker-f3b4985112a0517400b1e8b0917723da95ac2592","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6ad4d08630f935e297a3b836da0a4f48fa20adac","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.3-add-ci-docker-f3b4985112a0517400b1e8b0917723da95ac2592.tgz","fileCount":90,"integrity":"sha512-vvbr7heUdrB/uiqqFJx1wmV8GIGFIQgIW7y5IAiG4W4+kaNENJCfgs5pWWvsSwp8nESobNfAMfsKJPeSb4KqiA==","signatures":[{"sig":"MEYCIQCq9gTMe8/Y/zA8fBKbAKp8L787q9mW3gr60A/nJZP71wIhALu2eVRZseldzjhRmuS27iOa+vkYOGtfWcBSNjMIH7LT","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":515697,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfSRVqCRA9TVsSAnZWagAAsMkP/RxKi4LGEWvhFwEF3twk\n6QsVhrdBAygq3PSR2UWAqdiAkUJKw/9Ek8K6iye8w7X+fKvrl5000dpsS0Ng\nQ1R3B2+XpzWHuUZTlCVzywC4AZvGr2dknqxPQJE+66wlZfz6uQcqlIGlUsfR\nKP3tTCL04ipkovJGfQ8vIEW/81p3wL4Xrctbv0OmPAKdSxLBFhrk5k/fpCo8\nw38Cm3wMh9Df2Wok7y2OsjbBNICou0V0ov3zqAAq2D3JFJTrOerUSxQggMN2\nwu2autF9bP74wd4GHdajFsagMmNh3/BiKSpP/D+L71VgWovVSQzB0J0VBB3D\nBfrq4LUHlnyO8ZIIQ+ZFxehl/yzf3Vi8kY3rc6W9YjbqB+ce8C4vBqska8ng\n1ozRxlEXhEAsj6WkGB0hWFUv1Ea2DMmqhcp5C0Ci75cvZyG9L6sMdJXPAFc3\nHz/sMXdeIMcvD7Hvv79IHWPOZvIBtynKigmcYJ5GXbrzg4kjGMXtJ/zmWJjQ\nuiuoo6V5XetRN+fO6rS03MuxpfQTV1V6Zky9eAaAXtQzqVtRRQMuv41XFh1y\nO+YOHyPRCPgZYwpwDsd7TuKUc8cphGFFFLtCGv1xVpQMbsfyYtnd7DHDFaB+\nz79YdwYrb2QDKac37pmNbWg9ez9ZulAFagcDYrHUeGLsnZ6wx/GKaiLtTiCQ\nx8nF\r\n=1BtT\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"f3b4985112a0517400b1e8b0917723da95ac2592","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","lint":"eslint --ext .js lib test && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck","test":"ava -v ./test/**/*.js","check":"./scripts/check-filenames.sh && ./scripts/check-licenses.sh && ./scripts/check-deployable-lib.sh && shellcheck ./scripts/*.sh","lint-fix":"eslint --ext .js --fix lib test","test-unit":"ava -v ./test/unit/**/*.js","test-integration":"ava -v ./test/integration/**/*.js"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^3.0.0-1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.2","json-schema-deref-sync":"^0.13.0","@balena/jellyfish-assert":"^1.0.2","@balena/jellyfish-logger":"0.0.49","@balena/jellyfish-metrics":"0.0.53","@balena/jellyfish-environment":"^2.2.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.2.5","redis":"^3.0.2","eslint":"^7.7.0","deplint":"^1.1.2","depcheck":"^1.2.0","shellcheck":"^0.4.4","lint-staged":"^10.2.13","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.0","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@json-schema-org/tests":"^2.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.3-add-ci-docker-f3b4985112a0517400b1e8b0917723da95ac2592_1598625129848_0.4571748599350871","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.3-add-ci-docker-08bb1326f796ea7110439cacd36cefdad2ee61bb":{"name":"@balena/jellyfish-core","version":"0.0.3-add-ci-docker-08bb1326f796ea7110439cacd36cefdad2ee61bb","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.3-add-ci-docker-08bb1326f796ea7110439cacd36cefdad2ee61bb","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0d48163ab7d4010ff92c09fc87d880886e1c21e3","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.3-add-ci-docker-08bb1326f796ea7110439cacd36cefdad2ee61bb.tgz","fileCount":91,"integrity":"sha512-TYcgpyB3VW9McghEy084PFUsMRr8Q2ksaBCyFtdUWnPLf+fQaOSX5avBelCYQihCcXQ5hgQlC72qZf8WSCJe6Q==","signatures":[{"sig":"MEYCIQDTKO8IkZgI+9Z/OsbQPFbdSayxfV/uvH9jaZUCxU9rXAIhAJBJcp1LCv/dwuY72GBDBEqWyLis5BK6v3umoERHyDWh","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":516499,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfSubCCRA9TVsSAnZWagAAQ84P/3DE1pWd+J3ee7Z4EBzC\n3EsLqdaBozm8OZpLHntzp4Dl9tfdYzuyheNMstYJPU27Zhym+iIcs08HAmPA\nB/39K5MvaATVSK1kCpEOrA+1uVN+UhNDM/benDsBZUgIO6rVlOnofPRT7bPF\n1/fSyXv+n/cjA7OqmI1YaOVyvjUIETgaCGOUlFSodISnP1g5mKqq79MOGMea\napTDz0JkfaVM4rM3D0CQYs7v5rSb9GXw7pxuI+fgcsNiJcms6qYMtoq9b66U\nvx1pVMBJAlDXpPKAlmwCpAYjeeqbYjRTOA/xO4BPhDfxGGoupQpAfyJTe9dt\n7Pr08uRvVw09UlzUhdcRNP45CFYsaKJwhtITzEjKERlEhvHPkk27+pYyxmgm\ns/Xc+DIEBefRbZLLfaPiG1VWuPCHQukRBdIdr7JtLVIGp1LnhWE3Wb5ltlkp\nmDXhprCUiHLPB4GfVMu3Ql35ygkIEmq4hqRF2Q5h9TCEmTamB1huUM6GWcfM\n0CjZiQsJrt67+F53ihvIfFbw4m1hT2bG/eldP607peKhNpQ9W6ge4Uvcu3UI\nELvlPaXXGOg8O0ckWmswAGprXPJXamIpGmhKqm7k4CLDGhFTYKXNU6hBhjct\nhQwIiArX6cZ+R5B6jYlcXhwurWNow2KdsHlGeHayCC0AEvbyP/qMVOUwTmlV\n+UvG\r\n=6pQg\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"08bb1326f796ea7110439cacd36cefdad2ee61bb","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.2","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.2","@balena/jellyfish-logger":"0.0.49","@balena/jellyfish-metrics":"0.0.53","@balena/jellyfish-environment":"^2.2.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.2.5","redis":"^3.0.2","eslint":"^7.7.0","deplint":"^1.1.2","depcheck":"^1.2.0","shellcheck":"^0.4.4","lint-staged":"^10.2.13","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.0","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.0","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.3-add-ci-docker-08bb1326f796ea7110439cacd36cefdad2ee61bb_1598744258033_0.7536443233443613","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.3-add-ci-docker-98834709dc290f61db6907bb807aa5cb113b32b9":{"name":"@balena/jellyfish-core","version":"0.0.3-add-ci-docker-98834709dc290f61db6907bb807aa5cb113b32b9","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.3-add-ci-docker-98834709dc290f61db6907bb807aa5cb113b32b9","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"dd6c820693208bc4247324b376f6999bb8ce661b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.3-add-ci-docker-98834709dc290f61db6907bb807aa5cb113b32b9.tgz","fileCount":92,"integrity":"sha512-VdgJb9FUniieInp5Oh5Uc4ipHmPuPwz+xgQl9LPGe75QbiNK837IgY2+oVEtBQFojc6eA5vDcEDmlIWkXjqogw==","signatures":[{"sig":"MEYCIQDznKFa5LOjg33UyHbF3ZQDQ17WOsfoi9E7kwNgaLjbfgIhAP8M0PzXXFiiyQoIHg2/5rU/bZrr7dsABn0BMB6+AEjw","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":516509,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfSuwMCRA9TVsSAnZWagAAc5UQAJjoJdW/O1+J+GwScjHy\n46AyC8Nac9qu3FS1vqxspU72EsBqUINGXk+7kF0CKw03h36WWH3qqKpXZVpb\ntpbH3+LWjACpCZbuGjrjyjYcoGQvE2WYjzV6Hu+3e7Mt4uPdsgeTIyFMVIvM\nOWWwUm8xlz/aPgwMyLhkDwtlptVm4L7si1T+tJOmEbyPUhVMgdNP1YxH9HLv\nK/zQTRznyeBPbWuzXW94p3Yh0p0xJ0Tn+Yy8S4Pp2KJLb0TkcRvy2X2LPeCa\niKxD7O5BZcnzcsWdg9arm+0aYgMQPVzf5q185J9NxzjuxJubkRvd1ONRzRC5\nhoRaYy5P6VQp/jXnPTZB7X+dLRGKgHdjZZSTQ95G0L6oZop54T7HVwSjg4Xd\nXdt/BZf1/80Y1awoYxxJb0YRAyaQUBcEMGtcB+MkQQdEEpKty7ZREtUKjpxt\nLSOjBAUoi6G8r23XTDSrOlShEWjSgP2sBUz35rWVu4IADVxWHwYl8GBIvdhN\nUFufNIIn8F6rSQlLgU0xdzkPFZiimShLiA3Y5O0uP2qgwco0CdglhfGVkiNY\nmu+4YOPTGMsx+4m3V6IBfuvbJqdRpXIbvUkiQS23pqwOzGk+3LUt6rjHUtsJ\nb+YX2ejCtH/Dd6vJectjyy7NUxuC3Xfb2QvPakIauRwC3qw5Duezil/KKUdo\ntmve\r\n=KP7e\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"98834709dc290f61db6907bb807aa5cb113b32b9","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.2","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.2","@balena/jellyfish-logger":"0.0.49","@balena/jellyfish-metrics":"0.0.53","@balena/jellyfish-environment":"^2.2.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.2.5","redis":"^3.0.2","eslint":"^7.7.0","deplint":"^1.1.2","depcheck":"^1.2.0","shellcheck":"^0.4.4","lint-staged":"^10.2.13","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.0","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.0","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.3-add-ci-docker-98834709dc290f61db6907bb807aa5cb113b32b9_1598745612069_0.7665333136298911","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.3-add-ci-docker-9e89f292e448e38f06cc035af08c974f255610cb":{"name":"@balena/jellyfish-core","version":"0.0.3-add-ci-docker-9e89f292e448e38f06cc035af08c974f255610cb","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.3-add-ci-docker-9e89f292e448e38f06cc035af08c974f255610cb","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e6a578a4ac6cd58ff4e9bf88c68b46c7d2680e29","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.3-add-ci-docker-9e89f292e448e38f06cc035af08c974f255610cb.tgz","fileCount":92,"integrity":"sha512-Am1IifGyOZLdqBdQ3F4LjRjVAFg3513jsiHlzv2p4WR5UPJogi/eAowgaWE9XNn/H+51PgEwqFVyyBkUKdWGnQ==","signatures":[{"sig":"MEQCIFzZ2IlRUWJcUsNwm36MwYRKdDtlHTJBn33KgBTzgTEDAiBIUJAQ8CtYaUmhjbc2l7WEvv7lbGYweXvhtCu1jDyHXg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":516640,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfSu5tCRA9TVsSAnZWagAA7DgP+gNL7qDgxsmAz+BxXIG6\nYW8IWOaTiYf8tobPrn+Sz/fPXmf8aH8dkcHbffylOBvHSMhXAAcMzxY0ib2p\nkXH/28gcrCuuKWbn8jw+s0mpb3wmPvhEoV03e99XiSvC+u336PI6LJNjo1oe\nfxiut5In4rj2qFK3vGEVVnrVfxasIWVOCyRdNl5B+RQzut5qBLX0k190l5gg\nggzI8Q1OGgS2VxOIz4T+vEbzolVe7/iOOzLhF44P/XBUsrL+7Na4XzstDsln\nPqOp8hsBtReRJVoU3OUJvDG6QEjNyOHqLZQSHl0xZhkNg3Wf0GmMr5ud66Pv\nAQQ/Opest6A4Pn3xiuA2kxOOOzYaYsz1vsh4aaLsyLsV1W0b4wUTJAyBotl6\nJPECITk3MT2uulHWlrWETwaiaI91ALu3AmyH1AYNAfOd4yB/vx3XKqk9naVo\nLiySEJCIIzVgfnDzSVKUFdJcLoxh5xpqFEmnqx2AGaR0BlbdBe+rjPYFy3bR\n7ILbvy2655vZoGzclN5u85e5kc4Ws7y3L/JoYYRkoyFl88riErHMc/DP4Mx5\nWLQXS8wMM7hwLp6I/ICFwR9i8MkId6m8nMbmOJR2vb4InaNdh0owwBhLbecA\nh/syuynbiCapnFONAGqToQkBLITDkV1Et2Ig3Tp00OZg+0wfkMet1GTBidfx\nhXML\r\n=IXDF\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"9e89f292e448e38f06cc035af08c974f255610cb","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.2","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.2","@balena/jellyfish-logger":"0.0.49","@balena/jellyfish-metrics":"0.0.53","@balena/jellyfish-environment":"^2.2.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.2.5","redis":"^3.0.2","eslint":"^7.7.0","deplint":"^1.1.2","depcheck":"^1.2.0","shellcheck":"^0.4.4","lint-staged":"^10.2.13","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.0","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.0","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.3-add-ci-docker-9e89f292e448e38f06cc035af08c974f255610cb_1598746221167_0.42527013754293086","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.3":{"name":"@balena/jellyfish-core","version":"0.0.3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.3","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ce081f68dbfc0b33e290adb416c267398642160c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.3.tgz","fileCount":92,"integrity":"sha512-CiUtQtC5oZo72hLwxIr+EZUsNI8cosl0GtJvLJJ0PUDZzX0FDWB9ulQM2ejljXiwnL3VqcUdOyZ5/zaxJy0cxA==","signatures":[{"sig":"MEUCIQCIrbQa4KHzYnxlczD0BYdq8Bj93xPdWRXPhGnheD579AIgNZqaXMFPFBZPMv9m6LeeMRue6r16XKR89tXqcP5WXUQ=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":516585,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfSvDvCRA9TVsSAnZWagAA7/IP/jKVkIyGOTtb723Bjavd\nDQG1HdefNNorxGv+q8EqbeJDm32xgB4H8Vth+CD/d3qo7MVHzFJGQ6OBDf+F\njx//vsWUnRe4KzGyq6yhai50vRE7cUipCUksFpj5H9fwYdwnqK+9l6g31pbD\n+f7oWJ26p4eonSAR4B5NKTpUDtCycj5vfqYSIWC+f5OSGm/9PuTlgZDeSpnZ\nf69eD0GFnAaoTNTuMJiImKPucG/aSjgnpLfiMCumsyG85zIGhWm5fNvvSCdy\nZMBKQlUzqQN6jSI4ekq26etf6bKDRSq6iMq/yvlbSpLn7SYj23PGlFK+eDWk\n04dToFI0mAwZUuLai6zZdSmg9RzASHLJKgppPtQ7zoQxieYA159v+ftvjZyQ\nqApCHZpmt6YXnIL8F5Dc7UW2PtN0EpdmobGiYedOS0UrBNgDVVVfM0V0cCrg\n/lJjjDQlfCwnBvVUzVEEAX48w0OSS4a6iruWy7/h8wWOqQlyXBSCamisBuLA\nmiYB035Zij6nx0cl6mzsf8eU23dV7CH5UpHTuXobWjm9Sjqo+6A1StNzFoqd\n/G/WrrhN/cwNNhYxfyBGaZAF5VEBUd6eN/DPqUH6zIJ4tPoFWDoE4NFWu0do\nNPB+WIZezoWnm9HEY7UlbsxsH3Exi5v3xtEKZhQzktRxl0snfMNmsLgM2fHJ\nS38V\r\n=T/vY\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"6786cfbc5698600c6cea29e443b8a96ee66c76b8","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.2","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.2","@balena/jellyfish-logger":"0.0.49","@balena/jellyfish-metrics":"0.0.53","@balena/jellyfish-environment":"^2.2.9"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.2.5","redis":"^3.0.2","eslint":"^7.7.0","deplint":"^1.1.2","depcheck":"^1.2.0","shellcheck":"^0.4.4","lint-staged":"^10.2.13","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.0","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.0","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.3_1598746863328_0.8770448513379112","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.4-fix-redis-dependency-a76acc0aeabfce97b9440d4e7000846a4c4f73ab":{"name":"@balena/jellyfish-core","version":"0.0.4-fix-redis-dependency-a76acc0aeabfce97b9440d4e7000846a4c4f73ab","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.4-fix-redis-dependency-a76acc0aeabfce97b9440d4e7000846a4c4f73ab","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a3d87237f7aec5e86915b85efe6b5f3ae784f6f9","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.4-fix-redis-dependency-a76acc0aeabfce97b9440d4e7000846a4c4f73ab.tgz","fileCount":92,"integrity":"sha512-6lNtTO3LrR3gF5mIjDhrRIAmcojHlBca+Tf+ojpWSU26AkhT+79YMhTjaaKElpeHc+v0sS9XEq5AcWYQq0NzhQ==","signatures":[{"sig":"MEUCIEbkhb1gkvBS4XD2QhiPl8of+/m9Kn4eDmO/G4disz6eAiEAsYpDOdEujnEENjCH13CeOqcfHaO6JNrOoev1m1n4xYo=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":516714,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfSwOrCRA9TVsSAnZWagAAdToP/RqCDPQWnM/CbQ5Gd0Kh\n+D/ooYqAVD1gMVYuIwRftTdmp3Eh2E8TIj5C44SwUbNu294pt7O+1pq/z4ZV\nmjvIzBECWme+Lz3kPy+PLnf1k5VlIn810boYPVAI63co3z6Tk/eTMXC49caf\n8D6uWIeXq9Fg3X7rqUrNygH7+jws9tZ39v8a3QjhNfCo2b2hVsTzIYw0o40i\nKr7PBXvreSATS/PqpYwNEwlvEflc9t798uKBVJPaTIR+88+HBLl+X34x9oU9\nqwz6bqSVHqSzEt60PSbm0pI7bdJO2oSmRjI02rEuYHWaVVbJMDnI72KWNHu3\nVZg7ZoeEefAU8Bls2b3DyHKn1UoNFPeDYZLqAkwo6Ocd6sTz+4OKg7FKonqu\nUZ87DsGl68KbYaCto4IOhlONp4ioNHpP4cIBk5PhwHA8AlBlDwnovBQ+RRZX\nbfGlTgeV+7es4Ypie0qphRVaxlTZ0T4Kb3aRlZ4JUgWADfG7UIomGBBWJQlX\nBTaqluAsXlozaBtFtWgMbuWYPxtjc21ToqZAkB6eQ1XtzxB4AUEDrxE8GrtR\nVcKEpyjc6eozSNJZ2wPiXV0MkwoCHR/VxsJuz0H7ZdosUEBDhSVPlXo5bdw8\nK3Wys9upx1qC7ScSG/JgRKjOFma5R6jOQYpTZu8QbragGT2OBS/KQwSjLWYT\nQmy6\r\n=CIGP\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"a76acc0aeabfce97b9440d4e7000846a4c4f73ab","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.2","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.2","@balena/jellyfish-logger":"0.0.49","@balena/jellyfish-metrics":"0.0.53","@balena/jellyfish-environment":"^2.2.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.2.5","eslint":"^7.7.0","deplint":"^1.1.2","depcheck":"^1.2.0","shellcheck":"^0.4.4","lint-staged":"^10.2.13","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.0","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.0","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.4-fix-redis-dependency-a76acc0aeabfce97b9440d4e7000846a4c4f73ab_1598751658611_0.9172295286702505","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.4":{"name":"@balena/jellyfish-core","version":"0.0.4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.4","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4f0d3e9a254a7227470a0cde4688d39667a6696a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.4.tgz","fileCount":92,"integrity":"sha512-rz0LPj7rn4asByEeVAfPdUHWrVWRlMx1K3tC/X0bcnkw8yoFzFPcpuitI9ujP7Ey6UEnoyO0g5qbr10+i6a5QA==","signatures":[{"sig":"MEYCIQD/5tqqeI8wCtOznENnHLdQH3F88I75YNqmgXqxL/92YAIhANYOU4KzMFitPxBSMLj1QotMpRj0FdmkL/QK87atXw8h","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":516652,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfSwRQCRA9TVsSAnZWagAAatEP/3EfvxGL/R+/CrFX2vmz\nCg12d1nHSEXz2qunEts4MnkLwjO1rTo/dwSd5Lx4+FKjZJWaNhgvZQVdXIVn\n84cYhY/3/4LClZJBaiSD/hVq8laRFZUpmYcdTOonIbXoYjciVyW9CFetYPtU\no/ZYzZ51YUrUjWkcM582FpwwH0mEuay50N1yBdfqRJrfL5K456NyYhxaTXk7\nHQW2/1itJfZqto4JpG3OVhFJyd7VaI2DEoBHovGgtcL6qtcXl6a6NH8TfWDK\nY3G2VvOTxUZ6SPpnXThaiErBGl/w/hp2gkzN6KF2LOkEihVVuDtzogZPeooT\nyoS3HtZsKvHW6Khpvmr6hXbru2ovf+qLmtcAmflnh8dgvPP8U/WP3mhY5aPd\nTAmujZuKjtgGcNBKlHvh8kLKsizW7HYmgjrT8ulB6/Nh5luXeQvi2EOdKacZ\nQ9yemjx1eAC+uUHbX8rlg3MzUmtnc25KUJfwxts6771QcG0bxZudMRAhxe4S\nBs8QJ954didlupS5w/gHHLfdYAeRWbzs6NWOyb/9jF3INnipgAHWj0dm4Znm\nIVScKs02WP5cZ2DupKGls2RgUfX2nijF3HH65iqDZBD1HBk9RR19aBG8D5m+\nwVZyzkUV6l4nHBza18BUQTL4nlG50uRd+CB4FAlPIc8A3HWrpYOiSJJPsSqB\n5YoC\r\n=3am2\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"090c4b78b0e0ca04d3532950024767efabb79b30","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.2","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.2","@balena/jellyfish-logger":"0.0.49","@balena/jellyfish-metrics":"0.0.53","@balena/jellyfish-environment":"^2.2.9"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.2.5","eslint":"^7.7.0","deplint":"^1.1.2","depcheck":"^1.2.0","shellcheck":"^0.4.4","lint-staged":"^10.2.13","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.0","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.0","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.4_1598751824169_0.3742347243752737","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.5-set-commit-body-56baedd3e49aee5a373ff6bdd7b203b13945b386":{"name":"@balena/jellyfish-core","version":"0.0.5-set-commit-body-56baedd3e49aee5a373ff6bdd7b203b13945b386","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.5-set-commit-body-56baedd3e49aee5a373ff6bdd7b203b13945b386","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"60e911d462a014016f0945ef2d9e8ea1d3c3570a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.5-set-commit-body-56baedd3e49aee5a373ff6bdd7b203b13945b386.tgz","fileCount":92,"integrity":"sha512-ANO+XOEt2lQhXUPRLunXFjXVHwLLiJkCp1XzywA+WybxzuWmLgTY/5OoangjThVpc8fDIQQUA4TxxUn2C+cpFA==","signatures":[{"sig":"MEUCIC8z7B6MdaFqneyoqZ8ZM8uE85A7kwQXi86M1P+GqE6HAiEA64OYr0QNAHgcDH+At4KZGq3bgk0wpk86kSmIY0eL3jQ=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":516826,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfTKAaCRA9TVsSAnZWagAAF9kP/25+inUfSKXwqT20+2Fe\nKSltXdXNHZbtQNhXkziYtbpLuop54k+CZXlp6hFrQW2NeOpaBIRSMAK34Wtx\n8AXv0fxRBYel++J6+Z51RBGSgrufIDARLgxLVSJRP8qBGCaeX/lSU5h2yxeT\nQjY1VhbQCAKNkLsS7bJeDoJVTL/X/Yjff/ZDASG00bo1OsAKzG/TWmzoj8TZ\nFHMq8bqIZirdTHcJtGKeTu67rVIebH8VhOxp1StlxCCjTSSxB3XRW0AC6DPr\n/8mIYVNXvbk9umKBbp/G1+Cd7Ii/s2CfvJ5Gi6XF1APHcmrTukbMOwwd4E7D\n5YDed1cKLl3IqCDxa1vTN1HKBBgR6DqKmyvNTha8gYnlYZSwpHotPdsT0Bh4\nKbNCfFiDHbsj6C3p9m5l/gQUhoZRvsNHAglKWuUHBBjYcSZBdoflYpi3dLIQ\nKU8ht9JrYS1djkjSfqnPWo4VJpneCG9eHwOdElwAJjehQ8CWpjBLEZExBFQR\nVniQmksVjoEkKLuPCISwGiCR98n8LjEJc7X5kXY2CTtPndEy8t6T2VLcTmyw\nk8USptYi+5KuifFVYJAxwMsg7G4z2JpI8BuuC8J6PO6tlI68HxDD2yUTvRqQ\nwb/ekAcjr0VQBSvPHsYGn9I0Zc4c1xoSekN4Hgv+56m6AXM9sa6O00YKemqv\np8pH\r\n=nyDu\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"56baedd3e49aee5a373ff6bdd7b203b13945b386","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.2","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.2","@balena/jellyfish-logger":"0.0.49","@balena/jellyfish-metrics":"0.0.53","@balena/jellyfish-environment":"^2.2.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.2.5","eslint":"^7.7.0","deplint":"^1.1.2","depcheck":"^1.2.0","shellcheck":"^0.4.4","lint-staged":"^10.2.13","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.0","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.0","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.5-set-commit-body-56baedd3e49aee5a373ff6bdd7b203b13945b386_1598857242514_0.5432742538553694","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.5":{"name":"@balena/jellyfish-core","version":"0.0.5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.5","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"72e728834d75389eb146016e8bf0eaf8c27804e6","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.5.tgz","fileCount":92,"integrity":"sha512-JNNpRgRwzBe1GOJkX+bvEY3mAtiONKhNlu+pGOX12+736m2i4xKH4Xx7mELM79fiD1HYbqw9BvkcfNiW6f/uJQ==","signatures":[{"sig":"MEUCIQDLLdHQgCuQCVxGx0tVbFf5xYll9PDpn2zFJt9RgoSaBwIgSkaTZQJcZmCOwbxDQxKCRUc7XRy+11IccTpeaqMPPH4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":516769,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfTKDYCRA9TVsSAnZWagAAIz0P/2VWwOBgagkt2+f3VPpg\nU/n4ybSXDCoWXYSNJI5OjJdtmIz5ZtC/qyEwDshmXVzrQ2ntPKD/qqoCg34J\ne6K/0nmMDu5GvcWhkXj+h6sDFT4lNNBUrlKA9L/J7D0EoACoCyLv+5WFzgS2\nPnJq5lNONTXSYJ/c7gF+CyIL1Jnq8tWCfiNJaf3NXFrM8XF1EeCg+L8ZSK2b\nSpmds395L1fXACXJ+cCK3BZxmh0p73sq5KY+nk0TT4g2fKAL/rIagYEXQJ74\nXQVL5ganR/qfhJnJOVG6tyUc44/43c80hAHy8gBFgoAGatqAMCG4XaHlGonV\nUGfLj3f/3Xo7QuuInwOHvIbxt/nFGcqfxBO3uI512Jn9oJ5/atdXf8ONDcrb\nZ0aJwQmOJsezMbDJ+29bzlDn+VhX4V98kNObcXUBzh+atA0KhlIHj+lRFW1T\nhUv2blpzADfikhbtkHdwat4IbvAqRlyywy4EbRZdX6S8rR2GZaAH/bHJ4/vh\nsdqgnoldERgFA01lhWeReYHLdNE/rCB22Zb3bjzf/a23UGpJ/TSPmpdQKVjC\nU3hDtKbXVwQb0qKYn5loeCs4CrXs0gSIG9n2lvfywmz+ynwGhQh5sDoEM/5D\npVQ5Ip0Vzkfw9z4UUE2nwsY1QbykqLR/6pHiObz0W7H4sANi5rDhq/6kfRJh\nhu93\r\n=BwOj\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"8382d9fcf434220530b2783a830a295ee916df05","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.2","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.2","@balena/jellyfish-logger":"0.0.49","@balena/jellyfish-metrics":"0.0.53","@balena/jellyfish-environment":"^2.2.9"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.2.5","eslint":"^7.7.0","deplint":"^1.1.2","depcheck":"^1.2.0","shellcheck":"^0.4.4","lint-staged":"^10.2.13","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.0","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.0","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.5_1598857432217_0.09137886707784104","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.6-add-jellycheck-43221b07284aa7ec7008df8226b8c5712ca73459":{"name":"@balena/jellyfish-core","version":"0.0.6-add-jellycheck-43221b07284aa7ec7008df8226b8c5712ca73459","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.6-add-jellycheck-43221b07284aa7ec7008df8226b8c5712ca73459","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"39e524a38e6063d95310908e874b0da961279191","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.6-add-jellycheck-43221b07284aa7ec7008df8226b8c5712ca73459.tgz","fileCount":89,"integrity":"sha512-f7/QP5eUSjoaflDU3ws4VnLpL9jSGse8HiTYcJ6stHdd93rWLei2vJB73Aj377nBAlSyWCqRIKfFFb7jCfHcWQ==","signatures":[{"sig":"MEUCIFiYqDjSYH8DFbCAlL2GWdjNdGQ7PIoNE4EJrlPB3WVvAiEAxAnU/C+b9xkUP6gIDKGKXVRrIwz73SCh9pExWO4utDQ=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":513671,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfTgy/CRA9TVsSAnZWagAAUCAP/i6nAWfUJcSLCB+wHN0R\n9FNOYre5iep5Csz0/LE47pOLCmywZy1qif9CQ5tz3dL/sqi4veEWMMbCfXaL\nSgJDnptSlczkQhXysNCUSk98x1Acw07ZjG+pmfui25q5csg6bZwYnlmuLlUA\nroDeyxFwAzdgXKImnAbcDgMQAIPvYOXKwbzpcITIsn3bSY1xt52KEhGR7hZX\nr3UL7Qx/0Qp6e74YQ7kCKrgqmiDMlyF8X0MKa/A+hST7VLNjNl69Ad3V/6pG\nCttamrJ0uX2i6VA/u1DEC6ZIZBAiycLk4soQcbuq4q5zhW0t0OlU9rdRJzRB\n3GjGu0IXRzxF26jBOM+FXIQNEpnj4h9BtjeMxr2lmJCXN8hpkJeNztNuZSRf\nlw5wIgbIk/356fcCi8Gg3sKn3ZEmDM+pKGF9HZ+rEFZyQYf/GceG3HnrPAtJ\nSHjKzQ/PQR28MtBR5j1ZBX7tBLWC99yo4WlNbFY52YqgqCpVKc2XrV/Fh5M8\n7U8ZJYY9TVnE5FgoBpV5C6UJARAX8S7RocUUA1k9rDB1CIX2SNVsNpwXusLn\n0kJJH9s4Ic3NQcygDIymjPoDqG67PwDnjIa9+qJIkmpybyU+GXuXq00m4PUC\nOzmpHdnZYIPMhQzU1TClq9lThLYRmqJNbS4qgzccraInCU+f7+RWxDopdaly\n5MXM\r\n=qS76\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"43221b07284aa7ec7008df8226b8c5712ca73459","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.2","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.2","@balena/jellyfish-logger":"0.0.49","@balena/jellyfish-metrics":"0.0.53","@balena/jellyfish-environment":"^2.2.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.2.5","eslint":"^7.7.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.2.13","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.5","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.0","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.0","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.6-add-jellycheck-43221b07284aa7ec7008df8226b8c5712ca73459_1598950591078_0.7228940596800733","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.6-renovate-non-major-1b3799faef5bb054113ba6a8c4dfd1dda5915796":{"name":"@balena/jellyfish-core","version":"0.0.6-renovate-non-major-1b3799faef5bb054113ba6a8c4dfd1dda5915796","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.6-renovate-non-major-1b3799faef5bb054113ba6a8c4dfd1dda5915796","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"30324c2ca1b36acedeff6f484cdf175617e75a39","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.6-renovate-non-major-1b3799faef5bb054113ba6a8c4dfd1dda5915796.tgz","fileCount":89,"integrity":"sha512-B9LZIJYKNjMKGIvISmZfgNHBiyQC5q2oEG4JS/NDxg0ZW3ZU3fqsG/esKZ7WMExUAow3UwyXEerXqntLcKre2Q==","signatures":[{"sig":"MEQCIFkYs2axfW61Q4mlSsPZP08ifUPlCgNKcBdnxU/E3FBjAiAQAJrLghgIC6jE9laGesbz+AnMcaM33IGMa9tcshmkNg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":513717,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfTh5TCRA9TVsSAnZWagAAvbcP/A873sLBjBi3X2KaA32l\nl+ZbjC9+1+m7AoBccKRKXTtcIgz5Knh2/C2AtTu5HWNtm/shfpulx335T5F/\nR2CkpWMgJtKp2jXYk/bUbCjD5jRwQtqH+D8+z7rIlJIqR4tFEclDsOc2gZqk\ntFAktAMwE5CsD4KhiqBHA0Z2oTCBo2MGdDT/4xS5C3mCdvt/DefH2ZrN/Oxz\n45ioWKNC6ncS0L3jJkdqb8p9DkTj/Lo6FyqG8AbEMaVJUYpttfS2Xhjs4alY\n5NnPYOPscFETyfxvTmwjQPaKaefRaGkZyb5jQHEzqcjDoS2zhWxXR9YmXwAK\np8efLsQdZoNiUPcD6isY29dcGWN8UTM6waob8uxdatrFmsV3L37GHzyD8gGY\nknCPUInG09F99O3mByL2RlDN5FAaxjQ+ZgRpDBYukEmcg1geDPYoY0YPlS6K\n29US+E5Id1lVUl1tnpCwad6BOvB8vJuJf3SXR5r6kxSlGZLGP4UHRgmeuMsX\nPo6+Z5CvXuXuOe2z6rUWsuaI8E+GRWfeDoIyo2y5monZxzcIndeq4MPFThnk\nuxREQKA1zRx/VszPTy+Lc9AoTr73fqPdg+B31d7qXrXZCuVpDzzMweqsFrcW\ntYU164/R4m34DOtj8rLCS/5Z8bzVVLk9ek8DqzvdUv0V+FPgXaq4rf8euPQb\njQsh\r\n=KOOx\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"1b3799faef5bb054113ba6a8c4dfd1dda5915796","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.7","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.6","@balena/jellyfish-logger":"0.0.62","@balena/jellyfish-metrics":"0.0.64","@balena/jellyfish-environment":"^2.2.14"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.2.5","eslint":"^7.8.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.2.13","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.6","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.0","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.1","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.6-renovate-non-major-1b3799faef5bb054113ba6a8c4dfd1dda5915796_1598955090658_0.9790754029934383","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.6":{"name":"@balena/jellyfish-core","version":"0.0.6","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.6","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3725b5e0110a87555e77937da4d8cfb79740c425","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.6.tgz","fileCount":89,"integrity":"sha512-w8tmtR1LSbkihoplXEg4MCK7dsMjGB/wYg9+d7SHo9Bh/wFa/PKSYYDCF2PlNSyVkTuLm/ACqlmPGvk7NcliTQ==","signatures":[{"sig":"MEYCIQDi51PGftoGFslJFdrDEmO/QgfXlX5mQJuuDzDX/1s7rgIhAKy6jNro0/aO2QxaA+pEdVqWvZK9LgAdNL+BcgnYDRqn","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":513657,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfTh7tCRA9TVsSAnZWagAAWkIQAIGSbIMgFBIk/OW5onfS\nWvsAEQTaTJJX6YecBLFqTz+kUUhELEUqNJ3kYbeODkpNiuvxE4M3qfOF+hqI\navVF4nSY6/gcGBMiDiKSF3gLPMDk2NVopBRYybJtJrZ9dpGjAh6tJp346LKo\n1z5eeb39SZCWGN22jpm8YBSAWy5GTpkmRgj2JVfDVE4FaM+mzg5pf1PzNGOW\nscXyVPMoknsZBRVG006+UCt5/YzLLrphr+9tvyooClAm7++W2q6aL7aH+SL7\n3qJ4elmDPwy+sa0ac8uQVsnVPVTM9BnhuUkXTp/Tm3aNrB06eNgHBteBEdgY\nkFEolfa+X3ysUAAlTkqZM97qU+sO0LeaBl/lgMphvd29irzdQ5/w5Eyf9KCk\nEHdGH9Vs3am4M+eYvEiU6a7zdK3xKajPezYVpA/buQq1c6ZAkz1RuQFp+fSa\nvpyJUbWY0VKIZhGjurzCEKbpNmXMRHXpV3BH7tG53rYk9V2vs5GYHt33jAQz\nJCC0enWwEvigPDkpYRP0MOxnjko+WnhUGPm4LRWuKzfy/p1cHifpt4WPT3Y4\nejSqkHgXEwkQwF5yK26/ztLGv0d8t8OKy3knCx5GePwMjiIFE1/WJ+1lQpIe\nElL3Q8OIXQQYIUXsyaOO+fOdQkypIDhd0XpjVqxXfbIsvfNg5SsCk1VHZZvY\nhzFF\r\n=4ssL\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"67827b47907ed75b074865db4ce660b0b38795ba","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.7","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.6","@balena/jellyfish-logger":"0.0.62","@balena/jellyfish-metrics":"0.0.64","@balena/jellyfish-environment":"^2.2.14"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.2.5","eslint":"^7.8.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.2.13","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.6","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.0","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.1","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.6_1598955245314_0.6221419462447344","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.7-renovate-non-major-ad4b8ebc0bc4daea85c88c525ea43da91437b8d6":{"name":"@balena/jellyfish-core","version":"0.0.7-renovate-non-major-ad4b8ebc0bc4daea85c88c525ea43da91437b8d6","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.7-renovate-non-major-ad4b8ebc0bc4daea85c88c525ea43da91437b8d6","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d365e0656bc475e265074fabac04b7ff6b388661","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.7-renovate-non-major-ad4b8ebc0bc4daea85c88c525ea43da91437b8d6.tgz","fileCount":89,"integrity":"sha512-OLHk7TACzmQnG3z/7RpWVshUnk6IscgH8N/i79HpoW5U+5owZi3xPcb3fF6SytBY48IvNreaO+R7ZznA7zkqow==","signatures":[{"sig":"MEQCIHSONeNWZ38NZxeQQUg7UQgtnzOF/6hSQWMTomBjPpqkAiAcM3VQ5vNYX7Z7hrnkIipSDWT8fk5Ac3oqaZuE4wkBug==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":513819,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfTjYBCRA9TVsSAnZWagAA77YP/2beaN13rUakfj5AJbuv\nRuIlqam4Tl3U1xrG7NNL7N3jL0XVgpVUzIVYGIzo3Scv2mCv5tDXAqqTZ8iB\naKNjqcGRtasP78zbftf0vTWDShcvNcE/P4XzWLxxZpKXjED86jYSLwtylV9d\nx0S2x4L0u4wuZUJshQ6pgG1+mftNk7ddrYBKp5XWpxVfQRaDcBHwkT5rhjmP\nm12tYfrXG6CgitQDPcpOLFE9WsEhi7keA+8hX5gqZ0JavmIeDMaD4GNzAH/F\nllGhE1aeKVejSfL3Bwb5P680FHvbkduW8/dW0BaQkyk0zd9x2dyaxej/dyDN\nDs9X1NVCxmRLCnxQ9Rmo2WrEDt8KDLA8uzOMkHGSzl/am02KckE1lBg6p8WL\nOmYW7vR4wiD9Hdm4fuOsW2GC6uSnOOws/o1OS2eT2R//yjWsGR4i3wWCd21g\n7HZZBMj9s5KezryfMvJIb26cNHtbqY/Z3d7imPRkuzNUC8oayuv+aI5qXAzl\nfXZRqXtMSHy6vjwWeZJUaxj2v1mKMQa3mZq3bA15qIBrnC66xSvnnSc9LMoe\nOu/WRmlOFf/mJv2fwQhwoUx6cR/rbUACcnq1Cf6EmfuzAnsyK1D6P/N37cda\n3xZdn51Ff6r72dnVZwN6uzZaR4FkE26Bt5tBlATt7tz2NQE9dGYL3vopB+pa\ndCJX\r\n=52D8\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"ad4b8ebc0bc4daea85c88c525ea43da91437b8d6","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.7","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.6","@balena/jellyfish-logger":"0.0.63","@balena/jellyfish-metrics":"0.0.64","@balena/jellyfish-environment":"^2.2.14"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.2.5","eslint":"^7.8.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.2.13","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.6","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.0","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.1","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.7-renovate-non-major-ad4b8ebc0bc4daea85c88c525ea43da91437b8d6_1598961152902_0.34215643877535196","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.7":{"name":"@balena/jellyfish-core","version":"0.0.7","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.7","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"024c31d37e93ad3196bcc728d9759c31256c2b06","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.7.tgz","fileCount":89,"integrity":"sha512-wQoXwW+Xv+u2LnQS+Hw1uTxtF4LSbUj/DDVw2IVBgoqvmWlNTon7/qaR2v7OoV8ZWy779j8TKKtLHg9nIHJ5dQ==","signatures":[{"sig":"MEQCIH7kMn5pOJinjYiC8W4wqZbyRe1I0htzmqRss0m/W4TeAiBIVkOQMIkRM6MR95pakIARJ6rhECtHJC1Ym9JQoIEp/Q==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":513759,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfTjbrCRA9TVsSAnZWagAAL3gP/1N986Nw1ake/4HZRHk3\nL+Uu/zA5GLF6t3G1yQO9gxaCZtNJHAdp8wMmn+9aOlV5cKRvNCSIxWa/E7mq\nW/peT+8cDqxDB8Y+FNh1uGDoXe8AXZk4BLvs4+R6CxLcauwtS9KOUQglrwSN\nyNCZLTiCJkid3G+030yCJhLtSyIzaKqywHQly0aP0Goq8+V890h2UGxr07K0\nHSKZkehnQNR6QMEAUDrYtDKNRn9gi9PBXrtF1veVlc/RrTwnnx0IoQEqDUqf\nJumMMxpmsUerozzLX9Cm9EfTBbBmklkEJ9QlSEgwyc9JB8pPQSiyOmkq+WwX\npqZAGGwlv6rxNynWerJHNdbnGOxJtNbBRkcXYszYroRyGQ83uw4DHr41zRPk\n3nC/+P/T4YZloYuzhfp1vGJjxfx3Ir51C91omfZLZ/75P9Dtvn3VLKMwXSkV\nLbMMvasFMaXQjcWgs5jgPINmNXRiwyhtdQqii7A0UHZSKICJ3PmUXStnZTF1\nMWRnYxKWqDYeLuzWywa9NOo5mkJBcJZDhFAHRHQ8ZCCX6BosQJfLs/gyZckC\no97Cp5tn/lodYu2QmOnQeC3jqSpMIzKu0j0DFXCXHsqiuv2+JY3sU+65XuG/\nlOaV7oFyRHtXUV4/CtHowJRdA02nuMjZSnvv696qId0KH+oCrKt0W2AstjXy\nbjc8\r\n=vu1m\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"e64b79c7b39b918fcf95c5433ca7a713bbf09423","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.7","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.6","@balena/jellyfish-logger":"0.0.63","@balena/jellyfish-metrics":"0.0.64","@balena/jellyfish-environment":"^2.2.14"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.2.5","eslint":"^7.8.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.2.13","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.6","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.0","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.1","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.7_1598961387286_0.21328251797248332","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.8-renovate-non-major-0a840d11a9574d544e2b8f359fec976a7bd6627e":{"name":"@balena/jellyfish-core","version":"0.0.8-renovate-non-major-0a840d11a9574d544e2b8f359fec976a7bd6627e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.8-renovate-non-major-0a840d11a9574d544e2b8f359fec976a7bd6627e","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1d84f5f3357fa39bdea61cfe8f73fcf5178b49da","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.8-renovate-non-major-0a840d11a9574d544e2b8f359fec976a7bd6627e.tgz","fileCount":89,"integrity":"sha512-yEU2i7Q7qvt18Mfe2bDXjooIoLpYpPHUqabkXFoN2WnaiTXQ1YB6TQsnyivZKOe+kTtw51jtbi06Tep5WLX7rw==","signatures":[{"sig":"MEUCIQDo2Pf2VPd/JU1X3fiGIr0ulRo3kI/QcDCbyd2bX2o7pgIgab3KFOXRiDzo/WbBZwDKFxQKrYIAjA012l0FJoQZjcc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":513884,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfTlG/CRA9TVsSAnZWagAAOykP/1r82aTdqb8cTl+9Z2uN\njLgDtmJPuDNdW1kV1YuHImNu1l2HFcNoxBUawwS3+hO2FLSnWEi6MExcYuXd\n4b6EUStjAXVq5TGpGKJBTV3pbOl58uxPhGXJV7FvYbU3VkZuuGcRXKNCLBxk\nme6aqEynECLkgUUGBpcxQm7MizmeRnfe9GUlcMIK/cEeXjJOVGvFnzcwiLRC\n6gz1+jAvKPsT2I8Eghuf05nMj5o330xDIsvj77s+wzMku55Lf+SCuiS4dY88\nJhdHC5X0LorNv2HHJadB0o8HpkPv6xzNUpO1dzefhBXMYV1dgwy0NgnrtZWg\nW0GQ3e8APjSdgOKTfntgNqDi8EkLN1qCssbKIs+XlwhNRaLuqHytBw4vwAgp\nEWyqqdQKTTLk7Y5GXaxRNeR7gaxGIui3DcYNTJ1l55MqL1ETgY5F7ARz9Rlx\nLvDqHEmbQTRynL/mShRuSO/J7/utHNBwaZ6XtsHla7pNlufjUC8VGEbq7U1r\nEO1NQHS3CSaQ8nIj3iw5sa7Gy9BvREEN5xPdGF9b+AuGlfwEPUYo8Q8eDX91\nww6LKujy1Ztn7ZEYo73zvOQpfsGK2aizZ7jpui0cUWw9irUi2wgsfV7BcXyH\no/d1Fy1LW2G/dVPwXf3htSfyIVaZv6QKLnaDzPlOzWRuxl1yxpj71GyJ3smf\nun+Q\r\n=3Xr/\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"0a840d11a9574d544e2b8f359fec976a7bd6627e","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.7","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.6","@balena/jellyfish-logger":"0.0.63","@balena/jellyfish-metrics":"0.0.64","@balena/jellyfish-environment":"^2.2.15"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.2.5","eslint":"^7.8.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.2.13","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.0","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.1","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.8-renovate-non-major-0a840d11a9574d544e2b8f359fec976a7bd6627e_1598968254931_0.9209049162134786","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.8":{"name":"@balena/jellyfish-core","version":"0.0.8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.8","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"239979bc70f5d7737e211c351144be3415c6240f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.8.tgz","fileCount":89,"integrity":"sha512-FWl+3jfxEsvVODOktjQUXJkILiXi8CyUkrlfIFI4T1urTo765mItFUEoeuoDZ3/wWjYNrcqCgrve8IYg9U+JRw==","signatures":[{"sig":"MEUCIAOpl9n0Z4daXHgMVxels7a08Ean2LP8k68mUrtz0eutAiEA/poQ2VsBL/UExGeSP64yl+/2D8xn/yubqB+iFMmk/IE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":513824,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfTlJpCRA9TVsSAnZWagAAJssP/R+CQL1Am9xEwLSHi7f5\niwAwKH9I226mA4D6EyGMkHmGxFLFtbB6b+kURA8EVG/yxulztrEYcii2PPAz\nz/QefKyWONoTNO2dMalrkRUS83aAMgDtMxMlcEATOw5uU3PGnKI8z8C3VNmK\nS2wayLsTwU4JWMRAnioFnvP9DhyTZnPUUMn6fZVVsPI1XPPkwx7RzdXBZY0M\nHcd95HORvs77htikIx0iIbfsPpIbXpmDFo2hAMNSl5YkdOYeFiFY9YTCjknP\nNPwD2UBvE8cCXuIhMmHys6qMTbe55fpBgrYaiUytThJKyErxtUuIV+1zKovp\nJ6IQnoaYx+QpuSyPJ8DXFaE1GPX6kBQ7lfTqfXSG2UyoUSFFUzF7y6pNnjAv\nRRrDwtJvv11J55gFLu6AaTT8+xHWdW12QG3vXckSMw08a6j2wS2SJXpHTR6V\nvX1zxGsSB8O3vK05BcTjqqn7XJ5QAoMB3ND2nX4CIJ9jrhE2X59RoqzJX3jq\nAaqaQFIc1BLyZkNkrFc16+kX4CMdOmcw48iuI0zULjQZNXR9/0RokZFLZjrC\nV7rVDVH6WZ+XEjb/UqIyQpZOBXXWXHNvwBPO2QIxpHuVrTLaNIJI54aOVYy2\nRypr7pj8KNZm+mU1wyNyXzO7gl1SHz9aVhajuaklv1C71XHHdzYSNJ2yZH74\nOhB1\r\n=7UI8\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"82efa24a64cdb2a491c554cb41c0ee78ffac5f11","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.7","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.6","@balena/jellyfish-logger":"0.0.63","@balena/jellyfish-metrics":"0.0.64","@balena/jellyfish-environment":"^2.2.15"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.2.5","eslint":"^7.8.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.2.13","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.0","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.1","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.8_1598968425178_0.6301142531280091","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.9-renovate-non-major-6a60997e16204e71ee00a5272cbd52eb96017474":{"name":"@balena/jellyfish-core","version":"0.0.9-renovate-non-major-6a60997e16204e71ee00a5272cbd52eb96017474","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.9-renovate-non-major-6a60997e16204e71ee00a5272cbd52eb96017474","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9305f6b48e4bacb72b8bd9b4620110573fcc9e13","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.9-renovate-non-major-6a60997e16204e71ee00a5272cbd52eb96017474.tgz","fileCount":89,"integrity":"sha512-fCQ/aMv4gk/5ZZQpmgqweIuIcwsOXxQnCKv6htA/SXwKpN4zXbhjkeMji7sJqo9+Pra4fMWkonwUo2v04q2eSg==","signatures":[{"sig":"MEUCIQCRcabFB27Y5cfIa6gqMSH5Sl2JlEGHVdBigz8AQLbDyQIgTu0fHqerDGZQ1xAnPZXHqz0tvDDOTL0iGX/9E0jZ69M=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":513986,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfTlOFCRA9TVsSAnZWagAApsAQAJSpaP7EgBZkKq7/OqJJ\nCyD/hFYel1fHn4yrQbxJFruO62JBOaM/zYFWfeT8s7yIoTpCtUo/xWQ/PiK7\nYi4jPPjEzIT3jb1Y+z0Wn6NK3CUY7GIN0HR0dPotGM9iLZE/dgJkZlq5xFjW\nnzE+e7FXAjXatBaiyLr6tp4Ml0Y/EObI5VCPRl7QUGzt/fKfWFbr664J/SWc\nOzcq0euJvt4Xa31txDbSPaQXNzK0nQONU3WwcZjlXo3OipZQ1vR36PYebEzw\n3sQVBQPJu3IsRaJ5430d9qAEropCLbvJhUKEerpQbTn3rYdbnNkFHWyxfH3j\nh0XUrKEFaojk79BNnB0V36NyO1P/5oDVkmPubeBneykWtRb8H/fogwpBEMzY\nVGiuU7WjN5TnWBE/82YgXdhPhQmFA5KIJSF1xCUE9XmqToRTGwo28LWibj+B\nNk93p64PSLeM/OtzyiEZFGwtGEjca69o51gkDsaufvy3W1lMvwr/ep3dMhWK\nq/uaDmbpdD28Aw1hh+9ekImqNhU11ngEbj6CRJdSOvF9kffjWKY+wxDvsO9g\nD2hwb2zHGrodanWRmv9hi0g+QR33Nw3hkK4XhdRQJSNgCr0GFxwaPz9JpQAh\n86UCPA0RNDx6y8qvZcuQP0cA3sPcZxM8QoehTzP5D5p+LI8+xWXV3UULL5KY\nFPrP\r\n=j8CD\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"6a60997e16204e71ee00a5272cbd52eb96017474","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.7","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.6","@balena/jellyfish-logger":"0.0.64","@balena/jellyfish-metrics":"0.0.64","@balena/jellyfish-environment":"^2.2.15"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.2.5","eslint":"^7.8.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.2.13","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.0","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.1","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.9-renovate-non-major-6a60997e16204e71ee00a5272cbd52eb96017474_1598968709422_0.6227910590458947","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.9":{"name":"@balena/jellyfish-core","version":"0.0.9","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.9","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f54e2d29339aa55feb6a63243ebb242818d9e45f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.9.tgz","fileCount":89,"integrity":"sha512-9kioAu9rLv/qT0MvUk1cMMBTTBxp+8YeMvG0bZSbLEb+o52JzdNeAgNjNEv3KcZK5Jvs8sMyBKJg0BEiMGDfng==","signatures":[{"sig":"MEUCIQD1vekp+Wq+L2F6C9euKeAR+OMQY/LVU6zgfB2RVwKM/wIgaP0UlPC9UlfyhOfq6rc85Vevm30LJtFWdbrUfcpdZCM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":513926,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfTlRHCRA9TVsSAnZWagAAXLgQAIjuyJ+I7KJYZTi0lYOS\nJgBnklNDEpZqfdSQkSqR5HfuRMeHqkLae8+wPyPlOV5LgBe2025d7R3rX7IF\nZl5YOLXJAk04kKmU13lPD9SavKfVdnMbyu6sgh3nebhGcegz9y3OK3Ka3vsk\n5EQYk8YTd3/FyleUlXKTy+QHiWyX2322vxkuEF/uB8XstrvcthxNeD4i821a\nqW9o9dIapN9Bq3l4J0J/4uUXgMrPxcLtT6n9LmTYfubbgETyqmQMKKBmuaNY\nYxtBKBabSApQfSz7tOOvEpFbLVYAmbPLiUHYKpjJv/bMk1Ru/7W+HOw4luTL\nXvSA31fu4IBJUrjEy+iVXKAQpvezO0ZbP/udemNPI5EANeeeuqCGjo9Rr8Sv\nmlUo8knErvCW/rkEev+Qfl5j4gC1ThEPjbc2jTJTOVuzxt637A6+TlWwrT3E\nu/XgG8PxIOrDUSpcimLGxQwTQp2xfuoMjjjU2uNb+r3sJrzYJSlU0F6Df2/T\nPmPf3k0JspdEHjUSfhWiizZbTBxqr0EVTC0ZxrK6reI6B6I7263lFKReSWhq\ngBcQasUSBj6fYPjGTiU3PXl7NNetAYdE2UNp7Z+wxeDKRJduFhqNKHesX7jh\nYWZ1fcd8BmDnZwl11kQGBVsfnRE4VHYGexXGvJw3SLJHXXFNN8Dt8Ted11UR\n7Nxj\r\n=S8W9\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"6d67ec238dda4d66197c1661a43a21debcd32e8c","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.7","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.6","@balena/jellyfish-logger":"0.0.64","@balena/jellyfish-metrics":"0.0.64","@balena/jellyfish-environment":"^2.2.15"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.2.5","eslint":"^7.8.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.2.13","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.0","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.1","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.9_1598968902829_0.3904531123775854","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.10-renovate-non-major-6dfb53d1f613f403274f16e8b988ad00391dcedd":{"name":"@balena/jellyfish-core","version":"0.0.10-renovate-non-major-6dfb53d1f613f403274f16e8b988ad00391dcedd","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.10-renovate-non-major-6dfb53d1f613f403274f16e8b988ad00391dcedd","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"52d294e0752dd49349983c956cc8faeb94f5156e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.10-renovate-non-major-6dfb53d1f613f403274f16e8b988ad00391dcedd.tgz","fileCount":89,"integrity":"sha512-kpHGs5c+dP0kZ4+QnKLQts8oiLqphxZ2Q8h/awYvdtAmwt2g68PQXgWiTpKfYrHZRxJwBf1iVQTI15M4edW0HQ==","signatures":[{"sig":"MEYCIQCgkO+NkGvhSvNB9yU4v7n4/ereYnmyoCwP9+ZRZUZaQgIhAKOP1SniKDSptYr//vNPt9qoWJR+HilGousKK9onIWmX","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":514053,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfTq2+CRA9TVsSAnZWagAAeVkP/2BBTcNoWeIQepYJFLy0\nOVVz90HlOHxVoo7sPB6Dx8gYkVqC5hEJN7V5JHPj/9CjCYSp0Q8ofpVJHYay\nVnvtVB9xZ6/tFktJNdtwz5Ftp/KB5oY7iOXXzCcO4UDdBEIIfZG2mmeKeMwk\nK4KbZ/tL1SHs4Ds4Zx6l0ShrBuS0vbOhtzFE7WtvFQQpey6mkREld9KWFYCt\n/wKHg3Ay86YGfXeuLFChe/a9xuOxT9oP5sSFAJE8UfeTwAlR6o0b9QR+R6Wx\nzb70pWrdXDnLYIttDE1m4UiEhgxHD984jIlH7qp4jj+G0Sa+HM+dBaymfmhC\nEmNlvTHuEhy0CXGVUKECYXwi9LaX8L1Jcvkneq+O08WMbro8gKjMjKZaa7rS\n1W6I9yQUzLAyZPU3cf3yoG0VMt/Py786XWqs61krPwBuLiJKt/pkuwdu2SXE\nhHX1OKif2ui90oMXKB3L4pwdZRAqN32hrxq3coGc6ulsmAqsaGVFB188gpIF\nYl3whRGbQTt7BHUSgX7iEfXPQgUIuM0UYVS+NNXqqmmw0JoJJO2LflB83ycE\n/WiRRP0tPf+jhsScMGK3Z7E0eqHV7oynzaUmwZuuUPYjOEjmkgolYkjXBCUO\nP5/DN8a2NUJ51XuH3QLXeyf5jrr3YCc5xxx2h23JjkFo9d+Vp5LeUfmkcBhD\nDdzf\r\n=FiyN\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"6dfb53d1f613f403274f16e8b988ad00391dcedd","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.7","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.6","@balena/jellyfish-logger":"0.0.64","@balena/jellyfish-metrics":"0.0.65","@balena/jellyfish-environment":"^2.2.15"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.2.5","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.2.13","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.0","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.1","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.10-renovate-non-major-6dfb53d1f613f403274f16e8b988ad00391dcedd_1598991805399_0.5028703717562599","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.10":{"name":"@balena/jellyfish-core","version":"0.0.10","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.10","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"47f38b3daa1f8a2eddab01fd8cad8497895f22d5","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.10.tgz","fileCount":89,"integrity":"sha512-VNEl+noukKmH6R1x8iuncKPiZEzeLeNOILDoDWSy1P3paFIMw1FfDj7Np6Vwr+xVmAzovFj3LJd+AheHArk3Tg==","signatures":[{"sig":"MEYCIQC3Jk87nCGXsqG+3r6G8vzkju+gln81HdbPaYeifERbwQIhAIDgLSE5QA6phOhX5BGanakL3SSukDctZdLwRkIxVM9Q","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":513993,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfTq5nCRA9TVsSAnZWagAADhsP/3zY40CIlPDNlGwFXMLt\nadgZLaAxLnKwmXe7jsMs+wLAYchIZ247SoS/1BD2/SgoAHA1LhPDRf1dcuUV\nMJiraUGls/9/TJhHjWditUeKMILi0Msi+Ve+kw6RSjQ7nR24OTAzIi1tUKx2\nQcA2kQNL+4M1X9r31EwYIC+tl6EYt0CzQmaCqYvAqccb3CKWcdbJFIlqDrZ8\nD391rn3/MLItUQ7lpC3UUvszy14jnHLJokpUVe+RLnEEeof0W+DE88yF2uXu\nVBO4IQxdmUYx4nmgsD30HO0rM5faXubzzmfMiolGTXGeroSbZ5GfZL/VhEeS\nFHUkA1UrWw1mxueh7aW2zwqpay2rPjQAOp+/9C1vwK5GI4WOhdOGG2xAueKD\n61EF4UXm4Gf1+KsVdt3NXUAdnmypsN2SLtVRDhqpw2cxZa6RpYun/WMnOz6z\nz3FGBNe0RXV7exUc1PknXih+ySCCaGCgpdP2BKY0lHoVChde5fprfA5uACjO\n5vFRuG5c40zjCeC0iR5+2TPY6oIhYVx7L+GU05cJh3FBCKlpEe+ZxN8LWh2A\nUM1aH8NaTmWatHdB14gOU2mc8gbEZ72Jnk4RXx3l6LWbYA6qfUuwBKewYmLH\nta4MtKU1Yv1lOSpr3vIMrTGDQ0HIEpk/VUxpKIj+nXT3wsbrntl9RV4f8q8q\nVKz6\r\n=HiJr\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"83f3f126e334944a4c1e7e49b18a6efae022225d","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.7","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.6","@balena/jellyfish-logger":"0.0.64","@balena/jellyfish-metrics":"0.0.65","@balena/jellyfish-environment":"^2.2.15"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.2.5","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.2.13","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.0","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.1","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.10_1598991975405_0.3099357778000218","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.11-renovate-non-major-8b0d6f20c440a7d91ed8aab51b86bb51a8428197":{"name":"@balena/jellyfish-core","version":"0.0.11-renovate-non-major-8b0d6f20c440a7d91ed8aab51b86bb51a8428197","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.11-renovate-non-major-8b0d6f20c440a7d91ed8aab51b86bb51a8428197","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"865241e669b8fdcf2daca7c041a222c59a7f022b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.11-renovate-non-major-8b0d6f20c440a7d91ed8aab51b86bb51a8428197.tgz","fileCount":89,"integrity":"sha512-+SQZbJk6ku30PDAqR1pZx8mVo7kvibZrukh/1DiE88m2F++3v3lkjrPrNk8hdGUNuGG54mo7bo6nPg/47i9tlQ==","signatures":[{"sig":"MEUCIQC2YqPZY5wOlUMLMxTBxwyhJoJAw76VaHYjTdECNMYhhgIgAgikaTtsa0c6vPvKd6FnGYFcWGapcBkuKfuLPY4XvwY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":514119,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfTsQjCRA9TVsSAnZWagAAvP0P/1BdizXzYGrk4EtyPm5U\nykEp7txxKB91Rkhwk5SF5AE7a10YiyULHzlyi6QUR2G0AgGjaPY9VHSq1tFq\nyjE0MNo3d9BgVzXI9NChuCJvfSl7z4g6UFu7zbs4JKFsPk06gAQjQSS4aVlX\nEkPslgqL5Wydx8e+/AM3oXX3Tuq689cry/GVxap7IcRsHTkm5EZuwFKAFIJU\n85VuP0yzLCsCHRJs1ukoxyACrfi715Wdot1B3XFhE8dgRKhXZSlSsHsSpPFL\nVct3EPhr6VOFx6XYqYjQ0hA0ZvQ8pwd2UNU1SDpdyW+nyJg4HejLM0gdmuuQ\nqXyT2VhwurbdUt/g1+flfJT0Tm8yLqYjcwPkgn+b13dzwdzhcPo9AP6Rlkuc\nXhn3A7pAdQwMOYqeu5XUZ1lwh/06kZ+W7jQV+xR9rRoAB+dEqQLA9xYdk474\nTjcuwtEXyv3GSgl3TSnKCvwoK2NmxOkNZ1hJAc+2SOb1EATpZFVz/GKp7qu3\nr/xcLN57BLATGcJn55aNgQcq5/KJKrEEheBFiMvdPmPjr3G5ZDEvPKsdME4J\nkshSCdWN0pYY2oMYdIud8x8JIIkKh/2M7m0Zab8KyErauvW1pEZPTHHW3mYR\nMichGdj9uN5IGtJDNI3g0ldgne4veVl/ywsicG7HZX5dQPQk07bhnkVil734\ninN4\r\n=yZCt\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"8b0d6f20c440a7d91ed8aab51b86bb51a8428197","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.7","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.6","@balena/jellyfish-logger":"0.0.65","@balena/jellyfish-metrics":"0.0.66","@balena/jellyfish-environment":"^2.2.16"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.2.5","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.2.13","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.0","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.2","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.11-renovate-non-major-8b0d6f20c440a7d91ed8aab51b86bb51a8428197_1598997538967_0.6007796176977496","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.11":{"name":"@balena/jellyfish-core","version":"0.0.11","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.11","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f772db0feff1530de41581997d74a18d8b366189","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.11.tgz","fileCount":89,"integrity":"sha512-zJpTdD1F91+AFYIWecxAz57c0KGb/gH5AzB6h07Q6o+fJn9kVtcehK5FZO1QGROPWkANjzjVXQAmzZrt2vlpzQ==","signatures":[{"sig":"MEUCICMVeUGnZJoycolZntllyWlT9DI3rNMQ5rbBhyg8Hl+IAiEAkW+33JCiqg7abF7/KcEv5LPF/6NhW6R8RDXi+V4Usz4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":514059,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfTsTKCRA9TVsSAnZWagAA+HcP/1JoosJAvNrjFf34FMOU\ndZvnyDRsAlF1MD4Vsu3X/oHhN92UiqJEmxm5T880hgdBTaJ4LwUxmymgT1Iq\nuBdpwpzsLvbUo0xpX9W14e7qF0dyHQNctElm3rEf8w4e3Yx9z6Q13Opq5Ugj\nTpE75BZs4zsHJxGzIRDD92gZ8nOu3IxkVAvRvbH9E2sVBtVs0HpbCXIrIAJ+\n6LLWDs9CSs1VGDOgqZMe44sZ55UwZbM72pEELnuIlLB/kUv4Nn2HKbb2czCn\nwoOcOe5k2C02LWji8BsSMcW5jOC7CkOWfkSacLiyFjiRfoiPCIA+U7xR86HW\nqr9JrJNEIH5lxwhyvosDSuyl2Bcb3Rzq31FVhxIi9ZwM44HQs5+c56Xz85H5\nK4u9zm4gtRIeaC0etUXM1Ijm5ukg95fbu724Dwea51wzzYRHYnQRkmnblLGj\ng7HfzasUtvSTK/2G2m0c1vbdscbu+h33DrPravYozwf7JO0RJnfqo5waHbgW\nuXFPFfO/CmCCa+A1brgJ2Ws/2ddCqDWiehL7HYZxVJaKoWV+Up2ofXZ/6SLA\nmr060cusbuWH8rnqm1cLKyRLsgQTBK3tfn7pv6LvRIIO70o6k2Hc24kTTtl3\n/6/cj3ACeqA8BrgkGJ72BbOMvkms+D8DI0A64a12y22QRAf97X/ef6yHZHoX\nFOCf\r\n=rXKa\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"09c2ed15e00c7991c35c129331e9f71c1ac193fb","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.7","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.6","@balena/jellyfish-logger":"0.0.65","@balena/jellyfish-metrics":"0.0.66","@balena/jellyfish-environment":"^2.2.16"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.2.5","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.2.13","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.0","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.2","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.11_1598997705810_0.8844917140158952","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.12-renovate-non-major-e81ee1092a5bd250015401b029716442ec40e7ee":{"name":"@balena/jellyfish-core","version":"0.0.12-renovate-non-major-e81ee1092a5bd250015401b029716442ec40e7ee","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.12-renovate-non-major-e81ee1092a5bd250015401b029716442ec40e7ee","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9fd7ab53d15ed7f24fc3712c59b105337c5bbf64","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.12-renovate-non-major-e81ee1092a5bd250015401b029716442ec40e7ee.tgz","fileCount":89,"integrity":"sha512-kN8evFNQ2V0oVMNL+nHHBEMWbHOxOc1ORlOOim3u2UEwwllZBc4T9yP6G/LZaEnJ8kWEy7nZJMp10AQRu0kUvw==","signatures":[{"sig":"MEQCIB/u/MPd1bLid7ZxzbCcxkSKaYfljmHwsyPQVAOIC1jZAiB8Wq0uM5NWA/XyiyUp8goSVwV1zZBekUz8WHlQMsWIow==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":514222,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfTtarCRA9TVsSAnZWagAAcn4P/iWcIETjAhI0dl9AjaxX\nX1Fc96+zsSjHI+/vrhOd/BnCjAEDpBDpPpOy8pV5c8QHT5IbKjbLh8leQDbl\nse7hlCopgmpWzoNu6kL6GQl+ZVADpkzF8ljW6obFgFyCYD5WfNa2f6GywFY5\n68cjWzxjKvVca1KumFosdA2vJYC8mEI+osXIdWF3zjpOBSEiwzUWGkNs7J67\nUHJ1egnmfgamyNYAG59BVIjjCcYMx/TF6Uc3HBrBuMLgTn564jI+qMcn7w+o\n0veE7B1RovOfh3vG6Fgn8Sbu7iLqjTAYmrDwF5I2RCw2SNZEOpWD2pTG5Qn4\nB0DC2jaWI3WONRNrbGndHO1VOlyF22yanh7LmRgzkZU4l81yIv9bjvROnjqI\nxNENw4hc2c9mLtZ+3BsRRvGQ2dMoKDE9QG9ivztdzTFqwf6oV0MTtvNd4sje\n4thqxn5Pa/DI4CTqfpDwhd+r3TW/sMYlP5f0Y6hwpjxUrFdSOq/VABhFBdoD\nh6xU0XpGGsATJNMk9siOmk4PJFkyEndbUJCQnjXzQg07ZwqpANo+4kLcX10c\neYNBtFakrHzChzOGR8eSk4RvmmD3ccmjVpe/iaDmO0asTAfvOmiJpPVoXtTb\nGblJ3T0C/9aiLLCW7wThf8Dyh2n6/GptfyV3znFMvllXStnPrS8n0Hp8xRSb\ncDXK\r\n=vavx\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"e81ee1092a5bd250015401b029716442ec40e7ee","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.7","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.6","@balena/jellyfish-logger":"0.0.66","@balena/jellyfish-metrics":"0.0.66","@balena/jellyfish-environment":"^2.2.16"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.2.5","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.2.13","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.0","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.2","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.12-renovate-non-major-e81ee1092a5bd250015401b029716442ec40e7ee_1599002283247_0.6485886417315703","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.12":{"name":"@balena/jellyfish-core","version":"0.0.12","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.12","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a4ee2c7038a465fff430e362f15fffae71e9eee1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.12.tgz","fileCount":89,"integrity":"sha512-Tc5C8yxu2nB7PhhOyWN1Ems71kizm2vm5djfcaHkJeY5huthBXh3gmhzZrqF6huJ+5smErS+AdwKHs3+Ajffig==","signatures":[{"sig":"MEQCIHvFK5ymDkLsJJxSnrW2ZR8QJF4kov9YgfVRACF3bg7EAiAu5PHFucg9lYboF6Pid+4CddroBVlLampYBafv5s/kWw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":514162,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfTtdZCRA9TVsSAnZWagAA65gP/iLGyM5lOu5IZbbFX927\noAK9/gSYdQaNNxSX+TgqaKor61mOPwEHezsMuMVxGcLrF2H6Stk7SGJmSc/b\nUkIEVxqM8xGItpJCkYN9QKn5erERq2ITj7x7R2MpyreRBvNVaODoTwtCsDzF\nErOkXYFKE0edrASiaNxj5qoTvm5AhUkkP23Vkh4TzldZxqcDROwG5CcfjvUs\nVxCEXe8IKgac20VEbQv30S3T32byrNmkC8NnnwCTZ9bQN4OCf/xqgdFYWXob\nrn4FHMyJUvfIZJACvwqJzSaWrtaf+jwqPjy9LajEIuZow+NxvftGg8FI9ZTN\nqoeETjzT2q4cRyT+nkgNO6w7YjFuSVjNwFhZSuUnVeUDXXU4UCB7OEJ37Oqp\nto1njvTEpYmqJ3Wf25MvcRQQpkhPrWbBTTQZD5AwxY82XoJEftNLr8DfjOw9\nDsBr5djzsdAwEz+PsRiGIkw3dgeWhtOGFERrhzoEpmFdApTdDfza/hpDD3rf\n4O6xxFoaT5PmHTSPhyDHzmDkioQgWverLoXyW0JFk1RLvtfxDGpoIiMXAG6Q\nD0pG5QZHeuP/+bjtS4+zi281Z/xJp6XSBQ8wBwj7E1VqP9T1nrTLJ2v5oVjs\nalOjyP7/k6RtjDt3kqyKtxk87PhWVCd9wuVccDtN3FEt46z8DysbQ3howWYQ\nyQz/\r\n=aQXg\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"eded19b88a32e4a452c16951bcc7a11946b47195","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.7","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.6","@balena/jellyfish-logger":"0.0.66","@balena/jellyfish-metrics":"0.0.66","@balena/jellyfish-environment":"^2.2.16"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.2.5","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.2.13","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.0","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.2","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.12_1599002457297_0.3273570944691906","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.13-renovate-non-major-9c857d267f90a66ccb25dc837f2a3f5f92849882":{"name":"@balena/jellyfish-core","version":"0.0.13-renovate-non-major-9c857d267f90a66ccb25dc837f2a3f5f92849882","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.13-renovate-non-major-9c857d267f90a66ccb25dc837f2a3f5f92849882","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"00f6a47301bf4b4a4ee8a473d17be34afb4be716","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.13-renovate-non-major-9c857d267f90a66ccb25dc837f2a3f5f92849882.tgz","fileCount":89,"integrity":"sha512-4BeAKViWInS3DlWe1O/zEQozVij8JpwPMWYToGELfAkZXrR5EypL4SvIrOi9PzSxYXfrfGsupd55jSlTtfWdCg==","signatures":[{"sig":"MEQCICpRZIAiVkPhzByYUt4N+tEO5Gg2n1asD5zQ6r/7fP4GAiB9Lb5b+vqq2AFtmdpc0jz4m/66BkpkwhltcswAiBId1Q==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":514288,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfTuesCRA9TVsSAnZWagAAlfIP+wXlnegQhh0hGXEtPz9k\n1EqQyPztqGjnCt7vIqpTF2/1T/O6hw1i6GAySJr+PJVUozHLmHZh0Bt5gh4f\nAO3mV2YZc5fhAkqwngite+gN8/3k7h0bKGBEaE5XzholFGJ32b9qpLqhyBxa\nVJXE6wO8ArspPEwnq7cRvJ72ALB4uqgdvCjAsWzQMbgzzvLKmp0oVQJ64eXb\nEvM0ocGuMTcYa3T1Pu7L90n1dfgV8CoFRH+93YGzvMGSvb0dAXedEgy3/mty\nFF2woNIhRZXWU5PtndnXjRRXfjKkPeK4jZQ2w7zTfPOEQtnJDMe6ECJrGvKd\noAwmWxzaPZggGHCFvcafrP2geWJS+Vyprrzagt84afbDJOH1D8zpm0SWpWJ1\nPorO2sL/F+xOA83zG9zhjPqYE12/V3grUGmVT86iVEWKTCjjI4hvckn0liY8\nrjrYv5G+LvO/cI02t84dMO+y/RvJVj2yDn0ze26+m/g+BgcDHtVooXYLdZNJ\noRH7acgJHPbjnks/b+SoSRzfhEAMbeEUJunjzaRuI17r4q164KGJ5CwUbd6v\n5dc2qvh1Xkw29vAQdpwLDmQ0qsWGI6cJyIVyjMeg2NNO1r+TCcNUuqNRgZUk\nTy9/eQFU+LgP+ZjTAFDRbB11b65+K+lqMm85Kwo9uIaY8djut1y2kUtduj8m\nJTGe\r\n=kRh1\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"9c857d267f90a66ccb25dc837f2a3f5f92849882","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.7","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.6","@balena/jellyfish-logger":"0.0.66","@balena/jellyfish-metrics":"0.0.67","@balena/jellyfish-environment":"^2.2.16"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.2.5","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.2.13","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.0","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.3","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.13-renovate-non-major-9c857d267f90a66ccb25dc837f2a3f5f92849882_1599006636221_0.40423602921405033","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.13":{"name":"@balena/jellyfish-core","version":"0.0.13","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.13","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"fb71d2ee1411ee1f2c63a46f59ce80cfbb1fa12b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.13.tgz","fileCount":89,"integrity":"sha512-GluYGjvjmYeea+O1uDui79JNFuPK1+aTHiDp1GmmCrJgqjXolId5R2SIoYxRpez0CWWR9QTOJgGBSQUlralprw==","signatures":[{"sig":"MEUCIQCkg2WwHbQRIiI2x7hLtCFiKAPtnTYyDBntPCUQTF8nrAIgVY3AB3C3ZGv4NfGUT3BpotR1qBThC91pebI1nMjPioA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":514228,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfTuhuCRA9TVsSAnZWagAAGq8P+gJkwxAe+ZVsl7hXKb2L\nbVASZOPjm7bkDWUk1xrgBg51aEFf0AhAOAuB4Ba1PwCy8gGpZgZRnw/jXcU6\nuuGIsRdCCOyQSsdA2099SVJNuL0yJbebzSlRN00wnwPgpviMWV4HXI1rgGtS\nnry0J2d+uYzUPSNB5h/guoVlbUAcWcBOZX/jRQTkw39xhBnC+vL7HhWNntsI\nMfXLI+L31Ta/HGZ79DD/PNlkrQS6rc2e9UgMktVdyVPI7wGnpIKqTmR+MOqm\nmTokbKPp9fk2CayuvzEy0+OqbEipFLaoplFJm2/DPMEGlp2CbMiAR7eQSRLd\nppHb2EQCW+PWa29zed5CdwXQ5tlsLPJY9rLLuR4S8WdtPW9QaKty/LlFPybh\nO85H2iYMavoopV7XVJ9PN3Pos1IJDzRhCH30VsqXhRKK9RsQIt+k79nlPn8P\nlcWVxzjZg0e2gRYrDj0JQqn2onRx4NQBYMtk/cfqoibOK3Ibyhm+gxYQIQbm\nhitOs842qip3cd7/g7ieUyybMjGAKz4VoGLZlOzFBNF/n/nWufB2jEZIARDS\nByMSR9a8j/yKfAJUyRg5v1Pn7XKNvvjXNFbta4iZP/rOSA+xrqGFFI9HXfOB\nfIp6LDLGGRXY19KRfsJJUBap19ANVm1v5zGr+bBLouZhFm9O4mxrrE4Zd4ns\nVz0J\r\n=74dM\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"169715b67c8962a2d5c9eace65b2599bf5cfe757","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.7","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.6","@balena/jellyfish-logger":"0.0.66","@balena/jellyfish-metrics":"0.0.67","@balena/jellyfish-environment":"^2.2.16"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.2.5","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.2.13","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.0","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.3","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.13_1599006830456_0.9635823176731615","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.14-renovate-non-major-7ef385285db8bc28de10ef31a75fca29fcad741e":{"name":"@balena/jellyfish-core","version":"0.0.14-renovate-non-major-7ef385285db8bc28de10ef31a75fca29fcad741e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.14-renovate-non-major-7ef385285db8bc28de10ef31a75fca29fcad741e","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3395f3633d0122ac6331d1c0e7f07f67e5e12ec8","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.14-renovate-non-major-7ef385285db8bc28de10ef31a75fca29fcad741e.tgz","fileCount":89,"integrity":"sha512-uI18724tN88lI3nljPMgHIn8YEamyGnChM8q0f6lDo9nY+TzeIKadpT5sMfZ7o6WNkSV+D3nU9huODOGOB8ClA==","signatures":[{"sig":"MEYCIQCEXlEQdS1+gm0UR2Og/U1MNVcJrpL1xM98R6rfanYL/wIhAJwU57ifAixHsgc4sBUu0jBAmdNS0O2Emu2pOIx2qrMW","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":514386,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfTx8cCRA9TVsSAnZWagAAd3sQAIXnpDEDyjfm3jX68cyK\nHi48zC/qJZMyPyONIehk6GCgkazogPP4t08WSSKXPmjSBWpNrFJ2bpBdCxPW\n3kDK8xMqkZUqN7XdgnoG7zXZkAH0uP8PfLczEySloSeI16mXwiJDhZNKYkTI\n5iAL3LJ2SkKRb71gNFV4iD68YZXAjRoINbbML2kKxlSWBU/Y6tc+foC9KD3h\nF4QlWI2+sEu5S3DkVkOtyLdMlcYoEQYzCJqugZ1RX19KAs4xXHit8jRogLJ0\nLYbA1kn1Yw/58xp4MWYF1yLrMmtFj1sVjw2RdZkvEtjEeFn/ioM8QhIGTHMu\nnvVcynLM4NFINTwx4NxkAQVI2WTOyuhrG7nLX4MK0wCYxBqdQfG0EjkNrCX3\npSFwxC8jjJUssvdAlCbP4chZPrSrn/sKTwxwC13mDkdgwEJyPtP4pQPjidhj\nUfxUPK0wFxLridgmF0aL5CyRbixz2cLG411WYdv60LaID7u+AvY1ivZ6wHT/\nZzx61LCA5E4NcGqLQHnmhvhtO3OOKncBaNqC6vENq43m2IgQuMiFXRaXFsUM\nOktMHBlOt82+QrAKm9UpbiZxE3Il1sWurgKJOxHSWmE0/saR3RyAALxEqYDA\nV1MlofRd+M9k4lj5vT5zlDpuioIC46Gl2VxbpP/px4hXeoowkK3qA+PYWu5h\nBUN0\r\n=Jh1U\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"7ef385285db8bc28de10ef31a75fca29fcad741e","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.7","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.6","@balena/jellyfish-logger":"0.0.66","@balena/jellyfish-metrics":"0.0.67","@balena/jellyfish-environment":"^2.2.16"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.2.5","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.2.13","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.3","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.14-renovate-non-major-7ef385285db8bc28de10ef31a75fca29fcad741e_1599020827886_0.9883911980614222","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.14":{"name":"@balena/jellyfish-core","version":"0.0.14","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.14","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8db116888126b720546b3f90e29b620e97527950","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.14.tgz","fileCount":89,"integrity":"sha512-4BbCY19IGD9faBOkkwLHyvD2b8R8D5tnWIfvZFrCK1wHYER7Vg8czZE9KkkmGy0SLPlb2Wr0XfQwCC9+nxPXtg==","signatures":[{"sig":"MEQCIB/ijVUvsSLluBd5XvN6zm4Fn16QH0QUzmmL/5XwKQtzAiAxu18VrDWC1kVLwZo9GUYLUcbwpqwG8gpeINvCkELnwQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":514326,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfTyBmCRA9TVsSAnZWagAA7j4P/itFCYuiz5vVOKhDL2gL\nloMNZo++/Z31AMQkwFwVga+QHrAOqbmqt8ysRDB+R9sXgPUbpcWYIt2/wQBH\nz2W1vFbV/+UAswIRUoSKxR5Px7ZkG+WuMqPvWxxsHP/IetUNXsWfILKAaf3f\n7g+qPrMA0bBsmnWaPEqIDy4aPemxauOhdcLDp2x4qOAF5qQ5Gp1Y61jbfMhT\nHqVgaTL/LfAVCxWo+i2e5CN6aCv1L+mSKP3HVFffdmB54MvDOVi61zyalh7p\nefIIm7wdPwTDcShdUI+jiU6oNB8qf7pFrelFbnY5SBBkw32iS9Bt2UxFIPNg\n/fcMS8kI7Y9A/260hgP8AdAKFk8wh5esrZ4vI/iYEJ7gJGLedTKAyCvX/QlK\nYZdrf0sH2cpjDktLHad1NyvrdEh8pvu6Xo3+fSGAXHiseV8O3EyxhTsVBISo\npnETGcCEANsA0nZ/QJnCWm5G7y02H7+CDNhiXoHA1E5Xq3wTrFa66NX6owRs\n4LpbBjjAJQJlgpadJv5PdR3iHDF/2eyTreVEmiLuEeOQNdPpYEyporY6swN/\nQe/p0rvwJ1SDHEUftXOhiF8KVH1FE38oVSD1vZkApg7cs7kti33IQmhjX+yC\nVkfRy/x+IIqAMOLBuUGSPKQ8pmWh/9Dlk/5sr54tFDMQpcavg83F7yh1cldP\naPWU\r\n=fpxY\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"fe4b23acbd1925067f4ec787c18a2b4e2b199ee3","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.7","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.6","@balena/jellyfish-logger":"0.0.66","@balena/jellyfish-metrics":"0.0.67","@balena/jellyfish-environment":"^2.2.16"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.2.5","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.2.13","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.3","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.14_1599021158051_0.6704147299954604","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.15-renovate-non-major-dd3935d5bc985f26507f941f06138fd741a51db1":{"name":"@balena/jellyfish-core","version":"0.0.15-renovate-non-major-dd3935d5bc985f26507f941f06138fd741a51db1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.15-renovate-non-major-dd3935d5bc985f26507f941f06138fd741a51db1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"41e44c583682efbfe250b680199c742c7cbce969","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.15-renovate-non-major-dd3935d5bc985f26507f941f06138fd741a51db1.tgz","fileCount":89,"integrity":"sha512-2UNIGr7WWpOF9ziCS0FnC17qpBrR4rlKtbFkHi4qvzj7FPoaSqk5cVS7uF0n/Z8s+7y/CNZgsXmHtQ3ZcW2K4w==","signatures":[{"sig":"MEUCIQCN8PCP0Z3dLx4ki2cur4BGLQyOaS2uROSpKoI+wO0twAIgTiQ8s08vyuzFWi+hP9IgYD4uSuokKEnkilEmKsRROd4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":514452,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfTyGHCRA9TVsSAnZWagAAK74P/RjH0rOpMpquWp1HWs54\nA4GUoL+Gct3IuCkPIegN/Q2a8vfqCCI/afDsL3Xm30Nv5kd7Sd6MPEJqe2mq\nc6RheL9wD4e0RBX1QvJ4HkasTNLFxA9RsE7tj1SVim6TDMFK+6i2KktFvyKB\nIa2zoYGGTJpE0YXM+vpHe/NltnzXp8/lwtpI1NfUuqu4Zs5JCvL93IAqJvv/\n81rG75XjmBDdFfDSZoauJ2Z61ZrOAv5k8VaugT4/dW1gthmOM/k4K5m5Db3K\n2xqsSBb7OfeOC6GlfQnTE79yjTbim2qynA/ai7z7YyqkXNLOIjdRO8HwrfAd\nddRgLGdyODzj78wXIySEBmqFbjgfvYN3QepNG1pRMkZvCy6L5pQ42wf5B3Ud\ntSTfN+kc+agwwhCt0sigHhd5/z0N6RkNOur6Xz6sZn9kz1/qy98P/E4OLHVg\nPbdrHG7udJNRjwqBeNr4kC88ZlqqWodjophu225XZzxjOCEz01Nav5PBqAby\nJK2y2eSn9DkVK2S4ajzAnVl0zYLWl8mGKv12uvRbTXVuv0+QkUHZBNnh3CtX\nJQayKJrxcQImJ0hIHlbRQJPRrn0WfUxlU17UM6BT3uYIqLel/TfAnsv78Ehj\n+uNuSILJUBO9qrGeQf8Rki7M8sj/RArdsBZCvKwG8v85AKCA1YRNp0xsPsmX\n1O/h\r\n=Xiog\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"dd3935d5bc985f26507f941f06138fd741a51db1","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.7","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.6","@balena/jellyfish-logger":"0.0.67","@balena/jellyfish-metrics":"0.0.68","@balena/jellyfish-environment":"^2.2.16"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.2.5","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.2.13","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.4","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.15-renovate-non-major-dd3935d5bc985f26507f941f06138fd741a51db1_1599021447500_0.16759309375816334","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.15":{"name":"@balena/jellyfish-core","version":"0.0.15","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.15","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6a2ea673618d512d346e64b8e211dc91bebdf4fc","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.15.tgz","fileCount":89,"integrity":"sha512-c7Op5Mpe8PvxnjloK251jjQlzYVw3jZ/MAtkEIdqriJQx/66+447pI2agu68IWx21Vrjy5l6qyEz335BaUmydg==","signatures":[{"sig":"MEUCIQD0Gz3a0VeJPjD2OmgE7YMUq2NN6oBqB4/m9btCb8NUPgIgYFk/bmWNkqa5CeCR4IyGn/fxjgXI5NbhKgtTSE+SFGY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":514392,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfTyIkCRA9TVsSAnZWagAA3UwP/RPWxieTkV4PRO1UAsp+\nOJ5TbYAhhsdeSs2sxQShUpPKJPWuhMBfnOGB/x/QBdZ81xWWunwYKXFpPY1f\nzE3k05nCxf352j0RC21Mns4bxxL7Lp79mfynw501pp4VuI+xjVD7fzLTnN3F\nBjvFou+r4JAIcsBo3EHOd2FUs4yNiUEnAL8c4wfn3Y3+6WxDOVz+LObN5fmz\nCXJCXh3R83SgrSxFBHs1qXgbX6OSycrsFlf7x7c2UwlPCvyTeWHmheO87Msv\np3xFvmtBHvnNxWCGUAlkvZ9Po4c+LtvGp+nNc/D6TqQ1TAYAdoBEu0ArP8NM\nHBg+pQZoMb1CpAvaaOC+V3WOoNbZBe1SkLQWm4xC5aWZ7LtB16b9rS94MDtq\n0ZxU0PqCWsN35uYQiRJC7PFad8W1THVCXPLNT46UIh6BuBnuXkZgAvuOR6tl\nt/Duesq/C7P46sYh0bD9yiFL9XzF7GQTw5+jS6HBbQ4NTdIIhvxO37+5Q0UD\nCvu0vMMAKSOB8w3X2km0WL5JDOnbI5FtBafBYMZUt8e3E9nD/UK9JWI+YBf0\nXpl4LnrWXJXhuxX3LKfKToUYUUHtcA+MW6gduIh4yGWi5xyLdTce52wnxQQa\ns7tDniX0chOEYIMAv+wojPVvLZaUJ/AHYrQ5Ve/dAEMgR8qCpx7Mo1XpK92x\nf9FD\r\n=hDJ8\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"c91e4d2fc748abc611d1e0464772072b18822c96","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.7","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.6","@balena/jellyfish-logger":"0.0.67","@balena/jellyfish-metrics":"0.0.68","@balena/jellyfish-environment":"^2.2.16"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.2.5","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.2.13","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.4","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.15_1599021604237_0.5543061418343063","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.16-renovate-non-major-4cf9d16ae6508ad7c8b141473330c191ab461504":{"name":"@balena/jellyfish-core","version":"0.0.16-renovate-non-major-4cf9d16ae6508ad7c8b141473330c191ab461504","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.16-renovate-non-major-4cf9d16ae6508ad7c8b141473330c191ab461504","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1394c512833e23cf6eafb1d4b03bf4b29ecd00b5","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.16-renovate-non-major-4cf9d16ae6508ad7c8b141473330c191ab461504.tgz","fileCount":89,"integrity":"sha512-kM1G5Dnap1kUalTUmnyn0aVveeCCGA/aDSsqxuLS2vjGVtj9Mu2qE+TYW7Rsgh0BXjlnyJl+kMwRc3HiF8FDug==","signatures":[{"sig":"MEYCIQDY9DORE8ch0LnH2o/xgzRcsF+awxy84qZC63QLdYbRPgIhANlWkrr9HtExcTkpgt9qvnPlj92ifDm7X1tPR6j/Nh+m","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":514518,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfUDyDCRA9TVsSAnZWagAAMQ0P/30If8j7KUxWSXpY9JmN\nisgvGVLWj7BHvvahgKylzbsTyRh2Ic8+jKte/0j09nPBYwHGrlNALGxPkbIB\npg4DxdYFmbhix+CmELvTd8JgFKkU+NVyH4wOW3vClvD4Jlfa8BwH/EISeQAK\nPjDbfWl3yYORMXNZGpqUItpW4ynVpelbUALug5eWEEOr3IYbyZnr2n5A4JMz\nn2nK2Qb46PMBTtEjEdmlMqHT/1OrLrArqnXROhYx8XrKjjG3iFBw5jHF+f3+\nwnRZS6bbz9+OHYpcaiFa2Z1b1VWcPiY5cMNhQS2Uytx2M/rjy2+w4AVH6Bu+\nOdSbVJwZxWi2K349DhcN+7+6Evrkqpz/oESy2TdWjKIeN/Vzxv5kopYJ0CjK\nwZ3uRV1yhZk6+tZHxJf7H8DwCb4gLRk9B6vNrEup1EZ5CVaYx4WY7PgGRFmx\ndzORFovID0WY9pOnR9zBaC1qt0URfx5q/MMByuHiZQ2rmgJKkIxtAsg5cT/w\nbSrcLX8cqB29gaCaSCZNPz38uF7zXBOIXcRpQRg3tSVowDOGNcUzWFDP8OgR\nF2rBoGnd1wfuE7NjIqxdDoz4yZp2ZLs2/Ac2eJ/ailRLTRepEe1u+Xn9veHs\nGGt3iAoX+ZeCyXEb9GX5FSNo9e1Sz+TgphSxt4v0PX8PioKNrAvSkPYdmJuo\n4g67\r\n=KPSY\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"4cf9d16ae6508ad7c8b141473330c191ab461504","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.8","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.7","@balena/jellyfish-logger":"0.0.67","@balena/jellyfish-metrics":"0.0.69","@balena/jellyfish-environment":"^2.2.17"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.2.5","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.2.13","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.4","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.16-renovate-non-major-4cf9d16ae6508ad7c8b141473330c191ab461504_1599093890805_0.6141543478207134","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.16-renovate-non-major-1821a7e1298fc5302a138ce8fbd8f9f6b510d705":{"name":"@balena/jellyfish-core","version":"0.0.16-renovate-non-major-1821a7e1298fc5302a138ce8fbd8f9f6b510d705","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.16-renovate-non-major-1821a7e1298fc5302a138ce8fbd8f9f6b510d705","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f041f011197f703f8958b75d7bfba7ac23805977","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.16-renovate-non-major-1821a7e1298fc5302a138ce8fbd8f9f6b510d705.tgz","fileCount":89,"integrity":"sha512-FzWHiJHS6/kZ+1zCGE8EFFDnHSqhIqOv4tIeibK7wBA8t23gaVmf+g9g8Qt2Ebeo0D3eZi+ZS7LVsid4HJbAEw==","signatures":[{"sig":"MEUCIQC28rAPrYGR+pPuAHe8ftc68Y7T72/xEVIL+iUWl2YlYAIgZ20dZq5SLLKqjUXkemKXlX6gz2TTR/pWsmM8wqV3tuw=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":514518,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfUD2VCRA9TVsSAnZWagAAQMsP/1H6nK+9IZiYpAsXDleb\nHLw8V+829dkdScb1Jq5aPCjvW4ZZRtQn9O2JX5v78XW/Awgg8RvKWfQUmajA\n5vnNN7FqTrKTC+oT5ljAXV5o4zBWuRKhE4xKcvmXFzO6G82ECiMxq+2j8EH/\n215nphJr+1cS+qBxZDlo/bXITorGks49kt4VHP++XoRc0mXrfSF2axvVgVip\nx8AlzYWwPfMF/cWB7JnEw+ZznaY09nD/JwFcslDQmzWpeZRBKhV7Flm58y4Y\n+PPRJpSM5QnCDoOj4gA69cuQdpr0lzhMk1c7DY48F3EpkBwy6kUa35lXckwg\nm/YFAPb14xglLdce5hYonQXxFXtcmWYlUkvuwhIal5MbnEdGiYgWVeoxztp2\nia1nduQ8nN1PGMLdTQJkULWrKSHQjmeYOaEdonJLuvqhqeK4Q55oTDdNzRxu\nZwYZc6cikP5m8DMJT+lLCwh1K7JA1ecnDRbZMUAOwKjnlNUYCsVabx/Ow89Q\nTBR6B2lGDjVuvdwD1ox+vjP70P7eLQRFIPEZ0dAwISKlyPlvo0Bvb+fWeOqs\nqUdVgh0En+b3dT+SUcOEcSG6JyAX1/NQzr2QJAxOmY+TFlHUXoyuUeadqNSe\nVegOIjL4fLynb7SytrEi6ZhCQbdHtJQ1SBDEH/bHrdJ0fUKmymJn9TPXXKWZ\nEJ76\r\n=/G4I\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"1821a7e1298fc5302a138ce8fbd8f9f6b510d705","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.8","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.7","@balena/jellyfish-logger":"0.0.68","@balena/jellyfish-metrics":"0.0.69","@balena/jellyfish-environment":"^2.2.17"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.2.5","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.2.13","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.4","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.16-renovate-non-major-1821a7e1298fc5302a138ce8fbd8f9f6b510d705_1599094165314_0.03134083244509678","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.16":{"name":"@balena/jellyfish-core","version":"0.0.16","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.16","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"cdeef815ed78a8a6544162ad36762de797c8eda6","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.16.tgz","fileCount":89,"integrity":"sha512-+bHtc5agKLka6aEI8BCvc5uO+YkfCfE/vdw+TxnTKyBXxAf2Ia49K5tDu8li3zDmAV2vu06m9xAjtp7Bl/opfw==","signatures":[{"sig":"MEQCIBG5NL+ulPqKgedKG5DjvFxbBsi6KLlMto7Zfxr15eDLAiBuljtV2U3WD9xqNqojkw2g2PF46jxB5Nt7b3nfefmsvQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":514458,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfUD8lCRA9TVsSAnZWagAAtC4QAJzwgtKfuwMkd1MyuoTI\noyQl39Yt/YoX+A7AlOm97jz0V5jmG3EiblgDQitsNczg1jiUfiGIZgP/hdl9\n49eGP9pQHMTj2kvgJRiQkmU4LRHlU04m4xLNx4g0qBTP0OiYKUsJq/Ilxlq+\nxXEExGjK6jRr4cnwN80Pq04KlQSCiknRD/P0+zof0ayhh3Oruq89szgMCMFU\nL6IK6/I75ALa8RSvZUxcMKupyrSrV9s6iQ1gMqUYklztN8EisxoAVhViHFgt\nBzPslSURwLHPT56hz4UdHp9GqnU7Zb5X/BJllpcbcJMyK+Oa913GGiWInvMJ\nu1q2dpjm8zROWMJsIbhiik4UnBWpS+QuqIZlfENQD+JkjnVR1S5NJbj+ubK1\n2HYpkTYDvvsbBfxKDG3/PqJFTzrGtD6NcQIpRHYdGypPGxfkmnnXF4mXGWPT\nsx4G2i6eb7RJww/VWKLXEcG9+CpGfsmlx50HNd4sW6MpxkLP7X9I2q4IBTKX\nNBnjE1g2qORAC5ECQpb+6zyxuCj8ndQ0uol4NOLw26FIlvphWF9IoiSz4CQ8\n8VhzQ1+f/2d3EasEepOVp6TsN9IUINuJSulcVaab17RhHO1qs0Yw1fMwhP1u\n80rLoGc6jTCK5MqEYCwKJSKxPJ0I0tz4JgBahkH/6xmWFG6QWx3sk0RQRRgN\nzbpm\r\n=kJ3e\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"2fabcc5ea7be514590e3328575dca270e8f3f818","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.8","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.7","@balena/jellyfish-logger":"0.0.68","@balena/jellyfish-metrics":"0.0.69","@balena/jellyfish-environment":"^2.2.17"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.2.5","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.2.13","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.4","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.16_1599094565286_0.11480306183806088","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.17-renovate-non-major-0a8b11f1c394d1dd209eafe1fbcab8516c5619fa":{"name":"@balena/jellyfish-core","version":"0.0.17-renovate-non-major-0a8b11f1c394d1dd209eafe1fbcab8516c5619fa","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.17-renovate-non-major-0a8b11f1c394d1dd209eafe1fbcab8516c5619fa","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b92ec417e6edd452df69fbc8c742f7c2fe81b538","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.17-renovate-non-major-0a8b11f1c394d1dd209eafe1fbcab8516c5619fa.tgz","fileCount":89,"integrity":"sha512-oEwZIoi/ej2NZ/JCniEh7qttp4DWh4Cai03FSJn25kuatLBvPzFzz8XnLmEP6VR7utJN0Efjy9IzTX7VDC7Dow==","signatures":[{"sig":"MEUCIHqeEUoZvyXDCzuIUgH53Urb0z65rFgjUITbjniS/9SwAiEAkXtEtbaKBAk+NexAgsa4GQVLNgLqBCSVYZqdSEKHACE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":514621,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfUE14CRA9TVsSAnZWagAAiZYQAJRxOvgh35mWbCSfE7VN\nriL9EjCL1iJ3UkZ6x+e7nkeDpHlDkexvoUFinBSqi3HLTG7D8F83Rmnq9k0v\nQN3HZ6nWBDYjMjfNdRRquK9NVEdNtmsblJce1Ux4eQjCR6s/VNVtmV7qmU5b\nNjyw1vpE95LzKKUQTronFDPoBzjFcVHtEJ8Fn8Z0RqzMbXd9kdxrg753FF7h\nZQ5sWVt6B6svlB+dO9lmNYIaB4xN0exdIVqtWyGiGFcO77Vfpp3Qe1O4iUjK\nLsT2AikKoK30c1b8I6CD0jne/65hIKPRzklkWdt/aSN26kZ6GN9Z+in0UdCF\n1dQ+EQn00W3p8IBIchmtCsokflPoJwSzHV29QBc96O1jMVas5h3MvqSHC4cS\n1Vr55W9nWWv1iqs0FhbLOGhIV71PiCOMFvZEB4QE8zrnk43nTeXg2BGkCtCg\nbIH7yCVEcByQcoXUJRLa9a28m1cFeuHCPbgBDEdyR82lcTPm7bZYN/IL0sBA\nwds7SdW4lfMZ3ObUFvPjaRgQFSIDv/jIGqb42sVRhrLQi8uXewOk22gNbCCG\nt+dRfb/HuhW5s179D+cgUAayfvs3ZDJf/rAdS1ki83fXLDA2v3E2awTS+Uby\nQNX/f7ouTnpfLmBeV9Aidrxu3Xl5WWPGkQv6GjxVGZxiXhXQW5Vg3qDbkrG1\nId1U\r\n=lLGW\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"0a8b11f1c394d1dd209eafe1fbcab8516c5619fa","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.8","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.7","@balena/jellyfish-logger":"0.0.69","@balena/jellyfish-metrics":"0.0.69","@balena/jellyfish-environment":"^2.2.17"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.2.5","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.2.13","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.4","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.17-renovate-non-major-0a8b11f1c394d1dd209eafe1fbcab8516c5619fa_1599098231606_0.29896432562833253","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.16-update-code-f6755339893fc3a6d2a344ff5e67491253e8a3a9":{"name":"@balena/jellyfish-core","version":"0.0.16-update-code-f6755339893fc3a6d2a344ff5e67491253e8a3a9","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.16-update-code-f6755339893fc3a6d2a344ff5e67491253e8a3a9","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"725cf30ce086292298e20db1c03abf1599dbd34a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.16-update-code-f6755339893fc3a6d2a344ff5e67491253e8a3a9.tgz","fileCount":92,"integrity":"sha512-gtrTncpi/wtKdIakDUTE8nA4pKSBXg8/BQKUNeW1oZ2c3HDSwqVQYYH39XeeosirM4bfXZzeZyK2RlEqDtpp5A==","signatures":[{"sig":"MEUCIQDRl7dAgA0edmSsZKV1yH2rrY3yW8u/VTAb+aKa5WRTHwIgBNc2kS+Ycfc1jaCPbNN3yHXA/mivCf2Esn0aj76ZyNU=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":552352,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfUE7XCRA9TVsSAnZWagAAnY0P/idD/kv8tP/IOaZSK8aX\nBCt87MvVArFH+yiE6oSBC04DJ+ZTcVi1vjPhJ3waJOIArUiQUKnASLYGSBTp\nNhgR2mPAbjdrW2JlFulE4AYPCrlQJj0A79LzWmOAfal8x1X2TwxMWCzqn1XX\nP2dUfFS+kTyfTF/ANqf/DOajcUP+y+f9XN+ZvP9JhlS2zdt3d9bN9IS7UwSL\nBLcJtujwOTh/u30W25yD5k4ZaY56TAkh8dJmPk4AzkeDO+O6NKhCwEBvs6GG\nt4Y7sEVNG0GMu3OowpGBBQ+C6Gtp4F+SD9qrniKRDeA3b1cm4+vANLtKCFOc\nbKy9tcRnUa0yZuJ7SitCbZECItE1Cs2z+fw6n0+1tEEggJ8hxug1kglo8z7w\nJ8gt6/IUr70wMa4b6DHWryp36sdnNEYecRFl3GZS1TXIzplObsQ5gRXkTckQ\n4IZS2xCIfPsuam4u1hMadWmfKjMHtS7+r7D8OEOx7LTxPQtNA/+VA2rXbU/6\ncVb/ToMpHYddQjdveXy8q5oEcOOZ3kGtmPX4I22Hc0VaCEQB5CVZKEBeWkj+\nTHmo/RL18uUhlcGdSjB4eQGcv5wW5ICs9IcINAkX/oxt2ZV3/TXDlXaZSmuU\n8Z9lc5Lj9oJL6k/rgwBVlM8Z7SJ+Sqhf9gd5/K/d6bqsvF7AQX9HTUhqIFNp\nvRn0\r\n=u3Ht\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"f6755339893fc3a6d2a344ff5e67491253e8a3a9","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.7","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.6","@balena/jellyfish-logger":"0.0.67","@balena/jellyfish-metrics":"0.0.68","@balena/jellyfish-environment":"^2.2.16"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.2.5","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.2.13","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.4","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.16-update-code-f6755339893fc3a6d2a344ff5e67491253e8a3a9_1599098583365_0.8703511938789561","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.17-update-code-12fa2d531b476a6764a1b312a5378510ceac07dd":{"name":"@balena/jellyfish-core","version":"0.0.17-update-code-12fa2d531b476a6764a1b312a5378510ceac07dd","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.17-update-code-12fa2d531b476a6764a1b312a5378510ceac07dd","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b768310ae656a3ed0738da5d214106da6da8d65c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.17-update-code-12fa2d531b476a6764a1b312a5378510ceac07dd.tgz","fileCount":92,"integrity":"sha512-JqD/ZiYHayNfL3Tg3iY9Bw2yugUntSBSlDjNp6taW/vL3pJ28gjdw+04GLX9mXh51CNBfE/UXMHA2X8raTmJRg==","signatures":[{"sig":"MEUCICXx9+35Xh6AI04wdtVLfNvFYnb0+aoCD0APGvKnJzG1AiEAwEd1ij6BK0yEJyRvmMkt2Z8lvt4lm4nGMyMHLyTgef4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":552418,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfUFAuCRA9TVsSAnZWagAAN3IP/3E8DimKkD1O7gKxx31f\nZFl+3p4AIe1DRFoVYOzTyM73rP5QVimgLDthFmK/9iyUaVAt8rxFBjgTgNcr\nho8qXKb/D3A92J6WrfOr6Nj5FDeZYJ1FcMlhEAaWO+hVOFUyKvoPPST0dsxh\noM7dZ40JeDWN0Wc6iqLPHg6UlezLGjT/vRhaoYoju8+bBRVcSfe4dW9yIRoC\nMSqMR7w22zGs4PM/viBX8N93ktPFOXIr6vtNH+Pfr3AxESQ97yH+ripgGGD2\n81Ox3uhuVINzDVsgMijqHPSIIpOAvqVxE6z7nURcTlFRL7RdNGfxcOCMn9Tw\nuldh8cRaL+jWG8LCvMQAi9oYC2k5GJ+I+hfYj1Oe3FiRVJby2z6oipPjYttp\n/6uUb4z0/0/QW6AGeattM9uR3FoFK+fd+p6e2z0quBwdRXD15fZngW5uNssC\noyM22JSYQV7npe2a5p+I3lrtGyj9s9G+louy/JNcC+WDUinQLJQpMrMKmNus\n/Ls3PGycjSieCC7GPZdswvYMtFkDkYzBFkrUXEUJdrRN/o8z4MCCb04cJrUv\noq/NRdsnUEaKtkqHTz0oNfnK3KDE5jTZ/r9ac3Gubl7gHEKu9uYIWKAfggm3\n2JC83MijdFhtYlCkVdlcJ/sWBZayQulCddcMrfoCwcQJ6i6CoCmdxRKD7K8v\n0eQN\r\n=pm1a\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"12fa2d531b476a6764a1b312a5378510ceac07dd","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.8","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.7","@balena/jellyfish-logger":"0.0.68","@balena/jellyfish-metrics":"0.0.69","@balena/jellyfish-environment":"^2.2.17"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.2.5","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.2.13","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.4","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.17-update-code-12fa2d531b476a6764a1b312a5378510ceac07dd_1599098925625_0.6007601452660738","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.17":{"name":"@balena/jellyfish-core","version":"0.0.17","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.17","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f09884c6d94554a68bba53ffd4e0d0d850858ffc","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.17.tgz","fileCount":92,"integrity":"sha512-XOIM46lqdjvEZ+sb/aPbuMekGId69FuNb+aMmBEki26PDatHrpMQZ3cToCkXYoHQo1I3DhynHp8yHIE9TJgayQ==","signatures":[{"sig":"MEYCIQDO0Q35Zjc5YnYq9HahkhcxjiLcQyYZROFm8aZwGDhCbQIhAILNr4DKe3uQpOArLB8DW3bUVprGPdDGm1GvwHbDmcAx","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":552365,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfUFG2CRA9TVsSAnZWagAAZksP/3yzEkwcIlwkVvRDmF/h\neKICbhPa6Z5UL6cdDR3AEX0Unm5S/XeeJB6r3SqBI1yL6+p+2zCqD/rm8jUN\naLg0fdvbU87AnVELIK3vIeiKEQKtC0etzQ33eh+hUefMGBK8Lgscz7ITPd5p\nFylZI4dwHUasTl2azIDWlLIQ3EqCO5ZR4o6TYPnxk1Ypf/g56iRhPg7SsqeC\nN5x6vfIP69GxXRkQVawbAL7AIbFO90gzXYl98ylV0ogP20s1ucAj2PRjqzBd\n3CL3HmltYMyT20t00LjAjByxT6dAKfA6LjPNcbmQBpJiqinKZtPPb1idUFsn\ncF71iuiPE8uPoMQyvkbcmhQK0KATzF9PbkBxg82hqWwElZajxIgiZEp5/6Mc\nazBSa8GFFkpelmDTtbVbVKRYqSfgmTAYQB7KjfLK5ck/fBPPQzg/errOwc7B\n0Kt7vJhicwHQ+Wyz8K4J820qrtd5zzcX9fsbq8MC0N2WQw3UVUYtqEBECFxb\nXJzF+HEV94EUu0eF4AiwTjWKPjB63DG7RPGRwkfr4zFzqxRiNWD7Fkb0nwmU\n/sY+bgHvSENPnYzH+PXt4ppxpylRnZg2AgWPEaYO9//rcjXurnzQ+IVRBS15\nRAvxVXROo/xtNwudpwkguPJIMD8GmMvlobyBg3qgIV0h3TfhCz0zYtxfg9wz\nVlDK\r\n=oALn\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"2ec9ea7288003346775edd3d0c04bf5c7086416b","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.8","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.7","@balena/jellyfish-logger":"0.0.68","@balena/jellyfish-metrics":"0.0.69","@balena/jellyfish-environment":"^2.2.17"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.2.5","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.2.13","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.4","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.17_1599099318012_0.49523217414844445","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.17-renovate-non-major-b1a2d3c9e370c8a14af54416091a0c2228bf6792":{"name":"@balena/jellyfish-core","version":"0.0.17-renovate-non-major-b1a2d3c9e370c8a14af54416091a0c2228bf6792","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.17-renovate-non-major-b1a2d3c9e370c8a14af54416091a0c2228bf6792","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"831bf93ec5a9c7950dbb1b5dd6fec795888fc1cc","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.17-renovate-non-major-b1a2d3c9e370c8a14af54416091a0c2228bf6792.tgz","fileCount":92,"integrity":"sha512-pEGvmGJ0P0YYYzRZomF92NgTPJIr8S/9c6mgwSyS6r8KOpY7qMqH8gTyCteUASbb4SGxzEkAGFbC7aaaerq4kQ==","signatures":[{"sig":"MEQCIFehY7ZE43sPDEdiSuVaU93ZTvb3lgm29FXIh+BkyDYYAiAUz4dK2Ngu4ZKdsd0q89zcATzDqN56PaHsFC1kuxlZPA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":552466,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfUFJ3CRA9TVsSAnZWagAAZLQP/iEhZC8sytdoe6Qk5ZKS\nBapyV88ZKZBHCHEklHlgMZT7SOamQlJZJGgwmPrb8fqEMqM9g27tjhilIDdu\n4iLdhSC9CMwq1MMy0EgfSE/tkElE2f8vrXEZhHhnXbY87ky6E4ZAxE4yy/57\n9w8JAW9pCwuY7oR5kxT3QWRWizdAnHJS345xcxoeLyFcnFQlko6oCnxCdGhZ\nYvna+ixrwaZcdxYndbCwO/Ra/83MulMg8KotTgwZq/kjnTrUq0e6A6zmg5DJ\n9czkL+FQcJmzQeFpD6OyzIlrY+d6yi0HNgQg2HTbUT+67eMb3OQUNiDgYe+R\nc3tqa51MUqqT+OkmesofeDe67Zs2bA3uE0RzG8rmGMTP7uW8IePYL/OeuQBy\nWM8fdWBeHbTGqIKAY2hjMh2eLeT09aEv2WOkP65COhGwePxbpALcgS/5o5Gs\ncigrvCw/FO98Fk29Bf01WoIJgJ0FNxbPgvzht0HRLyY6JFei//51icXUPgR4\nDkx21JjBLwcV6DFJJZe3eV/OIMuRSf0piuYZ2URrXRhJ5pZ2XRQHwMTb12AJ\nqHE1ITQaEXW2wDnU9ik1HDVtUcVpWBFasDzo67xu7uXAe3h5D+BjPaxeJjun\nADjp/6IlARcH5SQQuPvzDiFbm8N/++VAV8rJWbGXvz7YFw4ahoXIzFDkFk5Q\nPHvd\r\n=oZB3\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"b1a2d3c9e370c8a14af54416091a0c2228bf6792","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.8","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.7","@balena/jellyfish-logger":"0.0.69","@balena/jellyfish-metrics":"0.0.70","@balena/jellyfish-environment":"^2.2.17"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.2.5","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.2.13","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.4","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.17-renovate-non-major-b1a2d3c9e370c8a14af54416091a0c2228bf6792_1599099510830_0.9098491118541863","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.18-renovate-non-major-8104568bb18b997bd80be456345d7af864622647":{"name":"@balena/jellyfish-core","version":"0.0.18-renovate-non-major-8104568bb18b997bd80be456345d7af864622647","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.18-renovate-non-major-8104568bb18b997bd80be456345d7af864622647","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6dc2f0f85969a2bb5aaece981760b637410aaf52","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.18-renovate-non-major-8104568bb18b997bd80be456345d7af864622647.tgz","fileCount":92,"integrity":"sha512-CFLd9ZJHujTjhv171HL6MjgsO9to6CV40RXVmlGFNeCQKzRSFmT/WNvSxfQzEa9C/06qWGkcs0aH7i8ZLmiQDg==","signatures":[{"sig":"MEUCIQCY+KjgagJGEJUQdo6MtZ1YjH0KFTk6zZE6QuXxLp7DVgIgCwOwiKAIboME4S5uKY00XGuQS1qkbSDtHSLKoohL4Fo=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":552491,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfUFLmCRA9TVsSAnZWagAAzzwP/0xvzg42gJxF+lUJ1Z/6\n8WOXvdQ3+RmDyctu4cCNzykAao0K15Nr9VrNRFwNRx4RreHXRun68cDXrUCh\nPMtS+gNZmvWVNyKfv0V9mA6hO/bfVzIbDsil2/YPsf3pVISvf+DjLOn9rIWI\namc7E+Xt0lsUyLlJItvKZEYZSMUdASFRAdAQx5gNAWQhHxMQ4/dyuGbrqMD8\nwBJR+jFOxW8F4ZcIyjNAvv+iiTV2fA6gnyK+jXZpx8iGBMBfHfKvRqJJDGkX\n73GJjRqrYr9R5gCsQtAX4bOdDMyGk7erw+s/RM9Wq2fsXroLd8lm1ozN35Y6\n0MmBY+v1PT5KVJnLsuwfGRWXBfFhnvD6vPKRpDF1y32dKEkYDSh3nk0XhyyH\n1MdwvR5zG0s0xHclVCH7OWtzCUOUEs50UH8WvDFXdedft+48p1r00hON2qfY\nNl/cQHRqEMRcCWN4ieBnPXq7MYCdzZ6vuOFB8dZs34iePJ0UhztgyzkbN97g\nSISbnRw9QDsN4wQm9j+Y9v8IWOrFCDcPvxGskr7Bejks3a4TbKYPkL6US8Mj\n34OcRJv1Xb6oUlQ3TgSIzGKTbTNDa4ptt/i+eWC9pZemg9D20lDxDXtzfy/q\ntoRcPQkvcO9itU+81a/1u123l5XRvG2XBjBGPpNxtI0twv96KQ6gOC/GfhQG\ncDP2\r\n=i4Ul\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"8104568bb18b997bd80be456345d7af864622647","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.8","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.7","@balena/jellyfish-logger":"0.0.69","@balena/jellyfish-metrics":"0.0.70","@balena/jellyfish-environment":"^2.2.17"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.2.5","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.2.13","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.4","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.18-renovate-non-major-8104568bb18b997bd80be456345d7af864622647_1599099621298_0.7219705218688559","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.18":{"name":"@balena/jellyfish-core","version":"0.0.18","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.18","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0379913c4cb1f3b2dc70b01fe5e0a2bc5572bd75","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.18.tgz","fileCount":92,"integrity":"sha512-94VFtIYGasqNGuPcLRM65zrigWo5drll7U0HaJel+GJTMiAf49j/xe/oE+VX+rfokFMMdI+et+VewHfU1Jpt1A==","signatures":[{"sig":"MEYCIQD67ysF/dcaZUYHovBqzqs6HoKC7YZFhENBqSE/wsXM7gIhALgddo2T8BKb+giJneN7A/cgQQkXjd3m+AYrqzkdQBbj","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":552431,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfUFOaCRA9TVsSAnZWagAAkcQP+gNUrbOHw8QikowEw9J3\nVbXsI/KzAlNOl6K3BXBTFXv/j9Hby3UgKVdAOfX9KU11zS3du3UMsRIdH6Xi\npckCx71HSLqhlLKXK5cUdUuHDC9hHitqyA8qz7bezQpSU+2lUfKiSiUcX60m\n47AkiHpPYUS5P2Tj8IUERF4lp7ITldQ2RCW79YkF5lrfb92pwR8yyuf6nTY9\nC1nuh8VJ3nS4Y3tdcVBmx1R4W7tweweIeCaGKWPFDjcjFBmqCqFdod0cbWTH\ni7BZ6flYlK7rdbU6aSAgJY3aSQSS99zVVBOBJm7hsQdQp2lavg6us54bsS5C\nbfBi2R4VuYKZKF/YOoUrJMHyVB+AZrFQJIRYX6Bh1HNRaHDOCtD0HFOoh/HP\nCd79XS59w7PLRvfuNKoB/+6crZnf/bvCDN8cf3B8VydGePpp6Bdf77ayuszE\n8GCaQ5lfPpAwVpBkszzW5yAW7UHMQmuh3TwZAw8aIxOvFWspGIVLEni6XQzt\nbopyAdEc9VwcdM6i1oUbu11vy9SLxLMXgIRdZByIp5LqebzOOKDcjRjkZAvP\n3IViznHFDLcsp1C593f524xmTbV+MHdP0ES1AkHfWTh+xvhn4mGphjV6fa4h\n05hKfOYcNZKLQA/KslVs9Ht4RTr6kD5ChsprPLroKr0Y/fOIvtFh6C89fENI\nEW1G\r\n=hsdM\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"74960253f9efe4da8e10eb3d2f24f88c753bf7c8","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.8","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.7","@balena/jellyfish-logger":"0.0.69","@balena/jellyfish-metrics":"0.0.70","@balena/jellyfish-environment":"^2.2.17"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.2.5","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.2.13","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.4","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.18_1599099801960_0.0956071479593239","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.19-renovate-non-major-383be0a83c7b45fc1b60d21f3b089bbd4c2b6ece":{"name":"@balena/jellyfish-core","version":"0.0.19-renovate-non-major-383be0a83c7b45fc1b60d21f3b089bbd4c2b6ece","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.19-renovate-non-major-383be0a83c7b45fc1b60d21f3b089bbd4c2b6ece","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0c1821321e8b7703831e4bd33bcdb30cbda25554","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.19-renovate-non-major-383be0a83c7b45fc1b60d21f3b089bbd4c2b6ece.tgz","fileCount":92,"integrity":"sha512-umVEpjxYfWCxuhena6Edkd//EDj+W1AKZMwHkXjFGJEn4kcIQYBuX1cmkdldnX8P8jrZ/wYfrE9iQRK+faW9vA==","signatures":[{"sig":"MEQCICXUKDNTLyGOrptAEGlR4g3vyaY86zNvqDCoSG4dkajeAiBh7vWSBsPIfcqp0Y+aRaeqPOLN7WJIUS3X+hCrRvbZFw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":552580,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfULK2CRA9TVsSAnZWagAAszQP/2oica40J9zQCZr4nYFs\nRPzAEfd5rY2klb/ymejy8KWcG+P/WBvPaVl7I1WwDFqb6oS1WWlw9K75v/l6\necf7Rh0DYcRQVKeG+b7l2+2MYT0Ve0BJ/9a3Ai9rIdaZU11CpQITI5fAhNz8\nWYQSVtoYR7apf1cA6Xsn3kMS6sfvx++Lp+TpotOpKP5ocDxUgAYG4ppTzZAp\nexqOz3MAO1EKWyLKTFTlCz01ml3XoTWs1SSggsl56BQqh4jydgmpKbvIyXdk\nIaIr3TGza8tkHy3ZBKY3/V04mMljrgXgl1/jmA/WsuQHCAzxzZPaYF+3ndsI\nnYD+lKPMJ4YlvGI5sgNM1mXGJzz9IdyUL5ajfn4YgEByZt8lLgrQ2UfhREdE\n5JeX6BObyI4RTYYaNKi6vIGd+soK+h8f9yv8waVeRTXxSm0Jc6xixn4esog+\nBYEA0WOJQ2nctNzBtvfh1ImgVEcwEGce0l3daNIFLgGGvzw+wBebxtrfNyh3\nsCzTD6h/e+d56BvGeBFu23XeOWZs5AsT1tGzzI3iPnkXiFKmof16Xok4nXzQ\nmP/G02rjs4ydElqPadTB5RX1JgqmiPChBgr4WakjmtyViln4k6p8m6xMg3M6\nk5F9HINF+drN4zc4A5ckvd3ogI1O95bkLOsmhykKPT41HWYAS9IpBP+Pzrfz\nt26i\r\n=WNe0\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"383be0a83c7b45fc1b60d21f3b089bbd4c2b6ece","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.8","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.7","@balena/jellyfish-logger":"0.0.69","@balena/jellyfish-metrics":"0.0.70","@balena/jellyfish-environment":"^2.2.17"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.2.5","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.4","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.19-renovate-non-major-383be0a83c7b45fc1b60d21f3b089bbd4c2b6ece_1599124149476_0.49665858181493294","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.19-renovate-non-major-a85355dcadbef6f171e3c8378b0fd48aafe4d8aa":{"name":"@balena/jellyfish-core","version":"0.0.19-renovate-non-major-a85355dcadbef6f171e3c8378b0fd48aafe4d8aa","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.19-renovate-non-major-a85355dcadbef6f171e3c8378b0fd48aafe4d8aa","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4bf9407a8ec4181ca0cd99e673cf8520e6eede71","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.19-renovate-non-major-a85355dcadbef6f171e3c8378b0fd48aafe4d8aa.tgz","fileCount":92,"integrity":"sha512-P88h94FIqUy3D1FdvJhXK6KPH312lh6p87NK/uoOhTFprWbMgbYSoHOTQ7LJliTyZxDiuRtZAiUn9f4DTFwT+w==","signatures":[{"sig":"MEUCIQCZr2gR8yt1uM0dh0BD5z/f+lednIiYvCFFNXItKkb2pAIgGxppSl6+cm24G0E8SKDNyWYtEfLAu5e2pJG1KSr5ja0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":552556,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfUWmjCRA9TVsSAnZWagAA8n8P/1jcB72UoJbcwSNNcg0u\nMQDtI6sSD1p9aE/+LtdEKjCdLeV5D/aB5j/uFs7rW5BXt4VdntJ0LJSefnZ+\nlp5sH8S1tYppw+/th5NMnuvmk/aSHkrvmE5LLzbin+s9RMsZb1BApXGeJS03\noLxGWAgSh+kAizCV9k1IOpvSBwR0BmfzwVK5ojxje/NpD/aG2Ublms2quJh2\nfG11PUCVFFYUc22+XvQ7hL2k+VM57PxTRkWYw7tEph4jfflTEPUMBGJFhaZU\nlA7IPiybN67pPE3lAM88BxHvciAABqcXt0uTp5Xrr/JkcTDKhhVd8vKMqT0b\nsLWZV7uGvJRrJoHaVr71YMR9ozK+dPZwl0jXfdY9p4ozG11All7W7DJyCML4\nfUoWQDNlnc8cg4QUs8NYTceSoUQh+3qOUqRrtCvB/RpWFciVnuHeLmt8K53O\ns3tsz7/7DhiBIgkgqKWSybCMH5KxH37RNyw6Tq/uH2qEOKJels0UgzrityUp\ngsy1yP06izGzAcNWxFXdb5UPiM50J97O50zVb9hDL5UXXDrpQk4ZE0A2cGid\nbmGZRIB7cTDpeCz7bLJmJ9zAkz0cH+/l+AKyfgOeWizWmvGZsddhZnJPeG3H\nAoSPGwbBzjr0OMUB41PitYmyywka+p62s2CKH1OCeLJm5CPQF17Q4BopxpRw\nygLG\r\n=gTAc\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"a85355dcadbef6f171e3c8378b0fd48aafe4d8aa","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.9","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.8","@balena/jellyfish-logger":"0.0.70","@balena/jellyfish-metrics":"0.0.72","@balena/jellyfish-environment":"^2.2.18"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.2.5","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.5","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.19-renovate-non-major-a85355dcadbef6f171e3c8378b0fd48aafe4d8aa_1599170979139_0.5239584343202432","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.19-renovate-non-major-6a07965026ae42bda3c8c917c3b2473f4858de3b":{"name":"@balena/jellyfish-core","version":"0.0.19-renovate-non-major-6a07965026ae42bda3c8c917c3b2473f4858de3b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.19-renovate-non-major-6a07965026ae42bda3c8c917c3b2473f4858de3b","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2db7c1d18aabeb98cc96cde8f8ed34df0a36dc7c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.19-renovate-non-major-6a07965026ae42bda3c8c917c3b2473f4858de3b.tgz","fileCount":92,"integrity":"sha512-rNx71WMyHrQTdTeKRuOyGBX7U5nN+NKQ+TJQtCcFGLYuBzq1p/vhlOfzoGKbJkAopWGaVX+lI4gZihN6j4jTWA==","signatures":[{"sig":"MEQCIBAcMLf+Cc+Y+T5D+j3DW9wM/vCqvfGyGAhcnsU4ZK+LAiBZHp3Cmik+rXeZ8FzUyOLe1Ci41aaZ5z52fKWfH/2U7w==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":552556,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfUWqsCRA9TVsSAnZWagAAlqoP+QHWddQonIF+J8hT879z\nLpjG5K3IW0XfgwxWZOIno/OJRsyILWhMJEwBdPCcKclsTli8p5W2gYJ/DvsC\nnIAywNnM+RbARMbnJFBdWvIZwpTkMQETtTfO6QuvwYAYHPzFSpK4z3mDAU9K\nkxgJSxXXuQgQ+Hxf7XnQBSMNzG92pEd59k8+OTTmaORVKSILxPdDQBZUR90K\nWc+d5fe9V16Ew3gfBqjID9A6Y0r2Mvo4xnJpWVTSwAE7vvh2XYw/mqlJdBrK\nwumJt7uxFiNw5DWZ7/dtl+XTJiaIyAJykbrwqQX6zvlUnQtsyq7A88DMiSvW\nqQ/11eirvJ+QLxFPdUsxLbsHmcL8VQePg4L2zIfz8sYuaVHstZbxLXQl1PX+\nr47WV5VF296cYoDo8nN9iglybkvqLNwImIfDl7AQ6g+ShxGVa8lKDbE2MSru\nBsIP2CxTM8ls56Sfvn3hBiNfFUguswUm1PrFNFPPa+4Y0lXykAneNy19/oUo\nED2Ytbke3u8oHH9ZiYtNF7dc/MJ6DZYs+G3QfEYx6nvoAM9cVAPRpwb3zTox\nwTMpv3Au2zPPfv+FjpNjdeKO23m/maXVke+KeeYMSiqEUbYVVAvcoTmNzbhR\nCYO0Mn7dyGv9U1wlyMf1QUZFHSfavxopVq3iKjxwYJdTMwRnmxULULl331Ei\nbhki\r\n=mveP\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"6a07965026ae42bda3c8c917c3b2473f4858de3b","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.9","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.8","@balena/jellyfish-logger":"0.0.71","@balena/jellyfish-metrics":"0.0.72","@balena/jellyfish-environment":"^2.2.18"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.2.5","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.5","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.19-renovate-non-major-6a07965026ae42bda3c8c917c3b2473f4858de3b_1599171243615_0.9279414607462395","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.19":{"name":"@balena/jellyfish-core","version":"0.0.19","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.19","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0bcd5cf678f212f33304911f6928f5d89172e875","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.19.tgz","fileCount":92,"integrity":"sha512-tdy11jL3VtGt4+C2rb0l5Ztv2Re4s9blMLPZm33VcsIY+/SYQ6VQW9VKTP80yLeDgtsW1qVIeV32YnYneCW9lw==","signatures":[{"sig":"MEUCIQDEfOrOb9wz3HD2uOmMrqmMDoFkMNKPUwkGOJ7DDfkCjwIgY+GvlBF9FueNYetQ4dvm4kj/uQMJGFOoY/WxQ5cB6OA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":552496,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfUWtNCRA9TVsSAnZWagAAPnYP/1G5O0IztQqZgm1r3oQK\n+VvyKJNMssDp2jcpN5P6bm3Xv/Yv/dASFmXUCLtHKu2fp4j9AYr1LZ0kvVQC\n9PIvG6lp+N/wkK0godTnobYBrCcQUl6//2FYC1Ue9paE3+j3yE1da+o0vsaN\nTUGHWnNMyUj8ZLLeiJRl+jGqHc/0ygQwToYInEc5B2aKsfjscdLxYGRUPCKZ\no77uTIcFk50c4TghtEPo1J0AcSZNANWK2toticx0YKZhfwe5yvbZpO35Gp+R\nhRLuXSutJUffGQIK4sYFzJEGchLIOy1I/ZZsDDSeEdYf8S30UqSGTeTJd/J5\nmMV6h/7GqTCfgtGnSRh/cCOToUjxw/JhnA0V4OuLV/c51i1AHMERdbUzYe26\nC+hsN3V10Yh50sX1I7hjlqo+0aqQllrz9aU+pq2xxOzQSClkqihzLMobKbyg\nk92neZM6IbbtKzHUWmiyQuR5YZKxUEveSwedlqmbDCc55spHsW8jgQjJLGam\n0EzSIHdtXG9OQD7rL3KKvoFSzv7vEueNggcq062mVJqPxVS4K8rbFTNNxG1l\nlfSse/zaSv7MEbcx69POxAVqu3gjrPLZGgBmeP9fEnd8QUMExZhdQkTVAekn\n8EQzgsaDvFhGRgmxkWptKxFB4xEsIFSbHdrIFl4gLP2cdyIicK2dTd2bVPVM\nGb06\r\n=MkLO\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"1735df4935143435d34cc7c69088e393510d1004","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.9","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.8","@balena/jellyfish-logger":"0.0.71","@balena/jellyfish-metrics":"0.0.72","@balena/jellyfish-environment":"^2.2.18"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.2.5","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.5","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.19_1599171405219_0.6218930346296918","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.20-undefined-linkedcards-5a69a2c9ce2704f329d3aad206b4e6134f4b8906":{"name":"@balena/jellyfish-core","version":"0.0.20-undefined-linkedcards-5a69a2c9ce2704f329d3aad206b4e6134f4b8906","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.20-undefined-linkedcards-5a69a2c9ce2704f329d3aad206b4e6134f4b8906","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8008bc3679634c3f808b53130cb14c8c430052c4","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.20-undefined-linkedcards-5a69a2c9ce2704f329d3aad206b4e6134f4b8906.tgz","fileCount":92,"integrity":"sha512-l0xI5qEH7kl56IX/EwAitl0wV38H41WAqbknvKrw9Wm0Fa7eMhYneWVrUmN0ftibezBnIzDGBALsSjYqKPF1Qw==","signatures":[{"sig":"MEUCIQCxXBcHSaR3ZJVMHGJPcH39kfafQq9BOKL9RT30Dt1IUQIgQF8GCZ98RiJ9XJh2kGUaQPBKvj5/AaN2IqrfyPbcce0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":552845,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfUkuYCRA9TVsSAnZWagAAuPoP/1EETJETbnXF3GsXvDrT\n5ZyAz/guViMri45Ouf/KNCNpdJHZH7raxiZaTZ6X3CAAuXJvItkRHcM3+lVj\nKYZ42HTSUgvbg6Sg04qvCQqTuBcvHI4kYeO05qSf39Qm6FISRl8eXUGPv6k8\n8bb6D+t1KqWGetJAVdi5pSaTPRMaXvaF6FTu5syvgSQ04DlQ9IZ1RPv9lXs6\n/n83DdeEJBBPBZnG/PgpUw350Iya3AAYq2+d0N3BQ5b1zC2/EfhdC/k7Anzp\nIdNq+2pLD5umUkEZHOFuE6FzxeBBb5OvGAWwakof32GPUnIIfBYIMZ/TnrOe\ndNlZe308LoxJhCz+YAMZ1Fk7Q1+FqFpVFyO36L/3Et/TQkq8NjJBjOMNmiYm\n4zb0HmwqhYj3TSNfILXzcuOgyRBtL+KFNYhZ7LQk/xyMTRin/ja2Q7xG+lys\n0hfrLJcIF2063TQNQGj/xCJ/H5L83NlGH3HZizSVxcI76FhUU6kDZvwSOOxp\nGojjo1M4eV73OSf/nNqH+oNvHXfBECRPVezuztZo4xFNwl4mTnccpfy/H+F0\nkPmKCeupFQhWQnt4OTSevM3m12i4kHQqMzyoY9H5lD0rjbGNiLBKdEni2kAx\nkj22jA0xeFjcb2aCPQUACiLvEkJo/c6XpxGzxJ7LO5GxyuC9G+NLisHE/dWM\nWHE7\r\n=dFpb\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"5a69a2c9ce2704f329d3aad206b4e6134f4b8906","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.9","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.8","@balena/jellyfish-logger":"0.0.71","@balena/jellyfish-metrics":"0.0.72","@balena/jellyfish-environment":"^2.2.18"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.2.5","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.5","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.20-undefined-linkedcards-5a69a2c9ce2704f329d3aad206b4e6134f4b8906_1599228824249_0.4724905294963879","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.20-renovate-non-major-1b777b33da0f890b673ceacf194cc04aadd2bcfd":{"name":"@balena/jellyfish-core","version":"0.0.20-renovate-non-major-1b777b33da0f890b673ceacf194cc04aadd2bcfd","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.20-renovate-non-major-1b777b33da0f890b673ceacf194cc04aadd2bcfd","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d4aaba99e6be86193b974f62b59e36aaf491da7a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.20-renovate-non-major-1b777b33da0f890b673ceacf194cc04aadd2bcfd.tgz","fileCount":92,"integrity":"sha512-9lcXjxmCUF1Zk9o4gJgfEaxdMLIbMAv1qBMJjSNCii+yRgUN8aIDCAGHa5pNivaCa9koWT9SIxKZWA8FvFnx8w==","signatures":[{"sig":"MEUCIE2ODA/MKp+IYrtEswqYQBioaWNubADp/TUGBczqYoj1AiEAmuU5iN2ZSElMoEY/JZBEtcbqA5qovLrpwkq3hU7crCE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":552660,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfUkw9CRA9TVsSAnZWagAA/t0P/RYTdE2J0Lq8fZGVkuKF\nPsHQX/FcvSpad0KGsDGT0RT//UFXp8SWL9OBI4SS4YIgp0Bw6OrIichYjyML\nuqGjcKPXgAroOBpAyAi2JwnyQMLZvT5zeNrC8Dfv3PVOdQPOz6+Xs1dE2a8M\n/GfrznjiISOdi4bpuS+aTGicUwntG4LOA9R34YIYnnRGH+ED3CNwapGnnVzz\n4YkEOs3qfJtep0R8ATbRVd7mL86U4qHz/4wI1QnNaEaKd+qBKzgFffAoh+x5\n7t9aztOqCdVspZyQh78Ih4HiS+dw2hEMlIhhIeK1hdqd2DzbuHYCCE1glvjB\n1sim1eMXQ89pzfII9gEnImyJPumaFnbusvAuWEXJgD58FdzwxN/VQ2jTjvZc\nScG4c4u4VkML3BEHUlJfezPFXCtcqVNCPYL8Gq8jkev6FIt/9d3Es5Hb8olO\n28Lh8MDmfh7WpeqKyRTmDBioBfa2yyYJHL2fhy4u75a4tbIhvKyiFoyjz4IV\nLQQKXvewV5oYsbZxeJKaIt7zlMBugLmjrUXbHi6rWjUuno3TUkSMU3kPIWVJ\nwYfO/fzpa87IqTz6ed2dAZGlAskTalNRSIs+vVUz3Bpkh0WFtU2Nj4C9ByQ0\nFV6x54+pntphF0pD+Oy7ft+QGAr6TJ3b06YeBFqSr3ptP2DuhNImkf7B6ipx\neNGf\r\n=cUoC\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"1b777b33da0f890b673ceacf194cc04aadd2bcfd","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.9","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.8","@balena/jellyfish-logger":"0.0.71","@balena/jellyfish-metrics":"0.0.73","@balena/jellyfish-environment":"^2.2.18"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.2.5","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.5","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.20-renovate-non-major-1b777b33da0f890b673ceacf194cc04aadd2bcfd_1599228988816_0.16455296121586294","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.20":{"name":"@balena/jellyfish-core","version":"0.0.20","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.20","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"45c110338af82848d5f6bf55db28d011fe4941e0","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.20.tgz","fileCount":92,"integrity":"sha512-Q6A2b0Kx2lwFsLYtYKoGSuOIAazwx4WatHmrcAUdYX4+Klf+RsHn5Gra9V5deHIHOrd08ppIa1VBp5CRcBX2+A==","signatures":[{"sig":"MEYCIQDTqfmzRlgbBX/id6q+A1HAcK1ea0FsYuRrLq+7k/MQXgIhAIlgt2QEbnqqjhVUWjqbBnvXDF3r8baY008TGnneShiv","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":552600,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfUkz5CRA9TVsSAnZWagAAIiAQAJXU/+19/rMRbd3//QVF\nYGOBMNdEXVPLRoJjOCnsQ2ETQ5WZrYuxhUvYf5+9DP+qaCGNiOBAO0p3LopG\nkEIOj2ipnVYobIPkwAywHVyWDsdONO872FRbD9DynUcEPiHV9HaWWdwZw3W0\nt9Swhd9quXTI7mgHx1TBw3O2LibsRjwaxdZk3UAhfHKF1oWNvcvhqT8adm7U\nfKu7x0nfhV4LabtG/El+qUTrlgzYMWkG3lVzsFIof9YJZX41rtfdq2KDXIru\ndXUPJqtRQBf1m0sZo1xrN0HZST42SKPi3S9+OM/MiApAzIWyRP077dGdH9sG\nsQyBYZ98KklmR6AzV7+/7ZYVPO8L38/pm1JVjhqdWj5CEMIBkBYJQnUNSpx9\nzDQqS/nwZynpwN6ymcn3+eXy4ScwhZgeYcSChFu8YgJy8jOi0mXsf9iCrVNI\nECerwHAkqopnkJXLXB6gA3TtWB/mxAZUqmBt3VQ/hvNz4iCsHnCNQD50dwbn\nytx0A8gXysZtDgcEmYQUR2vx+ehmFJ08zyDfWjxCFg/3V+Hn4bzxhSma8O8z\nzKW8Z2isnhV/4Pky8Zkj2It5mwrkIYk66BQCIkp20xy0wYOLjBSPuWGY/M1s\nttdWBmA7V1lnDsW7xWnnpzCteTZAvQMqorEMqRrBEGXt1a+8nlVJtAK7OqFS\nDD2Z\r\n=CLtu\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"0115f6a0c41c6e0ac7ec499e0765d40444bf05a7","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.9","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.8","@balena/jellyfish-logger":"0.0.71","@balena/jellyfish-metrics":"0.0.73","@balena/jellyfish-environment":"^2.2.18"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.2.5","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.5","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.20_1599229176873_0.5615781171909564","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.21-undefined-linkedcards-39fc59c5aa53bcf246d49e963e0ddfd68a1ea87a":{"name":"@balena/jellyfish-core","version":"0.0.21-undefined-linkedcards-39fc59c5aa53bcf246d49e963e0ddfd68a1ea87a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.21-undefined-linkedcards-39fc59c5aa53bcf246d49e963e0ddfd68a1ea87a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ec767f0bc89529651483002c8a0e424bd973c4d4","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.21-undefined-linkedcards-39fc59c5aa53bcf246d49e963e0ddfd68a1ea87a.tgz","fileCount":92,"integrity":"sha512-Fh3PZBKfwutGiKNzpH+6q90WCs75fLUNRGt1Yzl4rJfwFltNG6ROkc5QT2Fh3E/ArsXVXySs79ksj/MoeCXmDg==","signatures":[{"sig":"MEUCIQCLoFY0kGwv/Bx/5BYBBX/KJUYqPp6tRFvEBGkSUDw7DAIgIO7sd0cPn+kyCsi1QimZpfX0zFMrwwiDS6t0ZVkhWto=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":552802,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfUk44CRA9TVsSAnZWagAAM0cP/3A5PcfdGFcORn5KxdDQ\nkCc+AVZecHbfVusWR6zIS01UGU5l1HifR3l24lOJnkGMalT0enVg8NcLgriV\nPc7s7b2nQ2JKxX4FweqTHUcFbt097MrpzewT1+VziuHo4mc/nyhYHFsxFGy1\n5K5xevWe8vPiWNBCuscf2Pc7otA6aG9igWpe1K2qk+AZgrOMgJWpiTcKQygG\nEezixbKSZjAMXD+RTSKdpF0Qgrt1A/9IalHgbY9ApkIPSH5alDCbJEWk81L6\nezTKLSQpdzWJ+qPTMgV/FRFZQanjjubnCLBMui++EYw+lBPGCZFbeHM99167\nIfwqNhBTt9HkdXVuLh0a7aSEJFRNqSLdKve32HRowYUExjgMnT/cit+cMqEm\nSViv4COKYYI8nm5iD9M0i5ln0mrQX7yIi1odImd+m2xCS+WfK5SXzWVhZgdW\nmnRtw1xhRK4kUvFrLA6c3ty9CRCMwyzxbObknG108vnVhO8i18Ag1+pKtYpa\n84Nu8mUlltFlZULSWCHFSAdhKGTcMEoKhJmF/1OSsTpnlQuLRtL+qk3Vo/Vx\nPaH5dJDXt559ZTKey1ukxLDYixU1iiO234V6ZaFdnVi6qtptUbJsx85Nf+ZJ\n04l+OuyhuLR+LzhO4PmAdgxE9IXLucqW+OuZHSWIb8GQOgqeCXBTM+2JzUAx\nV1BP\r\n=uAj3\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"39fc59c5aa53bcf246d49e963e0ddfd68a1ea87a","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.9","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.8","@balena/jellyfish-logger":"0.0.71","@balena/jellyfish-metrics":"0.0.73","@balena/jellyfish-environment":"^2.2.18"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.2.5","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.5","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.21-undefined-linkedcards-39fc59c5aa53bcf246d49e963e0ddfd68a1ea87a_1599229495871_0.9026783638599067","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.20-undefined-linkedcards-102f8c14e81b8a95caa479af7160c590d9396293":{"name":"@balena/jellyfish-core","version":"0.0.20-undefined-linkedcards-102f8c14e81b8a95caa479af7160c590d9396293","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.20-undefined-linkedcards-102f8c14e81b8a95caa479af7160c590d9396293","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5125eeb76f064208ebaefd46fad4109becff8466","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.20-undefined-linkedcards-102f8c14e81b8a95caa479af7160c590d9396293.tgz","fileCount":92,"integrity":"sha512-ZGMWpmP4VW4KuSF7KgENT/pBtlKiuhok46WA5MKQP0mfmMbBB1yBcUbhNf7zKvLmrzhQ7OYGmswpS8bSACbZZA==","signatures":[{"sig":"MEUCID+NItaeL+WDaFiSbj7DdBxQgOOG9RIs0z6FQBm4q1zZAiEAqmSgklWOI1a6pvBUuHufSc29eNA4AXUC2B7swJBuszA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":552717,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfUlDjCRA9TVsSAnZWagAAXOsP/1wU7iyXUDZVV9wIytFY\nQg8Q3qDjCBX/tc03U0FYA3DHVdQ3C9pdMXH+uezc6GrA4i9w+7qARGBCtLVH\nHRvVSUvHIQT3JQLm9bhSm6QAn6BL/udVDfBfVzlGRWPdUPVl6Uvu58T8XNJ/\nNrdQnM5tJ76MBzpeaOZp91PEGjxxR5MmRzYksWn4EhPBi2me8UKvuFcRVfpW\nUYBGAh31XpPMhFYtL2ajX/9+pysvuBYNSmpJ3eO5ltKHXbd0dUN9D0kABJkz\ng8oo3m563z/oZKlZzsSpTCLm/V1Bh4+7Z4OXIlhJ27TL2Tu1WQSwAyBruQB9\ntjnNs/c+cB8cQyXscW160JzdHStVSdxBd1qi+OTREyepy6OiXIKk2NVgccm3\nLu4LfswK1Q+sWsccWIRZKwCOjl/6MXExlPY1ukIiakXlOtGa8MLjqMqZ73t5\nv8iB+9DvkrjaHY3ZDPCpCOxVKK3/ZQfx/jP2Si8+ADkNpibf0WiVXSVw9xTd\nEn99xmcoZ6AxWrOJPSFvwhIT94+fh9Rq9lOJS8Px/3rriQC8k/tyEv5d9m4u\niqmzlmfy+9pccW5xyxG8J9f1RYC1HQBL8EBnl9obAjyFISEf4Sj2tBYsIc+o\nHMR4gfptm95p8+L8UGNrXlDSkN1G06wr1hCxyGkd8bDLugYRaCZvWfw/y6Iu\nQE0/\r\n=QCQe\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"102f8c14e81b8a95caa479af7160c590d9396293","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.9","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.8","@balena/jellyfish-logger":"0.0.71","@balena/jellyfish-metrics":"0.0.72","@balena/jellyfish-environment":"^2.2.18"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.2.5","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.5","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.20-undefined-linkedcards-102f8c14e81b8a95caa479af7160c590d9396293_1599230179442_0.16082044409908858","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.21-undefined-linkedcards-456b9e574caee63acdb445b0fccb2bb4c9e294a5":{"name":"@balena/jellyfish-core","version":"0.0.21-undefined-linkedcards-456b9e574caee63acdb445b0fccb2bb4c9e294a5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.21-undefined-linkedcards-456b9e574caee63acdb445b0fccb2bb4c9e294a5","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3d895e0226141da03fb96b2837c47250b568ee09","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.21-undefined-linkedcards-456b9e574caee63acdb445b0fccb2bb4c9e294a5.tgz","fileCount":92,"integrity":"sha512-kxK/uVPF4SoFN6JmN+39i4Wcre1T7C21DXICpze5M/2/XW9vSc7HOQzm8QeIX1FRucnaMPGQWgRaZ/9eQsVOOA==","signatures":[{"sig":"MEUCIF2aE41ICbwpdHrPiGeA4JD6+lqx2FmSeAW1cl/5CP7IAiEA7xLhj1yBo5bkD3m3/N0XE1FDAEcjXYQxTXovnZ0ETLo=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":552821,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfUlJlCRA9TVsSAnZWagAAjQUP/2NwRpEyV1N4sVriq4SB\nBlO8awPMu5/wfaU39ynbaSiNjS6UOzroRymlJV462VfalVt6nqohNakzYhPW\nPO6gzsrqo+kzmEPNygLvsh3GSkqfCzEEEA/+UIFanm5rGbIJYe3Xz0BNB9XO\nqwlOa1GpmqhRPCGK9AlYKR81g8WFDHDcKDKGQFlR0k47LFFrBxGwttgmDHof\nGfUntS3+Ylm/jUI3fpgQx3Hs1pA7oRHriWBJR3prL5SDlDrBObNuWBjC6tu4\neGfHnD94k3sK3QhzOjr3iFoGZ6y7miVeo4WCA5SgKgD3pEgI+A8ZowWi9Bxd\nTRmS+HQeVkMbUR2GOMUq1BKACITd32zP9IdZ2ULPM2Cbqlx117ClxTGQMAUV\nIAqn0RcRXytuYyWGRWMCj6yRjOrqf6kXkYpylMQWsuvIYj1F11Q8pG3ONlz/\njuNe5IImfrnZ5Tis9GOJLVJEpJHpmEGgDD+19W9AHxw07E3MdlYI3Gk1rYn0\noE/c1ZhbW3VNe79tIC/u4kD5iNRx3S3zNzKFnqTn/Cq400xH4qycsfw2ZoLF\n/yISdC8Q4CxQP3e51+KQXqVXZAigpZ+qtl5mnkAVMHHa4xabfLTfGGSRJ92G\n6h3DTGaydNQFHkzqMAa3ac05TxJ0/1mx2YInxGwfxLxK0pI8yRpdxTnyZMFL\ndTbP\r\n=cOc6\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"456b9e574caee63acdb445b0fccb2bb4c9e294a5","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.9","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.8","@balena/jellyfish-logger":"0.0.71","@balena/jellyfish-metrics":"0.0.73","@balena/jellyfish-environment":"^2.2.18"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.2.5","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.5","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.21-undefined-linkedcards-456b9e574caee63acdb445b0fccb2bb4c9e294a5_1599230564800_0.5466395170308371","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.21-undefined-linkedcards-e9452c2601d1c3e61a23200f4f3f9bab7a092394":{"name":"@balena/jellyfish-core","version":"0.0.21-undefined-linkedcards-e9452c2601d1c3e61a23200f4f3f9bab7a092394","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.21-undefined-linkedcards-e9452c2601d1c3e61a23200f4f3f9bab7a092394","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a520511f247cfaee3eba1b5bd69229d185b426b7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.21-undefined-linkedcards-e9452c2601d1c3e61a23200f4f3f9bab7a092394.tgz","fileCount":92,"integrity":"sha512-thpFdH+pkJAyco2knONMcQ6F3qKBqAkMzGnG0V7Xkx82XGl2U3NlvsWmVbxjvzdh1CqaQ1PPWooYANev2m5qBw==","signatures":[{"sig":"MEUCIC8OIMPHTDneA+HXRjoLnUNUF0N56n6qyncE7pG0ADSiAiEA609DVJmHrg7HOj5aE+YN4cIJPyV2kk6k18/sDwqwwlA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":552810,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfUlULCRA9TVsSAnZWagAACO8P/RYjn2Wf7fDDNhytJT3T\nSB1JVhiL5dOsYUZKwfcUzU9aALYPpHcaBozI9Nvd23tX2TBk90tHPijv4QGy\nxEXsPXMGTkA0T0QrWCaUDpo7TpBJ1B92ol+eP2Wse584xdD15mh/Xsx7ER/8\nHZJfXIRVlMrb3L0VDoUH0weIZb3ol0doVeoXDOTiGhVL+inQXUnOA7YNPDfh\nrCeHzuad6DjBUPIcRLjZEPpvTV8iCnO54fuCSukftj64ovCrt6ebuKauxVIg\nOHmH9iY7UxAsVcbgTQ9cURuYh+nHed65YEKnBmu9hBLeMjkPengX3yDn18Id\nlvOBIWJT1WunB9U1TYOFw/jyI3C9rsdt0k6yvM7Dh0puwiiyPjrAs9fhWJQh\ndzf+mheJsjdgtPc7BniiBAfuRCz6/1LIHeV2RosqrljTtN814yRsJDAxXQID\n0z3u119ksAbEv9osyLfBHaJdpbr/h04PrYwlxrfjVs0DcrZeOzImFC8W8di9\nWmsLWV1kLj6OupVtgJUodTq+6fGplfwgzvhrIOQ6kcyh2XEYKZrJRdh5+v+I\nKZNGl12HJ404j8j4QUKvUN/ndkxpBYCF1WNslJYpJzXbFyQRuy29MAoMDLcO\nU1cMcACe43JDVKV/1Q0q/Kjs9UiSQQ6iQmokaJQis3aLcZ1q12AR64eEPpBq\nbydo\r\n=Nk4i\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"e9452c2601d1c3e61a23200f4f3f9bab7a092394","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.9","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.8","@balena/jellyfish-logger":"0.0.71","@balena/jellyfish-metrics":"0.0.73","@balena/jellyfish-environment":"^2.2.18"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.2.5","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.5","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.21-undefined-linkedcards-e9452c2601d1c3e61a23200f4f3f9bab7a092394_1599231242587_0.9303246851906737","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.21-undefined-linkedcards-cc7a7c53517793431daf4f0ccdaf0879f99c320a":{"name":"@balena/jellyfish-core","version":"0.0.21-undefined-linkedcards-cc7a7c53517793431daf4f0ccdaf0879f99c320a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.21-undefined-linkedcards-cc7a7c53517793431daf4f0ccdaf0879f99c320a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e12eee03e75bcf0c5193b232ec99a5526a1440cb","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.21-undefined-linkedcards-cc7a7c53517793431daf4f0ccdaf0879f99c320a.tgz","fileCount":92,"integrity":"sha512-K5clRQpL38Mo+3BzjYKeJw7emJkUG1UffswEMa1taUgmrII6eGMqF73lpIgyA12BGr2XzjOw3aIbnZtUueHDPw==","signatures":[{"sig":"MEYCIQDP2Vm1kFbHXzf8BBtaieSaBW4aGudmHCMXXYDZUKiyRAIhAK/DadOpGpmtsmfD1gLYfx+YXDvGoDHWJvp329iTEic/","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":552809,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfUljMCRA9TVsSAnZWagAAiJIP/0htKIQeUIESfjoZ8whX\n6JDh9g2m7NN7nyppdjS5Gr2icuDbv3qBElhS3PFnAeZ29bRcGjR/t3G33XYb\nuSNfB9iVZ7ujGXs8SUEzGEM5iQwq5AVSzooI5hvS1kCUslgD4UC7xyQmQTym\nS2jdOMvBWRtemihfod7eRPqgOEGBUpAZWl3rqNEXBKWDEYEiz64T9q39cTwK\nD8EkYMo3NqqQ1Ma4C++QnVStfLDcqZK6XZVD9QCG/SPeVUeUtPrUwp0jx4zb\nQbHTEPZ8Mfy3rjuhbwF0uDMfTpAXoblUqu7bO7dAA833VHmDj2ZBJuqCTUnw\nuJS/lVC8iaZKVBLsoPEdGePASv5XPZH2bPjitgts/a5/e908B/dcHv+9naW5\nKKsg8uduuqtHiQtZa2zEdMlaRe6CLTrQef34mS24qig5WH5WV3c8AoM7nfPU\n64NMKdptz+49wgwHk5bnbLpmH4FYL8UKgK8rzHV2MRgDNUoLs6rzwT4GJKZC\n7pVxyKrnShbgds28StUsIQQf5GHtOWhP9F8H427MA3iqMtyKwGS8BOF6gg8B\nkV5rG/0/64XROTzTCPxFvl2S+iy5bNuN0F2bkdx8YClRfUlmM/GpDfcs0fzC\nxpL8oIoEYq1NoD4VsOaadRinSadCeGRKZn/olJ8wSRZRp5VZBFjlcjfA4buj\nbIet\r\n=eNAB\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"cc7a7c53517793431daf4f0ccdaf0879f99c320a","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.9","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.8","@balena/jellyfish-logger":"0.0.71","@balena/jellyfish-metrics":"0.0.73","@balena/jellyfish-environment":"^2.2.18"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.2.5","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.5","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.21-undefined-linkedcards-cc7a7c53517793431daf4f0ccdaf0879f99c320a_1599232204264_0.2848468009259879","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.21":{"name":"@balena/jellyfish-core","version":"0.0.21","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.21","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4d036e6ac18eb0a9c13d61fbe406c292f56329ed","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.21.tgz","fileCount":92,"integrity":"sha512-YL1feUh5GvyzMsaOj6Ivo+ZVrPZkJeu8h1e/9YPlIGU/v/06S9dZFwo13GUhvXfi9DiGFdc6vSF4mblFb0KzIA==","signatures":[{"sig":"MEUCIQDxWTLRdDJqfi56wNE8QORMPkGpAlEaXb6oh4sQnjpzSgIgT3F8ys8CTzWHr/DFMGSEI6PgZfU5TVGUEgSw6uGjDr4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":552746,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfUlmsCRA9TVsSAnZWagAAbjQP/1TzBD7zvW1xB1Xxjjjn\nMItUmzkT2/MGYq8b0MoJGiiziY0G+8ul1xrEK2YplXFG4byRqiQhQ8zbs5r4\nAWhzggVgEO7wf9Nb+IvklU7StszsjGLASALElN37zYToui8HgifN5Rx4MeDD\nsGj8KGxa17QhdPe9PxFtOCuq44MFOq2kAA9O7gvhxz37sCQZmrCdB7uXZkvw\n04vD3ubFVW2JwJT+UsDXWt+qNoXVbGsD5PkRz4axG5/ZrEiXMaJpshIstxuk\nNz57P32lYL5CBKH93s0NJv458ukyYPyVydAcym7hPdtAU9AbrgY1nNZE/xQh\nImnmouMkPj/co/DEiN64eSA9Uy88qHOADheSdMEvIDNdRmkF4RWUIfGWKibJ\nM6ZeCP3juwzy7xlCXmo3zoLEQC/PWSsyCRRa+emGAIEp4qP02VoAv1jTPRzH\nzP4lbw2cfTseJXWEwXfxsEZv+YvHDAYs8IiQJd2YUgbiT6+CQqiCBxgKbGp2\ntcqR6byHhpKakqu615Fb4p86O/q+tXzM+M5ntPoYSJ8ougSgwRu2isyBgGU5\njK50LTqrQJBWdFWXz6XpMDTDvp7WGljhwK7m0HNbuM3SSTyBEoFFhhonmDWr\ndqjZbxng1kf1TiAnkuqNf2h8G2LVrnr0zpqkQ8HzZi7uH5qq+CLgIiwiP13o\n77Ww\r\n=0KaX\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"9a2a22a89e87dad9ba3a5d832ccd3022c47bf4cd","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.9","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.8","@balena/jellyfish-logger":"0.0.71","@balena/jellyfish-metrics":"0.0.73","@balena/jellyfish-environment":"^2.2.18"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.2.5","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.5","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.21_1599232427696_0.8444705288071701","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.22-handle-null-linked-cards-c3f52127c0b3541ff83f34798e489d0ebc196017":{"name":"@balena/jellyfish-core","version":"0.0.22-handle-null-linked-cards-c3f52127c0b3541ff83f34798e489d0ebc196017","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.22-handle-null-linked-cards-c3f52127c0b3541ff83f34798e489d0ebc196017","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"fe12bae548527b1908358aaa5e42f5767dbe09fe","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.22-handle-null-linked-cards-c3f52127c0b3541ff83f34798e489d0ebc196017.tgz","fileCount":92,"integrity":"sha512-gXIc23kQEItJ8mAR65L/RFcgNAa70BcGRPTQxRyFm55u0Ixk2Jhc2rahfuBszmMFRGUEezvgMWAvgCQbWOXwAw==","signatures":[{"sig":"MEQCIAXYT0sTba+NWxJ5np4KuzN5TjMJjDMsNBIiPy92qoHrAiBOFvEG2G51Ne8vrXwZfZM25D4baoHGr/PDDb/jdzinbg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":552881,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfUoS8CRA9TVsSAnZWagAAC7oP/1BVC6tCEXOo8hL4tKVz\nnHZUUaYpDvT8BTPpHg6oGLNoITB4ZZ0OB8dd0Xl3szp74IGgGEnfhu2maajD\nhj6nGtIxUIvmNtRRK+iS629pjFpduWq0NT1+zCbHgx27dWutjIEDpiaB5lKn\nRpgBE/CXeFSyfzNaIdLwQ6lvLsuZK7Dics3nb3x+gDMKCz0QJDcdfzppEyHm\nJWPFqNwScZnmAKDIT1LQC8HPS+jw/cF/+xVgsjD2xwcWq3r29tTbKWFO+xFk\nCqHUMFugnb4zzIyYkfnXkISBJH+k9kcbv/W6nwgCjxO6rKyOvfmkGonnrXj7\nv8geb5OjijoyV8nzpEdutX6sOrw6/U3hxPeI2q+Yzjmul/XK8v9EVLBxdX4/\n9hHHMTkuypKlVGSTJErMm7xf2rHjMbDcdHDNy7I1NuENDcOPsZCwvkWFG7h6\nYAOFnpv/BMwbAsibxgUhbbn+kmmmInomJ5X7gtHFzNwWt98Ye08tpz6/qFdN\nXstcTOhR0ujTK+Zjf0nHuKREW40SNFEV5sJsOLOTi5DvvXmm8XQhTdtFKHPZ\nDcRONkZXgt/9Q7n0jdp24qf8AhncHHz5boLoVOfduPxUBYWqnP/VTOKi5Obt\nH40r+x2aACJ++Qk5iT4VOu9QY13LyLoBnx+tRnl1F5kTf8tyrH/XM3BuqkSJ\nSbPl\r\n=j+Ui\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"c3f52127c0b3541ff83f34798e489d0ebc196017","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.9","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.8","@balena/jellyfish-logger":"0.0.71","@balena/jellyfish-metrics":"0.0.73","@balena/jellyfish-environment":"^2.2.18"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.2.5","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.5","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.22-handle-null-linked-cards-c3f52127c0b3541ff83f34798e489d0ebc196017_1599243451759_0.6396854705429922","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.22":{"name":"@balena/jellyfish-core","version":"0.0.22","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.22","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c14cfbc4200ce838c68cb8d4a1820117368dcaf0","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.22.tgz","fileCount":92,"integrity":"sha512-XSnYiWRpLyaOtKptqjZmYSBFiFRjRML8QUmmoIvK/LTPC0DJxpb1ei3PxTPmxS3VTjwPId+JnbhteP8s42b41w==","signatures":[{"sig":"MEYCIQCIVgSA1w/01OSHtZpG2ms8pzrswEetuxG534jB4W51yQIhAL2y3nEpP3nEFwLlJWeYxm8ERE2++HBhRcY0DyKOyH16","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":552815,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfUoVqCRA9TVsSAnZWagAAtTcP/0m2oL5slBMFcJ2dT2Pr\nRSiB5Ca2ZPKjt+ysIH5tR5kfP5XpbkqAOT5lZ3d/1BMC7QviNN0viM+q0+3S\nt7elPgWBc4/Zfg7gM/97FlI/N7mhdPPUoJBFZ/aSUtbaskWsHhd6DRqMhMsH\nHn3AMN7nxEKuVVaFJnUR8fwXBGo2+lFwQUFVbeG0Yy6cGpmXERq0d5PiT1x4\nbzndUKMSfuy/IwoP/v7f/8TsccQe/gZIWF1ZlLMCb+KBSu/1e2nShedSzFPd\nGj4Smu0aADPC2KvSfqACoVQ6Hj+y2IhRI3jApSBAH84KFOibIObJnfCJ0rl3\nr9GbHaHk2MjdcMqYhzm7SEuJCTUV3PnpAohDsiPsCVWh2PaPWatnqaxsaG2f\n7K5kF24QqdignCebY0+ELfApoenVBso5j8bq9xDxZ9f3tQ2nz/IRhrCILEB2\nvqrHYg3vC1ZfIk2VnqM03NU2XRkHaNgBvX+4E/+chajKngngqJue7gjFVjBs\nL6PUMuxogTlTsTSd4EjI8/YmNDFKihV0iz+/ThMswe5wjJX8ZxcHMlf715rf\ns6zHi9OzUWJddxE+CwFDS5C/Y+XAQRrtf87gb6psO6AQlWec4DaQ/rWs4hAf\nvXUQwwkRjCjWnDAYL9JT1XxNyQy9AarjiJ8XHHV3wConsJeZNrRp8koypq1E\nymNp\r\n=ihhx\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"bcf2530318b1299978281fd9a68429ab18c10702","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.9","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.8","@balena/jellyfish-logger":"0.0.71","@balena/jellyfish-metrics":"0.0.73","@balena/jellyfish-environment":"^2.2.18"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.2.5","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.5","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.22_1599243625935_0.6672517377551592","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.23-renovate-non-major-ce150eda592c3fa5c75433d3bce4304c17028a81":{"name":"@balena/jellyfish-core","version":"0.0.23-renovate-non-major-ce150eda592c3fa5c75433d3bce4304c17028a81","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.23-renovate-non-major-ce150eda592c3fa5c75433d3bce4304c17028a81","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b5832c78fb43dad3e1e2b480c30e2f63e5293457","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.23-renovate-non-major-ce150eda592c3fa5c75433d3bce4304c17028a81.tgz","fileCount":92,"integrity":"sha512-sJ/Kg6f8CFnFCvUQ0T9oJU3yU3fnMbQrGWpqwjUvUKOj9n4UJLGggLbcAYCcvWUrSTP4sWWNm8ksDbGtUyGNdw==","signatures":[{"sig":"MEQCIGulz4QAltcCHdIgCyAtcSnGW5sKgguJWYq94+6OGYDLAiBSSBFXkMtBO7xGOJ2W0c+/DS5THQvfxir7+u+V204Egw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":552942,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfUv7YCRA9TVsSAnZWagAADr4P/0JpnEqhCyxUu12mAr7c\nhhkOWqusprG47tXt8mHlxPjIszrpP70JJWAUr8BGGRms18Ljo9tPcZ3bWMly\ncsdefYHM/QTkp6zLWZv2O/AcMR+t4wtGJu28kOtJe/6ie+X+OP9kmaKkHbik\nVcICJE06Mghz+ohUItMkiIKQ1F50oNaTTL5aYOQSXNYtb8wY4zIQDGjhtTxV\nOh41O2UyHtBXkJjgN1PED58Mmr9FQUovwb4rQMeU8YepR3vDb8OLGvkkl48G\nKR8h3oB4jrvigYh+GECuOGA0MZTN7fSsJQyyx3HzStp1qmdM3cbB2YxLbPbi\ntsIqbBDXq13j5DRoKA6z3UHcRi7xYMUvw4aWO8MphkPNW9laUzZGuxb4gigz\niETKKXavsK9cUIc/Vqfy5l12c7IGJUu9MhSyBBoo2faDRt0mlEO0GgEMdiN3\noZVXWBVd8zKBxJOnfIlvLMUQ0OiYp/Ls6ARpcA8xnzxWqy9X1Ez2oD42FG1z\noqEPJQ1hq8nmejXdJ2FDW40RXZ6ZjPLtI2S7Rmvd+JuybSfjZFSGG9UmxoiO\nx05x36IYkVHD8en1pwvhZbWKrIWhExX9xwD5yTyfD0bN6mVhOP489Og+4N4Y\nlHBYJseCKOgPoygEtU/T6GYIa9HgrJZjaFwfYKWlP4ofTUcsUV1Nfn+PYknl\nLAUF\r\n=THmJ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"ce150eda592c3fa5c75433d3bce4304c17028a81","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.10","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.9","@balena/jellyfish-logger":"0.0.71","@balena/jellyfish-metrics":"0.0.73","@balena/jellyfish-environment":"^2.2.18"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.2.5","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.5","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.23-renovate-non-major-ce150eda592c3fa5c75433d3bce4304c17028a81_1599274712036_0.6602659466597613","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.23":{"name":"@balena/jellyfish-core","version":"0.0.23","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.23","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"38168cb2b6cc410a9f9e750bf2124100c88c8dbf","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.23.tgz","fileCount":92,"integrity":"sha512-KuONVJjuIa6/UgImJyG7bfB9QSqT4w7f4XxHixZkoqtgwnA9/RIF5nAaCyhXH3pCW8qtUprioo6UaAnAKZ3X/w==","signatures":[{"sig":"MEYCIQDWErXDBpEtUrypOl0D+KbJuRsWJhi4rK4qp4eRDyzQpQIhAOtElwvQwYmzeIuxipTJ27wpDNyN2WnJYPIE2QA7PxrX","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":552882,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfUv9/CRA9TVsSAnZWagAAxBwQAJW8iqPAE//Cyvi0rMpq\nlmh9b9qaTNeqSw6ct7UetqI90j09SfzX1Rvv/Kc2bLYyr0Hu0G2wFEsWoeDc\nXBbfCmlvpGWsFgPOFTwI+oJlUOsf8dWKaDOzvu9eRTELDaq+5RLi4F5zUvFq\nM4ogkEmtLYTFIDiIAEbfbsArhQEFvBAGC+k6jJC1yWxYFbmO3ELVq2C+PSof\ngNsxHVT2CsEQxuZsOlYVu3TfF2CoHCnmZd4iw7OUmJHS/V8kU+OZRpMrV+Bi\nRLtg3LiL9YlhiNmB+75Owt+eijoKsdqvW8emvXYbFneDF1Oa3cGe1zt8Xgg7\n/NpbTQoMe1C8JUuidLhOOPz2JRM13tNibS2LWrW/X5oYGvMNwd3mPXIFiaS5\nrJ19zbOw4+tuGKIMHnHjOkcyOzWunjp6EOunMXZ9Qor/GlOY0KP6u/9nGUAR\ni/n/GfL8BbF52i8lxEWiWqdnxZr4FjvPyrT06BaJNtloyKoWLjnrn2QdTslw\nEGlMSsNkazv8kIav4OJ9Y68xFNVdyGkHA8eTFE/Y7QABGRussP3rsHRjP9vx\n3zz3m88Xj5Z9tifFyf/iftfkQKhHLwv+RAFgOdHlUn4XK3xdRAvIVVQ69bKZ\n+L5fBYMo95xLywS/xBDZFAFBtEQt3L988a29VI/F+Xn6L7mQsDIHEUJ9Yxa1\nwHCC\r\n=+vUM\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"0674d43bed719a57a824b1ff2c737b3183085453","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.10","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.9","@balena/jellyfish-logger":"0.0.71","@balena/jellyfish-metrics":"0.0.73","@balena/jellyfish-environment":"^2.2.18"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.2.5","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.5","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.23_1599274878593_0.4121183620272395","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.24-update-make-test-9cea8f690b8621f00ad9c56e995bc53f16fa9855":{"name":"@balena/jellyfish-core","version":"0.0.24-update-make-test-9cea8f690b8621f00ad9c56e995bc53f16fa9855","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.24-update-make-test-9cea8f690b8621f00ad9c56e995bc53f16fa9855","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6788339d7e800c45a47af38a1c00260787fcd035","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.24-update-make-test-9cea8f690b8621f00ad9c56e995bc53f16fa9855.tgz","fileCount":92,"integrity":"sha512-bLTwP0pcEeG4bOG2Llk2Zqq1MKhKjwIaY1ddZNFMQPBLQ8Ok3E4Cb8RKQa55HKubAnaAKQJMMLZ2H4uua/NjCA==","signatures":[{"sig":"MEUCIQCEP9gii9PVTrjVAxaFyysFrOsencdvXA5sH84sF46RswIgSIV1H3nBIRr9pINEGGe6vJZalEmIxcBy+zQvx+oXN5U=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":553077,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfUwSRCRA9TVsSAnZWagAAgu4P/ArdGTa5QEcEdVTunHle\n25oD2QIfgvngRx7LQi3eS2zDJQMHYwdOr4Q1WuHvuipLxq8XdW12fnNv591/\nhYallhiyY9gCLRpSdq8EgIa2PBHMe9/tjauV38QqjzzsDRf7TtTjfagRHEpm\n+WSCI0AalkPHUvTsQA9pC3Xe5tg9vQwFz1N9Dc+pJlXOKMBxd8H6RwqLgA5u\nZcQP7xnKdS03ESHZL35hkNkr4tEIQHmmMro2JIaZO/NUfk5o+DQSZoH7zMj/\n7ondyxLWJs6ZC/PJyJr8r374uBPgNuQXy863dQf/x3M8k/BN1lhV5H98A4U+\nkIocwHGM+H8OZcC+JjiQMLS0HRMH1jtraU/BDzEtUfBDC3mbk+PBa8d+F3gk\n3G7OHPN2ISn+DhmoVqcHN09+gyDk2K2sQuCQ9Ng9n0SyWN6/dqtNHJwQmHRw\nGgKt+TbKukyHjJHcKIOqEkQpM1HWRU/qUnVsHT0gVJ3WS4huNFiHfSJSuv5M\nwhNeiMjLXC7jk9RqGtgbvyIYbu9NY1oBj7p4/rPe2SSDJLQcviBU4hS/kR3Q\nt5lL4r6VVCN9IsWjsClN5xhGzuXqCd/Tbj1U9irCVg0syoWRQpOjwZYoTc9F\n77l1x3OIjWIIi457EgqAHh30wQ+byy5KBPEKgy5veKS+GHEwY/NGh6/mLgru\nYfcu\r\n=OhJV\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"9cea8f690b8621f00ad9c56e995bc53f16fa9855","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.10","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.9","@balena/jellyfish-logger":"0.0.71","@balena/jellyfish-metrics":"0.0.73","@balena/jellyfish-environment":"^2.2.18"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.2.5","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.5","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.24-update-make-test-9cea8f690b8621f00ad9c56e995bc53f16fa9855_1599276177045_0.7349543327128409","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.24":{"name":"@balena/jellyfish-core","version":"0.0.24","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.24","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"14be52eaa655340fdb0fb7069f369d2e85ac5162","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.24.tgz","fileCount":92,"integrity":"sha512-ukU5GSsqfeyKFpDQ12GYXvWmrKTKsjU7DOxi2jb9H00fBT1n+a8GBZs19d48sEL3SO4WHaTfoGSv4k2QPHIIvQ==","signatures":[{"sig":"MEUCIHsq+aBSMZZmqdeTLgr4bVRhrNgPsiEhPtQPE07e4iFvAiEA2HhztOsqex5l6OsO8cJ92+zDaVZBz05PgqfjkAm4qnk=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":553019,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfUwU5CRA9TVsSAnZWagAAQJ0P/AxKZENcYntQRPOnXOR5\nKG1ZWCmXI6mL24ojo494GN5K3Up8CoLHx/1vf3/KAIiTiI4zmyg9bv0OZJGB\nGcReKkZmIGJLnGvw67gZyTdjlrJC3Y58Cl4h4v1QGoyZSizFhXcIEYAkG6tt\nXQEfwpU7OYBv62xGFZiRnF4w5FIdamZOBLBHWpyfRnVcovVRG4kqmTni3GRX\n+/WZQa2MEvoPGaWVEW9eJC++nC80Nxj1UutX5hNYJfpms35oshV0uhm658XH\nvJwtWZqT/yDSzMB0AbpUPrH9SN2ocKIxcVKrKBOMZfN2wN9ZXp4zdpG4pQiY\nXAPrrCJRoYg4415Tuz9+iCoqEfX+R736Lqt7IUUXhZVqCI+DvqDw9FLaSFjW\nSs8z8Mb/5E0c1TvnXn35W98Y38ZdsX+WDWqqKDOOD7lfn27ULRyZbredjNW0\nmp2RSg4mTf9Ac0CLe7pwntjxfQTr2RQxgLwoyjWjCgpqdu1dFrUbJpOFLtFO\nUxWQbveyj7D5gxK/DyNuSZSdgRBVqenCxtynqSjGNl4B/nwcHK75HlRYknLP\n9HeTSAH/ZSEorNkkibVVNkUm0xbDnBKCo8EPcecTgi6fAYvrTIR6kAggIgvw\nfvV3TLVh59xmGZhNxVmzBsk0ltMly6bchQfiX9a8P0SOPSM3LoO7oYKgMCS+\nW/9r\r\n=Dm2R\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"3f77d8d493d986c5ce79454663dcb7699daa0149","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.10","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.9","@balena/jellyfish-logger":"0.0.71","@balena/jellyfish-metrics":"0.0.73","@balena/jellyfish-environment":"^2.2.18"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.2.5","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.5","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.24_1599276345189_0.22498128124730066","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.25-renovate-non-major-f8f48dfb9b9ad904804feb11f52bcb9d74c19dbf":{"name":"@balena/jellyfish-core","version":"0.0.25-renovate-non-major-f8f48dfb9b9ad904804feb11f52bcb9d74c19dbf","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.25-renovate-non-major-f8f48dfb9b9ad904804feb11f52bcb9d74c19dbf","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5cc80f1aa87eb54f2ad5538b6573d4927cd55354","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.25-renovate-non-major-f8f48dfb9b9ad904804feb11f52bcb9d74c19dbf.tgz","fileCount":92,"integrity":"sha512-N272s6p0aTsvNcuREV7cav/6JQN7pL44NYPbnIWG3cl2V8u5G7Op1kEX+/19NaEGXLojs7C8TenIgSKb+cgh0Q==","signatures":[{"sig":"MEYCIQD+T0BIc4RBlBM8OVdIUG9gs72RpBDDSFX8zBlzCjXOEwIhAL4IH/XXzECifoVenUA2c+bAupDX5ncLPmIqCGzqkR2e","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":553182,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfUwaICRA9TVsSAnZWagAAl3UP/jcV6nCwYqoJS3OfexF4\nZhB71iis1Wms805suX04HXaC8IzEJ+CY12F4aSFMIZ/N2G+Lf2yW0C5mXIkK\nQCSpHVyMK31oeBLIkZ+71umH49thpBcrGB1rNpP7xcj+pkj6lATT+P+N7GX6\nHqQg/EHy8vuT5amZ0ZPWpiGwhYIKw4tKNuY2RHJcZ27D2KIwnvdZvuscigcD\nyeKxwORudQUGOOTEczSJGr+DkFp+AvyXL5tkU6XcB8J41fhOiyyQBsUNxvdw\nl1AIVdurcyahNHrNA0SyOIZZjBa1Aztjw0aHxpuuO/wKi81nKVgtQa+cA1JZ\nPWeHhW15+j3oyLAcSJBAWiyJpG3nrXuACsRwQ/ns4QKqbK9pgolg5gO4btf0\nD834Z1UkM5bTlilMbs4bu9o/pX/bP5QK9pfaKrYCZGO/mv4h07PQiX38KJsZ\nZZDoLK1yZ6kiv4lLoVZaeM35JV5KgBbR56d+VGF9U433QiK1nQQr1ZX2nlA/\nSUreMUdgzzgb0U/zWvKIlK7Px2Ii6VLWEmCStGSy8PTE6kERCCKlNNdNGPSg\nbBtZsf5ZAmXLuU+Do9Z/OjK+3IwX0T+20MLZ8gqnU9xIucCPIJeCfWVnNChA\nH4CusN/0VM+R3vE5va/YUFo3VzyGQ7gfB9LKqYBQkSnG/s/UuMwzhrLnzjbS\nMHrT\r\n=y6ZV\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"f8f48dfb9b9ad904804feb11f52bcb9d74c19dbf","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.10","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.9","@balena/jellyfish-logger":"0.0.72","@balena/jellyfish-metrics":"0.0.73","@balena/jellyfish-environment":"^2.2.18"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.2.5","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.5","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.25-renovate-non-major-f8f48dfb9b9ad904804feb11f52bcb9d74c19dbf_1599276680136_0.44967774662004345","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.25":{"name":"@balena/jellyfish-core","version":"0.0.25","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.25","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1d0da347ee04d43743aeafa77b48bbc5a1d45c0b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.25.tgz","fileCount":92,"integrity":"sha512-u8e3WOKExwmJ3LCKjacZ7zm71OY+yxS5w7LI3rwQdgVnVrjkXjUfPS0BVAaxXIXFZVeNWngSRHUm5U7PQDhqSg==","signatures":[{"sig":"MEYCIQDbtwOwVLc8jWmj9GyvZyKwChBk64bZZkFhdWWzRQ9AzgIhAMWeexhXZHVfHpkkDJTr8hp3KpcpyRBpS5pnvya/TJS2","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":553122,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfUwcuCRA9TVsSAnZWagAA1PsP/36jjah9msUMLTMq9SqM\na3IAvNqG7PbS4K3HqWB6v4u/0hFQiZeGo5uY7sZCZB/lM0TA9iXlg257UxTm\nMkw6WYkcFhFDaCBjw6zImURDIuUmHvELyE5AyVr61Rk1oOcsjPikdgIcMmZT\n3dEujHwH+kIqngaMTkCYgfnWAcXS3uJNxe1NWeGZ9s3xF8GA96RjIyb5hOJ/\nPUj1RT9AT6u++IDmFb2BZlCZAeTo53CKMadQasUhJG820yprVg6+0tMsVQ9b\n8XvLygHCHc0Dmhta+pl8+gwZMvwhbNbmWL2CWRBY+41M2Lk6hf28orfEtWka\nKbOS//2U+DDeBLAxoWMgEUHfiY3sG5upZHYI/ItT7FAIbun9rtIKo75n5CKD\nj2yLv+L5KNByU1CwHGeMxGkXVE2wieLta5Xf75hUxW0wW/1Uq7gajhDCa51B\nrmqHlh/OmE0OJP58zsQHnwO06oxttP3n09aXrGLugj15l6v3gSnaeJDUt0tS\ndt0Bm6ZoztQHowiy8yPHoeAiBAL7wnIdVJ6wtR2GyiCKdEHyBvNGeKWkiHrr\nEQ8Hi8QTQ++kPdlfWxeNLlzz4CfjuzTWYIh5UCGWjnTGi+XX/Gs3y2yGVvHj\nHKS9kBuX0M0lc2jSJYa/Wdp5ZQbzJgNg7hpnfvrDorpnyrJO/0zyiWb9lmnj\nHC1N\r\n=1FmB\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"978d2e5bf8b5aeacc292a6048c17bc688b910f5c","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.10","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.9","@balena/jellyfish-logger":"0.0.72","@balena/jellyfish-metrics":"0.0.73","@balena/jellyfish-environment":"^2.2.18"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.2.5","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.5","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.25_1599276846337_0.6148458774524066","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.26-renovate-non-major-1759d8db7c7e551bd6c9534f77f087f5c76afc85":{"name":"@balena/jellyfish-core","version":"0.0.26-renovate-non-major-1759d8db7c7e551bd6c9534f77f087f5c76afc85","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.26-renovate-non-major-1759d8db7c7e551bd6c9534f77f087f5c76afc85","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"db353a51b50d8afd8bf973e5f54e87e9293e6fec","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.26-renovate-non-major-1759d8db7c7e551bd6c9534f77f087f5c76afc85.tgz","fileCount":92,"integrity":"sha512-pglXIaZpliMIj4C/8Ss9Jbz7Gq/yXSmjoWp4CzZAtfN5yze658T+w0hNo86aLTRWL1HRY5jrkXT2JSkyqdIrew==","signatures":[{"sig":"MEUCIEEJ/qtP1kBjj3QnZzfgtU+hOMtsZl1eA+JuL/3urAIBAiEAsRtj7DEuLEKoK5yFTPa78U3bzsAfkHLoXtHfePtqSZw=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":553286,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfUwgPCRA9TVsSAnZWagAAtMYP/1f67peuu5Q0+ocQWSrm\n6iEmUYSBtw3dfCGix7lsurwnBxRjhKZI1TgOqamjX7wMqlDc8VrkDnQiYV/9\n8Zc1wYS5/LkFy4+RMTDhC3lzl7LBWwEJG4/Z8dTgMxHd3ViJgRdrQuLErm0s\nsvmlmr8YtX/lLtd9NXiEZghhNRMbeZymTXt54tjvmwslHTkqiGZCOjPxvY2X\nRhlkfXiVMXh7q4RjGxaTv+p2oQgik1zqHkA0geXB3fPYU/bvXHBFNfY7ihZx\nJ5rTBhsTPfP/aNEmiD7fq4TsqRZby06sXXhVQCA3d+110oEtPi/kFFQB2BBa\nli7+90qI/F421hP+XApVlmftvj+lQAf0bvFA8nLOpJt8gUCb49Dq8vJQcGrY\n/ZvrLihNopvHw0QUID1Eoy0RG1GVWaTkrfRBdlNglnyhEIVi/t9pO3TFshGm\n8M+eJWMbWKYKlbg2TO7lqF5xgv4wV11j/cUFD4yE+M/0F9Ora8JXOMiN5oA4\nrahkSzFpxyJkBptCI7Y4f0pmeJR/U8ozMTgG2NgeSlA+mA58OKkLt5QMUcXq\nK9Btuf7ls2czPgsFkV6nK6na/B8RSPUNbK8ASJ6djTqLDJOS2HQMVbZ5M4YN\nt73fA9Plnb2FscptJz0CFmi0vwrM0zeK0yWpwsClP9H2TxJBAjtxK2vxM8Oi\nURRk\r\n=pFS8\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"1759d8db7c7e551bd6c9534f77f087f5c76afc85","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.10","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.9","@balena/jellyfish-logger":"0.0.72","@balena/jellyfish-metrics":"0.0.74","@balena/jellyfish-environment":"^2.2.18"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.2.5","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.5","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.26-renovate-non-major-1759d8db7c7e551bd6c9534f77f087f5c76afc85_1599277070927_0.33885595635336485","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.26":{"name":"@balena/jellyfish-core","version":"0.0.26","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.26","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2969db110404e4db93ce78f5cb4873fe4a780726","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.26.tgz","fileCount":92,"integrity":"sha512-9egOYAdhfRUFqBmAQ5V8Tf90M8WT88wmr+KleJnKF3fEaQaEZweCcBsgJZEztY+yECIu7v++SFJO5h3AlasbAw==","signatures":[{"sig":"MEQCIAtCCqyZ/n5uCK1ZiFU2ItEdDdeUL5+o6UZ/EDihSP7TAiBqpIJps8X4kvIwmXDn2n/CaoFNYs3HnklrIuJTKIt1DA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":553226,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfUwjzCRA9TVsSAnZWagAAvPsP/0BKg4w1L1BWCSdwoJv5\n883RpBj6KiusMN06RgAHIkt3h0MVdwvn/X9HmK8KT+W3o14qls8SHIb4J37+\nl5hGXHc9iFw4vV+LxzxUecBHmVezKxgHQcY0kasMilMv/YThnH9bdnDn8Bs/\nj20bs/A3KZ/27+StDm/3cHS8IqyvkmdrjGrXDrc4GcHQE74BEbShthOF4jtg\nB/i0/tNFwDtCo/phyAtBQ1/wE7k4/EPgBlsXl7JapRY7d0AbapjtPy6szdoE\nBlwOy65GX8w705tKNw27PMVD8ERcu8ELBosm2PVDS+BNgi/SxfxGXCJSRfhv\nlRrJekmN5EvXLLtHAIAwuyEa0MOQec9cLcJ+VRW75Bovd/aSscNaX40fwTaV\no0OrTayT9vgu44Tc/hw1FE75yTlt3zJMT+8b3ZartLlZwmb298PbDkrutbv1\npCvGrFu6YS0ym3iXW9ZXbAWvDuBR7fnrVWzln95JoXl0yPRkKbNNudwcmcSr\ng+JyjxaVO+UNevrf5ReD6slUwfoP3TGplkBUsmencywVnLWVOYpDFigHZ4B4\n2tPP9c/Ep3YBB9FGGaPSyOEkTu9j5WsLkJ5uLaazOR5NswmS9xS1Dp8plOkT\n2uJg620MrDuZPdBxFExenXZW1D00B60TZ3P0CzodO/2+G2hQ+9jvpSyJoBrn\nax4h\r\n=bq+d\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"6782c0e02e139cd8da48a4d83051934d5ee1036f","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.10","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.9","@balena/jellyfish-logger":"0.0.72","@balena/jellyfish-metrics":"0.0.74","@balena/jellyfish-environment":"^2.2.18"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.2.5","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.5","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.26_1599277299031_0.9729598556949701","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.27-renovate-non-major-c0287c1a6e8058d4b43b54c707910ba6d9405789":{"name":"@balena/jellyfish-core","version":"0.0.27-renovate-non-major-c0287c1a6e8058d4b43b54c707910ba6d9405789","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.27-renovate-non-major-c0287c1a6e8058d4b43b54c707910ba6d9405789","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0492f42e3d744baacb0343d0e98f79dd5f1725f7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.27-renovate-non-major-c0287c1a6e8058d4b43b54c707910ba6d9405789.tgz","fileCount":92,"integrity":"sha512-GuRiflTJsz0JWFb9BwdInFtkTM9I3uvyKXU/P1uIKikmpB5sVLwGH1Mg7UUc768QyEAqNkye+YagyRz7wP2ZVg==","signatures":[{"sig":"MEYCIQCYTpZjy0FqysQo4v+z78NuZTZ2jx4T/HuG+A8heeZObQIhAP/LQh4ABWPchokvAXJVFzElERLd0u/X7du5osV6BcND","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":553352,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfVFpiCRA9TVsSAnZWagAAbo0QAKN5SPGyx9CZDGY4P6gp\ntOUG9P0xv/LIgxoZ53mioFEEeOGfDAtb1uuZ3CjfRn42n8pII1I8rqKHHIuD\nECULYD6vf2KPjvL9LAsmjeMpuafvCEVAWEt9s7uamGEG8yOSHUnnTG5mppRM\n4iW/rZT+TseFVlvGkwUv50gd/yd+HCRU/QMK81yvY8YXVo13bHmZBlbqIDpz\n+fsZPdlItZt5V7SiTmfPhsg0I7C0JWxIhfzmUx8l6jJTATTCL+D4IQjZjqDC\noxzqLUdamw4Sne8ZnldmLM7uiDGkxFAeM5iiLIHe591A4DHrmp7uBQh/QKh1\nFFak18XrOz6ZnYifZrlub/9EHEmcj2h1zIcetqeBoq9USN+O4U/87PWT8Cz3\niLViEIojkjs/SB9URcpLL+M/gGE7Z7rM4NrYO2omnzew9EzC57L19gaX8/8H\no86OIBAZTYRYPjw0TVNjAVoHXY28mOmVGa73lX7eOrtLcFLfobaBjX7WsMls\nOSr2jchrP6sGoUm6qtfHDqybFfFLbo9sL0vG407qfnWBlhp6XHQMHV4NpHWY\nJ1/e2mweEE55UdtFPFJ0d7MCBX3VE5kO+DCOgOoxTX3kxTYZGrjKP05icd1X\nmygM/GmKxOkSeNBrXE4yXslSp8KeNa1bsLgNDqeWCu2FDnJ0Ad2uFdqy0Nql\nV1sk\r\n=Qq/x\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"c0287c1a6e8058d4b43b54c707910ba6d9405789","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.10","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.9","@balena/jellyfish-logger":"0.0.72","@balena/jellyfish-metrics":"0.0.75","@balena/jellyfish-environment":"^2.2.18"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.2.5","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.2","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.5","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.27-renovate-non-major-c0287c1a6e8058d4b43b54c707910ba6d9405789_1599363682344_0.9253332473164912","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.27":{"name":"@balena/jellyfish-core","version":"0.0.27","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.27","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8f7c922b4e47165a9a5e56b2a950c7d1510af4d7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.27.tgz","fileCount":92,"integrity":"sha512-QxwbI9SMqOzIjRAfuKEJF48bC1zyvUFH7kZMJbzpfKVsvamgH+g46Cy18F0JqQIXJar3qG67oxwZ1HkjFiz2kQ==","signatures":[{"sig":"MEUCIQDN5srJqWKbF4Tqox/j9eG+reILl59G4RYIbi3oW8OmrgIgQSFPL2PVHua7ZaU1mylQ3w8xoUVi+ApnU51sWZXLnxA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":553292,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfVFuZCRA9TVsSAnZWagAAsWAQAJv4/sWdig6y2Qf7fRSG\nUDQ3uvjt+42eHSxc9gTVIeuq9O6jWsqfUseZTjZcfUe94RXOItnkurWkPGAK\nUs13ZE6LjVwTs70m4E9ON3v/gahnD3sIwaWbzlQfW7/1LGTclVck4hzoWsDO\n7LlSrVdTo4H+Yg+8qx94v2aFtqgdf9GS4bFYk+WzfcgGdeeAlCXpZmf6KOvb\nEG8dLRwID7425B7FSiTfm8D5ozh2aeBKEqnVxVgxw9hN7pfUqKOTRIJ82IXj\nKq3ZkdCfp9eohsJHT6MJmzt1kldmGyPMR4wyNX9MK/1xao/oEGEryTSarR6v\nNsYdunUn7mb/LD6d5aLGe8y7KR8byGZSnYyJE9my7SHxx9xxerb4MHj388MZ\nlcE7xzb7bJyxAzvEBn7Ww8pmVDtoZHehyVuEwW6bW0Wc+l8scixCJbSBmbkz\nvFa0PSO4YMSnw73sG4ZsMEPrLWfq6AleXimTnyCxmaHo9OfEkh8dycfYtn4S\n70y4mzLl8lwyT/xyLhECVBQBOOAIxq0NvaQJMiMAj57DDJaBcopwlKVdhFKD\nY0QldYMJR9CaZBP44ZY6/YB9iWu/1WT1UNfdBt9d1gMwucHh8uBur7AVfgAB\n1ygXk98d/dgwWaFY+2tv4L633iM85a6bXxEjLDRP57a+GQmsUt4wrkz4KrXL\nCHsu\r\n=lGs7\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"5a7aa207849b9b1ecba34fc8e82e7d96d4ef6cae","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.10","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.9","@balena/jellyfish-logger":"0.0.72","@balena/jellyfish-metrics":"0.0.75","@balena/jellyfish-environment":"^2.2.18"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.2.5","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.2","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.5","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.27_1599363993404_0.9490046395051452","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.28-renovate-non-major-41fb7102f0db8c985a1b163dca11ab32da857958":{"name":"@balena/jellyfish-core","version":"0.0.28-renovate-non-major-41fb7102f0db8c985a1b163dca11ab32da857958","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.28-renovate-non-major-41fb7102f0db8c985a1b163dca11ab32da857958","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f583c152e49e4af599859855b98db3ae91d45920","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.28-renovate-non-major-41fb7102f0db8c985a1b163dca11ab32da857958.tgz","fileCount":92,"integrity":"sha512-dwteGt7i0xcY+Q47arx/oVs5Ee/QzpbhmXoUnU0PHcF0SncnNxRPbmtivWLJmMMNeiYewzHhcIn0rwOyBwCgWQ==","signatures":[{"sig":"MEQCIG1WTbdIA2O3umF9/wiy/dxEeeZXqC7Ch/iMCY8iM34IAiAf4Jpw/dya4Mv1QocGdkFfmSV7W9T655r0yo0phYkWKA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":553418,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfVJXTCRA9TVsSAnZWagAAGMQP+QAfqOP/1b6I9tr3wDR3\nrWRE0j/OrdF19RR1mVPXiWQJCFoAEYQQWA4bBy7NRQH+BiJ8VPyJg9Wm4chF\n7EpqaxuQJKulTDWD1Sedi6tN7dHEd8jiFFbAM0YbqiLHmzDK1bkAlaNS7CvO\nN1XW40Ev5aFEQ1rfdbnL64It2KBFzrqKDVIFTtL53enXmx7nZq7mnJ8Y9/KZ\ntLhmhqg4KTbiyAxW+ZvAKDgmHv74GJJRHGWnY4FSa/7Uv9g8JLN/7tGUVutl\nL9M7D0ykQUCXdaM4vchPpHXiH6TTauCK6xK1OCEmC+KyO7Lnck1vHD4CbtLf\nV++jqO/s9qBzOwn913ZYIb1KFko/a0jbdZegmVcWw4TnkpRebM73/A2D9NT3\n8n4EBGVPfhPLuIx7/Yk9CeN689hgdIvP6FqhGVgpvYeY4fZh37wbzUEEo+kk\nY18ZuT0DLl01zQ+ZAS4VVDur6GXIvbXsXmBlIkEGkmiL4KiRwUxqjQDJSKcT\n++ho1OT5dJdx4dk7oBOvr/b6yHguyidS8OZbwXwRTrFKVGlcYgRheX2aIOWV\n2p/3xupUM0BAMatoEN6Zh4WQLn6I3fGT/d1OI9p2HUyCuHNbcM9fK5pWLD7+\nVknvAqVW6F1J2RmSiMMOEU1rF9tI3u7/NvyMzDrYh48F8D37QhZLo2KgmK+X\n/1Hs\r\n=DDml\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"41fb7102f0db8c985a1b163dca11ab32da857958","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.10","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.9","@balena/jellyfish-logger":"0.0.74","@balena/jellyfish-metrics":"0.0.77","@balena/jellyfish-environment":"^2.2.20"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.2.5","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.2","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.5","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.28-renovate-non-major-41fb7102f0db8c985a1b163dca11ab32da857958_1599378899105_0.4065223785726888","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.28-renovate-non-major-1b7f66a05a5386065a1541e7ea56435849296bee":{"name":"@balena/jellyfish-core","version":"0.0.28-renovate-non-major-1b7f66a05a5386065a1541e7ea56435849296bee","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.28-renovate-non-major-1b7f66a05a5386065a1541e7ea56435849296bee","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"654b435f7fec5a9721e4282c6e441177da4332c8","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.28-renovate-non-major-1b7f66a05a5386065a1541e7ea56435849296bee.tgz","fileCount":92,"integrity":"sha512-bERMprzz2HqBMDFR/aoT/XMC0PDxAn+rdTAEhOBpAI+QZFZSEBVz8dIdfjuXS4z7PKitLBOcJ2APfubx1VRrJw==","signatures":[{"sig":"MEUCIBXfTRj3Thnc4IfIjBONajHHU3+0THSiryA14v+4jV1UAiEA/Dx3pHZo/Z4/01H1TQp/HEzAd9j5KZCsZh0TZgns1hE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":553418,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfVJbuCRA9TVsSAnZWagAAygoP/2y77cWKrwpG/esR7nJe\nNa1qT2CtW6w5O9qD1FzlS36+XnjedZqrpprZTMDH3cmlFN8pF6F/ngdIIMr+\nz6Is+sdI9vwIlNxse8dP8xkkF66NyHtTLLmxK5D9bk7jT+Cy9C89pVrYDqDg\n2GVznGpljRrg3DE44tSCdFpKwf2amGhfaidikPcyENpKbwJnvKJwjlx7yPue\no4LYmcrBUkMP+gIq3/BWHcBLmjunjvKC96cwe5r5JdFMaLALR0rg4pjp/2ng\nJk0xtGrRQhOpB2RrdEhrCzNOj9nUsDiFgF6Q9OYIIYvPlik4NsdfmJT+PdI6\npWhNKCl74y75RRsQXkeZdQmOZ0z/3hwHRRhS8+PdU1HAkVas+SzC8yrtjKyQ\nWzqP1FDCqqYAyrIjaonFCn8C4TLT8Qomc1fK46ky3HS0zYPUtn2xLQaBQsjR\n7/Q4XbirlvHB1i+LSf0dzUwDq+boxDCTYRo27Jl99mtmbP82ffuC8RxvXB3i\niDDmUH0SyAmhIAJhaUfprnTm9vzrfD4fIwPCGdmAgS7M1FH4qThbqv1mkfSu\nbvs/yaMu23ajx4CWTglgj5hFH6aH+DyqsLUCOSOFTub5ZRPVDT5zKCYxSsnF\njGkY0uOBja+YFbC5nNbMXwiyiEijqPp2582qzDSIRALU36d41mIdyKXH/pUw\n11pG\r\n=PyZ7\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"1b7f66a05a5386065a1541e7ea56435849296bee","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.10","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.9","@balena/jellyfish-logger":"0.0.74","@balena/jellyfish-metrics":"0.0.77","@balena/jellyfish-environment":"^2.2.20"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.2.5","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.2","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.6","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.28-renovate-non-major-1b7f66a05a5386065a1541e7ea56435849296bee_1599379182354_0.4669217713868634","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.28-renovate-non-major-24bb63f19a49b17d820909041bcf9ac79d539653":{"name":"@balena/jellyfish-core","version":"0.0.28-renovate-non-major-24bb63f19a49b17d820909041bcf9ac79d539653","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.28-renovate-non-major-24bb63f19a49b17d820909041bcf9ac79d539653","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"7a04f175abe55b6cb909d7c946f909a762b8ed58","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.28-renovate-non-major-24bb63f19a49b17d820909041bcf9ac79d539653.tgz","fileCount":92,"integrity":"sha512-wjSqpc33JqRzeta8fwNzPUqKsitrc99/9rnUp3sf76Jp3Al9GdhgjLiZoUhGdj0jzITwqXZ13hzHiSXyg8OHWw==","signatures":[{"sig":"MEUCICtXPbHepzRZ5VO22YdhX4j1T8mZ6DYUhSLfirL0nn4qAiEA7In3R6scSaF9rKoYMj4Mst1IhGRoXIDYqwpE84ER80U=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":553418,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfVJfUCRA9TVsSAnZWagAAC0YP/Rwru5DleC/Cb08calRm\nOBxyCQnBHN3/W4/Iw5/NLPkEh/avKHYOg2JxjEPBW/frpUCYeWjU+WhSaaNr\nkt9MSKL+85386Lkb8Y+7GHtgZKb/0IrxonRmYo+CddW9ndGnDjY7uztfMOlJ\n+RPOskHNeW6RQWzrs0mKTf72GCN1HM8rX2tOFzbhWoEWDZ2P0J+8HkYwNAw0\nNmpprDQEY2KezL8Bi/WaiFzTaR4yASAV8HhsRh2LoTF4YGwwg8xq9kCpZU1T\nBsThfY4xvvZFiEpA7L9E/2Ek37YVCgU9XGdcwtL7cfbBHdEqW1uy9X6ZMXBB\nWeOFkxptzi8DcIFmkNXLKlKcvv/IHvLc7wrt08/uTqmfVLu99bFG70UvDMEY\nZTH1/IEqqt7/yLn0bFTzpINjJvCpeE3FU5HT+cv7A2fKF1GMkRA63PaenpLt\nv5qyP0GkX8U3tyjT/rtjl3QuwTsuU1uAjS7dDWKMLmbwLww3zQrUVaSXOhJF\nejOT70K/AxWLaY3V7hVl36JU7MMMLN2++4vS/ujDLTFDP7NPXsgAR0AUdm4n\nShQDCSry34O5XgNeCgX02RG10JzCYPYGt2Y7DuWrfn7PRM/T5qyFTDofLOI0\n0asUVOmLCoRPpoj+Ko28G6IChyXQX1tP4KK52/ercR5eMHcUKbSj6sXw1H+p\nkTHN\r\n=gQjN\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"24bb63f19a49b17d820909041bcf9ac79d539653","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.10","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.9","@balena/jellyfish-logger":"0.0.74","@balena/jellyfish-metrics":"0.0.78","@balena/jellyfish-environment":"^2.2.20"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.2.5","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.2","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.6","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.28-renovate-non-major-24bb63f19a49b17d820909041bcf9ac79d539653_1599379411853_0.17782559656822072","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.28":{"name":"@balena/jellyfish-core","version":"0.0.28","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.28","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8a4f55b81a915d2f2b181121ea0b6fd8c5b91cd7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.28.tgz","fileCount":92,"integrity":"sha512-UQzBFbbQgdjWNXQJq6h9UxX2WEip+AQUGKhgjZuB8ERoE6LRhxe0Oem65G0+bgUmZ44GvtRupgBNPLWU/Y1j0A==","signatures":[{"sig":"MEQCIEPSdkt+Aog3pzOAe20wFkpTrn0SSFApfORObAhR4cyuAiAOJfUF5jCbBnV3z8EjS1IcVQ5vCqdBFFIYq7BazT/p9w==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":553358,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfVJkjCRA9TVsSAnZWagAAiU8P/RQjjVX46+1tCkVbJsov\nPX1V9SPjen2tSe8S+9m6gt52aA1OF8OefTUuv9RHHyD8a68GGwFpP/CIFpLm\nKpqtxKfwvI7gVc3SZQ6OQJk1H60zKvaiCNjHoGrcdpmJIOYsdPuPjXyXwgxL\ndw96a2Sm9yLXmnJNdC12Ek9N7yfO/89LAISU2Zy54vlzOC4Efkxwfs1PzT+W\nX4KNzRANK3Eqqum5h+2+EhEmD3uiHSwk53Yu8Cn6HjSActLB2TZUXwNNW5Sk\nhgK0gWGcCcj4uihx4oluYUf+P3OHM2otODoR+deHpeyKReWbUBTrxzJyYb7P\nUz6VccyEbTDRMfkilnx9RKnFxCZRAWqCWaGqyHqekJ9m/pE5psHxZ93SgHso\n6vOD7cpY7R5Oq9HfEvNwroqHk3gTUXUU36X6kqI+wyUmTByNI5rif3HVKeQb\nXyzZ0LOY2imhpWqZsR8mwJMHtdQSWx5WG9jl/70XlbHNImAPNDmwvtIYTPCi\nOr+77KpZOytIi82437VNmrvr6P+ZOGzdma6i0+H5yAl72e5MQlQiW/R4GEPl\nTgAF8bOCQEQ78HImsgdhtHMO8hi5DpzeXfR+h+n8qJTe9If5u1xix0facLFX\nHkND4IWtrAa3TrBbfqfHNh1qPQGoJ2I4SCfIshWR5c37Z1KvTLgt3m0jmB6D\nRJQ0\r\n=bLUY\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"d6efce2932311d1640e8a8f274b8989395e19e68","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.10","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.9","@balena/jellyfish-logger":"0.0.74","@balena/jellyfish-metrics":"0.0.78","@balena/jellyfish-environment":"^2.2.20"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.2.5","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.2","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.6","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.28_1599379747497_0.3812451180981733","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.29-renovate-non-major-c9b4c77f125ad4ae1a9a0c11c512acbb46ab7cd2":{"name":"@balena/jellyfish-core","version":"0.0.29-renovate-non-major-c9b4c77f125ad4ae1a9a0c11c512acbb46ab7cd2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.29-renovate-non-major-c9b4c77f125ad4ae1a9a0c11c512acbb46ab7cd2","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b53f4f1cbc2e8ca8b46dcb144d9021bcca7ed6ac","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.29-renovate-non-major-c9b4c77f125ad4ae1a9a0c11c512acbb46ab7cd2.tgz","fileCount":92,"integrity":"sha512-SQXBhwQ7T401j+9mvROYV1qOPeYt7Zw8DDv5+h7VhuWlOEJ1e5OoktRCGWcQGpFqhftLG7u/asWuiJTRmlMLnw==","signatures":[{"sig":"MEQCIF6BDB7+dmo5qas5VFLAmCb/VNB1ffo4pOQBmRuIYq6XAiA3HqWIau9iDnwHa5DyAvBoQJcrJ3STGcehLQ06Q78lVQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":553518,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfVKnDCRA9TVsSAnZWagAArkQP/iqH7aWwWwl8Xk4+4SrQ\nNcegsglX0SnR8+yeeLDvNzDI/KvjurchToaUd7cl1kz7mwHsbTYAMRFs/FkW\nfCHJ6B3VVFWglN4I1VlnOSYE84s4h8e+7w3A8aq03fNPY/+HHyIcqDwdbUJp\nqRScqpXYFp6uZzSceB2JN2I45Aevgn2eVIKHqJPKXoBGXkesty0yJKhhLXr9\nS4v0/S54qRFtJtaa/uPegOb39HiVaGM0hybAqHPdFVo9RsSpBWByqCUtedaU\ndiBL0HpzvZzUrtzXFwWYpPlXp5dHbu32NiadD5/WQC1t1rd0ro4qVFa3PxDe\nzEGqLOLn8PcoCtVl+/0tSLTwoa3zJCQom3Vw4RgtjhrNeReCDYq5JkBvmZWy\nKvX0rz2XpCxzhgfFkYVe9SDKPNvws9DVyYbJq64IfF5BdMkk4eJRL4oOuhAY\nH0EUfPXIBnio32gBUkiP3Ojf6V4XcjCQ4bnP/2Qq39Gy56iT9hPmfOCXs844\nCFU4yc/cG1WzC24Io0k2vqUI8rgsx621TmDpniwExl/AAzVL5lTYU8BwNdnj\nU6EROTkIJhG5FW04/yvV7hEzq+IZchJxEAXVD7ezBWD3wS+2PxP3kpyQn7+X\nT/gYYZFiTLWyh2nEtebXJ4MLVNAkiUB43HC2eRAuNLGboaaGUh+VHotHfLeP\nnGSy\r\n=T6jP\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"c9b4c77f125ad4ae1a9a0c11c512acbb46ab7cd2","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.10","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.9","@balena/jellyfish-logger":"0.0.74","@balena/jellyfish-metrics":"0.0.78","@balena/jellyfish-environment":"^2.2.20"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.2.5","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.2","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.7","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.29-renovate-non-major-c9b4c77f125ad4ae1a9a0c11c512acbb46ab7cd2_1599384002952_0.059682557143798","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.29":{"name":"@balena/jellyfish-core","version":"0.0.29","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.29","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2042f570852056aaeaffeb11d6eb57df5bb24bc0","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.29.tgz","fileCount":92,"integrity":"sha512-XUBrx/bVAd5yO/bXuhN1hCpM4+S7N8mkmiIEJKZckDQeu/nzaIL68t8hHmOXEnxRRQAKphIuG5HObp5zAzZ+Mw==","signatures":[{"sig":"MEYCIQCbaD33fStiVuoWCSkbONofMHAZEEAPJo4AAAPSMP0Z8wIhAJ3JGgitdOO//wECJSh40Af6taY1T6g9Ji3NgT/X4+tv","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":553458,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfVK1VCRA9TVsSAnZWagAAqVYQAKTfVBu8ADV2re1rVlAb\ncMRf6m0+71Uq9VY2pSIEpJHyeShpO27mFPfhs6sS8Yc/W1GsCQgGUmzcmWkv\nNX+Tjug1VVTF5xrl2HrCba3LCb/iQJk+GI6czHMF9Kif19QPD/4cBRh4ZeD0\nLBnk1ZI3D2xKe22czbVZDiAxvj5XlCvbhQGzVVOiaj+2Q1FBSncF694gb6ox\nm+BAGvgTiBZzIUFt7RYP5BORfSu4I4hNDQCzeuta+/jRpJH4l4Hs6jtae1vW\nMxaR21sJzrOd69/nBQ2uMN2axUKjeGRSl4e38POMc3g4VYjYc/6xHQpLFoSV\n6cSnUD+0vOW3g1BFb5v+4cDxvIUS5xEM4EpjwZ7q6KcaxCOnZhmFwzpI7IXu\nlGLfWgtFPP4P9rZVINt59IuQyBGqg2Ick/h8p6OZDWJsafp4e+8B5dE7B2MS\niVw57EgJPXo/4pDBoN5966m4MYryVE1MGv77RpJCFDhW+24Q8RWiaTaKXSFz\nhbhEJkUEGKAhhX8PxmL96JQwtn6/7HUn7Qz3s7AqNm6fMKd3tWowYU7d4mlt\ngibk4qhm/48+P+Z0sP3LMiza6+mDo6s0d2z/1FztEZpKReYshY15DV0qt5d9\nLdYn1pc6hJUSus+ehqtOni9HDVLQVDD/n9UuvY5fkIH8j3jRuS0q+cuXbRBY\n6nu4\r\n=ek0l\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"9734b86d66c497730391ab5c72c4e4dfdc5eda15","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.10","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.9","@balena/jellyfish-logger":"0.0.74","@balena/jellyfish-metrics":"0.0.78","@balena/jellyfish-environment":"^2.2.20"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.2.5","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.2","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.7","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.29_1599384916822_0.3109448195667668","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.30-remove-node10-tests-fdeacb1241b21559b3a5666c81203d393973f396":{"name":"@balena/jellyfish-core","version":"0.0.30-remove-node10-tests-fdeacb1241b21559b3a5666c81203d393973f396","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.30-remove-node10-tests-fdeacb1241b21559b3a5666c81203d393973f396","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f34541f5c5a9f7270de082c577caf95d8b0e4e43","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.30-remove-node10-tests-fdeacb1241b21559b3a5666c81203d393973f396.tgz","fileCount":92,"integrity":"sha512-kqErAEZr6jynmPoYzvQPBPlanaoB6o12CYpHA8V/yxpys8/yRTwiWuVxwNbXPDwelR8YeWa7At4Y8aZW/QGmew==","signatures":[{"sig":"MEQCIAJtrzfXzrJGKIxcedCjjhBvZ9FVm2EO1VyzV17953WjAiAYAZotHx8CZeezxRC6U6v3QoX1W/7DsLpq06d4jKDltw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":553592,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfVK6lCRA9TVsSAnZWagAAfOQP/38OLjEIeiRqS2MnOMq+\nU3wVwJi1S7WzmutTFbbYMS/U014U8I75T3So5uYGu2XiSYdWeS1Vb/bThmUf\n7DZMiCe50LYsgv/vDdiw5QIZ2pxNQzkDv3d+xhvHfQOujrcKSUn9xBy4qIWg\npKQOV2Vb4Jby4EfCAXneawU6invDeAE/e8p7xc3yI3WTwo94tZeKC0epirBP\nMVM4K7ENOkDVojKgsH/Dyt99SJTJmyVPC0VZAKM4qG+jZ6fC6EGg3Q+WDkea\n2hV6CMYVcUGFn4xqy4bYZHEvproWSJRFfA9oB7P2IImr6xs1wlS5si7N9ll+\nGiAy2kL5P0AsfitAKYlusZNkJVyB+P6br6JIJu4vGnJiUHdBUGmX8v3mJuAK\n+d4u07sP6iUbTrD8hrtDMcLLZLxvJUCp3424KwuzvPIpBe/YR0w2VzBdvt3S\n/rlNynq2syzPvfavBTs10Am6gCwEDoUzjGOjd/BfvTSRNvuPvSYW48Eumjc+\n0/PeKhWixJ/fUOoukqb2FqzuOLH1kDg/ITrhKezK0TUxdTR5L2ElI5wYnnQm\ndqXVZkxTviPSr/Iqyiyf82aTYfeyjFyqUpsFFOjomGpRdQcmL3RJaT9VcivR\nzn8hPDHVyOM41AT5nQIdpSiyoVGkpoj9IkO3nhW37vZjvG3+d4+ctC7sVyUX\nbsw2\r\n=LjRx\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"fdeacb1241b21559b3a5666c81203d393973f396","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.10","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.9","@balena/jellyfish-logger":"0.0.74","@balena/jellyfish-metrics":"0.0.78","@balena/jellyfish-environment":"^2.2.20"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.2.5","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.2","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.7","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.30-remove-node10-tests-fdeacb1241b21559b3a5666c81203d393973f396_1599385253562_0.9435621681902462","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.30-renovate-non-major-5ff13c2e5ce5479be37832aadeeda1926b95bde6":{"name":"@balena/jellyfish-core","version":"0.0.30-renovate-non-major-5ff13c2e5ce5479be37832aadeeda1926b95bde6","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.30-renovate-non-major-5ff13c2e5ce5479be37832aadeeda1926b95bde6","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"bdaa623a7af6946834f10d998ec19a8c81f4a503","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.30-renovate-non-major-5ff13c2e5ce5479be37832aadeeda1926b95bde6.tgz","fileCount":92,"integrity":"sha512-e4uzffY9gpIoN8T0sLDoEWY14uYmUUZStZy6ul7X1qhDnfl7gUMcMldrdKeWgGWITB7yARsmUeqhmsdWGs1oUA==","signatures":[{"sig":"MEYCIQCp5S8K+9Ks0iUCiqBYidIpazTDgYKom9g5NMhHIzugUAIhAMSnuJXtQ6qkJFQ4JvAex7/l57dT89I7oJcoJk9m/cUR","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":553618,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfVK7gCRA9TVsSAnZWagAABkIP/jJ+Q5MEkPL30rHJkWpc\nUqoUqhLR96zxkZ4RJYdp6J92SCvx65Gye1Oyo6CaCm/MnJxoGUHwx2/Tmhez\nofGjVbma26dwN3PyYOBzBIpC0VWn/UDklITKpKNv0gKs7peIH0F65iKFlzFx\nBGTscclQ+6vWwzBwgy8qZkQ+dQAi52tVWfXGWue5IZ4g/RFSio5On+ZT7Lg6\nefk9If9IikSn9GIdGMyXVdRKB+GVZ1g4WlyFvz1R+o1Wk4D5WgOytaNcvO+V\nobWg8w+2JmCX3e2bbPIeCEq88T757A5Fg2mSSQ9WTEgjo4rGcm9quuM7klHt\niBt1H9ckzfnVjaJyM5t+3BNFJzNImvWidWxz7YLlTTjr8h/2z86WWdBW2CiN\n4HUy5z3TozN+OVvQmNK1dmges1I5K6uOfk9YZVAQ4fbB6sDeC0zc4i8ja4BW\n7pY/ofAsh1q/sP8eDk9EUA/cnVTso2MCIr5ciDpKI6aQ8gkCFIS40k1axPsu\nRXKDfygmhk38kzOvper2elaXvOyKhcWh26Q9MDFNFt2QqvW5DgvMoWZhotQ6\nJLivalbPEAt8FpskkmTI0IX40Rv7nXDCDLW1WSU0vlyLd373g2XgivQt+8sM\nDZffsOqPVnmjdPBbaOntAH9V7gipbnjlQ2eM0Y/9DzoeEeslGf9gmp55Dkob\ndP2e\r\n=RhUz\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"5ff13c2e5ce5479be37832aadeeda1926b95bde6","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.10","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.9","@balena/jellyfish-logger":"0.0.74","@balena/jellyfish-metrics":"0.0.78","@balena/jellyfish-environment":"^2.2.20"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.2.5","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.2","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.8","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.30-renovate-non-major-5ff13c2e5ce5479be37832aadeeda1926b95bde6_1599385312562_0.9692898556369542","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.30":{"name":"@balena/jellyfish-core","version":"0.0.30","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.30","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"370b51c3520cbcc441c03b1a8f14f6d1122adb0b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.30.tgz","fileCount":92,"integrity":"sha512-iFbLLrCLJ2X1wY0ddlTLpAIeZBbe4LZ3f1BGM1FMnoRDkyU82t/4Sy9SvtDiEh3pFYwqy9fvTcqqr+EBFBpO0g==","signatures":[{"sig":"MEYCIQD+OQK3Nr7nJEtZDc2XFXGqs0uRJO30E7ovZR7LBdgulgIhAPCTQpakzz2OzdYFCnmqBjZeF4RoYK1XznKt7VzANHIp","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":553531,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfVLEqCRA9TVsSAnZWagAAFEkP/2spYopfjfqo/i/LOisi\nfr38/FIc41kcRS/DngsJOV+WRD4LXgtYGuVtN38zA4secbzoQDTYSovWlOll\nvn8pJmY2vRG3X5zxGKmDU0bzFKQtb05gypo25dp8SamjKrOmjISDJC9lFy2e\nW10HOfCXqm7PBzl6tuQY796BmNwPMpBuzmM87MSujIBPb5UTcuP4yxwibrn9\nWASkEmrGTP24kZhylpb0CsboMEi4nyTUHNch9/eXli1pF0vGCbhV3yzUsHlm\nuHNLmYn2Bac7Sy4Vz+CYkISI7czL+9dC9b1rtLX2Z+n9+4BLCIttuUCK0x/R\npal54PoKKi6hIkv4yTTQwSR9cmYPKrMoHKJMuCGRN4YAKJY913zBdAi6rAaJ\nCWh+ifW5NjwmX8biEKyHgCTZmYFUyVBc9QnYS01ea2IPAL9zow+A8WOK3M95\nQIEHKYc5VeK21G8WoNeG3msEw3VbkIDMzStFsYKiHOJdl2LjrSSYTXd+72e/\n0p8ttKBvgZKk9OU9xCGA98ZHkjqlgJb4sRFoouoc3qNh9gmymhbGHgHvXS+I\nNiOJNnN41fYT722UoWkN8KnkfoFQHfSZkhTV3ncWJjmY7ALaMTmoF/aFh7lc\njcSifE6OSAm0T3hkikp62Fz2n2b1W14arWhxAtI0FGgIFsKKG/zLE6TBJdvU\nrjYX\r\n=CJnt\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"42a6a72fc80e7057ae821166a850a939c70b38af","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.10","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.9","@balena/jellyfish-logger":"0.0.74","@balena/jellyfish-metrics":"0.0.78","@balena/jellyfish-environment":"^2.2.20"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.2.5","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.2","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.7","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.30_1599385898361_0.6167864014671021","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.31-renovate-non-major-ba748de93ebb9744473537579483b07911e7db96":{"name":"@balena/jellyfish-core","version":"0.0.31-renovate-non-major-ba748de93ebb9744473537579483b07911e7db96","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.31-renovate-non-major-ba748de93ebb9744473537579483b07911e7db96","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f874a80913d566b853818765d802fb3d7a0b9b49","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.31-renovate-non-major-ba748de93ebb9744473537579483b07911e7db96.tgz","fileCount":92,"integrity":"sha512-LtRL3V+DdELpeJ0A6mrzumK9MRjAhhpCaxP1SF814K4AjVyG2c9PWXizKzkJAHTwhKXO3DcW3TMYQPk6tfTL1w==","signatures":[{"sig":"MEUCIH96KEukR46UTfIeSN7C+FdYxtbeoNDGu9az8dBOcvOmAiEA+pInFGjskqhJ9wJrNGkYTY0ueGqeCR8jwwlQ86gBKrY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":553691,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfVLI5CRA9TVsSAnZWagAAnf0P/j3o9hJ9sin2MuzPb6yl\nD0NIco9O9Qw3wOQb6GI27bgjw2FpdSi+2eXRcQaGZHVWHAk8cK/tpmqzUMiA\n+u6WEfaWiEHez+NHGkRZmPoRgxkvIcG6CmgrELlUz0SYWBqHOOC8e8LDu2EJ\nD/KX4g2WQ8gW9nNyXp4sBw+gSaubBUdT/1jckkyR+g9Hk6vh+Rma2enSB4ZU\nuYC0zLLjlvIwhRj1xC7CGkeOSYcxWWuiAlaE0VmemaY0P2a0wuFPJGrwraTi\nnZxJTsICBXgv2hLqc4g4rpWZ3ffBfRKda/tkluvbSgXU+BUu9UP8yFpg5bzH\nyyFIEywwVqQs86nB/hADOnYUuEBeUwVfm1/mPTEd0I4svFICPzJDp9J/c1dc\nB34S6/b1budDkHeg6Pb6MtxkQ8ehenGKLNLPiu8sxmTjS0bNa0jFP+dmRDGH\nfLJ1UpOp94deZ3ttVTZPledaaxWKHXLCS2Uo89V1uelbM2WcfQKzGxUczVbe\nAPqIBVv1P/dthGrPi65P0iNYzLssSQsI7U1tYKph3oPO2M/BNNdI0udgFA1H\nBIJMSMPyK41qkBE93zEbPeWNMapKW/6IEx+nlSNmJx9y5NQVMLQ9eOuzMjxk\nsEoWE2dm/bGp2RklApersAZTrlP2PUc21rLIo2Q2kYRJd0Be0ic36inUE2uf\n4FUg\r\n=Vf7d\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"ba748de93ebb9744473537579483b07911e7db96","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.10","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.9","@balena/jellyfish-logger":"0.0.74","@balena/jellyfish-metrics":"0.0.78","@balena/jellyfish-environment":"^2.2.20"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.2.5","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.2","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.8","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.31-renovate-non-major-ba748de93ebb9744473537579483b07911e7db96_1599386168824_0.5890694190631764","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.31":{"name":"@balena/jellyfish-core","version":"0.0.31","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.31","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"faefd0630e66ea262c60c3f5bdf08c292b5d3226","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.31.tgz","fileCount":92,"integrity":"sha512-p4+9DB65jsmxA5vPUg29IC4aDPht4mk7+xG4AxCCeaPb+JR55lOKmmBwK/JNAQAiqfOhBNfnBhHL+0tMjDkB0A==","signatures":[{"sig":"MEQCIEv/HeZx0Eq7Ortnd0LSpUg95gQGiUoAPyhvLA81Z5aoAiA++RIWFB1Y1hbZlVYDliYfEz68bPivXt+3vUmHNl9WXg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":553631,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfVLOfCRA9TVsSAnZWagAAaSEP/0PNKao2+NQpWvZYOdE5\nm2yMz7pK27y3YNX/X7ZoeCFwfNVxCsOWwvruRpUiqJ6Q8aI6TP/qZTE2PBRl\nl5MkImoF2qqkSYuytonhV2ipB/om/GYtUNzIKaUFT2DKTrAoMmGV34aZzeaD\n90TPhaNpZ0pv9QKscoULkO5g7zLG9ECAbdb7e2MQKmeoZkC0OGhsVQeXIkdv\nlcODniPCie/vAQXiUzOKbDb1pzgX8F2Rp5bgZzwYU6425yiwo2fbhrQeLWw4\nSGh1lTtfv9GNgcus1/hjmhcQXSbM5Fn+3Cba642g1vB9cmzUbVSoPf75syMo\n41V/twBjY/lUEaKnlfYk3YTWZrR0b0XN01xoDlujtn/kzXjT4AzcIxXqEFp7\nrmeg0dqmZbiU096piRDfPYqX8SX4jtHA3c4lwy9pYsz0V77xq/WCPJlJvxIG\nwd0QDsBK3U0x8eJIl63s+kf84LBeCilU+nxBcSOOUFLsxTQ3FDZ41SiETNTg\nyGi9YFVZ959OenYBSnSb+hPpMUktbjxWZ9smALo3arNEvTSfE0t/HP0qTHkD\nxZZjKWvIySr0FYBdCJt/fTT/eP5sHWSrmGtBDlFfTYBYVLs1W0CxKdkurmQf\nXQUeaLWpNlvt6/EfklWhHWQkPo3Ce5RYAJPu8g8ltJFgwKuAg8WvFeuKI/lI\njIpo\r\n=N9xN\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"e06e68a842849c81d2e773683bc3467bb44dc9c0","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.10","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.9","@balena/jellyfish-logger":"0.0.74","@balena/jellyfish-metrics":"0.0.78","@balena/jellyfish-environment":"^2.2.20"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.2.5","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.2","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.8","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.31_1599386527252_0.7044221719820316","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.32-renovate-non-major-50d1789acd494e0f7c8ec40dbfe64cbfcbb00fa8":{"name":"@balena/jellyfish-core","version":"0.0.32-renovate-non-major-50d1789acd494e0f7c8ec40dbfe64cbfcbb00fa8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.32-renovate-non-major-50d1789acd494e0f7c8ec40dbfe64cbfcbb00fa8","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4502af524e9a712b56ba48426c9bda3698605154","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.32-renovate-non-major-50d1789acd494e0f7c8ec40dbfe64cbfcbb00fa8.tgz","fileCount":92,"integrity":"sha512-VoFBu7IGx7Oq9+N6DzaGwIPC+YHlhQxnWFovuRpXBTGTAS3tK4ZOFIFgYrubcCmESDrhttzCggK+PW+C3lvYRA==","signatures":[{"sig":"MEUCIQCY4ZpRckIEIp/46PvtLeNwx7DKa9XPoXF3zN5f5f4RSAIgOSTe+xMBVS254dJRtOttxMYIQLgXnpsyQ5D0wiz1FKw=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":553758,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfVM5wCRA9TVsSAnZWagAA2rUP+wfs1sBj0Vih/ifGtxze\nYcCLp9lxzjRftJvjwi5a/lOLnUrj/2ZQ04eHoecQsOZa2lrNDSd2DfsIBqHF\n3kP7RtGgoFQhyBo33poWRWVQJdWBkpjcVmB2NvqyGSJh8dO/vy80nFXLYOsj\ncy+P3s9QXHmH6bZwfJpY/Sa8EzTEIJzNVIQ0ylLoRRn//0NWCKeuLNlNhdrT\nshOC/8thI0YEEI0OnNoTbQLzQbhhArUivSqt16HpayjSNmgCPtF2jOC9dxog\nZsI/sSRn8SH+jdOnwjvfgHl4LqgIdaySeQBWQB3ItGMWxDZXjrUqUXY8EXqt\naOk2bUiU7auAZZtZw48pnE10hpp0YyJ44nbSL7CUjD3qHn8Je4kVs/Jeo5BF\nNwgbUnTMQQYM3k9U7QwTh0RbcWB5PWHBWZkKSG49ZIbMm0hR+ZCR7IdOVUqj\n0dDsg5dTQEia6I4BJBfkoQNvvnjoxEW5wvevLpu2hntRQT3jWOY/lfwqin3E\nPRXQWGYAe3M2RTBJjmkOKsA4cgkcyTpHui7IFygBh7pa3I0X1/AWQ2GqynRc\n7pMX/1xo9RCp3G1tu9bcdHU2U97j70LJ9pf/Ad64jTQ3RvWHgbrCXFqeM33T\n6Jb40vGFD9mJg5P7olXDuIVY2J4aC/6wAMb4R0pSyUGTw46Z0M4LZ0ERk4BU\nwq/Z\r\n=xfzs\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"50d1789acd494e0f7c8ec40dbfe64cbfcbb00fa8","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.11","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.10","@balena/jellyfish-logger":"0.0.76","@balena/jellyfish-metrics":"0.0.79","@balena/jellyfish-environment":"^2.2.21"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.2.5","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.2","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.8","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.32-renovate-non-major-50d1789acd494e0f7c8ec40dbfe64cbfcbb00fa8_1599393391737_0.11007167343776159","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.32":{"name":"@balena/jellyfish-core","version":"0.0.32","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.32","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"525ccd0dce3e26168a95abc17200c58a544deaf1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.32.tgz","fileCount":92,"integrity":"sha512-8adSMe0tWHE5kNl/KHGhUJQr1rwt6qxZsRXo2rsPB8ElFe+i0y7zJIFnGJF3a6Bq4JDZy6eGa6ghshstgmOOGg==","signatures":[{"sig":"MEYCIQCclggl6xnfN4mgNGgkL1cfR4YxbZr6BcIy7GQM9box2gIhAPH0ptMt1VrJcao6Oft2X5vTVnd6NUWXnQTGlnBNW4NM","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":553698,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfVM+2CRA9TVsSAnZWagAACxUP/1uA3ltHOinpoFj1Urr5\n0+jfljWXDKfFJhNQ9FZzuWyV32Fct0EGouytAl6IBe/lzuV0ys1ow/e9Xi8Z\nXuvsrw/OlmpqzeQeIwUDxdZJPt7UueWk79zaYdGFyhNXov6KgECFwmyYBzwt\nDvGyZMbdyHbpFF46HqmYjuPeI8UP583/O8dLtwzshQ9KwwJwNKXXnuDIzr/p\nMyUUYNIgsHOigawfNz4ABhj0mb6uW8xJxHEh8t3xI59cZ2HXN/fago10seJv\nRiWTYZy88OsZUjh/XNPNuroPmscKqS4wrQGW6p8CphTGj4GvmGwEOWvKJnKB\nIq+y5DzISDs9eWt5BFrkbevUsNQD+cqwFdktQt5be2pCmYbYfAkGzhAx9vdY\nhOAJ0bKDRFRodzcjveLczcx8rFPCj+N8oK8yaJ9+jeUnwR6XtGbOHTtJFlOg\n3kfghA96LhAU06ueflavkTEfRgDOoxNmixrhLRS5a9WFzV5qVswKcqxoamYr\nWzFj+4ZTFtKqDxUxl31BNqFmS0r3+LDkdVsktTtVX5KQGLBP2SmTtTeBCVVn\nlXeA9dyU0dIscUShTlPqugD9ZgqWDroBOJHhE3YUY4Z2Lk3klojwNW9N+KVf\npU+IxKJ5GTEybza9pr5T+hPf70zWdatGCHK0jU04utqF9zUabCohNAYrVeoS\n/kKF\r\n=Vc5i\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"dbdc81b8262a03c885ebdfcbadef576147bfb716","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.11","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.10","@balena/jellyfish-logger":"0.0.76","@balena/jellyfish-metrics":"0.0.79","@balena/jellyfish-environment":"^2.2.21"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.2.5","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.2","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.8","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.32_1599393717615_0.43010986079574054","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.32-renovate-non-major-243a8a281c09c1300b45231be7a6ede746a2fa8a":{"name":"@balena/jellyfish-core","version":"0.0.32-renovate-non-major-243a8a281c09c1300b45231be7a6ede746a2fa8a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.32-renovate-non-major-243a8a281c09c1300b45231be7a6ede746a2fa8a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8075320ba46965b71f00a07ad8d95497e5cbaefb","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.32-renovate-non-major-243a8a281c09c1300b45231be7a6ede746a2fa8a.tgz","fileCount":92,"integrity":"sha512-FTD1iDvPaXlsSKdIB+rIXTqCHPfWbB+5hlRyWNq5yr6sPrOC4aFL+giQACy1zk0jUHYaVOFLyDSGScNGDMY9MQ==","signatures":[{"sig":"MEUCIQD1EqsX9DAUsHWiyrHgyTB4qEgprZs5SjcjagFwlN08ywIgOR3MyAxGFYlYZLY28GjxrOJ5uXqXFF1RMMOxMknLfrc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":553837,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfVNBjCRA9TVsSAnZWagAAsYIP/05HrS1ZLd2tV7hnI6Kj\nTahNC2Xj5RacpmNiHAdtOjUHIb2uJ1Es+VPM5LsISRD3iYZqB6TZhL6bBHPv\nAemZKQFmyYNdBGUUWAm2Co9waR6nZNOkYL536dEgeZh4ni2uNrB2g0w3tQ7e\ngmrM4YUhY2R25J5a+02BxfZ5IFvF5A83RnIWwOg0aAcUxmXdz9Om3tjZ+rcq\nPTOmOxDtQgHm6vu1rvfs7CmnyeSwYOw0eEly8QXZ8P5qXuThsE33X1O7ncf/\nWEkdRhN6h8rS3A2RdwmcIr+mEY3Hn08+7nWGKmr4jhNKIo/M3QtAAJMs5b+l\nHsLO1X9v40pL3ateozr1sMuBApyNTY/LEPqep/W4mkjXgHaA89WlLjw82lQJ\nipFqnviw940Xo2asWsUwUPjWw9nzRf+JSg9l1LQPCTpldffXeWAAy5XydMXE\ns37Z0Q1g0lWDedEWDbVHf5rpVrAcNVlvM4f4tvgbyELzevDXM3A7Bs60lU3L\nYr0p9FwkLaVXTqBy8JPvqXdxNHg8WpcKcIk4u3eW8m4kU917R2hwOvEGRJ2p\ngoksVorpwBlrdIzz8xfm2c0beK15NvbFxxz0PZsoZyduX4mzNjNSEqOcEh9n\nCYSlSG7yXknkZbKtqw/4MYCUdKI2Bvk/Qm+LBOdu1nsa6DrStXCLIEOag9mK\n0yRX\r\n=nAsF\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"243a8a281c09c1300b45231be7a6ede746a2fa8a","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.11","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.10","@balena/jellyfish-logger":"0.0.76","@balena/jellyfish-metrics":"0.0.80","@balena/jellyfish-environment":"^2.2.21"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.2.5","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.2","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.8","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.32-renovate-non-major-243a8a281c09c1300b45231be7a6ede746a2fa8a_1599393891350_0.836691328706533","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.33-renovate-non-major-8230f59b4a27cefad8b0bb2c7f6e999a4641783b":{"name":"@balena/jellyfish-core","version":"0.0.33-renovate-non-major-8230f59b4a27cefad8b0bb2c7f6e999a4641783b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.33-renovate-non-major-8230f59b4a27cefad8b0bb2c7f6e999a4641783b","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"7cb0a4ce5e6316bf115f875c9804042d77e6a44b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.33-renovate-non-major-8230f59b4a27cefad8b0bb2c7f6e999a4641783b.tgz","fileCount":92,"integrity":"sha512-kBuSGYlpegSzDNcBCgMk5h7MwInUoQNMql7epMQ1ySDRriFPIQbG3Jy12YXDX++3zILVY/8YZi0Kpm49+Kcv9Q==","signatures":[{"sig":"MEUCIQDQ+VTm/yz4fMivbkumBxYuTB1vGilBvbE6KyU4mi+CagIga11M0JonTvXaPq8v6fWQgYP6XtWxaqkg71djntPdeZE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":553862,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfVNDFCRA9TVsSAnZWagAAXqMP+wWY9K9vbVH6WmZOWWqR\n7nc+yLZxY489SjfrtIwMUpCq5MX98rtLq8zI/kzRNEGA6IAhaWiNb8qs2ES4\n7TBiSj6Cs3pRLbCgPpPw3M3AAeP1kPMy3hNbMV1P7RLrT09Lvv/n+C8AFPev\nVW2wtz32zrGz81vziDS4tl0yEvvGzo+F5vGL/fMln9vj3UWJsUwnZ5oWeSzl\ndaziwnu3aVHEJ58QDHon+AVRkgoJTNcMO3NNY7Nym/DqRehWOU3LDdLaa8nP\nWPoNg1UVNQJwfBtrVz5cx2IkmxPy6SLkR7rk+U290K+2gKgktmj/VUIQosTi\nhW3dvkzvln9DfOFe3zFSDAd8EsRhVsp//7Oxx/5zCqm0rqpMQqDUaMSgwlW0\neOD/1t0UOyxHwxNUhDiu1vsIQ1m14OkatHWDfym4sDeIuB7iNx0F8v1c0hoU\neNvuc/OzZ0/EDQp+Cz/q2vxYSuPqxJsHcXKJzcWYJy4IdJw4R1q/besv3JDw\npXrUo1/kz2Hnu/dC2iZjjNqSAvMtBsaft7BCQ4YAj1aWIgVXff9xcNEERR/P\nlY+BkiKiXt2YJYZ1Rv/PpCS587P+1V9fCTL1mCjTUZBrYnb7MJ/JD4EGKQpS\n6eX8K8ThKDosIb5EM/zvmpIFqCVRlguKh4tt+SplkdzzVOh/BDAIg833YPQR\nhors\r\n=oInP\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"8230f59b4a27cefad8b0bb2c7f6e999a4641783b","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.11","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.10","@balena/jellyfish-logger":"0.0.76","@balena/jellyfish-metrics":"0.0.80","@balena/jellyfish-environment":"^2.2.21"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.2.5","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.2","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.8","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.33-renovate-non-major-8230f59b4a27cefad8b0bb2c7f6e999a4641783b_1599393989254_0.5939300830293874","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.33":{"name":"@balena/jellyfish-core","version":"0.0.33","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.33","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"390b28a4692357bbde7b6064208ec62233429563","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.33.tgz","fileCount":92,"integrity":"sha512-/GQYClnPa3hw5WBwsFbzXTF9GBDo/FdZJFgvNFkCkaAS9N7Ei8DZ5eNU02LyNi3J0G6dp1AqLdYdY9I9JvIVMw==","signatures":[{"sig":"MEUCIQDdV7j7EwfzJz3MBeB84rjv6Ga4ygjnHksv3S+P9OEbnQIgDp00eMMhXNBpqEdHHDFWEWxhHWuuFohfnp2hrvnZKto=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":553802,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfVNIWCRA9TVsSAnZWagAA74IP/0UQioLSCWCiurnHJibN\nCnb0iJkn3PUK02tOe1w53Trt8A4zIVYcER6mYaVKuwyEYqT+CIiKgB20vyFd\nYrLWnrhtMje4YBBvbKCfGAuO/CQkJQQE7v4+BZZiI6WkNL/YklL3r8HRY1ia\nptbC35YSybaN97ErLTESWUPXFWHZGfkDohSOqwXysiUnHPytoPIOIErrF6FW\n0oUVCMFJuGCz0C+OVktkHMXEvR+KPV7LaBiDtoSwvUGvSTldnFGjFAf7IY+h\nywX57GJsQtMNXEYkBK+GfTLQcdPdBSuIuTHiHhkPXAlOtNgKrye4tuFhRKlD\n1I6YhGL5DMy+P26A9ZMsXuGY2ZtWwjhCxE9gNtJxD1M42SVWSwVw4J66DPGp\np/7DQAjave3uKqzpuhZ9whQiw/ZiE6LiMQiSEfrDlP59S7OTUoJhdTTeXq6T\n8lvzCT+P55+UfOHTpiEg48r9rM4ylGx77mkBVYb9+zE0QT4kF2uVtiBW5jtR\ngtCCppiXOz9p/EAg/3EWWaQyAXPKQp5Z+zKLRDnVIGNHuyp53q9J0PI4yD53\n4mKBhR0MgkTzXVkgXxanR8MWNRw0HcWFUoxG2P2YBqc6On0wv32PuINLgcqD\nf21F1aOmrBq0kOXzpS+N+Q2q/OInzsh0QzmfRd0NRx2E5g/aqDlG7DzqGQwQ\n2w9z\r\n=NOu+\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"94ca5f76a49748073d622661cea2d3d333d4a94b","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.11","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.10","@balena/jellyfish-logger":"0.0.76","@balena/jellyfish-metrics":"0.0.80","@balena/jellyfish-environment":"^2.2.21"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.2.5","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.2","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.8","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.33_1599394325869_0.6755096951896773","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.34-renovate-non-major-38dcd7a9d93f9a020cff9d41469987ccdf9b5a1b":{"name":"@balena/jellyfish-core","version":"0.0.34-renovate-non-major-38dcd7a9d93f9a020cff9d41469987ccdf9b5a1b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.34-renovate-non-major-38dcd7a9d93f9a020cff9d41469987ccdf9b5a1b","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c7be6b1b612b72646d437a8665230f6ae3361666","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.34-renovate-non-major-38dcd7a9d93f9a020cff9d41469987ccdf9b5a1b.tgz","fileCount":92,"integrity":"sha512-KQEFLGiBOl2gIo+hhyjLOKaxb/KXxgIOFrywkBhgo0QpMxfqCE8uRK+2j3bt4cp377Yg2gaVaFRXS2nI6MZYpQ==","signatures":[{"sig":"MEYCIQDVWoqnSfvt7dBGpqTeQy/lkuJS6GtiAG4Ps9C9IRoOAQIhANR5u/hciSaNqJD/E17jVBjAHsYnpSHjifLrV5KAa9jL","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":553945,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfVgglCRA9TVsSAnZWagAA/pEP/2GC0lsakh1X0vl5cd5f\nIFPMvk20cQTSyLHpvH/OnA9imT0ve1MVy1A0JP4eL6Vri+iZsVUxEMtKix71\nbiWBRofKdvlSunDeNNnPdHAMrgIVgRLwGoAwkE1DJ+LB6z1t1v1r+1W7rtN7\nJFeTIp7Oc9zoyjYVymbJQ7ajDeUvKCAhCw5ussTq4lfuSbyH3L/DTsiMznf4\nW3CHT7cB/Eb9Y/PqPwhlxfOxXexvjT72C9IzISDLOAYizsssjj5/fO6+yYXy\nbxhCXI4m/TR8GZRfUUizbEEQ0aO5BOKLH4C7DU7NDP58YoXQOarlHS0AAbcD\nEGVk1VKRZK14vkr6gtdsbdpI7niSTRlpn9kAXEHcKStoEmBZnYeQNxsKTjSG\n1IbnyOF2fDv8P8ISq2BA/CGMzbhJtd8MI15YcZEjdwyCL6L0g6BfGuJNKb5O\ng/16wIyc8l+sKFeeQAXAB+V7fCjnt4Duya2oJ/Cy6ch9NbrxpMDe+zzOho4d\nUvkbej0xdS9K+EbRJzN/6DmiWyMP7C8+IM3R4MTQWkMooKM0JaZ7aQFZq/19\nx9mIO1zwQFaJiOZT6zuqc6bPQ6z6Wv+iGbiEBhadmeObfNc0oKKRZJVokKkx\nth+e4HHLSq/Xx+Wxe5J8CDwDRsxgUdZTpetWwPnVDxDITauGyLRst0ebGCn4\nTIcL\r\n=WZ5T\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"38dcd7a9d93f9a020cff9d41469987ccdf9b5a1b","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.11","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.10","@balena/jellyfish-logger":"0.0.76","@balena/jellyfish-metrics":"0.0.80","@balena/jellyfish-environment":"^2.2.21"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.2","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.8","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.34-renovate-non-major-38dcd7a9d93f9a020cff9d41469987ccdf9b5a1b_1599473700525_0.8724294108847654","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.34":{"name":"@balena/jellyfish-core","version":"0.0.34","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.34","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c6f5bb83d618c2a624439cffe34e205ebbc818dd","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.34.tgz","fileCount":92,"integrity":"sha512-+x3IgGkzS9ayYKOVwW0v8DWn31DYH/zS9F2NT0HVtPsV76oFGGtOKMZHD68/INgSLlND6pi3qqqRKtYwaGWuFw==","signatures":[{"sig":"MEUCIElxqajomNgKzhZ0k0qUTnuC3w0X20dhB0Wg49FtLqWaAiEAljvOWtZo4ef7382EktcB8JtE8m7TmW0ds3HtQPDjrTs=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":553885,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfVgqMCRA9TVsSAnZWagAANfYQAICGRKmwUO2IQB5H9pp5\n+79BdLNuRM3Vn6XTiDdHbiawERVn8IX8/AkZSQl9aBa+8FaHbS0EsI4V0C4t\nr7E6duTFJ6ne3zg5r2HGut2LLpvo1NrGlfW7/QkgZa7qzgLhbkmv1Bn7xx0C\nuK3A+QCrD+tAVQdvRu6mx/pu0XucrvOq7I2tG9uAvf7jUQ3s27v6Nz18y0Tv\n+eRhsaljguygmnfdnWTTfhP7CteZp7JbFq2ipvVgP88uuD8dhngwhW52P74z\nGtGzE1Z4mZsPUUbPMCW/6K/IFmxQKqnyObZVwj9BhGHiHH28dxnGm615X6dA\nZtfPnYFFelq4oL4b9to51LFzSGcMztxcWfB5+cCIXMev4aiNVAq0sa+x9jgk\n1+VMX0Nw7eTAuOSt7z/gpmZ8hwF6Cp59ghqb0N6x6JwTXz09PpwydIdP+fld\n50F86UEprovkL2viBtWshnFRbtznwHahXt0gJonBKIoY8uJLdynK1hj74lcK\nvR+GVpbYxGPXMaXOuuTmZBfzXqboHjnYISKYxHPUuYwmP++YAmaaN0dSljXp\nS3guroK8TzyVmdiWMOLZG4XLi0JGgeVbgH2NHuLWuY8o11toOonrc0ofJfpV\nzeYU+sZTOnjN7XNKkey8d65P3MZpEewCcWQ+qClns6Csr8x2uZm35EoEES0K\nwI+n\r\n=ErAI\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"cf514fe18720ec46f1f3149ff9237401189ff45a","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.11","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.10","@balena/jellyfish-logger":"0.0.76","@balena/jellyfish-metrics":"0.0.80","@balena/jellyfish-environment":"^2.2.21"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.2","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.8","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.34_1599474315584_0.40690593444125844","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.35-renovate-non-major-2a763789aa7df6a9777dc6855613dc5f3c1116fb":{"name":"@balena/jellyfish-core","version":"0.0.35-renovate-non-major-2a763789aa7df6a9777dc6855613dc5f3c1116fb","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.35-renovate-non-major-2a763789aa7df6a9777dc6855613dc5f3c1116fb","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6ab8310e155f3a9a4410370b37ba78ffeece8dda","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.35-renovate-non-major-2a763789aa7df6a9777dc6855613dc5f3c1116fb.tgz","fileCount":92,"integrity":"sha512-+odke2zOUqdfv3i83dpSLq9m22vLgVdFNfsC16RJmUj0gmJyiQX3EPspKSroCxqU+m7uZStGpr5EUcMWr2vwSQ==","signatures":[{"sig":"MEYCIQDiXwLgjsdVXlfyWvYt4jN0uH7oGuwhdlW3MuLcRrbrGgIhAO52zSaEpfxN8R6ZcvVj6HGClWMZfwzVgag9iVLEK8yY","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":554011,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfVgusCRA9TVsSAnZWagAAz5UP/1N8eQj3ofDU/zwuf518\nXKQKPaxqRQtJQlAJ0UVkKPbP4shPQTfpi6/tQ2g97gSCFPfVIBaVkIecKthj\nCl2WYcpn9Y6xl1Dp+vryF27YBbqtZWqlqsMeKVbUpHFPVkBecX5fWedeg5MM\nvs5zIWIxAdsrS6upLqG0qFVfDLKD8e06Kd61VNQzJGdO1NTpJfG4R7CDOXtG\ncr76JrVe85ZPhWX1FF/2kDSbRm8tUHywuMy9HqGEIiUHfWK0x3Li8sk7cqZV\nAEo4KdjP8jk/Sgn/a4Tmv/g3FPQE1DHk/XfRXLkDnmdOaaKab+q3aMz4UCNg\nCPxtA1oRMULkF4IKywtB7tSrC2KIS/EDZ/3yu8D0UQiJsLt6MkUlrr8FYspR\nsoSj1lqdmvhrUBXaGXEFBqTnjrkguC8nvzbbbUMMV7LdvwQvlc+bjWFuL25C\n3YQOxZrqXyo7ukE9ElNiwHLYWFFOjPpekNJ7w63xZKnH8rj+b7jkfgd3/euz\ncNRVRQAgFv6DgKzQ3mzFfvg0GwqoidusWj1S/G2lqNuXIcTqLD3FpHa+yKnK\n6vL0PwKP1RoHgQ7zGJ3CXZ71i8FmJN9RrUyyhIHJwo1+58zaZj9xk0Ie4Qm1\nLV97vqgMbuTbyZx01g18S9tLtghmyVMVYDyjN3kzhsmrnlC6rUNPDvxLAyqS\nt62Z\r\n=03Tt\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"2a763789aa7df6a9777dc6855613dc5f3c1116fb","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.11","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.10","@balena/jellyfish-logger":"0.0.76","@balena/jellyfish-metrics":"0.0.81","@balena/jellyfish-environment":"^2.2.21"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.2","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.9","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.35-renovate-non-major-2a763789aa7df6a9777dc6855613dc5f3c1116fb_1599474603533_0.2801738377672869","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.35":{"name":"@balena/jellyfish-core","version":"0.0.35","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.35","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"petrosagg","email":"petrosagg@gmail.com"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9921e15f5a6deb19ae5717aba415b22e504b3d08","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.35.tgz","fileCount":92,"integrity":"sha512-iBukgjMLUPZYS37kGWQia3EQcesS9xKTw7K//R6JCy6V387BJ+pHFHL/8JGUAcT0uqQfE/6yNCjsh4qq1Nt4oQ==","signatures":[{"sig":"MEYCIQC+1V6nsJcX95R48A0VwR7FRlXeOC6SeEZTFeGbmgcU6QIhANfQ9GfUP1C97/dxqkm+/m5oLxAl8LCbKk1Nx8Rm6Wlo","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":553951,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfVgz0CRA9TVsSAnZWagAAAEUQAI8zM3NYthZrIjLVcpWc\nXKgvnyK/uS1+Uf8aYo6X/ZhzvtDBBvDNhvu4URnSDtMQOxUxS+6Zx/ejo5RV\nlzwKn+vOqLibGML8wRYjDg8PrT0lG59fQ2hYW4fnVTy1sVFZPaZyi2FZE7pj\nsHOhXxS62DRTe6tL9aMeFll2obQDzMR8+SoFji9BJVpuf03MLt9a8DMEQ78t\nPO5qZFQBHcROirgMuvVQCcLucvXQ0/mzEsZqeZzA2p5j4ge9pKOHIiEHh/qW\nWjbBz2wlxizvTgTo9m8UI0+y/Lt5M5bksgPu8yp5SYUATmZYLtqJT3X2q9lf\n52otbVzUqlpQIqIzrSsWCUT/vIm/87++cYd2svd8SvhZZTGKmFTDr9R3i5Pu\nY1YRJKfmrO532/chk6EigRkgnLFleq5B7FBGXeXoD+Dp8YmbQhk7Z4DKsX5+\noPLQ+6jLT1I1+MsxH7MZl3S7fYd16326UQhWZYX7BOxXswS9SB3pUX1uc60b\nW/9gJy1ZyOwW7jk1As1Dyjp8suQNF7bHKYH68oQMbJcWfR7TDs4k5Bkid8P1\n+eTpSc7UJ4Gu+Bl6ruyX1j/ZwHXh4cgt9hRhHe5BjYLLI/5dDQvglv74FSrS\nwxZqPK3OIgilCI9SUGO8MYZyP5t4VsKZWG9BK/O/WDiokZ7piltTgI6Va0J4\n039T\r\n=R+kz\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"04c5109fb1aa401338c543c07e0a433321071dbd","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.11","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.10","@balena/jellyfish-logger":"0.0.76","@balena/jellyfish-metrics":"0.0.81","@balena/jellyfish-environment":"^2.2.21"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.2","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.9","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.35_1599474932072_0.026009989762617813","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.36-renovate-non-major-fe026cc9d1bcb85ceac5738f8754e36544227bbc":{"name":"@balena/jellyfish-core","version":"0.0.36-renovate-non-major-fe026cc9d1bcb85ceac5738f8754e36544227bbc","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.36-renovate-non-major-fe026cc9d1bcb85ceac5738f8754e36544227bbc","maintainers":[{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"bce3eb9ba0a0beed1c701bb8d860e0ac718da0c4","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.36-renovate-non-major-fe026cc9d1bcb85ceac5738f8754e36544227bbc.tgz","fileCount":92,"integrity":"sha512-iSVSrk61jtrWX8tphyInR+A0ILh/aNvIOMLRUQlvc2C52sSs2lk/rzY0WvHX4L9C8EoAPUGBzDMOsoakyrWT9Q==","signatures":[{"sig":"MEUCIFQTbtT7f3nIg6WM0qjfWoEEq/K5z7Ae96IYGx5/jbgyAiEA4eGll9lzw9zQdcZ0TRucPJg5lW2ps7hUutJbm7KKnOQ=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":554077,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfVjIgCRA9TVsSAnZWagAAGegP/12TR4P7ioFU/s0f2S/1\nmRXulha0o9fJqHsgoxmXwTuqFywDQjdkllTYRrc7O9AlI8HVC3llhpe5KSy7\nQaUlPBL3xokKZHYm2y1VkMpk77uPToJQkZbdfLqCuOmFvrLyG/m5Z1tqM9HA\nwtkZF+JsP5q1W56DlOROFovp0ZEGz3nZ/TYWReIl7J7fo28xNvoNMGt+Lmen\n0+pSuEkLH66bNJkbL9Sd3ewVtzaLDUdhPUjZMOIg5sUr+ILk2Wvo23F47vDU\nm1BaklsgMJcrq/KsTHfq7ioHf9UnFt1hgFG1/b7rcQ8xm1OeLa11rtcLtsgB\nXGrUtlaIsAqLuquT1i8uP2SvDl+hQF6rK3xlFkOtg09p5+rvJldjtfh4f+GH\nPXmyCxlVqatVE1NvjuN0w/PtoXRjM4NmdCSOU398N7PtND3jOi1Oot2JoVrj\nn3CU80s77GIonwpoA5pr1rKKEYdPajpJtFkIAorEpn2KrCGrb/2inB5h8nmK\nNnBklqgfTwSln+DqAjP7fWQMfsV6yh4DilZ5ymH4jiZQ4fbLkzf24o5K+3cw\nZ0ewoFfyGJzzr8AidMnR4HveOQZD9pdlBfNVFWoNKNDa86m0KA0YRVUxyFVV\nZdHt51ICFdAL2w8o/3dH50Y5ZkZo8IEThXmouqSqT08GqZJ67ThZjuJId5c3\nFzq8\r\n=paS+\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"fe026cc9d1bcb85ceac5738f8754e36544227bbc","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.11","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.10","@balena/jellyfish-logger":"0.0.77","@balena/jellyfish-metrics":"0.0.83","@balena/jellyfish-environment":"^2.2.21"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.3","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.9","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.36-renovate-non-major-fe026cc9d1bcb85ceac5738f8754e36544227bbc_1599484447519_0.8001545065762652","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.36":{"name":"@balena/jellyfish-core","version":"0.0.36","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.36","maintainers":[{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9262acec3b30e6527954e2203a31f31c52d36fbb","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.36.tgz","fileCount":92,"integrity":"sha512-ZKgU8l7/m5DaqahxYnJ48VmuZwQ5zpoKIcUZ5Nmb4lKBnbvLW+FiB4OtKyPL52TzWIHIX61+4cKV8XfNZxZgYg==","signatures":[{"sig":"MEUCIQDnNr75rIdo8+Syg1ENMGOsK7JEJgqhESsuIFOnq8MoOgIgISccVjbJP3fpdj3vu7cBE2XrN5MQfHKIy+/Ysnhka0w=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":554017,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfVjNwCRA9TVsSAnZWagAASfAQAJ5HXte2uN+w5XPl3UZ5\n5TI7B6PVJguHCeYFRiJMiHDQ8lXeGSPTXiK2nEz1Ts0BR2ReGV9LckgQEedF\nrCt0HNcCsnKBbabinW5+CrhCYpNJTw7qXIfNfAM2il61xtn45IpRgu4IBX7q\ng0342GBZ7AbtqQuOZeKkBg7NrdOvQ+ZpruoF42nIXCmNQFGMOPoLKsMHQNPT\nDemYbs4BaJQzKiKvskpXz6aEUqRQfcK+MQT6c0QsZsT5WhknPXo/IYWpKwBt\n8i+k2ZOWU+/8tvbYqYrRgsz3VEGP7E5upVTq/zjU2yoDTpHcBCYUyfi26qk5\nF0ydsNAX3NvmT1OmERqdA02P2sXLSuUR1LETiwme6qcQBwXwvgEK8xOhRyTR\niTwQYrdSLmQJGurGysUANvQTyer2x/EER7UudPDuKSbsLknWlLSrG+MPZ7hT\nuc28JyeokMt/q4KXM9jAONYrcU0WsYe6pXFnF2k5x5GZJdXYD7/zoILbnAXj\nP0ueMhrYx0a7px92hxf1BiTU7cRnXGBQmBprZ9WcZTH1+ciKs79QzrOLiq8f\nzFrLK6RXahqrq2MwRA3K7lZ2/5ZA/6n3JLlYWZJFZN+MZuSV0dX3tzMmC5RV\n5S2iZpZtgbKwvoyc1U1QFiGFV8lmItMjbuZzA5qLhT66xKNPq0oIg50QEEql\ntVGa\r\n=fOMb\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"97f5ca97846d10843826161123b9fa8af86d26df","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.11","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.10","@balena/jellyfish-logger":"0.0.77","@balena/jellyfish-metrics":"0.0.83","@balena/jellyfish-environment":"^2.2.21"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.3.3","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.9","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.36_1599484784329_0.03370363750978567","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.37-renovate-non-major-43863c5967f4ce783ae1b10c05f0a7a585846f11":{"name":"@balena/jellyfish-core","version":"0.0.37-renovate-non-major-43863c5967f4ce783ae1b10c05f0a7a585846f11","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.37-renovate-non-major-43863c5967f4ce783ae1b10c05f0a7a585846f11","maintainers":[{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ef703bf1790864cfdab2e3e94f70d2c78ded171b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.37-renovate-non-major-43863c5967f4ce783ae1b10c05f0a7a585846f11.tgz","fileCount":92,"integrity":"sha512-3/Nwu1fzekl1ysbnPhL2bIQqaqUwYqUbNprHk7IfXPhjcJvU066YdcPhJ7KAgG5/u7C6uWlyqtnxfxZntCVZZQ==","signatures":[{"sig":"MEUCIQD55EZkDnjudWN/TEisUwPqTGw/Z4OplfxD0Da+3g0xjQIgL2Ww3cl0+IOp88TIAsoFMbI9mfCfGUwoMatAg0FmbfE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":554143,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfWDXuCRA9TVsSAnZWagAAZAcP/3AIFSt5TrHDnzVoSbFq\nTMO/FOk06E+oogarJDwnQo8iQ2x5TJ13sfObmUcIFUbOemDFnpIIXJ6rtdHj\ncnMVCUz/t1bQ0srtpZy4SUMHcMjpIMXtAOD+3/0o8xZUEOrgAQQZU9EnJ3Qk\n87pMfn6+Pd8cYlzJSNq7MsHpbtrVRTPGUKEP4vQBehNPVmih3EY3R8GfrSWt\nooULTL6vwed8etvrwQvMWW0Bwr9AATs59DXwsfiF2Gjwla+D8ZM4lQCyPulW\nzsCVY3MmymO8NQ8qM6smxRaUN8jjXZsk3rP2b5XELQyGTi8Prf409AE9N4WR\nkAkniuCdZSCJOBm+81h3VlCflHSDRLV13z8W3eUOJSII0tuKY0Zz0Zg93QwJ\nTd3eMoxtPSHXg1p6bdVTpA7JsFzhUcBEDk+0VNY7mlWe+BJL+F+NylsW/NIZ\nOZJ+yMakHTj59f4ygKd1OJr2/eHz72pr5lhQJBaudyVMsGnnMBASYEZSj4gY\nLRu7WULDw6mWu0NLTAN5xYsAcfCgwsKVOfv6EtcYlFE1UQHB+Tma9nVX7I0w\nEdek9Zcr3TgMb0z+R7Rt1Y1C5FH2V+04GX2lHxT9cwIi1CXU0A3MjBzyWJ5W\nD80YxjuYRUR8WX4FxiZ+DkSrm8kRs4oiLzmv+QOlIyb5GbOkBSDI8CuJHETr\n6rQI\r\n=K0Wk\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"43863c5967f4ce783ae1b10c05f0a7a585846f11","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.12","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.11","@balena/jellyfish-logger":"0.0.78","@balena/jellyfish-metrics":"0.0.83","@balena/jellyfish-environment":"^2.2.22"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.4.0","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.9","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.37-renovate-non-major-43863c5967f4ce783ae1b10c05f0a7a585846f11_1599616494057_0.6441021346143714","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.37":{"name":"@balena/jellyfish-core","version":"0.0.37","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.37","maintainers":[{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8e0a62dcf6e3be0b497ec2973c0f48a60d374bd8","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.37.tgz","fileCount":92,"integrity":"sha512-1sZUp2rIGL5AVFlu7feH3IjsBsXcDpvgzjf1YGhSJkD1luV0Prc2lIhv0+mv5V7ZoyCVUWTiX5f0RugxIGnM+g==","signatures":[{"sig":"MEUCICqGt70Kcnsr+1GDgIrOiVAprvgxgA/pXgArOVNbET3WAiEA4s1ZAWDPfXt2JKt0IfnTy5HUCywTfZqnuf8SgIy6fqc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":554083,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfWDd7CRA9TVsSAnZWagAAY/cQAJnK8ID32Hrxuv3IcGFX\nPxjHpnThPh5z01LCimK1HHofwFEChPQKcKYGEJIO3Zp4kKOKLqQd/i7PLWIW\ngLELnDp4VDV3dblv6x7cNvwpNn55nCbYduu93Zy26q48eB4lDLSnXF55mF0+\n3NWQDR+dPMtDLRMUtLwSQhpH0llp42XaqbV/wUh2l8G0RA0DUGV5B8ui72Vk\nuPQQf8eM/H11C0ixAVvXxiLDN73lGEwrT+xIw7feG34tJNpxq0GwVTnLSCip\nedPQRUVdl3EhQJyhjKO+ynXiQG573DE6zR0sMJ2DvPl8jj5Z/i3SNHMeTe/T\n7unKR2pTc0C7w20dnjccKjEtTVoEjh3O8dMWu2wrtVuvhG6p+dybN2/BalPM\nmL+v0zMKaelm9Rf75J26emLz6CxviTctrUaJF73wunfSt/IzhfjvcQx4rekr\nfutXxeQbwdbDKH6LTjPb5FVwqe3PJDX5uz4lcHc0HpfBjXK2aZPVrg7zG4jQ\n3/SM5LhMRRRlfB80jtVcc2JMTBcI5uEE+yUHAGVYQt51R9hh90dQbLBQwi1z\ntErHsLZpVK8ESNevnpD94eUt059whILgVCvUGqMfZx9UPevATt/lVF5xUX+I\n+wEuFNhm2XO8axDTKeSf18xwvz3cxAsD8Fb2RFxg17X92QVEI/egg4mMTRKU\nmUyl\r\n=B9IP\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"799c41d5f60f14835bd7069a3e91dcdeb7c9c31c","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.12","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.11","@balena/jellyfish-logger":"0.0.78","@balena/jellyfish-metrics":"0.0.83","@balena/jellyfish-environment":"^2.2.22"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.4.0","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.9","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.37_1599616890484_0.6320354161789985","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.0.38-renovate-non-major-a3a12a7332dba2b5cb1e51e1825300f3dd81346a":{"name":"@balena/jellyfish-core","version":"0.0.38-renovate-non-major-a3a12a7332dba2b5cb1e51e1825300f3dd81346a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.0.38-renovate-non-major-a3a12a7332dba2b5cb1e51e1825300f3dd81346a","maintainers":[{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"00ab573c8a8672547db0dfe6f1d112b5f413eb9b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.0.38-renovate-non-major-a3a12a7332dba2b5cb1e51e1825300f3dd81346a.tgz","fileCount":92,"integrity":"sha512-0c7Fwk0oNxqKpcsMuYYrhu/zKZuN6BgvOORhDsId5eMvBgzaiGdDuHPyCtjx1nBaB8Zmuv+vBiBdoE6Yo0dX2A==","signatures":[{"sig":"MEUCIQCQHBdzvCNAeeiP/cORt/bwXSzsai85ipnLwhOtTyD+qwIgFVpl27rnrM6MGfpdbSR2tWtplvI3jSvP8pWL4iL2tkk=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":554247,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfWDgvCRA9TVsSAnZWagAApbAP/iEj1YekM+EdjXX7doms\niMBfWjXeycc6ftXeZF0oHW473iqU6OF8dZXXOqQ+1WFQrNOGhwEsaQlivMn/\nbeeELU7gOh/a1M4tElBr9VSb1/CwdPRFqymVM6HdP4lzDmmZmm81Eav2T+pd\ntRrxyYJvZby3sEn4efU7dyeZpWwZm96eEi4K5OyTx+fjQ3GSCG8wC601T1nm\n4ww9SeE4tAqe474K+6DbrxpPbLLKIwqe3yzNcxaDK6hTwL7/1spc/x/RPB85\nWjbVoDFeTlxyV0AHL1lTPV3BRJMSdNcUDC0ngkLMic557VgosYwkqbu8Ze2a\nheB6B9fBgDkGUVPccL/Zw2CxetAJ3U5MuIkB2gm9/zGiPcDH2kP85Bzey9mS\nP9DrnDrsVML0+5UTXtFMQUvsq3Iae3J+7ng0lxOK0Rd4b/bcYTXWkKvjORaP\nLBYnTy1K4iRHyuk4oeDSKkI65TWa2u6DmvQHsjjdClYaEjZOJ9oNLY3+6686\ntkv51vimXZs+lOIEA/Car/AgihZcygeleJtFQT22JfDr8V6jGcDtNosMEF0b\nsWv9NJcSbYInbhD2LmGzC3TaFUE77yUI9rseimCMy1VHJ4Qx6dd/qM5uABQ3\nDosI65Cx59nAkF+bLGQ2ZTIyOuVYAjce19mKasVIZ+TLodpx+ZKbSL5fO8xo\n//Eb\r\n=sNTi\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"a3a12a7332dba2b5cb1e51e1825300f3dd81346a","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.12","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.11","@balena/jellyfish-logger":"0.0.78","@balena/jellyfish-metrics":"0.0.84","@balena/jellyfish-environment":"^2.2.22"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.4.0","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.9","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.0.38-renovate-non-major-a3a12a7332dba2b5cb1e51e1825300f3dd81346a_1599617071162_0.36380538985835065","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.0-ui-schema-updates-6c1575f57db4e239d6cd8081ad472c4fd8ecf25a":{"name":"@balena/jellyfish-core","version":"0.1.0-ui-schema-updates-6c1575f57db4e239d6cd8081ad472c4fd8ecf25a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.0-ui-schema-updates-6c1575f57db4e239d6cd8081ad472c4fd8ecf25a","maintainers":[{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"04f1c5585a4e08df150ea3e47c4c01c187a911f7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.0-ui-schema-updates-6c1575f57db4e239d6cd8081ad472c4fd8ecf25a.tgz","fileCount":93,"integrity":"sha512-CytpA/hiMNXLy0pjFDMa9BoLxPy+UPRJ1ocC+jvWgc11PSb0ljGbxuRW+yeNv83Ia83pTr/41W6iyw26IEKZ4g==","signatures":[{"sig":"MEQCIApKwbXT5I+Lb5K69dgy595VRY1F4NyMATPrPtMTDlm5AiBjSA0hqdtgy51+s5NDfRg8Dbhcr/RF2B5zPq1BDuNcHg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":556788,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfWKsqCRA9TVsSAnZWagAAOGUP/RqgYTh3qOn3Ug0tmaDF\ngTlqAweg2UrI7pwiy7unthnFlQaaHw+E0EEM54B3i4B86LQYv1DKWabBQFU9\nj8vozzT6o+VsuPoWeK0LXp758QrMu2Pt6gQymak8roTdZAwrVMFxBtP4KfSZ\nIFIJalXF39nUzXif0SwyV9Dvp1xXD9daSyUgtX//5x2nj9rOAqYUn9K27lVr\n9K3UKWqIJioJaT9cZRWHq7FMukCzK81h4pFfJIf9etK5ER64UAEPZUTzto0v\nCxFh6XwFomg6K430ijKg7y5O7Ieg+RiiMnXCSt9scJPtPxEU/tq/E4SnkBAX\na+p/W6jR1kwkXBGQEP6DmOnMIaaQIMFpH/9waiFS3Xgo/BSg1oyVCzCSl32e\nB/WBUK1hYzVycb4ktd5JByBej6Ko6C4spuVtMLCAJRXp/Ub2qKkZ0pRaWT/5\nYl8I5IpTUiowTVpdqw7S5IktdWf+hOjPX8EeE9rrK9165R4zAHaisCoonOwj\n/K77fZXFjCNvYJYNybedOCkXFZkP5oStscBNsr7Kq7UdYOBZ9pg7dcXZZr6s\nYi5vCFaqQgKWG/MdHGxDW75C7QatmJ7rMiCrPdoSCq7kRIO9uNnowEqCwXLv\nxcTCWVfGrqbwrcn1SUoayXLakpL4Iiqof90grtfO7rw5KRH8CMq8MHhq2S5A\nEEVj\r\n=yDUR\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"6c1575f57db4e239d6cd8081ad472c4fd8ecf25a","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.12","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.11","@balena/jellyfish-logger":"0.0.78","@balena/jellyfish-metrics":"0.0.83","@balena/jellyfish-environment":"^2.2.22"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.4.0","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.9","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.0-ui-schema-updates-6c1575f57db4e239d6cd8081ad472c4fd8ecf25a_1599646506523_0.16429439705794024","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.0-renovate-non-major-4b7e9c63b5ee20ce7c49420380c235ea506e6af8":{"name":"@balena/jellyfish-core","version":"0.1.0-renovate-non-major-4b7e9c63b5ee20ce7c49420380c235ea506e6af8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.0-renovate-non-major-4b7e9c63b5ee20ce7c49420380c235ea506e6af8","maintainers":[{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3546d9dd0669410a154ae0e71ff517cbc9a374bb","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.0-renovate-non-major-4b7e9c63b5ee20ce7c49420380c235ea506e6af8.tgz","fileCount":93,"integrity":"sha512-aLHzeqmpViPFrjjX4wW2u44f3kafeCwXXwbyGLK7PdIvtusnbT9z1Gqvt087S9oefHeGkdfuxUQ2LCSmnMnHZw==","signatures":[{"sig":"MEYCIQCK+/3OK067gL05EvHRef//RCDfkofOg26x7mqBCo4BbAIhAKqNSaA/oRqKyxyZWDq5+PyYdpXHrqdeYWax30KoMOAn","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":556831,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfWYEgCRA9TVsSAnZWagAAa6cP+wfB6uQL4Lz06QD3oBnA\nSRDbFnMkM65BK8Z3HVKYcO6+UOqJO80X1icw7CUY3g4/EaJVr2ZnoQWPjnc9\nzowh1VBOq+0Ku9rkrJojKPpVxOgbEKnKTE69A28sEkMsvK4dmKVJZ+uifw7a\nv07sLYAt9aF7Aa08IgINNSk/gaAOHA/5CXTg4BrVuTCitUxGdVm6TCyl61Vt\n0bMKyFteEwjVcr7cJJMLgOazVsdyG7KRd3+eUDzkpnGa4XYRiQGOKnBy7oGF\nAwSCZEcBEuKD/qYaGvJmxZCxnyQxM95XXz8KG41qf46RWKL+YnTAuuLHVa37\nzmQ+gAsj/Vs4hdtLag0g/fVbbgMfnHp8+9kpoKikM2rX+wOmCgfq1B1Sk53U\nq5qqTPeQrk06gtf2VwZhxgThJRi5oCkZn2p1iVhyrNsttP/XgXU21Vz+jJDx\nnOVdT0Gl82cO2ztkSZV6ImJx6eIHNYi8PXU5nDBMIffnWavFKjO0ObOtLj2t\nOe6zaXZ6R4UlqbU3REp0aTeSd5eA+7qfs+db+q5SHYNzMCPR4WQGBCaAhI2B\nkI7jk/hESneUpV5caEKiHbzlbvzAsvV8nxMQBmmZw69EmFWQLMQ8FS2oUqFO\no7qjKBS4aqvAMlNbO4U7FT+WFtumc15k5jsXQyKX6+D/MnAlLOp+RMi5p5pq\nkVbJ\r\n=B0Vh\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"4b7e9c63b5ee20ce7c49420380c235ea506e6af8","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.12","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.11","@balena/jellyfish-logger":"0.0.78","@balena/jellyfish-metrics":"0.0.84","@balena/jellyfish-environment":"^2.2.22"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.4.0","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.11","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.0-renovate-non-major-4b7e9c63b5ee20ce7c49420380c235ea506e6af8_1599701280143_0.9963769260243824","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.0":{"name":"@balena/jellyfish-core","version":"0.1.0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.0","maintainers":[{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"729807a1fe825cb4eddc59281a63b528efa949a8","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.0.tgz","fileCount":93,"integrity":"sha512-syL7Anhz4lzQmpa13YONNA/7+vwUnElYOXLDP+Sm+MDlv66VgwuhTj9My0RX+dcwuzYyMBifhnd8HQjJQUXsQg==","signatures":[{"sig":"MEYCIQDGCeVM5RZ/zCMFD27QMirUUao+4FZOU/H2muNDYHPqJgIhALhAe8G6Gm2HF6AFUN8ITAf2tb/VsgnobsT5Vo2pbyLJ","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":556771,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfWYJ2CRA9TVsSAnZWagAAK+kP/2v1RBgiSZZQWz+DGyNN\nWQr4xmLfd4uB2QhdptEI6wqTm6BSIkrNHS3HBQE7/DsSjMTkrZaOBuMcX43Q\nVq1VdEJgNcTY7P1ycrCRf4NlrivF4ZxFaalO0g4K2m9HMz+vDqtFlzW6T7PN\nJxk3IfosDlt6fAgKeNZuG9pGwNr5mJ2SDdrMy+mInWt6BQjYo+DE7LGxvqRt\n4Yjq4OkoOKL/nqO/YPPUiJ9c5FDfIzQg8O1bGeQabT6c2hh0BuqWARxnUzUZ\nf75ouPoz9mNGnkiO7/rMF5ZeB4AeRyD0eSDDcK0mFcvq2LXlqFOF7HDs0k9n\ntvQa1WmaUZj84k0uF1WlRwWh4K37Qvj9HgNUJuQfxrc+ks1jJTSW55jUNICC\noqdDUQDGR5ktAikP7Py6YCfAlES0sATZ0q7CZ0IP5JUSObzwAcrHSa8XTySs\n/hfsQwXahe3Iwor/7qM53cai83NB/64/KGGWDaRpaGDgBc8BjpUPjP2KCN1x\n8BIXal5zb+HENto/IzGeadKu33WEr/R38bbADPfMwkmlGA/2KKoXEsRbUkFN\nwfy839ewmpj6Du1CSNvd9+kodClmBMt0TrhIbfrEC/FdUJ7XFpTCtEGFFNNh\nm6HQPCVo26DzzSeSYDd8rC51tgG3p3gN5TmXR3Xa3HTtJFe4bgqD+6qjo6JN\nS8B+\r\n=eGqb\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"408f3596a3fd4efc4ee894080488dbed8a58f33d","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.12","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.11","@balena/jellyfish-logger":"0.0.78","@balena/jellyfish-metrics":"0.0.84","@balena/jellyfish-environment":"^2.2.22"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.4.0","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.11","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.0_1599701621926_0.7833126418414684","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.1-renovate-non-major-b08e4c69de89f5b48eadd1f25ebe502db817781a":{"name":"@balena/jellyfish-core","version":"0.1.1-renovate-non-major-b08e4c69de89f5b48eadd1f25ebe502db817781a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.1-renovate-non-major-b08e4c69de89f5b48eadd1f25ebe502db817781a","maintainers":[{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e4fe00e4e2e4866b99959b8a23fccd11f4ffbc05","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.1-renovate-non-major-b08e4c69de89f5b48eadd1f25ebe502db817781a.tgz","fileCount":93,"integrity":"sha512-o5EbslX1qJ7HpDtO8Cvbzv6mRRbT+WSKXxFrEFGjBXrO7q+CV/+Hs9DighakgTDR9VmLei6ZX02+qKs2LW2NuQ==","signatures":[{"sig":"MEQCIHRz61g9OqzsjfuSD9DJnbM5lhGkSdir5XAyXHVVcyx/AiBcy3EOR+cK7fhd+VHRrtfKUyMTehW9A9lVGYxJWAhUFg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":556938,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfWYOZCRA9TVsSAnZWagAAqc0P/R/bQ0YS1TKEAv0ddmWi\nEWmQrT5J+6KMffkiD4kuUs8uwQiGYWAWhtUrcYo8bJjjrqBzTcL6ZBMPuS9w\nozYk3oykCnWrTUvOokzzjAMh7ydm23PdAm8yWSCsPo45gH0ZsMA0V8PPg3Tk\noVWSPSTI9p2ahF97wPc0oUnBEWh25LXrxFkhnKse9PYP7pSsPNB+mRV8y2ZJ\n85FREMi/kPOlQIc6bBkkJkcEsaD+vBqO58623EYAmOYMVUvlcUnZh5KV9jxw\n6sCWKWhuLLiKgd/Hn6JEX12/dtyPgcwpZ2ePga/Rj1so7W1XscOwyZaSPka2\nD3tDuCPJEht50OHuM1IuipefWR/JovDklZu9Qw3uAjWew5y7G5WLUBSqsVRG\nznIQ89PlqU/dyUdUzCcxsIr4Rx7Z4Ti9bJavjqVlajxqmhjLHXuaktoXAQ7+\n2CENlWxt7vLqdtb0Rq6uGa0cNe9ByMSBKXKoRr19EGXb403yjY/ToqYirEc5\nr+C0RRtseuiSbhUzjgXZKHI8PSZwwqr32/kaWEO4isztKz2Walhrg3Yf8PEJ\nGcHuadFKLy6L5iqYkNTBut8kl1zt9nky/FaiJ3nSqq6KZCNiP6jBVi7hFcy2\n5GxaBay9zISJt9fMeicZW3No+XOUGq7tJ1L17m1NySfACkNDqtQFNOBO0VD0\npAS4\r\n=96X9\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"b08e4c69de89f5b48eadd1f25ebe502db817781a","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.12","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.11","@balena/jellyfish-logger":"0.0.78","@balena/jellyfish-metrics":"0.0.84","@balena/jellyfish-environment":"^2.2.23"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.4.0","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.11","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.1-renovate-non-major-b08e4c69de89f5b48eadd1f25ebe502db817781a_1599701912910_0.5136735466320526","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.1":{"name":"@balena/jellyfish-core","version":"0.1.1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.1","maintainers":[{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ae15574d7368cd365f7694c8847cdab9fc622cde","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.1.tgz","fileCount":93,"integrity":"sha512-Kyk6Bw/YujrSiSYG8R4qRiefBZfc2JyOcFJeQtb3ciQUrh5tmI69MGWrR3KZ5kOb7hIg2BFxT6HVgJt1kOiA/A==","signatures":[{"sig":"MEUCIB1KPMVTf/iaTfgtodgPMeZbcnm1vrNuKOqKpv3zyjisAiEAyLPzt96olJi8T1FR1/fSGDJFza8Pr+TP8s86v+w61YA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":556878,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfWYU/CRA9TVsSAnZWagAAJyIP/R4avZPvJrTrS8YBbDSQ\nRdLUUfJbMyMsGuydMiJkjTw2uvFfZl2egxQnB/X09ZpUcGCI9p36sKaS6wn1\nyEBOWXe4VpQBKRJ/fJBFSidBarZDzESsR3wQNkftCJThI2WL79tgPQ64qWiH\nyWgTxTsgTe8k4aRPHuBLFfRM2E07LLGy2Wq3qaR5JaG0RniQo+SIqGKITl1U\nOsU1DKrlJ1JDnNVagKD/90NJnEfr66+HCnuE0C4oVnMZW64aUHCJYnlgL+gq\nYC9dLLkFA9bz7OJjeB2StJasaSHx8nPnfHK9RPh0Niwx7fwPlk18TkQ//d7d\nVhawJgTndzs0SZ0mjNmXljZ4zBt7mN0R88xmvpNAHrfKabZycVjZoYfbXaYC\nh59qMBFW0MsIvSWBl1UXvZ4loyTHQ0GlliZEZVvn/fvYY10k/r/tL1MFtAmr\nwREXr9lt8nKussHG6VmkJLytJOBHz34++qwuY+mbCoWG3pE5C3mT+dPohi+4\npMgfSzZwu6XZjSmN16/Q5E2rsp7QtdPopJjeRrhgwIUm9Uk9gDDQeg8zh6s/\nNrU2ZlXZH2CmdjY9DJ5dR1CT13VsrzmSKvtinJXABWf7Nl4nevtzxDLGIeXV\nUiM+mpW+kbFUGqvpc26yOppbdUuh+76Oi2RVRwVL2tnv6YD/mZaxIkfOib33\nBnZ3\r\n=SUEe\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"a77d97b41effbfda7c4a58c76d3b13be26694ee4","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.12","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.11","@balena/jellyfish-logger":"0.0.78","@balena/jellyfish-metrics":"0.0.84","@balena/jellyfish-environment":"^2.2.23"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.4.0","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.11","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.1_1599702335332_0.7582770842061011","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.2-renovate-non-major-64ddd1f472ce2867b4d9f1f0855a13c08c5dedb6":{"name":"@balena/jellyfish-core","version":"0.1.2-renovate-non-major-64ddd1f472ce2867b4d9f1f0855a13c08c5dedb6","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.2-renovate-non-major-64ddd1f472ce2867b4d9f1f0855a13c08c5dedb6","maintainers":[{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"54955e61d4e14eb7a438a811735951ad284ad0aa","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.2-renovate-non-major-64ddd1f472ce2867b4d9f1f0855a13c08c5dedb6.tgz","fileCount":93,"integrity":"sha512-P0m90QfFfZd5KoIFpx6mVdQhnsB3LXxO1fWZLpmC22VL6F+mJ4DzNazNLPnsxgGO7yVRECZFl5gBGJGUkllJYw==","signatures":[{"sig":"MEQCIGzUpR2V9z9xKkLSwKqfixlWM2jIs+UFKJAlTiwGOyUwAiBI4HoQ5xumtzN81iFyaGqfunSseeq9Gd+wM7c7qyAm9A==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":557040,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfWZQuCRA9TVsSAnZWagAA6LUP/1EwdShcsHnmuMTKcjNW\nl/oejyNvYM6UhFqlXA1glnzP7xq+hx+SsImE2GB8pZtrrlPqTEgdanT+UnN0\n+7VvpLlBKoHSIbdgwnX71GNyix6aj9bpbMzH8OlXd23iAbalCCDk4Fo/q8x4\nIfwWmpNvWMq1daJy0ajaILDBHSrQcvNGj26fw9D5MnrHg9GBS2Hegi9qBw8y\n/MGatz3sZKuneEuNATFpikaH98JB1BCij8CT1nKATRqoj356+iJ/VoqUVP4S\ntMuKt8ZSyj5eF/bl5PhagbYqaGKwF7zznCVHzAuDKf63ACx9psQTe2MgBoFX\nci2LL/3+l37U4TTV27qBt7bbyVCDhOeYRKXladQx9hHmFGTCv/GuvRJrArmN\nsaEJrCD2nhOxO9Wo6U06ePshM99Pe+RLc4GzEqGPZkFd4O9QcYVPpMKaps/o\nOnA79J1x67m95TGe+VaUWi+TSan/0HrEgVWpx0OF4E5vG/ECU3cFv3aCblY1\nU/4oXCeRGRhCu4pihSMn1aek7jnhVoVl4AvFyBV6AqtMldvl/SFCxHQxEYk8\nxCDiIDUiWHch23Pe2Rteh1u3IGlzQBgZ5FBhmcCP0IgZmKqv3YIRgcA4FTqa\nNTx2PY8GyGxcItytE4ayxwZQImw7HGUGm5ntV232tGDfPntN4PaCFwf+xWwf\nHp83\r\n=sv+J\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"64ddd1f472ce2867b4d9f1f0855a13c08c5dedb6","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.12","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.11","@balena/jellyfish-logger":"0.0.80","@balena/jellyfish-metrics":"0.0.84","@balena/jellyfish-environment":"^2.2.23"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.4.0","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.11","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.2-renovate-non-major-64ddd1f472ce2867b4d9f1f0855a13c08c5dedb6_1599706157526_0.506816135198227","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.2-renovate-non-major-5bab7a162d0f90ea25bdb66b79307a125707a59e":{"name":"@balena/jellyfish-core","version":"0.1.2-renovate-non-major-5bab7a162d0f90ea25bdb66b79307a125707a59e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.2-renovate-non-major-5bab7a162d0f90ea25bdb66b79307a125707a59e","maintainers":[{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1798645d437ff311934c3f44ea87b2bf00c816d7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.2-renovate-non-major-5bab7a162d0f90ea25bdb66b79307a125707a59e.tgz","fileCount":93,"integrity":"sha512-BzVs3at6d9Gos7oCG9dwzpD1Iy5GXz+A35zmwQp0IuGo70GuYU1JelBIN1Ac6VbFZ+7SQfi61p5bpxcd3IkCzA==","signatures":[{"sig":"MEYCIQCB4d21Sbl6G08/TN2WvXQvfM/pqXu+BZMFGdOZ5JOYkAIhAMnsRWkAFbbCVm2HXqR/wBoX9/IrgKu89Or3lzLne3gD","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":557081,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfWespCRA9TVsSAnZWagAA7J0P/08d2Sy7z9JRdYSnD22o\nBMcIwJDSIUsFnIAQF9E+qAwOnxIfG2s7KT+C8BZvw38bLAsYh4F0y0SecIye\neW3gXO6UlzydYiGexfbPFC3Kq4XmdA97eOrgY+eFiireoKSlz/axwesRAOO8\np0fFLtPPU/QLLx3xvSTnuuxVk4+LSIA/pDJ5mctAK7LLc5x9Fepo7VGnpMgQ\nomyumEoSyr/3folGg4Fb25kZqsOzZESmTujlWSjpZxPtjuXOYFX4ksHw5ujB\nb3k+7jkjd3tST6e2t3Kc8gLAsqqFddX5sLEZsRxr8ZraRvfpuMPYUOj0b3VP\n0qbG7fYPBBNtULZMIziC5DlNgl9szyVumUoq4J5V0H5zCg9z5kYJQd1jFnop\nFE6T31ZwtaC/x3tkjvp0NhHHtltDBFbMHNfBLhoOtWUVUjcLfZL5FH2BRUB5\nV0kSHzxU/rOFN9LG2yKvW7d1JwP8lvGi0WLasNWsGb2HsquEHRe2bMK/gvxT\n7IyazkFAUecvnlOcuVmC+SatEscySxAmi2NHW7m4Vfa3J2M+yEVONxYc4yeO\nSPbQhqoCPV933yNM0YSeFfT9mvg1QUmsDJplltF45HM91zilQeIBkCbwZX8l\nTfP5ptMU8A0x6Zdo3ZAmpCR+0Ix2G82VWu3LlGP8Q6qxcKgnPbvwmbRJmnb4\nIM1D\r\n=e9lN\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"gitHead":"5bab7a162d0f90ea25bdb66b79307a125707a59e","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.12","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.11","@balena/jellyfish-logger":"0.0.80","@balena/jellyfish-metrics":"0.0.85","@balena/jellyfish-environment":"^2.2.23"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.4.0","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.12","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.2-renovate-non-major-5bab7a162d0f90ea25bdb66b79307a125707a59e_1599728424564_0.247744506505839","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.2-npm-engine-strict-3118d4f4165cbaf41f81882b980c075b6c59c146":{"name":"@balena/jellyfish-core","version":"0.1.2-npm-engine-strict-3118d4f4165cbaf41f81882b980c075b6c59c146","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.2-npm-engine-strict-3118d4f4165cbaf41f81882b980c075b6c59c146","maintainers":[{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"80f5b80f49a855fa30dcae7a45c635357dc20caa","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.2-npm-engine-strict-3118d4f4165cbaf41f81882b980c075b6c59c146.tgz","fileCount":93,"integrity":"sha512-jwGJN+085z1LDx75kcmek5OEY6rY/VsIK3Cq2Clve8z102p9UB2cLo/vi/EnC2vEyqCMzzkrA5qCTHRr4nqZKw==","signatures":[{"sig":"MEUCIC5v+fEE2WGedsd2f2OVZo7NEDjT60FZfUwx7DwzUJTGAiEA19QNKnwNULPMViydqGpdRm3je5wTqhx1qQGIEjQP2V4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":557145,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfWfGaCRA9TVsSAnZWagAAQhMQAJFpz+BWmDUGW/b1mP74\njtaZJ3M/sDclNU5U4YgI2HAmWacjBOXoGJ2+BHY9O3wnb2FttNzDe8zgOLUY\nKozigUSR7oLi5VbtriI3quuALgnfOd410No/f4e83yFYOAJy3hC2jLkPXibA\nRV3VqC+yXv1AJQjw/JkQ/A5yPM7HYUdnLKr3NUqqm3Sl/JjWInxYbTOiJB9l\nmwzuVQnwu+OCz9dAHT5Zv2FfZggsWJ3uHIkPisAI1MwiX0N1sk77hz+k/exG\n36/8dgJBs8Vh2nA3vuR73+sw6JcJYcjZvB0gm6NcFzjg2D3mOfpgcLD9VMTV\nMME9qnPXa7C7Za40+egIBvhaavZjliJB8NS+YLJU8bhKaSp2F1W7zgogViCd\nYenlgG6A2ev88LdxkQJIHeaOxSsJfo0WFgA7bjAHG3h6fJw1ewkx9TP43/Iu\n5V00Qhg5AtWPToZITW2o1ojL6iuPuBWxMDOmwU184u36LYtIQLsBXpCxzoSa\nnENUwYX+27044K/O3rPr0TDZHG5k922mzeCKGy98dVwSjywjIG+V6CqL4QBQ\nk9gHaBaUs8zoJKB3eCWuIou9zkzD3lEQlRCEZBT19yYNpcXg9IbDICUmxGuz\n7dHEGIIFiTvMMfKOoOoc6cD+bwJ9NhRln+1czz3qk0tURTRwLZNCrY41M6xX\nXf5a\r\n=cwMl\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"3118d4f4165cbaf41f81882b980c075b6c59c146","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.12","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.11","@balena/jellyfish-logger":"0.0.80","@balena/jellyfish-metrics":"0.0.84","@balena/jellyfish-environment":"^2.2.23"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.4.0","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.11","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.2-npm-engine-strict-3118d4f4165cbaf41f81882b980c075b6c59c146_1599730074046_0.8244132479424209","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.2":{"name":"@balena/jellyfish-core","version":"0.1.2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.2","maintainers":[{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"06957312cb110e14ee9a84ea80afe548f7bee5d6","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.2.tgz","fileCount":93,"integrity":"sha512-9Lr9ACCu1k1el/gx0npclAuhluMDUrGdUfgDY0YEd3iZZxDngkKG3v/JOPmAQ3eOfxNu//JfL9r7qHSgtsBCeA==","signatures":[{"sig":"MEUCIQDV6wEO9+Nmodpcu6D9yQWhisCyUDGN4fKww5/qrt5ABgIgWuib5+pM5ckN99vWxa9sAwOXY58xyVGBShMb/7Fr9+g=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":557086,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfWgp7CRA9TVsSAnZWagAAMlYP/jPqv7lUT1gkPHjqe+hJ\nOrji0kzcMGypcE72vzKwauAIzSUNpGBqC2pYRBO1hEWHlnlzSF7mms8IzoPM\nNnzS5veFZy5qqgCBb95/Cz1vOKxB3pPXiYQ9iv1opNTWgbMGikbqHJw+RVze\n86hWmUdy5qrI9rwz91f7fat6gwrqgb7FH4WUIZ6cPCqWrsc7lyuHe/wvUnuG\nikXyEi664vhzMX6INiepelOa5tZSIb7F5/uiPKbsIAHJiICqnXKOCqiBZbQN\n7C5cjA2J4ZNwZhnB8xv9wS63cxjZInp7AbuaS6XTp0EpLc38MFPWrc7JmYxP\nCtlZXhW2qky+Be7hAp18QhtURWyQvn3bcoZssrfGrzTaWSDAQApzc3rUqqkC\ny3TcgX/qDWMHMcggvaKC54Ujy5neFXg/AwXOEObVZe5GhGkHS7Qf5MreWXV9\nfkfkxv+4uuVtvdW5yg+4gsNsEXnd4ibnsi9BRkQcVD4QsPyV0hBbxpvJpFXI\n1bMMggJE8+yeEPy1PF42AlJG5BTA3pqLsnL9dqWxSwLW2a3w1XZRMBJzCRkT\nCKUD0JPHafY61N9gLJSDPlEIy15LihwLAWWEAFT24KHg0on6liZE+Io7AZoe\nqoQr1CTQtKPMs9j/RIgtPe80pRFGnyJzUODYetZID+lr/HJ0TLztBZTnjhqe\nbazF\r\n=9XOi\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"c09f873ddfa9eb503a461bf150efbe0139f46025","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.12","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.11","@balena/jellyfish-logger":"0.0.80","@balena/jellyfish-metrics":"0.0.84","@balena/jellyfish-environment":"^2.2.23"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.4.0","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.11","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.2_1599736443256_0.6338834886322056","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.3-renovate-non-major-a80fda08f499bce04e8a0a9ece01a369d311c5ae":{"name":"@balena/jellyfish-core","version":"0.1.3-renovate-non-major-a80fda08f499bce04e8a0a9ece01a369d311c5ae","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.3-renovate-non-major-a80fda08f499bce04e8a0a9ece01a369d311c5ae","maintainers":[{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9e5140b74a09298f487a9b2ca50f950460f25f63","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.3-renovate-non-major-a80fda08f499bce04e8a0a9ece01a369d311c5ae.tgz","fileCount":93,"integrity":"sha512-lMb24OzLskZRoWc7+Lfs8BQQmVBsbQBf3ghu52N7jf62jChuGi7+fyfFlxFakRb7CmI7w3EknS+kQyiNmsNWUQ==","signatures":[{"sig":"MEQCIGqtUpirTYc5jJGdaPliwzFdE5YNhE7BF8lncx+egGGAAiBSIxfpeCDNGnmEch/aGC0Y3zDCTHcQBrKv8VdeBeBl7Q==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":557211,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfWguLCRA9TVsSAnZWagAAnA8P/2rA7bzRiB+VT2V6ff0a\n/iwHtNWQSmh+lYCfrp29IVcFWVYDszqX5QPkWPCSLuqBx26bishxIqGWWeNl\nBY5CuGF8NY8EdOwXv7CRbhVFpn9ShRVlNL/4N3AcpXaiagjUK6Ni+S0Jys46\nHk7jbFpqKigL7mKcyH/FDY8ACMsuWmp7ch93pymeH7DZC+R3m2x/wHi7Vcgx\n98S+mRAIEbDuT2e8FOgGcIFEHog9mVYRXL2M1mAVVEoHNnHjpTrLE9zkPGyl\n4h1YvcdIF4EU9iFpCYuxFlA4e2i4Vlmu2Kyl9/HzTnWNnyvn8d4aZE0cSUS5\nAJVWvpmWYBpOF4RpHZ1SX1oTzxSVTQcfUq5pi1sAsrndTWj6YEJ4Fw0OHQle\narmLsQ7/0iTn3F8m4i3r2MatCgf2v760xCWlxxnnr91PARPg16d4Oo+DETdQ\nyDoXMjsF6bJGdLums02+aPAPgzGKJNis4dfMpM1mRo3faeNQkWTBjVvhtWUS\nTpUUHLC+N0g6TH8t2dqy2qL2wRipO+yb4I26V8GaoKL+EnK76EjUL4Kuj4E+\neY5scdgjfanZKnnJaErWp0fAG8yWS6/CJgIjTDulUePC0C3snbKGrNo5QBmH\n97N+3lxLgYVZJkhRJrX10VYIgeubmsN6jPtRclAfrKIhfz7Dn31c3wX7LSOA\nICvs\r\n=T4Pz\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"a80fda08f499bce04e8a0a9ece01a369d311c5ae","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.12","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.11","@balena/jellyfish-logger":"0.0.80","@balena/jellyfish-metrics":"0.0.86","@balena/jellyfish-environment":"^2.2.23"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.4.0","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.12","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.3-renovate-non-major-a80fda08f499bce04e8a0a9ece01a369d311c5ae_1599736715399_0.18691617988661346","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.3-renovate-non-major-645cc3994b57b9dbe4cddb787a08660491fdc35d":{"name":"@balena/jellyfish-core","version":"0.1.3-renovate-non-major-645cc3994b57b9dbe4cddb787a08660491fdc35d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.3-renovate-non-major-645cc3994b57b9dbe4cddb787a08660491fdc35d","maintainers":[{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"fb728223a14e600b9050d801a96b9e5cbcf642e0","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.3-renovate-non-major-645cc3994b57b9dbe4cddb787a08660491fdc35d.tgz","fileCount":93,"integrity":"sha512-SlYtgtn9OKAalrR2jR/7mB7//rzz3P/FeyLCWsrHE6Yo2qLq3Q+kXxAiOs+LQYQaI9re6b+3KYkjemOs9N9gJw==","signatures":[{"sig":"MEQCIAjZ37HZ6OOv4VsS4ERoyTkAQHDpqkn8McRSKptqGZaaAiApzXtr9IVJ6lz+sAn0kgtbbWg34xA5Pswp96XlKHexyg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":557287,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfWiyoCRA9TVsSAnZWagAAtN0P+wQRw2WjiP6GGWOQH+xe\nz+21oZnbiCDcCLxIglSRWQe87b6R5+lBqmKIoxq9m318swyG6q/f2pSbit64\n1Hak78cgZjo+wll5aQm2cLgv9TxaEIgn0voMbJSPl9ssxpN50bPRfJR6ZivB\n9vaTVV9xN+lGG7hyK3ITBYF3SHpv/zzoXAcE3Z/Dq5pAMOeXsBbvOkb80FEi\nWuo4fAC+g0pWobyBIRFvU/g8OY+NeKq3RHyH3s7zFQ7jFx4RYj/j9/XLBSZg\nGX5/uzpW6Rddm7PxkiTBZzQ8RIUVGiQ6378bV4IpZKI57TfEZMDddxg1kwNb\nJ2jsktryF3JbgQ7j9rM85KGcbFEcIN382FASfpCaO0YK7Mfiz4lrbHeHXRzQ\n3rh07c5EMEGROIKHeyFJuD++rs1sCXGs0oY60vovtDqm+8dm1PigDcOV4fxp\nbCJmM7+X5iIRqEgKH69Hi7pTWer6YAYaZEXC/t66Exjv3q8hNuWzzs/sICOS\nHU3flZneAcVoFmTi/aKrfEbuU9EU6RDR8QHC7HJTRuXHs0sLO9M1LVDFWLa3\nqGOQoKio3/rSXHREe2o33cXrHWh1LflUL3mBEaDSJyc1ZSx0+k3ClZdnqRy1\nCBteVhmykKC+G+U37wFkrrZEWLssLfhm7Fhu5Un8c3JvmE/GsudT8JT/hCs1\nK350\r\n=vdIc\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"645cc3994b57b9dbe4cddb787a08660491fdc35d","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.12","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.11","@balena/jellyfish-logger":"0.0.80","@balena/jellyfish-metrics":"0.0.86","@balena/jellyfish-environment":"^2.2.23"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.4.0","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.13","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.3-renovate-non-major-645cc3994b57b9dbe4cddb787a08660491fdc35d_1599745191968_0.2819324551215221","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.3":{"name":"@balena/jellyfish-core","version":"0.1.3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.3","maintainers":[{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2a7c0d45c8afd5e475b54a0bfce89ca23ddb4e42","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.3.tgz","fileCount":93,"integrity":"sha512-VovZYyz9upLHWz24tS+t3gWo0kH+27sEW7iPfle4Fsho9zMVxu9uRV4bS7QE8WzMuGx8nux5ifyi/uDiFhhYwA==","signatures":[{"sig":"MEYCIQD+odYP1SkBMdjH+LFaem4l+ojoPyx3vVRvTjtrrmoJ6wIhAL1TcmoUZbeq34i1rOq6HoeE47dAdCXS7TN9p8AIWn+T","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":557227,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfWi56CRA9TVsSAnZWagAAz/wP/R/L2cTsV6fFqf6jhLmH\n+e2Ga8SQsHkqzWbJ9JLrFWE+F6WThylQ7Um55MDIAkrRKvU6FbkCThe7ghdv\n+F11+PULSd8XMt0lWEHK3rhls7PEQPxt6lWXbxOydIjicnhlwJhnCR1y9800\nerYoLpqxJ2np8EgEJq77LdJJ6x57TTbEE0O8wc6RX1TVc5P72dLJ0xUO84ld\nedZyeD8GCcsDS6JP/YZw4TcWmXlH+dD5wMVF7FuzcsbpwJd5T/56N0w8oJeB\nJE0xi0g03Wbz9T86XMu/N74FCqY7uKKisdXjfwRmklabcqcVw4r734Qobo7y\nISL9hi++kfVXnQfq7qHX5lEm9/yfzgWJLWWIxAvaM9d+CfVkVp3wiGOTmOj8\nkwSRuTjdqtTzwWTyUslQj2FPiH5PgZVYW6O+YYWQW6+wolckUdpNJXOMjnV3\n7DZCqTybNEAZpyvtIb6GxHvz1hhGR5hNcqoC8iTWB9bFvGO57akk+x9NRRhS\nhSeSEBLrsUDU3LycAQquUgiAcuxJznwwSVYK2vdkd8k/+nw9LS3nfsgON+ln\n154WhzSIExcPpiWwM/0cnXkR1yaKEprllGcGi4gBSbFEIFB2A+cckvfFyf6z\nNSs/OIZ8myFp6nj68w6HORWMQJOdQhbuADMc+Nvc3xstZ6uTLi9A8A5wDySm\nErRs\r\n=SMXK\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"e8cb97248fae0421dca969d3828eb60a4245a3ee","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.12","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.11","@balena/jellyfish-logger":"0.0.80","@balena/jellyfish-metrics":"0.0.86","@balena/jellyfish-environment":"^2.2.23"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.4.0","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.13","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.3_1599745658372_0.9951137468418598","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.4-renovate-non-major-0afee84544e3b21db88bf9dea45666986aa7ab30":{"name":"@balena/jellyfish-core","version":"0.1.4-renovate-non-major-0afee84544e3b21db88bf9dea45666986aa7ab30","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.4-renovate-non-major-0afee84544e3b21db88bf9dea45666986aa7ab30","maintainers":[{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"eafe8d00b80f0242c53f652db27d4ada4844e7d3","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.4-renovate-non-major-0afee84544e3b21db88bf9dea45666986aa7ab30.tgz","fileCount":93,"integrity":"sha512-ok50w48h6D+MqhOpBECOptc4Olbv3xNIbgnZzhITYJaWWM/qfoTMkJHVmC16lYe7ZVX9bO/tiLr3W6xocAHP8g==","signatures":[{"sig":"MEYCIQDK+amYXuNOdTOXaeKdEYW0De9uhtnISqi24r4gGj0rdAIhAPx9usLAjcI+xYexTfVwXbgjeXoHPmMLGKw15zuVnIga","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":557384,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfW8OYCRA9TVsSAnZWagAA11AP/1XXcuU/sn29IeWjQcKO\nmiwNrypSRckWU/5/3hNK4hl66o9nKJd2kBRPKGVYLSgZVIOMPPVKD5AK0zk9\nqcTofNmAUiJbGMPMjAmSF9PoRpYNBcRFrsyESfOG+05MrERCmWrTTG/orKBq\nrTV10Id4Sq0xgAxnFcvkI7KehpKdsxpqr95CXBXEvhpIdhLnlS8LKBerB9AO\nhMmZJsShHBs+4UNAQypen+ltSZFT2EKQWth79+0w0BJHAMHJzgQZ1GR0D/uG\nBEfmyWiNr94x2tuoNAc/CH+8sBUiw/bVkZu741XgGbRWAId7Rh2VlfZkFrDd\nnemBhAF17Uz8S8dPMpbzCJH83hAMDvyD53ON6VKHbbXnMIDUGQdVuW7+aYrB\n+zljKjjFjR5m4Q44/2xsrG3rjK9xyV+Llh7hdZie9Sp4W4ZlSohB69NPhGAc\nyHi+T/Qv7pjsuYrdrS6nSlVsJRXuAkwG2cuZUzsF2BFS4H/IGnJLEqev4qFo\nttExkHqGbJvFdUHDXQMot8J8Z4Y32KQ6iQe1s+z9dpWCLT5ZxJVx5dj+eYij\nn8Y/zN/c8o57DNOl6aM7InBhWmRAU+I7g0RyQqVaB+cUm0dr0PZe/CeNxT9M\nbTocXKW+Q+R8eC1rha5VesGXImn4whO2raskmW8UPJ8zacBGgNT8NhV1uLVy\nydFp\r\n=D+k7\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"0afee84544e3b21db88bf9dea45666986aa7ab30","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.12","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.11","@balena/jellyfish-logger":"0.0.80","@balena/jellyfish-metrics":"0.0.86","@balena/jellyfish-environment":"^2.2.23"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.4.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.13","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.4-renovate-non-major-0afee84544e3b21db88bf9dea45666986aa7ab30_1599849368279_0.47471466690878406","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.4-renovate-non-major-d0869340eb12003a1093273ad6f5ce1abdd5a78d":{"name":"@balena/jellyfish-core","version":"0.1.4-renovate-non-major-d0869340eb12003a1093273ad6f5ce1abdd5a78d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.4-renovate-non-major-d0869340eb12003a1093273ad6f5ce1abdd5a78d","maintainers":[{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0630d87ab9b830078818880270bcf96f1bed8c06","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.4-renovate-non-major-d0869340eb12003a1093273ad6f5ce1abdd5a78d.tgz","fileCount":93,"integrity":"sha512-cWFh4oU3Qr01RMGy04o9IrxFV2scpo8XGp49xL4YOxJNqihkXRr4Me4PN/BbWSll95ySLoo533xiI/P4Euj9vQ==","signatures":[{"sig":"MEYCIQC9RzcvEdfQWyfu5ERNLKt95d74CvN3a3LYoMcvoUdimQIhAIdsUh3HiFNCMncYQROl0H64wUiDglt42d4IUgRTMvTq","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":557460,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfW8WsCRA9TVsSAnZWagAAZnEP/0xb9JyLIzIVzgUu+vsj\nY/x0RTcxRgiFkOPO2tJajs9tDK7oXF7k51ge4t4qTuVj1BewGGeyLzzeXKzE\nmUxHMEr9aNe/zty1+Hd4+JDuD7JS6AJWVZLKpZN8t7fOqlTq6zZnhApdsjxP\nQwgzspCseLqEaOcB51B9VnAf8R83LHoxpCja5PAn+ahxUmkeuQdpidI1pf2W\np68qCoNmXjV1PwFfHfoj63dB1QNKi5XAoaMCFU6KMSN50BQZdFDedqTRO4Xh\nIkNAsND2VMy9nG3MS7WP7B1Q5KaVVlZ/GNjX+1LtROGA0zU372jvKAjhO3dm\nUriYxHOPGlnktqJYIAOqkcYqVKyVkprUmVraEkpUqBAtp6+Yq2OLPbGTs8kH\nnTpb5aLq6pjaXRTgrdYX2HhkuC0cyPfvl5GTO2+b1Z0O9+MXx/gb3C8ZllBX\nN2ydlI5W7lcZ1NQpikHUubZg/0kwxgIzD6lmpDEN930IcRN/TgPog81u27o4\nGfC+rdZmi5Anf0EIJn18LbFja25Z7tkcy2yfzWspaPa67KozuFlNTOKyZjXK\n1ZTsGt7Nwl4sU+a8fd5/hqbcppkS0F6njrAKTzg66fcLgmiZnAUnqCgXBRev\n7qVr2DCxoVoyVQxkkay6hxz9VIj6PzTOC0WEV76RfhgNWQlt/6ZbEVCP4/Gj\nUne8\r\n=BHdZ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"d0869340eb12003a1093273ad6f5ce1abdd5a78d","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.12","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.11","@balena/jellyfish-logger":"0.0.80","@balena/jellyfish-metrics":"0.0.86","@balena/jellyfish-environment":"^2.2.23"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.4.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.14","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.4-renovate-non-major-d0869340eb12003a1093273ad6f5ce1abdd5a78d_1599849900139_0.02603849955971782","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.4-renovate-non-major-50f9f1c7386ff2886ea67bbbd085ead49773377a":{"name":"@balena/jellyfish-core","version":"0.1.4-renovate-non-major-50f9f1c7386ff2886ea67bbbd085ead49773377a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.4-renovate-non-major-50f9f1c7386ff2886ea67bbbd085ead49773377a","maintainers":[{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e83ad0384acaef35832b229b4f054d1d3105e99e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.4-renovate-non-major-50f9f1c7386ff2886ea67bbbd085ead49773377a.tgz","fileCount":93,"integrity":"sha512-YyK70fgJT+5luQR/kOd4NIYeUR8Fh1wVXAewVlFjTD8ldJuEUPBrmU+E0H5WEQt9RFco/HPvsJorygE7hu156w==","signatures":[{"sig":"MEYCIQDf9+zltJgaaKd+3BjGQm1roHe6OdMfmtWM8fhHxkj0gAIhAPGY5GlZFQUcy6D/lkTGWG1YD2viSazOx9bynR4ouuWp","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":557425,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfW8bzCRA9TVsSAnZWagAAEM4P/2rzkG1tqV6AxokgRRCp\nM4CC8mF7akmJEhhnSBG5exLBhiomhPhA6zHZvbClVq5yu8wmDCv1Yua6Q1DN\nIojcom9IJI/HaZ4GMT9vYHirjE7D/h92neoZtHGrOH124MPnvFtMfbkpqpE2\noOgq9qoCQOKIy2Y06i/waTz4GEyz4jpZWZiPzZehR5X41HgU8gyWT2PRR4Ir\njjJWVb35LpBpz88DIDGsOkGpkeutY7TWUG8YsYW5FfwtoA0QaNJEwENjSNcH\nV8BG9iLrnl8FjiSnz2FINuqHMntVg5BRFyWXbcQy6uIusRdL3gsisFhycNj0\nyg/bk5Y2qNInZ0AORPUmiE/ioZmAXxEgm5PXvgXPlztNE/FJWKwbmkpO5NEc\n+WraUJuPVbjtiuU7s2U5uKF7AkWcfJuKc8zS+Qawgk5TUobz4dwRiaEYxhXL\ncr8K9mAlgTziE+0ailBaXcN2ac7+qM4cyjHLp/unBLe5zfp7kaSf6PNkb15G\nEam0nvzlXYUmtGeZFSMUczYIzm2yiP0x504OplIeurj4Ts9BD5vv6+K2nX8J\n65nzPnevalJQw8IX8ovWGt99PVj+9tmu9F+jZGhDrPuZE6sYbuhK3+Zhhj/P\n2ZOYUzJwqzYtOvV+u3eILg/xVdGat6ajAaY0+JOuOd9lYUVHxqlqOiQ43XS2\nQQlp\r\n=Mrjh\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"50f9f1c7386ff2886ea67bbbd085ead49773377a","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.12","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.11","@balena/jellyfish-logger":"0.0.80","@balena/jellyfish-metrics":"0.0.87","@balena/jellyfish-environment":"^2.2.23"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.4.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.14","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.4-renovate-non-major-50f9f1c7386ff2886ea67bbbd085ead49773377a_1599850227324_0.4315739262051348","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.4":{"name":"@balena/jellyfish-core","version":"0.1.4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.4","maintainers":[{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ff50cea6617248d8a60b132edb81b16ae8478c2a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.4.tgz","fileCount":93,"integrity":"sha512-RH10pfoasKg7DXf5VagJgwbUSalla/nkUgXRQ4SlKV48PJ2URPkXH+jhONzT1B7MV/W4nqr2q7eWKgv8/osXrw==","signatures":[{"sig":"MEQCIBk/GXreffNwtnvuOmReAtEvlw9i1F0xUDoGM2E3wQ9vAiACza7RHD69j/YPuFs+iXpAUpFqKrusq7HZJB22D7s0GA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":557365,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfW8gpCRA9TVsSAnZWagAA6aEP/j85itWn+uicDSxtZaeZ\ncVZXf9h2qa3k+G7tJvsSC+dcXpN5RlO6FwNoooay5LMoZQ0l50RBVf5XaSQX\nUEFm7MCWX1c+aaJxUPtIqE54DjYADbSHIbnzH2cvmwvALa5uYwUXMKTNYp+L\nEwmUbaK2hqXgJobxUBbWhL2sr5+ioUWMg1+S7fBxOy5G7S6mTC5l81dSsg4Z\ncK8e1g7YCCQtwzBy+iliN8sbSExGUBQhyLRUVyP4VIjLYn7meehlB6R1H80o\n1gKW3JNlW/vpzte102QZddmcYrqByju6T23sRYYu/q64WYiDnm90d3oyGKeG\nmqLX38lvZfX0kL8ATCtDddb4Md/VG5FXEee2UgVEyoJTsW/r05T+WOGFTmuN\nvunfJdS/OraSCbkCjOVTh95FIyUojQrQZXpB3D+lw2739kc4dVayn/t1+g64\nJZ+l/4IoIZvbdb7gtZBlqXMrQ7/Cko2JTgBFuT30/0AHExYhW+4F9jCwJaDc\nZwbDjOMpbhqplSuqthg4Gt6hbTIdmYUzQKazvF/kGnFwpkJXzCsZX7zh0CkO\n5P3jDu5b+EmMOaeDl8rcHNJ9ob4mUL6016xItYC0Ho6HbehsZIbUCaAVro/x\nQhpuku9OjGetwHYe/dwZRW9zn6d8XMl1rxmMIBJn7wlIBjEEqNz8P0XgBUJD\nQYMB\r\n=TVL1\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"f19e371ec435055ce11d5ace4940dbbe007c0125","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.12","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.11","@balena/jellyfish-logger":"0.0.80","@balena/jellyfish-metrics":"0.0.87","@balena/jellyfish-environment":"^2.2.23"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.4.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.14","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.4_1599850537275_0.8742018509348084","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.5-renovate-non-major-cddb6b3d596f8beb5730a1a3967d263196339a99":{"name":"@balena/jellyfish-core","version":"0.1.5-renovate-non-major-cddb6b3d596f8beb5730a1a3967d263196339a99","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.5-renovate-non-major-cddb6b3d596f8beb5730a1a3967d263196339a99","maintainers":[{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c3032740216d4e6579d843d3efec48384966acc0","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.5-renovate-non-major-cddb6b3d596f8beb5730a1a3967d263196339a99.tgz","fileCount":93,"integrity":"sha512-X5ZM2UJ2jt6DJTQCOhlXFBI2B+rEc+fQgHE3g8XsJ3CNUeIfs9kNcAH2c4ihfDM6O8ozcKy7jeqo944fXEHm7w==","signatures":[{"sig":"MEQCIAWtTcJ8snz4N9/5q3NYMqnCsVRX7qp8XHBLvH/hmbeIAiBfxaf53/txd51e6QKUoKR2OM10ixrVlVmHpjmYJnp3qw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":557522,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfXLBMCRA9TVsSAnZWagAAhnIP/0FVjyu1G+gZQJuiifgg\nK3Zmu1E3Qm5+qqBKiEzU4bSKcxgP9DMTbwUb5dG45kRdKmWMSGa+vgA+w7Gl\n5mqCELkrRumMcdTM/nK8g4LzjuvZx+HXGtlEwVHoACtZ9r51n7ff4KvHDRnQ\nc5oLpyAHL/g09Vt1qiXQhHtfC3IqF9smwHFNPuBv7sHMwTC+jYKZlSoGmwwU\nA33NKFgAmMt1h/l32tu903yf4nKMRkl7+CiVilQZxaMCQYVOhmXy4Atwdu5b\ntqjpVqGVpuZOJCVnjOy0AsLIYbtxNf97Cj3Sjo8HSm5SwZ9ooPFiZSivUajH\nlaqRTbrlqOSR4saWaJFowYzjh6deDel1R2jHKZn0h4JnSADPw7CRCQPqefmM\nrrSSAerFg/PTLxw6+hNgvPdEOYzzaWEcB/3g/LaTEJXX8g4Y7Ca93OnI2e0a\n1+PclJ2GriM2q8M/uyA+61Li/adlHIn3eGB7910imOQCPnFYlykeX4rzGLg9\n3/+rvH7EJwRz4iPxZ9OBFsvO0z5xuODd6FXOdL6vEZv9M5KjJ8mf5Il5bpzB\nA9DukNL9Png1d7KVyPn6Y3K0v9tv1fMPzPf1PIuvx7uzXHcSZiJgSDqz84rQ\n2ZiY/nRSBKynrb6lDRwiMGA4P5JoilWlFHj3GhjD1A0H/wosCG1Al25g18Fd\nAF/L\r\n=ZTFr\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"cddb6b3d596f8beb5730a1a3967d263196339a99","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.12","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.11","@balena/jellyfish-logger":"0.0.80","@balena/jellyfish-metrics":"0.0.87","@balena/jellyfish-environment":"^2.2.23"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.4.2","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.14","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.5-renovate-non-major-cddb6b3d596f8beb5730a1a3967d263196339a99_1599909963871_0.8006344218070434","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.5-renovate-non-major-14997318e656cd14aaf0d47018294b7459f025d9":{"name":"@balena/jellyfish-core","version":"0.1.5-renovate-non-major-14997318e656cd14aaf0d47018294b7459f025d9","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.5-renovate-non-major-14997318e656cd14aaf0d47018294b7459f025d9","maintainers":[{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"11c86ce67dcb1b45f02993211d44f73652e98f31","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.5-renovate-non-major-14997318e656cd14aaf0d47018294b7459f025d9.tgz","fileCount":93,"integrity":"sha512-xUziFM76pOHLem413bejsCI6BHvOsBkzmX04+5WHIHRb9obVkyf39OrbetgrOpYUKGKhRenn1Bu+f60miEzo0A==","signatures":[{"sig":"MEYCIQDf5XxLJEjDC9APS6OH6i1Zogf98rqBshum7QHR0uvURgIhAJBcrJAIPcM+iSrjzVBsLM705/4TQ0bZULWcU5ZeAcHF","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":557600,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfXLKGCRA9TVsSAnZWagAAFCkP/0uT06qZrWvef1YcSBgK\nLE+Kg/O8uHr6blZDH/mR9wiyKhB2Zx+SFjqy0d3put3AlRMJTomPEcGC4El/\nPcWU1S0SqtYNlSClNfWkHbuTcmxI58glbg/DxX9bsCXl8Cj/a6n6hYntE8RJ\nHGMBCEdYDUpdHSMQ9dQkRQR1a9XM0/Ag1LWgIo3yd6lTRQMihUy0PwoqBBuH\n+4wJkcSBc6ChU+/fjcDGASTo+S4HdUqTDId4gXTGTa1SSN9cggal2ekYCUGa\nwjseg0vtYkcesWk81FljTehF0F4CXW23f+2MwNjPENgyrVspZCgFJM7QHPIW\nEAO1wGft+1dQNbqvOoglUyKTkoBCOWdb/LMT6LELlHx/xhZ3G6QMD/AYiJ1c\nzJbw0auIJLrfKnuej4J7sckyJNcEtrpbbZxG1sx0HRYkOCSaQtrh0M4hydgm\neiIhUb1KiF/F4nxsRNgQbZxEgryPnOeVNGj+GOVYIzJpk1KzG50UALeHki1g\nG//zuJH23oG0pJ46sJDKzPsKEPkdFlqdcDJrLO1+UYjbXhf9psvVpqlf2fPo\nFMPYNssRQUwl0n2XFfrY/Q51PTKHknpHiYKCczBWfwpVxreKW2+KdEsaP9Bn\n76sBA8ekXNkEnEzx2kfkZvk905AaFtIduyQ7hOzZPQDJ0FJHd+BWdLyJ0lLG\ndlgS\r\n=zK7v\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"14997318e656cd14aaf0d47018294b7459f025d9","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.12","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.11","@balena/jellyfish-logger":"0.0.81","@balena/jellyfish-metrics":"0.0.87","@balena/jellyfish-environment":"^2.2.23"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.4.2","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.14","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.5-renovate-non-major-14997318e656cd14aaf0d47018294b7459f025d9_1599910533715_0.11826137014222327","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.5-renovate-non-major-f79fc2d2c7d158e3a329636b9250268043be8025":{"name":"@balena/jellyfish-core","version":"0.1.5-renovate-non-major-f79fc2d2c7d158e3a329636b9250268043be8025","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.5-renovate-non-major-f79fc2d2c7d158e3a329636b9250268043be8025","maintainers":[{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"101075ed843d5bc6d991212f7166188aa10127a5","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.5-renovate-non-major-f79fc2d2c7d158e3a329636b9250268043be8025.tgz","fileCount":93,"integrity":"sha512-JrmVUy3kbtUhlWMHSLPBFIBsZKvVApwu9pV/AVa1fmgCxKqUxj73Qg6CQf8ug56RkM6sjQKFyUqCcifDcM2eDg==","signatures":[{"sig":"MEUCIQDaEBkF9lFe6l5icGd6cwd3VASzLvJFLtM2tNuEhfyAfQIgPLXiwbbE0NNOE5kqeL9dxjQKxjmSB6ZA4fbQRsRATrk=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":557563,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfXLN2CRA9TVsSAnZWagAA+dgP/1ZJ9/ErxQZA/2hWJzkP\ntGykL1TRLQWgj13g2Si5M8wi5NF60UoFmaOgtuoEOgwKvAHv2TiBKM3HIG3w\nAra0RTqn64eTDt8jjYcoMZ8uJf3t/6D8gCDiN2SYQUpHLtTq+U36nwWNcgbf\nJzJHpjbz6jk6ktqeayAQZ5NTLIgZWC7nKehfYq1ypjkI1n14N2r5nPeBZBtX\ntJlo9kdRqt6FlDKAFSoxrA+L34IuuC1ffuQqqVuqfuwz8A2ZtNdBiyZS8Wvx\nDXPPa+OQ2c1Oi5YzwH7lHi6PTJkpHsguz5qJnaEe44zNbUISvHHQ8vyjzfv0\n1H7MQn+/H7WFM689dcwgJn1dFxgPhhb2q5Aj++t9WQpIT0lU7ZknJcMhM1Ta\n4aR6KrEgq6G5pzTuYsvOLhc3wMhWgP5cluIWskPgLuXguhYTtjcJguPWdEMg\nlEMholVNURjO0+Kmt2yZOy0n/8wh2NAd26931S7z45A2t84OLmOJ702SxvTX\nQRbM4ivszJeny3BWHiADps+ufUqGIH43rLGQmtAgFagtdHEvJh+ls/YtOeu/\nGBzOE2Kej9Zw7Y0MYEnVmGyAHZEU5xyVvsKJsnIGRKG2ctq6xhWro6T1Gwzf\ntNnhYaJ3TN4xofCLvjl2FJumXtVN7nyLpdcRvH+pd6JsN50My6qyp2uFDeRZ\n1Ct7\r\n=VNAC\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"f79fc2d2c7d158e3a329636b9250268043be8025","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.12","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.11","@balena/jellyfish-logger":"0.0.81","@balena/jellyfish-metrics":"0.0.87","@balena/jellyfish-environment":"^2.2.23"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.4.2","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.15","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.5-renovate-non-major-f79fc2d2c7d158e3a329636b9250268043be8025_1599910773886_0.27889710096558873","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.5":{"name":"@balena/jellyfish-core","version":"0.1.5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.5","maintainers":[{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"836e6987da66fa43ddf46dd5f19064735bccd19f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.5.tgz","fileCount":93,"integrity":"sha512-dhkLVe87dxogOm5c4iMIOZmfhwTmkOAhKXf7ME6lngtADAsJNHuglBH/P7/EZGovMWJyS0lro1wPOyIqCxbdDw==","signatures":[{"sig":"MEYCIQDwZjyXH/HAJERO+4It9HeGUhDSMjx4Go9c8j5qWSYoIAIhAM5jsSLzB67Eq8rJg3XzHtWOU5feKhgqmp2xrB83pp9E","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":557503,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfXLSwCRA9TVsSAnZWagAA4cUP/RpDu6kW9mWUPqN+BNoQ\nGK5+r0VJ9k3ER/GzlSRbrBhuIRQYYax1HbtPccLfLjT5EgUtQjd+zhUj6L+t\n219zEV6DbQV0TKVrx9xhZ1bAySWleWQtnumTZd4iz1H0lq+6xod4s5qhsJYW\ncpD7vrCj3k5kg/3bNKTq9Sj5HDifygOyKArE4tsoZ7f7M9P4kVG0kC0Fy7Eh\nHMYOKS0FOCPbSgZzU8Jaj+SVW/esHaOEHx21RXQGkzap2b+it8KIFpQnlQJI\n5JZfc0OcQ4R1RX5EIGQOuFyA47d76L12jWh+ic3VTKy11zRfRtMaur3xZ17H\nt6lZN9nyQjKxWLhSyojNeirjgjpQAI0JqC1TGUsSVhtC4BqqDoODo32lH7UY\n8VIaw/eqP3Alpl1QssJij8RwL4uF+AVdrwowo51y2KLDkeLNJWnTBxcbQyaI\n4gnIJK+5hb3sGXPkKnjSra+X1cEHklagV8OeLWWNVlZDAyWa+pV6DZq/VlEa\nlRML8xE9zcVG0GTpuvAczq2tQM7jDuabRPZeVIrJxkn2ksoQ8F5JogHQRo0w\nda1GsMVbZaVUnKBrzgkXNImig14P8QXo44VcyoMzm1dNzJEMYuS1ou0cfiEM\nCD6xirzjMdl1YNb0E7wgfjizmIJbVau3QDg6LXuTA5XR/VSC4Ba48+Dfg/OY\nP+Kh\r\n=+K0w\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"6fd52049ba882f2a70cbda8523c96e5c1ec060fa","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.12","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.11","@balena/jellyfish-logger":"0.0.81","@balena/jellyfish-metrics":"0.0.87","@balena/jellyfish-environment":"^2.2.23"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.8.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.4.2","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.15","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.5_1599911087749_0.016040241357025975","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.6-renovate-non-major-5497ad084d6a8d1ecb7168474f46d832401ec1bc":{"name":"@balena/jellyfish-core","version":"0.1.6-renovate-non-major-5497ad084d6a8d1ecb7168474f46d832401ec1bc","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.6-renovate-non-major-5497ad084d6a8d1ecb7168474f46d832401ec1bc","maintainers":[{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c8efc2c9708412331984af26b0206ff9abb44990","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.6-renovate-non-major-5497ad084d6a8d1ecb7168474f46d832401ec1bc.tgz","fileCount":93,"integrity":"sha512-XAw/VtJJfwb1cdqSNdrWGC+8Bv/6WzBi8pgV1oSyctbe++daqFtjiMn8mz5Twb0zaPg64nNdz6CrDVRWf4n0Vw==","signatures":[{"sig":"MEYCIQCu4M5e0bXSzH5uPHXyIDBp1f/cH4NsfCXOvdqWt3AF8AIhAOpTc49zYy/wv421eDlXbc37o0lcDCfJbRxbotbgFqcA","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":557628,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfXTCvCRA9TVsSAnZWagAAHwEP/3Byy1XuZ7JIBZ4FDnJf\n5FTrWUnt6HMpfpV/CQq33tn9wTDyYd8hxs4Eqlev/g3Uf8iOIuDmqksvOb//\nDAGsvs2bvt2iAf5/jN4gygjV7LO+CDl/yzapGFzx2VkOm1Cxa3s0AiMhSQQV\nEIKbXFQc3hiLNieqKziKLoOul93rvaTE2aBbz6q8jKuCQS2xBcaAD/pUXId1\ngygFnvl6xobbTCJy1A7huL/IVBqJOP1I5tSYPDfd5c2YQe4+vaFcxdJnGn/t\nlLTO5BKPAmbMOEmwMdAROsS1Blum4f063NOUx9uGMRiICFuKrC+tQcOlRfW3\naHWNVDRonUMa9lRF2YMK2HB8yWLB3gN099piQ4wsseBwk0LVEO0fEDtrGXHh\ne8kyA/Aw09Nj2IToBZIwzwvGTpelhqwukNXRpir5u59T6IUnWrsSGvTI1NxB\nbbOO5tcGdt+3L3zWg43WYWTavX1JGGRNpWvRN+UzwS4bz3H76L6H1/dQxaFg\nDViQZbz5apPWOWyzwiYG/0eZtQklglXeywAdrrXFoQPRTvMIF9r1k/2aWKU5\nGfW0O7zfmoYyNsswGsISSxK1/nOfqgPTAWYgwRyWp5ogRzLxeQykTX4ObFWd\n2g9qjHDVsOauo+23dwmXuJaazAGaFZ8M8SRdYeWOpzHyjUcG28J8yAFhbGxU\nUng2\r\n=X5Rg\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"5497ad084d6a8d1ecb7168474f46d832401ec1bc","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.12","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.11","@balena/jellyfish-logger":"0.0.83","@balena/jellyfish-metrics":"0.0.89","@balena/jellyfish-environment":"^2.2.24"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.9.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.4.2","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.15","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.6-renovate-non-major-5497ad084d6a8d1ecb7168474f46d832401ec1bc_1599942831345_0.34261080758166207","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.6-renovate-non-major-113a6525cc9961aa2bae8d8bbed2f6a664b379bc":{"name":"@balena/jellyfish-core","version":"0.1.6-renovate-non-major-113a6525cc9961aa2bae8d8bbed2f6a664b379bc","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.6-renovate-non-major-113a6525cc9961aa2bae8d8bbed2f6a664b379bc","maintainers":[{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9f8e8b2c3ca24749c1c25e8f0fffbee9e1f8fd3a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.6-renovate-non-major-113a6525cc9961aa2bae8d8bbed2f6a664b379bc.tgz","fileCount":93,"integrity":"sha512-T4chBziokY88kYz3u+Yhs1G2lzBCyX9d6BaPLflmFuajuMsxa134FShPCkIhzTaHx2BM1rOjB8KjT65hwEGu7A==","signatures":[{"sig":"MEQCICbVQz/I18YNaN3UsxPxff0bK8qC8cUvLQgUL8fC9D7fAiBU904sZHOCSJMRKZkxgr+NBJDuvHI3kSddVSsLv9fY7A==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":557628,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfXTGXCRA9TVsSAnZWagAA3kkQAIO77jm2bl6BaPxS6K6U\nDUc1fSBWbj3/3z7x3Sh1x4oz9IivauwVKFl9mVJPnZ44hbvtPKx3xlLaPlC7\n84uDe4ic2BlIGin7jKg9P7VWryQKnyvSciC3ayoAMzCxhxygvb6/UKEc85st\nu4OeVKWjO14sLJY3NNsv9AXHTo/P8YT8pyJSSoaKx7z2ezAOAIBiTA4gJ2CV\nJxQDHyxD0ap6yDz8iRGI1RWA9FpP5Ggfx2whdHO+8ckrmD+OgY7eEJVcfv3D\nbMvoLvW58TwO1Fz5njdXvUOT+cxNaDjwD3EbxVYDYUxWU/qiMyIlsTlv44lq\npthexSGSqgTLfFbPHAuYkNwzw+PfR3dOHjXbtv2Bp8pF3W8ITah5/7Lxz3q6\nKOkGi1vw+F2KKp+I3wHGXlcZOkmdxh0uOf+CJhS42qmUzRX7tOkyUGGFSemg\npTGnOU64CzOAIMcyENN9d3/pTu7RRVKo1R63IldBoOcv7oUPOz1EQ+CDgTPO\nvJh708cEXqP5m3PhhwqbaL0m/QCZP3kgdmtcO2wFAOF0fNDnEU7AeEFcsQma\nEWYbZ4f4zlcZOo5ZxXp4rkGaWAG6T/93Ff7RRN8m0kpmJDOkhq5/y4IEdqqE\n+H11e/wbsnOOP+I/4rqJFewvyIZxgIehrE0Lnv/gdhvF0FfYR7nxQacRjJqh\n7ws3\r\n=UGMc\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"113a6525cc9961aa2bae8d8bbed2f6a664b379bc","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.12","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.11","@balena/jellyfish-logger":"0.0.83","@balena/jellyfish-metrics":"0.0.90","@balena/jellyfish-environment":"^2.2.24"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.9.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.4.2","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.15","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.6-renovate-non-major-113a6525cc9961aa2bae8d8bbed2f6a664b379bc_1599943062495_0.06292463994297237","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.6":{"name":"@balena/jellyfish-core","version":"0.1.6","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.6","maintainers":[{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"56605b016c524dce211947b1ce3cb8acf628fcca","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.6.tgz","fileCount":93,"integrity":"sha512-zNppGZmDev2FCCnHJPw45lR+0WL9uX0DjkLSOZNsmakxsmGseNah0kQsdG4v0uL/HAi5196fByODu6WiTeP0qw==","signatures":[{"sig":"MEQCIFLHQUGo3XrTmU5g/gxjgmVPqXf0BN/vRoN8r+Y/HSsqAiAQkmx6TmRCxcStTadwdoFVHK+dtpnVrQl4/KQtnpUy3Q==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":557568,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfXTMsCRA9TVsSAnZWagAAluwP/jYOG3mFbRRo0H8SDdxj\ncdKqyNCCtk2+H3UQTafoIUtypKy5fZcplIIjd9/AHKz8tUJsEzN+FFumsdz6\nA7omEyHdOsn/MNAO0PEanCpM6MwaRLbDZ8l0b2wQ1hTTH9nlrUwne7wgJgXh\nFRtszmIH+ENR01Rv5UpTFaQuYQbGJW0GqldFilcz7ERB5D+1Gynvb/VwKifl\nVCBrWABDDwkHTrG5DUMPyqcEVBCk2SfgI2q08PwukZE3JQmbuI1rsk4c5F3I\nIUZJbTJCs7Ivze5wz/Jv+Z5VsKY5VZTTTJbtheKpFeK6sS6nkz/fiknv3D+l\n2Z2J20XIBo6/dK0fz5fIXsGKVWod7BoVM4AemkufBKP23cJsgb8RpCVN/AFh\niAuELwpxCAEAJyTFWeQIptzz+Wlp5enOwVEmb+Du7mKfE0FqPP4YirXt4jow\nCmuquZhYb7uUKQ0TLRxAw6FMnSgx8Yh1ASx36U9xqjugkjBoYr3fPqDQ5vCB\nsp+3t0Jw4BFdNfqmg2CQS2Sdp+cioNifyxgGs599ulVtdm9D9S9/YTFhU/Kw\nRdKuQzs6Jp44940sheDrrdub/YNXQsewibzWpv64tUPHDFreStM0E0C1PKqg\nx/T9wI+cRUZHde+gYPvvGRKuSVKfkfapXKbE/wEyYqnLQE464giPzUmH28p7\nAZKj\r\n=kaod\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"00823747794aafc3ad0d2e1c0c76dd82d4eddd6e","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.12","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.11","@balena/jellyfish-logger":"0.0.83","@balena/jellyfish-metrics":"0.0.90","@balena/jellyfish-environment":"^2.2.24"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.9.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.4.2","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.15","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.6_1599943467564_0.9364235591071959","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.7-renovate-non-major-f05b534c6f73a9546bd1922ce7183cf84504bd66":{"name":"@balena/jellyfish-core","version":"0.1.7-renovate-non-major-f05b534c6f73a9546bd1922ce7183cf84504bd66","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.7-renovate-non-major-f05b534c6f73a9546bd1922ce7183cf84504bd66","maintainers":[{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c09850875b8c1cc2901c8d68b0d4754c63e4d4ad","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.7-renovate-non-major-f05b534c6f73a9546bd1922ce7183cf84504bd66.tgz","fileCount":93,"integrity":"sha512-9Ro2nCNvkd6znF32eZq8V6+B3OEeNH7GIue765J20meyet4FfJ3ybLOY78zTWmkV5Jds3UOAUPzhMPRKWVex1g==","signatures":[{"sig":"MEQCICed7xmtmmLnJm1cO/iK4lD8VRv7DH4TMlY1HMHOlrpfAiABlUiTam7kUrCQ7t2TvV5akRX2Vc2OFAT5jE+T2nnKCA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":557728,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfXfa1CRA9TVsSAnZWagAA9cAP/1SXf4MZszMY6IM0h7ZM\n+0Ie8A3VqTNUY098jl75BSEQZODb8IxaZWt0yyrs6lDH3tOgqWIJSE7eSF2a\nKDCX1O3qDXSGvuvSZyOJdX8D0tNvNL+sN6WC3/0A69gUbAwIAw6TsXlHhwUo\nVEGSKu8VOo6N0lNn8s1zHthjAOQcgJ7Y2q8wW+tUT52UVKOMBARsnQXbKVjm\ntNspNxbADhE1TwFIyPTwhS/oP1VOBD6MiUxxn/X+iFjfcKpSzvJXagwH3/bW\nxss32CDX+H9BhFlAuvskhhnJ0DihhZghpdq45m8QOmoyBOqB+MUQxUCXiBJ6\n2i8WzdxOyjOWFgUTmWGxuytsXdPgQdsk+x3W7CvrrhmbP9774fL+O9+tqYAz\ns3g4N0BWtGVR5iEEMecCxZBZhQLRUoj0uuYfDYvGSi7CJBp5YoAqGlzafCSP\nHPljYPgXJX8EhewxTL6WDTgWZH85KOmIbKmBoiIlJpK4wY5rW+y51YQTpfMM\nD463wWhps7gj5vWTdsY6xkjqblz0yT+yG+Acgu5cuLl520uAbbKzjRMMNfs1\nVTFaBMKjTRilwc87oohOEGTgHgEGcYYpp4BIDnB0WDpwW+D/2OVp51PV9T1z\nzDjBN0dt10kvolWZnmCrq8zzQa1oJWRRKxX4R86q5Ji3Y1GJZD8gNp/17CMH\nmlGj\r\n=X6/o\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"f05b534c6f73a9546bd1922ce7183cf84504bd66","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.12","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.11","@balena/jellyfish-logger":"0.0.83","@balena/jellyfish-metrics":"0.0.90","@balena/jellyfish-environment":"^2.2.24"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.9.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.4.2","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.16","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.7-renovate-non-major-f05b534c6f73a9546bd1922ce7183cf84504bd66_1599993525029_0.9377950483615654","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.7-renovate-non-major-5b10357906701c3963883599adc20e2db952e741":{"name":"@balena/jellyfish-core","version":"0.1.7-renovate-non-major-5b10357906701c3963883599adc20e2db952e741","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.7-renovate-non-major-5b10357906701c3963883599adc20e2db952e741","maintainers":[{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c6377318972cc9896c4c5c9ab1d5d74f4c22483a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.7-renovate-non-major-5b10357906701c3963883599adc20e2db952e741.tgz","fileCount":93,"integrity":"sha512-2TQt7SrcZBpEzXIrPtohc8BwIDoSGaNdom1FuFuzGq+mFLG0U/INnBph2VFqP4Xtl23Do1t0I4KIbRmwjBAJtQ==","signatures":[{"sig":"MEQCIFbd3DNICvmXx3DE3zlmNjWLUREffOjsp0+xBDu+izhjAiA+RcDgonjqtfk71Vht4XXYZl4xaNNnVLr6I5IJkt+JVQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":557728,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfXgicCRA9TVsSAnZWagAA77oP/ReRau9gDJnbB+fBV97K\nDQRTjuE3h8YYgh/BBpXDaHVuz6wCHBLdH5fZmVJlykgTb1ThsGJOaS5hYFmH\nVf1CLjcoBMtsR+trFkRMnlYpRligkkyylEubV0LXxCGDFd2yfboVlR1l5KoD\nYHWQ83mrNMlwrUi1RU4LllYZwvQtIKydwsftZTOPznaMmc7lnb5mBemyLu1j\nRbM2khLaOtAES6sGeBKO/qOBfVtUiimJfR1P/A58VIDaGRo+bTiauniNzxLF\nB+5eF8pJh4XbYd3HBoLA1FIYf/T+nC5LG9/HvN64aB/Qb5vmLwvbC2iLCmle\nvnWjdjIEBWtdyROBJy8YO+vEUUX0eBYoUSoSBfnb25nWKrV9ZkXj8ZpypiI8\nTnZty6OA7x/vbKWPHO9XsCsyJvOeqDR4pA92hvlJGAW3lZcCt6HSm/xNL/vX\nGPBX/kTZ+nukzAyFFj4DUQcOD2xKrPvU4GMoTv0FS6poimgnnqyNx1KQbrIb\noe1ZG3LXazmernM95ua5+OZQ8tFSHnHWw1Ca6CkC/VNrceh4CyPIPGrsdrcN\nQ98LeWo67gbEcGKKg0By+zrcBls52xPUKnD+EiiYrFmOz6bY+ZasP17bm9us\n5mMNWrv0HD748yYZTle7hyzFPu/j57BzQF9oP4wPcDb6JGxqPAw+VNnsWCln\nFQZv\r\n=/6hw\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"5b10357906701c3963883599adc20e2db952e741","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.12","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.11","@balena/jellyfish-logger":"0.0.83","@balena/jellyfish-metrics":"0.0.90","@balena/jellyfish-environment":"^2.2.24"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.9.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.4.2","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.17","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.7-renovate-non-major-5b10357906701c3963883599adc20e2db952e741_1599998108156_0.3452456956165404","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.7":{"name":"@balena/jellyfish-core","version":"0.1.7","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.7","maintainers":[{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"fedc2d5c2ffeba8b5e4907666dfe03e43bc9119a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.7.tgz","fileCount":93,"integrity":"sha512-oA5vN6PodZTccnvvwFeHMeHxnvxk73IHa1frSHBBaRQ40f9vKir+qqtWpmAQ5wvHtW2HjSdYPsLa6b+y5VUFcQ==","signatures":[{"sig":"MEUCIH5sSgbjieM7NggEKlMY1x7t55kC4SnZCnZIVYeGupamAiEA2D/THIezhmiEub0cWOLUqHBMJAbhy7OLFu82ssO+Vlc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":557668,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfXgoQCRA9TVsSAnZWagAA85gP+QH/2YRmYZLaETXhRltT\nrubKPdqnVueN4OTHcgYMIgrc/auHjqqjfCafIyXnehMQ7rofuvS2750xTx9U\nvGq/mcrXy3bEQvjC6MyjMN5K6u3j7VXHg25dX2QIQkFWMpTCehBWgRJNqMZd\nuvk7rVgAWYhpPSc5am7y4/M5oI26BH6tkKqRvSffHy6yqpXaVGdXD8qai9HD\nQMbolSuAjgffni7zhwz+GXSgSeXBVT54RQ3RNTuSiFB3gRrv4ryRWzGuNMdc\ng0WIqZ6k7WQnIrK+7ddNZ+yj68eq1GUT4o9A29uIRLv/rFXL9ZaYmGPYqB85\nHtTkDmOM2Sf1E0nj7eXCIdk3K8/hqNvaskjOSyz//t8Wods0j7gfwpjaw8P7\nttCqKPhBXUqj7PJVGrE94L/xd1AXqoN4ha4b+66wqoUWyIDZj/+s0uWXkwWZ\nqsgG+9hRUErh613hNLXRJ+91I9asVpCaWOe3yKdG04on4fLc8H19dQczpS25\nynWCeNoheLjUpmaYLsohTXZf9EXbNBuiDnmQ8kjrhZqpge59PHjyT/NTNifd\nXI06bC80JyzoYUrsJ4cyHo5qcORdK8I4eXqNBcuUF317vPKIEyZzldzYkbgB\nLTeXvA4njMiMdgINcO00REE1JmAmMjOPZ3eoZ2v4Gw8VgD4Xk8MMLXKdPDPD\n9SMq\r\n=Sttf\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"24877c4fa98e52e168efff2f68125cfc03b78222","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.12","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.11","@balena/jellyfish-logger":"0.0.83","@balena/jellyfish-metrics":"0.0.90","@balena/jellyfish-environment":"^2.2.24"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.9.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.4.2","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.17","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.7_1599998479901_0.1499609521940295","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.8-update-uischema-1-7a14bd8c5d6324b09ebeda46c54c4672d221341a":{"name":"@balena/jellyfish-core","version":"0.1.8-update-uischema-1-7a14bd8c5d6324b09ebeda46c54c4672d221341a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.8-update-uischema-1-7a14bd8c5d6324b09ebeda46c54c4672d221341a","maintainers":[{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5d210264c09853fb03eda7fedc7f31c05488df36","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.8-update-uischema-1-7a14bd8c5d6324b09ebeda46c54c4672d221341a.tgz","fileCount":93,"integrity":"sha512-/kdSruycRYZylP5u8vuNAJX/Y/RjenB75U0kxLcGtQWuHzrEPgcwjYtdquSJKsDnxlcqhCkOIgHUXr2V75XiTA==","signatures":[{"sig":"MEUCIAVDv9xwYblTYfopGIbEZmALpHc22VwVhH8xQJXZhVx7AiEAw8D6pUKpTVTFO3hl5OAwuccv83nejqf/LfK7AIgd+S4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":558083,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfXtvgCRA9TVsSAnZWagAATHoP/1mQC8sZghVJ/tnsDKPb\n4aeK+gka6ISnBLsK5+SDJVcFmYL8kjBynQ0mMj1hlwzS/CbywXr6QPa8xFYs\n4ecypyrkGckFrYJjMnaiLTZCZV7KpZVyXZvIUQUt2hCqcOKGvyT7DCYK6lQJ\nZ+7Gmz1MweNud304Nh0hMQNtiV6kXvJu/+ONKCMNBPv3uN/tjIOyDEZ2Ep48\nzGBsnzZ1YJxQbkNzTYFV8KhCWY1Rrv/WoGHhzAJDHaMdYkzqwGVblX7MMqDY\nowsbrHkWGxpNKxj+bE7xhVFZ4PaWhzGicfjEwyJKML/zfbv2LXgrP0RTwkNL\nSPyjc7W1J2p4Q0Od6gF5V84km8l9Q41AwB1dLpUEaoPTk4xg2aSSIB3AAPJs\nTSTtNMuDz8r3KxXeaRn2c3dKkemXlxSgyG+qxiIvnCkiCyaYU2iQzMlTuNQa\nm5Syfs+fW45Gf5OCaT6NSbwJbiYXrB3yYKvuIkJmiwLTxSCH77F/+MXenlXJ\nxfabv89kO0RUreUNsxmwTKKCm4Y7exeIEa5RXYDXa4WFwbq+Jq5EjKvHy3LB\ndOR0FnzhIm9HNYp9gAXJ3WMW1a1abFcBIqAQOjOeyQpnLijcSJGWoX+ZkcN0\ndp9VSgZuIPqTsbOKFhEmjki0PzHY/wql84hiiLvz+BQdWEtQRUE3TJofW/Ez\n6/fL\r\n=qCj+\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"7a14bd8c5d6324b09ebeda46c54c4672d221341a","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.12","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.11","@balena/jellyfish-logger":"0.0.83","@balena/jellyfish-metrics":"0.0.90","@balena/jellyfish-environment":"^2.2.24"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.9.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.4.2","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.17","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.8-update-uischema-1-7a14bd8c5d6324b09ebeda46c54c4672d221341a_1600052191601_0.13468653987378842","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.8":{"name":"@balena/jellyfish-core","version":"0.1.8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.8","maintainers":[{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"21b5839c4032bc1c20b1e02e2ec0ac332532891b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.8.tgz","fileCount":93,"integrity":"sha512-JZe0VwbS6Vuwn76qZEfkjZR1VTd0JSdGejy29TXeQ9PtDY1hzbUv9rdEF95Km0jg+ISDC5DC2dvUqJhla5WvOA==","signatures":[{"sig":"MEUCIQDs+uLBV0MtJzo/XD65SryMspm45f0g1wNpQfjFitYzugIgUSHwk1vDkXiTWJUvVlLSAp29F+I4iL9m/6j4qSk8XHY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":558024,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfXt0bCRA9TVsSAnZWagAA43kP/AyBIf52qkhdKynRK8/r\njuZHMBMqiSpgHXClVg9nTC433b1h3KqocA9rVecIKT/DSbyKLYODc/QAr1X5\nHJ/UYm9n7RbmbpdwejGXhQrrPmCQXm+yqfQXGkIIHmY9cuD5qcHCSc6DrpJ6\nrAq7rHzMwVo0zN4IJRY99mlxLAmDOuP1FXK6PTnEJyD+LHiE/PaaWTtZiyw3\nxsHm8TV9b4bHrxB69Xj64FVH9yTxw0t9UL2GDnqsUMNdHYtFCS5cS8TsixK1\nym50Dvf3PXGTp0ZdloOccdXWABsFLjaFpBS0t8DKfMQikPuBbodzb2JKc5Af\nuq26MwUGE0YMb51MfRHpD6Vy4eU4eADv3LwJ2xLO9r1EEpfd246cHI8oOzKG\nEL2KwQ+LXd9w+FYryHZQlmza0rV6+XlYIqL7wHV5Cc6osewrGhg9s49TPn5s\nmvRf9tgQuiWzCXyXYP2iAKQoytSq6azrkhemiDUrVOQrBzPDIz7EN2UjDP58\n0jeqnOMU6/iCJdBpWBJi779wcHD9OdTbmpo6TdFP+PgiNMCYlHUV2dqYOM0b\npAu8SoECIROG73LMJvgN32GJwTPpm6aNAV/woPa8Z075YKEpY5Y1QXsk7wzO\numqTXeoEiaUV5PM78fxW+esnhEJ245ZbT0JxB5WJHo1cGuvs1xnpvQKQ9k2i\nW9ZS\r\n=gz4o\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"8086303a117000248108c0398059438a847533ac","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.12","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.11","@balena/jellyfish-logger":"0.0.83","@balena/jellyfish-metrics":"0.0.90","@balena/jellyfish-environment":"^2.2.24"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.9.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.4.2","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.17","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.8_1600052506621_0.6192772395945523","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.9-renovate-non-major-4bf69e7929fcc3fb199b6e9a06944a86c64acb6a":{"name":"@balena/jellyfish-core","version":"0.1.9-renovate-non-major-4bf69e7929fcc3fb199b6e9a06944a86c64acb6a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.9-renovate-non-major-4bf69e7929fcc3fb199b6e9a06944a86c64acb6a","maintainers":[{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f1aaeda2f02f16561c285a6d7636dba71d62a13c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.9-renovate-non-major-4bf69e7929fcc3fb199b6e9a06944a86c64acb6a.tgz","fileCount":93,"integrity":"sha512-p9ul0XX3YqvL5R+fxtN5Rl19dkGzfv2MxukX3N9+D4b8wVfTVRx8YfH7rP1cnwrqDZteCUmIRM9BGH+YenA7IA==","signatures":[{"sig":"MEUCIQC7yKnxRe+0hZ+KBoKJNvnixFvJURJn4LI6OpnwOFCD3AIgf3D9t9z2a82QpeIZZIaYPYe4YvKi308fbDo0vqvcpCw=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":558181,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfXyV4CRA9TVsSAnZWagAAMKMP/02NvMzN/TmupXcj+ogB\nSRHF5b9Z8cywUCWMtO0EDJDIKu0yQXcrUcnwyTOVgv4jHOY0yXNCrOtIt8Uq\na2jdqCsOV+FGvkRbXTQD9gFfPl3h3ykdmSKW2r4T1HEe9nXUPbKrtrZUHDAF\nS44e/uhQ6py4MlTJIRsuJP/SKBeN5kS7Po9JaAEQF/GBV5sugHnVZnaGi8Gc\neGNYhdCm8mRjefSfBAxPz5UxRsz5ALULqfr3YGG9rRS/80EKnGLN772xYLMz\nYVrwa3s1SlHxzKK06kUNwKrKe/YpFqJDj6PKiANvXT5M+819nSWD90dnzj8A\no5wexWlIzM9xzAYtbTy5j8sNEr0zPn+k3e1UupyXcKhlm7YQ0GtyjrKVC1EU\nKAVYnVydXFJzEfzVjoVEss3KQ4YMFQpPU6Pq/z1iv0t3pbgFBabKl03s56JY\neU0NGvSqTbIwVjfSgMbw7pg+1Sy+VCtqzZ4B9LXmAnZNdw0ZSVBwxmJJo+WP\nliedgbBbn/4dWVBmFGXYfIDQgsE07dH1Q6IdxhFoDYPllSlCbNJOuYcDCtWz\n+Byl10ceNGJPh1eACDO9PJAjCxVEMsaWTegQ6ZRwLQdxTj/QBWJfQ643nXWm\ndHsObij1dSYIy6rWPO47/1DaUI2WcaCR3+TYWcxUCMRv0PizIuQCox9aDzQw\nqQ9K\r\n=os6K\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"4bf69e7929fcc3fb199b6e9a06944a86c64acb6a","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.12","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.11","@balena/jellyfish-logger":"0.0.83","@balena/jellyfish-metrics":"0.0.90","@balena/jellyfish-environment":"^2.2.24"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.9.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.5.0","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.17","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.9-renovate-non-major-4bf69e7929fcc3fb199b6e9a06944a86c64acb6a_1600071032078_0.3251386874434512","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.9":{"name":"@balena/jellyfish-core","version":"0.1.9","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.9","maintainers":[{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f003980f929d73279f4e225d7b99f5ea86d06ad5","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.9.tgz","fileCount":93,"integrity":"sha512-LCIL2S8yu9/uBhlTHK1RSDgPUZf1dZyVeoo5BK0MJmlXXvSQsU7SGdjH2waKqg+FOj5VgS8TModhh5TurqP2vA==","signatures":[{"sig":"MEQCIEX4lzHFqcWDQMpmq4nFjFwm+kow6BETeKf0K9a5ccxPAiBEUHxvGciTo5J7nVyM1Y9HiKFOjb3sko3erzn55qA1Yw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":558121,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfXyg0CRA9TVsSAnZWagAAW6kP/iAC83YKVtbq09DyyqXk\nBhkRitNXPMGXg+AynxShINfUhx+2pvI6Zfb1XcncppkRzqyQ8MFH6UrggZ/t\npBH6/aZdkMDJzPyw0/yGZF+AUwVbvIO4i3rDnlOCiUk8NOlwDI2cfnc55Af4\ndH84qaEqLY3SOkyoaZ1gTARFAiygwDr6JIVI/3MvYvE/pVGAgDGgP0fQOHNc\nLstRV9/QceBC6+llYb4+R1/EA9DqNWVYXf0wJC0u21KO5Dlff50+jPnjGJ5L\nhXLJC9r/Qxrfx/MU4Jc5KAMnj8flYWeahtf6Rskia3awKOfrZiWc0Xv3Y9pX\nZbgTxTlO6PJKJzgF1gy7XyvShuJz9HFjZsvp39tQD4b3xCpR3oaibB0qGeqB\nr62+6dSU5V/7ABIBIGzdon8G6aVPzloqQhVWSM4UYL6mS+6rsaSGRqWMyE8v\nqnIuWMXgdK1OOihxu5FLJt/A7q0jBS9D8pcB5TAjraYtgYkf3r5u0nwSGQod\n2au3emILcM6kLIG2JhbBrdR0NzfK/PHSdZg8AE2EN/4+03r3GB+oZ0LQ1SME\niEq/4gOdo6yWaSYVo9sM4SXBVqHRnKGn168XSf3wJO7IQ9nSrlBrm7r4uIUm\n9yZBMJ2sSfzp4RfFXOVFb9qm/o2OH5ISqkLHgu+O5I1DGgREEJTEeEBC0QF1\nKgmX\r\n=uEaE\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"107abfff77aa5dbfdf7cf745ea5acc13204bdd4e","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.12","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.11","@balena/jellyfish-logger":"0.0.83","@balena/jellyfish-metrics":"0.0.90","@balena/jellyfish-environment":"^2.2.24"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.9.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.5.0","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.17","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.9_1600071731818_0.11015505713869755","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.10-renovate-non-major-8765ac5670d213cb724353c1bacd238a82316671":{"name":"@balena/jellyfish-core","version":"0.1.10-renovate-non-major-8765ac5670d213cb724353c1bacd238a82316671","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.10-renovate-non-major-8765ac5670d213cb724353c1bacd238a82316671","maintainers":[{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c13ae1b9381c2b07300947b118c408301f20aadc","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.10-renovate-non-major-8765ac5670d213cb724353c1bacd238a82316671.tgz","fileCount":93,"integrity":"sha512-Ab7ElCI7PeBfdTKql4Eoo4bsT4Eb7scB/5hbHdkEM/BIVTka4aQi8Dup1HUpF7o9tBZ2d9LjnrzLtcJKu2qbNA==","signatures":[{"sig":"MEQCIGIPWAN0vMExvHmwchgcWzIRIZe5lTxmlL/25kiXzAbuAiAgJ092ZHD4CKE7VqfmaGcY9/QBgozO9C2lorpRlEHG0A==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":558248,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfXypFCRA9TVsSAnZWagAA1UMQAJSODsd7Fa72pNniybVo\nL1yw8HptiPbO4o6kLK3qb3Av8dFxU45E/yHl4/a8sJTBrfyFlpdbeneDRBgW\nI2nuuUDHBuvCuy/zHtnrMDcXZ+bOSJwlfBsIFyqkzlYjlEbelZZjrvor9woe\nimYC5ji88UbCj6ULmKg7sZHVOxPIIlAlw+14wul82H1dMTKVamo+1CHkkBW4\nLUs2D2M+On1gDOIhGSuU+qNIQpwq8wDizfi7xWkOjNovfyZYGRdCja7J7ZQe\nhPi4c/x9qlv1lOMtfVcsBQQNJcFsRtVD3MNr5SFv92FxYOIa17Uapbg4190h\nqBqw56sDgdS4IE7OpdZA4650BsIZJE3SL4vOri4A76k0dhchJLvp6clRAQjP\not1OxMzDOy2Kq0Hrh5xwmKcS6zrlia29w+yd2/oG+YI/Q6KR3SiW5rS1Jfz0\n/2j0jmyrTGK+MqROCuvDwGcJvt5XE4XlzHoBgyK/RxCyM4Yal0sdoGGVkZf6\ndMULai1iy7uMnXNsnwLadGGrlETBcuTxWQHGZxwjP0BygHw0gyR9VKHUikgc\nIPAvJOZYm64aUrfGDaTkbfBMS1zQ8xuDMUZhoc182bi+ZTV2LJoC7li3cR68\n2nFtYJQhuLAl41F31g83383SnvO16xsWJOGVs8ntQLmzaFG0GzHg2L44kmsS\nSaKw\r\n=YCyk\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"8765ac5670d213cb724353c1bacd238a82316671","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.12","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.11","@balena/jellyfish-logger":"0.0.83","@balena/jellyfish-metrics":"0.0.91","@balena/jellyfish-environment":"^2.2.25"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.9.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.5.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.17","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.10-renovate-non-major-8765ac5670d213cb724353c1bacd238a82316671_1600072261413_0.49335818158072553","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.10-renovate-non-major-8dc04f72539949be88d855c3ef4a51c234aeb362":{"name":"@balena/jellyfish-core","version":"0.1.10-renovate-non-major-8dc04f72539949be88d855c3ef4a51c234aeb362","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.10-renovate-non-major-8dc04f72539949be88d855c3ef4a51c234aeb362","maintainers":[{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e27e9f4f2417bc8c7cc7027eda7054f31269afbf","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.10-renovate-non-major-8dc04f72539949be88d855c3ef4a51c234aeb362.tgz","fileCount":93,"integrity":"sha512-4kamdf+fiW42ShSKjygyAHeRHdWvsr1GIXS4yDrrly+EFgLE3Gnsrnh98rtKnTKFQs3PE/1OcnnGVlHY0/hmWQ==","signatures":[{"sig":"MEYCIQC93RuPuK6YXzcFjP440dNYXEhBA0zn97/wa67Dm65bgwIhAKXS4bl6iHIEXQ7JcG0BfNrFLNpmx7ECENJtogj13/Bw","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":558248,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfXytnCRA9TVsSAnZWagAAFEAP/jZEaV2o2hf3Puy/CaA/\n2N9xDnEmc83b/Cm1ZW35g69YrWGoCinWCGrabjSKaP8uCNiazFD5iS/AzCag\nwSaQqEaTegMw2JNob0DWxE7Tus4Cnfz6jsnsIa9u3KU5baUH85mLMQgVJIPi\nhEU9vt6VUxjOvHQGyiVbgY+XV3AtOGzbQHt0rXT+wL1a3PhBo6MQpGXb/2Hm\n74oDxyesA3nMtTX5qKpU6fAGfE1DCaoQ1z2RBAyculmQitqqJtGyROnfFwBn\nG+8ZnB5aY28s4mK+ENRyvsySkAzr2mke5t01Uu+EjY7ZpvQ0bgWmGw3tPJzq\nptF6IdfV47VBP5LQb6Euf4aV69UYU71t5sAU2B8kzgjQ2SF1qY9ZLF5J6cL9\nRqE1EYfdzeSeyuRX31l+BI8MVH2hCq0Xb5jcrnisF+z6AmkmIj6mzAGXFT7k\nxpOj65n6VXuz+NVYLdoN16Q/3ZTZ7hivMJhEIlqJoH9KLxWmd67f5Fh2psE3\nxlUwiF9/XoylzNry8B/ntnaNDwKvgHuHrFtfNGDtJb/J3CoYxoBKRKn111Op\njGnf1ioGAQYWkpryvBa2Zl3Ti3HX9JnsB7o0laE7TNH5hlk/04RfvkOnRdsT\nIh7eu0iLNdaUET1S0EzZygrIrD08xAiAgErrlCFpnMtUEQMHCgzMDDk/82fG\nE/s+\r\n=zsq3\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"8dc04f72539949be88d855c3ef4a51c234aeb362","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.12","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.11","@balena/jellyfish-logger":"0.0.84","@balena/jellyfish-metrics":"0.0.91","@balena/jellyfish-environment":"^2.2.25"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.9.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.5.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.17","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.10-renovate-non-major-8dc04f72539949be88d855c3ef4a51c234aeb362_1600072550601_0.9270565914021465","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.10":{"name":"@balena/jellyfish-core","version":"0.1.10","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.10","maintainers":[{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"16b950fccaaa3d348eede2c17e841572714655c0","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.10.tgz","fileCount":93,"integrity":"sha512-5lbFVVbd+CGIVK/2Pfvt6G4l5ucYX6Vmw1kgaAAQu2ej0TUQIW6p0wSgkFVzhc7A0LYzvTqPiLb8821oQOf/ZQ==","signatures":[{"sig":"MEYCIQDqhfRHWtxxaI/2E93OOjvcLaPj3a0CgOP1eQu6uMg5fgIhAO5cpXCtJE4oiXo6Y9r479dXtHMgUjTtK1h1404teB16","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":558188,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfXyxxCRA9TVsSAnZWagAA7gEP/0BPpR2Jiql5UqyafdAy\nUM0ODlkvC6O4Y437LhZ3a+3UjeeaDSDwNQRMc04N55XS55PytwdzZqU1hQ2f\n9BUAsgbrfY6QunfXfHNARgXVQG8dBl9v6l9Uwo3YoKEWZEGU7HkyXqn3+rUP\niQS9Zp/txvUoTR4v0E5kJPz/Y/ygmn7hkf9AOebROcUJzCoW62KbUheWLDmF\nz9I7hpnchILbvzYrvt4s1ZYH93VPD64/bP0J9vGDuQ8po0EfN5Bu35xNJMUA\nY9l6pcateJk95W1c8JPkU5jUQQ2s6ERkpzGDbWcBBqCoutk0sg+FPcAPLmFR\nAxfziqb8AYGK8kAXUr3TSdQAqUxLU696ZK0moWGcZJNCuO+uPW10bYtYQupZ\nJsRH8iHk+Ue4LbsPgmLCChHbkf7uHtL/ywUYIrGz/23IHeI20lt6FlVWG1i6\nEM8yHTWIatR35BClBioLswBvFWgXqQ2yogMHHAcd0wEs5G48z3JiItPzje/e\np0fE5wL1vopBSdxvn6DwBo0CYVaJ2yCACypexBZW5+YnLCoK/+9e/61DQbDj\nRrPVt80+RX2OIrliQG398nVL3rJsgBCHnV6ZyJ9IF8etF3CfaPTeMYg79kUs\nlgiX3XjEpdUjs4FqdkuHduCJer6V/1dYr5EU8RAaVGLc4NCvzWOI623I9mjQ\nALq1\r\n=YAOC\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"b9d5ba279d76c3d6b058db0b7f9497294786d26c","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.12","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.11","@balena/jellyfish-logger":"0.0.84","@balena/jellyfish-metrics":"0.0.91","@balena/jellyfish-environment":"^2.2.25"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.9.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.5.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.17","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.10_1600072816780_0.604997833262999","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.11-renovate-non-major-cb28ff3685a1fb2451f0323019659e4046128851":{"name":"@balena/jellyfish-core","version":"0.1.11-renovate-non-major-cb28ff3685a1fb2451f0323019659e4046128851","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.11-renovate-non-major-cb28ff3685a1fb2451f0323019659e4046128851","maintainers":[{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b9aaf2074352894361a84c2996a74f094f13a077","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.11-renovate-non-major-cb28ff3685a1fb2451f0323019659e4046128851.tgz","fileCount":93,"integrity":"sha512-QeK5UECq6JGDRvvMOHRzHPnk/AUWTLQZcj+x6juWK4a8NWkURR6/YPoLa/s8ev6xnah4/aXjFw5z7BmaCmWkpw==","signatures":[{"sig":"MEUCIAMu9WwcmJG9gNlHGsxKY7LEDbbGUNC9zosn3FUmv5reAiEAxru8B+uEtlVm7q+YiT1L8EPM0J2chwekw5d5HHze5Ps=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":558352,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfXy2yCRA9TVsSAnZWagAAA30P/35xFo00yQ042gd3pHjU\n8w3ANFhOGFltfoF7fGVWOhQoAGcFw/qQX48QZSnJZcO30OEiLkGwh0FGhgfJ\nz/6FtsNmjBECFcH2Pv0PoJjyLx9zdqmd3MmeHxU4yvJFFVFcXynW3euEjn+K\nksndNPsOUAXFfC1OIFYQu4eOqRp/LUkhIkwhQoEpOZRBD8yvPV87hJBK2dd+\n1g2qbGZUIi3ZPsmAqKDbY7Pi0DEGvp7wSBbKiNvfvE5xohqjNNHnR2Nz86xx\nTWqhuNSCSeHwi6H8Iimjhtp2PtEbA582s/9HzVzokvv2rPNu7eRTMNwQNdU1\nPI+xBz+58cvxpfKrIuCW+SmXipJvgf6cOtD2S4gzZhsHJ2hwwuiDoDKY2o1r\nI/UOVqAesyTnM1y0VEMQ2GYJiwIr5vfXuGYiRjPo4CWrPnRFWrFBRENAzVln\njObAiCSJrwjPNaU3yfxpzyghcXFzcDC65Dh/ICkIlFqXiuLHpVaTRkHakVo5\nxoQVM+mgEncrBdD/+aizzL9nGiVgsoAJEfQyjNkkZd5cl3skaGoRkm1w+fzU\nr4gOaseojxvTXwf9DO2No1stPGq7Y5fxmFOknsatmQLoUCEu2kb1FgRYtrG6\nWFxCSjE+Mqtnjjis9jKnNVHMEGIbXEpXrUgqOQpasHSLlxv8mexIhojEd0AM\nK1Zz\r\n=3wUq\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"cb28ff3685a1fb2451f0323019659e4046128851","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.12","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.11","@balena/jellyfish-logger":"0.0.84","@balena/jellyfish-metrics":"0.0.92","@balena/jellyfish-environment":"^2.2.25"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.9.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.5.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.17","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.11-renovate-non-major-cb28ff3685a1fb2451f0323019659e4046128851_1600073138296_0.24918994567241626","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.11":{"name":"@balena/jellyfish-core","version":"0.1.11","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.11","maintainers":[{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"dc798ffdb677d984f2b285afed63b0a1dcaf8d7b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.11.tgz","fileCount":93,"integrity":"sha512-KmcR0LbGUQJ8D4GcMzWkiO1a4iBxFtUUU9UFyJ/k2oUy4kPe/ejqV26XUNAc//k7gqBYFMNvgCD8w5M1KVMQ/w==","signatures":[{"sig":"MEUCIH8nuXeMCIXZZglqvnItZOr92Gv+6E/NZ0Ql1WrdA1+GAiEAzHNwRSl9cbBBgxVeglcfY0DjKwyZx0L1W3wIEQQMQ8Q=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":558292,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfXy7uCRA9TVsSAnZWagAAgLcP/03+wXpJO0WzIibKPivb\n2A+hcQUeeHUj2fF12mf+OPWRVFOqTXGSIq/dC8Rsdhn222FLSUS3sevJWYiI\niCeAHjhMoHX4k2jzA158tn15QNCzDBUS8I8MAUhDp+PdquM74QPlWt8/VuKj\nptMuPLtUHBEiQrqFSTCo1xsivYuVsNd+oMVz5VhJ+ghHvXQNXbE0hBuI+1l+\nS5Cj4Z+alsNS1/OsB+/lZVPKxslf5VLSI0BGRc2UYTlsiRYl5GEOfvx4KXDr\n0EOiCnic5VTjCSiINBP9/SRfk1nDGoVqrjRU3fLlfkygJ59QclJwIhAZ8uyS\nZgKvtVHuFuUbg2oWU0Ywx1qtG5fuc0Lt1iJj8v7keZkco66h/PMC3jvuOUcz\ns5h0eGozhDPBuE3tKK8Y3+e1ZNP6XHZnvt78ofcPoG3Q9u4AXwhwQzXvMSn9\nVHS2qUvptR+zmhUtSh1Jnt2n9fRFvATNMmU62ODtGcJJ8N5VB0OKZplrOtjX\n4kxIvQRJVzb5ZRDpqtmpi+DzTf1tjsM+vfyXXcXNPrNXEH0MFjARwJKRmAAC\n+7unYOXgkv6wifrDG+TjhE2BYvDAXs80KQyuubB8MgL4DOHCu8eq30CWxFU3\nVEOA53ib/UWncq6QdFB4acUOc1gu241kj1KbuOVYlUzrMw1ANIV6luqq5zlJ\nAi1l\r\n=U/KP\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"8d09a4d97eb64bf959a4f9462225612e2fa44fab","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.12","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.11","@balena/jellyfish-logger":"0.0.84","@balena/jellyfish-metrics":"0.0.92","@balena/jellyfish-environment":"^2.2.25"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.9.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.5.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.17","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.11_1600073454522_0.7956676547220274","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.12-renovate-non-major-285c372d10a3667051538bda3850e5519e91c920":{"name":"@balena/jellyfish-core","version":"0.1.12-renovate-non-major-285c372d10a3667051538bda3850e5519e91c920","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.12-renovate-non-major-285c372d10a3667051538bda3850e5519e91c920","maintainers":[{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"40af64b573e9cfa5feb326c7522fd2c948bc78a5","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.12-renovate-non-major-285c372d10a3667051538bda3850e5519e91c920.tgz","fileCount":93,"integrity":"sha512-2JtSShH6tW2Yzb33+ztFEb9ehx4/D8FAzaHOtPUjXO0G5FP5P/3YFvTt/nWZfWYL39nvUdvEKHt3UfhncpV7zw==","signatures":[{"sig":"MEYCIQDQNBZO+7p8Hu0oFdeJwQakkaz0KFm8oBUA5p0G9dUcNQIhAMzcTh3FT54yVVWgfrY8+9GLtQ6HbQ2raVqQ8cneAyOm","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":558418,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfYDxfCRA9TVsSAnZWagAATAgQAJioe0qE1pmWYnaPkWm2\nLatlfO3wlvq2ZhNWN8F78G+ke+oBnTgfokRLRPnOdjzia7wPSpRrHrLnb0gH\nnXEFM7gg6U8n5kUQaYSZ/E828ulkuavIKap1wqf2zFMjR0OVJoEbzVo9FDjN\nQRUvuafdEcfTAqe6UoDCS7ysL9J6f9Nn4e7DnCLExUf0aJgPkX574rFgaeDV\n79m7/qpVUUCUIgy2otuvRgmZlRgqBe2m+GSPtkqmteQNdp67zeRhIWoFVN6u\nCK7DQhUgR3no+IU5gujbav2wmJeavwesIPv80xOMsu5TjZ5dl7KEI7k5SCOz\nksUikftVdPVBN6uiFbatXeRdhmC1zkqkgb2xYH0/9rIyCp/yr07BrsKRuE68\nCTKjd+/hFiDyWCRh4cdjlDfTpYTlKemVUEzdNqUrlHxb28sdiwTPKuKsylu1\ngd8cM09W68lsR0q7WaxS2mx/M7XTIXjkfs2N5esvAlujfBsAGVeuj4W3MdBS\nzJa9bIvxU+y/JVmjzUe9saLEBCEHiW975KJqCOAli9Fwb3RURNvpBWqaFCQJ\nFgVEnoACXOasbfyyQyt0nrVX2RNpgQiqKgxTZndcIgUtUvRzoh23wgBr1UlA\nyN/3oyVaxtFl22TtbHEeuM6VdZLBXc+2849qMRV1hutCTDEt/JupxuHI6/3v\nVJme\r\n=eP23\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"285c372d10a3667051538bda3850e5519e91c920","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.12","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.11","@balena/jellyfish-logger":"0.0.84","@balena/jellyfish-metrics":"0.0.93","@balena/jellyfish-environment":"^2.2.26"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.9.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.5.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.17","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.12-renovate-non-major-285c372d10a3667051538bda3850e5519e91c920_1600142431135_0.3998513047845009","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.12-renovate-non-major-92eff74d09b380e1f4cc97ba8820caf039d72ed8":{"name":"@balena/jellyfish-core","version":"0.1.12-renovate-non-major-92eff74d09b380e1f4cc97ba8820caf039d72ed8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.12-renovate-non-major-92eff74d09b380e1f4cc97ba8820caf039d72ed8","maintainers":[{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a14c0f3fc084ac8222bf63e23b0ca5e966791d58","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.12-renovate-non-major-92eff74d09b380e1f4cc97ba8820caf039d72ed8.tgz","fileCount":93,"integrity":"sha512-lfFBdU79tjgRqDyn/fg23RlQveDIKLGQ7/OMl6zryrP9keJ1ulVyacJtVv8m97AadhyQaLWTRqgbjfASD/ofSQ==","signatures":[{"sig":"MEUCIQD/Kr15LaSnZrIfQ1xVbCXhqo3PG9HA6PYaltxy+uG2LQIgZkPz9vM87Slbx2v/GWqsOuoMDS2CV4eyrZZSB7pCT+c=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":558418,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfYD36CRA9TVsSAnZWagAAE0IP/1mFSj8S34UqVWJrgxni\nsFSzRpCERTNIRR5uimLq0LhU9BTKfUnNZ50l4iw34aXbSHz7MNUMmGi1tVAW\nuH5tGg1cNsDJEddloJZlI8N6/orEs71V99J+G3k4MzKI2wUtZ3HLb1SKaSUh\nk1Zxr9bxkvz4v0dVnfItj/2kTJCSDcV2Anu976lNSaV5HtzAnvQxdiN1mG8N\n5hWVuHxMIHCqVKgWCdVkQvWRVd2Zrjo6pnQzDO4RlTI2KxX1FwRuNRyoQJli\n+P/5+h/fi/jZMsDLXWVGxe499rsb0B92GwA826DhY0Q5wiOiH1MWisC2cxfm\n/aExUAuY9WAiOfCqLmrHpR9j6UT9kMN2diYuzIC0pkmInGpjvfFAJtAeTqdq\n8smxs99+pL0ZwoizR52JihbioxW9d4Y82pBqfc2wXcsz5IBGfi6a647RSsnr\naWE93EgFA5cYNQa0Qb/ffIiVPutuAqRF8i+rRfFHyYVw2fPwV5glYp6cC6IN\n4x2fg57uieIk4utqjJeY12nA6egLPUJJfl9yLrcwouDTo74Q6BpGFhqWTOJ8\nilQ3V0FnYGBfP4E7C2zUKFPrCXYgxJ1MDuqOQagEKPomm3hJ93Y2TST9KqF/\np7RA59OIv6qctN8iAxkgroQCajzf8tnJz6t4Tz1rIoNvdXePZs+Gf+hj31lo\nfLfV\r\n=g2cE\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"92eff74d09b380e1f4cc97ba8820caf039d72ed8","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.12","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.11","@balena/jellyfish-logger":"0.0.85","@balena/jellyfish-metrics":"0.0.93","@balena/jellyfish-environment":"^2.2.26"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.9.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.5.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.17","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.12-renovate-non-major-92eff74d09b380e1f4cc97ba8820caf039d72ed8_1600142842231_0.6360564520501304","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.12":{"name":"@balena/jellyfish-core","version":"0.1.12","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.12","maintainers":[{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9d39c4216f309e3e825c107115a870ac7b26639a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.12.tgz","fileCount":93,"integrity":"sha512-WQp0sKFkKqQ8cJEBpBAAI6gLk+M/digPpCK+4Hj4EE8GF8fftNxnKPnJQcGSDeVgIDzI47AuQvJWW3/yQAmzfA==","signatures":[{"sig":"MEUCIQCWHzhjpg+Fb8/zUW1Pve2g/JubUjMQAGwjFSY8N9JRowIgIqq8Oc4kIGdfv3SoiiRi2ZZCfDDSciy7tETLBPk83EQ=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":558358,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfYD6yCRA9TVsSAnZWagAANLoP/1+YislpVS0OyNEVRnzv\n1PGtqKcNcC+kTCBLYnIHunsO7aUwSWNO72+Y2rJBUGiyueTmc60Qvt0RKAZZ\nYVnj4o/zsfO9mDb8EJiGGI41KbQGOApbyo35gtgnzJa1UjOyp35Jg21jOykF\nHiJcbeRBJup0M+w43LSAMqk56zAhW1QtYeeLxafI0CyjFL6LC+ETKwWekvD6\n0HlcQtAgJvwzbfEWZiArrK3CgejiVnw0adXvO5+kLYNLTZpwA+fQKcb2ZGXL\nNAJ9yO/k0NImIm3NtMujLjjxKqikXNwisAJJzjYbibKOtPY7WrOTiML9o1W/\nerTnnsojae//zk/3wPRpjoBaurAjNVHHtX4BWI/XMuWz9SKpIjd5OTOPvK2M\n5zhunAHK+drtnLgIjMpZQ95LzNYScudZoPA3tFkGCu0Dh9D3molHlcLajD2s\nfAqKRPKNOf5LS0o76RNMDpdetkkz4aCcnXyG2RRqDNikmDb4Ljwag1mBGJez\n5vJlwrFotMRqMjbeIKed5w2OwgDLzElUoUbQWDAhQEFjWskc2AnZLzbCrMzE\nLEloR48SA5y3wKorccPCl73M0YzevZsAWmxdeMZuCDL6tMAYhhKZaN0Ve/K4\n9VSKRPdBDgNM6oG+1NJDJe28YiWJHx0eMMXbp6YN9x1FWFN5HGl/9qFDvaPA\nBD94\r\n=iQ/o\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"d3355c1ad5e69fb7915ade216483f8a294acc7c9","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.12","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.11","@balena/jellyfish-logger":"0.0.85","@balena/jellyfish-metrics":"0.0.93","@balena/jellyfish-environment":"^2.2.26"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.9.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.5.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.17","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.12_1600143026267_0.002995166634405466","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.13-renovate-non-major-2ec000929541e694e381fec4ffaa00ceb85501cc":{"name":"@balena/jellyfish-core","version":"0.1.13-renovate-non-major-2ec000929541e694e381fec4ffaa00ceb85501cc","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.13-renovate-non-major-2ec000929541e694e381fec4ffaa00ceb85501cc","maintainers":[{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"cf9f630a731947ecce04e4d1da80c2c1813c2939","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.13-renovate-non-major-2ec000929541e694e381fec4ffaa00ceb85501cc.tgz","fileCount":93,"integrity":"sha512-SLrbLuu7seKuii0HZfXT8AwBUOrFo8zfb2AjA3SY08nIBD/riVSRj1J6QvFbeMerekKqGe4eQL+bIp6AiD6Olw==","signatures":[{"sig":"MEQCIFo+hfM20Qv6zJyQWM3ezgG1bZ9GlPMOxGnxoExOL5g7AiAM+mojctzfd2J1Gh4987DyWuIfrJ6urmnsFMvGk2HXzQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":558522,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfYE6ZCRA9TVsSAnZWagAAe8EP/RjxV6FmuzJiqBu2O0C6\nSdlrBc1tIQBPx7v6U1ICx5tO6RR1Wydd85bXN45CYv9lSdk5ltYjRmYgU4ue\nPPJpGM/p+CLjs3tLWuZpA/IUuZ28pLv5RkBJEmntkx3k7d4UX9nG7mwvMlCK\nYk3r7zyyT2tLwQfMxcE0HGiPxnwHwAYLgP7IjgFBjHg1EInjnrmWuHzzbVD+\n08yumlzm4CfnXV5TfGanAaoIXLD6ruagpACOFWLVUxisyWuWiRhvkdCaPw0B\nBlZix0+vIy9Ap6F7ZEvmakXbeWEaI+OYrJbsj41hHN3AAepTT70Or5Tiz7jM\nVUtRynYse1ELKEL/bF7V0ADPeOM6Vr8W5Rxj/Uc2TWQCIVZHVTALgbvtTv9b\nP23TCtP3/2psSjcjsjVU29fwdrNHbFx6M1Glb1e+ddbNkTNsbY/RuskMe5YM\nC5fnynl0u4s5tOVe5BWUETs4k/yoe6rzH7mjqZeGE2JKH+TyBB4Q+IBAl1FG\n3itCxBxCu9GmhMD+0a/toHdV02/Z/ssoI5YF/OEcDSZ1T917CzrV489gFptM\ndl096cPX49mtPlpX8h1fRHk0lPnsFqc+LnCfyM5GfPaBxe3d2glvxTpP0chR\n1CecbZAexchb8TR6Ooma1jqWph4eJcV5q7bByrcI4Fwqe01oHm2my4IxWrpG\nrvEJ\r\n=LcQe\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"2ec000929541e694e381fec4ffaa00ceb85501cc","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.12","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.11","@balena/jellyfish-logger":"0.0.85","@balena/jellyfish-metrics":"0.0.94","@balena/jellyfish-environment":"^2.2.26"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.9.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.5.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.17","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.13-renovate-non-major-2ec000929541e694e381fec4ffaa00ceb85501cc_1600147096453_0.6927297559806815","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.13":{"name":"@balena/jellyfish-core","version":"0.1.13","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.13","maintainers":[{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"93d7282a2b5a90cf655c8fbf1af4c41676e35347","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.13.tgz","fileCount":93,"integrity":"sha512-rvlHGTe6qCRbYS/GTdm0TYWuJ8dHVX4gly13V63GVth48OUXJBwR3uqhgRHDKyjm8F5T//yIbYg5B/xQ4pNtwg==","signatures":[{"sig":"MEUCIBAEFI4Mt1I0xjfYq1KrfOaihr9Qz3mvRe3eAU00riekAiEA/zYHFQ7V3eH2tm1EOrTFf4ipJ1NjFA7mJPq7TBzeTr4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":558462,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfYFADCRA9TVsSAnZWagAA7NEP/Rf9B8AwZr+vOwSsvNW7\n3Cnu00PSTyHO1SAzJcW+w+jBMsRwAnsTrw6Fgh/iaaEyFFHMNJ3fsa2gzWjA\nStx5i3zvuW0wZrAK8/0iQoDflqc+QDS+J0V+NafFnf6HKWMhX1yoD8DpJg8k\naJNktY/PghigbGnG6IVl53q4NpWYOEykrDadVdlY30T2y9bpRkJIaWltuTY5\nN7f8Dv2AeP5jRzrni/BHE4sayYdDwzAwGrqqYL5qKbFc453PpUUjPW3PPHgk\nXJN/aI1IPYr2qysLy0DFSr4DhYcOVSH5bz7dzOFDnA+Ul9NplPW/Eh15eAbG\n1+06f+kr4QHMRjMLVs0UyOJGbuyTbx4MTVhvtddyZVoioPihiFMLcTAw3KP9\nQeZj/ADtZNMnpqNa7fambvtmPK5cS8SNdTL5n4pWaTh6d6YT6R5GVb5UAiwd\n8UXlMEk+FF7MqaDX6hx4GbCx+2kvOYBjWHB04jE+MIU0EUUg614t6jdTTvSQ\n00kQtFyIsvJ9HTQFnsV8JndgFhehF5LiFyx7sVY3CBgzSKQiAkAZaiDkTsjL\n9M3CEzwI1qwn7aR0LoeosB1AOuiJnyw7IUBG5pMyT1W00wyBmsUXdDl/spGR\nmAy/5g9CfdxyNllhgCg1VENRc9WrC6kZjsrQp49f2Fug6v2IVJHrL6tLAycp\nB/we\r\n=veXe\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"50d79587cf8c4640ca0420bee0397810e7bf3fb1","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.12","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.11","@balena/jellyfish-logger":"0.0.85","@balena/jellyfish-metrics":"0.0.94","@balena/jellyfish-environment":"^2.2.26"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.9.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.5.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.17","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.13_1600147458610_0.14347745522312727","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.14-merge-linked-views-in-sql-1f26a0a56f006c220991579e2c92f9fe61a3a72f":{"name":"@balena/jellyfish-core","version":"0.1.14-merge-linked-views-in-sql-1f26a0a56f006c220991579e2c92f9fe61a3a72f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.14-merge-linked-views-in-sql-1f26a0a56f006c220991579e2c92f9fe61a3a72f","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"33a1a324019d49048a1c35661338687fc7979997","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.14-merge-linked-views-in-sql-1f26a0a56f006c220991579e2c92f9fe61a3a72f.tgz","fileCount":94,"integrity":"sha512-pSBUgqaPYoe1yIjDKmry8+VKaSp8hsOOcDOECZrOn4OhQD586p7D9xq2hW1rhkGGVTiRPVA+DLb8fNYl3pybRA==","signatures":[{"sig":"MEUCIFgs/Og7vN/u1RAowW0Il4u5063SIaZpabZ8M6+GSDteAiEA5M32SAizEZq9fvrqCK3bnqbfdpNAQeFDflZUEAIT6/M=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":558901,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfYRhICRA9TVsSAnZWagAApXUQAJQPhBx5R3F7EFSyz5wa\n2rXOg7uzVB1dWXeb0ApoawGXv7I5cbCBPhMdFNeU1uipaF9M0VcH4PJEWGxI\ngPILLMdqoljFTqWMp9+9jha1NnHrAasBPsVDMnZhd9ec45Am23HGmozG5bPU\ngjDc7Py6Ojvqd/wjEjKtNLcWgytisgyYKToX219HUEGvbgztX6/oIpxbv0dU\nrumIZn9AgWltn7ISD5yoKExSbMy8RtOTUQ2lv+h5U0k6OCsoSRnzqTDfKsII\nRvhAL8qQb4U1RdmrP9lBWyaDLYaVmsDI1sBORYgxEZDrdOFEKSjQqXvcnoxH\nREcKGQCIo0Fxz9FiHCJOGwJKjrEamvZ18f8ciq5AbtkvQfLl1qh9SpYx6cp5\nG+R4m0hkUUc7k0SwwPLqz2zsMhlhjGxHDMfqbpxvGWaUHsr9R0KSVhVs7iQt\n9q38ieVkxkDx8szRi1ozIK4dG799JdqxRLBtli+xWXUQ8HMBv6duiK7SnNY8\n4pMZyQNTUtRxWv9+ermxcUQMF82XdIBurlndqh7Lly1MWV0bP5fFSSVcLm6W\nmYYiaQH8WVAp+BUL6Qsjv36Qyybxxla2HEOfhz0N6gjPEt3P94yQ85BDnj5n\nEh5gGWHdUcZvSC0ebMsG8v0T4Fc165xsGw9YlimSwa3XwquraarReOyiqLdV\nLRe9\r\n=9VR8\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"1f26a0a56f006c220991579e2c92f9fe61a3a72f","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.12","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.11","@balena/jellyfish-logger":"0.0.85","@balena/jellyfish-metrics":"0.0.94","@balena/jellyfish-environment":"^2.2.26"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.9.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.5.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.17","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.14-merge-linked-views-in-sql-1f26a0a56f006c220991579e2c92f9fe61a3a72f_1600198727859_0.31398701313889266","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.14":{"name":"@balena/jellyfish-core","version":"0.1.14","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.14","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"7bdb1051765e44505615c01ee8471a57833b51fe","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.14.tgz","fileCount":94,"integrity":"sha512-adxRtTUiRJsnMqQhfdL4xKwz4s99DGY7spODsXSBoxfku6QsjxlCGkfWgJpTt30atVtNiQ0H6ExqyKZU/15JZw==","signatures":[{"sig":"MEUCICV4pxo+idrCbfdqXu2auB8CNEO+JRXYd6t7Sr8XNmxqAiEAxQItIL+vH503vxLXGVyFqyPrQWauJFPzth76uyQPXE4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":558834,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfYRmvCRA9TVsSAnZWagAAxpsP/2lsoqG+UCZw+AirPA01\nrql+1+QKSHH+i32PwMVcCs7QbT6Bvr0CvxwEZRGR9VOTpCbV7GfM3nQuVNME\nIKGyALHvn7kF9eoMSbzFE4ywelEnvDTdxQFINp+sIHqIRATQVi2Gy7LiAA6L\nST1lyLsRlaNZVpfexTDTanFTxS6Qyg4hcmVgdYtR5wJs7GErIirTP+L4LbKO\nSPHYQJRKz02YRL+bjbEXLYRLBBl8p+2MJLPILIFDoL1PTt+ntXusoSstBc/o\nZJskqp7sUP51hB0rFn9yl+zoIunlKhcXWG7oPlzsxPI37IQn1XXiF7Jct7NY\nGH7dy8gIPyHnQQ0kVnarWN7R9+BrT7WPRVDWbxJVYNQcldGQk2vwbepoMUdB\nLEuOEJMJlpopClKmPxrfF6EoNh1RNM3oPokmX1kP2tqGdrMSjG3IW+wim8YR\n8GkgAXJK7A4aiGH2txGbXP/nVz2fmFongWgALSFRWvFue5cpjI/IncEIUazD\nJRyYBsrWWs4DC6K84sQAoNS1IfiFShzfrYkNS3Q3Gvvig1mYHkTKzxRYdqMH\nBuHMPqqb9Jq4uknf4oFw5eOlhsghOBZI+Kps8lxm00KNVCrVl162to5zm3DP\nhHn9NL7YNQaqWGFqfZEA5omQlFZHFY1NYgdomArE9v+PkLksWqnGPLNKeDPy\nkf95\r\n=sOzi\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"04c68d3fd5888eb61b3bbcbd13bcb72bf6e8eb97","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.12","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.11","@balena/jellyfish-logger":"0.0.85","@balena/jellyfish-metrics":"0.0.94","@balena/jellyfish-environment":"^2.2.26"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.9.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.5.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.17","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.14_1600199087051_0.20791515455953302","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.15-renovate-non-major-68af3185d1a90c3dc1f1fa57a50fd8f7fdbb4c7e":{"name":"@balena/jellyfish-core","version":"0.1.15-renovate-non-major-68af3185d1a90c3dc1f1fa57a50fd8f7fdbb4c7e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.15-renovate-non-major-68af3185d1a90c3dc1f1fa57a50fd8f7fdbb4c7e","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5cf038b73ae1b450fb0b8ac6ab51d83ff7a5f804","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.15-renovate-non-major-68af3185d1a90c3dc1f1fa57a50fd8f7fdbb4c7e.tgz","fileCount":94,"integrity":"sha512-nOmCxXY2YdWh1NA+ZTnXstQygJXpyi2o6nZVmHV/MCmBsYnLko7XVfnDlhHUjqXmsh+RTcNgTOQ1XPMwk9Ma9g==","signatures":[{"sig":"MEUCIC80VnXiA+vCbZe+ElBMxqEIunBcqkt33vrCtlBTXl2JAiEA9LfeeNvefDzeGrC9iPYzbPUCaR3w40UEsQ+1UvYcuAg=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":558995,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfYWBuCRA9TVsSAnZWagAAU/gP/3aPF8N+TiopD9pL5cRC\nOwJnt+wXkiPlKTPF8G782HfnxVvfr0e9rMosUKa3OK99lqEKOrqd4UluvK4v\n5ylYO3DkCteFHAc4i6YKqwKvvNydx6OhYssZ+wAZpO5gkWa4xnqfqrSVwYiQ\nl8LJ8r29ZbzGpx8NeV82e19bkBJp2u5rbj4XNHlwEVsCRHp0+hdnTpeAzZdJ\ne7gW26QKvKS2f9Uqe9usTXGIL6NbBSGgUYS5PiKGd+8/b3NaP6YFvmTF0gMl\nWJTwHAIl9NQFJe1TeJRJigezFnHKl96vUFlr6iVKRBP0Q7QMYTJuOJmpUQS8\nH/UAE2bi3I04rQ4XYqXJwO4POYuO5NxejTieehjL9q7o/oB4QfoVebyCTF5s\nSdRGxIIBgxrZfVyI/1lOU2o26QY1YpLukEGe3z78Vl2lOBQu28+NWfKIdS0A\nm1LKCoQzv36UqvSscVxe1gnDO80/XjH6yDiCdnWI3PoW7EMxaT3PhZiTmjnp\nIASFOU6KVlThGbIO+AyiAzjBNSvNhEZ96fQg4F9vaCS0ejZJlj1kcvMLYvdk\nLXfEAXyTIASwNs9yVmJ/Yq2jLRGGog+3nOTh/IiXWMjpsRcUuBrODxWbMzFB\n1kv08QbZk3HwZfE3Qw08QxDSLR3jE/GdSHRtw6AVNRdjYo6dk8EtA+6c4sC/\nD0+7\r\n=u3/d\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"68af3185d1a90c3dc1f1fa57a50fd8f7fdbb4c7e","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.12","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.11","@balena/jellyfish-logger":"0.0.85","@balena/jellyfish-metrics":"0.0.94","@balena/jellyfish-environment":"^2.2.26"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.9.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.5.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.18","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.15-renovate-non-major-68af3185d1a90c3dc1f1fa57a50fd8f7fdbb4c7e_1600217197701_0.580251853340058","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.15":{"name":"@balena/jellyfish-core","version":"0.1.15","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.15","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"627074e1e24f96e350399a6ca2f0a9630bb9f0e3","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.15.tgz","fileCount":94,"integrity":"sha512-t4VgImaGzIfbfvcYReGbBp/ggH1qF4Dvc+dQW20uVi3/acxtDCh/LZjA+4fE1CY32xCoAKPR5uJwdlPGfwN2wQ==","signatures":[{"sig":"MEQCIHPA17zZIl40i2nrjIqlJXI+UGTM7XZGPWjD46sFgr7LAiB+Tm/qcecE1VPKiiSc/T6AYbHkq/cYWFgQifqXapsJ5w==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":558935,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfYWHpCRA9TVsSAnZWagAARD0P/RJZIGeQ1VTVS2EBgnN4\nMXqOh8Z5xxoTALWiYRnqK9Mt6cMGTtj48ghSo+HgpstdCsVcwuEq2Iu7dwS5\n1AWpeBob99rzwZtM0XVhohi3OKK3FvVNZWrSc8lC7PJJsUS8VHrF8tihIZYV\nia9vXS5c+huHr/RtnAJD6AQrWWi5FG52MIUR33dZOpE5lQZIbaQCsQoAqufP\nlFVvJOzVcPGPybJ3jC6KK9afQFfBlTkMWE8LH8kaFBin0A0Z2bL3CDB/ZqO6\nmMo63SSotjeT9Fac84puNTkCFPd6eyL8jpgTJiDnzki2dK0zWJAPbLu7V5Ec\nsiq+h61+PrmZUYMHs8udcVNJLtlO2EeEUhy2ccRNFR664tj8PmrHu8pA06lx\nkuAQCsLn5qaqyBamXgFTW3n9xJtIqpmOoQec+JG0+H/2azhuj3Sd/pCA48w6\nnZ95/TMOYnz/IQrzdoaYZYsZhNbQCkXhd/NnyfI6HYa6lrSjO6PoM/YRofML\nebcL0lIdBA0NDxcBu0r1kCr1u1juq9Vb+sFCxOKYPvPmwldaM4EljYSJwfgH\nKR6Wa7+bUnHRVsa+hPMeu1TA3vZfDB6Lyfy3sr/HO+I10734X8JeJbkq153P\n3oS6Ph2t9pQSDwfPLTRghqLsqxFQh3aUIOuPiRx4oNC+iqlJWGh2AcM3BQL8\naOoU\r\n=4QPC\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"61d71a71cb0abf70cad91642b1f89a995f0ee329","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.51.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.12","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.11","@balena/jellyfish-logger":"0.0.85","@balena/jellyfish-metrics":"0.0.94","@balena/jellyfish-environment":"^2.2.26"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.9.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.5.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.18","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.15_1600217576787_0.676667180998509","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.16-renovate-non-major-e67430db9919b6ab80c17fe772b3d9ac0ff2c63d":{"name":"@balena/jellyfish-core","version":"0.1.16-renovate-non-major-e67430db9919b6ab80c17fe772b3d9ac0ff2c63d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.16-renovate-non-major-e67430db9919b6ab80c17fe772b3d9ac0ff2c63d","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1305b9906b5352e20cc5b2b605d4efc2a93484f8","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.16-renovate-non-major-e67430db9919b6ab80c17fe772b3d9ac0ff2c63d.tgz","fileCount":94,"integrity":"sha512-zXfZJhXbhlc59YrJoByOtDpah96vDKDLnElSF++cRlvQfjBhDTRupBplHhZbx7nSiA7OfCG+DRGC0RvYuj5ewg==","signatures":[{"sig":"MEQCICo6xQIozIlfucVTJ1LkUgRhvLkfiVAziKZbuEruzu5mAiAK63r/3JKWEaG+jodTNkord5AkNgSXlm1NOVAohguIUA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":559084,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfYgkgCRA9TVsSAnZWagAApQ4P/in6MNKKhg4txqSzMCc2\nsc/8AwLFLbPJ2Xij1TL20jefvhEavwqPipeyqlBaAVPV53AFF76s9E/fumY8\n44YQwdR9/78tRH/uW+wCIshiK5wPkefwjMJQmib6VxfHK74NVgkgnu9taYit\nRX72Uuji3TmmdsNLd/7hT3SgQYgILzzeDa4vz/bz6X5pqeUjf1eSactRtJTX\ntWYvIGXh+/omQb1L7igzKqBpC8Sz7XJ6qrwIT6kHie8EIVH16pQ8vZ3dGcx/\nMiS8Fa1IEzZA8A1Xeh5H/AAnbN5hbDm9ErTmYp2WGcul1bdS8fFeu4yjfV2q\nwpCmI4DOV0AK4ThnRqlZo3NlPIs/7m1IVmQS/QkmkPaT+SqLM99Pp/jF5/61\ny9HMpDcMW/fZrUUoBlvWx+YkUoVLMDrDIPcldlqWg+LGGhsjS/uyQ4R4x3cU\nA0JqbmGlWGld6Ev7HwSxl+i1W2tOTRNXq+ALLxNl5kbgtRC2pGG5sdZ/XmxT\nioGDxzwOmPKcDSBsHZnzysINVlRaaUG94kPSZZjv3mgpDUupcraCXAZDAZgS\nL9xH9k3qZmKW8F/5YgFtRLjvhyjEtMYZWBiYnfMNtj+QpLE8WTUN/eOJzCbS\n91pnmrbx95BJW5mg3LF4qNuUF7GIEu2ngD5FagiErDJCrlC13w6LFxBamm3y\naVyA\r\n=tZ3Z\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"e67430db9919b6ab80c17fe772b3d9ac0ff2c63d","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.12","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.11","@balena/jellyfish-logger":"0.0.85","@balena/jellyfish-metrics":"0.0.94","@balena/jellyfish-environment":"^2.2.26"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.9.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.5.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.18","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.16-renovate-non-major-e67430db9919b6ab80c17fe772b3d9ac0ff2c63d_1600260383952_0.45313673409391564","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.16":{"name":"@balena/jellyfish-core","version":"0.1.16","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.16","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b648cbaa3b6015a57dfb204413d552fbf574c533","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.16.tgz","fileCount":94,"integrity":"sha512-ARu9LA6i3VbKHhriOPAmgOLQneZl6jCAWKFWEgDu93ZdkC+7ZMGTIJVLkafFhr1UYEhXFNkw7T7uVo/EZpu0Ww==","signatures":[{"sig":"MEYCIQCRRcpOAyXmPFTWVqyXLI/BAoGVcu7p47h14cABfaDiWwIhAJmzGcGzsEIFsumiukL5uH5gleQZnJgkchAO+PWvovLK","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":559024,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfYgpuCRA9TVsSAnZWagAATR4P/2vfY3jLBVJZ98AG4GjQ\nhDgEDgkmdGKB0tEAIS/2SPuc7b5kghi3EoyhdRZOuWJg7sB0DGB0+nn+DhgZ\n1QOVV6ROCgxi50Z7U3FQ2Qa8vT7l6kquk3FMWR2x+smO7TKqaOmMq9fCR+Ko\nbO4vOEIgMG2eZh5Ncp8q6LLcFnIhoAjZlqyKkWx49TAsjoHeCRRtQCwgvXp2\nuNGPdDJclPiysJIN1aNej0ymgTQKdzC086KvJEZHU2JMGQXqKXMPLsodVJ/m\nhtQagsnoRU4g/SB4hDsmcMBXeohoSO8y5MWI0Xe5+v3ohLzE4GIvB+H6kscY\n8lShUI5tGxUXipo88Ot9kmbSHAgoXaF0E4gmnAiByjaGIpae9n77EIJ96ZRh\nNI+/UdsQYGNrSP9Kuio4XdpKSTsCIF8+ZcLWD2+SeYEqqbgG+fQeFzX/36gv\nBUA8Q+53cWwgJmQ+ckTU0zLCBzp2wOxrW+P3ctMNmL+jyPBp4XhS2FKudYvl\ni/VW7NMj0XAgGLJ87xpb0AF8VauZxFdkkD9Uf45SiaJeXjszOKK7cAuvuj10\noCcJDyic0fE0wpDuUfiONceqcgC9WdIY1qo5RCbTJHCVhvAtjy62m7vzGhX5\n/UJ+vWFe2c3zoW/cFh1Ms6O7vSE6YJCw0AKBJDuCxq5JR0bSAQzD96SNaehP\n3QrD\r\n=lodb\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"f3bf14a6a5efbc9183575734f1ed52fcf7cb0f74","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.12","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.11","@balena/jellyfish-logger":"0.0.85","@balena/jellyfish-metrics":"0.0.94","@balena/jellyfish-environment":"^2.2.26"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.9.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.5.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.18","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.16_1600260717636_0.37631019869317783","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.17-renovate-non-major-818e59d98284fd3fff140c3dcb9ba9c223e17a8a":{"name":"@balena/jellyfish-core","version":"0.1.17-renovate-non-major-818e59d98284fd3fff140c3dcb9ba9c223e17a8a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.17-renovate-non-major-818e59d98284fd3fff140c3dcb9ba9c223e17a8a","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"61f932df0050bfed6076b1841e948cc80a512104","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.17-renovate-non-major-818e59d98284fd3fff140c3dcb9ba9c223e17a8a.tgz","fileCount":94,"integrity":"sha512-UNvdc6zFoH4VWXXeaBcxh2LBiVNnzRx0VLxJqpXZTPduRSxnW5elhrzM+4tfFi4NlvYz9MFFt05nLNzoUyXk4A==","signatures":[{"sig":"MEUCIB0g1l3gP7nzySkiHafJMVjyhU46aGbyNRz/5uWNJ6OGAiEApIueQpwqngHZQo00Qjw/46CHXATzmCRMk2BmCwEPMVw=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":559174,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfYhNWCRA9TVsSAnZWagAAizsQAIJ5RpDyXu4kPTZhv1Rd\nCUH+0nhhro9iUehxkha6rS0iXkZ/wwl9YpG2HNUcOQy4HZSOXsdWAbz5e78L\n/Ft6oMWGdBdQ/FYrT8COxixy+e2Z8YgMAvniANLU6jVh/zKQcXZDtjwHsUPG\n31ukNdKuXBzqNzyw6Z+yrlsiUaeLkFxrLJI4RCN/8vnAAaXLpuaT+tt+Fk2Y\nWTHN71OFYd3D19DIt+qZvl7EZFKB9JhC+9UCo9Ccw1dnjYQHMpz+aoLsqdFO\nJ670xA1z6ErEfV/hlHCtt5bR89yH6pWUBGXAMnjfQbhjhXSVHrctiim6xG/3\nYLLoVvsypK07N9mM9kvO8mTrbPnRXFc5EsqoQPUFA08bVEt44mOHSs5fld4r\nHSMnLeSu564T0bx00ghFeYwAJfCQw+azuhkOgX595xso7Qp8i/V0QpTd1j6d\n2ScwnsucTgSzBkWIOOQqi8kOYfDuE0lCHZi0hrehhm8aIcGUbky6TuSQ9ki6\n3jgnO1zX9/2E5UeTqDyMlepN9z2Ilk6dZmEnqIXEOHXuSjys5NgNB6SEMcSj\ne4F7eTAZPz1hepIKSxl6CbwrYBGrWdkrmB0oJZgfPBgU26I/iuJ6/4UrzXXY\ntS8WFyG0KrPbr6zl9GQOlUlwthNrwb9zjF4pJd93GpamQQAGx4E3Sz3l+klq\nWzVi\r\n=ECtL\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"818e59d98284fd3fff140c3dcb9ba9c223e17a8a","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.12","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.11","@balena/jellyfish-logger":"0.0.85","@balena/jellyfish-metrics":"0.0.94","@balena/jellyfish-environment":"^2.2.26"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.9.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.5.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.18","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.17-renovate-non-major-818e59d98284fd3fff140c3dcb9ba9c223e17a8a_1600262998308_0.8681456157647716","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.17":{"name":"@balena/jellyfish-core","version":"0.1.17","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.17","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"bbb5832889d79409240714deac6f394654481e65","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.17.tgz","fileCount":94,"integrity":"sha512-UsDC/i/KSP5PzPulIyCRlC66R1n5stLTudqwbXaX5VBsF1PLxwj1ooGFxls9EzZ98kJX4XOqUx49IhJPwfs3AQ==","signatures":[{"sig":"MEUCIQC8Zf5cLsrt8TIEQsh74q7cf2GNobofDFnPLGc0Kp3aVwIgFaZTwUvCb/c2MRqRY0Q0g2DII7CkWirLjsXl3GgiBHg=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":559114,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfYhToCRA9TVsSAnZWagAAHUYP/j2FA/EBIbMgmgQfPugW\nfhndvWPmrCaZjyGRYz/sHFkBaxtvbpWBUnNEUpKPrI4bf7Datx76rJL9K+Y8\npyKtrvHKqCrpsY9h4x4Ftx48BqRXrsh3QyKcVgerZ2A5AjbdZl1iT+LlD8Tn\nBpRbXr2pZsZLxzPmY2WQZung+4XagZ0G0IDpzUcjBNVSAfHHkijrPR2suBxb\n/sZ943fxaLWNDAIeH7F9xcv//c1bWOvMqHOGkJbrRYBc+wy0ZvWTcdneZkA4\nwhDktRIAcnbzgiT59gL81mK3mTX6HD+SNCUt9PYCz4g9wTECVvJqLOQQEMWn\n+lLNR4p/NIc2t7RLO/OayYI4e5pdxc1NOKf2cb/BDsGsGJnSlFumd+h/NhLz\nVZ2IqfK3ap1iMvOd6t5DDIlZHj62w4VcSqUSXu10D8ta3rcpjZvQafW5CFj/\ndSvz2nmHySZWHJ+ga4ZZjC1/xQ184iqOw0XlF8tnUApv+j5G9C5pSMDXYEyU\nbAS2nxSKJCk9ZEhYAF5KEPtq/TjEQD94fZTeE2pRy5USSAsFrXAidJeNaLqz\ngPRF8EWzbZJeEKuuA/Q71rdgjfAcp/EudY+47sBh1Z/LxeF/jTaEcjXQ6Qpc\ny409Nhxjozl5aRa3hmFF27FFYb//crDngsXeMAMaDzwevYR9zniC9o4S8Tg6\nuKiF\r\n=03uq\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"c02112f324d2cd79152081db180ec1b384eef5f4","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.12","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.11","@balena/jellyfish-logger":"0.0.85","@balena/jellyfish-metrics":"0.0.94","@balena/jellyfish-environment":"^2.2.26"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.9.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.5.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.18","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.17_1600263399718_0.4825187582970356","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.18-renovate-non-major-9b552606404c9cd4a48415a8eb0fbff07416f8c6":{"name":"@balena/jellyfish-core","version":"0.1.18-renovate-non-major-9b552606404c9cd4a48415a8eb0fbff07416f8c6","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.18-renovate-non-major-9b552606404c9cd4a48415a8eb0fbff07416f8c6","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5d23eaf48c23c9a646894c3e14829a6626b5cc56","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.18-renovate-non-major-9b552606404c9cd4a48415a8eb0fbff07416f8c6.tgz","fileCount":94,"integrity":"sha512-KBGcOo8YfXShwuYrgSMxiSDbZ7zfcg2+60Wtny7TUTEY/JsQcAmBQDfWIrNogGcNP6JzXwsEvJIDg3p2wBOPMw==","signatures":[{"sig":"MEYCIQC8EYVRfLT0x32jAmZ7e/M74C11IDN5/hdh8YuEkcaVMQIhAJKNGP+pRDyKBsZCaZwaherJVOMdNlSSMt07R5hnb1GP","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":559240,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfYhW7CRA9TVsSAnZWagAAzeAP+wQQ9123/5woW+x3FI8O\nowoEe/kOIVvPRYrsmNSyyTt37cm9SsUzDRLEjbQx8qg6g/IbeuCWxtXNAPE5\nNF7w+KQVpIIe6tqQReUKt1sxx2/2tZ3ZdOsKEsJHYfODzGM7K6a5MQr7/+xK\naCExUYyr/Zn2l2UUBztKR8NmBe+c5lphAOBwSEXysEERMAZ3AGHz/9nSnnF0\nmMxVRPJIWZBYAGbWY6ic+ERcC57CSJq48vztn7ShQ8R14rCVnQL6a7eVPg2M\nLVweyLvWvo3cc2ObKJZutfqU8IrQDbcQv9PO6wdjNE5rJVeccwTVhrEc5DkM\ngnuLqGSF6VcgAAN9Jp2xGlcb1jdP4DuOg3YC67PJwUxPNVDH1Z+fzTMHbEiE\nbkcXjEXSA7KmaahYoPvKgbggQYwqfdcoItoK8/d51pjPDWQFrxpHllaRQF27\nOwPofqoSWHW9h3BxECW0irqoLODhLWIJXX2YEt5c2/aW2wrzNiPULXOnJrW4\ntBiyQq4VKSifUAw2ivX+oMR+dpA6OX5BA6JFNjwwt/eraDPH1H3LmbrIkcHJ\nn2ubN12Ty15Q+BPIpdG1hx2qXdsoRMKjfycLI2Lr8gGbuA1QSI8mHaRUTnzf\ng2Awslb/ykIvIKRctsBP01fXn3V7M5QVFPhg1rzUJwKyJy/jLUE/6bAYJMUY\ngKiZ\r\n=KjsB\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"9b552606404c9cd4a48415a8eb0fbff07416f8c6","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.12","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.12","@balena/jellyfish-logger":"0.0.86","@balena/jellyfish-metrics":"0.0.94","@balena/jellyfish-environment":"^2.2.26"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.9.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.5.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.18","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.18-renovate-non-major-9b552606404c9cd4a48415a8eb0fbff07416f8c6_1600263611462_0.5449963008267049","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.18":{"name":"@balena/jellyfish-core","version":"0.1.18","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.18","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f7ee8e0859b0c95b77b06612c963641f4321011f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.18.tgz","fileCount":94,"integrity":"sha512-W2Ou7vE4GN+p69LwIJpbfbIA24dAHZTgS2Z2Ib27/WsLIejasMxO78pirKdfbHJvmGYNuV4CSafxJYZ6qlQ8AA==","signatures":[{"sig":"MEYCIQDuq3icZvZR6Is7gy3otIFDwem+UgFkQRyK23wTq1MhIwIhAP3FMii97sAnegcuhR29SshHeZYE+1If9L0/1S8vUBG+","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":559180,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfYheGCRA9TVsSAnZWagAAm8IQAIfDnwa8ZrNNPHlHPeMv\n3HTnoZ/UApcUHjdzyeetAXtKI8ETYc7A2oBhpX7Sl8vTXXTSI5kpwRHnG/Aj\n0AIucDLpLMrosikJIPFnvg70eL7IYfBUcxepC483e15tZ4VxxOc6WaxYZyWN\nqJX4QyGwOzEY8+uYzuNUTeYVWwmm33E3ysCsbnNyzTlBKk/aqFrOdiVffKJQ\nc7/MFdidrT2Bz9pWlzQL2eEqq35g9sDUpqdWQLB5uno2rWh1aU1OO3Z/cKr9\nnQNvFpeClZikJz3m0gNEfwCaCjwFTx6/qivgL0lv+WQGh8UEJehXQb4jr/YA\njlgqQDsSuCt6F1CIUI9Fk3Xdid23+LmiD9tZ/+Pwq+wW6m1VbJteB1rq/uT3\nCemyUniYWGm/YoQAMk85ve4ZhQcEw53aMLHUlWDUz+ve0MrFM0lYE/lYUCKw\naQF+f08ivWvxLWjLGVB/o9+Mj5UMjHxLL1s2lVU5rvmq7pVnh1WfNOAuUubO\nrfTEKtIFMisiYXy/vDeI+YrgITEPa25fZ3uziJSFupe2wooCmTvTxn7InHjQ\n3UhDCGGbMdTtVD03tgKJucv8s8oJX1/MM2PN3WAzpriIalWDHaKN8eujn1Rk\nKLtPUilVarHOVq+x6lqccczJpZsGvPaJjJ0zuDvdxNJANB4QR478kxJ8nSzE\nCn7W\r\n=5UwS\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"f19448da610c2ea75433ea0b826ebad2dd264fb2","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.12","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.12","@balena/jellyfish-logger":"0.0.86","@balena/jellyfish-metrics":"0.0.94","@balena/jellyfish-environment":"^2.2.26"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.9.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.5.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.18","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.18_1600264069607_0.8259223035365411","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.19-renovate-non-major-59e3431ef2e65694e2030f90c14a6b7ab29ce083":{"name":"@balena/jellyfish-core","version":"0.1.19-renovate-non-major-59e3431ef2e65694e2030f90c14a6b7ab29ce083","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.19-renovate-non-major-59e3431ef2e65694e2030f90c14a6b7ab29ce083","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"fc0c2d7645cda4a464a08c34223ac9c08695746c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.19-renovate-non-major-59e3431ef2e65694e2030f90c14a6b7ab29ce083.tgz","fileCount":94,"integrity":"sha512-WcETl9eG1uXwypKt13K1i6VtJ6XBt8t6RqK9esY/T/c3U9yTFMaMwg2WSEZaqOhtAuzHgKdJF2utcy4QwL9UZQ==","signatures":[{"sig":"MEUCIQDAIkuva9/Q0qDb+qSmxfhs7MSJnzmEvJ4KwpUigL2DiAIgHu/kfYK9y0Wt/6Iz7WvzlllQ3XZlNPGf7zz017nmWL8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":559306,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfYhi9CRA9TVsSAnZWagAA07MP/3Q5x6YKzfiqbmJxA2dH\nZwrZmL+LnFCfsZKkmD3lwxkyWfa3iHBxiG9tnSQy2KiOJOdhz1S1OOz/xPQO\nWCskmFuBRB0O4TbwukrLtRO0J7xELFfx/y95LnMGaAJv8dq9kCPVh8alzWYS\nev/eG5yygpTs6Vnuaa6QDshfd+zvVgEzeyKQxmray+XdhpMgCPd0aQ30Tqf6\ncQBOiiCaLnneN0y3+bL8wcYhJfYCf9cAWf1ZE/ZRHja3WcQzE32xocSq2S1Y\nBQM27z0w54mFSVAgi/1TOsH5Nj3dZ3Eob9Ubb7CWr4HjM/mHGFfb+g4aTSx5\nFm4NHIr/+Qb2s5MiA/alGALFr+72DlRC/gUXAaMDrFJVbvBUrd2l7b8Z8/Fi\nnaOnC0oGnTcrKgmRisBDRE2NcdrZgUahj/yuI8jFlLKm+rfZeDAhZE7Xo7k5\neAn9QCmFxwk1Ei9GAj7U9YNTuLHCzL/U5qh5zPneNoXNl/es1Oh/EXUPyAlK\nW6qSyvwRidQTla9i1sVZ2EbfZvBkmf7ZUF9g9MZ1xzSAHEFoVXAJer4AK6iM\n40zAvxlN7vBpDsHINnbXy2Uq/6jsg6AZQ1eUY1E2iS7DFwblO+Joj6VJ3elc\nGx0UQ15qzCxX/BEBPBPrmetD6A8y97mtn/bZI2TiSomjjbiq/gCi0xNsJ3M0\nTwYI\r\n=uesc\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"59e3431ef2e65694e2030f90c14a6b7ab29ce083","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.13","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.12","@balena/jellyfish-logger":"0.0.86","@balena/jellyfish-metrics":"0.0.94","@balena/jellyfish-environment":"^2.2.27"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.9.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.5.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.18","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.19-renovate-non-major-59e3431ef2e65694e2030f90c14a6b7ab29ce083_1600264380701_0.7594611513483396","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.19-renovate-non-major-b23c11cbf3c1af0a4fa8fd5484a22f5b98c358be":{"name":"@balena/jellyfish-core","version":"0.1.19-renovate-non-major-b23c11cbf3c1af0a4fa8fd5484a22f5b98c358be","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.19-renovate-non-major-b23c11cbf3c1af0a4fa8fd5484a22f5b98c358be","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f02588832c98100954cb80ad5999164de7acd55f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.19-renovate-non-major-b23c11cbf3c1af0a4fa8fd5484a22f5b98c358be.tgz","fileCount":94,"integrity":"sha512-uuYJzgDbBstCJE6sgGvo86uf9yMlUri8jHafROM44GEWAihKf2XtSOVBsCTZ+drNKdTrTsY9UWVPaSc0daRUyA==","signatures":[{"sig":"MEUCIQDm7eJuPckZnNvGz7cknjLXOqd+JSW40JsUHoEa1aSy+AIgXs5ioPfVnMxvAyYwdruK0ok3akgAU+tv3alqDICPcEc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":559306,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfYhmyCRA9TVsSAnZWagAAc0kP/2r5lYLHf5JZTGfJP6EF\nAlhkZYsPGNBRSRFnmRxiuZ3816Xgq8PoOvmnr3m2u6iw65K9dfuUXgew485Y\ngSXUyd3JdIeEzm9jK/c3u7sUXGpT6iRqSv/oiRwHm0QhZdTrHADK9WrkCz1g\ngr+Sbeho1nqbdH9GA0Qyzet4Pv8gQA0t457pS4AF7OqUNrrLDp74gg2WOo1Q\nbsgdu/HM4WHMVp61Nf+Snz1M3mzH7cfEczVxfx8YL5yxO8SkhF3iTxdKopZ4\nXhG3+aGVrhzigkGxW9NJwr44KDOac68LlWzNKLVKP3JNGwAVMQdkL1miq9i8\ndmPwcFxLBy0JfDfWO8a8sMQXYBfqwtd2UIp/D5Ip5BsNhKQDmq+5NxPI8Fhj\n+knjiKKCxKPEReKhu+re8kGX9tlCr4DhLrscGQ7Nsn37uAP2Qt5c7yqJOtyv\na+uyaV4DuBe+f807SNGwnNb7iuqqbFK4GqUfS6WMOUjBNkqHWFlzg4CVPaj+\nCixInt6kQlcaioWnLmBXFZLPYf431vUY4FbUvYd80+TVBfG6HyfZkv3gaYu0\nZLQMXBLmLJW6Hu7uosClgnJR3WWlY8uWD7zaAkUolYJI8oFkUl8M1dT68UJC\nrv7YxyhThYEubE8GQhpP0lveyAl+a0qgNmyhgx3psqQFKa4QXP2OZtCgNZDV\nTn0Q\r\n=/+gH\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"b23c11cbf3c1af0a4fa8fd5484a22f5b98c358be","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.13","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.12","@balena/jellyfish-logger":"0.0.86","@balena/jellyfish-metrics":"0.0.94","@balena/jellyfish-environment":"^2.2.27"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.9.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.5.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.19","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.19-renovate-non-major-b23c11cbf3c1af0a4fa8fd5484a22f5b98c358be_1600264626284_0.1373734916675855","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.19":{"name":"@balena/jellyfish-core","version":"0.1.19","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.19","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0a1f94a017c7457ac1937cc8a36d63bc7fc5fd73","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.19.tgz","fileCount":94,"integrity":"sha512-/VWFdPkb6VVeSlnF+RPEGhPGK+saUt8QwNQRTTKlbS0ZwLmtcP0BInv0xjMPe/iWiZmHvdvYKykUDBF8UsRbNA==","signatures":[{"sig":"MEUCIBWte6zmt4MqDHDEpKLBzE8iwVixoOnhJVDbecM8J1vyAiEA4hm0lm1FzjkSB0NCvu8wGUOS4eCa9vVdlN97ZAuG9o4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":559246,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfYhsOCRA9TVsSAnZWagAAYlMQAIiKaEe8h8IBxLVyLKXy\nhyFKi9yXieMLZQkzmKQbpMUmB01lyhn2jW6RAHTAmoilrUhptuU6qX/yL7v+\nnwZlACNcOdqzg+zDhkWwxcWjORJvF+g5ZWdeqSKLLIzw6y6ixvKWvf0C2HSQ\n24qfQBLh5qZAqbskUK3KlLlrmF6rK2SM7tQp7Y7duIlqR64tbs9FN8sLoA80\nRUTsiEUAj9x4gGBQIyY4ZTDcc+1Ofg77NKDhaxtNnpjm8m2HeYPnuuZxf+B2\nhDA9tSqALw+eZc5OjbmPkoDJT7Li76QO4KbsNArMzFV3vw8ouW8ie3maQtZQ\nJMjnuaZ2mo6wtHgauatGS1IzdKtdP2Bdr7WsoFTimCQ8HjvaYUE3GBQkGezO\n5AiOHYZLkrRt+WrIKPKAuQN2QtFHU/IfDRRcDFxRHGXy+gj1kTpexNoha+fE\nh9hFAukDRZBWTsKFbipX52pp5UOAhmevDTDZc33gVUrUKLL2Mnv2c4ebpUDc\nj35VE/tTcW448JnuOao5tBu+YAuj0nAPW+NnZAGlC2zAubNA35MbcQf8S1d6\n/mgnpIU11d9dMMJY2VHykSATlwvUIDd/bAmDKTrnXBqm7EXFvQG0SiHjN0um\n/QkFMtY7SwmRBENjvK7biYiXy0sG5Lq1SR3dBLApABajGWhZpE8fvL0aiHXx\nh25q\r\n=mKiT\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"51f2ce8d784d91a9448d15f6dc3ef6bcddc323e8","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.13","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.12","@balena/jellyfish-logger":"0.0.86","@balena/jellyfish-metrics":"0.0.94","@balena/jellyfish-environment":"^2.2.27"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.9.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.5.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.19","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.19_1600264973549_0.494305896991144","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.20-renovate-non-major-416c112ba5db55c9745f5e7f08b86a32c2b03a07":{"name":"@balena/jellyfish-core","version":"0.1.20-renovate-non-major-416c112ba5db55c9745f5e7f08b86a32c2b03a07","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.20-renovate-non-major-416c112ba5db55c9745f5e7f08b86a32c2b03a07","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"89d1b1ea9ae2fd05b63743eac4ae6474ab456d72","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.20-renovate-non-major-416c112ba5db55c9745f5e7f08b86a32c2b03a07.tgz","fileCount":94,"integrity":"sha512-MoUpV27k768DjuhMH7WZdqbCVJIxDWKwxIAsKugWTx8bNey1aSxiUYFGvZSHpICRGHBi92fKnY7Li6lI2jNAfQ==","signatures":[{"sig":"MEYCIQCWKNAoIyBUd0doiYt6nkt/S7Ve3NvV7S/ucwx9CMIv5QIhANdInKstR8Q797EiNpPTwPV4ZRDc4bzvtsWd3EsW00Mh","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":559410,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfYhwuCRA9TVsSAnZWagAAQCgP/0T58lzkoubSBwJXTxaQ\no1XdkTEY7VyA67CtsA7BSP0TJbUVmvZdiGBHa3umTVyzOR0cS1f0Mu5rZ7Ye\ncS/yuoZOUSApZvaPqBN1zAGuo9SsBaFjA8zMyz1HxRCrI6cyVwYcmfJm+XEL\nsj2sSJfjrWncSRxK/1fqbBZa2TCLNH8S3Hnb8NoRUs08lBTczoM70gcTBWSf\nSbMdpkq4rS+bnoeFb8EEr3bz3G2hhyfVZuurcNafqSFJWhQ/8J28IlHjAu17\nwLHICUPVi/XyU2SDxX4BfKbAwJValnTB1Ok/UA3pQ9tgnqWdlaGkQG2kr93I\nIY0e8k6SsVs78Rz44mXxxoA3lL1s6TOGSrkW6DOkDqimiepsmpbYr9RcD0ss\nrjMCHze71lHalJ8pixbCZ6xTTM4PHB2gxru6ohwuROfLsbaa8YM8wNaINqZB\n4NJy3iRaSEzut7z4vDoFJMV+YI1tQ1yc3nsX48tqQ6T9EEbJGSndrbYWPKtl\nnxHDQtzTnhZ72HjIpWSSi/D5PMLDKPdJXneL4QT0vF9qWvMduMydAxgsVjY+\nAOKGzRjS0P1PbyTfqWV1fcaR09rDHyvntkvA8tpd5uj0r0f8cyzpKqokTbvS\n28gV/rR2gI1LcbVwWAryDyecTriRwVp650IgMVsfvUNTIJtqqz8hshSKZouS\noyf0\r\n=uR9c\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"416c112ba5db55c9745f5e7f08b86a32c2b03a07","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.13","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.12","@balena/jellyfish-logger":"0.0.86","@balena/jellyfish-metrics":"0.0.95","@balena/jellyfish-environment":"^2.2.27"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.9.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.5.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.19","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.20-renovate-non-major-416c112ba5db55c9745f5e7f08b86a32c2b03a07_1600265261599_0.663732712886238","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.20":{"name":"@balena/jellyfish-core","version":"0.1.20","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.20","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"fc593db110f7fd92a65e2112ae65ae708180e90d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.20.tgz","fileCount":94,"integrity":"sha512-6EdtrfZbIlIliTq9LSk8hrYpvCBfAW8FQwDkEdvg85pHOwsxT92e36SJdGEvLbasGGh694kUF/ALn2vS8TY4hQ==","signatures":[{"sig":"MEQCIHyitsHfckpWtTMzIbKLuV8PSNtkYhBDZ2tBUhczNeixAiBr8I3ymferqqRUmTNduS5WJJZgT1/qfPH8ZgQ2+rjYYA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":559350,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfYh1eCRA9TVsSAnZWagAA8JIP/2mB3SsraVRzX+bV5bTd\nGosCHMlr12ziUapS7BGEBX6gQxqyShPwx2fDsOj2dYuefXdlgsuajZZZ7CUt\nRSGD/TMVM6CBhTKI1HpDZrB64C+eN4jKOs0eCPc4n9pqyh3QfA7LmO0rrOmW\n3Pyvoqefwulfvq8oc7Q2ZGUEjUdFlZxzujk2/bkABo7QoLO7Y8VxRNLCm+1i\n5xWNkZH2S5Tc44QdW6TApaukihKStE5NtehrtdIMa39wDO7gls9wBrBxkOmH\nXWO4ow0EvGLIVKL0f0/8L6CHtOcs+kcNkLEJh+EJqUhYsM0Fcl4lZpEZ4R7M\nOePYHLor1r81Yjdbp75YlFjwJDiMDNAxa9tqXz1K3g/z5+NhWcIyZjQsm7Z/\nsCVTDaSGrZnnEmqweMRc2pz+105WWDFBymu70j+DUwi3nHr36kD8yS2qgw29\nhd2994bH94dlBMBGEaUbc2lvedMeU2P8jAbupmT+N6Fcx526iZyFeh9rWBvS\nO2628wzZ4WiV7/M1gHQLTl+L+mW9o8QbrplDlaQCLO29lQF24aHLvv3o9YP7\nqzyNriAxwICbMuC5ERP1wTq98yjw0Zq9xbR92avgBhHKLebtxSK9AsMcCsUp\n2bfLsZvHCiIAs2TVTyWls7XxslELHgQ9UBp3IGaky/S4nkR35zvAZxAXYH5g\nJgiY\r\n=CunB\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"49fea0099ab867f535be07bd4fc903034a004da3","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.13","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.12","@balena/jellyfish-logger":"0.0.86","@balena/jellyfish-metrics":"0.0.95","@balena/jellyfish-environment":"^2.2.27"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.9.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.5.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.19","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.20_1600265565603_0.874306710647913","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.21-renovate-non-major-2802d5cc4fb1bd4c771a47450d0c2c5f6e54bab8":{"name":"@balena/jellyfish-core","version":"0.1.21-renovate-non-major-2802d5cc4fb1bd4c771a47450d0c2c5f6e54bab8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.21-renovate-non-major-2802d5cc4fb1bd4c771a47450d0c2c5f6e54bab8","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c4d3530f039c66bf8e0df42c148bf9277917b1f0","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.21-renovate-non-major-2802d5cc4fb1bd4c771a47450d0c2c5f6e54bab8.tgz","fileCount":94,"integrity":"sha512-UI1uDcbuGuOT2LJ+vWz2cEcPe7SmGD5BgZzOsklhITNF6fyIQeQg5Bl0i+IDZGKBiPaSzidQeQExbDRYikcpXA==","signatures":[{"sig":"MEUCIHwEFUIisY3KOINE29NFghvknRJD+cqsoSkdnVvKLMQbAiEAq+EbaaXowSx8ea2JCZC3CW/Mz67YPXxK+cTjI6pqb6M=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":559476,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfYkjxCRA9TVsSAnZWagAAJwoP/A6VjzO3qX/pimyLjdva\nEzjY7EmZPZ9Js94HWTKO2D5cr8+noZ0qfhTZi54NhLzjaLk/1YlxfQ8dHJQu\nFPZIwq+g0IDADIXORw6BhWNxXqGg8chw87nmIOVQQvdy3nwP5T6cFAdzmcGz\nQ/7dylRu/6tWFhTOU3xa+EKTAhQo2dH8BHNBeoSLvGrUfXxfSqzSf//pgskz\nXW0h4Dd7ChWL5zL7AEUAsZRyYFKO7wr+gvtotFO09FiPeDgknGNGlJbLigH8\nwaa+pKz8AG4YJsizRrCAAkrCxWl+8boiDFZp99dXU1l53xzGMtDnNaKIbaYE\nRIrXulwyvAA5gR3C01+sXPIsXobY2bf2YsbsUF4B4+TjLeN9TWjQOPFjJ8jB\nxdfI4xFdQvOkn8Mvhfah4YrbAh9DitSkYPwMOXtByrHnQSf79XZwsUmwoP+K\nYW//SHOmLPxpF19KXZ2BlyT91kOpfjTxCCX+Ydi5zaxffh5yuPEO9u6lh1qT\nj229b02ZjyHhrxVYiEIGzlVfNC/VeXgFZVEATNUkWaaxAjRN2V25vd0Ng+X9\nZukUZTWxNu6uYal+K0KE9kQICubN3R0X8LDzMLC4wTU2nvH3J6UQlp3hoMJh\nIG/49KlcD0yo1a21+R4RwbrcaM78GLPflbB1jhwStGBe+ebYsFrQfkUtIx65\n5daC\r\n=J0Qz\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"2802d5cc4fb1bd4c771a47450d0c2c5f6e54bab8","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.14","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.13","@balena/jellyfish-logger":"0.0.87","@balena/jellyfish-metrics":"0.0.95","@balena/jellyfish-environment":"^2.2.27"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.9.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.5.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.19","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.21-renovate-non-major-2802d5cc4fb1bd4c771a47450d0c2c5f6e54bab8_1600276721259_0.7239352234656742","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.21":{"name":"@balena/jellyfish-core","version":"0.1.21","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.21","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a5431280a63d2c7fb434a241b917cbf297222e37","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.21.tgz","fileCount":94,"integrity":"sha512-Rz+kuqSW5/AozOnDt/vnTUBixc3mTsctSngCqr8FLfulo0Y13tT5qOjex+iI/XL9iw28ebu83wIylFhAa6wYhQ==","signatures":[{"sig":"MEYCIQCeEVITin5xIAmDXyppZlad21MNzDQRAP1mSE4iB3yyPgIhAI9TrsNpv4F2KfY33CKCvsJsOoJ846/w95fwuWSonEAA","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":559416,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfYkpCCRA9TVsSAnZWagAA5sEP/0e5gfAtaIjLWkrrGClW\n9eiWIQjKVfj0EeywYk94kb4mASObpZd4cpZ37/mUoKakyplKdjKX5jr+78Hz\nYN1LRiY/opbqhX3E2j6rMwLszM/iT9x4ODbjHRHqKBFD4TPrXyhPGskou7f0\nBWvo4EaHRrZYt6/ZwdMFVte2/k282W8GnK2zqdg2stMRGXCVlywtGHnZbnWU\nzx0W5GXrEFKYcfwD7O9F8UXyCBRzNiQfTbey3eoY/NZ1zt4dTXQIrv8fVTMz\n7XzrThexy41wJlLgf/fg0zDXxj+sZ6qUzDu3Iip5OIFsDKn9M8slwzAbYU7f\ncXGdzUfmBBG8ESbndwI1Oo8CIab/mZzW42+hDcfam4Yh/v36IOngtWc/tRGd\npfVr5mOVyxUZhHTTFVYwmr95TPAtgtXu+jr+ME8UDC/4k2rYLqEoaLxIwd1/\nom5JMTfPVfUT6KoJrhJzqby5lvxwu0KS62h+LpEk9KlwJlAxS4oVp9UM1BjF\nAgu7Q35oLp8CbuT2qMDj1NhXFHqJEe/duXi01dWU8ct8VY7x6pTJSuQGhfhw\nuBSgxbY/1sKroDAYQBcP1nffCtltLZvUQv38JypyVOY5MYtXLZWVcJrEdSaB\nsOMeSQBO5IDG1YZMyPI1f3oJDmfOUy+z1eEwSBwXfttxYfZubDC4HNGGjNCW\nG6Y4\r\n=LmFC\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"a5009250249ce352c7c7244abdd2cea96968071b","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.14","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.13","@balena/jellyfish-logger":"0.0.87","@balena/jellyfish-metrics":"0.0.95","@balena/jellyfish-environment":"^2.2.27"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.9.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.5.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.19","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.21_1600277057921_0.3413380079369812","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.22-renovate-non-major-554fe25b02a6f53b8973694664921a45dcf1f80e":{"name":"@balena/jellyfish-core","version":"0.1.22-renovate-non-major-554fe25b02a6f53b8973694664921a45dcf1f80e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.22-renovate-non-major-554fe25b02a6f53b8973694664921a45dcf1f80e","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6265a6f8338ed487ddc9b982aa030f394ca6799f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.22-renovate-non-major-554fe25b02a6f53b8973694664921a45dcf1f80e.tgz","fileCount":94,"integrity":"sha512-l5M88fxsFBaLwmxn+aUz4kU7DN7k7yt2nwVsLbkoShYYfFaArmT4zD/HnbJiDHR7ngHejjHq1MtUCp3G5NmqxQ==","signatures":[{"sig":"MEUCICxKksQptcR/fZOW4cbS2YdSLctwzcAmtd6dR/ho/TDQAiEAxBNeHlpM4Y+u+8bJvGJNc6TDdvZzAfODv603hFPXjJ4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":559580,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfYkscCRA9TVsSAnZWagAAfe0P/Rpw6AItChgJah4IYHDp\nlnvLkRdqfuhNYn0N0lBHh6ZtZSPEq8SANE/x28ZjmRW89snKhfgaZ95cXBZ1\njsdg3rm88XhO35JzuSbY2YSHtpekGAGfco/pOyviV0SogC/3Ao/jE7JYjAQC\ntVfMySGCtTWvcO+VKoUDOL/Rc5U7QxVqlaAltFBXwEORxTcyjQRWwTR5xiRI\nqn3W0zWkCq8ry7F3eYn5WputOtkX5Hg5FpxlFmwrmrkqqW0G01Hw91sX4o4v\n9wlcjunYChrttC5/fenxMVo6X9L0RNIgYeKkyCAjwgf8MaSENtYzweTfFUCF\n3RjJD2p7Wk0x3EeYMZ972GCwo3YU5oJ64jHZmgYKC+xhhIl5t1EN+1WmTM/I\nVvooo3zbawNYVHWtN7gFaRl2Mviq8ohG7/z3+BWfvVie7UUnhECy+P2S5tno\nB+H2G4BOeEBlKIJkA+qS+TcWXAd+sTN4KkY3BrkfNULzlKPamJewwKC8/RbW\n+2URl7KkhtPxsalmu39B0TpLd76cwwjSKhKdsI+0aVD5JlxgPN+0tlHuV1nC\noi0VmBvuni331btvT/YKIwwFnQjWb8+bxPxRNx3JCCRY8a5/qQi0pPU0WrXM\nP8HdoTa+RHW0QlHx6R0mXk4Jw7TgJR+X6gy6hLJ3qu3lwJ/78aDjEJh7YL4w\nthTa\r\n=Ok7F\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"554fe25b02a6f53b8973694664921a45dcf1f80e","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.14","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.13","@balena/jellyfish-logger":"0.0.87","@balena/jellyfish-metrics":"0.0.96","@balena/jellyfish-environment":"^2.2.27"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.9.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.5.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.19","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.22-renovate-non-major-554fe25b02a6f53b8973694664921a45dcf1f80e_1600277276025_0.596795378312059","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.22":{"name":"@balena/jellyfish-core","version":"0.1.22","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.22","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"860987e32864020a8fb0b51a01b10e73e9149c1f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.22.tgz","fileCount":94,"integrity":"sha512-fIwjc4nsO+JcR83Nn3+4PJvqvfBj+yNn6uhDdgBBtYbrOZIsOrPe9vS2xHS5Oc17/FA6YdXawSP6+eCmySZefg==","signatures":[{"sig":"MEQCIF0vjxD092pFpLblOFzi78hJ2LTJ95qi1Ly5ptSGJ4DHAiBRKO9rH6lyf7snxt4MvO6BYTSET6Ea3DRtkMViYyicJw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":559520,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfYkynCRA9TVsSAnZWagAAgXQP/1E+Sh1Mz/uAQptjamxJ\nq194IqHgm/HSfWpoEGLCAVKC/5c33/AfZeTkQ83h9A7JYSYM65MsOShcYzN8\nYq1FgnObqTJ2eHie4BIkcDF3zAoxZgGkhU6h1eU2l2ci/6c+BR9C/BZZqIi4\nugOZTLkSKFU/PACcE7AXEYoRjw6Sm9Q81X0dNAKA2xfl0UGbSe4pCiFvN/rG\nsZHci7xjLFRO+WRhbRSgMZhSfFr6QRW8m0dfqdHFz5fI8ccnsQIJa59BCNPb\nlboq0cEqr7gNOvIeLD2uEvt4ff4hKSWNjgpoQNQl7nFmCjHU1JugPz5QaHl2\n4KdwRIa2B70tXLKWUKKkr1txibiccc/SwcBfRcNSQLZyUsJNjocX0h0UBJoU\neZn+sa3gdaqkwPzu6b9ShOk2XI8RiKmZnQmSTKxDzFmpKYm1WRwOxNMW3Gvu\naktPKw5XQlVus9wII4nFJeXowVa5KaKheYTzAUaeMe7DHiySfNjJHeNrH3Ou\nVOvSnGi8mghEPGiEAT1vjfwRZ2n9mAL9ZV7bq6fazdaOZ7bIhW05PPYQ2YP0\naMkMGq3wSdGUCaSTF6OeaVP1fSp55hm2Ph34FsHPKbLTqu7P8xzpD4biksWm\nwKh2EqVdvodJfCmTX6XgivCKz8UZJA1eI3fX2FEbRL/XpygX/yu6zWclRwIk\nbqge\r\n=r1zz\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"88a88a3bef834469f75e2543d04d5a3cb3e8930b","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.14","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.13","@balena/jellyfish-logger":"0.0.87","@balena/jellyfish-metrics":"0.0.96","@balena/jellyfish-environment":"^2.2.27"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.9.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.5.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.19","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.22_1600277671436_0.6537904133621497","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.23-renovate-non-major-9d84b649cf790eb1cea926956a9d33497ea1d80e":{"name":"@balena/jellyfish-core","version":"0.1.23-renovate-non-major-9d84b649cf790eb1cea926956a9d33497ea1d80e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.23-renovate-non-major-9d84b649cf790eb1cea926956a9d33497ea1d80e","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"fd2409240b2512c49a0164d36c2498e8613119be","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.23-renovate-non-major-9d84b649cf790eb1cea926956a9d33497ea1d80e.tgz","fileCount":94,"integrity":"sha512-Wpx+/kT0yLhQSqq9B6zI9e1d73HAy80PcNKuyefBeJDNT1AS1kh0MVFzYodRj/UPfRyjhDjnCfj97YfZS+8YEQ==","signatures":[{"sig":"MEQCIEDL0BMGSSwY/pOWKC+OYGHZdmS/W2oNDfjJ2hmFXxpXAiAO0/RfXpxCol4i8zrDus65VciRORf3Vk+yK9+vUNRJOA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":559646,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfYmR6CRA9TVsSAnZWagAA53cP/1VF+k6AxPG7YCnoP6HH\ndQ0aMwcpnntlH//+ZMkBBZg7XWaNcc+svFIe0HdzfiCNPqD7f0VGUwLY8zSH\ncgDGz3k9YOvBcTVHGcntTbmr7peTgHeTSrj2U5JcmfzL/p2MhY+O+yS/eDkF\n0qnK5wW7V9l3dqMhuyBCduvI0plEB2mpBxghx/dIOGgQII2kYnaQb11IBdy+\nfGiQeE5C+/GSsCMOFSa9GQd0ZzzRI5uHEUw0UDQHFzb05uHzyaPNdCRrItK/\nTo3nZJIRc8BEBo3qjxx+xU6mNeKbLQmoZXIqPWkbPfARMyfjkAqIgkN5BUpO\nV0RLPkgATWWKfza0cbrru+0rARPPherTO0Agl3JEFu+yQwoYFv/hIgjDiK54\ntqfS8p9vx2CEuGqIldkEoZO27iT/QU4cUnWkrzrYIiG7zH8E6u2H3sYzpyaP\nd1yocjcLZueOndDI3h6cypKeueF00b8hVRzct1IsO3mhibnUUbEhXFq+LkX0\nRJA4KAhsXf07MLRmDWK8VwnheJZmg2qlbjDjTeHsmfxEMTn85ABMnRNblrMD\nZzNyuBRSsL/CQCPYz2Qt1eaSaDj061cLpvkX60s03sziYXgjO1FlLUKBDHGg\nEnYg61Ku7sPBf8mvPuK9d7OJJiL83XN12Jx8pBzQ9RDDxcDmRnHjYMBbFphZ\nWQup\r\n=WMKs\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"9d84b649cf790eb1cea926956a9d33497ea1d80e","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.14","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.13","@balena/jellyfish-logger":"0.0.88","@balena/jellyfish-metrics":"0.0.97","@balena/jellyfish-environment":"^2.2.27"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.9.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.5.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.19","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.23-renovate-non-major-9d84b649cf790eb1cea926956a9d33497ea1d80e_1600283769475_0.004035133302764615","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.23":{"name":"@balena/jellyfish-core","version":"0.1.23","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.23","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"800c068e4bee630f19919cc542cf41fa1876118e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.23.tgz","fileCount":94,"integrity":"sha512-K6fS+HLhQ0tTmrQkjpqMuf3Q14B2I+G7+tCJPBL0Bt03zuDr4lwti5Jv7/sUhtN3srLdpjzOSIU6Bau4uYgvtA==","signatures":[{"sig":"MEUCIQC25Pef3eUEqFhqNH0MMMcOJD1IaGQS9IpB5p6zSjUk8wIgfBRhpchvbBRafea++FXFk6SfIQ0CMpCDgYBrrtJloZU=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":559586,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfYmYnCRA9TVsSAnZWagAATVgQAI3Gk3W6wkSFb4esM0W/\nGfoIGJdXNK33Ieu9i3VpenqKS9sH2ADkS48BW3Hzzeleiw5NF3rM1cha4MAO\nkO3Go91Wx+rz+xoU4SGZSfGehha71YVK8C3xQf2p13A/JLAQgdx1lo0NAbBy\nCiStBBVXJyILuidIluWN6H+zLWTRmoKpcenEf9fsy79KZpXK5rfeMFaIeX+V\nfDCMf2FK4ZuF4bKW3qHv7qSc2wuNFSKP4GZQ7uidI+cEqmVRty1mo3pxquGc\ncI3ej1HsgWPQ77HLqpG/mxCpiUSUXESJOE3emmC6ZI4r+TZj6pOQd3b7fCYF\nQujA37HhCbQiZItf3/LIBslz/UtVcKIFMbsMSrs1HA8OvB4PqhucRpdbfs+i\nmNR6WUdK2F2A3ynQnpy9Xd4+YJF6DB1ZzE3KjXhNinjrva6Qgl/X5RBMYgoc\nEes/zVyS/Jdfcr1NYHU8NRX/POn99L0ZiHZ68FNG1dO56gG0qO0wYzte9KTk\nQvMQhDlUAAnMgQ49O+9ii+MMV1Y1JPBbF6GCiTEHyH74XiHc6d7fBP8WQEBV\n7om7LqhzSy925z9E5PqXlDSVMFMj6vu0XayRihmJR+/KAJymGuh7BA2UNeJE\nA/EPBvb0XYYraW1PSWxBQsiIZS5sInk+aEnzOTZ76KJz+ptx4/Gcjlh2KD+6\n4fXT\r\n=PTsH\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"e5d07aa9c9c6485941a14deff2755de48112a58a","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.14","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.13","@balena/jellyfish-logger":"0.0.88","@balena/jellyfish-metrics":"0.0.97","@balena/jellyfish-environment":"^2.2.27"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.9.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.5.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.19","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.23_1600284198647_0.4295553763801676","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.24-renovate-non-major-3ecc8b15081cbbe7859f9d31c93204dbd5ffeae6":{"name":"@balena/jellyfish-core","version":"0.1.24-renovate-non-major-3ecc8b15081cbbe7859f9d31c93204dbd5ffeae6","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.24-renovate-non-major-3ecc8b15081cbbe7859f9d31c93204dbd5ffeae6","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9fe54521576e08474a060e9841fc08a9614c4213","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.24-renovate-non-major-3ecc8b15081cbbe7859f9d31c93204dbd5ffeae6.tgz","fileCount":94,"integrity":"sha512-WhtNqwzwe6yC2fh955JI/V/ZL9kGC6ePGTbj95c745CitJHNpV31SlCt+/PDzy87ns2Ba3MdQSBQAw18RNlmow==","signatures":[{"sig":"MEQCIDYHZvhy7j0GxUbUF3vAg4BvNHrKk3NV8oEJyuOPwoPyAiB3fhcUbUjbOOF5sLxIb8JYaPxXdRtvxIt7BAPFUq6a8A==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":559712,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfZIM2CRA9TVsSAnZWagAAlmoP/j3cKSAmpaZYqBin7Qii\nbHImWuWZolMcCU7izrsqNg6KzCWYWszR6faXJWhp0sfh/1imr6ToXxLsqbqU\nWzhA+KgzITlJzSF/9WzItprDP1ocgvQOmOXxUO8cgqoxj9rax+9moeHlIAPN\nv/VLJRLmlImP+t+un2ilvkeWyQbvy7mcC7Mktyo9KunzMjZQBbubZUPCbv3A\nj17kXCfiZzVwHDhf2TD981bScCcq6b7YV2mGXD6ym23Wgd+WQspqPWRnmffg\neGxezVE8gj8Czk7ETI5DcmoJNJteXsO4X92uQvoIYiJAZ0WwvL25c4wetdlG\n8VMzIbDq8s5IjahaWSAAqoZxTq0l6v/gK4DasRnSWYYSx5UdGCj3OjI7SZUs\nTu3pbvQ1/H1Ovd0Fg89bzisk2gcwS+dykxsRH9bQq3WilLBwBFDVpe/MahBS\nbeLXwnPtxshJNsyeok2mbSO2AKlD+IQeFiATnUwyHUFJ0bai5R1IuHvEUULx\nE3CcepDy7N4tKzqTTTFAHcPrX+3UauKh4pJqKaQIGErq9ZG10d3eoyhLFRe8\nEBamXKTnMsRmGY0HABd0JkQmSpE2R5VKcZw0LyNdb9SudnZ77JIIXwiJfjE8\nYJJ5OnUThpiIK4dRLXEDqcPbANHQJrdurgkFK9iyVn+ixIiQIjN/U0QLgp9/\nEzJV\r\n=owjf\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"3ecc8b15081cbbe7859f9d31c93204dbd5ffeae6","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.14","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.13","@balena/jellyfish-logger":"0.0.89","@balena/jellyfish-metrics":"0.0.98","@balena/jellyfish-environment":"^2.2.27"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.9.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.5.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.19","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.24-renovate-non-major-3ecc8b15081cbbe7859f9d31c93204dbd5ffeae6_1600422710422_0.4384833098433627","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.24":{"name":"@balena/jellyfish-core","version":"0.1.24","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.24","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8e59d63bab1659084e6e5ae679381a2cd63b1ef1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.24.tgz","fileCount":94,"integrity":"sha512-3Fh0hfzoXxom6TRDFYcMDknTr0OlUVMKkK6Ih7bp46dcA+PFOtw5warFgYyri7195viWj8gPiN33MIKLscHu5Q==","signatures":[{"sig":"MEUCIGFS68bV41N6AfARu97Z1argC/tPjtgjq9EeMyjP5usGAiEA0a0fh0b8HcYCxbEByO6vccZ/vokwVxaTOsrHUIEqLrA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":559652,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfZIv8CRA9TVsSAnZWagAApSAQAIoPTQxCitXw/GXy0Y9H\n2Rih+8UHmSsUVC+sFNldztFmaISGgfzzPhhixdC1spz6Jw7eXQKrBm5uBicO\n5PV9jSEEp9dTdA4M9fSdAAQmszJbZVny5HN6jcfGLl1/MK2Zwwuo225Zb6Cj\n8Fpa3/dRECb8JMYMP2kvtv2lmRbz7Q2ORQCdyqpWw66I5PbZjsPsS/pwASE4\niLVK1xFMZjNz3ahTlJJ/DDOYsDKj7MdEzX8BklSlvhos4YKazNksgBirXB63\nq3xdSwX8Iy1TkrUry9M77bwhJ/YmTUrZ9ReLSl/iL19zMrQNYozFTAy8cJ3q\nTrSk6e8rTynVrakyC40RnwgNaBrZ72Otzdcq4QT9Gmhj+Lxqe8UMQtxGsvzN\nAm0CMbqZNUgh+903n2KEVqRCEZpObk6va+E69eGR9WKrq0GN37VFnnzzk8Ti\ni8+IISx1OO1f+J9jwXZhpeuQC/2fJZwopad3BJq6ihHmD1AfeWeGpLO02WNn\n4AEC9q/NG92jGgubFOwBXUUTJ5lT4T/bEQTDSsNLCOlFS/zt7tiV1sYqKsBm\n+SJcg6SJHbA0ANwqQtHb30TLOnINe3EW+Y7e2tVQy4I1FMG2PcSrhJApElew\nNuzV3d++SALPCnhot4OfFODI+L5RrxmWDS1/aROfx/AlOZgqFBiLVdVPvU6M\ns7rC\r\n=6eAv\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"bf0b6f04a49de121ab1ce3aebb632c3025dbbd61","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.14","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.13","@balena/jellyfish-logger":"0.0.89","@balena/jellyfish-metrics":"0.0.98","@balena/jellyfish-environment":"^2.2.27"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.9.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.5.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.19","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.24_1600424955635_0.5402865184638008","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.25-renovate-non-major-4f61ad9b9f135c216bbe0d34a06b411fbd004ebf":{"name":"@balena/jellyfish-core","version":"0.1.25-renovate-non-major-4f61ad9b9f135c216bbe0d34a06b411fbd004ebf","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.25-renovate-non-major-4f61ad9b9f135c216bbe0d34a06b411fbd004ebf","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"65569695551a611beb349b029b6a4bdf2188a627","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.25-renovate-non-major-4f61ad9b9f135c216bbe0d34a06b411fbd004ebf.tgz","fileCount":94,"integrity":"sha512-XKYw9Xdm0+RWxn78P1KH2AsRJFiwAjD4sK4/rSYWeZF8WkObK+ipQsMnjT52kkWk+L9J9EDI/aJrJP8Wh0Jp9w==","signatures":[{"sig":"MEQCIFm7XrwUkPdKdSwqbnT1OLLxEfnlnDJ8iwBELhhGLgjfAiB4UHhkdjiLlWkUsAhKewVC/ue+sctS7xgfsRnG2rFPGg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":559778,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfZJ5ZCRA9TVsSAnZWagAAZhQP/2Y3UNOXNDElVOO/Qr7z\nmVy5mseAuVmXpOt0kxi5M2lilIalIh0Fok3SFG2PEHNTmU0BlQFUuR6edqDl\ntUfxth8IynAw/C5QIxEsEUOAhB9jEJ61L6/HAqix/Del8I9BT8Orra0Baez0\n54E7J7sl3WG7T3gT9Hz+OV60HRTD20HCqJtxsWSvh/I5iR7/94qzxURkowPC\nEBTAYXDT3Fm4/ECA4QT2d0VD5dd5YM9cudsksydIVJe0sZlcv2zuPrtaIxvP\nkMdFgWklH8JB9FfDJBl2214GZeoJVd9pzvG5WMh+XUeDvUHLNgg5htH6Y8qi\n8qPl5j8nkKDcsSX3ExHPampx5lgEmgRAIraOXWUijEI23opsBExJukdqxtSD\nOqA28L4K7jAcFiv5SphITRya3ITrdPXSPnd1XGFyHAt+AKkAM4Lnazn63H1c\nttSfGUEA/hyx54HTBaj65FBdgiiCLoO4KZFNyiTtc0Y274gPHJ00GjzzNiwP\nF6sfE3RF0ql5KkEWE5woiEwlo8X2QB8LOpvNWJasxL1j1QsMKf/ptJikSBX+\nKmjZHJ6T1mFfPneVG2WGOIM/yVPdANrZgTJ0c8tr8vxGXSPSqLCmNpf3x5i/\nxcrQKQfKnuEreLB2L5P9nkWLrqhz9aQmwKnBg84bE5yUdIB0wjc8zETp+dQN\ndOF6\r\n=eVMQ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"4f61ad9b9f135c216bbe0d34a06b411fbd004ebf","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.14","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.13","@balena/jellyfish-logger":"0.0.90","@balena/jellyfish-metrics":"0.0.99","@balena/jellyfish-environment":"^2.2.27"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.9.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.5.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.19","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.25-renovate-non-major-4f61ad9b9f135c216bbe0d34a06b411fbd004ebf_1600429657484_0.7991274295249224","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.25":{"name":"@balena/jellyfish-core","version":"0.1.25","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.25","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9479d654ce7c635d0dab13bd959c5adc5ae4a99b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.25.tgz","fileCount":94,"integrity":"sha512-N75QSca24vpb6FtP5zlwFDekLjniVmUJH9ZHM2eECPr+BAJhVY4927Urn4LCtUwz3UZTuTQmzz8T+mDTYnXGag==","signatures":[{"sig":"MEYCIQDwYyK6F2OP1IrCdtcbv3aiKXe68C5CBAkBIVdjrjuDugIhAP0doOuKGTHfsFw6qToTvOFl8RaRGA3yA1edWsOJxiJ7","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":559718,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfZKj9CRA9TVsSAnZWagAAUjEQAJ9RGmMEawAoJmmzLi8w\nLlVKx9BRDl3uUJZsLudPJImF8o7NyE/6CQAyAEz8XY45D+SUDKgVBwiVRn1k\nnfr/wgnZyrLDPHFhRDtPJV90Pw0kBtFRldf6rN6rzNQW31+83rdAtassjAfO\nONfaI/MP53qc06BEnD1zR5dp6ll3RScq0hecH068apHo9OXERm/AEuHbdTRv\nMS6tQB9edapEx/njhvEg886ksBLAgzGziXDUwrXMzwQEOQK4bSio7C2awu7E\nvt8oJyT2621aE6rxLNEGpAWmJIGqVUFVaW9QRc+lV7y6kcp6qTDJelR56Wr6\niHcqAliRRLZAZljT+HPabhYIFhA8dZ+xisRLt3PKm+kbEiGefGBS/BGjeRxu\nAkIa/tcIN28j9/uni2idCUt58OB4fA9ckkjdUvYmExd1FTMfGrgnTDL7mJyw\nKwxh0S5uz1tHYnv57v3SRkpIOlg+o/9wHFI1Wf8jdEHDZ1mZLgMBS0rfBETc\njMVCjdJpOodDKOsxqj74gk5LHkqJAfikmvs3prMqr/aMoa52ha47x4bUuZ0X\nXVAVNNo1rNZj2dR+l3PVc0coVSoKhUu9BxXSsZ/4mFLYmsdKBX5PazAscGtd\nUj9oF1rzNmy+GAjFz8XVK9vHvILqyiN4NJy/065IUx+P8t73BirKrd6LD2Ub\njV2a\r\n=m8E3\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"e63e8f6d095ac3703bc601c17c4e8993db350075","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.14","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.13","@balena/jellyfish-logger":"0.0.90","@balena/jellyfish-metrics":"0.0.99","@balena/jellyfish-environment":"^2.2.27"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.9.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.5.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.19","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.25_1600432380659_0.13629990684565718","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.26-renovate-non-major-1a25cb64ad43f10756cf40fad354491f4a6e9907":{"name":"@balena/jellyfish-core","version":"0.1.26-renovate-non-major-1a25cb64ad43f10756cf40fad354491f4a6e9907","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.26-renovate-non-major-1a25cb64ad43f10756cf40fad354491f4a6e9907","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"39e7deb09d86d22afe6f3f69e4be867aee81df10","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.26-renovate-non-major-1a25cb64ad43f10756cf40fad354491f4a6e9907.tgz","fileCount":94,"integrity":"sha512-X3sZLzgX85lqXnTScc5SfSX3ZhHxaYbYI1Qv8Q3W5HVedhrBkYIEFtiRJuI8ez19oEl8jt/6qtaZnx/R0Qq3tQ==","signatures":[{"sig":"MEUCIQCOvEFleED1vyjUbARrx0aEkhmvmtjvsc4ZNga2PUI4NwIgXia6p/6Q4eiGRjqgrEKmuAAO+3MVXcRm84nl+u8URv4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":559881,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfZMjhCRA9TVsSAnZWagAAORkQAIFZZMvxw8HCLGHnlKYs\nGXXqi/SfHzrQjR68pteXKHyQp8X4cgYgUXiz7ewKDix0iR03msJOtOdClHwJ\n3IpbFFIyTq33gfCC9Wd+tGjrkbZoDFuL7+b1zQGHKBwHUrT7SF+/uPqxx335\nH0KR1SIJ0sVt1Tp1zPL+55RnbkYESeNkeWxXu0PWtWsHsfoB0ejazhhAXCgH\nDb5DXh9CeGY6+0F2zZlXfQ72eUI9YxzsokXEDJuhjGoB/UBRFcLIFvdgFKFl\n4JnCuIDC8fPVJ5k/9bvgZzjw7sHR45Tz7ZYj1gwnMV+guCT0F9JRoW/EpSv4\njBFKkgdp3MVE6lTapXgr97A0EPGG2VG1d5b3V7dYY0lbAjHTA320B98q7Ey4\nRp+3gurOLKyfd4bt+0LIhSoM4M4NBIDtxrH/ePdYUIylCL5u/8rKAPrOV+LW\nZ2gkl/e0EdNqM3ybiTCmJligemyJhOJb3vy3wyt7G2lB9q79r91UMaMcNzQh\njSHewvVsSyoXC0+bijPiejVDjy79L2nd3FtoRVSeiOxwgmQ4vLNEvvkwJ0qi\n4WrqrY3C1O9S9IR9CGonxCpimJ0tWosx2dnQorenVTI5PKAXH41VrBTVo3Fv\nvhNtpLsC97wpd/QpWVohkU5xiDBq4WjTH25fyxOEnOiX9vnVZsX/Mgq3rrD0\nBuan\r\n=PKH+\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"1a25cb64ad43f10756cf40fad354491f4a6e9907","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.14","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.13","@balena/jellyfish-logger":"0.0.91","@balena/jellyfish-metrics":"0.0.99","@balena/jellyfish-environment":"^2.2.27"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.9.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.5.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.19","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.26-renovate-non-major-1a25cb64ad43f10756cf40fad354491f4a6e9907_1600440544916_0.626532515817513","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.26":{"name":"@balena/jellyfish-core","version":"0.1.26","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.26","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b5689a77950cfc01d7ac305bc3d3823c4613dfd2","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.26.tgz","fileCount":94,"integrity":"sha512-jlsu1V0Nc12jr7uDhH669DlFy5sl+BR3dbnCapx10SnEqduiCTohmL4uVuMo6jF7TI3knSN+rj4CDZZ9uR95Ew==","signatures":[{"sig":"MEUCIBr0YkyehiJxfWtXRW8TkErz9PJlkrkq0Jd+PGL8uBGNAiEAoNSCT1os4Wwg/W+GnhnskqCle8Ehm0CVVnP6lc1ILIY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":559821,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfZMorCRA9TVsSAnZWagAABikP/RGlAaCVtLe+Vi5XeeFf\ndk+DYP499eGO2WT/GrLvHzSgpL1vGMGsK9KlP/aE2BHemHUojf/TNJtBEKqU\nLwjUnttnXy5eF+ZSCRlBp7kf3Sz5YvUNxgcOzOI/T6sA8WC+aOIb22XtHgoR\nPJlZccuXLSAzmCQ1UcpJZ6ScVvYbVShd93PyT5f8mk8wgcBqUqPmQpGP55TF\nZ68LE2o3W1sOA3rEqu1ALm9X9c3cMnlOkERINZLbKC5DIFPy2naa7UYNTk6P\n30ER8bK++aCatGLXkKa0E6/Y3ZeFnVTa9xAq/wubXetKtHOcJbeneThW3VGD\nnvbyCuOxmRrCDDLgji/7TrPLXiIt9idZ4BxEsQkuxDuNVD5umb+LCYLkr6+C\nzBJhG/ayHFhRv4nVOhJJxY8/wriQrTg0X9X+jbhgj6Wu4aOxe3qqnrYap3Mz\nz0zqo18euEvCKpa3Ax4/wvBxWSn/SYMkpT1p/OCK8sDVrtP2UY26pTqnbt9U\nDfUSr0gyk9rzyy4kwAA2TbtOk5IAPK9vQNvxlIT+Bk/Rf00Nz8SmHH+8ymW9\n80mGRFQaqyKTbeTYg3XhmEsnx+cy9mdM0QMnRCPYgLk3L2istowfks9fAjUU\nKkqRdrOrr0TxMC265lSMR7ajicUiHJD+wekm5eP8Yuqzaa/65T4rzf47gCL7\nuKOU\r\n=/fnk\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"c057cbb75f0367aa0f74ea056b370eb4e2dd16ff","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.14","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.13","@balena/jellyfish-logger":"0.0.91","@balena/jellyfish-metrics":"0.0.99","@balena/jellyfish-environment":"^2.2.27"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.9.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.5.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.19","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.26_1600440874529_0.11884582073789218","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.27-renovate-non-major-b1429d5fa1359ab916ee4d4bdcf3cd96d943a47f":{"name":"@balena/jellyfish-core","version":"0.1.27-renovate-non-major-b1429d5fa1359ab916ee4d4bdcf3cd96d943a47f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.27-renovate-non-major-b1429d5fa1359ab916ee4d4bdcf3cd96d943a47f","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ac46ea50f5f8b65ba2a69e01ca5bc502c4332c79","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.27-renovate-non-major-b1429d5fa1359ab916ee4d4bdcf3cd96d943a47f.tgz","fileCount":94,"integrity":"sha512-lvfbjTHG+Xliz5uEb42/ecSkbSrqX0/Rdq1LwBCErVxluviFP8w0Z0OigA9xWfxFOYbR07LPsc5fT/NT3PcUng==","signatures":[{"sig":"MEUCIDpBc2xmpMVMQvYOcw50n/oUC/AjXu1I0Cl2Ug7FqfWYAiEA4AIxyK+4RaDqE3NNYu85C3VwT8CVkE7JBa9S/zdv31M=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":559987,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfZNfGCRA9TVsSAnZWagAAiskP/jQsqk5CNL+68rI7Uwzd\noJx8SK34dIWIdJnKQEP+Kr96Gz36v2VacML/9zsCSW+0sVUhCj9RY8mkWLzV\nWuv23LfAaL14XzKh1HWB2YunjNoxy3wV3mQx5bRc+PkllXVtXWVgsHL2Qczg\nSKEm+MUPsdcRLhoAyWng5QjU/K0EoQQZ+nuRq6XvyUInNcJTRJFjai63SPxR\n3O5S5+xo3zEOwerZ9y4KqQKgRBN80ZDT1tRzVIrRISXMKSFSgYf9XA/UNui0\nTGK7ilP4fjnCcrDqZQEXxdSAyZ8cno9JLdnmprB1D3uK9X33PhyKeFrLLCLl\nmwrtm8+994Jy70CxtbAbr4t5kWYIjJfpVMTN1gaUCvYXF/Pnps5E5sncra61\nXr3kNXeZ6s522fQFm7zjCnzWozuuFsngVKokO2f+t6uPLboEAleM5gN8pkBp\nYIn3d6si/4diXmlviLaQh9Bcqn30KGSYfjgSTqwij/K/pefqjnd0hHXNI/u5\niRaxJU+vjj5Rauow/K0rOTYeCX+B2p0XFXIchHgB4gMBDepau++AN3EZ2aoW\nsQbQP6/94bmc/X9lenk2+P8WfnH+L5jdAxGfPwtQWEBlM92MaVJYuGkdduvq\nJXcPgN/zUxmfTA5pHVeLk81lIgOWGJvaIVZf6/0gBKVTGwhpq1y/JI+b70je\nTROL\r\n=9JlE\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"b1429d5fa1359ab916ee4d4bdcf3cd96d943a47f","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.14","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.13","@balena/jellyfish-logger":"0.0.91","@balena/jellyfish-metrics":"0.0.100","@balena/jellyfish-environment":"^2.2.27"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.9.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.5.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.19","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.27-renovate-non-major-b1429d5fa1359ab916ee4d4bdcf3cd96d943a47f_1600444357544_0.3748947632348769","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.27":{"name":"@balena/jellyfish-core","version":"0.1.27","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.27","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8fec0a9640ed639157d6fa9452cb7ed74d909099","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.27.tgz","fileCount":94,"integrity":"sha512-VjbhJFtikAP37ZmoSIh7SVItzyQAlmmstx6Pi8INOGUjUK20Atx8mPXQF+ui5+hxshb+yavTVjV+37MXBd2tKQ==","signatures":[{"sig":"MEQCIAVWXKyHFlUr0CCRvt36PhozzHBwvt570GJm9AEPf8QnAiBixMRrUh7oioBwb495NbTk6YR6EbUolGluK5EKnVWCAA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":559927,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfZNkYCRA9TVsSAnZWagAAlykP/i9LhZ8FTmrQ4Gbnd5gy\n5Z6eDkVay2Sr2tkV+/zK+5dVenk8qHo12ti1YFsnlCYQHqnKKMOCXbH1OsI7\nooSEGubBnZZ1LoTV1r/RKeElI5AO9GQtdtiE3HzSwRVDcR4X8r0qTXwHK/W/\nropDAfFNnxoei8tMsdCcuOgvSiMZr9EXde9A3/XpGEJW6wxachoueeC+4LzH\niOP3UdpqW5y0Y8m9lUb4GNxrrB6sknrz2/vW5A5+iIsq8Z84tnOyTZouHP6T\nOymq4GgvPFZ5uBQgnEYH5vijDx1cLiJeI0zSH/3fp3VDzbs0hfrqfOG8rFGT\nAQFXUouWf4fDLNiqzV1hAiLbAiNYodpzifEOqYaieSfNsaBrA7GXJA0it9Hq\n+iRPR5NOl/ZH8QPOaUYoHpZo/JyCPrztofFv2r+1rkUOGn6iYb03I82wgaPw\nP+Lk4sptsOyw2HmB+ojofLkYfL7iyCost71cpfrJRpasXPKQJUU1e4TfvUNc\ncRuhMUNPIPfIxtVgCF185VPl+b9uZiCLSOTVq7gmElb+ghAjx8R22JeA2ZVY\neumRJjpG1Q0HxkhSpqFA8hBvoEKzAldRcZFeQREgb4HcNB4Tbr0yOMs3Rt/d\nc5MhalAub/Oq8DR3ZkMbKS8b65Ydq4sZ3qmm7gZvgmhWMNVkTF+XfiYr8Un7\nhFA3\r\n=JEmF\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"8c30e0313fe8a22e1e6d61b4d61896a90daac0b8","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.14","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.13","@balena/jellyfish-logger":"0.0.91","@balena/jellyfish-metrics":"0.0.100","@balena/jellyfish-environment":"^2.2.27"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.9.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.5.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.19","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.27_1600444695658_0.6515421197013285","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.28-renovate-non-major-604d11e3380052f47c0388a78b879853d641e429":{"name":"@balena/jellyfish-core","version":"0.1.28-renovate-non-major-604d11e3380052f47c0388a78b879853d641e429","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.28-renovate-non-major-604d11e3380052f47c0388a78b879853d641e429","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d0a29b8667c8e848e904a9549cf1ac5e0e0f144c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.28-renovate-non-major-604d11e3380052f47c0388a78b879853d641e429.tgz","fileCount":94,"integrity":"sha512-4iTG7XQ3cj5izLCyaRGJiKhMMdXkKzFXlnLxqmWVbsxRP1K3ZX9C3teJKJXakNQl/ubOeRPDArynUegMlazTPw==","signatures":[{"sig":"MEUCIQCbBuuZkaW936VLvuhAO+NurCUqpXWFiQCUTF00BMC4egIgQud4KLgpZawkfLr69u0Pj9npRw7FgqsODG75zJ1sHys=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":560053,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfZSRYCRA9TVsSAnZWagAA3AIP/j6VeXozJ6GiqfqpSE/J\ntYb3hebBhnZv3t0OHKR/LESGYIkojzD3S+eqW7MmPP3hv36UV8//Gpf0L3aV\nL++xeOSaAJsRiqU1gE4ID/+OVHogXjpOLwu2j0Z9EoW/ot0zhfKtaqfUuPAo\n3OKen9cJAXtTDED4rmXD8cDwf3hAp8HHySayDIv+A5C+/RcED6W+mdSe7mXK\nJked3gyTe5Lg8KcQxmudeuROeawlAGHUC6RfChDGbHf32TJsLOeT+JzAtwHU\n4G4ebzMkqkgRGH72/RRqX6v6oIyI1Cn4/14kfQKX406x5Ry9wDytDV4uiqcv\nJ2mkBI6spohdgZdLam1kyq8VE2Q1SyybJ+nagx37LBq6radowJHKb8TbcnHW\ne6/TlO8vrNKtz5wlw3dd1MCoRrXo2jlR4Cb+I4MwpKVdX/WVP03t8VnXphjA\n7mBc0SOipzqkUkOAI3n2zksaB4RS9VNZiqslDYEF9c3QJYbMi+9yAl0ONO/F\nkBJS8SVuPO9V6K3mdTSzz7+KStdScHTaL0UCmIcrVInOumkMhF8Xp9qtUI2P\naM3WEUWvd+1GHr9+nn6bw2F6jte4RC9PdGx8VQEDfTQANqULVzaIp8BRME2I\ngTgKxuZdEossZ73E9nPEkPuoG6cEKyv/3qxMQqVgPal1Kw1J91K+5FhhLiXx\n1hkH\r\n=Ocpr\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"604d11e3380052f47c0388a78b879853d641e429","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.14","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.14","@balena/jellyfish-logger":"0.0.92","@balena/jellyfish-metrics":"0.0.100","@balena/jellyfish-environment":"^2.2.27"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.9.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.5.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.19","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.28-renovate-non-major-604d11e3380052f47c0388a78b879853d641e429_1600463960043_0.19497658925946704","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.28":{"name":"@balena/jellyfish-core","version":"0.1.28","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.28","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3e2191327de240c52dd1e7be2289353f3f0c2b9d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.28.tgz","fileCount":94,"integrity":"sha512-0x/MVezIDNe/CDiPTTGBpklXKDBTr7U/HzIUOJ/hB89NEqG9yjF/R8W2vG/c2g2YCuEbysInnG0XyV96S5krsQ==","signatures":[{"sig":"MEUCIQDH84JMOwLW97g8UGQo2bnj+YJj0CJe0fLo6kGIy7nMAQIgFkYaQ62R0PvsGjRiA+i+3TtoPovcpuQ8rWJ2Xh/DYm4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":559993,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfZSWmCRA9TVsSAnZWagAAVwoP/1CukLve3WYoVcDAh0MB\nZx5pQnjn6bTmKtAXx8i8Ocpsm3ihXxb7lu4ITsFSSPdEWyAozW8SftTgSmbo\n2HMTXftT7/4pJahQHHAkn/fTUiM+uCh5bnNJam/uHnSX8crXJShsm2YL2in7\nXdVkLH3DUaAQwa8Wn3odBrLMUkoLIZnq6Mp4sOEc8vF+J90GKXyh2m7azmVj\nku+IjGpceb0orZykeRm5U0OszJXAS1Lcjjm2Eu8ybJhqtrtQY/m3qiaDYRBj\nB+O9GqElapw0uoBHn0vpY8qPvrfWZNP0159Jyct8/JUBd5hcSqKY+iKQtdS4\nF4Ge7i1ZdPf3fyeDDp4eRDN6YWD7wnW6e+7I17ijU5ou+eRO5QOJGb7+iJEs\nfkXJhAp6NalFW1YyZicmdIkAY1THBuUiFAEbq0tjbEqqmQDk+XbkZLtRC/c3\n0QGOf32nqn8ISyFu6Ovt9WyiOkWcd5qaW/2FTu4W150fKz4EBIxRtEt2GfCo\n1VF5eGh1YuFhDWQzzk/tdmesz6UCQCW7duPWPTtiBkBr6elQejPtq/Ay3Gwf\nC3XH4rJUWKeOfuuAWE4bX8wU6bdesREa1y8CsoaKOF4Ye0ChxSr4RnfkorZ0\n+6vNnBQ2Agxvexnar8lVL0+2TjHep5XL8WeI5bFC/6oy8IO1bNv5y3sLxDfp\n/gWB\r\n=r4wJ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"a1bc848771a954e6a2ed4871edfefd046726bcc2","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.14","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.14","@balena/jellyfish-logger":"0.0.92","@balena/jellyfish-metrics":"0.0.100","@balena/jellyfish-environment":"^2.2.27"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.9.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.5.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.19","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.28_1600464293690_0.7968772069510837","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.29-renovate-non-major-c23c318509b6e745c05483ae6454d148a6367aa4":{"name":"@balena/jellyfish-core","version":"0.1.29-renovate-non-major-c23c318509b6e745c05483ae6454d148a6367aa4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.29-renovate-non-major-c23c318509b6e745c05483ae6454d148a6367aa4","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b63f51fa126796c885a08c0d2f0ba3eda2e7d164","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.29-renovate-non-major-c23c318509b6e745c05483ae6454d148a6367aa4.tgz","fileCount":94,"integrity":"sha512-vIB4gOjBQi/n6JDzROJ+ZmOAl0wF1t/ceJq18iFxF6zNQM8RmJssOK7Opn66CPjJ/Dy+H2A5baV+1bAT+HhQEw==","signatures":[{"sig":"MEUCIQDt0LKW0dVxAsctWatI6HoctRFGGlrlaKVl62ugJIBrHQIgNQvigt9aYsZXNOUFkNbtsKEk6GW4e1n+pkgNUPpZMPw=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":560119,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfZTu4CRA9TVsSAnZWagAADuYQAJTsDHqE2y7xiGqK5yvU\nj+u8kE89aKJzsFtQao8q+RfbkUm/CD6d7X03oG94BsDaKIAEyOaR0bWkTBry\njBe7+cpiHOuE6NedaRZynTS8NK1VHDsgoJT/SKc4aluM87AjtAAlmB9JcYQm\nAmsiDPkxRHzValac+I4OviUcSpjD61Jc/ZkSnacoO4yyqCMMAA6YKghmejWm\nX9uu8t5NhK8Tub832B+h660QUl0T1AavK//oDz6dwPeFgapFVl5x0VLrwYjF\njwAuQ3r3g43Wswucg5lmBqwsQMy7aU+sH1gazhDfaxdPVKDcwqyCprjUeQMl\nJF+YDYBZXPzhetNkRbi9BJocxvo6egLwZ35hoHGG3OHDzjb1G3yA2GOqbadQ\nZyYi0+iKGm4aOygzXMPrhRAvkUe7z0vKAgM29yKRT3rJJ6siH+KFWMwx8rCM\nbQrgedehAxIZASRPLS0JgdiP8a2W80SYJwd4UC0gkRaUIHESJnHi/YzhiYM2\nGouNr35QYXIawgESFkx/XBu8YxYk6QQqHMM+1Y1p9X3GQ90s0TnWkDurLN8/\nyVFsw38G48j7DcnOjHwtcqxsZzvQk5laTZ49XTMdNQDuqLYfbkFULIXUa7Ac\nZg8kFnQ00PdcOA8c9FwMjj9uacMYUP7W8QhPjTtxx19FWv9VpimVNBtn3J5+\nGJVy\r\n=ewi6\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"c23c318509b6e745c05483ae6454d148a6367aa4","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.15","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.14","@balena/jellyfish-logger":"0.0.92","@balena/jellyfish-metrics":"0.0.101","@balena/jellyfish-environment":"^2.2.27"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.9.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.5.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.19","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.29-renovate-non-major-c23c318509b6e745c05483ae6454d148a6367aa4_1600469944366_0.3834084248955474","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.29":{"name":"@balena/jellyfish-core","version":"0.1.29","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.29","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"47c0cf04bbe4b4d930862d94ec65025f8d3699e1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.29.tgz","fileCount":94,"integrity":"sha512-3c3Ulyh+Q+Om4OMhsKxwEs5uOAjm2Z6F4FQglJpuM86159vwjouV7MSQUiKJMRMQbZi7D7YufUe5ky3DwGsdfg==","signatures":[{"sig":"MEQCIGpZkLD8UZrihcaGgwncfwB7iL4eOaBEJR4CFHt+9oZCAiAQ/umwxs7GGwfwCHvZuR8ihciy19dhiK7X0egp29djVA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":560059,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfZT0BCRA9TVsSAnZWagAAyf8P/2BrMk/B91vk2MvImFpr\nqctyZF01plbxfrsvtLe1tw/Hj8nyjg2NPdGqJeZCoEjfj0sSPUALH9igjhKh\nTrUh2cPKD0QXc9F9xR7CcJ3eZs4TMhVj06Xh/U+w1quSnYirDdh0BHBx1Dff\nlA/zPMGr2FPm3DbUrGmHi83JJDNGAbjonYX6kbpykcSA9UWvlh+Jd5fl1+Pp\nQ9QwoBhwW/rJMkjaRBFbPrkkHKPt3Gd54VSrEbYYlPh3FDGB2b/NaXyTpwl+\nbu6A1y3uGU/u9ulNnrOpHn4xd0e3By4pOy4LuGb4NKpeu1Owq866y4daN4sM\n8S0qaZamh2qWzHuYImrD1SpjOV3DyJR4kTWZzPROukj2gUbpvfQAmx/P6E4H\n0sE9eBwjf3MxzGW2RqxMRtju3xT7IiJEtHGqh96OgQOPPFxYJVQ5hyC/uBKe\nH1AunJB+lW1ANMNjutw453shk9Ot5qhNhoRM//saSG6YEPAUWgGZAIX1ShMR\n4B2A0BzB3QmnuaffTv/t2EK526JbBRWOqehL0nWiXTmDcXebp6zxCooAm7sT\nQjxsOt7QruxdQXQp/zTrut/IxKhRq5D1yge6IzyssWiQFiEdgXmZFJ8HKOM0\n6Evb1m9HZh9X4VduT2vr6TO+pc2FK331RwdqA0yGG7lJUPFKMMmCSkuf5U0i\n73pd\r\n=Yc7p\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"449470cc43014a60e35373f02c94bf5c460c0056","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.15","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.14","@balena/jellyfish-logger":"0.0.92","@balena/jellyfish-metrics":"0.0.101","@balena/jellyfish-environment":"^2.2.27"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.9.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.5.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.19","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.29_1600470272997_0.9579495426179492","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.30-renovate-non-major-d3de771361acd2e6cc1c7e2e501c5a26a0b92d04":{"name":"@balena/jellyfish-core","version":"0.1.30-renovate-non-major-d3de771361acd2e6cc1c7e2e501c5a26a0b92d04","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.30-renovate-non-major-d3de771361acd2e6cc1c7e2e501c5a26a0b92d04","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c44f2eb22092e975f6b03a534f45e987e711e017","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.30-renovate-non-major-d3de771361acd2e6cc1c7e2e501c5a26a0b92d04.tgz","fileCount":94,"integrity":"sha512-oL+XS/fpEaC2IgOyCX2mZsyOwHPLZ1+kOtLAvCeuQ14GN3St8qV59f+ij0cu79vbIeMuCOWGkgK1q8w5wC3k5Q==","signatures":[{"sig":"MEUCIQDDC+0wiH+xMK0dSM2SIyH5tYigO5q+RWkxeW1otktOsAIgU2IeU1gZCXp/IL8HeIWQrJ5MEBgjZZWB1/KCC2Xm+4I=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":560185,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfZzvjCRA9TVsSAnZWagAAv/EP/RjkYv2nPsj2gkEiqC6x\nh5EPbnSjjxRjypHh2Q2rjTnlWq4DhIYjjpERAmhoEGP4P4YwoXFCACd9EH/G\nHgB9hkqTNwgHPS3EFft98zI7CIiZx7yvbp0A9WVyScdmD/7eQ7QI3FpxDgaV\nfZ4ozDtHGzk237N/Z+7pzoOll4S2T2SFy+kN+/H8Y6CjExqc9S9JfeRZBrwJ\na5xw8CjecikbrfQVfDHb6q+A685ZkoPYazsJSj0S73kxJQqKQyWA3tpyhlcd\npoxjDw9QoS9lUuNIQNcAYPI1s/n+Gg6I2J40B/d8Svk3x7nsgnWZNWLgkOTF\nueb6VIBRVfisPkNkiCwWRnJ3rWWGKATTIETy15qIuNlrQrHse7/l74tWj836\n6X0E8DzwU7ozuNGo2hm1wdEt4xA8fs1eYIiXB6TXrBIxx+V7WXU0e6bo1oIS\nONqbdfVwuqKmqemW86JkfkfLXUX/4DqQB/dXe1/WdmBB/+hJZMnsxkfBnF8m\nyOdu17B/6Db/JoMOlBjlkP/gKqz1TgGQl0SHTKgaEJld3uXFyek8DGOgIiBZ\n2boZGgxVW3qfJMvaZeaK1GYIV89qXyk+w5MGboFQX4aZA5SrYKgsY58Er5yY\narSARnQInp3u5kpxwmYtNTzfJfwz0wwu3qANvS9kXWz5IM79rx5suofpUH/r\nKmLj\r\n=h1Zw\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"d3de771361acd2e6cc1c7e2e501c5a26a0b92d04","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.92","@balena/jellyfish-metrics":"0.0.101","@balena/jellyfish-environment":"^2.2.27"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.9.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.5.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.19","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.30-renovate-non-major-d3de771361acd2e6cc1c7e2e501c5a26a0b92d04_1600601059518_0.20854441436634108","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.30-renovate-non-major-19de51434796f1ede49660d977cf38bd6b3388ab":{"name":"@balena/jellyfish-core","version":"0.1.30-renovate-non-major-19de51434796f1ede49660d977cf38bd6b3388ab","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.30-renovate-non-major-19de51434796f1ede49660d977cf38bd6b3388ab","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6ec4a902c51415a4361dafe209368dd0a46ad32e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.30-renovate-non-major-19de51434796f1ede49660d977cf38bd6b3388ab.tgz","fileCount":94,"integrity":"sha512-dSMGyyEcT22p6d9EyH0ff1V98KH8hsxznaGNh5gjLWvd1eGWR/UUadtu+17MlPjldpKG/RbUOPtdjPKppgYkLA==","signatures":[{"sig":"MEQCIA1e8RDkNDBUe4RA1vqaXkHezsDMyOWNhGYsC7+R6veQAiBCWVy+fVbFGxfHJC5ZVootTfsghM4bXvNMbXsCHLyHdA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":560185,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfZ09MCRA9TVsSAnZWagAAm4UP/ixSFk9tb4x6BeZiBNwL\n+2WkPAcTF9EIhHeuM4ieThIEJPWkUKBgNWUY8lCHKCFx4mHVIg00oLH4oh/k\nhzw9We7FATCAcv3WCGQcbtBS7N8ypMGC2gvyO/a3fdLDle3wBcKI4s8BmWcQ\n/UL4YCyRF60gwRhnPMUsSvv9lMIFjE+btlnHxBgGnpbCzWd1P8kmt1ACzMJJ\ngxW9nx/UmOfok2aJo3wQw0E3qzHVWM8QjIodDBtqlx7cBD1cZKgr8uTT9slr\n6kJy27sHoFvX9jpEOfHREsSB+MXsDWyphbwgLW9MxUT+vzl0F6WrslnHXCA/\nqbleaDsZ/8gne2j58ZFrNwDwja23mkvpguEC703gEpBri9hyT/I9TSQtRX0e\ngt2depjpwv4EaEVbLFc6QJxLd8RHSGWcKzVqRhhO7iTD86RALJwI848jgSKx\nBya1dLlDE1/IDYVkQoD5hYLrRJ6MrPVKYksnBLWUArI5z6H6AHL73h5Qk/wW\ntZnCdxCWgKvD5gsIUflFV0tvt/Eqvm/Reozgw4W+LhQNiKF7wqIRy5U5srvS\nqbClajySfqSrbjf+9ooodkNruRQfh7AvMojFlp664ptdlVB8LaUkKJRPvIlR\npXaT3aDLG+cceRtrVL2jOqEjGTIjVUQt8j4+tOsdWBMgUd2p61aCA31FP0qI\nx/kV\r\n=zb0i\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"19de51434796f1ede49660d977cf38bd6b3388ab","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.93","@balena/jellyfish-metrics":"0.0.102","@balena/jellyfish-environment":"^2.2.27"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.9.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.5.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.19","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.30-renovate-non-major-19de51434796f1ede49660d977cf38bd6b3388ab_1600606028222_0.09769442915470838","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.30":{"name":"@balena/jellyfish-core","version":"0.1.30","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.30","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4ba0dc688e664d1cf14237456826963ccf0a3412","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.30.tgz","fileCount":94,"integrity":"sha512-UUEYFVS7Z7I8Yd0Bgl+vCNOJrokCDvFogLmOIgFPKzIW7H+yKRaQ2VAXvf/ONtEYbJyjmlqKJE5tApmaeCzWpw==","signatures":[{"sig":"MEUCIAegIZ+2Qy6QxqEYUZ43Bks0kBI/gsb7c2aUmO1kWCvHAiEAzQsS59TdX3bNLi2s71+t/2fT5MQBZ6RQGj81HhzwPAk=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":560125,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfZ1CMCRA9TVsSAnZWagAA8kcP/33WkBnru87B71jNEq8X\nyoL02f6M6zKKNmXTj6v1lyuO4UFISJLuan+KhoTwxMpo5vjtXjaSIQ6vwc88\nKOIxHH1fl2AhjzGkCj0gjrXpB5CGkN4kCuy9JCZErr57oNTHu5GEFfqlyxVc\nzY5e75VkBUh0ZSnZe4ePE6UPM6BHMDiCRNYL4/IZEXbvsf+UizVTjEOWrOGp\nYn5oqMYrlaGXiv3RMR0AQcwFGMt7xXl1Cv7DLeLUFyGxegbu7W1sXzsp/AMh\nwlA4S8JiIB9Q75fYWMqqrVArBRQcvmgvcUgK+TM8+FUWNq9XVIahY/WioQkW\n+4M/tm3cAJZY3hwkGGLH3cDXej+FwHgcwZr7YBjKCFMdVdjM0xeSAHAM7PI3\nlINJ6QaKbS8zghkHD1hn5YEzsQtvW7nHtGEmffFisiOVp9tQ2TVL/Sr8yNko\nmrhlNz6xXtVLQRJF+9uzhYbxG4Lvi20GsslXs+36iXEDSO2xkK6u+FipLFhB\nmSA5L0UOlU2tRMEGAasXTeSjh4Obu6xw93VTXsPY/TeOz0U93c1LDxgguCl3\nDc7QOrmgtkBjuPfdYLR2bAmpei2xPzHuCWmjR19sFLic+9Tril7lbDKPrbnN\nJ3BAcKGGpnoF+EQu4qAtcbyflh8X5qRyT+2TP/W+4IXB6s97hOfIWRr/ItqK\nNPx+\r\n=WBIj\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"5df3e4a01c75f1cf60b6242a40daf7415753a37f","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.93","@balena/jellyfish-metrics":"0.0.102","@balena/jellyfish-environment":"^2.2.27"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.9.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.5.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.19","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.30_1600606348417_0.39067031135051455","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.31-fix-aggressive-optimization-27ef63afe853d19888332aeac9e8501e6af4c853":{"name":"@balena/jellyfish-core","version":"0.1.31-fix-aggressive-optimization-27ef63afe853d19888332aeac9e8501e6af4c853","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.31-fix-aggressive-optimization-27ef63afe853d19888332aeac9e8501e6af4c853","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"fac99866bac6909832dede622ad94dee0c671755","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.31-fix-aggressive-optimization-27ef63afe853d19888332aeac9e8501e6af4c853.tgz","fileCount":94,"integrity":"sha512-ffz3GH1Hn6SAnAVqvpRgWxgm6O3vzruWBggvf/YqKrOvTluNVyxxcgrR5cfPI0aTc3gdlHf6Dswyp5Ygrrn/QA==","signatures":[{"sig":"MEYCIQC5GEFuWAK20NXoTxiLxZLj33tQc6OXyAJKcfx+ZFLMgAIhAOD2y8wWkzqA9aHLgVMT9vkTz31lY70SARZ2ogikV93P","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":563585,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfaN8aCRA9TVsSAnZWagAAcRUP+QGYA+4UW6ibiBw/uDnp\nQucTGfKXuVrPTGmnxp+vbaFsGcsMzVcDqyqqmF62g7ZQ6N1aNGnv7Guuk7CU\nUVl+C3oc6NE2eULXApBvTOg0n0rR6+FueVE7FMF0ODBucMjArfa1K1M5+1q7\n6n/FrpEIHIoAMJHEef2/KuHTj5rarg9gWMFNM6jK4lCF/nMWVmYHFl44sNfh\npG3Nv3nu0KsJxnugt7S5c7AFQQQVqIs3qkUXk6T4e5U2YVoT6sPvrlD5rPg4\nWXmIefUUgEM4GvvJzOiP8XMD/sLYoSjZQ7H77JdZfzJcmz7P7fErkUz3Pt0Z\nS0oz20AKuL+2UM4gnCgUDwKw2zPOZ3y09n/kLyef1RuMUDxqCnkKGIBbR3uJ\nZBHd4NL0RXMGRU1/v6TSZmQ1W4SQn1UMPV2DviwkiPzb4LgdVt3rPdW0llcu\npAA2k6EnJSTUKoNUyUlSippBAjoScAjIEO43MjvEWfW1NGGoLQZWRNLVMBl3\n+h62mNqwINUdfJ2MItFn1VO3P10AG/U0Cu310x5OPrUdizDKdaVm1tB++34q\nHf6yGZnNJa3POdNxkWIalHzN/yKX5RlMOIHmP7IyH7wJ/+OJ2IGoqeu0g245\nCiFMAYkR4Nis23rhPHb0XcWqRm/toYNoC2Q+2vtgsIK7hqO9ni5FaymYkquf\nZ8S+\r\n=URo6\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"27ef63afe853d19888332aeac9e8501e6af4c853","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.93","@balena/jellyfish-metrics":"0.0.102","@balena/jellyfish-environment":"^2.2.27"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.9.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.5.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.19","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.31-fix-aggressive-optimization-27ef63afe853d19888332aeac9e8501e6af4c853_1600708377407_0.06818450170474732","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.31":{"name":"@balena/jellyfish-core","version":"0.1.31","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.31","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"736d842706bbc1d1a0a6441793f871c5f3dbf75b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.31.tgz","fileCount":94,"integrity":"sha512-UhAEbQsYWpqFqFqo9lgL9JEES/xmu/0bG/zfNGKXKXPGba7gu9DCfWavMHFNtCBl8YCvQwxkLaJIppyFJg+n8A==","signatures":[{"sig":"MEUCIQDgaoGVAw/BUlvsIuZH+YXhFxfc3MsZGH2b8ju33Mf5egIgAYOJlOUCR6PMM3vFSdtvnJICS62cJMVU46gNG46y07k=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":563516,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfaOBcCRA9TVsSAnZWagAAn/MQAJn9Khn4fk/dJUr35prO\nxTiyljvH2hwdI4sC7BWtcBX48OOsycSxfiaOPqM4oiDfnWSXafqXFhtQbglS\nhGwCew3naXYBpWNia1T64KzmUZlv9C3mAfXYh9pGIo9H38z6C2R8wBHsZ7qo\nx28l3iYZHRYQhs7I8E8vroEwlGlH8fA+mZmDKwWcpzhnP/zakdFnwSscjM/y\nHg703NqHaT/hQPb5ruUMUeUDmY/cPEoIsa77cJb/RxNglUJWOf4qW8bLOgI+\no9i7lOJgH9q79T7bB9b1tI5MHzjwAyelj1HhOn54H0dUv86EU1tpGpu9fBY6\nX3Glfp6UvxHsX/+MxI7PxYq4zVlXLH4IEomeR2UT7exmMVLDEg/nSugvnsAj\nzdDoKEGH+Etb/qHNgL3fXpjfnFTejdUlaUvcyElEpJIAaJEuxF3xBI4IWHof\n+BV4J5/rH+dRCCm/u79fQQjdSdsRo6vEbHEGFi67CZ17ysBlQTVD/OplKE+g\nQsUG1XlRZT4NIrthcERhBBlIcEhVeOTn+mCeTKNo7HjAitmklDBz7lioFB8D\n5+BXBCl8O3C5KQSgJc/UCsCLt5Gdy7q8HzzXIHjcCRAhMjurfygBq3FOxIEl\nN0qlAG+B7xs/BTtzkuRxrijqY91mYixWsUj4sKBjsNhKkFvGcpDiumaOxNbj\n8Mst\r\n=T3HH\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"f5334f44eb66a714dffcbadacf0373cec4623e4c","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.93","@balena/jellyfish-metrics":"0.0.102","@balena/jellyfish-environment":"^2.2.27"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.9.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.5.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.19","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.31_1600708699897_0.49502336272176284","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.32-renovate-non-major-e36671af037db062e2b16dc25a55126ebe33d608":{"name":"@balena/jellyfish-core","version":"0.1.32-renovate-non-major-e36671af037db062e2b16dc25a55126ebe33d608","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.32-renovate-non-major-e36671af037db062e2b16dc25a55126ebe33d608","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c3c44c741771b8fd8e6b1dd4000e3dd21d5b0555","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.32-renovate-non-major-e36671af037db062e2b16dc25a55126ebe33d608.tgz","fileCount":94,"integrity":"sha512-r7fywZKsSQJhpCLTU8VBolAJDRznIo2fyxO38gv1gjAZrxOYYP6LMIGeUqDL8yIOgXR8V9fGrcgzr/eIfAYrrg==","signatures":[{"sig":"MEQCIEQqxCE0kMvevy4su8A40ej6U8Lu5DeoNVh9ZksuAqpTAiBfXzJHj+Sks/NVYi/fenzzJTdT8pYdKRuWRURIzut/Qw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":563677,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfabyHCRA9TVsSAnZWagAAN1AP/1Pqq2M5A+BmTqsC3l7m\nRMsvZOM8xnBCkcdzo8FrLdiZ0xXSOYb/1kSLt4LGUJbOI6kpkBux1gvE5mc3\ne62RmDihpuH7lW7hAGfj7lLqkXEnk/tp9ywqJvUTn98JqMHdiJYgi3MaJli8\np10kb7JWrWAnX6todZ5QHrkszPbCQRGGEehef/fWhC9lZxH55cqteqb8pzRL\nQUlH0aTV4s9f+2Wow9aTUf+sY9QmzoXJ4mZJ8zEJe8Ng9V6Ujle1Ir8EHn61\nxjitdh6BR02rj6b5Ys/D2Pv+sKt5AU2kd4SdowuV7u5rGZk2rTREr+OP1A7z\nh4OtnanM/DACNbz4IDToyRx022A2E7EZadvmNo2uXkumU/HfOf2zYJTPg528\nyupsNsjyVNSsIvqo9a40pUceZMPyU2J7Du4cMrS5uNLJKgZ3rHlWdfW2EAdN\neuUuTrW7rGONBn+OlJBVXqy7T0I1i6/0GwMXmEnrlmokYBz4FNB6K/H8wj7x\nzt+m5ho86Yv9ZXPpmoqbE1WbrBNBbWLAPLbP8r/u8W9O5bsTjDkbHfTy45f3\nedPjPi1Pw5B07Tetv9uUYhP3DwoMQsnUgJbBaHC8YAqv6ZoA2lM0jnKxtWcl\nHd/Ycan2yAr3yST4ofF1GFRIiGXqDLnJ2hvIk0k2wpnjJccGhOAZb/xoK4d2\ns+PL\r\n=xg1x\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"e36671af037db062e2b16dc25a55126ebe33d608","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.93","@balena/jellyfish-metrics":"0.0.102","@balena/jellyfish-environment":"^2.2.27"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.9.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.5.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.20","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.32-renovate-non-major-e36671af037db062e2b16dc25a55126ebe33d608_1600765063314_0.7677401161778676","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.32":{"name":"@balena/jellyfish-core","version":"0.1.32","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.32","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a204867685988d05439573afe0304286286417e6","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.32.tgz","fileCount":94,"integrity":"sha512-HVetdcsFYKr8SQ6kbzpsMY8GgzuAmA4vtJsVGFpbz6WUna5V3YGzZVA5GRUXRV4+RN4rOmcUQtBcmW8exwclJA==","signatures":[{"sig":"MEQCIGX7m/B9jZPCojrGFodExQIdEM4yaelmXpe8dD65Bm2PAiAT2bXNoBVZSGIXG5nd2tr5tUcC0HRMWoCHr9Fv99ks7Q==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":563617,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfab48CRA9TVsSAnZWagAAaL8P+QCLLKNHBWcUeKA39ELt\niITnQCB04KUr17MzonbwDJi5qUCk/udBFpwXUL7OdNRNeRM5LGKHbaD28WHc\ngeNt1ShmgvX6Rw7RjpM7px+RSCPVNlCH7HOOSWHgxVw/cLAgf58rSFAheVg7\nqnvxjWKjaeY+1OVHXj204q8EAjNq3Yq7XdmB3/sUbOHwgUBrzEKqAzrWs7fN\nBdLNPKglmNu55N12HUjk7WUA/oEkMX5RHulFmoWEbg2p/HBR6+E/ATZ+Yutg\n3sxPCiA6X8gYtu+2YedPh6KS9EgkLPg2sXINBxvLZp4ckaxMky2DVZC1R9RK\n/U0Zn3X3o4JcF2Z3TDm0oQ+RH5eQLs91EbaFI3BV/4bC5nhBfZuFzKog3nM1\n9KXxP+hglAc+4WztTaSt3YWAcC+xNI+5JvCF4h+7lQ185vU9wS4ej41n7VvC\neJsYtJ31ry1JSlw41WU331YEXMnuIEcVzrFu5+4n0fGoVWTX3ceszs8fHuo/\ntqEbrdQ5AwmnwPvFxSbTvTRjw4VmsrTcOjO+80zlkWd3Rsf8tkfbaX4PMIK8\nUG5GmDrLh4+LL8sIbGIuaKIdaBH2GayWEGbw9iQtRXwS68Ul0R1japlh8+BF\nzjX6iMB6zklW8zE4zUauWo5yRzFMP5/B8L20II6m6xatvHWoZbPW6a38oks3\naIwO\r\n=y2f/\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"765e539eebad341764c789b0ef3ce137bb162fbb","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.93","@balena/jellyfish-metrics":"0.0.102","@balena/jellyfish-environment":"^2.2.27"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.9.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.5.1","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.20","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.32_1600765500513_0.3397682062101257","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.33-renovate-non-major-649c3c8750033cd4d47594e9a209a2b1601bac0d":{"name":"@balena/jellyfish-core","version":"0.1.33-renovate-non-major-649c3c8750033cd4d47594e9a209a2b1601bac0d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.33-renovate-non-major-649c3c8750033cd4d47594e9a209a2b1601bac0d","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"24068a9367b73090b5a2f508440fa92a4af8ec18","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.33-renovate-non-major-649c3c8750033cd4d47594e9a209a2b1601bac0d.tgz","fileCount":94,"integrity":"sha512-xzh+lyVz6vncGwFMqNMdVzSgcZgOb9Ld0n7Tc5P4s2HjN9uSqg7pZvuRt9pgiZhyn59WBNYrXvXkoIUOWEmggw==","signatures":[{"sig":"MEUCIQDzAHfqsMKFzCiNr34AququgLRvrlLCEPL1KN5XsPmWDQIgCOMFSS0hMUSKcFRF96vEMgIk8g9+NuMqbrVHYNnFZzM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":563775,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfbdq/CRA9TVsSAnZWagAAMrIP/2cZ+zwLLNy/rRie/hfE\nW8XhjmST0DLln9ReozBOcCs9mvdN8w4RTaVZmz9oNFocEXRKRWJDeasD/XOX\nQzPoHqjJnGqBd2XO4iWdb0p7f8llCAvNKLZMDkuh4OGp0RQUU35MYMNAnqeD\nbIqAd4sBgckesFM7SQ0QzglU4e0cBys/gNUnyokl/TssjGDEb5s0vkUIxUoJ\n7FdmbEF3G4iOSRVF3k+NVRjQHDjXqjBimRYEJ117R1enV/qfn43HKWmXoajm\n1Qul5NoDzz37pq/D33+eKMlyGV+dwEZ6lzBEZdCeBdd1dEy3NQAY9StxtoQU\nzYqC1cV8QoEERjQeUG10kM1R9fIMDXvKwcswJAQkEb/7wfQCZxoCeCJm1NeF\nUFaPk3ojxjh2ExSGSZm6GN7u4wJOmmMrIf55QdyboFrntnFUvOckSWn/p2aP\nrzbMKLPpYiPXtVttgCS0DuzrovwEUZPbc5rsWsNJJ1G7tV9P1ls5CPl75ptW\nHZvr38stSoFaqd4zqOiFi7ng+Ra1F6tLHhN59VTBOzPe9MO18uEVohsV9ZWt\nFJhmknj5Jz9N5fGM+GwO1dQPwnXABPXsecjrsBeipu3joyXRwPFWTb2Kjyte\nxd0vibHK81dbxmZo/bClyawX4kPOoImo9hEQh/zikczRx+qR96uxttU6opEY\nmtVa\r\n=DaWn\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"649c3c8750033cd4d47594e9a209a2b1601bac0d","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.93","@balena/jellyfish-metrics":"0.0.102","@balena/jellyfish-environment":"^2.2.27"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.9.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.5.2","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.20","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.33-renovate-non-major-649c3c8750033cd4d47594e9a209a2b1601bac0d_1601034942624_0.12142484178727919","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.33-renovate-non-major-e9a41b9c2f9476ac115f14c7f06107e678861659":{"name":"@balena/jellyfish-core","version":"0.1.33-renovate-non-major-e9a41b9c2f9476ac115f14c7f06107e678861659","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.33-renovate-non-major-e9a41b9c2f9476ac115f14c7f06107e678861659","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5f8f466463e44828ff15340f7e0e326cb634ec18","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.33-renovate-non-major-e9a41b9c2f9476ac115f14c7f06107e678861659.tgz","fileCount":94,"integrity":"sha512-CDJL6LhEEDq2Is/WX5fT8Ktvxxu0INPdID5gXR45CuQtEYJU+GWXSzmUfV4EXHb63A7E6T2v/qUlezfFhq5kmA==","signatures":[{"sig":"MEUCIQC731vJ2A46EGBoXHEatVWVAV6MbOdazNAtSjoYwJYb/gIgF/1hmXLV0wyBpM4mk2ln/UrfXkrodu04UZpTTgr+0M8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":563816,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfbdz6CRA9TVsSAnZWagAAaTwP/AjNx378zfd/fdF9O5SQ\nLpKU1lBDxkfRHwBz3j3iFI4rgylBr/XYI9iCWbFHMUYrwup1YFCLfKGb9zx0\nqzyH+u1F25RWgcoMQniKYVrGtBUV5VGLFVBveFsr5AZVGXIUMKM++JHeRtle\nHYEEW+vyGg/NemQG9DcPiJkznj0oyWIX8T7SafCpx/gSPln7vMcii1fZWI7q\n8sB9SzQmpsBueuDqUuVzHZLRjSbr3S2ExSWeW5WEhMK/JJL1+jh3tb9jNAiR\n/GeMQW4k9pcIFBjgm0vqL6pGFQdKKg38WxeCdqZrD4KG4Gu5oG0HDeIv5l3P\nDV4STjAa9X5fjRRMSvQnrNEjI39oW9rqOotVrLmLXWarpfotAnEqjs2R00HS\nP6DetYnkrVe4EJF2+e33mfo1/xp1fEVbLXNHv9L302eMpRZ8MjiS6w5p7YEt\nKqBWy8OAvVG+Gwd/PyPrkcvZcugI8tRFHJCAOvhRwIZaqw+5Edx8DM9EDEpV\nw9Hdp10LWK0DUGA2tEsigH6aNWpLfHSkyWy22Tcsn0n+sUaEgU2u9+rK0Tmq\nGlj476ylVD/Wq0QzEhoGk6T6ECdzbOtucAUOgMD1AoKaEh8tnqcqWlYTINRT\nLG/9Tw+JBRLH+3es1Bnw+GlXJes7x/j8Dq5Nr18lbwFLGpbwRokHiBiV+y24\nGRyQ\r\n=YrWd\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"e9a41b9c2f9476ac115f14c7f06107e678861659","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.93","@balena/jellyfish-metrics":"0.0.102","@balena/jellyfish-environment":"^2.2.28"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.9.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.5.2","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.21","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.33-renovate-non-major-e9a41b9c2f9476ac115f14c7f06107e678861659_1601035514445_0.29633372948877246","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.33-renovate-non-major-ad37a944682d7161109dea9c975415af8de3dd68":{"name":"@balena/jellyfish-core","version":"0.1.33-renovate-non-major-ad37a944682d7161109dea9c975415af8de3dd68","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.33-renovate-non-major-ad37a944682d7161109dea9c975415af8de3dd68","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"53610ef6c848726bc58119391f75e926a9d339fa","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.33-renovate-non-major-ad37a944682d7161109dea9c975415af8de3dd68.tgz","fileCount":94,"integrity":"sha512-6uiuIXZYBDg/SN8Xz5CiHa3WhHf7g32QxBphZDP9jaH38Vwg702WwPYcFT8YjGZcxr6C6DwaLDImikKcsR4htA==","signatures":[{"sig":"MEUCIDefY4UMFQHCdYJvy1THlky9FajUH4y/uqdOKM/CRCkfAiEA91+4q/xk5Pj6B0/J/GkjEXbxikj6rL4CC+GapkyN2lY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":563816,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfbfZOCRA9TVsSAnZWagAAvEwP/jFfzYAGPMjEjlQ1URlk\nKCONheucNryNWq+uoJPBoOMOGNGXlj8pS3Y/wU0X1kL/QU+HRR+tV+9cO69u\noyX5Cv59e4sKBudjMLOIN2PwsBa0mJDOkICr43ApccF31pJ6f2cbglIN9O2z\n8ASmTUmX+d8r4Mv2bl2pLH0PIyas/luqiyxk7nrMK+4+x7K/dMLz23o2/E9t\nzRwIl2nZjkg1HRBPFS2SkNbwDHaXkBK3aVa4OkzAbDuksGen6xJjiiPpnL3y\nObURKfAS/ikp11YlQhj0pEhG1q+2asM9kluL9sB7VXpiAF4PnpIM9bSeygWZ\nnUHoQBf0vQhCvdFA/KY9Qo/wAQGcA2GIQeZae7gNbKTucYpDdiRZvyh2ed9X\nAA1X9T1rXUoObyQfA8AQNLMkdnL3EoduH8dQEemITvjabwVMbnuYsFa0kXBj\nnBd960dNRxtwHJHnwhhiymXeKCQMY22lvpqkSISt9+pKc6rUgeR/6XZeOcch\ncY8idgwK7U4tMW3jnVcHW3+Wq7MBxC+25zei/uEpiHQ0Sbr4iuNpO5pbngZb\nd0jIAUy+iGiBK6ntSYDgvMVvBHI8tGxj0namMoCDbK6VG3haFh+AXUknH4oY\nO1KbfzQT+nNegb7CoIbOGgJ1/YuNg9X37E/MXmVjrJk0sLAHklpajf9Duxpp\nZw9/\r\n=zWXZ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"ad37a944682d7161109dea9c975415af8de3dd68","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.95","@balena/jellyfish-metrics":"0.0.104","@balena/jellyfish-environment":"^2.2.28"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.9.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.5.2","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.21","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.33-renovate-non-major-ad37a944682d7161109dea9c975415af8de3dd68_1601041998378_0.24989577298983234","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.33":{"name":"@balena/jellyfish-core","version":"0.1.33","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.33","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"81af14fb5882469b866f69ec7239f0410baaae25","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.33.tgz","fileCount":94,"integrity":"sha512-xn9ZS1mioamhfJeZXQKsFy5aT6B2gcHfrJKDdZNlrHRtbLqfGGq5Eqp8b8SK8uLTXMMPOzw4sI4C+jFhbaErdw==","signatures":[{"sig":"MEQCIAWSF6sls414Lk0ICDavXxcsd8MHu4D2obQR/CWs/oiXAiApxBZ7WIr/SiNMYvaz9FPwpodcsgTcdjmm1A14MFssPQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":563756,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfbfeDCRA9TVsSAnZWagAAmkAP/0qK77UGpB+md8S1HkRv\nw5V8X0D5j2f7EFK8hMlOKVs8sBRV3TSjB0kfZ4sSdkNYP2TMZwPLlVaQ2c67\nS5LXAQut5O4/2O99d55gSMddQHACIpnmAv2F2/Cgc95mjMyrKKS/rfHFCvsy\n4jyCntd2NFPuzIFXUSG2RxTOnyQXRDbu2aY03TSA6e/0D5om0NUu0XE+aMeK\n/x8OwjEo6JWFKZD09V4SM4MdgOSXgWoKiaIdVDlC5H1dpvwQmzeSnU2O8YFm\niLJS730HY9B2CzvEbd97C6fJc9r4SMO62UxRnH17WP9ehUwfQVsiJZd9bTbL\nZJP8iUippS0HY9R+PgzXCG7BYzhFtnyrKxtZvNYlFTzWlQQwSOsSKNQJskyf\nsaE8swlsmvcn3I2tu43asWYFaKqnS1/5OrH7jevdX7gW/3YpGctMQz88N3iD\n08xLUtY79infSZmcDOQAfQcToE7CTqly6DAJG/fVag72rLDS0JmnzlJrphkp\n9VEPp924JzyjRV9olGS8y8+MCc0S/hTkCkNCXdVL3Qqb214KtaBY6eg5p+i9\neaVoGhFGT6pmlm2trxocK3K9L9/+WUeZF4GA/BQgA9edIqx0BkG1sjlXG8c0\nREOPkZ0WkaZl0emfJ9l3O1MgHAHbjNxljwUXenIbg3z1o38Tq2LyB14tiSSh\nuspD\r\n=C4RP\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"f877344434caf1e5e9c4cb4dfda4d2066fddd5d7","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.95","@balena/jellyfish-metrics":"0.0.104","@balena/jellyfish-environment":"^2.2.28"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.9.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.5.2","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.21","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.33_1601042307365_0.37318229827801064","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.34-renovate-non-major-943bed3961221b42006379bedbc4850bdf51a30a":{"name":"@balena/jellyfish-core","version":"0.1.34-renovate-non-major-943bed3961221b42006379bedbc4850bdf51a30a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.34-renovate-non-major-943bed3961221b42006379bedbc4850bdf51a30a","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"28adebb79fd48d335ce39d3927825659f918cf62","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.34-renovate-non-major-943bed3961221b42006379bedbc4850bdf51a30a.tgz","fileCount":94,"integrity":"sha512-/qb0gv3ImWxQDs2DNjeMi4HdilGpQ5Zgq3YDmuK677knuv+k80/a3DLY75NqzJ9fcelcZuFS506Xr6koqtceXA==","signatures":[{"sig":"MEUCIGpu4zwc6wGV9HQObsJ/JcNu4iPs/s8VyoY/8+In45QlAiEAvGPnNlNNxJQC85v8a0N81CUOM0VHxhiffAOZCw89NaE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":563917,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfbt+OCRA9TVsSAnZWagAAd64P/j20I3pvcUBDdr+WM6qN\nW2fyUnyTkD9kl6Qg7H82/n3OQIQvw9Jw98l3oOHc6ScuBhdaAcL7DBOfGsHW\n54Io0oU38e/FtOJQOPOzCTT7sL57HYUfOLVX8r8hyMW+ZQu86caYKWWnHRX3\ndzNtMhP3KzSqt3Me+w8ZFJDnPsc8aagZHfQ1k23Ckz1AtXR0hby3ErROEc55\neT30rDhyK63Xrl/o9fBtYJC56Pp67xutMUPfTQ7JOCcD6m2tn38J9K5ChtCo\nY55zTawr6PDLebZZXWO4Wr/qjy0LpJQ16OhwIAuoe6PNDdkx6tfz9BzDhRE1\nGotzfasAVX6aMhqEPVT6krSE/qNPUZ+6UHyL3wp4VE6AL6Jpm/mTSccmq0oe\nl1d+InmhudfIn5wy5HXMNnKYqlZ6cdhwpV45OsS8N+bGLbavLnHEhgDrV6WX\nO1Pg6u511MOka2aRwgQzivjFLcpLeUbazn7jX4QyuKRdxBgrMwr9hII1aEkr\nlEwqTP3gW+u4JyA6Ju8FgisuY3Bjf2SpmyEyb1jtF+blgJcsl8X116i6X4J3\nzGFKX3lKALxMTToDPbatwKX9MmOfGcQNlVQKcZpEmtg6blHhsuN1CIA72anv\nI66/r3E90/fqI1QeS/+FuAfL9QckyMuD5WbNN6CN9v7nAwcOmL1Bargvekx1\nWKNk\r\n=Ri38\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"943bed3961221b42006379bedbc4850bdf51a30a","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.95","@balena/jellyfish-metrics":"0.0.104","@balena/jellyfish-environment":"^2.2.28"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.9.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.5.2","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.22","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.34-renovate-non-major-943bed3961221b42006379bedbc4850bdf51a30a_1601101710283_0.21142924480189085","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.34":{"name":"@balena/jellyfish-core","version":"0.1.34","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.34","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c36efbc3ffaebb3f0d552d0eb288dbefa7348c1a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.34.tgz","fileCount":94,"integrity":"sha512-uNKl4m9d5O4hg8VqO6OkgsT4EfFEUp9UFcjeiVlnEJuMhEomrc4iRVNNgmmoBGbAMtt57/mM4TtvmsDOj/MiJQ==","signatures":[{"sig":"MEUCIQDG81mqvTBGBrtnVdfInwanNsNsvuEw5LxJ0Jt/057jnwIgNkLefnTWd1luBTT28lcsqMBn5I10uFsn9W0WwFdSFTM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":563857,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfbuDmCRA9TVsSAnZWagAA0rMP/0NkVjeph+f5Eeir/48U\ne4orSPgcIojjjyIMFvg077Pts6BblI5esXs2chG9a2J/Arlg/30WkNDtvPyb\nUe9EMWYKK1rNX30taPtGnwPQhMdYohONdWD9c6g4CIDEetn8S5oQyv2RHKTT\nsWGLdLODKG3f9/hJ/TTjesOZCg6kvDDGdH/bdyMjw+jRBbc3WJ2YYgrxaUL0\nEKxLfpUWItJWHvYoDWLXcD9TEcgSfAOnxlTMAhOw4XTCZxVT/QyHh5dVETlv\nGFu2JZsGGIPaDRjf7F4m9wtdT+EgHBp3OEB1kmXT+2G+8cU/JekHbTWzjv8c\nbBipwl6UuiQIdKkRZ949Jv2ZQD797Q5OjMpjEmp33CHEI/nPnJ8Pbx+CzuN3\nfuspgKYEqMn6JsMvydBbH6AqiuhLwvkXqbncv8QJS4L++cwyS/ofP0LRTktc\n3MJozQxHMoQOjdRjLNWkVQuiCIkc2fMmPFdMO1TO+ZZcUl/ceQxJ/WAPTCvV\nPAGtWgW+9kBEDRTNifU96o5OKa7X7SEVgQRwSHfdKadUOv63iSYSzef+XzLw\n3YsYEBMx6So10gYQKiUPqKkIN1HiDP2zWxjrZ5PJsB6PWOmO8ODbllCMrGbv\nUsPL2VUKyeODCHYcydtPKy/lrkBAH9PfYDGPSLWsf6Nie0aMmn3sI1uSC/eh\niKnS\r\n=aUPW\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"4e39b730efbcb9f5e47a279df16b2a86b0430e89","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.95","@balena/jellyfish-metrics":"0.0.104","@balena/jellyfish-environment":"^2.2.28"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.9.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.5.2","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.22","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.34_1601102054245_0.9268044458484117","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.35-renovate-non-major-dc237afc47bb69a5fe27e3060d61431164f18fca":{"name":"@balena/jellyfish-core","version":"0.1.35-renovate-non-major-dc237afc47bb69a5fe27e3060d61431164f18fca","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.35-renovate-non-major-dc237afc47bb69a5fe27e3060d61431164f18fca","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"67dccffe0010102edc4e605a1a88d22c9d8ce40b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.35-renovate-non-major-dc237afc47bb69a5fe27e3060d61431164f18fca.tgz","fileCount":94,"integrity":"sha512-vDph/Ejt9+w2oUFqXfFYQd7nqKCtZxRp8meXu9XSdy23aydE6cCn/b9aBAkv+/pwmY58/gq88IL+Apm/o8GMSg==","signatures":[{"sig":"MEQCIAo4GEvdRk6P3GFYaQgYYtmYjsFDL6vLnGV5gjfKu9KMAiB9jTOcjd3/457rN/7XKDo0YgbzillA0HuCt5f8PkLXtQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":564003,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfb7k9CRA9TVsSAnZWagAArTEQAKT0/6Xg6jKr2I3uzPVJ\nrhHv5+9HeWhhArClzfrEsHJ90ahp2fALkT3hHLr7OZTS1Pbve6ugJj5JP1b/\ngYJejkkBQRpgsypQ+RI81jbavWa/j44fsTlTsvSadm1a/9iy85LMnWlCOJgL\nSx8+p6+h6GFREWbCPp/cWytgWCzRcy70c7MEnTLIT6aHkx3ccJNdCX7tw1k5\nqfpZiQjB/piOBuh9hBq7CNFswSGxym6V10OM4pmePl91odRGLNezSVJzB6DQ\nAJLqZdGNkwksPI5C91A02NEB/sNZnxZzW0qhYOUw9jh7b0Pzll52nKkVNddP\nKJdM9MHMjvpDrRS8K3MoTxFVYbwQFMTTh9/TnWfGbwlfsqf1nQB1azDMuib6\nVRDI1/OMew+ocud0D9AZNWEWqaD9iyTy4VC478Tnss0PZPpmCiRUTxzFLktC\nGkThnvZpEGouc/Ork51CMoUjPYtpzDcVXLNJ1VPS0VRjH83cYaC4h/9whxKj\nradlTdfa8u51saHZA5eb8nMfveqPjblA3z23s6vFRMSG5IgiTJXfSs9zAht3\n68PhTTcCQe1Tj28ES24rWfNVCamqyOC+gtzhbgdg6oNzA63eeI4lZmmDwqPn\nOpzYBg0zEeH4N4U/St+S4QR28BruZgljcGtt+SP/MkiYB7GYjqc3EWjLPp5l\nlqIV\r\n=/ll7\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"dc237afc47bb69a5fe27e3060d61431164f18fca","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.95","@balena/jellyfish-metrics":"0.0.104","@balena/jellyfish-environment":"^2.2.28"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.10.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.5.2","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.22","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.35-renovate-non-major-dc237afc47bb69a5fe27e3060d61431164f18fca_1601157436618_0.3810394993336783","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.35-renovate-non-major-66a7ff4b48350e3c5669c604b6b124ce027113c0":{"name":"@balena/jellyfish-core","version":"0.1.35-renovate-non-major-66a7ff4b48350e3c5669c604b6b124ce027113c0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.35-renovate-non-major-66a7ff4b48350e3c5669c604b6b124ce027113c0","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2f70f305a6fd46ecd7f39d3b09a74091641be5b2","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.35-renovate-non-major-66a7ff4b48350e3c5669c604b6b124ce027113c0.tgz","fileCount":94,"integrity":"sha512-1pypiXlaC0qFgS3OA+l9Vwx3W959QZXgXgL3+1+vKiGuRMElFWWn3UoJLK574oF+RmKlnTJ4fREDZkmRJYpCdw==","signatures":[{"sig":"MEUCIQC1UtZ3jLtOWMG7ToOzUm0ccGaxPWsuKD9o/aOOGULzqwIgfgaB8yD6odAiFWJKu/6Gr28w6u6YnVXGXRlmYX/2Q9E=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":563984,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfb7pMCRA9TVsSAnZWagAAQKAP/i7FgGoysv3q1Jy/KkCn\nazq2A02YYEwrMMLomlASXBn18yaFJPpuUO9mXwHjR8RjwS0TFqioZ0JPPCwy\nM0xMpkBCQam1FpcMs2cOjt8/lrAOQNaq7t6yzrsl3a/ha7pcTcgX8lbjDdJ4\n27E8V7vwkKWws038sHjCgH6PyF4sDTmiiS47f+a83MRHFXCYEbvR5n1tG//w\nsfrfnT1N3A79PYm7wU5pqUPYL2NtcZFlU3igdldZwMcjffDGONTzAxRiIMB7\n+aduge05lpmMEySXEJi70OINqw5lSk5iFeEgVVqQwf9IdUZ/FvOGeiRD1BX/\nXCEzme5daSbMXrj6oe8hTcL0g6tnDTEbgmyq36PNnNaRige0by3HcccGq6ay\n6WbHlifcPs8/B6l1UoY6hb4lWKOzi8bAiD5BPfEIUi+ZdcmFuj6d234cc2Mm\nsyhsHWOYH46kwuGjFdCiht+cenV36udef5/rPlf72pJ/zFSoawlDH3obAFmw\nygTRaGt9vuDqwcnKoVCn0OM58jw4qx/K0lm8+NfJdsooBSFhcAq4uYaoK/bh\n8GRJiWavq/no4u2PTfJMPORwhfx0+A03EVPUwqvkwCB/Mw8g5RFgM9sA7oYB\niMdKTkldlbcayfcdZMUiWJsO4zksDedvX7fQbEk3plcsx29RMi1vQTEykML1\nfFBJ\r\n=nlvR\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"66a7ff4b48350e3c5669c604b6b124ce027113c0","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.95","@balena/jellyfish-metrics":"0.0.104","@balena/jellyfish-environment":"^2.2.29"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.10.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.5.2","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.22","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.35-renovate-non-major-66a7ff4b48350e3c5669c604b6b124ce027113c0_1601157708312_0.09578835594717372","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.35-renovate-non-major-a97a3885c640cafd6ee91e746cdd9adeb9abf655":{"name":"@balena/jellyfish-core","version":"0.1.35-renovate-non-major-a97a3885c640cafd6ee91e746cdd9adeb9abf655","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.35-renovate-non-major-a97a3885c640cafd6ee91e746cdd9adeb9abf655","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6e5df385838d475d0997588030e5d326774f7eee","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.35-renovate-non-major-a97a3885c640cafd6ee91e746cdd9adeb9abf655.tgz","fileCount":94,"integrity":"sha512-y/6zxpWFovewG5MlyTvyUs2c4uuV7SgAC3nQR5zlFuTw2kmL6DKLn681BhnNxFWkf+YqHLCWHClRvd19xnPyMg==","signatures":[{"sig":"MEYCIQDSlZLWSav8P96C++5xPMlX1W0rmKIqEJC+OFYoS2tyWQIhAK6po/ehqkUt7HFlEuQQcC4k4z9exq8xeRS8RNrMPV15","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":563984,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfb7s8CRA9TVsSAnZWagAANJEP/0Pv64X3nw1vf32e2fdb\nxWKN5ekv+GXGOwikeqLekLDruYvKfrH5RcbqY/gCVmw+VyJvjmAfPQmiLWBZ\nMTaU4DHFkFdCzfGaKz6saRP0s/UcaxhPNqFcz1WjxSIpuxayHbabBAP/Ev+L\nI3p+UDX+ADcbgvWBFCWrx61XadptnHKYKvQm6a+hFawPvUtyuq0vvbEDFAaE\nNEnODVgSCNkHWrKpQe7H1/5ojquHqZ/DR9dh545GRLPtxwMu/FVYPNWp4pyW\nOtWHSCLKdgndO54T9oBMa9bD/1KHo6Iw2ovQWFHgqAcWu4vW+1sweIyDe/Yg\n3dx2DkgvTdIxwdCs0HqXe2bFsn+EceA6th6AYOF6A6g7SDChsJsDQ/I6Ddf5\nBHrQ8zQx5HKt9MSmqYT4NN5CdNm29YQA5OQDJt+XmMLj5WFNgP8S/YHD6dFR\nejSOa1YqXKdDMyJ5qxpXr3yxkmzN91u3b3J9UdgJVEvAHjLEJsGo/Nc6ScaE\nMARz+QbQG+G+5oZQ9upnZ6c7Ux6e0ZoDPKF+VCOZgimcTutecFwDPgUM1mv6\nJr53xRC2wg6iC8sSTA4oH7jG8BEDpdJFHjFA1E995qWjNjmh1uIf607gU90R\nygJ6ynl8aOHm1l/Ea1ja6hIVZWUF7yKRG4Nw6p0qdwSRxvKabQtmQqTERJcM\nd4jR\r\n=5Kwz\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"a97a3885c640cafd6ee91e746cdd9adeb9abf655","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.95","@balena/jellyfish-metrics":"0.0.104","@balena/jellyfish-environment":"^2.2.29"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.10.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.5.2","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.23","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.35-renovate-non-major-a97a3885c640cafd6ee91e746cdd9adeb9abf655_1601157948031_0.12791619640938845","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.35-renovate-non-major-a4d4b1a053a58d44ee53f5b91bc2e2e9277dda48":{"name":"@balena/jellyfish-core","version":"0.1.35-renovate-non-major-a4d4b1a053a58d44ee53f5b91bc2e2e9277dda48","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.35-renovate-non-major-a4d4b1a053a58d44ee53f5b91bc2e2e9277dda48","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c5936c3d4d7be30367a2ff92cfa1bd31c992f781","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.35-renovate-non-major-a4d4b1a053a58d44ee53f5b91bc2e2e9277dda48.tgz","fileCount":94,"integrity":"sha512-HYRdl6r+iizrZFMt5Y9XorWtEjITfyMaRG2Z1MPaLyFdQz3f5sQ4vqiYm8OHy3H/qOJrQgAruWWY+JIFhDN1pA==","signatures":[{"sig":"MEUCIQDYJn9+JMB7gi1yDf9kMlX8Bi3oEttMV+CJ52/nkeOi1AIgC4qYeltiWIdpjwPBH3ojbVxcDsOAuNAE0YaMesjyew0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":563984,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfcAMhCRA9TVsSAnZWagAAvIwQAIeDjusJRG0axnKytoiI\nhh3OhfftPBUOjA8Vdx+LbXYAxcJo8xnFyqgRItoe4GFUVA9cBDtCRSHuwihn\n36bTf1LAau3z7z79/ATV4B9BOWsFbkIeWsmsgRi/FoVvFtwVew9tXmqTbE3t\n7d2NzHH+Q8EKujEYFvgWLGQ4SiUGIUD/WsBEJqQtFPfgxCM0k5cRQNmlxKEG\njRnXeuysi73ljlgKG+GGLgqm36iVgzqxms8/qK47XHv67FGJZBljRxWyxAos\nRDNuwIA7RRcjCeKGTUSxs9ur3bvjkbFJVeg1bwXLhvAP25OixJNwi3Xd58gT\nCo7vBOqblqwDjIqT+DUXaluN1yZ6cPgnff6BK4o2BujdzZ15ZSNuEXaGx+ND\n4tVZfVAIk8uu9AWePtN0dhrVfjER87hoZMdYHg8mbDs09EYjYOtf7TNgSSvw\nzrY4fFNbJWsv2sJv8BBLxMCJzul/K0Fh1AnTuMKcXGGLdc4WH8T1nSAGEO9s\nWQvr3cf7Z4sf6vvw+7H+4PgrzuPcYAlus5vedJldTHkwSo87ECN1yN7Bnp2C\nB4yWUI+m11e6EsROuS4hDXCnBKWHu0uo5tywN7toQc0A1yo5NScDTcvh2Jjl\n8UaAHLHlAtSAQyR22VqQpSjaHOZplZx5UAXAMIqK4ZExNsRRSGnb1pVWAZeV\nBEfu\r\n=uFVO\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"a4d4b1a053a58d44ee53f5b91bc2e2e9277dda48","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.96","@balena/jellyfish-metrics":"0.0.104","@balena/jellyfish-environment":"^2.2.29"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.10.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.5.3","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.23","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.35-renovate-non-major-a4d4b1a053a58d44ee53f5b91bc2e2e9277dda48_1601176352386_0.5949843747202703","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.35-renovate-non-major-4b7e37d97466513f668da07c53c47523cc760470":{"name":"@balena/jellyfish-core","version":"0.1.35-renovate-non-major-4b7e37d97466513f668da07c53c47523cc760470","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.35-renovate-non-major-4b7e37d97466513f668da07c53c47523cc760470","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6f3521533a443fb7fa6680b446e41b3d5be3af3c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.35-renovate-non-major-4b7e37d97466513f668da07c53c47523cc760470.tgz","fileCount":94,"integrity":"sha512-sIcU9lwtWbOzhojZXJhyYZnb6c7bZQwfaguUv1X67C/kdx35W0ocqFhX6eV3I3fuz1/6vwQoU0kRhZHRbV4Y/g==","signatures":[{"sig":"MEUCIAaVaHt/ETmt0tgK6qe6fKrus0/UN7jwdx2BgJBPij6PAiEAstGk9yrlhNcud2eA92VycIybNJws7e8eJkbNKiQcc0w=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":564025,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfcAWKCRA9TVsSAnZWagAAd1cQAJ54txZCyDeYsciBTbud\n7p6QkOOCzCiP3BBtgqiubwiA23XrbKMxkPSToZJDTXJc6Bd3MNmlG4ad2RL1\nH5FfCvcKPc/Oo2psFZAMbl0gnqgnN/aWfhkUVAPNHgDBHKXI9xvkybGzp/SU\n2muAyQuAwWJ4i7Mx1GYWTyTXTvOzxlNr54LsMYDpGAk9zvPr6T0wZzV/mVDM\nPmkjlbcSqHs3h+0qoWoI4hGSW4g1kvhVWbEQ4Haa04Ng/MbSJgsJ+zM+dP0X\nclVN6etKngDZDqcD7EyduuWQcerAw57BsHsJ/rAIMlp7YaOD8xE0YBnTTG+M\n1C7mzLSulc8Qdni6DtqXma9s0q6Xyqz2tEvN+H82g0JiyEy/hx+TUoXq7uCB\nzPgBaoOeHtkGWmpeWYDOZlhRvndAF9yCtLlg2NYqwRnI1DzVonqDaCZR1o8I\n/KWFbaDAdIRdDnvoNOrCvZu7SifQCoBmJLw7oinrPZ+PvhlTX6bxKxGnFvI7\nJsMN4Hp7AFoJnQeTi/YHJaUdAVVdTKH8umhggK4hFsJXAYRv259Iss8iN23J\nEXL7yV4XMoSnbomR25UfHYnlqppyo8DPSB+c8Leo2HH1NtuS1c51tIi/S3sD\naafhQ5Fb+yURsFRfYaFEAe6aqaxVySv83mN7drKSdg4a/c2Yzp0GS/7VDcxh\nvTDe\r\n=QP7j\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"4b7e37d97466513f668da07c53c47523cc760470","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.96","@balena/jellyfish-metrics":"0.0.105","@balena/jellyfish-environment":"^2.2.30"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.10.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.5.3","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.23","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.35-renovate-non-major-4b7e37d97466513f668da07c53c47523cc760470_1601176970270_0.14092149519551156","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.35-renovate-non-major-41ab07d86d3cee7ff8c54ec3bd5d04e1b5de5c94":{"name":"@balena/jellyfish-core","version":"0.1.35-renovate-non-major-41ab07d86d3cee7ff8c54ec3bd5d04e1b5de5c94","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.35-renovate-non-major-41ab07d86d3cee7ff8c54ec3bd5d04e1b5de5c94","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8e9b9714754553a120432fc0ab108980df70f265","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.35-renovate-non-major-41ab07d86d3cee7ff8c54ec3bd5d04e1b5de5c94.tgz","fileCount":94,"integrity":"sha512-UhqqrxHmczsh1BaeX3NqvD+brh1dEm18pvQ0HE9m2mPUUGBOLOIiH/Uy2PaZ44kdw8/pe+ld6KgbUgmIyVrChw==","signatures":[{"sig":"MEQCICpFVsFbm/O6ahxbPfvDpbQMp4/CVwwMtUhu66MpLrXJAiBnN6mNukREjCU6UrlKn/LnsZKq/kQ9yFy192g/7Fb6gw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":564025,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfcAaPCRA9TVsSAnZWagAAa2EP+wUG3BZjt4+GeXgFc0Ux\nxf3r0C7QaHIDO/TqsptlfZakLDv3x0U+Co2UZr7vFhjhyov0NwPGU8VvNdBw\nHeiApHjXKAF5pF+WGmiHtBOjBIYpdL4adrSDxFo2wCIvlSrJs7gBol8UbcKb\ncdBKDp944mWVbjUz5gNNwGtTSUd1YH3POlwg9+z9vpCpzxKB07cxAI4AkuWn\nfEj/P92jPnmZz1W2JmVYOmqITA4v2AIOkNhogreyt3OCtXjny8in+nGBnUvV\n5m2++kiaNXO+vLwq4BAVqNBMgpM9lwvb1p3HdtYRjlu8wTdhTgC1C1LhF4Mp\nWG/+xh1VKDcdz92zJMGj5PuQTNcW1r49FVvlxRV4jFWqQqgobN+FZ2DYs8U8\nIWwRtHCFpHABJ15vQWLEnKweMFXKXM3Erxw3xkcZD3fl+bH9rI1qTcSskCPw\nTOl1JeN8mIh0blOoqendmKor3qOx7HYuU82Db6CqLXMkrAJQDTdztRlaKrq0\n690XTyve/pKmse17rP+qVGVCwLDtTpnb3WTB/wVLvwKmmAxIt+ZKkZCiRVWi\nnOCy/O3dpEtTIwFQmRWr5k+AkCd+dNtsNk5A+gRGUusQG1kdzMHam8wIWaf6\n3rSr54IPyDlnswo0nZZp/RwqT2uiSpJMgrS82HraThn/TBFfEZTXvaXObALs\nYLzE\r\n=tE/h\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"41ab07d86d3cee7ff8c54ec3bd5d04e1b5de5c94","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.96","@balena/jellyfish-metrics":"0.0.105","@balena/jellyfish-environment":"^2.2.30"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.10.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.5.3","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.24","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.35-renovate-non-major-41ab07d86d3cee7ff8c54ec3bd5d04e1b5de5c94_1601177230800_0.8480317908139223","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.35-renovate-non-major-2e9e60b2fe98297d9196f1f4372d275a153acafc":{"name":"@balena/jellyfish-core","version":"0.1.35-renovate-non-major-2e9e60b2fe98297d9196f1f4372d275a153acafc","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.35-renovate-non-major-2e9e60b2fe98297d9196f1f4372d275a153acafc","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"abc66c7cf8193b39ba7ac56b0f95e98ebf556185","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.35-renovate-non-major-2e9e60b2fe98297d9196f1f4372d275a153acafc.tgz","fileCount":94,"integrity":"sha512-n3Ck3e2QtVvOFVpYe01usVR6lYQlebrNi/9e8W9u1MUpRQyfCZiGVe0CMuT18gbHXNE/XkKNblFVNo9SnwmpUA==","signatures":[{"sig":"MEUCIGKIHa/gFzucSQukDubKe+YGRZ9gGKNxr8k6izNaLRf4AiEA1wRX7BAkR+igC+SN+qFpDX/cnkXzaNetKOl/fItc9g8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":564025,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfcAfZCRA9TVsSAnZWagAAyk8P+gLG1ZS+x24gYTG8xHe7\nO/r6JE8Sx4W/FEeAJVyhWfApOhYTRAuefb8uVVW3kHof0Kns3Wos0WH4jvVB\nkFKNlnxvGZX1+J3wLW7CJt0J6+MuYuo0lEO5NIjmBIpJnx1K2sv45+h0TtIA\naYQn+dZG1MMz0bDJ1MGJpZajGhyI8cXqmnW2frXNk2oWbm1YsYL2pANlstsm\nztpW/4m62S8SkgjdEKgd7COWtB2N8jRrMNWFgeDIOrzaBAmaOGo3rF395KuJ\nmTnhuXuE3B2BrEFAfaw60hRBa6sC176zIGa41TKN/0+kAeGqn/ctCOpyd3ly\n0nPmxFKY4BYAnBuSuHZdXbNpjI8BDnytuwr7DaARcs5ZxZ8ifU9/6k8F9K+N\n1J9LqEO2fIG7PoO97x7qks4P8XJ0fk52Mcffh2a1W1UqQ5UqVbgc1E9ObUtZ\nZp4nB+Krv/rHhIQOe6+iNvjEFgAK9ew1PVKMH7dla7WFbVDMOL+Dq6NP7rV3\nWPUlPnd9bW+pZCRmo1M1R8IaWvQAu9k//fhH1BP0LJpuiWxZCWxzXXNAHOzC\nJ4WQ98fvL6w1De8lBydFjIbp7IPo36DSDqWnctKuTJAvYfGBjPowhloTK0yG\nx8hsLN/yT6CJ0EfzPWSOh1+akFRcH9blid/OaMHUu4i7zc5Acotdaak6N4pt\n4uZo\r\n=+xRx\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"2e9e60b2fe98297d9196f1f4372d275a153acafc","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.96","@balena/jellyfish-metrics":"0.0.106","@balena/jellyfish-environment":"^2.2.30"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.10.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.5.3","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.24","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.35-renovate-non-major-2e9e60b2fe98297d9196f1f4372d275a153acafc_1601177560766_0.6225334055017147","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.35":{"name":"@balena/jellyfish-core","version":"0.1.35","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.35","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"daab3aed7b5a3634542b4be1274b0df419e371ab","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.35.tgz","fileCount":94,"integrity":"sha512-LSz/GKjuTM2zJ4uUsdNzvdBN5h53aRcuACG2s6SDPL60MFspPXaiKsI/im5CHdcAmiOZakMkqzMPLjH11PmtCQ==","signatures":[{"sig":"MEUCIQCe9ETy465GZPInghtjtCaqmp1WlTwIxSymWwjYZ60dsgIgAzlc9QtVuIHhK7x4AoKkc2qCoDEEabWB0OytSjRyKcM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":563965,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfcAnuCRA9TVsSAnZWagAAqJgQAJX5P+vIg6DjtjS1Wa9y\n/mqA7bZPngsgYsb08bqQhwk4EJxHUdEcGhufyQhaTTlFAPavLLeK4mD+NFOe\nSoZ0YKx8W08Z94SsR9mctQWyFu0XUEGIwrr+zoI2eoZCV6BgiXMSrkAFr7CZ\ny0OOcjm+I3XZOvLltrUQcgEHMYS4t6NsH786826orOKmJhOl6cy/t/DSiM5b\nAslKw36hu0TmhWp6TiTRaijWhaN4sbqS/QzA2QEzUrzUYUzAf1hTv6gJSXeg\nxLJId5kw8vyG5k22gUK7KX0nWk+g1E1OZZaMbSW6fsjfy5CpOMOALiqzi4OW\nuwVhMsSDHO7mxurFz13rZpJhERb/TqxO4J0qUlxBLOOa1Boh0gYpZKK42++2\nrEHJSP4dbHbCOItPocYPYS8qhcoMDKZX0xuVSrSuvTrHqBOi18D4Yt3rrWDi\neaG2AYwPRl7hWaJ3dtfYmjkWPQFvFhY5QbLX8GOLeZj4FcYGkOoLWaTLSZsi\nprSbuDXE7loJ8+eF/jq8BHq9t4vIB/Z24zwLuFvWZry3H+mp8nkne0dJbeHi\nEMZh1RoZKMtdU6VX+e+CnKdsDRBw5OT4tnTYqKJ4eO9f0/vhs1lVQmtYO5qR\n/wAxxHP66uFWI5GnXgIWFoxXdKn8gjqMgpSFRDHZQRFvIy8oRrre8OztcWU4\n1vHo\r\n=XxUE\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"6fcec2a85efd408e147a0e6cdf0bcd5b3e105538","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.96","@balena/jellyfish-metrics":"0.0.106","@balena/jellyfish-environment":"^2.2.30"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.10.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.5.3","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.24","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.35_1601178094218_0.4909905487928068","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.36-renovate-non-major-4b0b8c3c5c16e380e8f7e28200ddd34898c13e6e":{"name":"@balena/jellyfish-core","version":"0.1.36-renovate-non-major-4b0b8c3c5c16e380e8f7e28200ddd34898c13e6e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.36-renovate-non-major-4b0b8c3c5c16e380e8f7e28200ddd34898c13e6e","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0569a2298fe8050d32ccf440cfdaaea633859744","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.36-renovate-non-major-4b0b8c3c5c16e380e8f7e28200ddd34898c13e6e.tgz","fileCount":94,"integrity":"sha512-dnLtoCfqTjLWVax4mnpvrEM0oTYtWWvOu6Oru/qZCnzW9XZO8B3xr5itypo8uTBoxpBXBFykmXd7kaljo3cNeg==","signatures":[{"sig":"MEQCIDpwBy39rYaao98PgpBehYoBddHKzvbkHkjTQ8h/AyWSAiABdV0OZ3qB6SxsL2+xKHi410F9GA5ik4c1mTuKDhulCQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":564128,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfcByFCRA9TVsSAnZWagAAtB8P/0llfA/SRUxVhYEpeXgS\niGIaslQgSZGm/6GSocOh+p0o2X7neL8wCufY6m1Xnz7H1q2+0phy/Oy0118M\nicRUJcRDCrWitb5LRBY3t6pre6YEd1ePjj/DoJYcXDyMQJpXiNkbgUEY4ILi\n2wcf9wir5Hg/5llM6SZU96V+P84rKJfiVZ9wkC8M1Y9p4HvDJlJEhGN4XazD\nwqRxLjB/v7UB7K8oJTXlhN9hApQgQ8uSsrDoTchNKhJ8imYzK9xR3dG6bGCu\nGwYRy7RPMERHYEF0pnqoEZadOscPxRWqucSrbAh2uQc3JkXUKzR4YmtrYO0T\n+FoNzAG7kwzyYrkeZmZNpBNrq+2XxzZx5d1EZtF8yYDE3RUWTGlYCRNEs6l7\nY5111bbyqoF9KoEE8ZJSDkJQXYr1XlKpVP2x3rQY1JPlpGEYS6MyTfpO0UXi\nZK8w/TbClV9hWHHhI+GpPqmj853B857ssFFRS53HfgZWk1e7ZPrzHRyLKNuc\nSxOuRbO7MjhuyCuGGGh8meY+lUo/4NOeF2UZP3HgmQxipA/d4mP+gGq32C4p\nCMNachCZp6dLZeSMkgGRJkDDqvTFXOn2I7J47wSY5IoF5LBE9/w6spVM0Lbq\nd8S0qFiUVqq2k3Ld3lsHrCD74U6e2MiT+qr+4jWZ7dqJpRBn6pS36SiAvufJ\nNNFw\r\n=k8S/\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"4b0b8c3c5c16e380e8f7e28200ddd34898c13e6e","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.97","@balena/jellyfish-metrics":"0.0.106","@balena/jellyfish-environment":"^2.2.30"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.10.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.5.3","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.24","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.36-renovate-non-major-4b0b8c3c5c16e380e8f7e28200ddd34898c13e6e_1601182852913_0.30738687488991867","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.36":{"name":"@balena/jellyfish-core","version":"0.1.36","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.36","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0653b227f5d4dee25f32482f2fc735d2bc24d846","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.36.tgz","fileCount":94,"integrity":"sha512-GMTRCg8nSrPC7o5Z0AgLfkOn+VzUS8rI+PNyoI6G4qv17brYPsSVUpQmCu9mNX0wTLKz3gwrut1pfw9Ut98Cqw==","signatures":[{"sig":"MEUCIQDtRe1qfKUr6e85on01fLYbFEdN/cl8QxseWCSa7yzczAIgNgwt3z3UKN9EIw/KyZZAerdDV6NSWMddJj9NctHWyxQ=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":564068,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfcB4pCRA9TVsSAnZWagAAOj8P/A9sOvECEDt8Q89AQmxg\nIZB9xZ85vuNkyEWE+7uq9GWBzhhUXZNfJ23yBrLGwwcyMwaftlkjmLfQ8ZaJ\nTthchYnrOplN9xaalPyA0feytYdkodeupbRIASk1i3KVAuimXTLwPVpSfDn/\nHsw1jyLRiVhwZ9JR3E0db+MfB4kkRMyIm3PVIV3tzajQxCeCwlCbhd187ota\nrq7VQarcMBOJna34UdlREguo6BUAGHVxqGcBl/iVmGCQy2kjUhaCOG4GGhGQ\nxr7dHtxYUsgdu5zoyaPb21edHr8ri7N5PEbpSo5rAQvtXadBzZcip7Etgrw/\n738lULhphCajx6KNL7x/U6nySeLOAord6Jdfklot+XSburt30jpsSZwZ/LA3\n5THby2i0jmH2cWX0M9gKtDeSold+q77IT5yvPo9yEo+ZaYYiqSlEBEM/LM01\nDwaiGPpogM9lA6OxLmBUmbY30YgoHE4RzJMRKcLM54UzaUizfP2gOFm74f4k\nqzjLJNhtd+68K788/tqs6iFVdytuOwWhsPi5epSC/jjW9kYxSy7z3FHmiyE3\n2KWlGFwqocAjOEb5+gdNtjZcg8OFwh7BZcBa17kHnU6JMAbFsRv8vwTW8TxE\nBLCfcHdGS+sBn3PkKll/dPBc7KAtctXX/EqL2zx1IkY4596WSxpfVQOP2kK2\nBEQY\r\n=UAkz\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"b8e8398cdaa8b8d97a8c334d77d85e3fd9cfef26","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.97","@balena/jellyfish-metrics":"0.0.106","@balena/jellyfish-environment":"^2.2.30"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.10.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.5.3","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.24","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.36_1601183273088_0.33664881828158033","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.37-renovate-non-major-30c146ec0129802f711517c80c71e63ec0988bb5":{"name":"@balena/jellyfish-core","version":"0.1.37-renovate-non-major-30c146ec0129802f711517c80c71e63ec0988bb5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.37-renovate-non-major-30c146ec0129802f711517c80c71e63ec0988bb5","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b4283ec4b184522e9f7c19ab9768c72b838d0762","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.37-renovate-non-major-30c146ec0129802f711517c80c71e63ec0988bb5.tgz","fileCount":94,"integrity":"sha512-qdI1wa6ab6+axEaqDJZbuyxdeMDGpqnGaUNDaFj6zKCjQXBDkW1qftVnqfQe/jaF+kMwnYqUJ5Lp9lzopjTVpg==","signatures":[{"sig":"MEYCIQCT4aNcWQOkPSmBhXxwcPtPW9jT37E0jkMaQMUI4OKIZgIhAJDQR5QLw3W2ovfY+bqqd6DYVBl8gwfm64/NGJgjGrhw","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":564233,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfcB7RCRA9TVsSAnZWagAAE/oP/3qcuidJtQ9VkKQyV3+9\nNRHHIaSovHxiipcxbztUJlLFBv8UdS4znMX8Vm37Uu7jOCQWKiMLVH29itA9\n8cNvZ0RBicpv5/gFh15qLVXg7HHebHLNwbUxZHEJs8eF6fIPXAQ3UJ9a/mCR\nClK+3WN628QD9KlzTUgv6xOm56FtGWGN2OjH5OS2dOcTkBXaCdOwvXN3iuMb\nVXSvjl+FwKub6rrDwz8Bt1ykK7NXP+bwPWWzDkx+f3m1HbQGAlu1c5yjfEnk\n2x52NwXU14D7Gs/MKV51f/EQ98qY6/XtuUuSNPyMFF+2Hr/JoYmvVUg66tic\nTRNeP3k3G1FuiZXjALsuCAeNcYBVuHArGw6K2OV2hNwL7sZLmc+Enx6vY4m7\nJlJyn79uDhea82Ew0LHde5Nj/BmTbHfCSAqXjK26HUr3QcE/S4H1gnl5N198\nqfyeltyR0sEacHt9kcqJdmHmq/R1x2Zxskg6/NP3timoOnEypqQl4ZUegc2x\nNAHMx4Kkx2mF5SJtJ9JWhHHM6R4nHmg+M7FX8fuw+1bhwmirkVUvU38U8/4G\n7LnY4hvV4UwW8vHrBSi8LAEh8mE4r98ZAVNoTS1bEoUIlCHk6lMoLLBamaUD\nK0XytWWfruEB3pp7vM1REyTxomcZPRHMFCyLKotYS0CXv6kdaD0g5ChBxSD6\n/6+z\r\n=D0bT\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"30c146ec0129802f711517c80c71e63ec0988bb5","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.97","@balena/jellyfish-metrics":"0.0.107","@balena/jellyfish-environment":"^2.2.30"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.10.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.5.3","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.24","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.37-renovate-non-major-30c146ec0129802f711517c80c71e63ec0988bb5_1601183441110_0.19150169308108445","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.37":{"name":"@balena/jellyfish-core","version":"0.1.37","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.37","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"242459e746372f265feb6555768f9cc3a3507e09","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.37.tgz","fileCount":94,"integrity":"sha512-yhYorM+dCJoNsGRE642x1qk3BeVkeKS/hdzYrDQfDBBFbuRRYff0wcAO8QLd9dkwgcu1nh1UFf+xqaBQlw4rqQ==","signatures":[{"sig":"MEYCIQCMn4oyPgdKBmjxgMsvGlBnkS+0ayvxKEXpV6Wi9MECXQIhAPzSNg4/+6PFm2TH5L+vY+HeKlQDj56ZMEUlRICTe2i8","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":564173,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfcCCbCRA9TVsSAnZWagAAavkP/Rp772+3DLGdncWxT74Q\nat4wSU6nbqLOfvKRgV/yUogltvs7cxai721rTmaxltrgnDe/Hpf/W+CoRuxc\nP7uxNGLnSAg6T+am74LZJ/KaG7bWbQTMWaSzeZ6SpDWNCu+EbNHRIYcDIpeT\nT84Q8mx8G3Zg6pXe/XrdmK+KEhXbVzAeai2eFr3cvNY19N6EUwKwmMQUInTA\nkHlGYGfH7fYjMGqHXcsUEcT3ZrFpK+Z/lPZsGh5jvhr7S0wtrjKaY8n9RNMR\nFIncD8Uff9gWGZ0e2C3iGZkHlGQOTW/QDZii+k0FgOz8viQvMCF6yS2dfZ4x\nSFyOLICyb2QHRhnNoKM0No59OUmu60a8vyKFz/b4NwQXCQRaYv+lJJBez6Sd\nzWt6oxfMNtZxvxi9KDc1xjqM+ZwFi4QOFftPBxCOlR3QrZUGalTlGDmQh2GX\nZPDEz8w0iRzCUOpHQ8bbaI8LoRHxvFIZuCFLIW1FAFLEwT6xaGpcfFzEBLT+\nUJZbwEXJd/cmQws0n6usVTxi0pWWNtEbM/sCGA2aqXqq4VuVvStmeW3P+qUH\nM9z97+4DMdB2O5YIxN8uniYVtDjsI/W+VGYj+4GB3XT7hENuJ2HF5YU8/WQT\n625naxra+C38oGw0SCWziZE3RA+xuJsuoGfqiLXDC4hsNiXrDQjJ3ZM4b3C+\nEGfu\r\n=2c1d\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"1f5a0ce51808dc1dad4181e7a7c487eab9a08775","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.97","@balena/jellyfish-metrics":"0.0.107","@balena/jellyfish-environment":"^2.2.30"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.10.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.5.3","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.24","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.37_1601183899327_0.6508842498166931","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.38-renovate-non-major-9596fe5007cc93a790664f7ed5c8345f398fafbe":{"name":"@balena/jellyfish-core","version":"0.1.38-renovate-non-major-9596fe5007cc93a790664f7ed5c8345f398fafbe","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.38-renovate-non-major-9596fe5007cc93a790664f7ed5c8345f398fafbe","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"747fb85582101567ef0513bf5a394a9a08ce3d8a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.38-renovate-non-major-9596fe5007cc93a790664f7ed5c8345f398fafbe.tgz","fileCount":94,"integrity":"sha512-o53QQQGsWzV5A1RcIxtZmTTkHDm9e6hFpPpMCNojviIE2DQvoFTzT9bSvUwW/5LgDVcmOHcVRZT2l75PaMHO6A==","signatures":[{"sig":"MEUCIAr2KfkgMhQ/+ryfpvpT9F67nLLZnIUVqPTbd6gn9RVHAiEA4ksc9TYRsD4c8RBQmHW1agAMFCPzYqJry5jojt98mpU=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":564331,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfcHwHCRA9TVsSAnZWagAAqmcP/3VSD16e58ne2TPD2OP3\nQiaGm2MpHxr3mNirAfoEbSYOdYGUhA0Wwv8g12gTlnLy4QKra1L8JCYoo5YB\nwM94agz2fP/UWlkSCE/7kZecAmBbRXQdXk5YxKdV98ih6wfwGMhoBio+6HsG\nqF6y9xC4uLUg0y+4HeXu6cqNuW7Kk1vJSRLqCCUpBhrRWVGh8jyzArIFyXXI\nvpb/XmtYj5Heui0eZNs1CQcLMg2gO6/zVWC4potcYuNYp7MgGtdZe9VM5r+W\nr7H6cKokDnHNxXZ62SZxrrSzo2G7gLrNgHyL0EMGQMoNWLC1TQgUuM1AQt0K\n6ArN94q1H+h4mjFYlxrj6qOOceAbJS0o6vAB58Tqv2Yl4i+p4IqorRzqEmz7\nWDbHPhlrEMFX3w+iI51KtL/J37SJcCZX8qE40U7otF6F5HtxIInv4ZLXwYdU\ncEB933jbkbD7ftFNW5TDmS+1WkKDrFrLVdsSorRIa4Ww24F4RJBTfU/mSTA1\nZDAtMlORrpHO3YGyZfVBi7wnbdzENygYN779+IM9UixV9a+uarq1Jv1Hs2Oq\nwTID6BRK2O54dHfkM54z9DTheoXQ+V9n4LaIW0R9aqb2+5Ahb2vWjsPoSLnW\n1d1ijeE062WXmLeXc5668XCONlFKjLilEP35otClDmVy85Vwmxiz7o++YyZY\nWDz1\r\n=cSqM\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"9596fe5007cc93a790664f7ed5c8345f398fafbe","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.97","@balena/jellyfish-metrics":"0.0.107","@balena/jellyfish-environment":"^2.2.30"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.10.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.0","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.24","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.38-renovate-non-major-9596fe5007cc93a790664f7ed5c8345f398fafbe_1601207302804_0.5940056388798403","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.38-renovate-non-major-29293991cfb62a288d9c411f7f49800087ec79f2":{"name":"@balena/jellyfish-core","version":"0.1.38-renovate-non-major-29293991cfb62a288d9c411f7f49800087ec79f2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.38-renovate-non-major-29293991cfb62a288d9c411f7f49800087ec79f2","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d816d0e892f06b1c1d037a8b7f79b138c0f4a27b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.38-renovate-non-major-29293991cfb62a288d9c411f7f49800087ec79f2.tgz","fileCount":94,"integrity":"sha512-qUpkW4/bnAgBPxaRuJx2fN+DQ5tU15nmJRPZZJeVltJTJi3msYWLw8Woag59rrMf6N28s9f0Ck4ncmH4K3hNHQ==","signatures":[{"sig":"MEMCIEs/LjrJ+whXuJ7vkWwPVuHFxXWlCo+KtD/7WqReDAh9Ah8Zg3haV02LYBNNXmPfy3QRxaVbHSUP0tLvkwaKmy7s","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":564409,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfcH5ECRA9TVsSAnZWagAAXDYP/1y0S+XPNT7GZsqefOE7\nxVt79sqeW5nVTLZB/CNQlTH4Y+g/rAl41LE4M2T1WFk00xG6dxKAQ9QVFeaU\nLfoMX0srBjWCfqCR8cW1CauEA5CpqH5hkQZVzyt/J4de44Cmmur7oJHxzfwj\nZEguJMFQavS461QG6DkjUrOvShc3UG0cjijl+zKAJX3dUN30sELNYjWlsHSv\n6UylMmBl7cEGDS2oZ/wFidxT6exfzVkNN9ma1Tdc0EM4206BBgWZs66MSUQS\nrpAVRuAZwD771NWd8vMD+hmYJ9QJsLx3+E6HThPl5hpU0n6W3qeJKxvlGBNf\nDX6LgsmlDmye8y2NhNFPXE/aZkcNPjNeSpAj0TaNIDkQjqQqdKHPmGZFIsDd\nOFO9t8XRzTs+hUwf64fS1b1zElCzdVv0bIVoXksmLctcKq0qSrLce3PtoPZk\nrYYR416KPfuUO1YArG/jgiFioaw+P9p/cjXQocg/PRQRjOaqLf1pXwILVVf6\n1M/9VkaFXbp+fBoyvl6LKaL7LXuJOhAiNzLuLomIVc8vW4dTAG28TQTa4miP\nKVoUiB2pGZGBCnbrbet3TS+o0WDC1tIQI3bSO5LGl7BSqHDB6xajUjSOs3DQ\nbWCh/mbLxSXGU3Ljq4tpc7y2tLKbsm0nuBDQJ8QaPSWFz5kRQ6cchkyYNSeo\nceeZ\r\n=MIXM\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"29293991cfb62a288d9c411f7f49800087ec79f2","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.98","@balena/jellyfish-metrics":"0.0.107","@balena/jellyfish-environment":"^2.2.30"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.10.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.0","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.24","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.38-renovate-non-major-29293991cfb62a288d9c411f7f49800087ec79f2_1601207876270_0.4459230762127071","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.38-renovate-non-major-2c84d4b8a42f715d804985071a3c3ddcfcb5b480":{"name":"@balena/jellyfish-core","version":"0.1.38-renovate-non-major-2c84d4b8a42f715d804985071a3c3ddcfcb5b480","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.38-renovate-non-major-2c84d4b8a42f715d804985071a3c3ddcfcb5b480","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8dc1185bcf49294f4dead4ed1a1155bae78f2e2a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.38-renovate-non-major-2c84d4b8a42f715d804985071a3c3ddcfcb5b480.tgz","fileCount":94,"integrity":"sha512-t+G8rJE3YnstsTES3D3Brt6Be+ingWU0Rg8dSMp0a7IrHftwAFGuQSMlo9cOcHt6MI/uB92HCZJncfj9TkS3YA==","signatures":[{"sig":"MEUCIHLhQk2piUfHZfxTa/peFgnjhP+rIrmOGHNL33J40MgWAiEA9q8Y1fpl7AGomMMQmqCtoVHjKvuiQD0HRoC5GjqnYLQ=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":564372,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfcH9kCRA9TVsSAnZWagAAavoP/3NYe1AxivhiYHnT30h2\nuPAco5Yve6Ezu1UKMvY3tCaJCdEz9DDxizmJ4I9akzo3+48Y3h8bAINy37iI\nJv7cvXbs9kPm4W9ZB742ognTVgriG1NJzJicp/N1kF2k2yOXtvqC2GmGMuN5\nxxd/dLeJvMN0B7TunbHOPA3336weZaBGgxIhjjGBUhs+CADy52KaeF//teYA\neZk5wGNQk9P91caOMunt2Oo4maFfmv/qjOrN+s7S+YbONTkUdlPH36yRlZeC\nVFxnUndvRcmuucCpjmiSHCiYOTIEdRVFWRjyc9DQcEpUnrO4po6kfeZhoxqO\noplwGpXyNzAIWs1FXp5bG2MV0lQySU+iajcr9f910sLCBx/AiqvOxafvZniv\nZkxS9gJwxZyGXUZx9g9UDXcD/w41Mc9vgBy1hJJulrB5O5XTsU1WLQnAEJ8+\nNEol43bX4duxtJ8WJvr93UvNm77IU6Xcjx5z0GgWT++5VyMa1Rn6oPbX0i2e\nos9l9A/HOBRB0A1VPKCY9k25LI/hYc6x4ZC41gX+lRSFIDxH0CJ74n+UlT/y\n5qwWzk4CtcMIqfF1LBl1LJsoSKuwWSK0fgeNWaPXOwJ4bKePfUzHzzNeTOA2\nV/BCXn0mil1D8GAFbZ2bjo+U0WOieAZyLgl/r112PXSTRpINdU9M2Agx1UPB\n/Wsn\r\n=Ztvz\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"2c84d4b8a42f715d804985071a3c3ddcfcb5b480","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.98","@balena/jellyfish-metrics":"0.0.107","@balena/jellyfish-environment":"^2.2.31"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.10.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.0","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.24","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.38-renovate-non-major-2c84d4b8a42f715d804985071a3c3ddcfcb5b480_1601208163667_0.9722568235193088","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.38":{"name":"@balena/jellyfish-core","version":"0.1.38","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.38","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3058d317798dedf46abb974a44e5f8cdf29677a6","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.38.tgz","fileCount":94,"integrity":"sha512-m/OKpWd5urm7syzj0poCsoipJt2t65uKcnnWFReFCqwYuaQqgJQVjnx7GZVriXbj0Gz/fUD6G2QrsKmIvuY7Cg==","signatures":[{"sig":"MEYCIQCkA1/mDhWkyf/Ka/RwtYoFfEC5Ig4xuVjVsTmxfRXJggIhAPYmGosiO2+8dmp2BJjzn4xZ//8oPOTdE2cVE1/I92bi","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":564312,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfcIDBCRA9TVsSAnZWagAARLUQAIT1y/GY0oFytTMHrRlU\nr3NfPtQV1H7VsNQtJZBmswVn/LFOg/fL/44lmtR4cjUhcfv8U/XZiLg846Yh\nSWBm9cMCkODKeRaJRFxAPXvuLUEnSId8GtWRgsi3rarPErzZ/vTvOshhsz8o\noY6myhufpXU50zCSp4USS0ajnKoa/v60M7HCP5viRQ2tTjgmtnpjhCIC9VrZ\nR8RnFIBXeQYBI8xzk9CPcOum688yv8wRh6pWmdUYPFPHTHlStGFnOoRc2NcI\nkVKUAFe/GvXgMJbMGQyeGWZPT0qDmGNS+95pE/Zm1HeQoICX3XdVhkmuNRZu\nPjQfbVkanPjAdSAfsjyhYi408qOGzF8tX14zmIV1t7W+Bk4aJ2HmTfP3TdZ/\nR+qP6vlJyp7e+gQ0eiJCXCmAgya+xdA44abHTm6EKYdzNMY2okWMU7sti300\nGNI44wUlttfFtc8pm/Xeiq3wvi2pWnZn/jh5/bN93ZijOG/mefkONNmT8SV0\n7maPWZW/x1P6OqazbzzS4g0tfGa7Im41CZJxPp2ASFGu6/gQqaQvzI7PL6xS\nGzKWzQvoF7SzFubUX7Vg1y0jqlsQics98jiggZy3eAV1tFWb7uCAUT7SoN5j\n04oIJ+DtRRAbFQWIEDwkp45P13DfaiqZlcog8MicS9QyITsuJJi27f/rzBpl\n3YBz\r\n=bTpI\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"e4e238fc2b7b2d1843da916521630c782c6b84cd","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.98","@balena/jellyfish-metrics":"0.0.107","@balena/jellyfish-environment":"^2.2.31"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.10.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.0","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.24","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.38_1601208512730_0.9090744140236842","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.39-renovate-non-major-8bdddef9ce860e46193ee783888384a300eaa0e3":{"name":"@balena/jellyfish-core","version":"0.1.39-renovate-non-major-8bdddef9ce860e46193ee783888384a300eaa0e3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.39-renovate-non-major-8bdddef9ce860e46193ee783888384a300eaa0e3","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ad9b6f072632a8d50befd8088873ac348a34c704","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.39-renovate-non-major-8bdddef9ce860e46193ee783888384a300eaa0e3.tgz","fileCount":94,"integrity":"sha512-qnj+8o9xUqLA/YFrjJqP1Jp06W/gk5y/vdyRL9bSL6iaRvY9M12EvvD62XzMjgYd0SNj8x+6rvMd0gWRQfuL5A==","signatures":[{"sig":"MEQCIEA5f+pllVTgR5xd1D4AKPkm5h4hjtOt0F7yfvNiJ+a0AiB6pobJ1y3CUEP8bXhLDg0CnWvpCd5ffAsI23KMT33+ow==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":564438,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfcJOUCRA9TVsSAnZWagAASIwP/A6ef6p8EY28DphV5RoF\nVbOGpj6GYHyjLwummrJsiuZoxkwbMF+ohLjjj6GpwYtfwVPFFtYiubqL9r3+\n9QWqwCyZUvzfxlLDr96dSrdldOribwpB05116oVG3xe58aak/AIxpWP8ZsPG\n/3F/qj4zMybZeSf3ot280oXq4Crba3vrszTLnybHAUoGChJ11uiNprYjwHXi\n6Bf0Gg4BLDj37GifPeII+gSzj7F3DsX8DovJ7HqRvSF4OaCA1CHtUPSQkFwu\nSUfUAX0+fUvebOhBfcNXdEsOrA2N45XyVOT6Pm3FqGurgaL6xpacfSm8YZfv\nBpmW7uUUb9jp+kT1I6SXtNRs8RZIqQRPGX3D131Fw4N9wvLFwmLHU2kq/NPj\nmOZgf55lrE4bhtdtbYSXtjvLrUO7OiqHbQcmJtnz3dIBCg6pW0ajfFL84aiR\nZ0TtohxKFyZhjbR6oECuhuvZxrZlAujA6osqQxoBxYW2ywmNCpHEB95dgO2k\nA7Kxradr4Iwjb91KLlSBL7jgPg56BkJq2q/ALI+yCaXLV0l1o31nNRe5yJiI\nwfxtKon3vRKUdipbsHOatEqvwj6Kf4E+qD7QUkcg64EkVgiC6yzdWB65fBCJ\nHir3I9eK6AZ7NuqKoOAaqtOgyrf1PULXrSadp9aDWnkdpVUuwIhwgvQI7L//\nb1J2\r\n=U6VP\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"8bdddef9ce860e46193ee783888384a300eaa0e3","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.99","@balena/jellyfish-metrics":"0.0.108","@balena/jellyfish-environment":"^2.2.31"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.10.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.0","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.24","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.39-renovate-non-major-8bdddef9ce860e46193ee783888384a300eaa0e3_1601213331909_0.50470380875936","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.39":{"name":"@balena/jellyfish-core","version":"0.1.39","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.39","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"da8f842ad5d749a2d3a5ad4887646369707075b5","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.39.tgz","fileCount":94,"integrity":"sha512-HN8SqjU3qT8Ghh3EdADESXW1PjllknkSS8PXO96FT5o1bLO7P0eYY8D/xJe6XmrVNnEz9UHnc1MWubJWZMM0Vw==","signatures":[{"sig":"MEUCIQCJroNvaUGOsDxmVdsOnki7B6XWOko8trfN+8pIZpLeywIgMyHN+VisN4A81s/6X6PBY75BE4pYralvCpDvcMAykKM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":564378,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfcJTbCRA9TVsSAnZWagAAfg8QAJy8rC/GYaPo9N6L3A/B\nn8snFqj7gIyJA3WKztM+PiAMp94os+CYgd5XiQLESYqQsY8kNgPIdJ/Z6WDw\nmNBVgtULssXfdUzgb66M1U7/2ZMfRFE2DhqKmWu27ylMHqCsB2wLnsvyjeaY\nwoeABMxc5NMLE8L36aek66br4Kx4EF1c5HT1j1w5lY8MreoDFSEmP0vG6Zjb\nR/ldyfMOjBV/BdXQ8AT7zAP+jHoBy5aw5WdXH10gmaIQm0ch2+CmBlShx4na\n2hpQNCEy9sSHKZIXw+TjT6PpQBuEPmcwQUNiF2XNXOUf65wroDho8ASfdg6M\nuHoHlb2xnlXzqwV0d8jLeTyUXCPU+j6qfEMqHKMxMlnMK/79P0CagCEub2af\n7aaVGylkdWsT16Fo8TRpCMo/ocvqI6+LDhtRN+r0Sc8IwGlu6PDGw97dmvw3\nGynCenvqFgiCZE1x2P9lLwvAD2OxdQtgXDnwFotHEuC+8WIIR7ll2TYOeVwX\nJy4K9fL1h03qpOB+tHwUmMSnretD5c4IUgi4RVmDK/79kog096979uyElFKw\ni8j2UKKLOkAEKxFpyrJUwZXlhIY0gYk1vdB7l0oNghW9+iM9FyLUorRhIpBL\nbZSxOsrGag8poREDBtg9KeRbIu/Ajf/A76O5LW8vnppTqeyv8AzOLGKKn+n9\nrLC8\r\n=UeMX\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"1f588a2c15ba64e8fba55561b4a5503fee0129ad","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.99","@balena/jellyfish-metrics":"0.0.108","@balena/jellyfish-environment":"^2.2.31"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.10.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.0","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.24","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.39_1601213658820_0.1580102403709609","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.40-renovate-non-major-d96f66fa2dfca9349c37bf046ae781c39d671931":{"name":"@balena/jellyfish-core","version":"0.1.40-renovate-non-major-d96f66fa2dfca9349c37bf046ae781c39d671931","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.40-renovate-non-major-d96f66fa2dfca9349c37bf046ae781c39d671931","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b01c5a191cc2ea68059e54acca43a66c3960f05d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.40-renovate-non-major-d96f66fa2dfca9349c37bf046ae781c39d671931.tgz","fileCount":94,"integrity":"sha512-5S9+/nV9OLBuHzC5X2lIdYNystefL8Rlj1bw0MSz/+XrPyE8H2xJ8z8abIdqLZ+KMPinw/jnyKLiei1qIPlvKA==","signatures":[{"sig":"MEUCIGgQSF5ZX+z6h/fipt6V0otjDamgDIB85WBM7N15ZC0aAiEAvzlVtfZAcwFqHtRTk0itCctrXRL5S3Wf4Dl4E+fC7Bw=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":564543,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfcKDSCRA9TVsSAnZWagAAz8MP/RwvAmq62FhalHtsFyX+\nBYZqkSd97d/D4AMu5+zSGKBjszv1H2clH05Usk8W3nMDziz08c7gyz2P3ebq\ntkdjDtVyieA7jo+/neit/6Xk0KCX3Q8s+tLEAZE6ZHz+dxsD6CgY2JNEswFo\n/ybYZfAXAN5iinq3hBNhYSoXa9ofLmEt/8Fx986atOEsCnqH334nUEEsK6fc\nKzbUE2waIxtyOgFw3fDUwWuAPM8Bh8jrHl0U7BQba5u7RcMAN3xIilBFnu0x\nkO/7ORmITbXLJ/yfxn3m/He4SD6aomXwrNJ3cwuQxctxYsthSnvSaz2KbPJs\nvosaFJU/SgXMDuTOsNty37p3AlxVDfa6cd+aSPkI3li/NoWZVUhB06B1wOqc\nnAxSqpsIUva/lp1AOw97hw8bsCe9mmcXgXYc7xmvJZ3Wdh2WghGNuAxv88D7\nFEztnGnz5qpYOU2S9QUwwbSJsO09fW8gM9C+ApLFUJsxbAH8FkynfoGwH8kp\nBKOp6/4dT4nmhxtCnmmCeq7LXSfu/GxrAso3XGG+s459vB+nY5BCm4fJsySz\nVMKph2ZOcpF30z9tnx58XshFOokl+FLr0EDMYuI35SHbrLswISHLfYPU7Ahg\nAgMhbGWfZscNKh3qo/aOC8bW6Vye0+SX1K+fHskniH3K3JQkNNKU5wm/hgeQ\nu6N5\r\n=+3J1\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"d96f66fa2dfca9349c37bf046ae781c39d671931","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.99","@balena/jellyfish-metrics":"0.0.109","@balena/jellyfish-environment":"^2.2.31"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.10.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.0","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.24","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.40-renovate-non-major-d96f66fa2dfca9349c37bf046ae781c39d671931_1601216722320_0.6622219446670725","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.40":{"name":"@balena/jellyfish-core","version":"0.1.40","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.40","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5b4359b700deae5cdd08e04c713c0724034f5e37","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.40.tgz","fileCount":94,"integrity":"sha512-BVmmk5O35utU/exnyPUMl6WCaQT5s7j3RA00fMXc1cqds5aZelO7CMVSZAUMgXtPZbXohKx6G7OnEHGQRfKVXQ==","signatures":[{"sig":"MEQCIEz2JRWWaY+1/nHYqWzeslDtD91B3neYXUoCPEL/ZUqNAiBf6ymM9K6HAtkluVbdvyij5ZI/jBOmdlWbiO8gsQXU2Q==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":564483,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfcKIXCRA9TVsSAnZWagAAgH8P/iR6HV33rkhpeRCdrbbM\n/iYhEhLkWC5qdlgo1h5NFTHChs86YGLk+gzzgSpVAYF5MeYNtgKSb1UcAR2X\nTXbCvIzZOGN326uxELEaPFxjNsbjftJImaZiSqjNewKAKrZe/6Dyjmt5i3Tc\nl7+L2d28c2Qqp6PEDYHAWwHoVDA9GAj6e56YV+K1tTX2a+O0RT7rNIKPHU10\n9VEXwWOxA/pT9xLm3N+GSdf9/clJtNo66fMEbUAYtd3K+298QbEZDuvHsCxq\nGe+bjYA9jmg9Vzkq245Bsvd5X2WdkjLEOfsvdq0EJIPwbLVI3Hb4oFp2fpFV\n6EJgF90rWf9giPwC4F3SVASqDkc9uzKpN/b3eZnCj1gu3ZCIiosW5LatfURs\n1GpaBVJMfrK/i0ojRT/vARJToMRzAYEQ6Tmn3RRFnSeZVblPo6MSVxkAYR9P\ndRJvj2HSrZyE0WwU2J+46V8mGQxBDR1PkNrp5NYWuYoMjg9T5HwmYSD8t/+h\nzQ0Oeh3axJzoVFSvBjHe0YMAvX0GikTci1vRsMZYSxWRrr+HwyrnX+ZgeFCh\nVQTkZALLK/qzXg30quH93P64pTn4bQGC3x1wbfwxXDsD1b8edWohq8lM3t6a\nwczXoieauY/u/ou1dEUdlDPmxmKtS6yc271vHtV7QQp6NuSvS/nKBD67TyP/\nES+o\r\n=0+QV\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"30f8bff39123aee80363f5834b3c1815db174220","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.99","@balena/jellyfish-metrics":"0.0.109","@balena/jellyfish-environment":"^2.2.31"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.12.1","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.10.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.0","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.24","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.40_1601217047037_0.6523456502374716","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.41-renovate-non-major-49b0c89257c55437c2a93a34ca5789c8adaf33f2":{"name":"@balena/jellyfish-core","version":"0.1.41-renovate-non-major-49b0c89257c55437c2a93a34ca5789c8adaf33f2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.41-renovate-non-major-49b0c89257c55437c2a93a34ca5789c8adaf33f2","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e69ecf51f7cebbd6e0b35a8b5bd6bb0c642f8acc","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.41-renovate-non-major-49b0c89257c55437c2a93a34ca5789c8adaf33f2.tgz","fileCount":94,"integrity":"sha512-uMEvYn9Kh60DLQNVphBRUjqlK9NP9eOAXEnLb4TJbuB2radq7NTP6xhQFHx0OzWky20LCIFmsZcNT6pkz1omgQ==","signatures":[{"sig":"MEUCIQDGTg1xYqH8kZ6SOOF9eYA6T+Zh6yUfEhdvvMygjBsx1QIgHCEnHoBgKdcxD/suyPCNTbcW5giiMSQu1XkVjuMPk+Y=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":564625,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfcLPVCRA9TVsSAnZWagAAqekQAJwcKM28UNudM4HDQxio\nuC3aX0xQhXponfPDeCB6Lbu5ZSfj0I86Y/Pu1fvD4rMBwWiICH3u0C8VMOeo\nSCfcw6p56H75pUmQKs+D76PpmgHhc2yKP8EvC3GzgGVuvT1cKWwydwhg8cmL\n+IoojJil6yLvusqEobQDNcmvYOMOSW/wIHNb+j3T0iyG/KnnIDr5+nsMfhup\nl9wPdoUL2GvWli4XKBoH9qRqiradt/2dbL9ScnI6cTJBHWHRGuLAbxm9FHZS\ntgL9aQq7w2XVrqKnzjqFAlZ93Uvidaw1f89btlnC14lI947gpssJyqUJxJF4\naCbbONoEhsqwwaYvTabsUC0n7DvV0HkNtu07rBavezuTS30yDU4x4/6rCQK2\nAVFN+Nk65pp9O/Sy7/aOZfxnCF9uZCtHsCakzemmMlEpSKuaU+LvOkOp/TxM\nPdO71S0ku0CbFtFXXiXCHFi/iGXc75sKiLRMiqT8f3pyEO74eL7hGTIyzHbX\nplpla8TCD3Ubmub4oyIrcoLzgAz82jmAXo34JT49Xc5mNAqGycXfsiRgU/9d\n9k5jGYU30E9WnvqJCYx+iNUYkqsRjhG4ueYjdA2TZSEq1zgxb6MoIuNqLZO4\nDZiCXltauba2cJrNU9uNjfbJU43krkDwiB+PnRVTSqCwKbaK/pV8EfYibVoL\n6wbe\r\n=o38G\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"49b0c89257c55437c2a93a34ca5789c8adaf33f2","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.99","@balena/jellyfish-metrics":"0.0.109","@balena/jellyfish-environment":"^2.2.31"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.10.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.0","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.24","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.41-renovate-non-major-49b0c89257c55437c2a93a34ca5789c8adaf33f2_1601221588680_0.6541232592282744","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.41-renovate-non-major-b25b39f16501d6342e6ec9efd9a23d2d9f996b8a":{"name":"@balena/jellyfish-core","version":"0.1.41-renovate-non-major-b25b39f16501d6342e6ec9efd9a23d2d9f996b8a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.41-renovate-non-major-b25b39f16501d6342e6ec9efd9a23d2d9f996b8a","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3ca3a3943f4755aed97e067d0bfbc857d4bba659","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.41-renovate-non-major-b25b39f16501d6342e6ec9efd9a23d2d9f996b8a.tgz","fileCount":94,"integrity":"sha512-QWjDv57bYiiR9KNwRnxNgxlDQb+SEWGkJNHFJHbxMjL6N3WsmQWjuqRtxa+mfMITUM4rBghrF6Ng+S7kiG4Kig==","signatures":[{"sig":"MEUCIBGM0/kLp7ctUxqwX8HXN0CurXM6fCFRGB0GNa0sfO85AiEA5jmDMaktcfhHztkgvZAr52gmIL3QRQHYpTots9Y7EdY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":564610,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfcL0HCRA9TVsSAnZWagAAVckP/3fFdt77iLKV/NEZ94tc\nMV13HBuK4Lq5PTcaeaExHFuHYs5etw00xW8KTLPA3GMOqFV4cNkJkhXymAXL\nX2x0WGnLpwCpF9SIx1LEkBJl8NIWbHmSaiyTJihzTsOBIYDiA3jI4hrrOuZQ\ncs30FqeF716ayFfiWuxloMe3EZG7d3CX8hALIZFbWZaXke68nDoE2E9lQxXC\nzdH0R4iD+Yo68ClS9fMPz2mssgFISAtMYnN8AfMyAOADWA1TtGfaNpxTmCWT\nPpj375LNGIGgfR1qdqU+kDiJ63ERtqgsNcQwpFnByS/SxtTa9vSXjVsxwmj5\nkfEKcIuH6tmhsb+E7wysf4tPHJWgh+jL9Lhvk47stcdhAE9Ux/PuJ4EKyADO\newk6ToD6VG98VKx607cZvJ9ThFqmjZKDdUpN+dl7HqWSf3RWV78DEcydmkw6\nzgiDlBsQlGdZwLjqckRgb2CX1uS29CUkT94GbJyUGrUGdZi0LU5Dh74E6L7+\nkF9qBO6jVqZo5mo/cUoUKvWVNdW2jhMwm+O85GawD8UZ1eVQs6rytQ1S/Cjh\n9w6VUTYpQqJGGUkr1/qMlyoAngVGtVsd/Uk5SlWJLoY/La2iVL6oThrAOcOC\n/UTpWiVslK52swnzw4OFb4uYWoPs2AWg3FBBYoXcRNz5SjMxubiOpuZuemcd\nVrgL\r\n=/jsD\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"b25b39f16501d6342e6ec9efd9a23d2d9f996b8a","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.100","@balena/jellyfish-metrics":"0.0.110","@balena/jellyfish-environment":"^2.2.32"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.10.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.0","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.25","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.41-renovate-non-major-b25b39f16501d6342e6ec9efd9a23d2d9f996b8a_1601223942637_0.9681069554100576","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.41":{"name":"@balena/jellyfish-core","version":"0.1.41","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.41","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5d10d0b468718b9531e999d489d676030585a996","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.41.tgz","fileCount":94,"integrity":"sha512-FWF3uVCjjk04HqcUAFSF+Gs+frwR1AtvSW6yDg2WCQvaecZna9XQl55mbloWemBEIwe4kTSO20GEDXLpuBO9Pg==","signatures":[{"sig":"MEYCIQDwZ7OM+SQce/LMJi3zfJhfUH4G2m9eKAQdriAy+YRhrwIhAN/u4hELyJJMQdtzIBNYWZVHtsYjH79N0lot9wOdsa7o","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":564550,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfcL5uCRA9TVsSAnZWagAAeTcP/A4wf+sni+SUYgEzDahy\nQwMM8J6EWtvEcFqTAvi5Plh4vGUYMRat1v+njAfXnD5OZL6RTcFGJOidfQ0Q\nepxateRtW5XiF+09m8irMMqnFdCuhZKvRX00ZxdjkwlodEdLFwwCk0Y5Klpo\nnTgDmSF0S59PFy5GqNvRc+sqJlbJus7kP+kCmsqLIQnDFcrxXc9Xlq8VZprW\n7HpJWa0mSPZm9t48zSAwl0ntQ+NrvlPNH5vkGaJ5jDjPv0F69SUUbAcvsW9b\nmFFTj3eXFmZo96W2SfuayYPcIXxYctPU5rLAfOrrWW/5y0JdZzOIKMNcwrAT\ntdJIkFi3s1XQiMtrdvqVQVWHhHCCDp3SuHQckJdnHZGkB2WC2PdVPKQ88QhU\nhm1epjm7o8shWJLa8sCFA+nqZe1dwVbWBHbE21D08nM1AXUwXeLT6Xex3Pwq\nw5rVKJ7eET0dklzRK8aB1kBSxa8XhQbad3Cjl4d161WVXRo5L128Iyj+IEKy\nbM44EpwNe9xSYMLmr02fVREUuYi2I6OrRH6dcePql8LJPIcCjYDA/2S7vTt0\nOZDlUSzBbz8+a9Rs1yWNYOQrJWtxwOCj7ctt5re0W808H+20snLMw4LYmCoF\nBDDR/7tGEyLuB2vMif4tTuDN41j1YmWILQx7Nk6cogBkFSmCKjoMcgA0/cET\nZr68\r\n=j4KQ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"8217e8a0eab847b5436b5058832d48d03032fd20","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.100","@balena/jellyfish-metrics":"0.0.110","@balena/jellyfish-environment":"^2.2.32"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.10.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.0","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.25","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.41_1601224301502_0.4589877640715041","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.42-renovate-non-major-e18af3c425d27a8331a06ba54b1c55bb40b63717":{"name":"@balena/jellyfish-core","version":"0.1.42-renovate-non-major-e18af3c425d27a8331a06ba54b1c55bb40b63717","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.42-renovate-non-major-e18af3c425d27a8331a06ba54b1c55bb40b63717","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"7ee411f0c7a7df6038dc3440da2369f9d074b2cd","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.42-renovate-non-major-e18af3c425d27a8331a06ba54b1c55bb40b63717.tgz","fileCount":94,"integrity":"sha512-K/80HBCdgkGr8tFKvdY1zWNQu5Qi5HFhVN83kDcDJ8rRYLQJXWdgAFGUuEIM+IRisUVD/ZTtjTETHJ4YXWbTfQ==","signatures":[{"sig":"MEUCIFMz2DobJjfYTBZf22AhMVe769vaTAQ99dj5dCzIejV+AiEAp2YPZfooJWWY1/SY44dkqLJwhAE43We4k1K5SRCuPOs=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":564676,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfcM0TCRA9TVsSAnZWagAAyf8P/1UwHSmOhtC+s2tL2fha\n/HD+SyOThIs1z4BEypMAXAoAlJ3rD46pPQpK0FlxqzXCkE64tuUSXrfL0dAP\nwypips7EzwG2wH6CibPr2I/a3yK2VnmYniPb0UjLMVQLBZ8OBBnIMiI6gbA8\neTRbGZKcK/L1h90QqusaHr3HVjzeUnqneo3RMA098RsDbhxUiydKJf5IC/Pd\n65qOqFIvtGyeinoFHW/DtnqbvLxQfkqLr0BJb0JBUyp07SeITmCOqhvGoYyu\nmbZ2145qFCd3pfX9vLORqx9QYuz3fhlpoEnUMzL3GzLnXe13nLbTI3ifeF90\nuBZ6QgiyOR0TGKUtn6+9q4QJtGgjyGk/iBXws0joW7tTWVeR18OKwiwnGKIA\n+J6+KGNoNjuYV1Pu+3E6DB+wqyppVzpkxidnErJWodgf5OWdBE0+aJ6YQFw8\nfOyCoz4Yv5yl7HWFzyVruWtefYd2sPghOtYhdzfZo4pSCPaZky01Dp7JvWV5\nFabHedUwAQwqVNS28xpSEPJrlZJdl+In2XSVeFt8MPhM/IZ2VUChrpDzyWUJ\n7hdt8Ap1QBa2mOPRb2ML8fYdw19hZeD5ao1I9aG0TsY3HW7IvDVhtOowJR/c\nySQhyXNaqaVHcnwBESpi7dlcinlCIKJ32XRuk3WRErc+Gs4eh+TRJQ4kn2Bb\nJs2i\r\n=Xlp9\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"e18af3c425d27a8331a06ba54b1c55bb40b63717","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.101","@balena/jellyfish-metrics":"0.0.111","@balena/jellyfish-environment":"^2.2.32"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.10.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.0","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.25","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.42-renovate-non-major-e18af3c425d27a8331a06ba54b1c55bb40b63717_1601228050526_0.05022724884003016","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.42":{"name":"@balena/jellyfish-core","version":"0.1.42","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.42","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"808f5e9052856af2b7af91bd93bdc5030f4f4d7d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.42.tgz","fileCount":94,"integrity":"sha512-5qvhl3sHNLzOBA6pAfqtk/BZy/HUYxcqxGubz04wv8UE0fd/3dNPjTfT+8rYhV5mcIO2u57Y+h4RcdL3xZ51Rg==","signatures":[{"sig":"MEYCIQC8Nl1uvGEAdMkNeTuT6bEN4btXmOYlYXJRYnrC8WVBBAIhAM3RwTqVnZOxYGaRdD2XhJz2vVVdOfXuJpfyuWQUjMDZ","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":564616,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfcM5xCRA9TVsSAnZWagAA4UsP/3Vvz6ZZ6/JbH7rgA80s\nOuNkK+bM4nNDYXGkZJdk6BQMfkgdW9BZJQqZBKM8W1fQqZPv5ahstd0X0EEc\nIxDQ1Zu3/zSwAmq5nAI3o3ke8f+RxyKby50h7U4+F8ZyB7QQS5ioUCkSE26d\nOcU411+ah0J4RLnn8L80crOehD9YSFYEjzDkWAeCieWRz0t07aDzrbVUMmLY\ntTLpx02WI5W+QesknfkAe3ZBIiC48e7det/gPcDcJElEw9vxf1mlNw39bN6t\n/4CXERyAFxVRBhkndK+w0e53T9jGUqa5C83iuWyOwRbklEAn+LVB1OCoKyo9\n5afsOCtOhbsu6jt0uJ4CAXWsiP7B3HstMId4NX4STXq0oqs7y/bfkWEhW9kJ\nXG9TlV4WiJs0RA/Zh75/cLWS6lCm3WlKgq9JNeOlaeY+IezVy66IjJKVe0vl\nN8/n1DftvZfv7qsYDHtGEnhSZ6Eg69Jj8a3WGXVaFWNTq2IO15LdEJDZjX8i\nlgnjXF4+Yv+pPqX81HuUv21mKXbf0ATyASZRXgk5C8BDBFkwRRAbbj+EPswM\nWVqHftf1yBryPc0aiJ6Cp9BsDmUAc/w6L5Ul56EyRPwaZ6jbsQ26e18SaWX1\niGvzT9Tf6fsj2PeBbfiaCfhYk8FSY5do1rQRp5wurBjaDHvGOIYBYBAa+Z8j\n4T1k\r\n=Bnqq\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"ff2798fd8945f294f518070524b8981b6a77d81a","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.101","@balena/jellyfish-metrics":"0.0.111","@balena/jellyfish-environment":"^2.2.32"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.10.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.0","eslint-plugin-import":"^2.22.0","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.25","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.42_1601228400532_0.525882752418638","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.43-renovate-non-major-b2007c977adb1df6a6128f9d9e328ae5a7236682":{"name":"@balena/jellyfish-core","version":"0.1.43-renovate-non-major-b2007c977adb1df6a6128f9d9e328ae5a7236682","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.43-renovate-non-major-b2007c977adb1df6a6128f9d9e328ae5a7236682","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"94baf1be956a9827b146503a8c443f54184367f1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.43-renovate-non-major-b2007c977adb1df6a6128f9d9e328ae5a7236682.tgz","fileCount":94,"integrity":"sha512-GKDOiPPPlhVXzSYJqP+NadzzoKjBIrU0n1Yuxq47Gez1loAs5KDA8mp1/50lWhtWlYqGYuoSWpvW5D7QWGoN5w==","signatures":[{"sig":"MEQCIEy7991FXqxxQeq8vysV51jaks0/V4jtyLVhWtZNbphOAiAJJAdtEg6ru9M3hKSOeKeCjOJjZlJGJbRlMECF4wBYWA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":564742,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfcUA2CRA9TVsSAnZWagAAZ0MQAIyWN+DGoZ5UuYknolee\n8t/zHIGxju5c7sNyvRA9/+crNW4hcX11pIsJxWrdBIzqu6HeMJxaNqLOHrqv\n2lql2ugVAMbQTR9M3NIM3gTC4kGvb7vWhPELPcZS2+JOV+U0vU9FuFpzro2/\n5E3yoQojkd22jHktmFc6RQq1MXbZRaZa/2KqZiArUzsfmNc0o/kNobgaUdyR\nsbIkry64Y730r0SkmBR69WenSwHy528bYoIgp1b8XzgXDQ5wli0VCdxvh6Ta\nljQvBJp9RLixd1V2DGPTr2iGgvwF+W07t9ZJlBl5dpZ7eIz2awdLUHa/J26I\n3QteU5zFZR32wBN5j0GeFvrXlZnTBRpgKPm0TycvtP4WPLxVzn0hvx16IOzq\n6KlS3l11AYrX/kFlpkhx1XYoV1OURU2FWT6YbyuyIlj76s713rh7wVi+LruK\nqs2niEfOX2+tR2GglBOKfUCabw8dTaIANe25tQWdjNkfgi751LaR4IGC3DTn\n5uCZxRLbL2RUc24rIVlcopO06yvRBU3SUuUHpNyvxLP7p1vwDvRVgtegh2sU\nfxMDqV0M0dBBeOP0gs88f+pHiYGHbAb8ifkkL8eKhvQka3sJtuYmduYGWdPr\n6T94Mi3lGSaUZ3h3Lqdgs7FRNhJ1SHES4Whuk9T3G62pSSGSGYfKtD9q9fVT\n1/cS\r\n=GIqK\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"b2007c977adb1df6a6128f9d9e328ae5a7236682","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.101","@balena/jellyfish-metrics":"0.0.112","@balena/jellyfish-environment":"^2.2.32"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.10.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.25","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.43-renovate-non-major-b2007c977adb1df6a6128f9d9e328ae5a7236682_1601257526120_0.8676456813568314","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.43":{"name":"@balena/jellyfish-core","version":"0.1.43","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.43","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d1de09653325de02fa805e60ba8e35db81a23769","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.43.tgz","fileCount":94,"integrity":"sha512-9AUr03MbZ4AD8JBV/L8CAxUrlmQcPSWjbQi+Zzm+19Zm2bX27ZZBQSlhf1urObwv+ZGqu6EgZBR8wqyMF+Dd9g==","signatures":[{"sig":"MEUCIQDdsKcZL3oCOAOucqy3qo5EaOyXUBPWh8ZaIlmo+FWVcQIgH+T8vMkst/3H7CZZwBin61BD5k9Vs8EQbsJjRYRO+r0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":564682,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfcUH+CRA9TVsSAnZWagAA7GYQAI6nMX+lzBd1ue76tUFy\npafA3N/WQd9GTMi+hTWBaaBnpus6S4Pijrklz8jaS6ehF2LM4l0Yghe0Dcnr\nL4XnGv/bVsgH7Ydk8MGCUHqjZbSIoYyD9KcMzzP6aOjT/1Gnwx+IOSle/F6Z\nYXdSIkj3LgEpmrM9DSOVkRH+RGs88+aqa5xB/BlB6SLGlY+JTRdK2p5gQLrb\nP+u7IV75Y4k4qPtZxxlIQfMD7A2NZs8qWol8ZVSbvuWhtmK0qlIjtlbo45Ha\nG3LUOdJ0P57+GoiCMbRmcrW0flhNV8qa5l2ugHmCaisxBZv7AcqA6iDTci4d\nIIFsZDqPFUvvfWoTH37MhkRNfyotOB/ToEew85KD54pH+ihFBgIj5SCWNJfE\nPp9fBHIJuyvVGSHWTP9U6O6rRDeNzmGQuW6pmhcqMBkYTjIoizBjAPNUwjcg\nlUdDF+gCJBQ53GW8c4dRe51L3jnl8MSgCNWEhQXeagD1aN/KveeafA9sQpGm\n2f0c9H2rT243stcm6QuW7LWNxYQ8QBW2UVfNiVfLS3UCFklEex/JXMkm37wD\nSAPoBa5ZsppC/ByLDaPSABiNpnIJIG1p7mV8jQjwFfH2iTK0XLCJnUvxfrcZ\nLL8sCh8SY3LCgPt1VgG+RTh3aGvYytPS9v2ktu3sOvqsd+seN36GSauKuMlf\nhPl3\r\n=p2ER\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"412ea8d27b33a2da2f1ccd1fcc1a39a6cb5de94a","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.101","@balena/jellyfish-metrics":"0.0.112","@balena/jellyfish-environment":"^2.2.32"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.10.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.25","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.43_1601257982280_0.9247210376130743","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.43-renovate-non-major-e377390138f425486f7c1855ca8cf3944072d003":{"name":"@balena/jellyfish-core","version":"0.1.43-renovate-non-major-e377390138f425486f7c1855ca8cf3944072d003","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.43-renovate-non-major-e377390138f425486f7c1855ca8cf3944072d003","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"7fba462f1c6015adbe30445ee6d5ebf3b0d657f5","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.43-renovate-non-major-e377390138f425486f7c1855ca8cf3944072d003.tgz","fileCount":94,"integrity":"sha512-2pb4PHMEJtMN6/zspiGRx5cIsR+hhBVnmaWbVXu0u5NLKyQ0y+6F91CnOaJAhK2E3MDNbgN7xRR87Xs7t0b6UQ==","signatures":[{"sig":"MEQCIDUBRtIPaiPZaDm7ABaeJK9Rd1VqhZGSHYdeibgWTCi6AiACZFjL/eALIxE0/sXYyYJf2o02N80u5sO3RW0Qmfmy6g==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":564818,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfcUJLCRA9TVsSAnZWagAAzfAP/2w5YvkGM8Bp4maotelT\nKjhhn0YH7sPgOVxxRhRJc07fBZ2I2t/NaqO7eKvKAeZT006R2uzQcPXCCAuN\ngm5y2F01SSFSpqdagDGJjmq2Pg01wjJ6XR5NSRbGFAC4B422SMj+TU8Pp+iP\njJomq/uf3/AvwU0n4hx3x1G23pbcbPDsLMMB+46kH1GY5XGyV/52Fxb0Q/On\n1Hji+X5ryhKuL4+ORzj+9MzLsHBxXtOS0KxmXXHcVV3JbuoN9sGJhomMbbyw\ndC651Z/mJcxeHeQh9ZGAFC/cNR7ne1Lt/EvXd7k3Su/d8U/Yer9UeOdfz6XA\n3YQB4LgMXMrW8Eow/XFFljwSbt40dbYvHGT22yHzLhJV/S2uKlGbf+ktGlmA\n1JS/VHxQcmgtEKqFdRFY3GwW4hdXrLV8EY2QXYuzYrGi4PA3rPAzny8j8LSp\nid5wYV5zoUBgt1ZiVe79rW584XEUdeSgZb7EK8TreE0y6abhGyqYhtYBOruB\n7zsfoEkA9vWJIyPtfVZsH2GIYPbWxeAgHmDHXT3SXDrPRby0a0Q264buh3Zu\nQYtzXQNUnGPO4osiyLDhztqbdRwHZNZtptkfEshst48m/Dy1OORTmFTb78WY\nTbZRanihKXtS3W3CCoJs53UgRNfvU+4yvwonyuhu/yOpTwKbckXwYJvERVbz\nTWKm\r\n=bQgU\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"e377390138f425486f7c1855ca8cf3944072d003","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.101","@balena/jellyfish-metrics":"0.0.112","@balena/jellyfish-environment":"^2.2.32"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.10.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.26","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.43-renovate-non-major-e377390138f425486f7c1855ca8cf3944072d003_1601258059340_0.0968911072495302","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.44-renovate-non-major-40942da98f56c1ca7b950fdce940452c193a5e5a":{"name":"@balena/jellyfish-core","version":"0.1.44-renovate-non-major-40942da98f56c1ca7b950fdce940452c193a5e5a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.44-renovate-non-major-40942da98f56c1ca7b950fdce940452c193a5e5a","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2375cabeb1656c996491886d43a54f301bb7f593","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.44-renovate-non-major-40942da98f56c1ca7b950fdce940452c193a5e5a.tgz","fileCount":94,"integrity":"sha512-+u7K+1R/uZXtLLJ3uuwWisSSKY1WKS8sZ/l2hfFLmvEj/EAqaXniUeliVOWk1+5rWQD/gduu02UVmzes5cbYEA==","signatures":[{"sig":"MEYCIQCqjdB+H3D38zfBsKUqF5EIf5wR928LTew4F/9z7HMAPQIhAIzcet9e2EnDN4UJuZ+dYFFnrUDLoDzwOSsQVmbZu7qZ","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":564843,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfcUMvCRA9TVsSAnZWagAAEAUP/1VPhQICCMVvSjSMGziD\ntioo4KI9TcR6KwBxR3G6Agz4wuARmeljoNGDELRDWgAt2FVQOtg+O9LK9047\nYBPacSW+O0OAXOC4edrIPWrs8h9/t+DotsdjSDw0e4B2ExiDBZwnKjpr8e3h\neEW8C2vHJKAN7RN9iugFWsPSXGUqpXl5MuWJJGECzwFGRakDffs3p6h+y82P\nTzhw5HBjxJeblxhsfMhpNjHYnoX1tA1oSaJ3sTCMJM+fKziluusph4Xf42G4\nwdsyuROLhK22Pb6qZnOwiqJR7wCAceq0MWvZGmrS5f2JF6sSfCa6M7LoY0Qp\nIPPwWYCBdar62C4HloUcvTnLcDQXAzF4PFjq3TvQaowO/iMg6WEwsrIl+gHL\n8ajxCyIEXOgXlMq8I/oiG4Qf68SyRyBZfMBIjMC3voi5vDAQxeh2fiLQon1F\nbCfZH5uQu3F+HViOmkl7wNDtRIw+dkW/jee35G1yDzPILowuOSusu7RTh8wl\nCflhO8vWTMuCGPSCvcXYYGklSwjYxdIwPdWNqO9uC4RGG6WI+jGT5PU9d7m7\ncbsxxJMHDVLrvdi2CX0/qMgjEfLSE7z/XblLzAu4slRRfhurlLEmYVA3UFXe\nIzGCztqrMge5j6Fz8ScozA3DxfHGB9/NaC9YDszG4d0+O9lWBZj5tdkhYk2p\ndXz/\r\n=pVls\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"40942da98f56c1ca7b950fdce940452c193a5e5a","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.101","@balena/jellyfish-metrics":"0.0.112","@balena/jellyfish-environment":"^2.2.32"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.10.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.26","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.44-renovate-non-major-40942da98f56c1ca7b950fdce940452c193a5e5a_1601258287213_0.11775743128981753","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.44-renovate-non-major-4969d290c42b523d766d7aa1d1955de3f22f42a1":{"name":"@balena/jellyfish-core","version":"0.1.44-renovate-non-major-4969d290c42b523d766d7aa1d1955de3f22f42a1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.44-renovate-non-major-4969d290c42b523d766d7aa1d1955de3f22f42a1","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c3e1336dce28718509cb2241f6833ffa4544d300","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.44-renovate-non-major-4969d290c42b523d766d7aa1d1955de3f22f42a1.tgz","fileCount":94,"integrity":"sha512-quup92FeDtPonoPDTIuTzJmO54dfGyBZ8/3dHiwvwHQ38flCYO4KKm/l6fqZPZR6lrhnXQ4wdKAAvs24K5CYbA==","signatures":[{"sig":"MEYCIQDMzu1yWjfHh9nrDrgRKxkQMEWvb74iCRGeTWZRbsHWBAIhAJ+VXH98gyMfNRg//w+uS+0HZ8mWWOz+Cu3XgBwZrlI6","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":564808,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfcUQaCRA9TVsSAnZWagAAoKUP+QDJz5IKz5jdy6CHnIPv\ncYoQ6LUAeUQwQLfe7vzKE4H/dKtvr10aCYtenY212MAsJC4F0QQAeqpcdXQd\n48U1o7v4MCTDwhuz7IBfCvT7D6yPA/eoaEeHKLtQsL6gC1BJSLbBjdY+50OC\nQGSvOkYGYou6kUDV38TJuP5GKV1G32z1mtvh1T7F6Hhydv6LRMjRQdHXj5Q+\nRHxpOiOi5qwLW1UlGFIy2wRXwiiToCfTvyKM0/GKsP2gFqZMPjQT4/2aGaTf\nF7fWkTcEwUcTKpUtTG8Xse5YbXgFmFNCC4gfDsF03p5hJxpzgh4QP0LcQ+od\nK9HRf2wSOF5uyB0miU8WNzu9y17SxQ+u2bslJMsjylw5uFEG5untNcNuZbmK\n05wXRP0BEH9rBnIGz6l6lSaoLV2RTg4Hn81gT5XT0hf2AZ8aXgBRy7Q+b9rB\nfl2vbtAhyFsct2J7zL79VhH5EMgan6dXeVuaf/bo2ECLofPCXK+REfGzk/wk\nDTonm6jQjjhAcDtVC2EIa+Joxn2R1dYsNGQ+P9Ondp3gqxcO6mSLn5o1yYPV\nhGYYLFeXnZkOCr+Xz4GDt2JVF69LHUCYNuVmtX6Ic+O3b31HT1qudpsLmvtL\n0mBbrukgZHClPvBkscoPG6hEdHjV7vYsGBZlExV6RaziMfr8T2LAD7N9wDCk\n8X3K\r\n=JH3R\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"4969d290c42b523d766d7aa1d1955de3f22f42a1","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.101","@balena/jellyfish-metrics":"0.0.112","@balena/jellyfish-environment":"^2.2.33"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.10.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.26","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.44-renovate-non-major-4969d290c42b523d766d7aa1d1955de3f22f42a1_1601258521861_0.7941116577730551","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.44":{"name":"@balena/jellyfish-core","version":"0.1.44","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.44","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"729a34331d35f4df89b4e4b24293b3f3fbacb242","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.44.tgz","fileCount":94,"integrity":"sha512-G3WZqpcfk+ocIsmQrZ0SftjJnyjdcuDKJjK5/7JVCEuKuWEiZ+0hJGMMf+W7+JrCXkccj/ECy5Eq9ACBeeYb8g==","signatures":[{"sig":"MEUCIQDOTFQPEqjsnwEusZ7Hw1SKDrICYC8lrryJdgMF2ls8aQIgVM3BQ+ZaE2sL+tqJfMpLQSU1EsrpYaN9ANJktkWuV+w=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":564748,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfcUV6CRA9TVsSAnZWagAAlHcP/Rp28ZIhKg3Zcg6iv/kP\nthTr9TzyMMOqiKTJYKPzRbHa6LMsTNo+3OBxfrM/iyvNVLwxMHhQR2QQPk1Z\n6qaqZOVoIt1hP/1Djw2qurgZzuxMSIH4b9eXkz8TaElVWObIOsGE2JThIdmw\nIUGTIS53A/+VxrEdN1tass2wy2cOBXsd7P8LQtJ3GW7794clx80533aFc5Sd\nPw5GF/KbceO6GVNuyX5JgfxRYbdHuunx1BUcOsaams6E9wRJU6/yk8EgkpbN\nLZVN3G4AQZHWpiWDYp3ZlI6QhPt9lyzv+IJA2soakzyJ4J/McD+KXVbw4YaK\n51fs6wtXRVHEeIZoUo1u9cSmriNcH2Hf952hbBS+t+XXTqkciZCgadKt4kG4\nIb9Si+Miyajvz9t3hAMiMLbr5bRkAKU2lMYMIQtTacDlkoVMAwz1ASo39hdH\nr5SiloaASuiw3Mk6L4TSbjOvH4jkoZrK7z0cGFl3w8dFL+sVWMSZLOAJEi0y\nH0WMNNHGuWwo5VZeAJZtOzPyluFjic44LCzrOcPSCPn1BeOmQ1jWn6zf9jBx\nmhiT7jihVS++aPlpJyMB6lhnrEsTktmWbkBQafnouVCR3XFZD8JYpTDx5jFg\n954kyCUOUD5D8BQb11sP2vEJ4ai8WVNep0GaPRr5DEnr/8GT/EmtPti0xFHZ\nbY1P\r\n=WW4w\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"e38245dc8fccca8553b53dce4c420156782128db","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.101","@balena/jellyfish-metrics":"0.0.112","@balena/jellyfish-environment":"^2.2.33"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.10.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.26","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.44_1601258874012_0.3611541807040821","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.45-renovate-non-major-7962407aae3bc297012febc7b925050718003c41":{"name":"@balena/jellyfish-core","version":"0.1.45-renovate-non-major-7962407aae3bc297012febc7b925050718003c41","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.45-renovate-non-major-7962407aae3bc297012febc7b925050718003c41","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"52ef7267a487c881df8dfb457b01255ffe1adff3","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.45-renovate-non-major-7962407aae3bc297012febc7b925050718003c41.tgz","fileCount":94,"integrity":"sha512-GlyEoHiusywfmb6Kt8r2bwlU6+JDbvmChx5ie6mYEwAq+21Okf9idJVxRk0PUqMTKqNNoRfft9gceV8k3qhDUw==","signatures":[{"sig":"MEUCICiH3HS92H60hflSxL4bI0FziBaYN6GkqsNLe4MD4G3vAiEAq5FMRWQDX7T0ZwgZZsLoFNh6EFBEUmh2FW7zyfs6eP0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":564874,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfcV5aCRA9TVsSAnZWagAAvsAP/iGZfNhQq6fBAX6+2GCj\nITnjlvDMzymojp1vny7ayRKCRy7hrmxBF4NWZx1K6DJpPYG9IUEkh1LY67gA\nvBvlc8tUGc+JUAkdk3cxIjkOD2DyuIL69wqzsL0Scv25YfE5vlNQdK/VWTzY\nsleyHW6P2wmD1AWJbEY4CFQwtLWthuh3aKbmP5xStbg0wpv5htPXUAsqkQKD\n1BEEWBeq/C6K0byw/+szOLUoRIjetFODrvtbVQ6yXTRCdzuUd7H0SAS3T7UR\nJZ3FRTzlInCMkZIHFpo4c6DG8FGkkr1W24Ean8wQTPcuhnlcfZIuAnbcGFdH\nvh0AQhr1bjkbZFvYghMDbxWOPZmydATb7b6dzzSfqCASxs8nBQ8wlHjtx2/x\nP62EClrS8NhxMlieypG/dzNkSWQ5vWhBwD/gAEQKWxrYhjRhIxXoiX6sezyP\n7JLnpumxrzWdaXczjc8DHcYnLlJ/g65dtUoW73L6Uk8Iq9NW6nZQd9yh6xFx\ni7WWLEFNxK2ZWDn0CD2dUcMUMZbCywRVVQvfifsvdzpwMmk4GcGVXFuA3+tl\nsANYvctq8SqCvoWOWVdmBBuP/kU0iUORamDGbzgMFVFVjGFgWOdj11PyYxbq\nSEcQ+NghnZ8w+PBaUaYZty7aJv4R57NrSNZeGA9J3sAb0c+71Qw3Mh1d325k\ntR6g\r\n=Y282\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"7962407aae3bc297012febc7b925050718003c41","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.102","@balena/jellyfish-metrics":"0.0.114","@balena/jellyfish-environment":"^2.2.33"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.10.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.26","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.45-renovate-non-major-7962407aae3bc297012febc7b925050718003c41_1601265242132_0.7460599822343794","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.45":{"name":"@balena/jellyfish-core","version":"0.1.45","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.45","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"33c169135ed3c1d49d2c4e625f992520532f9d77","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.45.tgz","fileCount":94,"integrity":"sha512-1arlrWNjU5M2MF62l305fBGKF0M/sz1cFjcMWyOhtACSI2eso0BKyXT9boqbeo7r6DglOCQ8KluYTg4wiPlCmg==","signatures":[{"sig":"MEYCIQDfUdP2tK9UH4ilKBahSUMhnhm35wVGmH+D3b8Pe/H96wIhANn4BGQ0CN18G46KIjiQo3mKA/5jUXv1Sd75Ne4oYsNb","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":564814,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfcV/ICRA9TVsSAnZWagAAQ0oQAIbIw6fxWCgBEdhdUkWN\nCj+WqH59r55eR8bYFDo5+b8FnJOPSkFLgSAgxIiwSM+6oM6unu+rrz4bFcLy\nETKH8QfLP7eOCE+ST2uE7uZxP+iBDhZmSjqBGVcMAaPHE8kfwuj/r16DNhF0\nFXNqcyDYUM/0RNq4m8RG97tysci7U46Mw8svTAbdKv59QWzm7TxfN1lABJER\nybTqoYSw7RJg2S80tkbA1Xt71FXdldgMjxJDDFvy/i0YT4898iwyyNaXTF1h\nsEdEg4IDPoqoLh1eiw9gj+cBZfP20STLN0Suv6cm+fEJ11yV90aj77TOa36M\n/XU2yWfvvbqHUg3AROtfV7EB0LsgPWRE2Fw7Ex+PZL6QTLoRaCFj8FHeM+XD\nxgb+R9irEdsivjvjHp2cA9jKP59ndrmb3EDUemEO1u1k7LlENXRm63nr/sRt\n3MyYcgXu9eVD8XNrTKj+xmw25KZx3tBu0KtWiw4Q0lnxnNzi9TPD2O5OTuxz\n22agHW8QlXUKxrFdgtJDX3wFThLlBHJIDJvErcq3DUmcea9628AjEmVg3sIG\n3BGqDDHLJJyH4Tq91IOHE1j4rb7NyS5S0A2g4gWtkkdzPwkg0MaIPLqe32tT\nV8JO37t+LtDsZPU+QUSeevy4DKf5eyW1YBaP/aLkALtYbS7fvSZZt8LdqmPL\nVSKm\r\n=lzQ+\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"1f40653e4d007562cca0593d4e3c27c7fd3ada63","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.102","@balena/jellyfish-metrics":"0.0.114","@balena/jellyfish-environment":"^2.2.33"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.10.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.26","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.45_1601265608151_0.7905017736392315","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.46-renovate-non-major-7121a4183a84a0d39b6cbbe4e572667ad7c58061":{"name":"@balena/jellyfish-core","version":"0.1.46-renovate-non-major-7121a4183a84a0d39b6cbbe4e572667ad7c58061","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.46-renovate-non-major-7121a4183a84a0d39b6cbbe4e572667ad7c58061","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"7cfd28980e44ff30d58bac98dd326168159e34d2","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.46-renovate-non-major-7121a4183a84a0d39b6cbbe4e572667ad7c58061.tgz","fileCount":94,"integrity":"sha512-coajU5guHOvDy7YwYLVCdt5xyd+ZjgjbrlVsFBWv+Tba3WRnds28/ONC06CK7eCOBpMku8wKbvdgnNhg70fNwQ==","signatures":[{"sig":"MEUCIQCwULCBTeDpYg+1lImP6Z5GcPj4KiohOPraz5+vv1pp4AIgfPT5mSOjUzJwLq9RfBwtGH5vTRhHo63S8Ea9JTSwFhY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":564972,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfceA1CRA9TVsSAnZWagAAnK8QAKM4yKg4R8UfFut4EQxd\n9Dp0fWE/GhbVkRMUo2hQWui4IY3WNZHZp5l7+BOQ6vBYHb78sPTWXW55TClX\ncBIlg+Q0cmABiqjelwWeEwUwCmqV2Gisp4RzUNKJPggxAUPvgbbtLBqILNBR\ntarvCQ+57eSBf/JrPutMXAWbpVNwuMiie7rs9TiTo1Vu/DnUSVFDimlFoaWI\nc6boAWSLwwg43Z+xqhFZI45yw/g5001AjzIgfgsMwYJAYbCoIYrCh91Hf7G0\nDxNt9zTEDPkFJck1+VdM5y6rUVCOkTpPVYPfxChLm/jvp9g4pq787xXOIFZn\nHkSLaH9ajGLCWQbjme1J2ytUy82ogJKXR5QurG9m/igL1lftGww8t0BetNLz\nYhi0vM9HW+jWYpGcCQz541pVimP0AmPgoNYxoGVVULZmu825GmcjshYJJbrh\n/dGU1x0vtJzT4zqnZBpS9Oxw2seD2KPrB8N1IvgXA9faTeEA3HFiG0skMtYg\nXmlVcvi7ikEfUseXEkfnw7OcW2uXOw5a60kx2gRdGSJL4m3YRz3lZvOqanFD\nhwAD7C0mWlYM3W7D3OAlS6VW3MjqMBDgWS64bB9e2nU4lpaSKMK+Xd5M6dDf\nLAtPYdYJ/WWIydz0LRLgtow7i0nYOQVQKULbiil9j2tE81pAMiKytC6HH2GN\nh2CD\r\n=KI6m\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"7121a4183a84a0d39b6cbbe4e572667ad7c58061","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.102","@balena/jellyfish-metrics":"0.0.114","@balena/jellyfish-environment":"^2.2.33"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.10.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.26","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.46-renovate-non-major-7121a4183a84a0d39b6cbbe4e572667ad7c58061_1601298485110_0.6986142389247276","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.46":{"name":"@balena/jellyfish-core","version":"0.1.46","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.46","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8dc9b24d7a5930366c788da4cce8862057a225ec","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.46.tgz","fileCount":94,"integrity":"sha512-RRwCBLnuiUKjL6bB0CiTjJWSS5l6y/N2NNcj/J9QfAXy2kkt3HRbFFo0DV+95Fq6SAPHpwcWwxx0IPiY+s0udw==","signatures":[{"sig":"MEQCIFOV2R52UP+8tXkyoMLAlgXbAbT01K+6uBHC1F/zWPh1AiAPeNksM3y8c9LLCQANHygTWoMf/MRdvRB7bRh6DAta2Q==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":564912,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfceJxCRA9TVsSAnZWagAAmW0P/2i4RWqmYkF2UhKmkrFz\nmfiNYSfmdJ5+nWC9EKllI5g5aVYBsIHEa/7aFuVd1VE5SRpCQIVQWqYvs3we\nXYzrHOGVLriXdnp+3PqmiX+E+1EMLmGfLix+e5yVZDdUdFLxf9FBGK8Pbd0B\nFoaykV71lhVJHC0BuQfdVRx7nO+KBaxBBkHzT7WPp5UYqTrJiE8l0qaNlbkK\nXAx7VVxZHuVxOyKlIOdH5s5R7/tlK2mTm0aIDfa3wUBughMqNjn7TnoZWQCd\nwq6Ij3z9irnadvX7AhsmDm8l/lVOxvD+/9XCO3NEBlJG4lOa+efaefFN5rVY\nMWctiAMB5nZinsuCsUxru0XkAKNDo3/LdXNfM62kGDM26naW29wHpeLnYYO/\nHaAoJ8jpPexohwtINWHqRFF5/Fz6YbXSAzA6/Nmsh7n1HR7t2lzVccn6ojQi\nVpZuUbaOJDxkPVGOsxKbbIasArH5betbS3rXZ9DIyQGSPDfReSBlB6fCCibl\n86g54Blic2yMNyKQyFoFmxx1zv99za9ip57WzSBF5EJ/HVUXnN9Jv+g0bg1t\ns9tMMNqvrMk5W9CaAGkkGpqoP6fH9kThWnQ4CxsfCAv0i9aqCzQIOozPG2Wm\njxMly9DSSV9BCvzvAIKAjS+2JtXgOJ05XRGBGuQ1GYtlz3eXcPjLo4tc++bN\nyh3f\r\n=x6HH\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"2d1542d0577fe997b42052fb22018c547443bda1","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.102","@balena/jellyfish-metrics":"0.0.114","@balena/jellyfish-environment":"^2.2.33"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.10.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.26","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.46_1601299056734_0.8489086768840435","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.47-renovate-non-major-ee1871bf227e80b820f7315005196d0664c3c166":{"name":"@balena/jellyfish-core","version":"0.1.47-renovate-non-major-ee1871bf227e80b820f7315005196d0664c3c166","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.47-renovate-non-major-ee1871bf227e80b820f7315005196d0664c3c166","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"efaa3af78b236561947a7ed47c03755e879dd5c6","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.47-renovate-non-major-ee1871bf227e80b820f7315005196d0664c3c166.tgz","fileCount":94,"integrity":"sha512-tOTwuhpmC7ZwjjD9SuM4W38/8fpJ5bKf5Af8+zM6RYF27QzIa5taaDZqyGsbKA+SS+uWFWEi8dS7mp5MrXqjyg==","signatures":[{"sig":"MEYCIQCKFdxml1RBX4d9mn7PsP+cZdg0yzIqrItEKaDj1oLBnwIhAKT6zDJgl9CczFXZTM1CDs3db4au52oSIEVPhnx5ctgy","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":565038,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfceT8CRA9TVsSAnZWagAAXsYQAItmzGKxkQuUE6WgYWyH\nWff7+sZfUz1GP8ASMcVYEqViKhbREMndjf1k296qzlE6wn1n34WKfz979k66\nmrvjJ5sz672VcXNp4zTo9WkFGTaxqDh3oCeRtd0k6i6V3+vazrjHA7U4JOEw\nfnnzEVuOKj2KnhPf7xCueaCHdqk5DSaScp2NtOjEcqgOkJ1bOF8Hw6j3c1VV\ny09PIJgJzwswnBbgXp07DAKusdCN4GMET3EeHuDbZ2D5VvioTspBY7fPp2YM\nt+oAT9W8ioyI0+/ssZaIsTeLzOmrhlfrZad0umUiebKfQj4s1g/irGUX5dAT\n96jt81mLkwew9MvV+7gSoaeAXzqJqKMrUVu9wRJQrH7CWCs08yA7OQBfB3P7\nJcNfFi4rSO7zef8Rp2icQbDBs5JJqX+vCL1Qf0aF2hHJZNxHZXxCaSubI3CT\nAenhrWHbjX3XIEppVvjWfAcdUC5PMnhdSN9w+Q/pMMQYstZvuSOZbCOiY+2w\no7zkKG8AthCVQOaYK2iLYx1ywmghwZksJgSD+UPH1haCn9xPRFqFwGNgq6vW\nKFkGmEiWWd2o2UYRhOfMXIsZv2vDZT9HkW52Z4YukQSDsAQ4NJjH9vvdO8wi\nJo5RnxDaFCJhUC66yJicSUGpdD6RfgrJHUK951T2n+Gu2Bj2HBD8WXmOjhM6\nsZQF\r\n=wOdD\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"ee1871bf227e80b820f7315005196d0664c3c166","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.104","@balena/jellyfish-metrics":"0.0.114","@balena/jellyfish-environment":"^2.2.34"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.10.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.26","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.47-renovate-non-major-ee1871bf227e80b820f7315005196d0664c3c166_1601299707979_0.25481243845441526","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.47":{"name":"@balena/jellyfish-core","version":"0.1.47","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.47","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1c8b63887ad3a3a78217296dec8f85f9eb4d5a0b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.47.tgz","fileCount":94,"integrity":"sha512-yGgS6CayhzwV+i06wN4Mu1YzLmm1ogJrsXO/ptQPJBj+AJUs1a6lkgpGCP1S68SmsCQ4rxlTBTN0w0Ah0sPrbQ==","signatures":[{"sig":"MEUCIQCEqaL/5o1trgl40owcRi9Ov7jgjvDcucTONJGKnr+O6wIgY1Rz+RQF+qdpTMK47CtQrzA4I4aTEj4UMfAsK7xuSb8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":564978,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfceY+CRA9TVsSAnZWagAAnAEP/2DqNYvFpos0oIoFg/Oi\ncNA4frbXZYsnvosBRusxo7PxKSkdAg646C2zQnDgpoAEXJxI8ffmC9M06nQf\nUO75NMRhUjy3b7VY+xrbz3yTC6IapkNDp/KdreFZvSLvInbLjhWAvYOfU/UC\n+sEjZJTsPgpotXw5zUoWszgOWi9OXB7TfPFOtCrJV5d4oe50i5DvLhPTU/Vy\n47CEApfEPf+Ys4BWws0+ZM76T6WSYqvQFNZmJ/PepyBEmgc/7Ta8k6yVM0di\nGnfAfqqwiH/6DxFeMaI74EeGq3ui7e2mwRT9kFfbz76lGwYnBKk6AqHpUSFm\nRcTSFRDR2Ua2c2Wg1Zfkbhoaj/2Ur9uTYFyB6MjhWiMg2IqIBLHJ3Yy7Afb6\n5W97nHkEbAJTZRMoMaYvq8ykWCUvr9GQN2oMzRwpuYnwNVMqlFSHaY8/DqLN\nZ+WeJEbP8KucHYzY/dVyP2l/BjK0Fa+6/oDQMUkiXuYmUFsq1GzNyH/v/hN/\ne6yahAOPzJqsHoexY3YR1iOes9k+Csblxc5DA973e89f/i+GOJQ5ckHNw0uW\nn7iZz4ce+N1LX/cusfzoTajFoJ6DXrLuIBjkBlxw1liBE85UB0IG8ndvsxJX\njxiQ8lvo8JhFCMhAYJA/bbCiLEvXk3byEp9ujlkvL4PVyP4vb/r6nYSJIwb4\nF93e\r\n=UOAT\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"7f33f02845cd8ea418890e56a0449c89db05787d","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.104","@balena/jellyfish-metrics":"0.0.114","@balena/jellyfish-environment":"^2.2.34"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.10.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.26","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.47_1601300030261_0.14544665957974612","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.48-renovate-non-major-47f420bdea35092dda57d191015af0d1d91fa832":{"name":"@balena/jellyfish-core","version":"0.1.48-renovate-non-major-47f420bdea35092dda57d191015af0d1d91fa832","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.48-renovate-non-major-47f420bdea35092dda57d191015af0d1d91fa832","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1c94ff8a698bfb01f59109cf92bc5f0d8045e1f2","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.48-renovate-non-major-47f420bdea35092dda57d191015af0d1d91fa832.tgz","fileCount":94,"integrity":"sha512-eRn+p/WRW6plbRDhuq57Pp5H1i4TY7IZ3MFwSeaM3iWeJTBiJZv9iGW2qXR4hoeIFwoFzmDNa/003yaRjS52cQ==","signatures":[{"sig":"MEUCIAsCDJdGkFsLT68Z3nACNonFu9TrkVBy5Fg9Nh40N8W0AiEAkjhXNTveV2+JC6fftDHR+50KXekSg5zQ+Z8b0kkcIJg=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":565143,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfcgs+CRA9TVsSAnZWagAA0q0P/jc+QCsJgkupCIR7fH+8\nCNDFnY+yrUuS4KxhCvVxR2FhSfz8/69qKWcDbRhtILlmP4c6N1I1KnLc0IDE\nhR8OHngFZCOQ5K7UxgTYTYl03jjuknbjKILn5yBpHiyggO6fTn1TG20FGRHa\nVml4smJw8Wp6xBi86pRMmtofcN42MALSN4I1w3CYzMfBNLY3Z6bsltggAQYr\nOagRd/2j6G60DOoNBCbI+J6GRGqykZXpL72aU8szau6Q11KRZqvmL3jUxGop\nqa6sXMwfCz7Za8u/+W4lIAlfDn9kmUAaQGK8/Yjaz77iH1pyt/GvMknyDtXP\nvWfQ/xrSOhqjZjyKJ5yrExDLRl/GJ4ikig13UEIKTWxj5JFjNNTSU8PmyCEO\nim+VBKQr7l6daZg4nNOhqppAH582O2yNfwTsaYxxVjqju9nHr7WIS7vglISH\ntTJQOpJIG33+9B9oncZjXe9V48ICNxiLhw2nF4WrJnjY5k7gKHABpLgNXOSE\nxaMPVRWY5A1MzhKUxCssctAX8R2+QGH6JIfjXjqS1MJ29pDZ8VJ/vVlIsvlA\nCQqYYVMlcS8l6LNgbjYegJAqe1KNsFmKU+E/QvhbVx1O1QbSlJmXwapBCxv5\nu63VUSZIswtVUanrY9lte4UkR80iEcnIWZPBkJ7RVPeCmUF8trv+L1Oi35u1\nF1QN\r\n=Ff44\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"47f420bdea35092dda57d191015af0d1d91fa832","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.104","@balena/jellyfish-metrics":"0.0.115","@balena/jellyfish-environment":"^2.2.34"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.10.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.26","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.48-renovate-non-major-47f420bdea35092dda57d191015af0d1d91fa832_1601309502221_0.49184273558627933","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.48":{"name":"@balena/jellyfish-core","version":"0.1.48","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.48","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2892d241feb1600e5a11321d101380234dce8079","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.48.tgz","fileCount":94,"integrity":"sha512-Gw3SlLc0IvGohRxi77qjCi/zo0NyVa5iwye4sC0+CDdWEIsyCVBH5M90V26Kx5sd5nkuWcDQ9Il5/EBK0+dL6w==","signatures":[{"sig":"MEQCIH3p9EnuWuDEcCZ8byiSVYMJ5pFaReV93MzCPJO40O+4AiBmrggYIBa64qSPP6uB/X0iwSY8nqsAoOq21/iCY6oCSA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":565083,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfcoIsCRA9TVsSAnZWagAAzZ8P/RQU0HTrZXuZEqrU2NaS\nxceS9kzhDtmuX0f37qxL+Hx3rmO/a9WGKjJsRvjr93/y0hr5x50LUfW4sX6y\nOUt/zEBQNdJBu4YbtxsJSAXED741Sjxux4w+I3353p/zdQcSz7iuYEuUc45/\nHLiBu1xDYdbemPlPVlWILGXD9rZPS5CS5d5b1YNaPpuTu/XyD8lL8gWyY42e\nEnGgI4nGoRDOHU/whsCrtDfoGdDmgqgKo5xZjp9k0VDZTsqZSKmFJkXUgi1G\nPpb79rJIdzyzLdYdg/Rd07xC0UV+a9zShxji46z5qkiGS4ecIhcSkqXQk5Cm\nbqorwBxDcF+zBK5X2H693r4tS/fvTHcWgytBl/8QD3FnI+qRRlK2ZnSRS+0H\n+CKP0dpI10SbghUC6Qrf1LZDxqfGhFFGOOfYQKd0Y1Nag0OeGaSSroYhx4Qy\nqVZFsE5EZwBLlibV4jCIDxEdGs6Km+EZTi/1B8VG3xvaS363gCmyfcWJ9JED\nA8YJ2GhANWnRnKcM42BDjnr4LVnO5exuiuvLpNQmeK0REAlnDTy+MfLndHS1\n6E6LCPIMGvdPWB8Lxzdc0AGu6+S+WoWMl2mrZv9y8nFOABJ8deoNxDGW1X/6\nOZa/iQ4ScYnsUw0fEsPeYBA5mHF0/t2d6PhElqzeVwKbkOIoCLhTqCrgJWpX\nLCR0\r\n=zBdR\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"1802efaddc18bb455d5aa2d0dd5a155918c1c52d","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.104","@balena/jellyfish-metrics":"0.0.115","@balena/jellyfish-environment":"^2.2.34"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.10.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.26","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.48_1601339947871_0.7633950825423215","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.49-renovate-non-major-ff46d2f62e8aa7c236bd499f203c935a6fa046eb":{"name":"@balena/jellyfish-core","version":"0.1.49-renovate-non-major-ff46d2f62e8aa7c236bd499f203c935a6fa046eb","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.49-renovate-non-major-ff46d2f62e8aa7c236bd499f203c935a6fa046eb","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"59aed04d021abe70f698c696849efbd314e4cd49","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.49-renovate-non-major-ff46d2f62e8aa7c236bd499f203c935a6fa046eb.tgz","fileCount":94,"integrity":"sha512-UrD+pT7ogKjsha5GTDHjI9TDRDS7CN0sU1OdeJcYZc99cdOSLb2ny9xgkykmL8w67jgngYFEw1hM3KJAZRLB4Q==","signatures":[{"sig":"MEUCICSsay4sGltzKpyhK4cZJ7V0iDSsV52ltR3c3RwHebcJAiEAm9fGHphELhWM8pRVNmbCQhbI9niwER5ZXlAptj5nQAg=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":565244,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfcoL7CRA9TVsSAnZWagAAr2cP/jIU4TZUa/VcQ7pkNUYl\n6NoFHw9aum8UIojbjzGpb0AYl09Vfqfn3tHDntsk6yTwVBbABmWJjADIbc+j\neOyVDogt6IDS0h83VBOfw8q5zJkBi3JHI6S25yOX4U/+SkJBLG5qRs9CfIdo\nb0lYKowMsUzs7HRDkbNyL+/KkMokl+Jp0KCFvTIHwKj9YEfxVwp5DBLybiRz\nKpn98ivUMNCkRaWnw+/nRAuuw3pUd6mLKietVUeK63kRk4s6+Wr7aoJ96eJL\n/LJOwLXhfWzwn7rWjvfJveYyCiVkXXt2sS9q6MyIe+s4GqQnCfKmzAaT93RR\norcyQ/iLVRUItpdPV77XxMTTTxq4sr0JBFAn3M90nENAlV2ipiEZPaQVcbaz\nPwywULOmG4adxtfcQ9U3gfZjCp6mYroj6wQtOOLlUwBGvbo21tzJl19R4BkW\nI30OxzQ3lGaoPVtZRZlrH8OqaFW55pgNq14LuIC+/6NtYsEf9oxa8IocT3wk\nJaUlpdllfPNY10lmz4dDws9i16YBhobjnmeoD4CigxLuynjIqPUQSdvMhbvE\njuOlptFQQe4rLDu5+WNXj7kISy3pcSVDIYrvLYeOlwApJq8URDNF7q2i73vW\nnqaMO34khEFdd2OcsvyaHz6rioEBhjUq+ylThKosuydNwXSP7QcOp2V9aWTu\nF6Qy\r\n=Vujc\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"ff46d2f62e8aa7c236bd499f203c935a6fa046eb","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.104","@balena/jellyfish-metrics":"0.0.115","@balena/jellyfish-environment":"^2.2.34"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.10.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.27","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.49-renovate-non-major-ff46d2f62e8aa7c236bd499f203c935a6fa046eb_1601340154937_0.19881320997522867","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.49":{"name":"@balena/jellyfish-core","version":"0.1.49","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.49","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"891316300e688fc69c9f1f0012b2d245622b7a4e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.49.tgz","fileCount":94,"integrity":"sha512-bQiIteul7daOx8xzyg/TnWftXHGybd2mqwpMI+RIZn9G5zOgwW6PCBl8doCf11mUt5w8aKYSToaA6l/F6pyHeg==","signatures":[{"sig":"MEUCIGthZXge3UU/oFm8gP8uwotjL+pDmCnHR2l2mdh0zgUyAiEAxjsPWsvvgP6p0/uJloC77MPJx9KhRnPrU9OosMFJfgE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":565184,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfcoTwCRA9TVsSAnZWagAA/p0P/ja4sRdhPOYjs26BqpAE\nRtVBYLv2WgufasTN3BKBRMSyoZBcqb4wI4PfeteZdX/T+P2akNTTlQifqxtG\nucRgnb/7EYopTkA+u9SIZ4AmTHj9lhE/M7r4wd98QRjUwIBVWrvhtdjN4IwO\n2dZsaZSb4XzJLakrMzt077+Vro6o1h543wzIqBi1SUNaCtzB56cTe9/5aHEO\nFSCxHb5ENRp++8jf0vSiojSEGePGnbCHdEJo71yfPKMsg1IkZeuxr+o75iAN\n2dNAyWQARLnhURCq89Db38Q1DrVHIIiqWE82bgJbs2bXlo9ZvaJgZzM4Cm9g\nYIXUsbOmYGMvUnndFY7o06wB3zd4IxTwxj/WOWjxstEj3EY1bo7pYT+S5byL\niU8jdVnLkcehvg9s8yQtYE4eq35amrKtwQNgLtZ0vtNA4SFtI8ps/XerUe/c\n4CaZlpDTRysjFYDZM6wPDb94FmJ4W+48aDzhQ6KjnBc+5XZZN1vty3xB4IWT\nr/JIq/XQL4FCYjQbt28lHbi4yD8TYx8VVvm7iKlIbvHdqVNPbQwfyNBAGVtE\ne3E7jqG+6zRnsJt43u0V6nWxKIbf2xT6yQ9i0os4wyptIO6cFGxQ09Le3fBc\nlvE6HnorSBIZsOEp4xIaPIMNr9eHPOcMQvb7f5ZMXyp82ZFWhuTDrnQrC6E8\nAUh3\r\n=7wtv\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"73551d606cdef044d7eef1f37f6d89c8c4c636f6","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.104","@balena/jellyfish-metrics":"0.0.115","@balena/jellyfish-environment":"^2.2.34"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.10.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.27","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.49_1601340655847_0.5718972973559868","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.50-renovate-non-major-d318a07668f91e863b2a787ae7d1f8cb863d7781":{"name":"@balena/jellyfish-core","version":"0.1.50-renovate-non-major-d318a07668f91e863b2a787ae7d1f8cb863d7781","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.50-renovate-non-major-d318a07668f91e863b2a787ae7d1f8cb863d7781","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"55735d026efa46fb997b8d0febc8aa6b70d70b5d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.50-renovate-non-major-d318a07668f91e863b2a787ae7d1f8cb863d7781.tgz","fileCount":94,"integrity":"sha512-egtWkpmb1niiI0v4UJEK0CGwlrICsibK6FjyZZKNGQmA026v7BPFVS9LOFAIVt7N2G0SA93TVoiF6nGX5lpf3A==","signatures":[{"sig":"MEYCIQC+RC/A2tH8tqVXXMco1P6Zu1UslNC8GUMGZcM6PGSMwAIhAOg3KImD7HVXHoLHdZecCirIZQHJcPQyRWv5CSa4G/Gk","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":565310,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfc197CRA9TVsSAnZWagAA4JMP/j+eCw5+FxVUOqpcJvY9\nWmAH1Q5n4q1B9GoF07sDQeuQgHoC49Vws+yEWIfRlTJqr23NxVJlg98iyEgV\njZf5BdlXMTZnr7XFG2KCxEN+cxQe4LvZduDNEFSK1arCZ2HZKuxSvdWkWOAU\nIWpb2K1mDTcVjcPtUuhGgk4DvcMoUk9iOD8Ok1rq+9enofI0IvmgLONFl0h7\nkl8yiLawpZJAk3WfA6cwiQGxW56cdMQWgZRHlyEuIAt1o74HDN7o8P/5Dbw8\nUNk6o328ccn3NOdHeO9u0NzuyGL5/jFZeQZal6QAvsXbBBjohQrSPFwjy7rf\nLdG3KbrtK7lTxrQWm5GK8GhVIZdvH8lteDFRF7DmWQQFbx45s83xpbdxBVMQ\n2/9XGEO+fvFmNvRiXszAXkyiF75I4HBkKQ9ypBMJeCSg3ejEbRdLmf4cIfTc\nenvuvAG6AuwR9zLVnxUPFAtTUY+D8E/tC8TCX2RPtax56XTd4w50bhBthowO\nszNTS7MfXnM+NqWTGDqgYbDk8avfJH+Ul0mzvP90dWNKjDHhR/G8wdVb4De/\ni+zLdSXfzB06XLOFsfIJwi7/RoHuOoE5zhShFy1RLUgHVt4nwwVxzyww+Rr5\n8OHZeyguTwXXz0GWx8PcHBc8xJC18z0bIn+6/O99Z7reVdIjGDZZCtmyNtim\n2TJf\r\n=Emoe\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"d318a07668f91e863b2a787ae7d1f8cb863d7781","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.104","@balena/jellyfish-metrics":"0.0.116","@balena/jellyfish-environment":"^2.2.34"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.10.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.2","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.28","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.50-renovate-non-major-d318a07668f91e863b2a787ae7d1f8cb863d7781_1601396603073_0.7949018898878879","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.50":{"name":"@balena/jellyfish-core","version":"0.1.50","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.50","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"192caedf722418ea2fa5bdc48ee19ce2993bb710","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.50.tgz","fileCount":94,"integrity":"sha512-fdk984xy+PYAhlO6Cr/UDbxNVS06QdMMOpvL2AHO4u/0qZ6erCHUWt2X8NRlo8CZxbtH7pOZf8AWSJda/yS86g==","signatures":[{"sig":"MEUCIArlIYQEX1bEcIn4706nkBEG6OBDvw8im5qi4O2Tlm5uAiEAqiyqnh5fZ3U5Pm9dzSV071mx/Fh1mTGZczT9tk4G4+8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":565250,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfc2DACRA9TVsSAnZWagAAhU4QAIG1NjsXL3DOgfLBiD8p\nC6AR27lhe/jOUTLtHZpM3f6yMWKfN0ssGLq7OGs0jVEvxssg0I/p17oNF3qD\ncELCY8iC5nt+QbUUeW+C+vfvUUFGOPCk9tnexJ6FboBqQj0mLSytx1C2YMbL\nd3z3uHEaxZbY4oxhY7yz4eZG/rBs9iOGPJJ1tCAMoyTvPGkbI+2qJQhplDBK\np75bwtBfeJ3YB4QKUeXgDSJfZiK6V4CZMCq/eCvXTqiAjSvGIcZK8KYww6fP\npVnBEh6EkyCEPO+gPNd4f5NNctztujvPRI0sDXmojBNFggFA3CjhJfKdbdj2\nl/7n1MBnW2TI1uDzoU7AYaU0PKa81/vSMBwA6sRSpoMHF1H1MzM3PKJAemm4\nGpj/NmEYpC4YVc7JWLp3d8p4OBO2DSP2vU+Vl6p3W3SstIJ/51K/gqaGRMfd\nU+bjt5cq+/A/OXlOED8GQhNEsHFFB00318WhX8oIM0nIxNM7nM77gMIAhyBX\ndUQBtWQPsDZYdaL4SV21zcLUblhp14TVtulUuw1xQ7exJ6lL5iMsUUBworZb\n/Z6pqXlzthcnG1+m1Tee88Ffb/VlMdtg2jk3Cm6qJTMiUbP2WinqVo1dXxP1\n93BhUe7dltnbrdfiI7zMNRux+nKalX8Eu2nAEEp/5hH+sH9lEqn5KSUaRYEu\n3VD3\r\n=K9Ls\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"7c4a71102e1148b571429f51786c22913a511efe","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.104","@balena/jellyfish-metrics":"0.0.116","@balena/jellyfish-environment":"^2.2.34"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.10.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.2","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.28","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.50_1601396927538_0.5380743687179059","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.51-renovate-non-major-8789a58fd39d39845900c66bab9fe01a57ae786b":{"name":"@balena/jellyfish-core","version":"0.1.51-renovate-non-major-8789a58fd39d39845900c66bab9fe01a57ae786b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.51-renovate-non-major-8789a58fd39d39845900c66bab9fe01a57ae786b","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8c3b4451aa25ef9eb43b76ffc262f1fb15448bca","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.51-renovate-non-major-8789a58fd39d39845900c66bab9fe01a57ae786b.tgz","fileCount":94,"integrity":"sha512-IJMaG3zjwj5zKHxq3MUN6cBJUJ8VsfE1BlZilX0IMQxjjyWU68NdLwVzdt8+0ozPZCoKJr21tRT7nTASTdvAzA==","signatures":[{"sig":"MEUCID9pW0hTw2IANClfrPzNEBjKKnaXNNDBbdroZbaJkCy1AiEA9nq3lTqcJpY9Yg03CEguJ4N5aGAXHNZI4TXrfQDWVyw=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":565376,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfc3kfCRA9TVsSAnZWagAAJ24P/i2q3868D4ldXqkJcNu3\nrXri3KD91mfvs23mwlHUK2QHGOB/ninLLPAaC3s53D+ixp01SjWz3d1o0DDk\nKdLUVwi8On1anRLB7/JZVU4nZQ8tcRUEqV6GNoZhbyehmFe770lGa1QNn0Al\nC2OMBk/VG4V/wiHgqcURd69J02BS69QmULbZSeTj/AVNW9Z9cE5FlMavl7NK\nWlUxuXbapA828TnYmcmM1Ee7PydSelJJ4VO3zaH98Oic69K9VrvNxnCyP1U0\nYMEn6btkJblQ27kSn2Tf44IufR4hlXy2UNfraMdr8c9lSKu+n3PDmFbYnwMF\n9dOg6uek/+ZrtBdG4IsnrvmCYEWvcgfFI+HPxLcG3HKOY8Ab4XSIxc2SU6aT\nNLuw4YRTzgzs4E2kWyhjwWzd1FroWN+qKam4x15taXiSen2PBbCdz282vOEA\nopIS4fZfrWFySoyZyFv7jYW5lo0gphDkl0MtYZjL8xQedUha3oyxWu4LWMQ/\ncQMcGSUqSfMmfF7h3qG0WDgajDiQoL0LmdZB4JPipdW1nRmDMeY+Xq/0ktqF\n2mORpajnc7A7lVkLCSPe4gnyIBdE/qp8JXvinSivH+xmLJVxAhB5HoAVp6gj\nLeV/KdSXLbCqZvogWVXBNWe8J3SiS4hvf7/D5iDSbynQdEsauyv4Fe7GhAkB\nFoZq\r\n=bHx4\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"8789a58fd39d39845900c66bab9fe01a57ae786b","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.105","@balena/jellyfish-metrics":"0.0.117","@balena/jellyfish-environment":"^2.2.34"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.10.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.2","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.28","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.51-renovate-non-major-8789a58fd39d39845900c66bab9fe01a57ae786b_1601403166976_0.8047643653484418","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.51":{"name":"@balena/jellyfish-core","version":"0.1.51","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.51","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f6ea35529366528925b927792fce237d3ef8880a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.51.tgz","fileCount":94,"integrity":"sha512-lpEyTL4+tiynYy9GQ2PzaF3I+0+3JL1mecpfKI+RBsG9BsZSpSfhslnzz9YC9lFg26qjie2ze0kdEPPlYl2Jlg==","signatures":[{"sig":"MEUCIDWhKX82QAMIyuBpGbpqVRxCjSxozfq0mlzAqOCtF9tgAiEAlo/K02SexMvX8b4e2CMcx+x4lT/Kgo0FDxW9L+zX4EE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":565316,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfc3riCRA9TVsSAnZWagAA6JsQAIp0wI1ZtU2JWHhMedja\nbbFqDJhQCxEIPNsj0PoZbflGsNMmzU00G0Ncq/oQa+miR8ZV2abmpK2czUBt\nStvRqJ1SPdd0pqxfUfRNZfh0+VyYFr5cxRr+9/HhXcLdwob3pFHdaIgA4zPi\n0L1gz/zzYJQr8MRBYnYLPFAXLI9Lfg19vyyOQc4cFkYMyQIrUzipW933LtGZ\nxM+//qikA92Wr7RPLN56OgHBy7aTVMtFMPyDH8Tz/axsAO+eh81Bx+zEPrEI\ndERRUgld+JJBckgQBZwD/Td+fr+iBKybFLw7Qn3m08O+TlJk7Jjail+laOSY\njTWM3y9Suru2L944YY7NVHMc+qRKmU18d58a//y6XqmRPS1tjFbl5fD7CkDI\nZ+tcqjzwG+RtX4Ji8vvchiQyHqfVtV/e969nAqBP90jroHxeflKWN6Lnl4K1\nQRezEB98P0AmdErsswpS66sbKRjfS0tGfASS1F9et6bOcVUKEBPKuWaP5wdP\nlYanfietyH/IsAnBqiVM7Ac388Ae90YckA7lK6PBs6Jk/op3G5j0yCJka9mC\nmLwyNmPnLtjuPqtjBvIhAbzA0Q3iboR9BcU33vD2WGg1b/Xw5LWCobAcWpOA\nCOQem09jp5SgI5f1bSTR9jO/0MYAI1htdItQ5tPYVRXFuD90+IMWVpuCLWdu\nxxe5\r\n=Ufg/\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"560708fbeac02d713f459cd3a2583541957e8fac","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.105","@balena/jellyfish-metrics":"0.0.117","@balena/jellyfish-environment":"^2.2.34"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.10.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.2","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.28","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.51_1601403618188_0.4238878662789254","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.52-renovate-non-major-7f4b7be070a6b3d3ccc61944252ca563f93efb79":{"name":"@balena/jellyfish-core","version":"0.1.52-renovate-non-major-7f4b7be070a6b3d3ccc61944252ca563f93efb79","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.52-renovate-non-major-7f4b7be070a6b3d3ccc61944252ca563f93efb79","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"12c1faf970b3abf55f67a3b6e692b689a416dc11","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.52-renovate-non-major-7f4b7be070a6b3d3ccc61944252ca563f93efb79.tgz","fileCount":94,"integrity":"sha512-ws/NLMRGrqXg63Xj+2YUIJI0xf7MrajoH5OVHfOEYXecJ/4KRAbBr04NreN81nBzpKqsGmVUv6OAxRPTKPT7sQ==","signatures":[{"sig":"MEUCIQCo+xgGcvjDXQNbD2IGgueKYvuGoVGIO93LzZfxE7eJQQIgf0Sw79rrB9XydTLKsH8ul+S+8QQkiYUeq+Da6fWfobQ=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":565442,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfdKTqCRA9TVsSAnZWagAAC9oP/iGT86r2Ftt7DojaPr7Y\nlSQ6yEgKdL+IM5m4Tlinj7s3+QzHH4DfaybGd7In9OmqIyceWM+tqKbgDSWH\no8RdR4mky9e25svkdbK6s9/NJkHFBA3/GbJSDcK/fKHdc0YXni1omnpmznHn\nrAGFGoYxADX7BC1Ay4fOaPu26TEx5oke027+KIPDyTx2yyAPCvyJLwYEw3Kt\naf1ndjtlV5CY4IusZ8FVFWamPDD/SXc2A4a4ZPTm6APvNCyGwUeaXgYCV4GK\nFoDwDSfWVmMaUTCYWcTsAOW0yStvskX8TGzgikVv5hj6eVabNzuwdUl1Rq/T\ny30GHkCOv/x5gly8/S5lMvzwCRPm+jr3gGVBZekI7BtLdUzBgmNy4xUB5UwH\nkFyJ3hMEJujiFn+aodA8B3AkVQB7yggYTwLki+7zk9TrKfgKqWwGisOofHg+\nes5Tc9aKIAUStXgcUrf7NivPKO2snZHwjcG7FbdvAWVrOlUoKD3s3vi71UyW\nYLnOItYvcuWm5YDKocUbAPi+SeahZwbDJpW9mq4qVinnJyvHAgFDpfy20TNK\nhPa7ihApXUsD4j5ZJv2ju/eFyUTS4LDAo2cL0tOoYmEO8s/lsdEiVgojum5U\nbQYanyQfvjd41UiShvyzIUCzbNRAPO7DPDnq243vvjl6yTtkPK7fhkrsidEa\naB+z\r\n=5NGR\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"7f4b7be070a6b3d3ccc61944252ca563f93efb79","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.106","@balena/jellyfish-metrics":"0.0.118","@balena/jellyfish-environment":"^2.2.35"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.10.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.2","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.28","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.52-renovate-non-major-7f4b7be070a6b3d3ccc61944252ca563f93efb79_1601479914289_0.49860840504643456","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.52-renovate-non-major-e2379e62145bc941ae8ab074d8466fff15e17e24":{"name":"@balena/jellyfish-core","version":"0.1.52-renovate-non-major-e2379e62145bc941ae8ab074d8466fff15e17e24","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.52-renovate-non-major-e2379e62145bc941ae8ab074d8466fff15e17e24","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5cd99dfdaf9fa43e196924f7dfb81706141e5919","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.52-renovate-non-major-e2379e62145bc941ae8ab074d8466fff15e17e24.tgz","fileCount":94,"integrity":"sha512-JVnx9ibwqEL8+e7rOR0fq+e81zPzOmEVzbP2yd24CPrlbV2VpdxCAEU90VWc0qQo9WPlWOlkXZ7TaChIcxEOtQ==","signatures":[{"sig":"MEYCIQCIkRjBvpyiL0cQbNsZ6QnzUr5uN0TrVdo3TRpmuCJAlgIhAJ6OXNO6w5Tq8kMtkXcmlswSUrGS2PZkuHGMPAIjs9ys","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":565506,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfdS0kCRA9TVsSAnZWagAA2hsP/0EV9wA3hVMlWAdlYNJe\ncDKmhBDPSPXpULECNeTdMjzuwwHHe67161C2TUwiOOx6ICKCJk8nyBf5FxzH\ntgKBMUvs82eah1V6S9i6vxIJ06fPUUFZklnvFO4tEDLJC0p0DTmAR/lAI03/\nIbMclPQTlgKymMYdBKy91UPkzXuHJifh3BdBy8jpVAiAvVgyxwEFklvcTKOq\n1+tCwjajDyCCLRgjFrI+dWOYdha+TjTRza9V1jiTSmJuxcgG5OVkDRYmlr1E\nMSujqckVqd/t6U3h6LmUhqyw0qruwmdzuI01rUjMagJKKDgKUcOkb08F9Hc/\nQBWJN8Ov0Shb7Bm3iZr1Cwbf0VfczZiFPiz3TX7agtIn1XME3HuqfVUp1ZyS\nDZuWRGKygUfEo3r2JjMW6nRIt+XtdVFWxSdqcSsiBrCi8TmOIhVWW8i0cbGM\n0OI9yduJN+iURrMCYstLb4wwjC7PI4AWeYW5ZpvE+9PFuuTpXyfi52/91qyj\n4vyPk1SLBBSfaBTqySd6fuXAn8w2Obl5bJxlX+EkOeLMAuysUf4ifYdeOlVv\nTZHV/QeDmc8rgHOGAuZBGQQ5Zj2wKnbqEi2y2dGQQ+e+OcGzRj6h768iX9bO\nod2XxdWz4lW0DxdXil8XrlWSQWfh8RNgOJD35g/xvefxBvz8Yd3XOPEkKi6g\nK1vI\r\n=AuHu\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"e2379e62145bc941ae8ab074d8466fff15e17e24","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.2","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.106","@balena/jellyfish-metrics":"0.0.118","@balena/jellyfish-environment":"^2.2.35"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.10.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.2","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.28","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.52-renovate-non-major-e2379e62145bc941ae8ab074d8466fff15e17e24_1601514787997_0.33301511738067924","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.52":{"name":"@balena/jellyfish-core","version":"0.1.52","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.52","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0e60ae4f04c44d65281a6e041dbff54b54574482","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.52.tgz","fileCount":94,"integrity":"sha512-/EbzoWCxnpNS/ZLwZPxd+NOOtVVfa2USBkxzyJD4Int+VD+V/X3zfOR2Uyy97+jBCsCnkVZypoHqblSnG81SWg==","signatures":[{"sig":"MEQCIFcNToKO2CkEeJrOaq69UVTAtHIYPYKaMU+UvgHzYmyzAiBzuo5ktSeeiw/vz9OxLRu3A7cOr2hNpSzKKtgiqWzeRQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":565446,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfdS6ZCRA9TVsSAnZWagAAxVEQAILBC/bivcGPDdT6qEJ4\nxmTZWmPqMZgbr6zpwz1Bsval0redjj+p/bVgAeJkNGCn59Q8N+reDvIw2NxB\ndC32gXmVDKDfIsU482hIOWqay2KTt/5i6sMuJSXfVH5QvfvMCrR/Vtri00xH\nVlWVkRVJoIneleBt37PlUgkAeZF6gbxHygnGNXUQVoSdRXFR1yuCk08BEb2j\nUQ3HqaBpW7JxaFPnxwzbWyulPYYmt3gbjeGiuj3OVB08pEOwqo58axwdKc26\nG2OuWmycNNrNccFoSuJoEtEMVvRPu2rM0KLjhz/uzoPTIk3WLHMDhItTSfxA\nm9EMJQ0zosv4m4WS/4MUG1+Ea89OSxUq5HLH7AWQa47A3FDRMi7+Wh6iq6Ni\nZF+3Isp8g2rEa+OHmVDK7cwHCNIW6/H8SnuNsp09x70aMBi86hGmMbwV+HAd\nZbOuYuH3Q56veZTeILgHVl4mn67u9Pyyr13BFalXEOOIentOYOSbp/kslr/M\nQdRqzlS+hd73qbAvRI5n0ZzhFEjM203Dt/hC1WQRGMzr/TdqdPUAexpJt5mO\nxsV/dfNfxl2mC456zVDz3yPplN6DJY8dlUmUPuxLUyGQdERE47MXSCzWqgMU\nd0uTPP2x3YhT5jz3d599rnz7jXFMzMbMwN7wXHrvGd4lotZRlEpcY48IUZzT\n17Gu\r\n=Xm/m\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"c78bfb49d676118f31d00e15a8c24d69b162310a","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.2","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.106","@balena/jellyfish-metrics":"0.0.118","@balena/jellyfish-environment":"^2.2.35"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.10.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.2","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.28","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.52_1601515160871_0.1912550167119902","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.53-renovate-non-major-5c81eb77e058ffa589d07de90b29f85b1246c12e":{"name":"@balena/jellyfish-core","version":"0.1.53-renovate-non-major-5c81eb77e058ffa589d07de90b29f85b1246c12e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.53-renovate-non-major-5c81eb77e058ffa589d07de90b29f85b1246c12e","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"13c1d202655aed9b70be5b6b28d0d9a60aae5dfe","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.53-renovate-non-major-5c81eb77e058ffa589d07de90b29f85b1246c12e.tgz","fileCount":94,"integrity":"sha512-eXMb/E5jwMPtBb+8EPUUclosufka+Tu95FMmYgExI7QTJLCVAkTFWGLq4DLsH+47RdZsVcSqrc8BCTsxiDrM7A==","signatures":[{"sig":"MEUCICpc8zxwT3buoz5dBVF8uXhoPAcwb/0VBIHMqZHrOSvcAiEAvosmbtsU5zneOMPn2Ew5LP03ALY5uY6YiuvWKKv/7yw=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":565572,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfd4NcCRA9TVsSAnZWagAA2ksP/2gHnm8IUTVALIo9kxAx\noCU1BJ/MrGyJQM5L+ftTgc1UdSdI/UR9uAPEnAJkj57kqc1FoJx3rB4n4+vE\nCFQTr2XcAevlVmyuenBNmoP1QKtd2o3QDZZZF4683b3xyE+ArhwAqf0VJM3E\nTJ56gGuXdfOrSAyl23HBEBT5pVKGjflUCe2A5hTQil5ExwhSuHJOpqyfJm7F\nmwqv9pQT86ggWmOJG3p9QOi/ENWzATcnYF2fKzx27SEGjh4huPE4xUjObQz8\nO7H0WHgcDxPqPC49+AK0hh3hDMaFPQ68vymY/owdrUMyvg3wNqfnFO49qUoR\nhA9gP93wjdQM4gTthK/WXYLoHwmd3bnaFX8af4ijXCcZCnoPEutf2Sxclf24\n7tvZaWGouNyTtOoYoAZ9ggP7H1mCiNJwJSVBIHqwzRT8W0DxMd1pkoc0LjOl\nBrXQa96wZ9aeYKvincTGOaRYmKCg573DQKQDVPKD1XvQnrraiBuUYaN0heed\nFIAajzldJj6qD7KlBFVEroJOjPUeXXMuDiB3QIWZZQvlsPsDEPglF8mCRHIE\nU6ENPUA38o6ld65SV3T2Q3ExflXvivg2HRw/qsnakJ7V5aaErXyr+iFFgYcs\nofmFH22Paky6foa11WI4DQL7Iimao7tbNNX9A2PIwS6nFfG3G9tw0h0LJcns\nydsG\r\n=9926\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"5c81eb77e058ffa589d07de90b29f85b1246c12e","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.2","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.107","@balena/jellyfish-metrics":"0.0.121","@balena/jellyfish-environment":"^2.2.36"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.10.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.29","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.53-renovate-non-major-5c81eb77e058ffa589d07de90b29f85b1246c12e_1601667931807_0.37800664974175757","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.53":{"name":"@balena/jellyfish-core","version":"0.1.53","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.53","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c7c8b00c4815a5ebb3a2c4779bcbcdd214ad7d05","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.53.tgz","fileCount":94,"integrity":"sha512-hnMcnpTM4UEkpyIiAM6n5xnnP+E5kX0hFcCEzfUT1L8ejNsqJMvXrYpagJsoa9s5yeXa4gkA/YakbhB8sQgRIQ==","signatures":[{"sig":"MEYCIQDrDMD4HIftCKIG+NDd7lI7x0pjEnvRhTF5e0vjlqiXSwIhAK+MO8F89I3zZL/3CrZqX/EXiVmWPFLRR6pJXTz2RGfF","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":565512,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfd4TRCRA9TVsSAnZWagAA0JEP/j5N2CD1JOrhUUMs4XC0\njHci5vIihrsdJo3hvUp22/rnxacAPLNx4iC4DveiQQYY7pttKIUSBa4bvki3\nlIR6LL/X47506kTUdYPBzN4JIrzOcPQfvTG0x7kcs2oBq6jp4d2xyb0TMqWI\nfbAOFV8vEtjW5lBnDguxpayYbXACrHQ5W2mkLp/5xjMJ96y2yuDYDBLGBY5p\ni9BtbV7UnK5t5RT6B9zYYbrNOSpE7nNGT/EcaCqDQ2Bdl8Gezn+hTC7tL9AL\nZKLiOalKwK64K4uhgdTThyfoTEZgl2AHlFZAT6d51fccPaVX4a9BsNyzYaMd\nqf8NjOTF43tNbcjP4Nedwq+TFjVzAwmWLRixE6OHGHS94qxg6WfbIAXvSPzW\nXoDFPyhwzLrPcDyhjuAyFipBRUiqPVK8wS8b4e4upxCI0E6/R+kktolKfaTW\nmW+glbYLQeY5vUH05L4gU1w8UGkUXt1DkO/+t51vzyQs29SsLykpfYtmUt2+\nm04/2SXMboAFnl+TQZ1hRqL6gt34yGDGIlD3UAjw8S705+JmwfkepbqTls7L\nXP0AVctApzl6PCiR1d244RuqGhuTt+zIn2LAkBT2uilI3yVA+48bAQkBC04v\nPUOF8WfWLsWcUXMOn0Fr6Y3gJG3bJae1rSNqJH7LIB9lKRcoTyNRBgQzTSCE\nUpa4\r\n=NIxX\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"2bc35db255eb33814b15711c5aef05a67515d9ef","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.2","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.107","@balena/jellyfish-metrics":"0.0.121","@balena/jellyfish-environment":"^2.2.36"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.10.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.29","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.53_1601668304840_0.8989786769589083","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.54-renovate-non-major-74089117770574411dec27fe2bda0bafab0da4fd":{"name":"@balena/jellyfish-core","version":"0.1.54-renovate-non-major-74089117770574411dec27fe2bda0bafab0da4fd","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.54-renovate-non-major-74089117770574411dec27fe2bda0bafab0da4fd","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"7c87c684c41159177f380f32a1c0e54987c69c80","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.54-renovate-non-major-74089117770574411dec27fe2bda0bafab0da4fd.tgz","fileCount":94,"integrity":"sha512-ML8X+rslSm7fV6EcTZsA/3B8rWpeCVnUYm5NEjvwMjJPvo3eNTcV5HQH04gSDptKnUxljGFuCX0jkZUG8yoVqg==","signatures":[{"sig":"MEUCIQCLuQNLtb6gQq4uZAuzTNSLiS2iOS2uQAEQJm0nAqQvzAIgIyuhgEoCyXgVEjg3JUoaAoKgbdHDwy5gspMYcctX42o=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":565673,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfd6FUCRA9TVsSAnZWagAAINkQAJ+RWh3khoq2UovCZ3TB\nAQfPf/UUzerSBw1E1+/sqQS+PKfq3IxSsYDVKFZeBqLe1HWt9C15wTeNCrgc\nL4+eFpKg6ZVjjwb510PnbcwNiBtyQjxWIV6+S7FLl2G8tEhAFlc5z/KdtwXQ\nL1oIBP4p8nqKB74wFNygh3pSQAErr2cCb39Dm40v41jiYCNonIArz/folnsq\nC4obicGfzJPSm8G1sPr6tzOtdJUovHlxUoogispmGcuwKUr5BhliVoA5/myY\no2ZQW5fxJP/GbNlSmTW3MhoZ5plF20eME71n7HOrYq0N1CHvCA+7FjjlH14W\nCFALDFPGbTtGGS4ZSlG9rLwmNt+5CpDDCf4a5n7utuLy5ZrOouxPfYxk375X\ncLI+XKKZno5Nh237A4GczzAjjSHNtEOvyHTOeXNqyE7HZNrV0fxXcf+7vgaL\naR/j9Kwz9A9kGL9HbHeiqGmMRqndN/Dfb/JWiXe+myFX3MzXGe8EuZxSC+GL\nbc8m0/QgVMvN65eER/hrzq79vnI+nXEcfd6bXW6StZBs9LJwmz+XhymzjROX\nHwrsb03CKhXVMmqJQVVcKtDM3dsDUNeP1KLkI04AsZdqBoN5R62n63YeMoJO\nahUQ31IRm5RXQa83lSzmUjWOkZbF6xx8drIav/RHzhpxjIW0NQvseiIvJCeD\nTxtW\r\n=tDzP\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"74089117770574411dec27fe2bda0bafab0da4fd","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.2","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.107","@balena/jellyfish-metrics":"0.0.121","@balena/jellyfish-environment":"^2.2.36"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.10.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.30","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.54-renovate-non-major-74089117770574411dec27fe2bda0bafab0da4fd_1601675603982_0.04000631635547758","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.54":{"name":"@balena/jellyfish-core","version":"0.1.54","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.54","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e4fa9310407dc1c6977e513ab770eda5fe4929a1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.54.tgz","fileCount":94,"integrity":"sha512-5fD0uzYzy6VqyzPQfup4R7tdMULGFYeVKgpBL0UyvCkeD6E3m8LsmIy3m07cMbckg8mwryumBTsR7Hsrk8jz6A==","signatures":[{"sig":"MEUCIQCO11arPmRMELobQqDL8dOfYEIfazqeDxhCjKV811UUwAIgH7j7/xIa+balKrpSSQ/+Lg4wxIs5cI3pYpqZoTP3V7o=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":565613,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfd6MOCRA9TVsSAnZWagAAqkoP/jPaqQviZYryGJFxWmBe\nTCJpOMhn6EiXGFCvzaz0yKxxMUNJQwzePT7UI6WgoM+3JRBlonGpm3ZI2wjQ\n3Ux9bLwnoUecmATomnibogn98ETopgOGx5NpqKDAkdRu0fZCqdNJ2GJP58R5\niNT5am4+w181Fxb+rN0q0P5WlnmlIevkZhxYqTbDY9bcfXe6NKdoa6tUSi2G\narNmcuTd0oKVMuzZ6HwWsrs4vanM/iU4WNIwkKLefZNMnPzp90O5G2Lqa6uS\nJlorovo4XNtw4JceTMcpJBjdbvGOdjedbVbIa1gGRxXImVU7GnRSWSe/xtA+\n811/DKsj0mzcPJbiFsgtkPCns1c+3fTd09aXJIPr1aMS8iwho5WgU+v2KUob\nEVGEm2XceB0a68Ron3MB1LF+PmutI3/5XIBqRWKU43Ub9uHEBW8lKKB/4npu\nfR4J2nFahdetnU25BKZTA/fq1fe45MUTMKle2M6vVjeuyee1/J1/1Mfcakqy\nG4hc5M2Ff+AQELnsi7F6s284Jl7aFfwucyhXUgfK90o9ZQ5JhZCGc4lSZhYQ\nQb1Jxr9P0WQ9wzzHydXuaJqEATcjaWf/SrJeT+vFfnNw++JJKxCh1uan+/Ua\nEY3ZR+yxbdIEFrcrsJA4+jbqENkLa1JkooYCZfxGeDSeL6Xb8McL4dAQqbgj\nQROf\r\n=0nP4\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"b77bfc98ce711df804db9ccf89c4facf019d2e37","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.2","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.107","@balena/jellyfish-metrics":"0.0.121","@balena/jellyfish-environment":"^2.2.36"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.10.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.30","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.54_1601676046000_0.19516957390652134","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.55-renovate-non-major-59e9efbe1e4608b34003e05b1421a1391c02f19f":{"name":"@balena/jellyfish-core","version":"0.1.55-renovate-non-major-59e9efbe1e4608b34003e05b1421a1391c02f19f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.55-renovate-non-major-59e9efbe1e4608b34003e05b1421a1391c02f19f","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1b79ff8a2ee6ea79acbc920dfd848a1d15f401b7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.55-renovate-non-major-59e9efbe1e4608b34003e05b1421a1391c02f19f.tgz","fileCount":94,"integrity":"sha512-q0y5p33BErWr0i57ZYhDgfNjg4ZlNnw/6ieUGx/fKxhA09gZUSnAMBb0BlOOspI2D4ltfJPntKFB9uobEZi31w==","signatures":[{"sig":"MEQCIEooxbmTPRhnnjNr25JXovGyEox2UlTzYip0mRpSCWDiAiAPz4Ug+K0YTig/3OUlIO4Ppfukky067yqgFl9aHuzCWA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":565777,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfeZwwCRA9TVsSAnZWagAAsBIP/RUUCbmDO14IErBHH/8j\n2foiWxt5q6yh1HSfjF9IWYkYpaYV6I1BOIjAGPGkcyLe1JcNr0MzVtIMMiX5\nZHcKFjteWb6Ghy8UXw8iBWYj9ftWCTOwhSWuIBKxnpU3G4mqez7YOYIFPrqM\nve+kvUVfYfJT1MqMDL/biPXPxngxi4xgVzlUz3uy37u8B+EV6EOfPZ6iLHaf\nJlaIwMX0W0a99qKS0EruUURgN0FBsMCe61k82mYdzrbDUJknV22knmr741pf\nsZ2AO3u0BWkxy48+IP2o9NXV3LPvglf9TnQW9WFMgWVKH20lOv0yH9Fg7Yjm\nbg9zTZDASb6OGWZlUTltpfsG8ikYpRlGYJwgvuqL0S+SBl+Xtm32TFIXqasT\nMZkmbwrvgT1yzDkeqavaRq/7p1qODLbqDW2P4BTuiXPbCYsAgevQgyiYiw7V\n8ypPTbqXV4tfNl/Zi1rhNWItdKqlnL7NY8GYm4He2Ia8rexFyHUFa0mCmaVG\nyNccclW5K5J4AWeP+kybpWEINGydtv0m8YrAdv1PRVqRX17GecH+0KrhYljv\nRi31JGR6Kase8WRFC1USRMbEMRWoYHbfH+K+wpxtU3MGO88rZk/xbHh5m7rc\nY1tfa5HMJO1w9Pfy68gydVEj5DHLVcYc72O+ijwp6nlOggjTorLdEuW0zRlb\niKY4\r\n=GUP0\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"59e9efbe1e4608b34003e05b1421a1391c02f19f","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.2","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.108","@balena/jellyfish-metrics":"0.0.121","@balena/jellyfish-environment":"^2.2.36"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.10.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.30","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.55-renovate-non-major-59e9efbe1e4608b34003e05b1421a1391c02f19f_1601805359714_0.2609674799456698","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.55":{"name":"@balena/jellyfish-core","version":"0.1.55","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.55","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8349ee50f3c69eee811f3c918963342f59938eed","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.55.tgz","fileCount":94,"integrity":"sha512-T2OxSYgLP5u0xVK3MKe2bHr5EFuaYYSGQvwEov4cROh2N03sk2PuSOBayJ6oI81MPUwDtvWYDIddeW+a2IqPug==","signatures":[{"sig":"MEUCIQD+n1iTkKMLWyOMrcrnOpTgMQpF0LN1aKByrj7y8NtawwIgdM19KOHzg6KLLJODuYpAEaw4BU9RuogYIOFI8DeUeUI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":565717,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfeZ28CRA9TVsSAnZWagAAEWwP/3vMDFn7pdJJv5+2ipmd\nJPkUcR1nJ557k39f819tHjYRyAgIV/udILTCA443xN8R2vInTFOxVqxzgMuk\nLAPEqNwJ+jJBfYlbH6UopWE3NCEbe+w6GF3oibXgksQ7ua9YuzxsKF6Upadd\n14yQfcW7DLu7a1wlxOPDO7TWNh++yIBTNoYwBD53l4pXgd13wvuTyfju4Z6B\nfWejFMxNcckAwYBUyMDb8PKiqoBG/lnnpkGyf7TELEaR8qwMQCk4ntUqTyF5\nbzrdtfSnZdqVrI9UYM5/nAy4HrupBKF51EjCEK8BTrv2uR0Jvo+xBptbj6C9\nDXh7F7ViGklvVWcohfM+FVpEwbgVGPLmsM58DXYEMUvj3UeGnedlGa5VM9Gz\nYu/QsrAbOY1YgAIC4H35Tzvlxyrg1ZkjMp6LX3vbhMJ5QmV0DLCb4mJE80+N\nywBNjmyta7Z/bb5dBooBGmlcZ+oyLB8tkzb2NcOVUIgI7bakCYqW+ta891pQ\nfZosk7GiTzjp/Fw+6Go94xE1HtgHj3LKgKXJSeZYLDSyJD7a5mM8JbuLKjqh\nvVJ3oyIaUdxZyb74hWovUqSLnKAWwjBhGu+qgOEBUPWhZ4b1VSZBQ+gzSxEk\nLkBGZYANpFnXX0ELV5srWR0TVxgKfhruK9v/83MPeM9PcT3T/NRVqAxHXpQ4\n8KGl\r\n=KcVw\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"5da94edde4e05ef701914dde64830287ddb606f3","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.2","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.108","@balena/jellyfish-metrics":"0.0.121","@balena/jellyfish-environment":"^2.2.36"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.0","husky":"^4.3.0","eslint":"^7.10.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.30","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.55_1601805755778_0.30213214003200073","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.56-renovate-non-major-9c8b905dbcde7c9b8fc3ea999f4de1835343f539":{"name":"@balena/jellyfish-core","version":"0.1.56-renovate-non-major-9c8b905dbcde7c9b8fc3ea999f4de1835343f539","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.56-renovate-non-major-9c8b905dbcde7c9b8fc3ea999f4de1835343f539","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"700c734d1da2062bb3a1cb65e65304cf5d5047ab","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.56-renovate-non-major-9c8b905dbcde7c9b8fc3ea999f4de1835343f539.tgz","fileCount":94,"integrity":"sha512-0WYXm/63s8ijgABHMB/AY4Ze7cSZujzcLe8/M6Wi+fM+Z00fE+48xpDBpeofGUhAqtnBQXQV4bQUhyhcWQtShQ==","signatures":[{"sig":"MEQCICQncpZ+uRN9auYAEvLJvmMr4/DvktW0sBy0SEo7FTFeAiAX9/T0jAfSw3kzfNVEHKyn8KOX+xSz4WjeNBXak8Zw1A==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":565843,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfefF5CRA9TVsSAnZWagAAlysP/0ffWECV0b33Aj7B1Owl\ngU6B4FsscXopziMj4U45AIwmE3np8frt4OcZNkKtTRVFHXVhbZli429VamLn\nY3Aar9MyQdNgKGAn2GuXmg85e2pqWkAFx3qPRIUNGi2wfabgmY5u8oXEQFNR\n9vQyj+i/wQxQFrVXHR2iQDggM5+Vk+1722iz94fEInqF/FjOC8DCRyLRfqf2\nRRGLc9BUocebwW7wWdwM7WqGWSOOxkHDvV+jQMvSzAsx9vTSftPD5kJY75Ym\ni9+6SaFB1FQNPRd30zvx8LjcWb4WRsAtpguuUlW+g4xY+YGOTsk9cbc2zfu0\njt9P5YmRXFrXhcP8Y8NTR3r9CUZNCY4wTJPoLhkF5xzMKT2m0L9VRSiu5lZz\nChmqyQueY7e0e2PvOeMWpvpCaNvtlPf7RM7ZJttYb+bxMcBh+yAOgELtJ0Id\nPUd1GLloE1HGUN4toBTpBAx8I+/vG8N4t/ebOtnNySmM0PZfM2heVi85tmE/\nxvP6pHoIfW5fQeGvVMUqqior/++kbenbtOJtjHpWu4AlmgNUkFl1w9iUYqQr\ng9aj3+18aZDQQvPfxxBaxvyqtZ8KKhkapilEwKQapeCkm/TnWPpv/CpAxOg7\nzGuDwxKvKmA0iDQGstSKIrkziMOh37LcwSGohVH0Y0efTJQB2jsy5LfqwXgT\nmaxp\r\n=ZkEC\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"9c8b905dbcde7c9b8fc3ea999f4de1835343f539","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.2","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.108","@balena/jellyfish-metrics":"0.0.122","@balena/jellyfish-environment":"^2.2.36"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.10.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.30","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.56-renovate-non-major-9c8b905dbcde7c9b8fc3ea999f4de1835343f539_1601827193386_0.47543134209471183","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.56":{"name":"@balena/jellyfish-core","version":"0.1.56","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.56","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"057b8a32d964894b1a03f8045ed8d97da37e9040","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.56.tgz","fileCount":94,"integrity":"sha512-0mJ7bqNUKX0ptVg6bmtPuVTYlfbBUiWYQSKCEBP5+g8phDMAZwcPqch/KYger6KT5cuAZMLuXDDcylJ3ji30oA==","signatures":[{"sig":"MEUCIEYj6jCeB+K2yoLmeWNEwfPjGMPeXJNazf0GbzduGmj+AiEA5URj/rSru/wJIM3g4aaYmRBfbtudOwVQVm8O7R63TZs=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":565783,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfefK8CRA9TVsSAnZWagAABRAP/iQxgSIm728md8QG8oHq\n/H7zdrf7Z+EpGNtVVjkEVqSl7Fg6A9yCmdAU4ayg9sYCZXOOITpr0MJWEwLd\nznrFI0ICxYECrNpph/NpCJ/3AINyHxYESx7gd8CWsSzjlUCtR+jTgpxx54W1\nPU9F2ugD+8WL0PTsCAolf0xxJJPBysVuOXJ/JZQaySb+p1XXjsCoCofobO+s\nTVmkvH8gy2FBwtEuNJEVrH0fOixMjyG3mUBnIhmohY9jzGOJhFgOnN7/jEOQ\nfovUYXt74ldQ5Fc4IhZaUOFnasNAe7Tsl9sQ3Dl2hNjvp8VlkzXQht6J5Gnq\n8dFRH9LlIqtEbxuHcT2ccsukO9qo1QVhdaFy5p8Gn27naAupQJrMk11lhGFf\nufiT3AzJYrTIBftx3aSHqLlU+iju/Gfvw2n6J8+Y8SQMpF53j9YOmQUWYZHO\n+7laPHcykQYFNaWQejCpgHWl7gjXg1fdrBWUoFBV/xnPyqsqWVTmLYzVWzpn\nLnhn5agmYo39P5jSElsM79AxaqUA/fNV3bqhhXapcnMODohM3QDR0osyQ2Eu\nO9AkejUNYjaH2et1Uh0v5kbe+rtaO31Gtapql+gxm9d3FDPmlip3ZEAbZPLI\nNalTdDxaphlDdrRPc3SPvRPxr4A/VsUr2TGSKfKLk5ayS8qQemfjkIQ+QjiF\nqQv/\r\n=WY8W\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"38ee23724f5493167017b8242157e5246bb905b8","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.2","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.108","@balena/jellyfish-metrics":"0.0.122","@balena/jellyfish-environment":"^2.2.36"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.10.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.30","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.56_1601827516461_0.8386065142367911","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.57-renovate-non-major-8c8433015e8fb7167ae953101905233168790417":{"name":"@balena/jellyfish-core","version":"0.1.57-renovate-non-major-8c8433015e8fb7167ae953101905233168790417","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.57-renovate-non-major-8c8433015e8fb7167ae953101905233168790417","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5cb13c73ed72402ec768429030dd38a313aeca5f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.57-renovate-non-major-8c8433015e8fb7167ae953101905233168790417.tgz","fileCount":94,"integrity":"sha512-f5eefIT2PBapKJAgRuCZfUnX++kn3xmK34uGqZqdUoe5l1Vl2FwELoB0Je4q8rf7V5lwNRk8eHzaPWBkhMn3Lg==","signatures":[{"sig":"MEYCIQCYnWrtLskE3bYwG+ASKeA+rxb0OMtPSMgaJVjiVHWtJgIhAOcpbCjooTG/8rAP/iEbd/Uz02tTPvdBkJMOfSxO2iaJ","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":565947,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfexwQCRA9TVsSAnZWagAAQ5oP/jWB9ATaAMRR5+DgN3gx\nJKQ/pqDndPUXpEca0X2H1H17QIXJIM4lUtbDmJpa56buiWnMBb3k0LpYMAxb\n8TlH18iQHVA66obk83vtSYkPP0yhX25J35aURrFJtha1iWl5bD7D7yVLkfqG\nX8ViP1IFyFYSgFHInHbYQuVUZaa4Q7UbovVwXdUl+dG9emp7rDOjEh5NLLu9\nxsDRJr9mTA6W/AC25SkGnqqaDr8/yW/kdUK+Qwrf1G8kQN12Mf5HdopvX+Mp\nNOYEYIPed49snuDD0GqoYwEI/x0X29KV9ZctADD1ll5dDYtDOrFKe1jS7NcL\naLRy9gU5kqfKofD40Tkiv3306epn8yCpl9y/mO+eJAIolpV4nvwUPOFw04jZ\nKeU/b6E1MqQWcryqZn6Ufzl0uXswhDGdkXO9Vmo+P04PrHHnSGiqHDeMbFUb\nkxCDxrVwS4WDbdlz4k6Sec83mdEQoDss4n/Ygbh941CGRwQHnYQKpMtefvUy\nrhHMfe79zpTW8g5E4Qk8n+1uLWc0+aRFd6MkDkJbGEBjC66G57sK+LTDMNXt\n6Ua0tUkM+5au1X7PYeNPQn6uMLZQRybeF7fTgM/uhu65dhyyX/rX0YyPgvMY\ncZIScFd6VDE/zp3PSAUNddBCmpbphC5u+Key0gUNAr3JTXLOVLecw1Xog2hG\nAi9/\r\n=El+k\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"8c8433015e8fb7167ae953101905233168790417","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.2","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.109","@balena/jellyfish-metrics":"0.0.122","@balena/jellyfish-environment":"^2.2.36"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.10.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.30","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.57-renovate-non-major-8c8433015e8fb7167ae953101905233168790417_1601903632188_0.9736645498369705","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.57":{"name":"@balena/jellyfish-core","version":"0.1.57","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.57","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4ed5c46484e51a892f78ac987b0b0443bf8ae7f4","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.57.tgz","fileCount":94,"integrity":"sha512-WFNPsgDCOSVi5eRwZDqCuuR0Yhp0USYfMhEeLLixISoZutjmcjq90YqvPFlx7yr9dkxhET8LkR7NxyC+b8FglQ==","signatures":[{"sig":"MEUCIArz/GagcYlebsVN6UyKYzETkCA1I0EHRMiIeuBPNjfhAiEAub9pwK9gX0R3x6I2ccdOk5WoblxC3g7k1UjZOWujxiw=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":565887,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfex1UCRA9TVsSAnZWagAADvoP/ApuCB1MgG7SAbSkQTDn\nNct8T5QiHWc4xEjxceRCrQmqcf3zmIzMrURXAiPK1nIjHsoXJipHXXzCKDUm\no3lSvjC7vBfuynJmFWmyw/sUEo7k0aOXF6D5HCW2IqC/fWONY7MGjTsGF7p/\n4SqWN2P6mbXNbzb1Yphl6ekgZe1XEE7+38UE/YEzMg+aInQbvfN+zME7z5Hb\n06O8EH2C0thR9iz0N+rQ+GVdl6Fdr4LW3707dKjTWajS06vNs0MUBydkCoB0\nVqnaFoeUKs3GvOBWBqh/bl9iu+2K5lzAgrpbpSvvJ9eXRInCmPPTLxQVRryM\ny9cyWHl0TLX/SiUjlGwlmuiAe9OUpv6h8wD1NbHAMv9iyhbClpwMgfskuPr9\ntdV/XSmA6NSxCUEDb+LRXIotAGJ5sjFVTHENF8wnb4Pmbzaf+1t5Sv+eRnEo\nZCdH6D7NqDz3nmaQVhBC517J7N0JWNmA3kch7GsJgtDCMrZ0xhmEwAuP9slL\n0Pg34Sqz+WBtDFe+Ce/dsSfU8m5uQafv4BUv/ATYER0F93qpTr8g3J2BnZfK\n+LZWaeNrfeO3++JnYqCsRNatnRDx1yUnWMFmqbSt3CAlXqYPPeUbar7O65nK\nqMumG6Cj3D5Fey36xYT0TJfHyJURVNjmlaBB1juPvRTQW/zvc7RLNX4dybee\nQIwy\r\n=/z1b\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"2d8b3b21dd447728a36d98700b08cb45f1daad70","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.2","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.109","@balena/jellyfish-metrics":"0.0.122","@balena/jellyfish-environment":"^2.2.36"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.10.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.30","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.57_1601903956203_0.3629479694421338","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.58-renovate-non-major-1166d51ef679495841e66d35a7c038e80e50a166":{"name":"@balena/jellyfish-core","version":"0.1.58-renovate-non-major-1166d51ef679495841e66d35a7c038e80e50a166","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.58-renovate-non-major-1166d51ef679495841e66d35a7c038e80e50a166","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"17a2673b2887c8fed9dbb983422bf01cf67ed319","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.58-renovate-non-major-1166d51ef679495841e66d35a7c038e80e50a166.tgz","fileCount":94,"integrity":"sha512-MnnmzqW3f3rTRsxBtkG6Pl0DQHngBxtva3qaQcqJp9/y4MWBvJ/yHW/nu88YSa7oxzLkM4rq+NeDDKRxhCYurg==","signatures":[{"sig":"MEUCIChPzzqXFJKk6m8xwGUcAbn2lk4Yv0YKgPezRsalaBtIAiEAr0cmSR7/1Vzh5yodPKUwxsBEPQWgY2pKGbamILVP2u8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":566052,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfezutCRA9TVsSAnZWagAAPW8P/17+CLND8UeT4CUZetw2\nc3SO0W2YYjUkazUAXHZStwv4qfNex2/+DYb9zY4I/VCsyP0bTZhJtgMIdYct\nqm7doQ1ev4iUt/DZcgNZ8UPR01i/myRv4/xBl+KmRaA4fwcMwd1Zw8am3wMw\n44hoIcG3S//bDCOVUAKKl7kZG75vIaIJ4r+XhEMgaiL+iwanBbC1FkcoZIC3\n91YR5wtvoPm6kBBx2KH851PyKfkoyePkThSVQOCVpCshaeqt/D7W3qniPi6m\nXzqCnxieSJgy773s+QgvMCXXkfVHnOcQExr2iWiZSrFvOP9NvIbyCq1Ig2Tr\nyQ4mkDi0YqzkO6aWMu+vMXA0oSt0KRHuSx3VaKW330/Q+S1rvk7W+wSpT5ti\npOnSrvZ0np9cmY5mK/ABnTFCRpoTe0TW9zJ7y8Ko4o52zSHh0Q+fckCzW536\n8sJIjySOQ5ZoTGkUwotoiOi6bXVe5bKhEzgcwzPIhYwt6gxlhmuH1N3JYOjs\nLiBqi3XpyuIZd7nuobsxr3+p38kT+G/azyn9VdZQoFCx5lavktZkQoy1GWqD\nxy0lL43CEmASea0F4aI4hiZX6L11ZeOrRnsn+xMmNXecyhJkR3kgzNfp6ktY\n5prRUdU8yFutNkdusdUtissO8rDHpEl8U9qgfvfekrQLE6Y8vXMEiQgW/Cfo\n9XoE\r\n=TtL7\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"1166d51ef679495841e66d35a7c038e80e50a166","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.2","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.109","@balena/jellyfish-metrics":"0.0.123","@balena/jellyfish-environment":"^2.2.36"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.10.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.30","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.58-renovate-non-major-1166d51ef679495841e66d35a7c038e80e50a166_1601911724493_0.34467284118415487","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.58":{"name":"@balena/jellyfish-core","version":"0.1.58","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.58","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4e366c395f7be7b6b67d807d078a3fc0b129964d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.58.tgz","fileCount":94,"integrity":"sha512-UujgN4U7/eMnDLvEvdgNFeMLG1BVDM1VPQruxd0hcCXm9tn80OfuJRtgz5HxoHpb0/iN2FtvMCtuhTlKH/5Rew==","signatures":[{"sig":"MEUCIEJDGDogj7DLy8YA6Ap9ocSCsFDa39N6lZBOoJSinORcAiEAw8v+3MbLApxI220anlmhNGlo8dyHBlO8sp7U4sB7ZBc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":565992,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfez1ZCRA9TVsSAnZWagAAKdcQAJmEVLl9g8r8YE/zO+Bs\nffroG85s2FjvRGCH5ifdOSs4EK38TSrbrmfMdKCeqVEREOtNrSlDionqQGkx\nQXd4gNwMmNZpV4pg/2ebLHg9SSZVrsYRuGDGjUJoK9OikLZerY4afZ2iER7h\nTSVLG4CrD+8ioLfACbuVfPHJlFoP7eGvgCcDSH+R24jHv42McW3tl7zZFOQm\n+oPjKGkraXUGjy85xiyO2h0DqA/cGtVE8sur2H24we3FdgW3TcLTzZMJaa9c\ny5oBpNdc9fKwGW5+vWLmmSXYcR6YaqSstnte/PrRpkeeeREaIeYH7Ikca3u/\ndNfIoBPrRXeV67YcsQG9WB3I3eK9pl1is0iYMHvIDu04i63oq4mINHqHGPxr\nW5u3iCS6a6V+Y2E0ZAPWe9Uh3P6idkm5mp85scH3jW1GqW109CmgQffQ/COF\nGP2zuHRpPq9lOjbKdxXa5lneuiVnMJ5jmhQUEd7lvHAOkNBL3OKZwNmRFYo4\nsBUiXEYxua+dagc/3HoZ/wk4oIrEtEw1hDAX1PNmU5cY6rm9nljrMHh3WcPG\nYKHSgN1ejKcD6BIOUv72bJRuKDDnbh/sqEURMNFo4XrHtiutwVgbR90Z8Wo3\njw6Zyxnn5CzRDO+YJwVSWTJIH+y5DD5CSOPshLGAe3AJGRtlt1kuUQgME/hI\nFYa8\r\n=Mkbf\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"f60c80c2fa69d509ce60a54cae260b74ac4de661","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.2","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.109","@balena/jellyfish-metrics":"0.0.123","@balena/jellyfish-environment":"^2.2.36"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.10.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.30","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.58_1601912152626_0.4526937588665536","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.59-renovate-non-major-41be2e4304bda9b1abcb583a1131b13f94539d62":{"name":"@balena/jellyfish-core","version":"0.1.59-renovate-non-major-41be2e4304bda9b1abcb583a1131b13f94539d62","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.59-renovate-non-major-41be2e4304bda9b1abcb583a1131b13f94539d62","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"28714827151c9919cf0260b47b83854276c5c48f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.59-renovate-non-major-41be2e4304bda9b1abcb583a1131b13f94539d62.tgz","fileCount":94,"integrity":"sha512-ygQvKCMmouf4vwtc/XwdFlHD47O+c9gcbeO4zLZu4iu6D4LcBWazxwQvdqYNboSM1wZYlAPWUPOEMkcutzuolw==","signatures":[{"sig":"MEYCIQCcWnEX4jRaPdO8V0GgPX/Eln9cAzAEcXRorIztKdcehQIhAP3J0Y5eDOQcHfnSHWE6oFosfyTIc0j5APEjWvXrbh2q","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":566153,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJffIVoCRA9TVsSAnZWagAA3fUP/0ybKgn5xI+PYf3JCkqx\n5Ug8o/YNefiqd7bgc4iQRXe/6cHCIfdYcjFahyxtxpejSolAebWsv0I7344z\nxvgp4cWQewuvbSQivq8iKmDXYrIDihwMAu2NLSYfreFaoZl6ry5H/kD5uGbj\n7SSIP6ulLZN7IgoCEd60V5TxNTl06TUv3OQTixDYrJWN0ZP7i2WTXpK1d/il\nO5LaJmfFgBv0V5QI5XAhLcX0O//RvGwu3Io5TQlILIqN0WykCsLyeL7svGUw\n//t29u120fqVBxArtLrhXozv9bdwa6CfgsYYwmP5i5eJLVtaSBvUQBk11+2d\nnNC9Ub4CdmJ4+Iq8hP6+X2PRKQ7uoPvasaOoFL9x1Zecw+VadD43AP4pkbOB\ng98YJN20ALU93YpWlU8echINbp3HD7b5X+T+FWj+yYroHD0RgCMUekK22KnA\nAr9wP9Vq1/QfuZY5NDnZglEeDdyh8lStgz2+iY2EC/CHvc5MYArQFIfBY+5t\nsvHtJ9F8uyYx0T2iVRjuY/abg2MDMX3+zFgr9c2BhKKLulCATETSEQ45CcUO\nj8E4tqIiW4KJ80MQTyWDjVrhlnr3gf9q3wrA6G0h4QmZugYIvnQbQC/5jO/0\ntZ6afHOjbg22RvoVn2kG/9B+aCPULfCkVIN6OSPk3bY6unbnGgIaoO9MVi2l\nz5hF\r\n=KYy+\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"41be2e4304bda9b1abcb583a1131b13f94539d62","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.2","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.109","@balena/jellyfish-metrics":"0.0.123","@balena/jellyfish-environment":"^2.2.36"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.10.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.31","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.59-renovate-non-major-41be2e4304bda9b1abcb583a1131b13f94539d62_1601996136242_0.35195676855695046","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.59-renovate-non-major-84d3dc333cc61bc2ecdb8113f6371771be102794":{"name":"@balena/jellyfish-core","version":"0.1.59-renovate-non-major-84d3dc333cc61bc2ecdb8113f6371771be102794","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.59-renovate-non-major-84d3dc333cc61bc2ecdb8113f6371771be102794","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f0cf34e041e2c980fbec69f071f94e718ff3f4b2","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.59-renovate-non-major-84d3dc333cc61bc2ecdb8113f6371771be102794.tgz","fileCount":94,"integrity":"sha512-+t9p+iMnPJjIh8rt33Sbh5b5NYiyXYLlq/71y04V8nvizCK93HTCueRnqxolAwYdni4UZ2iApgLIGW5T/z89mg==","signatures":[{"sig":"MEUCIQCD2b19sdArHwJPAkrLJZLCsjTekWKm7xG3TMW5oPODoQIgdu+6NOhy5a1V88G+FWrA/mxLk0DBqqbf9wksUjNvz4I=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":566229,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJffO/SCRA9TVsSAnZWagAAW8MP/iYsP08JbrQb6EJx7Pb4\nTBHeRQChq6JtF6qm5Vu+u5LYGfetcTr8n1MbbEIGKbYVfQh3pVerGssdwIgo\nxZolgOu5UyWAt+2KW4l8rDB5ldOVa+zF6wREkBP0l2NPbdozMax1TwMyIepX\nk03A6SB50SOagYXDaf5OyhVopGVWeU8UsUfY368fpMTG3hSwwWnumT2udN+J\n2YEWgiHX+ZL66mZsv0it80/bT68Eh5y7gGe6haPC+xRklpigsQtGwvJb1ZFz\nQXIoCwd1eUgXXlbAtrk/XhnlS7M6wMKf1/rD3qtbM2YOQgt+XI+PGHz4Dsxj\nxsxKHb6eUoYSrtsLGctDJMjPbRdHl9C3gtiau7Xh8elUywuUsMhBcuSj6ZSM\nsK1i732PoQ4Eq5Hwz5P5JmeR6mHPAKXQRsC16JyVXzAca8V6qAcNYy2vFKNW\nWc+ZMGP1rgl2cI4ztoshO/G5v+HExH84K+MCoqS7/K2+s14qVeXklAbiaCbk\nz+ozOICmi1k9Ob6dvzC/wHlNnP6CLaSpTD3Y3RZMn9zniOsHRZvJoELzK5Jm\nSM+yENzZLGz1LAM+bF4hX0bYgPCGlau8W5JYy2rdYcsdwpZ6vXiOQ15xMNrS\n0O3LVCsBaaZiifP6ZJyMxoQ7iLcBxhqXmjT3WyAd2KVQRvANiY8YScaKQei2\nE7RB\r\n=SJzW\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"84d3dc333cc61bc2ecdb8113f6371771be102794","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.2","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.109","@balena/jellyfish-metrics":"0.0.123","@balena/jellyfish-environment":"^2.2.36"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.10.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.32","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.59-renovate-non-major-84d3dc333cc61bc2ecdb8113f6371771be102794_1602023377331_0.20321948916165522","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.59":{"name":"@balena/jellyfish-core","version":"0.1.59","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.59","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"fc426de0a26e887b9d096b92033c6d53634445ae","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.59.tgz","fileCount":94,"integrity":"sha512-4wFeWvrmWrl68Hifzxnxc/KgMqbtRXEsdiUGfmhFsY3bWQpKB7Ss6G4gbwwesNOsvTMeU/YmZQ/m32PX1MBc6A==","signatures":[{"sig":"MEUCIFfWmMIqO20yOIAch5iWTvUqIsB7Gnr7zVri+SdOeDr9AiEAtEOX2CiprRRgkT3KsQHxVGhdxwmnMGbZDnZE2k1DYsU=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":566169,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJffPEVCRA9TVsSAnZWagAAZGcP/3q2DCHnZdgnlWhchlXM\njdk7K1zh4R8lMBHDz8CmS32VLGlIZoZbX5zQ5M/n73+LbRMO1rXjkUEgBBNN\naWOgiMMNBLNgiMzF2l1oV/fPXVkBk4Sb0S8cvgX5eJL/CAQOXi3wgZ7jXHJb\ntGNdjDqaVa6u9CeTEsOSw3Iz769TZlt1I5/EcovrKtVIJ82Nq3Kwbz28L3kp\nBG/l4irTgftS08n743TZ/rGQeO9JptJGJHUESqo+DS4hQkdHfmX/uYGXKrVP\nfnbtL0+A6J1jfodzMNuVR+mPn2iQ9huV2hY29/EPiFu/VPr1k7iGYQ2L410M\nWt4jYHfgF0iczXM11y2FLaqLQu908V6cII1iHZ6jul0WGNd8U/+QcBhPWs0q\n7IKKMrvmmQErAaV802s3mkKx3G4nupKhp+M3w40uhClF7eyizvKv1ufPz5rZ\nMJO2HDTqR0v3Of6eedv6/jaQf8Dhewqu+7UXjylOEnlz5cYRgr1PdfUqY8qJ\nE74+miVSrX6skN9J1c68Vo1CWopJISkuZrBv7Cde23ecup6TeXd/1hu1tlXO\n4ZQ0ayvzGfq4UUE8odYOacc/bBqGSQOKuPhLn04yVa7llT+epOJkn4O2S/+k\njGfcC7yMmvoN4G+9EIOmK7Jn6S2IJ9RnMO3OSG8QFaF+QrHMnb6OPfsqA211\nDVIA\r\n=1I0e\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"02b705ce7aca02a5f1bc94219fbf7772069247df","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.2","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.109","@balena/jellyfish-metrics":"0.0.123","@balena/jellyfish-environment":"^2.2.36"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.10.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.32","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.59_1602023700727_0.2763422463523668","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.60-renovate-non-major-efe5c29c089d5b0bea5a48593ed4596181adfd47":{"name":"@balena/jellyfish-core","version":"0.1.60-renovate-non-major-efe5c29c089d5b0bea5a48593ed4596181adfd47","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.60-renovate-non-major-efe5c29c089d5b0bea5a48593ed4596181adfd47","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ad1e70ff9deda746c0b91da9f9a47b437c8d62fc","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.60-renovate-non-major-efe5c29c089d5b0bea5a48593ed4596181adfd47.tgz","fileCount":94,"integrity":"sha512-b9MB9gE728GNZUKgNywcGPXi9KQHGYlVVCHwFx+Xwo7SPqnIFlxXOkTBTdVEA4bO14R1la14M2dSHVaHFF7y1Q==","signatures":[{"sig":"MEUCIBYXbBBZ4cXN6b7jxv8Nfc1P4uwfbJENoIlXNLJQHu0VAiEA9lcAKI+MG23HDZmql4F/zAxt0jSOVEDqZSZ0MiuTxAM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":566330,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJffZLPCRA9TVsSAnZWagAAkS0P/RTH68JmueFCnHmHfLvt\n05OYy5DEzAJODa1hxwO09d00DDez6E8hnBoSV2BUMdb8Bs34yMHw5WRMc4yS\ntJyvraHCo7MC5SrB21+ThZs7h1yvxyO4jOKezZMLV1/aDBNSGimziZNgOO6Y\namt2WbaMs1DknMsBDZ0FKirqKHMng08451KcllwA8QgkEL42mOOkBocD+i4d\nexAED0QfFBKI+kfkbmlz8QGp0U811n2XDnZY5FOE4q146jJxdbrJ5zKzC5BL\nBga+jOCsZBlOKjciSGJ+DvPEA1o3on5Rc3fnu9BCWw3pUEJfMGKUaTanbrl1\nu/0QG3eY3BqsbRzQW6KZJ5wHnz+ZjT8c++yNQVmHUqdGgKDFIDQNchr2q/XN\n+drgmZF+gn/zXWBS2136xGPiwVJjYkfMpceMjhrrq5gcvUkbst9jtDOMPrvJ\nG1kdGjB1kSPlGGW8s3gkee99XTKHDv3NVxV0bBYIJQvt/lINNLOzuIgO5eLP\nJO0qd5Eb5aepLN89LRr4RYoQFO4JIIMrv2St9HEIQAVW81zfONX0wbQiO2V9\nTbH9x5KtTctP5rm8vZZiQ6hejgfGoLOvU5gyH2yq+2bN/cJTXpvIpMXrmxM/\nr1FAT8NbjFsj9HBoPxkarnMrcGpAwiZuFEtVRLHqiUPDeMquxpzeccyRaSHl\nGXS9\r\n=K6zD\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"efe5c29c089d5b0bea5a48593ed4596181adfd47","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.2","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.109","@balena/jellyfish-metrics":"0.0.123","@balena/jellyfish-environment":"^2.2.36"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.10.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.33","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.60-renovate-non-major-efe5c29c089d5b0bea5a48593ed4596181adfd47_1602065103241_0.7060883955449206","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.60":{"name":"@balena/jellyfish-core","version":"0.1.60","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.60","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"451c9b3250c04223eddf5f030e2a3cfea6f18612","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.60.tgz","fileCount":94,"integrity":"sha512-njH88qqIzuVPuPgJ3KtxYYupv3SA5bcEDq4q2p7ZwjxnYvuLOAQRwB29fkc+8toEu5oSzQh/toOPW8Ph0mmYOQ==","signatures":[{"sig":"MEUCICpw6sd0Tzu4DvcU8YVekCWZIWMHkY1XU/nZSZQnWP9UAiEAjth6avn3iE4H/IbS/W+C7Wj2D53T7DiCl9Ouwf9GFqg=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":566270,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJffZRECRA9TVsSAnZWagAA1GsP/jqf77kacJFHwH9rz28g\nLBcyV++n5keRs4Tldz+P2Sfuf6UQ0dotDfcvK9Hb7fwuhzSJuBjQbj0ItUDM\nK8kEDf8NrcSZpRp9l5acijPM9Y3BCmaQ4mPNQs73f4tPUYa2xRGm8EjEDzhQ\ngNKgO/cTOEfMVHxH60xadm53UsIQG0k2ME7PTh+Z3pCyLnMsDR7OJhP6foDR\nlVLgweOVMGZtmEsgV1E21Xw5yUCWbV81Q6XLtNS6angc8UIH1d8chOlo+X4a\npGwLkXWwfuSjbsn519O/JKG4l0ChNJRdmCCEcPY4jSul/DtKYGXS4KnVtIzp\ncWThtlNj5Our6F6hajcvVmpc1vLhffXGZ1aFRPfBmfVzwGII9REp5ewHfWNe\nq+p+FVMj4GyvXU6nyuOlfE158/t388npFaBCoyY3mThSqKEC4MvWWO+273jb\nKug4S1fWaUlPuHnnWOJArXgNpe8WYvk0S7JSDI4eUQhn/qmUisCKcY7chY8J\nEBjzFn6szjm0tIiB29ZNsMMaNiXLjAE+OAkscWM6Tkl2TS8RSxls9K5VR790\n9JW6+a8TIC1G7GensmU8XJXDKeeSnDLIoL/dlkAlAJkpyZbQ/FWJAWTKKZ/8\nTCbgnxfzPygxPM4dvfXDBRRUakTs0A4cqYb5ay6ThRcOWZMEqwLIbZkqYS5K\nlxju\r\n=GSdh\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"ef110f4c4156b98a2d47bfa3546bfe7cdb90d50a","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.6.2","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.109","@balena/jellyfish-metrics":"0.0.123","@balena/jellyfish-environment":"^2.2.36"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.10.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.33","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.60_1602065476357_0.31354574280317826","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.61-renovate-non-major-d38a7dff79dc40da5f3ef9161b4a0b7310553332":{"name":"@balena/jellyfish-core","version":"0.1.61-renovate-non-major-d38a7dff79dc40da5f3ef9161b4a0b7310553332","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.61-renovate-non-major-d38a7dff79dc40da5f3ef9161b4a0b7310553332","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8272721d73877f4656a61c8776a89b3ff6b5489f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.61-renovate-non-major-d38a7dff79dc40da5f3ef9161b4a0b7310553332.tgz","fileCount":94,"integrity":"sha512-NgHsMh1V3mXdM+NlDh10dURnVs8GVyMps0b2PKhUTYHmpFjKFO19H6dj8KkiIuSnleqiXx9oZyCB+yfbVAXoDA==","signatures":[{"sig":"MEUCIQCH62WwNvjMPHBPqT+fydjKftrvAl4T6ge38Fw9Z3nGogIgXKYvhvlsm0LEO+BXfjax+wYXEDhChc+oP3jVcN1uC3s=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":566419,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJffoURCRA9TVsSAnZWagAAOWEP/2RarvHsl7NByWN5q7w9\nbjspRoMHlR6l5uT6+0BFfdLE/RXUwv7WWEB1rvqTII86vR46QtXCJ9L8y/VL\n9sXg2CdAXSTwtgKfAiuamVN+gQ3YytI5epSbpUWziRMZc89XC0QszzWv3X2v\n7d1nT/922dLzbvFxHqIkDFFg3NQlzupDwV3HeoK8wNcGneWnmFAJOVJ5FzD8\nnUalfLL5kGc6Gt9s5BV4izmwDBbjS9+o7RPvsvA1n0RW9uxSKYJpjKkwdOF6\nCRUFBYTITCrItfKHztTU7REIV8CRdEb8eff4ham901qILVcx8709rCCvshJS\nlUpZM0e5px/emHf4ne8Omy/K1CP0QHhPgpNwxa9dbERtRRSjpr5pLHNCXLxZ\nKxFeRlcI2WsvaUjOidf1tgs02ebrha/l1JrKqUXLCuDyT9taeQDbzX1u/l2d\n9+aQbewcMIqWOAMlJ9+voIwBDgVmUxQlc+JBpCr7xy9cOYgIm4uC7vpm71ED\nPwEmH8jXJTR1IrO5jGVrfbwrJ9TKSz63yQUynbz9G2p1FAl6XeU61+B9MyGI\nK+5NyjK6284IxTbOhmHORc8XOWFhc7HN2h7h+5n7O9278NBVA4emQkqADMoB\n4MejrW0TK6KM14umSPJtbbJuqHTsdMYccYT4WMwn1hIlrN2BNp5ahzQpMy50\ndglq\r\n=wbT8\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"d38a7dff79dc40da5f3ef9161b4a0b7310553332","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.0","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.109","@balena/jellyfish-metrics":"0.0.123","@balena/jellyfish-environment":"^2.2.36"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.10.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.33","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.61-renovate-non-major-d38a7dff79dc40da5f3ef9161b4a0b7310553332_1602127121118_0.6955089756000445","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.61":{"name":"@balena/jellyfish-core","version":"0.1.61","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.61","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"360e5c8a3798fec0be6e34f9a4f98bccd25327ca","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.61.tgz","fileCount":94,"integrity":"sha512-r/mqjFUl2jfAOhh74HdDKOi3zXtW8RR6TJKpD+LPJOTwGxfmlxA3swY0DDrIPO2gqRRTRq8L87uOSAAsNAQR+Q==","signatures":[{"sig":"MEQCIBt0Dtg0eNcdz0NejgU2Qk1dcSkkolLm30Ajvk0ufC7WAiAE3L8qRg9gcyShPFib6c+K9e/lHD+apmq3GP3AaLKFIQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":566359,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJffoZfCRA9TVsSAnZWagAAx0YQAJwhWbFu7zj5726t6vZO\ndzFspvFHGxufRVKBILRw+DO8hPxeyO1vxWGfshsRb8pzHQD022aInjEBz5Na\nXxBspXMi6SSTf7Be/xOUt6H3GrS5CoLpuLzN5A7pTFyVXw1qbFbjOmtc8LbI\nUKN/9EDiLvqYK0cbRYUJ+mP10O3etNWWQMPbGnyciywtV6e1RX/xcsYFa3Lz\nsfddh8CUOw195bSTr0GB8S5GJMIDoqprON/h0MucsgUDFyGOh1nVLwdv0eUG\n9Zlk3DCovp4LlWkAMAlTfjdNUmfqxJbX4J53bgHNAdr1B7tJoK3xlAHNHNPO\nhYFeSpXRoYmMI7my89ZwpiHcMqiubKPkGqVvGi5AiOHbrfWOXdDFk+0mnwKG\nRfK6PLTuGfGemqWgDdJUZb5wD/kS4KI7xdFg3OKa5zwzUJSWYuy0ibIaw3fu\ncrwL5CA68JaBuPlPwzjek2SiLdKoL1dqMZoTFuVAywYQtUmOs0K0VSQZja+6\njFTP6LKB6jvtY8Y8emEHYS8a68MdNhZ2Ep3bquBueKJV8pb8GUY60qDya2YX\nmPaB9DKrcxaf0Tq3nrzvY6y6wbyXSfAIQR5MTfTymMEel9C1FNulIQdtmu57\nUXSMsq5l63Fif2I+4bXnoYbmnogeSPRuBNZ+/YUYXH+wCViI19ji25q9gf5l\nQ2ln\r\n=K2oa\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"e3edc100edec7902c41bd7f922fd038c1afc2315","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.0","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.109","@balena/jellyfish-metrics":"0.0.123","@balena/jellyfish-environment":"^2.2.36"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.10.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.33","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.61_1602127454892_0.19721713493274073","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.62-renovate-non-major-5fe46f4759e00c7e2007aea06c2e1eb5695f543b":{"name":"@balena/jellyfish-core","version":"0.1.62-renovate-non-major-5fe46f4759e00c7e2007aea06c2e1eb5695f543b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.62-renovate-non-major-5fe46f4759e00c7e2007aea06c2e1eb5695f543b","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1506f521859e1595fdf7c8cc3fba198aa4f0a4ce","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.62-renovate-non-major-5fe46f4759e00c7e2007aea06c2e1eb5695f543b.tgz","fileCount":94,"integrity":"sha512-ozLXeqevUUrWwjsI962swWJD1SKBc/CSp0JA6b2sg5pHkZIA/gXJh/VdamL2zZbGgIf2jWG9iuNArgomIx6YfA==","signatures":[{"sig":"MEUCIFN5AsA0sm7Lu86cflN99gAzi7o9XGD3eYB63GtuWPJnAiEAr7ktsJuDOB79J7Sx+fx4b2WXyIfZ/gKR0hFgtceU19w=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":566517,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJff8xACRA9TVsSAnZWagAAfrIP/RXHioj/JdIR0T2QzTOH\nvYnVVLs1/n1H25RrZMI32Eike26MbIgr4piLBGlsK2wzKm85T+K66b8rNnc0\njKqoDE1Zd2DHsWoBWfWRdG6wcaROi7riDTIK8dHS7S0d2ClNur1TDtdiLyhy\n1j7fw6bZTymyj/HZknTZcPuef1N0oyPdtEqiW4ZDMPuVpagU/XyNiSr9qBSL\nWZsz4LYibhDvPWj8IBBFwEoshIbFHrH9y12PZOLi9lyaHiK4UdG8HPvaDWHS\nYNxEAzKKatSKGkrowqzilAg4QytlPvcaAX0Bb5FCxQcid/MzQ6Xbhak55//3\nflS1SDAA4NrEN0WiNHySrCY4ZNIQY86ObQKKy5lrkfGKqbwYcIDwV1e8iWdh\nsCuU4oY9l5R3SkHK4RaOgFe1kueMmuxF9eIAf+MOm78hkkDYyumtg7/fvapO\nGYgM5omOMTCNP5CNxk3qVanLL9xcX3gKSdYeAvWOsDHK+Mae1un77q21ue9d\ngmR975OI4pnuKEK2JSqbhbjOE1PStPLh72NkaPx8xsSmtAASioYKdgIhf65L\nqRKuWA2MdccF51t/2gdoewf3exVqnUc/+2WcRpU7bh9nh59aHM2zMt4/0MDU\n5KBytlMrCtI4oXQP99fdRUMEaBtqXtj7yDLHTgI1LmXjJwRO6F0zFF13Vqcf\nmQ3n\r\n=CUdC\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"5fe46f4759e00c7e2007aea06c2e1eb5695f543b","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.0","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.109","@balena/jellyfish-metrics":"0.0.123","@balena/jellyfish-environment":"^2.2.36"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.10.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.4","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.33","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.62-renovate-non-major-5fe46f4759e00c7e2007aea06c2e1eb5695f543b_1602210879846_0.4681494010227958","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.62-renovate-non-major-bc212dd423a646371fd54a2a1da69dab75dc672f":{"name":"@balena/jellyfish-core","version":"0.1.62-renovate-non-major-bc212dd423a646371fd54a2a1da69dab75dc672f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.62-renovate-non-major-bc212dd423a646371fd54a2a1da69dab75dc672f","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e65e9f7f85381392457c01ab46c2095fdfd2b015","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.62-renovate-non-major-bc212dd423a646371fd54a2a1da69dab75dc672f.tgz","fileCount":94,"integrity":"sha512-SRkLSkwGw69lU0ZekFEDp3sL0TcfjDmbXD86osMSpIisTRcfmYN6W3A7JjcK3Vd7t2T/182jf2X4FaE7BxKmuQ==","signatures":[{"sig":"MEYCIQDXe/A8N9KVX2abUPhcqqAhB2hJSSkPUe3Ro+XM91BZEAIhAM1UNtLXtmM9mK1vlDzWullqgRfafaP05CtsldfExQ6F","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":566485,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJff9TrCRA9TVsSAnZWagAAfW8P/0jsctPMFrPtj/WGuIXa\nLa0d4JEjbZUVOJMcvMbMHln145qZUI1q83RotU6lbNiqJoU8oYikODnPjVvo\nAIyOeL6VQzvRq8bJWK2gMCpA2Jsz3oL19r85fbrVttWDKlm1Z87YqvEYlPcR\nNjjWfcueydULX8tPNgbQycg8vaQIBj2+U+VQ9SCN30ZpBTZd4pdRXk+EHDfs\nQAAfbZBBlvEvAYQvWWcNgPwD9QVMVYclNnJj4MtFPuGyGXoLwEdvCspBWhHu\n0eiLROHaKlnFXYougBpT3Iq2q/TL47NXSw3qcvhPEvrFh3LLxIdgtaEb6cyF\nWXqbjFq1MVwoSbqV68aKFNmO1rndN19QAorFV90meSYYJsYqt83C243X0n1d\n/9A8OxaS4WO6Ocz2dLRC3Me7q0ePUn6tG1in1BW/Beo2K60Y6dyr1ni/KesT\n4uEcIoHm4yO+5TEMzpLV915FrJYRjC/8/WOpQtebZ0MR+RZFnt0PyCmJct0c\n6koTcTYaSRptefvJ3K2reJHFrjJa/a4fVS1jcdZV/9p+YKJ/0WJHPJjddTWt\nVLj1yxSCy3h9s9aw0SUXMtAE4lAKA8AIzxGenacQqyuVGENUo+Bek5akwofn\ntS3q0bA/6cRSXmekSTUyZuhGCRuVBDeqoXG97+R7xUjLyBtymh0t1MJ1rH8U\nMKzD\r\n=1oH9\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"bc212dd423a646371fd54a2a1da69dab75dc672f","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.0","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.109","@balena/jellyfish-metrics":"0.0.124","@balena/jellyfish-environment":"^2.2.37"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.10.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.4","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.34","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.62-renovate-non-major-bc212dd423a646371fd54a2a1da69dab75dc672f_1602213098663_0.4201027293232611","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.62":{"name":"@balena/jellyfish-core","version":"0.1.62","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.62","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e7f640c2b0db7dfab3cafd9d681976f6b16111c2","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.62.tgz","fileCount":94,"integrity":"sha512-dfx6D3o0WRqkA6n/y855Tu4c8ax4iua1ZAvR9Bo1xOrKGhNdR+NCaWjMj8kIUd0gwkKlKX8qXdDp8L5CVBPptg==","signatures":[{"sig":"MEYCIQCRUS/iSuTSRg055QOUXcddee+ea+rl5bE4+EgMI5LV3gIhAIlN9E2sSXPNILcom7RuOmOecAUQfiaM/wxCBX58hvYA","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":566425,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJff9a/CRA9TVsSAnZWagAA3PAP/10vf1RO2QktmE/EJ07Q\ngLZPoh0P6pDC4wydfsBDfDuWUzRb1KMtIRBzzzgRFgC/oFB48u9Ibz/+Xug+\n/lOAqbUMDxfR79TYRepkiqqoHjYqKSJabldOJQoe/4FoMas3tQhEKBOIA9k4\nD9p7u8eAUYZE6GdfVqwvaSOipAJVWS35cFAiITlr3MnN/uiKEwHB0gfXihIu\nkeDXjwjp7Ld1pnr9PgqjOdEyg7zAsSNw6qwxeovtdKhE420z0g+5TwcFhK0u\nFRtg8ruezfM//wnBhtsxFWR9MRhTezPgH880dHkwgMGEXAbjOmbelIVhIjwh\nSNiwqRAE+XDo+BLzYncT/zoV65+lYdNZOvF5CnQ94gi+lYYrLosniaQHpr3b\neaw7aVhNeG8YQ8JPDCd0NyEI60yrvY3hCi22hmAWYtclooLVBcbAZNtQ41tl\nlAaANxFePgBA1eUs9Wg3QGCwkslLLDOS1ftVmxo9Rnqn5IglojsDRXelW+Uu\nMqINsW1zgYWcSfpo/+6ifUz/NMHswg896saiksvPebXdPV2Uqi1SRbjNyPnQ\nv4nrp3C31BUsoIv5ld9wqYIEewCDwnRefVwHu0TN2R6cgp/vzEh2wr3sn8hz\nRomb5m3pN0hNj8jGX9FKMVJizWuVldRaaF0XvnMuEsC+iSmnkn62c7+VOjU8\nnLuu\r\n=Gn5p\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"ba3cbaa94cc6a59de498f0e9b2d187db25a7f6f2","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.0","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.109","@balena/jellyfish-metrics":"0.0.124","@balena/jellyfish-environment":"^2.2.37"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.10.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.4","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.34","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.62_1602213566820_0.43638253934845217","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.63-renovate-non-major-b960ba8a60611d0fcee505d1b3560df7c73ff779":{"name":"@balena/jellyfish-core","version":"0.1.63-renovate-non-major-b960ba8a60611d0fcee505d1b3560df7c73ff779","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.63-renovate-non-major-b960ba8a60611d0fcee505d1b3560df7c73ff779","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ea473bf87a7c007a8d485afae44700aedb625639","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.63-renovate-non-major-b960ba8a60611d0fcee505d1b3560df7c73ff779.tgz","fileCount":94,"integrity":"sha512-RLdqIn3mOb1953BoqylV9GYzun0hYSSUOdmW1fYNEqxUWwVBqfTN5BtEADEyMYN3Xh//oHLs9zacUB6dnF3p6g==","signatures":[{"sig":"MEQCIAHekMSjJ5dzMYyDGKxMphxhTNHmKdqZtRKEgD4gUrkHAiBkSp7ym04RlPAOyRq4jX61m8M4UQnnpcLFvaXjU44Ybg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":566589,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJff9e6CRA9TVsSAnZWagAAhG8P/A44e4NdCXb3y7FK2w4n\neIyVAdnuADNriC2nssL/s/vNIG7VyFl22lRN/f04z+6D9SUGSDCQp3uZkDH5\n/QHhExoihYIADuaE6g+2qzK1CUnrfTZCFRzuUwcEntui4EGMH8bh9rfTOF64\nuDnQJqcPntY2vwQz1RgEwBUkoup62/cMKsJcBT38j1iAUKA925nwINQkPAdK\nLKYTNWpLG66FVY1ZpC2wbIlJ7TE1yLHRMjYDjyBaZty9n4gVs53Uj/YElwmr\nnrXO+xH/SfPVblr2a7l/bEtIWcbosUv5cCbjGxT2wrv7rd0/VMQg7CN6f2IE\nfdqR0BAOa5aZTYgxKPMQ31CGNjarl4pUX+G82s1Z9BFBFLOPnOUIJLECbM7l\nXjBITsGsEwZPwGtGTRiibgSLrA+M1Ek4mFDomAS8HyBFxUU2ckaG+N4MezzT\nf3vB0FF3f9VKgBAyoh8+OOkBq7ocpSIa2drZ0RZWibo5swGqdeQ9vmsaYD4j\nG/TXUJdRlrdYRjxHRu1hRXftBnZnysIkZ72DWhOOTLlOJFEX4kRD+yxO8Kqi\nOsXgxwDXW9FOVwEVJYZx5BfRL/a1HICq2sv97bONZJ7bh2OR/rHCQyhrG4w7\nAc1W4LOjUwtgXEA5wDv+MQaXqdznrZFPZrJJB45H98yofmzS+1EJ81LrKwCy\nBQza\r\n=QzIf\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"b960ba8a60611d0fcee505d1b3560df7c73ff779","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.0","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.110","@balena/jellyfish-metrics":"0.0.124","@balena/jellyfish-environment":"^2.2.37"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.10.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.4","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.34","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.63-renovate-non-major-b960ba8a60611d0fcee505d1b3560df7c73ff779_1602213817862_0.9513216616306595","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.63-renovate-non-major-274660a0361a13969acca336e8c8ccabb7693dd1":{"name":"@balena/jellyfish-core","version":"0.1.63-renovate-non-major-274660a0361a13969acca336e8c8ccabb7693dd1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.63-renovate-non-major-274660a0361a13969acca336e8c8ccabb7693dd1","maintainers":[{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2489d054dbcc714c26eefcb7a895278ece6c4fa3","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.63-renovate-non-major-274660a0361a13969acca336e8c8ccabb7693dd1.tgz","fileCount":94,"integrity":"sha512-cEYpcr802myA3ZywBRlkxfV+iMmfR/6v1clIQQ6lBBHYgBfLafs5jIbXv9UU2sSMSCzPy7UToOgfa4d3Epx14g==","signatures":[{"sig":"MEUCIA2vVMOwnlYytjeEfhZe7Bpbn57zn0aXbe4sUcHy72ioAiEA8AuQdOkLMa2mggbRIL5MDYUUmSvrBLxzE2qoo7E+Vts=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":566551,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJff+1PCRA9TVsSAnZWagAAzZsP/RBLCkpyjje6QryYkhxL\nz1FVlwdIcx7NHOENq2KF1PwkjG2mJDCIbiH81mwmn8iWtu6sLHouDz7YAtxx\ncKv4Vfe9shXB8NDjL0S7By30vBKpNGX4ATKRdBI1yN98iJid0H8BzR4Btlav\nZHLJsyLlPdC+84BGi9CvfmpYGZ/Ro9KvFU4AOfeJVl1m0f7wgeN/93fNRC7e\nynrx+0ImvIRTxLa2fUC4oladgxVBUo3p4EuLP5Z+6PtJmgLfP0aW3qmG44DW\nY0aDOm1+zOdZuJnqjIb47nYGsnVV7S7rF/KdcrqVrVyfuqiZUSiCVPNJUspt\nhexMXZz+yFmYQp22vJ3OuTTa6neMNlF+uVP9mq8OMJNx05hCoLFtvcN3xBQ0\nl/fgjDwfuBQtk+PXgWpCByg5DimaYERkj6MqKIziQEoNYFXRZcwxx3+DoaYO\nmRY7BYY0rOzU4OBRw4/MSbVC1xcLWiRyrdTfk9+yvKIhJRfqr6qT//ZyiluR\n7XRoLhO0xUD5BuXW0NZM4xqnF4Jz9nf9e8M5wCRpCBz2WRB7YZOzhj1tmOxJ\noArYYfkVfCwmXhqwswCrFKimf8hQVDjpaQl6L+Nnf1s6yv/PcJGX5HGO3ivE\nUqIWRJis5RvD0iEVOVYNipNTKwKFljqliWQy0H6fPBCajuO8Tsnm9ZTz9h5E\nF2Qw\r\n=SL8T\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"274660a0361a13969acca336e8c8ccabb7693dd1","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.0","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.110","@balena/jellyfish-metrics":"0.0.125","@balena/jellyfish-environment":"^2.2.37"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.10.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.4","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.34","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.63-renovate-non-major-274660a0361a13969acca336e8c8ccabb7693dd1_1602219343062_0.5391636266349793","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.63-renovate-non-major-d2fdd7e139e213d2c321a3845df7bbd7f48844ca":{"name":"@balena/jellyfish-core","version":"0.1.63-renovate-non-major-d2fdd7e139e213d2c321a3845df7bbd7f48844ca","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.63-renovate-non-major-d2fdd7e139e213d2c321a3845df7bbd7f48844ca","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3a168b03853b8e715e4dbed25ddac62e5ade1654","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.63-renovate-non-major-d2fdd7e139e213d2c321a3845df7bbd7f48844ca.tgz","fileCount":94,"integrity":"sha512-x4s4eZ9173h6ydDKAc5Rlecq1PyvqBurjTyqgANWrH7HV2Os7DyAjhjpZ2BWkbJSQHiw43id8bprfJ6N/WGMNw==","signatures":[{"sig":"MEYCIQD8+s2EgbWWbl2z7GU3LRQ2jrowk9aTYGWQdGEa3r8EeAIhAMCXoW4C6eDRVXksG/GUpgb1/pyHRrHBxJQKJUdAZmJr","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":566551,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfgNCOCRA9TVsSAnZWagAA7lEQAKNp9sZd8ghrOgQ2il1M\n2Xc6C3Lnn7FjHVTLewJilCqI7W9S6UIxP13wcJ9HHOKpxgPt/MIJLQp7rD81\nue9yud3F+85FrVw4IS/xx/4LVH1zsRKjUox+q25lN4EufLuZHWjPQMkBmItC\nl5xFwKrqeaEh/jn9MJ5HlexAAhom4++L6MstJtdFeCjWNDpCJgwrBuwDHY7P\ngOvJpXHa2f1kxir6wYIol1TKQMtFAYyhy5PJNr9v57CIBOoBtb+ypmCP+Yt5\npxZZORqvIE0sSn45KVCBPHRgD6ixYWoLNhddi/jst0nYpHBfgxJi3X6ee4NG\n/TGutfAdJV75nru+tO42jACr1YApKL1kHBpuloKuDdMyyw9U5c1rRrDSU5LL\nwndMvScc9PiI9LdO0diB2kQrfDUH1Ske35QnGJuyxD9qG0KhNPywc7VVDxYl\n2YmcpXpJ7Ij1751RB2tz3QhBAVa6CJ0x3GRK8sSYZed2m2IAdXAZ34SF16Xo\nXRvjHHUlfJf2uanJaD37FUDke+m5naM7yLqf1lrLF6l7GSmYj/ZdYBCjtUPW\nE41lTCBU3K2G37MPRtmFdgDxCnTs0jnR0Fw+cL+MapBf+Bpsa6/Jwjr2Ah6c\nj0SjaXlW9ea3ORxycpkziGKxBSaFtghTiJak074oliW6bzJie6zD7nE/+hx6\nHBSs\r\n=2604\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"d2fdd7e139e213d2c321a3845df7bbd7f48844ca","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.0","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.110","@balena/jellyfish-metrics":"0.0.125","@balena/jellyfish-environment":"^2.2.37"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.11.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.4","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.34","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.63-renovate-non-major-d2fdd7e139e213d2c321a3845df7bbd7f48844ca_1602277517739_0.4957144521385495","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.63":{"name":"@balena/jellyfish-core","version":"0.1.63","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.63","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ddc17c6bc8051692cb03c8c8ba32bf02999d5928","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.63.tgz","fileCount":94,"integrity":"sha512-zQdTIGmEd9/ESaignaupzcLsyA315ecP5OhLd/F5uAHd3ypPhHpZRNT2FnztVE6JiTkLfETlNwJ4ZchiaJkN9A==","signatures":[{"sig":"MEUCIQDh0HmmQkYFf80zDCmqzvtVJz0HaBb2viHH0c26tHFaqQIgWECTF5vrm4QJbftLeGWGXcwXqYRu32Sj4Z7gzsXyxgI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":566491,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfgNH1CRA9TVsSAnZWagAAA3gP/1s/amtE1nxXYaQwR04E\nv1b+K9OC0crKovJT9s2aRDxEAEmMWVatqDBmlcuAnpxkaFTytQKCFwZSk9Tj\ngvM8O2hSAary8+B5MO0eB4VnB5H245ByUZqoor93T2Jq9CgwtoKGh3MqyLeD\nXe396uLzNKSpuBuYXwePMxkV/WGOPlQeqz/K9SRddcye5wfvZabB6DGdkxt3\n2QLu2N5B706jzO2TZ8pYrNV0dZVPvvXS7AE1FkOxjusoO/Qm4q8CJSIYxD2P\nUqFqLSQlzluJJZ90Gbt7cGEMUeqU7pNw+2kip4Aquf0pR9/OvZ9P4ooJSV/t\nQK+Ltaizq9d76UEvRj7dmgc4fDgoOubwPFC2ingc5cGnkCIP2IByVgLKmNi8\nZksH6b2RmqmXdF2MIQ0NS4Cq66/BROOYZ9NR7hp+FogEMEOrRbMa4pk5ls2V\nff63kcI/KvZKupQyrX3lXE5xiGrtRN5Bo9+sD24OS/wst09nsevmT7bIKwp0\nAKexAk/jRabKhVBnZkuCVlecFmbXXU0nfxAVCOjV3Vs1GA8D+MNcCntQLaxR\nm+TKt5t2n2gA62EkFGv4Z8xMm+xLKNlEpB6dCfl0RVPrArob/VPAzDmx3qZ9\nY9zIxpGu10sa05DpfkCe2y/p6UtfHIc7d12K2HnxQoxBmPhXfM0QaGnBvktq\nfmZn\r\n=vLOg\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"52fd4ac57a32180e87e194e21c5e740e5d6502af","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.0","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.110","@balena/jellyfish-metrics":"0.0.125","@balena/jellyfish-environment":"^2.2.37"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.11.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.4","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.34","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.63_1602277877493_0.28958415390646897","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.64-renovate-non-major-59ee1c5cc1657abca31f89f6914f35c866bc88e0":{"name":"@balena/jellyfish-core","version":"0.1.64-renovate-non-major-59ee1c5cc1657abca31f89f6914f35c866bc88e0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.64-renovate-non-major-59ee1c5cc1657abca31f89f6914f35c866bc88e0","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"7c91a7be4ec3450fb9f0baeb968f5347d616fe02","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.64-renovate-non-major-59ee1c5cc1657abca31f89f6914f35c866bc88e0.tgz","fileCount":94,"integrity":"sha512-aGWVXGsrlR1/2jKm1gIkDa/C4VFIkW62pAiOTquz8PHBYuYX/SXDoTRERAax3nndjRjyvBQGkhRqYUOiN9sYqQ==","signatures":[{"sig":"MEUCIDCFPAIkGorc4X8RSgcQSABPMWOQOj68w/qaombW8sJNAiEAzBO6jU4VVmS6Tpqp8OqtKbo+S3Qa4biB2LfXPNHWuEg=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":566656,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfgNKpCRA9TVsSAnZWagAAC38P/inF2/dyrOyPuS8uvgzU\nSmVSrUjRKZ5yOTxG5eYKkJ6otIE/Paa7euv86Rzted4tHBCw1cYW4Wh1LCSb\nX5VNP1rEtKguPbJBY3yU9HDimLEvE5ZAN59g2J2DKwWLg9xrooWunc8Z75Qy\nMyHSF8pAIy9NVLoawC13PEFgvAhBhWZ5dbJCCMD5dSxIwuC5SkV4Fwq8373/\nRP5aZxbjigGdX9q0Md+a3Zr/3qf1WHSGvJhUqfpinm50DVhyvI/8+GXh77dz\ngAlcmobhsz7DL4Xi0zZW8a+UIqvV3zncDIEzMd15XyJRLAF6XbrobBehZ9y+\n0lMhbibEmiRuMe/vAoUMn/JyAjpaalxoOstlNFQwhCH+KC7fGt6ELPxkmbLh\nb8H6G9tbhuxwh4hV4UlQrWafZ+dFkVEh77vSahsjJHO5f1xBglTCxIG/UGrE\niHEXHImhhIrESpEgJ7l7HKkmpsyYzu0frlEI9T7wDZfUVpQAt/f/ybayiDaM\nxxG9M9Z2wDHofWBg7feHfgigmJOyUsVvFBFJNqWJpZhgiRmvhmpt3whwcDwj\najKMOHnZ6GPbP0DGAb6QGQcxGSz5Og2UycL8EpqibsGPs8ZHviuK3GAjfxZ3\n7Q2Xvbid3+jcC90YgjrGFsfEuDAbcderbB+8dSYcHkihBzoKxP6vVSxWdMzd\nYqcl\r\n=RUNB\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"59ee1c5cc1657abca31f89f6914f35c866bc88e0","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.0","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.110","@balena/jellyfish-metrics":"0.0.126","@balena/jellyfish-environment":"^2.2.37"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.11.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.4","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.34","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.64-renovate-non-major-59ee1c5cc1657abca31f89f6914f35c866bc88e0_1602278057271_0.7125290203013923","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.64":{"name":"@balena/jellyfish-core","version":"0.1.64","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.64","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9e46393d8cb28e7cfc53f00fa3c7a298cc6a43ec","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.64.tgz","fileCount":94,"integrity":"sha512-tTbFgeN8zTV5QF5it0yjDpBy6dXgMKouk5X15z1VEWndDQgHckq6Cdg8QmR6MyD8lzbTBNpzVHWMe9SMY0Uk5g==","signatures":[{"sig":"MEUCIQCAMihHAUZlkg5hD93OTsBh7eS3obLctmqXhrk85/kpyQIgRQjd1+gf8yM7cRTsk6aIe1VN9a533Nig8aquwCtAanE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":566596,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfgNR2CRA9TVsSAnZWagAAUdcQAIb9TY9CHov/l7IOizJm\n1NZ9U3/Kw76T68N96lohoU/HYWzAEvkKHfco3nGjJ0GEqZv2AwlSP3xCvsFz\nEs30ejK/vpR7+JUUn1L+llJvA43NApkdpvBldpLTe8yhr/CNYYqvvw23D0zl\nYE3iwWsL5VKs8lGmeg+cwHDrLf0ymvqRi0yfqkzOqBAVXxCLfJhlarBeLfqa\nok9l5S5jMFNzuR2AWAyLuNURpzVlgMgh3j2T4GPlLA41HBaioELdid+O2ZRd\nUR2O8fZLMPMTYW//PzJttk5ezSc20SI9GNuOaQE12qxo/WChatp/433iZBKq\n13WEilSOfo2ESqxxl0gJthgqrhNfYpaMzDk/EtZ358g84hHC8m4Oc8jKVrx9\nhYC3Dby+xdcSgFlBNtqVaI1n3rUF5q+pU79JNG+MQOEIRs0752huNi+dwgHa\nX7z88ALwnBV6wtXKQfiHSlWEVgSXEbl2udiqbv0HsRHHMpYT4wAAHnXTrtki\nUi5VMRpEXkssUyKe5LlAhrPtcvnKU/pzmnVUUwwrmqT+qRhV22O6p+mTVgsq\nGxBY1FL4E43x6kLeZkoJgUjKxboB1caTtarcp5p83TdYZY4yQz/Rub6PYOA/\nvEmWvkF8ftN9QY4dXRwWpWdr5D5S3jbo5mH+E+o0UDrJRwWKBV6reVVVH3qf\nWzcQ\r\n=9jJr\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"9ebae39ab259a072ac344fd06f421b8f9d7bb0c7","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.0","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.110","@balena/jellyfish-metrics":"0.0.126","@balena/jellyfish-environment":"^2.2.37"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.11.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.4","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.34","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.64_1602278517857_0.4786168100747228","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.65-renovate-non-major-2278a485de86bd5e882daebfb5bb6fc168c1e2b2":{"name":"@balena/jellyfish-core","version":"0.1.65-renovate-non-major-2278a485de86bd5e882daebfb5bb6fc168c1e2b2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.65-renovate-non-major-2278a485de86bd5e882daebfb5bb6fc168c1e2b2","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"64a9eb7a8a30a9f2ab582a96b90c9614cdeca6ee","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.65-renovate-non-major-2278a485de86bd5e882daebfb5bb6fc168c1e2b2.tgz","fileCount":94,"integrity":"sha512-wKfIkGN2HcFz4ECS/UGOcPfUiWSAPenWi22XiZLu49tyzx5mRXE0vX0/tugi5KG1zyaWoGNN5+Qa2INpHtjruw==","signatures":[{"sig":"MEQCIB9YoeNd8X0OSL1b4QhzHwl+e31Ua6Ag+11JD4DA5divAiAgSxkTyUaLL6uyqpA4ZWJ0+4KqaZqV+v3Yo/myauzjEg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":566757,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfgNWwCRA9TVsSAnZWagAA4vkP/3Z6HGCjh+lkCrDGu8tO\nZBoEFVDxvdPSlLFngzvDk9mP7swgWpLzUMAKyI0n9MRJb/sT3nQmb5j0Gma4\nuWTfXtRWVb9NcQ04zOPM8Z4ZdzjTPek9RXhYaX+eL9S3r3zCxYQfmhwj52F8\n+/Nedr4QxFNkvyvVXJMfjoaSmu/B9wv0h3JNMsgXrImIBfGrVVFdLzTmEZ2w\nt57s0HRq8LOnKKDB5TMvtuCWFiHfO5QpU/MuVqv0YuOSH51wbPrTepEKfCE4\nrpdgQojc7GoUD4m68FDChinxfI4gOHES0SmwY6yEJySIBxWjrzQBVELoujI7\nhJe1Juqs8m1uzaMMxMyfVqG3xUU4jOv04MDcTi/nZwmVxmDVuwEpgiIVVUQ1\ndOvocH25a69qiVBBAOGMlGKUDhH4zS/dR3LcmNK7RKZkoQV5iUOh3caAXnF3\n1j4MRWiolwCrg9FcjenWWAHtASE+8dUR18pgPjmCRQVZLVN/Rw+qEEMOl6Ga\ni4s+aSvihmQEUoJKYcWNYP9C5Q2yisFQ7OZxbk6s8tLQl0sV/G45McDQUdxr\nuHeVx1f0Gbk5Grj97y1xtQMOe5NBR+pRcsTDT5myevSWbq7WNBn5bCXd979W\nPHvGkj/gby9Q0bA/647rmrJjo4TRTacP0BJB2bC6lO5+xAC0Kx3636/iRV/Y\nmOf6\r\n=1u1o\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"2278a485de86bd5e882daebfb5bb6fc168c1e2b2","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.0","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.110","@balena/jellyfish-metrics":"0.0.126","@balena/jellyfish-environment":"^2.2.37"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.11.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.4","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.35","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.65-renovate-non-major-2278a485de86bd5e882daebfb5bb6fc168c1e2b2_1602278832354_0.3573725489766899","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.65":{"name":"@balena/jellyfish-core","version":"0.1.65","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.65","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2591550bfba3d06b2375aa1caa9d90628760cd30","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.65.tgz","fileCount":94,"integrity":"sha512-jZ1ZXCHD9Z4OKK23dJze3U3qSvway2gNsMpTDWIoh+P3vhCOypAgSrUpu6PU5J7EqL0Hl7kCupByxonqE0/how==","signatures":[{"sig":"MEYCIQDLgXhufjK8tM3k9raTKyny2UcbY11VN8CiYJa5w6ZFEAIhAKagGg8FRG1EBAzedX1Bj0gfyWBCpIp0S3bBimW7RP84","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":566697,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfgNcnCRA9TVsSAnZWagAAYVsP/RRaPSpCxut/VIvsbJNg\n1APLlo2cM+YoSDZFT4nagRZB92/5eHB16cqXREKLE2R5aZOwGPcO5+B8Yl6L\nAG3PBeu1vXt83/CI55CZ6wD+VDOw7MRlYrwmopuFin3wqxwmF/zJnvLuJP+U\n2aVnDzvuLxtTLqzH0dxZMwbSw2lMH66srx4UIdjEZOHIJj+bvmhXK6bDeJRU\nJKXH2fC2bBQx5OJii/eQmoIq0pOboLP/LeSo1tvRCObUlbl5mQhpQPQ/9ZQJ\ndCrN8uCGTJP23aGInkfzaM+BMQoezR72qlOGqKtB4Ef7W+Dl6FZa4O2O5GTK\n8TZTvIm72pKtcIDXpTNd2hkNTkRb/5cRYlQ8+ps0y3v68piKJT3sqiwXZneB\nv+Fs+6utG3XQkH/nSRHYngvO7bxWOrIdvYNDWCzEjnn+7MpJvSK6574ZNJ94\niFz7dxtbP+9tMZzdLq8mMzD/ZcYYEWC8GJ6A78PUYa93XFuT+/j3yrSFUQoh\n+XhNYT+j8M0wfWEBz5D/kjwaEeBqRBGAq/Kmjx/sgL25o4aKvW/fSt4uhzTA\nl4U+OPy3fVZq0VJobJHVA1O0Lmd4X0AqxTpnO00UDc0eG4ozXCZ7K300K+lh\n1xtScBl8/o6munKyP9DMiwT66wdXTdz1HFmrcglqy8jblX4Qu6XWj4GW3xnF\nZ8Yn\r\n=QRh7\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"fd9f7067c394d93451b1ed074d78febf2120bafc","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.0","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.110","@balena/jellyfish-metrics":"0.0.126","@balena/jellyfish-environment":"^2.2.37"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.11.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.4","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.35","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.65_1602279206902_0.128552132269788","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.65-renovate-non-major-cf5b80eaf983a10ad37c46a285685bbeb5c2adca":{"name":"@balena/jellyfish-core","version":"0.1.65-renovate-non-major-cf5b80eaf983a10ad37c46a285685bbeb5c2adca","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.65-renovate-non-major-cf5b80eaf983a10ad37c46a285685bbeb5c2adca","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"692a8f928c8e5f230af98279782d975b21777978","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.65-renovate-non-major-cf5b80eaf983a10ad37c46a285685bbeb5c2adca.tgz","fileCount":94,"integrity":"sha512-0rbm484KbgDx2skgx2bO6KLvC/1Re9PtO0dRcKz22ZIhG4YY4V/QFlv7SrowVkK5x/RZE9iVcA8Oy6j4gkioiw==","signatures":[{"sig":"MEQCIHCYC/lH/CrYH7dYk37dyBv+R44TSbFPcGNqVPFLnmvMAiATERsohxrNGXeH/NWL5XnYnU2eDCRM5PSdT0jY3OZAQA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":566840,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfgNfkCRA9TVsSAnZWagAAiYIP+wSI9qPnXq09TxEsjf0U\nyZaxQR/S/JRcaiPhATxFm8EcCySAGibg7ayUFuhGal9q+qHaD4Q144JG9ABq\nWlSd/q3Y/Jl2U2E5cdicbi5VY/QlmmIx73nzBzH+IY/AT+kcbLkDquInn/yV\nw/S+0yZ2n/kwEoOr5JQGoKwo1pc25KTTy8Nq76prc/kCcPNt3Loblq0fBcX/\nnxUieMiqYf/LXTSh83NsorO5LPLHoijEjMU+QTjkJhFpPg/0E5ON5G92RUqK\nREZpydlltSvaNNsXE2+CuwRehDqQ59b6iaQ3MWfHTIz+RCnlvwnDIXEi0kRD\nkW4PqzDVL0b+T4iaVkpmYc8+hMKDWBXRvygOE8sdUCVfliTAozebcHIigaIz\nsxAeaBgh9xyCVZatqiq+DB0nEygbuMRKp2qpZvL6/3shinVkHS1Hv+hhJktj\n1VMdTzeSrm6Jfj2lqdDKURy1+PAQ4MUUHgORELPb9dZCwcgYR9Pnalav9I3t\nKOuj1EFofSF4t/IDYwR+Be07dbg//c8k2YnuJOvR6P30v0Nd4jl2mjCrT67c\nBsKqAcb3gDIwAYxgSg1pouCinmFWRv+OQ4DRyeMlOUS3iDeRoMNaYrkg3pbU\n9Ss2iX02vnqtluU4yc/2X9aLcL9QuxlZPhZ1E2Ze0KJWAKXBmrLNNYi06OZY\n89Vy\r\n=wyqG\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"cf5b80eaf983a10ad37c46a285685bbeb5c2adca","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.0","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.110","@balena/jellyfish-metrics":"0.0.126","@balena/jellyfish-environment":"^2.2.38"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.11.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.4","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.35","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.65-renovate-non-major-cf5b80eaf983a10ad37c46a285685bbeb5c2adca_1602279396328_0.011383996751276326","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.66-renovate-non-major-d7f3d4b8f6caa2ec8a990e7914b6c2d3e8bcb2db":{"name":"@balena/jellyfish-core","version":"0.1.66-renovate-non-major-d7f3d4b8f6caa2ec8a990e7914b6c2d3e8bcb2db","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.66-renovate-non-major-d7f3d4b8f6caa2ec8a990e7914b6c2d3e8bcb2db","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2f5429852820f2cbf7b3690804ac9df679ceeca1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.66-renovate-non-major-d7f3d4b8f6caa2ec8a990e7914b6c2d3e8bcb2db.tgz","fileCount":94,"integrity":"sha512-cNCV5xbjbCOBLFg+Gp6qLDDizK32dibIzoVXLATLHRlwim0WswBvPwfYLTAV5ka50RfA1YtZMT+qPfZp9Aiwxw==","signatures":[{"sig":"MEYCIQDfLJ0TaHKYBJ+k14kYi0Xjwnbsg6sq3f05zL7I+vC+JgIhAIPjob0llYlzQNtszwHZihSBKyEZ9tLRmLKENIcYzLBw","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":566823,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfgOXhCRA9TVsSAnZWagAAFaEQAIPsN4GAZJla+4nUJjUd\nTyGsPuvThaFAIbKOkIIFmD8vjPMaBxrBflr+rEDAVDNTdhuGv0UGROhJ/l5G\nr5dCwjWYMUJ8PkcKYMQfv9Fhn6+2uRqlNs8z35+v9PLTul1x9C6xIEPSukRA\nUEjGk8E2b8sKCUwclXXXzkM/MCYs6i9szP5lqkbnEg/ucHCoo2X85axe94tk\nBkD2EEu/DQWZVzR4Yak+Mh3GqWp/frI5bJXw3WoCnJfRzBiuE2vhYFNRnWmk\npj9YW3QDPDvDYg5AafmHQyprl5RB6l/FNWAUALzSvZzbHHdjgylKcqGzvOl/\nk27QfpqvTS1056rsVSWFm0sBxTjkhvCWxWDRyCg3MrKjfxOWHirT/CFCN9xh\nR3eghHRFTjeKFnzbzFZUAJmNmilzy1Ryrafadlpehx1HEmQ17fsgYek1V3OE\nvXKeHVOlvgbR92CY970TnJX3kNchZ75B1MplLXYRGQIlimD2gQjYhNdZu8KZ\niLHtWo4HroUwJA22qgEGoryfPJT9jKHRmIKBjj18zxWFTvJULwRUoDZAd8QE\n1GEKTlhZDhWStCnIrrUwEtDtyk2XiSsdZZXABpOTf3xokHF6LpZOZAQzJgw6\njjr0xPt9k7yRX7O7snxH5Q8FwrP6aCbhhCKXpuvKU/yoA3OhsZlXMaHkPS7w\nmsDv\r\n=Oj2e\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"d7f3d4b8f6caa2ec8a990e7914b6c2d3e8bcb2db","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.0","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.111","@balena/jellyfish-metrics":"0.0.126","@balena/jellyfish-environment":"^2.2.38"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.11.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.4","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.35","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.66-renovate-non-major-d7f3d4b8f6caa2ec8a990e7914b6c2d3e8bcb2db_1602282977307_0.07456398635229844","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.66":{"name":"@balena/jellyfish-core","version":"0.1.66","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.66","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1f0d80e332c119111a1d5b023c7e458c07fea2b7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.66.tgz","fileCount":94,"integrity":"sha512-DJ40YKHT6C8o9MVR70kLNbBwYoUD0v/+OEzD43TSReYau9clGBbx1SEZVcEyYrOmjdU6T1zr2z+b95fOuVE2yw==","signatures":[{"sig":"MEUCIH89+mgtSUS+H2+Q9+LLss7/AuBCl6jMML+U7a2R0D2XAiEA2AB/wj0SHTyRBSQVA6zNAMPT0IxAdibFYL5axvB3mWI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":566763,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfgOeTCRA9TVsSAnZWagAAzBYP/3PMv6uJCYnce25FqwPn\nwGzs7pRGe9ecHhnDAaXdHJCMRblrXrP5g76QibGKo/NQIU2iQFlgmzaLF4vR\ndUtuUt/5YaIWFbUKqMu91tt4dlPHWg174N9MDMph0438NnYOT6kSUQ/LWnG2\nZtNFeV0g5TZW+uuUw0CarqUlZaYpsOO7gemC0QsK6AP7nQX6riR08sr/Aonq\npNsDXcFFGJMQsZGmwJgPZNi04nOHMq8HWmui397SyxdWkSzBtYHz10J3Rubq\nvUnjfKnkxOSmq0qPYYtCvf6GBo6MDqPs3hQI6LN+nNgx1+ONElmlShABhALD\njsNFKyECJjFmp7a7JboH3kekJyS4vph7wQ3YR1fS1jMhpzLIEyXHriwbFPXW\n5iKByy4EYO65mZKh30g0OW7nJ5nDeYmwpHQ3LAyOfkgOJGdf/CZkYTBWF97Z\n4j/AHWKI3rPXa8CN5VoifqSWvHs9I8WGp7P1dRMQBg241/UEF2suvxK64y3U\n25rEp7lVVc32YWC58Oau/WERbWjzzimIHKs3icwupfrdSAykrUDSbzv+t/Ju\n0qmlFjJVc5zUf8KLD48gLkdEQBtwH2npCYrWtuAF6FQ3PAz9gxtk+m0ZTzM/\nkDk5R5LdRl90buNwe7KxUI9eEEDtTFfweAnB+oUr+CFnqIvZCuXQeCyA5xGu\nllTC\r\n=XVJB\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"faa0d0495a47361f811f92e42091b315b4faceb5","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.0","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.111","@balena/jellyfish-metrics":"0.0.126","@balena/jellyfish-environment":"^2.2.38"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.11.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.4","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.35","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.66_1602283410556_0.8796996757779119","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.67-renovate-non-major-5f39d9242f194eab2ea5e96faa33035c0e1e8bf5":{"name":"@balena/jellyfish-core","version":"0.1.67-renovate-non-major-5f39d9242f194eab2ea5e96faa33035c0e1e8bf5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.67-renovate-non-major-5f39d9242f194eab2ea5e96faa33035c0e1e8bf5","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"556217a5b5511f96f60ffea423e6d578dc233bb6","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.67-renovate-non-major-5f39d9242f194eab2ea5e96faa33035c0e1e8bf5.tgz","fileCount":94,"integrity":"sha512-FtiydJh9H5qUCpBVeHxT455asgK3hNFW2+DSajRiuAzykT8BEiMoazDX8iv/rq9o2bU2ZZU53p75QHoRc7A+Ew==","signatures":[{"sig":"MEUCIQCasHwGYLx5FSicYJhFXnS9kKWmArIpIsmoB3DvgC45vwIgOXLtANymGWBNqSwkZCttFa3k9FqtlpjhMyZMu5ulDS8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":566889,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfgPcfCRA9TVsSAnZWagAAqK4P/2KOn2LhaF6XuvNohZOx\nCXAj5DuBujecD3YZWpS2JJCW79NyRIAf8fHHUiHoyyUcIVmL792AeFuryPey\nTZ6IDjtFNpg12ylLzFg1/Y/4rFqOCyDM1D4M3f4tmhmpCE5PNXVft3T3LeRf\nMSdySMV/n9KeMG7IH9P3gVGxfYjLH75vUZkBGnq7crRCLcNVXocFBtZQ+fup\nGpIldezwI36ZOAloymV9lo+Fdopi35m9sULwin0WWSo+D2mici2F/kv1QoZw\nwsGT6+fvhkBjkSlonezO0DPosa4o7ssd6NanNFh975chOvxmsuhX2WbtbsCa\nZXxsZ8Ap4enWwLpOvOxIozzjIedgUAWyuJKsQBepWsktb6aBeTS4J8X/FSim\nH50G4MEGtHEgTs2RVsBc8fL9AU0q1R3DoU4NrQ9C8H8fB1+QJTRn8LwtHYH9\nUNdbnq489/1BOPp7b1lAPh6Mb4omQ7lULXlaIrSAnC5LmJwKE9xANJCzomIe\no6TwVJw7U3ockvWBmJITmkQV2B65nzV2SJMT6qkkm3NLYcChdpeZqlSbC9Bf\nmtipbHkg+QYUL9IbfBOZD16QKlCwtVYMjdwSIhUGYraZtyOdX6g7DWBaCYDx\nXzfG8YloOLbZJzoyvVHAQHwhBNRfSveMFvlsEd8vImp/vk09arETT+TPgKRg\nQ/uE\r\n=DkHz\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"5f39d9242f194eab2ea5e96faa33035c0e1e8bf5","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.0","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.111","@balena/jellyfish-metrics":"0.0.127","@balena/jellyfish-environment":"^2.2.38"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.11.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.4","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.36","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.67-renovate-non-major-5f39d9242f194eab2ea5e96faa33035c0e1e8bf5_1602287390852_0.0926788025607641","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.67":{"name":"@balena/jellyfish-core","version":"0.1.67","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.67","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6dc48c03edebce2da7a555a1c0b6c607e81a3185","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.67.tgz","fileCount":94,"integrity":"sha512-cpgo0I0+pMU1rfD9BoNg6RCOibaK2pdBJ/ypWaOPs7PpWM1NcKsZameVkQGmC6B9G3zlDicH+n9h7lkYDX962w==","signatures":[{"sig":"MEQCIBT/qSI8Z0tULYU0EPgnwFSLxCdAXg06TPb05M9jhjSpAiBFxugJ5vtGUC1S2n2ff13NjbQqoWXAM1rgL+PA2sT4JA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":566829,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfgPhuCRA9TVsSAnZWagAA7sIP/3zlFSVx5t/7XLiUOtuj\ncFWyfxG9cDwtTpMU9uB1RHkpbQMdbL0DyTNFfqENToBC0sh4gAihkeNqSByd\nCpRq0O/0p+Y50eL0uHB9ln59LdNy34hKMsHlPm3Exl1oBAEcF6Bl9FwgcoHU\n+1CyZgOGqf3kgFSzkm1UPwR9rzs+uZhqKFkes63u21HlzjccCxDwJfphkVYB\nwNCtP+SDr5DBlOfZFcG5WLKSNAEzfhaEgkp1OUairEI2ioYiA1VL2e287tmC\ngaU0ceP5e/oXHbqO0pCvLrsmBZrvd9d0+vuDu1NQRlfkFvFGnVvd8nT1I9vm\nrNuKQIHtUO4SudHlWGuCYAZv/w5mnnNfy1jIaEppO3tnGsRStzt6cichEba2\nGy8FAJxaAb6UagS5f1zZdR6G6JUkXxuVdIenrssdtZ6B/G9CBtPaeApCKNRu\nreM9ykPLhUvCbo/M0wjwLHLhli4/2glCMW+wlR0JrgWScP3953zDFF9DW0qs\naFd7pCPBb+HUaFbo1GvF7Dijm9J7aC37ANuKvj3IH6eTueKgQ4ZB1ZpAhV9D\nm5c17jneJTVIzKG+Hcwt+mBoc38tXtRGyL9f5AZis7tLN0ybNkNuIbimdTir\nD/hLcXvcjJiTupsF2B2YqJWH1w5gzMFTxL3m0VNlI2toals4633tdRTA4hud\nup1D\r\n=wUtT\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"4a01f8ae075eb4caa92856c351c4627aab6680f2","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.0","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.111","@balena/jellyfish-metrics":"0.0.127","@balena/jellyfish-environment":"^2.2.38"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.11.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.4","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.36","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.67_1602287725636_0.4202847419631366","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.68-renovate-non-major-73cf6c6dc1dc7d2ca8583cdec0abf5e75890e692":{"name":"@balena/jellyfish-core","version":"0.1.68-renovate-non-major-73cf6c6dc1dc7d2ca8583cdec0abf5e75890e692","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.68-renovate-non-major-73cf6c6dc1dc7d2ca8583cdec0abf5e75890e692","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5c13ccc764c25eef8c53da4d811bfdc37cc51ec0","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.68-renovate-non-major-73cf6c6dc1dc7d2ca8583cdec0abf5e75890e692.tgz","fileCount":94,"integrity":"sha512-oBzx+CslJ/WLc7BoJkLdgU+RyjKApzqQhoOwnK/AZK0fITu5XSJ/0v6MApYhGbkXW4X1E6YP80kD4Yhs38w2bw==","signatures":[{"sig":"MEUCIDVQ30TIhLUn/d8eIl/tU7V6/MfJXXIfhQ0SqEY0w1nFAiEA7o08FujXU6won9LS7CnZCWqvIVus7zS+i7d37eB+8KY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":566993,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfhJeMCRA9TVsSAnZWagAAtuoP/j1odfhjUdb/HqkjFArC\n1OOznH7yNKBt6PYlwWWN6VYsGCHLlopQhF4+EGRk9kj0O+VYcXqosEbsJNMK\n4WamfBE06atqhOru/SyEDYpqHT8lIYF+eHTQLM1+Zjj3VEwkYKfYOjcC5z/J\n8pKdbIk6Iqe7PY9KySOe0pnWPQZRj6cFaTjQQoa5vcclcCWHPr5g1vKoETTq\nUbuDCs0vuM8tNvQjOtQO1xLb9jOhvsPYvksh8xf6t8Ti+ANK+Bo1wL5mCk2S\nCENXGKm0LoijzoBOgLdo9wpXBF8BV9i8grmROJ8Wjde5Y8kNufVCPVpTbWuO\neGavdFN9ANIVhcNh0qVBbfvsHzLCQdHMJDahaa5qE/Hmec1v5l8avTh9AMZR\ndL/zMRlV8bSPKimYWftnPQQQPBKbPWT5ZuEOnT10jJ5cJ3Ao+GADAS/bx9vF\nlbwXvaG7V+88sObd7MO2nYwUK2SqyAkZ8a7UGcyX381UMgiF2FEEO42Uo7SJ\nbAi/6rrWfbjrjEVC9FVbvnGa3zbRguNdfOrb79kX1y6dmdxABr4UxRbFW2Dp\ndwtkR4wz2isKTaUzKLnzpZ+yS29Wf6nU0s50Ybfoa5VobGhiJrklrac0/ZCD\nCdif2KhjJ31w49oFAdpN9CI+bzZtpTFC/IZAyL0XJCS4KfBsS7FpIG111TOW\nUKZI\r\n=jMK3\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"73cf6c6dc1dc7d2ca8583cdec0abf5e75890e692","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.0","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.112","@balena/jellyfish-metrics":"0.0.127","@balena/jellyfish-environment":"^2.2.38"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.11.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.4","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.36","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.68-renovate-non-major-73cf6c6dc1dc7d2ca8583cdec0abf5e75890e692_1602525067842_0.4294784102781293","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.68-renovate-non-major-daf6830d6f799edaae398c0b4e0c6c4e4d4c041f":{"name":"@balena/jellyfish-core","version":"0.1.68-renovate-non-major-daf6830d6f799edaae398c0b4e0c6c4e4d4c041f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.68-renovate-non-major-daf6830d6f799edaae398c0b4e0c6c4e4d4c041f","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5b46fc65fad723c6ddba9362744153ec61581b78","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.68-renovate-non-major-daf6830d6f799edaae398c0b4e0c6c4e4d4c041f.tgz","fileCount":94,"integrity":"sha512-asrmi9cNydplvkfgr2QpyUgszBQNpkYIjDDzJCw8tnngvduWW9aXTb/U/bDdBKegnBq8Nsa7YA74uf0wVDoNbQ==","signatures":[{"sig":"MEUCIQCxcktElVh8xyQIOGSklB5zLpCD8kbB5mrjoHTsYI8JswIgO7FwBTwzuKsQp308cXXJN2WlNK8mbEF5Khn0CkC+8Kk=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":566955,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfhKSHCRA9TVsSAnZWagAAzfcP/1bJe39nAZGy1u1qv6hi\n7Zg3eog976Ylit17fItdR8TByXyiTJHDkJ88ZiuL29ZCVRylx0/hhq1lQtw2\nG7FTPalbH96Z0AmV9nvGPpVYlurG1HdBXBimu0EZ2M1De25StH0AnMH8roY+\nsNDEFDPkAcSdzClQ+F6kmMS2KSw28kEMir3fG//9bMABpDjU0xNne67LwYCT\nIINdYAOoaoz8nTZ+rcDo5Emy5Y+M0uqAsYCw+fOIPYlCmYWLWznyEAfjd2bs\nRhGen05yCYs5Q7rpMXpwOVu9MkzVIbaXMAnWWkgsStJmhkOslIof2biKxkJc\njogId7ZQj5Kmzhp48tF/ywQ502ewfCvt2bzSCl63A7CUWaFEm4gyH5W9xp/J\nfbdZx7Nfb1QSqM51hhv8exEniscN7BylaSldVSeEKWHEz7TQxuWzY1srHN+K\n0wqHUjITX7ms6W5D6CdoDs5RTKa7JCFIQ2Qpz2XGcjdAXRRhTh6Q+C94Xjf6\n67+1k/LWySK9Q4rP3jqErMb59endDJ1rhk9lLoGXJx13KJ9skotsotyCbd55\nRmmvFRUVyii4T/BNwwfPVPX6sKHz/dTfpVqY9N0Gp7uoUECuYC+XVIHkqw5B\nXEwcdMO5vdlgTJI7cnMDMJlCR8sEvQ5gzBlP52VXLGD/3bM7oUQKVU5opJ+3\n3yPc\r\n=nIO8\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"daf6830d6f799edaae398c0b4e0c6c4e4d4c041f","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.0","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.112","@balena/jellyfish-metrics":"0.0.128","@balena/jellyfish-environment":"^2.2.38"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.11.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.4","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.36","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.68-renovate-non-major-daf6830d6f799edaae398c0b4e0c6c4e4d4c041f_1602528391298_0.5901539554148016","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.68":{"name":"@balena/jellyfish-core","version":"0.1.68","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.68","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a223a799c177e5add23ba5f3d1a77dff412d0835","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.68.tgz","fileCount":94,"integrity":"sha512-rFhUjzuEbk/I5JUVsp0R5n5A1T9xjs8N63RzGSBKnC15nzq33t9AK9kVxxNMKYmPlk4YraxqyB1mBW+J2BzHtA==","signatures":[{"sig":"MEUCIQDEZh8wXW6aLHMNNz2e1UVml1HM5HlV++bo8L+ysSQdmwIgMWmFnv57/Gx1xYFL5o1ebMkNPpuvMKdLciSy/MTD1Rw=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":566895,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfhKXTCRA9TVsSAnZWagAAwu4QAJEU6+SClQAkqiRZl42G\nsY4Np344/653RScZ5Oa3zJYxrfaCYSmLj4NQlP+FPMfCUKBisqYCeegzXWbU\n6EAfynEWq0hUXlC0j52eNpSiJHC0n4eKqdRXrk3E8Cg2gXrZME8e5AIjg8aD\ng4+YoNpOGHVxdxS+4NqIdRdhpZofAyZN2XmfGGfG8bE0AeVG00u25uwNkhXo\nqp0tQnFoeG3KrQSVRa8vSwQ/74eh+dAAsAPl7BSqWeGihlVqdl+Niv20qqfZ\nLH6HV0GIAIRgQjp+9NtoAfn1stlLVEY1Q/ZJvgTxuBzM/qAdsuWxVJshgBcA\nEqdVBcZETOxsJokWOqYHuNjzgk/+e+vIODZnJbXdowAy4Mhq/Mv9ZC/cxrVM\nezPojBIYbvZg3JMtz1czNPjarBaPJSePYvneJfcQQfcQ5YTLK0D2YCm3Y/gL\nQLEPOsO2gVhnxC7RhWtf/zJqdYnI2zxM5pwPPCPC3zxcuALjk053vUTfYv5B\n/xmwtAJQ2rLw4ERgHEPNqiBDloRh4gRY33jN8qEZdQkyCJo+1sTwTDyn7PTv\n45bBBZIuoARBhJjEcYXGWGJDCC57KQuLiAad7PC1JcLAPomynqp5hpWiAfUs\nZ7DxBbMeQ6trYOOQsA9E6YvcItdn6j+bn/EYsYzOB8MZxS99fkL7frxzOC4i\nudZ5\r\n=vjZW\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"040dd50738b9e6fa59ffcc2afb10185cf764db65","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.0","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.112","@balena/jellyfish-metrics":"0.0.128","@balena/jellyfish-environment":"^2.2.38"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.11.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.4","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.36","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.68_1602528722846_0.23527075915019724","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.69-renovate-non-major-05ac715ee9813a6051071e744087bc453b8e2d0a":{"name":"@balena/jellyfish-core","version":"0.1.69-renovate-non-major-05ac715ee9813a6051071e744087bc453b8e2d0a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.69-renovate-non-major-05ac715ee9813a6051071e744087bc453b8e2d0a","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e1f61e4e753d040c9c4265117d0cbd0f7753b8e2","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.69-renovate-non-major-05ac715ee9813a6051071e744087bc453b8e2d0a.tgz","fileCount":94,"integrity":"sha512-c9t+XW5KA3G7WESBwJcfqWlThaH+JinmB2sqe32aBom2YqBihu6zZPy8pRtLigAo6joynX2vfK1gTLAbKOe5TQ==","signatures":[{"sig":"MEYCIQCicwbLMvxgbnvWloghfGKT6/GT7iGKuRKmLh1TGvw8SwIhAOYqMSb4evqX+PUpvXL/SOC/fyVbIomXXs2nHcZz0rGb","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":567053,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfhkjdCRA9TVsSAnZWagAActAP/iPUb78LNOU+DLZZCMzo\nFAo7HUT4tY7j5qLGekNn3vaZ34BFYlf9/KG8TbMyKcIWQr0BEtBy7fAWf6Y+\nH+07DDl8P+s0tZ106sG67Y7Dx9edrqCEe2nLCWD/i/DnhvNhAGFEcXDO7Gzj\nMmSNQZhFxq68WNSb0UPmSNjE+jvemy1c2SQyp2li6ceKD+NZwURRRg03UrhW\nWkmDC3Coi+2lY+bzi6o1pFcI5ymfiZaVQUxvKokp95IusznWg4EyPMxVdqzF\nrWwNxbo+sRMGhe1OaA5/WdMO6KT2sH0WjIMyTKjixBN1340H7inr+ipFgTD3\nqY7WNIrBEKKulTsMUObmEwdj6ECU9EkL8CCa8u9mrTkegTWmSejtLc4WJeQf\nPniHQtdsUxvYuAUmktchDSf/w94jMu/rTkcI1zVCs4a+rn1p518zHQkxrZVl\n5fNn9RreEg4MGvkUVNfoYQq/Ck2UzxOlsvNu8LzIJfiuSl5DZdjFenLMIcrs\nIayMM1scSA65QNYMWCPpu3jBArldj/4luc/tMTQU/jsI8QXjObOAlHwUyvjv\nBcdWaFo1/JT9ivplWZCkwFI6GKAo/JifVOgazGSjBh8QC+8duxaNR8mAvqBK\nGy2Jwj91dcOn6XEp2EWSKxtZCCxL3TDizQsBS54dAK6wOxqWTO4AuhNnskNG\nZWHs\r\n=N4eD\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"05ac715ee9813a6051071e744087bc453b8e2d0a","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.0","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.112","@balena/jellyfish-metrics":"0.0.128","@balena/jellyfish-environment":"^2.2.38"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.11.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.5","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.36","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.69-renovate-non-major-05ac715ee9813a6051071e744087bc453b8e2d0a_1602635997470_0.33025541395238944","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.69-renovate-non-major-d7677bbd9e2bce8330dd5242a95faf5fc6b2741d":{"name":"@balena/jellyfish-core","version":"0.1.69-renovate-non-major-d7677bbd9e2bce8330dd5242a95faf5fc6b2741d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.69-renovate-non-major-d7677bbd9e2bce8330dd5242a95faf5fc6b2741d","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"262b9207b4d625d4cbc15fddd4f7521536cb3e94","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.69-renovate-non-major-d7677bbd9e2bce8330dd5242a95faf5fc6b2741d.tgz","fileCount":94,"integrity":"sha512-LFzEqaVuJVSBGfYUw+/wdruHeWkN/QJ8ysyVyTooNvXzthHQmKzQa1FQk+tKL0DtDsbGEZACbW+6W5YvEFLiqw==","signatures":[{"sig":"MEYCIQCJifc/OLZ1vhIe8jp6/D3VjIb9urXbO3lopv2KOdhMLQIhAMDUHR6nDtkPMCqjdBMyAF6XrelvH471ukIgaS4QDG9E","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":567133,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfhksuCRA9TVsSAnZWagAAGgMQAIbeqd0d6NfrA6vcw2Mu\nF219TpzYFsMPZoIAdCP4ld4Ah3XjSrIDV7xJCC1y1z5dRBWJF86YLLrh/zAe\nQjO4JSuS8iyUCQ7PayycVfs6RYF+IZ3PDzZ6PP4HFy8/zatlrE9UADOlYdaG\nne0nK5ZWJGLEJkrqeAt+k9yBDaAGB2mMiyC92jJZDgQX4fJBBpw0ofBRGCaT\ngT3nzjz2f4KqbUIdQXnPZ7iSDSzxlsPssd0Gk+t5MtumrJQuwMTLoZr+p5Un\nHb2zkbn4fhPh3/xY2jVRuxuAQx2d540NqyyiD4YQbj0XX7/ofovQkwiPznth\n8n666rDeO0wIxY2nIJUpOOHxIkbV1BSN4MXlwbd8jcZM0gi0lIkOQEQ+0rfd\nEyUjF7a2a9gIWAjTSq62BNFPHPyXhH4qzSnBd+odEmjaixoxG+GtdTPUv2sv\n5WIpZTHvfT2z3rvTyofUySe+bULAn2JALThUVZ6M7RH4LUrxNEgoYsiCF3/P\nz8R9E6ot4NyAQ7UgAFjtdim2NlaMVB/oFtsCti8ulDwx+AqizqVtYuVEB0Ou\nQhUIaylVJ6rGGPhj8wTxt5cE7/yhdC4F4+Db7nXMYVU4fj/m+xTrkxGd7raR\nU5EG8GhXj7Knvapb3Vz9HvBPciKIiiTP8WOw+ni//WLEJTnY3IZT+HbOngSv\nMr/z\r\n=prul\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"d7677bbd9e2bce8330dd5242a95faf5fc6b2741d","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.0","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.112","@balena/jellyfish-metrics":"0.0.129","@balena/jellyfish-environment":"^2.2.38"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.11.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.5","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.36","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.69-renovate-non-major-d7677bbd9e2bce8330dd5242a95faf5fc6b2741d_1602636589546_0.7731991951196986","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.69-renovate-non-major-ab5c220bd164607a6ed61b2b6acdba33bfb6fe30":{"name":"@balena/jellyfish-core","version":"0.1.69-renovate-non-major-ab5c220bd164607a6ed61b2b6acdba33bfb6fe30","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.69-renovate-non-major-ab5c220bd164607a6ed61b2b6acdba33bfb6fe30","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9984bc9b9cfc786bbea954da0cd658824306d14c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.69-renovate-non-major-ab5c220bd164607a6ed61b2b6acdba33bfb6fe30.tgz","fileCount":94,"integrity":"sha512-3VHgyxfpAX+cxwib9Yh7DMlWHAgaam+QVGONehBZH781K3GviXqrGxJzxa5Y48q/RSKKY9j6AC0SjKscPIL9JA==","signatures":[{"sig":"MEUCIHwqNgGTdaUjPYaXjEMpMCQv0b8/k25U4/LUr6jQ8GN+AiEA+gJLZnCFPe3/WKpFdCOOkVBVhEM/zT8JN/BbZQJQZ+o=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":567094,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfhkykCRA9TVsSAnZWagAAJBMP+QAjEMG65Cb+79s9FM/D\nDjqh6Z+vXusgGjlXFydmTrHndwzU3o3ieVgtF8Om1gTxEVTwvgyP0qbRzN7r\nyYegU0yB0rTy0n6K62JREkr+0NKFoKGD0rnDINwQMD6OZQ8AsE2Ywy7TseNM\nvQC/zH7bvaNd0pemyTzhXpesCb3k/FdLWlPxz7yUXg9o8O0qG2/w68uqwiua\nv3h291jJNvvG6311Tl3wsyGgR/QdmoqvHgEksqIsezNWPO0LBOZKJrsjCRtb\n1WZtTVU5zlA9YzDUdbWDgGD7h3yVVI8cG4rdUBAduYW8YarllQuqpzaR3aaZ\nMYiEZEH8SzRBxkvuvv5CRpNjLapSpQQA4Fl/3iXuYVE9juQ+uqmYqOkN2iQ4\n0N/vUfOiCw7z9HVi1AedGHvgs/cuz0wIROm3AHxI24+ol6SVKEtegsPktajI\nQhedfGnEn83bpvnxhJ2CSHcdQjbHfzVXHIs0lktnUOmGKHASeF0h8CPOTMfB\nkEaKXcTYnoG78h+WxIXxB1fUCGcdi79fND3FZXctOJvCqrf2pUnaA3gB2HO3\nWx/en1XjWN11n6CuVb61iizJF2EUj6iuwJO+xU87zmbspvq8zoZBnneYPWB+\ng5dZUkhjT58gs6G6Mlz2F/RPgePTk5n0P4s8DvQ2gsMLa5KtqEtaUFW8uzHD\n0US8\r\n=WRnr\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"ab5c220bd164607a6ed61b2b6acdba33bfb6fe30","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.0","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.113","@balena/jellyfish-metrics":"0.0.129","@balena/jellyfish-environment":"^2.2.39"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.11.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.5","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.36","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.69-renovate-non-major-ab5c220bd164607a6ed61b2b6acdba33bfb6fe30_1602636963772_0.151714293545951","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.69":{"name":"@balena/jellyfish-core","version":"0.1.69","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.69","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"59edac3547d776f5461ff720efa76b8d334d2f2b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.69.tgz","fileCount":94,"integrity":"sha512-JUk9I/a5y0L/aC6xznrc4GFk2tSvFL+9FpQfMkllcFq2lRUTzZGtsjkXc62Tu1jcRdvawHpFHrVWQdOQnMi8cw==","signatures":[{"sig":"MEUCIQCdsBJLjluJ3hingrmmUTttoZdv7FIgZ3Mj0LS5ZI6uTgIgEAsYGhCf27qWSRICbtl1E/UBWG0USR1aO/FD91RsjxU=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":567034,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfhk4NCRA9TVsSAnZWagAAXwEP/202TqtlFLMtToKcS7YA\nFoqNJ8WGMA9SYPoejA4h7RhORS3k97mi02jblAyk46NuBorZz4NwMeLywVxb\nrfP6sRgUMhyNsk5NOIVDkGyjTq2smqAT+O96+Lnmq9QSYZ2OuXHYgRgAEI+8\nTWtcb9k+3kVfBTmuhc+r3QmHp/IxqLxYt4HHxUtvdbNTz/8MnVGB+Dq8mQT/\ntWyMlqzuHYAtG/tjFZ3DlKIWJ5UMlpsjXj4yeHl9fxyc/fgmnHzpm+qBYmU9\nVtXA3hcUk9VOLCdr4HFAY2rXiflMNMZWPYDwOsibnXOEqSiTjnWKqNw08Mle\ndJV59Tns0m+xBQGNlHxCuALf34w9VROUwleVGMzwSiqN/4o7RWRjeoN74fAC\nacPVFaYvVSaHN1k63eSazQ+VQe/5eKBeCSoT0dvSv9Q4PFoFt7LwfIhG7bu8\nfZvfCF4FePoU79GhwyHFiXDLda81X5JJrYMVXGjzCFFv4/KZAgNn1lqbNhYG\nrVhI2JasdiqaKGCpNTt6Uvtn+VKccP/TMycTfDElhOtLtifktL9Jjb4KjoR1\noBu1qE9PfAePqcfWpiIPmGZMkN9I4nmBs315eO4CMF5RGtXQEBz3dJCYzHbF\np/CGx3KGSkXdauUCpt3McEIlgPwfc04R1SdoyXFLD9wa+OOjRe0IKfCvntMq\ngSSC\r\n=NIQ5\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"1acb103b79cf96c264a250d5349bc7a91de67e6d","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.0","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.113","@balena/jellyfish-metrics":"0.0.129","@balena/jellyfish-environment":"^2.2.39"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.11.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.5","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.36","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.69_1602637324558_0.8062250320297497","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.70-renovate-non-major-ec027072b732c206708589e05c19eb595b8a70a1":{"name":"@balena/jellyfish-core","version":"0.1.70-renovate-non-major-ec027072b732c206708589e05c19eb595b8a70a1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.70-renovate-non-major-ec027072b732c206708589e05c19eb595b8a70a1","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1886d3a82a2978ee8eb1e5816a90220e7ae74f44","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.70-renovate-non-major-ec027072b732c206708589e05c19eb595b8a70a1.tgz","fileCount":94,"integrity":"sha512-u/Ro2t7doUBSNKaSh+5tcWfFfISerDEkDPJBOVnsh7avaedfO+cTKFzYHzH7piKykZjgy928kXlJaeJFyAfBsw==","signatures":[{"sig":"MEUCIH92uhOzvHVJKgs7qN+whCrqsh/pnhD7DTALnsoLun8qAiEAubgSOistNrma1qFlC81vLZCwgfv/tuacJUNK/KiXYdo=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":567198,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfhk7SCRA9TVsSAnZWagAA8E0P/2czNEsdrFAFCrHIg5HH\n53SP3E+AiDd5Wgshvdqw+pc6IcBXSY4CfaNkdvaRNY57iX3xAPkCA/Eeq1lM\nzJJpkaxL5dlrez4E8ekCsRRzk5sUmnAjNWhp4ftkyObcSSdo17eGlwj29zd/\n/xTpwG/GAI2GnFARHzzmZYinlzAJT/m0IkozP3c9zSttAgtNeX3a/pxV15DD\nUZxIV+JJrfk5lhuVA40sET05alWU3tgJp8ISyroAoYu057ihz18YbvoddvaL\nW10YgUL5KbEtVuoSedH9PvkbmEebc5gw1Our8OEEZjs6yr1C3OXX7wuUb6H0\n/UF/XLPEKWtsmTfATi9jKWGl4ouJZpnKG/rFj2n3UIqY6+Qp7qrFe/mxLVAz\n9zYg6bhO7N0rzfK7e5EGHOKN5C3WalhkAA+gj0m5yfIcOm43Q/kuLq9pi4pk\n/krE8lSLbfXgYf0FQRkefMJ/Pp97c+AKqTZk4yijgvhTjZYMdRyUyuTvJzk7\n0hmrKrTMtd+PbF9b5tOQuxfCkqhxgP6krkwwLCSEo/WxX1hFuf9YdqbPOKcD\nCQxPIR8JGPu0Q0U4XOHhLC9f4L/e3zR01t34k5BO51k/4d1nobX0jt2r97uK\nw7XAdZxAjn06A9k+cnGpb7jf0Y9FoXJ+pQ+e+IDu0lMloTYgbhNKIvwoxIG8\nIE9j\r\n=sLt6\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"ec027072b732c206708589e05c19eb595b8a70a1","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.0","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.114","@balena/jellyfish-metrics":"0.0.129","@balena/jellyfish-environment":"^2.2.39"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.11.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.5","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.36","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.70-renovate-non-major-ec027072b732c206708589e05c19eb595b8a70a1_1602637522002_0.9568505292279625","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.70":{"name":"@balena/jellyfish-core","version":"0.1.70","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.70","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5a158bccc6433bdbc643e3d4336d0ff87ec679d6","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.70.tgz","fileCount":94,"integrity":"sha512-306q56xVtChcnbgOnGgOW1zrj+Qr4pzCq8ueTZLwLyvLjKoNjnF+AJvO5gf7v2RYzZUnbPSHnf1unAJLKw90VQ==","signatures":[{"sig":"MEQCIBXjEVdb1LpR6D3ZGJKhjjb0vwxKFR0uUohzDpzybqizAiAY+PoXnKrLsh80omUm9z0YU82q3fek1jI/SDqf8NS63g==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":567138,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfhlCbCRA9TVsSAnZWagAA8K0QAITuu2sUYPgR/zdn2aBm\nL+9jFaNVVYbxqSrdpSxM/4Sc6lVHI2OgHxFCZONWbHrEIuWZeYBf3lMRbSEy\nSJ0TQJiLyoeGFn8rdKFYzbvvOq8I6MH7tBjxCN/iW6dKEfYaHHKglHAdlc87\nv+GpJ3Fgv6NqKuwkrsnD7TZc1vQa7rJaEFhATzZVG2WO7xdT/as9l2uXrJzv\nV3nzegIobQPDBCzOEtB+NeKmu1GJWX0Aartp3tXUtujvrAYNY7N4NiKoy6ev\nK/Sgg55G3JkmS6GAEd0aiL5wid2y2zRwpfU52tXJ56+ts01ZzLLYeW5cgQ/T\nY3qOD1m1jSEAe034Fcfk7VZGj+u6VuOCIm7hL7xcN8Ypwoeaty1OAmMh7vaZ\nssunjeMCYZnK2Ffcn2qEluLjvNxLg5gu9/QPxKlXklG8S+smQ5d8bXrAydV8\nhGyeOPM4RNAaZKWu6kfrrliR8rxtnIDIJHqcwLnn19M1YJ0Ew4ThY2mfKk4F\nWKhHi+9PpSkLaIxMJidvi8Sgi1ZtMctAfo04d0Qe6ValE8vWZ6dGY5nZfkQ6\n8xpNzQRzD1OtIpLJiFxEtLIIAozF1YPT6Mi1NNZjixHyiQW/OP9T3qHIK9zp\nVDGkGPRIRwL1ZWP35kQEpiUV/v14LAhBqX0RPVwaet9XEFU0MvMTu6tyDb5G\n7GwY\r\n=jvjT\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"678c385b35a337bd899b3eb52fc1ca5ef8a6f261","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.0","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.114","@balena/jellyfish-metrics":"0.0.129","@balena/jellyfish-environment":"^2.2.39"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.11.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.5","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.36","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.70_1602637979102_0.6341964940971971","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.71-renovate-non-major-b8fb77d28618cb72368d4bd31e9941c30e712ac1":{"name":"@balena/jellyfish-core","version":"0.1.71-renovate-non-major-b8fb77d28618cb72368d4bd31e9941c30e712ac1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.71-renovate-non-major-b8fb77d28618cb72368d4bd31e9941c30e712ac1","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3c8e2177f61f3c1641793cd5679c6d4f562391b1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.71-renovate-non-major-b8fb77d28618cb72368d4bd31e9941c30e712ac1.tgz","fileCount":94,"integrity":"sha512-B1VX6FLFTB2ASXcOvMREnGrLt2Ya4H3DtvvlJ8Vll6gvGE3UUJAx9s7YcgvpBjqKD7nyLxqXUiUch7P6slgk/A==","signatures":[{"sig":"MEUCIQCNBkOmGhj81QYVLA4hEbxWvlj5oKib/Mn2TattqLcgMgIgLztu/yEV/8CE1c91qH7rH5jWpM31jGYS/6w0hwS69uk=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":567303,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfhmP5CRA9TVsSAnZWagAAbPEP/RARAy6eYQEOur60z7RE\n4LNgylmQ2F3GWtPc46gWs0XjQEt4w6M/ntpgGxDzietS5StpRUtoDarxFW6R\n+TVobkL8LbSulRMWjGbG0vNS+KiiwArT3mPDSwfQeH/sNMGLFifLb9yyUHtO\nSXlWr33zX8SmyrsHNoKx6usqTOvGvtOAAqg09qrpr8sQmqBOfZQA8ntojbf1\nuUUwJCb/ZlBIC/xJ2pXc9263GKc1uRg39VUgtF24kvfbYooE8xvR+fTSmbLz\nFVadxoCTUbRd73hjXp74Ho2gwWzcXRtrycKVkV3dWq37QE+S8ZV2MCOWFoPb\nRlgWjsXSrig17SeV56lFww0g9t6H/iIO18Bevop8CkVhACjWc0KjmpQfAheS\nHqPwCOEbl+o0u8ezK1sGAt+trqhQ+pRlppQe7UM9Ri7DUIwsG8TjF97K02cP\nkMlmacW+fB70qLgbq7hGXJt3v7FLF9ebNojc+qZPKecUt1YucZNA3ft4Hexh\nFbjIVfRYE95T0pNgWNt8zFchm8riJ50ztQGimn8Tl2x7nfrb1negferN9Q7A\ndY91/og73bwW6OqsrdoBhc7wn7bDspAr8nwJhT1cTkJhumv29WeL+ESyqTmO\nDKIwnKxjhlzm7Pep4t24GQc5SIbXcOqjKBUuCvMy5XXbmL4dv0omfrcWiXV2\nTXD0\r\n=fek6\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"b8fb77d28618cb72368d4bd31e9941c30e712ac1","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.0","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.114","@balena/jellyfish-metrics":"0.0.130","@balena/jellyfish-environment":"^2.2.39"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.11.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.5","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.36","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.71-renovate-non-major-b8fb77d28618cb72368d4bd31e9941c30e712ac1_1602642936627_0.2543244980457622","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.71":{"name":"@balena/jellyfish-core","version":"0.1.71","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.71","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"49581c72463924eb34be42c1cf0e3cf715e1d741","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.71.tgz","fileCount":94,"integrity":"sha512-w5XkOMRoktDubZ79YeTy3Db61yY6nHbaYG64UZsYt9zpW3TXUHYt1SXDU0CIy838y/qYvbM2JxyOGEmyJA0+6w==","signatures":[{"sig":"MEUCIAURXuoxPLdzMBN/O0UFWtsqYG3fdeZWOSezPh7JlDAvAiEAikX0pG9WtVQDO0lAQlo8WsvXSLuhNFQG03mclgS9MsM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":567243,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfhmXbCRA9TVsSAnZWagAAQiwP/jnNCoglChHkmGP1CCu1\nujCpC9jzQDyuR/2ZNJGLCidw8B+eaEV1umuxadIJNvC9iK9fK8GfTAOBa83O\nCy2OKNnWmF506LAjZN47foa9uZMoFH+ONbV5AskFjhRoJSEUqX4QBxDYPQgg\n4AHmf0KWRCiILYUjkulvS6O070RlDCbw0rUCpZyLd5TyRmmhgDpsieYfXx5y\nJqDZA5wor2P1wApsy0FP4E0ML3EgTXv07g3R6qblxBQLiEe6PeTsdxL/htjJ\n6nktd6Ae1uEXPq4jM3+/CYJOM8Bj4Uji7nbKjz+9Djza5YncOxqN66vcn5rw\nhNp0bU3M1VrcWTLx+fU5fsqujhFOvN8XG9hjgJvB8GH8UxgKy+1RBRnN8/wS\nqQD4e+sRpqUtyViYMx9geZX7QbUyt81JL0OIZBVZtcH0mUm9QJ7fNCEJKuEl\nwK+29HZAbpHXhDd4HUX0CgDF2rCdneFmKy7t7uGVFj9SR2beoCwwp7yOzzwq\n/F4Hv/CeS3Lqc3JS48UV2MTIVSs1oZRf+R5ZiXVdBhbG8Kh2pl/okUUtixKO\nG0V5OY4VzY4IcouVnQZOmmoLwG79enrvymZ2/KenrxfNVXEVaN2QV4swndIy\nPSKIrVeyj1AX5kgLAY4yG+ZYTrlhsfANRFWBBByXsljsYyItjAaxl6W6x4pK\nXe8O\r\n=VdNa\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"21d933e666149bbbcc08a50fab90f5574b9dea80","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.0","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.114","@balena/jellyfish-metrics":"0.0.130","@balena/jellyfish-environment":"^2.2.39"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.11.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.5","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.36","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.71_1602643419116_0.7988353886207489","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.72-snippet-ui-schema-395df86551c95026caedd374443dcf11ab58a269":{"name":"@balena/jellyfish-core","version":"0.1.72-snippet-ui-schema-395df86551c95026caedd374443dcf11ab58a269","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.72-snippet-ui-schema-395df86551c95026caedd374443dcf11ab58a269","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1dd4e78449f5b9ba5a4ae0137f1dd12a9e344172","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.72-snippet-ui-schema-395df86551c95026caedd374443dcf11ab58a269.tgz","fileCount":94,"integrity":"sha512-gPA7oaM6bdGAeXxZWpnWsLqwkQmjyekV7/Sw0vv5Fz1C717E+A70riQQq0EqsBccPfFPUBBCKix5aOk2+cuvJw==","signatures":[{"sig":"MEUCIQCXI/bhH2J7i9SpTCfY4Q1KJ9IpyctwGMQ9Nu3Dc6lEaQIgUcCi0DZdLaxZ8VTDIypci+jiya99B5copXU9UjOT1iA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":567373,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfiPwmCRA9TVsSAnZWagAAuBgP/2EC9k2fHERWbpg5r0Vr\nriR2mza9BWEVCUf5RLi4woWbRTIJ8ZLsiB1NnVameeYii4YjuLFWc8jzRDdK\n/BpuGPxJTq5yiqKFDDoxMCcdh2BBxx3gQXLlnvKZJlL9Vfxj34nwYk8eX39g\nJwKQxb5ZQulDo5S+II7nkAe/UBUxvojh2/f9nJy4/g1kk/xkpow3abbEHZGX\nCBWytSuZFc/wZXfY8XZYY/jf1Vmh+HcKIPtdmhNCEGc4bXfekIrWbzVGzApc\nrB3bFpRKaBOcPVkVTMR9c2FDWiaJUkRzwrobjFnpOaHgLRvsdCWul0RwBhvp\nrlEq9ykfomjzuzy37c9JTBmNy/MX+V6ACzNFYh0Ey0YShJMi3WwNASs7QHPm\nc4cu1/aJsPjMrzUcjXJUSnXxvq5vppREuAIa6N0t8YCR6cx1mrgCSnEXfrx5\nJbuHSlBKO3TSeJvXk4Qm4XHGmOJN6tvGwknx9Abm6/qxIoh3/SvT56jiDOga\nmGXM17ywFKYKvMGZ1uaSaH1ZmIy1JAaF7lRMvWPWeyM5H3/Tds1H5UO4YF0q\nX4w38iaNyv/n1TRUOAli72mNeCGRdVJ7s1/VH51HnmtRgBMDopy87VhapK/m\nkTn1QlIgYs1bsJ1elhUvGQq5yaDQGXAtbUK6Wzt+/XY/NSB90gRCr9edWsfs\nLjFA\r\n=jIYa\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"395df86551c95026caedd374443dcf11ab58a269","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.0","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.114","@balena/jellyfish-metrics":"0.0.130","@balena/jellyfish-environment":"^2.2.39"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.11.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.5","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.36","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.72-snippet-ui-schema-395df86551c95026caedd374443dcf11ab58a269_1602812965892_0.5111036608563182","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.72":{"name":"@balena/jellyfish-core","version":"0.1.72","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.72","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2b13ee11aafd175d0c9fa67266a86f5ca26bb740","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.72.tgz","fileCount":94,"integrity":"sha512-0cAzzvoQ8ioNg3SQNNE1cJxYNRkdCNwgy5pGqp1yan/UgLbEH7HGIDY0WXWE8Rqh2ajBo8eMeyFCO9xQFBNhlQ==","signatures":[{"sig":"MEUCIQCOPvJfPPxq5FMSN261VVldgLdVEf67i0iflAOvmSSWpQIgSxjshxFTGexxJWWDPfWjBOHgaM2tjYml61jGOwOMhQ0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":567314,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfiP4BCRA9TVsSAnZWagAAY/EP/3IEfoBUJsG2uPO/lZm7\n9DJE/TtP6KE0SUIxfihkJowhYISm63344MlMVXRU/452vT9Zc0bQtiwpdJKy\n7m1T+ld6T57zvF9suiSIMAFFVn8IJNEnWoeRkrHkDiRGe9oT4YvR/QJu0lMe\noOINu1fi2AH+kP4ZbozBYoog7BQ83nG0WrQ5i/sZjX6/9IjBiDCHdad4aLGU\nxsYTFMOCu5IEG6JPsqO+2+LjfErhxCGwJIFYLdPl5uF58aKLxiJNjhbbXNhE\na7zzl3fZh1BOUkukAwfjFrVDZLjeV+W+8Jf5viV/kd8jZaJ+nMeQTXKgq7if\nxOFBNOiCnYS3k0A7Fbrb1/1zicZFHhZsEuAJo1LC0UveryWmiG1tu63XtdGo\nHVO3k5FD6q8MSGIN9/mbczK25k1X8ZE9J6y72zGZ6rwkPwD58gnw0GuNZBWt\nwrQYNIjiGRcqD3PUr+hX+auqQ/diIJt1zZXIFjZ9Ct1fFi1fvDT16bnSuYJL\ns+WueWMKVCDZHF3oZa0DLX4MPTjykCVRt53F+ZGuuD6YgvaADYgVU9uIRiev\n/ngyFBMZRZCU4oI6fh8MTrTXJqF6Umd0KtypDugN1BXlOGFuhxzeN6Kp3kBA\n0mb4/IZ7s1S0Leiahw05sMIEvWQfbkI+CUyNz0NjaJ3vJ1KRk2+zuAvVyHCU\nl58s\r\n=h1lV\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"8e0a18fe4f6795ddac0574fba2a70ac4544b07d8","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.0","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.114","@balena/jellyfish-metrics":"0.0.130","@balena/jellyfish-environment":"^2.2.39"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.11.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.5","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.36","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.72_1602813440960_0.8494223455207794","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.73-remove-default-snippet-fcc9da54f17e22b9ca739acedb6faf664677cf04":{"name":"@balena/jellyfish-core","version":"0.1.73-remove-default-snippet-fcc9da54f17e22b9ca739acedb6faf664677cf04","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.73-remove-default-snippet-fcc9da54f17e22b9ca739acedb6faf664677cf04","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3909d23b729560d09fdea352d3cc9091aa88d293","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.73-remove-default-snippet-fcc9da54f17e22b9ca739acedb6faf664677cf04.tgz","fileCount":94,"integrity":"sha512-E+5Hfxf4yjBjWxjS0fwLVFlok+bToYJxoDl7GXm257SqZCbsfZbNwOqtMA+xNbFtkdQiwrGSH8TaWA5aKJZiQQ==","signatures":[{"sig":"MEUCIEu8qOfPqZ+r+Z+2ZQhYo6YXVvJgVffZ2NX3LZO3be7kAiEAzoysmdJwRMPaz8oks4FivoctPwUIObnxWpMkmzNpXO4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":567425,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfiRHCCRA9TVsSAnZWagAA7aEP/3wXyLYEvVv5JHFvkcr6\noh53u2XZHakxHUEPNLK2fC7Lq4vKWOBf0eMV9jtjpg4vanGG8NtuGI5cBEOv\nPD9E8XUih6pGk7jDOSiWZ9pECsoD9oOqpaJshWF/CWQ18Z2Icbhyn1DLQJRL\nJtxYOFJcGTnr8GZetmm+eweMV17zsko4q9jho3T9Olj9bYROldD/S6V2y5Fi\neqxNo/pSX+EaK77Fo8roe1WM8PI4Mt7lfqBJhawUAlYx+Q/juESD+Q1z9Uzn\n1wLpTk5OcX/gP1Zljlrcc4+aWOn1GqiG+1onCwWcKGBhqN7zIUGjijQSgI1i\neHEG+HLOIQqfmdOh42qGZkSRjt/Z64iiybDPiPFxlm6seM+7545aDKJv8Vuj\nV9YDBT9dRxmynreMkJxSa46NLgrztjgeVNbL8kdm1K75gtJyMmcyIoDXqwxj\nLJ/lcxIwmjgJP6uIzVvLGygIHtlcnpoJAciZoF8idRYglv/vlc4fZXPxUqdX\n1/dDaADfR6b7KcfgVexscdu29TQMDAzEm44L63Rt6sSf5sFerYITLBiNKcse\nWxVAA6R1lLrZeWDhn/6g6EzJYlgGQFFuKB4VmMj6rsvLJMJHl/Qc7UOpM6Sf\nhR30HNyHPGpoJtMflYFkhWED0EKaTBqw8OaIedAKTkJIjqIe15Z6mV767qA5\nZ3v5\r\n=l4tZ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"fcc9da54f17e22b9ca739acedb6faf664677cf04","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.0","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.114","@balena/jellyfish-metrics":"0.0.130","@balena/jellyfish-environment":"^2.2.39"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.11.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.5","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.36","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.73-remove-default-snippet-fcc9da54f17e22b9ca739acedb6faf664677cf04_1602818497665_0.2532079172673043","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.73":{"name":"@balena/jellyfish-core","version":"0.1.73","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.73","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"18a771dcf8d467b65e680b4e31e7e238abe6e21b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.73.tgz","fileCount":94,"integrity":"sha512-w/uIOrTf67BSFZ9g0aciSb/0quufeCSv5oxQqPkUYtJmUe3KcjuHBrdSnnO1faLlsLf1WeNudQrHCDubpj2HLA==","signatures":[{"sig":"MEQCIDp8MOMhA3eOot7hlvWZ0w3FIwYp4G7PqBJCk8PEUu4dAiBogzlLIsAJAYpdQiITK0AH1DEOQQzPTNIbDSmniB6GaQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":567361,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfiRQSCRA9TVsSAnZWagAAbGIP/0+5OLsrixJ6RZCWCS+x\nJRKBECTLrhCEOOalmHpR7cXtwFA862QQHkhH6Acpjp+MeZ0h4RpJJonJExRo\nJhrgx6i1APFWMEyjGee32MjR3N0H7bIhLfcb2iNVC/JvbBwU9kXnxBnLtv5m\naO5nQVY1kSQ1prIFtYg+plGal+PIwempCibuQDVOo4V2HoxwKPB8PGuaA/+P\nnNi8yJytfVocB5aD+p6NGVyTWFID7qJsTQtv98o/HGIjdxwacC6K9Xgshb3J\nJ0thgzz+Pj2kcRZ5MHXt3qOoTYyTMGNFLLwsq+npHkTDOFbHHStHF9fY7ewc\n77UOR5+BsYFyy7Dup0dcZsyJ6VNxfEzZel9DiqT9HhB7Eui1W10vI3J5d7i3\nD5iaf1aXkUsBSkrXUDnxuXYDpLNIUzVW15NlxWL/+8EPtYGwvC2GHWqJSDZ+\nJTyP0ybQqAND3hpF8ALIDIaApglkX5+ztF1aHMFIIGDAHBU50NepI/+jp5mU\nzb2i+/upgjgJEbPgtIdc/wmsaN1lkQ+2U4cFp3vaa0qwNjlwyo1dxgnDkNrx\nxjHxHG9IIjvrUxRlM8haYb0hgpS6ZIYWewWYnYUWOok8Y2XAW2fRB/uM5yE6\n1P3Qx1WyHCCeQNHwrTNz+vFXj5kvhB7wwftIF+TSg5trLB47Wq/9Qdb5N6Oa\nWB00\r\n=SclI\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"77d59788312f6fe6d2554a38ed51ffcc5322d81d","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.0","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.114","@balena/jellyfish-metrics":"0.0.130","@balena/jellyfish-environment":"^2.2.39"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.11.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.5","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.36","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.73_1602819089947_0.7420715658397059","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.74-ui-schema-def-reset-3730ceecf319a8826aa79b51b7366fa6c1708a2d":{"name":"@balena/jellyfish-core","version":"0.1.74-ui-schema-def-reset-3730ceecf319a8826aa79b51b7366fa6c1708a2d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.74-ui-schema-def-reset-3730ceecf319a8826aa79b51b7366fa6c1708a2d","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b796d05c599ea5741a94d37b4cc8ef76f5cdf2d9","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.74-ui-schema-def-reset-3730ceecf319a8826aa79b51b7366fa6c1708a2d.tgz","fileCount":94,"integrity":"sha512-aliL0yHnwupVQ0jztUXcs/+YQD3JhHhmDwqI0uaKLfLs+Eb0pOWFakKlFTgRnGWo5tiTK7dX1FO33CK8uy3Lrw==","signatures":[{"sig":"MEUCIQDQZVQ1ZFh8RMd+6H6NnDYAuLksFbR0P8HzJcrIhNqp8AIgcqONoo4jozZ+C//OspgsyETXi3cOnFsfQExDn8v220s=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":567564,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfiV2CCRA9TVsSAnZWagAAlcIQAIsBRLeVH73isyjP7pYI\nzGM+pBULkj0p4ZTSBAhmWReTiLA3lzd8gVvFMR5suy9XP7fmu5mkY0mek+Mp\niLegvyebFUk+Ti20qKqNFVM+eDV5j3JBlAEk7xIjlarGNxPW6Zj7E1BEKsHV\nmefclFyRNCyu0da3CtElUybSAymi02vlFLxlrrZaoYTJCo/9K+FRrQ88DUwE\nPSne3v6s/hcu7IOZR6YrUzWZXc0rhZAXkw+KuSYCRLYtAGMusgymEEfKRYdg\nmDJ3+DWld7GqsNYKU7gZzpehBlcgVy4m9MvyYS2+dZA5NtZP1f0a4lwOXgL7\nCcERjfNAJalymp/AQ3exRfe1ONTrU/3RMEnWixQGfezR6NzkFGC1casfbtaN\nNcbDkGi4j8l17hnzORrSra3egnyEh7rxZuBf2hjwLva7fYhYdw0lsSTz9Det\nZW4jldQ88JqCVHh4gzeOhtKCdUCisbSEXsI7l81s2Pr+viw3T9go+CJmOU4v\nDQ3F2dHSyCAvXEKqelj2KPzN5dZiwIzP8Jctj1h9+wLNybV9O4f6WXcuGFv0\nI/00LS2FNnj2V2kYnrUWG8DVbSGxtf4nKV4OcBar4uk/Rt9pcd9L7UyIpieX\nhRg9lIjMc1itLeNZXLNnyPFqeDqCMhFPNZIu0eW5eZevVljIM+B2PWyTVeuy\neuMj\r\n=+Lev\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"3730ceecf319a8826aa79b51b7366fa6c1708a2d","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.0","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.114","@balena/jellyfish-metrics":"0.0.130","@balena/jellyfish-environment":"^2.2.39"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.11.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.5","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.36","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.74-ui-schema-def-reset-3730ceecf319a8826aa79b51b7366fa6c1708a2d_1602837889819_0.055380844943141216","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.74":{"name":"@balena/jellyfish-core","version":"0.1.74","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.74","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"64000c7f02f234aa87f25c13df7e14eaa59820bc","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.74.tgz","fileCount":94,"integrity":"sha512-kxu2GFJesLHRPAgK2RMae54E4yTQhTpJIBcdcZB2uJ196it7vVwpzkOMy90/gya90glanxvcKYSFxC7axNZd9g==","signatures":[{"sig":"MEUCIHDq+D9IRt8lMLMceQtcwcrH5evPF+AidViJoGwfloNUAiEAwEGsk0jI0qmfhXuCq8QYFMiX1H0O9R7MWV5+XWjC0vU=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":567503,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfiWP4CRA9TVsSAnZWagAA7QgP/3VLdrIGCbE9CKu3obkG\nGYYlhqFscHOYO0NZZZLGiO8XLJz46ttnHBtzLnEp5A3zN1no/tJcNd99MwJv\nP1U2DR38/J9KXwqCjM6hVFXGGj7Ght2puDBpJ+uqOIshjppC/RID89Ohwohs\nTvWmw6lyamgKw67SRuQsAPSyn5k9B4WowyhyT/oeodhyVX3dl6/UAyIwiFv6\n8gJ1m9DhIHl/V96hrtHHjJei/dXksO0JmoUbKFz24DZT3t6VuWEvzEeFSFx3\n0MWs3SL2LIX8ntF0J506JTLJVlKNa9bZKQULBpLGXRnirLbDMrudVE+UtMcp\nBtNeutY3UPaGOze/uODdQnc6fAyLqOTchjiVLGLCASqlfxk/sQ6by9kZZ35w\nIUhx0yezk5DkoAXY5DmQKWHrdYu6ob0VIc2faUCB8BTa7UHCDjymkyjuMU/O\nZz7KsAbgAVBXdWubsb078k21zcWV81d4E2AeVJLhvGPyxSuT9jlqLPB0Iez2\nk4OhOHTd6D27CP9ebjaf4PlckMjeyrwPu5VdmYv3xME1PfZ6U16OdZ9BHK/o\nVXuqd8Q39dpXawej8xZgGaxgZN2ZOxramGWgT6Zu7cEJ668p07AAObjVFyhy\nE0hWxksPGLzs09AiwraAFHExXI2ZWXI5Mv0fCuEFAeLAPtH0tYZInah1Wtuf\nqC0b\r\n=phPa\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"621cbda87659ddf87feea389595fa61d2917d6e0","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.0","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.114","@balena/jellyfish-metrics":"0.0.130","@balena/jellyfish-environment":"^2.2.39"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.11.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.5","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.36","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.74_1602839543607_0.25692628855379795","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.75-renovate-non-major-a2a71e2e3da1a9975088d36fbe9cfc11c462be5b":{"name":"@balena/jellyfish-core","version":"0.1.75-renovate-non-major-a2a71e2e3da1a9975088d36fbe9cfc11c462be5b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.75-renovate-non-major-a2a71e2e3da1a9975088d36fbe9cfc11c462be5b","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"235a1d00e92ccf0874983af5c204b6f23a6c335f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.75-renovate-non-major-a2a71e2e3da1a9975088d36fbe9cfc11c462be5b.tgz","fileCount":94,"integrity":"sha512-aFZzyBVhYmiaJIvrFAoJ8ZY1s1jZCJTywMcDtk5dtlKWM54U0zgfeMvbjhSsfiseOI0KGDx3lP5sC0InDIBs7A==","signatures":[{"sig":"MEUCIQCnX1Shgq7VOb/F2QjkZLcSR0J6owFU5cAtkno+pHcGnwIgBz8M9FJHflJlnFfoYi7y5SVBYog30ZskBZhdC+SCoug=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":567653,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfiaNUCRA9TVsSAnZWagAAOZoP/j/mpFc+vs+xspMvwwt3\ntn5jVO9MLqkH67p9OzpVkxv5fDZh4jgeZv3jWfcYog3RH/jCtcKb4RwGxAGO\ncdOObqtOKzNUiPUhvrCmwAM7lQJ9EZVCUF6jahtdJOgIgLKQTwEPcMTo6eKb\nGPBalneSYaLildYQ/cvWumn69fQGA6Zjqb9/fviUKQbUkbt3Gygn+/Rj3EKr\n0xjWgkrs7b5oYqyWlQcLZPRqcxHnGNC3BayFnfwv3YBz50sotnhplC02F7gc\nR9EBGK3Z+Gc1rKD3u9S4y5BAlB1WMdsGyuRU3keZKk55WJBE8JFYke80sIaD\nhFg9Epomnu2x5zV3RhIsJ8Sxpwr8t3D5ke7M3uQGchvQ+F2mck6EpxLBsAaK\nMKujWMI8tMeXQZ0fDbdb78Ht0j3Tz6KbbDRw7JGiWrLH/ThAf4IOSb2y0WkQ\nl9hgo5vQCIalM+Fwn4H+bDqi2RwzylduyjN8HwVOzoC9rcspWKcgLDzf5Stx\nkV/RWpiRqWiYdbWbpSpZh4ti+rXcu7Y36TpYgn0ScRb9uS1fwG08uroScWdS\nUIIVH6ZFr/ASDF9Nt3g986jvgc9eW+dIbtUu4VaZYkXPX2/Gxlm2ydT2C6As\nLrBy7JgNNDo4SUBDm0/nxAs7p+4zvI3vF4ny/xpbBNsaDX8bzvzWwGhALKaM\n+tEq\r\n=jlY2\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"a2a71e2e3da1a9975088d36fbe9cfc11c462be5b","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.0","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.114","@balena/jellyfish-metrics":"0.0.130","@balena/jellyfish-environment":"^2.2.39"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.11.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.5","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.36","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.75-renovate-non-major-a2a71e2e3da1a9975088d36fbe9cfc11c462be5b_1602855763647_0.45776534213241393","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.75-renovate-non-major-eff0cf7723a990bb664e2e1f9a99375ddbce7b2a":{"name":"@balena/jellyfish-core","version":"0.1.75-renovate-non-major-eff0cf7723a990bb664e2e1f9a99375ddbce7b2a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.75-renovate-non-major-eff0cf7723a990bb664e2e1f9a99375ddbce7b2a","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"730925d08610cee6dcb29aba11e2ba1949500f85","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.75-renovate-non-major-eff0cf7723a990bb664e2e1f9a99375ddbce7b2a.tgz","fileCount":94,"integrity":"sha512-6v6qa5d/gFvUzj9KDoqc+bISwf+Y5oY4xtYnbG7JhhQ8RipxICbWT/vxKx1EIqAEiXJmQ0hjVecEALO28WNKCg==","signatures":[{"sig":"MEYCIQChqWW0d4J1gFXazEtMpXKNOKIevHgGlMtM36X3GYC5RQIhANZvu7XwYSnseNLk41VRdAVrFXW8aLRLyszZ32HHz2v2","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":567718,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfjABXCRA9TVsSAnZWagAAFmgP/iK8BM6rKwtyEOMFRmJS\nKviaMu24D/NagHMQ0qHfRMMGpQ4/n29cPsth3bcfaq72qM0vRKzKyL/DPCsg\nKiXe+IY61lYC4HvPU5zmcOo04+HFyOUzpoiA58nPC93WpyokD3xA17Jl0Bgb\nQMXG8rbh1NcD97O1Hd0Wf+vbutfgM5H0URNlhdWWMUtXEjVfxjHf7trVE/6T\nAOGqQLr4/3pfrWE4/YWoW7av2S+OS4Z4m8OUsPBpKqXPFPMRePN7jVC2dcrm\nV8Eil69wOBjjew5F6ip3PPfINAZMTKHk8V7eHOs7n17XLtO/IUa4AjInRkpP\nha3T7IuzNhBe6h74EW3kNJw0kHkZoPi/T758gtkz+hZzrYLuRFuF/ppv+937\nWg6E6sq36hXsWrlOQYxrMhzrlEOmb6CC95MICO1ndstXKklOngZ8FKeQN4Ye\n5yM9ra329tMHWgU7oiWAOt/ooRDI+U/erKSaOMo40nR1997qEIjyIHJlV/GJ\nJYXJNlGSC+KXbHE07nOhx2QsX4EWSxAkvD5yPPiS+HpKkR/jyrTitCfo5zBj\ng+NSEiAoaPislrMdvmOGwptCUSk7Lf9beg9tk/SdOnTDs2/C2c0mKh5ZVfSx\nQYax5mE0rQGAqw1dolXvKFloAtDI0xNOhmJBnzfRW/XkE9Gg/UgCYkOGo/Fd\nCwGt\r\n=FJGB\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"eff0cf7723a990bb664e2e1f9a99375ddbce7b2a","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.0","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.114","@balena/jellyfish-metrics":"0.0.130","@balena/jellyfish-environment":"^2.2.39"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.11.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.2","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.5","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.36","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.75-renovate-non-major-eff0cf7723a990bb664e2e1f9a99375ddbce7b2a_1603010647330_0.7132554214031357","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.75-bump-test-image-dc6cfbb48b6f0f2522a9c64a2989ed884cfcffe8":{"name":"@balena/jellyfish-core","version":"0.1.75-bump-test-image-dc6cfbb48b6f0f2522a9c64a2989ed884cfcffe8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.75-bump-test-image-dc6cfbb48b6f0f2522a9c64a2989ed884cfcffe8","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"711353f340e4d903bc111b28c8f029273de2f7bd","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.75-bump-test-image-dc6cfbb48b6f0f2522a9c64a2989ed884cfcffe8.tgz","fileCount":94,"integrity":"sha512-JKL/7LAQyAnf58hZINEnp6/BV/c0PQ8PDsARiotg6rjVBMcPKJyFiv6HF1Q3AHoncjtkBCLF8YpVfDkCfq72KQ==","signatures":[{"sig":"MEYCIQCAt9KYc7U1QIMzQ105BNTBPiQ+mbPRwpmj+Z1i3lzlywIhAPc5IgSnI7Y28BEQWwaUCggLY2fWzs0WMixsO4wF8RL2","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":567757,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfjAquCRA9TVsSAnZWagAAxVMP/26LxdrJsb93Uf7dOOuw\nEqxY5HPk7TxF7pGL22n6+UbyjM33ea4beBooE5TI0sqqxBtRpLV/lZ3DCj0G\ndsD5dAhYDSQ+IjcHnIL7cmsngBjhKYXIuRkbdwrZCFKoYiAsbaqZk7pGHurk\nRvAxpAumxJytb72npZ7E761+Gd3Lnbjcn10vR1ShKEn2h5Ak7AkL5pZEMn+u\ngIx8P+pU59ZsrxAsNEw8a75ajJolT0ztlojJE+brYwa/G7b8VIRZuWXC6UHQ\n2CUpy17UG23Jy8zPHFUje1Odg/AqvryD+htxjcGZCQbaMlrAKkUpFkI9p3xh\nIBrjIOzCEfh9A5JoAsTzyFCv80WH/X43+vlwjv135aEuT8TMCEDSNW9Opy1y\np1bkw58oorDoD3+ygqs1qAHfAn6+F9IkZhvM2YZ9Z7xEC/KxLvcGUZVrURkH\nR/DxRbcklc1Y3wVk3+j6ApTXVjwIaYAV36+Xbb91CtlLRBYMz9Qa0yTw5TGZ\nuHhAHba0j/QnV5BAVEKzNz3UINnHux35y3aRhTi43iGXso6P/FD/TSZehaD/\nVlk/Sq7GoAEglfC5Z+ZVRXHC8UhcgDqEZlK/mkrlsY6b914LpZZ5AEXSMnib\nHpCWdexucQtx7tR4MHkBH60c0H12hcf7Yam+8imQBpDOGcoDKIzezCo7hnVJ\nXYfw\r\n=JYjv\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"dc6cfbb48b6f0f2522a9c64a2989ed884cfcffe8","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.0","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.114","@balena/jellyfish-metrics":"0.0.130","@balena/jellyfish-environment":"^2.2.39"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.11.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.2","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.5","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.36","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.75-bump-test-image-dc6cfbb48b6f0f2522a9c64a2989ed884cfcffe8_1603013293697_0.9524807488820595","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.75":{"name":"@balena/jellyfish-core","version":"0.1.75","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.75","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0d577306b421c71133f076fb5b99aa3061d9419f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.75.tgz","fileCount":94,"integrity":"sha512-eosbgoVIbBeJ25+lrKOsMctRGYeu2PCiJhAObn6RblYvG/WR2TEkyprZ0jGsuDq0zytH7t0pa8Gg4pGLgK0fZA==","signatures":[{"sig":"MEYCIQDoJQ8TePjd1JwMBckPbdMA01Fxq3gE3zFVmzHIjJs8KQIhAMXTFT8hkKA3LkJHPLZi7Xo8F3bhc+QnWLPqaCtKcyxG","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":567700,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfjAxWCRA9TVsSAnZWagAAAZMQAI64EqTQ0bVQ/5frAQ+S\nuBNUTPIQ2gbmWDzGmUhVVZ3u+pGRcN11PYiVBegu4kddu2uUPib8uGjdodBB\nV5YaIJJdwy9VDz9Pqb7GIJ91yBwF5PYFXvEtFfEb49PG0SVC924Hv98mh1RM\nuP8QXIm0s1qd7CZGSSZtuRFXtxlui3rf0qaGNrKZ1xTHyljxlx1nBnSHl51S\n7OOQotbGDlMES9zVcjdKzu3bqhylTab/1RkzhmMGHI6bSQiilRXX8V2qaNed\nTWhZLIvm/bTvWZya9H56G5ff5Ccgvj/Xsy5fjVWl09F7yn3mViHrevrGBDKm\ns2iXIGOBcdklFhyS7jGEieHQlgJK6MzE0F9Gt0Mv/Bg0Hua3mqWaMviPXlyS\nn0VuLJ1rDLx8OPu+IfA6AnD4RaI2+Af1PUeBLyzkiY2zU7D/QdvDwewT5sgg\nQhQ0N4DWmwRsWaXdqLAQbeiY7VSjx3LLER0OmuPINZMggN2b9J7x6/DATCPy\nMgPuhPUzPfxXWsjEF5RW9Isalh1r8Mb3q3VuQGDdBQE1EtgIUld8zFltZ4XV\njwUOco5lHGx7n67SOLXsycI+65/3VZEnEvlgaIdJbRCParfulC0wYu8kE9rs\nvBKPwoaEFn26DS85iUD6AGGYpOduzRzdTMpDKKs6+bRWa9e0nruZgWUVuU/8\nij24\r\n=zOcW\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"085c33f1572a912ccceed4a0cd497e0d08b1519b","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.0","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.114","@balena/jellyfish-metrics":"0.0.130","@balena/jellyfish-environment":"^2.2.39"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.11.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.2","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.6.5","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.36","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.75_1603013718350_0.17454009148656424","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.76-renovate-non-major-1485517122ecfce366e7eb08d6591ddbebc2aa8e":{"name":"@balena/jellyfish-core","version":"0.1.76-renovate-non-major-1485517122ecfce366e7eb08d6591ddbebc2aa8e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.76-renovate-non-major-1485517122ecfce366e7eb08d6591ddbebc2aa8e","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6adf619cb7c37930cb2b777e435a174410341b44","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.76-renovate-non-major-1485517122ecfce366e7eb08d6591ddbebc2aa8e.tgz","fileCount":94,"integrity":"sha512-nbrw8t+klIlmu121kjCZg3ZJe4wyWwO0RmMr0m8saNtuwbkuTvfdnPNh+1BQUF/lJswffT4qZov18SYr4wdN0Q==","signatures":[{"sig":"MEUCIQCWwaEx2smH709pE4y2Tm5Wkal7KVP/YtUCpCcx2TaeyAIgW1gs1rwslH/ug8lJcYQGcMnaLr3leQQhAB+WfCYqaqc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":567858,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfjabUCRA9TVsSAnZWagAANEQP/1MEke5VRpvK+DaVjSFi\n3AlyPDc3R5g0JWcWmWBmNR40Jjbob/qTus/bBg0ntczKNdpW68KfNZSK696S\nPmoonXDZ2q6W9Uqk70nHeWQW8M+ZgwZbxp9PrSeoEAJvFOLcEo2HpcvIL1P8\n40RcfQaiQkUa6z1IWhzpMAoKAQjiMLYJ8Ztcg/UqThaz57scxaP0Wtt7wt/I\nKtT18dzyJV3tog19pO7phBoBSLQLT27P5V4PF1b46zmVOjZCvDxCbEO4ygbF\nvm6BCfxM5GXlEnF37tIy+L5jWJo2swu8JdqP/YJIs8wshW9eG7kX6C8NUi91\n+/c0pWxz62P+gBBUZX/BDiyF6vBCGooBgIX8VEAE7GOqQbAUH6eTUgiR3hbu\ncW3//gDQSEQkce9MlAM7OhC1F42sNNcSgNwEdBYkEqojjBVz2232QfdI1NAz\nnNF2NQXx/wyMKjbLqWPm5YB4DAojSpy6l+jwcnX2VbuFHimT4oWuOml4Y9PU\n6R+DYwZRxoVBZVUQidtWWyQbwMFAYWe9AdqpzRfrVUEC8Jmv0RZ2SlN57O0s\n9h6S9UU7NqogCRfIBfkWTH92m2INt/SB4eVO5cIHL3L2vFc1E5B35dmG8BbX\nDvRcuPxTNxE3Jky83NjrSKTAW9SZcZPhGjzcu3w5Red7QzkuFzDW2jafC2tB\nsTzY\r\n=u/q7\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"1485517122ecfce366e7eb08d6591ddbebc2aa8e","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.0","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.114","@balena/jellyfish-metrics":"0.0.130","@balena/jellyfish-environment":"^2.2.39"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.11.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.2","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.36","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.76-renovate-non-major-1485517122ecfce366e7eb08d6591ddbebc2aa8e_1603118804046_0.2925642611853465","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.76-renovate-non-major-44e1d3365aed19a760e70695559a6259555def7b":{"name":"@balena/jellyfish-core","version":"0.1.76-renovate-non-major-44e1d3365aed19a760e70695559a6259555def7b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.76-renovate-non-major-44e1d3365aed19a760e70695559a6259555def7b","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ae1a1487c38065bcc28244b50110c0cc76bfe600","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.76-renovate-non-major-44e1d3365aed19a760e70695559a6259555def7b.tgz","fileCount":94,"integrity":"sha512-ggNqUYOyv633HIlNvBDRSYMN0G8CW+yFprvCTJWFEkhIAF1dI0VjAjRdvkhdNW4f6Wmwz4apfHdRjEAuqjrRyQ==","signatures":[{"sig":"MEUCIQDAgMT/k0cy/VRWBYlQLDDBlB+UHOAXgmz1wDEH9liqfwIgCM7KZuv1vygNwFK7/fEMC9mAsCJpR0cJipUqn89apjw=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":567931,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfjax6CRA9TVsSAnZWagAALP8P/1kDc9CwZikfqLGbWDdl\n+5IDsS6vE6g/YmmFhMy2iotL9Yss1LQo4F+C1zofYFt1DdyaKXuQXdArF0Ii\nBWurAKVuxK+pEwOHNqyMpL6zkURe4BToQBTtn1AaSK71QSzwsrB2fhucJHtA\n1vl5VscHp0HsrFPyQZ/DYG8HRejPIy/46lxYpe9pxvYVnh7dWVwg1sA7IX1D\nA6O02Zy/NLWrbNtNVNDOWAMqCIagGgXUf9TrNglYFMNCvry1z0mNeyBPdtSF\ny+1EbGugbJs+PzuVakxQ/na/3NJtFTlMRIovEiJ5E2CuIgE6+3cp85JndNqN\n8nRIrJRAeM8ahiosELf7TSuqEuoNnifxW7/34QFXhar8zJtQN6WD8ckpmBR4\n89TJ6qtqPNhT9bzy88yQaTAgGE54YWATtsPtLAqjhC+5GVLPWawUDba4KKhI\nJfyB/U2pKDSyhjcN/MpRnQpQ4Up6Zx0b3QEFYQp0r1Ajui8dYSIJ3tHQ5wYQ\nwwkK9p3zH57ooDWSkTWa//VOtaNFDbMaStEeusqWA66PlZm3hjNp+Su9FJOr\n2hL9/PHQrsgvyxlcNclLCHe4HdqK3PsuSxc0xxnDqS4P9kvRqeLk4poz+DeF\nc0PDizYC8NsC4UwLcIUiDupUr4v2qJy6XARa3EbeqB8XPQj/Ch4HGmGY3WId\nd1u6\r\n=Jfpn\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"44e1d3365aed19a760e70695559a6259555def7b","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.0","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.114","@balena/jellyfish-metrics":"0.0.130","@balena/jellyfish-environment":"^2.2.39"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.11.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.2","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.2","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.36","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.76-renovate-non-major-44e1d3365aed19a760e70695559a6259555def7b_1603120249806_0.22335279284399268","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.76-renovate-non-major-82a1924a83f5d5930e004ea6a14ef8c9514ed6fb":{"name":"@balena/jellyfish-core","version":"0.1.76-renovate-non-major-82a1924a83f5d5930e004ea6a14ef8c9514ed6fb","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.76-renovate-non-major-82a1924a83f5d5930e004ea6a14ef8c9514ed6fb","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0ca4d9c85332676c884592bec18360c0ffe3b7ca","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.76-renovate-non-major-82a1924a83f5d5930e004ea6a14ef8c9514ed6fb.tgz","fileCount":94,"integrity":"sha512-my5tr8QwQE3+oHVkJh2Z5RWZsUysAdL8AIyI9PV9TxPVEzquA0yapUzJsByFjlwV7CRC9a9YyHoaLlhjL4AQaA==","signatures":[{"sig":"MEUCIGrz3LlIGm/5N4OK7qICxhAM0paku5z9co32Asphu/RuAiEA58dVJC5Tdn3B40o/MtgaobEQ08h5hPensN5TBzF0B20=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":568004,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfjbqUCRA9TVsSAnZWagAAwEQQAJ9PkkH5DK/eoHoUsi0O\nqptcCktG3kE+4Xv8sicscHjiNKN0vDp3q4GwUd06Q/hxqiFsXSWY/NnliZH6\n478Pnvcgg9W8+1CK4sTlrpbOhZeygXOAvsNmUP7NNFFYtFCXS6UTvIOaKuW3\n5CP93E8M9utTMCh4bJlqrUa9EbAQmvgT9sXtJIAsQX80ciePDhKdclq6VjPN\nYtD9ffyAi64SvqUhzHjGdr2OqUKSZP3nraxbbuB8B7P/I9BA8Udw6I3SdHPT\nA2KQxZw1/9uCLg3eCMv0neESVu7tl2nNk/wZWBKjrtIUFJUR/7NTOL/aB//I\n4p+eSwQaUWc11VmeCbY9D6lFN4pd7IEI8gzL9+5vkctUMhwPGRq7CX/TwbWF\n779MCdI4j+DqbN4X41z4N6UlnqFEZFsK727G2oe+jPF7Wxb4Rw+WBimp+TvO\niECDNlzfwoJZZWxitSk4chUxNckH4s4UOX2110sfMID4OaaDHVUQm13GWiDt\nJ4fffVbDx2FS1Hn8jSdAnTMpyiHexgb84rWqzOT3KEBdzNMU66GRjHsA8qIW\nx5ng9rfUeV+9mJ1UYHS3n9hNeEhf5oKRlqcU4Zc07N7MCurXUh/dUFrXWeXY\nHJD2Gr95YATIh9laF2Sjhr8tnVVhFT9DCXjNf4HgtfiAfedlUglrS5NUOKGi\nMqFM\r\n=kDNx\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"82a1924a83f5d5930e004ea6a14ef8c9514ed6fb","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.0","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.114","@balena/jellyfish-metrics":"0.0.130","@balena/jellyfish-environment":"^2.2.39"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.11.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.2","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.36","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.76-renovate-non-major-82a1924a83f5d5930e004ea6a14ef8c9514ed6fb_1603123859490_0.9685859261236283","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.76-optional-links-aware-expression-filter-4b1e23c68bd106a5a19120e732e8e3c20366a1c9":{"name":"@balena/jellyfish-core","version":"0.1.76-optional-links-aware-expression-filter-4b1e23c68bd106a5a19120e732e8e3c20366a1c9","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.76-optional-links-aware-expression-filter-4b1e23c68bd106a5a19120e732e8e3c20366a1c9","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c586a51c9fe3078d18dc669d39a0fe66204c50e7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.76-optional-links-aware-expression-filter-4b1e23c68bd106a5a19120e732e8e3c20366a1c9.tgz","fileCount":94,"integrity":"sha512-h+RJaWfrApFg+FA3VeAyw9p/Ox4Wegrtgde1vF3xbAQcx7/4xkCFX9ST0poJ/W4ur7nZa0Ebc073Iv4qMR7twA==","signatures":[{"sig":"MEUCIDBBYsM53z5NxnUpib5JElov2wkcp48cv6ZwJNwc2xA+AiEA4bzPRmFy5FQR4OTftGMjKnROJRmiaXCBLwc57PwPeW0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":568450,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfkKeOCRA9TVsSAnZWagAA/GQP+wcYSv2o5nmP6gi7Qpyq\n7kmhd8SU6ALhVVTEVpZhg+sIv2xUd/u2bvPk697JWManT7l9ZtZwP93kSP93\n2xJA58vYCYWivC4l0P+ZvYW+GIU0uZ2dy5HgeNEVOlssnauXJG1XVyXACrhp\nrVR6eaS1TO4ppKys1IzqozCmSfv111X7qK4fUuwnDXX93exTieMiX2pfOIXS\nZRUBwvZFFqhBBW5Www4PaYcWhSf8P2nn4VRpHlcLzDFavX2MdkYR8qXy/RZB\nxlLNP4QJlApRN1aSwD3xTpRfugJdz3q0aFhLxRZhNzvralGZSetWP+LiDEdI\nyI0OCLjH3ucr+w2IIFFFfNHvaXo6cSNmnza3kz2y99U1UQwaPXMg+KXzDymP\nUmrcvhLun4YGaU0QDUfSR85W8LlQhpFJeJQNeQAXgcbRI3bbq0Y1B/e0DCv/\nqQmx7hWtTAoks8a9qc/rRhjmabdPs6eQsBqZvF3v6bFsfBE5OVu2mgQvAjaN\nktzUCowT/ovtAlnz0xUr8wjDMYD+Auf0ed2/Ucm6pLvAdAqCJJZa3XsA3p8B\nhVJZ0hY6kjZxihsLSotylsIaXAQGzXayZ0M9R9FPEUeCzFI7uuF+PdIlGJ76\n8hM5B4z9BwoCE6Jx6povFYokxQ474fSsAQN+AawcPmUUa/WJmZjq0thpGqb4\nxaXx\r\n=3efX\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"4b1e23c68bd106a5a19120e732e8e3c20366a1c9","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.0","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.114","@balena/jellyfish-metrics":"0.0.130","@balena/jellyfish-environment":"^2.2.39"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.11.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.2","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.36","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.76-optional-links-aware-expression-filter-4b1e23c68bd106a5a19120e732e8e3c20366a1c9_1603315597573_0.8417968715958799","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.76":{"name":"@balena/jellyfish-core","version":"0.1.76","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.76","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"afdb7f22b74a7f84cc36d4bb5ecbc13ce837b176","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.76.tgz","fileCount":94,"integrity":"sha512-sX2lRHvdOBTovvk8ud3DIYAkMZJvD3rR9/xAdKy8HB0jPrvTRAC5VWq4kKbDcrtotj2+y4G7qduDEynPpfJTow==","signatures":[{"sig":"MEUCIQDrM0+CrWg/8dGwahj9U+AHx5P/ODvAU/4PRCBjXz5aAwIgQa7/izfAPDNn0vDBD4TewuebAfo8zWzfpmGVMhYkQ64=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":568370,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfkKjcCRA9TVsSAnZWagAATgQQAI2utJ+WoLv3egUU9Xhv\nLKYVRB9Qg7aaeXelegnJ5sR7BR/3DpQG/khv8gYEJCqd/3rKsWBX8G77Vxg+\nCgv3fhaxBlbjihAlOyLWfzq1zVnSJer8LN+Yx45IfwkOQnBWgxj177RSwdGe\nHq7QtnpIpiVDnRJSsiFwIOm2q3OO19K5hyRuddWjmPc54SwU+jjZObOnw+Zj\nSK/NBc+XPOTUb6XeELEkWTmZuJj7CNzk62SUQUTOdOUta3+BqaNtFfXKujWz\nxjqnDRQeL1DFZZz7I4KV/rS9vNhAxuLwtwO2DeI9wFRXPv5BlJRcRowlou48\no5ZrcxP79KDNB43KMBr8xyY9/27KEHIrhFCtp+c+QE189bz1dQmGalfynnCx\n2SbcoiYJnKaFcCw4qSEdV1SCjWQJOznMz1dUggedOecaKRhlUAeWRV+QcSr6\ncGzMdPUUb1E4gjXrW3AVB0cxJqsJ8bdCpc7ddFOMQ8IipaXJgro4EaZxXXD+\nCjZAutHmL6sDaWQszvuRT/oAEh0FCZLuXWNb3i9PEvEAX/O7UC7iosPMQ4vR\nypUGKHuTKOocwGsRZgUPrbYa2PdRfw88NdRhmN9TwLK3pK6BjLXyQXGscs/h\nkcQcsUjwGc3iUrC6ZBiC24emdzw/pgoXxs+wbfRz6AF3kx6ckvVtfEHD2hwl\n00ub\r\n=enOJ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"6e589ae63a51c793da5bec6a3b61c21663a1838a","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.0","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.114","@balena/jellyfish-metrics":"0.0.130","@balena/jellyfish-environment":"^2.2.39"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.11.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.2","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.36","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.76_1603315932063_0.24210489563200022","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.77-renovate-non-major-f3bdbafe44d4f2ca67a216cafe065e3f54b87070":{"name":"@balena/jellyfish-core","version":"0.1.77-renovate-non-major-f3bdbafe44d4f2ca67a216cafe065e3f54b87070","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.77-renovate-non-major-f3bdbafe44d4f2ca67a216cafe065e3f54b87070","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"dc57fbd4378695a250e7cd6c7cfbde6fcc7bef4e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.77-renovate-non-major-f3bdbafe44d4f2ca67a216cafe065e3f54b87070.tgz","fileCount":94,"integrity":"sha512-DObaceSApwslgVgjGcbMLBhpKkveHTYZn/MkgNmIezo02nR/6MPTpswqMObLh/1Ru8cY3459DzenfXZ+on+kJQ==","signatures":[{"sig":"MEQCIEsLZ1FlYvYGn2OB42MuGZyRTCK/TdCPEuykE7yNUy7YAiB97Z56xAClCIk8qY9N9OgEZ1lkEndNWfLm4zKb/J1ZrQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":568530,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfkNCzCRA9TVsSAnZWagAAzocP/Ax5N1gt1WDoHFR0FHO3\nzv1TD09YsMzl14jUAKjwJ7VrdL09EGL95mp+wFuzEjnsnDUjRydufcrA1XVa\n6JAm2o4HpA/3Qiv2ilnV9qVJOlgtHMaVQoMJCuLgGpHs21MDge292hfCdsQl\nRDl1+N41WmOkjjhDHlkJ8ptm52vPNOhRMqKgVQazwWIkHd86p6ZebS2AjGyw\nnQtHxXsPEkrVZ8gw2eLsh1J5FhKvKhfNpdGqS2WHCyWsKK1da+0HMCnvasiz\nio/n+JIO1kMwvAI4FIujJXJCk3Qva7S6122Ad906sw9ZQSMfHY+jITH7Xa7n\nsOrJxjoBHCzjGj7Qhqvbs1jPhmQ2XcWNktKN0lC/hM0oLJcGBONgK2PYmVpw\n3spATF/CA8SPrXdqp6Vel9WKECyJeQXGaAsveAxVO+LTXS2lIjy4ehOiDUTq\n2PdOqbJOKPaUCi2LmYwPSYVKSdK2L9zRmN4uGKf258svRBme57ig1S098QY3\ngA8jZY80KTNwVHglFYAIv4mMbruJCeM6IJPIr8wj8HFjuRlrPZKWejH6+lgX\nnHyZQ+xJoAElvZAF41kpWloQFB+Sh4XArlEgLinv1QqjsxeQ4J911L6hzVKn\npi/RLxfpibemdgSZcafm8ILxU5PKpjcGWIUXStFtMboPETzQVJmHg3OSq72T\nRnMb\r\n=BNEf\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"f3bdbafe44d4f2ca67a216cafe065e3f54b87070","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.0","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.114","@balena/jellyfish-metrics":"0.0.130","@balena/jellyfish-environment":"^2.2.39"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.11.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.2","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.36","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^14.1.1","eslint-plugin-standard":"^4.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.77-renovate-non-major-f3bdbafe44d4f2ca67a216cafe065e3f54b87070_1603326131349_0.6816983567440515","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.77-renovate-eslint-config-standard-15-x-d7cb185b1c1d019883b1cbf32405d10100dbcab2":{"name":"@balena/jellyfish-core","version":"0.1.77-renovate-eslint-config-standard-15-x-d7cb185b1c1d019883b1cbf32405d10100dbcab2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.77-renovate-eslint-config-standard-15-x-d7cb185b1c1d019883b1cbf32405d10100dbcab2","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"82b14143b4573dc39d9256012a2c048f692724aa","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.77-renovate-eslint-config-standard-15-x-d7cb185b1c1d019883b1cbf32405d10100dbcab2.tgz","fileCount":94,"integrity":"sha512-dOrWOwmXq1PjGQo20feJP8FHIxB4Qvd0IVVOXg2NcSn0M4lFvE6ICPfw1APX8wT7LDGiXOPdzxaOWd7mOkSzMA==","signatures":[{"sig":"MEUCIC8uhuhe5H4Xxo9YpBBRUd879KCImxybBX1eHe4CwNXYAiEAv2hH1yWsiuu8Z2VFKXsiMjpBLJUSHYF1dESRyJtBi5k=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":568620,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfkOt5CRA9TVsSAnZWagAAx9sP/iyu8vCzpCMWwioSOZ26\nbhE8qvXRNO5RQv7CMKminspM58FRi3xYS2gat/f4TJbpXjx8qxe3GHfsfkIJ\nry3nxykqDKv6WwKEOJL8tkaIWthf4QnsrKFGeKAtvWY2DNk4j/4Mwpu4z+Ck\nOyJ9g1krEDx0gM5pSU/3/2LQk9vX8fSxXWszQGv9lU3Z+/IztO+cNa7IJP/N\nknlatAFZGusJQDuml77UMysq6/fuQm+KmSlgblfoSfS0w8Vy8xGKfJ6THOKG\noM1BoB2vdNTFBPRFHZCdVc20MeE8vUm0WiSZs21DpTIDgWLrr5bDve8U8L8x\nzq5+DDxXURduI8U/fv8qPIBWinfg43kImFm8qN7PduLyQohZN+n5N836iUCq\nSnafs29Hc+XspSsmVUNU/S5A9xPC9o9gTrO56B+IOtok90WrxswJIvuCAwQI\nHAtbnE5A2PvSysPpLNgob6bGHDzCBh6uamPJ0JekiG/d7TzjeKwlep8oMivN\ngrXkFBQkrehdoB8WLyucx/6dX4mwWa59LfwHLauv1Q9sgSrfZYfjy2v3MGG6\nQDoTQ8uFHB4Vz0IdBf/VGdSPu8uVc1J2ieiYOYQj0+b65ACiZVvpOFXlae/c\nlBD5F04J3gVJiW36TmQEG8k7gtoChey9bvwpk+SW+i1UONZpMmvzTTdumsyA\nxbsm\r\n=hou0\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"d7cb185b1c1d019883b1cbf32405d10100dbcab2","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.0","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.114","@balena/jellyfish-metrics":"0.0.130","@balena/jellyfish-environment":"^2.2.39"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.11.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.2","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.36","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^15.0.0","eslint-plugin-standard":"^4.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.77-renovate-eslint-config-standard-15-x-d7cb185b1c1d019883b1cbf32405d10100dbcab2_1603332984739_0.4181191771921742","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.77-renovate-eslint-config-standard-15-x-d34a1d1cf0c1064c4b2c18bc08e4217bb720c3b8":{"name":"@balena/jellyfish-core","version":"0.1.77-renovate-eslint-config-standard-15-x-d34a1d1cf0c1064c4b2c18bc08e4217bb720c3b8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.77-renovate-eslint-config-standard-15-x-d34a1d1cf0c1064c4b2c18bc08e4217bb720c3b8","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"bda9512856d46be26a1683d9f40a20d99911c8bf","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.77-renovate-eslint-config-standard-15-x-d34a1d1cf0c1064c4b2c18bc08e4217bb720c3b8.tgz","fileCount":94,"integrity":"sha512-6kjBqIZ/5EbQwc47O/akzUGV+5OMMlrnOKy3XlMmGGM0mGv8pTvaSiRu+CALnMciaPQWb1Tdff6SJLZJqMSvqw==","signatures":[{"sig":"MEYCIQCaf6eB6vsCrzQ7nnJT9KDhXkDmusD5gyCW//fWK7uUFwIhAMIu9KIWne0qF7PK4MO07sfBHAAbYPX6aRZYYa1SgR7U","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":568709,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfkjQQCRA9TVsSAnZWagAAwVcP/0WYGKBgrdAbgs6/8+Xd\nRaxqi82rWt8snL0UMx93grJ0Cc0Xu2fbI1dy4pOIqi/EXEaxSuLQkqWmcORA\nkzKLzub1wl93AP/udFeZDf7eZqmqVpM6fhc4lE6dIKNZ2pekicdCENjDTdDv\nfyu15tly1Kc4v7uJPs22dVeLZYocElFJGukqKclmCNstOQ2P/6kB8KWexAvr\nfS3CjLTx5Ud+nNk1bzbFYwfoD76wuUOMud43XFSOtEF7gV2jDG4IDlTdrn0x\nTaZ6YtDVQ8uJAzO95OQeqRGoJK/37DoSrlVaLqCe08m4Q9yjR40jhyoDvZ8L\nbgS+oMPU2R31rNdJHuxzUGYuLAgtW3GrOs+K3Qx64QEPJJiUuhG2m5PLc6j8\nqkn+JPq3SGGumweMVwNFV+1fxlDQrB76y2Q57pm2WXzi18cmc5DY35oRKZGf\ntYt42pjFnCf5bQujIEKsmxgmN6sy6vSDUW2oHBLigUGUoaSTfvdtAZR5f4sf\n5R54OkjEqrc2crSfbLsll/w3m1zHsUu3/fW/VEDafCJAxxXRs7m4yJC5WEPb\nYekIJJyFKOtKVmtHCCxj8QC8nt3xDAuSZGp9ik9CeOoEQBvO66rJhoowMLKQ\ncdntG88HkxwLdR1x6tDXwMAfJLGyJa27DMu5aYUkyM/pNqseiaI5UqgCZHwl\n1Z1j\r\n=+fiI\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"d34a1d1cf0c1064c4b2c18bc08e4217bb720c3b8","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.0","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.114","@balena/jellyfish-metrics":"0.0.130","@balena/jellyfish-environment":"^2.2.39"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.11.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.2","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.36","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^15.0.0","eslint-plugin-standard":"^4.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.77-renovate-eslint-config-standard-15-x-d34a1d1cf0c1064c4b2c18bc08e4217bb720c3b8_1603417104335_0.3575208820223015","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.77-renovate-non-major-20ac05640a4b87f03e3c72c9adf3e152bfc8f383":{"name":"@balena/jellyfish-core","version":"0.1.77-renovate-non-major-20ac05640a4b87f03e3c72c9adf3e152bfc8f383","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.77-renovate-non-major-20ac05640a4b87f03e3c72c9adf3e152bfc8f383","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"58a60216937a8c33ea4bafd70c9c475e8d26b226","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.77-renovate-non-major-20ac05640a4b87f03e3c72c9adf3e152bfc8f383.tgz","fileCount":94,"integrity":"sha512-dr2GoTYVjmXtIF0ZAjyjbikHUlfYpP3kjWnobVfwYM/j3764wOeyVv9tJ6sK0qwSRI9GhoOWhx236IohoCZO7w==","signatures":[{"sig":"MEUCIQD3eCj+H6Ao9NanBSdcoSpaB8nYhzx2AF1vYMXyVbzYTQIgZ9lw+nBp9mSLMhQe1KMhT6Dg2DRDOf96/bdu9OsnhQc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":568751,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfk5dpCRA9TVsSAnZWagAAJdAP/AlFwtG7Fz3PN3XiTIf2\nwEVWvPZbTMi1ita9QCC+xMPc8xUHTuRC7X8Cn9i7sCn7Zu7PE1WHO/+cL5iB\nqT92Ua4Kp9Mw56USQHKigwkIliJRYS1OUv6Xw+9UlZ2UmsJgMemGDnvjYWXg\n/sOKIpFTAdihsmwF05JVXPCoGIzk5wokI/ApXxKj8E1FRvBx+s0GwZ+55mmJ\nKiwDe13wRnphdM6KdVpNnjMWXumkHZku/ShHHNkfTw3ZGuMjoU8jWKeKz6pQ\nftM+chs8CPh/k4opgTIbH7zxq/6bzw89yKHMkbKf4WbEmcsnqpjDMH6mGWIT\n9b419jcYDEzQ45YLk/HjNPpYy8pWHSYic+HEjyYVuow/kmsPJhc3PnKiK5aI\nI1LYkIrIoBCPsaSPRmmVAHMuaK4ps2VHzmFH5I35aLhGpY8aTtT8pc2m0Orp\nnTnF53Qvnb3mPBSSBQRdocinEILOWJ1rAsRITby5n/bHr+2rJ+QP3iDXi9jv\nSym0zAfiicKxJYHbKSHAqLUHUH0MwlRfbfXkFFpeBbROBaeliYmXQjonQZoP\nQGltZx70J1uXVFVPjmEagbX2XfsnpgTLQ3iyOC5iBQjjC5B5+ylSUI3Vhnqq\n9NAc9LOOTJd5mODc6q/krbgkQ/+NAaBwDmNuHcnbvsmtAEgDAl7U1IZ5AV/Z\nx7eX\r\n=w+7S\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"20ac05640a4b87f03e3c72c9adf3e152bfc8f383","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.0","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.114","@balena/jellyfish-metrics":"0.0.130","@balena/jellyfish-environment":"^2.2.39"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.2","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.36","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^15.0.0","eslint-plugin-standard":"^4.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.77-renovate-non-major-20ac05640a4b87f03e3c72c9adf3e152bfc8f383_1603508073198_0.590295359454301","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.77-renovate-non-major-cbecb048b86f8d38724024b6b805d28dbeb435f2":{"name":"@balena/jellyfish-core","version":"0.1.77-renovate-non-major-cbecb048b86f8d38724024b6b805d28dbeb435f2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.77-renovate-non-major-cbecb048b86f8d38724024b6b805d28dbeb435f2","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ae70dc588de59ab299dc72b3d45fa703f10cdaec","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.77-renovate-non-major-cbecb048b86f8d38724024b6b805d28dbeb435f2.tgz","fileCount":94,"integrity":"sha512-m/l2Hqkxz3qkk7b761rGl0oveg8sGXjuws/2Jie9Pypczf775z4raOSHRrFKcuoIHiJ7azYUEIk3JM4WtrbB2g==","signatures":[{"sig":"MEUCIQD4VjyIO20D5/qYW+brPmjQnmCCCqauZ92F03MDNlWE0QIgIZWVbiT6Zk2PcgXpf3DLlafkhXWJF7TmjmzbXg8QvNk=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":568815,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJflLASCRA9TVsSAnZWagAAVmsP/3aeHuBugICfuvZCuotT\n0KTQ6SpzpSrmf4yz6Yykf2DAzIX3CAQuk1CF9BjGmbRO+irQ1MF4doEJ2Pxp\nbsKUql29z1K7cbpXPOp74Dzjvdf05MWS0BJ7y2oc3kOZBsUunsLUm+ORS6F4\nwwsjT1B5NDb0iXmpdHqUB+Xkb+2jPzavfp8n6vHFWp7kjdTspbJErNGRXJ7n\nrMu4LguE26SY5nuHOluv12zA0Sg/MQgEcY6NmY2InCC784G+dnu8LiSQxeQC\n0dUiCJoJQ2caXhykxtEE9LCeZLIdIomDzGAFoteHilORxA//mrj1aqo1TVAw\nDgpnJrNPYXTSvyLfGzAP3+DPwfffUGGX34dzX/lZsxJ8sm69b1L1EvzAq7Qg\nyT/wgES1zzbG2dxov8uv9ps6CypawT0cmjbIEQa4ydioTQznoLW57xYJXqkr\nBCmP2MNL1Uyx/GjRdIkmEoG/jNgu8lhKwfo/EJeoZfRLUpiqE3w1sCFe+zug\ntBLiaZLotjtMvCiFcRZTW2TutCLfK9O+1xbO8sSs/yfrMmLxsRuXi4lWBdNC\nVQgOsO9DhxHHttibOI5tuVIjNRGXBuK6SGqDLIGF7hMcDNB7QrXFE8CRS60V\n9LC3IWtmVs/VVM9IennfwXitysZ1SI5I64TddtbBavCIhQwCGlvzs8CgsZGe\ne3Zk\r\n=fRcw\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"cbecb048b86f8d38724024b6b805d28dbeb435f2","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.114","@balena/jellyfish-metrics":"0.0.130","@balena/jellyfish-environment":"^2.2.39"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.2","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.36","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^15.0.0","eslint-plugin-standard":"^4.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.77-renovate-non-major-cbecb048b86f8d38724024b6b805d28dbeb435f2_1603579920964_0.3112174309216309","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.77-renovate-non-major-e0811d5fa4166cab39a5493774901263082dc691":{"name":"@balena/jellyfish-core","version":"0.1.77-renovate-non-major-e0811d5fa4166cab39a5493774901263082dc691","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.77-renovate-non-major-e0811d5fa4166cab39a5493774901263082dc691","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3474d2dc6683698ca37b14e5bee0c69bc3402924","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.77-renovate-non-major-e0811d5fa4166cab39a5493774901263082dc691.tgz","fileCount":94,"integrity":"sha512-uVPj9lVIemdO87z9i6R8V0DEa5HxM834UvY3jdXpPbWQGmWM42Wd1CTDRhFMG+vwFkGjvdP46urnXwjIH9TNZw==","signatures":[{"sig":"MEQCIBoYt9QheIFQEbpp+9AYavZ9ez9COJxqLgiTVKOYe4/eAiAW6U32MvYPr5G/1Bgyz04cEAd06Apw3+imn+Mgf4Aosg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":568792,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJflxN2CRA9TVsSAnZWagAAW30QAJexxHWF61yASsR2RfXP\nzfZOQ5Ir/06TK9MPWTL8eFkDLdauGeqYGcW+1Bc7GbFKCXhRdHHLKM3zmqdX\nnZg4rkcGEG+biQ6JAVxrzuJnisSRvzJjaNifnnwEuNbEYRPKbXZmuWV6QVI2\ngw/B6hZPbwsNI3/frwb7Iiz+VtECQf5MONaxlfn93VGiqyldsL+MP1Jp5Pz0\nMenjOcPakuh+/ryEzXiXyse5C28C955OABm+PZkii1lBJJyvs9k4DSA87iZg\n6mJ9H9d5VVbFe8n8EfQhYHapPqluAMcPQHCI8eIFrupiwG4BbL2dLezbKcKE\n3ZsBVNbd9lhO99DLZfu0zhwV4SXPuJQidljtsmF9c4CBWe8PeAlQi3KLJhic\nHYRsk6jleR8lTgCPBydwfS1ALZBCKjp/YRwMMK0qsiDvMxEepzDxm/B+yKdk\nGyGrP1HU35eoRZ0cqUgGjuDhhMyAlgHfEe++5d9hDXPo7pSsci7f/YPVuogG\ns+sw68ZQc9uHo2w8QskAGkP/XK46oV0Ef6VCP5/qCd6ksFUDKdGAM30Lv7O5\nAOINn/RamPlbMqWp62+vF/SUbqB4BB5g2uRB49ms4Ct6AUn/Fne/SIxNaJJ0\n6tpv50U8b6b7YuHg0g9EmRgDc/bW+gTBjh8XMagMHQBwm96G8bkMJPUDLSjP\ndiWd\r\n=iae+\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"e0811d5fa4166cab39a5493774901263082dc691","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.114","@balena/jellyfish-metrics":"0.0.130","@balena/jellyfish-environment":"^2.2.39"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.36","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^15.0.0","eslint-plugin-standard":"^4.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.77-renovate-non-major-e0811d5fa4166cab39a5493774901263082dc691_1603736438337_0.16934683860962885","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.77-renovate-non-major-60e09b0e459831b7e7085d04a4053f1e8db8f5c1":{"name":"@balena/jellyfish-core","version":"0.1.77-renovate-non-major-60e09b0e459831b7e7085d04a4053f1e8db8f5c1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.77-renovate-non-major-60e09b0e459831b7e7085d04a4053f1e8db8f5c1","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"fcaa0b8d94c80bfac07f52e02ff0dd31a7f81996","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.77-renovate-non-major-60e09b0e459831b7e7085d04a4053f1e8db8f5c1.tgz","fileCount":94,"integrity":"sha512-TAVkNf1u4hnlO7NbQ2gAXeEWYny4CiQtuYNXLbvoXQKMtE1fyGXKqfwABMvsycCDt7DONB2s84GK/2HCVxOBDg==","signatures":[{"sig":"MEUCIQDFFUrjTua2fyII1gw2mDnyjUtILGFIyZMstBHfdJyP+wIgESdg0FJuPVIA0kDBLnSQu8sEU/L8CzSeeYOf8+vSTbo=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":568792,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfl6dCCRA9TVsSAnZWagAAm3EP+gOTTZETC+GLD9z01gLa\n2KEQmftAk384Mpf3PV0UbnaK7YP3saPIlcJPiPsNOdsVZH3me+e8KmFYKNTn\nWyoaxbGf95HoWGXAHEeMVXCvjEE2WEiEtAbGVSMgbXsmB0N3UkEshwOwgyN3\n24HDBJJeuJ4NCGptcSnyrNZ5N0XTphXtOXAX5F6tOwxPLrSDihnOZxYKJaiV\nONN72huVuUFbX3rUqMmvCmHJcwBrvOEoxxtWuiTog5dQdk0gkLmkvYPa4jOY\nHlQzYf6JxuhRoV6UdctRSZvq/nRcaspenHjqqvMEoy2e+DXZV/7f/FztuYnS\nvzsuvnJTwP7GvPxUXym5Ez1bLpSk2EqCPl+/EQatKmEvW1EHlUzbDeSYkMZR\nsUcrsrVym62l9WWVRUuMdkE4cKmOl8cJyotZSPK7IQJOzb/vL/E6qqcIGXVH\nKvozmAco+xmtmPVchn3cvkU9/sB0wT8ka9oqW4uNOlgOxqS/ESGW4GW3DUQG\nyJMNGMu9JloOPXT0zHVUZGTIpThi+5PkJBwoXH/FnHd7Cewp7N/KvZ4YnHAY\no5lN5oy+X0yuPyHZrP1qyqgWOfO6vIG51Ws1HpXIFHVrck7990hZBQ9cgkNI\nXEx6WemlSFrAoiufiFvHbtAjFoExBLOUGIvq7RvZP7vbi3+bh6WBsTHJ9uvs\n5+Mi\r\n=jgGl\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"60e09b0e459831b7e7085d04a4053f1e8db8f5c1","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.114","@balena/jellyfish-metrics":"0.0.130","@balena/jellyfish-environment":"^2.2.39"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.36","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^15.0.0","eslint-plugin-standard":"^4.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.77-renovate-non-major-60e09b0e459831b7e7085d04a4053f1e8db8f5c1_1603774273766_0.34319604578732466","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.77-renovate-non-major-46be5ea55c8fe668a65f2aa47f3acf556a0c133f":{"name":"@balena/jellyfish-core","version":"0.1.77-renovate-non-major-46be5ea55c8fe668a65f2aa47f3acf556a0c133f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.77-renovate-non-major-46be5ea55c8fe668a65f2aa47f3acf556a0c133f","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f182d1b29fa097c2ec51308270be7a73da424c68","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.77-renovate-non-major-46be5ea55c8fe668a65f2aa47f3acf556a0c133f.tgz","fileCount":94,"integrity":"sha512-hydzGMYxlZnjphdvs6XyQCBNbOtd2Pu+H/qEvYTmw+znO5OShwNHo8W2qjHM0a2MHYuFswQqf+L7yBvd7EZjAQ==","signatures":[{"sig":"MEQCIEFWm52BREjh9VrQrVb82N6pLVDRfHnNmy5PWm8aJd+/AiBsDgZDU4CTx1YXLNsYauCBWovNgvZEcvk0B27rldCMVA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":568792,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfmPK7CRA9TVsSAnZWagAAFEEQAI1Be3yGycouA/V5yf7U\nfVQvFfUeHS8ZQxFejLsmb91UuE/uwYgVjx6f8VINtlWhMxf6qHyJwprbDKtZ\nUPnG9ba/jNjd1P8jVx8z+O46C5GbFpU/JNIu8hXiwvqE+btCAXr/B126tz/y\nZNZkkDZjt8q6H6T5BBxtEP/KQFqifCiR6y/q5fsi11ONv2rEql0DIPD+y6jS\nqaAhPtDS3G+gG07nYKHSKQLiss386dcMyeMdNOUdKiYeZvFhDFVk3JnFpTZ8\nS8y0hBmirxerZbj/fLV0fECT69Cw1KXlXZeMvSN09+WlSp8VAyq+EY0u2N9G\nUHvg1HKhi4i3GOM4MS5e2rWR/VUFlvlWRBGHdHh6vSm3jWvcABSgCF4p15qS\n7IXwYpzm9DusXfwvvzqZP5uPekP++eySuiienlz7ex/Dg8ypaJsRIwXnOiEg\nJNrW3Q3FwXxhda9ZODV1+F7eYNxyWjgIrKEf8mI0WGX2WVe+3B2BgdiYdiAX\nDuIX4wm9PsdhTLW2BYltrQub2rXHUeRmbhgXySWmFBlY2Rg9fzwir52fgWC5\nIuNl8xF8CqryWZChwD2AvFudSD6UHSNvWA0M1CEjtzUTbq/uyffZ0dC5iKXS\n2rWapWUP1GqCuLUi8rwi16f+iGm07qozkfFG+sJr0/2SKx5sOJwyRe7YqW4g\nUh9g\r\n=XHYc\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"46be5ea55c8fe668a65f2aa47f3acf556a0c133f","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.114","@balena/jellyfish-metrics":"0.0.130","@balena/jellyfish-environment":"^2.2.39"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.37","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^15.0.1","eslint-plugin-standard":"^4.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.77-renovate-non-major-46be5ea55c8fe668a65f2aa47f3acf556a0c133f_1603859130486_0.28087422858240574","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.77-renovate-non-major-60790510447e728235c6381046c687b4ed759361":{"name":"@balena/jellyfish-core","version":"0.1.77-renovate-non-major-60790510447e728235c6381046c687b4ed759361","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.77-renovate-non-major-60790510447e728235c6381046c687b4ed759361","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2323a2390856372c5e3712a4bbfb23ee9a94bafe","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.77-renovate-non-major-60790510447e728235c6381046c687b4ed759361.tgz","fileCount":94,"integrity":"sha512-hIfBdIdVdblSVyAUINUXmMqIHcPEof1x/ww1dw0t89/Q24eOGg/eQZ03wRdR2xLbvybLzVc0Z0X6QPy3dOiQMw==","signatures":[{"sig":"MEQCIH2kogzqY4dTAdl2LfXzK9XaB25qgrMNSOi5PaVQ7XzsAiAl2+K5RLBP1AcsHsThSApPLJDyVZWLljfNeC6pIFLumg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":568792,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfmQGpCRA9TVsSAnZWagAA1uMP/1qhdX0zrOwl/v0Shok/\nLiVHosXxOUov/rog4ujRU+5lwk9czzDmdufwyC7NxKZF7zojGcua9/Ycv1If\nKS8qE1CHzW9DL7B04NyIK+RyApN34h3i3iX1yAjKsc7Sd90YXPeIvZbsJjZS\n55d9j46AotHBh93KceaKEZyfQJHeBrcLkMTbcqKGE0CjSYL2whh+Fws0xlF8\npPRrtV2lFDQYsl8XI6x+eHOguT3x5lAncNokHZxzmLVt7Npqwn0qRdY6IBmP\nq5Gt6op8o2arFzzC6mTAv0lYkvX02x4Zhin1eIcD2D8yJhDc/exQnaTpfg3H\nHl+HfYeW5CJfJRtN8aPogN3QgnToop8YAgpnXsSfNtvi9+bA/dNfUDZlf0RN\n0EICNZeIV8yw4PFxDMISYVS6IjC+m3OmWCjLUMKWpq5UIXP7fzv55cfx9w1m\nKSRf824nptoHjYMCLDYymy4hIq5aJWeBMjw3DFgQLhvS64aWGWCMvFHJScH2\npI60vRKguEPeDrk9rhO6VzB8EUO2rz1cQnEJ3EBn0p9VM2yeA2QsvuemQwD0\nlH1gBQtXsuTxjCnYIIWjgoLXWPOXrkLoqeZ1oILbv7RHcE4j0mJTjzZjzDiH\nbMHYPEwGvgjH5r7CXa1WEWR1Wufxgr10hiPL8b9uYnsUTeuDwiQqv8vYKG6h\njHlH\r\n=Rbmy\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"60790510447e728235c6381046c687b4ed759361","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.114","@balena/jellyfish-metrics":"0.0.130","@balena/jellyfish-environment":"^2.2.39"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.38","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^15.0.1","eslint-plugin-standard":"^4.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.77-renovate-non-major-60790510447e728235c6381046c687b4ed759361_1603862953366_0.5467662378066642","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.77-renovate-non-major-154cbc01a07110a307cc18563e7a4b019bf220b5":{"name":"@balena/jellyfish-core","version":"0.1.77-renovate-non-major-154cbc01a07110a307cc18563e7a4b019bf220b5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.77-renovate-non-major-154cbc01a07110a307cc18563e7a4b019bf220b5","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6ee4604fe9d1853b72b5929320623feeb087b706","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.77-renovate-non-major-154cbc01a07110a307cc18563e7a4b019bf220b5.tgz","fileCount":94,"integrity":"sha512-0tuflqKvd/JofuXkqQtnKhx6XlatEWEp07lKPkSEz2b0cTTShK+Lm4N6ryCUpxNkON8/UI9SvYRHs3u6uJjymA==","signatures":[{"sig":"MEYCIQDqS079zTO1S3eoPI9xFZzEUq7hHAolSU5Lj+Kg5pUMrwIhAOrrduFdZ45YknjSeA5i6KtfAzNg+EuU5HM7xVnbGTQl","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":568792,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfmT63CRA9TVsSAnZWagAAI6QQAIpT8SWzMd1YDZLboH2C\niDBD/tmhggnZyaPN/w90FaMqHQTCmrHWGOR7MDYgiLnB9P/VyFtpJA+GHNF9\nRhOzM3iSpq7+ucxRIf2Na69NQCpJsRAqBpTxZXi75PBgQTMF1XuGyvhpJr76\nCcJvuBXeWH+yIZmqKPmmkaPwpCk/UgZcJ10pGnaRupjAGF5amPWcQO/893Vu\npYWCfoRcMFjeCqbLv4P7SOgUM0NEWwQAtXWrECNb8rOCD0r8NWfbz/FBEmzh\nRqOXwqKPynwdlkecyG2CbmA5F32sSuQksQk4JlGT7yCOT/FE28PjwPqj9oFm\nBadh39XGDVvb1ApycdUUuPur/zj1boXIFonOL+S9JbOLG+TFPzheVpKcR1Zv\njeNMEx4oPafaLy4qZJ5ZNxyhsFO6y9IHA4uFtz79jrTSucVKhhsY/8r9ZOEx\n2vuqEtQ4UldSfPXfXykBDCNMt7jq9YoD11fOxpKUHV0WIzYCMFSB1Tuy4wg2\nN9Lwpmiw9FQqScHIeHTSxGT88Wjzj5huY3fTLjB3ScboW86BvtIYuG4cXGnM\ngJzxa853Wv64jQuuKjbmY1yaYJK2cZg6WtnPv+45lNb8GAVvpzk72ii/1RtJ\nmt5lyRNOUoDvldX/dhnTtLa48kFOQJQYH0438BN7uVwJCyi8bHsM+RPqxiSi\n1PZg\r\n=jAXy\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"154cbc01a07110a307cc18563e7a4b019bf220b5","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.17","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.16","@balena/jellyfish-logger":"0.0.114","@balena/jellyfish-metrics":"0.0.130","@balena/jellyfish-environment":"^2.2.40"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.38","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^15.0.1","eslint-plugin-standard":"^4.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.77-renovate-non-major-154cbc01a07110a307cc18563e7a4b019bf220b5_1603878583012_0.15282779912771982","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.77-renovate-pruneStaleBranches-88159df60fba9ff483c5ae1a438fbf9392fce800":{"name":"@balena/jellyfish-core","version":"0.1.77-renovate-pruneStaleBranches-88159df60fba9ff483c5ae1a438fbf9392fce800","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.77-renovate-pruneStaleBranches-88159df60fba9ff483c5ae1a438fbf9392fce800","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ef6046aaa0d9387a3dd41c06964a62e1193b82ae","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.77-renovate-pruneStaleBranches-88159df60fba9ff483c5ae1a438fbf9392fce800.tgz","fileCount":94,"integrity":"sha512-fh4v7kbuWXQ8Y3+KLO+PGfWrdkckylPuNQOl3ON1OwHP9Vedv2LNQ/fvqaRx5rsFjXXUGh15UEJQQK6+x3bhbQ==","signatures":[{"sig":"MEMCIGM43+3xM+z0jYijRmGt8En/h2ILrGciz5iPs4aDCOfGAh98Lt84vzMpG9WrkH3xPT9NlhUy0mxYE3T8l7AG4/Lo","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":568862,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfmVz5CRA9TVsSAnZWagAAT8kP/1JvJgcEiDSxKiJQQnob\n3MKf7Kfta3o9ARbtSXhadQ9nHjwRiVdNL1LcuiLRYESyTWTdYoNs6FgoiOca\nIC128nfISYzJNorznirDg6YPRzKZdIpfK5eOudD3UoH7POOdbho0oBA7RB31\nVeiU103bl8u8dXBFOt9WVgpE53z0qtx9DJiWvqYf4At1vP2zFN+DZ7ssryJa\nAnJI4mVdvqKVlj49rxNBA/1h0DSxdYjroqm0SUJlYnrS4Fs2c1eZhleAhGPe\n+YZwKhe0TTf/BmKVW5Jm6AytQ0ac/242Ocf2oEhcvs3PKCtLBONKSfIndaFo\n05Yi7sGy2pXIFjyB3dLbvxjP3Er88t683k6V07g/7ghIgDYR9g0XbEtf8K6S\nV3iTtqLDYOdaEexbFOqC1KmlxTE39L1iaNIGTkh4rRReBJEG8KJJQ0lz+KHq\nZsf5t9LvDTuwY0OpbV6WdxBUAo4XnHdQ1I62tDk0KqaC8j10LqJ+ryZSzrFx\n22Jq/sNYT01MtNfSjOOQ0vGK0vl3h4alCDG18sxPflAPG2ZViJPPYIaVcsrL\n6OX9+HplPYqOzA0H759gdZSnDMR/A3vkfFfrqKxZq3+c8Kf0XJq5gGMwlf46\ngt3uP4IsRtrauzxeMX3Uko62MgyhBDSZeylPWJzinhn+GyBSEsMq9jYRLJxG\nm4bL\r\n=FrUI\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"88159df60fba9ff483c5ae1a438fbf9392fce800","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.0","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.114","@balena/jellyfish-metrics":"0.0.130","@balena/jellyfish-environment":"^2.2.39"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.2","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.36","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^15.0.0","eslint-plugin-standard":"^4.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.77-renovate-pruneStaleBranches-88159df60fba9ff483c5ae1a438fbf9392fce800_1603886329303_0.36623670505122874","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.77":{"name":"@balena/jellyfish-core","version":"0.1.77","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.77","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3719b7ff02936084979c9fe41ab051fafcfdf858","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.77.tgz","fileCount":94,"integrity":"sha512-9Lurd5IMQ6Dcu69FZ9vHIhUBLivobqbWXz45D5enPRaaa1WsG5D7YO896SnwiH/G12Jp5p9k29exqJzoazQE7w==","signatures":[{"sig":"MEUCIQCPiVqJt+z9Okro5oo21tmXKi6pWfQTQ2WEyIM0id6tSQIgJeYMr2ZjHTwkZa5/2DTMOBzc6F//UjIOgThSMb6UJOU=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":568793,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfmV9ZCRA9TVsSAnZWagAAtSYP/RLPvMmR9hi07+G2VBt4\nX4lD4ISOImNhKclB5wVEQ7bZ0FOWJv5o4tbaLuH69qkSiREZ9cfXCAIutueI\n6D11ykWYzI+m2HVwckQnq9tLMePfFK37/oliYiukEd1foQr5js9YSgLXNFgh\nbbMAyYl1YnrLaL3mOsoMYCcwehJwHlIkQzMcRulF6sRI7sA+/rCySQ1eBbMx\nqUVMzawBcJGfH85bJQ1PC1MJRqoKltSUzctvSvwAsDDitpBlCSiIORrg8p0+\nioEiEy+TumvemM4rW0HSdpwP0zd+hHc7R2L8ezxs0rrLaySEp7f4Zoa0fV3u\nx0cC42xWG7PzjlVy/WevU8FnDbEYucbn3fAAcrZM8lEdQC83PVlAxaRzT8Fe\nQwIpZqRrCAYUbAx2Ew3Qh/k5OSzle93y+pt9jGz9M/31KXeNqK/kUyx9azCG\nKW03ctEqHQhBkqbwOdZGtPdZudDDMvqwTW7bhnfx9I3XQdN0tWUSodHSrt3O\n7mGf7j+GI/oEdJj37NrmDtJt6c0bXW5KkmleSGy/3k/MnD51JHYImWRE7/gc\nDt8uiOJ27iJi0Xx2WVqogpTc8E27JJEAucaoWxx/1E8WUNeGodL2hxgXJaqd\nDmJ/Zr1j4o7VUvdX7OH9t7GcDQPoh6isIxSJ24iL+OSwCS69m7eXir8zsKL3\npsbB\r\n=nnaz\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"689bf7d15d9d434362c7775060f141486ca43180","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.0","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.16","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.15","@balena/jellyfish-logger":"0.0.114","@balena/jellyfish-metrics":"0.0.130","@balena/jellyfish-environment":"^2.2.39"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.4.2","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.36","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^15.0.0","eslint-plugin-standard":"^4.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.77_1603886937281_0.17778884892112368","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.78-renovate-non-major-62530994013102770bac423aa96bb2710fceffb1":{"name":"@balena/jellyfish-core","version":"0.1.78-renovate-non-major-62530994013102770bac423aa96bb2710fceffb1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.78-renovate-non-major-62530994013102770bac423aa96bb2710fceffb1","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b6ebca8c01466273999af4181f6ce370b4467198","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.78-renovate-non-major-62530994013102770bac423aa96bb2710fceffb1.tgz","fileCount":94,"integrity":"sha512-8pWdssGRb7WhQQ+Rofz6M+eO0X/s8jvQQxbExzrRg6HNqvYtaUBVmJ/xEjIarJnb7ufm3cmS+bSuRoDKlvWPlw==","signatures":[{"sig":"MEYCIQC4MUraLLlpi05HX2UaJFYrG6l/dGFJvqJy5y4t3d6HWAIhAI9uwzgRJxEqiH/CJy5nycmgc2MNm7j/RXh/yHPWZFgV","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":568919,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfmWHVCRA9TVsSAnZWagAA3kkP/3qFw2uvRyq559ZHLe0d\nMZZ0qaOPCGtso7EwaQ9clFfuBpa2pOeveIG3qXux6sK2u35Xf7x+Vjo0s7lq\n/M2xVwVapeyodUE9SoKwQO92Z5TX2yzgTxiuqxpvyUJntEgRXNCIpQuaYz7g\n9J95sr/7beG18YvE8yqPPAaDnlVfRfuAJ3cFbhkLlbg/hVbtVDJBAxLjTBID\nvZvh/KRx8fhgyifc5sYtpkBWXrvv3BIuTs/khxqg39C1N//myCa1s7gH4p/k\noHYFO071mS0npI8VfpGske7VqokWLS1aYFZ1V12larxUDLxWXB/4HAV+qwrn\nBYtBZvNRACqVCxKuPrNqJeeU2cTMoHlfKOmubd4EH20mVfTAGg5TbscqcNmN\nqRW6fNfmUUFf/mcOoN+014dr3zFt/uu+VsRLHfpJwxT77dJEzICasjvHBN04\nSdHjHqboQZNpEgR7v0HX2iYaPUga1TaY0k+uIXjZY6hUMjvNu+soL2MVAvfj\namiGKDknxXmKiMOx78PB84JeeNHVA+FzzF7wSTWOh6SQRS+BnskH7uNbvm1R\nUtZYx4YuCyOgHnlNO3W3Qp3+lrQblciTKAZMdJ2spFln6U5Pud1eDkv2oJAT\nImvJOEFvyoW3HtNZqaTe1RsFxCBNC5BStujfQGiF4bAbZFbj4jHrzNjT8wwD\nBAJr\r\n=Hjat\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"62530994013102770bac423aa96bb2710fceffb1","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.17","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.16","@balena/jellyfish-logger":"0.0.114","@balena/jellyfish-metrics":"0.0.131","@balena/jellyfish-environment":"^2.2.41"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.38","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^15.0.1","eslint-plugin-standard":"^4.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.78-renovate-non-major-62530994013102770bac423aa96bb2710fceffb1_1603887573227_0.9974418115466994","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.78-renovate-non-major-ca25894325e5bc71e4305dbc12557ffe15a2ccc2":{"name":"@balena/jellyfish-core","version":"0.1.78-renovate-non-major-ca25894325e5bc71e4305dbc12557ffe15a2ccc2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.78-renovate-non-major-ca25894325e5bc71e4305dbc12557ffe15a2ccc2","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"670136fd0a88f92550c6d1c6871a38e4f6a85b48","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.78-renovate-non-major-ca25894325e5bc71e4305dbc12557ffe15a2ccc2.tgz","fileCount":94,"integrity":"sha512-PyEj1mXCt6zfzF6MY2Re6yNhvAEMKrNrvKhIGI6ohd8VaLvHDaq3+VKsdE+0oG/t2auR+XkCaaIfsJpFYkyp9A==","signatures":[{"sig":"MEUCIQDo+MvstKIhbjUhY9gI9tRNcNnQrEh2Vq3Digyv5/+79wIgQvNPb8KEEKmUeGE0ibTzK693FyswDP+wp+unNOZKLPs=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":568919,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfmW6MCRA9TVsSAnZWagAAGVoQAIjNS5UFyrECKGqw3Tai\nkscLEHG5u8ytTJpDMsMi7FXMSJ8873Z2PYuYBZJnkLvLyEyu7aGvQHOVrjmh\nn9OGhbCgAFYnH8Ag1PwwWh3jcWD2DUtW6z24RH8BtKyc1G6leZ33re8G0opR\ncuGBEOml+LsGE0DBspvg/i57pNmILQ3YJ3hF1K/x5omH6mnatPFfMjdXyu6l\nL1M8TSMpASQ1qEcGyzVnU/gSqHJVJbkegfIKHkDGSEXP0AoUSLj6ty9wj4fR\nRLh9wJaIT5wLhF3Nk62bSJAegdrRu0RHlJBVfR8vXBJ+84m5CekC6VcuyvUK\nbN+NNaAc/iFgyLEBxSJm6jh1ME3Wa27277Vwsu7yU+ITccuQdDBkURE4+7E/\nvSvBYgKyo7Gw/S2iwv9FFSuv/4FU+M9eiXdCs9ZZ8XKjx687HqGDQ4lTbLbF\nbcW28kOH6bBFwbPpfykavwyDMvRyWnpfPCU+RrgNMZDH5dSR5Yopopv86ufw\nzlPvTvTJRJqT+TEKILMSxHIsTt82C8XaPzMpT9iYvScXSAQmoTPK7iCrfMgL\nK9P8k9IWfVK5AqueVU6JFDsC17U4lfyRSYreoVXkErhiymNWA7MtFfVn9fpK\ncPBztC2fZGNktXGyKlD1s0VCddTCE2fiKcbAhMGBWq2i9b+X3QjkzjFIJf7T\nUWu/\r\n=UgXl\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"ca25894325e5bc71e4305dbc12557ffe15a2ccc2","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.18","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.17","@balena/jellyfish-logger":"0.0.115","@balena/jellyfish-metrics":"0.0.131","@balena/jellyfish-environment":"^2.2.41"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.38","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^15.0.1","eslint-plugin-standard":"^4.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.78-renovate-non-major-ca25894325e5bc71e4305dbc12557ffe15a2ccc2_1603890828167_0.9217305055160805","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.78-renovate-non-major-21224f99d207a580b4fb93dd24633998e39b66f0":{"name":"@balena/jellyfish-core","version":"0.1.78-renovate-non-major-21224f99d207a580b4fb93dd24633998e39b66f0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.78-renovate-non-major-21224f99d207a580b4fb93dd24633998e39b66f0","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"7062e0b10b3a3dc1a0538aadcb986011b1f3cb46","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.78-renovate-non-major-21224f99d207a580b4fb93dd24633998e39b66f0.tgz","fileCount":94,"integrity":"sha512-2e/jgaHd7js2Ra4UV9flGj0oWaAL8fVvBFEeqkJWwa4BT2ZEqEj8RRkHznzqPsrVYFzSnJeJXsqEFrm/s9IlwA==","signatures":[{"sig":"MEQCIHc21KSNA9oy7pT/0gBWFFb7t0ICFtx87eU/tRKNzK2XAiAf6BsNpeEGe4uCkHqBJAA1rmi2m3+ovfE6pF+206g0Iw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":568919,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfmX7QCRA9TVsSAnZWagAAFe0P+QD350lzZUZdyzPiXe1i\nhlAi+RqGLjYZn+2HyEcreOcROvkMUYE8lMyNmLh+58dYpl+7vDGo+JMrSNRM\nC6WdM1J0cgdAAvvPE0ruhPmchLIsaL8rRXh0u+jA8L8zLTMaVrp3jzwX4c+G\nggGY2dr6kHGt8r53oGCWkxsCRewQo2ut/ezQ2N14uwN5/1tW2U8j5iRYnKl9\nDdsuhf+tjayCHQ7HgskiEcOHK0KuCihjMu/aIld9Z17bDjg6J9dvqTxERXHI\noD4/p60qsQpm9kz6uVlY4i2ypYw+O6uxaDSRGFFTKmTCNyrY7KCHawNCvJ2D\n2kYvhe5yPd8kmNyshwNuVUsJ7FU/cPL05yqhOlHX/JDxKAle/QcV/nyHoB15\nKuvxSHS6k5VTQGRivO61JEsP9qpQOUHIqscv2m3yBvQ1sNbxnFbAAL1sxJh3\nvHVVvSlGm03XAShVbtZ9XEjqmBU7bcO17Je41yX2ZTSsCSkFdFhDfqOt4RB9\nNj6PplZ3MoBDFEOUx3rT9R+t8ws4L3crraI/Xf3JnT6vzEMSgHKFZzOhQIy+\nPbJMa4ZtMgVk8NDSqX+PpLVHO+hqQcmISp8MIrdZZTYaC+er1X6Ymj/7jwXT\n6MYWUJfzLRFut2DfgPytSq445/+T0r9XIlnLWYgOGbgvezHE/Hd6mijDqQZ7\nzJXC\r\n=1xHW\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"21224f99d207a580b4fb93dd24633998e39b66f0","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.18","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.17","@balena/jellyfish-logger":"0.0.116","@balena/jellyfish-metrics":"0.0.132","@balena/jellyfish-environment":"^2.2.41"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.38","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^15.0.1","eslint-plugin-standard":"^4.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.78-renovate-non-major-21224f99d207a580b4fb93dd24633998e39b66f0_1603894992036_0.4366165517184706","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.78-renovate-non-major-1e56df2dac974197ce0c036850ce4d30dbe22007":{"name":"@balena/jellyfish-core","version":"0.1.78-renovate-non-major-1e56df2dac974197ce0c036850ce4d30dbe22007","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.78-renovate-non-major-1e56df2dac974197ce0c036850ce4d30dbe22007","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e3edeb88e3a56f03b3adc9a33787e0a12922aee2","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.78-renovate-non-major-1e56df2dac974197ce0c036850ce4d30dbe22007.tgz","fileCount":94,"integrity":"sha512-Susx4jc2a+u8VHx80tQC1xt4FY3dkERci8XRip+Bj1jhRcY9ddpkFVjgt/qwzAo/xcDk4F8G1QBHuU3vmMr0gg==","signatures":[{"sig":"MEQCIHnx/DgZGQnEf27p14oz3knF+d68gqZjFyn78VggOdFeAiBcrO83C605sH958A3+FduNTJR30OT+KzomIL2d6ETpbg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":568919,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfmdD0CRA9TVsSAnZWagAAnXAP/Rvpi4p+SqEDHC3Smx4C\nD1C0uFmOf0ndmSZP1DIf+sLZe7K/qoFqS+RjqKgPiIHoHJt+G9BZBjMKI915\no1Qh9kRYdCWzp6ndWo7nug/tWVr5ScqX7hdnb25gvz+2vkJN3nn83Ubgnl8h\nTgiaKYlP1yBkDeyZEWlAeT7mXROLwEEE2JFt85Gbu5eeXX5I42NMtuHwhcB1\nH2xwufyuEfXLFW9GYQ376wVGkqbDA4QWB2+CWrB8gHorT1AXSdeGMJNPkaaj\nbEkhWxACfBInwH8aSDmy7GnwUjRKmxPXXMlVbhF6KHympSaOlfm4y4usxFb9\nkpPPGCIeGhsYKRsB/bty/t7gA8RBc3PToyGyxIVibeLACKIaKFmgJtC7a3lB\n77S2NZ13a9iPYTa9mS4VnwTWEG2CvU6ctn3TS8OE35NO/ILH9/HY/ZTS5Nf5\n3uofu+pkSCFFbuenPZkvm0A2cjiDXCWTZrYXrg5YsHAPO6eTPLxlRFyN9/rY\nssW9FgaaxbSQcw1UoQryf3avmQ3T6oBHDah36rtVv4cr9wUYxuWD4LfLmEUM\nVR4FzvgD25nNQWtGhzr8oSwkiWEFKhSJsp/yIMiOlLvsDp6iVUX9XL/wcA2B\n3x1j/YeW1gkN5uXqBAdSeddPXNLljqVaiiIMTwvUfnVKnm0Oed7RCSBCu8vW\ne9/e\r\n=Entb\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"1e56df2dac974197ce0c036850ce4d30dbe22007","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.19","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.18","@balena/jellyfish-logger":"0.0.116","@balena/jellyfish-metrics":"0.0.132","@balena/jellyfish-environment":"^2.2.41"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.38","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^15.0.1","eslint-plugin-standard":"^4.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.78-renovate-non-major-1e56df2dac974197ce0c036850ce4d30dbe22007_1603916019970_0.74640516795238","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.78-renovate-non-major-913a6e82a62b4559f3f9f23b5034de3840b4988c":{"name":"@balena/jellyfish-core","version":"0.1.78-renovate-non-major-913a6e82a62b4559f3f9f23b5034de3840b4988c","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.78-renovate-non-major-913a6e82a62b4559f3f9f23b5034de3840b4988c","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0d6d1e39e58f4e31fbda91fd38dc1092eb8f8325","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.78-renovate-non-major-913a6e82a62b4559f3f9f23b5034de3840b4988c.tgz","fileCount":94,"integrity":"sha512-yj8DGaBrpgh8oCIf2EHjR1D6L5JrTCeGcet80fsKtarz7DBUCTCKPexNSrHiKdSZufDLTsw2dcNP8x0KVfto3g==","signatures":[{"sig":"MEQCIHByaoXLDK414+zNuHAMVeT2k/0oxh7Or7LJPc9i4TAZAiBHaGA9xmeRDfNW2z33u4lLBT5VkOyQbPVadVFPfwpa/g==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":568919,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfmeYyCRA9TVsSAnZWagAAlFkP/1HW6M/r2r6WfNsvxzzF\n0N1+nlPipI2Bvcng6CPzqY+e/NZzdV6a3xeBhMPi4f1X8PP8cGHW7W6yQL0+\nT82BlyNMxzxPVCYIrf8N3VB48Yh8rmU05Wh088nNUBuA5AB6j9T30MLnPiCJ\naL7R0W4wMp07xg+Rt2/zZfqE0frdFv/Cu/RC5s1hwp9675nDi84ZUHzsk23K\nR5Ua3jXaW8Im9MGgSzJ0zNSxl7waKLPiPqs2mKGpm6FIxtdxdHI0u3CvobQD\nLWiUVIX8F9zjxWWu0ZiOt8YSv3kd/mQVjqmMdf7dTkJP6LMPXUPEOr8YIbb3\n1mN13DIXxnmTgJMTcbJ8gUJQnmyf3s54uw1efgXignk7JXVkr9ZqEk2Mbzbe\nsaIqXPHs6AxY7mRYQ+y+kiMUs8wzKZsC5QBZwKJYpkyoCiDsVFvy8diKVHZa\ncW7KHEoDnRh/eND9SqUYaouhcP+ivpJony/q+8ESgfwD/O9PhOVKIDJ1jg5Q\nVXOKMXyeFbaTQFw3gcr7xVhl2B5STDfDkz1gaVB2f7BwKZVWxmz4C8209kEy\n8aA7qFLDe2eJpKBf1ueFvHHB6rddC7eIfyuKaw2XgEv2D5EY+uh5ktu4sDbC\naOMDurolMGPVwwKOq4q2n/kDMGUhAkDFCoi4/mNgUiyMry+0R2bWJey+dtc4\ngNUy\r\n=waNS\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"913a6e82a62b4559f3f9f23b5034de3840b4988c","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.19","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.18","@balena/jellyfish-logger":"0.0.117","@balena/jellyfish-metrics":"0.0.132","@balena/jellyfish-environment":"^2.2.41"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.38","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^15.0.1","eslint-plugin-standard":"^4.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.78-renovate-non-major-913a6e82a62b4559f3f9f23b5034de3840b4988c_1603921458117_0.042712201075949974","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.78-renovate-non-major-4cf1bff3f0eea83ce88be237f463b6adda757f62":{"name":"@balena/jellyfish-core","version":"0.1.78-renovate-non-major-4cf1bff3f0eea83ce88be237f463b6adda757f62","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.78-renovate-non-major-4cf1bff3f0eea83ce88be237f463b6adda757f62","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"16f76bb8b61a96ba22c224285397758daa8dd584","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.78-renovate-non-major-4cf1bff3f0eea83ce88be237f463b6adda757f62.tgz","fileCount":94,"integrity":"sha512-3X474PE1/czfefqMfQYfk8mrsjwbAnO53ipRMwmfB16auoFimCvCh4sUTX0DU+2y8D4b9R5vHYJvUnj14CfW+w==","signatures":[{"sig":"MEQCIAQzgRkuTyv9FCHBn9HLQ4SHdHpi+2ot7PSh8bTsatZOAiB7reNqyri2gOeari48R1i6XjRH/tfqMXxTxSKm/Kdk/Q==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":568919,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfmedUCRA9TVsSAnZWagAApQwQAJO1MdEPny7l8ReUY26Y\nB44j/rzb83mLwB4SR7n+CoHyHQHRUHiNAkyahwwzwGK4q3T7vFEikiU1qftf\n+9lDfjeoWPz2hA82PoSK4LHrzsHJ2q+vJUxT25gwwZPVUmfvdWhdjONyVr0z\n1f4BSZkaqIUfdrDj8H4niIEehI1dwAlkq5gdduHu0pY+OVAphSD3wkD/4Osm\n/6nItHqD7XFjNihnmPJ9io+kT+GVFDfn4S81cY5jtwjgIs0ZqrlT9G5w/6VK\nfFY9ZwIJzgUMqnl9RMRm+RMnnP13JYRekkq82y1ffiYRlz0g53AbH8VFfNLg\nUjdt1hx2YnJ9GJLzvCSmjnJGAiwIeYptKiSDip5/6onPkO2acJc/PDr65kiZ\n2k14sOdauF3G2R+ZsSETbTYkqAhB7/7iFxHj3gMZ0zLb1Yrb/PmHgqFnZQpU\nlZeeaUDWVDxUa388aZP63gyUfOD+MD0448gNH8DfPherFeJ9uEWkVNtbIhiD\nHiOqZ71UxIv5YSsVBtpOPNi/etTDctfduWHbWIGHozxDxN6MWCAoGDElA+R7\nQ3mYGeVOc1+SW8dQQDYynXPwipsI5P8GbZ64bOAPT+X3Zb3ydHzgcUHBOnCp\ndrqppx9j1J4OIVPBkuPoomUkBvyafZEbpr7g6WhFCqZ618r1kngNzyu5oLOT\nCpY2\r\n=y1mU\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"4cf1bff3f0eea83ce88be237f463b6adda757f62","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.19","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.18","@balena/jellyfish-logger":"0.0.117","@balena/jellyfish-metrics":"0.0.133","@balena/jellyfish-environment":"^2.2.41"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.38","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^15.0.1","eslint-plugin-standard":"^4.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.78-renovate-non-major-4cf1bff3f0eea83ce88be237f463b6adda757f62_1603921748423_0.6345006915694915","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.78-bump-dependencies-manually-ab3907691d621f571c2580fbf6adc7e6843a3d13":{"name":"@balena/jellyfish-core","version":"0.1.78-bump-dependencies-manually-ab3907691d621f571c2580fbf6adc7e6843a3d13","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.78-bump-dependencies-manually-ab3907691d621f571c2580fbf6adc7e6843a3d13","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e035c63c0ba9a85e49bbd24aba0a1502363692b1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.78-bump-dependencies-manually-ab3907691d621f571c2580fbf6adc7e6843a3d13.tgz","fileCount":94,"integrity":"sha512-x4si7MyEfegIxBa1K8k+R+bS4Vf0PKfdApxyJmoTEInetNf1uOAQb0GwmKqsSIqxejJWujTYfPz+UxiKGAJXaw==","signatures":[{"sig":"MEYCIQC+fCUoUnKiTz3r0Lblb+9aZ42oWbazHN/KCdh/JZ32tAIhAMoJ7O7QUd+evfw6lUg3HuSpY6QW7Ir1dcpk50ZIvpoj","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":568931,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfmlRFCRA9TVsSAnZWagAAubkP+wb1GI2khMr1E8Y60uF4\n4s+aIkzhXhiH1w+YUPxqdsHpNLtI2rrpKJekQagV9Y13eLo+DdmTH92igeDn\nXiIs/lYrw3nmYXb+oJM4PGvH4B9+VNkEy/did6x/crySwuKW3sUxldCdZorw\nHRcdI94wGsG0HWe74kfoHtsxhpEl+6Hz9Hohw2mzauEOajwl6NHMYpbfRbQd\n5mqeCbucjOguV8Br2UtNj0FPVPx42E8pK1cjJIlfjIs4zp0sxG9/JoNuOI/h\nMUUTTSGESSzMUrN4yOM7hHhbTMFxlgEuhvIduuYn45eGYtpjl1nBD+VFcs4y\nXB+X81KGus7bitWDjicO8XWgLWPpIJeH7ErXRf8raj2VLdUSGG9dcQUGTIb6\nBfHs4xKR5pHB52H4OI4QzAHbS1Rf5/xnX8llGZb3BHUZ5ifWH5/RJ9xHlkWs\ni6WIAaIFdAw+fiVqx6YWACaTbj69+Y459ex1TTCs8EJTncqZOq4CLOEVCRkX\n9XMYoe0BfGfSQPyDUxSsNjRjDEdReocgNlXDwZ2Cshm/ELDJlx0lUxU17SBM\nvRPH2IGrs0S2KI0b9/rs20V1+pCUlYUHQHh9kVte3ul+d4xr/1wr/hgw/P3p\nKRTCQ9CzGugbEQdI7/S0Ed++Oz//oHSLvkIqOOU1q+uMjiflSMhQ2dIvWibz\n812L\r\n=aTtv\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"ab3907691d621f571c2580fbf6adc7e6843a3d13","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.19","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.18","@balena/jellyfish-logger":"0.0.117","@balena/jellyfish-metrics":"0.0.133","@balena/jellyfish-environment":"^2.2.41"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.38","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^15.0.1","eslint-plugin-standard":"^4.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.78-bump-dependencies-manually-ab3907691d621f571c2580fbf6adc7e6843a3d13_1603949637537_0.9376933635723981","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.78":{"name":"@balena/jellyfish-core","version":"0.1.78","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.78","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b6415284586dd04da642cd759f13a3c1eaa181ff","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.78.tgz","fileCount":94,"integrity":"sha512-yT8Tu5LNJWKCey8etFmc6gXJqy0hHqqvHaT9jRofYWDHbjJcaMEq+hJVoeAL3C9BFUoC2En9M4ZwmyGu9NK1Zg==","signatures":[{"sig":"MEUCIQD57Cd9ETT1kAixWVtzl29z2/IMeVuZuVgmNpxL4006IAIgBfafTKqhFlhyCHhYMgonF5U48hHK91bKj5CgT3FJ80M=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":568863,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfmlW0CRA9TVsSAnZWagAAfyoP/1j/1Gkbj+2LC9SX/58B\naSdUBQu+aC5p+PLnh9aMYyPZl7pbsm3jrsFX1UzYLupcn0Aoy8EiT5NN8bx2\nFrhZnfvjgW40qtKtLSS5pOn9n/77OoCoUULa3G1DJhUpH9ZIZyspuJ/oI3Nr\nfjNA14+CZzQ2lCGH+QJIHU6v3DjNcgXVLkkbRum9fuD5s9fcqrlz2pLQWMki\nv1Mz2cpHVf/6fygPNWZzKe56XZt1TfYw9MoS3o6FX6bh2xAVvH9iQnzPXbL9\nPf31RM4/ogRJkHvj/a5oU5TR93FOQZASyqTxaAc3NcxpR9l+OrcH4NUsnxYd\nzxoVnwZqGDeQIyqDqpeMWP7WlpPWNWJ+Yi4WLSKjY97KXrMJ0rqdUoB1q0pd\nuNbMBJ6fd+ipfoiYtAnPsVOR5Rx5J0GIklDzeRq5GjdhuL71DYWsiDQtNa2q\n8JSIrCJsjJjhxLKH9wfWjgQ8VyqxmjoRPhoMw/9mVBNlOgkqHeHO094XI/F1\n/rTv5uMIX3LtcYrmHYvG1xwAbU8oCpH9eiGGAjBvuSz8p+wVFlUuZ88ZzAzB\nbmX5lt7NA9nlvvJbeUS5svBDXwTuSY0pJe1V5pc9X85Z9HkWT98U6bAqLZr8\nJH+rF1f8iLvWD6Tl7aYO8Nu6lgPLlkGY6w90M7H82QPvmCYf3yXasi6rbHm+\nutum\r\n=HMdn\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"467348f5f9d96dcd6d4c85294fc53c3c0704bc0c","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.19","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.18","@balena/jellyfish-logger":"0.0.117","@balena/jellyfish-metrics":"0.0.133","@balena/jellyfish-environment":"^2.2.41"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.38","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^15.0.1","eslint-plugin-standard":"^4.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.78_1603950004191_0.8452859923626665","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.79-renovate-eslint-config-standard-16-x-8acc12ba8d8389bce749d22f89a273157b32e18a":{"name":"@balena/jellyfish-core","version":"0.1.79-renovate-eslint-config-standard-16-x-8acc12ba8d8389bce749d22f89a273157b32e18a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.79-renovate-eslint-config-standard-16-x-8acc12ba8d8389bce749d22f89a273157b32e18a","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4b61a07d4e22239ea87038c5757a2afac3b96cf4","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.79-renovate-eslint-config-standard-16-x-8acc12ba8d8389bce749d22f89a273157b32e18a.tgz","fileCount":94,"integrity":"sha512-VbaP7IJrlQ+2cgM4Xhm/AGipHtwNUQsi4N9j+3Nk8JS7jqrL5yEF+wLUTd1u7D3xUsH+ohhPwIc8QsD6eGxPKw==","signatures":[{"sig":"MEUCIQCLxAqXRtg/4usR5aOtOQLnUozurcTXBHnGPgsdQV64HwIgFpALciw60jMg4/C78cKVIK8ZkSBNdiA437cjGooza+g=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":569038,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfmmEhCRA9TVsSAnZWagAAH/4P+QGEM3XTjsHunQ848PwN\n/pc8DiGu5PXiEiEP1xxQfuQUEyZ700M6/mU2tEMoUJ4W+++lkBC5jgjSdum/\nQmp6gt1zig3AEpsx22mt+l8okRgGlIKEWs1wqq4Tph/wWf0Tu0sK9gtbtsVk\n9h8aFAKHSoPWa87OwEnFkRcMK+qPRxpgnlzVnMnT2cWTIrGdRe18uEXamMOl\nfFZLkva7MYv8GPuugbF9XHrwwBZfMUF7ojGJNsDeT5ecHQo/V/ZE3YPn2WuP\nKFY16eqQIrsHYZCmd/G1WxIlUo0B7NOtgYCsDyvHqUpGT1OHq6YPCh2Ff7l3\nx5aJD/Xm0Z1HEr333TB1mJSxCsnQ3QW6L+HCSiyRtrY6Qh+1J3SUbHMOP29W\ns5g8wJNulFwHk2GGVLB9QhmX4AgDZEQw+iqJ0bpnvxtJxgPXkmZU/dLS7BaO\nMMB7nrbZHqacXpXETNa11HrhX4qjHKcjJutKVVkUOpCuN1HU1pCCeJYz32ss\njWorm9lL8P0u0P4biVHrfyDJ+ZvTw/FkMUp0LMmQ+i5KXtwzNioUwIjFVNgI\nBYVTJ5YKOe1r4f8Cz+UiDABLmQOHFUESHlJ+V9f2758HAitYbu6vHZ1ZYUDc\nUu87rOBJ1bio713Kqsuq3TNO+Go4RBaHvboBPzFVeGsEE5AVKaPUDCsu5gOr\nKB6y\r\n=jjh8\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"8acc12ba8d8389bce749d22f89a273157b32e18a","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.19","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.18","@balena/jellyfish-logger":"0.0.117","@balena/jellyfish-metrics":"0.0.133","@balena/jellyfish-environment":"^2.2.41"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.38","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.0","eslint-plugin-standard":"^4.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.79-renovate-eslint-config-standard-16-x-8acc12ba8d8389bce749d22f89a273157b32e18a_1603952929345_0.46603469472531533","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.79-renovate-non-major-5612d24ae09ab06076eff2f9c796b00f14087847":{"name":"@balena/jellyfish-core","version":"0.1.79-renovate-non-major-5612d24ae09ab06076eff2f9c796b00f14087847","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.79-renovate-non-major-5612d24ae09ab06076eff2f9c796b00f14087847","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4e2e4bc7928999d12414032b560d28e6e2a589a9","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.79-renovate-non-major-5612d24ae09ab06076eff2f9c796b00f14087847.tgz","fileCount":94,"integrity":"sha512-f5i63zuOqLBSbSFrro1weI+J0fwMfrqKOkH6F1kFe4VVWA1uKY4bi0I1GGYNUoGbbOdoe2zfCjedV/4IJp+rDg==","signatures":[{"sig":"MEUCIHiK7VcrPxmFo5vI0QWtz+WsSdzNvp93VM2tOb9SE/O8AiEAslQR6pC93slAXuF6qzQaWE85wF/5p7ONaadSTgTdXhI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":569027,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfmrJYCRA9TVsSAnZWagAAh8cP/0XXurz6F3IAWLz/1LuN\nEvuJk6xO23+9S4amTp4m7dnvZFG0EfBGNuc/oMxGK3urkQW7oQDfx7KHulD0\nXywzhheSo5WKcWcP60kJW7YvP4xiWhIF6vBAPltRLWBWi5wAG+X69VQfZPgC\n0hgLURN/uNxYGqbJslTWLm2wR7bUzUSmb6AXnANSErkgAhUwJck/33FcSHQL\nwv5NPlmICQ0ukuFuiciwwXdZ73UOhk21WdDN6jQFGZPJawlOhsNhDTihd6tY\nAcOw1obzh/bk5sKCyfl6fHh+4+262qeTYsgnku4x/6pgXwFhBT64fIZ6+/Wx\n6tJJ8+9DUKoXkFF6Nlnc762XF4dpbZkjSCYu5UaL8ot3f50eAa6ACnLt/1fF\nAItoPXRs+8GHBflhf2kwAMTqEukgEszvf4rFAIZxbFgMS8TXly1m+TRKYayu\nhuMfSkWbXHHOzfpyC6oqF3owZP1UAcaCm3Y+YWiG7kcn1gZUiOtBVb81vvnp\nCtTRWbBXMdxRfluMD0+JgNpHOkYZ2BLNMRzIDizQ9B26SRGN9QFtLbeqIHPv\nVuvJPKnlkkCnkDR6FdoD8qj535ZuWh+MvAL8WUGbYZJoCWMutsjQz8EigD+1\n+vCMQvq9g+yfiKUjMUpRu+e55TheE8JU4WsFGZbYec8omNg9zQW0iKZqdoM2\nG3gP\r\n=3ZER\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"5612d24ae09ab06076eff2f9c796b00f14087847","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.19","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.18","@balena/jellyfish-logger":"0.0.118","@balena/jellyfish-metrics":"0.0.133","@balena/jellyfish-environment":"^2.2.41"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.38","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^15.0.1","eslint-plugin-standard":"^4.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.79-renovate-non-major-5612d24ae09ab06076eff2f9c796b00f14087847_1603973719720_0.04281920754762414","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.79-renovate-non-major-7308e2f4e33008a9bbe6a4604e440816bda5c2eb":{"name":"@balena/jellyfish-core","version":"0.1.79-renovate-non-major-7308e2f4e33008a9bbe6a4604e440816bda5c2eb","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.79-renovate-non-major-7308e2f4e33008a9bbe6a4604e440816bda5c2eb","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"43c4d2cc4c3f9ddb474ebdfa04547d1e2ac1cfa2","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.79-renovate-non-major-7308e2f4e33008a9bbe6a4604e440816bda5c2eb.tgz","fileCount":94,"integrity":"sha512-tbuX5be813uyjYHQZjorn4cV5zVSt57qGRmmImEstRW9KQ/YvHB5GJF3f7b+aSAwZkBhV7aJFT5Dm1vz6X99Cw==","signatures":[{"sig":"MEQCIDWSDtNI31nKfQrdIw0JVPjQGtXF2Suv/4SwugwMQm8jAiBnzfgkd530kNENAh9vZ+esBaZWzXhK6FJWf97Ex/0HAw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":568989,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfmsQ3CRA9TVsSAnZWagAAc5MP+gKXbi95ufu8KRnIBvB/\nrLHFESj3gwof5zO1+iteFfgP5CvxBuVb02mQ/JgjmlN7qtfNqV5w63tjXMHD\nf0jMK9qVTITvE8XXg1iyeZB+7JzbFOPTngu5q6KFk02+X2WEumArWC3jEPzB\neNFqm3GFiozYU2yNHYNRLkYiwuozCr2rND62PEJ+SiaPR9f/D5hp6jTWQeCj\nbafK+1dMAbARaJnW5xZPYKt21poZSoKGFKL5da4ygZq/YUyjCkfLN981VgkC\nGFjmMKiHFZiHUzJXB1GSPVa+YsmoNHS3jwH9yBn9sQy6sKejzwPkYzE8UORd\nkkgqkCrSU6OZotVopqfcY/DT27F4fwusUR+i8VZkJSC/8O81FC+iawf32DAH\n1/PLwUIsVTv7z7JTl71uTTY+8tfKgVYO08oM5MZ3Iools3o9l+CWAESF/1qT\nJwFXOpuK7E3ANseYHXx3p/pK0Fn1EZq5p1EbK+uacS+bqXkn21JmAreQ6PGC\nXejNhIRwCO6KxHkxyxKvZPHo1XRJIH2NxhIFsQn5lBNRO/YPAXZ7F1wNRLOw\njPADRn2sGTzeTbvfyt9l5zeCZZYXAKjjKhGx/jQI3w/hCmjFtXENtgvIkrMc\nUc2tOP2n6u8UsaAivuFwmoSVHJHQuQA9UQF8T4iLF+6UIz5XuhRobi7y9KtJ\nKK5+\r\n=1d0J\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"7308e2f4e33008a9bbe6a4604e440816bda5c2eb","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.19","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.18","@balena/jellyfish-logger":"0.0.118","@balena/jellyfish-metrics":"0.0.134","@balena/jellyfish-environment":"^2.2.41"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.38","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^15.0.1","eslint-plugin-standard":"^4.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.79-renovate-non-major-7308e2f4e33008a9bbe6a4604e440816bda5c2eb_1603978295186_0.5164706249295088","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.79-renovate-json-schema-deref-sync-digest-9a9e5bc797c16478901cb3a92af78c3faa0fb157":{"name":"@balena/jellyfish-core","version":"0.1.79-renovate-json-schema-deref-sync-digest-9a9e5bc797c16478901cb3a92af78c3faa0fb157","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.79-renovate-json-schema-deref-sync-digest-9a9e5bc797c16478901cb3a92af78c3faa0fb157","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"139562d95b7b0e96a61b6973590735c54efc4153","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.79-renovate-json-schema-deref-sync-digest-9a9e5bc797c16478901cb3a92af78c3faa0fb157.tgz","fileCount":94,"integrity":"sha512-0SvhpVYYl7qD/h3ZhYYQfBYgjXJaEDMU+3NmK7xzxGOJOtNrTAlLneMkw7zjL+47Bqz82abh/rvi/O1muPE4Fg==","signatures":[{"sig":"MEQCIF1/qjyquQ3PSafeGUBRxSApvCr2/46hrZbBfSxRqcemAiBCheA7whWe8yFqLBa6GAFZam4+ncSmFuZpKhaVJ1nRaQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":569045,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfmvQvCRA9TVsSAnZWagAAzKQP/R6DJk5jwb/SV+L1XiiI\n7Ecnexiop5N7wr2/FG8SFglY0nZzBlL8fPeRcg7cXIOGM7+G/cQKVLbrkTMt\nEe7/hWUD4kDqjwWV5f/4+1FW29UYtAqq0P7VpBfpUEM8DIg/kT15r1XtcCRW\niSTpPqpjriJS6+GdlUQocOSJDgztesrFqtAH5OUNXzxOstdP6Nf5nw3SV56Y\nG1CyVr5srqG4z5Uj2e0z4cjRBtDZpMxZsSQuK1on3ig6JHEHQPvIkAnmfh8Z\ns/GevCSbJtdcH7b+/DI0DPvrOGCNztQxpxnHZWGuXoNAhavFB7XsUDjgUvcv\nSmZOKZhD3D8qz3yNRGBH4ncDN65tjZnCt6EnD9jjxjJ4tby7El3m6jTcFx0N\nTE7/PpcUUytIvObQFjkLi2Xj9FyMmtkIIuHeE02G8jFlgJBGg7evj1o2O05f\nAso81igh7by41aDAx7a/8/ogg1BjJfow501wpOoGGVQSvFOvoJGcO9PMhH+W\nD8B1PG7Iu6DoYZGoq9/VFBpTdtHUF0sEXylCVIlDbXRTZupTXBLaRm7oXXoL\nWRlZBYr6OT31KUwZTpn/joP//OftC+JUOVzTQ00DorJ1BxmI8yV52XzVmH4l\nZqsHs4dJQmUyFPqREXoYRnMtK12JCEW3j8bBkB2JRQHdoU0WsjoSLfuFHY5K\nknry\r\n=ra00\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"9a9e5bc797c16478901cb3a92af78c3faa0fb157","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.19","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#c526f8ec340a7970a0c065dd6c93446da4b19bd4","@balena/jellyfish-assert":"^1.0.18","@balena/jellyfish-logger":"0.0.117","@balena/jellyfish-metrics":"0.0.133","@balena/jellyfish-environment":"^2.2.41"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.38","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^15.0.1","eslint-plugin-standard":"^4.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.79-renovate-json-schema-deref-sync-digest-9a9e5bc797c16478901cb3a92af78c3faa0fb157_1603990574482_0.1085909386050612","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.79-json-schema-deref-sync-update-54e0215db3acd9d4d443e94393c71330f5560077":{"name":"@balena/jellyfish-core","version":"0.1.79-json-schema-deref-sync-update-54e0215db3acd9d4d443e94393c71330f5560077","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.79-json-schema-deref-sync-update-54e0215db3acd9d4d443e94393c71330f5560077","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"992ab9249b324a79ee2ab90edbd7064753dd3e69","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.79-json-schema-deref-sync-update-54e0215db3acd9d4d443e94393c71330f5560077.tgz","fileCount":94,"integrity":"sha512-p5ogZ2lL/f/wV1Jg+KGhFRt9s7TAk4UlGW7sQlqkPdnH/0UnS1zQ1F4se8cp4B/rsnbI7Vroyg4mfIusXKIb7Q==","signatures":[{"sig":"MEUCIQDqTvcTdZErEDmlaEAfDfL1T6dHX8aY36CPAqYTtafVdwIgcwRnH47onqH0Gt7MDOoXLgbdLpkYby4fHOCimEF6mzc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":568959,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfm2IcCRA9TVsSAnZWagAAEQIP+we98bhiD00Vtqc4J2Ve\no6nj1v7LAYXIzUYn62sMBB8wt4M/6DnD5OJ1B2kTKhR5MJ/wvUqGI+YjtLYz\nfM1oneD/tq6ZPTMJrf+tq9eQSSurvFy1KtwXMrU9sfQ12MLiN/YEsLASmia0\nlmKk5uBOwljjtAmmtGOfQUdKKPAot9kQNUMYMcSSgOgYSIrcsMCg/joEaWPC\nof0Mf+zFK8kiL59+Y0DUDCLeV+1d8dq76YLeWIu4Zd3PzGZwg73x6t93gv21\ninggtxxHnBNQPWCAd9bSag1BLTwRtj6gqPHUJbh/4mrTv32Xo7q5/a2k62Ry\nzrU4B3wlvvizBeaWQQfgG4ywy2GtjnRjBevs8ViUcUkiTgkA4tHWX/+p2fK9\nsJ8J8ALQJ3OTJI2PKF1fNpyNHlPSqqiZfXFpx2de3XF00KRI2lHsNDBlaBGj\n/TCs8DG5Re4YCk8q1+kxknNwCXVMbE93n4u8Vszcw6NYPIlNOHRuHksizdIo\nVfhp4OBq9rNTXckI1k/jFT+YQ5moBPpVBXAihxkoBqpX4YnU1WY+TGLznU8V\nE9RcQb51Sc0MP3GiF73vWwOdsA1OrEj5QYV1wHno09To2lOUCUgI5lSNSznI\nh0UtqHZKTaEZ3g+YOlKtbPYKRt1SEwgpOBrpW7s+jBUS0GuztZAkAIc8uett\nBi4G\r\n=hJ3q\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"54e0215db3acd9d4d443e94393c71330f5560077","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.19","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.18","@balena/jellyfish-logger":"0.0.117","@balena/jellyfish-metrics":"0.0.133","@balena/jellyfish-environment":"^2.2.41"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.38","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^15.0.1","eslint-plugin-standard":"^4.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.79-json-schema-deref-sync-update-54e0215db3acd9d4d443e94393c71330f5560077_1604018715464_0.2917937490473792","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.79":{"name":"@balena/jellyfish-core","version":"0.1.79","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.79","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"28542996e1f0a76f96de8918b62362f93e78d942","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.79.tgz","fileCount":94,"integrity":"sha512-FGeOhMJIkuXTL7zo4tiRRcdqs3kGZZ8YKTI2fEHOaSHx7viRZzybED8MRnHjEPYfGNhSTneRsJz8hADx/TKRAg==","signatures":[{"sig":"MEUCIQDLTKf/7zabJIg/8ZJMA4zjJw0u4wS3USnVjPSeCgBP9gIgcESwYkYgQSqaVN5w5J8KvbvGcCQr7vH2bRFszWugaHE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":568888,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfm+KVCRA9TVsSAnZWagAAqKgP/2vj//kybgPu64hIcWkW\nqRtWvN+dJd4xFMUL3GkrB0GoFiZ9VOb4XeYNe3YLH2bHDRMF608PgGV64fTe\nn0O4H/u9mRCfRyFVXWz935pg+1VSJa6GhiY1sV06QqWKgtVf7XSSb/rWF4fD\nc9hhGDCnBblyGzhGVXvqzlbhoHiXzLOlBO5b3fKTHIWB6p8rYad0OoKOGATV\n7fq6EdYmpKq5+ZAvrFw0W81DNAVZ3mj75dblVgjAxo0Jk7radaPWVK/bdbSI\ng35GGZulzW6KKHBZLSY9BuCDS9wbqfYTIhcL3rxBSA/PZ62Ww/KWwmoAGsaM\nnjJU689iyDmtqLZ1oOgebtjCnPuHtkvXdlkjaCqCFFJn8onB8K6SJv94XRkn\n7yRcJjyabV1rtDCzlW8NSdP0neVfhJfpBvN5Oh5GMNRvc2+Ina4qo6ipPIRT\nie0vRQ41c0sPwO84LWqohoEDXuoJyopy+Jz80WDZhDoKwh/X7C7PQMZVmPw6\nbN8L1PkSPDzBNOOEwsv5XjujtIMr1Q3tcCE3GBYavY2IbTDe8qFmffcGi1lk\nKOKiILnp6Ah+p2B+UQM5U3n0xkxHXtKAzT2s5bUGE6GE+5lK3qVG5l/1NGiq\nmCYfbAY+htEFNfy4URfX0O2T4Bii2zjlw5rzv72h0Ivl60ZAbjnhE1sEOkfn\nrEk5\r\n=Cy1P\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"e0b86544fb5145da15ebd6b604bcc9819824eacc","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.19","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.18","@balena/jellyfish-logger":"0.0.117","@balena/jellyfish-metrics":"0.0.133","@balena/jellyfish-environment":"^2.2.41"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.38","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^15.0.1","eslint-plugin-standard":"^4.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.79_1604051605085_0.20972936548105214","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.80-renovate-non-major-785eb4705430641250374cff1afa88635f737440":{"name":"@balena/jellyfish-core","version":"0.1.80-renovate-non-major-785eb4705430641250374cff1afa88635f737440","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.80-renovate-non-major-785eb4705430641250374cff1afa88635f737440","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"66c67cc7f7a92ecefde188cfbe5e99085baea40c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.80-renovate-non-major-785eb4705430641250374cff1afa88635f737440.tgz","fileCount":94,"integrity":"sha512-WMjnT6MP43NJ+UHz7dv5vsi7OKAz18Svha9K9nxyg4Xdu/WrUMJGSxII2jbzjFHCW6stRcj9opkmSqjpd1G5KQ==","signatures":[{"sig":"MEYCIQCJ3YJmhtF4jTLn9rk2rpvClcnffxsNXoyrvj1izwwsywIhAJavHNZKc/WfCh7oZAQMcCE+3IBmTIXTWgocPcS3qGQW","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":569014,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfm+NiCRA9TVsSAnZWagAAXzkP/2TPLZgxVdcOVAUoNXr8\nn/58jL6Z72L0z+3iRjM1AYd5w7OH21XshIJW6MNei80pYUiWI+HtXiO753w7\nyxsuYDMJurbhV1ojNo26g0G2t7k+N6Zohw13fvVbAKohO5BiNucoZiXX/cN0\nXKSGR4Bv9xNeR+sQKvCmuZSaPZ1f1grkGtdYlLgy37OCdbnk1j5/P6N6WZ2n\nuoJ0bysFYMD11g5RF6EDtjNCJ+jdtM56Zpueg03NWb0hvpm0P5j3bggqUbSH\nbo9JbVgu92Nb9MFHeGrwzc4BJsVAg5NEJpgrPKnmHN3/2OWypCROdNn3ok3Y\ng51J1XsJWlsPF6B1fFVBGT91sQpp4E0INdTlzUinoweFvnkaUodYgIfXViJB\nKVa25yyFAattT/eZXonn/i9Y2UKpZV68z2I8NVhEpTo88Qp3q8vZkIJUTWCf\nCFLEBDLSjYK4+jJQjY2p0xe66u/+HgHPhaagkPJKPOPBt/FKi10RRFUJCoMC\nGC58Bp8Y/Xu8gfEth/o8Yf0G/Zm0gjZwi53uX8cb9Z0OkeyI3GbXyuz1m7Wj\nnrJ6I6fju05MuNOlPoN/yQDeICFryOkWAKS2edlCyCInUBM62qusxFva4uMe\nndJ48JdrMSVbYTqh1eFGnZtyW0jaeXTfVGB3GwiXMm6r/WUxO9neRWlCvGO5\nigBd\r\n=HGle\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"785eb4705430641250374cff1afa88635f737440","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.19","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.18","@balena/jellyfish-logger":"0.0.118","@balena/jellyfish-metrics":"0.0.134","@balena/jellyfish-environment":"^2.2.41"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.38","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^15.0.1","eslint-plugin-standard":"^4.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.80-renovate-non-major-785eb4705430641250374cff1afa88635f737440_1604051809638_0.6239048060207131","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.80-renovate-eslint-config-standard-16-x-b8cbafc1ff3198fe1254ee70d01d60dfbc051f9a":{"name":"@balena/jellyfish-core","version":"0.1.80-renovate-eslint-config-standard-16-x-b8cbafc1ff3198fe1254ee70d01d60dfbc051f9a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.80-renovate-eslint-config-standard-16-x-b8cbafc1ff3198fe1254ee70d01d60dfbc051f9a","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"76a485d7c384d90d1c6d793a1670fb002c4a2d33","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.80-renovate-eslint-config-standard-16-x-b8cbafc1ff3198fe1254ee70d01d60dfbc051f9a.tgz","fileCount":94,"integrity":"sha512-vS+4NuM4rFB7eq1eltR1TP3CUSYhw3QiPFpIQl0pThSwyESPvOHOZu7BrDXOeQ9CZjCsHvV5/nkLnSyzJigU7w==","signatures":[{"sig":"MEUCIF73/QOkWHVkLZx4dt6mcMh3LLA6mXM/GePVw08VGdeBAiEA9TL4Ut6Cak5hS94K80hwIAop857yXY+L+/7zMOC2iUs=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":569063,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfm+O/CRA9TVsSAnZWagAAIyAQAIqcFwvhZEcdXMGrQM91\nI1mal0XiRavXbvZ9johD+IUcOvVOG/msdNVqivADjTU71koewV5nzzaiqq5h\nGRAYWCTl923jVoXt7vvrvSo17CSU3pu7W1R3FV4ZIbOvuQw6en9JmQvMHuuV\nyZmq7/gdg7sqSnioFFwQ/Km//0YhuFwyiL2PzbP9Ph9fCj5Rn2Du1x/nbGbm\newGsG7qno5qxqrpStLNNV3peqzJQQ1u0uLdNH+oHo46YLfGSKGBezxmbp+i5\n1W5BHf53BPkPjn3oUqNCZt0RoFCLP0P/LiNVfZpsHNRQYC2Sx3TP5jc3m6xk\n/DN1Zh1ByMrPt0hudFHMPAkNAqo1+SPVfoAjv1gyWrr/6dQhf3W1PtQ6dGd3\ni+EOM9iQmVY+Npf6ebu8rCHYooetTi5lRlGamI5jRYN//crLlhw+PW2+Wh3z\nPrWfc4yNeMbX9tJ48/8YsXmC4FSDiYe0bZZMkojt3cqUyd+gq2gIFlzYfI94\nTHbBZGXCoQaxih4cdZsPBHBBoBwOYLVHy4DsK6vzHbiqAu1S+a7zVrKFBxpo\nLgeyeLwPrMUV+gkIuEmLCxrhZFibdn713ufq19pK7CUX0uSy0R1dqtwo5cuI\nNy20iFq0M9BAyxi6bIefTjOfk1XkAinUy/yKU8nanZwbnmxQDeakNzWPAsvW\nqqvC\r\n=tOS5\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"b8cbafc1ff3198fe1254ee70d01d60dfbc051f9a","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.19","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.18","@balena/jellyfish-logger":"0.0.117","@balena/jellyfish-metrics":"0.0.133","@balena/jellyfish-environment":"^2.2.41"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.38","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.0","eslint-plugin-standard":"^4.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.80-renovate-eslint-config-standard-16-x-b8cbafc1ff3198fe1254ee70d01d60dfbc051f9a_1604051902358_0.10375839485843996","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.80-renovate-non-major-e63bf3207c7329bbb30aca81eea1e235aadb4b16":{"name":"@balena/jellyfish-core","version":"0.1.80-renovate-non-major-e63bf3207c7329bbb30aca81eea1e235aadb4b16","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.80-renovate-non-major-e63bf3207c7329bbb30aca81eea1e235aadb4b16","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9cf0eeac162175f5aead53773c1d0d5a4ea4575a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.80-renovate-non-major-e63bf3207c7329bbb30aca81eea1e235aadb4b16.tgz","fileCount":94,"integrity":"sha512-5phpHTl36akGfbI9W0pH91pZgLjIhEPqAkPeDmqNLvh9DJew+RhaubsFfTYHrfNi4x0erXtw2HXcaGkmLfXGzQ==","signatures":[{"sig":"MEQCICpbULXC3mTW1KJLBy0HxJ/BTpq9t1dfOAJpx6kX0GQWAiB3o1p8q+JQRhSKkQJAIiyHiX6ob4zumt6w61wIhsAcdQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":569014,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfm+QuCRA9TVsSAnZWagAAcD4P/jGVdsLB5z+gwgBlIuLA\ndGy5k6WZ0SzHiXC92vuaziA0+UH/qkhtUPcpuF90l1BmnQUmksYcMBry7UEm\nfAB+gCl6BA5TCLI3UGrb5wuaPp49WfI64VdLv5f/koCnRUJ1O9zTRo8OePL0\nYEPcNRuB7uU6MvNlcRzi9axfTC+2hpLUVvCTA61wrwjnicICaVXCqo3CyCAK\ncNXxXxhJRHv4drpO8GrASqLo1b+Mvi2O7aa9h4zvjF9dBPRKUNMHLOC0eEyG\nzNzTORYZr31IhWo8SBo1UUG3U43+6lY0OKxNnoKWV+iDPNWmPDeeDdTuxRHc\nAM9UQdivBviDEbSG6pm3mripOeaAPUHaB1PLNLmmRIJx2FIrHsAI6BRTN43B\ngbpHTDQyi8NZ/wxFwSH+5eRcZnJtqIrSqPZaH2Si8b8cEEQSzToTvWKHY0aX\nG3LP6H4tca62/W3afkBOWj85Oj1eYh1XMgmZhCTcAyE4PDBofY4IWqFVSlM9\n0maVsdtSNjOOXb19cQ2GGFPy0xVRHynasaVb4GAYS6Kyshy44kzBDCG3Tzlz\nWJ2F/BIT0jSvQQ56Gy4GGGkDqz2lJVVEimPnJHkaPnIdIhZCp44hzbF73Vuv\nHYGdwPQvsvt89YrD0Il2vXacBPX6F1RYTG5gpkG+gQGsW4+4jpW5TmBbLQY0\nZnzo\r\n=Jg2B\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"e63bf3207c7329bbb30aca81eea1e235aadb4b16","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.20","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.19","@balena/jellyfish-logger":"0.0.118","@balena/jellyfish-metrics":"0.0.134","@balena/jellyfish-environment":"^2.2.41"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.38","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^15.0.1","eslint-plugin-standard":"^4.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.80-renovate-non-major-e63bf3207c7329bbb30aca81eea1e235aadb4b16_1604052014172_0.5019341860808508","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.80-renovate-non-major-b6f17c5f6ae7e5b1df18ee16a7ebf473ecabd56b":{"name":"@balena/jellyfish-core","version":"0.1.80-renovate-non-major-b6f17c5f6ae7e5b1df18ee16a7ebf473ecabd56b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.80-renovate-non-major-b6f17c5f6ae7e5b1df18ee16a7ebf473ecabd56b","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"931294820ae10a98cc3b6506e7d2c88cb8f3cfa7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.80-renovate-non-major-b6f17c5f6ae7e5b1df18ee16a7ebf473ecabd56b.tgz","fileCount":94,"integrity":"sha512-pPGH/Xi+DmS/KdA9y6RQ/KRP2eKiavwQxhSI4mnVnLVIQvmZzQkNL1SnCJS3UwurHotsGsChwVRYgszV+eQaBA==","signatures":[{"sig":"MEUCIEkq8n6cmWiBNPbV82+/zuzyae+QiLRZID85sw25OTOsAiEAzpBYP0aFh6DEbhBeKjMALXqNS5x/j/8RsQ8lZYAZfvw=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":569014,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfm/XRCRA9TVsSAnZWagAA4ewP/jPkxzF93YYl+Uxbx/UM\n5CEX9oh1kj37UGknjn9QQmZucdQl3jciGQCEYeKpkh5v4l8J+XoR4V/g4lRK\nUHtfV196z/xuZ+TbqgNAqXYW4/kjrt3iV1qBd5knM3fwgs2OMgSg+soj1ko0\nc4sxeCfcUVqV2K5xST+THfYTKwPGadUPrnSJftZ+mK05h9pPhhfR3SIjSSd9\nGnYX548IbCnm7r5FiHTeuT8+DL4CdQCo6Zf2XLLdeJyOSfy5L8Buf4MZw1U8\n3Y6RoO5ql1TH/hKtbfWOH0SyHIWZUiE9w5wDtsHebhg82ujJD1c8AmDd0RQJ\ncTWNJqFcn6sQdUG/RXQPpgMtUaF+Lbn3GWRpMebkdJ3s5/e7lSudEWF0xaPW\nN4dWFM+25T4uPvegTMEiMvIsse3qMeq/cKAWHp8DbQidKw+r1DKnw2je8MiH\nNjg+wLEpHW5zcHoA5odh9ZUUuKFmdWpW6T1Ppo/FUOpi9dtpAW9XLlNfwjCG\nzeMwZllc1Q9i6znLUTdwlZETbf1oisZ0Pp0Vmcj2lX4wGN/y8o/6BSdU5NXl\ns8O8VRX/6p4fCEDBpu9R4vk3a6RRS4jebwgMyznEte1LjkXozd8ra2yy6Dp1\nSfuhGkx4pmEd2WOTYn0h5P0Mm4QDGKX4ouHSBN28RVErN9ZPcwAE9wL4tXNz\nXgzR\r\n=BsEb\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"b6f17c5f6ae7e5b1df18ee16a7ebf473ecabd56b","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.20","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.19","@balena/jellyfish-logger":"0.0.119","@balena/jellyfish-metrics":"0.0.134","@balena/jellyfish-environment":"^2.2.41"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.38","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^15.0.1","eslint-plugin-standard":"^4.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.80-renovate-non-major-b6f17c5f6ae7e5b1df18ee16a7ebf473ecabd56b_1604056529453_0.687681586420142","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.80-renovate-non-major-c1d68a73251f3b772eb620b926e3a4c568fb988c":{"name":"@balena/jellyfish-core","version":"0.1.80-renovate-non-major-c1d68a73251f3b772eb620b926e3a4c568fb988c","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.80-renovate-non-major-c1d68a73251f3b772eb620b926e3a4c568fb988c","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"71bbbd3075e3ac2d079a7790e345618fe3b1c1be","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.80-renovate-non-major-c1d68a73251f3b772eb620b926e3a4c568fb988c.tgz","fileCount":94,"integrity":"sha512-ZXeKiMgI9hTXKsi0bUFi8E2IE1NWjGj14G5djraPcxkI7OBNhU0Fq2q3WruYQEO0KbzU61a6YNxEhxHUBKJvmg==","signatures":[{"sig":"MEUCIBFPS5VCCHbBiG0iOqeLF2sWInk09GL4FpczbVsb1UHTAiEA5RVwGSNc0UX2y8fo1Hhv4J6yjwWNXMPacM2zmclmv+E=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":569014,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfnAoNCRA9TVsSAnZWagAAC40P+wYyWJcxWDYp9+ZkRM4J\ngaiByzUMGjqbbLI77RmLaz7FMXzVhPT1ngws02IQ34ai4KiTWtEJtfvCpJRX\n7pRyy4BOBiJhqcpRjtQxspsmqsmAPbVhHvvYIpaL/FaPifn4mvqvgbyOGTtz\nbWgB9BBJ2eHcgXHQeglKI7NeakVOtL3JnrfwIXrgYxT4EvXvGRvnQ4uYmznV\nszlxvHv4tQCrdgbMp/CtjTxq5kPAeayWU8pztp8u6jirJ6El5beOM0UXOZb6\nmpBkvbCbjH9Zz/w9ArqWy0b4EhZbOgrZ7RZs/BemNqBfqbpQiGlOvOzClyvm\nk/sGrl3lomKtI5X0iVwOFzD8iKOb6C4b6zs11WojQ5Gm+092e49aXnm0xGLs\nF3btyIitBJFE+q/YxyvXuF098RJU3SwV2J2DhHufnp2DA9BelnrwME+B3JN0\nEkw7Ln7NAaQW953JCeCSp5Vglh82pNCrRq+HiCpzO2NURD5l/tWiy4x/S314\n46xSyvRj/tO/DlyhJ9FX+Nh5YzXc24MUbbVvyT2tHxvHI+WmIK9UOn9D9RJo\nbNK7m1wI3LLi0voXMVgVGf7kC69QKRNPgwu5pKGsWcPpPIdBQwjepRhzvWJi\n3ss5RyMOkxX5EUOwfW+ymLd2r4WF2N2pHZg92z3OFTh0FiDz9xA3bERLYjtZ\nMMqn\r\n=VDzf\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"c1d68a73251f3b772eb620b926e3a4c568fb988c","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.1","redis-mock":"^0.53.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.20","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.19","@balena/jellyfish-logger":"0.0.119","@balena/jellyfish-metrics":"0.0.135","@balena/jellyfish-environment":"^2.2.41"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.38","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^15.0.1","eslint-plugin-standard":"^4.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.80-renovate-non-major-c1d68a73251f3b772eb620b926e3a4c568fb988c_1604061708520_0.7733390341265998","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.80-renovate-eslint-config-standard-16-x-96460dde743ad5b62a11dfd63d6a9914dffb2955":{"name":"@balena/jellyfish-core","version":"0.1.80-renovate-eslint-config-standard-16-x-96460dde743ad5b62a11dfd63d6a9914dffb2955","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.80-renovate-eslint-config-standard-16-x-96460dde743ad5b62a11dfd63d6a9914dffb2955","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"343df94c7365fd44fcefb318e66152c6f13a256a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.80-renovate-eslint-config-standard-16-x-96460dde743ad5b62a11dfd63d6a9914dffb2955.tgz","fileCount":94,"integrity":"sha512-jBcQiI4TZdJ0baa3CbK9eLuHqpDGhZHyKzwHqYqKl/eeY/2HFdwAr/cbF8LoYyLieIaIkMGVm23PcK1/xWbP3A==","signatures":[{"sig":"MEUCIQCWa3mjX4nMqNjB3Kq/bVu9ew68pJLhCU1lqfNxR2ghzAIgPvRM+v/N7ukd6difvvr9Chp1ZzFTIOOUBgmkfIOGGAo=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":569063,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfnISACRA9TVsSAnZWagAACY0P/R94YzFsRyR2+8gdchK9\nXqiKDJyOdEVqNKv8h3MSfT7ghSRdDYWqa2upGAXnS4sPCCQD9TaF60PcAoPz\nPK8UBNoyGkPnMkBr0cJS3TVVk20JgBW/fTiW72SBFu67vbyUcLMfyf6C6cJ5\nCb508b6K2MNHJuAtzo5Byi3BTQ606DwyOhAHl7m7Q9S2+T/r0nWHV2yYotpd\nxoEQTWNyZZfvbutiKQDivvyftg7EAoJUXqfMJnubbq9owhOhRclBbD1a/1Mm\nSOvLFtxuRum8VteTlUGacNPS7H0W2Z6iUMgEg0bEZIPNXJmMyRma1Qq5EY5J\nrhl1h3HBXvP5PLiN+Kjy8NZcANqfyNTfSe5Wc9B5a+gLu7wHgxvRAEpgrVZf\ncqM7PaeRZHWWnYBp5dMTqU66RtZesOTqPvJA03gkx0odzqwJphVSr4cHEwyT\nqdNbdnuwaw/DyEQ8gnj2RsFMs7a/0lfwMWpvkELnP3yZZgOpcY06UioB11CB\novRpHcbO8FGFvd37drtx5irI+bp/lsXXrhIrxnOmnWTNzH7ju3a7rotnbQnb\nCfHv6i21KhHHfRnuZhYJREJcwnfuMLOvhNuM35D3gH3qI9WL6nPyHoZIix36\nPF2KbeibM2JzIXf+v2GmGZI6VVFuUwVLm0fCR7fvOI3heNb/MYBQ1Tqv27gE\nkTLB\r\n=avLr\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"96460dde743ad5b62a11dfd63d6a9914dffb2955","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.19","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.18","@balena/jellyfish-logger":"0.0.117","@balena/jellyfish-metrics":"0.0.133","@balena/jellyfish-environment":"^2.2.41"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.38","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1","eslint-plugin-standard":"^4.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.80-renovate-eslint-config-standard-16-x-96460dde743ad5b62a11dfd63d6a9914dffb2955_1604093055474_0.9245022999403747","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.80-renovate-non-major-d7e1720ff168c93df077f34b39c22d91f22519ee":{"name":"@balena/jellyfish-core","version":"0.1.80-renovate-non-major-d7e1720ff168c93df077f34b39c22d91f22519ee","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.80-renovate-non-major-d7e1720ff168c93df077f34b39c22d91f22519ee","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"51624d3517865c5ea09ebcc655d62ef9aea30ab7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.80-renovate-non-major-d7e1720ff168c93df077f34b39c22d91f22519ee.tgz","fileCount":94,"integrity":"sha512-dZaU5ginSEQKXUiCfGje6Al7bgVnwvwAtbExcgR+BG/oi2hvMRNgIvxgaS3a9IKNao9g6Ql5QSu+SgttvI02ag==","signatures":[{"sig":"MEYCIQCWt8muGgdmg04U9cdrSmi1YP3eahh2Qkmo/J7pi3juCwIhAMcx4aF8R7rNPof0tvqUeVjU+cjU/bPckInNQ+mkXk3G","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":569014,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfnQObCRA9TVsSAnZWagAAybkP/i+oarPlEhJx/FAnStlC\nsBJNQpS6wnMjYbl1DmKJnOrsLYZC/6Dxnx6f3NAp4995BJ4+5A057UDUMNe6\nIbAhh0ZWqKXzy3FGWJIW/U5x32v4nQ70luqlJLhWAdj9woSwhV31eo2yjpSi\ntwobVfjYedL13QBGKa6HwiR2KZra2Fgh5g2p+mpUGsC/xkEKCZYQQqSFWefU\nMv5pufMBOJJ6TTC82fM1pQTJ8gd8jjOW9/CgqNLmtGlxCllURHZduA3ijcso\nbSue7vNAHiz7eHosEI+uFPa+36W+X+PrPUnhzUMC5+EYUht0RSOHt6sc/5LL\nUhdz53RAF3glNWha8SxjvTG3WYfAVVXW6n0KdE59i6PgSRkgWb4ALl1Q5QMr\nfd1dlK6W3ZDaE/rkJgVXOQj9PPEUdlf5KBudylWCAiO2e7e+9jN/KEDilHZG\nFIOcFexXQ4P2YarpumZBqNIM5l3uvEx4LFWhLgC3neMwpVsqFj4UmGzsyYJu\n0qxHyUneku+P6gF6a0wWENIjzee/94gy0tQBMlyI4ghNvXAYO4Wu7LurmQmL\n+CU1yT0RwKz0SXEWkMivY5F5pmejOl3kOYllU5LchNN8M48ab/aT06Ios6GL\n3bAYPiXdKso0tdYg2Fg9CALd+oUMWprygWeFe1jLrcd16JTsfU2WxaZB2X9t\nzIcB\r\n=VMjB\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"d7e1720ff168c93df077f34b39c22d91f22519ee","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.1","redis-mock":"^0.53.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.20","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.19","@balena/jellyfish-logger":"0.0.119","@balena/jellyfish-metrics":"0.0.135","@balena/jellyfish-environment":"^2.2.41"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.4","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.38","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^15.0.1","eslint-plugin-standard":"^4.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.80-renovate-non-major-d7e1720ff168c93df077f34b39c22d91f22519ee_1604125594635_0.44036086130009844","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.80-renovate-non-major-e021f917708c8f98bb0b4a2276a8d6dc26bb4db2":{"name":"@balena/jellyfish-core","version":"0.1.80-renovate-non-major-e021f917708c8f98bb0b4a2276a8d6dc26bb4db2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.80-renovate-non-major-e021f917708c8f98bb0b4a2276a8d6dc26bb4db2","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"44b4e7e090946ea353e9fdf4b06ecdff8256ea96","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.80-renovate-non-major-e021f917708c8f98bb0b4a2276a8d6dc26bb4db2.tgz","fileCount":94,"integrity":"sha512-bdnl+skyze4tFlbVCGtmrH0iLQ+ZRDtePwXknTRo5ek9DvHWNXgYTP+TQ34k725dWtkqEYql42+RKGDQ8ti6vg==","signatures":[{"sig":"MEYCIQCxleS7pNJBudLE3E1MYlSHbn3sKrpkXWWlTz8s1HZ6OQIhAMRBkCqGo3ECsULLCcnQVSqF6vKSroRKcM2/RwHoPaaL","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":569014,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfnQgrCRA9TVsSAnZWagAACZIP+QHbGJUYuYe8Ava4gbeC\nZFJfJPkeHL4JKwTngNT6pHKCWHSyJjfMR56EDyMcmGjY+e/fwgtUIZ1p2DOg\nF3TccJp7/Lwne2VbcEVHpDhPo90Caw4OA67S5Ii78p8EpGtioIKOlOk1Wvu9\ncJckp0ERF6+/XswbVa9NZb0SqQtG7fx0QGNvwLoYD1aCLRKvgLNNru6PYz4a\nLOALgGNCx5eDevCk0pY/GT2qqkpzc87YaQXRJkLdfB5TKqS6jdPzzr/kRZdL\nr2bev/El1NYXjavdhRCTxg/IEC3mp7LhDnV30FG7k/HC6tGpLLGNW0rPYh7k\ngxDKk8pRjtup/EOLC05WdMKJoSucZPqE1gA1P0NVU4fe3CiqniJJpuHl9437\nwfzCoE2F4PWGkUb7UTx5V2VaffueK8gSic5+i5AgtQcPAgKbgpDIu8x/HK73\nKjm/CRxfLYv8AtJP/eYBoqNaqvJI2mBtgfZQVu8NFu8Wxn5pQqdDiKZh5gkP\nMuVjT9F03okTeUrcCUYtEXTZL6b1bqSExrynPw5Am5mWBQZcZEd3iHB9JUKh\nvFue2M8AeWmWJb8tqNSz667SH+Nb0dAcvOIIAVkgOK1JT+YC9aj2cwS2HVd/\nLkR36dwqIF6z6AVoRGI6z4pu8J6p1tVtj1+M3eOvKYqVMlR2K7VmbZJJR0fJ\nQ+t9\r\n=hB5+\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"e021f917708c8f98bb0b4a2276a8d6dc26bb4db2","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.1","redis-mock":"^0.53.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.20","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.19","@balena/jellyfish-logger":"0.0.120","@balena/jellyfish-metrics":"0.0.135","@balena/jellyfish-environment":"^2.2.41"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.4","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.38","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^15.0.1","eslint-plugin-standard":"^4.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.80-renovate-non-major-e021f917708c8f98bb0b4a2276a8d6dc26bb4db2_1604126762617_0.5323318910982606","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.80-renovate-non-major-19e89cc535401532152ac0a72efe21ceb157d989":{"name":"@balena/jellyfish-core","version":"0.1.80-renovate-non-major-19e89cc535401532152ac0a72efe21ceb157d989","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.80-renovate-non-major-19e89cc535401532152ac0a72efe21ceb157d989","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3daff4aaf84c76eb28cb6dec75bc384776593057","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.80-renovate-non-major-19e89cc535401532152ac0a72efe21ceb157d989.tgz","fileCount":94,"integrity":"sha512-82xjvlcG3yVYLGgNkQ2A7V1Wivf9LXygNEzLtD8gBkhl5b9/wXFeI5LEFpNVndQ6drBbJZ4AFPr1P1zOnni3MA==","signatures":[{"sig":"MEUCIQD+GbqGQAyphC+lYsoWQK29GvGLjxFhYQqPrQyjOyPVcgIgFf5v7Xmj6TNNwN8dmpcJGsDrF91iN8EOOgyDHNXcM9k=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":569014,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfnRlNCRA9TVsSAnZWagAAqQQP/1eu+X8Do2cJaKxjmPzP\nS6/04BOKpFCAFdFECySYMN9O6zgwNGTWDHvE6ZDSfzwADXb0hF9SBJBpkNZq\n9upT8+cz60AIpke7uYdK1EAh0crbcH5QEziLNkhGpWpjvLNb05Aabhmk5PlR\nGbwjF5eXm+PctKolFKRzc5KRpXXaxSL9TMW74WU/iBWIbKb1v0ueYFiLo+iJ\nrXJNvMMoCmKGHLE6lLV29PTdijaioss3+120/kGUYuzV8OhorjE+7h+pBQai\nepIXlbHEBN7z5inebfdBvDlvLhs8Htx4HOQocg3Cm+j6G2vWoeEY3wx3WL/+\nXc/OyqxrEcWUhKgGg7XqmzuMLbrl6Bm8lmf02pE1fuLomgHkGprNKWt8NHxN\nXsda9uCEERFyQYpDfEPUMsNW9gN+1H4Gi853cNaBaVUUAx21UHuOx4S0eW+v\nG7geZXJxcfwPedupE/VbDCIgNiI06/kT6TRO2BrtDiM7FDMx5nl+LXuc2xev\nMQqI1x0/tra2isbSaKNnlzPuKCe44jc19MdswjMcnwaACSFIkRan3kyvs940\n3HSXTJALdlKdB9MO4956triiAL6cOhzevqutMzp6EUWrj5uWdo80ObZSqmZJ\n4X828cau2ncdXu7a2BPFwHqCocHKDt6IdMVtXk+qyXJd0XzvvckBR4Mg5IZQ\nRWqQ\r\n=qOXJ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"19e89cc535401532152ac0a72efe21ceb157d989","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.1","redis-mock":"^0.53.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.20","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.19","@balena/jellyfish-logger":"0.0.120","@balena/jellyfish-metrics":"0.0.135","@balena/jellyfish-environment":"^2.2.42"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.5","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.38","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^15.0.1","eslint-plugin-standard":"^4.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.80-renovate-non-major-19e89cc535401532152ac0a72efe21ceb157d989_1604131149297_0.42886781987383116","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.80-renovate-non-major-4d0fa59c55056caadb2b9a3a0efdf92905abc7df":{"name":"@balena/jellyfish-core","version":"0.1.80-renovate-non-major-4d0fa59c55056caadb2b9a3a0efdf92905abc7df","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.80-renovate-non-major-4d0fa59c55056caadb2b9a3a0efdf92905abc7df","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d9b2e9005882afaeebfcf3cf63506f4e98ced57f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.80-renovate-non-major-4d0fa59c55056caadb2b9a3a0efdf92905abc7df.tgz","fileCount":94,"integrity":"sha512-YIn2+wCHpm2EDIrnZlsJGlSwWwdvtrNEcziALUJtsRbZNJP1WVbzpQmkofyIqLmwsllYTI7GOi3bh5caziy8Gg==","signatures":[{"sig":"MEUCIBo9cQZHDblBM96ekoMkuFlLnUkaP2mDAVQvQ7YIPEcXAiEAkbhk2A+fjCd9NQv0jQ+5l37b2pRilYVdSL80zf3glx0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":569014,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfnSLHCRA9TVsSAnZWagAAvHIP/2v/G59nfEpVFqzkk4Ke\nOhn+eJPmGSWVKdA0pCmu7bka1rsZUvpmqrCQp/fP8YjTYw+2d4DMGoVzWi/s\nJWFShPToawWxx+teEv3dMDyw0iT+QIQaIVN+Nihg2kaSvkW+/MCOUpxjh9yT\nJzhTiTaa/OASXUOeessrf+KfiRcQiHbi3EIik+a+lreBLsDr7pLsrIoVkcZp\nVxCT7ngoHk9HVf7eFKv079FQPk2rZ776CChYZXVltkyC6CZDuA6xyCHqgGeo\n5OSpJ5bvQzJEgQa8T5fgay//oW/MhWL6raxNsSJ2ktfVPXHI1/M/rWiULIlE\nPwd+95Ug/E6RXv8qlaXmpqCOSKS0cwZ0c02EEsCgOkRdFnE44/4zrgjme2th\nyqwg9l/fUBJIJgoA8S2cf9uOqk0KjBURUUZ8vAb8FmaUART8WcK84gAKV9pP\nCyc/8OvkKzhP67kwxKqKPc9PBIaENNc2o6TH/jhDX6kZujj6XO37HgpDO1JO\n3kpbr0e23ct/iLVKc6AQyKZat1xzi+4zeGwiwsxQfdbuDg2Z9mNzy+7CL/uk\npSsCWlZnsWpdikng+ec1Y86v/OdCQSi835QYYu6D6VMnEdwwEXJIJE/MlVdB\nh4O8XkC6ZNEGPsbP/YMaJO8f/fRPa4gyCzxFFtygKDYhgojo2rKPQj+xL/Mc\nuU/K\r\n=Uoq2\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"4d0fa59c55056caadb2b9a3a0efdf92905abc7df","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.1","redis-mock":"^0.53.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.20","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.19","@balena/jellyfish-logger":"0.0.121","@balena/jellyfish-metrics":"0.0.136","@balena/jellyfish-environment":"^2.2.42"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.5","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.38","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^15.0.1","eslint-plugin-standard":"^4.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.80-renovate-non-major-4d0fa59c55056caadb2b9a3a0efdf92905abc7df_1604133574964_0.5306191687444344","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.80-renovate-non-major-1fe01b352be9b032d27f3aa2f5fd0d60b0b1391f":{"name":"@balena/jellyfish-core","version":"0.1.80-renovate-non-major-1fe01b352be9b032d27f3aa2f5fd0d60b0b1391f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.80-renovate-non-major-1fe01b352be9b032d27f3aa2f5fd0d60b0b1391f","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3f281fb24a2d678f8de80717373c60304dd4165e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.80-renovate-non-major-1fe01b352be9b032d27f3aa2f5fd0d60b0b1391f.tgz","fileCount":94,"integrity":"sha512-1jx3nrgyEPW9tv+qD2ULoVyo0+tug2AMOUtFkFnTDYLqJXCMA5c/qTUNpTvxrvdU6BxBcw1464Cfjvjrcqigtw==","signatures":[{"sig":"MEUCIH6FG5cJe9UrLBF2JHE/ONXOUamjsQpMOCNJmrcAGPX4AiEA7axlgPcfcokquPsK4NZOhhE8hdfbSA2fWeEDy/zOtmc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":569014,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfnT1vCRA9TVsSAnZWagAAM5YP/jC9TY7wETPct+xoWEtW\nz7RB8gw/4It43cNo/9sjVvqO3ggiGjs5jP942ElI96+AEeDQ4/AOW/DkPDqZ\nKLxv9ko+Wcc6E3flaHVOMb6QYR5+3xzcHvGoMePDdcCEOLQjwCTqL1eG7/u8\nWfz1U34X6Edcehc3Aus6CvfjGuCNkp9u4GqspVOzVDVTH/qlL14RfEi7qAOZ\nxBYPNmIqmdnPXmmDs4KXwN8ZFwabdAdYtcOPx4wSXaxjNK+LfXTyDF1sOQ6s\ntnzAn6EeTedbXdwO8P4vs7DlrD2WXSOo01S6K3xdZT2mh6rzBGMzhFQxGHU/\nce1xglh9JfddoHL4mvRsuuLDMgDKWAK7HONk1hSrkX0bFbSQH2/gKlDXeNJ6\nhjrnwQvnb/8Pg+rQLTZPmRc8gNgxkWUBilvxLYfYUVsLaVo/HZC3neB8Zs1l\nu9TZXV1X4F7KxXtp5yijWQv24VahVv0k0fAnq9cIciHmZshuebLgT+RpGJO1\nT4gg6ar+9uLCODiO44mOPZ4mx/kb1NQ4+g/Wy7jQnNevP1gv0TJYjkPI8cCE\nqVlJr9PN+8UG4aCOrWYUVTrCte9Y5XTazTtesn45DTIbyMhMB7IHAVkOBNdt\nJh1EzTcP+zWxZdUJts/DfwmUR1Wsl17rH1K5+vrBj5uRQJxGvkXEsUEK28Gj\nZksT\r\n=CQcU\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"1fe01b352be9b032d27f3aa2f5fd0d60b0b1391f","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.1","redis-mock":"^0.53.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.20","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.19","@balena/jellyfish-logger":"0.0.121","@balena/jellyfish-metrics":"0.0.137","@balena/jellyfish-environment":"^2.2.42"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.5","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.38","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^15.0.1","eslint-plugin-standard":"^4.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.80-renovate-non-major-1fe01b352be9b032d27f3aa2f5fd0d60b0b1391f_1604140398879_0.025844041727898892","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.80-renovate-non-major-ff2f5ee8e03ae98ab41443b1fa447cbcae0ed016":{"name":"@balena/jellyfish-core","version":"0.1.80-renovate-non-major-ff2f5ee8e03ae98ab41443b1fa447cbcae0ed016","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.80-renovate-non-major-ff2f5ee8e03ae98ab41443b1fa447cbcae0ed016","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"df2226ecf1d6a5e1332db2a91a60611e2bd16c69","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.80-renovate-non-major-ff2f5ee8e03ae98ab41443b1fa447cbcae0ed016.tgz","fileCount":94,"integrity":"sha512-QRTAa5TpLs5Yt31lE24EP9fAHwJO4pxBwwcugFrH0wJ+aRGduzRAP3ECFhX6BzPm4ttUuR25BWn3htPBc5hdAQ==","signatures":[{"sig":"MEYCIQDU3yJDGUK6cNgewte7lYl9oLJdM4G011AH+kzPTi6kvAIhALgcgLSLxznG7o9yEi+Y+g61q4d9Djhs6Ymbl4MAo65m","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":569014,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfnWepCRA9TVsSAnZWagAAU0AP+QAXjuEMQC5g2itVNsj/\nmBwq8fh9QlodLeCw+63uIMhPb3aXFvNbcnInMnzR0iMyU9/2PCQN6Dp6DyvY\nbsivRxCcwHgelgpDSggKkGMY8G9mxiwB8VaWtjepxrYJhs1w526KwmweSxXm\nytY0juCSF/0JZr/tuk3f05ZV44bGnXKe5hFlubW+UP5POYUjwCZfIYt9Twsq\nW1FH5CNXRePT/ORpFEQuvHumlFnZZ6AKoW1drcf373o69RWx/QJ+G+I6T96Q\nGDxNbDiM2/lJ4ANUV/LVo0ZSP+M6foZRIQgVPYUjdV5PWkG0dqEK7CeN18j6\nlQB9i0qNZWyEs9IknfsAwJABdXIT1kDynDks6dkwg2yIUL2pHkXAmrjgmHzB\nhk9puc8rZzVu3d7u4nU6cdXtkL5RiNZdXJhFlpwCz5iIT4slLyDhW1ykt6dw\nXlWrYTpLOgPof1Nsc4wtZD5/rv9ylMqeaDKMxrqbube5wi4ryC57DKbjgGuH\n00JUVIslr4ZnwPiPxp0vlwkgEkF3hp4wBAiJg3mB9aY/qPXdx9LLIfg6qaAr\ntGzl0wLpTFBq+Slm+NzOA5mVPQcYV6Mp59znFG0ID2/5D1ZhTM7YMkSfSJPd\nKoNUlwHwGtxnsE5VCWgD5o0wX8+lSokgjXe5+jQCDZGvH2emOUMRPkXJ6Zfw\nXHWB\r\n=jVc8\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"ff2f5ee8e03ae98ab41443b1fa447cbcae0ed016","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.1","redis-mock":"^0.53.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.20","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.19","@balena/jellyfish-logger":"0.0.121","@balena/jellyfish-metrics":"0.0.137","@balena/jellyfish-environment":"^2.2.42"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.5","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.38","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^15.0.1","eslint-plugin-standard":"^4.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.80-renovate-non-major-ff2f5ee8e03ae98ab41443b1fa447cbcae0ed016_1604151208738_0.9779996444397139","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.80-renovate-non-major-bdc1ce41558eb076e8916e7b3f6b0432b3672618":{"name":"@balena/jellyfish-core","version":"0.1.80-renovate-non-major-bdc1ce41558eb076e8916e7b3f6b0432b3672618","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.80-renovate-non-major-bdc1ce41558eb076e8916e7b3f6b0432b3672618","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5de7ef0c755c0dac162e2a8e6ad78c62b20db450","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.80-renovate-non-major-bdc1ce41558eb076e8916e7b3f6b0432b3672618.tgz","fileCount":94,"integrity":"sha512-v9DBO4hGKHfJV0uUWwpXNEtXOFnLSStcujky5jYJbumA5oTx5b/ZdDtO+B33x7DGuU/6YvSLAUum89Ecsbne8w==","signatures":[{"sig":"MEYCIQDTA/byOYFa5I0iBshEIpCbjUXEQ4K/W22cBHY7Wphu+gIhAKUX23BBpxnn8WM78FOaYeK2Hemc89nQw5Is1W8r6quQ","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":569014,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfnXplCRA9TVsSAnZWagAAOGkQAJ4BK0Bq1wirXmKfQFYN\n9Hfdj1jw52WQXf9DliQ2QYsIR3GTm9fOSfDu1x2Ds+1/IM61d+h/5UqGyayF\nDVmHZTzU1VOIZgpc8RZBIFr7NDK96f0Ac+OZN61wilWBDrehhdqe6knhrTPM\nzVTSpDJIQfv5GdXFxrc3G/CvcO8E0yshlS2QDO2cfhZkmCkFeDECLEaOf7uC\nxYCbjdXjMnM7tKImNQqigyj/0Lg1FEj6NR9IsUNt6krxdPcc56OeafItbXqH\nsdHxh241jWwKCcuolmf3MhI/6uGIptySOf4Txvr6WKunD/ucr3mY0Zg6iElN\nHTJ5PG6sKYqiX4x08fR7T8SgnJob2rPfyHT7ow7RrWDftfxk9PJNRSuyT1Cj\nxwt8H4NTwLRnO5fBpGQlaIOCEb/wdiLTp9mMm7OwxktaVxpypuvPmjblbkL8\nIaUbE3FJ93fImtbPTGEhmpSHyxwkk86M0Y+7+Mf90oRQUDu4eOump6EO9Fzp\nfdEgIiMRQZkRiP5L0Qi4JoJvKA2xC/kyxiXYkryOVfOWxYo6YrAIJQIYazi/\nNKlXlHy+Sii/phwwOE5Uh0vIxtKNIusinZJkI6PaOH9W407P7fJYVHPApHqS\n6ZUqs6bodAnfIKqkp0IhPzveXIoGKEYiWHSi5zTFEJnuA2I1k/H6Fzj8C/YR\n2bfY\r\n=oPaX\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"bdc1ce41558eb076e8916e7b3f6b0432b3672618","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.1","redis-mock":"^0.53.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.21","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.20","@balena/jellyfish-logger":"0.0.122","@balena/jellyfish-metrics":"0.0.139","@balena/jellyfish-environment":"^2.2.43"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.5","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.38","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^15.0.1","eslint-plugin-standard":"^4.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.80-renovate-non-major-bdc1ce41558eb076e8916e7b3f6b0432b3672618_1604156005291_0.5112733902749573","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.80-renovate-non-major-743e53c6d59f2f24d5ea9fbe9c8e2bc6b0604221":{"name":"@balena/jellyfish-core","version":"0.1.80-renovate-non-major-743e53c6d59f2f24d5ea9fbe9c8e2bc6b0604221","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.80-renovate-non-major-743e53c6d59f2f24d5ea9fbe9c8e2bc6b0604221","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e20f4ca6b8ec612c95d8ee409cf6ee7e841d1758","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.80-renovate-non-major-743e53c6d59f2f24d5ea9fbe9c8e2bc6b0604221.tgz","fileCount":94,"integrity":"sha512-keHzvs2dO5zHQuh776ODomlYvPhMgnIrOrNUEwNBL3OzRHf6D9CH72jSaDQrQa4AXJ7DOuDRDUz/jK9QAfHH9Q==","signatures":[{"sig":"MEYCIQCppM9ODqUixqTvUno+eh+ILx2cOcf68D3QVutpFM7DaQIhALUcBJhqWsD9hMjnu9ee6eM+MAeiXq3sL6iBtX8I+GX5","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":569014,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfng12CRA9TVsSAnZWagAAV5UP/RtEWNWNWsdtoNbXO+33\nlejrYFOBZpFALA8k+j1V8JlD/U/x5c8wLm7tEwnFTXdpvW4g92n5RLuvrLzE\nmvOQ/DkEIhQt0WY1c+RXxNEAGsVBsAB3DYt78h6DEtzFLJsmxLNqM22lsqP5\ntu6rL8yYvREZh5UB4R9jdrmRUYsDjTsq2d2ZDeohpSS6Ql0KkEVkZDLhgzIu\ncx5mBhBjw6pyTFx2qcl1odf1iAZ53mfK4auVw3uoTGI/h42E4nBQWH9EuDht\nxQv+hO0FDff+X24UVhHmDYOeAmEnMjm9RzHUOWVwRQTViZ8jUb1fryClISGD\nHi0EWweeWiLzx2/e6b7JCS/k9l5v+wDl9iHSURpbMsUgNE9NFL+huvGDIndO\nFQ+9HVjqnRcLY6W1Uu3Kac4G7NwTb8w5W2SlBKnyfL66a6KVWG/6c7MSymGk\nqvdVpJWOhnVpTFC5Bv5V02B+0E0rJngkkjh3Rh+RKE121hsIpw0zbbf7RCmy\ndbYeqeWAtA8duUl+Fryqv3Ca/sVFoCljb/0nv0s0c/1cFZDQuTX+2zn/dDWZ\nbxy76BSU/dbuxSh8BMhtzQNmff9ovgwiOSirs/SmSrdRwILF4xeV8Qd4nelW\nFB4hvevgT9EgKe7RBW2dNMRqk1o/V/IQfzKMip6PC2eybsfOn/iZXiLoVxyO\n/Grf\r\n=YAP+\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"743e53c6d59f2f24d5ea9fbe9c8e2bc6b0604221","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.1","redis-mock":"^0.53.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.21","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.20","@balena/jellyfish-logger":"0.0.122","@balena/jellyfish-metrics":"0.0.139","@balena/jellyfish-environment":"^2.2.43"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.5","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.40","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^15.0.1","eslint-plugin-standard":"^4.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.80-renovate-non-major-743e53c6d59f2f24d5ea9fbe9c8e2bc6b0604221_1604193653384_0.02247170638841789","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.80-renovate-non-major-0141d2ecd76ff7cf73697b51811417d7dd85e972":{"name":"@balena/jellyfish-core","version":"0.1.80-renovate-non-major-0141d2ecd76ff7cf73697b51811417d7dd85e972","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.80-renovate-non-major-0141d2ecd76ff7cf73697b51811417d7dd85e972","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3d25a52e74bee2d55f10fcae53f091a7dd77ce29","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.80-renovate-non-major-0141d2ecd76ff7cf73697b51811417d7dd85e972.tgz","fileCount":94,"integrity":"sha512-bSbFJj+/8IsgLKlFYhxtAnORwGI8gt5kuCxY3jrncNR3x8BuIMjtyCqwNzO8zDp7LhTej2goeMiuWYS/hXkezA==","signatures":[{"sig":"MEQCIGGOd8Q9hGycyR17bxIN9OBdfqDVDWFyBI7OP/0yW6O/AiBN76fDOqCltgRcganEfcoDzdonpvSETDnGPtzmEFpZVg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":569014,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfniKFCRA9TVsSAnZWagAAErsQAKPzjrqWrZNkXbPIr6Ou\n7EDGZDSXCwb22NeWUtDDQ/APHkXEzaSdwrr7TZsd1d8blL00HADMiVnH2nUz\n/GP9ys5GrYNVu8TgNmwV2iUulONCTHxiWn6qNsdqfDES1abTCmYZTxc06yaE\nvkyPi50le7G0BPjS/cN1P5EBYYlwd7+Aqz0ULmRX7sxzwHOz+Pe85Dtb0hSM\njojoLTkaFzYC7FdXe15S6OJYeCC/uISqzfu/KM9lSFHZ8kole+gcxClgvkyu\nzHUbYm88KD4Z8rXjFaKdhNDiQxReTIT0CV4AkRmL+ZStHD2NpIA4e+IyozC3\nv4JGeWI7kBl+EB0nHxDPyBhbHjOVj71KkElZQl6l84J4ALfv5ZFrjl/3ISvI\nb6hLSQBk3JUWus9XiQPTbqYciZnMziSz32D0UDH6bPAbPINrBsxxkO0JWPND\n4dB0ODOUd1FpLac0xyLOGLx/vGQBknGYuj+ns1CHuxgstnOyl7lcx5lQjnmI\nkm1hLgt+ceKEyB4N30bqd8N2s8DIHZizZJ9IldV/mIMOAtI6yCNk58r8FJOo\nxxpaQgOR17awe//yTIFxX8cAvfGzndFmR8AINcfzDfQQ/i085omwyd4G0T9P\nR9skvk/d+z50RZ12L9Y6yiyJLs9rs6bxf8Q1pcnnakjAw+JZNJovI+WxzzYt\n9i4V\r\n=cAnV\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"0141d2ecd76ff7cf73697b51811417d7dd85e972","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.1","redis-mock":"^0.53.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.21","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.20","@balena/jellyfish-logger":"0.0.122","@balena/jellyfish-metrics":"0.0.139","@balena/jellyfish-environment":"^2.2.43"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.5","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.41","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^15.0.1","eslint-plugin-standard":"^4.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.80-renovate-non-major-0141d2ecd76ff7cf73697b51811417d7dd85e972_1604199044757_0.8269247160488271","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.80-renovate-non-major-b797e9c6d30d648b1561b06f4e47523c6586802e":{"name":"@balena/jellyfish-core","version":"0.1.80-renovate-non-major-b797e9c6d30d648b1561b06f4e47523c6586802e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.80-renovate-non-major-b797e9c6d30d648b1561b06f4e47523c6586802e","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5280ebd36a01860cbc28cb2908610485ee6f5cac","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.80-renovate-non-major-b797e9c6d30d648b1561b06f4e47523c6586802e.tgz","fileCount":94,"integrity":"sha512-ie+gsKk9gb+8JTMrJ2z0QP5vADAUdQ26nfFyHVKiY4LKwOFR/uOf8MQBvlB942JMei1YzRdFPQo4itjS5UFexQ==","signatures":[{"sig":"MEUCIQDT1YaxHZq+/S+RvRJV+LffmlEKwZLFs/yhCHKJrUzj1AIgZe8WElXN61Hf+4Q351fNExfSo92uXW5hIqZbyu2UnKU=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":569014,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfni1VCRA9TVsSAnZWagAAlsgP/0Vmw4A1kVaLrG1dIlvx\niod8/5uVG/YB9ncXduMrYsSX7fNkkMYjBa8l76BxFoSFAGXMye5BdSLmWaDX\nhNfy0WcH81iYLdtl4iGCqLouTfEfv4zZ78MlyevKDnhM2mNwda3SNQZ/MXT1\nJvzdDEu71qY6nNa9B+uvtj40UfvV2FEZoySVc3O+Xz4M9e7a0sNjo7s/eaCO\nqW6vmOsMgR+jYy2xrH1KeChqJLWI5GusI3p7esRYuGpdjFWeTU53rEZihgGj\nbUQ/4N0kwsHv0MtpK1bJjvSbfAEd1zxNvj/bNTzQnE/G5Tnv9r9gqT+E01eL\n8YIiUEOrlEylXXtQGkP5CZa7/gNoOgiectKLZFCiP/492Ar5th9SFs7y9i8K\n7c4fB8/xPd17JqvjH4ik1HOhg2Y8cB5/LMAuXWkqoBNxSItlbVnEo62F8vXY\nfKsVdDPSY2CPN/tluSWZuAeFxAL/bQZh4OMdqri8g9uPNVkKkdxQyRLoDgwS\nyx7U01akV2Z78lio0PyPxatq6EV4V+KQO7k46nvExIi8vzTaj2euy0FkXzo4\n1IsA+B8cklbU96Z6yARrJRAwZIAnEO1i6uiTgJsOtFEwGQIN3OKAXVli6Nrl\nlJclvq1OuVMPO5xC8pAxX8GpLXXM19MWsjoNVCWQFbYXMdtLtZ8Ye13/su5i\nMBLt\r\n=aSKR\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"b797e9c6d30d648b1561b06f4e47523c6586802e","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.1","redis-mock":"^0.53.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.21","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.20","@balena/jellyfish-logger":"0.0.122","@balena/jellyfish-metrics":"0.0.139","@balena/jellyfish-environment":"^2.2.43"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.5","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.42","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^15.0.1","eslint-plugin-standard":"^4.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.80-renovate-non-major-b797e9c6d30d648b1561b06f4e47523c6586802e_1604201812744_0.9954488278498745","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.80-renovate-non-major-15c237a220e7a709c5636a061f57ff9f6c94b75b":{"name":"@balena/jellyfish-core","version":"0.1.80-renovate-non-major-15c237a220e7a709c5636a061f57ff9f6c94b75b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.80-renovate-non-major-15c237a220e7a709c5636a061f57ff9f6c94b75b","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b99517c18e72c42a81791a01e68cb65e5859cf7e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.80-renovate-non-major-15c237a220e7a709c5636a061f57ff9f6c94b75b.tgz","fileCount":94,"integrity":"sha512-S4dDOHvy22PpOS1WTSRL5bk1pGyUyTLn96UvmGzV8cMSPb9gSe8+/1e+iSx2m3RJtkGuuzIi5LM0nj6oqgBUZA==","signatures":[{"sig":"MEUCIQCdOLGyiH0HFuyBEwCt1nYq7n+h/6U5qJudi16IoBN3uAIgfysXqfeVLsJIThXKSRQ1M6fGtpVzbK8tpnJj0T4/K6c=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":569014,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfnkh6CRA9TVsSAnZWagAArlUP/3V2dZEYGOO2cHnrmncM\n5PFT3GZAtqyPe5/2WtrfdGFtOyxba8KroM6fwJWzQvDO9XfWkZRLf/6ZPfgc\njI+sI9/GVLm78tx3oatndwxxhrE2+qZpTBjdEY+WAoEmHOFXJYEvHP8LBEs4\nvl3OCW0KnNidpm92R7FBNkXCl3WOLSyj/9UNKx7nSUxhauv2nSF3sl1A6JrC\nA2eY4fU9DBxz9AP8o+dnRzwvT0Kcv5OoOJJ3eCIzYkLjRGwUc0ciCjhWqrwk\nLj9f3HCwF26cr97hn6RCeJ52y1WsJC+nEO+rjgMvJ6YWfIYzoxaR8hmUXLzq\ndE0UNsLiv8Exc9nXa2DgAznE7YpfNhiiNXtZbFUBiqn5DgvuE/jTri67r2HS\nVHwHpe0vhv+SENa6jsjybSNGRqoSV/fw73IMkYh3smfPwEhf8yxTAWTHihEM\nK37KsWx9tOporE3d9kt2++fBmRupGajzVmpnVXTqr0lMHy1gW5NQTQung5W2\n2UNbIAZv2VEHpuKin4nJrxdEeAmsbva1h2JUO8J8/lNuH8sNwZHeIFyN1DOr\nu+m/hC/weVgFkXLKm1dUJNuN0GwHxFqA7ZTvjJK2bCYGunVjdL7VZkEsvRMK\nzjqe82hXfXFazapnXmw8nR+xb+M9/Trht4AU4awqvN3Rn84IizEdSZKGQoke\nTUFA\r\n=lLpU\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"15c237a220e7a709c5636a061f57ff9f6c94b75b","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.1","redis-mock":"^0.53.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.21","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.20","@balena/jellyfish-logger":"0.0.122","@balena/jellyfish-metrics":"0.0.139","@balena/jellyfish-environment":"^2.2.43"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.6","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.42","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^15.0.1","eslint-plugin-standard":"^4.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.80-renovate-non-major-15c237a220e7a709c5636a061f57ff9f6c94b75b_1604208761962_0.9751626845424437","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.80-renovate-non-major-27a68c791ee7452142f1238526b844d688091684":{"name":"@balena/jellyfish-core","version":"0.1.80-renovate-non-major-27a68c791ee7452142f1238526b844d688091684","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.80-renovate-non-major-27a68c791ee7452142f1238526b844d688091684","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4e20d07dc12ebc06c7acaf270ca963a29f8c2475","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.80-renovate-non-major-27a68c791ee7452142f1238526b844d688091684.tgz","fileCount":94,"integrity":"sha512-kV3oZFsrL0j/rH08s/GzPrAbwso76jEvtUjNmU72r6ondKCfyN64Y9YErVGdM3efhtorsO8oPre3Z9Sa9gU33A==","signatures":[{"sig":"MEYCIQCQe2JRLvxds7p+9Ir3LWSvrwXR8W9Mu60imdk/dYsBPQIhAKqOHziY43o+/oLmZa2V1PHvmh9lyfjuN9eDLNcaTYIK","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":569014,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfnlaUCRA9TVsSAnZWagAADmsQAKEZgrdz+5XcclnhRLIa\nc3GfnbE1NuqzmnR8QKEjthP9WeL+QaklPOPBwkG85kmk7Y63n3fydx2U3xax\nBI6OanXZFPgaJiPRNsEw9TlPEB3AzUbeNiaEBhUVASfQLCQwpcAz/jUOz55K\np0qUqEshMVuQiJ92xLjSGgG++pkLBERoqPCFjLEuHVMiq/E7blyDbB+LY9Y1\nt12/xcKSZA74Nvhd1+w3ZOobFmHjl6KYYDMyvs+xd5cfa0hto/ZwHP+4csBM\nxYtBR6BJm+JwAOx/jRJV7EtAO3uC/J+OFIsMzuyCcg6+E1lO8hHudSAn9QHy\nuIgMIC/aIWi0dTxPihH1fAD8BQ+WdgU6yNHMagSdNxSgpLcd0JOJSMC6n/yP\n2f4C5O2p231i32wBfe8DsTidZzV1TvlL2TM0BWiJI9+CsLOaxTZNtk84pWzj\nwerXNqcIZfGKgEik5rOExp6nmwXkOdWhctDFG2zrqLVtevawwsYG/10NJvOc\nV+y+0EuiUhHekq1vlIkbvpKhY/Zj3FSjj30YO2gWcSKyMpbjSihbyEpLsm9Z\nsOmhbPFFkOZ2oCIOkBKrG5fRREjmCjLftlQaLqAOueArHzyyJg3MhDVrYkO9\nWn8knqFAJ6pcfOVhnK1sN5uGo7Rsgp6g4BG31M1kuVRc6/1ExUGCyToFYLe6\n+X4C\r\n=Von1\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"27a68c791ee7452142f1238526b844d688091684","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.1","redis-mock":"^0.53.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.21","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.20","@balena/jellyfish-logger":"0.0.123","@balena/jellyfish-metrics":"0.0.140","@balena/jellyfish-environment":"^2.2.44"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.6","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.42","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^15.0.1","eslint-plugin-standard":"^4.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.80-renovate-non-major-27a68c791ee7452142f1238526b844d688091684_1604212371448_0.8634624312033967","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.80-renovate-non-major-7255989fb5d4c0aab85689d20e55348e9e63019f":{"name":"@balena/jellyfish-core","version":"0.1.80-renovate-non-major-7255989fb5d4c0aab85689d20e55348e9e63019f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.80-renovate-non-major-7255989fb5d4c0aab85689d20e55348e9e63019f","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"521f264a84c358ad720e094161dee77087f3f005","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.80-renovate-non-major-7255989fb5d4c0aab85689d20e55348e9e63019f.tgz","fileCount":94,"integrity":"sha512-Md+/cx3Ky86IpIZGbcX1LYG/lhUuuUO7OgLEaa9X8ge+u9ndSBxclqVy1WMNbkhCPS1C6fTgG1pSeXmFMPQEGQ==","signatures":[{"sig":"MEQCIGSoWx2IBKxI7V6SpyKIABXx3kr8bS5LtHT+vWPDlTY8AiBD+At21GhkPTmHJu3kcqR7sw2rV03VgjCKtaENK0jGJg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":569014,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfnrEgCRA9TVsSAnZWagAAk1UP/jIdL7FE+nK74AuShnXS\nOMB0/ECcyUmdow4lVhN8NFYrP6je76zqCxyQYI85dsl0kp1p6aH+gRoFwaVm\nC39yBvevMrrtmnrVmJb6zADBDYlPVdTsLbaYYHEwxxuHYhou2ootluOb55EL\n9Zu6yN+qvwEd8oaY2th7p/xBe/A88XFfGjBOdS+o3VhWabSS6RXd/z7L6t+t\n+T0cTMv5cY/GRTTjTRLU3vPGxb0XsvsRw8OExGyIjqYnVHCqGTQIYAKVWn0H\neAsUOLRXnGodwFsiZnp+crf802UFxDTeAHa4yOG1JSCtavXwWiIaLKsUZwuw\nIHMiV+e8AdwMJTuwfFBbgKpJ9DAf/LoCo5mH6kdasKQThlQDPsjiK6DGm43g\ni2SWBY6EQ/pt/6GWVKRMUckjem7KPEI0KzZTxmYZWE9cXV4MtfpA9lg0nzEF\nYvav0shPHWFm3ZZfn7t/tJHVmiLF22h3x9Vzt442vX5TtAunrM1QdiJMz9lJ\nMJexZ0VibggXrcFwV2Z3xoZMf3i9SuiV6TiEIUhMxJY/CqZ3aCLsRzxv4IzQ\nY7HJXnbtRVo7p63vQ9WM4ePYyCXuhryjRdhZ3DReS8Jn61BiqTnqdCFjR0Wh\nj35W9M6CuugiXkHzjSDTWAnoOeOglqYoiRZ5+sysqCdMKkn42JiKBDRSew0o\nQ3D2\r\n=jX/H\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"7255989fb5d4c0aab85689d20e55348e9e63019f","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.1","redis-mock":"^0.53.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.21","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.20","@balena/jellyfish-logger":"0.0.124","@balena/jellyfish-metrics":"0.0.142","@balena/jellyfish-environment":"^2.2.44"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.6","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.42","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^15.0.1","eslint-plugin-standard":"^4.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.80-renovate-non-major-7255989fb5d4c0aab85689d20e55348e9e63019f_1604235551908_0.4400339526495498","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.80-renovate-non-major-e1241a2d7a5499e783dcd5554c12bc5c67641b77":{"name":"@balena/jellyfish-core","version":"0.1.80-renovate-non-major-e1241a2d7a5499e783dcd5554c12bc5c67641b77","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.80-renovate-non-major-e1241a2d7a5499e783dcd5554c12bc5c67641b77","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"87c909b6668e499ba83e1da3589c22ba8353a467","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.80-renovate-non-major-e1241a2d7a5499e783dcd5554c12bc5c67641b77.tgz","fileCount":94,"integrity":"sha512-ifKxFEg/fWPao5Pe63ObuqmJihpEmMf6qCsZ+8Fz2lfcOZfBV83CrrKhk1vwD3ma6+CopA3b5yhypH7R9lo8EA==","signatures":[{"sig":"MEUCIQClNQADr5fnNC7Ef6VmQG9nL5yU8OlpIAetqVaZVgBf+gIgER7ENSyKXls5fjXTgGVX6eWnKtVQkEEbdFkw7vqi19k=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":569014,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfntcRCRA9TVsSAnZWagAAAqwP+wV7zQX9vBnpoy6Za4vf\nbRReXCtG3n6BVSuMaNxT7f3a1N1gE0QOJR6HkI0wklS3WMfi45ez3DTN2HsP\nimsgvFvNBM8t5uS5oQ3l4Vzmjd15oDKXr/IuQBi0AQ08OLtlqG1tm9iXsXsR\nYwPjh5hAA16RyQMbw40G91HoieIP5j3l10rbrcVoRH5p+/EJgKlT/DHA0SKl\nXz+ZHRWS9jrKPzM/1XBNjoUsq2vSG/pm/ixV7Y9cC8OxUZkyOuaM3t3LlNhP\nnbaa8eE1Fi1It7BE9301jicBvnTIjqb032rx+WiDkB5qDXnlALzH6TQwXQuj\nr6QrTCzu8MpypoEst8Bl70chtaGoDbAiHF5Dlir84JcTZ6+g/4WSpGn+m4aq\nksrghgclZno9sRRSLiThZruY99ARS0p6cIdg/YobiIBlzUY92Zmy0kUkMR6+\n1YuI+jGKriqZw42mcsnmkymeHgiIIYglw+orq/wk3hjtA8INcRBuE61OZO4j\nSq4zqDvHeH6YMQpjz4FHDsw5kOxtfi8B8o41hWWr5qRpN9mDEcc4+vadRHQH\nxnciQJK2zUPAixQ96fqCoMy8NkFqbBdog9BHLvE5/h0SNbf4XXf0bTgH0pxP\nINwb5J/CcY799djn6aVNqPnzWshB1HDsGkvU7z+4iDMMd3cxNt4/8NSJObr3\n9Wht\r\n=8A8x\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"e1241a2d7a5499e783dcd5554c12bc5c67641b77","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.1","redis-mock":"^0.53.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.21","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.20","@balena/jellyfish-logger":"0.0.124","@balena/jellyfish-metrics":"0.0.142","@balena/jellyfish-environment":"^2.2.44"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.6","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.43","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^15.0.1","eslint-plugin-standard":"^4.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.80-renovate-non-major-e1241a2d7a5499e783dcd5554c12bc5c67641b77_1604245264947_0.4269851734657202","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.80-bump-test-images-837f6efd8ff8f422a7b20625248a3e8b1ac67e29":{"name":"@balena/jellyfish-core","version":"0.1.80-bump-test-images-837f6efd8ff8f422a7b20625248a3e8b1ac67e29","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.80-bump-test-images-837f6efd8ff8f422a7b20625248a3e8b1ac67e29","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b15c71d7745e26924f66dc41e50a30d95f7863a5","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.80-bump-test-images-837f6efd8ff8f422a7b20625248a3e8b1ac67e29.tgz","fileCount":94,"integrity":"sha512-s0lRPmzjPVm58/a2ilA+kkdsRlqhkIT4S4nX9G6BClJK9iIK8vqIBp+SPTeADfuxpLtq0BJqCiZy5ap/3FHNZQ==","signatures":[{"sig":"MEUCIQCvAgJg61V2owRir6YeuVHR9e1tODowJh78+TjEZCDEMAIgCk1LbxkGKjUv/uHguefonobv7dQgX3qEDfvxYapXMj0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":569012,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfn1uzCRA9TVsSAnZWagAAMzkQAJ4WlA1FW41CVa0XLsJn\nYSDiyIr/uSNfhSIulUcJ+DSbI0PnfhikyQd13zNsBnDHduJrLdvnNQjBj9wl\nN9g90embNHXUtII1WRpJIytZHtowoprRm4dqT2t5//FcHnzu8e+ZiVzH2dzK\nem/XKFLhMxNOkDP/BKwSQlNb15i6JQ04MqTxmqu9g7zM/roEY40AvlwaW3b6\nXAIGBfq7pCWkaCJo9UR0xAiP5oDOG4SNggCDjHbWF+MCVwzoKQkkTVQU8Xow\nzmPR6U+lbspH5LOtiMcbNHg+RPj8X4G9I6Z5GO+nHwS+QQcvPx9oX8n0QPmm\nR/vlkcpxyMnthxB/hvr4spqqorIFe5ue47aPOx582olE3oaYS1D2P0ufd080\n89HnnUEX6MhbE6a1ZmT73gYMxQMtH8ltfOEaSzbTZSX9v4giaubFql6oE7KO\nnJTEyeErfPyFIKoa3pzV4Tpie2Et4Zht9zCTGGYbYaz3eLUmwHQAuejuQW60\n5wZ1gzAzSy6nFlQgPFpn/cK9Fc+clLWYfCGwbOEBuW2y8Eww9I1R8VUBXfXn\nNURnrxnr0aG1HRcr/aMaJu5DKrch2pfiFlLQ+wr9OOyk/aMX4YHUnO2gep4U\nxZ2Z13f0xo6wCrwzE74nTRddrrfdzsHZBvWx69lJUhSt4zZxXC647bsM++My\nz8e5\r\n=yxcX\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"837f6efd8ff8f422a7b20625248a3e8b1ac67e29","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.19","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.18","@balena/jellyfish-logger":"0.0.117","@balena/jellyfish-metrics":"0.0.133","@balena/jellyfish-environment":"^2.2.41"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.38","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^15.0.1","eslint-plugin-standard":"^4.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.80-bump-test-images-837f6efd8ff8f422a7b20625248a3e8b1ac67e29_1604279218993_0.1641907237086211","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.80":{"name":"@balena/jellyfish-core","version":"0.1.80","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.80","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ae936e5a2adfde1c8c94e7f13c1b29f60dfab4b4","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.80.tgz","fileCount":94,"integrity":"sha512-m90WyDcdUkjnZ0v/pDpouIgjqmiyjnMeWb1ZfZQd0skHd1EkGVccI2OBII0pNq55NHQ6u130DHpU/TXfarRYKA==","signatures":[{"sig":"MEYCIQDmWGquOpqRuUIKpY77XQGkF4KK6jQ3XZB0HWWhR9TP9gIhAJ9l9sZFsDebVX34XISBRk0s93Vae2lu0Z8+snxfpbxw","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":568954,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfn101CRA9TVsSAnZWagAAdCQP/idv7kLbCo3z4ju5+ANt\nUOTnPlzNyJbkk/f8NQ0ddJYQtdIjQHlNAjrJ8Ib+3cmiIzWPG5PN4maDuL9+\ny3ZYFkmkqLHxoZ4xv3qX8Hx+5RH/tb15btWcfzObR3D4X15RyodBLWWOx84B\n2XKLEu2+A9QXWfH0azDoXnZYk5uXudrrced3PuWwnqyCH17Jc8YXgljIeM9u\n6z7Yjfbd2f1xUHuR7+HfV102VGvRQKrSWAbvz5675XqfGDEvlU+o5FtzRYd+\nH2uq6dDDuB3l8tkj1oAD+oH/GUVKeiGVQQb30UMvVstdV95+oBt98B3aMIqE\nvi0CPFq+PIlqez8F0DSvXC2Ee+QAab74+UuHC0dXDE6EwPdytvHHnvvSxWwg\nYw4fn5rW+xIg2sB7N0nL5yBaqfkVlM5eMYIFIcM+EEugyZSo99zOAPQzexpf\nYtjMnrl39DlyhOlfyPNAhXmlAY5jgiol/MLgF8cSsd7HaBu2aprh44pUqZhM\nSYDxaBWjfghjJ3OfgpLNwnGx4BGf6b+An4OMy/CLasadBTsTbmpPbmQEQQ54\nZszOozhlxKXBTCM2g9LPwCHEfDW6OVCTnTboTqfQmY4RPKgklDw3nooUWmxM\nbiAn/3mfFtB7mgb3yakEtvq16ICeOh7dSZcl0Ztr+VWUrtVDK9YO5xIqVLdW\noUXx\r\n=v0D1\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"945c3d2e52e964b8892558aa0e226f563f162ccf","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.19","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.18","@balena/jellyfish-logger":"0.0.117","@balena/jellyfish-metrics":"0.0.133","@balena/jellyfish-environment":"^2.2.41"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.38","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^15.0.1","eslint-plugin-standard":"^4.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.80_1604279605096_0.49232466746125625","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.81-renovate-non-major-8b5083a780eec87fd108eb659578ef89080fc04c":{"name":"@balena/jellyfish-core","version":"0.1.81-renovate-non-major-8b5083a780eec87fd108eb659578ef89080fc04c","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.81-renovate-non-major-8b5083a780eec87fd108eb659578ef89080fc04c","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0c88c42ac38db38f1ffe1683992c2c0f3cc49423","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.81-renovate-non-major-8b5083a780eec87fd108eb659578ef89080fc04c.tgz","fileCount":94,"integrity":"sha512-gl5TaRGhp2tsal6K2IjOSP6Ub/at8oxCjvFFQvdQzGYLW3a8G6pN31M9fg21J2dlTjZtBOmK0nnaLxZDeYR+BA==","signatures":[{"sig":"MEQCIAsYgWq6vJq3Av+XLnTALEp1+E+baDqGo0kLOZjqpRnxAiAbljdtUrddfcc1NJceCU6EKSoIymuthKbFzLydVzanEA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":569080,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfn131CRA9TVsSAnZWagAAHfsP/19N0WuAxuNiKiPLu7Rt\nY73tBTJ1QbkVxJBCl0VqvURsMZwJIwbAnJ42WigSVBnydAjwYClNCQ3znIP6\nWK0cg7G5scvsm70rXa4992dDsu86rlOmsXE2ivCukrprXNYuDcbG1C6kyzXX\nUF3RDxrpKl3/eAC0CfIATplBarWQBzDltEEa4+fFZW4WrYg4ok1EW4JYgwGC\nQdU9YGr4/wFbRE4sq86whgPGefd8RwxSqNDKeowq5PO/kp63xli4LMFCZuwZ\no9BH96WKvmuyXwNuJjCumtMf+Z/DhxxL6tyN477oT8dnwG5qNNFow2sqnFuy\n1W2gZ+XsTuErgqok0BcbX46NjRESuqfdOWMeDlOG2GqtCA9j63C2Lo75rW/8\n4qsSoWxqybB1lKNjSh7+OYTlhLWZWY/wPzYOvHAjaWtp0c0wlst77tPVvXsE\nyGJBNmo5EANnnztDgQ5kGHhDaO0vK8hpFoN6haKA7FSGguBrlr3cHIQVJfwC\nKPvN/wOZCMSzxqJQJjtkjcwzVYNY7GWChrVFP0WEitnXBjUZX3KmoEYYWY+d\nqHsUz0drllAvPzR0GNSOUqKQR/ViIkuNg6FHjRtI3FYUB6wJQElpn+gCPUa6\ntozTxr9ukuLfkvQRj9G50nK9lweRlIif7ly8nR+jhCrH2EpHt68oqM6O5UB/\naGM7\r\n=gGHR\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"8b5083a780eec87fd108eb659578ef89080fc04c","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.1","redis-mock":"^0.53.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.21","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.20","@balena/jellyfish-logger":"0.0.124","@balena/jellyfish-metrics":"0.0.142","@balena/jellyfish-environment":"^2.2.44"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.6","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.43","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^15.0.1","eslint-plugin-standard":"^4.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.81-renovate-non-major-8b5083a780eec87fd108eb659578ef89080fc04c_1604279796412_0.3264543010379588","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.81-renovate-eslint-config-standard-16-x-7653594f93ade96b48f7a0d093c55b4110be7d18":{"name":"@balena/jellyfish-core","version":"0.1.81-renovate-eslint-config-standard-16-x-7653594f93ade96b48f7a0d093c55b4110be7d18","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.81-renovate-eslint-config-standard-16-x-7653594f93ade96b48f7a0d093c55b4110be7d18","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c18a5bc355785cf2f181736bb2582f55c41f5f77","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.81-renovate-eslint-config-standard-16-x-7653594f93ade96b48f7a0d093c55b4110be7d18.tgz","fileCount":94,"integrity":"sha512-CydD4Q8+tZ+u0ZeJyDEAJMa1PKdCWFlkL9CGbACyHuw9WlX+qqdXhrkVrGfxFz7Gyva6pNOVEDB9mTKf0O2Msg==","signatures":[{"sig":"MEUCIQCoUar5Tnae9ednetby6J5KBCj0S5UOU81dk/EfReyuuQIgZMn9JCdi+oVyVCwlMUd7tawlE/H1o4bQ/IbTAl1uGhY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":569129,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfn15BCRA9TVsSAnZWagAAAGcP/RGGrEUKIMc3NwXnpEtw\nCh3cgBEaTT34M+Wrwco6AYpYbjRTpLm6+KjvjJcSl7dNyjTeWSCNL97gnExi\nvWOXdFiekIcfqdaMbxqra5jTLayfMF+C9L/89r+5jVqNxgL2IgedKbljWKTU\nhMfUjr67WkN3Zw9AF9+Nme5Ps22CZTQI65xiI9rU2+4CsgCErd0Nbafi7lml\nlOlOtDS8SeGMR+YGrhrIzC4hZdplbX9OeRiDULi57I/vJJsy7hincXt8BC3Y\n/EgyrVMwPNxHvl936bLjKO5PfMfOIKrOGumtqN3dkiPPUWB9y8WOyUUrHGfZ\nz+rr1fU/XhF++zhDxD1u1gdL8t4GeJq0/WcSQLjz/AkeWmsXOy410JxkT07f\n4xd1n3Sjhrc1Q8s4AqPVX/Dc6WXRU3tTG0A+VpVp6Ocj5vSOj+DOw63jDLb8\nw4Mjn4Kyakirnh/+1rGz17OZl6ri7+XuqsH77wRdw6a8Uek45k27xtmOBKkp\nJ6tqJ2dK1mFHT0XCQN9A/BL9HLTJtpI6yQCQ+m2nkfJTcWyAx57o/X/xSncH\nQTdKWMV5YQVzP+dUtorRg0kbkFgqdsf7gvLXim89W6sJ3dfMwGWxHce7yBPC\n5qvT5NhcLGQqseE11DS/1/M0LG9EBxFkNeP3K/5fE8XYKc99Mav9D4ha1iYt\noIVP\r\n=kjtm\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"7653594f93ade96b48f7a0d093c55b4110be7d18","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.19","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.18","@balena/jellyfish-logger":"0.0.117","@balena/jellyfish-metrics":"0.0.133","@balena/jellyfish-environment":"^2.2.41"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.38","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1","eslint-plugin-standard":"^4.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.81-renovate-eslint-config-standard-16-x-7653594f93ade96b48f7a0d093c55b4110be7d18_1604279872824_0.021069406323487527","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.81-bump-deps-e2ea4ae3fb67c2369e1bdd15f11e0047d88b242c":{"name":"@balena/jellyfish-core","version":"0.1.81-bump-deps-e2ea4ae3fb67c2369e1bdd15f11e0047d88b242c","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.81-bump-deps-e2ea4ae3fb67c2369e1bdd15f11e0047d88b242c","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a2351df22c54be1e1f9f739060c784f5002566b1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.81-bump-deps-e2ea4ae3fb67c2369e1bdd15f11e0047d88b242c.tgz","fileCount":94,"integrity":"sha512-CUDitZOhH3WBNl54cu5NsNxl+38vVkgvLvavs5pC6NBcpCfoZt0zzgcH/QrZFTW/n9Td4QDOw4dPUpt2V+2Kcg==","signatures":[{"sig":"MEYCIQDagfPiax6rf47Z/+fhGF1rwMx9OBk0903VF6zdIunWAgIhAMObV8ql/5K8509Zvl1+ZPedoCbu0oLrXrE/xToYk+Yo","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":569065,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfn4qgCRA9TVsSAnZWagAAchIP/iXkSkp0sEbyyhIdi3f+\nfBJJjVRsAvwKlEJV2qB7oa3ZbNn4lbsbS+aaRqYIdWcWKYVNQ0VS3OXMBRR1\nKDwneUtByD97Vlpw9rOb48r1JTdKHwNwbXPr1S+Mh/+HVF/0d1lDeEgXDqzs\nRunFljB19ymXXynwiB9ZV58aHHphxm9BlDwHe2F1UZrNcO4VozPBCqScolwG\nlqybz4v+v5XRoVk3OMHn+VzkGE7Q2wPTGPL7YHXT5kVaze4g0loLJdHyolfz\nPXXufMIQ016IPXC6O7BBm2aSYlYhjC9JyhWNIxt8tjE85lspXy5UQYaQtpF0\nfarsRpeKGtRn8Vn54OOutPrMN5kgVBAoE6ZAiUtTJp0i1v25izZzUlMpYrVu\nsUIPbWCtPQmc5NPjMl+e4v+Kw/FK16VYHNtpF/D10rNGIjwXlv6PpwObVq/R\nUxv1GKTn+dBdOjMsDHcsX7ygL4+zkUkHwn5vH4HYj6OsxDWPHWchBisdIlRI\n1pPilb2bp/7BY7K5pttewVPhvroGRuEi08SFdZFddBe+w50yrD4uxD27Wm5C\nfhKjWHXKLZ0BrxhqChjEN9UK3/ZWvQR3SjmF4LANPSRFSaqphspCOehti9u9\nEoDCUWOBL7rFvWMyBftEKhiW9T2ZlRxxoQ8MNXLqF8utUyXrzLIUw6BL9ZVl\n+zGL\r\n=dh+n\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"e2ea4ae3fb67c2369e1bdd15f11e0047d88b242c","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.1","redis-mock":"^0.53.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.21","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.20","@balena/jellyfish-logger":"0.0.124","@balena/jellyfish-metrics":"0.0.142","@balena/jellyfish-environment":"^2.2.44"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.6","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.43","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^15.0.1","eslint-plugin-standard":"^4.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.81-bump-deps-e2ea4ae3fb67c2369e1bdd15f11e0047d88b242c_1604291231480_0.5433648740050943","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.81-bump-deps-2026f3fc48590db56ed5a35bc6b198032b1785af":{"name":"@balena/jellyfish-core","version":"0.1.81-bump-deps-2026f3fc48590db56ed5a35bc6b198032b1785af","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.81-bump-deps-2026f3fc48590db56ed5a35bc6b198032b1785af","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"39501d51253c571396ae182e400feba047c180f9","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.81-bump-deps-2026f3fc48590db56ed5a35bc6b198032b1785af.tgz","fileCount":94,"integrity":"sha512-uyPc76ApdGyi9h+Yrszpphna6waGVOBNfv2eKrXeCq0HvlMdM5+4iMwPDLe4+KS/czHg4ym2CSqUwzBGThSblQ==","signatures":[{"sig":"MEYCIQDwAw4OlPSk2kSHsg5xgKGpWaVMdpLY/zE5bu2dXDEs8AIhAJDk/SN5aAnEcKY0L1wYp+EXthF2XzCnNt+mg4zjtloX","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":569065,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfn4yQCRA9TVsSAnZWagAAJ1EP+QCuhJ380hZKJNV8609q\nZf95HBp8KDE/gn83coQYA0hihDMNIxjZtz3MJ4lWLjvAaldOnMWdqmavYbe6\n8nVzfLGqFWLd6hm+Qe4rbgeaLX53ubTqOPlrn3CtiHev1ENsQsr/5Zr130Yw\nVdxV805mks2HXiTsag7HXsq3CXsjRCWQVE8Uewiv0aXmpG8fFsHEYiHm0B6g\nEwEYwiPxtjJpIYK6xve7elQagkKyoph7/+jmarwh0sHa4YILrXZU9V6YYfg/\nhPpYgxtKHaQ7aT7OS3zaHKCR6N0UUvCGYfeCIlClBujKjUALR4dYzTxMRnhM\njuwmgaRvhW7LncL9A6moXgpSa+90HabYB9pwm9QwcyaJvUdVmKMcXaEUmdu+\nB52jfUp7pCgDEXHqJkTZ8KA9ZdSAQ1aFb302y/NvuXy0SVOCdyI0rr8UpM2u\nkeS7d8xD8/SoGX11FlXDz/+5WRObPfGuVMl/WTgAirz2SFnpWBs1tPIeHI1F\nEvVJAfjyzRrLQBDzyBC7SeLDTVX3RJSAvmN8it9jTtGtKJ09nhH9z8p01TD8\naJAdwkkWiRPx20XPGWbAa5rtKfuvYxls4ZIZvvkF19/33wKxfaFgPJoLRAR3\nnkG4vrzpgImxM4bMJyIp0DOLootn/V3LbqsLM9DaW3dADfNL34oYnKdoGvC2\nX5TM\r\n=uR7F\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"2026f3fc48590db56ed5a35bc6b198032b1785af","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.1","redis-mock":"^0.53.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.21","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.20","@balena/jellyfish-logger":"0.0.124","@balena/jellyfish-metrics":"0.0.142","@balena/jellyfish-environment":"^2.2.44"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.6","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.43","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^15.0.1","eslint-plugin-standard":"^4.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.81-bump-deps-2026f3fc48590db56ed5a35bc6b198032b1785af_1604291728211_0.689891169568597","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.81-renovate-eslint-config-standard-16-x-154fdf1987f6805c4967ed9dde2be7f4bac9e54d":{"name":"@balena/jellyfish-core","version":"0.1.81-renovate-eslint-config-standard-16-x-154fdf1987f6805c4967ed9dde2be7f4bac9e54d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.81-renovate-eslint-config-standard-16-x-154fdf1987f6805c4967ed9dde2be7f4bac9e54d","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"dad40679124ddbd8cc2855ebdd25f1c66ad27447","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.81-renovate-eslint-config-standard-16-x-154fdf1987f6805c4967ed9dde2be7f4bac9e54d.tgz","fileCount":94,"integrity":"sha512-DVupDUgTA7xKRtwYHitha9NzaLjORxGurkslAf3u/UhrMW+asuIPbaTw/7ZsLXQz3bHxmkEZg20ExT4s/JT5Dw==","signatures":[{"sig":"MEYCIQDIeb5uuQEXXT5bjSZ/vMbheRp/CsGwkL8BV7UjvWEixwIhALB0WizXF59/HWsM0XoM8vLzX1V7Hn1yUbRzC/l5txL6","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":569136,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfn5g5CRA9TVsSAnZWagAAyjEP/1InHDp/wOb0loLeQK2Y\nroZBb1G9y1Ta9tz7IapxwcAqk25j77p9KE+9FK4a0o3T5bK5qIXhfZGZfdob\nTqHrp1PVJEF5a+u8ipQRFbSWugZYo3bc8ejN/IdWYmT5GLavCSCSqNB8i8yw\n+8gGP6FsVy2/hTydhIQFp1TFUsExH1bj6NkBU/9PmMgW4I2eTO5VBlaaSWcC\nACrGH24S7rxN4l+JyGmQ/vfkrPaeE4+3y3/K/XP4/UONJLEuAoJLlO18rHGx\nULplLnAPchkkcs1JTZH45L8RloBcIORRmRf9pyF+F7LdGJSTYgk7veh5aLJO\nz+rkHLg0/xpWUARWJ+LkPuBjPSHC6YlSPl8ok0aNl6rpoVB/Gw0B1UDW6vZt\n/jsC3g7VkONmzDgYJC6JwYy77S4VnIvBhDAJLnLBUbfUr0/ix6QcXF5HNJDn\nPx7DpdxvXq2BmgzsV+iHFESIhVfV41fFwGu3c0FtW54k78rv4xMOcpGA9ALC\n0hS2rOYQTzuXvlrSGvRa+M40xzTTcvRLfr6xMTyUDA8cIjQcTF5d2VEOSjMl\nQJRP/tJ3Uoee2U/GlcwtCMA7hJ3mdUMZ54I0gU6fHWqwBo9QcGPl3joL53zP\nk/9DngZfbTEJBeaNSi/uJ+S+4U/KygBs8tLJ9z30GooIwXO48dUGxZ9xjMSs\nlASL\r\n=H961\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"154fdf1987f6805c4967ed9dde2be7f4bac9e54d","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.19","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.18","@balena/jellyfish-logger":"0.0.117","@balena/jellyfish-metrics":"0.0.133","@balena/jellyfish-environment":"^2.2.41"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.38","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.81-renovate-eslint-config-standard-16-x-154fdf1987f6805c4967ed9dde2be7f4bac9e54d_1604294712737_0.46008679142578357","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.81-renovate-non-major-0f92595f33583d599a57240e0322e9e12aa9def5":{"name":"@balena/jellyfish-core","version":"0.1.81-renovate-non-major-0f92595f33583d599a57240e0322e9e12aa9def5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.81-renovate-non-major-0f92595f33583d599a57240e0322e9e12aa9def5","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9ccac65e9c782ed11ad1f4b3114ef5efcdebb05d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.81-renovate-non-major-0f92595f33583d599a57240e0322e9e12aa9def5.tgz","fileCount":94,"integrity":"sha512-6VY9dq1n0/82d7M5CLW1i76x+BNLH6S/ibpNAhKb4GVjAm3V7QlOkd/9BxikzA4/49B01f/MuzI9fQpB4inMTw==","signatures":[{"sig":"MEUCIQDE2WK297Ms6fkpy3TZCo1O0XZX/1mVFvOyKPx+uzyc2gIgL8uGGLYC2sR99GiSr0k9oS6g2h70yPDipa5oEVn7qs4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":569080,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfn5i7CRA9TVsSAnZWagAAGw8QAJA5fq9U4vDS/S8hhrst\nE/ddEBOaN29k9ndOdjZaNLWZ7gUV2w7LX3JuraFsaA/WVCnkRwu+Eea42+F8\nf9hDcEONj/488f2T4ZAQxXj3MUS3fkjTdHvFCYHg5yTljxVbDsdM0f0SSnQN\nj7k1x1v5rmjJGeCQiHOdbV0hnoU+rk9vIlpvcx0XtvVfveVctCUYLXWe+KZ5\nnQO5yKQ+XJxAghf/TF800tcLgpHP07E2IrtQuQZDvtdUHsxTorUorLMGNomN\nNf8fQ+6JODVk8F+Q3NKJWtYOyBXGyUFQ0Lww6Kdo7RsUI/l+Z1HHLwUTkPvz\nrmXw/aJcEjAsjJDD8rSB9WftU31h73kSGdXESm1W/tSn50joI67VGGlxSlMO\n0McOYb6Mv8MA5Qzm8pd4EXzYmY2yJOKzsuGmZNCBNyeQr3itd15mDAIveYkf\nzN5MdnYEdX3jjs0h2YKrk/ebLnchDWStUdPg/GkMm6PH7e+ni1hrJTmAp8os\nX5aV0indhpnOgod78DxGnAxuqoBwybeidEX5gQG/Hgq2p3mBAKPsxJ8Av0c3\n6wWMRt0mtJT07EEpynC2K+u5/Lf5lHxQeXMabwm18MIGxw6sZqX6lWLgAIGC\nBXY5MsoSvsV5GgvWb+14nOGa3t6Zr+c6HU24Q0FL/eCMPVBIBvK9c6pFbrGE\nAR2q\r\n=hiJk\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"0f92595f33583d599a57240e0322e9e12aa9def5","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.1","redis-mock":"^0.53.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.21","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.20","@balena/jellyfish-logger":"0.0.124","@balena/jellyfish-metrics":"0.0.142","@balena/jellyfish-environment":"^2.2.45"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.6","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.43","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^15.0.1","eslint-plugin-standard":"^4.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.81-renovate-non-major-0f92595f33583d599a57240e0322e9e12aa9def5_1604294843051_0.17835079788410502","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.81-renovate-non-major-b46dad1778668e77c06d4d85c77dce09d31bfc2e":{"name":"@balena/jellyfish-core","version":"0.1.81-renovate-non-major-b46dad1778668e77c06d4d85c77dce09d31bfc2e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.81-renovate-non-major-b46dad1778668e77c06d4d85c77dce09d31bfc2e","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f4b987060e86b309ee234790f0dc9a01cebf23a3","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.81-renovate-non-major-b46dad1778668e77c06d4d85c77dce09d31bfc2e.tgz","fileCount":94,"integrity":"sha512-8WOjy0RM9tldvNEVVsRvj+SrTZTqmzB/Q28vUXMhO9j45nn6wlNpTbt15FZ38dW0cg3bjWSrBXlAf/g1UgNxhA==","signatures":[{"sig":"MEUCIE05J88Mk2a16hTxCwLiLYYJFilSmX4W6BeoC2d0qy9mAiEAxX94cPNLlQMxyp8v+BqxiY37nFA9KL1/EXHobXcMw/g=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":569080,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfn5mECRA9TVsSAnZWagAAPWIP/RrqEs0Aep3L0IztopXI\nwN9cfm61PdrIXBCMNlB+KzbNtYz8eJ3oBaPUQOo0Ts010QFVYUEWnHUD53sr\nrq8dewsW/XDICeWp9t1f8tS57YllGV4/tAqoJLVmPkKtsfXtqS/fctOIFMRk\nXsDJCHS83kk6x6U9lmDejgWT1nZZGgtc2/FYYxPzTqBrD/C9s/DKgGUubTgt\n+qUVlLSHtMx5SRa6TGm3wIazpZnC/jfb/WBN1kfSmRSYAqnh05wzexmlHHEC\nTS8qGqok5+zZ4bbIMevt2ibY0bKOrnslQNab5syXxj1fd0regnoexlEGz2UR\ny4RkbmyoGFO3tc9vmXswQGDEj5/wzQW4KipcL6d0QncJRS+q2bIYW/Y7EHAm\nm3q844jlDrV+ZCSOWnMuD5Sy3O1RGChtsphroUc7jPU5tbqKFOWmpRj5VAK8\nrWHp2ympsODwn3s4jAhcpp+q3srSCvGWEbmh7yg12pouUn3pHhIfhwCDotcP\nnKkqdULYPozboXCvlvqCTa+OR7PZ9t5Kin1gxSGBfafW28Vk9TISZG2F+cIy\n7mXwzOPjpq1qpatauPhz/W1kSI1HIIX0u27+a9j8G5x4Uf/zw1AIFKXuhsMH\n0CBNHyIQzmcabP+1uENyBxF4u+nGfnIBosD9j0GMogKNQP/vJeGw+QEij2LM\nKexo\r\n=sknK\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"b46dad1778668e77c06d4d85c77dce09d31bfc2e","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.1","redis-mock":"^0.53.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.21","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.20","@balena/jellyfish-logger":"0.0.125","@balena/jellyfish-metrics":"0.0.143","@balena/jellyfish-environment":"^2.2.45"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.6","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.43","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^15.0.1","eslint-plugin-standard":"^4.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.81-renovate-non-major-b46dad1778668e77c06d4d85c77dce09d31bfc2e_1604295043965_0.2136854828815291","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.81-renovate-non-major-fae9e47474311aed79685529112069ef0427f6da":{"name":"@balena/jellyfish-core","version":"0.1.81-renovate-non-major-fae9e47474311aed79685529112069ef0427f6da","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.81-renovate-non-major-fae9e47474311aed79685529112069ef0427f6da","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"55c7d3b25ebd2d56d1d35841e3efef935b7a38bb","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.81-renovate-non-major-fae9e47474311aed79685529112069ef0427f6da.tgz","fileCount":94,"integrity":"sha512-92pellyM/jx6S517A0L0IbhbrlZS2Otwne+JuHR6DPNIOcJqf2N2GdZeJrOgyYIi1nOZV1FRttAQVfjhtXsmJg==","signatures":[{"sig":"MEUCIAEzntkz2rA7z6BQNasIuQt6n7AI65UoJ2u5a+yhIRFhAiEAngVuhzkoUtmNUxNKEgSMRN2/BbO+jJP5AN1zIjslAYo=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":569080,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfn6avCRA9TVsSAnZWagAAAzQP/itjIyAa5CrDiP7ONyv0\nQcPP6vDu1xwNYnECBRXygelKuYJNcedtuRUgjR3S7fgV1hvn5ooCAdH9qeGj\nX6bahSt7W++GWu3p6o6U6CDWGpHvtuF7tECBSS8iN8bx7emfyl0jjKvyoumj\n+Z6e6k3RFkhZsvdJfI95mOMRU/VuXz90UqLGR7xOshYP4CgqzaNww++2NGC1\nd3GhyKhfJNVhzMckvL+us+aSNI2XXMTr8XX113jMswMdMyMYFTGBwFzPJHFx\nqVjJwoK3Pm9YJv4vqPvKiXU9dLpSnC9rKH5FOHV9Oo7IAnn6tJ+vBeYRSAeg\nlGPf+2fUg0I09VpTzYMvdyoU3JfciGPhCFlcbf1s7WuxTAdZtHfSEOB5MQaQ\nDYrL/DJvWfu3GOn/RWN5p5MWftiQdTXW4eVixN7vhrnXiFx0rXn69fNpbUI2\n01/lhAaqenjHBF1cNhgA3bZggBHOTnOnU3jg6IzvXN8TRT3xJWgzo4e9O+70\n1bhETpQ4ePnwbGADs9bCxi6LJHaYdEe5GrvpJrTGTNWJIO+ZGBecYRsz9k5m\nzJQdzuc3/sVuUlSH3Gvka+RB9TYTKVpc9Q4OMSnct0lk+j3XxVfHDR47QCnl\n9nTKvhWrpqtyCQK7DHXVU1pjDxisVU5gml8wiQrRNujzstpq7827ETFJ0qUr\nmUip\r\n=L2zJ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"fae9e47474311aed79685529112069ef0427f6da","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.1","redis-mock":"^0.53.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.21","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.20","@balena/jellyfish-logger":"0.0.125","@balena/jellyfish-metrics":"0.0.144","@balena/jellyfish-environment":"^2.2.45"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.6","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.43","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^15.0.1","eslint-plugin-standard":"^4.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.81-renovate-non-major-fae9e47474311aed79685529112069ef0427f6da_1604298414973_0.5269843784111417","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.81-renovate-non-major-970e0b4a31d5229baff426ce8f49819112531757":{"name":"@balena/jellyfish-core","version":"0.1.81-renovate-non-major-970e0b4a31d5229baff426ce8f49819112531757","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.81-renovate-non-major-970e0b4a31d5229baff426ce8f49819112531757","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c73ac211a9f680502222097845f51b2ef201f82c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.81-renovate-non-major-970e0b4a31d5229baff426ce8f49819112531757.tgz","fileCount":94,"integrity":"sha512-ZnFRCAYGaaJff2JF9hul9YWXXwAlXNK26HQJRPnvLKqTjMMOt1I05vcN4D66FQdS8SPF6cEhyl5aWnVK2tk5Hg==","signatures":[{"sig":"MEUCIBIPhhYWwuAdZ08fSfXOhU7vI7vaEpYkSvIK/2Nz9UNFAiEAtInapWpcTR3Z9dSKzO56TPopkyXPYwjuu9xRKhJz2Yo=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":569080,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfn7oLCRA9TVsSAnZWagAAG+QP/1vbpV9P1wvx2eajY7pm\nP40O+YjAvWeBdT//OD63rfyT1LFFsAzNkM9PreakyyYOp3J8A1erd89k40Q1\nK9eu0PRVIFXo6q4WSHEzOGXeZlgdNkqjNNd71eVpeS1VhnuT88Mnv0XXMkKM\n2vFCdcwAAzK1u3OSFvX5fHMvqgpZzj5UCNf6y5yAt41Vi21zRgWnGaYRASw8\nhSL/3iokoZRCdHRBdNLFU/55XciDeXI/jRyTv9GiTrP/hJ2aPIL4qWwCc0i0\nrpwZBYCVKynTyyKcyFF/nzB3//P4MxFFIoj5nRpPEc2Ze+9NZRfKPHrLHaD/\nlQFH1b3bwbvqzkBmDbUceU8VTvn6hsqt9JkUPBvgXDt8+dhEZlOSl5J2436N\nhVNYWJOGXLFgi17ny6HJIjGTzA78+ZYbT3kavKmcNJmZdKWgsgP9WGE/xPqM\n3WOu/1fYD3Q9ZCDLdO0hxmE/r5C21TOAs7LINR6IWFC5nLnOpqe5wStYKiJo\n/lbDJUjyXv63xyzvaI2E2hFApMy71bTUkiOJchlh1O0wUlIYtrBiXktVZrdw\nUbUPIvN12TlobUnmbTpcc0cuz2Y41NeiiTFrbC6y2CsKbfm0VhA4x7lFLu6x\no9d5iLlmorafvSjMMG65+t/zocvTAPvgKF7LuyhQh2eZfHcBfqsu1CPjIvmk\nBGr4\r\n=/PIq\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"970e0b4a31d5229baff426ce8f49819112531757","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.1","redis-mock":"^0.53.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.21","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.20","@balena/jellyfish-logger":"0.0.126","@balena/jellyfish-metrics":"0.0.144","@balena/jellyfish-environment":"^2.2.45"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.6","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.43","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^15.0.1","eslint-plugin-standard":"^4.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.81-renovate-non-major-970e0b4a31d5229baff426ce8f49819112531757_1604303371178_0.35715073251147356","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.81-renovate-non-major-f830548ee7ed398d1e618d27bf03eae8beeff067":{"name":"@balena/jellyfish-core","version":"0.1.81-renovate-non-major-f830548ee7ed398d1e618d27bf03eae8beeff067","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.81-renovate-non-major-f830548ee7ed398d1e618d27bf03eae8beeff067","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6b7c75d30498fe3b7a7b8f2724b2e67319a484ad","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.81-renovate-non-major-f830548ee7ed398d1e618d27bf03eae8beeff067.tgz","fileCount":94,"integrity":"sha512-cNWORcUxtdUjUjQIpF9B9WKgDjTmyCk/s9VGxLUoLeqEXYWywuzkC+sRupj/wrYOUYqktFQFdeYmcFLUXkQ3Jg==","signatures":[{"sig":"MEQCH0qZGxxWls3OPrMYGw+gvcv4Z2W6NjL6WNFMl6k8++YCIQCnQ7DZlatVf4NrwEGWb/Y1tokN/MN9GoV4bLSG2jV+2w==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":569080,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfoCetCRA9TVsSAnZWagAA1b4P/A2UGX6Lggz6FobdjcuJ\nFTT/BooElyg7wmvi46qlqLL861vHONTcJZ6HgcjvJYn7j19nfdSQEdnIFmkG\n4dvLy3NA/RzsYKHxR2IQE4GYZkoPuRx/pf7a/biF4GjYqpo/JNOrz1ki38e4\nnuNzNZ8neHLPjrYNcLGn0Tg/+4z2l7if5yQCMatcFV2/qVhRK836wrQ+hVia\npt89NavazPt+oMU/c/YlN4/Px5bhf4B2N0OxOXSjNLj004oy8s/qph+YFKv7\nwXOla6uICbbk52Hl4zZsyBNRZkkqgQrcVabHN5jvDcTrpVGZQxy2BA9BHN0q\nu/pKucOOaYQqChvQmJvPlhoEavU8gVXRWm/fezdMHBmgbocwd5eBBeV32Our\nRDi1R3DZIqcV5WU71kHi9vszX4H2Cg6yTMg0T2uZdBWOBkujFVty9uiq/9vr\n92qmzU20FBVFTCczyxjTgQsOkxY3ZmPVrSEYfYhWtbeG//BQpE1KoWj7a2qa\nYmGYdDyoJtzDbrJahiBlYiX0Ezby1VEEdMmT0N8CWo5ZYvjgaVh7y/99Sttg\n8KL4j8g5HpzlgQx0SAuJeuZIDlxso4VnVklROrCFZq4/vr0lmM042DP3IatO\nuh1WidOmshJP/b4nfhobmpebCcixt27SknBlcuIw/GSfiPeLGg9kB/ux376t\nRVbu\r\n=qbrJ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"f830548ee7ed398d1e618d27bf03eae8beeff067","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.1","redis-mock":"^0.53.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.22","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.21","@balena/jellyfish-logger":"0.0.126","@balena/jellyfish-metrics":"0.0.145","@balena/jellyfish-environment":"^2.2.45"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.6","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.43","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^15.0.1","eslint-plugin-standard":"^4.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.81-renovate-non-major-f830548ee7ed398d1e618d27bf03eae8beeff067_1604331436658_0.28263228620063097","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.81-renovate-non-major-80e574110563b9116fd20fa7a3bd2611d0f8d29c":{"name":"@balena/jellyfish-core","version":"0.1.81-renovate-non-major-80e574110563b9116fd20fa7a3bd2611d0f8d29c","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.81-renovate-non-major-80e574110563b9116fd20fa7a3bd2611d0f8d29c","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"65d5c367045e696fdf8f7d82431c9127885d8cc3","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.81-renovate-non-major-80e574110563b9116fd20fa7a3bd2611d0f8d29c.tgz","fileCount":94,"integrity":"sha512-hE/857ZgXsMnMeI8nIhgfUl81b7ZXM1830AoELXwG/ga37cRddgYCpy2p/iCAG3X+9cu2vEMpxz1o6vWLSuc9A==","signatures":[{"sig":"MEQCIHuh4ekLJB4J0sBIud+Pzumb9os5AImdmS3PBpbv101uAiA/x8A0jysL2k/h7I04mPCjKnbPI1ARcuFDa+AoMj36AA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":569080,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfoDl1CRA9TVsSAnZWagAAnawQAJoIMPTlV+wHK88fIy1m\ndBN6UCKl8Vt709IexOaVLhrPoUiDceGfn/x32z51720bPfDHZ3Swe2CWy0L3\nh96c5MrfRAmKwN8UFBZoiEENiL8cOonXmEcppoqswQZbOaOAlR65sK5iG739\n939J8Kbo5cIN5cx2q5VMyXMR6x/xl4UEZUV1CcsUVn5JRCFz3hgEF9HHSJFF\nuJvAW8W7UDbFQVRkta8bpO47siihfxcvVaEONMDg1bF5zMwIEzmub/hI1BVY\nIkmp/ovtWxz8+r2p0nRsz9EHlO/YH10QHfxBqrxefJVb03F0J/0jnevqM8Wt\nE9soCAyNtwZXDecR8yspU8tuKnwxoo90DmRw9Pmj5mz9vTglR87D/PPzlfIE\nmFcrZr/EQO8eN0WzTj1t4RkNqGvT9W0fvWEpzTGpkyfoV4TTsED3nDx3pmWn\nMK4cMqs0b20UDGxLZM49crOW8THBrKvRZpPMXlRIeBDtnJaTN5q/G17bRJp8\nmXGPTC24mSlUJCDrMTJG+5e6R5/zSL+6yMpzZHO+ydu8V+6Kd4FfNq4FXSo/\n4/F+TOL1BW0VMO0D2HYLTk0yojwp1cSmqLBzfddCvpep9bdmhrAWGjV+M0Ka\nl0sNrQmkiaQtuWYNLMtAI9Rsq/UGDvETrrUF03dXHRSvp1vYKs3Pv5Tbptci\nHmV/\r\n=1N0Q\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"80e574110563b9116fd20fa7a3bd2611d0f8d29c","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.1","redis-mock":"^0.53.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.22","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.21","@balena/jellyfish-logger":"0.0.127","@balena/jellyfish-metrics":"0.0.146","@balena/jellyfish-environment":"^2.2.45"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.6","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.43","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^15.0.1","eslint-plugin-standard":"^4.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.81-renovate-non-major-80e574110563b9116fd20fa7a3bd2611d0f8d29c_1604335988598_0.16261330996257528","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.81-renovate-dockerfile-6367d8565cb4b772d7775fd4a69549eff1336e57":{"name":"@balena/jellyfish-core","version":"0.1.81-renovate-dockerfile-6367d8565cb4b772d7775fd4a69549eff1336e57","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.81-renovate-dockerfile-6367d8565cb4b772d7775fd4a69549eff1336e57","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f50a75099be313b284cc0eb92b65e370f3656520","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.81-renovate-dockerfile-6367d8565cb4b772d7775fd4a69549eff1336e57.tgz","fileCount":94,"integrity":"sha512-1YSfxSj/1JTtU9F3N1vDlsozNgWAQvn8Vi38AQAi7UVx0P/A+y4Ur84+JbOua4TtjarQ47Sww3KRRBlgP6ztvQ==","signatures":[{"sig":"MEUCIQCfZa5nyglkwSMCi49DSacrXgY9hjqYQRW9pSMAvD3P4QIgF45NKre0nH1cId8dcbBXmJMUzW4sizgy2R36OVYKEww=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":569134,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfoP9WCRA9TVsSAnZWagAA+uoP/iCzFy1Y3co7VLo2NL6U\nMb56HhrNFYoytvNU9fhnY4CHJz31O5qR+x8azFNR0fa8nZd0BTpxdii67xGX\nA5tKG1bW4ZUsvevSxOcCDzVlq9ubLfBL9P+r1k0WEJ5EeHkVIjgvUdlC7CQZ\nVowCKvkvdNwInBQK6LAW0VbNTZ9IrDzELnx9b4DfMiAcwhzeztOeMTVtV4xA\n+S+1kLAuLEj5ho8bMuN2VLWt8gNDp2Dj/y6AUPVqY2LJCvpg2p/KkKaO4dGO\nGuAGhZApd0V7gQ+9rLV8ywxX815dp2/TII4hkJUcMsTzPk/mQddMQdGpnEoF\nTiAu0q/pd/IbgUy3QDQ9xXpehaR11sDnTqTJqAefcyCCUB5PFnKBs2OJsJU8\nzeUymzx29eiBkyfYJnwiXHYWNxT7tv1N2lIV4xfSRwVLtXoYjiLOyE1W/Djm\nc7xcGFvE1FYUew99gJoGnXPyY/qusWYBvQtRjDzdqTxQZ00tviAi+cv62ZL7\nFAv3ulu9xlNt7dZwjitsFLtFianN1wKjOve6T9A9rz+kFUD5w0bthfhrEq7f\nYx5l5oJ7rzUVoU8HbemV4n5wCsW4nwl03foUVmTtdcD5j2nq36ouPuXwhaRv\n6YsHyXBQxzCqTq/xJKxM83ZZSGdz0SluZ4lG5B/MpmKx/BLi8snT119uW3tA\nuwKE\r\n=yyLo\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"6367d8565cb4b772d7775fd4a69549eff1336e57","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.19","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.18","@balena/jellyfish-logger":"0.0.117","@balena/jellyfish-metrics":"0.0.133","@balena/jellyfish-environment":"^2.2.41"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.38","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^15.0.1","eslint-plugin-standard":"^4.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.81-renovate-dockerfile-6367d8565cb4b772d7775fd4a69549eff1336e57_1604386646151_0.7749796160989588","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.81":{"name":"@balena/jellyfish-core","version":"0.1.81","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.81","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1d46a74d54b6ae9ae122e029f130511d76317a8e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.81.tgz","fileCount":94,"integrity":"sha512-tIeiceajkVIxhUgvSPL+iLGnlrOtSSv3YRguQPE+9NjpOiXbYE5TUriDwnK6yFIqCXJTDcQHrmv28sxJ58PDNQ==","signatures":[{"sig":"MEUCIQDvH4XuytiS+sBMX7Z3VGDAIDq8WkBXOkyntLvLGHidgAIgF/jwhn1yseP71WmiI1itic6F9q6neYAbHaKF0QyhJD4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":569073,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfoQBpCRA9TVsSAnZWagAAtKMP/3LrfvSM1zBnOxJyyy3f\nkbXmyAiAGYLyv8MGyjhCInkbR3sAvt2oSlW7R2eB5NZpqbN7ydXSeeTBOTBz\nvS+OKbcGLhCSIv+4Rv7HGQRN34rYRU2EeZF5WUvNHQxxd1VSS9YMfyuPJvOU\nuxjt9I2n/QXgkriU8JrCgSCDmfq2mvAqOo3SXQG4UXpWaS2SQ4dvmyL2wu8s\npcPH9LzzBMWmCglmcSUGZ5jPvMPZEAapOX6Y9ciDJOkon8kYLFpX76CbEyS/\nW8WfF5SBkAG3ZIsUrpkGKVan+2JDbMbKphnoTvqAxlQfXacbgjJ0M3oJ4N/R\nUy/1z7YMIWlCrItiKNfvxgTdidCcGAOHnGTJXTc16Gjsc1X76HOYSU+OTRTs\nzbtnLQHI5cj32zfFPRyap502mfDFTVFM+o6cXgN+u6snpvvgkrADOShWYr1l\nrIwNdhfdP7aq7gCGGIfs3/CsfiQg9Ta4uvDvT5reM4eMuZJglwgdY5whq/Ua\nS9BywjMEudeyN2dttH2gp9W0vt6RIf2tEM5ZVwZ8PIbcbzDoHZY9gvBDzYtK\nnV6xNLBxH90MQZG3d2ZvFGO7os8pfiV/rSGHWAxM0p5ep3Om57rtMaKjOTYk\n+tN+QNbThFlwOLiRZuJMSejO4NgMuLeNojxM88ec6UsYZ2UsVX+8imPeAF/f\nvKSB\r\n=cPBr\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"e20a127c5bf7a124cc9b46ceab43dd4bb2963619","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.19","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.18","@balena/jellyfish-logger":"0.0.117","@balena/jellyfish-metrics":"0.0.133","@balena/jellyfish-environment":"^2.2.41"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.38","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^15.0.1","eslint-plugin-standard":"^4.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.81_1604386921020_0.5773731569720648","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.82-renovate-non-major-d7f201b13eef642ca58b657a5c20169fe894e400":{"name":"@balena/jellyfish-core","version":"0.1.82-renovate-non-major-d7f201b13eef642ca58b657a5c20169fe894e400","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.82-renovate-non-major-d7f201b13eef642ca58b657a5c20169fe894e400","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1fdd2df9743f44ab9b87cc7c9cf674f2aa5a66cc","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.82-renovate-non-major-d7f201b13eef642ca58b657a5c20169fe894e400.tgz","fileCount":94,"integrity":"sha512-yAoEexSSjReZvSbn3vH59vLQGp35a1evrFchuICvH4zOFC8ANd2iPlvAcKlyKyZQOho4mQeBKPo/9Ro0/HYH2g==","signatures":[{"sig":"MEUCIQDVeE+XyhIXpSFv7brgD0rg2RooVEH98c/N/7CBP0l9kAIgdi4xUr2mkLMkh4rUwFkpKtOaW9mameSj115WSDPLE44=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":569199,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfoQFnCRA9TVsSAnZWagAA/4QP+gIUKDxRwzXWzcTSeo8w\ntLLDnmx4L1lJJqlUWqBHN9blQW8JjDXPTqBZ7z/Iz4tLqmnNlCp3I3EYS3GB\nUI76Ig0ceBPyMPup/mvGIAH1PgnoQeifm7z37fag2+EY6HX37Igshkquw4Cm\n2/PXdvBbe6rtFms06V2UQySmDtPXaJvDyUtoltofW/LPLbYn8b1aIPaqpTrg\nqWyFT5uIHKv2zn09ZfAQ5561DMlODb0X1w71xPIN43hzrTdvKEmoABl8Rq2H\nvkGMAxmbhBS+493VwDEV7WlXPeMkLFNnhEWwfBcj0cZE0TdcVKH4wxZCIK1+\nlSoGxCXsF6UkuHw94TAHC07dZX1YHOanQ4T+zPHfo6rVZYYBzxOk0PNY9fNT\n/rcDmXp6huFf/aoxumHvW+nLX6afFzqd/EqZIqh6LD9hd20KLpnqlh9w6dp4\nZRR39QGhccbltkTPHl99RN3Hw4hqcZMMHmseBL3txmWQboP3De1E2gQYX3FV\nyRBTpGkanFibb6obNL6bmISN5HVGOtSEAqG7tyTpXHtAX7odVQ07vmve8lrw\ncoa4Hwtf9COxniglpqAsZBITLwz/X2Q4nc4p7rLskK23A+se3TOC3vLwLHhc\n8yBqhtL073l9QaVEHeXyEBjiSxBrfMDnOO8GML5u+/KQuXVrVbMRbVyqcoj1\nlQqP\r\n=oJDd\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"d7f201b13eef642ca58b657a5c20169fe894e400","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.1","redis-mock":"^0.53.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.22","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.21","@balena/jellyfish-logger":"0.0.127","@balena/jellyfish-metrics":"0.0.146","@balena/jellyfish-environment":"^2.2.45"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.6","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.43","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^15.0.1","eslint-plugin-standard":"^4.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.82-renovate-non-major-d7f201b13eef642ca58b657a5c20169fe894e400_1604387174778_0.16724490485672217","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.82-renovate-docker-non-major-dfb90b49224f77770359dbb1137f33a38f5b4895":{"name":"@balena/jellyfish-core","version":"0.1.82-renovate-docker-non-major-dfb90b49224f77770359dbb1137f33a38f5b4895","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.82-renovate-docker-non-major-dfb90b49224f77770359dbb1137f33a38f5b4895","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"352b9ad0cf1b6d0dbc890ba1bcba4a95d5420213","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.82-renovate-docker-non-major-dfb90b49224f77770359dbb1137f33a38f5b4895.tgz","fileCount":94,"integrity":"sha512-6SubgkruIJmDrfG75qSTJH8pxHcBg7rBcEmvtmQEV+8xkiMOXgZRpxQuGccuLmTNORFuHRekc/k00XRv5dmKjQ==","signatures":[{"sig":"MEUCIQD6o2AXjpMW88XIAhbBPq6zoLmMXurjXAOX7o4+jtWY7QIgNN2tD15VDqS9HPgSy6ceXsucz+UNq5qGWMIWRxjQgHE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":569240,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfoQGSCRA9TVsSAnZWagAAhLIP/jhc1/faO9g+KgtO/J82\nxyk2bLryloO9ONsEvnXmZ9tlFx2i/o2gjfq73hrgDMG0kX4H0WuRXlaN8B79\n4laYIJ2niQXtfgrXBmJIJsdbKed5zy2VZemy7CEa7jCE99aL7z9/6+S0EZUO\npMQPUGenY5xkYMvrzWLw6DMkficOelvIr5n+Lz4CzkAbGlypJpldzpHXL/OU\necYfDFDrKshdW6X6XNCw9JmUo97QK8C184G/SyXlObTkcmaORqrZB9g9LctW\nI68PCm6Cw2kfwB9rb6TFCnOnMyGnFMQMMXdBgnVQs/cDuAk6MQvV/HwkmoEE\nhEG5zWhHZaA2BECtg30Tv9Z3TW7xIGg39c4CMua3PFu/eGU915G651IIx4bA\nZI8g8IvjIJoPUKOWRgRNVykVcYBmm31NJV3x5KPMKUxCr4Hmi9bWLSGOPJJp\nzJEMt5lbBGGhUPgFl63DY1wFUYFRp2apE2xsNmKWCvZ82F4FSmj/mlsl+m9k\nIDD3iTppCcPh7ikmWISQTDOswtw/aT13Djb897sQE294VtM5QVP7C8lYDf7n\nZ7Hy80YcZCcuQr3k3e9iaXprgpgreM8LOcEndwsP1VbNsLoqcIHK8Fq1cLpi\nmWv7fvkV1TqOJQy8Ew7Jc2uf/+LRWzDZtRI3+Jfq3WRn/yn99Fb/ObYnYlNS\nrPLR\r\n=LBlB\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"dfb90b49224f77770359dbb1137f33a38f5b4895","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.19","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.18","@balena/jellyfish-logger":"0.0.117","@balena/jellyfish-metrics":"0.0.133","@balena/jellyfish-environment":"^2.2.41"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.38","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^15.0.1","eslint-plugin-standard":"^4.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.82-renovate-docker-non-major-dfb90b49224f77770359dbb1137f33a38f5b4895_1604387217672_0.6588810676984549","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.82":{"name":"@balena/jellyfish-core","version":"0.1.82","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.82","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c15ce1a1c9c0e53910f2a4b94a7536c9b033b74a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.82.tgz","fileCount":94,"integrity":"sha512-auP5A7TR3PtipRadhZyywI/TLTppI6OEYCWSz+hCWB/zAyDEDHpq1jqv1OFgiGIPnqRKt510YhsCNJeoJ12BsQ==","signatures":[{"sig":"MEYCIQDaECrFaE1S2v2JiqQJJEv9GqoUDwZojFLoM/sjnHCkFgIhAPW2vsm+ykLB79lIo+C7fxonWP2H6EpM0331mupLWe1R","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":569173,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfoQLWCRA9TVsSAnZWagAAZScQAIJL5YH9JeMQYqlLOlrF\nf6v2GMSs9JUxwCLXdfNDSomP/wx50EgNT6KQbOx8m9i5iSkFh+24Ez9Bq7lZ\nM0GYwcroMWgU4S0vSL7aiGIxLhWcPkn5wD/76wH8DLrhTr8qeLt8lDeAdiH8\nN9n4Po3Kyek3TPKwLa7P/+A/s2IG/CAEmnYlwWcSZfQTBl/qkM0MpwLVpx2z\nKyNl7ZIEs9UirppVC/0gkzexrbjf1MVu/FgwTsAjSBPusUnxPGzjhlw5LtN9\nT+r3SCzY1xXH1O0FCYvxgwNcZ1UHlt7sdzrua193/EtaiQKEITwmm0kXIXsO\npyTlwz9sQTolE8iWX/AhRm7fHBMMlPinS8nBtq/62I67o2MYPZFAnpYGZ+HT\nvkAqvj5vYafF5x2ufHpVchOLziWStk9gSQYb7r0j1AUSa3C7fsn4O3N03YWF\nVN7eM6wQkOigVKoza3Pg8f7Mb1vdyCWoMlmycgepS0R3aT5dPUW2WDxrtedD\n4bIcphRddSPiXK4SHQvFJqeet45AyrxzUHGeLulCvd8AKzDNqKnbaCHOyhQa\nrH+E7gVVZAijrxklwaCkoz+C2ixZKJItcIRDzhO4FYf1xplwxc1nm7WNQLA/\nLg1JvKjg5XO0geuteDC5OmAbxSZVElGpMg8NsSsavur4L/6YDP6W5XX0Aehx\n06y4\r\n=aEYN\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"6b2ae8cafefa981329d8f4f5ed015efd09b3be4b","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.19","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.18","@balena/jellyfish-logger":"0.0.117","@balena/jellyfish-metrics":"0.0.133","@balena/jellyfish-environment":"^2.2.41"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.38","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^15.0.1","eslint-plugin-standard":"^4.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.82_1604387541905_0.923547082081374","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.83-renovate-non-major-44e65721d77343b4b663c4e722143d591da96cbc":{"name":"@balena/jellyfish-core","version":"0.1.83-renovate-non-major-44e65721d77343b4b663c4e722143d591da96cbc","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.83-renovate-non-major-44e65721d77343b4b663c4e722143d591da96cbc","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0a184f867a9e14ac19e3b1fb3c9d2b2f36430015","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.83-renovate-non-major-44e65721d77343b4b663c4e722143d591da96cbc.tgz","fileCount":94,"integrity":"sha512-DSXPMZcRmPYR+rAci4H9NjgDBcQ0Mzu9T7o2yBsHmh4pdUUTGzVKV4NeNHbImAQd8Nbpi//PXHqF+Vda8w4UMA==","signatures":[{"sig":"MEUCIDbPzNi9K3oE6o/2bNqBqDKG2+Gyo8L9Y1X2pR1q2DMtAiEAuFp3lg2G+jWvVcqEITrYO7aX3Jfsm/86+2DKsdBhMOI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":569299,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfoQPECRA9TVsSAnZWagAAs8cQAIql+0f+WeUXQffvRZB4\nuEsRPMxqradxm7iOJmjg10kkC7ZLEZ/CmFSAJbDNpJsg85bCNdEWeA1HnTEd\nZi13UfiZLN0bZi9uK9qBUQoJqYf9HlU5anhBsmukmQujAcTG5+qxmdhG5XGq\njrh85NrqxR4i47lTJb7MHbgbUkWIGH1wWW96uUYrjr3rn0Ocz9vBd6krINfd\ndEX1YWROCMxnEvSVAmm7SlCtJU84F+tQcpgmbO0eoDuhKAOKGIJ1209tgrbV\nBgny417QQL+EIYIGS4pw/MbXtJ3SJoI4tmfekKWXDSS/VCaUv4tFe1NWFRQ5\nVJt8gYTiEp+4TMZ2qgB51ZOTsHViCcp5vQDBsMePtT1cnNPjV3wBt681ck78\nCsvS+BaaaGMFGKnYrUBxKiin1MOzbzBoOi2OhG08awZFpTWAuKxF/AHVMelt\nLpYpUx5ZeJkzQwVhpIlc2IvfP+cGGbKK3C6HtGIXLHEBZiInJuc7/nGG0L8+\nvdE97L3v06tfRjritvkaTKzV440VEpqvzoUaq7M5s5s5jO31TkirwNliqQur\nyzZ3G+YRf6yHbSMhLuAHisos1j2jHbe5mlNigZHOSjXYaR/SGnSTVOx12fwj\nGUYq6QarSPm6p8NvAxA1Iawlfasc+p+n/lbSo5tzb2IqciLe8iTM6Lq0evO0\noBf0\r\n=q2CU\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"44e65721d77343b4b663c4e722143d591da96cbc","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.1","redis-mock":"^0.53.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.22","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.21","@balena/jellyfish-logger":"0.0.127","@balena/jellyfish-metrics":"0.0.146","@balena/jellyfish-environment":"^2.2.45"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.6","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.43","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^15.0.1","eslint-plugin-standard":"^4.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.83-renovate-non-major-44e65721d77343b4b663c4e722143d591da96cbc_1604387779651_0.5889695801847137","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.83-bump-deps-abdde24ea8e8e1643ccb65ed978a192945d9c23d":{"name":"@balena/jellyfish-core","version":"0.1.83-bump-deps-abdde24ea8e8e1643ccb65ed978a192945d9c23d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.83-bump-deps-abdde24ea8e8e1643ccb65ed978a192945d9c23d","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"31a1d6d757ac3ace116881b152e596824d604103","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.83-bump-deps-abdde24ea8e8e1643ccb65ed978a192945d9c23d.tgz","fileCount":94,"integrity":"sha512-yUp7FoRx+A9RFjhpTuo1g54ZtwEZlpWhGF5+kKEcES5F3Y61qmyGW1xLb1FXrc9gt+omEKSZ4soB8mr9TRrnWw==","signatures":[{"sig":"MEUCIQD0EAdy/QLLtGQ+YrXgIb4eHXAaMRtUGtYvHft1irENXAIgVX3IE8wZnU9gGIhw5kCETJ1xti6qH8f6vn/izXDj/fE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":569284,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfoQ1JCRA9TVsSAnZWagAAXxAQAJSR/GHOJSw+mV6/1jTq\naAOsicmirEN+xciC6hcd0eq6TEACNU2E35hzoThFdIgP0VhDAFK6bL2lLuz7\nN8NBXE6/8z/jeUmJaON5u/eTwwneMnD0OTSCq/MD4hmm/rDmWAos5Iw0ffhO\nxvhGMHXY0BRZ2ZLsintDXfvbXjAN9E4URJgzqbKVIcQW/nFj1rwuAm9Q15QY\nkfk42imMDYSPvppBTnnK6W0UxvZ0rjk+FdmmKCUX8lqZ4tUa7tty+N0Y/RNo\n1TzfnFC7L5s2E8Q5Qkru7NXC/FUQhvU+KcQXQpQahaTyrCYjkyWna/0nOgor\nqU6qCEIytLeUepHA/i8aU8ulF6tajLwnLDj2BJO96d3VeErCKp5g+5D/L6hj\nBPOz2c666lUPwmi4oXaUKWZ8HBLoUYqYBQXfhWPwUWgZSH/xePxG8P6pVfQX\nQ2vgWH/hwYWMiXvoPej2Dst69COtOLN9FrbzrdaCzhPycskjfgNgYarnsqmd\nb83xXWoMbOpg+nGX/NDttMwYxA8awEKp/dwhXbhU1dcPI27Ue1Qn8gT94guL\nP1l3aAmaX+KNeKY4hrmavR+DjZWs2q2kbU+qmBZ/n4VM7vOxtW3RaovS8eLg\ne7Lfwr++7ksOPzdpS80VEXXjtUYl3OyvnKaxvHWSvfGNnaey1NeEmNReUQ0N\n1xpA\r\n=XBjW\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"abdde24ea8e8e1643ccb65ed978a192945d9c23d","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.1","redis-mock":"^0.53.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.22","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.21","@balena/jellyfish-logger":"0.0.127","@balena/jellyfish-metrics":"0.0.146","@balena/jellyfish-environment":"^2.2.45"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.6","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.43","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^15.0.1","eslint-plugin-standard":"^4.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.83-bump-deps-abdde24ea8e8e1643ccb65ed978a192945d9c23d_1604390216576_0.870866387795856","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.83":{"name":"@balena/jellyfish-core","version":"0.1.83","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.83","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5a493fde16416ad86a71c5b8715b1c91b14059e5","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.83.tgz","fileCount":94,"integrity":"sha512-SjSRyaLBwpIu17eouEIiUrobhNOU86QQUMUKWLwTaF+ykFcar0ZBoRU8H59IsRC/9p2+0qul/b/hVtXraGepAA==","signatures":[{"sig":"MEUCIQCtMHL8D+vBjnf2MNxSYCkmfMv17MpsivrWQhrM7ppH+wIgA+HwoTdcbbGFRg4i2SjYV6LKqkff619lJS7LW2vWLuw=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":569233,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfoRFPCRA9TVsSAnZWagAAI5MP/RGECTAxdtyLZEVEWT9H\n92j+Mvyjrlr6IE2AHjtTg9pR5BVVeK78TZXtl6RtGM3cfMb1vwhh3v/q3aZr\nyt1e4LVMoJ/3BVaoH1dAzPvmtT4ITPqhjZ+syPqutRMredGDMjpZbbfPEHTv\naDY/8ilHW1VWRSmiGmhzPRyf2O1uUoJ1BhByNVSlIgM1x3rB1hZXZ7o33+1O\n5xspApJrkEDhhalAYSdv90F0Ca/KBE40Vn1SCKuAj/GZA1l3xsjrP0O1lTEK\nfDv1Rlu9gRUH9++95izCI7X6Tkng0O/IrxAe+JGMpfSVZ3IjUOMIl/vStYJ5\nkyvZ53E6y+Zj49xohnI1hjBYVS/iT1cXVf5t/KnUAsCoNDp/uIC/Kj/M6Cg4\nhd+klycGz1pbr/Pbo2dO7OvytFyvVmck9aKnzO1h5eu1RyVfcvUd/28Pys5/\n0+/bHom4LcUAZPSNQWQUkv9STezNpzUxcldVYt6zUWbr02QJdtKEvliDWuzY\nRLoZfyWoIOyPDL0zB5oYQNbYz8iWdRFDYaPOqAYmmyc1qFHO43tNXwyT8ftM\nsznZoEEz+2Pf20quSdI+1wF1cCI44Ytt4DUTPzeahooNeZ3ddfUaxtcyCYqD\n1F2ZDPoLidH2zfeLU67eW8+05a0uWocomt7WVA3wmJq52Qdz/9OzkYuTiRRb\n0iJL\r\n=VNzR\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"6a1ff61a38d15f8be4f53b658871cb20dbf51d15","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.1","redis-mock":"^0.53.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.22","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.21","@balena/jellyfish-logger":"0.0.127","@balena/jellyfish-metrics":"0.0.146","@balena/jellyfish-environment":"^2.2.45"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.6","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.43","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^15.0.1","eslint-plugin-standard":"^4.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.83_1604391247099_0.9391993864227357","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.84-bump-eslint-config-standard-380d560bc564afdcd01cb60c0e3506370163bd5e":{"name":"@balena/jellyfish-core","version":"0.1.84-bump-eslint-config-standard-380d560bc564afdcd01cb60c0e3506370163bd5e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.84-bump-eslint-config-standard-380d560bc564afdcd01cb60c0e3506370163bd5e","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"7fb33160786099dc08dcb56daee1209487dd19b6","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.84-bump-eslint-config-standard-380d560bc564afdcd01cb60c0e3506370163bd5e.tgz","fileCount":94,"integrity":"sha512-bRDNSklCC+42jIRu5d1skHzhb+IRITkbP8WBWdsBuiV2xrohnDgcsDP0eZs0g5dTrchk5GMK7z/xIwP+Hv6PDw==","signatures":[{"sig":"MEQCIAJTFcWxYVGkJLSwOHcMUf/dCz35qxLMi1zKdW+Pl/h/AiBtVZgI+WUX4FVSbq7CKS6VbZZVDu5vuaLpKCsR5a/jtQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":569339,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfoRQyCRA9TVsSAnZWagAArs0QAIAnQpu9EvG0Xx3DCV2k\n4pLhEMaCyItTyZZN3z3wjsBzGEmPw0gLh+PlJ/1HEpms8d481rdVleWcpHSv\nt8XhYe5f68ZVnKY4t+tQ1PSaeeKEI11KwaDNahcwRZJvxlA1MJpptVa69WWJ\nvW9hTB9iKVCtwSMjg9Uu8FQAikHV+0plmJ9kIXLAA1kM5mdtQtipVeu3pj9L\nYXFeC6us5UbN7J7q1wjFzC2avlr5eqPkCodzmtEtvNHdMztqcqGyH//cXxIY\nXZ/jhm74jMljahbM7ZPi47GpwE6GMiJDz86eVjWlN3c2npHhxTWc0BUSnOSh\niWAMtaSWT8EfdQ7ZrGhYmldMTnlhbd+OOYI80PfY9MbqpQJpsRp5Splu6Gni\nTF/+Pd0+smynM8yE/XPMheCxfDiyoCO2LNKuDZOT0n8TP3hSXMALdhwVRVNW\nBycfa6K/+Yu0l7z7nggAhBQhIyXesu8pPndAG9wOFgL/YAC/9+y0Dx0rMxuJ\nh0FsBO7sKmITc2SiDZ1Le4k1LzlAdCOSW8cFnRSi3T8+HLYxQ59efBBUrp5Q\ndtt6MG3IliwscA9OMbLWrnxNp7+tS9ZUqiBhzh7qrfQ1dFIwhDcFBesM1K7s\nYZzt3kZQV6rbPXydtzOGgIkXz8OO7J+vayQGlnkCZbPctRTISOLIS46nWkUA\nnD8v\r\n=3WMU\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"380d560bc564afdcd01cb60c0e3506370163bd5e","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.1","redis-mock":"^0.53.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.22","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.21","@balena/jellyfish-logger":"0.0.127","@balena/jellyfish-metrics":"0.0.146","@balena/jellyfish-environment":"^2.2.45"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.6","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.43","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.84-bump-eslint-config-standard-380d560bc564afdcd01cb60c0e3506370163bd5e_1604391986260_0.5060822511594973","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.84":{"name":"@balena/jellyfish-core","version":"0.1.84","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.84","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a37c405c1a3f336a8a1b37d510f3cc9a1d691d81","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.84.tgz","fileCount":94,"integrity":"sha512-UrCBP0xTO3jvrcN0JF96OKGjqJS4bDZWqjufrz1gRCI6OwLjl62Ks3PRIcmnRQa4GYStL447GU1M4ZOhUuD+zQ==","signatures":[{"sig":"MEUCIC+Ky9dtuEx0RDLGwb+D7mFxBFeP/nksWP0XdbuXMO3RAiEA85io+lWitH/hiXEkSjjSkbKsH50+cx8zLNXASE5ybTE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":569270,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfoRWXCRA9TVsSAnZWagAAoD4QAIIxA+yyyTneFi46uAH7\nVaUm0CculKLsAZASy6GbcPeHBuUjm5vl1KAhjUcJK5MMvZHIh+0ZVzf9spjX\nszUfSyjrsF2tUrHYFapGAkQJTnHR5++O0VB0YT1UkQCVFVhUVip4l7Cv5TfU\nZOrc4alpbIOUUjSHSMKWIdiaInmPWZaLWyc31SEQzkbTCxGt0vT+ZwfQ0Rgz\nKsas0Y3g/zav1eysWp1DEIf35oq7a3iT3IyMngEtR6gV2CGciPpPbuhYXohM\nHRiRkCYYiQ+YFvzWCsDdyVwqCW57QuTIdap4J6W5k4faA+GtDrENIXWPbfQD\nOdJd7rWu9rxmFUlHWNpbKI1gusi6jjyJZGEZbiRXAju5KfLMWdlHRk0+49Ew\nLXuKJ+9GUnU/G9KLLJdMwEme+KIJsJUHMjCVGEewUmwDdr9f6tUjqu9Hxy5e\nXK7B6bVFGYpZjIEtAOYlGqgddjT3feGZL60TJMD3d2cD3uMHbUUWGX5/fuTu\nLycx+C6W6qPEgCQiMC3pwa7mwCDMm54KM7j3vFTVnA/qFEMsi1sMe1WQwO8z\nhnaXHilL+bGHkHtF3/KVOZSx8zXOdyF/sTudVH8nfaoIaEUeVLyAYe0oxe67\nZXUA2S43+IADdXQ7nwDx6ziPjYFGavDzWadSQlyc93INciFp3EN8IlxsuiFs\n8nmT\r\n=EEAL\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"654c7a51c956a6a2c289fc852e8dc74e05935f18","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.1","redis-mock":"^0.53.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.22","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.21","@balena/jellyfish-logger":"0.0.127","@balena/jellyfish-metrics":"0.0.146","@balena/jellyfish-environment":"^2.2.45"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.6","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.43","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.84_1604392343460_0.8631449541142489","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.85-renovate-non-major-bd3983c3f83670dbbe4c8449094be38929874008":{"name":"@balena/jellyfish-core","version":"0.1.85-renovate-non-major-bd3983c3f83670dbbe4c8449094be38929874008","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.85-renovate-non-major-bd3983c3f83670dbbe4c8449094be38929874008","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"129b980f502ccde6b93c457f83304ed0f76d6aaa","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.85-renovate-non-major-bd3983c3f83670dbbe4c8449094be38929874008.tgz","fileCount":94,"integrity":"sha512-+avHVE3Y/utb9U3A8dF+p7tM0AoW/Yy0sEHJD/TuhxMeGhqezDmUltw/7NQ4HzwY0vTZ57GA9Pi1/LHqpy3z+Q==","signatures":[{"sig":"MEUCIF54I4Uo7iiaW1bC8UT2GHllJeSGJsnkMW05JZSzYeK6AiEA6r7wesh5fIs9vdgCiv/WyswCpvxevOT6FbL+ne5WhRQ=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":569419,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfoT1xCRA9TVsSAnZWagAA37MP/jDWdeMrdMl1vlfiwYjK\nHq3oNsFITr0K1b1IaP2tGiJJBm4PpuHB4kVviC6DlnL21u5EuwRQFUbLKlfU\nAe/9uRoanjg9YFqyxb11P8F0Y8JuOu4FGU6BSU6nAymlt8Lk/8m7UYv98Kji\nz7A2DTp7hgz42ZH2N+O/WhYDlCPSQNIcxlEtUfoicgilYGDac0DHBdYXVz+2\nlwTOlOGCXEl0WHKhempClLo2B25bu52b4QCwOkCB7RQ3Z0jERkTTKDiv6vH4\nsjXYyyHnYFMDlHSYVzcDBLWjvZsUUSD5AYNUeAFv2YGK9t0QMdtkOqdOCtjg\nvqHxF85T1rWPaOGbcArOfCNc3iUeXQei/1ie/5wQ8P8YU72b9xayM0HS3meo\nJwaBE/F64YIodiF2M4xYCp4hIf/xsW5RISiVKoA9DRrS/HGoNC8n2F1HdCco\nc55FuiCqAzsdycVRWtaV/a6IQw06PFt+2UYiy0uZh0gUCfqt9G3P7arKW6Zs\nj+WZGKLl0mxOKbeheYkz8mYmHRymcoCPbZ3rEheZjyoHLm5+P7b3UwcZZyuJ\nhHnfxq1JaB3EbFIDJr5XSwExee8NKHaPn+FHepMVhkCOhyu9DC94K8yGlCzI\nzDm57S+wRA+/BXbp8u0fOUFjOB8qD4uWY1msIntpum7jGJRAidpjZWWhyW3s\nQXTZ\r\n=aFLl\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"bd3983c3f83670dbbe4c8449094be38929874008","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.1","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.22","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.21","@balena/jellyfish-logger":"0.0.127","@balena/jellyfish-metrics":"0.0.146","@balena/jellyfish-environment":"^2.2.45"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.6","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.43","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.85-renovate-non-major-bd3983c3f83670dbbe4c8449094be38929874008_1604402544870_0.078378403392114","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.85-renovate-non-major-b5338d0a759297d94a15103ab0101b178b04c635":{"name":"@balena/jellyfish-core","version":"0.1.85-renovate-non-major-b5338d0a759297d94a15103ab0101b178b04c635","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.85-renovate-non-major-b5338d0a759297d94a15103ab0101b178b04c635","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3c828b9606aad6e724fbaade7c5417fd569d5dea","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.85-renovate-non-major-b5338d0a759297d94a15103ab0101b178b04c635.tgz","fileCount":94,"integrity":"sha512-wIcqLby+y4hSU+hr4TEjnfwJica5PMYRQXaJ4lhiv7coKP+iXVwl/XE/7DZkwOsYHxViDZfQGbs4j1w0giXpXw==","signatures":[{"sig":"MEUCIQC2Hg6/ntykgQ96PQCsEnRiGWCpEhrnNcymBPHTEQPpNgIgfyGxFUAzaA26jWrowV9/j7YTOCmU2zhjaHZ9Rx+4ckI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":569396,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfoWxcCRA9TVsSAnZWagAAs4wP/iQkLI5XpkUcGYQuhq1Y\nGy7jnhNuM+WMNMhpGw8A/3KTJQAC3VqO/aqRcEXeuHuP3nW+14enjIu828Q2\nOXHuM26p3YxK/uN5Nk3283wY/hzRTUoz53suI4ELtQDe5b1I1kn8/OC2fQJj\n41OFU34Zi6PfZdP477DFLqh7HFtmrSOZlUTtF4kh/tqVZUWeFVPeIZC3oyVo\nO0/zFOMch1RMxulIZQs96vUbDdg5rTLppbdaDkQkJtNdixQsHc3yr04WHLLN\nv8bXUN/r8lfvLBhW6vHyXcjyXOG2fPbMiH82dkqo5z7efoxn28BKMdHvHb5x\nWDbhLSuBPZB4I/gQ1AXi/pMK+gc0lDL3oI/ZHY2Ksd1cal7IGMoGUI/+WGc9\nwPpIQeqNxR0k9hAvorDJAGbi1fr2T04BszOn9g+Wb6lrdRl4Z6qCGEanqkly\njoVc6mvkP+7jsZyNWMI9WlFJLWHnZjPP+isu9blRelS6h1+QGeD6PNUitydT\nZBLpH93vGXquQfw3Ne8uCFN/3cn0qH1wULTEf+BsjgdeoMYzjsZb84mI7aAf\nKGK2Cp0SyVlA7IZZR4uQ/lnYykbIf7VG3gid5cKMRWrnTowH38Q7lx5ogS4f\nkCL71z80ozUxLo2GPvP8zzBekdDk1quxmNsH8W1iJtKG0dtn418XQczmXA2q\nuWnR\r\n=0sHn\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"b5338d0a759297d94a15103ab0101b178b04c635","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.2","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.22","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.21","@balena/jellyfish-logger":"0.0.127","@balena/jellyfish-metrics":"0.0.146","@balena/jellyfish-environment":"^2.2.45"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.6","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.43","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.85-renovate-non-major-b5338d0a759297d94a15103ab0101b178b04c635_1604414556435_0.04482604916144717","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.85-renovate-non-major-fd4c2827bd69fc1658382dacfd37b67fcdab08c0":{"name":"@balena/jellyfish-core","version":"0.1.85-renovate-non-major-fd4c2827bd69fc1658382dacfd37b67fcdab08c0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.85-renovate-non-major-fd4c2827bd69fc1658382dacfd37b67fcdab08c0","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8299514025cfbb3cfa830cdb56298e26a4feebf1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.85-renovate-non-major-fd4c2827bd69fc1658382dacfd37b67fcdab08c0.tgz","fileCount":94,"integrity":"sha512-ABN6t7OBedLSQebgqCTy+2/GgnarUJ+wh+Qrp6Y1Ms69/hIX4VkGMT6P/gWGt04PD4Diz/+B2hXlvvs7hp/DOQ==","signatures":[{"sig":"MEUCIQDNgJ5AvqGR5ZuY0k9tlWTmELlia8TnCDApSEx1DjpXjQIgBNjHsXT9RzsBEBR1YTKFtoJNApN0w5TEeO5S9jBl658=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":569396,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfoXukCRA9TVsSAnZWagAAMDsP/2fDR55mwfoN3uIWBIJG\nEqkkp5WIpH0XvNVdrIqst6Y4egRljBCqrBaeT4e5qkuAMbQa4FKJCz77f+ky\nxp0+WcLCr91mXht+GTwmSzv47tWtrS8HVDG6Le8qXQTiOo65zE8fqLy7RG1/\n/jmTaJuHbG27cjy17q1RVFBycs9HW0aYDaLB0bfqLXK3+KbryAsW1X4X6MKy\nVSlYx95urVhtBx8rQZQvg1AtnAzwn33HWBObOnvSXixacsnxlizkFat1fnD4\nweIaFObHF5XP5Se5tc39vGc3wft1fPm4+1b/vIaHylusvmjBQFXwa8BeUurq\nSf2AE/qUH+dDs4KZVbDZGehoTvGHmNnmciQeOyzh1ZjV8QlNnEgLfz7/vIYl\nF14qrA78Cyv0uvFIcPLrLGO+PYeu+QxiA6y0LJEoQqQOML9fZYCaD7IwinOr\nPBJGWinxBIvlTSSAtMDKN9DP/QjCAAOPrY0DCgUQecrsTNd1c+Lr1zMdJdRS\nsH4M0IVAFqnxQqHlx99MolR/Wisu2Ypw/NtEH47h09R7Z7toTUebazX5nE6N\nG92LBX6iBsu1bvw+IOh1Q4UvuecOdHM135WkzUFjVusZMpHaFp63Blz2oWg8\n5xp9T4qbRAoUJsXOKXPEX63ETUHWRKM39z70wxCml8kWPK3ojKeBj3HEHsmq\ns1lw\r\n=GWn4\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"fd4c2827bd69fc1658382dacfd37b67fcdab08c0","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.3","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.22","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.21","@balena/jellyfish-logger":"0.0.127","@balena/jellyfish-metrics":"0.0.146","@balena/jellyfish-environment":"^2.2.45"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.6","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.43","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.85-renovate-non-major-fd4c2827bd69fc1658382dacfd37b67fcdab08c0_1604418467546_0.11340481630395649","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.85-renovate-docker-non-major-5f26ff7379a43113defba86f1c66f1da6144eae0":{"name":"@balena/jellyfish-core","version":"0.1.85-renovate-docker-non-major-5f26ff7379a43113defba86f1c66f1da6144eae0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.85-renovate-docker-non-major-5f26ff7379a43113defba86f1c66f1da6144eae0","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"44c2684b7fde6bb250ed15a8b8fff44114ec2f45","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.85-renovate-docker-non-major-5f26ff7379a43113defba86f1c66f1da6144eae0.tgz","fileCount":94,"integrity":"sha512-5JFYkZcWCjloG8KNrtV2F6kcMqhHMjesWF4/BVFfbG1SvUt5SVXv8c2n/vd9/Pg1GtpEAmJRLFeAWTCylVC5Pg==","signatures":[{"sig":"MEYCIQDKA7zp4Hca70aiQn8uKWRRtNeMvDmsqnCknpu+VfcuHwIhAMKB5TQ4LibzXC+TACm2ECAYkhjfMKAtzfLBgCch/eow","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":569438,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfoXvcCRA9TVsSAnZWagAAz4wP/in3eUaxcZfEdMHMYlno\n/dkz/5z3zciryRaphDJpRacB6Ff4b5yzhURARUJTYludVH2SjDJigZqByf41\nN3+h0QC10oS2wLmPsSEEvltBGYlG9acEHAwH/hX7qV6ki06g3sBa+V9RISLv\nSE6TWULQHwvHK2P43OWBCSs3Xq/0P31hH9ArOnj7ayYYZjO+lR8Bf/tkk/8z\nlNeIv7RcJhxu96Kn3NcC1mjdNfdYtyTRxoXX2/FJMATI6Rz7oaVyD2n2GBAE\n+GVfSJgv1fjnmEYkNyxB1xVE7UH1xBk6SOPph9vlz9imePWO3tEDPO6VKdkb\nELgyFlJfalq5lrwA0p5m9n4PvytgwKFBqB+H7CaSTifdJM9RwNJbGKQygfhJ\nhovH//DIJPz64+Fer+bnZn9qcWCQDJth4ROfNRw1oG9pG/tY3BJqfrMRBb0+\nh06j1Ye3SsGYof77uM7E1YrKxUfDy9ePl+JE/VLJrY2cCNF1HMJlj8E9oYPV\nYls4ZXIzzzB7YQZk7oI7oigWCJGepBU+SOR3a4Iqv6FwbKSYHz2BUmr60FNu\nISTLZgZA44gEDpbxmi+zJSW08p8ILlRmq/uvruhbnw/yy5gsoJRQJ8x2J80q\nvxpUV9JLqUsyVyZfH7P1Z2delJvKVUJlxtDLWz39A1sYhe7ULOvnmbDMEw7j\nCSFv\r\n=44OD\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"5f26ff7379a43113defba86f1c66f1da6144eae0","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.1","redis-mock":"^0.53.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.22","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.21","@balena/jellyfish-logger":"0.0.127","@balena/jellyfish-metrics":"0.0.146","@balena/jellyfish-environment":"^2.2.45"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.6","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.43","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.85-renovate-docker-non-major-5f26ff7379a43113defba86f1c66f1da6144eae0_1604418524473_0.21733700842794712","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.85-renovate-docker-non-major-8ebaef6ece0075eec3409e9358c4000df5a7ab84":{"name":"@balena/jellyfish-core","version":"0.1.85-renovate-docker-non-major-8ebaef6ece0075eec3409e9358c4000df5a7ab84","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.85-renovate-docker-non-major-8ebaef6ece0075eec3409e9358c4000df5a7ab84","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b92120c5d415dfafcf6565a3b5001e0a6f54349e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.85-renovate-docker-non-major-8ebaef6ece0075eec3409e9358c4000df5a7ab84.tgz","fileCount":94,"integrity":"sha512-giB9m8RK7lKPdMueeCwx1NxK3XtQ6yWXAscWkkOVqxXYzjzLm27qGGoCQp1A/mQqbo+17l9C5P9m8/UyqCCGbQ==","signatures":[{"sig":"MEUCIQCvbGmQ+6CcfseaQN/hTJLWj5lOQ/YW+zFQo3knLSmghAIgX4ZhFCQsgFeyQWZ60oJHYVja7e4hBHDhVSkMY4Z5+6s=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":569438,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfohftCRA9TVsSAnZWagAALlAP/3+MVZaxfCkulHeVLqLL\nDtbyQynakXftHyWGWOaMkIet8Ht3gzZ8fjPYH9RYpy+5V6w5HzgAMDHxpjDn\nKurhLBSKf6+V3SZ9kiTn992kbrRs4VIXHh0uRPqBL65JxtPjZy+TcRJqjO2N\n8deKsQVFTprsr5AI5z1xqeCKj1BOXElr/c/o7U9h5ogjhZKiu9zv7DRuntrj\n49RnreqaWRSeTfuRr4VQlb8QbpOSR1Nx9DrNYVcoME8K2SUeUqF4J+3ro52r\nIvhpI56DyaxWDznNwc7QeovB91d1ixuEmRdbbJ8b6vF68THc3dDqdBkqe+an\nB+joq56KsANGJBAeOCja6XqM9mLzQejz1rWNRHTt97pkD0p7c1qnyLG6dtsT\n36XAo2ov8HzHg+hDLPqBXTHQU82nRZ8oYKDlbTK+XFEBOSfoh5BU39GdOsq4\nFCDFVDmJRUiC0pLPCUNuw3LGBMtZmHBMRtBtjC4QboPC08IemC4plWuYr3Js\nzh2raRdZt1bPcDn4n5iOI/k4MGh/8gyUIgDZmF5DldLhspLRaZ36BcWr0JFt\nvVCBCN+afClxfoMIGDcICmH5gKoFLI4Ud7wkjAg9+rXb6Pz2Q+SLM8eJcrIW\nRDz3oJ6tl7E+QlQiOwB0/GG6GpdiM1pQfhhzrKc+HBYvrD22nslwYMB/dxtM\nBsb0\r\n=ooZf\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"8ebaef6ece0075eec3409e9358c4000df5a7ab84","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.1","redis-mock":"^0.53.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.22","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.21","@balena/jellyfish-logger":"0.0.127","@balena/jellyfish-metrics":"0.0.146","@balena/jellyfish-environment":"^2.2.45"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.6","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.43","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.85-renovate-docker-non-major-8ebaef6ece0075eec3409e9358c4000df5a7ab84_1604458477149_0.1746245695589106","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.85":{"name":"@balena/jellyfish-core","version":"0.1.85","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.85","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1d04ee1790f5906352aec08ec0eccb02b9502665","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.85.tgz","fileCount":94,"integrity":"sha512-UrmC2G/J0OKeTUZCQYscrkm4vw1B6kYWG7+cwHjc64rP5my0SB/y9c2FgyAFVImWqot1bOrT2G29dZl4Q3JD4w==","signatures":[{"sig":"MEUCIQCl+RaY6bO32TazSUmLdpecG+W7E2IQrM75vpXhAs5T7AIgPjxC8XpIBcpvQRZqA4q70vp2koCe9MJFTJDaDaNLC3Q=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":569371,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfohkACRA9TVsSAnZWagAASikP/0KJVyJ44yoMH332/fpN\nKRhz+/G8xipPS7U2b1v/76Vw48ugYl+ZGXZwpmgV0QLoi9bJgZ/5aVwK1lpC\n0JhnuwUrx2wBw5zRojXSYY5hr9Qd5hfej4LQ6eNe5o2onx+U+RZ/2W+0pIA9\nonEeTpYy+ZfPVDC51/epzdSLpvyDxzjbGWZEgmbOyHfdE9gcdQ3864GDTr7A\nDfpwQeUqz9qyU/FzO3AJJ6HXuxLLTLr5DM9va7LDwl7TqzIXk1CvLTkohky1\nV11Xvi940869aDS3IJclsRHNU3UQgXY6N2CpJouynrvvJGceHN15LgyCRWZW\nCx8nV3VzduC+ZL9X7bipaX0n/vnbiXJuaxmb2PV83aVHW0yJ9kIlbJCIUyLZ\nbXjjekhhxaldKeEw+WO2Ck9ITvCr65hD77T06ZD3XGRbCm3WJhcUkGCaHVag\nOvLJ7ldXFigB6afGwCh2+MRw9+sDtnsirjUENMMMRH/wazrqFG/6FWRtM7cm\ntI1uTI3Zh34W2w7uBonA/DdSnYHgwiKdTxlo83ERaMBiF/WMwEqGA7MmbhBd\nsdUWJrp7oldicr/Nhsvn5JVARBjWY0Wlk5ece32I3lDmfuXERmTFWqRPsPM3\nqzzF5Jptxquzsr99oo/2JsBRAz/KubOHp3rPps1wm9AOHXjoYh0ZJAp9BUUB\nSPfc\r\n=L6P9\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"72d2f17fa7290c44d6ad89fe9eade97f01afbab6","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.1","redis-mock":"^0.53.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.22","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.21","@balena/jellyfish-logger":"0.0.127","@balena/jellyfish-metrics":"0.0.146","@balena/jellyfish-environment":"^2.2.45"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.6","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.43","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.85_1604458752042_0.4703994954215003","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.86-renovate-non-major-6626df25123b7056c7619f1521d3b71078a0aa44":{"name":"@balena/jellyfish-core","version":"0.1.86-renovate-non-major-6626df25123b7056c7619f1521d3b71078a0aa44","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.86-renovate-non-major-6626df25123b7056c7619f1521d3b71078a0aa44","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d241a2aaafb3d0da325de0cf35897c4f56d405cb","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.86-renovate-non-major-6626df25123b7056c7619f1521d3b71078a0aa44.tgz","fileCount":94,"integrity":"sha512-vkUY5SqhqU1Z2yxo8VzDAaPD7CDRnEyhltzyeWPuq+gkWh/7MGDuoZOFpxykapkU1jEzM+le9JODfYmCOt6+nQ==","signatures":[{"sig":"MEUCIGCUj4w9YxhdD3oQ4LBb5cPJiYD6kQ90nJ7rQ8B4ILs9AiEA2O39mHqcSB3euyYeR9CM27zFXhR6tQAoy8uJLTrb+FE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":569497,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfohnuCRA9TVsSAnZWagAAuzcP+gJK/uTcBgxObwlrIk+E\npuZZaFMmjdAOEiV2VFNjv30gFLDTdVY/Cam4cNeQokrDYwlYYgTTyCep981a\nSrMwiG6acCwVXtdkc29TOSQbGMPciPs7Z8zjsbTAJC/3T9mMdnOZkfnK+3sU\nRsWRyap9poQymbp71un3m3vJIL2OZis7ExjwbgFEpc7RlQDTtRw3NCH+EO6e\nnx56AR687pLbcy09ssQMBhQNEA1X0I3xByOLpMuZTfJFjUZ9/Q3Egy2jyuMl\nk+fl17B6gYDKx7cSItkMNkQEt9N31fYEA8opHs3ApSYz5aSF4bfZjuAtV3rs\ntBFF7rGm/pOgKoVHBE4NO8gx9lA8LxQlgWhL6WZV+Jo1Vxbvt56feYnyn9Mj\nn1TKz9Wpxkq+o3B2T06Bl9PP5J5dK1Wp55Mjk4PJs6PBueiIEMCZkRiItJga\nIraQviIQkWJHnh+Hjm9eM5Y2RePgPSap35Ifb+lvyMyYIB7nec5KX+h6OxdD\nvjgWDNbwgn/rIOk8rtLB9/LmpGM/8Q/u4i1DcmVfPcoblesRD9sjVoCgOPp7\noqMz0wWVvT7MVAUSDMSOOPoFpuKu4KOctxh49DE2ACfdccvjl19eMKhSgWkG\n89cfG8MDTlLPBMcySu2QL58Jg3Ihb8kQGAEq/+u+/SdVIC90BB10KER2I0mV\n1kgt\r\n=iYX+\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"6626df25123b7056c7619f1521d3b71078a0aa44","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.3","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.22","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.21","@balena/jellyfish-logger":"0.0.127","@balena/jellyfish-metrics":"0.0.146","@balena/jellyfish-environment":"^2.2.45"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.6","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.43","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.86-renovate-non-major-6626df25123b7056c7619f1521d3b71078a0aa44_1604458989754_0.2990307261441687","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.86-bump-dependencies-9fd94ff1fc00a15d0074515a216f1b7c6b36a792":{"name":"@balena/jellyfish-core","version":"0.1.86-bump-dependencies-9fd94ff1fc00a15d0074515a216f1b7c6b36a792","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.86-bump-dependencies-9fd94ff1fc00a15d0074515a216f1b7c6b36a792","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a41d6cfaa954d8962bfe90ad9a9da7c6f108ec00","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.86-bump-dependencies-9fd94ff1fc00a15d0074515a216f1b7c6b36a792.tgz","fileCount":94,"integrity":"sha512-1bVq2u2dpPyPSSeGVI3zZtGxq41k+JJoWUvf5J+g121NC8tuiGorR2m3qo0KVXcTKT9G3qbNbqP9HTa2Rw8YeA==","signatures":[{"sig":"MEUCIQCEnSnZ2A10OeMV+XZ/Zqc23heqDD0ycO7Ta7ShRWkwbgIgWYBpC6XuGFissxaU4HabH5EaR4xFE3KYhnBu0SiVxlw=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":569490,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfok0vCRA9TVsSAnZWagAAUkIP+QEmkfMlvcSEQ1TMTj37\nW28XJZtQlxtWgpv36izjY2RfTc9lvR7iXvZsYLPeXjuijSaMFbQRhnbLbTj4\nW8GKCrePLIQ6qOdRgOk8SqA7aQp5v7Kq2tnsGAYQU7BY7uJ0ewtN3AtbWpc/\nDl02yXSHRdSV4ZhztTU/bxfczN3grpKYHoCtohTsB82I/mbHMaXzByYUKw2t\nzWJ3ppY2sgKuDmOZnL1/jEZ30hVhU4AmoMMCMjCIUCKKTK/+BYNFlf9aYQH1\nmO/1ryY5UoydKaZwg/wknWSYO4zTvcOmB+NkOEW3o/dlHAMaJFojapBVJMoE\nxpq5229Ne4/hSD8pGOI5F2Cojv8Syarv+8WlQn50hbqVwXHHsLJtVTH88iR/\nfVnD3mk7VdFBB0WayYKo8KODGd1ojvNVEXkknsqnXx2sEMX1iL/n3gaJZhty\nTa+FZsu2Wih6ySytshdmWrWj+sZ8Pum1BmtbmcJCkU/a9fYTc/2zp9lt9yxg\nxdRm2tA+l+TaKMlmFNcv8S7XWxUo6u7Mj2K5w0nnZLaqzD4+mkiuIXRtIAg9\n2DfL7LnjX506FAKmij7MS+SFHwDGLEjN5ww5m9iHiZ53RpkIS74vVibLYdB5\nZ+JsQEJF7pilqwKb+VkacQCHRH9lQRreQXN+ciEuWXLy9fZog+2dTiYjUaDA\nKfmH\r\n=RMjY\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"9fd94ff1fc00a15d0074515a216f1b7c6b36a792","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.3","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.22","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.21","@balena/jellyfish-logger":"0.0.127","@balena/jellyfish-metrics":"0.0.146","@balena/jellyfish-environment":"^2.2.45"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.6","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.43","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.86-bump-dependencies-9fd94ff1fc00a15d0074515a216f1b7c6b36a792_1604472111276_0.2571031767472902","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.86":{"name":"@balena/jellyfish-core","version":"0.1.86","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.86","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b5091530fabb387dbec14acd068e944529ec5a7b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.86.tgz","fileCount":94,"integrity":"sha512-2KWuEb8qPnyJYeRxvOSsQXdl+5bxxfuXSNUO3Pt1/JIUZj4rsLUu6QevWAMTLOoX+UGZomsJlTURxZubScftvA==","signatures":[{"sig":"MEUCIEVk0exnsBzmGF20sz0km3KPnV0QFBBH5ai8D6L6DuXUAiEA2uIKGAPVgz//t1wQBGBDtNLKvfH2gSEtd0iPQ2AstVk=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":569431,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfok9OCRA9TVsSAnZWagAACSMP/3pTwMxUOCJNNjjrW4E6\n6jWRlG2OcLeIC4mXKIg9kiReEgG521z3qdGFcPS6teHcsnGVbMC9PQegHQ+k\nQb9dlaTt20wgNGYbUKR+sBBiV/5UhIgIzDKw30kURGpeTCDQN9IfruD+VXEc\nTvHDqiO2Jwfoyu6kFUH1NB4koczmTtHww76CD3/SE9eRl+K3iuVHcV3gV8ZM\nEWizDTAMYBn6yLDoFAinQmTE2xt1TuKZybxTTGD/IpLc0+MEJpsx/4zsM6XH\nKWUfqTU5wDho2VpWS5mKBFT+55F5DhKqOXPZEPM9bT1PsCjYosG6kN/N4t4U\nZWlTBGty6KaO4Fu0MYLxyR42+vYu3XAnYtyJ0cVJKBQYtCyndDbrvj9QnZA1\nngHnZklIALHWpJTsO/mEqH2GujptO4kblwO0P25yp7EzOQNzC+MubVSoCLd8\nd1sRYbwYTBqOR8V6D1PxnI3bqtFcf4NIPRuwrpQtyvDQ6XrdZLLdIvmt4LGE\nibxaw/ncsfXXNRJd77K/S/LFlEcjfZzXkJRSAgfQLf0E/xTLJBIr8IAO4GAA\nvJS3fX4T6ZMiQn5t4C9ggeqV4Ix2XAFLW46UyE+dWpQHFYq1+uPRPon5+1Uz\nPjMzzWAQHntumWpq+/wd4LlPBNPd2cVJcH+lad1EKWUIi0xurdMTfLTHxZQi\nDQNn\r\n=0rTz\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"62249d679b7a36c1c8500d096128297129bbf209","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.3","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.22","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.21","@balena/jellyfish-logger":"0.0.127","@balena/jellyfish-metrics":"0.0.146","@balena/jellyfish-environment":"^2.2.45"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.6","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.43","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.86_1604472653543_0.6305297889744994","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.87-renovate-non-major-01bac244fd6585b93f6d6be391d652b113c3ad25":{"name":"@balena/jellyfish-core","version":"0.1.87-renovate-non-major-01bac244fd6585b93f6d6be391d652b113c3ad25","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.87-renovate-non-major-01bac244fd6585b93f6d6be391d652b113c3ad25","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"27f19d20000f3510eb38c70d0b171f613a95bbde","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.87-renovate-non-major-01bac244fd6585b93f6d6be391d652b113c3ad25.tgz","fileCount":94,"integrity":"sha512-yHAfAoRHYEFxgTHubEHwG2vVFhdXr1Nb1KfDORw2K9ioFDJB+tzpAxqT1M21IQJZgL4jTkzI+ZEEKXJksx7vEg==","signatures":[{"sig":"MEYCIQCuk1tk4hH2eOinhsPf+4NCJpkzWToC7Uap/wdLYIeDHgIhAJdM2jFzNObkCiVdljo0Oq+aKJuZM1t43pXcNtKNj+kP","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":569557,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfomK6CRA9TVsSAnZWagAAnhUP/21xqMupe3ZcR/DKHvm+\nUyR+UGei6tzTuf+lZFiDCMr5UNdaL6yx74Arr8X+cLMb8z7fLaHEn17EWPk/\nPZpYbQMybYDi5n2fKQTvv+DHJKx5Fyq8K2QF6IroGVZJMgebYFuCvgMM2LVo\njYSkEGAyINAzUGCaqhSsIg1gDEC4SJeZNlxY/EqpiyV9ScRLyDDK+X+L24HA\niUUyZ6kIKfKnxXbj50qZW1M6yemQ5Pe6DUuRwx1UbWY3lIZ0Rxv0mxssfl9l\ngNdC0xLF8vmZGtJOArnlXiDNrYPYYvreLU+BjFjM1g8A+/Z7RLLBg28d4Ktl\noiFXAfPD2og00xIzQY9N6SyUY78nEN/muOEyUmifuhICvGe+9Hcp+SSJb69v\n2tWE3V5QS9sfOm3wTixjpwhPDjcJR+DnH2kKAHRv8hAXYLhpdT/oLKiobv9+\nWJZY7WceD2rT71TV+YGppS9oZHZoFrlklefpcLO30P8SFdOX8A0WjFr5ebo4\n2f7+aErbD2OVMtg9IF0/hR28oDlxt73vpcA5DMIBPEGuLqOvGfXO8hnp3jbN\nKIgC3ZnObABYTL/p9tF4VzMkESTodgeCp0PEYIBHhOhmMnRl0XkQIl/7A7A8\nsuHxGufottHFFgnfr19OfYqi6YmhDD7JFUCGmVduZ/bh5snyoLVO0lG/mEO8\nzJj/\r\n=FFz8\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"01bac244fd6585b93f6d6be391d652b113c3ad25","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.3","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.23","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.22","@balena/jellyfish-logger":"0.0.127","@balena/jellyfish-metrics":"0.0.146","@balena/jellyfish-environment":"^2.2.45"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.6","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.43","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.87-renovate-non-major-01bac244fd6585b93f6d6be391d652b113c3ad25_1604477625629_0.7704918946855319","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.87-renovate-non-major-6697444ce5ce815b5d437afbe50768457d5dae54":{"name":"@balena/jellyfish-core","version":"0.1.87-renovate-non-major-6697444ce5ce815b5d437afbe50768457d5dae54","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.87-renovate-non-major-6697444ce5ce815b5d437afbe50768457d5dae54","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"30b74a65dfc873162ecd5bdac0a53ff98755c2ae","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.87-renovate-non-major-6697444ce5ce815b5d437afbe50768457d5dae54.tgz","fileCount":94,"integrity":"sha512-yO0oIZp/PW8WVkLTpbbopkojITfEwbe3aNxKw/tv+eazM+AdTD+gR5eBgkJqQNNwbHj/WlWbE1T6C43wSUab8w==","signatures":[{"sig":"MEYCIQDivQvhcdgkNzOCtY7Ik5YMlSh5N7xQPFCdgSxdqLsTOwIhAJ3mQ7zsyelZj1Bk03YEeKQdjZaTmYDdrxnK+ItovskN","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":569557,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfonxECRA9TVsSAnZWagAAdyoP/1bUml8Cs+srxjIU5jUy\no1Q6avFdRDgEsUBB5+1t2MQ5jwHowJGo863C00ncK68NX/rvSG5VHPhgAp25\nOfIyc/4wLKSgROTL9geIGRc3cdRYBFFahZvGOcReHl6W7JKmzwgttnWffmW4\nsFs/1vcaZSnDVBYqx3wY44cNbwWuz9pDBS27MEKfM7yjHD7o3bHmzcGaNC1v\nkaLqGZgEt94pLlb9tBP4sxBLv26P2ETWSkdJ/8x9xgV80Leyyzcsc+AbtPWZ\nrDPeMHHngvjnCMqmKtafPU4ooyZrbjIEbUiI8JgeUMu0c2O7AjjB0osuWNlS\nF7v6sKqUQJFyaPmwXz6quaijuKJ9CBbLzRF662aj6Ge/tuXdyYU0mQscLFqZ\nZCDRNPgoF3ZPeIKJuALWLk79o/7z5AZ4wdaol85gjpj/LfNUaX0/4TfXDmSU\ng3EselmQ2wffXXUiQK1jiuSaBLsvSpWY3Mte6/VinR3rDnH/OtyAUEJ3Cz07\nXvsbadfMc14wxlx92v8xDmnbq3qOLRPFsUeYeFmhx4ELVvVjdhXi/sSBWZnF\nXMEs98MHXXx2wvXZWDP1Axe12+0hGiyC6JEB0rFffLqjjL6kui1QUVF2vPZv\nHx7Uv8NheRcpJ4n/QWuI6OfbyZntuMqe/heNrQog0k5ZzI7xfOLtmzg3Dl7+\n+0eR\r\n=c2fV\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"6697444ce5ce815b5d437afbe50768457d5dae54","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.3","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.23","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.22","@balena/jellyfish-logger":"0.0.128","@balena/jellyfish-metrics":"0.0.147","@balena/jellyfish-environment":"^2.2.45"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.6","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.43","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.87-renovate-non-major-6697444ce5ce815b5d437afbe50768457d5dae54_1604484163749_0.5303276874661063","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.0-add-default-view-mode-to-card-a93691bc5b0ac9099d9407ec7d1f70a2f5610739":{"name":"@balena/jellyfish-core","version":"0.2.0-add-default-view-mode-to-card-a93691bc5b0ac9099d9407ec7d1f70a2f5610739","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.0-add-default-view-mode-to-card-a93691bc5b0ac9099d9407ec7d1f70a2f5610739","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"28b5b2feae453c8567b5a1d15999cfc1d03c60c0","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.0-add-default-view-mode-to-card-a93691bc5b0ac9099d9407ec7d1f70a2f5610739.tgz","fileCount":94,"integrity":"sha512-5+CwYobCYOx0AFioMBbN+QXwT5JvKJ9UEyp1BU46En/RThm5XWmYc8lCTfuZFLeBff6sfGVPWIC9C/Rf/ipEmw==","signatures":[{"sig":"MEQCIFdgtPm6qsC7F1q42/Y1MjdiMjH+YNtYjgPdhjznr9XbAiBMV7q7v10eNVdSeX3IKi30okclrp+PUQhgq6Yv184sEg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":570391,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfotbXCRA9TVsSAnZWagAAiAUP/3IDYwNvvbCQIvyIH+Q3\n68aMd4eif74sp7h4pQ0J3xSFFIxGTnIF/WtyHye7bL/1eO1o8H6YOdTEvUfY\nzi1b4CbbUB9gnF1+qo2geG7+RgRqvpq+YZnYMVl/O5FsF7LTVskW5DGs1xVS\n1BodItsjhCD/Zv+6jFfNVSnSQB1s/kcKbluerzf7FskRJN6ze4ZECnmJBSwK\nat4n3c7EFA8znFZDwMEi6UG//VeF3Ae3wtszbtwg07/SJewIiHmwCdF81Ps4\n5+p8YyhItO4SbcrZDqMEbi58DVrl98im1aoBsq3HSveecOO6mONFevSmTtR8\ng/8FO7gCDsCyzRwyksnxp2WV5ptIaZgMZhvD4VGYEh1ZAN6KCerNrLSPsQyw\nOeJU635yhjCc7HzA1kAq6N+ClyBHYd/0AK9oPeQEdzufv+uAniemp8oLnVf+\nLrWHn7ChrKm+nDC4z9ktKoedVmnvs1Ocs6ieR1gLcDDUlX/JpL3wduhoKo1d\n2VELMHoF0z9zn69KZb+BPfOheQp5D4+iFCmQgyWDx+DBiczdZcVYvwIiZai3\nMZzZblhsOKgmQZpIGbG3ZtMaLxdLS2xU8lA6SylkGYcryKFRbUwgTHuv7YRR\nokPFAfgBxp1xflRahSl0EzHf/W3/1ldCqZmrMRBYism9dNdehftFpLMpto6h\nWJsX\r\n=wN2Z\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"a93691bc5b0ac9099d9407ec7d1f70a2f5610739","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.1","redis-mock":"^0.52.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.19","json-schema-deref-sync":"github:cvent/json-schema-deref-sync#8bdbac1ff3e001a7b459e08229d3aa93f42b5f13","@balena/jellyfish-assert":"^1.0.18","@balena/jellyfish-logger":"0.0.117","@balena/jellyfish-metrics":"0.0.133","@balena/jellyfish-environment":"^2.2.41"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.38","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^15.0.1","eslint-plugin-standard":"^4.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.0-add-default-view-mode-to-card-a93691bc5b0ac9099d9407ec7d1f70a2f5610739_1604507351079_0.14561706165678268","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.87-renovate-non-major-2d2f7ba8769e1c0230b7ffee3e8ab20916f5805b":{"name":"@balena/jellyfish-core","version":"0.1.87-renovate-non-major-2d2f7ba8769e1c0230b7ffee3e8ab20916f5805b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.87-renovate-non-major-2d2f7ba8769e1c0230b7ffee3e8ab20916f5805b","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"290f53e88d2c924961717a2536e5c977f450cbf3","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.87-renovate-non-major-2d2f7ba8769e1c0230b7ffee3e8ab20916f5805b.tgz","fileCount":94,"integrity":"sha512-8tYkoyPX9bdXMCkkMiRAm+aF/y8RDUkZFe+qrO9kAaJB4X5dtQMIAp2xJ+IEjsmzU5US254QAqxElay5Qn+5VA==","signatures":[{"sig":"MEUCIC+BSgP/HxANpT232fqnENP1D49Qzl1HcPxA6ak+UyKDAiEAiiilnV8/KwtrL8Ci/sTzoBLzFAGDjVXp5KU2JtGv554=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":569557,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfou7QCRA9TVsSAnZWagAAfVgP/3GOMq34lmmiI8kKJbNR\n7TjeC+nTomLd21T+5jFYEcD74zHZ7opY7c00OGqLDnhUTlzqA5YQ6wfTzZyM\nendaQpSH0ywP2t/yc+xDK8q+6WXrud4fStip/4Wrzex4x+a+yyjY+SYt4X6I\nFTGeJcBtWmzicuAtM/XU4Vp9GpoXqxf441py8oJPV5YoKlDGk2PbirN9dYY1\np3e+p5BArBzVbtd6jkBN145c9apNjyPfaYztD8JXXFnMaZddUzx007AAVjWb\nJBrjZdfoupA+KOCL1bxHBJIiVPRR7gweI/ejkYRVSUIBtA/aRlSSRz72QxW4\nmZwR5O8SoDDrYOfKIHYbQ4ErCXYNUrvMilpNLUGlfXO9jPA9/jsv455r4KUY\nbHo/ix7Cac9oZs1/c24dWclO1wWDjpYQXSANm4oxaeWEVKTMvHWSropFAEWD\nAQaUY1OSL3BP/cY4JhONvWysHmsZmN/sqFY6qqdfrwYjxIMT3q5GwS8kkmne\nHShSWixy5gDN8wrQ1nYaiD2mlZUVB8nddnKT/Yt3P6z17CaCEiChihfqWZf1\nyxHBBFcK4Slb89sAkdaF6yN3dvVXSgpkEgWKPgBvKtAcFGGtYPP78gCRMq1F\n5Qo1qUlkBwTIAm6TNUXYWsNKZjudbYiEhBlYZT8KjDDNlYzWbLM4UQzbQucb\neDyQ\r\n=CAUT\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"2d2f7ba8769e1c0230b7ffee3e8ab20916f5805b","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.3","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.23","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.22","@balena/jellyfish-logger":"0.0.129","@balena/jellyfish-metrics":"0.0.147","@balena/jellyfish-environment":"^2.2.45"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.6","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.43","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.87-renovate-non-major-2d2f7ba8769e1c0230b7ffee3e8ab20916f5805b_1604513487581_0.5100723549130437","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.87-bump-deps-5bbe368e1953f4730856b16b7fbea98e9eb3faaf":{"name":"@balena/jellyfish-core","version":"0.1.87-bump-deps-5bbe368e1953f4730856b16b7fbea98e9eb3faaf","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.87-bump-deps-5bbe368e1953f4730856b16b7fbea98e9eb3faaf","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6bb9e42cd7a9d679b78e7f8b814b60c413906174","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.87-bump-deps-5bbe368e1953f4730856b16b7fbea98e9eb3faaf.tgz","fileCount":94,"integrity":"sha512-su1x3TOUKNmcUEipDnQt9wnSj5CSdYAedO9NYRmA1aFN4poH9HHPy8nP3T5Y4858kALwocmIf5WGOUAsIvhFvg==","signatures":[{"sig":"MEUCIQDC/2PfhMmHPtY1REuuf1Rs8x9hnFWP1cP+/WOul8CUegIgPVzXl7d9vpX/PYmF+ReYr28TLd/6yOLqxeuZhK4ynhA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":569542,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfo25ECRA9TVsSAnZWagAA5S4P/1zJr7/50gOL9XYp1IT3\nhmawPXnIzJCG4u0z3ZQoZUMtcXQYv/94+CIjfgYukXpbldt6S9c16ysVWh2a\nhleyYegVCIfSxCkDyO3OdzSfGTtMhsIR0aRvWf0erOvOdT5LLDIaiBct92Bf\nQweehk4XgvHx1frK5jUW2pG5w7r7iibUF2lgpoQOCGvXMflBcoYtbUKTRB4L\nPjHe6nDbQFa0LTVW6oV2IBe4B3V0q2J9YuucpaUlsf/0XGJ3s8zyobwznnKb\nLonwVE/BAyR9QHdKhK9VL0FJlAPoWYYSpDHzVygzBjQyJLKd/Kd6YsJ5mQYK\nZOaKshijGe8ZEkJ8t4lE10pbvHlfkcfU94/hgL/rIzIibgCjWJEI+KElPA/1\ndMaTt0qFA11VNKlT2a3xHq4T+nRgLLIBowVm4v4gg5ZP23ainMSUEuqhLCM4\nKh1hJY9ZJRPT1+HXtc1/x5r0Q70LK+AV+o8XNcxaKwMCsVaOaBKWKPZe1yz5\nL2/m9bBGWmUnDj3WVhthESa/QFjz7V3KSSihMJW8o0Vbh+YM5KyrKhSlDNO8\nl75lTux9spA0PPPGeYA0UvrUrDtFC0chRaBHadKlgw8Dp7x3EiGELnTOw6Y7\nX4L4QvK8940StkEwZc5aoYl6rywO9OuIQRxZCzQ9EY5ivCMbJqNkX9k9iQij\nKWyG\r\n=gOrr\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"5bbe368e1953f4730856b16b7fbea98e9eb3faaf","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.3","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.23","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.22","@balena/jellyfish-logger":"0.0.129","@balena/jellyfish-metrics":"0.0.147","@balena/jellyfish-environment":"^2.2.45"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.6","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.43","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.87-bump-deps-5bbe368e1953f4730856b16b7fbea98e9eb3faaf_1604546115565_0.8223523478761685","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.87":{"name":"@balena/jellyfish-core","version":"0.1.87","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.87","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"97f9e9a0a90dfe1a6409e9acb4eb77af7c95addf","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.87.tgz","fileCount":94,"integrity":"sha512-uPMay0zctvR72sFiQk8sxIXf7CBGrgAb/RyqzJNtIIlTX0pj44/mqHOp3wG8UAlvdITQ89P60tfpJyneVop/ng==","signatures":[{"sig":"MEQCICdOh0W2ZWA/y98ow3sjZVsdmc19rYGwYtzlLDbK48Q3AiBSAAaPj6YpoVIJTyH0pqHIYSamlvQ56UmTQTgZq2SzAw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":569491,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfo29LCRA9TVsSAnZWagAA5WUP/AtkurjyoR1PCJ//ycuS\ntqFX7lFuCw2bL2j9O1wW7JyUDZ1AAec4zV9Uri6vKq9WONuDpbwaLikz8ste\n3Hxv62QFStdSL9ET9eGDVHzx1j+2HAWVtEXcgcDz4itBJ6lAOGZtkuH6c/zq\n50hltr2U8mAe9F/eWvBzDsiIQBc/FLfGbl5v1+CM0OP0i8IDF3HgbNWK1U7h\nKvC1A7Kul8SssBdog/PX5DB5ueZqsXDpxODrk6eQXwPvvUVrJgtcS5Ww9F6w\nIPnz26S6e1DaP31SNGO/gJqVD68gYv1DmjgvuXqgrL0+vUR+a3yR21mn/kow\njg3EpirfMgY8/Wg7C3poJ/FgDcoyF2AK+lwMxs9KOEele109gVYfiLhF/PIi\nW/10aw16AUma5g+raeB4qoaTiYUh3brNbGDyPPUSkYtPA5K/B0rbu3oqdjXL\ngIWExLTmvNXUY7uhaCI6jOD6QICiryIa9wm342y1YF7qZhgtzyOFqHs1TUyk\nw72sb5HEr1+VGU3HP1NoKO8oI0Fct0lQksFlCRC17INMhKktDn/XxV8mfEh7\nZWquj/7xRHoIIh6250MIhOxpvUL2wGCONmfsOFqJurr+9BlIjV8GBRCp29C/\nwlRjOHqXrSZM7kn4ZWQmdxBc3tlPyO9Gv9G1s6IxOEWG5AbjtEvHlt7hJfPR\nu81P\r\n=so02\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"f0a2aec19fa95e02aa10b992c10fbc7f51bf277f","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.3","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.23","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.22","@balena/jellyfish-logger":"0.0.129","@balena/jellyfish-metrics":"0.0.147","@balena/jellyfish-environment":"^2.2.45"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.6","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.43","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.87_1604546378812_0.7019235453426218","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.88-renovate-non-major-ef0074b11b4338711436853513a836bd05a0fa73":{"name":"@balena/jellyfish-core","version":"0.1.88-renovate-non-major-ef0074b11b4338711436853513a836bd05a0fa73","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.88-renovate-non-major-ef0074b11b4338711436853513a836bd05a0fa73","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8ba90bfa423f3e6dd4543f9610959cdb2ee9cee7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.88-renovate-non-major-ef0074b11b4338711436853513a836bd05a0fa73.tgz","fileCount":94,"integrity":"sha512-wTh7OGf7wyqJZBBH87jtc060gH9aAjZXOtdMIjPVRuRSmsctKStJxNzBvSH81hny79sXyNsiXWuT4rO8TfejqA==","signatures":[{"sig":"MEQCIEueXQdEReYlkKZDLzFvZBZIKC+wVyWGr79o4+YUgNQbAiBbVuw1EtIH1tyxSZUNwCmZgrXFvkw+bDfhRBD9dzbRLg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":569649,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfo3v6CRA9TVsSAnZWagAAfy0P/2Q7m1to4RYR1VFMIxnj\nUPWsfoRkwDKYe3BYfgbkDlPz4lS8WIKeIuhkiM9pTwfhiFX50vl+1VOXd2C2\nt2tNNz9B8jSY4X8ncA5sa/Kbx3aeuOVRTIhh2SbUsW8In/fdNNN4onpdK5FG\nnZF053cTP8RQPOtFnMOB1phZAx6rrKcqziyst7NEDAmHC9LqqUALeSl8nsiL\ngTRpNNs32nxUqARTfHt7zj7/nqXzRwrKL2P4QjfP0LzN23nM7hDNgXekV61e\nsoVaq3FijWWLuFh/XfQmR7L2h04Mfsc1RTbzMgb0N40WhIGxDNUBXGIpxrQ/\nf1YGOHry8lUC4P9R7NeJ4OXO6+anhgqPKyDMirqm2+QyYRKtLXP2OBGUuK9m\npU8opKtwGQ6AhXb/M3gC2bqq3eJ1w0JxlyV+IO93OkTUEyI5CT6b1kCeSiWk\n4JectIgfCvNcsh39YXBScrSYqmUK1V+7X1ZTGu3+7E3oWuLEI83VHROnRfeh\n+NbhPvLeJfHCjQara3pmcktnwZXrB7eOAjhFju/L1NsnUKbawubkuk9nxWoF\nMurcGw3pBtPeFJeIPxKk9r1YHLA7Sm7Ce7lQfwtSM40HTt6cX8K7MsaMRH8N\nmj+4J9wGKalqRGmyozH6TgEk5CMxDA/cyAlLPQT+AgLg/D7PreF/plwhfB0g\nJ9Ti\r\n=/MoC\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"ef0074b11b4338711436853513a836bd05a0fa73","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.3","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.23","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.22","@balena/jellyfish-logger":"0.0.129","@balena/jellyfish-metrics":"0.0.147","@balena/jellyfish-environment":"^2.2.45"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.7","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.43","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.88-renovate-non-major-ef0074b11b4338711436853513a836bd05a0fa73_1604549625504_0.2590289797759311","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.88-renovate-non-major-acb67743c07b14f8e0f4718abaa3ccaa75e40dfa":{"name":"@balena/jellyfish-core","version":"0.1.88-renovate-non-major-acb67743c07b14f8e0f4718abaa3ccaa75e40dfa","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.88-renovate-non-major-acb67743c07b14f8e0f4718abaa3ccaa75e40dfa","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6254a0b4648bb9490becb5b46dc2cd65ff8dc4f2","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.88-renovate-non-major-acb67743c07b14f8e0f4718abaa3ccaa75e40dfa.tgz","fileCount":94,"integrity":"sha512-eeDliWLD/ZMespKu5MchxvRWgnyUewDw7NBekFdfkUGZzdbGbS4RDNOj1wKw2PZ3KmcczVK7YAC1MXYwlnMnIQ==","signatures":[{"sig":"MEUCICyyRMxD4z8bBIdE3rfWO3Q3jT51B9xGq0ezjCv/Pde4AiEAr1hHMbAZh2fif+T8F6ZydJ8KoFyCnHRINkLAU/nGXf8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":569617,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfo47qCRA9TVsSAnZWagAA1A8P/0CF7adFURqlHkwQkxyY\nnHwbi6eBfDDM+JLtUtn8r1WGu9zPguZEP6OffcM+3WgOTfKPy6TWyPdJgtk5\nauYVE8KAj/PJFi8DuZ+riROujCAoWGSn9CnlT9XxGu11pKvc3AAvWdAZPENQ\nChPHcZLHzl4AgdVhJUlgXILOraSPeDOQwc87azyPxZEdxjwaVyaPGvl4e8wG\nBO71y57himwgkv/DhBNb8U7sUW/eAelMDTvfGeSHrZxs/zPEIco9ugZfrZvR\nU596w0OCgGmUby/RCdlexfhiTPsyfDOSDIgEpTAtF7WYn3T/1LEKQgSE4ZfF\nvGQP8wI90rVmQWvFxSpwv15OXufGehV4wogLlDfwdVguYdJ6JvzGqh25ZBPj\nSRuTeUpGVCjHaU3RHpJQFuj9C5dTNF9vt+Npd3pi4+6Ka9BWQJ/cb2b+umzf\nDie99g79KUxEW1/biZP0t3btN2cA+VyHNnusNgqKbgrFjWOVfm77ZjiorH/9\neiRmkwxU3rHkRDNDHbRlFiDHnH1WBSHlIwmtbVVpSKmSj2hAebL5xHjPzqau\nPiLrg5G2sgdFGWWNhYV1vjF5h3xKcxsphrJfkskbNUmdbr7pmF9wcFbC0a8K\nwFtxloyYyRXqKrnLbDGrErn4LPxB6vUu1YTl68JajD9A57YeFuvJx0reF8kP\nYKlz\r\n=WeZd\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"acb67743c07b14f8e0f4718abaa3ccaa75e40dfa","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.3","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.23","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.22","@balena/jellyfish-logger":"0.0.130","@balena/jellyfish-metrics":"0.0.147","@balena/jellyfish-environment":"^2.2.45"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.7","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.44","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.88-renovate-non-major-acb67743c07b14f8e0f4718abaa3ccaa75e40dfa_1604554474166_0.9712216325222027","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.88-renovate-non-major-a171dc016bb41d0e0e8df47eba846fbb50b668fb":{"name":"@balena/jellyfish-core","version":"0.1.88-renovate-non-major-a171dc016bb41d0e0e8df47eba846fbb50b668fb","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.88-renovate-non-major-a171dc016bb41d0e0e8df47eba846fbb50b668fb","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"19c2848af4cacce65557fca1f1dd92a7135b3092","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.88-renovate-non-major-a171dc016bb41d0e0e8df47eba846fbb50b668fb.tgz","fileCount":94,"integrity":"sha512-j8wbrCWKlC6aWoPZ8soDZZ7icCrBaz6NYjohhM/VCgZrQIm6JedvPB278P/44Qw4uyw13pv/0LyzoKiZM34xhQ==","signatures":[{"sig":"MEUCIBdboBmsALt2QWVHeLWDNgz1DKh1O1a2u/g1rxAb/x8zAiEA6m/W6D3+qAV1mnrc+nZAVDJoAtjI8Qc0zIym07aJ+Qw=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":569617,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfo5yWCRA9TVsSAnZWagAAMWkP/iBJL6NqQyUBtvsuhWF9\nCt5wGUxjvUeP57+4Ti/7Rdq/0QAE4qguFlwENApc6q/FZDNAmWmgBOLffWiL\nqski14vGVqVWtk16/RHu/gIXPFbXTujNjyOzbeS5zrMo/KEdYiYCMuqdW5mq\nQom/PWXnnghzUSoeiUklxmJlAm4wwTGiXlvKK67yGcJz1NWKGlSFqYx2CmXc\n3TpN1oPNH1MMmeF+TGhlGk11Tp3lF+gx6kSJPkw+JwGA8S5pv0xq0kSUp1kp\nxeU1RIVhH3ndyGsHhns+5ZBx6J00PLpzTLyLPTyOHAtZD1cexrTHt8KE51MO\ni0EuyyQ5vykpJSzAH6Io/6HFar9DnZTjggfWN6KQbFctMzVMwJMLF6ABJhGg\nj2eNneSuBjneMmv6yPdR3ekVZbcVRlnxuUI+0uuPlE+exszNRP6cVqzBtJ+m\nF4vjGXkYZXx6/MlyUNz6+/Y13WV4C0pk2Dx3dCvuw6Lk0Ns/CKf/oNJ33DT+\nMswDChr/coTnRXNgEBTnAyNXsn8K1chMZlP7nWT0/seZN8VMWls9A1Oz8fd5\ngOswlYEjHD60UoUvnoFvNnfbmSmZjodymCn/8zgr40SmjkjPeUVi5aGr11Mh\n0ebK43hKosRqkFbLwAmJRcbp3XYqogE/NmIa1ATGoxnMJW7b8Zz5Y1ot2VbI\nEac4\r\n=cQ5M\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"a171dc016bb41d0e0e8df47eba846fbb50b668fb","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.3","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.23","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.22","@balena/jellyfish-logger":"0.0.130","@balena/jellyfish-metrics":"0.0.148","@balena/jellyfish-environment":"^2.2.45"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.7","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.44","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.88-renovate-non-major-a171dc016bb41d0e0e8df47eba846fbb50b668fb_1604557973710_0.24171666907722233","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.88-renovate-non-major-c504776912b3e63e3ac668761d5cbee056cc3bc2":{"name":"@balena/jellyfish-core","version":"0.1.88-renovate-non-major-c504776912b3e63e3ac668761d5cbee056cc3bc2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.88-renovate-non-major-c504776912b3e63e3ac668761d5cbee056cc3bc2","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"047d56be917c7c58b252549d06d8c4c168767376","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.88-renovate-non-major-c504776912b3e63e3ac668761d5cbee056cc3bc2.tgz","fileCount":94,"integrity":"sha512-U9xQXEd0gkbSYUml8KGQbELK2iiuHW0nPAHZIxgpLoX/X3KPw7ScCgI0PvzvGn3lZpa0wMpqMeVC/9upvtWmgg==","signatures":[{"sig":"MEUCIEi4p/KXmF4pLYsE3g7sXLDxOu2UMImFDf7K/09Qj5lNAiEAiX0RlhtBDmq7try4Tjv1VadI38FINUAnNO+2QWWLQSU=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":569617,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfpFOPCRA9TVsSAnZWagAASisQAJeFOjYxwszHAQf1lgIU\nA1Y1KxNbdWr2xCOMNOYyu3/o5cEPyDHiKuyi4ehThIMHxGJM2LR+6e5vK7BI\nsiqYNyHh3kdQf4ut/1hPjS48iq8KLd0zMXVCqWmIsSr/Atm8FBQuGSRhgL5O\ndo0iC7E6ohDexwAKfND1lqTnBPsWTxCWEHhiTmcQoKNkUm0oQciy1cu2LcSV\nhxk9oMEk7rgPqQ4UO8+vV0pW/9QYGfNdGK8En4b11N2nZFFc9FEe47Hzmp34\n9MfPRNtHcrlRGf1Y2fXvnVNh2Uh0E8bPyvlUyf+npqX4iqLN5woXLr6p/4Gy\nhgqTN7pdUNEmt1Ns67GxT3LBFUqvLKoOImt+Bp1Y4jxtFDO900HcJspEcG6Y\np7E+PyEwdx9CylReV26asCL7vcmsHgSsKfa+g/RoWV46gJ0Tz8KkE6YA9oKQ\nOEKxJEImrsM5bhoUFfHOem88HS/CeEnTVjc0PtXeS5O36N2/R/Nj2wSdVLrM\njvhRJWEEkqbiIHOqbzRzXDKW3Fd63B51UKRztRrRqzVEnwQet99uQ1s6vLVb\nGVZiPJo3vKwC9VGTP/TJy7TSvoTpjCR5QX/W6EZULz6Jb3s6qzfjeH2rcpwt\nBXvP/9MIGId7949TDMiP5kMr07Lv8wTzOZdZCIpBb76oSl9HoEA5bQBtD8tB\nwlgS\r\n=49NZ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"c504776912b3e63e3ac668761d5cbee056cc3bc2","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.3","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.24","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.22","@balena/jellyfish-logger":"0.0.130","@balena/jellyfish-metrics":"0.0.148","@balena/jellyfish-environment":"^2.2.45"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.7","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.44","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.88-renovate-non-major-c504776912b3e63e3ac668761d5cbee056cc3bc2_1604604814570_0.6053656200660358","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.88-force-renovate-node-version-f64339452ea92a55f9e6aa71cb1c615824acde9b":{"name":"@balena/jellyfish-core","version":"0.1.88-force-renovate-node-version-f64339452ea92a55f9e6aa71cb1c615824acde9b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.88-force-renovate-node-version-f64339452ea92a55f9e6aa71cb1c615824acde9b","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"570169ceb96f52ffb15931ac0f3e7ee855c0128a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.88-force-renovate-node-version-f64339452ea92a55f9e6aa71cb1c615824acde9b.tgz","fileCount":94,"integrity":"sha512-TG8R7stQlcBMgn0UjFu0ma0qs4mh6F6jib9Idz56boV0d1qfeGN+91+NAzcmGVUTMiC1vZoR7s5+oSqwMXp1kQ==","signatures":[{"sig":"MEQCIHITM7j4jHKDH/EtzAgnJyCzX7GbIGDcQNkHiYtyYYnGAiA+0JA4LGwDXB3AFg9myHeAzAQElaVtqq0wgxaQLRQWWw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":569703,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfpLX4CRA9TVsSAnZWagAAPJoQAIkt7ZiqJkvoxkhMXieG\niXHlSUmYRKxLsxCt57iOU9irMZQHvcY2XNkQQjaQFqEjbmadoKc01a1Nn1zK\nK80RNsNpLJsSzCMzRJFEXsFUiYvcP5tBMTlpklacV9bDn3KQGZU4sPfDW8kn\n1gAE890jPobHbX1Q/GrOmE4TPAzmgz27c/qzWYgmgwNlsk0sl0GHk0Jvcjk8\ndzMjPZa1xZ7Uky/BKlTfEerm4ex9K2vIr/5z03lA47efOgyuBmjd//RM49UX\nBXqgjbMJBalNE4pNjXf6ROeHc/5DvhoUrESFrHbMH+rmB2vrWb8+LAy8fVHF\nBpJM+/UtDs9bYWaCNM/BbmhinL8fsqjcQMvrDLofz9RiGxrWVXihvpI8zVt1\n61XbSmnxt/8RzNqMncoHL/XJYrQz6XagxBzwImqawMi2H33e4Y1q7EsFEajK\nR78b5m87S0CRB7eNDoDLOx4/OB0h4ZFmYNHbhSUl4vcwRGX6eZsn7kwrU1K2\nn5yd4aZSglAWkx1rJivYI060e0u66xe4qTF3dYHpoCBEFNdhaTeshKZaYGjU\n3JXUasMSJIL6BS9vwr8HqSHml0wxpfBeEpu0Ii06IPx9iMM13NF73apLIkjY\nXtsgMC5ZAJlmyntmcYSk9+72bL1QQQOh/PXAZqY2rvrt329Yabz7uEE2SyKV\nHozZ\r\n=chtK\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"f64339452ea92a55f9e6aa71cb1c615824acde9b","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.3","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.23","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.22","@balena/jellyfish-logger":"0.0.129","@balena/jellyfish-metrics":"0.0.147","@balena/jellyfish-environment":"^2.2.45"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.6","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.43","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.88-force-renovate-node-version-f64339452ea92a55f9e6aa71cb1c615824acde9b_1604630007477_0.13138004560740169","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.88-force-renovate-node-version-3e3b367d88853092c5cfd5328569799ab3db2c89":{"name":"@balena/jellyfish-core","version":"0.1.88-force-renovate-node-version-3e3b367d88853092c5cfd5328569799ab3db2c89","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.88-force-renovate-node-version-3e3b367d88853092c5cfd5328569799ab3db2c89","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"104684fb79106af82eff2ee40ccc21bd92ffd18a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.88-force-renovate-node-version-3e3b367d88853092c5cfd5328569799ab3db2c89.tgz","fileCount":94,"integrity":"sha512-mJrfHthkTJrY2j9wsa+hG0qYycDR4ruQxGLeGJPT2QATA5fmyLIQgC441oH8N+rvCT95mlhFic11jpuNXT0ioQ==","signatures":[{"sig":"MEUCIGcLi08+6Yikw6lXpKX9ghr7itC9icZuwVddrIt8WCafAiEA9qXG8+6a7Hk33rlYF/JPsfo8D2V4Zr6zN4AYMyCqmz8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":569704,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfpLcKCRA9TVsSAnZWagAAjpEP/1syDj4KJUuccXId3KFS\nq+5Y/wnSsuWIW69Tk0SMOK2wKtZulr8W5VfK7V5txEV8ug0XPmXHfR/c7vC9\nLV9yXNfYVWSrlWZjLiAc51+HSGouDnwp9HaBQ87DVLAVMT5KoICaxFZ74qLi\ngoaBEfcC+2CJMtL/JHayRR+LNtKsvLcQLplybwjsWydsvG9xXYQ5suC7vWug\nOoDN7sOFoV6tEGh9ggJ4k++x2Do2PzM9YBT4OPxIff8mpOG6dsV8xeuHm7hH\nOejvNysfBGAUJr3Ebciyysd+20ybTzelBjfVKk3XqIstOw4+1foeumnGthcN\n+LUS1dCVeJaGEwlVaMwvGk/G+CAoU2NTtjulVidcUp53RlWVFRytC6g8o/8P\n6d9Th5zpUz83BFVFH/ZThv1a451iCYXO0JJ/mBUFEubRjBixRxUY27E3K8N7\nzFcEoXWYihb2Q94tsBD0IKH4ipUMWrALPRfdNgdwJCST6ENYqAXC/0AHnieZ\nKBBVcVeyzuO/w1BvNDlfubSLrzAPr2ZBQuIGfYnD15yQYnBjrGLSDtoXAEjb\nr/1d6CRl+46dZhVbYOZ9scEEvoNUvVb2W2AfKW29+e5aRt2rj34PY2hpVUmd\ndyDlxqe3wI8Z85jf3h+s822v7VJo38dmwPYnOoNi7dxzfYXpfI4GjRwJhcQO\n/PcL\r\n=72Nb\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"3e3b367d88853092c5cfd5328569799ab3db2c89","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.3","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.23","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.22","@balena/jellyfish-logger":"0.0.129","@balena/jellyfish-metrics":"0.0.147","@balena/jellyfish-environment":"^2.2.45"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.6","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.43","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.88-force-renovate-node-version-3e3b367d88853092c5cfd5328569799ab3db2c89_1604630281890_0.5443868761109474","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.88":{"name":"@balena/jellyfish-core","version":"0.1.88","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.88","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"23cf8280c7fed890409ef2984f26b2dcdfb7a5c4","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.88.tgz","fileCount":94,"integrity":"sha512-gJMHcg/RxyOYzyeZ2DVVK9qd0zpcok7FkK+TYVMXQPbnIIqEipfdwXl3XSBV4gsJKilRt9pXsDMUilk0Iw3bjQ==","signatures":[{"sig":"MEYCIQDhZnCCVrwfs2W+E17yGsXIOnOmdMwd9936xQN00DQijwIhALTHNZGafobsBVUdtPS6i7OHtMPmVxysUGs+JpQB7RLF","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":569635,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfpLhsCRA9TVsSAnZWagAAM0IP/ROkgEcs6qAX3LiGPqw2\nGW+NOCY1e4HNCJIWNPb1C6i1tkD2C0sQIzHZZsaUHDsmzD3anPF3sGPOfhox\ntC/uniLoCQOCoMAvo9TlgPiXKqBqAwlW3+cmHzylmN7Enwgwkus+eHb7VOJT\nqXHgqVPKhIXdOQNTB73CGqlXopMl5DPFBgb9n/nwxxv80JO9GQFvvfShk3it\nOpHlTXyJDQj8KrwsJbCpRtHHCL6wGNZrAzMeKCIarU4Lit+oozAm8ECjCghs\nwLK4fxOWKGSvIt48Kbt45P56pCQ49wSgykZDRr9mLdj4geGKNnXwqK7sHIhY\ndeEbLKpxw4EqBpliod8pSVdJAUaz2E+OLEZroD39txfFZGSEGfyZn/02IrM1\nOstx7fMVCDtdm/lz8qpVsRlpmHSyRWQtWr6vNZ2crvWKMSwoZQ4NkmNihCSL\nRDA/F49c0Tm1orJg5nZJTXx3zktGJOeuiRMMf6Ee+sQtGPvPwLJrQVADjqyz\nAA2oHBPCqCoTFve6XxIFu6DBm6hcFS3Hz6OrWwXoABhOVRurdjRUe1ARkQMY\njVmeFjAZ2j2XSYEYZHwsqfGfyzj9MRQXGq3JRp3pIzLyI209wgtN3rSb7mva\n9Fi+NuxQ98CLz77A7rPB/vZlbyzDq/iob7FV57Lya9jlu+yoPxXOTHP3pApX\nJZ7B\r\n=8UCW\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"b21f712911e8adce5cfca1f7cabfd5aa73d5f94c","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.3","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.23","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.22","@balena/jellyfish-logger":"0.0.129","@balena/jellyfish-metrics":"0.0.147","@balena/jellyfish-environment":"^2.2.45"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.6","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.43","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.88_1604630636026_0.040268268404350716","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.89-renovate-non-major-17136a725888846b436a39963fa32fc3bebcc618":{"name":"@balena/jellyfish-core","version":"0.1.89-renovate-non-major-17136a725888846b436a39963fa32fc3bebcc618","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.89-renovate-non-major-17136a725888846b436a39963fa32fc3bebcc618","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"be14d93a8db97726c0f4305168e45d2a26baecec","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.89-renovate-non-major-17136a725888846b436a39963fa32fc3bebcc618.tgz","fileCount":94,"integrity":"sha512-lNRPRie4CXYAuOSuXV07T92qZs2L/9IOoEsaVslJvftlXe6KvQ6ZVqoLdxC45y+02pl3huo7BGzMU0OQEGeKvA==","signatures":[{"sig":"MEQCICGAQ5jub73QkjAqtXVr7wbDgdqjWbJNsH1Oe3pORJmlAiAZmI0uAMmBMZxm7MCx3OsPc9+LFpV7IuveVjzTpTRUdA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":569761,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfpLlNCRA9TVsSAnZWagAAoXcP/17XTfHl6MEVidtRSRsj\nwGt679UW8prFzu7zBOOTXEa+6rfWf0+N4+mMY0inxKfgbS3eY/lEKUoUxwoP\nILhdYSJtzQ/Ad8usFUbTIJIeEXmWrh3+ejiZXGNYlWhcTHikgbqwY390E8Ga\nAYFDCNB2adpI+6TKC2ecFtzEFixXAbrexqWaV1JvmGpnOajIDAifoX/ozZZi\nPEbdz/I98YH42DCthHdi2bqgtnHf8oFE3I2Y13U9A4j9d2Kv6PUT/xHK5j9n\nRpjIRUHar/wD14mMmExlp8/xk6DjG8F60QFU71TJBxFZImJFwzex1/eZGnb7\nB4qfboEqi86KAgzdewLITZLk6nTDkItqIye5E2pIRWa4yGfrTXx0mZn4mM1n\nXE+k6wUCq52O5FIWFcmXkWWj2htVOuxsr2utpOVkISFBnSsgjvhGbTxAcxOE\nkCpW4D9i5kUmVpRgTFbrq1g2QEbOB+ccg3xZF5PK6J1K5u7RNL7phwUDY+Md\nWgXlvjLlGeu+5d6R9vWh7gz7ggcvl3Gx8smmiPkIxRxxRfojCVtiNw5ls5Fw\nRCIgkuf/V7OD1DAKCHvadiiSAV/GsUVm2af6DrrvDnh6BmMp0QhAeSqVHRfW\nk3tPT6QYHaNVVB4FIg2EghdCLvGAdht7L+4xUBI/gbz0RYVpKPjRIdDryccn\nzqHv\r\n=KMwr\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"17136a725888846b436a39963fa32fc3bebcc618","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.3","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.24","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.22","@balena/jellyfish-logger":"0.0.130","@balena/jellyfish-metrics":"0.0.148","@balena/jellyfish-environment":"^2.2.45"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.7","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.7","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.44","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.89-renovate-non-major-17136a725888846b436a39963fa32fc3bebcc618_1604630861323_0.6311314975613953","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.89-renovate-non-major-0783d63886c03c6555685db01e3e31ed0dc89c0c":{"name":"@balena/jellyfish-core","version":"0.1.89-renovate-non-major-0783d63886c03c6555685db01e3e31ed0dc89c0c","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.89-renovate-non-major-0783d63886c03c6555685db01e3e31ed0dc89c0c","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2226112e0a9b680e4a1d2d33f3a4b4b6675396c0","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.89-renovate-non-major-0783d63886c03c6555685db01e3e31ed0dc89c0c.tgz","fileCount":94,"integrity":"sha512-pkj1lccMG8tYZOAKEAPwEFvaDnFhHx6ICCsGDEL+3Ovc4WPjCuw5W/jyFNp56ffxOAWqTAUhviYCK7CkhDrsqg==","signatures":[{"sig":"MEUCIQDyIxuxX82GYt6QoGenFycJS8Y9eVcK/PxcYj81UodatQIgO3ddlU7jsF+IadMD3dFzVKwbRuP8RPX5E9AloryN12g=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":569761,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfpMl5CRA9TVsSAnZWagAAAIoP/1OBZMRl2CrJDbZlHAYx\nWCvr2fd54wxBHWJ9lky4j7tqFEVS2bwMR5RcIyi5xVMTfHOou/PMng4b+Rhk\nhy998l47Q5nlRKrvnb17eNvpBOAJow8Jj7Uj9xbIer08mYFopnjBEWUDGQeK\nZjx/zL41QvQ+O6Z1vsjRYw6Q1skQBPTr0/3Dvei7inN6uA4YO3+HgsOMGuO9\nF6+FcesrABZZMugx286ZvaUn82bsJfc7qSOrf/q+UszmSLauO/0dhcTOUWl3\n0/O+Mm5qkM7dADHOdQsFRPK0dTuCS1DUugJokBuxuHPiFij3U7gxo/3Ui1ae\nu3GrWZspwWroQwP0AalTjEETcFjQ+3TwFWOWGdLRFHbSZC5E9uOl/dMIFrxI\nr+6osBA0mRlcBjIcK5Wds/CBlaMigVO82oQm8Xb4QX1QoqGcf187YBDdbe16\nuZjmfaC3F1T5wzIvviwtPoaV8l7W11J9b5A300MICMeyeMPR7Bx0uZzVSyNW\n3O4LRxr/gOI2hjnGjOwoejdbHqT6+lqr6a/KlrMztPAYPfxDQ7bLVBCCnEMY\nBkIlfTU6cO8WAbZt+eXDWuW9mFHuvJsVfIQkGD37upBfhwNbzorkS6x21yU3\nqCvK198I5WSLi0mNHK5E4DvQfZ1Ogoa/JO56qa9JOlAo0AF4ijdfoZKkeElz\npp9f\r\n=hhqE\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"0783d63886c03c6555685db01e3e31ed0dc89c0c","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.3","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.24","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.22","@balena/jellyfish-logger":"0.0.131","@balena/jellyfish-metrics":"0.0.152","@balena/jellyfish-environment":"^2.2.46"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.7","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.45","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.89-renovate-non-major-0783d63886c03c6555685db01e3e31ed0dc89c0c_1604635001181_0.8773107736542582","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.89":{"name":"@balena/jellyfish-core","version":"0.1.89","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.89","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"20efb06b0e42801b596eb547f6cde91cefd9a281","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.89.tgz","fileCount":94,"integrity":"sha512-aC3Jsmn2WXuB6l0Y8gvTY6vxHqusT3GFbz/P9ndBY96mohdyN4QLMCyiIKASLZnpKLS3gW/GY1j/NA77SCzNlg==","signatures":[{"sig":"MEQCIA+lQfn/XHxPzOvFRtlpY4r1ceuN7+jb79TXZSiQ/3ckAiBR6vlu5yD0evqXzigf42act7ev83AFPtD63090HACk/w==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":569701,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfpMo+CRA9TVsSAnZWagAAme0P/3yAVowP9ticBXmZ3oe2\nT9S9M/GIfT+R8BoSKpluAKK/Sqd0owU8UiPgWfbHr+fb7ysO1lKv75Iuqs2i\ngi95ChOU9IzJHUkTqYoveQZNt2sJzXZISq4qtY26dN0XZCf1damUG4RzkUj9\nyUTxVHejt3CkfXWovgj8jhFZ3dRx+VpkTCFZum1SCaQ7dga7iHpFaEsLd6bT\nVJ0R6HWU8PeBQLk/eEr09rfLsl1FhrpPNYC+PpOWPnqAeGzet/ZMCEE7VFyj\nioTW/pYdMYJvL+5jaHFLdrleB7lwh5v8MXGf7ZJ6QtdhyzACHtrEwng/9reF\nfAPXAQ1JfQQ15rCfOpWjn8knq4E8K9K74Qm6D0R4hAkZGEqTbao4VzVKtmfl\nqN9OoJoHNE+ScIDNZVE4C2fmySme+HpbwXucm36GK7UJImvZfL4zXYVjWmer\n6nQsot4XfWJI/A4ZhPPW+wdKeCV7dQBIHCQWR2n5ywF6wW+NehiwPhUa/rgK\nlW49ao3yM3TngkJaiBKSdg41ek/diXb0bsSPsHzQBNisZRGk32EzJ2YFC+k0\nTKafhbt5gIrGiZnDA/z606RWLoU9g5oi/cAcn6h/GrdSkmU6XdjUtYiHTu0p\n/lsQG7CacEid66A/dFISK73OF8iW1A+tKoOZtsvSGQNaT2E5uDelT54yjZG7\nsBDi\r\n=GODR\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"edd28409fc4f36e8f2be9d5a8cad8ad3ac7353f8","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.3","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.24","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.22","@balena/jellyfish-logger":"0.0.131","@balena/jellyfish-metrics":"0.0.152","@balena/jellyfish-environment":"^2.2.46"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.7","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.45","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.89_1604635198309_0.2249879922856819","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.90-renovate-non-major-3a0040de3079102c210c76e1c6a99ffa3c100f13":{"name":"@balena/jellyfish-core","version":"0.1.90-renovate-non-major-3a0040de3079102c210c76e1c6a99ffa3c100f13","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.90-renovate-non-major-3a0040de3079102c210c76e1c6a99ffa3c100f13","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0f94cc1081a1e0f6dca800f24b2fa70a093f628f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.90-renovate-non-major-3a0040de3079102c210c76e1c6a99ffa3c100f13.tgz","fileCount":94,"integrity":"sha512-oI9rTyPGmAB4EwIqqOoR8RUEy+SlOaabWwKkLMPZ35IzVVbqbtYJJcNelIvhcvGK6tEYvTYI0OrIpb0sCuJqwA==","signatures":[{"sig":"MEUCIQDgMAs6XbcfQw532Xx1NjOxCmFdUqAhg1C9WewVVzUdNgIgQWw7Q3EyoUVnd9kw8DOTJJ5cLThdqk7lfkvHwvKP8kE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":569827,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfpOAaCRA9TVsSAnZWagAA25wP/iax7fj2ILSrNefjSG0G\nMop85vVvlco6fOUwbb35In+Gtt0toukxCjfBAhwMeocCS+xQOT1CLeEZeyLE\ne2kc4CtCPJnhk9m2fm/YK+bjmKgvSLK5UlPQcHDZx8l5PyH5hTVtpERN6cYY\nyN+E9Es0/rnxTua4s+XvjI/FWbOsgGWP29N1Mlj4bzSq+XrxsA0Wqqv8tNz5\nkf5Hja2o88v6rWP7kXrH9/TV1GdwJnowq/EDF8IGM1P69vZvxW6DHhKrELJR\nhk7FUYlvWf3FpHZx2ppfwF/54QOqWIr4omKl+bLpQNEGJ7/lN/hpuIehFP/h\n9Ctcvon13KhMgq5P8caS/BMHDqzng5crIOl4rTEvh8nllUVKbxQrEMkDLhF6\nWttYvrZ6fea4hAJFjzSxHkUiJtQrDgPwOaxT42Jzx03cLmuoV+gNS0BKCh6L\nfTHWJ+sDJfwLd7hEGWk8qJAzV1Kl1NfZ+K+3weBi3lYvw7UBRrs2BFVd2MjV\nfn9GuMINHcTBlb4IgXOJYurOnnNgMYG2t8EFR4YZnf22NJXA6yWTOp8QLUVE\nH3/W8AdSO/mC8DsPCQ6OZnP03AZmMMRUG7DCpckMjnn0F3TZJWZNYYdzZWUh\nrzUsRjTZq3f9Uahtb/xXvOXbWHybM2dj6Ff/WRjWygG38zePaddOmuQsljYP\nsE0X\r\n=CkYY\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"3a0040de3079102c210c76e1c6a99ffa3c100f13","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.3","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.24","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.22","@balena/jellyfish-logger":"0.0.131","@balena/jellyfish-metrics":"0.0.153","@balena/jellyfish-environment":"^2.2.47"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.7","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.45","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.90-renovate-non-major-3a0040de3079102c210c76e1c6a99ffa3c100f13_1604640793557_0.31881982407782794","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.90":{"name":"@balena/jellyfish-core","version":"0.1.90","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.90","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"467453deab3db83b92beff90ddc541072681a393","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.90.tgz","fileCount":94,"integrity":"sha512-BZnifqC5sHC4nR6nXAOuf0gfkUZA/nBU6L+W+26XIgNVZsjA++ZVsyVHS6l38hHOy5EaimXpuecxednzVoQAPQ==","signatures":[{"sig":"MEUCIQCZI51dmdJJM/i923FMUDPa3sv1s6sQsTxuUc3wri0sIwIgDUWJkXkvdwNBLCmIhqh0idKINfeL2OkbHQw0HTvV+ds=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":569767,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfpOF+CRA9TVsSAnZWagAAqwMP/3DwFcBIyNsY5owbDO8o\npv54eOmEbiSvTGr2Nt535SlbcUmE87LgKwY1XC+ntGU5ig4VgawJP3HwuSFK\nXHHa5SFsjrmUNaUShzU/S60gd0bDqM3nA6LTHC3jB1h8rMh4adS83DHi+cbH\nEAW/TMACwyqD5GGaSjp/I3gycNWt49t1/vNY2Su52yqvy6ECJT7H7h165sKO\npTuN3TtnTx8UkL6o4MLn8imXXANAtSETwDz6iAMq+jmdecI5ApQITm0kmuSG\nPept72+Dnr7ZQtNHxrq/Gk4sO3PxZ/tHG5N78TbDFxpuarRkUpi/BnOVbFsd\n4bCxF8bnp5C1D5YoiDe5BmXQ8SG4awy4KPpgJP7eO4zNimo8Xjp6BevElopy\nmPNcD5vF3ky5joeYZ31ns3DKqAAkgpij+Na74xffgFq2Q3K+izR60l6kTipW\nLGVCo0eeTLkCuaA40NJsDZbxIVQU4jiAowKjacEae697TYeYDXdhi2MxmtOy\nbmghi2Vx6OyJouMeKTpzaCRxc8oYrJT5HjM3eklxWNnc9gkQymAxF25UOF80\nP8hZhihvpzkUMTaa5FKl6oT6fkJstA/h/KOJlYVc1M/UR0ZIPe3lGUOH+w/s\nhOryt6dnZC4lIbMUoGK8ySFrwDx8m3Rw1BlJOUpfWujQ2uUr0lPJGzGClVBP\ngBg5\r\n=gpR1\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"ac99bcaff1a3e9b8b072a5c40d7a9a9eb75f882b","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.3","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.24","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.22","@balena/jellyfish-logger":"0.0.131","@balena/jellyfish-metrics":"0.0.153","@balena/jellyfish-environment":"^2.2.47"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.7","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.45","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.90_1604641149845_0.9781441581425863","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.91-renovate-non-major-82a096b4b4d6f8c2939bbc58ffee25c19651117e":{"name":"@balena/jellyfish-core","version":"0.1.91-renovate-non-major-82a096b4b4d6f8c2939bbc58ffee25c19651117e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.91-renovate-non-major-82a096b4b4d6f8c2939bbc58ffee25c19651117e","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0a34d97db73108b44cdc189a1310fe596e4c72c3","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.91-renovate-non-major-82a096b4b4d6f8c2939bbc58ffee25c19651117e.tgz","fileCount":94,"integrity":"sha512-zS/UD/WmijI0rY8v+pzeg8M9qhiFHa3ir0Ksno1xYROedRh8Yn9zu6gZNPDBkTe1pbQQ4oTVaruTINiYWAWM4Q==","signatures":[{"sig":"MEQCIGP2KO0HOaG0X6I8kkH3hImQB9eboHHP6P0hTeCQyWpfAiBBeAZxlK2Eqp9DY5227Dr0sj6+4Cc6smNwSDGla4FY0w==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":569931,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfpOrlCRA9TVsSAnZWagAAW3oP/iX1D5UysV/YgPQAckrr\n+Cr60ZBXiU5hwPQANpjTJRFlk27/2kEy269vRck86ba54vYjsMqewq0BtY0d\nfLYltc7FSL1MML+TKx6iHCMCChRC0GoQpSdJqx68/6sKE/9GOVTGYrIsK/6m\nH6+Pem39/0m06+ntPPoljStKSAMkjZWKjozOnmEkYag0yPxTYV8c6vvO9YE3\nP+SGADc+2jY81509FSpNJZC3gAo7IMkBVw46jlL7CpEXlvak4WOWn8ET2hnR\nkzcG+1UXgKWE7uZ+aEJAHZyxAiHyqHV4qLpia6IWDFlBkQYfpDa7MoLBglvr\n+e4AgGQJBHuY/41zMPNL+NL85P6YpTrp/BhUiFSDI3NSIpPqj6zfNZN+kQev\niC3Pl9u03zjudvxPJi02z8w7E4VlORyKLv5dGEdecFS/JZou0jwq0I3eAKlS\npwy45MhpApqLmfNgItXrA3KyhE4QZj7nlTSOLksEEm4Mgldny+N+wSZT3N6G\n4BZqw3xAx8Cnk3zgfiPeA5GQvYoXN0PaPGn+284MBdYsZ0gleYGW8SWpkmwk\ny7YtNjpynZgQH6rLnfAIq5qCvbx2dcY4kv5Fn4Rf9wNnEHiL5bpQttdRQY8a\nmoQZRrwYSbaA3wwxkVdgEilG6eEbTz2DVhpnXkJvZ2CMuo8ng0bH5IFj0mAf\nk29f\r\n=G07U\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"82a096b4b4d6f8c2939bbc58ffee25c19651117e","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.3","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.24","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.22","@balena/jellyfish-logger":"0.0.132","@balena/jellyfish-metrics":"0.0.153","@balena/jellyfish-environment":"^2.2.47"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.7","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.45","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.91-renovate-non-major-82a096b4b4d6f8c2939bbc58ffee25c19651117e_1604643556913_0.7168890786824051","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.91":{"name":"@balena/jellyfish-core","version":"0.1.91","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.91","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"bd1fa57d4c55dec5d7b5ac4cade837c369b7d22c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.91.tgz","fileCount":94,"integrity":"sha512-xsfYt2zNoXDI/3xZ/lMJLvdQApVFHJfvwx/FRCRBcFVSjYwrIz8Rz/3pApPP8M0YSS8G4AbBUO66XRTeqGRKPw==","signatures":[{"sig":"MEYCIQC/+F9IR3upJHmeR5xGn8jhxuLyVdlQPWXeeLxpU4xJUwIhAPDBhNAPp6dnc2nX9Gct3DyrdfFhwSIB67s9x4lit8Qz","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":569871,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfpOwYCRA9TVsSAnZWagAA0vEP/2rHlMn1VaVb2S+/mZyn\nFyz9qw5IesHn+UigKaCK1yNlAEwrr55p6OvjA/ZPHEPijU1BzBDNL4W/QYH+\ncXyHXCZOUFmoWgHgqWtRnVS0nbRXOc7STLX2z8WiFBcGyNUg/0r2wVHgVKiS\noyAFoRxLCIoN/+siBZ9YwaYttZgSusrnAxiCoVyh0XJOSwfqhLAPmrOb4VA4\nEg7rThql3GfOVrWueHSPZXUHk8jPQwFdL69ubKp/yyToGdzFKfG7ySl1ub99\nmgc0SP1hNllnr3ILyova0FJjHglQ0Zsba3uSZntA9B8bodS4bTRfyYw5/NTm\nIrGoC8Tv0vlqzf6815k+hm7Ho+zAGqv3nNYv6fWgBP3V5db59HIek732BmuD\n72UvFLtfVR7suzWoch0LLYW1WnMm2WD39FImWbuovNdFMpX31W4P+ttcrAvP\ntV4zpRVc1Kx3hq3dVfVplzcb2kbSYn8Lf0I0TU2gzRLqK0yXJiQlRW3geFuC\nnn8GfOAtwvnRJrrQW9bm/lyiZu59sD7CYtWe7TOLbt8iURK6EkkKsvMiuf1h\nOwldxe8cnQ9Xu1BVAeECerKsQ55cJLhsXQ7n+mxvkz2Ke4EMU68UbiaEEvFG\niVQUJv7JZYSFqk1VLrg9WZGm1U87fOSdx84X4QkUUx+qw1y+8ahEXxGYyIMn\n3wjK\r\n=1npB\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"6b2b98752d73e9ca08ecb264d4e405b10d739831","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.3","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.24","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.22","@balena/jellyfish-logger":"0.0.132","@balena/jellyfish-metrics":"0.0.153","@balena/jellyfish-environment":"^2.2.47"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.7","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.45","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.91_1604643863832_0.9727886931850964","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.92-renovate-non-major-7c25c128cf4855ff2a96f167edc5f6ff128ad41d":{"name":"@balena/jellyfish-core","version":"0.1.92-renovate-non-major-7c25c128cf4855ff2a96f167edc5f6ff128ad41d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.92-renovate-non-major-7c25c128cf4855ff2a96f167edc5f6ff128ad41d","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3e9cb378c2ee22ac535d3f55c15f08d46875ee62","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.92-renovate-non-major-7c25c128cf4855ff2a96f167edc5f6ff128ad41d.tgz","fileCount":94,"integrity":"sha512-Gj5RvoFkfQ0wI2Yw0u0vPMYbA/E8/UgsddAfU5f/yHE2c3jkweVjXKQ3gqFG2GjS4m9iTe/HeNfWrvaX3Deqog==","signatures":[{"sig":"MEUCIE6ZAYjMpjeB4oCWskW4qpG4O5DF2ZDv7RUZMny1FWAuAiEAl3Lunr9dx8KIG6LgEUdex+6O64sNoqzfCi9N5DdUzaU=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":569997,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfpQB8CRA9TVsSAnZWagAAddwQAJB/LS4XMTZlNN0SYpQu\nJ0wHmHLclLPXEhR87uu0+9oNzwBiBDzUgD5NVJjlWCcBKuAmwgGUqHu8Hady\nV6Rv0Ghg2CG6L0II5aHSHUGAuKRZnD0wWFbSxAMDD9IWfJ5vF8+9t94Q/1TU\nEfDU29Dd1Ff3KXy4qRSr/Qm/aIvV2fJR1nF1qxyhMICPA6KDeAOKVy5SWyYG\nEM8u+oqGMux58Y7bkpU3zSu/KYyvwGQwIS3VInzLg9SNN332Z+xpKmUHcx2V\ntJIFEQBd7sHDtMWi9aiAKZ/3+PptSO8RywbXVPu6SVBYHlofSvQVqUSmVp9g\n02xu7Mv33bsdLlONLq9a9etrKxARcvhbwQNauInx2lK3TegOwQPOYB5fXucL\nxDt0LIm1j8WA2jqeWRiKICdzOVaebX7owc0g6tkPJGBhDjxV2Oj4q2Ilojja\nS+nAE/xx1OanD7MEIyOoJ0kDI1XsmqK+kEgHUWeC8aIIPWPUtjzhOaAN4gN1\noMOBQ27U3tIsFAKsBuawb+LM3epVz15cmqFrh3NO+0f33Rh0m7DP6rSqVsvB\npSYL3tNRIpWt6PKN/nEvqZHiFavLgmYijEEt9HYUNxM+aRtMKRdkUr4N8DlI\nDmp1I4yNGsRvdIDpimHb9kD8fhSPmOMgV9l/fM3xihmV9JZjlcNEejz8+W4g\nXWxK\r\n=Ys79\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"7c25c128cf4855ff2a96f167edc5f6ff128ad41d","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.3","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.24","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.23","@balena/jellyfish-logger":"0.0.132","@balena/jellyfish-metrics":"0.0.154","@balena/jellyfish-environment":"^2.2.47"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.7","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.45","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.92-renovate-non-major-7c25c128cf4855ff2a96f167edc5f6ff128ad41d_1604649084339_0.5983656300148095","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.92":{"name":"@balena/jellyfish-core","version":"0.1.92","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.92","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"53954302e1f9050020ceecb4af86497852eaebc2","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.92.tgz","fileCount":94,"integrity":"sha512-LPyWb4QMSJN0H0czHzI7K1Za/dzFoZ+gbyUgoAbwEuuhMDyayStxJmvtXdcUMkZ9jfw2ucRaA132b0otvb7skA==","signatures":[{"sig":"MEYCIQDloSe4xJp9MQuQ2vUDTRLUXwx4O8SYeRiP75BWh4x6ggIhAMYPVQcjhbXMB3cX6UvgY/Kt9hBxpCnnrJR2u4cpP2Bs","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":569937,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfpQGACRA9TVsSAnZWagAAYn4QAJTAjM22nTUbk/JM77FG\nNckZN1HHyKjbqa9eMOpUwyBOJ7ywDyO4tOD6CCqQE0pVfvugaT+iZqgcO7Of\nOolQYxzq0JcOPkP0/jT79cf/RwvWdHqq0wH/Ih+ms87IEh+6WzFoBwMubqFm\nc9QN8VJiNgwuEtbjRoH8f0LnYxxRA3FYP5USIJ6KQEQncnyZq5YMkXz733lm\nBxZoq+zSKdCW23EdNFV9H915e1QT2U+6MorcG4ef1BYa0lPFoyXqkEkx2ELf\nGi/OujNrWJqDIefqpDBU7XxrKHv6nVmse/qYK/irrPgcJcasvl/VLDbetK1p\n4YCzicY9Pkvt7+puNlZt4TOtXFd2Hlkjxaq0TEcsoGMKYV/jku9Djqq2DNdr\noJGk0sz9AOONhuywkutWM1JTxzN9hwxAQim6A69qx7De/USLjz7U5OLVXWF6\n/m0keINPr3aipmxDMiNqxb/++TxJdJaihrEes+tO4sLthnPl+RAmoe5/Y6o6\nCtSoyMoltEM+WJ9NS3mJuMgzXzeh6onKV81VI57LFPWS1+EI3r8GnOOSYeFl\nbm6RFJu+BgGpDqilfpLnPDPHwiA3InRNa8G6XddSqPSTFGHeQrOeYKQno0yQ\nH/4jjlFv0+OvobjpjxIsSi7K02w4KnaX0/eebPq9j9VGpvE+8iybPgODfHq7\nC8p9\r\n=2gkt\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"319b8f4c29c502968ef3c03220c61a32ebb163e1","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.3","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.24","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.23","@balena/jellyfish-logger":"0.0.132","@balena/jellyfish-metrics":"0.0.154","@balena/jellyfish-environment":"^2.2.47"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.7","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.45","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.92_1604649344108_0.7970854687508393","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.93-renovate-non-major-759447623379aa9b18e0f23823392ecb790ee30d":{"name":"@balena/jellyfish-core","version":"0.1.93-renovate-non-major-759447623379aa9b18e0f23823392ecb790ee30d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.93-renovate-non-major-759447623379aa9b18e0f23823392ecb790ee30d","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"cefe2225c90d5bd37d5e529829cd2334230f8f01","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.93-renovate-non-major-759447623379aa9b18e0f23823392ecb790ee30d.tgz","fileCount":94,"integrity":"sha512-xMjax6CkRx2oQxpmpO9IS04g346U9Go6kiUWAUdB8fcLF5sLogxxw2s5YPxDyIFOux2v2d0vJzkrsxGqAP+mMw==","signatures":[{"sig":"MEUCIQCEYrp0aXGbF1ldPwZRxHVNy4q0ZMkBgV1eGJdcdDtnAQIgItnVfdYr8RZIKqNtfYReFCqWaFlLEZ+z3D5sUf64Z7Y=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":570101,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfpQK+CRA9TVsSAnZWagAAMUoP/R/6EgcesEB8ZIm1rnPE\n6dwG7tFri4Mtv9wC8Dva6wo2huPlVxvI+9iJ9FtLjFRoXiHOlUm2j2sf5Kg0\n8YDhoiwaWd7BGZu3nRcpff+c0HDrxmPXi4TkHgfr/cyeVbr2WPiGIStxcMzH\nrvR2+yQfx4Yez+j7PBdb5Z1I3JOHrDI7HPxoILclBI6lIIayVggaM3oenMRZ\nJL0Nm7QGgB5+cAjyW2+v3tEYuVltWG+kuD68l1L24WBjHhWwsPajgZ3M7Yqr\nfn3nOM8RwB+/ndhCvyItxVacGR6HypTKCul4t1E8WqmM46G8KXqjBgrQQl1E\nmGNpa2mxp9pAMd1DhbNU0vBejjstmyo1itel5+Po22tnqpfzmXV7DmRQbQF4\nrK67mb9GKs4uwFFvXNrGR3f7tLooMigqwAB2+TJjEHHrJA+dBhdgXIxL9TDt\nNVogNz3UWmHZYhsyH3ybbkhTjnEBMU8H6fdtWtcGsB1lcVPwtFiS7EQlZFg+\nxRf4Mhbq7iCoF2kPxyR/zKmA8+swKiFg68eSVyoyRB6Y9CDyLQN72wcVDBUR\nImYOEYbGOgvmOmNOGWs9UlHnFgwf2T1UgLO2sAg+xCap8Mtvya4qO2dw/5YO\nauJroNw6iYnAXOVVT19lf9sM2COCZQCp9MJ4eWDHXdPMtShX1Zmo/m1TPRzh\ne9YD\r\n=6Hd0\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"759447623379aa9b18e0f23823392ecb790ee30d","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.3","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.24","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.23","@balena/jellyfish-logger":"0.0.133","@balena/jellyfish-metrics":"0.0.154","@balena/jellyfish-environment":"^2.2.47"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.7","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.45","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.93-renovate-non-major-759447623379aa9b18e0f23823392ecb790ee30d_1604649661702_0.04179164934068025","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.93":{"name":"@balena/jellyfish-core","version":"0.1.93","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.93","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"58ef07db89958c67829cdcd80419f10ef9e0191a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.93.tgz","fileCount":94,"integrity":"sha512-DZFWnxRxtt7EfpUieSMwor2IdZ6KrPEDJBNKZzqPMfd5Z9FUwRyB1nj8VXMbamoV6YHUw5IIUIvwsQ7zE/6wQQ==","signatures":[{"sig":"MEUCIQC4rV3S93MGXlZQLLrL0eF1rqFE3SPKInDwQgdt3reyfgIgciuoW/GSoO5l3N9vW82yqfisZX4TThx0XzdXybA2qeg=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":570041,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfpQP9CRA9TVsSAnZWagAAQbgP+QC03ltmNcpflLmJSuOq\nocK6Dw2Vx7wJI2M5nY2+peu9ZaGgGUo1sEJfILQ//tkIuuHtSTbUI0PYbLw6\nWQEh0aVyXSDrswcfwpBoc3EyaHmM3wo5+7++o/nzOHRT67vOPtB8RcQNAxdM\nV5uIztGa/tyRekydWFnnMREV2YaVydft2ckjD7Ej+fMGknbpuK2XlrXCh3Ig\nUaf8uRmww1SnTk/djk77h3zNvNlqFNicud0remSbA+Od/s5/KTX/tKGKxi1n\nNlNV4CLWcgAEUqKcflxjc3JwvKLQ5BwfuM7ly9k+2tKFLzZPZdIXvqwZfyix\nzRVZpGqZndWRCwhptVhMJyzZU4kcnndtvh17FTuFWanvGpJy1kU6lH+r8MHY\nYbNpmFVK5kPQnoGUoEOveDUbyrUDKKLOxihtEtis2O1l97nEHqETq2zqdGf5\nIsyWiZSfNj9zLY5D/T7Yb/8MprXqMNod479jzTCXnC9rzHXBC23rRsZNAsxw\n5ZNOEBtlgCNBmwUkYNH/LOGX99Lk2sew0kixjvlc49X8GdqXuIgehi1D/3r0\nqNE1nR+2WvSpkQHbtZPuzQwaLgk1atNZUmuDQpVDu+6kSOzR9lZlBppNoK9n\n/VHn3yJKLHfYtNTMTlQgubvq/g2g8hJMp78AKAaNRdI4MSscQDjfyt/NBSQo\n+Wjc\r\n=IlKH\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"2e5956a2da7e264979c4c291957316415b6df6be","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.3","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.24","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.23","@balena/jellyfish-logger":"0.0.133","@balena/jellyfish-metrics":"0.0.154","@balena/jellyfish-environment":"^2.2.47"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.7","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.45","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.93_1604649980661_0.3261738059659789","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.94-renovate-non-major-383640aa912406896884530f123f75ecdb7ab99b":{"name":"@balena/jellyfish-core","version":"0.1.94-renovate-non-major-383640aa912406896884530f123f75ecdb7ab99b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.94-renovate-non-major-383640aa912406896884530f123f75ecdb7ab99b","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9833ace27bb57349c2b8549e3f467c902e8927b2","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.94-renovate-non-major-383640aa912406896884530f123f75ecdb7ab99b.tgz","fileCount":94,"integrity":"sha512-6Vj3tOBVwJ6Jr/aQOyBHDOmD04WbK1UbwC7xKfomPr7IeNABfY4p4hRtRNiUnjaCslml/61TgV+dF1Q0fB6Adw==","signatures":[{"sig":"MEYCIQCieqgulcO/icQepZxZVKOxO4YL2q3e9OY5JIo9Iz7iIwIhAPq4bHNQmMe/mXZoaJjBvGnQb0/JWcGrEJww3Cb9WdkR","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":570206,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfpRivCRA9TVsSAnZWagAAerUQAJOMkVAUWVN5a3IfqvJP\nz9thpQ/n9hcYuMSUwxz6TY0YtZZLvqE0jIXhOQMZxzCn3KiHvbwsy/zE/QkE\nF5pUqe+W0eKv9XHnFrNXsgIrcxr4demxqzjmHbaxC7xW63IdvpFIC6hFtMJl\nCX/BM5b/51o43IZm1KESorO/97SUgGsJB5LW39hbVOLE8cxl32yIaiC2yrNB\nhegdu0SUvArRpkajrOIi9LQR5/o1bNKRdr7mhz41MSRDsS8x4duyTVEsB9AV\nV2p2PrhbgHvYC5z8U9xSWJOdS6hXb1x2JYFfFLE5oLAW/rVoWvnJ4BefITH8\nxyi3l+wL6Rf9RUbdiweJW5TRko/TA4LQU0xE4ZDaTVwGCkpqw/kZV9Rns8ep\n2dO1C33ejSdvMdetfFKGpRPxoHUIfgZ/6pBfJAuy4s/bvlIVMPdW3/u5GpTY\n57VH8b4W0eOqRuhdsudhTu8nheTdnNr8PdvxIqwPT6HUsKCyqKyKB+LrLS0F\nzXG5WreeIMg/WJ9LZLlMhtGaJBlrMw8AJZT5YkxJWer/qY3Yzeu9/tY9IIRI\nFy8p1qQTTAQjZSRLLHy755vDB5uyRzhjKjGU2/s6TpVJlXTwqYHz7f25ubbO\nkBvOglcROAL8z3MsMYSuigE1+Vl1Fai6QOY1YA+MJqIjeU0odetaHjPNCwPG\noDH2\r\n=a9Gq\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"383640aa912406896884530f123f75ecdb7ab99b","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.3","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.24","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.23","@balena/jellyfish-logger":"0.0.133","@balena/jellyfish-metrics":"0.0.155","@balena/jellyfish-environment":"^2.2.47"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.7","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.45","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.94-renovate-non-major-383640aa912406896884530f123f75ecdb7ab99b_1604655279265_0.7198185730177424","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.94":{"name":"@balena/jellyfish-core","version":"0.1.94","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.94","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"10f33bfb0abb75e07700730ef1d523a99dc8ea8d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.94.tgz","fileCount":94,"integrity":"sha512-b+C/CQvsK2l5om+1ejvvR8q4R7IAMc8e0YBVumDShit29UFivoqu8AUc0IczlQlZuDXACI40a3uMUEKR859lQg==","signatures":[{"sig":"MEQCIDlGR+Jn4GBS/xwN9oFVzgE6Pz38PNWRAAmgR1NNjHHrAiBfQioYgp5Q/8Kj4dho9M3K9jVtndOK1nS8l1pfrd18vQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":570146,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfpRoSCRA9TVsSAnZWagAAx0AP/0wAh8d/f9hiNi8NApbD\nVNESXslRddezLnNS5kY9PD1eOQaCCZvkSinqz8a/g/TcPPbPLVz0MNJVVgaA\nlWPadofhIp7iNQJrt13x9Xl2sjWU2jeyzmNWvDnO54e0ZCKfgbgrvfBiG3Dm\nS0Ej2+GOEPJNsczVs5SUrn+gKtxoGBeHd2lqac6zmdU99TimQ4qevG7Knp+V\nx86wxxyksTsd2VJs5yguDXxEW8/fwxnRDw/udSFU9PEU38D7rEU90VkX0kfO\niQfE7xqWF7kJYmDlAjQOUM/Br7dxDbxYqAAtJ46ZGruX0I5RLnYTl8ovvIHe\nmNmFeTivKJJw49sm0Pr6NxAASTTSbXMnVsVx+Wb5jufkipcHurLrtiyj17as\nSDxG0XJBUxA05r+5a7Y35iJFfCMyEGo1BnVto78p3yeAFk4cspTd0MZIiSxk\nNOETZgtVYGPeKHOZTn027zB/LAWbN9FH2btozIweVx7DBCKEoF3aYj9+Z3I3\nRJIAVflDRW9kgbm+xDJKUZ6MtsmDjzNaLZLjm46XY7lFLMOO4TmROjW0xq6h\nO3UOg3E7t8bBVnJVJ7Ih3NKSZ7lInPskiRkzU5dhf/zNWMg7aK7EaPER+Gwy\nZh3CXNiAn/9LsyU8mNSTDkC941Lt9+IIY9O1jPXuCkI7fjTX2cWtdjoE3DF7\nMCij\r\n=c+Ez\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"61ddb69fde134bfcbb336d448c64641a05e5a49b","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.3","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.24","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.23","@balena/jellyfish-logger":"0.0.133","@balena/jellyfish-metrics":"0.0.155","@balena/jellyfish-environment":"^2.2.47"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.7","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.45","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.94_1604655634138_0.13868158155944732","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.95-add-to-and-from-slug-to-link-card-dcabfef5c1dadb3343cd61fccce89a2b9d814d46":{"name":"@balena/jellyfish-core","version":"0.1.95-add-to-and-from-slug-to-link-card-dcabfef5c1dadb3343cd61fccce89a2b9d814d46","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.95-add-to-and-from-slug-to-link-card-dcabfef5c1dadb3343cd61fccce89a2b9d814d46","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9150a1caeb6871e71c4d17726fa960282ab94b6f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.95-add-to-and-from-slug-to-link-card-dcabfef5c1dadb3343cd61fccce89a2b9d814d46.tgz","fileCount":94,"integrity":"sha512-O2/sq5bBNsE3+LSPk7uRNm/Uox3rs2eHzI6lRst5Y7PsQ5i2zx2Fyjt4OlegJhrjAlbFX6gBQkdttqJQKKm51Q==","signatures":[{"sig":"MEQCIF/owX7jmIa7Ape7sbE6DmFEwi0qQQ5OqlR+U9dSZ19dAiAGfNUw/mOvBn9Mlro3yD/KNi/UfDWobJwAXKhOyUCQ9A==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":570420,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfpYzlCRA9TVsSAnZWagAAAPYQAI0mZLMB0vr5160Vcm0T\nTP1piqfReMZ4zqORrqJpcm5FnDTiyEN7yu/ScoizJwXjNzoTGqr3KEljqlTN\nWONJIANQ2Vf6YKJOxmrnESs7/blcAoVeEUKRUWRMGu9/nSWLm+wRMJpYnvSh\n398EsIi8lztFch8yeDrtAWuDSSFBTUzZsoDsCJloU0nqfFxUm/Ty6O4cvAX1\nGxS5Luw+6u6wg+zQsQhLdtSMT021mjLafcT0746713MY7Bsuwz8oOLqqfftV\nueM5PLaDBow+AFx73DF62HqYe1Ygaq7foFPJ+n7kVt2+1M+tNDEvWyu6ClF+\nYmNxY+qZQeHqsvAI50NyIHqBmMUVh/WEtnoQrKIp/yBYDDxvJha3LdBWfVHs\nodhTqgJFhq9mI2ETDGj3505J/CTNFmYN5aZ8y9o+uKnySr2kLrLzgDVSJWVE\n1wlr0rUrUzynCQN9nQcnZ/RAVD1idX/R9bFmKDB7iLUqcvNXUgaPlpxf7wgj\nfm5zn1wQfkH9ndtPkL2owaBOAoUJNz0fb6a9jXjDFC5oqUXobdePmeJVnRHS\nlS0ZwjCZDgViwBRwR2P6/kbj0BtvtRM30s1GLGQTJEOMqdYRTPzIFditWUb4\nIO8AZFwZeihv8KNmtBR0uJp3Iv9zTFAFMRumtA6z/4hFSKU2NK1BUbfXH3P3\noNSO\r\n=DyZB\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"dcabfef5c1dadb3343cd61fccce89a2b9d814d46","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.3","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.24","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.23","@balena/jellyfish-logger":"0.0.133","@balena/jellyfish-metrics":"0.0.155","@balena/jellyfish-environment":"^2.2.47"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.7","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.45","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.95-add-to-and-from-slug-to-link-card-dcabfef5c1dadb3343cd61fccce89a2b9d814d46_1604685029028_0.5078383297671087","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.95":{"name":"@balena/jellyfish-core","version":"0.1.95","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.95","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1d53facaf04eb0c99b9e45608cc3ad53dbd5a7ce","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.95.tgz","fileCount":94,"integrity":"sha512-DnDBRGvD805Sjo30l/PgVncWcsM5jBEIP9eN4JvVwiHVKXQoGpo/VQ/DSDFe8sXpb1/feQaYqAsvKK/Y9oNqhg==","signatures":[{"sig":"MEQCIFlg0ZWAwEtboJU6UlJMs8n5P2pWz0YVJhLfpL4upIwSAiBpBagJHyOdAIeOraIUJl+hKedKk4GY1F6mjpsffj/MBQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":570345,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfpY9BCRA9TVsSAnZWagAAVYAQAI0I6q2bTPj3Ql82XbJR\nziRdkv+lJ2j3ASNN+2ZvyNhXPNc8p75LsbzSkhYJ3w4dG1ORhDSYee417yR2\nGTHmbq8AeF7kCsbtTKMTnp6LnonZCRmWMPEJngZMdFYunvbkN6WBGkvCDPnA\nVZ4c7HbjRURJBMpmNPMNmPhP/wN+QZpn0qiYjxucVSvrJ4hJPkKRAwO+Neou\nI7D68HteMqEWjjbDNsHw0EFAZgyJcuse4x6NKqZaPcdkM9P3XiXBe3vs4DN2\nsNHSq+tuJ/fj6Kp1nFxc4HkaE+gR4+9QUf6934WiBSSu6JFdGVCGYqQhfZkM\nLtVO5sBpL5WXMz2i4iW+Sc+uh4JaqpsyoUx4PoDQ92N+NwcCzn9QdaaZxy1V\nze1Ox1MDBsU0rc5a6M+25bQIFZwvbuJ9Ua/FiVTcSvkvyOSZ8HH6wq9FL2We\nyeFD8Q/oo0ffVbDBbN6k5MGyYEFfmCSCTjQx8fJrH0cVnho7t7rSkKS8l5wo\n+2hzZMK1LL/7h2IeROpMRmhI92f67yGE5lwPzFpAMCNRI3PtFxZMoEserjhJ\nqtUCSqWf4ulCCli7SvAYs5/2k8JvGvpx0vYWcMUtizIwJvB4c7N78hXnPEyC\ngEZ4SUY0VD1TuFiin7DdCforEW6WAARU/icM9FdAW17V8aLnfBhw/j8QXWwD\n/Ipv\r\n=YKks\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"408b3c64745bd0f58abdf5f308443b6f2f97b402","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.3","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.24","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.23","@balena/jellyfish-logger":"0.0.133","@balena/jellyfish-metrics":"0.0.155","@balena/jellyfish-environment":"^2.2.47"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.12.1","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.7","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.45","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.95_1604685632624_0.454560468434936","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.96-renovate-non-major-ade9abac2b2e91d9d544340c4ac82fad198c5a22":{"name":"@balena/jellyfish-core","version":"0.1.96-renovate-non-major-ade9abac2b2e91d9d544340c4ac82fad198c5a22","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.96-renovate-non-major-ade9abac2b2e91d9d544340c4ac82fad198c5a22","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0e0bf6b067bb04b8526fb4300575dcba385274ac","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.96-renovate-non-major-ade9abac2b2e91d9d544340c4ac82fad198c5a22.tgz","fileCount":94,"integrity":"sha512-QOu9c5wQAHmUoQh4JxnIEOm5pX+y3Km9AN3HhtTNIwvHr0go3501ZSyGwmP1JK8Rn3zOChSLQAPFB1Xca4G+lg==","signatures":[{"sig":"MEYCIQCfv3x9Dzg/DLkMLHrJtEKnZURRsIvZLVTa5x1+wY68nwIhAIDYWQi61SVHe8ZOjJ+me5HNV9r8GfJPIOUmU12dGcky","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":570471,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfpgvcCRA9TVsSAnZWagAAYEwP/1W4BrSpbsHpdPHlCU2x\nbQXtp9FZjNHCk8XgaacuLuxQ+HvL2D04g1fg9mUsoBKDS7x+wFaY/QrRll47\n/BDCjZ7L+CKLeQ5D7nmNGnE8blb80h7f/moTEtIJEZ3LA0RoeM7Gy2km9bWQ\n3AmMy9ZvV4Gu8Yg7IdziQEH7QW6H3I2QAXW/RWaeagxTka0i2iTHHRXC8G9j\nvANZg/8vEJtpqfljq1FCKjTDUEjM2iP0DuztphL54Bvtaj4pV15USTIWBjhI\n77wW8+POia585+57Eaq+ZT0VtrlAhECyc04Ove1/ORrt2dUQP3HkCpKHt1Q8\n+Au9mxcTlVydiRTS/61PPdxp1OsMXJCTek29mZeyHjyToM+DWvbusWBMN1sh\nEt+qC6CD+NDTOuWr7vl3bWN2a0LbYThCmuZ/mT6o51vqqT0XJFg9kxzHErkM\nQc3NbL7Te1/Ko4FhVaSh2M5qREGhpJtnTk4VWNjvD6qyzaXJMejpRuqUolKu\nwGT205gq4WxwCDJoxxbw2vD2ItOmzQqH10hiqyontCYK8ysed6T+Ba7Rhtao\nkLkkX4t3uKupfLNow7JSYNwJSw8niZ5liUc1iA0JiSODgzfk0gdwmFctQwP6\nLrPRCTYBS45ALEDl/A+w64Ed4dzLL0/DhT14szGHUB9dVyq43kfb4U8H3pNI\naqyQ\r\n=77Jg\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"ade9abac2b2e91d9d544340c4ac82fad198c5a22","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.3","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.24","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.23","@balena/jellyfish-logger":"0.0.133","@balena/jellyfish-metrics":"0.0.155","@balena/jellyfish-environment":"^2.2.48"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.13.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.7","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.46","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.96-renovate-non-major-ade9abac2b2e91d9d544340c4ac82fad198c5a22_1604717532046_0.6608701376854287","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.96":{"name":"@balena/jellyfish-core","version":"0.1.96","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.96","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9d70dd9725cadd056923b94d8c5efe54ff738248","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.96.tgz","fileCount":94,"integrity":"sha512-VJuVYCkxlSapjV0qo90XXNvpRsIPJ5rcUhM1dFNZxMWyoGRANLL1XSvOD7h2X3nON/1V/Vg1vCaORs/mRtMjpA==","signatures":[{"sig":"MEYCIQDsBXXgH5v7HH532KW+K4cugBnx8fhgyTXSrITV0qTm5QIhAO4TscszSuoKBKPlioyVN7jEwXah9va7/fUb+1Tk/J9k","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":570411,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfpgzqCRA9TVsSAnZWagAAXqsP/1YcjenrLMCMOj25f/kk\nmd/LXqpL+oc8nYiwdRjCzq+Np74tE2kXoIrAhUv8mPUnSCdrqg442d+tMEBY\nq/2QfEr2nXNFD4Gyb5XHBwiJGv89sv+24unhfjZWjh7ZWjZqZrOPGNEtc00F\nx3bpgcRJ4UHrp43XKZYPa06UOsV4Yg1hDpeHv0RtQIZOqBhio6QVuONjebRq\no2AfCItO4K0LnT7nfEH6n4jdibsZI3k2k+C3EXQBY32zwhHEjvQcKui5UeoR\nL9W6UEEzl1hpkNlw/a/5qwrPdRANIX6pehNyVgrATrH1DgMFCjYf6g5NumXK\nT9bJw5uAxjxzr6N+Rrpmu2BIYjlCtqLNUGENvpmgClpCU6cIJ5+3nlZ1tq8q\nnKy47wpGPPmF1QwjpnCYDd2Y0CNqYWSqKvf30vouxTmW2tXeVw505JfAtTP9\noLmbarVhLPrusvVkHgloFVwWTkT1qGrvhLOM3N5hzx+mQiVaMvsPTIXd2UBi\nt/DueQhy8MMP3+jZgC2BgkRXrfuTb8AkaDYL7qQEgBHbLB8/0+SrIMnu4D9p\nKnpY9Z4R1SyQfXdvANXyWD6BOP3CYBghyj0kCzgJnE5OC6FotV3uUXwu6C0p\nWLfE8e0BRyrCSZjdj02wJiBwkfccxX/1uBIEd1pXm30MZ1pDym1RX1LFSWps\ndBFA\r\n=Wk4B\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"34ef0b61f97f882d47fc6d9f724a7056558b97fe","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.3","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.24","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.23","@balena/jellyfish-logger":"0.0.133","@balena/jellyfish-metrics":"0.0.155","@balena/jellyfish-environment":"^2.2.48"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.13.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.7","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.46","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.96_1604717801585_0.4190668603267027","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.97-renovate-non-major-464652bbb74a1f582dda649bc415749a9989f297":{"name":"@balena/jellyfish-core","version":"0.1.97-renovate-non-major-464652bbb74a1f582dda649bc415749a9989f297","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.97-renovate-non-major-464652bbb74a1f582dda649bc415749a9989f297","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ff072b4c17d1079a564149f46c9b75c1eba745cd","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.97-renovate-non-major-464652bbb74a1f582dda649bc415749a9989f297.tgz","fileCount":94,"integrity":"sha512-r6JmiMpkQZt7AO0Pw6tMnKrKsH0xCGWW903pAjhTlg9MmovDn2NTJWR/dTdbXd17QR3m+WICk+Qoxd227WOK9Q==","signatures":[{"sig":"MEYCIQCbN0bG9+UAHio8mYPL4lGbKidRaHvhzgZ6aoAoVgqVygIhAPKfkyCz2ZdFkT5eciKHPRQNX7DIwI1cbrRhhZDYg29T","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":570575,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfphgkCRA9TVsSAnZWagAAcosP/2JDxxarfTOpSMnUcnNQ\nb034SJi2WGVjE+0QsxrgvU4CkGuLjCJE98wfaCinnRkcB2EA4ECu5GMr/BO/\n4vD8jygdlwFEsYPdrbl5UKfRn170KMNG24PUiY7pX7OVo+YPsdP02uawo65u\nCOD0kx/pcnXqEY9LS1TMrITn9aRv+35hkBDN0Y6qbOUxLyzji2oVMTGhyrLl\nJEaqLsV9gtaqGbGP3TOQtkX/WJ1o14M6r+2kefiJg3lIcKe6AwMNdoyZMye+\nBXMA3kXz5oSLm9z8/Xh/qg+VbvHRgtrEVCo8OWzMk+19YK0cHeaOWpceaZnr\nk85W+J+SUDWCezdkJPuDypimkqv4HMRtudQlXme24uwotlqhuCZbitQRF/Ix\nLaDFb/YJcR3RcRHvLTIepy1r3VCdVN5GZfHBwMvKOy3spTsayLxmq3b73F0+\n/UVYpPBMBdjzIns+j749TlQJKGUA4IvEailPh0GZtSUsU3B+wpVpEe2WcozY\nsWc+5GX54TFOIvlGHqxbmp3Rh4IHCh3jr4Nj9oXYY6nnZwGFlA6ImpqaulxI\noLxllIOMPfAqr/CbIkDufSAeC1ljOA0WiLQS4xaAGUMv84bB1mVcJJlMiFtn\nql+/MJ7KkXwFlK4UKYm4MIkwxynFvw7zaDaMw9sfi+h6TMbffL9iGO48sEAR\nFyn9\r\n=Oawu\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"464652bbb74a1f582dda649bc415749a9989f297","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.3","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.24","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.23","@balena/jellyfish-logger":"0.0.134","@balena/jellyfish-metrics":"0.0.155","@balena/jellyfish-environment":"^2.2.48"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.13.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.7","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.46","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.97-renovate-non-major-464652bbb74a1f582dda649bc415749a9989f297_1604720675817_0.32528228267580483","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.97":{"name":"@balena/jellyfish-core","version":"0.1.97","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.97","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5eca75624bce5ea9d074571d0e15916c65263483","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.97.tgz","fileCount":94,"integrity":"sha512-YhucXKjxBqCx9JRv98ZlHQjnvBDWlFnzLeOuJxegO68Rctk3YNJ1lXX6MzDlTgDG1WtoM1ugVIdmAobPCrqnUg==","signatures":[{"sig":"MEQCIA0y2bMcz8fcBakWhq4994KqTiwIfWa8oxkmnUn75123AiAcK49Ynpk7s/aZhfeFMWu1fl18y3TgKPU50Kra5hhGaA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":570515,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfphkdCRA9TVsSAnZWagAAnv4QAJLrML3I/e3jUSQwHu3C\nADCRR/AsWZ4gjlVckyWDYAVtZKr1+zyaWRV7dPSyDkTgtWCq+mSVDu3QArJo\nvvflivxMLBO664VqGFFMQIo8QuARN0Xr8ByfiDjBhyscEiakkf3vY0YUnHSj\nSIWvj1PZNYHdswwts9DNNTI4rWcQqlbrvC1zW1GzWTH45tjTA9ZoJgbTtkOP\nKRvVdn37/9wzf4lO1J3E51YkwB8riJnJ0Q6SXZ50dkcogk4NfUsxRpTn6Sd9\nirW9XqcA+WIh0Ln2CdgBYmJ+CZASUGYk7F/m6GerjbqgL1yGgLw7qxTz7Lvd\nRg7hi48PioWdy/wryACawJnvuxjqEzb1Eyzm/e1v319W7sFh/2Xt+nzk5HUy\na51UXWcKpC/V9eSkUTnzzd7UvSpvbrtgM5LLoaV+1hkfhWW4wzWhPJGicVXp\nTuwlGXWd41kzvLgB+Tqkd/V97PjS31kzRXjahmIiboknRkAfUdAG7PUW+Iyl\n8dHzorVy0a0p235c3NAWKpM7cWsDLVRSHUcyofTfsZE+hwLq0NvlfZOjCT+r\nQTAcNnGJ8bb1/5Xpn8wTbTgm18qAzxFHfom6SasV10kUZlwhpZi5PYDSvsAa\nDE2i0401va4O1ZFfl/azG1F4rBgk56aQ4kN5A9dfLl5wGET5wKrrHEgQ6d3c\nL3BS\r\n=23LR\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"aafb5e74cb7a2dade5c86f0b2110922733d1fc90","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.3","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.24","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.23","@balena/jellyfish-logger":"0.0.134","@balena/jellyfish-metrics":"0.0.155","@balena/jellyfish-environment":"^2.2.48"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.13.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.7","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.46","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.97_1604720925391_0.9627987465521011","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.98-renovate-non-major-57f27ccab4a80c78eeb2eb4440816c3a89f3abc6":{"name":"@balena/jellyfish-core","version":"0.1.98-renovate-non-major-57f27ccab4a80c78eeb2eb4440816c3a89f3abc6","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.98-renovate-non-major-57f27ccab4a80c78eeb2eb4440816c3a89f3abc6","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"186f21964c157e1dcdc513f9921687944a70b910","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.98-renovate-non-major-57f27ccab4a80c78eeb2eb4440816c3a89f3abc6.tgz","fileCount":94,"integrity":"sha512-LeVrvwJWYls0oJq+vlEpwBYLq4a6zlOXmCsQrW0vUFDyRiOlpR7euZc4s31TG+xMZwfCYMDnBnz1LltBacUmzg==","signatures":[{"sig":"MEUCIBAtyE04XiLC+oS2txY3jYdZqIj1O95jjjKMbTLsBh1dAiEAgPCboMThl7rgaZBGUwIaER43qhHHfVn/uLMp3EfSRCI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":570680,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfpiRhCRA9TVsSAnZWagAAibQP/iibpHWCerwBgGFEvY0l\nLFGbYXZlO4nqq1xyGzmSkU9xaWnNvPgXhxULFQzH1END7BJ7qURb144YxRGr\nv+taEiQfDqwvH9WYgxdkC8yrIX1KKW1yV1lcHFXUc/KE/Ts45gmKx5r8UKWi\n+PICYw7CW08eTxwA4xFHd5YsmUrSQ9J7yDHyD82k3MIZ3TWIfhkNyV2gUTh1\ncyM1WXHermDTYp4Wn3AjAUFIyQq3FSaKjTkNgsYZr8qLr5lnyiydlJWl6c7G\n5DqwexnZH8MrNbKz4eJNzjHS5SG5rRq+ZGfaVr0Yg6O86sJZDn3DACcca2zA\n8+9fg3lcKTpS+seGnaVryaoPN42yMbG/HgSyoXuOBjtxWf2+AYLaDTQf9TyG\nl7N23T0PkMsvHF3kO+r2ZCQ0q1eQHCzrbwFGa/mRquwJKb5X/q/xNsbjnFN0\nggbC2qrcudcRPDnqUBhwSk758+F4ARe7iuFEtlNEyWvbUFPaYGeZScmVHqIB\nWEpnXG1QMx/qD2t1s8vsEuyaXI6NXY8AsXuEqKKWp1nVc7aQLzCBIenpC29o\n4G4PYBVZSnOdNel6gpzaqWmV2QeeR/1DfIoSxYswtu6E/CY+whkjspMx3SJn\n7wAuNESiA+rK27/ijinzTD6CfOowTK5nJLvm2lje//KbCVe+l+2cIK6ZBMwZ\nfBPT\r\n=noJH\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"57f27ccab4a80c78eeb2eb4440816c3a89f3abc6","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.3","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.24","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.23","@balena/jellyfish-logger":"0.0.134","@balena/jellyfish-metrics":"0.0.156","@balena/jellyfish-environment":"^2.2.48"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.13.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.7","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.46","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.98-renovate-non-major-57f27ccab4a80c78eeb2eb4440816c3a89f3abc6_1604723809089_0.7305656161230727","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.98":{"name":"@balena/jellyfish-core","version":"0.1.98","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.98","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"petrosagg","email":"petrosagg@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9cc0c6640011decf125680704cbc2225ba66d919","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.98.tgz","fileCount":94,"integrity":"sha512-Gy/GYedfZUp8111R64b32/tL1SQn/HSUK0YuAW1PGmQ0qo+HJb//2wF2Trklgc7wq2mV54fDWSE1BMHFyOgMtA==","signatures":[{"sig":"MEYCIQDghAT8xOKzs7jAtI466aeQp0ZvIbFIERt7VVc3KACEeQIhAL6i0FkPwYfQqbYqspOIekoci2F/1cEmRXmb3H1hyqfc","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":570620,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfpiU/CRA9TVsSAnZWagAAnl4P/jUHWhT8N2khGK/4109O\n9M6UHDPfk1dk2GibkxX8kerI4mWkW0jq8+Uc6cIxhBcuSk236eHjVIt+NrCY\nutJtsfWXryx53DZA5rSjI/WVpRdx1lPY1PjJBCMxi5XCHblUksYXhPTdDgAp\nCc/i2q3r+w1O11sWuMlr9Pvcivjj9xxid8T3rYFbG+y9MMBFPlO+rK68KKsG\n8zQHDJ12bUozM7mapJMjTmsoSawzQ57lKPKQ0NTcLlknLscO++u2LSGcRQmv\nl0VFRgROQ+unrFPzVzoBYe+v34Q9Mc1DIY4qO21pPiigh/7nV9TN/aW8aBsa\n0jjjWOFxBIpB3fx7FQH31DaRIMK7y3waeIcLCRkf1RmZCN5O6J9ZFC6L/3D/\nj7Cb2GJ6nsNTzmkXYqe0Ee9sdllPk5+B09Cz2gIn0uLiRKuO+orUoza2ps1n\nqdAUmqZ/zyZeX5YTFCteJpAmXhBH3nL/Qwq7Wo+RsgA32p7D7+weuDE2whPQ\nUMgWLtFjtVezsnsBqKJhEVELpHcY0I47UkcHsUxmb1TgCDq2Fm5Pxjpq0+c8\nBgtpqQ9+5IzJ7l3ysG+F1/MXTv82bEekINxM0lRn7XEL1y/kjU4HLrwb4W3b\n5h26zZOkGISn/IX851cevbT6qT5TJXQcrxL4bnSQRROZHAj9iD1EYSFWEVQT\nuE08\r\n=ktCH\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"29a470f5d853915f5e2fb485b4848b8e75c3ed2e","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.3","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.24","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.23","@balena/jellyfish-logger":"0.0.134","@balena/jellyfish-metrics":"0.0.156","@balena/jellyfish-environment":"^2.2.48"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.13.0","deplint":"^1.1.2","depcheck":"^1.2.0","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.7","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.46","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.98_1604724030911_0.5266949536235501","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.99-renovate-non-major-52e6b9c509ff9725abc0bcf176cb5b9048d4da60":{"name":"@balena/jellyfish-core","version":"0.1.99-renovate-non-major-52e6b9c509ff9725abc0bcf176cb5b9048d4da60","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.99-renovate-non-major-52e6b9c509ff9725abc0bcf176cb5b9048d4da60","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e65bbe4f87bfad489a0afb5d84262d88a08b5f6d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.99-renovate-non-major-52e6b9c509ff9725abc0bcf176cb5b9048d4da60.tgz","fileCount":94,"integrity":"sha512-muzWe5oVw8b95p2BT5x+ADl+eTUIWna7X/QKy+V0DqPTPMmKUk1qhhTzhfbQttcVku+cDfE0ryQ81WXZOpl9SA==","signatures":[{"sig":"MEUCIE1SeejYvWDY/UmtZ0cPMWixY8tAvKXHikfJmRjSB1+0AiEApBEBtDVA9B4USWL7IOlOSW9HHYxicXNIdd9v/qgI1Ls=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":570746,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfqXoCCRA9TVsSAnZWagAAiEUQAIpZSuhAafVUAnhpCPic\nOE17Ajsyu3hxesRz/PD+FP+CN1eftVUJUe3r3kTM1xYjETbFa7SHMOTRiCA6\nBJCCn3xp6ncId+RQNQ3pmco74bPa+CP+me86e3UwANFKEEQuqmjIx6zlq9X/\nRoMIkG0Jsah8dDWJsI5t3dr9dy7ALQW7sLSnlK3kKf4wQlDKFSI+qH+WJ0e6\n1ljmbLWerqnvMorA9Zs9ruh0nEYDF6Xb6mW+tng2jkj9c3vCAr+sqQbKwJFK\nqHQ1dmVd9VZD86yEMCB6YrF7TJHOy1dJUbus8LhiQuuF2b6FrvbcqWQaVyvH\nVoMr68C3GzLXr76OolXVsVfJkwQxvF1aoVrFkjy6xsSCW/DfiGYOaUPXvQ/0\nJ67AqIGxmzXYF/cQ+/dvG6ZVmM7LbdWJak8UvSA1s9fPPhQGm9uPuK5xaR33\nbQ0vztwewqJYPoLgaloL3ljpA9YxXxAYGm9ZaK+hBLW3luUwnaAH18fs4s5x\ncVk7gW7ltkUKPcKCZ1GOv2jOTiTlKiEiT/5b3lAbfG8ncPXdCv1eASPXFt5P\nVR/RhBbEs5Vo4TEl5ZfWWQ42ZtRnc5ayWyk3Q6AkpwBcb8QEhHapaajNMWo1\nYxcTIYtlq5MbPxh2nwy8sOnw5L7HyyeTl5enncnRYOAHXHED6AfwX1Hul0LS\nVUfO\r\n=xfj2\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"52e6b9c509ff9725abc0bcf176cb5b9048d4da60","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.3","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.24","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.23","@balena/jellyfish-logger":"0.0.134","@balena/jellyfish-metrics":"0.0.157","@balena/jellyfish-environment":"^2.2.48"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.13.0","deplint":"^1.1.2","depcheck":"^1.3.0","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.7","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.46","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.99-renovate-non-major-52e6b9c509ff9725abc0bcf176cb5b9048d4da60_1604942338222_0.4146367484551856","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.99":{"name":"@balena/jellyfish-core","version":"0.1.99","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.99","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"89517514c822e1748feb5380c111ea14643a2337","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.99.tgz","fileCount":94,"integrity":"sha512-GWrpRA0GK889MBcY2URI99hGdLubQVRtrdmRQ50m7qKbxA3NxDLDLotE72YUpuN2c1Jbpj/Q7a5cbfF/ScoLdA==","signatures":[{"sig":"MEQCIHE8kllZOAtlvUXr1BGjqlBN+sUrgMIhieXe/x06tiSQAiBl4yQ8dhXSytXVMBhJQeyuI3Ru+zb2L7yTUYtBZQl5xg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":570686,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfqXuYCRA9TVsSAnZWagAA/ywP/0Qj0z7CyTkJiwwUg0+C\nW6vjjp3lQhehTeCpXnFsn0oHf2Kc7veueWdD1UR77dAMBJ+Yts05nrkh5ck3\nz2URCREuHrlo5eC8+HNoFsbO0niO9Ot8aHjyG83sLdA5/BwaCItgK3W1tUok\nFTrEaXAP2kKST3INCFlWjRR1T2QRGDtb8e4+XX/b1arQgjqSAmkrbV542BFo\nYwLFQ4DA5LlCNBohsLbIAVWbJpzFQHtce21A0U3RT1b8DJXrgTIQnzTY8uMT\n1+EwgXGf8WQudqv5zhom7m4mfnky76G9XSNh3ukTSIxWXLvce6VhJFBFthBW\n5fmE3gJai3BqL9LrazWQDk3uTBraabRwuIyWiL9cOtxXjeH0UeYvJolKm6Um\nc3BG5tqul4u7orNAsvGkXy/fOBFB5qhJwkqo5tyzmS0OfTnAk4nhsVZVkY8b\nfUNfn3L9igBKACPv7zBvTYDWrxbYh0VmP627A57hx5bdt5HowyL74162zPit\nzzUFYL7AzOcfDkAPbbvRm3PNFog6fb5IbFVjFXW1mLl+trhmWYQvyZDq4CUo\nURKD8pehQcfrNiAMK/gEAnipoaIm3f8gyRqWCMow6uP+mFsS+z08gn8U/flI\n/vdRajdnlm6HriCTnTZmLsh3cdPpNrjVRqT4wURRhkeBKdtWR+zmUWNg2URF\nBgLc\r\n=37lO\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"42113a1e822aaeff70ed3ff1a0679ddbc8141257","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.3","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.24","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.23","@balena/jellyfish-logger":"0.0.134","@balena/jellyfish-metrics":"0.0.157","@balena/jellyfish-environment":"^2.2.48"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.13.0","deplint":"^1.1.2","depcheck":"^1.3.0","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.7","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.46","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.99_1604942744239_0.25700700417507405","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.100-renovate-non-major-f52c8153ff926745b35b01778f42d7551af39489":{"name":"@balena/jellyfish-core","version":"0.1.100-renovate-non-major-f52c8153ff926745b35b01778f42d7551af39489","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.100-renovate-non-major-f52c8153ff926745b35b01778f42d7551af39489","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ac35fe8a3e3069ff1dd51943847c6e7c852a0070","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.100-renovate-non-major-f52c8153ff926745b35b01778f42d7551af39489.tgz","fileCount":94,"integrity":"sha512-S2x0XNXC/1lpDn15YJQwwBIOZmQiZVJE7Uix2opH/Le5KovVfuuWneKhuP8firScDbPmReEgfXC9BgbbIyvFVw==","signatures":[{"sig":"MEQCIF5SJOi91mlY3cDjWFEM5+zmaXJm9SzyrjMbF/wyHjVrAiAWNIoiDSx3RG2VMogFVLgcL7z5Ej87zaOEhyXT9FmYng==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":570814,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfqeNzCRA9TVsSAnZWagAAjQ8P/2i7UNXVM6qdwLlE8ToL\nR4dlZuJhuYVUwr6eqqK/FA3gFML5HlXfLKNTasEoeiXyh0CgbB/ZT3+fm3pX\nJ1Uh4z2AeYjygiGwgsZEEI7qHwgzCy2xX8jnvxYjn8j8elnC1anCLogUW7UX\nCn+2rguS4nsxWa9SvkzIQ3eIGoHzB9oWTi2zr3KkOucIcyDImRAtA7URw9u7\n9XVcPEe4GPPQ7YCF4MP+CbMzY2X9UqkJzQKQHatB6mwPMC6kwwsu5yL78JEl\n3zgdWFqMfYz9XED74iHaTXFi/3ebDV/C14PS8xtGl2F3x0WHu0U+48b6M7+i\nryIyS0i4T/cDNjylO5rDGjVFgnH7IJo3F1lHV9LG2HyGhihpVhE89y6w7QMm\n2Ldt4SxmbbQWM7NzirF4AgH27XpNMlIZGL1bUtflfNFm7FYSBiYNUehIBSxH\nZV6VKEeF4l7xmLx+RCzMPcZn9b6KBvrmpW6ZZToclIFVH5NWrz2zZEKUjvcV\ndkak6NiZWPxUxq8r3Zsncr5+jqaLoAIdx0jWSeukEm1cGBiX7kXzvxTkADQj\nBGNae978ZCJX4tvL5DAVqo1e4KKgm71Uiippk73ibGiOCYfc5RZXDHoTQ0KA\nczBhz84vO6tKSzvH9MHsUg4Dd5S/cH2SNpNKVpCtqSpa/Gfc1H2Gmgak01Dk\nEGhk\r\n=OUyC\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"f52c8153ff926745b35b01778f42d7551af39489","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.3","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.24","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.23","@balena/jellyfish-logger":"0.0.134","@balena/jellyfish-metrics":"0.0.157","@balena/jellyfish-environment":"^2.2.49"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.13.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.7","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.47","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.100-renovate-non-major-f52c8153ff926745b35b01778f42d7551af39489_1604969331424_0.35883610255629006","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.100-renovate-non-major-b24f91e9e87169156d5a8d568e31ba02837681f8":{"name":"@balena/jellyfish-core","version":"0.1.100-renovate-non-major-b24f91e9e87169156d5a8d568e31ba02837681f8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.100-renovate-non-major-b24f91e9e87169156d5a8d568e31ba02837681f8","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9255b7d97a19cf9a7afa34d63b459b8edf1b1dd6","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.100-renovate-non-major-b24f91e9e87169156d5a8d568e31ba02837681f8.tgz","fileCount":94,"integrity":"sha512-+x45AqXGA/gk/DVDDgXXpAa+bapPN4wMpa3hlAWaAvjglvf60ygeEQKRyGI2iCdPPi0HJxqbsimp0MLfFtFHpw==","signatures":[{"sig":"MEQCIGtYdXlqHDLKk+WoCJYw9RCAWBxH/bEcjwyGYfwO5LZaAiACfOvIPqDcRupC2ffwfHfG0UYSAr32AA68SGswkV7qzg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":570814,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfqeTOCRA9TVsSAnZWagAAFeMP/1/dVLElAPmRdtGF7jWN\nOzPnAugMZXyoRlwL8T4ygXwe6HYeez1rStK+I0TKGxEnf8+R0+q6zTa9rGNF\nw1VcPYz3kkYrqll3bXCTvl0xQB4OqnHdkEr40NXP5WbIPYpPmYSO6htW17H2\nWRCIUg2g1G3HuhoSk0kuBOTyAh5UOY8aXEOazp+YGSqkfLr2j3/h0MgCQKeH\nza4PN5QCr4zz38OZijr1qZOU42lIo37I1K6g5b3PlK6f3Dl1pjsUIqMdhV1F\nII2gtnNxZS3g3EvjcYHiVzo4cOSoDtVCel+hW4ZHL/e+CwBFt+/gcNLfJ1oe\na/MiwlSSHs9prczbLb1cpeTPJRUFVUeo5DP1K8IioVbSPqk29c7UfcSbmGqE\nmgkr/7c1mUwlFYB41mf7CV55NwMhB1uG99Ewye46yYgkkNh2z38u0Bzjw2kv\nFvQskbCjSxWBTYV/CCdbVux7ZwGhdvxefejobXnbcDXdOIOzD0ktVXxQsDDy\nsiMnpUhWeyjdL4IuWg+bY/yoRTEB8WOIVVzxPE+8T09TL9zXdNwGeeZX3fbh\nhkfbFKQy7x/u/9GvAaORMysplIwQ6YI5PbP6LRa4zA+9Ok+kz2lcJISwDGgA\n9yCq3+XvvC9MgoiLU7oHHBGu8ZQHFIUNLCb7c03l3cdNxi02JPO6NOz6ObOy\nDwox\r\n=ostP\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"b24f91e9e87169156d5a8d568e31ba02837681f8","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.3","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.24","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.23","@balena/jellyfish-logger":"0.0.134","@balena/jellyfish-metrics":"0.0.158","@balena/jellyfish-environment":"^2.2.50"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.13.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.7","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.47","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.100-renovate-non-major-b24f91e9e87169156d5a8d568e31ba02837681f8_1604969678223_0.47420246151188716","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.100-renovate-non-major-e165b0839d453c1537c4917b2a6ee6034c7484e0":{"name":"@balena/jellyfish-core","version":"0.1.100-renovate-non-major-e165b0839d453c1537c4917b2a6ee6034c7484e0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.100-renovate-non-major-e165b0839d453c1537c4917b2a6ee6034c7484e0","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"80491f97ca525400f41658dd9dead1b6495f1111","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.100-renovate-non-major-e165b0839d453c1537c4917b2a6ee6034c7484e0.tgz","fileCount":94,"integrity":"sha512-Cj+IHX7p0PCRfdFquawklTiCyPovtuv6CFt4gYAbHecg1ax23Q4900w7CPIhJuA/eAPjAgM1rBshScgrPU3FAg==","signatures":[{"sig":"MEYCIQDiHZKxL7VYNfDfDdo2G7CNg2K+GpCQALPkkhCkHvwMzwIhAM2S56KkvhBYSpqDZDMuzdCvboVDOl5fZdecFFDWMYo1","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":570814,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfqeXeCRA9TVsSAnZWagAAcxQP/jyOvuyJ6NUoE7b7yT7i\nxRFKNYy3XcIF0fGZIO0ewaz73QWh6B7h05F1Bgg06a2D1AUbYWctQdClPgy+\nRY+IiolJkLPtoMha31drIz0UtPK+5z2EYUfVWEuLGZFR2yyOVuOsBuezUeTh\nkgM8Wc0PfOnvn5pQz9r0MtVe0SJ7jyAftEbsLm6Y6La55UKk8Ok/gO1sKvf/\n+Zr7Waj4VdPLE5z1qQBb2KVCiQNrmPPohwCYprR00fFx4uk9qi7/4VdorBjb\nYFQzGNTByh98Kui0QNkbYG+UNUugJh19UYshz8vqpUkdGp6F/uLEVIpCNlOH\na0RbJOd64VKx2nq8raQZC8F3Lv25tNPUFwTqbR57xbnkN4mptFMHRxW6WXgc\nWo3bAMLnm/lsl5Inb+AyAbz5btWgpFJlkepyQNahcNfFHR8bHUAzVva1yWRc\nI+lU2nliz90vfljb6fOUiVJ6ZcPKtjiEZOik7AAVT9djx/8rFJaXQgLr26+B\n6aOhK1+icwOkp6kJ6KTbL+vR3cDzkLqMc9lGCpCb9C0fo4w/8uahso24E/uM\nNgAuyWh+1ssJOZj5GBnuPcpBTvc3E1UlZj2jIqPnhBkGuV/cE+HYlI3/Xcdz\nnc8BwyzIIqaK77dhKQyWbmTZyP+r4X66okiUlQR2NHrOxf/tI0yAELPtH+xZ\nVMZ3\r\n=nwv9\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"e165b0839d453c1537c4917b2a6ee6034c7484e0","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.3","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.24","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.23","@balena/jellyfish-logger":"0.0.134","@balena/jellyfish-metrics":"0.0.158","@balena/jellyfish-environment":"^2.2.50"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.13.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.7","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.48","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.100-renovate-non-major-e165b0839d453c1537c4917b2a6ee6034c7484e0_1604969949845_0.8826608978543751","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.100":{"name":"@balena/jellyfish-core","version":"0.1.100","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.100","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"46d41dd66ed1c351c09c1f6625cbff57ff381726","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.100.tgz","fileCount":94,"integrity":"sha512-Ehj8jsUo8w/C38xLLGm3c+SI4uhyDWTx45+btkOOU8PXvV3ps9hUNBFJYj5V/ZQIRd21aLs1oAYNKI/hT9IJjw==","signatures":[{"sig":"MEQCIBKSqNgIHt0b3HeN/DHTHM5qOwWmvdHyNKm7tAzwEZY5AiB5EKUF8IXHtHoPhRWISMrZcgBlpeJL75AVxhlTeTT7/g==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":570754,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfqebzCRA9TVsSAnZWagAA4kUP/jICTTtuccLHf6tId+G/\neQ1XWvBzzVIN84iYAsx0gurO+vMRrI9Fxcs7Zg2I8vObLBFh6rnrh1Q2UO89\nd6GCIAaItXl/KyQYCm7yEcE5yDWFxyZOpKxbNJmC/jEMqKJuQgvuj3hUNtU7\n8FTyrJTLRprsl5it1IZuvulH9oRZvC1djMB2wybH0ulGC1Q3bXsddrlppj4U\nfFixzEGIHKq3OBTlYMoGbfapF+Z3HX0BEwO/Q26zRecDrajP008AXmAW+FnA\nTQ4ubMVKHJ61rB8ElOq/kUc7MdH1kYRTHLXZd34HRWaltlByvJ9XXN/NBRSC\n4f63xVDkU9FqfRHJpVh30mP00KMogF7GjQonifhPCr3818PhnxIAXPANeDMd\nBH96r8Tp3bupLKhNWDGSiWXolagOAbLlRRYB/7ZtM9yZLkhBYx8x0730XS7v\nvzb7gloUITZtj5b/FA4AixM3iKjTSnLerK5jGU8vXCN0j7cKLvOoiKJOA8dy\n62vaT5vJ7yS0EY90WXLWDqASl3smqlXRjv3+o7hYdtn8//xxGKwJBko8W/7d\npvgFhPa7s0kdg7fUcLCLgPAnExBiLuds10U3pC7J3yJ4twxw3A+zQuHJwhqX\nKJR+FixGOslkKzztX2mugcP8GgpphrdXzCPJ/0up9RYd0jf2xPmpCdZYLEND\nt1t+\r\n=JgK+\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"27c3bd79e009b6dcc993fd89083d2febc199b123","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.3","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.24","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.23","@balena/jellyfish-logger":"0.0.134","@balena/jellyfish-metrics":"0.0.158","@balena/jellyfish-environment":"^2.2.50"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.13.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.7","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.48","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.100_1604970227013_0.2748655898750687","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.101-renovate-non-major-d273d8586911171488cd362b617fe2f60c42b4cb":{"name":"@balena/jellyfish-core","version":"0.1.101-renovate-non-major-d273d8586911171488cd362b617fe2f60c42b4cb","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.101-renovate-non-major-d273d8586911171488cd362b617fe2f60c42b4cb","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"77ef62702f1bcea423a2426b059a794fb469ed19","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.101-renovate-non-major-d273d8586911171488cd362b617fe2f60c42b4cb.tgz","fileCount":94,"integrity":"sha512-13aocLAZ5EF7Dtr9A1jxXhlYFPrrx/TOMen80lTlKdfiOtaR05Lf5xVPUdKoIZz8gpUtW65El+cMkfbnGKNy9A==","signatures":[{"sig":"MEUCIE6Oi0zW1he0epUkk+QkdD8edOj2LN053Fnc2chVGGI4AiEAyaSuYW2OITjq8sM/w5fohJni/WH0skqckvcPa55yAT0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":570881,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfqfxFCRA9TVsSAnZWagAA/sYQAIgWvKIGybn4GfIyuH5w\nTiZpZxGTkiQ/i1SAlKXi64et+TcHI/SaaURFCQ9zhILB9jYpNvtAs0cQO2xo\n+aCacQeFHpg7pIPdUfZ4U+w+UdBLir1ayhKoTmH/1M2C99vKe40dJO7pH5lE\noIf59Fb4pujSYly3NLEjg/iImxQZLuLdMhECof+Y+Y/1OdunIRdO4baFBK2M\ndvV3XN7fKQsWSZJT0Wc2N5L9XRGzsBfJYTmdBT9TNlMzMBASehqT83V2BtFt\nCdvYSnXj6KAxH3N3rkge1UEOnsDjjLYH7WJ2dII6B76gmYGj/is2QCMXmZaq\n3fKeKgSYZVBfvRyDS60jHQSHN875lfziLpWg7nwtWV6MyAVwj2Tc9Vud3Vw+\ncd+HOaPpN0Bfv6XVh6V3vAuVJguSfNFZ9qn30LluIlf+Z6Pskyuj42RbU62t\nH+VsdOrv0vprRw+jrPu5QIEZgTSgC0QiSPwPXdqh5hX5VxzmgY0xkcGUHwke\nZgcFwxw06oU7vVywaqRyEde+v5hjrTBkZZcfiQan1lKO3ltwjSu0gnmzL10x\niynUiESMjBVRgmjYk/FbCAdkgK7G2FeBXGsLIkAqPj1dlpL1qvh7t2286Y6n\nziSxmp964zH/dLCO/oW4/dAmJIhtjhOCPfJDSMZ1C3oDzpvP2tiU9StyWq9Y\n5sO0\r\n=NUZA\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"d273d8586911171488cd362b617fe2f60c42b4cb","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.3","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.24","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.23","@balena/jellyfish-logger":"0.0.135","@balena/jellyfish-metrics":"0.0.159","@balena/jellyfish-environment":"^2.2.50"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.13.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.7","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.48","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.101-renovate-non-major-d273d8586911171488cd362b617fe2f60c42b4cb_1604975684926_0.4790320999924389","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.101":{"name":"@balena/jellyfish-core","version":"0.1.101","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.101","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9a253f0d76a42f81c998e0afe33f5336d02b34c0","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.101.tgz","fileCount":94,"integrity":"sha512-/NcC8FHQSvmJE2+5MNs6KQeL4R/TslBEkSRSqvFhG8kziELQTFm6hhX1PTQ32sfghVz1ehe6NGmvOSoAbON1Kw==","signatures":[{"sig":"MEUCIBEBZkRb9RvM+KpXotI3vofPvsQq6vjFTn3u+6aW5z8kAiEAuQ/8fdVE8ZDVjtbj7uyxvN7OjwTk6NmnAgTZjbBk7Is=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":570821,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfqf0+CRA9TVsSAnZWagAAynIP/3oJTkalgMgezTeXLleY\nJIGwV3S/i1GWLHXivDz1fkZJ9jTxh8eX1x5BJyEDaOWqlmsFaVqO/L3c4gt5\n9E65Q1u1JYKxi5w3bwsiopz2RzB70w8WkXOS3++xp4ME+ayT4Wn8dFZljq8a\nIyu5+yDUGVFiGPjxDzTFJfVNQj93V34RKwBEqa8uhf5/FUWRhqdQ9tnyY9KY\nFySLwZJ06BOT0BQ8Au/8051XmZ+xIol6NFo7ffvqj2MG3wQp2BCuerfjtYF0\nW/ElOu2Qq+Ho5DWPr1UIHOuVQG/lnnp3hBW4mN4dvw/h3QnYOrF2PtvS+EGv\n9DaEloEMUXZNvQ1Afxnftc1+NfovRZEEhGwaHD0UIAQIvErHhFmQXO+AOnnB\ntpxMIWEqdKrFbd+csHPmtY0mfS2P7usOONRKxECOFessHZWy/T3lWdHNBRml\nOnY/fJRmk6Y8s4fgjenMKZtIG8JNYmPxRPdwwTD/TcVNPBqR0Ua8zZw6SQ0A\nfMzF7I512r8RZ+w+7pzqHi1dM/SGvjHkZb9UeSPmCDWBuTY/dwz0dHoFYBEF\nX8e4AYYb59E6lt1iT0z4rtKZK84LfEF5ksefjUcXa86zj8awh6cE0rdiKeKi\ns+8rFoDF3OaT/OBXlcN8WvGnhlVb/Dl+pVD2+G1T7SaTcVdCV/pryuQbmzuq\n0bCT\r\n=4fZw\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"e20cfef5474a1db38e54765ceb33390fabf70f46","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.3","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.24","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.23","@balena/jellyfish-logger":"0.0.135","@balena/jellyfish-metrics":"0.0.159","@balena/jellyfish-environment":"^2.2.50"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.13.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.7","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.48","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.101_1604975933514_0.7399815421102625","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.102-renovate-non-major-4e0bfcbd733187690e78eb227a704d5530d2cad3":{"name":"@balena/jellyfish-core","version":"0.1.102-renovate-non-major-4e0bfcbd733187690e78eb227a704d5530d2cad3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.102-renovate-non-major-4e0bfcbd733187690e78eb227a704d5530d2cad3","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a9db4c14d6e4d837c53771c5847ffa1ea08c78ee","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.102-renovate-non-major-4e0bfcbd733187690e78eb227a704d5530d2cad3.tgz","fileCount":94,"integrity":"sha512-y92sNuBmMJ5yYWxCeKz3E1rkbZvEh9BtVekDOp92Ad5jNSR7r7I8PGhi36RSeBEFCW1U/Qf8pnQB5dzAADOrLQ==","signatures":[{"sig":"MEUCICrcaITWix/uDEK0lLJFWqkLgjKEjJ4Zfk9R6Z8siQqUAiEA46nGFnyKIPSNuqG4XlcA/Gg9cZ/90t9x9gPThfMAdLI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":570948,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfq5nhCRA9TVsSAnZWagAAtdQQAIY63jEWCf3A/eBH0C0M\nit1cLRh/w7j4CJecKhmtGrEdAJVxLrhmy20ILSE/SwpNoT5aoW6+96H8m2aW\nWct6g20tbpaGqKwjmdXcUTZedefe9d4LJyjrCNqs9hICs9VWYAAp//iYefXX\nhlZglUd5zy8/26oVSbd6mDOM3Fgg8NYScoOPc/8S9kRHwhwAuY0iHP/y2OvK\n0jKy8Qg2DX65enunJUywcUEao8H+FhkpWIW3Tw54OqcHZnrpvM1rwFCSUij1\nJaYY4XO6igjTRJMrxpRjpa+s2UnjN0NYRNietTVKZB058SqSnwkBNW80fg03\nvucSw5rBTgx75eGOgbBz4G/ETM0xt9+/oEUBjrYq3+WvY1RzOM4rFKomPsHt\ncMQFyVa3aIl89Ye2+zBq1NIoycDOJaBFO7Pz3skpYsgQZB/VvFX5jKj7kE+u\nbAcciVY7y8tTKmDsMdXv2edokg8fuIB4MwYEgaahASUGmbDaVtZU6XKuJdaT\njhLYY3C1Yqe9qJxOhbTqgTu6S/D67n26n7B9cMpYp+oK64MeHCPE3AGgYT+H\nZf2DjGrfOyxNgNZ+ok7uCtHZter4vCElgSyirzh0KnR3yihnNklVABNTwT7Q\nFmSk5eXfWbuP5klFFLiparFTncbe5KHUZh18TUTxh5/7qSY2hbN9dStw1SC5\niOW3\r\n=QZSJ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"4e0bfcbd733187690e78eb227a704d5530d2cad3","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.3","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.24","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.23","@balena/jellyfish-logger":"0.0.137","@balena/jellyfish-metrics":"0.0.160","@balena/jellyfish-environment":"^2.2.51"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.13.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.7","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.48","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.102-renovate-non-major-4e0bfcbd733187690e78eb227a704d5530d2cad3_1605081569157_0.48287361133913853","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.102":{"name":"@balena/jellyfish-core","version":"0.1.102","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.102","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5f4a3a0c73f29ed6eb8015b53a32fd98a31858fd","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.102.tgz","fileCount":94,"integrity":"sha512-4rNk+XQYPLt3ucsGgzlZQiMwnBh1ou0nnQ1fhuwieB4dlf4YhT0FkaTBf5tP69Se+IMgntsprHbKLAHd7sfbTA==","signatures":[{"sig":"MEQCIBL3MgJAhC5camu7d/z3MWuimOlTF4eggj+ymcckGksIAiBhdw2EqvvKmf8yjuaJVybo3lrzAMtLiOundNyf79RKOg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":570888,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfq5sVCRA9TVsSAnZWagAAxeMP/2yQiiCWYnIg3/YDXLfz\niKpU1n8VZk1JwNEBLQyVpPvlQl6/9d7g1jXcLD69S1/RpatTdQKp+CKgtq8U\n2qecI8+y1g9WEcTQgIMGDEYwpLINJoHXvLm/Vq1jqE0R/BNXIuTudAcWdtmR\nCw2O6JRgfn/PpnnK38XLOw8vqi6ggneZJYzjL8rK6lDghH+HpZO4YGY9zTOu\n3nrICIOokxvOYvelAwhk/qdsh1qfy53aQAov8lhTz1kbi0vbVwrLeAK0V0Xn\neJbhb4vhyr08V6OpzgOl2Mww4ODw1tRgpuEZrUi30XMfrDfHM59S07WCNkAQ\nN4oKKfccDUKMpWoJmAzLuAdXK7vso21tdKYx+g92p+W6Z0AodqkUJGRmb7Yy\nIbQAfmGw5+SKP5bKyXwzGa+5NXtkiAn15r6SVD8JxxCWzCEA7RBIEERlzhK6\nOWeBsu6bwdpBnQg1iVoE86e367U7u/9EOd7yewLtl+H8T2UdFw6lkbqWKAN3\nI7XFqP3ozjoLrRbMC5w3o0HXTSlm+awPBDbFl6nOREn827+TQ9A2OXKMnRiL\nrus39yUz+jkXOaZP3trj3CBfjS4NZJHFXe7aZOb4KhkllFpYILuJ6O6qKMMH\nUIlsOvHOC3tk0in7bgwU1tXcR1Prx1nkTaJhQ/77aQjoFXdAXTinru9lUjSr\nwVZk\r\n=lefb\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"8365bafe7ac24b050243c2ec06d878707378866d","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.3","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.24","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.23","@balena/jellyfish-logger":"0.0.137","@balena/jellyfish-metrics":"0.0.160","@balena/jellyfish-environment":"^2.2.51"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.13.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.7","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.48","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.102_1605081876742_0.6822153902199055","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.103-ci-use-node-14-83cb196da00f385ab147fd969c2fce362048f92c":{"name":"@balena/jellyfish-core","version":"0.1.103-ci-use-node-14-83cb196da00f385ab147fd969c2fce362048f92c","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.103-ci-use-node-14-83cb196da00f385ab147fd969c2fce362048f92c","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"afd3fcd6d9924d06cd53d2c2a21b821c5478af2d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.103-ci-use-node-14-83cb196da00f385ab147fd969c2fce362048f92c.tgz","fileCount":94,"integrity":"sha512-FHFTCmSrO/F0lp5CTlXfC2ty4OpB6fSVCtgX2dl15lXZyczZ7KMknpeRqr/0RDDUCuyxfdYbXtvbzx20PNA3SQ==","signatures":[{"sig":"MEQCIBLrs+9u/mEpsZbN3esAzZWbkb0GfToqVJSkzDOsAz4uAiAEmhXS4O/F3e4LlLwLMSIPjfHZD1pRL23N9hsm1VYOUw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":571016,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfq6eJCRA9TVsSAnZWagAAp9sP+QC6vu+qnxTA4hs4qSAO\nEZXLGF3M0mLjKDGRbUJuN6WfFzLQL2GUzXC8dPElMDY34b7LJWao/9P02fHv\nNIz2zjem7573i4+Sl/lhjJQK8M4Hs4FHJnOfOOUTDpqS1pexJnL5V2KpWeBt\nkeRG3395DrWA0N8TqZOkn8zq0cG5VXlngx7XvFz1mP9jRaRlmwlj85eH+J/u\nPYtr96rtgEN1RjPJETl774tuu3Vj0DR/80n92jl6utYDgHb19Vm9j+lQyC2l\n26PcB3feI5PrlKTYMO9hU6NzP1OgPS9aXi4/myvMWEOL5jULZATx9RVW9aAL\nNKx3CMJnoaGvi9mQVAyjYBZd4yij1O1ckoHri9TJTMb3ahCL+6a1HBgCAQsm\n3QG/dJ1jA117OpVAKmBYH+pefy5zSl1+Kz4+8sMwGoPPh+M63pkx0HtgbNon\n2bhy1ERx09ayflrkjv8NVeHBm7gPPXlM48IaCioR15yfoYbT33f2s3LJzMYR\nyPvDZi8iRBOpCRLJX1gMBCZ1I+aSmMimFunFbUTX5sQPteLjqIQpkv/c4fiv\nM8iye1aVf12r1Swl+dhSZaXIryVC7jPWRoMEfFsEYJ4Z1ElEXv7o1vrFZok8\nyYLFrSc5JM++y6lAWgIfLG4y2orKxp6R+DiInTqBr0pmgsfnrSqdsMCoiAkK\n72+8\r\n=DcEJ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"83cb196da00f385ab147fd969c2fce362048f92c","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.3","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.24","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.23","@balena/jellyfish-logger":"0.0.137","@balena/jellyfish-metrics":"0.0.160","@balena/jellyfish-environment":"^2.2.51"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.13.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.7","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.48","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.103-ci-use-node-14-83cb196da00f385ab147fd969c2fce362048f92c_1605085065143_0.09555130146910429","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.103":{"name":"@balena/jellyfish-core","version":"0.1.103","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.103","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"fffdc7c6a949774b5ddc934cc586798d26a4c07b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.103.tgz","fileCount":94,"integrity":"sha512-0PR2b1b/Zv/FC95fP/IahCZjuKA2qjuCfmVk22C7VcFxsLkrpDecZnVETyVMp94Fzi4a8WFtxT7p6QCYR3J3/w==","signatures":[{"sig":"MEUCIQD5rMtziooQxorSMfaId0h8Zc+1+cbXWHQJeIqRqgp6hwIgTBQcVaB8zOKU4UYyPs3eGJz2MG2Nb/CG9BNHeM+QGck=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":570960,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfq6jfCRA9TVsSAnZWagAAWNMP/RldbhzPCLRS/oWqmI5g\n3Esqj1DYreRLruQpO17wv3kiDoXja5R9WLxuW1tZiC5TpWoVeLAuVckY/HGc\niKpedNqmFZpuKG4dRwCLZ1/U2jmHNItXtWrjQ6QQ1n5UsAsFBxthTXnVBYnC\nq6AOOp4xbRrtoE11kQ7pts+10kElfXR4V97iVVIQKuvTwhyoZU+KrjLq6np4\nMORKLj+rmuIBAFCJ963a68rT7gBrEEurN4dk6DxjaILUApadUpWAGW1H+an9\nj4l+qLOuDWX2O8ADr24CngH3pdQl/468Mgost8wNMQpHBrEDniTyE+siSo/w\nCwqq5v4IJZohTbMnYU4b+hS1O5I7rchdhnKaKRRgtWRbi5rRXA76e+FRRBU8\nsD5f/27Vfbdp/uWBETGzvNw5gqqt1W5adWwFQwVUXYyorGxmdcQTkyLcKt9G\ntnAacDMAfsD4MipNn/bvAgBjT/23Wmpc2VQJGlHI/jx/nfOPLWh4JBTPdNAL\nTZnAIIIl2aO3OycM43gSXdMgKajQ6tnFaSt5vdLqublo3vaW0Fvh/kVl+yrD\n8nazrWRbW4y440pFho+riZKBti8u1IKpasvvZhupqKbQZfw0ODTDnFAPz5+U\nZajtN/+jLEWRvReYoVcyPqEPMbjLueI/B0W9jG4f8KKMYxxugmUbycoHw8Uo\nYdeN\r\n=SgK4\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"a6c19b336c33f12acf27c09c74cc550863a9b6b8","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.3","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.24","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.23","@balena/jellyfish-logger":"0.0.137","@balena/jellyfish-metrics":"0.0.160","@balena/jellyfish-environment":"^2.2.51"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.13.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.7","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.48","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.103_1605085406821_0.23612760124994048","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.104-renovate-non-major-aa8ae5e0b06838d568ccc10e676da971bc639d73":{"name":"@balena/jellyfish-core","version":"0.1.104-renovate-non-major-aa8ae5e0b06838d568ccc10e676da971bc639d73","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.104-renovate-non-major-aa8ae5e0b06838d568ccc10e676da971bc639d73","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e9163c7249852b6c4a3463efd51fe719461484dc","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.104-renovate-non-major-aa8ae5e0b06838d568ccc10e676da971bc639d73.tgz","fileCount":94,"integrity":"sha512-ArQ3yehVv+9I75S+4xOUMO7u8Lqrqg+bcKXKyDvu3XlCpadZ1Un/oAqrHysRM3iEs4qXmDh8cwz4ZY7KP31GeQ==","signatures":[{"sig":"MEYCIQCnLRuKAuWtB7OVnrlwTzScVZjKpqhLMWszFng+m0493gIhALR6hJ2u46WfRnmHnZ6dTL7kvyBkQA+5x6+BPfyd/FST","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":571122,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfq8bsCRA9TVsSAnZWagAAnRMP/1zSwpOznRsznq9XvMIb\njFAJPhrODC1KJAbfH2g+VIyxO2kviwyb5JqBjtxIAE4MU4uZNQsr3v/kEGzU\nUdmf6isKgXDEcHBd7F4lNs0lfq0D6IBUhEOwVnZe3hfJ4OnuCqNpTzcvZZ0O\nmrt+Srbwlj5Xmohg8xovqQsS34Xz+ibPJLW7mShaP7uTdxBJGCetBCAYqafF\nFce2M0CPF8hkYOE8gfu6MeA+0Sv8r2KYaYpc5CkYG4ZTxwEgfhQYz4+NyA+b\nzDLrzNhNnoG8Cdsl50fKP390GDNJq4IvBSXBOo0KHLwjypbWDLcOv9r0xWGj\nJvH8NaHrBj8LKPYLdcvUQqWsTmBWE/7xG3Wk6UoNE3qufJwP4wtgRzlKas9K\n7WsVJAX0c1OYAF0fGHV38jFE4U6+xUDNjjhc45/CcKG35WgMZW1dH4JXt57F\nq7Yppq59Zqjhm7oZA0B8800oLpdvvaYfFYdKq2jAL14TcsugQjOkfyEjG5VC\naJco1DiuGnb6OxCDd92P6ieK5DmCFLyrJz/PtJK+I5gqrs/Ug8Yur0qzoxTk\n0wgYWiN8+1OANui40ljR+wvcY5q/n/MHP/POoAJ/Ft8vPE1BZhHOVHrVlaA/\ncTfquzwGI9YxWkC5cLb+vlUR7bJvrECjxz4epK0qVIhWSsEnTWlZIyqW0lwc\nuvjc\r\n=Uqjb\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"aa8ae5e0b06838d568ccc10e676da971bc639d73","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.3","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.24","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.23","@balena/jellyfish-logger":"0.0.137","@balena/jellyfish-metrics":"0.0.160","@balena/jellyfish-environment":"^2.2.51"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.13.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.7","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.49","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.104-renovate-non-major-aa8ae5e0b06838d568ccc10e676da971bc639d73_1605093100273_0.41393730903489834","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.104":{"name":"@balena/jellyfish-core","version":"0.1.104","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.104","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f62bab102fddb9ab8188b00f4aae155b8bdf4a27","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.104.tgz","fileCount":94,"integrity":"sha512-RMZjbCShv/y/x2mG86wI0YSf+Kx5ar/p5AxcavBtgki91dBgthS2JZvhhrxG3gyCUzeVVqr5vqz4cWCtaT11Fg==","signatures":[{"sig":"MEUCIAtuUYpIkl7/Wlu7YPaOWfHMLTu9+Y+Vv4YzDet9fPsBAiEAxv6KUEeO/YOhvXbi3Uzqlhm9uzWJUkt5zaZ55/sjsZc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":571062,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfq8gDCRA9TVsSAnZWagAAUJwP/jKzTHSUn960GxuzeoVm\nABHbbPqDi1XKAnV0535kdKWB6XBJpAZToveYKaggCK4+CtO+yRWH2x5/AtZI\nVECAqPjhMfWIwCRC2TrMMxHKB7axJRfT4cnsbrNQqZqfPqKi9rpbV5AAYNuD\nNkbzzjCow3N68A42iT+AXuN4KYgXrC+qc3E5TICZaaQVSqXyFl1/jLKBunJR\nfNrIG/QOoaixQkRgqcjIPShcgvvHGp5rSk0H1qe5iPG+jTWKadosmnFx7qQ3\nd5JPjuGePf2iXiWL/sMIjrUixIupSR80a+lE5Evr2WiGT7dN1fEL8f3/M1bS\nYTxiCaQfEM8eH6w5qOxINerrRXOyI4kCrfzamHpJ3U9re+escbIacl1WA88w\nvm9LhyRVoG6rPqUKGHcssb2DsBwWZoYMWCS6d1MhuBY/JKE4+eypJc1WA4yi\nvQGGUAi2lzUCKGesYV3DhgN8gTTMSAbpTKfWT1CqtgLqVgs7pjcJ3H3oUjRo\nSKVyT8EqVZ4uQ1h60dRzbJBVRXQ39fUm3/n8DWy/sR0qd1i5oq1GUaa4YU0H\n0ub/an6wqryuNakLkMKUdJsaDyfSjPHQ3z82h4onR+IrcXbrtEhfNQV4k4fW\n2r7ceWZHSr/1rUERQLgoAsj0ROB56q6ir4jXbI5TH3GixChGG82ZKI5Up6Zp\nUaxc\r\n=sb1y\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"7317bbacafac3f557f710118d3fbcca1b1586ae0","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.3","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.24","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.23","@balena/jellyfish-logger":"0.0.137","@balena/jellyfish-metrics":"0.0.160","@balena/jellyfish-environment":"^2.2.51"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.13.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.7","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.49","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.104_1605093378866_0.8101336349225301","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.105-renovate-non-major-58b3c24928389221120c5aa13acde163fbcc6aa4":{"name":"@balena/jellyfish-core","version":"0.1.105-renovate-non-major-58b3c24928389221120c5aa13acde163fbcc6aa4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.105-renovate-non-major-58b3c24928389221120c5aa13acde163fbcc6aa4","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5a18ea05690ea53462f2ae1969053a7a2a425b17","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.105-renovate-non-major-58b3c24928389221120c5aa13acde163fbcc6aa4.tgz","fileCount":94,"integrity":"sha512-hUBxb4S+EWhVL+XtqnPmTbaPq1uD1p9OBf9ckOYzX9Cyp4CfX9Z7meCtjSQYYZ0U04SIkqnMUPQlFdnVGDEblw==","signatures":[{"sig":"MEUCIH82YuEVKvHbQgC2skAj0QzdcEsCJtT3wJRwaPGnBY67AiEA0aoJQYrNxcZH+oABSXXvOWcXNQrIWZZz2nkhXhq6hQ4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":571231,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfrI8QCRA9TVsSAnZWagAAQe8P/AoXqDAhHhW3mAogCuvn\nEcr19Zw3Ba6sTt9WoSqPb+kSSJYQpcm3Dk8GRlD3vkfjlENZbOYUJLPdFvsL\nWLoM2daMP4RW4jrbqkrEhCJb9l6CMC0AfHcnrQpT/QS1BvH3kbQVSPhX9aO6\nz+E5fpOVO/rQPxt6eqPiB9IM9KaETnCbTlfnpT+Mo3oEZDrRT8Nxj4w2thsS\nC8z+K09yOKLxETrR3cDbYkTZ1MzWwEnvfyWOSF7oRoEU/k0fHctwQ5+VJKek\ndNFUJ0r6eYHW8BAT2gWRmci6FsSOSHN0ESsrKQVO5hxygGaJhSauJVC6kDK8\nfgHo0ME3RyrAMHptQM5647JnOFdA9+b4WG/InJi2XrapyeUW/rm1iGLplWA7\nazd8FYkQsdlaPtJu6y1YeUF18akyWxGfJrbV6uNv1PrYK6ThgPROZvh6RDog\nmkcNn2s8wAqMBHezAqOJiEUP6sj1MuXuF9MDJf5d3/SfTNAm5RfWqhhmHS8c\nVNs03tEAjprUjTNWN7sXEVj4p5bLhAKbfuonbDJMV8VrqXOHRVCWYGPBayWu\nQgejGkrtgh4EO8ZuqupzKXqgAaE0DWHBuUSF16UOJvB+igvXWXA3fM1ujbZ1\nQV3J0+0WpkEcEJS6Sm8EH6bpP/p2h1RjiMJyVznAhs+XlZ4/rlyv6BbncPmb\nDCuq\r\n=SRgJ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"58b3c24928389221120c5aa13acde163fbcc6aa4","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.3","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.24","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.23","@balena/jellyfish-logger":"0.0.137","@balena/jellyfish-metrics":"0.0.160","@balena/jellyfish-environment":"^2.2.52"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.13.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.7","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.49","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.105-renovate-non-major-58b3c24928389221120c5aa13acde163fbcc6aa4_1605144335705_0.9252525779928675","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.105":{"name":"@balena/jellyfish-core","version":"0.1.105","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.105","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e5abb7e913f9d4e8f9c48df64101bd1a31cec144","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.105.tgz","fileCount":94,"integrity":"sha512-5H4vt8vnJ3C++lCTch5cZX7fhDNpoUsL5Bzrq6M+5sFQuf31bvBTWkYtzLyxbHG71yR1w77EyKYXYB30S/rStA==","signatures":[{"sig":"MEYCIQDkat/NqcOlngOYjwVEFwB3UN2mHOyxseJteTDfkWO1aQIhALf0KQt0YhiJIk4hNvAOUcz2+fIuyfvWo/3S8SRO3nry","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":571171,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfrJBKCRA9TVsSAnZWagAAnZMP/A3H92SWOP1O1hMCu4mY\n480ELuwiLeX4dB08mEhEftvrh2obBPywyhHVgZho4+HgeWtiS2hRWOfE6M2B\nvvWonnfflBTgrWgMvlp7pQ3WBJlHpZJ4Gyq9bZEKAhtf7pY8cR5n7RC1VA5f\nbZYzXE5nllgSF2Z4OZ8LmzW6JoF8TCd41bP78ygHIQot9Vexd0OKfSTMkWc6\nkXnJziO8DK+h3/dz23f7vzMn02y7BSxTnHvsiaiFAbfVotdp139tySq/NqiP\nosARZonz0Hd2LhUGfDbqbVjZhfsHDF24fbkIggm/haXD2obLFF1j2J+21Vwf\nE1xtvTv+jaJ3XEp0rRXIwSkTUHoTK7MGyXbimU9MVB/ti/Es/LDyLpJcR6Fm\nTfDyYiwadJiC6pZ3vERfFX0ktVQPVfAqFZy58iixRfmvG7tJ1dZBh9QzYpJk\nsHIHYIFOW4qePjHunLUJ1vf5yLiNT+pZZTY04cgi6qloeKegYfRF1mzOHFpC\n42bSTjtlJd/dwqFJpjglBCOG4ZIR3yqw/sE0XgV3GK68PzRklZB9elECiJJ3\nQdV24765ZDPH5Aicwgr+eSRrxMtVRo/m3xdENHc708BemdKsUBFlm5W9Ak3+\nkpKv+aMbrHYWtVoGGy5KyjgpwmvgMts+Za+Pcj0X5k6Gr/6M5RVy5D5PYTcp\nUCI3\r\n=5wai\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"9453ecc5b74f22a308b599df0de3e3ce9bc21a87","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.3","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.24","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.23","@balena/jellyfish-logger":"0.0.137","@balena/jellyfish-metrics":"0.0.160","@balena/jellyfish-environment":"^2.2.52"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.13.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.7","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.49","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.105_1605144650196_0.8486390057136448","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.105-renovate-non-major-b1ce6d778ce76a9d144e5c3184ae01b528b81dde":{"name":"@balena/jellyfish-core","version":"0.1.105-renovate-non-major-b1ce6d778ce76a9d144e5c3184ae01b528b81dde","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.105-renovate-non-major-b1ce6d778ce76a9d144e5c3184ae01b528b81dde","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"02b67abba37db34b7f4a00d737a72efc315dfb99","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.105-renovate-non-major-b1ce6d778ce76a9d144e5c3184ae01b528b81dde.tgz","fileCount":94,"integrity":"sha512-Id6gJmKS91kIN1t2e3KdFrzHmuH32NZ33l5UokKJckQZrCW1rNbFpW7RFKwFDf9JT2nJYJiDrCOb6+LCzsRJbA==","signatures":[{"sig":"MEUCIQDsBZ3yJR62befK1Mc46LKai0ZWYbHJfoRYLVvdDP7/MgIgbwdjA4VrsKh7gLxdd63hLBXAWdulNNiWGh12mJGMwek=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":571310,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfrJDxCRA9TVsSAnZWagAAUWcP/2CuTnoI8/Xn7IG/jW/B\nu0ylOc9oCOsSLVLsHIeXnskmT/d+cVue+v9lwR0ggM1HXcO8qC84n0ygW7px\nV6MBSW7B+BBLftRSRVwFWvNTB0W8bUcro0XT2awYOw0IYf0RNh0AUF4PjAZc\njS9z90mnWzOu2gUBImMziSudfNb/lsD9VQlLPz/8XhEZkcy7FtTeM39zZ7SZ\nln/j20XMhGdhF0M4n7ujfONRujodTbtJJLP8yRPcHCOCnyz50WiTH42pHpgq\nWX43z23kZ20akV8dw9uGZq8TlVi4JocKBl3RQcalMnX011fC9qifppFkb46R\nySkH/oyBIOIjPi/j7DpaXk6TQVzihfjYKRUwU3mluHtG3+kPz1R4Aa8gQvV0\nwMI1kDQdWse20ktZ7OL4fRQ21oa8r3T7zVRAvOUpr4x0qX6MLm4CAj1Q+18N\nQvt/UpczHj5Lp+X730Z7ovZt+1QQotBQmHhW+LqyxKNAefCukfNN8WxtNys/\nhzVIW6flrA3X9EwL9BculSt/0IQefbrK56lMfGrdRwRO4bjLUFgjtToIh5HL\nLn30auusf2eycR0G6WM6feLjtMsZEirrg00meNtn8cip86ByR1BqeLzppl9g\niSEejNJQozhgj+poco9MQsv8eJDFAXQBvp3bm2z21Qrn7D+2xDXcUBhfDK/J\nDiCX\r\n=ckJl\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"b1ce6d778ce76a9d144e5c3184ae01b528b81dde","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.3","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.24","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.23","@balena/jellyfish-logger":"0.0.138","@balena/jellyfish-metrics":"0.0.160","@balena/jellyfish-environment":"^2.2.52"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.13.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.7","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.49","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.105-renovate-non-major-b1ce6d778ce76a9d144e5c3184ae01b528b81dde_1605144816550_0.13460077213117483","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.106-renovate-non-major-87129b12366c1527a83cbf5035bed254ccefd223":{"name":"@balena/jellyfish-core","version":"0.1.106-renovate-non-major-87129b12366c1527a83cbf5035bed254ccefd223","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.106-renovate-non-major-87129b12366c1527a83cbf5035bed254ccefd223","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3ce810e1d46544705dac880d49e8d64e3880b1ce","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.106-renovate-non-major-87129b12366c1527a83cbf5035bed254ccefd223.tgz","fileCount":94,"integrity":"sha512-5X9nBvqZwrIKeTDmnj8fp44DTOu/ChahYflEGRwR6UFasFpBIlC6W5ZDV0nU9pOMnshye1VWNk3zxjbkxbHFQg==","signatures":[{"sig":"MEYCIQCU3soY9hf2Hp1C60sq0Tt4s2jQTOvMGC1SK5SN1M6QkgIhAPy1XjS2sLNGunNfAQwosNxYIy4NJNQtY30FdqJHXNh/","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":571336,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfrJGVCRA9TVsSAnZWagAAdSYP/2B/SRFRTMDn4HIIUiIN\nyvsMHP2Hk7Jp0/ySMBxa4i0RFWvPD9TeE3daXxoZOdyuzZC2KCJvAAqSKD/m\ng2A0+a0wwETC4RhuuFkkhaznqDE1g6j8XwwZzSYIeSSCQtSTM/oxpi5mLJ5g\nXWHt4dEjkf/ODqbRzqLVNEAT9inG36ZdfmGTHf+lV/cffqHTIVLvpBSW5uxU\nJKdTHAkMyRVSsP5AB5QKZl7O8oO/agMUpYIbj3uNLpVtrua9xAvoEcIqpLbU\ngdH8SkHY2q+5JEfRH3B/slDiskPiJIhT2nGNjPBNFKT3az7KXy6vaBdTA56i\nBURRb70T6p62BYsAhPu17d+fFrQCPxrN4TcXWq/1kPKoGSFl6dmFneWWn2kR\nxQXFhiM4PRa6Oj5dtpBBy9MnA/O51+a3gJpkt3cQNse5/xURt2sNLxRCI9w0\nTC/11ReMiA0euiwFjmi6OviUXidyph6MeibbBUCp0srXwo20SQOu/Snr5xr4\nQ0o9s1k0/rlusRBWGtMcK4A79sUz3wcuaM4TntIsbPZJM/KUgBXyb3BQHOBF\nSmv2iz0oSxSvJYvxOeJWfMFBCct4gmlFhOjxuut1h3tKQFf5QZ/FNjRr53nh\nnE4Ik3WZc8q3kNAiMVNoiVs7m+wWXCBeVqkTd9cBeQg0qE++AXscQ7EnqhVZ\n0aYm\r\n=IDYW\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"87129b12366c1527a83cbf5035bed254ccefd223","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.3","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.24","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.23","@balena/jellyfish-logger":"0.0.138","@balena/jellyfish-metrics":"0.0.160","@balena/jellyfish-environment":"^2.2.52"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.13.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.7","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.49","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.106-renovate-non-major-87129b12366c1527a83cbf5035bed254ccefd223_1605144981189_0.8979174142972193","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.106-renovate-non-major-9d9219f5b98bbcd1034fee012e5a926213ff6eae":{"name":"@balena/jellyfish-core","version":"0.1.106-renovate-non-major-9d9219f5b98bbcd1034fee012e5a926213ff6eae","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.106-renovate-non-major-9d9219f5b98bbcd1034fee012e5a926213ff6eae","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"cc8bbee32c0ecb47848655145a20f2bdde0dd3d6","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.106-renovate-non-major-9d9219f5b98bbcd1034fee012e5a926213ff6eae.tgz","fileCount":94,"integrity":"sha512-ZAvQn4ghL9GaYrT5Hk1hj7AOtm6K3vruOlm4I9W3YQx0cocafFQg56DtgQxdrpKSdtSGhPnjJGEYEL+q8nauIA==","signatures":[{"sig":"MEQCIEVTnhESfuiG7DsYG639MSZtPtgqw50gCSLbKp2yjQsrAiA0l7tm22//h5iud3ObyHfRSQCqOl7/ADjPMy2apwYscQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":571298,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfrJKgCRA9TVsSAnZWagAAxPAP/io4nuC5S91eIoCzXKRY\neOSc4yRScYEHwpwtWDm7rKWnAKorjTD7KwHnP2mbq/JM6rI8f/GQUiVUnRJg\nGuD8iAhRes1vw4lW5XZOwh3uYkdQ6wC7E3R2Bpsatcpf9CPUsBpGkfNNHpY3\n7mLLMGeneG9U/kaPNOcPHM0+6lzXM5cVVdtcs+cVNJJmWzprg0L/uVvSXQ7k\nZjXrRYbXDflpXDvF+RXVmedpcES1kaBBfYVQO0dAXTh5OQE3AcDUcR4czYPB\nUDOo/eUR3XbcLgSzdQOOD6/mEeZS1nplLGDAEZhzTb6xaWi+jeKYOWV/+dHw\nSk+AC5y2eFa+9VlmrP9deKBVvAcIE18qUwDeMknssYCrpd8fbtNKC23i7vzK\nG+x1JPbYdhxbpTAqygSQHprSGlQfurHByziWgttD7rMe4VOsi2VUeCSroc3Z\nh8IYVGmH7h7rPPwsYWX4ZZrsTWYMOz+Q07eZtioracUGx2iPgiv0L5rWX1Is\nXZtdJCnDo8plxqhi3l4gB3vEsODYvUGbbcXySAPJu3LxFb7S3/16W4RuPidL\nWpHCIdUjsOIBUu9DwHRGnKZxoOdq8g3nGB5FBWTAFYG9xITspGgV/Jl9B+eZ\nHYkWDgipH1SH5yQmlFtcpBV53VcEurmW4tKl8kbR3GTR4fUo3iaSfu+v4/qy\nd5eX\r\n=iqzK\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"9d9219f5b98bbcd1034fee012e5a926213ff6eae","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.3","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.24","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.23","@balena/jellyfish-logger":"0.0.138","@balena/jellyfish-metrics":"0.0.161","@balena/jellyfish-environment":"^2.2.52"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.13.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.7","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.49","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.106-renovate-non-major-9d9219f5b98bbcd1034fee012e5a926213ff6eae_1605145248239_0.8751664106514396","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.106":{"name":"@balena/jellyfish-core","version":"0.1.106","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.106","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"071c955586dcb5f2dd467ecccc4723b98139cf2d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.106.tgz","fileCount":94,"integrity":"sha512-52GN3ExWGOim7BN41/0nj94u+zt0dRzKLjOktmGALa3RMkBUVE224NqE9puR6rXs2Bd4uboe8eTpmh9LvZIH6g==","signatures":[{"sig":"MEUCIQDxLg635Zp4eDg4HY53ti/5hieCYRKyy3Qqb81e4KMdWgIgXj6Qh2JN8KQzi1QvwMgOObNofw6oxdBVEikh2Amc2kQ=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":571238,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfrJPKCRA9TVsSAnZWagAAXGMQAIyGfMLDsj8Hj6zTJRf9\n+psqvHBzz3JLRTDDPeDXZ45GXjfq1F2obFDjRaecdX0IYNSsZSW4lDBe3+yo\n/13jAAyM2riVw9dShpimnp3hdbz88Zqi3vHaSv3yJ57fDXTml32evBVSrR9U\nGNeX7KAU0LYr7nQ/Eb6MdE/tHxZlzOVTj70im4f8f/Hx5z1UHQIaUKq6O5A0\nwXSqXtPZg5rL7RdauLTcalLXWRUUkHntbsTJHDvHsaci/NMOtAZewM2khwbm\nnjUe4dUxYGE1XhCEtS3W6cN3w3aL+teE9MjO3SkZN+5ulQqq4ReT08WerS5Z\nZeJ23+QVnPPcVwBfto++JIHDa6cb+qQDyv7FJXWf0vJzmoufEhYsbGtsOQTL\nObbeYo/1meedB1/zeDlLM0kHgf1BJfby9YhfEQa+hhoJYYuUJ1w7LPRfkhD2\nzSIGMrzNoLtI9wiwqZPvPiowsrEfHwYo9T6RnvIdKylumzWcC61xKwvLkIwE\n3FkUIUCGSo0Q5PZhu2GMq5A+Xs0o2CG1AJmkglUhL29VsxorW+txUNAQGTOZ\nQsPL5zAaoUXP1+ne5L2ETDTcsGm4ANzc1gwp7qav8kdqgCERZ+aGGohg67Lr\nm8AY+08kP3VJ341S60522IITMgK9+lvk1X3q0c3oim6QZf7mdRXbR3i3VlNq\noJrQ\r\n=3DE6\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"18757ddb123f0ca24a262660d32bf8ac766efc67","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.3","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.24","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.23","@balena/jellyfish-logger":"0.0.138","@balena/jellyfish-metrics":"0.0.161","@balena/jellyfish-environment":"^2.2.52"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.13.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.7","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.49","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.106_1605145546409_0.9863772941925248","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.107-renovate-non-major-920241b5db99b37ed9d28d274dc35f8967cbcc54":{"name":"@balena/jellyfish-core","version":"0.1.107-renovate-non-major-920241b5db99b37ed9d28d274dc35f8967cbcc54","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.107-renovate-non-major-920241b5db99b37ed9d28d274dc35f8967cbcc54","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"75eb7f61601091935454be40fde213bae4a91520","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.107-renovate-non-major-920241b5db99b37ed9d28d274dc35f8967cbcc54.tgz","fileCount":94,"integrity":"sha512-VqDCeVOAQLkKOut0dY16/Q0r9TFFdYaBgIQuxOwLTivZiUp+a3yDAhCvnetRB4PLiAE35fSc8pc45tdMkHl44Q==","signatures":[{"sig":"MEUCIQCxTlbzx19bE/505Q9Uuwb+eWSa/Tx6gnCYnmDZJZOnVQIgH6Ms6WsHQIX7Iivc625FnlicrWGbOuNl/OLP4meetlA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":571404,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfrJR/CRA9TVsSAnZWagAAokkP/1ORUMqmFFdxfB+dmHjb\nEaF0xrB4WgwRp8t4A3pjdq0lbJKbVVjzO93K/0FvR7YHQIQgQBwIq69ZN+j2\nm8uY7SbRk2wNOC7twAYWXHxgAqIydnF7nXWlozQogUlBbiY+fSYt5r1OSm2y\n3ekw2q5MRkwwuAGw82AkE+S99H1/ISVJO2+vvYTaYh57Dhq03wQ0A8CBTLRe\nDL5r8tWF/gFcp+NbAu001BVpzsMYN64YiOI1OOj47RhtQucPAssXh9Z9BBCT\nAXtAq/vBwouDmAdbJWH+NyanKcxKoXLJeiE5ler3GKTGcEfmLnukj0bOy1u4\nBZkrEbvu+47fX6u1pwEj7KEZHWZyka1Jzf9h3p8MQclGnWA9Ge6b/t1js2xE\nLIKDyFVVdgDszMQFuzh3tyYWnYjefB5/wVOa4Te41nCf3nI6QWUoxbMAEEVf\nPGNJASo0PDhCRqLJvYwInTqt+30w8HsSakclje98KIiJaG6wtD95XbN/wqsQ\nY21+Sz6F3IOGnIWDNkZjBgYr1bXTQUvGZx2p/NcCtRprFltCZk8D+/a2ZXJm\nDPCDq3/f7+EN3SqaO4i4D5hcMvxuGkeIZoYzLu7QfBRA+z9KsgPKdnuT6xqf\n6jE6B8M1ZTRgv4gNng9XQpqfK4fUSxnoDtAfatE/xF8/u1KsqXGENx+kY67L\nG0y/\r\n=604d\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"920241b5db99b37ed9d28d274dc35f8967cbcc54","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.3","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.24","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.23","@balena/jellyfish-logger":"0.0.138","@balena/jellyfish-metrics":"0.0.162","@balena/jellyfish-environment":"^2.2.52"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.13.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.7","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.49","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.107-renovate-non-major-920241b5db99b37ed9d28d274dc35f8967cbcc54_1605145726615_0.6259346905009684","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.107":{"name":"@balena/jellyfish-core","version":"0.1.107","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.107","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"59378498590eae7b31aed2a6f9bb93f04e4058cd","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.107.tgz","fileCount":94,"integrity":"sha512-CqI5AEzR4ZQAxQ/lHE0N8OAjEcEtQU/i7QNl2666i4nqCM22sAl3dOE8L1nXY6OTL/8I8+JfgHxyEkXR4H8wAQ==","signatures":[{"sig":"MEYCIQDuLs1zylE4fZLhY4vjyl5sERYTkvEXSvjhaSodrxP5BAIhAKsM0etLm69uUU+OqUu/1SRx27i7vmbUoMxmVKqsUMNK","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":571344,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfrJa/CRA9TVsSAnZWagAAF+cP/0+/qRJmRfgM5KMe6709\nN7Uy/6DKrUO1e9IiZHBW3UXmDv/lqXRAvT/ORgdsdYdowFHGtJmlzBDc9Eyp\nl34uEBAUmhz6AVv6H7ys4Rr0GFRPGOjr6Rq6inLUSmA1dazAzm4akwN+Y04x\nLc6IJXyNMUbKWzgN7P1JBXA89f/fmk80qjpSG3ZpNOFXfyKv6TkX/9/hLK08\no5nJ3blcBWJxz5hhX1wO8ZapkEgQl8domuv7qcMElhjyBGmp98Z3DXUlmcti\nIvOYPXxC1dJ3NA7dg46Qok73yIS/7qc8Ar0WFBpZyVnlxalq5q3mYfH3okEp\n5APbCoBANFGosLnBaEFCg9KKciy6T4qg3hlu13aKZuUvXTLN0foctedfPctw\nam798B9NPnHOMqUim6HD8gyO6oiiExvxwoRdrPeWC/FCW2bbVQu0XiL82H1S\nP1OYx2h2/wbsWTop52k9t+6S7q9FfTdVdfA/MVgoRCDCUq2rfnkq/O8sKuSc\n132seNLOKKB61Cj3kNFGU3Tp9USLUsX8aBVQrIOaGmr6UP3mBXv2fhtJ6BMe\nv5b85MkqE0iLN8VGP1dsWQ7tPHSKC33+pdRzgt8B0YX/xF+gdk/Tn1sOiRwx\nyaoaVtzrD6afR84rOyVceDRwWBgmrDvCKxcQ82R6w/E6sc2TSh84upMbhyoN\nkKFV\r\n=6ubX\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=12.15.0"},"gitHead":"b7d567ffebc73b43982f9fdf921bb49c1a8b1b6a","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.3","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.24","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.23","@balena/jellyfish-logger":"0.0.138","@balena/jellyfish-metrics":"0.0.162","@balena/jellyfish-environment":"^2.2.52"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.13.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.7","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.49","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.107_1605146303003_0.44244167852474314","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.108-require-node-14-da578b65cf05c8259cef19064a216d2b289b68e0":{"name":"@balena/jellyfish-core","version":"0.1.108-require-node-14-da578b65cf05c8259cef19064a216d2b289b68e0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.108-require-node-14-da578b65cf05c8259cef19064a216d2b289b68e0","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9c84fe912867cd47157cd4c06925c5a71298eaeb","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.108-require-node-14-da578b65cf05c8259cef19064a216d2b289b68e0.tgz","fileCount":94,"integrity":"sha512-Z8hW4EzLOLlzrg2CFK3U9NZ3s493uT76dfvFsFMyIfLnM4RVvWL8TIqPgTWSCGjqnOYlYhb7Sb9WIiio9jH4Nw==","signatures":[{"sig":"MEUCIQDxboloNNFBYbZPFQdbvE7gUWRkFsZ9WDDmxKOSsJX35wIgRGsv4Cdvbf/o8sPrr3qeXH6BVdqkNtM2Ojz5UDEcSVg=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":571460,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfrJhoCRA9TVsSAnZWagAAZBUP+QFZ0WFQWQOpeZ/QS0/l\nWRq+GCilWA1/MLUAYlGwjjBd9sRR+Pq1SFgaAY26qCoLYMcAUVvuITzuRFPw\nAP3/U3+e/jsNlZj+geVA9Ci/VIMJlBqYwlnCpJ5lgKhpnSwByH3n9zS2y5X+\nb8UZ5eftUtNkr1wPpY/L/sUYAsM61pkL91VwefTidMp0Z+AT5MCLFDURG7Gf\nowtQcEbj5yZkrAn9e1JxFqbyg9KZ0MwTPG7YyNXULwYfc8ATVJwGhJztrWWA\ng3Q40ZF1fSaDMkcoY5o395UHyXG5XehMumK5FOBDQ4DVNDUQmXWXKw2Oddj6\nV18ZJ/6ny9Z7qdtMmWq2crQrk//NqlaTgBPFRiQWMLOHWPlMhvdNJjrStN77\nqUGyabsSExW0IuALxHJ894kOVCXbAvvuUyRSa6Cir9y9Yrnk7RIvXorHXnmu\nLVRgr8GQA3Or3ma6NzxwUHznya6JTtUh1GQha5IIryVz2K3SVSAi0Y+EWpfg\nYX8h1rNXqMOHn/MNCxZYrcNuqFoBu6RyZZSRgZAbiWbF9agupQPUsWzaX0oc\nf1SvT/Ridz1XlyrsPW5skE++cW+17KIK05ZkC43Q6m8FnMMlCsHFZHb45ltE\n2XqdocEqSttiyhcJcOvw2kiVOUYvK85AG7FsbAkkmcr45RcpDIvKgZtKAoan\nJ33P\r\n=Yhrh\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"da578b65cf05c8259cef19064a216d2b289b68e0","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.3","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.24","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.23","@balena/jellyfish-logger":"0.0.138","@balena/jellyfish-metrics":"0.0.162","@balena/jellyfish-environment":"^2.2.52"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.13.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.7","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.49","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.108-require-node-14-da578b65cf05c8259cef19064a216d2b289b68e0_1605146727887_0.24428308031330936","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.108":{"name":"@balena/jellyfish-core","version":"0.1.108","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.108","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"61999668c4ccb7dac6a0538183c11cb4f6787cfe","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.108.tgz","fileCount":94,"integrity":"sha512-od704NmABGMiLah/Ub8HyVpq3TARxOgyIP/gGZou0mYdpZGnnzLIRjWATXEXrszrTa+coXZ8zTtoGUxNFU9X+g==","signatures":[{"sig":"MEUCIQDuu3oAjHpi3CD0yWCFBAeVTtoOfxzLFrlcizZk1xqSVgIgWQY3uJCwmfWMOxlzY3La1wyPjBZvYyW2WypwsmVMgso=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":571403,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfrJyaCRA9TVsSAnZWagAAvy8QAJnSXtj4ess1Qu6gkAv9\nRbMDa4K2MEN7o2UrGcEv5fUltxZBjavYnW8Fppp7AMjrtbb3K2np4KYa7a4t\naL71JGFNtx5WFrASOAb1EUM9iQ6or7yy7JZX6797QaeXoYV2ogcOdt/KW6qZ\nDUMl5mTgff0XCVgaYt1KPXtkJF768HVJi5hBqqTK+MZotjVAXQ3Qgk2uQ3I9\novpHzH3m3Z/LQvmPEKPLITvo/7QZHYZy2wbeUpWgqEoSVPvb5tUsEHI3We32\nV8yN4SkBv2s+yqmpm7zUelWsyYhYk6bxZpAbrLK4nDUflPFoYrORMTUUgJgV\nwu85dwhQPRWCiYiOm/iSaRZ29s3gXRT5siiY5j3zJ6C5xHcT1YeSOiXpUEZw\nu+F3v2rAkMfd0qCWoSG+K6QKDd0HWKvVpXQ0JV7LlkfyNlKSmWRaxdtmSqE1\nI96KOTJJ/8cyuFzA+BpyT5RWUJ/sohm4eUPelKq2dhs7oJUR9FB33eydChVs\nUiN27yHPXtdXHFSvnt1BhCHuYFLOPHQ/ySHwp0+LysaVhIX4sXZPfBTdEg0I\n2p/OtM6kHbH8EtMZ35wRDtuU814KLMe7RlExTvcQ8K10QdlRbk+tleDIOt3z\nVJBwGuOqG1OfaCJ2RfiUTh8rISc+YOTrzqcoO8h423jZdhMxN7ee4ukp0RlC\n5Qy2\r\n=uy83\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4fe38adba107ed2274fe80fd9278019828ec1e4d","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.3","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.24","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.23","@balena/jellyfish-logger":"0.0.138","@balena/jellyfish-metrics":"0.0.162","@balena/jellyfish-environment":"^2.2.52"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.13.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.7","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.49","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.108_1605147801521_0.06644411336605582","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.109-renovate-non-major-4be97e41838e8d2944c67cde826a7d2f6dc29dc0":{"name":"@balena/jellyfish-core","version":"0.1.109-renovate-non-major-4be97e41838e8d2944c67cde826a7d2f6dc29dc0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.109-renovate-non-major-4be97e41838e8d2944c67cde826a7d2f6dc29dc0","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2d43e02c0db6d7757e04b0366dfbbd118edba812","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.109-renovate-non-major-4be97e41838e8d2944c67cde826a7d2f6dc29dc0.tgz","fileCount":94,"integrity":"sha512-5MvVr/pj/FAKqBjUB4aUJoBzWkXteBQ1aaUHLxtvHi4bX+CMc59w5jro92r7iLe/jeijR6Ug478+g6DA0xzoLA==","signatures":[{"sig":"MEQCIEPbNSMCfJDJUCWppBp4l8aQ5DRSsUxno/7wArNSdIXkAiAx9A7b69qjEiyvwSWvj7/6KHxYnEMds+y/pqMjSQp+Fw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":571530,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfrNUlCRA9TVsSAnZWagAA2IQP/RorrpyPaRZkL6OIgTbO\nIBKVfXuykLIvXWgBFODKLO0IPOytyYGw85/m6Q7212pyqjCMegp3l4uvi74V\nPMtGE/EvjVzqDXn0/eG/5zjVRl9Wu1VFd2Y4NOHBNaEnANo7Zpe4dbPhqpaZ\ntjGTARuQoJ3g7PZr4YSNyyui2+SZnbQFheVbih50CKX8qgg1YWpdWfXrwf/i\n5wDmApalEIp3QYRcn2EzWJ5Xwyh7x0oVQkSixozf8rQqE2V7IemjjzkhIDVT\nCH1B+5M8CEOuNzvKzYsvffB8tq0vy/yt8EaZxvhH+1pesMxHgDcje7L4Ul+i\nLoyrPDKFCId49JpWnhkRFMwnM0hV42sbG2LNiJpxKJyCEnrxPoxFonB7H2fs\nxxtti7xnfmmNUYLSBBFBEIavZBoqqQi7eNqIqTDlESnm17chBvqu6tl/Kvkv\nbdveh4hnQR7ccy62kBz0Goj4J128pv9msKQelPHishzkSU1KiC4JmIqevCe0\nvMO+P2mIKV5P4odwa+HkazCJd8ZDE3+4Ap0J/CkRPj93bptPC1HXhlaFpFOS\n/3Hh0Fi3LMAsPAgjT5vXMw2v86uiRXfmCP8LIEil8HfyB18mfdNXSLXigpY8\nzWeJ+vGTLkuX4551SoW1hPIcFfdtS+uzLjIx5ZdZst/g3/DP11WYoIVBWvpT\nN5Wb\r\n=qUaU\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4be97e41838e8d2944c67cde826a7d2f6dc29dc0","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.3","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.24","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.23","@balena/jellyfish-logger":"0.0.139","@balena/jellyfish-metrics":"0.0.162","@balena/jellyfish-environment":"^2.2.52"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.13.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.7","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.50","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.109-renovate-non-major-4be97e41838e8d2944c67cde826a7d2f6dc29dc0_1605162277020_0.8906612859684075","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.109":{"name":"@balena/jellyfish-core","version":"0.1.109","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.109","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c5768612fa790d3f3274d5523482858b482015c3","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.109.tgz","fileCount":94,"integrity":"sha512-qTVPC9bhKWTQpwQKrrQ4CLFEQu6ATZbA6jAlzsGKtpb9sj5lxW6RvBWo1/8fIsRBD2RCfjzN/NDUyNAADg+prQ==","signatures":[{"sig":"MEQCIEm2UsHQMrh9exoGqEEaCr2GnuTOYy5Uv7sE7/mx0s4ZAiBjz/uCnJgs4TcCppmxo1Us6ZaZUD8CwptyYi+CsikXkg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":571470,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfrNbuCRA9TVsSAnZWagAALQYP/isl3a69RjVrL8VEI4pK\nbKLjNGr0QEr96bDJVNpCGQxQigkMf9FbiydvhdC3kanDEZENtU0hXA1M+95d\nvsfJXBlm6owwe5YfSF2+IqTRnBYASGcb/1yrZCE7s9/Qkl4SMFtHxMurn9iB\nCVgRa5xeYCRrLCmsQBK7iptjtkFl74fLZ3xNDHp6n35eyLLveh4U4rPPOz6O\no/h3dbTRbCJWTF6DXuskje8gCgW8urSKsnUrCZwDSYnd2/5z0cyppqFeE41p\ndqCYOyDh0xC4Vo2uS4eCHgJYzTihui4rgG1kuWtL46vqxagPHJSJfDFozeL1\ndN2jmEY8vr0BMz6RpfXLKtgPtSu4xZ8m+PQ/ypWHn0/E6IKs9RS9jCLAdX+z\nzft/Sx7WFx4c2S2YYkPvp6NUnGwfBLxZa8FtHLhyO6D5Si6KsdhpsfTs/gWa\nTSpAIXsOXtmQD7NnP1NjayQV1+22n7K1UMPkIR2HW44BUYGZq1BcIKRY+Pbv\nh6dzQQVS/NwXHPLGx3LeeHqYl5l9i7rFoqIiBs51qiBF/SmDdXfGNkJPl0lq\n5ULia+1jG4GU3aerrSzXgAMZAc/yJ4ye9jhU4/DxFQiNU215pfMboGKIDoGq\n8TUL1hVjFHdPwJtcgySosA83RId47HT5gGOaSVSpVVYLTbqawKWAQbOd4DzX\njJEg\r\n=Vgpc\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"1802f73018fb33ba39f0ec54087df71bdd83cabd","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.3","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.24","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.23","@balena/jellyfish-logger":"0.0.139","@balena/jellyfish-metrics":"0.0.162","@balena/jellyfish-environment":"^2.2.52"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.13.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.7","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.50","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.109_1605162734037_0.9825992959263379","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.110-renovate-non-major-ab5a8448a50a800a66fe1448b6a74a8b4c6ef738":{"name":"@balena/jellyfish-core","version":"0.1.110-renovate-non-major-ab5a8448a50a800a66fe1448b6a74a8b4c6ef738","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.110-renovate-non-major-ab5a8448a50a800a66fe1448b6a74a8b4c6ef738","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d062f4a1478b2175f455a52db96123926d25f173","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.110-renovate-non-major-ab5a8448a50a800a66fe1448b6a74a8b4c6ef738.tgz","fileCount":94,"integrity":"sha512-NLWuidNwhVx55IivChoqNnnucz8FlI744uYrE/kYCFWvdvfwFgMQ7ugJojNMwI+vMrXhuxYn3egtYpKFdA+lQA==","signatures":[{"sig":"MEUCICWgV3ssVibxD3WFO07/Ns/NUAlBxZ6ZKWwWEX/sD+5JAiEA4sBgjKVnys1z8KWzt489zZErTzj8T62XkWj7kttdgQ8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":571636,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfrOxBCRA9TVsSAnZWagAAV1wQAJxz1R0QvV8L0/Jp7q9r\nUBAt9ROcr6PPbHCjot4o/u5ONq5hGkQnqXmsLOXbx13WVhmL4plSR3ZOyJj5\nMeWRs0Odd78vVM/KTyT/z2T5+Tj5Bd+ojT/MKWj01aoq+Ro+QTHHjipnWHuI\nOdU9CAQSJfmsUlgMtu/X7JcBOKsO1JhXvjZLJ67/oTgRBcMF7qpWV7xjDz0D\njlO3l7a+/eYLU/8uqsNZ8hhldjHK8AahwTn7D9Zj7hFBaQOmQF3nrsdEH9Nm\npdVwP/085AwYHb9b/X61wAoamPOWtrgmu2vjShCT05zg0+vPcoNom+fyNeYp\nOsifvuUiIRPmotsWp2O9smBm7IdBOnsFhMNreYRORH3KPHgxhnfAHj2cGlpF\n88i+IsJL+HoG4cGdY3TiI5EuPbpRbw4n9EItImZmNoM1D8FVzNY4Ietyo5qp\nN7snh9UBvbN7EyE+cmC6XEwJsGxPXcJIentOsjTcZ6VwFu9upPJTkLxu5Lgq\n1ZEDUjDIyg7SpqF0smwJYjEF8tl+UKOyDf+Dsc4+yedRNyb+msFB9OCkg41J\n65MPpPYYcFc+BwsS8HrruqS557WYtUM5W4vKEwp7EUXLFwNKJYB8dX8Pubk6\nXl2Ft5FwrAUrYBA7XOcMlSQQp78cAgOZ91YsFYV8B4aMO7DdV87LVn0UzS+6\n+Hzy\r\n=KxNv\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ab5a8448a50a800a66fe1448b6a74a8b4c6ef738","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.3","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.24","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.23","@balena/jellyfish-logger":"0.0.139","@balena/jellyfish-metrics":"0.0.163","@balena/jellyfish-environment":"^2.2.52"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.13.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.7","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.50","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.110-renovate-non-major-ab5a8448a50a800a66fe1448b6a74a8b4c6ef738_1605168192320_0.6216087715219785","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.110":{"name":"@balena/jellyfish-core","version":"0.1.110","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.110","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"alexgg_balena","email":"alexg@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"762dbab498cb071579b26bb5b42f27945b7f7f04","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.110.tgz","fileCount":94,"integrity":"sha512-h3x/DCS1gygh2QbFg9zfsd0MvWUSXpy5TaOtOqeUS0elqgjLndqGkVVzC7JzafZhIvU/hStGaGCuGr5dGBCi/A==","signatures":[{"sig":"MEUCIQD7SA38htf4w8F8xCcrsAV8uGCMQgm6z1QHStwwP1XHtQIgNViPjgKdrPFsFKXg6nTQzgLghYjTi7tX/b1PsMPD+DE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":571576,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfrO2eCRA9TVsSAnZWagAAQhsP/06wTczYwZIPBYvoFMUP\nEa8d9FiojkVVxv7HvHr67oYrvclFENhBYEeff9xdfv0DgAliBYC92PFv9/kO\nICFtURvsS8AWX+6eJXc/tKw9U2Jx9EnH7mxshqjIkLmvbQKtO2Pl39iwn/qX\n/CiLRvIVra3e3wtodsrOnri1HMM+eIDoMMaZ5n1OvoEgno9aDLGc1EKEHNfK\nc8YNiC3qT2xamBGrqKqe5Fz2hDnDVq++nl+Isbj7aSk93vv3IA7bltCqT4K6\nAmTcPDqoxAX4kkbRFC9Vbz0qisdRLVmyIBJ6yxavAMTqnThrxrCLHZoAHyZ/\n1QppggLNY/PK1Iysef1TM4o47dG6g8mGzWxGbwdBxFuWf4b0jNv/8Th2zhR1\n/DcrDjBrJBpejgli/SbvIJ/EITUm7Q8UE326qcWSLgn/xSQnN0aEaCLKGp8I\nYhZpP2qhUsLW96A8eEshD8MDDL+vP2B/JMCQXixom4p87Lw71J6gaMC6IllU\nReol5YSMitx+qgcv41oIIZWGvrW65lJLd8faI8b0NtxdJ5fWy7dyhIn97zhw\nzAThZ5VYnG8lBVjIlGoB9jRhsC32+kJ4hvE4hGLCeRbAHqrKqYDb1Sj7i0qh\n8ze4+Iq2dNNUGhMQ6K5wxbrmYtctj3xEhsBjwKJZLI+8OS8cPQjGyRYw7+C/\nAeOf\r\n=XU+P\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"255262223427f44a6d4a6cb226fd96f3424bc3e7","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.3","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.24","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.23","@balena/jellyfish-logger":"0.0.139","@balena/jellyfish-metrics":"0.0.163","@balena/jellyfish-environment":"^2.2.52"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.13.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.7","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.50","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.110_1605168541780_0.5318383518189811","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.111-renovate-non-major-028efba37a52adca618d56f9350e87102b019c27":{"name":"@balena/jellyfish-core","version":"0.1.111-renovate-non-major-028efba37a52adca618d56f9350e87102b019c27","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.111-renovate-non-major-028efba37a52adca618d56f9350e87102b019c27","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b77a8ed2b20618dc5764b3d3be6f23d5ab3c6f95","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.111-renovate-non-major-028efba37a52adca618d56f9350e87102b019c27.tgz","fileCount":94,"integrity":"sha512-S45I31UwStJkcQPAVtB9lWa6GbOLu6HME2fJFjsK+Je7wo8zzxPQkToS//jJ+CYlk7yORf4RndzA94Wi6C5yEA==","signatures":[{"sig":"MEUCIQDNa/zyVvbcYtwuE0YpF6Fi+yZ3ZBWZs/h9L+E2oUDSFwIgKyadk4de60P8guDzjDAPv849bs2SHwwnxdpgtuudQi0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":571741,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfr6TwCRA9TVsSAnZWagAA3RAP/2y9jWXxkOqddWUMifpW\nSFAV/1PFpVj5MncY1pb13iB8xap5hSVIk56LwHNryEKTs8ZTTIW9m+AAEE8Z\nBTSO8fM0cSi1y/+L8Xsj2Jv5T0IpC5Gq3A8S5tF3lb/JOT9wBtKhL3IprIRe\nTrxmiRCpqhtyRFHrhABLaeePBmAqC0Vl3dAgRCgYoZftAmV3ZaRsRkv17cu/\n01FbU4YYjIXD7kxMSxPa2rWnsEDEzfQTc1F/ah2fsdSa03X+7+in+6T1SLlP\nGS0+cb0wUAl1vhVKmmXh5WxQJ7FxPOjxSF4IgpSqYtv2ZzUMefDU6j/2Xajh\nBLv5isylNzMPX8Ymvd+vIk3C9/K6TN9pdW1wRkDQQLspsyj6Fiq5jE9qmyiI\nXNkCMocbpYYIOjPeuR51br9YqJwzQH5XE0DtQ33H8kZDaRqUddIFgPc9PAZH\nVrHAZhQatvCLeWdB5Rnn/nmSeGxw4F5e6gpYv0m2n7jJwk2BYLCah+uMYs1B\nWcb9TX1UoatetvaGn4Sh00KgrB3Dgw8zIuGZMx23LUJ6fqloO4rIUDTCqorC\nlZwAOaJDZFTVnnKM4VPAmcFl3kTS7DQpqzpd7E9tniMYXAyyeiMCsdfawoSV\ngeESodUd092KaDpviaG9VKXGWey4CRfxZLMiw0fmTXrHjZ3Jq77Vry7YKq2V\nK6N9\r\n=5PGM\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"028efba37a52adca618d56f9350e87102b019c27","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.3","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.24","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.23","@balena/jellyfish-logger":"0.0.140","@balena/jellyfish-metrics":"0.0.163","@balena/jellyfish-environment":"^2.2.52"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.13.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.7","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.50","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.111-renovate-non-major-028efba37a52adca618d56f9350e87102b019c27_1605346543941_0.06375777272803917","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.111":{"name":"@balena/jellyfish-core","version":"0.1.111","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.111","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"edd14ed8807cc0a1b3945aefec7dcb55539b06bf","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.111.tgz","fileCount":94,"integrity":"sha512-txCIJlidyluwqqgMhULFlIIkgbuDuPSxLAhFd5ui7Kj4us2jYkvdZRn83RsyVKGaQ33l9uqAZhVkkxiftI9UYg==","signatures":[{"sig":"MEQCIDFGyBNQoUlCpumPl/q1+lDGsmLgqgDP5MDeBoTqKG+lAiAhFkuORt4VlcjCS4BUizB03XSxGTYlBpXqmsVDsejwTA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":571681,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfr6YbCRA9TVsSAnZWagAAQTsQAIqKxfvS4MIXF8iFw6VP\n+qJiQN5lZUYuVjGGvRWjhSU1hwWOTpae0sFT3jcFm0RqTnqorRatNtrRzV5o\n56P/mTMJMG2PLDf0yePsBXLloc7aAeXi+kT2z12/feEemF9wJI9M/A9hLoOS\nm3roLTcING/fN9kl9JNWfU+IMU8lk0YKZHgMIpzIP1ojyNawiMBFSJ3yLpxS\nJ1RE64WG9aRdeELdzVyio78J8es1TKr6oIptFepwd3nXTSXbA4uRb1ODnoIH\nfk7bLcl5T/U+WvM7t4/zYsB9nZP46xJZW3j0BN+umXrRNNZf14f3VRu/I7+d\nseh+pKdlP4H2NcmIa2Ysvatc4dbPvNiKTFWrVGyJYDpy6ZNILrf4pFLyD936\nQqhqxOKzQkvgIOWhlo44D+LP7s7zE2sR10JkWMNeAzLe6/sh7HIgD7Y0bH7E\ny/LnoF6eBxWYgganjkx5/BE6F1y9gAEcsoPjJnBmiPZKLFB78BNym8t9gvgJ\nP7+j48VkcHAGDoBRJfyiQd5Tuyws4fr6waq2W9DNiPy1v7AlrGB5UHiekJhr\nplbJonHudRAzRAg4j+9pIbb3aXJeiyopJnLkhLmpE9aIVfk39PLU1A8EggLZ\nObmVleWnIdHP4QsQOXT3STJYem71ztbMdVFiZ9Oci41VaM6SoR9yBGVUPW4J\nSJYF\r\n=wEWO\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0f8006c7f40b9620eaf159cff57233e386e2cc94","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.3","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.24","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.23","@balena/jellyfish-logger":"0.0.140","@balena/jellyfish-metrics":"0.0.163","@balena/jellyfish-environment":"^2.2.52"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.13.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.7","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.50","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.111_1605346843149_0.8846238115901304","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.112-renovate-non-major-2ff57097b1cb87449c2676ae12f9e5b0312c6f8f":{"name":"@balena/jellyfish-core","version":"0.1.112-renovate-non-major-2ff57097b1cb87449c2676ae12f9e5b0312c6f8f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.112-renovate-non-major-2ff57097b1cb87449c2676ae12f9e5b0312c6f8f","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a844ce31f8c08cfc1e6757e1289b4d6d85d97322","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.112-renovate-non-major-2ff57097b1cb87449c2676ae12f9e5b0312c6f8f.tgz","fileCount":94,"integrity":"sha512-LCw2ZX8diKjE8ke43mkn5Zmn8cWadH7fXRgXY1nyk24BHe0pQzoh8DrD6tEALTsc4hEyx4mDsj3Lr5pDLLW4mA==","signatures":[{"sig":"MEQCIBw9DTPTzHpiiSN8hjffe78G146pGLFFsjCMKf/REGenAiATI8jY6DMqo51pwzfwbJ2INjSxXRnBNuTQl8DnqcEdDg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":571847,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfr7OQCRA9TVsSAnZWagAA9gkP/iXxG8lQHIIVNsJQpgQt\nR54rdgWLEuPaudOjy83WWOttmoefudISi/uekMYSXRdiy01huOa9+rCj+6lo\nzWykQz9CMmJew5GIDs7Rx+2L73LR67UuIsxiaoFzMnKI5GMPVWdXYNyjJBBb\nGf/2ESt23su8ObOfGg4ciWFPZX19QwDV/E8WtYGvCDBqEeKDguVsBcokHVwL\nXLmGKPBo7m9awt8EF+2UY2I50m8w5nuKaz0E3rVGpe+604hL6mOkH6JQwQnT\nqCzxyEsDRtlM6W7yzGwW4WP/Z2JExP3RLf2kjdyhHb4CXo70qxaVrm5Zez8Z\nZsefWbACkYVC5RYCZvCBieNB2UZa3R42MdLN7KJytJig8HTKx13WAWAR8f6m\nIjeruBjArlWBQdp1tif/vsPICCcoun57aMMvKKYuchuFcZmU6Vvc6831NL6G\nwsptCI2euY3SZnpWAmrx2jlcxBY0AG2mh1pHxSH+VeoBZnpjGYmqhKC4S0vY\nTybsyCYCVsUbXmH3ZXci+mdX0BuiFniButzM4PBSCYaYjLMwM7wvaLWghq3W\nCMSdt4uMDzLVt+3PoG6qU+JhEiCPyBQWYDY5a4eHYwPbpkTT3uFIXDdy0yI+\ni1mbS3db1EGRaylhMJg6VgxXbOpE+v0bM5jjph4p35Lctz87MIFB5Fr2y1xB\nwn7O\r\n=+3Ax\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2ff57097b1cb87449c2676ae12f9e5b0312c6f8f","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.3","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.24","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.23","@balena/jellyfish-logger":"0.0.140","@balena/jellyfish-metrics":"0.0.164","@balena/jellyfish-environment":"^2.2.52"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.13.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.7","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.50","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.112-renovate-non-major-2ff57097b1cb87449c2676ae12f9e5b0312c6f8f_1605350287810_0.5508856340382215","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.112":{"name":"@balena/jellyfish-core","version":"0.1.112","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.112","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5c2e4f9e9eaf2740e6aa110004eb2957da4faf7d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.112.tgz","fileCount":94,"integrity":"sha512-QSIycp7nuBK4gV5wUqmPK+0asqgmSyKp5rzUU1vnZrLUWFgiM5Zim+qJfDd6vK0UROsQuY6mFeYOKg9b37H7eQ==","signatures":[{"sig":"MEYCIQD/L5NC3U4xd+N/FroiU6j6L2gvRBbPwhpn14EmquILSAIhAJdkSJZPgoccACv7gOUs3JHAtXFa1Vw7Mgm43g96TwHf","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":571787,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfr7SuCRA9TVsSAnZWagAAxjYP/jScs0qGg8lvhubZBorl\nuqzjEi4ah3siZTysbQhRnkvIznZ8n56FVBjlCEiWb+Tc2OTkBZvpJsSFum04\nQzysDeXytKruS27PtkEiitk7dKu/ffYXooH1FlqiN+S+ZcdKpuneD9pSV2Qx\nCW3NuvF/825uiy4H7ad17bh569LfkPfL7JZyvGirw34IrwoLM4mtRwkOuAeJ\nc6VFr8PR8aMHOoRzd/yJs3VEfuOvW/jhiN2AjWYOajm7p//zD6akVkqVObbj\nfaG5OwMOk0tuS53eO3RcuaSDC7N4xGfsxbgO/4KLY/z9LphuCQyDYDesuLVz\nWRiadr5jzB0Oa1PbFynLuc2oLl//hfNgtLzGYS94p57fofCfrSnMIQB7NaFC\ndz4HrupKK0Qyt9C6F4OXpm2BeysEtnvtdMSbfmnRsScC6fIDDyjXv2Wi820R\nmc/bNtDz5QBV2DH0WyrwmajUK+LsECsqXsl2Ey4cjAcPdeYHxQNL6pgMnPLy\n1uAsGsQ1KkmYJbytJZLgbPoaLgugLXBJ688bA3wSUqGldB0IE4PhGaONJJ83\noS3oQ5RgiyGkyufduAfhIe+gWIuk7r8bdjOqw5ohs3SiffX9lkIk6+bOBXZl\nxmLBuaUGYHQTt5Dyf3CQdXISjiRa4kxsU5o5WORK3cFvIARQgUt8UorYzMfI\nmnOq\r\n=sXk4\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"57a5b383c9d8d961a1639fa40179f4e6d9f5d0a5","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.3","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.24","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.23","@balena/jellyfish-logger":"0.0.140","@balena/jellyfish-metrics":"0.0.164","@balena/jellyfish-environment":"^2.2.52"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.13.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.7","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.50","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.112_1605350573882_0.40884390056227393","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.113-renovate-non-major-ddbc6de5b54c86783741bc147d004759da4b9e81":{"name":"@balena/jellyfish-core","version":"0.1.113-renovate-non-major-ddbc6de5b54c86783741bc147d004759da4b9e81","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.113-renovate-non-major-ddbc6de5b54c86783741bc147d004759da4b9e81","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"11996a55b3bf6de674824afeaf03cddf1cf196fc","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.113-renovate-non-major-ddbc6de5b54c86783741bc147d004759da4b9e81.tgz","fileCount":94,"integrity":"sha512-LNBxqIZNWm7onO+Nkz5KLZCHhROYpUCdE7u6CNbzvoAELzM30kXJNM7ORnZWYDyunYmSJs6L8pJpbUQ9vS638w==","signatures":[{"sig":"MEYCIQC1C+n82r3trG7EzXwkGyyQITiyCuwHk3RaZELuk7mLTQIhAORsvtFWhfi6zFTXKQY3/mi8TKFiUyCpbhgGFzFYDjNB","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":571946,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfr8fiCRA9TVsSAnZWagAAXHYP/2o4nhFcYSZ/JJajJdVc\nfXBozXiPY/yRemXhTsccjK0i/qaJatF7QKCsC0+KcJqW62GRToxpsjxlffxr\nENgM+3puE9CEKP3eWjQcET8Wla3M8+B43Gv6E2+S0Im8kNHHxgCKJkObjgnj\n75pIWiLcw0+k+0eO7e9vILtCwjqS+j114Vu0qbtNWHUpbPm5OQbkqesUx3Vo\nXr2qJBsOxXD/y0VDhGess/Ab2x2s5IvY/OHvA8gQfbdcgoThGSKxOuhZP9zv\nUu8rymBRP4slgS4jk9604OyFD7+s52i+OFwJe41paLaSXtG0bSsrpX66D4zj\nN8eP9e/rEpaJtlD54ck2OLLWMUsA8QpKH2DMbBy7/Wu5otDQyV9VtMpkeZCn\nsDIcjoAzT8RtYoTR4J1X96rXRUr7eNqa2qaNBSlmY17oY2Tiv41DWFBl2+hA\nLN2GXr+OHpXiXspMjfAC/8PvbpGwp4fkFo+38uF2VlcF4WpRaF3YCubYrUhi\nI1ITyCPa30w9j7cyp8TJCh6bTTsg2cluNgJOJbInMmPv3x2Tmd2nyhDGWHKU\n2I29iPT28jLTvfDUKfv9AQ9ZGfKC+kN5w6PXTkLHNYxGG+5NwUbg1GBly/BE\nmU+ReSjCB1+TK+zo8QuLhliBTd23wfMbe93vp1Iv0vTviUnmwaFVVVIWm6Mj\nN+eJ\r\n=FqdS\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ddbc6de5b54c86783741bc147d004759da4b9e81","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.3","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.24","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.23","@balena/jellyfish-logger":"0.0.140","@balena/jellyfish-metrics":"0.0.164","@balena/jellyfish-environment":"^2.2.52"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.13.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.50","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.113-renovate-non-major-ddbc6de5b54c86783741bc147d004759da4b9e81_1605355490094_0.5871973893017242","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.113":{"name":"@balena/jellyfish-core","version":"0.1.113","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.113","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"af2473f87b58c42b307c2a0942aef578ed024860","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.113.tgz","fileCount":94,"integrity":"sha512-L+u8VczgUehYZe/8a6zspjnCLD7WSYqF89sSUwScmZAEg4FTXkE+fZQTQnpyUIBvbzKoIOpKsNRChbmrENSJjw==","signatures":[{"sig":"MEYCIQDm1714E7Tvuxjm++WmAFfq4bvWC0OVCAWeHRNxnKabUQIhAOUXN5mFCgpwqpsODTxhRqqd84ip3Ke9GQu8M50ea1qd","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":571886,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfr8mTCRA9TVsSAnZWagAAeRYP/iuUCbn0KSMHItHGL5sr\na4ROK6LMiR6jAF+42aqDSvm0s0oJBv+DQIRgRlGdwtAtIzoBg7Nyqg5pDfVH\n/sPWjAakxhcnUoWVF1kVKKEE3cSdRqRXb8BiRYoV/PCEdx8cxoiStI5F9cdc\nS3P9JIOwrcZX855CjQoGKwxD0ZSQwr2W5MK80zXrDSQ9UFO8SVQhEbCrFPMy\nTV2N1bGsZuTvsASNfnFCqqgtKk3zmQwNDyqEgOYdup9c2s1l5ED3C/4J6W68\nbnsGLvIjVEyaX2BtAlxaJcmJA4SwD/+D+gQXzBJt6CMf4RVpZWwDUmEP6kGS\n5tQQqIC8N8NK2s+0kHtLRhgpydFfIrQ//UCFyKWq3LaTtk84UP5h3RhHqgf/\nc4N0yyt6Dglq9vbs1Zsm0CjyFPhFUQ2y5uoCX9tkZ9wNzuboxU4jhLFxzYyr\n9emrL7FAUk9cHe7uc2KoMXCTtZ7a7/6ZRUXZ+MZWkFfoiJhAOJ3I7yqKEsCI\nHAH7fvHQapMbBOJFzxy2Khz1Rp0+2j1j38QKV9toqDej2Aq8U1yXStmu+TqU\nS46Qjlv1mfLqKEhg+iSw3iL8NOi5qVCpEiud6u7WClG4jbvsP6bzFe0tH142\nU6JgqmfPjlmrKtTwe9wL93Hp1KXtE4Xk4CCGg6jlBOctwdu7vV/muoEvb4TX\nkH+1\r\n=edhd\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"347fac41d826a44876ecb875f90f1be11fa70e61","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.3","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.24","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.23","@balena/jellyfish-logger":"0.0.140","@balena/jellyfish-metrics":"0.0.164","@balena/jellyfish-environment":"^2.2.52"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.13.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.50","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.113_1605355922735_0.3894250475611116","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.114-renovate-non-major-9ada229d3211708b36ae0ed69c8e97acb2215ea5":{"name":"@balena/jellyfish-core","version":"0.1.114-renovate-non-major-9ada229d3211708b36ae0ed69c8e97acb2215ea5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.114-renovate-non-major-9ada229d3211708b36ae0ed69c8e97acb2215ea5","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5d9c5aeb816c3c265b1f622d02ad6017ef46cb5f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.114-renovate-non-major-9ada229d3211708b36ae0ed69c8e97acb2215ea5.tgz","fileCount":94,"integrity":"sha512-fOZvIKasY8XTsqxElJofPl7Tgc6VkWFxS/LeZlAzQPkL9OEGPAvZQzRpyjWThDJDk56zYIdA5DNb80hAuB1uuw==","signatures":[{"sig":"MEUCIQDE5wpwSsCJa+RqgrBXaIcLUYOvRU+SsUcJjnYsoI4jdgIgKoBUynj25SI7KTTQwG+yyBn6fWTh8Z+ue6ccqzDpP14=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":572013,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfr9iHCRA9TVsSAnZWagAAnzAP/1Gsmp3rIADcIhCQZBuR\njHyscMT592QzIVXqRzuwI+Z/ubaz8HYieTRhSSefTax8aH47qb8GnFFeNTLZ\nyaS5GwFJews66rETS2X5VD/s35YQ/2jFnRLd9aAfyEUn1+b63llzTc5tZSzG\nQN/q2kUT+9lXDxGTsKTrAX4nCKOFIT4JecFe0XgSjCUe5VRDPtW+QkScFBXv\nSQi15dWy0Pbj58pQcm6IueyfN8czxlZ82oV4fLFEdFNyXmsz0/uH9ufltSNJ\nTRgklBMEXh5g4/g1+V+hI54C2pDLUn23Y8tTiN6KUxX6NpU9xjdISsnY+GJq\neAl19O1V5OAHP5Pg9nuVz0L5N0ot3F5s3JidftNZwXn9TcwA77B3hQ121VCz\nTdjBWA0kHPeXkiNCn+Ej7QKp1YywLi4N11Ynk4XH1xjIPJA6JESXfek5TLif\nz3I8Fzw40aWEojtbUIcq5Vq3ESLbly2+QTwiZHxChSzmQHCzmr8mAB+U4/o3\nPBLGYnzppOSxFc6+p2ABriRMRxo8q/YggHvGwGvqqcmjbJtpwhuHtH3SJKzP\nBQ+A6EKVUsHjHN1mQgSk2G3cprlTf3XucTUikzunpaiBx++fVmpWLIM8QYRJ\no2E8DSBipf7jhzKV49fw2yv4TmQM46aOqs4qb4TRJY4Qwf+J8kOvIuZi5MEv\n9mzz\r\n=RM6X\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"9ada229d3211708b36ae0ed69c8e97acb2215ea5","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.3","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.24","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.23","@balena/jellyfish-logger":"0.0.141","@balena/jellyfish-metrics":"0.0.165","@balena/jellyfish-environment":"^2.2.52"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.13.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.51","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.114-renovate-non-major-9ada229d3211708b36ae0ed69c8e97acb2215ea5_1605359750535_0.5446867251699103","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.114":{"name":"@balena/jellyfish-core","version":"0.1.114","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.114","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"fec0674f08fd6f5308abc687339bd064fc689b01","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.114.tgz","fileCount":94,"integrity":"sha512-IUimnVZDOR+5JOzN8leviy7u/Dk4/7/BVTB6Aox7svzC7f4NuRMXmOtHEnX3rdATyb52Fj2oTzxQQhaIoibx3w==","signatures":[{"sig":"MEUCIB1zGuoJgPJbtK/KFDH2DRjtslTCe/6Ptq6pWONrK6KnAiEAqBzajGrZKq54Cs4SEuPF1UN1CauAdhD+a1RSGk3PAuI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":571953,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfr9nzCRA9TVsSAnZWagAAHXYP/ReIUHLD1u+FTdCnD7dt\n+6VOZ6KxiJlwLCDLJKDhoWh0iyZwsht2YN37Othdu9xcjgdmx88HmyQU5YQk\nFDw6MFHu6SFKVMOVFSM+bLLvxZcJQaAgga3kIQOBG91EJxx8xXQdFVlwmvhl\n0AI+RDqTSih7vJVkGmUCy9Qi4KoKCQfYPj4+shOBhifnD99H2cLa3m/FAo0L\nslz6i7FC6oBW4FN5re4LhXafLSBHsVA1KOl0jdvaoOrp7VrW6J82IgBycQ12\nZmLSfC8vzcHGNOC+oRA0/QeLcuYrYrDJXd3yBxXtC5XQpeuI0HWoq6XbVP4Y\nOMuJzqMVwUB9WC3HOCXNpMPTNKuQhtMXxszivnJzy9vzhBXtgkA5adGpXkWZ\n8YN3k6rWKE/na244IaB9mpAScvFtFxqhSwljHx19cnOReU0zlNn5roFCYU16\nATELzQoLKlI6PYJ6eVjOoynVBjRWey/C1Wb/D7VGmU3IwEKXIFPU/Ud13Sw/\nTS60Q+zZNOU+dHJUNAYseBljRN7FQNo6c2rwIJjkqUqNzQDvV9dvqynSRnt7\nSrCvnnjByh4tzIt/0zPkoZ46kmSutTuPyHXXQ4lJKxfMCF9cgDwLRVfsc3bm\nDd1dBaMarOqAw5xD/5TXGAew9srFfcnYcLZIklDPg6g8WtUk6MXyQ9/fSP+T\nGFRB\r\n=xuTi\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ff0b2466a92ebfeab01d8503aed8f3f341adb20f","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.3","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.24","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.23","@balena/jellyfish-logger":"0.0.141","@balena/jellyfish-metrics":"0.0.165","@balena/jellyfish-environment":"^2.2.52"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.13.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.51","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.114_1605360114761_0.05405423822930966","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.115-renovate-non-major-aaf443da4c885ab8d28451c1bd54cc10167b1b43":{"name":"@balena/jellyfish-core","version":"0.1.115-renovate-non-major-aaf443da4c885ab8d28451c1bd54cc10167b1b43","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.115-renovate-non-major-aaf443da4c885ab8d28451c1bd54cc10167b1b43","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"82a288de205f9911f30c1cbf6647c70331b59e2d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.115-renovate-non-major-aaf443da4c885ab8d28451c1bd54cc10167b1b43.tgz","fileCount":94,"integrity":"sha512-TtoMpJBdXgDMHhSh90YKwD+jAd3w6cbwCDNJScAX1SLJDUUQoHxeDD2i8gRqtveArvLXcvz5vG19m9/yAQtMWQ==","signatures":[{"sig":"MEQCICZTHiaa1w4QhVLAY8cmVEx/LebPMdbzyxq7cuyk3MRIAiBMVV3fIcAGL41x1EgbfopyNsD6GhM9hasPZpeX4BWnhA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":572080,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfr+a1CRA9TVsSAnZWagAAUk0QAJ/b9fVb+V/3edrFCSCc\nqociDGn8Rh4avLt5krQNMvzzxC1VDpyuyMHgZB/MYyARMzizu89PPO1DGDxV\nf7G3V6G3+wLB8Lbu7srZdY93i9u5jQ23fck3csEEM5YGYm2J5x46gdZBuVkS\ndc+RRVacxLiVIL0CHzSpr5sgMcuvq3PkRVQo1AxKHp2vJFDQP3EcRnpyV/E7\nDXqHSKv3JuVFvGPphufbKW+kc7NI0tjzfXR7JIejOdgvezuU+Az5xHRjlEVd\n/in34/K6FU+01HTlzTJ5A5pXaG6kPj/tVcf1YrX7cXlh2X9pwnULOkhF4GEf\nitS2lMpUw1hsF09YSZchUv+e6DL+DyCdn+AR2tPnDLkUckLKxKrRbs6EWqfD\nWlPEmKhTFQQTNryJDz1pRcyrTnaHY9QerCRYOXkTKf94GLc0DNl8FyRLjwvW\nt6VILPLH/2pSojAk/bJshadu3g7AOHN2lesWmGadAMPqX8iB+rfCtiWWVL0m\nu64tg3cuEvIV8V9oxX9if6F6AxKYcbizj2lUjK91RIdTQADsA7WhewFybbLD\n8ebqd+K0UnPBuqrRxA9OLyO3kLgdS5NoyAxhe4YWhG7d8pMDknJLTQdFSgXA\nfS1JZ1O9plrRaCM3GRn/274bS/6lVl6zrjrtRMzBS12LcvkEXRF+I1JDk3iN\n8OyD\r\n=s7Ps\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"aaf443da4c885ab8d28451c1bd54cc10167b1b43","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.3","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.24","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.23","@balena/jellyfish-logger":"0.0.141","@balena/jellyfish-metrics":"0.0.166","@balena/jellyfish-environment":"^2.2.53"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.13.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.51","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.115-renovate-non-major-aaf443da4c885ab8d28451c1bd54cc10167b1b43_1605363380434_0.8528387670175468","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.115":{"name":"@balena/jellyfish-core","version":"0.1.115","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.115","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f6d0c5ed24d1a0a87b3c5f761f842efc2711f8bf","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.115.tgz","fileCount":94,"integrity":"sha512-L0B01cVgy4HATxKWmspbjEd4guxw3RClXqreHJdSdjcJYmUYBov1Xz4a3wYJnAodBlH2obhpW7M5QkO7HSEbhg==","signatures":[{"sig":"MEUCIHYbW7oY3qmGs6zJgbxTc3QDj+kDDMF4uIdzn2yPmsU3AiEAt8tlyglyeAQwRGSZcJeGv0mWC6Qrlq57h/fwG9yr18w=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":572020,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfr+f4CRA9TVsSAnZWagAAqkYP/jfC09X9rpyK8eYrZM3+\nCg8R23jwYF02uQ54RV8Wh19FhDwAyuQrTdojIaOyqkxmWiLSa6iKvRR+oXwa\n1AqeTZzeGvtcbv0hnImzTdlOV5+2o6oWbgo0ktkD8j7g+SMcqqXiG+C7WrQs\ndqtjiUVkDbzVQ48XUe/O/uRlvl8ZWa6ULRwt2Dj6odEoRRBCgANJ2Mj6GrO1\nAyeRbi3c0tPi8YTM/Z0p1pQk+1u+pdo0vOLSHMlT5qkmXyysjaoZi6OPDjEs\nB8FfU1qQ7tWsNbMGFXf+RTTVjq0kJducKFcOyFuCGmnoeDQWzlloLO/c8FIW\nAY6MOXHjOplkasQ+RM/JrrisVL6KW+KpxrM4J4OrphWnAhvUKzuLBVU8kn9b\niGGh43CiqfFA3c4oYXd/TQH+g9TwFNdK11GWbLjDx18n/4NvWNWOHIxUzHu2\nSp4S3/u0gD7QFHe7jQvEazOHk5R4lZIzg+lHn+o5lSDTKAnQ+T6iFUQO4ZIL\n2x6/D1wcwoqX0a3BibZXYqkxmYYvEbn/NvBk6XUiR56L3mchTRoaNLXN8UZc\nkxQDDPs7ZjgyKTg7KFrLvYuwobKn10I+tEIRWVE4vPwfLf1W0/oS43yYMBiV\nCw0LX/sdYLS2YmaRfvZccklmFNSk2s/wtuy3edWdvWpAgNnZV6aP3qUfzYvS\n8BUa\r\n=JVOg\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"1e158860b4839a1463bb94b802d31a3f6ab3af01","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.3","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.24","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.23","@balena/jellyfish-logger":"0.0.141","@balena/jellyfish-metrics":"0.0.166","@balena/jellyfish-environment":"^2.2.53"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.13.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.51","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.115_1605363703824_0.11470850928115461","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.116-renovate-non-major-546cf5b0dd76ad88a85bcc78e49d3625a65c2f40":{"name":"@balena/jellyfish-core","version":"0.1.116-renovate-non-major-546cf5b0dd76ad88a85bcc78e49d3625a65c2f40","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.116-renovate-non-major-546cf5b0dd76ad88a85bcc78e49d3625a65c2f40","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"7796fc2bb26526d7f3d57ab5267c6166e7cc34c8","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.116-renovate-non-major-546cf5b0dd76ad88a85bcc78e49d3625a65c2f40.tgz","fileCount":94,"integrity":"sha512-HZFMwnuRLl6HJCg0TkxkhI/8xRv8dGJkgxaL/F7XKOT5uKxi4GDjQ85hJ5oJN6tcTR/GM4VHp/4FyuuEsOCe0A==","signatures":[{"sig":"MEUCIQDEYKfi8dQ3S6E42J5raesgjlG3r7GFHdjkIFiwr0FyDwIgUsr6OQXyY5rqD/FvyfBkk/F/dDrPk9Q8Yczrslp16g4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":572147,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfr/Z9CRA9TVsSAnZWagAAd28P/0UUlgruC4Oq6MpkrdCB\n7a218QiEWp2u3KKkYDScgfkDimIBXzmLimgU+0mUIWVH/9QHLi/LglwTx/GD\ns00uRD9calXiHJC02lhhxX7mD/tgmD7l5m9I5yLB5Y0qs40oTaKe/QJrwAGI\nIoStvggj0msW6/dCy7LhChQTlYBvYwimIXDWcclqLKuXLi8FkLuqcLGKHkdU\nrqTDeh+xuS13hjW2Ho7pSxdS/QqZskBngj1bdIQrIsUW/+hMmRIBgHQ++j+j\nWFk/XpdqQvxUCUCe8Bbu/x6eRuDARWVmGuvkAgn8BS0B2wNQdLjdHY3n+B18\nzwcbRMjJ0dmsPOANd1svns9Ybs6SWDPTEgZJZWkKgORPGkKQXeYP7NYK7+85\nE1AAyHp8emBA3kFYRRFL59pb6jhpAoQgNoQr8Mcc6KGBhzqkkFPdC5SJUkWr\n2JwqHmX0pEwc0j+rU7UK0701WfEAMBDX0bfHiSG5IvAVyTYKCT9Rif3m39B7\nUlpNx4OMSiwichmAQtViwT5265XMXb0ubIDXVPIIudgF/lzan27mFbW/3c53\nYHdtNjqR/eBMtEQD6JMRuGiMPdOxa0rv5ZU/yC6a+/4h/eXfl4eEEe3PjoBr\n5GDyRW2uDs1ihhjzw91vlLhlEiKzs9alDBCJ/qzyodkoCALM5bp9GOECkSxL\nTQUJ\r\n=5Avr\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"546cf5b0dd76ad88a85bcc78e49d3625a65c2f40","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.3","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.24","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.23","@balena/jellyfish-logger":"0.0.142","@balena/jellyfish-metrics":"0.0.167","@balena/jellyfish-environment":"^2.2.53"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.13.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.51","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.116-renovate-non-major-546cf5b0dd76ad88a85bcc78e49d3625a65c2f40_1605367420785_0.2686228616471471","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.116":{"name":"@balena/jellyfish-core","version":"0.1.116","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.116","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6a0715e83e49a73e3d652bdfa73a6540f62e12f9","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.116.tgz","fileCount":94,"integrity":"sha512-o0qxI2RsrZVSLHYRSiwpEFCvuTn08QpOuGg75HigOp+Y1Z1XlwERtkFKlB3kZTPCZlft34jA08CFyAu4ycS+HA==","signatures":[{"sig":"MEYCIQCsj4je5F5WCE5nuZjvqfI/07iKGU7FG1Fz3VCXoNvQBAIhAPhin1fBpG0VscKuqe7cu7qPo+pFlaLTWjepRBCOhOYn","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":572087,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfr/efCRA9TVsSAnZWagAAOX4P/133kkQTgFYFLhJhl1Bk\n+1n8CustZMXQEIeAhVC/rwLpAfa0S0u76MlYez7/v4zlLI4fsTY2cqN765LN\nkZJ0JqRGURnoMiDF2lan/jnGrQzSR+vXF9ubW2LH+KgQW0KtQTDZ2cZhFJSE\n2Dn+t6iB9TSMCUc7uJKDNFvbqfJfhplqm0jbvJWjc7rwpRYPyS78LX9iVCqK\na0DcYAJuwXYGi+Dn7YAeuWVAVOQa/i2GvE0/6hcmU8Z3ELdFSzgl539aCyOl\nJ6RjyU2d+tkTfaLQAgfZt53ZrgaMuLr1fnK0ZABeufRmAFHpAPKgb+c4lMMy\nDqNSzLdkxPo2eZcj+GuwivMPWDRAz4VA4eCH4gKXbWKF/5g7k9GJxYK3Qmyz\nwjvAWeaMQmzK7spCLe3/bhgIkiL8oLW7+tC/PV3hc2NPZFXvYfDJVRiONc42\ngaHEImGGP8dKfvE1vfLlQoMl1HQqhcQ/fsHIAkeKXCfqNyvGqUXTh3Pj/AZt\n8qgJgi8/FUvOZh0IjvfqlIFyVJCykf+9ROe61FW/sEn9C0lHUOSqjyriCDGi\nFwppLGrV/SzNZYHYGrbxx0RAwXkwuTlobidbmtw+X0hNLptmP+VRuEJgRSgb\ngyy36XK2+gZeLCOOZrZ8jpuTbN7RIjCDHJPMl0kD/8ecli/A7H7Tgc1Iesmt\n90fK\r\n=qn0Q\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e6a2488a4d15b02431bd8a47ef499e1e2aeb05ee","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.3","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.24","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.23","@balena/jellyfish-logger":"0.0.142","@balena/jellyfish-metrics":"0.0.167","@balena/jellyfish-environment":"^2.2.53"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.13.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.51","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.116_1605367711075_0.7386480401023865","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.117-renovate-non-major-7fa1cfa84a106ac421d5079e3db3c4d9284f4135":{"name":"@balena/jellyfish-core","version":"0.1.117-renovate-non-major-7fa1cfa84a106ac421d5079e3db3c4d9284f4135","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.117-renovate-non-major-7fa1cfa84a106ac421d5079e3db3c4d9284f4135","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d3699c669412ccf2f0427830075354eb9b65a7a1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.117-renovate-non-major-7fa1cfa84a106ac421d5079e3db3c4d9284f4135.tgz","fileCount":94,"integrity":"sha512-nkQ/kzeR8yB9CnQNtXncvGkmxfRTaSqmCBAMBMSc+nOnIBvElaWE5rcA9ywVSs9XyhF8h9Q2skelBLg/4dVXPQ==","signatures":[{"sig":"MEQCIGZH4/GgE0y5IXRRwiyN2oDTMwy6GX22ajAb8EJEJsQ0AiAHLDc9U6wgSMbe3zvYPOmxquWkhfeBcxS58fm23DYeXw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":572253,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfsAMDCRA9TVsSAnZWagAAJQsP/0lc8hV8w0vMoqrDE8D4\nQoqDM2cV+ns3/4zF31GPne310cvVu+mt16R7FWrIP4lWhL3Q70N04sLd7aLK\nMpRRHpIXbWSiPTP0T19JkOQ7W64iEkeEoWRl3sRNcbv9zrIQkcZbwNaPPR7F\ngm39qEv0dpY/ZIBzuy02v1hjpp54CYLwyVZBfgCuno131/TZxoLP4Ne10KD3\n0+7vHpHdlXPh4vqYFJ1Ppu6GQ5qoOTugmLlqLRAen7N3oKTZ7HFJNkm/P8Jw\nDuyZICZiMwDL5b3dD6bo4Mn+xSliFpZY3PJ8EUFOdU3dVHKnzJytZCXwY207\nYfk3JVl9XgwSMYaJqE98vWSiOsW9bfO2FI2ezOwm2YmpPRdbgJSAx6wJWNAx\nqSRJKwLq7h9mZJGMX2YHnMBmWE+C/uZyUP/wCo2wFi/7H6EFGZ3iD9ESFbdK\n+a5GAb0KrPsFUPubenO6PemaBMPfjnD9iHipCly+j0tEw0GdjItNeky065r7\nRl4LhAFmRYC25Ly/009SZz+ZYC84K8TtUVTEbOcyf0LrnZlETdDdKOdGuLli\n4v9oCSKmxkxIYFRemzr6iPOss32qJj4K4aNWwhnLXhSKWfLZYj8YoFDNJoJS\nOEiqwMhvlOq1EMTwtvC/5YROP4X6WlhYFS9K5Sr2deRYlfSb/W/cwKKjPbHR\n6qAs\r\n=9H8l\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7fa1cfa84a106ac421d5079e3db3c4d9284f4135","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.3","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.24","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.23","@balena/jellyfish-logger":"0.0.142","@balena/jellyfish-metrics":"0.0.168","@balena/jellyfish-environment":"^2.2.53"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.13.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.51","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.117-renovate-non-major-7fa1cfa84a106ac421d5079e3db3c4d9284f4135_1605370626901_0.9562890554740144","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.117":{"name":"@balena/jellyfish-core","version":"0.1.117","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.117","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"978dbf30aef142ab13db85b17a74b2ec7867c96a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.117.tgz","fileCount":94,"integrity":"sha512-HBdXKrFyPAT72nWFSe8LWDL5Vi2EaZ7NL+cBhQh3yUvn/01CX4mwPBAWm9LGQnHbFSzPn2SB5Lyz4l7jgO+ATA==","signatures":[{"sig":"MEUCIELjHyePC6Mp9SOuKnECIVONLCliXJhBIh8iGLIVxoAyAiEA5PzwPG7Vp1XAfbiLIi87ezlkOSq/YhVbHJ7PUjhep0g=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":572193,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfsARACRA9TVsSAnZWagAALfwP/iXQxiSQC3pc0GSinrrE\nZqQlawNkoGcaGtg5b/glfk9Pt4cN3SSx6Px/q3zuf+EnYZxlGzp4XbNKB2I0\nIxY+l8LjS4JbBsjAPRbM8hxf02vvqwaAuXztSWGscd5R9bhkZmH+ymC28ER6\ngjSacm/TjJ+Nxhaqy72dt2EIIScLPPuIwhUphS01Sv51mFQOtLwemxF2MFbj\n6godEujUJJoWzXoOKsIblezoQuXd/o0Txv3Pnmlu66k/lYHsWkLme8g3LsE+\nb59FLsTWIhwKkq0i5lQruiwndAISTHBdeiT4UgbNnUfLQYn5XUbzRRdGER5h\n63FfN567jelILA3NYdz1+Zd613t2tdRw7sfAft4GW0xrZSHE4HAW+MtOPykE\ncxRU7F2mai9yk8cUJ6pPJr1tXwQfykjMUfs3Oqr8itstDPKZLXyLCX67tkzw\newFlb9TIbMWzRZQKyJhbDYa8/M832qjCu5mx8Nr6ePTSEHQ5io02rqp7AC8U\nIwS2JwxGd8KnOf+IkTNbKcUFFRZkt7n774gFqKng8Vl5TuZtA3LQUld1f7RG\nidXDyRZ64Gb5cmcK7uHKnJbC3vbN7b1/ndqusmAn6Sy9Oc2a0tMbvDOioL58\n+VQ3z8mGZf4HJzH3hISG+3FIZGh1PIIuZt9fZGEPotTKMqR2yvZ8w1IXby5x\nKPMq\r\n=yeFL\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f2cd8739e473b4b997cb9f8b27dbfe545995a3f1","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.3","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.24","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.23","@balena/jellyfish-logger":"0.0.142","@balena/jellyfish-metrics":"0.0.168","@balena/jellyfish-environment":"^2.2.53"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.13.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.51","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.117_1605370943927_0.7181509826688752","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.118-renovate-non-major-2a3fc367b9bd1830cb35fc02037a39d9e12a78ae":{"name":"@balena/jellyfish-core","version":"0.1.118-renovate-non-major-2a3fc367b9bd1830cb35fc02037a39d9e12a78ae","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.118-renovate-non-major-2a3fc367b9bd1830cb35fc02037a39d9e12a78ae","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"87bbe707adf385463bcf5a9e6b2e07aa613f9c32","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.118-renovate-non-major-2a3fc367b9bd1830cb35fc02037a39d9e12a78ae.tgz","fileCount":94,"integrity":"sha512-0ZR/aik2fQhVICy4LYAx0XXrj3uMLzuWWx4HQhbJB2lg/IUJthg4M2TMjN9QFw3Af5o+Uw3t/bBlOd6gcZzrDA==","signatures":[{"sig":"MEUCIQDxUAjdl7yZ83eFxpamwkYBKGg1gVSjiXRJbAKzX8OL+wIgROh+aLU3m7RyUopC/nNDUzPhMiNFPoVMxw13cshqJUU=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":572355,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfsYyyCRA9TVsSAnZWagAAixsP/39Cm0kEkWDrdEGFRNvN\nD5jb5MDkwf0foEgdqgmvOa2hXj3bPK9T3hLVsipB5fgRPxNiYFBEYCq96mdo\nFgDZzbBUoojhfspvcLfWN3MRC6caPZ1NkhuUQvfPEecxunr4/LLfpIOnxHiY\nKULPJB4e+pZaZQm0G7+FnsDbxTMo/Eo6ZxIBKlBvBX1gFIj3PSyjO7ypzCf/\nrPEzVRlAm3l3ANcTSQC/S7TUzFy+zSJjsrkZLVQjEqU3tlzbkyTTAuwWcmc/\nOl9dlPvxEFd8Db7vlsaaFyMF1+6Zm+WYcuFVTmGs0PVaPauFuSLYo0MnBy0E\niVx5JLihmUzRYTN7PpK6sZ3G+dMaqx60s7unE7jri1/lbtsOQrfX9ZRJeJ1h\nLd8aIXK0KdGE2GXDVHyyRXrvPsZZbVxB+go2ap55gZtkhlqGrQt1Bn8jIjre\nG7uc2cROJFM3D0T3RJ4b0idbQ3+TXU4IHdmjQmm8Lg1oE3RfkHJG7oxO2eXU\nSbAQYs0slMgi38PvZhPO0MnEILsrpTQbzau+Vp4YrOoIwLn8cR0u39RGFS5b\nyuw7tYGI1z4HlAcYk2uHvHbNxijoQLjGTUm2S/QuKWAj5reAPhhTnHZAH44k\n+kVY4WHYuSt3FBsELEr4CsPdXfAfynI+rLdpld06G7X8mS5Zbt7YzyxvKp/t\nRpPA\r\n=k5Ul\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2a3fc367b9bd1830cb35fc02037a39d9e12a78ae","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.3","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.24","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.23","@balena/jellyfish-logger":"0.0.142","@balena/jellyfish-metrics":"0.0.168","@balena/jellyfish-environment":"^2.2.53"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.13.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.52","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.118-renovate-non-major-2a3fc367b9bd1830cb35fc02037a39d9e12a78ae_1605471409940_0.8099491309293276","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.118":{"name":"@balena/jellyfish-core","version":"0.1.118","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.118","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5fd036a9d272763b2de4371eac04862582386f3f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.118.tgz","fileCount":94,"integrity":"sha512-QQxbJLXJvxIrDipcZ0lNqItC8miina8jY5WcWmZ9hCGywUoRvwUk99k8AGG6FMbvKq8zpsiqKmRtl1C9KT9cLw==","signatures":[{"sig":"MEUCIQCEXodOtPQ0LCGKAJSKn2KBQZjqL2F06pjM3CNSrACVNQIgTTB7vM4+f8sEJTffloZOtasczqDWeGxqOItSe5KwIZo=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":572295,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfsY25CRA9TVsSAnZWagAAVc0P/R+1t7r14erSsIscZoi8\njp2YRW3n+yeeyku+IPkxEFzp4ACcyBfm2FKsP+6uu+2IWRB1Zyq4S2u2C2EP\nnGGQEQxHR6kTkZsMmOHX5wLuc6ISUQ5W0nc/diDb1xE2WZC2N3xmvlWd+vs/\n8Sl56+n1J8Lz5gHNir+EID+V2+rjY+ZuSoGRRoV48skG+h4YbQAmeDM8CC4z\nvkQi96qJgI+QvkhDHhuBviiNZ9st6zBkczSsk1mvcP1kwkk1zbLvW5JJPFCg\nR1cJyioYaQW5Vvpk/GPegyW4clLHNR2rWSY7oXsLDlZihbekk2Gq81/iybCj\ngJeztHo4NHo2CqpLu382E3ZGA1UKAAEp39SzGOHYOUTY5avGslvqyf0qyw2x\nuBncVxGt/zHh4Wyvn2ZaaHuTtenG1rrE/YjZ50NWKuyxBBgitD+TalFvI/Da\nuwGnzzDiHLO4JSL15Gvh5F1H2VpSB2pRHsQikmqzEqRNnPemh5x8VIREBhF5\nO4YiHxQ7UFPdTUsoqWCCw7p5R6369xrZV1YniQyOTV+sL3rak1d8P4y0zUOp\nCz3Qn5+da2wvj9bk/JY7qt3dxCuruFZP+PwVPDF88jwJ1XaJjDys2oiaQIwH\nS1s/w39L2N5zMRzZ5FwktJ0dWMlNz/pf3/WToISTVYKY/ZNGXyteezwwlMSM\nq3ac\r\n=sTSq\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"be687cfd5fdd63a93d86c49abd3a4a0d9f2a5a15","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.3","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.24","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.23","@balena/jellyfish-logger":"0.0.142","@balena/jellyfish-metrics":"0.0.168","@balena/jellyfish-environment":"^2.2.53"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.13.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.52","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.118_1605471672699_0.11503897845814781","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.119-renovate-non-major-801a8b8ba130b0860613e2cdd497acc4e7e6d256":{"name":"@balena/jellyfish-core","version":"0.1.119-renovate-non-major-801a8b8ba130b0860613e2cdd497acc4e7e6d256","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.119-renovate-non-major-801a8b8ba130b0860613e2cdd497acc4e7e6d256","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"777c173d4aeb77a7ec495c38dd75a8608230248a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.119-renovate-non-major-801a8b8ba130b0860613e2cdd497acc4e7e6d256.tgz","fileCount":94,"integrity":"sha512-cDoFi2e5nERPOUSBntwxV/7xt4qixNHNuU2OSVRnl4FtooQxFDD9Vr5LY4lEbNzgt1uUTOgDYTx8DcPhd4v/Aw==","signatures":[{"sig":"MEYCIQDBDPUeShbi8Va5tWwDfiQKiAzFN36eHyfjOV3j7zbdzgIhAKrgh4MSyKLYu5DydDqwnTH9DdiQp9xHiKUDpx4cIK7I","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":572445,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfsc8qCRA9TVsSAnZWagAAgc8P+gOHbfbQEnzcItyCdoRZ\n7c5v3tm5B9d6c3tcHmp6Kvhl+uPzibcIjjAUPqjbyxNioRN3SfBRr2RgiqBw\nI3ZgXR/GF0eGGsczWNAabVvTlq6pIwk0CmXegesFlBSuMrdH8/qnB0ENwzQJ\nT0mS7ergZw64LoW8kh4iBc5WXpFOTfNHm+91nlopPFph1VQd7tvT3fm8bGfU\nqjeL5yTkHnJWHUhOkf9bwe1zqXmYaHEpSxg3hHZWBnkFvPc4mY+PHZes8UBH\nzwf5n5wzNOA3RZdGMygteoynE6nh/2icuHZSSaWnui0yzCBKQPczypm/8CQa\nCR4y4ojVzKixsFUPnt51jlAMfW1MVMp+li81j0GHILAQU1x/4eUri8m6K0pO\nhKpFxKsEOi+gK4atE1DTjOZjMVbAFnpQBNxTs2mpaBu1M624oHrEnCBvlwk3\nmQPIpGp+30dGlK1UJE/uomubAznNx99jm47ie3mWaYNM4NYEu6xX04cMkLMy\nqgtjblJuBh6DZ2FA0JlfHwKdgRjXnvsNB4lo81BcmsrbLG65PRnxLVcwDnRp\nldJmXu71aT3aArCceTOGcLtOIQFuNKqsKY5hTJyrus2h+hVn3uhSxeLv3y1q\nxJUsWRpGeVOKwWwXYOYKerOnHctfjxmn/RJTR+y3nFYs6h90VKjE53vI9nWz\nBhk7\r\n=9aS2\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"801a8b8ba130b0860613e2cdd497acc4e7e6d256","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.5","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.24","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.23","@balena/jellyfish-logger":"0.0.142","@balena/jellyfish-metrics":"0.0.168","@balena/jellyfish-environment":"^2.2.53"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.13.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.52","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.119-renovate-non-major-801a8b8ba130b0860613e2cdd497acc4e7e6d256_1605488426256_0.7712725394271995","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.119":{"name":"@balena/jellyfish-core","version":"0.1.119","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.119","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"10a7f504b449003e6d50b1ae2dd7cfe04ce3f443","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.119.tgz","fileCount":94,"integrity":"sha512-OWhPRuhUCI3MhNmjUpyJvrd8ETQQIDiDw3hao9SAB4utSM1wZBbkC3J1TDwt7ght3nV14UDupCCSWsACN9FHEA==","signatures":[{"sig":"MEUCIGlvzgUJrq/bIGPqw2BbyXJKIMIc4sfKp0Nu+UVx6F++AiEAlmFlCrKXpI3Xyfo2hbH4jMFJqm0CCtqajqjeXGcGRNE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":572385,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfsdBwCRA9TVsSAnZWagAAY20P/3fJF7VWg7WdI0g0SzmD\nUF6hyYN1d7xKZVhMDD2zIw6gH0gveIi8RUDg00oh8ucvrKHHF5rvZTsarD0H\nv6mwgMBH3W7AVAXwpTW/QZovAcvSNVT2y2fPfe7DhB6nhj81OjBZjSkT8BfR\ne88JZK02OhC9Rem/4ItjTAxAAzF2wogVNwswe4WpJWn0Dpd1LYTkgMlA85Z3\nVFyBFhJrMDUYt0247hQmOQcWzGD+mjqjLetpcASnT5t9HJ3GYPN/7i9BDUVg\nGCqdpwlOpy4uHFOmB86NxUHGRuTVqxmsb9ohlXgPcf/3VkuLMr4Y1op0ufOS\nwkD9bIWIrIxHSYLp2gOCyopym2x/sDmEjXZyUvFhlEImJXc80TyLhLt9vgrD\n/iQi/xe/h3u4IV2FSprOQKwvTxooCcRkL8jj1C5OyaBYODALH55wgDiazvoW\npm743iEb/TCwnJN0uZ+mo2S25z0KS037H7a2JGr0YQURWwOK+h6QzcTBtsRa\nNqVBLU4hst40rPcQULoWlvDSF6KwG92sdJ3sBDLeX8AttApE0qqUSDgB3DzR\nS/dhIFFv5taZriTPP8PeRw26X/5KuezWWvb4r9umButMi7I4tCvin4m/WuSL\n+DboBOSZ8XtqeX1YS+h70lLoEnBGTz1Ch2gXCj0MEeyKQOa7GBmJQcuHEaGJ\n9nho\r\n=Y2uM\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"06644cf62805ca4fb9c097352048687b41420b2a","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.5","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.24","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.23","@balena/jellyfish-logger":"0.0.142","@balena/jellyfish-metrics":"0.0.168","@balena/jellyfish-environment":"^2.2.53"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.13.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.52","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.119_1605488751861_0.24119125128852947","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.120-renovate-docker-non-major-9c1d72a17ffce9a199dd868bf887ab5a11d6d123":{"name":"@balena/jellyfish-core","version":"0.1.120-renovate-docker-non-major-9c1d72a17ffce9a199dd868bf887ab5a11d6d123","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.120-renovate-docker-non-major-9c1d72a17ffce9a199dd868bf887ab5a11d6d123","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b7b1baa3c5ffd955c4e4cf5e9feb5dbd647f7969","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.120-renovate-docker-non-major-9c1d72a17ffce9a199dd868bf887ab5a11d6d123.tgz","fileCount":94,"integrity":"sha512-/+hmdqDgvNFUAM4nENoJ5krT1nM7QAiAdOh7xacTpNIRpmPJfUIwqkhOEJBZjyIsdXB25tKKIL3p87GCXdcEHg==","signatures":[{"sig":"MEUCIAEtgBvM5wlLMh6iFTXcd7vmY3e0Loixk2s+CGhKm9RZAiEA9v5sd5npXGWdGv/dXmucgoj/QDHfun6QDXoxWZ9JCf8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":572554,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJftSlkCRA9TVsSAnZWagAA9K8P/3LLKV4OwytXqSPsUGj/\nE095esgPVNVKaZJKuHEnbNMyoh3kGOPHkb4WFFgpcpKGK9Oc3lgxTDaIa9Cv\nYv557gmFgeJDvVrntgljXJrIG0o1KoczcK0XX4L2IjuFDMZzU+2zc0uAsGus\necXmKK6nMI/buBasBtaOCqxBbM4SwjqxvhMQLa4RORuHbAtiZsOfNFli4sF/\nyKlF+urQbGuiLIRpf8BstjK0pvMXnniXavRKN66821u7rzde1zVVqdIP/+Fu\nhsO19tUQvFT7cgFA2SGRknLHaW9ysF3JQ+80dG9saOWOY0+9C1n7luJ7q2Qp\nGUa8+XAB9yTnIlYdHaNfC57XDktoPPbSiWWpXlapMNFmyxixVsgILfGC23cO\nIkUNOksEHUty0lNXF1u/ZxTgNRZBcyPLk6To3LnI6RNZEdyoQE9Jw8BaE/qW\n2JuGkLmbDWLtBBTZjUZLK1iIy9yPl1zidikA88h5ofMOCxPps6qEnxTm0WNK\nxofPbZ4SOmz5+uGS+RSx2XNAFQPUqvIeUwSDlsd0TnWlLP2m0MhjNwk25uLn\nRF04/D+2y+moUQt0gAUzA8hrYghZ2+KGYMX/ODaT0JELNPjBBVy5N4WOVVKv\ni7jH2zTLSlGzXl+ET62N183QKTaTi/wySS5dE5/+PL+ABi5LN7pATYTiSsFB\nca21\r\n=p9YU\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"9c1d72a17ffce9a199dd868bf887ab5a11d6d123","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.5","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.24","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.23","@balena/jellyfish-logger":"0.0.142","@balena/jellyfish-metrics":"0.0.168","@balena/jellyfish-environment":"^2.2.53"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.13.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.52","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.120-renovate-docker-non-major-9c1d72a17ffce9a199dd868bf887ab5a11d6d123_1605708132316_0.3326902961908109","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.120-renovate-non-major-5c0bb964805ec77ea5c4819ba53a5adfd4b9f5dd":{"name":"@balena/jellyfish-core","version":"0.1.120-renovate-non-major-5c0bb964805ec77ea5c4819ba53a5adfd4b9f5dd","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.120-renovate-non-major-5c0bb964805ec77ea5c4819ba53a5adfd4b9f5dd","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"fa247820e897f412f23ed67c5d1d5a1095573954","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.120-renovate-non-major-5c0bb964805ec77ea5c4819ba53a5adfd4b9f5dd.tgz","fileCount":94,"integrity":"sha512-7f73dKaGRQvYPVIdnboQDDbUWG7z3D/MDxD5vovjk8Hv43X0kBSA5GKmBukDE5+dTR5MzrWnvK1u/9S6QtiKPQ==","signatures":[{"sig":"MEUCIQC/S3ZJeTOk62KVtZrgNjvPrLPGKftkQ/uYQWTG8AMAmAIgGbO8xSG3mMeSaaTnfSdusF1np0uIk1eUNQlwM4C22PY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":572512,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJftSmdCRA9TVsSAnZWagAAjkIP/2+9dBifBZHA1uYDu4pH\nJ6daoHo6UrLToAkB+nH6tNx8ZietA89ErJ1kWZU42dHOSpV2qkMCfCfwNksP\nGxvmzy5i6TIF4emXPdFXMSnDGH7M2OZx0SlzVJafonxxu4iLb+ut3qyNGNvQ\nEONcYgoSnb7s+8CKOXepFjIrfF7jkUNC+sAbYG3NQjobTjElSOK4YOuYi498\nu9863159FGWTpqpgcuRDTzmtQ40+ca2ntmxbOJWxR86MDOHVe85vc5fglsMd\nyYQoefcY0w7ow0HKIyOaQaiLpnlY5BZLh2jw65F4B7pfpVQsYOB1jD6D2haf\nm1CuLVmsn7Fz3N8Tm0+4QeazHX4iuD5CkjPkqolZnTjMy48KXb3HuS/MaU2J\nTtShJvWWGDXR4VjBjhkOslHD4tr6R4akBSx94yfW5C2EIZxEXJzLgxvBMzbz\nBA7I1968BikejT+9G5lMgFHLO7EDMbOPwn6+wljgaHslyNJrmRryJ/75W4Wd\nPIJaxzYIdwx4cT1fHe7dYlwHjC8Wr3jcVp2C1dkbP7wZpM8/Bs2Iu0tbW0Kf\nV6+nMDrHIjJVvWhjKgYwXSir8A4TYhXKEmLT/3TJbGaJa8gYkxkh9K96kbZe\n1OPznnOiRDIueRiHrG1ne2zuGgtI8SUfLgUW3VN5cHGUmpQEfJnwKqhOxwi2\nHt/E\r\n=f4k0\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"5c0bb964805ec77ea5c4819ba53a5adfd4b9f5dd","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.0","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.24","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.23","@balena/jellyfish-logger":"0.0.142","@balena/jellyfish-metrics":"0.0.168","@balena/jellyfish-environment":"^2.2.53"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.13.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.52","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.120-renovate-non-major-5c0bb964805ec77ea5c4819ba53a5adfd4b9f5dd_1605708189380_0.7944152278236407","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.120":{"name":"@balena/jellyfish-core","version":"0.1.120","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.120","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"62a0ebce67d2887e290d219957114a6f422f32e9","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.120.tgz","fileCount":94,"integrity":"sha512-ZtXHraHT51tXD4/fxRWOBe6J5AFUOiob3TqKD+q5x5By1V6UFXHUQIO2BMUVk0nO5vCZFrDV0lqJHPuL6rNbow==","signatures":[{"sig":"MEQCIFuwZs8y3CvnSntvhPHrblJL2tLGRXphjdbqkWp9D/P+AiB3/BhQGO0KkjnxBTkYEMEYfPrNobJBvWy4rtiDdrt4kA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":572452,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJftSq5CRA9TVsSAnZWagAApz4QAJl5/rg67QTu4KoFS/jH\nNkDHeosThxzTwjZZvIJWaDgcpjNg3RT57VxsfP/ZJfLLWfJtJBaMW/Wwt2TN\nnY22sQll6in+Ni+2yzHhuh2tHn6imzpezgkA/5bD26Qp9b7qN8fkmZP2OwmA\nUb1iwjv/D460hz1N3XMQgoGj8H6WDsyJMWISr6hNswP4d4CGlzUHT4DqZz8b\ngf2MoNLnao+rsVR+5w27aZu19BUGjANP7FsRWV5tAebVXBYLBywcrhonzVsm\nFPzRRux5VK4rTI5CTukFClFrkkjMngE9SzSp7tUrvQF9dr0hK9WIpYKFgVK5\n/ydjX7vbUqnsCIwdJcYNphVG17q/Xc7MrPLjQGTMujwsymHYI6lR/jcxlx9S\nYzHgh4ADhG9wrxP6QJy1wlXLRY41KKoRuegwfBdVBD9H4yzUeQVLAmpOmVNv\neNMSYNdWa1yD0YvAfrPjUHXHKUVTXeWt065OdVFD+NpZVfBUm3D5jKcbEAsJ\n2JMniCUZnIgZkj0ulP9LZfc4GWAHIrb+V2MeV7OYQKLa1y183C+B4eQDmoc4\n8MLbC82jOIpKA6VadCoNL25LvUjk1TmzA5bg+9L1rdNOIQu6jWpNn7/jB+KI\nFyXwOBsJcBNfmHjW0b1LIoMvybbvrwot5yAvemWeK9mDfTCpkmP2Vrd4qBkQ\nquly\r\n=FrbP\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0a68dad8df1e2cadcf194f8c7b805d1975d66042","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.0","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.24","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.23","@balena/jellyfish-logger":"0.0.142","@balena/jellyfish-metrics":"0.0.168","@balena/jellyfish-environment":"^2.2.53"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.13.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.52","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.120_1605708473287_0.710177529889858","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.121-renovate-docker-non-major-2c88638ff2bcead731e2e05ab8f25148d1fd6428":{"name":"@balena/jellyfish-core","version":"0.1.121-renovate-docker-non-major-2c88638ff2bcead731e2e05ab8f25148d1fd6428","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.121-renovate-docker-non-major-2c88638ff2bcead731e2e05ab8f25148d1fd6428","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"363685e6497fdb046156508bb204b9e9e1de6f50","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.121-renovate-docker-non-major-2c88638ff2bcead731e2e05ab8f25148d1fd6428.tgz","fileCount":94,"integrity":"sha512-KzGDmADKybihd3QS+e9YTKpf5bYU8I1U9a77cFU3+/jEsuUnX3oaprTLKy4Mij3/FjpT6P1eXbhYxF5Ni9P3tA==","signatures":[{"sig":"MEYCIQCojKzOIsCFwVDLtstAjWBVcJf/Vlg5gKN3O8j4ICs09gIhAIWQYsVr2wGChqIy3YhSEtMlgT7QUS+yGVfKovRUSiSB","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":572621,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJftStxCRA9TVsSAnZWagAAvQMP/0UOWYEiKk45b3WnBifJ\nA0hvsMf+r/2aWS5Dgw8oJ3NqiDntdSccOUIYJJ62GMkwllS1S/6Hb76jNE6I\n8WJ8kH/ojnDDP5RPBRXTGQtyRj7fTqxhnp6bkZ9BeaIOGOUjpwl7RLPix82e\nnx2ccH66fNpSzcAWP/pUh5Hmq846v24SNZzDWBXvsJ00+yMpzHMwLPaO22qE\nmfgq67k4bMftO4rin4VENLSilnBVYnk+9MqAMMTDhdCWw0P/kNjsmETBNj7K\n51V531FTLtcbQlTTOUzUDzyRUrgD6KkOC4qeoBQKLaBqMgT1qMKD4woTLWbD\nVwh8R59lRUXauNztouZp75m8BOOcajGb6yhvptEMy1FGxeyDrVJdWMurf3It\nUJhEA68nGLCZZRpErX/HhSjsmMCvuVYQFToOdKDKJOpsLM5at1hPbrABpdBJ\nDQYPNZ7PyelEzPDiROsZT+ulbS23Z7EFD9Z+IDbCM1aFL7g3Vk8zHoKOEsQ1\nl7N8IlRooPJZM641LvVBjQmp7WezWNqWwULJKx9g9GC1zNN/lK195D9H0S/o\nSkkDKNqrt/xxNeaYWzzdzkFQ/GkiZ06f27/JaJtuP6CgOEAAO+IO0zQrTI5u\n02wCXmqmefUhRNoqVYxpIvjzG1FuJftUGcWYiO3yqdPqQpwQVLw2CY6OXRzH\nYAvW\r\n=bcpZ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2c88638ff2bcead731e2e05ab8f25148d1fd6428","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.0","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.24","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.23","@balena/jellyfish-logger":"0.0.142","@balena/jellyfish-metrics":"0.0.168","@balena/jellyfish-environment":"^2.2.53"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.13.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.52","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.121-renovate-docker-non-major-2c88638ff2bcead731e2e05ab8f25148d1fd6428_1605708656573_0.6594377075066418","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.121":{"name":"@balena/jellyfish-core","version":"0.1.121","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.121","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e559091d69bbc03d2679f040c3b45f31399fd0a6","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.121.tgz","fileCount":94,"integrity":"sha512-fgaV701FW3R14LXRLYq7L9VV9393G9YGys9prsEMVyCObAss0IT0iP3RN9hxZMFV52/wDLJXjjemzyPPTMIkQA==","signatures":[{"sig":"MEMCHzOzl+SD3oWSSszWyczydfUiLcIRRbh+GWLSzFYgjOQCIHgW5axkUDhfPyk07LEPYrG6VTrdcxNS7+lH+MNc1BRz","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":572554,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJftS0dCRA9TVsSAnZWagAAYrsP/2gusiN3ouoIviNj5dTS\nYY+NRNxkPPCORfyxFk2mltPrkOa9l6Th0H3D4O780dfoFfuvWApmATpeEPSk\n8ODrf8T34QSverokI724QZR88u+ySOj0NmTA1qAwVtXv4zz84uQlNz8zhIVq\nsk7uU2vOu4G4AG7/ssvCZeUExxFSEakpkj2aSD+AR7Qvo91+cOFbsN9zVt4g\nnwnoNzYNNM+krjakdO4wOuqWSvlUTSOIXFboZ6F6QCnTOIZoX1+ws14yN9JP\nR+MJbClqmsCvE7/KVaQu0Kquf9900q3qbpvR74xrHyodPy996vGZIq0ZGeql\nSXa3ncplPLworIDl07VJfSdSVH1t747H48CZv0RHGDzWSDZ1zvZMMy1E/Z9S\nI9vrLAaRNz5wNHFbEaPJaYbyyceXe05I37d1Kjmlr+QV6WaTqZvojpxtCGBl\nNM4+JAINMeq+BYV1i9IxULMBXgIwseAdT4YEuTedjSwH1BEVOS3ASd+uHMMy\n3MX5ZgrGkU2sobzwk4GpQIeXtIsu1wK4N77OxGuyiupDFpoxi7QLd6BCiaSs\nrohYtL8YFPmqnJqf0D9kRudaSIlRBKE8m0Hje2vtmmVLIKAV0HBMrzfb4K4b\nO69bjR3wbItAibBTuvUMzl+nodC60ZO/+wJ2XswaLqYEIUJQZZZ80TgQZW0x\nTxMc\r\n=4QXk\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"270497d75cbf81c35ed5097fe807920e182106fb","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.0","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.24","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.23","@balena/jellyfish-logger":"0.0.142","@balena/jellyfish-metrics":"0.0.168","@balena/jellyfish-environment":"^2.2.53"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.13.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.52","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.121_1605709085062_0.5084396673171689","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.122-renovate-non-major-6afebeb900657db261eff4c1a363f5de7a0731a8":{"name":"@balena/jellyfish-core","version":"0.1.122-renovate-non-major-6afebeb900657db261eff4c1a363f5de7a0731a8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.122-renovate-non-major-6afebeb900657db261eff4c1a363f5de7a0731a8","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f4e4c49e3af71544cf5bac244c82e025ed21530c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.122-renovate-non-major-6afebeb900657db261eff4c1a363f5de7a0731a8.tgz","fileCount":94,"integrity":"sha512-WxdFD+ZAPUrI09tdBCfp7zogBsbpDNdpLJngn+OUrOOCUsITjhSyiuP7Xb18O0G6xhaf8rzRhMZ/VhhkltS6yw==","signatures":[{"sig":"MEUCIQCAFFoIl0ZsmiEU6Tf4aiDPCyuf3esxOnRh8oD+Xt2FwwIgJr12zPp/vm2ICtdbkGvRdIUW388ec16WT6i+Bv3Mb7g=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":572681,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJftcjOCRA9TVsSAnZWagAAtUUP/2W4zxYWOyfPZ4axaOOf\n47CjxutqFsHiWkNaV+jz8YWCHN5m3abIZ/S33YNtDI7NfVfe3lxJhSr+sVRW\npqtIcmD06haQKH3EycjM4vM4u1lXNwKHFn6N2NCG90v/r08AeeBVjlQJ8vVD\nlfFxJX76hXnzIorVyEyTitw+maI3Tn23P+gxw8JpL1cjzkgIgadPH3K2HOGm\nkbR5BgurxnSb//Bar2V50bbG6wLQTEotJkI7HT2I90hAEk3BM6+ds3yOt4Hb\nGgdEyiBDTQ1D3ORmGtGXuVlmo75UiUepBMUA5J+HrgT5Xh5xWxlJOMYEqUjB\ndAxmaR0ebK73OXBKqq3mrK2s4MXiMJDeps93bJIN3YPF94k04efJaqxUiYCB\nGoeXMCC08qpPGZ356sxI0H8lyXHVZbP4YMUVVdvGzoCDP78bS4OeDt0MggUK\n5KwsZobpCTkjPh3QN8gCjCTJpWZbA6kcWhS9XfG8nFle8NT8RoEzpabclQ5L\n5Kf9FukG9GD5U0ET7pci1NC+XBC6kF9MQKiqQ6BOAEOT4/Sjb1/M51WImqzK\nQKjjXOWRhs9HV0OhSyhu9eB9yX9kWPUbYFjLzkzJ7g9D5g2X2CSjeZJP0Vlr\nOunvehsDiq+5moFpuwnm383Gh4y83mM5aiSSgrBuR499SVOgPynxSA41AYxv\niXiz\r\n=aAf8\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6afebeb900657db261eff4c1a363f5de7a0731a8","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.0","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.24","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.23","@balena/jellyfish-logger":"0.0.143","@balena/jellyfish-metrics":"0.0.168","@balena/jellyfish-environment":"^2.2.53"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.13.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.53","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.122-renovate-non-major-6afebeb900657db261eff4c1a363f5de7a0731a8_1605748942118_0.1421219068180508","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.122-renovate-non-major-8cae75dabffe7229b7c13c7ea5815a1ad9175cd0":{"name":"@balena/jellyfish-core","version":"0.1.122-renovate-non-major-8cae75dabffe7229b7c13c7ea5815a1ad9175cd0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.122-renovate-non-major-8cae75dabffe7229b7c13c7ea5815a1ad9175cd0","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a4f784d0af16a213489b80b5cce55df19efa74f4","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.122-renovate-non-major-8cae75dabffe7229b7c13c7ea5815a1ad9175cd0.tgz","fileCount":94,"integrity":"sha512-/9vVbagFOeIRDKrwv6cV3hPnjujERjpYeqakl1l2mN43BUwl5TTj8+xPmjQMLQ9UcI80DTjzUnseMBnSqCYjHA==","signatures":[{"sig":"MEUCIEAv2pJshK7pFIgW/2sObqwpp57aFXdUCdbcSqy83MPLAiEA72b4Z3yO57pyI93mn4q/G4dYQgBOTULW6qQC//jGqsY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":572681,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJftcpNCRA9TVsSAnZWagAAppUQAIUQLgCyYRrERXx1QlM8\noQdnd4W+KA2ERAKUCdESZknj7PdRwS0ifaTvOUFJhGqQ0HIBFsjwP1N30rvK\nXxTU7LfiN/WQxjqOCZEqtCjzDSeNjG969zVhMXcp+CT9bpVOa1Dkb7kbXV3y\n9H3ad+LMswAGLA/cLJgeA5k/3zIDUIIgxS0xFcQXODZ+6YdMtfaF5/8wu6MR\nS+qnGEYXMFlVQs6S/oz4/Kqvht/ZFLUsbd+XiFcW1v9ldmIls/nQUQpfgbLw\nGmDtN5zjexYUXszf4T65bVxCdsChIGwQLl2dDd3wCdd5vpt6tXR5xewUK8Td\nAPuLWvc3VQNqcWT8KvnofJKlJd7cXJUnwGo/2Jv8B8U3yOhPWbhEN5db7YCF\nWjQIoP13M1tN7ZAYXcl6NuyfHycV1Hbm3wFf1XgqWfGkeM7H++enHyQOBH0c\nlpTqZtha49smgtW+l5qVyPhC8oeqvCcg337u9y0yQI30QWl1v7QlX1wrQ6aE\n/wXVpCWuGffmI27TLDlTznhsvD2s7pzdlT06skMkoEbnV8DNqWrnovlqUcVs\nDz+5t5idINs1qf2CEhpOitsfHzV8AefKfRl3J0wyw6qlAE1dkRm8m8gRkwNE\nwnA6/iyKqROiKuR4XryZUoXHRaVCeuRmrEriQejEIl9u7JW9G0XTZ/30ON+q\nI5mT\r\n=cmiG\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8cae75dabffe7229b7c13c7ea5815a1ad9175cd0","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.0","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.24","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.23","@balena/jellyfish-logger":"0.0.143","@balena/jellyfish-metrics":"0.0.168","@balena/jellyfish-environment":"^2.2.54"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.13.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.53","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.122-renovate-non-major-8cae75dabffe7229b7c13c7ea5815a1ad9175cd0_1605749325368_0.6880102608406837","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.122-renovate-non-major-3b56e960995e7a61af9897c2f4c7f5e110d6fd7f":{"name":"@balena/jellyfish-core","version":"0.1.122-renovate-non-major-3b56e960995e7a61af9897c2f4c7f5e110d6fd7f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.122-renovate-non-major-3b56e960995e7a61af9897c2f4c7f5e110d6fd7f","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6e4929a03c8a0b6163eac847c989ab8e244a9a54","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.122-renovate-non-major-3b56e960995e7a61af9897c2f4c7f5e110d6fd7f.tgz","fileCount":94,"integrity":"sha512-7wV454mTID1FORbu7ALl3U8C5yNnR8gQvB7Tya9/AyvYFpI9xeYeH8GUrd8ObDJEgVSKPsTPmnw/a1dY7wkKug==","signatures":[{"sig":"MEYCIQC6YiHLMF0+abGl4PHHzyfG/vwMLN52VEchM2Bl4OXy4QIhAOazgmaXqSWRtYfTda8nmnQvBLfBS/l6Ay/Z8J2emeKM","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":572681,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfthSoCRA9TVsSAnZWagAA5X0QAJxlVS7xmL5gsBUXRJa6\nHKFQykU/Eoqy6cTb3zXAuD/5rEnAqLCzeEdiWwuzKIak41395ibx9YU3xsu6\n/1lKEDuGZH85rWrNglXp1QIrZrtLldC6on9hnrWHIECRxToRE7TLn89dV4+T\n1HAR/fvLJCXYwLW6Nh088Y67d9gbquvsAFVGGhkhiBtFdXxokbeFKxf4dFcx\nA8aD4ADlWbpb/GJeDp+42GOrDIIBDL5boUKbgqSSVXZP43Oe8avo1lpBtiJc\n4Av8TxoUhFulCZJo9CX/jjsKM+ky/JloJdLywieEWzf9pReYPzkTWAxXTBhM\nf2v7mRYBek8n/d7u+hSXbQu1/Oo2a+FWSiUGTqT0tGG88znPMpW1NJq6RZZ/\neEJ4t+HRi+O33uEipE75PlHv3j5vowkWVcnU/jFesxyHoe2nYLEZ27NEBFtC\nA5lE/CkN0bNZKWPjnQoGtw/qRECRpO6YE2yazjPzxqMCfPH7YX9dQ58zxZj7\nAi2M72uXlLBi4IDSfasa7+3+0zdptjU2Qk1EX6eeJJ+HQwYD/rVuqs51Puoa\nt5HQF5sgrfvtN8Vo6vVXh9WoOOSaY7CH+EIv7tHxgxS+jjE8I58Z5piYS99z\nMdejBu+5gcwLbt6kYZUQy2Kl+e0mcHKSoCjFiwXTVEFBBqg1fqEKBOT7vrh8\nqvy5\r\n=nU8L\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"3b56e960995e7a61af9897c2f4c7f5e110d6fd7f","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.0","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.24","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.23","@balena/jellyfish-logger":"0.0.143","@balena/jellyfish-metrics":"0.0.169","@balena/jellyfish-environment":"^2.2.54"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.13.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.53","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.122-renovate-non-major-3b56e960995e7a61af9897c2f4c7f5e110d6fd7f_1605768360428_0.7740182058337097","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.122":{"name":"@balena/jellyfish-core","version":"0.1.122","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.122","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1faab2230c2cbc700ec13d685e4f361e324b8496","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.122.tgz","fileCount":94,"integrity":"sha512-pSKJZHUe61YiPfqxoYMiwaBbXvo9JAu3Imse8WcP/uz2j1Lw0xVGrRnwrVm/vXDQCfKBPI9jQykdjhxthblGAA==","signatures":[{"sig":"MEQCIFAjXROUf92TI9B9bpPwKp2N1fn/hrEKf2s06RN24JHfAiAeeaNRXuxaGXo9aNjLLdxQKnkOGj6xb4TzqpXmrHoN1Q==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":572621,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfthYCCRA9TVsSAnZWagAATTkP/1vA7JPO0GB3i0xM8+WT\n9Rq9sDZELt1W5zZm97BVwygWFnP/JHXkTyNJn75e9ZRmBT+NotfVgkMTMZBN\nEj0c+F081S8ghM6uKcisVDwt6dpjuM+cHy/SHsjdRAPYrHfrBgvQMh+Yr8EY\nyhfD3zyyUdi1pY22RnuH5eCTxm4zg7rE+OUv44ugPibKoR0sl3kSviQbAdHK\n6AechRgKEjEBx5yAkDuJVH3d4HEbYt6fI31yis55jEv9a97WgX7DPFJT97Ge\nEOJnS2cIjPP2bV+8+ERNQlSrt5ei3mPdYlcG8CoUzAB5d3WDuLSmHAxWdSzw\nYt2ufqHtTny69cI98EloH+G1PaHqp4er+6BEp5XFLKZ54le2FJJ1aKMWqc9m\nLcGtRUWfO9hpzdfnsiGi5lzUZ7tiyFQmeX6ZEucdEVWvVo1wClvJXrVAJuoX\nghaqYYYh0cCusb4JXFhAURdteWv2Qx2vXXNrClxcrNkDgNgaJP+A3fP+oeqZ\niDYyiF4ghnjNKXZzX8sWom8U8IeJmVyU16UrRNDO9cC3l4yIkEpkhoxhVBFS\n4EIzx4plCAD8tPH4fvkDimAo0dXnHOZHgapeKKk97OhbAGyEL/Lg+oeU99Ak\nyhIBxNOYQP9JnXhkB9edgJ5iN1cgLzgxHlDL07fvxPAkd9N2f0zB6fJL1Q+0\n/ZuY\r\n=PbYV\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b8293163d76ac8d4231ff56ce90387eba036a9d8","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.8","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"10.20.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.0","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.24","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.23","@balena/jellyfish-logger":"0.0.143","@balena/jellyfish-metrics":"0.0.169","@balena/jellyfish-environment":"^2.2.54"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.13.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.53","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.122_1605768705918_0.06352859216477746","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.123-renovate-non-major-d407ac76390de5c3f83ba70f98c958fe61549085":{"name":"@balena/jellyfish-core","version":"0.1.123-renovate-non-major-d407ac76390de5c3f83ba70f98c958fe61549085","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.123-renovate-non-major-d407ac76390de5c3f83ba70f98c958fe61549085","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d2ddcf2eaa4049a611ca49ad9705ec375e4add42","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.123-renovate-non-major-d407ac76390de5c3f83ba70f98c958fe61549085.tgz","fileCount":94,"integrity":"sha512-0anHAvwlR6vTNDZyFZIdZnTehMoDKPpHYi8PPZKZuIsjhvnbpWE18odcODWTGq/H6yNCvdfGom2ITsq0xkoOdQ==","signatures":[{"sig":"MEUCIAGv61dXvcaRbqXBHErq8CLoRuZAnJeQO2U7iM7UGKnwAiEAm345I9S6NBLiUaOfAL2AMdQD1BTwHlZmZgIRj/aExQk=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":572748,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJftwMfCRA9TVsSAnZWagAApbUP/R4N+OMKA27AdCsXSYIz\n+NvUJehH42j7iVwJJXSqrpcUtedbWSxi1b/y/waHvCkv/MJlRgBLEbXCKldf\ntuhUqC1J1rsCC9QtgL6jBfKuVjm/0ysfowK/wezhO/Y0/posaweYVMhuNn/n\nXb/dMuy1OJVBlj+vuGX5s7PSQiqdXbh6PXPljohXv9xwhu9DDzF5zu1OdR45\nXp7ZvbF84FA3eH/0PzjLzUb+c4YOLGTkRD34DNWVQZ2I3qIGK8u1FTM7Z/BA\nwXSI6BVjDZTpeAU5/LViFRxzbdWyhkgTWH6sw5Lm12+eHjfhZd85+mL54/c2\n4AUibMCuYQFDiniWNdtPYPLd2O+G/Ausv4X+xGqS4mpm7xTLC6ShvVzWJxdJ\npRY6+N/ozc6IH3sC+2oZYqbNOCGMbCn4A9omD8HmIDu7ecdQZQHxl8d9ZfDc\nUlkxbYWlaG1rmpOLhxB26/FAoEA+26Nsvj/ec+QPUhGThvV92j7wRBpOf1qq\nCF7uBY+V0tyPCy9iZhUG8DjocDGCY9xbbPQZHxdG3wTfKaHNVigclfWTe2h1\nDZk6OHmkm8v9OdKlw0C7VFkd/YVG3Ows5YquKZjd8oMdH7U0RQOTikA3Ulew\naIpxTkVA3bl0F455GIrTqDTpYkFglKqcY4Xbzuj9Yl5jCaP3EwtCbYHi8lqU\nvmvi\r\n=IvFa\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d407ac76390de5c3f83ba70f98c958fe61549085","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.0","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.30","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.28","@balena/jellyfish-logger":"0.0.144","@balena/jellyfish-metrics":"0.0.169","@balena/jellyfish-environment":"^2.2.54"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.13.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.53","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.123-renovate-non-major-d407ac76390de5c3f83ba70f98c958fe61549085_1605829406386_0.22861863881976663","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.0.0-links-aggregation-ea724df2434811e19c7c62118b9740234915b0a5":{"name":"@balena/jellyfish-core","version":"1.0.0-links-aggregation-ea724df2434811e19c7c62118b9740234915b0a5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.0.0-links-aggregation-ea724df2434811e19c7c62118b9740234915b0a5","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"72e838fdada3678bf2b2186229f06250bb06014f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.0.0-links-aggregation-ea724df2434811e19c7c62118b9740234915b0a5.tgz","fileCount":94,"integrity":"sha512-mwLVL7BPOEiEx9gkhucgn7Gzr205faiNwqBp8+ySUCJkCHR1nim3WYeECC7yWeQ+X/BlKs497/pjVpoAolEczw==","signatures":[{"sig":"MEYCIQCUadFQBGnb2NhQp/f9PDXb7c4l7J7G6HlN9HPiqw+IbgIhAPJjfRZgD/gg3jI3+qQs9b+Jr9kgg85HGkLSuCPC36g0","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":572550,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJftxDDCRA9TVsSAnZWagAAmHwQAKUXINUSl4fZtQvIKK9Q\nwapisZsLalykHdirWAMRwsKc3IIrxNjYtbIOhtZw4ccqfTTeIjoFsvuRY57X\nas2j3GGS/fWFIRk73L/cs8xws+SfWCvoKyZmec0PF5y6uF38Z6TebJ+LEOD3\nyMUneO088c3h0dNDvXDDIlvyhBDSXGUt7tPqPOEcgKdfo+V5pVAD5d5NNyx4\nkX9HdHr11WofdU7XBSZE3P0QtKOVIV2FyJgMB0dazWxrUJwUqVFEq1WPmcL/\n6rO6yQ8buVR1azyJW+jN1UW0Y7JjZ9faTphil3AU6wXpBQrnf6xC8VgCSWEP\nqlqaeQeqWKXNwgppc5rMnshff6wHtEVsaNNd0rTmPw5d+iR7qx8pwFHDaJZ6\nVjl0v65fYyi5nVP3wUWq9y1OB6bCr7K9Yjj7UmATuj4GQvNlI8mv+ohqSd+d\nm2E6CJNdAX7zHekUvMs89T2LKN44z/QMvC27bGdU4fh//GW1xW/Kvzn15reR\nBHP0G5DCgP4ygaxi5sp5lQ46aC8yuI8xiiR1tQ6VN5PXPm4Jq52TPZ+7Ydz/\nx0hQaGCiAhQ+BdHo/9Whbi6JraBNP+gsV2uYSIWYCyT6v500SyMeQUHxiO/c\nn3tzm9nJBCxcyztaq9tB4JQcJQC1bWf2YybebZG9RjDnFsCoef4O1vJkH4H4\nLxGy\r\n=+giP\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ea724df2434811e19c7c62118b9740234915b0a5","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.5","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.24","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.23","@balena/jellyfish-logger":"0.0.142","@balena/jellyfish-metrics":"0.0.168","@balena/jellyfish-environment":"^2.2.53"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.13.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.52","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.0.0-links-aggregation-ea724df2434811e19c7c62118b9740234915b0a5_1605832899007_0.8907695178624859","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.123-renovate-non-major-5c551096b18d01b45f69e2bebdcf2b213f06a35c":{"name":"@balena/jellyfish-core","version":"0.1.123-renovate-non-major-5c551096b18d01b45f69e2bebdcf2b213f06a35c","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.123-renovate-non-major-5c551096b18d01b45f69e2bebdcf2b213f06a35c","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"758c7aa4e46ab30e46860c40cf3211e1258aeaea","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.123-renovate-non-major-5c551096b18d01b45f69e2bebdcf2b213f06a35c.tgz","fileCount":94,"integrity":"sha512-5+yHCF6yfMYnjuhMT+Xel6lPKUTfjJJFDUzmkOmtHRO07Rz3WLD7jlR+cUrd2QYJpMOMC2uOKTO4pzD8O/RS5A==","signatures":[{"sig":"MEUCIFFsNWjtIZM0X8tTUJ+IlKVUYUYlGySyVEV8JkNM9gCFAiEAr4n4IAUfEK2wsDkUrsrWKsC6HGMzVsaKcSwoqs4teac=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":572748,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJftxGACRA9TVsSAnZWagAALUwP/1LjXiEB9kFGh8XleyUk\nti/6uCa2MqauyybbIfm9KcY4HIibUFA4D7/A/IlAYxOmeHShFFMgS81NL2aT\nKgiMT5PzF3/VUkirO23T7tCCn8eUUmHbFo69p30bdM60U0OdYjBCSU+aaB7F\nHhKuAeuYyBQ869dU+AMFDX4ilUP9Retgo96Mi9JiQM4hIkMYTlUmITMzxhTQ\n6d6PYRN1QsD4/k0ck/f1p5HWm494u0UKxX6O1QXHI+n32Xv9wPgX9e2hlLks\nKFN5oacUnwR4JR6oyw9Mev0TCY9lonaxq4C8YP4XfHPvgYcZ10MkJYnT3r7m\n7jCh7CpJftMZ/+PiRzw1mQz6BK6QW1oFQ94J/DjUBUcwPxiXNi5Iyr5eXbQY\nXdgYuEQisCgWO04pHMCFQsIWQxkYwfbyS4OvVbDCjslEXUgQB3q/gFfzHTIg\nbEaVwt2tCc7FnQ2LN54oZ5y6bh4dX4Tygt/VOqbEPdoVcSjn3I/Vux5mOruh\nRY2H0OPDMM6nVz8yN31K2ZM1aKzMHNT1Nd1ImH8EqmxMNhELtIPWIoAL1tPs\nRUW+mGvaxWhPAOLIg1kNv3R3W6cntgCU0SJ+2/1hv5cxIJqjecqO7j6cBZJJ\n9LQsOKPZJ4EdEWwKCqGfoBmczhgSoQ9aMo1x4SLISm1aDd3IyS9o98zmP+LA\nE3V0\r\n=yuk4\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"5c551096b18d01b45f69e2bebdcf2b213f06a35c","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.0","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.30","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.28","@balena/jellyfish-logger":"0.0.144","@balena/jellyfish-metrics":"0.0.170","@balena/jellyfish-environment":"^2.2.54"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.13.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.53","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.123-renovate-non-major-5c551096b18d01b45f69e2bebdcf2b213f06a35c_1605833088056_0.93840595344942","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.123":{"name":"@balena/jellyfish-core","version":"0.1.123","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.123","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f6d37b7e9d1fbe61dabad3c5de0910f487434d3b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.123.tgz","fileCount":94,"integrity":"sha512-IUh/2kxIy+6teEi52wB7Jmh6yKgJex68jihRs0HbxgBqf9ZqIDdMcwky/XqH6DlBQ2Agx8hqvi1te009M7bJFA==","signatures":[{"sig":"MEUCIG++90HPwVfsJxkqnMmmcAQUh2nSs5cmf/W7X0QQFHBFAiEAq+ZhXaQPw4O0Zfe9F3xn/Ox9aNhI8WlgHTQ6S1be1Wc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":572688,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJftxK/CRA9TVsSAnZWagAA75wP/jR82nKOmJnsVjHtn1Aa\nfOazop0UwvYYyISXtcmOQchzbxl0DJuVvySf3uemAzbJUjLfsy2Qrcofw5+X\nYMS5M1g5FmnM4bDm6OLM2ZcBLv3gv+RUUHtkWNVUmHHen7cHcyG3xkaofgVD\nXgho80M4I0O/0pW5Pkkn0BPtWWyIiqsKP93Qz70b3djzl3ppW7e2zWNyZs0+\nIARExLd1IJ3tLP4WODADpoxtLUxa74De+BmcYYNikf/PdKqi1s51E4PUZXCB\n/8BEjyWaFoEeg9ydliWfa5hBZkMTkSeN9/w9+nsxhxpq0Tt6IbdPwN9s+w3a\n6HfkVkbpnjUxEhm5EYmMxkGoOaJErYzQ3MWbNYNY+O+xlGdtV4YBnSNoqINm\nj9DvKaVl+mnsxXYtlLkOgXIaSuMmjn/GX3//MUA8gx8wd9Hxu4snQGY32Z4m\n8XxxFuxSBw2I1PoADQk35KaeMJD9f7/cZoWT3y4aQiMSxxg0XyRUkNsu7JG1\ntKmaCLEhfDgZP0aOdYv3m3CEc6axjpjnXWqKlfSkoA2k6tChJSZxhn6rY1CM\nHJ0Dz95x8edBR0xPtC2KPe0M8Kr7Fo/LGTW3o7lIAgvMuMrqoXgAS4zlGRW2\nO7u98eAxZ5J5jF21Ayp1/bcq4NpGsQJ6HIWGrIi/3/NWzoP9QyUtLAKRCxcu\n+XfJ\r\n=4uEG\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"1a1725b6a604ae0b0cdc46c1f19ab1defe1e8510","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.0","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.30","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.28","@balena/jellyfish-logger":"0.0.144","@balena/jellyfish-metrics":"0.0.170","@balena/jellyfish-environment":"^2.2.54"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.13.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.53","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.123_1605833406650_0.6940567110516844","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.0.0-links-aggregation-3b98dc6356d914b706771d598f167923af8058d3":{"name":"@balena/jellyfish-core","version":"1.0.0-links-aggregation-3b98dc6356d914b706771d598f167923af8058d3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.0.0-links-aggregation-3b98dc6356d914b706771d598f167923af8058d3","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"db5330a301ac531fb1e8f84ce7f10bf45ba1a625","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.0.0-links-aggregation-3b98dc6356d914b706771d598f167923af8058d3.tgz","fileCount":94,"integrity":"sha512-9xhzaZ3Csj8QT26oHoql2m1jPgfggFYKGQ37a2CxYFFZGSPLP/FBU1LintKYcRyumHoLvrd52AtLxzM1A/XBug==","signatures":[{"sig":"MEYCIQDSnF6b9r3wH/GCtHqRM4kjCw++ynP93H4CNhZ6wdk41gIhAMC3D0sXTxxTRGLP6f6kkucq/Q4N0IkqRymUUq+3ekbF","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":572576,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJft1eeCRA9TVsSAnZWagAA1AEQAKSXJTIvJeGzLMvLE2c6\njRLbw4MaNIRmEs5F+HQnxf8RnAmYbUQiZZCmzfgecNGPrnYfw7Qujuaq1yhU\nZCoLI8/L/iegQOe2m4gtuQKyamein043dINYT/HNfzOaIir77RlyGgOs/0ca\n1tiTN++Kh3xEMCdqf+XYpViZUX/PXQgjJNMuv5wDp9WRR1hr4s3JRcjnZDcA\nyXFcPXOmkSMQQfyzbIOb8oJutgl70Sa98I+A2ui3lm/Uy/rbR2QNvsamDC42\nAwRdxCdiq3y/Jq3dWMev3V1ZAoIsGzsbw275YHk2yT3cN/EF2K0c/v4Y3KY7\nMVWor6Tc2ED/IESBhjmXcoRDUWE+rqI+YuYoBv3y0aMOx2AKBA/prlBE+ha1\nCqWMZuu+NzN2tfGfQntg8x4VzPd3J3Hfplv7HRKIPA6B/vnNc564wvXp5f/x\nO1U5WDqegZETYJri4W8Oxv15jEyrldLjFYnmeRo0o2KTQoY/W0F6NDzsQ6/V\nqNXHV7n9vkkeWB9jYJ5+DzoogKK6gs1yapUIcutZwDG5ONptwzQ6r2ikA8T5\nkNn+ypH666/cpr6r2cOjgs3DmYWTu6Qq1nii58wKwwqmAxBsdL2iH/Cau1G9\nNrF2I2C4pDoJ8vdGzXkpmADH5bKnakNN+ZYhCjzju2Tx6EwcCc7IQo1zsqkm\n3Bb7\r\n=TpaC\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"3b98dc6356d914b706771d598f167923af8058d3","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.7.5","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.24","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.23","@balena/jellyfish-logger":"0.0.142","@balena/jellyfish-metrics":"0.0.168","@balena/jellyfish-environment":"^2.2.53"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.13.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.52","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.1"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.0.0-links-aggregation-3b98dc6356d914b706771d598f167923af8058d3_1605851037574_0.9118599960417224","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.124-renovate-non-major-de310b6498321141ef81cf7054f261d7ac470376":{"name":"@balena/jellyfish-core","version":"0.1.124-renovate-non-major-de310b6498321141ef81cf7054f261d7ac470376","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.124-renovate-non-major-de310b6498321141ef81cf7054f261d7ac470376","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ebf86394bb0361fc6794ebd44fb5b3dd3aaf278a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.124-renovate-non-major-de310b6498321141ef81cf7054f261d7ac470376.tgz","fileCount":94,"integrity":"sha512-s94Aqpdn+Hi/ZcGYz8f5wNCIajJ3cBq1HFxlhgfiYXrzTeaoH/UPv68fqZGAn0KGHmMbCSV1Z1QM158SvR/SFQ==","signatures":[{"sig":"MEQCIH0fCluVW8+aeyWJtlv3qlo0v0IQP0LSnz8GlB0cVsGvAiBQusQvGvGCr9EWvWjbdzr1Pwpm9UHTFFm9jlm4hAS5Jw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":572815,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJft8hsCRA9TVsSAnZWagAAc3IP/0qUghE8x0JAUfv1Hfzg\nSzfd3rcEZ41YAEnpFfqJuAmsz6eb2E4ZVpZaNORj/dBVxMMVITzm07SfigOK\n9FXKL6EAl8xgaVzplUAj+gfpBXXgU6iGs8SX8DloCLRMGCvWEp262tBkxVmr\nFW6rqiNhdsKd3VgN1Q/E7TsvrVnm4/rETcN9j9/iOqYKUnxFbh0TGPx5MFen\nXFWXdM/yUL2JwJ/D1/oLX1N1uIDuy6pIyEXVr/QNiNs8BpkFmWDet1Xo53jK\n4DKD+wQ/2lpB00Kc+vW4AIgaeYNELJ5OB4wSMRCIKHiUJIi6PT63fQZK7+rk\nSYSGrhBggPhTUuEhyi0GJlEYl3XIPvu7t8JQM/O090lbEAw0q33DH7tPJJ4e\nYmMWHoteCKJYaI4dF/kAebekBcde7cwmbgcHJb6k8alhuur0Z5nRR1XnJTt4\nm3xtQTypFKVNevUH4oh67yx1k5ihL6cd9lUbfH2AimgpQNFNe9DnKpunX1cF\nGjifnRzz1FJJIRX46OGLgOYLY+YV5qtyshK4wD1HCAcKFZASL/w+lQsw/fcX\ndw0ZxcMPFnAgExhlWirvSZLCNG1BWGdKP15dEZbn0EebmH2b6uBTlnFPELtW\nsMcSZZOfnGEHGRVpMhqG4R2Az9VH8OjwofpBhsxnvzOlL8++0QZwHk7fRZSo\nlHiU\r\n=Xxms\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"de310b6498321141ef81cf7054f261d7ac470376","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.0","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.31","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.29","@balena/jellyfish-logger":"0.0.145","@balena/jellyfish-metrics":"0.0.170","@balena/jellyfish-environment":"^2.2.54"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.13.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.53","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.124-renovate-non-major-de310b6498321141ef81cf7054f261d7ac470376_1605879915945_0.0024820456657892453","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.124":{"name":"@balena/jellyfish-core","version":"0.1.124","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.124","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"257ce570d8aa57094ca47ee9088f2381442e081f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.124.tgz","fileCount":94,"integrity":"sha512-ekZ7pKE5mC8qGMCGSHQyOk0sLH/oEW4tjUtcRiyb0+TKhdGz2aTboPD6Jmvjc1LDGwc688hY6S2aSC+/YvtI4g==","signatures":[{"sig":"MEUCIEXae2wUrEyeILsMjd3enWudflC0v+uDInUfAKRE1OUnAiEAxz2nf7jmQeHv22/h/D3Kd4v67d/YPWKoQrTjZ/Fkprk=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":572755,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJft8moCRA9TVsSAnZWagAAwRIP+wT9VhBlhaVONuoGyW0s\nTLdJF2RzLusjDuZLTL4+8P/5nRTAbZaIBh63biN1ZKGIY7T0rgBh8amPp3p3\ncxHfDDeAkdQuq3XYi/nqmucILJLonrQ+rrj1xKkHsEpXGq/D5O1tBXe1BeFE\n9X65kagq4OWcTp1Y6bGtocK8X0Cjs9xdjj9+IbEezTWtzMXMrH+rd9ra/JH4\nSdFFFHZib+ZeZPGry5pV8Bfwk+V2RIzflBErUG7WjdFj0wQFygMZ/Ihwdjsn\nNeRaId9ctbO/lL4tUZ2BsNl2uu+lc/cevweesztNWRrnidKZ5ZhaFQh8llGd\n+ClhojNidFxKyjz/gTFVWjwvcvrloB7KFqZTeyarRaRgp/5IX1MLwtzOAMPx\ncoSTnXatilWFKUHfdYBLVuKEJM3iyJDlYEHBdv8KTu0U2lT4dtUTAqzKLEkl\nFFuF2EI7DwTCoMEVzjD+k9FNeWhakLL3SaSn2eeNRSN+/cm3YTvaxAuG7JFc\nvxZhGa7gsJBf0Bxg1GBldblOqawarlAlewwHW7Iv5Y9wl0Kg501W7/nD8QrK\niZ5Q7F2lrZoC5A/YPaur88Rt03VJO2Nd9hOOJX9Ub3FwJp70pPKa6axe/5VR\nK18ynpgQ67cwsyZmgEy6Oh5g1MZKOzKxVTs+c49AVY+m3qFt5NOIfyvOedRp\n/Nzo\r\n=rTeA\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8bc6f9394ed5a0309aaeaec4d1f9b47d4a6e3439","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.0","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.31","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.29","@balena/jellyfish-logger":"0.0.145","@balena/jellyfish-metrics":"0.0.170","@balena/jellyfish-environment":"^2.2.54"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.13.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.53","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.124_1605880232485_0.04718943455768376","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.125-renovate-non-major-62fce54f8a39422adca0d8fb054f1e5dda5ed1d5":{"name":"@balena/jellyfish-core","version":"0.1.125-renovate-non-major-62fce54f8a39422adca0d8fb054f1e5dda5ed1d5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.125-renovate-non-major-62fce54f8a39422adca0d8fb054f1e5dda5ed1d5","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"813438c1d3a1607dcf53fcf1c7c52ef6afe462ba","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.125-renovate-non-major-62fce54f8a39422adca0d8fb054f1e5dda5ed1d5.tgz","fileCount":94,"integrity":"sha512-wGAV6mO6AU6R10AkehSIUZrjTucG0BENqju1aZCgw8YmnQmYQVB1wnzEnhup6PKQIfFXVcsbyyJieOng/Of9+w==","signatures":[{"sig":"MEUCICa8bpLOQsuCEUyksclzmOVeIha9GD6PFMsL34sx+zHLAiEAo58f+/OnrGMkiaD/lEf4/hMFtHvGuHA/TyPaX2rM4sM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":572921,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJft9kTCRA9TVsSAnZWagAAavwP/0X+yTd1jHVP0Xw/Ji3+\nn6BGgON2L6/7db+ZpbhBo5gfmPkiLL1uti7Y76oaM3LDixlBreU6s4y86HB7\nbM1MTK5Lr8EVm79JhTzvASjgHCMmVGf0QUyt5MQ0ccM4zH/DSbXFfJ7R4mvV\nsv0WyD9+fQoja5x/JPDRE7lKyzf+bOHiCaG1BOyzJ2Jd4y1ZtyfzSM2xQdDW\nyLRqNt3orZ7lYBH+gSp9clRzIfp2sBoyR69GthTDncWzTo40bLw0og2Q6MLx\nW5SCW/chiULflbn9/BcTh0bYCiWfM2wfxHMElJgxez5mgM8qn7J7iqbOqeQL\nECSIViaCNcTBO/Jmb2GiJ9wcjAGTfk15fKz+dZ6hME5k8Pq+bxXpjWeLQQdU\nXTIoHxMKLfPIHTlOmIOhkK99XMWxZ0vJUng8MY6bFhpSPtOQ4h9I+Fe40aI6\ng4lA9vMCVj/J/0YP1jk2Ixm3v/c8OI4f53/otXf3+RbcLe8tn1Buuzqt21Ww\neOOf0GXaBQ4nZqGswJk1liro3pLgzPzdwX8zzMQMM6gzHyfa61QISqx7ancO\nNRYM+T6JNbA5gt5kzgQCNOXYpp7n+g3zEzeXkjLgJ+bPbZfBpWu8nAYO1mha\nwSdlPuy6i6p28S4jFxw3Nhtdaj/e5Yp4asD3K5P50R0usyu72kx/IlXsWBYS\nYFrw\r\n=Yag8\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"62fce54f8a39422adca0d8fb054f1e5dda5ed1d5","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.0","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.31","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.29","@balena/jellyfish-logger":"0.0.145","@balena/jellyfish-metrics":"0.0.171","@balena/jellyfish-environment":"^2.2.54"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.13.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.53","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.125-renovate-non-major-62fce54f8a39422adca0d8fb054f1e5dda5ed1d5_1605884179127_0.9753955080449292","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.125":{"name":"@balena/jellyfish-core","version":"0.1.125","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.125","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2e174e8d94c9c8f7e8d8a1a044dff74603c69a93","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.125.tgz","fileCount":94,"integrity":"sha512-M80D/57GI2SiBCxYFT9oGxvr03H7EpJ96tSgD1o9xsCxFES7rcGdGzSvC+iQ/OkT28CFgjV3xY2YpRdj31LAFQ==","signatures":[{"sig":"MEYCIQDTJgre9gNuItPslWHlU1CMpLDC0to3DdIeaG5CIdsMEAIhAKfSJ6IdKFt+Ao2rHuz5wGkM0VXDer7hRmfzJPjtXzRm","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":572861,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJft9paCRA9TVsSAnZWagAA6wsQAJjtLBZT5XJRmWAUA19B\nFEjEewh/hFHge3qLT5t/8h6RmfFf1syqfisgc0iV8jgIJsKDljeUVcn28dxq\nwNmrxM2104rDIVFRu7NLBL8BgnE4MvW8VDZqlMX4R5zITvHz2TZpVBCcxVcn\nakpXBzqfL+tsjMJX7yRZG4nPm+vHm16noGYcwb025tI1P4g0qdgeCwVQD1Ob\nPTMS348roqh9/rDh1b98ZWgvnc8f25sD7uZEwZIKmCW1WTm2UNlQxPGJcRBU\nw5XTOjtNmQZG60bh2loPyemxmDBzxBk8maPb5wtqynlP85Q3sbGJeYmCig1P\nxWkeMCnAnfImRyxTg1YRqOXsjHKO119/LXbrayXihgqV5mo3NBls6dBHa9pt\n1YtbocJ1WopKG/oAtbc5scpIQbFxxv8y9oASQQpkA0Cjqy5m20FJRTVnLr9R\nm0Ayg1nAk/C4d9v8q1hBT0Ma5+yp2H/wPzP/F+ihyVNBduOxZ+M2b1rauyg+\nQigaYCpKik37m89dQPlUrNd7nfDexOGqIc0RDzp0HO+9sD90UXnwDm6qQh6F\n0fQqrpyop1OFqEBWxUT13moqtmDpwJeKZhXMMZppEK8LvtBjatc6qySth4Iv\nWLydakO6YZewCXSIdkFS/Vm6dJbdllrm42AdkUxVn9EsoP3XODkgk/bKrE+n\neJFr\r\n=GIsN\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c7cf18a4b24c23cf74a322bef1d9a2d140f0fbc4","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.0","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.31","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.29","@balena/jellyfish-logger":"0.0.145","@balena/jellyfish-metrics":"0.0.171","@balena/jellyfish-environment":"^2.2.54"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.13.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.53","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.125_1605884506375_0.5932042625494311","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.126-renovate-non-major-84e59e6dc8477d1b559934b0161d4e6bb393340e":{"name":"@balena/jellyfish-core","version":"0.1.126-renovate-non-major-84e59e6dc8477d1b559934b0161d4e6bb393340e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.126-renovate-non-major-84e59e6dc8477d1b559934b0161d4e6bb393340e","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f0a774ad4cee136395f5f1d4fcebfb9224acd5eb","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.126-renovate-non-major-84e59e6dc8477d1b559934b0161d4e6bb393340e.tgz","fileCount":94,"integrity":"sha512-qlBUeo88+M8mjmpws8BP/bWreRtUmEeQZsExns7/GFoV2Nqwvww1QKBK2CVHzjDOFd7+npiMpLv98MvYsvHwyA==","signatures":[{"sig":"MEUCIFjwh9pTGE/6nk7a5gaVVhKgNsT98uYIIfGeMJlsy6t6AiEAx/GbSQbraNG0alQdF1xWFDS3uANt9nfmP1PxKrxzxf8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":572988,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfuFXUCRA9TVsSAnZWagAAMhsP/1STJbTex0dxCO0doPSO\nZp7qnAb3Z2v7aOYlnJC/3B8pXTubX+ryyC/8edPVMQsYEW+sIWxfDy/VXrgO\ncLKmf6DNOfZUtB712xj7PtTtWSQN+8+BN8rwxoqcWWUpbtWbw9yEaK0fZ8ie\nOe/1WZplPtYlVOkB/zkzIWek11PgUMMJlFDqV7G36zwitnfrnsW88Oy0z4J6\nG+jpLZMo82cbWEJGJAfO4rnOYYNTurNuWz+6+QLKvUBvtIcftWvGOBMJNejs\n2tnXI/h6HKT4BHtE9JN4/q8bsl/lk+F/dbaBXS/mEkr7XVyg/M1AgU76FAtP\nqjen0sC2YiPg3ZFkCbM1kPhjB+Dut5mIkWs8z0GjzXSTouiHb4URVLeQbdVv\nPSQyOLVOHOznBIs8RcVACjKMiZV9z88HJSW1/66x+7FXa2LX5VfVgPjni/xW\nkG/dWdhUb7c7aB+civ59n9AFSdTIAMPp/0HDdS5IDqbAU3G/LBy0ohGm3ZEk\nXNu0rt58Zkeqx6nWwpZoyE3Gs2N9adtDXYJr0tXe2jWsQj7SV6NNofgFd5/6\n76kp8O1PODWpgTY0rFhZ4QlnjXpi+uH+K+9IuTs8hpamLJs9HKjDjqyTM88N\njC3Jf9EMePxGiE2ty73DWIEPDVPiJ3Vae9Cr2B6ABM/YGmlfyLbCAlr0ihfc\n5gPe\r\n=YTol\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"84e59e6dc8477d1b559934b0161d4e6bb393340e","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.0","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.31","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.29","@balena/jellyfish-logger":"0.0.145","@balena/jellyfish-metrics":"0.0.172","@balena/jellyfish-environment":"^2.2.55"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.14.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.54","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.126-renovate-non-major-84e59e6dc8477d1b559934b0161d4e6bb393340e_1605916115517_0.6781995148241966","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.126":{"name":"@balena/jellyfish-core","version":"0.1.126","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.126","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6840094ddb5a59301fe324d0a0e68b418661164f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.126.tgz","fileCount":94,"integrity":"sha512-8CemFp4qdi3A5tLf0t6DJ+iHCQ8wgi1Fgy+cdvUY47P3MzCZ9uJUYAWYANisLbBaWBooW9W7z1Z85asGejGQEA==","signatures":[{"sig":"MEUCIDt18wX5E4r+vdv+t/yBjQHmbajN8CZZbRf1faEC+sxrAiEA3ED1D4CKPPRpQqZ5Qb3p4nuY42MGzlaYC99nNfj8eWY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":572928,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfuFeSCRA9TVsSAnZWagAAXU8P/3CgrYh5MkMQj2jfkssU\nGsupOVTogQf190fJMbTzax43EKE1o3mpHTuj9XlntiR/uhTGE26IbPQj/LMg\nDz5tQ0UP8TK4jwp6+iSsmNrL9oXitGinvOa+5VFSWhRafLdu1OW770CkI7Ju\nyd5LpEXJ4DVgWNk+02ZpGbMf8OP55FmV2WYopo0A0+iqjjyEpD6r7J7oSNhh\n5Eicw8gobi4FhYNJUNL6MZK5elIz65eaoILUI/EPTalfGMar3knPyIl+/nE1\ns48Ac/tkhnsKpYDXBHsOJphmQ1AQ9frEPHeaQF1zFxTgZxIszsy03AGp2zLX\nytIb6qn5NfWR6epbyM61RdW72xcz8WJGZVFdUoZiyBEDlPcBbph0Lan2oYAw\nhacf/sZxXTE4AeHGXFuI8jCoUCmq0ako9WZU4KP/pooqOXRUZIsk10frVvIs\nOd6CjNN3xEfrsztrqfd/pJEm6PW1pDpSOfCFCBQ077mK2tamnaMssCky7mtR\nDzBNiIcuF9SiMA6SIG7ZShOxYCwvbnx2CSvDkzvtBbBOSlrnU9U2FzqBZx2Y\nWEXTVt3pFi6UaHH+Ij3RFatYKs2Ei6/OfG48oyV4bWWatvzh9oe12TLMiA39\npLlzrnX3PRMF/EtlVi+6NM3acR8mfix3GgLBwE+kiTifWdAZKjlZwPiCsWQT\nSyC6\r\n=TIQU\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ebbb7f7d9478e39fc29f11e78a69ff360b7681ee","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.0","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.31","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.29","@balena/jellyfish-logger":"0.0.145","@balena/jellyfish-metrics":"0.0.172","@balena/jellyfish-environment":"^2.2.55"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.14.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.54","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.126_1605916561207_0.7271130717401819","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.127-renovate-non-major-26ee7f4fbd4d4d0aad0389f868bd83e09f522df4":{"name":"@balena/jellyfish-core","version":"0.1.127-renovate-non-major-26ee7f4fbd4d4d0aad0389f868bd83e09f522df4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.127-renovate-non-major-26ee7f4fbd4d4d0aad0389f868bd83e09f522df4","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"fa5cb593190886354dabd4449832abcee4adf8e0","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.127-renovate-non-major-26ee7f4fbd4d4d0aad0389f868bd83e09f522df4.tgz","fileCount":94,"integrity":"sha512-PfmAdj9qjI3s9wxtIB8tIoV2+ESGg5MmueY/Z76tm/bUpzyNhM5CGhH6JIAvH+CGE1Ib34R1hEAojodDrxQlVg==","signatures":[{"sig":"MEQCIGiDTUWh4PYk5FMeG7vafWcmGIEuLvw4HVZeZWkpjfhqAiBHH4trxIPR+xOsF8q3PM4VwhyJtFnTBZeabFAyuykSAA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":573093,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfuFjYCRA9TVsSAnZWagAAAFUP/0k5YkOop4wXVoLbHFjm\n+kpSTHCmiRWCqUWO/P3xpTUrGMRJ19avo1dv5fBHePVCdXTugRMmoq9q3jIz\nGBUss0CySRi10cU/YqmmpOgx+M83EagxKVjyFCWhTSdMhYzpddKtiPYKYc1p\n0F72kfEQcOJrnkZ+eN+Cx4KaUNli11UeBGf0F5n6aK3FQVojlmeFc9Yi8h6d\nlq4hg8VNODWYLHKvnNhFhTOr4ug4bWY+rmkybf/ynawRKwuRq0SIRucTdsu/\nn8Nf7m+1sSObQlHerA8gDGJiqXPmenDrNolCmdIocwEww1FYTpSZ4H1vJXnE\nnZimkk6uU5LEj//ekJEG9iNtJZTMk01vzDpYyTvYhuVvOONIFqig6wHhL+I/\nYbHjNK/Z05E9gV65JdNKHf2KWLdysPheNtzmvGKYweyrIWcMuohWWkH4CIV6\nrBHa7v4lua8D89Ga6AjkT6klS1hw8YShv4fspDd1X43wRaX3dqXdfdMBy3wo\nCrzsgjzEezn4aXfMOygL44KDRMQBoP/TYzfwBaQZIPauNkVlSNBytqQjDz5c\nJ7lBcH8pPFS3BACABDLYqda01l65BeG2raHzQUc3DbQ7y3e3X3N1yp3l0qeB\nGSQ7wNz4BzUoLYYzkBNsVHCBfR128fKUmqm05XXjBQDe7toz5nnt+aMpzGXG\nLlfW\r\n=o68Y\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"26ee7f4fbd4d4d0aad0389f868bd83e09f522df4","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.0","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.31","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.29","@balena/jellyfish-logger":"0.0.146","@balena/jellyfish-metrics":"0.0.172","@balena/jellyfish-environment":"^2.2.55"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.14.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.54","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.127-renovate-non-major-26ee7f4fbd4d4d0aad0389f868bd83e09f522df4_1605916887635_0.5428971177167572","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.127":{"name":"@balena/jellyfish-core","version":"0.1.127","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.127","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"48bbae30d1f1d0223aa165fe6903f29855c13ae7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.127.tgz","fileCount":94,"integrity":"sha512-yA6myGmryhQJoUZehMva8pY55kfGA/bKycjpwTZ8dBD9NDngBspUodSszXL2Uk2aKw+gsbdVdeC3Q71+SCWtzQ==","signatures":[{"sig":"MEUCIQC38GmV6VSZcUITgn9FYX8f4GVtbMp8vZ58cRviTFlEuQIgIuegPghkoBxLCfpryERU0ngudNRyN1Sw1FTMBlUEkHw=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":573033,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfuFnrCRA9TVsSAnZWagAAth8QAJEUZu/4H+A+oATDUQ5n\n5fHG30JJkt4/nVEYJGRU8xGjcm859rw1/YXo1qQZg1demNcsXKH3Lyne8xG6\n6vlHFqxF823sVaovksPTuG70V8FxJ1jmZ7bTHmn3/6VeLW/zlCT5PcXIBP5r\n2hKbh9npt9zN5oCFrm2NLX0GH8Riwk3qhcXEdyky2PSUw3VzQcOxEsyTwjbW\njuz5vVDLX7pb7BwlMy1lQPqsCYTCtktClBjIdJphsI7xaRCw6uYAc9xUTRR0\nTdgHgR0/CzDwfgur5ywYfv8ViDFEILRS1kXDKYT4Nb6/cVLYqffseTyHjun9\ngW+s3/UIXolsl6bJhUlPO82FfbA2EPLbwIixp/hcaQbUzTNS4Q/x+xlNdh2r\nVx0q1rJSjisBxvld3gYPHNhySKt8zrzTxBK/r2G83Eq3KSkoEwlCzB13/oV1\nLX831Fwm2ufLvuTUUcThTI3LBzv5CyrfU1e2+16sJMswcm5xgk9gPGICjzdk\nnZuKZYbp7roViZeRpylNwMFewxEpvMULt85GMJUM2jSVQzHIfCr5Dzbg8UhW\nx7vOVfPQZqYRZ1G1BmpcF2Bmg1Q5FwfZyr/P1T1+k35Sz143duoHdSCn9KHF\nw6bscK7lx4N2QitgRqSNruBvgzM3gXV6y89RuZvDDBs3eK/FHLlAvsr4qudn\nKsqK\r\n=ZRUV\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ad1dcd07d51932b22bcc95758f29bc2bfbef2cf2","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.0","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.31","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.29","@balena/jellyfish-logger":"0.0.146","@balena/jellyfish-metrics":"0.0.172","@balena/jellyfish-environment":"^2.2.55"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.14.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.54","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.127_1605917162989_0.750479825733996","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.128-renovate-non-major-de5fea194c3d769cabb63b4935d548206d5df1f6":{"name":"@balena/jellyfish-core","version":"0.1.128-renovate-non-major-de5fea194c3d769cabb63b4935d548206d5df1f6","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.128-renovate-non-major-de5fea194c3d769cabb63b4935d548206d5df1f6","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"fdcff7eb48a418201dd9fceae8eb566fb48868e9","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.128-renovate-non-major-de5fea194c3d769cabb63b4935d548206d5df1f6.tgz","fileCount":94,"integrity":"sha512-OXuaz6Nm2wjsMZN+6xaGRCZ9pSNYmE/UmAKybeiUN+VqLVAG0LDi0KnWJo8lWttBpDPY86OZpDsoz3Sn7K1beQ==","signatures":[{"sig":"MEUCIQDFpDhyAQC3zathL5iVrusssPIaTMAoXWLnEkq7gK+YZwIgXk6xjWKe5/oBjlCWfACO7EJNVMtVJXr38/JZXhEOQkg=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":573199,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfuHgGCRA9TVsSAnZWagAAS/gP/jbMnR6l1tjQ74SvpijE\nvFMj10P0JwvM18ApsOUB/CIrL/+2VYmpNbiTQNZQKHX2I94+BpXc4I/lPKT9\n0v37XZ/CLwrA/Jy95qpQNs4lbNXqmI5QZdBn+UVeJ+BMgtph1DZeEgoyFdIn\nxfbOpAz96LmbJQ+0ohHsvgIFWdzHLbA9sRGhDkt1S8FZMmbr7AsTTOwkyI/f\n64sJwf3xH3j3AS5/xD7QFZ35o4MbHofMRTQszIR0aWk+aB0X94FyvvH/6eHW\nDm4nsbiwOXkTOR/8U/suIfM9J9zAy+s7pTWMM+wtZRJ3hdfmn1RRvKEOvD7j\nmC0YhH3cav6yNXHZpyK3aC67uYDhMI+3AQo94ZMa7JiBF9mMStTUNVIp/tHc\nzRjS3LXbogBGOsTmc+biTvrlQ1cJEUXq4CMfP48FBxUN5e5JzwuLHmhKtY4p\n2VVDd9Kcac0b+WfTsk6DTxeBnhmgKvPWnVN90clC8oF2bEQhB+LGld112Zyk\ndQy2afHTIkScE1CSkHl1bxjXvbpc/xOkaNZRtB71BikktEyAya8nzNGge3PD\nb8FkcOzRdJyMY4dy7jbE3/YySJRqtQA6Y9wfJaawWkHxRLrBzP8dEJyXdXM5\nBMtgjbo8ZGAhkOMi6AaT42OjGDAUfjDVx98jy6LTec6xWXvvBY3O3zADNYDC\nk6mm\r\n=isNn\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"de5fea194c3d769cabb63b4935d548206d5df1f6","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.0","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.31","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.29","@balena/jellyfish-logger":"0.0.146","@balena/jellyfish-metrics":"0.0.173","@balena/jellyfish-environment":"^2.2.55"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.14.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.54","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.128-renovate-non-major-de5fea194c3d769cabb63b4935d548206d5df1f6_1605924870192_0.9903956086971653","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.128":{"name":"@balena/jellyfish-core","version":"0.1.128","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.128","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"684799100201985b3d138e28363feea33945cc96","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.128.tgz","fileCount":94,"integrity":"sha512-gbmguIZbQgL4/d7Cy0pLOvhxxD0Z/LbT7KiXhJjd9BudWhzpIMu/+HOCup1d/9tsPK1mqYR2VJt/OWlJ9mqqdQ==","signatures":[{"sig":"MEYCIQCk1bIK0D5t3vafuboZnQaMIvx6GFeGLx5vGva3MC2miwIhALRhAyKBbrmqAYJT57bWOqZmWJ4vmBvC+SxLoN9xgj4W","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":573139,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfuHmHCRA9TVsSAnZWagAA+RMP/3mjzxIWbiH+1bHD/Dvn\nIwNwZgxqD9iGnfYBLVhItBnNnvuIPK5dWmi7En6y6BtTC3mzFYMSrCB7ZJPa\nu2IcagttE5Kq14Ymd+BC5l9+0rGkxlQ6Bwqqvg87KLO1p/KANlvff9XTNkJ7\nu87JxBGnD+7F89dPQXZBFDfPJPTqsHX5xwrMZrXRsNtJ5davIcFvCaFJK8tg\nxOO8E5RxaZ2SnbpOIDFC/zMCFdEZJOM+mL2Zq4tfOu2bFaLOJ73T7sRwp7uK\nVN+F/G32Uwazk+eiyMBj7/8BroA38J3RbcnEiq4j4kOfUPxfTaY7HgPRjJos\nTMXHRBrMFaH4HiRKn9Y4iQG4Rhuck+RXGw/9e7gj+z28Zc3szeiQF7rRItxo\nURWrelP9Ek3XSKlD6bks5i8QUV2TtLUclD6DrZMstkHJcOtTI4uQYVvq7lku\nBy+Q/EkbvAR5klPuaT9rGCDZ4X1VKM0lvMl/vcJuvrGG4uNjDpyLO1iSabSL\nvD/2RPreJeFiGiU8GoidNv1cfXq1UKVb/5xESi5dfDYR991YzPZ376uJ2elk\n3N5BK/vyYTwMPmK0ntUrKSzvMM0uCkAiLYZw+BOMkB8c/CQ/jj/9UrI7Pw2X\n1hI52CGvpQQe8dpcjy4qw6vc0cvEv7LgxB9XPJqOMzkg5oyks/mjI00IEEE0\nDOZi\r\n=uTZI\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"da6ab24b789521c0b0cf74a795b6ef32bd5914c8","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.0","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.31","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.29","@balena/jellyfish-logger":"0.0.146","@balena/jellyfish-metrics":"0.0.173","@balena/jellyfish-environment":"^2.2.55"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.14.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.54","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.128_1605925254573_0.5481442787886801","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.129-renovate-non-major-1e523e9364312492f4acbbd25649f6dbd97e194a":{"name":"@balena/jellyfish-core","version":"0.1.129-renovate-non-major-1e523e9364312492f4acbbd25649f6dbd97e194a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.129-renovate-non-major-1e523e9364312492f4acbbd25649f6dbd97e194a","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d2afb18295ee1b7fae7c063157e6105aff0ca9bc","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.129-renovate-non-major-1e523e9364312492f4acbbd25649f6dbd97e194a.tgz","fileCount":94,"integrity":"sha512-Y7/cSmN75WfhMQYc2/hkuxIyh9HIB9eTTNo8XsSYbIj6l3IkmJs3jJu/4IsEdcXUTE75t/HxlxkmKRzkpP4gHQ==","signatures":[{"sig":"MEQCIBncgC4dMvuUWj3hrYXnuDSyqPRXLWluNO6XWY3UzVEwAiAFWD3LOzLkDYJKL41eSW0QdJLmynSLOhwYX6XysJ7uEw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":573304,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfu56JCRA9TVsSAnZWagAATsgQAIjBbm2tN53WPplNJdMz\n7f+6GEUR7dh4G+oihO72H/lx+ygQk5UM1t255EMPJ9Dg8d1BNzW7Q7TzO0gX\nEwnhcH5R2Ay6s6Q30iXLjZNXc0isit7n1EBebaB7EcGg5BSg1NZ+aTn22xd8\nyATFBLqrcZpysNVuP+iXm3zUxLpT17yCzeEHphIIphIBjTV70K99MjXs7HFi\nbyYARTc970bQyIJJMWokXiXGmAZB1hNNsYR3OzQdPs1uLb+92ZFqx2J1Nmtt\nfLTuUENxZxB60rXyGG0iKIzl/zCziQAuozPIiEHKkfxnbU4bKJmeIQMpkkDE\nNwXtdGHQOBmRn5EDZ5Cam7bI15tT6ADkE0IExwkex7zCq1BQII2jvWHJW8gj\nu0fFCKD8ynGTiaSwcFgxGT3/EVmeOKl0gU9NtuEIYe8cFhIHsnudj/fGLs7D\nXNsuoTDAFwTub2C5tJ5kXXpPl5yjXBwc+AvuVVDMP6OB6fmKIe4TywvwyeTh\ntlrIqZ7cxM1USywoXJspyXH1N06XjmPEl9i3jpxzgfLkUvbQQlxXlZ8d0sJZ\njOjmfSjdU7SMeWrzGAN36tmvfegMDSi9fodO0RyjnaQ2WBHQNwEP153KxLMs\nq6Vtdjse6AtxiYgcN0QHqk/ejThL88ou7gpGLJ0bwXPh6EkTTP9fqFE2QrfO\nw09Q\r\n=2AbK\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"1e523e9364312492f4acbbd25649f6dbd97e194a","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.0","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.31","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.29","@balena/jellyfish-logger":"0.0.147","@balena/jellyfish-metrics":"0.0.173","@balena/jellyfish-environment":"^2.2.55"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.14.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.54","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.129-renovate-non-major-1e523e9364312492f4acbbd25649f6dbd97e194a_1606131337421_0.9148790467559802","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.129":{"name":"@balena/jellyfish-core","version":"0.1.129","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.129","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ebf2ea9d20e386de0be8596d6c624e41020cc51b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.129.tgz","fileCount":94,"integrity":"sha512-JO++ff4jeq9fVikZJpI0+1uWOzfPu1DpF0F9VBzOXvh/coIEPWuIs6YoPhRC45r03v0JhdwkDrqjCmR4j1q0ag==","signatures":[{"sig":"MEUCIQCrV74SVrYZ5aIwaYD2BMOZAv43tGLDk32a0CM/DAVSKgIgd1x+i7MT5T2qvgqPj7SPmrNzPaqBvo8T5NHtGxpzSL8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":573244,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfu5+9CRA9TVsSAnZWagAAsyoP/2kkIcLVqkiTVSRevTTw\n5TTrh6daNBXhdA7ORlsumzONEGy5gdOCjBeQYVsIZGj/Sb0N+AMZifZ8q3KI\nx2RaLsxqx21NI4Tq3IsmzenX8rgc0CPR2uevlLVt3++WsXBMgMV2Ep3hcKfX\njsicvs/E3SatjGi0k7SLKDRc8Bia8lWwU+fMnqWifcZ93iGzkF4CltCJC14G\nX0NntkUTOBG3TnQkvz4Cb/iMVzLm68m8QuNR/ztDSKZZlSSW+P8nfADV2r7s\nR5W9SYLV7JTicY7zWF1b5I8vnjvpmwMabgCYhJMJJDYu4L1CTmwrkvUg8iTk\nt3pNXA7Gc2J04IQdq1emErYYggI+M5iFQhEKAeQZaMDcikwwKUZvCfeLLY+M\nKnWLa6ad8Vt0zU7PyI3kvxo9hcQJZJ711/WTlaL2uhFTtA8fbh97VMMBcOhI\n+MAWmbrfIgDqVt0helK0dVdzolmnXEY31l0jQyZDU/L4tL5OYtk1vkVhDGkC\nFHlDkM/blxE6bgnev+vi7PZyWbpQXF6sH78c3oSltkCisMptQp45Qet9hs5G\naP6L+KeCkkh/29ioqTAYaG+5aqNlYdR516ROqHvOES5Vp0FeH0lrjbPMRRCc\nkImDDjXYWNQ71HOGLSbIdmBxay48MRlQBNhMavUgvZd4HTmAnIuFHcH0mZ6E\nus4f\r\n=YQC6\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b29c3f6cf8848fe63a452985232ff17717463485","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.0","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.31","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.29","@balena/jellyfish-logger":"0.0.147","@balena/jellyfish-metrics":"0.0.173","@balena/jellyfish-environment":"^2.2.55"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.14.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.54","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.129_1606131645289_0.16784665032972446","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.130-renovate-non-major-244155f4efc97504a52324a363463bb159445678":{"name":"@balena/jellyfish-core","version":"0.1.130-renovate-non-major-244155f4efc97504a52324a363463bb159445678","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.130-renovate-non-major-244155f4efc97504a52324a363463bb159445678","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"cb7d88cd2d9c7022e49d50d3f5e64da4b6d5ff76","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.130-renovate-non-major-244155f4efc97504a52324a363463bb159445678.tgz","fileCount":94,"integrity":"sha512-FrnXgk7wycDnZjJ34CfG7iaZMOLBs2gO9vwvDtaG3MgKEd3GmrJYsfLig7nzzluLJx88dRCPtPYGdfs7KtRzVA==","signatures":[{"sig":"MEYCIQD4lbM4W7yPOoOGrCWIzrQmS/AHuxfUfiROf+ciBC6PTAIhAIbiIAQkT4POWILOHNR1MOvKClMpO51kaXlQBAJ8SGII","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":573371,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfu7a9CRA9TVsSAnZWagAAx30P/3TSNX+xCqxBKpJ4juKd\n6N3oOpZIz6dGNxRMTFsvKWBmfdN59HMFHfGsIR2VZwuFsO3L3TENb3EjrG7Q\nPxXV742/2oWmsdw27byV/tHFK/G5GOTtvpvSnVsIao7WZ3JVfIPrXU+I6ZBV\ntWu++v8HG2y4ThPMqAsLso+HI8hEZ6MWc52z+f1d7wf1BTswZV1Mgx76xI8Z\nInGijWxcSbB2Li/DPwkjlRpc5k693+P3HxG3Z96SXrbVSLRGMLKe3Eon+o1A\nzcZAt9MfHGNMX5H4k1szY8JbColPdgjuQr7SE0jol+A+f6NVdKKNOexB+2mM\nBB2dm250TNUIq36EE5/dP46PJ37SsdVx70Z9g73d717DEwSIlhN4cTmw2sKL\nHMdcdmIJuyrCsTC9a2/JvrCjWixZF7efPzQXM74c1j7OOkATlNZO4mfuaspy\nEEvR5mWW8XwaoHCdkexFFqdAOscEmV6w3eeJ6O6Pi4aVoyb305OfcWP7TP0e\nVXswLkANPW1f6OAQMfHakSrmQoNthh/VXIM+mU9MVqVkQnbuBt25vTU177d4\nnSlpPPuK1Ziv8ilCHUan6wS4ET1xusivUZPLhY1nCO/Srq91bhWiWzyV2cyl\n0bpPgy6xM0fDe1MCD9+Ay7ckGJ8taPB2oOkq30FRcUlScDUr2Zrl1fFRhGpx\nJq1i\r\n=KGoT\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"244155f4efc97504a52324a363463bb159445678","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.0","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.31","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.29","@balena/jellyfish-logger":"0.0.148","@balena/jellyfish-metrics":"0.0.174","@balena/jellyfish-environment":"^2.2.55"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.14.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.54","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.130-renovate-non-major-244155f4efc97504a52324a363463bb159445678_1606137532988_0.8161194013206889","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.130-renovate-non-major-d852e7c639f108f37a81b427c57d68e5a05d702e":{"name":"@balena/jellyfish-core","version":"0.1.130-renovate-non-major-d852e7c639f108f37a81b427c57d68e5a05d702e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.130-renovate-non-major-d852e7c639f108f37a81b427c57d68e5a05d702e","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"bb9c62b1d37fdf7f05ef1c3966aff4b2ea6cc695","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.130-renovate-non-major-d852e7c639f108f37a81b427c57d68e5a05d702e.tgz","fileCount":94,"integrity":"sha512-+mxMTOw+tJftotMDDm5H+S61sYRzG0FiybkW1k4RdbXQ1Znk9lIrnfHPT0rZVoHOqX6L+ecgpKAn79ukf8K/rA==","signatures":[{"sig":"MEQCIFn/S96Xid3fHqRK+RR8iUESCgO1shRf9tkishcHKO6bAiAHlcK1N2Wi9l82HRMN580y6TlZmiBL9DinxziknhB26w==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":573371,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfu7fTCRA9TVsSAnZWagAAFQAP/3eB4/6+YknPbcrgfHZg\nHHWO01dHN0YxaOjroVqkYjuNzm4L0KBnb/EdB5gtMy7juPhPkHixHdxaRSrM\nHPxVVvuXwns1dpB+h4c00CyVAsQXovy35fPZ8YdmOt+tqa8Xa1xjM/pTNdr6\njnFaX0B6ihYDxPB2ttVUUBBZQwj/FK8yzmYit5aRH2cBmsKBmhv4zgDqix1K\nmTp+0jsT3oNYinI+EqlaLRfFaJ8WhrzH4vMK+VtFfyjGkeXOSOEk5ii2fqlA\ne+jZ0S9GwWEF48p4z2rBXxLTlpwsvertdpsvl+i7lNfYvDVlTwrgvyjaOS/L\nFq8oxzoTLXGMIVGu527saNStLYEec4zF1j5iHZ5oKUoSJydK9UC+t65ygkBD\nFaOjWmxriQLlrG/c8d2nVsFVMmesx8UGxMK9a9aT/aXxZxJ7RZM9vMGvx+Kg\nwM1pbvR2QHirdspAzEFW1z53Lec7N5XEpVy4kYm7QfKtbbotVgqxAjgffJjr\nupP5rdII4w/0APAcZMGfKIaHT1MTX0Dkml0GGSTC9GgttFIVoN+uITPV7mJ9\niBz/KwGRp+s5n+6v/qc8phN09EZihn8A/LBYiPTQbH51KVLh4s3LWGejptA1\nymRWpBwyqOiab3UAHlXoOM0UQLx8ELvwgUeXlJwQQEm1B6KTxyltoYdVkidh\n8rq1\r\n=CzV4\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d852e7c639f108f37a81b427c57d68e5a05d702e","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.0","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.31","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.29","@balena/jellyfish-logger":"0.0.148","@balena/jellyfish-metrics":"0.0.175","@balena/jellyfish-environment":"^2.2.55"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.14.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.54","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.130-renovate-non-major-d852e7c639f108f37a81b427c57d68e5a05d702e_1606137811273_0.06567932441881674","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.130":{"name":"@balena/jellyfish-core","version":"0.1.130","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.130","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c772b598ce53bd01b5814226e788a574f4c19383","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.130.tgz","fileCount":94,"integrity":"sha512-hPawam07sDVVN1TR9xBH+AU5JiIcx83IXHNcjV7ZymJfL8QOsE8vt/PIb+neQ5oCtAjdJEugID4vEqDhctz69A==","signatures":[{"sig":"MEUCIQD9qSIJ+bD3NyBklCbeEG3kTSMuS3h2AyFSqhJE5E+obgIgHYDwp3Fxiwft37XdZ/sEN5HIZ4kkQQLM5GwQ+SVyw+w=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":573311,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfu7kyCRA9TVsSAnZWagAA27AQAI2rakcW1ArW3NkpXIhh\nxh1qSZ+jlZZzazgklMOSOER2hudvbijAXLTVApy9DTc6KldKWYcTZADX9dDb\nsG8aPqznYf8iovv9rz7MfPYrmOFHPnEd6cQiaAeoW3Rzjq6qvz6qhqVmDKnC\nWV0wA5QSCk/Bjm6eITbf5Ez1avXlHz2UmAJhkPyWvA2GSAMKCBvw+8AaaXnd\nT0jlrqilffHmEpEUjON5bZvHmv21vxsD5UWaN45a6GXr4CJNmpWaaxco5/Db\nEoUprZfgPCccbeV7K1mIwOBuGCjCCh7teoJ55qdpZaNRwYW7oNE4Tp3XIImg\nKlqoEaZfSq1m0vQ904NDve7BslXsyusmH7v/x8ih5nsO4SxJlNeOYAm1m2pO\nEyGXNfPhKbh84HCsdLwar+9xEdFgpRQnMOenW1XGeqp7zmNb2o3ySywMZ0N8\nnKic0nsB4WEnwzTsNDApNyY1IcKYGF0/p0aW1d+/jLq66K80NNkvnzytLRRi\neQ0HwNhcCKWdp/RRCxdZEzzdq2PFuSmZ9Rm2eeyU0l/9SOAy3S2M2ugclPjA\nyL2+xxlFWWQYS8Kxo71nb89iCfEhj4sA2dusNJQTKu0vExhtanKYfmoSqacX\nwPw66hFFi2FADiaLvHMq9QA6CEJpuQK5hC8wDFzWfKvWuTdsSauw2ivJxSmg\nIxPz\r\n=jIGn\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6373a7f74f5ebd360fadb1104d988fc6e07f1824","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.0","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.31","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.29","@balena/jellyfish-logger":"0.0.148","@balena/jellyfish-metrics":"0.0.175","@balena/jellyfish-environment":"^2.2.55"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.14.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.54","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.130_1606138161611_0.21338768813984843","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.131-renovate-non-major-ae614ba047bc8ff91fe0f7d3a332ad7920ad6641":{"name":"@balena/jellyfish-core","version":"0.1.131-renovate-non-major-ae614ba047bc8ff91fe0f7d3a332ad7920ad6641","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.131-renovate-non-major-ae614ba047bc8ff91fe0f7d3a332ad7920ad6641","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"180fda0bc2f0aa5fc718cc97e563c32be3e4823b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.131-renovate-non-major-ae614ba047bc8ff91fe0f7d3a332ad7920ad6641.tgz","fileCount":94,"integrity":"sha512-Hu/CzYYC0fEciaOpnyV0DluFAOJ3oBoU3VnAa3Rw+UDIYrmQ/5MkPgF5bOSOU7Vvn+OemxE3PvUd66qQeLBqMw==","signatures":[{"sig":"MEUCIQD+B9bd+3s4B5c7Iav1HLMnl6vvGgY8Vw5VmrPO4r4qZwIgMkxEvqaMu7wK/vOyFQy2eA7Z+hXyr/KoYRMHYVdjW9s=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":573462,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfvJX6CRA9TVsSAnZWagAALgYP/3Ti4pkU/AZxHSdZ+xYk\nqFe5XNmdFZZYRgxbJf/ptX+O4KAAGNoxYIPMWP4pwVLKxjDP9HX0gtp/W9fe\nYNXdyOgEhtkEPaDfy/eMvMMnHCxOO+kr/XbEEbRa6w8l0PVlt5hBc0qZkrEw\nTqGxxn0/9z8wflmvOgh/3actOgphjXLpcrdM4wAjUbeVSP24ucW0AEvkvQmg\nrdpa9NOwSewyxsPpnkKPELL7QUItcduj9KuJPfQdZ/ER3TeAXEVl7zHmIs6v\nHDkAVyDVsUA16zjn5L7j551VhHgFJCv0c6fmPuxI4NumxfWhJ/J12eB+Iqs8\nDCWAJe6JgKkOR1CqFyx6XeixhWBlFFVLeFYaEBDf8Oa6vWHBBKutBAVbYmz/\nW3dedCLUVrN8i+FAZzk9nVjNaGwb1cioV+1Ekt2/biC8b+W1x1ZM5xon6lWY\n/bjsFJ0Qdsp3NPRmtu1bRsB5nmUKzA9DGT6Otch6WWQyr4pohDPYYX5iGI2B\nRJCMlBxG3Wuao1rZkkYpr/SHDPH8FpxQMcUxABd98D4Xwt2cqnjvXmwuIDD0\nAJfxJWmB6aWl2VvY3s1x+pOZ1S6NALo7jzkLf9lKi+EXe9q03Hj0ZyURdwsO\nxLg5ok+hpgLGAIRVvcljXcJaFHisWNYjhlYfKtKO6c7KFe9Gl+s/XzAD64h0\n9SRW\r\n=HJg1\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ae614ba047bc8ff91fe0f7d3a332ad7920ad6641","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.0","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.31","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.29","@balena/jellyfish-logger":"0.0.148","@balena/jellyfish-metrics":"0.0.175","@balena/jellyfish-environment":"^2.2.55"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.14.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.2","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.54","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.131-renovate-non-major-ae614ba047bc8ff91fe0f7d3a332ad7920ad6641_1606194681950_0.15641768552455626","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.131":{"name":"@balena/jellyfish-core","version":"0.1.131","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.131","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"7049ed43e343f5d14e2536b7f0e36d9948b3a336","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.131.tgz","fileCount":94,"integrity":"sha512-mrj2r3RUXJWePszhHZF9o13ajZ+k3ZFdtWdGLLgKqVwoGZddvc4sQwcg0Y6tU8NJYtdRNV21YKooMu9ajmZy8Q==","signatures":[{"sig":"MEYCIQDNK4s6RLdGNQOkxeRNTA4hyYsPUC/IZm6hxS9Rk6buqwIhAKbZBX4q5V7KZ+QY4KpKg4fnRuqZIROE9niVrFmH7RTh","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":573402,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfvJcWCRA9TVsSAnZWagAAQtYP/02RvuNxE+Q19oTy5jZ0\nf+LKigetxx8pZWhv+S0qWK4GS7GMo4VvhFbxsaJfNN428jxfO3B7kM4a7Czi\n/M89kcGRhTb9EaLz8TNKv+0bwY0IwkbzzUzb/wfFw/1716DxfGH2ZaL1znIl\nHi2t8nbnOCnvKegqBTDhMMVMHZiT99DeoKKQFcrabmCj0KsK7LQjsVd3dJLP\nD7IrZveWmSFt17Md7trA10XES1R8gBGQn0MWJrHXk6IZLyph/Y7msNhsqMZb\nLuYaeitB1Jh/KddyvQ1apEKDEkEHzfQ/Zy/cZqLgU0YfmW1l5CuWBzWgo0PA\nCEGLIa9xxKCq5KQIiBpYDMe/lhq/lyCsdC58oEK80oJ0XyPJXe1Fv8Z/laXN\noedHa5ugvtToUiFo+buAMbaIvVGJBvffTZix7ZEkBTtrJrmtuem4E8Z2QRI+\nRr4KAMZI1BXvOnte1My7jWuUdDRSiuTIV9KN07QTq2SfS6ctcBSxDjzUyfZL\nWeQaTjKaqWabPYihqfFMk6bDURgmLjMSF1qBM+quTl8TSf3zpfpTNudZ+Kh7\n8fLm+Z1PUvybkv05ywZ2MMaCZwUIaZBCMYpLZes3jtv4Tl3T0e0RVD5R7rXV\n3bCSutiDtAOr+dcRIjcy1HiWIb1/rW9hfnjXk9B934IAjT73JSIgfVWOKo5N\nPFm2\r\n=rqQ0\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"45a6bdc7ef78a743faad3979588ac7f332563e88","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.0","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.31","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.29","@balena/jellyfish-logger":"0.0.148","@balena/jellyfish-metrics":"0.0.175","@balena/jellyfish-environment":"^2.2.55"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.14.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.2","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.54","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.131_1606194965375_0.8519356170103762","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.132-renovate-non-major-8346fc161ace311c987b45753c9ceac98dd78659":{"name":"@balena/jellyfish-core","version":"0.1.132-renovate-non-major-8346fc161ace311c987b45753c9ceac98dd78659","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.132-renovate-non-major-8346fc161ace311c987b45753c9ceac98dd78659","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e20ac60df8238ff8f6178eb535e14a09378dd24e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.132-renovate-non-major-8346fc161ace311c987b45753c9ceac98dd78659.tgz","fileCount":94,"integrity":"sha512-26jPjy3+stmjo0cXSxPhXaAYJ6kMfMmpINWOZY3LQrSD60x+qrdbQWDKs/j+8jaNJDtQbXNuJ319orSM7tpiAQ==","signatures":[{"sig":"MEUCIG1ZNY2zczDrrruCEd0sMJf/nf7IZ990X3SqlfX2AFzTAiEArYpwsTwcynvVGjOU/GSNZhxsymrWT/fCS6fbnXFGPoA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":573529,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfvLOxCRA9TVsSAnZWagAAaY4P/2cVJYcS0a+6svRdHjG7\npHoBQDXNLyEsKpcBdzuT+gUPGFB3QfEziyFRPGgkFooOKBZLCGvetcHEWw7V\n/H0O1lqPDnrYmNrhgSynuXouEANColUIp6xz0vKbguP3gTSSZRTq7uYNi5CG\nTbnKBaHyoI6mvNQOrGOetdsYwwiXzv2VRU3ANYoKAbE0ih8eA48/lVNFzN+0\nRvOqbuyJNtkNYu6xXkZAIAOE2n+NvZb7ksNP1WzkrroQ7dCrFGHRdxuXChSu\nV8q8N/1B5t35B1ZpptcYgg7Mue7zLtfDy8qDvgUYzvmcOLk647vR/9ctZmra\nCjSbcUaqc4+bVT6Nxv+42xiVAZB4slm8b/PUrvt6MHQkI4Hj8SImYXJ4fP5I\nH56U0masJOJ82tov5jn9TLaS4ZmyOgQ8z27mphSe4vEc8tONWRbAgTKgjbHJ\nj114rqWuqJK8sGdMJyKMyO7YC6fK53/ceADmCVjWOFJUTWjdBbbkND6LUxYU\ngFosI9zkJLtXKypk71hhMWduobyT36q7quy+m9YUSZ7uZx0dmFQoeKGkVhnn\njLYm1kUKmvsuaa7Ldp5XZpWW2bsozm5LsnaQo4jKf52z2XxrpF/lvXn6j1/5\nOGYfK2MhAJPopaFlD7eIJ5oufUVHovYp8gBejcN4xQgbPTLV1Z1gW6n0g5Rp\nfyTf\r\n=wrxl\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8346fc161ace311c987b45753c9ceac98dd78659","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.0","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.32","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.30","@balena/jellyfish-logger":"0.0.149","@balena/jellyfish-metrics":"0.0.177","@balena/jellyfish-environment":"^2.2.56"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.14.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.2","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.54","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.132-renovate-non-major-8346fc161ace311c987b45753c9ceac98dd78659_1606202288551_0.02167380108579353","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.132":{"name":"@balena/jellyfish-core","version":"0.1.132","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.132","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"77c9b412e49a80c1f7c01e8ee36ca61091791752","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.132.tgz","fileCount":94,"integrity":"sha512-UWvGZpARm9zoTvhem+B5jivR4OMYsGnlhRvP/AfEMm7/dRpiuDItkl8yYv3UWrDH1AQmwUFRZdCRV09jaktOGg==","signatures":[{"sig":"MEYCIQDXlRxGKaVYha9Im7swY9wE7nvMHu1d4Ry02G7KPT5pzAIhAN2mpgauBayeQZ0P0la2QmxaRjqQpuNPswruYZ9ah29e","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":573469,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfvLT8CRA9TVsSAnZWagAADmEQAJF1d9ITf75HWMffXr0a\nGkLpDW5YVPXmTl4sYGg4YtT7suphXBeJN9Dyqdffhu+XykbC4gyYB6bOCROF\nVNUXIQyZCY8e/k530hdqxY8cdp9YC/PZlbsANp7U0pHN0nLw0Bnj3uwNg27n\nU2XqgWr7H7VYYQj/OaZRw1kisHn5XYsudvUbWi2/fY4zkskW2eoW0Zm2XUYF\nKY/Y2v4Oa1Nd227KF2B7fpXet2Mpb/mGcyWRy4JlS8WVNYjeWyWC4T4SExf4\nIublG3tL3+G51nDdBQT+VXd0kSDYhxCVRkG4mUfXqvPLFz7ce7GXOXuEttdT\n/S9CtcVbvrSoND61EOZJxY1XivCoFRssULQVYkIj5ZgDo5y1MGMHtFzGFyWd\nP52X7WK9Fs+q8sTXr2Btk7LwcrbI8vAjbo564t4FjYoND/c3qNOd8FURv8W2\n+wolKqW1Y7NZLaQ//DulAqZRY3fbjIGtI3gzNtoztN/FB7RkHAHTp7wQKw6l\n2d1qM1umh2199xCB1PkWBoWvCQberuQ632cA3mfCFfix1aVijMKFgCIQyhcd\nIE+qNxBPJpdGeRlQl5GmqAjF8/Bh5V5MsNatJQBhg0nvyrxlq8nfCwwUv3IZ\n8d4SvNxsygvgZ0Lg6ov2MLDJ48Vyj9JDicoOdCnmBO5/vlW5TrruaOA1gxq2\nlgt5\r\n=TMob\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8f1c5015538381aa41a52e98459cfab2618cd8de","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.0","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.32","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.30","@balena/jellyfish-logger":"0.0.149","@balena/jellyfish-metrics":"0.0.177","@balena/jellyfish-environment":"^2.2.56"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.14.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.2","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.54","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.132_1606202620225_0.11582998528403321","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.0.0-links-aggregation-10e70839e1ec2ae2b2e3a21d1d0601f3c60bfd15":{"name":"@balena/jellyfish-core","version":"1.0.0-links-aggregation-10e70839e1ec2ae2b2e3a21d1d0601f3c60bfd15","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.0.0-links-aggregation-10e70839e1ec2ae2b2e3a21d1d0601f3c60bfd15","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0d7721d298878b77d41dace71056167e8d4af4e5","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.0.0-links-aggregation-10e70839e1ec2ae2b2e3a21d1d0601f3c60bfd15.tgz","fileCount":94,"integrity":"sha512-MvJ4qZ1Rnbsdj460S4kPY18f6V1gk1//ZvIjr0/VGrnzWpbHiCr2g/xgWIf7D22myupih9su5yE+ByiNfL69kQ==","signatures":[{"sig":"MEUCIC4VpFJUppWxkM0ShelTLZNaDcdhWiKsBb9K9LgYwtqtAiEA/vtENAY6DIG0/XdMzn6JYGKdkUvwP2U5lebqhs84b1c=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":573660,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfvgTDCRA9TVsSAnZWagAA/y4P/0ILrriA1qtdwh0A1qtS\nn5PLJJXc7we5Q2XrEyl/ecq2O+wEKH35dW3uRa/2zjyrGjvwZ87df/7itope\nxluhCzndcmQnMBOQPHx8hUdXaesZ9lbPkIEoT2s20ELjaqvgUejlT6nFsy0H\n4DlZdFjxBcTjCO7Jbt76n+bRJ3JcdZGqGNaKymtSvUyhl0vTgfvSTeFz+6bR\no6q0yIv8xMd0oZk8siFSoTc4y8Y2t/p8kbMS1DMe546whYO6fibGwzX6f5el\npyeccsXs5T3AUnluUfZBGUOj987QCKn5FTLRDajOwt2Qp2y1075c997dY5/7\n0eHsibJJKx6pr/7kxrQaO9GvsiR5d2H+e7m+iO2vbnBthVzilGJSOSnz3572\nLRrosHdZVqDUY/QSrJrVGVCrn1T1a/9GPlcIUvfOIS6HF7OCbhZVW02gVn0F\n7v7riGHGnyTEJJGWUeGyQsVVFacv1GSQbwZWpZ21URUqqgqGVDpDqRf5ojzv\nIh9CUwXn6PGmegev/Sg0yH1owjfFPEu9zKwakQ4X4fhha+WmccnvZjVXOF4v\nYlknnRe8cAXFn+JUg/fqas0OsBdQwNgeDlVUG0Me/YfzS34QTQ2GQ9Yaycz8\niheJInD0q74vs9Ts3a0oLhZkEglX4H2rNUhE/SlHxMsVcE5m6i8TAbrSGw33\nZLeN\r\n=/Dhm\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"10e70839e1ec2ae2b2e3a21d1d0601f3c60bfd15","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.0","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.32","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.30","@balena/jellyfish-logger":"0.0.149","@balena/jellyfish-metrics":"0.0.177","@balena/jellyfish-environment":"^2.2.56"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.14.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.2","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.54","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.0.0-links-aggregation-10e70839e1ec2ae2b2e3a21d1d0601f3c60bfd15_1606288579204_0.8904412690838626","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.133-renovate-non-major-8a0ad80910a21d9a08e43c2232fd1ef1af43ee23":{"name":"@balena/jellyfish-core","version":"0.1.133-renovate-non-major-8a0ad80910a21d9a08e43c2232fd1ef1af43ee23","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.133-renovate-non-major-8a0ad80910a21d9a08e43c2232fd1ef1af43ee23","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"cce8775f7c69f1e69976740bc4bc7702e762418d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.133-renovate-non-major-8a0ad80910a21d9a08e43c2232fd1ef1af43ee23.tgz","fileCount":94,"integrity":"sha512-p+CpEVtnuXJcmZ0s/hc0NcptBjyKmdK6MMH7fnL8mdrVjhQG2jpvz63R4CEL1o0r94cQf2Urgaxgj9NC/xx6pQ==","signatures":[{"sig":"MEUCIQCzSbrPBWF0mTi7geeXJ04r1BdNW8viGP3K62i9imwSRQIgAravnqjZ2I5IZ28rzvpCr14E90w3GM1FqF7KHxu4UB8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":573631,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfvwnxCRA9TVsSAnZWagAAhFgQAIGQU6iyj9HfE0FUznNg\nEphhCtkCC4wqA0icH7HF6itZJuyYkk/JoMIVYNx0FIZ7IM1NFLh2I/7/v4Ky\nRxHzMmVdAvr+d9XXNzrfn2V3A5vAiLeHhJIyGuu1PZmGrUg0XNkoRW9J9Osj\nRr2R1uUl5oc/e7NEv3Pv16/PqW69EE0RD54X0D4uzazsZRKNf+DYqCgD/PZ8\nlhaGS5ZROHpacBQTqQO0aaFqd/sgDgyYikvHrIM1iaYWZDI5IoayQzilX6IT\nvIwDmYskTMY7zgZD/ZfeW+Refp4iFyW9XLUAiXp6fNfN2H/PI075lp+d/j+D\nTo3VYup/hG4214vwgWPVyzT/Kvki15MYJG8siaUGbO8sLs2Y6reOaYM02KG8\nJgF96rO/DaI6wlrOSdklFUyw1hq2dNCOw1oN7DVjUbQGH0RLuY5+vYdxvp32\njSJupTjsz/NhrCWXez8gf4uBylqwVYnBycOJFDn49v98z/R3rrAXQkp9s8pf\nTqKXLyWx4Sqw9f9FjzExB7Eoq9We0j3LPxNb1FkHCI2Fk6JyxBAT3UfeCR8E\ncGxIrc1wLoMfKmG/L65z1XeWLH11KTtHvSkfmB/rN7UuNhEIfC00paPaNYm8\nsD6/Y7cnbNzzRUdL2rfjj3+C5BDvSc5yakCrF8xSjQrnEOe5+Tml8wiDqkJm\nMsd8\r\n=VPTd\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8a0ad80910a21d9a08e43c2232fd1ef1af43ee23","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.0","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.32","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.30","@balena/jellyfish-logger":"0.0.149","@balena/jellyfish-metrics":"0.0.177","@balena/jellyfish-environment":"^2.2.56"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.14.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.2","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.55","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.133-renovate-non-major-8a0ad80910a21d9a08e43c2232fd1ef1af43ee23_1606355440502_0.7237066477424408","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.133":{"name":"@balena/jellyfish-core","version":"0.1.133","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.133","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f1f1774d087c95d6cade0997bef9b9c69d5fcfce","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.133.tgz","fileCount":94,"integrity":"sha512-d4zoQ+OjVSrR7smYe0H8lFhELG39mPXxeKxoXFtlLU5UbWTrYYhq7XvOGSMs52cEQpyfgdA1ME3xZWEUxmX+1Q==","signatures":[{"sig":"MEUCIGxYCempgv06Db5o0YEQzbR+jIuxFm72WrPfWTIpIX8EAiEA05ky0+dUYZaj/OeYPg4MJu3mjTrAcrIFJy/cty17H7U=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":573571,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfvwtkCRA9TVsSAnZWagAAvs4P/A991q585Ju2rBAd9/CA\nsUhobAMV17TLuuAndggTiPGCIUBx3fELFxyp9HyG801aDXF5W+AdkFKB3Flb\nx8NwwA0Qd4E/Uo4lDJoU4JVqXqaMEYaieqcoJlUmupRbdlWzmDcPUPN01Bw7\nhUuXj4AyK8hA5tDLYBIdbUxN8TT2bdWgUunjGfgKWb+AsfV1q2/HlAfyb79G\nf+3DXbpcYqw7IIy5rKs/zIkH8Lx07pw4l2pnTbjKyMmqvuKWaRTnUsgljHID\nl0JN1z3D3WVWbqFXvO6bU9Tpt6NiAD+QxQktNaroUv5VuKNo6ps403On5X2s\nResJsObKfpWeAqyyCzhcCfnQxjsRlO5B1/GsEzAZ/Iq2z9ZQsw4YW521vExz\nMkH/XdSd+g9LcHdUGJ8mbypfEFLSy81Z8a3xsgWES3SK/tKpSIzP+YDOFnxY\n9wo+tStCyVgePMK09cyyfoheWOgSCDLiP14CfGimPwN9IvZ3iHwFVdg3WoX2\nQ14sbxxoLYXfTvh1MLI7vXnXQ1n6jEDkziTvb3LRsfjsgI1e1dUe5iXOnGR+\n12B5kO4mjZ0sNCzRi3KRz5LAZ7/SBgKjXb2v2Ji+HgsLqtaqtFsXm8fwT+TY\npX64CWy8Bqm4xslsIxTI1vkjD9ct42hKZAsrFESLE0zB2HQjcA7GdeSOw8iE\n9hJC\r\n=8WGm\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7cc1218e9e601899c1cdb72f67539a4fbb136686","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.0","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.32","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.30","@balena/jellyfish-logger":"0.0.149","@balena/jellyfish-metrics":"0.0.177","@balena/jellyfish-environment":"^2.2.56"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.14.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.2","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.55","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.133_1606355812334_0.5402176679877075","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.0.0-links-aggregation-4231e0b454b11b4b876b4d1393aefb7b4f0ed42f":{"name":"@balena/jellyfish-core","version":"1.0.0-links-aggregation-4231e0b454b11b4b876b4d1393aefb7b4f0ed42f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.0.0-links-aggregation-4231e0b454b11b4b876b4d1393aefb7b4f0ed42f","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"61e91958360ae1078db4b906df64b9e227ce8717","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.0.0-links-aggregation-4231e0b454b11b4b876b4d1393aefb7b4f0ed42f.tgz","fileCount":94,"integrity":"sha512-1Z4PLq6OQsHz8uvn8G6oCwb+NGl2wC54kjv/41R0S04G8ftF4Q7vdnolDLfOyrbDK6e/+bUHwxM+mRZ8xSkQKw==","signatures":[{"sig":"MEYCIQDtp4zNfX9MWh8JUYNlR0nWWN7k/0UqyC84lXTrob25MAIhAOzZRQxrfmDhsdK+e9ZAny+PjD7tm9QskkSdqoz8aOoF","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":573762,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfwHeMCRA9TVsSAnZWagAAdj8QAJnYAIKMbgdg9s3stEpp\n21m0UyZFyJcQgVa/zGq72a3QcKlsdy+QYjlzU7KA0sBKFCvMOGG3XMAV3Sis\nQ33dlSus4sfUxloW5nwaP0NE5xHsl0DfIyBOG3J3rZqZEzNSu4gct0aJZeUH\nSq6gb7KqpChGRcysgnd3emwi/bA2HlCrQhTxNQ+YDnKKxluZ6kABx96ZtHdV\nGP8zpBQRtio6tYGSQGcGRMUbUpBY+AhSBvVzLLU2JgN8ZZ0qY9F2xY/e1ClJ\nrbACojbnWbFQUI+MuKiBYD/RPrMGtMzLCUZW2Pv3qBWPOLWdG4YFybxGKdqk\ngFM0ck4G/lWmIs79GR0BdP9X3K62SLW1C6TPdhlv0KXFzn9SnB52zveoEAcf\nyjjIZZvYosL4/FAPuNVXyC73Lrv3ZOK3CwwbuoEDru9OeNk7Vc6FQmZkfbM7\nz6oymeVuUeNcydXYsQ2mxEfr0Lg0CYFqHvS40fIOPh7CIylx+Mepb4/KWLpp\nbzBpYx5NmufwO8WUJkMwUeK8OmxqVX1Lfrtx0iYxcM7zQLqkMLEeWISCjyqs\nyBNsGA8NRWPOFlu/EQMIRtETQiWi8F9ZS2fcvSbEPnAFcgqZigMal7eXfabm\nsPVOO/wfNuh4bx77vhuduU+/c0iPJJoMlGsGroKhl1M1mBXHL9udqw2TYBEX\ngCw7\r\n=hrUv\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4231e0b454b11b4b876b4d1393aefb7b4f0ed42f","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.0","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.32","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.30","@balena/jellyfish-logger":"0.0.149","@balena/jellyfish-metrics":"0.0.177","@balena/jellyfish-environment":"^2.2.56"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.14.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.2","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.55","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.0.0-links-aggregation-4231e0b454b11b4b876b4d1393aefb7b4f0ed42f_1606449036401_0.9389314588800048","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.134-renovate-non-major-72215d2e4c0a6dbed4be66e5973b0a92c5d65918":{"name":"@balena/jellyfish-core","version":"0.1.134-renovate-non-major-72215d2e4c0a6dbed4be66e5973b0a92c5d65918","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.134-renovate-non-major-72215d2e4c0a6dbed4be66e5973b0a92c5d65918","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d3e3f1780f184f26273f29db8e2c05664e83b2fd","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.134-renovate-non-major-72215d2e4c0a6dbed4be66e5973b0a92c5d65918.tgz","fileCount":94,"integrity":"sha512-fNfYrgMEWd2jpSPvZUZWTIcH1+59FFu6C7QzreWX5CEHvHrZ1aYECIq0Z4rD2SCVO8s67YOJrmI//cOrdCQ4cQ==","signatures":[{"sig":"MEQCIFJRXzMpI40djOtyfwgtSXIS3vRXonW95zh5OiukE6bfAiBxsaxhtjIzE82rEqz4oXY7HxZ+g/c747eh3qVBFlnX8Q==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":573738,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfwK+VCRA9TVsSAnZWagAA9Z4P/2GA8bPXaAe8vqapOeeZ\npULYuJbFkAaBMuTVb6hu6DQlRvVhqQHPySMgFxRnq2F48u/lSDAV8glQTbmI\nc/m5mktPohsPmxOjbZQ59j8moJeGU48qV7dW6YYWbxHDuaeLNxOWTl6RtdjM\nhoMEpfmaZ761ocwpcWaTTP96fRSkLc8vWcyGYuua13ZOwKyxArAz1jgsNEfN\nqA2GhSEat1y0CBRAEb3wZ/esgTp4orjoqMSIzEzHLTrG7/4sOLMiOhMLUA/K\nWazHzb9qsNbA5E2SKs5IMLyB2c5DtKtRnAmHO1VW6AqbnegdFkdqzX1IAoNH\nhgOBDiRtV91Vhqpe8aPnnu9ZVQ02dg9dHeqtxen+/vYlXn8kl+1eCBoltn+2\nzFfYF2Bo0UgnkQ0FokfEYdB4R8CXhf3uIt9yq2h+/NdBKv0PXbSrAP2nVNKq\nOda8DIq7s0/CRoETytaxxVFTCDtVJAihMBlO/760TXIGr9O0tyL1kmJ7XTLD\nq82qlWOFdhZyOgjoJBqKmr+k9Pn4RCdCPVHFZfMVzOptgJxel7W5YJ1q903R\neDQZfE2qBgbBDYvf8zy9o0Zd9DjuY2WLC785wunzgWX5ef9Ti6qBvF3sW4rl\nyhMYTml0ie6HmDlt8JfCpShxd3xTpRaCyZXsd+FONRBw1QRD96IeCCVl1H3D\nrpLX\r\n=W93y\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"72215d2e4c0a6dbed4be66e5973b0a92c5d65918","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.0","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.32","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.30","@balena/jellyfish-logger":"0.0.149","@balena/jellyfish-metrics":"0.0.177","@balena/jellyfish-environment":"^2.3.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.14.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.2","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.55","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.134-renovate-non-major-72215d2e4c0a6dbed4be66e5973b0a92c5d65918_1606463381021_0.13157309456740673","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.134":{"name":"@balena/jellyfish-core","version":"0.1.134","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.134","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"577dfede5435859fda12c4181ff18b7a8b579328","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.134.tgz","fileCount":94,"integrity":"sha512-yg52/IHfbfcOV2feYrAFsoRJENMAtf4lx7V/w5P/FDzUSvArXLQJHJCKI7c8N7G1B38uwN8Xh92/IwiBmHKKvA==","signatures":[{"sig":"MEQCIBQ/0KoXkoUDD5qKCWTu3hdS+VQUaRXoY+C33fTchkfxAiBALSXPthxwMJfOdQ2xyYwC3AeLDcCE9xZqrujgT7YkgA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":573678,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfwLC4CRA9TVsSAnZWagAAg+4P/i25A3E8AyIKUtwbOpid\nMYZz30xmxB2uYMLIA8r7NqQSOwIJbMvRe/cOzlZ2whO93kyG2IB6JS/vHu5N\nProsC+lY1R7Ad3cbCqMipblYdN0bONu0oY8p52CPPoxCdeNyKHFCG86nTsHI\nE1XmZQZ3TZuy4G+WG2+Z/BhZ8cp/AkM8zd44fpyhtWMqn9DGh8pu/jLCMEcG\nZGpqcNwoYAn+PmgDjArYGcnnt5mq4Pqd4wdBWsz/5eGePBvd34NIvrdm57f+\n7slxRgHNnPn/IXL6b2pr6LX4/7siuosBHkfCw+EVvfMToaynq+PZ9WD2XcuR\nhJchaxskUumgqiuMxRnHks8p+yGJEPajQbZ7QClBOSdKntwC9zkTbqT4x5uI\nwz0ksI7DyfqpMNxptLZhw0dhB2B9/oMRIZIJkuSKP9wsXfL4sUuAjszobazA\nzC013SYXVmTcqWa+KMUfuz3xzsAYaVsO/qaMG++6v8Bq0H1vmOihH05zY6NW\nNv+BL3VE434txIf7mAptgTWxOuK1+nOFjbvlH6x7CshEZsSf95tfGmXztFlS\nvEgwv1NT0CqtB1GXIWg/JiMdT1DvoZLxGIVWF2Yb9VjoWFuXW7KIeFSbF+4v\nodIfJrnO1KcIrTC0J8jJE1rB+D/H8RrOvG+ZrzgKCI6YmNdPJS6bajY2u77m\nJzzD\r\n=hLoj\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"964b3c7c7fa0892915ddd661fe222de39dbf295e","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.0","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.32","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.30","@balena/jellyfish-logger":"0.0.149","@balena/jellyfish-metrics":"0.0.177","@balena/jellyfish-environment":"^2.3.0"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.14.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.2","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.55","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.134_1606463671915_0.622019994565945","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.135-renovate-non-major-a6c45e0ff814505a663bbc963b060885202ff4f3":{"name":"@balena/jellyfish-core","version":"0.1.135-renovate-non-major-a6c45e0ff814505a663bbc963b060885202ff4f3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.135-renovate-non-major-a6c45e0ff814505a663bbc963b060885202ff4f3","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"70a6d8d7b10b12f0a6a0a154d416adfbd5b27406","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.135-renovate-non-major-a6c45e0ff814505a663bbc963b060885202ff4f3.tgz","fileCount":94,"integrity":"sha512-OdycouBKgwOwdjztSBULJ1PSwmpixWM6tGbDzJSD65WTolTQwyhrp9u8kNU3us/OR4VtnfJsr5d1sDHvXfROiQ==","signatures":[{"sig":"MEUCICUTd0qShPdibvyZndDiaTkF/gGngSfZKnhMjB/JBmNmAiEAtkv7bLdw9VGSGHx9mlQJY5Q0rZALIQpel1yZX5LMerc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":573805,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfwLdMCRA9TVsSAnZWagAA5x0P/Ra5tyvwwTRE07cx+1K9\nUvmt4Xre6ApD1+zPKlFSUR3YLXwOSLiFmWFVDKKz74zK0Xqkwto4Hz5+MomM\nE0xddUCBJ1ke6xLt2i7HVm51orbk7DJZw2YKxIFgSSM4IFExPLw28pd0LFON\n2qXDVmSc7/B6r4Eb35Z0bIswWns2H8Z2jEZyZOAl93c2CDmhQ9tFkkshSHAz\nUBDJJtLbyaDg/K7S2MtUdTEC+N7jH1US0Idxw+2Qz9jCQbUdVciGmk5St7NZ\ntawMUcWHSJVxHl5tkiEn/DCDj9eDYFjDLj50XR9LzgZF4qn/OrNwgQQ6pjRm\nKmlk0N1GIZuIsBKPnJWfKOGxU3L/nduMgbDnSNuIZocml1ZDTfrUrewabvAj\nNKYSJ3/VKDl+bwf8a221hjlw5wF9S8t2R6CSTjxFeXV4tf9Dot+IZaUk8z6r\nZxAVHhOqE6MqyDCoGVWd3kKsRdeMIOKiAYAiTPiIU3xtIXDaCa3u6QTm/ICt\nCWb8hsMg1/EZZSH46WfuTLFk8dgY5Es90VbiF8qIGQp5wK+lxK8mFXr+Nx5r\n6bsXPO0KPCtsW6TjnQjG+bOK8AH+kc9kS0Jb/MNLuiMWeNL64clC3PvN/FUK\ntZazBjgQuf8pU4Ua/kuEXDNOOGXF702O2z2JUkACVZU2ZG91qZBnjMiZ6jE7\nAA8/\r\n=JEZg\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a6c45e0ff814505a663bbc963b060885202ff4f3","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.0","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.32","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.30","@balena/jellyfish-logger":"0.0.150","@balena/jellyfish-metrics":"0.0.178","@balena/jellyfish-environment":"^2.3.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.14.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.2","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.55","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.135-renovate-non-major-a6c45e0ff814505a663bbc963b060885202ff4f3_1606465355654_0.5598207360527878","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.135-renovate-non-major-9b665114a2c2ce7cc6f863d4471ad588d221142d":{"name":"@balena/jellyfish-core","version":"0.1.135-renovate-non-major-9b665114a2c2ce7cc6f863d4471ad588d221142d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.135-renovate-non-major-9b665114a2c2ce7cc6f863d4471ad588d221142d","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1093f1ffce5b32dc1da4fea03725a06088c43338","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.135-renovate-non-major-9b665114a2c2ce7cc6f863d4471ad588d221142d.tgz","fileCount":94,"integrity":"sha512-OfdXxRbdQ5Tz3MttiWkXTg5+9zMOLOKBJAyj/e4qtyW5svHGvsL2l1ufJcG6bJ7I2Zw8/hO5Jea/BW0nw2UFEw==","signatures":[{"sig":"MEUCIFEw9I0rM4+lMgMysKLHDYojB1AOZeWMXbVy6p49e/EYAiEAnvvKME6iYJ31eK5EEvxE3sC/uZJwi5FhdxZKmaC0bMA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":573805,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfwLhWCRA9TVsSAnZWagAAvqoQAI8T4ZSt51hwKGCGu18C\nKu8JD8C5Zz3FpFaCBSYCuiA+QJnwdwWJsXLdy5KNw1JH/xsMkDXOxSela69h\ngYvVGTkzNX3KMuojlJlHxG2Jwq1D8sLTP6z8YCXhOirhpwUJMpYf6EshVIep\n9IH3NSDYPg8O6K7Eejj7eWojES1VD4wSgVARQ+VOGDNNivFQfQYwSM2opQB3\n6YxehLjp6JJWUVfbwQM63fYoLyTK0FyMOpxW+dvUzJumwQBV2uGRAg1io8bF\nzaB9GgGJyt8nxP8LGRZIwlB9BU281LGRNk6eVfQOwL2zxXJW+WRinZ4hMM6s\nn5dnRBtv+rA+N70q7q4+SlFJRh8Az7PjQyEJMlxSOxl2poDnKjEexlqDVH5z\nvl58mWrVfbjCRRaRA4c9mGXPoE3ZuQyMNSGEdVTYhchgoG//SyxprAQ6WU/M\nfeoUwPIc8umB64V0TEm5ZT95lGRTITdrTM8XzxPdWr48kLEfGKggRq/CtDJ6\nOZ6t0ImYdhg3Gj6bdYnmn6ebbsJCyAT39o1nfrjoWQExl/AzCLcagH48jOR3\nK7I2FZCgOBb0lFHerTmTpv5x9G4/3RM03Rt14mjPKbPMvjSLfDkojw6ajsGt\noiEVr2m2okFXmHHjtX3uZOIZeBCw9216fJpiFY0qjQkM2Z6VEWi+hptkopF1\npb5G\r\n=ovSQ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"9b665114a2c2ce7cc6f863d4471ad588d221142d","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.0","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.32","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.30","@balena/jellyfish-logger":"0.0.150","@balena/jellyfish-metrics":"0.0.179","@balena/jellyfish-environment":"^2.3.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.14.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.2","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.55","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.135-renovate-non-major-9b665114a2c2ce7cc6f863d4471ad588d221142d_1606465621992_0.5742133019004392","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.135":{"name":"@balena/jellyfish-core","version":"0.1.135","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.135","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d13aa700e56482fb2e0aa36b993f247927e91685","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.135.tgz","fileCount":94,"integrity":"sha512-8NLqtq/2BEIA/B1gqI9HEN5iPk8UCgxL8JZnk6ktCINQWzVfPBv5rQ8ZqVckaE8tSeteu0am9nzJ5q2v8B9ARg==","signatures":[{"sig":"MEUCIQDwlcntQ0M56Pj9aGucZOG5E+gluT5SDyQKt6Vr5PMwHAIgON3RCp8rTtHwujR7okV4q+siO9Mqso0e4k1Yjckkcco=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":573745,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfwLmXCRA9TVsSAnZWagAA0T8P/0Mhamxvft7TkQJN+Q3X\nHyaHBRP5EnBHo+G90IsQO/C/czqnjY5iq7RBfqbWpxC84saG2ANbayPmFiY2\nq74rZaq9QXFrBuUVIfu+SbMfB3s0TO0dcwIBt4eQLUM1nHLGveVuzramK8UE\n1yrIF/bSjd9DuR/X94k/NTPmF0G+6VoBVIx/Vaz593lI/OVw/RyKnbt1IHQ7\nB6S1Hh7fArbfMT9NDQGHlkSnw/1+uR0kERzo9VPzVC2oEQ0Th4FXet+GoLZq\nezkml9Ji+Ot8Cn+YBxTn5K/POg6bOehMCSh0WrNu2ot7ieledbuGQOEMKEJU\n6UE50nyh+UmeIEJK3j4Z9jlkScIqPeGFHUDUVqZy+AzpCUMgG1/DNkfxOruw\nrUGfDCRy/MgKZHGnZ47N40JWkEUdZgFV26sdzFLe3YyIFSQMbOYsbxOJkVuu\nCmbIx/jsuNcgcmobmA7QVz/M6z+FHQ2C37m7O+e0JjjEBMEAcraXz2HcJ20f\nCyU2FCxwe4JPLzcqwnWgK/fI+Mwafr1qxDxxXaOYnF7+LykxTDyh2yhHu3qC\nhiU+ZtbUc+UaXoZ5qAEgE32/jSUuvtLsiVXglHqn8dh1DXoKc+htrfnP2dtl\nWYs5IFl6wMXyWVo3jyplhJkfC/F7uRhs5riAV28Q+GSnpwZZL4ycriBcGURd\nEycX\r\n=qwjg\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6f8b41c5dddde51ac566d9126b66c1057a03613a","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.0","redis-mock":"^0.54.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.32","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.30","@balena/jellyfish-logger":"0.0.150","@balena/jellyfish-metrics":"0.0.179","@balena/jellyfish-environment":"^2.3.0"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.14.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.2","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.55","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.135_1606465943353_0.3936750896558412","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.136-renovate-non-major-d3950a1039389c4d5439759067d8374dd06d9e86":{"name":"@balena/jellyfish-core","version":"0.1.136-renovate-non-major-d3950a1039389c4d5439759067d8374dd06d9e86","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.136-renovate-non-major-d3950a1039389c4d5439759067d8374dd06d9e86","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d82f1d76f63d8038240b60bc54e064ae872f3868","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.136-renovate-non-major-d3950a1039389c4d5439759067d8374dd06d9e86.tgz","fileCount":94,"integrity":"sha512-NB7SNA6FnV6ptRLjwkbVwYAChbr/sT2uZapOQqdDMf7bg4j+ck+eVgZclof63nXF1JY/oXEN+6NHJk0yf0IHpw==","signatures":[{"sig":"MEUCIEQRTyDSIegePWogTqELQN9FrJyj9sTjuKPKZwNjhmwbAiEAp4gpN+JdgurBQmcvxe5V588wVRZqgGyxavXZFm7IcI4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":573895,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfwPSyCRA9TVsSAnZWagAA+W8P/ja7ye6PYE/V4u9JR+E6\nzNLPR0FLQdTFm7lm+HnMwKDTIxVRk+/XDEr1LU0S4FJVvPVVZk9VfG9+MPVw\nEX/1Xvgu77mCr18t+KszCuJKy+ijKx8dddBHrguv+hitLJH+eqnbOr9gBNoL\nG6KVfWNxYZoe19V7+2LXAsZ3qdb9GNgfN3JQ7z4CkvbaJV81tOLMgts2akOm\n3ah99WgEbqvUjyB0uj0R9w8TyBOjHNfR24N8/C/FLT4Kih7ISiUBQLjX2BBP\nR+kIH8uaC7u97RBkSC+jPHUr2L/44c4nesfmnMwDIm7EMcXQWxeeZG/O503x\nyXXB+HvC+Mw6OEawHael5dRQ2NnxP76rVND4f8fAb85YW3Y9krARujnIuucX\nLec5PMAt7FEWqNyMfQ0mH62eUfoo1lj4wL2TyzEvRGUOZ0eU017AH3pgYJHg\nzKrlBINbkNbatZob/Ojwp4WQO7SPkYYZdq3ehQ6haB9R6K3n1UhPjYJTmN48\ndDRD/5FSaDc3heBqpjmM5rdPALECoDLnS9D5Ty5alUVDhLK6EFSA8/1iNiGe\nG0SgKvB0yyf9XhuLb9e2XtQH+8ejfSySVx6h7OeTVN8bKxLM//Y2iaXtqlfx\njwdvDse5SFM82GKPplTttnCRepAJuiH5yADtHCz2qQfHH+pP3q2pIrksNknC\nTIZw\r\n=a9oM\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d3950a1039389c4d5439759067d8374dd06d9e86","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.0","redis-mock":"^0.55.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.32","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.30","@balena/jellyfish-logger":"0.0.150","@balena/jellyfish-metrics":"0.0.179","@balena/jellyfish-environment":"^2.3.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.14.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.2","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.55","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.136-renovate-non-major-d3950a1039389c4d5439759067d8374dd06d9e86_1606481074486_0.9724546997534542","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.136":{"name":"@balena/jellyfish-core","version":"0.1.136","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.136","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"846aa137ad44d4003905e85119e017fdd17a96c7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.136.tgz","fileCount":94,"integrity":"sha512-GS7SztD4P7RytXcP29hbWCmiIuBINaEoNhkJJMvm7SvRCAPFT8DiAmFWED6dr1QZlqEoM7IfKkQcr+yxIhIaRQ==","signatures":[{"sig":"MEYCIQC7O5te7wUio5QQrmTRdhEbW8PLhqytg+mDOoohf3XhQgIhAMQF2kNm8uAx7lSzTT+/rD3xcw89tGXtR3HM7pOnS+Ke","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":573835,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfwPXBCRA9TVsSAnZWagAAJXIQAJyz6+RgxsV2XO55Wn0I\nZivKBk4p3FL3c8gVSPf97lpuQxJ6MGCNUp3ZALu2aAtsGeB7ZW6vocRVL2V5\nb7kVX282hLvRYkDH/51wsBmNutPf0Dyqd7OpWKSusOrvHUS4Sywwd3mNM/nd\n3zfL2lpBmOGUbpe7UG5rji4OTUuCN/tr0yAQgyYC+nHMZ6cSGlPcrbBfeHY5\npcZc6xS4pelB/Cn7dIE6BV57ZgNLbgbElV+WYMZ2X88kshvavV2PR/0FHafx\nkpuSt96JLbqHNelN89/afjjO//w6ltMFmkVioyBd8PUMHteD/jwxihuAITIi\ngh7iM9YSAJDe7h06OVIAMMNhnQhUwAnraySn6GFhYLk3U7wOYnBRCxnD6Fdp\nny/aUGGvnUiqFlj8mkrnURSGAwU5+ac4NEaE6vfJGrJA3EYpftafRq8Sxd/F\nxU3W7djklDgsz05NNhKGgKS3b4ATMddfFxy/RDY8KI1R+wg2ZcsdhFIiEudl\n1eOlaHc3Mum/vDhyLS3kSyWZ0ESy8FbIcNCiy3YwUuHAufK/SYLG0kJCb7tH\neZDPYLdV1xAK6S21vFDAd3u2og9zfNswa1DwQt5al6y3wzufgR3wmsXKwAE5\nZR7TRhCXh2nIu/xPrcjRhSnyyXY8mSkyNEdA+QnhtW3yNwQi7iHS5+O4HXyr\nuQSk\r\n=sg/K\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6a81754d7bdd612eb642cae5628c84f71985c956","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.0","redis-mock":"^0.55.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.32","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.30","@balena/jellyfish-logger":"0.0.150","@balena/jellyfish-metrics":"0.0.179","@balena/jellyfish-environment":"^2.3.0"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.14.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.2","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.55","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.136_1606481344799_0.2080939949204812","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.137-renovate-non-major-4ab74eb653940cb8efb3be9455bed61e705f53b5":{"name":"@balena/jellyfish-core","version":"0.1.137-renovate-non-major-4ab74eb653940cb8efb3be9455bed61e705f53b5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.137-renovate-non-major-4ab74eb653940cb8efb3be9455bed61e705f53b5","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4951a2b153b2384312c56766578c5057ba2f8612","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.137-renovate-non-major-4ab74eb653940cb8efb3be9455bed61e705f53b5.tgz","fileCount":94,"integrity":"sha512-Y2cYM9xryrVpe1K0XO+E9jpg53u1GD3vAo4vS6HBqTNbvqNH92MHmjEWDBzJGsfoDyb+vikwbJbV+7RxeMVxAw==","signatures":[{"sig":"MEYCIQCpQqejtDaphvQt+IgRBHstMsMLuP6LQ8kdSmuGwjS5+QIhAMNlHgd13BTE4q+s4nUVE43Q3CwPwa2VS5tddwSFXUQ1","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":573985,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfxVT8CRA9TVsSAnZWagAAs9MP/3xQCvgzwC1yFZJSPktN\nHxGyZwMuC4uhMp5ucDRztiPSKEwrmjepwOpHPXERNDuezzoLcYwsjqABnYMR\nDR9OyfIQWqsXGiFvXsRuZGHMefAi7eI2DvzLVPvMAS90ltQlLdnt+OX+fuyq\nryv62ZEOgA0mhiHSHNPnZ8iOVXBsDG2DBbD2ivAoZvwpQLV4dZES121+R0bC\nqRWnAuLZCQmWY8x8XhcdtrWV1zY8oPC9YJSN+UibhIUCVWA6CsddP2jNBgsG\nc7VaJJLIAGKxk8DOwWnbq0e5To7//wm+b+TaCb24jI/3Mvmk4jEjaAAaONol\nhp7+05a5fCDyvGg09XaIwRBbOPsV4AcJNHYtefNhrdBv/sljn4D/3unJNjRt\nOBUWFfMTlIs032GvHhRYTXJ9GBXUWlBvI2t74t0xMKLhZXnpDS/iioUQgNGh\n1YM/rBjE229DCJOTkn0uv0/GNGQWPhW6gcIg0VEyOjkmMo+df3Wu+f7RDzrF\nUwfoW28o3xm1eLfSY2UNeLQpAMqBGsTtDCtJ8zItto+tCc1Ypx9Rm16u0wfi\nKvndwdwBLQ+utxDN7h/N2dwRiBKad+iMhRK7+yPVzRXdduDGIJvlaLcfc6Rt\nOyCl/nU9PHV/SVw9CKEuzHbOUDqmkpo2GTM2NKT+75jcvx/yfF6GRjGzohXe\nIGAh\r\n=sRSc\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4ab74eb653940cb8efb3be9455bed61e705f53b5","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.32","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.30","@balena/jellyfish-logger":"0.0.150","@balena/jellyfish-metrics":"0.0.179","@balena/jellyfish-environment":"^2.3.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.14.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.2","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.55","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.137-renovate-non-major-4ab74eb653940cb8efb3be9455bed61e705f53b5_1606767868332_0.4821383796765113","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.137":{"name":"@balena/jellyfish-core","version":"0.1.137","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.137","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"81d804ac20379233f8ac062490fe629cc60f16cb","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.137.tgz","fileCount":94,"integrity":"sha512-O0GZrqE6C+qJ6DEll0bsugpcO9VDMI3wskkCnoePTs+KVeLPrXiypI8KN9r7GPHQSMBHBWP3kiVn47/1fOsgmw==","signatures":[{"sig":"MEQCIA8bFh26s8ccvsF01ZYFtOhbbiEvksoKiZMirzwxFs2/AiBf2jF2Yazrm8iT1ltaSaRC/FDQOh2Xm/2r96BRpC8t9w==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":573925,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfxan/CRA9TVsSAnZWagAAsgsQAImByRTAf1/wcbfeSIdj\n7aqgMmSkuPqOjLX8uN/y9Yk5GQrgEuyxvysC2G+xA0qtWm7/GWD6ukcvZlvF\nxZ/No2FZsUWfxv0gAWuApjDFvLUfd/LBAAJhVfmm8ufh6RJUYORk4pKDyL63\n67F98WehBa4yY1B+HPhWYzgF78xeTPy2oBq2uxXd203w4+hbD1i3w3Cyse0A\ng/Ha8aOgwhsn9hlNcPygyqNmahDeEVXi+AY/ATu3aia69nvg3RMe2YUnU3VX\nhyCSohti87xJMR3kot191y+3bqZNQSDmEOIca+pEdshcoE18TKN+jLo4sNG/\nESYl4a2Hkgipt02b6ak81pE+lJSb05hvwY/wzAHGLiSRZxnvB3NSxtSfvPhi\n55Q/kw8Fkijtk4QTAlHoZmy1PIP1ldbI5edcWPF9TBfM8bZJ60H2+Hdzr0sw\nlv0c29IdxfosgDGExEBY+IP1WXKyeDUAJbgbvl84eS+rOGwupFEdV7DKJtL4\nLQCDxnKS75bvi7eXo688+C70FJ6df3d6OfF7u99jmDG6rH+3zlsh16M0FR+x\nJrOQVN/RUZAE94MCtPa+wml80PXTR/nk/+o9CshlyMmRo5iZgRjZa3sT7sLm\nY+S2Y6CkEOu9hiKlfeOIWNzYMD4CUg6CiWxayx37XsTDU9teYm2E6m66KFne\nWyCS\r\n=ff5+\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"3ddf18250d837f97e6c1327cf39e1d2b5ea809d2","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.32","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.30","@balena/jellyfish-logger":"0.0.150","@balena/jellyfish-metrics":"0.0.179","@balena/jellyfish-environment":"^2.3.0"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.14.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.2","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.55","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.137_1606789631120_0.023727420767003737","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.138-renovate-non-major-ea5317710c33e5918b5b7f6b5d1065bea625a53f":{"name":"@balena/jellyfish-core","version":"0.1.138-renovate-non-major-ea5317710c33e5918b5b7f6b5d1065bea625a53f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.138-renovate-non-major-ea5317710c33e5918b5b7f6b5d1065bea625a53f","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8004ec56a865f0ad93dd57fc2fe7c37299f7f368","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.138-renovate-non-major-ea5317710c33e5918b5b7f6b5d1065bea625a53f.tgz","fileCount":94,"integrity":"sha512-tRrFX8rXciQYwgW/8EvOrtifeJLGgcK8mGSrnfeSzg++w7vW5DYnf22YN3xTGpvNMWFDV8o7R2aSnlMUXYcVDw==","signatures":[{"sig":"MEUCIQDJJMvzIxl9caIkLLCwlOrqDaqzOiKRuJV9PkfEAQzL9QIgDt8T7xYo2k+fK/s8PMXn9TnYHIW1MzLOFZoeNo4Wyr4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":574090,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfxmHOCRA9TVsSAnZWagAAehIQAIcTE2MwpTn+wASo1oc4\n8DVZECbsqIUePy/fqc6K4IGHxn8BcQCRwkFp/a3ZPXEeIORby8VwVmFRGMIb\nRnc6AjT0iHEnmZ+qPPJO+7VF46RQ18Rm9UkjiV1yIlRa1UyWu5pJcqrT5IO9\nUhF9Xgpvvjb/y5u5y15NelDI6cY0f2pV+9D46ZIVW7542qVmafc0VVeKpsqv\nnVnUMq2nVkGMw9d3M7FpK78JDUhYOrCaLGgt2lX2mVyt5SQEtubN21KNm+vf\n5weccJjy5rc3RRLvKezeAXqai8HgV2JssMAOq++vGrSQd9mTW/7zch+/vFEQ\n42xLUDBGWpPn8sHRYBxWww7MQZUNkzAOLvvBZuWS6E4L3iwxEN8SH+guzEyt\nGzerJDP6PSGnoliMMn9Pc5e/jbwtgm7vRf/cmL2sEir5nMBO4Z33Sh0qHf1G\nKfSmaBWSdsS39WrO/RYNpXbq8HfAN2fE9GzCQElC9dtK1dWtxNOqf1tvzFX3\nUHDubT5JE+x3msKUqTAHN8TE9fJUVrK64irrrNhOLCZ/ZIr0Xre7WC8DCBOu\n6Fn889uI5inuy4UZl7XnV0zWiIhg2FW6gm7Q0CF3WvP1TphIpLGf50rqSIto\nOTCEHmdeJxTf+GDA4yO4M/4hEkmEsqVf8LQ9+XCwRhYFh2x37nrOhWgJjvSH\nbdWO\r\n=gF3k\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ea5317710c33e5918b5b7f6b5d1065bea625a53f","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.32","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.30","@balena/jellyfish-logger":"0.0.151","@balena/jellyfish-metrics":"0.0.179","@balena/jellyfish-environment":"^2.3.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.14.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.2","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.55","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.138-renovate-non-major-ea5317710c33e5918b5b7f6b5d1065bea625a53f_1606836685586_0.6516593931495529","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.138":{"name":"@balena/jellyfish-core","version":"0.1.138","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.138","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"417be17fe05f1583eb64d9084050011bf3c00ce2","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.138.tgz","fileCount":94,"integrity":"sha512-stu5TreSu3RM5DOhxqMjvcyX7SduNjQ3xx0udfr2C0lA/sNGq0zj5DlEFBmZeF+/lYp+Lmrz776b/lTcqm5WWw==","signatures":[{"sig":"MEQCIEXJsXOdn58oaKBoMepG9fNqfXbEoVnjfIY6TIZgo8y/AiBKVadVybJMSjY+48Ns5huYjww/SIKDxnhFHQZ0kJI0Qw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":574030,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfxmLnCRA9TVsSAnZWagAAQvcP/0qnzjym1zhcjNhWN81X\nqNLBI36EZq7f5ikgLUywp55Lm1+qiLGKgPdup2+WGrC8TpSz9/QqfTwWQByM\n414BTAir3grsNvAXXBuc9ZCmWxG2meCw/Z5f+4sLsSN1BEHj8TMXHFerq6DX\nASxh03f6I+B1rpw055OfeAjYiiqeMD0HgmftktCTjTNoLmOsaO7o+vCmLf+U\n/Xg3W/3O2+ift6Os2Be7yDp3HlzoSrI/TP11IsLbix74lU9UqsXSDHOBu/G7\nAgeBTx2emRXadSGgS6WJtvaYqZsNqWaSgGCfQ8jMQnyBqg4aucPxzjhndq3G\nEN4HCZNoi0eBH3j7cMtlcHjvd3Ktsg2wVmnDmtTL2jczLOr1KLm7i5kgZ8UE\nb6Bztu+NnvRdZ8CAiScn6dNzL43GlTb67ZUk/qaQHRtnwmYUZpnoFscLgUhq\nJ2i4E73hD6btm16oSRsK4RWFdYO4PvEAZzuz5T5+ujntKGJK3zSeSQR0YQeV\n0obGA4l2oJzXpsTezPdhqUwkiN0jU8i2mmy2wCqRe5cPIAUNzhmhA2yn2EZS\nN3VIlNBnmwCP2ZxYFD4/QDQLsBvbyCEfWVcfnbDCN2HCWXyGH0sAGanNvod8\nZcvbkhPDmeIe19BM5Xq4uWIY8vhyK4i1Bmd0DHcO+cvEG6KdENswZmhgi6Ul\n0+1o\r\n=VQJd\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"3c3e41921476083204165adee014b8ed19b9e53c","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.32","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.30","@balena/jellyfish-logger":"0.0.151","@balena/jellyfish-metrics":"0.0.179","@balena/jellyfish-environment":"^2.3.0"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.14.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.2","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.55","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.138_1606836966905_0.09268718292778488","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.139-renovate-non-major-8fef61184edad6713522945eedc272d05988eec9":{"name":"@balena/jellyfish-core","version":"0.1.139-renovate-non-major-8fef61184edad6713522945eedc272d05988eec9","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.139-renovate-non-major-8fef61184edad6713522945eedc272d05988eec9","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5cd2fc5914d6a9414f8d5414cd0e0c7fe42e6262","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.139-renovate-non-major-8fef61184edad6713522945eedc272d05988eec9.tgz","fileCount":94,"integrity":"sha512-189uKYee6PSngICRGCxNQC/63ryrj7cyGfRU3HX1iZXISMALP7TXHmvAEIK+y7SocbcmeppuzyW/3jx1NJe5bw==","signatures":[{"sig":"MEUCIAOmZVmj+V2E+0eyWVVwoLdzfo9Yh0Gj5LaaixTUjxYxAiEA0APlbjIwm0FjJsQ8nEbemZSOB+CRELMYL59QfWijzMU=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":574196,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfxmnOCRA9TVsSAnZWagAAEwMQAIeeN27AC6UtOTK8N5Cu\nogcD+1qqHR+at7y5Ya/QOHa8NVSRIr3dxN+/RoSJaH2v02S7lzIRcvMoT0Ur\nEFaX3rMpXT94sD0MIpHHneeO8w5y/uY9xIptdKJ9c4PMfeoOO+m3RSxzvtSL\n2l72NViujAcnLJXuFvfNpfYo3F6lgRAk4UWjY74tVUZTPyprVX8Pw9+hEc2J\ngQDdeFZZGUMaBu5uAc2EdLeM9Kz9Fyw/uX0KthueHnFO6mirrosj0sjepkh3\nUQq1gEpyD1u/1fGY9UzsOf6CFF36zJdEXsnrbwguri1zLKv/RtRMcGQydTXb\nXQaRO5OOx9ZdZ0lerWCJc69d/efqK86RRS4UXYkp4irdFqR20L0jwWYIqHC6\ncLyvjxBlUlY+gS/OVm2yBow/kNYXrUPW8ZTb6SGHzBCrIBbAZ5JK1pPaH4tY\nfhi9Ay+zO5Kf0nRqBR71eiVOdncYa7oRm0bysDKttxq8DzkUgp85JWFIFByh\n2h3b1akWzvqkV3K42/jtYCm8IpjhDftx7HL+XGTNsklUkJ+/SLQd5Qb1DdJS\nCd0hfhhG7jlYymPpKKatu9WuiqT0CEV+T1hViSRgGQjX5v1wSemb7rO2olNv\nCncvfuKFG1096sxHruuW1WmQNqNZ196Fj7fceo2XcaY2ErrtDbNo0BSXV5OJ\nmo5x\r\n=gZ3n\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8fef61184edad6713522945eedc272d05988eec9","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.32","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.30","@balena/jellyfish-logger":"0.0.151","@balena/jellyfish-metrics":"0.0.180","@balena/jellyfish-environment":"^2.3.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.14.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.2","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.55","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.139-renovate-non-major-8fef61184edad6713522945eedc272d05988eec9_1606838733870_0.8890283434592059","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.139":{"name":"@balena/jellyfish-core","version":"0.1.139","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.139","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ec012031106ad0095892d6d285365a3492464b1a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.139.tgz","fileCount":94,"integrity":"sha512-grcW6GKhL3PJrQdououu6Sr7DdXzNdRI1w397h16ejpW5TMm34JZZXx0e7cTaLcaHjmQNMfq4GdgZhIJxecptA==","signatures":[{"sig":"MEUCIQDfW0SBNPNnpTusH4IWvkAa0saBvQ+sz4xbj7oq/eAjPgIgU3iU4y4B28rQWcDFVpaawU88AxfGwVl1a48W95Varlo=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":574136,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfxms1CRA9TVsSAnZWagAAWhUP/isuc4d6rUvkJ3RAViaU\n4eBOvEsLBl4zJO4g3vEiv1gi50CaA5MRjoDdC/UlWySxL2lHtd8Z2lBgPC5C\nIyqyuvdnDUJK3M8mMTr8u3md/mEtsY5Pl0Hi5L3zBSAiTSghX1JwpVpShx8C\nfBGvlYS7QCx4QOhgUSxX92t/KtqfT9RrwOnGn1Kwy+fOUzBuEaBzVZN3iXO1\nJkP3Ph/ZFbArz9BuAAWoBMKWepbAs7urUnM97wJsPPyHut2KOOoZjEjevOyw\n60I968kvjBFeGPogB15U8P3S3SDLO6ijEgHqqd7dlT3fHqL6sRnM7WGO4LZq\npkTL8elnpVRHpz4VX62b/r0n72NjyJb8hjvXJV4ojuf33uImYDd9HH3jyanA\nzaHYluFLGUGv0Il8TB6TBUXLav7dvlPcTcBDcaZ68Clmo6XcGjfHg1z3hZDT\n8X/1+0/h5noIqTZ/rWRA+vyTNwWLJYcpLHjCzUixRM7Q8MiOOh2zEH6KXJuk\nFU14i/wV/oX0fPMQ4xNKScEEOP9KVaV6aYwT90djOybFcr9GZtUiBHXv0rPS\nMDXDEgwtOT2E64hm0mTkoYCZeqZNxw0UFdKOgPYZnFLsES+r0p7Aq7QoIsS6\ne+2ozKFzUkABac6mn82taYtcIfuQECCLMl+JdPOpcp+tGKGRd26+BFLkl/bI\nmOYz\r\n=yTrc\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d2b0aacb5dbb6d778cf77e6e29aeff8fecab9987","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.32","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.30","@balena/jellyfish-logger":"0.0.151","@balena/jellyfish-metrics":"0.0.180","@balena/jellyfish-environment":"^2.3.0"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.14.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.2","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.55","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.139_1606839092552_0.7801615998133","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.140-renovate-non-major-c7045141b6ab3a7d3075dbbaa5cbb269390a66aa":{"name":"@balena/jellyfish-core","version":"0.1.140-renovate-non-major-c7045141b6ab3a7d3075dbbaa5cbb269390a66aa","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.140-renovate-non-major-c7045141b6ab3a7d3075dbbaa5cbb269390a66aa","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f8b957e745607a5c7a91e506c086f253787d839b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.140-renovate-non-major-c7045141b6ab3a7d3075dbbaa5cbb269390a66aa.tgz","fileCount":94,"integrity":"sha512-JpC1nbFMbX/4TQcGilzELQbWvfJtt1VLz1xZxcbqb3doCPU4KWsetuO+cOzn8mNnM/O/scebBkCMX4w0IQgCjQ==","signatures":[{"sig":"MEUCIQCvIB6nIs/gm5uR7lV+35/HtzY5VSJCfeU3LeqlvREoegIgeOwBrVUqCGz7mtr9RXWsCwHgP82c1e83u3AUzuo/W68=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":574263,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfxq+jCRA9TVsSAnZWagAAxFoQAJmbekVdzi8u5QtWxJ9k\nwSnpU85J0W/VSapXirfj/0sXYekeEcTNr+7sIsGu6fjrj85xx4kKSCQYYvBv\nMz3Jy1w/Cm6w6ysMLM20fIPBeDOB5phGNeYr+SSw9ecBvCZiD9/VgjVYSOtm\nJk/2sJZ2ebGzitmbanTOFg9Dt9DGzMXdkwYNZanvhSu+ui4oCtG+uHIJmS4Q\n7H2dRKiAmre35Rh6Du5CKVvgY0k/PUmCH/3SO/jQF1w+OlmSyoIfCL40TUZX\n16CJUuJGuIdnSnsKfdQYADYtPs4c5hNpFJ6sIFiTnakbVkZ2pjtDBqT+HVko\ngS43GRL3PCvT3F6uFJzczPK9WofoxzrpQUmfHR744TyjQJ80uTW2srKLyDRT\nJwxtSAv3gLuOdjhTiZd6ex7QUJD+0czuAJb6MKMO+z3CC+/xBDskoNyRLKzv\n1yr11XbDX7R4xADN8iSADM3AJWQZJ/vhMQhw4z0hvy0z5DScd2K1jcGzJRuh\n0UkdO33UvIHus/Be5Poov6nygb8BEz7kuogydM8CSuEkf9j+4krSzh+Fi6zl\ndY8a5a97Db9iDDO1fdNMphVSTMjo1+/jL7dwu0H6lv5RdMSK5S/DOG4FL5Jv\nEZzcqZpf55XPiD+W1jwALlmjNV5jyx6P4cxLjHsdQMUl6YoZWdfyQZlwO6Gr\n7Ln5\r\n=I/v7\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c7045141b6ab3a7d3075dbbaa5cbb269390a66aa","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.33","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.31","@balena/jellyfish-logger":"0.0.151","@balena/jellyfish-metrics":"0.0.180","@balena/jellyfish-environment":"^2.3.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.14.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.2","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.55","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.140-renovate-non-major-c7045141b6ab3a7d3075dbbaa5cbb269390a66aa_1606856611269_0.14056082517665214","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.140":{"name":"@balena/jellyfish-core","version":"0.1.140","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.140","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"fa740085b48f6dc1a02d5dc9ca5b7bb544f692d8","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.140.tgz","fileCount":94,"integrity":"sha512-71TKnQgNcm58p5mHtK3eg2hi1TYmXnYdOaSGESoCQVhBa+yV9RZNFUQh5Izh+k+Sm8jQx2JnYtBcODXXmGLwkQ==","signatures":[{"sig":"MEYCIQDw5e7Df1AcNmzbyVLtrHTi2e1wJq52CqB7wMQ4iuxwtAIhAPccJyxWMzbwAIKiIsGYe39ZWIUQWh/fu4PUt+4GGN9h","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":574203,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfxrDZCRA9TVsSAnZWagAADQcP/jEwPxpSGMlx5vSfGG8n\npjX+B7LnGvJxr1MddBtDsArtIAI5XBjsHfTgFrPNRuGAba3oY72Ze/adeGdH\n4zZKtPDFq911JcuRCC+DuEuyqdxLW+InLL7jhpJ3a1u1qu6CjBIqpO8O9Ram\nlrfh4c9SBQFMDekDOTZnVV3jwqScucnIz3VP+EEqUxNm00A6zAYAqjOK8BWQ\nxCQeKUj1XxZbrxceEvFTrTZj3IOtB+YsEL7Pyo2hRY2NYgBF6WVm/CKyGD8N\nughwSEigHuPA26OT65C8eNw7QxCQVTRv1hZTHKzzkmyytiIM2djF7aWK+1w6\nuFDSDb5a9+06upH5qSo0jjdKnZOJ2Pc0Dq0dEeDE2s1No17eZTw7KO1gqwVn\nLTXZcuwNq5EFpJ7gL+CuP/z8PQmG+nQ/ubBi2JaAiWXBU1LWeuL7Z6/n/mOF\nhqnKATN/DV1m9c0ztZ3XGcGou+lzyE3SKaEaQArkNtq6v5nbPefdBE/zVvJE\nFigj7JbiU34BFmzIF9u3OT4/6+Nt5JN5PM0hBZGeUFV27403wMPR/Emqq0Mx\nv8G+KxJEe4fEzgHpyAePkmjIWgAZahslsQvN3zURjsG3jO1fBLLzc/xUPCLE\nTKaMAnyEBNDV8tKqCbuivDRJlWfqyXs7RqBckA2yon2j32NpG/WPzH+PzLIc\nfaMu\r\n=MEMM\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"405fea39b4a08718377c49c6e78d9e96742f4aa9","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.33","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.31","@balena/jellyfish-logger":"0.0.151","@balena/jellyfish-metrics":"0.0.180","@balena/jellyfish-environment":"^2.3.0"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.14.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.2","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.55","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.140_1606856920537_0.7460836881494244","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.141-renovate-non-major-3bf64a635c0bd6456bc3c581d983fd956110652f":{"name":"@balena/jellyfish-core","version":"0.1.141-renovate-non-major-3bf64a635c0bd6456bc3c581d983fd956110652f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.141-renovate-non-major-3bf64a635c0bd6456bc3c581d983fd956110652f","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"21460f198d81f47775bfbbb5147d2276ce731b1b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.141-renovate-non-major-3bf64a635c0bd6456bc3c581d983fd956110652f.tgz","fileCount":94,"integrity":"sha512-My4vzCVkxsKuHA0WqjrsH+lG1OpO/8oR0Bmms0Qf0BM2iqwd8q14yxQuOV0o2afU+peVkrKehzI4JG0rJot+Ng==","signatures":[{"sig":"MEUCIFiMuXX4f96f1berL/R7yYL2mjy81ezzHSfDUorRoyt+AiEAn/AG3ACFnmsWgY1S2rbbVV5FLbRECbwLcDw2s2HFkZc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":574330,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfxsrsCRA9TVsSAnZWagAAV2AQAIdKv5ny/FS8krhZckIR\n42boOVt8H/G0BSN+jtczPb1JHNSpmpoBI2fXeS647cuAfeSH/f5/iHKWeybV\nmViFGoRWEgvoLOP21PeQpas2V7JwaKcHwp1ncj/b0/VNtfpsO2pNHMact1+J\nYWJs7KIn7P5L1wtOH4nyOLA92lqPSgwgyOFVt+0jm0mhxaV/UsS2/O0FYAjK\n2AR+M2c8H0nROSjs8+viH8467sN4ZYGkBV8qjysvvLJ9M3UPl5+8KfwQRcfm\n5BKz1gtlVKYf6gc84kqxDNTce5oTqaCS27oiw8zHCk087zGDknB+50rsoz2k\nkOw6+vvi0xqFv5q1tIxHXoyYA+ONvpqDoN4i1fBWAyALAmPthSz7CILR8QPg\nDzDJcoFoBbnOdXcyxucVObOSErhCqD8/PO6wl82YqOiqK8qBuhAWB17nEthu\nYbN4/blMI1/NL7tA44qQRwzXzdbmKLT7kXJejRhI8TrwcmD/yGnnRzg4nsXb\n670q3RLibr4WIrdmPAjiQUQHTBYS1EcxQPMmKvnJb3Bkizvlk1zvYw7NiprI\niyP45dwlH4wUOSJXX8iI2pOCQytpnrmprWIiddU5dlwKu3kCieYJtoJNF2l4\nrnL/W7Jivet0uPBHmFdxESAQYa3DMrZvQGCVOT8ZlRWNwCStXazkLZO6a/uw\nqAe8\r\n=t6ug\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"3bf64a635c0bd6456bc3c581d983fd956110652f","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.33","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.31","@balena/jellyfish-logger":"0.0.152","@balena/jellyfish-metrics":"0.0.181","@balena/jellyfish-environment":"^2.3.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.14.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.2","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.55","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.141-renovate-non-major-3bf64a635c0bd6456bc3c581d983fd956110652f_1606863596177_0.9872054240529826","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.141":{"name":"@balena/jellyfish-core","version":"0.1.141","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.141","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e8e3bfa97977d37cde5abc17306331a61407df16","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.141.tgz","fileCount":94,"integrity":"sha512-LkmAMGT3s519NOfs6BeL0EXj96RKvy23T+crU56eMukcNBewclw12QVunbB9JxsUKyYm7cXg+bruOr0G7uNOxg==","signatures":[{"sig":"MEUCIDEpnMbEv+dwXFq7DDsKUey5K5t9YaWsRbC566wYuH90AiEAy+VoDs8RwUvnJmQy55VWaeisTI0ruzpELCVmRzHNFsQ=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":574270,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfxsxGCRA9TVsSAnZWagAAv/4P/3InzuDZsmYMO/CJWtFI\n2xcdLek6EPubyizo+SdQAI0kUg8+wHZmXqbhEWqLVO6mA0BLb34JVs2eId74\nCLFGB/J0HuXsUDjULxTcvhdwo4FPspCcXff2+po7e3PPb8dXfwd2pNlYFLsQ\n4OhDma0aQ8XTJpVm1IMPgYAXJJQMcCLB9VQT1URNBVm5XVcmcPBXcBtTVjV7\nba2z56uPoxtofkx8Bvs4kYioUYw7QrdGyWVMCZOHZYtRZnfSDLe9bJvbx46R\nXLlYxt3wXXZCec1oJqQWy4p/KRwT9gaARt3AQigYaoXe+2JFxpwttdJ5PjEg\nJ0v53Au99gEnInN9l5LfMizHfqtk3Q3KNka60YydocdOD5czGUJECroT26ob\nIRbyONt3OaPLFZfu07ECNxOP4tREk7WjIdG9GtP2ZRl6VwFksWyGBYl8AHOd\nMkAiqXNC3Rrs/oBen7P/l9NAXiE2Z+Sol+1Gws3ckf/RAI8wA3HVs+/EhroX\n2GGLakgEefpPsS7XA+pKv4b8mAdrwQyZPIKaYAn/fDNcZ1yZFGY1i0sfHn9A\nBNdPAGIyTzGiVDXVP/9R8Bc7+Q+3JHAWHqoquhVU1weMJget+NHsgr5Jc7ob\na5EEEHRBaIMDAYi49ehgocStCQV2hxlBD+VVnNVtTpcyNd2pl6n3LO3xLWTE\nmImh\r\n=llqE\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b7e23fcab77572380e703febe1c8f7d1c6688c8e","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.33","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.31","@balena/jellyfish-logger":"0.0.152","@balena/jellyfish-metrics":"0.0.181","@balena/jellyfish-environment":"^2.3.0"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.14.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.2","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.55","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.141_1606863941528_0.46112691955766216","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.142-renovate-non-major-566d5e94f33ea194d04df60126f4dabe03b6c3ce":{"name":"@balena/jellyfish-core","version":"0.1.142-renovate-non-major-566d5e94f33ea194d04df60126f4dabe03b6c3ce","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.142-renovate-non-major-566d5e94f33ea194d04df60126f4dabe03b6c3ce","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3cceac3e3630e7c770912625a0d7929175298879","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.142-renovate-non-major-566d5e94f33ea194d04df60126f4dabe03b6c3ce.tgz","fileCount":94,"integrity":"sha512-Isw/Q/pIQ+T8FYk8WcMpYDTFWoosJJs9e4gDGjwTzT4s9Wbf1uZcASIQ6XLFL5fTWRscyYTzRrxM0qMlO9hrHA==","signatures":[{"sig":"MEUCIHxlerTJXvFhN2FiNGoqC+QtE09icHnbfo3Z3jDhomNqAiEAkEHfG/5ePAf4/w6Gk7oL5H6E+wFCTlueyQrmce+UlhM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":574421,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfymTkCRA9TVsSAnZWagAA45MP/ArFbdDBg5VgFX4t0vHp\nYeoFJku+ZhzNsEKaw4na2MZLh3KzjVpOEMiCpNWxmyuCtHIXt/km3u678Z9X\nuZoZMbU/KDYrHrcz+XoCqjdQyDJgyi9/3GEqFVKEAmjXFArMSAPgM19tqQtJ\n178J+bE2XDxFiCBR/ozWE4AGEKenCuPJleBhJmPQu4vLVc8BOdYWp6bY6bEl\nakta8jfYIAaNj+aWROHK2DZK5pzsuiO550JbJUgLkGyxeJIMGlfHDtzEs1kO\npIoNLUfdcyNQKNier1rEINFQY7jMJOUSfRLxDdX59XlOU36Ut54p5xs9KTaC\nodSoMFQc2u/Otsq3DgsVHVhLSm9tOlBYEdIKUmf5ohT3HPl+9aRCqzZ/L0v2\nmrVKG5i/bywEEwj+H/LC16g9IsjOUpGO2hyFpjzGJQzT07MkTUBbM5Z5ZoxC\n/uinQadCl3DCwIMQ2Xroz0lzAL5pAA62PBFmriJRETUlhp96oeOIuYc0Z0hH\nR3y/TAfZcVnAqCzvsqJLWREcjixZn+80Gg0xN/DG5aGcnXy1XUL/ITrZCSum\n7hrpDzZiuMxXHMdHLvvdTd4WkX8yzO/WnRm5YtHB+R/NDA8aoYGZDlgQjs+/\nT+D698TR4wcc7VGgJIVnQgnYJ6NNUxZr6mSxWKUyNegUIjtjHxXUXgOrD1eb\nugY3\r\n=nYgW\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"566d5e94f33ea194d04df60126f4dabe03b6c3ce","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.33","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.31","@balena/jellyfish-logger":"0.0.152","@balena/jellyfish-metrics":"0.0.181","@balena/jellyfish-environment":"^2.3.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.14.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.55","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.142-renovate-non-major-566d5e94f33ea194d04df60126f4dabe03b6c3ce_1607099619518_0.1801971675499061","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.142":{"name":"@balena/jellyfish-core","version":"0.1.142","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.142","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a15835cc56dd4fdec585f4e99c5ef4603a18ac34","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.142.tgz","fileCount":94,"integrity":"sha512-g+zLiCv4XvaRbug/qj6b6l1VGf08UVCLFZ0PP3D/1hBBv6s7gb2yzKcBoPAR3Ab4NytmqIPqlag0ySqmwucTvQ==","signatures":[{"sig":"MEUCIETJUctzeyvQOsMI4cQeJBZlu/hxQ4mgnSpzvD+7dEwsAiEA1Z1lCuRgOxznFqs2/ZEHj7H1e4/TEn2mS7wTKhB0D+Y=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":574361,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfymX7CRA9TVsSAnZWagAAmgQP/0fwyann8P+VprN9+BLA\n4BMFME7Vtm/bnyMe0bcfJE6NFAVlRHcerYKf9PenL+4UdrnuW33AM7l+JQF3\nqFjZbc1gaCBvvHL5h2Ti79JfGUvrP0OlQPIf79zlnqGOZaYtHsfOKaNAc3HS\nxE191oNOOX37mKVyGYSYXeD/0i/4QG9LU1uPAfz4CCCun/wZAvnRYd9vbKX8\n5fzLO+tLTuE/62cvlncHn2gfLWiQjQauCa9B2aC3e8KzK5t4o17HajSBAU9s\nZvTOihUJsynkXpLM8fGxjjCzyQGcxTfU6D5giiYn0e4EgbOx/GJHHS6EfMK+\nwOUbYR853hNdXUkCtajoaaSF4usUuTl0gV+zsOxoOcdGySSQeCVxetngayjt\nSbbUl6u8i4Y770GyFdoVNzCXtHGpgvhCk4ik6CHEOSl0+GaEWXMCnnCr+F5g\nfVc0nwkNKrv3P0fuQVQu3DS6KIpfCN4HjgEBQeoQRsdCsLnWMLMpo5vsI2ct\niu6EMzLN9TRe2blMyusmIK11vc80HxJDg43yoNUWgmh7x4vlIpm8WWaV8ekR\nqkZX8dgiOosmrZvkjvqmljtAkju4Ih71prcB7NrMNDfJ8y5DmjLGIayUZfZ+\n0v5399ewkdqQroQEg2K2VXqRa6wMPdhXN2hV1rsTvtiJXyDwvV2DIHWFn5YE\nb9Rr\r\n=LpA3\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"161309f6f81620910737f37ff54478e5f4b60742","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.33","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.31","@balena/jellyfish-logger":"0.0.152","@balena/jellyfish-metrics":"0.0.181","@balena/jellyfish-environment":"^2.3.0"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.14.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.55","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.142_1607099898490_0.7118778495244615","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.143-renovate-non-major-2c8fce784e29081136daef442d58d18c8228531c":{"name":"@balena/jellyfish-core","version":"0.1.143-renovate-non-major-2c8fce784e29081136daef442d58d18c8228531c","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.143-renovate-non-major-2c8fce784e29081136daef442d58d18c8228531c","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b28c9a5b7baf15de7d162e0d61868f16b971db90","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.143-renovate-non-major-2c8fce784e29081136daef442d58d18c8228531c.tgz","fileCount":94,"integrity":"sha512-ZzBYhHe+2cDyyDnyROL1uW0j9X8SvBK5t8xDauPL+hBj3M7Mk1lwso4bB9zNzlCgAV0UxFKE+y7GRmTHpAP58g==","signatures":[{"sig":"MEQCIA3+M2zS3fHOM7Dj409MDhnsPEIRDdaL92P1bpX6bmxbAiA3YwEPsgDn3qxBHp7rYVpeO581J0ELpdpVEUdq89Bzyg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":574488,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfynKnCRA9TVsSAnZWagAA2tUP/2J0B7cFvV7qi4bPBfGc\njIvmTJui+ZhdLeWkuCP6NjXyOipAtJ8NOymgtk0jpxnn32xehuxNfJPJ3sOF\n3X1WXm1O5IQHhLTlS4NpnKeuuTJTXN40duLTVMO9THE2YXAhwkjmQifczk5e\nT/YcfeC6oLgRvvkEDxDWIURwTQBT0HD1ACSe66tlX/QtqhqZD9F3QpcmL8Fl\nQR27v7nujTLVxALAOK1ixfxEEBr82dD6ymNv1vCpLAMOKHZXxgg/BEnQcfh3\nkBds02ABzrgqSwSa9bu7BNSbLd5Ve2QLB4Mu+EKXksb+oE2w+1k/KuCX/v/d\n+Wf76ydaqF6jOpL3mrjhQpIaSEnGBUMWc6xxaOKCzz+NmUP+AEJBLhRKNMFl\n7TE+8C4ZJ4eQeombsKG+1uLwlOWNWcI9bWFlTCYDSQvI8VKt2vF1P7Aqu2Qb\nhWCA/Bm9ZmwJ4U/PQUiUZA/o/96BrhriOX8FfNZnQakEMu5Cfo9Te4CkeuIz\ntmHJ61ddp9C/RAWcYsqfkAAQKLfTTem60xKrTbSFHMltvx54Fh/up9lmmM3N\nmljt6MXM5tmZZTNRek1pMYeUg7N4JXhxWWBx1TZ7KJj2nmO21b8t/iBiUP1z\nP8/+UuXgGgDZBTm5wAyseEfcQD9tGVD8WBq4oaw/UD8w7xtfis93Mk+VMvst\nPiE9\r\n=vTzJ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2c8fce784e29081136daef442d58d18c8228531c","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.33","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.31","@balena/jellyfish-logger":"0.0.154","@balena/jellyfish-metrics":"0.0.182","@balena/jellyfish-environment":"^2.3.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.14.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.56","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.143-renovate-non-major-2c8fce784e29081136daef442d58d18c8228531c_1607103142623_0.6118283525339503","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.143-renovate-non-major-7276c22be367bb6da54d95a5a4589b6b3ce3e24b":{"name":"@balena/jellyfish-core","version":"0.1.143-renovate-non-major-7276c22be367bb6da54d95a5a4589b6b3ce3e24b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.143-renovate-non-major-7276c22be367bb6da54d95a5a4589b6b3ce3e24b","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"cb171048afd77116824013cc0f1ba879f513c0ef","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.143-renovate-non-major-7276c22be367bb6da54d95a5a4589b6b3ce3e24b.tgz","fileCount":94,"integrity":"sha512-mnmt4GWza1XX4ItvFo1GqezlgCrOpp/IdBGX2zB7D/i6tD2Zme98VfBw5CQplpbX9IAvPTP/Q/U2sTABr+5Hng==","signatures":[{"sig":"MEUCIB+ZgXYh2uGSY4eISlk3NJWg8whvIp27h7rJihGAVMsgAiEAsjdrUF8MSywU63g5mmzAaoJi5ZAeVf3xgU8vGelYPIM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":574488,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfynOmCRA9TVsSAnZWagAAjyAP/1A1+2d1Qete1doDK0oC\nCyuxtgBWv4cwIF3k1VJkiqyN6+wPwO0NzNQoO006TuCCL6DeEOgv6VzQ2i5G\nU7G+aT+/ownbqcy3SCbX6gtcSK6bpeXmWVJhQZF47WArp5LW/clF60SLcEF5\nUQE2mEqWmaMEchortf4Dmi1M+KD8J0ruFtYLc71eYufksHfZttsBBJ6xxQiF\nn3eA8u1psfqw72vuPXt9mlKmQNzF5WlFPUJvS0vWAOjpXa//DMoVPN19Azo5\n7f/L7zAC8Wuz0c6jihOf1LoZM3bsIXnmyoJkqnO0VFGDSnmdDg/gkxpwCFOd\n/mKGOGziUsRTnW9j10HpbiibaNAiueTsOr6KlJolIzePCd72PSV2SSIsY+6y\nCpqGrd5ebnmEe6F2TjKMxF5S1RnCbeAsIxoonjDNatT6/EhB8z+GyKcFzCTt\nLRtgNeG6vjICNRKfub1pTwz+pmUgMyOTHNmm3mhy2XzrP/QZg4YB80u0qnt1\n+0+6rejlPE8TG3kviuwqkoSGr49P+eOyUxQiq4ZkhMtrSG1nN3+dpI7OFFgt\nfauFFLAsMS56/klsEdveZJRqzpmq+L3OViXWcTMaMx0hml1Fh343qDWQ5C4f\nxZTxu53zI0b6TIIGy2KXymkGUkFWpZug8s+SaBtbh3DaNscXFKmG1BF8PfPr\nygjF\r\n=SXhS\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7276c22be367bb6da54d95a5a4589b6b3ce3e24b","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.34","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.32","@balena/jellyfish-logger":"0.0.154","@balena/jellyfish-metrics":"0.0.182","@balena/jellyfish-environment":"^2.3.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.14.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.56","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.143-renovate-non-major-7276c22be367bb6da54d95a5a4589b6b3ce3e24b_1607103398283_0.24892100376959037","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.143":{"name":"@balena/jellyfish-core","version":"0.1.143","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.143","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"35c7930762a8f8607fe6eef5d538c54c120a21a7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.143.tgz","fileCount":94,"integrity":"sha512-LKOQzET7CzRXqTIXyedbghU8IGypXJ28kmdOCBvx3HF4idSuhmyGs7sTvGMyzYxxGDRDQft/ZZZyXsqumX0GQw==","signatures":[{"sig":"MEYCIQC5KiBdkRVnQlv2bc8sjEhITw8hfa4K0BbOqDlE+CacIAIhANXE8FLVb7yiqH7wqJM9sDgdfoBX5GxA/IPPftpPqvBs","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":574428,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfynTjCRA9TVsSAnZWagAAAN4QAJRdj9MvD0irnvnDikoJ\nMV5JFf9KC4v+XpTMt6BYtMImUhIY2OrWTgAz+V6iDC3E7ho0xO0rw+sylT0T\ny+WumVGQjZyODaIV4BNhOpZh9Db4tVZfDVFim3W88524jQqG4r3CA57Yklpb\nH9ur6JPloYKDOQlFy+F9LjDl8QUdhIBW1lWXYFaXPX0dSGQ6DBN7qUeYSQgZ\n12bE771Jj3aQ2el6yoXTYqnv82USMdZ/rUjmb3VeVRz1baWCdroBwR5KfJCH\nPEbx4854JoESB1+wW4fw334+huUpE6Krm6ESG3NmUPKvPU4zeYUrn6UtYLX6\n+Pk/GTxAlyToteRu2rkxejAe7xiiUAE/Q/2GkhX9JfLJenyCo3x1yyeTGbkT\nq3PdEj3N4NlbgUuXW/budwPr9DS7QQbCtI7WQQthGmOn0IjxTgXiBWHA9nV1\nLIkvCcSSFbd3SXCceHcmyyzlixjg0mU6DSPC4xoGTJsJx2FH98/DDGRxhIG3\nJf2qiaAxUhamnurCkcZNDBb7btX8r2L5ba1TzhaiUX3ANXz9HD1njAkpSkBO\nBmsJMSx4ako0ngGAFKYL1W8a2CbiPTqOBMkYUmb78dbzY1TEJ6py10ZCekQP\n8ZUgxNpFQqLYU71aCEfCHiMIhpJTijrf2jAXaKFMWi2ySN9BWEYoNn2h4F64\n/x/y\r\n=vNrF\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c327b95df1876cb188740630f3dee0d227dd81e7","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.34","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.32","@balena/jellyfish-logger":"0.0.154","@balena/jellyfish-metrics":"0.0.182","@balena/jellyfish-environment":"^2.3.1"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.14.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.56","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.143_1607103715331_0.8958056019336822","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.144-renovate-non-major-f11afe23f32f65794bcbbe4b6f625ada99e60889":{"name":"@balena/jellyfish-core","version":"0.1.144-renovate-non-major-f11afe23f32f65794bcbbe4b6f625ada99e60889","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.144-renovate-non-major-f11afe23f32f65794bcbbe4b6f625ada99e60889","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e658cdd9924a7308b03ee7a7e9454976d5148f31","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.144-renovate-non-major-f11afe23f32f65794bcbbe4b6f625ada99e60889.tgz","fileCount":94,"integrity":"sha512-zSJHKylHp4z5+ONA15+mnFB9i0EdJT0asbLC3gDGus73Gp7VcEdLGIpu7ZyIqw5u2/QiW3OQDZen3ffGNzhpGw==","signatures":[{"sig":"MEYCIQDSFCWMO51gVK4ckYz2bI4RQRHpw9I/uBgM60E+T09oGgIhAJMzNhn1HQ+gpRw8fg9jO0YK9epoYXrRrNxb7qtuKppZ","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":574593,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfyoY6CRA9TVsSAnZWagAAUR4P/1bZaED/AGNpmXyfyp/u\nsX3Q5MokcCQ9oPQcYrICLWbka4MUHyvBffLF8JcwpYonXa6LoRqLlBlFgf6i\nO5eDbN9JtW/ZGgrS5b5VbDEA48ZGO1WoM3w7XueIk93e9FlcQrjl64FlrsFX\nmrEheTh1V9XfCW+p7S0byOrldYgvSk7hRm93HPUaZdbedJeU8jeEBGaIcVLx\nXfCrV6v5VCf4cceqe0O/EEaJ8aqM6ioIV/76MSF9h34/lnHlC+pbKLshBRs5\nAhClHHpISbKZDhadOFToK0rvmg8E5q1N5++U795RYLRBaqZXCmHrx/vjSXF9\nsI9p6+NJ6wPT/Sll/RFMLwU9MM3otl+5QvtDkYb+rMwrMvo+ZYwDlilUQ+yT\n0g0CJdxJB/mvkQhZqYeYkXBxs0GhfcAIgDVTuaAHg+AcV2a6BYFsQ0H//Of3\nYIfAD5yR/JaMjTtg+0d2hsP5fA/9eg3Uq//c5HifQ6TcoyM+SUXg7YhKGVnF\ndestph1rJlSYhxtx298XMNaOx/r7qoASSb+oGl8LOuZWt3rbkXhNboTCdtbf\nQtwF+JVti2GALJkzTGPCtofQnzlKfxDGv+pN7a770/eXyZsI8yY0Hnd2DyWI\nNQek6AZ74tjde3u5qT+DyvLEpq0hs5/MurAWECj70jM6SSZIAzk59+qgvgfM\nXIP+\r\n=5nAq\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f11afe23f32f65794bcbbe4b6f625ada99e60889","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.34","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.32","@balena/jellyfish-logger":"0.0.155","@balena/jellyfish-metrics":"0.0.182","@balena/jellyfish-environment":"^2.3.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.14.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.56","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.144-renovate-non-major-f11afe23f32f65794bcbbe4b6f625ada99e60889_1607108153685_0.7148546114502985","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.144":{"name":"@balena/jellyfish-core","version":"0.1.144","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.144","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f9b6baebf7b8d57e5ff5bce18e12259931576752","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.144.tgz","fileCount":94,"integrity":"sha512-28/IeIrG85oU38623XhB1oCxHMPtKNPOvFrsPvp2FOiaz0sjidLvqtt8XqdozeKDCFEzbtdDb85HYF95WSZXJw==","signatures":[{"sig":"MEUCIC3danse7PQe3WGeMZGCmCyHvHgAiGDyEK3I0+wIVF29AiEAhx3YYBJadTCbDc5ra/LQlH6NDdMuXv1OjUGUZ/8arsk=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":574533,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfyoc9CRA9TVsSAnZWagAAYtQP/1rD357IsCBz9bU9Den2\n6aLpr3UnHxbR++mGHg6m+BCTmFKq9EBTaT+EspPco3qPr5g6xeDkeZaJAqG0\nUpGpnwrewuAsAEtFBWRY92rUnvJwktaBoN3TvMWwI/EviaGMJ+WFWu5+kxoJ\nuC+ST7aQvfzl3DJ/MuR3ESh0+AcpsMMH846gP0301+7I3h/o0eVXVWGN1QxD\nLw4Xfkk0sjkhmhnPLULHL/KZTJSgZut7ylELd5xB1izZKLXQCo1kN1RcY82L\nOmpdU7lyI88FNUClIai9W02l7/8t19dmSK5ZeeZv7A+9LPMUP/bspoLIMOTg\n8+6jLFrBmAeQ4Vt9LcY5rCq1x7zy+kGZGfqJTCzpYdCjbuCq+fLFeg3xcUmz\nQWLfcE+mC2qCvz+yTYvv3oLG18x2qS+DKP/ZkuQy7D7U/XlGMLKLdixCgK6E\n8pAHcYfhLgU+clgQ0k1b4M2eTsztYGABFF0D29ntdWU74QmXo6uNNreqCbpW\nfZki8ft6N+cKqiBGm6FM4TMRzDTlWHoP/RRMQZQq9MsK4LCwTwbXRNCdEfcQ\nJjUkyVRo0aK+bjHoLCszNQOTF6SDW5iHgkpdRlEV7N45h+vWPivJUsagi3U2\nVppvjp27KVT5+6lNqveKsL4++ebnPhxCccrbCgv/r8nEwIZcJbhn0aAdo+Zt\n9Wvg\r\n=LFlh\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ac93d47110443c4abc4d78d859b3475dffd447c4","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.34","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.32","@balena/jellyfish-logger":"0.0.155","@balena/jellyfish-metrics":"0.0.182","@balena/jellyfish-environment":"^2.3.1"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.14.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.56","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.144_1607108412445_0.43609090062800226","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.145-renovate-non-major-49c3eb1cf7bdae0ec9610a434c4e5db1a7fe252c":{"name":"@balena/jellyfish-core","version":"0.1.145-renovate-non-major-49c3eb1cf7bdae0ec9610a434c4e5db1a7fe252c","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.145-renovate-non-major-49c3eb1cf7bdae0ec9610a434c4e5db1a7fe252c","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"33c9257664e2b73942ee2153086101942cad6128","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.145-renovate-non-major-49c3eb1cf7bdae0ec9610a434c4e5db1a7fe252c.tgz","fileCount":94,"integrity":"sha512-utwrTNLWiN1tOAzT2nTlZDsbe+/baYFqcjFtqWw2kGZHcTMYHDuzR/7s3w67jSrHl1Pi2fbtrmVW/+JrsK7U4Q==","signatures":[{"sig":"MEYCIQCZhWrIoke+9zVYUV8y2HPodb0QO69Yj3z/8fM41YEUiwIhAIXqPe1pyFhbA+oxp9bCf0NhjY1WLVbvL28T4yaosTgd","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":574699,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfyoggCRA9TVsSAnZWagAAXewP/2C2Gg1rtqRPEKO83ATT\nM2MjDjZ6/1D1mS+jcGZFLXxqk91iYHTts7FRUclLyVGPYyMheKC/zBe5HRyy\nTC/cxukAvNbzLDBG0X9EARsaQbmBn1TVY+B0mcfSq+LHGlSsALZ0TB8iz54l\n9QkTLnRH2m0xoKXX1MTwCnvgPRq6eFlR9Fwhbe7H5oY5ov5RMu8PZIAKTQzq\ncS78Vu9aFWyg/M+jGxVFJx7bHB7bY75FkhHZ4KKlbxKvMvAp14lc2OhISxr4\nt0mQHlbYylJvhvZWgRLTDfNGNMlXlCKPBWSZce/6BoZQiQrYftIkBxvFIOTX\nps0bInf4MDf40udC/gG7RKtVtQwf8atB6gqfGTlVCbXmrDFBSOiqj0Bkct4n\noApByFhME06pO5Aa5O6t6xYeOy3+AX2mnGCt4qdRwenr97oGvmAH/yVJr9u/\na6N3r041zkeB3F9dJPePJfH6L1gLjD8zjkBUyHUPAZj+bdUhBSnaB/v/LBZn\nyJme/TuJ5HcLbIWLMfKwaxjL0H9eXNASSNKNLht7BeYHXXAlfB/fwpBBpmRn\nye/Kd6oPHJbZjLgHd8YV5xh2nMSO0p/uofbXzR7xyTXG5iixanFpL9d8bKro\n4PbHY3wxKjjTZLRjHWJNFFNiPI6cTJEXAGPCLFc/Mn7NqQJU1SmbftvAkPwO\nuw4/\r\n=Mfdp\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"49c3eb1cf7bdae0ec9610a434c4e5db1a7fe252c","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.34","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.32","@balena/jellyfish-logger":"0.0.155","@balena/jellyfish-metrics":"0.0.183","@balena/jellyfish-environment":"^2.3.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.14.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.56","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.145-renovate-non-major-49c3eb1cf7bdae0ec9610a434c4e5db1a7fe252c_1607108640356_0.7188165979878915","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.145":{"name":"@balena/jellyfish-core","version":"0.1.145","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.145","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9846d27999cc2b85967a6c891ccc18ea437eabbf","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.145.tgz","fileCount":94,"integrity":"sha512-iI7abFX/+Pcw5YCT3vCOQgo4zvc6XHaYbkz9bATi2PkfM391IhQoTYxZfdiEvLyBftl7zwqQzbaM3TsGfAQqqQ==","signatures":[{"sig":"MEUCIAyjbqPO9aJSzjz6CFyn3bPucbfAnfjRxYrVUvYGtukFAiEAs0aNIP/yGmHjYuYTJkIDVu7gzF4e8BK4dSfnA0zudEU=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":574639,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfyonNCRA9TVsSAnZWagAAus8P/0uoDqIcZCaV2JlupIH3\na8KbCudcnl5c3r1g4ib7HjOsumKe9NXkNvf0XuKXPxpHFzjjGtTttmEfVhsk\nqoVLnhfdaty6jYHMmCXWDnbIF3Z4meWEW8fboX1SaDFDYDY+0bEnSYJibs2d\ntW4kyuTjf/NUCxEqbGIACKrBR+XXg2KbXirFMf0WJYTBdTDCcFH5v+ApQ3OK\nEyjyYo03f4XIP+TuczBP2V2IfVantWogAUek/a0Fez3WyBx72OySA93QaRVJ\ntaIlAEjjVpOsikGBuTbNCLFdCSfirSU/9gemH8GQ6cOCCD8CsBKeEwF0g56x\nhw9nlGu9L7s0Jkmv6TxZMbudiOkUoYOdjWuy1P+1i6r8omibS1Tbu4MRaaGU\nOI2DFWdMLM70etzTM10QE3yDEmqyLPrYOn9S2gc4G8WIqhSyrE76nfN9P61m\njKs8uc7HMhM886ICUHRqHne3hON0oqFsQ1fIvIIGQk9pWYbN4HOgtQIMHvak\nKgof1kwmby2Mo/sDSIsi3nN4wu0cErpQlLPJPi2oIatZXWpb4d+/UIhhrI3a\nLMgGE6HqYUj7P7GEBN5heZPBU6em1SgqCuvKqv9q78ZIoyiUPTJkDrBkFsp2\nzT5fNkevgzwVKe0Fn5p8A+/xmBihmEmJ6xBlefx0xrhFzyCadWdLEq5s6Auc\n91+o\r\n=IpZ0\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f7bc997f22dbe508b371394a76d6d90d882a6e56","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.34","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.32","@balena/jellyfish-logger":"0.0.155","@balena/jellyfish-metrics":"0.0.183","@balena/jellyfish-environment":"^2.3.1"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.14.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.56","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.145_1607109068735_0.6117726329216617","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.146-renovate-non-major-e98f44816cbbffc04fa6c8084679ab07f4eab236":{"name":"@balena/jellyfish-core","version":"0.1.146-renovate-non-major-e98f44816cbbffc04fa6c8084679ab07f4eab236","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.146-renovate-non-major-e98f44816cbbffc04fa6c8084679ab07f4eab236","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"cab65b864d99617b898b926da6158b5daa4ee600","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.146-renovate-non-major-e98f44816cbbffc04fa6c8084679ab07f4eab236.tgz","fileCount":94,"integrity":"sha512-AYsmkxy0++kDt38SQFtOB1z5D+QjaIOa6oQzxgwJUiy6npf4eXWT7HQ9I/FKGn4lro66FTFv/W8ukBA+dQVA2A==","signatures":[{"sig":"MEYCIQD498OqNL5LCVQN7F4N5jZu/JOmN+gd7H0QXdWjhqdzGwIhANt+PTAMr9HvpreOlBr2d6aV9B+AnPIewUma+OuwClf/","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":574766,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfy1k5CRA9TVsSAnZWagAAOp8QAIwNeRn2Dzo38/7NtPHT\n3vpi7Kb4/T5HOE+VUyWhG/8lXwTprQwnxKBcn95w6JE3lYj4+O4sZcE6bNK4\n4L6NOHb0slMw67bUg1PgtmPz4y+7AnfiDCHZgJfTaRqkwFSUsU7uAbCMmJ8S\nGVCcbXtiPgn942QsyLg0Bw3S96A1VbwyHpynunwXgdh9ai1LDA/PO6Hmnv7X\nZ7pZ6Ss6phAXy3liRPzVcZJX5Y3qt15ylkOJ8vfeqfn6BpRZnmUPuH0QcBFo\nfRNJQakeEUVrpczeohz8d7Y0JmB2IvqT3vZU3fK3AXZeF8h0S/UEsTBMnRsz\nurhALMVaoyz8OueL/2QwwZY9uaJLokq2o8deuyiYiHC5o2Wgmt0dFRw+Lh/y\nsq1gFoqKW/JmJn+1izzBZFuJDIQVhH5lvSyEKYcSf8vAwsdACJbMIdujVxLX\nG4D8T1JAsHE7jragP5YgXQ2n7//6F8odhizM/xrYxCjdKIPdNWRs6gldiTD9\n+dupyrCNxIoUGDaLvruFkwqHEd8UAWnPFACZtWUyL1/pYEywCasMxYv4qS6N\nnJD6yc/mBs4kmbJaCSEz8LxcUIHZj85qhk3sbA1uXBTZgLjxPXgOhCfVOPB5\nitK0WnKGxyOvVfv7owy5oanp+XclYK41fKGeu/OTRXf9Ko6KMe+rLOYdZcWA\nlFaH\r\n=dP+4\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e98f44816cbbffc04fa6c8084679ab07f4eab236","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.34","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.32","@balena/jellyfish-logger":"0.0.156","@balena/jellyfish-metrics":"0.0.183","@balena/jellyfish-environment":"^2.3.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.56","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.146-renovate-non-major-e98f44816cbbffc04fa6c8084679ab07f4eab236_1607162168772_0.35304445830044395","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.146":{"name":"@balena/jellyfish-core","version":"0.1.146","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.146","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"aa127d21ff95242c420befaa29c8441fe70d2138","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.146.tgz","fileCount":94,"integrity":"sha512-EaZ5QSMMqHTAGa3uxdDgFKoCUIPoqndu+VoCM3loNpxCVIqjCLm+diB1kaM+OlhPps2DOHRYkd/t3CxbY8w6AA==","signatures":[{"sig":"MEUCIHu1VggfhacMytWk8SS54JLscJIlILJzwn563KchowIRAiEAvE2a2eCcjZCT6s8q6xaddrvoYkllp9UDHqHOFYUAR8k=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":574706,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfy1pDCRA9TVsSAnZWagAA5xAP/R2YESZ2XnPy6D8G6DOs\nJ5YsWuGqnIHjD1V+kaC8Rhn+BC13a6vC3vbH/K+yEVW8qIBXVTQFDhTFDEZ7\nxIk25o5g61IhWoyTzu+NvkfYdxN6eqbM8sQ5qlAIlCZqBmqLwuSWeLn5ljT3\nYf+s9Q8t60BJtA1xgjHnXkmRDFS5Fb7865eX3UBasw4V23l1PKeCJuKhXzZD\nqPvfGXM3uBBUUcETgSBHYWStD12szrx3Zae5Ao8cLLd3ODs6mHLxpVoFH5N7\nWzcp9sX/NUGxMLOkFbJMVPNAiLsmeOp2Guw9J8KpCtReLw8Mn8ZQLsu8XxDp\nN78IuL9i3HXySXMYLxs/8oBg3RNfL4rqv/UGpncqXJTy8mJJc4ATAyxGwdB8\nWhakgJEp7mfhSGPqn8S35w49lflzVVIJtCkFCQbO1GDOgeqWu5fB0g2i6tgF\nGrDqv08IvSYxGSxV1Rq9mqtT/+TdCGbykJVmSvPu3G+nsgxLqVWK4hD8RIuz\nCVzuAc83MnTZuX4ga6eNGYCTt1sjKoE624TkQ0IjBSqcsdyjqIrovr92QGpT\nbrsKd3u35QQkApJd6aQpns5wtYSB+GGJD7CWM8vuaQa2k43kJXH0A+3ocllj\nyWL28x90gRxjC0W+CE4KOa818fEoIfXs/MHkrGKDUffSsTidDQ1GGIA/grPS\nzTW2\r\n=gV8C\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c61e16046c27c039210843f44e79a55462fd5f79","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.34","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.32","@balena/jellyfish-logger":"0.0.156","@balena/jellyfish-metrics":"0.0.183","@balena/jellyfish-environment":"^2.3.1"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.0","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.56","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.146_1607162435025_0.30476162191500156","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.147-renovate-non-major-da3a51a808a27fe1e46093bee3d1d7c452416d9f":{"name":"@balena/jellyfish-core","version":"0.1.147-renovate-non-major-da3a51a808a27fe1e46093bee3d1d7c452416d9f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.147-renovate-non-major-da3a51a808a27fe1e46093bee3d1d7c452416d9f","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2cf5300734bfa73fd8278479f9fa973aebc94728","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.147-renovate-non-major-da3a51a808a27fe1e46093bee3d1d7c452416d9f.tgz","fileCount":94,"integrity":"sha512-ypNXw7qSQSBNd4XQlGw6GvF9FFlGWGV08jdN3iQcYfjNIKRpp98DLoLh6IT/ztFg+7pGnGLhDwVZp0wuXRk1tw==","signatures":[{"sig":"MEQCIEAMwvQtS7ScEfekkJLYk2eNeptwGR110PhhPN2Z+0FPAiBAXf02xON1ggcA5GFCnxyoK7kpTrMxP6XqCSJ7IVAJ7A==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":574850,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfy3kCCRA9TVsSAnZWagAAzqQP/RpIR4frDBFcqESWtDJP\niA0UrmZZ/78TPF0cwYzxTLylcEaOuK6z8MgfYEHA94jWpmVqP5iFYaA+XB3S\n27uJHuMlq5OhV48MZHpd1ay8LVRI1W2KoYIG3HfsDdd0xYXSPTn0MnaDNEyI\nGzLiFkWBSGjkK4xseigRPGQWNr99jdehI8llAq9cC7EqLzxVIkiD4ipbTc65\nh0qWyX5ieTCimINPOnI/ZR9RY6JGBxoKHKCYWl2lQrBXfkdEv7k3mftrjQw2\nPiM8YXjC1Wg4Gk2AiDGsloswC2vMgIl2t95t/ofdhJxjGP3X7ee4bhepqZOc\nBhtESIlirT2onl7Di/yq6vkLWN8iBjUvtsK4Zq4hLRrSAhk3ZvyTvm9KEWaB\neFoJreoJqrWVv5N2+57X+griAM76uthxEN4nY2WnCrQmllUjoeVJchPIco4O\nfVG0O+VPlVIQ1scZDEemKaZW/i5vnbNOJ1MwuRwN5aA97Me1ytiU2LQiHudp\nXbj6jPJwzyQAY6gmszxOFXuErDQMhX3bfL2zN0FyjxmDRjkeihIlSVySvaHQ\nCgnktxvKmh+YesANLyLOA1R+2VQMH+yXl4ySBbPYWhEOlDc9I1JYLQPdwS8p\nVRYNhfrsoU34UCsrFvcjs7/u62pzyrQZUQ4bIUMGnDrBk/UY3y9abp5LSjhG\nZ5SH\r\n=BQPi\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"da3a51a808a27fe1e46093bee3d1d7c452416d9f","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.34","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.32","@balena/jellyfish-logger":"0.0.156","@balena/jellyfish-metrics":"0.0.183","@balena/jellyfish-environment":"^2.3.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.4","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.56","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.147-renovate-non-major-da3a51a808a27fe1e46093bee3d1d7c452416d9f_1607170306316_0.8728655414941153","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.147-renovate-non-major-bac3cd731e68905fdd852eb105b3a76518d9245c":{"name":"@balena/jellyfish-core","version":"0.1.147-renovate-non-major-bac3cd731e68905fdd852eb105b3a76518d9245c","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.147-renovate-non-major-bac3cd731e68905fdd852eb105b3a76518d9245c","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"052a40af68aebf75ae4475b94486a678055ed43b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.147-renovate-non-major-bac3cd731e68905fdd852eb105b3a76518d9245c.tgz","fileCount":94,"integrity":"sha512-U0TnX6nSytNqWPX5bEsFZWK6syWHHGzI/x7Byt7OOO5K9XiYCagN2QjDVWNWLMsoWowqEc8FB6iQ4uPMhGsLYA==","signatures":[{"sig":"MEQCIAxe9p5C3tyrzqFivP65oPGspST/WBKx7uzyCDfnPqToAiAQqskrWF8r8Nfjh92VcgnHc9rFH6yooIEX6FVL1Y1/NA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":574833,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfy4+0CRA9TVsSAnZWagAA9I4QAIFWzQNyo3NOd/hf1rDp\nFGSOo8FTNeIxjXjU/ac9fx4bb+Q85Kc5YFx75xNM1wWwXIzNCRQ/WF9/akUB\n8do8izJ6ji3OEgMH51DC2r022IpHdsTJ3fR6WjDcFb1Dcd9DdP4vWjpAij0K\nPkvMp3UmDX483GHQXtYpQmSr9Psv1mO5KoudKvL6jEvLOHbu8EN3mMO81wZA\nF3rEL9C20h6nE99FSAJSp8GfiGPeozPPjcJQoM97LuonZXohSbMMMLzqxgEL\nX+8u/uP0EkJNOpTITD4FAJIwngbb8debfLg9/BBel70BLBYga3/ltJWJp2HA\nqMiEQctQMeMg+b4OHorp07Mypp15Ko7gjNF//l7AKNycl2h9omTMKTMZuyqf\n1r+NaEbEsdYSO/V6J0pQtVTkpGfy1jR73XQ618z3dv9RFGWxt19sC1RUQHld\n++qgyqE3HgClfFjoagwllpQPv0ZvLyaNwVfC04opQ9UvEvNas7gNng9PVNv4\nOAOT/tcFCzKfq5dI7lcrgzQdPnYPkCsZ9q7RX8tQkc6YBlmA4/2+nVvmBt0e\neyqZ00B2/H0ECajnWAKEt4gVS/9NA02wnxF2TO/VG0FToxzCtEAikSoxAdNu\nnQAk1XhkVPfpOMYuPHOgx1/7QqBehv64fKPzic72asfErM2k9LkGhKdMJqp7\nZqfr\r\n=Dyug\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"bac3cd731e68905fdd852eb105b3a76518d9245c","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.34","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.32","@balena/jellyfish-logger":"0.0.158","@balena/jellyfish-metrics":"0.0.184","@balena/jellyfish-environment":"^2.3.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.4","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.57","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.147-renovate-non-major-bac3cd731e68905fdd852eb105b3a76518d9245c_1607176116315_0.18365541376629446","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.147-renovate-non-major-ef6c47e7bd1f250c7945169425af8456ad52c5a6":{"name":"@balena/jellyfish-core","version":"0.1.147-renovate-non-major-ef6c47e7bd1f250c7945169425af8456ad52c5a6","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.147-renovate-non-major-ef6c47e7bd1f250c7945169425af8456ad52c5a6","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2673fe6bf04124e93693fe8b41fe7168a342e0b1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.147-renovate-non-major-ef6c47e7bd1f250c7945169425af8456ad52c5a6.tgz","fileCount":94,"integrity":"sha512-TyBrP5Cf/FUWDxMYop7vbJlu+7MssGMA09J61ZfFCHlZWo43uswJNlcS92IUvJ2Ncu3RhJxr4fQzw2dR2/EygQ==","signatures":[{"sig":"MEUCIBv7wWD9bo9JYzftsL2z6+QRDnl3Q1Os6nsND5A0ZD9qAiEA4kOkQLSHivSUkAo4KCDa2kysfvE5u4ZWUYWgj7HA0EY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":574833,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfy6jsCRA9TVsSAnZWagAAoPEQAIPWBQeZz02mRlQz97wq\n32zl+CjaUbkUNp5ZexZmGXKxoTGK2IuC+JkjaQASGPGUCWNaPDu5QqJZYKjz\n16EbZzPKV+KnTsRTCZ9t726NVu2Itg6JYnOcJD+0+SWHC3sOEZdm0TuUc3rp\nBg4SVieAi3jieNgPkLj4r2xg+zueuSAcECsMJNHXGYFKTPvO8SmJQY5gq4mp\nUxgDFaui4RDecg2oMPGUIzEx3mp4n7rDCcEgjReE5v9S5cYUIx71zvKC2c7A\nEctog0aseB2vU0SGfB9AtpuMkXvvv8hhzGmX492UF0+j5U3Z9p79d/WU5xHU\n3kssdttsfA+i4WEY1ReaNmnPDcTdr8D0Y+UQPUpm1T0tLzco9Xv67G0M6ou/\ntgdq0O8UgQCAuaJSbPS+z7rYvW/oDvBPiO+oQCxsXLdvbdeexlCAZ7TmfHEA\nz1o4FIKcg4P2KURDJFme87xoL8KlUzTI13x8NKU9CgFt0pUY1x1lNxl9jkIJ\nk1S6IrBNSAWyEyZjgB8wsk3U6DOZX9AXgad7dVeKijAxfObUfnukRiDrwMQ9\no3Zmz2qstRwTqrtKk/zUliI9rcoNQdvg39HfXu2uab+5Cgu63HA9/US6zzKo\nlEtpf1Xr4jd0aFoEbP/Bs8R6enpTTPyRBO/PEId2n19waGSo4fmraprlwYLy\nghni\r\n=wEZ7\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ef6c47e7bd1f250c7945169425af8456ad52c5a6","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.34","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.32","@balena/jellyfish-logger":"0.0.158","@balena/jellyfish-metrics":"0.0.185","@balena/jellyfish-environment":"^2.3.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.4","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.57","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.147-renovate-non-major-ef6c47e7bd1f250c7945169425af8456ad52c5a6_1607182571989_0.6807668187593234","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.147-renovate-non-major-93c31cbbc1081fb37cd58dd1b6c4cd7a4391a321":{"name":"@balena/jellyfish-core","version":"0.1.147-renovate-non-major-93c31cbbc1081fb37cd58dd1b6c4cd7a4391a321","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.147-renovate-non-major-93c31cbbc1081fb37cd58dd1b6c4cd7a4391a321","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"80a1a5f332b4e7dcd132294362e15af1a7eeb57f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.147-renovate-non-major-93c31cbbc1081fb37cd58dd1b6c4cd7a4391a321.tgz","fileCount":94,"integrity":"sha512-fMl24JmT06dfPSu5OWddNxG/Fpe3/PNuku703plwZyKnG2Mqf2VCWZyf0dLfinpGCVmOg8HmMGgjnDj2jQFNvg==","signatures":[{"sig":"MEUCIETBd7hmeFZ0LzULyzZw38N761LiIG5Fua30LwXy2LPAAiEAhxnJ02e2vfzVnQrwn2n6+wCTiXD+FwETx1tcy7Y16y0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":574833,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfzDJMCRA9TVsSAnZWagAAynsQAI5TxY7g/cKI4JgL7Syl\nLs437H5/0l69QWEBG1nBLGuLYxZGd9l+OPm4tKh63ARHMhLbgebaCjEEy77s\nJYTonqetVinVZQpls6UfpU6h+ggz5sIFsaho/CKy6ijSIoQU6dj2kjGzQcV4\nndZtNwpS3IpZ0W+0xXTNvk20EuFIPqMBFYvtab4A6t9ijtr82ouiKYm8PqVw\ngKL9/s0x3a2lEWe/hl5udQMg6PfxEi/nO2WCxVoJHonztAjHz05SOz+HgJyF\nXMm9X6X+/xDLRERK4XGtWKLfRTTuSNtjzj2A8K5NdFv7oIKztxE2c99YTe1U\najm7D1ehSmSKyPyhF2TzdISkUZE6l4ak9/odtRIgItlNl2pHiesVpOIzhQDY\nJ/q3guy/cLt4do4gWfHKmqN8dgFAFiyzrIyKQeDHquwsSAn18vjYJ1+WTtBF\nFm3bNFbrwF6zbiAaMrOBzL0VDSgeD/md9qMIDd+gDMuRUFra/o+nYZAICSRp\nnRhKQlyFNfRf5YDxu5jou47W9kpVocYFO9aFaqQNr3JoYmXtVti0yJP714CY\nmSHgFvB7hsTVBUl8MhbLIoaV3mg1toSN+kQ+HsguGFJi7WacRM1/2PmV66jY\n6QyTm3QUH18+HYtMcZugctzWvDa8YGBU4yB1mxgOaioY+gNUbJI/mbXHhpUb\nVkif\r\n=IQ2Z\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","husky":{"hooks":{"pre-commit":"lint-staged"}},"readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"93c31cbbc1081fb37cd58dd1b6c4cd7a4391a321","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.34","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.32","@balena/jellyfish-logger":"0.0.158","@balena/jellyfish-metrics":"0.0.185","@balena/jellyfish-environment":"^2.3.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^4.3.4","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.58","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.147-renovate-non-major-93c31cbbc1081fb37cd58dd1b6c4cd7a4391a321_1607217739964_0.8213233319578732","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.147-bump-husky-v5-8cccf842075b260229e2f260ef8b4f66b34cb496":{"name":"@balena/jellyfish-core","version":"0.1.147-bump-husky-v5-8cccf842075b260229e2f260ef8b4f66b34cb496","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.147-bump-husky-v5-8cccf842075b260229e2f260ef8b4f66b34cb496","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"32c610a33fe298b0d67ef35ecef33a0949bd1890","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.147-bump-husky-v5-8cccf842075b260229e2f260ef8b4f66b34cb496.tgz","fileCount":95,"integrity":"sha512-YDD/rR8PDtVT2FNtP3DZCZg4NsUhYDPi2hEzNaHKRywGmPQpjrClFwWyfIGcSWgqr/eI9AH3k2G4Pi7cPXeYMQ==","signatures":[{"sig":"MEYCIQDWSaPbw0NNA4AgRMViwTkiXGK/yNZsclx4D/uYP6gHzAIhANnGmp8B+7IH100VXdgkeDFgdjY2rtnPt8/NTIOhqgiO","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":574871,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfzeQ6CRA9TVsSAnZWagAAwW0P/0JWxkJNcFHk71ANCFr+\n/SJMPPagIcuDtXWHUpEuiUXVHBMqbb+LPi+gBFF6mOcCvgzRuYRpY4kv95va\nvK4eCRiYpe3ECC4MXiA7vQdGTXErAkrDZd8SO5AWdRNod49d8eCU0AnY2xNc\nKrr5Ae9LNOiBEaj/tFvHi/SHoUozb1R8RWJNugR4s2K7U0YWF6B0wYAtDNZ4\n6zuoAqa1UTvgKR19yCuWllzuNifl0t42umDuOF9gpcCYT32BMoX+lPbFFrVz\nv9RWj3mAjLQVZqAGavfe8aUVFK6OvZnzv23FUYmpGe3iXA3FlM12wPSqXLOv\nvsb19KpyvBkbdozZxhXITHhXtGOl587Nilw+3CqP5NQppTMFoSQ0vFRSMzJW\n6z1jLlxnmhTF9EY1aIKJ75d6ukN+jJJRmxt4HY7V8K75fU8kQeWe3oYqYrS5\nIL9HZDDeNg5hZ9jzWFfbgLpPjQVM30TX8w60YVnXdSxoFuMjUVyW8AzFWBGL\nKNC1Y1wvcA8/6BeIKtiw1Sl56zbENRZzdgm4ptJJmBLT3Rfjndm7TmbKmemH\nNTVfvtaSxTp/yCWpG4yH7+/tAA16xm8/HS9hiPz2FTfFMmcLWi2qVx6vk3Gx\nCm0GPIdexYm/2nALR37pdDfJFE5lKyLpuzpcxLuJbgueVzAXICbV8rykcxjf\nAOjQ\r\n=UMaL\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8cccf842075b260229e2f260ef8b4f66b34cb496","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"is-ci || husky install"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.34","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.32","@balena/jellyfish-logger":"0.0.156","@balena/jellyfish-metrics":"0.0.183","@balena/jellyfish-environment":"^2.3.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^5.0.4","is-ci":"^2.0.0","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.56","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.147-bump-husky-v5-8cccf842075b260229e2f260ef8b4f66b34cb496_1607328825764_0.2955469100294379","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.147-renovate-non-major-68d3a7820a80f2f2c30e0f2558813691aec4cdb4":{"name":"@balena/jellyfish-core","version":"0.1.147-renovate-non-major-68d3a7820a80f2f2c30e0f2558813691aec4cdb4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.147-renovate-non-major-68d3a7820a80f2f2c30e0f2558813691aec4cdb4","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f4f6ada67baa59dc04ba7b8df1ffff5088a5caf6","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.147-renovate-non-major-68d3a7820a80f2f2c30e0f2558813691aec4cdb4.tgz","fileCount":95,"integrity":"sha512-jghQDQuzPk1lv4JC/zdpMubXNiG6TfB73OtyQ4psn/FLyRT6gHHc+Mt7+J+z5yUrBNoiU6WWqbOYtY9S6+Ps+w==","signatures":[{"sig":"MEYCIQDEbcoEVZdTW8IW/JHFod+RHmlIPHpxrKjpxiqYXLO/oAIhAJeMPbT7rLw2bl1fMe7NmnCWQT+KdVpw8aA4mbw5P/R5","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":574917,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfzeZrCRA9TVsSAnZWagAAbCIQAIuBdjOiKP0HL4wUQXuY\nXcdw1ULu4dzGdv2aGHmPvmIzSsDnOUQXwroSkNZXV/b1yYteTX8cyXvbCMSZ\nVMgffWyx3F3cUshKKhJC0xqUhFr5GLOVhYYT+X8SVoc3190F7oVA9Zf6JISm\nSp+VKTEcpLHWB0rR5SBS10FTQooyyNm7wnWEZU9YsDu+KCzB/6VA8S8Ef2E6\nOQoaMGViAr95XNnVD1spNOdUdyC84stEYiDPY2RWhfbER3Nkv/nq8AqoMdX4\n6n1psi3QyMZ1PP0tFTAWHWf+nnq9VgZ+jodWkGsGbt859FK01s2z1bes0Sim\nyLro5QxQ5XADsc4J7O2PJx4wyQHF8hDS87p7fSxIJ5YpKggKCaQKsj3kXzpg\n2snTO1rwQ09dlUPqq1/9mORoEIteRxhTeNsR/wvETUlC8BjSg0kNCy7ds4BZ\nxq4hOHvS/sl1j+V5LmPNJKno2QeIfaOWdKijgUQch4phpP0Q7HOQwkeB4eQ6\nzysLn/zIIPJ438lhhw4oTKNndkNsG3399snNik9t1zJH3irN8a6KRYrD8KDV\n30ICuxrO9tLpZcgyJd9G7U96eDnnL7jO3wWFRJKQlxhOGNmVfsmzWbQi8ClB\nw99v8PrxYT9DUwVouvvw/WDi0vwf+0zcpSLFL3nFhkl9MWHKBZICxtdAQy2t\nV+db\r\n=oM5a\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"68d3a7820a80f2f2c30e0f2558813691aec4cdb4","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"is-ci || husky install"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.34","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.32","@balena/jellyfish-logger":"0.0.158","@balena/jellyfish-metrics":"0.0.185","@balena/jellyfish-environment":"^2.3.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.13.0","uuid":"^8.3.1","husky":"^5.0.4","is-ci":"^2.0.0","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.58","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.147-renovate-non-major-68d3a7820a80f2f2c30e0f2558813691aec4cdb4_1607329387005_0.37268671123605746","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.147-renovate-non-major-6fcaf24dc2f5e40628a052195b75085a26db825a":{"name":"@balena/jellyfish-core","version":"0.1.147-renovate-non-major-6fcaf24dc2f5e40628a052195b75085a26db825a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.147-renovate-non-major-6fcaf24dc2f5e40628a052195b75085a26db825a","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f85bfa2c6a84da1a06b6ff8612aef9afce2fe4b5","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.147-renovate-non-major-6fcaf24dc2f5e40628a052195b75085a26db825a.tgz","fileCount":95,"integrity":"sha512-V5k7bGdq7MKtubGVb5kWobK4WPaIP2ZTlJ8hOPuORTsRRDJRXXjVKeeIOxALbtpFc4vxroXOR16OwiYIzFqGFA==","signatures":[{"sig":"MEUCIC9IqJvesbfp2+JVmqfKyzo5DYrUietPfzwFpz7llGBUAiEA7Lo0pWNefe7DthT3dhHfLYOaMutdX6l2nEuYd3GXEy8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":574974,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfzfaDCRA9TVsSAnZWagAARTsP/3LMndPkY2mpQAvJQjPV\nNo+k5rCtuo81mlmDePbdsWEK4N0qUAHIsz9Q4yqydZSK7I1yw+6lKjUtrWpz\nPrKCRhitcZmYne4CiEOyDMhhGRwGVp8DYSmPi9dUl5gDPTk1D9ac5WoMm/y6\nUuKpiJMzSfm0MePdpgs6u04CxBOYW8LhbBfq/nfYcyUNEQoyxkYLgGcFDavO\nqBtR8pgFQwsa2TD276LwRFgymODwxZakgrHayVubBNGpfGxRo+KbL7ogGOCj\nIurwBqT9urvqk7HyLEt33aZVIAVrd4JdUKuVhEgl0ReRTgzURqWYmPTu3A9/\naQmPzU5AST9nl9MPwbXGy3G71f6NiODqsjxlLtR9zZHtDmbgdFscx27H9aww\nAuZMITyUlelCvcUZ6CnnJ7CRvSjo0Pv6zCd64IKxBKJIzZU531OWM2GUF5+g\nFEKykKkWu61SNnAV4HHVT2U4aGTFd+zUseHf5eT2FGCAAAnZcy8XuG0n9g/2\nH38WaL/P0oP4dAKfEavbU/c6W/aOIGyGEhsHBiw6f63vmnt7Q+99Ar+ZFaph\niRJgclYi0m4//LDFhxSnx15Q3Rg36Xy5JBoLs78K/DJt40J62uxD9ykQBngo\nSQq2cMf+d7kLYc99/6to83C98PlSPT4IKzCLdVwcOSbhQDZtQDe57jp1TZGF\nygct\r\n=IJVR\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6fcaf24dc2f5e40628a052195b75085a26db825a","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"is-ci || husky install"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.34","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.32","@balena/jellyfish-logger":"0.0.158","@balena/jellyfish-metrics":"0.0.185","@balena/jellyfish-environment":"^2.3.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.1","husky":"^5.0.4","is-ci":"^2.0.0","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.58","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.147-renovate-non-major-6fcaf24dc2f5e40628a052195b75085a26db825a_1607333503497_0.43717055332165766","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.147-remove-postinstall-123afe3ebefe869c4429a366676e4cf58bd5cfa1":{"name":"@balena/jellyfish-core","version":"0.1.147-remove-postinstall-123afe3ebefe869c4429a366676e4cf58bd5cfa1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.147-remove-postinstall-123afe3ebefe869c4429a366676e4cf58bd5cfa1","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"546f54c284a7b87152d7d7c9ba14ef1114f12e47","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.147-remove-postinstall-123afe3ebefe869c4429a366676e4cf58bd5cfa1.tgz","fileCount":95,"integrity":"sha512-WdCsRjKdNVdV2vIMq4/CkF4Fyq8vh60sdg06BcswISCHdgIV5RP9SN3xOffmgf08cUdkN6TtdwARp9jJ8gJZlg==","signatures":[{"sig":"MEUCIHwlhtXg26z8MoAFRLKY7SnXBovXhijMt31+fDMw4UlZAiEAosaPP81SUllWR0HLM1XG6ldQ2wF3iCx2UcMNRISFQ3w=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":574948,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfzfgKCRA9TVsSAnZWagAAswAP/25E/ptxVedsO8tpatd+\nxHjGElCbQew31YeN0PERiPlAiae1TYm1QRf18vJGc/r+geFalHLyHbyjIqJs\nM2hyUJ8TyYCZUESpiKofKVuKigB0HBqVQPzLsljZPZTUZZO44XrP5u7jnjwg\naQ8LRh5Dq58uuDDAKz58tPVshqR25E57Uzjy/VKvS+F61NP2gEjPbVLemiPt\nTlfnXThUE7RL9bAUq0mGTC8+xnR3C/3cIRyJyZLQ+ERyl56IVAhNx/0jrzQu\ngfmD5A96I9uaA+pPLG8PeKe4zyMemGNj78fSSTkZ8E0ZAmuJUwcINDydzDU/\nwYgXwkoxx3XI55J2Mog5TTzHm3874BBrvfWT+tlhPZRD0u6wbmozWtDTHeiB\nEURsQTNOxvBK37svwPrVlF8b8FkNXw3ZCKMl38or/s1JUTtYUzk9bzqXYRd9\n5TY6z7g/JoOS+4TENEXMyTdfIeX+c9JY1f7UMVryeQOueLawWhty/1NO8I+t\n24Ae6sPaTkDPehKqadwi93EMexq8mYuACl223owUJTs4rHer+m10qDnU+M0z\nESPv+axCJVPBFXMZC4kuNeGrCuzhG5Rimm8MntoyGRtxDonPQOkSn+mdIRu1\nPK4vRQZYQi4mk1Eh00yBOKfKtmS8fASHanjG8ye6/ZpA+LFKVE3ErmJZNHyu\nz/Tq\r\n=Yq/r\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"123afe3ebefe869c4429a366676e4cf58bd5cfa1","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.34","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.32","@balena/jellyfish-logger":"0.0.158","@balena/jellyfish-metrics":"0.0.185","@balena/jellyfish-environment":"^2.3.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.1","husky":"^5.0.4","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.58","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.147-remove-postinstall-123afe3ebefe869c4429a366676e4cf58bd5cfa1_1607333897826_0.2833674134672781","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.147":{"name":"@balena/jellyfish-core","version":"0.1.147","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.147","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b2b347426c6a97d777ddd93ca6eb9dfb448f3020","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.147.tgz","fileCount":95,"integrity":"sha512-NbWZASeJ+2Bm5Itl+UP/Mu7W6SHFZjS0Sy2NyBsviA3kLztioy4VP2ppPJu1uw/JjuW+lXMIflOYynxOhTtiFA==","signatures":[{"sig":"MEUCIC49O9NuCq9k7R8tWJM6eGm4rhGVSBK0SnKF5IGqO4PpAiEAnVp5FHPMiksdFnkQvmxNAqy1eneHtzZ3LVIF4en4860=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":574888,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfzlpGCRA9TVsSAnZWagAAqRYQAKL0e+Pyd+hYxhgYYb/9\nin7leM0ZzBWZxk6R9v8uaMN5bEabs5oQKPGm20xh0v97wnOgGCitAE8CJSEe\nGQimY3VhHPIuLzdEIow/Tvx1Np6hCB9E2aMG/SgnTSv+Gn6NfDKCJinU73A7\nBQdihexj99XKgIazwPyUEMDWsQf8KSxe0CUsysJSgKBmdxz1wjKBPZQzYhA8\nuIZc4+iBmcQYEPa+v2d4Xrr/AKh30rRbUdP7ckBCsd3Pi4zlM8Cz7SoNpKmo\nVVbkYmezZb15bE8qVPxL2NU9KmhYVj/UuzhmJwKBT4k6fvAbXdUEsfFPFHzQ\nJtrP1QDJPS4UWeCShpOU2V/amVNfOLbmU82IG9A7Xzhw2coQhc/hL4+GeBgm\nWmvBSRZIGU/omSeuOsRgAwubWF7Y2e+AuYloVtlL21teJfulZN/Y4ZrTanrK\n9BtoboaP1Knag7GWOJvRf4HcwMVBY8VTBtS9b/MpbPF7eEsJJ/UJi1R6jUVA\n/BjNvQ7y7o3S9VYHfUHRAfSymsPTCEFgFe1f0cwXPdEFf9WEtgBMiCBcii82\n8h57qutdIVXlYkK8AKAzG08aEkR6uf7CrVAcAn6rgOkuHHPUuW73Xs5C/jKE\n2hxyewTds87wAH6vHy4+Bl+Z4GOXCIRzx1UIKf2+qxoo+/PiES3VNpSR+8Sf\nzOkr\r\n=hthA\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"582271b16be0e9710ecb89972dffbed5de725b5b","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.34","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.32","@balena/jellyfish-logger":"0.0.158","@balena/jellyfish-metrics":"0.0.185","@balena/jellyfish-environment":"^2.3.2"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.14.0","uuid":"^8.3.1","husky":"^5.0.4","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.58","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.147_1607359045646_0.4606613055219815","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.148-renovate-non-major-71dd1ffe75caf4e33f53c9c6bab5ff21f3cebfaf":{"name":"@balena/jellyfish-core","version":"0.1.148-renovate-non-major-71dd1ffe75caf4e33f53c9c6bab5ff21f3cebfaf","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.148-renovate-non-major-71dd1ffe75caf4e33f53c9c6bab5ff21f3cebfaf","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"fabf4ee4479379f59baf799b3432e74f6d37ed3d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.148-renovate-non-major-71dd1ffe75caf4e33f53c9c6bab5ff21f3cebfaf.tgz","fileCount":95,"integrity":"sha512-OXmBHZ6PSj8EiKTMvnc6jx8n2aHFHoxTlJuiOMNMS46ZtBPNNvqRxcg4Laonc652xrPhFIWv4FGukE+Q5n73Cw==","signatures":[{"sig":"MEYCIQDrt8IceFuBHxvBfy+0M9po94o06M4yHB+meD7mBjaPqwIhAJ2L7yJ2ixcwYkVGMqj5uY/KKJhNY/8pqQi1WzHhWc50","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":575015,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfzn0SCRA9TVsSAnZWagAAm5cP/3REPjCm10FDpU4j26gj\n7J/3ZOkJUwBE1ETUCBo6fzPFcwe4Jyzdres/nGKMtwNhAT9sXK2JmdpW0xrX\nqVPKMIgQiG5Ifs7k7f1iFmzsNiru7+CTj95VGBdXG8lf+F5BK5LqQzt2TLbl\nI/hX/gwK2l5215hQO6Coe5TlMbn4wgajnD4OARQo4t89qibXBuzN0hqdYJrB\n0P2q5gnCk84N28QXT1g3d81BEzqh+zF3Fd95bAaVDj13nb/tVRbPcZi8d4iW\n5lq46j8QZRv6s0+kqmBU6nTCap4eDwq5ZbLCbeLGi6tCgiPS+WbdpPEDsJwi\nT+d1legpsAIAZZAmeC6N/BnPi1tJSWRA3O0PaV4OeuPnYc1pUQf7oTt4PHI7\n0Fp/3P4I9AAY0ZBWIHgLmqqPnEt+RBkhwe6Asbk+ZhF+gJRdLSTA/JTjdaUu\n4C53IopY5fXR6vg+N6ArOA21sc1FFmpSPOgCzaPyMZsMxA9ij1gVX+Z5as0t\nsoYf7C182rtn6qqjgvGM+g2tiDsLQHluwGMxWQ5tmVXRi2nEco5uDloQjoYl\nism5xdnS3r2S6Ibbd0QzAE/OCcCxi3ADFalOE9spJICJ4IBtej05FZh2TT+0\nElvcGEorrMXHOL4cdMv8L7//bL+74l2mDQn0qfV8Rq6s4i4junO3e4Tseu7k\npNuR\r\n=eZ90\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"71dd1ffe75caf4e33f53c9c6bab5ff21f3cebfaf","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.35","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.33","@balena/jellyfish-logger":"0.0.160","@balena/jellyfish-metrics":"0.0.186","@balena/jellyfish-environment":"^2.3.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.1","husky":"^5.0.4","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.60","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.148-renovate-non-major-71dd1ffe75caf4e33f53c9c6bab5ff21f3cebfaf_1607367954311_0.3571426771815258","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.148":{"name":"@balena/jellyfish-core","version":"0.1.148","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.148","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f1468e1534b4e3a2f7252c5270c291efb6752562","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.148.tgz","fileCount":95,"integrity":"sha512-SZArIjuMpg9sdDq1Q+PJQBCk/WFaKRJqqbbaTQFp5AWwg0p7H4LtLvOv8fOWv1TqxCjgoLV+tjeTxJ+MDfSH5w==","signatures":[{"sig":"MEYCIQDUatB5jQneTDHnaTVSTF5H2p8Pv0WI9X8fm3Ys82+tPAIhALvU4f7tWqlhB1XT/FwieMy4wOMKSgd1mL6lXA+nVB3p","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":574955,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfzn8bCRA9TVsSAnZWagAAczUP/i62y9Un9bbQsNAY1wbp\nthZWs1lhKzIbxt28xLVSjpCh+lSRE9u1ihe8qjDwC4icJFx4JVkwG1Ng/WZu\n77FaZ6dqcpACrYL+dpHKggYJZII/EJpJYWvzlzHd9tBLzkAjfXcmc6UqciO0\nZW0ezXpApfXXNgsiiu/V2c1sBaAyzFSzK93ihXuhw/OiMj6gjiSiUdMJtvq5\nYeI6db7YDCaFXP1ZYLMm2z7GFFnYXYKPHtt59lRJaGMnqRWIdxTI5gYmdBKq\n8l+MoMQUudIMRYHHs2cPPOo00cM0rPlZyFCp92N8KCIKe3Qhd5ySoAYvy0Ou\n6PmbSE9AofTf59OkoUFvxGqAm+D6VuGRviHEDeI60g33RXQjeAUco//nHP3O\nwazIWvT0FTv92tvK2Es6RVqRw6mE7m6KIOuED2WJi8b+yvoMMeriMfdfzU+Z\nnI6H5J960HMwuUyt8toVtKmTb/t0j0FM3SjD5G6/1XqMk4ulrRMSigdxVFjH\nbasD5TqWY+Ms8EPApWYtrUZmuc2T2TJBhoIfh9W2uXT0Fl+iBwjATKKphuBW\n8AkcW0l3EENQ16nbbZZIP0YAGsiXlWR2vqL+AuWHh5MvnQALsBgnN27SQasb\nBs9YnkFbN8kT3ZW4x6qTYE9tKWhAU2K6TEpHHNsTVpj4pTmpexxcGfnZQn5Z\n4z5w\r\n=T6sW\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"fbcea685f0409bda31ea97f066fa0042e8bb1b64","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.35","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.33","@balena/jellyfish-logger":"0.0.160","@balena/jellyfish-metrics":"0.0.186","@balena/jellyfish-environment":"^2.3.3"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.14.0","uuid":"^8.3.1","husky":"^5.0.4","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.60","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.148_1607368475012_0.3185425343787891","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.149-renovate-non-major-9d1cc0a9aebe6b595ed0643fd6578737221280a7":{"name":"@balena/jellyfish-core","version":"0.1.149-renovate-non-major-9d1cc0a9aebe6b595ed0643fd6578737221280a7","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.149-renovate-non-major-9d1cc0a9aebe6b595ed0643fd6578737221280a7","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0bcc6aa7c1876e5f7dbe62f47f6b94ab16a7b9f5","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.149-renovate-non-major-9d1cc0a9aebe6b595ed0643fd6578737221280a7.tgz","fileCount":95,"integrity":"sha512-pmEMv60WLGdwrNUxAJyY5YXcezD0z9lgolCkP3mP1b3GvKesZoxmIRUNWrhH6eZAfR0/aHHpgRH3oq9jJaRsAw==","signatures":[{"sig":"MEUCIGFo8DyL6YFeGRS9cgASsGag3K22TguGpZJZy8ihgENpAiEAhgfL7vqLbY57VtuOK1YOP3vh+7kACyK+esMpmE9K0wM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":575117,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfzpPWCRA9TVsSAnZWagAAUg8P+gOWO8LCJ4XJqHZ8aPCR\ngvC+WgRENZpGv2yvxXlJ7d/dVSnBcBNuvSUxPxyHXV8Nclil2r3FBuPoxcQQ\nFUmdLDEXPC+yxV1bHPa6kTCIUD6hj6xmVOyBHwQq2mU4LljVew8LSzQLKr8V\nYJEfUWQzbvpIJNdP7q/ioFtW4iubQ0TELRV4QyOayLPRdp8a0YCQZdJAQP95\nRa5LWzAb6SviauLRrD9QAH+WoPY62mXypGudvak5hxwTl9+OX1uZ6FLjijfv\nJsHSZ3I4CLAtMnALTlZeFcdiiRbGTsht20tFshLPjSSmW/WsdJIhLbDn6BWy\nfc6yJw1Wvel35vjuGvnMhGbUutuSqNuFddf0a76HF2zpzNH1s84GfoF0gjLa\n01Tn3xJY3SayE7gCx9yXYZU93mvMrxK7qaxzZG2b+iAcm0kGwOv49s2DAYNL\nbXQkrit4t45gPJfbPW7FNS7P9z37ZBzlXMRLau8gMUfP5MrMpj83T4/d2xq1\nS0FjK5AkVEUJUQ8+zlMIXdW9SRS29bUzSxjF6VPHQAlRKprWcAzuWCP5OV0o\nA0KzRnTHZhaNBave2HwniuGcplQX6U+EJQ0QteOqi7yyGyJDaGjy4GfID0fi\nK/A7YgkE5lFax3dXzwum6IREsdtnQCr3oWsvej4aI9eMurOgl1m5AIjDE9jd\nZbI0\r\n=SsnQ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"9d1cc0a9aebe6b595ed0643fd6578737221280a7","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.35","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.33","@balena/jellyfish-logger":"0.0.160","@balena/jellyfish-metrics":"0.0.186","@balena/jellyfish-environment":"^2.3.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.1","husky":"^5.0.4","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.61","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.149-renovate-non-major-9d1cc0a9aebe6b595ed0643fd6578737221280a7_1607373781650_0.06300135382515881","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.149":{"name":"@balena/jellyfish-core","version":"0.1.149","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.149","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"89e0a03dfcc3ea74f11bf31e389d667a50be43e0","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.149.tgz","fileCount":95,"integrity":"sha512-p3Nz1WQsIDYHb1XZ251abqthQet+TPnHdtEYaN+tb1gHEGhYpDk+vfj9gi70knGrg/CFEQ+YJPpxcZurAKmWCQ==","signatures":[{"sig":"MEUCIQCEZQhFyKOQxdGOwSqVN0k4lJSX8ebwZtYapFZ93u+iRgIgc8LwMySJ2+M02Ps0FkMbbhej5E5DOGnJ0tOptKUrwKg=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":575057,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfzpU4CRA9TVsSAnZWagAAN9gP/1qE64561dNnNtabxmjn\nl/mAELaf4yDRPu0Bgl0kgI1a8iDbdvvbLeIRKyYzhJgjnyuP+WfAZmlkNvvI\n5voYSJtz+EbrLqjlUdgPWQCib5aLGyEgf0lkmGBYEucnmuH6yVQMwFjFLZek\nePIfRLDmamKs/q5Mib6jgikbT4y05yJJcz4LNJLl/G3RY3Ad3M2CMkZEbtQj\ncIQoEHY81mJNElqAZvFobUdg3q4MDl2mSkPtUKTDTvfwLUZdeBtNEdQoQ6FN\nqKkpR2DBPliy1hEw0WvQbLmiVh5TOvuve4LLZ5Kovl+pEdSgiJbrpcaRVYXa\nPkXahFm0A1B4zJOPuS59ej0T3T+WIdHp0xWSn++FLEyUd67qybqY/9J9xnMX\nsf720mcsq+WY+cxVthUWQSuT+uqR+L4TX6Bvcv6gA7oTMHGjOhddXCsue9fe\nXEhWuFpmBZelnMr0GzS6ATbMdz7U69YIJOkgHclTgwyzElSUkIAHQz1FS4lq\nGPW3qdolwcNbDfif2g9nReMBwodXS6M+IgkLxl+9GFjMxkz2cH6ygYAew4yq\nYxOWi+lcC/DDC90vGZ1etr4fOl5XAmjiJTVX5rMDTJOnfIjhlEMHC099ibaW\nMXmiuQMDaNtNH4FqEqIkHHKr26IBkyxKHGLjGN2J9XyvxFcywOeSy9TLcAPg\ntSfB\r\n=wGcl\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"5357a00fb354b64e905fbb2b055906a2c27df74c","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.35","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.33","@balena/jellyfish-logger":"0.0.160","@balena/jellyfish-metrics":"0.0.186","@balena/jellyfish-environment":"^2.3.3"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.14.0","uuid":"^8.3.1","husky":"^5.0.4","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.61","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.149_1607374135695_0.36961528968074275","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.150-add-husky-postinstall-036f69fc1a480bbbbbffa0f3c4caa4e2442987fb":{"name":"@balena/jellyfish-core","version":"0.1.150-add-husky-postinstall-036f69fc1a480bbbbbffa0f3c4caa4e2442987fb","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.150-add-husky-postinstall-036f69fc1a480bbbbbffa0f3c4caa4e2442987fb","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"241cad8d2ee634d3ff618b7ec55d0b61a1cc3324","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.150-add-husky-postinstall-036f69fc1a480bbbbbffa0f3c4caa4e2442987fb.tgz","fileCount":95,"integrity":"sha512-joCx15LMagp0fpqhrx5VyIrIwFJH4rSvDAuZ6f8L8RLrNzDD38Zh790/xQUN1W/CBxyrmKsZLKpIeheYasO/fQ==","signatures":[{"sig":"MEUCIHsXWk/VT7x8BSjFoHNFAPx0Uv/d450gWWRmTeNH0RP2AiEA98FF1rhft3AWnhErKmVKRnP3JCFPUgG4QYBXapk9GlA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":575266,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfzvZyCRA9TVsSAnZWagAAiqQP/37c72EpQm9nIsK7gwFW\nYtSBucUNW5PI7TEl/ep1coQ/oXAuLOKRSJEyvp8ukTsXt8cCc3k/X1CXAqM+\nEnnWw9jVcpnLA6savXt0yODQiQN8glqJwuoUU4uoHd3DJiDdGa8wAf86LGV3\n2VRwdrwqROjQ5QE8eCeZa4TsIQDSKpuQCMqayd6n73xyr/AEbEksb3EMCCbH\nPYjH6KZ1XmlHjYSau42XObFF5QEkPoP72wof56FHGhbyG5ZkQbiCuTXBslbF\nPNEZ3jR6NgVHvi6hY89EcDA6Ej/hnRTZMyb7N2KDLmCpXxjrpym0HoJqAPVZ\n/5lnIU6WZ99mE4VS6z4MC6l4TgUtbUm+8fDRdif2IfMHPpVbpgtSz/H3UQLR\nJ020i02uN2ewXqq3QhRmeU69Gt+B9qDq78IrejDINLqhL6JkbKH2TNXa/cHL\nwTux4Ga1OTg0V92TbjmtU43EGxPay41yh9BnAGE5iKBM9z01gUBezs4in+Oh\nvG4hcBf1hK9YLl8a7sucackKlzGsQ8UkPeFkRhjbEp4D5uoVQGzu8MLthtR3\nyYrtZ4r+57rGTS+pFDCcbDVbAnmZhOp1IylJ8bGmKP9YHbglrVLeMK2/BoJJ\ngu/0MndUaDgnxqAnT0rZCOg9y3c4H1/T2Mcx69TLodUABkrYwPDpLxsxYy/M\n7yRt\r\n=0vdx\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"036f69fc1a480bbbbbffa0f3c4caa4e2442987fb","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [[ -z \"$CI\" && -d \".git\" ]]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.35","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.33","@balena/jellyfish-logger":"0.0.160","@balena/jellyfish-metrics":"0.0.186","@balena/jellyfish-environment":"^2.3.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.1","husky":"^5.0.4","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.61","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.150-add-husky-postinstall-036f69fc1a480bbbbbffa0f3c4caa4e2442987fb_1607399026102_0.8755515263071048","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.0.0-links-aggregation-05e2e39fa633f616c55c3c8ca1c9aab0bee73600":{"name":"@balena/jellyfish-core","version":"1.0.0-links-aggregation-05e2e39fa633f616c55c3c8ca1c9aab0bee73600","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.0.0-links-aggregation-05e2e39fa633f616c55c3c8ca1c9aab0bee73600","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"64623839604f121860d3674c7efdca952a630e96","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.0.0-links-aggregation-05e2e39fa633f616c55c3c8ca1c9aab0bee73600.tgz","fileCount":95,"integrity":"sha512-YLqBPRUfDYhUExIb8H1BRoG1kf2uTpmrwVOg4Z1y6SKgWtbf3s1v0prRZgkEwSkM0wiAnK0VTvJ2wotX3244tQ==","signatures":[{"sig":"MEYCIQCwQb2mDQTUQW32zJek3P/HHiJjtXj0wjL+RA2LSQZ0xQIhAPht3s/SvHHlQf8oRFoLgmo2hE4UWdTJ6ON+5vPS8d3+","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":575368,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfzw/pCRA9TVsSAnZWagAAg4EP/icpzM4DiGO8GbDByw6G\nt1mJl1I62LkQVdKsplIn7ue+wctMi12cLfBENTseEGYI9lVCAyAfbPBcEDaY\nujBLavDxw/JEmtHrgHjQt9N4FB6elsbyKDEydOXnUCvjls+BigSxbm/qMYFb\nY/817Wpp7hhJCqCVO08DfokKoK+JwfgqG0VR36IH0diD57Vjl+RoITnxx5u7\noaGxQQgpBKmyoylN0LByBDmEbA+5IbfdqI1cPihg84kVrGjtvuyxjhIG72GE\nqdFL/F+ljxim4DwSaKm7FfBTJiqzARaxd17wMV0gqGyyIWzZAX9ufKnASls5\npd84PCWgre7NX78ds1MUVqCweKvw8Evk/bj+JAoC4pvLBAm0MJZyGXmP8tCN\n8Gly+uDcAqVR9zOlpZ/4ZL9AIaM77FmwE/qn8EeRCRSeCYIhnEpU7GPHrFzl\n3CLPtJvWXfZn9lm+q1eMvGJtLDkKmp8xS5d8W+i+rZx92cxheGd0XeT267FP\nuxLVBt7k5NxuBQMzwxHoMxvdU5BhJHDsUizr/Ans36UpRjskkfCVvarRaMcV\n/Glp0ijRFE8zVDlxu63JHEvKGSc70CwGn0jVKYdEVbdFiFbkRNQ0s8JBHej1\nAXP+119P9Atgco6ZN4LkK5uNDCO76zZfr1Akp1RBkRDSukKz1i/1iOLMMELj\nok3V\r\n=GOkz\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"05e2e39fa633f616c55c3c8ca1c9aab0bee73600","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [[ -z \"$CI\" && -d \".git\" ]]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.35","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.33","@balena/jellyfish-logger":"0.0.160","@balena/jellyfish-metrics":"0.0.186","@balena/jellyfish-environment":"^2.3.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.1","husky":"^5.0.4","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.61","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.0.0-links-aggregation-05e2e39fa633f616c55c3c8ca1c9aab0bee73600_1607405544799_0.37569006796701876","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.150":{"name":"@balena/jellyfish-core","version":"0.1.150","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.150","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"75445ce5dce28a6998ad45837e062c0f9a337267","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.150.tgz","fileCount":95,"integrity":"sha512-QIQBtvCU6RecHPdJylIUCyeVsPjMpNNoyoHdNQiTqe4hM5R3jdb5cZQDi1McsC2uERWUUIE1o+li7l84ZC2RLA==","signatures":[{"sig":"MEUCIQDTmXktGlVMfdZux1OM9QTqyCXlqxBeaVcQJTIEzv4migIgLtHWlWyTps/hnwsIdYW/M8E338OX2rDnncf08dgMBEk=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":575203,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfz1WPCRA9TVsSAnZWagAA/RIQAI5qyJBtnmYzzfmdO0cJ\nd4CJ2Ba4kA5P3Jd4J97TECzJQo0ObrQNRW6lTETjTJz/vxKH2i4lnczSadiv\nHPHIpIcfRUNkGKkO4zl9lUm2c4GMyidYOcR3A3oN95YEY+HipvS/fiaRhmPJ\nLeLJCQmFT5/opEblfemLw1HUXEM57SYuYC+YLjhYPMWjwjbaQYtdVKUlzvX5\nhfxWP8KFHwWZ2QO1FlldoxWc+jJVL1kAUMdiT2YYzZLdTDroQRsu/cAZsY8w\n39nSYgTNfDp5Br3L91dHKVteG7YcM+HlbNb66iT4nXklMNZb7kdpAG3zQBfQ\nMFZyNXDhUQLggeaAGTDSZgUpGFC1O3Rfe1AcvKP/jfxkadtyX0poPyH/tDIB\nj07qz9RqpxjwNrW6eNIsBXCk6sIHeAINctOGtNHM1yDqJuOn+xljaDhOFzHR\nJkbRrJmOY2Z2uCYHmVgOxIzf99SBVbCeVcigfLF4PjwdV184HTZpCX6/twqo\ncCOWMS/Ukpjlr8jGlUFSpFPzLKKUmHwZCMMnXxG8n3ZyKGBnOsaPZNbNixsm\nR70JPqdtl+DfoxVlepxddwxAmPKX8KRynDKw10o0ueVmUNnE0ZZ+GxjItxuP\nrn2lH6T729+H8gqnVImSJgElTAjlefnDl9UPdt7Eu4wfKbBGPAwE1cz7i9kd\nu2Hj\r\n=IJtC\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c3d09bfd2c381e743a94fd7256a618a50f2a4f5d","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [[ -z \"$CI\" && -d \".git\" ]]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.35","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.33","@balena/jellyfish-logger":"0.0.160","@balena/jellyfish-metrics":"0.0.186","@balena/jellyfish-environment":"^2.3.3"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.14.0","uuid":"^8.3.1","husky":"^5.0.4","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.61","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.150_1607423375231_0.8592047149414841","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.151-renovate-non-major-d8817dc6ce14689f5a3f820ce5c49f17a87d992d":{"name":"@balena/jellyfish-core","version":"0.1.151-renovate-non-major-d8817dc6ce14689f5a3f820ce5c49f17a87d992d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.151-renovate-non-major-d8817dc6ce14689f5a3f820ce5c49f17a87d992d","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"48055ff480c2b78ac8160de354d4a4f8179d487c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.151-renovate-non-major-d8817dc6ce14689f5a3f820ce5c49f17a87d992d.tgz","fileCount":95,"integrity":"sha512-vcAJFPCWGoaQoMioTvvGwlvcStdqM5pCDLBmQ1W6hnGUtm/SOKx63c1Fr91Sa44MM8y/7/FuJJBR6dvK4qF3gg==","signatures":[{"sig":"MEYCIQDGEihLqJYLL/omT3Kee3VdhFgRd9YTx+l/JoyNMvnaWQIhAOUEQty+XoRperAvYoqiIZR/gCxRh8a2ylmxkxRRaRIM","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":575330,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfz1eACRA9TVsSAnZWagAAf/EP/RPdKPQb/WLh/JkkwSKe\nUIPGskjd7tSrbWv0S9kEIlt16DsMk4HZk3xtH21wijztJxABlvBqqzoydwCP\nhRNEbLdi31Fgsu+jM0YEuJVLevQW8PNbnlPVFmMxuzIVTC2lVR8I9TQdUDzS\nrqZOJ6CyGBDnDQ7YdrAGWT0tTvsd1TsuzSTovHqbEe9CFR9EItYNK1Cr+hg7\nnO1U2nUyl07aFqsapt3KhO0GjnjiyrDttAKH27BHSbeOI5rrUEy7Mn6n+T31\nQDHOurz8xWCYw+k1h0MUaRGC1ploB5H1QkgaUhTn+WWAJGvbvcY4sdkDyS59\nTZSy+w+ETfUIFGeYnFx/swXf/G02wouKhPgPHQzUmoiz928MqDxcIEwl+ms5\nJxJGKF0lVJkwoCb2KP5nkJ1Kl1BM69cT9FELpk5kJYwTSlLbd6wICFEGVm65\n+EEM7+bCn5SQPE3cFpyE8Irx+EF73rr42JR0nLJ8yNbhrc9QSFkXkVl1PO9O\nc7hs+z73KaqLKqDuiMM/n0/K/KAqSZkOb+2vA6W1biY9TKL1LdQv6Gt3tgbJ\nkO5k/Q3ByXGG/M2tbeIUKtkvxlHbGKgacVB3RuP+mDW331OXCH8/P/kScQva\nmytGOLX1rvXYaHtW0jnHP0GJNSmhS09wkdUA7hkG//0SVoWmP3XWUdyrdJNt\nkvG5\r\n=He8A\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d8817dc6ce14689f5a3f820ce5c49f17a87d992d","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [[ -z \"$CI\" && -d \".git\" ]]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.35","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.33","@balena/jellyfish-logger":"0.0.161","@balena/jellyfish-metrics":"0.0.187","@balena/jellyfish-environment":"^2.3.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.1","husky":"^5.0.4","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.62","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.151-renovate-non-major-d8817dc6ce14689f5a3f820ce5c49f17a87d992d_1607423872165_0.10140797153691206","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.151-renovate-non-major-d0e3b239bfb50160a2a4889ca8bdbb68da02f9f4":{"name":"@balena/jellyfish-core","version":"0.1.151-renovate-non-major-d0e3b239bfb50160a2a4889ca8bdbb68da02f9f4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.151-renovate-non-major-d0e3b239bfb50160a2a4889ca8bdbb68da02f9f4","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"556a4e36f82342aba453dd7a34412caa1b5ce3e6","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.151-renovate-non-major-d0e3b239bfb50160a2a4889ca8bdbb68da02f9f4.tgz","fileCount":95,"integrity":"sha512-vRfd8YPbBHMdxKwO/p0sopxtJzR1lR8bLUIR90kDmhHHJiSJI9OO1qHAexwKNT3CETYCeuILViI9fmCbWtvcHg==","signatures":[{"sig":"MEYCIQDkSqnlb5l+/2BiYSFZ8PbR8KAtaIf+6DzakRl49pZTiQIhAMdDGn50AHgbUVMQ33I9aID8o+an+iNp8eShvGvzyUjo","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":575330,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfz1jeCRA9TVsSAnZWagAA6fQP/25RmxzgvZ+hTYY8ixr2\ne+A+LKfUyybxL/VpcJjLSU1kS18ZL8TY3/tWFewR1Hy/K+zmFPrMMMh7vLoz\nt15rRWtWlcWZu489Ur9M8W0FITwPQYLWHdwKZcdMrfHWvSN8QggydCLGo02A\njdsbvgqpkVY4zkJ+OtmGNgWBBv2VMOAhfFO0Lcg7D2r0jE6s+rbiJuiiCrM/\nZucckmiL6F4UP5E+urjfBAAN56MQOL0IZdNqlY5U8NHqhpzRtyOEOcDOIV5k\nwOCNqztuyQfG9F6CTmFnFW5XQoGfWIvbxRQK3V7GKDXbDkoGU8G3ZUoUPPVS\nO7Ymy8RsP0s1MYpO5zXnXeaJZx+ikglTLue6LPCoyfn7UbbxIkkfHYeQe0yq\nX4Yhch225CosZDY9mjq/HfeN7ERveibsKjqffhtcPbBWlqpU1mDV2AhBtVh3\nyddpeIsZMvq3+zRye9zVeGMtomjO3d0h+cVZbBiUx1gqWu9qjchykhZlqtwg\nVlqtog5EBaeWeGTnyWIZqaew2VWXOwWRPgfwJB7uC0uulY05l3vjnc2K+Oly\nHhziGSlib4bA+Z0qdKu6mNMRUJP9qfeCzW3+4BF7bxofdyZsgJYrF7uyyUgd\n74OD9ecLaE+RAu6mULBpBcIOv0PBjnstOXEKL2S189f6dELuxUdmhLMJKxgU\nGerJ\r\n=N+Zk\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d0e3b239bfb50160a2a4889ca8bdbb68da02f9f4","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [[ -z \"$CI\" && -d \".git\" ]]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.36","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.34","@balena/jellyfish-logger":"0.0.162","@balena/jellyfish-metrics":"0.0.187","@balena/jellyfish-environment":"^2.3.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.1","husky":"^5.0.4","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.62","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.151-renovate-non-major-d0e3b239bfb50160a2a4889ca8bdbb68da02f9f4_1607424222150_0.032482153431725225","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.151-renovate-non-major-8da95af6d034468d09261ad16484912bbed9f601":{"name":"@balena/jellyfish-core","version":"0.1.151-renovate-non-major-8da95af6d034468d09261ad16484912bbed9f601","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.151-renovate-non-major-8da95af6d034468d09261ad16484912bbed9f601","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c217bc44192d4c20df71b8509f8e80bb5657899b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.151-renovate-non-major-8da95af6d034468d09261ad16484912bbed9f601.tgz","fileCount":95,"integrity":"sha512-2+VSedcKaEQ8uovrP6URlqObkJjNj2Rt4cgzs+9OKMK5y7mWlVBPVkm+h8BpFyss4hEaV6ZyFt/4XBNK11iYtg==","signatures":[{"sig":"MEUCICZLh/Y0plo2Nc6mpGamn4HVlXfHWYnJMwLC7WZ3pxzNAiEA481w+j017pt/Thb6r3d8GOng2/IT5reom6RFVy7tsrk=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":575330,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfz1nxCRA9TVsSAnZWagAA6lEP/2GPkKy4gwtrspYxsWlg\n4r8aEkdoQ6MhFoxZbLG2Uvf3h6ZMMKGc8gfshW10rNl2gqKelVIpJjauKpqT\nfmqudEutsnLHkSAyBbBLsNj2RXSKUkJSke6NweqsVpGpdns7nRiGlhxDKr4Q\npbNALGK1xbsolSUn1qgUrFRf4vpXMEOrACSYIIGl3LCBUvavtGEYxUwQ9BTe\nvRgvhOhHPSiZ0IduLHNOACFKw1TfruIiCJNPELt0M8avhXg5Zb/uTq81zl8x\nMUKh9BEMy5R1dApqIuO/ctkVuBBIJ1CACyEQ4mPVpO6Omt+hdmY4ECKBL4uU\nYDa+OmD8wVP4tAOCN0mEO9GT3QzPlyCFhx/4ZHFWQ4zCPHXn5vmRCSnZCYv5\nZZsyHPWf9ie6nFgP++5N13q1aPpMsX7Q7W1N2U0PpJBY+mq4Jv/g2KDaeG2O\n/NWk2Re7TjJvMJFLbLYMPfVeTPceLA/q9bTUUoQhsL6heRvczH5RjFfAVlpS\noyBUhnybBLsZMogFdUVwgi7fF0Dnj9tui0QjeiefDu/JOWWbBJQALNl67ZT8\n+SERmjV+ZoM3dZ66EKBO8+9JfJUrd6wZdnLHjE88UCC1URDFBj3HX21SJX6H\nmFVaKmxI+xcIH9hCrhDDcQpN7GP8o4CKTNUzrmGZVEbvgiRYOow45I/YvMI1\na5e9\r\n=jrUQ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8da95af6d034468d09261ad16484912bbed9f601","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [[ -z \"$CI\" && -d \".git\" ]]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.36","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.34","@balena/jellyfish-logger":"0.0.162","@balena/jellyfish-metrics":"0.0.188","@balena/jellyfish-environment":"^2.3.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.1","husky":"^5.0.4","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.62","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.151-renovate-non-major-8da95af6d034468d09261ad16484912bbed9f601_1607424497327_0.32188335116229805","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.151-renovate-non-major-ab08216ecc883c4a2599dff61d692d41d5885d3c":{"name":"@balena/jellyfish-core","version":"0.1.151-renovate-non-major-ab08216ecc883c4a2599dff61d692d41d5885d3c","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.151-renovate-non-major-ab08216ecc883c4a2599dff61d692d41d5885d3c","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c6abb3aaab1f262ef301b15be01ce67d0827fe80","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.151-renovate-non-major-ab08216ecc883c4a2599dff61d692d41d5885d3c.tgz","fileCount":95,"integrity":"sha512-WJPT0iJjy+GsDg+nnttfjK8B7JQqgfvKXyjZOKkbgwZNEnxaBOkjnCblwz+8Wm/lLn2AU3BY3QxoQnM+QBgjUQ==","signatures":[{"sig":"MEQCIHXtuX+DUK7MKdkSXDScmlFr9J6SPCXangsc5O4Yil/mAiB57XxHE8sPpr5qbHAviWWBCRbW2NUQETIp3G/2HTs0HQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":575330,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfz2MTCRA9TVsSAnZWagAAP9IQAI1eWb8f6M19uMnNFdgJ\nVFmFnxULY1Ys28tQIQhimA7TI429+IIApVpH5k7FUbQQ59n/QhQlzMaGMNt+\nPf56jUQX4K0gqwKFUiFU8QMkCTriilx/kTLspJ/s9UO2l8U29O0SgIHDUhrU\nC5/fd7Ks9LQets0+fVDurr52Q4H1rfQ/pzFqXJLw5OwHtUkGiGSS+z3+2uuR\na24m9+fmc4IRIt7KrDqnupvIoljPW1w/wSvZ0P4cIbty7QfMpWjdbZ5RR3i2\nD6eYSzdcBwHGxUz6TqkzUuHStIhXZhefpMFa/mTYHrjoecnDB7dUxxGHSHqg\nX4dTivpEP7d9MALicFScoQVepEeS7R/dCb4TVy+BvgN4vGxuczWDTKCocOCN\n+XcAJDbygieLVOD1haGpf7tuINmEz2zqojIFas5aY12QL+r7jkr6PWRaCsqq\nXSTeGWJqNJKFYNT4RJaD/Otk+z2k/+Tvi9VYxsHhkLnX0nH79YgtjpPUnH/6\nu+hNffHuTGiohH3d8YagUY1yYsUG1qSzK+cBm8qQfqQCPvxC1phSjCC604qu\nLllR/nnTTunaQ8suZHNa1TehXciA3gd77+nxzcBbbKwAlygHNg52ubpxnmLA\nT4N9qPAyVkHqCT4unKVPuRpnCFSh7ZfR/63BNTBE4oVkKLnvbcbdR2KgoXGJ\negdE\r\n=ax7I\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ab08216ecc883c4a2599dff61d692d41d5885d3c","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [[ -z \"$CI\" && -d \".git\" ]]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.36","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.34","@balena/jellyfish-logger":"0.0.162","@balena/jellyfish-metrics":"0.0.189","@balena/jellyfish-environment":"^2.3.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.1","husky":"^5.0.4","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.62","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.151-renovate-non-major-ab08216ecc883c4a2599dff61d692d41d5885d3c_1607426834870_0.9152599851998129","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.151":{"name":"@balena/jellyfish-core","version":"0.1.151","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.151","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f187bca926a34e6d9c936ba52d81ca2fdf81da5e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.151.tgz","fileCount":95,"integrity":"sha512-psAbpvyh+jku0dFLTV9cIFa536/kGL7YwIQ9JPrKykJ0rgrMbOovAwdR4njd9B4FfBghr3dniM684xHtu07EOA==","signatures":[{"sig":"MEUCIG1QktCt0wr29TrA1MnvttSmwee5cD/wckmhHqKKn/AlAiEA6rvvWAVwSk1UF8DmTK1vIpbWVKKpOoGsGe6sJ05p9Js=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":575270,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfz2SCCRA9TVsSAnZWagAAS7sP/2zvaX6bMezzf3Ql7Sqh\n/Og4qS660jC9x/XT9VWHcwKjIBdk/Wo7bHN2/VHl/Nj93kFpAGFxMbNQiIhv\nAL9TbBbhuUZDEJioX78RdVTphja+xRbqGwFuBGBLqnq91oJYNNzReoJCgM6n\nd8mxJ6U7R3tGEOGaoPSfvpmVxmDH+CkOZ5j09nIpINQ/3UwOwDoZjr65aG7I\nGu5jV8f6i60PXczY1/S/trItOBWm31gvA6v2A8bA6H5TR6P8CuCn3ody8Hxm\n1Tdn7eeQVpHqL9Ij560Ex0mImQHet6EpB9+C7pb2UebDsWvHkIloHM9bKVwX\nwlIRd7TJMjquLKRlWIGo0nNOrDj2/e9VAYNoPJ5Rif4DfNyGHi/2TJGwwN9u\n/pAP4kv+gjUklIeeUTTZJkri+4BVCRQn79DSTVCZdBULT8I0dm8+cRtvv0eD\nrgq1K3OyJgn5rPh5MCtLAJvdV0o69Jr7zjndVmXhWkAfN3AGj8kVy4ivcuVb\nOEI45wF6ORUerVmgCugrI7K7Xp7571vXEbUHp1LGXQZh/fwt58/ytYifapLE\nY3LYzkPlD4MZVaU2IS2VSBhFr8UwdUkXyftcDM55qiWYUHFeydKuE32I+TZz\nDldsdyK8UUYUtBaOjXZO4Ut8FzSFiTeWYxKVBuNJSqebuAaWyrT4c0p4b6z9\nSipQ\r\n=XIid\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4bcbab8ba226209f876846ac380076733d73fe1a","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [[ -z \"$CI\" && -d \".git\" ]]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.36","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.34","@balena/jellyfish-logger":"0.0.162","@balena/jellyfish-metrics":"0.0.189","@balena/jellyfish-environment":"^2.3.4"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.14.0","uuid":"^8.3.1","husky":"^5.0.4","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.62","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.151_1607427201524_0.9651561788027645","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.152-renovate-non-major-f13f71224b0f007ca66cbcf069bd0fe7fe99a5ca":{"name":"@balena/jellyfish-core","version":"0.1.152-renovate-non-major-f13f71224b0f007ca66cbcf069bd0fe7fe99a5ca","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.152-renovate-non-major-f13f71224b0f007ca66cbcf069bd0fe7fe99a5ca","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f3f1de68858f45cd62bee0f0af7fee0e59ab5168","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.152-renovate-non-major-f13f71224b0f007ca66cbcf069bd0fe7fe99a5ca.tgz","fileCount":95,"integrity":"sha512-mlEoBr+4hqDt8zjqUyW72QZtJYUjGArxuWD1rjQs7SPvhC8Guk+0wpF5ukSNLqEMbSn/e0NXwRniN4VwmUKpqw==","signatures":[{"sig":"MEQCIG/MYfzEuxamGkio4OWI+4kIKuQJujoS/KYEEkvqJSE9AiBwp/KBDM4k4ziyOPc3ecmgKbBzrl2C44+D/LHEc6hX/w==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":575397,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfz/RQCRA9TVsSAnZWagAA7BkP/RrzSjLni1Ipom6eCT7D\nWlgvqLESDZEjb/vxDM2RiFrXaq+bC/n6kqjmJVKSoPZDKPu6vQFusLjNwkJv\n+taFxdIvbLz2tFgEs+RZQcb7DxL0nu4yeLQxohorqCxwZoqQ0jpnC5XmwRQ/\nBIQzT3EdBykHSnj8mbPnw6vsfqkCfJm2JV/G2vlKt/oYJSTtf++RZx86OQ0n\npNj96P1Ejf5mCn/EANnUPyuvEYTv3vNLt9aiIDTSe/55oImHZjPomR4ZBDx6\n3VFc4DLSYqHGRGrNh2NtBXRmTCBfZFxJURItLSJcOQbnJrHv2thA+QYhefjA\noBgp1ZQGif8IZ1ttwc8pCGW3tcxqteWH4Ar/WIhhja9AUJg3BJZKXGpTiGLZ\nQP7B9GFmsc/IOesnV8xAKf+F+fan9sUTj9Rx1HmWn1mD7CviB+OIEvyvFmGD\ndhoFHqC4xVG/tUH0czB/x3nk/T+LHq/aPANlgd5dHP5KTzB0rQ8J3b801Fp7\ngkYPxshVZGNzQhZCcgH0fARc1dly5/EVS/EKnFMyDbDFwe2ryCD8E/80JZC/\n3jd+cwyW2XfV/DK4GkMwS7v5DtkcD5tbbV8hJinH61ksXW61shjcJR8bWESF\nsC1aPu60r8AgnNi+VMX0zIp3oRKCubjB1efPSJ6kY/QgI6tCSHZav1rYSzCb\n1UqL\r\n=vDPm\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f13f71224b0f007ca66cbcf069bd0fe7fe99a5ca","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [[ -z \"$CI\" && -d \".git\" ]]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.37","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.35","@balena/jellyfish-logger":"0.0.163","@balena/jellyfish-metrics":"0.0.189","@balena/jellyfish-environment":"^2.3.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.4","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.62","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.152-renovate-non-major-f13f71224b0f007ca66cbcf069bd0fe7fe99a5ca_1607464015565_0.5999445339785536","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.152":{"name":"@balena/jellyfish-core","version":"0.1.152","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.152","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3223b1f371e943c271c18a50fd30251d06c28be6","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.152.tgz","fileCount":95,"integrity":"sha512-O7aFTargcImnuKTwXRPisAbm3SCEAwSxs3SPX9CkLUdwpqk5CDoPSODyWUeapHac+HA07PPP3DaCfJc42mjitg==","signatures":[{"sig":"MEQCIEck7E4PqlsW22tUVyWMYHdjD8Hx/jYUd7RYP91EMKk4AiBmCPrANB8DgFGe9oO8MseXRwrPDAXm9hqTeCYm017O8A==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":575337,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfz/VpCRA9TVsSAnZWagAAWSAQAJEn4+Nk7dYPMy2Rx1uO\nR0B0F4tpURdy4QGjFZaeFsXeE1JZShur0e41MIUtYvEYKVN8s8GDtp4jbVOY\n2HV/b899Sub1Z5zoz/nGzTFKhjlPLqgkKx4xaJC3z3f+DzzcbF296joY4L8a\n4dN6qlTWagT6gva2nMI1eUDHAVNR/G9a4Ho8lmuMp6fFdby1i5DH2LGPJCdc\nIzF0JMKET3uTcER0D85o4HFFx14V7T+W93h0l6ISNf0eqnB4N2LJnr+Nmcyq\nbTvTWKyZKhpGozjyfzLHO3w3KdsIbMwxBFb7tvn2J6llJk87ZOgqIBZA0nUo\nuENEpA+2iE9bzD1IbMRFQPh9xcXM3zgcsyCRz6WhTGd/FEgNsEiivD5zqvjI\n90T6UF4bRz98iIDipgdo9GxIwupII94sFusuajtMP5TIha+bGv/ks5llz3hw\nTx2Ah4nad4VZP1l4HN00BnxmCImvu3O+Bw3ZGfmMFEhS3z1i7M0rEeXSmp+n\n+JYGUkJNoYXOpL4L2xqh8dQ/GVgVgV+KHKB1s0cg1iSBq6ibIynYrDvjAfh8\nr9ScEDg4TEuSvjsKCsbfyjVES5JlWysQklsEQaTS4dxs/ni4iC/VUN7ZmBoV\n0RXW8E1znzy8tJnKEseThWtUZqkWp8AVC4jBtVxNvEUrOpTD3drw4fDC6sPa\n0/7m\r\n=AWj2\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"aa988359926bf4451113c615c6d3f5c0e1976e33","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [[ -z \"$CI\" && -d \".git\" ]]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.37","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.35","@balena/jellyfish-logger":"0.0.163","@balena/jellyfish-metrics":"0.0.189","@balena/jellyfish-environment":"^2.3.4"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.4","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.62","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.152_1607464297117_0.22634577386101484","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.153-renovate-non-major-e3da9a60a9a66a3b77df8aef8f1f61854f7abe95":{"name":"@balena/jellyfish-core","version":"0.1.153-renovate-non-major-e3da9a60a9a66a3b77df8aef8f1f61854f7abe95","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.153-renovate-non-major-e3da9a60a9a66a3b77df8aef8f1f61854f7abe95","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"dfb78c530a6c0a4a80f15f7ce8bc2e8a40e5afbf","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.153-renovate-non-major-e3da9a60a9a66a3b77df8aef8f1f61854f7abe95.tgz","fileCount":95,"integrity":"sha512-9CKkIfc59N8aWnw5bo94LCCkuhgV/+VDf9+UM6GpEkOIufRDK8fsOaZaehufUQBd6qyDKz9usMsIlBYHqXIRDw==","signatures":[{"sig":"MEYCIQC0DiU/fnzzSfRvHGso3CcPFRxI9eSMuwrWjQt1v3z1FQIhAPnXDrj9d/PiwvNQmfNKMUnP3KXPnqp9pMSAwtQ6JhbJ","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":575503,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfz/apCRA9TVsSAnZWagAAuFsP/2E5mHUdd1exAcjh4h/q\nYr83OZUo4MHFg+whkuhNN1i/XFGTHhqusdZ8vjVlc2GVl+oLpX4R7uw40JSi\nnqaKUWsM9LplOtXjLSOISeLM6eaJwmNJGJtMIFPd8p3hpDrUMOQ2l7VTU2z7\n3pWSJNXcP1Z1talFPq03XBFOqgf7QW+Xh1/VRTc8sIQ3HDI2uYTU5ft0IxsS\ngtC1x1WlCuWzgCfnxJRlKwtQXDzz4ZatBoUy+UjltsN8OBwADEo1bPG86/D2\nqGs9Suw2IOmx+o7CBCLNKIWhLXcfepoXPLJekgXOqRz732DMZwcMNz5NCoF+\n3ZoGUzQpcRQJyJfRybKfkIoRv34xLKwJf/jUvHU2dAzA7o/35CnRIqy+WwVE\nvmbkz4jEQzxjBksmkU173LVK/FGPnTZI/RNjrEy9SPWKWCPeYIMc0alWxG0c\nnyRRKFAHcxt0Gmu+JUK8In0+hpwHwy6w1E0msdwekdZCwvlAI9k0VK1Slpq1\nyDeRL2sEXWRw0Ut+T2NKtkdo3vgbNDcaudRNqWwqnduNtVgtr9R1WQBFlmdq\naxtWGz5d702qXPS8fxLc/jIXFsrFe7g/HtwebHFChK8GjfjeGiwbgpjjdCGy\nUBrNiHaPDGs19jul6An8Q4sgVvZhT0SxM77I8gI15notqQcd6LLmojGR2dzi\nNWHq\r\n=ONRi\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e3da9a60a9a66a3b77df8aef8f1f61854f7abe95","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [[ -z \"$CI\" && -d \".git\" ]]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.37","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.35","@balena/jellyfish-logger":"0.0.163","@balena/jellyfish-metrics":"0.0.190","@balena/jellyfish-environment":"^2.3.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.4","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.62","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.153-renovate-non-major-e3da9a60a9a66a3b77df8aef8f1f61854f7abe95_1607464616797_0.5142258600369622","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.153":{"name":"@balena/jellyfish-core","version":"0.1.153","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.153","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"05f07c3936d5c64741c678013d7f30626d42b508","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.153.tgz","fileCount":95,"integrity":"sha512-wl+aNtBXbiVST3qrwv3kHlwqqWluw/Xgvomk4xt3vpn27cX+X99WlwHPomvzu01MiUOgKJBxJ4Hw+gzZ7jYdDQ==","signatures":[{"sig":"MEUCIQDncM0cPjJ7ACYsxIyQ0XiWLQjjl2tH4Z7jnh064JhqTQIgSaC/nJWqYlxC/VGEk49FU6zbvz4p3J6FidAG1ds4AOQ=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":575443,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfz/f9CRA9TVsSAnZWagAAuFMP/19U3PwF7TB4viKcvgWz\nCkjYIqrijUgocjCeLwZsEIYFBNmfJve2aL0II3qTIJJwnSN58CIFH9g+jraz\nBQbDLe0AJ6dsYztMWH2otOZEWpXDrb0eWUUsWus1JehWgTvah7vyDFlA8ClB\nm9CPs0TcA1R8CDpzuev323sctAxtMKk6xCNyhFQe7FmipQXQjDYPFka+/zRE\nmQbbe5WxTNS3CtbkeyHHlfer3HG16u4ELwXuvCzYvUKsDE0yGJR4G4uOQfjz\nbCzF2m/5UuE+VT4iEJIt4eRHI9MNu6He/GMAbEs5eh8IATKqQCqZblQE8TB8\ns7yJChQ6RucxRxYoQGkbKAf9WYvorH+vPmdIeT6Eq74sm4QGbLA1eP0lT0N7\n81YE2jYdf/Rw9Qs15jxfWBqcoRTKP9MxK/h238IBG8ie6JxIenGxGBpd3uK7\nA/voG9RQ8yUCX8DfWCOjOwzCzBcAPvmQETUm1MDfFZZHUFvhDjAn88Aw4w1B\nsAydLgl/XSIxmaEktbFiYv33xAaVfmwHOrba7LTw0iWGkpvfL9dSBrXNCtnj\n7KmOIloGobJcQiCF9Rcpm0qCFJvEJ83JJYotHtigCLy9RVBwajbF9Ez3LTEo\nNPhK5GK22roljHa/Z7nNoysG76hbMkBVrbpPNXigLmCaaPp81l+u4QbtcXZ3\n4es5\r\n=w/3t\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"712ecd5a24e26e9e4ebd54167268cd30dae0fb2e","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [[ -z \"$CI\" && -d \".git\" ]]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.37","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.35","@balena/jellyfish-logger":"0.0.163","@balena/jellyfish-metrics":"0.0.190","@balena/jellyfish-environment":"^2.3.4"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.4","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.62","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.153_1607464956708_0.0837173190096645","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.154-eslintrc-root-true-6990f0dca048a9f7195feb9d7d52d50be5b376bf":{"name":"@balena/jellyfish-core","version":"0.1.154-eslintrc-root-true-6990f0dca048a9f7195feb9d7d52d50be5b376bf","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.154-eslintrc-root-true-6990f0dca048a9f7195feb9d7d52d50be5b376bf","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ef181298696349a13b077fe355e7d18aca51e445","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.154-eslintrc-root-true-6990f0dca048a9f7195feb9d7d52d50be5b376bf.tgz","fileCount":95,"integrity":"sha512-sHQIb98eF18fN6pnJgSPnm1E76hTVBNcys541UMXlecJDdoEFSSTyVHvhhuUBk77OHGo6/oo7tU4awKvfGgvtA==","signatures":[{"sig":"MEYCIQCB2TjZl/fH2g5Toe0zf3SsZ2ilVoi0MXFuRQ9DnhjbDQIhAOk9/SxuejTtdPQ8LHcAimoH+ue93uPCvoXCYKhIOCsd","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":575595,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf0b3rCRA9TVsSAnZWagAAxsIP+wYlormGY+e1ikxo8Wso\nlfKm6zFj9qKK/bmiyw9OsTlQWCINZKYpinNcxY3DeYG1Q2ycxCtbaI7rt2HI\n5UFPteqOVaWKDDixJxblSVXWV/bk8gGMEFgJVq2zcvR2/MLEYI/TqNGsVsvj\n1j4MidSSfa/kJ2sPEfWc9OUl82q+HWD3GG2WppuZUpFR9tHPMS8n1VX593hn\nW++oSufzY0RiNFi5Ep9tezWa12JorXPAmI/t2XQUlT2tENYHsBEDdz3QBiQr\nCZQpXPtiSF+vYREJtyA4+syefUaAbP+oczlKcziI96qfiyeKMkTZ0tjHz1RP\na4I/NlA5c9A+uFb84fAWEhmjbWd5Jyz3xwoQtud73rIPXuMfsh/UFaPoj7Vd\n690uJu5RcYFWkI0nCDQZN7IhjFWsSchlupjo6b/s1Z9lCbdSmDljyz7+McDA\novzB3IMpiIdFpMxDWaHHfrluKSIt3v8XUODgx68oDcViiDMsaiOIU3/EdEpF\n+C5rjE6IFxQhBWis4cD7HBtmyMPccrMwr7a0Njs1B7WHQTaejtPiXFVRgCcL\ntFlSqyPDfmJuA+5kwGEc31yjZVEsqHNCweRodE1R4MfaFrkWUbo165OcGnDW\nUvvu767ltj0i4YSTbRoTXhzsrVNryry2MuTOICpD3Mi5ad26KB0dV5+0BKuL\nLgYf\r\n=8ora\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6990f0dca048a9f7195feb9d7d52d50be5b376bf","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [[ -z \"$CI\" && -d \".git\" ]]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.37","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.35","@balena/jellyfish-logger":"0.0.163","@balena/jellyfish-metrics":"0.0.190","@balena/jellyfish-environment":"^2.3.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.4","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.62","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.154-eslintrc-root-true-6990f0dca048a9f7195feb9d7d52d50be5b376bf_1607581163400_0.8995102818446594","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.154":{"name":"@balena/jellyfish-core","version":"0.1.154","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.154","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3516d6d793951e5a5ec1243a83b1e17aef12fda0","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.154.tgz","fileCount":95,"integrity":"sha512-m9+USUqaPWcNeMC/190dcc+Wv/+3cwW62xaJL8Xeh7xoCjMQXD6m1Z2Hs9SAg2P1a4lq+PJ5mMy5MEY3M9FqrQ==","signatures":[{"sig":"MEUCIQD3jrShAzmD1Jfx76E6uFqdbFYfF6RZAF8XY+jKZVXMXwIgEbdiE5awaDtv/mkayOTAuj+ZVDuNtIpp4WX8adU6iig=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":575535,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf0b8TCRA9TVsSAnZWagAARMwP/iIvT4Q7JtT+Tx2n/AjM\n+k+vHOcpbNRJma66VUPasAKn4AO838YnDGjJHiFQUpoT2Bk+eNP5R0VyFwcv\nFl2GI7P1cWI83p2Zx13ODpKZwaeWmqPex55nwvzU8BX323YccpAEEW/O1rKM\noejx+PPcVKmu4zg9QKIRK1q7GINF+ol1O2GoHbgj3BJPVm59tPRN0Z6hk8zt\nDa/dipKxgJDblZUe8W0XJryRE8zdNwopYjKlMFPms8Uwnsz931Uy9WTFamom\nT1Pu6Rxrege/mX4UJINy+rcpOcJW5iFYczHKIqsxohNwx/UB38WSFrC/pbMq\n7jqfRBBhfVmETJR5sEqfAoM4+7ywgg2v+KShMjKNW4y0/ZWiEcCFlDtC1AOX\nHf6E15R+ONCvCxi8mqebslRH6/BYEGFR4RqSe1FnshTgw+Pn8LOWkUJAt86I\nFeztdYXSJDT8ZekhIg7KTGvtdP4HSOUESXVpLSojd+tky2OSF7ynJJOTKhfS\nHbth8TxNPkV6gYh3+reg7u1VyvF5vrCxzproYpTtq1aTWifo+ijy+GCUZFQd\navmOEb4x+FvW9W3w5bUPCXJa7BwxFmkC5X6RQ1JsMGsNZhH771JvqmUn7arz\nm8BePIgugUHuSkvliAEMwsns+l+Px8oiWcgl16gfoSApMbJmUuWW7UbFY2El\nRhQT\r\n=YCPM\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"180b7ac5330a3de99363cc23760ac35dd7e38ef3","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [[ -z \"$CI\" && -d \".git\" ]]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.37","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.35","@balena/jellyfish-logger":"0.0.163","@balena/jellyfish-metrics":"0.0.190","@balena/jellyfish-environment":"^2.3.4"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.4","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.62","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.154_1607581458860_0.033860889617433054","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.155-renovate-non-major-c4770195ab0b7e09c314165f7dad7fdef41c98bb":{"name":"@balena/jellyfish-core","version":"0.1.155-renovate-non-major-c4770195ab0b7e09c314165f7dad7fdef41c98bb","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.155-renovate-non-major-c4770195ab0b7e09c314165f7dad7fdef41c98bb","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"eb05a09201d90d4c1951e50621cefe37213132dc","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.155-renovate-non-major-c4770195ab0b7e09c314165f7dad7fdef41c98bb.tgz","fileCount":95,"integrity":"sha512-omgmoLXae12gj9+umsciZ+WX7M0IrakdxeAa6+UPSWi27WvWT2pC6u5rA8ZA91IR4+Xo3XlgfcKvZX5HHwJPCg==","signatures":[{"sig":"MEQCIEfTh6RoJxD6MzFCLxWF3BXL/Fk9HkbMyvd4s+N2ZTk4AiAMRNeIx8spYBamslWuTdqCjYSVUL+TfJay2aKUlk6Mpw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":575662,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf0cBrCRA9TVsSAnZWagAAkMAQAIjPa5phapQoX780SACw\na/zDeUJRkmskNl9ny+8Jf1wozRtS8/aXMkwoqmm9Ti6L0eM8xgwv6uc4lWXp\nYo8VWmW97f/TFBs+m86LsV82PFnZqRjPkhfQKfI+8ZWKRCAgnJbzmYyaPdYF\na+ZYZPee4MopE2Tvo3AIf1V5h3J3qPuE8YQ/JQ+K2j2i3STPITNbuVrgqf+L\nWad+O7G5QTyeKKO1MMsfpkj3xKBaOmTE5UCg1O9QL+XUpjN4Yqq4hV4EjVQM\n2NoFVzAYPZQhExj4NaXC1HTK3fjqLhwqnOOTcEcQkPJ34XYnc4e74HAAPI3z\nqZ8yt3Ej8OZLmzxd5BxQnw0VOpXeVr9mbAsiStYNt8KFdxQJe7rWoOtAL0Qe\nPTZcZGfmjLrWSVyVs8ipOpAQbEHovZncJPCb2TiZ5kLhgnCP+96bSx8w703h\nwGv/6NEvARuYyrhWrdbsV5+gkEqGreCfBHz6MJDdXnBzd7veExg1wcPTgwfZ\n6z5ln9fmSpimdNDj71pHKY6edT5xD3pklKnvchodwwqewtWMUOzndnxiZ0qa\ng641UdNOVlswqCAHUexxXLjr0V7XcJzwHJwYiNeWIkdA3EdxOQKAskN7F+PC\nCS48oWm5ZizkgMcuKkkKm4I9CPlJDyrkiqP2asus74AwhupXwIF/KRcZaPUf\nMwyJ\r\n=zdEh\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c4770195ab0b7e09c314165f7dad7fdef41c98bb","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [[ -z \"$CI\" && -d \".git\" ]]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.37","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.35","@balena/jellyfish-logger":"0.0.164","@balena/jellyfish-metrics":"0.0.191","@balena/jellyfish-environment":"^2.3.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.4","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.62","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.155-renovate-non-major-c4770195ab0b7e09c314165f7dad7fdef41c98bb_1607581803058_0.5675055881887905","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.155":{"name":"@balena/jellyfish-core","version":"0.1.155","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.155","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b027374cae1d9624619f84bf1dad08e350031d7c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.155.tgz","fileCount":95,"integrity":"sha512-bOajaNWp6t8LriPELlwdTeffCAwvf/5ZD0QD41bSqTfrgYTkglpQly5VqyRhRVlGQQZonqKriw0+Zf0LyFYAGQ==","signatures":[{"sig":"MEYCIQDvfl0GzX78kyoWY2WPy8qMVibVd1yBRgFU/EoUVIbvhwIhAIVTqEG83S/bLio714HCVECHPNQ6v4oBbPDwPCvMZaVf","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":575602,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf0cH6CRA9TVsSAnZWagAAKqoP/3F+gCWyu8NXsdcQtlFP\nV6uC6Aczd83EnkslyfmXRR84gYCrf46QcKOXt28eayNkAunNeCcpz435MKbR\neS2+nsyz+goZN9ijTREg/BYL47YpEwIgsiyxok/I9I9up0DMS5tCiSG9RuoG\nuTdc53n/SQIZGPWBYTlmRYGe1rhNsOaAQjxsej5YHqc5xwxBQdet9GmKwV+1\n0PWrIv9m6/FTNThrIHg+oT/TUFiU3p1t6l0FTfLV5iYlvHN+7I5DAo/3+dr3\nSTWpPZ/7MdffHBKiVzYp60YAGFcM7A8q4NbC5lhjs49IRgUsGRW8JJmf9taG\n8GHnronwXabZxAWcdIc9PoKxEnVSBw35/niMhTAeJFcN5JKGn++05sL/kpgk\n59JB29kW8UAqis/g4mYPmNrrkC1IOelpMiI99mIP3k0/kQAMqTnJ7YHKcRXb\nAKwwof0CxW96VQ8rxgoQBxgU/Zyc89Y42V3otlCQFosLhDLUvOAMWGmeivY/\n4COVa8sK75u9sG/AILTgUcH+e67FK0GBR9o9GJFZg8rqwaSzmXG5MRny6HVB\nNHhY3BRGGbmgFh9KF3uqHuAFAikWdtGtGZFR4wTO6WlTPwY8/BG2akFqAJJs\naqHQOWD4bB2F0dLGi2Bar2r4FUMG4Xr7rjWWavtouIDbxvEJ4/q4mbLe3Llv\ng35y\r\n=Us7/\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7cbb0dfac13de2db59be015245914343856709de","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [[ -z \"$CI\" && -d \".git\" ]]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.37","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.35","@balena/jellyfish-logger":"0.0.164","@balena/jellyfish-metrics":"0.0.191","@balena/jellyfish-environment":"^2.3.5"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.4","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.62","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.155_1607582201535_0.5489587419466027","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.156-renovate-non-major-411023935429c92da8049608844bc6b041b65970":{"name":"@balena/jellyfish-core","version":"0.1.156-renovate-non-major-411023935429c92da8049608844bc6b041b65970","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.156-renovate-non-major-411023935429c92da8049608844bc6b041b65970","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5043f68e1b875e68e995701ea13d76164328af3a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.156-renovate-non-major-411023935429c92da8049608844bc6b041b65970.tgz","fileCount":95,"integrity":"sha512-46r9E8CtEGGdOF5Jhub4RJDKn305nTFoPmOoDRWrhdyyf5BCu3V9penm4jEB2Wmdq1sYvdJu3awMXMNNjQ1VFw==","signatures":[{"sig":"MEUCIG6uzchWMI23o4glGpH8b7JCzqS49TFY96HQTbbzx/J5AiEAmvmeo6f4ZSyrD8Lc9d7kCUQ5oR0O/Ey5W52VSRlU230=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":575767,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf0dOSCRA9TVsSAnZWagAAPhUP/1JkODXHjUYTR/mZZSgw\n11fsJUM0L9iTmCsWXdWBtPYdngWq/MRak5qes6vNXXi6DzvC7Fbgva/VJf5u\n/Ae1uFcy0iiHksH/26szbHvodXE4ErfwUCjJAAE1GDKB+QI/91hwSXxZUsqm\nP5hX9o4qHF3u4wn9y9sSdjn19parENbdm6PJV5VfrQa3MHHXlQ6C6n4wVHdo\nXGizctUuj8t/vAWos08cLbXPqt5HpIXPyvmXnpSu9qFzYssSo+W/+qbVo7Rs\n95ThghnHA1d2KDZ05931GwO2PEHnYrz9KYWJcDXKEETjRaBNOi9rIJmdvhQF\nYo/oHCY8ny4SXJcj8C+aVnV4XmhguY/8Ki6Sv44JznYlW2cfQSMQfV3Ha17j\nvH+oErsOsfpfwmUsgfYhaq3USPQPdw2jn7rmaoiHoZ5UakhwuED0ZCM05Z9O\nTETFVJnLwT11CAvEyo/+e5ouLDqdKdFiIzoblaqwy1+ZSKcwaWKjQrIR4J0E\nku9Ua22bFthO4cUxJOkhjN9lV1zWpXTPmVj1iSU2StHe6fJAbjD4p82b56Jv\nJ3PgNgmHcfastsQ96/FIL6mwALJgW8AfEQx8RtiUblGhhPPi0UhDGjNwxLx5\n7DS4F+U7HgspQSger2ll9e72nzTqqfuwx1cVIsKWP4YqdET76gAlxc/B6tRw\nAgK+\r\n=vKgM\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"411023935429c92da8049608844bc6b041b65970","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [[ -z \"$CI\" && -d \".git\" ]]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.37","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.35","@balena/jellyfish-logger":"0.0.165","@balena/jellyfish-metrics":"0.0.191","@balena/jellyfish-environment":"^2.3.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.4","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.62","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.156-renovate-non-major-411023935429c92da8049608844bc6b041b65970_1607586706016_0.8009355274027812","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.156-fix-sql-alias-84ff5ed25f6b40a2d293faf42cfe4e493beb927a":{"name":"@balena/jellyfish-core","version":"0.1.156-fix-sql-alias-84ff5ed25f6b40a2d293faf42cfe4e493beb927a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.156-fix-sql-alias-84ff5ed25f6b40a2d293faf42cfe4e493beb927a","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"44686ab2aeeff346116507518d277f604b155db1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.156-fix-sql-alias-84ff5ed25f6b40a2d293faf42cfe4e493beb927a.tgz","fileCount":95,"integrity":"sha512-I5A7LITlH2htTZfA+0+gMJi1U3vWpyCO45d89eWxbd0hMDWzhrr2hGSG/2fKrKSPGPwuiIwROjbEpD8T9uRxhg==","signatures":[{"sig":"MEUCIH77KSesB4WB8p+56zmqO702kJx7x/fIUHx87xCSXMYzAiEA2/Ev2KmbGJIjAF3xgNC/xLlAOsblGVw8hpATx5lxDag=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":576372,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf0gHDCRA9TVsSAnZWagAAg00QAIvCqWCeQaXdTa5l3v0p\nCuhMARYgQZkdxDKeODsuFZpC3MRuAb/zHVf6qKWIhfgzN33ZfhiCWJk9ePnV\n3gEweWxQZ6YhwypWXyxSBooyRxsPLRwRXOEOJ6Vfu/YlQ/woRGfA2qIREDz1\nnRG3Gk2Hqbmdlm5j/bW9rexeYu+Qd/BMOjP9D4ymzTwvLT35DES2ia7267nz\n8ShZuuQ9H7QT4Fz95eA7hGQ7KtQlSeVqRl2SZ4t+OuNbJ5Y8AcM0a4K6JTpQ\nsYAsjO1U8aLv5l6K/xjd3TWY5JcuKAf7AYX3xlrBVGOjsB1nRLOZ4Ch7XTrm\nY39xOloHdJwtqUw8rPN32X5E1nj3/Ei5kohWHi3r/tz4NqiHUFnSvB0zZsXI\nqOsYqza24tfdfGmRdFDzfXIMeNmMi21f0ZA8RHkkKx92sZmIT+gmiZcgpiTw\nII14+IQz/9mRt+Fs/Iu7z976VB5+ExRRFx6fZUb3/WmJ4uX1bhWgnL6epCm9\n9VIgMGf5prJgoNCEArSYCietKii5N8LATJZOYUUddLY5yZlt3HXIsFCtxMDG\n9CVeQ4la02sw1qG0s1p1wX6K9g9cIGlw/+eDZySF6sSRsE5nXrwbeEMO1kas\nYnRJ1nnMXbe28pIhQrbe/QeVBnRygH9lu9itBf1B2I4+wzH8xxmsrnJyFeNi\nD+h8\r\n=K5gR\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"84ff5ed25f6b40a2d293faf42cfe4e493beb927a","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [[ -z \"$CI\" && -d \".git\" ]]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.37","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.35","@balena/jellyfish-logger":"0.0.165","@balena/jellyfish-metrics":"0.0.191","@balena/jellyfish-environment":"^2.3.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.4","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.62","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.156-fix-sql-alias-84ff5ed25f6b40a2d293faf42cfe4e493beb927a_1607598531426_0.8694502203702921","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.156":{"name":"@balena/jellyfish-core","version":"0.1.156","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.156","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e09ba7f870f0009d9fff11e6e4203c42fb0b54e2","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.156.tgz","fileCount":95,"integrity":"sha512-FSGUCfLpZyQQcpsd6ifqXd9t6ULp9EsAl/TemVebkCNHNzPVtpPMuW/gT8oUAOE0Ipum5MM2VkFD23Knl3Hs+Q==","signatures":[{"sig":"MEYCIQChAUcF08iYomwscMVRTW9ban/VNKWcwr6gv4nXKD3NHgIhAIwOtV/2FINkuFNuDXeqBzpC2ITSESJPjZYt3/yVirTl","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":576317,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf0jAtCRA9TVsSAnZWagAAAr8QAKJTP5iy18pDVRm2+qHZ\ngzUXx7zhG5kxtkSA9qYIsQUEiLA9PBjw2PvOXqRO66tb2nsPAqeaREOEc+rW\nWq/Q4EdHLLeFee+ha+fGeG2LeNvFz/nnWay95MurMgMWG8mB3gfL/+HW2qbw\nbNL0XnuLpGOH4PvUWo8k++N2OjUnydfsSRdcq+pk+8BaU052fkeMuy6lPqVF\n1x2ND7m2SbLIMhgiVDd6w/mauQ2d4DI3CAdcd6OIe+lZCdK+VhnY1kFIDtr7\nE9Y5gIGMj/4ja/CDfGw0EIoRvMkuDUGFCZXGPK+faijZ+GSUM5/YqOLyy7gH\nsBOIAFDD6BxsOyM03RXHNEN8HSRwlXdjCxGPEIPS/KZtL9StvQg942ohd/vY\nMMlmv5zyHLDj4M+7QUha27MtwW4CJUGockA9nVEbZ6f5qA4LTyJ3yjU5wSQN\nMmCbmjNlW5ENh/G3EiDomelxEt3vUPxqiIcH1IcrxWN98mbW4m6yrt0JD0cz\nAYgdMYD/nzEp/ki9MZUV0ZFytz+Ohp6kfUQ/poPkgDkIGbavnjyu82EZc3/z\nTSVBjf4X2QsMJNaJlGBoxm2TemWXDJVcvRlaEWJqg+jEKTmHUJfnf2ht0eUE\ngZ2TYTD/G/6jFiysa6pwIJ/Hxmbk4lx0COy2XvEGR+eIh8wrsigdwQmP33ue\ngJij\r\n=NvXG\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2821573eb0b9b1d7df729fc1ccac3ccd78ac42af","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [[ -z \"$CI\" && -d \".git\" ]]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.37","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.35","@balena/jellyfish-logger":"0.0.165","@balena/jellyfish-metrics":"0.0.191","@balena/jellyfish-environment":"^2.3.5"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.4","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.62","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.156_1607610413222_0.6514833074744786","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.157-renovate-non-major-7e8d66bb9d256279df1866d349de4d1ed37d0dbf":{"name":"@balena/jellyfish-core","version":"0.1.157-renovate-non-major-7e8d66bb9d256279df1866d349de4d1ed37d0dbf","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.157-renovate-non-major-7e8d66bb9d256279df1866d349de4d1ed37d0dbf","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"79af36c53bd1fe3bf8a6682d7932d59a40f7c7a1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.157-renovate-non-major-7e8d66bb9d256279df1866d349de4d1ed37d0dbf.tgz","fileCount":95,"integrity":"sha512-KVsoN6e5R3IiIcFAbVkvrbvoy29+/MNv282caanKdir1ZcUdfIpRseM0Ht8rS+jbebWE3RKQUAS2QGlPV/OueQ==","signatures":[{"sig":"MEUCIQCuNMPPCXPo1P5DDmA8dLbWxab7ETVgJld9Ym+wHO/jpwIgC8COLsSEb1KthRaaVqYUHylN82WsLEEaC7wcNO9M49g=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":576483,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf0jFlCRA9TVsSAnZWagAAGjQQAIT+RmFh5gUMCC7GnJ7+\nojDMcJMUrvibcgRt7xhYGgLEN8B3si8gnCTwm4CtPoGbRua10BgFRe/nkvr2\nIz4/xTXlWm45trntZTKPiODoU5qXS6+g9XqNbb7rdWXJ7zq5Rx4AfCxdSuta\nq/ija+SBrV2mdC/UuORcnLzutZh+1yJr7y9PHJ5TYUO+ADNmBYZZhOKHDfa7\nj7lx0Q60lNLlAxBXGmJDY3Jd7BGgf60Rj/aJMfn5e81XFEHuz46Y+XRCNkRc\nsvwZ81/lDYncQpAyQnKvRdi1SiKak8jmQAeple0PpqLOLRdmjvNY+wQ7QnWI\nxrYnpaFs/STzw1dreGK71H8XOb99DzUXD9SgEPogY5O1O5XkqYgy9RYM7Tv9\nUC6H0hWmo65IOurCzIKid+zQ8RCCI+b7LfvI4uss5VKNxZJp+Ucs0TJz5ZsG\nhd3lJw9Nb9YgeIN5hRKQ9brB0xHHIm673X4YCUS/pFklu9MZ1eJGu2S3lgBZ\nFKOOyeEBAVFbk8kJzIURrt8bQkedE8gyE7YZN1e+P8xHBcIDQ5jTbc+u0HkR\nanNo4gj629BnY66BfuU3tWfU/9Py4XkNAjXj5jXmWfl/OtsFvIomoK/aX/LU\n8UTKwG2aVorFQH+L/RTb7GAii6Zi0TpRd7y6IgzamqjXW8Sdlv6qRyudZ4wq\nEQYq\r\n=GJBY\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7e8d66bb9d256279df1866d349de4d1ed37d0dbf","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [[ -z \"$CI\" && -d \".git\" ]]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.37","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.35","@balena/jellyfish-logger":"0.0.165","@balena/jellyfish-metrics":"0.0.192","@balena/jellyfish-environment":"^2.3.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.4","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.62","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.157-renovate-non-major-7e8d66bb9d256279df1866d349de4d1ed37d0dbf_1607610725401_0.774570771076021","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.157-remove-option-type-df0aa15ff35e0cb6a0bc0fd5ce30095a08a01d45":{"name":"@balena/jellyfish-core","version":"0.1.157-remove-option-type-df0aa15ff35e0cb6a0bc0fd5ce30095a08a01d45","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.157-remove-option-type-df0aa15ff35e0cb6a0bc0fd5ce30095a08a01d45","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"cd95cef307bde28a0e0efd022fc94365fdd0e729","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.157-remove-option-type-df0aa15ff35e0cb6a0bc0fd5ce30095a08a01d45.tgz","fileCount":95,"integrity":"sha512-HgTKfUqU7YDSM9Zcrw7qkgHr9LmCnRBq7YHVPOd/Z0wsjekLIa89tXZT4Q1XGV/0mSX4y10YpCcZf/TZJhfoLQ==","signatures":[{"sig":"MEYCIQCAgMc3PgN0VgrWqHAm1vaGln3+oJsaVASEBv4+9jbLnQIhALr9OqiUZNp6N7L+xGUKBUQnzelGGLBKW/AUDaSDpkhX","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":574760,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf0lECCRA9TVsSAnZWagAAt/AQAJilV6/z8Atbo9mr8ZC/\nXvoE0COGoSjM6tMQAXh6u09tckFWKZmWYry0jlglm9YFPo3YXZfLDc1Dx52A\nQgau9Ba8WIPRBbzSj6us7HdwCOZZ7Pb22g8KHzBPxUMLbGr1cTV4mj2Vsw/Q\nhpTd05EUrTaVu0lb1ZZCK0VxhuyHcSxz2ZC3qB115YklwlhHrUcYQiy4Xxo7\nHtFc/pMdB6Bhl3EkV000Tl7w2CeRiH6s1LlIKd5w3Qkc8yzhdvmRx41PNL9z\nCtCZUUJNfmfhRVAVcLctQVqzgGU9g285G7Wq8aU4eURfZyUwUsEkM5Og/zmQ\nWH6+Uqs8t4bQXJXUGlViSlZYYj25CsK/+uorbVGhWI7ao6Roeau8fk2oHAqf\ntpUDU1lgwPzcyJdFwhEh4AcGlZ+mEUv5GToZUpBPQgmVSRrkkr6yqqYiQDek\nfdnFjiMlwEo8Zr2JRCt9XF8hEOf9GRtPey6oNncGVCbbhrtysR+S34vgjtS1\nKHLWLjdix9tbghINmhZt34aczvTDuG9KELe9ARcm1lL/simUG17Kmk1PFS8f\npmiHfjpsRp+bcA/Mmx+Byr9u41ZNd8jS93vylCvT2a5wl6KiKlW8Ai8S3XJo\nxVOiPnps5Wry8jw4DpMU73BmUHAQ3T/vmlljqTUDERC4S4eA8Pzw4RfgELeT\nZTSq\r\n=KN6g\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"df0aa15ff35e0cb6a0bc0fd5ce30095a08a01d45","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [[ -z \"$CI\" && -d \".git\" ]]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.37","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.35","@balena/jellyfish-logger":"0.0.165","@balena/jellyfish-metrics":"0.0.192","@balena/jellyfish-environment":"^2.3.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.4","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.62","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.157-remove-option-type-df0aa15ff35e0cb6a0bc0fd5ce30095a08a01d45_1607618818224_0.5440882714050861","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.157":{"name":"@balena/jellyfish-core","version":"0.1.157","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.157","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"956e1c41ecbeb07c70836799d56cac54c481f673","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.157.tgz","fileCount":95,"integrity":"sha512-oG4NfBBuwwhMEEaR/via1/qyfssf10MB0EXhJ89XH1qlKMuqVx/wnnS3Zzftb44jG5QD7XJD2Pgts5UU6tfj2Q==","signatures":[{"sig":"MEUCIQDW5yTbPQej9+70iphXBPlHwSx30RSC8Hc4fcsz7O+obAIgT+XKQ7kgpv13iRuRGeJQK6dKYp4uRXSl4nQAT5dv+G4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":574700,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf0lLMCRA9TVsSAnZWagAAgrAP/3cc1tT2yXELA3hJJc/9\n1i0HU6ENZ/lhJDlHqsWdjWZy65WHpakcKyv9GNAnvO8s5e7vEPvAWa+VUkl6\nCtikQclKfOmJRwDOlcyvKQ8PERA2wnQ0V76zqpkxwa4+JLJTqrABreJTy0AD\ny6/gMh7n3sqobHgPdV2Y+EwsqkWpFRt/qzEAeTfXm4asH10bDOWqT2XZSXA8\nO/EUC8ZunZPw/1K2WE3O/yfYloBcCehI75tufGfNHkS65z1sRXxmAAp0U2bR\nznNutPcitAcYf39S24cN/6Sm3Y3N6Q94mL5w/kIEgcAPUgk4IFFVSUmzbGR+\nfegDcHmswNeyO29kn31aEcA3bCzV0tTQA9wXPaLIsYNLqjqKCKJXT6rQC6h3\nRWQLRtk8EJIiFJlV1A49ywKL9x6ki5y+erwN9QbbDQE5Wm/zt63T8Ql8rXuz\nnvvzjvPqBv1YyBZPZPfJsypYxgC5JEwvMlnybk9jkBWuXffdj4eCTUtmiEKo\nfilDkpt2HqFMWDnJNmJ6nlJA+Ol0QLQQoXzNBDk4y6PR9E0smcpg/qpWva+O\nAOl76v1oDmTeQrruSnESH/5RU+iLzqQeto+PKiFS7RrI6F6JN5z/dJ8W7w5Q\nUxOb6AWTk5aSksJa8fvUl41Piv2N0U7itMNU4MI7zGNXz1A9eI2IOAKHcGKN\nexry\r\n=bJI4\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2ed6c0d8898abbeea167bc0dedf112b1e6660141","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [[ -z \"$CI\" && -d \".git\" ]]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.37","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.35","@balena/jellyfish-logger":"0.0.165","@balena/jellyfish-metrics":"0.0.192","@balena/jellyfish-environment":"^2.3.5"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.4","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.62","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.157_1607619276254_0.443556430881886","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.157-actors-ca37136ea01f294eb7bc98718affe620d3b0c46d":{"name":"@balena/jellyfish-core","version":"0.1.157-actors-ca37136ea01f294eb7bc98718affe620d3b0c46d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.157-actors-ca37136ea01f294eb7bc98718affe620d3b0c46d","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b61e4b3471c3d713956ad96bd297d9d2f3fbdbb8","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.157-actors-ca37136ea01f294eb7bc98718affe620d3b0c46d.tgz","fileCount":95,"integrity":"sha512-v1vzE79CZ5pN7itwmx0TdGgtm1+S+zKVVHsqU15OuZfs2iCx8tpzxUrN/e2SrKb37zlUKxcLaWXEUwEFT5Un0Q==","signatures":[{"sig":"MEQCICgfqr/RD4TRXhr0MyXIHzwL6TCu8Cxuu56fOOOvrqf2AiBpFZPIhQlP2QfBOmkqaZcuyf3x6KZvOW6nNjtQ8gQMNg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":575037,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf0lPJCRA9TVsSAnZWagAAEywP/iqr5pi0z+xOdWsYmTQ4\nqLMAHpzACA5jhUZRl/7pCT+mTaMVuAJ8dMf2ds5Zt8je47HdnnUo60v04P3W\n02SALWeHYugccXp0A+0LNA6ObABCaQsHC4yG/mwtX2WwZftSvCg0a9wpak2E\nM4ymtv8JGdEmceUIhCE/nE9ToEU40gYe6ZxXtnmAnzoapC3K5nI6m4droepj\nousG4E31fgoGNkuVY76HG8VL/++i3lUDXvXrOlwZw66jkSepSivDDovta0pg\nT1fHkMocNIPIxN8rp4QbSdBy+oHtVxYKe5p20hadsnq5xLnGZHI/RpZB9RzK\nCROfK7WHFEMPyNJAn++bgobehGRzaNCbNe13T+osgJtPrKcHygxsPQfpgfd5\n4DtGZlQMjcFvMIykQmy5NHLYoRWKXU8PlYt62Amb4FPxyPtjvP+m0mt9vIY2\nBI3hzagY3VEftZDJhmNBJ9cXRVZ+bjQ0JO0JIAd5qhORnBbQ6czabl7bEWQu\npwP6U6BGrSpKcqJ4ha9NIzr00WBMcYuOCfT08ADXDeV7+4Nk+LX6pjqqaURF\npc/z49paHfMAiyb4gGiRsxtFB+jRQLFiUwNKWVFysXhB8PWgRrKDxzwQhcAR\njPOCcEfYxelHq0foelbt0mMVX6ueEg0UDLDuKjfzKWZIxJM37yruWxg7+McU\nyuQI\r\n=gu7w\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ca37136ea01f294eb7bc98718affe620d3b0c46d","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [[ -z \"$CI\" && -d \".git\" ]]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.37","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.35","@balena/jellyfish-logger":"0.0.165","@balena/jellyfish-metrics":"0.0.192","@balena/jellyfish-environment":"^2.3.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.4","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.62","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.157-actors-ca37136ea01f294eb7bc98718affe620d3b0c46d_1607619528504_0.7583444048736017","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.158-actors-81bf99ffe1fd5e8686936dd5f745019a2eb3aea3":{"name":"@balena/jellyfish-core","version":"0.1.158-actors-81bf99ffe1fd5e8686936dd5f745019a2eb3aea3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.158-actors-81bf99ffe1fd5e8686936dd5f745019a2eb3aea3","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"01f31b94deb3deb58e1e9ee454d1bc6425c69c66","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.158-actors-81bf99ffe1fd5e8686936dd5f745019a2eb3aea3.tgz","fileCount":95,"integrity":"sha512-qOzczRRLSfRtR7LWIk+JisMI5032GGNbpfy+Thv/kZf9wgot22xqjiAvtkFHD/MLCYdxdOkxlM/zz78daa6haA==","signatures":[{"sig":"MEUCIQDXtIRu4QOwlp0xa3eE8Slsr94Mm7oewh1TYoPbTsew4gIgYEQogK/WHox6A/iop4+MGA1HF7xnFmRIW/S4LnTAfQM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":575065,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf0mSUCRA9TVsSAnZWagAAhpYP/0LbMfEVkEg5Jpa9ttW0\n45uavDrgHlwCnuwsXihCP1PYMK1KmYT5yo5vrNuxiH2tsmFFNrn23oAuTxdb\nKubNKq63eSliM2Qwn3hT+jahlZ4RaGR+bZPdZ8VXOcRbgwiHWtMoDuma2nJy\npa9pNFtDwYFMGVHYaIPzdG+O0SpugVO0icd9aA0uU05eCQzbj308KFPcmFuo\nc4HW9zi6e4chQxdNClWNo7Ifow5c6ZMWcblwvuo2Tr9mmxBWZHVbiomjNuhB\nw1QNPVBWdGgqeccWfQW1oGY0DtqgRhsDYZJMEj6lgnK+gHxO3hvdlRFxAA2V\neAaa4oL9pgj6xV4kWgh/03OAoA3Jh6M5BeaNB+2X/l4HTjRIj2zNqIhnhI0C\nDRb7PiAeJCE/IgGpMteNK7DTKvweYKEYQuLiv259WUW5HQ3Pmh5HLINz7QUK\nsICVRB1pheKe4Na1K2bbnCFT8q3wcnIcx7WAUwKN91nq1I/w7EnHoz8v23Dl\npMKC2hXAYdMwX7Nl5Monh2MUVJSofUK1KYYataThTfQ98t1+uXXRPylOJF7d\nV6mzbL5CuLYVVmfH0AHx1G+sGuLgJhMZ06nvQd/+bpEl4jmMEkqAdYjBjx9Q\nJ2AOV3A6RBealHMqSC6obSk3K4oR8qGO4CefK6b6a+6EvVQifNUOAVzhvR7j\n04ia\r\n=3xEl\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"81bf99ffe1fd5e8686936dd5f745019a2eb3aea3","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [[ -z \"$CI\" && -d \".git\" ]]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.37","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.35","@balena/jellyfish-logger":"0.0.165","@balena/jellyfish-metrics":"0.0.192","@balena/jellyfish-environment":"^2.3.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.4","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.62","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.158-actors-81bf99ffe1fd5e8686936dd5f745019a2eb3aea3_1607623827494_0.4754829485717913","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.158":{"name":"@balena/jellyfish-core","version":"0.1.158","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.158","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ec0b3150c8c4f5d75c0594773e437c7a7c4b7b87","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.158.tgz","fileCount":95,"integrity":"sha512-jYX+s60oPjw+8hynxrF9K6qVxyiG13/8QGH7YZaNWKpk2vFciytjJdz6X2dr9Pex0AI3wz8NxTUXU+9GJjE7GA==","signatures":[{"sig":"MEQCIFzMgbvfvdBrXytW3OdCpmtl6IHdN16uTh85XlT8cu5FAiBroXByL0a9Kr84qpK+dzcISWMiyrj2YPng9YJnmyt2jw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":575017,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf0o4ACRA9TVsSAnZWagAA/0UP/A5U2EFjSE36sk9lt7Fd\n8y1p+GhBqTyyuC+J9NRRapcxROBKBEseWVy2BTPi4eud59XKbKAIS/V0e9DQ\nqrsy8l3PS0hpYYpnxSbscftECyzT8bBf+QdXRuZO/nfMDdEfiMVhYPcTvDo9\n3TJ7O58B6hqtpqnwEaYQqGd4kRifBAHKm05mlwBTDIfRTofg0NgROkFZ/y7j\nPdxOByKSkAnZLW4tV729icBFu2t/7VkecY7ERfyVR7iSTHbPhgQwNQ9bmdxN\n8MiSrjxFsboUroRyDId/+VbojL8O5OYqxGtAWSzIxbvX/jSSkHalfP+Em3C6\nz/ddjk0s9dK1V+fZg0IUAwady+EMUZNqYd5bb0xG4YVz5V9A43Z0CpaW3kLr\n2TmUgou96XlbR2hNI/voGzf3GsFAr0OLlzz+DhZsg+avSh78n9B1lgA8K5lC\nROAB9tyQH5ynGgT+RmvlRFGaj46JggIXTdB3hnBQ1FBx5ooGxeCjGm8mB2k0\naWzK3qtIEClBVTLF7Ix3FVCoFWJNECyVJjsZ2zUzfCXveSGZmsJevHriQlW3\ntnE7WnsZ/pJ86z+owMxFsFCly9ELY49zaANkHY2P7ZQj5OqXu5Nm6FOoQOjS\n/mLaaaV3ZXF9A4vV5+jJjvrJA880CdytHIsZtk7mY/l24MZlIGuO3I8baBnl\nsupn\r\n=CDCz\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"003ab519fd61f3c9742939babfdd219e8a0941ca","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [[ -z \"$CI\" && -d \".git\" ]]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.37","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.35","@balena/jellyfish-logger":"0.0.165","@balena/jellyfish-metrics":"0.0.192","@balena/jellyfish-environment":"^2.3.5"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.4","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.62","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.158_1607634431647_0.2584035602977517","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.159-renovate-non-major-2c07823e3c7b97a825ad5cfc19693138e3d86f97":{"name":"@balena/jellyfish-core","version":"0.1.159-renovate-non-major-2c07823e3c7b97a825ad5cfc19693138e3d86f97","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.159-renovate-non-major-2c07823e3c7b97a825ad5cfc19693138e3d86f97","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5b15c51a8338ed853708bf83ecaf368503c1d2c7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.159-renovate-non-major-2c07823e3c7b97a825ad5cfc19693138e3d86f97.tgz","fileCount":95,"integrity":"sha512-Xd+rmArIHQPxqMNVU9Etbco8H6qaXIWZuOAAo+zb2M7XL4DvnKgd/iTf1QzIslU0loJFcXifv/dSFjrfNLXbhw==","signatures":[{"sig":"MEUCIQClTNL2MemRwthem+vkjSMqTTH4h2zatm6UsiDEib8RqwIgHS7GrkvkC3BT6ANTouVX/RpJvUtb/HawSBTpjR7fuqc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":575144,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf0rnPCRA9TVsSAnZWagAAiX0P/R7R6AT1TQ1zZtG5TUpF\nIep06n5RNTgrKsmI7ASrzXo0f6pC8LRN7LU2DqVVVuxCPRftgMT7I1fqoC4O\nMGZM0w9QO5jRGeYdZ2vA8XMUsVaf2cMz/N46JVIfK7X/K8dVGIDmAWpncId1\nDrZWld1iPkTRbnMaDeqGGH1FzUsoxq02oKPMgUNt6YzSPuNQKhdrApeqTsXw\nX/y9bre9rwwrtqOGPBdClMJIvfsiRg3AlXk5Qw5d8WmxvGVrLrBtGTt++IBY\nD6DCv6t6LbNgx+Me3+zYr/UmidwYW4qx7QtguDG5PA6qXqm5GSEsxgRLpJzL\nnLKZX5oLBvPYmHxm0dCUpDmbTHRSaoL0wF4FOaVOdDZltae8nQgnhtCN0HBr\nStt1pN65jW6x9a9zipgdB4MSLB6Z3HlD+v2bGOoXwp1TyN1gShhN+7An47bL\nD7vVtdw7LjVtBkIEjwQZH0AM5OWohmE4Tk/66YkoVxvFH7eKYA10cAhQ95a7\nc8Yv6x9ukRjwprRMQjfxzmbwCq2c11T4S+lpYAZPE2knTfu7R+wKFrcOvWRl\nccbvMWM9EzTEBkf8F9XoFWeiSSi/Q8f/3lO5kWDvvUx5Eln8Fu6KY2898/AY\nTcKzr1OFLqg0FkCmyB04+DZQddQPO+wbSQaX4RjMjrBfrFelnjL2gj4itCMT\n5rJL\r\n=vqYo\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2c07823e3c7b97a825ad5cfc19693138e3d86f97","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [[ -z \"$CI\" && -d \".git\" ]]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.38","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.36","@balena/jellyfish-logger":"0.0.166","@balena/jellyfish-metrics":"0.0.193","@balena/jellyfish-environment":"^2.3.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.4","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.62","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.159-renovate-non-major-2c07823e3c7b97a825ad5cfc19693138e3d86f97_1607645647023_0.9919985462598762","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.159-fulltext-search-string-arrays-16516c119f4407ea7f975da0e0e15da1759348d4":{"name":"@balena/jellyfish-core","version":"0.1.159-fulltext-search-string-arrays-16516c119f4407ea7f975da0e0e15da1759348d4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.159-fulltext-search-string-arrays-16516c119f4407ea7f975da0e0e15da1759348d4","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9966889750c090f1626c46388222a67397c48dd6","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.159-fulltext-search-string-arrays-16516c119f4407ea7f975da0e0e15da1759348d4.tgz","fileCount":95,"integrity":"sha512-nNP53dC8nu9Q3OS1EDeEeBips6DzckPe5NrX5i7hw95c5csFk4pc7t9YCDB1Kr0Phray141Lh1q1kHz1QHT6BQ==","signatures":[{"sig":"MEUCIEbv5pAniuLHvvSWzRZmXpBHpBzaSqacKpic7KpW1FJiAiEA04NM/oqUs1HaVY6ua/LTyyoIMmz9QmapqN8B4WYbqOg=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":575186,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf0w9aCRA9TVsSAnZWagAAt8gP/jiR4BpzxsLWbOa4khV+\n9XgYA7P1Us63yvYU43Q74kGtTWiXg2FRi9HpaEMNcrtVgduIrYWHMEAAFsWB\nCIcDMJa+bXBft6LDelHDXz3PyKzw48xGZw/PFvOgh6txovL2t8qUbqMeAko3\nfZrpD/lcvfeG+CKJINZiPD0EKUi5/C1VHHvNNy5NjvhEKH7aDimJfo5fiQVy\nDpSCUqpuYLJij+DMIRCx7TIe9TfpD+5+1Ao0tTy+NCVtsHPkNMoffrqjtCd1\nlHesNc5KD1YumTwFU4rwZ1pIyUhy662B70BcZQDnFO8LWhN47seqgacbfN9V\njXyrz6P68PXD2GC+ReFOl+CogGkwSHKR8ic9FeZQteTKqFn3rtBbj52lKZMC\ny9LCgdR+tTg6/2sKOF1tmJpPIOTLexNmM3JuNggRyOTDuxtJdZTYyummPXyU\nmUefoDQxxCZkHwagb54NIfAOTJ8RMpc2PcutqNltuExX3Bugnv1lZ46eSeMn\nu4Y7SA1+xEqMXTpSu4re1q6ekFsIQ1oIMqlw4Dby81f0LFmR5zFs3vuqPsk8\ncSRYQ/zwlhGAhFPlUwWZ0lnC8Ytd6jhhDrGc1WRj6VP50BFnP75rhuoO5ox4\n2fI+KKmgmRuqUjSi7/Tv6cVjc0OOvDg2+HTrwRQPRKJk00+om015OHifehAe\noTvF\r\n=Kjch\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"16516c119f4407ea7f975da0e0e15da1759348d4","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [[ -z \"$CI\" && -d \".git\" ]]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.37","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.35","@balena/jellyfish-logger":"0.0.165","@balena/jellyfish-metrics":"0.0.192","@balena/jellyfish-environment":"^2.3.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.4","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.62","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.159-fulltext-search-string-arrays-16516c119f4407ea7f975da0e0e15da1759348d4_1607667545965_0.7595933292529686","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.159-fulltext-search-string-arrays-befc7dd4da5cc9132130cdc70bdf5a59de0ff31f":{"name":"@balena/jellyfish-core","version":"0.1.159-fulltext-search-string-arrays-befc7dd4da5cc9132130cdc70bdf5a59de0ff31f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.159-fulltext-search-string-arrays-befc7dd4da5cc9132130cdc70bdf5a59de0ff31f","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ea4e8e0b80055aafe25b9f12fc323705b00d248f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.159-fulltext-search-string-arrays-befc7dd4da5cc9132130cdc70bdf5a59de0ff31f.tgz","fileCount":95,"integrity":"sha512-Knu/cBiFUgg8OI2wzSUxWw/zMdQHl+uPNoIq42lI6rhJ1f5oa4U/h5YErCx+jOVClBDP0BtnC/OVMta96LKJLw==","signatures":[{"sig":"MEYCIQDrER6c5iL1S3EJR0GlLgbyF6a1PnoeHqMvv1g9yU+h7gIhAJFtT4tyy1sL3miFXJvCif0e4nkhOSBf2oiQC8kEqEML","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":575157,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf0yVCCRA9TVsSAnZWagAAb0cQAIntymynZ9plLHqn1zJu\n/WNkB5NbDwr6E9ceAhVVW4uXpZo02UMA981FGnvh5mIqk5HakRUt8eRdWBfO\n8R42CdLqYt37k5/b06KiPFuNtThl36TcHT05JJc3g0erXWzNzVmjy2Cg8vAD\nIr7NOv+ZxdQcYg2jk56D3UhPdVBm7at++ZtLijHhNesPHhciuVGTn6y0khDc\noyYOi4dTuKQFcKy6j2cqmDCHtsEwA3hswdzlbDuTsDH76ZVfyFxt7A1CV3fv\nZQNUCezSTWW/QEU06cHJhcarMjEh19sCDBEp2GsOjqR3O8IvlCPax3DMMUWY\nx4NkaBzGA/jBaeU1/tdXb1N6+Nhm2rTVJDeBwTgFnvzs5B0q25l1u2rD+ARB\nvnLQ017r2qY/4D1JapUJlzUebd0N9KTzpSNPaRxGoFqc9WUkLF6CZ1NTMI+d\nttki70su62J/3CRB9S53pyzSTGwzAiu8tZhBEteweP1a7Q39EMzT5GrjzT7E\nZeRyhfHYuI9TKi914TXkZ4o2FJoC1a6UETWrb3mg4sSWW8erq4WsKJjCD9bJ\nGFRFfb0X2lNR/GAvOjJXQUP60Y3Y/8oOJKtfDK8o7fTJc9VVujed7HOXXeKl\n3QHxPrcxeYj7xngAOw1B9eMOeloChArHUMnvIPns+FBFJ7nRaVwSfPMtHEN+\nF3be\r\n=u6EO\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"befc7dd4da5cc9132130cdc70bdf5a59de0ff31f","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [[ -z \"$CI\" && -d \".git\" ]]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.37","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.35","@balena/jellyfish-logger":"0.0.165","@balena/jellyfish-metrics":"0.0.192","@balena/jellyfish-environment":"^2.3.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.4","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.62","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.159-fulltext-search-string-arrays-befc7dd4da5cc9132130cdc70bdf5a59de0ff31f_1607673153901_0.5560120366950145","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.159-fulltext-search-string-arrays-28dd532a2ce1b00145f6ee1abddc6356098f78cd":{"name":"@balena/jellyfish-core","version":"0.1.159-fulltext-search-string-arrays-28dd532a2ce1b00145f6ee1abddc6356098f78cd","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.159-fulltext-search-string-arrays-28dd532a2ce1b00145f6ee1abddc6356098f78cd","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"127db2c2c4e86d561549dc4b1cd0f520d0639a01","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.159-fulltext-search-string-arrays-28dd532a2ce1b00145f6ee1abddc6356098f78cd.tgz","fileCount":95,"integrity":"sha512-71te5cv7bCe0E0wotEXn5HKRNWU/wlid8OuTn5muj8SiMPu7HwE9mikD73J8o1SvK146XQNY30A7oQORJBpseg==","signatures":[{"sig":"MEUCIDUwP+aIi2WCmvl2xmC+JacNzJZjVFgSeEO97HUOkSmZAiEA7yfM9G2zt7ju5nw3vPBvCtEGoqh57GQORXXHfIbETNw=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":574878,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf0zaXCRA9TVsSAnZWagAA9z0P/3lYbcrVh6mWYJcxpOCs\nKD07CjVHOJOVM5K+bApaPycWwwPrE63tVhFkpaX4xW62eRLLepUs2ky/KE0y\n+qrqeQy4++aEJW1TWzPeOw6hkbkEOSq5MzWYyuA7I95XDBEY+9ZYRSekJJ8c\n9DBEO6FBy/YYCrxwvo8bWeyU9soGoqR4uOGMlTKDcmdu4H8mzxrIELuH5sOC\nRjE3xQbXsFVzgQvocUd7iwl3MhZAnmrfDAfa0nxMeDq6BpsMZ6aKIBAP18z1\nJ7hQXe2UZqNyRd4ddVZgcUkPEN779FifgKjM1kuh0hWOR0b9OqfSjxC2prZW\ntNBTwnlJFM7FAaB1zp6LDpJiGLhrzO1+Su6ylmoAk+N8AD4gI8obk4CRE7hY\nb9vHYCz0VNpeHUy0ramJVwLz4b6LZtippOM0VaqUI/gOepRnhoVvjvcCX7iQ\n6sz3e4q4L2XnNYSDMk/2dwLC9joJ9ZIvFxyT7f+gx20aSoDpTOSEy7mYPRR5\nlCdsFKJzOnJ7nh4jjp/e+CgCPWAQ1kmWCE2jJag/NiaQHtHzg9yJqbB6yIc6\nSxd5KhTs8c2wQHK4orkWRjGuHFA8oLTS/agn0qvXYpYvAx3bM8BTB65rmSWB\nIIzxYCoJ27qfwsYY42CG5YfRNWVBFsAay/Nrj8CAKr13uON66cjINNBmDezT\nfnBA\r\n=28kl\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"28dd532a2ce1b00145f6ee1abddc6356098f78cd","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [[ -z \"$CI\" && -d \".git\" ]]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.37","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.35","@balena/jellyfish-logger":"0.0.165","@balena/jellyfish-metrics":"0.0.192","@balena/jellyfish-environment":"^2.3.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.4","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.62","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.159-fulltext-search-string-arrays-28dd532a2ce1b00145f6ee1abddc6356098f78cd_1607677590847_0.4674099741665394","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.159-renovate-non-major-b253755628d26849b8b9f64dfbc5e46f486a8654":{"name":"@balena/jellyfish-core","version":"0.1.159-renovate-non-major-b253755628d26849b8b9f64dfbc5e46f486a8654","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.159-renovate-non-major-b253755628d26849b8b9f64dfbc5e46f486a8654","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"505cafdec7fb7292c4de6d7dd00928f741fed16a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.159-renovate-non-major-b253755628d26849b8b9f64dfbc5e46f486a8654.tgz","fileCount":95,"integrity":"sha512-NpKwa0/bdzwCh/JjFFLITD/inftDZdZkLuBi39M1IyWni2KC4aEqIDnOEUwjL8mN8njyPSxcox3epuQqy37ZxQ==","signatures":[{"sig":"MEUCIQCsSBCQYHZ+GoyS3wdtpeRZIQrLoUEIfufa0UkSkTwJgQIgY97Pvm3TkiIR/8NsXpNTjyx5qP1hCOuBrMFvKkRafD0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":575146,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf00/UCRA9TVsSAnZWagAAHd0P/0xM4WwuvS2VkxdJw6dp\nu2Us4Ap3G04UfxxiGeccTpSInuAkEmy7i6ZKcAVwRgGl6G+ETzPdVx/5tv/u\nhGECXwdtYzK3gh8N+hhVERtPuNPtxiNmveitfJvNBKZeO/Vb3tLuQ46BFtDW\n/KluIw+dwT27Y9UPCI2mhoWcxSTMMPOW1WF4/QArhc0YyycB7KPbQip+W8pS\nP430TvLji49li+sA+QnNbiYppXNf2yRg1EUYNHL1JDVx2GbgSXjfEpkW1b/O\ntqCzJcKWg+LMT0w+c8SdO1Wa/HBb7Hs5NtT1TU63AD2REgtb3RwohbwGc0pQ\n/3irflwNRaolxn+85UHTZO3KH4otqMUp8Q6c7XvteiDBcxcrf3wfK6U6HebB\nMXwdig+Ss+2r5CwqzNw2WU0jWUrCHLLwKAxAJA0Bed4XCZVDPJ2vdndhUxSe\nLjhGyXTdjxNR96GIhwPsKGlNxoSdJ+j2uUDv4UsOzmNs0BOZSmuNgCNBrgeL\nHy1ZijPU7CqVfsIvyp3FRZiqxvoJ2esH4N7OyLYGYgIsdWHsFxibfPo2YYun\nUnnIRs37NgGutb2Jd7VIstozClTU9jhGtH9hEQW7EphhgHy0ibBy+2cE+lGL\nxT4hJWPDpjzejDnomcFQIki7zoeHOavtJGbFitJlDW9CDdVyr4FAy8jh4yce\nvSNT\r\n=1vdb\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=v14.15.1"},"gitHead":"b253755628d26849b8b9f64dfbc5e46f486a8654","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [[ -z \"$CI\" && -d \".git\" ]]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.38","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.36","@balena/jellyfish-logger":"0.0.166","@balena/jellyfish-metrics":"0.0.193","@balena/jellyfish-environment":"^2.3.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.4","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.62","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.159-renovate-non-major-b253755628d26849b8b9f64dfbc5e46f486a8654_1607684052041_0.807977586279589","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.159-fulltext-search-string-arrays-1f8d146a46c9ebd02b1dfadddf43f5c799f1216e":{"name":"@balena/jellyfish-core","version":"0.1.159-fulltext-search-string-arrays-1f8d146a46c9ebd02b1dfadddf43f5c799f1216e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.159-fulltext-search-string-arrays-1f8d146a46c9ebd02b1dfadddf43f5c799f1216e","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"916b76ab667c12b56b038d08236b6bc63ed4c60c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.159-fulltext-search-string-arrays-1f8d146a46c9ebd02b1dfadddf43f5c799f1216e.tgz","fileCount":95,"integrity":"sha512-KDmQWLe2EYURSSv+9RbfMCCYTQ/+LjiHf6bpGrFf9xeTKHBArQto8qOs77JJAdqukRAm8/LjHMNlADZJt1yOOA==","signatures":[{"sig":"MEQCIEb9ILwWM4IIvLO0knFg5WnUVWRsLJ5BI4f3hU8BY9K5AiA4CNOIOC+8lyN9hETt6ytDr28OVjQKYfR6Sj3+Vb0VNw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":574884,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf01QSCRA9TVsSAnZWagAAn0gQAJsEPSfoe4whdCyxV2pK\nkHug50S2ob8lQJ1m0GmA//AmkDJJSi20kTF4AqZsZeDJObB2/0KclOeFciEQ\ntdqNwcjiIMHGbot4W4XAk7Rm4Y6AGvr7lr7KTfGhzyq/gnYp28rfyb78KA0z\n6DXC/nXKKA6Je3uT8vkQAwhNti0PMCb5OZ3FXDqNmvurO7PBSVGtGIEnOS1z\n+IQuA381JJeVMIQ1zfyGcQszHqHm4G0IGTLBF6ab3SsWTY24ipmBc+kPP+uS\nbGvEgwQkjrTmNYcPmqyyCANQZ4urqYD1HYloWIMDcVyws7a0goDFGRQ/jwdV\nRFgyaTRB2eXGRcFyZZfEkrxKmQ3wzVLfADNCRMUVAO2WDjWJYMG4QUhwHg0b\nO2TgdIeqnLdBadV3iar4QbzOzHlndySh3/v/XJ8ZRx+bb4dS0lWqD2y2+Ty9\nPQSNA41dusnquUSAkaWarPmGbLKR1gl8LjkTz+t4Pl5XoG4VZe4SWupsab9y\ntRi8wZLCyndN/3fT8ao3OZ/2SaZiVhIcDmmXYd4/I88uFE9iMdTFO2S2nMQd\nqonxLooKD+G09tEJbpwdmn1bzPKmtcoSTSDrGsr5Mu0j6I05nocbKcpi7kuG\n8msNl1C3p1wfSQ6Bfl0oxOMhTkOh899epWdZZGrINNafSbZ13GBgNmf1UTL+\n+W62\r\n=gtL7\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"1f8d146a46c9ebd02b1dfadddf43f5c799f1216e","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [[ -z \"$CI\" && -d \".git\" ]]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.37","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.35","@balena/jellyfish-logger":"0.0.165","@balena/jellyfish-metrics":"0.0.192","@balena/jellyfish-environment":"^2.3.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.4","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.62","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.159-fulltext-search-string-arrays-1f8d146a46c9ebd02b1dfadddf43f5c799f1216e_1607685137560_0.6676248222165393","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.159-remove-debug-statement-1ab25df8dbf8fba28824aa06027678fc54981c1c":{"name":"@balena/jellyfish-core","version":"0.1.159-remove-debug-statement-1ab25df8dbf8fba28824aa06027678fc54981c1c","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.159-remove-debug-statement-1ab25df8dbf8fba28824aa06027678fc54981c1c","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d418221054569e0f48fa07183bab893dc424f666","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.159-remove-debug-statement-1ab25df8dbf8fba28824aa06027678fc54981c1c.tgz","fileCount":95,"integrity":"sha512-4QLIFqLaosYuyzPtu6u8Dr3tJzWpPDfrm0P3w7uwvETtBMaue9th8mzUWaIifIIdvXxXxX+xJyOJLRFtAwzCcQ==","signatures":[{"sig":"MEUCIFOYT31jZZbUs7kVwpKRe/8mgO9HDXkas+9uaFyL53W/AiEA52tFlDnEp2LqLRGhPWy/W8CxhhMMZ4ZQxXnt6c9jhjY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":575114,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf02cUCRA9TVsSAnZWagAAn6YP/iM85JelBZY1cQ0ChOod\nGizYY8Tepd/w7JWfcQla66qxghlvILE0si62EX69fRMbnH9OYqB7dNwCj0wc\nbh7xmEv6eY12NjlIllI/HwBSOiHEfu613JMK0I3zdX6dkj33Sa9SfS7uZ3W1\n/hdIEcORBIFHhVtRKjsDeMtUn4lM+sflr7loHyaZLBXoOT5aTiB0wZC9SxZS\nEMhX0GUMOQMmLigG50TfQ/ePX3oALAPaIFINRYeTN1vUrxW4obuMtzn1AAD0\nMhX6Bltw6AyScFlkXDPOf/pCdS7xLSf5PMdoD9WMKIVTbbgMZ7yHEvpWuIE+\nnVIPRubc/drnuvTBqgdBHbQb0J5Yx8ITfmSacOYkpeJ2GCKn93zVm9Mra7IG\nt68qAkqpBW+Te36NAC4fDVXhu1ll4YkT21WmrCv6U2NRvhejYD1v2aIPRPiK\nBSgq4tplKELjyrfticPkMNMX8otzp9IGkH1VE4UBPthEhQmizXNQbvsEPbqo\nk2akgnXfcQZNjt6gpAx8IqqiPyFD4ew8HsRm1gXQzUkhfRw3LCQ0e73SnmWO\nu3p1YJ4Ka6yfVyWpgPzlvj8A6VJ8DQNHv5KAFKi3nWAcrj/vq7hFz+BqAxZZ\n6NnBjx9+vgEQbLw3XVK+66D+c/b9FzXwon6gR4wIrtGHcYVsTcSjJ1trLEZW\n9/lP\r\n=3EF+\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"1ab25df8dbf8fba28824aa06027678fc54981c1c","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [[ -z \"$CI\" && -d \".git\" ]]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.37","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.35","@balena/jellyfish-logger":"0.0.165","@balena/jellyfish-metrics":"0.0.192","@balena/jellyfish-environment":"^2.3.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.4","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.62","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.159-remove-debug-statement-1ab25df8dbf8fba28824aa06027678fc54981c1c_1607690003315_0.3267612458913687","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.0-allow-id-on-create-c7bf7daad5bc3fd19581be5136c6787cda378591":{"name":"@balena/jellyfish-core","version":"0.2.0-allow-id-on-create-c7bf7daad5bc3fd19581be5136c6787cda378591","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.0-allow-id-on-create-c7bf7daad5bc3fd19581be5136c6787cda378591","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3d5187e51bd53480c2faae5cb03de812090d9aa0","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.0-allow-id-on-create-c7bf7daad5bc3fd19581be5136c6787cda378591.tgz","fileCount":95,"integrity":"sha512-WIiPkXVvMkc50KWMVV2Roku9CvSqHEQR2axjpdbBU2R5WkszwN4WZLUz6kLSfJLtrrPiPMxhw0XtUD8Th5oOlw==","signatures":[{"sig":"MEQCIC0S63Nt6ftkP5Jq7iaF6S/rWmGKpxmHstdhNXFgi3SeAiAA6zasPFOWQc+SlScSj8lTCwA7CW/0+blprtABhcbhrw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":575187,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf037ZCRA9TVsSAnZWagAAuwAP/RkkY6XFRm8dpe4TRmFf\nMxcEERAlbQZvGShkquAwTSzah/qXIO8uywHyeAmgfhO+glnrcWtBJeJOKb11\n+jjdulXrKpsXnkTTTo/iSWpwqCnIjcpk/D/2WTbfGJBmPoLii22+PHmK6HcJ\nKzfkG10zQGlsmAx5zSgs2j70r+4Ld1S0M22OPDOfiZq6+sP2CljvKpDiUUdv\nGlAAA+rKWtVJhP0x6x51NM9srzoH6HgXLbfpmnap9Wf7UYze5jNEuU/C2KTz\n+Z9T1yQCQuAhwNZjZF4hdE612mQfjNGr19WRdKg6ZzzyEiVG6OSBUncxah1y\nrrhtu72sncnt0m1bfSoRQZ3QdWSiqQIBMfPlc78ofPUqz0nNKkLQUXTvaGUW\naGK44OqTtSbjWNR7NQDx+9OvwE6KP8ypocquVAcFqkVEByqvpGPXehrtIBKa\nHsm+aEayWWM/qKGZJTOU4yuybWkqzNJvKziARVdcFEL+Drr/KgbiYO03GyFT\nFOK+SjOo+8Y0Rze89H9WJEXcv9fjEjYV/egqspdhhryPlw9+2se9NDxlsHEn\nGmIDp3IlgXGYP/u2lIkxxg82msdu9e6SnAvW88s35MvKVvForVgvyYrEf5zs\n1vYF2NzIMXdrNtfB+Ckuw7WR29RGPmnGDMtyPq6k9P9BwsWpkNe1gpboXXCW\n4lQP\r\n=12Ki\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c7bf7daad5bc3fd19581be5136c6787cda378591","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [[ -z \"$CI\" && -d \".git\" ]]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.37","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.35","@balena/jellyfish-logger":"0.0.165","@balena/jellyfish-metrics":"0.0.192","@balena/jellyfish-environment":"^2.3.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.4","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.62","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.0-allow-id-on-create-c7bf7daad5bc3fd19581be5136c6787cda378591_1607696088868_0.7633908977981221","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.0-allow-id-on-create-a870a6176a11f3cb61a1581b05714c579e9f4770":{"name":"@balena/jellyfish-core","version":"0.2.0-allow-id-on-create-a870a6176a11f3cb61a1581b05714c579e9f4770","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.0-allow-id-on-create-a870a6176a11f3cb61a1581b05714c579e9f4770","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0ac90cc78940733f04a7d03ed1ae2e52298df667","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.0-allow-id-on-create-a870a6176a11f3cb61a1581b05714c579e9f4770.tgz","fileCount":95,"integrity":"sha512-QhxbbqAHc0F+aHmbq/3+BBAxGpRE0Yg/dGiDfBlPmunIVnabD0JstqyN4MLn6vhAWwyPlBy8rl69qwMRSy/4Gw==","signatures":[{"sig":"MEUCIH2hNnDw3qCkl7GYP+24s6MhUePMr55Caet11NMRoiGwAiEA0zIRLKVBuqg7HB1CH3aEf4pJ61KwLWx3LBS4gWm5Dwo=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":575182,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf04RICRA9TVsSAnZWagAAmC8P/2q09HL8uf12NfzzD15x\nxbD8zPZvKbtF8htzQhNOnvuWRDcmdgDUHJ0eSx9S2z1GeRy3PVsMKJ1hUgYt\nWSyb+qAnkaUlm7XBRneB90AAILUy7NpHNU1nEQce6//FeNMnOA0Y5dy+TXW0\nNOEDFUrVZtHl3AbJpaorN+meV8QoGfq/ugHvJfzOvecJI5qyUbEKPAUGP6kg\n1JPiwgesBQToqJBrqEtkDSLGEb14+7/VUl0BjEb9PgCxuZwb1Qq3QpmAuDD0\nMXpjPGEAi8PwEp6No00a+1yc1Qq4lAgvfytRrGzlUiRHZyWBK4UpPJGfRQW5\nrHa6TJTjUkOn2suuWJeDn3ROYEmqx97l0YxvrO8WHaldTMKkIuUqFSLOaq3u\nOY9aR4TK9HA0/UKuB1FzL2LQBwWvF6k2owXmhP0wDOSBAEHISo7B0jNzpZjw\nP2tveMQ3fWTkE8x2q5fC6ztC+kkLdq9uRRvyqL++y6PNa87n8WZ0qElpsO5h\n4RCLvvUSdl1CwnabEJ0ibWrneYcYsVhUYO1I6Z05Esald1OapdOvTpMZQAV7\nqj8iSX04D1Pr9pIftHsA7wr59sePX/u/rrtYxVQrRz6Hw57XGfXaFfNZBn9L\n3qXM2mYlQYNBcjfO/8u/2wkmzrxEApIai+CR+gQA1qIlKhO0P4M3UX1niYUH\neym9\r\n=CKka\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a870a6176a11f3cb61a1581b05714c579e9f4770","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [[ -z \"$CI\" && -d \".git\" ]]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.37","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.35","@balena/jellyfish-logger":"0.0.165","@balena/jellyfish-metrics":"0.0.192","@balena/jellyfish-environment":"^2.3.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.4","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.62","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.0-allow-id-on-create-a870a6176a11f3cb61a1581b05714c579e9f4770_1607697480270_0.28148199377031013","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.0-allow-id-on-create-5885da5c3c4ecba3350c53bc89508aa4d8cfe8b0":{"name":"@balena/jellyfish-core","version":"0.2.0-allow-id-on-create-5885da5c3c4ecba3350c53bc89508aa4d8cfe8b0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.0-allow-id-on-create-5885da5c3c4ecba3350c53bc89508aa4d8cfe8b0","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"aed1fe05c28b00fdc0d467f7db9e83bd988136f7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.0-allow-id-on-create-5885da5c3c4ecba3350c53bc89508aa4d8cfe8b0.tgz","fileCount":95,"integrity":"sha512-fDt4uQFlGC+YBzpgyOVeRmYrq0vKpjpGrCROa0lVx1E2YTWYGQU5Uy5XPeubragkVE6L1+trKNHQeYuxTRKsng==","signatures":[{"sig":"MEYCIQDTrjYNKU9GUOV7C1O6hkYmgYNuG4RcpfWPwg/tFJ7odgIhAJIG3u0k6C1qoNFJ1+KjS3oNJytsbpQtr94EB+5wT6de","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":574491,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf04VtCRA9TVsSAnZWagAA9eYQAIUv+pZSLmpELlC/Y6oc\nii/ElCJSqUQpNfrh3cPZKxSO0k+Z96kRjqdfD6ubbNpR0Nxe3NQKntxzF5o+\ndcnu2CLdSWxP7H+NXMWPEtzTQE8ggK6j9RfUeKYkGB/sl9vUROvlA0FmXq5q\n4ldUM5mHhD0Hol6sDy8VrpE21ka0tmqV+8upurY14/tI4LYDrievV+2dnbu6\nWlrRSG4q2Ya2bNVzp16osQ5IXVWM5PrhiRhd7f+5u3LpMf24ctSJ0FkxKCSF\nlPFKwLXsfJR/ZKj+WykdizH1l30za3GwQLKJx5UNaf/EbXU1BbTj0tGVBs1w\noJ5aJYtitK8+1phKHU8T9AMbbDkTmxacUK23Bs/z72zUF4EdZaeXIiZxLqTd\np6Q/4m6cd194mQWOBVD1BIK88XTSByT6y/jQvb+B/cnuGvASyaazms7h9pLD\nc+KnVNTqGaWmCwNj5SwqsylzH6RRKCcVEy9TzzmNalv0a6jIs0S6k/P/6fVL\nGEqlNsPNZ2VNK+zyiQG09jmBjt4z06eqqKN6NPhRqecZfcmd3g/Df8BYVEIE\nIBlCtS85gycLU4QdO84vlU/Tk5mPzw1Td7qFFEmTzaz+IdvYeiNcekTGEtkH\nnNi9Q3wWgLPGuX64dA4qdLEP2ezCCTQjrU4A9YFftZq+h43+CGmcUcySpntS\n4dvp\r\n=B9qw\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"5885da5c3c4ecba3350c53bc89508aa4d8cfe8b0","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [[ -z \"$CI\" && -d \".git\" ]]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.37","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.35","@balena/jellyfish-logger":"0.0.165","@balena/jellyfish-metrics":"0.0.192","@balena/jellyfish-environment":"^2.3.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.4","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.62","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.0-allow-id-on-create-5885da5c3c4ecba3350c53bc89508aa4d8cfe8b0_1607697772999_0.8321795477607197","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.0-allow-id-on-create-c42e34e4be14a8e5a30314d934333b3092ea39b6":{"name":"@balena/jellyfish-core","version":"0.2.0-allow-id-on-create-c42e34e4be14a8e5a30314d934333b3092ea39b6","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.0-allow-id-on-create-c42e34e4be14a8e5a30314d934333b3092ea39b6","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"bf33228f5d4d303e9842d18566c33e31a90c5e63","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.0-allow-id-on-create-c42e34e4be14a8e5a30314d934333b3092ea39b6.tgz","fileCount":95,"integrity":"sha512-fx2hkwAyH/JtCiC3LoGO4CPJBlg5lTUWVvQ2Jj2MQCxas6oAbV8RHevY48NkTULhCWmtoy36gxWsdCDVAH8dTw==","signatures":[{"sig":"MEUCIQDxUoVxZR9mNCiUptt+AX3YqYtngGYB7rehqzJoBb/+PQIgNdDzSUT8MJxbkamvyGC9LhM8xP0l1gul/cp/K6Ux0Zc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":574486,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf04rrCRA9TVsSAnZWagAA2JsQAJdQrut0RCAo7a/NptwU\nZmRGTyMw/kI2pRWYOCPPBpN8is7dq9A+TdN/eZJ6APb5sbLh7l6sOkqsLC6z\nhrLgC0ba9V/ala0+dn3o+dD+nc6qA3R+x//KNqbq2/KhQJTYkb3z9UX1dHuD\n8U6lG+6503tIKQQm3TvnHX35PtjyrpMQyavbKWJT/8nrwILCTcV0EgHS8MsZ\nlCIUIK6SNMFkvplHaSy/U+HCIhPyD/XRztPvzPvT/kRIT84xyIkmG1x8RL9G\nf3AxUbXDjB69Z4Hqnkr0e6Nr4smqcdfYTeEfbj0HK1L5qNf6ZIyGfkRqlwn/\n2F3piBex+Vjv04JKzbmDsBpFE3fFaPxQlC3NdRMm89bBCUVQjYAfaEhg75ND\ndWW1EcOEhnnz7DwNO621fCL5Zl+cCggdFEa1KeJFXm78mtZ+DN7uA/9foK9y\nK+rn4X4bjtkTGfMBzzAPLLLSeS1JlLG2+JbXSaUIbfuiaPob+PwZNxYf+qFv\nZwsPbaMEg4aNLCdZvJ2FlboScn0pM55AcGAVV2Nx2uLWyMXEVkgTModp6VWd\na8hl7glmMrxIR36MpBFDPP6ln0Njncan/YXuH3GjKgmufwiwtxtYITDDqwyN\nVJkkUZ6T/53f0W6t6M4o8olqECfq60Sl4ukxuVv5NBOEhZ0shj4o397u9dvO\ngrsO\r\n=oQIf\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c42e34e4be14a8e5a30314d934333b3092ea39b6","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [[ -z \"$CI\" && -d \".git\" ]]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.37","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.35","@balena/jellyfish-logger":"0.0.165","@balena/jellyfish-metrics":"0.0.192","@balena/jellyfish-environment":"^2.3.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.4","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.62","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.0-allow-id-on-create-c42e34e4be14a8e5a30314d934333b3092ea39b6_1607699179406_0.6390831906984182","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.0-allow-id-on-create-23f787edd8ad15b611e00b5c152975785e07ad72":{"name":"@balena/jellyfish-core","version":"0.2.0-allow-id-on-create-23f787edd8ad15b611e00b5c152975785e07ad72","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.0-allow-id-on-create-23f787edd8ad15b611e00b5c152975785e07ad72","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d0e0342a4568602cc0dd2f1ae852c8be2fb2847c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.0-allow-id-on-create-23f787edd8ad15b611e00b5c152975785e07ad72.tgz","fileCount":95,"integrity":"sha512-7mATTcOxtB+ZLo87GXeLR7F0+yuaph5XVi7BVabb6gRfYyNE23CgyBlK5w38B6gL5mIysiXbGSPVc6kx3knR7w==","signatures":[{"sig":"MEYCIQC8M824HfgbenI/xb59PfrHafMSWrn1N9maTtTzg99uqAIhAPMOlSUs0MSkzL1TkPV0BqTYfJLim06prs0fqXZLefL3","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":573606,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf05HxCRA9TVsSAnZWagAAVAYP/24vPL5qgQvSt0+PMB64\nwxD6+jTnqiwSvFfqfMGv+vAhMr/Y+xGkblaqcKjcdzsacDMOz65UnQj6Whva\nxHaWZ921euznyhbAOMmz3k6mqgLkDhHK0Bqbh5eg0BOYiEq5fymKNLFfhysr\nig1AvZoWLhtQyc6hm1XB2JswuNz/+Pe+k38VnGb2ah8KZJSZoKQ7AwPVSMPF\npU9ayf3PNlsIuAA7Ziou0aTYsyDheK6vNqHPRNY7OamxzEYmEl4qC+V2Ds7D\nT0KqKYmThYb9D8SIZdmQTYfkh2SiidgstZnPvYuOCSpIKd/aPhnUCTaLLc/L\nAyVFgPdzdojxCk2MAIZ5mdLZYmEp90ZMmpOo3Y/guZPRrppSQm8eqxpRxT9P\nDK0Hyh6HPHkHcGr4LtSHPbZSCfUqkOnoRKbijXyBmfounixVdDQQVH4WMs0G\nd/cBo3mLmr3Rz+mttbXrFdd6VKx+w7xIBGDrWT63rQSP9IiHRCQA1C3Iyb0X\n784JOnQ2DIDhAFc9MBSUEJQSgFYhXiaYp1NeiZ/zhSFaGojsTwswzb788gUX\nzLQggEIanY12MFCXfc59pI9o/G5bJUeO2ZDmMaFPeg49ugqYFzi1rEGeDTUs\nUJZkdwa3tNMmsPgypBBmTM/TRp9YUZ6CHLSeflWSWnfE4rRGqqdy3iocR9KH\nW/8j\r\n=9yGr\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"23f787edd8ad15b611e00b5c152975785e07ad72","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [[ -z \"$CI\" && -d \".git\" ]]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.37","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.35","@balena/jellyfish-logger":"0.0.165","@balena/jellyfish-metrics":"0.0.192","@balena/jellyfish-environment":"^2.3.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.4","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.62","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.0-allow-id-on-create-23f787edd8ad15b611e00b5c152975785e07ad72_1607700976710_0.05237070432915525","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.159-renovate-non-major-a7d1a3459db7655ea03ef33bdcf216515ba51e62":{"name":"@balena/jellyfish-core","version":"0.1.159-renovate-non-major-a7d1a3459db7655ea03ef33bdcf216515ba51e62","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.159-renovate-non-major-a7d1a3459db7655ea03ef33bdcf216515ba51e62","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a3449ca4d28ee073e173d64c2737b5d7251edf32","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.159-renovate-non-major-a7d1a3459db7655ea03ef33bdcf216515ba51e62.tgz","fileCount":95,"integrity":"sha512-/Al7FP6yG4F2QjaEwPF29UWKIoVewTBfg2nBW//CaMIsVq0eyWxpenPjWEu2VVsxvlLFovIKQaqgMt7JsH71RA==","signatures":[{"sig":"MEYCIQDeSyc31X5CI1IjOjQEfMczxUMfla9v4noYZCucnGqdEAIhAJBot63a3fScOhnIgeKmcMynOefx0UQKaXYYRRfOAbyG","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":575146,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf05OvCRA9TVsSAnZWagAA1ZoP/ignuDA+q5VH1JrwsdmD\nVO2cbBDu8FHg3JKUQ33UfQuUYJ6dwIHKLhlFxFkR5N0GbBramx3O7ouCWeva\nMuy7j/MwqRKJffwBRO5iFhuvEQZ20DHka41xQ11w9wHR/PBTW8QTkZVtzN2T\nUIbkjnagKYdcgzCo0BUzlAIB0Qan4CVofQly1bX65k/+8n8ZkDDWOIk8FWWW\nuOqqicLwbdQcLJ7XgL0sNU/98EJJear/J6VL6bLfDS7U7XuKXeuJrZ7eZw2v\nsDL1fA/55DztNKTHONMt8i/Ipw+NWZAfuf1qBhDsfFcbvR5/F9HCVkD6Yh7q\n+nsts99oA6Rx930Gnt/MIWptwhRfN0MUV09OBnZBEP75/U1on55mCROIeHZp\nBbK7Z0W/JGt5lVATxGC2YKuq1lkbJ6du+BirDNUGXre3iaNO9Sjzfpzh3kz6\nvcemYSyGPXJkb2qgBpPy28/Kw5RgGFoSLOe9hq4/L/374cjJPBeoNd3vSoBy\nZeIduI3a6qq2hF27ia7KAOt5Aks07ZVueb59jqstpZd16bN8h1gVWSxCBF7U\nn6a6JdiGWhwGr86FRhKX+Nuq0LHVknb5VBWIKMlg9qyloSCHe0GrLv7xodGo\n7nLXEzHtfJ1Noz6LbHe/nfykoWgOTSk99X/ula/STaQ10a1BNzZyRiAwJpfg\nmWM6\r\n=cX5O\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=v14.15.1"},"gitHead":"a7d1a3459db7655ea03ef33bdcf216515ba51e62","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [[ -z \"$CI\" && -d \".git\" ]]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.1","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.38","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.36","@balena/jellyfish-logger":"0.0.166","@balena/jellyfish-metrics":"0.0.193","@balena/jellyfish-environment":"^2.3.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.4","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.62","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.159-renovate-non-major-a7d1a3459db7655ea03ef33bdcf216515ba51e62_1607701422827_0.025059149358725108","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.0-allow-id-on-create-31e52f26a9944535c778493c8b572743ac80fed8":{"name":"@balena/jellyfish-core","version":"0.2.0-allow-id-on-create-31e52f26a9944535c778493c8b572743ac80fed8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.0-allow-id-on-create-31e52f26a9944535c778493c8b572743ac80fed8","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"958b6c5bc9a65c83f607b405f4d0cb39212d5323","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.0-allow-id-on-create-31e52f26a9944535c778493c8b572743ac80fed8.tgz","fileCount":95,"integrity":"sha512-kqXjwPUQMe32f4LnxI5LXqG9s1V9fLgWxMj86MGezQfudhcIPfVqSeUdAuUCMLQvVxM9ot2PvRC4Zl7mz5Lgsg==","signatures":[{"sig":"MEUCIQCebROVao8u0JSCxRE+CH+mObIMJDR5taUclSUE06+oRQIgfdJAeerE8Z0EU4bXfp1VSOlqUE7F6tCEsPtWH813bwg=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":573941,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf059oCRA9TVsSAnZWagAA9YAP/1Vpf2rdrhYqTtFHyjvt\nzUgpJwp4yerZpAWvGquLmiYeZM5iK64TPeC0cSHzof4smqVqo24ev8xJB6Re\nH9jA3Rxw66WS+mlWa7ktlWQj9FCVSwDlDW7cd4LaY11RK3ysBvMjoMdhWplL\nIqyHHApLea44Vlx3ooSL6Ag0iRY3zoOpcXSo/41+N2hpuHcBPWPo+2dDYksb\nfJ9fyXRGM5hsbnEC2C6yJDLahdH1zemjmKgT33EQrwZN/Mzq8RQXoC9TdDAw\nk82pKmMPJXbdQDgJF3BEV8XyzjQiBsW5vbMDIqaGPm+R4a5PzQHqSowVVWo8\nrU/9RLxW1fjTGGWj6iQ4cCtKzp8BhEZdJ+GQOrlkTgTotl8WISXBAEBDS0Us\nEXX2t9UZIzZge+5d/YUvcCK56b0HF0XpvmNk9tDU8vPNYwJ4gdsQPPXGSqIa\nAKcDAdMdsQtAeytVQrhm3CDYjfOlDIUO5P0M83pSFYf4J3xfbhKlhJw+4nKQ\nljmDnH742SIxZ95YqTqhTGbFCZh86wxLXefRYlyyhlTSSuQy9MUtSsV9VZTl\nVOPcAuoj77ecZDIBa7CGcm9mAktenYLFVgf9jnpaGnfPuwbVWk7QkGzr4dCu\n1TKA+3ELssYs5mVFMUZSGGtKCBHUOFDidQ2Xa5ung526WemVsgKuOe/bPNj0\n46U8\r\n=RHu7\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"31e52f26a9944535c778493c8b572743ac80fed8","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [[ -z \"$CI\" && -d \".git\" ]]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.37","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.35","@balena/jellyfish-logger":"0.0.165","@balena/jellyfish-metrics":"0.0.192","@balena/jellyfish-environment":"^2.3.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.4","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.62","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.0-allow-id-on-create-31e52f26a9944535c778493c8b572743ac80fed8_1607704423699_0.9627555244286878","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.0":{"name":"@balena/jellyfish-core","version":"0.2.0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.0","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"56908db9163eb3937e3050a1a66dcbde2709e86c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.0.tgz","fileCount":95,"integrity":"sha512-igZeKeyma3uKF/a6slv43OYBvVcblYXo6z4TrCLsQSSp7mlsGy7HOLAj3EOSWlpcMzvP/XgW1tyJS18nV3g8ow==","signatures":[{"sig":"MEUCIQCajeEoujpbwvqUZHdKfOpxdDQPD9lPM1SjxQLiJ8LDdwIgYHnUqCaspyr6d3C4Bvi4g1PKoL7OhvpECif0a8iCcTM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":573881,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf06D3CRA9TVsSAnZWagAAsh8QAJMPMyVBFWk93PYQoPgL\nsfdy1P9ZzcqjZl4SObDeW6NwkBjxVCBY8/0A3/769/fHT22hcGXiugQxwmAC\nY/bjIFx9rVj8grBtCErDRTHZXkoCGfCkldqxG1xn81G5vMJWPZI0HLHuf0g9\nn+ii620DBPG2IqF89exWnuIXLTOOdXb097hksw7YdHUSjD/vXjSSKDX9v27G\n6ORiQGatYBDrQ0xlDIatRvN0J85Tf6t1pQdrqvZTh3h5ZftHwIO8ELtRmIQA\nt22CXc2a18DTroIgrINz+dxeEQjmem43g0UNuh3JJdCiLWynmHjRCxAZfvWG\n8EI7Q18prZvd203SqFWLUxxxcXEDbdh4ITCZz+u4pUYiFHfLY7e3qkgVO/G3\nYBVhzjE80jEPGEFvSyEpFH9EEHEZarEiIMcst73CgQ20K++yQt9lrt1ZR+Uk\n9JzA1vX2cS1b5DkfUOtB/zn0ATmMX09A6t07rH78u0aUzcOtBWmHFtRqYfSm\n0Y3dpwsQ3Z8KxyeNqzsuR5fW4Ny1TK2FI3mvMLXxPEQDZEedjuVR7QpmLMNd\nFIB1YK1xKxYKPXykL0cw3VGbBVd3vUqxHSANfqv5Kj+MuJLqLAtNJ8kwy1/x\nEOERt76IKns7UBUY2OEeZ3oQjL+kB908v26P8N+QRZ46By8LV/DpGDoVOgdC\nIJrV\r\n=rnjx\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8c8dac8cdd6f6ca582a49efd83aab805ec81412c","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [[ -z \"$CI\" && -d \".git\" ]]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.37","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.35","@balena/jellyfish-logger":"0.0.165","@balena/jellyfish-metrics":"0.0.192","@balena/jellyfish-environment":"^2.3.5"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.4","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.62","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.0_1607704823197_0.7122199908696258","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.1-renovate-non-major-2d77aed9e8e04481a8103e4b48d6764f2d1814c9":{"name":"@balena/jellyfish-core","version":"0.2.1-renovate-non-major-2d77aed9e8e04481a8103e4b48d6764f2d1814c9","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.1-renovate-non-major-2d77aed9e8e04481a8103e4b48d6764f2d1814c9","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"05dc1a085b30dd585715c80cf710a98d869bfe13","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.1-renovate-non-major-2d77aed9e8e04481a8103e4b48d6764f2d1814c9.tgz","fileCount":95,"integrity":"sha512-3Jhpi585G0PrW9RS1amqdaGIS+iUI2WMD8Dud7nmvecB+fZLRJaZ6E29eLF/ec6mjwDHau1tCEcrbkoQXLMYlQ==","signatures":[{"sig":"MEUCIGB2N+HWpmHq2bthRWe8qkLeRW1JQwM8TiEt9FwD77S8AiEAmOqVAZKupTx8qdUwj8QltoOJWiP9fA8CyhXtRadTz6Y=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":574008,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf06J8CRA9TVsSAnZWagAAD4gP/180r4XDIioUSgoRQgtE\n8qLHd7G9l2MasbQzUCBFYQict+76hq8RP3Z5TeBVIKYO+0vHXka11Ts0xk3f\n6E4TmqlRz0YJbsvHHXkSudOrMN/fLKZhEU7bz7Rxjl5XNKz9jUXpSCHWB4RJ\nyLagZD+5lMw142KEDG/bF4F1AeH879hNiKfVQ/ge28z+0xY4zv2F9vr+T5xN\nz2Qv0ThLUv9q+NCj3HjLha1uTyBB6ufrZzMhK1AocgSm6GXfzqa8/o8qcdHn\nYKsh9xTVtgPEYoF1y8+5K2Qf6M1TWn4jjQuld/4uUR1SPZanVgzHwZD6Yjqn\n8232NfdpBK8McascT13bx8+LYPNB/hAK3ndpSg0nNk/6pS2y1KuBsTbsUvOk\nl5CKVSnaCZdWEcexT4710iXL7G0rguLEiDoP4Bymwzy/eF0+ejQmIt4gCFiq\nVHPXXB49vjyNm5FDLY2KgdiykNE0pse3FEwOYrzmlxnNsDM6omehSbOPfasb\nnP8rbZp5EwKw2wyUP7IZpWm9wVpzhHboKJ+qPBeKj0ux0qVvBDa7X+3xNqi7\n0diFxcThJ6NRPBIBtvKe/5clvYjR4wIiLcXmATMyKpx4s+wEb+0gUGBxCQsf\nBANq2ubvsACF2o8BUjrJXctc5Ff1TxcbnGQxvhg22vkM6EwBFxVOMZtPI3wa\nCX5c\r\n=AXSW\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=v14.15.1"},"gitHead":"2d77aed9e8e04481a8103e4b48d6764f2d1814c9","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [[ -z \"$CI\" && -d \".git\" ]]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.1","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.38","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.36","@balena/jellyfish-logger":"0.0.166","@balena/jellyfish-metrics":"0.0.193","@balena/jellyfish-environment":"^2.3.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.4","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.62","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.1-renovate-non-major-2d77aed9e8e04481a8103e4b48d6764f2d1814c9_1607705211841_0.7969140208919188","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.1-renovate-non-major-7580bc81d2829e3428c0864afdcb7e42f1f7fed8":{"name":"@balena/jellyfish-core","version":"0.2.1-renovate-non-major-7580bc81d2829e3428c0864afdcb7e42f1f7fed8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.1-renovate-non-major-7580bc81d2829e3428c0864afdcb7e42f1f7fed8","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2f6c93487a5618cd5ebe89882f8fa980acd0805a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.1-renovate-non-major-7580bc81d2829e3428c0864afdcb7e42f1f7fed8.tgz","fileCount":95,"integrity":"sha512-GjaH2JoYqt698ilOmlxrRwgjukRJC/8U4VsD1SC8xStXn5+P7zzn7K2BiJ0oGgcz4XZ5CfNUsN4VkV67vEmXxw==","signatures":[{"sig":"MEYCIQDiNsSZOfrC76JFNBc5okCy7RLP0CSJiKjSTuFAbBfkMQIhAJl8ksoNvZaNqDNTRbdUwyA9m7zccBj0VMWS8hofnOE5","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":574008,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf0/ixCRA9TVsSAnZWagAA0noQAIOfTsxuycALOpvIQhbh\nrMjm2Z7UlPwrOSgEdnhPsj2MBncIlbhw4tVNrTUGOnsuG7iNIvixT92mlxSs\n1u/NrjaaRgfRM2uPag8h3C0+KUj6YDuXFXnLV46GpuucjqqwGEzPBlv/WaGm\nxsO7H00+v/RjHa1h8i113t2bv4EZbrFbpXY7Gqkv+0PK4VxfU+1VyO/WWfPi\n+YxFMUHAYEHTIwqkuQt+Go5+XVwqK6YXq8tIn9uJdIiltMlBJCtPHoF2O0I3\n4J2vLYVPztlOuh5jZicBFFN9eIDUuGOSXOAwl3hyyYFkyrl23Z6hJik8OObR\nT05IV+uFNqibJnWvHs+Mt718Y6zM0pIzXHS8rGsRDS1h7ODgSa5HffGFMOVG\ndg5HGhuUIneobqFO7or9pqnQTEG3MzR8k6XrYyPEnCxZNv09+YqJWIkqNU7Q\nhfIF4iUnepyTGhd8rO/7zbLvvAMX9IRTZro2zJkVStvCxCCmbzSvBgUimkNy\n3FHqQauh3nRViMVtANXTJdUAqa66Cng+YLouTli+5j70C7NBsSOld9HwxaRL\nrRXBOAPRTXmtMQs2kJSJrMlM/PC/+ZSypJwi+iao5N2qy/k6owSebwf0RYg4\nb8jllscggmDORrZSHNbGQRoxrV/Y4AYoWGAwO8H0+yW30xO4pt3CQWB3FSbX\nabiF\r\n=ZQsF\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=v14.15.1"},"gitHead":"7580bc81d2829e3428c0864afdcb7e42f1f7fed8","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [[ -z \"$CI\" && -d \".git\" ]]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.1","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.39","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.37","@balena/jellyfish-logger":"0.0.166","@balena/jellyfish-metrics":"0.0.193","@balena/jellyfish-environment":"^2.3.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.4","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.62","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.1-renovate-non-major-7580bc81d2829e3428c0864afdcb7e42f1f7fed8_1607727280549_0.8026068394022539","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.1-renovate-non-major-c7e74cfedee8eb789a038ffec9eae8562d928ecf":{"name":"@balena/jellyfish-core","version":"0.2.1-renovate-non-major-c7e74cfedee8eb789a038ffec9eae8562d928ecf","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.1-renovate-non-major-c7e74cfedee8eb789a038ffec9eae8562d928ecf","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"47c031d0c3448cdbb2ae59b813eec45247e45991","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.1-renovate-non-major-c7e74cfedee8eb789a038ffec9eae8562d928ecf.tgz","fileCount":95,"integrity":"sha512-17DhpbfuBfpGixblJaooVnAlA1RWU+wxItVM6oAVHpLKk9y/+exMbU/YWZf+DfndV1TLwlv4oqzUlZ7xUmB7TA==","signatures":[{"sig":"MEUCICUGRzioiokDi8P9UZBXh+1SSfyeiE+3B8uJ6GhalITgAiEAj2IJv7oMUxHOptL77qiPnZiUgTfasCWVX97KSI3QHt8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":574008,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf1BxtCRA9TVsSAnZWagAAYEAP/ipyMQE+HFJKFe9GXkjY\nZc+2CHjP7JMy+v7LqhyJAQf61gGh5w0qhUU2DqAUtPe0NmOvXlk/hnsvqaRl\nZR4DRGgsdlCFy731UXmaNrF3bvTsPoOQgcWtxHhC1xrmC3Lou2ZWzbBFb5ip\nhrq92rq8cPo0QtkbiQyAxj7LnIIH+n6H0voKC4Kh4gL0BuqaxQb0foWkKOhE\ndYP7AnUPsCAnE2BOAgb232bQyUxwJAxFTjs1feO1W9iznC2bDZStzHnxoIuE\ncL39TTcRSK3piAs6Gq8rtgHIY9Yq6HXy4gZy3Fp3J1yaQrTe8pzi5oBYBUzM\nJ7ZQZxzWjopdSlhBSlkelo5Nu4c9QmuFnAzyNUP+oBX6TPz2N1Kitli2X0tw\nnpS85/SiN8T2kOfGVBBi2RzjQKfwiBk4ANFKsSLNv4511aZyHdLHTMzdRVMN\naZWtTUQjdduID8KX0g3nwKCwwrG4iAS4gbTsPJYOveXDiCfVLrzaS/wDg78n\ndPJxWNEPf2C+LHr/GZFMhwjdJD9rCItqINLCCBU4BbE+p/dovV8Q+6QBPSro\n8RglAb8CyC/T08T1ShoD2SUvJDeaJwjPJoFJV3tnnHCOPAMbGVyiw8AzBWpE\neJGhAzGQA54TsdtV1X3pdVmah4yc5bFbKbNW4BOoIlHjSRIQnWuGY2eh2M0V\naxbc\r\n=uS9h\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=v14.15.1"},"gitHead":"c7e74cfedee8eb789a038ffec9eae8562d928ecf","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [[ -z \"$CI\" && -d \".git\" ]]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.1","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.39","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.37","@balena/jellyfish-logger":"0.0.166","@balena/jellyfish-metrics":"0.0.193","@balena/jellyfish-environment":"^2.3.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.62","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.1-renovate-non-major-c7e74cfedee8eb789a038ffec9eae8562d928ecf_1607736428946_0.2469817456304675","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.159-fulltext-search-string-arrays-bedce67095fa2a270681f6cebc7237ba87b6cb43":{"name":"@balena/jellyfish-core","version":"0.1.159-fulltext-search-string-arrays-bedce67095fa2a270681f6cebc7237ba87b6cb43","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.159-fulltext-search-string-arrays-bedce67095fa2a270681f6cebc7237ba87b6cb43","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c2587855bda6edca018c1fb608d04294c3edacc7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.159-fulltext-search-string-arrays-bedce67095fa2a270681f6cebc7237ba87b6cb43.tgz","fileCount":95,"integrity":"sha512-MJ+W09cOm3Eb5fD8FtTYS1hao0hHub+pkqh9e1PqJwFsDALSex/6XMI2oILakHF7rRAZaWfFLy7FavJjVhd4mQ==","signatures":[{"sig":"MEYCIQCSewNrQ8o7TaXn0VzHPw/U4PrDcVyycZ8nEeA7nqyZ7gIhAMmNstYzBqk6FAwSu5GhM3f3NMbhS5I8OppP1QA/3AgM","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":574770,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf1CBWCRA9TVsSAnZWagAADkQP/i2YUOMGiJ0nF6ovtIft\nDR3eeHpKTQyzV6JSdsZdtgBz9Wf91UPrSnN5qpzxP/4GpG0NhkxXrtMs7YsH\ng1t4hY8ztJcgf+TgUrS3jsFNhK6jFg6grFu+MILBu9h/Tr3HW3um2b1z2pyb\nHyAfCnGIyNfvbFD8pmYe/Mg3bdWga9bWe1y9WXPmKILcgS1OfQn2wNmFzUUt\nNnzCMaoqu5xr777iXHALCc2ZGV5543WsHjneUX8Bnbl6sdxlhB972zoV9lSP\ncbbhKaX4zvEElZMPAoYQmgsX2pTaHDNCbkcCtU9DCzU98YhM/P72KwOdNNlv\np9RjhqVSQrDtv7q6IuSS7jJs3CpY7bcaZu4W8P3b6E+OSRBRe64b3HK+i6rq\ndul58MKVoxBHNJYF+zoZBQgb71TkfiA9L9s8iSiHmkePdVmsUbVqLfzWJEys\nRU0/zE5A4YTmWWiuvrTFERo+1yfuKM/awaUB0VBP/Dftd/s+e5f9JiY3d5p8\n2VO8hcJtBqOZlpMmEnsNP5btNijmh8HJip3Zy+OdEIwo3NfWTjw5uMFfxyrp\n7SoG0x7knOEebholCeJKHwNt9l2Wz+Nj/svvfMSVumSmaQwvXCliDaWdl/Rc\ne6SPPQC2sWVpMLxO2z2ryFzYTfNBXSjpV7KrsBvyEpJJet53KLE+G7m5JM0v\nu80n\r\n=qtTd\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"bedce67095fa2a270681f6cebc7237ba87b6cb43","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [[ -z \"$CI\" && -d \".git\" ]]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.37","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.35","@balena/jellyfish-logger":"0.0.165","@balena/jellyfish-metrics":"0.0.192","@balena/jellyfish-environment":"^2.3.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.4","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.62","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.159-fulltext-search-string-arrays-bedce67095fa2a270681f6cebc7237ba87b6cb43_1607737429677_0.6635152795911063","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.159-fulltext-search-string-arrays-c7c9f83aa739aaf557a65c30734ba5dfa792c59f":{"name":"@balena/jellyfish-core","version":"0.1.159-fulltext-search-string-arrays-c7c9f83aa739aaf557a65c30734ba5dfa792c59f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.159-fulltext-search-string-arrays-c7c9f83aa739aaf557a65c30734ba5dfa792c59f","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"07baf7029ae9e65c42f62d921a8d9fe7cc34e9ba","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.159-fulltext-search-string-arrays-c7c9f83aa739aaf557a65c30734ba5dfa792c59f.tgz","fileCount":95,"integrity":"sha512-bL9knntwYVR4a92cZWNzh4Tq20xoX5NBhKtHMN/x3Xq0Ri6s/hB9ClxovfjCfOb+NZhRFPSpu23KifX1sEn7Hw==","signatures":[{"sig":"MEYCIQDZslbDXk1AYADvpV5u01atjo0pABJyFToUsI6av1CezwIhAJ311MtNnQEOCNR19VYqaKFCS80K1nl/IxZ5O3TQok/3","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":574456,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf1CWJCRA9TVsSAnZWagAAwZ8QAIitTiQAFTgAmY58WKJ3\nb8Y/g31qBvz9ZZ8ekQY4PaSMtNRvPGaddO+cfAF2zTYFa4pYZdBWfmBIJAHI\nmxiwF0bbn+IcA1MNcVmF67fpGyK+my5UCUSfumkoJHn974d3pCpvkln+MLEx\nc57Xh4wkgBv1vW0rJY+MxdmArnPMGwHenHAqYMQQNPrmRf7Fyzgm9vD/QMLl\nU5qITKHfgNzDw+sQKL5h/6mLKEpcvPSEV4XNPzUcqWKjFd7KZfi7Ra2ptu78\ng2vetMajqPmgXdFwWIDa8cM568IA5i4HnjPt13gfKlLlYjiJWVFmMzivm+CH\nS4FVQMyf0tqPv0f0nQ/QUtpcloqqWrUcu/xb5Eaq19nvD43PyUSOiG1vLAHc\n9ZcUW4jPNRo1VYhhDkPUT2NsKscVKY5wrdIZ48AJUJUJ59TKNcVQXlJ93pAQ\n8bxbFvvMlL+U/zI522eDlNwWpnOnv/4T8Wn9M+BsRaR+kCegYQdowjEOmExz\nqOnJRMlByTmH6lku9XnMciVs7+FRF72p/N4qJYXRjUUvbPzue/CJ9SNBfTtr\nJZKwIrWkDBRwdIyfmzlauMS7AHwxhLnMSKTNAhAFgDTIHQcSY0LiCFGPwgxo\nkRJ+1CPSDTNRUh+0ZjVsztsA4tSTlaln2c1GU9qdeBjyUbzHCZcBZhQIUZe9\nLm8N\r\n=bmD7\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c7c9f83aa739aaf557a65c30734ba5dfa792c59f","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [[ -z \"$CI\" && -d \".git\" ]]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.37","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.35","@balena/jellyfish-logger":"0.0.165","@balena/jellyfish-metrics":"0.0.192","@balena/jellyfish-environment":"^2.3.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.4","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.62","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.159-fulltext-search-string-arrays-c7c9f83aa739aaf557a65c30734ba5dfa792c59f_1607738760507_0.0055332101311627024","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.1-renovate-non-major-45fa1fe4a3c55f6be9aa665bbc2aea9662b8cd4c":{"name":"@balena/jellyfish-core","version":"0.2.1-renovate-non-major-45fa1fe4a3c55f6be9aa665bbc2aea9662b8cd4c","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.1-renovate-non-major-45fa1fe4a3c55f6be9aa665bbc2aea9662b8cd4c","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4df79811fc2f9fb000a95debb42066503de3d8dc","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.1-renovate-non-major-45fa1fe4a3c55f6be9aa665bbc2aea9662b8cd4c.tgz","fileCount":95,"integrity":"sha512-6GbVSGOId239Cl/PxNl9JigZJqKSibHDvtMitGGkmlE1blKTEPpOOCfQyXq5c5E/lB9QGyYwU+JSuFZuoa9djQ==","signatures":[{"sig":"MEUCIQCBxZDARt37QOrJl9A1oOmLc8To25w+txztCV91wj2esgIgBA5TDgXPImTcK6HTXA2dACnFIcmn3WTNg01cF44Dbv0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":574008,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf1EfmCRA9TVsSAnZWagAAzxIP/3tMdfI0s58F3pS3KpU2\nM0533jx91B6im8mCSJ3HukTgzTfFWJvNaVScpRN7kYwUELnB8+MJtR/89eWa\nB8IE1boMUm89b+5DkRjomFxxYyDBDwFZtm1aPmeE9UOQms6PJatVpD8XHe/F\n4eyyS4LOizVZ2EhtU4tMpzhc2nvzh3IL243bG1R/GY4j9ZGBGAdSBlFVJMyt\nTX32Lmui0N9rNw3e+3nGPzzKouuBkKX3lSvah3be45KHy8OmdBfVsG9m6TBX\nf4VhE8u5/i1shQEqVpN8saDWOFNcxuQWy/lEkZk1NoCpCItjjpSY5cZUC0aM\nn+N7UyjSGYyVwL4cAC/GmBnhD8qitu1/EbGuDUBTSXHsR6YNV4jyNOgIt+26\nzGct+Y9CDDQ8VylUxuGYmONQs7AiNJNT2Laua7HARq3Ly34l/ukrHw2EEZ6O\n3nCQhN9V53OATh6VDP3BaNVvfsPJZNoG4SgE827WqgNcKYDfZydmcEdFUt9J\nXDRrJtk0TBwNnzZ4AlKC4Tsrj1hudAvAA4nJlkpPeBkBlYYu1HWfOQB31/Wa\nh4Jq7Xv4gESEQQPiYUsdLMg8TWO8M2mw2JhIfwS1BOEYzOn1Gvm+fpfBV/xc\ngD7vAcCysGDEMgEkSIjDIDyHCmLSV08VQ49R0jkgO++n8X6WXF7jfqDr1fSJ\nfEfh\r\n=YCeb\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=v14.15.1"},"gitHead":"45fa1fe4a3c55f6be9aa665bbc2aea9662b8cd4c","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [[ -z \"$CI\" && -d \".git\" ]]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.1","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.39","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.37","@balena/jellyfish-logger":"0.0.166","@balena/jellyfish-metrics":"0.0.194","@balena/jellyfish-environment":"^2.3.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.62","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.1-renovate-non-major-45fa1fe4a3c55f6be9aa665bbc2aea9662b8cd4c_1607747557915_0.1270134953155817","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.1-renovate-ignore-node-65985b6a3e96e1efac69e9f59996de84c5c042f0":{"name":"@balena/jellyfish-core","version":"0.2.1-renovate-ignore-node-65985b6a3e96e1efac69e9f59996de84c5c042f0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.1-renovate-ignore-node-65985b6a3e96e1efac69e9f59996de84c5c042f0","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1a0141b4f972a492f5a001b85b7f5a90696c1e2b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.1-renovate-ignore-node-65985b6a3e96e1efac69e9f59996de84c5c042f0.tgz","fileCount":95,"integrity":"sha512-JI6YrmfftNCO2vXuh9Vm3eiJUXwKZn4Bwf1wJoTGzQP8y3x6c/NhAmyOIJIohd1+42y9vUD3TWBORrwZmEN4iQ==","signatures":[{"sig":"MEYCIQCpSsNjnKqaAtvYHgmLnOnV6e67CGOMZRlMLQCm4JlozwIhAPvRgVr10amis/CkSJ/oEIxnr2Qp710sE+UNxvYX7pgg","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":574054,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf1HhuCRA9TVsSAnZWagAAzggQAKMkKEQv3J3S7beUfzxW\nMytWXC0CZx8H57apCZKwg+f36HCQcVWieNZum9pngXfN1OyntVbVAjDiGbSm\n/GJMajR7TtnkfHabQDBJSVu/9xrVOHUkW0IAuzyYa3BPsPDxd2G52rckgqix\nuXvlW9x8qf6SBexufHcKp9Q3aFUxm3po6qzqbsnos7/Selzeiuari67/zs2u\nAKcsvNmQf2hBlWfZleuEJeasTE7P3l5c77vBtYyMXxTlmb6Ij5ElkHFrwBby\nNg9TwaBs1lnfimlSdaOVOythLHz1DhQk0xZ7bqwEs6BYpu5D7eRP3hUlikR3\ne0gYqKCv/7iKnLzNCo8fM/p000CydK04lGgjWZha/joVrUTXZfUU/j9I1oRn\no1y6pqZ588LGmUw8DHBsNuAAmfZEnfhf/gO+heo2iPbYvN4NaNeQJxaPM5zq\nTebxpHoSIwBbWmoMOTY2xO0ysxb+6uydjv3V0F4+Uc9g+54MB7laqjd+rDPq\nkb/9Nq6csd33UHeRlr0grXY15QrCY1F7vtVR/Y2MLHkUfZ+2HKdaKlbV2MPL\nGMrNAcbN0SY7A2bHES0HubMoKLrE9JIwHdFRYOjBFKsRkHNamTQQwKUz8WUF\norx/CGnAxOWLFVjOvaLYDl/GHGgkermWKQG+hPVWcSEasKVdf0JBs3ioGw1Z\ngbo6\r\n=QgA4\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"65985b6a3e96e1efac69e9f59996de84c5c042f0","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [[ -z \"$CI\" && -d \".git\" ]]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.37","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.35","@balena/jellyfish-logger":"0.0.165","@balena/jellyfish-metrics":"0.0.192","@balena/jellyfish-environment":"^2.3.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.4","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.62","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.1-renovate-ignore-node-65985b6a3e96e1efac69e9f59996de84c5c042f0_1607759981937_0.4014118412045986","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.1":{"name":"@balena/jellyfish-core","version":"0.2.1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.1","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"adadf07d73228f2fefe7a67cda1b45eeb02f3538","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.1.tgz","fileCount":95,"integrity":"sha512-6sBU9PP+pUGJQlvJ7PRFOVjKOxTQgAf2vZG7tTBpa7NYziegOFAEroSQVE7b3srMm61RxJKnOtr1M6njASDBEQ==","signatures":[{"sig":"MEUCICL19m1T8jQe73YTAJD0NGrFUDKTvkJLiruMsw9GKGdTAiEAzGcQOE4WQr4zMDtiu9Yxi8QWOfdmO60FL9T4kg+5q5g=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":573992,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf1HoICRA9TVsSAnZWagAAbg8P/18ilXm8xaAUbZXUxUsE\nwB8Xv2WFuK2CGAH4rv7fdPyxl3X2Pit2SK+n79yQO0V23Sl3PbGtx66JiDXp\nIRVJ0CWiuCMi3ZfpZSlo3RH5aGUr8SNC9NDqamDbPsqyg9Kr9DDlRvKVpoO9\nmkEllmbiZNHui/WaJlxxUS+Lgh29upMWGEMN+P8iK/gfsUPxqLmFdNCZWq4B\n5HUWuxWsp/U5VOxju9aEu+kIMyoOZSdL0Vs3WGpgG3VKv4yMNLbCO0jbbtto\nrVxNbnrSu5iIYQAWTL4sMkBcmypacrRWuTQW2xEPEkYTFSKiGaYtRUQE3vp3\nE5XRAEtfGUat7lK1yVGzAEz4vrVYZSDOd9MJzpYxbp9bT8ydVPNcQtK93GKV\nhsUT06hZKPQL2ZEySDrDeASFRMU7kMKhO2Z+LUWG5HSipK5KnIytdfd8r9Rn\nZ1eAZaiBgLL5Er6OqUUnossPFQNJrC9Bv1ESYaUTaVcuKUNvPzsNpyAhJq8X\nji4+uHBSNmbSYVZ0Ma2UTfy+p1lnMvpNL4UAVGohaYv803JQDa4hyVCpfaDw\nkZ3pcVRpZbgL7WMFcRy4Pkw7YwjLaerkhHwraQiTtSaKwydI7awdpTF71XQ5\nYMk/wZV+oVqBkf+/V9WIBTgHr2wu4RpkIm2hHbg47/II3VpfhLC2ue1zlPcr\n7TRU\r\n=aWKA\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f791c4d2b7513f4f17c9f1ca2ffb3493396965eb","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [[ -z \"$CI\" && -d \".git\" ]]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.37","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.35","@balena/jellyfish-logger":"0.0.165","@balena/jellyfish-metrics":"0.0.192","@balena/jellyfish-environment":"^2.3.5"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.4","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.62","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.1_1607760391899_0.13078536958919784","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.2-renovate-non-major-e0562743476e0ad038fb49f1c3e3a4b0bd577a09":{"name":"@balena/jellyfish-core","version":"0.2.2-renovate-non-major-e0562743476e0ad038fb49f1c3e3a4b0bd577a09","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.2-renovate-non-major-e0562743476e0ad038fb49f1c3e3a4b0bd577a09","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"98dcc453b5c36290cf29dfddfd320c98ad217e22","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.2-renovate-non-major-e0562743476e0ad038fb49f1c3e3a4b0bd577a09.tgz","fileCount":95,"integrity":"sha512-uhhTADsfz7SsY8enUn81/MsUGVhVnoe9fywTCWFpM1EAZJxyzYYgnyc/2EG7rXPyY07E9Wg5ZlT41358UVC8oQ==","signatures":[{"sig":"MEUCIAh46HAJJm5IMXwIMRlTVQ2hdrHcnvL3U4XUxJ3BAB7SAiEAsXHWDH5SJtsxnSRF+ywjXZf1zCgdZHiA7wVqnWSkIgw=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":574117,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf1HuDCRA9TVsSAnZWagAAb8MQAJEc5Nu9hykx5CC5QefU\nSnTGzHl/wodzEp3nKMZlHierYggtGKtnZZTkYRwDFx7/cAsfuvCYyGMNkOtY\nCBbZEEMkOFNbVEhHzzPNocDBMAjcVCyMcnMj64yl5YHOG3jRQIxlwuAGMkSl\ntHVTksvPg9MmSFBxnGJ5X15anUmukKdLg9eWIwIhH4xIZQpBJEzyLMJvo1gZ\nGZyFDH6JkNqILI2Xx9ppHkbofWnEnp0NmoZPNNQRLEWnWWT42L/uf/F5G/Pm\n03LaEC3VYf0RM6XvDctmqu7pe/6ioIoDTS3ji3UKRqEVvsnYYwRY8LqsjpSy\n7HDd7jyFxA+hlkEOohSbzl7p0sEgJmAx0GU7sn/x0mOXSmONOFoh6tCKUzpj\nC65gr1+2kgOKccy8skn7NReNGRkNSEvZSdPSQjww9+adX5YM/Yt0M2gyCFqa\nd0Q81qEzfbXOox+MkxBbccZfreBsi0zpz/CuA7jL3IU/3ZBKJkkOPb0nkYM+\nvSPHrnHB5z21ArmZd9RWoHUg41kyINgfF8aT688yN5rTrxsnpVihyZ7jGqqq\n2ve1aaxwMtN0iCLrK0cL5uMXkFrvwyj8FgnrXqY0+0nUxWpbtzX8UEt4zO/p\niFTlfR8N6RCVuiRq03kapXDeaqLU6Q9sz7uRSXGAHxSTKmote8iYBguWa73J\nVNuM\r\n=nXoH\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e0562743476e0ad038fb49f1c3e3a4b0bd577a09","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [[ -z \"$CI\" && -d \".git\" ]]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.1","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.39","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.37","@balena/jellyfish-logger":"0.0.168","@balena/jellyfish-metrics":"0.0.194","@balena/jellyfish-environment":"^2.3.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.62","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.2-renovate-non-major-e0562743476e0ad038fb49f1c3e3a4b0bd577a09_1607760770868_0.5754348536573963","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.2-renovate-non-major-299c9c2172618958b6e12893de4dce1519a6e1eb":{"name":"@balena/jellyfish-core","version":"0.2.2-renovate-non-major-299c9c2172618958b6e12893de4dce1519a6e1eb","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.2-renovate-non-major-299c9c2172618958b6e12893de4dce1519a6e1eb","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e586dd1536d1579300fdd2b359497a8929296197","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.2-renovate-non-major-299c9c2172618958b6e12893de4dce1519a6e1eb.tgz","fileCount":95,"integrity":"sha512-GrZHqVpJe/tBBL44SAi50VWxgfJx2vqUb3KKd6SdigWvyagiU0R2/CXU/TQkaMDxLU0KLQOCwFojerekTpHI4w==","signatures":[{"sig":"MEQCIAEO+XUr7njl5p6oU3KAg3FjCx1QcotRA4fPLAEX/NfCAiB9E4OKVbOKouooO2BF18XlxGAH++9R4H9FwKzvSm8tTw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":574117,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf1HzeCRA9TVsSAnZWagAADtAP/jOSJNoG66nazfj7rafZ\nMMLV/mxH8Irjr1t3qhrgdFJTBjOHXIOYYHgxjYgbRy9/dPK82Tpblj+WiOpG\nnQMbawhcZjIhTl+BIcbZ7FHXF7tD8AD+MfyauAvX7LSXHw5Qu1vT5pTyczfk\n2c5b67mPcfU5zVo7j+M4zAZ4MK+K3NFE13g3eGpehgLqpASazwnJfYMoMlio\nSLSumEcMAnU9LfRadtZlh82n0cEN92couQMZPfAdKlbDc74lXF3kccatp1rY\nWtRnbJh/GJedRnc9xx66yb1xnYEA5F5Fx7j7cG/qEJ+SoKuKFq8bnPbqG17R\nlXZNIJsL9BUvWu22pyV2nDhcmunV0HjMJNUm4XC9TUqRT+QL2xAvCISUI6oz\nqPUl1Hei/ICiKC0p3uExQsnrL6ewaYsa4XrtcyGdK19Yh9KY//gbpPAJe3Ny\naYdnUczP5Ifn5y7FFsl+CVddDLxRTwIFNM8+AX8eXDwrclnkZhGTE3CNqrCm\nGOqL5E6cay46wftZ/YT4ZIooU9rUmcVUgK/Wna21ZNim5RN11xGv3r7zspnB\naBsWF2Cd8DhqmHB9oQ39R3nfNa+fRqOMEceEUJybnOx8SLZ/HjBUmxb40p6h\n2cAfXYkvBbL3RGvSavGZTkpyo97FcMCzwiiJsztCsiQYWGxgmbNeSOABqVdD\naJt5\r\n=Xxhw\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"299c9c2172618958b6e12893de4dce1519a6e1eb","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [[ -z \"$CI\" && -d \".git\" ]]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.1","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.39","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.38","@balena/jellyfish-logger":"0.0.168","@balena/jellyfish-metrics":"0.0.194","@balena/jellyfish-environment":"^2.3.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.63","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.2-renovate-non-major-299c9c2172618958b6e12893de4dce1519a6e1eb_1607761117817_0.7227713322495883","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.2-renovate-non-major-977944ad5d97ccfd81769421c93988f832f26b6b":{"name":"@balena/jellyfish-core","version":"0.2.2-renovate-non-major-977944ad5d97ccfd81769421c93988f832f26b6b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.2-renovate-non-major-977944ad5d97ccfd81769421c93988f832f26b6b","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"7238ac8fc2006e792c504f2942169ae3f82cfc0d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.2-renovate-non-major-977944ad5d97ccfd81769421c93988f832f26b6b.tgz","fileCount":95,"integrity":"sha512-ivWRFWKxQzf7jyd5fJGS5nU92MFjciNeD5jyAERmbUC3hD+f9Rn0hvl7C4m8oaqn2b+IzVXs4xhF5BfJKQfLzA==","signatures":[{"sig":"MEUCID9kJ7RFvSqQSwyumWjwhj2RmM1wIeczSJLEbcRcbxo3AiEAwK7temURHtgGuWyiPTdh+HQ+jukJ6XDy6NLgfHzQluI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":574117,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf1H4FCRA9TVsSAnZWagAAgP8P/RVhI8JxZR2C2V5QAMBa\n7q3D3Rga10RvGlWlLX2SmCQ/XKchS4CxcZwgKq51AQSGJr1050N//MlyWaK1\nTQAX0LTb2tYlIFpRcyj1EohAROxYG8slF6VorkXxyJdpF5yaz43TJPTO22po\n0CEHLSdrSRDsAydHKHUv4Ndn/fkBbcDRwqlgrOWp6F2eGVoJojH9BQr4G8jB\nfHrwYz9M9iLhLJsr9RC5R3nzc9C4gTmdKRhfJKIv7yKHmuET+A2uact2ZluO\nLPKfPeAKb4o1yfT8x/JIHqsKhWlByT9v8ka9R9sgYNgtlUngQwo3PPKIy8ea\nwJQ2UnpKh+a/yvbXZQ3ZY0QbCrLzH5doT4F/yoSH77/dvU0s+rSqMAX/Cy+w\nuqlzbbbG3o1z85c4sVHOZL9BeJ+nRaKRiSrN9XOfucwOOYhyDGH6R5tk9sWu\nCK/bBUCrjBxx9DEgFLjjBJzOOwA02feceaxk6X40neGeOJzSyFMMINa26LOA\nxmPb1vO7rwOu0dthgPcK8mxOSdVnpKNhFFuE6g0BO0e8EXlKxOLbeStHMiK2\nAkdiuINWcVVvW1WG8XNwCQAjsErK+3Eu5eSO6PQPq/WTuzoee93JG1RnSl9p\nuHqtXp4iVAvJCiwh8SCfnZBaTXFVxs/rzE+L/PG9j/L5AdjLjb5hBdrmf6HC\nystE\r\n=uIyo\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"977944ad5d97ccfd81769421c93988f832f26b6b","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [[ -z \"$CI\" && -d \".git\" ]]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.1","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.40","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.38","@balena/jellyfish-logger":"0.0.168","@balena/jellyfish-metrics":"0.0.194","@balena/jellyfish-environment":"^2.3.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.63","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.2-renovate-non-major-977944ad5d97ccfd81769421c93988f832f26b6b_1607761413176_0.7513005230391896","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.2":{"name":"@balena/jellyfish-core","version":"0.2.2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.2","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"aced4a1fe28e30c1643aa2ca24eb04cf8d636d22","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.2.tgz","fileCount":95,"integrity":"sha512-X1fSjG83gNvNjO2aQwBs9WTu7s5Vs2BsNq71eMDqOCDuKOcrplBbSLfm9Ap7RrATNMxM33ItW9VHdPITKrfQFQ==","signatures":[{"sig":"MEUCIQDAUffrvDG3wUuxbQwS7G0m14gcmTRy6FRsGER/DiCF9gIgfQNMQGkXYNl3hDFT1FJWbvYMe6AWXrEI3DgiQ5tYmHY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":574057,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf1IBACRA9TVsSAnZWagAAaIIP/3aKkTLxmWb51RMtQqFm\ndZjH32lU08C6RDp90uHx9bGrNmijnSuvNe8oBLltuApXjHQUjorykUeTgShZ\n4k//kEhI7p00egVHwmrA5ZuWG7eAjtHr82xg13IIwA4acvJpLv4welL8L0jX\nN2kr9aMzpFueNYtWYDrGXIfW98Hfwiqi1lTa3tldJpEll+iIP3KLray799B+\n9RvtTATZ/ANSJpRp9r/zhynFQpwIOfujlV38mUYSM/83We/RRQOJzHgix0cH\nHrNR0YR4bVarnYdntLaRzvWosD4OVhdxcJtTCuJPH45p42S793nSODgivMow\nmLIaL/L90kr4NO+RJYYDwVpH0WK15UMKMCkue1AwQ8PsVoAZcsY52huZVeE1\ndwuMijYM9FHJ2vTJ0KDpgQWUAxS4B97rDjgg1aOrwOwKwZwVKzX8LhUypQ7S\n1FHZSQSZ9K2Xd1pCyf6fHeweeiRKH+gcpLyZgTQqDQr7bsUd5qAuW+JRi9qx\n2uhh8ORPpNNMsN2HiaTjPCHIGu4N18Yls+r4wRIY2CRVNylubxwZL4AiK2xW\nW2NrF6/QF2CmBzz2EX8w6rdokkNutTNnZGlp8BWFVqfaAvckqkHdU6N/YBqI\naKsWEKMpBY/QqOw8fC6Szp8WdtTZ3KIKuUlGNecTfVJXts8G1XKvMtoNXyZ+\ng3wI\r\n=/HO8\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ae908bde90b9926a76bfea4ae7afe47a6101cfc8","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [[ -z \"$CI\" && -d \".git\" ]]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.1","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.40","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.38","@balena/jellyfish-logger":"0.0.168","@balena/jellyfish-metrics":"0.0.194","@balena/jellyfish-environment":"^2.3.7"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.63","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.2_1607761982842_0.08132960301350756","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.3-renovate-non-major-4315cbb39bcac2f817a41ee5c121fda7fd7b075e":{"name":"@balena/jellyfish-core","version":"0.2.3-renovate-non-major-4315cbb39bcac2f817a41ee5c121fda7fd7b075e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.3-renovate-non-major-4315cbb39bcac2f817a41ee5c121fda7fd7b075e","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e139668e3c22fee041bec0813eb9ca77acf01070","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.3-renovate-non-major-4315cbb39bcac2f817a41ee5c121fda7fd7b075e.tgz","fileCount":95,"integrity":"sha512-3btynrr1AH0TA4FyLWfyK6k2S3mH06BhQEUXyJOGrQT1/ukgkCR9/IU6qIMXJ5Ka7R3SiaOU1geaHakkoesYBQ==","signatures":[{"sig":"MEYCIQCClF+lEpv0nenejpnLuT52i7cSpFnWEpVIeb0xpq1zkgIhAOLJvqVHd9c/B+B7toKSJgfIgpQND9xFXtMDBM8Q43lP","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":574223,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf1IELCRA9TVsSAnZWagAAYaYP/1znMUVQAqzkGnN7WpTV\n+PBHVDJV+H5KEYamHPDCc0KIsy51giekhjG/k/T1PmCGeUa3ryCRRmbNH1J4\nZLCYrKgG2h2BUVv2oC6DajpMXZKjDnzWYC8gxc4XPGK7ZcoXQH1ZeAvy0/az\n+59KUZb3UUIc3EHSOsbfNAAsH1vZgUIPZPDbo3vhp5k+2u5rMarlW3mY8AEq\n6gKxkzj5L6OMqGK3O9mWZkRYC83vsN1xD44ALGKLW8FnE4RzcDBL2CtolzpV\nl93uSR07BRTvyBNjfdIMcgLKAht2xwGve7ipgRL1E3ssGiUa9eijQS2NixYN\nCjrNUfOZpcuYus8Opacqc5QXAfYWYqZzaCxYgqygWNSPmi+4BjyYh6rbs1i3\n9u2+znF7TZHSdrx2RhxLS1H9X0FuD9AVpublNwDxqodYF3MI6thAFtymbZsI\n+oEwDK8oPZVK7KS6FHo7Vhv4BVP1jJCDtFsq/I3HGFW84utF+tS5d/x9EsA+\nITiMWlhZGAuy0DQ1bD8gvtAYKOYBRv0B3oxnEYWXxrezRB5FhQo8+300lEF8\nTXJk34H/tWKC0UWhkplcJ6E/kfwcqgbRUCxuIWK/sFLtwV09JeFtxhOKUcKa\nty7SgS3sYARoizVR9q9WtAbSgPqM7SCA9s22g5QHtSyZP49zg3Wfl+LbZWVK\n3dZt\r\n=bm9K\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4315cbb39bcac2f817a41ee5c121fda7fd7b075e","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [[ -z \"$CI\" && -d \".git\" ]]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.1","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.40","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.38","@balena/jellyfish-logger":"0.0.168","@balena/jellyfish-metrics":"0.0.194","@balena/jellyfish-environment":"^2.3.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.63","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.3-renovate-non-major-4315cbb39bcac2f817a41ee5c121fda7fd7b075e_1607762186678_0.9842691963527777","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.3":{"name":"@balena/jellyfish-core","version":"0.2.3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.3","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"94a1406d2afda35b057caca4c29da796396fb54b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.3.tgz","fileCount":95,"integrity":"sha512-wr16692UxBdmcdGN3QcjZ1vpLBYzfmEqNwqzmwnAsa5LYtw3AaS7Qup3wBPJ13IkevRDsB1jIeVrEuFwNN6jCQ==","signatures":[{"sig":"MEUCIQCrrMF29qNf5o5FA+Dw/12k9BJCrHJhvXG2OLbcQONs5QIgKLb+36eYW27zbQJj19j0aJPzRN9RdUyJKa7ulnsIooM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":574163,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf1IL1CRA9TVsSAnZWagAAi7gP/jJmnesqeG0IREVAJlVI\nh0hn/pubXoN7Uqy2M5rz5J4n7aQ2VTRsZu+eKvQI8a2sJRiKxZa0qp5G50wi\nbjt7dqQpLqERkshGiVi10iHzeDAH03HTHYZIRlXrTs22kigSV+bmkUnhrSzU\nFenFjGsp271CP9U3AGmssBgPCOmf0hxTKe5Qs05L43ObhKEKAuJ8G8a5stIZ\nlhDqi+FBfRX6m+3dB0USEHDHUpjrqbJYAZQWqMStvZUyaKQ4Cai7ZEZiENdQ\nChg6pMr9lHPhC3sU+aiMy4+zn4dwAGAHIm8B7Ou9g7lqKOnvkvZL59UJwZe4\nmpWaUiri5ThHYZq7m1TV7WIb713PEVIXjZVF0YDfdQJoukxXfemDEMwPBamj\nzXiGtXYGGeA4cTuTprHmD9jyKdPOzGT4/hMdxtU7ZZragNk6CedWEgrP5Cb7\nEyMiCw8vKfB8wSXK/N6VuVX6SPBnzyak5Y+FWOoWd3vlhQ6mVxLehxA2jKgS\ngkhNedqbaVatUVIs4CEKrik/ZVDsoHo6sJ2BKKIHWQt9QSyh49uDGQ1kzmL8\nnfG0w251iI4beTJBM3dxGkpmnuZFKY1R4VI0FaRPQDoSi+bQs3A6w/6dba97\nKcT0MXmLj22h7kISLr++mf7H+1u1A8Yj5XbS72d3UYhTo+DZ7Ikr2AMhdCDs\n5Fl7\r\n=1ojo\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ecb456001f06effc4ededda4af42e4a59495778c","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [[ -z \"$CI\" && -d \".git\" ]]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.1","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.40","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.38","@balena/jellyfish-logger":"0.0.168","@balena/jellyfish-metrics":"0.0.194","@balena/jellyfish-environment":"^2.3.8"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.63","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.3_1607762676886_0.7904128445614873","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.4-renovate-non-major-700ab375157740ec380b660b7c499f0f455f122f":{"name":"@balena/jellyfish-core","version":"0.2.4-renovate-non-major-700ab375157740ec380b660b7c499f0f455f122f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.4-renovate-non-major-700ab375157740ec380b660b7c499f0f455f122f","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"fb903edf6b7873bc2f2749f7c68059302ccc231e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.4-renovate-non-major-700ab375157740ec380b660b7c499f0f455f122f.tgz","fileCount":95,"integrity":"sha512-S8y1vefVv17uD8HkGkUsTheGOHNUCDIWBe9inVPLRp5AHS5HwhhDlmpdtqafDmvcKm2HXL0SyuQcfkKLApDB6A==","signatures":[{"sig":"MEUCIGN8aRdpK5LggyXc+bLEuWSHB3ceN9zlYCmYZtqvy2WrAiEA3FlZ8uPuiUuJOlZVbEg6js0tIv8W9LS4SKGTAWrKdDg=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":574326,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf1IR2CRA9TVsSAnZWagAAt3QP/2L8vd9GUacuZNK05CHQ\nxPAcb/SUpJXs27kJXfmx/xYS2UwCrNg7fKZw6lj85MhhcOiiJjnvCZo2F46u\n6hFkmvkSznskeHdg6RK4V9Jxhf2DIGkqPoBe8Zz+Df1ErBQlXKOoz/GMo2Tc\n7fsxh8/2+Qz0V8eH7tTeAiHr7eHtFgrhd/sPKYbynFLxzR/fN3cduCKdYbzA\nx6+sP7hlPF9+ao8CwrY4D0KVRlVDqh6m6G0U/qTtjk15MITLjQGum+Rr3vpQ\nEtv7cEP6ZpzrtcNbFD6L9WwS/glvUoj527q3AMrgoDRjoz7xpHjaYLKDi2U/\nQa61sit4sj2cWx4X7YkblVv+nQiE2VFzyuHS1ZGrTZoi1ELWT3/7Xgufzhbk\nOnyNl0BpbJeRiYcXxpTFU9yOidSLjC/fDz9HPOofpYPNvU5eZQEejW3Lw6et\nEhLFTil49O189JXuY+OAegcr03R0IspkyxfR5QFBAlW61IqCF1KrlST1yZq+\nI/lYpaq6fJBQf/v5cPa4Le+SfT3yrZncOxaalNufV1FcjSC9LHEAZAwW6jul\nq0MyT4O5MNzGb0K4jq+biCwyaXRFAO2GlGTY39JCiUkKJH5OGwdRSAD9nITm\nt/G+0Km5w4+vzcc+lIlKrUtrT9Zr0l3FD6rp2t4TVFiNreREE/8LNAPkocbx\nBKW1\r\n=Edqo\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"700ab375157740ec380b660b7c499f0f455f122f","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [[ -z \"$CI\" && -d \".git\" ]]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.1","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.40","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.38","@balena/jellyfish-logger":"0.0.169","@balena/jellyfish-metrics":"0.0.194","@balena/jellyfish-environment":"^2.3.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.63","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.4-renovate-non-major-700ab375157740ec380b660b7c499f0f455f122f_1607763062427_0.5419439614702208","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.4-postinstall-sh-compat-2382a04170bc9289ba59d17ad7f27c3b10726286":{"name":"@balena/jellyfish-core","version":"0.2.4-postinstall-sh-compat-2382a04170bc9289ba59d17ad7f27c3b10726286","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.4-postinstall-sh-compat-2382a04170bc9289ba59d17ad7f27c3b10726286","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"456db012c34199619f57be317e2b57920685e4d5","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.4-postinstall-sh-compat-2382a04170bc9289ba59d17ad7f27c3b10726286.tgz","fileCount":95,"integrity":"sha512-bemVbQp8ED8kSWx69WGig5WZ+8grZHhWDu+hPAPY+P0LaKLxHPXsqq6Pys33YUqUfE9kPXhMHkKxp1PjkliATA==","signatures":[{"sig":"MEQCIFsZqI1itz0/vBYcB3ODZPGHPcRp1nBgNxRuEu+xyyRzAiASveoM7k8LQF8HZbFQmKAeKOIQFUmZzFpij2JEHIXTUA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":574309,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf1ISGCRA9TVsSAnZWagAAV6sP/0tevhS06MR/gbVBTIEk\navCoejiOwKiFrGNDAqMvh4N9+0tsZ5GCMl77Ip1RxxwrCxlOFvVhhX+295Fn\nT1mmbomkPwX58gom+UQtdE+4OC1j2rExONNF3iwhtomjnKLPVb57pa6LgM8A\nJHB+LvPKSOvnUMiWGMogMeuKKlU5Kbx1YvRNoCu//eDjDsFiSRsTYN1fSlcy\nn6OVeclIV6ZMHVsmsKmddm2TOZ9lJ4iq7chYvFs2OhIcSs5BuoTeBi/Zav37\n0SZX+bLaSd797lBYifvYFu+L/RuiI2HsPlWjdyJhBeXMAOXZQSKaFJ46iTeo\nTHVuuKjiwXsgRkU+Rf6Rf80DoyfTD39OTee/RVgCov9sb3Q0S95jylLmfZYw\n0+QYlikE03mAe64jQ2ng0HwXOJxld8hEAaaAq9mRAfYakdaoojiYr6B/+eUG\nzWrAfZ15MltWTSPj2Z4S9yDoM6Z9PcLVOHCAcL3R5/uf5+3Vv036JkXzD16h\ncXX3qKL1c0eIn5ygVuCgsDuFkks2wjDmHPqqPedqf4mxqwtQc8j4zsgmeQlL\nBfJXDccHfo0mhEKfbo616DfUkuPn5o35nxPymln5h8GeerirYTcwdpOiGBDr\ndnh8DCruqmydq0QDNLaCquWhSwBrhFPhu9uuRDcZbA0zPRFqVERWlEvpboC9\nMTRW\r\n=EFPw\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2382a04170bc9289ba59d17ad7f27c3b10726286","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.1","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.40","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.38","@balena/jellyfish-logger":"0.0.168","@balena/jellyfish-metrics":"0.0.194","@balena/jellyfish-environment":"^2.3.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.63","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.4-postinstall-sh-compat-2382a04170bc9289ba59d17ad7f27c3b10726286_1607763077287_0.578394743962698","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.4-renovate-non-major-687496d8b6661a79e41fd321bf5cbdb06ac323e7":{"name":"@balena/jellyfish-core","version":"0.2.4-renovate-non-major-687496d8b6661a79e41fd321bf5cbdb06ac323e7","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.4-renovate-non-major-687496d8b6661a79e41fd321bf5cbdb06ac323e7","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"19dd7be393f4aa6b6d32fbb577d85dc37a8fb2fe","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.4-renovate-non-major-687496d8b6661a79e41fd321bf5cbdb06ac323e7.tgz","fileCount":95,"integrity":"sha512-1TzAgoaVPUEZU7VATCwIrdHkaoG5/M6mMQHxf4BjhrFdkfpDjJxcDlf43Rhq++/3FAX/e3P/UZMmYSK5oaWFQA==","signatures":[{"sig":"MEYCIQCiVl377Ygaf59oaQrlkORLV8yYcWwkwiiC7Seo9ppdAwIhAMpxz5zXR5NLeQaoQhUmcktewVT6q342EsS4uD5BQku4","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":574326,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf1IWHCRA9TVsSAnZWagAARjEP/jO5oiNBZtM9LiAiwBfD\nxe/G/eVGew1RTirwyyN18ihO8IF6IvDkIyYJ1W5r5CKViUAOw0VgV1UzPMA1\nc4d2LY+eMlmeIbUMk1BRezv/ZGZzDuERcU6QxpocgpHfulpEpe5ARGBYfNWK\nDYO7DUcrVCO4J5typNML7GdQHpO4tfFSHH8gaYPwGDGiMsYbAK71vEWKq0DF\noldm9Nmq1UgY/SRLgsLPfwW7sj9O/9Vu3aEa+lRB7Obq+/nrurLBu7bDExTw\nIzmL1CfK2DtPIxIE1MCyklSWVfmwd0XWdt0Kcnk38s+CFQ2TSRpTu/vbVYff\nh+/hU5PPoS5+MbkdxhN+Rr1FFvG0LE3AwTgAQ0wX6ELQBETd7T+h2SonKRlE\nHpqIy2Kx7INlL9ZQ3pfw7ruNbQMPZW8zfXL1Ukol/3l/cp5ksLhQDySKNhi7\n5aUqXPcdAOdhOxYZbuAvwHhiAWmYncqnh6sv1cDIic+IAumx/yKmZS/tnhpE\nZ+exhCZNfIQB0iRNseGRAHNXZbhYN1O1r801vzSr8TAQmmkWacDwoeZczejn\nlA8MF27Aa2pZqOQwSeTOSBfJqteRhB/b9se0G50A9CM4xEyVda2cRfrJ3CI2\neiWc/wj54JmhGPDbQVznbzTahH05INajF3zqmMs8yCwoMjsupaGuQ1P0iQnq\nrw8A\r\n=AMrI\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"687496d8b6661a79e41fd321bf5cbdb06ac323e7","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [[ -z \"$CI\" && -d \".git\" ]]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.1","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.40","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.38","@balena/jellyfish-logger":"0.0.170","@balena/jellyfish-metrics":"0.0.194","@balena/jellyfish-environment":"^2.3.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.63","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.4-renovate-non-major-687496d8b6661a79e41fd321bf5cbdb06ac323e7_1607763334697_0.7675881962908655","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.4":{"name":"@balena/jellyfish-core","version":"0.2.4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.4","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ddfc16e780171aead27adf42d277814a045cda9e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.4.tgz","fileCount":95,"integrity":"sha512-cYrjje8VOkkymzTSIC1wDL8AO0mplDpQji4ZGZVwrclf5pxGhCdWjFog7bk+dMXvSZl1+C2n8FuXM475DtgVfw==","signatures":[{"sig":"MEUCIFJJhrEdWk/OzN+3jbV1VZejEGz13lDJD2V7EMUFhv6ZAiEA09e5jB6MX5KD0F00g7sQ0BVNNyX99mz8ZMP+1pV52NU=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":574266,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf1IZsCRA9TVsSAnZWagAAebsQAKE1NJxyICFqlRLGH4vd\ngliJHz8mqyN6dDkwvOjcSDXvfGwl0sDTvRk4e+urHtMkshlalHWWX8rXcIRf\nEIAoX+Kk3Co3NZ7EEkyQdWEjZUGI37a1Lmr2ZEY1Xbk+YJYOG+V5ADJjYGpc\nLqdTpmyjVUtZmtCDmrenqO4luI6KHPQh38/OCpGQ6SDTASQ0g5Riv/WdE7ru\nFgwkim5NIXway9ExkC1qPVqwCgSNHubhlhZ5SvyPSysPg291DcRaL18iLXtf\nVDFnHfAm8i4exnKZ45soqDZbr76xnVdEikfhbik65Y/K92hUuAaHX2zYpRc4\n4qbQ7QJKl1ogY/SxCSQ7OZJ1FlBburhCG8Wgu2W4+Y8DNJb7knMij0jfjpV7\nBmk5SD7JCQd3FJTfy1dIqf8S+Vq/KhkTyaiguTI9sjF5R/VpPvjS104XPQ9C\nF+LF4HHOBCLi8K2hF0p3IHxdMq1AgxqIK6SUN4O/8uU3wSLySPUHBHfiFSXU\nP26ZuF5RL53WPIDYlIUclP4wc3/WpTDmWOjOQGQMSDM/vLj2NuT/p7QlmHyz\nQlo6fxb4EUHQ7PaXFxzP+XbMss1VGGX91eUT19ItvGyfYmh5BKj5iszcmGbT\nWvuyDs4IjE9v/HVS/wB6fWfPHzF6qxy00itqAVRxkN1pZDaopqDyZi3KS/NQ\nzEIz\r\n=0QiD\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"67704de2426c3491edf4d20df4524cd80f4b33b2","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [[ -z \"$CI\" && -d \".git\" ]]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.1","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.40","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.38","@balena/jellyfish-logger":"0.0.170","@balena/jellyfish-metrics":"0.0.194","@balena/jellyfish-environment":"^2.3.8"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.63","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.4_1607763563848_0.4725841690614063","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.4-renovate-docker-non-major-28cf1ca6af4bd3e9eb3575b7a21dc1f6bd29cb8a":{"name":"@balena/jellyfish-core","version":"0.2.4-renovate-docker-non-major-28cf1ca6af4bd3e9eb3575b7a21dc1f6bd29cb8a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.4-renovate-docker-non-major-28cf1ca6af4bd3e9eb3575b7a21dc1f6bd29cb8a","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"372359898120dc9db7ee4b464877085c4dfb51db","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.4-renovate-docker-non-major-28cf1ca6af4bd3e9eb3575b7a21dc1f6bd29cb8a.tgz","fileCount":95,"integrity":"sha512-Car3tCxNczLmjK9JUG2E/rCYZCLcyIBO/top2O69GerLyJ14PtgD2YoxGADX/khSxTLGBUSYlqZBrT8+FePdUw==","signatures":[{"sig":"MEUCIGiv76iE8cBnMXpG1o3rBKzYJdX80w5dA7y+pegOt70aAiEArCHOjv2jyN/qPAOb6O6ym+UWE7Wjgb0M4MVXirpA9vA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":574409,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf1IadCRA9TVsSAnZWagAA4qYP/RqseVABdxnYTwKdRGK/\n7wNOzxf5gw0uTU2Uo+i/CgA5TMj4mxh8AQ1egKeRv7DERuBRZfzoTE5BIriU\n1rG3CGEXwiArdPINoZ1WzP5gxDKQVb8GVm/88QLVdO0icpOBHLuyQqdF+ey0\nPOTbxrAOtXMsIIuWfc8csE6gS00oV0QfuDPMONl7xM7OpxkVr9P+9+8ADB4e\nvPiiQAIAk75+RfjJztJAWRuA7QaS8BKnWMZNWYMt0Sl6UH7zokbVFj0vPF7d\nCL6JXhMgzfCGJ/SpqsPDDLoklQ5hwCXoR9XxnbWIuNYIRV1WQQWEo4Y0iRya\nzJs0aZnc+i3czy/2+wFWsF3QBTZOgzVr+SL6PcX0fYaF1oEQTxcFOeF0KuEF\nCrxZtSJDnUgotPmPTlJeTAULalhEei0KEBSLICFANuMXy8TD7iVb19kaYYb+\nvbNRqK3LXwuYK/7IEgKucigwS7tG6r/cfFjurMfrgHAQn13qcX6o5y+gitDM\nOzycKD0t6391WuFPpYjDwFLrLCWj67hsmatWWFjt43aOYzdwDjalRfpkixxS\nyWra+wTHK6ixHF1gW7OkxrObwBCSUVKjWF2bkZSlKwsfhx6frsbbe8NI2b4e\niQ2LcYLSGKFmByroJLs3Evc1gAE3ywoBfMcg2nc/4sSEP1czageJ2vaIJpm+\nDv5O\r\n=6jvo\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"28cf1ca6af4bd3e9eb3575b7a21dc1f6bd29cb8a","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [[ -z \"$CI\" && -d \".git\" ]]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.1","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.40","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.38","@balena/jellyfish-logger":"0.0.170","@balena/jellyfish-metrics":"0.0.194","@balena/jellyfish-environment":"^2.3.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.63","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.4-renovate-docker-non-major-28cf1ca6af4bd3e9eb3575b7a21dc1f6bd29cb8a_1607763612654_0.4567124973935215","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.5-renovate-docker-non-major-e93235f4853ff3542809f5bdbe5ddf6f13bb4d16":{"name":"@balena/jellyfish-core","version":"0.2.5-renovate-docker-non-major-e93235f4853ff3542809f5bdbe5ddf6f13bb4d16","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.5-renovate-docker-non-major-e93235f4853ff3542809f5bdbe5ddf6f13bb4d16","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1418594a398c932862fa0add61f2f39e37f29986","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.5-renovate-docker-non-major-e93235f4853ff3542809f5bdbe5ddf6f13bb4d16.tgz","fileCount":95,"integrity":"sha512-bPHCeWltVDu1xg8Tz5jEkU0YVCqWFlv5xqOQiekBbrh0JdeYD7GKot4C/jbT9yecUGPTnIBB4rljgeSPuv9RZA==","signatures":[{"sig":"MEQCIGcpl3w/GPdzmgR42b4KfGENdpzNcV7Y4NxZ74rzsubaAiBQRyMkGzQ4f5FYi4kTEBFXTvBttjeoG7XBCHexkhmm9A==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":574433,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf1IdVCRA9TVsSAnZWagAAf/8P/ixS6iCcn8ACnD6y3JVe\n36q8yiV9YwM8g8r1PWHlwfBGOGlD5DifpcQtV7L1YDtTW49UlOv0aUUPH0tC\nxtINITlwuiXvQG5Mdy9f26Of6BA2OEG47ChcYbTt8dUGr1oQIZX76CSxcDxF\nadtfpeyst2brVjprJN5EGSus+HJ8B8HE/YJok0LKF1Qgq1nbEjH/90hKtdJV\nbByoK8PRrQT0q2fcuut7HslbkDjl4W1x8LaixLTpNDMhowtHQwSs8z9g4C4i\n3Jmtz+q1E/KmnQ+F3vhmXB0mLS0H2zqOCYLs9yNnweuM2IkEwknsdciawWVx\nNt27SGNHCm0QRyTj0LufW2+5szVqEMl2CNY7x2hNaE02P/X415NVE82VeiJU\njqIrNWWi3Zt+3Vtgk1/ofcgQRu/LtSHiFwv17cWnDv/thoCWEOVZCLNExdj6\nYw0ioUZ69CSHXwQSWLFIkROn+ChxSOKkLQUtAgg3pqPpu05XW5ecAAuNiw3t\nmoFag7fUEBHl0cV4TwPhiiaFm1LEf7qFMaTfj5dXR2bfC82VrBu5bZIdYMfV\n0buOML2RRPKsAghhiObmqU0nbQzAvfr0ZIyzG8n3UWkkivfxwJuueTSFQS6u\nKFhIwMV7Ne+wOcGb0ZtpDg2/BF1n4vzHUGx7ae6AP/wqStl3hrQd1F/LuWq5\nlFW6\r\n=AHL+\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e93235f4853ff3542809f5bdbe5ddf6f13bb4d16","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [[ -z \"$CI\" && -d \".git\" ]]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.1","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.40","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.38","@balena/jellyfish-logger":"0.0.170","@balena/jellyfish-metrics":"0.0.194","@balena/jellyfish-environment":"^2.3.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.63","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.5-renovate-docker-non-major-e93235f4853ff3542809f5bdbe5ddf6f13bb4d16_1607763796601_0.13677555601088476","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.5":{"name":"@balena/jellyfish-core","version":"0.2.5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.5","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e29d429beda209565491cf2a9506b73b7565f144","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.5.tgz","fileCount":95,"integrity":"sha512-LZQfCsxOvRYFA2UUBnD4XlXYmT34Bzp86IlLcI/05St0B169OAeT2ZvDWUOkXlaVkAiLH0gRgOxtZUWStTCfAA==","signatures":[{"sig":"MEUCIQCHQHm8pyl/DedSz8CLOUN8E4hLHdRourqoi5a+I0bxgAIgamg0+HUP+eGt/XCDGOQkG4riFDKmldINBRbxc+E9AbM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":574366,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf1Ii9CRA9TVsSAnZWagAA/lwP/3GQmWZTKB7jPTN6ne17\nWxCWuC2kWVpkdhqwA2aytpa16DzA442J5XcCgfrTp/idpVWwQoQxVKbdwIB/\nrUhIWiriRLEwHGtEEAGfhVrJ7g1CDhFWrGlCJrk3nYfqDDpGjnSaUZS2yUVF\nNubVQG8CPBSAOYomsbeajYHw/ZrW8Vdcjmox3b8+c16A0YPFEp8uePgWnRnN\nwyXZIyDNMprMEsjLBwOdCH0VDvKAWR1dSnmbYwFp3IeEj2M0gTS8k4Xp167W\ntipvVC1qA3QbS2IVr+hObU7SnlxnPMN3juOaJ8UTxhqWyF1WbO/jmV9SBQ4F\nNkooV4kyP+d0tQHXwgxeux6IE6exaL6kFG5PwgNtkjQSG1VSCV6u2xFvug0k\n9xaxi7YyYjh0FYZFBWdYiVqzpWxPXJ2A44nGMmo+0990CmyNkUi6/Db+X8gT\nljxm1ulCsuQ4D0625xdtpF1iaWNg9Kh77Jmlv1V6GuGQFVlw2eVt6UefybIc\nCUMBF0Vh28thQsJhU9h8BSDEK86NmkllLGaLebk1BNiRirdBjT+ibsM2z56H\nbBlSDCMK2YeUUHmF5Qi8ppp1qSJk/2Y1wqiTtZnfAcIYpfcdObwck6/3Kefg\nGhPM6P1N0qU9aGMS0xeDFh/eTyqHQE78fQnIIg1PwbJ+v279lk1XLF92S2GL\nwnew\r\n=prJe\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"49b1e879daa3ecc0feaa99636c37998f8b206d16","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [[ -z \"$CI\" && -d \".git\" ]]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.1","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.40","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.38","@balena/jellyfish-logger":"0.0.170","@balena/jellyfish-metrics":"0.0.194","@balena/jellyfish-environment":"^2.3.8"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.63","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.5_1607764156627_0.10657638490464971","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.6-postinstall-sh-compat-91f698490b77fdc6c7a212fcddf4556eeee7ba02":{"name":"@balena/jellyfish-core","version":"0.2.6-postinstall-sh-compat-91f698490b77fdc6c7a212fcddf4556eeee7ba02","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.6-postinstall-sh-compat-91f698490b77fdc6c7a212fcddf4556eeee7ba02","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4e41d8ee13ab44482506047d81d6f604b2c1133e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.6-postinstall-sh-compat-91f698490b77fdc6c7a212fcddf4556eeee7ba02.tgz","fileCount":95,"integrity":"sha512-tBoj7dyEeJhTS+SZRY1eU+oPAQUFZ1IsCvXp98ewUnNYShTi5FIsUmXdF4EqyWQxV7KbCGNxel7/7dsY9+F9HQ==","signatures":[{"sig":"MEQCHwPw2FsMEKweVOOZd/OA3dJzk7dTcWeXedV1B+PrjqkCIQC0fMXcq1/hS9skrz8ZbrPmwJZAeNjX9gCjt9jTKOaGyA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":574512,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf1IpTCRA9TVsSAnZWagAAnRkP/iqRPtKFUGqMOpbYD5Xm\niSJ1w0nUZRR0u4EB0/K6HxAETinhcphsDot4FRXr3N86YFkUqVKejUT5WJjF\ntjusPlST3I8YXukhzkZUcPi3ii2qGU99zeJhOBKYmW4+uzVxV4Z7AdwLsnOJ\nOnlU/hqW8vwRmaPm+rV8hwVF2xWrugYWytqsunjtB5LGrz6PzF3+1kSvfxij\nhcwEtSM012BlnVN5bMlBTMt/uTZ4ElpwfO0PhjcAZyHY0r0E77duYG5jofGZ\nmLuM+CfF59Epvwa0QZtZj+MVyByA5vyUG69O0iOputn+9phreH1Yc5RlbpRC\nDRWYAumNHI+8aqYF+KFautTHYVCLaVFin+NrWXdJ9R5pQGNMbFkJx/pRDFCC\n+ixo7lCcB66HBKePv7d8o7t37fKcX1X64fvSfZJSZyOljslLPcA6PxcWgHnW\n0yaYoSCUYkELqkl15yRbri/tg9kb03W0SJgaR1SWubjHA5sfc65Ps2cku9HH\nre3co5ZABvB15IWMHe4ofII/sbt2P0ZNTCw/sOmbOimztNvzIOHl2AE9yoGI\nu2SiZzptm1T/MfHOqJx4FOtfwUnluePO+txq78Rn86DnFjVzA+BHStoBF+Xe\n2SWpRsW0kau1kf6y2PghDQWtVV4WuEF+cKS0G+5VNIo3C+WLAN4jKsOGFTRq\nBBwr\r\n=6Alr\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"91f698490b77fdc6c7a212fcddf4556eeee7ba02","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.1","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.40","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.38","@balena/jellyfish-logger":"0.0.170","@balena/jellyfish-metrics":"0.0.194","@balena/jellyfish-environment":"^2.3.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.63","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.6-postinstall-sh-compat-91f698490b77fdc6c7a212fcddf4556eeee7ba02_1607764562858_0.07802877831999244","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.6":{"name":"@balena/jellyfish-core","version":"0.2.6","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.6","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"bb67ad8aed760a0891c4ae123b012fc8dcf610b0","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.6.tgz","fileCount":95,"integrity":"sha512-x771hK038RrWhebQvlYmBiBleppBHbor4IOqRhY1DIZZaEKuRkDsRHtV7+tqinJZxMzZ9OZ4aZnH6GZIy+Lx3Q==","signatures":[{"sig":"MEUCIQCzZRXjjki9UxmTVtlo669YYmqaGtYUmMNpNTvTLu1oXwIgL0Sy0rFf/ZmBTAG5eMeYof8ODFIpZQo2tEIL64VsAcs=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":574449,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf1IuyCRA9TVsSAnZWagAAVL0P/ig/dt6qzZSXAdbuu6/M\n7ndmIRX3YvVtaaGUe/pgeYZ19sdR6UZsIIyCzl2nZG0g36lhxh8jrnjuO4te\n5VPTQzt+ILkk9N/KbSSRwhM/lSQr2LVgYt/v66jDt02Psp61mHEAw3+W9H8l\n8oOQT1DRIPutAsaK0UB2R4DXFlipJBhvlgt+s5vWkQAcFWy6VygHoHSmRu22\njWyt8U/ctuEMBAkYP+VSCg3dwYcMogiaXjkUmTzzkCOXb2wUMhfpXc0FYh9m\njPBCLI3Mryd3PJCgej8ubqSQYNi4c5ifudvKb/X/VnPCGx+z0kPr7P5C8e58\npN2qjw5oJ2DH0TPBC1+VQZLJyPi4OcqSnNd32WlTyE9s4Bgp2BUkD5aFWxSN\nyOMV6MpKdf0i4irBRW7CTD/e2X12ZotOqfXMUHPHAHih2vPqiUcUImSzOQ3T\nwMCDMvOndRVHfK/3jVj8wjXoxAr+mQfQBPwJjNFy/UnOMCPRVn1dBjSj3/rh\nX1RU5M87zyYqKv4Gxm9RB5eY8nZk5qcOUIJbYu4BPiB+3uVtDtT4rFb6m0di\n4p4GV8S/iyerJcSC0desm7FnY1ITxwSGSkCRgxMVPzivSk//ykVWej61nXTw\nEGhjVydaT6BUl6WNB8ey8ZQOYeWWxKIcFnqw4Oq2PQO4ib4uBfrfQXQSsyjJ\n4AdG\r\n=lW1I\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"854ac94e0089164489b652c0af8598638b83b60f","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.1","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.40","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.38","@balena/jellyfish-logger":"0.0.170","@balena/jellyfish-metrics":"0.0.194","@balena/jellyfish-environment":"^2.3.8"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.63","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.6_1607764914462_0.15193358959024783","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.7-fulltext-search-string-arrays-fd4ddfd0aa5a659fe5c3f777015945eac3fd4626":{"name":"@balena/jellyfish-core","version":"0.2.7-fulltext-search-string-arrays-fd4ddfd0aa5a659fe5c3f777015945eac3fd4626","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.7-fulltext-search-string-arrays-fd4ddfd0aa5a659fe5c3f777015945eac3fd4626","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3eb08335f0a372e5286b62344f6de3fcd03691a3","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.7-fulltext-search-string-arrays-fd4ddfd0aa5a659fe5c3f777015945eac3fd4626.tgz","fileCount":95,"integrity":"sha512-ihuuDLN6vSI7unlzPHwtXZtlkXsRcOHL35EoVyb90DTCOvPsx0kYQGE+W0qg+m4U91VU+01KmvbJivZ6U3xx+Q==","signatures":[{"sig":"MEUCIChJMTGOmRVh47IJEbaRvLzhQGUYKdVHNSKQEsW1wNEjAiEAvLlPa3lUFPGBGKHQZKD0U/UyopMzKEj+yGPCmrsOzsE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":573994,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf1JCSCRA9TVsSAnZWagAA4okP/3g69w5PwzMpWSesq5hm\nkWdZgWv9L5qDv4sGu0SqsZ6vlQe/pXbOscGFeUdETvQya5kNrtIdnPi2oMm8\niefVY/cq9UBtU58L9iQjXEDeuiOzutP+ViPCj5NcQH713GqYSYOdCw8WFr70\nuELsnpdtCcxvfUewu7XJsoH/0gDMcl378217oAK+uUJrucunyB6be8WFtJMs\nSGmO2dzg7L+ZpHb4tbpsbWeB4xqwb25qFgNAncf7367SGy18qnhBgg9oP/Yv\nB9B+DkM+jYx5ZsG4mGDsouHLUlbtyaSDQ5WBd3m7Xw92NLwHAkKCE2qMqU5M\nHm0NGuxWNXb4jQmB0E1Rkuc2sEvLz9WHkrQ+aCWHJjRiBvdNSIEkq8RGwU8n\nepLivwGfjhh59YEd/fSaoUW5Mz4+KWcYUqdHcBmydFgAgC0C6bbO2Nn3A98N\nGpIHbt9AaWRhqnlvBzPhNdnMh9TFTFLLZeQ9EqHE6vWCh1G982VuGIIXQ86h\nn6Qlv6QuI8u2jSWZpxmL1MuqoYk1T38x5BQOE53UZQTqR5BRfqBdc7+B8V4D\nNvkWvai/Vakok3n+yehhYppVeeS4Dw2VeknkESu2zKKKwIBaUsOOISms/llF\nk5IuL2WaVwu05L0BCRnjZbh5snKkEgc6EfO4dpgH3UmhUkvFYZP9rU+A07Id\nDBPO\r\n=edS1\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"fd4ddfd0aa5a659fe5c3f777015945eac3fd4626","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.1","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.40","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.38","@balena/jellyfish-logger":"0.0.170","@balena/jellyfish-metrics":"0.0.194","@balena/jellyfish-environment":"^2.3.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.63","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.7-fulltext-search-string-arrays-fd4ddfd0aa5a659fe5c3f777015945eac3fd4626_1607766162016_0.8396578553192167","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.7-renovate-non-major-764532366c067ebb57c935ac482a7bff36dff25c":{"name":"@balena/jellyfish-core","version":"0.2.7-renovate-non-major-764532366c067ebb57c935ac482a7bff36dff25c","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.7-renovate-non-major-764532366c067ebb57c935ac482a7bff36dff25c","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c58078abc41eae8bbbe272b9234720a394dade23","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.7-renovate-non-major-764532366c067ebb57c935ac482a7bff36dff25c.tgz","fileCount":95,"integrity":"sha512-9m8ahyBslwIZw9diOefoRmp+q2GH/takYaqroFjy5QfAIy5Nzm2ZfD9GjnrTWGRSV8LEcLjIm2J9LMPtWNXTxg==","signatures":[{"sig":"MEUCIQCkfLMMBsibpVMLaXu01bzFNBzomzcjAzY0f79mbi533gIgGrHt4+5UQEUJdf5EUVLfKgVzHFlOCfQQmD9jYM4z4y0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":574574,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf1NbMCRA9TVsSAnZWagAA5E8P/2MPlnLIi1IYqLYFTuzL\ng6z1Vrq2IHItXk2anq1cNUIIRsqbKA2WKl0Y4dHEqM1JlTGdZBd3h3OP4UV8\nrU5dgQfDoQAWdEQRFBJrsoeH5uF3DKS7TrCW+SgKg3i9WOCEN0RE2PJvL9Wp\nXB+vRSBIKmkqvnl2Ij+w7tNZZ+qFurileqreCBE7LFrD2Nh5vtiRhn+ynsBM\nVKVtsptYNoe9Hn9dijRy/UgPGaUH5RlTYcPgv56tknQ9BVIOkMmAciZVFlyI\nCsrzDi9ot+UFIh+weQmD37dQk0CGGiU4sF+0v2+jodSINHtAPtdkWjBaGuMB\nqv1lMywjEvZpCqLsuSKI6Osr8a0t/l1zQe35XbRh5f1tRFBEXcF3biH8qPHp\nGKl20e/jPmAMZrShk6ssgaeYlcKZZWgMFtF5AysS71dwwVVQr2sD16HAyALN\nrE1J4l8woPrZs7GYk9df8fbq+MU+iZGRhqzCc0cSP9ybnGUZkpJDtGsXELd9\nkcDbuNV/ZkJSlgTk9w0sxWnpmL/a7WB4+QrO4lVnyue8MN+YVvMUOntwFmaV\nEvyHF3jm/PCSZWLVPV3ePWEplnSQ6Gm6FA3S+UfDw1dkeTqyXiY3IQBCnu/L\nThro6xW65rKvJ6roJNPKNMQbJowsZoT4MyjYhiJniChyJPuFA4oRcOlHmC7M\nE5On\r\n=ZMpB\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"764532366c067ebb57c935ac482a7bff36dff25c","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.1","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.41","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.39","@balena/jellyfish-logger":"0.0.172","@balena/jellyfish-metrics":"0.0.197","@balena/jellyfish-environment":"^2.3.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.63","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.7-renovate-non-major-764532366c067ebb57c935ac482a7bff36dff25c_1607784139759_0.8215677379962891","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.7":{"name":"@balena/jellyfish-core","version":"0.2.7","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.7","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a3865530d3ed30202ea6d680a54369ae0ef85d18","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.7.tgz","fileCount":95,"integrity":"sha512-68zAq6bXXajcuYk4A3ACmyT6fngFrdjjT30D9JlglYf7+DlsYHbYoHEql3POkshRRKm9U7TOpVihltz5s60AhQ==","signatures":[{"sig":"MEYCIQC4N8ygK4HgxP+ZkAo1W9OVyAb9ZAO//RVB6FQAB/Gs9AIhAJokAyX8KJDvwZuSARDj7bbX+R1KgLnCkDCRwBy1c8fx","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":574514,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf1NfhCRA9TVsSAnZWagAAJUIP/AmJztN/7yE0D5PW1nCQ\n9e6UUOuTdojT1kQVGxFyo43J3k03nOhcSkqElJH0Y2NM4QFjz+1FIUAahyfq\nuMUk/1kWzHleRd8M7yQ8Zua2+D//LkIYPp1hDVt7vvTstUQf4vccQd0njFtb\nm6lwxpFFjrHnLvMitEOKfdW8jrezwC11tc5nidPD7BJLzWj/p3qypxbIbsYs\n+qLLD12CbqT9O8TXO3zOadAj4SQRTG2NWX0PTZ6V1Ov76ya3nEU2cPyCx7li\nMwj1YkScZADYVHnOwS1iPyVC4m2gbNAc4iqpZ5Ah5bbEIATdz6X3eqfd0OQY\nxKXfUqCeVso1zB6o5MKeHf85BjviszcRn+BE/Rmo/NoQGe9pHQ2Rb0CbGY3/\nKeqhwKZ1PkoTeDDW+pJdHKnSB2e9iOlDBnIzA4UoAZ4ZDs6I8aPrHgr6kn0F\nWbbW9ldo7PYsm4RU6edqcwfyHYHtDbrJyHZkVhT9SGSzw7srKrTqwe2dYqqu\nSuwiT+QXXQmq0BTlegL7aB8oD4cOH+FAHltC54ihb4gXSU/D1/b9mrr9OoHW\neAkFpX6WihF5kWcz6tJ7d2S3MbyuQifPhIrugsg/8qFxoJ2tyKlhgUaE8A/X\nvlL1fe0g4Chs1HxFLTasDjNVjsd65kWlWHynj0w1Uf4bH53NuJEQoNZUV9ZQ\nBIWd\r\n=hnef\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"75253896743f35716cbc5caf6617df3457658300","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.1","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.41","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.39","@balena/jellyfish-logger":"0.0.172","@balena/jellyfish-metrics":"0.0.197","@balena/jellyfish-environment":"^2.3.9"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.63","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.7_1607784416860_0.23970541816501356","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.8-renovate-non-major-d74e1813c23b3cbd51c8a250fb5074b6de8976a5":{"name":"@balena/jellyfish-core","version":"0.2.8-renovate-non-major-d74e1813c23b3cbd51c8a250fb5074b6de8976a5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.8-renovate-non-major-d74e1813c23b3cbd51c8a250fb5074b6de8976a5","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"aa0e264756961eabbec28b2610fa279832564c5a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.8-renovate-non-major-d74e1813c23b3cbd51c8a250fb5074b6de8976a5.tgz","fileCount":95,"integrity":"sha512-pMnqC2prMoxK51qMafP4+Y6lWbfJ33LWRUI0X/TthE38xDusvKgtV1+udcvD7WNsqS7yiSivQBNBKlNkbnulXw==","signatures":[{"sig":"MEUCIQCXNL506eVlnd/zFNtIwL0ibYxzTTaASSq0MG3QvXfdAAIgD2p2y/IQR2Nbi0p+2KWgVTRzMuH5W/JoqXoa0+gI0jA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":574639,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf1PHlCRA9TVsSAnZWagAAmHoQAIJe33e3dOj43OlV3p3k\n2h9Uxw9MIICzpmxqhRcEzkR7Npr0C/dRzhFQ+l/9asGU2rOqHLJ7zmZ/X4F6\n0KSNCiqzpfvG5z/LMnZoxv1UB2jYC5PUZN00gayjzPy0NrbdKmH6no0x/S5L\nT8oGeMPDy7ZPVHNkpOQpIDRJ5LUq2L1vG7yOG2regGOyINZ3sJq9Y/59a22H\nM3gCUKaTcwpGcdPdQaz5Kxi9vtAz2otWtxBO48vb/qSY8RJyvXNK05vm2yg7\n8PoM0EzwgRHEwAJoXr6soCOzRqW3D104vDcPDr3GjvSoVJchlLp9Hx2ne1Nj\nI+fNct77pan3nrngFnjZ/QMOVDuFikEEsoX5FeWqwaQunSsrd70KmBAOue8h\nvpPCFfjqWMracVLo1hM27b4rqIIbjxUnhyuqgT0dcIKbvKTk1P+14/qrenCF\nsEWEnMQ8N8J7PlJ+USEj9TXA5UH1HcJI+YDq4tdcOgSW0X1ScCyXMnYZa64O\nY2wGkS4XU8/UxFhZd0CcOGPUCyKyqBLikVfh/Ar7xNkL+q+l35Bwh4PDzsWM\n5O8al49DCFcFaeukuVBX7RPKFCrYx7SQX378NrY4h7DlVG9Gf1y7e7ze5xAT\n5F7TbRfjyjJ6YIMljNYnmiLgW8BqyvSqNylzA/AB3WgJW/o1xZUhTNvvi4kw\nsaFa\r\n=heAK\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d74e1813c23b3cbd51c8a250fb5074b6de8976a5","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.1","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.41","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.39","@balena/jellyfish-logger":"0.0.173","@balena/jellyfish-metrics":"0.0.198","@balena/jellyfish-environment":"^2.3.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.63","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.8-renovate-non-major-d74e1813c23b3cbd51c8a250fb5074b6de8976a5_1607791076924_0.6164322451901272","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.8":{"name":"@balena/jellyfish-core","version":"0.2.8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.8","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"25f04d1e692439299943d23635463a3b93097be3","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.8.tgz","fileCount":95,"integrity":"sha512-Th5stZ15a0hD2DT1AhQTr4BzH2S5z8KG+a7Dq2lg5V8iOn2o4J4KxrQSO20XxClo7+o+x+q3W5I2ily0Zq8WPQ==","signatures":[{"sig":"MEQCIALP6aOtJXqB4XXfEy6f7Gg17x09BCOOKTvEQC0yfynXAiBXcSsutVX/gvbbmX7hBS09J8MEtLNDEieqx2xdVsWSCA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":574579,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf1PN1CRA9TVsSAnZWagAAbeIP/37JPCODyYR/umdjUXeq\nnsAIyUNAcoDXY5jmjDOmkYHN7onkk5WA0FGouclljzIRfLrR79hFHRSSeyBH\nhrbaaOaOzuIZodpMFc5I9DeX9X4glbZI6P/gQ8CDbc0k0sfmJzAB5EVAFjUs\njmbtPnuM48F9FoZqwwUZBN3Kaokpbkie32OTPw+M6XV1+iXWFFUhmD79jr3E\noiWtRjTD4hMMNzFLCx0QRxKyOKqrokAkDD077zJsxQPrLKKWuM0LZF3Vo8l4\nLsva6Z9uy0nWc3CukK723+esWTEIh/nNa7QIAEvslHwtNuJLKtEbY8xO8qAs\nnXQOvqEAYHcwmmdymAeVEtGwpiCcOs/BbfJ/h+pHuj9j0W/Q1qFZjyA+vQXW\nO4TDborT91o0IC26Z6ct0RQBQnV8xiIm2vkdoZhSnxMmIfsSGznTRwTO9QYz\ndzr7t661EvuNiBJF7v2rMYHwc6pHqImHNl4Dhys2fwUvdEfAzMj+XuY/KZys\nqYHME2ee9dVDM9kNbIQf6qBhb6dXMhnWdkN96f/OREWhfgkgK/FyEZMoRNXa\nORL5JHM+C7UpnK+tRCEF4mbgozInt8KXKpfZuEXlwFqln43TYpji+o+yNiDG\nTLyv1ej/3Y8o0Ozzr2373N/fTGm/rcL1z4S/K7r/jwFQZeOD5g01OM1vpnIp\nkIRh\r\n=11eV\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c0be13edf145ab40180fc8c672c01593036e2a57","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.1","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.41","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.39","@balena/jellyfish-logger":"0.0.173","@balena/jellyfish-metrics":"0.0.198","@balena/jellyfish-environment":"^2.3.9"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.63","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.8_1607791477436_0.694313264275382","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.9-renovate-docker-non-major-6bcd3ae3f0d8539e3d1764605a2b3624f2f398d4":{"name":"@balena/jellyfish-core","version":"0.2.9-renovate-docker-non-major-6bcd3ae3f0d8539e3d1764605a2b3624f2f398d4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.9-renovate-docker-non-major-6bcd3ae3f0d8539e3d1764605a2b3624f2f398d4","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5eed0e443ef98f0eaf3d03762803d885998cf248","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.9-renovate-docker-non-major-6bcd3ae3f0d8539e3d1764605a2b3624f2f398d4.tgz","fileCount":95,"integrity":"sha512-Cl9lvBIV40ENt+nmOkD/tesXK4tPq123mxMkxQeI0HtpQ2h+scAwCNt54EkLjieEc9woEqN8J86gHJkbzHD4PA==","signatures":[{"sig":"MEYCIQCjLv3p5D7JadMVTJ4b3mRplfKxNuoFIVxzXd74FbT64gIhAIpu4bfk7wtc9xy9F+cc10HRsavm0/Sb0M9fpVKsrflB","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":574746,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf1XrSCRA9TVsSAnZWagAAeQUP/2ervJqbR22K7ukSrgE5\nITyY468rPy7/EyZbzd9WBdzKJ7/17Mh6M5XORdlZ9tbAK6axdXDGZC9Gxa+V\n32mY7pVRm6wpGLAb8kMTmcpVt0I5aXWCxWlmHVAYtpYiRdK0ycAObMvF7PQI\nvBcqj8aRlRkvyIZJfk+YoY4A8AULD19EWkICeWoLau7Tto3oo4b4EZZCDN29\nDSRCwc+4QhbFbb3NS2TsnqGjnfDryP3FnuBqNw8oljp6QfLZxUvoUKSWzOqD\n5tMoZEs5jv2Pw6yaquogRkw+NcTf/vBt3D92fFbsYysfF9abCaTPzIsTdIPI\nV6u6A8BbxuCxoshbFokCp88M7+xU78TgYz+DB3Hnj+iV+DJrpGlz+K0FU/mn\nRLffRFjnvBuExymv8ZT6UCkFTvxvlqdJFmREeVDa7cyBn7Y+GkIkQKZavaLH\nsFSbDW+rthGkmssJ7OQTH9vWornzE80qjnMXbWiM3cuuHQKcFAwH9KKkiSDC\nPNw4wa8wqeDSLIJLHvQ5/GgKa3jtYc3n4eGFcyu078v22ME/XVOTzU6lYps7\nx1L1zJEOvbpi0p0puoMB7X7L8boBJ0CNfhkRSkJQvlinzurrTF4X6Q6EZRsC\nUTDwCeZrmFgFtEyzGI27/bY00P+e4aKIdEyWZiO4T538soZOb3Jh9qeqkW9d\nUbTg\r\n=jDfe\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6bcd3ae3f0d8539e3d1764605a2b3624f2f398d4","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.1","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.41","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.39","@balena/jellyfish-logger":"0.0.173","@balena/jellyfish-metrics":"0.0.198","@balena/jellyfish-environment":"^2.3.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.63","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.9-renovate-docker-non-major-6bcd3ae3f0d8539e3d1764605a2b3624f2f398d4_1607826130034_0.4938779627327048","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.9":{"name":"@balena/jellyfish-core","version":"0.2.9","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.9","maintainers":[{"name":"page","email":"page@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f68f8a6ab20609eba9c2a5507f506617a074d853","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.9.tgz","fileCount":95,"integrity":"sha512-tjeyUNRUjLT5QZ6ShdIC2sR8MzQJVgXEwUyZQfQ+z1PyK//cBP3h1+rIlCdmMt0LIH5nvqWJvl4BZ4/qbq7PbQ==","signatures":[{"sig":"MEQCIAw2v5pe67YeFe77jz4dpVW0y9W1wOz+CsTQCkNQYBtpAiAbzZkdM8vWhJukos5eB259+vdSQKTkEeW2iBis+H2rNg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":574679,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf1XvwCRA9TVsSAnZWagAAfEwP/jSX7xcfqxL+mm3H9ahc\nNgEYtO3jNcmoWe5z5Hc/Kg5BiCKYbCAxt+mou1p0msilAfik+hKU6sEQUA2c\no2cE/RTb4ZuABOqdBg6+nrIpOWHlwMJLNazmu5P9gD4OO198vKbDiLW5nSr0\nZ9jOm3Wyni7h060bUFggIIpMi0NNNthckR9PSqaioQm9omm0IxQdVD/2rTq3\n3iiao4Rv+MAdQvSlZydy9OZu3BFzqMe7ejdJnMywal0R4p1umX8mQIc/2eY2\nMjqcTbStqfksgUs3kB1NY1stq+3o7gAGvQ/JeJNsoHoMGzE9UfzS1lKvGGk0\nldxOhWYNHXzUgJU3gPrUZzBuOVN4LQSUCcRTHJdGSftWON5jn1V0kMUGbmI/\nZXkFX2oSU1FxzOn6nWUpIkGtdnLliDncVMT3OxgdvrRVrcxSmG4SyY9kg+8N\nNq/oMj8ReTTfnBbiqrimrBwYZxnxwM/ObHcNDKJ3NmlBQEnSEmNrnFhn95SD\nv1iJVdMPDnLw8BA8jPQE/Hl0460bZYWGw9eXjh6uSdOVy1dlWB7veBU0KNSS\nPrdh9guJp8U6Y5WFGp5XOQzKeH6HpgKbVveRPgAGDWPQjz+CAF7tRgNLCqBn\n3xzZrxnmQQwvQ5MkmH55F5yhsl6Vf+EZrnDI9XsP/rhKHkVX3YYZteySVnoq\nNamH\r\n=eDCh\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"bebe077a8463f393c1c4cdb5690654ae413ed8cf","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.1","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.41","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.39","@balena/jellyfish-logger":"0.0.173","@balena/jellyfish-metrics":"0.0.198","@balena/jellyfish-environment":"^2.3.9"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.63","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.9_1607826416008_0.9319854560368499","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.10-renovate-non-major-22610d91660c32f114c52183b934a4b4a17dd269":{"name":"@balena/jellyfish-core","version":"0.2.10-renovate-non-major-22610d91660c32f114c52183b934a4b4a17dd269","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.10-renovate-non-major-22610d91660c32f114c52183b934a4b4a17dd269","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"625c0a33c8991402aed3f11a39b0c55c7a3ef327","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.10-renovate-non-major-22610d91660c32f114c52183b934a4b4a17dd269.tgz","fileCount":95,"integrity":"sha512-MEss93b+0Ofz/NdoFQkjs6Z5CBvVcnI0K0HeFPjubu/DuZAfDOPvNvoarZCPO11GSrVsdeVGyXnGdH9smrEztw==","signatures":[{"sig":"MEQCIAXxcKTLJBHrPL71qpUNPdWSAyKMHf5bt6W78c/h3faUAiB5U3bebeZx6DmkDN87vJp/Z3POt+1bYhGXnMxwd/hG0Q==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":574843,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf1mTPCRA9TVsSAnZWagAAhH8P/RK5OGPsoiuYO6qgGTkf\nL5Le+czr1xKyR+a6zYylJOcNt+/hFcH2g3jq/cs7teQdFyTeGOL3XKgjmYPf\nrqZIavZo25OIPRc6qLT54RQsYqMQTDjAxXRvD5rOPrKf2IMdkb3anPCHu95X\nDFdzqxA9WBfo61pm5W5sCHVJ+TUH9GRA5NtaepUbAuAFRDgO0R5Ff5YR6fSQ\nI6E6OXsDElOoJjQ78NTgDGvVWcpPcpMIPaLXmB4L22ySIMk7SXGk6kJT+oRB\nkI77SowQKKqjhaDcc0/seuu7WKVXkGEWOVkROMum5ZtLgJKf5IUyFVEvz6Hs\n6qmSJh4P09OiFvM+2nTwkr70nuTmJTykk/OMOLJW6E3Lu9DTBUnY5zeWfH4w\nNnxr0i0CLkG5+k/Bcedn/BuJ/eugza1Zx4LF4GJOXufupgCH9dldtiOzZMCZ\ncD+kI5LE4Cvo7R7a+5aN7Anw0uuXktASqumEp8u3WOZRuKJXZFn2vHrgdE53\nrd530TuS823bCEWVzphcScTsBZKcGdvTme2iEFe8DsiLfGkIBrpx1fhiOHI8\nxLnA/9TvKFNykbkeMOTi70CqNpYc8fG+3R4oBSMO0ymIaAPaZnFhGUJywHhQ\nDLCJzG8iH4hh08gmMdbyA//UgnWCekFYxblkegwzzqzLk5A+mCOVVfiO0XeN\nHXnA\r\n=D8ds\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"22610d91660c32f114c52183b934a4b4a17dd269","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.1","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.41","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.40","@balena/jellyfish-logger":"0.0.173","@balena/jellyfish-metrics":"0.0.198","@balena/jellyfish-environment":"^2.3.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.63","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.10-renovate-non-major-22610d91660c32f114c52183b934a4b4a17dd269_1607886030796_0.8355352222095971","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.10":{"name":"@balena/jellyfish-core","version":"0.2.10","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.10","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3795d92a98b084074924675aa9ae68122250494c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.10.tgz","fileCount":95,"integrity":"sha512-wFfO9ayGRC8GvhpHlSxHG6r+eq3qXmqs6HUGS5AN8tp6LV+gaqLN2WtFy3UkpNDKhPLNHzZNHXRZBZ+fUI7Clw==","signatures":[{"sig":"MEUCIQDAJLRJwVjQfAnfDi5FfkL6oKMR9D/le8bbVnjSE0YFmQIgDlfJXwxYGKdHn1zJ1pE9lHZglTFve9SyGBpe4VZTTeM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":574783,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf1mYcCRA9TVsSAnZWagAA1vQP/2pL+50l7KbTBep4tFMX\nLNS8a5tU54oFOrShGIPLq23oG2z30kb/1+cw/i5fbjqYFWW9EhEwY0SCm5Zc\neyBJAe8n1gIiM/xCUIBb+frS6P+j+Y8hWWMBR981eJfbtLNNv0wYk+s4thCB\nVvK21J2mgYN0f06N4+v9TFBRX69JfJyJm3r9vQhuxVDdGEK2BxRZQq0KnueG\noUxVJ3VPY8oNjbWIbH2PgCgwK3H+Z/lGuxVA9hcEutLioL4OM9cTXUqiXN6Y\nf56Mj8c4mkj/SML0/3QDc4rpqD+jZBwAzoVKksFOQESjoMQqEmDsrTsUR8Ai\nLSyrJlVDUFTGHo1TZSGgIQS1gVNxnG00Hqjz4FJLP2ut7TbaniqAOdW0vcl1\nbFba4kOj9YxIx/ZkAHVw24zV2WQ0bhnbBeRdu4a6chvTk7TF+CSXs3RAzKyx\nvvDLOzsSHs5pZ+SgDtdmYDO4FzKlATuwUxwCFaYEKIs3k8OzBcO99LWHQaLC\nT3QPoSNo8PbvNh1RGzzfN5a/TIC9GgsSoJ1D8FJtaKOqWBLrTibHP8PdJPhc\nrUOZwEidJV6CgL6Ape9v+DpEyJ+Pvo0/qKhFBxHxlaIjOjFk27dhE2qR3gWI\nJFbHRrjj7VlduaJzrm52BOz63lvNK2dZSSMnBhM4SHRZWXdHJ///9WI5fQ5G\nNd0c\r\n=ZP/u\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6c47242bb05f4a24bb0061ed06c9e1b00a9f0bb1","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.1","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.41","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.40","@balena/jellyfish-logger":"0.0.173","@balena/jellyfish-metrics":"0.0.198","@balena/jellyfish-environment":"^2.3.9"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.63","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.10_1607886364074_0.012403978051674258","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.11-renovate-non-major-4bbd691525f6f00f224dcd128a8ffdb3afec2bd1":{"name":"@balena/jellyfish-core","version":"0.2.11-renovate-non-major-4bbd691525f6f00f224dcd128a8ffdb3afec2bd1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.11-renovate-non-major-4bbd691525f6f00f224dcd128a8ffdb3afec2bd1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"72768e111f9fbafb4acddb4050f49f7829076f9a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.11-renovate-non-major-4bbd691525f6f00f224dcd128a8ffdb3afec2bd1.tgz","fileCount":95,"integrity":"sha512-GkKX/cMwgVblcjpeOTNft2TuToueMKslHn5lpWOxydVC4XaMt4+aT/Iiu7p4eiJEhHykco1RcuVgs4PL76R5iA==","signatures":[{"sig":"MEQCIBaV4G9zRU5AyW+1fzthU2SdSgqth+rtZvjJ6hUS43jTAiAH2E8qLAUH+kBCExouhWtgm0JRSchoZfn3IE46c7/BKA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":574947,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf1mbYCRA9TVsSAnZWagAArR8P/1Q/44DhM1NoptWJ+NJQ\nn1Ys9QIX8RCw4Kl2+xqkg4aUJoIhzd5jtuW6FBSk43r2YWAYfULZgqdiHw1N\nHUHIn71mv8GQqYJqUUV6+CthKI2yHpEF4G4uipd1LakDFH1shRFvOOlhU6kB\nhgFsZVP/Br9ipnjDOX2G1ak/QjesEbYhoySEpUWdi/+HBAydb8gALWviuJtx\n0gfvCMCse0Cs8ZjIH2aXwvN3Q2AmO5ZdyQn3v3xrEhoWmy9RHk0jJ0ZOHc4x\nZrlOSPlcYsq0qRM9IZB2IoQzqxp1Bz90HccJ47BtOBlqmC189gomZwJutRN4\nh7UkFxUTure/lv+ZR3/uR9UJXkzHjfqsFiICeZD1hdYlBN55k0h+EDXtpctd\nc4Vd3OuJVrZG9ycHWq8XwMTQb8TBnI28MX3wll9sRS21jmzOGj+mDPuSJR20\nVs6N2cl89NYM8VY+diLzJdpozeifUdzxOsHQnxaMk06ORUDqeAxPXeR8iVC8\nZq3gxGnE5+YIJ3P7aFzdXP2DecsEZLWrkkmYhME5mi+uXs4/+055ilkSbLZn\norHRt3ujcWQch6RgBPhBMaUDLIcO5+SN3uTl0/yc1qo48lnnr9E1VA84RTUd\nv/1sfD7lhxoYTBpqieVdUxVKbKNU8n9okWpwi/vNZIgTAYmJ/5zE0/wCo3qc\nEw3G\r\n=ohng\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4bbd691525f6f00f224dcd128a8ffdb3afec2bd1","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.1","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.41","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.40","@balena/jellyfish-logger":"0.0.174","@balena/jellyfish-metrics":"0.0.198","@balena/jellyfish-environment":"^2.3.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.63","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.11-renovate-non-major-4bbd691525f6f00f224dcd128a8ffdb3afec2bd1_1607886552178_0.5568230272110126","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.11":{"name":"@balena/jellyfish-core","version":"0.2.11","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.11","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5360f50cb6cd7cc0553a985bfa3cbcd15c6ffc4a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.11.tgz","fileCount":95,"integrity":"sha512-2WtXhR5gd37YzWI23ZWZaCfUWhfc4b8jMaa96BQ3SssFkYAhCepKpqzVGau44wBCaOIAzhNTQgFgBICQU0Sy4A==","signatures":[{"sig":"MEQCIC8+mnRpZK5X33ltK1mVKLAMAVPZ/jBN9PPUpUhg/YbUAiAsb/Pv1k9XRC61+9q0cy1bm70XFk9J09y3nRwoeiOr2Q==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":574887,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf1mg7CRA9TVsSAnZWagAAQD4P/2v/Xy71wz3fE5LdJ/M/\nikDrNKZaHjHndviLGWqYjg0WO1qtC0xPSAW7Q6fF/p/iTYxjlqhBjLf2UjDy\nppEtObxmyc9nkSDYfGwDXC+GrX3tZe+DbFYGXN1E/gEWeFnSHPS05oMQF1GT\nkmNZsXMKs/NnAKWAfiGeRBrOkV0EAR4Kd26iKOnkiZPBdPDI9gP45UmE+4mc\nVRAQmUdw0+9w5PTXra8Yk4hmRv2f8BVd8IgcKLybzZ1B4dE3lfQOTB8bRDKl\nqanG1vWldr7pWj1CYVDyCM6n0oKVSqLBag8jVGWru7Z/zK6L7zd72opo6nQA\np0Dr2DGw7a7ay0yW8sOlXmdSlL0uuB+W8SUOlntieImHjiDca9LTNnRr4dkm\nuvZzBXRTE2v8rzrdp2hmuXSeFH4nSgFVR5xryOO5n88E9VvY/N4BI02Pu4GP\n384ljgjyVHxDsaUydqa2nhtij/OUyQYTCFcXgj6mRyrAVwjM9NHTUKH45dYl\n1fvlbBnmJCnxJpxpXCn1OLfqB/DwpRFljQY6ZTjZyc0rpBd48lhpjPdjqxzd\n9p2FRPb08iCR0MDAu4Q3kbUrMWJBHZtMEWTDlRrnCws19uTR6sHrnn7li8o+\nZ6P7zPsfTOqKEFuMHEwu33B/CUzhO9MvH1FRw79eRFsjs8LlmIA4Ihb2Yvbe\nf+N7\r\n=EzMN\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2678867a6c17fc6066871b8cd0a2e0d6affa284b","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.1","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.41","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.40","@balena/jellyfish-logger":"0.0.174","@balena/jellyfish-metrics":"0.0.198","@balena/jellyfish-environment":"^2.3.9"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.63","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.11_1607886906749_0.621994155360031","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.12-renovate-non-major-d739d1489f7d419734646523e23ef2dffda109f7":{"name":"@balena/jellyfish-core","version":"0.2.12-renovate-non-major-d739d1489f7d419734646523e23ef2dffda109f7","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.12-renovate-non-major-d739d1489f7d419734646523e23ef2dffda109f7","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"67d73feb9b4936adb2322b726dae15b59db259d4","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.12-renovate-non-major-d739d1489f7d419734646523e23ef2dffda109f7.tgz","fileCount":95,"integrity":"sha512-ndFHeePmEGHiJ07bJz8tclmgjjBbuv3zxUq+xwr6y+pmKFpABx2rBn79vkxsXRJDFSLLNzwKuf7w9ZpPFS2GlQ==","signatures":[{"sig":"MEQCIE3BkqU+htunEtp96uyIedZfrwyEhoCuBdGjvfHyqPDRAiA48WMYsujZNVqozHnCA5DgVIoeutGHcoVrcAgvr439jA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":575013,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf1nwBCRA9TVsSAnZWagAA2kYQAKApvY8T3r+k8HZifdTZ\nyVtEcFWV9EMwYg8Ju7r/J0vcep991mH8SfPJkvpgQZdOHDDuhJApKAFSc4Tj\nRZeTsdL254M+CdpnjRKsV1QXuwan9vQoTdarHq04s96vl2Lk52xhlxx5HXSG\nf0FfNU9vDfrRTBwOxtuOJ5n4zJXkRwxK30kuSzbNx6pOwg/z1vcv6vNZiRzo\nwwK/VLr9GQpvV+M0fogjkdXcE09E0bn1reUtH3F2Ay+vHm7FoUp2NYCP7vzj\n7xLcL7KKqsIbU1bIWvi/b47mdnyTLADyiJWh9ihNA35WrDp4UVG9GMog31QB\nHkx0Ho+HuEZMulFRkoXcp7BM8i1BmRzUqSbnWcUJcULSZh9XfrZogP3retC7\nAaw0NydRXjPFGLmua7Y2xe1IP5qvOC386NycsyiAqS2XKToXpxpYVDNX8LQZ\nPsnunOQ8nI2a6c8ZGKFWCBxte7JyModyPKTtnkgDJ8xrmcGBSl0FXXtpxPbR\nxNPprBjqZtZv4cjXxn1q0FOFEMI0ZCQWaR7ppsOKN/jUvnOAUIQTz19TooWD\n2Aa4Vj7KYL62HTO3FPH3vyEqV7kZWjIXPrOLsH7XFJhW2KLvJ75G5gT7fs0N\nI2ODSwxqlJClgMbGhCTj6M+1uf8mg4pwB+G08jQngHVcbR+dhGoc2JRxXBXB\nF5wS\r\n=h4G9\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d739d1489f7d419734646523e23ef2dffda109f7","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.1","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.42","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.40","@balena/jellyfish-logger":"0.0.174","@balena/jellyfish-metrics":"0.0.199","@balena/jellyfish-environment":"^2.3.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.63","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.12-renovate-non-major-d739d1489f7d419734646523e23ef2dffda109f7_1607891969481_0.09585395017017273","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.12":{"name":"@balena/jellyfish-core","version":"0.2.12","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.12","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c9f977beb1327735f4c34d09abcbdada7eb90a46","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.12.tgz","fileCount":95,"integrity":"sha512-OpDWaJuoCZmsh5NRP52WxhVOVM5hA+mKW5L0uw+USwzycAHhd5s27CBZ92yUdZND2DWHa2y/vO/bPriCD2YMJQ==","signatures":[{"sig":"MEYCIQDDMzxn8viLyYleQGGJMOqBqMqguMJqji77CgwHTPGQ9AIhALZLolRKByrI6i/c3IrML12PItJBvNdM3Mpki6A3V2e5","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":574953,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf1n1GCRA9TVsSAnZWagAAV/gP/idyUkmYgqVxnUj5GWg3\nesaHt/jq0bdcPfROrtRkw6BwaArWiXxKbtHKwn2KP0oGWdlo+LINWeaGEwVJ\navWYCSum16UgjfGAubOMSwP3UdF1L77AdOwt/Ik9s8XQ5aLKljIUB+WbUPqU\nM6V2tKHsOl/XuLheXl7wmvgGnJj3E9MHiEyO00EqGkpcbNRB5XmO7hfSj1WH\naZfvqO0sINVEsGfOvWnT61M2HstXo6Hn5hvXZiErOypW2/uidck+6NdA1p6q\nlKsyT+r7mIHpk22UL8kQCg0gyla7G/9UWMunHuH7aVdyCCzgD8pztzWg4bVy\ndk2wrcp1AcPaxMUmAe72u9BGveIf73P142tsvz/84HJ2DIp5oFr7zjjzSbMS\njfKZZLwPEkdZEKW8wYEgMezLWJiFnZrkY5TNcIfGwmLSaWzAOTlPMBpbYZgP\nvwV8bE11LG6ZxC+9sFw5687SWsQ6YpWdZIYer8lA80XPwrI2tu8IHKbZdrWE\n6dLhgfzmKlCQM2Eohnfi0r+jlg0CAHzRNXaHrZe+PYF57tS/lxerXdghW0TE\njZJF0l7lg4Rz+fcswV+cun9z8UCQEBAADkITthkxIqyopQ2lKiM6q9HnK5i3\n294SRlEc5+ra/EUd8lPr6at94dp6rCQZd02zB2J1wwgOlyHD9Uk2BmwblHS+\nrEY2\r\n=r22d\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0476fb3f8bb3b75ae4becea4684287cd0bc070d0","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.1","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.42","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.40","@balena/jellyfish-logger":"0.0.174","@balena/jellyfish-metrics":"0.0.199","@balena/jellyfish-environment":"^2.3.9"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.63","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.12_1607892293869_0.6791387791186756","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.1.159-fulltext-search-string-arrays-1b1fb54ca73d3196f77dd6895f1cec3aa2fca4eb":{"name":"@balena/jellyfish-core","version":"0.1.159-fulltext-search-string-arrays-1b1fb54ca73d3196f77dd6895f1cec3aa2fca4eb","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.1.159-fulltext-search-string-arrays-1b1fb54ca73d3196f77dd6895f1cec3aa2fca4eb","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"88f8cf392b9e86ede9252a434809e11a47a681c9","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.1.159-fulltext-search-string-arrays-1b1fb54ca73d3196f77dd6895f1cec3aa2fca4eb.tgz","fileCount":95,"integrity":"sha512-5L++F/qsQBzAEiBex75OpcyYqtv0vRA1Zb0Q/pptFeJZFzzcewPV2uTA6x5XeD7GEXQ2vDDwxzEAlklcB7ldEA==","signatures":[{"sig":"MEYCIQDFk/e393RtCPXN9HYpnfBIenKG2GW0LcY5C31X1UpSawIhALC8XLypd5tbinndZKyp/koCD1wwbEgitfqqHw6FvB42","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":574450,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf14O4CRA9TVsSAnZWagAAVqEP/2KsxA2k/RUYblZvuWqe\nBFGQWCAj1uBwOxY+fy35/CA2PT+q/q7Hz471rPY8YSRGOEJ83elAfQpfKuCZ\n6/MrXGUucv+ffwL74HF+CNvQHy2aLkB5qnSRHx8jaqcCyNXcHOFtdbQgQC+Z\nyB5menKeo03CgNQQqRtonsS0LTei99gBOxcUV6lPcnslyiKPL9g1JxOXnCZM\nxQUXdslDuFQnTLHiiv320q9zSsX2XhBGwTxaCbROr7bEdOO2dCGZCySJ26mf\nqIWOwricqktCp94pCpoDFK2gFgGSrJ6z0X3IMvKGoQscMDgm/NnLb6gympGo\nh/Ll1oZXyjGTym6ZWTQKILsPkrdnr6yAnZQ6N4Z5ab2JwIHZszlHEGDtWnG9\nax2RUsXsslhk8nZ1edevMkXI+qAr9IQ9UPla2UKzCEBYcbd2TpkgQeY8dpMU\nfosxLT2RryM992G7SpCmMVYp36jovYvrwDgM5sEeHkNKr7UhJcJerqMR/b/v\na2pe2SHJCCpO6+E+T9n/iCwQEk5UGqGckyVdOEqR+lZk9xE3JtLIxoDoIXg7\nCXfQBMT99H+OJVR9D9jZRMoIkoAXLStf4eDSLDZfsKB2IpOWewmBJ17yl991\nFgwW24xd8YJKzT+wjPclJB6Nc3dJW2wOnXcglHhsvqmXypQvP0OIa5Wd1vcY\nOJ/y\r\n=FW2g\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"1b1fb54ca73d3196f77dd6895f1cec3aa2fca4eb","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [[ -z \"$CI\" && -d \".git\" ]]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.37","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.35","@balena/jellyfish-logger":"0.0.165","@balena/jellyfish-metrics":"0.0.192","@balena/jellyfish-environment":"^2.3.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.4","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.62","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.1.159-fulltext-search-string-arrays-1b1fb54ca73d3196f77dd6895f1cec3aa2fca4eb_1607959479939_0.5562527359851603","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.13-fulltext-search-string-arrays-9a8e7d3f500c0511f607f95ba611890fcb2f7d79":{"name":"@balena/jellyfish-core","version":"0.2.13-fulltext-search-string-arrays-9a8e7d3f500c0511f607f95ba611890fcb2f7d79","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.13-fulltext-search-string-arrays-9a8e7d3f500c0511f607f95ba611890fcb2f7d79","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f99c206c68fe29f42c6fb38128c3658378dfd9a5","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.13-fulltext-search-string-arrays-9a8e7d3f500c0511f607f95ba611890fcb2f7d79.tgz","fileCount":95,"integrity":"sha512-eOC7nZMfdIL+dcq2dDeRDA2jOQAXRTdtNfsDsuMSTKTWN7bphfDPwbmYpVbAPa0QZmX+aVp4y7UiHQfMUWGwcw==","signatures":[{"sig":"MEQCIFaOJ+TpQOq5DSUZ6QXaHlUPepFXHZFJW4fHc4qkuL8gAiABKh9xy8L35/pgcJFy+MVT1ynwIkx0OEk45DI38gWHRw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":574709,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf14rDCRA9TVsSAnZWagAA5doP/RFllYkzMj4hfyFILRTz\nUuPBxPlDWcKTJDCxL35+qSzcajvV6zdJ/zR3PETCrq9iGohO3AVJ5tXqesUQ\nWPJMKOAntTOreCoWjdduik2iaeLmYkUH5fSstzuDkDhXzxL/acVfpTWHMlhO\n+Y45ygDQiMqVzo0Zoi9yIYCqkr1NAGbdGLoTLceQLPD7yf9f9DkSW25fbFA5\n8BmEFbpCZijf5uoUW2nt3JZ7YM8ExsGiReSMz+CvquEadOgojd5hxOUR4w0P\n81VQsqfVsk+wQN8NLlheors2BzcfdrFdMILmrRLJEr+823DIJnUiIouOZyD0\nNQZXNPMO2h3tAFSNO0T4Xup1c6PSEUfuMfbe33Er7QMaPlgxx9Nh3L7jVdSo\nzzNALhNWHyUkP5/nva75j70cHbKy6fYcQ0U065mQHWD5XwT2/aObD44ifPTF\nAsWrByUaFPwxH7hGAPRsuyvkKN8CpqYd+SN1C7aZIrBCLa/Z7Ky7oQKCY2fg\n8R8ov8ENSBGw8FVkYkPodrWKVE8fSpMm20kyZaS5rLn8jQmxF4o/MHrzwMtf\ntLXlMaEY1WXePCf/CO8SpNmtBur1cZOIS8NGpx/f+ztkzFzPDUlXfxH83x1Q\nqNtvB3o8/VPm9sf9fh3v8gkWX0TKNAuTHQdps8jWTG7EzIhhikbD1hP689yT\nm9wc\r\n=E+PP\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"9a8e7d3f500c0511f607f95ba611890fcb2f7d79","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.1","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.42","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.40","@balena/jellyfish-logger":"0.0.174","@balena/jellyfish-metrics":"0.0.199","@balena/jellyfish-environment":"^2.3.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.63","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.13-fulltext-search-string-arrays-9a8e7d3f500c0511f607f95ba611890fcb2f7d79_1607961282713_0.24495813830707847","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.13-fulltext-search-string-arrays-58b0a6200e6e67a666e847c8590f8a66b465bc3c":{"name":"@balena/jellyfish-core","version":"0.2.13-fulltext-search-string-arrays-58b0a6200e6e67a666e847c8590f8a66b465bc3c","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.13-fulltext-search-string-arrays-58b0a6200e6e67a666e847c8590f8a66b465bc3c","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"628f00bc5bc4b8cd5b50d073dcaa2a20a10e07e0","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.13-fulltext-search-string-arrays-58b0a6200e6e67a666e847c8590f8a66b465bc3c.tgz","fileCount":95,"integrity":"sha512-16hYWS9if4mfg6p63B/zmx//1Zx2+/kKS8f1RBLc4ZQwy9sC5dpqbdHchxeL96UnJeCWnap3sOwnoJIHIrNY1w==","signatures":[{"sig":"MEQCICmo7CHAp6K7hFLIpeiE7jXEN7/7vIF+WpDWS/u6J2KbAiADQDd1SaDNjuxESiMTwIPv6SsCiaKmTBzHcUVLiIcJmA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":574505,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf2AI7CRA9TVsSAnZWagAAKykP/1iN8ZO77/sU8BXZEdqQ\nu3e1Fwf+9dfF5mt+Wn80wqmAaPzaocvzKVay2wUzsYkQ5kM232yrWszdF1Mm\npWuAWJhyxgjUGbYIFTwivfF4b3Av/zNvf5eYLWbsWV2fcn18HQVBJExbLRxf\nCxW+mV+sqHp9mffZZulkoXKCV4Ot2kUoWyfipTCert18vi3DMf3Azeorg6rM\nYQT/RXIyyWtCRG4HKgYzvD8kZlxe35xL26xja2x90mgecZ6RqORQGOUGqjb1\n2v5b3eqVgeA6tNKTPvZaMDyQC/MKgQVGM4amjvTbpKqjvLEi53N9w3MQke4c\nv6YxVELXXZonY2lfvgnF7uK8zPCYDxwswEo0JLPQuYYtt9SlmfvsLXXQqCyW\nWzcOWXSM6JuJwgsachh3yvpko3nZwLRitBGbxc5Shi/vqsX/U4xTSg25ISIS\nvp5kzthZF4YBeNGbXzcK/KeYAKET4Pc2kNGlHSEPutV+waqoBhqWyG6vdCxF\nGMwWkT7uj0/N3vipAMOb9iDjeXtVLQuZtuvR/mU36/i92bcPtE8MREfMEkn2\n3yffaw4zRdTzmtLJbEfyZm9PgT982ja5sYdMDkihkDbPNr5XkFDeGlowPif3\nvP43GgIJq/QZtJAmdERnXEtG1C5JNpKmmIdYyIoZgctbweCyKSyqz7eCoJV8\neZ2L\r\n=UoiX\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"58b0a6200e6e67a666e847c8590f8a66b465bc3c","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.1","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.42","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.40","@balena/jellyfish-logger":"0.0.174","@balena/jellyfish-metrics":"0.0.199","@balena/jellyfish-environment":"^2.3.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.8","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.63","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.13-fulltext-search-string-arrays-58b0a6200e6e67a666e847c8590f8a66b465bc3c_1607991866836_0.09689058112557625","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.13-renovate-non-major-97a04100d0197403050b09457dbc555bceaf6e24":{"name":"@balena/jellyfish-core","version":"0.2.13-renovate-non-major-97a04100d0197403050b09457dbc555bceaf6e24","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.13-renovate-non-major-97a04100d0197403050b09457dbc555bceaf6e24","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"32a1a38749f993578b46bc4d8d7f151fcba80665","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.13-renovate-non-major-97a04100d0197403050b09457dbc555bceaf6e24.tgz","fileCount":95,"integrity":"sha512-DZHYtCnW7RqiMgFQ4q+BEz443WrTDWuJ5Xq+9YyNAOTZRuJvSZPhm4AejenrNiHMG13TdtFnOXRk1b9k28AW3g==","signatures":[{"sig":"MEUCIQD7C90UCFo5DVac3IApTB5UzDVHmUq0LenPJDtY9sAFswIgH3zriTCHlgZahEbgy/k8c0C/P+z7x1EL9wC/nmakJb4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":575109,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf2E9DCRA9TVsSAnZWagAA6jIP/3OnJtzyOIPTldg1k0YS\nAIuStrFH2EqyD7jS/IfjXrpnUOmFmAqx/U4lUFokjHZvessevQsYQRNfwQdU\ntc69H4Q4/Sp+hI4a/i3sdJOY7LRJqXPun8NIdYlEd3WECyThMXAjRnbeWtjc\npUVHVjL5exlooMu/lshTQ3UKy3INGyxohI5Q/qwvabXzTTpKGUnKZyRGF6vx\nP+zQ3r1pIZyNTRpWyWNqQjkI9LNa1L6vV646LKWnaIsxxvGk74IHzgd6mk3d\nY6zinGNPzIvzuDrrVeJpSBgjzQEZqhs6IQwxGEzhqev4WJBKy+3xiG3FXXrq\n3aYCHmWV3BBB7hH0QYLh+LMGeRpuxBLm3SlVfwMqFGUG/OrnbvDwYLoaPTPQ\n8Yh5McxQnEw9TyzdL89YlkZNdRdKxK6sK/DpJ+flsKz/38TcdzTXzcZsd9YS\nW+JbtgPl5w6ML+vjiOYfnXsTL5Oz7NhV2HbrsSBJmWdQ6/HBdZIMEEJHFTds\nH+gzoHhI/hQV704h/+XB/7z94ktebCYqG30CEw7FN6my3q1gCmlOxZzfO1sI\nHhxzliM822aWzhFBjNp6TWkV45HeG9cZMJfiHbPFwLqlT9/2LMDeiWu4HWnQ\nwxYtO+sYjvQ4NX3q3oESswVcBnRUf16eXMkobWKUVSn3smIluq0+CINn7wNY\nyXVF\r\n=wJBy\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"97a04100d0197403050b09457dbc555bceaf6e24","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.1","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.42","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.40","@balena/jellyfish-logger":"0.0.174","@balena/jellyfish-metrics":"0.0.199","@balena/jellyfish-environment":"^2.3.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.9","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.63","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.13-renovate-non-major-97a04100d0197403050b09457dbc555bceaf6e24_1608011586905_0.6166096384145183","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.13-renovate-non-major-39aa6cd23f940c50c32c82e1153db99bea905067":{"name":"@balena/jellyfish-core","version":"0.2.13-renovate-non-major-39aa6cd23f940c50c32c82e1153db99bea905067","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.13-renovate-non-major-39aa6cd23f940c50c32c82e1153db99bea905067","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a82fe69f66d9d68c52f2139975b2320921abd33a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.13-renovate-non-major-39aa6cd23f940c50c32c82e1153db99bea905067.tgz","fileCount":95,"integrity":"sha512-UwksvPXPYEC2N40YgLdkj6yKbukOTR3yOx7Gqx90kNboa6J+nReEkT1w9ZYQsFhl0yOj7taOuJ8+6YNjWJ3vkw==","signatures":[{"sig":"MEUCIHRXuvBru2lOQtfieh355W9gRM4WpXz7VYpUECEwh3KhAiEA9TDHwqHweVDC1QRzs1KarJjMw+19cAtentedwpG9lls=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":575080,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf2FBRCRA9TVsSAnZWagAAI24P/16CZ0FDvkaz4Ca5aFAp\ndDizoISQqAsk4JOdvvOffvWt8YgU9UH3oLChlA+s43TMThBYav1w7gwgOriR\nHCkqJG8D2VsHvq/UV60iNhk06S4on28/DasQ09mv1I1HkjeVPF/3u/uYZ8WV\nLFr3tRbRjQ4KDU2KlHooVrbHE/bonPGXaXxA0CFVLQjYgxufGWDdUC400quj\nVdPEkTfEsVGwghW4ZcaDe+qdqP2Xdka2wjekj7pS54cURgmNZUoYyQOCsIv1\n0URxLKwPirFkMLoy1QyYZhwKFTs4L/qLxFKAj4TkOFVuhq/BWNcHfzbqrCeQ\nyDVbks1w5mfH775gnV/x6zKJLA9dSRGV6jhHDhFL0hm2V6BxW5K/GUsWV3Ow\nrX7rhCgGO16oOH8apZQtpG64OytW15mubAbfLtr1/9kR3VmP6DEVhjhTtUdE\n/k0WmRd8J2d7kQP0cylj19q1YzzPv7s18s3gk2/akPjjP+iU75y4bX8k70kX\nzHltzSd28CAcSo7XMv3P+4DN0LPECDMpr8IrLbA+uE8z0sIzhwG1ai1Nml3A\nQFlN1HxSMyIKFVm1qGfMu8Tk0tCMZWMqYVnWClk0B5YnCQqS5yLxAJz/QhwG\n58eQRjXPydURSip4b1jh36HkBehaO1jq31ZzlXE7WeEff+z8208I5eWDgI9a\nqqIy\r\n=KneR\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"39aa6cd23f940c50c32c82e1153db99bea905067","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.1","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.42","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.40","@balena/jellyfish-logger":"0.0.174","@balena/jellyfish-metrics":"0.0.199","@balena/jellyfish-environment":"^2.3.10"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.9","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.63","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.13-renovate-non-major-39aa6cd23f940c50c32c82e1153db99bea905067_1608011857006_0.10288755277940598","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.13-renovate-non-major-f1fb8d244d111aeff36dbf9e420ff2f776ceae57":{"name":"@balena/jellyfish-core","version":"0.2.13-renovate-non-major-f1fb8d244d111aeff36dbf9e420ff2f776ceae57","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.13-renovate-non-major-f1fb8d244d111aeff36dbf9e420ff2f776ceae57","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"aeee7d84f220493f9b814e753a7771c0140d38c9","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.13-renovate-non-major-f1fb8d244d111aeff36dbf9e420ff2f776ceae57.tgz","fileCount":95,"integrity":"sha512-jWaihHsyMSVYRvJKzWimMj4i5X6QivLDL/YZ81Suqb1Ud/+zB9tfyYCVhduhfi+7XNkr22vlICM4h3eU8g1UnA==","signatures":[{"sig":"MEUCIQC2X3u8/ETWy9nsrhNt4pG5/N5QQmMFk1fWcqqejz0G1wIgdjVDVpVuZ51Y+0F1/QvrTLWFd6bO5/GKi3kVpZcGGnU=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":575080,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf2FHWCRA9TVsSAnZWagAA0rkP/36+R9aPOLEARTBEF2Sl\nfCbRPnIM2S71y93+aHeLBDEN303676fWr58IcCKtOzgMinuXckyWW/D2DS2k\nGcxoG+BGF5NjYaqQ93uUyAece6d4UkbCBBvMpzzedSxDbocoCPRJVKMY7/pe\n2EAEWg0lhmIcPdcX2q9jrqqN4T33uRC6NYXHlo1PbGuHICsRg6/Pg4RsgwVJ\nZpabXeE9slQKtyjqIMI37O0bGA2m6cjlH58jQG1Ont4NZZDU4sjymAmVt7fP\n2x3tMTtaIqSF57zYeK9hyQfiCyQXvL9vrYT5fB8IPel76/UlTtdy5LYCu4WU\nKR/ttdGmFFeIDzttQ1x+lGdGzwYKG1af59GhbYRjhitfhqYhNRWNoIkWGUYH\nxiv+osH9FOseSR+XPRMFPUiVg99VLOfhytFE9RGfpxO9vyy3wgVRYfuJrPA5\nDie1Rp24FJj4VIPVex9U4QTU8gDj1tLTKjAWvwgZHYxKZpDgFJQO/+kxyQRh\nF9cM13suddaLk3JzTblHR8p8CJIeLpjQi9JPimEcY4oYIMH6/Rs5sBP2vOkG\n5Ch1Ky/2htlY04NGA77jhe3GMR1jmaOHe2Mjv5NpLlHwx+QeZ2lpb5Ji+vZ7\npIDKbteOoZF2T+GguHOzssLNqK6bvk7xKAijvZn2jqOp+Bs0rtMMOyXe4rVJ\nT6tl\r\n=pDtS\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f1fb8d244d111aeff36dbf9e420ff2f776ceae57","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.1","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.42","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.40","@balena/jellyfish-logger":"0.0.174","@balena/jellyfish-metrics":"0.0.199","@balena/jellyfish-environment":"^2.3.10"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.9","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.64","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.13-renovate-non-major-f1fb8d244d111aeff36dbf9e420ff2f776ceae57_1608012245644_0.11887278473684781","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.13":{"name":"@balena/jellyfish-core","version":"0.2.13","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.13","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c9e2fc4ad0077e3a37863db3d9e78745760b079a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.13.tgz","fileCount":95,"integrity":"sha512-SuVs0KhibvjGdEmsOryX+ob1Q6cbKVQS8vQPRpyOR2cjPeuodgQlhUU9SGnOsRU/zQsC4AR98ECrA1Y2k48RgA==","signatures":[{"sig":"MEUCIGeBagtEhHtPSBOO75NhZC6HIoqlhzDzYQKWCSHaVJ/3AiEA8HcHOGmeNIdy2yXbGInROD3IMZ5aXXiAJqvi/5mYWkI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":575020,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf2FLuCRA9TVsSAnZWagAAxSkQAJ1Zhm64hE8gbxmihb9m\nqi/xcxpiZ2t6geEyAGl8uJAXsp4lOXnvVTSLENcxZfHNmAhcgZNX8Hk314hU\n/Ux7FCIZlFRl1rTBqHN+wx/XgUPyXwZsZLP37tRWjhb2rZFiFBQn8Ji00nE6\nSmhTS0xYCPr9z/y8ifmxOSoBhBTcNGIId07XEOZSAh+JpZLqxESez10UIBZ+\n1nstsnZtPJDnZeQ0xLkLy+f21VaGHZznIlqvp5CyNLpe5hIJFlEvazyJ786J\nwdooZ8N3+bVvWSfxcyDj6/Wd4swBcyC5PMc64DgklEPRjtYIcw4qJSuQXADm\nlOwT1BuXzIKD6xRTPfiUaoZMgNo4q9yuQtbPtHjCjIOH4eujL2vJfLCsBEwW\nAxI3YALBxgDX3XMrGYdaoHEAy7Lg3JqirjxIx00OiQ9hYTUt8AMLh1se8m3N\nypj61sR4g/nomi2tTW2wbxIw+qpnP8Y45Gf7k2240Z9eTrISclCZtW5jit42\njQY5a+0v6jCYKlcoWK6HHA8I1EqAdYzx9I7jfpGJWEzWrow2xzVx4wtJy8uo\nwmniS2Hj8/ctaJhrCysPG3cD40aUA4YiopmoHN7eBHTTVjTKO9n5NiVdu2tt\n3zuvo9sHrE/Zcj/cos2tJcgDAwVRWknWZxJWVgGqDHApfX5yWme3IzE6xi8T\nwMgF\r\n=ZPkA\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2cbd67ed6799ea3d1e48bf4e29995589e741d58a","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.1","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.42","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.40","@balena/jellyfish-logger":"0.0.174","@balena/jellyfish-metrics":"0.0.199","@balena/jellyfish-environment":"^2.3.10"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.9","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.64","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.13_1608012526076_0.01218676920315831","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.14-renovate-non-major-672e56a165eba1893e75eddaf2b8072fe55c3c20":{"name":"@balena/jellyfish-core","version":"0.2.14-renovate-non-major-672e56a165eba1893e75eddaf2b8072fe55c3c20","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.14-renovate-non-major-672e56a165eba1893e75eddaf2b8072fe55c3c20","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a8d03446e3545b2ecc9ba0ca5bee67ef91ff5691","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.14-renovate-non-major-672e56a165eba1893e75eddaf2b8072fe55c3c20.tgz","fileCount":95,"integrity":"sha512-4AcYMAr/gYTbViW+/aamOLKdejpeRV/8k4MuX9czjPArlo323Z0rgFSgL4INX3jyr4dw3rniGerk5VayCe78Pw==","signatures":[{"sig":"MEYCIQCml30E2lwxY1V9eD3HWlWBEOtKJiuJBGSsL3NydmHdXwIhANZc43tCO6Reso8FbZt50SaFXu4OjgScuKtagjPumy2y","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":575185,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf2FPRCRA9TVsSAnZWagAAXJ4P/3zA+5WamzXzVCJp32hM\n094SfUdnTHzLK5oBBxKhmy0mKR6uPZot6Cx8Ls3OACke8aIsjWv+f8r3oRF5\n4eSg4OQ65taJN0aeVtI8g387jj181pgsFoZ+f9Gx3qfZ7uZsOavQclx7ODs+\nAew+Is+UrXiyCqFgD8eaAldSNBBHPT3seXlouYLrckXRvnoMVOUVJzcN2gjs\nbaUXtWr53kj/wt/elAYrfH58SKpgAmVz9kK02P5EBhlO4TY+3j1LQj9TN/W+\npnrJvzHVnlzXtawCtNIqjBpmstQRb4EOvpdwMjm45gH+/cx8pOmKsemLECNk\nvn/3ohBC/D+I7WbD1P0oUcveHeOCAD+mBfVGhCFQMJvvdvBjdUUP8rbDLnWD\nHh79w1ji9w9uR0Vd7EVx5H+Me/5zpwWYTO2qAUt3+SubsGAZn5hHcQoQ3ohT\nU/YlkAWMTjQIbBbKXfrVyahItTjqu65sBRIzuii8zUGHyttrrpq4cl4q5h2K\naYbf7bHzCJagakFcuarwtJiB4nJh3qNPQ18i/VhlyOX8FcPhD0toE30GV7c/\nr0nrQypNGzo+AwNh0WuLu9PhvEJAoQ/paUBmxkAzGA9XzyaycBfJnJWTCLnd\n83G3+2Ne9jMgRzSeRMm4kaNGLmHJnO85gqsg6w6xgUMj8Y7GdUzKYkZyOijE\naUNe\r\n=N8MY\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"672e56a165eba1893e75eddaf2b8072fe55c3c20","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.1","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.42","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.40","@balena/jellyfish-logger":"0.0.174","@balena/jellyfish-metrics":"0.0.200","@balena/jellyfish-environment":"^2.3.10"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.9","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.64","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.14-renovate-non-major-672e56a165eba1893e75eddaf2b8072fe55c3c20_1608012753455_0.20089675955462383","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.14":{"name":"@balena/jellyfish-core","version":"0.2.14","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.14","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e837e9566f343a4c912fae13feca86a2a7c7858d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.14.tgz","fileCount":95,"integrity":"sha512-SnQw/w8WCmoKyGCfP5riUqS0xhFCehK5OCgrIeJU917LDfJgC7BeVSLgm/dE9zjMGh46y63MRvF5A+Y5Op1qjg==","signatures":[{"sig":"MEUCIEIhfIm5xJPpI64B1yd8rPMDHNKjaOinUVSbQejZ4BwAAiEA5U18ucLbAd2BtiMw9f+HK5q904uS0SCdM7/25Wy38MM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":575125,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf2FVTCRA9TVsSAnZWagAA6fwP/RSUiC0sEqW15gbiNJDb\nOD2BCth/FIv2LHrSG8vG3ahArwt5LRu9Y+5F/oyQW5BowWEiLtP7qIy0N+OP\na8/2k3r6Dt8lpnhzsXv+8T9kT2Z5IOJ6BxRtj6qQcJymSTWChuZ3oHn3Bv93\nHBFXrtchHAkpeJtB3JqEYMpxfFAnf9CePf9pnKvfR8IFSU0Bv2CJ9fhPbu98\n0wjKwPA9lgtmRs6OgJc28otF0jhTudsjG1AYL/AF97npz/enuFV/fNeI3CwH\nGnER7Wbsbl1QgKUV3ZvQBIPB/rceKLMo4X6qJSpT79egU1JTVR3idBjuWnNa\nsOrqFYMjgPgLmKu4fFDhBryyzCoQ1/hO44OcH9vYVPeuNyuj01DLMvPt8MEi\nwCXU35LcaKUCSt5jONVkxChYM902lviNrtVT7U2lwBFRJRtH5L5pMtXwUwU6\n6NJWQxitHQO6sQwo9rs6/2JoYeuqdpAUwp/m1CCLefv/gi1XMXxQoLTS0BlN\nSk0kVTNhXASGGx6foqnkXHOH7A89zU2idPTsNO6jObdiPRSPUrdSVIYdQr02\nrN0/78mir2cU9J1YR/90LuYn4QDebmzfvXhYXGey6mz3la66tBSe5dzzwDVQ\n/ZlH7qpW7LhkDAISN9JW2HomdasCOeON/eBWbXOhQ0BjxjtAULgesjyFvnWs\n4HGh\r\n=5wZR\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2f8f1cb2be35306f3f4a2ccccc9cbe969d1ff629","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.1","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.42","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.40","@balena/jellyfish-logger":"0.0.174","@balena/jellyfish-metrics":"0.0.200","@balena/jellyfish-environment":"^2.3.10"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.9","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.64","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.14_1608013139424_0.023199162399818674","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.15-renovate-non-major-ce726ee1af9c1336b156add855336cbdb43c11d2":{"name":"@balena/jellyfish-core","version":"0.2.15-renovate-non-major-ce726ee1af9c1336b156add855336cbdb43c11d2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.15-renovate-non-major-ce726ee1af9c1336b156add855336cbdb43c11d2","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"30b63c79af6e1120072af445080a07b1a4f1b1af","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.15-renovate-non-major-ce726ee1af9c1336b156add855336cbdb43c11d2.tgz","fileCount":95,"integrity":"sha512-hhU4vLXjWUtOi2zo8ekTudrEEtsnvfF0JBFU3/cAyeCEq4gwi99xGmgYCwbWLRWn83BdydNWzvjq57mg6LGzOQ==","signatures":[{"sig":"MEYCIQDSnhPtF7KenDMDbBwsCfDqswMdtL9ZGGyWClotI/0QjQIhAJi+0nPqxxafPgw4j5FfEcXwuwnOM1pf3bw0Ixft57Ud","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":575289,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf2GIbCRA9TVsSAnZWagAAm1oQAJ4hBWZtrOQNVvYaiS2d\npdoDwRemK/6Mv2EzixLN7qHHg++LU3oMVD7yU1ixBM2Ry8zLElly2VOTi0zm\nIM2BlxOe2L7Akpyg8V90YIo4CP8ZUpf05+yDjBjgLJW2+d8I8ErbIe2M8CMb\nPNZa9xRGUSoTDDF01PQF+NZjFn00Ya+6LOsvHlasb+CMMio4x0ZDMC0og653\nt1tAmowdkfZBZMoqqYLvXMPjlWe61ZV1D56vAGPZd+6EX8FYSzMGCCvYeUi7\nlbOUUVC5iXNgVjjI/BqqWWhnv495pWIMGh4NfNF0z+PMKF0GKpNnHUfg2YAz\npEosRX7Ofixhdu0ZfBF6fxOIv1yRKxAetKODRDWwoFArKQoEU/CdEVhenQLb\nQ5W1OzuY+ZhBA8/DLbM+p+n0OYMUH51qoPFhlSCEDivROdcND0pWXy+x5ori\n8mk0O03KxMEdCzch/pTqik/XcIycjj0+Bpd88Z0aHAwlj8btDv6PAODMl9Gi\ntEHOLAj1glrx5TRXqFlx0gB/WWZO1BalBKHwbXMkf99B3EJoMpQKA2XvhAHs\n1pY6VGbAwexWXzjv3EMlE3VwyqdRmItI9tbdGF1suoEDf7JBXs66otQxLbNJ\nahpvetKu0onTYrhlTaqo7ZmHJIhlvn32TQfed+zIKSvBmZPOcfURZEEt35Hw\nZZc9\r\n=vgLu\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ce726ee1af9c1336b156add855336cbdb43c11d2","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.1","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.42","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.40","@balena/jellyfish-logger":"0.0.175","@balena/jellyfish-metrics":"0.0.200","@balena/jellyfish-environment":"^2.3.10"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.9","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.64","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.15-renovate-non-major-ce726ee1af9c1336b156add855336cbdb43c11d2_1608016410541_0.35882549801336583","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.15":{"name":"@balena/jellyfish-core","version":"0.2.15","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.15","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"08c446c8e934632026cfba13c244874bdd5b67a2","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.15.tgz","fileCount":95,"integrity":"sha512-7p+Uv6+f5owYGpodQL4EhmdlziBhQ53lBHiExo5dffVNegdsJh672psH6BxcW85spqOll3aSSVVPR9t6F32NMA==","signatures":[{"sig":"MEQCIHv5I3fOMHFKvm0GWLPGSazAWhxuo0SGfIB8WDOhHTzSAiBbW0nBW1TnpXPne70mO5d8yYSzAHMFRlZHhlLqUoUQQQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":575229,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf2GPHCRA9TVsSAnZWagAAgOUP/ApIcbsNBfEPD6gn/xTC\n6195EnA0L3OXWa9G02gcFUDY0TIg349bZMIEQYz5eMkeYsDZvGjx24cVQpkO\nMIHBWKIX1dBbX/aiDeRAW1U8T8fCM9iUxkxDJ2vg52V6tCjKOnxjX3W2lpKp\nSbg26Q8J8Azt98WydwSEf0KNfMMs0rcH71rbw8KJLQy63BzZqzbKIqwcQ+Q3\ne0BTAfZ5F8vzM8O+QMVx3ftLvu+MYOc/AGueqmSm3vxOYDi+0f+h8QHLPAk+\nL9CwANRqy19rNIDzr6/vWN6q2w2Tp3wVneCxdZJRQNXuEuoDNmYlE+iZgy6t\nEOqZn2xnbVVgFN6EpMJa0xWdcvLt4iu0FCRQE2Hexbm1ifUIBtPT/l2yk76D\nEV+9YLHUn+Z78RcRbR20vAKHnlJJTt0stohlqVaWP3NLzYBB2P7oq/SyTsLH\nKvvVhgMRUaxhxtU0cVew372KNe1rtwOqK/gx3e0KepuOZkO30KcU3RHq79wx\nvHNRKe0MYAg48Ei3ohE8bXxS+MBzx4avfRlYnlhSz1u0ZFCr6hMrDiLkULRF\nyOdbPI8CAU9vLG3P0pVyUAs3fXQqj+vwrB7K1rkynisbIEd4q5+jzSJKhRjT\n6ci5AqEG4qZoenUuhWgFD5HI3YcAC6hO1yWuJSOHDhYOHDAUW8+krQEb2e7t\n3YWT\r\n=rB9v\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2809a077c64111a7f8f8cc855a8a97a4abd75831","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.1","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.42","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.40","@balena/jellyfish-logger":"0.0.175","@balena/jellyfish-metrics":"0.0.200","@balena/jellyfish-environment":"^2.3.10"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.9","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.64","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.15_1608016838905_0.35797638370688545","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.16-renovate-non-major-7da54c94893b83d0d7bc5799d990f8d93b94446e":{"name":"@balena/jellyfish-core","version":"0.2.16-renovate-non-major-7da54c94893b83d0d7bc5799d990f8d93b94446e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.16-renovate-non-major-7da54c94893b83d0d7bc5799d990f8d93b94446e","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e39bbcf69fb59aa73fdd14eb076309834aba2e33","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.16-renovate-non-major-7da54c94893b83d0d7bc5799d990f8d93b94446e.tgz","fileCount":95,"integrity":"sha512-S6hUjwmUc6XYu0iw/A/lHs3EBrjan62IegdYplKO81soxTfEkRZ7L1tp50wvWvcCwbm9HcBhPmK+F9wOT5WLvA==","signatures":[{"sig":"MEQCIBonWtDqx1fX+liYJ36QiFMcyXGbhkm0ofLTTTj9dsNNAiAWx7Cwj5OBadnNty7+Xl8qlF4QIYV05zJYeaIz9bykKw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":575394,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf2HbPCRA9TVsSAnZWagAAJl4P/0SsqA0cT9sq2qSBpTS7\ny4oxJMQky4x5tC6hIJHbLaDhULHfexbOlwckbLCpEG8/O7zT5nny2YMFcKb4\n3iVDnqOgp6q8VjWwU+xo9x1uTNVU8qKtPMlkCeTRiiWg/jEQ+0hQfV3V7hqg\nSeajt4Gv6Oxa62q00O1R9ETDpSZkGoElrE2KZPr5IpbHJiGwNrivnY4rI8mY\nlZBvRsJpi10ptKu63cg2OY+t8x2D0x1LIIHJIWoPgfT+sKqMz5md6ZTQzIC0\n2N8wG4clGle3gO2Jr5SdEz+mHjyUSoJZFdcCsCG7T32Sh5lPqamvFFb6aSp7\nuzrtI+N4VuHpYQ07w44cJGyvjGBgNhbhly155ge/VYrbjiRofggc4GTia+ka\n7kqbgFEsxS/ad++661S4/jwhjJwGUOO3dCOt60USNuU+rFwa4m64cqQkSii+\nZPor7V9JuhYsVth1F5MykxwMNnb0DcZWa0eBBB7Na8s7sb7pVqZqZmi2+aCY\nt2ti+7fvRjvctXBMEc42KNjAWDYHW18KEJ9FsWKXy9M50AfJtoYPlDgBXWLY\nRCmJCvBitCjgLCQV/lW/8IYzFqGUxtFIUgYee21nPqCbEt89W2piMqfcf1e/\nyRjJHvM5Kul2vQAIDL6Kr8dOzhXhXLfUqx3D/RVJjo0xL3fycRHzqPilrEPD\neYeR\r\n=pYz9\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7da54c94893b83d0d7bc5799d990f8d93b94446e","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.1","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.42","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.40","@balena/jellyfish-logger":"0.0.175","@balena/jellyfish-metrics":"0.0.201","@balena/jellyfish-environment":"^2.3.10"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.9","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.64","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.16-renovate-non-major-7da54c94893b83d0d7bc5799d990f8d93b94446e_1608021711131_0.19114488352744607","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.16":{"name":"@balena/jellyfish-core","version":"0.2.16","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.16","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"7403d68c6ea2894b63bef9ba1619889878872659","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.16.tgz","fileCount":95,"integrity":"sha512-NS7+rwAGFfjSDsxEH0L6eiiBMMHm6iHUQDK46+GGZNmV/7/UAJAiyIm8SUR8on9IGDkgNnKPu0l9YQ8HPd3wvQ==","signatures":[{"sig":"MEQCIHg6/gL+kYoUHkT5KJYAArDDpfBtqPC54gXz4AJ11OeNAiAFXp8Eq9v/OsYSOO2fIUwnZCosgjEw33U3271/Scvcig==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":575334,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf2HguCRA9TVsSAnZWagAA2hwP/1nTeGDe8Wyj31TDGIHq\n0MVfTGiY+01ixAh8F0QnWXVPSYvdXSNJB7RywDrcqWAVca56a2nH6z/i8cti\nnv2pi6a16mTkgCmwison+gjIqNXS7tLBuFaMXNO1hHH8tuVt/BlxtJKWAm8q\nAool4OlprLQ7IiTv9hBiK3ICe/HtSSZ18SgEn1rRJoalVhSVkVsv1NeCgvM5\njdx0dmZVEh6a+oWdS3NT4brTaNh9iQIbtSTVxy6zkfPJr2/6P9U5QNc8dcTX\nDDuBv5bw87ypBttxrNt9ZB/fnOVYAqln2XWbWKioLS9P4JLQS2LjmV4t1Qds\nlQvDO1mailrS7xXPEpI0zKFS6qisMUvihs/bLZAsN8lyhqEiokfq6+ZZX1l8\ndCNotcJzdK4sXX1pVD8FGIrphf09vsdznWN5vavrA6m12xQ16IHtpvZOWzfQ\nnYaIkyKNQQIrn/A3CpHZsp4WVgtRRSwCTHdQ+wTxJvIN9mXazeN5uw5tCKCE\n6CVcU+o2JgGjrx9OOVtB636qz/fGjQySEeKtQHmViAs7Nz8RyoWeQTODq7Fq\n6PudbcO6+IGl1V3WTCmAZnfOidFtwRZ/mlarAZ2+2T0769ISKx6UTD0kU9Wu\n9ABh/2ZJ+DGgwfQASaNAs6hQzwWaxHhZIB3tV6ilUgRDLa71f2lrkdJ1IxWV\nO+RO\r\n=DSsj\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e4135c20046a39fc7579e42f300f19a9784b25b2","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.1","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.42","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.40","@balena/jellyfish-logger":"0.0.175","@balena/jellyfish-metrics":"0.0.201","@balena/jellyfish-environment":"^2.3.10"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.9","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.64","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.16_1608022062058_0.7848616156166015","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.17-renovate-non-major-c15ecebcaa790a4f655a2d73a8bd4a5dbd7674d2":{"name":"@balena/jellyfish-core","version":"0.2.17-renovate-non-major-c15ecebcaa790a4f655a2d73a8bd4a5dbd7674d2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.17-renovate-non-major-c15ecebcaa790a4f655a2d73a8bd4a5dbd7674d2","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8b2035aee4eeed4f3a245b1d52297d6c77144a44","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.17-renovate-non-major-c15ecebcaa790a4f655a2d73a8bd4a5dbd7674d2.tgz","fileCount":95,"integrity":"sha512-lDc75eOostoPPbAOHsogdIXdwiSd/yh1GX8ZBIsJKCiJRS5/ciatWHUAwZ3r3HzOABhk6p0BMhZ0PLXUtGXkEw==","signatures":[{"sig":"MEYCIQC+53NsqkIG/gov4O++eq8ZeH33XNAFUPLa2nVtuAnVUAIhAPKvZ2GZtZWUxoNy3gDcE5gJqAB1sIrcGlFZhTClhQs8","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":575498,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf2ffxCRA9TVsSAnZWagAA7hoQAJbUpK1h+WUzx7E2mDzP\nmjvNWIKeHz82tc+FpeAEs+BNffp0z5Y8eIJxPgwG/eORV/ygr6KltgRgCbbW\nx3gO0AH/+W/q8rASrg4ZxBr8eHn88FslfICDB1vY3+16MLQv22yDscRRHBen\nnLJJywCZXYObLTYwhkZerGiYUl3znSjBWxMUR88jtpIfmik5c/z/ZLAnX4xX\nTCK0mI1lrLkFhfzXG/B3xy4IYga2kA1R03q8pUOHcxldfu8jveFAsS0F/jCg\ng8m4F99DJBy5x8S4BynnF8R2zLdevJGshmUL/lNUSGvusi8E3GToswaTavp/\nkL0CBPs9jHjMgE6MSiz3lIs6x0dQUCuNqr39lwzfBr/YxwiMxLiNj/SD+vPr\nPrB/uzHFHfKxgSYYyZtd23Dwimy6iZEZ+7pUbfEsPbkoQ+l61NhzctJ6nmae\nblXZVnjqq5jEi6jeh/PWhtgLwpHjUmOiJYIZYMNPtVaN2VvTU6XDLZKFURXT\nljRpWTu8scxKz9TM1vo9n5KMbUyYs0Ie8kimHBcEXpnjl5yG+/55Ghf3xNBB\nE/fZUhmYOfbfQWTjw5rnqmPU4mIL6jaffyBjFGuPDWmRr1vsO020HQy7oaLB\nol27iDKakfRMTvX7w3RNGc4cYLqZM9O3As7czq3FzV0r1xMJ3Bq4dsKSxu7c\nm9Mu\r\n=OFJM\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c15ecebcaa790a4f655a2d73a8bd4a5dbd7674d2","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.1","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.42","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.40","@balena/jellyfish-logger":"0.0.176","@balena/jellyfish-metrics":"0.0.201","@balena/jellyfish-environment":"^2.3.10"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.9","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.64","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.17-renovate-non-major-c15ecebcaa790a4f655a2d73a8bd4a5dbd7674d2_1608120305276_0.2656252810568751","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.17-renovate-non-major-2016b24e3d2c64b797fae8a575e3cbc8a61f2f4c":{"name":"@balena/jellyfish-core","version":"0.2.17-renovate-non-major-2016b24e3d2c64b797fae8a575e3cbc8a61f2f4c","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.17-renovate-non-major-2016b24e3d2c64b797fae8a575e3cbc8a61f2f4c","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"db7b13655dc1550a347bb3e619062555d9f46c72","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.17-renovate-non-major-2016b24e3d2c64b797fae8a575e3cbc8a61f2f4c.tgz","fileCount":95,"integrity":"sha512-O46LJsaUBdMajoRorHoQR84Oct/oYTEcnwBR0i9jxP3tXWpIKnBEZFrF5+Gt++Oy0mYNeFppWFKmP+u/nADY7g==","signatures":[{"sig":"MEYCIQC/Xb5XK1emRvjzRp2/4n2M6Ot35g/kqLrWVurhSdpmHAIhAPaD/VDqWrRYlDMyMaSgJSbW5lDdoUC5bnh7k/mK18iJ","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":575460,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf2glJCRA9TVsSAnZWagAAz3AQAJ4x8g88A2wleacmlAHU\nf/jfroP3AtwRXqeMPN12wALNnI7vvyLgvfoAOiPxC4lLOd3umQE1f6c3CAQd\n33I3uKDvQeOVmm7gjieqPigm2FgMtlJMy8vAZOlWzBz2m4lUFm1qXe2FiigW\nS7WGned5hEszo79mCl6Cl+BRqsdvlqifdvdJFjudLahH0kNeyQRM9D4bUI5t\nwYOk4dQJXAFj0axOYmgecNSIdpBQzJ7FXE3BdZEanbhtttBhCKIS/pyHGmZo\n/dCe659Etk/tWDOEdvwgjLXK91FUoOliRLE4+u7WPEDRF+xY6t5BLyUMPgdR\n2xC4l1/6ctp8DO4E+/yN1gleNuiG5QZN+B4kEQqYRCF5ZRqaUCr23zFeN4+h\nexnJPMZ+CSrCoFzPUJxZyUbx+ZsHpioYXVKMJ9s4sONRmG1atONfz+JNo2VR\nKtLlzt9HiaQxpRAW1QrCL8hY8GbeBrvjno1f5jb4pNnL4GrNXYPGo8gKp0+Q\nCbkUXDIC1dftuvKLSgRUt+u2PPWd+xlE3rrgmRzYEiHlLOiZci46o+RMtvNw\nSHK8DPitttsIufw5PCy7M67lsTlfMKDun3M7o3G89sCL8+1qOQe+3X7z67hQ\n/chb3OspDlkqCAlL+gTEnSqSynIuWQbYzqf+EnmdnejfvixMESHfrMl93hWV\nZDzc\r\n=FKUT\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2016b24e3d2c64b797fae8a575e3cbc8a61f2f4c","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.1","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.42","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.40","@balena/jellyfish-logger":"0.0.176","@balena/jellyfish-metrics":"0.0.202","@balena/jellyfish-environment":"^2.3.10"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.9","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.64","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.17-renovate-non-major-2016b24e3d2c64b797fae8a575e3cbc8a61f2f4c_1608124745328_0.3677171970444062","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.17":{"name":"@balena/jellyfish-core","version":"0.2.17","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.17","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0558f90315c1586f6d95e989c60e8705fe9b9674","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.17.tgz","fileCount":95,"integrity":"sha512-Bpm7li/nLfzzw33xjtB6bA/tESUs8uIMB+6bZEETp3rcRjPiqfT/KACeehkd+u/RLHbxAxdkPG+yyZpQ0IEK0Q==","signatures":[{"sig":"MEUCIFOclZuAXQPxErHX+XmvjBaqQ/nUboYD+13r6smsGfGJAiEAkU3KgmjhY7XRhCaE/h51Rl+NCCfWd+6bDBy4DrBWGBo=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":575400,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf2gpgCRA9TVsSAnZWagAANo0QAKKSmouOOd+tdcXgxIxs\nclSD321K5/uiA3O7pd4F8fJWVSQjJblgGjXV3Hz9kJqt7ShEscDJn8TBLM3A\nvurVAGa7xLEW7k1or6gVyiDTKZmC2NAM+a6Rhr6XYagu1cAX7ZNdDW7wHffb\naYAeSecjqnDX3x/doIKkHlO+LZTcU42v8wCR0p/cQBuVBToH+/2aOC/av5Ru\nRKhAgFLb0Q0PKSXwwo/4lL1kbSLoEZxwpVUm+VMkZvCiHWM90f+TAaGgk5Lr\nEeX45gwGk6LoQEhVkQWrXTvjCyH2zsv4Bdoy4okNmP+GYJDd0sy6yeD5itvt\nkap9TRozg/UIcZLDqXePEKz7/4FoNZ2FsiScc/ACVDMhtCFrKy3Hjyv7HIVE\n6nDGfpqpok/i5bnaonURzX3tvduONbS08OZBBNp5JTbBmKpxp/D1S5k2OYM7\neYBIb28+SLbfBS0nM4FCiv9P3sTHC8kVYd7GqTAc8u3kcrZZCCZWJ9lJkYi8\n0KDD+G2dZ3aTDhZQJ/K1FhX9C3Vmdhdc33j8S59OfVRsOffo1KzAGjOHhlRh\nsJmAiBSY+UeShwaFxNYGTfxFKVBzwnmpJ/neKqNoKrTkPiw1wyJ0UBx6Kjr3\n4MoYNBvQItWgKj+yhDt7hXEjBovMtiabYDU7sFhvW2L/wNtkOLOwUPZGKC5t\nXGt2\r\n=6zK+\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"1fcd88cd7dda4583b5703de683edddea76d5ce17","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.1","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.42","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.40","@balena/jellyfish-logger":"0.0.176","@balena/jellyfish-metrics":"0.0.202","@balena/jellyfish-environment":"^2.3.10"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.9","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.64","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.17_1608125024521_0.7958017602035004","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.18-renovate-non-major-9f0139cc25c375781aa4af6424e2f9479f25b09a":{"name":"@balena/jellyfish-core","version":"0.2.18-renovate-non-major-9f0139cc25c375781aa4af6424e2f9479f25b09a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.18-renovate-non-major-9f0139cc25c375781aa4af6424e2f9479f25b09a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f7bf8c2080e55d4f881e844aa5f13fe21142fc7e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.18-renovate-non-major-9f0139cc25c375781aa4af6424e2f9479f25b09a.tgz","fileCount":95,"integrity":"sha512-QHZISC8NKTumiORKN6tcmuOaSNfWKJONM815+ywx+kXZQtwBWM8h/lSrrSKJtNNcG/QvNzAjL2YgbQ1F/FsUlQ==","signatures":[{"sig":"MEQCIAH4UhmGg3osgsjnhzBGJL00L9C+mgK0XfQdFQ/uZ9BcAiBmb5m+zcMHw6njISSXVegFQXOy/9LHg1QmYbT96CkXyQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":575526,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf29YqCRA9TVsSAnZWagAAWYYP/0jv+aIh0gxNilzNGGVe\niKlwAUJYOW872gWv9RCPvRYiOAxtwuMAWRNkcxMQYASHQQvjOGOba2LXl8w6\nA0/hHDBT0FXsYztd4BaU+Ts4h/5HS59ddjrjeaCvAkIqctxu933fuGKftbAA\nHi8YkU4kXRE+qbQuZfs4s1F2o2sTx6z3osuOop0T52tBHiZN0X1xFLynGCEQ\nvsFnurp/e1pJKsTSZ5MKsXAoWX+pvH2sjrMSVZbFSBMLfyQrhy/2/i7qBqiD\nJnFzFaPBirYhgG5famxJ61xqqh0c7RmwZoTQ3LksA5v4Yvvchmxdhxveagxx\noUlsOrhc86ThIqfTU+wdvwXPqfk7IcQHVnIJADpYdqWmEgJNz4uFchCzC+Om\nho7+uKzo+A5vmp0fvHq1GNSzvrWBG1y8HBewUirPvf3FQMjoU6JI0wzgd7zJ\nrQFnrt7hzRTOpUxiyNdPlXlpkgaPwugK5Y/QLE1fpvt42hd9e7blyeNQkJfe\njDhKdt1sNyEMLLwmAcFcG8i586TN4vcMwl7wjL7aAPYDjSaokt+19/0X+vkv\nciVWJV8S9Ei0m9oPXIJvGFx2AmYURhnc/q16NKuLV/Nam4wqFZKyUH5v2kBI\nxIs6ui9kgosUythC5Fg7qFtr8I1aqzdhJlQrr6lUlkBb6u7+jkL4ZWFd5y8r\nnlTB\r\n=Jyud\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"9f0139cc25c375781aa4af6424e2f9479f25b09a","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.1","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.42","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.40","@balena/jellyfish-logger":"0.0.177","@balena/jellyfish-metrics":"0.0.203","@balena/jellyfish-environment":"^2.3.10"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.9","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.64","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.18-renovate-non-major-9f0139cc25c375781aa4af6424e2f9479f25b09a_1608242729793_0.9153633934836343","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.18":{"name":"@balena/jellyfish-core","version":"0.2.18","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.18","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"07341717a1d43dbee50aeca08685c907a0b49693","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.18.tgz","fileCount":95,"integrity":"sha512-Wlir1G0VIysyJbIuWAN4LgXcrroghsyR1jAy1wLvn16QWK1lWCwQaHw4bhMOOYvNhf5HJ5rkaGLdvCUVeqLiLA==","signatures":[{"sig":"MEYCIQD/Xnrn/rCEABm6LNEbfY2WJJwdccPDcojIF6hgs4BhRgIhANlsrxsCa5a28Cb/teXDtZ5sSPKd/LtWqmjW6eHf5UtI","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":575466,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf29ezCRA9TVsSAnZWagAA/coQAIxugfH0Sx5hKPBV2OiY\nB0zpMYwU2zgyKm2qHavLMq809Bx80Sgxxi+iMw4bonYvm6+krBfbU1GoOLKz\nIj4upI3l8vXzs/59Q1XZBvkxdinYh6JAozTq0D0RYaI1zlukbsrvvgW+8/M6\n72t6gniyaN/cTPV7Y6Yznf6HJcdsMHkKloJN8O0gYOnJBSA+HU8D4xR6Mgeq\nDless5MxL8yYR/RYfoXb3FsoM/9EknwISHznQleyJvBTM+6twXuKjMhwRALw\nQuZhvrfZVWJaxOBaBin3GPE9BnLyUZF11YYwnHrcnOXx7/Gg+SCXTfarX5yh\nfmb27TBwcXNJ5IYRzBKs/0XjPYbUkaV69+HNrWiklA0UhhmhK5C4mLvLws1G\nzUfZrTP4QSs7ajm24IKM+lz1VhqUkRAgessFeoaxYeFQbD7p4nnRvJwueDLt\nB0lcKpNYMtjsWDrmtHLURCFDQTrWH7GaGW0NJp50+C64j8pQo42u/uJnUvlU\n10Y+CXxa4v1/YjsU8EuWkKEh8hc5L/Oz4/b3P6Czi4Shyux7Boe7lJ7sjBXs\n2ir8D3tcequoOIRBFxrlYsu+Ei8iw4l8iDHdYg8WOC10ztc+3PQTJ6knVJ2J\n/o2MlM1t8D2TPpBrcVFN3IBpQ6YxJqAepCtxkUm4kbRCij+djiFJRjFrF8p/\nR1B9\r\n=+o56\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4859c2c3d2b6287383438adecce0188c86a3963e","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.1","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.42","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.40","@balena/jellyfish-logger":"0.0.177","@balena/jellyfish-metrics":"0.0.203","@balena/jellyfish-environment":"^2.3.10"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.9","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.64","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.18_1608243123327_0.9954159247614116","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.19-fulltext-search-string-arrays-5a9cc5f81761767c6d3e8a53a56d68a5df4a83be":{"name":"@balena/jellyfish-core","version":"0.2.19-fulltext-search-string-arrays-5a9cc5f81761767c6d3e8a53a56d68a5df4a83be","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.19-fulltext-search-string-arrays-5a9cc5f81761767c6d3e8a53a56d68a5df4a83be","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"143a5d601b4850b42f037548e0937808ad081e89","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.19-fulltext-search-string-arrays-5a9cc5f81761767c6d3e8a53a56d68a5df4a83be.tgz","fileCount":95,"integrity":"sha512-wNlI3XZwLltn61GllC5UBWJorKgYbLuadW90kOCAz5osFAtEvuMaj1XzzM97hD5JOAD9YeLiM/zL9bTrWysOQQ==","signatures":[{"sig":"MEUCIB2xZu+qaZ8kQt29VtzL045LyHWv53n4eu0s3+mP+zecAiEAtJZnc/XXSC4E5EftsKvEuuXmBJTa92LVVCk/K5iue9U=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":575184,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf3EEKCRA9TVsSAnZWagAAf3cP/RgPLs/b4+os0LTnJXoE\ncxchIOBb8NDoxusYbAUPaG23xVnC5DPg7HWr+lfrtcC4cnlb++7/6pE5uSRO\n3uodOjVIGQvUqNV0ZuHIsev9Xqqj7M7F3vB3qHak0WdyD++ww87dVckpnVmX\nrCJmNCffatsFYV080BILM+xIrB4gtYuWnwR98c0IoW3tZBDRbUdIApj1bY1H\nBdRJhGk/gSp+aAoMDWYucWZcTVuagDGEK1TaYCUhQl6QElZPK6gnk8TXlJqa\nUsNuMeVsdDs1HXaB/q0wejrydSKMRsfx7s1TSwPfGv3Xiuks6YpODee1lpWV\nj+K4mUXSEAbqNkQ3Rll55FKhksYNTNSFJOfLgQoLsijVyOBUO1HlYpbMfayB\nsDvaSHVBF4y920ZC3ECW2uR0JhSl1FMR4//fM2EcyjD5uN5pwFhnKp5aiU1b\nuBGDmyTEEsQFWI/xVdS3BoW+UTqXGWAfx6BoszizkYrRCp5YY7T/HVHtZVgW\n6ix7r7TQQj0jW6mluHl6HGUvYLUDQc6p+D6Qd3Z/+7TfUdoHzIdROoIozl/k\nLehJ9IWV2zroFQilMXpkFY0FX2woG6VRX2oYp+ED7n5GwNmT3Zt2Uf6+S78f\nho5MRZsPnPlyItLeO860a2eFB3LEKnnrAyzZX7aZcAF//8hxl1wfUQGuPDHS\nisQs\r\n=TVij\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"5a9cc5f81761767c6d3e8a53a56d68a5df4a83be","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.1","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.42","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.40","@balena/jellyfish-logger":"0.0.177","@balena/jellyfish-metrics":"0.0.203","@balena/jellyfish-environment":"^2.3.10"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.9","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.64","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.19-fulltext-search-string-arrays-5a9cc5f81761767c6d3e8a53a56d68a5df4a83be_1608270090314_0.5615448752590702","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.19":{"name":"@balena/jellyfish-core","version":"0.2.19","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.19","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f0c870ada548335f42caac4bc18e236d7911eb12","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.19.tgz","fileCount":95,"integrity":"sha512-jkcTGMBRrtAGg+h16YCwxut4qxc/dwNhiKmjYidDHwIbwnWTjRP77y+Ik/bpjqey58lDPVQ+lUj7zXXpGdaSEA==","signatures":[{"sig":"MEUCIDYabyuhkJ6C9BlZLsC/wXQD1/39SuqKyt0IDABBfzKEAiEA8LxiPzyIPFROD3PYMplYa7/1l/LbGoAj27JcXVT7/D0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":575113,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf3EwTCRA9TVsSAnZWagAAXJAP/1NRqdu7W7su0D1JGMx3\nw9yj4jWMWsoxfZqnKgsO2kzY9o/q632iTfTEu/qVkTUhXGKpFQNbeujbcJR1\noVz9Rg4bxcP6ODUdtNinkTGqJaVFnq33s5gipZZi02DvZW2RUGuu6Y9Pl3Vf\n0uoGra7KQZktDsnEmQGiB+7smP2mZMkChFLOjr6+IiD8c7aCNZ/TycZgh1tJ\nyL9JWdWP8+azxLB+ui9PfLFo1FCGEKhJNWsLusrOwW8f2igl2pyLQ8mlvvK3\nGuXEsMuM1VDSWAVJmAN5DYpOu1YfTnmCDOnB7ot+ibcdIxkN+LF6stOUW9h2\nrRl4P4d6XptSBspoDtKMy/4BFFhGHEkKUj7pIJ2/KoCb8OHWrHnjmEpBqd3D\nGAHX6cSlrb2cC9/GBlXeGCVecaw6EOFYN7Wp59cF0fG9F4pxACGxFNp2RFYd\nHHnPgEPfoB4M7UekxSHYnRve7gVLLbR7P1FIhYxxuWuXo1WKsmIQADhy2en9\nxV53HrD+IMBPlqnFw5sYJL86NWs7QbfJgngqS46lqntdxygTCPLAGihT/Hvx\nkdv6FEsWi2HdRzGs6Fe6t4/VJySP6NhhaeLVgS/nIY3kEK/5Rap9AceQIPOF\nTVFHITTZ2okycxhAmWL8ZYFZ7ra1qNqvu9Nf738/4h4JmL3m+14C7kLHlB88\nLDv5\r\n=dD93\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"77163be18ce359cc86be3cfde6daf802ac76febe","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.1","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.42","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.40","@balena/jellyfish-logger":"0.0.177","@balena/jellyfish-metrics":"0.0.203","@balena/jellyfish-environment":"^2.3.10"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.15.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.9","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.64","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.19_1608272914801_0.15538892274647087","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.20-renovate-non-major-f8e0c1620299d822821c4e720272162b04a971ee":{"name":"@balena/jellyfish-core","version":"0.2.20-renovate-non-major-f8e0c1620299d822821c4e720272162b04a971ee","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.20-renovate-non-major-f8e0c1620299d822821c4e720272162b04a971ee","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"66b48ebe432429360dc04d352185751571b8f140","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.20-renovate-non-major-f8e0c1620299d822821c4e720272162b04a971ee.tgz","fileCount":95,"integrity":"sha512-4ZwQn0FbnBklkhjw+5+JFjBWXPVftD4D9iZl9bfUuQleL3tqAzhp0HziEEwQGfBJHGC/aHC0jwOWK1od556zbw==","signatures":[{"sig":"MEUCIBG3MX0Bgog4aUf/h7YYmqLPDNYRSOaCh1OI1aiakdh7AiEAkSkQQTVGc1AfIHVZR/DAa3A2TDzM1wIvlVNcgw5FffM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":575239,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf3UyiCRA9TVsSAnZWagAAGtMP/0tPEXHCiSAoTbFhNfPx\nGA6G9W6ngsqs7Y2qEtndkode2N01PyPteZBbcqfCvtPkdK+O19C1OVTRQnj5\niQtEA2bF0HAO+knElxlgChTC+RZw1rMnOJ0Xyi5s5OYwnLjhlidkrhYSGAWN\nkopV+xJjQbtHpFa18LLUKmbf6SjpQClfqzNke7jkuB7HN9AiAOsCBJi5zTGT\n+9f7gSiQ+2n3OcsZWgaUkfDcAkkB//NCgxYJbfMOKFYbyxxBJBb6lzM+IeTQ\nWLcJqG3kHvxMnw8MJtXOxpnuj4IBdxTY30EHGeqLlu83/BPOYInXB/P+LldN\n8Re1/17ouV9CAg0DgNgLwYIj3ijkX3vl2kz2Ot5bKgsKF3XwwTny5+//QvRk\nE5luSYE2HOZ5aMD0BII1QySTpspC2QyTHCYry4jRLpjEIUDYvXWBPWHYfbpH\n7/0Jj9ZLpYL0rhgDwKn5sCVi82Un9ZGowQOp4DFzc192fPqRCBfjWC+wj20y\n5BmjanrRcHWrzmHIFf7fhA655NjwNyZASBMCxYXz2AZ/z1IZUBkEDQwLtc24\nz+dCEnR9copvJ+71eHEonKBSbJGgPPI8rnxqsRDW6zPW/fYjyafeJ2PMp7KJ\nZ9iGZk0uxkKHHf3y6EClCRshTJJa4tNpSzSjqlDAJXOq+DeKBLpQ+lr2NuC5\njFpN\r\n=7HSy\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f8e0c1620299d822821c4e720272162b04a971ee","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.1","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.42","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.40","@balena/jellyfish-logger":"0.0.178","@balena/jellyfish-metrics":"0.0.203","@balena/jellyfish-environment":"^2.3.10"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.16.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.9","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.65","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.20-renovate-non-major-f8e0c1620299d822821c4e720272162b04a971ee_1608338593709_0.11115451405957821","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.20":{"name":"@balena/jellyfish-core","version":"0.2.20","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.20","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0a412aa0cbd0bb2970249c43276f802426557844","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.20.tgz","fileCount":95,"integrity":"sha512-ZvT8x6LCp6Oudw3HcaYx6yenRMmEqOqkEjKk3CyOTQ9RhnOm1/jUfYpMSGiQTTAkKYOVYR15jonZclZJrhIgmg==","signatures":[{"sig":"MEYCIQD+TcqnyzynB9QmP2/4VQiYJXChPtFJbI0Yf2s2DG5ihgIhAK36wSzqjLrsq7i/vSXjc3Z5GHOecFamoXiFC9D9XlH2","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":575179,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf3U6HCRA9TVsSAnZWagAAkgEP/ilm8zqku73hTtL+tZOn\np71KRlEMzOQpyfamkAakNHqQlMgL7Sm23U6N1rNTsg54XZI95+gzNf9LTMVA\naAuupBL654NO3HJIQb2Df/ezSFQc52YoKtBUCU8kvb4sgDQ5J3yPzaAXil9x\nAJLbz4YAhB4vcxNrz2A/RMOLcJtKUy8UFS7S90XxIidZVa4BdiFfMn0Dk5X7\nkk8zk+iwz3QhHDQSR4GoVEQflnMqXU9WeVoZ973thP0NbBUhpX7U7wMh2+PD\n8kTpZg4Kg7lovLRgfMv+uWtYJBGNcdwbKWZIALh7JmFac9/KhrY0WoAE+bf/\nwcRAz5ccUPy2uIElbk6BDtWez5kM6Jlp+xmZ79+JhbYm5zq9z/Je2ali5B8l\n0BHq4TWNEPoHaHPzjywZ/3/kWxs+xN7LCU463Xn+50jF7abnmUqWrHuR9ert\n96tFJeAO07XUU3KkWUj9Og08OJntqlM+8a/VD3b6MQ7CqnuRdi7c9LH89Z6O\n+uOaeSPZEZWrmNXJhU6VGBeaSir81yIQmafwtN58COwDXBrMOpqOiU26tTSy\nKL79NpPI7ac9x8Y6Mwrcfb60/MDCdtEC9pejpeRlkD1ijju29+9an/WyDXm1\nkdm+ijUuo5qiBCEO7feasMbmD1WBoQqjitaiy4wS1yjRAoz0U2FO89Vrno9F\ndR97\r\n=zCXX\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2d04f976cb6842d12ed870a12172d2e37408e648","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.1","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.42","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.40","@balena/jellyfish-logger":"0.0.178","@balena/jellyfish-metrics":"0.0.203","@balena/jellyfish-environment":"^2.3.10"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.16.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.9","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.65","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.20_1608339078793_0.09777097868312401","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.21-renovate-non-major-fcedc12d0df63112636a7598516be01baf47715f":{"name":"@balena/jellyfish-core","version":"0.2.21-renovate-non-major-fcedc12d0df63112636a7598516be01baf47715f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.21-renovate-non-major-fcedc12d0df63112636a7598516be01baf47715f","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"221c5f9ef9f529ddbe57c923030d4f504da7ae85","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.21-renovate-non-major-fcedc12d0df63112636a7598516be01baf47715f.tgz","fileCount":95,"integrity":"sha512-Rjc3Ob/Na5kQiGP7kzG3FtPipi+qq5GqhDxcl28uNyO4P0mCRPY0F2ckVxBEbjSiJdKcoxIF0DkbjKLq84IH5A==","signatures":[{"sig":"MEUCIQDrZUYtqxLhtzU0BbISeEHm02WrnOt0wZp0FFKAMOdPkwIgNyR81x/80YXZ5yeLBahvXWoUQiGxyXcbRoWAfapswWA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":575305,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf3VBgCRA9TVsSAnZWagAAJ2YP/106arqN/RWDZd69SAZl\nKkK52whMY5Qvasi9gldeljWQMixaDdc+jTqE6bk6Gfsb/9DRzndBJJre+2Yk\n98APoIWZy9RFedVErOWCl6rM4VEs/kWxme6rXDewNMtAZxi4Wgkfb0hMqTJ1\ngt4yK9YPDm73NtrY6RZ+F9vOYIEl/yk5oL8OD2fnVt7KcjSpyjdn8CQUt4eN\n9q7oLc5skZq4eIBFCDcODq7AP1YytrxERUUSJi4iK6eaQOs5b1OLXKp+ICgU\nsDja99lnqfsf0jHsyya2ZhDg9Nk5peYEp9z6Uq75b+b/eWTcj7bf60UezD4b\njOap9uu4nzJmFTyZ5Rr9qmarLTWuAK0adOrSEkSf7wHMswZM+ENVxcgCSlis\nRBVKKQg7HCXrRgNPrsZSNbtp+liWEOXga9QkR1CNUvpjrk3trxMcPeoUdY1q\nh0jHdzh94/r+uEzqLkTG7++wC7kZxp4m3MDC9SQbLafWA8R0qkPW5rij5e/V\ngRxZ7mtJ0Vxw1DRCqCamYChkWfI6GjmVnFxYj/mNpuMVDC7X0SiITbwH6So0\ngq+fNovXJsXuMSj1Xhonv2surMVWHuCQ7/6DMB9sn9/UbajQh2gxiisII72O\nspjEvdtUmQTqFQbNpJm8QMH/nfGZ7WiV3xrDKk8oAC/FxN/vfdUO9abiWkX1\nvhtm\r\n=jFDM\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"fcedc12d0df63112636a7598516be01baf47715f","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.1","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.42","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.40","@balena/jellyfish-logger":"0.0.178","@balena/jellyfish-metrics":"0.0.204","@balena/jellyfish-environment":"^2.3.11"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.16.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.9","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.65","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.21-renovate-non-major-fcedc12d0df63112636a7598516be01baf47715f_1608339552040_0.8926486313401085","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.21":{"name":"@balena/jellyfish-core","version":"0.2.21","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.21","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"01fe74aba05250c3ed7d7e7c0e93037415e15c82","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.21.tgz","fileCount":95,"integrity":"sha512-zto8XDz2Bc4BxI12lloRcAXJrRoS34govj6A/q5jUm5tf0lIy3UmmU/bS9gKpMRpoPynmXFFIIw+Z1A7Ojb5lg==","signatures":[{"sig":"MEYCIQDDOqa7HghgNcqWWHnPF530eY0FeY52bXtEzVql62maRwIhAIpG87eH29dWzaQ2GRyMoYD0hA0jbgtg8bfnzJ0k7RQu","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":575245,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf3VHWCRA9TVsSAnZWagAAy3IP/3KVfYn1+H0ulJbK/0wx\nKWMpS9vQfhfE5gHFBRlPSplkd8m3g//YGb1zaVvdkYHp5aWXbbzzkSdAsYjZ\nyObLD6x54YmMz8rI3erCnRbHZa7G0NEQVaQX/nnC5GAjtYS3+qD2iOcAOSh/\nHsGkUK9mVZog0v5CWkPx+p0zebwnkX6MVsMCrCREPtfEJhMV6Qas2K/ZIQqz\nPJS6Zt23/w7Q8/ikFeIJJgdkWk4t/UiZlowu+gffhW/vxFhPPeWC23DWd5hb\ncOhJPiJGlhBvEZkZqPz3NZbiOtqiVpF9Uqc3R0W2c83QWzWsJ5xpsQiERngH\nD5TT9imBxx18noaQO0gTxlDSA8XDjOJxn63gDnOS55+w7lS9FCDCeJO4OKLZ\nUnruK02pyd7TxVvEP+x41pNEtmB0fIOuDoi9fA+5+yb3HxoYUXDhnXXKGV7P\nRPiR6vj7tqD3Atw6vM9bAUQaPMEojy5vi4WKUT7kKRmJ7jrmYUzbggNnWxvf\n5JDJQvRhQUOvHrqhP1fQICm4X/WvtCGsl8EazeYwU2ZZQbxGnrJOgCR85gOW\nMxsYXXNGx78rkMlpaOClb81YWlALqirg20nWo4izdBYhRO+R4IDc1u24ahQ6\nCQS8hIsCWM5y3lz1D7rWLJLY7XA3WNVUj3p5+YT/dEYSqbzWp2m2IKXu3b5n\n++Zj\r\n=Qu3W\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"31d00b019696ddb4fab96ce05c4fae4d2719d43e","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.1","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.42","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.40","@balena/jellyfish-logger":"0.0.178","@balena/jellyfish-metrics":"0.0.204","@balena/jellyfish-environment":"^2.3.11"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.16.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.9","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.65","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.21_1608339926082_0.928836997005545","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.22-renovate-non-major-321a8b855842f4ac18090afc4d439314b9f2d762":{"name":"@balena/jellyfish-core","version":"0.2.22-renovate-non-major-321a8b855842f4ac18090afc4d439314b9f2d762","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.22-renovate-non-major-321a8b855842f4ac18090afc4d439314b9f2d762","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"7b49539b9b123fa09d0721e8e04102b1114e41c6","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.22-renovate-non-major-321a8b855842f4ac18090afc4d439314b9f2d762.tgz","fileCount":95,"integrity":"sha512-UCo7BjjAzq5KZSnT5Y/7/EiAw8WShHJFcqUWN8lOdsijbaIbMJStPBhwXW28tzKBSXD9ZlxLPO7B+Z+HPo8PvQ==","signatures":[{"sig":"MEUCIHCLEfHPhDTBWAk+AuIkvr7SPD6KhyzJI9H/tsqrSxkOAiEA1f6tQGFsUfIAkjJe0cZMNgkM0/7bt+ip1f3UmzjHYiA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":575410,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf3VNOCRA9TVsSAnZWagAA760P/2JJwdGuqOmeg6bTSoXj\nDiN5kaHTz4gEYnwTHdEHUPov+gn2evmrSv0D/i56mNLjASTcaJ+WKHNx5h+H\nfLutRnXobcPo2sxw67ZUc56pvsshSZO5YsPGsxx+xS5kykfNnlpoR+NvdDiX\nrSSzHRUJgopRPca3YZpfgSWNOD7G14SeW7CrcYhhGlHk9MpCD/OUkBKTty4Y\ndpIdQFcYjKZZ4jwt/qKu+TBfrwsxU8XL13BIQgzx1ny/sCvWhY6ZRDLS21gF\nTNN9DHgFyXlAFl6mlxYiKRRYjoTzfr4t8Mruef+bonctE7baZ6aMuqJw8r0L\nhthbOfTjkJEATZG69VMez4XOwPQRFWpGnvXuqzm5gUWDihDIzDwEBrchP6tN\na9kCJgHbfMHYApLNpVKN0utGWk6Yo2x0UBSJRbl5ifN3VNcDLkcJx4SqF3M5\nFM5C+hJURxxH4jHDpCMp+2uB0jh5jI6UAScWObZrBo97en5AFHXeoOo6ZwYE\nt07fLXuLs47lNDhiqWOAKPGrf5oDY5DQXbJN8sJksPvj5OiK+Sp9ttq5An6+\nQaOLGAjR4rg2NPasFQvnSU1kTkzKMsJpTvb0QgVg8huNI704be2y7TLQ1p9q\nFZbwYD+GRxCoyRZDPnwQ+EdG+Z2e557srXCbUhShAGFQ9tLVc3oWJavJIOaE\nJDOf\r\n=QoyE\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"321a8b855842f4ac18090afc4d439314b9f2d762","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.1","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.42","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.40","@balena/jellyfish-logger":"0.0.178","@balena/jellyfish-metrics":"0.0.205","@balena/jellyfish-environment":"^2.3.11"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.16.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.9","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.65","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.22-renovate-non-major-321a8b855842f4ac18090afc4d439314b9f2d762_1608340301853_0.09601324051325766","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.22-renovate-non-major-00ffbf02adff961ee1509e84eadde41427129b50":{"name":"@balena/jellyfish-core","version":"0.2.22-renovate-non-major-00ffbf02adff961ee1509e84eadde41427129b50","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.22-renovate-non-major-00ffbf02adff961ee1509e84eadde41427129b50","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1f4d8957c44ca94dd8bbc91a6a069d5acc3bf507","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.22-renovate-non-major-00ffbf02adff961ee1509e84eadde41427129b50.tgz","fileCount":95,"integrity":"sha512-S5gtrevAWbx4Avtk8ZvKmM7DI2ZB/92LEgOnB0j3ZIoM7ZAVBi/U8XnpduwIHzM9Sov2Ajv1A/TbIPgGMRVQdQ==","signatures":[{"sig":"MEQCIBXgiUCCBBAr925FvU8uHKHxA4hu28GHRFALhwn4R7mcAiBubvja3RfRfcsnX5uLQPS8TZr+u1/7yeyJoDPmjTX73w==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":575371,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf3VpPCRA9TVsSAnZWagAAe2EP/3yOzDQC4sNDnKt2rT9S\nUXQYOZ4zUm/rTGWpX+y7HQgtshhYDS8X9JLIdkAlJ4uoZRc75FY8m52zb+E2\n/tQai9E3yWEGO8edggBLRZ+fGHamYnRcTTSjdLgv+s6lIKYcKrB6HIb4yRDT\noyiH4omRklLJaRzSCXZ8VAINlX4N3rizUJh1RQ4oTU2wGMznqGTjsLyUqG5e\nzL+O1aYbWsKvzUPGvkkreNA25yQuh60LpISwtxKoQEj4Q09kWESAdlBjJbB+\nOUuM1wleBjbGH/xhDl16LWRHYCAe2aLzh5QWSKwhLeJd/42lMj1+J1V0JFjc\nS0IhZGn5QsDiuYaFbPngSKCAwU/ETOfxM952wjgXxlQzrjzxk4dO6Eu9BVoS\n7VDoGA6qfm9mzo/xvp8uiNuNA2VFzO2Zw9hekoXWdt+fM4V2kphoN4PVoFex\npZCj1gRBg+ym16GZb+GzktUTcosp2pQWRJM+g/NCdTh0LaSIniEUwzk1sXx+\n9Oqzn14tv+YdVb0+bQMfKWJnuaK2Q5k9p+KZF4AEA4qFwyf0jR0lKDnaae42\nqMZKUlVPVZIPNijHNfsAeSVRZ/xraU86JNj9P7qjh5W8Kj6z/PQQ7Nt29QcF\n/ttrS1rAUY2ZjfWKPKL9dcBJIqlcadnTsaMYs1M1wNXwmPrFSRgV7rfwEeyU\n0Htt\r\n=0xRZ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"00ffbf02adff961ee1509e84eadde41427129b50","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.1","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.42","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.40","@balena/jellyfish-logger":"0.0.178","@balena/jellyfish-metrics":"0.0.205","@balena/jellyfish-environment":"^2.3.11"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.16.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.9","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.9","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.65","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.22-renovate-non-major-00ffbf02adff961ee1509e84eadde41427129b50_1608342095112_0.16129883930224742","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.22":{"name":"@balena/jellyfish-core","version":"0.2.22","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.22","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0946739f26b44ac8d6fba87c475df018b0dc2609","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.22.tgz","fileCount":95,"integrity":"sha512-hsIPKYzW4kf3tXVcku2NtWpj/ADp4Og3Bc2BPqvv9afpRh5PAs3CCeWTsOmTGbXmn9eWjvQg32Pk8lEmgNIFUg==","signatures":[{"sig":"MEUCIQDeLEZWjtYzJg0o5I0xpEdc/DPd0sq8NDuvUiW2RNniDQIgYKSHpcEu2OVLnNzLzGl6KcND+5gi4S0nopSPR1nDxnM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":575311,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf3VwpCRA9TVsSAnZWagAA4AUP/0pQJ0L9coSpAmUsoc2v\njbjsUnvpg1T8sOPU/8edO1rIyS2MEk+gjlq0edidApH0xQ530eQhJYpoKeCg\ntBn8KerqDDuOrmUakG8aIBJiR5FvvrAPnBplSqJC264ZbD3SN8JOA5vQW3z7\nf2EwpRP/P3L8blvgVKSjqIniaN8KPvpaG6R5KYmQxoOcstKIH3HQQdBPa4ss\na+supLIIopQWD6Rs2lQdZRxOUwEmzr29H0wyVV7uwlWAqfjtEGhLQeDE9pCP\nLCpVDtME/jdqFMEiGQ2NOjJYcHrz0E0ozdiXDFfl4gDiSkXyXcYNJzmNK2Ut\nYksebqlJsyQ1UBSL1UDQ8MxCAIQ5jHKK8TKAf7llQwp7plWJG8Kt9KijaNt2\nFIm+o2zGmYpnNSuRzenRpOmOOKu3ScitCsF3E2IUltkz5jiUnvfbxh5UvxuT\nmjHq8PQn9aVAETHXeVThLNcr5yTKhWMuubpwdAATXFGjQDoPJ6Y3imEuLiHE\naLp7mIY8p9MrJ4k3MPJrZOihRZC/cKiEgH9VWUDYFb84t+qgcZogdYTNBPW7\nnW32ZSGhI3s1/8J0byb7+WeXanHTwR57J8YG9TMftxU7XmPRcz3zNaviTtKg\nxPd7DG+lc19oyyAvux4rDoYkILpe+d9596IbM4xxLEapdGLzT62ta4/V9LRq\nWblk\r\n=FX2t\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"aabe18f70cb1c26309f29d5ff9a111fa2febd1ec","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.1","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.42","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.40","@balena/jellyfish-logger":"0.0.178","@balena/jellyfish-metrics":"0.0.205","@balena/jellyfish-environment":"^2.3.11"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.16.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.9","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.9","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.65","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.22_1608342568800_0.5975414412613378","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.23-renovate-non-major-7599cad719eeaaad2d6681435b2dc8d5f0b9f669":{"name":"@balena/jellyfish-core","version":"0.2.23-renovate-non-major-7599cad719eeaaad2d6681435b2dc8d5f0b9f669","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.23-renovate-non-major-7599cad719eeaaad2d6681435b2dc8d5f0b9f669","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"940dcda477baa5032a5bd66834ab50ca309fa541","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.23-renovate-non-major-7599cad719eeaaad2d6681435b2dc8d5f0b9f669.tgz","fileCount":95,"integrity":"sha512-bPVFKPXd5U/oHsluNNKW6sJ0tCBAfWqVOrxO73pD2QfkZVKaWfJCXEBacJXlfo/mXw7TNd+cZB0InkGPHOaWQA==","signatures":[{"sig":"MEUCIQCgnkyP2AWRcKyg4CmFTsk8SGitRqwbRxQBr6yAYLyhmgIga247I9KGZrGqAjxIQogjLZ/LY3LrGNKn1WU/KS7nx90=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":575472,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf3WlqCRA9TVsSAnZWagAA+cUQAINqF56j5gcvtrecbkS5\nUYVWEBPsGJgZy8x4CB2hmMkzaVTK88wkGRC5J/d5iSrWRpOwK/prXTEiGCiU\n+tUOAQ21gCHu8o9vlyFEZZeYW8FvzjYnCy1u5PF0igqMdraYafn4p/EyYEE9\nri/NHAd9IYs0wH4liqt5Ju8FKWoDoft+5w8aRmjwn9xSJCVEzAcmpA1nvFWz\nRBX+A/M+kkF5H2MMN4ElIkIirLkX4Sih0qYBEMETgFxZPjrXstkZ4oLS3+yx\nTq9xdUZFdFTtPldLhcjSv0Cir99Q8CY2nMHlCOiaXbhzwPMUk8bOhtVMgHlu\nr/T/LYtoIsM5KS6R4a1tMHnKHyCdMNzgINl2dNkVJF9XnFlXl6iJHxeQ91nj\n8LlZ2PcmlbU5kh7GJyJKluZxQ4b55E6PkM0O83Yceb01ko7UNjuq3RI4zs8f\nWA9SYoUfv4//JtmVwN+yDqBnNV+NOKEbpMl7pHO4PNc9nmOgja9/cC59xxT3\nagmq4Sz/D2R/BaAHBf4sU8m8fzNppMgj0tRwRqPPAXKR4wH15poVd3y8Lppq\nJMed3QOowz28oNgdDYhjYqYBwgeIghlZFCvFsDYn0RiRZBU6SvlqgSfjgzUV\nZsr6CnlnF5L4DeVJVsU6XKQDGXCNH19KHxEzHy84wI6NaU+mm7YdGVUDOU6b\novx4\r\n=lnu7\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7599cad719eeaaad2d6681435b2dc8d5f0b9f669","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.1","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.42","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.40","@balena/jellyfish-logger":"0.0.178","@balena/jellyfish-metrics":"0.0.205","@balena/jellyfish-environment":"^2.3.11"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.16.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.9","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.9","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.66","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.23-renovate-non-major-7599cad719eeaaad2d6681435b2dc8d5f0b9f669_1608345961870_0.8649563406093925","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.23":{"name":"@balena/jellyfish-core","version":"0.2.23","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.23","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e1e8e88350a2c991d902f5e86d05a3f3c9c5a2c2","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.23.tgz","fileCount":95,"integrity":"sha512-LtwFqIYLBIIZH74KaIkYt74UEm8NrNdnO98UeENf29WC6ETbfcBm4BGGA85qBQUl5Ptfv4NEOS1Yh4/HvxysNg==","signatures":[{"sig":"MEUCIHc0myNJsJD7qIfTfH2q0VnV9mzK6zNnnBenFom7oEkKAiEAkbTmoFdOwZBwyaZeJ4rz3X6aYRU5IQLut133WYjCCwI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":575412,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf3XMZCRA9TVsSAnZWagAAMS4P/2xGHbQKUimcuQN7qWv6\nYtSqRRPqz31FCFtenW4OoojsN4Kcj/SXMQ+3/jyyIGmvg7MFLCUi/4fiJNc1\n0b9L2FufjJ+2BfAcv9FUCsMpDjgCRZo3Bquo5iDD+xZy806N0wSm1ambKE/S\nhVr/HaYZ2bGU68O7fgHQhVVJhI1AtgaM5XgxXdSRyaTulfEQ9GkI8GkwnmtN\nz9f0Mc7ZkxogSFeQPD5CJoYZXdGRye0Q5VV0p76SXTZLgyWKh5N2mh5R16VA\nR3qS8IJWflyuyBurn2ZIz84PFQ6OFsAx8M/JKlKnCfy7eWEyVVvjZYA09sxY\nU2JZ0t3qn+3ZwEAskkTRk8jfpPiwlrHYI3TEx0dHdCbS3p3wb8n1TXwq/zvi\n1yN/n6sU5s6pSpJGAJA5KZuS8kXQWXVPJWCxHEI+ro4B6XkDQWV03vthK+U6\nJ3rCPqr4lVfWyRM2tNFmGruAo62z25yES5fN7Qd/l0A1m7GPXSNLLeJQnANq\nTUfhx752AhPvrA4Ipzlr8AVwf5QFLDsJoNK4Y7AZe2AhP6C4+HVvp7JrZH7m\nNDVqESP/ZXnpLlJKy0R2zDVbPFrZSZUHHw3ypr4618P98kBffR4nb50q2wA0\nLHfQR3PjLJqxBBjE3DmDBYgCXEjtpZ8e6yTcGfonhvCCw3Vnst9fCV8eJlWH\n0kok\r\n=DeTK\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6fd6a09efef99e49a4fe34d42e5e5baddcbf99e0","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.1","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.42","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.40","@balena/jellyfish-logger":"0.0.178","@balena/jellyfish-metrics":"0.0.205","@balena/jellyfish-environment":"^2.3.11"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.16.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.9","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.9","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.66","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.23_1608348441082_0.6394038212154665","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.24-renovate-non-major-35965e1798625f068cad90b561057e3cbde81cff":{"name":"@balena/jellyfish-core","version":"0.2.24-renovate-non-major-35965e1798625f068cad90b561057e3cbde81cff","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.24-renovate-non-major-35965e1798625f068cad90b561057e3cbde81cff","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"90047ea027456c9b40e49e52eb0835f7c112b988","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.24-renovate-non-major-35965e1798625f068cad90b561057e3cbde81cff.tgz","fileCount":95,"integrity":"sha512-v93DdFNbRzeLAUdxWgsCPDEyhMlE9KeBLDL03tcCXo4/srxia1YPlsxuqR8LLRlF2IsYAbCYcch12SZZIq9weQ==","signatures":[{"sig":"MEUCIQCEsXcZXZkLCYfYrm3yK9x8kTLQeOAuEMfan4+cYD15rwIgBiRb9FOeA5XT8bIOVtt9sQH0UFs/mw/TBDKU6qxdN4M=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":575580,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf3bRPCRA9TVsSAnZWagAACvEQAJ0vHTogn6Ar55+JYjvu\nBNoAgsS526Q5l5rcqf/FW3CNJ6lGyvez9KjQ+bDSFpnkFJMvRvjaXvhg44HD\n7ENGV4MzrGD2apkw/s3yylKTI/+qovCz6c4iz6xmYhpK9fQDKZxZhI3aHhPF\nNBSTudKm1jDw2DDpAoEIa5MGt1UvgaCL6+aAnqHlNOULaIyarFLHfAf/p5kK\nG+oiKC33nzyUDLVQ4XAYJRJtkjI6NzmrA+yEa5Fn3K6/Mur15cSn8peX37b/\nFSJawfS27FM2nkRstgUEmNPQLIjAkoQPL3XULEbIkrp7IkTMlY//rWY55wyU\nX5qnxwDwhu2TdjFEuN7Tw7cyU0/pXlR3yCG81X/dxWY9/x4RmeEzOM9sz25h\nQ+IQV3M4A1b0K30X0qUORW3f+UeVii0gKUYy+sS7GWM1fMEpxSJAe3JQ829l\n4VKOX2cwQLArTMmJORoo5dBK7HbVJOl9xY5t65T9Ndium3gplt3F/3KNJBtZ\n/MLgWV3LOBWiMhUJqnHbft11fCVS6BqtUyDsFS9X68V+4PmbQpYr0OmKPi1s\ng9xAItYbRo8iD7N7JZNDzRzQsBdXzRXrpf37PgOUoPm+chB8ynSWviHeN5HM\nWfiA8MjNKGQtm6UqsC4fVEkwuo2IWqO4onXkopmZiyLSXuzfeUIYce581vXt\n88vM\r\n=ubf/\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"35965e1798625f068cad90b561057e3cbde81cff","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.1","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.42","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.40","@balena/jellyfish-logger":"0.0.178","@balena/jellyfish-metrics":"0.0.205","@balena/jellyfish-environment":"^2.3.12"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.16.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.9","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.9","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.66","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.24-renovate-non-major-35965e1798625f068cad90b561057e3cbde81cff_1608365135094_0.7745343861714804","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.24":{"name":"@balena/jellyfish-core","version":"0.2.24","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.24","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"61759826ba905aaf6b8b5ca3d10d888d7107f608","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.24.tgz","fileCount":95,"integrity":"sha512-vlvq1ksBLvMl6vekVTWR/H7+2z8uHg1FchlmEwY6hbAI1tOjwWcLEzLyzZHMucd44/mEvLsO60KvfZikxhZetg==","signatures":[{"sig":"MEQCIEwRGrPgb/q9s0QmCC++tNLDWsKetsQ4ihtJ6rL6wbZiAiBmji+P5ABn8TP8ow9pN/ErbppPxppECqcOrKLsV5pYAg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":575520,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf3bUECRA9TVsSAnZWagAAN+0P+wZuivnIWbJw1PNYWfPV\nj3r9DZ+eAwxX/fqkW8xhG42OakX9pCDcgCW2kdpbiUSwDoZF2Q971PGeQMF9\nA2Rhq8tYFOvOwQc7fWeOFo/mvHFPS3Z3goUIfHur1HuZguaIcnWN6+LWpRqs\nBnkYc1uZIrfcG8JjeT2kNs6Md03VAhpyMhk9GYt5p+G0dAbpcOcLqHhlwLq+\n6sUjc/JRGGmMpL2KBKvHUIGZD7zPYSf+VT7udjAr7TCX5epnUXWWUiTeiXPJ\nWFthafeZSnpB7q1k6dS7FGaRCubDsRwqDX2g4CkixmCPK0ziX5NBtbDjNuCF\n0MKm+1gwpHoXkxHqHHIikbPNW6I5iwk+umojccx7k0kLSwYD5etMAxz7a7u+\nTShzRI9k7n2May35aUWEwCodijRkE2uaok3tV1Mf8eE6C3sI9TtIFCq3BB+O\nvTvY40LE8HVKMM5t25Q6JLm7DxCZE/5imUj3vFDFzYGwSnzUssPJbuSDv1Xe\nmsSAEnEGYbmW4xhGiyFRZ7cdaoT65S75Tv45tFat7GxkCzR462lD24OY/ibW\nC52oJ7s6UmQSnWNUXg3NUH6i+c1xQg6K6eOj7fGBoYPRa6+1rG0mFd3keQt/\nZQlXDvsYAyg33WQVskpk1xdU/kOWnA6UsnYbM8qtK7HENKoTgz/tCyWx0GxE\nnoh3\r\n=f69o\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"736eeb79fc192bd67290659c5a11d949aaf34c4f","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.1","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.42","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.40","@balena/jellyfish-logger":"0.0.178","@balena/jellyfish-metrics":"0.0.205","@balena/jellyfish-environment":"^2.3.12"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.16.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.9","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.9","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.66","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.24_1608365315940_0.3206513726340401","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.25-renovate-non-major-83424606ae524b985756b65a8a474084fac89d94":{"name":"@balena/jellyfish-core","version":"0.2.25-renovate-non-major-83424606ae524b985756b65a8a474084fac89d94","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.25-renovate-non-major-83424606ae524b985756b65a8a474084fac89d94","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"53dcf66c55a0752835df66d8feda1899667f3d3a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.25-renovate-non-major-83424606ae524b985756b65a8a474084fac89d94.tgz","fileCount":95,"integrity":"sha512-CG5vaoT8biMx8JfAxhxb3uyu0HuuE/BUL84cYGRd/xGdvfQerX0mqIsIWQtKG8NYlfnGGJyVLEqSBg0llygJeg==","signatures":[{"sig":"MEUCIQCB99mD5O1lcf2NJ9ectKrcmdll2w+7eW/ATKrYl4+PqgIgQCtwHeJ9HvUKtkDRf6Mmcwtz0qafccKxPbgfyhLdu6I=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":575646,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf3c2xCRA9TVsSAnZWagAAdVAQAKDS3AjnJjqtCvSOtJqp\nl6iUc/sit9R/8qAnC534qhUqzrrodAudzTk8yj9s5x7lyDEJLy6uEtzmJhZm\nVS0MZq95vtSbLhgo4JdzG1q5m5ts5H8NuWB6aQY/bodBUrLT2Iv8/aI7KdMC\nHYXNpMRRGZD+fSn5/uOJ4xdrDG0nrMxwfot3XBs8oeLThXvb3cZd7iALSkqg\n9h0C6NUjozeDtgVSobZ3XqjOOrKkcay5mwYW3D5x9j0++Iyc91+3lw60IZy+\nzdd6rIUozlxHSHKggFmFNp3Kp+kxUx6tNcxxXtu6+UGhlA43WxcQGNTAZAaq\nThRIvKyrdIE0M6Iv8R95kMRSrYyX/nYcZiTiohiNuWiUSr9/SzZEZsHjCNBR\nZgd6j8U8Fj0KeKkmJLj8a6LUT6aRydRfJAN/81NPqb7iPy6j8yNzlT7/fCHO\n0DHVaaJH/OsC+c1A46d9bygmBHCfvvzfkXNgZpR+OP9RmmSe03bxXTOrv/8v\nB5YxwekyOe30xayRJD4O9vqA4nI1RAI2kNinNq0nyJptqQqp7urM/yCfTDpi\n5LucOGdj0znsWFgfQrYGUSRMmAT/WeJHj66ZQzbXk6NtzRWygvzaBE6dC9BZ\nB3BWn9iNzIAaVIrK6FH92jd2CVUAUCfU1RJQ7b4+E959heT3+b20mbeQXqIB\nvhyX\r\n=Tr3v\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"83424606ae524b985756b65a8a474084fac89d94","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.1","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.42","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.40","@balena/jellyfish-logger":"0.0.179","@balena/jellyfish-metrics":"0.0.206","@balena/jellyfish-environment":"^2.3.12"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.16.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.9","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.9","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.66","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.25-renovate-non-major-83424606ae524b985756b65a8a474084fac89d94_1608371633194_0.050106359373201625","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.25":{"name":"@balena/jellyfish-core","version":"0.2.25","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.25","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"40d7d0850f7aafaa19accbf891a20eaaee03eaf5","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.25.tgz","fileCount":95,"integrity":"sha512-eMCl91V5a4Q+E2rkRUuj6HulUEeKU3gOZKg6dpE/mGAav8eylmpBong3mN2XtR1ovtrFrRJ1p8gjuJ6ipoyEAw==","signatures":[{"sig":"MEUCIQDyE7pliok36BzYcLAd+aIrPx2S0anXcmoJ/ySVVrrEBAIgFifpCkUXzdzlET2U0pXMHzanfVA2lMPKlCYLhzFk+/I=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":575586,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf3dF7CRA9TVsSAnZWagAALeIP/2VuWoLBP+o4cbxc0A8c\njGUc/m1c6/RroGcv1GpeSFRpLe3uGN+EOmRI9DlLvlpox48s6U3MHLBGriQi\nUTQj2HQsPvYKN+ygRtwqrL2N64g/7QwUZzkC79BWbA4I3NbmsVSwiIi7CSdj\nD4kaHAdjqF+7CwYTtGdIjAEvpuzBphfe9azS9BCpueveOYzjpEQFel8NrjbM\n6sIS9TTGvfncCKsYV00OhK09penG+G4Ym1OKFy81lgrmxrJxhonXQtzuvM2g\n2EFdkGFlumzdvMVZqHvBOnf+t5mITxIgeHK9fmPfvRIUPSYzWB3+WyEpjWzN\n+yFzuJwOml9jHTe1kV6reOP8k/r2Fu51rcfWvKfL/dJFT0miHhRmpUuh5XeR\n/OlqMFBKLofGsKpDIMyxp0swE5HtH7aklV0Zy4758urJiel7rS9lHMy2P+40\n3sYGKn4fN7UbFXwnl/LCRDfcI+ORqoZ/s8u82EUroRJXQ0q7FRny3I6h2VgZ\nVRNbKoXo9cDGNz6ZfoG1VKe2GsmTGCPQrndSl7QxUtz1fjjc74GszuaqE9qs\nngu/wkvkDx31CZOO79162wWghWHTJKTLznflJRT9iksMrVPcMlZwVHelYzTh\nsaCbfqedwn/kP53siu3Zb3vwPqAEykKBW6gXuZW5o1i/hLjyQHVmmx/8u0yA\nhhPV\r\n=mH5T\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"070c107f406a7b32828922822d4b98fe90e3c256","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.1","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.42","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.40","@balena/jellyfish-logger":"0.0.179","@balena/jellyfish-metrics":"0.0.206","@balena/jellyfish-environment":"^2.3.12"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.16.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.9","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.9","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.66","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.25_1608372602558_0.03893347056194618","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.26-renovate-non-major-8119244d7a92fa550392a28e39b2bfb3628b98c9":{"name":"@balena/jellyfish-core","version":"0.2.26-renovate-non-major-8119244d7a92fa550392a28e39b2bfb3628b98c9","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.26-renovate-non-major-8119244d7a92fa550392a28e39b2bfb3628b98c9","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"75ae66545966be3c9cceffb1538e520cc424bc98","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.26-renovate-non-major-8119244d7a92fa550392a28e39b2bfb3628b98c9.tgz","fileCount":95,"integrity":"sha512-wqXioNxCek7W/FdieveGS3ELByFTMzt/9Z0OcaQioeYsOh8aJqgWmM7VfM8PHjHBvZnw8j2ZaQa3VPund/Q6XA==","signatures":[{"sig":"MEQCIEkY59LYMakLaOdKmM2WJfgiCmjVSldAD0vSiJ5gg7OrAiBS3/q7pYgZ8L8dZjDs0vjSCp1voMNC3q5Lg5wnp2tVlA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":575751,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf3hUfCRA9TVsSAnZWagAA5jYP/1sid147SHwQgzbNc/8q\nYAPZicRnTlLDDeOqLE21DB0yCGf7dSPdQvtTRjg43iNCkRIIcq15IMg2D2s5\niwgJmGsvrWW8w+xvQ7VRgOF7OvHqBQvW2otqiZW5cKUtypLKXoTsLYw9aRxC\n11jDGSE4psAZ+AdkYVCwRM/P8WSXvHqhd5dkOO6HDRSUtoUVCEWE4W3tQgzQ\n9e2KrNA9sR5w1mVKDEpkkjNu1o2+xSKbF07AGHmDiq2DJu7XQTZ7hpnWugOJ\n2T8D8nf+6hrgWnc6IiM4daXbENPK3NEyKVtFQJpSqTWMgMD3GyKdIxZkmqA8\nFcW3hzy0CTbGybWowGQs+fwoJpclfNV7PoPAtEvyg6Royq2ECZcP85b1RjoS\nl16tbTCYzpWkDAEpEeTcLgTJuHzT+EWRs0iQZ5W45gTnaqoaNT7fqtQDlq3Z\nTpQEOSmG9OFbbLGbn27uk8Px53Pt41JrKVahxcLzDOKmW+dCoER2tknu9X5l\n3/fARaZCD8LVYjyPzPzLLQKrenksSTw5XCVH98rb8QXul+d5eK1na6YXNMtA\n/6XCIft3Xsenh0ov/Aw0GfIPooEzANkh3ejUrHNCp6nwsJYMJDywCbvHRzSz\nkSoLh1h3cXDPNz1Tn4n0wqfN2jlZxk4e2EbKyTE1pbSpr7BIbUuPUHSC9V7A\nwL34\r\n=RDeq\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8119244d7a92fa550392a28e39b2bfb3628b98c9","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.1","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.42","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.40","@balena/jellyfish-logger":"0.0.179","@balena/jellyfish-metrics":"0.0.207","@balena/jellyfish-environment":"^2.3.12"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.16.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.9","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.9","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.66","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.26-renovate-non-major-8119244d7a92fa550392a28e39b2bfb3628b98c9_1608389919253_0.8468015197685215","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.26":{"name":"@balena/jellyfish-core","version":"0.2.26","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.26","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5f203773f2bebe398cd2a56ccecfd76c738cd96f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.26.tgz","fileCount":95,"integrity":"sha512-xIwETiRDTbobpFv1YMduAjOyqtoXzs/bt5IQqa+N3FoeCc2XvTSMEZHGHIYEbm7Z1TAfCYphXzWKwEZgGM410A==","signatures":[{"sig":"MEYCIQCaun76zHuNMAmNyMxAtKMCUbIOYer6kyc+rjGPF3dNqAIhANXkhsHKhEDzwhkXyYQcKlCWge6YzifV5sgbbTn6/QDh","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":575691,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf3ox1CRA9TVsSAnZWagAAbokQAIW0gzlt7faVp0/gddjb\ntpxMfpZdQwN9/cnkh4qPpJ3qnCWXrDXXBx9/QHe29k3rH0lWJ7lSy1dgVDEu\nfKvbMY5JzdsaXpsHScBwahTE+qei/bIG5Sz0z9CYITzBPWMAxU7sMD7/qvIX\naaAfClRW9Eb32xD/nj75Kd0MkyLUespUUGj40WLS+eO+PF8Y/ZoZk/OlR2/h\ny/P1QdtEHMQbTr47v9Ekt4wo6q9yx9P+WXzkV62NxCv3I11jNWrkS7HphEnf\nLS1dsDcv04xh9RwYYw8GlgAxoQiwzXKWNNT1QeJrqlJMln1jT72xRLn0mAOI\nI7VG6aH/jzIikGb+RM0yxWSBiZ5ENYTuEBcmb9PblDo4OxR1d94nFE3pmLzI\nejhjOeFb6ORciaL7vQRepp9NW6D2Q8cXDnWJVGjaGrIGOO0JmNsLwacf+1yv\nuF3S1E75piUUqhHbSJ3pmzYCkOZgIL+IBntdOKToYcRdc+RyBcCxBBqYzXuz\n8blC4eL5No8U8IzoeWf75eSzW6BOTdgdtAjLA4foTwCQNn+xjdZiVEq+4vVx\nJq58VUAX2zmYfqgX521inHgYvbXaveM5ga4LEeJD/LlBaKAWlzQmysgIYEki\n7KQkTnZHsT5n6iGhLGSll0fnzh5iV0mnAk/E8BhbmqjQIipWpC949JFdcyTb\nfkcV\r\n=kLKY\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6c7fa483a798a7a2338f004e0b69ffcf4d08d0f5","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.1","redis-mock":"^0.55.1","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.42","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.40","@balena/jellyfish-logger":"0.0.179","@balena/jellyfish-metrics":"0.0.207","@balena/jellyfish-environment":"^2.3.12"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.16.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.9","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.9","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.66","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.26_1608420468608_0.575134701172245","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.27-renovate-non-major-ca1238f809afbf98410d26b6f0bd24dfdae8c100":{"name":"@balena/jellyfish-core","version":"0.2.27-renovate-non-major-ca1238f809afbf98410d26b6f0bd24dfdae8c100","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.27-renovate-non-major-ca1238f809afbf98410d26b6f0bd24dfdae8c100","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"daafac91d1555e7bedb37a13e31a2ac4c8eeab3d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.27-renovate-non-major-ca1238f809afbf98410d26b6f0bd24dfdae8c100.tgz","fileCount":95,"integrity":"sha512-t0IiMclWtis5/eCpRJFfMZcsJisHm7N8QNjog7xRAF0uAQ+fbnAOypc/AB+l3s+f8haZsITJLOKYGuDTNFFsGQ==","signatures":[{"sig":"MEYCIQCCqnN2p7gAqYAK/WMD+bmtPimtLOEa5IBQ1jiU2doP2QIhAObx0IwWbmEaMyXgweLGLmMAsM9ve8DOBfYWKVkPzLmK","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":575840,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf3vmpCRA9TVsSAnZWagAAE1kP/1qO3WsKS+FgSCpn4Lq9\nyMNyI6GpbmaDbxbunxc/0BZb3B2d+61CfdILuVazKPrcDbCAFaAieNSkapfN\n6fKGwSKJW1i6PbLWMbLtcopKMqs56J1JAFVnAkioO7PZ8y3VYxb73zxSEU7s\nEJ362085zXr5EsDgQH4Te3Lf0ANJnl+jRGjh6OOSSAvdLeUWguf5BJQiWIlr\nES8hmYNjl38fM9YbdffK2ZzZHY2f63S9WrqXaoyxP1HvryuG59d4cuW6hIeZ\nt1ogpvrGlmj0DuFP8vf/rvs3czruVfVOsqKrXVfu5pmzHWl/saGlAtyZLAX9\n//wd8OPZTgDu2M4iDm0Q5RcmKJMmpIlXl17fyOGYSPKJ79j8U6rIVLy8NEOj\ncDPEr/4RS0tDdFNtftMfDzGqfGrs0nL0yH/mlHO1n3i37mrBWAHpY7Ku2924\na7NmsbuX9msROUXcbDbshYVRLzTBImYk+Ww+UGE+TE3Lx7qsinApVZ/SPmkv\n6rRrS7fxskIe3cp03/qZY3o6f8EG/dXnddinWiDRXEx9ERJrG7x1AMS+5aQI\n5aeN+SmP3sDxU5moly+6TxFNdgxtdZ+FdkSPEIGna2uTp1BaisZzsBt8nZdn\np0zOm/XDabWLhx+HBJ/RG4LqDU8mwlba67715WGoOFmfssxymNSCgOa1+qT3\nCqjK\r\n=5u9D\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ca1238f809afbf98410d26b6f0bd24dfdae8c100","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.3","redis-mock":"^0.55.1","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.42","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.40","@balena/jellyfish-logger":"0.0.179","@balena/jellyfish-metrics":"0.0.207","@balena/jellyfish-environment":"^2.3.12"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.16.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.9","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.9","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.66","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.27-renovate-non-major-ca1238f809afbf98410d26b6f0bd24dfdae8c100_1608448424902_0.8663755203094812","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.27":{"name":"@balena/jellyfish-core","version":"0.2.27","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.27","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"489cab8c10e77208509d2007a28f8ab7eed52af1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.27.tgz","fileCount":95,"integrity":"sha512-Pd/wOHPul3i/TLWZp0n6E+L0piGrcjhEoOhOl68nk7nUw7l5n79g8aZPuW/H6Li7BmzWTFclTdDRXLmv1UblKw==","signatures":[{"sig":"MEUCIQD7gtXSYeOqPLTUywIf8slNVwFR3LYVAPwdH0wPHFMS2QIgQqV7X6qLqjFvrx9HQsTmnamB85ryw7gkaaSbZ9Ocjg8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":575780,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf3vrJCRA9TVsSAnZWagAAYyUP/2aVR/J66w14A4pBQnV5\nEJTkFuPMeJ3AdrTT5CvbCANbmKUmHqLmtYvGufbSu5WV778dhiI0r90Rr2dQ\n+iQ4YeSavJK6tOe2mBwhZw6Khf6h9bMZDhvfVe9wzVL165MOzSJJCYka0Gvw\nBYz7OvqOrPfl0Hx66bijJqWkYf4V8NqobtEoPEJrOqjRjjHnOi1epMnzk95B\nxCCoDadq2r5FDbZENlWzr2sjpLtqXN+l+r3Y8BxRT6w5zIeAdMY2wZjQ8thG\ntmICN6egYgTiWRqqOUQIK0Kk5lt7E11ph341dXut17RQDOfCv6vEtP614JL7\nKHYtPBli5q7cDHR/qyR2+CtxbrwkVG5iOnY9L22eN3oG4ne6Ss0C2TetOz2A\n7ZvWNOMhKr7YWAP0LsON6OD5LVrIvuUok4KrDIE9Dgp0BDTPP7cy5qPInzIM\nxDjEJ/BAikVwDlTOKkz6uwVdfEI82btmTW9GSMKdQtiEnvzk+EyjXGVrguQc\nSkiLnXTpxHKCx0GdLlR/cv4iTSq0L+ZguSiNgiqhgnmo5DIEx28j4rzoHttD\nYxQZzGD3ElGynQ8tewAdvN8vO6g11F2z+wuLJamGqOV418xUeGcBEyNrZ4Y1\nVCyoPY8SYSxMYzYTOPjUst4nzhGb3IF6atu1qNv5FG6vKLwrhReEvNf5Di63\nYCRR\r\n=rkEh\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4bffbf592103d3bfb6fa5c4cc77bd04c2f0a31bc","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.3","redis-mock":"^0.55.1","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.42","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.40","@balena/jellyfish-logger":"0.0.179","@balena/jellyfish-metrics":"0.0.207","@balena/jellyfish-environment":"^2.3.12"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.16.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.9","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.9","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.66","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.27_1608448713071_0.16889087486768384","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.28-renovate-non-major-84607d62abf57359df0f049b241a6fe15afcc1f2":{"name":"@balena/jellyfish-core","version":"0.2.28-renovate-non-major-84607d62abf57359df0f049b241a6fe15afcc1f2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.28-renovate-non-major-84607d62abf57359df0f049b241a6fe15afcc1f2","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6a2272d2815d283145c0dc695527d836abdc901d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.28-renovate-non-major-84607d62abf57359df0f049b241a6fe15afcc1f2.tgz","fileCount":95,"integrity":"sha512-ZvA1hlrZXR5xi3iVglX38QHB6hKJohpobL6OlIvcZFKGldsDQEWLVygYi16ent3+zIrFBZEddZATlvJZi+fvbw==","signatures":[{"sig":"MEYCIQD3lvMdAGGNvimRwlBLsggInYai0osaB0tB1tmRYVD3UwIhANk81gvLlsbR0YDwgBdOBxLCyTzIj/CSEgpXmnw9xIle","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":575929,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf3wPkCRA9TVsSAnZWagAAapkP/2lNkszUBn2T6eTNKJsC\nbdtQcFdDPDXI/cqYW0M1JQMxi6iN5lgEAdBpa5/95VdZemQrwaGv0oNRuSwj\nBSCe4TINw4uZWqCj84+1TjbHLgBQ3Iw69YwByTeIfCZMFI/5J4OF5aF8/s1j\n0MFNPyCpCOPp8UthnEkiHgRamtxfodf1dN5PF16T6fgv2EofGkMuvkFn9pAC\n73FqtWcgJKMjxQMWWW7pLIHqDFl1Tr73Xs49j7x5O7qlEwykbv2nbi5XeQm0\n3mwyKYHtoy1FV7fnf3fA8Xfdo+17riWcJMqX9+fnHnaBcd46X5doniFSLYoF\nV5AnE1Yft/PjB6KFiWPeoZF++GNOA5JHz1AkIE94CqfU16Q2Fc1/9dYRfIL4\nAqwqpkkdkGmlGnQDk8JCMle/leqJ272vV4S6EX9qz/1Jm+jWMRYtzel+l3Q3\nsLnwBDonzXOVpuJc4/2zjEf/t7n8555rVDS8ue9whu7igRvtQHljZpFBSWpa\n4hb7yXxkHbMoQrcccRqweFbOwVRJGVoYm0yZQGOKh7Az+P7vJNugSMp8YQAq\nI9YJ5BV/cWHUnTO3o7/kwNpkR8bjU/dN0dtuBmQwSJJqJ2ZKS3KnLCunTaRD\nVvDMwa1aOPpU/bIWFW1DY6rTA7sRlYcfqfgVSEbNHZDL6TCg5WlgESwQIGJl\niFVg\r\n=QOfu\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"84607d62abf57359df0f049b241a6fe15afcc1f2","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.4","redis-mock":"^0.55.1","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.42","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.40","@balena/jellyfish-logger":"0.0.179","@balena/jellyfish-metrics":"0.0.207","@balena/jellyfish-environment":"^2.3.12"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.16.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.9","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.9","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.66","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.28-renovate-non-major-84607d62abf57359df0f049b241a6fe15afcc1f2_1608451043536_0.6036403996201571","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.28":{"name":"@balena/jellyfish-core","version":"0.2.28","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.28","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1416ade92e3cd9ec317500cdac25f8b19ebfbdc5","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.28.tgz","fileCount":95,"integrity":"sha512-XtuG63mv4GUrsLtw0Tmt4o20ztujjGsiUKmtq2wSfVjSxK1mSM6Bju420gy/iP9ZxTW/UTyPOmnEVXaHt8p5jQ==","signatures":[{"sig":"MEYCIQDCwd5/tt5m2tMF+ngJ382b5qS8FQznTSDylevsOkepfQIhAMYV4HkNbbSt/ihlvPSXzcr0Od+tOu5ZoEGdUaZNWU5Y","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":575869,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf3wUsCRA9TVsSAnZWagAADaIP/j9rj7j4L4MeRn/fF5Cq\na8onXCCq0FgFMLTOwPgZvUQQqZDM2wYdbZMo79BNyWbCicDi91LAj4KgpvRp\nYg1Mfb5BRlGhxqKDjXFhhCo2MJRRfn1hrigMTkztbNfjrSmCnQDr1Qpu+Pd+\nutI/UY/mAsy+qvz0m3xE5xP4+vEhk0rBje2GV13wiDtoBHQCfl4HeSr5He9a\nSUbA5mAYKBdesr5XYvlAd4DrMemZFVxEXmu9zVV5tsT+Iud3TPO7Hb2AGeG9\nRNgRzEd9lm8F5rxFAcfBEdypc0VK2FtqyPf3RYWym/4GKZS1d4cvra4w//Qq\nWL+d/RTX0gXMdCpJ/IgIPcIiRMj1BdIWjAVCS6ah+dsmjbWz04ujhPc1oMN8\nydqpANBZ0N+VAL/3qRvBNRh+eBrrjaAbt9kpxGmKSxv8pDhXM/5ITQ97ym+2\nf7m7pgPCxvscHCCGcrNAebTptGUyGLbRrmC4PljVGDxVgE/4G9Rx5Fdrezos\nW1uhNIEra0tkrWuONrftNdRfu/qaS5KqAt7P1owQR4yQkimXaMaPnqMa+xdN\ndq+/Q1J863ORkhuD/bv3TAVUE6raCXck8ifoZaSFosAh/+zxnGEDSygIdVna\nizI+AeWUbjbY2A8MjQ4V35CYLaeRqcTIgK6x0LQ4iuYEGf7FZ3tCgkOomOSl\n2X9n\r\n=mdi/\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8631a02d26050153d3a0a5ba04cacd90c394e187","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.4","redis-mock":"^0.55.1","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.42","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.40","@balena/jellyfish-logger":"0.0.179","@balena/jellyfish-metrics":"0.0.207","@balena/jellyfish-environment":"^2.3.12"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.16.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.9","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.9","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.66","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.28_1608451371960_0.5071722566075276","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.29-renovate-non-major-9c59eb4f1781d4d3bfdbba4f289d6036655bda1d":{"name":"@balena/jellyfish-core","version":"0.2.29-renovate-non-major-9c59eb4f1781d4d3bfdbba4f289d6036655bda1d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.29-renovate-non-major-9c59eb4f1781d4d3bfdbba4f289d6036655bda1d","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"892ea83b1384006a125cac0b79ada789a93ddcd4","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.29-renovate-non-major-9c59eb4f1781d4d3bfdbba4f289d6036655bda1d.tgz","fileCount":95,"integrity":"sha512-qVgL9YKi1OlDQFZQcSNwC4gBvLsgmRPbWxwEP8yP24xU3Lpb2xYR9etvDgBg4u8uVdqwOoDFF4AsHbfY7jliiw==","signatures":[{"sig":"MEUCIQDUIjLFnuXBjLyets4BgqTzZh08ZGj86cPdtlNqJWVLbwIgZP1yqcRLuof1CyDDhjNHVgvLoQzI9TZKbokCqQgwk/g=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":576018,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf3zcLCRA9TVsSAnZWagAACwMP/i8FFkHVFH3moposevCJ\n39HhjmAh2jSVBV6FF+mo75VHmI8XoKevn6H91NbmE+hsVjopvsK7RSqtLn8R\ndQTGWdsQmRg69dKZDU8ddUVSzHTf3S9hoiDXAVgpBO0quFJVpLJQjwTCos7g\n8CNDuKK2L2uiziSf0OABdFxAZASazXECCZTvPS1MCs6OXOVAvAaT4KTudCDa\n/4/ROcSbFD7Ny57IDwW8Vx8kuT0yKOAWz5zKl1sAWFN4XZ3HiSsjd0nkHGLE\nHCk7G05xOngTUau6SVVrpnb7FAhDq0FS+EGIsS/zScl3nIj2wGZMjXSBKi6t\nDg80uc6cyEHy5BP90GrdF2k2fIcq0lCEbrVbP0UGZsU6923IgtNatJj4bQHC\ngmYwqSdC8vFAT5qtgluZuq7ANoxicEe4T7+5eSO/NRvsyYIYq43f1XOUM7H0\nMgJTQHBovCyZQe+NydN8/IAFEDkk1mJgxXmGP0OvitQz9G36hzpvSVMQgdDx\nMmUIIMDcqhZmAS9IJS9oVYi0h4jg8CeTlKCmXYUFeCbCSepjw/qiVicUOK5h\nJxGWNaIPowccXCAYE6DpS9N4Tl6cC3yenD2sHM/4AkC8tTBqASo55OEINowE\nWmNtw/xJJ3f9P7hL8Svvi3gu4SxrvuBzzgj1vNfrHDE5OCyvvyZEGic6cCdb\n1qJF\r\n=T3Yi\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"9c59eb4f1781d4d3bfdbba4f289d6036655bda1d","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.4","redis-mock":"^0.56.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.42","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.40","@balena/jellyfish-logger":"0.0.179","@balena/jellyfish-metrics":"0.0.207","@balena/jellyfish-environment":"^2.3.12"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.16.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.9","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.9","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.66","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.29-renovate-non-major-9c59eb4f1781d4d3bfdbba4f289d6036655bda1d_1608464138287_0.18852964058635635","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.29":{"name":"@balena/jellyfish-core","version":"0.2.29","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.29","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5edb78ed65ba6766e41a929a1374716e7de7a41d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.29.tgz","fileCount":95,"integrity":"sha512-1DuAyRBQyKAqyEOlHZwR8PkjEhp0HqYXpCMPIvOkgUIYDR+9CpRQ4GIaFDQ2LkEEbKD31Lf12OoPvvvdy4BaXw==","signatures":[{"sig":"MEYCIQCzeiXxFZdnyLog+HuCP8MaUJPhQUB7hjbhP0cJskE/TwIhAOacP+iSp/ZKRfKB5DCzQresA2Anini/PrA/IcqRif50","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":575958,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf3zgICRA9TVsSAnZWagAAr8wQAKElh15BjuSz316Qnau+\nTUh8sDrg0+ZTdWPoi2pxRRSQmY8zuI1m8WnRt97hyWd9kZMwJF7S4I43eaUI\nQFEihMK5++lXnOljSGrwPx/g7sJBQg52JaZjR/qOg527+3nEQcMsQfH9iI71\nHe4KH/Tu4DNk8fL4LEwtL6OD0aIbUGwiUzRFqzEtkmPNcG2X9wg9VTgDGRt3\nJUdNRIWHHGka2nIrkDktG0nYmTooCNkPN9QPTb0Xg8alAsmlrkOM1/GGWbx4\nIkePdn7f1j2ZAl1hkqicKhrTZo+6WOIL1bhgolD0+QLRH9IOsrMq5dKcpI8Z\nuu7VnWAdkSuRMssfmVvYBOZ7w1u859dMJrirWmJOiTG+KdIXjVRAB5uzn3m0\nJI+fkjLWt68KX0owHLOSQ5XRnqNR0c/1GsZq2Im0BandIlAi09x4B1J1zqLV\ngWzaJ2uyPwa2kGOO77ODzODXX65JvcDsQFVFgS2KuvGWbh8xoiwMkm50vwxY\nUl4VdU2LQW4gPe6JUtMQ5m8bW4VCrip4cVa8QNS3VBNEhWLz3G5hMpcVnOyB\nuppV8h4AZbInsEeMSPFBZoVplyKTdvuUlkxnt+mmpwAAXjjVk0hvj/jGZ8YK\nz8fg39uK7+DVAdr3qqv6/jJ8dSqQNpVAEtscbYHmRE3dhsWQTiIhhhFz485Z\n5J7C\r\n=L6/Q\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"64009e4cdaa0adc7d55b641b41ae6afac5eb1a99","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.4","redis-mock":"^0.56.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.42","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.40","@balena/jellyfish-logger":"0.0.179","@balena/jellyfish-metrics":"0.0.207","@balena/jellyfish-environment":"^2.3.12"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.16.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.9","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.9","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.66","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.29_1608464392191_0.13397306209496063","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.30-renovate-non-major-4885a018004ff4fe6eac7b582596a9b0866930f5":{"name":"@balena/jellyfish-core","version":"0.2.30-renovate-non-major-4885a018004ff4fe6eac7b582596a9b0866930f5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.30-renovate-non-major-4885a018004ff4fe6eac7b582596a9b0866930f5","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"af1601a9fb5b75a4b082289b4b30cf427ff84b6f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.30-renovate-non-major-4885a018004ff4fe6eac7b582596a9b0866930f5.tgz","fileCount":95,"integrity":"sha512-5mkbnUF+ndeCwgJQcbGg65GXQorMc3Mw2bX22AH1HujJ88x5/5QOlnC+f+ajNyYiy1HBAz7rP1rsP0CjiHRsDw==","signatures":[{"sig":"MEUCICbGsthtl0B/Awo7xKnvM7v2tAy0BmAcg3Bt5RdPIWUSAiEAxb5GvEqaaz5lqxS/g8TcC4JyIzqZI3ODR2+/8fTvSvo=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":576107,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf32QuCRA9TVsSAnZWagAA1eUQAJ7OkiS7UuY1/iUvr7Jz\nEhvLa/qPQ+5uj+MT/lsN5x/w4PJaOZq0XrGaVP4Z4tFSowKpE12oIT6F2p8y\nlQ2viBMP9Pp6jouNguL2C/wdTtbgQSCGoZi7qPC7b1T1Osf7M5gaFaGwUtX6\n1VEHs3UaPgwV2+i7C1yC8wC2V4wYsXuY8no8TEBEk+M3a1S/+QR7cuUi2qLC\nHkQhTqD8PfwUQqvmknlL22I9CaEsL50a/qK/3XVZrvu/NXWU5aUvSsKo0kUG\n2raXch6fOFoXUJzqiox4K4be9Vyuy9wuBpPmR/lIHfxDJ+5niFLqeBo1VD5K\nUAA1ohEc/5UoZ0weSX8zz8Za+sTvleOfzGmccAJDm1RHPQ/nHYEaT2revOXF\nQGE0din2lH693zLf6oAksA3lFA0Vgifw5KoVOwIMXNpAwU3EV4kWGqBEoMqO\nzMmAMb6Lt1mwng7B2FkMV5L7a3uGEkLFCEaBi+kNhjb42RDyVquRaN/rynBd\nndkLUx15C3D0OR3zO986ekPcidwSdATIIrPYk+KTDlG6/bpBrdZ8G5L8GwaY\n2PwCn5hW5UlLKWjuvtNoMv4a6kgKRZOCRzhoTpK4bgLtaBUr2HZxTjbGf+vH\nOsHoKMCTz96Dst7LRfF/jCbqImzv8olK5JUDrg7U8gNHQ1yc85u9UrSIzj53\n+R9x\r\n=DySv\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4885a018004ff4fe6eac7b582596a9b0866930f5","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.5","redis-mock":"^0.56.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.42","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.40","@balena/jellyfish-logger":"0.0.179","@balena/jellyfish-metrics":"0.0.207","@balena/jellyfish-environment":"^2.3.12"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.16.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.9","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.9","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.66","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.30-renovate-non-major-4885a018004ff4fe6eac7b582596a9b0866930f5_1608475693727_0.011589465321479864","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.30":{"name":"@balena/jellyfish-core","version":"0.2.30","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.30","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"793275bd60dba0166bdbfaa2d8fdcaf80640e70c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.30.tgz","fileCount":95,"integrity":"sha512-Qb8goXFQTq3dgKZ/6Vd+i1BjSmiF7PfqmK6yReca0X8DS8y/eCx9ackyMZkzssk7H/P6OHZ4qnVdif+7ZaBVBw==","signatures":[{"sig":"MEQCICTE4lJpkfpxRH0IbD8g60onZw6W/TNm0HIXwFysM1lzAiB8wqfWj6dblZhBF7Xr5/+cpd1wv6+Yu48oWYNAWqSEQA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":576047,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf32WwCRA9TVsSAnZWagAAjnUP/3U8Qxuoc6UyQAQliXGR\ntemmIckDGWWwQ5RkT4T/F809VRS1c+EvLPnvM5Thnt0yeQopOHON/dITvM9O\nF7a0lBA3gIPjgIXLdFugwHYNe+OhQZyXmF0NxHcSrmps4IZmETyinIIoIdkt\n4OB2rZ/zERrbBR1sjmlpbvLeTv1lhF2H759tMiNPTSBgmUGWruxTILqROvg+\n0WpOA6JIO/KxHmSCtfMkiyRtPnTd4atXwNO1suaJwhGFBizz76GmiP78znAG\nlOBsrg8LmKeITOcOjaIDldZFLQIpwvS3vjD1cnpkJn3CCq52J+CSXvVYDAJ1\n4OITmC3rhLWSWD4cNV8EW7iVaf+u7nUZod5qJQrYYdZEFw00HSONxnO+Xwd+\nJjioBdJhTI45n094hAd9t+dJJPQy/YBktMJPuHl89IX/ora6IeUH5Uwc0RpY\n95r1RQgion2RpmS3Nr0yyMlV3amDGw5Q0GbgrtYepK+5+o8JmcaWmGgI242n\n4XSXv5BFbdYhmiV10EgzSq1ofcrqM/iD72OZf1DYxmvDzxthxe0c5VkRctIQ\nIFZXlAGXNtrQFGJZSDVmEY8Ws2Zr22S3aBwZs9qXh+exdNNAa1eF2jGC5+7Y\nzF/4pWC0PcbrjHhfZn8omFpX2ZvY/Yciw/wsR/kWZgdMYvGYhYMX3rKTjjg6\nNRFE\r\n=H7QI\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"144e49c7980d0e2ac706ea3f8835c43f81e7732b","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.5","redis-mock":"^0.56.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.42","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.40","@balena/jellyfish-logger":"0.0.179","@balena/jellyfish-metrics":"0.0.207","@balena/jellyfish-environment":"^2.3.12"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.16.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.9","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.9","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.66","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.30_1608476079626_0.3072111235525934","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.31-renovate-non-major-02bb90187e1e5a8d57f3cc18e156b4df29495187":{"name":"@balena/jellyfish-core","version":"0.2.31-renovate-non-major-02bb90187e1e5a8d57f3cc18e156b4df29495187","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.31-renovate-non-major-02bb90187e1e5a8d57f3cc18e156b4df29495187","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"bfecedf5de9f130f27deb97dced5a1537b0d34f9","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.31-renovate-non-major-02bb90187e1e5a8d57f3cc18e156b4df29495187.tgz","fileCount":95,"integrity":"sha512-CrZAHvYTJ8y2fK7kYv9mxQi7Jjk//ePMRikgN/WDS9uAbnIJBj/sjWLfLkhdEeTxRu9PNAipJrndyV06tr6PYw==","signatures":[{"sig":"MEUCIDK30hRWmoAMRIQxNh70eZs6ZZYrRNX9eN0+auJ3m09WAiEAp8yg4OiYA2YHWXXsRUXCkhMmVmKX5JPkia9tJTctJwc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":576196,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf34ICCRA9TVsSAnZWagAAUe0P/2bqTpQph+C0eB1SrtRo\nGa7Ym/qzEAvJOFGWKkboKmVx7+f86Nt8txCUb/T++YYWOWiDlk2m+nDVtNle\n8pwVZmLBoiS/nCs4KW08ndwANfnznqPPinAzAw7M/gxwCqpHOWfuibqqDWNS\nRqnwY8AjlcCC9sUOabUczYqaJ5KIJHV5INCtGq+xmkmlDaKFZU04EHUlJxqW\nyoZ4pr8eRJkGM4vijfxQHKJqMaqi8QOcb/fDYprGdZb+GhyaUPae/QYaLar9\nvQJuD8zu8+fGHR4mITRnpFuYa60BbPLR582y+jB4ODD0N9cGP0VM+VKhpAe6\nz04YcoZC4VjUqYCyt/umHPGMlBNYZizl+dSB9gF46Y3WhzkGGB8jzXpYTg3u\ncd5otjGtoiHggKpaUcMRjdlRQ3wenr3wBzNkLoPuheKkg2Ews1W//HZvv24a\nVc8UMqbM0pbMBUzOAP7qW7yBJTkf6gKdHJ8lSzefbRl397TU2hlkFsIV8YKg\n3zOLCKoM8jQJ/fNARPV3Y4IxHCwjPgh0Soi2cRgr5GFN1PRDVz2brNaHq085\n0i4navFuJMDmzO/muGEiShGTSGE/FSIghxbNnkR+gyxJ/x9uPqS1qUqKMosD\nMwEENfeH4Ug08/2PvPgufZgU2+TvVOpCEcAox/VCzN9yOqffFZyPbjgANmk/\nupID\r\n=9w8a\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"02bb90187e1e5a8d57f3cc18e156b4df29495187","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.6","redis-mock":"^0.56.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.42","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.40","@balena/jellyfish-logger":"0.0.179","@balena/jellyfish-metrics":"0.0.207","@balena/jellyfish-environment":"^2.3.12"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.16.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.9","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.9","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.66","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.31-renovate-non-major-02bb90187e1e5a8d57f3cc18e156b4df29495187_1608483330134_0.9058087997604869","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.31":{"name":"@balena/jellyfish-core","version":"0.2.31","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.31","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"24aa402c1d5e94b5e0b98aec6345543e244eca75","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.31.tgz","fileCount":95,"integrity":"sha512-5zdiOBppmDNIugNqMoFuX1gYAj9WgXb81u9xOkRaqBEGKxOEMRxhYmB13utpsqaY+x2oVyQCXF+DbdnhT5X9cA==","signatures":[{"sig":"MEUCIQCK6t4DVoGEolL1N+Ksia4MnnZBUltGrsg71U/y1vay2wIgJjqZRXCcIWmJl3ahKwMRfWoyD5xVrjnNSmRGLEKGP08=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":576136,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf34MCCRA9TVsSAnZWagAASEoP/3HxlNPYSr+D863/dcEk\nxnxGy0mE99ReShL/XyOFeCtbDmob1HAYx9X8jbaVwf9uSfeBuC997XaLVcKH\nKfq+p9GMV35jkKd/dYQ+J/jSieKb1ej8W7zrwEZJLDUZMmY2Vf9Vhvp/CbM4\nHh1RlKiIC7x/Fct6JfWAlA+yMLQdy1RJ6V6G22CY+02vLZofPVPgZsDmO51t\nUvxkSE3Fb2AC+tqqtmQuNye4TmJYqBWuVqUr6mzF2meO+v09F4UbEHqlPy/J\n9mQtPzcEm484p+KzkJJ7qGGKUxcM1dLKOm1P8WyGZoZJfw8XDbsrGmtApkaG\nwFfF+9kBZ2eMQyZ66doraCcvvjPXWouIbABzzE8QHCY8otF6IkvNjCHvDSvp\nffYm4PHGsyT7Wa21A1w0BqVgQrpOqMKpH+zmG+u9YxOuT8VTthWEplC+56B0\nLipTlZBFDX0Zwc0Q8jMfa8T1s8/s3gZuUqJkfnzYPI3q0KAyjuIe5XT+aV2S\nKUBO0z8d11dpgfLwvuUO2NYb/Kk+r9EXASBE6iZrVpPW7ubpjVzTFXvSP97s\nRZxZsAHKYtFxFH5gTtj/6GEmLcNMZPXCUt7dP2ZNrqUWftmUONOqJ9mIzMI1\nHgNybn9LTud0Vpcg1yvLTdik7I+6VDSfjQZjYGu1wtwZdIhBr7DAL39i6O4O\nYOdA\r\n=bvkb\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"bff8ada5f4ac20bfd176d82c4142933964d7892c","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.6","redis-mock":"^0.56.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.42","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.40","@balena/jellyfish-logger":"0.0.179","@balena/jellyfish-metrics":"0.0.207","@balena/jellyfish-environment":"^2.3.12"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.16.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.9","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.9","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.66","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.31_1608483586210_0.29219533910888273","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.32-renovate-non-major-d93717ed9bfceaba448a47014d5b39709eec85ab":{"name":"@balena/jellyfish-core","version":"0.2.32-renovate-non-major-d93717ed9bfceaba448a47014d5b39709eec85ab","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.32-renovate-non-major-d93717ed9bfceaba448a47014d5b39709eec85ab","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ba6bece3f4d7bf73825bc55c3a3a73b857e995fb","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.32-renovate-non-major-d93717ed9bfceaba448a47014d5b39709eec85ab.tgz","fileCount":95,"integrity":"sha512-8rovOOMLwMWdqCsInlw98bP+ayVz5UXuVdVXAZEvf8dq/yK8h8WI/5YFGkI+/PEhuYdzCEIQRSKUQBZ8LRGyQQ==","signatures":[{"sig":"MEUCIQDBvRX8HpRpAvcZ2Wh8jsMCRNE1s7p0tiG96GkZ3abd1wIgSEmQ0QQc2Zqn97/lCqfntxHO6JtuSzR31eICS/Yfvs0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":576300,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf4LvRCRA9TVsSAnZWagAAG1wP/jxxLWPErnuySFKFUQPV\nP8ETmunWmgI4de00KnQ/hGz1Hx0XUEl4m4i+QM9LdGET6LDITNDHhdU7JJiN\nBojs8DDj252AnRCj0v/mrfAC7lLolxaHGHzN9xvwMwDtL+1faH8bCCZLBAIQ\nFHgxr/YqnjnpyazvK7xsqNrh9m+zHUjxPepzvvhFWwHPehVMwc23Tv0fps+k\nGHseo01ZTGVpAF7CAlRaIqPMV/swjqeRR2GxT3JvIPR/yo3LfhPl399ZUTnJ\nBzvgE3SGE65TigRANsQ9boPGttstViDFfEm5unyg+0Ftr9186/h4UdAbhoZe\nbSijs77ItsDddT3vECUImMfeCFKlknz05pE0viLxbCHHF7q0Cjd18BdxSCDc\nZ6Epxt6vspAHlfVpe+RfqgEaWN4BS8vnBXpRCCdXUpS3pEo4lNNPlhZpgqr7\nMO05Yz4XPqrXDSUb2ctKHdgH8Zs18bEmxKWYFf17U71D33aF1kg3ZGgbXHGS\n6WtR3sCCwNIdm+D7OG0VuXwtyWpvv7Pz4Q0IyAnkhdOcgYX1NxZbqJNaTW90\nJHkgo4otuhyHFdvwXSGtQY/cEMitmcusW8Fz2k7wWQs3czRZQQU1Bsoa4ERm\nfZ9y5f2sgJZkXYepKzBAOUXLh7c3ucYtvy7Gch5kHWbBq/KBLmYf86P+n7Sm\nJ69j\r\n=rpCk\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d93717ed9bfceaba448a47014d5b39709eec85ab","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.6","redis-mock":"^0.56.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.42","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.40","@balena/jellyfish-logger":"0.0.180","@balena/jellyfish-metrics":"0.0.207","@balena/jellyfish-environment":"^2.3.12"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.16.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.9","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.9","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.66","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.32-renovate-non-major-d93717ed9bfceaba448a47014d5b39709eec85ab_1608563665285_0.38579059962425855","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.32":{"name":"@balena/jellyfish-core","version":"0.2.32","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.32","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"658512a1c403ee91337229942b21dd6edd9663ca","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.32.tgz","fileCount":95,"integrity":"sha512-mhe6bnHD2z6uepgG20hk8JqinryAPzrqgthRvvuyDmIxZ3fVyhIyEj7nmBcZdrKbV/HzsxCIKZj7oTXllFRWfA==","signatures":[{"sig":"MEQCIB+R4DXT9ffAoSizn5f6s0w3XSXq+hoVbYYiyDLneKr/AiBw9h6665K+S9rq0ujQe7jYEHH1/8v3Jrc/YAMO+6kXLQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":576240,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf4L1jCRA9TVsSAnZWagAA2w4P/jHVpe5IaLjFNEkUNvCF\n7wITEbti4qgWyqfWupnaolpUGe8tlVwNmVeZG0SkGGB3qfALeIrlMQFUVyl0\nSyQPBAfX8omQa0Impj2nxtwTctPbnxv7qtOJqpqLuTQHZdFTdEuc8z34cAnU\ny6XcmLz4b2KWOaALB4XPVOolzoW/20jyvOvrRbWjFRUo24CXZOhcKg8EUbnb\nIkXEUHTkD9LsOPanlnV58sz0DtVs9iAsHAf81po/vVx0zDr0FB68aPxJowz/\nzCF31f0Fssp5YwBZxfAWBh/CQcVsgHFTiu2XMmnQ2JnwQ2MtruJ/hYnkSoQx\nJCAszvM9Ksjn8gJR4bRxRiUh5jYryjw0R2nkzUHvsuuSHU2p7E+07UyRykt0\nnJewi0LwA9WyfrGrj3MsdIHktr7mLDUi8qE0spA3LTGjA6/t/c+FW/QUl+J1\nsu5gY2S16qBWdzbbsAD1BscG8tTmAU6tU3TFb43a+4zdU5ZnFNswcNsmicVt\n+SFx5gnBBV69b+ahSTcu3GmjGyifl6PXma9RZMnKHhLXPzeQKWdQPvHPXkWp\n7Wtw4J2mE/okKJ3faxs2croK4qMXeViHueUODwB7xwqHFDK4RX2jalGPeYo+\n2pdRk4HsfY8FwwsdDkUk5jKIrMn7BmS/o2+/rcrNrRSOIeDSlH51pJKwRJ9y\nK2zn\r\n=EjmV\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"fd1df1668076ca13d789eba9ae03a9e4f32ad53f","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.6","redis-mock":"^0.56.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.42","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.40","@balena/jellyfish-logger":"0.0.180","@balena/jellyfish-metrics":"0.0.207","@balena/jellyfish-environment":"^2.3.12"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.16.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.9","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.9","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.66","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.32_1608564067130_0.9134914170013848","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.33-renovate-non-major-31b6678e459a7a3b3c5d5fb33d72fd48a1b5e7fc":{"name":"@balena/jellyfish-core","version":"0.2.33-renovate-non-major-31b6678e459a7a3b3c5d5fb33d72fd48a1b5e7fc","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.33-renovate-non-major-31b6678e459a7a3b3c5d5fb33d72fd48a1b5e7fc","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ea5a78c781da189a87bb3bc6cbe520e482305093","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.33-renovate-non-major-31b6678e459a7a3b3c5d5fb33d72fd48a1b5e7fc.tgz","fileCount":95,"integrity":"sha512-2cbyfSdG6+73eciRHvd8DuLrC2uosbm0Gf7WHUyMjxrBFYIN3L3VXLFNSkyzm4qmIyuvT83Dk3OK6TefpjyAEw==","signatures":[{"sig":"MEUCIGekpPMtZzf67ORhirDdbWAgj/qONAOIeFujDnRVW99MAiEAxEVo0pOR2EyKj9gePmRQDh5x8w+ZUwKENXrQz1p0RhU=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":576405,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf4NgkCRA9TVsSAnZWagAAML8P/1CTOzehd84KnpYPB5WR\nVJGvJjs42bXFQEBUgkjRYyNJIfo9d4JzqHwNojdCd4uI7bTHMe4jDyGktMPm\nknxytjZdy+CPYGgp+g9mxMIfhWSI2MGj1gRaJRLrrzCOmhiIgfFxYFI3lZO9\nsZUD75q1CJ2im7fQ15hAEzJFxBxv54jI6lpj3uk2nUrD6mW7Yl5sFczWMPT/\nIV54BYjd52JEJANDgAuAGHZdmCWQORvk4mhwCjzAdy/ygsns/VfoQFyTHON2\n4M6dHE5ZdzGKBudqfQ/yLE2KPsEv2xAljb6mgm8uCd12PtL6Tm5Qmypx2WdZ\n/seZB0Q0JgBAixLhdn0bQ/TO6DBDqdCgeGiWc1UP6JpCEgsxxCizKGOqFQ0h\nlbIo4iz41EZ6VZiokywRN0Z5dBY/AUd9ji87PpjEYjJfOXKhybLfkZuUGXvW\nyCoUEdK3NHYCwBHrg/KO2xF1EuSPNI0llG6ZWTlatHoFUsQ186EbIU0aXxf+\n/k5k1JfoIkm3FGq17pqUdAem/ztZqfvKXASxPnJ62NQKRwNIrn4c7wd3AS5c\nwqOxy8KrvIq9DgjpJux1xtSbLnliC3UuHwGBQpIqfmMp6hBSTTKwn9M6cD2r\nFwB+SX7GUia9nO/z0QFG1bEYBzUtgoELivFLzVJmgoMbf+2ND+rMsbyIAgfz\nq12I\r\n=kC4o\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish/blob/master/apps/server/default-cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"31b6678e459a7a3b3c5d5fb33d72fd48a1b5e7fc","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.6","redis-mock":"^0.56.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.42","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.40","@balena/jellyfish-logger":"0.0.180","@balena/jellyfish-metrics":"0.0.208","@balena/jellyfish-environment":"^2.3.12"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.16.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.9","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.9","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.66","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.33-renovate-non-major-31b6678e459a7a3b3c5d5fb33d72fd48a1b5e7fc_1608570915910_0.5667502604018517","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.33":{"name":"@balena/jellyfish-core","version":"0.2.33","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.33","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4cb531eb9c418c9693b29b14bab9faa2ce61311e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.33.tgz","fileCount":95,"integrity":"sha512-S+Z6oevC39DYuJwLOeDSyu/kUtlJ060qYVxDxneRApmFf1HX3yUbx+J4CWIna8uNTNMQ0E4roZlFxyH8bZIlpw==","signatures":[{"sig":"MEUCIQDc0ohTW2mXNQHrsuwmKuWs6tPJkHbH14THjyubovZExwIgLPOi2HaYXj5h3IMtFwt76GQvSx2r+jjVYZR7GuVpq3c=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":576345,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf4Nq4CRA9TVsSAnZWagAAbr8P/0DUGc0TbMxZSllb5oyN\npk2n0qSNLRVCAq0haktqFW7f21bGxC6Mq7sqVPV8l//w+Mt18uAxypq5/gDy\nvNzZQjhjVmX3lIFuyncKIOFqLi8BoEOlX6apjeHpxLCi0kvzc1Vlp9oKql1A\nyCLjzx5rq/PByqyvSj6pqmjc96E4toErei7537rQBLq64gBjp6PfKrfjt7I/\n/lp/k6M6WSExLwVQu+iw58z/ktgVz5hsWsye4qEKLcWQcak0wKMEzA5ouXjH\nJ4FBVJ/THrUbOP5E5CQyEHh006s0k6brVJ3Yleb/f+0ASDadgafgvJzeL0Vz\nHJU+wDFw7+YDsdSOwFyzDP4PYSvRa09zs1U+7wOf0Fbt89YOTCRqzjAoJk+O\n6RDhCAJy745V7b2Af5DDDy3s2fy5xceGzU/9NkVwRx1BjXVYpfH9Fspu35gR\nYbb5XzAKJbirnedUbAjPmjSyaiM33TTSc92S96AwyJ1DfiIiadAK7UMM4Vfl\n+hZNcY1n0d0inVlZPOXRnoo8pWdLAzCKrbUyeCS+Yx+ZxtRmyUOKq/42fStN\nkuFcJmQDFyeUf3Ic1pY++4DhPvZrwpFmTJlbqYraIarXAyWKFWpqi3FE4Hl9\ngj1tRwANy06Rwf/w3qqYmbh7mPoUSEohNK+qc+8hATuqCsSWoezhJLgciAWO\nr2jD\r\n=TaU5\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"07b6a8e579df301b91682d7add46b1f3232c3b68","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.6","redis-mock":"^0.56.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.42","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.40","@balena/jellyfish-logger":"0.0.180","@balena/jellyfish-metrics":"0.0.208","@balena/jellyfish-environment":"^2.3.12"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.16.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.9","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.9","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.66","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.33_1608571575643_0.7626223696062073","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.34-plugin-docs-dfbfe6881b43dd6d085b923e756079a8cb6bad7f":{"name":"@balena/jellyfish-core","version":"0.2.34-plugin-docs-dfbfe6881b43dd6d085b923e756079a8cb6bad7f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.34-plugin-docs-dfbfe6881b43dd6d085b923e756079a8cb6bad7f","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"64278ce2a1fa8a6f14d9419844a858f46ee2c489","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.34-plugin-docs-dfbfe6881b43dd6d085b923e756079a8cb6bad7f.tgz","fileCount":95,"integrity":"sha512-LTpH5frX7q8BSqPLrP9anfXuCv8w3WfFrgT/EZkNpoTWTUVBoEzWjz6NFOXDoGGj/rm0kQ2Rl4bGTD6hGDCCpg==","signatures":[{"sig":"MEUCIQCyvbK4iXTrPvOYaoDhIZrWUd1kpqeCCD5iz6dmKk0i6QIgCd+e8qhZONLVBEoPJmo6OtL/Rhep+scFyCM8HfzZa9I=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":576481,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf4bpECRA9TVsSAnZWagAAufAP/20ZqRYehsPsyLS6kBvC\n3gPz/Ud+qmFY7/xfp/HaSXWW+3wZvTgfLSDZ5Jj6Oc5jZPCc+Efs6clxkIuC\nVt/JAodDWRCajFvPwdrBQpbw1GjFODDtP/5eqDGZUx+Oi78K8Xdah5FacgvW\nIjQ5yJ0z0XbNScQxg7x60xLi9LtBdqEWjHO2M3Vq9EouO6CbCyxApNfKm9KT\nuv03RTx8Wf9CrGR3Ek8XywiEhsCiJjFhwy04NX8UqFPCn14y0Nw0sHCn5vl9\nEiH/UZ4Qlkxcn987EFaiPs1d251z13LbCBFMILHwhtkSWgDUNWwY4cLm0yJ7\npfeXZnbwBJ6IFmzjRi7wXKwR09lZZHvGCTv/004gXzAYpZ3oHr4ndWVfVhQ4\nx/fE0REJZquZjeUK3bAmDd5K/uAD7wPF476qv2bsVJxtD89FzP7Ys5aNr5vs\nBC2D+iPNlHrcih6pClwcaQPcMsFAF5Y1Ig69HyzFYdqawEhKSoHfyYdsDhUR\n2pN2zSlZ2Zjv7ngwVDe3CH5BWqY58+27hfjEdicI+jxx3Mx7YS0TjiX4GKcg\nn9zF2fl7iGoEtSDQhBK7ZJGNNAPdVIYdvpf0//pBmHroQcnGcCWGdjGeHUAl\nhAYwdH4yg/0Eko/o0YkT+WZxBmDiWngN5HSiYsxu+DCc162GRBOjwaJy7FKH\nYoZq\r\n=n3Zn\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"dfbfe6881b43dd6d085b923e756079a8cb6bad7f","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.6","redis-mock":"^0.56.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.42","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.40","@balena/jellyfish-logger":"0.0.180","@balena/jellyfish-metrics":"0.0.208","@balena/jellyfish-environment":"^2.3.12"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.16.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.9","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.9","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.66","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.34-plugin-docs-dfbfe6881b43dd6d085b923e756079a8cb6bad7f_1608628804089_0.3355510280618712","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.34":{"name":"@balena/jellyfish-core","version":"0.2.34","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.34","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"89f81c6fa4c0959f8ed1473204bc24800f5329f7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.34.tgz","fileCount":95,"integrity":"sha512-geqJLe2TreVah2ubNvXI8kw8kozH5fMwqCM+5twjhTlEiz/Y6wrzadodiZFLecyqrQMib/53Z9A+RcKUWJHm2Q==","signatures":[{"sig":"MEYCIQDHaI9Lh0XYl5yWEsYUc54UXpMmAS4Ej5/P8lQ/D1HMPQIhAL54mQHhvArt+3kxjJJNdPYUWJISuW+hLsaZo3Ovo+fU","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":576428,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf4btRCRA9TVsSAnZWagAAOIoP/0BTBgC8uHgNbkdpQOot\n6zP24uF5nUAFvfywwmBZtkGdG05YdpqLAnmGzmJuDcWfCiTM8jnXuo38MkCM\n0+3A6sceFPWAzT6TIMnhLY7c2XkD8dJQp3/26NDG233bF9ZzFOX24zFx7nEH\n4m3o8OfsXVYy3yXYEDJL6ignpMFzjgZ1Rbg6UMh5ITFf82ZqtgajMJmTeDOu\nw2+nrHaCWtJM+KsiKVK5DhrNv12Q9iPo5zjlEsiU6n889MNbmo7YkQCYtfLz\ntMJWNgCyr4iARBIImITBGKD+Tw1YcjVwf6c7BUfXpCMFrmvhUDF+jQVtNYus\njUZ4HSjSMudfAZLt0vt8dAV7lZUUQrCyopshj+EcSABRzxn+/6E8Ayi0rICM\nWrF8Fa3xYBPbtyEDipN4juUaSV7EE1SFJtioOig9FKZG7DblTF+hbg9uNIIP\nrscQ8KKSapzLFEPQiCex77Zaz4cpCO3mQRQkLh0804qDS93+pc7usCy6IoHc\nSp/H//0t2TbLAW78n15w+wO0/SHWCIrG15B37fp0MyeE61NMdfGz+bCJWeeq\nU9/OotZNcsIKRhSFFUtrvaefjbZ3YHl7WPi0EtTkr9XpYfJPB2lNhVoivUl+\nZflkEDLqdsMRCIO/Osxt8mv5UdWrRX8TeBznYUycUerzouJVCLoc93CKuy7n\n8SdI\r\n=/KbV\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"13a982794e73b9520fe7dfb31db6aaf211752e71","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.6","redis-mock":"^0.56.0","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.42","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.40","@balena/jellyfish-logger":"0.0.180","@balena/jellyfish-metrics":"0.0.208","@balena/jellyfish-environment":"^2.3.12"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.16.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.9","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.9","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.66","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.34_1608629072849_0.4512708537521657","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.35-renovate-non-major-de4c428c99f40e3c72b0df50d9dc095768eb872c":{"name":"@balena/jellyfish-core","version":"0.2.35-renovate-non-major-de4c428c99f40e3c72b0df50d9dc095768eb872c","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.35-renovate-non-major-de4c428c99f40e3c72b0df50d9dc095768eb872c","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"71b57bd8fa044e1f88069caefa38950c885f068f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.35-renovate-non-major-de4c428c99f40e3c72b0df50d9dc095768eb872c.tgz","fileCount":95,"integrity":"sha512-SVX6xdpAxHenTZIXW10iwollg0CtdSC8wndP2IclaHqPW8SMb61ZxyYic6cCATxrRUci4hi9iKz+rAuw5p7iPA==","signatures":[{"sig":"MEYCIQD9sN5f4ad9nfeZ6NWU99R9vtz9+BIQ2Ps64Y+AYIY6xwIhAM8U89C2FFO2JpEsQNZiFH0lM1KlwFdXmYGvG7aaHcAU","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":576577,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf4qwDCRA9TVsSAnZWagAAMb4P/0sUhp6jQmypNGWDCuFb\nTL2MDD36uCDp7EaX+1/p1yEE6do6S7Wj+GQSPfTKsS7gOT8JTzmUJVs76pKb\nnRKJyB8mtu5VGdwONlfig7HVR3xFePN94xkjyy1rkzSIZqTx5BPZp5xW+WYw\nF1reoOnEY7N68DUGvMnVaAcj4ce6Tod941KIMiUT26zFZtHb+bbCtb+GlnnG\n/dL0IFQsJy4uFuh43tlGpEnETITBK0HDwmQd5TNRXsiu+ifIuKwla8D76qqT\nrvMitOpUlq89yj3R0cmfZjzklTX3IQylgFxl0D9FxE9zPmDbojZYfetHuibl\n1wNEipzgKPY14y6vn0jHA9iOXQs14N0KPUT/9wgpiAGwn7T8GQwtQt6Q3Zbe\nmvtGotUQzSWMPSj07aeijy9llw8aR5ASTzGOQcOsafeVcM4yMoUJNSMEZw/u\nnee0ouFwjyWlkgnRnN9sWz6u7mGiEPvlQYUcmwaHaF4x5vFTe+U620ABXo+d\npZ1lhjekNdkXXEYljTn/GM+J7RBlsQQ0bpGAaRfBQCKb5CUJtymeAcquET/D\nVqXX3zp3/k0n6wQUP66P49AGV/bDI3not0fuFEar1r8ywic/pQkRsA6KOqUf\nRFTT01hh3LXoYjHAbkoMHyPm1Tqokczaf9al7ccujIjzQW424UVeSALTE9jx\n0RUU\r\n=h+V5\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"de4c428c99f40e3c72b0df50d9dc095768eb872c","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.6","redis-mock":"^0.56.1","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.42","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.40","@balena/jellyfish-logger":"0.0.180","@balena/jellyfish-metrics":"0.0.208","@balena/jellyfish-environment":"^2.3.12"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.16.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.9","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.9","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.66","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.35-renovate-non-major-de4c428c99f40e3c72b0df50d9dc095768eb872c_1608690690408_0.06908967948918865","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.35":{"name":"@balena/jellyfish-core","version":"0.2.35","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.35","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9a2a5249b61352f3ad7c67a67e65540f9615462d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.35.tgz","fileCount":95,"integrity":"sha512-hmKEEOalc7S32eugBJvKWtIzrwmpDG0fto/jCUXyWfoWWKpPx8O4bJgY9lxUN+lkPKFo+O6WA2/nHy6XX7roEQ==","signatures":[{"sig":"MEQCIHBEn+QZQn6RW21oBiYiUW2hvd5DH0ZkmCKPn8eRc2c7AiB80Uq6siPL6gWuP4h+BBl51czEDoOweFVjzT9bR5YlDQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":576517,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf4q1pCRA9TVsSAnZWagAAO/AP/3KkW7vKHNINNXO9uK2O\n+VZqlLm6lxlLvvFYb9Vvsm2EM6r7DAQjSS7k77S6+ELS9uaZcI0e9s5tIeca\nHLQvJG27D3ldLrT/AcBhP6wYnfFkJ+UrLguJzTn5vP8xryAAy7hsmjiLkYhx\nhxrJuHx+diba0XZ09o2I/s8YerT0Zle0aYylxvbOJi0OAqrf16KkYIZz3bZ5\ngmCt9thN8Hfwg4uc/hil9A+n5EQlUATbbhSTKy+FbHiDK8z8crpKqENNSZj8\nfs3ymCpYrSlm3kHgXywpCxT511r6RUixbWyyAmqxD59AvJ7/louX8KamdRgP\nEUtj6pGT7NT9VGysQ5If0hMYMnONF6ZNsisvwxHteI1Wm3HNzTuSILkIAYyY\nVwXCJL3CUqOoQA16d9C+YL09JhYR20yVoAZtBd6F3rirjCuOYYSzIUsiGa+Y\niQEnsDDdgnmYIiOHYSltc0Qwswh/XMuvxSlycdus665m9Zimwnhr76pL/szY\nIBqzByjemZ5P0mxojtsclWwqscvXaQr11YOPFTOwoMpQ2BGaRPjIVWWHQGfw\nzkG0lnYYFJANt5WbGQVfUd+maucldQKX/pMUaAA0eJ1wFt2tEXob8c8uvuns\nDD/ksyDO0PCnACEn+iGYJ/SenimGCQIQFrk3NlKbqXwCDgZPpGVL8tFI/J6Z\nv7JI\r\n=Q+Q3\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"87d1821549e6fbf1ff435b2591e8b0b48129c88b","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.6","redis-mock":"^0.56.1","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.42","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.40","@balena/jellyfish-logger":"0.0.180","@balena/jellyfish-metrics":"0.0.208","@balena/jellyfish-environment":"^2.3.12"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.16.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.0.9","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.9","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.66","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.35_1608691048795_0.3441567268573078","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.36-renovate-non-major-b3567bee450b500abc0953b76e25adaaa1d2ba25":{"name":"@balena/jellyfish-core","version":"0.2.36-renovate-non-major-b3567bee450b500abc0953b76e25adaaa1d2ba25","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.36-renovate-non-major-b3567bee450b500abc0953b76e25adaaa1d2ba25","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c71ff4ec16c72fe197d9fe4cc59cc344466ec92a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.36-renovate-non-major-b3567bee450b500abc0953b76e25adaaa1d2ba25.tgz","fileCount":95,"integrity":"sha512-3xNZpfuZSfT6duYhk8zyVVB4uU3N32BPMvv3coczJDVxz7nKbUo3iHm+ZKvjeMlpcBMM2geNVE1GcGgs+yDnOQ==","signatures":[{"sig":"MEUCIHOjU782OzAhWnJZKXtLec2LIFu5g6odj997Ckts/0OpAiEArT5j2Qy9SvjtEmlNwH8alJBFLM4J9roRgLDGAPuGkkA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":576643,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf4wWkCRA9TVsSAnZWagAAg/cQAKUeIckxy6sEWMGKeH7k\nA12QrcDX0rmo/b68pOShF+mx+OEjCGtMq264EHCqcH4QjqiOJj6sA5+R+uE1\n6yNi75Rum5QRbML8Ul834KA60rJai3Vgk38NUSYIXQu67StEbipKk1Go+G4n\ngXAWcqfT0FaLf3njrEF0MDYICqpCOde34/q3KklS54y+3c3G6FfFTKOdSYBq\nfXJRYzoGSDQA7ji0NQVgpN5js8sJkvxmKX6gBPEN7kmA/mFErr/Jb7BkoHX/\nMnayTLqEPYdPXFzZcppdFaNivs0se1gApzZG3BsUvGs+OltSNNazLtw4897t\nFDlDL4RCMtL/lKyp78eQOR4R1j5CnQZd8pcYR8DFvA3Trqo63IOmg3jNeoL6\noFoxkEKOU9AUuoPTtiq/iZH1eXTYTnQDTG2BFRGqmuXyFF8QNU5ViBF6VIWu\nmQm4N9PVX6cKYGgb7QUFhoPthWmU6+QUducNPZDM6XiYJ67tbBRsL93DlGYR\nUmtp2utZYn46dQVQhbCun8DsZDmcCPd3cYF+GYGTWBa81GY2xWQ3+u4SsZi0\nPCIEt6z/eol6U3ZUDRyo4FKY8VlxTnjMV9v5DRJivOW3ecUyixerTAxxLkbR\n8kX/RCO2MCixwzcmogGYsg66c1NxDcW3+3WQmYkOrj+RnOtIy2rRJg8SFKRc\nScTb\r\n=LubF\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b3567bee450b500abc0953b76e25adaaa1d2ba25","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.6","redis-mock":"^0.56.1","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.42","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.40","@balena/jellyfish-logger":"0.0.181","@balena/jellyfish-metrics":"0.0.209","@balena/jellyfish-environment":"^2.3.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.16.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.0","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.9","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.67","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.36-renovate-non-major-b3567bee450b500abc0953b76e25adaaa1d2ba25_1608713636317_0.7780046557690075","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.36":{"name":"@balena/jellyfish-core","version":"0.2.36","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.36","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ee6f1a446cca49aba0f1d74f5e4a5058420a5c36","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.36.tgz","fileCount":95,"integrity":"sha512-16vjI3gi3ranwniIphiNKLhBb+F1mpp4SmycBPsL5yigOpGQ82EY/Ds6bCZaRX/d3EBbMtlBZfW1tVz9Ik8rTg==","signatures":[{"sig":"MEUCIQDbYLhChkn5rPkFxim4pDrkAf01F3kTqXouhzzpHVsDngIgdgMcBPepIpCKIWtt/gxWQFX9WgLKMfxUOAAdjYM4QrY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":576583,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf4wbRCRA9TVsSAnZWagAAnJQP/2WjJmKUNt8lD3bgEzXa\nducwF/G3eCZY7lX3A9iUwZVM4/GN+Dl2M1TxPreLaEMdu808EEVvvZhyVrkb\nWdVtySsp4MLCtu23lMRq+MmmfgSkQebNOIFiBBDRw6DcyT8kcMdjCa2ZUpRx\nAuqY6JUgIykAGhKVxcX9K+gt81TgUwm3LEcqMblFA9tvO+omyFGXxVgZusMe\nm8JNYHNznJoMEwTizL2jMVGHjPLciOyOgS9Fom94LB+fG87yBSMjUI64dAOO\njV+CvXE78xnnFA/GRlcIOktYiph+K0j5QVTLxq/YmUC/2/PKHJwx0XTJlDmt\nbwkPZJ8f5r1ZslFIfvyb/8+YNQdy0gr2FIJ9JnQxxnTKceVyxaVAAoDnGN9M\n0ZS8162ZgeA8Y2z/+lnM31IY6OfVL5pMZdhJpu8YRRunAuPU15p6uTXgkVdi\n9T6uBecreyUY7xSChdlQrfZEZU32X7oaiLjgsDh4DZ5qg0HxkqVkpMRBXg/d\nrYlaFoKCpTS94s8smC6SFmnCtbncruyn0XzdxKd7SMvAbO8Y7dN2bF8kgoAX\nYdloxnFDC/Tg9GmRqIiDxZbNRwY+hef1cG7M5+P/EduGY6ZRCydQYH8+dhYq\n7y/dbHO1r6MyX/4V1lObgQUy4574Mlv+3xiAb4qEi9OCKQxlQ0nLKkrBnmr8\n3hdY\r\n=vM3Q\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"1ec056d12a555a1fa5befbd79f06224f041990d7","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.6","redis-mock":"^0.56.1","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.42","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.40","@balena/jellyfish-logger":"0.0.181","@balena/jellyfish-metrics":"0.0.209","@balena/jellyfish-environment":"^2.3.13"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.16.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.0","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.9","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.67","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.36_1608713937111_0.3452843789604265","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.37-renovate-non-major-c1b2a76b4257eacbd3dcaf1e8ed7295a521a18b4":{"name":"@balena/jellyfish-core","version":"0.2.37-renovate-non-major-c1b2a76b4257eacbd3dcaf1e8ed7295a521a18b4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.37-renovate-non-major-c1b2a76b4257eacbd3dcaf1e8ed7295a521a18b4","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"65d9c81d2b735d45b42e9f59b88f4d2249624e2b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.37-renovate-non-major-c1b2a76b4257eacbd3dcaf1e8ed7295a521a18b4.tgz","fileCount":95,"integrity":"sha512-SBtr0PCPjVraCj0SiIJ1XF7LIvVzzmvWDMkNIGXfm9V63ZrAyV5zp1cAbAqhFwfOKsKegRugVpOIucN5fg6vPg==","signatures":[{"sig":"MEUCIFrqfywA3CCrtp5yZlCZUileh2zfRdpn06X6uu/toy15AiEAk84DsMEuwtAQl6Rpymbmkqoj5Wp3JNUq/oUt2pCcDxU=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":576747,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf4xWHCRA9TVsSAnZWagAAMC8QAJcJO8kqtJ5rJkHnzhIu\nT5hOs+FXuTkoSkkhhyqVfYHs7WU8w/chrpdyPJOSE43tLo7xr/8O5u5ggviD\nPGfQr7kL68GQ3LEKuzNLMW/QX+E/vCMCeRqsko1HIWII4+fOJeFa/mWkUz0r\n4OKi1PezkFtHCm4tddSdW6mIqJBVpHB8dmk9NRpuvmeeATEVua4caX6S8Nid\n3KQrGZyF2svOYfMbOYhO8k+iundL3lRjYCxqS2PsoihCv/PJKyEkXyJYJBmM\npcRQGcHZl9RKK+PfAXncWBtxGNIFWZpCjWKSvvLMDaupFv3FOj4kmtudU7dW\ncZbMpgFCViMCXDdv9btkrqzLkT85Yxf/n9dUN8SrKpMx0MRizPfohxqE23+6\ngEjVER0EeDC8WzTnPh4eKfRhjabiTmnuL4mJ2x8AMYPng+Z7RFirFAX0m5Dc\nWt7FMb2l1e1GMDUFsBKFQhmBnnO5fnjRnCn3vWHuTjPccMu1Qe/9dLv6l4My\nnG6+nVut0Xt22W55fauJtKG98A3DFwL2sJH2R7wxALRfGGB4LwBxHdKdeHFq\nM4rQIDrZTemE+lmm9LxKC//JF5Q+jqe7BEysUHGuvqBq4lwbhXXdMhcsdAK/\nUDLkhQQykIyurpYibiNgtmVikvEtqDT/dkGvWszeXx4RPg5zISkokoBz2CKi\nr9Uh\r\n=cnG6\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c1b2a76b4257eacbd3dcaf1e8ed7295a521a18b4","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.6","redis-mock":"^0.56.1","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.42","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.40","@balena/jellyfish-logger":"0.0.182","@balena/jellyfish-metrics":"0.0.209","@balena/jellyfish-environment":"^2.3.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.16.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.0","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.9","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.67","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.37-renovate-non-major-c1b2a76b4257eacbd3dcaf1e8ed7295a521a18b4_1608717703497_0.48347688262187405","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.37":{"name":"@balena/jellyfish-core","version":"0.2.37","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.37","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2450406f308e348b94932b100b6cd97afc217996","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.37.tgz","fileCount":95,"integrity":"sha512-2mRYWB16LgEGeYPJo6Szf+/jlXNil7dpwlqrtPhEYALf9tC0rFH0p8/gz8Dnu4yymsZE48lCTURfW8LKNGZYkA==","signatures":[{"sig":"MEQCIBKbaIFseosgtM6nx0Kw7ajxTK0XPQAcSHNZ9pEBZwu5AiASlVDJVyHt9KuDTQPQ4VHLIxP0TfCSXbc+Q/eCw7Lwcg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":576687,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf4xd4CRA9TVsSAnZWagAALiIP/0zdxhcy1a5pDn7AJu/r\nCndRPlvxW7wjn3w5Cf78D4TqB6BuOW6PVd4yVAfVG0zOukgBco2LBNjQEmCp\n4kag+ih+Y+yVJ14KA3UuVzIYvi9sj4+WqgY99AUp4h86CxuJhJSbvkv9lpRA\nyo1fRSis3Di8n91ypk04n1ToLX29GBc9S0cmUSLzu9uTGv+Kep/Bn0crfCe7\ny4wRLXYgcLOYs0FCYu6MdT+h54lTEC758LUWUIY36R2mtj7KOIh07IOzcsLJ\n+y5ShYO1OZwdUhtsGq2lha2AfFS2cOVnNR0s5g1QlKnENJbsCdRclP8IGjdg\nZuJs7oVHfs13MnJ10Q28ydorMtdbKy53TPT2OF8YIJkjD1VY3TtrNvkNcfc7\nuae/IPCL1B85/Kvf68d1bGCNTFYX9aeKXhS8MHoJ53Y23At+cbqj0XYLvN8D\nbr1c3zchScYaTVtpUefj5tvIYfBOjHhsqimXw8K1Re96R4gnON03sL+HN6no\nmgPGqs4wjrZIIcXc7JMEMyKZNWjw7HNW0CTvvRP85s6BoCg51BV5ybsssLWD\nA3abaEQqRykoXFQJRilCL1A0Fvok6gvRfE2o6qy3wG8l2JNWblIl82g4/Q5u\nws4gve1j3hH+JKs9aJjyUJw2s3OynwhXI/hOt+ECZfh0UEC0b7vpZB3KAs/J\n5fPf\r\n=zUnp\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"88c12a5b3468ed83bc4673a62f61c1cedca6ad59","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.6","redis-mock":"^0.56.1","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.42","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.40","@balena/jellyfish-logger":"0.0.182","@balena/jellyfish-metrics":"0.0.209","@balena/jellyfish-environment":"^2.3.13"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.16.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.0","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.9","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.67","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.37_1608718200322_0.10822875249108077","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.38-renovate-non-major-ad021ee951596202aebd9df14d309117583ee2cd":{"name":"@balena/jellyfish-core","version":"0.2.38-renovate-non-major-ad021ee951596202aebd9df14d309117583ee2cd","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.38-renovate-non-major-ad021ee951596202aebd9df14d309117583ee2cd","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"30f0b5d4460159843770b0bb6c03a89a9bc5b569","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.38-renovate-non-major-ad021ee951596202aebd9df14d309117583ee2cd.tgz","fileCount":95,"integrity":"sha512-frsrNab/6qwL1wo8a2Ux1Z2gt0uAC4R5lGpACyxPvWlMULfPHP5ZxKTeqdYwQ/NDEWvp751v+Xhu6zeLr1JqEQ==","signatures":[{"sig":"MEUCIDC+WJbuXXvFIb7542zHmy77ztM87CSSmbx5FToUYeDOAiEAzYxAyqwgUbDYfOsAoZNl9y5eM/qZd0CniCXZ1u63nLc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":576852,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf4yrlCRA9TVsSAnZWagAAVNcQAKMiG5mkdA+IRc0nANyT\n/yHu1U2T7X7FljPG5IFuCNIm9QSlFQxWPnlywU3IvJscNIzLdD+NMu4pwGeF\nuL87yISxvtKAMrZ5m6rPCnpr28c4WhlsMZgy/PrJMdwPRFxM6pteqwHrT+7L\n/YqG42k5dLYl66s8FGG8Hg633aaoMmG3TPhDuuK4rwyENpyUtjM3ODS424ld\nCZgvEOA/nxWrOqY3Xw8/+4zLJok3s98NEex65/lcUl+hyLddnLcXEpDNJKqM\n6uYIkHp5fmnpSLpnIWxk5Igq9T5KwvIHIEybeVR0mS6QcN0xRY5c3n4rmjZX\n8cWc4LmXg68BMHfLTODIoghuRkCGa/glQhD2RALhfiu4BQSeIruGYxeuRJph\n4fwN1kZkylEg4V1VLqrGcFWGnND51O9P6+DaAgWp+3TqP/iJmgESszuIU8k8\nd2m1vtAEnMZOzPqrlztlyrbwtAuigdaTB9l+FnOqEpeIZ6OZhNM8ffKJATFS\nwgF45Qyho/D1qCKdKQmgmec7ddyCiw+7RWb3cUeTBIzqJyutwtNTW+D3zXid\n/9bpq95V4cMPWRaAtF8PSaUDSPFGgWEFmlu0nWsqP6BwR9IKOBIsy0otVkBU\nWL5T/MCKJTrGxe02mFc+uckJiuaYRKMcx2jFlJR84gUqkk4i6kZlsXGcnl/c\nSESq\r\n=wyB0\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ad021ee951596202aebd9df14d309117583ee2cd","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.6","redis-mock":"^0.56.1","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.42","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.40","@balena/jellyfish-logger":"0.0.182","@balena/jellyfish-metrics":"0.0.210","@balena/jellyfish-environment":"^2.3.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.16.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.0","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.9","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.67","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.38-renovate-non-major-ad021ee951596202aebd9df14d309117583ee2cd_1608723173068_0.7018853122787967","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.38":{"name":"@balena/jellyfish-core","version":"0.2.38","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.38","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e6f88f669095a1e86827513a41f8c6eccc1365ff","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.38.tgz","fileCount":95,"integrity":"sha512-phG6rv/KbA77i2aW/aV+pSH6PsOhFXE/rsIWu51WfCnMFgFa0TvD1a3hvMkI25Izi8nfBFQEogJXq8KIZWPMWg==","signatures":[{"sig":"MEUCIAlyQlko6U1KvHxvSbwiMo6otzShjq8AmbVA//AwT8PqAiEAnPXNXiTH9sZKDuCUdriaytHZ3CUgSTI3wU95tCc90os=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":576792,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf4yw/CRA9TVsSAnZWagAArhkP/jI6YFeRhsO9uzd+hy86\nMiU9EYftEe3I5n2P0fdLC5A4kFnNx016O07b20iEUitP4tf0ZSLzrcnjUv9M\nxlDg1F1aqEkfudecVQtu9HW8kNYGk5gHh16zDHGL0qVwiUMwDU9FHMdtZu4T\nuWurKigcdOJfxwrzB4znIHFi8R+GwmOdTrOFUebdf/Hkw2BMdgN8voi/CWdz\nyV1J3CVT88A0elS46fnZwWHHhEDbgQZDXJWQLOeK/9JpblyaGGuisWOvDUwg\nB7SoeoC1j+T5o2x6X4RYKbcbvgkt0unsISDd6jmX1Ws2i94YCWD+l1oozlgM\np5Ae4N3HWcrzfkrbmjDLhBfo0Utk//EBSGMh6OFyCPX9Tgn2SEHpmNK7cbje\n0SP/YM1ap895Md6zIbOkS4zU6YJGGlCTqz8Bn0ozu/BKc48QM9YIx34X++Hq\nmfOM3ttK0sLvGvUAm+82eKQU8Rymoo8pqcqGiPv9nMDk0hBM9dmDHJkVjcNT\nUf/JMXTvZbWfiQybOyC1yn/MdRE1bSyMuqRoTwFrFtF0Osbl6iKOZRRpwR6q\nXcF1RzfPPMhA1K7IRBhcr6MPvx/ZDyPIgCz5whvItZwdm0Am8kDEveGN7jGr\ng6m0gJ6KGJUN/AQry5c7W7ET3WtNGTF0yeC4Ltyp8xBw28rk2+w3vWFvpH6v\nbsf2\r\n=a1SD\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4f1b4015489d8f0f40b76da328e5bf22cd88853d","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.6","redis-mock":"^0.56.1","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.42","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.40","@balena/jellyfish-logger":"0.0.182","@balena/jellyfish-metrics":"0.0.210","@balena/jellyfish-environment":"^2.3.13"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.16.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.0","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.9","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.67","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.38_1608723519253_0.019456716565928067","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.39-renovate-docker-non-major-f5935c63c88e6be6f59cc4b4d37b24dc31c4af3f":{"name":"@balena/jellyfish-core","version":"0.2.39-renovate-docker-non-major-f5935c63c88e6be6f59cc4b4d37b24dc31c4af3f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.39-renovate-docker-non-major-f5935c63c88e6be6f59cc4b4d37b24dc31c4af3f","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6fe4701fc2f54729278a8fad6a7a6c0f65a49510","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.39-renovate-docker-non-major-f5935c63c88e6be6f59cc4b4d37b24dc31c4af3f.tgz","fileCount":95,"integrity":"sha512-bHSN89Bil6yQgOljSwCB/GUYK23399s4vLRGoKJ0ojw6jFS0hFihOHsKFFMc9GalfIQy0IhL2tKvY0LFMF5UmA==","signatures":[{"sig":"MEUCIQDUDM3W/etKX9RA6fYCxWzbT5kHg6yK3sUxxpY9jmRtgAIgfuTR1YRK7ih4otgDB+tzJ1vNuIKKnU9yDtGnO/J0FMQ=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":576962,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf44E8CRA9TVsSAnZWagAAwqIQAKHyfN6Ac1xAM4kbWobh\nT1qiZcWa/PnsRvpHSDii2mtCzohx7fXf2Y3DyH3eR3nNjMTB9laBaSEbLajs\nr/+7AWHa7GILGtuE2h7YrHaq6dgykfqugSLVdXxJq8++0fyNW5m5iG0AQGAj\n0/td7f3fd/trWLtxYL/C/A+SYUwCPbjGQLd3q92Ju/PDHdX1Y9d+7E1xBs8T\nFGh8vaEdszfEVMB9YBt3WIQhEd9VvYwwg1/5sONPOIUlqbEKJ7X9Fen3UihL\nrnUpElQKbzShkQIo4UbyIuO8UbG7u9kAiqWu2LTuqgfSvtq9eYwV+qj/AO5u\nOB6b3i1MKWoXeHXU0WVyxFfPIUb81WgMf4kEqq+35BP9aKbE3O49BF4q4NPt\nADxodMDCWsLSnpXcpiI2qB5UD4/2gYsapMNEqa/wfyhIIzd4JryT3uyOiUDe\n+YE6iwNgAcgsxUyn+Shyzh+yBrFbpbU/aZOtmzPsU6TjpX2XXR5U9tBVKOwO\nx6xjxPaj1XMvp9h9tDuHJZcTvwhkFPgC80h+Zg2xEQGd5neMA9U50pFo7Sjl\n08Kc249CI9vMYTYTS4FWwvmw05qQ5zz4kq3kDf3QZtx/iTHMX91EMzv92HKT\nWlhnYsHXTBVuOzUc0DwwceL0rL1NC97u045q5+B+pVDsbQrhHaVG8P90UcbH\ns8el\r\n=/0Kx\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f5935c63c88e6be6f59cc4b4d37b24dc31c4af3f","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.6","redis-mock":"^0.56.1","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.42","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.40","@balena/jellyfish-logger":"0.0.182","@balena/jellyfish-metrics":"0.0.210","@balena/jellyfish-environment":"^2.3.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.16.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.0","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.9","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.67","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.39-renovate-docker-non-major-f5935c63c88e6be6f59cc4b4d37b24dc31c4af3f_1608745275447_0.47088039013301053","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.39":{"name":"@balena/jellyfish-core","version":"0.2.39","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.39","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c3d940df1ff2c025bc5e425898e8885c1440ced7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.39.tgz","fileCount":95,"integrity":"sha512-qFX+RqKSOr0wz3wI4DVtOKP0Gjd38poPs1CObEGXlUtdaP3eQtwfpnoPf50ej7gDbYZV7SKqeUfT7DvKjVLy4Q==","signatures":[{"sig":"MEUCIQCNyLAUpv8OA0Ds/81jkhxHZqVSoN5RcezVaaHvywaQdAIgM7O8uDbBJA8MA5QyWHacmEEg4HQ94i3YLUo5cLciPDs=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":576895,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf44K9CRA9TVsSAnZWagAAmcwP/3rJtj54qnn1KKMFNt5E\n30V5bYRi6zJ05GF+WW8IZ3Iay7zpFut0a/Z+gzf/c5vXgVjUC6uYKZAQdmqy\nF3id99vWe6q4Yz9s/tF5wPotLAzsPZ39zV2aH4jK1zMfR4alZtrkIT08/NUM\nVV1w2GecpyRXvygaazWjMfD9tYigAob9Yya8z54il0WmYiqvHlUYtMyfaDsL\nrNtQs6wNN9SX7YnWotccjQl0J4kk+wdGltWvlx5TpgrWDt0oSBmnsK2W11/o\n4wHXplV7ZTOyOyRCOS6xAaWYo3HvtZO759TfafDNIf+m8MNKX6WeMwYTlcHB\n6VlYO4XFXs2DOuB8vxitdbSx+Mil16D3UbGlRnfQPf42q/jQefkSbL2Z6NxQ\nnASgrdXD/aQToCK1LagFKz19CGLHay+YAQb1sgim6NycgcMWVrIHJ8jYIfQc\nE3E8I1Tg+eaELYqDIhjBYe8GA3CHciFrLQ+d1phZJ2r4NUh4ah/6UtQ3LAlb\nsEORrhPTSZlzEUGqRZKh5BekWlxz11WZos6DyY69f6arRdOxpNPbQF882SP4\nJbQ1qQVcqOBdWi89XkM8vkkEaPKR87c+hVpI8tVixXSt9wNmxpR9lEllZ848\n2AAmZvw0y9evxHI048ovD+wwOFwtep73AMHSQP4y4QkwCF7yc7Dxw5FlyUSo\nX4ej\r\n=erIg\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ded291e72f1fe1f9eea5eee63d14d28c3f0351bb","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.6","redis-mock":"^0.56.1","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.42","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.40","@balena/jellyfish-logger":"0.0.182","@balena/jellyfish-metrics":"0.0.210","@balena/jellyfish-environment":"^2.3.13"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.16.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.0","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.9","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.67","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.39_1608745661195_0.3614821287565113","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.40-renovate-non-major-04bbde4f8b251b23183abbe9fc1c5934107ae553":{"name":"@balena/jellyfish-core","version":"0.2.40-renovate-non-major-04bbde4f8b251b23183abbe9fc1c5934107ae553","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.40-renovate-non-major-04bbde4f8b251b23183abbe9fc1c5934107ae553","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"edb206fdefacff5390820a3ce63ca458d4e31975","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.40-renovate-non-major-04bbde4f8b251b23183abbe9fc1c5934107ae553.tgz","fileCount":95,"integrity":"sha512-rsafa2nwuOKhRH84FvRweGjfLJOq4TqP1sqW7BfxCvV9tfCeoerBYW/05v7mKp7LYxRS3xTRH521r5EKE2gWqQ==","signatures":[{"sig":"MEUCIQDYE1vOXUmBgGiY3SJhavQi/l8zqzygjdOAU/Pckx8W9AIgDXYtvlwuWCMNeNGk9b0isRih70clQu2rCuJYWH7CR9k=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":577044,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf44UdCRA9TVsSAnZWagAAZwoP/3HfBvYwmmaLCdfg+E0j\ne7OFN6gpdsCE79mimtvcuzxEep7iUNud2jLwtKZ1jdoh+rKCYYUlwTRU/rqX\noD5B2P1H+ThjzRncEBH8WcRrif135FXQISVufaKNWFzJeMXLVKovyOZ6WrSo\nHpql0SmWb1AOLVrioRgN6QXdU93GibpipWsEoCLvQSFSXZO5yf9+LxuXkxfI\n6R9DlVd/bgy1jfIZfXVUbk4Y1BdPCXA561332MdlDMzbOVCwrDyR+9DgewnQ\nhS7b5AOQwxqq43KUfv30pfbAQxcYyYUL5GGpfASjBxr8+RretyMxHnlrmsR2\nOzTJr6LoMAIcnNrYp/SYs79EXJkeXcFNv3oRad/N7vudH+w31XEbXWxtBmo5\nTQKWd+VLnQgFtkmBi8djqqBNrc5PSL3vyqrLlsroGsPBWWSYTASHham/d2QE\nDSZnAjCU/851n/3vJ6bZCZ7y5nYI9CGy2DFoVF0j+ilYbSI8+Ej5Wt3kam5L\nkgj2X5mcQN6dOrQmI6L1QbYY/PHBRNgEfgUlqfpOtx/kiR6KxAeUHXrkDah3\ng6eriaPzPDm8GGS+6CuF7oV2zoSQyuKlTMJ5F7L1vP6+YIKObtUO+Q6ql/J9\nTLA6vr3dk7sHWES6kIJWr/H1631m97tH/kJQSOG5QEhs3zMX9rxfrIkJkfO4\nEIAB\r\n=zV3G\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"04bbde4f8b251b23183abbe9fc1c5934107ae553","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.6","redis-mock":"^0.56.2","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.42","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.40","@balena/jellyfish-logger":"0.0.182","@balena/jellyfish-metrics":"0.0.210","@balena/jellyfish-environment":"^2.3.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.16.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.0","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.9","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.67","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.40-renovate-non-major-04bbde4f8b251b23183abbe9fc1c5934107ae553_1608746268557_0.940100237630574","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.40":{"name":"@balena/jellyfish-core","version":"0.2.40","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.40","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9dcba5e068e83508509fb82377e7dc4ffcbda04c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.40.tgz","fileCount":95,"integrity":"sha512-k+YEjLKemXHskC98SINe3qAo2Im8vVjfdfz/f5/zK89gMnrVrHFSFf8SArmfTUtnELHRfddKhC0xEcFkWRKeFQ==","signatures":[{"sig":"MEYCIQCqWa8ZwASa9JinIK8oa1NuuYMgSm+WXsEcg8tYOxUrsgIhAPLTAIEzHBOvvslfzjdAggI+eBSEeyCiy0CdK3fZeZq+","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":576984,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf44a3CRA9TVsSAnZWagAAg7cP/ArEohrah7bM3dCxlh3q\noGXG0BpwA7V1o6UA1r+Xi8toboxMYY8WNvkluRAkDFXEOFILDAXCwhJgkPer\noCDKZjSovG0Kj1GAk6yy+gUMYjVxx7TBVBvy7leZP+9TnDCqXiIptcAWz4ir\neoAbBpRQ2Jn24OCk/ns5SgeMIVVRbGqz+3o40sJcppuOAaBCjOsU0Bs0AtHw\nLkneyipbha6rQxO19Tagsqb2GrpkkDMPmyOUv/9d184jsfW2i0si8TpLd1y+\nlnwhSCE9rOgj3CzZDQih67iq/LghHaRVtdRFz8T3ix7nzNWSKFA8zy/cNOOb\np5XwukLmXLzkR9LxbXOE/xDlI5z04jn4I9XNsYaPSHb1E8pXLBiT0T3yp5eW\n1KiBr5/n+4qSspP++jiiGqcyuttQnSMPTYVJFtQNiyQ1ob2e7hB/+nh6DE6z\npY8Ph/hQbiECNfxkR28GR3pMABbPmBQ2wyiZ706zUX+WjLEMRD46CKioI+wx\nTEZc4TpCTv80OFtdT0aqO4Lls5f58vxpBxaEnodAUfSXDpRPUcbt1oR8Q2Jp\nj83eJDsKofyi6aCZEjz7ekSJclfKc8y4KBTCOKnEqqpZxhf0uoVxwHhUHfk4\nj388vubpBAqhBgmCrLzdC8giP6dxQp6t5VhCeaGZI4AKioflm/Okpcmp69G/\nR4I3\r\n=hcuy\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"08dca58fe567c06868cdb43fbb05ec78f8b79b37","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.6","redis-mock":"^0.56.2","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.42","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.40","@balena/jellyfish-logger":"0.0.182","@balena/jellyfish-metrics":"0.0.210","@balena/jellyfish-environment":"^2.3.13"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.16.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.0","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.9","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.67","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.40_1608746678708_0.7400677390930779","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.41-renovate-non-major-07c74417d98d9c8f3984805a02644bc0a6070301":{"name":"@balena/jellyfish-core","version":"0.2.41-renovate-non-major-07c74417d98d9c8f3984805a02644bc0a6070301","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.41-renovate-non-major-07c74417d98d9c8f3984805a02644bc0a6070301","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ced8f9cc6a767474ed1a22d682207c46580b5cff","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.41-renovate-non-major-07c74417d98d9c8f3984805a02644bc0a6070301.tgz","fileCount":95,"integrity":"sha512-RBu5js2DIN5mGUFT1A+Mvj0gi/RnCY6CUgdeCjaFATXsnFZJX7iXis5nJWO7ApFuCjOICFc1KwtNpB9RkuQ6jw==","signatures":[{"sig":"MEYCIQD7i8AU4XMTXdOEIyFKkaG7PQkk3wnzrYM3Aerai7hn4QIhAOUdwoZYdpqV3O1asR7nhV6gNPGOB3q+AsGRjrvbAfTt","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":577133,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf46KrCRA9TVsSAnZWagAAllEP/AmElFRFQr1hvOQji7eQ\ntyTu8xWk9RljitbqZ27/Hg3TJRtlRGQEb/f5mM1drzq8Ftqg5wsfYChegHeH\nDLiwRV/IL1GXhqrNe2VYxvtZP0EMfbm7juR+bu1vWWAc1+oUYyG808B7tZnP\nrZ4wIT2mOvGx4A7hVIUdlPwa/VatU2Td2mWGGyAR6E/FRtSP1sgsq4C3qGL7\n3fpvN6sTj+fzKpXsNuJc2JSEPIBSSooFVkbjY1/8+3VAlLY0dMeoF5X/S9Ip\n8yRH1v3CyMVKUHlLwW5md2eO61eS8Hp7TiIT5AD9dMDFFMQBovqiXy+ysZVl\nNHxPIChc4iI0OLeor7iWhrBUxvFt9M1snR1g3hcE5RIXTREim+Q2u+T0NWU+\ngwSOdQL/Z8XCoEe7/29GuzJeIXd1Rrn5ahziIncEywG0SJqoIIcxqvGAL6dC\nQpCKShFeCp6Ak0ekSWIO6RGAPOpyXeSaSjvhWRd9HErCt0mF8uV7mdzFyEgS\n3XQvAlROFI38TvX7M4/uheaH68HTufzT919p3dSfdvTd/1wOCw+qeUNI1NTf\ne6PiSlE7FKec+KNeK8Vh2k5Lv3Xk1T4mfK8Irhg5VQ4uCP6xbOIDQo2x3qEz\n0f4T7fmR8Zwj7f5Zup4Svba7cUK4pKWEzfviN8pogLINH63Nyp9lfp3sxbI5\nvZe5\r\n=8//s\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"07c74417d98d9c8f3984805a02644bc0a6070301","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.6","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.42","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.40","@balena/jellyfish-logger":"0.0.182","@balena/jellyfish-metrics":"0.0.210","@balena/jellyfish-environment":"^2.3.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.16.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.0","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.9","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.67","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.41-renovate-non-major-07c74417d98d9c8f3984805a02644bc0a6070301_1608753834459_0.958970921172426","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.41":{"name":"@balena/jellyfish-core","version":"0.2.41","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.41","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e25f34cb62d66b13a4037491c7b1abae179d6c68","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.41.tgz","fileCount":95,"integrity":"sha512-MrLcuW3lQ2Z9LhhofK8KI95Z7M0GmBZOlfPDDZkeU5yfI6r1K5uNoVOnUSAISrQeSmFJqj0nUkOb4UoL9U8iog==","signatures":[{"sig":"MEQCIHLWOPJmfFmVPlfx+4YD36uW6Z0UMMslYmNO4W0S/FcfAiAhY4f+wG3SPio6YEaNHduXDESFcpCwV1l9WeFDwpOb8A==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":577073,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf46PMCRA9TVsSAnZWagAARSQP/1aiadlAjWjMzeP1dYwM\n6VcHYzcdjRAqXSXQRFpg6FjuGcYdeuHPuS80UEL2Mvob6/jXAULr3cR959wm\nxZvaZ3UYRWZcnJ+AUCcoFXrd+aJBHnitjm9Tbwa2lHYMdioZ4uhjfwGGy7IT\nfmJ3FZ9H1TyAEbRTurO+lD8IS0qD8DBqkl+XsMuuSUDZZB6CCqKkdEu5kjf/\nPp/P9Hj/NEdyTDtIBfDeYlXM1xlz/nNv+YVhTeunr1qcoiw45xaJ455W4yJk\nCkostD8QMqwa25d3nnUMrFTyi58ule0vxBQ8W7xZCQN//hY+O+NY8/vg8xIp\nIDRoxw25oV6OmC0tMAyC48DyfM9JAlSKHhjNVzQxkZustl0jzprSMyge0mCk\nOmpo8Y+PZGHVptwIJIXg1VLPVW8hGJb47ByKdBY69ewq+17FFWfm4VEEwKQ7\nqIyhs+023IBNAYw+IIB8x4PQ6+qehtTMrmZ8YyTsXplDW+Bxx0qPyx042piy\n0ao7FD081KF6y5aYmDYaOU5CWZV8xXxoS0sLXXAuw1MVrbe1gl1p715JswWF\nGNI7ciwqTfGjEk6VzyJ2BsqvDx5gYPIPEz77S+dQxY2Qpa2uAolT7OgsmZd6\n1eCHhfUM5BsscZK5NpyaDw5/HjFl3Yii0jsuXQjKOTLTRiHjv728obulch2l\nMt8d\r\n=DWxs\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"17a7b977ee278e0921ba3d71bf63794e83d06264","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.6","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.42","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.40","@balena/jellyfish-logger":"0.0.182","@balena/jellyfish-metrics":"0.0.210","@balena/jellyfish-environment":"^2.3.13"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.16.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.0","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.9","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.67","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.41_1608754123662_0.5675779032767663","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.42-renovate-non-major-11538408ee43715e7fa447d5b6021619516b5837":{"name":"@balena/jellyfish-core","version":"0.2.42-renovate-non-major-11538408ee43715e7fa447d5b6021619516b5837","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.42-renovate-non-major-11538408ee43715e7fa447d5b6021619516b5837","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5d57fd00ed7165f6326abeecc457bda0f4e0c610","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.42-renovate-non-major-11538408ee43715e7fa447d5b6021619516b5837.tgz","fileCount":95,"integrity":"sha512-h67gZn8UU6uz/+iwxbGDkXn1TC6TeAiZVQVExqC4IkmvvLiTznwglc8p3R28RcBQQ0kAf6xVwMHBOopzIoBBGA==","signatures":[{"sig":"MEUCIEK5RlklZS97DvbDtRIaUx8EqcWkqaSO1taumvcDyeZyAiEAjxVBt0na2rPYs/uPq5nUqRNi9bh86CzQPCX2s/pmb/g=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":577234,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf48hPCRA9TVsSAnZWagAAK68P/1iLneV7+yIgyAhJRGB8\nZN7VDe39sYuNXy57LN9S6NFkbPMJluNGZie7NrksnQQWi1tPPoLcoPkU1EBt\npjRIh79Qrn3Zn1RTOxeHd47ys2mkyKuCdoC8R5xufbwb077e650/eSN5PC71\nqFN3mVhhMsZSKgdIy276rVv1vXb1qf8UoKp9j0poefhD59bpGcV3oF0CqB8m\nR8Tf+MLIIyimlcyFeiI554prNOJBn9PfrzP8KBb8iFtUVT/UiqP/dAmssDt7\nBP1mqT2KtZf7BgzWFdXegZbOqEECW2OyJIgSRNBaUdSbB6xFSGN2dAZKdq41\nMZXSJqcRhK7owCk/oSrqEt1XhpB29K+g14aoAmhDJRZZtQSNtDHvsUXIvM6t\na/rWXyIbYvvhhtXbEPdxZINLr1JRjhxrXKpI3EnYghr+HFMYFTVLJNlFJsj4\nGO6FFebHVt269sce/AxUcv5DvUVSQgpaAz/d0TDHJPu8zSk9Jo5WoIsd3izY\nNgTJlGpLSs+az0xoI+M8oyU068PazRkW2oHMfY8/vA2e2FDaAuB1rnf0oxxe\nGIB71qsDMgFcc5F4AEGv99YWrpaFbv2HfTNL42Z7QPowDUJVzV+f3yhY9VgO\nDEhN3efBU/BeCYF82JsY7e4gptKmiJKDWIQc/DwxeHf9Hs6PLMwzKjACM3eu\n+Z1W\r\n=I3Yl\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"11538408ee43715e7fa447d5b6021619516b5837","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.6","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.43","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.40","@balena/jellyfish-logger":"0.0.182","@balena/jellyfish-metrics":"0.0.210","@balena/jellyfish-environment":"^2.3.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.16.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.0","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.9","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.67","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.42-renovate-non-major-11538408ee43715e7fa447d5b6021619516b5837_1608763470679_0.6686899607753536","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.42":{"name":"@balena/jellyfish-core","version":"0.2.42","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.42","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1c2d14687ed9dd7f86b1f24ae83a98455dd6cbf3","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.42.tgz","fileCount":95,"integrity":"sha512-4o+f/m2SxP9gkIOu0LDEJRN441U4nsDHorgMCCNjQomuYLHWnkiDN/+V1crfZ6git8StnDAgzc/U877aNlcIDQ==","signatures":[{"sig":"MEYCIQCcGh9/FeIe94VQ4dm+QNtYmr+lSJN6XiD85q+yADLOaAIhALL7z0eotmExnYbzLchxJNPl5Lci/pCs0hO1pRqnfaOJ","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":577174,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf596vCRA9TVsSAnZWagAAFzYQAJNW1foUKx9fBHS+Tj8f\nc1uymc2hMLC1kYLSk8wF/PyhAMhTP+4uaasu45NCIVS/7UKjEf7D/TZumKWN\ncBpupcWUaXjQ/7x4qW9bHoA6x+TrWWypr+LUOHgK8V+MkxQMUX0G17YdppmT\nckncn0PXiREX/GA4PJD7FEV3ZrXyJSGiCAhqAUmTzJGaJSSwRdR3++lFwLAx\nCfZBqD08foR2ilgLUWMLUyHSNln/emmRlKIkaIAYy7PJ1sEX93xnnXw/fQqt\ndLzMgIaNF8SV/NA4LBJwXWwzL/hCIDbY8Zqb+vvVTFVHl8od51ktBSNpwgFj\nDURiTvIOkQbABT2jhMiU+wvc33N+6NUpXtXtrpj6snsRmhaQ79A7P8iwwtNY\nl5eplMzYHG65Nfu7MyO7Ee1UEH6JI3NmZ8E1Mc957m/5xB3cJ4KvmQgpHg0v\nFrVpM5SczVF/23eD1YVUMnep1vyE5GTZJv2wuCXYqMgNLYYYzBGQtYuhDJ6e\nlEiHgtYOKH6aKuP/5gq2ZXogRJPldjfPk8iZ7vwR6/dKO5Yk2daxHv99ETD/\nidVt+RZOprDjPcHp1x4neIwlwVmg0HCOKiHKwHX9MiBg2iX2ME1jOH2EelVv\nJxgqav8T4s2rIHJZG6UraLqCPKSxhsBtSmo9wPT0ly5Z/ejVLfjHrWwcAEh6\n8uvO\r\n=0f2e\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0b63a48a7881f97e7277c0c4939e8b60c647e9d1","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.6","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.43","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.40","@balena/jellyfish-logger":"0.0.182","@balena/jellyfish-metrics":"0.0.210","@balena/jellyfish-environment":"^2.3.13"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.16.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.0","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.9","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.67","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.42_1609031343295_0.4344588404409915","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.43-renovate-non-major-cf670f089fab700493658f454c308838c4b5961c":{"name":"@balena/jellyfish-core","version":"0.2.43-renovate-non-major-cf670f089fab700493658f454c308838c4b5961c","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.43-renovate-non-major-cf670f089fab700493658f454c308838c4b5961c","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a5b82af10f82bb9fa935552156e126a24d6d4f5d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.43-renovate-non-major-cf670f089fab700493658f454c308838c4b5961c.tgz","fileCount":95,"integrity":"sha512-7UiA1F+9tGnH/JSoSSdiNlld0MoP36kRmBHsLl4GkG9q2Gf8wnR/iwgGvVv9lJESXMZQQDc6MPat+IpGjBHYZA==","signatures":[{"sig":"MEYCIQCZXhbLlIn3mVTuJtXcCgewTUBIWKCikFnp5zKz/PWu5wIhAMGBib29o79vilZODgay0S5nQmy9ogGCtZ/TQk3kTBbt","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":577300,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf5/ijCRA9TVsSAnZWagAAu5MP/0L6Jumj/UGgHzC5qqzw\neC/slNMHH6euHPjt61s0CbzNbIPpB9XvYsPu9awJuzJSlMyMa9b+Xn7fRX55\nWpWBD7w5B8TxuxLwMpuDnDZzYhne1PAO0wOx8NH2somoXhGyTOTzljfsBQIQ\n7FKNwwl4ONM6Zhw0BU8hZlr4Riojc/sdouuFS7DWUV321l4sOTmRMFLp/cxo\nEbLG3V6vySd1xfuZ3KSH5Ir7Kux7uVLGmH9SzZTQVpJ+BDh8eSqeoWpgq4a2\nDVIeWph9PWxF/S1JiULhHgBLUvxptTXG5LhAvwsnbbCWRqnEmrA66j5TvzTM\nO3yv6QN2Kpke8bo7ejnWble7CkAjGZyfLXt2nF1VhZrBi1qnf7FQixrK5UGX\n9olhDF+vukrMDSj9h/q4gXaL1cU7wMG83FFJ4YZmGxhmAtQ9kRWG0B7Uqbax\nc9PVRdht/z/GozgJsEm4OpVGcp6Ydle1w1lGFWJLuOck9fLCcqgrnmNTTyS2\nlYXSy2Lxn97n1hYDEuEz00h8WIGkOEQ/phyXRofJYVM4TSFytMmiR+T2wRTd\n/LHmsmYQreD0kgSSrPwbW2c0nWAcDmn1H3KFSpF3mEZval9eVEDchdXAmpYv\noeaKZEBx7hqNNCTrlXs6yiHI5TIzUdrFmzXLfxOg/7O9h8Lv3v5WwzsHAcC3\n1nkq\r\n=fgrQ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"cf670f089fab700493658f454c308838c4b5961c","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.6","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.43","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.41","@balena/jellyfish-logger":"0.0.183","@balena/jellyfish-metrics":"0.0.210","@balena/jellyfish-environment":"^2.3.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.16.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.0","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.9","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.67","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.43-renovate-non-major-cf670f089fab700493658f454c308838c4b5961c_1609037986594_0.790687563552047","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.43":{"name":"@balena/jellyfish-core","version":"0.2.43","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.43","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3adc073b584c9a387507523965bf61f0154bad35","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.43.tgz","fileCount":95,"integrity":"sha512-RSCMU5NFDn5A7hVE72/loEuM2ZOTYRjT3cILokneVQpuBWYFmfXhbBqAnHB4wF2R2QeiDwHNWGjodk4IBs6a1w==","signatures":[{"sig":"MEYCIQCNIzRC9Y5Eg6Ed69WPVCw4z3ecMjtxO/rLoYLLZaVWfAIhAKKStC7V8mxP50bRGGgTwvffEvkoWwyyJ6UTBaEtMuFs","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":577240,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf5/qFCRA9TVsSAnZWagAAu0kQAJuT+2tgDs1Mimi4iDKE\naDwK4SAM97I5zcZY6jQ2H0BucGfmHrXJsjfFw3jKOK4o9BuLSgotknr/vDz6\nDGYHdALepaRc4vojK966ObGkFFETSZIBGppJQLfBE3/1eGA+29muqRDwCifG\nEB1W0i9yfIEMlsSGIbLcqxoLSXo0b9tO4zmeEJfGLi9H0BGsSf0fG4uIh7O7\nrfVinf9tWjamWX7rk3yOP/Y1XXJuNw8WNpUtTz7L+yWUPOQ1AuJaHPF9LjA1\nenEv8GzssuaKZ7cWAxeA3Lj2t8Ibdky4x/TqEWiwSFKfNIGVrMtA7A6WlBTy\nJazjP9n35M62b4cWqnqkadl96rrlBtl4/pX1+SRWglD7nNtl/LHSFzuzxqwq\nEBT/yHMCyH6EgWYJ+Lxb4i01/76Ab9VWAdU6PyshPiwaEN2f9+IBrOBUzW3T\nEdAfuKLSEkxlb4eEzQALtVGpLr2/FQz2+yMNNUyFaMPm3zZOVxuUie2oJXQJ\n6skQFS9lwn7lw3EYYc+MqFmZrt9LQPXvuNIWxxJTVqJMupyggQEJGWi90EQn\nY+RX0CWtHkycBIJn+B3NseGWIR8FvnHHTTmpCaKc9tGMkOjCeRf+LT7GvYr5\nzppm1FEcjErfbipzoWo9P9vvpibSUNfeB94Uct+E1XslujBH1f5P5DbJvUU4\nScr+\r\n=FWM2\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"665c2dcef224d9f5e732317b6426c43aed17787a","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.6","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.43","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.41","@balena/jellyfish-logger":"0.0.183","@balena/jellyfish-metrics":"0.0.210","@balena/jellyfish-environment":"^2.3.13"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.16.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.0","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.9","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.67","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.43_1609038468913_0.4369671861741464","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.44-renovate-non-major-85d4a741a7c892d6837fdbe63e99bb09daa1a3e9":{"name":"@balena/jellyfish-core","version":"0.2.44-renovate-non-major-85d4a741a7c892d6837fdbe63e99bb09daa1a3e9","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.44-renovate-non-major-85d4a741a7c892d6837fdbe63e99bb09daa1a3e9","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"bed3091152032c6a393e798df16918b2b5a769bb","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.44-renovate-non-major-85d4a741a7c892d6837fdbe63e99bb09daa1a3e9.tgz","fileCount":95,"integrity":"sha512-No0/oRx9tZlKcNKXrpA7ssEp5PKs6Wx6/sqCCj1C4el+EeRUrGCLX0QZCWHD50va3smJBaTnYQ1cY37RSJnbLw==","signatures":[{"sig":"MEUCIQDNl5SLstdgPGfNBoTz0p1KoZs3ymVdoQoJEWFzz4sFdAIgaJkVSrYvS8UCwg5p7mffBjmQXEmVa7XpylmnWZ93sYA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":577405,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf6CRnCRA9TVsSAnZWagAAGMYP/0GXJ6Puvs/amWks+TOZ\nOQ+VZEaiwzwdW3U6vCkA+9UilkshawjFYPolX7VsqeSlytI4dukswR+qWJN2\nKgdILdl8qB/gHVcAM1GS7LrvB+9k4JYTCJtijXtQec5Bn/8TLmw5ESJEbvLD\nW68/tN1b4NJda6FIo3Ykba70HAja8rhJhVmwjlO5vZMAuK0hSDOM5/jRhekS\ndxi4j5tTCzDOqr3tbocv+ABkO2Vqt3sDVwRW2ZiabZtsOq8nYH+YznDggq2+\nuXgSZFs6VBC0oRTfOBuSsxHzXTK8xYz0oOxbxlrEQJkw/o05B5ZvMdz99EbQ\n41RgFGAxIaVDzold3rMVS8Up9PwCLNvKMfRGLEg0IsdO6cYYhYYGBAi8ijAW\nLwtHllELomOOAZhpjtn+BWVwpgz49rJzYHQ0YTnD4ZwwEoMfkfp35Is9c0In\ng18DUrJCfhI+yNXfmz8T3ojLGuAQVmCWl3oEIQJJ6iVCbW9mgMB+d2kRdpak\nAzdGod0ztvAnsmp82YFJPrWm0TKGGsYXepXnhrRuc75Q24yAt+BrK1CWr1lk\nw+xr9Y44dkeNmnXUB27Y7O0PHD5pHiJDyezz75BJjjHyG4IrBcId53kAJd7G\njg2xvxaSb/APGHQQKXiluXry19LAxwt35Nvw8gxLpTmciXBp2NCNuanmbXHu\ncVXj\r\n=pd90\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"85d4a741a7c892d6837fdbe63e99bb09daa1a3e9","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.6","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.43","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.41","@balena/jellyfish-logger":"0.0.183","@balena/jellyfish-metrics":"0.0.211","@balena/jellyfish-environment":"^2.3.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.16.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.0","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.9","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.67","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.44-renovate-non-major-85d4a741a7c892d6837fdbe63e99bb09daa1a3e9_1609049190804_0.6304438311356244","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.44":{"name":"@balena/jellyfish-core","version":"0.2.44","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.44","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f3c69b10b758c8e2f90ca9d8b454c8ac7bb8057a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.44.tgz","fileCount":95,"integrity":"sha512-kZJHjiy7+1hkueoA0C0Kq4tXoIY1JaTue95m61kI3W746HPVsAvf9SekqljAwTIjooU8K5sl9UH9vdqjFA4gyw==","signatures":[{"sig":"MEQCIEztnoJwwyOylho7CLYmeeXgq6r9juQHHYla7qd1x5YkAiAxLjMiHwm5rjxOtslGX+aWfi95rBD25mZXniVxbsAtEw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":577345,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf6CUnCRA9TVsSAnZWagAAFDAP/2wxu5HEfaphAB/EyI9g\nVruUbTc6UeZrjC4snqh/vUPu5ZdRC5NpgZFL8TrPkF5XrOQpxt17q11AxhyR\nO/D8LqPEGaQHPX34t2j2FBNJINGJCd7zg5OWCO6EUtklG4DR5eHRPvzlNzde\n/gLo/mZiuvsLqrPW85UiIY9PhdBigtDJouYCLtQ33afaJ63KcC7t7kTDdaiY\nxwAZq4jDUiU/ShJLka2hlZDVh1uGsxGjUpsDfkIpvUmGywU6Jvh7xV/VoGlc\nL90FZAwqsMggpR21fExFXJ/WazHRd0hy6EzPwrRFrRjcs0qkOOLVU/cwcnDZ\n3n/IcGhT+b3GoONj2628UGxlF9/g+dKBgHMZR4Y6dpX9kFEqNfLzmz9P9vlo\nSe4e/I9dgGAuEqxSvKOKXkf5xHKDW+1jnxYxMJIXVoEkhIj7ltY8kNxqbeM5\nqD2Q6N9CEm34Tv83YiWI1wI27zW7yPycHm7juY9NUXtRqokgos8uXtul6AMu\nhzay8qLyGThQfZz11MW47QdA1NMjNXrbS+3YqStVsuiHfLeQEVBpIyyaGhBO\nDQ/FOXGQE7m2ghrcJ1S0ZCzI3Xd3WIDPKSaXVwTJr8zVgtN3kvZD5EgPTr+B\nM/oQjMUsKlivb7FOErFaAY1cXlqTFTzh1zSpj1hLZ2z/0RV6UUrBSmD04qhx\nBVFp\r\n=T4Sf\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"1bf4577a6eb83e983e2e150648c6fb0180238aa4","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.6","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.43","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.41","@balena/jellyfish-logger":"0.0.183","@balena/jellyfish-metrics":"0.0.211","@balena/jellyfish-environment":"^2.3.13"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.16.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.0","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.9","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.67","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.44_1609049382778_0.24810066628717808","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.45-renovate-non-major-6bd6718a9bea91593a90811ee3353411c4e8db94":{"name":"@balena/jellyfish-core","version":"0.2.45-renovate-non-major-6bd6718a9bea91593a90811ee3353411c4e8db94","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.45-renovate-non-major-6bd6718a9bea91593a90811ee3353411c4e8db94","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"90b89052b367623ccac27c7d8fc6601cc9a57863","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.45-renovate-non-major-6bd6718a9bea91593a90811ee3353411c4e8db94.tgz","fileCount":95,"integrity":"sha512-nJcyU2E3WpTs8960UfJU1B+DIMlZBum/p31tCqGwz3BgIp4zkoeP7zy7ruLjGuEkhT4SEXEqLE1Qk6oyyfoTlA==","signatures":[{"sig":"MEUCIQDL0iHmodmEPmbH38CyCxYG6Q1HiQjJoF09Ubrfz06PuQIgVnfpCoVJUm4m+bSr+B7qUF9sojexJjI/woSkZM1i7zw=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":577506,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf6H8zCRA9TVsSAnZWagAAuccP/AqpkYj8HLSJwyNjrXYt\nTtjQ335ZNGOk5D4zpjtwPT1MdCdmaR2oM/KMSg0omKDYlz6agLjVtruCDdLR\nwJpZyRSNOauDqeYDnzgtA1GGkmgItV8CtfeH3isUN7tGOT/ddxWR7xs+4w/E\nqSSbRfT7Goi1Ca1Qn3D9mjXsdtH7eNuSXuiIu6IFsSpX+VubPUUDaCwAr3ug\nmDT6yOHN33xXKGhIYVW2EohldHpcdJaa77MK2dOEgbv1/Caf+5gqVHEMhvkR\nZ6dzXgYDvtcjQn3LZyEglI+zeAcKgJ3/+RAa26RbyW8NP+msoGA0guAqpU5U\nmD0MJLIkYAGB2rpVozGX0h3buQZAEgXsFB/s9F8yDpZzm/xku4+3n6TKoqf+\n/+5mnCeD2Y1Ik7W90JoH4VIXxeosp/pVB5zKavpLX2N9CukenR+718bzTEs0\n4AfsbMFdPC9/u9FCDZA04m1BrvtAwULZt8d8ZtI6ktENoCBElWh9g5OUhRUu\n3EifGXz/2cPuOfZzW9IBMzKaQk1wC1DadGx9DSBO/nLPEsN9mUPB0TgmTFEs\naWiVRXAWmlLv6+CZoa3dlIB3uQvAhETgtAQv9p2vju+6t8ycNsArDM3tolR+\nFlNMgV0wPDfA/koMnkqWlsZOFgwHo3bm5DBI0/MyVXhMl9tItMHMdHbmkCXe\ndrjj\r\n=wY6g\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6bd6718a9bea91593a90811ee3353411c4e8db94","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.6","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.43","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.41","@balena/jellyfish-logger":"0.0.183","@balena/jellyfish-metrics":"0.0.211","@balena/jellyfish-environment":"^2.3.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.16.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.0","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.9","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.68","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.45-renovate-non-major-6bd6718a9bea91593a90811ee3353411c4e8db94_1609072434484_0.964160083676914","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.45":{"name":"@balena/jellyfish-core","version":"0.2.45","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.45","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6bfced109be99e67a770ecbda6373930c47ae624","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.45.tgz","fileCount":95,"integrity":"sha512-Hh7PoYcmK+MuXdNlKXbzysA1CdM8WiaxNW6Ov6CoDwGotxgXIuilYTpdIo4LZ25d7fP9ILkLgxAIgFSeyT5OZg==","signatures":[{"sig":"MEQCIBnWylG+zxN8zU1Fgd33hzS6uX5/YtJ82luJqZvPYJswAiAlmmfuz2ZR/CQBIf2RqdIX/SA+bRvt59aavb30omT9mg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":577446,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf6IALCRA9TVsSAnZWagAAy9wP+gNfbuarSAFrNfiJtod2\nnAXBZlsbEpbQtDS5fPmxx63IWJAzb7cFuSYowFd6fXXAA2UhTtw/r6f8fxPm\n2ufrRvu/kSps03UvwpHK09W995o833J69m8WxzzsxM8Xh3il0Xx4rwNIQEQr\n+yJuit2TIpvW8c+xKxxgCoMWHmm5LZ8SfKTf7UX8+iBGCEGqZE2bt3+M7PO7\ngfs81p04e4M52Umg8/MkRGKtERLhaJf89zPxTNx31ZyuN2aAzktKfdOF1wIH\nTGf0D6lvAnvnVcL6iD0pUbLUXRIYCbRlJNhaS32357l7RS5kHLkQkf63L3iH\nIzyE0q6WEaOhPytZpJpZw2Z1EWyk9GqXuOxv7HiJmKL0K93jBNEQBWHTI2oW\nW5fC+e4CzOgzNXZWfcfqKbhX4eYJGfo1mCK5cvAlO4ymCT3mRL8UlZVkFyNk\nxqOSdNMum62HCHj3y4s3DdkPOlulG5gh0JuHz3x/yX2j9+mqjZOT+1X9UB95\nq06zgnjJWd8zOP6tDLCOgjILHm85tNU87dmgEo46iIGZAcag2qKzHGL4JMcz\nU9x2Y8L4vxduOv1IduanSEpwOGQPj7EkhoAaYFlfFwxBitVfM/8QZrPwyzkr\nlaRpp/I6XaQb0pEET9/4HRC4Gcj6qbcF4OFYtWDDy/wNGLlLlJriPKGUl1EY\nnDeX\r\n=504K\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e88b27cc3b867c2cde3e9247009eb09273979813","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.6","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.43","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.41","@balena/jellyfish-logger":"0.0.183","@balena/jellyfish-metrics":"0.0.211","@balena/jellyfish-environment":"^2.3.13"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.16.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.0","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.9","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.68","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.45_1609072651039_0.6999152663530164","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.46-renovate-non-major-6b7957941899c20ce47ce7f00297302347cb374e":{"name":"@balena/jellyfish-core","version":"0.2.46-renovate-non-major-6b7957941899c20ce47ce7f00297302347cb374e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.46-renovate-non-major-6b7957941899c20ce47ce7f00297302347cb374e","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"15178d2013a8b37c33dbac6fcba72488ad5ba749","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.46-renovate-non-major-6b7957941899c20ce47ce7f00297302347cb374e.tgz","fileCount":95,"integrity":"sha512-nPqelXbQmA23TgYQRa2iP3TaQSZaFPUvx3oB1Z6wawsGMupi0CMdUm15Ffp4nNATIInpTIAjqUd0YvyJ5Znc+g==","signatures":[{"sig":"MEUCIClpLZ+TPM/bcVBjWMBZNgh4ALxX4qThf5T0RPDCn7hrAiEAsf5nT0VfMhxqX3FjoM3tlIu5nISnBe6MYUInr61RqlQ=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":577572,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf6V9NCRA9TVsSAnZWagAA/DwP+wYvYSYrcqNZTu0uIeIx\nOZ+FUTfvOqnhtod4PffXEeAI+2L8Qt2bWVNtdl2W8PbYQ1qzzS8rLnXz1PlY\nFiybCR7fUKnJ9Vf7WGCppElG8Oss0GGTTHYxadNxDkmd7qV3j4uiRz+ioV8/\nPvCpAWDJcfQQQYa4IRFBhnneVllO0p0ByiyyW6i+tptOduJHF9a2ctZXVxU2\nC9gJ/2c0ZvWJMKhU/U0ePVHDAtaPlJAdLt+gFNfzM5o5XYFnVD+B2WjBGRo0\naxYqcWH7sZE8TgkO/F3ZuaPEfG1j7iCKe4fbF10xOtuwTIJLlazTMwYU6rdF\nE5uH4+qSny1QNoZ7dhN+pZvHOZH7t1XDODKrhEFY66h/Kr1J3RHC8j+IoXwc\n2gIZI8WiJFmYKiOwECAbCk9q1fEwBoxdIiw6DqN2WlQko/tLa9ua9jL/w/67\nR1aYkq8nPdKIcWR6quxCcN5g8WLoL8nyfZPc1nOk4SgPA+NzeyB6Vj0B3Fae\nal1opN8jOq3vGVKknmTWLcliVIfCkJLTTHJCJQV50Oo+QzCTRq/6+TNf+odb\n+H5NVD4mSLeb/A4t/3enyWVe+61G/3L5O9AIauTgMnyt6MZ3dyxJeeOLMsBZ\nHypwCDlvTAjouSwYIuNxwSkd0deu2lhYZCqjejM075r1OO+YplW/pAy9qFFP\nCu1E\r\n=6i//\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6b7957941899c20ce47ce7f00297302347cb374e","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.6","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.44","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.42","@balena/jellyfish-logger":"0.0.183","@balena/jellyfish-metrics":"0.0.211","@balena/jellyfish-environment":"^2.3.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.16.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.0","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.9","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.68","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.46-renovate-non-major-6b7957941899c20ce47ce7f00297302347cb374e_1609129805058_0.694596405342675","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.46":{"name":"@balena/jellyfish-core","version":"0.2.46","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.46","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c0b1df887025a32695b233d0b1226ed9be441ae9","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.46.tgz","fileCount":95,"integrity":"sha512-VvTMf0MfQkXl8sbsswGWsD7LaOFs7umq+G9CmJrfKsFS/PQKhdYzGcfQ3vg20gI6Uz8X0KYC0mA2yuMva5vm4A==","signatures":[{"sig":"MEQCICrnZeZZHDRxzyU6BpkbsqERGOJLe+EnhYlVpCpcSCAxAiAy5q35jKOkOJ/mIXYfrhVXD4KgY3KykyrW/EqTc1fSlA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":577512,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf6WPvCRA9TVsSAnZWagAAawUQAJ8oZik9S3na4RxyxwxI\nmxFYDflimVm1aJfKVVf5Viw9BFJL3zLerHT8QXg3PxxWrIS/JFlu60te1K68\nUruQ5yTfy80MeakgU6zoLvAwJKovJqvqg8vr/a6TFGx1lWq1aOpOCwYQwKy5\nwXGhYEg1u9pKq9jMIHINIwZ7vJmUc0DqzVQdxi2LYm9z/yofXJLU4j8nEnv4\nenvVnCe4mXcr6TExO90vWCANkGhArjhwzwhyP40q+0omdtiqcda+qseCdHt7\n0AU2rHpkalPaBql8qUs1CpSEPhgOtLaCO33p5wlBVUI1LM67RkyLfudVP93n\nlGT8t/0Q3hgih0+dGI5AX5mnUwACsNS+K+cogvuzV1SYtYnGQUvRNmrr1Eys\ngPW5f5BMCoabbrrW7l65HBdJuZoxxiISaW+PmUkimuKc2MLz75rUZwIjYQKK\nMUF0MLJUfGaD3MWfW2/932D3WuJyKlQLccfgotW2MioCTaIOQ3gngw1GsxbX\nn9ZYimIVqoFzyMRZ+14vsT8Pm9lG428+ULHjscI+jQToG6dZKl1CWEesCRUY\nO1QngIIwgJRLSPm0Y4k1LkIQnXQhobQ7C6THkk1/OF92OlyzYmuKhVlVevz0\n1KD+2YhO07aJHMd6i/0LHrnr3VJbz63Kvh6Jt4pd+EDhnzJxRpnyMu0KJywm\nqvzZ\r\n=ITyT\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"cd6505b4fbbe7eedffba2672aa793a19bc45d0fd","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.6","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.44","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.42","@balena/jellyfish-logger":"0.0.183","@balena/jellyfish-metrics":"0.0.211","@balena/jellyfish-environment":"^2.3.13"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.16.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.0","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.9","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.68","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.46_1609130990682_0.11561248301056493","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.47-improve-patching-6323104071996f167bb5e2a721404f1c28a94264":{"name":"@balena/jellyfish-core","version":"0.2.47-improve-patching-6323104071996f167bb5e2a721404f1c28a94264","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.47-improve-patching-6323104071996f167bb5e2a721404f1c28a94264","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c8ff5ee2d09fdb6aad85fd49d5b97970cbaf9f80","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.47-improve-patching-6323104071996f167bb5e2a721404f1c28a94264.tgz","fileCount":95,"integrity":"sha512-WLQnsEihdiz1UIzQH+xh9meuUEFxYgM/FG9S82dPZTJppk8wn5quS90F23yo8dSTY/S4NKLhQfb++8sDw+9NrQ==","signatures":[{"sig":"MEUCIHY9xVghQq7y3JYAHAKIpzoE9USRXTN+XTOI6MdodLEdAiEA9uHmHYwY8u1QoBiYlPdXdJwWnWBVKkgdKy/xqw5/pB8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":582496,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf7AzNCRA9TVsSAnZWagAAM/8P/jhxZ9wVoC41Qb2AKPFI\nM98Ik/euXq5ou4N8Zq+yspmAB5tGJEP8buvFIdLEbNHUnhwaPynSB4Q6Jf5v\nzDSksqg9/fFLZfuLdZpidzluuJisMaPHldt3JGuX8LJhB+nGEItlYq9t5uXH\nmlbIvNVLMPiRyECE9x4j6S3PAA2m8W1y18ECALMBp6ngGHdiUMIIV9haDyme\nLG2AZiHjdZcm3R+9OyWriFH212Ek8lFUIR5WdMVTA2po4qfSj71UpPdPhZ30\ngfWC3Cqjig7NetZA+8DGvDtJwO1QdOy/8aGosfP/5hqvw5vIcSf3l2klJX76\nmq8a48QOJ000qgWC14d9qVjCD9QBWfP2Z6KHi+y6cw6EXNmKeNVooaDdfTZ+\nR4yn8DPcmoZgR2rjBOyuXrBlvEJlW5nt88ChYf2LlxsbbY99mJ4hJJR0JPvH\nq0/Vbu5BPbblQDDu7C6pHMEab+uuH5qnBCqVQEhXTw9eDPiVmYUjMEt9XOAX\nJ2bkg6ms1PKsyPaLOuL9H+7GdhGdcJlpcesGjNsO4yj0dG9vxiCOelRCN1IE\nW2PD3rxQ3nVkVLnzVBAKZHw7jpuAph7shBbpWrTkL5Fih4rmt9aOPe0ppugf\nzaC4zvU/ufLeyU/89YYVFpmqqLQaguhdta3h/YhfMffE8Kdp6BHdPiunbbtQ\nnVe6\r\n=e5FE\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6323104071996f167bb5e2a721404f1c28a94264","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"flat":"^5.0.2","redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","deep-diff":"^1.0.2","pg-format":"^1.0.4","pg-promise":"^10.8.6","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.44","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.42","@balena/jellyfish-logger":"0.0.183","@balena/jellyfish-metrics":"0.0.211","@balena/jellyfish-environment":"^2.3.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.16.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.0","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.9","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.68","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.47-improve-patching-6323104071996f167bb5e2a721404f1c28a94264_1609305292694_0.9483220136104442","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.47-renovate-non-major-557aca3e5565b93678b024765597885a7aaaf991":{"name":"@balena/jellyfish-core","version":"0.2.47-renovate-non-major-557aca3e5565b93678b024765597885a7aaaf991","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.47-renovate-non-major-557aca3e5565b93678b024765597885a7aaaf991","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"133475920152ba0bd7e9633b2b999688dc50ebfe","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.47-renovate-non-major-557aca3e5565b93678b024765597885a7aaaf991.tgz","fileCount":95,"integrity":"sha512-9JWPvgohEiG81+SJ/esMbdnvJPbAfvTKk44/yTMAlAja33gri14fQgZNQ4olVWnheFwWgzXxAWdFvw6iP+U+tA==","signatures":[{"sig":"MEUCIQDo7dWMZiRVhkIMXFA1kHMhBY52lzqiclUMt9w/ZimAfAIgdXmq8sJmV9M4Lt05M70/zsdDnwBOxbuxi3wPEtYxB70=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":577638,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf7CTxCRA9TVsSAnZWagAAFeEP/A8rNsKs3vZSx652p10n\npx1/ZJtMcndzii6WdbTa3kCDxTeiqN4P+guiWPZ957jwtbH+pe0VRszXThae\n63X5HHkzx6jET4bkChdVW59/jOsO15UwNLbSSlJTYLhO4v/gKGQatxT37Cag\nCObYRfsMnEvqWjPq88IAcBLCvsSmMed3ao7xGV4XZdMPKfXJy3bGUsw/X/xP\nk0ZLE78uLXdcFnUn6waZtGrNmcuqXgpVZp9NKRChyQFtr40iuUNPZWPGtza9\nziy9Sv8AtfCBd9pds2V5II+iny9dEwT0KY+cH6PhLxj6+HWbuaX1cf3/cRR5\ni2moO2Fw3P8AKhbOuiYdQGoKhmrQSHkYjyCZf5FrIkeLKcaZryIBQ+HC97eD\nqgJkZFyMqy4Kd3eKXHaKQlL2asXCEH/vb1vVWWnXsXEfyYVlWiMaNBz2E7kZ\nwnGUD2ocEVRREs8D0MrbeE5RYZGPEO+xlXewSN2rN4ABDp7pQQnBGGmeFIzC\nFLwfAk5L5BWtUsxJoX/lSba9e5Jp/aOIeHzSbsFCrnK4xZYM1t7dqoAk18S4\nQYY37fp45/ZRyDvCPWK2Ww5o8/+sJ+mTVw0SANuaeANVIP0VtJF0Qc1mjkFe\n5eSXp8QIxZF1rERw6m2Tb05iPmeAmjDUTRf4OmUuYo2ypC9HdN1VFByaQcur\nyLWR\r\n=s1Q9\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"557aca3e5565b93678b024765597885a7aaaf991","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.6","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.45","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.43","@balena/jellyfish-logger":"0.0.184","@balena/jellyfish-metrics":"0.0.211","@balena/jellyfish-environment":"^2.3.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.16.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.0","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.9","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.68","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.47-renovate-non-major-557aca3e5565b93678b024765597885a7aaaf991_1609311473213_0.8211470340161922","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.47-renovate-non-major-fac73fdf33dbe5f0cf12cb5dba0ec61dac3c3945":{"name":"@balena/jellyfish-core","version":"0.2.47-renovate-non-major-fac73fdf33dbe5f0cf12cb5dba0ec61dac3c3945","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.47-renovate-non-major-fac73fdf33dbe5f0cf12cb5dba0ec61dac3c3945","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"7b4796071b1d60ea1a728c95ca9727938d36b84c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.47-renovate-non-major-fac73fdf33dbe5f0cf12cb5dba0ec61dac3c3945.tgz","fileCount":95,"integrity":"sha512-fasiWvddo6Q7yvKltg2AKFwcrvWWhIO+1sHxgxgGmjaHyungbVq8dVBv4SlRS1bWXjP56YiXVapXmtrU05gQTw==","signatures":[{"sig":"MEQCICjhzNWmhNPBegqQFRbKMlZHmIM1DB/fDiX6Zw81iErXAiAzH1Qjdd+MMgiAuCzn3cEqUmpsD4ejmhB0nSuRz6Vp+g==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":577638,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf7DITCRA9TVsSAnZWagAAOlIP/3qV8BUASPAORXIjr+pJ\n/RoEEEoG6N8B0kXp8WvGmveBpsvIqy8x55TeYfIchBBNymfLClDKEqyXyzlg\nb6BG78cBSZCD10YvE3GX6pWNUfxZwwnSmxpGZR+ENYJQKqHnSGE1zusPtqdA\np4mQ+N2Mbzoe2zmrgq7RKxiQ6ayi/NS21Izlzg9XHlj1LXvXmaPKb9CfFKhb\nAJoEdotg45APbnvRGZDGY7eDMLrgY6l5CWWusIvWA0AyKZmE2oXGwjnkpsII\n6NtxLWmA+En8jup+hYph4r7AEMcspAJmO83Bn/qT4tDZC5g6eN1o/ZoNJv/x\nKYu4cGtrcaDAWQAlcLXBqnuI4/WqcVWBdsW4LedJcEI8XbdtsmJD/14gyRX0\nc3u2hQTFB0tImGaXwKE7XOQVogOnBKOBtbbqO70iakpn4mBvFfNStxgmFc+x\nw8j0Yb3ey/GMK+uaL5/FA3hl4yUPdqv3enG0Lx4QguCtUYwLiAVkkn+0UszN\nLTy/Amb6P5JUhA+5kctvQsRWlbPQhF1hRc1C0C1jL+je6n3z0j7vXSunW6VJ\n0kGNMW2f6V/SNDsDPqbNW55GkygZLwjAzmDBh26IEmjkPojuBgL0a8cC+bR0\nsNon/Okvw2Lb1D9HCzj/BO13jN6Zfmet0NVmu3MPLaIh8Lk7RUkzK75qxQ+S\nk0XY\r\n=u38D\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"fac73fdf33dbe5f0cf12cb5dba0ec61dac3c3945","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.6","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.45","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.43","@balena/jellyfish-logger":"0.0.184","@balena/jellyfish-metrics":"0.0.212","@balena/jellyfish-environment":"^2.3.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.16.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.0","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.9","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.68","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.47-renovate-non-major-fac73fdf33dbe5f0cf12cb5dba0ec61dac3c3945_1609314834547_0.12095368076386581","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.47":{"name":"@balena/jellyfish-core","version":"0.2.47","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.47","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"64261fcd3b9b6b593a3168914cfd4b5d21d3d1cc","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.47.tgz","fileCount":95,"integrity":"sha512-7w7mDaTS3m+lWWNS4gHhhaFRvnrYn9f9LcIo1iW+upCGgSNConLpKbJfcxB+Ayn83iqr3CAUMIh+RZUbXVjnfQ==","signatures":[{"sig":"MEUCIQDSj6myhhiV+S2nexnLH6C0e6cV0AYN2TM6aim/62b0AwIgXIZba1AAsBKeKBe9bV4yjbZzh17n2Jc/TXreBsJoiUY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":577578,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf7DQUCRA9TVsSAnZWagAAttsP/jPL1MKx0an9CxTon7D0\nOr4cn+TTmumFde+L4cg0+woM09PdyMe5Oub1mToIBmdRe6iBAt1vg8Vp20//\nUFOwhdjRXNWC7bosM1YbZGYdvkYVw/CEkfWASRaQDaQ7+ds20ckpdWPvAbP0\nPG3d7l4sGmwJjJAPO4PdWkUgE52wzRAzTLlDaemGBTw0zcdg4MaPO6gFcsGV\nYgYMoNewFkfx4qLfqF5y6kKwBa4WrduSbCTfamNmA9hq5LnA/+pwzcST3VU8\nMRqWAGOTW0YPxasWO541p/z4yxPuqlQ7nISJEBK8ZWLS2ps5AOzilYYcNSzo\nrp9mhKTeCwViQbbj/IQ1f7rCkPNIYc07UVD7arWS+c36pb6tAt/tDJnbarco\nNnYkxlVELD6qwUJRiCbgjkQMLpYmrsBHRMAua3W24jUP5F02pL7sImHBaYCa\nVX75VmpC3JWRoYxq/IQtZGW2vzIFIwUJL/qdWpDFhLans9XFmUq+hzDQaXaY\nYtxl2tlFFPuyLJqq8mOaR/6kyMmrUNbR5P6BnvN8yr+BApI/rqD4MwjjfyZA\nH/8789mE6sVPOya3oDOu8FhKoA/CurlWTLQloLJj1ZCz5DRHwjgbKwAOZkZE\nGM+bPJFCOuZ6QrzXCYKONTVwglURKCdVqM8QXek7ve8/Gd6JPZn4w7udXUCK\nA+dc\r\n=5udD\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4f48c8bcaf5e8ddcf33e2fbbf0558b7771b8c2db","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.6","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.45","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.43","@balena/jellyfish-logger":"0.0.184","@balena/jellyfish-metrics":"0.0.212","@balena/jellyfish-environment":"^2.3.13"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.16.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.0","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.9","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.68","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.47_1609315347517_0.14785607668083767","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.47-improve-patching-648e903172fb8ddcd19e6e47e91a830d7b238001":{"name":"@balena/jellyfish-core","version":"0.2.47-improve-patching-648e903172fb8ddcd19e6e47e91a830d7b238001","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.47-improve-patching-648e903172fb8ddcd19e6e47e91a830d7b238001","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"04957196d02f2035a46139b2483dce7f566243c1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.47-improve-patching-648e903172fb8ddcd19e6e47e91a830d7b238001.tgz","fileCount":95,"integrity":"sha512-UY1WL17vcOYLWNzsG2iNH+ApcziKdLyBAelQiKj/A0e1mshmF62ajbrdRXaOpaglmstJ5Qz+8pCIjDi7mbMSog==","signatures":[{"sig":"MEUCIQCaSskIWtKWAmbX1ruWRxrc3d4DvqopRvLlUMkGlHSRcwIgOKuq+czicgQV41+ZoNsoz44p39Yw/5xm0x3Zld1XXmM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":582367,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf7EizCRA9TVsSAnZWagAAv2kP/A8zNyb0rBbsBAEGSDCp\nBvYYjowsvgXzYD0KGB1YAUJItLTU2L1R4YSgknXhE+MSQNMgpAVd9vBdzSnJ\nDdAl9rF0scY4pVDdY1AQ67FUdXVOyKcXQD16wFAxh5Y+V0NTh3RwNgvth4Ez\nXclPgJ3lpmVBGBAYtyWr76nsoHxnCX26GZ+i/WVnnQp4qwLlpotifHMZ949+\n2UBBt6H+siBMLT3Cy19ZA8MbmPFrjIUfUVMmzLoLwJ315R+6uV0XuDlmNFay\nenezVAqHu4QL9TPklXJg0KAI/2mqQL0M7wCbnLES0YqIlW7qSIdWDGxlwAt/\nD83GvcwY5E/69Q0MADEbx2bzecpKkISvrf8wUV5dooVH1pTUe7hyKW8OEeA9\nXQJr6OrFytiB3sLBAe5MbHfEp85DVs+OpYyf2Z3Smfv7x0igRk75NSMhQKUj\nVrJfMk5k9T+CSEnc/zs223t8iGnEnpkYUaHGGASIw/sbxHUtefNQSF4CPTKm\nKJUj6VODQN1jMxnMgS2FW/e7Zqt0KQ5HUrEbZGmZq/kh9krYjExxFof91Tk4\n9MMYd5LDl96qE3vf+zbY8AydiV1VF7QRqUB+1WBf1MSu8t1OwfzaIZsb07W7\nDtxeaZ0oEDc3LxigeWNPY7hrsXiXylF7FV6KtQp3zCwiSiWJh9nOZg5j9xO2\nF/W5\r\n=/BKz\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"648e903172fb8ddcd19e6e47e91a830d7b238001","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"flat":"^5.0.2","redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","deep-diff":"^1.0.2","pg-format":"^1.0.4","pg-promise":"^10.8.6","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.44","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.42","@balena/jellyfish-logger":"0.0.183","@balena/jellyfish-metrics":"0.0.211","@balena/jellyfish-environment":"^2.3.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.16.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.0","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.9","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.68","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.47-improve-patching-648e903172fb8ddcd19e6e47e91a830d7b238001_1609320627200_0.21573011408153908","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.48-renovate-non-major-d47b9436a88fd794dd974dd8530f00d58e8a47e5":{"name":"@balena/jellyfish-core","version":"0.2.48-renovate-non-major-d47b9436a88fd794dd974dd8530f00d58e8a47e5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.48-renovate-non-major-d47b9436a88fd794dd974dd8530f00d58e8a47e5","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"63423d78e4ff1c7a99ef429f64f9e1a425a05a74","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.48-renovate-non-major-d47b9436a88fd794dd974dd8530f00d58e8a47e5.tgz","fileCount":95,"integrity":"sha512-lHsmyUD7m8RBq08DJsippo+j62mdI1xcvRxC90YgJfRPCYgk+VMr1sRccITSC/z+wORjjkk4GZANS5CJ5IYw7A==","signatures":[{"sig":"MEYCIQDMb3dc1gkGNPMuHzudzsEsm6EIqIskqZ9iZKd2S3TvvgIhANkS0Su3NGbms+4Hxb6Awl2w2uoCMQZx5XfOGUPLRtU1","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":577704,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf7NZXCRA9TVsSAnZWagAABYoP/0NCSvB4mvoCZwrjtHQM\ngYCVp12uopgXpKs52rzynVMH/Dd8uW1pdDsRxm5K5Ics6dVcF4NPBdDj1xSc\nfB0FWgHii1KEDw/aJ1YDhdUz/G9rTxdHZETtEZB8Pf4/puoSpMgUu6HgEgjP\nvR506sCn/o2R8V8d3cHNSDWIvxdZAycOHHENmrI4yyhRjVIs3z8FCdzSJE6/\n6gqHMPRVT+pz4k4ugNsBHJDgtVuMpPIVKNzxx42x7+2kjPKBJ98WpZYTaiF6\n7cLEVuMubVQsAI5iyw+Bc9HhT7MNLUKiSiGeMWmwPSmNgwsS7CjgI+CUBkw0\n/dJ00zkM/a+b8TCvVtmJIg0ph/XRW/vOIsNa3T0jqxeK5QJ74MvaQD8lJ8wn\ne4xIZ0nxOT5FBg6En/BJ550I53GGhj5k2745evPwN9JHoSHgYEuWyyi6LQH8\nJEZd4U5F3h8a7qWDP8PDpcL+FQYoQO1tt/v5uwA3qvo2HgPsgW/Vq6yNXarn\n/OwIk4qoJnJzptxiks6ORH/Sr5mNnZgbKi8DbRf+GaxqIPFmN6wVEDFVAySR\nQxIZmmGthnVxj/4lytZwT610CcBJqvhXKeCo7rm/kdudo57vwMTmklFLJL65\nbAVkQg8XafhiMKlYVV569IDV5JK5MH6MEtqScR8jTq9aX0ukmmA8xbibg6Uy\nKjuc\r\n=AyzO\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d47b9436a88fd794dd974dd8530f00d58e8a47e5","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.6","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.46","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.44","@balena/jellyfish-logger":"0.0.185","@balena/jellyfish-metrics":"0.0.212","@balena/jellyfish-environment":"^2.3.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.16.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.0","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.9","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.68","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.48-renovate-non-major-d47b9436a88fd794dd974dd8530f00d58e8a47e5_1609356887110_0.45025048763245024","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.48-renovate-non-major-2066eb9254e4632df848baed23cf2b626aba64f0":{"name":"@balena/jellyfish-core","version":"0.2.48-renovate-non-major-2066eb9254e4632df848baed23cf2b626aba64f0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.48-renovate-non-major-2066eb9254e4632df848baed23cf2b626aba64f0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"85f057a01eff7365dce4c95c8e48a7bad5c9acb1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.48-renovate-non-major-2066eb9254e4632df848baed23cf2b626aba64f0.tgz","fileCount":95,"integrity":"sha512-WyfiOMmSlkUZb2V/wwXvrSM4g2peMMO2EKd8xyyibp8lUHZVb3DYh/BNcS5tgnovfRnRsf9eJhWK8VKk1/HP/g==","signatures":[{"sig":"MEYCIQDytlthKCshcr+F4meMNMfxjfmjPdC+JdgVKb4K7Me9IQIhAIQ2g5ML04E814hsOuYpc0JzgdmVmfiudoFCkJ8WUYz0","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":577704,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf7OOdCRA9TVsSAnZWagAAIRoQAI4VLSI7+1K2V8PYviXi\n0Z5ICvgBu1gJLdAO1i7hAjArmmjd00+JVXKFXSTYjIcgtRu8ZeaQeypNQGNd\nAQQYH9NmaamLzG1SCsxp7oXhwLW5zLuwY3jMZc6fj8mRixatuWhI/mDDh3Mj\n1xNqtUh7Vui0W8E48MMuSGyy8F9n/3waX1w1YSQv3XUT/6sSsxNDZJ/BAajJ\nHbbdQWTGl3I610FQfiAM8bNQ8wy5FTX75AVwTAlfTXlOuLkAZGRJksebWh7P\n+n0b4FqUdDckeXNHt/Z0zrugYTeiVdETd57K3IcltZ8qwYscKjJQlBvLH3NH\n0XTsCKBE8a0OR40YMEw+zU9b8LAJV9OrFL1++3houa/BsLSe+c+Ut8zxwW5a\nl80rhZxkA4oIX0NLjRV0OFBtUAJJlgoYjiy2RIhoI0t0CqnzS2mLZSUVJJlD\n3VJtK53Z+EAm1uubNvDXds4KIwIedOKjHpvbiNyFptJvKDbZWgJViI1dNBRq\nGMnKbg5c80M/956Hz9qjcZuBLqDYj+1O8IvzR8dCjAiwpId9O0rMqs0KNRco\nBsF+aXgKuP6Fkkxyy5Bd+AtBBp3+gPQAIjCvqsH8MR++WHKJSvJSE4iGQfZG\nIe7w95XVCwnS4IZqAq1Kt7lD9ZSLs+dx2JgiMON/5wPkNW4/5t7HfhQH3ADy\n/znJ\r\n=wenc\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2066eb9254e4632df848baed23cf2b626aba64f0","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.6","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.46","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.44","@balena/jellyfish-logger":"0.0.185","@balena/jellyfish-metrics":"0.0.213","@balena/jellyfish-environment":"^2.3.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.16.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.0","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.9","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.68","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.48-renovate-non-major-2066eb9254e4632df848baed23cf2b626aba64f0_1609360284439_0.34692749651635824","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.48":{"name":"@balena/jellyfish-core","version":"0.2.48","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.48","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8a41545b2db3c645e6dfd0fb8e76eacfcebc53ba","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.48.tgz","fileCount":95,"integrity":"sha512-v+4vVOJePY5CgxDl14Wy631JiYg9omkb7KgPm7y4MLiNS9pYLJc6DnZYB+8C+RxrxyBvbdnQUIDFuPdUKKEI2w==","signatures":[{"sig":"MEQCIHZp5lJ4msB8p+GraXOfK5tYwwxpu6ZOjys+zg1BPz7IAiARP83HFycXdc3aIRbdRvBwIEoIQugzggLREIKCsbYlIw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":577644,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf7OUHCRA9TVsSAnZWagAAHqAQAIywgKGRwwPKnSL++1ra\n8uSIuPVx5eZTHeOefkLNgK7bwj1lG30Bv2jgU3VskQ4i7iPxDD7+BSw50IyF\nKoVEFw8Bl9riWWPEusG+uKM25mSDy6zllLMwCIuWGPQLnVid26cFmbmakvs2\nZL/DCWH3K6FCBIpQ8VlW2uUWxYd+qcHWTEN6quqV/wZRy88zbnoglN8h1+qL\nrOswth3wVQoNIeDCi68y89RaNlvUqk3BFOX2PNM1JtcNkohX7tKPvzDfoDNX\nyvcubPihcJ1kQfn//05zxp+xjIZQ2qmt4HEVWK7SOH9Tg4cQVVflVEGwOwdj\nZU2zPYC8IF6h5MWagdx5uJyrox1GqKCSPSq+48NGoD6CAyogON83drxVFY2p\nJv3tuealigj7PCGMwli0yRWIOL+6UpGtXgVgg/L6EFfV57iz257A7Q+UiG0w\n84seAooA7uI3lSLKfdp4YdsL/l1KJAE6f32Oa24/9YRDH4pOTp68z3uhdf1w\n1ykgBLF4PJsC5vigICMPtjaQvLJrE9EFQGD40eMvM0mcMz5kB1l7hB1FWpB+\nh7FyHva0gSzPP2tFhcvXYuLZW65bnBoz+kp2/8GKebmSW724x6xjTkSoLxF2\nCBsUTB6Q+27wTX0GXP6YDYHH3dhxkLyqBJnKKonfokB+rrgthmUVolfMGc/u\nzneK\r\n=jG3S\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"eafb93e9955c832ab4ca2ab343460f001c6e863f","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.6","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.46","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.44","@balena/jellyfish-logger":"0.0.185","@balena/jellyfish-metrics":"0.0.213","@balena/jellyfish-environment":"^2.3.13"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.16.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.0","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.9","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.68","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.48_1609360646413_0.4179675839474857","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.47-improve-patching-44e43aa7ab40220195149f2afc6c743cbf1b673a":{"name":"@balena/jellyfish-core","version":"0.2.47-improve-patching-44e43aa7ab40220195149f2afc6c743cbf1b673a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.47-improve-patching-44e43aa7ab40220195149f2afc6c743cbf1b673a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"55890490a8e3453701e0169616c1b754ddc42a9e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.47-improve-patching-44e43aa7ab40220195149f2afc6c743cbf1b673a.tgz","fileCount":96,"integrity":"sha512-gH02xhfjYe4mI/oS1VnUWgFHlN2NMkrzDJQsCnvkY3Vhlxn6BEJF0le6QiThMDOc5wPWr3tuiveJjo4FjS/S6w==","signatures":[{"sig":"MEQCIHeU6ou/BptE4xImp0e1JnX7FnBHm5WM0etqXU8fs4lqAiAoSgYyIA/JxBu+4al+tk5Zr5B+dye2kXmpi2TbRG8GAw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":586501,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf7YypCRA9TVsSAnZWagAA6CgP/3MoF2EK2jz2IbbsQQGb\nl77dbZKpBqCPuL9fUYNxwk5S56wKgFrHDCBV7XkqPPZxqjQi0/9vS+I+u7a1\nrgag35gtNxHezdZqy5HBmzk/vZvIP1ZTwsunzDb4unthtjPPJ2pkfX5yE2Qi\nCPiAVNiDHULsWxZT+JiP77M/Fo/BidO8JcwBcioMzskhjqQH3IwRe6sWTWVX\nXq8IUuAeJnUpSyInELt/UAn3AvC9pFrU7gVpll8kRBJ5T3MGAMknrY6TVWQR\njFaeCi7mxhyBUjOq8g29hHY38X0I2/dpvrG/EQR9g8rrJjScFnjkfJ7ctw0B\n5A5/Fi7a7zaAL1UPK2LotcV4PNG2/kF53I+b8ZCSjuCrZbQvFZ75Uc9YQx4E\nacyIvwv34MBfJIHDqM7hp/LshDYl6N+41fH+MABukpNTF6fTHuF3phoT+Os8\nI3tvzqQjK55M9GW8TkyqIdOlTIfuorITxrcBcAdJVZVhSsYEW6IHesxOiydg\n02qUhjJdza1C6G8G/cDEO6gGtFlAysYExEmsj9MS/APm0RsaJZFTFvASwPdx\nGUAla4vPDS0X5vV6hDuP3q6eucnpbjzLGigCLQOhayq8sNZtYVuU32Smc5yh\npUZ2M+Uyiev4NJiuhPQrP94GcuBmRrwlHVWUQqC9peJp3QcE9Cz00o7GXnQp\nNzo+\r\n=wYTX\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"44e43aa7ab40220195149f2afc6c743cbf1b673a","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"flat":"^5.0.2","redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","deep-diff":"^1.0.2","pg-format":"^1.0.4","pg-promise":"^10.8.6","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.44","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.42","@balena/jellyfish-logger":"0.0.183","@balena/jellyfish-metrics":"0.0.211","@balena/jellyfish-environment":"^2.3.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.16.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.0","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.9","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.68","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.47-improve-patching-44e43aa7ab40220195149f2afc6c743cbf1b673a_1609403560521_0.40501497307171785","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.49-renovate-non-major-9962616fe3c7c6e6de07df88ad27ff9842267b4b":{"name":"@balena/jellyfish-core","version":"0.2.49-renovate-non-major-9962616fe3c7c6e6de07df88ad27ff9842267b4b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.49-renovate-non-major-9962616fe3c7c6e6de07df88ad27ff9842267b4b","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"812aee4b9f7865a82153079e2488e9cefc87777b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.49-renovate-non-major-9962616fe3c7c6e6de07df88ad27ff9842267b4b.tgz","fileCount":95,"integrity":"sha512-l61CyT42X1bWHu6aJh+I1QXjrTsCr2JitDT4qjuk0o6RLi/YdLpKGP0426/z3BP9Qx9Zwlp8vavLuDPCrvCZVQ==","signatures":[{"sig":"MEUCIQCpO93xVfnPjWvM464AIt/TIlS+U1+haDNqivmUVHTzPwIgN197u/Er1LFC+0By7iZzgB0XJR+6OKNRC27/vP3NkAs=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":577805,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf7oURCRA9TVsSAnZWagAAR9MP/33rxHAKK+UJGpp6S+0M\nWSw3BUh1I+jibs1T8BdGHbwHpBYX0QK8ZcwEg9Q+txlCXwuT+AaNkgxdc3IW\nEjLyXSN/vfMlnpXgzG7FAw93wXCvAAApsOCMfFCp8YCrH6QRqAa4ia6gVhpA\nLv2RUvobl/5+DOL1qs02RptYShD+MmwwIADjXXKbI09tfBOjgWCmlAdaX7F9\nqIb63hYNcNYyDSoZCUirWFcFl+2ketwOdzJNpcCENG/wYvjMTRllvhLHJAae\nwxGQw4chIKaNio34wGdpAamjwg7aUgw8HaKqQye20dcWJk5sghQhrrTPr/d9\nfJr1vfEolzV/PbKylnZqm08UqWFf+aqz5FVcq2MMVYOf/Nrg5BgoPVMWrX8f\nafotRYdC2RgFGA5o8zxcEVNYM30X9AoPUyzfwL7RP3/hJeI+pjI4vVRh3kbw\nJdjaF7rSQdnL59wV7Gu9rwatfAKMj5HaebIELyYo0iOyvH9AzIvQRHGJE7C+\nyrmuYVR1z0wO5NW2Eqe5gHRs31euAFNgi7JYKaFQ5do3UXupiUdmwI6x9sqn\nZc+H648P6QMPsPkyVF82fwmuB7wpFr3YMi/jVt+G1hIlyywL10BfoKgz/930\nYym7TbornpbpsmriR2Gwfe1Ues403QJf45XCz71jnaWWWK4KMClxqfFYStxk\nLwjt\r\n=Pd3o\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"9962616fe3c7c6e6de07df88ad27ff9842267b4b","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.6","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.47","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.44","@balena/jellyfish-logger":"0.0.185","@balena/jellyfish-metrics":"0.0.213","@balena/jellyfish-environment":"^2.3.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.16.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.0","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.9","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.68","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.49-renovate-non-major-9962616fe3c7c6e6de07df88ad27ff9842267b4b_1609467152650_0.40729620586586224","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.49":{"name":"@balena/jellyfish-core","version":"0.2.49","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.49","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"366f68c07e2fb874c609ddf7eac2e32931efcf6a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.49.tgz","fileCount":95,"integrity":"sha512-0aKUxElOuQTz1osFtqAl3ppY3mQms4nkYo1htT0Z3EcDY/uxk3dgDyYshaR3H21MIo1Wx/KHUVL4G9zcx6NHVw==","signatures":[{"sig":"MEQCIHiW85YvCsU+xxoBcmY9D4jLtxJvteMbKA+pup1D8UUuAiBOPt8nUDMDPeM5ctOzWJz7YD7PdWNQuq/+pDek0SUGgQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":577745,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf7oZMCRA9TVsSAnZWagAAYeIP/2qEb8Owf6Qe96gfKhig\nl7uEdanOw6fbFrjS3R2DtuY4UgYCx+ZYSAOthnye8iS6COMKmnYhRSkxanA0\nbHHjB7KKjocB690W9meblbirvooh1kkuKuc5cuS44XkmvDh02QW+wWoMdh6p\nF3YX5TpcxYtxZ2y4ADmkgDoGYf4vzIPPUSTL2v7knSbb9P938DtCyXWk3Ism\n4geGkX4KWo6/TJXc3j5U3EtC14uHBRDREMPyaTrLYcgT6z0AOSjWVOwTVbkP\nOOX3OqqNFaID07Tg/0zmcvhNhjHISq/SuK3UuNJCSbtttkhFND5SZWZPYKyy\nRs1xEo4neEzwsObTDVPpkU9Oks8I5l59mFHzzShgLXSzklaIWR7BjZTMEYP6\nIvZOEaXaVy7ZQ9vHt8HQ6GypwIKCIsTY5oNEUsTJTgfHxnKFegDEBPMtqkDk\nohLXUTNWO7LJdaiMmQ0ZwsRYuKIqh1aOV+l0Jt+E4EK0Vcew3qU/gvyEStNx\nHxvFGxkJy9pmQqnjHkm2WeT+Q02IPcgm2pQub2Fy1Ql0iV9v3MR7e2X1I8Et\nK15/Na4c1LjL8gvGtCZW61PRuQiPUW3vEGFmDpUunfmtkLu0IK02f+E0uO0Q\nrRJwE0v/GG1zRbRr2ZpJUlVnmgSVHWnMWPrAVaLNab7mS/UDIgu7MP9Gh/kH\nv8m4\r\n=AAvf\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"54428ff832080151ef0966250b80b6a46f8ff861","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.6","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.47","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.44","@balena/jellyfish-logger":"0.0.185","@balena/jellyfish-metrics":"0.0.213","@balena/jellyfish-environment":"^2.3.13"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.16.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.0","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.9","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.68","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.49_1609467468124_0.8470189053629056","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.47-improve-patching-c37c3ea420c316a61cc071ab40cb533685f5b804":{"name":"@balena/jellyfish-core","version":"0.2.47-improve-patching-c37c3ea420c316a61cc071ab40cb533685f5b804","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.47-improve-patching-c37c3ea420c316a61cc071ab40cb533685f5b804","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"7df177e593d4b131d35a202fe892119c33cc5e66","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.47-improve-patching-c37c3ea420c316a61cc071ab40cb533685f5b804.tgz","fileCount":97,"integrity":"sha512-+KXWTd3Nk/rLMp9RmMe9wzZAIG2Rub6FHANEQhTFoT77aZSqPHCNOyq/Zk/GdqAveRdrFcdZN+Co2C1Xr9Tk4Q==","signatures":[{"sig":"MEYCIQDfbG0UTZTsCcq/0Vc8iDc/Uo2L8mhOksfu1VN8Osn1VgIhALsRVlZKlCqdCYfxT5VxgvF0KJ2n2eaRoqx/DruRtKZN","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":592861,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf7pAXCRA9TVsSAnZWagAAWpgP/A307hTE+PjjlD10hTwb\nV6Nr6J3y55h8YkGyefUBKMDO1BPIhr/0fOklGKFO8ggo7VIjbxw4wDncRxPh\nq75WDCKiTtui5gcJfozEvKCVo5ZJred+JbhTCDorhMw4vFAa5bmMZv2RkDkO\ntQaEhw6UP+iLjCEbWlw5dHeXAT0HHgQgfAbvx0A/ys2HcaAsBPS0xVvaoMnB\nJoasdGqyoX8bx/sl0RxRYyM6V7jLuc1aHgo0q91TiDyNzfYJ31EOBlqh0QSF\nKDyqj2FHWvVEhQ4Hbc9dA00/WCI6RIYzQ5QZ1k+mAFI7viKl73pGbm7fJJcI\nfVg4gm/ZS59I52Ou37+f5OJGR+GFgYn8KHSH6rpVq8+vfFDIkm9wRU7YZt8n\nFrWxic3O1yHj7hRKidaW+yyjuUTxKcDtAgodvIjyfh/+Eww8YCveEeZH3g51\na6cjxmHxKuyHF4fvK3ZqkbQLmLUQnnW8dlyFlV4fZeAHltBoGPb4UDTtw711\nehAEenJh/x7EvtdLGp2fBS9ofJwFa6uyywGAJNYoAGF6X19SR3WuQ6DvxnsP\n87b/4zYxShI9MTHFaYoQHW53HMCdSXslg0eG1t5zq3EsT45eOSYMuB/qcuCO\nGH99AdNhr+fE5EHuE30EmzvrRvY+SyQcIGmzsi5QFa+Ds3hpN/x8Qp9mXenJ\nAhs/\r\n=B5KB\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c37c3ea420c316a61cc071ab40cb533685f5b804","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"flat":"^5.0.2","redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","deep-diff":"^1.0.2","pg-format":"^1.0.4","pg-promise":"^10.8.6","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.44","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.42","@balena/jellyfish-logger":"0.0.183","@balena/jellyfish-metrics":"0.0.211","@balena/jellyfish-environment":"^2.3.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.16.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.0","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.9","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.68","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.47-improve-patching-c37c3ea420c316a61cc071ab40cb533685f5b804_1609469974529_0.6689780341601008","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.47-improve-patching-85c9be91964aaf25d6bfd5f23b43bc43dc1afa14":{"name":"@balena/jellyfish-core","version":"0.2.47-improve-patching-85c9be91964aaf25d6bfd5f23b43bc43dc1afa14","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.47-improve-patching-85c9be91964aaf25d6bfd5f23b43bc43dc1afa14","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9d1aec124acf5a1e43777e3ca44bb5366b501c18","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.47-improve-patching-85c9be91964aaf25d6bfd5f23b43bc43dc1afa14.tgz","fileCount":97,"integrity":"sha512-CwmOmAc+sVJtdPaIoFlOFGV4huM15GsAZYqKNbxcLMcuHZloLZp1NYTXux0F9XOwxHCKHvAuk6MszDh2BG0A2Q==","signatures":[{"sig":"MEQCIGQJ31RU37YO9SUT1NM3NZhKuPv5exA+HtHlxd7iV1jcAiAUYFqJh4GdFQly+5n+I8HddqqmKKNPORyFDrsNmyZzUQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":593077,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf7qq+CRA9TVsSAnZWagAAU78QAJq85P5IbgN56SQxvK2m\nfqS+OFPXM+Do/dJfuyNDegU7XRQn7Y7SZdh2kpcF7ou0JJZ9HNBLSuOX8yAs\n8EJaSR+awQNyLKwRL0GQ4TUFs6non8mkVmU3pF5gixfGbsdODiAYoKPnHbK1\ndA4a3Klv05VBNq83eXG8Bc/CST69rL5DIun+XBEELwb0JGFPiTMK3z3EizLQ\n+NGWOmzZ9RdR9UQbqZSxqZxZcSUDCWWNKpIFEjvk83qj7p+h8Hzw5HAdmL0m\nI02ZW1f+MVE7HoFNhYNF6q8XdStz1C7OI3TPi2sMeXCxCRE5iSHw3Uv0BY6x\n15HlKieueVilfK81dUTqu2yzmdZ1GRzJO0gC638SGKGeorODaFBSoFAzdz/3\nIyDL9npoaJpx+RCclmFRkzZYDWv7eob+/uJJqOm+gf0Zb0jpnsAoKKLCZfWV\nCoH1nXjLrp6/iHXwLm+clbWSQT/Y9R1Mj0rA37HlfNu+wTGajf22grrccabA\nzpmAkuMSf5aIkXiGoeXTzwkHBNP19lD8/5OMEHSN+Pf4jWh6LGqst9hWVcQS\nLQq8K0JY2v5CNJkMo16rf6iPy4UXVXnYN9zLR3QUya8HxYl4DZzfUbeWsxb3\nz/7j4W6Mat0GrbLw+wjq0N53Y/BvwAnrKrub+pacm/AjA/jH+YNLvmWKWgr+\njB2N\r\n=elOC\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"85c9be91964aaf25d6bfd5f23b43bc43dc1afa14","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"flat":"^5.0.2","redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","deep-diff":"^1.0.2","pg-format":"^1.0.4","pg-promise":"^10.8.6","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.44","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.42","@balena/jellyfish-logger":"0.0.183","@balena/jellyfish-metrics":"0.0.211","@balena/jellyfish-environment":"^2.3.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.16.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.0","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.9","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.68","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.47-improve-patching-85c9be91964aaf25d6bfd5f23b43bc43dc1afa14_1609476797731_0.6180263720746808","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.47-improve-patching-0ff8046466bedcade01fafa70ae41eecee6377a8":{"name":"@balena/jellyfish-core","version":"0.2.47-improve-patching-0ff8046466bedcade01fafa70ae41eecee6377a8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.47-improve-patching-0ff8046466bedcade01fafa70ae41eecee6377a8","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ce838361b43550270c02e82807d677d930aba02e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.47-improve-patching-0ff8046466bedcade01fafa70ae41eecee6377a8.tgz","fileCount":97,"integrity":"sha512-F5ZY69aHJHpi462f2lg0B058FdhZTxdr84QafH5JUTFfnz6dmKsOtkbob0F+fO17izXbI/tXosJQNx3BLuCFuA==","signatures":[{"sig":"MEUCID0QUlEKiTs+sLvQqrqmwHTcNG8fYA5Q5Aqb1kLC7FyWAiEArG4QyE1KERjEOjtUgriU2EJbMBA94yhQg3XvtmWSmD8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":593101,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf7rEyCRA9TVsSAnZWagAA1D0P/0YwSeOcHHjbaqrpYpYw\nTjo5Md/XbHDNin+xENas0Bkhe2wDLUGDqSGpVIfe1oM4j0rSPfQ9SMvUDttN\nmbOLPSH9WeyNfVSGcEb9f2E0mzfBY7yhsPK2ftZIxAVzspIip0Wuqx9e7pzD\nz27V4Hdpx9J+HjQDBc0NHWyVOU8CVMvpeUFwla6nr/y8KvXolWAcIfsVA6aE\nCS/C8SQGipNX0uxZtdRYB91PSQfY/yuVQe9gYHc0pQ+wVrCCz6G7YfLYsoKK\n20okRoQVmJFBZcPUGMQQsSX1qsHBrehZunu9WmFstMrc6Z+a4gMjmu61/TLX\nxT19M9if0JIkgnM2mRXlwpSK0CSC6L7ueW1YLfX+B8niItRqL5um06T19aB2\nbyDBP+uMHaqy4CMjGr3x7/AxMpN1zhAbeGEhLxN52GVLuYF9TOuCY94QIklF\nhc/T4ez7s31sMseG4U28ls9OkdPDKKEprTs02IgsTq571re0wJFc2uhX01at\ns10kveCJCZ+VOHCZsuecOu4hMvja6b2LF7VgJrbwx3IG67MBj7Ne9Y9LY27B\nPhgEqS5YHZAuxoCuR0VptxNJ5RWCY0ynEaFSn/Tm2EeOSMDPptrvQs4Bt1mT\nZn82CtFno2tFUVHK3dwgoJYc+wU28ltezBBzQl7qbwugPEFYEuPHsfUWFKSz\nqAUw\r\n=MGM0\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0ff8046466bedcade01fafa70ae41eecee6377a8","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"flat":"^5.0.2","redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","deep-diff":"^1.0.2","pg-format":"^1.0.4","pg-promise":"^10.8.6","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.44","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.42","@balena/jellyfish-logger":"0.0.183","@balena/jellyfish-metrics":"0.0.211","@balena/jellyfish-environment":"^2.3.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.16.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.0","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.9","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.68","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.47-improve-patching-0ff8046466bedcade01fafa70ae41eecee6377a8_1609478450289_0.1281409239983009","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.47-improve-patching-8ba8c530aca08e77e962e904e0c20ba019fd4967":{"name":"@balena/jellyfish-core","version":"0.2.47-improve-patching-8ba8c530aca08e77e962e904e0c20ba019fd4967","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.47-improve-patching-8ba8c530aca08e77e962e904e0c20ba019fd4967","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"746c33b25a47f49285124f43883069534db2cb77","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.47-improve-patching-8ba8c530aca08e77e962e904e0c20ba019fd4967.tgz","fileCount":97,"integrity":"sha512-xtWxXS0RKCY3BRdjY7yctDB5THa2WWKcqnCpPrNdq1G8uxLIPYPyyeHnRp4WiOIWOaleeCPrmEGG0pFEac+GXg==","signatures":[{"sig":"MEYCIQD/PWkHqfaKYiw0gia05hTazniqzNoKN3GLUT/ShTCEvwIhAPdD3/sbnOl6bTmL/xAEgtf5fI3h1luvpcOs+stFRE9q","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":593260,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf7sWjCRA9TVsSAnZWagAApM0P/jx/ZfnC/SWyOoX835Dy\nRnQxd43s+2+POAsVFJ9dhf/Nvkih0cmI80bmVscoKojurqrz8cHpakVWEJL/\nmMBpjwtKdmxqC0Tb0k9S170dAAbONnk4ya8VMikTfp8ZZehHZ6rrHLfnK7bI\nhUAzsyzeF4ApAs8tzr6wyLFV0BJLexNO+4jkT3UgSuXLtOuJOCPVWtNcNH7u\n50JlSpPnKfGmiR/xEQ9PFwQK9loKw8ss7hWGN8NNeIriFldtPuf2OPxZIVn4\nWzPdwzvbs4PNwtyNGU7R0KII23502HvZl3f3PmP8APsxDAp2Hf9JjsmHiLeu\n5XaVL/H4HFgZReO0NndBx50Ca/E2/UaM2FeBGYaBQ19VYS9NQkRM5W8cQhkp\nYjdscNDkdxFW6f9Ur7aJ9kQAVR/Bwmt4omotNxQo8pMSdoozhq/UJDDC+Yg+\nB7ayNIsEZzJZgjFED0JdDlfEDrwWMUAGBShZo8O/VafY++7NRg1dBxywVUkF\nT6loSpOWWspFETC8ShFk9L7+me/kI8QhlnYi4UhTLVFfrlExalUFmdgP412y\nEa7LuPyhXf53l19Dn30sgZRBNvYLBciZC/KGptKYP6VAfvYQ+ktMfd20uLk1\ngv0FKHRn+SAXwwAP1m5UNpB41ZUynBOGuk26eCx57q0ZLUeiDjJtFQOIeiBJ\nYioP\r\n=nSEb\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8ba8c530aca08e77e962e904e0c20ba019fd4967","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"flat":"^5.0.2","redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","deep-diff":"^1.0.2","pg-format":"^1.0.4","pg-promise":"^10.8.6","redis-mock":"^0.56.3","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.44","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.42","@balena/jellyfish-logger":"0.0.183","@balena/jellyfish-metrics":"0.0.211","@balena/jellyfish-environment":"^2.3.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.16.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.0","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.9","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.68","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.47-improve-patching-8ba8c530aca08e77e962e904e0c20ba019fd4967_1609483683012_0.647518031381191","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.3.0-improve-patching-a371e1081a21c53f231d263f16fcf7ba8ed489e0":{"name":"@balena/jellyfish-core","version":"0.3.0-improve-patching-a371e1081a21c53f231d263f16fcf7ba8ed489e0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.3.0-improve-patching-a371e1081a21c53f231d263f16fcf7ba8ed489e0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ac0ba9199d1ab18ae1fdce5936a9f897e81f6669","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.3.0-improve-patching-a371e1081a21c53f231d263f16fcf7ba8ed489e0.tgz","fileCount":97,"integrity":"sha512-HeWTwa6DIiVWWavbJCEc2Klftv7ym/Jd9gT70IPGgUqBIPOB4zs9o3mKhWUVMBLU1WKeQhpWn5mhzy72T7DMQg==","signatures":[{"sig":"MEYCIQDNxIIuMqO7KJevg8xUNXlu53X+gTfdkbyocsa8KnLnPgIhAMZRGg/4DV1x+gkrR07XqvwWhMV4H5Q6xCISeouazfMh","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":593055,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf7sb4CRA9TVsSAnZWagAADrcP/1AYUT1cNiu82+wH+cZ0\n9KaR4vXy9W7blGVtspRiXMK16VCE5BlfdckK09dEascC1hgRXhFvsls3DRab\neFmCC5VyQrf7CeDkslmLmGtPLxupHOrvtDbhItqrZ3LkzBPdPxDy6nwnr1e1\nJCbfXqH4jNVzsQSDMiDVFU6dBppHTS0EkjwhVVyiNkRwLdXVjLlMRFBw2raM\nUfFp4epKmZDqZPPZg8NNIwunvYhiInw/0F4wSAJGpyQZtgQt1xw7i6Pbsi/o\n1bmyLPBq9pQTGep03uzmBWXEqUToXLH5yyuLM6aBvjx7VgxhteCx8yL05gG9\neQOXn/JzuhAyAt+iyGOXQdFsjxakna/xhRyLw5Bw9neGa43dpX0p9uOHcFd3\nQ70/WSa1xUQ9LXWsG5aENe57pmV5cV042DS9fT32JCBk1sS8uLuIpBiDV0u6\nqL8FrZ1tD7pCYqRIccdNBGyuLklpVmGsnWDmLU8clTLh1rMzqsfUdGaMxZIZ\nR7dEiQ7JSbzRJ2m74UGvMQAcDbR54mx1BJiFB2xy2f3PD/5YX4LDM/iOLsaM\nGgYEKhIskuZ6V6WlZb32KajrhV5s8TyL6p4tMpbkLKCogvwinNNCXaCADZ58\n2efWxwtOJwQk+623VUZgmvBcazQyt90oRUQn3K7TF9rurz2qEgfBr0Nz4lPH\n2/TS\r\n=+QaD\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a371e1081a21c53f231d263f16fcf7ba8ed489e0","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"flat":"^5.0.2","redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","deep-diff":"^1.0.2","pg-format":"^1.0.4","pg-promise":"^10.8.6","redis-mock":"^0.56.3","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.47","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.44","@balena/jellyfish-logger":"0.0.185","@balena/jellyfish-metrics":"0.0.213","@balena/jellyfish-environment":"^2.3.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.16.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.0","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.9","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.68","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.3.0-improve-patching-a371e1081a21c53f231d263f16fcf7ba8ed489e0_1609484023705_0.05866543242541589","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.3.0-improve-patching-535e941615c2d2d41de94534950e6980083ff97f":{"name":"@balena/jellyfish-core","version":"0.3.0-improve-patching-535e941615c2d2d41de94534950e6980083ff97f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.3.0-improve-patching-535e941615c2d2d41de94534950e6980083ff97f","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"73e24b49ddfbd537576f514b9e46c051fdcde373","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.3.0-improve-patching-535e941615c2d2d41de94534950e6980083ff97f.tgz","fileCount":97,"integrity":"sha512-7ktM9wdb0pxUDNU0fXJZ7tGiShfmp3Dul6YEGbbAvqPworxDpZEKy3RYMfsTkmLRqvHAEIixXuMJtVExagR+yw==","signatures":[{"sig":"MEYCIQD8kgB3XL5tcKFNhYm6NVMwWTpc9BkDopK16KaRW5Q7YgIhAO591jTpS0rsASXw332Ewa8ctijfkQfdm9cd4xBv43Gy","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":593046,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf7tVPCRA9TVsSAnZWagAAnnAP/3iUqXI81dwIig6C4teL\nOy70toeTLEz+SqtwNgINgfS0tW3p69JiTsz4WGvzfpw55DW7HTkCNUifoi+G\nd70aNVB5ZfLCEgnTfcMP0RBomzMAh0hzOlrnC+SJ52BRGcG6W/pND0JzBqhk\noH2oC9bndgTyFkA3/jHmblVsSemdXy7G53VOjdZ5eOnSWcUfe/OqXR4+kJJ1\nxIaiu97mKkUaGz3lcl3GNVDifI//5yv/Kxm52n5DdLg5wKZYvMGXgtJRXaR5\nQ7xm49ZQe1rCn4+3h5B+vfHGmpccfU73HtUv5kK4DsTqrZlCl2aPu3lPV1rc\nA8qDBQ6Lr+/v0y9e42amjkhpzlt47kmNmV/EZ0hRb2Ott8qKdsE/p8mtmw62\nRDvUpSVRFKiJwJjR8tDSU83/GYP2quEkSRZ2ynUWoxDCRdbV4Mbz4Gx2R8cH\nT3OCV7AyjK2AXOo+MqtyfMkj4GkstfGPzhWdMHITLh7t2YmF7WlJKz76ef+e\n5RveICs5EKXbX0/zYw6p222k7pMouZoYFG4ZZLNegQgNIBndBMFp3QXxpRtx\nc0vDlB5Vs6NPcUrZyq97EG/0LZoEtGOMyHIqhSuZYI8Uq8GaGv5Xd541qeAt\nHY0jF3sfDV30IqhJyZMoAUugtx2DEFC6SNb8IfYvSv8SmbBVI/V4jwOrE6wv\nDt0w\r\n=kwHW\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"535e941615c2d2d41de94534950e6980083ff97f","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"flat":"^5.0.2","redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","deep-diff":"^1.0.2","pg-format":"^1.0.4","pg-promise":"^10.8.6","redis-mock":"^0.56.3","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.47","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.44","@balena/jellyfish-logger":"0.0.185","@balena/jellyfish-metrics":"0.0.213","@balena/jellyfish-environment":"^2.3.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.16.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.0","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.9","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.68","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.3.0-improve-patching-535e941615c2d2d41de94534950e6980083ff97f_1609487695064_0.6504822750781623","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.50-renovate-non-major-69100df31343a97344367c7e7e401e54f64f754f":{"name":"@balena/jellyfish-core","version":"0.2.50-renovate-non-major-69100df31343a97344367c7e7e401e54f64f754f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.50-renovate-non-major-69100df31343a97344367c7e7e401e54f64f754f","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"31bc1e100613a117529adb82cafd5f331429ba42","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.50-renovate-non-major-69100df31343a97344367c7e7e401e54f64f754f.tgz","fileCount":95,"integrity":"sha512-uN4p+XTf0IsWyCBmWqyyKRBsBtN45QAFGPfN44GvVNe8SwZLUekgahpwAjU7Fg/ZZVIRWJuGP680nWufLeDKZw==","signatures":[{"sig":"MEYCIQCacQcs8rG/2+Hf+pC9dm27J74q/Db/XmPUmnBocoiH6QIhANOIiMgui4K77tKP3zC7zRFmFDYlezuM78UJjIIce/qw","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":577906,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf7uKuCRA9TVsSAnZWagAAFYMP/0OzSHWsaxF8OVRAxCj0\nXZLVatJWnCUA9I5dDmV1wFhIPdNoFEGpkRv8/NLui6aYsNgYR9/ADrP041Yu\nK9rHdI5Oh+8qfSSt9rSTgP8/xi0u533+lqTRtpC3B2KZ6uEdcj/BnSfB6BF/\nmavgacJERfX40SFt3USRl5k33Bk19cTt38x2BRTFAkrHtgaRm3xYh+bUjcMQ\nigyNt7f/sfS/YSu2j5BhEBVC1G09eRn23wMeB9VLMVTrHUTPWshrZSoSanAY\nJGD9LSwyzCcyZAa9iJ4/ExhCGsMpoknBGtwrmIbEZmI/DccLgpZz1JgpR60w\nobTZbEl8oEiP5Sk/8MUv24R1jDStQYjQzc9gPis1yECaA0FtWVapIeJsvsTG\nOxG49FIFLbCPgIxd9qVFL/x7Gx6RdkN25XKkUWoVeWma+8WjO4MX+lubhPqY\npNhFDTHJ5m8cDY0KydK0FT7BFaXB7BHWQOAkgjYkgVZmF8tkArUQCrxTyPrU\nBdl/MhDJpke56wtOFX4DlryBHm63RsEEJf1pwbsi4y0/sfU+RwI3RvmWSlmO\nzQQXI9dAFPL62hDUCgERDbVa/8xpmWqjeAB6oE2WMJw5nWOEKzqEhrqx9R6m\nu8q9PbQkaC/+0XrxQbdzLZUaGdrArDEv6s9j9PloBky1Pm+tFllouNOtXhCE\nR99R\r\n=0Mb2\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"69100df31343a97344367c7e7e401e54f64f754f","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.6","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.48","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.44","@balena/jellyfish-logger":"0.0.185","@balena/jellyfish-metrics":"0.0.213","@balena/jellyfish-environment":"^2.3.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.16.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.0","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.9","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.68","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.50-renovate-non-major-69100df31343a97344367c7e7e401e54f64f754f_1609491117512_0.28634201770075496","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.50":{"name":"@balena/jellyfish-core","version":"0.2.50","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.50","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"de1a317f018e3e23ea7626708902162cb2deba39","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.50.tgz","fileCount":95,"integrity":"sha512-YXm4AmTCf9Tv2LS+h3leKRgiV2Tdw9kM0XZNm1k7ggUwuC0sltn3J6zSARf+HarhK1ZTx3MZmaXdGXqNa1W2rw==","signatures":[{"sig":"MEUCIQCyr0pC2ikgc0CuR+dCjybPxpC+H+jyl1n2Ap5j9EyKKAIgL9hg04G1SRSyaCPcBo5GnAK8uCn6ZTvrC0h9qrZLGo4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":577846,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf7uTaCRA9TVsSAnZWagAAvocQAI0vn1Tbl91kmCc4bubt\nPSBF52qQMLAVso9JGNOMPVOgGVmucmyJT7w+X6KX72XQI0eT6SAr/ewYnH6h\n7Cm1dvYAQQXMZIX+xXhl+GShAjF9LVviQJEzhujMUoPiO5hmzbcDOUnXhi5U\n6udGSBEvw9c7RkZh1KMxTrB4zQFJrPHRrMeXsV+ciI/btAtq1T6KLks9QBka\nlS3urgZbAJnNMvSXhcMFHP6AlVahuEyCD3X6Gezyzs3wNjX1BdAmzvwHfYu/\nvLndS4Txw2W237uYlKkpQHKP/6C3qT36wVeglTUYqYIvtLuD6BcI25hLzQN7\nb52kc7Bn5aUezKI6uSv0vNkmz1YUMBInhl/vYsFRSKINHmPpY494dbR/9rtW\niAO7n1+kjddUFL6Fn9j9gATvKHbuHzsfXRYCARsnZn3TeMPEFlF8DTNpio8D\nwiPYU3hFDKdwe2ZOl0xSvmBKKaGJNAzo5obG8YFbTbOr52u4j2QLeTpLjduT\nXyrDWq8kQSRoOdpa9v7rU0WoqxV5DtrpfVp3sepr8zISqkhe4RI202+9Zveh\nZsHzIhGApS2UH4XAGSLuDNRH93RgmN8LTZUZqJgYDV6GWlZmnZZvi5EungU0\nkHceQIDqspVkfH+VtOiFAW7idqZqyFuOfv/99pwDz8zLCqtIN+XzmSlLzyK4\npFvO\r\n=rRy4\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"07e5e84dcc096aff1013c3e0ee80975c0ecb3cc7","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.6","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.48","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.44","@balena/jellyfish-logger":"0.0.185","@balena/jellyfish-metrics":"0.0.213","@balena/jellyfish-environment":"^2.3.13"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.14.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.16.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.0","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.9","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.68","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.50_1609491673670_0.203284991997301","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.51-renovate-non-major-345e66254bb9ee3f0e1ccfc582db557a2a8a9aed":{"name":"@balena/jellyfish-core","version":"0.2.51-renovate-non-major-345e66254bb9ee3f0e1ccfc582db557a2a8a9aed","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.51-renovate-non-major-345e66254bb9ee3f0e1ccfc582db557a2a8a9aed","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c93e28db0548229f5bd3285d4a747ffcd392620e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.51-renovate-non-major-345e66254bb9ee3f0e1ccfc582db557a2a8a9aed.tgz","fileCount":95,"integrity":"sha512-1nznuTCmahy5Mm5xHcfC/A1AS2AAOfaWdX4+uSNdCaCLIwnwvO8oZQG8kpMYEaNidSNG/qxdB8CtLid5Eti83Q==","signatures":[{"sig":"MEUCIAoHW8QR+22k5dfOJedZkj+dWYViZeGqbL9fvY/nI+UZAiEAwpRvDKwgWVHW6SyWuVGFvAPtRSuRPrkid77AMexaQyQ=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":577972,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf71MuCRA9TVsSAnZWagAAsFcQAI1AaqmaijthizwUpZpE\nURrINqtH2nGvbTfTWyPjIXaqLSmysdGdlzjafcZjppCDgvXF3Wwu0zhBmYbL\n71n+1e1+TO6rUhKSXQziPpPPV1uIUdxFZmbM5WOnsPkeMEa0gXXvf8NbzCGG\ntz/D5JO+qWQbOrnFBCffQVdai7p/Mu8oY3ZPrTM7HvbR6B2YoCWJWA1C670o\n20kksFH1M6pwQtuCslHl2O7ZZC/bWvXsWWACkkaukS9BmDF36Xvy8UNdGyiq\njQ8VA4Gjzaci5/WryhWOseIsrY/zq3PfQ574UuggWMnqncRXsEMbFY647Q89\nemwu+zzcHB/AxG99+hCGxmtQuHWfAORSZrISufPelkEXCzEz4UeEVta0ZOau\nnKzK11kZNSDr+rc5RlU9O/NldwtILTjbapxauZS3Lenifg5kl7Cp0j6Lg/+Z\nK58Wv+CWaVnm+HXWXAyx6vJBFflgJ0ezTQeqXnfKXWLYzO2cjC69Adxx3+0d\nkOR6YLDr5W3XOpntThHNpvj/gGVrou7jAn7zxCctUpjYfUZOkYTio+InWdWi\n+FarIZgTTenpjOzBDbzTzO2+iIRXg8bm05+6mdcYI/vaTVSTPYLabT0me4Ba\nAHUKmT0EqoIyS8MImcODQybIlA7TbPb5qGUuCtWPAkMcEgLWZIoYl2X8WNV1\ng8gh\r\n=lrgG\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"345e66254bb9ee3f0e1ccfc582db557a2a8a9aed","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.6","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.48","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.44","@balena/jellyfish-logger":"0.0.185","@balena/jellyfish-metrics":"0.0.213","@balena/jellyfish-environment":"^2.3.14"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.16.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.0","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.9","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.68","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.51-renovate-non-major-345e66254bb9ee3f0e1ccfc582db557a2a8a9aed_1609519918442_0.6712932096317394","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.51":{"name":"@balena/jellyfish-core","version":"0.2.51","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.51","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c82ba58ea728c65db4bf7bbda64cf80ec83b1df2","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.51.tgz","fileCount":95,"integrity":"sha512-GC8hp/atvxJfn1wuQETLvbPMKCwwe4c5WeTJkQS1RPxQTv6+3ylMEuC8bJtIFbCtLJLuWbcI4EXS2jqVEI2HcQ==","signatures":[{"sig":"MEUCIGfzpYSEOD8EOizm2mNYaqPXTP9LaxCp2ref88kRkupxAiEA3MNHNheaK9yNa5k0SbP/fFGb0jE3CPh8dpagklMmh8Y=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":577912,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf71UgCRA9TVsSAnZWagAA1ZUQAJAR8GhM3jvfB9OKGmBN\nWPeTBoKlqIjaSzly1tHqgbWc+fl7bcebmam/iu3iji48GsBuHF6OvvBb0zeT\naQJo4TUM2aeup9WcmC6CH8nVIS3pl/A1rweQmDAI9Dnn+mmKf2CDuhLPGikv\nNBp18pm1sbJ0RSxpTIqIMW6j2FOX3zACDU+Hyu/o/GN2a3QMMVTabRuoSJvg\nlKa2UPljITjhu/nd28KMhb9/2Uf9uepUig2o0ZnhW16vLMCDMQrv+neTfZ3k\nJ5uSq0Uw8qWWtMLY+7rv+iUtDEJFvspv6PR39fR13CG/JbUdFRDnNZqMfJZF\n13SBbFr1JZtQR0ugVo3raqRYxYi9XUouUo2IUC/NoJlzAewwYRKYfZ+neykl\noINQlwQUCnMkncZLxdRaGZ1+f99ieoDbyWMMNwHCn92z9dXl3l/2lPRRV8E2\nX7E/MxVbJ/4dyhu0bU89LYbs9mAySoQmLCVv5zF1a0NSFmou7Rhocw0ijzd/\nJizF8nT7wWcHrZ5PGcMr8w3pVvXfDomQUE4JxFAlCk6AD2RrSbcyjoUzL36V\nuAHkln/EqcZk7D36Ux655YcUAfSH4vMFxLfmQRQ0SZOJFF+WoqVXISSxrjR4\ngQJOpbOL8TLlk/Vwk8lWg1Wkg11UKC8D5aWLH1cGjgBEdP1wAtpV05fZ/Wyp\nzR9a\r\n=D7lT\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"755d3e183c703346daa501f29d8b1c09385a0bde","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.6","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.48","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.44","@balena/jellyfish-logger":"0.0.185","@balena/jellyfish-metrics":"0.0.213","@balena/jellyfish-environment":"^2.3.14"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.16.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.0","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.9","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.68","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.51_1609520416166_0.4367495131889052","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.52-renovate-non-major-937f7df7bcf26a600feb95b6b352685ba26325da":{"name":"@balena/jellyfish-core","version":"0.2.52-renovate-non-major-937f7df7bcf26a600feb95b6b352685ba26325da","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.52-renovate-non-major-937f7df7bcf26a600feb95b6b352685ba26325da","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"10dd1b651519e191c7a0a0254bf358d4d3742416","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.52-renovate-non-major-937f7df7bcf26a600feb95b6b352685ba26325da.tgz","fileCount":95,"integrity":"sha512-PqlLYhx1jEeY2792/A8KroxgNeySrCiJJ7mTXg+6rEP/F7tnQoizBOrz/DB+j2xqd6GEAoU80VXrCPxvDUcHiQ==","signatures":[{"sig":"MEQCIEcwHiU7DRPNirPzbot2bPZ3J5LDOUe3y+VaLIboXrWrAiAh8E6XGXYbMNx8DlzRURaxtIsRyx9KqbHMLGYmmeu5Cg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":578076,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf71ZhCRA9TVsSAnZWagAAlC4P/2t2b/8W1cMPqcT0DZkF\nDxIwY733xHWNEcuM1oIAbOX3cce9A+QX73DoEQG9ogiI0eTqixRmxIPSKKig\nqDebUJ0tBb/vCv1oQRpf7mhvaYSKa82gTVmaQfS8m1HoN4lMWtoelyvr734j\nj7fQ2OJKu5wnNCuT/dCeZ2PwnGUfeu428zP4q+++0J3bTTOoxxIaDPIcGIpl\nAnNIvPwzZs7p9gzJC0HWXmXWkKERSe5UpcziAqT3GFoQIaHOGNg6Y+/aSiHu\nZ+Oq6ywnKLFpsAJTZGy3AGj8PFxH7walWlBe4U/PgUZYmVbJrUSlDNB2poqe\nH5x+6E2Dv/PoMV02w9J10+0dYrFMPJ6eI1pezBDqZhtCSVAZL0XtL9JqcUOL\n4gUoOYEfLgwDM3enGLO0pdk/cL0HqcgckYAkXBywjaW8cxiiZ48Ts1gFbX+0\nnDZkpLfuMgBoZCrw9srxAh98pXzqc8uxkWmNX4vdSKjXYLXT71QgRMujeV5g\nj9HbT+Td8UYaF3wJ4KHUGDbjBBbKevvfexz9lzYf4YnM+peSRQSu2R0rDjCa\neL0iIODMpnvfbQieRHdYQgHi0fOiE2HBYaAHSjvbaiDY5QODm7/vjqKjv/VW\nXyXZCMEzfQibdjv4mYpj53oNFcgdoRA98yh0yBrjXhHaIf3zfTlZ+M7miZXc\nbbQo\r\n=f3UR\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"937f7df7bcf26a600feb95b6b352685ba26325da","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.6","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.48","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.44","@balena/jellyfish-logger":"0.0.186","@balena/jellyfish-metrics":"0.0.213","@balena/jellyfish-environment":"^2.3.14"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.16.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.0","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.9","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.68","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.52-renovate-non-major-937f7df7bcf26a600feb95b6b352685ba26325da_1609520736898_0.9228350794906173","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.52-renovate-non-major-066d7746bb46c5a2f001f306928b58578c2f075b":{"name":"@balena/jellyfish-core","version":"0.2.52-renovate-non-major-066d7746bb46c5a2f001f306928b58578c2f075b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.52-renovate-non-major-066d7746bb46c5a2f001f306928b58578c2f075b","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c360980da2d5feea46591271da4b85b88874338a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.52-renovate-non-major-066d7746bb46c5a2f001f306928b58578c2f075b.tgz","fileCount":95,"integrity":"sha512-v02BNljybKg10aLoDWMEk7F9AGQ646ebYdPlDrQx/AkUOetaFPEj5H3BPzSBeGGxRCnB+9PJN0xzE+PhUEsd4w==","signatures":[{"sig":"MEUCIDQhQvwsmhEb3hQkmFW1wrqCXrwB4+zq/+Bi6JFw2S5FAiEAj4enr3Cn1jfqNkSiTEL60BLjBnmdd1xpeOi+yioeQF0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":578039,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf8vmNCRA9TVsSAnZWagAAEpgP/Rbx1c59XmfYs5fuTXz5\nVNtPEzPQ2X38N/1G/197VhtjOn/MeRwRmU5UpZ8wzwJ+P08uMAM396hvZ+V3\nEizPS2IIOg9TfWueX8R7NXUvWVrIdBGtLP7JoQTsEigAvdi2BZowPx9Ddcea\nGCyVjpdGW5TgFw40QTtcUq2zRxKDJp2mPEtx7gjnARQWCFnVgZnN1JCQCN6N\ntT2D0hFLuXh3ybdlAyBq75OQrcPd3dH09s5R6htLPFziBfFTorfPvNl1KG1H\npbD9xndh080FJjFonG/4VT/eElDKv5y6E1xqYJy+h33hTYeDfvtwHIOjBo5Z\nQPgyYP9iO2uqnmpRg2tQSZcajMvZsaeLL+zKiru/vSjR6G3RgNH/qLo+ZTow\nDiq5aaCPa8iRf92pyCuOm05dS8f+/iN64n4TzvKppSRxlNhsnAcRZkpdoQQL\nN8ZEnHutExFOE2nmAOmQ0XWJF2Xf8+XCZjXfpSq5uJ9K37mDf76RL7Hn/Vv+\nTYX7bp8HYHavBrQJXjRkO5wmm7UydD1EaTmIVGcpkqO/Ta0wDVcO08Ha6XLv\nch+w13bt5gz+wfhgyG2CRyz92cUOgtk/nEiTitbsKbHzTQs6pZ5sCLEQOQci\nL0dO6sQ9tKc9t1Y7V3dijA1GHNqLgSs6FzJeo6k6drhImUpchyU+S8IdkUR5\nsDK7\r\n=VhyS\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"066d7746bb46c5a2f001f306928b58578c2f075b","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.6","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.48","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.44","@balena/jellyfish-logger":"0.0.186","@balena/jellyfish-metrics":"0.0.214","@balena/jellyfish-environment":"^2.3.15"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.0","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.13","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.69","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.52-renovate-non-major-066d7746bb46c5a2f001f306928b58578c2f075b_1609759116991_0.7942031371916276","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.52":{"name":"@balena/jellyfish-core","version":"0.2.52","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.52","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"62f2ccdeaba507227b510316a8b328cc58198af1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.52.tgz","fileCount":95,"integrity":"sha512-RSKeG7P313zQEg6ikvz30ScEnVpVLH4rCK9Zf0Q/sY334vGJn4KdXH2O19FJULy1+DhRMeHEn3SVEV8ouktefA==","signatures":[{"sig":"MEQCIFHkk8d+tk0fEI3vKdYJuNZpF0NTDEavLNMVtBECjJdSAiBJNexqPH7WinD0d9BJRkEJcWenR4TE6kQLNpmgrcm1cw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":577979,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf8vsVCRA9TVsSAnZWagAAw9sQAJMggG9bmxA1sww1wbFI\nz3MCXK/gEVLaO6w9k4nDtIaztq3hPx6Z8tsvQIP7uxuqZEH3ajS1iXCAgMB+\n4HjUgceQuTwoxqv5hlE9YXTkuHUeC+6mVggdjF7z/h9DyV85PTQjoTTEu3H3\nkKGnYEEvvbNeSkVdcZat3ldaOpuqCAwAc0Z++rVQ5KU2b9885QqRAJ/7JYeD\nks1cD+lmFDn8JikkILtABCtMvmtm5OkPT1GkteIOuOINOc2iADcFhSsOp+h+\nGedccMEZfASaXrJ3fdd+uRRQ7Ezi5hVVPM2kketrLCbKqdTcLEddIPmKdmd1\njVRrcLdOC2gB7x2+wFC5GUlmWyXOYnpjWD4y9F8bGKkrAsUBNu9Jmol5wZTh\n3N7M/qVWdOoBWhR/gnZ/8h5tTShXh521QWUaTeuBbazahoxX1HcJ/ToOJb7r\nyyaKGL41ClhjAtUBzKRoJSqhqHZ+bg+nCUaMGBM/dESAAIciO/JXSg2/VlyI\newukXrbJq5wMqsx8ZiUiiRGRpCVfTAJN/Tb0vy2uvBX81uUpAMgVbOmurGmn\nTNz24XdYv52S/yLN0MwFmtT7tEqWRcbrGc43J0kG8arWuZwO+PcV9d7wIeaC\nL7pPi54OLIFySKkDGuOlGxeF03MwaPJuAKq4SUCimbEaaYJOTrY300sCzYEx\nwWKI\r\n=+PNJ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"3de81112718165add6ff6b00059d5c53815099b5","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.6","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.48","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.44","@balena/jellyfish-logger":"0.0.186","@balena/jellyfish-metrics":"0.0.214","@balena/jellyfish-environment":"^2.3.15"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.0","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.13","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.69","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.52_1609759501636_0.1433055125695566","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.53-renovate-non-major-cc0729ad6d5653c7aba2506a160b1af0c7e6817a":{"name":"@balena/jellyfish-core","version":"0.2.53-renovate-non-major-cc0729ad6d5653c7aba2506a160b1af0c7e6817a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.53-renovate-non-major-cc0729ad6d5653c7aba2506a160b1af0c7e6817a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b0168583b7ef7add87cb4eb9d5ad91f11e5f0f16","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.53-renovate-non-major-cc0729ad6d5653c7aba2506a160b1af0c7e6817a.tgz","fileCount":95,"integrity":"sha512-m42dFEJcRUBY4244Wnlh8ftpjtz3GRS8Txphg2hUWR43AJfLo5Tgp9OiMm69U12M2qUcWM5mTMl7BcpUJNwWiw==","signatures":[{"sig":"MEUCIQCs+JnRR3ljUJ+T1TUtB7CWjOj99x0Rwq8KGhEj6fcqSwIgK5ESKF/R/Tu6UJSTsNRP76NHiDkEsozWwjGofr4wklc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":578105,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf8xGVCRA9TVsSAnZWagAAcv4P/RAcuPYN7jhRmdHyrVCk\nk8YtbKgdTYZTklWZkhqiuEm5Qw1Uos+wqyPBK/Ljn3NlhoeH5KO3MtndALQ7\nTFIj67l2grXas1CAJd/3elTwMI1bq9wv2yAUqmheANAyPD4kKNhsrQaWPFQI\n03bTSxj34pX4chJ6MUZRctS8nq+G9NxV0tPmrAqTBDU/aDZMEAq90kbnw5Tx\nsAmk90SbU+l63ZnFnQhiasW4qjXFjsVjigRO+bTr70zQx/n5TOxZ3zgkUyVZ\nIZExBP8XFERiiT9rYBfGsyuT5PVTX5RAvd/DLJQsySd9YPLq1ULhNGUBH3Vd\nS1x8lXpZDdIYtdkLWHxGv6j750aZYFiogd8XagmArQuwMGpy7iwM3HvGS2XG\nSauRlwq0xVKqyNIjpkCCmnsBc1kkAWsuhCgE9myNPL3ecKVrffSk7Dnfw2zf\n6OekZfZpWinfci5uavj03c7TAUsErBM46wS1GFSQgTL3KyMME4bD2v/gHLzm\nDL3EIYlz3Nm+qc12IOhNLM9lrXfhVmLJZDNZGXEdwUZZ4z9qmWD2BGpzuQPa\n540/VLjDu4erL6IGDgox3snYdtuDPyZr74+MzkAMR0Hl7YPcMS2v8WQMm0n3\n+VxcJ0ucmZsGjEL1lm/qI5NtIeXuwswBbQTh7nIgj3dcVRwDzbHWLyGBof0x\nndHQ\r\n=A1uH\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"cc0729ad6d5653c7aba2506a160b1af0c7e6817a","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.6","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.48","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.44","@balena/jellyfish-logger":"0.0.187","@balena/jellyfish-metrics":"0.0.215","@balena/jellyfish-environment":"^2.3.15"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.0","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.13","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.69","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.53-renovate-non-major-cc0729ad6d5653c7aba2506a160b1af0c7e6817a_1609765268979_0.5082412275083625","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.53-renovate-non-major-bac4907a08634ba48675f0f0a15888872af0df18":{"name":"@balena/jellyfish-core","version":"0.2.53-renovate-non-major-bac4907a08634ba48675f0f0a15888872af0df18","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.53-renovate-non-major-bac4907a08634ba48675f0f0a15888872af0df18","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d54e0fa4b8abc52fff27a2bcf2a233d7075ca35a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.53-renovate-non-major-bac4907a08634ba48675f0f0a15888872af0df18.tgz","fileCount":95,"integrity":"sha512-MyV1Ht09WejuRg1bGAUXCDXhd4iX0DU7z42eKECKL12+4anNwThzz76oEPwPcIPC374Yct+zSVklAi+9t3413w==","signatures":[{"sig":"MEUCIQCBUFcKGbxvH2AGEaGFhuN5IMbDASKl6PYdX2xPlxM/mAIgejkF5SQbKxuVoU7KeiISQ8W6oGVMzfzUMfrqHlXF3vs=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":578105,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf8xKFCRA9TVsSAnZWagAAlUcQAJ0oqO92FMpR9hust9IP\nbe/Y2nC/1wxtA2IR2nbBPV9HqVg0CM+kddWyBN5RcfTuGle0Dr1nJRTrSeyB\nr20QBcjN0DkR0UACGA/eQFFkb+iBb36UXlSXao/ypqoOaEWwalFBD0r0Q01S\nXN3RANBiKS0yzD8el+zAgfeG9Sl079XNgMG0JpVkp/FJMYatzne0fHrUPoUn\nY+NHmr/ApZ7tkGiwfBCXe4NTFZ8LbBJAqo865TzlHPdDkJ88Q5Bt4KhGyrI7\nkY/gcXzfhRIoUsWviwRXVfzTqpWJM51a8GKDoHQesPG32ffUS02yB900eZu3\ne6XzO7BWthqLQiUTm90In8wkPiM7Ds4e/NGqoe4B8/rDaUhQ9ZbDiUmXUkm4\nP7v/ZMs+7jDFyFViIyiqILQHMvsIGdvOauR4DkDbxK0Sm3jYKdQOSbkFezGb\nlAGbpK9dwiq6u8zCnbViwXwqQWu/JPONwpOjGf+aqa+TH3sIa9zJ/47womJk\nh0/EpBL7mmde47Zkub+hsHi7OiThLLgfosJwVe4JLd4szqBPUBRwUCYbUneW\nZlGfgnghKslJwb3nIc9s9WHCTH0SUkAZM/NPXmZHTe+0D8aom4noUZDzfsPD\nLPmKOLCUzYwfzypD6GEp0zHJPzqg+rm4EogNjZoEv6Zd4E+n2qF00MJUGO3b\npMYz\r\n=OKKQ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"bac4907a08634ba48675f0f0a15888872af0df18","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.6","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.48","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.44","@balena/jellyfish-logger":"0.0.187","@balena/jellyfish-metrics":"0.0.216","@balena/jellyfish-environment":"^2.3.15"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.0","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.13","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.69","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.53-renovate-non-major-bac4907a08634ba48675f0f0a15888872af0df18_1609765508892_0.4382274816002367","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.53":{"name":"@balena/jellyfish-core","version":"0.2.53","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.53","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"380d2737f7ae5f125014ed74db253af4072a2087","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.53.tgz","fileCount":95,"integrity":"sha512-6htWEhz7FUIfgLNo+R9oillY9X5C2mENb7ctWiI/YS6aa0T0Oiw/VKc1AyxhLKN0c9eBu5wbi6BKyD4fDm1ZDA==","signatures":[{"sig":"MEUCIQCtS+Xxbx6IIDTHBQyiUiuCTDVQBI0QnOndXnO1zuqnjgIgYhint9f6XAhixnufSlouS2UadOuy5UzFu4X/GgDT5oY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":578045,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf8xQDCRA9TVsSAnZWagAALOUP/iRnE8hERCotblLPa1Jp\nUYmNvl3+Y/ppFkY7z7ALiSmRiM+h5xA/dTWs+x/lvKN7EQfVqFh5twXMY95d\ngI6Pt46rOVjPNrvLDD0fjB6k/w9O7IdMI4abZezbaTJH2tSzD+SzWe3N7xVJ\nT22USTK4hyJDJ4LAHbvWCsIvGKg196SMBRbYRM2fjGTeKLpi6pUlzilW4FhA\nT322ZYV2rUGmAS8etT67Th9zsFbIs07seFKb+6pr6TffpsJC8HPNZ4o/2WD3\n1/c0TumvpZRNqExSbWdGzrTxlI3qLAS4/IyzInMeFPFPRWzSDfBbrdz7gBlv\nQbQfXyEOrHFs7zzwOngwlIm2SI12XKmoPqy7X0eI5bsY/Gow3D2yRlnPOopf\nKNasZ3W+hQtvoFSoxwjKsBwAqAJrYf1+CQure4KvDee5cpiENHkYAz7+J9/8\nFEChaZD2XGTEKwzQydlrhENshSh7VFdynbUuDfmShy9GnZINj1ZB+waH71UB\n+GJlitoElpaz4hfXc0Uuf+VZRi9Vs4Dr+J0kWCdMXg/33IiOGCy7YlEjf021\njHejZB0vqc5PwBxs/NFZrs7QMUtSG+M6M/I47o7kY/qQOn9+OLFX5JQxqk1c\nfA2xkie9nbPQp2/oyZgFZ2r8OVRxGOSZ/R84rkhWji7cn5G54MgA6YoA679D\nFcW9\r\n=7cNa\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"960a0d6f395b9799f197ee547ac7ec03c7afd22a","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.6","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.48","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.44","@balena/jellyfish-logger":"0.0.187","@balena/jellyfish-metrics":"0.0.216","@balena/jellyfish-environment":"^2.3.15"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.0","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.13","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.69","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.53_1609765890871_0.7117092351588175","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.3.0-patch-transaction-53b085fea6ccccf34d09d5a05cd891e255ebbbb9":{"name":"@balena/jellyfish-core","version":"0.3.0-patch-transaction-53b085fea6ccccf34d09d5a05cd891e255ebbbb9","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.3.0-patch-transaction-53b085fea6ccccf34d09d5a05cd891e255ebbbb9","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"ava":{"serial":true,"timeout":"10m","verbose":true},"dist":{"shasum":"ebc924c78caa5e82dc63ccf4cc87926706367d73","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.3.0-patch-transaction-53b085fea6ccccf34d09d5a05cd891e255ebbbb9.tgz","fileCount":95,"integrity":"sha512-dic7Tdz5NTz2mJ1U11b2AtbfSS1JxnaGxaY2RpYfQwWwsYaFRDa0SPrlV/hTok/5xwfNh8Yjvo9HnHKk+HJL6Q==","signatures":[{"sig":"MEUCIHd+L6uFlHGIPt9qoKOTVOvs3NlCb0P6hpWPoFZuvWd9AiEA+SYB1Fv34WhommloWvAzHH7i3sPfYptxpSSjyMWgAdM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":580439,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf88KtCRA9TVsSAnZWagAA22oP/1ij9jb6FATQ9CAJw7t1\nL980f9WPWGu9gGrmK8xKkYQvLpW8zQ5e99/52FdgVC6OHMVT+qnHPlTPs4+n\nqfZfs5QvbUOY0O1LSAJIOhh5MmsjJdTJSo5L8QaSSIhh/jKRMqgoWPoXdHSl\n3G/kQeZ+yDz9opwqNzlZepVKYyaaIayRtJ4fcjGxklWtZF49CeIWIWQHQ+ew\n8BJ5oiVssTSG0B4hujVbgMBG7NR1o9RF1Cc9jGX54bG3p1A5NVDIOcDwAs4T\nG3y/3Kqf1qZ87NMUffMroChjnt28z+DU2ZCfb7sWxDJFMvJ9qDfG6UuyrfU4\ntwnW1XMzJeMHj+EdUKz9Jmna2Vhwrbo1hLiZwotRzQkdEK+8X3FkZzJdXzgW\n7PzssW8BduzKT89AO+/AbghKgp1j15gOMNY/FxVNFwZmTfnd0f7Ptdv+xUUQ\nOeagnWHTB6WfNosRhzkXnsV1dFE/Pve7jJirvOKCqz8BjotwqY6NzeF4L9iB\nAY6aVka1nKEsknMxYSV1ttSrpl7js8n2+m8i2q3JydAnXQZL7Aal0GEDlBdT\nevWTRNmlyYk4rUXLc8fD+iPzktqu+ZbDn0MHl/CgOClRwIkGoFr+t1/sj+f3\nNkjOiCY8l1MOmKZ4miBDxfAl37CjizGluhrlpR8PI+i3KVP/VNBlNYvQevL4\nhE7c\r\n=1OIq\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"53b085fea6ccccf34d09d5a05cd891e255ebbbb9","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.6","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.48","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.44","@balena/jellyfish-logger":"0.0.187","@balena/jellyfish-metrics":"0.0.216","@balena/jellyfish-environment":"^2.3.15"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.0","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.13","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.69","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.3.0-patch-transaction-53b085fea6ccccf34d09d5a05cd891e255ebbbb9_1609810605057_0.8187983532067755","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.3.0-patch-transaction-fe9a7b422fcc6a82f2f913cf463122373ac6ca17":{"name":"@balena/jellyfish-core","version":"0.3.0-patch-transaction-fe9a7b422fcc6a82f2f913cf463122373ac6ca17","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.3.0-patch-transaction-fe9a7b422fcc6a82f2f913cf463122373ac6ca17","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"ava":{"serial":true,"timeout":"10m","verbose":true},"dist":{"shasum":"5f427d9e33d618c4a3b32e8d8f562f9155c829fa","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.3.0-patch-transaction-fe9a7b422fcc6a82f2f913cf463122373ac6ca17.tgz","fileCount":95,"integrity":"sha512-qKX3Ms4c3cnyIegJn53z8frkR4Y2jewljl5fFfMP0n6BmHJPXGvDcZ1yIU2+KeIa6lKPhQcoTSYZcgM5Z4Hejg==","signatures":[{"sig":"MEYCIQCZyfx3LK3FrG7WP25EPRPqVKTpV73MQ1NEBH1gGtWy4AIhALz/Ab3/3ESNkECTPqxD+xiTXX4Hqdq6/QY4iscqIzlD","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":580394,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf88MzCRA9TVsSAnZWagAABrkQAJzhqZJSsKQHcT4YWIgO\n5SYhuVZhUH1Y0oXrvui9YlkLokd3E+60SQGxJmSzkXJrUSg6g6d7z5ARpbMY\n6RzpHLVj+oDcBlvUx1C1LRrk2o87V8hln6k4WWAr4V/R8c2EvgegN92jIabA\n7G6hQlahpko2b/FsyneQC0c4+/o3xhSTlFwY8OtuVMYNWwETFpTCDw1Y2s3t\nf5aeA3i9QFmGLdYiJtLx9rDvjRDpGiyOhJXnafl0JXy4oRO0KxQ/mGMsBGKV\nAHJGU5076DiDmI8MusrRJgy0jUr9kgI/LyaMhfMR/t5K9awRdmdVX2WLxPsV\n8lT5HqfN4fzvGV9D68djkjxfxjABhgNMrO2/Fydzl/b8vIVZQZ6S4vIlyBQR\ndvM4X+WHS/hXvaawgq/XizEk5KTdLqvmO92KW31fmCEfRkayluPUJwYJE7lU\ntNg8idRzWGj7MbQ5FrvmagOeNEDYW79+5JODSvafa9n4bXrCyGTthFnFZxjF\n9n8fwdem6D7Lf9jWIIPnCADABa5tz54eKFLiJsag2mH6jDR0fBpOySV/YfNS\nrpQ6bUsOvmidsUMaxs7BLMOx0OcUZ+PzBHWfm+ti5NSAFDu/n3nFhWKxcNUd\nBdMElpiSzYeZgNiqUha4pl9b75zJAPdXLtFl8diRR69qVwG1vYcoO3Bu/a3p\nd2gK\r\n=JUVI\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"fe9a7b422fcc6a82f2f913cf463122373ac6ca17","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.6","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.48","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.44","@balena/jellyfish-logger":"0.0.187","@balena/jellyfish-metrics":"0.0.216","@balena/jellyfish-environment":"^2.3.15"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.0","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.13","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.69","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.3.0-patch-transaction-fe9a7b422fcc6a82f2f913cf463122373ac6ca17_1609810739306_0.939409326118525","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.3.0-patch-transaction-6f6da61c1ff52e744c1dd9ba10561ca8ace68fd8":{"name":"@balena/jellyfish-core","version":"0.3.0-patch-transaction-6f6da61c1ff52e744c1dd9ba10561ca8ace68fd8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.3.0-patch-transaction-6f6da61c1ff52e744c1dd9ba10561ca8ace68fd8","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"ava":{"serial":true,"timeout":"10m","verbose":true},"dist":{"shasum":"7669930fafa72c7d55998a5472ece6154d1f14fb","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.3.0-patch-transaction-6f6da61c1ff52e744c1dd9ba10561ca8ace68fd8.tgz","fileCount":95,"integrity":"sha512-Oww19wyfaQrYSxa0YfjiV5FyjDbWE6rlIkwAkCVRpZNtzPaN1J9tBCftl3hnJOAuhwdsWqWYeZw+BQQNV1lSOQ==","signatures":[{"sig":"MEUCIQDR5HBXUOdDcOB9B5+fWQkcJZb1CrCD6EWnQXjIQm03gQIgNXTmD/tSN8AYOibUFgbd3GMSgmScQhkji/egW8dstDQ=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":580339,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf88OqCRA9TVsSAnZWagAAhAoP/jA78HZXCtPNWnfz4EBc\n0A3wSvWL3U5uH3CEo3wgLLmkl2MjuCKFwkRO9W6dZBvp2YQoB1QT2ToHfhaO\nB81k7MF3XEnuUHV9dRQ5wrlnMFNVUmnWJq11redmcV7ohSWapQMKjdYnqAfI\nmARhR2/ldUm7AU40yeoXu6r6fFq+UgKYFyYPjDim9KJlSvTURwBdf4zZWRZ+\nFHdGD8PAEJEMfJiaGMRYHLPiUqyrQNkfri/OnwrVsoWnXFAajN/cZkKNeMbz\nzreNZHiYr4bOBcLEdQScEZxOV7Q8oxhuk0tqjCQ+eXrMa6r9oFcOFKWwvMm2\nBva/rYFrHKbJ6nGrHHTEDj3KwyRL7/cpBIIJ5bVLplDXkamw9+TrK3E5OQZQ\nQ5ouMfovjhdNnw+i4Dp2afc02Ri0NpNpyKIGVA/l50xhy0FVAsnw1EufWV1k\n2TsBzwzgYVnNsBVLKRekVvcRSkuOQWEjbBBfNo+Ts/LsWJjNfsR+r9+McQwu\nvwSBl/v8x3YPCemBcZCUQPeKmkMc43+ZYhFuNqx7p7wVPAqYIkLC62sgEPQN\nTrzqj91uSG7MA5kzs0gjOIcxK2p1AjxfqAe21qBWQaGTx/kNVz+1BxXpQJ4K\n7GbGTa+5h6N9WGIS3wUfYSz5aWLAvymCNgVI6+h9ldDyaCo13gxdWMVv6bCv\notPW\r\n=WBGC\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6f6da61c1ff52e744c1dd9ba10561ca8ace68fd8","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.6","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.48","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.44","@balena/jellyfish-logger":"0.0.187","@balena/jellyfish-metrics":"0.0.216","@balena/jellyfish-environment":"^2.3.15"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.0","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.13","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.69","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.3.0-patch-transaction-6f6da61c1ff52e744c1dd9ba10561ca8ace68fd8_1609810857912_0.01945973165388004","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.3.0-patch-transaction-701f135bc25627e6dc559fc2c7317cdeeaad0394":{"name":"@balena/jellyfish-core","version":"0.3.0-patch-transaction-701f135bc25627e6dc559fc2c7317cdeeaad0394","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.3.0-patch-transaction-701f135bc25627e6dc559fc2c7317cdeeaad0394","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"ava":{"serial":true,"timeout":"10m","verbose":true},"dist":{"shasum":"d5d9e9b8c85930b834eebec3e5cd8a8e36d22476","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.3.0-patch-transaction-701f135bc25627e6dc559fc2c7317cdeeaad0394.tgz","fileCount":95,"integrity":"sha512-LkOAht5s4TXCK4vF3pUG6blKzDzAcxb2AFjRFqGrN4kpyco7ePDNgjWGrAgFPs/4ZKsqDWDKKWqgZAEMvxLsNQ==","signatures":[{"sig":"MEUCICwiyffO6lf7tqJyBchg3BqQ56A8QBh7hORdqF8JhLWiAiEA/xKsHG15kzdJVfzBcltEq8rD6aYED4XVuLtMx8CqRo0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":580307,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf88T9CRA9TVsSAnZWagAArAEP/3JUAXIPuoDBTV8yCIXS\nao6niNHon4IBNSTiLQi8+YKXssCA5D+euduTZ/WUsj8COeU2Uu1ACPKnDNyr\n61Ylkz9vnhHBwWF1cW3FqY1ScwnGH8fyhFzFegt4cVJxPY25H8+/m2PvOzAk\nsltMyt87d9/hTTcJzSiQ79kTGAJBk8jr2K7RaEladEsFdigACK0QSvk/t3o/\nKOZCW4DFs6heOQuVWQE7fZKwx4wXMeQb2xr6jUQTX8QHxU/9Y9CoYXy8uE8+\nK4JltBkz0GDPTMptVqE7VRYarVs+QIp5EMnL9QKUOm85i2CQTsytG/rUGit3\nxXIE3V0pQ/qZGF50mzKi8E6P7JmyxUFdvC/kpq1FXP4weCg6gpvGjxrayMd7\nGq/h5TibC6XO5ruMqfK6rEdLlT68Wd+xAjEdGDOG3lOBL/d7n639ZwZHgWuM\nRetdURZ9vSuVuXIzT31VI0r4gGR8i73+M0Z8kkNbnaUJWb21hSyVLOgZsGxp\nJPmdfYDg5+AwME5wMy9Dwmtnu/jXR21/Fq66PtAnKMx5yE5OVbgl5gvSC4PQ\nHLIkrD1YlwYXyrXOLXZHYzE6cVBhurZ2EdseXb/NmbsaZ+E0OsUQoqERyCgm\ngshj7bQh/2n8fOR1GIKoxsLkLH+VH3XWU84ZezRoSpoov+ch22lHScmC3tfo\nwPi/\r\n=CUpx\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"701f135bc25627e6dc559fc2c7317cdeeaad0394","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.6","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.48","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.44","@balena/jellyfish-logger":"0.0.187","@balena/jellyfish-metrics":"0.0.216","@balena/jellyfish-environment":"^2.3.15"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.0","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.13","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.69","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.3.0-patch-transaction-701f135bc25627e6dc559fc2c7317cdeeaad0394_1609811196501_0.13276176638310178","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.3.0-patch-transaction-398e391a660fd8c3fbb6e8daf412ca9a9c7d5a17":{"name":"@balena/jellyfish-core","version":"0.3.0-patch-transaction-398e391a660fd8c3fbb6e8daf412ca9a9c7d5a17","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.3.0-patch-transaction-398e391a660fd8c3fbb6e8daf412ca9a9c7d5a17","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c8e2c07801788b5ed801e59bd8f0119380210b6a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.3.0-patch-transaction-398e391a660fd8c3fbb6e8daf412ca9a9c7d5a17.tgz","fileCount":95,"integrity":"sha512-RWKux4oDmrZOiJL6naGkiEIf5k60UFzbKCfwI1owD6AP4XQ/3SR0VZf+jcR98GPM1+LTcodhzCofI6Y488kw5A==","signatures":[{"sig":"MEUCIQD2jh+ZXr/47Xo1MN5yqTT9zeNucARVc5ENNijB2eqxQwIgfgMIqQzAQFHaGh8ctSHafEexgytakHou9fBHRGdDDcE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":580336,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf8/70CRA9TVsSAnZWagAA4FYP/31BJ3izV5CzVLmExEtm\nNWXo9lYAysl/6nv/ZtXOsfw6Kwhnci381safTlhX14Sen7r3mpThd1TIqX9C\nVqJGseQ6xtSUgvRH98PZt7lfIm4/pG/nIP0Vo14Hb2hb601VYf2CuNs33npl\ntqkN7SSQbn4f+ttq23s6ELPSY1nkAchXL+L/OO/DiVCWyiImV+0ExyYfLX11\nXbCEwudzJxdrRwpKehCU6zY9h9d536ha/eFrIJo61iQnVh9cDe7DyZ+c5rt8\nLNyoOZBmCfTv1/H9XW04DnmY3r4D80xz+5OKFyb4Ousr9FW3jKLhIhodNe2j\nrPZa3XWp6+73bzfWiPNNOscKtdiGXKdBheT/E+rTPo7aFmkxsiciz5CP3ZVu\nrFfK/e1DB6nMjxW8mMRNVGcCvLNDUPPy420n6yv9DTFSAXC+zWjJePU2olD8\n8rxxrUAqHq62f1tEv6Z5fQOYHNnhFy2tswjV1NmnCVPMf1umfGhfIkJYPqsK\nEltom/TUvsE9Utd71yOQogwhCtwYQgEDqxL7Vi+HT5qg31ezxwpqoCujlKln\n2lNrLmDV8F3yTRvDadeShyjKBxTJhXMn1iwAp/hJ9ksxcjtBctRjF5SzpFQU\n/D6OoDDu3aIOX+qmPtbmY1hL1l0Dzw4WdRlw4oT0vJDn3lVqAVfu/PAgYvon\nD4t/\r\n=cwsB\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"398e391a660fd8c3fbb6e8daf412ca9a9c7d5a17","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.6","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.48","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.44","@balena/jellyfish-logger":"0.0.187","@balena/jellyfish-metrics":"0.0.216","@balena/jellyfish-environment":"^2.3.15"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.0","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.13","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.69","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.3.0-patch-transaction-398e391a660fd8c3fbb6e8daf412ca9a9c7d5a17_1609826036224_0.37383863023368535","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.54-renovate-non-major-3fc2084b44d9a806448d589433569053e083b7b9":{"name":"@balena/jellyfish-core","version":"0.2.54-renovate-non-major-3fc2084b44d9a806448d589433569053e083b7b9","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.54-renovate-non-major-3fc2084b44d9a806448d589433569053e083b7b9","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4b8b86e10ced00270d7d1c85c5c84ee89cd4f1b3","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.54-renovate-non-major-3fc2084b44d9a806448d589433569053e083b7b9.tgz","fileCount":95,"integrity":"sha512-xaZ70laZG6uZm+4F6toH8huOcqHy3kLa4etzUr3f2Og3i3SZjaFgWqhhmzV9yH7SS8YGa2fusbSxoyGfO0xADg==","signatures":[{"sig":"MEYCIQCy3nQiAHEm2MMneg4yZMd0T+7g2sN23mwXfZ2TrS6XigIhAK+bqyVumU5ehD0bynJEOeT9C2kFSGlUrYzM/2q47pIu","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":578171,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf9nPcCRA9TVsSAnZWagAAEdwQAJzpahI44LRdgL5Hq+jv\ne8aayBUglReVL4irqQ19Df0zzdavksf5ZZsWuwtE5AP+EgpzCXUbzkWi0K73\nwOPsLQk8xXOId7KAFhQ7h6N9blZllG6Y/IHAJgsh8aWAZMnN6EIdu+6zooGO\n1wvhr/NHG49zam3wFS5nYXqqkc1DWccbcXI+iqUlNYL3VvHEpNxZ4U34TV9G\nQldOPqgPlJ4TuZFqczpnbHf+D3yd8reaTN73HnfuIMoeeiJU5u0XwITMrWeE\nzFrClqAYQhbQml0GHi29rC2Ae6QNhKNVGic2z/5lWe41QI7txRb8JLe6gy+w\nk8KiKJWLXelVbbCuqI1jI9+SoOrYD7FttjUbZuJqkQKO1/9F0h2ed9KucRdf\n5udGYzRCa7x+SONzo3NDO6Ilt7j5h91mKzvBPNJE4JSQ2bZk8If2+k/xZJao\nagFt2KXZWsSnopYaTq5tRCqXmQY/hNXbB7IhAmEGio6e62wJZvXOPgmg8+7r\nVXu52+ixXrAezUO5hxEXcc1t0KKr0HfD7ZKocRHTVLXHGOmkM8tsI1PA3O+b\npgC83HlaCINdppps9gJVUH4d/H2nb+OUanabrO/V6gRjK0JO3GCz5bfc89ND\n7eUZyE8Jg9KvHTAtGspp0imKDF3V+PoO0s5uWrIINFCCkiuTFuruCJ4SKG68\nMYrv\r\n=bjq4\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"3fc2084b44d9a806448d589433569053e083b7b9","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.6","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.50","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.46","@balena/jellyfish-logger":"0.0.187","@balena/jellyfish-metrics":"0.0.216","@balena/jellyfish-environment":"^2.3.15"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.0","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.13","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.69","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.54-renovate-non-major-3fc2084b44d9a806448d589433569053e083b7b9_1609987036137_0.11649425075184494","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.54-renovate-non-major-af15d99acdfb9a80c1c745cdfb3f8acfc712c576":{"name":"@balena/jellyfish-core","version":"0.2.54-renovate-non-major-af15d99acdfb9a80c1c745cdfb3f8acfc712c576","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.54-renovate-non-major-af15d99acdfb9a80c1c745cdfb3f8acfc712c576","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0d52efd6b10086633d7d98ede8b9def69796563b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.54-renovate-non-major-af15d99acdfb9a80c1c745cdfb3f8acfc712c576.tgz","fileCount":95,"integrity":"sha512-ejTvzqxvKKq6jCxkY5R3mpQK5shVWqSG4R4F36VuT7Iq0UrpelBHhcrro+81PDfzUuFVmYpE3B5FudWi+PyqCg==","signatures":[{"sig":"MEYCIQDdV9WD4pVUxttkTHsUNJVigJ1Ls2E6e0jAMQpfqzSm5QIhAIEMMPjDilMz9fr7Zer5r/P/CME+KSXeORrkTHzVDJoz","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":578171,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf9nUSCRA9TVsSAnZWagAAPKIP/Rqe77mtkZk6C5qz20hr\n+HBqxDLP7fCQWZqNQj/NHPtr/csqWT1HObJbZnbhie23BGqeHRpfxJu7vtUj\nByENA3ZEQvsqxM4RTTYQ7NtXVjtYhYn+QIJHKz3YpUYhGQMRIIwQ7RkO9K0H\nG0CfGSEN2rtui8bK6ZIMEhUMf+ELe+GyuqjVEscRixYEkmrnWI2WfDlqqJ2G\nmcKsMcE8AhEBnmMe3VOSLme8pVXyUaLIu0xUCWUCDQvogS2kEhNJgos9PNUv\n1sbHs3gQh0JxT8rmKiXG5p0opvwcztLYA6eFcq7gmzOgiCE4vG42uFQbZaRK\n4stx9QdPAUIk8V8fxClej7mGMrNye65KhHR9BspLHX52sQJF0EZgbdkmvSFs\nscsGn7c4gx/vATruKiQ4HF8Ov4xaeCw9ehOHsXzis1B59W4RNCL74M9aviX/\ni35R0rxmmSSMA4wnv3YWzlcR8jd107TIvtb5EVP3R+OVG0xNjr3H0UWtPMHd\nAsaRkiFi3YyfmTuqKN5EvCJumjpBbSGyweP2Jk7lqfY9p79aNeZ83z0sBtoG\n5KBrpgss3COpisTRuUdHkXYMORpSbrtlnBoGd7mguYoC21xSNKnQGYvAZd7u\nGCLoVxSO3/yvuRTtCDaBFZ3I/6ug2j61y4kHpMe4wor3D7FQVPhg2XIZCrgR\nB5WH\r\n=L42L\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"af15d99acdfb9a80c1c745cdfb3f8acfc712c576","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.6","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.50","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.46","@balena/jellyfish-logger":"0.0.188","@balena/jellyfish-metrics":"0.0.216","@balena/jellyfish-environment":"^2.3.15"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.0","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.13","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.69","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.54-renovate-non-major-af15d99acdfb9a80c1c745cdfb3f8acfc712c576_1609987345952_0.6761253214082945","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.54":{"name":"@balena/jellyfish-core","version":"0.2.54","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.54","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9c94c812bdebbb2a3e8cd8b3baae1e99149c1fd0","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.54.tgz","fileCount":95,"integrity":"sha512-WruMMN/Df3GSj91ZlbYH7QwL+rWk4aTGn1g/e4JA2ylSsnKI5CpTt2xkQpVRpDu/92Z71Q0Dy1GFGBxANr3CBw==","signatures":[{"sig":"MEYCIQCTI1YQnvJnfyjyQacBku+CERY9JyGwUqzTBJAT23XX1QIhALF26BwItId0qg0PCFhfsBfFeN4oOQEcYaeCNqlS4UtR","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":578111,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf9nZkCRA9TVsSAnZWagAAAjIP/R/A+3mOw6QbhO+VVmFC\nW76NEF+NjA+PEeEYpjwyjZyLX/cc+xJGkqZe4afbf2U8H9pi42QJ+VwIG3zK\neg0roghYK9lDhFhk9x5B7fqya62Xw1fxm4C3G7wOTsMBJVIddECdJV5nlBrQ\ne4xdDce2IqJ9w0qvyJVvbfzTPwniYYoYhfTaPQQEOXhBdqlE5UPiZP6Cebs0\ntunzi1ZVHNE/7M18X8y7MtyZjDAp0POqkabjf5zxVURptFiQqqSn5qN2Pe1a\ngVw+nYg+YdU2HWECpBZJlRwvV8OrrXQO/4AxuX5zqzLTJZFVsLuj1Gwf3kCo\nZXCzWOQBZcV/EAaYhKgUeWUmeJQ1IhNcFkxc0wDuXYODBoQE5suxftmJtrsB\n7kccDh37li6egnXM+sZYd0Afv3Ial/9eQvaOeKZ+lqrsRR/BL6snL0e1Q5pj\nO2LVrxE1feKYgXGEUAlJPF/aNgyC5c4ljG5AjOhySEqfuG24TBXn7EXhD/7v\niCndW5MAqz1zqGlOCSD/rMv/k6h34mxrC1svBEd+HmCfC/Pt4iwdyDHTWa7l\nDtbRnWhx25tAxBmvKn1DTi3Phxz8Ij80nytooVbmwvDKCN1q+tvX3lktHxNS\ntxiCAJ9nUFkU18a3+uWYyjCZHSu9Uv5itAdXCet7lHEdgXJOV4OHoH0m2fyp\nLQiB\r\n=yOXf\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2ac19a811c14110265a92e86d75c8581273fc843","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.6","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.50","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.46","@balena/jellyfish-logger":"0.0.188","@balena/jellyfish-metrics":"0.0.216","@balena/jellyfish-environment":"^2.3.15"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.0","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.13","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.69","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.54_1609987683637_0.9757270754277265","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.55-renovate-non-major-dd8f324a7a66254cefcd849b6afa51ee38c40970":{"name":"@balena/jellyfish-core","version":"0.2.55-renovate-non-major-dd8f324a7a66254cefcd849b6afa51ee38c40970","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.55-renovate-non-major-dd8f324a7a66254cefcd849b6afa51ee38c40970","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"11ecb56f03641f0005083d88acc1340b5f69aa52","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.55-renovate-non-major-dd8f324a7a66254cefcd849b6afa51ee38c40970.tgz","fileCount":95,"integrity":"sha512-tRbZ/jwreeNerrKquWoRdv4HcdaJzOoFMjtInwFoDwRdPAfQAwRpfqcyJtKzZPijhUrpTih92lwmWT7WMovnTw==","signatures":[{"sig":"MEQCIGZoiQ8CyD9rXmBUd4ctppx1wvZwLcCBiYvbo0KU2rpNAiB2pUY1G1cHL42fdBULI72Z2kS0RWA26Fa9NoDdi1d+CQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":578276,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf9ouRCRA9TVsSAnZWagAAC/0QAIWo3/xp0xNoG9b/LTLX\nwDfGitRp2G/TR/vr1psp5MI2/gmrtVf1lFzOi65YxTqcfntFAoD9AaLc8n0g\nR3Tu8bbnT/7zgmO3PMMXZFraLuoG+d0b75xktKDjg/5SkRVsima8kNhxkPJ3\nWuNYID7i54gh9q8fGSdkdyGC6jvpo59sBn9E5iyAnwX0nuW5JqbP+RprY7NA\ns/FtsIlVXxs7iSTZAZYjCNCHkQtIwIM6r2ltNgtFnPQn5IeR1+udzIF4hScD\nfPX1Y22ynV9+RHFwouOKFs7GuUAhiHdwMaZB3UaHrd6gixnaPLt9Dp0YnuXT\n/xMwlaOY8RpD1cBdT5qjV845qBBA+gTmITvzRsgewxv2X9QeRWaqUdg1rLNe\nI2UgvsOj/Kx82fx8y/pIwbDDpY7rkq1MqDowmSR7YaGROFt3lonO2b3047q/\n6/g7eKg65tOX816j7+xvsKBGC1sghS5qE5ksxB3VPNj7BcRduA/9TCzxsE+L\nuHZm9RNxWRSdpg2VFzUcp1s7EHcTESSLA683+2uC9/k/AIRIyQvOXYCrMr5f\nVG2Q7q7pIHuM9f0fht2Y8l8VMSAezGDXwWYlaZLfKqpYr2Vyw5ivP5SNAmlu\nMzC+lDSvQYhsk7A56dA24e9L1mhmtBjxknFUEu/myskldhGnsF3dCy3CG1BK\nSwst\r\n=tdrc\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"dd8f324a7a66254cefcd849b6afa51ee38c40970","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.6","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.50","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.46","@balena/jellyfish-logger":"0.0.188","@balena/jellyfish-metrics":"0.0.217","@balena/jellyfish-environment":"^2.3.15"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.0","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.13","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.69","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.55-renovate-non-major-dd8f324a7a66254cefcd849b6afa51ee38c40970_1609993104940_0.48139039219118973","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.55":{"name":"@balena/jellyfish-core","version":"0.2.55","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.55","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f2e1f2c7396dc6f0a4e9297a829c260d40dcfd1c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.55.tgz","fileCount":95,"integrity":"sha512-3IivoNIZueXmiinSmCavc9OEvEG8yUWOBWAj6SmmLibliDC5srogVIdvKZgVyam4ehpuXJRu1vjEMqSIKAxigQ==","signatures":[{"sig":"MEYCIQCMm3+AU+PAYLaq050UZndmyCaLYm9pn1VeFATW/xNDJgIhANzP4xB5HL9UY0i6n2kZMeqEqdIP33wpidJ+vY6h9Whd","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":578216,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf9ozRCRA9TVsSAnZWagAAq4UQAJhikVMYUxaOOcTIG+7m\nUdiDgj2OQHVuwrGNMoscuptffK8bHzvx0gcPYdUWBFbIsilPDGPgK6XZzqHj\nAKGnU42GDQCayFPHWbAoKn1997KVzovq0j1tnS7JFz8cx4/POkgVQqZzTXnq\nvq5AwvLoCjv5L3MtTpoQ7BZM51FMack3wCXj2y5F/zR/xnG1A9WKZWXuL2aE\nuqV1reOT1aRGSLO662SWYlVt7sJ/aOg8wKczv0ZPqe25clpHwD/3TbsT8RKT\niyfgAzi+EeU6PwlY5c/0aZ9DyP2MjxwKTj9aLPeMeUwR8jagJFOVBpJS5V14\nEMiAjuGtGxFzMCsI7RPySp0p8ipqfybWVkfYjpgiIyC5wAVseK70OAY6G/2K\n4z38BJA2s+sGkYsOJM/gZJCpyR5fGQrMpdf6dJMwWcbyOYJHv8sUzKUB/jwY\nQqj/cdMSH9m+ml8286L6Dkka3nSpq5v7iQTSbvwAw5+KtnBNMKmo3R34sAFV\noITLBgrmTYiQrURJdCyI8Ubse/yTStYmtwcCHw8Zf7GZQkcwIHh7dvcBuDxQ\nNgqctT/ytZ758bazD7ItgYGkdyf9fNEYswlStvg13lPxV02bddc6qIbTTQCn\nmh9q0HnfxR6iAHzNZYiAqe4A2e4rMLFfIzmbJjE3XJUgYH8zCgQp7Hg0dyiU\nGZ0X\r\n=NgFA\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4e554f4bfdf6c05f4ab226e5dbc9d776bfd6f39c","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.6","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.50","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.46","@balena/jellyfish-logger":"0.0.188","@balena/jellyfish-metrics":"0.0.217","@balena/jellyfish-environment":"^2.3.15"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.0","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.13","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.69","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.55_1609993424994_0.23581630310907076","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.56-renovate-non-major-3ee6b17d9d0e5447e7f277ffea207f7e07ef7b80":{"name":"@balena/jellyfish-core","version":"0.2.56-renovate-non-major-3ee6b17d9d0e5447e7f277ffea207f7e07ef7b80","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.56-renovate-non-major-3ee6b17d9d0e5447e7f277ffea207f7e07ef7b80","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"37e8d22065e37d511ac565f267a997c20acdb505","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.56-renovate-non-major-3ee6b17d9d0e5447e7f277ffea207f7e07ef7b80.tgz","fileCount":95,"integrity":"sha512-suqxick0I43Hjz204nofldxjKt0DSXKZGT9WYnnrvEFT6MK3p8yGFuPXGIavE4PKeI9tShwvj+EeXpoKPm0vhw==","signatures":[{"sig":"MEYCIQCKrAAltNfm3dcH7VNZSpfj0OYcJkyXYB2VrJpNV+JT5wIhAK3yGbcge6d2Lafu9U5LDjQVQ5qG/pV32lrjqoFPc8L/","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":578377,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf9p+dCRA9TVsSAnZWagAAjxEP/20c+N6DhzLc9FXKbtxf\noagPenP8aiHtXk9XWEy9vXgjfGuPl50mUejq00l4Ma7JH3SJC5OOVcnmpoVA\n1nBcgTiZRMM7oj+FTXWF944SCJb45qP791ydXbv5i3uUGC/YzFOyS+AdeN68\nN+IzGz4X4yoOu17WQIh/Dy/d/shvpw9v2DEYKuZacbbreYU9JOmRNIcuOV3E\n3KDHl8q8RZOLwT8TE2PoQhQI1cXX842Im7TQ2Lo3tslrg6YQg01ynIAhcesA\nb/dL86KYCdD7NluQlozfSuCq1cD/PKtDJv9VhAuXLWDvQzA2pxbp08R9gEIB\n5JXLgAMDmrZS0mArPIo7pBeCskMKOlCh4ptgQPenedan5TpEhLJ01s1uGUP4\n9TVuwFc034oVDuKbdFxpxoAJi8CTuBbdhD172ujrwNJHLRcXOivaTFpix9Yy\n2hnaFIfYUzpGL+GGLpybaVaecVYRo4y7Bbdzd/GAz7Dmw7DkjW+2VPPSbGph\nam7w9AuyuoRHATG2xXtz8Y3T2wYLeNrrsFkZ2LsVSqOMY+ivt2EW1geL+dg0\n5XLTFoH6RSvhQ/50xO3jIUxOW0enB9FANwWZbd9FpoKvseTKkhII3LoBMDeE\nBsC3j//vdWrFwnvNRykDBVzStsaUdmG1lJgDK85mW6tn4/tW9+EIJMgZG9pW\nqw6X\r\n=eXBK\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"3ee6b17d9d0e5447e7f277ffea207f7e07ef7b80","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.6","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.50","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.46","@balena/jellyfish-logger":"0.0.188","@balena/jellyfish-metrics":"0.0.217","@balena/jellyfish-environment":"^2.3.15"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.0","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.13","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.71","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.56-renovate-non-major-3ee6b17d9d0e5447e7f277ffea207f7e07ef7b80_1609998236621_0.34712311035118293","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.56":{"name":"@balena/jellyfish-core","version":"0.2.56","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.56","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f637ec7bd4234b6de6c214fae8e6539ff7bd2cec","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.56.tgz","fileCount":95,"integrity":"sha512-puMiGf9SpExnQpN1s8TyNlv+MI4qYVH74aQEDrltMlbXDU9UymYTSditEUre4+4qY+blslKH7Vd7NXx2gwFjFw==","signatures":[{"sig":"MEUCIDqKsch2ehD2CpY67DK1NZerJZxoalNBlFyBwlfJD0InAiEAtJXnGvb2a/qck8HSAVQb1IQU/U1fveWx+hEXNrQzeeQ=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":578317,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf9qECCRA9TVsSAnZWagAA1Q4P/3MBYs2H0tBBcv754AEE\n1PT+achpcSepXAay+Myfs0WsnrAGtjjQ1I5wDkAxek63wQSMaizX5E3R3Zx3\nKTLevjwIUmFlv3J82EvlEM2KGhc6FUS3d2UFLf99YkzOC4/fkd+XjB+2IbCR\nvOMQ8V95gd4pj4sUcyncn0hvlWmvW/TBsT79CIASBDtWMBScjtM+J2VoGY0E\n14/ibZ28SELDsi8M722j/dEQuP8BRmgFxzMHj8ueVoLW4rKK/kpOF4Ozeypf\nOieSabhwu88DpY/49ECtvxOnmEN7iJaR3XnJUXknt+nV3ayJ46W0orpWS+LC\nk2Xif3MH0l8dn1LMU572vi2v1XVzu7NQa8XjdPeMkxzXUQnK3zIdhOzPp1n5\nxB8RnYExGujC0UO/HxqubNuY3XoaiEIiAkVqK4312LVbuzQ6trxYv8H6pU7j\neMffL/6HbijVZzKHpQKWqKkIUWem8B9Hng4sv0DDhpOet8KNl4G+RrhKKpbZ\n+6H8VKxBbZl2n36Ml4GftR+vd3NeDVP/xDAeBQXcaGIFbtiRbAJzdUWo7V7P\n6qKgkM7UXqvU4wD73EMsgKQ9XhLJjQvFGtWGZtGf9rrMFu2W5ts8e/W8ykbM\nLhqd00SbqZDBTlnRolvBjimlGWJ/3DL8pdgDnqbXx2g2wZARHr9qS17i38Nz\nQJxD\r\n=T5nZ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2ac4d10b4304c1d231c837bf6faf40c01ff5e84e","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.6","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.50","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.46","@balena/jellyfish-logger":"0.0.188","@balena/jellyfish-metrics":"0.0.217","@balena/jellyfish-environment":"^2.3.15"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.0","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.13","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.71","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.56_1609998593589_0.9717809869673917","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.3.0-patch-transaction-2439e8a823ec0949f89c979680505c356d0873ce":{"name":"@balena/jellyfish-core","version":"0.3.0-patch-transaction-2439e8a823ec0949f89c979680505c356d0873ce","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.3.0-patch-transaction-2439e8a823ec0949f89c979680505c356d0873ce","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"49fe663107fcedc0860192d65bb77727b7b40e4c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.3.0-patch-transaction-2439e8a823ec0949f89c979680505c356d0873ce.tgz","fileCount":95,"integrity":"sha512-uDJDAKrwz333BBAf9q//P7gIlIS6B2aDxdreV8oeeHl/wDQOF109UqRHEl6Eq5DSN4VQzoako0P3z/t7peKNYA==","signatures":[{"sig":"MEQCIGbKe+pdoj/L0sqPOYVVpxKrV6Mq3QFPOF4p+w+XszP5AiACqBReaL3JJfvnsL3lKoltXiF1o8HeYWVAIiAdQ8QZQA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":580502,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf9rI5CRA9TVsSAnZWagAAMpMP/3ngFW8RdQWw5AfmsIld\nQFC78jtvcRBdZ8u5PkrkeF73Ieiw0tCD+54FKJ38gtjKAbtpydaTppfH1HG5\n4hfL5RiPJOvD8AOKLSofyp0f52PFy66sNCc3jkb/EK5ITODRvS7+7xbJ+vnq\nGJLsh9+gZDKBSwTu+ul/RUWOJf5q+DLae1VvrPwLK8WO3+9Zpxd5HMRFIpEu\nmwXP8YAKuCgD4HnHJQ1nVLbFGeCM6h/DZvCKrOqvJwHXako0ELIhvzHw6/fe\n8fu+hHO0Xn+xs4W8AaXKgXIBs6B4ZuB/HWRfUBJSZrUNOtT+KpOLA8yeKcE/\n2zD1+bw/X0Vi/lgncIxfjqEZWZjMd3BYx3vXT5qvePRo/72hwPTKuym/7tpv\nCkIoDFJvAr9rcTpuvwUb84e0cnO71ptR8e8ifMM/wcNe0yoaN9U1yftOWiVk\nljzrGlj3aTlX5PZTSPWqEBiQ0ibJ9q55IsbIttWX9huL/4mzWJaDZbr8PNDP\nZPZj6kyfMS5IbmnOedX5b7wsdUDjvs8h72T0IozO4EL399uqb0aGar5dHsNl\nZKyMMOjBFoZDV6mUl5T2Mry+l0zuV/LAoh8Ii5ASM70ZcbVfxT3J+7n6QPmT\njobi31N7UsTDkWAYWozdYpUB3NinblO9jURFO4Gq4irSQtSQw6M5gUnn5o+l\nv3PW\r\n=aj9g\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2439e8a823ec0949f89c979680505c356d0873ce","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.6","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.50","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.46","@balena/jellyfish-logger":"0.0.188","@balena/jellyfish-metrics":"0.0.217","@balena/jellyfish-environment":"^2.3.15"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.0","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.13","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.71","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.3.0-patch-transaction-2439e8a823ec0949f89c979680505c356d0873ce_1610003001320_0.08296224981482991","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.57-renovate-non-major-df9ff1cf27b480856dea1667b0d4f5a4e6a976d4":{"name":"@balena/jellyfish-core","version":"0.2.57-renovate-non-major-df9ff1cf27b480856dea1667b0d4f5a4e6a976d4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.57-renovate-non-major-df9ff1cf27b480856dea1667b0d4f5a4e6a976d4","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a0f3138361715d93bf05dde1daa9b9c4b5227a10","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.57-renovate-non-major-df9ff1cf27b480856dea1667b0d4f5a4e6a976d4.tgz","fileCount":95,"integrity":"sha512-wCZ08y1S+6uHHLyATQ4nYPf4+bLwRwK3MftRsaqjFzJ+rF0RuIkvfpLYCYpfVmUm9UU+lhvJpa9TLYZf+KNBjw==","signatures":[{"sig":"MEYCIQDzBck+D6d6z+qZ0eYrL28tXNadaV6MwPpBDr0hqCsl5QIhAKRYEe4Ookk9k/Erlh+UgAqxfR6dEmUbWJybEOSHf0tp","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":578443,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf9uByCRA9TVsSAnZWagAAT84P/1ckcRTh3Up5xVCwLKx4\nu3KDGftkHx4r2sW9QGIYfAiSj3FHtx67l02+mLurYWa6XectwUibmfgnCg+2\n7a6elqQ6GFlqkgk9pnvw64LVN5dBB0zXrbUdcKlx96hG25j/248/hrOwzwu9\n525i8YrbH8la1vr7tq9SVhwKf0Po3LZzr6DFZWkfwi2u/ZOB7lHrufzVVRLf\nk2K499PPTN9w7C0kmnqrVMkLtrmt5FSzKHLV6Fbh490D5RRmVb/1S2pe212/\nlRvfZhIkhaSws+bjp80DSVaXxwbqDAqLx44SFKykKbibPACKEtDnKOboabci\nc/E915Ha/WMoXMXJnTMveSUXjfktDTJUInIEQP//KirDsfiS48j2pjjMPvCq\nXzoRNpZ1573J/uxa4n8IiUG2YxUAn49YfCLHboz5Vg4iHwNh7+3AILIV35Gm\ndpBWQjO0SZI/fprjsuPgjA+N9Pd0XLJrTw8BSiW+TRQZoRoko5OYwiDPzXcR\nGYqmqt20DXAqvhYTUz/k5dWx9o2VhinbyC76g1vb8H30iw/yv+LagCADFrOx\ndBCejsHabCV946V2RiSZZU7weduF5shkwwSElGFo004DME1uS/a/UvKA1Osh\nMHNq4yJPHEdVen+tJgRYvTOr+JacUxnypob2yRMs3W3GAO1gfTIuUwtB7OwD\nQF3a\r\n=pq7M\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"df9ff1cf27b480856dea1667b0d4f5a4e6a976d4","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.6","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.51","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.47","@balena/jellyfish-logger":"0.0.188","@balena/jellyfish-metrics":"0.0.217","@balena/jellyfish-environment":"^2.3.15"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.0","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.13","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.71","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.57-renovate-non-major-df9ff1cf27b480856dea1667b0d4f5a4e6a976d4_1610014833823_0.2988986909558966","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.57-renovate-non-major-0345cc86f6491892f4e1c7aa166da7ab55398c3e":{"name":"@balena/jellyfish-core","version":"0.2.57-renovate-non-major-0345cc86f6491892f4e1c7aa166da7ab55398c3e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.57-renovate-non-major-0345cc86f6491892f4e1c7aa166da7ab55398c3e","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c50847c3334bca1cc7a12bc666476363bfb259fe","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.57-renovate-non-major-0345cc86f6491892f4e1c7aa166da7ab55398c3e.tgz","fileCount":95,"integrity":"sha512-nF83x9D5gZZI4i9u6wno65Rz4+9JLI1cp8fsCepJP4KkXf1ACpIMNx0sXrzJ1iaJS5x1O7UmEHBPkUhGlq5JgA==","signatures":[{"sig":"MEUCIGOqzwAwUVVavzITF8MDDDxs5KoqFMFEdTLCQxC/ogDMAiEA0QwR+r7DavI/xDww7SixQKiLc1wAxwEsf1oLZL74dDk=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":578443,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf9uF8CRA9TVsSAnZWagAA7mwP/ReCmZ7Gn9nVt+x3lu34\nvItMAfY+kXQI+8CyE955ISRwQ6MV+LJvIHC29wKait8thh0EEegWlnXFrOyb\ndfMF+nvnACQ804seeKyfhsbZ1H3HHn5zyUF5jgieCy+LOKiIBLjOv72Ap/Hz\n25s9IDV5hS9YEPzzH+1KrTPNe3cvCqfX3LUjWSsAzLi9Y6U3TiyDrzQFDdQR\n950OxY2HlmxBwzPMTLfHQ17Ct2OdRDDzzO7rrk1LkkcjCle/hE+OLuy7dE2N\ntG47qITVOL5M35M/qO5AY+PSoVOWnz3xYY1vW8YgC+PtvtyKR66jwlqJV1rU\nx5wnnzmLJT01vEMS5BxxZl5KF/ImkLjqgblNkLlNsqE4cuS688znZVi94WnL\nIWS1w0k02+qiBTP0TXo6CUqqzmEu3IDs2uMgVkZ4VjHGkhT1dS0PY6sLh6AO\nxRqNPKA5SRTTqO7UO0UZV284rCwxzkuXk8MAIP3ZkksxjZTYQlpXEK+c/vuY\nlJdG2WaRuOSR26ckcxJU5qhDigR6aCmmuaoH4IvdOqwXb2EigLBZ4DXRDv2B\ny0PfPZCvDf0weGftOdRu57Wm1cMVT3gTRgHFVuSZYVErNjM3v+O0B7/5xVsz\n+ZKGYm6JcVEwoFDNsILuy21iXmPegVmVwTyZ8Ire46ZOgnCzKrYWxH1vhRbQ\nctQt\r\n=D94o\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0345cc86f6491892f4e1c7aa166da7ab55398c3e","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.6","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.51","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.47","@balena/jellyfish-logger":"0.0.189","@balena/jellyfish-metrics":"0.0.217","@balena/jellyfish-environment":"^2.3.15"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.0","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.13","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.71","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.57-renovate-non-major-0345cc86f6491892f4e1c7aa166da7ab55398c3e_1610015100212_0.18786425714954613","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.57":{"name":"@balena/jellyfish-core","version":"0.2.57","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.57","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e5bc1a321c3c4bc008db29e887b0632afc6510fa","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.57.tgz","fileCount":95,"integrity":"sha512-ztjgxe3G4wxY7MP8QLXIZ75nfc613HPSNQZHsKQ2xxa5X1+7+iZlrWo20vswl/vSLR3bGHQ7MSDB6bL8c+nMdA==","signatures":[{"sig":"MEUCIQCrCqQgI9i39aRBiYQc4gXTxORqAE7CNoc84wE1VM+jGgIgDGfiTvXbUnFSbu2fx0uxLJqh4vUNj2+eODuGE0bC8Mw=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":578383,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf9uKhCRA9TVsSAnZWagAAM9UP+gPfrMh2hhChLr72Atlx\nUHLCLvsFm/bPpJpyvV+j+FsXWXbHT6Ql67ma7rCtUj/RH7TQ9fgU6ERK9YtU\nmw+whjz+ShXQgSv1YkId7hVfs48uLyXRDKOkQZpYcYdYGUq40sewJNK+gd/d\nDfckTptr/x63P0j8nTGxvOiVz9E6Ye6clwZqKjyCt+9KlQC0DoLysSL8I//g\nlLti/pIlUCLGbOFAeetREhDF3WUqbm3uBa6qGYfPLP9MdfgaQ0uDizd6TQLP\nmJkT0g99Joyk1Q9jBI1B/bNA7w21ntV9G0GMWDaJTc4ag1SuY2BcCQ8xgJoo\n/X8ryk+/YfiR9I/IDpDYI3PwFStM3pOy93odS2GSl67rT+Gbec4J1IxTCf5s\nH3vzfXLYKPqUBwpstqNUXGDZp/SYM7cuUWwRjqzZvEoM8lLuOdmimWKltaq2\nGQiYRe3zTWLJ8cDYYFE2aNhLfKVZexKfFQaH4hPlZaoOVVDkq/f+klMiReuU\n6YK8wp0iE8z87cBtHHAGMq55N8kCAKS3uNBex8VH5vZkNi3zfCokUx5KHuNO\nZlVGXxb50P4VKyeU7HdWMuZbNPpeFkmdRQH54ixuYS5TFsIl6vJ9JDACu9IB\nca+/cfQCvDvFi4jOTuItZ1Npstvzy0fWoaajzBRyuR5GVwgtf0oZOu2UJFfK\nKwr5\r\n=ioUo\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e7f35475485831dc7f592f0555f3cc8aac3e25ff","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.6","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.51","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.47","@balena/jellyfish-logger":"0.0.189","@balena/jellyfish-metrics":"0.0.217","@balena/jellyfish-environment":"^2.3.15"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.0","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.13","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.71","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.57_1610015392873_0.49510149588358665","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.58-renovate-non-major-7a64530763c4479985be14468e926555da713cd3":{"name":"@balena/jellyfish-core","version":"0.2.58-renovate-non-major-7a64530763c4479985be14468e926555da713cd3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.58-renovate-non-major-7a64530763c4479985be14468e926555da713cd3","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b86b098fa79e097dddec68d714e954f3758da4bb","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.58-renovate-non-major-7a64530763c4479985be14468e926555da713cd3.tgz","fileCount":95,"integrity":"sha512-FrQByoSKisabQUWHbQMUDIh4s9HnqfY+0B5Uns4I3SSIm3EyJTxa1HSCsHCDu7RSRMT+k7MPUJbtR9ujhazvrg==","signatures":[{"sig":"MEYCIQCFkUdFF7AzI+iDl9nb5VuyP6uBN/Xp4iLOuUdJb/i2SQIhALQE0FeUSlTxuAkfHTrFtg8UPLILzH/GN6uOHgU6SEmW","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":578548,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf9vAzCRA9TVsSAnZWagAAkW0P/ijUrFtwHYMqIsd2qa+0\nBXvNqChb/DvstRCpPQY7xBlRAs8DtE+yEXOjK8Rr22AOEKHUWGuezl0cuYp5\n/OWItkYNiaHN7IPTCThD+zRKRaFCVUXpb8pR2ZCPOAXs7QPSyfvjYJbp+bvX\nCu76FcNqgyG3lboa0jsQl4H35tNh59ubRR55LP6YXcAcxmyyXvEi0se5fNyQ\nxY5PSZC62bMJOsbeFD+bTiojcitK3IdjLcEux/OQyHoHwEm/5JZxzUM8tiTV\nsZ6+e4kXrZTy1eZCPbAeiPDj2y320ukcwIJnVV8A68V+f5LkpIa6ni8aOICl\nY6+OCBNurQKwebAPwrr7UG/ItHPSnS14JzK+fYpFUiNxsYQj4OMSwLyFpJeT\ni8KgxpKxUQRTdS/sLowvYRO//ynyZw5CNFfqspA1IKqupwTttG3NWruMMLu0\n4Cee8gs6jsRp8gVJISTrDnnguIUKGequI6TYEg59bmjgO4dgICZB3bUx8BB7\n8hieTin/+D/F6eLc37hIoRAx6PGW96ukfWZMYQUUcIgFmzOg5pHGuJiAisAm\nTaFzwb9WHaffBn0dYf8xv2+4QNlkLk51tf5iw3SO9GvzORthTv7JjDhm9fz8\nozNK0IFK/Mw9P3HUbbxbJ2lxZgNm2fW37TX1nKwqjkOl3ONKX+Ld9MIykNHk\nd3Oj\r\n=pzJV\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7a64530763c4479985be14468e926555da713cd3","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.6","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.51","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.47","@balena/jellyfish-logger":"0.0.189","@balena/jellyfish-metrics":"0.0.218","@balena/jellyfish-environment":"^2.3.15"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.0","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.13","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.71","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.58-renovate-non-major-7a64530763c4479985be14468e926555da713cd3_1610018866845_0.25942806232180926","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.58":{"name":"@balena/jellyfish-core","version":"0.2.58","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.58","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"281ab50bf036de1403100d8b1c186e9a14ef769d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.58.tgz","fileCount":95,"integrity":"sha512-Bq9uWgVuVOgd0kCIifD2HFsSMCnPwVMEjh+7Lw5SNk1JCkPVx/H1HpC3G5LYHNUdz1L0SMUKcK6IDwKhEZWDEQ==","signatures":[{"sig":"MEUCIQDZRjQURs0iBKXRT18K3d4xyAGnnBcHpaDIQ9WATNATWwIgK+Twa5Kc4p9B+ZstMYgGXGyrk+YjPagawWmV5GHGdvI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":578488,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf9vF5CRA9TVsSAnZWagAA4UwP/1VD/LfLIDvKWyOSLM0V\ns0ZBUUX4Qk0GLmLqq48U6Kfax3dkIOHH7gO6lfp8evnRLwqfARqPze+TDt62\nGKUHIt7W0CRCHgc3NiYMgk3HKJ7CnYWK6ibHAA1fn/PTpK8oTKDlVa3rilHU\n/nZNTwIvzYS7GYK0kGm/+TDvp3N9IOItouDeLzn20aYd8teXlorA712vqbjm\npozyN+JlvxOV7s8dfcfjKwbVh0rvVH+VQxQnv+C02Em9Sgil7S3iS1WUFm4w\nRYElA2e/Li8g/LnQL1LLUgWBqUAMNSSL770A1sRf9PMOyxIvGO/nsus2sBe8\ntwHFQYiGYBjHdmHKPga3kdd+UkyOLmmdQv80tXfvSF1jVSXdEhZMuZ9DWV3l\nVCnyrvCI9GrNWXercZl4kIrCyaiGqpP9bgLLvGOswInVln+4u7k+pQHRTxPl\n+stoXBZDylsSlZtygNgfqkVCbSxoNEhJnOJa1hP0kuGDMrvbCUoqEV4mtzGQ\n7F9qGT0BZ2O2dwRdgbAJzyK10Az16oryJ33I2Z5V7Q1VLkd4DPxHQdZez5tQ\n2xGf40GXhg1mB5HLeZtBm6LU4n/ylJz2CtTLkDq3Ku0Ny1SMA8OM3HkzuIDa\nc28VSyc/qsykcpLY0urY1zsmp5BxhmM344csOY31NKoA/NrQTRnEbbWcRm/h\nNe3s\r\n=IkTK\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"1bd202e6c844933e1a48c360ccab20d3f4c72479","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.6","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.51","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.47","@balena/jellyfish-logger":"0.0.189","@balena/jellyfish-metrics":"0.0.218","@balena/jellyfish-environment":"^2.3.15"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.0","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.13","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.71","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.58_1610019193390_0.9099622595263297","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.59-unlimited-connect-attempts-c84d032356340526462377ce0363eeca07abf362":{"name":"@balena/jellyfish-core","version":"0.2.59-unlimited-connect-attempts-c84d032356340526462377ce0363eeca07abf362","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.59-unlimited-connect-attempts-c84d032356340526462377ce0363eeca07abf362","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4e5113867ed09e97b5c6e1e72724401b4d67828f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.59-unlimited-connect-attempts-c84d032356340526462377ce0363eeca07abf362.tgz","fileCount":95,"integrity":"sha512-a5gq0aMNb4rpRccIFAwnlORjCbe59Xa8yYOPeXq1A/gyXt4fEl2MRu7wnD1EK6NZVnseF9kLT/zz2y0pHv3cYQ==","signatures":[{"sig":"MEYCIQC2vchAeVrJu6jkw/fzubzxSljxdvwb4LC26/l0AGne9gIhAJrCKzjIVgk4w+b+q/AHJpDE8YQUt1qjVMEmqpOB1rIz","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":578564,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf9w4ICRA9TVsSAnZWagAAWWsP/1hPk/yczBONbMq8VvIw\njNbKgp1BcW0tEiQR76YfjzDPyhr+Y+9YaoAkBEx+eUWoBsbzb9mAKLdlfu6i\nBAHL9bXwBJuhhPy2rKGfrULO6I+edbIwi5pWn/XVgGJKCirgEDbDMbL+oBMQ\nkUw8G6L1gLeyAa0plnUGiV5ZqPTlcgDsB/k9bEMqTkLk8WUOkZeOVcXkcrA0\nyCGs72hsOw9sZmXgXI7IY+NJz4iydXa2jK2/UjZtvPjxdTbekIdUR039TgMu\nK2QwMhcA/P/+Upmycti9aq+/imalwC22EdbrH5gRiX0lirgUiSt96N3SKmmn\n2UIj7SaG/QuwXw124u6B4y+o0se0XUxJExpwBvJ/WF86rscvOmYC94kMRlKT\nxh373Ycf0c/G+W9GpM0ih5cUojT2rNBN189feCo3W7S+fR1KnYa7cqX6ztjL\ny7pRp3X10/tUmgH9jJWYclJ3PDQ5hdsXzrhHGQEwmD/1WcCWHmJfDGRF0ocr\ndO/Uo7od3QhqmKkNN9zNVOOtIHtKVqkQW4mvO6xRyM1ZT4+U2bEQn+IrFYcq\nx9DtYTfcvk5V4bZr+ysXpbgt5NUnHCIhqg2x1tLRvSf60TVbfMUMLpjzLgvc\nLQGm3LJyIy+eKitZzeeP8xLI21+KVEE9exEDsZcV6IG8cyUbn9AS8CPkoPvb\n5tGe\r\n=8OHd\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c84d032356340526462377ce0363eeca07abf362","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.6","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.51","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.47","@balena/jellyfish-logger":"0.0.189","@balena/jellyfish-metrics":"0.0.218","@balena/jellyfish-environment":"^2.3.15"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.0","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.13","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.71","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.59-unlimited-connect-attempts-c84d032356340526462377ce0363eeca07abf362_1610026504022_0.5747093300573711","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.59-unlimited-connect-attempts-f79d569a8f2160cbeca9732e2e9ff059f7da9a1e":{"name":"@balena/jellyfish-core","version":"0.2.59-unlimited-connect-attempts-f79d569a8f2160cbeca9732e2e9ff059f7da9a1e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.59-unlimited-connect-attempts-f79d569a8f2160cbeca9732e2e9ff059f7da9a1e","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3b91ea26deea85f30654f83a34c3a18eccabd16d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.59-unlimited-connect-attempts-f79d569a8f2160cbeca9732e2e9ff059f7da9a1e.tgz","fileCount":95,"integrity":"sha512-X8Tc2Ll12/aHm5ziJK0IbX9A2HddJ/jMmIARLVkjbGgx601X/WKO2D7obx8IMJ7+jS/Q8ax0UHioYSnqDGTDoA==","signatures":[{"sig":"MEQCIDVWtTvzE14JYqcFDIo8l58MSXoQpYS3ytzHCGeB3dtKAiBrMBnGyk/DA/dubyDmvlgRj0B9EV9iqgkpwQe7fcs/Mg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":578642,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf9xBICRA9TVsSAnZWagAA5tMQAKNgyypsvLCD+/vD0FB4\nw7Udf/RCSvM4lAOPrOpmtJx/lJpXOJ+T2VVCTImvxtnl/EaeVACmoi/VY76u\nOpz4no+cBXtGEd4XCMQbUsDAT+nq6gVe2Ddo3b1H5NT7uVfopY8pIXTcyF1V\ntEIVZkU8gcLn9j6NfTcPXqmbTKas5nqnb9DL2YpZ2ac7fJ9tKZqKE/kjazri\n2YjQvcSiKVKzrQlUtESQIu2vXuOR/rggSHuZCA3ZxNGzELFNTIYgie2DjdAR\nO8hI3PUE7rQjzfIDVSzoXzasEyVdY/YR80KgCkXp2PpnvXiohjwUSct5d/ZX\nsd0lbtBo+8OzYfpY+8d4NelDxqlHOrVyMfqAY+eiTBuzC57PBrEZ7K7UwfSv\nakWTp8oJAqjQkXnepW+syglHYdSESXGewTO3uNnaqlbQ7+C2X3i/QygMtFhD\nfqz0Amn3QcD/usDSCrRFSFyXEs4XP0hn6PWoAwgmDkSqzfF9XFV6ODFVUzoI\nCm8VbfQLxfHyNYZ0A2ifYmaUjix7vxuZU6dc5H/gPE4nbRnmfqlTmGbN2mdf\nPYGDuafOiK+Ee03jhvqhg5UNiX3FX2JgKjwLlU60j2oCJjRO8nK111nBYYgz\n2xuqsSCRxKsWiBLUVzNCEUsBRSChOq4KnuHerfFCm3TRIstyXiCJC0+/jcUM\nd+w/\r\n=txwH\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f79d569a8f2160cbeca9732e2e9ff059f7da9a1e","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.6","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.51","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.47","@balena/jellyfish-logger":"0.0.189","@balena/jellyfish-metrics":"0.0.218","@balena/jellyfish-environment":"^2.3.15"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.0","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.13","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.71","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.59-unlimited-connect-attempts-f79d569a8f2160cbeca9732e2e9ff059f7da9a1e_1610027079477_0.6444772738748208","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.59-renovate-non-major-3995d98fee38689d402cf8fd2f1a7d00cd6341f8":{"name":"@balena/jellyfish-core","version":"0.2.59-renovate-non-major-3995d98fee38689d402cf8fd2f1a7d00cd6341f8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.59-renovate-non-major-3995d98fee38689d402cf8fd2f1a7d00cd6341f8","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"aff96fbda4ab7f169c1de5c1ca5fa192804092ff","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.59-renovate-non-major-3995d98fee38689d402cf8fd2f1a7d00cd6341f8.tgz","fileCount":95,"integrity":"sha512-vo1no/Yt8iz1fvsxyFy231INMQd4myNvqvXlH9hpHbytrlHt73D5LR4c38C+J3FmfDGPE4ehc5BHuK5GZAHNZw==","signatures":[{"sig":"MEQCID2xgF8bom5jApmrzlAc9CM1cq4hJnAG6V12uRjnCfFFAiBwYlnnK/B9WvS+CZnG/FMGEOI2LfcGU81wLoYrP1Bm+w==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":578637,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf9xPoCRA9TVsSAnZWagAAalAP/20tBeQjm8iOtiyD6ebu\nZ7LFPsL+lWTWAbUIp8r/Fv/N2gJziKZ7LsUsLCI91LT70eHypevl0vc9BX9X\nM2509+xLWSyptY9iuJM02ZXzWlCN1ruT7YO3wRo6HvMsBJRZVXACkCCFEqZm\n9kN1Gaz16+4hbGRNwJweengXkmcDQ/I7f2eIKegMnD3Fm47L1eFu+99eX5Jw\nrp3+Ivd7HzIspDHhlwpNdug99hDWX3PTLIGg65pBNfFYkM3n8ipBGx4ozHtC\nF1sTovolXlS/juutnvIVAFE40RhIz030/Xh32ltt+IlAIll8pkFvvVAoVLmR\ndHDG+Gvlge55ZtP1Z3RQRqErJBHkdpBZ4ud/Bso9dz1PihrXucFNhysr+v49\n3sa5TyKJA6+BJk+UiDci7Y6JVp15CL2Z81yvtDu//VxroWqGGQ6/8O3Pa9jZ\nudJ4IRIlxj+P9o/b/V5O0WmypxTJC5HrPcKcmxHYA87bdhMGnGYWCLtn3ZVA\n/b0oAdu46r8pQaNuBo/ZrBxdoBdFyLXlTMD+0d/sqDz+DHwfm3PVL9bXK92b\n5YnfkbuHTmXGrD29M/UWCVfmQl1W5Cs+2dQheLnI18Pq6wT51PYbO+cJZ6RZ\nGt07Vg7Gfdp6uiRMnRbCRqDIXCPsUN52+KgK+QVXwFUHG22GGKWOYG8Mh3SD\njzY+\r\n=sTtG\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"3995d98fee38689d402cf8fd2f1a7d00cd6341f8","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.7","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.51","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.47","@balena/jellyfish-logger":"0.0.189","@balena/jellyfish-metrics":"0.0.218","@balena/jellyfish-environment":"^2.3.15"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.0","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.13","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.71","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.59-renovate-non-major-3995d98fee38689d402cf8fd2f1a7d00cd6341f8_1610028007607_0.8161019565491792","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.59":{"name":"@balena/jellyfish-core","version":"0.2.59","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.59","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5040068616cfcb90709c9ec2e1469120deb98370","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.59.tgz","fileCount":95,"integrity":"sha512-qHJqDMYewe67UulOq7fRZf8OIq6dM/t7MqoskR/8aCIa1CkL5EC3yTKqI0Evt2h8mLw8YVCo4Af3wgzIOr476w==","signatures":[{"sig":"MEUCIQCzLcGw2FJzYYbeq/2Coo7g6lcbMYfA/ccJEO61kazhUAIgCgEg4Vy7vD6dwYMmVLGds9h5xvTTOnhK5hz7Xjh7u2Q=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":578577,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf9xUMCRA9TVsSAnZWagAA7pIP/A6IlNRijN3xzEiQ+s93\nhovCEzT/sijz68bRPbegRMuKQYGqVrAIPeNHGmT+y6Pt5hz49XkYicQBql10\n4a88gcJkst9ktpBtxdWoNyhgaSWMCSlWY4YLeDU2726QvOEU6aOE3RqUL5Qv\n7CepQ62tfBI7z+GGheozfVVZXI33Idjywtt2HxMhSPE90Ry4x2Qe5vqEnXii\nhTsRs4TemGXFAVvP1D+zlTjEEADNX+CqzhLRql3Io5MifdvWFhbwGmkiwvGF\nNTRe8B84gmiZUPQVl7+h1tiEXQTGXGhQrA6MwCAlGNKRmv/WtCfWtIoVPEMM\n6KwbDdWRcrLTZ/G3al4xiXJ2iHAc0h+ERP10wmysIF/vxzwTgJBA8f2v7GH7\nCU21385gK4s8WHYuibX+eHfoG1sq3457KZGe0ukXsSJG467DerM9HARvw3N5\npDw7HLX1s+1RkFY54bGI1fuRMVPRcNNSLrggmLy1+mEgF4WBNPljzypsDzl5\nMRDE9jjXryJzqcHjwyWx6Tc9lHn5wSiRRN1tzQP4ePUaAi0ube1P4KxWdTEh\nBM01SIoTbcZlXW/YURyAknjhjqtOjcxysaJIdlcNhe/Xmb7HyWnmPWocVsHJ\nN/h20QfCbKu0bth9dLyN+OQJu4iRetfsnlXMxMj1dq5+pu9IwDDbzzJe7cVM\nXkK1\r\n=fxxq\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"06da8848e3181735ac96a6fedf3e78737bd02cc1","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.7","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.51","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.47","@balena/jellyfish-logger":"0.0.189","@balena/jellyfish-metrics":"0.0.218","@balena/jellyfish-environment":"^2.3.15"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.0","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.13","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.71","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.59_1610028299968_0.26180144224457735","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.60-unlimited-connect-attempts-5d8bb7b8cfbdd65523bb7563a8d90be9ed9d5cbc":{"name":"@balena/jellyfish-core","version":"0.2.60-unlimited-connect-attempts-5d8bb7b8cfbdd65523bb7563a8d90be9ed9d5cbc","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.60-unlimited-connect-attempts-5d8bb7b8cfbdd65523bb7563a8d90be9ed9d5cbc","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1dc420dcbfe5b1ff01e4143d2838d1a210209ccf","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.60-unlimited-connect-attempts-5d8bb7b8cfbdd65523bb7563a8d90be9ed9d5cbc.tgz","fileCount":95,"integrity":"sha512-KUbEYOeeN2wV2adMdR2+4fsAPgwLP+PDJbpwJhtPqkB8GBaIqj87+Ftd9ik5LEWTeWT25WIhO8aaERYRHz4k1A==","signatures":[{"sig":"MEUCIQDug7f1NjkQhEioXWjeLupEpsI45HnIZ/ns//wxUyI/UgIgHmOLLeShTQR3jFXxcZxdxQat59r7ndGdLEWRfLRSqTE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":578731,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf96eKCRA9TVsSAnZWagAAH1EP/0XYJDNSEV0+V51OVx6L\n9o9c0sX28x9wfh9Eh0grXp91FjM1mPqSWIzKH6enDWJqHFULKGdbGwrE6cLl\n3hDcpdxP9iiy6WDNosmI+JdC64yGW5ZqDY9vR7qMnBgDpAD+C3vkCkajgtOH\nN0jyfFAHuNVtXumaOTqsv2rCPGapm6lp/XIxcLgwhOM0sc10hVPD+ckY9GRk\nqAxn+HuzQxOdh1DTW8TAs/gGVEOiSu0sO9Vc41ahc5B20LgmBzXpUIr0JB6e\nT++xkJmjyxCRS4ucQFR+n3Y7T0NUCJJdumtGCiVmcCc45HKvis/qhhhwMYXG\nU924OMP7shFmZFND6ridMtxV9T8bM0T+3ZOlnwRPgPurrFm8Xgy46beZU1QO\nJHDsdNsTa6qlCTUCzWk8sE89oF4v5R16DNHR6dpCl1p109MxrpkZwKv1bXsa\nCRiKOrY1/HUHbLJCyMqj2eABhfWyhhWAsxXRqOnS3E3j+q6VsieXrgLzwVwi\nZCpg8+GgdXDlo8RY0ZzuGz9von1D3Qww+GsxuOPeHe9DnhHpDRTS85Tfwh2Q\nILL6qQDMaV67r6JIF06uI2yo6ozhQEG+hKD3ElO6yMl1dR9s+T94UZ4QAxfX\n6vRGscVzOM5Q/YNLqYRWXsnGLwp+m2lh5yNhDie7l+azUJsnf2R3tcsz/dNW\n5uSD\r\n=/g3+\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"5d8bb7b8cfbdd65523bb7563a8d90be9ed9d5cbc","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.7","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.51","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.47","@balena/jellyfish-logger":"0.0.189","@balena/jellyfish-metrics":"0.0.218","@balena/jellyfish-environment":"^2.3.15"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.0","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.13","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.71","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.60-unlimited-connect-attempts-5d8bb7b8cfbdd65523bb7563a8d90be9ed9d5cbc_1610065801607_0.3860378847116437","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.60":{"name":"@balena/jellyfish-core","version":"0.2.60","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.60","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1fe28dc09c7f9f15428229559e731a069b6ecbe8","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.60.tgz","fileCount":95,"integrity":"sha512-+ZOYkTdu6FBJNxj0f5nrxuHrx43q1mFfGzBNr4BdOYsCb48+TPpTPdi39Xc6QTG1QGyaY11dbiDvmRSyCWul2w==","signatures":[{"sig":"MEQCIFZ//UFhtiHRq+HkctsDQrDdBlH6BSSodMjgATFBf4QlAiAEC3J55+BZzEx3s1hEDdfNYTYtYZyDEO5295bKVEHuCA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":578663,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf98K9CRA9TVsSAnZWagAAvBUQAJ90mn01GF11VEFSl1BF\n1ywVOkk8RzdQ47ZJyAbdaVWVeheP71ox1jyyLWqDmf34nQrY2EA0pz9Gol06\nMWWCCxbSh/Ux40x4BORW5gz1XflO7U2Sf8RsVWbULNtf+J3NbmgWk6TaH6Dc\ngY3U1UPNpC3t432mxfuwPsJncS1u5eWw04eWxXCINBUMOxjVJGE5a505qmfN\nDZhsFVR4qnrDdP8kKAAj19aLEZWqEbmiKwz0k8hLGdCcji0qLPPmIYF3lCAH\nVkcjAkbj+plasZgs0txc7MnXzbhYvEwB7YmEHk3IsvyTpKUTsV4hP6y3LI9q\nq2+B5XBwCOUPHwNFtDXtVtFOg7WWybwwNJcDNGfxygwxd4fH5iw5zVJ4sJeI\n34GX2HAKF9zE3d7Iy4z+ijSSPM0HNGDMY0Diz8vX50MIvBqKPV/tJ++7biAl\nHi3EcuOfmciQD3NFY1CcAg3rgdMbjsOSEwTy04TqqmmONyQKDsSwFSlpuwu5\nmZfRMd4Qv6Kr3Z6PJi3w6JiVVS5gvCO4t7qol7b/j76g/gZgO2upZzy8uqyQ\nrLDjMDRtpA0rhws9ppZZ97OTVzTQjWIRsQT5f9QJzXIpLrCURcmJFiZaGXrH\naHJgdu4I/wldtBrcz5bGonaqXxcnsej4witLJ21nQ0x1LYo8+LkUWq55OUGD\nZQ9W\r\n=nq2x\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"aeacdde058547d68665ba2928a3dd6034205719b","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.7","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.51","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.47","@balena/jellyfish-logger":"0.0.189","@balena/jellyfish-metrics":"0.0.218","@balena/jellyfish-environment":"^2.3.15"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.0","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.13","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.71","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.60_1610072764707_0.4823726208731953","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.3.0-patch-transaction-4e652b6853d23cb8f9792a55557a6001d064b181":{"name":"@balena/jellyfish-core","version":"0.3.0-patch-transaction-4e652b6853d23cb8f9792a55557a6001d064b181","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.3.0-patch-transaction-4e652b6853d23cb8f9792a55557a6001d064b181","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"547a4f243419f7f3e1445b6a685dcdbbd7cbb0ad","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.3.0-patch-transaction-4e652b6853d23cb8f9792a55557a6001d064b181.tgz","fileCount":95,"integrity":"sha512-NyNeMt5B69me+gjMEB4aAjJ8WjB0XK0gbvm5wC4D4HhJA6omKpE4LOdYaHgYvVpt19n41o0GrIqvdlw6jdXw4A==","signatures":[{"sig":"MEUCIQD5HLi//gbZ7dbbwqaV8MWkUds+GSg3qfrPQ+/tMxhpNwIgOTs2CUaQvv+ZETw0P+wFByNgVH4HR6OEvbx63qEMPp0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":580627,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf+Se3CRA9TVsSAnZWagAAXx8QAJLhsvHACY/L7BvcRZxE\nwaXIiBYRd99ioQSjQ7iyf1a1JzEY+2kgS1vlUFPU5Cx8hZNfgU4b6wug+Di+\nH8qIrivTmXKQxtfh5D3QI9KpMEZMeWwzLNMFPPOiTj4vndqc1n+TCP6aepMc\nI00fK+QhrjRQ+bLxiKvqVRh42OYzaWVZfpxXgxUbsW4oVmorN1kITcdt7odz\nVWOG6dnjkOxoErBXs0I/8LFaXL0LITRK6obOE7r5/HeHNK6isBJlEfg7XgFb\nesyNsXKNsjHBBfDJIP43FytzVqpEbmT6omDi048n02+LtHzU3XQDilhRwCxu\nWcO2vV2GD4bPM3uun9Sd5I5DcmSXV1Pqjdsfs9/Od7QmC+umJKJTeOwyZP/k\n5431s4WpAyyqgK0p+bfU2w7iMWjKa0WUNIenKPK+r16yFR6lLMwRsNEGScmM\nzeBT+aBFlhy2hChix0roOLpSL/Y5d5U/RDnGZGh3R4voEIC1RGtBPdqRvq5j\nx+tA2Q5jGR+8YjWVQGCCo+1xW7Doxihr+mPsP3f4DdHKvjEoSitIQ6fPCAUN\nxG76Pp4DROsX5Awju++cvVJbRtSxgxxlfi0XR5Vlr2KfNbESrYgv/kQJqld9\nFIxYFoPfYAjW6YvqinoxBRg5qDuU3/bHjbkCxn8bbb1REoBN4R3+sP8yLpDi\n8y8p\r\n=3kKI\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4e652b6853d23cb8f9792a55557a6001d064b181","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.7","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.51","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.47","@balena/jellyfish-logger":"0.0.189","@balena/jellyfish-metrics":"0.0.218","@balena/jellyfish-environment":"^2.3.15"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.0","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.13","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.71","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.3.0-patch-transaction-4e652b6853d23cb8f9792a55557a6001d064b181_1610164150480_0.6721728677689092","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.61-streamer-db-client-reconnect-36e27d1384d7a856d5923c56e2cdf1946b846e86":{"name":"@balena/jellyfish-core","version":"0.2.61-streamer-db-client-reconnect-36e27d1384d7a856d5923c56e2cdf1946b846e86","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.61-streamer-db-client-reconnect-36e27d1384d7a856d5923c56e2cdf1946b846e86","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2e649fa717d2f27a630860c740e36dd43b76bd10","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.61-streamer-db-client-reconnect-36e27d1384d7a856d5923c56e2cdf1946b846e86.tgz","fileCount":95,"integrity":"sha512-c7WqjTyDfebTQO8jhAXwcoV+EzvTavyBMDoD/2bt9nxG/mZjcIJIt/o8BhFseMAFcSJ59W7RXW6Z2LS6E5fZww==","signatures":[{"sig":"MEUCIDzD64vcbDiPopN2zgH1l9U3C1ybiZpOj8Owd2V4r6oeAiEA5BBT7vNslJiD7bFnBJCxk7bWsgPjbPHwBOTjwzvY54Q=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":579599,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf+W5eCRA9TVsSAnZWagAArG4P+wducxiUc6ayQr9sE1Fn\nzcPlX33MU53wi9rbY1x2a+IuvmbQq+ToxRk6Wg1OL5m7Cj/6/k8J5i9m2DhF\n0jxbtEikOtg5Q1/nscUEiMLWwQBUer6Mnkc6iDgMV3UnKQ2PpIhlmTbwi/Jz\nVH610QCFcYjA5cKW/vze4gakquw1lWGZ1gRJSAIKKoNWPCft8BQ+ArXLJ1Pb\nXcb5wPaMQMPn9yH5h7tSgSIHTsJzn+9xw3iBfhZh0R8Vod/3PdkUoVTs8zMt\nFWMNLHJekNj3AilW6ozNM7KZOtPaJEbP/6EE4Elr4fRJ/bnObx4g5T4yJDCm\naW8K7M1UPkPcM643Jtlfn5vsymH2TY+f0ox0vfIC2R6nH0k3imv3HJ1HrMCd\nBB1eCRxc8quFI8CC0aagcgPw0K9Xi4996NS3+2zD+P5K0wm9sEKVUatYgmf9\nGRWjp1Txx9jRGP4NziK3nqg+/lUl4CgPRRu3gayVDwJKbleIksZVwfI3kGeS\nljClkuEO/PoPyRu8+aliXre4YcchN6YExkcNIrGogBZstGH/BsxE2NCh966i\nVvEs26C1g3PdDVy/fcX3ZZjiuDO9YlwoUSvJP560yJ2EN6+sNBQQ3w2z++x5\nlmzsjC/v/xQz6IEtVGbkNmgh8/MEv/8azljv17DO0FT/itE4PgdWpE2yJ5q4\nFGE7\r\n=Ssn+\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"36e27d1384d7a856d5923c56e2cdf1946b846e86","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.7","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.51","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.47","@balena/jellyfish-logger":"0.0.189","@balena/jellyfish-metrics":"0.0.218","@balena/jellyfish-environment":"^2.3.15"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.0","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.13","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.71","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.61-streamer-db-client-reconnect-36e27d1384d7a856d5923c56e2cdf1946b846e86_1610182237535_0.22822572072448355","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.61-renovate-non-major-c2d8193d6f16519d44aa55bf633ce00e4859ce0f":{"name":"@balena/jellyfish-core","version":"0.2.61-renovate-non-major-c2d8193d6f16519d44aa55bf633ce00e4859ce0f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.61-renovate-non-major-c2d8193d6f16519d44aa55bf633ce00e4859ce0f","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"66d8c331211873b465ba35f7ade64fbd8c05cfff","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.61-renovate-non-major-c2d8193d6f16519d44aa55bf633ce00e4859ce0f.tgz","fileCount":95,"integrity":"sha512-O15jjpxODzGxGFrqhKIS8S3TYcTAIBotJYPxnerKuSYBre7AgmLwgHSKJhvocMenrBggP73hMi2hSB02c8Ej/Q==","signatures":[{"sig":"MEUCIGkh1DfTaNtFi8MWiMuqtKZ8Ro4ot4Un6jwPr32zzgHJAiEA4FhK5KLqrN5Du003ydg2ItnHRh2paBydsijEZ6wjeu0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":578824,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf+iaeCRA9TVsSAnZWagAAUyEP/0cvYPs5qryPtqNHmHSO\nwDetmeioo+udU82ui3Xu+TZ8jc4VIaYkQjumEvOxnbArqnkiLY6O42ChCHAS\nKsS6z+Y1RLIm6UluoumH8V2LkslICiy0jqA8YZFeNq6cSalnuVblPmCmmtOW\n1rFOS0BN0l+XHmXJucf3T9z38qoQMQC0z47n5XLjTNlmC3wAphEs50hfq7++\nwbn/zopPWsOQUfRD4XIUXOSahyEoAJpGomTnmJ4n10yBs7EfjbXWIqxjlqlI\nuu9twMSsul2oj/Rpkeo8hn0KZQaHbbp0vf0SQQ+7JNeRTKvLRoam4zc9JMsV\n9RaK0cdagHAW+pcgVr7fGfOPPzSmrqJtHNf+233PJ/MOI1hxQd3ieVGtsPQ3\nvI0zWLA+yFjH7wKoMl1O6vRpsyhMOgVu7oDNwz+UXlIegH9iieyg7roEKnkM\nRYT6+PU1Sps2WWlHkQUoCyfpEi3aGkWw4+iimmQTs+1evVwSBKl+oqdZ4t/W\nRFa7Am34Lb1R6Q8E9gRUtbAbYroVLZr3EF5wYe8GD6A3LQTXGA/shkX2fhej\nk2Ewiw4BLOL4T4nBg1MKG5+1OilQqw7X5VPPBtSIKE9qKbmVTKmajReJewax\ninNbSfraxeEQtlC91El0D7bGUvUwyJRj9cSLK+dC5Va+XEHBlqQwCJ57DZ3E\niN5J\r\n=KOCi\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c2d8193d6f16519d44aa55bf633ce00e4859ce0f","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.7","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.52","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.47","@balena/jellyfish-logger":"0.0.189","@balena/jellyfish-metrics":"0.0.218","@balena/jellyfish-environment":"^2.3.15"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.0","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.13","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.71","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.61-renovate-non-major-c2d8193d6f16519d44aa55bf633ce00e4859ce0f_1610229405736_0.7178908695934938","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.61":{"name":"@balena/jellyfish-core","version":"0.2.61","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.61","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"198c3a6c499ca4f3b9d6a4d9eb62d82c1cc72379","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.61.tgz","fileCount":95,"integrity":"sha512-X6QpvIDiFLK8XK2JUcWB/Q8lTZdBzrI8x7P9lzDWusdbVllzKkcs+i+XnMfjA2lFA4ntVo5k0rZVwyoP9aNsgA==","signatures":[{"sig":"MEYCIQCK3KnyuAP1qhC+xKM9fjsIWzO3IrlESrsEkIsh4zR8DwIhANZgvxvO4r8RAwNfd4k37qpY0tjXsw0vfgzirfCWmITz","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":578764,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf+ifQCRA9TVsSAnZWagAAlTMQAJ9BBqdKLgkJ7UpZVAhU\npoteP/97MgPgXtI4t80ODFdBGM020mTKtJIbkyoWR99uytkVX7OTfJBTgNHq\nkpPd7Pv6mM9IbkL7k26GFFwq+/CHQKKpxQaw+M4qtHBWjBCvWo8f3aF5ow1y\n+AA98ASbiWW1e960YkRKoRZFp7acDqDW29YDZj1vvV+2Hc2bUDIzz527b/pC\nogKx4nCH6LzPiVnIUMOUWA2tBj6caO0zQubriF3PSnF2n6eZirir+4S1wfsn\n5QHI3ezXRF+MtOWzADPzQf5fJnNVINJ7gN2/0QSFTz/P6B3Wsatj0PHwYXEG\nCuiStaCh77tdmoO3ka46hgPRrMeVmvmJqMuKiF+bcrTbklsc7iwv/tlfCU0c\nMzOqxJAhKCBhpgQHP4yLL5xzY3rU6NoX7LDAD7mF4qOLr/rvFJhfsei7JbC6\n3JCyH/HxOrSaAqu0EgGNijQJXXWlVW7PcozKEYF/Hk/e8b8jXmntAJX8mTpw\ntfBzrrq9comwZoQ5syIRijOKzer+lV/xW7vAk0yUvbz7i30RsHiSnRXYyZ8E\nr7eEcSpQNzFFwd63SaVG45BM+CrSZ3OVuCNNP10EEIskD3h84Zl6UX5pPFuQ\nq9gXHF+Js/XgfqXmprd1vSpdp+eogNAPaYOhDT62Zh6KMB/JlPhGP35mZ5nQ\nLzVO\r\n=+s5K\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d441967f0e9163e3da56892cb80cc7db3fddca49","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.7","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.52","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.47","@balena/jellyfish-logger":"0.0.189","@balena/jellyfish-metrics":"0.0.218","@balena/jellyfish-environment":"^2.3.15"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.0","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.13","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.71","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.61_1610229712277_0.5851484919255359","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.62-renovate-non-major-7479d38742fbce930de1de8004c8c1a6b02c31e7":{"name":"@balena/jellyfish-core","version":"0.2.62-renovate-non-major-7479d38742fbce930de1de8004c8c1a6b02c31e7","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.62-renovate-non-major-7479d38742fbce930de1de8004c8c1a6b02c31e7","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d74c5ce536f038b102d12c024cd733e7427b7119","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.62-renovate-non-major-7479d38742fbce930de1de8004c8c1a6b02c31e7.tgz","fileCount":95,"integrity":"sha512-aRzyoASIg2HP9jv/oLQ+SabRl08Yhn/eKeyLTpC6/JPy5BOeHDoS6PdF+riYDqiDi28WQcsWNv8c/LCpWsb8Eg==","signatures":[{"sig":"MEUCIQDc2FEhY6IXpZBf9CfaHHxCuQlbUb5+6ZpYm+p4g41ccQIgITKCswR87ZvFISGR84eFdOHkhU/5bfy6Hf6St8q27CM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":578927,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf+ihyCRA9TVsSAnZWagAAEX4P/A8iZpkyv54i+Oa+zXii\nQe3pAG9WlHwRUq0J1lFmyzY6nFY2hd0sO5Y9RvtUJKUfG7R0Y/ys9bsIy4Ke\nf/ROSk11iHZP0e630wkbqHmTBPh+AF2ykNz8G1wa0V0krUnACrc35dTSTvc1\n11RjRWcDM2owDe7aSlyVlxUFBXj0YgEARsq7tRdLtcS7TLbmbvIHZpEmVh/e\n1NODy5W9aocWkN7z1gY71MY9twJUiKOtZico97y3HLo6/+K5Ig94h0AT761n\nneSmgVaMnktIA1j253aYM01Fnk0Wm24/jxSEdj2dMgOSVFkEw33jyTb0kUh5\nLP1vtT/biZ3coeBOHsG32yiKS8oGUGGYnr6jKcvM5dkj8h9hsX5kg9ZWcmVG\ngyKF+fLtW4+0HXpQyvifncJ1VXLh+cGqEV5VtX/t902tDYem15RqwewdtJzL\nuRj9pu7zccqBc3IXCpjJq5wnEVmi31savMdtZhuqkG4U8cZSnE6/ZQmd9kKu\n0fpmjOSmkInbTD+W0VPVRmmyspR7DhyGfEFwyug5we4bmoj4CqacmBG3oXqT\n+lKYn0w4aqX2+kyb8+EVj9G1CRLyZZdGhUg/V0gIlM/UU3FDqVTrJkCG3CeG\n+kvkx5ZvF/1Dq3JVwqBD8kpO2F2SCZy48hR4m7zDp6k1DpJAl9qcD5KwOCTf\nWfCl\r\n=7sQ+\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7479d38742fbce930de1de8004c8c1a6b02c31e7","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.7","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.52","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.48","@balena/jellyfish-logger":"0.0.189","@balena/jellyfish-metrics":"0.0.218","@balena/jellyfish-environment":"^2.3.15"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.0","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.13","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.71","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.62-renovate-non-major-7479d38742fbce930de1de8004c8c1a6b02c31e7_1610229873731_0.07392996793522566","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.62":{"name":"@balena/jellyfish-core","version":"0.2.62","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.62","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3b0be6dbad0eb55088faf9aab992f6c49fc66923","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.62.tgz","fileCount":95,"integrity":"sha512-SeGmqfss1Wiyrrq57Wjag54FKR5ogBqVeDhVhoT6Zp/Eo3eSk8olVvGjRFScQycAIwLkbYdd7wOUSlAVZVSk+w==","signatures":[{"sig":"MEUCIQDSWxaYU0aUcJi+S7J3Evw3SnLRMosBq7f6lWhS75zauwIgREFMTq/sBkjK4GYRZwnNcUGadiqNh+4mvw3fAd6HH6Q=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":578867,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf+ipwCRA9TVsSAnZWagAAMjYP/1a15CmBZ4MwDoK6rbWl\nNEiYLNF/fKHkjtWRQiKatamp23cxugQwPRRQhA2adGqe7hVUyd+LpUb7KmuT\n/awZobaDoaLRv5kug4gv2vDGLfWdlpXnay9UcmSzPsTSrOD+ifd6JWjHd0LJ\n5kcfhE8TanAWFHnjZRTLZy3uA2zojUxrPaGhjwAZmBmV4ZVFIJLCg6FYs7Nn\nvlW2hH9edghwcuga/iC5O0ouuSn7W8n9E+HpA2/U4NiVwBwHbo/qaj+M6IcU\nsl9YID58U9y1Gjee/l36bnCxS/SNGxyN0h47WR/wEXJMqddYOdlR9k3enUAm\nFSUlmX/lBi2PUruZoalW0F8nGFvOPgbj2YOp4LLNP7wj11a/1h+WM1rYupUQ\nHLDS8BAd7Sx/3+TyWifxrEwgwL6MWjGl8nbvbqJsYfmk2UpBp/HwaCloOxVU\n2hOktg7R79dFwrH5CmeGV9cddx3bHe2o2Bu+61KpK4c0JBJ+uHPxN82lfTRw\n7PVmCRj/ztiwd89Clue+ro2X0c5iUe3K0+ILHB/ixFuAnyUcLE5u8fdF99dy\np53uCmKi5FtSyH5wpk+xN+D2ofJC+JVVThF1up6b7d+7eqLBbr1qPKe2frtb\nifLVfJxrmqCI5o+sqRK46Ek6SHCs9sz2EVEE/lLeosPum2XzhLL8PaBDqjlH\nkPqd\r\n=sg1X\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0ffd08085f5e42940da466fbba6240b94d398913","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.7","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.52","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.48","@balena/jellyfish-logger":"0.0.189","@balena/jellyfish-metrics":"0.0.218","@balena/jellyfish-environment":"^2.3.15"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.0","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.13","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.71","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.62_1610230384123_0.7004774298580443","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.63-renovate-non-major-6c470c518c1cd148efc1a18d86f3be17c819765a":{"name":"@balena/jellyfish-core","version":"0.2.63-renovate-non-major-6c470c518c1cd148efc1a18d86f3be17c819765a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.63-renovate-non-major-6c470c518c1cd148efc1a18d86f3be17c819765a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3b2f3b5a05d5e2e125297dfe030cf8ecc4568f05","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.63-renovate-non-major-6c470c518c1cd148efc1a18d86f3be17c819765a.tgz","fileCount":95,"integrity":"sha512-+tmS2RgudP+FChPLuJS0FI8Q5RgjEm7n+lkUDD2DwGil/oi3qfEZBLKiVk2idvl1vCMmuHC9aO4GlbIxDCCZNA==","signatures":[{"sig":"MEQCIEkJRox7PEHHQyBhTIPRem+G+7m9D+SgImo4N/ySwpYDAiADCuYLMOXhrfZkfmRK03j/bf9XYxAvGJtqKNMPswVIUg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":578993,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf+jmXCRA9TVsSAnZWagAAPG4P/2ES8liZ6J//Hq9yO+CN\nz6kLx2uZHkQZl8lBuhLBTZ2oN4ldN4XGo2ebk1w7nJLswAILNOn0pTIORfme\nuqxjcxqviosnnMJsOy0AzQLzlvP1DfmEzIvz8TV54Lc37rPJoUoW5AHXt+hv\n73ncZL/0EU0NhwpMgDnCgAVNbUefCfw73P1Ic0PHdPJUr1mNZwlaRObEYURs\ngUh4iZiCorkrlK3w5X1bh668E0xx3xkev/28IYsD+m82QwdbaIEzRQoiKnl8\ndFS4Fdk6jbwBZN772+s2s6FVnKTf3JemrddhI3c7Up8OiUeffarPQJhcS5Bl\n3IfSladUn6nECtOpack3xonvxPxCpl34mLflAjTOWg7Lwpw5TybTZexaJrhJ\naqla2eNx2e0JjRf0V8ZVUNa1f49isFiabTy/CyuCxKViWYTDQk+fCGcrALfF\n0X8YVvqodiYjHf5o7vh7g06Zk5lJg/lGEyvCveCXOvf+PC49M1K0xY1SlniF\nG0UN81cfilDF0iZr2GdvFX4fgAEp92d5k4zZRkgJ+45xGpEhq6Gy2Z/z5jnW\npv8qRh2zRkiKtYj2KjZxStZZ87kQhW+6b1T4YTMhqX42NnGER30UjfNlBF8f\ns93wBd8giCtblnqzQO4XDM7dThOQ/DH+JyQH72G0ApQLGu3gtrSU6wOQ6Aai\nX3ba\r\n=hYRx\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6c470c518c1cd148efc1a18d86f3be17c819765a","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.7","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.52","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.48","@balena/jellyfish-logger":"0.0.190","@balena/jellyfish-metrics":"0.0.219","@balena/jellyfish-environment":"^2.3.15"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.0","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.13","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.71","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.63-renovate-non-major-6c470c518c1cd148efc1a18d86f3be17c819765a_1610234263237_0.26695714079751265","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.63":{"name":"@balena/jellyfish-core","version":"0.2.63","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.63","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4725bdcb2bf1d8f242b30bb6d77058f7f1384609","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.63.tgz","fileCount":95,"integrity":"sha512-ODWav8br4aflgbnYzJh+BnXs9tfELBVfT4Lzk570XFvAMEhuZNgPSTf2p9/ymFeG45LIt3T7OWSor6sz9YIPcg==","signatures":[{"sig":"MEYCIQCB5n4gezjshe1ziTpE0WxE/hEKMo3/a/1wthGKDRZoSQIhAI+Q7qijbWdweIFrXLMUAilX2Fqlm7Mzoy0GKEDYJlRe","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":578933,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf+jrtCRA9TVsSAnZWagAANP8P/imcjhtQTsJkMymwVNQ6\nWvtX/8ifYf47HF9uTF8zqWBUlE7Xt7mtQmpY57ZAqmNBYciVygLlVpxzat2h\nxnBVrOTu5O3cgKlISHPTAbRxnk5bfKDratR6s2+0Fo94KfddHSLJs/TaTOWi\nnwvWQAztDt2zhKL24m4iJ271evT1PxWYssaiiJzF4fR0aRa2QxJCXbDZtahp\nrvHZ9Ozw47GkTKUVE2Xnne2CPRSFBuHtzRD0xTqEumiAx13q7wbP7Z88X5tr\nDlTBnzrS83eIu59TlS35ozh6tN/aDpntyDGp9lhtuMW1OLpgzEX4DSQfyLBW\n73mZQN1BOfe40Bh+5lLUd4MHdiqedC/Q6S5azV9XiVLnFdVaBKwhlvW8g8MR\ndHpYVOB9audXJjjqbwQ9Wc7ih4/YyT5ohDZPd0lKzdInwdZVt/YCiD/mLWmb\nd2tAjbhzRjaTgdEU/Aj7ifB47xwtSvqSL8culURuEmf6ODkmv4CbZU5LSl6w\nRTg9XrowW4Evnqt4WqHJHIUwzgg4X04o557/osxk1TP1knzgZTk1tN/KHwSc\ndSC9HftjSvOop57sSRSvgmVTUJ0sX6wmt2gFdsFJAuYmvj1J5Xy5FC4R81KO\nP38REVnOnHUTLJ3Btc6/sNhTi+ATjQ0CbUWBKE1Z36i/4uFsbu0O7jSQeX5v\nqoeX\r\n=Mwrh\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"763b13b8ab02150281e458dfac239f33581487f8","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.7","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.52","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.48","@balena/jellyfish-logger":"0.0.190","@balena/jellyfish-metrics":"0.0.219","@balena/jellyfish-environment":"^2.3.15"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.0","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.13","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.71","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.63_1610234605092_0.6668955363101743","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.3.0-patch-transaction-53d7b6cfc8cc5a9188433a9df6d0c2e12751fa43":{"name":"@balena/jellyfish-core","version":"0.3.0-patch-transaction-53d7b6cfc8cc5a9188433a9df6d0c2e12751fa43","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.3.0-patch-transaction-53d7b6cfc8cc5a9188433a9df6d0c2e12751fa43","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3684628de338a943826c230e71d7edfaa4b2cf18","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.3.0-patch-transaction-53d7b6cfc8cc5a9188433a9df6d0c2e12751fa43.tgz","fileCount":95,"integrity":"sha512-D4t47zGviLqXz1opZfapNNyK9DpMIrFOA2n8k60C/V+wDunLpQU+5HJNGpD8lLVpC+u+azu0/fNU6gc/UbCIxw==","signatures":[{"sig":"MEQCH1oa9V7B/l+oGGlHRWTT7xlgxXvy9Duij9ZDUNdxJXMCIQCYZ1gCFN+sFK4fhVkySpCbPmyY+dYz/Sq4xUqw9DfwFQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":580898,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf+x6wCRA9TVsSAnZWagAAoBEQAJhyWCoEQJ0+2ZU8venE\n1p+Wfhz5vNzAHrLIL7Cm8irCgKQiVrCgjDrnNpfNmFTtdR+ezaA48fg0zz8s\n50z0e4deAJJl4ON8++fafgd15dD1v/AFBJhcC3jtD9GAXiXvDTTfsFPkgNWs\nz1kVU0jiFRQ4UbzxQqlOUoZz3OBhviL8xTwhOXvSORLSL9xe70STc8K3sRIM\nVaJO/Ei+jdF+gcxnrt4eJNJRwZhSBsXlc2UiC5Gv2athKkEWhBZThvjgXNsJ\nUl+0cIpeiIHGI8+cPBQM9XKSvJUZ/UobyNFKSDCbKJnlG6GVM1lHlDTcTcC7\nSJ+/KNXmEMZVrRCVYHxaQyobU8Ej0svE5blSDL4Fx+BDRWnleMUs3Oo/iWp2\nJc2BtzoAJSUcGlr4EbATxvg1ccZkKrgAwzU7CXEo7H7DnSh/tcZ8tO8crVlm\nuXmHf9P/INrpwpnyHTqdlHzkRIi7fx0KcNTLsoAFKz1xO4oNiXkP03XBZhfR\nxOBG0JO0QNmtaTq4IFij9xHPAZbm2LqQh6I+fcjjHuActQbvZHyzEt7GLqv0\nVXHk43FQ0da2+IyPiZIAYqhCofPCrkcn7tmqOeLhSvXN6ORrNpnTY0h2nLXi\ndxIJgq2RuYnM0FumSg+4cjwjn2KeDaQ+8gjVs86SZtKlwdgPae32WH7zMdGR\nrTpF\r\n=ZgFC\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"53d7b6cfc8cc5a9188433a9df6d0c2e12751fa43","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.7","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.52","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.48","@balena/jellyfish-logger":"0.0.190","@balena/jellyfish-metrics":"0.0.219","@balena/jellyfish-environment":"^2.3.15"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.0","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.13","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.71","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.3.0-patch-transaction-53d7b6cfc8cc5a9188433a9df6d0c2e12751fa43_1610292911629_0.11430287693572283","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.61-streamer-db-client-reconnect-2be81f7e20e8ed67961742b6e8f346fd8facf7b3":{"name":"@balena/jellyfish-core","version":"0.2.61-streamer-db-client-reconnect-2be81f7e20e8ed67961742b6e8f346fd8facf7b3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.61-streamer-db-client-reconnect-2be81f7e20e8ed67961742b6e8f346fd8facf7b3","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c0c798f20fdf29f38d098e00e5c85ceb1e154de1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.61-streamer-db-client-reconnect-2be81f7e20e8ed67961742b6e8f346fd8facf7b3.tgz","fileCount":95,"integrity":"sha512-uEOccZLysIoOAg6KIxvMeLStWWbqk+y6oz48CU4pVT334Kvtdn8MjZi//gA8nlcsxIaYEBqIDvATNy92L8ql+Q==","signatures":[{"sig":"MEUCIQD5r1N1WOZr1iv3FdFTV2siMCbh3I8Ijmx9KEyV0CCTeAIgNmhAQHZVi+HIKZyK7HkX8DOpbSkDjHh0RkdnN4jKBJ8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":582400,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf+8FFCRA9TVsSAnZWagAAbFsP/ilkIYAwHFYrJmJbf+tj\nxtsEFkjNDAlQ76QtKjbQTTQ6pa25+QXc8cC8s925U9vAubbujK9DSCvr0T6D\npfR4PvyPLFqpjYZO5EiUV6iX8LCGTDoVu5CNxryX2RUa4xLnorOIJJenyyBw\nx+Ux9FqUwGsrys3JU/NN8BkFYf0dOc5zuc5NyZmQiXZJnf3I90GaNeOIeZNl\nGTOFaYO+CkhTA1HQD+hlA2J1VNnz+U+yzGSe4NCk/uQLWovmO6vHf/gaMdr7\nxza0VCWYKm+RD1G9L4NCsEiFmiKxayloUhv+zksCcdQ7iEt9LqWP9O/NKbuG\nW7FPT9PZRP/ELkQMDRYJuvduwzmcOsxRERJWbTtl7HDfBEsvlwdyNQI/9d3w\n+rW1yH5Jt+iLoHuXqwohVGJ4rGG9sCfLsvWLX38jSOr2/lgvAOBiHax71UjO\nVpAcy+Mfsq1/1za20wWBWrnGyZBnQMcg0/7sBN03+O17MzHeh5apNGhyEl3r\nzLOj8mewcHXm9hDZiwhIznKustar9GN5fH/gDheOoIVTCLEQaJ8Xrr9frhy/\nrkuC1Jo5unKjWmHXlZz4/k5uzxR2JFBaptGOu50sCrTzDUHFj2En4XvMVzeZ\nbe2oQR1O9duZZdGdkWEcs4FCJTwEbpG7NZQg9INuFJ+viGhbuAhgBmcbwf2H\nloOW\r\n=DuLZ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2be81f7e20e8ed67961742b6e8f346fd8facf7b3","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.7","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.51","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.47","@balena/jellyfish-logger":"0.0.189","@balena/jellyfish-metrics":"0.0.218","@balena/jellyfish-environment":"^2.3.15"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.0","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.13","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.71","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.61-streamer-db-client-reconnect-2be81f7e20e8ed67961742b6e8f346fd8facf7b3_1610334532494_0.12467726999773143","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.64-streamer-db-client-reconnect-f994b3310efcfd4fd87329797d8d30c07285b108":{"name":"@balena/jellyfish-core","version":"0.2.64-streamer-db-client-reconnect-f994b3310efcfd4fd87329797d8d30c07285b108","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.64-streamer-db-client-reconnect-f994b3310efcfd4fd87329797d8d30c07285b108","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4db16bb7c21aee1904990ea3ba05ff321a8721b9","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.64-streamer-db-client-reconnect-f994b3310efcfd4fd87329797d8d30c07285b108.tgz","fileCount":95,"integrity":"sha512-MTyPx2Rj0gzFuzRihPI9Go+kkp5q0dB7/iyC7BiNUFUT+mqDMXUDU1uGIlYs4OQMW+vgWjrHH66F+t+C+bkeBQ==","signatures":[{"sig":"MEUCIF2YS/ynmkaM8P+vcc4OmluypCa20RkDk5DwobbQME8wAiEAjeGSKQ5nTNG1cxeS3Lg82vERov+cN/0i8zjvhl/potE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":582581,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf+8PdCRA9TVsSAnZWagAAqHcQAIC7fjF9hUFLbY4sCXSz\nHMsLpwJ68WVFm1k5A2zqP9TPQ7fSD0KeSB3gpIX5kN90hJo1HKPA0iDM/aT2\nGEn/Tx27XvWoXJlLrUg49IYw20UvfsyLjzTN8BLHB05oIE+Cf30IH0OANWZf\nK9YGzTijfPHRrsz4hcyDfREUpfiUR68IATf8m0IJb78Dmt8SZeXkh1uMgy3B\nJMpOo204AiFdiv7A6qz04y3cm49R2vMWxMQygXX1hrv9Y2HPmRH0gCJAk+qL\nb5vmGBZg0eUSTJ87+YVSohCamfVIJ0a8Hp59N3cLZXxVtDvsRmEYjMdiaS3r\n9mvDdVW7YUcZMmJImmGLFYCWMzxa5dzQXOKHTprmcE6Lf3zpQPy9PeuBENYi\nFIhl4t0QQuIK0ANCHJ6bMe5XAfZu7ne/hC7/nhedFOU6iXxEHb6fvHKQWPZU\nmivDp7okx2Jt3WF9I7dNMKEmzol0PqWF7TR6N3Pl555JlqMHrF+pmDes8C7m\nIndzMG4UgQr/K2PhfPoAAInVxBvpNnwgGRRdw+P2QnG07BVncNzv21oLM0HG\n89dF1FUeIlERgvJGVyqmHLOyY/0dFPNvZAHc/VcXANo/eaEPBjYVh+1HTrNR\nhJY4TY68YcRg2x5v0HkcZM46fbgJ3YdjitIbKJUZ7wLqDdRnfikKmoq+FL4c\nnjLp\r\n=FDSn\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f994b3310efcfd4fd87329797d8d30c07285b108","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.7","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.52","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.48","@balena/jellyfish-logger":"0.0.190","@balena/jellyfish-metrics":"0.0.219","@balena/jellyfish-environment":"^2.3.15"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.0","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.13","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.71","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.64-streamer-db-client-reconnect-f994b3310efcfd4fd87329797d8d30c07285b108_1610335197361_0.889234833842796","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.64-renovate-non-major-692f59ffd825f8dd5e0c654d9838bb4bd49f0402":{"name":"@balena/jellyfish-core","version":"0.2.64-renovate-non-major-692f59ffd825f8dd5e0c654d9838bb4bd49f0402","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.64-renovate-non-major-692f59ffd825f8dd5e0c654d9838bb4bd49f0402","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0ae5a39047d91e496e9f3bd72687f8a699b14adb","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.64-renovate-non-major-692f59ffd825f8dd5e0c654d9838bb4bd49f0402.tgz","fileCount":95,"integrity":"sha512-3yOwn4/MS2NAoCePjGmVbRCS1R/nRl1AkyLFv4DyySBwU4onRtl3BD/Hhr6jshPSdRWO9d3dYgpY0ON7tzLQ2A==","signatures":[{"sig":"MEYCIQDt/fO0dlLtkTOtvP0VfoeDcNAEtOUHW5KDKBPcV20wiAIhAKhaMtCZ41Y1NM60tzxEygbkoetYk9/6TIXOi6zSDmHQ","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":579059,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf/AbLCRA9TVsSAnZWagAA4LcP/1MdE2+oReEztpwVvP9G\nCEVCnrfyD4uH6li3a9grkuEmweCdhStvDRKlXXFE1J+75vFwHxvvlw7YdOlR\nI6I7l4kcoKI1Zu6/jo0ObXB9P0p03OLe4Nd6ViEFMDt7TN3VxLvNvmwndwH7\n8iML8iDpIOf81miIF855HElHSczD2sSf/6HF3qxizDKwDdcDWpqneus+AuVA\n8xDHgYmCEq9DAAt45kNetUxFaDkBDAVZvO8qP2WuBLIlyfPfXLl48KbmO+Jf\naSaVKscfnCSC5QZGymm51D7TNfiVnFxJcaIwJt7WeER9pn+OAtMg6IJRJ6ck\nSSO1eQi1hGjywBianY4QwOZiq9mMWheyvQ9RXKOvoA+BJaieJu0rNVOX/hOu\n0k1AlV7w0/a7SIugEGqrgXhuqUQeUP7JUg8oRpPTV6WMvylpcPvvhw1/5D6E\nhtsaI9epo/9pdvjnqYo2CxUWhjJOF1a3bbYz7279+TvkkXz0PU9DORXOtAVJ\nNvE6oqG4AZtzB49zoB18CCywYqMI/zKWf3bPBWGXDvEsb0jO5qS9NiGPsWZ7\n6029oMaFKhJx1OuWHHhMDCAQi94rnlhru4bD/e4htQi8GioowHN5kHPt2twp\nLZdUuq+LXeTSEoZLNEd/yfHmL1BOdjoaE3mm9xzDswvuJJ7/FugEZMBEY9hG\nxfC8\r\n=NReY\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"692f59ffd825f8dd5e0c654d9838bb4bd49f0402","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.7","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.52","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.49","@balena/jellyfish-logger":"0.0.193","@balena/jellyfish-metrics":"0.0.220","@balena/jellyfish-environment":"^2.3.17"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.13","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.73","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.64-renovate-non-major-692f59ffd825f8dd5e0c654d9838bb4bd49f0402_1610352331318_0.1417319774618131","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.64":{"name":"@balena/jellyfish-core","version":"0.2.64","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.64","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"7d4bb8dc388571d415fbc65eaeaba47128dfeab0","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.64.tgz","fileCount":95,"integrity":"sha512-KCiDvLqgECR8rZOP+JXAbqGn3mDUOaSBXwTIeZSe2HprRbmbN1rPMeRwNZIDp8VKYBLpHy7GjKY0vDEYPb/2aQ==","signatures":[{"sig":"MEQCIGAWSaLhfmSYlujTfTJZ+2zCihrjEL61qUqtcuCmiYiXAiA7Fiq28bswkWxjXflXYUzafF4fWiIpqTh6s1h6G6B7jQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":578999,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf/AglCRA9TVsSAnZWagAAqB8P+QGrT81lDGFmr3AttHly\nVURIgHeF30hIgXl9K92ecVeFGmhdW6D+lr1pvuTY3Asns62XRZm9CMqoKSSO\nMdVVPJWm1cLxL8ErzZUGYQIWPaKqE8pCpFCiueKzt2fP99+IsjfQTOvZn9O2\n+4D2pQ5hjOTxdCvUJbZL6cez+Rjkn7QVXhoqCvLxvrpHGFk6yjnjF6bYz4rY\nPXhbB+X3OEydMQHh6sjAQUujWRsfi1Q/OF8jKBuDvhWDqnqsUrrrjfjGxBGj\nyyS6strm95b3tvn74ZrcSg6lTFF9/9B+c6tmJ0LDGQP72YTPXfMdyfd9E++s\n+ye85WX9/bNfXA9Ixf7qlOgzfh6qzmseeuLmLcfWItY95s39s4Ay4Tm4oU0c\nlCP6WS+kdYOIBCCkpZhdJNYUK8qrpUxsIijMOyegNiKPrZHokGHIZVZ0r3f9\nIZrzoOTStHOt49vMQpaYreLpVgc9gancJO6hfCFXznr3jZjqEumW++8dyXPV\nhTsRPsbAULHAizRq4/IEAY1zVTZinRaVdGGLYBslspVjUBH0qe1S+AR+RE4G\nz/duwVVJkJODqkF0bDMVFI58jVtgKem2iUX3QHTTkJsWMSlrr7L5290op6PT\nmkcLGOdXXWBVLWZINCiaZRTIliTYQfDwK2g58UU5pQKMrd+4AxWjhJZgG5AS\nntbU\r\n=okqA\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"cd0d3f5c9372a7ccd6df230b27f7d2fbcd059a18","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.7","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.52","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.49","@balena/jellyfish-logger":"0.0.193","@balena/jellyfish-metrics":"0.0.220","@balena/jellyfish-environment":"^2.3.17"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.13","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.73","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.64_1610352676934_0.2317256951743829","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.65-renovate-non-major-1b4e2324061ccbbe48f655f8cffc8b5cfa10387d":{"name":"@balena/jellyfish-core","version":"0.2.65-renovate-non-major-1b4e2324061ccbbe48f655f8cffc8b5cfa10387d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.65-renovate-non-major-1b4e2324061ccbbe48f655f8cffc8b5cfa10387d","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6edb41b3a0aff304b6b2ed111863514646f15218","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.65-renovate-non-major-1b4e2324061ccbbe48f655f8cffc8b5cfa10387d.tgz","fileCount":95,"integrity":"sha512-KFkQxXxeR8RFWd0TtjNXGjUdbAhaopKNqeM5DNuZ28IJ+gwEDw5gUty8vOX224mJ08Xc61ZTLmh8KSiOPiGR2A==","signatures":[{"sig":"MEUCICwmmS3qs0EgafJmeMv48uZ3HWXqgvOxHLACuK6G4KP1AiEAxn1lV1lCZ+VEbmnODdR/QP27GOcLUi6zNK+mgp/NA6I=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":579125,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf/AjTCRA9TVsSAnZWagAAxCIP/2b9w5NywQEedBS1xLuH\nCMCMSvSot2rE7TWCFu42co9QJ0u2GPK2erJ5zEnXOgaLfMrD1Mc8q6L51nYm\n8OjjTEKuOfvOAAGhnq3n9KVxTQ4utG+zU9Ddp51kyz3kZe+iTxnlZDicxbcf\n/ZFmwv0j7hc4bHyWvAaUnzrK/lL+Ca25QDnyYxYrYvqwabbCVI/k5VPdj7JH\nRpGVbCUdOtuNchsmr1LGMb+hfqDeExgldrBmQz7OUWv/J19gm5RRtRBQ5oOC\n89ayWcqat7LxZqBUuQ9593Uvm/ANt/fWD3HHxGOMJTiAvu8OhmJ0myguk7tT\nd/HIWcrbvWGfUON7q0dEa1TtaxXGKZS1337rioJDtZoxXo3Hy3oT2bMHMVVV\nST4rU1CqzjbiXQAxsPnIua985P1EEqhAZfnXtAzapNPxNHp3jzsYn9Mv4rqF\naz9fw7spx+9GXtcCdVUQfMALjHIhVxB8eOqs9+jFHENU31DAWaHqsHmFxP23\npYj85/lYpGUJPW5819x1izeAuc/xIDVSeqkI9zpY6EFwpmih1umHw7wtPQLz\naIZJbWm14Y2NeGC942YZUXQxU9gjxUn4SPjRJREXJig5LyWXJP6m4y0skdye\ntLR80s5uITF1whEONiwI4f816B1W/f8TVlL1zvB7bDy+F1dhGeHJDrvC/ykG\nkxzb\r\n=rEB9\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"1b4e2324061ccbbe48f655f8cffc8b5cfa10387d","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.7","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.53","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.49","@balena/jellyfish-logger":"0.0.193","@balena/jellyfish-metrics":"0.0.221","@balena/jellyfish-environment":"^2.3.17"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.13","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.73","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.65-renovate-non-major-1b4e2324061ccbbe48f655f8cffc8b5cfa10387d_1610352850589_0.3426379637693373","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.65-renovate-non-major-fed52f3c703e568dc2a6ffc4ff9088b59963113d":{"name":"@balena/jellyfish-core","version":"0.2.65-renovate-non-major-fed52f3c703e568dc2a6ffc4ff9088b59963113d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.65-renovate-non-major-fed52f3c703e568dc2a6ffc4ff9088b59963113d","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8bb72d181e25213a9a513d264e0c5f48f9371bfc","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.65-renovate-non-major-fed52f3c703e568dc2a6ffc4ff9088b59963113d.tgz","fileCount":95,"integrity":"sha512-z54qYiDSzyKvWbqROub8M+C4I5sb3AH0fR5jAqX1g68Qmzqf8aaje4VcK7ko9jv7I+KNa+mAD4vyNMgicR8gYQ==","signatures":[{"sig":"MEUCIE/Mj+h054VhSJtkzvktvChlbv3ka250jkSwy4EGIwKmAiEAzCwLCtp6eCwczzfd7ChmRjtT/nItpwaG6ZDsB4DKSRQ=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":579125,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf/AqVCRA9TVsSAnZWagAAS1oP/RGQ/mrPpasU0OJPq6G3\nHC1eW+M1fvGH5hIQ476tYguobnZMnEUxUeAqk5HBhW5p11vXWZbP9hKzuZ6+\nI7kmvZAK0gHMkcforb6Yh7EMaj9+RqWIEonRqPQCp8h2TjHLIGIvwS2upYHU\nAK53q00H710O/qh0LiagCjbyTWRAuefWICqOvI6OvbQHzw/TvIfBkYLCTEl6\n8E7Qz2xDUBEyk0IXhNeTZRWwcELKrYKnR9ElhJNcXESSbvUCpCzqeBrdRw9T\nbO8DX4m8i8d9uIWQ8scp7paUl57y/u9RlEYQGkUy782hzEzt7pf0U82mmjUi\nFwqNrcaDWMbpgeuPtWuB8IpIsBcrMBfP+GhYSYz/1guge/BaARTw+yA/QRwW\njz9OSuoim7xSFwq0lKoxfSzDYfs4WI9U+5otLsnkKhKyjC4nbpRHMAZlFlWT\n1kbrOWFvYOEuqC228iUcmCbMV0AUmdlHrb8+r8bYaVE6y8lfSq3mtn9PHzOq\ne7YvfDpgzzgiiGmibW6otCiq6f5XEiEyclu8AIq8Rt+hiOUGTdb+KzCtQ3gg\n3gNukajoAt6KD5SzIb3rtGz7DPyFNW8l+fjnSQHGQKKASXtkufXXlpfTKi1/\nj/VcCdzg8bLp21IR358tAGro4y1V7Snk5fj0CxaDrGwAc/3zFoMgnIEviVuB\nsa1Q\r\n=PZEP\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"fed52f3c703e568dc2a6ffc4ff9088b59963113d","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.7","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.53","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.49","@balena/jellyfish-logger":"0.0.193","@balena/jellyfish-metrics":"0.0.222","@balena/jellyfish-environment":"^2.3.17"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.13","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.73","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.65-renovate-non-major-fed52f3c703e568dc2a6ffc4ff9088b59963113d_1610353300985_0.0936486475997349","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.65-use-company-token-19b19568124fe90650e3d8227fb0015dee1c666e":{"name":"@balena/jellyfish-core","version":"0.2.65-use-company-token-19b19568124fe90650e3d8227fb0015dee1c666e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.65-use-company-token-19b19568124fe90650e3d8227fb0015dee1c666e","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f27e8de7ed5e6c12b88c75fe800383e01b8b17e3","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.65-use-company-token-19b19568124fe90650e3d8227fb0015dee1c666e.tgz","fileCount":95,"integrity":"sha512-dDhgxAot5kNUvsogUNR/qpjGg4jbuT64+L861D99gC3XfkUbLDMMkggIKAiOJmdVV1UqbDR2X87WQNdAedAqtg==","signatures":[{"sig":"MEUCIQCR4cp91gTJioXTq/ugfigcggVbtQ6hp5x8g0UXPAGsYgIgKCJNfXDYbRybshrAzFH0b/5v6MFNVKXlo8UBWfWD5Ho=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":579131,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf/ArDCRA9TVsSAnZWagAAe5cP/RrkxpL3SLDie7dNUXlQ\n1g1oTUTPQtHlHqXWGadfMlgMvoAu11YhoV+vmN3LE1CFn49mmiLBDVJ5ibeh\n7BwBtwMy4YrchiIKcHK0kwy/5QR++VtFfHarLun9UuxbwyKuftN6ND3RGWPA\nLCSY6klLlWx54G977nIuILcaSMIgpSHxgbU0GiOUObsActy74xOhcClDGkSC\nNfoOAD/zkB69x0vZZ5JdMjpbhdjYwN1FZ8RS0vVU9GjQOud7iyU97EkyjsEP\nCHsqBeDZbLnEIw/vBmx12YTgTuIfjkevI2ecrPnHt8tLYpF0b3/DXCMh2imt\n3ItYY/d2HJ9JdrDBK7OJog9l+AEYtMeZSzX/MD3pmQKKJ55yLdCfsOyK6QaR\nTia1Mn/l6QtMNSfhy4CT0JWkh165FvLB2RVm2AxXbLzyeLrnXenUAF7X2lnz\nKfpAo09gSrumTp2EnuZIxmTqisQU/WmDAQlLurPE0FmjOf6j376ws3Nf9pRT\nDAwHgzhDCUvO2UX3j5nJvZOTmc9SASBfN2G71sEuPB0YJlJ6WIs2e9CWGX+h\nJcHjjaEB2om12sJhKmqw8kJ8lFlyKv7FjnE8RrDG+XyNs+S7WwfYOyk3r72Y\nf75uuAbTtjn8PCEkD6wu4Zu+KZ7vsAPhkPYBUA6yIfK4AZFfdkD5vD0bXeFt\nacAc\r\n=pxaN\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"19b19568124fe90650e3d8227fb0015dee1c666e","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.7","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.52","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.49","@balena/jellyfish-logger":"0.0.193","@balena/jellyfish-metrics":"0.0.220","@balena/jellyfish-environment":"^2.3.17"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.13","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.73","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.65-use-company-token-19b19568124fe90650e3d8227fb0015dee1c666e_1610353347098_0.4086905318929901","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.65":{"name":"@balena/jellyfish-core","version":"0.2.65","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.65","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d2f481944fb195510632b1e1b6c14ec19633937b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.65.tgz","fileCount":95,"integrity":"sha512-zAhMPZaYM+3TF5TPBitb8JB+vdrIg8Oz8N8x+HoKO9zSJoY7ncOOlL+mRc09TBU3JxU/dzqRIfbBQRX7yM7UTg==","signatures":[{"sig":"MEUCIQCfu3qYsfAJEejPfKlH0wNPmVn8HzNfa0kMY+01Asfv9AIgGnH50BxMjFMp7rGhzzSckDg1qGhhUhKmH2H0Q1SzPRc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":579065,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf/AvPCRA9TVsSAnZWagAAN2YQAJdlXqKaIi2/lWfAyrVA\nXT4jEOPBiY1KjACvhIPY1zcR6drtE9okKZPPSO2pJ/NqT9QXYEOxAM+KA4Xl\n29/kaq8WbmRoQZGqmT3QwocORCvIdT+Fu19pueeoDvmfEOPlKIgKRxgn+YE7\nrWm4BxLTJHNiKxsL3NsuZ2HxWi9uW6U4uqrIYuLOzyg7050DbZSjSoelMYYu\nOKc8I9ArOuxF5pJ1q7NtM/Sbj3j1G/wy/vUqEtFeMf372vE0M2gfXbg0gkIx\nQuex1HFDAFpZg2lTML2t1K9QhySnFAAoCkukw1YCxOrbVMKEwa9BA3nW8V5z\nLEJEF8koyDFxosKQ6Z9w94U/TdDqLlM9I1qZa0r9iBNCF6VjKzLDAYdOD8tE\nAWaupXYZbrHFmVK7/Hy8pnaswaYkecBbroq9SHF+63LbCcqI3hbIWcnjyIbV\ngkVHx+B9WtzXv2NKKZ5tcaZzJoI/aoULyWK6jzHW6YDnKbl62XC3UuvHxaX5\nnPuBo9eF/ch1CQaz0hSlwOu/O++6bZpqGvXG6rnxADqStzYs+c+YCnJtYKzQ\n1Sjrhz3kcFC9F+qLQOEem0B1CnbQJYGCTsH6+3Cah5Sy2hBvrJEAfJ5v3aIN\nX+17imkkR9mWps8FnihIMKiuAeohAZAJeKb8eGjgBDt7N8946ZZS807g4W35\n9BA5\r\n=RpOh\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"aa17ed9d6860664174f96ce9e8cdf98820145d77","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.7","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.53","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.49","@balena/jellyfish-logger":"0.0.193","@balena/jellyfish-metrics":"0.0.222","@balena/jellyfish-environment":"^2.3.17"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.13","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.73","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.65_1610353614887_0.4008746697363852","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.66-use-company-token-9249f39679711fdf8538d0f2965691a467a02583":{"name":"@balena/jellyfish-core","version":"0.2.66-use-company-token-9249f39679711fdf8538d0f2965691a467a02583","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.66-use-company-token-9249f39679711fdf8538d0f2965691a467a02583","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d3c2dd6251491a397819905f34a89860af03fdb2","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.66-use-company-token-9249f39679711fdf8538d0f2965691a467a02583.tgz","fileCount":95,"integrity":"sha512-973Xch2Qa0PIbMa7NjYTIMSo4gb9tdKT9jqsXjC/OGFBH6w6zx3+uJBgLXSfU3kYAEIWRH3isX2ZPN8kwCeYQg==","signatures":[{"sig":"MEUCIHZQ/rEznBsBHCfx9xrCjnHpSCZZyeasz+5HYSIYR1PvAiEAvLZZJoseE/1HvmUFUUtLRD8sfTXTYphOlMfBywhexpE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":579197,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf/A1dCRA9TVsSAnZWagAAmUoP/1n5PZN1QmB7Y4EigmvG\nEuA1IBuof6MiXRqHDDJ6P9QCjy3G5qw8xdMjSOqa1pNfSF5sBd7GWsaeNcJC\n16RRKBX6/fZq0lTetp/OQkIZlRVK0JeY0SyI8MFBF8i8FElm42QFeNe4U6hm\nuBOlUXKXbAuDGnJlUnJ0/VMqSZXDkuEEQo/inWLe0Lf+Av5VoSCAVAwmHhvp\nODZqN3GhqVqzJqqXHYYxb7VIe2QKpcBPq4SP/jsp8/CABj5XdD3KbSXjJGOp\nZFw8jM3ebIYdbR3cblPJxRz7c9Pal2FIXbyVk8ibJR/latq5/TEcT8H+bnfN\n4uSJbyGb1FtH834KP49hp90G2J0/g9xj8/PAh33s++tiCmD1FxwQBjsP/ieI\nBjFXIBd3OtY04DViSc2Bra9fopgHgnIlNruCSRBIPBj4eJrJzWP2f4st67o3\nm+5MSbXLmsV/tIjZ0O7aSIVE4RdYWIzk36WUyjhkT+WvaKYAloLouZzy3HGu\niNnAZiuEI1NKIJaglzY1anXR3RZJ+ILe+INwu85ewUall27+5BD++igEa06V\nDtVw/N8VxyHmsvGjabcHUeb1V3N9Vp4ZjdaWyCMkY05dXDAkpa134+ja2Bd3\n64WTh7IGiTi91Xor71mOBSTZgjCJhvE9vovTziV2mgTpEO8dHQmYmLbbSIAJ\nqDrz\r\n=PMUW\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"9249f39679711fdf8538d0f2965691a467a02583","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.7","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.53","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.49","@balena/jellyfish-logger":"0.0.193","@balena/jellyfish-metrics":"0.0.222","@balena/jellyfish-environment":"^2.3.17"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.13","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.73","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.66-use-company-token-9249f39679711fdf8538d0f2965691a467a02583_1610354012928_0.8061006687601016","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.66":{"name":"@balena/jellyfish-core","version":"0.2.66","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.66","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c7841dec1b45600a12d6b9588be633d967493925","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.66.tgz","fileCount":95,"integrity":"sha512-aCXoEmDP1ggHh6TZ2zUREZlXPQwGDnnr0Vz+BRR6xjyXLkePYtbZF+WVlWFvM76cJ9dTVxcPshLb86D3jzGv6A==","signatures":[{"sig":"MEUCIQD6o5Bt7HMtc/XEu3ICGr5aB//sSYnY1u6iqGtw/t6afgIgMD60SkyWQjgifVxtpTFxXf7XxNVG496G6zkbBvKyPt8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":579138,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf/A6NCRA9TVsSAnZWagAAXl4P+wQIn98wwh6RxY8ezJup\nuQwa6SKEAICNrjdJLLWXDNGlxThc7t5TAt4eryJXJ6SM9zZE1tIvmATGU6Gg\nh0KZTxhZiUnWwPSQJ4754/kD4kDMQxCmqIlJwTRrL2h1bFSflcc+IyOHHGe7\nRjHZD6Aglxsw9M9zXpdJcoNH9gTYo10guaqFWkCotpshWsqmNbXSJux4MSzJ\njT4Phjz+zeeYw3lqDDEa6cL1+I/PrR84rVhkxabmYFTj32NDomUXIISfe+EG\nek1jhayOzp5Me3tcQD7qHhFimV/jqv0jai88cURWmCbMRN1oJftCymLIWBu7\n4RYEZNXT87xGVpoQRIlha7lE9omvOafsBmYmMLyP0FwtLDmM09acaaQw/aZS\n/PAbTJno2qQmKDjyCyPYwqV/sX5mUeZQE5yoJ97ggVHVvW1xMxu/3PgbxgLZ\nJG4BAohoYOvye31HFXucdcqJMjpsm5ryLyrJTlCjna9xTwxkMVCy6dtL2mSq\njFabgNiSV3tsFG+4rlA1n5A/sV29yVJlBbxQq7xFXwk2HjVXnrV2e9NjMQyq\nJaFVJLGlujd+4/1hH6IwfJj7Zqok3nO6mHr0t5uaiiPIbRcEaT5QKjlVGv1q\nIKD7104RziVY1kG2IBWQNcSyP7TVHJEQbjiUHEokhzLXm1gGLDoRgm2veF8O\nly2I\r\n=n5nc\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"58e469a676a34323d62ba23508bb6a2078fdbc81","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.7","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.53","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.49","@balena/jellyfish-logger":"0.0.193","@balena/jellyfish-metrics":"0.0.222","@balena/jellyfish-environment":"^2.3.17"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.13","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.73","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.66_1610354317431_0.9639381686508905","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.67-streamer-db-client-reconnect-09461da4e98244c6f90b72fb35bf72f2bbb8b5d7":{"name":"@balena/jellyfish-core","version":"0.2.67-streamer-db-client-reconnect-09461da4e98244c6f90b72fb35bf72f2bbb8b5d7","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.67-streamer-db-client-reconnect-09461da4e98244c6f90b72fb35bf72f2bbb8b5d7","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3f37e0a14589028ec3e611d35c643c23f0cdb633","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.67-streamer-db-client-reconnect-09461da4e98244c6f90b72fb35bf72f2bbb8b5d7.tgz","fileCount":95,"integrity":"sha512-EZhUcmexqXjlW/dLO7Dtf7urWgoiYfgH9ktwAq/6kFvoLVcnpxlfQYVv77REC1gwGrogJiXI/ih/zfJuKYcT7Q==","signatures":[{"sig":"MEUCIQCkrd29xRq2JD2tnau1pcvAPkhsnJoUukhogq64pecwzAIgK31OUhcpQmpHr9k8rBsjvypisiREFhvA00evnypophI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":582786,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf/CMICRA9TVsSAnZWagAABW4QAJk74lqmrXXf+wAFbstW\nEtAMJm12FsIqd423Y84Sid2MbKKDuUuvN5pDtzLVUGErq5sbe7gpYnqAjJUK\nTpmUtPmp1mySRZpmvqSsm+rrovpXhPiqkowX8BRf9zQNAO1oPZtFgKYmyAwt\ngIGS60BFORHAtOs9m3pXydEHyGbbHVinXq0de68DRqO78NafGwZ/YvgBwLvO\nIbSfS2UQdnRvTFVqgi8+lwzCAUjmijvk5O8EiTTBd5+wnW06a7yuHAj6ietL\naumyYRwbt9t8cj9WS1Gg76Ld1Q7rvQu8Ojq9wRek2674FfhlvK4X/oe7SR/b\nx+eKzKvX93U8Oqe+z8LEcH+qTAeDkbGwY8OcS9AoHH2eLG5NVQ9DIlGKvcoa\nRRokN197nNfxHNgSZXj1xWXoQ9eOCWFfZ3XBFCEeqTD+/FO/YVj13a/Un4vT\nkm8V22VhJPe4yMaI/iSENOlYNF3Sxju9kvnsL8F1H8C+Y8VrPOKqkEKZmBY0\nyBjDcPrHCDFf+PZ8wBXfvBbH7qop2cjl1V+hQVvgYBMtALl2ttRqcn1xgWe0\nuIn5notm1fhXEoXyJo9ouf2DT6r5ANrk9BUVRj8M9OT56IYcdbm98FMDSmtw\nOKQ9zf4Yui0VI9+vRFLOetMzEPy815mnJDrVZltGOydx/y7vCT3exzesMghW\nstcA\r\n=Ijrz\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"09461da4e98244c6f90b72fb35bf72f2bbb8b5d7","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.7","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.53","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.49","@balena/jellyfish-logger":"0.0.193","@balena/jellyfish-metrics":"0.0.222","@balena/jellyfish-environment":"^2.3.17"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.13","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.73","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.67-streamer-db-client-reconnect-09461da4e98244c6f90b72fb35bf72f2bbb8b5d7_1610359559684_0.27734037597248196","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.67":{"name":"@balena/jellyfish-core","version":"0.2.67","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.67","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6b822ac3efc24b097650fdd4eb62bfd2a1ca8b67","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.67.tgz","fileCount":95,"integrity":"sha512-dG4R0BHUbLnPQG2yFZ7fK5ftU4/9prbhLxcJgYm4b/qzHZqmp2K3q6rlKgfztTt/r5+m6hVD9KgVa8XnuN7N0w==","signatures":[{"sig":"MEUCIGOdaVfY80dZT+43Dye2I6E3gRpSITasoUMnQG/9dFp3AiEA4KxxHdvFRO8Cyc00K4szPsP50ijaiTLV9gYcRx24L1I=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":582716,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf/CQ8CRA9TVsSAnZWagAA2mgP/1Vk99HXnWeoUgufNRi7\nhg/kMNRIYsfrUFIxbmN9aMwR+R6CdfhrL+zfFmNgNZ9iIJLbzk6CUlO71JhF\nX3BsheF9ao74RM31YzgSSYV2pv3AP2iZ2IDY95k9mBMR/qOnDkbK6aKzCquz\n+QIylUS+jkmPFrFQ4pfkUw+ZEPnijZ+oFcapDBXum7le0wryDv3uSQ+a+vpA\nia8ybY6dC25WzCKU6h152FBI6/BfnsPynnvwWUHdxx+MQt1woGyMcNDZkdD4\ndrd1vjxvz4oUcyP7dckDiEBhygg9up0Hs8g9JL/ipUDOOnA79kLhOc+GodKG\nlzLiYVfGfn2U4lhA4lLIOV9j3QflZFBYpuayQhMp1P5dJWCaUnguGC0/Nes8\nTayJidBWt3LcVwYxvDO5LNihXczcaSZCREYKpN34OKgsLwr4d/ynjYcEIGbb\nkyWEiuvB73BnL+aBaoXMa0cVnbIkfl3LkD4VUIcFZeWgYmwMzSDeRycSwC/w\ne5NxkMxStZxRHP3jsq8LRJ1MOYWga1xDv6eYsZ9p3E/+JqNh8rA20kUnrW2F\nWtQ9bXLmqwFp1x8ZRf9S3HdLiQFImH+N45KWugRr8obyV93EbWNQ+aes7kC8\nuu1H7n28KUUcvshRjeCEfmZB2NRhO22/TJ5klHLbcpVkmgAAbRVSA4qH4MUi\nnXYf\r\n=3/6j\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"eb20d47afb98513223b8090d4e8a546941423b50","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.7","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.53","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.49","@balena/jellyfish-logger":"0.0.193","@balena/jellyfish-metrics":"0.0.222","@balena/jellyfish-environment":"^2.3.17"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.13","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.73","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.67_1610359868117_0.7967689625196528","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.68-renovate-eslint-plugin-jsdoc-31-x-99971b316149e93eda632f7931669bfe6b0a8c57":{"name":"@balena/jellyfish-core","version":"0.2.68-renovate-eslint-plugin-jsdoc-31-x-99971b316149e93eda632f7931669bfe6b0a8c57","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.68-renovate-eslint-plugin-jsdoc-31-x-99971b316149e93eda632f7931669bfe6b0a8c57","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b8e7c53ff0e9bee71a6d87aa0976855e8b0e925a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.68-renovate-eslint-plugin-jsdoc-31-x-99971b316149e93eda632f7931669bfe6b0a8c57.tgz","fileCount":95,"integrity":"sha512-poCh62zeqwCENOeG8CAvKdrGEksdaRcnFURj3URt28n6R/PXunf6WZAk5irQjHXPsprpxbjnUmIsuEmwB1aZTw==","signatures":[{"sig":"MEQCIFP7b67g4AUATlLJBONG4W20PhwG66SniKo3aqSoMsVNAiAGoqUGld1SBb3ov5krg5gJrEIru54rv3oXVaGRuy4MUQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":582884,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf/DflCRA9TVsSAnZWagAAxHgP/jWpbnMN2kDwq0pSJ4pG\nxjPIrWeWWt4ykSI8tbqEOhYAEJ2W84r6ipyWeorzt6CssbhFFq01T1bOr4XN\nIlA0Ah3/IDf5tIXc3QS6IzZd1xyZ13sgSSAsdO4vDqDiZzcdDzgYVe7HgOJD\nnRaf2v+08oa6n31bmpnt9ZGZzJaKvnG5yfrThzYpquNgPsXhZtN6GxKT4W1j\n/lVHaCeo9tuc5L2fIcthwyw6NWzE2u4WA2SnFrK2WC4XtLHP7ZX8zth8qgMo\nvCLnC0iGIWf1qFVSzxwZrv+WNennxuRxteoUXBh4ZFxaah3+ca8FLJVfmpmN\nqtbq0s0suDVuoImVbquQNzJeERS4tDn7FxqhL9c6flNxXBLoGEUFU/pL+WwH\nsnQvc1cOxdt/uWFRc9KvRA/flZefzt0lwiS0jVhIVV8oBpuYIQD0/Pzvr3og\nf27j+oPofMrxz8CfMkxKCLcY1VA//JbFVquXZHY4+iFM2+4S2N23F2XBatCe\nMy2NxtCy4v4iroqrLROEcYKFCMQBIgFxZIAJ1B57V+NNEd6fToDI6vkXVlN+\nYorKzpTrtgxjOnca4i8AX0iHNiI6kekYRrExsg0c7ee0I5K1h/7F6n24cEd/\nkoW12JwL+Yia1GR+HHBnzhMiojgrSc408hnHlZVPCGFtfgAOK0RvTWsk48Sh\nXaLy\r\n=YC3a\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"99971b316149e93eda632f7931669bfe6b0a8c57","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.7","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.53","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.49","@balena/jellyfish-logger":"0.0.193","@balena/jellyfish-metrics":"0.0.222","@balena/jellyfish-environment":"^2.3.17"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.73","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.68-renovate-eslint-plugin-jsdoc-31-x-99971b316149e93eda632f7931669bfe6b0a8c57_1610364900880_0.8445039228973061","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.68-renovate-eslint-plugin-jsdoc-31-x-39436da3290bba50fc07e4a3b742995aa2e13824":{"name":"@balena/jellyfish-core","version":"0.2.68-renovate-eslint-plugin-jsdoc-31-x-39436da3290bba50fc07e4a3b742995aa2e13824","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.68-renovate-eslint-plugin-jsdoc-31-x-39436da3290bba50fc07e4a3b742995aa2e13824","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"68e451340960916017d4dc4e88e5a73d3c11dc57","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.68-renovate-eslint-plugin-jsdoc-31-x-39436da3290bba50fc07e4a3b742995aa2e13824.tgz","fileCount":95,"integrity":"sha512-v+Ea37HGChpan9IB7wyDrdn4niFDWrkqT93LJuerMZhcFxvGRuYQTXkR7B3DTGpGaOpx47V4MsAtHHzYamG/Cg==","signatures":[{"sig":"MEQCIFT8Fdqs5FYLJ+uFfmwoD/YP2r23GDS1NKKQwptnyhjhAiBejeiIwrK35oTxm/qp0l9MgIBGvKGhcQC0EuToGwWR9Q==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":582884,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf/FSdCRA9TVsSAnZWagAA6ssP/iqaTaDYYmbp/2LY4TfK\nCTU+2K4xyokFVaMOnE0Lh7eKdUqnFlh+lkwpFgYDMNsz2qxKYRXySjpWCnYA\n6aNGlgS/K7FY+MUgMKNr7QE1lQ0ZzpPCHw24FjVaueOhMmAJHAiKif9/iLVh\n9Oj0RotiG5/YVqxNL/4z25Gx3Eo2yLXn4sfGjxk9jxrFSZJoZUfXR9AXpoNy\nFdStZXzzeJSpHKGs62iEKk8zQbxBAGMTbkFffKysLx4ABDW0UiUD6/u0jaa9\nBzjeLMULejtYmnSArvxZupN9OkLwpjzp7yNLznJv8oxcu96fSm3dh6kTc3aq\nrbzE+SmoB5U0Z2W6UaoUyEcVHN53bwXCo8lIO5LVzolfDlcHKL0MjJwN8HK0\nBDNQovzzJ8Q4v9wIKcD8TdkSslehfr3VfCXUfhXWQK7Scm6/44wlll3O2Kal\noT7KaFEZsV00uiJpQl1sNilihR0onoS2h0g8E9RBCCoxB7G6Ldlde4CWinMf\nO49WLbzYxaUWgxPRH9NXnaWdYkRAAmz7kKS4iKCbZhjD4cHZ38uQ+IcI9LeN\nlRFD6pKRL08vmjnoHHZicrLPM+SfaWI9VB2j0YVZ+6IhbnkUcMHhqnEGPu1c\noIWoy8xWGsOGmcelCbPgbkBmB0cXPdSAuPmaOZpt6XZkkntBvx/F5aFPgkdx\njnw1\r\n=8tdB\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"39436da3290bba50fc07e4a3b742995aa2e13824","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.7","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.53","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.49","@balena/jellyfish-logger":"0.0.193","@balena/jellyfish-metrics":"0.0.222","@balena/jellyfish-environment":"^2.3.17"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.2","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.73","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.68-renovate-eslint-plugin-jsdoc-31-x-39436da3290bba50fc07e4a3b742995aa2e13824_1610372253223_0.1729703785529133","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.68-renovate-eslint-plugin-jsdoc-31-x-a4cb4a6a3d78cb2fa551dc413a4ebe1f3d7c3b6b":{"name":"@balena/jellyfish-core","version":"0.2.68-renovate-eslint-plugin-jsdoc-31-x-a4cb4a6a3d78cb2fa551dc413a4ebe1f3d7c3b6b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.68-renovate-eslint-plugin-jsdoc-31-x-a4cb4a6a3d78cb2fa551dc413a4ebe1f3d7c3b6b","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3e28fb3b8d74ded0ca899578c9d2ffbf174e21df","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.68-renovate-eslint-plugin-jsdoc-31-x-a4cb4a6a3d78cb2fa551dc413a4ebe1f3d7c3b6b.tgz","fileCount":95,"integrity":"sha512-OYxl2ZtqtFWarIp5Xk7PAG7V7khV9BKzmqssZGOJWRlA06MbbDrgRoD66OIQBqWMJzdpk9QLhm6Q6de6w2gKkA==","signatures":[{"sig":"MEUCIQCenrwW5IeXIKmiYYmTJJMoqh0vguu9hQA79A6VOPea+AIgMb74upS4pVHd5uAXiWeu3PeNFcTJhWkfuA/OHeKYr+Q=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":582884,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf/IFdCRA9TVsSAnZWagAAfrgP/jpNLVa3bZuB+oSi7wn/\n8U8gB0sFJT4rwMslTmZJ9Y5Fo0MY7+9CIAyfF2nRnXo3v9KWylg/cK/HfmjB\nYJYxuJPKuzvbICfxvZm4werHEc+OHQKiTs8CI6if2YYl6J0wE3FssQmhaoXP\nysc+mDc56UD2BLf5WJpUmtvhK/s62pqSGDaSw39ItSn27b5qx0563QZYHJdS\nOEViQeaFywJPuzEn05aRtMJi3U43/XdFDMJ43mzY7KcD/WMtLY4ynaKrUpKa\nqubyQZhS7nJmgD0Joeapdr36eeaO0qlHlwHQvoztbNCg/1SxvlRfBmpbkVxo\nJTU//TUFIXrwUjRqd5JMBE5foBRngUoQdmZU/Tzk52mFrKWhGzerGGFk2gQw\nrFU03D3ZnvtV68F4CZgjlD5nd27vBxPo9LJ/qUWuYxZ/rLAhHHGbChoLPgU/\nJ22dnmEEGaEcozVT8B/r9wDlO5rdWDSp5qMLDVbhWDv5T8wWki60J2UG6tMI\n/BsG/B7Vr0eBls9eJbd+CQk/Otl+FfG2/9V8gnTFXRzMDq53falx6JkUt6qi\nX4PflXsZFttWsHLVuEQoP6jrI1p98+jakFtHVS8rjJViI2xIF1Ug1OLYotr8\nmrwSaiDX3VEQETk+Nie5vpml/dOwhZA1GY8/vUvEw0nMOjGP+McTMv1OuD8E\ngh8z\r\n=wVL+\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a4cb4a6a3d78cb2fa551dc413a4ebe1f3d7c3b6b","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.7","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.53","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.49","@balena/jellyfish-logger":"0.0.193","@balena/jellyfish-metrics":"0.0.222","@balena/jellyfish-environment":"^2.3.17"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.73","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.68-renovate-eslint-plugin-jsdoc-31-x-a4cb4a6a3d78cb2fa551dc413a4ebe1f3d7c3b6b_1610383709354_0.8904705766289243","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.68-renovate-non-major-e83c1a0441ab4d0ccdbff34bd60471497b868c62":{"name":"@balena/jellyfish-core","version":"0.2.68-renovate-non-major-e83c1a0441ab4d0ccdbff34bd60471497b868c62","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.68-renovate-non-major-e83c1a0441ab4d0ccdbff34bd60471497b868c62","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c4835283bafe2d2ed621294f456ded0a42ad4b72","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.68-renovate-non-major-e83c1a0441ab4d0ccdbff34bd60471497b868c62.tgz","fileCount":95,"integrity":"sha512-3FpB751QGiralCe47wMsD29HuUxS4UpLs9Qqqwzna7VMVo9OC9A8HspyJ6qg92M0N2XiO6NKx/bN2k2cYOU1tw==","signatures":[{"sig":"MEYCIQCZlTrIJXSko8uNWUghp/uwwOzY1qfyT22b5CXQNSrH3QIhAIWEXHet+ptYNwjlEl+KGppvXZvj611YisqKZZOIF9LF","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":582881,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf/PxLCRA9TVsSAnZWagAAmpQP/jw6gNmBThVK7Uz3/1GA\nnirl60AdzRCoUuNpH8Wklq7olz3pVwr2IPLpBgEUPqut+WMYvH1CuZgKkpdu\npJm8XOArLEvS6rBPQxqdp9nlB1YNGA8XQ3tDd7FCVMSd7NzgEcwTqj1RXyug\n8CPT40BRKj8r/K5yhk/UHSERNjct+KegeVflnEXijOw9z5roblYc6h4zgTZ0\nio8AjGfsbmMCVH1ViN46F/7fD3m16bBR5cs26TzT0rJvfDYlPXt0JoGSnDvp\n8iH/t3pyVJv0dmWZfW3rlI/cy0B/RWpnCBYAUTza2Wn8qrijDsD6gSJkvZAH\nZsxXnMN5zvYXuBXvZJ2HbA9BxUn04fC81CVnbYYWTsFuQ+kZdLOZNOMkobAG\nILJzbAKDo4d+ctuHkQsY9LWWiB8J0Dkbt8lYpYPEjUD8iuoz4q/231qUHh9h\nbx3N17WlRoc2sR8gRsAMYM7h2Ir7woaspEWq8cGgIT5z0caD4wk90apYWqhG\nqm1XUEh0OCYDoYxXEYGe6jBCxRzKZ5PX1vJOuLClR4UGvL/XX4bvgQFYNK9N\nAp1+PwMpm5U2DqwNDapj5RY78+PynVukt6cH3FyNedAC5hOpWFZgD+4YZ6It\nweSsJteyQuZF1pN+aa8wM/oG5jInVvVvZGoigVMh4/vWnwCmEb+r9sHAOtjF\nvMfi\r\n=5zbx\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e83c1a0441ab4d0ccdbff34bd60471497b868c62","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.7","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.53","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.49","@balena/jellyfish-logger":"0.0.193","@balena/jellyfish-metrics":"0.0.223","@balena/jellyfish-environment":"^2.3.17"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.13","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.73","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.68-renovate-non-major-e83c1a0441ab4d0ccdbff34bd60471497b868c62_1610415179083_0.5371980860311054","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.68":{"name":"@balena/jellyfish-core","version":"0.2.68","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.68","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a25d9eb4ba8c8de4de26e429f71d77b1407f0649","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.68.tgz","fileCount":95,"integrity":"sha512-FbNNS+zHxDn8uuR4uw+lbqXiLNnxtO3KdPHbMiIX8Hzu6xRMxiiqq/PEX5Tox2igEpI0wtWhVCaMxE+pq5gAaw==","signatures":[{"sig":"MEUCIAx9sLk+7pecw5NNKKJOOk2k/PSa64UsllaIZi90/hCmAiEAipqGi9fL0W7tQeIkrbiMqBkICbDG0J976H0lGLhXZHk=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":582821,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf/P1jCRA9TVsSAnZWagAAdYIP/0fdpAz3m6LgMXAxkb6Y\nI5YlUm2yxUxvA1Up4RzEHOCb3EhbXv1qdiOUyxlK1ZJ1zl2kjAJmyWhDBZoI\nNyotoH8cUznuPNHyMBimvO4P9e0+nkFVy9k29trwhZeCkTSg1+EXwB6z8ely\n3oFxidfoUab7aP+tgedkMaQJrfPQ2JhN/lD8QkiWs7RMOrvgXQVbPevbDuBl\nLzGzDse2ataNAqhLVAXObwVgXqMdtYpxr820d30RcGBILSvsvh9uzFEJ65lL\n07Ll2iZrp/3K3BPUdljkYbetIFC8IujHG5GtlX8I04gZ0o3+mNLTFpugCRDs\nob/b4DDOELoMOOvX50lIzv/6IEM/CX5J9TsA0KYtku+X5VrYdjJvOMYeRWDy\na4oRj09uP8GbKQe8mEd8KT4e/MPR/BvcPWe3Hb8Rd7l/TtpC0D672AAcp6Eh\nbpiNOfle6ZJuaFxG1UHckLGinCzcjuPNT3K+xYhtgr1HE5HW4V3r9koXn6fl\nnx8+CDbaX/CSEWEygolXo94pHz70OinlCFjbBYc5KB4XcN9uG5AgaijKf47h\nL+L/GTrup22JG/sRxC249rlqh0TlzUAJxs9r3PHJKECaDYZN7oyTqWC5Uywh\n9nKZt3eISL9nGNSXbgqUY2Xqg79/tuPpUGnludWBJz7O4kVfFCfsofl8gvuG\nKLlF\r\n=0C+j\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"dbde5f4df7a89b5610371231ba00b93a4d3ea37b","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.7","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.53","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.49","@balena/jellyfish-logger":"0.0.193","@balena/jellyfish-metrics":"0.0.223","@balena/jellyfish-environment":"^2.3.17"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.13","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.73","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.68_1610415459086_0.15166042619615894","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.69-renovate-eslint-plugin-jsdoc-31-x-21fb1281d7c8e0d2c8e266d36e17dac452caa06d":{"name":"@balena/jellyfish-core","version":"0.2.69-renovate-eslint-plugin-jsdoc-31-x-21fb1281d7c8e0d2c8e266d36e17dac452caa06d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.69-renovate-eslint-plugin-jsdoc-31-x-21fb1281d7c8e0d2c8e266d36e17dac452caa06d","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a376b238139d1b18d58b72f90f4d05d65f9c7b7b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.69-renovate-eslint-plugin-jsdoc-31-x-21fb1281d7c8e0d2c8e266d36e17dac452caa06d.tgz","fileCount":95,"integrity":"sha512-H0QiLlk5kta5bAdU7SY2BmSN1y7Nax4P+24G/6kODwlcDN/Uz8SQIFEntwNk/3OI8j/ngKvNPBdndkpWAWqx7A==","signatures":[{"sig":"MEQCIFPIpwtVt2tMarjjy+Eee2RhZpwy09Y+9By4d5aXTeE5AiBECV6Vtp5nNt6P5vqaZKSw1fK97s8dzcStPQ/e8tjkjg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":582989,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf/P41CRA9TVsSAnZWagAAJtsP/0sY6OJq+CUiEcMrejpE\nuxk6OSTvnKtb3jKjeUsHuNvG056yzI/+LP8Q4AHYBfHFCOFPTufhN3i1vKFr\nzi3YcClZgVsVZPr7JZsLHqDT5TEMXvYNlCDM66SKhkyxeGhb9Q/oMc7htKmK\naiHR8c0nRI0SpNPlP99m1eU3K7aD/H7qz/TnpoISizKqk/B+aK/Y3DqP8chm\neo714L3ejBM8oHtRbUkI/5VBLTaiwnesH7qoTwG7c119kkChCVjBd7ibVO5B\nWjL77ayymAVhAtGwmtaH2bkrmBd2gSvpAxR5Yy7PvCpL0MsclXXY64nx5hJK\n/UnyzScNHtuPY1u5TNAfhRFnuGQidR1Ilmfdmbg4BAuKSnAaBgtdqbuVDL7n\ngEmN6VnwjdQpq4UI94kiskbf3AT2KpNlqmMU1WMqkX8wb+TRHA0h2LjZA3R3\nwiC8cU53dfVI07ybX1eCPAaTJ3Uf1yy7xjrqjU5/5U+Y+73QOAXV6z9pjzoS\nvrWrXhU/sd6gjgTOYyD9upeFkqU03uHiz8boKCB1b0aoY61c8LhwJR3/Lbxb\nSoI3B9VouxWyTSjbl+193/3VIK3ET4hb/8kz39ENWT3nEeRBecq1zrbrvncc\nY4XZDlWqryyqAb8F60bdmrmW/+tWCZSUv+0u8iruhIuLhtQAEo9eQqc+dH3V\nIHED\r\n=PVMn\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"21fb1281d7c8e0d2c8e266d36e17dac452caa06d","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.7","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.53","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.49","@balena/jellyfish-logger":"0.0.193","@balena/jellyfish-metrics":"0.0.223","@balena/jellyfish-environment":"^2.3.17"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.73","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.69-renovate-eslint-plugin-jsdoc-31-x-21fb1281d7c8e0d2c8e266d36e17dac452caa06d_1610415668507_0.30433231403894867","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.69-renovate-docker-non-major-ba979142707d915c03cdbba5ef57bf81a49f38dd":{"name":"@balena/jellyfish-core","version":"0.2.69-renovate-docker-non-major-ba979142707d915c03cdbba5ef57bf81a49f38dd","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.69-renovate-docker-non-major-ba979142707d915c03cdbba5ef57bf81a49f38dd","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"23cc87af5a583443131b8c1c67b763175fc6e927","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.69-renovate-docker-non-major-ba979142707d915c03cdbba5ef57bf81a49f38dd.tgz","fileCount":95,"integrity":"sha512-W+KbknAnXPRzuLw9mi0Gl0ptR+SXGLwHzal+daHoLda91a+HGhC9gFX8hegys4IuDDd1zsNNisosOF5huFW2Tw==","signatures":[{"sig":"MEUCIQDx0TXsN10n6VlfkdsZ5O0a8G5TR110xByu8VeGTfp+DgIgFyzJIpJsNuZVrHDc1nvULL6CShws9FNr5wBR+sqpkx8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":582989,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf/SkUCRA9TVsSAnZWagAAgfsP/jZzsfH+db+ZJuokOogQ\nMxTvnrAYcgJsnbfEGiDBptlixyMIIcDMPSOWlHmUBFyVRtBcXeIAIxNmTSO9\nEchbVY5zaylr8UBwwzbNPDz6O0b5RlfGQEAgj8NQ+yKbZ+r0zorcd+FwKHD/\nJL9kBNRCov+LwnUTwaVOky0xt1IZKeYrQc3PPV0hUbPUXQk70qB8G2XGfi3T\nkRJ7CA+pNElP6NafQDE/qslAcsJQvIRUM+h9pvkqMwovwv3q4GL0vgGmoHqR\nPi871cBzhZIYe2O6soYPs6/ZXd83RNUm9/5TTY58/mvS2P2zlYHe2IH5vlzA\nRC8cALJ6iq42seaq7dgTxe/pimHSSR13Kjw9jJTADzGnz95IHoIju2k48KAH\nDbQLboakZWEMYl5yatoSsjX4qnRyVc6BZLtcDN69MnBD4B20mcLMlyBd3REt\nA1w0jYvhlxsGr23uDjd1DHiQjZvbcWmp8IkK0AWYOeQ64bmwkGn8/ovbZijQ\nUF4G7PFmTvqagzJU7eL3vheE6pjDc7HcYLV6Ke5vGMV39o8R9utNqpPWhwqE\n57gkvVU6Iai50L3OjBcygyvkNsqDT4r0rMic8evkdpoWU773XkuVfB7WYU4E\n7R9twW9jpNcq6t907HXP3evXbVbIgg3Esa9YJL6vPv4epwitywr57ql87ciZ\nrVfe\r\n=IzFn\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ba979142707d915c03cdbba5ef57bf81a49f38dd","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.7","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.53","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.49","@balena/jellyfish-logger":"0.0.193","@balena/jellyfish-metrics":"0.0.223","@balena/jellyfish-environment":"^2.3.17"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.13","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.73","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.69-renovate-docker-non-major-ba979142707d915c03cdbba5ef57bf81a49f38dd_1610426643708_0.7681212444212335","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.69":{"name":"@balena/jellyfish-core","version":"0.2.69","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.69","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"df17fa8cd01a4bb0114ebead67b7bb2223772025","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.69.tgz","fileCount":95,"integrity":"sha512-vfsDXqwXDEWSbsR76LSEf9YqvX+6l7ndyTxA7mbryFXTBGNW3AioyYkiJGI4gIH1qZZzRzwHyfOVTyi1W8++wg==","signatures":[{"sig":"MEUCIFjDlv74zwJeCz8bV+pPxnX1vVzn+JAJPiANdzMJB0YMAiEA1WNZjZbXa/4li11S5+OM5BRW6dPL+YEr7q8YbHQ0pj0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":582922,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf/SqhCRA9TVsSAnZWagAA8GIP/0XCSUaEqqhP0tYP7PMd\nVffJEGEj3MiSDlFIkV7GQ+C8GXAIl7vevwoVGxVGEd9ogSbWnN/m2b3HtVGA\ntsZpSTFy7TpZIIMZnde1kzKYlwCL/4pqCcf2ttG0hGoXDGetmvBBIWNmfmQ7\nxw37vqo1HZe2mU79h6dwYBc7NYLINPzM5G4qn/unhJVDF18JM3ZRqQb7sfqI\n96kP95aIQmfrGRv8P30NsH4NXvGoAnLkVpFrY6t0FXDL+4lSjo3dsMnQl7km\nR2uC8GiEKNlWyRYv3Fynbrv3pPL9xGApKMqHuY7l6uDIvjRR4BqypeIt8sZT\nbnsntrpD/9EWmYl/4AGJsJ64WV+gO3xrVl3yK6gADHfhY6g18x2gVs2Shk1S\nBTvHrURuAY3+4ubo0GkWOhf2ZVJrdaMj7jNn8Iqs4ovWw6piK7mxSA6NTPLF\nwjSj/i/F+xRMVXiYLPzYm4w2KLBMGw4dzs4rTwEUkJGO/JLFnrDlzltoZmZZ\nrdzWWoJvFhoEtXDEUMPnIhFZHsttOsuWyrx56eY3M3guZf0bD3X3VMU61opG\npwSKgtrYPH6sYO5IaPlBZvTl+/78CBYg2/YqpTs4uXFk92Ncg57I6seNKOha\n2PA09ZGyrHm1fb7LdaDGToY297nuCE0mtvkN48MLA6y4xCdLIVo3+5rfupLa\nxTl2\r\n=KUe4\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"93e6dc474097256f06235ce459ba12ba2bb3ef04","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.7","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.53","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.49","@balena/jellyfish-logger":"0.0.193","@balena/jellyfish-metrics":"0.0.223","@balena/jellyfish-environment":"^2.3.17"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^30.7.13","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.73","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.69_1610427041013_0.6786946252100603","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.70-renovate-eslint-plugin-jsdoc-31-x-ef87ee1a61e8a5dbfc3f22b26fb4bda378763b2c":{"name":"@balena/jellyfish-core","version":"0.2.70-renovate-eslint-plugin-jsdoc-31-x-ef87ee1a61e8a5dbfc3f22b26fb4bda378763b2c","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.70-renovate-eslint-plugin-jsdoc-31-x-ef87ee1a61e8a5dbfc3f22b26fb4bda378763b2c","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c3d07d3749597b727c0d9309533cd616c29523f7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.70-renovate-eslint-plugin-jsdoc-31-x-ef87ee1a61e8a5dbfc3f22b26fb4bda378763b2c.tgz","fileCount":95,"integrity":"sha512-PZZfixsXLb32SfxEM1qTZtAYNRqllmJN84zCAwF5XoL0tHgHT9ZUOSsCI2wyRACzc3fpkKsFgUil8XGNY06fHg==","signatures":[{"sig":"MEYCIQDGcZFnRY3uldwg8WtkQwAhlZ4tVDQ09sEmmlmqyS1RYAIhAKx8OFezLgljppJwAkw7h1P2jLJZmelSz10AOw9O3XSp","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":583090,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf/SuYCRA9TVsSAnZWagAALCwP/1a9i6CeQzteAM5qp25z\nFJvtYaqfFAlmU6qbixSXHsQsmXFqXLW+/JnGIgYUutYhERvTKf2SH+R5dn/u\nP5iJsdzEqpHeM/u6U5JcCCpz9uY4akMEg8lGU2MyUEUEZbMCI4ov+pw6ZP7s\nTg3i6NI4SdCtSSERuYnburgrId6Z3NS/AEQ5f6JAFKoSBKHwML+5Jk6BIjxW\nFayCtzor+NRPjhuSsA4Jiocd5Je8gHm7+5vgor7yNo6ClGE8LhTMtfRRDf2t\n4VAXB0TgqWzy2ok4Xl0X+X3xjc8B6xxGC1APf/S8RFb1AEvgQkNKTHTaENYF\nCOrpwouHitTPhSGFA5g1SXg6FdOLXrmM+yDaTJIxFvZ4KJxwPwqZSttP0xY/\nvHhvg33mWsVK1abW3sy8Aed53w9/eHBusjk7kZ0G8FmuOw3CVZ0O7JW4aeFj\nhE0t/m46upmihlQvdWxNw6NhXzxQaVtt4r+aKynL5Mf0oJObG9yw88PJoVeA\nJDM//0I5wmjcIH+f7+fGHcxN867NTQ34wzKVcbeCyY2LRekGGs31dQ9xFNZC\nhA7Yhgq0K0SqlOZT0wBHwb/iM/O3fRAkLQjqPTkudwKLrTt6s2jYJyvIMK5j\nL2vyXSAJW8VHaxSM8Nne9RGCqjAQktJt2DisgNVJMnQN2vjNDj8dp7KbXhgu\n4ZQ8\r\n=Z3kN\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ef87ee1a61e8a5dbfc3f22b26fb4bda378763b2c","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.7","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.53","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.49","@balena/jellyfish-logger":"0.0.193","@balena/jellyfish-metrics":"0.0.223","@balena/jellyfish-environment":"^2.3.17"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.73","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.70-renovate-eslint-plugin-jsdoc-31-x-ef87ee1a61e8a5dbfc3f22b26fb4bda378763b2c_1610427287756_0.48496951627993057","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.70":{"name":"@balena/jellyfish-core","version":"0.2.70","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.70","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2f9f56634a159589f21197a0da33e93b3c4e9053","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.70.tgz","fileCount":95,"integrity":"sha512-L+2bFTDHnb1BKKbSCFlGTqSL3ptaA0TBeVVh8Jb1RDMca/wltRpOvBEMFhGILe4mXwZcvrAPu43B31TTOf8fVQ==","signatures":[{"sig":"MEYCIQCmeJJMhbmq7I6nPltrd6yR4TizzKWUvaQ6DbFi07TfNwIhAIv/hFQV/ILZU8LrM2dJTn6dENwr+61B+KX8rG61uqP/","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":583015,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf/jzmCRA9TVsSAnZWagAAkaUP/iHNiNCuHVBRcet4Jxhp\nd/svFwKOJ6DqQjfBOoQ7mCaR9biLVgErqESfLAveXfG/CDoXCRvYleyRmt+J\nB8Vbr9Rx43goXJt0lJWFrhDh+U5ZJDW1a1RBLHHD/ugJ7HE9faa/lAke6nsp\nFDOX/IOQEQMZDUF4Ldxpgj3pr9CVPfPU7H9o/GxcHGU6w1LE/pIiInMevYfX\nXeTpVMTB5AmJwdOWZRKOm2J8Bk4P6x1JJoQT8+1+mw+vpNdt1djRx4l+3U78\nMHJd5ze3vKkqQPdrs9RZrvfPXIUJabBUoHsH5SKldItPl3grBF/wHi1wVNlI\ntjrx9XAn7e8n29FFanEuCeCVIyrNbjEKegKFkXkRd+VUySzfshLDXiiuCZ4T\nRE/ml9xn5UN5O/RfSVl3xEdgZgAYyUU/clkTZbKM4SFJUXW9wjW43lg0vjeb\nk0ZlpxfCljTu6DsyfbL9okSrvEh8qSBgiCTykQGhAm7sB55P4wbs2NCcOvxO\nUdAMeYBMLIXKkQuU///aijixbMNayCytm0SXpr6QknriqxN/xQCsCThDJvwO\nxTcg8lF9w5uhrpjTEzh6yQlvNNgc65V76wGOAs0EzCkeGu9B/cuHHqth/iDc\n9Q6BQTrwJhTPiesUZEHenzYcBbnnikqKmEbeY0+91SyMESWMkm3jPti+TaBH\nwtUJ\r\n=8UoQ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"dd5a749656e3607c06362d6bb81b1d35cf19fa5c","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.7","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.53","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.49","@balena/jellyfish-logger":"0.0.193","@balena/jellyfish-metrics":"0.0.223","@balena/jellyfish-environment":"^2.3.17"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.73","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.70_1610497254235_0.7598023421047151","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.71-renovate-non-major-43d0f4de85f6460f412b38615ca908b9ec14b1ef":{"name":"@balena/jellyfish-core","version":"0.2.71-renovate-non-major-43d0f4de85f6460f412b38615ca908b9ec14b1ef","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.71-renovate-non-major-43d0f4de85f6460f412b38615ca908b9ec14b1ef","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"86c8a14b9c0a2aa08976ec51e8a176900698c587","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.71-renovate-non-major-43d0f4de85f6460f412b38615ca908b9ec14b1ef.tgz","fileCount":95,"integrity":"sha512-cdQQrhh82n2rCPJFXHeQlhT+NoSXpho4UbraodWBsPEZOSC2MPNz0i9Rc7iQEhCNhAfJC0IqFHxTM9RATgwYBA==","signatures":[{"sig":"MEYCIQDa/gcVBnESdEJdVBIIeFWcCWxN6A54C5ZChVm7zKRP4gIhAPD9SsPiw7FFVbttRgGr8j/oVtSdzwEv+YgSDjG7awdI","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":583180,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf/oY2CRA9TVsSAnZWagAAxlYQAICwWwUobH3vKNueRzI+\n3RTootXFUGnonVvl2/XHRcX4xAYLADMNwjLNgMU+Yvmr2LNP/zBzHCL3/rtC\nbOCBDh2zq63WgqOEWtUpiPEiDSVOfVj631XqWNssOnFYu4QgmKYDLWItZGcZ\nRMQ209LBe2lP+9WPzexE4YNMkighMBMNJnneM8qYY2N5PQEThD6iwW/gI5b/\njvtoMYZkkA/Fsqycatg4vfQGsi9pvhu11ruH7bsEib++aS2ZzUaRsQz26zln\n45bXhD2mbljvOz+bVS+rvW9nFNbgln+87p7YRx8ZLLSFzIXEPvQi22/rCSIx\na+svLvWi/fAtxjPtrCHcK+9bKDEIDCiJrJkidCRWu3gJDH02w4AYp6hdEg/n\neHYWft2eG5+DENRFfwW9NfT6e+3zZ2cA6xwVtm8D6fa+T9/gNzSl8YUw8UfR\no6U79Dxm4ikZa7yXTIzae93ZPTXIjZBOnwZLw+NbDdth7NhiD5aqNUypcbX/\nhkxzCaAYo3i6HJX7ZOBur7qKGU2mBDHf1FeV8KtDFhJ5O56bZPb9De0jaJ5a\n8yR1e21YnLmFsGJLeWt7o/dGtCzaiyAYMlxj3f5Fu3HHBQ52UKWwFskeIyYL\no0Q8YMCwa+uM/KK3gmCtdHPzLr2irQm7Ec63uGGePokrXWEuPDf23zoWHKRS\nUn+W\r\n=rQv4\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"43d0f4de85f6460f412b38615ca908b9ec14b1ef","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.7","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.53","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.49","@balena/jellyfish-logger":"0.0.193","@balena/jellyfish-metrics":"0.0.224","@balena/jellyfish-environment":"^2.3.17"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.73","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.71-renovate-non-major-43d0f4de85f6460f412b38615ca908b9ec14b1ef_1610516022165_0.9112385250211681","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.71":{"name":"@balena/jellyfish-core","version":"0.2.71","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.71","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"95f55f1efcfd6c10d2a186cfbb4d48eddeccdc22","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.71.tgz","fileCount":95,"integrity":"sha512-s2QBa9lU00XG3gnMofnNv0k0UV73vcDKq57hwxx5trkr+oT5fY+Q4Rif3jTkm+KLsneR76MuSzOvD/jwBcSZkA==","signatures":[{"sig":"MEUCIQDr7Kcm2q0sQLh5rvZC7EE4KEzr+KlWgY1igu7fq5TgqwIgc1wRUv3qipCrTadlDxVDvhif56NJoWnYd0sijuvf3g4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":583120,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf/ofcCRA9TVsSAnZWagAAZ+QQAJ9jdg3w8IxpbRd6kFG9\naQSji94xsWci1F35HUIldAo7emb5tX7vwEE5kAo0xaxuE5LH6DU+nTBRe9iY\n5YCAbsWqpLbhENlbMWI5eYhF8BNMHHrvTJC0lQxdndP61OFmnbmZfRuuXl8F\nONegig5AmA1Ugx20kbhtRJ/h9HLPR4SoQNcm4dGmbxayGTefOeVHu8ciec7z\ni5E3Ho18oKK0z+NzqejbKY2dbFEwWDA7p7ke4V+ko1mmzCXFYh6NGuPZCQNd\nE18GVQPJf+kQ5h9xhRYdzWxu15GpyTY+VuF9XxkQ7LP12iyAflqjf4MXNb4t\nL2s2W60DtEceGzDMlZEUvRKl6PASszlQKaWprz7WyDApJ2t0LNeBFc7tJrhw\ncA3E7aD7yXnlV8AsU7iFYw1oIWv1O759YeXnbwA693gdiKd5n4VYV0cmKpte\nlWoubt2X/+DLoRjam8o3jpKnO0c7PmnFrGTdvixFZcy15N0J/tzobA2S+PAb\nMdgnbiVSkJmBSmt3aq9UD3eGXnsh2/gl1qrgNM2ymkQ8yGqsN9OR0JdnNOZ3\ngWwdJzuyS7xMSYx2opBQJp5064Kuf3mQK/NjxZajaj4CaZbibLZRcnDwLxAi\nYJcdJjaxFp9d469ctQSwRwDP7vQ1Q3Xfi0d/m3fr3Bl05T/oq4pfKOS/awvq\nRpj6\r\n=FhID\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"12c7f36d8cb331aebcb32e1e88991d6b98252889","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.7","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.53","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.49","@balena/jellyfish-logger":"0.0.193","@balena/jellyfish-metrics":"0.0.224","@balena/jellyfish-environment":"^2.3.17"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.73","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.71_1610516443866_0.6967127551157475","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.72-renovate-non-major-e41057726b2bd9f5c585cce49172833a1ce80687":{"name":"@balena/jellyfish-core","version":"0.2.72-renovate-non-major-e41057726b2bd9f5c585cce49172833a1ce80687","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.72-renovate-non-major-e41057726b2bd9f5c585cce49172833a1ce80687","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"80c34e5c9ce807bc26acf90384b47387846b4743","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.72-renovate-non-major-e41057726b2bd9f5c585cce49172833a1ce80687.tgz","fileCount":95,"integrity":"sha512-zwQ1oMNq5wm16kqXCa20EG/SFX68RwIyQlzSpBKIjwla9G7wsfYTL0Uhyf4y7xSsGkk9Tl/7yaoSi73b0LOAcA==","signatures":[{"sig":"MEQCIFP+5jkloh2fRxB6Cvx5bzioluZvI7YZacmL6NacGPTrAiBD6KoDw/ljyVe9NFMx1qZIyPjrmxOkmlkhA14v6I97pg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":583281,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf/pf3CRA9TVsSAnZWagAACM0P/2aILFMRyaKAU6rbhl+k\nJgeUH7NOODfO3vqTax1XtKGOORE7CFiAMesPLA6mp22m40M1bs6t4tlJF73v\nstzCb94yZTB9T/94q6igBk0L6lRuDe/8PsuVMgAH3KDLMHLg/EMLvJ4aZuix\nQYEbVRjRjxCmLZwrDfh9o/gg8iyt/LTe4YV51tl+zXxyq2L9JaasDjSUbRBp\nT/jNUGn+b7HRU+8gHo/sTuY7NeUwlgPZEAro8nfPVr8J73kNR4qhFn1Mx7Ge\naJlkeCngH7BROuMhmW8GxAcbEFxiIViLbUWSVrdZGGOJE7xfJQgKmkI60W/3\nJ/5mlAFGT135oi9i129Hofwol4RlnQeH+XO8mxpboO+bzQYJ9Y2+Wbs94o48\n2AENgEdy38iIST+t220Bw94qlTlG3QJa/b/91SfLA9zDQCpJZoJyuWQSCGF2\nJNdwVUVKtmKSG+HqiFWyFrORRH23ra0rQ1IIeMuS6oS1Dvq+eccgFqXK6C0m\n0ssWmh5U8OEilMzLoprna0vv5QpVFKQLJFDK9b72w+zS0hYc1UqYI4l4G7me\nI8rPzyxetYRodzNm9LN9p33chOZUvIZg4gc/rLsA3sEHCgfoOR+bMujPtV2h\nynQFllhbzXNJ9hldS/JksaMIMIWr7xCtF7OX8A1jmcP/QEz/JTU5TP4JSwpg\nls/y\r\n=Qbrg\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e41057726b2bd9f5c585cce49172833a1ce80687","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.7","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.53","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.49","@balena/jellyfish-logger":"0.0.193","@balena/jellyfish-metrics":"0.1.0","@balena/jellyfish-environment":"^2.3.17"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.73","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.72-renovate-non-major-e41057726b2bd9f5c585cce49172833a1ce80687_1610520566569_0.8569343968716086","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.72":{"name":"@balena/jellyfish-core","version":"0.2.72","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.72","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0f77b2e9a936dae0e5b39f03be743078358b203b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.72.tgz","fileCount":95,"integrity":"sha512-rHQUAqGp+IibrLObK41VPCEj3rZbnoayKfzvoBNSx9olEhSAMUVuLTTF/W80CUpp7utmsH2GLn9Lai4eRMoIYQ==","signatures":[{"sig":"MEYCIQDydmYWtTlM1YjPQsAn+uwYGbgxqLrWMkjdTxSxl3kDswIhAKgCu2Gcc2570zlTYgYJemX6xjMDdMa/c9BB5KRXUHVb","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":583221,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf/pnLCRA9TVsSAnZWagAAXSgP/2XeUTYAm/oV7lv1Azhb\nNmh+xNRnnC0YPkruduRErTGe4Xw3yOOlieHCpCxAsLl+bw1/zD+M1iJY9Ncm\naAofe3QJCYc0xm4VQSu0LpcPZv16KBNKlMQi5fQ4R2iABa9KNuDbd388mSPS\n5kPjJh34DQvF/6q7Op3wpBJoSW0RQMHy07Y+1qXtEQLHM2oyNQmmXi1kPdE+\nexUqpoXJB4tZckDHcPAqjy+WRp+TdKadvG9TExMPqBYbTPXQxPswYHmr3Nxo\ngPb+dckKfdUzV7e8ipYcwWXDRcc0uhgTApz8J1vCvZk+jhhUcf4NsVBm/wLs\ncCysmiHzFr2ukNA6yTgQXBHdmQghY+VRbwN3IS392SAaS/YBCvL01P50Hoju\nlqr8snc79hkO1hfruGz/vnLDskm9yOVTkFIpdR6pnK8zjy8je5vNaHeit4Wh\nOvQmvXqqBbJ8+0+ztiBRC5LrMN8v4UGjhpu7HWUwud7zYlSqosPGy+pIyBsT\ntp7UlvS0h3hDZhLmwx3EekkpDo4+yBrjR04rppuq3ADX2i5bSiRsPBdArWi3\n7SoaMLFJBIdBkCFILkr0cPSiN6ESgrarXjmNzKJeDvIB9yV3sluI6JeRZ0n9\ng9bHO9FLr4Xj7kxZRIo1M0S8eOapz/mQfYjvGgzhVBh0lz4YomI5mkX1hy6N\ndu0N\r\n=o9dw\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"af62ba89125ce4ce1c4f8711d0346575baea8551","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.7","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.53","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.49","@balena/jellyfish-logger":"0.0.193","@balena/jellyfish-metrics":"0.1.0","@balena/jellyfish-environment":"^2.3.17"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.73","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.72_1610521035190_0.0397672650017209","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.73-add-card-patch-metrics-fd60d1329cac2dc254d7d173fe117e2eecb53a53":{"name":"@balena/jellyfish-core","version":"0.2.73-add-card-patch-metrics-fd60d1329cac2dc254d7d173fe117e2eecb53a53","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.73-add-card-patch-metrics-fd60d1329cac2dc254d7d173fe117e2eecb53a53","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1b09c7703216a8491928fa50599b851431be0ff2","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.73-add-card-patch-metrics-fd60d1329cac2dc254d7d173fe117e2eecb53a53.tgz","fileCount":95,"integrity":"sha512-nYID4ai9JLYZj/O/aMCWc+1k0hrOR8rjuXs90Z1WTUgsIPzxj8MtraK+SWDVUc1Hy0oAM9vQfLHYldq9iN/VRA==","signatures":[{"sig":"MEUCICd9qefgr2zpnlKe17OO9wa9zY5fjHemoPwgOnW3wjCjAiEAiuIziTAkjhDYpmm03d/EK7J0udL70AJys33cUpi5NYs=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":583558,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf/qyqCRA9TVsSAnZWagAAVAUP/A1VWjIF7ZVfghdSqDMm\n4C7OHqMtYOKYKk6o3VxI4GZC6zifWIYvMk0rPj32cYHaTYPUXrfkChUrRaXq\nnKwuPoWCSgE2FRMd7lkzpT+oAw+SorQYNimYsppTvvR4HJyHUP49RCXMfIMh\nwZL/RslWp1ALOeKUBuBF4XpSeSdb0yDS3fcwljaBvGHU/ARaOiP7EFJctjS7\nhd6ERH5kyBX1XU0Wrew89uPAkYCYTZ29mjigY6sNKJ7LVB35PUngmJakM7Bc\nsnexTF3/dyacem/5Rb5H70YTYQZIv/OASFXaMVhYoHcLrVu9ds+QbwMkQbyK\nTpDpiFlt/JkPo7NjzM/YUcUXTFPPJqmr80mYVB/sLazJE3XpZvzWNByS0z+N\nNBinBcEx3cHzp60nxXDb7cnxSRN97HiL73qSUWCFXduRTj8AndoPNPvg0qq8\nXbUCmpSt7f8wyBVAOQ0+rt3GsuOCizDRQbqF1z+uSvSPav2VOLBZiktJzPDi\nh5a5zAx9pCmFmaD+FXqdK75smfQT50JVl/IsQRvOSbjb9j+IGzKdd1wk8NcE\ndofAx3B2EFcnycre6x/9mLpNxm7N9pDiPthav831gMAEf05lkTywBBeKnQo5\nt54fq+rngIV2EOebIt4fyucXjs1fHiU7Sv8jXAVP/S4TbAd52NxSdfeMcUEC\n1dCZ\r\n=J0SN\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"fd60d1329cac2dc254d7d173fe117e2eecb53a53","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.7","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.53","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.49","@balena/jellyfish-logger":"0.0.193","@balena/jellyfish-metrics":"0.1.0","@balena/jellyfish-environment":"^2.3.17"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.73","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.73-add-card-patch-metrics-fd60d1329cac2dc254d7d173fe117e2eecb53a53_1610525865466_0.2896218520683318","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.73-renovate-non-major-d52dcc64899c4ce20abd67bece147643acca69d2":{"name":"@balena/jellyfish-core","version":"0.2.73-renovate-non-major-d52dcc64899c4ce20abd67bece147643acca69d2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.73-renovate-non-major-d52dcc64899c4ce20abd67bece147643acca69d2","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8286276858e0c7a2e9783cc751c1b1746db2a2b5","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.73-renovate-non-major-d52dcc64899c4ce20abd67bece147643acca69d2.tgz","fileCount":95,"integrity":"sha512-BxQw7bbZRcnWzHibffesTBCEnkpd20lKuNHEB+I+GQttp9Puf+5dY/5jNIQ487j7GKY08fV73LwXleitaOEAMw==","signatures":[{"sig":"MEYCIQCLkDmTiPM7XYx1AbX2iySpfztmdhV4cOO3Cykxl2kKMQIhAPO74S4Kkwx/yQJ7t89BtubXUSj4JIJUh92JMBKKDxj+","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":583385,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf/u0fCRA9TVsSAnZWagAAPeoP/igRY9QkO+DvfmeTdwID\ndSz0T27wnsvDOx97+TB2ZTkBd5EV6Dkf/B9GzjrtecnBDukEDoM/k/davJ5g\nHHiHbygfDVlQUkU5vyOfYCvFeOBYcvzg8UcypIpfvl2oLiIeTxtaFpGDW0HR\nSLO8ld3wpskXtqj5oiqZ73y16tDpd8XAlwncxpwhfWQtb9n16r1dDMU9OTis\nb2876fPmY7sYH36e8q85kwvKSo7SGCIg0Av+7rek91OzTpFLb3+5CxaqfOZs\nUiBUaX7TON4bFh9xaxCkeDD8026XcXd5ytIumhvfFlJkzfjVraAxfBYTQ08l\njgHxviwWUAxTkWeN/eFONXwyB0TTbs8Oy1TAR+pmaCV6phuChvq023JEeLtr\nKZuIkm7tfOc5WF/M+S6MJ0NV29wfWpoBjcF9SYp+geuovBcLEF8twi3v3thA\nwK38SjCan5qSixu8TWMOCKsDHE1SoGVLpQprlxkN/YrhqvDXzMDIqemI22TR\nUQB9BE6U/Vj9Q/jyc2zOG5/jxcw/HO/KXeZW/P3E9S+0aLBXCAVkPYd3Narg\nCVmVBd8F/heIJG+yn9sY1rvOvIOjM15KFVZGKg1bEUGWwPPMdkYnZAczVSKD\nZxYVOrn/v3rbFZSGn2Q5XQ6BV5bFXvMU5GZqexiBULKsB1pfK/kL4CRQ2K2V\nT3s/\r\n=F87r\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d52dcc64899c4ce20abd67bece147643acca69d2","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.7","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.53","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.49","@balena/jellyfish-logger":"0.0.194","@balena/jellyfish-metrics":"0.1.0","@balena/jellyfish-environment":"^2.3.17"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.73","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.73-renovate-non-major-d52dcc64899c4ce20abd67bece147643acca69d2_1610542366649_0.919728494107513","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.73":{"name":"@balena/jellyfish-core","version":"0.2.73","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.73","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"eebf8d8d45f5fc1844b8c59f19d8ae6eae18266e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.73.tgz","fileCount":95,"integrity":"sha512-/N4BwO7lSVNol2gDBXjExFybYZgBIlLA/YJD0Vk/ld3+jrLmi+tIhg634iJ7frR4EsCXtSoO/Z0veAtY3wLE6Q==","signatures":[{"sig":"MEQCIGe/+XUYZ1KtPKIs6jUFuR2/hfHZg+h/OL3oKlisFZiRAiB9ZgOQojSbWCXqc6I4JJ7Gi9avAkuPWZEl/9SxUdrj6A==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":583325,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf/u44CRA9TVsSAnZWagAALyIP/3IKdigd5eMivNEQA6wt\ntG3b7VK7cEQFMe1+E1zSCHSrNiCWsTNxUdP6FcCe5mwndWmJ0QEYclRupjhr\nr1PPw4o+HYN1gfiD1KI4QIlnmsuJTAkM/PnKpARFdvFX0BOWKt8ftb/hnvuc\nPRH7m0UgUbj/7ohzLpkGnv+Ql7ti3LOVOdrAlz8sMRofQi7ToNtwqBNo8PYx\n0SgLawaEa6b3ykdugJ33osiVxHbyRdqbazfj+ap/4JF7IeAFjhUanpXWVBd8\n8HPuBg1x5Sm7Fi+EK8djPPAn1G1Zr4APQ3nMly9oV3ngRgBZYshiH5d5kfrW\nqFUjIrBnFvo2KvkhwBa7e10LDP3P+vebvL3w1/ct1Vpv8a52mdzod/jLLEFa\nSGXjHgFyQuFUwScA0u60bbBf2mg6VrwgRvHBud8bjATC7J8bzhft4rl7euVY\nHvRiRyy0urrsRs8Bsx1KuM1F8XNWTShGO7Q8+jBcigv/9uGLPkKOf3DddDGu\necEAtayJI7avwqRNZTR+IBH2UzVJv2W/Kw95Njv+PXBcRWuddo/Nr3OpBIns\n4hKGlu+8zFqgk7CGilEYSG1nynUSW7hgwR8OHHaET6EC8wv3uA1y/9X/woMx\njN20a1du0wpFjQhpDd9UhSdN0T2IeUoLF+r1kjmR9l6F52GjY6d+utob4GpK\ne0so\r\n=7Q7a\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"35dd16237f2a543bf0517135bc61f7ec4b1bf2df","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.7","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.53","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.49","@balena/jellyfish-logger":"0.0.194","@balena/jellyfish-metrics":"0.1.0","@balena/jellyfish-environment":"^2.3.17"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.73","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.73_1610542648133_0.009291477431727646","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.74-renovate-non-major-1700f9439bd07ca47d74273022a297e48c9e5877":{"name":"@balena/jellyfish-core","version":"0.2.74-renovate-non-major-1700f9439bd07ca47d74273022a297e48c9e5877","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.74-renovate-non-major-1700f9439bd07ca47d74273022a297e48c9e5877","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d406782c1ed5920c5bae00dfc0396b097bb8b128","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.74-renovate-non-major-1700f9439bd07ca47d74273022a297e48c9e5877.tgz","fileCount":95,"integrity":"sha512-xciiPBN/aSJxI499lKZlCKziwm0dHuyqewzsCqVQh5TRpiIPxxs0llaqQ0VXdJ6VttU57b8pHIoqcc0Mc2LnPw==","signatures":[{"sig":"MEUCIA4nMYcpyYOyZJbNC9wVT7nWgYpA0wYLN1Ff9uf0YYzQAiEAx1Kk3ariv4b0uAo0WWbD7/VdpUi+XzDOmDgHIptFhbU=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":583488,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf/wFyCRA9TVsSAnZWagAAy0MQAJnJPhseFlWgfF+oGvzw\nKKkKx6Vk4kr78iboj+TNaTM27bhiVZLD0ypyEkr/re0TBjw8YOlzUhar7vsF\n/KtmtoWEIKbvFZVygv63QoVtYBKR9ZtGstzipbFQRj/Vj75czuj+WI1e96FR\nZgWxeIJgsS6UPSj2dZFXZiZAdRyUPBQQYtJwsYeVpF/Up+n03IHmBMvv7Mb/\nFhxgjwMjiW461dBruFM0BfirJqJ+6D6g0fPVoSLOUrTZnX5eel8g27hAkOR6\nXzGboHYCTSZnEvT5NrkiSRplMoYz/6QfNm0tbR3W6hhzfNmYKS9siKUh9jgD\nneCpcb1iS75pknAOuWQ+gnwa+ZHAABfaNDa+MTvOBR460XNgcu0YdNsHUbkH\nXzzDzlwoKLXvrSTBxSYrgUCpK9f7EJznuXNNfFPDS6IYebyOx9w+2KUM08Lt\nFJ7x/XSzUzwlfUo3nA0PCSJOS6m8Q8F417Dz6wTgqqjVBz8GwfO22p3y5SQm\n2rvrYbYG1gyJsXYxLWxBX/zzWihmXsf+5SkS1sL3Yfrnzw2VMva/ve482OnD\n8+iS12PiT+s89v3qsmDVp3xZ9RkbBk0MPPzG0X+VQp2EvjFeIKVMzpQfaw4i\nuCNZ1YfC23Vp6NkcRbhmYZ655reXMQy9Bvf4XtqNR2koU99GzK/mCB/nyTcv\nBKju\r\n=khMx\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"1700f9439bd07ca47d74273022a297e48c9e5877","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.7","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.53","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.49","@balena/jellyfish-logger":"0.0.194","@balena/jellyfish-metrics":"0.1.1","@balena/jellyfish-environment":"^2.3.17"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.73","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.74-renovate-non-major-1700f9439bd07ca47d74273022a297e48c9e5877_1610547569972_0.42319559453765043","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.74":{"name":"@balena/jellyfish-core","version":"0.2.74","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.74","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ce6541fd1e1efaa6b4c515c3f6e17dbbd4d8f79d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.74.tgz","fileCount":95,"integrity":"sha512-5zC5PYjI5vPZdumYLS1b2jvYZ81c8QsO+LsoUuKr3Sjp48LVgNgXFF3+giUplmBjzk+3gFhNbj+z5daNt2wu+g==","signatures":[{"sig":"MEYCIQDI+Bm/x4N18zquH8Dj92Ig8PXjO6QOSGAKOd/1gDj/vQIhAJVUL7KR57Hd/erXET26QKYWCOHxPkxK224RSWLm+DSU","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":583428,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf/wUcCRA9TVsSAnZWagAA7ugQAIZ3QxNxg/RMlmdbDQK5\nINC2D4gF0NSVbxdSl10W1IuwvGrJagDzDj1VEjMYlVqmj3dX/zhhI6gTMjf2\nEIpUzHLDWDe7OazatJEAU+q8xmwDZO0/AAbzQQS8lbniD59hBqf74m3uN/p6\nS4AUrDT0rsQiny4gQ/qPk9OuM9bgxu87M0Aj+xxfLakk2Fh2nXnQ5BmpfZDX\npZO4+t5/7WWn+jYAwgrOC0/E8GR+j9mdOMqCv0sCMQhzWEkrmZBJXNfDI6kz\nIfPAXjnGBwkkDXpG2J1nQ+a5WD2LJsLCwTeD3wWr66haQl/EJagDCt978Rs9\nvxebZGj01W7uYlD4h0CHDCP5EkpMkfE0n5h7s8DGtwYmKRzjEmEn2DX2J4gZ\nE1SF3+CHejYsn9oIWT8bGqFp/y6nYoU8o6bILMqWFw+6iZ3RnqIKPx/DS7GV\n5TfIUYk755MPU7FPkSfQ5Zgn15niNarHIt/Xxjhu+gipwclPJ5+kgvI9gld0\nZ6LY0z/ayqcFy7smv71sVHGSkIR2WEuAu7KIyQRWTU1KHYTWdnCPk0HZYPER\nVTO09t6y4kC8wuXyhjlXhCorDDoBbhOxL93k8GgcS8UcjUpwpZ6Z61KG+ytT\nu+E3GTfsOL10Yb3/1c9ISLdtGNFjaI/ZqoKguntPevmtkxzXHYvu6910voXn\nIXJK\r\n=oTOh\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0c597bbb8d1903cf4660a8960967082d6119e354","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.7","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.53","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.49","@balena/jellyfish-logger":"0.0.194","@balena/jellyfish-metrics":"0.1.1","@balena/jellyfish-environment":"^2.3.17"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.73","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.74_1610548495930_0.9545323140685249","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.75-add-card-patch-metrics-d251ea411b67634ab7467bd17b6692c9b678c3e4":{"name":"@balena/jellyfish-core","version":"0.2.75-add-card-patch-metrics-d251ea411b67634ab7467bd17b6692c9b678c3e4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.75-add-card-patch-metrics-d251ea411b67634ab7467bd17b6692c9b678c3e4","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4c75f40c2187cd6bf06ccbe4bcd777b8df86de05","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.75-add-card-patch-metrics-d251ea411b67634ab7467bd17b6692c9b678c3e4.tgz","fileCount":95,"integrity":"sha512-KhrX3xTog8GaJIfGhiAo/gjS1w4q7hWzkKFPdcJo2OJn8ki+W+nMYUZ8Juwu4BLFxNYn9SCaprJTMiduwmFHmw==","signatures":[{"sig":"MEQCIAykwwQxeWrW+lwx8uV966HOhFxbYykI6KTlyRAf+ulzAiAwKGetzGOD/WJIJYnqb/8LKQFMdRHWzbLle6mgEIBhkg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":583765,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf/22aCRA9TVsSAnZWagAAKLgQAID5EuQ5AGEUu+kAdQJh\nHcOe7rOTG015zwevL0VrxHy60/neHrzUBmkBO6psWXPwPdc3Ag7jyPRvNtFB\nmKf4iPxTSRLkFJ7mhYj+yT+QZ09eVRm3XJObBM9S96f3XuJ35SMIduFRgkqt\nluw5eKvkeKmzCAPqoIm97AMYP8GWvCpaPcviUfkJmv3yJM6K5hQjkxOJojnl\nQp6oBXjhh4fUvU8j2idQvQ8x5AW9Td823k7VmjNwwy4ckfQjHt6K5mQppREf\nxspc0Quj8VN2tfLSj6D+xTKiVURHCGu6ME7Su0NKl8tANmJLH6LQF5dVHH7Q\n6LK9GLj5Gg7N0y7lgOQEPFK7eRplw/hVFEbKrmgWTRjCiTYb0+pA3DuBNpvo\nrR8qzd4BYdRN15hyzCGLtG0rFmpjj/jcIMooN5UdG5+iK+oSo/MF9xvdYHZK\n+Ho+8XjhvmCZWz5aAd7WMCfKG19dx9Lf9i+CBUgKDI/ja800+D30qNmnn/A3\nPoyzOpWiIXif5q1dUC4yY9vCRIOCrFKvYtnGlaZxwHigpR8OBFzz2dqezR0t\nxlwcVKjVFu4wf9y0X5z3SSWBDEq8bZI42vWJT1dR9reM8h8TaWNiWAaauhsB\nRUb0i+Gj0sD5jD+Z97lEoq3qFLFnb1BHtlEsekk5sjRTlem8ZOxIbTcbNN6P\nSb88\r\n=PcKH\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d251ea411b67634ab7467bd17b6692c9b678c3e4","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.7","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.53","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.49","@balena/jellyfish-logger":"0.0.194","@balena/jellyfish-metrics":"0.1.1","@balena/jellyfish-environment":"^2.3.17"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.73","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.75-add-card-patch-metrics-d251ea411b67634ab7467bd17b6692c9b678c3e4_1610575257522_0.6536787963271529","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.75":{"name":"@balena/jellyfish-core","version":"0.2.75","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.75","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b7c955584340c5493ffad28c0f172fa712fce593","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.75.tgz","fileCount":95,"integrity":"sha512-kqzcsdJEj7DK1zptbBz38QGz48RojbvOtEANkYUY7lArWo7xL/pVKGIZw8GkLJmOuhQP95VHOM5CK8BxOeCMTw==","signatures":[{"sig":"MEYCIQD1IaeivEAs+G53Hfmw56hUFTxLprpAAGd5OCBunpeE7QIhAMHWkXYKJCjwH6UCKQgQk20R8EtKJwTUrwQXvq0x53s6","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":583701,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf/27ECRA9TVsSAnZWagAApMEP/A4/nQAP5+ktlojXuMBf\npsD/LDge+b/+VZs6FOXNxGtcF/8g2palSNlwrq8GmiXUzxZY9MMLPrEmwCnA\nJPLUZtSIVTaOGO5OadTB0ePvui1kNG/2LxiG4/HUYOyDRiha5mezsw0zINv7\nmV1DoRqfXjJ2mfe+OXoQKdpj6ApcpAnVUikT3AqbQucuBFOED53uuvNHVjxu\ngFUcMiSo9caZIh8ivlQeTK03vox2cX3I/6LrlmVdRR/6U3CZieISJ3bv7tjG\nVNZTK8n2mvdYB6uwrOIRsWZJbFPaAcUbe7g232b48DE8QAPuqHibCfOaOOYp\nCNigshTCISIRZvU3oykvvcJvfDYi+QHIYf1lIkPPso6Z7oeuK3JyXw/4QDqH\nhxRL+EaCvvPth1l+P83EPCD/g7GNIxDL5l4Zxeq/x8mojUC/yr+wN2Y7c0Mk\ndxxJX1a7lka4ivjJ3686WMfUVazU8lQ4bmHBKihLi2FYOQYIL+1voIcIhPQt\nu06MrMBCKXiKFVZ6cSplxxu7jqekhyXzajkZg1ATTqkchLD0pUq9cTkwTTWn\nTGBPNgt7/5hU4q8z5GZxbFegfvH/3eTgAzzT+nR22lG2j+4egS/GDQjaxLPu\n3LXyxD1khLZVAYHt/VKCSci2xqf10x6EFq4y+0JWF2oHjcssEmxuiU5MQ/zw\nRJ7p\r\n=hsnd\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e6e5a7b472a476c01eab1054e8b78355a88175ad","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.7","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.53","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.49","@balena/jellyfish-logger":"0.0.194","@balena/jellyfish-metrics":"0.1.1","@balena/jellyfish-environment":"^2.3.17"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.73","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.75_1610575555295_0.4429627989964806","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.76-renovate-non-major-d3e13605ea786a17105982637f75dcb4c50e3df9":{"name":"@balena/jellyfish-core","version":"0.2.76-renovate-non-major-d3e13605ea786a17105982637f75dcb4c50e3df9","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.76-renovate-non-major-d3e13605ea786a17105982637f75dcb4c50e3df9","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5c9ca11264c2408921e536e694817906ad2b8fb2","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.76-renovate-non-major-d3e13605ea786a17105982637f75dcb4c50e3df9.tgz","fileCount":95,"integrity":"sha512-q5eb9sDzh+iuyfv5EoTZeJiYg/kPcGagAnW0FtlkFH/IwM8tJoOAc4xCr1rDdgLoUTFMVu7ZaqYr6+mrtY29WQ==","signatures":[{"sig":"MEQCIEoNfUsVQqb1qpepaLA5pH83UFyuLt3H3TqxHzMq7Hd2AiBxOdKkwkfuWJkjZux8DHsBgwxSGvD08Enf1Re3qvtjGA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":583827,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf/6+KCRA9TVsSAnZWagAAcK4P/0R5vpYLXmSJWa+Su+zN\niJsbYlqIU4zam08p+VZ3Fu6voT5F37TF3CVceRUKG7TQIRqgfsSSV8T9l9C0\nfDFYfU4L0zgm8NZlkbU3MVeHSVWr42ce4/UgfoBnMj7GPMeGkH6duncz7CcX\nt0jTC0Ozjv/GI15MU3cvA2V6tLz5j292RlWeykeTSpAfdzJd1J1FDE7oEZaD\njv9eDwuwgsekKw987ZEQdAWeKxKnWgMEDMc1NmqVPMFyNO8J5g7Zn2v6R0cx\nDUXfcBcb6CD4SdJW0iz4QHNfY38xWdWDy8X21s7AJtOoMYqZeijWpl+ULWMd\nE9+zehmI6dpsf2/MXfdPe+osW2yrOkdNaOizy1GHcJhgqmeRJjOvFNAMu3GU\na/DM8bjxJQZ3Gqiq54i++f+WuIAlU3lH511HmzX/6zYDlrnVseCCk2Ku7dme\nQVNbKoK/NNZf/bc7unKO1X+RMhGL3+K15GD5+9wduJqJ5P1cvmGGxiDlfnr9\nGRbUrs2CGmkbNt5GuwM14f/YxVAh4fMK89PaiCdZ7bh8vncY26SPMZgPKZJq\nQ4grAarfmMuhZ/CDgagNfkRKS8SROZu2vTyjTaoYt8TViFU9hBZgIInyOaza\nMFr8et+5NK+12ZdbqonEpQxnanUEVUcSI6CVVnN/mthbM8tlpg6o3VWhUCtN\naT4a\r\n=FHht\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d3e13605ea786a17105982637f75dcb4c50e3df9","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.7","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.53","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.49","@balena/jellyfish-logger":"0.0.195","@balena/jellyfish-metrics":"0.1.1","@balena/jellyfish-environment":"^2.3.18"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.74","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.76-renovate-non-major-d3e13605ea786a17105982637f75dcb4c50e3df9_1610592138275_0.9499498481157498","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.76":{"name":"@balena/jellyfish-core","version":"0.2.76","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.76","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3b8dc417def49e7a84e5e3e51258e83db8fc51a1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.76.tgz","fileCount":95,"integrity":"sha512-Z9HmcQn0S9+R/nRq8J9e0VEQ93mVb5kKxc94DvVJ3CNRIi8JnmSWhI5L4e8RgmBnV0Wy+bAR1GdkPEYD9pUdAA==","signatures":[{"sig":"MEUCIFVaCk4v3N3DNNHdqHZpLnhhmoEQ4pZ6TIK7uoWvng9nAiEAmM8Dp/dMtlEhry7aRsR/cLdAlp68CE0HxUhvSrDEVkA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":583767,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf/7CyCRA9TVsSAnZWagAAXh4P/iwOf3BEBNUnEL7d9QeB\nyVkgQJ485MTc5NJ2vkvvhZLv8Q75WmrSF+NGGU71zHZy6xHi3x1dhWo9NTI4\ny9mIkCKIDhHXoqHqq0cJPTVJR5X/Q/MPeXoT0F07JtuBxxFGgx+BdPBUsFX7\nsbI0JQzSJJRyckBFGbLQ2X4JHbeQu+ze+2Yz4QBaPkRHzJNbqN7KompsO17u\njdRpS+cvjhIvgvuJITU7d3VmIcGpcncySLdTvqFM2bDpNSLdRxmtdUgAjEym\nI7kA8PiygPSaaeWzMrZFVg8rEvIRPbg6wYAIFpQoTcHfG6proUbVBFJIvmjQ\n/rF7whFRUzWcxmfeeedKfBNHQ1CDGbwjVdADPynQPMcKhoNReDuW/yHgYcOq\nKTja3n47qwqPDXE6oRzemAyeGSkoyAbkeUmxaR5Rsr6hqK6VwtdWhdV8qZ4k\nnR30R+bmcm/MyX/3SmlIkYNeuj62+xb5R8x+XsRsPFdXKnIrClw8yxFtc1mz\nt6fnLhrlnsWizkn6OOM4FfxwucDlaZkPbNTEtUTw6OMPSa7mOS1YdRTOui+Q\nTVO1WJpOgQ5Rp8+Calum8vbuI7HeXdhnmF9mGo5Q6UdmfNYioyzOluIliOf2\nMYPx2qZpW1SKT3Uhjg5yRXF+SuqsUPR9IIQEsYFKQ5mG0S0P7K0NFRaJI8E+\njfIi\r\n=s8jC\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b9aac2e54d7d0c836f9581377df0dc96d55075ed","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.7","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.53","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.49","@balena/jellyfish-logger":"0.0.195","@balena/jellyfish-metrics":"0.1.1","@balena/jellyfish-environment":"^2.3.18"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.74","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.76_1610592434132_0.8445355264104526","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.77-renovate-non-major-cac8d24fb6032173378f1cc7630b75d1d5f9aba8":{"name":"@balena/jellyfish-core","version":"0.2.77-renovate-non-major-cac8d24fb6032173378f1cc7630b75d1d5f9aba8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.77-renovate-non-major-cac8d24fb6032173378f1cc7630b75d1d5f9aba8","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"da1ffab06e0692a0983316d3a73903ea9d94a02a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.77-renovate-non-major-cac8d24fb6032173378f1cc7630b75d1d5f9aba8.tgz","fileCount":95,"integrity":"sha512-ioFXLR4d43k5jQQ6HWw8dM5v18HyAYtOC70JFyA/F7gCeZZ4t0Pom0h2Xz/61o+LRkRwUGNv21m30IFS6ScElQ==","signatures":[{"sig":"MEUCIQDOqB5wYElpUHbjxdrKtZ+9H3Obrj5E+VjT726+8CQVYQIgZdrip2/zcw4sPRzaH9Y6k8vBQg6moqzwBdkiLZ9Cn5g=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":583930,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf/7ocCRA9TVsSAnZWagAAH/EP/2FqN6s2+tOrIXhY0O9j\nJGGnhAuTmDP2FEAT3I65mQsTnQ3OzZIptEBKbxhYgM+mCwtefS8yV1npFjI1\ngH8dJWwk7IZep/S+04zQPGBgIBCNajq3XVzgmqsBiBgas27R+V0RdAdoPP1S\nwJx1N4c1OYiutwNWi79vs7owkSVrGjeSGlpr2m2movbJxh3qRSUl5wnYX4Gx\nZFdqWY5F9k+3ViY6PG0jzDKD85EHGt0TAuQbdZ+hGk91yj4Ty/wpBWQkdsVV\nBv51MawKc7YNFg//K3U7EADgx1YVa1FLXh7DBYUGaZDXWiu9aJFaEYIBNUzj\nAPYOtUjIuYrovsnPZpZqaVm2P6yss/pKOfPWBRyu6SxvjFXTCSUqAr0likFK\nIUJ9DlocSdK41/Glt6KlUSGMXOt7VwAP2qivoWCWifeeAW3WsfiyVAkgrKCI\nHQlltP1vN+x7roqvpkC4ShQI3k3xUKz0IpaAKnEU1S9EmnrdmpC+hqLcXv1w\nvU04Rr1DbCqF1feOZEW8GLOyhyN6O6i+650XJxYdDcCV9dSU1WqvGfZ+9wJI\nTN2b1BLjOdj03T5Ko44KFPLPGN2dnLL+58dm1rxRzGrKlCseJtKmkBAJ3cQs\n7eOlgP4w3IfwlFvzaLidlUcHacD+dHkrMAX/bg/XMMTSUUJCLmZ6VX/5gp/J\nKsOP\r\n=REcA\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"cac8d24fb6032173378f1cc7630b75d1d5f9aba8","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.7","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.53","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.49","@balena/jellyfish-logger":"0.0.195","@balena/jellyfish-metrics":"0.1.2","@balena/jellyfish-environment":"^2.3.18"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.74","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.77-renovate-non-major-cac8d24fb6032173378f1cc7630b75d1d5f9aba8_1610594843940_0.9596582908074607","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.77":{"name":"@balena/jellyfish-core","version":"0.2.77","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.77","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d3d31e1fd1373afd16420483e73b88832fe80273","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.77.tgz","fileCount":95,"integrity":"sha512-pxnlVpKrzFMH1EsnPGBCo/mi9Fv9E39MzAj/mcFlsXbH9/teAalQE+7fTQg2vtcqeDW4r75a2ub2zWShtuBLWw==","signatures":[{"sig":"MEUCIQDZ0OoKP8+ndLzwgp0W5OqwFNOFiGWNSZXHO8v08VoJFgIgSyazRJfVnHr5hyj4QEv/fvNT9sS1oVrGQqKoIHgVqPg=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":583870,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf/7t6CRA9TVsSAnZWagAAKHwQAJjOwCuZxfhymcebsnYJ\nc9Ev/E6ol6EkLMjiGdsHIKhlddlTsIWYsy/pl/YGsksuE4dLf8ZPGaUZXxo9\nKiNi7jI8xa/0Rj8BFlpplDKIHprsZEPF5r/WklY+EPx+sYgrprGreB0Agllr\nYP9HiGpcfdAdEN80+0P+fgXuhtiVcz1bZ/bNjRqdnQR3ts1r/MM7TPJmfEGl\nwrBoxg0HSyzempLDyFJiamD9fHd/wmC/C1CxEuVtOCbr4QK93C+hws/4p15p\nup64I9vG5wrVkMNTW/D2lRNGfM+9lVFFvg1vUp6lJDLEOzpW78ouT/3u5fUw\nNW7uDTJu1ZOJ1zyeq3DGPJieotCSLPxrt5HAAksjXz83R5SrzLXGtBhrM5nx\n+UzpmD0fOHE+ZrR2TLd9zbTyXHyEPvM/nE3ry5nFKEcHlj+tFDNCdbb/sQeR\nqwnVADCb3chWCA3hKW4bWcV2GysotpfZt0LfBfK3ZjsO4FlwwkiarFCR4gmk\nXyK0Gbr7at/+KhCY9mu82c4SuYHEhD/Cxnt/6VjjLJBGZmKmOsp5QPr8oXGE\nGzqAr3nJLN9l/OOc773KOgXOFOU1YQjd9lJC3GFXbYKyJAkDQMl2mZl0/SEN\nt/dPfXmbse0iLGjuKyoE79oM0pfuGom5aZXIm37hrClwLfzjuc7DUxN0VCQR\ncxYL\r\n=kgby\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"527c29d76b3a4ddc2328de9a645636c5be7bfd84","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.7","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.53","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.49","@balena/jellyfish-logger":"0.0.195","@balena/jellyfish-metrics":"0.1.2","@balena/jellyfish-environment":"^2.3.18"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.74","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.77_1610595193549_0.10206175888185931","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.78-renovate-non-major-25636c66427918c8949c2a8caf8977ad28eac789":{"name":"@balena/jellyfish-core","version":"0.2.78-renovate-non-major-25636c66427918c8949c2a8caf8977ad28eac789","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.78-renovate-non-major-25636c66427918c8949c2a8caf8977ad28eac789","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0247fb4c584e9a1cceb1f72d9d1daf7c68738512","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.78-renovate-non-major-25636c66427918c8949c2a8caf8977ad28eac789.tgz","fileCount":95,"integrity":"sha512-Y2mPxhWNOvx/W3REOrxTZu7HPS4/202f/Lj8vD3//sMXT24mVNsXkbINvanh83Fbu9uOXT0xDIIBQy4t4YTUHw==","signatures":[{"sig":"MEUCIHjLxVzvhscAjVsOGYKrqyPWY1QuDDJO/EMNqWaW5dotAiEAvV/eij0eHOBG6uKo1AFsvpnVQ8CI479uz0ss7gZQb1U=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":583996,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf/87ZCRA9TVsSAnZWagAA06wP/3oqpfIVDdigDgW2GSyK\nfY/F8UmROPBvJeNKwO4x5kXmu47x3vHc9WewaH7gKRQXETlrZxi8MpVyhRg0\nMe0d6oC5lLO/6vHIEfax5yjwvU8WTRaTqQPi4MUTs2j/IoIgH+mNNnOsq/h1\njszWOwyENcubtUMakitUcPnhpyYRmqe0/aV9vME57aKyKvIN03ebPUUBfQr/\n8pgoChEnGYEi2QbivpHH6FSB6JVBJM2rMlXoN8OKWAWJGb4q6YkGn0r91ppD\nuvyecUetPYtj4/O9biVnBeOv2FO6RFZiivrOPx13yBm75lbFzU17GPoJkbrp\nNjaCvjhjStf8WdzYbZUobi5bDr/+OpijeuSXTgcISATJYXjk4uMuXlTuUYp5\nhavSveNUqEeGioQYksdogqzZ4GuByYnnsuv9ojS8BVUsVPTTblHMtUzQSMrc\nCW3YYsKGa9ME9ctBUlZfa1oo1f3Zci3A8n9vvUVSOHUc1bZJ3jT2NYgvz2cX\nLFoaC2VToH3JlGuIpLpCU2sulZjisPF4VsOObARqMMLdcT/XDsFsiVbYSu6/\nk4ud9rO9svWnC3re/s1jDHU8ubcLtg7nJPAIJip85sZbVIzpgPqLTR2DO1UN\nignIa1dV+rKrDzTSHM2R/FqrEAMhEvQWL7qXh8/vNo3+Rq1PR8VgJN+OpiJd\nPGLK\r\n=ZZQx\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"25636c66427918c8949c2a8caf8977ad28eac789","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.7","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.53","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.49","@balena/jellyfish-logger":"0.0.196","@balena/jellyfish-metrics":"0.1.5","@balena/jellyfish-environment":"^2.3.19"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.74","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.78-renovate-non-major-25636c66427918c8949c2a8caf8977ad28eac789_1610600152471_0.6911984679582648","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.78-renovate-non-major-9a4a7e30e914156179749c555cda90772d7b4e98":{"name":"@balena/jellyfish-core","version":"0.2.78-renovate-non-major-9a4a7e30e914156179749c555cda90772d7b4e98","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.78-renovate-non-major-9a4a7e30e914156179749c555cda90772d7b4e98","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"78b768773f066d5d1a824c4b06fc1f1680e88c16","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.78-renovate-non-major-9a4a7e30e914156179749c555cda90772d7b4e98.tgz","fileCount":95,"integrity":"sha512-06UCUn8Wh3AVTM7aPjieG+hThAkpVWdN5LOz2K4K49vz85Oy6PikS/3bB1zNixkRPL9ZNisrFPR0HRP5QK4JEQ==","signatures":[{"sig":"MEYCIQDUMyaAW4WbMkp3aX4UNXwQrKDb36Xkk/slORxjVhg+6gIhAMkERX/jkk2xAaHuEx/B/q1zciPjt0M9Y5bDCnULgEQq","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":583996,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf/8/gCRA9TVsSAnZWagAA7psP/1Nh1OvqW4hPKYmNyl0R\n6eXG0B31ucB4Se0BWUtpNMXsnQVcuTY+Yd1A13b17l/6cxcHkpvbKiGERiu8\nEGSA39cn7bt6vkWFhd8We4QzSJw8AIzvQmnEWPWp8VtOE6M3z7auky5lUceQ\nHzrSy7fE6rWN9dZ2r4pXDVeyYsG767TzVXIkM8xAABYrmfaL5POEkfYYUZfA\nbl9w65SSUeCYMbxDnTCCNVr/n+4mktcxSxDLw7XITxjB93QhUHPnQukUMAFs\nn/EXWoHJIL6SXjbz6EoiWooUhiiP2RqqQSYkub0wlOKhgdbywEJf8oBQwqyk\nWISKK/08tD9EYJSIfysAzQkSne67RVlkVa3pWAL9MOt8pGwIpM5g9n4hQjSQ\nR20c/XUUn9/iKB/yhLbKNmuB07WQA6JcUoWXGWl7IP4DhmU0yWo2gVdrNEtJ\nfRS6Ecr2NQsC0XSQ1XaOBJ2HQ+ASy/bOyJdWc7D2oal3FV7CPaqFjkKCkwq9\n5hU/0iWeJxCEj82p5hFcnJlHh7ZNx0GVSQIoCkkok5F4Mjy7Gvcru63gdWct\n6qzRuh+HaTbgdz+ih2qQQ/BKgtTxQ7YCghvyLaK5hunW9axdTMs9c8htt6Gb\niMX2tQdiQRrLemj3NOdN1slZNPEwgrvd9dzFhtVpgI4dkiAiH/W/LBmtzX/b\nmm7E\r\n=QKdy\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"9a4a7e30e914156179749c555cda90772d7b4e98","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.7","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.53","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.49","@balena/jellyfish-logger":"0.0.197","@balena/jellyfish-metrics":"0.1.5","@balena/jellyfish-environment":"^2.3.19"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.74","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.78-renovate-non-major-9a4a7e30e914156179749c555cda90772d7b4e98_1610600416506_0.032884146534146286","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.78-renovate-non-major-56d0a1489a17ca66059705ce6313a9d32d432057":{"name":"@balena/jellyfish-core","version":"0.2.78-renovate-non-major-56d0a1489a17ca66059705ce6313a9d32d432057","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.78-renovate-non-major-56d0a1489a17ca66059705ce6313a9d32d432057","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"24abeeec1f615fadd472140ae93ae49b3008bde2","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.78-renovate-non-major-56d0a1489a17ca66059705ce6313a9d32d432057.tgz","fileCount":95,"integrity":"sha512-lQsMKPqRdbnzO6mwBLkYDuEUo1ePfaMeT22lgDsI7U01QinI1/GSuYw3Ta6rJuuR3lZ6UA5N5q4wqXSQ6HBD5Q==","signatures":[{"sig":"MEQCIAlCZKrQETP/cT52H917gkHKAKE50HnCjd4Z79cZERF6AiBC1orBF4mi8Hc91XjDyWZQNK/i6oWhqX5SMoMs4Rqtwg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":583996,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf/9DwCRA9TVsSAnZWagAAnaQP/RjRu/bdkueMqMJbjojS\nUci3D6+RCaCWgVCuUXlDd3cJjdw2m7EgZan52ssni0b/rGRox2em3tA2kZcB\n+03tjotTrkWoqBmWBQu/NIUPpPudQfx/0TVbBvuOGsFC1bpXYwYxkXwdOcCa\nfs5LZbFEMCr4gBrGf8KTWDUuhCAn6nR7BXg+w99xta9++H4NzCL1JHthW9zB\n3asskm52dLd7wQr+QRyxeNErkd8tfqrN3FfR4PpOwdfbtZ0c6WmSwPkWStTE\n2mYz/uIIhKgLeqDD14C1fZB6Yn0TxS2yfb1MonKie8fcAxEtIV13zwYT+31t\nffIvqsCSxnUYw48p6DcJ7GuDKRNdGKPYxHJa+bUtYs2sZOiKla2SW1RudewM\nLaAGfDy7R8bPgjCnRA0fV9ffbFqDYuzxKvGL/LOr4bf8Cy3A1Z+Yp4THHmr6\nAG0q7vEJY5HeBnGabBTr0B6CWrTucsAoc5fb1OHtMy+zjS4v4lvxfAaErRYK\nag7nNT8FCJDeM+tGwe7oAzgHul0dEofp4ZP8o67BcHqIs56Qj2AnzxtVlkgA\nopHkpVL5yG66VHWk8MS55fqVeZFKdo4diICHgK04MkiFyY2N64c81kT2HSvJ\n78SbeS4gLprmXQE3XxXFMIYHq/9BNw83vTGKtB7U3/kNetTJ4Xys/P3pICRp\nYdpw\r\n=oGau\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"56d0a1489a17ca66059705ce6313a9d32d432057","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.7","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.53","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.49","@balena/jellyfish-logger":"0.0.198","@balena/jellyfish-metrics":"0.1.5","@balena/jellyfish-environment":"^2.3.19"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.74","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.78-renovate-non-major-56d0a1489a17ca66059705ce6313a9d32d432057_1610600687566_0.7419084221861614","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.78":{"name":"@balena/jellyfish-core","version":"0.2.78","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.78","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"828f7aac4857a46592d67e10256f95bb6cb3a121","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.78.tgz","fileCount":95,"integrity":"sha512-xibddWW7wFjwalF2+yhoffCxmQDd1po0rCRYX/QM2KEpHXugG3U8GrTCEbNDH+zE/LNhj172Hgh0pfdrntFNwA==","signatures":[{"sig":"MEYCIQC8xpgBof3WnBPaSii6sQIs9+PvmKA9uyxYUMhF5R73qQIhAIzpePj36cJYj5DUzFonjZRGmoECOXF/X2VZRP3oMiLo","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":583936,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf/9I1CRA9TVsSAnZWagAAkNoP+wUZR28aHfPsmW3Y3Dnq\nVeh+kHgCVy0dC1t7WqbQ/i+AVvCicI9viBi17ncHpiwL+ecTHQBUehRZU24e\nJfjjq/nbTC6ufezXXOfoMRfPpwC1bTPANh2i9+ejSc3pXrtwi0tFN6Or/JOk\nbcOzHn//t6cYZ4LZYeGPsUesrjWojeairkxmhaWiDx5o1nsxSBAwc/CJOt0o\n8ki0OmrPK5KbpsrLZzm0T1GCmEd4WA8BcP+okQ7zaPUB1Uiwj+szh+GQXCQK\n6QLyFJfOs15vAZsoJrpIRpvu7iIBBTKRlNvs2cwSWxhB+0bP1o3L9xck3mnD\nPH5x1o17SmQfJKbTMlN0zBj35GTPKaqdVKnWUKpURFoGHldKk/i0NbpFCMbK\nwxOaeTq3zphLKTALnSEytGgosqXQu3HSuJS96LGNCakKfwZlZd+19dIPMjc5\n9BYpbqVuB/fDI9PxyuKgDwioied0+/3xjL6j80vj9fr/JMmOzbrBkvbIrJYw\nseX+apy+HXjpLR4uJZw4a6uFF6YODrxZ/wgqfrf7TZzcfKiFC6DNJFGCfrEx\njC0d5eaA6ZmIHBW9r+YA6N85DaYf9On+uVQhTQNGLoDrDPy+KYSBkEFprcZH\nef9ko89sNeRcg7KzbKAaPuZ+1X8icLNcIxYbAibjeoB+vqXyJgw2wY5/5Mjm\nfmji\r\n=hJbH\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"3b8484927d01490a2089c5fd30fe2e6c76459ecd","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.7","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.53","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.49","@balena/jellyfish-logger":"0.0.198","@balena/jellyfish-metrics":"0.1.5","@balena/jellyfish-environment":"^2.3.19"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.74","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.78_1610601012517_0.8435182282705305","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.79-renovate-non-major-05374af3167912b72e36ba29f911f227fcddf288":{"name":"@balena/jellyfish-core","version":"0.2.79-renovate-non-major-05374af3167912b72e36ba29f911f227fcddf288","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.79-renovate-non-major-05374af3167912b72e36ba29f911f227fcddf288","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8f056d18e5325d3be3e1e6af4f63422320b044f3","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.79-renovate-non-major-05374af3167912b72e36ba29f911f227fcddf288.tgz","fileCount":95,"integrity":"sha512-hhpqQ7ZWELNqJSGd0jzy9ArV54I8Go5a7OiG7oHcccakFUaIGCV88C+0A//I3ePi1GISyHG1eKi1lajaJgM3Kg==","signatures":[{"sig":"MEQCIE5VEpiqiRYDL/nFI4WHxLG1ECDuBAlvtKo9jpbhH9lmAiBg1Qb5QXzs6p2UQq4aYXX05JnJOPw4eC2pkPtWt6bcKQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":584099,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf/+MRCRA9TVsSAnZWagAAQXcP/0kvrtIdCElMwELx677B\n5g00FqdflEj0F0ncPghfKBnxjbiw5ydJKgTdGFpfCEFctSk7WzSSKxiaOj91\ncoAzjZ3Awxd5F3mG0oBNM9hNyDXg3TaxDSJMwvMAAU35P7CXi6Hiy9+Upc77\nJN+qFKpRJ4OusWvyiR/12ackVm9Yp7eLFRCjT4dShOI33BKCU8ALarhVi8Bm\n5ksNg4xbv2GVoPew2Ba5ZHLq5m4uuAAHiK14pVvx21+MDbMrCcFITFqo+oSM\nf0kNTlbn7nmyNqwRpXBmTm5W/JUi+zYqYPSoxT4eIuFvGh27QZbxh4YSKmOw\naDjIo0E+HmMn2kMkv+qAurr7VgOX5M0Wpo+sfEbHSsH9nX5JKnCqkQrrTMbc\nl0tRflo/8d3E3qqBDi4buyS8Axd4f7wImV00nVfM+IuYhcLsI8PmLEXLrXO1\nWjA5rBNpbhnqp3qH7ElhqpBbGRvurPqXBi7fmqpTQKn4xcZrnZkiJ1ZyG98P\n5onxvXrYndkRTIiInyUzWbnl3Urm6X0gSDzpMVZJdoTbzb/qGKEIkeYgHa7a\ncN0ejrg2T7Um1QTN1bMgKm5WZKiSyEe4c8PaDT8nxPJ5/CoVYqn9yWyTQinH\nVipTACw463Eletv1jkAZB/9pI9MsyEa94XWwmqLBnqzQbimQ19PW6k7ik8Oj\nkueG\r\n=hXOK\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"05374af3167912b72e36ba29f911f227fcddf288","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.7","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.53","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.49","@balena/jellyfish-logger":"0.0.198","@balena/jellyfish-metrics":"0.1.6","@balena/jellyfish-environment":"^2.3.19"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.74","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.79-renovate-non-major-05374af3167912b72e36ba29f911f227fcddf288_1610605328714_0.9084382523948937","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.79":{"name":"@balena/jellyfish-core","version":"0.2.79","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.79","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1cd6bafc0df4714ca6cb61dfbb781cc523797659","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.79.tgz","fileCount":95,"integrity":"sha512-bzAJXBPRikaC6Ghmcq5/VtYwsOCdT0z9nhq5Oe9vthJm2srzfUxhrJ3BFT6joHKestiv8ejSoSgCs4i2yC8Vng==","signatures":[{"sig":"MEYCIQDYyZMn4evaSo+Fdv+8wOc46BdvSAW00QSDQZNfBBNsOgIhAKVyRkqDL3Uy1bYuWR7pP845d5Q9DlSzySx7Mt5VPkx+","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":584039,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf/+Q/CRA9TVsSAnZWagAAyR0P/1w6qapeioYvD8mIj49G\nkBGZdVODYnCKIX0ayUjr6o8BhKRb4VqV0GU1hGEnJwOhQKVlMekVPdaD8yKL\nnci+LxxefDGShTj651AjOJFh66hU9wWAjce+AVVcV1e+N7RKiMdCrQmsMD90\nPDkl8WaQviumUj3/xmt/yL0B/LE0NDXU0ctiAJwImFcnyxYo8aYfVP+vmSOu\n/r9E35HDwLJitMXN34o4WgNJkLF+tW1IIpsMGHNWK2DaNSCIes4haVDogn3+\n2uQ3noKtM3+Z4TdMb/Yc+3IVvdZ8GvBU5MsrIDkKEebXQmvC+T/qze3dXhAP\nY2EMwgN7NsuXn7Brm3uL0XIzfU5HLdFV4J6Rnm76IrhDGIsTPbYIndH9AWXd\nQ9YKjUHWwQrNQ8IPw4qp6DjxPHwNjCYaOg9B682B+vecsUZ5wUfyOUBJ5qYC\nvFWLa033n7HkYEMWs4sXVEXMOjnLitFk6Th/xRddoEAEOWdrqRJXNwt43EX5\ndda8dxsLKnQP+s3hN0Ss6agbHt8t4Cu4oYwCgq/ZMKFMyTXYqpuDoKycjGcr\nM+mOHOm5tqwbtf72LGmKJCQGdh2UUR5g5jxRnrBy8qQWgEbk6BU95uq4QcQv\nh1yif2+a7xTn8FBNN5wMFUohBR3XKsJ703WT9sQ3Vg/FBleJLUoHlkraUxee\nGobP\r\n=BQT5\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7d9f2a3eeffbabd8837e729f444de2de63324287","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.7","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.53","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.49","@balena/jellyfish-logger":"0.0.198","@balena/jellyfish-metrics":"0.1.6","@balena/jellyfish-environment":"^2.3.19"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.74","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.79_1610605631258_0.6551010056443534","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.80-renovate-non-major-5be420fc1f284cb8bcffd8ba2e5dfe8b8b930b4f":{"name":"@balena/jellyfish-core","version":"0.2.80-renovate-non-major-5be420fc1f284cb8bcffd8ba2e5dfe8b8b930b4f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.80-renovate-non-major-5be420fc1f284cb8bcffd8ba2e5dfe8b8b930b4f","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"904f61d535bc1eaf540c490f2c1ca75220642b88","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.80-renovate-non-major-5be420fc1f284cb8bcffd8ba2e5dfe8b8b930b4f.tgz","fileCount":95,"integrity":"sha512-vYq7U2tj7snXgBZnyBlDrMj/5lgmj/JFAEKL1jDqfdTbFPC0vaYcHO6QFqmdEg5ABLP4fCDDuQa8/mIhRke9SQ==","signatures":[{"sig":"MEUCIQC7Op6zmbkft3Sg4FiuBvM3zhX3j+bqinglr4UpZoTl+wIgfsFJfWBvM/mLUv9rE6gNzR7tBSp19aBrzkRiG09MDmE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":584165,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgAG3LCRA9TVsSAnZWagAAq7gQAI9y6iHZJ9sU9JGx4jxb\n6Ik0nzfnCpCOlROWRUYGIsZ1WjUsjro32UW2YptwEkYYhMuaOU8iFs32vl+f\npJyTvhoo3VN3yWPnL8MyQQ1kXpLaYeNFYK/05eK4LPt50ZYYp8KUBudlqdAv\nCI2dkn88jr3RnKK7Ty0AIKtGZgrP3WxH0UMCorsHXNqIJ4bd6MT5zUP8Uy/p\nAXEmbOlNpfyrLc2cgXGLjr0IrkOJsXOg+bP8Imt+uSJTHLYLpEbT1Kajjcbh\nkTAfXfnbxCiynos22Myw98smObhg8x60wn/yvG/Hi4d2WFl/bONBny1BjOMa\nzfMc83yHV8xgSagsonZaNbrSq6IjZ9be8QDz3faHx348272EvgWy0EbHPJDp\nJguqTR4DfmbZAwYIPiJe5LpsO3aav5MP1m/+plR7ZJ1l8KGI/m95c50XAQV1\n2DQSGPlQG6M6G17sfG+6QBk22vflHep95klTKUpYov9kwX3ytW9w2xyFtdV2\nTWEx6saS6qckr6RW6iSQyMURLlIeWOHzzx5qKNJu0Dt8BO+jJaLJFegwgv49\ncpuaHiuAFQ+SwcL32m1koMzkM738svgT6NqrP23VrPMXIuKXLv56wCtMPCau\nXpEvatWru54loSlXzK/DE7ox7MmrwM7F+XfTZHaxCcMWG3EFZGRqbQYmPAsX\nm4KX\r\n=u3et\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"5be420fc1f284cb8bcffd8ba2e5dfe8b8b930b4f","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.7","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.53","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.49","@balena/jellyfish-logger":"0.0.198","@balena/jellyfish-metrics":"0.1.6","@balena/jellyfish-environment":"^2.3.19"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.4","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.75","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.80-renovate-non-major-5be420fc1f284cb8bcffd8ba2e5dfe8b8b930b4f_1610640842788_0.6294973331427878","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.80":{"name":"@balena/jellyfish-core","version":"0.2.80","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.80","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ce8077b02c798bb1e4f0f14c78ca995e6d2e5138","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.80.tgz","fileCount":95,"integrity":"sha512-9dPYNLAkctCkhYzoA7CrdYRvRZOYL34HXRx20pvbSUDHvYxSxMcXMIu/BRdxYilFYZAIbfIQSD4WQUzxoi0MGA==","signatures":[{"sig":"MEQCICjfdvSDHTnyTk84pYPdrUiik4DjjVoHeNlAJ4B8JbC+AiAMr0DdaUryimS7A2FOXcYU6EfElHF4ZQNAeNjyyfBJ9w==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":584105,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgAG7tCRA9TVsSAnZWagAAl08P/0oCEQehuVGPh2IOMZy1\nPG3xyAhierD9dFPaFV/B3nalZJqFPNPK8QAjgO+JHDFULDylkbwfA7vaze6F\nWn4nt7ttTOCamfIffbNvqfUzy1kWotmltRBgH2NskSqprMPbzd7mOp8gZmkI\nxFVEzRDpQZkojV1+HFBi6+hzg1qKEAPtR+lMUPWP8EGn6rlJIu/szlbYcott\n3MIyVIumPlS4ry6W1nUPR7hywQ3bbJ44T/xwwC+HYSEDweNTVpBTApXxy6BC\nVUVRZsq1nOGR9h4yOeBUw0qAkBlX4T6SjVazN2h7aYahSI+rwfmfWWnBI5Sn\n05VDDErPghgZ5ElfKtxd2zSe+CiEgaFSrL7a15uyHzbXot1AngtNe9p94yE6\nAGJF/MYrsDc5y8wSWYd7sIjtDkFBK9mQd2pJFQ1N87mwEJj1dng352qFlbHH\nZR3tZpEDJbffEUSXojakIGe8+JLI3A1FAynuQWKP3GsDzaKNYH7sybRThjAk\n2pCb1pXdCJhC8bS3qgDCg71p+Zo+gbefselhPbprgYvoe+rO81jeIrQcSEkW\nY26tPlcmjkbTRLGxPi4rhiswPLJUnRNR4uFQf8Uzh0QwcHgCIfsihseCpRSf\ndqwIsGZJaYwbxpzUayBxj4/wIJ1MjGO+mrj69rADp+9F3/3YpM597XsgZqf5\no2Nv\r\n=Wme9\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c6deb38e985e65088c6677e705a6093cff73c176","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.7","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.53","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.49","@balena/jellyfish-logger":"0.0.198","@balena/jellyfish-metrics":"0.1.6","@balena/jellyfish-environment":"^2.3.19"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.4","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.75","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.80_1610641132882_0.43432606187684053","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.81-renovate-non-major-5c753165251c983601114d7ce94a1ce5c4753bf5":{"name":"@balena/jellyfish-core","version":"0.2.81-renovate-non-major-5c753165251c983601114d7ce94a1ce5c4753bf5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.81-renovate-non-major-5c753165251c983601114d7ce94a1ce5c4753bf5","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0cbfc7f7ebdc76f544855fae1bb05ef15a3ef789","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.81-renovate-non-major-5c753165251c983601114d7ce94a1ce5c4753bf5.tgz","fileCount":95,"integrity":"sha512-fxUZReqR2IR0H/pNvIyQLD56dV1MUI0OwGOJ39XglOGy8QNErlQ+JcN+1ImLIrcmgdfnbi/w0G34STSV1pwIXQ==","signatures":[{"sig":"MEYCIQCcVuk2KI18VPXGzWpo5wA5ThUFRKD1n4phEv278FxwKAIhAO4fcthP3aWPfjPCLhmn4ZAaiG9UG60Hl7gBEwnaHhsJ","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":584231,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgAIRhCRA9TVsSAnZWagAAhlsP/2R3xEZXbw5Sn3mEkQZ6\nwgQR0TrZeoCu59Mc+Xvr7ZzmYElD4WDqXblVtCnkgFNWaQtMgCeE/0mVEK5p\nKsMuLrr2dsjEdwqZVJ1EpZXwBoC9alJ2GVUN92cHoXwRcHNuHEGsuh6XnS4h\n8toDC1KCs3YyakfsvF1yE7ePR6Nj68NMPT5zqb3+hNQIT8gha6Kx2XzHvc0T\nMRtZjF+yWPG+oz8HgmnfmgW0pdt6QCmb7aSXoze6U1vnVN6N+Jqy9tCKlu9g\nqdJhkecvzJXhIaDHlwK501fcSnVHbNKY+lRPHRSECQRFDu0gVe8GKwGZPLOG\nYVBnbSfl8FOPhDvOSaE6OGLbPQgU8NwuidiMoQbq+G5KJbYaL/5Dc+X81wNV\npdiqXOk5J6qatPcNPETe8yzoGt8+hM1aeCxx7hwMH8BgPzPwyGGjv+9QU7Tw\nOiCwURatLDJUEq9eDUHPgEbJ3mcLUW+P/kL1YFuyt0uof7l4C6xtCfZWOxKX\ntF+BgxuMjR9HIcL6XQknnwdmYk6HEiUA5Ay0Kec1ddZK0+RigB1KCy8Vj7+F\nSZYazjx74dyHdlSrLxznJYmN1PuqseroeM+o3UJlsPxwXDrvRPpADCYyWFA5\n2HjZbIYJB6cqXeK6ggBlbTP6S7xGPwQQOK7n4kzJRfKAc0rX42e2t0E5//iO\nLVMz\r\n=4iCE\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"5c753165251c983601114d7ce94a1ce5c4753bf5","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.7","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.53","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.49","@balena/jellyfish-logger":"0.0.199","@balena/jellyfish-metrics":"0.1.7","@balena/jellyfish-environment":"^2.3.20"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.4","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.75","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.81-renovate-non-major-5c753165251c983601114d7ce94a1ce5c4753bf5_1610646625380_0.21156948524026165","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.81":{"name":"@balena/jellyfish-core","version":"0.2.81","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.81","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9a51f7e10969251f369be881b328c254def07412","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.81.tgz","fileCount":95,"integrity":"sha512-8+XdapGjnO8oBmgkw8ngNg63U550c8/NTruuZCtR9usP5x6y4LwAvgWgqMvXL8NaFZC+ZcHaYOVvUYyqPaTG+A==","signatures":[{"sig":"MEQCIG/XlilyM7HLtg23boF/08ugciMX1wOGOu5m3cghLInPAiBTyLrMTwcWd9Mpg0OK/S99l55m64qlvKIyD25uuKs/bA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":584171,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgAIYvCRA9TVsSAnZWagAA9K0QAKTFT+RozhgJy1hMTPHa\nSsj4yXkbdf2tTwza9e5e/8WLzLWuImHBgNsoLhPrXders3UUqY+VjTqpzWCG\nTqDQ7emgqASY5ckdgDNK7+k9JZtZmIiGRfFuG98cYP/uM07eWHb+YYLQ/CKS\nTqORJAP8xW6OXDtqn3Szg/ueNU9WI3vTTsAxcbF7+q9xdfE0cqH3jRgBu92y\nvwHXaSncM95G3ta0PpDBbBkorrlq0nSeTdykZfzrIrE1hNLtvGDpPNpgdlaj\nTBKhknFmV0l+ziUvoMhcn1fYjP+b/1W7vPLK5hlv+KEXP0RgfMK7k6lcbPgu\nuxHP9gs+GPE8PR7Jatkrz4rkC1DkR5GZRuIKL3rQBVO/mgy4zzcvvZkPXhM8\nGCNUPcevJrK9N4fzVUZ9c3QHQJ9ONGv6IQ0SQDPcjU1OflUKJbOVHiyV+Tw9\n3i3j2GA9e77QAKSrDdTRpwTCtVOvooatyoXdjNO9yATPYhEPIKNMrTAxd2E5\naie38tnRKMA81tfJpfzZjcajOUWlsHiRExpB7PaLFp2VNmLTFPBwTyQD3rPj\ntYiGLN0GnCVA834zCrn/+bvGsOS5CU9notseyQZ4mzgSg03X5uDtMijBqIg/\n5W0oCOKnzmYbGdIbaN6UAlhkkSYt3EXMfzoOs4tLKUDRcojyGoCOse9RFP0B\n2GJ/\r\n=6pLV\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2dce4a971a8015d42b4ac1ecca5033bf230d2721","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.7","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.53","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.49","@balena/jellyfish-logger":"0.0.199","@balena/jellyfish-metrics":"0.1.7","@balena/jellyfish-environment":"^2.3.20"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.4","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.75","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.81_1610647087115_0.9065422003566586","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.82-renovate-non-major-18aa3e52520794cfce00088329f17bd8cd53b61c":{"name":"@balena/jellyfish-core","version":"0.2.82-renovate-non-major-18aa3e52520794cfce00088329f17bd8cd53b61c","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.82-renovate-non-major-18aa3e52520794cfce00088329f17bd8cd53b61c","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a705eb469dfe0446659291b98c21cf410d476ab1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.82-renovate-non-major-18aa3e52520794cfce00088329f17bd8cd53b61c.tgz","fileCount":95,"integrity":"sha512-aGNkeYENlMkp99b5AXD/gYlDqI7wEoAVXzHFxtDTYTZJLwMAZ1mlLRiNm9X1U2bDIoSCcilHUgV7Is4PNvHoYg==","signatures":[{"sig":"MEUCIEJCMnHAaACMpMC50r+Go55Slgp8iCve739qYqOHHUc0AiEAiMk4M1N3beQfZ+PjKkLbchSX0OmefXx9XjpdWvgOegY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":584335,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgAIbzCRA9TVsSAnZWagAA5qMP/2z//ViwmDBws1AWLE/y\ni7kX0wARmzRcpe+OJ92cFazJ2gJBeHli/yLYZx1eN2GKy7PY1x+GR6+3zRII\nkilqvYv17j79CqZyfL5b4DMrz4TRk2S2TOwlmJyixl2TiyH5wMZkqPeuvBeM\nngFgCvhfLj1WsvZ9JzLGmwoi59VRPDaNZg8KG52rZVMS+l0gXkWPsA/shIJL\n5U8HCqvrLR94Tolr5cmJIiOW7kuC/t1jJV2djLNlSVRayjhGUuY74QCo0IVJ\nYDiFl2g6ww8IAbsWjSlic2ROwpm558xME5VB6u45HLMZPB1iC7g0VJJTW6AQ\n/RNs4I5iJxxr55ZoehSKhMhWbgPfdZqkYRWGgyDlxzei+pdtF3+1trvyKhp5\nDqPu+bWhaNakFyNEZNebeYyvS7lrmwZtrOliDsS5pVbHar6Mi/6GwXMgkEx6\n4xTiZIZVQh+ia477W8dLaolmqcwodX05KvnQjveZfubFWrm72PDjlLtsGGBm\n7iERo3fwkJeL1Zucfg2veXJAJtR8f61BaxZ1Zir1BUmOFNHGR/s8WmbNFfuJ\nD/iVjYLVdHB9HIl0DKbdut1T/sj95MFnfNvpGWCRJgGGDEZuNnwiXqRoQIP2\nMlkbboMCS2WyY9/0fu5AlY/SEIq4YLcqHIepqYf/XCk9AtaZsJL7vJ8HvCJ6\nvuku\r\n=c28g\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"18aa3e52520794cfce00088329f17bd8cd53b61c","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.7","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.53","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.49","@balena/jellyfish-logger":"0.0.200","@balena/jellyfish-metrics":"0.1.7","@balena/jellyfish-environment":"^2.3.20"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.4","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.75","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.82-renovate-non-major-18aa3e52520794cfce00088329f17bd8cd53b61c_1610647283275_0.2548042188362134","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.82-renovate-non-major-d0a10f9a4769eb3ec0ebd61c517b523a8c33b669":{"name":"@balena/jellyfish-core","version":"0.2.82-renovate-non-major-d0a10f9a4769eb3ec0ebd61c517b523a8c33b669","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.82-renovate-non-major-d0a10f9a4769eb3ec0ebd61c517b523a8c33b669","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"7b12b1a270227b4262438085c6eae74961deb5b1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.82-renovate-non-major-d0a10f9a4769eb3ec0ebd61c517b523a8c33b669.tgz","fileCount":95,"integrity":"sha512-iW2gTA+TEgf+o3MKKgHj8H/s4uTobQsJsuyifAhmmJz0beQ+gu2yednKrRZAWFwvetrV+ovQxlRpWQTtF1NnPw==","signatures":[{"sig":"MEUCIBFubFYUrC1+pgtjPSrhqZY93E5P7hnfZj1kKlyStDA1AiEAjgA5e+65es1DaRxnDSccOIvGTnrP5JfXoIiqUszoXrQ=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":584297,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgAJxuCRA9TVsSAnZWagAAmOwP/1tReN9Dfj1dGTCE9VR2\nfP8bNQYC24E2SxJ8JO926qN/DFQWvcx0Gjw+8cmiOKxJlLMAC10lDHItgPgW\nfvem6iFNz/+MwnC9aI0hbChtOf9PGtujrxEoguqfGOaXBsI2WlnXKbszW3tj\nlBvvKBh15rLVMI1x5AqCDoAVKwVK7f9CaIbHwhobTwuVZ8zlOFbCbRgEsa38\n2VWyp0kRmvdwWVYdHkqij8hvla1T/G5rI9soYxrOw60SpULadQfaFo/2+dwu\nprAjk81hDhH2mRCl8CeXiObAksC9PmMlN69UHxvNcMR5S3fgf8hZRK6p3HrY\nsw9zAOiLmO5orPWUKHYYvMc80uGLB+x/efLxWM0dGwNuAjVXePYlmY8gN5pH\nhqZoJwjeImURnRCaY26NaDLgPlxB51SkQgoinWfFMq5jz0s21dsc0bsJRX4i\nUzT06muIJKpMqpESDA7NZ0pcJCYdU18CK8Fh+PfHP3Hk/3/5k0lRU8I/m7qS\nXnTSGLTtitG+cWKOB/ODoGRPI1qw/8+5Xl3eybPMS1jppeuGwM1snX0zAhpN\nnpAXH0LQ6Lg9VRlOIwnt+p1RHlloVHd/msNGvgoZvcxXqgOQfyePBOagD0It\ngeRLik6GYFlnKux6CxrMC1bOZ/7j6OmS56f3J7FLxtRfpQeZ1JbLoctf86cy\nELrI\r\n=8yYz\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d0a10f9a4769eb3ec0ebd61c517b523a8c33b669","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.7","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.53","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.49","@balena/jellyfish-logger":"0.0.200","@balena/jellyfish-metrics":"0.1.8","@balena/jellyfish-environment":"^2.3.20"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.4","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.75","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.82-renovate-non-major-d0a10f9a4769eb3ec0ebd61c517b523a8c33b669_1610652781648_0.16184108847354306","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.82":{"name":"@balena/jellyfish-core","version":"0.2.82","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.82","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"12e1b8b269221184cf835b22a6d75bbe79216f3b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.82.tgz","fileCount":95,"integrity":"sha512-Gue9Hde2n16mq+MfOU7Px1QApfIs4M9ohfhFLbe6Xmyfb8KppKFfLrxkkFCQ6ODb2rDEFCr1anb1y5ZWP5BgPg==","signatures":[{"sig":"MEQCIGuKwx1FP+OPB5V1gsQM8jckOtYGly1t08AtKGohDtNjAiBeHCDqkSVxGzP9mB/9wNmHY79sz6nq3/1WiCVbXaYhhQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":584237,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgAJ2kCRA9TVsSAnZWagAAhPgP/RE7U4pV2bvdKUEscObD\nO8unxHp9q4SxB3AFzNNK8T5BFINY2OgGqadGz+kwlTAXcozqnR+OW7uyvZWL\n+A9BpDpjQP4TRGAzwjxwnw9/E5eS7jaOh3V6WnSvUi9gOWePTXDidF0YInmh\nG/P9niS7VcqEly5ArMtNhYQ6Ps2xFU8Y1Rk/XGlgnsD6gydFYYVzmxogPK6O\nt6zEh7sbqs8acW5v73A42LTXmc8XwM+T4N9JEP5qkgyCQvA+Dc0E885Zwc1x\nJR6LICIDO+v1eAKDm6JL18agUghhrv9G5GWlsKc7qB3e24V8/Qpyn9IdI7nc\nHKZX4fqlK+hxlnVHa7HLLMMENjY6sJDNSl1nM7IPTAnAnkNP1jhqn0kNxnhw\n0g0mGCyohTCYwofBAxPuDgZogYt+M2u0ag+UOKtHFaYjqtNg9PHDbgVJ/7JP\nNvqKBHgVE2yTcnS8caf2+L3v+JOdbQFXWqZjRM7+LNV6annAk6Mtt6gL/QUi\n4jOiz02EY5+/qhSo12yOsrnlTyg+Az9OzWSzUSm5db+BYuod7/ZawLC38UC2\nEtkOwDCSY6nPNFSuydb6v3nDumpwxL5NYujMS1aqZ+7SbKMikAOu49JbbvlA\nTPnYSpD1+W5YvlIdQ9wn7K0kBZyq18FOAiOl+LVL08wu+n5dnTSOp8Hm2UNu\n2IcR\r\n=vudF\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4bc0490dbe6c1a5b545c5e2cb29065a9562f49ee","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.7","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.53","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.49","@balena/jellyfish-logger":"0.0.200","@balena/jellyfish-metrics":"0.1.8","@balena/jellyfish-environment":"^2.3.20"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.4","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.75","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.82_1610653091416_0.8216576608562138","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.83-renovate-non-major-97a52082584fd304fbacfc1e211b14c862b12107":{"name":"@balena/jellyfish-core","version":"0.2.83-renovate-non-major-97a52082584fd304fbacfc1e211b14c862b12107","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.83-renovate-non-major-97a52082584fd304fbacfc1e211b14c862b12107","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1bc46f905f38fd201c41140aa6b70d1cd0cffdab","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.83-renovate-non-major-97a52082584fd304fbacfc1e211b14c862b12107.tgz","fileCount":95,"integrity":"sha512-+wlQ0rbbUNktHT7w4rvvXK1Q963hnwalDP3+NOjeC/z7dzh1+tpVYy22V+if5TNpD3g15S48iH7Nt1qLe4Z7Ng==","signatures":[{"sig":"MEUCIQDRi4RIgLYysBMvfnH5mjdgv9CrVtyb74R7bhTstRGczQIgTGF7Uqpp7TvbPfc13Df1VjxZPcXKSS9BDCiU40xT4hM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":584395,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgANoMCRA9TVsSAnZWagAA0JcP/iMipujzvkIwV04KCsL4\niCMX4hv9W/ed5t6UZ1T2vqEgKTZCiZ7WzzhpCX/4P34vrtNQgBtSCtpfsKNl\nuIIn1gx6LfI/X3omJf2JjQUS9oTHEQaNbJlX4Tk3Zw8ZRWprpaWf3BjecZ1w\nPzwLins+dy/CcGqFwCZu9qdsDzgjePPrbLoLELqiBwYeJqgB4iuwuhvNkQNB\nGNygES5EZo4O2SotDmZbXGVlt0thVXGDldXRakUL0J3jxdPlbdDMydvakQYY\n1TaOYiHWDCLuSOF4qFfF5J7cpXuxrwWIDFX/TgyH9NS7JvKJMe3wJLnBW0iv\n3YMil10mxmLTgWxTc13Tjvocxfu9WECKOf80MgNAkckfJDv1jclupYrC6UA4\nwb1MJJkVvrcLfBUrBenrIPhgKGFTs2A48+bQm1kiEGu8JaDe/s/KI3WtPyVQ\nqvrCxKKD/iLxYzo38nJeARQ2UdwMXXLQJcUyqrOrkM2+gRkhXNPZoEbO9pGo\noi9+WJl2yUdYH1D/Swip23vm02qNjctCQxTqAvJgybed5o7n0NG1EWTiYFy1\ny1CqbAIaFz/+RVIiEFQUDjRAkQFrsmnrRGOfTFk6mYxBif4RMa1d8cBBoLCq\npy657oUMLgH6/5UUM4F/5xqnAjW5APO4RJa/p558sHzMNadE6RbeWLFEg+75\nIwHn\r\n=pbO0\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"97a52082584fd304fbacfc1e211b14c862b12107","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.7","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.53","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.49","@balena/jellyfish-logger":"0.0.200","@balena/jellyfish-metrics":"0.1.8","@balena/jellyfish-environment":"^2.3.20"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.5","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.75","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.83-renovate-non-major-97a52082584fd304fbacfc1e211b14c862b12107_1610668555647_0.04018954021786181","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.83":{"name":"@balena/jellyfish-core","version":"0.2.83","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.83","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8f02b8903637cdbdb315374e55bd723a67d9af7e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.83.tgz","fileCount":95,"integrity":"sha512-yWyaOJuQoGdHH1ISnmXgCAnsj4yfNcmPA9CQ8c6bcfbxI486gqBnmZEsVvvTUBLJFfLDiYfssq9M0XsU5gW3HA==","signatures":[{"sig":"MEYCIQD0aFHl7fDzLV1clqcIdqPCMJstccUUPeGR06Qw9TyAUQIhAN49ZLRrJlLocZRLDvU1CTQCG8UWgUimxBCjfeKk0WkE","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":584335,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgANswCRA9TVsSAnZWagAAIm0P/RGmjk25WvNVitRbRT+X\nb7TyLKGaal5ckUE7ocqMXjAv4JqVmf9qtbhH0Dwt0sdRqajIgrR2PoaIs8iv\n2jt1JqyWl3000Umlh1sCsgja5Kk64nj/i+szvl2MxmX8YMMIU3/2xCK6E3QG\nxDjawDEpWJkbkcCa7ycxmPCTIhCxGVH/dDR921kM8FB9fWAB7X7BhA04StnV\nRXi5x/EsiSf0lTzOXw3/F2cPVrJzPM1GlIZcf5R95k56e3h8NqD5PhYrjEf9\nQfQC+Yo4nThe/FPAGGT84lbYMvrPlVFRiPVYtM3ZaGm1fC8USwjgh1ONRScw\nLAai7egXGzbjql1Ocm51k1WMnTaBgTTpe52ND4n+Puo4SLjN1nNuOmTt9I4W\n38hvtbMxBFkmL/oQXqpobTRAZHM8hhiluD8+/yb9HM5eaA1G1T+SCSDpR4uF\n787sQEALv8TQIGpTl7GiNJY+NEeUtc59V+6QAXlCn5L2PGjTRoFXDdaRDHU/\n+qA+QL3UmjKuhQJeH3NW6Ou7m6HnMDGtbqXFI8n9ZwIJIEkFbbnredNDiVEO\nXqQn3+Z8h56oE3w8m0fhjHfti7hC1ZLkXR5KAzYvcmYJmcp/NVaMvwvKHYfN\n/7/1YypAM2LP/g4GwhiC2KFXVU5xNIHRb7WIfh5QSJcQlof2uejPtefhrIeJ\nuvjF\r\n=R1yS\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"5ef66d9119879a1c7f75359dd95c28cfeb000e56","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.7","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.53","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.49","@balena/jellyfish-logger":"0.0.200","@balena/jellyfish-metrics":"0.1.8","@balena/jellyfish-environment":"^2.3.20"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.5","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.75","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.83_1610668848041_0.5104819504636635","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.84-renovate-non-major-865cb4542fe387edf3f76951a78212d984fffd46":{"name":"@balena/jellyfish-core","version":"0.2.84-renovate-non-major-865cb4542fe387edf3f76951a78212d984fffd46","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.84-renovate-non-major-865cb4542fe387edf3f76951a78212d984fffd46","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"7a20af7030343d776af938629a2e786fcee40fbc","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.84-renovate-non-major-865cb4542fe387edf3f76951a78212d984fffd46.tgz","fileCount":95,"integrity":"sha512-X1ICR7Yn5kNU0NV+Ai39RPcaGBpUcJI4MjkRJs616eE+GuskPJKtjUCG4MWi9p8owcR6RpOvxumFqAVIwjo18g==","signatures":[{"sig":"MEUCIQCSHDH9R+5wX2ULBBm1vZ7xyssWsVu44MgAnqSimHp0NwIgXlZHyFghOKNx2n04xcPEKcBRIpO4N+hrpEq6wzfqIzc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":584461,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgANxlCRA9TVsSAnZWagAA1ZsQAKJ6yoeWQ5lByM5BNz7A\nAnPv1pwg0nu7Fhxn44ZX8ykGlkgUI49JbAtnTrYq6+nN9t7EPSxNk3S9VirH\nNdFzWjc1d2kl+tAunUwdYmlK2stINwAIREZlvRg+7ShBgJPvZcYb1U9+bmBB\nb5+uM5mj9VJiTSCepFqmLVjRjKiaObjcftDcTtCJ+htOAPkJX9wLfe/xqWiM\nnlcmlNAPEB9xnGGIkhBO56hdRoQ3vLgsLU6xa2h+bfM/bozL5eeFOioyKXRL\nEyUtmHaz9jyZRw/a9IWvGWDgD9V7Wabh+GxEwZ4+HVG5SeMuwtX8n8R2eL5C\nY/KzDmaP3p16IQvIe/O6EsH6N0Rac35amVmdNlSiAJdjyQXKfDI36sxzdoUv\nwra8KZeRYqloauzZDyggVI9cpbWS+GkC56BZ/Um3xjOboBKoQ0LoIY5DSnbb\nWl2vB054IgucY6omjQm4yDCxgHH97XmxtQA9qtxCr+vh9Nc9/oceHa3uS356\nSzjo8n8INgNSpkNH6mtxU452lK2imz5LO+yAV+gGpSG2XvofUDutiY0970LM\nWkkRFZmQqMFOwqUW5JBMOlb+vt3et03c/7ivTYh37UZ85Fuklu1y5vOaWeW1\n8Z2zumJur8kmjpeOHzrOsZDGONm52DC/tUdavOIdT3/lwlvvytXD27est5eR\nsZ2u\r\n=qpyM\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"865cb4542fe387edf3f76951a78212d984fffd46","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.7","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.53","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.49","@balena/jellyfish-logger":"0.0.200","@balena/jellyfish-metrics":"0.1.9","@balena/jellyfish-environment":"^2.3.21"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.5","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.75","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.84-renovate-non-major-865cb4542fe387edf3f76951a78212d984fffd46_1610669157158_0.061229702511637285","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.84":{"name":"@balena/jellyfish-core","version":"0.2.84","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.84","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8b403075b2c5df5969796695a885ea881592a91d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.84.tgz","fileCount":95,"integrity":"sha512-j9ibLkqssxEoERXhF9vicPGTXMYBePeo7Ya0dLy0YIQDPE2ctXtfXG0eVW3eatdk/grYPJB0zsaQtt49AwIVnQ==","signatures":[{"sig":"MEUCIAuPsB9E9iiuzCWG+CDjcJVogWB+rurJW/CSHdwxX+6xAiEAoJKMJ5/DlJUiJpMTAnOL/T6thROFl6Wjbsvj8jhq5Wo=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":584401,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgAN2YCRA9TVsSAnZWagAAyMgP/R4VtfWSVzTm+mMIMRu1\nraSIcIAMdsWsA8/zztw/p5X8dO9KWwmL0NeB4X8ob5SXlXAkGJTIDxjZa9Jh\n6we/WW9/kgqdPf/kIHnPUjNDxc+7kOci77hCvEZ+ETO+IUElAH64mMl0TTmA\nnrKqF6IqpAh5kelCrmcB60+Nx8m+/NygIy2t1GfLG3PK1g/csAduaYipss+k\nZQX0t642G+5oZw/cILivbJK0e1mYmKPxRm30LE8bK1y2IEqnuCVv1vqVs/fx\nsqvrotAk1IW0eIJ9riFo+sVV0+wVZSwr0GLV5N009LfJEwOr93B+iAy38tW2\na3DVJuD7bsaRP8KNdsibHl/LimJ9DNmPNorjL5daGeFIlEWSbhUTtLQIj90h\nN1WlpzWjfgtl5rcZeT+LSUDBtlwL10g0yzxOr13U4wa0Ti2sEP8aJUyFqvuH\naTJ19FEivmv8Wz7+sOsesAvImsQEDpsdcfX7L+wssW+gE96d5UeLfwpoZWEP\ntOveP0e6r9voEIZGWSDWcnwtVah6t55Ah2XMI1LUsBNsL21dmV5NjptIL3j5\nS0e5hlEVS1wQwCM6oC6VFqqQlscWQ4AItwgp/U0/I2vZYWJxiefoD6Ki8c/8\n/l/RysI/uw82zfLaxxVLz4ZB9LahdhgPzTF6RvYqfl7LuGcT+gwF4c8TLSnk\nfDh1\r\n=KD5F\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4cf5a14a351740b429c064ec0fb6b8f5e44c8fc8","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.7","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.53","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.49","@balena/jellyfish-logger":"0.0.200","@balena/jellyfish-metrics":"0.1.9","@balena/jellyfish-environment":"^2.3.21"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.5","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.75","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.84_1610669464226_0.9147811212059813","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.85-renovate-non-major-67d321b80eac6ffcf5da5a6a91161167d3691b50":{"name":"@balena/jellyfish-core","version":"0.2.85-renovate-non-major-67d321b80eac6ffcf5da5a6a91161167d3691b50","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.85-renovate-non-major-67d321b80eac6ffcf5da5a6a91161167d3691b50","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"dae91824ca94c11ab9f611e1e302594ce7abc877","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.85-renovate-non-major-67d321b80eac6ffcf5da5a6a91161167d3691b50.tgz","fileCount":95,"integrity":"sha512-24WyN0LiQ66NA2DfN6hQgCyE691URK49H/IByQ7BM/yEFVAT2D0HSKb2s5hJdmF4Kcdz1RgMC6s5fhfdt09NZQ==","signatures":[{"sig":"MEUCICQdDzBJhGbXMBMEpFuHPNa9tZ1/dDImez+LtQE52MTmAiEA1/ir65xNA7Lt544+7Ji7/cwVl/TqbnT0AtNpIS4s/n8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":584566,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgAN5eCRA9TVsSAnZWagAAj+sP/0fRhfh821fHCpyW2o1/\nhXgLEOvixZ6zy72kaDkVjxqMXDsqOpVGYG0989GWi8gSlA7Ea/DZJBBFTaQe\n0hN/NMp090V/31XHRejB4Q6PvXvrqLaYtz3FujSktKHWgcPQ1ugu3IjwSNi7\ncTiFnVyX/jHScphNu5dBme51lBDOM/2L7pwyRZBIyMnjOvI5aI83ewb2JKEe\nQa0fpLt1O+XvXrkzc/mTwNqZifIc19UhW7lYSaXGpyWMexlv0Ryv0x+KPZVH\nH6BHK7bxbI4R80hbnlhaQqJ+qeCBEFqM2KSShVydur58RAku1hFduUfpf1k+\nV9Hiyo4fXJO8o81S8KE/1TurSiRFQoeP18bge50sVrdn2fPHUcNfiugDqqSs\nahw7LXijhxRldIXG1IiTSQwQdSZxNPXTfFlZbOvf65Eox9RlAauHwQ2ccTJN\nu9zHnER+iegWT2UNhi8ouloHjUsLxfOh0mwmXwruTbZUmb9W6n3QH08MGFcr\ndwmN6OKdc33xvCzmPSrMMxle4/WNy9Bg7APgPgEp0usjA8eAd/BjJi8gZDqA\nZqfYmGqSZFeSDIx52hkNrxsIolhxbVO9c9VYrgZtT6yL7R5exctDeTsZpCD4\nXb57NRedrGdLc1+YfT7iEs2OtGqb5UMmSZHMsPm+WlvytEZAuAflmj+HS/qm\nx0t1\r\n=MioF\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"67d321b80eac6ffcf5da5a6a91161167d3691b50","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.7","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.53","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.49","@balena/jellyfish-logger":"0.0.200","@balena/jellyfish-metrics":"0.1.10","@balena/jellyfish-environment":"^2.3.21"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.5","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.75","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.85-renovate-non-major-67d321b80eac6ffcf5da5a6a91161167d3691b50_1610669662505_0.9079554135747936","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.85":{"name":"@balena/jellyfish-core","version":"0.2.85","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.85","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8f48fcc63442256ca4b27039c1659f94a3036272","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.85.tgz","fileCount":95,"integrity":"sha512-lGAasQZQAdJ2bqYEVUg6iiU0CxeW6wF6C6yGS3TYoNhUxoLCLRfKFxo9CiRGxewl/xC3dDkOroWwM0Cxf9Kidg==","signatures":[{"sig":"MEYCIQDaLx8Hesridnb+kJv50kqAxZtxqD5IR6XcRQ0pDoq3DQIhALwEbxG+OZR34SYDwE4Qs4pkE1FbYoosQMTi6ZUkFtc5","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":584506,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgAOCaCRA9TVsSAnZWagAAX3QQAJWz7mBjN+aLAgL7Q725\n1A8zv5BIsUc5T42PDCBeuP/3Mt5eN+b6rnmQnnBJ+vYPpTWI1A+JEaKrhDlq\ncOACTo4vK0TbTX7FqvItCxAP3QmmCg4pcK9IWuKqe8n1gqvxR+PEk0Jw47Q2\n1IWQpoVaBn0Irce5gvwNkl1DKeCs1sMxj+weZ3BxMOax4mJyutiG/x4ynhZ7\nygSeQfAcvZFJB9VaeDtX0KINT1B1gWY/ZxjWl6x15BeEsU/A0JdUBDcSaIhF\ncCfnuUo4FztohhkpMb2VuteKaaeaUMJVOVDakAHuw0u0IBxAgxfi6HcXFKju\n8hhpZ6WGpaHeKqCq5p1hVib36u8FNNiRJ9kWqk3sTCuR/V1S3nGWc1FPrcav\nXygWlkmyEKyKoS+q+BDEuaQGtcDDMbaMww+XUj6h8fgHn5IDn+ghwQNOmeBQ\nYZNTakbxvgmbpuw4i351lli3R+Xs8D230WXzEJLakYK+Uygzkry9R06bBMe/\nzSPeqq7yLIspczDD4D8YPS0xRPcX2YGTkeSKe9NuaMBvmRVetkFOLIuyfPiz\nRwXUag2hTRcUTmOQQ0A8LBxh4owykKycRu0RoTaEI6HNjqfyJfIjvCWn+d/I\nKLyA3mC82Mf/md6MqAshtegs51QRcAh8vGz87XsX2ONpuqwDUMOvTXWQSa0h\n2Vtr\r\n=m1my\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"fce9e70171e79d684e1e8e04c67a85e4fc98a865","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.7","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.53","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.49","@balena/jellyfish-logger":"0.0.200","@balena/jellyfish-metrics":"0.1.10","@balena/jellyfish-environment":"^2.3.21"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.5","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.75","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.85_1610670234125_0.05053847216904139","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.86-renovate-non-major-7a0a04e98c4c93810d37f9103cc23c96557706b4":{"name":"@balena/jellyfish-core","version":"0.2.86-renovate-non-major-7a0a04e98c4c93810d37f9103cc23c96557706b4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.86-renovate-non-major-7a0a04e98c4c93810d37f9103cc23c96557706b4","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"16345fd378c3ab7dbfc55e283501094dbafcdeac","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.86-renovate-non-major-7a0a04e98c4c93810d37f9103cc23c96557706b4.tgz","fileCount":95,"integrity":"sha512-g7n/7NCAY0q4etN0k7F+IUcbnJqHylzsm66HktwmFOJrsJmukcwyzIwj5dN7XLUHxdEFInf/Rd4tWUTBLKK8nw==","signatures":[{"sig":"MEUCIEXq/MHpYeQd4OTkA4WB7Po8njO3TNd3whZC5Fzrz1kfAiEAvR7F7mHggwJLzfoo5RUfTYFSTRhdDgtKBi94m4ZUW4M=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":584632,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgAPRKCRA9TVsSAnZWagAAI6sP/37IxErdJt8XSAq+EV3l\njyjhrqJ70JX7IgqJpo0Z9Y7Qw5xk/hePZQeFiA7TQT4NgV/Cl1bJ9IlwzRmo\nyVJeHc76/lX5UJ/Jq4jmbMyAndJkujm6kgLWnmJJxI1hcwKmNHCFqeKmyWEI\nat8G4xOJgV7PJwKU75xgTvetCZpoGkKBigCVA9loZdhC1TweSnBfhBi4XDxH\nROkV/ASneQ/F8SBDMNRlRHHUroi4FgMQcyieXr6/Wb2Kmo4ohGLwUep0ibXM\ni/BQdcpyn0Rz5Srh5zJ0aeyOt0xmFdgxl5XgfG6YnMjm8ye/v7w5NEJt2TWB\ngM6yVeWykepdMj2u3MqF7zOklSskmPTVxv+5v8UKPZJ/NlzwkpiHfetpyWMm\nX3ckQwmBQmnCY7EX2F+avssYKuxg9lGi7tIVt2Vir7z/NQe96I3b5JQP1wlG\nt339Z9fzvXkovbWEx+F2aq0+NWrCakmO0AIMZ2SRNT8uBBWwfNsspG9Fn9WK\n/CHnanKg6tDuu35pVmyQnJrD5FPLq2x9NPA/hH5s1P9sItOXp2qkhtZwDeZa\n36oFLcoMKkPSbIlkhQwyI1XATT3CbmvV/DW/JKIM1uc2TdxT9M5Y4BMoWRHT\n3VGHy0kAGqNdDqvA2Dh7B+y2uPO4CRjHUqkNR+vwtjCWzAUwhZgRwCVRPDmL\nL+G/\r\n=jeoK\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7a0a04e98c4c93810d37f9103cc23c96557706b4","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.7","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.53","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.49","@balena/jellyfish-logger":"0.0.201","@balena/jellyfish-metrics":"0.1.10","@balena/jellyfish-environment":"^2.3.21"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.5","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.76","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.86-renovate-non-major-7a0a04e98c4c93810d37f9103cc23c96557706b4_1610675273851_0.9394194593062559","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.86":{"name":"@balena/jellyfish-core","version":"0.2.86","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.86","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"27e9c2cb648bea30369257116b6a69c835bc53ba","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.86.tgz","fileCount":95,"integrity":"sha512-FAIXhMIME/33f3oN47V1B7FFsMymEvdnUH9rsOuAUn77NM9wErpck3WWeSjFvRwJhoF1TQzE0+lxRguStmQQxg==","signatures":[{"sig":"MEUCIB4EoVCx8B5vTQN9PT6b2lteMzvPrqaVySxn4mOjaB4CAiEA4WkYnC7bWTBhjwFdkTdVw1j+bXY8UMebQcKGCsseZJ0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":584572,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgAPWMCRA9TVsSAnZWagAAQ50P/RIGZdH5WC1/tKkWpSBj\ngq3m9I4OC604oa0sKWBrFukewSDzQ4vNM3JnrBhee3tgjBYBFEzuAD+tmT6R\nTBvBFt6B43s998b1FxH7B4YlYrhKAAJsOykZ46yq8vyXYj7waEWz3LrdfZlS\n9eG6H1xiFoYkv6XlU27JHh19f3TJUHYw0lkFW0jbxklP32Im2wmOdsRPXjLl\nrQdLyFM+T3R5ZxEUKJ74zhbzv8r7UivswgcL2htr8KE5ALBrI9EqCHwuKMko\nt//yc8sd6CQH41eAZw3PppGR2JVRwzjkzypnZPiIUj7buffXgJnNE8qVmWwu\n3yoU53BkTF/jt/sTDYhLRAe5kCIclyHbHpo9Fey/1/reemR8x5UjlA5ToDWe\n2pULqgyPJkM/tUwxeXTea297AwuUjxhNyR1ny/oKwq6N/gDDu2RK6QtuQa9Y\nM8Wb18ZjQ0ULUE0/NsvjjuRNLyR2YRPd72gF5e44OdqrST9ksmrGuUAMM1im\nldLf6HFVb7F7ANt+ruc9nOGZNlg6kWvfSNk50n1+29AbV6QoyXvWaG1oAYYI\nZa22cHKtpBiswMjtKkJ5WaDavyOiaV2sxPFasUBNI3RvdjE7C+LT0EilPEBM\nQooBkCfvxs9TyfvYzVK/pC++89O/NGcDkwJlwnZCcq5jEfXl2/rBvlMwbK4H\nCVIg\r\n=AnZq\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"aade535bc982fca3b69652f886b6fc9aaa8537b7","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.7","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.53","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.49","@balena/jellyfish-logger":"0.0.201","@balena/jellyfish-metrics":"0.1.10","@balena/jellyfish-environment":"^2.3.21"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.5","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.76","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.86_1610675596206_0.509545932229341","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.87-renovate-non-major-a3c58ed93801fdd829d5472f21170c899b99d8ee":{"name":"@balena/jellyfish-core","version":"0.2.87-renovate-non-major-a3c58ed93801fdd829d5472f21170c899b99d8ee","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.87-renovate-non-major-a3c58ed93801fdd829d5472f21170c899b99d8ee","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8b211227d947ba27657160812984267c378d41e2","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.87-renovate-non-major-a3c58ed93801fdd829d5472f21170c899b99d8ee.tgz","fileCount":95,"integrity":"sha512-kyjUTBZupq5aWAbl1gWmqfy+1dnBRjYM8LrO7hAyo/KGJQRtEXkraFUYg0g3xFl542fGCCB8pY2aahYY5Kngnw==","signatures":[{"sig":"MEYCIQCw1Po1KEvx2r5D6xVbn8usmZD9l8HxujRxygnU0DddQAIhANKnYTrMJL/JZNtnN+/KlStycI9n07ceu2837vHzW7qq","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":584736,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgAQQ+CRA9TVsSAnZWagAAcaAP/iIuiNvkM4loD3FuZBOQ\nAtlsjgAYCtov0MJb1Dcp5HoBXpvKf62RvjNxnCECzjUuAInfeATac+UslwZu\ncKsZyN2L9r0lVhR77OzTUOs3AbO5uLyEzTK3yL1sio13AUBecDxgXauVtEox\nJy5t9bkYaPXKFcq6qB0f+5udrcSo8z6WcYkAW1bfOn+dGqQMfFb95Z8kwPPi\nuWUBxH7hNKoYWHiHSo7tdgC9Y2yxBThyAakia1vhQq0UN+i+A3cYu2pAbLdQ\njDg2tTrg8tMII0DCaDj6LvWWXSPCmGe4WHkNeyVJFTr+yp87TM4C7igdAiEg\nOjRtL0jaYFWMJf1+sae87xkx7Fz8szhsqcCjd1LC0UuQIh5Ud4xbYsqKzesm\nUoUIqmX2xVdlBlwCwYOyR4D/VsGoVX/KhvOP2jwrubHp3fnTgDwzOrP3FUPe\nHtbaL9atfGu0kRyScOVoOAO6M2oepmGxFFv2iWl3XLVXELVazMBIe5j/sAoi\n3Yy4qLfv7IOXw932ndGTipyguoMBLihqoqMlKlNJ6YK3LoJDX2iCKM3tfSs9\ntYjT/vOmGjfTBQoDrJv9cBut8VXEMfvO3g4FAgqOLzz8z+ZhOgSzzeRTaz0k\nBLbH3NUuZdpk9RWW/sJE7mTssMnrbyEhnjqODk90Awl6ZbIsj2WMH4gXwQ6s\nCAuZ\r\n=rkmI\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a3c58ed93801fdd829d5472f21170c899b99d8ee","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.7","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.53","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.49","@balena/jellyfish-logger":"0.0.201","@balena/jellyfish-metrics":"0.1.11","@balena/jellyfish-environment":"^2.3.21"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.5","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.76","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.87-renovate-non-major-a3c58ed93801fdd829d5472f21170c899b99d8ee_1610679358113_0.8714767447156311","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.87":{"name":"@balena/jellyfish-core","version":"0.2.87","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.87","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e5e23e50254e16a9ae6b5d0b1e138359696b24c1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.87.tgz","fileCount":95,"integrity":"sha512-u+eTtNlRu2GJzQB3f3K6EZ6ckL3qdRFxnaCEMlKvf4XrTWcIVY7lloIfdi59bo4KeqSALL9o/GruQkuccz0i/w==","signatures":[{"sig":"MEQCIGqfiFvML3U7Q9a94waFZIxnCnQ+y3cq6686QtE6UVbSAiAQYWKm+y1nEdnTjvUIrYlzlL6qtM7AaDcYn3hyVfsjLQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":584676,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgAQWnCRA9TVsSAnZWagAAtu4P/0Evxw0saaeIY6MfWxwH\nH/o1R6RwI5sjE2DC+NTCcB72KUpt39hYyk6NPfLdd14IkhBj76LwQgga+KU5\n5v5Guv/ljnjMyUFhdnxARM/46+9LZOa79ZID17Xt51eTZ2yKrbeSsFt6qQzB\nF2fMWeE6YIe+Fz1yqfV6dN1d1mm3JU15zwG4UVj7F6phD9f6/wXOlGbu1cUJ\nis9LZqsf6dLAAIsBxBtNZ/zrXf3zz+OpEXJMwJnRVS5ATXy4bQ5pW3Y2Ult4\nTi4txZ0lZUw96lLCIhbhsO50Cox8xXes7lZn+/wLIYsjfpCIn64iL3Tk2MK0\n8//WT65/GQLzRxG0GVIDIgexpeF5d0t34DMzm7/a2BVlelBTwaMCJVumlH+M\n6OSWwkasaeetqt32CZ6zB6rPeqp6zOpHktCSDHLc0hHFwkXMVoa1/vCaWXbS\nEVKH6uroJVvZ9s+/fch21KrxVkoQ7jcqKnvOBuirfgK8y8VKW5/vwMhIcOlX\nZ5v7BNoFrDz55mszmju/BfMyWM/WMxLesP1iom1uQtjYYrbghUJiVOfTsQ4G\nJHR3ereWow0Nge9+9Q01uDM2veiMFq6RThoXxKZo9DAzUC0Ic1fNU5PtFY4P\n8KTrE/lIcZBqcltIqmpFa7w0EXtWDwro6OOtdTJ/nsQhbQoIOmYRzTB/T1/5\nhHz8\r\n=sZNJ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"5713b79ac6a8ef269db3d36a5000521ec556abdc","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.7","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.53","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.49","@balena/jellyfish-logger":"0.0.201","@balena/jellyfish-metrics":"0.1.11","@balena/jellyfish-environment":"^2.3.21"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.17.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.5","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.76","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.87_1610679719075_0.15138411088592263","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.88-renovate-non-major-ff113d571168d729ab839c4aba5af9d7d469cfc7":{"name":"@balena/jellyfish-core","version":"0.2.88-renovate-non-major-ff113d571168d729ab839c4aba5af9d7d469cfc7","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.88-renovate-non-major-ff113d571168d729ab839c4aba5af9d7d469cfc7","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d71db54e9e316746af74b226f9c32c423bc1d726","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.88-renovate-non-major-ff113d571168d729ab839c4aba5af9d7d469cfc7.tgz","fileCount":95,"integrity":"sha512-33WftwT6uUQdlX8dMfObP3WsgcV4zxh1YQS1RUy1mX4uACAK6oyJan43NEG2Q6AfMQdBYfVvGaCJpjra6qEhEQ==","signatures":[{"sig":"MEQCICQmUwqhrBdTKXDzLHiSoyX1SlMZnqV4765jGgetRezCAiBemJ6Fvs5tMVy3FN1yTO616XJu2Z7RJcNuQelWgTN+6g==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":584802,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgAj7ACRA9TVsSAnZWagAAXEwP/08eE7hbkIV3SNk5BacV\nH7LNM2EvL7VGx7iBh/8RIGyk+iKW64gAq9q7LWWxFSSFlLfqly9tLv4k+8hT\nXHvaTqpfhDmQ4bZcjLsPq4nAcIftR8Fp3NvK+r+cdxUrFdpdGFG2wJ6VJCMK\npCSzQFzuaXNp+inXq3Q/+fXMoabmP5HM7IJNkli5/mpo+zCf/xLrOl05xYO4\nYf8+Sgw63Gl4nPN/Sfm218BsAio6Xa++aPzDkXh2OmUQQoV5JPHoIZLYdvLQ\ndFZ+IJKXZEpE3nF/v7GVhae8Z9R1Ai4bAhbtbHeko7e5n7Y2dhi4lTGfSULf\nFNfifBKa7q1d6p9p4sthKuPBU0Yz1Wfa4V14iEBPUwZQDrWUpIxlhpwmcCQ9\nagliXFg5ufvpQy2Bn4HrbQd9yB3MJZ1/pKAq8u3FC3/TSBMLNjZn+7MEkT5P\nxz7pk3MBiQyytLKEwhTvXDi+eYr4YybyGeFNWAeQnpCGTkZjKmirjyT3pvn3\nTuodq99fYvArRLqmR/lPZe6u3Dqwc6oVcsG5B3axUWbsp4k/v2IKXZhXobsg\ndN2rmRlPdC4m4xNJw7MEnjRgIIB7vPVm0inN+RIcQ/bviXMCVDgQVLsXPs+C\n7Bi13/MNV2e7OEN6YZTIShTpZku/zNTzDUOOm5t06nfimoKgU7MF+ti1pfK8\ndgSQ\r\n=Dtxs\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ff113d571168d729ab839c4aba5af9d7d469cfc7","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.7","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.54","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.50","@balena/jellyfish-logger":"0.0.201","@balena/jellyfish-metrics":"0.1.12","@balena/jellyfish-environment":"^2.3.22"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.5","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.76","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.88-renovate-non-major-ff113d571168d729ab839c4aba5af9d7d469cfc7_1610759871742_0.7727926033304608","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.88-renovate-non-major-f715126a3cb3a8e592590d6a50a450aabeac9158":{"name":"@balena/jellyfish-core","version":"0.2.88-renovate-non-major-f715126a3cb3a8e592590d6a50a450aabeac9158","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.88-renovate-non-major-f715126a3cb3a8e592590d6a50a450aabeac9158","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"84291a863799e59106083f5f24ea735ebc81c680","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.88-renovate-non-major-f715126a3cb3a8e592590d6a50a450aabeac9158.tgz","fileCount":95,"integrity":"sha512-r056udlr4rGFPOfu7ceCROjqil4Np5/8QcnJZgL2lNnKGBVEFSqNxJ77/WrtIChLP6CyaYIiRaR6P1nUawIxpA==","signatures":[{"sig":"MEQCIFGYMbWNynkIk1CIw2Y1So9HGUJbUam4w3E8Bog0jLcKAiArdaj62MD1WoTBjDBAEm904X613fYDhmnBxCpds6DyGg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":584802,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgAj/WCRA9TVsSAnZWagAAvrkQAKA6G40yd5YU6fmPKMxT\nIWWGuBOTUcQXmaXq7F2klF9rLZepDFomsckRt3I8ig1n+1THQqa7Lbxv8W90\nqArcdc7Y2dRiOJHSiOD05MjKq9MeGiHf/apI8COVa6EktoCzkpAu62AlBHv/\nNODiDBwukhvtUZ+EW3ZROPDcgGYtEw3Xsazu93aZZhFjQhtc+8jOzYk6RGov\nIttrkoWt6JPriby1I12gPJ1oznYc7ZwaLMXvIgMNz8aEp4c8E/9emThq9ndH\nY9uwoocq5feXh/AWQSxMMazp0Gx6Gyy2Hd3emLws8tnLgMhJHq5siQs7RvM+\nova/wsNCO+HJZvKpkhzxGJHnDmvf1SXsbBsElWVqy95C+JeTvB3nAZ8UtY5O\nUtwrywXxvGPpD4l/fuzYgkHqG3sx4ESz5iSJ0wCbjA1IRLUZ2o7aRp/+tOrB\nSe9/k/r/6tsiGTdi+yD5ow+xyVbJPlzH6N12CPMhfa3wFEgwtrr0xfHXT3UQ\nJfyU1SPlVNum4f89sI6QaIhmF3HLGgrONGR5Scefm1p7QPyZddcttRtmFu9f\nLIhqiFLPTff8OI3C+pDDSeTYgo7TJcGZmMQCKs7BwT174nm+btx7bBzYzsJp\n+ZLRu9umHpNgiSH6DnmIwH68hUeysT03Dm4qcb2OXNHeP73KijpCQjWKi9N+\n0XdW\r\n=JNEr\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f715126a3cb3a8e592590d6a50a450aabeac9158","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.7","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.54","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.50","@balena/jellyfish-logger":"0.0.201","@balena/jellyfish-metrics":"0.1.12","@balena/jellyfish-environment":"^2.3.22"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.5","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.77","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.88-renovate-non-major-f715126a3cb3a8e592590d6a50a450aabeac9158_1610760149693_0.6798723131961855","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.88":{"name":"@balena/jellyfish-core","version":"0.2.88","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.88","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f78348172b6613d2e4a61796dca3f0d0c11f4093","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.88.tgz","fileCount":95,"integrity":"sha512-f0jtR6LjI0r1bTzGL7uixOnP76piUDEQkYjFlHtrXS/DgkfrQYMuaKX8Xl/8xDS8hqZlSaVvifemjZfB5veQmA==","signatures":[{"sig":"MEUCIQCIapg8M4AEd17L8NYjrKymiT0C1u/KQPzharoDdbIUGgIgBZRzEvL7C5jYZZSGidFIMkw/zuLP7WttP0m0oSS5o7Q=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":584742,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgAkFnCRA9TVsSAnZWagAAESEP/3BlSUR8m2UPepuynVtV\n+GlVAq2aJBmGnZ2DudMKJ8sotQtsnI5gSHu5HooL8ZUzu5Zlxw5kG7bY0qAx\n1dfpg7ozoD/V5haFMPbqRyWQDoVuokpxoVjBKJiVNqP7xZz9NOWwK9VA9Z6g\nW5lCLVtf2nW//LxmIRhxj8OGSYQAgBAlRUzVzfKzCDdJEz0AJ8zn+uAmvNfb\nH3jezBybE1TnfTbDOGNq6aheq+IXSSDFAE7qDxATaJDyLMjUFgcBQJ+l77Na\nIf+VDe71UNo+Ejw+Vgw6a6u5v/XAzIq1woE9Kc6k0+dBTPDrVVt1MTdX6HM6\nIbsw9L4CHR0IOPtutudZXrWc1n3PvEyK7S1u/VN0M6pIBD//UUZO8fa+AJKq\nrnBnA1gwQIoX2mlbNrLSO3us/Ue6pLuIoGy1EJF5oG8hHuvxwXVI96Pkn4AI\n5ZY5iBKSfGBnunnKXxXF0sHNSbCu32hEXtzrHWqMtWz+xFHfSoHQev/+xZhH\nrHP3tcTckpL5oGW0hUprZ6ED6ggADjjVae1qw44HccFj/biytM23gb3cGiJ6\njMfBzsdzsNLkAYKP4j/Afeq1B7qFFVWfaWDUYeQ0fE1Gg2sxFNftdBfz9ANF\npS+VvWXx+/qq1+t7gJjCbsmEkiNqo4G7TC1uX94Mm8u4XvQy8Ib5N2WcfgZv\nQbfs\r\n=X8UU\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6bef1520fd2a17bc272e0c6324fc138a8fd0c0b1","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.7","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.54","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.50","@balena/jellyfish-logger":"0.0.201","@balena/jellyfish-metrics":"0.1.12","@balena/jellyfish-environment":"^2.3.22"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.5","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.77","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.88_1610760551210_0.4906812603570976","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.89-renovate-non-major-fff68d03f744fe3d651ed8094d61355c6b994e0e":{"name":"@balena/jellyfish-core","version":"0.2.89-renovate-non-major-fff68d03f744fe3d651ed8094d61355c6b994e0e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.89-renovate-non-major-fff68d03f744fe3d651ed8094d61355c6b994e0e","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"085c9437e9c7e8d927c4dbe8d9a127d3231b704b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.89-renovate-non-major-fff68d03f744fe3d651ed8094d61355c6b994e0e.tgz","fileCount":95,"integrity":"sha512-EWAEfrCjWIWgMUYFFW4woDrO7daeP5uP/iPkAK8n67jI70ij0YNKm4oaZibaIBJr3jgHEz99GwsPP8E2YF5iLQ==","signatures":[{"sig":"MEUCIAOUc5z66R6jPyM5H3jVPrI9j8J92Y3gdVHy06A8yN4wAiEA207q3mOZG+AqSQZLnJzBtOCBwzNnyBpboihBhI8R7D8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":584868,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgAmMjCRA9TVsSAnZWagAAzIQP/iPEqILG8I3CKIIkgXXE\ngpIejuRlK3TTILRr218KPdgmZEEmdqJvjqxEfaHvkW1bI+pD1MUFoBZvuIjF\n958OiT1RQ1Y3+i6lbvJgKTSLPZozOlR3LxRvQ6HWnCPOVeSl3di4P7mTeBOj\nHFQqA86BzmGoIgGVnxRHdFdNcog9nd6qufdidZ456JyEp9WGnfT2PYPl0MEj\npYTolrnY2oR7K4jKT+9NE1yHhWp7+sBnu0/Z/CN/sJY0169FsZvyVUHc6MkW\noKkgYhvwXa1qvHcXhScVj/FJ/XVgPx0xEdbni4asoMYHbNfQxOGEXqWTxvZU\n9EqyPW7CzHv8TBTKjqg2KERi7PlHPzOhD6veT3LX7uxeHvycufmVy4eGYcLp\nYR7YtkPYD4NJ1bwjhRCvAA2I28KgbDx6I049nO9+s4hDHbpBoN8tLGdlI7H7\n1JM0gi1X4Bi1/6g0jgNkgDYad0FBcebhaTYTi9w9gQhD3g+LhKlFGwWYliBY\n19Z/ptmTVIiTEAx9gZvjmzsxfbEF9oVyxPLmzXzCVUiMIo3Dc8ziGBfKfzkf\nR8SZfgGO16oYfbO4gM+rnkwXj7dpJh4YlzGjKarfAzGHxMuOoQ9EpeIerxEG\ne9QtXTRaMTkStStQyR4+gwNxVZN5cMeeFtbLijk50s5mEylahyBWgIVo7xhH\ne86g\r\n=u/4M\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"fff68d03f744fe3d651ed8094d61355c6b994e0e","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.7","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.54","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.50","@balena/jellyfish-logger":"0.0.202","@balena/jellyfish-metrics":"0.1.13","@balena/jellyfish-environment":"^2.3.22"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.5","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.77","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.89-renovate-non-major-fff68d03f744fe3d651ed8094d61355c6b994e0e_1610769186926_0.5823103410600194","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.89":{"name":"@balena/jellyfish-core","version":"0.2.89","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.89","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b3be96f3ba9fc19d8e67741d10fcedacb264dfaa","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.89.tgz","fileCount":95,"integrity":"sha512-oNt2pLpOW00Q1O0f3Gt/LfM3Mf44sMTEabB/FjVNthXxHsJSVZZpCnqgW61VnokUPtLKf0EWdM7YRq5lZneipg==","signatures":[{"sig":"MEQCIFQtiudZpYl3QUbQw0THaayC45dTKu4I3ECOdd17q8XMAiAQMei/MjrbxWc9vyuSA92bAFOilJ88XABwzw/kYq4ZrA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":584808,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgAmR1CRA9TVsSAnZWagAATg0P/RcS4Zs2HYpp+oWRHg3J\nZe0ONv+qxYs7XDB5dEMXGxVvtYhfvmOlopYq5tKhOSSTzDvBMvFFCjx/r8FP\nXZ5/xZgN9uwNW97suM6TOOUhbGeAq8fcBNtXHX6cc36pLQFI1ciKtTM/AtEd\nzRQMghMPN1u46DvKLQcJi698Re+Vw9ugAifoDu0fntSWb7YG9tgSH2pytaZ8\nFQKUz0ydDu2/rUEtBL0CNzP5JRi8VKZYtHH5IuAUD8tifJP3ZrgaRy9OYEN5\nuamLv6TV+C+MUOWut+8SRdI8SZvrTCXxCTlLY5Z1Br7e1uqUKs2vf1qkgBtm\nPpJpgMbFnt/wBgdlR+bRhFyVQ3WvWeuv+CiQqexit/+yHlfSg6PfEIo4Y2nQ\n0Q/wRCdtiAyAON0/ep4N5P+0+i1NvxzTy3izR7assuf4OD56NIzrtjHAFQb0\nYvMoc9K/M0SPw9ghEpkHlsRzTmUnVGj2P4dKrPtf3ascjDn79s2vr/CfyQDl\nTxCFHZiiy+Hk9YBd3ScTgbVerXNJyj1nfO1WFvp/3Ilr5OE2VAiQY2Du8d7m\n6NwG6gT+/XB+fJzim8YFVZYs/8SnqsYMI7LbbjdjKGxvE0sjJDED2oPKL0UB\nCvmNTWD+16NzIVV99pCkINgzEzK/nvCJhfx/EXjGftrVZawVVSF5DSqozaM4\n9MDe\r\n=Tqlv\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6fb3c328b24b060cf32ddcf447d8267e49313f8d","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.8.7","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.54","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.50","@balena/jellyfish-logger":"0.0.202","@balena/jellyfish-metrics":"0.1.13","@balena/jellyfish-environment":"^2.3.22"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.5","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.77","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.89_1610769524825_0.3711811969696419","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.90-renovate-non-major-3d67702ae38f0ab7399727ea980205446103f3be":{"name":"@balena/jellyfish-core","version":"0.2.90-renovate-non-major-3d67702ae38f0ab7399727ea980205446103f3be","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.90-renovate-non-major-3d67702ae38f0ab7399727ea980205446103f3be","maintainers":[{"name":"toochevere","email":"pj@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0792608e6989adb46221300e72987169b391abad","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.90-renovate-non-major-3d67702ae38f0ab7399727ea980205446103f3be.tgz","fileCount":95,"integrity":"sha512-uPOLmHTTFgAu7YyP2MEWISkU3koKWgV8T02g0KEll3QKA+11qpHnIGydP+kMJpxm2R+pja8coEP6ocYu6GkpeQ==","signatures":[{"sig":"MEUCIQCcA7g4+60rx7b23QZ+B7RJ+9Yh4q6J+k41FJ/8fq2TiwIgbhtJHVVw41WQDI2gfUiE/FnEBhjqcgj9zg5y6uNSOT4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":584957,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgA4IrCRA9TVsSAnZWagAAv6IP/icRIssHEiRQZ4e8uVhE\n6HVDxq9xp3hJOGrOPhrnZ14OflPnlCLyao80JeCldCZ/sPbrsw1yha+N0vAo\n/SEUeq7/IwsbNzE6tRN0DBjK+WiJba3TRMC8Z+fpKjQO3tZZR6rLLfQeruly\nNkqL2//PP8tt18Q3jb0HyFxhOCD5a+kZcKeORZSoVsw/gHkIylUcrBA403lh\n3Wbx0T+uI4dlDPoQk0vD5lAumdsfxABpUSIeAtjzHV0lBmQHuxMeQBXvNWHR\njGRy9qP63eC0Cj6Pe3kNoq1DNk1856S3O44gfgvHZzdsk0Nta5LxDgac9oHB\n2GdeDwoUttSBB0cqJxmmG0xn8+jlwkDUUO2iL7kiJzDLAmNQahKDNOC3/lsF\nlMbC6mlBFf6p1J+bs7/cYEtWhwi0bfX5Bo1RqKEwueNVa0ZfR6fBIf5dTJ3U\n+zcLG7J2InrnWKKkPlAiTHzfdkaXLP0pwKEdO4FB9nb4Ow5uJ/CErMECIBPL\nfrOM91DKyG0pITcm/q2xJplDFCGyCI/sTtB3sTJN708hzqH6mXOJ66VS9DtK\ndYPDjE/tybu4bp1lnXV6i0qFSIiU6elaPSU2fhcFhQJTuC35VRIa3DI6g3G0\nIPmTxQWsMkBPEwiMR10RP14Qv/+S5AP9AbcIv5lSqK1MGkzQvNbXkKT2oxsU\nbXHz\r\n=n/vz\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"3d67702ae38f0ab7399727ea980205446103f3be","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.0","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.54","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.50","@balena/jellyfish-logger":"0.0.202","@balena/jellyfish-metrics":"0.1.13","@balena/jellyfish-environment":"^2.3.22"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.5","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.77","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.90-renovate-non-major-3d67702ae38f0ab7399727ea980205446103f3be_1610842666410_0.03160538637900623","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.90":{"name":"@balena/jellyfish-core","version":"0.2.90","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.90","maintainers":[{"name":"toochevere","email":"pj@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6c322c27cf24ec75aaa7529c2dbe32db0c186ab8","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.90.tgz","fileCount":95,"integrity":"sha512-TbJlwEvlm1lj0Ew2LmWWr42NJWWm2fiatMg98zJCy3z7utYc8+V93oon2TOPMi6mli20g4t/vFnOLkKvdaCj3w==","signatures":[{"sig":"MEUCIQC90BjT/B4s00CR0QRCbIe1eOBfu/Wk18mJP7ihOMcGBwIgMM+QoxvDHVtCC0qafZE0y5Q8l7gETvvX0vcXvDS2+vU=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":584897,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgA4OeCRA9TVsSAnZWagAAMNAP/1NL7yIfyJqZSMRAsfQe\nrcdUS1Gv8R6IR3TeKm+6b3GP+hQ2yM3QCrM3HtfVQjxElKvXaKkGnuTu/DT/\nYAYErPF8fdQYoPDOdXyXxhjixrSVkpN0W0IX8Sm8pcnBXXpjetwo2XkgU4OK\njKUyzx0lA5+LZeAr1SGyxIBBtaPqzLSNtmK1qBAlCucu7HJdVUMUOv39SDnd\nE7rMkqO7q/8ThelV1sK5YOuKQvcoMKYanWusVlWKJTMQBOk0hGPv9u3G2/q0\nEQmRJGnXdb0d+2Ot8KYcWDn3IecTxQzMgSR5MRWo9oEzcs2XbMtgLZtUhB5Y\n6X9RHF7rtkXbvEOWJcBjRaQYCRSLwigpzkBEYD0UmdAgz/k71RDKbPHIvDB9\nUHQK0nxO0lc1+M1Nsp5kgaahrLdelPhJuQF9OneEixR+xSiwJFhxLBsiThjJ\nekfbOIWpb63QSr4GQaiSmYW0UzArnwK4/JINoHkCCb0RNTfCMPG5WMMPw3DY\n9+agRPY5fsaIotEAOPWoa6045d8RcfCeeg3jZecbmSCh3UQgCxyXrXsJBbVV\nSwiqU5uKxtTuN5rlh5Qpvb/ulmbrqoARIKPLooOLwjnlKlz36mR/9BiAJMNo\niNkq55HWDBXwE02nyrDP1Ep9ny2+M2q1y3Yk0XULKMAIZDNmUHXl5y5m8qH8\nILG2\r\n=m4lF\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"155304028e31bed89bf60bf2da807ff4415ec26b","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.0","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.54","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.50","@balena/jellyfish-logger":"0.0.202","@balena/jellyfish-metrics":"0.1.13","@balena/jellyfish-environment":"^2.3.22"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.5","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.77","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.90_1610843037988_0.3663294225449949","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.91-renovate-non-major-5efd364ec193b28ecd07f5124d0b5d7f235d56b2":{"name":"@balena/jellyfish-core","version":"0.2.91-renovate-non-major-5efd364ec193b28ecd07f5124d0b5d7f235d56b2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.91-renovate-non-major-5efd364ec193b28ecd07f5124d0b5d7f235d56b2","maintainers":[{"name":"toochevere","email":"pj@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"580e3453e486b7598e7304414f0078db812e503f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.91-renovate-non-major-5efd364ec193b28ecd07f5124d0b5d7f235d56b2.tgz","fileCount":95,"integrity":"sha512-wD+VxEjv/ZEY7w6vTgVJFohjemUAowjUqk0ZfPM6irTyxLOBkc3c4nDQgilLQjM0gNPJo6ZPHNDpHpJ/Htww7A==","signatures":[{"sig":"MEYCIQCinWISntUlQjx/QDwRVrDP6liK6xYYkwgo3YOlYbRvagIhAIw3Ft9qlZrQogYhZZQwI7GT8Ovo/OeCe1zhrRnd5qS+","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":585046,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgA9YsCRA9TVsSAnZWagAAG2IP/0x3u7ROQalrNjl88uhA\nDy/j3fzJqlNskzqpyhtRb6T7fpVJ3onr5o0y3jixXONg1Fv37QSFE9W0jPnF\nwHNBtxCC84sRv3kjtKtbgjwh6ZQU+QsR4muJoFRrCeVM7ju4KXHa8PcLWPr+\nVl/ZoEoFSY+gOAZ3vxljvEynawN5t+UTXBTtVxsThwn46q8IJXJmwXXFaFMd\naHWJdUe4EvMEm8TK1rHAFyGFKiHyXudAaWIw1Sp4suXt1Yuwu0bqB1m23NCt\nWjKTXCbXDFSg0qjGoMbAawvhieD+FP8eqezmKAzepuC/yrKwFOCxEqo8pgQq\niQ4c4Qa1AYf4eI29NCRfuekHm5XdjRrg1oH/Q/8UjKs818KDWdBd/julIoKR\nBBOd+RQeaHl3Hu+QDJNNkOHO6Ucmws+eC91pwGSOB0XGqmr+LYThK7m3vunR\nysjMZmf3+e0NyM5mYna5YYehRphhG5XrnDXhHGcz1lnJv5kGR5idbJmmbHBg\nS/c8haYva2OLLGJFItk7qBzhv61V55T8OuF6P8ajvUJRs4ZQXeWBmiI2mtAb\n37wINQHlu7Q2TBFWcsBc3yBlOwQhA1pHHN04P/KccLAmSMmSQF3yqufcqTf/\nKOCpmxLC1dSF2KxFXva/y890VW96pr2RL0mvVw1qxz1GC7oUMk6ZsxSQTS2T\nH4V6\r\n=NnKA\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"5efd364ec193b28ecd07f5124d0b5d7f235d56b2","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.54","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.50","@balena/jellyfish-logger":"0.0.202","@balena/jellyfish-metrics":"0.1.13","@balena/jellyfish-environment":"^2.3.22"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.5","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.77","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.91-renovate-non-major-5efd364ec193b28ecd07f5124d0b5d7f235d56b2_1610864171973_0.12622597829621673","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.91":{"name":"@balena/jellyfish-core","version":"0.2.91","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.91","maintainers":[{"name":"toochevere","email":"pj@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8e27a9af9067211de77d90316cc72886072c8144","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.91.tgz","fileCount":95,"integrity":"sha512-qbAjT3nHF0oJVwz5rh7I6cD++x+Xyq0hrnK0AO1HbFoqlimhVifQzyusQy/wBEg4zBEaMmuiMs9ntijhsuu1Hg==","signatures":[{"sig":"MEYCIQDU8HIZMljDePXwMTlvGVBp7/9P4mAO2rp6yBnmDtaUEgIhAMhoToWp59PapKpbw7QYFwrIuuzlmKPjzM5ZxcIb/YA8","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":584986,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgA9erCRA9TVsSAnZWagAACugQAJvgxms2GfPmCBa6c95w\neh12S+ZXBZLqTZVgcxZcBtvfAN0XUbQboyr0m6LF6QPP9Cm9UkH+m+Cyd2Tb\n+sOekwyp06HZQjCVwvYaOTuK6SBNAX/RcOshi9qE2YTd63ADXxNqBgHf8GdL\niHJhBsbg8mJGmPgFdIxRpaduZchRADXmO6RN46CsUoSM9pDDF8J8NBU2Kzta\n1ls5uSFijlO39CIE6xTj1RhCFmV+qsD5F9VplfCCSy1ci9bhy6RshI5yQBye\n0VemSYo7zqwmy5sgCRMqppEvaUd90LEyBFKHr5MVSmLnoFAbMawjeYgaJ6H0\nlbP5Trk61nzbiKfrbqiDHeeFOIziTWdCNSnsexuI53/TS1pRmSSG0JcqJje2\nNfspKWA0D9dtUDLpEXfp4A1P5oM7JTCetS1/w0Tu+3ardaEJAzlWywekIBJN\n0os4ZgO79unF3IDJyMRHr+Dj+QOfVA5u0bSGH1PbiYJgTE/QOdujDb078p0l\nSAhA3ngGYOGytXO8kbobOjiQyE8qCOOKiSHTCW2/9Tplxsxg+esu7+g5srqa\n0EhdW87iKJHKJEb9HRYy6CmjGptQ9afZa3r/ctgxqzZpy/zlSmynHpXJls/h\nugcdgdY3jCkXn7EkTcCJ7bMKWudpGGApX0K02JY6ZxqnPNBax+dcaLA7q4nv\nB0N0\r\n=wZcn\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"333b7edb91bc34e43470d7264e81d0857f78cf9f","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.54","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.50","@balena/jellyfish-logger":"0.0.202","@balena/jellyfish-metrics":"0.1.13","@balena/jellyfish-environment":"^2.3.22"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.5","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.77","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.91_1610864555096_0.31028353109372353","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.92-renovate-non-major-02a375c3dbbd2d7743d87dcde785b857547f377e":{"name":"@balena/jellyfish-core","version":"0.2.92-renovate-non-major-02a375c3dbbd2d7743d87dcde785b857547f377e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.92-renovate-non-major-02a375c3dbbd2d7743d87dcde785b857547f377e","maintainers":[{"name":"toochevere","email":"pj@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5534638e907334ce7a01d3e53536d729b6dafcd9","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.92-renovate-non-major-02a375c3dbbd2d7743d87dcde785b857547f377e.tgz","fileCount":95,"integrity":"sha512-QPYrw4BKtXLUVxlOQqOC+1QBbA2eMFOAmatSiXnbxXuU1O3QYRogUHKoN6CqOBOx7ZGVVIm1hrnyTPoQBwqHPA==","signatures":[{"sig":"MEUCIBGC4EwlNfDUZ4kA1Uxnq0047/S7Id+6EYLnIzSIWccIAiEA+r8aVkCFT3JVdWUihCWmS3qnaQbEXJosJ/0rUn52BHI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":585112,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgBDPUCRA9TVsSAnZWagAAECsP/RQfnWZOzsXZFRiac5Px\ni761QHvhVOSZ3h39dWVaKW76P4b7eRNuMW94wUTiHpO7KhaJ9IoF1W+sHXdx\nmoDnn9dePaIJ7y3kAb86mihFT4M80ETp1BCK+Kaxpu3fNjNOzAXceXH0z0CU\n7vVLq9gjFVv/2q2iY7/R0JFKrhl6nhK1wEkYwEAjoXd8LqBeF3M5cPnewCKQ\nCmD+Fstekchq3Df1F2ZGaAqww8/4GTGC/PKQYWEhaP+EDlgLn0qCzan27M/1\n7n3FXxRgumz1pkPXyx/+jCS+zD510XWvnqtA9QxOKFItsDRTODYdyvcsGKOc\nL/t+hao97s7wA4xYhGclt2oUGkTUXWZ6ai09reX0dwJLn3ceRubeDH2AUjw+\nGCratZLpsw4HxYBwiSpJPtAnLP90aIPxBXX2P7xCWkhzwUf7uK6a1cjQXw3m\n5wj02rYFQgw7JCIGWZ0suQmrVneRVOrfVCqbsaz5A5gSQ8+4LR0BHhOb/dqP\n56J2SYMZJAevVMUb/j+125eFpy9CRh00FITxSd+krsqDVDs2E3NtJN0PNrJl\nSaJQe7bfJqNFhYqkEpEdkxndIHSJh9sI+HvJ8rWkM4GWLO8r3xf0ouYQoiYk\nXNF0oakzSiWn9n3oFKTB1SEsCZN2NfOpt4T3eFFyLcNAPLtwN2SBLzX+mJhe\nY8KH\r\n=y/aF\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"02a375c3dbbd2d7743d87dcde785b857547f377e","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.54","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.50","@balena/jellyfish-logger":"0.0.202","@balena/jellyfish-metrics":"0.1.14","@balena/jellyfish-environment":"^2.3.22"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.6","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.78","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.92-renovate-non-major-02a375c3dbbd2d7743d87dcde785b857547f377e_1610888147771_0.13067894852768092","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.92-renovate-non-major-30797ffed67de64e314aa84f32ae791fea7c1cff":{"name":"@balena/jellyfish-core","version":"0.2.92-renovate-non-major-30797ffed67de64e314aa84f32ae791fea7c1cff","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.92-renovate-non-major-30797ffed67de64e314aa84f32ae791fea7c1cff","maintainers":[{"name":"toochevere","email":"pj@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8948fc8b436812d062354e571751dff5a8b18c16","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.92-renovate-non-major-30797ffed67de64e314aa84f32ae791fea7c1cff.tgz","fileCount":95,"integrity":"sha512-HWSKDdWKVXbLiSISUN6MJQLGycDizO3iZTseRO/9VXFPxVjqu+S9NU4G9aslYLRDFYWVIMlhJX8vBTly3ZJbyg==","signatures":[{"sig":"MEQCIHMIeg2C+H9hrFzdEbc+pJstLjhHo0wEHMgXYGy6vf91AiAyqlHMl7pb0mJiq7KqvjyxOhhpve/af3QvlDh79HwoAw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":585112,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgBDTUCRA9TVsSAnZWagAALkUQAJAj4PC+2sEIX2s5Ix84\n8/s7oFpZsUEcYE2A2RVlcCPy0aTj7pfiSYCzxhiz9J7Cb9+E3Q1pYFI53u8y\nZV7VO5aBZSQ6qzuSxYsxv5kBKWjAI4RvMeIvEpiUTCeyjSJapQ96W0s0Rkye\nrQFTuGIV8jN+Chz/d7KYdd/+zjZf9koJt2t10JLK/NeYDUHd/nmaZ5UrR19Y\nAtuqARoMvDNDmW2rRuDu41PMRLxvU1dUJaF41fIk3bTnyMgaxnUx2irulnwr\ngSNjz6pfgcPknb9UW0G7bzzID2ak81X8fLRb6wAPfvwTmq9dBoYqryNz06C0\nVpdfjnoooM7S1xXixU2hBGBO/0XZyHa/zZtNcJvjNEABYDR4Pxrb5vLg4ipr\nnuK031bV8k6+bY0mnxQPuKy1nxLIs6PUand/1MHXNfjewuwAnR3gdMbQ01pQ\nQS3QA/gd2zbgiQbb5c/iU00LLEMaIsZDCMn1H1YoIhMDpaQWQZAk3h/AwLGh\nCDoCqdjJEFoKNALXqrZWQFT1Zo/43ntVsCir2Y70pW7SUSm4uk9u5x0Xzf4V\n4HldGcrLMLy/PA41N4fwV/FuXRBFCq9TQsJyqHXA6OoqAyaEePGhh9wKoeEq\nNCXW6mMuZMstH1yuv7dmVI2fE53ns5BAkm/x8PC94M+t8MKBOIGfe16M5zvz\ngP4n\r\n=kgRd\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"30797ffed67de64e314aa84f32ae791fea7c1cff","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.54","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.50","@balena/jellyfish-logger":"0.0.202","@balena/jellyfish-metrics":"0.1.14","@balena/jellyfish-environment":"^2.3.23"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.6","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.78","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.92-renovate-non-major-30797ffed67de64e314aa84f32ae791fea7c1cff_1610888403882_0.9324814873889722","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.92":{"name":"@balena/jellyfish-core","version":"0.2.92","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.92","maintainers":[{"name":"toochevere","email":"pj@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4de68d77f5499a8ca8298c72fba5d2c953dcf029","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.92.tgz","fileCount":95,"integrity":"sha512-5wDUHvTqiii5tJSxaqVe7f4XcqLNI77bQIMtQgNniGDGjLqzIw55P36jYlhalOhctVYi/OceLxR1LJOWLAkRRQ==","signatures":[{"sig":"MEUCIQC5Pi/isI6+nLP31N3eqSEwWTnWS6QYY05bL4AP0F691QIgbNi0YRtivZxk4CQXE5LM5tG4+J7gbOIUxk43zwUn2os=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":585052,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgBDX1CRA9TVsSAnZWagAAlBEP/R27op/kq5IZTzkyvCjv\ntsa1PHiC7cNtGpW8XZHY3y66YZW0zoBTTqfKo7gSSIlzAbKYPPLxuiskJm4E\n6sD7Y77cGuRYxXSJfzxYr9vQEC/arAH3XqQV4CqMsnyqPOp+Q2Aq9kpckkc6\ng2Vcea7IujFxrPFxQ/52NhTK3C4JZg1/aiHQeVQHoO2eAv5XkIg1GutoruDf\n41pyCvOPhO0+wvfNt0akm5d2ejW7xsYXoShp9LDKfQr4ZFXjUcfh3DNZ0Qfo\n2EMzxqBziC/H/IMMhoYQHLrr+kQVi9ldg7qfa2GiQG/jxBnzlp/a0EcYza2x\nqUrwVTzGnxwzelPBrZW4+s7ytGNkdzxkWGP8yTJEIb8sb8lEb1vr+GvXEKFM\nl4HF3dEO3Hx6WvxG44z+u4ZSSKxJ+CNlHLn8o8i0pJIfacqEW/3qMSEAhOjy\n8Bi6r+zx64uLRLb91KXtbZxUngJ9QUm80aYQd66zS4WyDpOhOzr273FQPvWt\nP+5/aIDcgbC4Y1HQG284HjVFAxnCs/1pi4rFLTejO8dOUglb6st9Tz23Sq/c\nOH5Ymfk2LStMW2SQ30ILJeudsL1NfH62Tc7LC/8aUpM7+6j0B7M7e8/Z/LSj\nTImlw6CbBEf+x1nZV6iBkZMXzepCCisS1N0b8GDwIMZp4pf1kRLhrAttEKpB\nNriP\r\n=DbG0\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8877f15d49300fcee61964005f7c28cf7ce10f75","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.54","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.50","@balena/jellyfish-logger":"0.0.202","@balena/jellyfish-metrics":"0.1.14","@balena/jellyfish-environment":"^2.3.23"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.6","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.78","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.92_1610888692573_0.7359616165031042","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.93-renovate-non-major-49e22af49a6ae0f3ed2853534e06cd6269498347":{"name":"@balena/jellyfish-core","version":"0.2.93-renovate-non-major-49e22af49a6ae0f3ed2853534e06cd6269498347","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.93-renovate-non-major-49e22af49a6ae0f3ed2853534e06cd6269498347","maintainers":[{"name":"toochevere","email":"pj@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"def5333888bb3d90bb2b797b1a5b2cb070d206c0","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.93-renovate-non-major-49e22af49a6ae0f3ed2853534e06cd6269498347.tgz","fileCount":95,"integrity":"sha512-rCgI0bxvLI7N53jXD9xo5yqpxZXnmBvL68Pf2X5ECRobCkPoQyc7YoQQVGqse+2gsthAIj0z370xEc286dmcZw==","signatures":[{"sig":"MEUCICXe85k2aAx7qa4UaQ1cA5aeG1P56++NGKeM3z/5HRr4AiEA9oSyWC7ja5RAznYY8tsU+u/0WZp1Qg68ppbRUL7NHSM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":585178,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgBE3CCRA9TVsSAnZWagAAEfsP/3DnCp4hSb49KtWinsNo\nOdJsO7bAbDGKyTK6212JTY6C1K5QU0VTgjkqU9Uff4nUw9a59lJfytpgzVPt\nR7PzJmWF6pd8FLPH0oZPdZ9aWqu24qtpXAQKWh9TeBG1BfIzCZvhe8EemRIx\nmdQoD2vrkRxLnP5HKV3yB9olPLp7zTOChEL3VyAHVWFWRgmeutk6KhA8eTGw\nBQUE3ycR4XlGkgUH24pvcQ0zRzGqjN0mbpq70wkvnj0ZTjvv0VD5flNWuuyx\n3pVIUjdiUUekZnwM7C2qA81LVTaAexgZGDXkncHsCV6RIy1nFHDiQ0dW8VgO\nHmtOFgPH+xWcUGVDozCO/PhXTmzg4bqdpic5bj5ypv/cozTutXPk7W7wtVJx\nViRJe2KK2CwAaX+unuspFJ70DeroOpWXp8ntttsiBAhmtsvId9txXQwh/rGj\nhkLB95eMcIyK8DUx19GxsgrsN4NxEgeNfipHxecnxOCUSCWfg90uKBEPhlwE\nC3w8zhATfGLdZ4/RsH/+L2hlpXStqU1nvwi8rOHBPipH09Nz12L+h925z80l\njSXy75ulTXowtAC2GyQoM9/pVFoWGLcjLuSADLqXH0jBPSdrrj8OZOKpyfnG\n02SxB9BIKKtLDESD7s9zQiTxHh54/QL0rUkHFS8EZlPF6KuyOi2YlZToUIgX\nD+OE\r\n=SdaZ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"49e22af49a6ae0f3ed2853534e06cd6269498347","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.54","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.50","@balena/jellyfish-logger":"0.0.203","@balena/jellyfish-metrics":"0.1.16","@balena/jellyfish-environment":"^2.3.23"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.6","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.78","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.93-renovate-non-major-49e22af49a6ae0f3ed2853534e06cd6269498347_1610894785976_0.2202381151288506","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.93":{"name":"@balena/jellyfish-core","version":"0.2.93","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.93","maintainers":[{"name":"toochevere","email":"pj@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e7e05dfcdd46c26d2a79ebe7b2600e9ae05c6a71","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.93.tgz","fileCount":95,"integrity":"sha512-ou+58oxC3x54LUMkW20e7QDbtVWkghnDjYqP20WpgHSLYkIK9y7b7QXtrcC1sFytk1fqJirCIAMrpcXJM/pFbQ==","signatures":[{"sig":"MEQCIHWzYr4cRXuCpxBbebnW+gTZhMF6LvvmhOrtBGz5aVC6AiBmAnP4qK3VeOMm8V0NrEUlMz0qtWM2huqvZF3EUs1rFw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":585118,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgBE85CRA9TVsSAnZWagAAhh4P/25edNVgATVJ/jnoRcw5\nH8+5BsKBUmcEIEO/STRHwwDoZNFcgqGKBTBZXUi3B2bhuwKd8RCnC3de4n47\njinPAKS5iYvg9Sc3jOkq3X6numnBPwIylyESzL3JyLi2d+gkgpd878otUmui\nhlQTM5f+uXSUHigQbVbC/XoltYQ4iqsONbdH8UWwwiG2rdW4wmuxqTGro3Nd\nS8zoXALR/WIwlIfMHyLcrAI5kTIzJnAHa42ZZB3TLeCPPpXgYSXOVmbz4jjJ\nXrOnmDZphBqNEUpc8GkpDB/PoSruVKhvNqYBu1fENHtr4BM7SfJaDr2/uuA5\nqDEtwjgE88kJpA1ZeErXOjvY8gRsIrBx0lpr3nRabXOUo4t9cVns9f6G9Fvn\no1iERZSV9UcTyMFKegUv5QOtqzucD5eGjkMdbqGGufxlnDP5JgdjgQ8Zu7lB\nOwmkhn4SIWI7cFtHzUxzdV4bXyQ5IWCprKh5wtbLzbKFE3xaPawbZiUwjU+D\nfjYJXFUrc670sS45bs3vvpXZewsrw+Us6SZ0Lrb4BZaVsty/XGz7+HJTmYqa\nv65m+/Y+b0MqkN5kgQSIwC5Xi4MyuOReVDvTlgLxJCXXjy5D+zleBrVGQk0I\njT9leRKFbAxOlwU1yRkjGwWNuiiT/j6rFkqROYMH7WzQNImLx7TsxDOMvQlB\nhEMb\r\n=Fwq9\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"001e165e03859d77b23381092f313d81560b68e5","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.54","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.50","@balena/jellyfish-logger":"0.0.203","@balena/jellyfish-metrics":"0.1.16","@balena/jellyfish-environment":"^2.3.23"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.6","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.78","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.93_1610895161059_0.07417152354942935","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.3.0-patch-transaction-70b783b609bb4b43529021860b22c03f34a45024":{"name":"@balena/jellyfish-core","version":"0.3.0-patch-transaction-70b783b609bb4b43529021860b22c03f34a45024","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.3.0-patch-transaction-70b783b609bb4b43529021860b22c03f34a45024","maintainers":[{"name":"toochevere","email":"pj@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e5c4fe1d8e3931237a762185d4ded50928d8f97a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.3.0-patch-transaction-70b783b609bb4b43529021860b22c03f34a45024.tgz","fileCount":95,"integrity":"sha512-Qy7ZLNxT+ZI4BH6/kNtRPRvUyYlOTyOSI43ACtVwaPNbGyM1yWhXBRjia6IV2GDrzwY3Vjqe17DekyIVUqoUMg==","signatures":[{"sig":"MEUCIDBt1E+1rwfrNPbzJ7orPbC2h54n2ov4npVHC+00X2jkAiEAhjuutsAfOa4zQdTiKptAc/U7qIyWWdtAWJUR6OFyhl4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":587081,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgBNnVCRA9TVsSAnZWagAARjwP/A117B1Zu+wrnsIC6U/Y\nPO49buqqiNIFcAlcBJQkRu5FYHKN0nknr433QNoWLHLq/QVqw+7A2DdEuRqb\nM6e7bxDdmPE2KqClUQc6aCwJLSjpmVemhSPf5Y3N8wRPW/tj6q8lTnGvjKav\nkCA341aICnw5z0IZ3pN9RbAHj3wZEdw9egr8smR8+QGU/af67irrfO31Iph2\nqSN5JQdiNwxa5SOAAkBbjpRkjeQFGnK9vIfgCwrkjS3398pBlOeXbTSL6jwG\nKCl79u0AyggTw2qwBZt+W0/DAqChdd7tZv1XULYX3X1F513AdKDP7UzgBRLm\n0vMIA5cssG9h6IBno/UnXw9QZaZQ/c3SGFCrcCWBS0z18tO3PMjv0mWMDcQS\nq593e5SHehHJm4QHlBCPJntoLbccAZFimvalvVmSIla1FSfh19szTLu2OfhQ\nT2Lr/bXzK9Qe4oUFzG4nGkC1ByHgx/oTvfadhvkLgIlxGiJwV59dgeaOIycj\nB6p61DlB7VoEooQiaUwF66HgKGv7N6ZRWawbM/l5MS+ecPHYSYbr44yP79gx\n6oGwx3A6cC6zM/Tf838mIqX4KyDu7nHeumdA8Do5hK7HmxRkuOJG+i14ZxFS\niqDlU70GTIuk3e3pzGIUyjusPqx6HC8k/BSeIZNowiVwmrxR7G8AW//hf+fV\n1POe\r\n=keQ0\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"70b783b609bb4b43529021860b22c03f34a45024","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.54","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.50","@balena/jellyfish-logger":"0.0.203","@balena/jellyfish-metrics":"0.1.16","@balena/jellyfish-environment":"^2.3.23"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.6","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.78","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.3.0-patch-transaction-70b783b609bb4b43529021860b22c03f34a45024_1610930644557_0.6796425161439563","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.0.0-patch-transaction-e51c9797b6178eaa94a8f1e18dd47bd8ed080c79":{"name":"@balena/jellyfish-core","version":"1.0.0-patch-transaction-e51c9797b6178eaa94a8f1e18dd47bd8ed080c79","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.0.0-patch-transaction-e51c9797b6178eaa94a8f1e18dd47bd8ed080c79","maintainers":[{"name":"toochevere","email":"pj@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d2ed38104e658c665bd16a3897d7ec4623201734","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.0.0-patch-transaction-e51c9797b6178eaa94a8f1e18dd47bd8ed080c79.tgz","fileCount":95,"integrity":"sha512-XgkfFvW2y+iiFOLOBZQ/YVpVJX2jINAF8UGHGyQ1y48EKebv9V2FcWX+IHHvr4ZnqioKeKWE3kTDIx0unH0Qjw==","signatures":[{"sig":"MEQCIGOiM4xkZ8gZm/j5AMJidDSlAaZv0vmzckrZZKCx4UDfAiAKkHhsWceaI341JByUc7jmSsZQRGABGgOdpYl/oct46w==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":587081,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgBQmICRA9TVsSAnZWagAAvB8P/i345b4X6G3j1YdU/pjl\nPp+vQ1O9wqMj0L5jOOiPfwodK1ka63iTF/EreKzLDd1lU2hvo6/RmUvUA8tu\nJOhKIr9eM4ki6+RADyrpdvDiH14VyMQItep1eQ/7M3Qe9I8SBHqRnHIml549\nZUpOgUImcEu2mP0oOdNcy4UuYWJwifqPEp6WXdMdKDOHakpx03AlfI8esesZ\nfUApwmqBVxCsGbDgVy6e8gztbxvaXNMZrea9HjRlwAM3yp/RmhL8aaheyJu9\n34okoNnjhyFDvcCLVntiBw6p04vXuWN9YvcXUZpE7+dpkjUhhrj528APl6V8\nEh2LyjBh8YuMm3n0JYLjdq6A622ugSYJwUv6Qh4QgCAxLz7xa/zzkQ1UfVaO\nhIpJBj+Qj37w3mA3atJ2IFc6LCRzqij/ETO8+iGlZI+r4nWBjNZP7sHKCx3h\naoRPI0SxU9lIhZhy7Qauz717gEWViIAvlUSshM2lCn0soFIcL/pLkBV6D1Xu\nGxvj3mKdwbCLNPfE0Ec8sIppb76OyCByFsofLOeiCXZrkQWYZWCihXeWilX5\n5yIN1osIPuOeq9Oq23Ac1RTzDB7Ng04pFsL4eOdBDEG/Q6EmkfKMsDUo9YeF\nIioqrQ1Ba//7c3u9Bw/QGF6dMPULvkpLmDXMFjPVl+pvwRhIqYdeA0/QQVu9\nA1jY\r\n=8R94\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e51c9797b6178eaa94a8f1e18dd47bd8ed080c79","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.54","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.50","@balena/jellyfish-logger":"0.0.203","@balena/jellyfish-metrics":"0.1.16","@balena/jellyfish-environment":"^2.3.23"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.6","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.78","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.0.0-patch-transaction-e51c9797b6178eaa94a8f1e18dd47bd8ed080c79_1610942855520_0.28115849141842264","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.94-renovate-non-major-d590a3ce1da5a0f244faf3eaec6f15d419922ac3":{"name":"@balena/jellyfish-core","version":"0.2.94-renovate-non-major-d590a3ce1da5a0f244faf3eaec6f15d419922ac3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.94-renovate-non-major-d590a3ce1da5a0f244faf3eaec6f15d419922ac3","maintainers":[{"name":"toochevere","email":"pj@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"31f015fd0cf63692bb88d9372f1f2e17ecd1d9e1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.94-renovate-non-major-d590a3ce1da5a0f244faf3eaec6f15d419922ac3.tgz","fileCount":95,"integrity":"sha512-ZQyORKBbOEKcCy4qEk+a+ofDpFM3QcyuJwN5RyWqMD68tvBEsZubVa/V4yAr5DESqiNIa3T2z9E2pxXEG4SARQ==","signatures":[{"sig":"MEQCIHelbk9XsGJp6OX8tSWbvUaLLowa/Zq5XFBqmDtEMXvPAiAthc6uZ9EnTpSUkdn2RvWaXWjyQ0Bgc3RUNiznjSe+hQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":585244,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgBUg8CRA9TVsSAnZWagAA2JQP/RE7Pbc3cR3Prxn8W9yq\ngizDU3fcORGfGuqYjdM0YJeYhYMvGuNqItBATMiv5bU8z82efAWxEARuHO3i\ngZJQBPfFMrQ1wB740HVfA3S5hcf/H5el9ClUiAs2wPqA1DbHFq2a3RRaPhO3\nL4L6RJmB/t0mG173SlEjm+20of2uu8SPisRqW/1UEq3ESRhNLWpXi5W4HLPd\neJYHKCgreaUwTF5CmL3mhFuIMFeS3a9/otyeu5zhTBBaik7n4Zfayt30mtfL\nEWBxWd4lyrhuHui/FUwGtTOsqzbGZ/uPPusyoYYATeqK7oE/3b255yzdTLWA\nD1GvzzuRIreLRvGPvGh8oRHYU6+XpiIof6FtTbApmL9YcB9u6t/nkxqq/lrP\ng/Mj8g6EU3N8142r5HyUTLlgkp+Z+3IbyyXvvAm/4rtqOlLqApFM5Deojv99\nDtxlENl/VNOBsdgM9cvmnKY/gOY0b3TrDgqWMEBCjp6GOThKKuNtBADTPZOM\nJAhx4Vk1mhm7QoUAAn0gH8OQEY/ZyUB2Jfc6g7k6oI6INUQjof9h5MnSrRHz\nil8tvdJ1elMATbDcUieErRpMYFDLczW977DGb1rbmEFYVlb3Cwd5v6MKCDyN\nX9PRDhC2zoXgPVVHxU3vvMH5AsdA7fCWoUDtrcD+WoVZnpPZRybaW01wIWcE\nFLAp\r\n=Ym+n\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d590a3ce1da5a0f244faf3eaec6f15d419922ac3","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.203","@balena/jellyfish-metrics":"0.1.16","@balena/jellyfish-environment":"^2.3.23"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.7","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.79","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.94-renovate-non-major-d590a3ce1da5a0f244faf3eaec6f15d419922ac3_1610958907746_0.7457407273679966","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.94-renovate-non-major-ccd269dfffa29c5ad900b7190ce3ceec1546c948":{"name":"@balena/jellyfish-core","version":"0.2.94-renovate-non-major-ccd269dfffa29c5ad900b7190ce3ceec1546c948","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.94-renovate-non-major-ccd269dfffa29c5ad900b7190ce3ceec1546c948","maintainers":[{"name":"toochevere","email":"pj@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8a5559bf536f41fcb60cca1c776a230d327dc3ff","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.94-renovate-non-major-ccd269dfffa29c5ad900b7190ce3ceec1546c948.tgz","fileCount":95,"integrity":"sha512-cscbZcIoJJfw04SmUHlJzfd9kMbdvvDTOAqRRDOmHihrRh9LTNYP7uQ46ZXcszJ+lRNYS5ddntmIR8UKPkjQbQ==","signatures":[{"sig":"MEUCICaqUy0vCATXn8mKFhKGPRcB7KxA72lK06ME9Qt64jCpAiEA9Pn1UpVDJfEyFf4lpO+dLL52W4I2pEoB571sw51kcQ8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":585285,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgBVxFCRA9TVsSAnZWagAA1VIP/0F83S9p6rsJaozbaOvP\npA+plAPjLA0KPzQf1geqem44LHmZVR+RdwOZuT2gJ4631BD+FgbZo9ef+uBu\nAOCndt5N+7CAO9d8tk3Og5VZ0ml3wIKwUy5Ztjepd4Mc8gldejqXEIaSQvm/\nr7cRVDBG16hOco4CF8MTCCFNOJ1zltIqi10gvSi2lyXFqzWW8AeNsg7Av4fo\necqHt4gCjdyVqbqnP+wSxMXMpbQcayhEDY9vbJkudrOaCH0BktPLciht1oAF\ntDb8KTKnVh9KcYaf+ddgAoMjkF143Xb3t8gw8Uyd8jgP5iRZQGYZJxzBLyhP\nfE/2WMqelsumydMc0ggnbV/KPQ93ZwvKuMvotmvbrw7vqpKFj1x5V4jsmjno\ncKT/yRQky5ZI8Z8Fio71y0WPzBN4qOAaCJI83eGajQrdjuS70pw31MrdE5yO\nelJw9hBrSBz38N0PIKl4IfC+bklAjOWLxCNE7De0bubpkAiYxFWiH8Uor+Kl\n5Zf/MM3SbWCcFbGUhOlMyY0VM8HuHpFp5GPYUu7q5d9tMEu4hbBuODyLEZ8O\nGBMat1JFbe/ZHIVhaZzZwnDgqKK9e3orlGacAg3U7SsitrwherdaMeS9Ed41\nmfZp6nnSYaurA+ztfb8/McUnVlgqbd8hZk2/igv7ig/O7/9ouiRjQ+gn3MbV\nnMpX\r\n=Bh5j\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ccd269dfffa29c5ad900b7190ce3ceec1546c948","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.204","@balena/jellyfish-metrics":"0.1.17","@balena/jellyfish-environment":"^2.3.24"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.7","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.79","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.94-renovate-non-major-ccd269dfffa29c5ad900b7190ce3ceec1546c948_1610964036978_0.8209211582932998","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.94":{"name":"@balena/jellyfish-core","version":"0.2.94","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.94","maintainers":[{"name":"toochevere","email":"pj@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"7668fd90536b4d9b6e9a3a8b4ad493b25fd9a3dc","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.94.tgz","fileCount":95,"integrity":"sha512-1zfrSvM6WkIcuMjMpIn/3YdX6ltSV/24rRqPd3kuWRSN/Gem9LTZCyN3oN0QwQQY9vAQ8ggne+PIXPVPR65rNw==","signatures":[{"sig":"MEUCIQCHFT7S8+g3QwIvvqTSS7mGUdWo1VGMepGeN1g5WoB+eQIgOh9BCV+QxdkadPz5bDW+nfh4ZcWrJwi83vD5Arsdm4M=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":585225,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgBWjNCRA9TVsSAnZWagAAVKwP+wSCbzOIGggzlvlPbedq\n9AvjkJn4NlAktcIBBfqjJbaSKOqnvEmeh3A8jQBGZGybT3+lo66G2LAJsZa9\n5j67BNUKfMXyTyc9kb780Dhg3JTQqYAQrN3U+ofY5baAPH+2tC6wWTcOPIJf\nk0HCthR4LGnGIFXYB6vcqsVfX0WW3FXxSdKU5D4iIUHpYXAbtfpnuzhAQHGR\ncIzzXV/0xS+FMnhbRTXSAGxxiweKpgDmt06sfp6lVkQ3HJCUyNgHET1/xkx1\nv/zitSkiTIVNEXwLQj331g39785tZ6ztc8Ny7LFWnVaQipbJRsa0oeOAhjGl\n1zOgJOJt1Nhj2+FwHvodI3MJr6pT+vR4xkoaawTUANta7r5fyb3mFZdEWTZp\nee1127yA8AjmzMvZvtnQzUvLUDTeefb3ltI/6mVov/0WM/VM2928LRgXlFmk\nJYM8BYsqichNpIIR3uorJ1KDe4VEVBBRpN9NIQy2Ma0yEZoznq4OQLlmltkQ\nca4b62PAzJHoAjEePgSoCty543Yl/ZUVpV0EdY4OwWitA8OUcIvjo/bIlXrI\nXkPg3teTqSVI+0WStb7ex7umcUf7jxwBnbu5JFfG6JWl+7s0ytg9v9yoJhLm\n+cW6UWLQey7KFVBzuLql+j4MAC2jOuy1/nLcZIy7FKp4nqi6n5az+M0eBvL5\nhlxi\r\n=wqeB\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f3af71cbfafe417f453e74c256cca2c355578066","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.204","@balena/jellyfish-metrics":"0.1.17","@balena/jellyfish-environment":"^2.3.24"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.7","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.79","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.94_1610967245132_0.05218911314750607","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.95-renovate-non-major-7a7aafffbecde58d562031a9af8db279b8897715":{"name":"@balena/jellyfish-core","version":"0.2.95-renovate-non-major-7a7aafffbecde58d562031a9af8db279b8897715","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.95-renovate-non-major-7a7aafffbecde58d562031a9af8db279b8897715","maintainers":[{"name":"toochevere","email":"pj@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0a0414c7961b648ead9941519e80bbf8fc5bc19e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.95-renovate-non-major-7a7aafffbecde58d562031a9af8db279b8897715.tgz","fileCount":95,"integrity":"sha512-b1E0HxpP25b5uEQnZ7xg2RFtRCzClxi6bqLWgDXURFYQPRxossvT3pVy5LuPGZXYgUgDkZDbFHdofrw2GOj22A==","signatures":[{"sig":"MEQCICfsdfxOxcc13Lj4A7htqVkQY5Dog+SAAKc6JWLEQo6+AiBkFpcp8ehumpLzmHyPzjjy+C8rhm383S1JjRRnMLmEhw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":585389,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgBWoKCRA9TVsSAnZWagAAnDYP/1duCcSbPsHU9O0A1bDM\nm2dX9TEhFmsSZ6/07S+ndaCGnW/6fuOK70lBdx6LEiXY2XnSFIiXDs47VlP5\ntctBz0Yf2PKROVkLWMdNiZS68n+HfEuoLfTqk9btp9n8+BIFhHnb/tCk8IuK\nkZxkgGK6z0QvUVPFfTf0NGX5uI9onIgYQWgb+Muja9wkadx6VaB0zq2anAam\n08OzEgUyzCv5jrZH9jorT7IkC6dFD4ZVfG119H4GfWhgHLWNcc2DDfiUK6F0\neJE042uFqWCu6m5ZGvyJ+Z5vSqlURHy+Hz2X1m3rubz2RcrnfS5PAA53ksm8\nqQxq2sg0Zn5x3y5imXKYYC3j/yXuR80Cqe///3spFHCGvr2HJtOaibmtmM5l\nCiP+ihSQWILL+AsZhzkkQvEdcj/LA6fKel1p909lKfs7tf+8FkzkpFro0ain\nuQKJTxLhArBpLk9HyLlCk2pvyA0jZdbnSLMabIl/PADrRwVQbetkDLby5AAS\nG1hqV5QX16ODf40qGFNgTZztv+3QbINyogN8hzMhkANDaLhQ31cWKk3vgdDo\n31fyoGqBxIzICjFSZmRWEeqABaUH3Pm5Oii5fAZ/0zIP6BPI+fVLB6t3hjrO\nZIyYzPTclLxamul0cU/aaCdB44ZmrY/3QNeo3iBehqIyOci0oMg3gaRoY4i1\nuQtn\r\n=M6Wk\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7a7aafffbecde58d562031a9af8db279b8897715","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.204","@balena/jellyfish-metrics":"0.1.18","@balena/jellyfish-environment":"^2.3.24"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.7","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.79","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.95-renovate-non-major-7a7aafffbecde58d562031a9af8db279b8897715_1610967561722_0.04834019371508402","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"0.2.95":{"name":"@balena/jellyfish-core","version":"0.2.95","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@0.2.95","maintainers":[{"name":"toochevere","email":"pj@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e2567563c917e4a0efa2a21c04b7a95ce98678df","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-0.2.95.tgz","fileCount":95,"integrity":"sha512-EIw636/8aKvN8/fciAMeymuFzGd+SQsvoIlGh3hxJBKnlR0003kQzeqqfuae/0dPw/x1Sv/NrGBkzkjN5JY1uw==","signatures":[{"sig":"MEQCIDULDVIwTON+tGWnx4YWCUXTY8nd4S3uVWHfcKcY2fAVAiAH+heRxRwIyuZFYtRjmI5Mk2qM2tu7fKHYvWAtjs1hJQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":585329,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgBWtDCRA9TVsSAnZWagAADeAP/2RWcNK8u9OrPT8hAVAV\nwndAjAwDZqp9sCFAoBAJG2X2s3UCFsPWN58UY9o5BjSd6LkqqjBCrDwUkYqk\nXtzhavGmjtuWD/HifBR2mAu/ZNYjn7sAecrdPfL/NkM8agdzQxBbr7RGnP8J\n2MMFGC6XhQ1WduOb3hfN6eQsTMqEQMQZerpxFAAvf5IBRoyBsxs4r723uV/e\nTqlgIhFljriZGlCo5+R1kF8IYeWlor7YqFFUSL9JBU+ndzVgtVrM7RNAmjs3\nY+PwKj5F9+PmP6YZnsRpHdRFWwQ0Znt/KDn92bcjFyHjs5ko20ykLyJsbPv7\ncdnMwllWIiBh+afYAZjNuJCECRlUECMkn6mPkFPYcM6pMIaVpO7PwhxW/ssk\nwv73s7lZhY1nPK1rrX3weW3hxHFh0tet3s9MS46EIKAbQhr3TFBs4b9vI4Sm\nz/eXdah1gdvxQ4XQEdjwx3UhLbfaCnRZfFP1nq+oUQlN6c8fsQUT6kBWnWto\nWJo1iORszr71IUQoMm/t2hl/yTs8TaT24iSLDWMHSwsbVtvnvCJOV9qrl5Hg\nLfAXKQSDH73YgASR5nXJ6Gd/yhbWnopbnwVof677jD7NMDVhC1WmJfs9C1wU\n0YC6WzDejnhjDLI3wiRQXJJzvJze0WptPpvJELk+RV4w/z4jBacr7KQoOAgK\nObwc\r\n=tNmi\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e45ba4d1974dd18a427c60f6f9143e42fa9ce8e0","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.204","@balena/jellyfish-metrics":"0.1.18","@balena/jellyfish-environment":"^2.3.24"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.7","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.79","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_0.2.95_1610967874907_0.6733468359113781","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.0.0-patch-transaction-f49b1365303af4d3839da8a272ac3cc11d9d126d":{"name":"@balena/jellyfish-core","version":"1.0.0-patch-transaction-f49b1365303af4d3839da8a272ac3cc11d9d126d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.0.0-patch-transaction-f49b1365303af4d3839da8a272ac3cc11d9d126d","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"763eb660aca810d8ec69376751571eefd1529b7c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.0.0-patch-transaction-f49b1365303af4d3839da8a272ac3cc11d9d126d.tgz","fileCount":95,"integrity":"sha512-YmkkZHRMhGYNB8mzb4UtSy7fcP6c1fl6L3HB6JNWsaBkA5n22B8moA/gttYQm9LFzjKgYI9ko3tj71RI+hAE5A==","signatures":[{"sig":"MEQCICoI7rZ5clnoRF4viphiwOT/Yrv2uOPwfD4rvHKoIj6fAiBqlXkNudfJjtJraccGeESu3cPsom6hw+4DwDxIEZczow==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":587264,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgBkvlCRA9TVsSAnZWagAAa1wP/RjT6xaw9m9ecAJb08kU\neBwSHb/1NjI7LdtH/CyHT+w74CpKPZMKOm9afyjjRP8KCgqKJHiK9ltAd0qX\nQnYo/rF0WXng01F262LYu9raG68GbanMf5rOx/skUIOmfe5JIdL5D5Px/Gbd\nYhd1Zb/Or7SPB6wsS9cql4+yiZdMzYqKEYi1RZuIueKRzrueKWBTkxchd0va\nXefnXTk/IE5jUNAmfDncKpPammRYmYJ/vFqeqG/yVbiN/nrto1XBc7be7mp+\nEKsPAPXkCi6f6OmVfcVtMocaH+R4xctptapAmoIivbv6MP7sUZTwFx1RN2If\nEy7qiuHI+uw1elOYaIWGeoyfNWMwwv9mx9cQ5KS+0nf/mcuLKPinTpztbxWT\nRVs2r1gQvgo/xA9o4TbP3iU3D9wc5b06ff8XUV35SY2YoW7q5LvGCCvCSp3+\nVbNmcJW3ErZflVT/UgtsdOngVYps57Lv+urtk7j+rcQNV4JX0XGtuL8y3z8X\nfkwQ/yrOpOJz19nOYAQBsV8vcZw/bX5fFveQeDqFVreiMEkztTvYKkdS5ufW\nfVNSfmsCUdfPzv+N+4IGpx9arNfkKa1EGiTJwZf7SiHCmOXAP3R0xFO3vsni\ni02V6ckQ6Sjosk17xPkDwdxYccDYiryBjWTNZILj8MfkSB+NatWalcaZYZIj\n/U8L\r\n=id+0\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f49b1365303af4d3839da8a272ac3cc11d9d126d","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.204","@balena/jellyfish-metrics":"0.1.18","@balena/jellyfish-environment":"^2.3.24"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.7","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.79","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.0.0-patch-transaction-f49b1365303af4d3839da8a272ac3cc11d9d126d_1611025380712_0.45210817509722445","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.0.0-patch-transaction-a30a918214339dd86423eece51762d6196af5b17":{"name":"@balena/jellyfish-core","version":"1.0.0-patch-transaction-a30a918214339dd86423eece51762d6196af5b17","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.0.0-patch-transaction-a30a918214339dd86423eece51762d6196af5b17","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"af3eaf6ef24cc0bf494d181624bd19bd5b2a2716","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.0.0-patch-transaction-a30a918214339dd86423eece51762d6196af5b17.tgz","fileCount":95,"integrity":"sha512-8waJADOUyv12qoD557cea0Fg55ZMU/7j1KQoXGLhGIBCG2C5+uQfl5z3ruprMpB3Bi61GVC7JGcYD1I+TiDNjQ==","signatures":[{"sig":"MEQCIBrr2nXU7rDF9xyB/4lr/C8KzIEoBopF8Ela4zmTmDpkAiB4td7arb8O9xFPrNyA1AtCuZp/yUkBzobvuyp0DrTqRQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":587286,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgBlrUCRA9TVsSAnZWagAAGNIQAJaJ6j89a3aiaci/JZ2W\nRMdlcA1UjTBcBJUQDtFUzIvMvI7/39XZwxe6RFCGCNHhTjjfHmpaOFCHn0AG\nr0V0fFcqMx13VOGcTpWeNr+7EeapTJ6489Uv6zrgtQkB9ZwHwwvaAoHg22pB\nAAuo+U9+SD/+0mwFrlpGVB9g3fFjrHwwXToUHSD5amvu18RJVBsLOS+SZLjo\n3FNo/qBAxeMJ+/TzPaKU4TpmwF6Y5SZjuBAs7PUm0wF5JOdwYV3O8LTU75Jq\noLHZPn2opuC7JKfHnpvoNs3JCPV5X+OoT9eCkrMKf9vd4vVjoFm72nAqWbEL\nyOA0+2wd9IbzMVeWgcFXUmy95/sTitf8//EHpQs86WumiO9c4Hmgyuox+KLf\nVafBYElJBGVJpt+SM90zldjw0VkKoJMAz2zu9HctidyfCY61XoD6BcDz6p2v\n+0FtOOBuXC93KOVATga4G03ly1OPcPj7yDyH+0pzanxqOvmMmbu9aPClzovY\nR1Emcxjk+hip5usJ4SMS6jTVlzR9uFydFboUYkp47PV9okJcD4ZBoUVj8Qvs\ni42rUoH7qgto05ujq18GWXMUthiisIIpg8UCti9xhcUdQ6IcJgExNIotvbh1\nXP7qb2dC4tEJj6apS7ZaO9kXixDC71IfpR97MSqrr2F+dd+A1SEr5Oxa4H5j\norqY\r\n=pkWr\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a30a918214339dd86423eece51762d6196af5b17","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.204","@balena/jellyfish-metrics":"0.1.18","@balena/jellyfish-environment":"^2.3.24"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.7","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.79","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.0.0-patch-transaction-a30a918214339dd86423eece51762d6196af5b17_1611029203861_0.826738058801435","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.0.0":{"name":"@balena/jellyfish-core","version":"1.0.0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.0.0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8952dd581438e3a783b1583ba70dafc63cc3d1c6","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.0.0.tgz","fileCount":95,"integrity":"sha512-brOaioJFkk7yV/H+vpJfFNO0WDfhARj0PtdCsUzLPd3cDly75yJzZoeP3hnOLKAAwDYMztCBGyC1IDw2WVoHPg==","signatures":[{"sig":"MEUCIQCjViZf1qXd13WuYyX7pmWlEEO2KVXGpXDVZQnKFGwU3gIgJwafO9a6eQMUN/FbnOxUSEDhXM9hLN9ueE5+op00Vlg=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":587227,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgBlwbCRA9TVsSAnZWagAAEUsQAJg/as4G0DG1VLJ5SddG\nBGv2TmfdZouNaUu4hdkAO9OXkQrNQmMgHNMGobmiUIAQN3RNct5/PeivLtM4\nF2bwkzxqTU94xg67DwvfqexgROZ52q1+fqVeg4XxA8ObfJGdCJzS1wou0k3G\n9o/x6hQgQT6DT4np0dMZm79OTwARmxE5QHixdkSYb98pLr3V1THoc39KJ58z\nXYuiFuWhDwkAK13ewIN1I4CG3Apwebd0z4SEYs78VdOpA+H4UBI3Gx9ZJUYG\nOz1UMAF4iEEidsReMS50VjmAuQrFNAsKlszS7pLRL1+OJm2ZbPAtL6eHLZJn\nOlGoDshVxlVTGjiwd0yfkX6cf30LrmYVjWgT5paVuTxi130M9COAYfMQp5s2\nxV9P5Z5D3cpNXfpfYdxvY9MtY1XjwsBLP+M66q0p0xq1p8xhg/4/cwlHOu+7\ndg1PRLiSLAsGi6r8n8WV3j2J4lLoembvYNeGnIAg1ooTwPpQS6cuHNLAqJ4z\nOE9zGSZaUY7FAX77ndj6BgCtxCaWGbDnXSAvpunFbRHW6FAU3Q8zlLAWkRxS\nLe8EwG5lsDOSI4AhMqf2BOiNKAr+fq45j0obww7r+eAn2y0YsudSleA2O8sA\n2hm/yU9ch5alREsEQcsZimRiNfUGgdl/XJEksyTq6E/h4WFJPNr0D6XlGP+o\nQG8z\r\n=BDvY\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a486593bf9fbc296f6b25f042537baab552869dd","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.204","@balena/jellyfish-metrics":"0.1.18","@balena/jellyfish-environment":"^2.3.24"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.7","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.79","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.0.0_1611029531132_0.015162980016957928","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.0.1-renovate-non-major-6fb4836a72b0b818fdea9287c3368677a43fc1bc":{"name":"@balena/jellyfish-core","version":"1.0.1-renovate-non-major-6fb4836a72b0b818fdea9287c3368677a43fc1bc","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.0.1-renovate-non-major-6fb4836a72b0b818fdea9287c3368677a43fc1bc","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"639cd2ad44c6eb47bbb94893d38e7c38e23eaa08","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.0.1-renovate-non-major-6fb4836a72b0b818fdea9287c3368677a43fc1bc.tgz","fileCount":95,"integrity":"sha512-V9PCGz+rJU7SX/x96hwK3d1EtsTePYb5S6XavoVMOxPOAI7w4elZPIm8WBTZdhNfg8TlZxt4MNh5Bpy/YfTWHw==","signatures":[{"sig":"MEUCIQCGpIvDZpNSgy8Z3AKjQ1lk5/9YIml+pXrBndockpoZdAIgWRxgVem6jfex8Y3XetorlOTPKDtiIXWwWwS/NxyOMBY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":587352,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgB7kiCRA9TVsSAnZWagAAeAcQAI1ZBYyRnH6HS6EbKEYE\nN30x/bVLbMR+SVvtjc0x86klI/2PtRSbZ2x6gIlM/w1OUUFbWG0UvnLKCv8O\nPGAwE4PFmkaR8cd0t6kVKvDCDtc3L+6t6guVD1dBRfxpFaiZBG14vLDa9/wt\nvSXZRxmoNJ4Xo+nDhkzpZVz8xxnfI/fk8RdzJ2TxBG+q0w5NgBLWqeUYKYVW\ngNiigDrK1yWeAzsjK/ALIJEHbkhjPPsTYyhphG22YQ2e+9wwrmNpfqSmMfQG\nBxrH+umUQ7ZholpQrw0xtsC4NAi91LQJ762eFI4jctdJOsBa1TL/l/3cBgvt\ntEtPyARRbRUEuM0DBuOfKfUdRRZCKJUPRoLy7pB1j7crUkeo2DqJXiiiiPp3\nfMiUB40Q5NyT4yT3H2XpYYwCR3n2a/QJNFY1hPgt3rdLaBhl1LpZeGT4u9y8\n2KuUU3Eb/QftanglZ7IYeFTONjPw53Ieb8hFWsv0wHSUF+WAnMbFSqZjg9qN\nCRxH4r76+Wd4yv3ITZrnz8TUIt/nU9jEg2i3D6XwIeAgIVBiKpgwe7nyrm2K\nF+AaLbo1rb9UR+ZKvzxBHyjIajkd4rfbz3+jRYuUdY+VRvYxji+Tk1m6P4U/\nyyo/J4NhmJYevyR2VIFuM3v8sKjcUjSlK5nC6uXX+iC1NzxFjdoKOLykkyon\ntG8i\r\n=BTkB\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6fb4836a72b0b818fdea9287c3368677a43fc1bc","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.205","@balena/jellyfish-metrics":"0.1.19","@balena/jellyfish-environment":"^2.3.24"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.7","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.79","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.0.1-renovate-non-major-6fb4836a72b0b818fdea9287c3368677a43fc1bc_1611118882410_0.0729513834132054","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.0.1-renovate-non-major-a5e0d61546f7648fce2112bd48cafe492cd64ac3":{"name":"@balena/jellyfish-core","version":"1.0.1-renovate-non-major-a5e0d61546f7648fce2112bd48cafe492cd64ac3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.0.1-renovate-non-major-a5e0d61546f7648fce2112bd48cafe492cd64ac3","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"986ef8a5962ebfe8ba405c4221a8dd401c86b26e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.0.1-renovate-non-major-a5e0d61546f7648fce2112bd48cafe492cd64ac3.tgz","fileCount":95,"integrity":"sha512-UsZKZYu7OOECU/ZPoGffWieDX8sA6Uf1zagkO8i4/jJN3rD6GSNRiiHVe8+FccAWN8v5o2jxefELhNJY+AnG5w==","signatures":[{"sig":"MEQCIELUZhRSgYedcWWKwVQZgqSznbI+lv66kamI4cuImrDOAiB5LLJUJMunbhmgQG0+/xjsWpLuqKwNvrO4AWp4g50A7g==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":587352,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgB9NkCRA9TVsSAnZWagAA/CkQAJoA54Q8twpShjZZBGVT\n8dfR55d+4CHHcAtgfvnyOpPYcq79/pGArmnGPm+K0/lfuqZrfDNUaXbKVwIm\nvR7LmFLY7ijelNyBTuEFQvKhQWmwB871+4UwjI/EaKTyG8PjCb2Dg9hxSHP8\ntaYPrSRuxedq6g5U4hwUC4fn/hFGvfNALgreHU3W04fyUvOoWnlFBVkwE7xc\nuSeqA8ltQZVyXXkjkHmLHAXRsIVhyfR7Gn3/1FxY5/D+vGTYpb2+hqVKy8Ts\nyl0C+imKPfHqpH6LKWhY3vkfofWq2Vj/TxJ0YK5x64DuPdr9Nasc6D1pCf5n\n0YBqwuBZS0sfo0a5KvyKsGOi7R1eMNQ7GFw3gtyXpOHUj3jndZtNI/E+p6Zc\nDTtmQ0Oqyl5Zd3i4URpMX6q+SkMPvImM/N7g7ishvEiV5GD8R85Fod9nFaGE\nV69ipKfbw7bZAVn1TWMmEImaXfJD7CyzZt25NUPqs1PL9yTD4cDhHWPF1cTr\nwaBz4858QnaKRXNuHozJA8d9wdOppvLSiJoirxqv7S3VinpJiaLw/TlN/YhP\nzhSTNrtekpceH46QiTNGLCvQP8IAaB7S97RS+fqRdnQfregVgXQVM2cEEn+U\nh0SR9J5ncTWrjnhw1yuGSvvUJmWkGxDqqnj0JP+Y6fkVxLkVWPZllrGRKXAh\nwAJk\r\n=z+9j\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a5e0d61546f7648fce2112bd48cafe492cd64ac3","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.206","@balena/jellyfish-metrics":"0.1.20","@balena/jellyfish-environment":"^2.3.24"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.80","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.0.1-renovate-non-major-a5e0d61546f7648fce2112bd48cafe492cd64ac3_1611125603866_0.15274533332749596","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.0.1":{"name":"@balena/jellyfish-core","version":"1.0.1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.0.1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"92960f3176326156774feb48a2991d736d2e5604","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.0.1.tgz","fileCount":95,"integrity":"sha512-MB/dFul+cJoc3l4XQR7OxFOURLmlTFyjPy6gdeEI3MNd/yoigEwLIDSPeAZGpWFNaHY2VD+yF6mziq2JKL+DPQ==","signatures":[{"sig":"MEUCIDrYfQrlyBXVX0biA58Nv8VtwPYpLgcQNloz3Pr75Bg6AiEA9L62hPYq/uCXxDSyuTJiNOutGJ7VXlX8uvGIZ66Osm4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":587292,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgB9TNCRA9TVsSAnZWagAAB1oP/A6XkQwRIS7SEvJBW2/3\ncs/2kW3A9PTz96kZt140DP+sD3fQPnYLCCRHAXFH4HA8hjBr1eyg9ASI+Yyj\nCA+ArHTjr6lRcCNI5vxUFwRy2HsFFubWZRmB12U9NFZN8c03A31yakFrTiIz\nAzqU5yIpSYkgfilV6rLBec567aC77REkr4zlAWvqHhuxR7VA3VXmTnJU5Axi\nXiNSU5wfu7+s8NZuMoannuee6BYo9GzETqQpZ0Y8/kBLMfoih4ZhYv6OX4Nt\nbs1/q8MpsTsWnTl2YnGHxpfKktz5tw5UNn8scJPwPStEGPeX1k2WZfVvIL3l\ndfPWc6E+JXtqt5oOCCzEEHKmZ7BlUUixA250x66qktVAD/nqetxTUJijDcjQ\nVSYIuj1dL7g13hRi85y1xVdCgWQWEsNs7OqVMvIgG+50Nw7KcwcDq0FqnJ42\nytVlHkbeQiKPDnUeDmPzabFSmnADPjCNuUcnZ+5AAbs3Xhd3ClySyoUmULbG\nts1QYJ+RNjqzGMcEosLgfRYxUiyR0van3rNrjaNfuIAUqnQIWseK7bgXEZVR\nH9wODq4TN/6iVEDN0tckzJtFmyurVuJlHtYpUX+vib2DLYcrri1kGMd+mtW5\nzZ9zUaXuMuozIIxS1LIzIuUwcgAYSzxTQ8L42RGERrsxo+RPwX5UnaxaIBsF\n0Km0\r\n=Z6Ri\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4471e6ef1b6e7931629b1aa2cffc056635a3d6a9","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.206","@balena/jellyfish-metrics":"0.1.20","@balena/jellyfish-environment":"^2.3.24"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.80","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.0.1_1611125964713_0.8615755334607769","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.0.2-renovate-non-major-08413e0211493fc0fe17242c990b0adfe0822edc":{"name":"@balena/jellyfish-core","version":"1.0.2-renovate-non-major-08413e0211493fc0fe17242c990b0adfe0822edc","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.0.2-renovate-non-major-08413e0211493fc0fe17242c990b0adfe0822edc","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"fa1f45fb39bfab3d2d54495bf61524b3a98017ee","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.0.2-renovate-non-major-08413e0211493fc0fe17242c990b0adfe0822edc.tgz","fileCount":95,"integrity":"sha512-kVIvbABP36n8s3b0lbuYbGH/I35IOOMWoQg9J3MkqUv/pFIp48VyAMT2IpNJwknDKQU0cq/rT39bknNqw8jX6g==","signatures":[{"sig":"MEQCIF1NBZ86Ct3GTzbB96XmMaiC/i3mYQFV8vNiXgyg26fmAiB7MLFUi2D5f0y6G3rg1e0Y5X5BSBwtZ04NkX5enpb2SA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":587459,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgB9W0CRA9TVsSAnZWagAA89EP/1ZUjUnBRtsEwIncRUVt\nWVWa9FGMlHi7miPBVzJi2FTlcmFw8CVHjAnCEcpmvePT31C+bUJ+2HvaJM04\nTDtsvspyYaF05ZDNdBEuMI/S+TDfAuR9rQtQlbqMeQ4bsrfnSflG6S34yKEd\n3LglDFc5hj3qrw2umTs3wWXjs6nToHmgfiIG0iWgjKZBJHWs1S5QeJB3Abe8\nyjNMOWZasOG5z+2821J/HWV1sNqV13c7/Uas+nFQb8+/jrX+56ZKRpchVIS8\nSq2J6E3Hf+4nPtZCzUrv+I98VIfYCZBNrrOuvA97kLLdIPpjrY7HWV93FcC3\nA/GZb49jQu0GWHdfMmn1Cc9xaS7kyUtfnuBuSpf+eYjH+N/MOAw7b5NUs/Dq\nsk+cNJeoBcr0o5QjfxAhcCy7cGbRbWs81U1aIzRc48GJiBiEUc7AIV5T3AlH\n1SwXGifLrhOafHyU1aUu0dWoucrfBCL6adqmeh6vNe1y1RI/lJegZJ9c9uIX\nav9gt9fhvrJHD4ofSeY8vNIW//olhlm93fcjB0L789NBVSo7Afte0yd+93QV\nVCAXMGiqAhAObjsHh0rFMvnAnURpGQehPxJpDbBKb/fcFZ/Yfvx8gNTVK1SL\nIhVbBKSPUlPNiHkNVTS9lt/DqWJ6qmAOmH9bzWorr5A8KqXOWYn6Ryp9tyGQ\nMN1m\r\n=D2n/\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"08413e0211493fc0fe17242c990b0adfe0822edc","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.206","@balena/jellyfish-metrics":"0.1.20","@balena/jellyfish-environment":"^2.3.25"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.80","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.0.2-renovate-non-major-08413e0211493fc0fe17242c990b0adfe0822edc_1611126196072_0.42922787835183995","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.0.2":{"name":"@balena/jellyfish-core","version":"1.0.2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.0.2","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"bb926e9df44e13296456ca9fedf01c74e061290d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.0.2.tgz","fileCount":95,"integrity":"sha512-aNZmidBnvtYmGg0b8HLyWiAm0Lrys/VpqTdP3pUV9yG6ycq74fCDhzydPYR3FV06ZO/YEV6bY3W8Qppo8WBEgw==","signatures":[{"sig":"MEUCICy1uwanDRoD9SwgEI04Y3U9rw9vs0yXXkSCyQlB83RbAiEAuNDYepmdy54PUPUcT44ZWiMcvr9APlzBTGtqbEVKxj8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":587399,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgB9crCRA9TVsSAnZWagAA4BUP/12+VaI/ZKltfn66qQ6C\n7U05tpTgQzaz3L09xRIVfFCAM4CIf6mPhsH5Teqapie8OFTAdSMyh88hQ/Ge\nquw/52FsZFSvpZjfkB13OSbwYSyT+z0qEzyDM1grtDUBFu7EeiTO+PbAgMZT\nQxr1y/iywpMVTpkKNCZr7x9diteDNECPUeh4RjPDeZpwiyVWy3+aP2Ydx9St\nHVzn6fU5klm9m3KOz9BNGhl06bm3LsCSH00B0uZIt6v4mXtL9DEdD75rMlAX\nL/K+CHqgNKm2UXov8B2uF9ICQeeqMQUTpaRfWtMTAmHPmKbsYdbsrgu5xy76\n5LVImdyYPR199aNZWzwUoo4RQuV4m7tppG+qv2t1worBg+VsDYCU5umsQZJW\nGpFK1eYVillE1pU+IBJoZUj73aINSnHrH9XT1yfXpSE8AMRWA9Oa0+DbdH4a\n1sOenqwpevo8N1zRgIggqJzxZSQ0VYbAKnH9itdDuuETHQlrOfp059KLDOc/\nz36vq1dBk6zMyUI4tva9ttgtnF/Tx6XZ48GI1WeXz39Bfq3PuvqVuIeDrYNy\nbrP1pCalBREHqb6ernVS/uX47dUUU3r1Yas2xvKHq5U6Ii/4pKgBOJV2r/N0\nwxqfNEOOkoNf6s0sgE1+xKhO2XuxTwhNIgnRufGiAPFMkQHMFWIl4PwNkDL9\nyzE6\r\n=vO0U\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"273b97eebd73c735aa608ebab2320869c72e0a17","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.206","@balena/jellyfish-metrics":"0.1.20","@balena/jellyfish-environment":"^2.3.25"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.80","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.0.2_1611126570718_0.6399708655079586","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.0.3-renovate-non-major-5c9d1eb91c368ccca5afa9e3986eedb7cc7a206a":{"name":"@balena/jellyfish-core","version":"1.0.3-renovate-non-major-5c9d1eb91c368ccca5afa9e3986eedb7cc7a206a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.0.3-renovate-non-major-5c9d1eb91c368ccca5afa9e3986eedb7cc7a206a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a334646f42177bd28ee8535ab49d634ca36bc0dc","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.0.3-renovate-non-major-5c9d1eb91c368ccca5afa9e3986eedb7cc7a206a.tgz","fileCount":95,"integrity":"sha512-zuIS1RtaK6MowuoCoGxDs4hMMj50V4RYe4YXAHOsq+O6JaXs1V1MfsoLIjjvis08OVef8QrE6w1w1D6/UFmPlw==","signatures":[{"sig":"MEQCICGLT+6O1ttG6783awCvO/hK1wjouMQv05IAw5rYMYORAiBEvvIpdNdvbg3KvmuTU2L7X9cpS2GkKj8Yf18+n+3FPQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":587524,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgB+rkCRA9TVsSAnZWagAAuhkQAKBYr0odB4wnJSiTdJKc\n31GXRwbAGivr6gFDm/J/3AcVzsZS6Nrd+u8LU1bC0rjg9Xw5qJCDUt1MVFjn\nLY4kOM3t0d8+CLNJQWu/cNvAUidmDUNImr43XGB78WvSkmVgKJs+eOGhcjIg\n1X8UIziY4973H8rN3QuERJjpZus38WKu+zuM3iAQBWXPYdsaDiXtq/F+A6LB\nC3S5H11XzEPR7w2a4a75t8jL0SVEBWb2aYOMLVYZGPxjj/xYdWvL/WVXC90r\nYxBldR/j77B/SAhhQQ3VIv4TM4I0iEkMeGjFTECBDCUeO6oW8QFUt+BiQXqM\n34ZR4WPPEFccfmQl+gLf3GMHGiWwqH6Ufclvv0dBS0SryOlDLz+ainXCFcSq\nk0Zh333eQ5152ZJcpvSwOBX/G2Huza1wd+T9n2DF+ktbRDspvw3VwACdqWv/\nJmckxCGclOuOg9JIMQg21+LJVC5l3qNdHC7yOurkH2C9HV6cFjsh7g+E9IGa\nR9W5K0m9ArH8ZOFncLtqCTzTLZcLSC9uI4i0Hu93oyR1AekHJSqpT9mUOD0L\n1Uj6XnMJWwE1BVfZ/qC9zVaf/wamFVAdL+SF3WYjUrcDZ2f7zzgkbKTDn0Qh\nlTYa02n+MhliYjn+excNuEORgZnwyHl+ALuvicJBUhF1XcX7aJnp/oys2oFH\nKWK1\r\n=9PQl\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"5c9d1eb91c368ccca5afa9e3986eedb7cc7a206a","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.207","@balena/jellyfish-metrics":"0.1.21","@balena/jellyfish-environment":"^2.3.25"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.80","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.0.3-renovate-non-major-5c9d1eb91c368ccca5afa9e3986eedb7cc7a206a_1611131619647_0.9495219364757057","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.0.3":{"name":"@balena/jellyfish-core","version":"1.0.3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.0.3","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0fb451be98870acebd7fc671c294ab3d03426d37","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.0.3.tgz","fileCount":95,"integrity":"sha512-12wZUzLAo6V1wLjcaMnPHdIdsA9+0rmRN8dxa424dzKUbX7TIxxpCnb+miWaVTt/2YibzLRYag3qME5IuPSQ8A==","signatures":[{"sig":"MEQCIB9Zb2bXx7SWA6RPsOQCE3l3OQxIpguxWeZvJ1hOYoBDAiBuBjAGOliHoe6WNRU6VeecNgwMH2XQ9FroHoJOU63WMQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":587464,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgB+wRCRA9TVsSAnZWagAA25oP/Rjur89dADOz2v76CmuY\nACNz6w4jnBLkpV8kI0AxjaSVAaQij7Jc5YmKa+Sqpyco81zCOFrjFNpnQTNO\nNryDxVeOp7W8cMcY+x6xfv3EISgCoQt1P61vnlYkwy0VO4pPlVSs2i/EV9QM\nJMzDeS+SKSf8nB3Sl5zs1RZJfC/tbJpwizqZPKflSiLrkSgi9TNplJ0MkYq6\nTN6fHXDEgN39d0cUCXvVIY6JzApiAXfnIKXQyVKC4qIddT+85xkoY5KTtEwL\nJf3R4dR+t0BkYILvg6U8RMRO9wGrcYzK3odfQ9NnlP4dNqPJ02zXTTcoiyRg\nOavz63bs0gw9EBXfwLcolyAcl6BbyZJw0aHTN/IwKi50NNt9GKIdVEXi+JQK\njbCN/Y6Wi9IlBJKer3J73SN7S4572ruWOk/xNm+C2+xYFpAO2yLRXalEPEn7\n+qOzS3Sthy6mUyJhTbraJLbl+HuVrFejZQ5UwMVLYs91MO7DX11cwqEGeh4S\nEYbIlg39FhECdQ7w+u2zIyd26lRSLzNgD4CHBRgrht5QzccK/YCD24Cu+Een\n0K9oECYEe6VVJZxV4fyZI+PoZYRWLmUV4Cm1aHbMTNSQxt3kU6IoI2l7U0rg\nSxHINiHxgyLFm1GA9i1YrWX+iGqcj6sLISx0R8DdZQfss6jMPc6HWHsz0hcf\niJRJ\r\n=7aw5\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"9194b55e5a386934c184956aaedbfa35324a4cf1","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.207","@balena/jellyfish-metrics":"0.1.21","@balena/jellyfish-environment":"^2.3.25"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.80","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.0.3_1611131921430_0.27924125234775543","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.1.0-with-ui-schema-snippet-078e79ef728ca876b58fb3c4c66de61baee24e4f":{"name":"@balena/jellyfish-core","version":"1.1.0-with-ui-schema-snippet-078e79ef728ca876b58fb3c4c66de61baee24e4f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.1.0-with-ui-schema-snippet-078e79ef728ca876b58fb3c4c66de61baee24e4f","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e9f17ce3de5b5875c9772a8525445ee2025fc8c1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.1.0-with-ui-schema-snippet-078e79ef728ca876b58fb3c4c66de61baee24e4f.tgz","fileCount":95,"integrity":"sha512-Wqiat2Tvkno4QdYD2flTtOaz5d9MLUmxBfftjv1wMTlF3hcwEu7W7y/7st/+TUWqvQmVmhtcUAnKZDd8GZqyCQ==","signatures":[{"sig":"MEYCIQD2+jamHR1CYeL0Wm7rysV/5aWTUxPuhmJ2pxF4ktv3fAIhAN/28KOZ7SssMQt8DXLQhrk/ckLtVWBU/dqSE1XcF85F","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":587614,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgB/cwCRA9TVsSAnZWagAAymoP/35bJZC7ZymQkpAGzGie\nr8ahh49Hz22vwq/778ATsSTBlnBDSzyswquz1dxzi7xFlei4GINc+S+2rvR3\nxWWhUbgslpjkrPCd6+WU8Vidg2BXkizJCSxVZYKmMCho3jm9+Csg0voX+xLq\nxjG0iCceH91+UlzBfDLHEV+S0L5JKa0/lqb2FpZdq757llPYDttWlg/GCvbO\nZ8Q4Hye82O7gtDx7YVgVubPPnKrl6pPTHZ2nG532c1qaq0gfAvZAjRvwtKeu\nwkO4mA7gAd9jmMEoEOvtRS81UKtxMU2Z01rLCJxN+65x3b8syr/Pzf3OOoBY\nMZHlrUHdIqRIZsFzvbH5FytBXslLPSAE+pdXDABl3vSitu9I6LgkQjTF2x56\nfWKqDj9be6O+pzNK2wGdIAdiSngKXoB+Kyx2v3/a4xDCYigu/coA2Z9YECdq\niI96E5a3ynT9FwgE0l/voU1194zSoVVEn7nWJwpMBW5EFY8653iCDLyEGtTr\nV/j3vuw5OrEUhmDAjPEZm9zBJiTUDoQTuZ8kETJw4hUiBoqClbn5yAGVVDVO\nJ5b0u7tGA8OGhzxFhy91mO5GO8clmbkGVZqKdhJ68Dd4Fk9WFz5Rj45UHZsK\n3c6gNz4FdybEcVHhUkOQCHE9WSITO9h8LY4GHsTGnw3fvggyfk6hh9IRs/2Z\ndcmw\r\n=cLI/\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"078e79ef728ca876b58fb3c4c66de61baee24e4f","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.206","@balena/jellyfish-metrics":"0.1.20","@balena/jellyfish-environment":"^2.3.25"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.80","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.1.0-with-ui-schema-snippet-078e79ef728ca876b58fb3c4c66de61baee24e4f_1611134767825_0.683113666140289","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.1.0-with-ui-schema-snippet-3d8249d0a22d548e2f8da483d478393ab299e048":{"name":"@balena/jellyfish-core","version":"1.1.0-with-ui-schema-snippet-3d8249d0a22d548e2f8da483d478393ab299e048","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.1.0-with-ui-schema-snippet-3d8249d0a22d548e2f8da483d478393ab299e048","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6c350ea81efc5aa60c802a79e6d38791963804ea","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.1.0-with-ui-schema-snippet-3d8249d0a22d548e2f8da483d478393ab299e048.tgz","fileCount":95,"integrity":"sha512-QpT/zgVTxhjrQPzuwsRd8omZdLN4xcSwIuwhxU/ejbBxRtIU1kCzU6zbKna/f9tPdgsVCsl/36JReq6dyvLNVQ==","signatures":[{"sig":"MEUCIQDXTspIpJEgQpUs4sMt6G20S7pU78TMUccjCA15rCJ8tgIgXk+SWwNqx7dOKWDtUwVRcxT6q93lG2gh31mIlZpdHhU=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":587679,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgB/gNCRA9TVsSAnZWagAAC7gP+wUsZLBshWTS9wJTmc7v\nKmOaCCWgParzMDzl1o9oMRIQARmi5O3pr9PGr9Xln1oSec0Ncm8dMaDefNxZ\nnnoQukkslkwSDMfT+dAcoLwk4qzP/Hkf8Ni5OWYYoaDIBGF31PhsMF9LDFtg\n6kS1BIu9xiHp3n0ir2Djv6sGbyxqYm4LQmJBEYH26zWGpUlxf1xj2Zc4E56B\nVb5HWqizbHwZWTwXnkk7gwYIscxMTaTVHDs5eWw2bOLhnrjuGWmS9SlLSY8Y\nj9nINR+g3F4ioZ+3lhJhSz/Wq2dNG3rHgmfxqEGmfoEAXHufLPxhQwQmtLEf\nFQrCh9mtJBfgfSnk0ZVuQa14y6yvmzRuh9G3i7Lo4nib4umniRPNfK8X+JX7\nyuAQ8OGidSLwpGtMooqkd+JTNm3X/zYj+0HDHvREic4u2KfBiDqkD/G1nfMQ\nvGtYJdBwlvnbwzf/UCNhelpwzhY/PeryW82jRQ6Qmby2q+ekeTHAgD6KvhDP\nNSOEM2jv8rpoo6bYxaZdmGX0tL8NVYnKt/8DFdJ1phqzrtarJZMvNF+3Mgd+\nBllzdCr2m1xUWHez02KwVOV6RPyCzlDGbP+DoL8GKbjaR95Pg6wL1CgscKzg\nPK9eUFHKu0emv2xh8RqqkbQr/jBF+26YY0pRqoC/uaJ22sQPXRXJXaXYxIdF\n5UtH\r\n=9vb/\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"3d8249d0a22d548e2f8da483d478393ab299e048","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.207","@balena/jellyfish-metrics":"0.1.21","@balena/jellyfish-environment":"^2.3.25"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.80","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.1.0-with-ui-schema-snippet-3d8249d0a22d548e2f8da483d478393ab299e048_1611134988729_0.5050174419730844","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.1.0":{"name":"@balena/jellyfish-core","version":"1.1.0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.1.0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b6fa0acc13ae838c5fa3bcb5ae08a9137201357d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.1.0.tgz","fileCount":95,"integrity":"sha512-jRyXvVyrQUHgtoru3QFzpYm277WxuU+/se0QQ4ZCAbT0LlQT3W2HR8Tfubt05GRTdi3dlrh3kUXFJwULPKZClg==","signatures":[{"sig":"MEUCIA13kAkguR+/qXHxnEhWJP+06/nmPWy3FKiAX/oBhvtJAiEArhexzzknromJB1Pu2z5xQH1q6tvFClAZ9ejQ13GNfqk=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":587615,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgB/zTCRA9TVsSAnZWagAAN6gP/Rd2a8D6oiO8MXN/4FYJ\nttSImzWb7Nx/sS2W5szpyFD9wWN/QL3gfvWK6nrWNVfdGoTvdxnuEsirTHGP\nJXpLIwACFq2yEnTY3BMf9FfB00kkduCYUt2W1pLa7VJoWl9u51Ro8lFhUtS7\nMCjNT9HmkDPGjsBO2Jls4bzBechfL37ptxMZ8pvfFKMSb781UGfAKB4ZbqRg\nntJ7L5S7rnUrjAoxRsqVmnr9OiMNic12pGynp1nXp+4WiB1mVFfXKSfpcHH5\nluimmN6Afm9tvR0WTU7BSIPC8CYRaMVJ6/kLR/s/PPHF+Ln1jkgIRP7UnOUT\n2ngtZHF51itJ+abR9/Vc/KrKlGwl9KprrF+dtibIJ0pgmIhl+5wsE51+nzBW\nlaRT6RUe818aRZRD3tNunFpO5JAaHvsZqsxqvjPLIk5zP3yyC14kOZehKyBQ\nwo0q6G+xeDOVACNJJKUDh7urgNdFMxFx5QnVH8vBvciIeS1fB+1M8sSBw5Ox\nH9h3OAi+cr9d+OjmZ+S8QPheSne97t/DeK2PxXBxGQFNfl8mUN63/mDl3JqE\nt70Q/trdpdInU8dl4KJL/yuym9h/+X3RM80/5xVrFjDcL2kwHk+3aHDSFuDM\nCozQgZE+FYv8+J3+CHcwCMGGpwvopYX+dw3MeSOTE+Hdfz3VgX0dKul7KIme\nUEML\r\n=v7Sp\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"bf3831719693132a8c0cbc6b6c5fe496c677cd8e","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.207","@balena/jellyfish-metrics":"0.1.21","@balena/jellyfish-environment":"^2.3.25"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.80","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.1.0_1611136210986_0.39187119731836195","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.1.1-add-link-patch-logs-c9caab8459bb78b7f4b90530cccbb8d48ed11407":{"name":"@balena/jellyfish-core","version":"1.1.1-add-link-patch-logs-c9caab8459bb78b7f4b90530cccbb8d48ed11407","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.1.1-add-link-patch-logs-c9caab8459bb78b7f4b90530cccbb8d48ed11407","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9cb8094fd9c68f5bee8ad6fc6e1fb0886d10bf5e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.1.1-add-link-patch-logs-c9caab8459bb78b7f4b90530cccbb8d48ed11407.tgz","fileCount":95,"integrity":"sha512-9BvI9awl+GUGw4V+EZnIvW/8ULy8C6df77Pg2sryA9l31U3EOxZRNnFWbv1QDJySYwTFkXCVt2fYMgCNhD0deQ==","signatures":[{"sig":"MEUCIHG9kmOK2NjzNFtW16j10AT8nq5sKl+MOrk5kFHkznjuAiEAiZ27DLrjXNNIpI3NQRMWDDPJTaCn3hhMnSOJlJEteOA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":588214,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgCPloCRA9TVsSAnZWagAADGQQAJSAFMcrpmabJ2siB+DU\n5wGRcqFItddVtV/bhRfjO0gl9zZOE8h/vnKoKTugrfe/xSd6X+csLrW9Ks4y\nqGDBCBZ7JT7emWsqlJHVA9WqIrrbdGqeMD3R0RCd994qObhFd1CfJ11sk/7C\ndrdem4h27/tbEYrmeP2eXYGA/cq13USK04MD7XvdJVLRdYg13i4xQ7lDrXwV\nO0Wp7X3OA76gxv+cSwc9A0ajjf5aO8vLnr5TvV1Yt+gTISa7bGnTyC1VFNWc\nVy5IadC8Iv53tUTLaS9Ka2ZFSLwjSUM5a3zJx4PONepeachQjZGfNe8mzrhI\nC1vsBEkmBsCPjxcoqs6ujZC68Fx2YtuKdV6cJFPTD+0W6hJQLeBvE1yI/NOA\nFgJTB7dPUt6X92OVuEebNFD+G8Heoq+wTlCN7FmXYC8CIlTe1+6f+ye3mgIJ\n9fQ1VIBReIHrNgpBXDqr4FFpaxsQAkcYX4iHncpX07KTuwiwRAg01M9OiZ1c\nuc/XZG0DKqqXQT8l49zTUI5t6Q7J0lFY55yWNSqeAeojbmofmqY0aetm7ahN\ngnFf99upuFE8l8IOV3oO56xzeQwTMRwgijPewst66d/SOa/09ABIgwWP1jAh\na2+DTvX18iy7hV5EXvB5pBEAtxh2wdKxQyVWsr6Otbc8T4QX45Ghe5WNop17\nAjT9\r\n=ZZMT\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c9caab8459bb78b7f4b90530cccbb8d48ed11407","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.207","@balena/jellyfish-metrics":"0.1.21","@balena/jellyfish-environment":"^2.3.25"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.80","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.1.1-add-link-patch-logs-c9caab8459bb78b7f4b90530cccbb8d48ed11407_1611200871923_0.271349822479086","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.1.1-add-link-patch-logs-5943cc89bd94d6a9f89e052c885f27c36f3aef76":{"name":"@balena/jellyfish-core","version":"1.1.1-add-link-patch-logs-5943cc89bd94d6a9f89e052c885f27c36f3aef76","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.1.1-add-link-patch-logs-5943cc89bd94d6a9f89e052c885f27c36f3aef76","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"fd74c0ec4a01b21974989ebb56556468dd9b1268","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.1.1-add-link-patch-logs-5943cc89bd94d6a9f89e052c885f27c36f3aef76.tgz","fileCount":95,"integrity":"sha512-Rkmx9/gZSzBomVgMmWsCLQ2ziJDUfQTz4uww/l+OIA/EVgFaVQtAT6ZrppRaTDwTyQ1oshjISxE6il5qh+xWag==","signatures":[{"sig":"MEUCIDm/OGoGL4e7Me8HIP4AX+ypyf9ytCknKSgInx0YisHuAiEA69rVUS0FrODFVtHL8Y7Xr+JMFdH/I9Xxs3CVSZdF0vU=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":588240,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgCQFbCRA9TVsSAnZWagAAb7cQAIORHDw0EyP85jzXyp0Q\nu2eQfQja+OFl0uADQXrxMvmMnM7POv8Tcags2b8gTsq77tP6kda31rIkFRKG\nXqPkrYmUqee2FsQsUBIjNs1LyeDCHURgxPaetHnxncOJjO8JHQrLs8mTEW7U\n09vcDhprNjK4fEVTHn5HujYbY5OzHcaJXS19TXg6HwL2muxmsuPNNo6bFL3V\n4NekeEg74Dz3xCdkOxpwq9yX2nof/qgDfcN9iHTf+l1whXzIl7GQzlqpQ/Fo\nPwbZPAecDbCfLMK3L2BnQS+YHMsRsssxj6uRMAHQUAw/E9WU/7KPXtS2oS4G\n0nGal3eiyfOfZvPMT9woe+4RnvC9o3c63IA7yF+lhVsOdyg7vvotFUpltQgq\nrUbin9CbB2rL3SAO9FuFopVSsdus5/njB0nIq80O67wZF3WvqVCzBblF7gLg\n9RavENaenavXXGNZLzUZ+F12mO0ptBxq7cB+7jRk4Gc0EP/oP49A5kcTbOwr\ncEGgu//l4LPg2ugLyRS109FbgUSfbbSlUXigdC/lHg47fO58Xnevjz9YGF/w\nasf1jPoTz0L6bcfMI4tA2dgRStUqf2MJ/bTOpTBX3aRRU1oK/ky4Hy0J5zIJ\n0MhJZTFcggdbu2EnrWNGViht9wxUmusooCO31fVVs/8FQGoOE59Xvavu28zN\nglkK\r\n=MS/w\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"5943cc89bd94d6a9f89e052c885f27c36f3aef76","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.207","@balena/jellyfish-metrics":"0.1.21","@balena/jellyfish-environment":"^2.3.25"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.80","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.1.1-add-link-patch-logs-5943cc89bd94d6a9f89e052c885f27c36f3aef76_1611202906575_0.7254848327658454","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.1.1-add-link-patch-logs-e90727f0ba28ca0e19c723463b382ad653b6d69e":{"name":"@balena/jellyfish-core","version":"1.1.1-add-link-patch-logs-e90727f0ba28ca0e19c723463b382ad653b6d69e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.1.1-add-link-patch-logs-e90727f0ba28ca0e19c723463b382ad653b6d69e","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f0a42d8655eafe47614c193903f12536ecba874f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.1.1-add-link-patch-logs-e90727f0ba28ca0e19c723463b382ad653b6d69e.tgz","fileCount":95,"integrity":"sha512-m+end3+Pl/rASInopqBJMcHO0evN4+gtvcC0++BSwuUsZq4kyVts+f4KrzBzko+7Dq4te+rL2h2K+AQo8S9HYw==","signatures":[{"sig":"MEUCIQD+O62VzTIgrC46/hYsqjz7cn9jNbVbYIITKecZ7FXAdgIgGLKPD7aCmNrlm7RezY/rhOoDQMnG7WIxNRUygxmcXB8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":588252,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgCQQNCRA9TVsSAnZWagAAnxcP/AsXWGOQpC49Jmx+UiAX\n8YkWmVfSFl0qoopjgxGJ6J50RX9dQ2KtS14/+EJqCQM/DjPrBsdfRip2gxcX\nHO/ZH5ZxR0ICVun8lx7cKdaM3irU5NxOQOZmeEAzhYVOSvoILNu2hb5PUE61\n2+fFWx6vdRwRQBT/dcUyhgXqTb+QNAYTNo8pVN72I9oLD5mqsflJMeWSm7dd\nUP5ZXQZiEuHI9FyodxrdtcVhSIZFAndzlWXJ/l8NBbf2cNl5NqYt2quaHyiK\nGVfJu6uFk6cKpWMB7PPU9VT5KtKLnlyxrMwqIG/A0dQxq6SnJyvNgfHzvp52\n4GqpoYv00IOOEWWfajI4XpYpKvhYNMYuLWll7BJ65Oe1E3jb2mFuGbwuHrMe\nYA1DHzdFdKXsZK/G0T0DHbGS5K8FvQ2TfXX3N3b8r00ArCSi6/iA21Um5jdy\n1seh3xqh0QfpSbgxvoilGMZr0FhLiKShzzQ4Rc5E+GaWZal+McggBJohYZdA\nKJaFD3Xr/+xUaX0Xi9JRVG24j+9UerNYEJGLd0GiKwaj3qeEmHcHaOU3CWqt\ngHEUk6tNMvNEDoDwPZWTfJaJp6mP2rRix9m1E1Qo7HWVmkk5Yg8aw1qRQIJF\n/GW1lnl9CSaulXC9LljXexBO89+KWf34Kq8CssdgS2T8GIp+Er3M5qZEPfa9\nfJtf\r\n=hfG+\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e90727f0ba28ca0e19c723463b382ad653b6d69e","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.207","@balena/jellyfish-metrics":"0.1.21","@balena/jellyfish-environment":"^2.3.25"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.80","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.1.1-add-link-patch-logs-e90727f0ba28ca0e19c723463b382ad653b6d69e_1611203597011_0.9527423160484934","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.1.1":{"name":"@balena/jellyfish-core","version":"1.1.1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.1.1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6a2e986fef552ab5f346e949929ea7e2e7a658b0","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.1.1.tgz","fileCount":95,"integrity":"sha512-N7u54S9BShGaY8gSfC+uA0dTBjtPJDjXPrsBoArKvS0VdtvoQ9Jf5GCsvfIcHODsipy+vQoi78/cEeFTyPnFOg==","signatures":[{"sig":"MEYCIQDfE40DxmtmpMsg/yM2xVGHyV2zJNhMKGMsx0lMTXee4wIhAIndvErv8SDz2B8pBShOGk8vvKkHJ+aUwAZP+zmKHOse","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":588191,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgCQWZCRA9TVsSAnZWagAAIpsP/igGmulk2D8VAe4y1D8e\nTWiTWw6D9yEMEqNsM5zzJGqgr2EimUw3etSd6qhGzeWL33cD4+T61WuqxQDf\nEYBNzcCN2JZy/vCaZjko0wpBXpHTepj/k25nkbGuKy9IYDHaNf+mGcD8Qbbo\nOOf0G/if5xle4NwmFLkMno4zfQ3SPVb2yG6Vj/MqCctHwxwe++m5LDelKsrB\nQwNxP/nk7pFr0rKSEKjxIwx4ODhkjA1rhqY8NHk8gC6VrCkRsCtSJfkFRVKa\nqDlogCp4DdpW5c5Es73PKaNbGIJqjq6iN+C1eWtjpMj1fKHK7/niuiDW81QG\nqMWqwwrO1FQKruFG2lv3+DUU0Xtf4+xmHvgs7ufdztqmYuuC7FWazbVnJpbw\n/NFmw1i9ddOgHiAKhGK+TwLqw0Tof6baEOJQch8jeMAj0UgMBq0GBRoyWQ0L\nP4RseAHSwm/M5CKfzQpY6nMM+UqrTxNQ/0y6pUTpEO1bUj2YBrXmeo1PwNwI\n/5KashXi2SrFEOua2xhaf1f06rLKlr9awq1Tj0GMVxkvIfoBiUPPqum9NXXY\nk3TQquj+cv89p98y7w9qaF9XGv12LpuyIm1aXlO/8G9waeSungGkn7ZjrkRU\nK7LIuzZdrlwaZ9g+LEelrDqEaX1jVmEMiDwaTgj8Hznf8QwKF5zekWrxl/RR\nKQYU\r\n=6xg7\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"90af80304bcf58ea36dfd6b27adf0093a4aee407","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.207","@balena/jellyfish-metrics":"0.1.21","@balena/jellyfish-environment":"^2.3.25"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.80","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.1.1_1611203992666_0.7996622110339198","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.1.2-fix-user-email-field-a07c130429c0bcd663b260f20cf97047943a0099":{"name":"@balena/jellyfish-core","version":"1.1.2-fix-user-email-field-a07c130429c0bcd663b260f20cf97047943a0099","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.1.2-fix-user-email-field-a07c130429c0bcd663b260f20cf97047943a0099","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c55d6ccedea08e29fbe70446e6e7dad697d9f162","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.1.2-fix-user-email-field-a07c130429c0bcd663b260f20cf97047943a0099.tgz","fileCount":95,"integrity":"sha512-h6a1RAGZStZUtMH2sRx7UM4j2+DuH/pz/GllaJ0W5SYq9fEljDoWIllQF5YEzDQXrorOUf2e5TifLxIclPbMsQ==","signatures":[{"sig":"MEYCIQDWQHzoIerEr9ZbJ3x9XOpD7NQWmOZXekvy4mtObt4iSwIhAIZf0BmwPl3tbJANV1Qr5IOcTarMWkinoKmxna1cnL3C","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":588507,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgCUjfCRA9TVsSAnZWagAA5CoP/ipyOgZ1+g2gSiC38w9f\nOpAC/6yx+dES6DjjSC+ZLE0SvuWm1NPymlqF3YPSp1IhhR7ETSyEUbZKkMgC\nUtuUu4YOaXWCcoWBzzY3V5IhGVXbuc+LC6GGkeOl+Qbe9sg4xWGqr+jCOooJ\nAUfnVRVz/Os6D23mwxWNxArQt8lloJBQFvIqPRRkIM6w2ni1qszLOdSk2KVt\n2qO+uh3i3Afz7b6MTQxuYCMfvLWVZ/PSazY28Vw9xWgAzbaHp5/VbjFpvJyT\njxqLzFc7zYfc6CmSyZRKZO5ENVKtHF2sxCeuPjWP4Wtg3svIw+6fwNp9wjOE\n30OCVsXiSvNB6X9vbYskyu19FQ3t2/qX3aPSB9k3uhI16pVlXq+EbxAlEvND\nD9+KusWp7EEqPBpHLbJ6YAwWxan83dUXFdy/LmOd7+3AJVRFQsX+Z7V6XdVd\nqzz92w8NGiIjKGnIKbYOioBm4+K8OxjBYMeI2+jM+6pnutRdMGS7XC39faEg\npA8/xFPBMOgjmN6yy1pTJXskamtrgII00jBbRxvQPA51a0KsgB41p2cS2x1B\nEzh6RmnLEHnhpHrmYTClm9a1Krk6QQCy1C4oiKLqA3qRX0642bTHiMvPbu6R\nF9V7kjtuTc43DehSikxKaiaOY0Hmiin2z5tzS4FLKIym+GCsSi2BgalF737A\nydR3\r\n=c9Ck\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a07c130429c0bcd663b260f20cf97047943a0099","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.207","@balena/jellyfish-metrics":"0.1.21","@balena/jellyfish-environment":"^2.3.25"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.80","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.1.2-fix-user-email-field-a07c130429c0bcd663b260f20cf97047943a0099_1611221214578_0.558103817788167","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.0-5508-pre-build-versions-01664d58395f1147e6b7b23e3a29e70f6df16aa9":{"name":"@balena/jellyfish-core","version":"1.2.0-5508-pre-build-versions-01664d58395f1147e6b7b23e3a29e70f6df16aa9","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.0-5508-pre-build-versions-01664d58395f1147e6b7b23e3a29e70f6df16aa9","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a285b238f09b587d9c5f3316f9d55c419d608e39","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.0-5508-pre-build-versions-01664d58395f1147e6b7b23e3a29e70f6df16aa9.tgz","fileCount":95,"integrity":"sha512-mr+qUK9H+UpusTI+vsIOUEXIidOvaTDYpeY7C+MrzGU6pGKBC/FiVJcW0hgrPFju3q0nqQJkvnPP8rvvKnTUlA==","signatures":[{"sig":"MEUCIQCQzOiFDCTJna5mBKCtHVzsZ5ml1R6srvomZUpxpQA3dAIgHmzO4HdVr+Qug4jXiScHp2NwwJwkBPKFs9zUnANCwL0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":591478,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgCUkICRA9TVsSAnZWagAAEjUP/jdAeA2dOQgUFM0CsYEW\naUCeAqkarhr3BgbFQIr/T1Gs872Jnrpl+I4kkyvMxhSJaqKHXQiCOxdlQLhc\nPuHAoOw7qa1G9p1qkOpX2yBem8fUqExhkgXfxD8sMQbg6ksCbQ0tHGGVI1qg\nPqExFek1qS5SPd1mnEDgUjmm5rUTUcPW/0g5lb3JrE/Yjncv4ui2UKKyaNG7\n6Wzr52G7/C6KxQDd9LIeyR9QQE2nn7XsuRxDZABofcgdc5TCJx6XCqdPoBIk\nZiTk9anHimTkOT0FywqpX1fT4hIv8vUEPA+p66xhjq3I7mh8IoP+eGLbRc16\nokc/BA3qHcg27vtvr892hsmEwfT8Ebj6opXwNngC4EyIo1d+flxFmhSQxR3n\n6pBVpP3GH4ZKDJKeZ6VQbPzdY8aVIg6oNRnnG3OxpAOf7sBxZBQTxGiXGqbV\nzfJ/i2Lr+Tdc0dRHgwCLM/c5nEHAf0QAlMWOlV5ON33vNwecXoQtWHtMT/Pg\n0KwBuQ2ATAvkaCls98wgQCkhrb9dvPNtciFWb7zEKlkrzUuTznaVfJ3flTyG\niEsJxkqM67bp+O8d9Tv9Ya9VPJQ5CM1cSxQOVJPfEoFd9V9QiTysxOkUFUrz\neqDQ7i9PGm061oUiQSr2koWgsFUG2yMXs071bFcCdhmVx2PtMiy2IZf2sp2c\nuiab\r\n=B5zM\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"01664d58395f1147e6b7b23e3a29e70f6df16aa9","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.207","@balena/jellyfish-metrics":"0.1.21","@balena/jellyfish-environment":"^2.3.25"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.80","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.0-5508-pre-build-versions-01664d58395f1147e6b7b23e3a29e70f6df16aa9_1611221255578_0.7316011684192787","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.1.2":{"name":"@balena/jellyfish-core","version":"1.1.2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.1.2","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9f15324821dc8c7b115b68a4943f2616e1f06c47","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.1.2.tgz","fileCount":95,"integrity":"sha512-1fzrPovyiwKgkWZzbUwNMrjHU2/Mx8D3QnMEM5tyhfajHngyuVksNk6WX/0RfCgnH3RxwGkFLctQ5Ku4LZuJxg==","signatures":[{"sig":"MEYCIQCqe9YqgBElrbakQNWMVlz/UWu6DQCDprMIFmNRKNgc3AIhANievdFPuHHLZ7D+v0Ph1F0LWZ4nzD0Chq7a12F+NQ3Y","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":588445,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgCUotCRA9TVsSAnZWagAAzaoP/3mx/VPFOukakkHhpOnJ\njr2cAR5wyypSPT8Lfc3yortShotQW3IT5iwN868MHe5vauYKJPHdnTFT+MVg\nd3HWX1VwBqDv4T8mHjyYn32EOAPRvk3rxSWKbx8bWUvi7s+AECBm8Bz+gZ4I\n4hqzqASpBaan4WkghHPD9RwJdDLiRluzlfIGqnNY5E49JEhqgte9FT5rsInT\n8TSiaPsvO/yaXwfDCNlHxcdBWdPzly67a0DMsRTUywOvT7rXr4Doy6/X+Zfp\n3obwgWczGvQwjh1noU9jQrLruTN2dnpKyoMN1QY1S4FqWewaQqimdzssUZxw\nbMrkaKmbvC0ksP8NMxxedDpCgcOAEGok6T4u0Hyqnrb/eMkg2ZaQaMpWGc6N\nInf7ozf3j9dqv0xM4kYxTTNTfFSnxjLvZOgX0S60YwA2uivqgeW7St89T6VD\ncoGlsoeqHnJ9H0DYv7G8zVVsnuVWD/emI27GKaCvl29hp7orqaanZ3fgkjQy\nm8zxNII54VG21YAvM+AhSoSSeTbuzTxbKjmhHLbU5iMGzfzrkl2Q2iMZUHaD\neZ6gFzXVUR/i67dlMK6MXSTfhvTgDWsum5XD/1l9QBpUjrRxvobH5mhXtgKO\n3AT6u6JPdG4aRgIyjSIBdTrz8OhA/jM8AyPYYTDSDuxDxT4ziq3NA8aF9+mj\n15sT\r\n=DWZF\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8b0b8e0e1404db3efe2a0bcc3387c5694c1c5dca","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.207","@balena/jellyfish-metrics":"0.1.21","@balena/jellyfish-environment":"^2.3.25"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.80","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.1.2_1611221548896_0.5263648076683163","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.0-5508-pre-build-versions-c9fa904fe9323f6e22335abd78d26e015a0ee6e1":{"name":"@balena/jellyfish-core","version":"1.2.0-5508-pre-build-versions-c9fa904fe9323f6e22335abd78d26e015a0ee6e1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.0-5508-pre-build-versions-c9fa904fe9323f6e22335abd78d26e015a0ee6e1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"26bae0c26f6630561093dfcfe2cc5e7944aa95ae","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.0-5508-pre-build-versions-c9fa904fe9323f6e22335abd78d26e015a0ee6e1.tgz","fileCount":95,"integrity":"sha512-h9YndAGEXy2bDFezy0EEFOr8sB8BM/kfJEFyekbHJ6FULuCqq/zLAdVEnHwVdlpy0PfWQrKRBa0cO8er21av5g==","signatures":[{"sig":"MEQCIBSQJJhXYNlcRs4b+zOOOAf+XF68TT1aBd8iqxGGgf0IAiBsaZBPUwt/osSX9qxTWUCagwfaXmJQ0yQiXlBLw+cOHw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":591522,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgCVRTCRA9TVsSAnZWagAAKVAQAKGM69+vo/GpxdPSgVGp\nEi4sp1AzkV6mHYz7OrtnlPok5VgE7kx4nX407yn1u+Lxwebi4oq1+K6y2MnF\nGY6M8tXDk+TTm3oojmT7EQf01sNcBu1wy+zvNplx+p2GJStw63KWo3u1/D4D\n1EYqiNi+kwKGmYakkF4IH/4APqT09ZAlcZjxBbA5VEMNdWcK8Fc1o4AGxE2U\nh62f7Hn93/NjVwKZf9kklEHhI2Fu8FIOPqlCGj1OHPQhYc+9WQ2+OyvhpQzu\nnnRW9Ajf3xhSftae/tw+T9cf/+pDLDp/zve4FL/Ir5lI0jQsjuu9yMb43gfQ\nrfyOmeBKYC2rISF3tZjnc4UA4EmFZPSW4LJxqpeezyLQG3C1cE3uRvu/CirB\nX7q9C7GknVzmjD3zzd0K3YhoeJ4PMsmFdRqV3yuoai9FBgpHC0qAxzVundoX\nYk0WWmBZehIJCq1yxvys8aE3rkPbRwEZcOvL6cxDV7C2kuVYkJE0pt81rRrf\nH0g94FKUEwa8O8nl04rNnUV8jHcRU7wSkErD/E/I8yhMmPzTnJSn4j7t/F6i\nf0RejchHp/NzQgWxRA4zAoQeA26sT4McHEzLLTva8qJq6NBviq+SPROylHM6\nCyUWaybaa7tfzKkhm/RB21TvEHLJGC8tVl0BG+wh41Os+/IEZKegS9cniEK6\nqj7v\r\n=cSPL\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c9fa904fe9323f6e22335abd78d26e015a0ee6e1","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.207","@balena/jellyfish-metrics":"0.1.21","@balena/jellyfish-environment":"^2.3.25"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.80","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.0-5508-pre-build-versions-c9fa904fe9323f6e22335abd78d26e015a0ee6e1_1611224146857_0.35495193598434316","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.1.3-renovate-non-major-6c843919bd88f01e92d009833731433ee0b3082d":{"name":"@balena/jellyfish-core","version":"1.1.3-renovate-non-major-6c843919bd88f01e92d009833731433ee0b3082d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.1.3-renovate-non-major-6c843919bd88f01e92d009833731433ee0b3082d","maintainers":[{"name":"srlowe","email":"sl@atiklabs.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b706a9ee37eb621e861f74beb6ed60f8138dfe87","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.1.3-renovate-non-major-6c843919bd88f01e92d009833731433ee0b3082d.tgz","fileCount":95,"integrity":"sha512-8p+Qq605xbXxNQh1hWF/3UsqnT/mD/TEwNjd1J8jIWyUnJWk9mDnmVg876zgR3yGSD29l09wLqcF62oreIZwaA==","signatures":[{"sig":"MEYCIQDdVgMqYATvJwBfPUKgWbuvowi2WpqhXsHzjhre2EngRgIhAPjKVebG4EPYVxZrm/UxkiEHrV5qoyrZU3BKsL1JGABM","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":588570,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgCaPPCRA9TVsSAnZWagAAP2IP/2CxSiAfx2mhKseyN/yx\nEWMVZMqxFfuw1HaiNay4Mspogz9PYR7ZtPMhagZIDTt/VKCkk3Ts858XL8SB\niFCtiltCJNZJkaGLIabCrWf4152xv0YmTNUvwO6qvxlKNoiR8JaJ1N/tmrxO\nt/dSOMLpEegvFlPNkXvd2o4Sf1qcW22xuf/vOYlWBjGlWNexveWsvDf35vT6\nAgatx+I5g3imndocL0jKWIIcy5NzPPEImExQcNc1gYa+vaXiw+U1CzjllzU3\nvzHkrJkZuaBPwS8FQ24wJfuNECzvE2Ga6fFa58SkL2ufE45KcKO0v0GBtKvY\ntnWOKVsa3FPmLhb6U00bLiXoZvODVxiuvLC8PqTIph3Z1hDY1aKT+qV7QwtW\nIfif4uznmSPtv0zsSr7xuT5LOKTgbOQ3/k7isbmJdLSn0vLMoHpE/tZ6JR1+\niPAlR50BERkv//r3p0o1ZmtNv+I9KE9snW0JydV2/EUzCi4w8A8xVeblHBeT\nlwkVqCXqv7vooHUY+CzsMbzTPO2a/T84gpW2humkNm1EmvSjiprJCuHRhvhR\noQi56Lgexf68hVRtNaKZfgaEBGsavPmg4qchDSvLfuHI8DZY4hWF6fcjfe2T\nWOAOtSWLctccwbLDQh4/k2TgZewsALJ/uONv25RLcyAR7EJp+rVrRebU6CIa\nwaf4\r\n=gfr5\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6c843919bd88f01e92d009833731433ee0b3082d","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.208","@balena/jellyfish-metrics":"0.1.22","@balena/jellyfish-environment":"^2.3.25"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.80","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.1.3-renovate-non-major-6c843919bd88f01e92d009833731433ee0b3082d_1611244495445_0.43898357911256025","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.0-5508-pre-build-versions-3280a8d6a912d2fff33e389ff211ddbc0c77aa51":{"name":"@balena/jellyfish-core","version":"1.2.0-5508-pre-build-versions-3280a8d6a912d2fff33e389ff211ddbc0c77aa51","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.0-5508-pre-build-versions-3280a8d6a912d2fff33e389ff211ddbc0c77aa51","maintainers":[{"name":"srlowe","email":"sl@atiklabs.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b12c0a863167019f9ec86e86df519ef2e2044e57","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.0-5508-pre-build-versions-3280a8d6a912d2fff33e389ff211ddbc0c77aa51.tgz","fileCount":95,"integrity":"sha512-sYBVkZTGznS4lLdCJhDF05DRPYZe7ZBfTdjC6mayMnZt+IZHLxplMWqfamQSV/lU0stDnp+rAkKaANE8RpM2iQ==","signatures":[{"sig":"MEUCIEWfGkjSebskws4vzgydNl37Irmtf8PuMfUkNR+U2IwAAiEA0+0kuD+1g+Sgp5zAI2iab+DPzHH0CARBqmo9n3bnHIY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":591682,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgCaxJCRA9TVsSAnZWagAA8LsQAJzeDOQSchB3G5yva5E5\nwQWYvbiIHZIQNRoRbdwRiOxjBZPYKSDEqYx4kHdV4KavfLlvY0T6h8DcKRLB\n8uwUvA2hmC1XspM5FIbP9Ne/co5N2un3SI+VOwVr0GIqxktQmWvlmz9Ebgc5\nPGZErd7int4ctwC84copKENWFEpkr+rNJoVsaeyjAcbHu2hoKKbOe2seNvgY\nqP1zPPAUzcu1lA/kNDZjhDLaVoC2fewnP9Ydztv54YAp319rPqYcwY4WHXN4\nAHOyGZ2nt4oZ2S12YctOkMpY8PNSU7SmM3kxjlJYI1VQGCQZwD0Iw+E4LP46\nStmrpYMuTpObIi8jEX6oRIlfhhFX7ZQwl9Uu3Zm9xoIziy0gEQ0QbEY3+x4N\npoJnR9GjlEcnEWCoxVyOorWWmrsfgEbmBWVXt4pWqSj78N+vkMi5VOO7RUp5\ncW1hQ232MKnpvjl07IwMAgKxRBKQURwrlLjnhRJkoYnWJQLq4m3nmepLySDg\nNtnnzrBwh8iPavFGoLp9vxNmMR7rzFUNZTWjClSdVb+lFS0rU7M+m9jZpLtX\nidD92X2TL4w6GUoUCW6sGWzOqrYWLcbSOdlUb+GTH64WNwys6ktj6JKthQ22\nyX6FU1LdEzi3khkr43+M7rtllci64SejBAbheeseY13zl8uQJsshUsGOg2/o\nJW/1\r\n=etb/\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"3280a8d6a912d2fff33e389ff211ddbc0c77aa51","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.207","@balena/jellyfish-metrics":"0.1.21","@balena/jellyfish-environment":"^2.3.25"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.80","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.0-5508-pre-build-versions-3280a8d6a912d2fff33e389ff211ddbc0c77aa51_1611246664542_0.310600602288841","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.0-5508-pre-build-versions-8da05ca0d2e19f2c91d06b0c5a5d420edbb29ec2":{"name":"@balena/jellyfish-core","version":"1.2.0-5508-pre-build-versions-8da05ca0d2e19f2c91d06b0c5a5d420edbb29ec2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.0-5508-pre-build-versions-8da05ca0d2e19f2c91d06b0c5a5d420edbb29ec2","maintainers":[{"name":"srlowe","email":"sl@atiklabs.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6a4fbd95156af7f5463f03aa6fb21f483619d22d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.0-5508-pre-build-versions-8da05ca0d2e19f2c91d06b0c5a5d420edbb29ec2.tgz","fileCount":95,"integrity":"sha512-8K0WLJAw4yP0styW5rgw9HV3DkSkYDN9ZOe251MiGJi6FAFxisPKfe9TuxVnY9RXneTdXC9S0mMWMFhPGOzHlA==","signatures":[{"sig":"MEUCIQCRapKwNX8RyaYcd9z9lsEBPD/2fSmC7c8vqv4ze9d3zwIgFBe+ompQG5vji67NJPAvQjd7B7gGApxOvKVqCRqdhXg=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":591547,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgCbeNCRA9TVsSAnZWagAAMFAP/0st+rKUqDridQERiQ2J\ne9WYZvHsI/d5lWemBBBizA7HbHbQJexrFiHY6UnXPMyLwEnsS8nqs5UOGaqM\ntDPEFDnW1QDLg9NZQlShZsEV/sIRN1y0tKc5CJ3UJ1rNmk5FeaiH8a/Z8kFf\nzAE87+HN0O1y5mR9+2EQ/RXbOdp1yvhiXAfrT0Tkgj5ZzrLFXCMP8Z0/8UBT\nLCjj4EdqLDCoTdBJT0gR7IKKbqew6b7o5YBi2LCigT9QxmvVY4XlsaIafpgn\n2Tvm8SUpP5h619pQQPsP4Z01jCl5xt7y1UiCCaVxEemBJQFefeuJMmscMax2\n49GlPZP8HvwOk+wkLp21N4qVNb5hgkGYhpR7nr/c7TGTxtpLgaSlf0Eng2/9\nF6Kubr0cVG32FD3kemm2aYUsCVGcVArqQ8BywzMN55hmEucSUgL812cxUecF\nLtXfXleqDfFzg2wIEqJvbU9g25Ty9LHNH77Bpp9PO4Mpf5H8IMX113D/+bpU\nT5MLoyRlQZzSC05NRDwAA7XxAbjBCSPc198g87EcjhpN8H/1+xonNs6zqnOe\npqvi5y+dmr1Uz8nBAwGLhFjpkg6aUD+oX1mCvbWsuLiSQfzyB/M/KIyL2yMm\nouRyYG2Ur9yvZBU2erYCxxylDDU1Fw/kQCg8BGtD4bAcrQzart+nvJM/gTb/\nu46y\r\n=NJg/\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8da05ca0d2e19f2c91d06b0c5a5d420edbb29ec2","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.207","@balena/jellyfish-metrics":"0.1.21","@balena/jellyfish-environment":"^2.3.25"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.80","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.0-5508-pre-build-versions-8da05ca0d2e19f2c91d06b0c5a5d420edbb29ec2_1611249548642_0.6410681783240657","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.1.3":{"name":"@balena/jellyfish-core","version":"1.1.3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.1.3","maintainers":[{"name":"srlowe","email":"sl@atiklabs.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0e3e7bbcbe865156596aa54b6f1849d2b19c54fa","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.1.3.tgz","fileCount":95,"integrity":"sha512-ZalAmEQEiGj/TUD1CpPHh7xdGdXWHpa8k0AvsF0j7SiBaftdAOChm2rdQ//6BhrFckQYoAqqtnmpnAO+x7ohRQ==","signatures":[{"sig":"MEYCIQCT0Av3WAAZOKrG12I6xU4xmm75vPS6x4eJa0JHqsKR4wIhALKQTE8yczIalOwEo0pJYysF0Pl1BYROBgXet3MjRc1O","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":588510,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgCiTfCRA9TVsSAnZWagAAIlIP/2FUf7nIopbQr3/RBMkH\nwz6HZ9GRZ8QspEyTd2Z+9k1+K0CtxCWeJlV4phNICy0+5zPmOHcxNShcv8zh\nF4LWX6UGFbXxh+h+pQjwKAXa7wPNpyb9xWFLJ0uzFtbX6CGqJuE7xcrzuh5/\nlFtrVNfcSy4luBmky07tnJaWY73Ljuaju4ytdLXFUFcR7OTxdpAaoZ4WrNfm\n2GLaT3V3rHs4Jqh51LAcJifQ6MTwTNz6QXFviGyT0C8vNivzCx7zP2Ej0hVm\nFbrE8/7Or84LkITpHl2g5dfteOaHHLiTkyrdlRVDIacIL4E499XAJ1nS0tSt\nDp+vPg4d2yImbBbVkQeq+GH5y6wturjhZKDMvPObBc5uBOgyOdax6FHnq/i4\ns8nkclR7+EaaDi2pzkMiuJt+WfUBn1iGzWfvmUiVbvt0UMkizXhBOff+NcQv\n9Y/HWy8sAotLHYD4tY5/Z/vzBIsI6Od4eTmE5m7qc0ytJQIFGKAXTUACVEjm\ndxiMreR5VeHysAp7d9ch8HEpLR3dz3ZwaJONHPFmKCvPc+v+v+TpxlAOhisr\nZhgKx6z7QJzrgzH4is+W2Yz/OITkQkxqBQ1PoeWVp6qboZ2f6oO9J7b+xCIP\n4BYJ+LQK3+nVahagtTldrMZ7ryDpGcSF4xn4lHCUHV1yhCD63MyGki/P5O4v\nzQyB\r\n=JCji\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"47f1bfc6fdba55b3a974c16b2c47d62858e74b8a","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.208","@balena/jellyfish-metrics":"0.1.22","@balena/jellyfish-environment":"^2.3.25"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.80","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.1.3_1611277534789_0.7905615938424866","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.0-use-ui-explicit-in-ui-schema-7e8b94935335dd69efa5018b2112b7d259464522":{"name":"@balena/jellyfish-core","version":"1.2.0-use-ui-explicit-in-ui-schema-7e8b94935335dd69efa5018b2112b7d259464522","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.0-use-ui-explicit-in-ui-schema-7e8b94935335dd69efa5018b2112b7d259464522","maintainers":[{"name":"srlowe","email":"sl@atiklabs.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"42e04cf0bef099da1f285f32ad619c151e27507b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.0-use-ui-explicit-in-ui-schema-7e8b94935335dd69efa5018b2112b7d259464522.tgz","fileCount":95,"integrity":"sha512-b2UKwHbrFORnz4Xhx2B8/X+sFm6q2qpI4G2ET9VCsrWjZvPLA1tthkWwrOLrO6lJ8hjswbGMlMYh6qF+INOM6Q==","signatures":[{"sig":"MEYCIQCa+eCslMxq823gypY6dRXeX1HpOs01KfD0258Qv/F3XAIhAJMwCNc9k4NfVoufxirVFKadtFPyyckdW6vpF2ubhJCi","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":588695,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgCkp0CRA9TVsSAnZWagAAkoQP/jq4JWuYkg4+VE4MORO2\nr4SxqsiBxniystqcwBaOtW59cMpgYLv147p/JbnZzB4NvRj5TySqFZ2recSw\nTkWIqzMgcHouvu7pwW3h280K1ebYb1RUsQhHfNM0PBT2PEAbr/yJlvmxbTiz\nSCSXhlmWmzUdfbXSB+bhmapzl8unQfo2viIHAdyuBHqpK+zaVFP9mcz6GoG7\nQAroPRrLB3viygmCnHl/srvGA7RKSGVPJokgNNejyp1UktKu7sGx1szM8qb2\n/bblF8LT442hQ6nequaEbRE3ER74ywDs1Wuul5wZuqAKwN03RC31Qdm4D/C3\nlEz+UweD3OVNXTfQ2N1/JimiOdHMF4spPV8oztARoIjZEorYunlzjuPEX7v6\nxANaoT7GwFUo2pa9AQ5DbnYWkgqV0X4dKXHbJ7oQXkfoz8oDGxRkv+0QCkay\nrJjUSdlO7qwhIYFiciaa0koP2UF2KcB+DlPY0VDhnsyuvNHrVsBxo/Idl4Sh\nhzc4BpNbTvlHs+j8mzXJPaF9dKhiiM+XCMOKd08LM9Lt6O9m+ym/xA+jF4Jq\nubCDCzkwjFUd4hS9tmfkMPGcvE1JPTL49Iz97mo9lfGuTbgGYIyoj3FdYLh9\nqgKXwMRl9hxn6tOvt1lPR4ULc4yMf9/8Ux6RlMnBcI/ASJVhNxbqjKE6FJuW\nXGXT\r\n=2GJM\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7e8b94935335dd69efa5018b2112b7d259464522","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.208","@balena/jellyfish-metrics":"0.1.22","@balena/jellyfish-environment":"^2.3.25"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.80","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.0-use-ui-explicit-in-ui-schema-7e8b94935335dd69efa5018b2112b7d259464522_1611287155818_0.5300274902418842","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.0-use-ui-explicit-in-ui-schema-944b88d55357af1202a81ed409fd562d5d964c5a":{"name":"@balena/jellyfish-core","version":"1.2.0-use-ui-explicit-in-ui-schema-944b88d55357af1202a81ed409fd562d5d964c5a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.0-use-ui-explicit-in-ui-schema-944b88d55357af1202a81ed409fd562d5d964c5a","maintainers":[{"name":"srlowe","email":"sl@atiklabs.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"57f6eba526f47e448761433b9eb208ae8bfd24e6","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.0-use-ui-explicit-in-ui-schema-944b88d55357af1202a81ed409fd562d5d964c5a.tgz","fileCount":95,"integrity":"sha512-YSLhqPVtU9LRDscYDkY3B2Kth79vC8rblu4TqwTDJwJoN+k2rsNul1U+YbOmeJPV8X9mieL1JvBF68B4qDegJA==","signatures":[{"sig":"MEYCIQDQQnOQ3ctnXF27uAoAcjZZzT+UHY98VS+vQW8wqWERnwIhAJRZGUjD70ND8ndYn83lm23dODzbBNj+JfnugkQ4L+cR","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":588718,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgCktYCRA9TVsSAnZWagAALIUP/3q1Dkz1fErJvux4W+i8\ngAVEb4aZsvgJxyf5MDy227nVEH3FqHHFlD85whAC4ltTwQoR8Askfe/ll6ZX\ncSqHT15uK3M4HCzgo021dBpvWEfkWct58Lf97bJ9Y/cYefdOb8+GuaGMZO9J\nstN0WncufTMwnxJ3A11AqI56iION+9DrOvDnwbSsxYP5Lx+exw+gg0ydoJG5\nS+eNDD//dIS5mCO6ck2beBsPLbUmTm8FMuZgVycweu7yAAg/mShLZrsUXn2r\nZpCLHHpbcj1Sl7yuxwnvo8yThhxVhNv8mhlExcZA8QreQwIhuPPNBmYGdH98\nRGg93+QAYU2EiXGZoRmxmsVUCu+BdsSHlgFc5s3X3PggLdOA/tHfWU4OLQy9\nrvLcOObssvd1S8qAO/gRDxRvuaV3j9O8I/4ZFN551/IY4/D7ygJ2a35o05b/\n7UfaeormuQjIVBYtBpFPQbBeoJizxfhoO+etrh0tTcoS55Ej5TwIN85eVbWh\n7Wi+W0FNSvvAQ3wIgjLURvWFiMcqfEwznyOiv+4tDBj7XZLf51Kb9ozsEaEw\nzLlkOjC4CGs8cxQcGa44PMFP9fwb3kfcEiI97FVoXmuaTdxbN7Uo2lC0QNUc\nmvaEhoi2d2uiMM3hIKAgtCGFgb9nrJ+7MnvDWRua+DC8zCfRlD9m8MyhKqbB\n0L07\r\n=4lm7\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"944b88d55357af1202a81ed409fd562d5d964c5a","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.208","@balena/jellyfish-metrics":"0.1.22","@balena/jellyfish-environment":"^2.3.25"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.80","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.0-use-ui-explicit-in-ui-schema-944b88d55357af1202a81ed409fd562d5d964c5a_1611287384308_0.16365237759363627","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.0":{"name":"@balena/jellyfish-core","version":"1.2.0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.0","maintainers":[{"name":"srlowe","email":"sl@atiklabs.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"937ace9b290ad2990033b3d0f16bffd18d1bf97a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.0.tgz","fileCount":95,"integrity":"sha512-ckhFrS0WSSwTv8BMc7xzZxIfDGVUVtNaOFjWj7MGmxc28wpShPHhMZaWEioLP8j6ahib9MFQd7axufeZ1zMfdQ==","signatures":[{"sig":"MEUCIA6HCmHfTMJBzD9vpXi3qbDSnP6ulROVULUi8u6wUG+HAiEAyGRyivTx4qytRTS9GGr3CFPQ//AFLGi6FUlTxHkPEp0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":588648,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgCk0RCRA9TVsSAnZWagAA7dkP/3d9moyUYEUPfqkraUzW\nTOgTeYxp4jhiCrF3bN+e2+WFZcssslfJg+9p/LGX4IbvNEXKJK4wRMHW0lRt\nNsdCqQkdPcTEhkJVyrZqeHK58SqaHrkJKiee2mMi4S3vL6kYR6x28R5LA2BP\nWebkpUc6sHP+tEZV7hWtej6APBLLL3/Jpd6qU+vJqw69Hgps7UbPOiX64HL/\nAcCH93E0glsQOF3phxcs4DL3zsC0lBcfpzJx9PLfBSt2uojiCM76wITypZvs\nV1CTH8IzYWHLH5VxEoJO6eLoa8ZxRWeX9Zw1emN8UJzBj3LOFoxqiu6z+Fxb\nIZi94QUSV/6i0h8bghbgmxl6jOAgC2pNmzlZJGzmkAGYuq2dI/2knn7mqlSi\npr8SoHGJKjentO7M0RB1cWngCl6qE3CBuvQWo3vpzaFc4eRM14ROWqc4VDgF\nZrX4fxp9yhGk5cja59+qV/dXg13dBCrv8zicCIUpwFTY3YLoHZqyyPlntmR3\n7dBecGyQw+pKHtpiubds+HigNRlAJ0x0TDdYC4zexnWEHgkBLnRGF5kH2KUy\nDvzQLgjzpk3QeiSiMzi+MI0ltejzYh9HuDYh6xdzru10KtcfEhYek3B0OLcZ\nVZH+zUtfs3t6GzPARoY2fbU0HZhnkREGXVT/aMXSvfsmtjvQCNCNxV63sUOx\ngW5O\r\n=qp0D\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"75580965be46a6baa73001f0099307ecf73a796b","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.208","@balena/jellyfish-metrics":"0.1.22","@balena/jellyfish-environment":"^2.3.25"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.80","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.0_1611287824608_0.45424279661187006","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.0-5508-pre-build-versions-7944f166d5e64b27a093486600eb5f9f8f8c3333":{"name":"@balena/jellyfish-core","version":"1.2.0-5508-pre-build-versions-7944f166d5e64b27a093486600eb5f9f8f8c3333","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.0-5508-pre-build-versions-7944f166d5e64b27a093486600eb5f9f8f8c3333","maintainers":[{"name":"srlowe","email":"sl@atiklabs.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ff4b44a4c21c4ff6257436c8c166da3209f4bc7a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.0-5508-pre-build-versions-7944f166d5e64b27a093486600eb5f9f8f8c3333.tgz","fileCount":95,"integrity":"sha512-H5fZzo3Atoa8hjgEdnomYA/2b6hb6S16zH/AK0NORZ/iEwXcRQTTLB7ou8IDLYPjQTXV15LsLuSf+tvUdmDOBw==","signatures":[{"sig":"MEYCIQC4oXmQnwroTn97cn+1illKBpQhPfpvr9QbdSlXHMhypAIhALIswf6UmQVM7nU0cFuHCqy4e1zLG7GucPHuqkY3ifFr","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":591483,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgCooWCRA9TVsSAnZWagAAvrcQAJK0/aLC1vbNKum71+Cu\nZM3T5+ssNS6WrmxD0vauTmUJYaNbmQBzMv0b4ejOMDymP+mFtWd69huIgKTJ\ni/xREINxePBb2XFfkw+YE/1ijMYN0AIiA+VJIWgNCkVbDt+JQ+jbHB2A9QF3\nA5+0x0+SXDkyWW5r2jhd0H8DRr/vk0fNfa2WYqykfwGq2Qye8UPfqHPzJ7OA\nfwLg8hwvaURDR3dGyrM3zmGcZRJB8KQjt8xxAxkMYzgq92RxZBpDvQ0ja6e+\nw+JXZMw3XM/8BD7CATAe5k569t0Qjxs6BzJMuWoNFogg4t4dz7uHHrjmZ2Gk\n5le6SA1EGq3RJ5HiFVeWt5b4HsGUounMyK9YUV85xhY7tJXzU5el9+a7trZQ\n+b2Jya3H3uzgtEUNtEIRLwnL3GCgw4LneVaU+yQ8MmGhNPIe58QIO/57wzYd\n5v+LNL7rHFRBXqDw2VAbgYaZ4xIsH9phc6H9E29nQ1h1eSkQcKS5ggEXzO+o\nZOBLAFD7kuRPdMPpNUBzmsT+dvn4TSQp2Lm++GT7husLzYeIBcIIOXSNWQGq\n5FuEgv2L+zVqtzPdjsojfgHVjuvDDl4gPQfjmyNFbEOmdQC2D16Qhe5nYcqL\n6fzNtghnfFlTRVnyJOrbtNveQ0iQ0ZmEC3VXLVx0hvdeKrVE7czaEbnEswu2\n3qs2\r\n=Vouq\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7944f166d5e64b27a093486600eb5f9f8f8c3333","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.207","@balena/jellyfish-metrics":"0.1.21","@balena/jellyfish-environment":"^2.3.25"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.80","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.0-5508-pre-build-versions-7944f166d5e64b27a093486600eb5f9f8f8c3333_1611303445908_0.6983837189595554","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.1-renovate-non-major-efa8f3053a2b3c135b013235ea637277e8a95731":{"name":"@balena/jellyfish-core","version":"1.2.1-renovate-non-major-efa8f3053a2b3c135b013235ea637277e8a95731","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.1-renovate-non-major-efa8f3053a2b3c135b013235ea637277e8a95731","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e699e32926893cf0f102d7ce7bc83e2df804b3fa","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.1-renovate-non-major-efa8f3053a2b3c135b013235ea637277e8a95731.tgz","fileCount":95,"integrity":"sha512-5t7DfpUnbEpv04Vux/WMdnWsO8YAsBx4EU2ImfkKGVmWP19faxQPEbThioWaVS4Y8ICI1E65bOQBQidoUCoO8Q==","signatures":[{"sig":"MEQCIAgT4wR4n69pimxxb9OokA1eYtJ9/bVkSizI8krrWrvFAiBjFJpGglWmsNU0r3qfhHu1/GiduTj1i+MpvYJsAeFtvA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":588773,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgC6ceCRA9TVsSAnZWagAAIUYP/A2wBHRMTaU1MgOmYNND\nMwC7L1vdGeF017tDpeg5xs5qLYWvYg0MjRDSlv5RcJFLpjVddTE1RDVXL4hG\nHWD8VzLVdMjogB2OoOwsfsKCkv0Yl4GeiqXYTCZWBQ9JhbBFqcfQlmkw3aWm\nvowxnF7U/t32tOpdursJ1AkYf6ITBtT89bvAbWZR0WTRR9LXmRrCCpiH2ba0\n4AHpiNbFojFetB1473qDIh00z81GoKug4zTIeZlDVoGZc5qsKswYUI9V/w6e\nL8VgtpJyVAU4yl4Nd7H2IFX4GKEHhw5VFHH7zXvtlpeW7x1hl4ml6vdxkKtl\n+yebw3l0EZETW1HNv8Td4s7cuCAeTrKIwZbc5mKPWmx2Oyd0hqhkp+uyANvH\n5YwQSTRS1KoWH/SmPhjzx8EMAvEAdq9R0pC44PMhKy1fIosYhareqTDtTnM0\nziUvswOuv2o2nSAkGlsr1/hdIFNVud3pIOJOhnLyxsmacBAPWxqPHgT9HS5m\n3MKKp/D9sbNug0OiqANI+Czren8FWfaIN3E5q0G+pACrrvze67kr0bKVJj9S\nDYEaZXkGI/ZOQsIkZKnsD71qegrMbLOTIByU/Ys5euyXsaifLH4B7nk9ABqP\nIX/lL1vQlKpJo9AQYcH/Kqh32EqtsNpuaCXk5ieUepGpawigBwIbYR+s2l0z\nO41U\r\n=jmrC\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"efa8f3053a2b3c135b013235ea637277e8a95731","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.209","@balena/jellyfish-metrics":"0.1.22","@balena/jellyfish-environment":"^2.3.26"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.1.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.81","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.1-renovate-non-major-efa8f3053a2b3c135b013235ea637277e8a95731_1611376413541_0.6985618187342977","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.1":{"name":"@balena/jellyfish-core","version":"1.2.1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"94cf9c534b7f057faa6ece462a21a0251fbba53f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.1.tgz","fileCount":95,"integrity":"sha512-6zpc2aTcj0G+CONZhBFRtl9LFBNYi60/j6aUUm0ANWwJqfusbaFWnxmcubIwx/99oNuRGefu4ExW3mVsdOFDTQ==","signatures":[{"sig":"MEQCIAP+aFKmgd6cDzroYgm75tt1r5nbfcEhWnzjLTX2K47uAiANuBYYrL+8CDb9dFmifaDIEllkM4mCIFmqbflFG0sonQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":588713,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgC6hICRA9TVsSAnZWagAAey0P/1FC1b9ieUdHaLwxP515\n/UXwbaCypKkZhLAJzERZxzcWoeEGD9GjSjdOm89Rjwm5He/9WVESUf8dGkip\nlV+aB4DlBPPFb1x1iEk07tzhoiKXrkVx1WSqcd3ADt2RLhHbco09qZREewlF\nxzxeygymIoE1STNaNhl8i2sEuQIYSkdNzHIqE09vg19d8lnJIGeYrSdwS9y7\n0NqFop6UxmyDHD5lg4FLR5aptlL3UapkvIFoAZ5KWCPZ+/hMIprl45YeVU9a\nrsmmZU6JLtRitfwXmcwtjTG8bni3yaawY9jiS3KtcqutTTXJkt2MYoKWooaj\nzIdbMNu3uqQQ0aiFXKWLxCvhWTsNJa0Ek77nNjziYayKL0eyApNfko24zyKD\nNaKOjQ0z95cxv6HZ0p0RQnUP/Bm6LGSVqihW9Yl9Zr1+RP3Qbf3LEgZpXwSa\nKsZl1ecpYXR68mff0pAVnwNRcmxbsYrpbUsp1xH64qrz4dRouRwGo/spsYwA\nNO5UEPYlSGKNke8mSrmafXCe4fownPOm09pSukQUeoqtYhfDqOJ7gIYP/iP9\nbgnNVVUySdnkhhuvB/HcLf/puhu54wenq5l1RJlfO//BMcWOklzjEm0fXGLp\nl5dAj6SMioWF2Wxz3uUhqYKon4TPEtWsEVY/2ftxEWzAmCgO6Iv4t77UcAY3\nHtCo\r\n=/qLN\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d70d0edcea260200315cae6699f32b845818c69e","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.209","@balena/jellyfish-metrics":"0.1.22","@balena/jellyfish-environment":"^2.3.26"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.1.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.81","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.1_1611376711514_0.6658151623262756","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.2-renovate-non-major-1cace351ce631c1d4b1b8aae2250f352f943fb90":{"name":"@balena/jellyfish-core","version":"1.2.2-renovate-non-major-1cace351ce631c1d4b1b8aae2250f352f943fb90","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.2-renovate-non-major-1cace351ce631c1d4b1b8aae2250f352f943fb90","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"fdfa3042a267b8b26e086a497b0371df4a28c3fa","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.2-renovate-non-major-1cace351ce631c1d4b1b8aae2250f352f943fb90.tgz","fileCount":95,"integrity":"sha512-S7nTdDBvLwl5LBXbVP88+i0n8VgAHNBY7im2jC82QZtcn28xy/LdlfCHEjtRlA7zUO7lUMOFZO3xseRNKGD72w==","signatures":[{"sig":"MEQCIAMhW5WGEBUPc3yVjleQIXuFfoPQwgp7TXkglxrwmm3bAiAh0VyaTMOFvdct+kosuvPq7mDd9tTPdh+c/C03D+uZKw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":588838,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgC7vdCRA9TVsSAnZWagAAvyQP/0mup/Nv1EroqZTLXlcj\nHOODbjc1jalWt1YnggQq/PFNZ0W9J9KD5TuntAo8HDJE6Xff8jVmPy7JFIkp\n6/hQ2bsVFHciwigLzeWilB+diIebXoGi4hDDpqyJK3sxF4jWZg70Gha3gPV3\ngH8z7tNITQttbUdgjekk9QjOYnTVcoTCGwVi5LmJ48556gJw6E+hNMgGunNg\nYnx2wYQtLHE0AEvj40bPhyLEam8F2dajhxnxn2h/bDqTxCkC2FzhaRJ35T2R\noQgyU4NZsh6hwIGg9Ay7dKc80ObiiBL/zbHDbP9kuKexRhyQzc8VF6T3s8QK\nzU1+edVTbJHABtcmrN8fjhodqQ1624bjou0oadOBBICoXIobdRr/SErNr21Z\nDInAJAeDxt06W1pTbp9wbXgnTSz0XGBybOucBi4fk51zX2SglImfcCT9tDPy\nTe+uI0VZlGUa8ZnO+8xF7DBR4p6vduZynbsWfATnUdwygBwTlr06aIAim3vG\nT6AEb4cdxW1oAPXxGtoeIHvXbYn3wBEPhisJ+Sdl1ajT40rTvXN4+5qrD/uC\nKTaeUe/PpjiWc4CR7flrU8qnVTnmP00F/VFiTEYgOXKVvYc8Ll9YaLLnC5MA\nRYSu9NYKz0uLGJ6GSJCU4EtV81UXbO1l8bnUlyjWGnc3dmTykFsFfA1AOGoY\nul/I\r\n=v16s\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"1cace351ce631c1d4b1b8aae2250f352f943fb90","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.210","@balena/jellyfish-metrics":"0.1.23","@balena/jellyfish-environment":"^2.3.26"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.81","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.2-renovate-non-major-1cace351ce631c1d4b1b8aae2250f352f943fb90_1611381724505_0.09433971255951112","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.2":{"name":"@balena/jellyfish-core","version":"1.2.2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.2","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ff6abbbe0f6e9e63dec990fc7a377ecebde3aee8","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.2.tgz","fileCount":95,"integrity":"sha512-tm6DSqMfE5oZM+7C1fl+yPiJAWJ8mgYx1h2mfxW0hnclwZ7yNvhVfYyeu6GVJKMXolFerump1On8U7hrqQoWYQ==","signatures":[{"sig":"MEUCIQD68xzyQcMctjBrJIzpPxk5MB6uoS1ACPTgWyL1CT8lYgIgB5OG99Z8cst4yfq3qKAsqdIIsGlbKX1wzBVsmaBXJQ8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":588778,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgC70QCRA9TVsSAnZWagAAJmUP/15gaLxY4kZjxNy+17DA\nr7YQacFo1+ho/+bxAPsRsiV/INaj9rSiyvf7SvkmuJE01IjcxuvUTc8riFNu\nUXcUHniC1ScQDaDmAsgxpB/JrTjpHU01SnIILSOMgtCf3kHyXZeejGWqhHSl\n0K0pLOcGx3xdqkJBUW7/K78MBbIik3qY5GMqz8OljwUFoHiAmNLnkPJ62oNV\nGPORaFS6bApkU+T19FuE+wMc/i0F1OB9N5LwW+G0wAWrgIdystLnzZTJC+dj\nYefN43zTMWgHZ8FyuP/zc73b7N/EDwUf/9hZ5h83OlG/Qpw0izlQyimvWZst\ngD76Fn5H5bj3aOIbDPbqx6+aXuoaUzOOOSenpOFHzHAuGaVDUZ04D64JyZk+\n9Otnkq+3njRxKsQ+3wYem8v5taMlV+Ed+khMe4ZbfnNoSYTb5C8G6s1HKOkd\nPui9RUilRcDCUcjOrhAAG7j4TNtmm0+++UXOPg3T8Sy/gukqUdlZrIOejQBQ\npmyKhLR7VZVuLnfQlLpKfy0gijZSWjvRImw/OqLQoEaznK4BgFUd6c+DHxe0\nHhRhMFoHRjo45p7xTNJFqwUh7FQcb+hWFLAwFEcsqSB/UKuQgXEizBZdM0iJ\nHSbyDRyDi600M4P/Y7bjl/oy4fjbYnkVQ9z3Zyh+UD+eCK4zLi1LvonILgTR\naZQC\r\n=vv1r\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"47209715a4ef5818b38e4749015bed03b02859c9","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.210","@balena/jellyfish-metrics":"0.1.23","@balena/jellyfish-environment":"^2.3.26"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.81","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.2_1611382032330_0.14496935751892237","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.3-renovate-non-major-a08133d1939395559e2c52643b730580ace491c6":{"name":"@balena/jellyfish-core","version":"1.2.3-renovate-non-major-a08133d1939395559e2c52643b730580ace491c6","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.3-renovate-non-major-a08133d1939395559e2c52643b730580ace491c6","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1a7155fd6baa744ae19db4367e68c837e52c7dd1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.3-renovate-non-major-a08133d1939395559e2c52643b730580ace491c6.tgz","fileCount":95,"integrity":"sha512-fjhWKQbdPD7+vdiFinCosVPeaRV5ILo7OAxfyzFU5wSz2yJzlZ1ZskrmnxpYthDtS91qVxsnTZtq2RfOjMnrHA==","signatures":[{"sig":"MEUCIQDzf1StWXBbInZag+R15OF0lu5nV/Sr/d73cQuf8yFvqQIgAhgQ25C3oDUegbeJtyZdAXmH+7BsvQOkfObpsOtcNCA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":588903,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgC81xCRA9TVsSAnZWagAAS4cQAJtQINWBrj9/akIuMLqS\nsM+Kj740DDJysVQFlx3n0glhQjf/Pp+0wdvnPwTl+/K+kiDRsRt4S1ufdX5g\nul1LvyVLtgfDxTyrJSsWBEihgdwKX631DCLkpCQ7WFWiADq3lbMB7bkv5Ehl\n/GKTCchqwcAb//EFEPU02kJNUIoTEzp/dvImPk+LQpuD8Wp935TAj7WuuSVU\nDsF0bc/dAklg/WOj6fKxCSDPujzVyzxUxP6cWEy4310IxS78P4VnO0YC2sXx\nzm4aVKDfzjwdPot94OjDhd8t3B0Yl4WC5Cl/Gu+yQmWoUUz1JJXHOE4PW3QL\nB9T7eIvJu1IPYxIpykVtpyN0Md0uBKaimVHY5aFpjZPLWmKwamClWTIN3YuU\nAxvQueJs3k+zJ7QYa34Z1nqzlezCQjX0NJdBsbj6wZpqCC2Pqn786D5RGWY1\nguGi4RGgBFnSm+Zf4187gLSEeslXJutYRH0uT8AQSMdmmRnmYQUv+Nk20FIp\n2K/j2I1HxuZ+FUcfvU6LbfQoRsu2TY3smkdFK01n/epSrLMRVwfxKfQRfWDG\n0IdeSYIce5x0CBSUvbYGIzQuVfqTKfNLEMzcajFdMqclZ/EgOSTf+25VtSC5\nhrGUD7tNlkdblzMuKwaexc85Hayo8eigNWRnPT279KQZMlbUo7myPxFRIeeA\nn5Ni\r\n=l0+Q\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a08133d1939395559e2c52643b730580ace491c6","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.210","@balena/jellyfish-metrics":"0.1.24","@balena/jellyfish-environment":"^2.3.27"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.2.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.82","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.3-renovate-non-major-a08133d1939395559e2c52643b730580ace491c6_1611386224774_0.39793340049940573","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.3":{"name":"@balena/jellyfish-core","version":"1.2.3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.3","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"80c9aacac7c59994e8d458b4ae8dd8f1296c2ca0","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.3.tgz","fileCount":95,"integrity":"sha512-XoRK7kMoN4RjIiTqVnh3NnlrVSfzQ8a3EVIIqjlMrR+KorJ2Iu3XvVDAC4RNP+iUjOMdu+raEONeMtipj5DeEA==","signatures":[{"sig":"MEYCIQDFUvKnp/O4giJQLi8no9/TEpG1Nn/xXG8Itrg4I8dUzQIhANgTCU7mWmJ59Y7W02iSEAnuhCXF63S1KXsZ2yAaI5OS","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":588843,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgC86yCRA9TVsSAnZWagAAMegQAIl4JcnS+n7Bgkc+PVdX\nFogsu0uP6UGKxUzFdi9jwKzmgniSa9WmhETWJgTcKHvtuiRoNId26Y6zLI9s\nSJunjE6V0q9AnxD+rhLijq+yZAGvd6KB5fgUIO83q3Vpmhz5WeSI1+5bhNY2\nf7hxlfdQppOgJFZeYiPjOVGQXGtHjKqfK37Xy13xZpmEsp8U3T3Nt++Ef+DT\niKnAtn/bQ5Rq6vNr7RCPUVYi6TgFN41M8enx7VRlB02QPzowugEFQSVR92Xu\nZXiUC7+htXSfhAufaBqLkhnJL07Q1lvu8QBHQnlMxwVXsuhqUuJksX4m9S3X\nOfAtFh81rI6jr7os6Eqd8aP97BgV+oLL86qnsAz4Koz2SVLHSHwZQN8JmktH\niLJWWHKgH1WUBUxoNCj0ip+dviu/+wFyqPJAzd1e4gXladWVr15AwN1zM5jU\nCnP+BhbJ1RLZNQFvo+2fMS1a6ZvmUsIKAFUoFuVrnA/K9JSnHzyOD2pnx4oO\neOW85AAj5qwwZLcJiRBzNT/foZn3/+erDwX+fY3qs0QT4Fc84ef06qLT7JW1\nHQQWVGtQXLihSbzk1KkPQyiJLPdNRQbaB44WBvk6WvLJ6ceb3V2ZmPiVFDdX\n2aJLH9v0qJoSo36f+xSwlvrqXJZIohXTLZUfpbtFpsDMsJxXVOkG0OEbcTCE\ndbNv\r\n=/lKI\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"76fd7457b002698ca88c4038cbec9b223c8b30ff","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.210","@balena/jellyfish-metrics":"0.1.24","@balena/jellyfish-environment":"^2.3.27"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.2.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.82","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.3_1611386546211_0.05851906312216015","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.4-renovate-non-major-1a2d159daac8fb8d2d204fbf221a4f409a921434":{"name":"@balena/jellyfish-core","version":"1.2.4-renovate-non-major-1a2d159daac8fb8d2d204fbf221a4f409a921434","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.4-renovate-non-major-1a2d159daac8fb8d2d204fbf221a4f409a921434","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"df77ea7d6aab8b729c22de8be1bb6a573d64e98f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.4-renovate-non-major-1a2d159daac8fb8d2d204fbf221a4f409a921434.tgz","fileCount":95,"integrity":"sha512-PYx8ATy8bQjrzrgkcOG+a1ucyk3UvuL9tHrBRRBvAjRW0/p4Io5nuWoC209Tg+0edcLFZAz8VLgbNMNpsLEzHw==","signatures":[{"sig":"MEUCIQDND7qpOy2gtN2g9cMTcJgK1fzHj76Uuz2D6s2Ox/z3vgIgClfXPYowyMlQrX7Mk9+dKiNPHm8Ca+8h9JKyMcdAQ/A=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":588968,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgC+yCCRA9TVsSAnZWagAAlSoP+gNIruoG3aEO5KqZIxeC\nBTDyL9gecd2vNe6YFzBv784FRAwx5J7fwG/7XMNopYB23tBnSbwcZwRctYk8\nq5RwILNiEs0ByDS0sIIlyD1rNPVWQoMOWycP43xGxYoQbBlNPHfKa8obe+4m\nMTGCsM0ArGdbEURX7UCSU1s+xnZWAt3/hYB4Dzmc3VN7+lnhUz6xHFX60hc4\nO8JGJBd5GJR/BgifREdfZIoJspHn961d7aojXJHKqyABZar46U6GKOQErCeS\nu1CBZroKbtQ99bFChMjcncO72Q+oX2edbtygy+Bc1iiUJYm3jhq3Ug5XC3bh\nF4yiif9J8Ozr7s6cONnBiab+y3jyAFh1ty/Irrvi2oFagWV/lp6ophiCmEZZ\nqy7dfCT6LddgKGuNFcIFsYUvcStoEGBOGBZgT1SY2kuDNKhQWGcPtzZ0y6oa\nVLssz33M1mH7sVHxP5I3Pbkq6m0au7PERIgagPEwOQ6OjwjfUCyAjjkpCDR3\nHAdO5b+63lrCr2YgUMY981cGDFhsaMcSYCi3KgjuQwj2OizJm6StzmHpdGO4\n0y8g4AxHD3pVSKzdRjgTKNlg72aRTLwqNYTXGg1uMUkIEXVwuDBBsSsOLc4F\n7gTfzPKaPjgJXyrf/y/ILdymNzLJ5PUVrTUANk4hI2aTZ8eEqfPZK8/11yez\nlg5D\r\n=ZlNQ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"1a2d159daac8fb8d2d204fbf221a4f409a921434","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.211","@balena/jellyfish-metrics":"0.1.26","@balena/jellyfish-environment":"^2.3.27"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.2.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.83","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.4-renovate-non-major-1a2d159daac8fb8d2d204fbf221a4f409a921434_1611394178054_0.5441194289426226","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.4":{"name":"@balena/jellyfish-core","version":"1.2.4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.4","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"aab891004be5ada04cceb82fbd70aece54234d4e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.4.tgz","fileCount":95,"integrity":"sha512-0QStV6/5TIK1VTQJqYjcKBryt7dAlJOfk/u64oBegtpJrLdnDynkkvCAmmHwg/mzGlzgK7Wcn02hLNY3mmxFSA==","signatures":[{"sig":"MEYCIQD6wk1ou4oVMr9fGFVS/JFCZOKqL8QfibSF0/9uW8kV6wIhAIQ/UH8vY4HTmIPcXzwNt24QuhmS0k4w8e7AqyEFK6jD","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":588908,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgC+3JCRA9TVsSAnZWagAA1RwP/i3OjACf6o2dsSfYgmQa\neELi7te7ZxHxTDi6i6Aqoq+Db7BNMmwZiinhvrk21qBkJp1QHq2CRt2TGOtD\nZ/fBVq+H07lvfDdZcpeEe8UN0YRUdbN9tHUTMioXu2F/4N9XEvEvrJGHjes/\nH97qNoDkFCUwh7t6tvzfBkH63vNRVVAI545k7SPcVqxkAfvpnf48Fwxq/5JC\nrv70vnk/dSArY719IEfqffGYkyZpSlhOZ33263u8abDhDGdQeigfl9juRNoN\nHNY4ioFkSBuRUPwKy0aQoPnnAXtlOV174+kEcsSiwy3UfMC0c6DHYUpJp0ox\nJbhbMhFdRyNF0qKvqPLIyjpeGVda5ZgojzL2sGN6qUuUEQpdUe/tVJtCKXd2\nkTAjfvT3440ZcwBQYWnDLXK26eiBA86TLral3Nw6G8kNangDgwR3c0dwkEtt\nIIBVlZenShcZgShDN8BkxlY2+r4GG4a0qnPGuxV28jpsMoRvs9HE4MIBZ5Ns\nD4Zm84mj8/qbG4tY83R0R4t3L0K9h7AdN7hYxtm35XjL3LDoVouE599v+Bn/\n82bcBCQ3OLDBL+MFdWfzihp7Xz/ErD7xLVDFFe+zatUSvHCgq9khc/tpT++M\nv7m1j51q5cs7SgGoF0+MOcg3FBaPnL/vI9v3u+8HnUqGFdN88Hvpdq+0rtyC\ng8qK\r\n=1CG3\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c3c6e3ffd7f62f043634098a78c9c48256ed0f4f","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.211","@balena/jellyfish-metrics":"0.1.26","@balena/jellyfish-environment":"^2.3.27"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.2.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.83","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.4_1611394505120_0.32086829243832615","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.5-renovate-non-major-320f54b092d4dc07e565e1f3fe8aa7dedda0f9d7":{"name":"@balena/jellyfish-core","version":"1.2.5-renovate-non-major-320f54b092d4dc07e565e1f3fe8aa7dedda0f9d7","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.5-renovate-non-major-320f54b092d4dc07e565e1f3fe8aa7dedda0f9d7","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9d8b890e8b9d9672489657ca203c2a6fbba7765e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.5-renovate-non-major-320f54b092d4dc07e565e1f3fe8aa7dedda0f9d7.tgz","fileCount":95,"integrity":"sha512-IZOx3beFv9Suo/Um3Gx8mMT4g8BMoxsrLSAwGncTr1oh2LuC0y6k0ySmMDU3MuYYCNn/2+LBwmj8CcXiPM3frw==","signatures":[{"sig":"MEQCIC7PXtr54l+MKIwUEoQtkF/pJpE6XJ/74WAqvvT9iXHoAiA6CJ5MU14P5GZuCLHYExfzaytBhHzaoU6wQiBzrCC4nw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":589033,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgDElmCRA9TVsSAnZWagAABJQP/3XL1VexDH5l+cyH5GdI\nn+wpXUazIXFpW5dXH29YybL+76+q8xfDJTNG9KV0SIIufYLB63TW51F145oY\nIj/c83+qLZ3b2//wWCmAJKYhlAb+uoRW+mWrc3XmFjZfJrm4ueyLRocMmyYw\niPnKNvTNcZkWUK+Wbmc4x8gFGeIdorDy0/gIXOGAZ0W1M2A1EfpdgrHlrvhV\nwUKVRdgdPy3pdJPjtTiI8pZxWY1STlVTNztL4nJVHgehKd1rn+1dA6ZE7UN2\naF0sT3qZzoMqyNgprWz40VrqStb8liN/n3+MvCOOZLsyCpIl8gyK3BGBO5Mi\nkzwyuE1pongtopLDgDhXfdW6kqkEkCtS8mPFyzH7+rEtfgCXzooyTfYR6moE\nM0a8Nxmy6u3GOl7QjUiSCddK0HmaSPFeq8/cXywZ6l7dJD/nomkFXQ9Swa2v\nqMjx00UR8JNjy2QyGEaViiPqXUKEyngVY/RsfdFaOMnh6l5aLJMNaBrXOhaU\nH12LAxZ5JCCkn8D0Aefx1bAd7776Ia/z/fTScRCgYtxcUOgvVPpS6aq1HJQL\nSXiMUVOVl1fYgqQIRPPGaoP/YhgadCEKK5RWlaMtj/u+cEu3RJFPS8ks0fO8\nWWhD/gcejHZJ5j7PemRpJH5tihNqo1JoQDj7y7afKSXWeMimXJh1BL1Xo2DB\ntTuT\r\n=zPXt\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"320f54b092d4dc07e565e1f3fe8aa7dedda0f9d7","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.211","@balena/jellyfish-metrics":"0.1.27","@balena/jellyfish-environment":"^2.3.27"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.2.2","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.84","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.5-renovate-non-major-320f54b092d4dc07e565e1f3fe8aa7dedda0f9d7_1611417958257_0.11066304009558503","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.5":{"name":"@balena/jellyfish-core","version":"1.2.5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.5","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"89b48aed985beed0bdbb04e126e7ad13c447094c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.5.tgz","fileCount":95,"integrity":"sha512-EFHrdJLP6bMRU5C8oNgevFq+87z3ge07S8bfNfngjMIlH1tgvEarDyByIIsdlc2UWifilcQJh89fxye0wFnmeg==","signatures":[{"sig":"MEUCIQCNHSZYz2b7BeKica1Mt2H6NmYGMX4jNOuEvQR1d6a+sQIgaPxKxFvVjleZNSxsNQy4Ad6B9Z+oBAQlRHbdshaoGrE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":588973,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgDEr2CRA9TVsSAnZWagAAKjMP/jJPCwzOp/MqIkwmQhsD\n/v6VYVrSS34fBOkynKVA4NtZnDUJRJ3XMesRIXIGf3oHo9ULso36UUdUSNa5\n6Koq/rZTKwCgci9ddV9s60p21z/Y7Frc154PlM93QrW6cKoGlgu852mX4ZEf\nGJj2ffiFTl1Hue8ECXjCwzQFALJi9fLuJpH6LAQ3UdU6v6PLBLVIriu5P9dL\n3MFgwetbCsCAtS9jzzO6iCD6NdtNpMEHn4qcMkA+98Z9FuVF14sjkoOHukaW\nnZ0k7hbeQBpA8aMuwNVSwYeWl51Kp/TAYAwl75XJbC3b9jFqCOP9lEkCanMo\n7bKvTu4zkn5AYoKe8007mWb4zFUALjy03d77hib9Agvk5t9Z5jmC4Q4Fr0Um\nhyryAdAgIdUePWZnKt1Ee6wWgrdkeRU+cUj2cZKNTm8voG8ehjyK8lLYBShB\nJXq2/8RuOO37JEjB7BxZVaOXK2EzRpRdBeOUbX1pCX/DEDDtL8S+VjSc3Djm\nz1OLz30Nt2PacZV4CVqYSRMwhOb0CTc7/kXSJ78rBJfSWZhYPxwJRR8Jcqxy\ngLBRmp6XYGsjw5GMxC7U6U7xuyLrcwOgYqd1QUCoux86iUByOsnwO5mgynU3\njptvioYHGG/Rt27CvVh+O9Hl/m7yR9D/mYoE+FUFpfGdHLZrNH+uDOTa9ZP2\nm9CK\r\n=2dOM\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"769b32a5df85bb99a9dad3b3db85b22017f46890","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.211","@balena/jellyfish-metrics":"0.1.27","@balena/jellyfish-environment":"^2.3.27"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.2.2","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.84","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.5_1611418357651_0.3472917264671562","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.6-renovate-non-major-79572816642bf3fc92dfad5b99fb2ca581aa7941":{"name":"@balena/jellyfish-core","version":"1.2.6-renovate-non-major-79572816642bf3fc92dfad5b99fb2ca581aa7941","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.6-renovate-non-major-79572816642bf3fc92dfad5b99fb2ca581aa7941","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d400fd3e83679235c513d08ad380d282ff6bcb11","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.6-renovate-non-major-79572816642bf3fc92dfad5b99fb2ca581aa7941.tgz","fileCount":95,"integrity":"sha512-04UZ7qXFOUk4L2M7fsIjXYFMnHnowPGXkdHF5lxDfyeZ8sZXdpyZo+OpMbSsI6pX6m8CeRnJoKRg7ImWoWG6WA==","signatures":[{"sig":"MEUCIGG24/l7qLgh+gMna1ip4i0TzwIFxEwVAsD84KoCy3ydAiEArsHaAK6O/C1KLH379YqZzzBN7lJoM9aqeTG5jTClJ68=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":589098,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgDFr6CRA9TVsSAnZWagAAgjYP/jUDftPpybqO7r8JtFJ+\nJHNJPLekSx3KNMBgmrokMqF2N6uuutTCS5F39lo78wtIlaroTklSbAGC3ooO\nS/gi8xt2ZWO5szOO37t7wcD9EHAz5B0oOkEUuq0zTfBoD8JvMs8pE0ih1UIh\n58t3/Zpxpr952/dCnZw1uVap7TdpBqcmGNBMJfOZKl+nMQYDBvvnJJXl7WCa\n0yp2Xpr1uC/jwnFAvJgXFopfLpwdaD8bkL/yJq00ydn8Fz+DudY/QmICINNP\ndIU485ZrSbJFa4kMcpAZ9abEFnFF+VUbGzHnaZ61e4pMN5gm8LpHYPLjVJCJ\njrPKCmzU2sFC6cZrDhfZ3ZGM0o5cL6yOp/j21AUqlOQ/Iu7J5fJgPyYBnQ+K\nSa4cT7tCpwRQ/YDS+0fALao1hpUav7oT++At7GYxIF4u8S2q7PAQaHH+qZYL\nrueEu6FxtQBr2RmiiLTnql7UT12br6lO4GGiasB8GCMqkL3eMLx+iRK1bxjl\no6RN/KSshFZwdFfMjYgyN82S64/ay60AiwlZkgITJM1CsrPkNGXCHghhJC56\nxkaGvY7E6j8r36cWcS89OqulquiwE83oCZQlpoQjs06S2UnraiAzyUepqkOo\nQ1L/gQyh3nhiIaAeJKYOv3aVhscmKAFary8x7AhXAbLW2vLB0COE2s2Dlwk3\n7qYh\r\n=hcfO\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"79572816642bf3fc92dfad5b99fb2ca581aa7941","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.212","@balena/jellyfish-metrics":"0.1.28","@balena/jellyfish-environment":"^2.3.28"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.2.2","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.84","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.6-renovate-non-major-79572816642bf3fc92dfad5b99fb2ca581aa7941_1611422458036_0.4429148887984289","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.6":{"name":"@balena/jellyfish-core","version":"1.2.6","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.6","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b864442971e0e240cb38745839145860690f2f1f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.6.tgz","fileCount":95,"integrity":"sha512-02qZMtqEYD/aC9Asr+ywEZVKrg4F7WU+uzVkfOK7JEa/qzzhDvzKGytIn0ZOj0Lf+rSksqOXJXJNrXgHx3BTqw==","signatures":[{"sig":"MEUCIQCYDomSjq9K60JGTvkFlsKoByIb2lWbEPHJFuIL7ETc1gIgP4WAB+m6X2SkurZt191QhPS3+EiVeI5xn3Uwca/Iu+c=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":589038,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgDFwfCRA9TVsSAnZWagAAZaMP/3tcmxMuBoahrkx8KdnJ\n9Idegp9zAHHI48z+tQkwCw9KrbCTqE7rEIAuDxrIDhLUwgzHTpgPJVNaiOXB\nV2xNN5K2T4x1BD1JgV9djshhjAocWWe1NIFa+wkXolI4biIksQm8Q22a4F27\nD9W7lC0tV7qfHtoIif2XLNtgunegIeobTszJSu0qLcETsFlOnGqkCewnNVDp\nb2vmyPqy/rE50noDTU4p8sK52JdP/aoKNkUiCRSFQylYvLwzytDsuZkHpc0z\n7TvdaNMLcOJvOHAXp6acgh+cf6o0j71Piy2UiPIF3UOzL9LlpTbVBTfaUtLK\n4gcXB6dEAz+CAEmJQ1wpd23D1NxDawydJZ9ePWISQMYY9TZRjwhCjXPhc/2M\ns7esvRFl1hHladqA/h0h6OZ3ZE6JKTZT88o/l0O7bdFcOGOHNS0vdZ1rN7SD\ngdmv+y72wzeio3xTMRuMZ2i/Ash5PeiwbdLnzLMeh7YZFnhHwrpaCTa5sm3d\nuKa8ZdZ7ds5ZOmi3+deiHgj3j46ZUTQ675gdxOH73cXUD/p4LsyJC86KerrQ\nnzg/ec7QxE5fnqiNN4wnQJjtpkeIJBAxtoSNH9q8kmpxTy/U7olLYbVZvLmw\nQMkG64q15H1gqP0qsidOjhg26QWSoWHmM3M2Ud67+cw7PeqJaH/G8MyQQHlU\nEppX\r\n=udui\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e4885ad38d4c1b03f2d652fe283d456559747641","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.212","@balena/jellyfish-metrics":"0.1.28","@balena/jellyfish-environment":"^2.3.28"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.2.2","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.84","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.6_1611422750727_0.26783549923281025","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.7-renovate-non-major-94b08f6b6c0804a40c10bd7355ff192c26f0a2c5":{"name":"@balena/jellyfish-core","version":"1.2.7-renovate-non-major-94b08f6b6c0804a40c10bd7355ff192c26f0a2c5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.7-renovate-non-major-94b08f6b6c0804a40c10bd7355ff192c26f0a2c5","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d4c567381ed62d8452d0ff198560d71464dcf457","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.7-renovate-non-major-94b08f6b6c0804a40c10bd7355ff192c26f0a2c5.tgz","fileCount":95,"integrity":"sha512-dd4uZFsVWyMTdTiBFvDaH3Dib8mqW96IEuiKZKa+ysFuv3A9UHNrIB7PcY5wsJJH7g9GJjZAJEyPB4nHabbHmw==","signatures":[{"sig":"MEYCIQD/KEH1eUfCBfatOnXgRX9SqEWeOHWO9AARAabCBJUykQIhAI8rp58HsGK9Q6ZPQMODPwFQJLrjlIR/RnAEWJUjH+Y5","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":589195,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgDXlzCRA9TVsSAnZWagAAQm8P/izIcKcm4CgoZlFxcYv0\nX4RYYvfcRzgH8dnUxEB1p+ZuG0MuJLZns+/dpjvGaAT8VkREUfJJwLKLfwVt\nAM2mw7IIeTHSGF8RLTqIbc94KD9UbBBt19MOi2OhRE6WAJI7aSuTB0tNodwk\nadNbgg2rtBAZM9QjIyXIBRu+is1OyVzhkY+yXwUIB2hfSACwPtEe24iJJgoD\nwsr7yXexyTyZYta2aNXoWbctdlqMPcwqM4VrDWePw+5qgM0AkjEVhMyrd9eP\nllAAzqibKvIwt3QC0+m/cjeWazi3NMdhK/o/YU1bgDrWEoTGX7MkBb8Krpvi\nfBhQq6RkzZAa5FkELWNIN5YiQfPjeKL0uVZu9P8HFOYst++UY1iKz2eVYA9E\nwaYxLmpTL6wWY0FxO+Ko7xkvHEw8avWa6ILuUVDNk3aJ1whLBwrg+NzWJ434\nQFc0QkbJch3zL0Ry3UaSNEIPbKWRmXbM1AbGqOkdKajTnEjoTwKqA4wwGnTt\nudLqY78KP6htPHm9E7rgc9JLispx0mSQGxWDV3dUdosDNkM8VIFfP1yqMJE9\nCE63AztCwjgIrMZMFQAbmtCD14Zu0h4Rr5JML5KI4ypErT8UvjVLvJSl7aQG\nvANMbBWQSRi7jpNev8iOOfz0huymwvxNs3V24JWFywrdDdWOjrRezsC68Ujg\nbe8E\r\n=9nLq\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"94b08f6b6c0804a40c10bd7355ff192c26f0a2c5","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.212","@balena/jellyfish-metrics":"0.1.28","@balena/jellyfish-environment":"^2.3.28"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.2.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.84","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.7-renovate-non-major-94b08f6b6c0804a40c10bd7355ff192c26f0a2c5_1611495794723_0.3555174077293224","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.7":{"name":"@balena/jellyfish-core","version":"1.2.7","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.7","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"74e3c217162a58fa119e71675aed9cf11fb321df","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.7.tgz","fileCount":95,"integrity":"sha512-N8fGxZlRycCFcfHDq2tydxmWJgy5ZaS+k0bg2WK120psVBeLyJkjZOl6L8NAf+Q90KW015OPiCv56O4jsl8Vfw==","signatures":[{"sig":"MEQCIHyhKhJwBmpQKBariKggvX2dyajEIkZ7/tE/2tuPKp8dAiBJeYlNQ7Z711k50OfqmrUKGaupAz+3e5QBe2wobm2s3g==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":589135,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgDXp/CRA9TVsSAnZWagAARnkP/iMdOD8km0HBqb9bAM7C\npLPq1dupJi5sSHgA0gvVNSxa3QcRd4+uv+AlRXim+26Thw9oSTVZWXMINr7M\nJeUCjVHOUK0eFb0ZLD6p1RY9/dNgYi8DunhO2Y9oc3gIk5LZ0g7EaYeKBP53\neeyvvgjDmV0eerg1JDSADsUIaVG+Zc0/cU9NpEPbjUKAdJcI3PZbAT49yOeF\nILrf61PioEwGKYsR5HieJopqFXW64KKl6gvKtEHUzNvTdmaNXvJHtsaYbtcQ\n4ZFjiamnXwtJLCK+WTAaI7YpLYch71jJPrY1bpls3WW+k3vprvTj1BjwYYO/\n4z8n+EKhnQuV1cH1dO0cCtT0U42k27uWMqthfsSED2RKnpN/5RajfxEiOlhr\ngJeri4Q4fafN5HusNmQBfCYdHvZw0lTmayVfBed5uZ1FGlnsATN02bp71FPy\npNrcPCKa/cbcs9JakXllcDuXBEjgM5Sh782Pq/94aCYkx2C69sYlOayv6Eyp\nzmMYYOYUDUm1S1r37p6IR5ReAGQD03aIiN73N3BRMMIF6ACx4YrJpX5tzDGA\nZj1vBoyAt61YJyIqhnX44nPThiHsklgk8EcYsnp3BRg/WmOlkAwiwYoHYePz\n69fpI6cPHMRjqnMv7p3xubm5u1tQJwmcwW8uyZz6njXNXRrfzIrXpi3ZlrDf\nQiJ2\r\n=lldS\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"bc4d342bdad5f1cb4eb34ba0bccab45185da7195","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.212","@balena/jellyfish-metrics":"0.1.28","@balena/jellyfish-environment":"^2.3.28"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.2.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.84","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.7_1611496062653_0.8900954539395678","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.8-renovate-non-major-dc220fd68fdad4effb42ef986fd86f0e2a2d8db4":{"name":"@balena/jellyfish-core","version":"1.2.8-renovate-non-major-dc220fd68fdad4effb42ef986fd86f0e2a2d8db4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.8-renovate-non-major-dc220fd68fdad4effb42ef986fd86f0e2a2d8db4","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ba957462312162b4f118c99b84e6080d912ad2b6","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.8-renovate-non-major-dc220fd68fdad4effb42ef986fd86f0e2a2d8db4.tgz","fileCount":95,"integrity":"sha512-I7o63+/9PrEDylvNbGVghPaXQr840RosZTdOzryAi0R9kkUjgf7IrwVZ2FPL5asscMTJjMbaBLYQKdPbbOFbUQ==","signatures":[{"sig":"MEUCIDe9U9CcgTaa2Bjf68mV6jyeKvcjVG8bWmgW0SS8h844AiEAhAb4/7+AGBR2H6NMVMJaB5eDLtLE6HtX06363Llp8dM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":589260,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgDXupCRA9TVsSAnZWagAAWLQP/0aYo8vPOpkdDeiQqLf/\n/egZbcpLGHYP3f+e2hpQd8nMU3AjniESkCRR+oieZvOJXNnyusvG8w2Ddj6r\nID93aos0/4t5IicpNzwYsKQ6U2KZoLc67mHM2ujWQjgtKbT7RhEcUW6UyQcC\nUdD646SFcFPKBoZv/+o5LsK06uRW/2jBRG1YAFycdRFoCAxvbY5+A4btZutA\nIOkLQjAE/DE8Sf396iMhj9Gq7f7FRze96MZ2pthVesXIxiCB5xLuV8a9/WZ0\nejUcM/oOF01v58LVkTUkE/HgKDdcueqMLw/bpyucPBPg89uhEBoa0JjChouA\np3jaAlP0e4g3lYyIMwn++rQX1abSODl/rhwTUA8F/v6fXjpRZ3TdN+Pcqq5F\numdKijfnJBV0tyXhmyivQjR1vV3tVfoximSjN1bBMNHk8vGGwTOjjvGkBt1H\nd7GwjSxTaVym+rAE95mKws8kWg0lew/Il7BChwm5468kA3jLcDBbMV4aiF1b\nozjPrrP3ABvTwQZpvxnb0ZqDESgW9LypJx/HaZh/fFdb19bpQGw7BI8XQHn4\nNnTkatmgWb+NAAJVCsDli8XXE8qyIN1GKBsXZnXHlw419YKnBAaM1TG24D/4\nGESvwt2pv4Ag2qylRnozCDLvQbpYev/FVyXDxyHFHSL4AnEi2jUSEl6pFNIj\nNFEp\r\n=5laK\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"dc220fd68fdad4effb42ef986fd86f0e2a2d8db4","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.212","@balena/jellyfish-metrics":"0.1.29","@balena/jellyfish-environment":"^2.3.28"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.2.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.85","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.8-renovate-non-major-dc220fd68fdad4effb42ef986fd86f0e2a2d8db4_1611496360964_0.4332036704279072","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.8":{"name":"@balena/jellyfish-core","version":"1.2.8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.8","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f5a65861ce1a21435db5430705704e5c56aa757b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.8.tgz","fileCount":95,"integrity":"sha512-azhIgUqB/1ijszwwf+CWI2+21B632EjoX9RY/ObXFnhcbXtx5MATd4wIS5yFJ9egt1wuo1BET2lj0lckItuthg==","signatures":[{"sig":"MEYCIQD01lG0wdZMMVXi/yasgI/RM+TJ1HXuCtEeLKpp3Yg4awIhAO/piWSDJ0rMnFIT32LYEvRenCAil54MibSyWZoLkuIg","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":589200,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgDXzoCRA9TVsSAnZWagAAEJcP/iTDUQjP7cbKjHb55SrP\nUBpporLbEbaSxvaf8Ismv+VCK7GjbNyqK2IlGNVkMrri94qCm2ufwL4umNww\n0iKTVZx8+X05WLoqgIDqYeHbFrb6rmLNeVc61cy2eAR6ipe++SX0IipaHTtG\nIQ876O1q2ULr/sHU7gapidV3uHLT23R4feKHOKZLXMdMyF9WAMzv4yKk7x93\nsXQzyJdWxhNEJUqjuWQ+BXZebrazxU20+d3WDmXUEQlzGdV/1uwcpMbkAFq+\nfBYvGVE8O8sLN/LdJfY+iidNNRImOJSPcbZ2TCieZD6i/pZLLgV8kHEiXKV+\nTxeK+6F6SDbsic5DeDAzU0+cxNCN4tXEXH7d+mhfUtAQIGz9nNCgVh3ctT6+\nja90UbU7L53bjz27ZCs8nC70COVS4WlrsLeCch92UtN+jaTDl5QgmJ5/jbiA\nDMDIsZAM2o+f/TQhQ3tFNoMyE11Yse002mWgKgdxTazsfs7nYwEzKB+te+v8\nHRDs5G0WDrBJ7V+MMXTTRVQzjPPJi5LUfV1/yCy1MC8jBRX9sufuOSlQaGtC\n4WDErXZXeBcqwy91ZWsPTYnMPAleUk1NbpC3ElLPdZlXrJH8gAd6fJGQTR+l\nYo3lX1PpNVQ73jBYld6XO2PldV3TEaT9/Ea3Ug06eRe46IsX3jDo2K62RV4d\nBV40\r\n=HzvM\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"38623e932fe407711aa2266c3afca6bc6c440789","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.212","@balena/jellyfish-metrics":"0.1.29","@balena/jellyfish-environment":"^2.3.28"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.2.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.85","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.8_1611496680077_0.5039070015714766","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.9-renovate-non-major-c2c2dc420acdf455726bb76163ea6727b61de99d":{"name":"@balena/jellyfish-core","version":"1.2.9-renovate-non-major-c2c2dc420acdf455726bb76163ea6727b61de99d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.9-renovate-non-major-c2c2dc420acdf455726bb76163ea6727b61de99d","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"43470437f9f787dfd76f4fbc5268bf865bd274d8","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.9-renovate-non-major-c2c2dc420acdf455726bb76163ea6727b61de99d.tgz","fileCount":95,"integrity":"sha512-WWF4wEbvpUQK+zpf9MGt1N9T5IOAy4iCcs/JzwMKIkvb7NuzcRqmgR3izuC2AkvvzWA008ZsoCaHomFVL1+NeQ==","signatures":[{"sig":"MEUCIAFuk3kPzY/zTGg1Yo6cXYRVDmsXgZIufDuOrEeHPz5GAiEAzEobGXMMCmcN1zOH1WQ8TgNs+vy2ZdvoVDotIh6QSIU=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":589325,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgDYoeCRA9TVsSAnZWagAAvqsP/12TM8XgCVn2ihulhuiR\nSlgL49j1I/rLvl1ABfHUx8Jblx/AzGo5jghhKwwOO6ZQryoPf7ysNd1WgGlZ\nlKZ4ydXROzKbPaNwWk7siuqo7O6NcNDX8zJDksjL2ce2PFxSlN+MbMTrDehS\nTodxl5E3gp5u4hHPvUnnWvVTFGt2UdDEqxupn16y+Lj8FiW3owbIgunDNbBT\nsACgR+atLuZWup0u8OAJAuaRXs2dMZ2LHRb07e9vW2epzc0BlciuKJu+Ao0v\nn93X6GjwJAe2lzdMqOoPkLA8kNjuM3sAnrmMtpkBhmTiPt8nNnneaWz0cJc6\nwbHUlJDXlLo+xkGWrEDbXqv9+BHmMC3qnEwJe7iEGnECp2yuxQh9I9kXtIOD\nvPYiMGM8QKaz6AY+xrzlqWOn2j2KLrFTjI/3IjMxLDxR4j/aJO68JL83Sk9G\nrIRgFQEJkSeU4IHAdjtJ3DlvuKYIODcVWyVf/wU0oL1/hXgER4oTiDv6mi/3\nK9AYTVQx9lb/MQ257w0WFm6tsGYr9g8SkGA/dg5KtSD5CBObpcL5NyIMPjPx\nc7U4UZGkedsxDZZ7Qyy/IexrYk5ofhNuhqC7G4rTx64n8IuyDZfbml5dXqE+\nYMyctK36c0x9F3ti0DcWPUe/vfXadYtNIfkw+h0FrleOZo+wQRJcO4GClQ4V\nq4Bm\r\n=951A\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c2c2dc420acdf455726bb76163ea6727b61de99d","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.213","@balena/jellyfish-metrics":"0.1.29","@balena/jellyfish-environment":"^2.3.30"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.3.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.85","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.9-renovate-non-major-c2c2dc420acdf455726bb76163ea6727b61de99d_1611500062020_0.7036009468848115","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.9":{"name":"@balena/jellyfish-core","version":"1.2.9","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.9","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"230cc6558ff34363b1551ebdbb26def9e4697955","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.9.tgz","fileCount":95,"integrity":"sha512-VuB+chgtRrMsQntK8ToU98cMl+ueZsbalSy01/NmDx/XBszLPOxvwuT/znXKymwstYyA+Sbkk9KAFAcqQxEjXQ==","signatures":[{"sig":"MEQCID4h/XZXPeJ8j+jD7xb1ZZuWL1PC2Ap+4j4FB49mSJOzAiAzsSyCySDc1o/Gykf8x5nTpp6YVAZKSSzcVXE/OEiDgA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":589265,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgDYvCCRA9TVsSAnZWagAAzwAP+wToOu2KNA38mgvJuC1J\ncCOc93pePUMlR/M1qEF9h4KSSpnl56ynGhhQx3SSolKhU4YwLzVcgYi2TROc\ntMYJI/4Jm8fV0kE4kuHqCoYwroCUNh3p3i57umTsrKxPrlppHD8j4NW1TkJO\ng9hHc07EjLjQI/nIx/PY/kCO9SmTWgGOyZlfMSqctf9ZRuGMqBc3gH+20bSE\nVmpTHG1tTGqCTUxdJh7KaxDsuiwf/w5QC0iN4AAUCNsHR5p7s4M7uQYIS+GW\nZKWEHL8kh9saZR2o1bH4RRdQptAPs4A8D1ysSq4J7NUR4VpvxA6P5fW71cnY\nBr5vcMjijRAlQScu1gdwv+Dw9j0VdxDAJWAHYsxWEvvhDdf2Qg9mZ8x2GqMh\nM/BkaNmE8l3No/Gv4Ip+CLVtGW5xWbN5GxPhKoZxLQp4qV6DXNajNwe2zHWO\nRDD6BHf2TyNAG3ToFKZ/AdWlT7JJ5gccqOv3PJHjbKC71JXXgY0GHt1cHQhx\nO2h8gkdgkvournVys3SCko9TJuBU0kJ70Rik9GSrK9T0U+M1LuTHUa28fw+J\nhtn7DJEAugyu0fKkaxgayndMbuipLo7E5VeY+j6vl8SCErtIHfhMEepqctTL\nnSS8tg/FGmsVQBz3nLucbJvUJ3B8qX7Um2bMqMRi0iejepEedf4/Q3uDCHes\nSQLw\r\n=rkN4\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"9406b209e307308b50f6c5ecc0a6aea7af943cbc","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.213","@balena/jellyfish-metrics":"0.1.29","@balena/jellyfish-environment":"^2.3.30"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.3.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.85","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.9_1611500481567_0.5280894906518538","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.10-renovate-non-major-52a0b509bd40a5d9cba5cc2840b95c91bffbdcd4":{"name":"@balena/jellyfish-core","version":"1.2.10-renovate-non-major-52a0b509bd40a5d9cba5cc2840b95c91bffbdcd4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.10-renovate-non-major-52a0b509bd40a5d9cba5cc2840b95c91bffbdcd4","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"30bcb16b0e259c41fe8544b0af9bdef9cf7fc30a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.10-renovate-non-major-52a0b509bd40a5d9cba5cc2840b95c91bffbdcd4.tgz","fileCount":95,"integrity":"sha512-XWhEiyEVstEWsV5OkAKyWp265FOyzwAeZLm3SmzVA+RjmDfjtWJq8y8Yx4IIZTqBeZPboGMjjJHTFZ8TMwGCqA==","signatures":[{"sig":"MEUCIEjum8pvioCFLjeS++1N4ohKWK6I59UtSQxkuw7g6SQQAiEAiYrcvLo99mqvEwX97NliFL0JUioOKTxYz3GrqyCLzYw=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":589392,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgDYx4CRA9TVsSAnZWagAAzeMP/0CCwrtRuzwX83F9z3aQ\n8SEojw7BjTAwQLdriN3NcQlxpdcYHTb+Z78e4rGg9xt15xONnMQiRWeA6SC2\nj+TVpg5H+tkGVjzyOnUCs2uJ0l44To3RITzzNZ7nVJHN066nwYTLL+tTF8qS\nCYZ/rvs5XyHWcPLUGtVZS1nrjhkFj/E5kzc+3cb8BCZBLRUzoMMsoClhCMEj\nd/0Lap2SqfKGK2vHeGWFIhTQLiPV5ORFCAprKTOU+8yiudytCqNiD6BKbcne\nivV/ZPnh/K2E+/3Uut6iNH4R1NoLxlycS/PZgQhMX1iQlQmmqh3gqbO18C05\n4s5tEjbNL6m7REMQ4SK9nnjO7IxpbszZefRRpBVo57vOMEYobJYZVC8mrIsH\nYyIpFdQqQfWgZTy37KJ8Y+YEVh311VTyRlYA5F9CewAT+NupmQHv3O7JaoLb\nRKIKFBVSdJI/ly29eT/8M29dSZz/b3Zp0ZYJcUoCUiY5cfWzdyJeDUGVr0G/\n7+qirKqzggptkBP0xEjFEc+mXa8Hwt7FpnnWkwy9QaEJoutJdEfEleNEyqSJ\nf4XqGszukseIQ51RziDc/iphd428U+2Vmdw6wr4KwoygMm05YSqP9JDZlMAf\nWqwCvB73i3GgQxr8AgL0po78pjAHTNRTFaW/1gU0dr1Jm2KsY4yswCOVHU4M\nyMfC\r\n=8LM/\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"52a0b509bd40a5d9cba5cc2840b95c91bffbdcd4","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.213","@balena/jellyfish-metrics":"0.1.30","@balena/jellyfish-environment":"^2.3.30"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.3.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.86","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.10-renovate-non-major-52a0b509bd40a5d9cba5cc2840b95c91bffbdcd4_1611500663808_0.06780443551390869","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.10-renovate-non-major-aba0fa08a7c101ad991fa2696f35f5843a69bd1e":{"name":"@balena/jellyfish-core","version":"1.2.10-renovate-non-major-aba0fa08a7c101ad991fa2696f35f5843a69bd1e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.10-renovate-non-major-aba0fa08a7c101ad991fa2696f35f5843a69bd1e","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c4e4be1e654b6d35577c8035952a35efb8223b48","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.10-renovate-non-major-aba0fa08a7c101ad991fa2696f35f5843a69bd1e.tgz","fileCount":95,"integrity":"sha512-tI7CHpTC2dVpETuq4gDyBOpY2OtgixAutOm81dn8Phnzt1ZOfvq5+x1xuFl2c1DrNKPFFRy2Ri9c1Dv7CTZIAw==","signatures":[{"sig":"MEUCIQCHeg9hGg1gKEA4ffZPpNuedqz95TkRyrl97SfRUiU6pAIgUHTp/sfTNJQy6Oo0fQ0iGK4psgtzDv6wIXvsAGHPCFI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":589392,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgDY2cCRA9TVsSAnZWagAA9KIP/3eUJ8M7atq+2MWD7MjE\nG0YvxoFCxwx2ifetu/5iQjQ9koLocFfx8EmsLPbbrpektR2czTNNMqgu6MFC\nz5SVbSAUIjvlHpznLoce8wjIJYDK9B30L8QOZqsbAbqihh6z7RyAgenPi7sJ\nyDcPFtR/4oZ6CXCRiE0XCsOKYW45eknYAN92tM1z1bINoli7MNvZlxx46K4L\nxX9CQM78K1obHC4DUbYzOsvT1SWyJiK55SRPaPljjyxnG//ugxyD9BgBG/q5\nAYgfrcXsqf8nnaBH7Zhqdyx4LKXokqTMg3YWnlTd6LcUjCMtbSAwJxcwNAA8\nVYUciJL7b2gjWhypxLSUNPx8Rajvg/rOeSG70BO9lbo8Q+szAfik7VqsX+IK\nrZwPVQWYiFMyrCV5zMGAzzlP8bCYmsgVIX82RL+o4dsINRKL4SxSFf7Vwrdd\np1zDQgBWD8vHiGRgcXxZXyyZIBC9jtCcDK2UkGjHw5E/rMGat5yeylPGwAeL\n8eywqxrCChdS9Zy+aMjuTZZMYKRSRpEzvPknVVPmiCU+ulHoG4BoGlewYoQ9\nUIaV3CQ7NAoyGAm3P6BoT91ea4Xfgcu0qRixMdLnpd1ToAaijNFXjQLKyUBQ\nHAX2faQ830xAdJC6LtFeAynVNgogluQ3z0x9pCM1haMZIvOeb8mlS7mXzdTC\nrIDy\r\n=PGfq\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"aba0fa08a7c101ad991fa2696f35f5843a69bd1e","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.213","@balena/jellyfish-metrics":"0.1.30","@balena/jellyfish-environment":"^2.3.30"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.3.2","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.86","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.10-renovate-non-major-aba0fa08a7c101ad991fa2696f35f5843a69bd1e_1611500955661_0.4659502902726771","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.10":{"name":"@balena/jellyfish-core","version":"1.2.10","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.10","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8754908ee1362aadd008f56be3bc7bd2d789547e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.10.tgz","fileCount":95,"integrity":"sha512-XIHhNLo7ZUznC11dp3XFxDuL1RuwTiemSJviDCxOJT12f6T44jrvze4Hli2Ye3SkCPEd6R1Khj4ekPN3NX6Qdw==","signatures":[{"sig":"MEUCIQDEG+5nIjLRtjQ7sj39acYUtY9yVcNWbu/fJZBbj9BV+wIgKKB5LWahU9nHNshTk8cI2e5RSMy88KUoQGPE71qsdnk=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":589332,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgDY8rCRA9TVsSAnZWagAAu4wP/13ZNQG765G0Vb+oC4bb\n2BezFUqeNvJ4rLNOZkKmF6ptEtyN5Nu1cu0yLZg1rC4vcBEhP7D0GUQOh2vR\nphl8qy2shEn8Z1a+6RyXO71XCCVd8AUhjQ6R7CHjcV8pPcUgvhbs4Zaya8sR\nuco26lgbiK+ldqj7QpPEep7S0eRPuOQ6qQZh+KPNEFARPT3Q4A3akYkQFVbL\nH2Zi8H1vo9YpTCT8IUzY7PmaQywDlgEoLvWInJS6agEcJ41tEPwN8vDEhdOG\nbDve9k0nPNJpDKfORPjEXZxcqC/6HdNKIaXpT+96iP6cl5+Dmt7cW6dwsU41\nXxh5qIJbryWOrmwpOiiF5lFivOFPNY48HapE9j5v2b6WLi0Pco/wCNlFhzWT\nwbb035T273+PX3ujG8gHIjuRNFb7/eFgMGY7iUXUUHqm+AJgida+ygxeJRNO\n2mpyIQ0unu0EEhewvivEc1Wlfxk6bo6rLvgPhmDvnxA3nlpAaJtpFzUY0CCT\nq3uhLWo3cwOlRm0zhA6uDMwhiR+YbP0VPoSUFH4swBrG2eZcLdIPOgUQqzqD\nZK+8fGpiDpWaELwCGI2n8oqPOx0TH/mbwicJLKxhhlaUjatF5P+kdGwB/zdK\nBZmc+DVpktidHR9RQKTyUDbqN9RyxvwYd7Xeq5dlHJqpF/yX92mUunQT1vKu\nMK8V\r\n=B91/\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"436c901aa97901233bbf9bd416d0923b6e43892a","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.213","@balena/jellyfish-metrics":"0.1.30","@balena/jellyfish-environment":"^2.3.30"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.3.2","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.86","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.10_1611501354546_0.7873643612431271","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.11-renovate-non-major-720fe7942f52e3f65803f696c0b986f489fd7cda":{"name":"@balena/jellyfish-core","version":"1.2.11-renovate-non-major-720fe7942f52e3f65803f696c0b986f489fd7cda","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.11-renovate-non-major-720fe7942f52e3f65803f696c0b986f489fd7cda","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d1ba8d115f51809c67e18ca91601f32b30612d4d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.11-renovate-non-major-720fe7942f52e3f65803f696c0b986f489fd7cda.tgz","fileCount":95,"integrity":"sha512-kfUSz5G0tWUPoeziCxiTuC1hKSWbvma4EemvnXomscI+z80bIrcUy/n+/fV7lN5gNFIjTOVZYI+6ooi3LTrSIA==","signatures":[{"sig":"MEQCIAwd+0ouJyuUkBU5yBX80G+TEpF5mBUX+BjW4IYFKs7wAiB93r9EIG3hLHco0jJFITVLSTKLefxIL3HtCt8IatxNtQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":589458,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgDZ9eCRA9TVsSAnZWagAABZIP/Az4QJi6kwriNRsn2+i+\n0Da6sQDS4Y5vcECwHfkYX39GNrCoGApQ0+sHaisPwC0zdKhigz4G8PlOfMLE\nQ+2Oyo7B4u3aPg5Djxza6zZAilNp3Km9wfF+yO8w8cGgn9c+nOukCJzQjjOr\nN2lgfPmpCMMMR8MT/cxKtk38gnQtpbxpniSe9FhmMvqx0CRDwBmDzBnwifHL\nC2oPOH6LYjnXOpZxT+BshW20J/5vIIKBv7ACeP2G7F4j02X5Z6zpS47Ptckx\nBO2IeDEOti6k2I+/+gveexc/wCK2tl56ecLH9i65umrIbhjCdjg2VjDa0iiT\n6PjC1mmU8bFBwp6vSHhg/0LWtmGRTTIpi1qfcj9tnO6Rn1l2aCtgkeEKpmlV\nwlAmnJxiQA6OsAaq/aho5bEXdFDYGDvUsPZV8Cl/KhWieEUL6Ek8n6bvzVyz\nJkwdusYlnXAUinZc9LhuvWdiHEdLB3WBotJuE2ZWmLvmW7VWdMez2EUZY/rL\nO1jS9kpVCopcdo9MoxlPv+nVtB5tJ1bRHVcXOoY06zGO/hmujetczDJfXr7z\n8PyXHwfEc0cSI1gtyrJJkcSFfEIFi6U9f67Pt8zZCyRt7qvbnZKxyQa7zagY\n4E82jkBltIHe6u5lUiKnZOYkIeifu4o2HDHiwZXc/P0O1AOxeJgFPoXLRkBm\nJS60\r\n=2wsy\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"720fe7942f52e3f65803f696c0b986f489fd7cda","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.214","@balena/jellyfish-metrics":"0.1.31","@balena/jellyfish-environment":"^2.3.31"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.3.2","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.87","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.11-renovate-non-major-720fe7942f52e3f65803f696c0b986f489fd7cda_1611505501908_0.3793895432623373","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.11":{"name":"@balena/jellyfish-core","version":"1.2.11","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.11","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e5965533634c07a67399c976a2b6a41ddfe608f4","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.11.tgz","fileCount":95,"integrity":"sha512-ZCU9K85vv6qcIWTQ2jDdv7tw7TFwjKvxo/Lqg3AWwUb89Fj1c9l264JlkDauMnPHXvXF1X7BrZG++hnPqbazpA==","signatures":[{"sig":"MEUCIGxQ9SuqNQ/8aLV/hekOeoAN60tXuph6Gah6OMi+UD6zAiEAkyn8+xNLwmhZClR+IHcQbP0mteowpctweh3G3I9qP8Q=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":589398,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgDaCECRA9TVsSAnZWagAAznoP/RIObVuD1GJdeCdRdTBQ\nFL2RlSFf/M2/rPixUyRUeoTUzBnujMhZC5kU4umAs6KRtghtaafKW2mxlokf\n42cUFoMGo7OvWDJbZnh5BB7NTU7A4SfDE1WQY5oii7NlZcSSvudxg03DwtHh\njfvcTEsvfzgdFjJKsVoNEGpnIqPkI2L8hrTypriPaoTUy3TFu0yB8beF7jY2\nRTfrl7xTpp/4jx79F+7ibmUTzWIw93d7etR9YEoUwsRIt6TrnWQc88wrl7Jo\nTScl8830ozNSZ2iqWJs5q4+5JhU/Pbm/sGgg0rtA9foGA0+/j8VuaEC19KUQ\n0bmaH1OFM6qiqTP+z9sfZ+y6K5sHhI8xMByy3BTY01KPvQfhNjdc+LuxLl2W\nxYGbFVdvs6zUUZSRnn59wlka18PyAbfC/hU9ofWFGROEuYek/UH++oaG/F6Q\nPQvIoo6NuGuL4D7YNXK/a6nRsJLpj7vVdVIaqP8yxnNdjum/WHb5ZvsdWyuR\n0OqkOJgfhQXLnH5QgZjhaeudBulw/Gftl6iocm/b+OwmeP+oWrD/Eig6zCpD\nqUxfOFAFZYH0ra2II8VKubVMxnKX4yN9PDaRK9kk0UTE77CwjqM3U/UxSFaU\nZGq0SkjjhDCvobYaYB+nnc81Pt3rbjZ++EgUk4ZqrDGI/hq8DPrLFgRatPm5\n2TmT\r\n=m00g\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"11dc805c0fa9202bcd628526cc9d64562cf76c85","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.214","@balena/jellyfish-metrics":"0.1.31","@balena/jellyfish-environment":"^2.3.31"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.3.2","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.87","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.11_1611505796408_0.6212100505858802","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.12-renovate-non-major-4fc9587e931dd5497ba5d83844b913f030904edc":{"name":"@balena/jellyfish-core","version":"1.2.12-renovate-non-major-4fc9587e931dd5497ba5d83844b913f030904edc","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.12-renovate-non-major-4fc9587e931dd5497ba5d83844b913f030904edc","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"303a4d40f50327893a6c62859d64289f6a486b7f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.12-renovate-non-major-4fc9587e931dd5497ba5d83844b913f030904edc.tgz","fileCount":95,"integrity":"sha512-BU+oZMISmQdYrYWAoR/o9xOeTy3E+jzvQgErZLy7BqskVdQDu2gOF2kLlVm0XyWnTU1eI5W/hWRUsHY1981g0w==","signatures":[{"sig":"MEUCIEa81t/eutuPQEwAsMd41GZU9TAZOVMXX6uk1nXOq1rKAiEAyI0L0B6vVvXM1PdXf0xw+xLM74By6c9u+FLg9vwO4IU=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":589562,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgDamPCRA9TVsSAnZWagAAP5oQAJ00Z5Bvp3l9pabBCy31\nqrnLo9Ag0Uf0ZlT5K7Dv8eXq2RT4UfVp5XJEtnJhWfN7cfJY7NC/YkWedfDu\nb65yNneAk4VwKUQAvGokwsgpMXYa1mclOUaPqgXkX+wesrOntFPHkO4gbl3G\nAwT5rlvw5NqA9+pe4mQRkxEmhrRNKMhh5O7/563laHt2yWwj6v1pCzIHaT+3\n1R1+tjai8Zleab47PndyREDZcqZHg/2UBQ3EbSB9eZ0F4+HoFFmOrbjRlgUm\n3Cefwj9MjunX4HyZRdjJkIMOytWjLtHOFUdy5U3w7xRZ8tVAN6NVQ3PLrtdM\nNycOFAoV57H8jEsotwKaKHEqGvsx3xREyUYYPEZciwRgFWuM2IXtgyFnYB2x\nePJn1tLxZHkAQldN92d9fyUoGrQDMNYJ9sT3u9jovolumEeJFUe9HijS3oFB\nx1awnmdhtPm1hV8Y5I8uCuIuvGMcjHL1GgRjQEhSWY5xcrl4AZQhZ4GqNtg9\ne83SEMH+Jypzd1aEZ8UNg92ftSGl7xvjVYKa8QN4CGrMrq/2PIapw6Ev2PDp\nPccqvOdS9OU9Aqya8FhIwPTYUZKOmulnejYFncXBJJ/SUzTFj0YEZ7fIkZfT\nGbLLRZZ4hMUJTiNyUAkglVvDf5GxsC70GTNaqGs/vZO83SsrW+d/rTfeswbD\n4R/J\r\n=yvx1\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4fc9587e931dd5497ba5d83844b913f030904edc","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.214","@balena/jellyfish-metrics":"0.1.32","@balena/jellyfish-environment":"^2.3.31"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.3.2","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.87","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.12-renovate-non-major-4fc9587e931dd5497ba5d83844b913f030904edc_1611508110969_0.37525769535995757","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.12":{"name":"@balena/jellyfish-core","version":"1.2.12","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.12","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1b1e651105d5958f52173cb0602194154b87fec9","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.12.tgz","fileCount":95,"integrity":"sha512-EFqw/iX2LPqxVd+gSz0zw3b+RA79mCbkoe35EhCiYPF3JJyPF/Er+j6O0sc0L5XMYxCliaI1ctjagg5znIYuxQ==","signatures":[{"sig":"MEQCIFQR0kHTRXqawV0cy+yIGp1KhkvrCTU6taSD9lr4FallAiA6cdiVPL9w66DpqU9R/LfOVA67du+ESyKADAP/dSoOCQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":589502,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgDarDCRA9TVsSAnZWagAA//sP/j8D1n4/B0SaOxvA2IZA\non+YeKDnJB/qGcYmHSjRItD3QZuf9jvgXIna6vrBKUELFpdcf2twKtE9WzJD\nzNjV9GLwzuzYoUvhTmFt5Kp3kpHs2zQmAhCfzE78SDNkOXGw2a+2Onl/R/fv\nzS8iXBhKbGuK5TpVHrZSSOkByhyup10ACYkzeTTUToes5JsNoMOSFIfRzruO\nWZsnwTBDNvPLZv92rUcykOZ7SNUo3oFWUTzINnXlZD5+fEtNeqhkhcpxmgdH\npyefTr+y0HvrvVPYSV0/3JNahXNgEdg7MhvjtFZQwEqINakVNGus0KsQEJjq\nhd46mcrIKliiS5bmSHBs7fvb8SXaQ28EBI/EhHXT/YJgFeca5t7VBqkogHNG\nA8JgWRKn45rFs2Uvl6GIDm69jVR3RcYT8aA/mSB9tK9xKpBb/Ei1bdf3Afq+\n1PYTkbkq/80XxqMHKZU2it8TjGFeHY4eMTV6SD4qTDhwcLOQMlt0x4JmYMsX\nu8Wmf+n84v8nugHkccx7EaNq91OJDYAZw1Vbs71fMkzRGAi++HVaoAIiyKoA\nQVOkgnIfIsUXqzlW7irWNCRzN0U8+6CFEt8BGvhGV73pU3vVVgCeaH5gL2Bh\nOqnP42gajTkaLSbdwvQ2D+NcwhmBxuVN3eIuj33i72T6lxSUAJuXRIKLF6EW\n5Bep\r\n=7bPO\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"9f7839a76e99c451c5347d533f150fc0440a5355","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.214","@balena/jellyfish-metrics":"0.1.32","@balena/jellyfish-environment":"^2.3.31"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.3.2","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.87","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.12_1611508419293_0.7672860200194502","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.3.0-add-session-scope-71410c2a5ec896984bc44a755011ffa9aa691d57":{"name":"@balena/jellyfish-core","version":"1.3.0-add-session-scope-71410c2a5ec896984bc44a755011ffa9aa691d57","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.3.0-add-session-scope-71410c2a5ec896984bc44a755011ffa9aa691d57","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"149425f68cf11abdd76b67cee48820b63e5572ce","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.3.0-add-session-scope-71410c2a5ec896984bc44a755011ffa9aa691d57.tgz","fileCount":95,"integrity":"sha512-b6u+8rMi7qqwQH0A7l7Z1+5ouQyFnQzhMV5pAfSKjAyXBVAsyw6dnwpSL7ewOetjN8CWhYBGG9ZEZ2xlXltpZA==","signatures":[{"sig":"MEYCIQCcFIHHPPYaudd+kp9toqxEjIXvz49VobRQT4/rQKIKWAIhALk92gBe8X2qS2b9IfHzTlIYTxEUiUVWw/DQNdi11aNg","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":590001,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgDl6aCRA9TVsSAnZWagAAQwMP/1Llm58rFSPovFiUW8wx\neTsN3wQNYBliCZj/Qkzj50y6/s6EHIHJ+ME5nysjEMARR3INySg+rVsBNG15\nNPPoqAHZGkOiTm2LlfyjWJCrY/pjoDg4vPlHdIp4NQkkmRIqkRuOVgdL6jWc\n3k8UTEjCgjp/zeRz/YyNDk8vuwaY4Vqv1TrGSe3JNE3uXOVzCX7c/7xeiGgb\nODv2FV4MV+vGBufF/RESxCR84DkjBg1og0U9/wQjGBzhSsywTyy4ftI+qo20\nhWOXBVyvXEEUWav7mUHWc0oiWbrMaLCGxYRACHk1uweBrVQC17yzbJzTvT/q\nwEk0hVYJ8rdTVWJ1QL4eoIHdR2quRHThTUsT5Ypneis9URH92HHdtB3+wduj\nWUE7rRw02P8H/x8+JCwaxG9BgzF8i7oCZwiR81qm4h4OIXL6Z/PMFjtsdtZ3\nZCp8TXhU2BBW23YmZUE4bEaCkyOp68+sURgwzvuvfP9UINWKG/bkwPj0P9En\ng+6rv9HjJk55SCYED317ddNOaYnlh/k9Xyy2LwC2KTU2742KHSeU3TqRb2jC\nhLK9NWg/DLyfKU59vGM/x58om2sQZvku/2+ECMFYIjX2/u3cwyfjN+6ZnbXq\nfMD8IuDpLlxkaCi97V/pCSqL0h+wgFUyylXC0JNB5S/arzlSjHGWgAsQP9X6\nhKYH\r\n=MESo\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"71410c2a5ec896984bc44a755011ffa9aa691d57","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.214","@balena/jellyfish-metrics":"0.1.32","@balena/jellyfish-environment":"^2.3.31"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.3.2","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.87","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.3.0-add-session-scope-71410c2a5ec896984bc44a755011ffa9aa691d57_1611554457470_0.5839821339524052","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.13-renovate-non-major-6eb64b2378f521503e962996b45938134eb042a0":{"name":"@balena/jellyfish-core","version":"1.2.13-renovate-non-major-6eb64b2378f521503e962996b45938134eb042a0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.13-renovate-non-major-6eb64b2378f521503e962996b45938134eb042a0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1fda43909f5381f52ce20286ba7987d95943f4c1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.13-renovate-non-major-6eb64b2378f521503e962996b45938134eb042a0.tgz","fileCount":95,"integrity":"sha512-AL4nHgtGZpiYJWVTYIZ33dmiHR8HHNFZtBBOhJ/av3cF/y5PjPi+Y53vJdFev851Qpi62+v2RdaU7dtKjOEDuQ==","signatures":[{"sig":"MEUCIQCuj4S9iDC0zHl8weUBE3O2kuMOkNac1+y5bcRIsZON1QIgYfYTXKllL9s8RfuZqtnSYzV0HMkuVaXJ4oFMTKTdC2Q=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":589628,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgDorPCRA9TVsSAnZWagAAxmoQAIHvosuqAm2FLU3dcp9J\nXVAI8FkvCVspI/htVjRUQf5rGNBPJWbKCXiUs/EEL2FIjVHu8xZ+ZSuCS9Ql\nE4waojFuKqpePSIfur43NVMI8076OBv4S/unv0oqoF77ObhS9+uITBnfLNSD\nHWLOcAcUYrnE5qEEySNGqS9OLGlWyD6TnrH5dHdOLBDgkCwSMB9owSvuU/wB\nadr+qu1MNYZuryFTH9DTh9gr+VaJsG56QMNONU7vHoa2F1lcK2R5kv/mqk/p\n4bDlY+R0HyI5jwOsKXx0bobuQ3zDxcNke0UrQs/pvRiSfJiJxuq5u4KaPYfJ\nK9yicqHEufTMHIdLRtL7l7KtFvbPSBxMwESekXTYbiMXYM8l0mLdyE/ohhE7\nhTBYK8V7J1y26AjrDi94Bh1tfazxiU/vEoYF7vR2YjhwbtiMya4AXCsbwV6s\nbea7ma/b6SbWVK4xbIWRjDdrsb4mSxJ9+65BTvrzrc5lhCFtMOOb3Sml8Imf\noopCqlasyEAOrUSxX/K/DAgOqfsKHLkZhVIOwk1KK2EDpBKnNDterBFO1avF\nk1pmHVXQGitBayTTLbkbJt8W0QUNVoGgLdhRkUU6hBvNriblrKJUDOSr+8Al\nZKap3KIz34zbMuQUXas6XD/hVRfL22dmnhbc8fGtSTHkzETbDaXM3QO5TjOE\nmUQf\r\n=zXS9\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6eb64b2378f521503e962996b45938134eb042a0","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.215","@balena/jellyfish-metrics":"0.1.33","@balena/jellyfish-environment":"^2.3.32"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.3.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.88","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.13-renovate-non-major-6eb64b2378f521503e962996b45938134eb042a0_1611565775075_0.11573014397310688","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.13-renovate-non-major-72eb9ec7ef75b4a0a3ec130f8d733dd1ef5e5149":{"name":"@balena/jellyfish-core","version":"1.2.13-renovate-non-major-72eb9ec7ef75b4a0a3ec130f8d733dd1ef5e5149","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.13-renovate-non-major-72eb9ec7ef75b4a0a3ec130f8d733dd1ef5e5149","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3f6df6b9701d847c3e8cc467c379ca07ef6a371e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.13-renovate-non-major-72eb9ec7ef75b4a0a3ec130f8d733dd1ef5e5149.tgz","fileCount":95,"integrity":"sha512-gtY1nWemTifBVsXN5bBh6OqqPRLOHDo8UCFO2VQSlxI1AV7aKS8YyYpmAnzA0zrNku8QTIYkXEd357Sfyf0U0Q==","signatures":[{"sig":"MEUCIGBq1dUovF7SiW0qc+Xd49P77vuFKADIrz9e0SUNh1yJAiEAyqMnM/eQAiH85rMI5c8VhElexnCI33y21tMjXI/uhcQ=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":589628,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgDowRCRA9TVsSAnZWagAAVtYP+wR1f2S6QRLGogipoMXC\n+rD2vhinlPiz0W4apxmGlCndbg+4jyv8puPjH2ALQHvltsp4LYbe1BC+3JlF\nM5oxRxPno8fSlKHC72cW3nIrb17/z9dag/Et3RvgbB34lmIjsqZZvelXURMW\nk5Xrg3EmxGdFtTiOhWBAYWGTWoWj461Ky/NCWWG36PU92BxTNS0A5KnKN5F5\nPo0KeeH8DYMTtvr4CZqUqlpcR6Dyo8C6+pubTZIZfe08FK8e26GZNT8hxH9G\n7C+fsR+DWxolJxQrUiMItRSeJUt5YDs8HUcb918GFVMdPTzjTC0MEKOhzAZe\nkIp09lic8XTIt2/NM44Bj4DEHk/273FnwAaT3Erhuer0NZ2q3/jFm6XPVXCC\n/PY/Lk2Y59qMypCV+i2wA4YUZXL+ngLuDE6Z79c31ZAX3edajLJTqSQKdgFA\nwppfv1MUUmkEh814Le7tlzX7p1EJTW0+dEN58pj62gtMIABiL3Ur3lInBf99\np/OceXouKmgXKXFwsvyv6scmfnBhCm/UsgGrsyeyE0ROt8SYKB/yeb9SAGky\nmczDzKgB+J8GtnNR81L1RkYDf1R3+I7vdaG+BAOSV9+ZXffv1akami3VG4bV\njyBefjyf93X0w3Qbh9MM1wIxDa4bnOn4Br+aaW2qGBqeyoYYpLCDWkRbmDHE\nLAl9\r\n=B/e/\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"72eb9ec7ef75b4a0a3ec130f8d733dd1ef5e5149","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.215","@balena/jellyfish-metrics":"0.1.34","@balena/jellyfish-environment":"^2.3.32"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.3.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.88","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.13-renovate-non-major-72eb9ec7ef75b4a0a3ec130f8d733dd1ef5e5149_1611566096996_0.9987592530195677","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.13":{"name":"@balena/jellyfish-core","version":"1.2.13","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.13","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"7d5a975027a7a5ec61dfc5bf6e7aa94a52515f2a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.13.tgz","fileCount":95,"integrity":"sha512-+RWZRuYUhyzFxVHPzhv28VORDq/pstXGt59h5ySIpDdbD8o5sku8ZfUInCiYynh2PU+s4ICCiUiAR7Zlc8VonQ==","signatures":[{"sig":"MEUCIFOmUYomDsyMkr3ntAiu8aZJ0ZMRjT1l+YB/hZAnbD5jAiEA/rAuQ/LK+Hp6DB2w+JAy9IdEafLJ3pkD7y512qdMcME=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":589568,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgDo08CRA9TVsSAnZWagAAIewP/iRwfh4roV30yynCdmwL\nQ/sj0/CdDcGXU6MeY1zqOsubpH9qqNf2fd28hi4ntzb0gyfGv0kG2GARLgHb\nazCr2Oi/xjIVHtV6QmIq+Ap/atAVxUsF3yKWSpgLH9hg30LBxdQalU7kBvg9\niVn6MfKVxSSbIqWy//hESFHkGVdXHjii9EmtgfPrkWzkbu3yyPiikmKMQ91Z\ndjvA10AVCKgcQmqBsWQQGaVFV0JUAIxUrWu8SNIzbH7fE4Eo5XLcrUzbLPaR\nDn/USjqy2LrYtOODmXAejn82KmkXewdTf6Z5cviIgz/Oxw2WzzZeGx1SoiQE\n3MkYVe8fnFiupEViBW70kgRwFnncs50/ciCxLJl01hSrOVP6tv492xnGokPh\nJXFGj1Sxt75v5y4GKwOFfU9qMYzSz5Lz9wKy+EaIHFcAyD0A3Gkoox2SfSWG\nRr5AFJKoWyluFllQ1RPi9ps8ANj3iSjuhHThM3CFg0lWrt7vtUqtNkWmy2Di\nRAQ9GhEQnG5xu/h4SvkPlPjYIRAk2wfNNvY4UYt5M622kdLT5JgBnR8AnWf2\nXYL43qDPNgsi86GNUv1trnQNSH0r3O97JSdEEty6g+V9suAzMZyWRhVnaiQr\npbZd2j6o3+m2Kc3k2FyaRKo109bXOc9mKZPIs2rh6EqxYLmLjxtT+rneXKM1\n6EF5\r\n=ZcAc\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0c3e9dea6fb8483bb9daa07e19f916296e48dceb","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.215","@balena/jellyfish-metrics":"0.1.34","@balena/jellyfish-environment":"^2.3.32"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.3.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.88","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.13_1611566395628_0.9229916266292728","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.14-renovate-non-major-350ee9aaf6de4b523bad9b0e1837f27a534c8550":{"name":"@balena/jellyfish-core","version":"1.2.14-renovate-non-major-350ee9aaf6de4b523bad9b0e1837f27a534c8550","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.14-renovate-non-major-350ee9aaf6de4b523bad9b0e1837f27a534c8550","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"72c1365f34f13e243531d7f9fe1bb40eb1aeff15","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.14-renovate-non-major-350ee9aaf6de4b523bad9b0e1837f27a534c8550.tgz","fileCount":95,"integrity":"sha512-CO+6lCykhUM8fA27eFZCwYBxvb0x+CJUZueISl+VvZEYYGUtJyVIs2Pc3q04UfYPqbFNPBBSzqXZIDWHrayluA==","signatures":[{"sig":"MEUCIQC3NUE8SCtvDUp4nwjeFkP5jL/nXev9j12XgpKjKWOADwIgd5MydPrkGnFRHdF6Lg0WOaTJTVvbWAEME24OM3TIP90=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":589732,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgDpmpCRA9TVsSAnZWagAAyYsP/0jH+4IhyGeHAi1x5msD\nbgaVmMTqri34QzQh24pcMB5darGcXcoF55TM+yNYQuAmp5eKnR7vbUONtp5j\nV86kWEjmgswldjIt5AyGyu5cADP0dWy5AHL6G3dXXPcEv+wRgVWmW5aCszEp\nkZ/7o9XKrBUkw/yXAQXM+w/PjOtYsqC0/l6Yorswsj3Y2U8yiiAYZP1YZ85S\nOsBwHGBMvtDHhYOmlo/z1GqwNndU90Bp8xSaC26C4dWxe694WjgfRjqYnB5h\nJXz/B+DReUEhJX6ythWccVNhTVwApeMrz2+dkZKnKfCol1Rs5mqY9a04BuJ/\nNDjM5Kjut7cGYRzr7f0PuqC3uPGBPlsAkqJyZpnuQbTZdLTOo8PT0Hao8Nkn\nqzdWLKxLPqA37AEXqHd4xiP/KF/98EUejJaSL8uF38Bfxo5Bd++ezVaeudgL\n+iKoKqMebmSocHpztmSXJUad4zeeFocGZDds/lw65gXtvypmJR8F4umIJ3BB\nbujkVBq4LIwfOuJl+xtNz5cj36C45i+BkftKgCWTAt4U+F1bqjqWim3oQQgG\n/Iu2F2qqAv0uxm6Fs/RPvNxtMu1aXrK+47gET5zbcRQPg5Uj15Fa8N6xMqYY\n0ggvaQUqdYRMX5VBmGg0l+QLZiPkcu8+hYtJ8/1rnKsP5AWRAPINs45U/T+U\nc5GG\r\n=y8Af\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"350ee9aaf6de4b523bad9b0e1837f27a534c8550","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.216","@balena/jellyfish-metrics":"0.1.34","@balena/jellyfish-environment":"^2.3.32"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.3.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.88","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.14-renovate-non-major-350ee9aaf6de4b523bad9b0e1837f27a534c8550_1611569576486_0.6517264800111453","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.14":{"name":"@balena/jellyfish-core","version":"1.2.14","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.14","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"daa1c5634c946e1e1267c6a28b8fd03cea79e782","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.14.tgz","fileCount":95,"integrity":"sha512-KwCFMhBEnnzxjcfaXX2JCeHQpVysCW/Ijq3aUCtJ6e8sryWAa90hMYOlT+KsMuUJrXGNBbA44bipN/t+4+ByrQ==","signatures":[{"sig":"MEUCIQCH+g1d6lhLdovVphOCXCZmGnRjdTPQUviSVJQp3t4JlAIgP2ERbzCcpGezxZIN0eu5/C8vckL2TyS152pCbG00h6E=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":589672,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgDpskCRA9TVsSAnZWagAASssQAJ0hAXmSeGoQ+adWH8HU\nB4yfv2SuDXBITvQPqUaIXda9CI81/fsxnUufEy8wzzyycEOkRjMrhCoF5e4Z\nWtNM2jHpqdSo/gCM3cw9ZYB3no/ynyeeEfYEF08/yqsHiLYj8SumT5c6W5Vt\nBYBImiN3yFK0CRIwBXuCeYq9QdyFJDZ3Y9Y3LmrHVdCebO+of8I80s/DGVZt\nTDXR0EOILleX0mFXMG/IRg1TSA+CAoSmZa6ilBpF9rkQuaZ8maYmDCWPOQKc\nHqOiCOKYBa+kjMMQwXGFHKeLCD9TkX0wjb8Ui9NWps6WQJXeiSxCz8TSQHlq\n+sJwDffCrdvITtoRJlFjleAIkurRRuh6gOLOjqYTeDei09/O7GtcnjdniU/q\nudFwPFGGefSYGOYbvInLruwF6fUfzOs7Nh+aWNykMVWhDviCNf2QRVWKNCBp\nnoP3VTbI3B78GeSbnWF+n2xsqwRN7MKdmKLU6rObnBtYAXLeIiiprhezJ3Rz\n83GXTBvgcJ09KLlh8RDVg85KX51zab1aqLjDf5vnzoQPULMCrmuNpEl8u3yU\nVenQoNy/IFDj+M+mhwFNTyICqYXMC2Y1Pwmb0q0b7VK4rN7lMHyEzvGmMqM0\n41dsY1nez/7mzSpLs76UbS48vZwoCop1ZBiYxnnals3Us0cvhjDCdxY2SCPy\nq06q\r\n=1q7M\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2a78746457a8eca7092c6935f760852927b33a41","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.216","@balena/jellyfish-metrics":"0.1.34","@balena/jellyfish-environment":"^2.3.32"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.3.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.88","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.14_1611569955927_0.27724982433529366","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.15-renovate-non-major-9f6105f1567e98c34b837feb055c5d3d2192bcda":{"name":"@balena/jellyfish-core","version":"1.2.15-renovate-non-major-9f6105f1567e98c34b837feb055c5d3d2192bcda","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.15-renovate-non-major-9f6105f1567e98c34b837feb055c5d3d2192bcda","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ac8d580d094dca7119711a423d459eebd8af30b9","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.15-renovate-non-major-9f6105f1567e98c34b837feb055c5d3d2192bcda.tgz","fileCount":95,"integrity":"sha512-O4Z+9QBpxtIS62S8leenriGOZSp2fkyHm6pqQhpk/uebTRLp16pgaFt8I6zp1KZpAkN3fdiJ2XOqhr0NaZrhOw==","signatures":[{"sig":"MEUCIAZC0nnGn8gJxCDOAGHx8LeE6IJmmHB5/LZfBF7jMK3pAiEAgDWqRienl/4ekEsOhLZY+6rcRKpZnhFMGspiaLSCu3M=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":589836,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgDqpSCRA9TVsSAnZWagAAqO4P/1QwSpbJ8PppJDabPeYG\nYdVm14KNnQRRPQS41rjWIqK9TL4X1ZiMGPiiRqVZJ8lBejrNDxEd/kKdDw0Q\n9oji9ZmyijweGllGockLbAr/7XRI4eUtky9aBtrpLpsFPCNePQ9EUAsielBw\nyB0mzrBXD+lczT2uwTdihDiFaEkXc3IjbqQdIKW+UyZrWuIpMBACHeYP4+ks\nHQpAHxrTeGgfw3+tg1geERzV3uKVJ4faHSlCRCQIX4WSeDWboH01DHv0fzMZ\n+It2DAz2GrND9sT19LSzA0D+op+J8ThRmcDyY7t+Lol/0PnKCgsSMq3n4/5H\nArJRNyU8VsKPUsxDMzuJFuuGkSQz9j598iDZ7aSG7ABjH4K7uf18sHzx+wNQ\nHgrvw/r5a66Abyt16h1RRV40xe+4JlEwxRn4cZqzb6cnFDo5tailLLAd86Ft\nHYpED3EwQVdiVzrgz9tY2YcIA/NDNzcaaqwk+g9poEqvS9+oQ2luj1DKFxPX\nk4I1SMUoW4yEXsRA6pdHapZtKVrQViA7hV1XXNpU+TeyUCuTr+ThsFVxXGfY\nT8ZU2yYJ+FA1m6ni6G2XXeakBktmcRgN11V0GPekHyFp6WAQUp6oFdyU6RA8\ngjGckrhRuS8ErCUSq0hk9oy8yFKOgHkcc3eT9RcL13Sb2GGcTa37jylejwCn\n1lRn\r\n=oVOB\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"9f6105f1567e98c34b837feb055c5d3d2192bcda","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.216","@balena/jellyfish-metrics":"0.1.35","@balena/jellyfish-environment":"^2.3.32"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.3.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.88","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.15-renovate-non-major-9f6105f1567e98c34b837feb055c5d3d2192bcda_1611573841677_0.1275061990426527","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.15":{"name":"@balena/jellyfish-core","version":"1.2.15","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.15","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"326f1de804ad9e3ca16ab24c8830a7422775a930","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.15.tgz","fileCount":95,"integrity":"sha512-yMnn7GulusymD3bCjP2gIrmSmXw3V6QeSgh90tzlRziitRVI5kLX6XBoa8g0IJ99WH1LT+mbbvJplE9+QMLGKw==","signatures":[{"sig":"MEUCIHzhA/fGFDJ8a6ECLC4mRj+R+elQmru6FFOjL4cwsbrAAiEAg4NU98RYv+tLBzQ/GfeInJA4kEVVyK0JJkGOGq3xMT4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":589776,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgDqwJCRA9TVsSAnZWagAADRwP+QGn2xHZbQlYQuBCckTJ\nUuR1tbWVZQbullU8p+pMSRUIh+WfAH7tAI87ZThWXcBQNN8xUoRq7m4kBdTJ\nctMop/oVUQut4OUdjHnWCzHb7utBl7hchOCP/FA3COvVzzPy0bsXEx1wC1+V\nBYWasd1fjerdJeshOqV0Cmdyqx/n9XaBJEiqvjNHx5UjtaPWxbN+vRYXZwLl\nXF/wJrfrIlYxcBO3EcZGoATwef5aCI0aagjBUB/fycwbY9m5x9GubIFt4cri\nxkxUVLzNlXNpgF+6taACro9cy/NjNWUT/ztfGfOPz3MjjW3ovnpgGcjOYdo4\nXt4yicwemVMOHXNAI4v53eDnPDA6D5XtDIFu9lOAd0krxmN6FdCqCFcxBAOa\nmcZC+KesErTVsFrD5eZ4XnICvemzCoDid+2zP8XUAHlGxK0zmCa3tTEGgIn4\n2qjGYUY1QJKGu7/3fIFWivUUuXtcWhPaydmSRrBTBvfV/39S1VvSUKBGmTFF\nCrLmcM6DxjWEo3DFUSOUnOlunQTsquroT7CJzJev8ACqC5OrUqCi8H/sE6GS\nwjmYpqPcpKrse8CQfbg999FJ/gkcNxZ7pILo8NUe4eqKalpjK8MnpaL2+TRI\njRkzZmkeCGI8Eda0oBPhXrWkEMc7RW7lMalH+57VgOvc17oxeYfT2b0Spimo\nlKbM\r\n=9xCQ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ae52259f55995ac615c03637fc676a9cf83fa029","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.216","@balena/jellyfish-metrics":"0.1.35","@balena/jellyfish-environment":"^2.3.32"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.3.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.88","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.15_1611574280979_0.004544232280131899","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.16-renovate-non-major-dfc4f4712d4a4c4829659c13c6f98cb32f855309":{"name":"@balena/jellyfish-core","version":"1.2.16-renovate-non-major-dfc4f4712d4a4c4829659c13c6f98cb32f855309","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.16-renovate-non-major-dfc4f4712d4a4c4829659c13c6f98cb32f855309","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"164c5e31907acec586a2675e252bd0b9f2344b64","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.16-renovate-non-major-dfc4f4712d4a4c4829659c13c6f98cb32f855309.tgz","fileCount":95,"integrity":"sha512-DIoXx6UUVG6QMMeNIfnBvUJoNNc4ZQu951cF4XJg5MhW5aUmZTW3x2wFG4AwQCBbC8WiOd6ik9nBlZeMjAI1KQ==","signatures":[{"sig":"MEUCIQDDO31SWBpL6yiK9a14L3PzQN+fX7LPK/wZIVy7wSWymAIgM4maeaCLTsI/j1GtlpSbTVfXabHGI/Jr1VOmztTomZU=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":589940,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgDskaCRA9TVsSAnZWagAA0/IQAJsvK+lvZD3L4lzlWUx5\nApMv7XSUCAXP5Cv+q5b/DLDZby4PH6oWAxLcEGfRraq7TSTFfMf8LV6DwWDR\nU4qRiuBXdXwvD7XiY21CVln0YbXCgQxipVLbYNQqeiIsCE7EhoSV5WwJjxjr\n81LEFmjasy3Q7aHZipDkUgwwPwuCkQSc1k0tb9n2I3MKU15Au6hI+8F4KQoL\n4r9sNwYwyPAA+JR+JYbU/aJmbNNbSlrWTb+TVbAAQUJvn0yOzM1vjBKPIIkB\nD6GcG8e12Q5TnCUgXQEnGLbXWSJ/RqXXAOEva1avaMVGDxA/5a4In0eKz8W2\nLboRGO/0FPCdDPM50rTAXJJuOO4mMTLi33hQhubTpYTcjIKm1L5TBAlJDcVy\ngBEVXxiDlyETRH4JFD7pchPALfdFDmyXjtl51ec2BoQljbCymqEz31EiFejT\nAZbZ+l/0VbI1wVAa0/nZKEtArMKuZDe2eLbw/HxQnqlRKkXWxB1kQZmjp3eG\nddVUXGiBKARCONBglrUCo/v3TB66sohH6vFDpwmEaSF/1lZZEOu7kqk0h6u+\nJpilIY22zb2bgbA7OUGbmNtZdXZXtkumowK46/SRzwrLCPGSRUoFxYEHjkCT\nfHDcI1j8MxxJT72CBrQXEnDAREMEiG7CgtsH67PKkZCNYYbOl5hCENqBH1JG\nIBeK\r\n=EQv6\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"dfc4f4712d4a4c4829659c13c6f98cb32f855309","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.217","@balena/jellyfish-metrics":"0.1.35","@balena/jellyfish-environment":"^2.3.32"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.3.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.88","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.16-renovate-non-major-dfc4f4712d4a4c4829659c13c6f98cb32f855309_1611581722215_0.3362657693237956","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.16-renovate-non-major-843c21c80d628fdde8087b926d8008e3ada11963":{"name":"@balena/jellyfish-core","version":"1.2.16-renovate-non-major-843c21c80d628fdde8087b926d8008e3ada11963","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.16-renovate-non-major-843c21c80d628fdde8087b926d8008e3ada11963","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"57eaa5af448051fee527c73bb8ccdf9d69353b27","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.16-renovate-non-major-843c21c80d628fdde8087b926d8008e3ada11963.tgz","fileCount":95,"integrity":"sha512-BFhRvDG7MkIdBqopI4oLnAgM2FNoBYMqN7+6zvqiBZsONEUyqdW1E4TUMZ9WuiIkFa/CrsmGYK6wdI9yAEriIw==","signatures":[{"sig":"MEQCIE5/8F4aZUDlRvKKQocA/KEgFQ5xK9b2GBrh/qBsuQC8AiAZ7z5z6rHgSI8/3+Iad0vpllBgKYeBQUZrdm0xkq7Cuw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":589902,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgDtxKCRA9TVsSAnZWagAATvwP/R8S/D+LRXzvvp2tWX3t\n8DcVTuuOaB2N+IpLdcizebuVGiYEva3WktT3VAsYdkjLhDRQWzmafNbn+wNW\nDoRos4rav8smRni13tpVzGwgKzsh00U/DqpnuRElafJgZWn1GCyIJ086Hdp4\nveP7TtqXoz31loQFn3jLaqf1FmbCVk/z+WSJaucvznUiD6HD3LpEye4Xrt9x\nXt6Y1p6tjxszEJLEUVVV9C0aY0d3EniRHtx0g6SM/9U5pZmIj8o6oEZyHXpF\nm+w6ZBsgfgnXXohijnXUZ0Pyry7XDnerFZiKWhMOlpsp7V9gmwTGML7QFIol\nRqple14HZAYu394vNMlwa7WDrsxip42sT7hmbDkvHJxrlicOYsgiY9X1FP2+\nEEj9LT0r4EjLRDYFPAIvmlgAK2iLqpUhA+K9+chhDF0izFNBDmAQJF7uWy3W\nrKeissxm5NBNTno+n4Tmk3mhGZ+APkUk5nCPTpnEbizXYBTL56qdEl4P6Z0F\nFy4Gw9yBNpLN1kgLAVqPEdCEeAOuiBR5kg2XrPkeLzZXgfKCcmI4MRjYZ3Ph\nLpzrmNpUk0f6gI+oxiF3Tlwn4Lh+D1QlBHTIuxYLBlNDRqzCetciSsPrmNlx\nUr/Pv5INZWcFskPM2NPhseGPV6gYtHdERqB2HwCqhe/m4CZ2BH46TlTO4zvS\n+44w\r\n=IRI4\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"843c21c80d628fdde8087b926d8008e3ada11963","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.217","@balena/jellyfish-metrics":"0.1.36","@balena/jellyfish-environment":"^2.3.32"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.3.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.88","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.16-renovate-non-major-843c21c80d628fdde8087b926d8008e3ada11963_1611586633887_0.6447839343841757","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.16":{"name":"@balena/jellyfish-core","version":"1.2.16","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.16","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"kaisoz","email":"tomast@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"fcd62113f10db080e861c3ae4f208a9f26d3deba","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.16.tgz","fileCount":95,"integrity":"sha512-kZ26SrVtkGadtBEAmKq3urtJt+BMeinLPRx+71KjLtLBxhZGcoExpX833TA3VhOWg/7dmvBS1rP0hdNx7zOb9A==","signatures":[{"sig":"MEUCIQC69+kz/KuTwIbn0M/NSr97ueZStQB7XiGhjrR7+Rl0+AIgV2YDLL0bVWXnixMNVOn4CYmVv4jyAQDJctkz0Tl26VE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":589842,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgDt3sCRA9TVsSAnZWagAAkFoP/112UQKUa12LHv42MC3z\nCkM7gFwXfi0ABwxFZr3wNyklJvbQzATkmfDZko0GGpD7Xg3paQM+ve5Z0PHF\nNYst5tiEBmLtdAAlb76FO64bBCKtNA68WvZZnZZScNqhncEqAmI0962WuzAe\n4wISZTOL9aMkZMA2qE42RKLk+Oj8nrlyY72CE4WVgVdvzx80exWom8T3Mi+S\nwypXntZwlU/JlU2cyFPNNVGS3F4R85tXqou1MbQrIXNBIKZM59t6ZnPrIWg4\nW83ihJCjjE3nwklUt7zPaHHk5wDjBHHSpA+N1qW5NSxnhK0EaiZ8b7Cd25iA\nsa/+HTEOlFzv3db3irSHVX/Rz9yT3xRBuX1klARoMY7Ox9vPNlKMvZ1u5wSp\n+U/raGgnsFyXSl7PJksyG6Ph+txmndQQLvgFF3zPdqA50kFfh8ScLpzdoEBR\nLHzOGz15p6/B/wVOMxK1BCcYwGMe/MZaxTkyFsLx7qwjETPLkkJG84eLHGWX\nDpb6mLyE+Owirrtr9FGzjsxsLrZ4XhQYVizuuFkauwsuiYDb3yYp70WPdxJE\nXJ0NPAEwEkePfY+WG9zu+gU3XgyOtR4c+yV6NEHQF3lAHDjLVJY3RPMQM/0A\nLShzdZmEMz+yg9cyl7ZP7Fy6OXOpTexpAB1umNCGTKxzgVHU5eZ317NJWjdV\nKVfI\r\n=XeLo\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"105e0403d4d77de980ca7f49b1f01ac76f8664b8","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.217","@balena/jellyfish-metrics":"0.1.36","@balena/jellyfish-environment":"^2.3.32"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.3.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.88","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.16_1611587051600_0.6001180278758225","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.3.0-add-session-scope-c49abf2a59dadb72ab67c3dbc51c31b01458dfd5":{"name":"@balena/jellyfish-core","version":"1.3.0-add-session-scope-c49abf2a59dadb72ab67c3dbc51c31b01458dfd5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.3.0-add-session-scope-c49abf2a59dadb72ab67c3dbc51c31b01458dfd5","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"686e01b0061bfdbcad91f3ba23f3136f96f63c98","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.3.0-add-session-scope-c49abf2a59dadb72ab67c3dbc51c31b01458dfd5.tgz","fileCount":95,"integrity":"sha512-x0ggCqhK816Hbpw1eceRVB6Vdr1wiX69CzMTy0/EPxFAXwhpqK7c1QBJaepXrEkQ/3M61gEW1kmHDIPL+Teecw==","signatures":[{"sig":"MEQCIEx+52Clhp/njwxEG+Lj67re5CKdl4geTan87BLkbmm4AiAVP63vsvRYohoovwWXjGVrforLSblLZDL9lrMq7YmR+g==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":590151,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgD8P1CRA9TVsSAnZWagAAilMP/3REa2ER+88SSj/9d0KP\noECCTObVScI79vZZeku0zLRZ+YZqFM97YVY6lVVoUu1ThE7mcksGP7UxN2ac\nz8Wsmy9beYzNqePlKIM3obQDNfcMEKEY3tO1gda2lGXJ3lP7LEYg7tW79tk6\nx2hGmCMyZbGMkEpmH6Ba/SneFIRodjVS2RLOPTqxeojjDKoCt+dCMRUp3WYu\nwocKk1ARYnXVipn++cUYIM59+AAlmN9woYz7PrYv9QrUCDr2xVbLF1958l38\nrWG5rjDCEJBWgvFSIxiMtT8SrWRmXOSUnTGmET5aWub0wC7rb049sJOtHsP5\nwiBFbVMqiMtvKe+A+P+gaswYjJTxThB31+4lgkAkxPaSbSMD5Y4xJveVZ7me\nfantLr+nYxuJJvWllMqqGdRe6mRcQdWM+YQY+l8dv/N7z1BC2XcMJwn4sEUt\nvq8TUcs6HJFsDfX1e3DVjmPwijFiyhxjboFn7j7+Ygtte4v1W2GeEYU6AyyO\niAxMTy3PEeKrohs6YJcKiM1d9xrC7yBS1y6biA5p0BMx3YB7jpHUmcuoz0x5\nQjE6m06KKtJ/nVew2uBZeUpZALnTcA6pGnxH4qEi5FIcBfjMV1xyjfxqHH5T\nEczagnM9jIVV3b7tYGUUZKkCS/XHxu1z+L9k8q2fnSlJ/ievqjSJGuN0DVHU\n96vl\r\n=KrVm\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c49abf2a59dadb72ab67c3dbc51c31b01458dfd5","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.217","@balena/jellyfish-metrics":"0.1.36","@balena/jellyfish-environment":"^2.3.32"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.3.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.88","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.3.0-add-session-scope-c49abf2a59dadb72ab67c3dbc51c31b01458dfd5_1611645940851_0.8566617073133724","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.3.0-add-session-scope-bcadac6d252feea83704167a7b14dbe172c6064d":{"name":"@balena/jellyfish-core","version":"1.3.0-add-session-scope-bcadac6d252feea83704167a7b14dbe172c6064d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.3.0-add-session-scope-bcadac6d252feea83704167a7b14dbe172c6064d","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2fb60d4028d0641e256474287938c59ecfbddbdb","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.3.0-add-session-scope-bcadac6d252feea83704167a7b14dbe172c6064d.tgz","fileCount":95,"integrity":"sha512-mt7EgfiGKvK+jp3H3qZT6d0/dE1p64uUgf/i39Xp28GVdg5XuwMgOJ/Nfp+4R4hSG+RQOOzYidGq4+SH8k3rsA==","signatures":[{"sig":"MEUCIQDOppFFxppX5jWvfO7FiFCw2Xq/TGSY/3mlq8n9ESXmSwIgRgLNqsr42PjSGGTkCVt9R+SDp7/aVp+9eSl84vBsnW4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":590284,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgENwyCRA9TVsSAnZWagAALXwP/jveiMcxj5v13SZAGbVL\nMRSAoYXYtHuXhXTI4nsul+zHAL+IaVJmepaRUKRm366qBPolE13O8rRESFMH\nuDINynYpUiHItfnMQ2Han6S6liYhZuedN9dZrWFYEJIKyoz6CHFUXyezcHbC\nNs05C5knNHXRueKswo/Mx19ThxiETpVWYCJOO6/zlJ4YRlv5Q8zA2qp9sI8E\na+81XLtOjVUh8f6D7dTl6QguC9Yk3AymmoEs6iak0axW4XSIL3GdOHlEWD7M\nSMHcXRgBbqSnX2fxW3wxleVym/HK9p66PTR5PoB/A5ay3wBmFc/i0aO31dAZ\njbo89X9ox6rEphesx+XKVBG+n7mpWQhlA0LIeCR33i8dn8zSTgVGabUf0P7V\npFFm76ejqtKTMuEVm+h6SZsrwHawjoB51xYWOHuwKcs9ei/LtM7aIx+7YC0G\nuVFVooVh5llJyfxP6tfbpbWjfylfYBWXj67d8Kfrs0oWHIxCs6/qpkeRhWqS\nhNq69BVazsjLr9HP5bvvy0VEuiFSOwW0Xd6p1uFU58Xzwh1Lc5nIjyJBo0XX\nOzuQMwLfXS9eHvbXkjO8MOgZbp5azBtA2QWI+K2dIVTMjJyp83zd7/x5U93b\nPjhMnUcKVREukMyXD34LnaMj/5UypXam9p9pOfDrsgoAG4Tm+5pLXhhsufX+\nhNpS\r\n=0g4/\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"bcadac6d252feea83704167a7b14dbe172c6064d","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.217","@balena/jellyfish-metrics":"0.1.36","@balena/jellyfish-environment":"^2.3.32"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.3.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.88","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.3.0-add-session-scope-bcadac6d252feea83704167a7b14dbe172c6064d_1611717682200_0.7637917391874507","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.3.0-add-session-scope-b5c445409f82a6a6892cfea34ab070eff6fded6f":{"name":"@balena/jellyfish-core","version":"1.3.0-add-session-scope-b5c445409f82a6a6892cfea34ab070eff6fded6f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.3.0-add-session-scope-b5c445409f82a6a6892cfea34ab070eff6fded6f","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"77797b15e4b9f6b3be65d48cbb4ee0db8325404c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.3.0-add-session-scope-b5c445409f82a6a6892cfea34ab070eff6fded6f.tgz","fileCount":95,"integrity":"sha512-IDfSzFvZBa4T692Pz/P/0zAnDnbQ3CGsDHKVJedrMtscu1keez32LDWsyjfAJLXsFFz5vX1Mf0sRa+OnYNiswQ==","signatures":[{"sig":"MEUCIAvFzNhlxI8cQ8htLELgcoQabaFx0llXomhvwBalG+HVAiEA46waIrY6kRpEA8UPSku/2B0V4QRE1F8x7mmQfkfRhwI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":590294,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgEN99CRA9TVsSAnZWagAA3hAQAIQguiHlswKFYIKQLH+L\ngutMJV9JObiPQ+v2Z+WBhI4KMcCIfpiU3I82R17QLVEIUcURXEwbcVyK2DQE\nZHjxx7rUWq+TUU9b52fbyFCQYxssIM713+C3PholN0HJtxWiwRVnWIygW0Sq\n2Utg2xyRdf3hSkETsU1PGGGEMPfZz7KdfAMFC+s20oOq9DeSfTCu3dbpAKX3\n4YGCtANbDlQfcKz639rO5vmCkq9+GjPBvhf+Omf1cBsojYPDUhvQraM171zY\n+H//+3ZmzxsA+GFaHHUOMm52ucJZ+o7YEElmt35wv11kz7kq22+ECPeAbUF3\ngG1qXd26QmCNBuoMa1LQLRCC+VQiXaszYOacv2YUMAkRp9+2KdwuUNyHixbc\n/iWq78t/2/VItMDeVW+bb8jhnX+LS04TkvjNQebccq5db/ZgB+P+yR/yc+fQ\n/TSjBCFUhP/OPTQTd0o595PX9Q95axTwWFtASZ++qjTojUuw9c53Xr7oTKDI\nCKF8058lQt44fQmfWNc6sTQPHJnY0HJXiWoVEFOaQXH25RoMNmoEIE411Vq5\nZ+ow4X5XUKIO0dE8idGbPiHd3BTKk6+gFy7M6hijAodinYDbvKyaqg/mafb2\nmlfcv6bwj/CFdBR5AlBzWRikYO0cvZLHFKYplBGkb9GIjBSZ8kRpV4ktTP90\n/fL5\r\n=3PH6\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b5c445409f82a6a6892cfea34ab070eff6fded6f","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.217","@balena/jellyfish-metrics":"0.1.36","@balena/jellyfish-environment":"^2.3.32"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.3.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.88","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.3.0-add-session-scope-b5c445409f82a6a6892cfea34ab070eff6fded6f_1611718525270_0.8971430158661859","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.17-renovate-non-major-5e2d76c7d030b42a1ef343b284035055b906ee7f":{"name":"@balena/jellyfish-core","version":"1.2.17-renovate-non-major-5e2d76c7d030b42a1ef343b284035055b906ee7f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.17-renovate-non-major-5e2d76c7d030b42a1ef343b284035055b906ee7f","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ef845940e1735f9febde72800be4e6fca39bffb0","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.17-renovate-non-major-5e2d76c7d030b42a1ef343b284035055b906ee7f.tgz","fileCount":95,"integrity":"sha512-hgFVoccD3jStJH5z71wi3Y/dsp6Dm3uayUf0bhUQDTODFsKuWijp8U1mi2hQC9Hc2kD3k2dLzr/EGSIBXCBAJA==","signatures":[{"sig":"MEUCIQCBftiCQel4WGOfBwZfTMIVyz/juME560lYhN3VxTHVYQIgArhtmEW3d8+mU3nsRP53YNESLkeMv97tVHHA+49H/QY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":589968,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgEPx7CRA9TVsSAnZWagAAb+IP/3ha4kkyY3QMF51Cbr/0\neat3IOhL0PG/Bo264JgadHp0er7SlyFBIzj0RvitH6kjpbCcV/oyygYvGwCz\n+wKcpTgbmizahN15SlnNk5mlKvhRG97Q3tOYaNt61s8X5o6O/YZcqQnYhlOI\nB2bDI3VkEoARG+t7OqTp4LWOJ5NgrWmhivmySVGUzYq/7/VRZC7K2t5GliAS\nqhj6Z+cmNDpvhhwy7Gge/QdFL6dWT8qL8jLge66PhFxl0Ywl2a7vDWdn0l5E\ncLH7CnT9vEKWWTu/DLBbJm3E3WWUwyS0Tf0JscSQvHW6om8Fb8+ZfoZcDijy\nejQxHU5QaC1tMY36T56Cvs5ZcpYCbpjyilnmaAYAMEUCGESHX6a6q22oEsEs\naFS83nuwHOHgb9qHGlfQQWQBZBoLyXmC0SQ0axaTVCTbL9jEmcUo92h2PCNG\ngbc4aZ0Vsg0xsvJwhqFSqrB9KcbEXQt8YzV3x04Ic+xiggoHqWhDZp6Vuo8m\npESCzNqFmcqybuwf913c7974pmOg32g04huH/3ivKWsEJXfUTxsw9ebnFtQc\nlg3ZInIejA0e/XBtkwfcdoMJisAUI6AsnTWJ+8pCX/F6COWBlBt2Kh0VU6m2\nVMVbGMnrn7A3nSk32tE05z49BSKPxh1BECDR+whUXWWF17vkk4wSfYAJT2WO\nZaWx\r\n=GhTY\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"5e2d76c7d030b42a1ef343b284035055b906ee7f","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.218","@balena/jellyfish-metrics":"0.1.36","@balena/jellyfish-environment":"^2.3.33"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.7","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.3.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.88","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.17-renovate-non-major-5e2d76c7d030b42a1ef343b284035055b906ee7f_1611725947496_0.6668880179299468","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.17":{"name":"@balena/jellyfish-core","version":"1.2.17","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.17","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6308d809f61ae20ae7590403abb6d83ec12717f5","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.17.tgz","fileCount":95,"integrity":"sha512-ay+nVgLr7f8sCMiz21S8aPnLRWZ/VQ75mBDhR3Or19XUUKwm0qYApq2Su4IQU+vXfiik3vu7axXTDjC3a19QsQ==","signatures":[{"sig":"MEUCIQDftvqG9tOqEoW1D+FviWu4UyezX8/b94s23RKxeZhE1wIgWZ3AFvchhi6KOckKuMyhkX/PlA9LlkzLRS2fKs4ehcI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":589908,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgEP3GCRA9TVsSAnZWagAAt+EP/RHZyEJYyhSsihT2mlRQ\nh+eYji2MpvxeAlB0yxmPuMLTjVZICE8680IyPdd117BADk0ztVN5yTPNyu+h\nP0SAC1qHhANz+/5DjlBGxpAWKDe5Pi3pYXgwEewYqgEJ+xiNy30X3b2bUwPS\nKIyjPOq7xItDqbu8Znlej779y54yuVhgTRSRpwPwGPe6YQG0kUBOaqkpQ9EB\n0YA9FKh+eOMuS4MarYbnciqvndTye9dkR/U4UtuHflpygZnlhoqLreCWINJQ\nCdkYnyhf55EkCDGGVj8dcrjloIkIJu7ExV65fZnRVjHWthgFgtgd2hedjuos\noRhVWhdt+GTSmPDM+Ez+xaIi9PVNLT9g8y041ErTINDHSj3zcczAa7VQALjt\n1xszxLmmqJ90Y50vjVwlj4je8hpsok3jU4WrjqeVIB7d9hTNT6Wmht91qjdz\nzqQzQPLAw6MW8v3tvu+wjD5Q0PgBB3wYi1PZcXTf+NaABf6i2VJsIbIFWGWa\n8zfnb3hUY4SNXBOJgPYfGkGNBYFVWMPh2YTeWf/Aial0ezZliJifjkIO30td\nHN+cUJX8Yx3xtl253x/9w5H2xEqS1kuSVSZH978raX93bTrw1X8XiX6JIp4T\nTqiFh03kwIUKhsIx7KxTDuZ1fZ7JKPFGcno37cy5VYBaHX2XRTeVFruHKX7x\nfpoZ\r\n=WrTl\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"605614f20801b72c767ea88463dbe446b25b757f","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.218","@balena/jellyfish-metrics":"0.1.36","@balena/jellyfish-environment":"^2.3.33"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.7","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.3.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.88","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.17_1611726278320_0.06030927649995155","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.18-renovate-non-major-e661c265ae6dee61b123c7138b021f8b22a92eb1":{"name":"@balena/jellyfish-core","version":"1.2.18-renovate-non-major-e661c265ae6dee61b123c7138b021f8b22a92eb1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.18-renovate-non-major-e661c265ae6dee61b123c7138b021f8b22a92eb1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8b865c4044660c4efde09c83332fe24cd27f8d53","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.18-renovate-non-major-e661c265ae6dee61b123c7138b021f8b22a92eb1.tgz","fileCount":95,"integrity":"sha512-mE/2zlf6BwuTHHUJryDDEOE2jn6NUfU4WoQK8gD8E3oplzNK8a8Wacc4TzDyikFKtr4SZcCIbmmXQvghzpVvgg==","signatures":[{"sig":"MEUCIQCW5XwzI/ThpFRjKb80f6eqxDeqce8IrAul2Kh4Fz3IlgIgJW0o+jeUHQPlWyQPfn9ENLB8DVtH5SG3mgtaJyJ0izA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":590072,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgEQYvCRA9TVsSAnZWagAAow8QAIU08/yGg1fGE0klUEXs\np5ekv9taJBWMBaXcguT5ptdZxZQnSKtQro2Q7QQ4jP24114Oeo+xy/mScvYs\nn15DIsAwdD7gsCdqJw8nYWz9wHQpwI3Jk3xkbl/2i1ID/MphupeIm8i7fG12\nGhggcrllPTSMTsttQuzg4kiCJlohuJrmjOd3+NApEnbIWY1MtMXKOOzqAjvU\nyknYMrfrOPLtbq9yyJY7fFbCyiF6zH7xijqdGhHIvqtpU0ZveVZ4I2gbV1fj\ny0r99QV/lRCpaOjJo6G/tN1lLDiNYEPQpxYi87GwpuLtw4HQAWpSWxFHkA08\nrRscUz0i6I144giZhd9DUZLv/3xRBYu5wDjcdxirwMaqDHkiN+9uxuaqmQZI\nlE4aoG4h0mV/4sF0tMVFRqqAaNvymtuZV5IRMLyGPiLzSrE5wj96uAppxqES\nMSGYCnl0dlpDjlvQzTia9NDWFB7rU8I1BGweTBw6X7z9GBhghMsfEsB3t7PK\nu0+K1YbQMFdfBxF/VtQYL7fomHrWXzXZWPf/oaD1lmBuesz0y4PFunxVRJMA\nZD/36MQoF+pFS1iM92GLuFSy+oHGfAFrnx5384iYQ0PH1khdiOpllAjijOQ+\n4RQ8VqL5Na4a+NEsM4wU+h6YagUbQUUxf1Q3wAl3HjyTFzW/JLC1em1UK8bY\nIB6Y\r\n=TmhV\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e661c265ae6dee61b123c7138b021f8b22a92eb1","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.218","@balena/jellyfish-metrics":"0.1.37","@balena/jellyfish-environment":"^2.3.33"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.7","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.3.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.88","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.18-renovate-non-major-e661c265ae6dee61b123c7138b021f8b22a92eb1_1611728431371_0.42128789433488034","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.18":{"name":"@balena/jellyfish-core","version":"1.2.18","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.18","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a53fa5fce0f226ce5c28be554493881dafac7241","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.18.tgz","fileCount":95,"integrity":"sha512-0GRUail0gNunY1deCgwEBFDZ6gGMgZRTXogqvWvVrrvYy1AqHkexK9r05Gtt437mdexdK9DcqDyusWiDcWBgBw==","signatures":[{"sig":"MEUCICHcv62IMD1L0SxYMqrKt/RUyeqfUo1g30Kx7Jg3MhuCAiEA0olh5lxrhfFajDe/yWBB29yaQLIticuktdSEIpekD54=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":590012,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgEQfUCRA9TVsSAnZWagAAGxgP/0PGpLYjWi9bm1425McL\nOxZsYz6F1pZhNz4DNmLNJAZNI5C3tc8ej/y/1C54d9SFLxfFdm8IL5jC8b+X\nWdXwtvXzkj1ENBi0+CehpA+2l0tf/rFLv3Smvc2m71QGo2WXm6FFha0ynvLD\nVoJuyAMsC6a69E4JaL0+aBCzMdrAzDsJJbxJNf3j4aRSQR+OpGK/DPeDvSJe\nd2gNojsDnNILc8EbdQsClLYsH2uePk9gMw80IcZA88e5DB951b7JbKX/3SN1\nzPMv4J1OiFxzc4nPYt4eJPB/STpMtR3qN5Z18Nf5GLsynW3EA34sutNKY45B\nmGdnyqEbNEHt+acy3sGCvN3N1F+WmyuOeocUiKSQoDjEkLNPmlzCvWODaif8\nimW6H3FD+EAQNVrCmBmXIDGtxs5NTEk5aL5DIJq6NHV4WkeuPnTiEn770Wzx\ndJxaXTJ4I456swBNog3GnWDRgbZnpAcWmsYi8owRkIiV//V2FywO/jHNJS1h\ngsNpc5b8Ei4/SNc8n/lMyUGodUYt3cqUO6TZAnUR7LlksQiLE5juFfkTBLqf\ngKLK+Z25Ud28LVTIzpZjGGRIFJmvstioLhcKHotdKtBdqd5AFjglL+/prEQe\nrfl+gskyjAFFpRDEeRzLvxbD2m5zF0qzhXjMW6Ky63Jn1W/zkanXJzR6fsPX\nLTXO\r\n=yPhe\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4f5a093e6f9865de174c180adccdbd5f71878368","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.218","@balena/jellyfish-metrics":"0.1.37","@balena/jellyfish-environment":"^2.3.33"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.7","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.3.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.88","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.18_1611728852260_0.0693898303873306","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.0-5508-pre-build-versions-f1b168fee1704cc09b134cd7586b426e8ddfb27f":{"name":"@balena/jellyfish-core","version":"1.2.0-5508-pre-build-versions-f1b168fee1704cc09b134cd7586b426e8ddfb27f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.0-5508-pre-build-versions-f1b168fee1704cc09b134cd7586b426e8ddfb27f","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0fa15548313e64b3e35f4981621b3324310a58c0","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.0-5508-pre-build-versions-f1b168fee1704cc09b134cd7586b426e8ddfb27f.tgz","fileCount":95,"integrity":"sha512-RzlbqSTAq7hikkMDSwVHk+N6hiPei/8WiXun7KRoSNw4UinJPYQosZL+43pRm84OZeOXOUKzxTNU2t9nN9Jt8Q==","signatures":[{"sig":"MEYCIQCeB1egaHDENtxRMpqA1gxAZ+ouHC9CeUBi5VHZ/0aFAQIhAKAwtZgnYu1b7jCKxjQGOwDk932AGVzNM7HgqfVJVBJK","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":594720,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgEUOlCRA9TVsSAnZWagAA3BIP/A7mfXQQk+fa/WxRGe7y\nQboocc2NqmuY3k8VcHvgryKhRuzfHoJMeJcq0NCF9zuLWD3b12jC0KMlTY+8\nqb78bPqC3OT/K4kO5FRly/LPKAmphdkP5kV2ijNUQWYx0zvdSgdxikOpWPzV\nC0HTDN3uJ9thItO/3w1atnIaMzDWPgVjKlGZI0GsYKAy1pZbVODGDHc/ETx7\nEFZFBlu53oXs0PjGHmBTEsBSg7ixk3xdYSFrubSfhbJNIR2DhDJrzLYzcd2F\n9bRBJBf/g8uXkRna1qD++TfMzlffSAmOFbejGJCGwm2ChvKdiolFMFcwTDzK\nevyJKX6eKYnzH/y7ODZxJIMjJ5jSPnjrPvE3Mnca306NMi2SHFy1Y+2F2JBA\nm+CxBSt+FyXyFhJRr/je7JT4Lp473/WWWnJAtli63f9yvFBsE+//XFkx4YGW\nYDR0o4lIcQPXPGQvvqM+f1+pLuWHpsMHXbbwD665jqVhEpH6TkduupUYN2jo\nZanxlqhlzTOHsa7eq3bCLX1vjHYDz5RloSg3alnPlMGFkEuU1ZqC11+1IABK\nfIZJ+Kma+4eGYbohLOvz+Xjxvv/ADKE/7rU3QBvR0Cq6rhWNoEgJdjunY+wB\nGP57fxMRMwKlMM3elxqfv/KoZLVLWUkVAJta9fBDJiH6YJSRd/neGHk4vaxt\nQISt\r\n=TqUA\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f1b168fee1704cc09b134cd7586b426e8ddfb27f","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.207","@balena/jellyfish-metrics":"0.1.21","@balena/jellyfish-environment":"^2.3.25"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.80","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.0-5508-pre-build-versions-f1b168fee1704cc09b134cd7586b426e8ddfb27f_1611744165257_0.1605763099168458","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.19-renovate-non-major-4a99705044dc40658b7925a9ad049fc848b65d7f":{"name":"@balena/jellyfish-core","version":"1.2.19-renovate-non-major-4a99705044dc40658b7925a9ad049fc848b65d7f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.19-renovate-non-major-4a99705044dc40658b7925a9ad049fc848b65d7f","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f0403f10667ea897fc973cbc80cbc64513246e50","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.19-renovate-non-major-4a99705044dc40658b7925a9ad049fc848b65d7f.tgz","fileCount":95,"integrity":"sha512-oeEvytzv5nNDUbul2Zb3KGkP1O9gIr8wghGwMgE9BJqld1MxDXDXzWQlBZ33MqDT9dkWGg1NaCObyQL15zZ1+w==","signatures":[{"sig":"MEUCIQDR9XeBCLQUKMZV2lzEWxijqHiimTEobY+vhYUZ09l2jQIgK6dVszYq45wdfLTDS1A2x6mBpGDH20Owp2Bw5uM2nNk=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":590170,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgEUTJCRA9TVsSAnZWagAADXIP/1nGHQEAHtzH+8N9dki5\n8RWRmxgApWuj33j+cx+BeRvf+cMoeDWfihL7Poyd0wZpPet3zfKr9vDxoJva\n1iro+6mNrC1FEJX6lejM7pV/feg4GfHUCudbr1D9PPSDoqKepoPbJ21A27Ly\nN49xS8jd9yVMOjqQbzw15/3WyLbX++jl4xKP+SpLLbf+MkNgg04STRcx05Vq\nXy4NldTzQPj3CxlfCdghO9YD/50/tAHAkUNYjz8CDAL852+4l3LubVuKM2Ic\ne6hQ5anRcWMdJubO1d/7O0lBPCnOCyobZcfhZPLLjJFxSyjk5LbKwYCTHXyw\nf3+4NkWMe75yaD68FWk02VAFf5xjhV0BEvbSzoXCOXk7Xyaq5o1d+i45u0xF\n0ovD6Dlsy54P7aQ4rBGO575zux68ugLOOou9tWYxDsQWekpmSQZFq/oyYRIa\no765k8SqeQBp8HtL/1kIGJeDMqSuHNbmUSdXezs6jv84B2wjjvQNLnAfzFmj\n4DC5Uj3NEft5fm3oXLKGQOMrEBcUCDgOAmB05rlxiUlPy/uS+ajtI4MMqlMN\n1jAGBsDq7n2kSfVwJK66BPxVlQuzwQYALyFtyUkPzIVrkVgfEZFIpoEzxzCC\nOnhU+yGevQ4t2Vy/c5NyQKxsZL+vzGV/hy6zgWJQFojIU7k+0m6eQnVpePCn\nH2c9\r\n=TSZx\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4a99705044dc40658b7925a9ad049fc848b65d7f","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.218","@balena/jellyfish-metrics":"0.1.37","@balena/jellyfish-environment":"^2.3.33"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.7","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.4.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.88","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.19-renovate-non-major-4a99705044dc40658b7925a9ad049fc848b65d7f_1611744457248_0.9120382797426707","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.19-renovate-non-major-332438b1e3c49cc9f104b64e63ce5c1378dacd0c":{"name":"@balena/jellyfish-core","version":"1.2.19-renovate-non-major-332438b1e3c49cc9f104b64e63ce5c1378dacd0c","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.19-renovate-non-major-332438b1e3c49cc9f104b64e63ce5c1378dacd0c","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3a3a31781af042a3a0ecc1700a679455c72e12bc","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.19-renovate-non-major-332438b1e3c49cc9f104b64e63ce5c1378dacd0c.tgz","fileCount":95,"integrity":"sha512-mnEvM9B/0JyvFm0aM3TX51bIBephm7vD21nfKzD4V7jW5PN6RznDtHIIdOTaCSR2uZrVGfPANcCtF72UfhX0ag==","signatures":[{"sig":"MEQCIGipeZkyAxAjzHnvUs2xJkUFdrs8NOGWE3z8U7NrM9A2AiAvHpNH/xhZYjqL737DTnkv6b9v7bevFQR6sXaZidq7aw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":590138,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgEUXVCRA9TVsSAnZWagAA5FYP+gLRPO/w54g5U++wtOqA\nM7RXxvKODTSYaY3nFyU+LxYUloA2rnbRo7E6kFOivrnV2IY2Ag4qQAT14hO5\nACLLfwxhggnTDcO/Z/2nBuRs8FkBHMiZLk4uQC5LfwgkAXI1Oj1E+7QpeLCR\nllwPy9gGcquJUJN+s4/2LYZpCuyjn8B5I2L+Mhuwm1x8SMfKvN/oaIUJDv0y\n3p6oNawaoY41K6XBMz2u2B7mOl1yHJ4NG7dW+DFyrG/oefRKZ6C3quZfJtsZ\nF2McpQCT7qaxoi800YC4PKkbrpNxnB21lcpm5ave5NF5tpIZoG2Yp1NWtyJw\n/ovNyrEkNU4fHUnvdpwCgg+9eRHF+623CnONh6dZOb/b1Ti5Yr4rvyV8P8fb\n1CD6LK/BkGA8aBth+Fo8wCd13e0hnBXVwyY5lsvXPRvV1zCvCGMeaaDEBVk2\nXEvhjxjlRfoNp/ybvlw3zYUYDvGpZjbRPXeDSAWo7ib94nh5LKwyfBurq4Aj\nBx+9aAjTzgNHSZ1bXLzW7PV+F8x5Pttft0NSojrEONf1Zf8xNigLh4tOPMIM\nDL7mYXhAbtw7o6gubtfQZTY9RXyB0MC2+0bHuG5QzBkozuyAn0JcKoOT6Z0m\nnWXTUXPzmdEwbPj1+KuGB1/pQt4FpWSHuvEMzgfH/g064ArfUgWsQZO8SGcZ\nrHb2\r\n=VrOM\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"332438b1e3c49cc9f104b64e63ce5c1378dacd0c","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.218","@balena/jellyfish-metrics":"0.1.37","@balena/jellyfish-environment":"^2.3.33"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.7","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.4.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.89","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.19-renovate-non-major-332438b1e3c49cc9f104b64e63ce5c1378dacd0c_1611744724906_0.8783183208416188","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.19":{"name":"@balena/jellyfish-core","version":"1.2.19","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.19","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5ab0b02629c7a690184c627a7436526eda513bef","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.19.tgz","fileCount":95,"integrity":"sha512-5wrK1MsEEbFB0MCczoEkcPUk6VUItf2RXS+s52RnlKG6OF55KD9O1aaS3GzHM3OXbra3YzZfJ0DPjkSRbgpvPA==","signatures":[{"sig":"MEUCIQDBkqlVAfR7Oh4+gfm6tVMxLhknCRzC8Q7r4twapqslbAIgL2sUohig9NB7wgS/ngrp5MOvLtL0WFqISf3EZfASv4o=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":590078,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgEUeQCRA9TVsSAnZWagAAuWQQAJ1lrSPPF8isJ5X5rCel\nCYPiNMHO9D+4QCLrthYP7+hgD0S3yy4OHVV0nfLNeEc5Yhiutrf59yBpZJEg\n/biOeUIpjCXVNxqJ+xQlAdw0TfcOhFQpZrBuC0kFHytFgQLg7BF5C62cFy2U\nrNu6RNJIg5nnLSJFK8fXrYGG19gRumdGFbEg/q6Ed5mQbWlEn4MeV0xatpFU\nCVzb6KM3lGKGlmUZNihoj6io8NYYWq3XVHmGJCeLvjiGUd4aXGNggTHXqXlG\nS4JDwn1IbaTuroohX16hJk53udZcHLL37IZmE4o0FN8Yew/xTXaumWXLP6hF\nDjjUm4LnljL0HqjhIaxFQ6anfgZbmeQ7RdIqL4u9mVzPF7GDw3GZTCTO5S91\ne5syYIuGgPs8z0eNd92vY/d/pw+gKf5iZIZtYzJVDs6jNys7y0tUa7/CICeZ\nPadcUIheKnt9uKnkLA359Yg6TCkBLhGzHyIdQ29jGcLEMcSo/YS1lEERPXaw\nArgMCAmQFWHUcFGmGRo4O7ALOq+W/LmVPIIY0YXxsgCBzl2xBCBtD28GNneW\nSFVf0nTQgA3AhaDnbaouDyXzpqO/8IyB5TXsw02ipm4//4cQ7uPtLFl0EUoE\nLDE4vwMwo3Z5fHXlUW0aP4IVwtmgwRZoZvU9Io1RyNFoU5O0fuLHpf1aYovD\n8cRs\r\n=6pUa\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a0d0f7b8e9cd163b23715716187717f1b543385b","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.218","@balena/jellyfish-metrics":"0.1.37","@balena/jellyfish-environment":"^2.3.33"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.7","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.4.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.89","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.19_1611745168082_0.06206305615250729","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.20-renovate-non-major-892e88ef9ad820627375c3d1eba8caa199ce9193":{"name":"@balena/jellyfish-core","version":"1.2.20-renovate-non-major-892e88ef9ad820627375c3d1eba8caa199ce9193","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.20-renovate-non-major-892e88ef9ad820627375c3d1eba8caa199ce9193","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"acb115db867c2c4b0f75328cbb46b4a4f23d0de5","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.20-renovate-non-major-892e88ef9ad820627375c3d1eba8caa199ce9193.tgz","fileCount":95,"integrity":"sha512-+rLyLZXe04qx+92CDU4irGfwd28dyh6eLRqiSExZYduAk3Zax091IBQo9C3JXTGCaLvHH3pE/gCAZjYMG+1uxg==","signatures":[{"sig":"MEQCICaZziARV2giQKYsmvlM3pSkFewUZrVak1+gFw5t2P4eAiBOiNhbJ0Ixtjqb1JQoQGEykGph8WCMoxbzfsBICycoWQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":590242,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgEUhTCRA9TVsSAnZWagAAzrIP/1kuJB0UHlwKlfEZ7ia4\nvoWmRZxk6j7c7QvENZ2d/7OQvndK+Ozsl/sBAhNMBJr7RmlVQwT0LA1elyb5\n1+5l1vwmNiox3gj1AcmdB+BtPwVS81x4qV1kUdglrPzOUyVKHzaBSCn2ZGsO\nrt2MgoqewypTgmpbFgiiqmxEBXy6Yc5QKv1txBqFYL8x6Hs4HcJTz2nDBLqK\nRO+Cit8MNwwXSn23HoEwbV/rLxnWGskVjykt0Pv99gycTIQutDGmtjpALSeU\n1cU0gWasrZqJtsphgFdZf2Gvh4hvjveC0vgqeMdK6Wz8VhpkPzukM/SuSLEk\nMZGqEuKYl3LrNq/37JnCXqXzSg6bs/5ny3WprjYz0kAWyWh/gqExRnpaZpv4\nkVS/WNhL5i7gEyT5EN40AdouxUrp1BcQCzs4Ze3ftYr+qhB8y33nUGWOvVBk\nqGcjxIk7RS5kXRDzOO83fTZLFcccCYUk26xHb8rdqaqQQ1clktZj2esVPrxY\nHCmfu2Dd48k0oF1rjwXpfEsuPrBxxofDI2fwSvxJnbAzF40QYAik6cWkqHWy\n53lo7C91oPueZnUECXATjufsdMiZrIFKhLaRlxuwA8tKqHXLIIVpQZheXpjk\niua6EmyWbzaIsUOgXNXi7ha9WwRlV4Zy4gZ3bXODCm51S0lgvRi0UWji7iEq\n9naB\r\n=15Iz\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"892e88ef9ad820627375c3d1eba8caa199ce9193","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.218","@balena/jellyfish-metrics":"0.1.38","@balena/jellyfish-environment":"^2.3.33"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.7","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.4.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.89","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.20-renovate-non-major-892e88ef9ad820627375c3d1eba8caa199ce9193_1611745362618_0.76567192281167","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.20":{"name":"@balena/jellyfish-core","version":"1.2.20","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.20","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"38a03bc063f8ccba4f9cbe73c2bede82bcbfb034","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.20.tgz","fileCount":95,"integrity":"sha512-5kpHT0cqGTVTUB02QA5MjSgSwEDOyrGr8TADy1G9bVkGjOTy0KYcoj3KzArRWp3uaMfVLbdo0jkvb+5tb8pvig==","signatures":[{"sig":"MEUCIQCATeCuX5ju4xIE5vnnJ1OUNOO+sPBtsbcEyg0XoKMbwgIgLTcE9oA7etPZdcUi0esq9jssqIGBcd1L2hwXgKjAnuw=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":590182,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgEUoBCRA9TVsSAnZWagAAezkP/RSc5WqYaTrkbLnCTHTy\n9+qYflaM+9B2N02NxtotsbsyNydbUZ+OC3hn3fB90DtMvjiG7wsNpQM4KQAp\nMGQyTLFclyUEFQGcyWnBB99YIdUDIe7/gWNEREVAif5G77/I0AwZNqH0PRUE\nowccpFd1dGSc7d5DAa1RjNg2Sg7hsHeFuagje/5gvCUDUPRV2iVxXBDcKDCJ\nCzqLBeeqFVRkCQdpZF8oshHfHWy11Gog1D4tfxafuh8UTnJLBvnt53AdAOcK\nR22O27qhOcWmMq9vKyTGMPEwriR+Ugds6AuthNLjovTCSQriDqppR635b099\nMOTQnBL4VnXYf30wOuoopLPV9YQHb5HuOf6I7b8HPlSRclB83dPhLSRmayFN\nh9U3Ylnre8ceC29NkQXUessKrA1QcpaK+WZmD8ZaxddZPTLwjdBdkGW0LJ9U\n7pftw9pEReSX6QemPYlgnWB4eV2inck9bRWLFcpOylhKTU0NK1lzbgq7gGMS\nPT510BiW5v9kGQVPkQbYHOqbl9EVe8L66afKXpR4mFSASIMz4xtZ8oRKXB6d\n+Offct7jxkhgZ1wZ7gW2J5b/3gPedLfFY1fR4XmxC/aX9AqKk7KEJ5Dzwhzo\nShqp6gPFIyER2sKQCCxRbQPILpHRWgBydf2jCzqoyv/WLAfXwkv+iCO95NaF\n5jDT\r\n=j4AZ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c0220dee29833d2c158dc9029770747392007351","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.218","@balena/jellyfish-metrics":"0.1.38","@balena/jellyfish-environment":"^2.3.33"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.7","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.4.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.89","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.20_1611745793197_0.5951106487296518","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.21-renovate-non-major-9886c73e167d295cb4bac297875c0d202ba77b28":{"name":"@balena/jellyfish-core","version":"1.2.21-renovate-non-major-9886c73e167d295cb4bac297875c0d202ba77b28","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.21-renovate-non-major-9886c73e167d295cb4bac297875c0d202ba77b28","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8bdf03878eeb04e0671c7bbf6161420a38be15c6","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.21-renovate-non-major-9886c73e167d295cb4bac297875c0d202ba77b28.tgz","fileCount":95,"integrity":"sha512-qgEJ+KyWx7SNEC9LY2ZAkaJTia41ibvSjNSbHWU/LQdziWx2Rz7+e/lCZuqzdwuaYWWFEg0+2BAQCHU4VeYE3w==","signatures":[{"sig":"MEQCIGcyOtR0agn+Tos0Gh+99dj2xfRlA0u9X4xwxh7jrtPVAiBCc4jKiJIWp7lkLRlVISm0s1RoL+1/IVMcHE1ioEqEFQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":590308,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgEWDfCRA9TVsSAnZWagAAWz0P/0fXZiIojLkEgRpCosyI\nSM8mQ7n4VyrxOET0cLki4de94J1z/UixfPWkX2l7dT8fLphp0j3OSOcCA4JK\n33+MiaCjfdpYtMjATuhKgGjRm8sqXUxee8ITxcsaaT2isR1PWDEYriZ1eH68\nMQQ8AxFagz/DpOHvpMXN9c0782QU5bRZgUYoP2UvLSq8VlAzeCamcBy7a+xK\nfGpLlWJ7XdrdJTrXZUiIi8x8CFHfgxOtSIKAZj83ushqbji/RJvW8Exm2L7r\nSIP97ToNF/ubLJMB0jZphEKxr1Rs4D+162tEiXm2/ugoW6L0e9qDAkEUbq/T\n9WfTf99bqrhtQLykb77wbVOxLAmIyZmsKmTVRed159J72cuPyf7EkuzwRz/q\nQ2eSselSpM4vMfofuYRuleKRn4osWFihEYFWc1xOCnNSv0O+0B87eVngVxRe\nOpG861JWYbrJdlmGi1xvBlWQcrP5fWV4M41z+asM7TEf5jadVtY0SKUBlRDz\nZ3ADufCSB6/nfYBABACIpbvAPeonXCdW+YsDOLla26QPGBjio9x6yxCF1ogS\nDrXrwTFN0Ao5oSOIeCwf2eLOv9DW8eDhJ5J+Q9srEim8oRmzPwv7mo8h43tQ\nHAb2TxD1D9h/Nd5Yx9imaLydtkJO7w0tuncQ+wSBqXHwTgbZYC7mq9kVh8/w\nuMqy\r\n=2eYj\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"9886c73e167d295cb4bac297875c0d202ba77b28","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.220","@balena/jellyfish-metrics":"0.1.38","@balena/jellyfish-environment":"^2.3.34"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.7","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.4.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.89","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.21-renovate-non-major-9886c73e167d295cb4bac297875c0d202ba77b28_1611751647161_0.05095240244483268","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.21":{"name":"@balena/jellyfish-core","version":"1.2.21","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.21","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"61ea39e6480ee5bca8420f74fefea24e78886918","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.21.tgz","fileCount":95,"integrity":"sha512-MwhPvpTqkjvec0RhIOT0mUmQ3wDYGUk7UFfrfp/tfB6leqMAJ/i27CRM3vz5YHFIDMV70o22cD2m11ptT5gCBA==","signatures":[{"sig":"MEUCIFqLF4jy526JuT0lmFVoSzKkUN4nS2YO3oAjmL7vD0JCAiEA6Z4gtD49w/26oS4TxHeFdX+ys4IOqy7kF1GwvgZlYxk=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":590248,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgEWInCRA9TVsSAnZWagAAF34P/3byn4cp8jzXiOjZDzdp\nOveA2u7USK9eUDRxcdgJED4WXrUnQ8c8q1f0wYRdJ6fuMwnDd2qU4gs/EYXM\nm2PIkt9lsy4ZOEKZb/CivhL4qfKd3duZcq2ExHw3v7IxBhrEJNXAzWcp26qS\noEd+tWZaXJzOz7K9jmPNO5HAlvazCp5Sdi6ym/0/bqabWlfMaJtK2r+4A98x\nTvg4SZIc4I9rkBp9VDcEyHeFwWAeLeo3lATIqeK4qx6o/OMMYCyFIbCxZSm2\n3111vId8A+YqawQNLqBIW3x/YGVZBwRccZ56QhMH5SkK2dCJaPDef+/jdFxw\nN5D0Qmbvj+nC7N/Sj2YNQRUll/02YimkaqWmvaOxBcVawrSaWSE6b9csqzIV\nketO3tO0rEUPtChGFVYEU5hjsVeInYJcyousj4ZozVw5SvX4xUyPoTCwYwx0\nYm8Lf1jxjtqvqJWxxOTyTMxAQHPQTKyi9l3H1XpSmh04pZKe4kDnnTfBwxHx\nMzxRQ1W7gCYiDangLgFNGlrL3Jarawwyz8+xSvG0ygvp0dccLTGguCqEB+Bc\nlN5n4JUeKCgxLJgIy1vcWmxeM7nA0mSfJk79MSzZKjFwgxRLi7ywPLXNDCFo\nYctcwV9XtQolXXkR7XvcZr0Ndusr3aRlsWyRxpHrfE3bquqJbHLuS64fyB8D\nvtZc\r\n=lF3K\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e567a9b19a8b6575c0b62d27a9e5768879d904fe","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.220","@balena/jellyfish-metrics":"0.1.38","@balena/jellyfish-environment":"^2.3.34"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.7","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.4.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.89","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.21_1611751975199_0.596563591718873","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.22-renovate-non-major-5ab555ca210d87d3dbb44f7c8eb7a836138713c9":{"name":"@balena/jellyfish-core","version":"1.2.22-renovate-non-major-5ab555ca210d87d3dbb44f7c8eb7a836138713c9","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.22-renovate-non-major-5ab555ca210d87d3dbb44f7c8eb7a836138713c9","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c2194af1b276a8b6e2dce54f72dfa9b3cbfcafcf","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.22-renovate-non-major-5ab555ca210d87d3dbb44f7c8eb7a836138713c9.tgz","fileCount":95,"integrity":"sha512-VO6k0Uiof0HSsww+GKFcJMZwPPb9Ds01/X5GkBLzBOJEFOLY7XFEdDEfVQzWiyU2xIFGZXRtevSktJ+HAudrcw==","signatures":[{"sig":"MEUCIQDLEPLAuqGhLj5QONjQMkTwh9Re2+EKnqwMlLeN10htVAIgKLmlV+opBDODcJVL7Pkqbtn53wnpioTf3glnbH0ATMs=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":590374,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgEiCECRA9TVsSAnZWagAAnQsP/j7V3yB3XNz768oZK4Ka\n6oorYV9zyr9HrpZUpXP1kRbYw4mY3GMkwVjWI2J9u4j+hrRPL0L8H0y7Vr5Z\npB3CBj7Ku9vXTm6fOFOheecgEu9RX48gBo2Ctgml1jEoxgom8So4DxTmuZNz\n+fLcTZX+Rg5ByvxqhXAFvzGkl/z4YLrqCUPm5dH3VNqQbLnzAC1445yb7E8S\n6ydhKPhP/u57LdPE8qCTdlUm1R1PY0J2gKXqJCpSX/fk0NsLJAqkFGjOBa63\n+X9TwXVMUz+nlnB9FuTEDi/FbScE3qXBzJWy8IeHSW4gwl7JsSFOpI/PABAg\nTapS50Eo9XxDa+L+BWhxkjwuYbTl0gdHjwRhy16F4dmNceGo+ZLermZtOE6v\ngb+kRFxhsc+9+pVMsjJ5zeLEVQPJL+hp39lDglMp4KcaErS5Fxe7YP37qwUn\nR1s2OmgEO/Moxl7+vkK1sCLe3k8va/EQLu3wbB8Dxyie1RFiJ57X16bL588S\nYK1xa9myNNqSWd0c4MYUWA7sqQwOmZ0h3Qpx8MkTTkYhe+bpqCcenrZoOvbu\nJ0kLeyTEFU+tEDYhL/U+XKyV7xooocjnNee8hDAk1bgw0DmGhDi3lF2uRenT\nnwZyBMvSfSQwcLtDCg/gToZPQ4RFUftSY5ggnWcoFMnX8se26eloy5NMGMFH\nejty\r\n=3Ezb\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"5ab555ca210d87d3dbb44f7c8eb7a836138713c9","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.220","@balena/jellyfish-metrics":"0.1.40","@balena/jellyfish-environment":"^2.3.35"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.4.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.89","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.22-renovate-non-major-5ab555ca210d87d3dbb44f7c8eb7a836138713c9_1611800707684_0.2930543080860952","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.22":{"name":"@balena/jellyfish-core","version":"1.2.22","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.22","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5cecd372a27449fa8f6dd4b87acadaab19dff5da","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.22.tgz","fileCount":95,"integrity":"sha512-Hfh1IMArgK6DThdgRSb+PapcmLsE9C6yx5A04SbcPMbRIDP74BrGZaRaZOQRBr5kuJbUWqrybdiipjZx2/71FQ==","signatures":[{"sig":"MEQCIB8WIDtoZPOqt1oa45sjDvQ5QbnHjGzzc+jbybtqcK5ZAiAFJRsW2W1idLdWspDt7dqoyfpV2YrvMpK97Sv9VJGjng==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":590314,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgEiGkCRA9TVsSAnZWagAARLUP/jyRhbXyZFTEKJ5cXbMR\n2Msqbymm6wuhB5q6sW6r/8mpeO/I97SeqaKdQg+UEgLMDvypMc5/1w8vpifY\no4kUGCaGX8IYX/y7DG0OujEnuL9YtnpUEPxLIE0zYV9hTmv9NI3hXJL93LyE\n5ET4x46cYiV4bFMLdjLhXuWVuzGiW0qoVzWavgAvP6Wzq7gC5JtfDs3KQznz\nLn+7dCwE90w04eGViQZWZZnNDRu1uvSiA15nsT4ozOu7FvaQ9+4a7SZ84g/0\n81aoJNE2Zb3Q+Bj/I7NBJtUQRs9V1B9yj4o/KWFFzfWM0Xqz+nQhXDS1Agap\nh2tTSbUb4AuN9FRjdvOWh+eCG1Z16X66D83B5WN9OehY3c5Y3ZCNE6ulr4rS\nohQGGDNc6D6+v0mxcitygJuSt3HYetamTZuSlxZnoCniolzx1JEzPtRo+K47\n1L8tkooskQlFHWMFqR1XJQzLHrYy+Voj7EcHlOTbS9d3O+lpvPJOr3pGzPGb\nmBtS9qxgAxotqUAcH2kJOjqAP/0oZIROyT3MQxq5R1wV366figZ3ZBeu0YOu\nJnNpirsny7ltyB876TsVyOJIQghSvmXKOSBPEuRrzE+NvN2IzJhwKsmjYY9/\n3ywEH5iUJi0EJwO9aAXAqTWPX8+EPd8uyII6gdXhDjLe/OhoedGitC0UAmqS\n6I0I\r\n=/9oE\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f202e55572e3c5eb442f22f1e4b89797c4ba83fb","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.220","@balena/jellyfish-metrics":"0.1.40","@balena/jellyfish-environment":"^2.3.35"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.4.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.89","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.22_1611800996126_0.5438467616141944","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.23-renovate-non-major-0bf4e44fa3142d4cd1683bfe24f508ae3cf471d9":{"name":"@balena/jellyfish-core","version":"1.2.23-renovate-non-major-0bf4e44fa3142d4cd1683bfe24f508ae3cf471d9","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.23-renovate-non-major-0bf4e44fa3142d4cd1683bfe24f508ae3cf471d9","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0bb2c61e3345221db56a18debdb78480dcd07729","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.23-renovate-non-major-0bf4e44fa3142d4cd1683bfe24f508ae3cf471d9.tgz","fileCount":95,"integrity":"sha512-u+9PC0p06MEfxlE47nuxiwcfm9LGH2tD/ThWWDKgb9t0Za5RT7S9qvcCSnXzOOnb+0BaZF6FJIZKmjTUs2FDcQ==","signatures":[{"sig":"MEQCIEbeuDQfl/CjaJh5urUynG8Wbl2YS/CvZdOzv52sMyROAiA0kCtdFZD8QxCeABNgAH48OzIxVFLTiDjBIIdOWnvgmA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":590478,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgEjW8CRA9TVsSAnZWagAATRsP/iSXAMrg64TSL/p0X3l+\npI0K9q/zQRJMO6inJKTu+wyd/5BoavZvZlzY8qVuMN1cb5ZL5adnww0trW1t\n2cN73peWHj+uIlFLas/S/P/HLc9aIRRiBpW7EhsGPBnLPNeji8acBqtUTkqH\n6nhRWqbMYWiyYYw6lwvW0UpLVxcDnsvpIcLV+/mqOdBDwLsdxMhmu0YgeEib\nI/MoKzSEa4KztxFgnS98vEOzNvl4BoeBuycBN4bVURNBjN8OuPFw1bKSOsBV\nr6ICeYnn06qB191Vd8abZPdjfSn4Q6YBiLprdpE2nGMepf+oCjU0i23jwZ7j\nIbxl4hHhgL1+Ay/NZoLDMFOHON8IfS8AXCGuHMKm4WDqveRl48FUtrovLN0s\n55a7useaWYEPm4RXNbMaIxWro3DIBY+rN3pNTNCjp2HoZQuVlOeBny+VyBHM\n0D6KxNBXa2st7TVKissIsCk8imY8RQVVSqkbN7KjoZTUN4uzcRJvq6dvS6fm\n7PA1Zf+3Kct5AR/Q99OsPeztHYPcpxKaD5TMDuNGS1PijZqjCJNDBGB6FHtd\nl/PnT5SRnkWadWDNvWGfife/tfxurUCjR6T2Ak0xFl2cObP5RM9FI+LsQgr6\nKLafL6PZCbqHGk+QC7ESjA2F1XR8/hLPNJ9mb1oBO1UICpOWWOnB9rwR4VJQ\n7FjA\r\n=2iNK\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0bf4e44fa3142d4cd1683bfe24f508ae3cf471d9","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.220","@balena/jellyfish-metrics":"0.1.41","@balena/jellyfish-environment":"^2.3.35"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.4.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.89","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.23-renovate-non-major-0bf4e44fa3142d4cd1683bfe24f508ae3cf471d9_1611806140141_0.5647433445386012","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.23":{"name":"@balena/jellyfish-core","version":"1.2.23","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.23","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"34d5d8dd28405c8fae616c1b09abe84caa80018a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.23.tgz","fileCount":95,"integrity":"sha512-LvNiAShQhKLsMbK1lEcDls11JhoW6UxzD2JVbN+84Ypqm1VZBWEDde+8uiwPjRqCF6eeg4q8eCVy7pVbc9ixxQ==","signatures":[{"sig":"MEUCIQCHPtp2gEXJxZvIp5bll449fVHIRf+Q2bvzucMqvVjD6AIgNwBj3g3EmWfq+d/memg7LyMd31/w+SohJ2V1U8p9aQ4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":590418,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgEjcJCRA9TVsSAnZWagAA7Q0QAJEfhGDQmQqJsljZfOMa\nvDIDy4FsOzC2StxMoZaultNO4ij7x7SiR8TATapOoLN431MX6EA/LLksxxtJ\ni/gV7lUmMi0MtD+kuR0sAQ+dKFj9jUlN66TAX01jQpOdXR4sKxYqjvQD4RQ5\nkLW6fHldLQKaMWvaZnXNXmGDRrN62lfJlvDpdwtBp6DpzoRZ+ml84F++2cC5\nWAjgQGYxsVlBsNteyBzE9TdkBbTnslBr9x8duDQILOk9WzLUtKUNwu91MevI\nsxLWOfGnHRXxmRCb1pQLMu7DiJbJXqRtGnQmOwfha9CUx0lKcwpLJD1YHUbp\nOS8eNYw1Wl5vm+Yyq+vHDimYnNlCWRrEi7VCAOndgvEKb+e8FAOow2/fDhPt\nNB+sicVxmSDl3TpvjORiEXqw4LnLX2wsA5h9mWqU2NPNF4Mqj6YPttEl0EWa\nU4zcNrqZZEHJCKUFr2wgtlDr91f+KltyNk0hcSMmSnl1mSQWoa7xhonYm7K3\nQ3YoWEh1LGiOQQEP2jKFWbvhB82mLCY2z4nKwES1Rx/FUjYBgL5PvnQv3ms8\nu7dvVRSZJYHlC3egavYQmRLdKi8yUFc6eGLj80HzPiYxd3W+oq90Z90M+yk1\nqclSTpKYa6UyaJM19T7d39FxJnHOBM6R9Rt8Us7k9zRs0zKyth6nnuGEPBKm\nZyYf\r\n=CBfT\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b2a913bfa441485dd63090831c4dbb74aa307b96","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.220","@balena/jellyfish-metrics":"0.1.41","@balena/jellyfish-environment":"^2.3.35"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.4.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.89","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.23_1611806472464_0.441364599369944","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.24-renovate-non-major-93ee0cb6b2d737f529f287a7c2f912e0f3fb5b9f":{"name":"@balena/jellyfish-core","version":"1.2.24-renovate-non-major-93ee0cb6b2d737f529f287a7c2f912e0f3fb5b9f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.24-renovate-non-major-93ee0cb6b2d737f529f287a7c2f912e0f3fb5b9f","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"cf1b4b03ab09f68a7cdc5ded2daaf57b3a47f965","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.24-renovate-non-major-93ee0cb6b2d737f529f287a7c2f912e0f3fb5b9f.tgz","fileCount":95,"integrity":"sha512-LOGuPvAZOwzHtRAELl9HLk0j9mmjGcxfC+JMzMlpmAegqAmNKBbbwTpaon5dmkX3phA5+5D8MiSz96AGcsVtLQ==","signatures":[{"sig":"MEYCIQCMMgdSaIdG0DSqR4mM+uZY9aT2SCpSE2DziQ1XrTkoQAIhAMN/yaEbvCFTXqzJb5kJiDzT0l7Q0Pa9VTwL9xoje/nB","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":590582,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgEjfGCRA9TVsSAnZWagAAQ9cP/idEVAvYcmMTuiSrKtp4\nhLl9GuMIdnUSzt/Q7UjGLpr4GtUbcZLVELFfanjJgQdCghy7tb2q1jynB0Yp\nTalZGUtZO4ZzmJpzO0MDJSjpKxslEvNQVD7XuUMvmGhJMlKrfLMKy9d68iDk\nyy2urmv3hrwQ+/4ssSab+gSj2o0vuijm5BK9Y9lgR4p3RKntM0F4IxS74mfZ\nl+bYh6x9/UEhaL9e+ACnV92I84uG1dSLlplJ3UB4I1Hi2U4O2zs6hu8RDfhZ\n3XZolag39EkcCWrvFVlRe8BmD9kMyZC72gU7ACuXbdyD/8nSVjFphNfGw48B\nX8+ssBI5/ARYLeHBQS+PwQ3xinEx6Ti9f+u4UvvTKE7rmleDXvwUlLLWA8Ep\nx1SKVmH2Ed3fg8GegIzbuMcEyoL6K84HO0nC5qObTehN233SMZThy5SDzGOw\n0fAL90leU1gg+WdCk4glfhroY7VZn6DCPQDSVL0HtHcHnNiGQgcaDTtDeYNE\nWOsckUwg8GJGpfAFWyQvTYgPNQu5YXSMk3JJTqH8ntbg9+CX8zP48hf8bOZb\nNjwbK9OOa2XLuI/VF9RFnSaJFv1BT+RCeAHsXKnX462ivfBdMnpy53Z5ayPx\nkxF1HVMwt+nOaRlbHeRpiJ7S7l1OUYCYaLmFgaMNFq6mxH6dmiBnrhlZ9CBn\n6kOQ\r\n=4cu1\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"93ee0cb6b2d737f529f287a7c2f912e0f3fb5b9f","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.221","@balena/jellyfish-metrics":"0.1.41","@balena/jellyfish-environment":"^2.3.35"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.4.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.89","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.24-renovate-non-major-93ee0cb6b2d737f529f287a7c2f912e0f3fb5b9f_1611806662136_0.40057111592566486","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.24":{"name":"@balena/jellyfish-core","version":"1.2.24","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.24","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"7218b3377785327fe54c5713b46a2de0df376bb8","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.24.tgz","fileCount":95,"integrity":"sha512-KE0lQcfD+J50gKm0viq4/uV2slfQT87ds1e4IvAYimXCUinFMqvHobGvThq4WLiwLQiFGyLyzluLD5WGlFzxqQ==","signatures":[{"sig":"MEQCIHkiRa1ZcMV0xfJUg4MR3Us7nfsRCgBBQgXy6k+qqWsUAiADcVEdGoIzLzalRisKCHojsSuMUi3tysqayf1pW3/xaA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":590522,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgEjnKCRA9TVsSAnZWagAAAVcP/2E0vAnabwzlrw4jFqa2\nd2R30kDDrcfsfFLLFjKzCC1TaiF/O83Itb3lm+d0e5+JTXu4Qai9zfEk/9Xl\n+nGqdRFQAof3LlCHqCieUOP2znsjPJfMoZkiiqBwSTxJ7Xph3WzWKDGE7V+E\no0EqmhhMXJHFFEH5ZgjZzM6s6oZNBGLDyxISAUGTCm5CZKMQlZMbKKL5Fy63\n/B1si3Ol2ajjo/9urkbkgz1b4pVzrAagJ//o9D8+geBMWuKr30L1st3ry7ak\nx7R9nP0mHe8rYCNKxGsPcGyFj25g0H4bgDDUYHqAbKYMkSpgMzsnDdQkZ9or\nx5g9uLED5vN93wQ1uz71tnM1Rdw0MWlSz/3DwK96/hrQ/2C1hVDjMZGqtydG\n2jDcRx6NvSPZNt326XI9eHwtJ7oYLGO2x8ZA5W4tvjOLiYN4Y+PoKI4vlPvr\nJp1BMI4G+VNQBi1UGoe8VLbSmXhuoxDyc7nPwVl9dJHTLOfN1ID37ZaPyI2x\nANKqthFvOdmufIJFWbbf4dW+W0sE/aA7qw9LAIjwBCyq0tjPndKuaqwBxZHx\nLo2VSgwSI5JcVSgHjLLjFqTeCIIE4FHpWsXhNi8WUUPd3Z6CmG+AUL/SjQIK\ny7zM3uTXAsCS4/eHbjd46SN3hKj+u3uUsZgPSe7ZprcN5XqJLKVkVKrHzFuJ\ncN+K\r\n=bYR2\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"1f52295c79d7ff3e66c93a9e2ab21a56032d99d1","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.221","@balena/jellyfish-metrics":"0.1.41","@balena/jellyfish-environment":"^2.3.35"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.4.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.89","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.24_1611807177961_0.7185651730387854","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.25-renovate-non-major-9e61f156da5449d5e80a6fd82275eb89b788b773":{"name":"@balena/jellyfish-core","version":"1.2.25-renovate-non-major-9e61f156da5449d5e80a6fd82275eb89b788b773","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.25-renovate-non-major-9e61f156da5449d5e80a6fd82275eb89b788b773","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0d098b70de81f8aaac167e60af9e140a108d870a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.25-renovate-non-major-9e61f156da5449d5e80a6fd82275eb89b788b773.tgz","fileCount":95,"integrity":"sha512-0G8IaGB3qFeljjssVB4Wfa4xl1Oj4XY/M9ZZxjf3RPGQvHnVDp6oNhSS/HlGkAL2sFcg1Ng35mExrZBOBGhx2A==","signatures":[{"sig":"MEYCIQDjcCobuZSwi4qWRWt2Q9G4aw7PmUzjLEj+wM9MKj9XbgIhALDfIRjqrpTRd0l6xV7awuIembRecSKGIVhhANHogus+","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":590686,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgEkaWCRA9TVsSAnZWagAAD+gP/jElqt3Lkf8T1oxJ+zqn\nRGhM1jnyZJ0tp7m2bYF53UZouTJLWMxSLq37xO5er6EumPP11TLTzrnYS9yK\n4fSGh4dasMlKIe8yIg/NxGKS39eIo89zVJaeRS8SB5B/iC2Gx1kn7OGudzQR\nCpzWLiI3d6ie4Dss3YITXjPzFAQcFNIepKvTkh5Dlyzst+soTKqAbmn+VvAO\natn3dL2fC+O//YAMdXmdK7VnzeKIRl3L0E4Gr8Rcuv2P4hi5GqeNQXw7fPlx\nkq3U7lfsmxquNQ6ZGY+M8GULGcUNHsyHmkj0tEh2MN/e0zi5c2SSAxeZA5nt\nILkRynzKKfRTHUQObSf1B1BmAtLFavwIfNrwVrTKDvyZskdRl0EBJ76TkNYA\nX74O7AI4U0A3LRmBeQpz9DiCfPBIfEwbICoulbm94GV9tsDrtKJ0mAWAd0g4\nAHFqdTQMnRmTHJsy4bRwVU8+mxVxEwVs3Fss33PtnlWmHEQ5ygCbn0d1c0uz\nLSmnHUvuaB+jlxzuPr96o33TvTlIRnPprZeREYTgU5iBwWo+77yW4QDikFfy\nHLk8jDX7gy2S6OTYrRHgHouNu+nSo9TPtHLTN0Eh9rnCT2dD8ScC9hCgigKy\nEBAzFVaTzIE7baV1lVLdAg9K6tVrd3vqmbU9OH1/uvsBnnqrl7/sizo+4gry\nfrND\r\n=pWfS\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"9e61f156da5449d5e80a6fd82275eb89b788b773","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.221","@balena/jellyfish-metrics":"0.1.42","@balena/jellyfish-environment":"^2.3.35"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.4.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.89","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.25-renovate-non-major-9e61f156da5449d5e80a6fd82275eb89b788b773_1611810454055_0.7578971276904809","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.25":{"name":"@balena/jellyfish-core","version":"1.2.25","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.25","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f33156d506fbd8c59672bfcc70b77fdc96daff8a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.25.tgz","fileCount":95,"integrity":"sha512-8WFin0eNTo8tGUzJxcWOOYe5pAx7HPDxf3nCrBjIFGu+64Kgjv7BNTcsrl2DqEGe9pUc1dS3ywvY0P2R768oPA==","signatures":[{"sig":"MEYCIQDzH4M3xPhenTvQ50bLkkgFThfVNuqi0OPCoMx4457V5QIhAN7qbAT4VD7l1PEdZ74y1ioKi0AQQXISWllPqKcn78ME","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":590626,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgEkhUCRA9TVsSAnZWagAAs0MQAJ+zYztdZP3OG4nrY2Xm\nJMP528bl9YoET0FCtFQ5O5PNeBvMHz2sivNuTzcpI1payyYVgvThbN3vAtQg\ngyP2iC8ANw2EnoZVcuw3qyajV65g6/0Bf2DuT6Aluk0NY16edfqkh0znJ4IS\nVuNTQ26ZQzSJw7G44dkENHzH2e1DiG58RblmKQBjhHlZ20Q1NTUvVU4ViI4N\npc5d9nr7LWBm8s1Wj7ulILHBARFqKBggva96gxZGyQ6rCL4RhKjWi7jeKSQw\nkz7jCh+Vl73W65zT1koNcf4BdsYk3EDzsa+61WEoWTiAJSpjNTMMcEbZbe9N\nlcydaKepYETH/asrYqi16eOfdtYje5PNJfeDTtNnIkenVoaFSaj4ZLxZEycW\n9U0VN1JXojV36cbfNDxfl5lP7QaLlmZ+87F2lv10IM3jlMvdacY9CCt+HOxN\nGfTL/O1eknYw9b4n0bC1qcGqjmDSDOSeMPsLyX8Oe6wD88i8O7bfCCWZPT9F\nufDUGc/gSSBnU2PWKCTN8pWyaS6GrI+PCYKh6GbIP+7CpV0+TCRorLdr+D+Q\nvtbzscGd8FsFo+X2wgQhDDVPh4Lve0iqH+AIJu/xZyUrzM6l2wdTOWpe/C2O\naGmc1eRuyjb32R6jaecNmEgJYoEL7MlHnm1NO1GtFeS/SQXPvkZWcbSGDniA\nrnC4\r\n=fJyk\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6557e34a455e28dd7376f33831fdd3e46e80f539","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.221","@balena/jellyfish-metrics":"0.1.42","@balena/jellyfish-environment":"^2.3.35"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.4.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.89","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.25_1611810900348_0.42374156014748743","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.0-5508-pre-build-versions-0008543569e79898fd04b84e2be46cbd25672811":{"name":"@balena/jellyfish-core","version":"1.2.0-5508-pre-build-versions-0008543569e79898fd04b84e2be46cbd25672811","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.0-5508-pre-build-versions-0008543569e79898fd04b84e2be46cbd25672811","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3159da8b3c5f23fcd377c78d281771be03a22ad4","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.0-5508-pre-build-versions-0008543569e79898fd04b84e2be46cbd25672811.tgz","fileCount":95,"integrity":"sha512-Uoz2qDBVfo/NWljVZx9ul5F8ivh3Jxf10V96W7+MEDCKLTwpdimgqTBifk/LK57gVHnfpW6pfqkQnLdR1RMaGA==","signatures":[{"sig":"MEQCIFX9OY9tyvPqEWTPYEDU88THbv1Ekvf2ep4grdBW3WFIAiBpFaEMSx9l4WoiphScnna0G6v0h3OzgD2TVpEYB9T7EA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":594716,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgEn4ICRA9TVsSAnZWagAApM8P+wfUzVJosY+lECSlHM2D\ntnv4CDkaWiBmDzziUe7glNw37AB9mphkMIbFlMhGdG5tE9n7zPw8+YpMt4TP\nU/0hDzA1QBAhslnQAV0EIVXvP/3pGCeDC90iRFHxInEJ34DaSdhVuoG18Qn0\n2qE79m+EvP7ILngZAWJlNPNpHCJwHt7Dke37XE65F3sXnde8DQ6y7eG85oYJ\nTBRr3QDraWxRotvRNYaWP0j9rNaKhGUbu6kxxKSg8PxErNKnoZosd74SV5Au\nhwcC1IPthExRzL6NKkzK6L4VE3jxF+SUBvPFcNRs7BwMgb1NwgoTsXfy1Xjh\n44KY8xOqrAClutry3ljEl1QvRpNgHiqFyw51MzLutkw5wShOO/OiQGwJ+G++\nw8u9QqGSn14RCsUdVCE3yG5DLWMtxmwT3ji7q1Kywt1KTmP44PhA2hVJjqPR\nA6vkmMe5prebdl3t25U1dsM76MmB6eTTP3RFs/+pycRFQFZlESnlLVhCB+Pz\nEijz60ID5quAO/TtZ+UqL0NtARy1q18HwjhNmy3Gj70CGmCFOKdWflOap1a7\nIn78QGLeIZ/G9THKxRHN4miTPbbZQfXkh9vF6DJvOWtvmM627n/d1ivnU5f/\n2ttLWGTAp3sXbmQld1yHlb5FGenbgR4O8H32lJFL45TBDo0TbIiRoUAht6FC\nuiVH\r\n=jU3Y\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0008543569e79898fd04b84e2be46cbd25672811","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.207","@balena/jellyfish-metrics":"0.1.21","@balena/jellyfish-environment":"^2.3.25"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.80","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.0-5508-pre-build-versions-0008543569e79898fd04b84e2be46cbd25672811_1611824647587_0.29014500132443266","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.3.0-add-session-scope-ef78abef0fb911379013333bf0ad53c74e5f07a5":{"name":"@balena/jellyfish-core","version":"1.3.0-add-session-scope-ef78abef0fb911379013333bf0ad53c74e5f07a5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.3.0-add-session-scope-ef78abef0fb911379013333bf0ad53c74e5f07a5","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c712f5014029d8848dea84dd06f07e1135b6e045","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.3.0-add-session-scope-ef78abef0fb911379013333bf0ad53c74e5f07a5.tgz","fileCount":95,"integrity":"sha512-S3Edn747xGnmoOsnN0yCUVZQpEFMGywC8FAFMzx2KXz1Ffn+wtlTnjIZxE4yPWC5d6Wp/iCmTWup/EHRqPzRBw==","signatures":[{"sig":"MEYCIQD9Z6RXd7YxegdE4MnzEC3dD8m2qLtgYuaXq47CWifVNAIhAIWyagHlOckBdpeeONJp9fZdnv0k0GM65Y/Qpm/6fcyg","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":592045,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgEoCSCRA9TVsSAnZWagAACIIP/3RzIE9BTXVPvcvsL2Fj\nYLqEQqZxK4tevFA9JWe4od8jOThfy6/u9/05JNQl9jlPGJl6J/R8MON5Uhi2\nem7LpQMzHKikggGE8fpSBt51voIu6eHg4YTBgbA2JRWspOe9XhOz/Ei2GZaQ\nM4BAT/y15c7zSaB74cgZsi5Lu7/STT66Rb0dLJxocqXg0QNite3oyfyIzrQj\nJWqbwmF48FGyUug/4+x7eAtxEsKRB4KFQOQoyv9sRBeA3sxgNZ555jW+r1CZ\nr1RCh2oj+exgmh3p6aDZJ7tIshaBj9UQGvB8XFjAIsLACnmXWCkbThsNWM2B\nTwfMS7PN+/0VHi7PssB6ugJOQMV274eSOvhZY+joxSjnjQH/sewjnqskIQkk\nKK6GPX4x/X2jz4TlBJv4ZNZGcuQbNEzSn2yqDDFWViDtMFfQ0ghvlE4nb9Lo\nJzVMToHlDMPqBGKglXl8StXd3jeZMoWWf8YP34wmsy7pJry66R0zFm/7guCr\nxicXUshrwpG8ZoSiLAuYFD3/ZhXLpdv8Mn/JeTByVahboPah70bqOnWCenpG\nE+SKQq3vDypPBYkjUo9rXp89nhyrNo0ZBkjeUCkuZ2VHS4NwovCzg8/gCP5n\nL8jrCE7sTuWzyjqnU9x8Rt37S7NGjMwVWOh/4OpmBrrnczIJYrxdoT+H6+It\n0utr\r\n=2kSg\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ef78abef0fb911379013333bf0ad53c74e5f07a5","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.217","@balena/jellyfish-metrics":"0.1.36","@balena/jellyfish-environment":"^2.3.32"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.3.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.88","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.3.0-add-session-scope-ef78abef0fb911379013333bf0ad53c74e5f07a5_1611825297954_0.40689181431629695","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.3.0-add-session-scope-b25d6a72037fabe33e21a829721e5b78cc70bfd7":{"name":"@balena/jellyfish-core","version":"1.3.0-add-session-scope-b25d6a72037fabe33e21a829721e5b78cc70bfd7","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.3.0-add-session-scope-b25d6a72037fabe33e21a829721e5b78cc70bfd7","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"92b40e8ec23df5eca27e27ed50ddd9195a9f6562","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.3.0-add-session-scope-b25d6a72037fabe33e21a829721e5b78cc70bfd7.tgz","fileCount":95,"integrity":"sha512-dlpE1r8YWrIou6W3qrZv9cKi5BYaLEVl/sG3rV+AdQ4aKXdvEYM5bjma6R5EdrpNbTWcEo8QxZJzxuSd2snNog==","signatures":[{"sig":"MEQCICRdhQnXavfCrW1hn/1LiaR/HEjL68hBNLa+VhmcKH83AiBhDrJsk7VsefeNxB02DAIlrnEgD/i6c++lENP1NqI32Q==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":592775,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgEoGDCRA9TVsSAnZWagAAl/cP/1H/O7LJCuR41B9trL1O\nyzfvchAENKsRCvs9s5xzLN0o+h5TG0lPDVmSKokSqki3FgDXzXOgvJse4jDk\nOAqS75FTWfv01YjyXVnpx40U7kQmc43NEVp/pRNJHymWGf0c5YMWCejWvmAL\niKY8sMbzctq4YyVhMRmaQeDG+zSd783oue/+CbWsk1rVXA6jLY07gPz0Tvdk\noBwGPp/+X2G0aVeHLZPeioTzJPQPlhehjWEix9AsYuNwFIiKR3yy7NOBb610\nCGbcjcKg6AXsEjDm1QD7WQVQksQx1nUp4uQcK32h4ASFQR2ADYjsT9qAikd+\ny+dR+XTJE+ZNKcfZCOsbBscf6+cpcfoTgFnEE8HrKPm8/GOVbvw38il7YHOA\n0iBpA2kuy+tncLvbSXDlAcsVLk3M2OJQPC9xrujOf4g46XGEPWKn7GSc+D3i\nDUqK4hS86V6QgRNG9W6Ad0N4wQTzPNngQuXDu3lp7tcSq/Fc8upts0iWAqFd\n2bm4StM8v6PB0CtEn2hwCulzS4D1xr5DV3v5x3CQuQ+cOu2A5xWYBaJgMxW6\nNqZUfJRIi//uSLQB7aCJzMVDR/+PfsZFWC+8SOzaXV9LqBat+XcZPhuynnH3\nX2azOYFbn5dYffUqa9u6Mn1DuvqcZVRL9c4+0yi6C5KPMdH3o4cWZSQe8m9a\nDX/A\r\n=npd9\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b25d6a72037fabe33e21a829721e5b78cc70bfd7","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.221","@balena/jellyfish-metrics":"0.1.42","@balena/jellyfish-environment":"^2.3.35"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.4.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.89","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.3.0-add-session-scope-b25d6a72037fabe33e21a829721e5b78cc70bfd7_1611825539178_0.9146578248439214","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.26-renovate-non-major-8f2f7668a46939a8675cdd882571bb3817627322":{"name":"@balena/jellyfish-core","version":"1.2.26-renovate-non-major-8f2f7668a46939a8675cdd882571bb3817627322","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.26-renovate-non-major-8f2f7668a46939a8675cdd882571bb3817627322","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"cf9dbffbb3a6aa944bc0f797ad58a23ad20ca8a8","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.26-renovate-non-major-8f2f7668a46939a8675cdd882571bb3817627322.tgz","fileCount":95,"integrity":"sha512-myKPi2PF1/h5SZ4d1grm31BaJASjj5G/fRfwaYXrUNmiGJIi6SoM/8Yt2uQsKO2jaTRt3leRkI/1DBIWXhJ3tQ==","signatures":[{"sig":"MEUCIQDmKlOxReoM/YfHabnwC7wbfIYLmyn7XkluYvvT8xciKAIgBjFzr3YIYKZRvC2YUold3wc0w/KAvfxrTKlcG3U2+q8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":590775,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgEsKoCRA9TVsSAnZWagAAYe4P+wTbG6Ioys3ij8Q6gyZi\nkW+WpUmjf4q0uwuhNwxypKrkJ85mAnTCkpmDf+jAmKKkS5XhK3Cjqaf4p88g\nWTlkKmU8F+DmtsXlneIIBImszYJfAI4XFZoOJa5U0tp1yTzdAzlnzLVw10Wp\n3RN71ZZzrszHWlO6EOW+PpGPYz8dFu2GI24T+BOor/NbUjPLHNa4DXxDImP5\ns1BH8+/c5ZWIEJ5GMUgilRKTyLAJMntgivuavHAKUtQF2oCGI/0ZYjJkCtxs\nQZWbuZSlaap8OMC1kWqHEZ2t3Oe/w6o78gebM3ydboPK21UF9QII5WTYpglP\nqCOwboHWgyy39GTPal5Cn1zj2LHesrCRJLSIw9aEgdpbz8kFV3pbLCKGLwg5\ngvoBdrSePEtVYv18h4rLeawa37p22mKzQSZxdeEEpHvhhIvGMXhOMfHU++Ce\npNCuKChO6TvM8hDk/dGwaa3lYm7VVvUvPme+JlxuzYLD1c3l52FcJ4XLeBB1\nAsCphXD1AlhBdn5pRwL70zQX0HRKOcXV3LMpXphF65up5FvgCSgT8rSBKVIg\n+mTY64oj0Me42HvCT+i9N0zPKOISTzZok7NS+DdZAjVsP7BGpwDCwHFtv5fM\nioW7mX3ka/QxKbt2igloc89F29b2D78//W+PbPm+q0RE/qBZUp7VqAPaH/fw\nH/cB\r\n=iTnv\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8f2f7668a46939a8675cdd882571bb3817627322","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.221","@balena/jellyfish-metrics":"0.1.42","@balena/jellyfish-environment":"^2.3.35"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.4.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.89","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.26-renovate-non-major-8f2f7668a46939a8675cdd882571bb3817627322_1611842216305_0.8758831841418759","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.26":{"name":"@balena/jellyfish-core","version":"1.2.26","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.26","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"58bd93070cd003c61102db8511f70c53de3ce33e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.26.tgz","fileCount":95,"integrity":"sha512-KwxNT5zZBz3K7LdqRMXdkTZTFCnCP/lAbwLn5O83deD5BRXwSp7ENJgRLfrG/9ZFgRLQVRNCeivOZFIqf+5QKQ==","signatures":[{"sig":"MEYCIQCXTdpRmqX/vfaFkkfGtY4K6r/8Ev0oKTWpXqLbjeKOfwIhAKcFNvPOoI1mcFQCOICesVUubrbgmCZKXdyauLxD2s9A","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":590715,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgEsO+CRA9TVsSAnZWagAAZ5MP/05CSczwL40bdd/k0WsA\nwdwThj52zTbs0QYS77qVZlvaWgpFVEMPJjTVCMm96S0oG6zu7WJ0E975CMO0\nIcYKOZDJpGLwYBOWqu7G5UeyrnBgwn9XfNtFlP9JyWNU18NP/DsEBgtifFze\nl1Ua5FEmM0Fkzjd68gofpserNoGAeDD7EEow767cgqxQ1a37Z1cxg4pm+Wm5\nwQ7K1IJ38JWzqTLWUSbAb14Swq+5cbsN63AYEjGHk1PTcGXOkdLOkgUEeWI1\nLkdPPFRZb6uhFGwxk63NYK76y67ibLMkGCBdoaFJm2xtjTpazYi1sLYM71q0\nxs0v7sZB3VyXzlVkt4qOi3BfW7HEb0kfEHsGqWjbjLxHuLrFu/vhBGSx0Ejc\nFcKYtfiIo0ttzWadKZHBSb7hMvvahVNa4Li3obnZPk/4L0u/wAIJyymb9wik\nDWE0N+MihCCuN1R29kjoM/o4HHbA2N09BtDfxAV/dI59RexHA+tY6T7gFN+b\nSzBmHo4COp+eKN0BV4QcsnBKEpNx6cpM9y3g+Jm1qXIPrFUZE5saiWdgM0D2\nE3Y/eWmMCmgz7RuQ4jh7aIF0OzgafiWsKx9mOqsSjJZMQe3GKo7Ig76PEJnt\nSrpNZCXIFBOTqbkvXKf0te9oPyluXY4EcRmRSv8YXTABB9rg2KO8iTNFQgjk\nZYUr\r\n=buUz\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c45b2b6e5c3d70112dc7e81b272fd017b0a02676","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.221","@balena/jellyfish-metrics":"0.1.42","@balena/jellyfish-environment":"^2.3.35"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.4.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.89","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.26_1611842493760_0.12517781028904884","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.27-whitesource-configure-b7ff8c9b1ab4e6523a3d2ff03358c7cdef6c94d3":{"name":"@balena/jellyfish-core","version":"1.2.27-whitesource-configure-b7ff8c9b1ab4e6523a3d2ff03358c7cdef6c94d3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.27-whitesource-configure-b7ff8c9b1ab4e6523a3d2ff03358c7cdef6c94d3","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4184f250164ec4a948629f8de8f50543af15f887","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.27-whitesource-configure-b7ff8c9b1ab4e6523a3d2ff03358c7cdef6c94d3.tgz","fileCount":96,"integrity":"sha512-wJYbOKrgWEbE+1jIwa8Eh9ZrWqBZEjP1vCLybKz8iPQwXsFrdrqKSmDbACAdqSKWt4x9IMdkLPc7Fpj5YeLxcg==","signatures":[{"sig":"MEUCIQC4FmG4i69HKNYlEf0pYfzQippet7Q+pbHf9RcCMdLYogIgJJ96ck7WVdiNWwTKEwrtZckHbM8eas0NiIiHwy0RcqA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":591089,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgE5idCRA9TVsSAnZWagAAshwQAJlKZ3cjxxuG8CGKWGug\nM6pHEbXSxWfO4e7hl7ZrxDNyW7cSz7/h/gPXHUQ1X9YD9zz4GCv+lcJGsxrp\ndyBHMfeea4MRNsTyrHFIgX7lFah1H/ZcC6wKF1acAAOVILbzveS8nx8T6a/6\ntuVWTNR4tQGG50ZPdfx6hwrMNVbkigPRHSk+qetcfBs7un4BUn1EHywhKDqi\nPPfQ/2SRyq+yw19XLwcYmQ+UPFX7YVdkcLM/UPktN3UiprZE+3LfSemd+Xig\nV8AqBRszsq0SI63/YkhU1Pt5Ga1X1SMZOIbsyp+p4GQP02vh9D1P9UN4yyUS\nmDJ5rh58SkToJJjIkwsJEb7fH272fI1KILnFTGKhmZiKcxrxYk1WuOw6C60w\nSF0f/nJx2HROISrevR0YSjLuTvG/TedngDFv88HECcosA9NnJt8zbPODWexU\nYYsx+KYnZRxUYxFFbqgAK+YEG1gTiyAz9xieoQ6lqohRaYtqUj6+OQ/SBYzM\n5Y/lQ8/l2l2q0kIFFBaVfU3bGUpewDp+qN66RLpAwGbqk8N7A9Gx8OFbfFZJ\nxPwbEmSKOiX6dh6kEnHcCgwpF9eybhv/oVPP8RnWwUGH3kDZa601WciXsK4e\nXukdRtICGI9Qyb2kHwCFXjGL6lojh4pngi/RcxMPxuB87GnzjA3qdQqm1gOX\noMdw\r\n=FgQ+\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b7ff8c9b1ab4e6523a3d2ff03358c7cdef6c94d3","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.221","@balena/jellyfish-metrics":"0.1.42","@balena/jellyfish-environment":"^2.3.35"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.4.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.89","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.27-whitesource-configure-b7ff8c9b1ab4e6523a3d2ff03358c7cdef6c94d3_1611896986830_0.8307599894826074","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.27":{"name":"@balena/jellyfish-core","version":"1.2.27","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.27","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6b6ed2286a224b64fe3fbeaba7826454a955d662","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.27.tgz","fileCount":96,"integrity":"sha512-j8r0/5S0qToB5DAeKF3zBJcx5rMixGSNev6PjUS7/LMIj8FmidBWcGy4lQTf5QGnC/HPJ2Sgl2kqGgmzctugEQ==","signatures":[{"sig":"MEUCIQCUInu7mTVgVt41z2/oY5/fCwd7YS69J1SxMNTXmxJv5AIgQN25Gs0QqV7gx35/vvxH0e40U8sxdS4MpcJEc8FlSAM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":591026,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgE5twCRA9TVsSAnZWagAAIb4P/3I66QZnM8G0mN8IUlDi\nlDoB87hIptjs35ukFlFqLBPuqHdFXMXQmHjNWJEb0uO5R5nZMak1zYghK5Su\nf89wYuvuj51VbYYYFgsb2gvmVa5HOsrAkty4YhntwNMvV3IiJ2+HRKYdxFhB\naQwcRVLmMowM/BmwEc21SA7jRqeT+wsUFsdsCIHIqatFlq1fmOaemgwIjoen\nz8e96nMVfMVre5kS/ss85DDcYX+iFYzCj3O841EH0Xi6XY7CI+wvJhVKVLjo\nELha0G9YGb4DiJCBUK/8hCrsF5H3TP3cqArzmNdu6XB1etXYpPwiOieUY/mF\nmY2m28mObsqUXRhAsqecu97sXSuDR6VIqjkXRAG8L7H90k+TeDNBBEj75NNn\ngE/A+oJlM4he/QUE93SJJDygQL5U/QSxjyBsv4wPGDBUYHin96J2fVL11LVf\nW7FFWoRUgalUfs9X7DwNHbIt6IJhXSFpvZcKiqT9mb70dLZL4qSQKCyK4o5B\n6XAz57rTt/Iv5S10SQJvVAuTXmD031C3+vif3OKVRKd5r3YNZe/YnSpTo4P2\nd83BQWr3QQOrNyoz9dTJfzMX62wzMOz8jjZY9ICBGsnR/tuH4CH5tOBVSP/p\n1siVkmfXLIQfTnhxXrure1Wot8VZ+mgGHCUy1k7i74vXezOeaaEmIuOdmfxJ\neANb\r\n=XzeN\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"cae9133bca33d1b1fa03ae812124e9b5e0262211","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.221","@balena/jellyfish-metrics":"0.1.42","@balena/jellyfish-environment":"^2.3.35"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.4.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.89","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.27_1611897710830_0.34034525379391334","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.28-renovate-non-major-2b528d689c905ab4613c5957ce032ba6d05d2a58":{"name":"@balena/jellyfish-core","version":"1.2.28-renovate-non-major-2b528d689c905ab4613c5957ce032ba6d05d2a58","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.28-renovate-non-major-2b528d689c905ab4613c5957ce032ba6d05d2a58","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8e25da25a907c5676e84757db74e44f553a8aab7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.28-renovate-non-major-2b528d689c905ab4613c5957ce032ba6d05d2a58.tgz","fileCount":96,"integrity":"sha512-+vI05HmCDXtWTZjAAbd5LJpauZLaH8j/2i4unhy2C5lOsPVupvOnZTA5bJ06UKsg1ghnU+76oosY5wkp87oUXw==","signatures":[{"sig":"MEUCIDBX/CjDcLn9Den3NSckfWCrbvEOjAC9Mu9A2CgAW71TAiEA2i+Z+vcVLgH9GOyuSP26ti4iS5IV29ASeFQKxf2Bh78=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":591152,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgE5y7CRA9TVsSAnZWagAAcnkP/Rp3V2ywxWhlkoDoUz+j\n2qnbH6W4Rd6h//gUBXu4twOVQydhNjLwls3Mu3qqTVvnfl/e2qvf30k/dnDC\nHZgLhq6W484KdriB3tzrOidCbtyYDmnb1DPNkiyvZnkKhAIdE71WVb1tpWf8\nXDKYxmXriMDQzqKebqLCaV/OOKn8OG5kWd7vxbRVrJtX8g8S1O4qKuRhWJGU\noF/wptHKXg6epxdPZvwi09LBrhMiZ/vwOU88fjBHY/JoGNgmK1iXvH4srcpj\n3ftbJufDPLJi53y1aG8t/cWZ+9WbKWSPY4cHO06+ljaMgwbxr9SVCJKyMALF\nyv/L9fLjQEzOHpq0sgZu2ypGTRashBLKqhWNUXnnw8VfXT+xCK1YNO2vBTWy\nmV4yEUF8zLfImM4vzGw5V8gNYmDTupVnLKjXKmWf1UnmcnprBBffBtvOCTLM\nHMDRVkB0y8rVdpSqQPuVIpIWFxcj1JXf8iUERrhG5mIfFCGoCPxYUqymJ07p\npuiEgGHZAW3nB3loGBzmkQX7qSR6rm/UZwjkbF1tdNsDNEpT5uan+K/4le9d\nelvj1OTLwZwTSX3laDhLwFMWnp5oxg/0xTC1aMOm4qzIQzEXzqlKOQd10qqo\nPoCv8uSQ9A9la31DmoRdn9i3HqNoV6U4MDDURxk4qlHpcFdrKk2ECLHcBC+5\n0Jeq\r\n=WDwS\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2b528d689c905ab4613c5957ce032ba6d05d2a58","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.56","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.52","@balena/jellyfish-logger":"0.0.221","@balena/jellyfish-metrics":"0.1.42","@balena/jellyfish-environment":"^2.3.35"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.4.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.90","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.28-renovate-non-major-2b528d689c905ab4613c5957ce032ba6d05d2a58_1611898042924_0.4132885572066738","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.28":{"name":"@balena/jellyfish-core","version":"1.2.28","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.28","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3142e4729a74abf5926fccdcad66d7b762960d36","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.28.tgz","fileCount":96,"integrity":"sha512-zEErl2Zu+RwBM69mNqUJcg8yETXs/QvcBFs5pli2iOerp4WozbLRLTDh21Qnvw93Cncoku16TtLvXQOi6/pFyA==","signatures":[{"sig":"MEQCIFkgiz0D071nl7VWKdnLfFegXagPXXT8z16qAfJUqsFJAiA5MJQzfP6ZlewKFc6ScQbAfM2i5LcQM6hXOPt8rZm5nA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":591092,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgE56CCRA9TVsSAnZWagAAeNoP/20Pm/+kycSXtLp1U82L\nqtz2w8MxcrZuBRNy46ZGKI9vV8FehbC3AXS6vsnurU9IyJruc842R8/1Yrv7\n2X0+483cGuJeznpTygtAdqhz94XUrsUbgWr5GCn60VvanPdUbTzvKWuJ8P9J\nTSpv0dBGiRDvIsnyzOxSJ/u44GA/J9NuKT2ia1lK47kL5/gj39ZzJNEZ7sXT\nhmj1zUP/sE8AsANsS/fNglnqmaifhFvS3PcZ+VXdi/a6pBavvOvgeJYZAhZc\nGPUM4R1Y1s1GEri7dBd11eFzfylNHbAmu40Gqt+NrR9a+Q84QEvan5vC0sw2\n8Jl+yOwrDNogCU+DIz98L0ukWe+Yc2H09jgcD7rbbacJ8ltiKnuQS0Ek2Ysi\nE0o8cbAku85+4duM8osFvihV9HJK5CKM2rIPb8dLgNH6CxbO3NCTcp2Bov8O\nipS0MevyP9x11oGc0PWW9pnl8LrLJwD74uZ0XJlJNfz+vT2QJD6wwnra8NEr\nnog8WJ0OWvr3JoHGGC01Yrw7AJwcCUBv0anPPWUnZeSkDrmiPW9Ka0mOTTyb\n8cQZ7ilRaT/2hdRRzog6WSi5YfY/+2MvzJUJQK5Xx7moAHIq8gZrb6Zv0pG9\nd+Tpla6PYFAhH2szkPVl/z2AISc2u247OfSH1k0l8R4JoncluEpVhshdobi0\naV8P\r\n=xIoC\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2b1fab3576ae79f5b17aeaf455e395e0e1cf243b","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.56","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.52","@balena/jellyfish-logger":"0.0.221","@balena/jellyfish-metrics":"0.1.42","@balena/jellyfish-environment":"^2.3.35"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.4.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.90","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.28_1611898498124_0.2206820891792336","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.29-renovate-non-major-84f381087749a8fd51be814cd4bc6ea5dc2838cf":{"name":"@balena/jellyfish-core","version":"1.2.29-renovate-non-major-84f381087749a8fd51be814cd4bc6ea5dc2838cf","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.29-renovate-non-major-84f381087749a8fd51be814cd4bc6ea5dc2838cf","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"050659fff8adf019521e42d84c0eb7fc1df02078","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.29-renovate-non-major-84f381087749a8fd51be814cd4bc6ea5dc2838cf.tgz","fileCount":96,"integrity":"sha512-LUQy+CSZDhRqGLDF/LBcss7RUVluusv52vozzKSPP89hsxPqmJ46kTQfIDLJQTYOqVcXXbTyPjR4vbvjwvetsg==","signatures":[{"sig":"MEYCIQD3OJWf1emUsiNaGYvTcRxBo8tLf5fDsdmiL5f6ms9ASAIhAPxvpH8hMNVMfguLf44efPn47p5E/TujMWUHXzIPuyXW","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":591218,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgFEpjCRA9TVsSAnZWagAAh4kP/14FkJMwLPH3s0R5Pf5K\n4lOhl0LY7bNSYEH6+KiKoSVO2NtX7MvOR/PS5CL3V11lVxXsn05PBo/pAWO9\nV7JsfKMu3+fEVxR7p8vve5WuOjoS0FS6aYCfkagIW3KCGMgW5iY9DiGDVavZ\nypt9yNLhB48SF3toBftGFn6HAT4u8PaBjE98L/py1H0BSJyRN50fKK2DBT51\nuJTsfAYS2dfPDJ+UYzgB6oXnxXdXJQ10BxQDdaZ64BJOJidw5JU6OLmEg7ie\nZ2m2APa+ygK7jqKjWrTWhs+rR8SEK8Hamx63SOuuoV6/moVy4PhjkjTBqXY8\nSqTIcxorbcj1/KjIwxrKU5/9EgpXXS1NJ0vxy7IgYBmUk4TnAk14x7kb8dlm\nVrbQFo5o+eV5nusPEf3Py7mEp26Y7+cVg0UDZPymO42aBf6/pk/RZIWEHhIV\n0xmh0AJUUos+KUlj9W61vh1ZNcZV/etu5XvY9Ocb5Dd+p8fy1hm743p/Ibqj\n4YomKHE8Nq3pPGSpu1ZVA8RdUO+NxZIpQ8NM6elgM0Ns2V042wAVhAyTyiFi\nolrpNYIpR/jnvfXRObH6KX5182+cP2zcm6oPDbjaM4DijkSph0LTDVnskigA\naEtIU60F3Z8A7mFaQUcHsyS5U7IxeLwjj/lIKigSa7v9F/xZshPcVhCS3PF4\nC8v+\r\n=fMfH\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"84f381087749a8fd51be814cd4bc6ea5dc2838cf","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.56","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.52","@balena/jellyfish-logger":"0.0.222","@balena/jellyfish-metrics":"0.1.43","@balena/jellyfish-environment":"^2.3.35"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.4.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.90","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.29-renovate-non-major-84f381087749a8fd51be814cd4bc6ea5dc2838cf_1611942499095_0.8151944874635451","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.29":{"name":"@balena/jellyfish-core","version":"1.2.29","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.29","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"09fa44ad9d223653f88d84090532f182e6e504ce","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.29.tgz","fileCount":96,"integrity":"sha512-ydMDw6tvQmEcmcATDnq/OWiu4uMFb5M4XXmoSN4+pXjcc4oagjs5FczJKkVO6xKVqgkc1tcASitoCcynA9jJvw==","signatures":[{"sig":"MEYCIQDiyWyCnF0CqkOl+tAQbXiHPucV/rXcypVTszvanvQw9QIhAMYyrETw+tQFYlmxth7nnD5NfDK2TGMdoJeWw+6y3Hj6","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":591158,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgFEvJCRA9TVsSAnZWagAAO3AP/iRXBS3nAWFAbbkAbpRP\nJPZZ1OJPbS9hnMARvxpMyj8sh2vtrXSsmyfjEvZl9POiTAvXna+ZX07QAQsc\nJUgJVVEPeuvSfMHy59Ghud+4HAGkEJ4a5ryYidHZMFdhtNLxSK5K537zZEJW\nTXv47pK/FHNEqsBfP7D2I9fYAsyxYsLpLFhHItxnPCDp3akuD/Ty+bOzDxFn\n/IWBQ+9A0+s67VXBRDYwFz0lYVRck3W+KAZukdRwmXulcriSwBOy2ciz9l6w\nxwdiYKUpslah5w/oEtzj1gbjO4C2FliWsFcmQZrsO9sPdFu2M7A0wFunsb4S\nEI4UrXFhY7R0h+AGNsn1VCf0T5i1p3VlKK3afw81ET+6LShHNSt0ZkV34i2l\nDWoPQZqt1L0XBTHf3NljPQJiV+an7aAaAGEOIccSPrL+amZnz9Z/bWIgw9Wk\num7aQ+MTV65axnX7TgKrJQa0cA5Fthl+/APgzlIHsuKd6qgTTusirmXAzuE3\nFdPlNzL7VV+57Kdjk6c/XCh3SvaKg1u1ng2CtlwAjAFOV1jjyC0YviJFg0Oo\n7KXXCivSEPPVEF/Rz+LfyIx2xWTAs0faSVnyLqa9BaErgjgoqaKCcLYyXQ8R\nFKGzedhhgmkrmapgGWQRzNoQ9P9G6xuqjcjXS2rxXhg8q2M6hIL4LqWWARel\nnXI9\r\n=HHD1\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"5078e0132724bec79674a2dc10902d28a9107f27","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.56","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.52","@balena/jellyfish-logger":"0.0.222","@balena/jellyfish-metrics":"0.1.43","@balena/jellyfish-environment":"^2.3.35"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.4.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.90","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.29_1611942856978_0.971455081120489","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.3.0-add-session-scope-402bb7eabcdea892d8bb2284da825b66d76590ad":{"name":"@balena/jellyfish-core","version":"1.3.0-add-session-scope-402bb7eabcdea892d8bb2284da825b66d76590ad","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.3.0-add-session-scope-402bb7eabcdea892d8bb2284da825b66d76590ad","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4e489e2488e9e4dc5636994534156fbd50eb3a6d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.3.0-add-session-scope-402bb7eabcdea892d8bb2284da825b66d76590ad.tgz","fileCount":96,"integrity":"sha512-6KhZLdjJrl+XrTk8GRqOoID2iClj5gNv4M8H1m4HJQXR9/q520a3corXNx0lPJs3yav8Uw5GvyGxF6F2STwQ4Q==","signatures":[{"sig":"MEUCIQCdaAVrDYsoHxO0+nWpTnE0a0kS5oVap+3bM9OljLAczwIgYLB/C9UU/TCX25rpE7nmLK4tmtAkxPAMtxdcpfYugmM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":593307,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgFg7dCRA9TVsSAnZWagAAxFYP/3fs+1Bhj/pKd9B5H9u4\noq06uxz1+r0QHFQ3GcEtZ54IduBeafLNwZGzRVzTZKsxPa4SFOaERairwIbx\neKrmOIjwJ7pbnJAov2CK1gt7140AWaHjcj7573SRDF6eJfxDJanoGckL87VU\nGU0GkrQMZmuzTUX8VN91fPPGoVrMsM+qoKAkEK/I3d+eyQLyQ2w4LGHZbeIc\nr9VOj3JZCQFDNY94VXNCImRtJaMoGNtjHC1J2a/v92rHW91r0gFMxHE3mxVl\nIVQVDY+eo99RQptw2mUh8K61BEL00XKqilGDPJnR7CtZ4bIAcb3uZHC454aU\nyJ2P/auSU2Q4z3NtjPTpJ6C89Lcv3BvrNbIWv0JP39X8Sjr+lytQ8sunmrfy\nBtoIaMl4+NlUgwmuzpRysT2IpJHz3RnsoWK79n2f3nzsy9dQK7hJNLuwncjq\nSyDQ4Oa0jyxB1+lmuYtQVL4dKDezxTMXiGr+I5baXuCJoVPEZBqrQIP4c97p\nYtTIRVy0CynOjQYbepcJSQLEBmP/GqlcdR1+JJ/ciIKlUuaGIVOf5iJiyA+y\nQR9WTULnlXWt8AOFr9t0hWALewDW2ZQp0rIcJjcKqN1Ay+pE2d/xVvsO4ewz\n9DG6jcui8YxJ9/MaC17xSwSaxiKqViFoSeHotskTCDM1ZfrbI0eRH1yeP0dV\nrLCR\r\n=QHsp\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"402bb7eabcdea892d8bb2284da825b66d76590ad","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.56","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.52","@balena/jellyfish-logger":"0.0.222","@balena/jellyfish-metrics":"0.1.43","@balena/jellyfish-environment":"^2.3.35"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.4.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.90","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.3.0-add-session-scope-402bb7eabcdea892d8bb2284da825b66d76590ad_1612058332983_0.8253386194121786","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.30-renovate-non-major-8c1b049488c672537563a115b2ffb32d1340505b":{"name":"@balena/jellyfish-core","version":"1.2.30-renovate-non-major-8c1b049488c672537563a115b2ffb32d1340505b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.30-renovate-non-major-8c1b049488c672537563a115b2ffb32d1340505b","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"eecfd64693102e7a67d6c6394aa9b96c87603f51","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.30-renovate-non-major-8c1b049488c672537563a115b2ffb32d1340505b.tgz","fileCount":96,"integrity":"sha512-N1uzY0IkMVNtBSDeWaVtNZz9MgUKQIQsYUaiflSMO+kFGf/rLf/xeruog4ByvNutLP6N0wVQFGjf7Y7sQPmINA==","signatures":[{"sig":"MEYCIQD+YFFcc8xUdB6TrXSGKeU7ht4Y90doBAm7gSN23Nt90wIhALp/bnvCpKOBU5dkIE7DBqCVDOlqs4yJKBt1G2QlHwGr","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":591303,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgFj3jCRA9TVsSAnZWagAAKqEP/0y1e74Ty7f0j10h0uJd\nlbnW1Hct/5IsO1Pci4auMOwOX9I1IjMvyWZ6EJNv4WBOI4uZfUiG7eMkAIfh\nvms+BUdxEDOi7q126BnPbh6VlBULTQGWEiePCVFHBzJKA59I+lmrtyPkg0b9\nvkNJmgj9setMYlL/ExSkWeB/nptXj7HruqvTAMojCGqZEZe70D+x5eQixNCk\nKN45HdBZpRR0psfIKOBk8g+2sg+23gIRvM/9gVCsUgBaSm4KjCyZuMsrY9ok\nWknm+GACeit0PczYRYTziN/juyfIcft89Wph44ew9kWts6X+hsn2p5bpL2u6\nyDs6vS6I02PWKqOllkAALiDQshuOg5AGm+5mEWlUV7F2IjFE/3Gauxafx8O6\nNjyyUfylXA9VyoRGM5xAI0LyE2s/cD3TLIsRiXEG7Ju5FC2pE4RJnaFa+HkD\nWxMeUbQaYE5n+zZ6rhKQoJMdab4jEYiR6FkqfGNmi1aAG7mHb0W0ym18mlQc\narn7mT4nEI/3beP7S7FND20Cngi5SxDIuBP7cLPUUP+lmjkuJzSSTJgnzKIQ\nbiMU95aGSDIgciqSCRcrX2GOIxxYK7OqmYGzF3li0LijxWd0gsL+2o2KoCLI\n4Nng/pn1ekDS9LU0owh7w0ieJ5bKVMFaZknWp6jOd/aiKfaB6zeOT3s2CeVP\nMV8M\r\n=7z4F\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8c1b049488c672537563a115b2ffb32d1340505b","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.56","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.52","@balena/jellyfish-logger":"0.0.222","@balena/jellyfish-metrics":"0.1.43","@balena/jellyfish-environment":"^2.3.35"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.4.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.90","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.30-renovate-non-major-8c1b049488c672537563a115b2ffb32d1340505b_1612070370828_0.49123412695727753","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.30":{"name":"@balena/jellyfish-core","version":"1.2.30","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.30","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1790e0d52c1d15185fb6cbd5ebee3709c848b435","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.30.tgz","fileCount":96,"integrity":"sha512-CGL/Eeid5dyTE8m2zwAG/0t2JXqg9BykM9fDQkgVZTx9N8vU9r5UcNYUkGwQVq3NK4pwJ9uZtKPGccLoGEinAA==","signatures":[{"sig":"MEUCIFid4uH6BWGlsLapMsD2oWtsKo4ZPGaJ0BRB5JpSOx7+AiEAjxFf3ywHWcqC7eR3RAjng4mJ5S0HfNGE7wYEOAfSnok=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":591243,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgFj8GCRA9TVsSAnZWagAAMy0P/3bi5foWz/KrXFoMEYMP\nzrDC+ljVyrppDP606pUMjS4MpxcAoo9kXTZ/+dF64lnrs6/J+qK4QqUnZsph\ngsc8oVWGIuIThw95Fw+chsTKRbLgcKrjn/wFSmO8IH11GFzRc0Di/B6Ty9JG\nCdRHR8EfUTAApKxbEeo9KmW90u2M6vlZLCpRswSM6kBfefK1AUDpoRywdOyF\n/KGo2rMvyAC0/jFPLsx/Xyxa9mnEeF5Dx9EPyKWI2RdV7sdlrLDNPLS2MI8Z\nycBx5TWZHRxqqPDkrvTGB4/paxtKA4S7+KZgbWt/BDSj7RCbZV8mmrkOJTyY\nPl3oRRw+9P3EmZEDM1TAp53yG55wzQTG2t1Zb4x7ouAVM/qROvzDacGl6x7Z\nLeM5ISbBxmstc7lpOCydNYLlZ5INvHmkFgizTjBRG/6KWWacyglyhScHKVuB\n8LEc3xAgTLoynOsYa/25TUwV7Q19/x/t3pal/R7fj1thIwtD/OcKruLZoKvS\nbwOycE6bBjxeouWXoC71OkoPrUGV7wYfYpF+/F7NuQE0cGbJHGlNR6+Nf1yp\nyQVIHr170ZbOQCPF5ExveU4bTq6ffQJ3WV0pcJTyzvu1uYvwGt2qouQ3pyLH\nq/n6c48FzSJZOsWwf9LtmNhRJJRXeOXGsh1z+x/InG6mwBviH+b8qsASsG0w\ncaD0\r\n=16qc\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"1e2ca11c245940eb82673a1d66cf410296611226","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.56","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.52","@balena/jellyfish-logger":"0.0.222","@balena/jellyfish-metrics":"0.1.43","@balena/jellyfish-environment":"^2.3.35"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.4.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.90","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.30_1612070661953_0.15272997872587246","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.31-renovate-non-major-ff7cf2846b39fc87e6834deb9759c551cbf76d3b":{"name":"@balena/jellyfish-core","version":"1.2.31-renovate-non-major-ff7cf2846b39fc87e6834deb9759c551cbf76d3b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.31-renovate-non-major-ff7cf2846b39fc87e6834deb9759c551cbf76d3b","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"06610f9b64a0f37d9215871d1b090ffe65a8023b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.31-renovate-non-major-ff7cf2846b39fc87e6834deb9759c551cbf76d3b.tgz","fileCount":96,"integrity":"sha512-Vv5bw4GR7R+AnN0i/cw602xm8tbGzNh5yp0/8PVhJ8YpLOOjo2TxLlXAsJW9qaGjIn5T4UG1HTmk6N8fTM9xew==","signatures":[{"sig":"MEYCIQCkMU3T3ahNAWuGeKWkcHfSMriUyI8VNfJZJ7+COq8xvwIhAP8vLznZQLXZ00LSREdK+u4NEKMEPmj7K+h62UF8SWp5","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":591411,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgFj/aCRA9TVsSAnZWagAA3h0P/2sSWxerNBTftsyVWTL7\noE1X1yBF/udOP69F+cfFM3ruU/bIYKUe//SOa59mwt71OAaJpANFcWuH/3lU\nqadhqPWkAynOZXMUxmr8iTDuaUg2Reu2RINhIQokcwiMzRnB4QByuSfM0k7I\ncTqgBoTpK/jGUUmU4pXRzAgJv7zpc3P/MjXZHHYlMMRSHuzTzkIaiBA7R8l7\nDb0bcq9cjD1TvoeZQorTvZhu3goJ3T7bqX7VhZYnp4gOpHf957j/z+5sHs44\nDyUVfIbByT1A5/FRQIFJrBjmdX3NjNrqHqv10WiMSLlDZJNki1EOjILD/J+2\nJaEPSpJeLkgdcezDOYjjFBBp5pTEonQPfzxmH9ShnL0lb8GnO7iqKIxVdOhs\n2i6dCAV4qujYaEl7zYrm+D+C933w+sMmbM/j6kQeUmiKKE4xbPKhXSugkqrT\n2Wjsl/wH8DQlnACk2GVEhe5Nmpv3N/sOXn91WQa/lU6spqfyGvqzMio7v8jr\nrPOG75f9YUQj5HE+tpAqUZx4szuF5HbicaKKdjGa5YFNb1XShIDOP6KwkAOX\nwE+R/Zq7WRnxEDSoj3OOFKq+jq06Oy9HKlFNkS/LqLXX+vcAN+0OYjs7NEsj\nRh/Chbg0HXH1k4Pl5q9Gq/EBvvasmn6j/M+h+SI1DgmTs3j4EpITlEY+uu0v\n8PXf\r\n=Cfb6\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ff7cf2846b39fc87e6834deb9759c551cbf76d3b","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.56","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.52","@balena/jellyfish-logger":"0.0.222","@balena/jellyfish-metrics":"0.1.43","@balena/jellyfish-environment":"^2.3.36"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.4.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.90","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.31-renovate-non-major-ff7cf2846b39fc87e6834deb9759c551cbf76d3b_1612070873431_0.3086019233839552","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.31":{"name":"@balena/jellyfish-core","version":"1.2.31","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.31","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c8c88ef1271d65b9031e0504cb07da6dfb2dea61","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.31.tgz","fileCount":96,"integrity":"sha512-d9q8x/RnSjUx9cZJNWilrsJPHJVqqSzURzLRe+DOnO3Ec2oriWdFtA9hPgQXNC/MvJfN6I2txZd148//Es7VRA==","signatures":[{"sig":"MEYCIQDv1AOCCcUux+o4UB3MZ0l+aCHTx/DHKs1oxy/IPcs8HQIhAKfzRI2w04HuwwqfOa3NPS8I6DYc9HuZZm5e5qS+0ik/","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":591351,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgFkH/CRA9TVsSAnZWagAArLcP/2MupPNG0fMHtucnsxer\noYVyyeQtJFkDUlKzniOj1k7VYB6k59FJEURoPXFwJwq5qFACVzK3JP4GbEd6\ntkx5GMjKSr+9HlJXoCrwJG3HQWeeUF5ZA+4hlavn18PMNgcfS5aiaV45Fyqb\n7XeUULz9OqzIOhR5fF3mGpxFLK/i4ow2BkVjmF4Io5/jZfxZysEf8XMvxEqK\njN+AoQYIdakDs/13fxvbmt8puz8dresrgl8Hmq2Y/nXEIyLsBlrCqv/9f7eC\n0K69ucTQvIjOaqzewIA3rvIpHqevbpuI5E/njGVOYtDbqundzUC6/klB1GcN\nzotG0h8YgXzBZYXpuMAmMF3ReCE//aoHl6iR4sQVSzhT5HgCTktIhW3INPy9\nuq1wHSkVL+zIfCaC3PW+IeXhh3wTES3NRajv7pzAracPsWafS07cxTZVJ9O3\nxcvDtfKs6FHcGTUpWlCsNV4u08wQzwbFpYThPseaoA0OLF3wPzAwAmmDqvi8\nuWTNrnOKGIm+0UYn9Sx0UGnq1V0R9lVNqeCRDgeIoWwdEqbYUrqsGoVXQIY4\nPNQE3Zhhu2G02++OMoUGs52Df/QH8rHH0I/6Z6XWZsKZfyl2q9xW0x8VkFo8\n9gsRqkjPw3GncpviPi+UGwaXilHyZ1X3askWv2x3iyG8I4WayB9w0evRm3dP\n36ou\r\n=W27H\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0d8573b83bcf2bf9982d46d4d492d1a3225a61c3","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.56","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.52","@balena/jellyfish-logger":"0.0.222","@balena/jellyfish-metrics":"0.1.43","@balena/jellyfish-environment":"^2.3.36"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.4.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.90","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.31_1612071422565_0.6773282478913538","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.32-renovate-non-major-bb3db86ce43b87fc521b2079f1d16e822d915baf":{"name":"@balena/jellyfish-core","version":"1.2.32-renovate-non-major-bb3db86ce43b87fc521b2079f1d16e822d915baf","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.32-renovate-non-major-bb3db86ce43b87fc521b2079f1d16e822d915baf","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"10a8f8b3d80eab174a2298e715c3e05f176e3b7f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.32-renovate-non-major-bb3db86ce43b87fc521b2079f1d16e822d915baf.tgz","fileCount":96,"integrity":"sha512-p086L3us1zLZgTbegcBRy3oSjKWlZnQ5ik1ClC6zEA1ye294BtchAt2HskSNn+mmZVYYHHH58AIlY9OW6/B0Og==","signatures":[{"sig":"MEYCIQDBSktRnJj9zHoQBgE+T4e5VM7em84RvXLMrh0ps2bxiwIhAKElhHMrKSrvtZHWLhTSvgS2QVjszaARN/1Z1pixIExp","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":591477,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgFlVtCRA9TVsSAnZWagAAGaEP/RrsZ09TER7hCWuqLGGw\nKqZZJUKT2RAg5nJD6ykBAwDZssnIM0I+tI701SDWt9iogHWFLSzY9cWzITdt\npqCMm8sVq7Q6t3QFtKT1d92/NoGZjCqAp2ZeZFioYk2kcL8Za1O5QOSUIEYU\nID3IFjvvACJykkrD66Af5UbmMSuVLfp2J7ckW+1dt5JtFotpzUIcn8zB8vIs\nZU0mi8D36QTOHSOfJO22yiMUcN1t0ZxA/gzjef6DJjs5B/WXYCmshCc0HdC8\n08sVSR9NMCcbDNpfIuH/cJbzPkNMedaZXbfPcH40IeRtpbV7UfS7t/ab3at7\nuRtn2YihPK1uHfVP6c9ss1q0/zqzGl/Ep3MmuEM4wFnEA8oWt+P1Z/wbdPz6\nPODJXOUL2tr0+ht4bWhNioqP6/aSI/1F+WITK6eXRkdWCun0rNMZb2EzPzEI\nOeqsoKOTwAVkyQFdN5z3SK3U+PoJniMfhfoK/9Wv0jAHh79U4EID+whDPlNl\nW3e6Ev2d2tHqVeSuzr+q7M0Wi3aJqQwu152fHcoWglEwXSI3Ld47EnCAe2b9\nWJO5kakhcUuOn+PL0zIagvCjY9C1feEcZLRLZxkpy/GTBJ3d6O0r8kf6yLDP\n+b6c+kmhROtWT1e9bA99R/IbqLv2KS0CgqoKSrdLgPDiDmT3ybstmdERj+DQ\npgcl\r\n=kKaO\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"bb3db86ce43b87fc521b2079f1d16e822d915baf","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.56","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.52","@balena/jellyfish-logger":"0.0.223","@balena/jellyfish-metrics":"0.1.43","@balena/jellyfish-environment":"^2.3.36"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.4.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.91","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.32-renovate-non-major-bb3db86ce43b87fc521b2079f1d16e822d915baf_1612076396641_0.8737391667443057","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.32":{"name":"@balena/jellyfish-core","version":"1.2.32","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.32","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"99773c124caf66008a010d0fab4a5aa0aafbe6d6","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.32.tgz","fileCount":96,"integrity":"sha512-wn3X5nEoj6doaDqmPCPuSkrqXOW0zzx/KnMYpVsU1DBx+1k2iUIsjZrngyF3CcjAM8lAgSP0YNkUAE9uFm3s5w==","signatures":[{"sig":"MEUCIQC8q+EkoTxcbxAYAmASuh2bPsNEzk0DLhvPy4qz7UnAwAIgdO7XSiC4FQIkQxaFN3Ns1h68ADz3H/6Qpxs7AcFE2Xo=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":591417,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgFlZOCRA9TVsSAnZWagAAAzkP/0IvhDdiyHvD4a6z1Nry\ngUPTW9qiR7XdTP/ncxBXRSyvkbWspEU+3I9CyoPx13K52sE6lhfD9raGB+/Y\n9sD093x79D+dE+PYqCHuxydmZL9nN4Lm/hJz4WEkR2nJ6cbqhGOzpdUYrYvr\nAtP3GqQC0tPFLzY8IrkvI3y0R647FLBnA4ffF+uXiSxcMzjlcb3htezW7gPr\nxN6ebXL6tSQTrJs+Ao1MI1XRdkB32j4zUy2uQ+mtFf+4dlkR+y+zWBosjv4q\n58VUjEpljZsjz7MpteAdHUeMzSOyw4dIxnI+DO5PNK7/79ffgiDHD0NN/uhs\nBkvuYyJnNIY1VJJyWOZ75TAl6vSS++TUMj4qLgd90lRdbfQ3r1o4VhmpFNuy\nw++1cEvhPHNaQvfxsmfeDyy62APlCecMTrleGWyvpq3UmxFTnDIw72nR4alF\nXECWh6hAQoFWPFxhs7h8IVT4KGYxzuVE2eT80ExkGXaIFqJ0UL3NNGjdjBTk\nAVwc1cQ0Ul1YbZeMPXhs30sq8RVvfiVTHuvf2iwLVgkVksxH0WxIB3u5ZF3A\nwJpFputa8Y4h4LLHUP5uFYLCoXJes58gp8+QFoF5nasRZd8ccbCiG8wC1LNY\nX8/swMCg/Bp6sQLynzrVm9gpYzUmhZGCTCWjjRA8hHuEEXF4niL1w3WU5wLg\nq6uA\r\n=nSOb\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"19b5b4238236f5555e9388aa76aaa962e95b9d40","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.56","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.52","@balena/jellyfish-logger":"0.0.223","@balena/jellyfish-metrics":"0.1.43","@balena/jellyfish-environment":"^2.3.36"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.4.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.91","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.32_1612076621685_0.19099145386935645","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.33-renovate-non-major-fe05dd8a34143fe684559ca4e6f67282e07ce9c9":{"name":"@balena/jellyfish-core","version":"1.2.33-renovate-non-major-fe05dd8a34143fe684559ca4e6f67282e07ce9c9","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.33-renovate-non-major-fe05dd8a34143fe684559ca4e6f67282e07ce9c9","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"119f8ea67d730ae58899b7f064b7d15208b71747","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.33-renovate-non-major-fe05dd8a34143fe684559ca4e6f67282e07ce9c9.tgz","fileCount":96,"integrity":"sha512-Sc0ARNxrSn/ZJlvp5KcEpxeWEAgZaSY66bREWz5S0uLo5eDr6Q1BuK2Dk2U1pNhM1PX2yRiDWCDCTm8OTDKW6g==","signatures":[{"sig":"MEQCIE89/+UkN2k0kMeGoje8pckQWLUchDqF24yuGHiZASknAiBMTgvvdMDwEvEUzHznUx/78Ni+irO71nqLxyu3LirmRw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":591581,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgFmZ0CRA9TVsSAnZWagAAunIQAJ2rz6WLYuMOF7MIiPFo\nUQfT4/6tV+VqkmEV45wSSffmnLpulGcek9XcnsPmvQ58qyP/rJ55ckgwkfb4\nzY6TIcMu0XSNCPIu2j/ZGwB4iDTTeKZUoVq+fGJEsrWU99wE2NtJ512Oc6dP\nOXJTrCPvX/nuRN6/saFZz6VsDFklfF8eLtHLCcr9Lw5cNb+qJFzmXyTOn9Sv\nv0d4hTnSzsFu2YhAgXg/Su3HnUkMYiZLevrd8aRJFnLtExNJDoJ4m2U/TIC+\nDXiRk+c+8+Vo3hB0/UpVEuQt5ABBeHKRputTSzJl4JfrNLfViyxg5O4snZ58\niEBoo0/wfyKwly/HyB5GGLrTubObCROPYzS+kYSO0aYRVEUe5iRKK5t2Kp3n\nzxywI735ImkmS3gg8FwmXWlTJuNGEeyaEHe4ix64Mkq1em9DX+CzM48ewF0V\n/qb0gAvLMR9csFk93Qu5GkTaSNv2DE+AsvSJ0cyggYW8mBmNu70MhtFe7rWl\nL2/WnLaSSAoqqQwIHtLuGbBkJaRcd0XQmDunjhd9S/qhAvC6cY+jCrTNjeA+\n0SgnLuub7x+EOqYGVqVzRc1aNhaEqaWZ1ojWm96vbW7D+pGdqXBiHL6vPspJ\n5QYbvni/i9KmN7TqRBBNqMuJb7XJpfGp2ibeIz7+OQsQnJTeRgs3XlzXzi/s\ndUg7\r\n=R0ZR\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"fe05dd8a34143fe684559ca4e6f67282e07ce9c9","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.56","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.52","@balena/jellyfish-logger":"0.0.223","@balena/jellyfish-metrics":"0.1.44","@balena/jellyfish-environment":"^2.3.36"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.4.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.91","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.33-renovate-non-major-fe05dd8a34143fe684559ca4e6f67282e07ce9c9_1612080756029_0.6318029563973591","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.33-renovate-non-major-be66aed21dffba6e790d0100d3a3ba79a6d84259":{"name":"@balena/jellyfish-core","version":"1.2.33-renovate-non-major-be66aed21dffba6e790d0100d3a3ba79a6d84259","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.33-renovate-non-major-be66aed21dffba6e790d0100d3a3ba79a6d84259","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4323db4f70ee5c4d9f04d9c218d72dc6f477d6ac","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.33-renovate-non-major-be66aed21dffba6e790d0100d3a3ba79a6d84259.tgz","fileCount":96,"integrity":"sha512-QXEP8QfAVJYF9FBsLLCi7eYM0tyqE7t1aGC8R3ZSHTwh4hZ7TbPhMv/ZAitURBHssQo3OEiDGOvzGFEtax3ljQ==","signatures":[{"sig":"MEUCIQCXCC6I0ZfPanw05UOo9AIWl7tLJvzg8IwfJoHX1FM8lAIgEfzb4K1z2LtIPbYB/sTVnJMJnPZe7OCQkxU6hNRUbMk=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":591543,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgFodlCRA9TVsSAnZWagAAbTcP/RAnYkzt4YJw61E/IPc5\ndUmRP8IdIFqELcSlJKuL8dFlCYeN4WgWEO9Tz9Q30RHFP1jzf85mYgsg1upV\n86zYXLM/p6T32F1ga8KfZfZjfuKS2vooQrUug4Hid8FcWWxsYKB99kyrI9yx\ndCQjj/RxiAx9KaDSqazzQ/ZHgIlE0KPJv81F2O1dccGRQ2Lte+PlJ2O0D7zX\n1wgRolZTDlvjQvPK0llVdCjnDFV/qw0Z8DzTKTFegh+8uuJ9BPy2YhkUMO8k\njCHdM+hadF/enQQDHBprDa9RIHPqS2CnokMahJogC9VZnWG0gSinyKdMXpcR\nakaftmNXJ61Coq6ZPJQOUwlgm5sBai5bVjFAC/ULAGqdsgid/6Tfykx5iMlx\ngE2VPDeWGfjSDzlsWn2OCGOig100X9CRYTW12y0359j6fJ6qUxVnheFOmIGU\njBAc5AOoZbJ1iD7Wkd938dBOOwli/fTOpOEdZdG/p24C18HXNu00LYIyqJz6\nH+KN9eHAHICo2eL/K+1JZZdBrruO1ByV79pLng28LTe8MpUwmYaAOOIIVykJ\n29vjGiCtp9Jb2EP4mGd6CmNw1CF0i8seyMxObtp5U55bQpjqGUShoTnuJKJ+\nEUDuoLc6lILKR+ZE1hjjuz/Bz22jQPSUwI/skdV6iKpo1h5uJsMiBEAav2T/\nAG2R\r\n=M02Z\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"be66aed21dffba6e790d0100d3a3ba79a6d84259","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.56","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.52","@balena/jellyfish-logger":"0.0.223","@balena/jellyfish-metrics":"0.1.44","@balena/jellyfish-environment":"^2.3.36"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.5.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.91","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.33-renovate-non-major-be66aed21dffba6e790d0100d3a3ba79a6d84259_1612089189001_0.8662880956333141","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.33":{"name":"@balena/jellyfish-core","version":"1.2.33","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.33","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"58aafa2c2748d45a93accb32d25a9ef3536f31be","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.33.tgz","fileCount":96,"integrity":"sha512-RC6vXRqY6oAm1i4FZPWNS5ArPDWu/QyXIKRIUFkaCKlo1TIIhEhKgkjgRM9UkqzfCZoBCV80oNIXVb0Das/7Hg==","signatures":[{"sig":"MEUCIC/Ai18kgfaSRbK61O1g6eGVptI/GY1ECZ/uj28DZl4fAiEArVFZ5xMnnc75ykvJv/UeV+nCXspl0mFADoje5lxtrVs=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":591483,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgFoisCRA9TVsSAnZWagAApuIP/1+7YYGcrAcVvnJmUyrb\na9fW/p83hznDL9xB+V6Tu266EHmsVTstbOocyiIpIZjGf+z2Qq9+YwnHTn/1\n10QEO8ig+cIro4mNeibMWJJ+fQ7I+TuVVzQZCZ0Qoto7ayit1WSBUfY0yC0x\nW0s0ap3t0E9OslRd+5J1S8hCHVsccXoolexID7084uEGCCn+Gc7rtIROwkTL\noBCuT0uoy2AQ0JUArs0SYlYMLbow6yGAKiEt0bnllLIkVsXB50UnE7J0xEdO\n3bsmnnuUmdLcYbK6CrU8eqbCb1FMufYtlS8LzpE/vL9Px3SoeQwjcq2tpNMr\nKu4oOlFUx8JG7KeMAFyDa3RMilibcTSF5PeFvOFSmC5Shh0nxx5Gjqbi7D24\n6PVZsx2SW+krSOPJ9TBT7XgrSJP8ouZAL9bPmn9qKRuKqoepEK1GpptqVdSB\nGbzJeStE2ZSG7FoHk1L2dhyZhyJPNbNdl9BNCVX+xth23J6MZbWkyhWJVtId\n48Bncmd0i/VY3vXcZlbiIqCQJyr200I7UQ6HKioNfGz7SdivpGFMCMQ7GkF3\n5KEepx6mewlyjyV2Ew3CbyreKDxkVaSe4xWRKA16nueMs64y8X1c4qLUJqjZ\n8yII0p/cgeSMNxTq2VGtcuIT8CD0il2v/cFEX6YccrCubQubTN3khwRS1ab1\n9Ryp\r\n=a1pH\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"5ab46b6501a1ec872ff59f4cf008fbe37d089306","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.56","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.52","@balena/jellyfish-logger":"0.0.223","@balena/jellyfish-metrics":"0.1.44","@balena/jellyfish-environment":"^2.3.36"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.5.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.91","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.33_1612089515680_0.008251450387848802","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.34-renovate-non-major-ae4232f69f923cb4d87842a30a09af59192c0daf":{"name":"@balena/jellyfish-core","version":"1.2.34-renovate-non-major-ae4232f69f923cb4d87842a30a09af59192c0daf","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.34-renovate-non-major-ae4232f69f923cb4d87842a30a09af59192c0daf","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5debec687a44c9f3a7029a071dae7063a38fc6bd","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.34-renovate-non-major-ae4232f69f923cb4d87842a30a09af59192c0daf.tgz","fileCount":96,"integrity":"sha512-vJEkCKHoKd00iOFK+7xjluGwGJU0n0tB7H0MekUaeB7+HqewwqcAWep7hRZ8TshaGG3o7qAOLoipt5FqYzjrJw==","signatures":[{"sig":"MEQCIGK5WUJSwjC3lEYOXakfIn0K1DepMCH0G1eehoMJE8CTAiB0H2k/31s7AUi291M++9UKQazSfsPgR5PlcaS5hxDgQA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":591609,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgFpJ1CRA9TVsSAnZWagAAY2sP/RQQphARN2mgTkLLVww3\n0QSZtTR4WbRvVIfIwFuaggSQoKFdJhzuVcpjvfXuiYTkBVvELTciZy3jgMKR\ncSVnIv24fXEH0kAsftdDDY+ro/ozOEhlKchW9u1CmMTFY6JZKcHwb3kg1L7J\nVj++zQ/Fy8YOi7wIZ31jF0T0h81ZJB29dqT+wQ5/jnzPFVzuplgB4Fl32UOe\nKgyJo/tVD9RDOEvu5+zgDsjf3WI7cI4dHpeTQ9zcfzbvGuARNmqpVZoeCj6x\nxnMwZ0tZDg3ankXFm7D5jv9hAoa4cecOlSXJhMQTVQdzc47uBPPlx/9K5dBv\nJegxBHpqSJ0vBFvU2WP8KN7CkEgiQs7IvRfki8yTjmk5qd20pc4/MNJK7GIp\njVQGUiUPIWfRON0fBOJRn6sNlS0b2w+jj2ui0ufAnC6Eh0cowiRdmSmJTFMv\n7ALuazGuhkQ0Yzkfjx9ha22R+jPj9F31AUKrPmaeBL18pUgab1uaUvyacDvY\nDBus0tjXUOJC07DCIG7pHpukDEPeNHN5ZIuNHWN0j8+2P6j4uS99RXhP7aog\n/z4KSZtRfWqLH+5zUG5HhSeo6ZQiJyGPYV4GAvTJHYlAIoI/jD0z1IDbmQqK\n0BiORJJE/ZRLpbxrk2CMq4VFJmklZ0AVXdIoU8hLqiMoKOPtKDiruuxaFYy/\n6/oV\r\n=gUao\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ae4232f69f923cb4d87842a30a09af59192c0daf","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.56","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.52","@balena/jellyfish-logger":"0.0.223","@balena/jellyfish-metrics":"0.1.44","@balena/jellyfish-environment":"^2.3.37"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.5.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.92","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.34-renovate-non-major-ae4232f69f923cb4d87842a30a09af59192c0daf_1612092020705_0.2026071119262447","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.34":{"name":"@balena/jellyfish-core","version":"1.2.34","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.34","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"70ddecb1492cc779ea1b4f026838c22c76775f0d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.34.tgz","fileCount":96,"integrity":"sha512-f1a5shv1j4Ud0l4CBJBDsgrqojD9CJZsgiZmSmw7XrS8Q5upd7Bx0Mz/51iak3uN6P4mpmJXElUWSLPu3bVQyA==","signatures":[{"sig":"MEQCIFp4EVDgm0OooHrOq0H34irpaMr/TpEEDd2+jH5uNhj4AiBa11HUjto1qvU4y88mQ2ab3uURgnLHrNgPajzitXutxw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":591549,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgFpPxCRA9TVsSAnZWagAA8IEP/3QKm/IPbkSC2+BF5f9n\na60c46emy9aRRWlV3CMFwVaHgzQ18M1tYVrljYKdqrMbGzhU0njotcp51QKz\nTJM+5fdppMQeosr9F9AFCRtXjbMypOzKAedwJRSZCJaMoibIuCHKA9Vwi35t\nDlptMxvP8Njr/ekulHdHS5rPgtZRQAYInKnG9imZoiWTOvZnEqUkbelOQ2vL\nMKQJ866o+WzyLdBQNlq8C2Y3lOA4d7awmscEEpjHx0M8OueXiuO8sObCNsfe\nhGYjzcjeXulAbyt0my79e9vhAvPJhuIzpib5mnzracIzt4D/mvUP26EZIJr6\nyFjoXDFMPfsLirBs0MyedV3T9IeLH5jte9mShHHVayah9rk13C39ePIkZ7KZ\nIMvYVJ5iyKllUMAdZlfldoiSiz58zoWUbqttFvBPv0sxHn+xaMGIxDkN3h6v\n0tVi2U0Hib+dRlKC8hOxSjrMLQvRkym4r3rMyjyE68xChn3Haxn4GojYJTOR\njDKLudBnhpPp8oXMQBW5gg723ROKcKSDLqn4zLkMkt7XTNmHy5Ix6sBwLwGb\nEKWkIJrLHG3YSKgLraTShr4fUaqhj6T+xuOIW9VVuu1WwuhQt18gT1krWA/W\nDHQHsiExr/AYggOue8mZVpBZwGZMKMzBuxkccY9cadCcAWyZ0PkpT274XzmN\nuoef\r\n=Uioc\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d00daf7e17d982108e566091ff3c5d221dd83ce9","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.56","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.52","@balena/jellyfish-logger":"0.0.223","@balena/jellyfish-metrics":"0.1.44","@balena/jellyfish-environment":"^2.3.37"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.5.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.92","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.34_1612092400974_0.6608505104322917","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.35-renovate-non-major-c4ba32f5082db077b02ccbff9a21ef2a5b39a3a3":{"name":"@balena/jellyfish-core","version":"1.2.35-renovate-non-major-c4ba32f5082db077b02ccbff9a21ef2a5b39a3a3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.35-renovate-non-major-c4ba32f5082db077b02ccbff9a21ef2a5b39a3a3","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3b8adc830207ccd718e94396b95af575a50a1b80","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.35-renovate-non-major-c4ba32f5082db077b02ccbff9a21ef2a5b39a3a3.tgz","fileCount":96,"integrity":"sha512-9syci5OAEo0ALozPpZOWSDZ+ca/kP2HGdaPUrTieqk9afzbtGPcDTY/3UoQWsy6Cf2vtqjt6Dx/YtjkjyZdcVw==","signatures":[{"sig":"MEQCIC0yCLsHyFixK2ogkXxm5QVHrkziY2z7gFLWIDSLe+e+AiBqdWTGQPXJBEGvJfUS7W6Zj182ZVSOKkqrCaM5xYjnEQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":591675,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgFp4ZCRA9TVsSAnZWagAAmLEP/iHmdbIv8tSy5knfg+Om\nRJniipVKTRGWqrvj8BGpadT2lOyYiRd3/Bofo7QX/avfiTqQjRGQc4nu0moN\nvz2V6Xvlltvdu/MiHGPvBA+50VjC7iMWEU0zgoJrGcb0DXrfrMs9mjqwe+5q\nZiaYyN8Gz+MvasDK4HGmdb9+UzLGrn33UfLyam4IPEaNxrabIale5gq5pWHo\nbamjf9684wBu7GGxwlU97uJrqiETD9VwSektPw9tFYhpcHS3ak6fevmw96w+\nNfHmEWZxG3gqOfRjfhI6WSp+fBRlgrb8WC3DF+DCyi5NWECZbIa8A6WulrV3\ntU4Coj1vuHQmDjbwN0OB5S3UDsLMPhFhU99IkQ37Mz3yEAao7ntCmRbKCW0Z\n+oWbf5UNEEwonKDPm5p47R8BdfWCoH5FgT8hHU0xnZGHAO7iE3L7d6JRWnW5\nEyK9/zYGv6xNJ6Yx84nhQKjYjsBW4qzXjHOlmuYFoJ7P7qCyKhD/07bIHAiX\nZYX/x+cLJ/tYKLvRfB6HaotHHLEXA8rFxzHobzkbMiSjWWHMrP+OEWc8/5j5\n2W+48sUJ6N1G9DLoyYUYtUCjLAIG42Oce1hEK4jo7OT9p1Z36aYDKA2zfd1s\nTTGchsEd8mlLe3ajDCDbrntI2yXFQ+5KoxcH6I0hL1r0JN6Gmvf0bkBuoXGV\nbCQM\r\n=TReh\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c4ba32f5082db077b02ccbff9a21ef2a5b39a3a3","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.56","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.52","@balena/jellyfish-logger":"0.0.224","@balena/jellyfish-metrics":"0.1.45","@balena/jellyfish-environment":"^2.3.37"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.5.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.92","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.35-renovate-non-major-c4ba32f5082db077b02ccbff9a21ef2a5b39a3a3_1612095000929_0.9789154022462268","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.35":{"name":"@balena/jellyfish-core","version":"1.2.35","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.35","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"97a6f4fc6c2845954181780368a03af21264d687","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.35.tgz","fileCount":96,"integrity":"sha512-NOt23mms37S/KkOioPo7wm7n+ObLgwCGlHYB1I9toQkTp2wwVdawLmWxSee1skRTy3+/pLd8seESEbs0K62fFw==","signatures":[{"sig":"MEUCIQC9NNdaT62+4VlEPOt+4KrplTx3q+gi9xwN6d9OHlOH3AIgJ/MCwBPT6pjhT4AB3TDEnA1sXmCM/TY3hhjgRNu/9WQ=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":591615,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgFp8gCRA9TVsSAnZWagAA89oP/3Ysm5lkakXPKRNmBYqQ\ngbWIisBoUt4zmFEU0wiFbzY8d+ShBd1J8ymccWaWXFPLVAIarvYgDYlWWirR\nGfhsobYSiqEW0XFaRQG+ix6cDy/KHjmvnhklJ3UidfnUXe/9mrEGyncYCQ5l\nVXvQ89aeFSw8yrjgOQA95ftgXBioa60z+nGpAwyA5KKRUvBBqaSHbU6Nypk0\nl1ddkftgxYlfHl5K75dUW4yQtD4u21HBx3kT/v97gIMNbsBHLhsnq7fCjd0m\nPU1FhANfrVHEgV1mTgDmpiCjV3iJ/pvPpmg1QFa1QIayMHGx5v49NCc8Xrw2\njtgSGjXLU6C9y+GdCsJEi/d03TWIhJA60FlgkFolqx9+npD0RxHLDZxYhDQI\nwOt/9PopwwXmaPo84yyV5gJm54DCDOrJSE2+NwAQ6YOVHKRev3j92yu0VO/6\ny73BpwyJUwOVVCXqT6qrpdyiN/AfP1vGM1F3Px9ebhG6S8l+z58P9YVD9c2G\nLxiORGheB4VkH35LP1g0b7f/pv2WjsfwW8g9pZsHCxmMaW41+Shav90Vpf0e\nPMs9ybASlBcIfUs8GQX3SiJpxIV4nFKN/zV/y+n1uhczE19aoL88CZE3MpiF\n4+DTXB8QdSuKc40Tt36yVCcFyuSFxYX7rPBhWzoNM8q0lTBFqLY8yTy8tXdc\nFfSG\r\n=vKON\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"640be17137b47d32a87c86b392093ac3b0298831","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.56","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.52","@balena/jellyfish-logger":"0.0.224","@balena/jellyfish-metrics":"0.1.45","@balena/jellyfish-environment":"^2.3.37"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.5.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.92","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.35_1612095264059_0.9996391607679489","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.36-renovate-non-major-23b3cfd8e70db7ca08f879b2284fec04b3872144":{"name":"@balena/jellyfish-core","version":"1.2.36-renovate-non-major-23b3cfd8e70db7ca08f879b2284fec04b3872144","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.36-renovate-non-major-23b3cfd8e70db7ca08f879b2284fec04b3872144","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"47db90d6525509a9ae6a84e119fae0790ed42cf5","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.36-renovate-non-major-23b3cfd8e70db7ca08f879b2284fec04b3872144.tgz","fileCount":96,"integrity":"sha512-7nDPV82hr7G0lI0nbq4yzeHrScwN5iqDFO54MppVQSb9WjOH4UC2r0lKGkKWT7KRx2Rzaa1p0h6cz2o53aGmAQ==","signatures":[{"sig":"MEUCIAHkmTGF5DdzGm0RB+SRVRgsljCcAJCdvZhvhvzwYbVSAiEAj1UIyzYhc6Vq82iYT7RrBwHafPi2dZX/SV8J5+dDffA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":591741,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgFuaICRA9TVsSAnZWagAAkq4QAJgzG+ujIZvp/PSjyCmS\n74do7Drk+jPtIEyZgRkWlk5b1E4ysy1aFBVVK8t855afkUoiY59U2umLtSL1\nlbWzxe6lf7BZYqMHJ5Wg6XVANyRUADLtbxKhyPiUQ/4npdwey2497geenkbj\n4xxVYGCKHQCMk/mDr2EX1A+5XzJxpjppnuaquiuhDMOIfYzauc+MaHrbAUH6\nR4tZ2lJlBaEAz++yvGVOReXlQOHcOUspww4lkLQFApYLsAJf7I3jwdNGnX4u\nE5sP0htSXIr7AwxZ2czS9YqjYL2F6qald2N0lHPMW6iHhZlqlU7BdfKG1c/X\nfBYN8FSmgEyMbRXRgAiR11cZ1JKurHXvhpXSBuGwWgeNWjOOR9O62z93LOeI\nqALRuc/RkJCRQEJk6cz0JSx925KNulyMSJGy03UJJvN09p0sRcrj/8one2Db\n6F34C5m3ZPmXYX6acVFoHkpA75NhMUJoKzKglcOeeknB+3jCV2XFbE3INquF\nQ+v3muAOmlfhrWoMIOyQRQh8HZE3pRTc9fuFi3Mcq7ISHAOQYIy7uOh9Xw57\n1Hn5DvD3SG6vCSogaR3H3QkGr3hwFEm/dIyvrWyqtVd7EunGqpFNGfYR2YnX\n6/KPr1dEkL3El3851Il2mPGLto1o0SIDO+hRcvFLap6seoTDM8fkm5Fiwo12\nOCFL\r\n=wuID\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"23b3cfd8e70db7ca08f879b2284fec04b3872144","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.56","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.52","@balena/jellyfish-logger":"0.0.224","@balena/jellyfish-metrics":"0.1.45","@balena/jellyfish-environment":"^2.3.38"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.92","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.36-renovate-non-major-23b3cfd8e70db7ca08f879b2284fec04b3872144_1612113543950_0.5987671615720802","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.36-renovate-non-major-58eba00feb57306a7794e5522b8d1530063408f4":{"name":"@balena/jellyfish-core","version":"1.2.36-renovate-non-major-58eba00feb57306a7794e5522b8d1530063408f4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.36-renovate-non-major-58eba00feb57306a7794e5522b8d1530063408f4","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9b152a8b39182faa47b9772565155e3331c751ee","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.36-renovate-non-major-58eba00feb57306a7794e5522b8d1530063408f4.tgz","fileCount":96,"integrity":"sha512-rPltJ40rL6BdQtV/8S9qm2xkUZpddP6A3Oqn0Pi0OGcNZh1VLpbZPjRh8hG4eEEzEupYXGk4aHuwMzZTH9mmkA==","signatures":[{"sig":"MEYCIQCR/9/QLMhZyWNolTTwK6/DKTFGZj9mAkTgMYNwSZOQBwIhAM0MZ1nnxWydT8ixZE9cRdNaOEYBY9jL0IESGDzLs7mk","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":591741,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgFueFCRA9TVsSAnZWagAAmTMQAKHIADr0iN0ScGrSydI7\ncWLLURUbPAAsrGOdCYaYAEO67NbrcfJNnv+0N1oA2qt9Sx18s3qCIywKuZcF\nKE9s7M5006p4AVes1LEf3bxTaK6PP1u1OcsWVTFtWYphlPeHidTmI7ckPYa9\nXOCvFuMlnMuXK+hGA08yC8Fj3eNcos6pSyjWAu0z1fsTDvviNegvPlNaKiZw\nDrsjl1Smapdo4L9CMFUQ34UZZ8enSc++x6JQzcHdkun2QzpajVHRlbs6nw+f\nzbC3DharU4i51gDL+axSbi4nijRC5ksf55XBSzWpsnYSqZ4qynqMBWz4Pxtf\nOYY8W+KAYFQHeD3OJeWigqpnSIsQ9uWjkntqOa6Rgj1BpEkBfZpcYk8FOefd\nPutNJwKXKoOKLE7KVHNpx1Wmw1OmOfcYufy2VS/MI56H58Tlr3AzH9xn+8xv\nYcdqBIV56QcHeJDnSVwba+3U/Y4PFMZ+MB8aCr2ZRUTG7Dt1aQIfpqb5gQEq\nE1nr54RvSFaSA0A8hvvbNSNokNQuclZDl8744jRLkocYKU+m5OKQzKjLlMpU\n9vfI+cB1LcY3CgxkvOUzv1Hhwq9KDAAUm71+A6aQC2mDSZKcHeUC9HYLDSBu\nq1x7I2Zf/gGN2vHliMSYqQUzxyoahRDOw+PvvEpWqaU1B49/AdqIkvfY3Y2O\nf+kA\r\n=AAXm\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"58eba00feb57306a7794e5522b8d1530063408f4","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.56","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.52","@balena/jellyfish-logger":"0.0.224","@balena/jellyfish-metrics":"0.1.46","@balena/jellyfish-environment":"^2.3.38"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.93","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.36-renovate-non-major-58eba00feb57306a7794e5522b8d1530063408f4_1612113796745_0.8548445770561706","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.36-renovate-non-major-facdbb1ffc8f471ef15252449bf5c6d1c568c653":{"name":"@balena/jellyfish-core","version":"1.2.36-renovate-non-major-facdbb1ffc8f471ef15252449bf5c6d1c568c653","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.36-renovate-non-major-facdbb1ffc8f471ef15252449bf5c6d1c568c653","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e30198102705a5c578e386b6aae17088434a0831","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.36-renovate-non-major-facdbb1ffc8f471ef15252449bf5c6d1c568c653.tgz","fileCount":96,"integrity":"sha512-vkOnnp0u40iF06aKBtE+ue2HAxVKhLpXyB5RT7fmVrhVUORXS6vrXw8egAAuiMrxXc1unWWKR5kN5AfqubY+fA==","signatures":[{"sig":"MEQCIFLSEdzI0AiedQLiwarmUqDBOO6qkllFq+G5+8pbXXUjAiAI7tjeeWsyrqBx6RUMH+zNS9smESR8KQXJ/I43OTBrbQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":591741,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgFwWOCRA9TVsSAnZWagAA+4MP/RlQvIBlvD4F3eM/a6jC\npEbNPbaKa+uOdpWcOuKhN4EphErb6d6fviIjAvL8klZW20SneF+sDkB49Lw7\nzXOcSNgzW9dQBUvbfXsDfUTY3okfK+CLEQFuxp03T+M0ZVlvM9//u0T6jDAx\njcMb/1hIkv6KfZABPbCOVPM3138kOwLu1QMdCbUtA+64/HHWQgMjquM17anf\nA3jaqZJLYcVYm6iF9aAG6HyNuRLtXC+ReAM4Dmcpil3ExsVIACvkf9tam6yZ\nlpgF/YYdsPpTbbrF8o4rk0QN4HKEjH3QnoTb9/Hpp9ptd05+S0sJ193DSJo4\nGNm/2H/qkW+AuKew1Q+fT27du3/bwBX2mROEo+5KbfTk2zfvsx0idyex/dvO\naK9LAU45SsHZcvN1ZGUdrJrjvPUon2k5TR7V1daI08HZpBL+jjtcFr41UUry\nSlXDCQ7BU9aYPV29c/Yl4elksm8sStZakaPxiaN6djcwpanhwvFmLoavf7aA\noSRWeWYmtTHGfFC62AQzRabZxxj1kQ14ORxvWFWi1KiXJDgTIBbL1cz2UAmH\nityage6hQnIBa4MzKvqnTVuYZzR7C1HsObBc+jK/tNJPh74lo4nMZz0Tasxr\ndetIXqIVXiNcn0xHvyM19Bb29cIkhw9nX6D9+x3kEuiKnf5CoPmPYnteLccY\nKxYX\r\n=5mXz\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"facdbb1ffc8f471ef15252449bf5c6d1c568c653","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.56","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.52","@balena/jellyfish-logger":"0.0.225","@balena/jellyfish-metrics":"0.1.47","@balena/jellyfish-environment":"^2.3.38"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.93","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.36-renovate-non-major-facdbb1ffc8f471ef15252449bf5c6d1c568c653_1612121485649_0.8677409913944065","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.36":{"name":"@balena/jellyfish-core","version":"1.2.36","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.36","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"11e537f2ba20153d35c4b9695118894a0ab4ac40","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.36.tgz","fileCount":96,"integrity":"sha512-L1+h1Y9LBR47jKB6s2ti8zwhPUDi1CHdsWS1hZe688UfcoZha5T9oWEopzOcnuckfH8lfawPt0vINMCCai1i/g==","signatures":[{"sig":"MEUCIQDyxjg/ovIUYvyNiTTyijTOhhX5Uv8QkwJPIaCwT7jSVQIgA6haDrgV1Zv4A1p1swHTRNjpFpr/xGzoVCpDfNFoU5U=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":591681,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgFwbYCRA9TVsSAnZWagAAh4EP/03mF2sUoWPKIeii1Z1j\n4tQSA5O7gIQvGSxYyH6W7ALzg2buBPq3OZEs0hQA13UYNUPMGFaFyvq2sKPv\nEk4fU9QXpBiZ92et5QgMhmE9U+OkjdBOgkloCGTvQyqCYbYJbDRtGuSiniKz\nt6Hb5yfciswcLFqogj+Y45bvsibv8d0/3/9VAoqpdOumJi6foBqxKjogNCeq\nAzRREVsRjz8bwKL2/XzWi+I3akWv9yJiRBYfdVFOXNyPM39GL9wlCJAnIWJY\nIzbPvPQnt9ZS/7IfczFs7+dq1bdC67/3BymWPbr6YOjfZCGL74A2KDRR7ZU3\nIH1IVWu3iEggG/Y0Acp5qHFvCqlxFzYqiGOpbhZLpxNE732gjfLjBLigt50L\n3JTkqxEq0P2UXm85VvXw86hhJkQ/IOBrVl0DZY4PDbyk2P1XXcyiXc4ygnc3\n5qo4uiYOYHcm2H1a/m7hxo8SzSXpK6ksk/KGEigTAuV9aPXjwWh4GCyEYrJw\nO73HphiYd3ywza4fAqS5X8uXqXGwpcTOAOuYywjv/JA3Y2q2DzWX55fJQDU6\nMQePjiod0eBi/DfpoM9rBCJDl/iX62n36tqU7ZXXCgP7/bpTtDP8XDGqgyXe\nQGUgpdJUfYaBP5PHHDbTEE3KX0h8KSjPaynjDX1rB+j1uDtUHnsuhDDdm1Nl\n2xKv\r\n=c+OU\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"189228795e9f03709a70026d22f8b37892ab12fa","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.56","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.52","@balena/jellyfish-logger":"0.0.225","@balena/jellyfish-metrics":"0.1.47","@balena/jellyfish-environment":"^2.3.38"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.93","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.36_1612121815720_0.5136827952046572","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.0-5508-pre-build-versions-8d2e180f26673910012c3192b011e491cd0f141b":{"name":"@balena/jellyfish-core","version":"1.2.0-5508-pre-build-versions-8d2e180f26673910012c3192b011e491cd0f141b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.0-5508-pre-build-versions-8d2e180f26673910012c3192b011e491cd0f141b","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3f7deda3f09ac308bd3b9c1e1a193695234cd40f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.0-5508-pre-build-versions-8d2e180f26673910012c3192b011e491cd0f141b.tgz","fileCount":95,"integrity":"sha512-iGZUD9r/YT4hQq49CCWDv+Tz/kOZCpvCbQcryI4iFIVg7ZneGqquq8UF2M1fWiTftzqXhomhVg+oBUIrHGDiSg==","signatures":[{"sig":"MEYCIQDd29niq2nHLqAuedaxjuuTxEWMJRFk3gWmrvwdNeonGgIhAMA37Jzprk1htjPsaRZ6G7xstnKVL7+vuvbA3Dsn7o7U","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":593371,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgF8XQCRA9TVsSAnZWagAAqPoP/3c30v+5c+gzJhU6l256\nmNArT1n2u3niKDIF9m2MP6QXovVpnuZSGhUkKldH4w2t3C87csoJGjBYfXem\nqv0NMEe/YUi0pZjRTl/O040+6FWIfNXvyyACTi84Rb7ubRUgQ52aWLQdq3Mq\n9gtoE8AxsFKUhcxPfibUzP6WJaf5vMLjvyatAz3+Bk5hdPYNX8nL1d0rhUIR\nrgSrt4WpR0X8RcJbf6QRYISgC2LqCF5UP2PRmglflV2RBWQiPfq2BTbJFkFw\ne6pWxp7rd7xG5RWaLSGHU+GqUarTf5eLpVlYa0BqWjzQGalm/oUuNvhZtaRA\nTi4saQKn6wkojcHPJ2L/VYOZ5rnrIbygEIjj2ybD80z65oCRknd1KKkY0y6t\n84cJx4pS5ZhfAKS/c9Gn6Z4mpdMURyNk1qFVwNe3kspRY5YQc+jEtWpxyB9F\n8S3IN2wNcM/yCtfgLQW8dXQ88seRP1QQHrH0VhgcEQo+o7VDTOLS5lgF1zgt\n7ZdJ8Rk1K8Gn5N5iy+Vgx9HXHZ3vGuNx3jhRwk9Sm3RMfuo/ZeEel4C2T1bE\nttxJp8y57Ok81e2kPbQLvKe0UfLM9ullSjEdVgelZ6cs0v7MYavclBpDjMWd\nS3S1wWjpp/xdib2nb7O9AC4zNzLttcxLqCVkdyE8Ti+7KgHMCRKK8wLgxKq4\nT5vK\r\n=lVat\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8d2e180f26673910012c3192b011e491cd0f141b","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.207","@balena/jellyfish-metrics":"0.1.21","@balena/jellyfish-environment":"^2.3.25"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.80","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.0-5508-pre-build-versions-8d2e180f26673910012c3192b011e491cd0f141b_1612170703367_0.6119282116560227","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.3.0-Add-options-param-to-stream-logic-625e3cddd2132c42aef7208e8bb58a5ffd8cb896":{"name":"@balena/jellyfish-core","version":"1.3.0-Add-options-param-to-stream-logic-625e3cddd2132c42aef7208e8bb58a5ffd8cb896","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.3.0-Add-options-param-to-stream-logic-625e3cddd2132c42aef7208e8bb58a5ffd8cb896","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2a9ce7f18b657540b835b57fb3265e089849ff30","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.3.0-Add-options-param-to-stream-logic-625e3cddd2132c42aef7208e8bb58a5ffd8cb896.tgz","fileCount":96,"integrity":"sha512-l2JF8zTXWpPSt7oK9+Pvdjh+18kh/XIbb3cVl+ak4JtYHGE+DPr0IBB3RjEalSG8m1lnCHJAthEhPxlEhVyagw==","signatures":[{"sig":"MEUCIFpUCGFDYQc06bH8n4hKgtv6I3CkO+f0WyNQhnbomQ3VAiEA8j4hlkhvzzGHQUFiA1SxR8ATdzAzfZZea8dl3RpRPTE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":591990,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgF9/sCRA9TVsSAnZWagAApsMQAKPlgVprXhkqYE+kiv3F\nBHN2pzJe+qhbEooMdi52z3aK4G9nN/iylfwHJQ/kP/IZZD7kEXeKB9bTAEYS\ntnWcTXQoa+4SI7zz4AgbEl83CPq/SwjBGFDe5xRUIGnlddEad2e5A+pEXcLH\nfc+I9bvoCzaK4rOXH7hNxa+n5tvFmh8iEPiRgkTtQnvFCxJh2fqwYtTa2fBC\neJDzRJ1GlgQ7KWCyHSlQrB4M9dey/zjufFVtpDbVATPCnZygyLySpfJ09+HD\nGZPcRjI1pfogDIYOztPb3W/ni5UbQNZ62GIFvIk9rJe0QcMDg+LUOZUwvBdq\nHqA8UL1jy9NWs4RG01tgnIsdJRESRqlGBnwFH7pxoxFJm020uP//nbKG2lN9\nvwa7UI4aMz27zsa16OEeuKHsGMMjvat8GBnrPINC5x2z7YcJ/N7Feu5Y5mML\nafICbNKGBLTCYd/mbZPxT7TZTVsQf18O62jHCAPNlPpmrxAn3s/GZuVplSYB\noPR6LnwbPT46I89KvJXBkwO1yiWx4vfejawDh2V2Q6i7+Cn9BK/2gGm4vEcS\nWCZ+kCh/gSdrj7+Cmb1y6TRi+oZjQxrogUZzBSpcc3oS27Nj0nrofYYFdzVH\nldnoCUBSFE4zHRVvPaOiIg7MSEw77G5OAYDSraHy7ddXmqq/XRjpKCnZJui7\nJn9D\r\n=mnwH\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"625e3cddd2132c42aef7208e8bb58a5ffd8cb896","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.56","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.52","@balena/jellyfish-logger":"0.0.225","@balena/jellyfish-metrics":"0.1.47","@balena/jellyfish-environment":"^2.3.38"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.93","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.3.0-Add-options-param-to-stream-logic-625e3cddd2132c42aef7208e8bb58a5ffd8cb896_1612177387803_0.2851610277044405","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.37-renovate-non-major-b50d97e7fb96b44f7866b5a2271bc7ed733e031f":{"name":"@balena/jellyfish-core","version":"1.2.37-renovate-non-major-b50d97e7fb96b44f7866b5a2271bc7ed733e031f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.37-renovate-non-major-b50d97e7fb96b44f7866b5a2271bc7ed733e031f","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"dd929219d8c639c5a601c6c094fdf47ce3edc6ae","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.37-renovate-non-major-b50d97e7fb96b44f7866b5a2271bc7ed733e031f.tgz","fileCount":96,"integrity":"sha512-ljkE/APLcDorBQd/9665eevJd2CbqJKBFTRSRJ7+aPl0HY7B+VPIjRBwsu2dxKObna4fO/6YdkHI3LGoBAlBnA==","signatures":[{"sig":"MEYCIQCh0Zyq8hHxufcV3ni4qpxHrXnS8GznSlvGm9TpqVicuAIhAIcIfJdM20NEg7Okwj4gdYpqdZHkACFS9crtJWpKSo28","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":591826,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgGBQlCRA9TVsSAnZWagAARgIQAJA5K6ZkRHH6/I2DkCz1\nbl6OGrHdNULC1xFxqouv8w97vO9lOLgp85oAodrkFA55I0TZ2eFwWHYp2NYX\ntTROwFVJytPBjZZsMZ5woyJxKoISiYD4vq2oK6inI+SG9C3BBffv/J8B2qWR\nXX/ZaAI0ZQtlb4ELEi/8He3cKi8umwfcVDxMn+Jg8wKiDFbsJWlomIbeh6R5\nKXQr16sZczB6fC5qJVujspw8rxexMz34r6SuhL649E90pk57jepICAymgiEj\nBTFgRwRCNDWx89ae4MKL7DlFsuV5rmprIiXqnQXK/299F8YYxO9K6lCDtGs/\nmo5oLmGAUUL6pnDewQypALaQ5MmcY2xGVEzWDGO4fIh+sgLF1G48ri4EbfDW\nWhTRBgQMpuQRwUwQxnZ0xYXzqrml9aiwAgq4blBPlByXRXhtFe4eC3s6CD3q\nkKGQGVKpjxWLg6AyjbtTZRpPbVF9qKASPGihKuID+CEcZ5e4uXBfpj6iy8xD\nW1QfVw8UIU46gNdOiXMBRrFYj+sNYvNG5F0ILSheZUBI6p2NFkSGtL7H3DWD\n+Mqcf/9udQ+64WIjVDJsMJLJxco1ENk+AmEEViEkev5tHguIVbXmaGKHRxBo\nz5M+Mu63W2VHe6zZONKGWRnTyMCKyN5rHPv9uDucQw14McGHZEohVpglvSB4\nAP3l\r\n=2ETg\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b50d97e7fb96b44f7866b5a2271bc7ed733e031f","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.56","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.52","@balena/jellyfish-logger":"0.0.225","@balena/jellyfish-metrics":"0.1.47","@balena/jellyfish-environment":"^2.3.38"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.93","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.37-renovate-non-major-b50d97e7fb96b44f7866b5a2271bc7ed733e031f_1612190757254_0.63100665575199","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.37":{"name":"@balena/jellyfish-core","version":"1.2.37","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.37","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"01ef7dd8f16ecd831a317564366ac2e26cbd226c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.37.tgz","fileCount":96,"integrity":"sha512-s3IBY9zMuMXFWCAxNbcCK2e9oQ1+7ahUObz94ovb+vBV9+nlp4kX1uLulC1YowqzPOkGKusLCrrBlyEulhQbVA==","signatures":[{"sig":"MEUCIF3WkDZ+45DPEn0EY4lLcLtRITxzwKKX8q+7L12ykEVlAiEAhqpX+PtSC3/Jirp0eogBQ4T6BUGsUpVmQbrwD5AsMxM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":591766,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgGBVLCRA9TVsSAnZWagAAWeAP/3bpr3E3xKcMWpZlseZX\nkpTimvTS/IABRkCHSdfPHOPluuYogbrOAGMV+c3MCn8MzYasdQFgMuNCTY+I\nE1fqwfXrwI/diAmGWJ/a4/1MGrBrChjOUFgCAZhdoZDuE3GWxGaRdpNrDrCB\nl1oU8/WHI6NsBn4ZOlbCqpbhlHBVwnF61tIo+UgxO2i8GlwjIN8aOe+oZOaV\nDVoRZYsXHqFhtP4Dk0LXiFMlN6d79A9FKGLkkhRIEUh6RksOUst9JOPwiC6D\nCeVm73r9QNfVi0INmDCQGish1uknatPzwrxA6hgOppB57KQypBxAB0k159o9\nRXF9mYzc5y4WPKLi6pPC5i1TJ8w1Hf5DQLVdfnuYDqikIhUYqOW8Dhz+ltQE\nze8sy/IYa23q//TWGcWUzEb9jIxUTiV/GU23HICACUGUkjCf869dx20ePswU\nUXMOYI0NuebsXHLJR3Nai/ITlF9wHrv+QgQGX4+lNUmfUejkZLUMvYkktZ2v\nwZGXANzg5SS9L+SscFvxEa/XHrm/5GK/7h2Yo5OxCHEkCRA+1oxvKApg8Yf1\nI7T8MN6aeFPHmQmm2g3IdkqANkpDSNpY20k/TA/V3P3R0K0iMaPI6lgsFrCU\nDCH9oriOzx1czJ8o8ZiCGWxyvOe+gKaNSU1EyYLw7+Jg8ZjCfrK7OWJ4xoyt\nvh6Q\r\n=af61\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"251fcb43c54911a861d65c77dff944208efb9e3f","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.56","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.52","@balena/jellyfish-logger":"0.0.225","@balena/jellyfish-metrics":"0.1.47","@balena/jellyfish-environment":"^2.3.38"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.93","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.37_1612191051306_0.7696254037992492","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.38-renovate-non-major-8eb175129bb77d568f5f03b68d2ce08c0969ac15":{"name":"@balena/jellyfish-core","version":"1.2.38-renovate-non-major-8eb175129bb77d568f5f03b68d2ce08c0969ac15","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.38-renovate-non-major-8eb175129bb77d568f5f03b68d2ce08c0969ac15","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6eed45cea463ebd9e6e76699af34dd0e81c6edd5","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.38-renovate-non-major-8eb175129bb77d568f5f03b68d2ce08c0969ac15.tgz","fileCount":96,"integrity":"sha512-0qQkBMjFQ/NLqNRpScxcU/P1psXYIehBhdj2b//gPgaR1f1OyUQdrDOcFHk/cDkdlwKbi2q3GhkqC+J750IetQ==","signatures":[{"sig":"MEQCIFVB4ZdWZVcZLF3zyY6xY6nbQxTMD2mAqSFPBjBCtC4TAiB0NPHoidC/yRQ/xT3iVxgXbvgKo5eu10+naN3vhUhIWw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":591892,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgGCCuCRA9TVsSAnZWagAAqssP/i3yFMqOqB+DjE6eC/Q5\nGdOlV/soToTylFUunAe9vTlsnh6K05UZMxxt/LkcvgIZ2Gtz9IiZErzBj3Ji\nvvV5yr/3/GMJuSCGYqXzxtLsxXqrVaMlRcpi/9cBcwzyu9cy0lbC95RSYGsn\nr6U+6bRyaEwDePRwu9JanlI+Ti5jVzMopmyLJfliiMZg0PTbsd31x9giPcry\nbznppFOrEPojHMS9Ox5ZaYwrbd88quLDZcdMnsbJGNtMXkeZsu6dS5/VAM9Z\nWQh/FuNfI0PddF79HKKvJru4KXSGLKDyLiLlpnlnnsFfM+vupbS4wRtyFJeQ\nT+w/plTLO2tDo9N1yR6R74nzIpjUbBzEb2EV7fFRY3YiqH4sujs+JReGtVBM\njT+tYTvKL3QKU4C9E5DqauBVO0SR9ZcMKXvIVWyHmYtDESHtyfZumAPyQDh1\nzQWgU0qWWTThLslC3rZCM3uxTyub9Hx4QTxTTBAHewnUluV8sBC1XPRUvd3s\nscqhxMmbEepnrLWv/vQP4IWCt0W+E9aqXO+GbQg0gAldR+/myn6Qa+oWkvFz\nL3MH2RhImdJdByjFr2nsd7YmhxRsfvXp61FpKajW9xH3T3HFayjsHE1fgdEO\n3LcOdwBnFnFBE37b4+RGvbYzN6J4gJX1V5bZAJqkywAkz8H5EU8XyegKSFOH\ns3xR\r\n=CPxx\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8eb175129bb77d568f5f03b68d2ce08c0969ac15","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.56","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.52","@balena/jellyfish-logger":"0.0.226","@balena/jellyfish-metrics":"0.1.48","@balena/jellyfish-environment":"^2.3.39"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.94","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.38-renovate-non-major-8eb175129bb77d568f5f03b68d2ce08c0969ac15_1612193966173_0.33586663965466745","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.38-renovate-non-major-9df47a57f7b8ee740455b09a38aff0756a514da6":{"name":"@balena/jellyfish-core","version":"1.2.38-renovate-non-major-9df47a57f7b8ee740455b09a38aff0756a514da6","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.38-renovate-non-major-9df47a57f7b8ee740455b09a38aff0756a514da6","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3da700423de15b603f6f1c82766b504073a9b022","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.38-renovate-non-major-9df47a57f7b8ee740455b09a38aff0756a514da6.tgz","fileCount":96,"integrity":"sha512-jCdJCX3oSKPOYYAIesB94mVeHzi7rRulAHYR93uQw7LS6B1vmXibz4NFu1QwSDlcNb1mEqel40RhA11f/1LD8Q==","signatures":[{"sig":"MEUCIEBLoeLI+XqOZK0M4sk4FOGfG8CyUMo/TWW1Rs1IC1GVAiEA/Hw5zyrqDXD4Gsp0dCCqCzCGYWEh7pfCEzxxcjzevJM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":591892,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgGDKHCRA9TVsSAnZWagAAfHUP/Ag9MVwVGbeVpOdSLp5M\nkYGkn4x9vCUBUln2Uie94eSqOeJ6w7pjMCeSDMY2/vGVrxOXT27VAPiRWrrQ\nc5YUj2OqtEk4j2yRYpvml2dRJmfS50hd93ycQLAw9+8K0fUDNrqwXkwgqm+F\nHSZuTLRVmJPIA6m2Smh1rHf8hiQUnAHz9oEIqRoFxF/a55IwvYN4yN6mWN1O\n0M9SLFFhBN3F/4PcFPb7C0xcLfJKGrzdSHLYhmsLYqBNzAp9on1Nbk9C2Nq3\nzM4X5mwZH1yv99FXheTdbKqB+UUTD7Cs+kh8lP+qIZQDhCQczS2IJGHXZznM\nTeUlPv7bXTxHJK0IilG1ccysAlDvDkXcJMvRao74o+nwdMjBk7bLqEbKSaov\nHp3PJMz8dexJ0svxuylc2wxO9vDNwBQ939t9bir5WtQ7q5dc9+rjHH2kROz/\nWGou5iH74lNGG0sdfb2+MtwEB17Uh6joJXyxSaC6W7yCQ4zewK5RzGYJZvvW\n37XParLePUdjbUMbOdJcu0ERW+fM0DV8qrafPIIQUtsOeyhARTJUrfuwB3Oy\ntdn7ytYiJt3Gtx8HAVSyXDPWhlu+9fzqz41BIYwJ/InRUbstrPEAmCHySkeD\npaJDb7ktxy9Kb1rPh/ksTJwpvKXHv5rn6veKKUuEmppk2PSLoMv1Kxaf8nmW\n4WSu\r\n=uPOH\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"9df47a57f7b8ee740455b09a38aff0756a514da6","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.56","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.52","@balena/jellyfish-logger":"0.0.227","@balena/jellyfish-metrics":"0.1.49","@balena/jellyfish-environment":"^2.3.39"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.94","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.38-renovate-non-major-9df47a57f7b8ee740455b09a38aff0756a514da6_1612198534541_0.9501110229315664","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.38":{"name":"@balena/jellyfish-core","version":"1.2.38","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.38","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"177999869a4699e94bdab98b32df3a3813bfe7d9","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.38.tgz","fileCount":96,"integrity":"sha512-gmfdmOIHE1wuEdE5ksMOikMzYqThnY8SK65lB5iq+vP07QpPTR14T+Wj8Vh8OJH5kFwVE/IdvDA3JK7nd1+mcg==","signatures":[{"sig":"MEUCIHLM/mgDaf586RIFNgaBi7NXpLF4nFaV3kvfCAcVGu57AiEAo2+2fJoPL6DL650gk3xn50Wo+HHNCnfMcX8T6yXJOVM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":591832,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgGDOnCRA9TVsSAnZWagAA2gkP/3aPLw4nHZIKxZssWQBe\n4DW2byWtPkKUJsNuXy3kpv09PezXQcPmZnppSaQRBQCzQQ8I4hzYIQ2UdF8v\n7Rnd66gZybXen07V6Etl5a4A/8mXYYkpaWEECnAGeOQFA4XXGEuBJ+aIzEP0\nrd8mFOwXVUawW0ZIiB76O+BMe1mRVnXM0rbVN04eAFZ820/7VGtjOq9sLarK\n2XHzr+wvzqzaZnPaAxUJuxUt3VFrueV9pCrxO4ZrycLheWhspmb9C4tvPRKW\npqOJhxsIOVOMwRvpNVRYyyCSBRCfOtwiuW9Ixv08s4fXibqtpOg0hgOheVVg\n6yJHXVOH3MOA2MIpM7WPkbZKOqYZrvyTOIdobClPBhnwVstccs7lTrb+v/bd\nYc5d68fyA1/yy9mfvbAqb9jG2nYD/V9rY+7nqZxLf9mutGCQDKjcym4+XLKP\nAsq866P6bNFyfu6E1NZU/2DJsvGaFmVST9inhjwrbHYbS90wePRD/4roAFJu\nFtYToJeM+8ea05g3j2j7m2z9UC9H9LcQyM6sBo0dv5ymkzcPu3Eq/kLKqtEv\noQQe+xSJBhmeyH/UuY7Uga4iG1nD2/MdPZCLaMy1bolkZArgXbKDaL5x/y0K\nt4iVH3l2BCD9hFUnXoYzJrDLnKxQ4/60rmy5KjjtAJNkdydtnUqkwrsc3z4k\n/wZZ\r\n=ZNhC\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0af41c74de522287ed1773cdb619623488361d26","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.56","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.52","@balena/jellyfish-logger":"0.0.227","@balena/jellyfish-metrics":"0.1.49","@balena/jellyfish-environment":"^2.3.39"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.94","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.38_1612198823130_0.08988200842490635","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.39-renovate-non-major-1035a787133beb11e6c6603035cf066b1ebb159f":{"name":"@balena/jellyfish-core","version":"1.2.39-renovate-non-major-1035a787133beb11e6c6603035cf066b1ebb159f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.39-renovate-non-major-1035a787133beb11e6c6603035cf066b1ebb159f","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"49d2e9cd7fa88a4409aceebf8f4b824641632b4a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.39-renovate-non-major-1035a787133beb11e6c6603035cf066b1ebb159f.tgz","fileCount":96,"integrity":"sha512-k5N8sjFfsRyIOfGmP0ZeqExivScgTQUQbiVfk+/kMRQEKFnd4Pi2N2dpHJ+7Y4ylcXJdMK42LpoRGAyIeBx+4A==","signatures":[{"sig":"MEUCIECc+ZRoLOr4UV4MMZBg3+ZnwfroIM31emoDCvPl1ViiAiEAiNiXCDp0cX0CzeaMbc/31rYcFhhvXsiwHPZhnVd/lMM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":591996,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgGEMaCRA9TVsSAnZWagAAw/0P/jVclgyFs93HHwXbjh4R\nNehuyEhTLfjLC+h8HyWvlsfqT3CXpHCUEVy1k3gnojvCv8sEfT1ZkPER2vu6\nMsTbzpb8pcb3Guf7Yyx1V2V9+9Gwz+f5b7gzVfc7bG9sSBWYZyQkvkMJohjL\n4LROid760JIIVvWsPUqgFFzweIcUoLMt3y9lZjBgiGB0LeURrkfgYqCJH6M+\ncSuIl46p42cvHW1brq4K6UR4WUL5La0/mzXofaeU1Uyo7HxFo7SmkMmblzSs\nJRc0bO8+w3yqE1lXMSxPzaPc8Jr+6xLvFfSuGaONmhccJXifacFrinL3Fzie\nh7eG2R5OC49bHmSvNDUh6G4/AVEpfrSbcQzrKe4OUt4AKUMogHmxPAMqnc9j\nnaFhxwGVB+3IrLGJpVvfeh+fRrXgfklns2DVg3zE/CqaB/E6SH89VH0R1ize\neUdKfTGGJmlkaR1PhuFBB8PiUv/8jfkKbHdWQpj74bV0DHYIJQ1iIECTKMru\nHN8clEcKZK05Z8xey+GvV4PkqPdOq19L3MBZ2j78hCio2Od1ctBsnm+SngTt\n8O8sYgG8yhGkLEVv2TLrLp8HagG9LbWZSDSCzYFa/J9ZM6jcqDRP+TB7/q2z\nR65xcOy/QZOZgctqbDXqNjENPVUAh3goJjQtXBFatgLO57mXs3UMkRUZvPcl\n1+PQ\r\n=yu/2\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"1035a787133beb11e6c6603035cf066b1ebb159f","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.56","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.52","@balena/jellyfish-logger":"0.0.227","@balena/jellyfish-metrics":"0.1.50","@balena/jellyfish-environment":"^2.3.39"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.94","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.39-renovate-non-major-1035a787133beb11e6c6603035cf066b1ebb159f_1612202777825_0.7369039493394374","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.39":{"name":"@balena/jellyfish-core","version":"1.2.39","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.39","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5902e8ad1d19f73b50a137e0440c1d5df42dc2b9","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.39.tgz","fileCount":96,"integrity":"sha512-70Jh7/tkAbmOuVYQ+kOciBERRxhOflkn5mEzmFiGZ5Z1tKkkt1yaOBtvDBChcTnUgSfeRXK5p6zpBARvUGkuBA==","signatures":[{"sig":"MEUCIQDYkfYK+ybCs38xexIL9djQYsauCQnHX1Z7p5Le1CwLeAIgVIFVvt7b3qavct+4JwiV2VKRAW7vb/JYEfsgcN1Dz9I=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":591936,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgGEQ5CRA9TVsSAnZWagAA2ikP/0JqVWkJsuvhNeeaYF8e\nwBhbPvlwZlQJBR3HMmijxpU1A6c0d6cmiunezF1KmavnbhkegPX3jGOce+lr\nUWh4BNo2xzT22IJW59LM/uk3VsHOQ7fRB3eEoBleIqAqnCI8l9dG0tvT4zwF\n4+lnae+0Vvjpgv030nkT+k1+p91xAw3LUWJQCeU1NPTlYH0X/+V0g2WyB4qY\nV+FnIk9fpbKxjdMwXo0AwtXpQGQe2Pr5EiBJ6lAYByIKcpdULUu9+n788VBG\nN6D6e9UfdEhwcTc/2lAmgDE1Je7yokFD12zlvudHYjCV8MPfoYwooMMGntlz\nLuoOeYTCpre1vOla/tYy+ueEYvZih1zACuybFfM0Or1Z4VmTkwlFJOcYcw+1\n/jq7R4KtK2l+oJcfPSVX4OJcG5kBVm4VCX7DwQf0K7EnVy/ItoxRzFw+dZxu\nzUwSuHe9nZHsHDOOiscM/hr8950wA/xbif+4HRVyAeUdQRY2AOmkTej+MpLM\nxVBeQsYfYSbWXOVdAmQlwGZzNxo6WMc5RB28VqCIW9U13CARyfCDpo8VU8LF\nBiWnsLsx6G8A9O6bB6MZQSTYs/uvXgpWH60wuvpIGBvg0Idivzjl3pjAtST5\nX4c2V0m+LAv82XII1Vdw7UwvxxmVTXLQZGi8KfGWW8Ir6EowD8YXmqn2/qQK\nYjqo\r\n=wqyh\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"9872114cdcce069945fadbd38e2b30f4499b2963","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.56","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.52","@balena/jellyfish-logger":"0.0.227","@balena/jellyfish-metrics":"0.1.50","@balena/jellyfish-environment":"^2.3.39"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.94","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.39_1612203065227_0.6610672571104073","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.40-renovate-non-major-7643ebc8812d718ad604c49da746d744f21b6b0e":{"name":"@balena/jellyfish-core","version":"1.2.40-renovate-non-major-7643ebc8812d718ad604c49da746d744f21b6b0e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.40-renovate-non-major-7643ebc8812d718ad604c49da746d744f21b6b0e","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f27a78cc2a688ca1ca557f8e02ee876e3de7d2d5","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.40-renovate-non-major-7643ebc8812d718ad604c49da746d744f21b6b0e.tgz","fileCount":96,"integrity":"sha512-kbiqdxwVU6A8DNyy0LQzg4/fXOrglXVowPdnnh4Ct+Va5KeDgGaPxe/mUbESG0YWUI3Ky65RxxELrU/FE6YWgw==","signatures":[{"sig":"MEUCIQC4GcFPozElmJtIJ4Uc9d7nU8lr56TEFHjLLEq1gJE4LAIgMQ4FWqwA5WjxTtQaSrJep5OaBvYxru7A8RrdqvNSpzo=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":592100,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgGGYICRA9TVsSAnZWagAAp5oP/32C0Tda/+eszuUGKlnb\nFk0t63Fb9vUBapsnxTcXdA3tQvGarnu8BfKqM7jAkK5Klnawm0T16UITUGlh\n6XmUTYkFPCXzbwQSde+MlOuPlqm5n6j6AgZtXdmi3cOJis+solSpUaqsE+gP\nVdrM+87q1aG3SCRVf8kSGXDpSMm4xNXJ5v6PqJLolPoPPZsMM3w17/bV0kyF\nsegPfXmhprZQfZIZgQJsWx1jtYJx1L/EgDpZHbonbdJ2pSaJ8nLGr1DIRZsF\nPBeLyHv/AW9KevNPRDatqElADqR7nxEe4ph5MeDWAnA5TNH4gPFPJqoGu5Qk\nQCw5CJAhOntjehvqhftjI/OuAdhDSi/krnEPEpEScETpdUI1qBhg5HAQPzbx\n9I1eZndEDqyHw0ML3xzUf3ZhotSjU7ja/wlpV3ysZUzG9++K8FkEh2qg0pdD\nJPu5K/AMt4E+uYrQAfJebK8fsNNk92IVD1AX6AbB9Jo6bF8B62E+WBgeDgUQ\n6PVd1T3nLvhNLPiAnqKfxN6+oDpoFu/s/PZC22JliRglKpcwiOlFpslEh8Kb\nkELpwV5IJ5FI3W4r/XcXjNFAA3xizQK4wVT5MmWJk/dhs6DTvLGrhnSvZ031\noFWHz+YRo0TbhLxFd6iffafYRVytghkSoMe6TlPgjYjLZkT1lN2wY7WkWGqc\nd7fq\r\n=Kvb1\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7643ebc8812d718ad604c49da746d744f21b6b0e","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.56","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.52","@balena/jellyfish-logger":"0.0.228","@balena/jellyfish-metrics":"0.1.50","@balena/jellyfish-environment":"^2.3.39"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.94","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.40-renovate-non-major-7643ebc8812d718ad604c49da746d744f21b6b0e_1612211719769_0.05321654632639583","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.40":{"name":"@balena/jellyfish-core","version":"1.2.40","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.40","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d9b9ecb63fea00c9189f3e15d39fdd97516a1364","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.40.tgz","fileCount":96,"integrity":"sha512-xEhwKDgfl+LONoQ9khHjvGb8430h/oyQAgQ3YcsTbNLgmpNwaevBOv3bQulTmEI7rs6/oe6wIAHvXlZmVdFBCQ==","signatures":[{"sig":"MEUCIHWTnvqfvArs6WvqgkuS1xo8R1s9noMEImwQ+y9C2C1dAiEAmLvRTJA5VvtuXt2f2Dd03eOhvxXIzB8cGsfrAlFW/2M=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":592040,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgGGeoCRA9TVsSAnZWagAAIEgP/2IlaIEjtZMI3inxUFYp\nNceQqJ4ll88x18dOCFASWZIe6dDG4jHD7U0u3AyekLKlK3LbYHfs+3MErat+\ngjhfIBeKQCPRH+U+MRTMe9QVKM1R+hlejMpEqvAthNx+M+1VxIBrG0DKGnZ0\n+pwVHScze/uAGF6ms6eCNw1537a3eDahJYFYuBMu9YT8mJ7DxrlG6liV+Hzk\nLlEksJsDwn9/LzYCiBgs41U7K+Vf1tcpGrvXXYGmi4fM3qr1dblIr//tWm9B\n6oyIMfZFyvQo+L24Aep38SQGU9D+yjCu9IIxAY9akmkfbaYgPk1az2+sgTQp\nkNaXJkFPj0CKeYATwpwciE+QYj+8yyHAQHsPeLPm/AjdNtKbhnIjoOD+OBg0\nZcbgrjfS7DzGl+0Ujnrs11vDaOvfgbrXflaa/jvOdE0FePUaiTuVlPsYveT+\nMsHiwc15xqwwajSicKT/SeLxFzbgNL+L6hFZgqXEL5dGtD5DfT+yHKqhZj3T\nrvlD1xyUM2c3FEJQHcoUAYmnHsgbC8+0g1V1dkmNdl1/C0pfGTqGLAIYvnBJ\nKU7e/RomURhtArGrHsCl2LV/s1uPZTXwxBRTEhDOPwXZ4Qdh0XmA8DCBst7I\n9k5W55fUStBnfk1Dnv4ZrlQKt6pYl1wit3uMZweIhZM+I+3Z4XxcGo6x6OLI\nnIld\r\n=g+hq\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"cb59f47d44d63a270f328778e9e1337b1c6afd4c","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.56","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.52","@balena/jellyfish-logger":"0.0.228","@balena/jellyfish-metrics":"0.1.50","@balena/jellyfish-environment":"^2.3.39"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.94","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.40_1612212135842_0.7724446352119851","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.41-renovate-non-major-f73b8cdbe585895ff42f5edd7fcd08d33e9d895e":{"name":"@balena/jellyfish-core","version":"1.2.41-renovate-non-major-f73b8cdbe585895ff42f5edd7fcd08d33e9d895e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.41-renovate-non-major-f73b8cdbe585895ff42f5edd7fcd08d33e9d895e","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2b9fd6941481e9ef7072ed192f81c94ddbe7336b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.41-renovate-non-major-f73b8cdbe585895ff42f5edd7fcd08d33e9d895e.tgz","fileCount":96,"integrity":"sha512-9xFdqyF1LE7fbIQQCjXJ/bEHEsCkFGizT4TWhJaMTwLY6XI5pEfnGoVdPpLvjpsx0egQz//OtmXI+UW53Ll3OQ==","signatures":[{"sig":"MEYCIQCg8fhoQ/0oR5LqMqBJnGOse/l1Asfk+OFYWEG/SWyYIQIhANKVSZACaWcH38uuYVoQ1kZE7+aUNbjvVW+420GRRNrG","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":592204,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgGG69CRA9TVsSAnZWagAAuFsP/0H3wTF6RBvvJf+hXP6b\nyChRLtHMYsjIWG6kJCUAt1fpYDwSaguqGASCKsdnqk+W3+NVw8n2YeNhFqyh\n0cvCkabRfPxlwRxQDJ3QdUT/EsUb62MvESKAXmqeZXnVFs6INRofOwQMBAgq\npL0FWhqUpByPzd+/HXdDcO23zPKXvo8e5WPqmE7YzQCj9D5DK640HfuCuJtL\nb2COTsuBpNeEiBwMdp+b1cmHq/4j7LMh5wf8rT57ULAp+EFBXyv9TWwdP8vH\nC3MaWyFUTFooK4ZUySNQS9CCc+7wBBBO6Sxa4IpAzqgUljV0y3JIp8AxmEOs\nBm3982j/rwwKUZxEItdpAU8lhnjNCkVc/y5auj77SmtwJhar4z9CWXHZGGVo\nlJN377ojRJG4kwo7cmtX8Fo0fhYJ+skihiKLm1VYMaEvACm8jeNpaYNcHlWc\nJv0Gng31z2uIC2142n6FjAxX+ZRCVQsUYGMOFx1BThmamSCe7/J2b602nRwf\nhXxlCbgR13OYYdofaP3Hpi4dIpQiaT1VjH6Uri2cNauL4Y2Hw6QCQZ5XFLge\nL26o2ipnK8FH7mhM6pJxvqgJQU5bBhu4YYnn1R1KBUb71k95e3H92k7Hzbib\nGnGiEfkYKKSztClsrCHXiL97UdqD46jYuHW0Qm8t3GduVGWql6VHv1XleDBz\nH6yi\r\n=SkpA\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f73b8cdbe585895ff42f5edd7fcd08d33e9d895e","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.56","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.52","@balena/jellyfish-logger":"0.0.228","@balena/jellyfish-metrics":"0.1.51","@balena/jellyfish-environment":"^2.3.39"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.94","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.41-renovate-non-major-f73b8cdbe585895ff42f5edd7fcd08d33e9d895e_1612213949083_0.12918386580913555","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.41":{"name":"@balena/jellyfish-core","version":"1.2.41","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.41","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c41734d5faa535270c04e51a7a8662fe77bc47fe","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.41.tgz","fileCount":96,"integrity":"sha512-kBarPZhSm1S1ph/ZK/+jOjwuffablLiGfs2zhWIs3w/QwmwCm1T00oHBerkDY99AoL+DiKGjKDxuRLGdacBxHA==","signatures":[{"sig":"MEUCIHhxNI9Edc0hKfnsf4VUMxrA8K0yuSA2IIPgOTq3MoBVAiEA/DuW6Njcgb10cGZkxQVM4ckgyUTCujkyZyHn63ZR4rY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":592144,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgGG/PCRA9TVsSAnZWagAA84IP/AzU+j9MnRjIU+4WCa5U\nu1ZUKsYw1g9xSpEd9+CBmQME7K7/6WrqXEl3DwesVtCXa+SaHVQ0XnFTRnet\n9S6hOO+kYMVdLpz7wz+TknFYvqOg75K9LYpseym2fh4mXN6J17PBe8ZCNdQf\nzKYRxvSjjtDBhwCzm58+nMwcHroKXsrAV0Fe7W4u3tdQUhhgqPkiErHMMSJV\nCOx271mFUM4C7CKDvrdaf9WDY9x41CV1Cy9dZ9nSzhzz1TBdjEX2ohOFzkLZ\niK1aHNG0eO5uxEYFIl8tNczoWSvDw4FEDA3j0f218QSb9Y3zjjN1kSu3xomv\n5bTUefVG2rWpPIrUTjGgRk5Vvb3V9rBKlBE0Dib6FBwSRP1IRTC6G410utxS\nTHnC4OJJbHSUnGN5ogdF8PmJM3AZ/wiHCg975uF8X3u4ArvRVAXqplAOJhN5\nJgEsiXXdGTaF/aki2SloColxwvQQiqVbttgWHlDHTSS5zJs8OFQJBV5OhkIj\nSTnw0oRVQmyIaIoLz9ZAeKuDUmjNHlUGN9T8h/gNz88kCRrE5ObNc/MzmpFz\nRkPnMN8DTc4zdYsU10fUzG8TNFBgmg6RLoCAFcrP+tVDlzuAYlyQg1Cr8jS8\nFK2EM7+mzYzTU5magXcklG4h3Z3g7CE1vGo8ZbEGhy6DadkUakokixUC53Jr\nhvsA\r\n=LRUd\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6cb4d7487a456b6f3de3cb63ca3575c657aa7bb2","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.56","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.52","@balena/jellyfish-logger":"0.0.228","@balena/jellyfish-metrics":"0.1.51","@balena/jellyfish-environment":"^2.3.39"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.94","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.41_1612214223350_0.26698589415969365","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.42-renovate-non-major-b4f7d8e727fc315f16dcce2ac1bfd6cea18b770d":{"name":"@balena/jellyfish-core","version":"1.2.42-renovate-non-major-b4f7d8e727fc315f16dcce2ac1bfd6cea18b770d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.42-renovate-non-major-b4f7d8e727fc315f16dcce2ac1bfd6cea18b770d","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5a88ea1d7b1af928a1bc7af40009aeb914af2a67","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.42-renovate-non-major-b4f7d8e727fc315f16dcce2ac1bfd6cea18b770d.tgz","fileCount":96,"integrity":"sha512-WeUiU2h5qtVdMMpTLZs5mPAj6glqwcQkPZ1nJoJ8/LeGqO4s8mXw7oRlLRdQUzlNXX4GCto+4SfbWq3+U3LNJA==","signatures":[{"sig":"MEUCIBNdwSkFJRWW/o3jE2qKI9KnZE0/eji3rAJO8ovhBzypAiEAmn1AYQdMIeCJN90tRgBO4BDMYI6H/joA6Xs88lurp2A=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":592269,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgGKmRCRA9TVsSAnZWagAAKhsP/RZUWosbjd6fB+kb1aju\nBgeEQg+JOmTsqFA9QCEi1v4CqRhGmGKqM5rrbLR5QXyfbdzH9LXdhjepiLnv\nHgGTL4/zmaiXaASC2gwR0WjPRNqPH+gZhJFgoa4J08ktSiAS1IPWS6ITBuMF\nGRWhZhi3IASWP8LGJi5dM+cxpnjZ98JthbCF9L1rs1RJrAx6JIXuE8asLGEh\nx4+TyDPuqtcuotCkZFX/7H0RzRmCPXBkVXmGbsxpQjQendiDc8gSX0SfaZoG\nJAb7UUY/kPzpbiy2bG0aMtvzG6it2bKkf57xg8ZyjNRYYj4zdwEoIZIap5H7\n9yA/C9PtirKipUj/U+/P1U6Crt2DvvG30COdSQme0dkKs8YAjZrnG9Iy7XSI\nUfPdqvXL72A7ev2pjlLRa3fuCDFj2muGJSvkENI6GVohT/wdh57Fo54moezw\nS4nGp9pzv1B9NmzJxibXOTn1m7rbpw2ofgpv9jkLfhOheIjNeXAiKJMHV6W5\ncfchSxmvtm43McFAoMSTCentbttwsVsHjHsjrzEcaP7Dk+o8Vnl6dBLZPZS6\ncQDe9qJOBKvOP7xOcsiTyi6skYK0Y0EI1tfEd0vYZj34Z2WIQ+4/ggJiaRxG\nA6Y+P6SPFgeeZgPFa3t8CGTh/eCmU0R1bDjjekGzPWydcFZ0VfioJIJlIPcj\nHy0n\r\n=gu+4\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b4f7d8e727fc315f16dcce2ac1bfd6cea18b770d","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.57","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.53","@balena/jellyfish-logger":"0.0.229","@balena/jellyfish-metrics":"0.1.52","@balena/jellyfish-environment":"^2.4.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.94","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.42-renovate-non-major-b4f7d8e727fc315f16dcce2ac1bfd6cea18b770d_1612229009307_0.6740424366390583","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.42-renovate-non-major-36f98f20b678f1df4f6083d1e2a2b9ff303c62cd":{"name":"@balena/jellyfish-core","version":"1.2.42-renovate-non-major-36f98f20b678f1df4f6083d1e2a2b9ff303c62cd","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.42-renovate-non-major-36f98f20b678f1df4f6083d1e2a2b9ff303c62cd","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0b131d576f9a75bc005fb0586189e43c933793f9","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.42-renovate-non-major-36f98f20b678f1df4f6083d1e2a2b9ff303c62cd.tgz","fileCount":96,"integrity":"sha512-IXTIzsnRGtbE5MYuKzXxDi/K7Otd6L2L0igH4bKM4qIQ5kWI+hvh7369yUxbFgNpIcni5RV8BtKFgkjRQHd5Ow==","signatures":[{"sig":"MEQCIBYFiy/EEaXqIyzBxkjYNUvQlFURlhaWJiOrGb3EV2HsAiAXfxRmHy7+rUpviwXvOdIFr/CjKCLb7euq9hIPyC27ow==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":592269,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgGKrcCRA9TVsSAnZWagAAw3IP/iQQ8Ek7W1aEYXjznfo4\nwHon2LROhzPxTdPCnvWUyN9vr7XjQleuBxqc/c15dk/lDs40aTWiaprgIKx+\n2joKgJmSqXwFLFraHzAqJvSppG2luW0ldFC5dTyKx7zHB4+nb/ye89YvjeUS\n0DRdOauphof7NBmEj0QYHuSkexOGgr9xiwRDFCb0ZVBEuEukD6lln5ID40I1\nTi0HriqPxCwczH5uP4vH8oMQabI2J3+eMQwMLJd6SU533Ego29nxuq73x79w\nt/klfBmGqzTgauYbuFZDtUNZJCBNgOQo1dYom2x2vDdnjOlJZBk9DbyDzdmF\nSJgs5hu3ztYhQM7PlOIZQ8eGswZmy04hcApXW1Wigp7VXXXXWyJjN0RxOFgb\nhSE4zMth2hk5gj56PjsWMZDEpwa2AJ8ozAy5+uAGhf5n8XRCAvEnhTm4JY0E\n6i3WnISWUCZYX/RPSr+2R378F4m0OeNDjk7vyhthL5KUNuvy/cdsB12plORB\nGNFA+p2RKYgMM5Hytucv8HULg/eHo/24gNZnT1pmxdavzMm8yh1HDPzUUIZU\nP5VAtI3RJmDqUBj9FA7hRUAPEmbjJpTmTFwMrZ9qVaATG3XIWdC1iwLwR75I\niMd71dcYJNV40nXOKYE6mfyF+ILuusvT/0/JsNFB48+B0eQ9i+QlucThiXKd\nS8v9\r\n=LXwh\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"36f98f20b678f1df4f6083d1e2a2b9ff303c62cd","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.57","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.53","@balena/jellyfish-logger":"0.0.230","@balena/jellyfish-metrics":"0.1.52","@balena/jellyfish-environment":"^2.4.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.94","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.42-renovate-non-major-36f98f20b678f1df4f6083d1e2a2b9ff303c62cd_1612229339747_0.30398176196867377","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.42-renovate-non-major-898d627a2411e454a585aefad2f9a76a5ded98c4":{"name":"@balena/jellyfish-core","version":"1.2.42-renovate-non-major-898d627a2411e454a585aefad2f9a76a5ded98c4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.42-renovate-non-major-898d627a2411e454a585aefad2f9a76a5ded98c4","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"fe4936b512df86d8440c947cd388f603c55b7ac1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.42-renovate-non-major-898d627a2411e454a585aefad2f9a76a5ded98c4.tgz","fileCount":96,"integrity":"sha512-u5/ao2fnx+ILr3OOxsNvhkc5la/JSVrmXDqW5k1cn5OnRkGFkKEv91rWg7YA1NJISjcH7czmfXHXdelzhbb1ag==","signatures":[{"sig":"MEQCIGBO+B5Gb/nJYqzlIVJYiyZfgNmYx0iTRlsg9mjKTO37AiAcRthuv2KsEoWMDVie2JUuwvQpIIFYQ5pYWw2UR5Znnw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":592269,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgGKwRCRA9TVsSAnZWagAAypwP/0m7i0Efig4Kbfb1DV4J\npOZp2Pcm4T+lb84WEuZQpPJzgZAY2oORn1Mc5kGEX+WkaOJQBpPIaS4V8FH0\nJbBz1dWHXYYEdZO+CL9EruVyrrjA/6QNTCiLCYV5yAmUCJyg2KjKfTxwriQs\noaMdIY5dsGuCSTfLHDzX71a6nbjsMKsrg/ctAYG56s2/4Od74ifecz1z80io\n/bj/JTmSucngZk354FHNOKkibxwDShb4jSnpbFSrns9XcedrTZcU8sL/LhLR\n1XJ12rgJ19nP6PdXCnw7moDSLB6ANgXwofQz8/05JzlvcS6x0YyHAHBjSCFP\nf/nGJhX7J4ibPXqoJyUzhiIsCqbNsZl3MOIC+gxyVnTffpUIe+QKglef0opS\nwjdAahUzCMKw3Sy/Z+QGymVKPjAjjzV7alMa1v3Jmmxi46L5J7WKGfpAlyDF\nBBASDs/5V78UtS9DscPqUZq1pqMyn2Xe+je17g7rMyf79pu1Bd4EF8g1l6dj\n4cQFiOmq9A3tiEqH5ju2+u/1ds2324Kud393XpJNf2VUaCIMlD5HETAkVKlo\n24sVmM7U5xAWBVTE1UfSQrR8/EHVdzaHqoE0KuPiwZ1cA4s7SfXNexw8djTt\naljcHblwva6QC/8tcfWXJD/LrjSQQjBD2rS4emi9/8M2CvTZ7S4A+atVSqXG\nvhze\r\n=RHRJ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"898d627a2411e454a585aefad2f9a76a5ded98c4","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.57","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.53","@balena/jellyfish-logger":"0.0.230","@balena/jellyfish-metrics":"0.1.52","@balena/jellyfish-environment":"^2.4.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.94","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.42-renovate-non-major-898d627a2411e454a585aefad2f9a76a5ded98c4_1612229649427_0.5112036532385063","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.42-renovate-non-major-edfb427780d41c28731a9e303aeeb9aeea3dc131":{"name":"@balena/jellyfish-core","version":"1.2.42-renovate-non-major-edfb427780d41c28731a9e303aeeb9aeea3dc131","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.42-renovate-non-major-edfb427780d41c28731a9e303aeeb9aeea3dc131","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"15757dd33602d0ae9cefaeb7323cb7c4a3f3bea4","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.42-renovate-non-major-edfb427780d41c28731a9e303aeeb9aeea3dc131.tgz","fileCount":96,"integrity":"sha512-grRtPLoPsZMAYWUBEqIxNwmNSE8P9v8rH05Me5sQ5SsBomsB9rGG8lfk8Q4ZxMF5BDrqTfUu5w3t6mhzuhhzVw==","signatures":[{"sig":"MEYCIQCTSTqSR574zZckcbyiNEiXJDLDIY/9f0OnqCtbNTij/AIhAL3vYZxhZraMmEalSFVVMH04THMePQRVJ5lZRUiAYFiF","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":592269,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgGK0yCRA9TVsSAnZWagAAeeUP/2suz7zKVpFsNof9ZblN\nVMV/UNB4+n/T2whACm3JbNkqbhDDliCadGYgfZkVzRxiWh/60jMoJRS3I36w\npHT4V/tfO0Pzxz3K1bi5sWXaWbG8TJGAC/bFRwqIzDuzp+qYo0eA2r6peG7I\n1ZirxuALPogB/CUXqryvGnFVNCcAG8I3rBEmOu9eV4Su6QjYka5RJUklCVAy\nzOx9f9Q9KR3gBvOc1yl5lTYK3K5JfnmhhZmls/E3hO6L+DkSPh9NW1JD3QyS\nar5h4iNnugapdqH7NyIv10tJtY4ZszVSqzhbTM1K/KuTVZzDPFReAFzeDMyd\nMDhkyzLYnn+S5+0CJwXchzWr72gahs4EbR1BhHBuqQGyD9fFkXbhf0/iYjob\nrp2XBQ73pVCNtXF9VuhUpCHDHE/710JaZhHMtkCAOlcdc+Sg7pPcNtZK1b1Z\nkqN/pEFz6pjSmMUwR/Fn+I0dAUXofiMJq1AQNBlnmBhIPFGjnn2H3X6I+Lmb\nR5+UEcbS5X3UxDIQ0qm4V3/rDAf53t0r/85iKS337zoJw5gSaIN81X6Viss8\nhaYHDUPkiQCwlAJs+LaNx7/A3cCrdTYQ40uKkXsEf8SrsKCKtQSXeMrgFYnN\n4QWd9ywKpiIb7VjqNIKM0Y1m533W6zAblrCaae5y9goZpxxJs9JeI/iaWmvf\n2GYb\r\n=2WTt\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"edfb427780d41c28731a9e303aeeb9aeea3dc131","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.57","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.53","@balena/jellyfish-logger":"0.0.230","@balena/jellyfish-metrics":"0.1.53","@balena/jellyfish-environment":"^2.4.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.94","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.42-renovate-non-major-edfb427780d41c28731a9e303aeeb9aeea3dc131_1612229938340_0.20823010654820284","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.42-renovate-non-major-9aa4315f363f75006b107d1967e3d613905eb88f":{"name":"@balena/jellyfish-core","version":"1.2.42-renovate-non-major-9aa4315f363f75006b107d1967e3d613905eb88f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.42-renovate-non-major-9aa4315f363f75006b107d1967e3d613905eb88f","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c1627400ad463595b4eb7f6becf182ffa47ce7f9","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.42-renovate-non-major-9aa4315f363f75006b107d1967e3d613905eb88f.tgz","fileCount":96,"integrity":"sha512-nE06Liy0euV6DQMvNLMZ3W3tKQaavq8qg48UXje6ph7K4rj12ooaBH4kOMxohTbXO7uriZCE31czLckXxBUeCw==","signatures":[{"sig":"MEUCICPbGj9ZCXCkzoJn0vZISiomyvCFjctiCH+QT2cKq2MBAiEAx8wEknMeDZpW7hksmLgWaC1VtiDB9J5w2i7sm0ekKE0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":592269,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgGLqQCRA9TVsSAnZWagAAxgEP/198vhGX9g1OchaVkcC5\nMWGePCcpspNj9YSCAKFglMw4uB047nRURlrWx3b+KFOk7p32uh8+jy+FMMs0\nDBPjwIVXbdtipk07Ajsm6c0aTqjt43dYq5Ql5OYBwCZruq3MS+JZ/NwNOy3X\nd/rChleKwvCjfrQMpgkw9NxpG8xgV65PkUbSNXbTwImJ9cX8+FLaKqcbWcBp\nJm9SiK1d7b1E7lQd9ij6v699+Ym2gBj8A+MOhSB4vdOCcLZ2CXcbXKTakILI\nZAkAQKK6mOykfRA2xGCU3Jw6x24/GEo35yB7tgZhVJIWEB5yjIXff6S8NqLs\nitHajKVSzOjvZSTA1hxWCq5+PctjxmOw0oxcT/WrO+fJeWKqGX4udjlwEjza\nEOS9pwe7zZlOiz1CiMOSbQ38dxOoxfmzn1Za0Tpe2+8FWPurVzWC96kyUI5f\ntTHGIhybk8I071SqwHn78415o2Fmtnf5RpRu5XI5tHni8ldCOpsFLjnnWUs7\n77pwzogKZWoiJBeFJHGQbowQMQtKyKf2qMaeEVpW1itLYZGMOtFUdLFifq7u\naT7QJgPkLyYFqWHOFZPpVPcvFqhbrznrq+BPmhEexKc33Szphbi7EK2AE4an\n6YIDgkRJmFoZzy4QJbLtrwcUwSoueT+LFcCE7bTFI+YhJe4MNU6PHK4DC40e\nUWHH\r\n=Ceb6\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"9aa4315f363f75006b107d1967e3d613905eb88f","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.57","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.53","@balena/jellyfish-logger":"0.0.231","@balena/jellyfish-metrics":"0.1.54","@balena/jellyfish-environment":"^2.4.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.94","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.42-renovate-non-major-9aa4315f363f75006b107d1967e3d613905eb88f_1612233360333_0.08194272181784301","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.42":{"name":"@balena/jellyfish-core","version":"1.2.42","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.42","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c595b102d801f7b16f6e734b18a5508bad5f3e2b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.42.tgz","fileCount":96,"integrity":"sha512-SgCmLJ1vIkjYNYTp+hvL6qTHOnzwlXhBUtwcXtqnS7tlQdffF6E4i6Lemffarhzo/VTaaSBIVYGH9oo67WLarQ==","signatures":[{"sig":"MEQCIEnwhNRFmXmlR39bsv/5J2g0Y4GU138wyPOJSdR0V66DAiAQRYSEDb754j4RpYgXEoEXAeVzp+8SjFEk6XE/JzQ35A==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":592209,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgGLv8CRA9TVsSAnZWagAAKCsP/2KiYnukkA2U89yO5i8U\nZONT0lFr7DnNL6SkEvn4flqHIujklmACTVydqwNFjW8DojSeIrCzEEqATKUt\nGznYPjpN1TO9PhS1IAm8S3ULwcP7dtwktFL5s4qWMEEO/Iq4VI7JDFRgH+ty\nVrqstNNVjeaB4QGHPl7bCOJwzzbDdnJdq3NpZP0rgUSENSJF9QBxlY4Ok7bT\n+Qgrkw3Y3C3XicOiLalWRIc4ZcrXvmrg35sEvCeQGCEEy9Z67P2JTONkyiP9\nqXBETL6XpqE1+dh8m80z5YoIsON4K5GQqhyPAO13Llc46WIjb2Pj+wyB71MA\nnRrD6TH3f9KGd+Wu0XWc/+75IwZU9RRymQnYIceeauO8VmWD+dvtLhM4s3o+\nPWdMAlvabfAkfZOL4CsYZh04evHOk+Nt6q/vCYxsfuShIOli1Am558kAAAIm\n4F612b4IlSmXyEbpD12ukAwdv2RZwAFVFLuMLQLMi9UBX8Xpe+3ilWJkfm1x\nn1KwP1vCMIjBMwFJPSryZSAVNbhOf2yWLepD1oXeY0ZawDEUCBjOA3wP9QA9\nTBuTWV0Wh4mC/aYblUaXNcs9jKkY1SQ2ykRqUzy8Dqe2Y8y57MW/jjelegHR\nCMja/DQG95OG5cMv1nxcr2ObB8o+OnVPghiTaLbloBMXzZzTRxQNl5eXHMpx\nlq9Q\r\n=BSgS\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"392df35248356d430ee16638f92de39cd668c81d","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.57","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.53","@balena/jellyfish-logger":"0.0.231","@balena/jellyfish-metrics":"0.1.54","@balena/jellyfish-environment":"^2.4.1"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.94","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.42_1612233724461_0.25981865488086386","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.43-renovate-non-major-77c6376cb087e709e7595a980ceb2e0b2c664eb9":{"name":"@balena/jellyfish-core","version":"1.2.43-renovate-non-major-77c6376cb087e709e7595a980ceb2e0b2c664eb9","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.43-renovate-non-major-77c6376cb087e709e7595a980ceb2e0b2c664eb9","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"404c4f2142187476e5eef13e4d83e6f45f2c3968","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.43-renovate-non-major-77c6376cb087e709e7595a980ceb2e0b2c664eb9.tgz","fileCount":96,"integrity":"sha512-OegOMR8afR02bVDfG48GM6td5Y+V379mwJtXVrfH1hmEtH0ErdxfMCThDMck+nKtPOZkhUeslcBR+1UJ1slDdw==","signatures":[{"sig":"MEYCIQC46X4XQistGv6FMdLPkOm9x+rEShf3BnwwuuIYwkGt2gIhAP4tBme3Vrf+p6XcD47PYF8UgsW6mmiNDwFTZ+FWbp2w","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":592373,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgGMs1CRA9TVsSAnZWagAAGvcP/jmZ/YKs+DjcERXSjBQJ\nof3abMu9XbXpjz+b2RMBdnxQv65LUvaEKpwoVQcHSWSUp9y9zV6zxL6jVRtI\nNTRgMLHx1z9QEvws0fAZ9tP389R7u+7Y5BpnIzjLMRh3htlZelj5WbGNN30S\nR8szXlZiZlvACZvNH4NCpH+gz1pRzPTWdmK2ZyFjOs/sZTIzxNJfGFmPer4h\nZYuyeDO+g8M8A5QYsgMUzbCw6bSTO+CY56W7vvf96fZOFpj0f8kNo96r9JBJ\ns3PKxnyB4ZueWnUXXfsHjjjcPkHv7AxOksJfBAQo9WVwdLvDnggtQr6LQRBv\nelRJDldahDYjA+NpduDpW0B7Fazb4l51bzcb7Tip7DYsdNEGkryg+Bhh3Yju\nK5GA+NkGJTWRtUWBFpfJ4oU8X2WgZpo170dvgvrzuUT8sHllcY3/PHUmP2nP\nrE6TfXK94C9o+9ee1rlppt2nje9STksxPYsuCzjbTDR3KHmaBRo3DDyWWe4q\n4NVKbpj2VypOaYmiXS5mAmOism5JOFDrQVg3qAmbwIYyD5/JuzdzrBxf2+HI\nrTNBtDtKF0q8HO+4n2IOQIgElr9/RTR3MUzL5URwseEI/v7+y3x2VGoljcnO\nGwlowpcqOo36XZNZUplVP5cdjtwTQPuA3oPg4PfZH0S1itc4tMT9H4ALkb4c\nRJhH\r\n=6Q2z\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"77c6376cb087e709e7595a980ceb2e0b2c664eb9","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.57","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.53","@balena/jellyfish-logger":"0.0.231","@balena/jellyfish-metrics":"0.1.55","@balena/jellyfish-environment":"^2.4.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.94","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.43-renovate-non-major-77c6376cb087e709e7595a980ceb2e0b2c664eb9_1612237620726_0.6696780218491722","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.43":{"name":"@balena/jellyfish-core","version":"1.2.43","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.43","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"049815c0f4c9c2d4e2cfe24929e3939135c66b5a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.43.tgz","fileCount":96,"integrity":"sha512-XvcGwHkAbYrQAclxh+oEy7rQu00i+NRsTm1AtuaiuirhCB/YyPaisdKO9JPdKdRsomj/5mlS07FY5qRuUUsPrg==","signatures":[{"sig":"MEQCIAcGR7gsctly9eKP7f31mJOEIiV11yWLsJWDOTtCRVdoAiAqGmsAj+sfM36mBX1oNScD5xEDtHtubrnhmBaGOjvt4A==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":592313,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgGMw9CRA9TVsSAnZWagAA5kUP/A9pax0VoHKk/pEE2Rhz\nInQ1IAHctnuOK+nv0cHu3/fd/7V5R7teJg0E544KGSOpToPGKgdtox1bSk1J\nHkpkN4qkoF7stHhH/9NSKhd+x6+BcNlgDOmTpcXxLFamE+dblT4QbfAFiXdb\n4BfH2scmDFAIso9xP0gEMTYM4Imv5azchYDNJK1Gs51YuZvBch8qpGMulYAW\ngvmeju3c7p1CNAcCW4cKcj07SlIeCIuWAThqPzyQGe9RkEkerIUcFFSu+Sq0\ne+dFQ4dg2GoBRIh9taZTtPB6OMBkjvi61dBkvuN430hSXZEYGh/meuuSA3xy\nYFJ72RSVqKztKjCddsIJ19YAUjTgHhTS8K1gisV0GwKvbI22nWeJxuXDWNSZ\n9DrXUycd6xrZ9SdTxQNwnecELTPkBMPNdczDAqqnX/wbhmEzRWukiPhVBqbN\nNR8MhU6TBY8mSay7J6HlbDm19XtNLISK/LEm3U3vlrJSZY3xVvguiMWhmn7p\nWxYEEwa81klLNWnbf07yRiQjh4xbu+pPUbJ2yeolkLHcWDQ1DjgaDZhd6pVc\nR31V83AeMdreGFKBYITdZG+Kf6j+NPN5PHcM9ju0rfPvSa2xma1iGG6QlKxC\nak9Q/RJoQfuJCCjDnCfltD+iHRDwln4XDMk+g1tqoAdN+vIZCGR4SyNMzxEa\nBUzQ\r\n=nRZI\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ac86347bbd4c6095ff96f2d3c43aa71ca1404457","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.57","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.53","@balena/jellyfish-logger":"0.0.231","@balena/jellyfish-metrics":"0.1.55","@balena/jellyfish-environment":"^2.4.1"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.94","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.43_1612237884433_0.5567513174723162","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.3.0-add-session-scope-3318b089fb4f5d0a1e513fde35b6e23d84e1070e":{"name":"@balena/jellyfish-core","version":"1.3.0-add-session-scope-3318b089fb4f5d0a1e513fde35b6e23d84e1070e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.3.0-add-session-scope-3318b089fb4f5d0a1e513fde35b6e23d84e1070e","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b4ea2239843f9b1e2d8ff2eb9eedb66c5199ee46","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.3.0-add-session-scope-3318b089fb4f5d0a1e513fde35b6e23d84e1070e.tgz","fileCount":96,"integrity":"sha512-XXQ9+h3pB2TV4Vnt8V9CbdN1RMpa6WnMYMv8+ZQ6OiBDIe8zYf+lAoq7+fllF/f94sfPqg9MupGfEnx5kFhfjg==","signatures":[{"sig":"MEQCIF3X6RRTr46cQV9BvywMOx9opu1H7Oc9P3K2286BAdeRAiAFRwYS9TkF307uie85yiU8yavjPb/sz+sufNHFSwLTQQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":594462,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgGPHfCRA9TVsSAnZWagAALJkP/0ceG6gpvxRlnc+CDhid\nHaeYf/p9IWXO67w5gfNBj7IIcyYMG2qqZVt1W6zXGk1s0zAIo0YUVpzwt3+b\nc96Ns8gbDQ/dfkZs0NA0tE1nTuaOb9bpUCNgXtKRkxmTZEqyo517BaRb/wZ+\n5hF9kIvO0PRnOeNU9iIxwxZQUqGCJT0Es9sUVBtuRo/Qe8FQLrkpAYSk1L5N\n7S5PO1O2BFs64rrmLCLsxGWGgy5MpP9UUftmDG/vNlwZ55hvD94/yvwIxhkj\ntdpfmZ+N9mc5zZABy7z+SqdvrvAFO12J86vMGXV2r8HKXfZKPZ612SSjJnu4\n1LO7eP86q2cfTaSyqRUMXIazbEXsxW1om8w49HEg6GeSecsf1OreRut01JKp\nTqFmYYs8PpTVzY2DKnj0Pkn4vqLRdEDvpptPcIeSRxZDzka2wU7lhRqE2wUl\nA2gJT18KE0IMEgLold5A9/zRHpFnX2vDg7GjYUXH5L+i/oP7TmLtYOL62EAL\n7Tg3hT9m8JUkrokWaipcVZ40mNplberwLkjJCGx9fbJSfTKAbvcfYJ8p9CER\noR12z0XBFCj5V6WTLrnpIYKJQI3CQKaPAYcSgAVLpvvakuQ5Y/3dZzEROzlQ\nGoPwmvssHj/2gnk5dm9sgCw6Kd640880U7OYLCTuMXVnrvXmcaKKMa0yjD8J\nOLUF\r\n=Y1OJ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"3318b089fb4f5d0a1e513fde35b6e23d84e1070e","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.57","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.53","@balena/jellyfish-logger":"0.0.231","@balena/jellyfish-metrics":"0.1.55","@balena/jellyfish-environment":"^2.4.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.94","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.3.0-add-session-scope-3318b089fb4f5d0a1e513fde35b6e23d84e1070e_1612247518648_0.05269414018646579","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.0-5508-pre-build-versions-12bb8506076dd96f4399d36d13dc94b169adc1e2":{"name":"@balena/jellyfish-core","version":"1.2.0-5508-pre-build-versions-12bb8506076dd96f4399d36d13dc94b169adc1e2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.0-5508-pre-build-versions-12bb8506076dd96f4399d36d13dc94b169adc1e2","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"362dd2a24ca07104db2bddfeb4cf70b3e4006bc6","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.0-5508-pre-build-versions-12bb8506076dd96f4399d36d13dc94b169adc1e2.tgz","fileCount":95,"integrity":"sha512-Fw0GTwYfBuXP0RHLuQbUONgmkJP3fZlZW60Ny6UyVZY073ufvaCXJNJ8yGiGOGOke37QqizU1bOZlS2imtiG4w==","signatures":[{"sig":"MEYCIQDAuw0GrG0+52WYjDTnKLeWklpVlgF+M79tU1JqFsaWqgIhAKTTrrFqYLUufybfdlUKW6d8BG2EXZn9h0hxmxGRc4Zf","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":593368,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgGVGRCRA9TVsSAnZWagAAPw0P/2T9ZAf1XKzmkLipOrZS\nKN81O2Cwx2f0OTGvyYXEI2SNNRmdhemVIwJ0alAIsoAVMoO0B20cVU2NFt+R\nnxjGi3oziUePTlcTgBX/XGHq2Sn64DP94ojtDmoFTNuo1lNVjo0m3HcBeDGt\nBoFd4aE9wGPdTv1ge1bTAEdb0PVUbd1BC7I5BUr6BfN5H4S0eFaWLhJhbgZn\nNUEYjzvTiJjSYyq3mnZ2ZBfj3q6CGilBWDtQImPomcHKeRyHdRBboWAGUzsQ\n5UuciNGK0DxzC14oHnjVk0iCPJR0ForuN9WSYtqYdlDZM0d2O29ArM9pcj/c\nNmU18RXxrdDSTqgmY2ruNX6OdzMzoEVn2eBmYp5LwcFMGzok/gUmnf6enO03\nYPua/ldFaQ9vTMz6gdHnirI5tzBzifpxGk0ziQJbPan34c8wszvWU1NwGWrQ\nc2FJWT/X14/Jp3j4MiyZlnfXCSn5Dm0C1y+HQr0JNnKXwh+zKKJgQqeivF3P\n75EGYzVqaMpXqyfon8MTrPho6oEjc9PQR7hRpDkqd2otnsAgXXDM4i6B+Qim\ngbU/8luVl/OVLpBb1Nt5cCIhWLVYGi5/73IP1aJBYpBg5ZWqM0BbU0UnzbBf\nb3Yf/HRdIunsntGXc0+Ra1S1Bz0xDq64rzPU0bpTFx+YgcTvQAZXDDL2xmVi\nIXjA\r\n=McYR\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"12bb8506076dd96f4399d36d13dc94b169adc1e2","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.207","@balena/jellyfish-metrics":"0.1.21","@balena/jellyfish-environment":"^2.3.25"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.80","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.0-5508-pre-build-versions-12bb8506076dd96f4399d36d13dc94b169adc1e2_1612272016854_0.6630976514943068","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.3.0-Add-options-param-to-stream-logic-56126644a8af6d3a53397c198fb3a09879178846":{"name":"@balena/jellyfish-core","version":"1.3.0-Add-options-param-to-stream-logic-56126644a8af6d3a53397c198fb3a09879178846","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.3.0-Add-options-param-to-stream-logic-56126644a8af6d3a53397c198fb3a09879178846","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"cbbd26e6e571a60196b4bd3b711a58902522d19c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.3.0-Add-options-param-to-stream-logic-56126644a8af6d3a53397c198fb3a09879178846.tgz","fileCount":96,"integrity":"sha512-nurT7LZ0RYDmDYb5IYHfTrcK18XRWkxmvPnRRM/ntOyHan+v1k56POosm6QNefJMooZHIAZ9cfVLGGxPU3XEBw==","signatures":[{"sig":"MEUCIQDHiTz9r5RKsEgcv4izORhs+rtkBl6sNcfdxmrmQ4keGQIgDl3lGi2WYtXYl9Z3Dj9qgyOsMXE/Kkl0M0bToWz6gNQ=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":592622,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgGXh3CRA9TVsSAnZWagAAthUP/0MtdAmUlDh17YGS10sf\nWAdzgVsMcTVYg3mnzBHfDkJ/V7Dm0jLfIA5czOO9tRpmVSiq5YL61T3sAcIo\nrnle6tbJ6LCRlAo6AtHmIWWXfRE3JImCX64F98JEZeVQKxQDi2CainrSSJCl\nCZObySc/t90cav1Pyoh/cEBAJo75BLIaB7Q5lbrWvkNHvbI8mHRueKYHQnpP\nfm4TCqEwneuuaK+NRyWZ6jpvNUEV+4qhFAdlB+2dMWTeub0AXvhR0oJnmdqd\n8hkDzl2uE9DdMWNJwdkKbpninVudW/5gMyfB0DJ6P8FptgR7OXRR1ODCJDCi\nf8H9b+hx8LKj4q3gdYXceUswSWM6qHkiL30OaSVqqBaaGSB0fynmRS3YOD50\noatiRkCJBJqtsjDx+DhMceMIMTwO8p+m/BlpG47/JMQpahp8kCi/+KPKkg6i\nuy+mX/+xo30IYHCVcZXw2jHPim3glmbKUiMGYXEDJCZ9XtufU1rf0U1MxyQM\nSpQf3SiCeQjskkAbtmD59c0Wsx8d2xg6CeWi8nRTIP6K/MLPpFVXMXQ3hjld\n4akl6wV7jtoFKgD6Wv8ASZFBA2gUwXZXp66RmJ6rTSpkn+1cQb5NrS7q04RP\nKx6tZ+26ieTM0qDOzeHNXol8p+0BskzMIR3b0QFv+2R/9OUwS/LIdJfCd8cm\ngI2c\r\n=WvJA\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"56126644a8af6d3a53397c198fb3a09879178846","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.57","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.53","@balena/jellyfish-logger":"0.0.231","@balena/jellyfish-metrics":"0.1.55","@balena/jellyfish-environment":"^2.4.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.94","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.3.0-Add-options-param-to-stream-logic-56126644a8af6d3a53397c198fb3a09879178846_1612281975316_0.9358414154025061","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.3.0":{"name":"@balena/jellyfish-core","version":"1.3.0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.3.0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6c893ad7e388dd5bbdf10bd833fe769c38bce080","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.3.0.tgz","fileCount":96,"integrity":"sha512-LuWdoNdO1P0z9Za9Lc2dFuRLcsw+wDZBOHgK4mkep8o1VCtQ6xLDaYYpYxv34fReVh/rxwcXZEdbVDn/rx7pcg==","signatures":[{"sig":"MEUCICmiobX4UYhWM0WbDuoEl+8QVi9p4AUfk1w6dhmGjXGcAiEApVyIJjQp0CCDeCQEpocZu83d1sQTFy4nXwVjOxOmg+c=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":592547,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgGXnuCRA9TVsSAnZWagAALK8QAJuecsNozEyNu1w9EdVv\nEx+2t3njLGtIsMJpZTo8TBadhdFAuau8GTDATkSRh0jBh8CVg0lG0i0I4thi\nmIhsN+G1b3l6bxFA23JTjHIgEl67nVeWImTkkfhs0YP+quyKqZNrJ5t7P1dv\nMq01C+R8K3Nu1e5RaCZYI1mGOoza9SPX6cFK/iMvgYaLRqWwYz92ZVVco8px\nxVhBlbAATupcXn6lJzL9oXaQHx1x9cix0yxMUG64C9eLda3faT+sPD0SsIR/\nCmNg+Usddny46IhlhLAhrmrQxf7kcK6ee+YIlosVWSWom6eRLsLJKPxUsUGC\nOFqdmV5lrCqEj+j/YktysR9FwKGi19ygMWksvL0Yq4+rBXErNmy4DhlQig6q\nN9kp3BxICmOgzaBqbEuxtW2qbHPja2RwmomtzMMWuoXjJD2GiI98PuDgZKuP\nwoH+moL/IF9IEGY0j/TQ2Pkoqm5qL2h+dVcptLdZP9EbAsHmNTYR4OgYIbYI\ncuEFA1r/LdrZPxJYhLXAgVa6HvH8dINQMbx1XUJFXJjaAHcj+duwbGuXwGM0\nYiXjEzdB1iYvL25HW85Y/1pcI1DKRj08itOjSdBSFjMf3+2vnRCP3jJqQKdy\nuvV1M5vn0Rlzf9EggZyKwGZ5YwBgCIYgjgVPn0Qf382bwFZ9rRUepQ8vlUKQ\nrmqr\r\n=x4sc\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"58d7cc08e2276471192c472e0ae3739d88ebf889","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.57","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.53","@balena/jellyfish-logger":"0.0.231","@balena/jellyfish-metrics":"0.1.55","@balena/jellyfish-environment":"^2.4.1"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.94","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.3.0_1612282349508_0.06856841960600457","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.3.1-renovate-balena-jellyfish-logger-1-x-edb08b973a0a77ae07d980cf7a9cb489341a58a6":{"name":"@balena/jellyfish-core","version":"1.3.1-renovate-balena-jellyfish-logger-1-x-edb08b973a0a77ae07d980cf7a9cb489341a58a6","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.3.1-renovate-balena-jellyfish-logger-1-x-edb08b973a0a77ae07d980cf7a9cb489341a58a6","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"49d7fa72775a15d2fd7c4217d2b8a1a55925940e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.3.1-renovate-balena-jellyfish-logger-1-x-edb08b973a0a77ae07d980cf7a9cb489341a58a6.tgz","fileCount":96,"integrity":"sha512-yjcruF9AqgysjI9oWX7CaOmWBsIIbduX9bPFgcRJCfapD21j6RzfFuLIpuHgVxkgbDpipv7aeoJBi8iW6pOxiA==","signatures":[{"sig":"MEQCIEoyGxfPGdZAobj9R4LflMrRD/XEvP8eYqLAvxC9lWV1AiA7Y6vWJ5mqrsazHtv9hs/OADgYWYOdB7IHTFD4E8oG6w==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":592720,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgGhdaCRA9TVsSAnZWagAA1cMP+gOUfRttCV/z08Ur565v\nDEWVw7sdVRD9vuyHhIbIcjDBx3Zoe5RVGn9+p+0QqXIcVXj7NxR236VhZ3Zk\nwi0pthNdCjNYtD2iYHXF/LymvM4qGeotIO0Lg6HgQJaZK76EDS90Qoj63phf\n0Fy63C3UIn1uttkNx/QMzgpPzb39daUNiTnX6rxlCq7q/5k13Iy1wYQXEtV5\nLr2B5vtsoMIhp42DNDMYK8s6vJlCXadOyDN/Fq2vsr12TcdBryuUQls+r8qI\nLJ3nOyqoNxOt7DP7xYKsN686DuOV12e04CAFX8Npu7ja0JdJ1e2O7nyv7hCN\nxT8YFeTPdbbLJ0RXpvYVI/5GVLfEbXn7jZ9AOIrYcCG6fys8t8pas1BZfmTi\nA70SXdsf2Ehzzmf95gV/Qef49xsBgSOL7st4+AbS5dbYI3IY/0EeZsr2fagp\nDyH1uwj+I3JCI8zuf7bHHHTecVY2AF2sEbNvgUxJPUI6UCA0LvhwdaicyCQv\nyf5l07jsA4DskMv+fS0H9mMfPzm1nC1nf5ncBRy4UyF7E8V5B04szgI5mKS3\nrTIp7v0MxmcGjybtVqn0mlNaoYaBJ2Bbf31/xaYR9axbZKRVkzqJOtDTcYVH\n5iFJE70M4+Hr8OPB/50J3/diDwHNz8znzy3x1HMj2d/6Ed524mKa+/6QkFGv\nENIM\r\n=bfWr\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"edb08b973a0a77ae07d980cf7a9cb489341a58a6","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.57","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.53","@balena/jellyfish-logger":"1.0.0","@balena/jellyfish-metrics":"0.1.55","@balena/jellyfish-environment":"^2.4.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.94","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.3.1-renovate-balena-jellyfish-logger-1-x-edb08b973a0a77ae07d980cf7a9cb489341a58a6_1612322649928_0.08304514797895268","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.0-add-session-scope-d9e92d6fec120104343a2140dcab41fdc14d472e":{"name":"@balena/jellyfish-core","version":"1.4.0-add-session-scope-d9e92d6fec120104343a2140dcab41fdc14d472e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.0-add-session-scope-d9e92d6fec120104343a2140dcab41fdc14d472e","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5201df57865671bb6377a8a7b99412c99c60ef35","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.0-add-session-scope-d9e92d6fec120104343a2140dcab41fdc14d472e.tgz","fileCount":96,"integrity":"sha512-EIkdG5HN8Nku2u9fbjDQQyyN6tczcCa7DsT9GE/T/+bDp1KKp4abxX24ZHb6qT8xZKWwLfT4Q1CftPRMK+faCQ==","signatures":[{"sig":"MEQCIGleYIzg6lO0SvRmTWuIGE/rln3ALBGKjNNEmEtrfFwYAiAF3qns3vN2aD/QlwWvHmEoHBg87Q1s/xXiMOvvUL20yQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":594697,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgGi2MCRA9TVsSAnZWagAAoL0P+QAZ5Bq+8ano7sCgJV8y\nqVjlp0TQZ/IIoNDpRXpGHAgoLQg9to0vp5YT2UABnS/DnmfrkiZ8us54k2JG\nKWQJ4IgA0Ohr0KYiLFss7W1VMaAYhwlODjoaUJf9pRZSyZlMiBOTBs5CNYfr\nAKPNqMYEnChU07M0yk57pPjx678/iS9EgRFMSJs4cwzNhnBZMCYUOkSZS1xz\n1lT04qRoluGwTitI54XCPi9YhjTC2L6cm3Y4vTQuhQdaSAn8xNQ+SgdqNeJN\nP9EOb3LA9QWYcE+RxM1mVCriBrjuU79lQgioHYzC1FcFzUQQthUH7j/qRjph\ncStb8am1q+QHCkpnRhoqD09l9UtHlzH1Mu5nxns876lAor54O+eGsI74m3qR\nYF6XM1YD7bzTbJxb9QfaRlLCCT89+aPVyQtGbxSexKAZMk83RIQPMcSP6w12\n1ir+iQFD6/8rzqzj/ZJBx3JLLoTcnJV4nNMI1A2JuBb+RASp4/FqJSgW9v8/\n7LQ2H6qTs59WErVUtFMRrLae2N14z3Aw0E1K1c44/fSSue0l1jveioDy/Kei\nnXEbKxoZuPaAHzzZgTBeDvn+xrxNoGo5ou+7hZ6aHnW941FA+Wang6cCE7V9\n4jjYfT2UFCEIlhBg0m9VFrD0cTlVacJLdDeHjzMCS0arnF8/lLOpO4M7VvcX\nFNsh\r\n=+xbv\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d9e92d6fec120104343a2140dcab41fdc14d472e","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.57","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.53","@balena/jellyfish-logger":"0.0.231","@balena/jellyfish-metrics":"0.1.55","@balena/jellyfish-environment":"^2.4.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.94","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.0-add-session-scope-d9e92d6fec120104343a2140dcab41fdc14d472e_1612328331336_0.6033776072235819","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.3.1-renovate-non-major-d07bcc343e308db3682c88662255c2bba5a8762c":{"name":"@balena/jellyfish-core","version":"1.3.1-renovate-non-major-d07bcc343e308db3682c88662255c2bba5a8762c","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.3.1-renovate-non-major-d07bcc343e308db3682c88662255c2bba5a8762c","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"dac5a98c9354d31360bbe6dd80dc723b480ac23c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.3.1-renovate-non-major-d07bcc343e308db3682c88662255c2bba5a8762c.tgz","fileCount":96,"integrity":"sha512-IRCQt3KVveWhjWttGN6WzBk3D4c63T2BKiAAcp1gP+tUrWKOiA6ahmO0FRObPH8P9mlF0QScDXE+cHYsNCWThQ==","signatures":[{"sig":"MEYCIQCp51PiNroRf0b4PROcqwsWpicYmKOFeZ6KTwnQK0vvoAIhAMbcSNoZ8yhmBgbbkHXm45u8r4gs6rmbpWVoTh50kC/+","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":592672,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgGjDfCRA9TVsSAnZWagAAg08QAJ6m3/I1rClp5EDsHz3g\nhkOpWdum0j7kSO7bakUTQHh8/tGaktJAI9Vx90YXOjcwZMZC651+QXynJdtg\nQ1ww6pOiXHJlPem3wukR4XXXzsRo645X7gqZG2zsYF6dWaZoYjRSUiQAzOSl\n0uEVE4S6GDqwga6I5NqDQ8gCwWlZL1Hhca6ImdT5/BFT0gC29aLtWu4bGlrD\n4kGtQm7O/MDxFSWItGMZcydqFWWrSnYnxUAuLNMeydZcFCBkk195vqkqlrjD\nr+8oeXWGfjObsaW7RuyAn6i4QngU36NWe6xmjaiz3UC/TYdrMN78n9Tz6Otv\ngNhFuqcUDP58pSXjVuVGDi9mlw/Z3lyQGC09yvGw5GWY1cXdaP+cQPBt5ac1\nBIes3G/8qK9+6oK3Q1qn8hRHKlyPqLrfp9PEWxPHSbQR1p9dfNsZ1+X5U0wm\nV57OiwutWUY/uSIuXBcMhXYkAuMNvAWXhHmk6v3XLGvPp1kWu1fLtPJdY/hT\nYZUJ4j5S/GrkYUSmGz/rfSmRdVr12C53T3imMjzGbWkXfdo7LUxySJcldIe7\nxrP0OYKIQWlkjxN7HRbQGCqq6uPz2BFjyKkbF+Y6hmrEZB72YOzDOz47OTae\nlZLLL+kxJUs5c9ZOqm9NJ92r3+0wBEWr30DloqrkVpMdIMo/XJbuJqtFx5AQ\nVS1P\r\n=7k8e\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d07bcc343e308db3682c88662255c2bba5a8762c","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.58","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.54","@balena/jellyfish-logger":"0.0.231","@balena/jellyfish-metrics":"0.1.55","@balena/jellyfish-environment":"^2.4.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.94","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.3.1-renovate-non-major-d07bcc343e308db3682c88662255c2bba5a8762c_1612329183184_0.9494871667309144","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.3.1-renovate-balena-jellyfish-logger-1-x-2101aeb90c2215a5fd1c205ee8e2119f1fb13064":{"name":"@balena/jellyfish-core","version":"1.3.1-renovate-balena-jellyfish-logger-1-x-2101aeb90c2215a5fd1c205ee8e2119f1fb13064","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.3.1-renovate-balena-jellyfish-logger-1-x-2101aeb90c2215a5fd1c205ee8e2119f1fb13064","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8aaef01bf7a88e920b472b8958b36199787799fc","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.3.1-renovate-balena-jellyfish-logger-1-x-2101aeb90c2215a5fd1c205ee8e2119f1fb13064.tgz","fileCount":96,"integrity":"sha512-zNvZfK5hFRRu3krgiFWHSVoqgQ86fW8TIwJbB60VGyLYiKkal4XMOMn1qpmk2o3ph4XEfsIxaHM7ZNDRnt23lA==","signatures":[{"sig":"MEUCIHm1h1I2LKWXxzQv39VBT2C2hz0nJbBxKlJwmGCFcAKaAiEAvClUgHRKEOcS9z/W4BPSEgGCDQwCG8nd48lPDgT/QDQ=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":592720,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgGjIpCRA9TVsSAnZWagAAvFYP/Aok2iXoPvjOQ32auHId\nTRuKxi5kimsZJbqIyayBDG04r5todaLPoQmDrSnge2SkAeou4/GpP5hZk6TR\n7MZCV3FxSH6QAysoJ1GnjUq2qabE7yEnLF/R3WpSw8hGG6vi+5tHiiAf7nQ9\ngS97UHzIk2GfldMs8JvNryRaz7HPsMSOrLDst/0phsuZ9V9mqV3VOoiqsw+H\nApLKGfO9mO4sVE8NC/Q35267g+hXhpIJyuInVtkFf++ais34xTlOB2eFwyB+\nuRvox8tgsJvd444y1xORNVcPHk+mmy18/ZMODzn7HpPpBXb9RJpTLEHqKbYQ\nd8qS6vQesIuA6SsEWhe2Qux/cn+fZL6tJwKNcma9h+GjIIixvNEgW97XTCx+\n2rN7crPFX9ChonNzCLeNrammQLV0bD7q2voLxFhoMAk/Us9zeC8StqS9Tqx7\nUe6Yxb1VcHrSLKf8dWpmnRTkyS1oZKZv3REgNT5b6w+LYAusbuGEsZ9M0/+g\nRBbj85LtNjCdhIaeZO7RSh13RklTxmDte/FxRcJzizZLvvZjk7hv8sMg7Y/+\njOhUoO95IY9QwxW2t5hlbYE8IEjUcebCN7nHZBuwGPWASBDFYH/g2TSLf25z\ncUw6YeeMIJ5zwqMaKmoYiOuN5shLNROFyr6M3PCwfXz7JXU+LEBVKOCVXWtz\niWYI\r\n=t+Nx\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2101aeb90c2215a5fd1c205ee8e2119f1fb13064","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.57","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.53","@balena/jellyfish-logger":"1.0.1","@balena/jellyfish-metrics":"0.1.55","@balena/jellyfish-environment":"^2.4.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.94","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.3.1-renovate-balena-jellyfish-logger-1-x-2101aeb90c2215a5fd1c205ee8e2119f1fb13064_1612329512968_0.5789379204144081","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.3.1-create-indexes-concurrently-651ec9eb92559a340e8a0fd6ef245e3d1c5a2497":{"name":"@balena/jellyfish-core","version":"1.3.1-create-indexes-concurrently-651ec9eb92559a340e8a0fd6ef245e3d1c5a2497","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.3.1-create-indexes-concurrently-651ec9eb92559a340e8a0fd6ef245e3d1c5a2497","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b2ea017e536e88d17c02542df781ec7d97b9d3e5","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.3.1-create-indexes-concurrently-651ec9eb92559a340e8a0fd6ef245e3d1c5a2497.tgz","fileCount":96,"integrity":"sha512-KsoJF6dMRXzMwYyBxS+3thga1/ZbQPf0CNz+m/qL00NfPkbh+HTHyGgLqlzvO/2RNf60YzFglnRcgqRoju+gvw==","signatures":[{"sig":"MEQCICwHvtEezsDx2B34YvVVnutit3TgwL1i+e9jhejlggVGAiAEP/wkb5gUWXoPH+UHYBMZDL6AqKVzQw40UStl08BH9A==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":592698,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgG2oMCRA9TVsSAnZWagAA0V4P/jZ7RscAAvobdcuS/Ott\nrTxowep0DfCxKLCT42LWUCho/V48q9WpQXmBm/UnzE6NdauKM5iroH67R2m8\nNbrOYjnzJSy18s9jkRpefdwjcoR/knAwf4HyUcIwa0zzf7fN8XnyD96z/UGG\nyqCDqL4uGkSfvdu0GZhDl1XLTw+T/+7rHTOnraokj9rkdQ6tcgTgtuJEozxw\nLd8fZdDjn0ip4fjIj3GOfUM4ueDPAADmenPjwzxXgPhTIKu6gGVt1+9WNKVD\nUoDLoWfDIh9Wjq/tNX2urHCZWHR+XTqNvsUAk8cGh6iKATf+OeP7eWEUozVy\nWl61s3G7IB6rc8iGWecGFt+nGiBYSnO4JR+pJ2XDvNnA3dIMtzMza6L1O9R4\nccllzHP48boVTiJzVptC9DPII+pG1w6iwKMmt3kIzOzoOvtm49q4wczzcYDa\nAY9INzf72/3QlVNqL9i7Z8UIo/4K0CHb5WP+qC/+OCzQ+E0cduinglU0kciS\nPwO3mLPRZmR8Kh41cVtJAniuuPQGaAkylxCMPXUAZbjcyr6tDa9YB6DrVkTB\nSCQ6Vm/3sjemanHbh6/Vko3el+6JC5Oy3pzh9wr6HZDjFN95QdMO/wisueBP\nUAn8qtWydHzZe+cZCg2vpWjnDRGhMPg59A0l1KgQV92eljfpl8Q2CpoVKeWK\nlcaH\r\n=PhHC\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"651ec9eb92559a340e8a0fd6ef245e3d1c5a2497","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.57","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.53","@balena/jellyfish-logger":"0.0.231","@balena/jellyfish-metrics":"0.1.55","@balena/jellyfish-environment":"^2.4.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.94","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.3.1-create-indexes-concurrently-651ec9eb92559a340e8a0fd6ef245e3d1c5a2497_1612409356367_0.6841135553825417","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.3.1-bump-logger-f2ae67e11666b5fd79965db02d6e55d6a0f5a0b6":{"name":"@balena/jellyfish-core","version":"1.3.1-bump-logger-f2ae67e11666b5fd79965db02d6e55d6a0f5a0b6","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.3.1-bump-logger-f2ae67e11666b5fd79965db02d6e55d6a0f5a0b6","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"572d8dd48d09e50ae6ffeaf4a7af73e4ae431b68","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.3.1-bump-logger-f2ae67e11666b5fd79965db02d6e55d6a0f5a0b6.tgz","fileCount":96,"integrity":"sha512-MGakKozTJOpgV7ht5SWs/VyPiOf/lcP+bzNlY1ptoO6SBShyr93v8xwoWIWcKBAZMH+BVon3fdKh9sqaBRPRnw==","signatures":[{"sig":"MEUCIASH/qoHxjcCIt2nwBevWF8Li6vSQe9fGRefqsEbXhdDAiEAmF/UdDWdTagnxu9ZXdTTr88v2RWHrir5cNdIRuj3K+w=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":592673,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgG50kCRA9TVsSAnZWagAA4Z8P+QFI7Gk2ZkZjgCQSo/de\n+cMVbnhR1h4ntXfTDOUYY16AcUVKJydXM6+iOOtOBlbO3Y/brbsylkzCwL91\nErxXLW7cIL5NvysXhJSGLL6HuzxXHvD8WUUvd++txkIjrcOEe3n627SLo9rQ\naTe/7GGbw7VXh3Q8AmUAjHzVTVyEl9fyV1I+jdBctwjEk2T//1G+7IsmY957\nDd86aGoV7ilr3AaDqAnQBYVGBVf4nHtEB+uCRLnoOINCHqNbb4YkpjwN4OjK\nprR8M652h0xqvlywoOe5Hzc+89Ts3LuLwTYrJk5tuAFL8U0vACPF0bvTqU/d\nWyoZK65zbnqwHJBKOdCq5MTJxaG8HZh32oRrMCRp2r4S3kkknCTfiJ8/0wGP\nrMIlBz8OZkZtPjUqHrxhtHCvtu2o1BR2RqO3RqBkUcujTPVWjfXfGiOyQUq9\n7UzU0fvgalJ0hLHaYa9KQVwC7rOOIyOK9QnUdityp+A+uWBp/DzaiJCY8OK1\nbWq0EnvHsnr6JxurLwH08UYC6yjvW+V1qXs4BQNznY+J+TdSFyaKIimfFztR\n1oRANzlIbTh4flC7TlEqzpBL9r7+XX17wIHA/2DmaP2PK1ovKZhIfwsmczXW\n1MousR0HzhablWQsmZs+79/ub1DTinJAA2jWdcabCwqC1/YNY/aeJMs6Rmq2\nvgzl\r\n=TQ2v\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f2ae67e11666b5fd79965db02d6e55d6a0f5a0b6","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.57","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.53","@balena/jellyfish-logger":"^1.0.1","@balena/jellyfish-metrics":"^0.1.55","@balena/jellyfish-environment":"^2.4.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.94","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.3.1-bump-logger-f2ae67e11666b5fd79965db02d6e55d6a0f5a0b6_1612422435537_0.9034896897562794","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.3.1":{"name":"@balena/jellyfish-core","version":"1.3.1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.3.1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"61482ac3edf1dba319551a673088f18295c216c0","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.3.1.tgz","fileCount":96,"integrity":"sha512-qu7m95h+bnCQeswFcICye2mntSPNamxuJ0tj29p+6yx17g1kmsOQcNUUwi1YCVIvKOCZpmIeEWriQzssIAAvrQ==","signatures":[{"sig":"MEUCIE/V7Sti7ks0WWhaHpZGsCR+y9Pr5SO07wtqWvYb/tDdAiEA43WO4j/ibvAgXJrD+4Dr+0VJhCAIoTDaic0GtWmEptw=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":592620,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgG57jCRA9TVsSAnZWagAAXI4QAJFejmrys6Ent7bQdnM2\n3DLbN2OnrQJN3z2IpVbdz6aJQ9K5Ur5v/LgYyTBQpu5YRJEb6qL6hdubrnnc\nQm5lMFHsSA/aY29UwR2dLebbX9s+GXKg56JuOXYt+O6zV1DRrRsf7Or65HWp\nWXD8J1EY9P3Rk4ni+/nTQYxX1FHP7U+LhDILzOSh73+X0wVcTQGbeT2uOIMh\nGJiJbDknralFehybO8FjMCUQKrMZCysyvVMgm8Ukmxkax3mUcv3/CoLKXh5K\naLBcgxrAlKOQoSmVGYE7Sic77Qf7U8Ju3xKoYk1gz5fPppMuvCHJyj8rtcNM\nBfcLY6cGFY5TGgAREGbDa5dVeXNQSElbpvKCqecxDwIvCB71x6t5q5KNJFuh\n5FJxT78ncyBJgjQPN9tNTgaymsc6kkFMRCSx0weroT1uzUT9y0dcvJFpIXZQ\nf444NIKa3YuTPz3IiEJO2RkhqkjMjASwYEq86CWEqO+4BJSFw8KfsRSGi5a8\nloE1ef/pe3BxkasM6S1tMJ5ct2yddObK5DVkaUFv34tyfl5o9PX+uDaOuis6\nTJ5Q00IPlgwT2WcFe8eyeBdHvrzzm1XioYG0O5BNamA625ueGW2JMJJpJQZF\nFlgbPQ87hmSVOFGjAkn04BnunRKXLLOwXXffyFjLBUH6QW8PmRuMq+t5RPIv\nJ1Nq\r\n=dXrm\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ebfc8bef697499d29caf56e2c0e772f902e90f12","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.57","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.53","@balena/jellyfish-logger":"^1.0.1","@balena/jellyfish-metrics":"^0.1.55","@balena/jellyfish-environment":"^2.4.1"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.94","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.3.1_1612422883028_0.3309775074710384","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.3.2-renovate-docker-non-major-c177f548dd4787171fd62b4681b7ed31e1936ecb":{"name":"@balena/jellyfish-core","version":"1.3.2-renovate-docker-non-major-c177f548dd4787171fd62b4681b7ed31e1936ecb","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.3.2-renovate-docker-non-major-c177f548dd4787171fd62b4681b7ed31e1936ecb","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e62f1be69a99c0ba73c02c4cd11c0252cefa304b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.3.2-renovate-docker-non-major-c177f548dd4787171fd62b4681b7ed31e1936ecb.tgz","fileCount":96,"integrity":"sha512-IOMbGrfDfQBHObdy+CiQ+kaevWtlOC2qQOUxHuTx6fD3OgA4Sc9Sfxw5agoKXO8hR99t8sHmi2WDx/ue8PjB7w==","signatures":[{"sig":"MEYCIQDs7XvMyQllBRhQPYVf/US+DT0Tmox6Qd1MrUczxbwlsAIhAJOcnvWd22AtRhJk2sdVGJMknAYAbSj4qEyJNJS2k0zB","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":592787,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgG6AACRA9TVsSAnZWagAAbW0P/0H8+Hfv1tGaeYhP6o6L\nICIyh5Y29FEl/VJdsF53JxoUq6Fvov22Ars7T3r7VUYJeyG5R0OoHTMTrEsv\nh4TH0TN2iWsVedH0LeABdqMfbxkEU1EhSt+LRzxmTRUi16qZGLd0qo1wod0z\nmXfQYPK53HjdJOskFS6mSun3mnI55/N811GtLl0aS9zw6p+DrXX/7+B47vsM\nKqbNbdCGtMOjauXPK3PL+tik3iIREiv3jBEYmXwSkykwhn5vAzGtGfu3bgiy\n5BeKWVMM1zAMVTCA/3r4K7pq+nn+Ggore4B+hTkgPLlIr1vMf2xbSOF1qdBt\nn9rZKko9BP16DJY1XHWhLqSJ0NCda81QDPN8sqYSuoMT1gXf9g86gSl88GHp\nwUeqd0gXhwpyM2OR01YBOH5VuUiaw4wNzr7n4sOQwmJgNi/i6iMjgn6r9BMj\ncYpd6rC7ywdF6zchPVGe3I27HYc/OxNmw2xrY4PRR/2H7bhrxSu8d3pn1NZ+\nQ1q6lx0HYo8qyDsrtOkz6R0YXlFLaXc/d+TCAvvd+Kms8mKDhBAVqcy/dqbt\n3vZdZw4hUS6qM3RabrYnNhbAFT5aMh3fdlofAasIZax/4b5WAyZX/421+RJ1\nTHrcWMH8ET9PhWYKjjTQVJJ3D8LxEpJjEQRMsRYAVOt/WIxALlBtnMEvUVJC\nPJt9\r\n=WJXk\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c177f548dd4787171fd62b4681b7ed31e1936ecb","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.57","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.53","@balena/jellyfish-logger":"^1.0.1","@balena/jellyfish-metrics":"^0.1.55","@balena/jellyfish-environment":"^2.4.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.94","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.3.2-renovate-docker-non-major-c177f548dd4787171fd62b4681b7ed31e1936ecb_1612423168493_0.32546306777333056","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.3.2-bump-non-major-d19ee14bda18bad8b0dcbf8644b94378e200d312":{"name":"@balena/jellyfish-core","version":"1.3.2-bump-non-major-d19ee14bda18bad8b0dcbf8644b94378e200d312","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.3.2-bump-non-major-d19ee14bda18bad8b0dcbf8644b94378e200d312","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d3ad73900f6c8ecb2ad05051677f1ce68aa65a00","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.3.2-bump-non-major-d19ee14bda18bad8b0dcbf8644b94378e200d312.tgz","fileCount":96,"integrity":"sha512-XTfFPyJCeA1QvOONOpxzJgD1NX3HjbHsmnYY+aXr57w6Bq4z7x916whAsBaall+/ucWu1NQrUS7yu9ugWcr81Q==","signatures":[{"sig":"MEYCIQCxz5ttsJoaKLYM0pE+mI+I1Jmb6QreUJihjpVbQSBr8AIhANBX0A45qKZm2bq6Z1EZDNFWd0zmzeFJHUREpZMvAF+2","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":592745,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgG6BTCRA9TVsSAnZWagAARX0QAI1OXriOhb1N+e8xwaio\n6+jO1JLnzcfeEQ8BbQju3PGGSXBSEoQiU5HbaHthnHpiWCrmIwVEDJ8Rw1K9\nE9W3x6W4//zAAZ6wCdirjPc78gC6VBvXf8SdZoDtkwnJ9pBL3VOMPUJHmOJY\ncHVbhQ2neKTkiSvK6HU2evEfV/WUM4fmFwUMLBwPrZQDPSgk/pJmta4Ij7gf\nfJ+Yvx6CHllsohAWHMO7ZioazJ2/63pKHcllgBUBouTpJFsl6MVVFNlcGHzD\nzt9VUeCkxzpTt1Qq6fX7NYhkPHC5NZ+qYQbDwZKkVM6n+CBe62LgQ/OMJAkt\nj0uVWbs2KniiIzqFHbX/UXWnpVwxAQQLHVas0yZA3HtcjS3cfzs7OlgVL3nj\n3K6m04OGPwQHOryw6xowKnvlBFIVlKo1l6jF5j/lPhR8kSreFSR9ymLR4sJr\ni23S0hhuoBCYvvOvIfopcBT6r9pLVe+Alito8PC3vuazCKjJZMWFznW2xVtI\n3SL9rv9z8O/GSj3b5VAUXbPkDdHunYui+vIhY1JLGXkDEh2pB3r1NrJSCDib\nQli0DdqLFDtWLxyCnIE598gIC7R3X4tSI9PxgcB3YEc9Hfurqomr9wN3avMx\nKANS0XxFX8S1tZ6qqp4IGYwjSh1x0+PRQl0aqz4LUnzPHUN/bO7bvTaWNjPj\ngJ3t\r\n=86tA\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d19ee14bda18bad8b0dcbf8644b94378e200d312","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.58","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.54","@balena/jellyfish-logger":"^1.0.1","@balena/jellyfish-metrics":"^0.1.55","@balena/jellyfish-environment":"^2.4.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.94","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.3.2-bump-non-major-d19ee14bda18bad8b0dcbf8644b94378e200d312_1612423250546_0.16855871220598906","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.3.2-renovate-non-major-c5c03dcb7a52cbd5b3558bd935a938d364b6a340":{"name":"@balena/jellyfish-core","version":"1.3.2-renovate-non-major-c5c03dcb7a52cbd5b3558bd935a938d364b6a340","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.3.2-renovate-non-major-c5c03dcb7a52cbd5b3558bd935a938d364b6a340","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"7b932d8f5df48766cea6df3089d98328e0e31581","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.3.2-renovate-non-major-c5c03dcb7a52cbd5b3558bd935a938d364b6a340.tgz","fileCount":96,"integrity":"sha512-KZl93suI6Rr8ARUuerWOqEAFRBEFjzwNh5smhqEBka3hk9FAXgv8G/yULbCQCp1uMDxpDgqZe7ZIJSbcwYx9Aw==","signatures":[{"sig":"MEQCIEUK4TfvF87dZ2cXk9rUiG0PNV6lLkaYYWOKCod5f4p5AiAP7suKowHCfuXLwb2BE1czu2vC7N55CkeVAg8vnatK0w==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":592745,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgG6B2CRA9TVsSAnZWagAAeHYQAKKAgNX9bhEzAKokO1yl\nMnNa0Quaq53v9UKY64G5eN7yi9DWb9PLOb1T8ijk8A/UqMwa0EvyM4SoJRuR\noEKMk/upZWfCmswpAQpDO8HpWSUo2tUx483INsQALY0wbHnB0BDWkO4nOkwX\nVNVVUNnUx1ZMCjTra4wVeJNQ2Tjtu6QGWu+GPOdFW+D0e21rJgCPToqRSaTz\nX3Nz3AFHw/qdmLEOBbtvDjM0eYPPGAKX/z22QnpU6Zz2zKgVao2x5XxyHz6E\nh9TeO0x+cTknqxALI9VtAx2heMhf8x0uTNMgT+fOiZjNc8VtFUV1S7+xlVrx\n/h8Oi6M/EjyWm7oiZdo/2aUSd8qMZtqTv4HN/RkkqazCdYMwWTsPq7p3VZQV\nkJB+jAjMZQDCpPmitiBoH4/BSK1z0eBMXn0kcMuZ2r4vsUk9KVZ1YkqDwLBj\nLdxv6Sr7zr/EFiCkZFkSjU+o69pulsFsnuCw3TqTvmXHSnJAdmBFkptA0WAc\n35lrak8z/neMvcdazjkFMpcKGTB25lM/fn0WFfCjMss7jwCYitghf+VwyMJB\nTRjiovdkaHI6DLNhFE8t/6o2EcQ8m2RBE4Klq7dJbNUEdHaU9iQkIKMnHUrM\nS7VhS8RG1B1zHV/PSfyXyo7qsdJMTTX4ZCvdDTVzKgTFucMann5anAuB6CrS\naLO9\r\n=TimK\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c5c03dcb7a52cbd5b3558bd935a938d364b6a340","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.58","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.54","@balena/jellyfish-logger":"^1.0.1","@balena/jellyfish-metrics":"^0.1.56","@balena/jellyfish-environment":"^2.4.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.94","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.3.2-renovate-non-major-c5c03dcb7a52cbd5b3558bd935a938d364b6a340_1612423286207_0.7062506385387941","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.3.2":{"name":"@balena/jellyfish-core","version":"1.3.2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.3.2","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ee6c8e601a2d3e7aaac08540cb97963f0b8eec5f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.3.2.tgz","fileCount":96,"integrity":"sha512-keJwI3laLpCeyKRyP77UsvEjy8Gl5/B3TnA0YAHyxemQQ0CdnxRXuL3RSp/tRTxykyrCFa3YEa7wRmHc2raB/w==","signatures":[{"sig":"MEYCIQDOB6F2t1uH2vIY6Fwhq1IeaIKIpXvF9cRPX/8SETJsBQIhAJZGl2g+BpUZ8UZ0Q5rqAp0GrR0D/0WFzzzjAT2Wy09R","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":592720,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgG6HnCRA9TVsSAnZWagAAPe8QAIU4+v0orZg46KkUHJcy\nyIQIJtm8Fajy9JOzUiSD5vDT0bJJRY0oJMYPmcoW+oeV5ZblO8w/1b5jZg2Q\n3DwUkzJB4qyFbXmsRjMimLtl59nnkZ04qtBt9psEaBYwOTLctlgR8s0DIWV4\nYZKq28FEiRmxOYNLfEFEAJvubY1q1LJ/+U8vC08XDj5TX5Rbv5uc2SPXVs6s\nu1Y4+40DoWIsctn5PH3WMti4ffxdlcUU+mQrFnU7KGF4/laMJky0LHuFCTlO\n6AUtNVimL0s3m5CSnSzw4oGI0ooro5dQVbuSPD9Zalvii6pk2aG0YLk/XCVn\nCemQ3s+hBx76HuDi+Z6tTq0FhH8YUbNQ3I7WSdTOxGdokltHewUCZO8qXyNg\nmt8XOcmnxvlzVXPEPY/18FoDlUMa5nsYJQ6I4Lf2Zuhv4Io1ZZLFOhimduRc\ngxcF+5oFsGvxqhrNARYnDAlPvJEPs4NVADB+5DHDrF4Fc379nungm2FbkGQU\nIyi4ICDln5v8AcUORRTOE54VIBUZudJi0dgMXFQ3kgaIjgxGFccdVktdKUM+\nMiFIAwxbHRFN3JjF/FifwoawZj4Hm2KyCkrMa7IuOIDDdibBpwQWpzXutt4u\nnh1N2DbDBByEbuNB0taqipSWhbC/asgOxZCcBXIVbPQ1WHGt1ET4tC0xj7hy\nPkFC\r\n=WFl7\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c5fefe802caf5a87cab020ec1bd7e923998af8b5","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.57","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.53","@balena/jellyfish-logger":"^1.0.1","@balena/jellyfish-metrics":"^0.1.55","@balena/jellyfish-environment":"^2.4.1"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.94","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.3.2_1612423654611_0.18491634023193537","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.3.3-renovate-non-major-2400edc95b4dcdcb683a6b5da18e2db86d21661a":{"name":"@balena/jellyfish-core","version":"1.3.3-renovate-non-major-2400edc95b4dcdcb683a6b5da18e2db86d21661a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.3.3-renovate-non-major-2400edc95b4dcdcb683a6b5da18e2db86d21661a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"92ee266e3ea64952d0366c4d60bdcbb00b9ecba9","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.3.3-renovate-non-major-2400edc95b4dcdcb683a6b5da18e2db86d21661a.tgz","fileCount":96,"integrity":"sha512-wfyXNvWSmK8eb8kjx7lThF+lRYEa0WxCtG/4o/lYlGUMpaxvIfezYP4WPqob9GfRlSP5KIq62BjvagHghHELEw==","signatures":[{"sig":"MEQCIHFV+KSWXHT+zoo71a6MkKFdNf6fK4/nKvTM3Tz8ZIkNAiAJ5hsUrpRd4iFdNU5M8jvsxOojg21mShd27rG/ltMuMw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":592845,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgG6K8CRA9TVsSAnZWagAAa+IQAI2Bn5P4rR+Vs+sTNE96\nZ61oT91pBEW21J90znduQ3UP5hGwOo+gbckrgzE/3y1tmePHnlYkxg4sDpkL\nn+iY20o/MRCV9E60p5HPWWw5Eb+ha0TyoV866X6Tf6zl387qiQAjFtB2BxXl\n9iHj4UUVAcc75IY+X2uHIPYREeG2KPNevZKTrqhZqNgBhSJRTCgxBrhOH19m\nBAE5LBXrdiEZJEZ3VhvmaekXQysulxWUcOGOwAhmvlcyBrVhAauPRERM20FW\nzdcoLFh02gCUF/F+wI6yhyeT6fVuFhr077DcFdYJjDK1rheOIC8+5TVD72Rc\nLGRkNnp5eUJH6hrwr9g4BnU0qJYE5mgdz5xSJbnP95YdWaVTmv9II+K6fO66\nPu3DLUS+Hk+nrbOYDdAmnTigPtLnN7Rcfmj1V6jlCeQ3UsYUvbqbdMYFgiOP\nAeCBZFAQBI6dSqOlXQxGPTrLj/DRdo3ZV18/Eyj2WpgWH4QdswKvB/JswsqN\nnlaCLZA5h2YM+SOAFlPsUt9QJnyNoZ6hij7tmiWPnemWM7uCWimwZpeBGrqI\nsUhIpoZ9OROqvIXLULkY2iUuFTd1/JOEo4CMd55c4mAsORLDCQBF3yqhvkMv\nKUjibY8mI4GxeBvphFN86nz0eM6hIpi/9t0LQUJRvNLcajc+Kx6lrhAKtfe3\n96/u\r\n=XCky\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2400edc95b4dcdcb683a6b5da18e2db86d21661a","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.58","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.54","@balena/jellyfish-logger":"^1.0.1","@balena/jellyfish-metrics":"^0.1.56","@balena/jellyfish-environment":"^2.4.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.94","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.3.3-renovate-non-major-2400edc95b4dcdcb683a6b5da18e2db86d21661a_1612423867789_0.6296394165037726","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.3.3-bump-non-major-9c8bade9a893c27d2bdcf3d465dda97201d54c18":{"name":"@balena/jellyfish-core","version":"1.3.3-bump-non-major-9c8bade9a893c27d2bdcf3d465dda97201d54c18","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.3.3-bump-non-major-9c8bade9a893c27d2bdcf3d465dda97201d54c18","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d35ebcc7ccb7a1827d93f3647bfbbb10f2883d74","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.3.3-bump-non-major-9c8bade9a893c27d2bdcf3d465dda97201d54c18.tgz","fileCount":96,"integrity":"sha512-bOb8op0XQK0OOfCarRH4muDs6VcijWRMt1153A/H25a6XoRbsY+x8GYld6h5uuQEwU9OWczVWjxkZ9nOZAJYFQ==","signatures":[{"sig":"MEQCIBS6tKnWgu+xS0LyYNBmB6TSHq5kuZKvFOzxjCY5q29wAiAV6Ju7Z+6GSKz8GEdIX8NM6rKZUSB1gt/2McqdtU2Wsg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":592845,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgG6MoCRA9TVsSAnZWagAAxl0P/ReIKxslRDVCarh8MxA4\nBJ586mhKICYGwpmcZQBl/eW8gjdBXrh/udFvtWcBMkkHPulGeYakHUQMB2Dm\n6oDJl27bR0mjZPAVPNZgPGfBrZvEdoThI/60y/s97ozgQjXbZDI21h/zmRia\n4wN/wQbFB35IgFH6etDwCRyjWgJZ9j6sF80ah/Gt5OsMgNCZ/BpW3wVBQ4v1\nURSYVWKGvLEXvPmWdD6pm0O66cL/xXxrOU768t3Or9yiG60uIcyOaWoey+PP\njpZDlkzffum+87+QRAqIbeN5BnXT9OqGKKLqKRRX53uHdMF6tcQ/Kfd1Pkcd\nPkKSuvnlpOfIcch3O2xKMznNhXv86PvJ/fIXaiLRIY4Jd+jrCCy3Dt94FXu/\ncZBHi53zRSbzQP78clHqCY6MXSIALKB8s1+M5IU6qaaYCfMgj+0KNUPGB4Ug\noC9znnupE5RdqX5/3pBKzzNHBgoD9G0+QsVhqtc2RAU+XuL37CAXcH6Pd0rH\nJx2EDJJsBKVmwnTQ+6hh64SQXepJFqk8EmN5Iij2WPZJ4KKiEN/nSCdmsAkV\nVQq1uKMxyn7jj59FVWq/H2I5wihdlTklO00boPINWHnCE2lufVht9FONPY7r\nvl/krm58flpqH/l6MnSFXso7OhJ+EI6WBWBOOeNTKq2GnQgwAB7uKzNqX6ar\ncaLn\r\n=zXHN\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"9c8bade9a893c27d2bdcf3d465dda97201d54c18","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.58","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.54","@balena/jellyfish-logger":"^1.0.1","@balena/jellyfish-metrics":"^0.1.56","@balena/jellyfish-environment":"^2.4.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.94","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.3.3-bump-non-major-9c8bade9a893c27d2bdcf3d465dda97201d54c18_1612423975844_0.5618554819893196","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.3.3-bump-non-major-a923a53c7806a882e7e3dcd754564d1a06b31434":{"name":"@balena/jellyfish-core","version":"1.3.3-bump-non-major-a923a53c7806a882e7e3dcd754564d1a06b31434","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.3.3-bump-non-major-a923a53c7806a882e7e3dcd754564d1a06b31434","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4f37251898b7b11a6174187d89be4b7d317f43a8","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.3.3-bump-non-major-a923a53c7806a882e7e3dcd754564d1a06b31434.tgz","fileCount":96,"integrity":"sha512-+hKulBs6TkR3X9Hy6DOTbTz+W19qeOfQhV9XfY9Tv3szdMh90FD17T9jqBja+OoXczHvJGaGkj0XHFCFMJfJIQ==","signatures":[{"sig":"MEQCIAx2lbfKI7BjAuxo/YUGeGWwykjJWB7PO+qUag1nBiKxAiBoGiBMDK0Inubru/SpqttD8P6fGAve2egytC93hVzyVw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":592845,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgG6WECRA9TVsSAnZWagAAJ5QP/2fSmkPjUOKYZgRg5mvv\n0/yojyUzv2Yvysf+JZIdT6u7pGWWpuuqkKRy53B6zFz3iSOGvYeTxFZUdw7E\nalBSCIUHZRKV1WpEerdU8p6tlrNLO77HAimQGJfCjsMbAskDRpLUrdetOwjp\n6AQsdt3iTYl02mDWy/CHcBRhCP1Z0KPrX0VTbqBARBns66DH7RizOXC+E8/f\n0z3nLSeKNRzvqV7ipfeWxJpgj+EBPceNTzrRexYDhN5oSQo8D9JaGDjZgz4o\nGJ4Wsh/p4aoTk6sHwEOoycS3d2gOOgXVlsA/xuTfN412vR2q1LkbYKLsR5U+\nEspwYR0PWv+6S+nEj2NIg2HcVQ1Ev/JuNXJo+NiZVRXC9RHuWRDgaaybH4g8\nMrpqwxS3tHkZskps5vcFO7HhVTxE9083dApr2EJDmY257bunh5WLOfJ+DXDb\nvYdC9q9TrGBpqkyw42H6leCdjCLZO1RePruHP7e5ff901E+4t2o3YAkIniYa\nkcIIYFfG26F46p5oH7y6jagpJ/40UUlUUjTmwGYbRJB2XYxs5su8G3Pb2KAL\nsVtXiPK5kP+jdRJiBZxOjXrhSLc3D4DJyTgFYvInYcpy4xr6lUz7HO8lUnkc\nvoM53+atB8CrUzLj03J2rwPCqkHEW37LYN9S1/GdhvI1vBiWwbzFIJuQE821\nyum1\r\n=Z/CQ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a923a53c7806a882e7e3dcd754564d1a06b31434","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.58","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.54","@balena/jellyfish-logger":"^1.0.1","@balena/jellyfish-metrics":"^0.1.56","@balena/jellyfish-environment":"^2.4.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.94","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.3.3-bump-non-major-a923a53c7806a882e7e3dcd754564d1a06b31434_1612424580197_0.8079686382710773","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.3.3-set-npm-version-763359197298fa7b500876f0a66dc910bbcfd4c0":{"name":"@balena/jellyfish-core","version":"1.3.3-set-npm-version-763359197298fa7b500876f0a66dc910bbcfd4c0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.3.3-set-npm-version-763359197298fa7b500876f0a66dc910bbcfd4c0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9a6c0af8a81d2ab584c74ce311538ed59fcdb30b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.3.3-set-npm-version-763359197298fa7b500876f0a66dc910bbcfd4c0.tgz","fileCount":96,"integrity":"sha512-Rai5aEMx4PGJs0V0dWFf68QTkepjQJQaL/yRjalnfKKH4Td/T03NVWOj9lDQ+REWnfkEEcrjInUJY4I5b7SDxg==","signatures":[{"sig":"MEYCIQDlKi5Fob5ooblCLxr5hl08BwRI7YFSxfnPH6mqyNJMpAIhAL1mY1YwIM2bw6Zk3qkCwSZRW3NK1yYd9q95CaTyF9nW","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":592876,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgG65gCRA9TVsSAnZWagAAE4sP/0fYFHks9w5pbTVsP5Cn\nNvrRwEAXSWuOV4zfbXOfpw0JKsdytzeaqUfhbLjKL4jhy/QptkUVkiafu0c2\nBTaMzDefPwN03Cqxp0Lknd+I92shrL9tUFuyO2I3gAgnqnGqUyp4YnEalntU\nRlq8no0eSPY73F9Ig66OGowRLxf2z1xhCUzitI+n/AFgi3KT25AI7Jw5C5yF\n5OKKEwRwD/gd5p6R7lrICrh+pvZf3RcwsilFYacEnfsl4Y30l/60oqvBGg0P\nghefsIXCPCazhp2RVRmCAkCfEo3D0kZ4rvmllsu/w2sFIbA7Oi/dYECV+r3i\ngIFtKLKul57is9IqwAzKoDgQJkj6NmSVMqD5SM/hr1YuhSFNE8Ofk3f8Vtmt\n3c5qLTYpHYjyMNwvVZ7caH5HAR1v4mLVA5K8qlefh9PkGLUgzRiY9vHOOUkO\nNdHiIGZNlZa6AWwYq3am/CxNdw/jZdp5nH4Y/dNpmfrN8Uqg8K5zSRc5INBK\nk1toFwQZqTkgTHYt/RhHtwC+zvT1zatpcWsificEnkkK/X5ABPofiV2ywj6Q\nfabQ+dvaIqEaw2AIJnAfB0mvkDM7vRM90VxXXpfSM9u9/914w/1h0ki9xSaM\nUvcV8vYGIW2D/H4QP+SoavisCBEJu8FwqyXKP9quBM7UBfMxGza06lE1uYva\nw6Z6\r\n=uAZ1\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"763359197298fa7b500876f0a66dc910bbcfd4c0","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.57","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.53","@balena/jellyfish-logger":"^1.0.1","@balena/jellyfish-metrics":"^0.1.55","@balena/jellyfish-environment":"^2.4.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.94","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.3.3-set-npm-version-763359197298fa7b500876f0a66dc910bbcfd4c0_1612426847918_0.42263926154304343","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.0-5508-pre-build-versions-cc7d5b28581985d51b041e8a9c68178aa6297272":{"name":"@balena/jellyfish-core","version":"1.2.0-5508-pre-build-versions-cc7d5b28581985d51b041e8a9c68178aa6297272","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.0-5508-pre-build-versions-cc7d5b28581985d51b041e8a9c68178aa6297272","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"209ae31459b8d6c1d96dc76802f5c8885894939b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.0-5508-pre-build-versions-cc7d5b28581985d51b041e8a9c68178aa6297272.tgz","fileCount":95,"integrity":"sha512-WTZFac3AcIIuggNKil0hQ5xvVypm0vcQy2bh+aR7HiDkaUK4uT8/zoQ4VF3Gcl5uzMxPuulfCSa+DiVrKSvqEA==","signatures":[{"sig":"MEYCIQDdnMJvbTrasmDBKhOb5ozqcdEjmoPA1rzPSyLxRZRqIgIhANyymue1YHzHVDdXEIuagb+54eOOCJm+yUKYGgMA7NO8","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":593617,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgG7ZSCRA9TVsSAnZWagAA/H4P/2/6gZRSNtfydp5QiNuF\nbdm7/akenfLBs3dzjcyDzvfl3+POAuyapOKpECYZQbKfLAHJQf7f5jJSPM/C\n4ErZ9Yet4ofuC9Qacw1HayTVgDOfMl3Mfpy6ZzqP5TN0V/wk+Uz4R7z2Zy6H\nAaw1Qv48J3XsSF+xmOsNmrPdIzwfQM8XXyn7ECTD/dAnRBMSbHUR9gA4lei/\n9PUXRrXHxLGwcDPUHnSWTOxH+nAJ9BubKtgcMQIje89O6028ikbbnAHYuif+\n4/mIKdIFGiuY1rtGvNM+dPkLKbx6VKGUINgjsv+TqSnfsRbZhCDfetyJLB/S\nGY6AujdkRya+bnlDTRNieD8CfxXClZZFrqNoJcDZ2bsRerBApWO4DW2ilfvu\nc/8JpFPOxC64/GPn4hWlEfh1bIu3F86Suy/aJCjVqRb/xs54Y1W+XDeL5C34\n8/aBRZ0a4GM2HUmsq6havrZMiFgLzMfWRkgKBZGY+eIZ5d93XAsn0SyRL2ci\nqCGHMpiuI45XGR1hNlTExes8EZrhJelBCC6Z/jSMZthpnlVA4KLby96LetRD\niyBj9lzWUPsAsYBdUHaY9b+iSizxjRz7IHiHkfJ0z4JAgWCMA1qKW6OST2yz\niKy9qxMQCK4sE6hdGl8hUOT4wltaI9XzHCJVy9EVRziw2vqJx00JfzotYkwT\nA+r4\r\n=W4/E\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"cc7d5b28581985d51b041e8a9c68178aa6297272","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.207","@balena/jellyfish-metrics":"0.1.21","@balena/jellyfish-environment":"^2.3.25"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.80","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.0-5508-pre-build-versions-cc7d5b28581985d51b041e8a9c68178aa6297272_1612428882186_0.7902899514687654","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.3.3":{"name":"@balena/jellyfish-core","version":"1.3.3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.3.3","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"59ba2246e8ee5fa9ad2acd4764a59fabcd3bedcc","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.3.3.tgz","fileCount":96,"integrity":"sha512-ulaw6wU5P+eqTsl9R8Jq4aSn5XSSDTv1XExYf4dI82FO7a/blrCLJQ+6BnSm6XB8wLDlgTScEmCy4uRSVouXZg==","signatures":[{"sig":"MEYCIQDCAqdHZDdttEE3pjIlGjIP7ugPNM8gr+L9qqgFy1YzdAIhALcDzGe45zfmz8qxkz9i92EsR66pIbOGFq9H0EnnEt8w","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":592819,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgG8HUCRA9TVsSAnZWagAABVAQAIpXkXQLp4fBqHYQFIvI\nib6PUp5wdjQgBvlXnzTbjLt3norykwcHhKNpqfK3pGrn/DA/r6Ku2H/0NTqS\nrLKMVUiJ3QTlA4+bMqh6+QXl7TzNbV0ZRSKpggBCbMTtSGDDhiSeoHIfp/Ra\ncTZtrkD537har4F15TOO19/NQxkiH639kzY5iXbJ/uxRyUC4orYFXanB5tEC\nYTHhB9wV0p9SygyDQjYdRvziX7M/y5v92mFIyvleUUydReOPGL1d8ouJqqfK\n9FfaGTv+1eJJAIDhjJ3VCQPrSaJ/zKXVAbHc+fTDNisKz7ZhQRidR2JEg12a\nEbW7ml4c1VJHsPvIIR+9A2tOOooHCs5aqUnkQ+x47N9Mhuzj4N5kaZx+y0Ey\nQLPBfZ71hVNJb/4IfD2rkp/R8ggtm1RI4eYYuQb/7PMQl0GkGs4IoF7sjPgZ\n62b3jaGCK6RuN90EBbV+WznEf0TR3U74WQWl8IFx3sEs7j0WAxeGWkDVZN3m\nN6kKwssdJmCdrYclnRGPxZQp8coKy8eBc0A8BpgYkhwIrKPqBIz51UapCBmL\nFx2Fxrb4U4KFEBcGSYHLUDVU1TOY35HafBeNW0cpsDjmqHplXRw50TtqdSDA\nAVQLab46kCpp3W1ywkcSeR+LK6l9cWqAVUFyb5q/u0vk5OuumMmRePq2CKIC\n0feR\r\n=xFoH\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"5cb5dd2d6307f3640deb5d7f874967d895f8d8ff","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.57","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.53","@balena/jellyfish-logger":"^1.0.1","@balena/jellyfish-metrics":"^0.1.55","@balena/jellyfish-environment":"^2.4.1"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.94","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.3.3_1612431827895_0.8280927225872707","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.3.4-renovate-non-major-cca9f09f472f813174a770f218513ab99dd1ae02":{"name":"@balena/jellyfish-core","version":"1.3.4-renovate-non-major-cca9f09f472f813174a770f218513ab99dd1ae02","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.3.4-renovate-non-major-cca9f09f472f813174a770f218513ab99dd1ae02","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0af05b31840ba1a9e441ce128073a5c329902090","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.3.4-renovate-non-major-cca9f09f472f813174a770f218513ab99dd1ae02.tgz","fileCount":96,"integrity":"sha512-XmWaCCEmAVJvmLSZFIQfJA/m+eJVZAsOT/8SkhAXj0NIzVrOEZSPjwggz9DXvSIXGP1Y1Pe1YJWV0g2z361ksQ==","signatures":[{"sig":"MEUCICfXih0D1cZqyj3zJUu+ObKdUW2kEBMpMYSkCoRL2gvLAiEAgIjPyRgfveDWTSqo1mHl6tvPO9hyYKoAV1xouwEZEDc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":592944,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgG8KACRA9TVsSAnZWagAAi+AP/jBIKRLx66VE3nvAVJ/Q\nDl7gOL00EZ6Gm3+IfBqmZ6Q/esQ1CgGrKS7+UnKDnmWxOGMvhMYWgwRHoaNy\nbiHPuI8kdcXNpmpGlE17c0LAPfNis5qqUhbWBAKXbc1EDi7Um2Ys+7fSp1he\nMCzqwSFIDSb3h/R+vntN+sdfAJcpCsfn9bN9GYFkVN87ZBs4Ffuj+UzjKnjn\n0gDXEeI2m7gnlt6V2YFuQ3HIE61ncP5Xg8yT8tBlC5vHYQeDQATHibt2eZE0\nj7TqJ/suY0t0TH4YN4bC96otUGeXYHY/BNaWSWKpr2RmW42ZMSomQ7UyImE+\n555XcFycfSDR3Hw1lBNaiz0IePqIqgoPf1ReW5wbBY0Z1TBRUFVDftrothZz\nk5PKs9mW12K0JpfgJvB3jQVNfx4UD3ZGUg/55xCJWMAQvH1WGyFOUqbFR5iE\nb1PJ8DNxCEvNd3T6XMHKNNs7S9sgVjObOgZ2FEBgfCZpPzRnq53REW/K+lKn\nSiTr7Iks6wDjVMXQW9OrD0Y3pN4O8DKK8YiTsszXXvFyy/qFYJhCcX/ByON5\nHkXlw95XlV54ijz0e+aO23SsB/MtUOBwvv6eOcR/yy7P0cZ+5ChGw8LomJvk\nZFQEeHy9ZF4FeJ53oKLzGoIRYk5ZAt0dISFAD3kx6E2VUeOH4wZ2K63+ypSD\nWnYE\r\n=ffib\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"cca9f09f472f813174a770f218513ab99dd1ae02","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.58","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.54","@balena/jellyfish-logger":"^1.0.1","@balena/jellyfish-metrics":"^0.1.56","@balena/jellyfish-environment":"^2.4.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.94","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.3.4-renovate-non-major-cca9f09f472f813174a770f218513ab99dd1ae02_1612432000496_0.3484407209975433","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.3.4":{"name":"@balena/jellyfish-core","version":"1.3.4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.3.4","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6a6754ab194aafca04d1fb22ce7e1a7a2ba6f763","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.3.4.tgz","fileCount":96,"integrity":"sha512-93K03y7P30wGuMHmvQkN3umyMXn2zOfXe8b33JxiF9wI9FLtvLLihVt/0mNhECE3zIfWT1ftUwyiLBEDEmdSDA==","signatures":[{"sig":"MEUCIQDMoiF6DcKnNBQbbt4gtNnjdoxa7Ebw1TBh2sOMY6FfjgIgAtJsgclj6PXBVvOGk+1LSe4bNK95+rMRYs6en+08vpY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":592884,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgG8SHCRA9TVsSAnZWagAAJwYP/3LUy2JBDCHQutntIZZg\n0avQrVCrXthNjKQVCXBB3MHUoPV0T2F0FYZ1kRq3C1GGuL7YvBjCX1O/6MKe\nCzo1qCtqzd8WllXpzR+vKKrTlCjsn6zs/kLBmUAqjnQkt6ytul96bj8lnAh4\nMYbfjD/VKuUL84u2EytF3ARfHr1cMrMkC8mY8iq/Sjub8WYnQomUXZDBrWSZ\nIkB9FiiFa48nZjeZAZNLwUXMciC7vY/od3KDathx2FSwBKO+71udCnboeXs0\nXasRyV/J58JD43lE8iaDbV8rKYH2ATE5PEtgPWmE73lou0O8CG9Oj6ZSsElz\nSWIKSPfYkMbqF/bAHJT7F4YICg2H/vA2wqwBFO5IRfMMhtdltCB9dzaoVve9\nstjHh+palfzCrUmW6QtbgR7ztjJlqC5qDGd/79YWYDAH0LIa4FR1KdTKC7Eu\nol+BzIuBQV6wqo7U64liaQ0Ja6eNigcMsRB1CeZp8USZACuBRGvyAbcJzl0M\nDqOlZqKoaLF0ayBrx6q4eoc4mDZuhLtMNxT4zyLDde9/FiBOWq+nK8Rvkl+y\n/T2pkq6LFoooQtoAhkCJhHT0FKETfGO6vawRZc28jcmsuPSAhDMhE2CQfeW7\nyw/+F5Os0/tuaPgBFtDs4whjv4pScVlXe+PROIq6Yl3Np2397BcEi4AOHdUA\nA4GM\r\n=VdQC\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"64832a9090816b51ffd12366ee36dc431a1d872d","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.58","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.54","@balena/jellyfish-logger":"^1.0.1","@balena/jellyfish-metrics":"^0.1.56","@balena/jellyfish-environment":"^2.4.1"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.94","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.3.4_1612432518861_0.5108430560561124","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.3.5-renovate-non-major-19586aec8dfdd6baa155d3d6b982cddf07b5411e":{"name":"@balena/jellyfish-core","version":"1.3.5-renovate-non-major-19586aec8dfdd6baa155d3d6b982cddf07b5411e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.3.5-renovate-non-major-19586aec8dfdd6baa155d3d6b982cddf07b5411e","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6d66570641ed05fb3b0c5f2812066b0a0000c126","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.3.5-renovate-non-major-19586aec8dfdd6baa155d3d6b982cddf07b5411e.tgz","fileCount":96,"integrity":"sha512-Y0POr/Ip9jiYAWEefqR+4MPb/z3asqr5CPrzbbdwAd9/5IRZOcvbD1LgQBlfX1/qBm1nwvWy7cvRnMXcVK8WzA==","signatures":[{"sig":"MEQCIC/8ovRqS2R24GHULq02b/foC0ERQCWhBPDU4tMTo0cgAiBmvhhNru0nPAi/BDzLjXkwVVgnj1EKvm6oWpQbFf74sA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":593009,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgHDFwCRA9TVsSAnZWagAAJEIP/3Y3Y5x5t6VqLNs4vg+G\nrocoumB91jnLxPWJmX36MLL6MFlrciaB8JEsgwdSI/JucwUFM/djAr6WLDkx\na2mFkRntI+52wlzJjbk51THRrYF/LooV+xclc/0OKdcT9Bikcm5ckuAfWsip\nNFVsPPacN9T6Rv9ek8o9jBH8SF6DCXMndl6TB9WH4N0kbRfJvQYC1q10BKOl\nzXdVQKbLNdykIghCwFVnbVGsjzd4mk4Y9V4d8pArP13p8D47uVkFMTQqRUoh\ngCeQDrw1a4SLok51WcbDPu3LMviyvFMBnKJYYmdSrzwlqd4zewvyUOXW8Y2y\nqZkHGqNAHjdxWpCJDX1q8V2eVlSdqTlEzDrvRyKLH2PSMWu7RoVOS5SEASss\nXF8Cg6o9s5KZ0y1IlsuFRImV77JBPk3vEcnPHJ0iagwWwJ4lMC/V+W4KtvLK\nJmIY1at3J8cmXQm4GKcCdrgt6iQ82BENcpyU33u0UVFqKss1mvj80sCrUZrn\nLVpeKxmccqvg6GgKvMHGs0xNSSCafJAibrcBOe9ZuT+is5lHaTtco6ZWosVk\nkTdligyyTnj9/qLlE82WgBKGqN4coxp8uWQ4qIunGZcxQO3Ird8U382nSiZr\n5zmarqE1ijIerFb/cAs1PAgTTYbOcikdJ+hSPW8ggz0qVIP7qckMrnj5RvVB\nyASo\r\n=4IKt\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"19586aec8dfdd6baa155d3d6b982cddf07b5411e","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.58","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.54","@balena/jellyfish-logger":"^1.0.2","@balena/jellyfish-metrics":"^0.1.57","@balena/jellyfish-environment":"^2.4.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.94","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.3.5-renovate-non-major-19586aec8dfdd6baa155d3d6b982cddf07b5411e_1612460399713_0.7585054676568561","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.3.5":{"name":"@balena/jellyfish-core","version":"1.3.5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.3.5","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"867f3b430d7e74d6cf64a16d5abb9a58487c39b1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.3.5.tgz","fileCount":96,"integrity":"sha512-lmsBYc7F2vVtLq7TeQ09B2FKRzMFSk5mt16Gi1UHhQfb6IHS18sbTOfMnjwsv0kN7bk+qy+LRRvh/On4H8VUAg==","signatures":[{"sig":"MEUCIQD+/Y382qWU2rLjde6Nau7b6vdug/fSYs4wfanOsCKi5AIgLD0m7UUGTeLi3DpmqiAgjJthrlmZmyD19Wqkbi6KWgk=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":592949,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgHDJsCRA9TVsSAnZWagAADQ8P/jlc1/afi2F0tIE2wwjj\ncxtHk8GuQQrwEfLW7nyXJY5IGFBET3pAN44bKTDoe86oq0HybvfMGFvvYlPj\nIuEkGojs5EK0Wa6sspiuIeMHczKVRls5ykI2uTycxrzzqjFLCMSCtG1MUK0f\nGouuk38p/93/h7u7t3YpHoFWhRV7/B8o2+SMDN4w5arxiqou6s4feZ4LXMHY\nwEcrTPC+NpIdQOPJl8BsnJMOX2s3C48TxPLjBgfuisUHM1yuGGc8/kzbUBns\nu69FITEoNPV1eVp75fl2TYh+sb8pi7aQvujAumTOwBS4hx+ZcCnJwD3nrIjO\neSgGy7zG82R0nS2NrfNrAVZaig9UfwvECejxYxAI430OXHhfnT92JtDL3ez4\n79PVE0Dg5S2NYhCwhf2IT4+ih+tgEfrAVmniAvXucE6rzOvMnmqe8dPpANV6\n8ALQDqb4fRZOSa4nyr3CIlSqE4/gXal/8+jMFnLZldzWee8mBw7WUUt7JvNp\nq1C/AhxPAvjg/LK/douUDTriu1Bk/ADitl+I2b68hz7prf+DSSeFT1oPolSe\ntyxqtFJ5CkVih3AosX/ixaHQgtV2VkzCqAYE5iAs0+UQcNd30tyK98JKyI4B\nbH6T+UKwo5RvfAx6kMseT/tKM/yLVeyg+NE4Gv86CyRnMJRdQnHGJdMgKtR7\nBJxW\r\n=iF3C\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f046e056665742262601abe8e3b12010b1a40766","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.58","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.54","@balena/jellyfish-logger":"^1.0.2","@balena/jellyfish-metrics":"^0.1.57","@balena/jellyfish-environment":"^2.4.1"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.94","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.3.5_1612460652145_0.2695741820427895","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.3.6-create-indexes-concurrently-9162a1b7849b13f34472421b4c9111c384e9b269":{"name":"@balena/jellyfish-core","version":"1.3.6-create-indexes-concurrently-9162a1b7849b13f34472421b4c9111c384e9b269","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.3.6-create-indexes-concurrently-9162a1b7849b13f34472421b4c9111c384e9b269","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"67362ddb7cc362b75a593b21820c3a4ebe09a8a4","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.3.6-create-indexes-concurrently-9162a1b7849b13f34472421b4c9111c384e9b269.tgz","fileCount":96,"integrity":"sha512-G3lB08P8arvRdd43OzK1jWdi+aPq42qJIVep/ryteuqkAomaza6FW7tjvDC/gddgyHsKXdObldJqEPW5Y21rxw==","signatures":[{"sig":"MEQCIBVOOrDW5BAQakoRcQyg2aDFtjKI13uNjUj/NdrPjPe1AiAquIVY0q3U3XyUAbrBWj/d2pUQ/C2FiLQrVE5boWrlBg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":593100,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgHHnnCRA9TVsSAnZWagAAoIcP/RyhhHpWGgNDaC4dEHdU\nEXHlVSUKWFxDtVixiCKkbyx9WTvINWpCLsCDLNulDSiGGU6g5NaqFR8tkGjg\nxdGOMg+9Yq97/cQCcAYepL9YLhFRfxQ1KyxZt2NlokoAT6yAbzPRnclsvmSW\ndq94v2+eoTHvKIeGud7cpdg1S+JFekYYL+r5GUoRzgfDFLkpk7zV6Mcc+1xR\n36UoDe4/8/IoyIAEhcS7O0KhhxELQfejfLLlLn2Z+rK9DcevWXg8gwysGyiD\nTcVrXdeXb5q1VVCKt0JuOKSTFR4bQ5bXDkjIwcBasNUze0OMIcRr7Z4+t9I3\nxVMUrYTpayC8C0A12BykREfC3QNCe+t8d9MjutfdWGYYHnLF7fmG/tZgVmD9\n/iokCPcyLOLyDrHaoqOr7FnpXPeuPY/sAAPX0N63w28v3ZR3gX083G0JtmL+\nl6OYSWvg1P6dHhsbP4OM7QubtkjQ5TFG5iAuXaen07nGLEQqYrSRYV8PlkVQ\n3BudPM3aEwj+sqhk0eHJuC0k0z3sm0ONxdX6AVHzXBqqt0Key/dyO+eKQenE\npdw9NZFHIq6FZUZaTDFzbxezBPWD/zRvUIXLU4klEQmEDIQjwT3Ynl8s/y2O\ns5Tmr6iCdfHF4/McqHkHOzZaosfgQr/Un+zbwy9Xfq+QLJ5uTgLvWS2/Du7P\n1YQg\r\n=1Q5S\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"9162a1b7849b13f34472421b4c9111c384e9b269","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.58","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.54","@balena/jellyfish-logger":"^1.0.2","@balena/jellyfish-metrics":"^0.1.57","@balena/jellyfish-environment":"^2.4.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.94","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.3.6-create-indexes-concurrently-9162a1b7849b13f34472421b4c9111c384e9b269_1612478950907_0.33679277332332425","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.3.6":{"name":"@balena/jellyfish-core","version":"1.3.6","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.3.6","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a5dc9788cca050bcd0aeb224fcf38b726e1ec3d9","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.3.6.tgz","fileCount":96,"integrity":"sha512-YpOwjos4jadIzVu/jhaZBHMXNT5+AmdUpantzPPqNieHVvSJTTaLIb8BIEoyAvEcTL3dpoS9RD5mGtCrGq6nSA==","signatures":[{"sig":"MEQCICl/YS4ibqdXSH83la8YicBEyM+z6ZqhtvUxEK1GKP4GAiAmEMlpyhx8e5o1czzsfFsSTnR6MMpmWukVUMqGjah6eA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":593031,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgHHsNCRA9TVsSAnZWagAAyEsQAJ/fpqzF5CjQ91AJiuS6\n6gua2QcZzoQ0MDRex+zl3bxkOFgNsDTvIXUnI6FOlAn7kxQTLKhlHkAi4L7B\nBg47VUPc+ENfSHoyn3/btLv/q9AeEmf5Hm1y9UdmwtHMEAXizA2t3lrlGP8I\nSzbfgjmF11juZx65Wu84CEu0TLrGoNDnMzNttNSj4badwLZlCZchcu0OxURm\nuR/K7YIp25kejvnKHBKOUYa8u+Zz2ZlF8Jbm5lNrmTZ4/AuPwO7pujRnyvTN\nqQHviKx7lo/vZFU3t9kyhWFyAQOexisGE45z0xO8q+20KQUMOKdbCfAetNj+\nZkcd8qNWjUmVsUAsvD0jJ8D7jnRo1LnH3BeANEeChsGz1iB6gNRMKh77Pyf6\nA37cFOAnUsdLetpiv9slKX4XLbi8Yh0lbGF3M3ux2+gtMpuQLOrsNq87Xg2D\niHMfuHRBTD5TO8emal/d92m4PINGWdASJWMeAmK7Vg1jMDA67abK/Fgzoh1j\n3PBR5TJqZjwsOxPCtwvLhoYT7nksJRPXyYrj/v9HEH+Oad+iaJoGyPD6/smS\nP1CQsVF6PPIacGnHQ19tvQEth34q3Z888P2/IU83z49AQnNAjWUU6GG+wBRd\nhylhPqqo6InFfXzM64RZzhWqgNRG4cKk2W850JOVQcqf4TwbEuwK2/aG1y8v\nXAeO\r\n=Kpq1\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"994e166d52b5df261f014f4d9fe90d210de63810","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.58","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.54","@balena/jellyfish-logger":"^1.0.2","@balena/jellyfish-metrics":"^0.1.57","@balena/jellyfish-environment":"^2.4.1"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.94","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.3.6_1612479245275_0.797147366352525","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.0-add-session-scope-a5607e7be92779875530545e29479322e20eff62":{"name":"@balena/jellyfish-core","version":"1.4.0-add-session-scope-a5607e7be92779875530545e29479322e20eff62","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.0-add-session-scope-a5607e7be92779875530545e29479322e20eff62","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"263a8d776d03e2b5c56835e9da15fad84da2b2d9","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.0-add-session-scope-a5607e7be92779875530545e29479322e20eff62.tgz","fileCount":96,"integrity":"sha512-4bQ1YQttDOAPQ6bnNeKeW4tx2RjnKzNJViJuTEpYbX4bl3ByWy2cQoyBCk/OtwIfv4/wPw+NtQIl3eK/IZkBFw==","signatures":[{"sig":"MEYCIQCouo5ZZAqS8IvxmGDF2UHH22IHG65fpyALHvD1kbvHjQIhAJTCMKj8aKFRxCe0kx0EzghH/5X8tAfkF4pUVEGTmfFz","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":595181,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgHI79CRA9TVsSAnZWagAAURcP/im4LPae/PycfJo6q1AP\nKbHGy9c2b7bGzg//S9OMnAfZ1MHcM6eQP39R55tJUU9exR4oVTzZZXr8AofZ\nalWxUdR7u1Tp4KkEaTmm/Na4CmtwSmMWVhMgO7AoX3/KNA6J9QYmwM1zbfHT\nTZ8/0Ejlk6X35QO0eEtS/qhOxKqdalwOAXuonQksVTsUTH2SkqbQguA/SHov\nOStRCVo0CxHDiu9pLe0EebXhX5VbF5w8QkL0en0yr8yhrFOoK21NOotelR3v\nXqdBaBbqEAtrOQrMGo6YVDryHL6dqBhIit/R72y8GTupYPutuIuJdN7RTh1k\naUQpYacKJ/taNdeAV+YaOsGTv+hVlZnPGlZnbPfIEsFzAOZUWuS6VQAyvXYg\nzuFWd00loBYj4Qh11B9cvIQdbAkOucmynTlCqYZYDHMeQwAHx50xWPFXwSst\nzdRxlsbJuOugmdF8jRTb9ODlbl/SC7wPNYvIa/RGJZ04n+MW9Z2IXDFfV7O/\nLg7uE608x8oR38IYQC/bbx6OjXFAzHU3DdEQYRsePP2deaBz723IwGnrNKUr\nVjv8AUUeF1sLTU4nVR0xGfTzowUm/zJR6MN+94eyJ1O0wlvJTj9LqPEN9k+K\nc7jMcIJ0+LK0zGq9FcwRjij4QVee2u8yGoejri4q0OmYZOpUCRFdKPSuLWnz\n5Aih\r\n=xwhH\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a5607e7be92779875530545e29479322e20eff62","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.58","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.54","@balena/jellyfish-logger":"^1.0.2","@balena/jellyfish-metrics":"^0.1.57","@balena/jellyfish-environment":"^2.4.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.94","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.0-add-session-scope-a5607e7be92779875530545e29479322e20eff62_1612484348662_0.47075971218805845","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.3.7-updated-at-timestamp-index-91315f94f3e305da9e5919c2cc7d513cb632fcee":{"name":"@balena/jellyfish-core","version":"1.3.7-updated-at-timestamp-index-91315f94f3e305da9e5919c2cc7d513cb632fcee","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.3.7-updated-at-timestamp-index-91315f94f3e305da9e5919c2cc7d513cb632fcee","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e644cdca5227a392e1373aca53b1a92fc497835e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.3.7-updated-at-timestamp-index-91315f94f3e305da9e5919c2cc7d513cb632fcee.tgz","fileCount":96,"integrity":"sha512-vGLHQ66pgGYUM28jGILwrmJ4aJvFcCl7A2Lb96RoQmSEOWlakSESbRMU3EiLiT3Clm//CL0cNLn3SnHyQ5u7Fg==","signatures":[{"sig":"MEUCIQDnxOVHB6uticCMgv96AGJTih3R0+YFSV29szmRn1i4IAIgb6U8qduqmjSNUN+i9cr6CEFdF9yWp0O1N+O7HRtZP+Q=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":593232,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgHJA3CRA9TVsSAnZWagAAavYP/Az1ZyA10CncU1VAf3h5\nEcDqRpeD0dC/BZecjD6pHcejUmcirwfI1WlpO1bMM65xBuReUVYUjJOvnNTU\nG9Mrxlc20vmHvxRa6lda2UJBzQaP0T/0zEdaTOzixTHy27T5IfLoM3FMPvya\nMQmYmZ8o3bn0F9gmp5Dlp2GvJ6/BkPPo5J4nhAN9Cc+zjH5eNfQ/dIcl9ri4\nn0URvXJbI7pptwS7+ZmKx3zi2S3mqibQBwfwF1+cAehhlxYZB4H+DHxWsASn\nXHcjsKrUgkL3FLBkc5X9wl0XEtT2VQZFnTkw/BPBYYC6252CniQdShrgYRXZ\nuUcfoalqteKuZvZA3dne3YzXmscONIVlUR7d8LKuRdmEpjoUjtQ+eSWFprUO\n4iLoAUXsO6c2ENR1nBM6H20+1H3UOejq5Bmf4i5h2GzPso7IUZ6sYpdShqDU\nI21o3tDDk5if2kgbtqMatdTQI2YLDCEP+mslUvH0M9IPtdOaUlsoAP20TkOk\nxoINshDy7XtK2KKm3oK7bUnPSV2+jBTcqM0lpCML6y/3RuHby1VlK29tZtkV\n9fX7ta6G7aXO/KRjErXYGfKVWL9RKGvHJo9wicZwxVMWkMQAtWQ3t8Lktr6u\nuTz24bLKemzu3BMrv7myN2grlE9p6tkHV01UBjhTEyAhC8ugDshx16AomUBP\ncLyp\r\n=fy0Q\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"91315f94f3e305da9e5919c2cc7d513cb632fcee","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.58","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.54","@balena/jellyfish-logger":"^1.0.2","@balena/jellyfish-metrics":"^0.1.57","@balena/jellyfish-environment":"^2.4.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.94","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.3.7-updated-at-timestamp-index-91315f94f3e305da9e5919c2cc7d513cb632fcee_1612484662755_0.9592410773242268","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.3.7-created-at-timestamp-index-0393db2d2f51c9545755c7e0f7637d7d357d0c53":{"name":"@balena/jellyfish-core","version":"1.3.7-created-at-timestamp-index-0393db2d2f51c9545755c7e0f7637d7d357d0c53","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.3.7-created-at-timestamp-index-0393db2d2f51c9545755c7e0f7637d7d357d0c53","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"868f28685e44e85d48110dea5e463db66b57652c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.3.7-created-at-timestamp-index-0393db2d2f51c9545755c7e0f7637d7d357d0c53.tgz","fileCount":96,"integrity":"sha512-74gEBP++3nVBzgCF79Vktoy0o8ScETtqJJZXTq4Ix4c4IXt+YMNsMCYk+vJbV2niK4OJYoCKeX1HBu4necCfXA==","signatures":[{"sig":"MEQCIExm6xM8S0R/g+71A3mYN4eYHvtSHI1Y1bddAUNzwUjtAiB8QSmSQewQDcLqyq4muTpZ8f6mh7HyiZRk+AOYEaqIcw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":593297,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgHJG6CRA9TVsSAnZWagAASIgQAJPIYAbGq/23cDBrvSNo\n/h0lJYTh04JceZKWTUx4UdDc0fxsMgT/6HaB60aa2EowKrNW6hEdDdmHaRGn\nPxIMePiRvNQCTVCVjg99jOLjAq+DhrzbrVWOkmjkQBTFyHg4DA45IHjGBdjP\nj7/H1bbKygUpjNq64P2L9NdfCxqcFUzrsu/8kYiVUULh55bSwBdEydtYz3sH\nnQdmzZs/fU5WS5Oo2iYb4rHkityITZTvoCsUBkP888bK1HoceKrdUMbdKKpz\nEXyYNge+ERLn9FuERWpComsuqXRiZIRJeoXrX3GapGS7EbyJQNiRz+mBdYA7\nq2UHMWNoRnuPSo+nWpES0h2SMtymYIdU+VpG2OMy86dA4h4y5lPZf78UES6h\nhwVt21//gbq1ZgJCucuLsloNFh8ns7nWkDA0WKyMOcVzLamnJ3qSq4ua1inc\nBiKnd+DOFehCNaIhFNQ/KYqEMdeq52AUoShZW0LL4FqtL6IrFZjZRtKzUrSb\nJgu29PU9qkzXzG5Yr4zp8ikS/SzdS13+O6LULwx7KsywA2sl0aJMZRzZ9pbv\nLjoPLlICj7EGGX42XEaqsHVm/D+24mYIwuwGcyMjGvsj8lYT4UAq3sfl9qgm\n/Fe6yf494kMEu0osR32Hr2c67izBylgnut61+90HXEB1q4gmncogUH6xxahU\nS1dv\r\n=EDRj\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0393db2d2f51c9545755c7e0f7637d7d357d0c53","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.58","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.54","@balena/jellyfish-logger":"^1.0.2","@balena/jellyfish-metrics":"^0.1.57","@balena/jellyfish-environment":"^2.4.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.94","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.3.7-created-at-timestamp-index-0393db2d2f51c9545755c7e0f7637d7d357d0c53_1612485049581_0.6679700565020046","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.3.7":{"name":"@balena/jellyfish-core","version":"1.3.7","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.3.7","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0254e2b6aca2251c4a448142737654c2966bdaea","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.3.7.tgz","fileCount":96,"integrity":"sha512-mFd90DWGeKZR+51dq5xG5Z204uA91XHEp0yT9R/+x5DhqT4rL7vEi34bCxqQ4lOZy5HA6OO5U2ROfTiea0PYVQ==","signatures":[{"sig":"MEYCIQCteQqmDXu2BAdnKssV+oKTqSeEIxK4NKjkrHGE0Qmr8wIhALNflQpIjOO/k76fWPheeF+yjYFRZYI8oV6LXjfquycV","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":593164,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgHJWvCRA9TVsSAnZWagAAh4cQAIpf1RlaIpYPxXXxTMy6\nsMRtyzz/2w+JsmgixMcbK3FaB1HeG51l0PbjIHbF9GUyL1bRnCiNhovCtaho\nqrJVPotdRnDMs2uHUufaKT61q9RHpuATqckQV8zDKXwr9YzGCkWypCmb3S9Q\nrq4cOxP2zI7JI8wXH8DTh5+Cp/c2pBZq2GiX/lQMWbfC0dBE8I7UrPMZZNt1\nw2E/lDJMsASmxBOdUSBkqroO1/SWCtSB+AoQGKgsDzMlvUhgyxtTvb+B948E\nRgZMB1Al+5nx6v9eJwLF9loOCpMzzdtqV/IGGKPrIqz1Ly//1A3VsWLBIkpM\n3DdX8dMcFcuzbpJ66hotjSGNyx4LFwGNfNgJHP07ittc06bOqNLPQzkRzN+9\nBJi87s2iEBqdR0YRikQKA+5gadAhEDRkvLwuTFnsm5Vpn5LgMLxjqgdFf8mq\nj1YzZLdGTk2sGCq25NQ+myEBMnlJzIP5xsXaE/txAHBPD/Q20Ze++JNr/Yob\nWZoMSu2SPH0rnhqPfLwhj5P4Un56ZP9NGp3rrexK8jeisEjRApjOxdp91WQ5\nPb61Ot0reMfEikXAUG5cFjt905pU58ZWuvGpEFj7+5kC6a7z60VFHQcHuSDN\n8XtGj6e78/LS1eXaf50/qpbPPEMBLg3d6tNU7/a9CB7mpPxlWsp7lF6nqoKP\n07kb\r\n=o9VL\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6a081bd294189cb65eb6ef1c0ed105fb340fffaf","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.58","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.54","@balena/jellyfish-logger":"^1.0.2","@balena/jellyfish-metrics":"^0.1.57","@balena/jellyfish-environment":"^2.4.1"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.94","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.3.7_1612486062920_0.8835529141221314","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.0-add-session-scope-3fc206addbc441ba817acd667f76a2d8daa99465":{"name":"@balena/jellyfish-core","version":"1.4.0-add-session-scope-3fc206addbc441ba817acd667f76a2d8daa99465","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.0-add-session-scope-3fc206addbc441ba817acd667f76a2d8daa99465","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d65b3a03e52f18bd57280dc89ec7bc696716575a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.0-add-session-scope-3fc206addbc441ba817acd667f76a2d8daa99465.tgz","fileCount":96,"integrity":"sha512-9ENIIrDzfM/pvSVPuLzAcx+3Z8ITtft0EQ1FmVmHoC9yCOi+opLiTaN2htsBrvd5BCUHyfEBVJUDpd8uBZmxIA==","signatures":[{"sig":"MEYCIQD69YRVUmvy7cfNUPKEMQZ52li1iOBY2Ri52imzdfZcCQIhALWVrZODNxl4Toc4lxqocc3i+soceDvsTFBKGr8d0Gk6","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":595462,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgHLm8CRA9TVsSAnZWagAAZEgP/0NCbqADTekxoigcHS9K\n3UpMuZT9Mk1c7WpEwk+1rbQ0m69c26WZLjW7yjpkuqfCRw9Mei2QiJzHkfgV\n+Oc1vWSmmYbfUt/GnauJjksCjHTaRpRt5i/NdS063u0Qlnt7cExbsmnWn5hG\n6qaZuTVBIShVblCqBNmvx/vex2WHyQ8KUdUqvXA4JhDsZxOuC+rql84+LDw7\nK5Q55cXueEwWgNe/px3oeOScinxTYw7HwBNpODMn2bzGBA/8fDIBn3zIkpYV\nQanNM/Jt0t7xVznsgzyMFXf0usSLWj5GzyaX5NnuaELRgjpjw6mkqTwXfOVz\nfnbh/U1dY2zBQhmt0moMAVCvBSaQHEu/2nm97EOnILFVPkvEkOuhF9y2zIDV\nNAnFD9EP+Q2lqS6VxHA+y0ql3+6CXr27/s9tnYn9KAllNtyrytI24OqTjQkg\nB5j49FSU+IyflutNOJZ6VDKDbYfa1sHYWN7sSaHU7YOXFORYE9KDoqvtoHAA\nYgRBtKH7JOWb7uAhN4yfU3TDIJlysge+nf3ZgmnI1HT6TJkAr0M6uZz/hI1l\n/cyjUzEVdOjmc52MTOiMJCO+1L7cZlflOm57Rssu5s10u8LaGuJOb7leWLYw\ncDFAs5mV9WhdxaW2rXRiTcpfFALU3u6UzZbcwwgugVCzWaSVoZ5+AepE0CDu\n1MSO\r\n=zzgB\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"3fc206addbc441ba817acd667f76a2d8daa99465","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.58","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.54","@balena/jellyfish-logger":"^1.0.2","@balena/jellyfish-metrics":"^0.1.57","@balena/jellyfish-environment":"^2.4.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.94","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.0-add-session-scope-3fc206addbc441ba817acd667f76a2d8daa99465_1612495291739_0.6246959248317503","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.0":{"name":"@balena/jellyfish-core","version":"1.4.0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"64596107f422764d5ab273b294860d7753b50b34","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.0.tgz","fileCount":96,"integrity":"sha512-m3BWNpTnwOBhFF9U+3GJ0IIgIa2LyPP/kW+5uzCewOGRk5adQqeY+9DgfKXAOhBFiyHcnfHUsDG1/En4qi9CEQ==","signatures":[{"sig":"MEQCIC+j9wjxIV4XRDAZnyc7Mw+huMMxOeEs5J26ri2Zwyv0AiBf2GGV1r03XQ4kneUyaL/yWgLldbCHiDEr/cXiX5dQ+Q==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":595403,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgHL8GCRA9TVsSAnZWagAAk2wP/3GOIhqUrU3t1fiQTjh7\nnPnu/oLZ+j2ICFtrcXg7IxIphQzD+wg8imwWLHu/oZFG3R3nNcftHFPhdER8\nLt7Hfq+753hjvj8WummgEkvVj/cM5w9JOleen3LJpjlyoXqP5Ha6bpBe7C3W\nNhpFOtrE5zPjQdJ46UHrv5Fs9Jr6QV+esVMcBfx7CcjhaBl4m/t9EmSVTNj0\nFFUSMlvkVcn/4tgsCBEjaKjtNXu5rUeTaqT62gYeH31//WI8AUjsfpBfQDbd\nj1BJHXoIL/dPCNHgPKs/6+Q3A52rOZGAwU8GscnKcAArab8Si9TLvwZbQJn9\ntDQo7AqSkBJcBK5TKIgz52EyeA2F8NbosLy+79m+umhkZMtKQSU7xggqowP1\nc+q72HwE79BKJFlah5tSgh6zCJcQtFxUDMVCPMrbAc3Xxo1Sndj6PGXSTF+F\n7QJBOcg6eAeA2ffwmcGCFcYaZxLRC94j+EsFf6Pek+IteMlYRIRvg9IzgBH1\nGuCTB6fLhBauEKpd4NkK8RSSBQt570qTnV9q2kEl7X1dWno4hx3zv1w80z6/\nWdjKzk7Dl8G6NNNObmeT/KGJKJqC9jLo8RUIod4nacFTGFMWNXObbdH3Htjt\ndpY8TdUEjzAOMr2Vjrk+vk1NggHRbIqi2CRNfQu29WPTfBT1pvk8jitWBq/M\nPDMU\r\n=4FpU\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7846389d86ce154c260f988c229a12ba48b99721","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.58","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.54","@balena/jellyfish-logger":"^1.0.2","@balena/jellyfish-metrics":"^0.1.57","@balena/jellyfish-environment":"^2.4.1"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.94","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.0_1612496646336_0.4076998059026693","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.1-renovate-non-major-c78e58a492986364351519309244583d3bdc52cd":{"name":"@balena/jellyfish-core","version":"1.4.1-renovate-non-major-c78e58a492986364351519309244583d3bdc52cd","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.1-renovate-non-major-c78e58a492986364351519309244583d3bdc52cd","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2865a90fe189cc42236c03d150d90e2013757e22","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.1-renovate-non-major-c78e58a492986364351519309244583d3bdc52cd.tgz","fileCount":96,"integrity":"sha512-U0/YHvX+GYMgjGp5CFj2V5DVpCbHip5I1LVYIlQcbAXZxMJtv1R7Gr1l1daSnrS08zbHRVQmkifBhSJ/NBIn1w==","signatures":[{"sig":"MEUCIGvIkIy5/PaB21iHwSXTbpVnLWUuOmEOywHY2Xwk71/PAiEAo9ZLQArkp6BJ7V6W4iffoIs7et3Ji2S8u9cCHgNRD7w=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":595528,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgHNs6CRA9TVsSAnZWagAA608P/ixZHDY1S2bcGKVSs5K0\nDjXszWYefJrr4rhzGzpscZYmooZad42fH2VtKN+hL1ZR+JYu5/XdklJfveuu\njVw/ZBVNwfKqwV9s6kMrDZ8kQXUiM9erDJw/c34mX/yS5QW91PqqAWamsc5e\nYYA/Bnzpc09ZokdM2NaWflqtQXMfwM1nd6JtDIpJs0jibNt3g7KrTYksXWGS\nBh3Acf8/ohv306igp94X8GsK4Da9ShG0WuxegoL9EuUSGhxnMDvjgsQYGFN7\nVkHv1bORY8zm1cwmH0aI9kN9hE80b5WxIb1PLYworfPWmon7lqZ++NZ/K4gr\nHnYGBk0YzvCgI09CVP3PmckLSEw565YWC+MFRBuy9N4mRW6t1Qq6WPHzOQb/\nAmpMCjTo4RjVbkpYrydi3n5q/vqwz03l3L+n0K8kOlUJydssOuhj+iPnc6LI\nfphtIicKQz3ndaRA9yA1vFmglzNyM72o4yeFeGsTUmdpV8X0XUIr5IyKgEWY\nkck8qczqqAzLVLw8FgAQonv2jy6OB+ZAUE5NUNFnWQgLsDsunpJpyayUYT3u\nJZdAw2nWJhHuaGbTK0ID69f2rpTWe/ZH3HX7JpuhpqdcTeASVi3QyIk6EU+F\nlChgRFYFuN3zl9IJKL6w5QIkE1aH4eucfPw2kOwSIyHYiLSD5pSWYIPRxnqc\na+qu\r\n=1ucP\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c78e58a492986364351519309244583d3bdc52cd","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.59","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.55","@balena/jellyfish-logger":"^1.0.4","@balena/jellyfish-metrics":"^0.1.58","@balena/jellyfish-environment":"^2.4.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.94","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.1-renovate-non-major-c78e58a492986364351519309244583d3bdc52cd_1612503865999_0.46775571699194374","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.1":{"name":"@balena/jellyfish-core","version":"1.4.1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8409c687421cafa61ad31221ccff755b6fb92922","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.1.tgz","fileCount":96,"integrity":"sha512-0dltalIENyzK+T0qD87hy9NEG15zepm6EWACjY5LxIXR+e9L9zXUSnmHvIcsO5BF+7K+yedkg0EMltOutuaB9Q==","signatures":[{"sig":"MEYCIQDu5R8wD03jdyPPVUHzar9rz7R0RkUuLor1O1FfLmDsQQIhAImlLOOJHaKA/ubEJwwzUE/dBp2LdklsXgiKpuCLCuqI","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":595468,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgHN4ACRA9TVsSAnZWagAA1U0P/R3TDSo1NaYmJ4lQ+VW8\nWRL5QJHuUd4rYDo9yk/3eeHGkJNEDplBJ7alD0W8McqxkFjUAPFtso7G53KW\nXZWjRVcYF4Qu+eLg31Z2xI0MoYyR68Isu8oCxK4UL82ja3URIBKBdSY6Tln9\nqGU0MTyOum62eIOKFvmF1TYnhtzY+fP/dZwWn2rn4JstKKGNz79ToRy3OBy9\nuvGv8KRUuwoWRPUh97Tg0ohP/StnyOb4VPP3sfWymRaL2PIQjuga/yfzhtfR\nZWLuh05Wi5Vxyfh48zNu0bEy2pC40dLDH1n5g5Cc4EXoBJnIhx6fVmAD57jY\nAqvGtAaS2PVlT8FTKypX8Z98XVQ05kT6O6uJQUKtpTlLQQAFw/dSLCpBp24C\nfoJwQig89S9ONVpt8t2FEAcAJV0AfImTQhodowbEjQA1FxU35GWTUPlgv6iv\nObEj+pU23RU0FHb652ZQi82EkwzpBoC/Jgj4tiWktjH8CfrOcYA7oZ8GG862\nYkamfZaPzKVWt8MLAyvyRcNEQBeCH925B4LeonpcbQB4LExU3kkWBoaZQMmV\nME1R2UB2TdiBfgNWVkYURIPLIp8NiJHaXVuZk4+sLTlEtQGwxYFoevWPQnCZ\nkVRC5N4Hq6Gm7qpSG9QBH2eGHVwdCA1Mcj4CvmSKKFycseZh5HOs4MhEBX+E\nN42R\r\n=aFGV\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4c82efe44109d4d744ab3a6872dfccbc1e078a6f","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.59","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.55","@balena/jellyfish-logger":"^1.0.4","@balena/jellyfish-metrics":"^0.1.58","@balena/jellyfish-environment":"^2.4.2"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.94","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.1_1612504576374_0.5703502632918516","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.2-renovate-non-major-b25f9101b180c50a6462833b37b721f8fbc18bff":{"name":"@balena/jellyfish-core","version":"1.4.2-renovate-non-major-b25f9101b180c50a6462833b37b721f8fbc18bff","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.2-renovate-non-major-b25f9101b180c50a6462833b37b721f8fbc18bff","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"bc5a02cdc5089dfd9d6a2078f4f6ec30a76590cd","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.2-renovate-non-major-b25f9101b180c50a6462833b37b721f8fbc18bff.tgz","fileCount":96,"integrity":"sha512-gzRVrkbeEE5ichiQ9dRHgNTniZGstfKi27U8f08EKUbSzV5nJhU1iQeuhn8rpwTKfbEd5oVG+FAJcpOXES71Cg==","signatures":[{"sig":"MEQCIEWozB3xMc099blpieI0UstszvufryMzCDak5+2E3OCzAiBnAR+ZlC1bawZbFIMXYgjRLsBMdRxPCSM19H1aklqvmA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":595628,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgHN72CRA9TVsSAnZWagAA9nAP/15DPG0xmcQq723pc3hM\nkdbYgRXW5LWEf0ugwDj5IqQOJiENjasfMbuxBlj3seQOSFR8Srjxckdn6nAr\nnvU4fmdkaDbt875XGNYcvjJ+DN2/4yY3GkrTbW3gMbPT/AH8DFIxO6R0F6rM\n2dDeZZpw4eORYSOKAoNdfmcd95K79f7T9Z1Lra3wVmFxF7Owe6BX3xlgy8/J\nTCI9Tc7xLPTSJ1X87PGy5gAMCrbfvyTj+qvNCi8jC8Qcyvun74/u99+vEOHi\nph9Pjf999naCjEsD0VujkneTze3b30IWct2VzyzRtHcSyh5LqhBszz/cGZmA\niAlhx0tjUVDNqEZNtqY7u84oCpI6T02kcWoxDEd+d223exacCoa5Az4iS7Qq\ngdNuESofEvLO70WIlffWAEuDj2Al/ppxr7fbM3sW33jlnvgVO3ECeqcG3ZXR\nKV6MhCZC3VC+B3I2ZsnGH+2RXVMz0xgHtjkok3pVaOInGctkUgC8pP6Mwa+d\ni2XjXabrzlT/XxZUCoE/UHyFUvH5o78e6VFu3W9WZ9oF+vprl1JrLpDUSCed\n4yvB4QCZ9NetHn/5n1qkgWMXmUyrhFEafyUTkGxS6ECVYPh/P3VJOK0R1SCR\ni2m5xLH/I7XlE07gbHEyJuUkNQ75AfPHDGJedGnGcGbNwFgtLyHHFLdCo94W\nl3rs\r\n=XR/c\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b25f9101b180c50a6462833b37b721f8fbc18bff","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.59","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.55","@balena/jellyfish-logger":"^1.0.4","@balena/jellyfish-metrics":"^0.1.58","@balena/jellyfish-environment":"^2.4.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.95","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.2-renovate-non-major-b25f9101b180c50a6462833b37b721f8fbc18bff_1612504821831_0.8009297793637316","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.2-renovate-non-major-efddf86cf1bb006dcb3a9381ca333c39952047bc":{"name":"@balena/jellyfish-core","version":"1.4.2-renovate-non-major-efddf86cf1bb006dcb3a9381ca333c39952047bc","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.2-renovate-non-major-efddf86cf1bb006dcb3a9381ca333c39952047bc","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f07b66079627ae314a730cacc22ed3a408c1e6c7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.2-renovate-non-major-efddf86cf1bb006dcb3a9381ca333c39952047bc.tgz","fileCount":96,"integrity":"sha512-WhTMha9Rb4ks+eE5dzKOZnB/ccCtBgQh2NbDSgMiXhYUbc366N/iccINPSFzTu9rE2qHQidIVmzu4vi7sAHy6g==","signatures":[{"sig":"MEYCIQDk7F7H6idWdVf2vXiE2YKhuxHcafk2ZY9JIZzDr6ZvpAIhAKzfkX/f3rTNBdvs9FcOSxYhP6S2JSombGuVhuFtNlHF","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":595593,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgHOAaCRA9TVsSAnZWagAALHYQAJMVUJPpbuEuTaVOsVr8\nBzJzDGjHJIMM5sQDXBkERf7xOIG6+sL2ABqoV7cR/ARfNqZgu1oWLAxbeu5T\nGN+b/z7n2ok3CxhSKthVqJUvtMTpqoMP11C9xMdRFq0dpKEdLnv2/6rrl4Fc\n8a5LMqLwWurzw0n9JjuKQJFM9Rpo9TMPPWyLBHCjwwgqUMIiXg8YvspmyfKg\nfRokliC/LPXBOP8o0fGnEbIlHoiN8B/M4UiyT9ir7ajliu0+s8jp/Wz3nIKA\nxrKCeCzDO4rnRdSsMihxtDB1VJrpdxlx+ugZPIBLHcaB6hFb0jpb1TAU1Xe6\n1fGlkl7G6W2xjhWYD9LhXlG89QloWjZ5aGCoT2KlS5jtTQJ526zLelpqP1en\nMwi0rojV4T8iGDp3MD5i29xnRaHBX3ZZe3WZNRbXWR8iyshmOn3eo7/1+Wvm\n3zbu4E+97yW/eVRA3MdoVeqaKe959xu1AGNLytuDPJBzBWpjbdYEfkTZixSD\n+lHPv+71ebj2E2tHJMRgIQWSiCMZQksiqlF26dzyEtl4BweFchpkzY6GHIU6\nEvF5WGgf+cFZ4JcndxUrleL6FIkMSRezGpliT22i7nUKuhxBlohEV3G4kqgH\nE1P0dRG/TbsAqcyDO146jTQUuo+8mRvoEvELxEGqmKLbUBiTg5yCKUzE/rz4\nvUuh\r\n=l76I\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"efddf86cf1bb006dcb3a9381ca333c39952047bc","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.59","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.55","@balena/jellyfish-logger":"^1.0.4","@balena/jellyfish-metrics":"^0.1.59","@balena/jellyfish-environment":"^2.4.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.95","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.2-renovate-non-major-efddf86cf1bb006dcb3a9381ca333c39952047bc_1612505114512_0.78499731421391","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.2":{"name":"@balena/jellyfish-core","version":"1.4.2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.2","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6b9569cfa485a677242527e9a8ae7738156acfea","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.2.tgz","fileCount":96,"integrity":"sha512-k7Zc6FijZ2fu8QiqD53v50cCwVkmUutd7wJnODJi84qvET/nRLtiP4k2xOIhLQL0CuJTyXRgBF63hDy2xxagKg==","signatures":[{"sig":"MEUCIQDOv1jlrauQkpIACCJ8nevnYiH+RFkdKf9rrMwcko+PqgIgd6l8LwasvOzycS+jLpaNBsFd6lWK+P1NRlnEfar+Pw4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":595533,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgHOE7CRA9TVsSAnZWagAAvJYP/2bf1DgFwK/qIJKtsBFm\nnrvOM+65YaLtqkF/RvkfRVhoAI3qEBYFH11C3Dl2N3fRTptcNmKdjHZnn1bh\nCiG/a/IAyDef1OpGzVH28EOa2hx89UphyQEq5IKLAOsz5Zg0x89boytkbuMq\npMj2Alvf666yLHLFhWcp5nQFD5Zjsp74NdwtUs9WDYXO7qh/oTndBz5hAjRI\nydGlWByw/ThLtccJ/Ye6+hrovF/G+aR5e8BpBScUyhf5iI99QeTqiX8fXsAD\n1aFhPD0Sn5R/RUNUj6Do3iPEtMLkTtSIFnGj2ArbEmOTdZHVc/btkQZLlw9r\nPGlAsxPsWuu1tvWhDIs7pANQTPX1yFSqu9dC1gAb89zFZABQJvS3gDHfI/jj\nqN1zQohIJFYTgYGUgkuoDSjmSQCmdOQ4jrytvOWZOYO5BeRLvqzJC1SWz8ID\ng7bt1Ipk8FgP036pV/5J4M3CNdZB/9HBJHbCGFIKBjTzPUk2ByQ+/EeCQUBf\ntm0GTR6mQMvdnumUhmcDOlZsMnl3OUPSwGMgesFhN3KECJMVJgTD4o7s4G5v\nNCbuC5crNivhK4bzr4Kasxqc8UkZv8sD78BRt3VtO/o34YKEXE+YAYu1CjbT\nCuNxKPCNX71VdyH4vs4+XDEFA+jOO5tPA51TSdowmDFumX1ot4geVVtm84tk\nKzzx\r\n=6zdX\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"5e1695d26e15ac0cc6849e037bc8adaa535f1f8b","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.59","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.55","@balena/jellyfish-logger":"^1.0.4","@balena/jellyfish-metrics":"^0.1.59","@balena/jellyfish-environment":"^2.4.2"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.95","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.2_1612505403004_0.43021417760874825","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.3-renovate-non-major-cdf9a3ac65a32f68a426a0de05f7dc01fa409ac8":{"name":"@balena/jellyfish-core","version":"1.4.3-renovate-non-major-cdf9a3ac65a32f68a426a0de05f7dc01fa409ac8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.3-renovate-non-major-cdf9a3ac65a32f68a426a0de05f7dc01fa409ac8","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6956a6da560c31a8a9459952f73272183e30a12b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.3-renovate-non-major-cdf9a3ac65a32f68a426a0de05f7dc01fa409ac8.tgz","fileCount":96,"integrity":"sha512-8QJrIImIjlL9c1acOfLPw+HdMCN0jfpkziA8c3tOq5dLtITm8Z7dUihbW3lp3KyyY6lTguYrQaTI7fc1hZJb9A==","signatures":[{"sig":"MEYCIQDPp8j0ZqhoGKE5KzQjUMsn/FUu/OejiJvSV0l05z8x9gIhALZQXDqmGb3dEq2wsbMoFrJOvUCIFAQvtJ1GbTTOMPwd","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":595658,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgHYcWCRA9TVsSAnZWagAA3REQAIbtqP0F7MmJn0VtbacE\nX+g9/LNFukrhDWL4qVa3lJs+4053gfHeAVww13uAkUCKED0wcVX9jrOBokHG\ngrJ0uVoYTKdVj0tfHUFkVnPH6Zc9IPQBKK3LLyPzh04ZafKJwwC/mbzxRCQf\nUDDAudQHYT14/YSV3m5TsFWtH1ULfza3kKmNmHIinUi4w6JnoF6CIq7+b6QB\nFAt0lH/izB6vk8Fh/uuPhGW2/QPZT+mDLYQ3rdZRgZkIsa9Er/5HtE0OErBM\nT1s6XuKT8UN8nL3TXq55lYn35CFHuxFkdKCM5pR4M8gAtWqdA5m/RYEt9768\nKWg9GO4Z73/v1+vIGgWjzTRhbqE921mKaRgSMXOh6UxWSPa/dxUgyAmWd61V\nHKpEGhUHn5xnysQCvyLvxKcoSdSPQoKiK8Uhx6KCUpoWYUgNSlNbJET9qEj0\nXhi3Ahau+BEb6hGw9SHFqUsrAgVmqgXKrNlx6QOb46byTyoln8cbNeDnjeFZ\nmBU5PhuI/ro6MpieBCoQfd7HRVOFGcdqP9EnLFw2vBtKfttWUvHOdAJbbLvr\nPnFIsb1b+u5gu6tJvT+rLjtE04d6+wIK7jK+HxnwJf2tbN28Hsm3Avz2tLh1\njnHixX4rjo8WxT8Cx2F3SL09oY2pjQXqqOVe3Z6l+5tNFIbjLxUzUTtwYf5q\nscsS\r\n=vfHK\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"cdf9a3ac65a32f68a426a0de05f7dc01fa409ac8","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.59","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.55","@balena/jellyfish-logger":"^1.0.5","@balena/jellyfish-metrics":"^0.1.60","@balena/jellyfish-environment":"^2.4.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.95","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.3-renovate-non-major-cdf9a3ac65a32f68a426a0de05f7dc01fa409ac8_1612547861888_0.018484093156418968","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.3-renovate-non-major-31b52ec8d9d460266e29a2dd80bdfe99ce903384":{"name":"@balena/jellyfish-core","version":"1.4.3-renovate-non-major-31b52ec8d9d460266e29a2dd80bdfe99ce903384","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.3-renovate-non-major-31b52ec8d9d460266e29a2dd80bdfe99ce903384","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4e78c35422b3cf92081da650832eab9ff62f201a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.3-renovate-non-major-31b52ec8d9d460266e29a2dd80bdfe99ce903384.tgz","fileCount":96,"integrity":"sha512-0Clz9LOIDlONfxRbdJNVRui2uhGtiwxJFc5lCjQGEyWygAKHVvkaJbs82PLlsvKCBz/GaN1RdFi21fPVCWeepQ==","signatures":[{"sig":"MEUCIGUpo7qjKLpBGUO9ay5iAtMuO2GmSdesjaswxpWR5YS0AiEA/rls4cDD6jw5DA77COWn1ITh+jPueYW9wC4uCRVGvko=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":595658,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgHZOFCRA9TVsSAnZWagAAUhwP/jtuHXzVjuJk08waaGZI\nVv3oSnuRYPzryy23+Q8wmGzNFyGW2uSZc/pHYNH5v+rNhnoSnt+81IPnJSRc\nLBQtmxr3AOD5DxL3ykESn1qIF3QbqMiJpWyGH3xYlzWW7mG0vJcCoLbiMP7r\nAebLPhcKjioayu7ZNapZ36C/jIu5E2Zdx3X7RXvD0T8oyLVyUIA2ONLDbGk9\nsP2mVQAQgAalEdOoJJBe415poaYGGXyjUWJqSna/8l9sPf6kDpOTfXVRsGqx\nX4WQ2fVNfkuMqG7ylHOx22FZh1kRrqNlLMZj0cJwUXoHKrIPAidFunOxrEx4\nxfW084xFsMApedkxBsApbnGcgv9DSuXY3rmE338mkU7Lx8QCGeXQXjMadWvi\nKwe4GqTE5hh70ibdXJVGujdKcLBhK3p5eJPWKmiyWjN98c662i9GDpHx0IuE\nEV2kcZCARkw+siyJKGp/7G69AHchhE3qrbbHPO3fFsI1Ncn42GkyjB5o0n/D\nCUFDCAqRsKfhPSvqg11o2H536JRr5kNLXjVLRO8kKAjBlDpfLVDCr8g2qG49\nWdAAb1zQguPf7yOIrWnidhnPi53AXxiHNFXQUAG8MOr/5/rwWPCjI2rpMEOW\nQLke+iBt7VnTBFuxnlstkBZpoIQuOitjP1g4f72FqUV0xQC5fCTMNWxWCsBX\nSJEz\r\n=Ycff\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"31b52ec8d9d460266e29a2dd80bdfe99ce903384","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.59","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.55","@balena/jellyfish-logger":"^1.0.5","@balena/jellyfish-metrics":"^0.1.61","@balena/jellyfish-environment":"^2.4.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.96","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.3-renovate-non-major-31b52ec8d9d460266e29a2dd80bdfe99ce903384_1612551044691_0.14781503723679745","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.3":{"name":"@balena/jellyfish-core","version":"1.4.3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.3","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d01dc199a2fbe1bac598e30ff66b98b14e8f06c0","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.3.tgz","fileCount":96,"integrity":"sha512-ccANsfgpTmcBdvsRe7ob97TAfHx5qsLhFagCzF+yTbwupllI/AgoeafvEWyqXkj/wSB7mu8efJCgJ8uZ56i4xw==","signatures":[{"sig":"MEYCIQCzQxbF9YtJakaN4oxTwgfOGLqry0IPALK/8Sj7zr9GmAIhAOc/mxXKHrvhHTsJprOTgrB2bOUX3exbR8D80/e4+FmG","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":595598,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgHZSuCRA9TVsSAnZWagAA4I8P/2xK82oyotrDkgTLg0l9\ndR7zqbuNWgE8vH2XiYt7pw4WfMD5F0mex5fD21NvuLC5Q34QeO+KLFbKdWCJ\nhqYQEXCUTuY/H4rDNrS/ZNKpA9FIZcoohFIoavxW9+zMZC3DOob4xjistTMM\ncaBmEDHeutX+ifG2DbEL/HHSFlEZ3oBPbQ3pPFKdCpJ9Y5EgOaE0KJqkPBu9\ni5kjAEO0BLRnYax8B1WHEqux8yZ9f/C4dPuMI0ouL6fLW+pSnPzty+VIL20P\ngTHNfFiFctdaOcyW8bN24xCP/HqTvVmqn1DkskHiVI8QwMjdyhdASWdVA4tt\nb7/ffDsOpIjqMphP63NPAfAH7XuRuQWRLyuMI3uJrwupfjhE0Gyn6TJ5mH5x\nSfUOQLlIhjZMjc0cwE5iamosKcTxoN6phc3oQRlz7wA8zLF6k9SlvoGGtvxr\ncDLYqzJVrMw9mneFO+X/wcGc6Lbqx85cz7wf7Au8v5TnVmZ8yuIteP5kBeBN\nSxTm0bEstRIEPXPuAoEq+ASp+2UzQuJAfVXgnTmhCvmAbvSR3HCWrIb/g819\nyY5v/qtG2jLZNOhSq4m5l3zYm7UPcnTgqcz+Anul8FidGv1xTiO8a15F3Dqm\nIapmEFSf3ThRIAq9QNI5IybcyFtNHAxXNgHfFnlZhmOiVbTNtJL+UucK2guQ\ntwbc\r\n=Qr3V\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"128e78c4c818277b2e143971dff392f09ed6eb7f","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.59","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.55","@balena/jellyfish-logger":"^1.0.5","@balena/jellyfish-metrics":"^0.1.61","@balena/jellyfish-environment":"^2.4.3"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.96","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.3_1612551341824_0.3483422637730407","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.4-use-shared-renovate-config-a409a5aeff42706e498c976f53c57c07887e496c":{"name":"@balena/jellyfish-core","version":"1.4.4-use-shared-renovate-config-a409a5aeff42706e498c976f53c57c07887e496c","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.4-use-shared-renovate-config-a409a5aeff42706e498c976f53c57c07887e496c","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"323d81f94a0fd75e1c0234fe69833f33d4cd8e68","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.4-use-shared-renovate-config-a409a5aeff42706e498c976f53c57c07887e496c.tgz","fileCount":96,"integrity":"sha512-ywV679QC1PGilqBVl0OaO7H1t6wJOi94pmcr5R8xNv78RNLBxDw9Z0MiJg9fUJnlE/PcFL946zF4wLHDy6o+3A==","signatures":[{"sig":"MEUCIDdlak+aOvdB5Gg/MTxG40GHdInUSXkGnENZIfffd6wdAiEA2Bsn+qdy1VPQEUcmozNn53hzu7RinfOoWjHPeUYnq1k=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":594822,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgHfT/CRA9TVsSAnZWagAAAH4P/0mYbzTmMXd3RES/Esxk\n9bjOrQnHsSxxB4cZL59jMM3jMGP/W5idpDkx0rXwcYcp/dD3/5T5PlhX4LQe\n86k2e7kHUCrO6TPKI3+s0LMOmcmJTCP5oBsD3DwUdmlipsoHVJ3u9cX6wpa+\n04s6M8poUWEEkyiaZgQUmMHfAzD7mQzQ+cbp4proWgQJ/BcI1S4IB3BYOnRV\nyOGv3soqZtrRiPLW7ZPiAt8dtvzGLlNfMGn8MTOf4lccsfKidwCMRjhG1kLG\nu+JZXQnK2foK023HUm85u+Qtho8qPP0GLg70GtqYH1eOVpTpXHZfyBgAouc2\nOUlGbESxybwQaEpeyh/QzXQb7svfwCse5IyzBJV35H8irionam8ghds+o+Cw\nmlivf7LcT8IVmTSO28B1Ryf//W2dwFMSePn8l2yLBN4PPj6dIezEGo/a9H2C\nK0OMe8bDjStXvqaHvgjMsBkMWbkWieSiO0Ff36iOfosfDHBAb1i03/kWxLjH\neE6uAaYWIAk7V0cMdH8zDm/otwkmfKnytM9pKRGf84Bgdt3BJ+LzJBJBDaSN\nhoZeTihJuYIwe/Zu/xexVwHiOffOrKPK2NMgdrQ5rJogVm2k61+KLyRTJuuY\n/T4iPnT7nEkBY0vQcjWWjhXXh68uHNQrWpnDUUSqw7QOZjVxSWGO7d9aL4Ox\nJ7U9\r\n=Wz1e\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a409a5aeff42706e498c976f53c57c07887e496c","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.59","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.55","@balena/jellyfish-logger":"^1.0.5","@balena/jellyfish-metrics":"^0.1.61","@balena/jellyfish-environment":"^2.4.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.96","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.4-use-shared-renovate-config-a409a5aeff42706e498c976f53c57c07887e496c_1612575998575_0.41324973270597654","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.4":{"name":"@balena/jellyfish-core","version":"1.4.4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.4","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ab48d65668e9149777aa9dc0db478b52dcfbf29b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.4.tgz","fileCount":96,"integrity":"sha512-IXhbjFtYQ6ew/OYYOyi5hJ5wPbEr5KsrscrrQgHXVxhYYcyrkExjoNy+w5lsGbZQMjGLQRoAb3ht9axTKXRQTA==","signatures":[{"sig":"MEQCIHH8lwjbR+GXoIO6uKUqlm0kg+ticoyHRUwk9eXfSecUAiBzWJUKGM3+/phhiBoeALTbZL3VdPwnuNkF+TTrApagjg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":594754,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgHfaTCRA9TVsSAnZWagAApEYQAJOuP+AqYRSyUmBR1CEO\nuKJeqqlZF8OcwBUzm5hgsw2CErE4ZKP2M1Xk/v99mO/BSqATfVsxT6DdM58W\nEzjSO7MTqjN6M8BKHYKBWTaQkr/5ca08DAfz/3NDUtuB91VUFEjW2BEEJEF3\nnztDHyNfKYR+EgIshjS7y76TvHlPi74L0DYztTX8gi5loeZ6dSo82mDum2zt\n9kRmOzJleE7NYDpygrayJFiHOejtxJGh8DV6FPRm2N/kZfPytYWg3GDG9s5u\nSIRx23PgHjrHc7WIKp7x7eW6ZY19OLpqpJihAfrpeTQVTyHXBxCy0rMN3ng/\nqfuqcsdunz1uCSVCtYxG/eFmSe5Ueu1Xd/bkL5n3fRlPVNT0eR+ANvTvgutP\n5keuK/roW4qBrm9svkxX3vIL3V9OwnDJGvC3QbU7AWvA1ZaVhpozZJeq1AC7\nA06PV9r1Cdd/n5BnBVjKgfxUyzPZEraFq+0uGDwSN9SBN7uTHihtflkc08xv\nS8lQcv2J0i0+o22mYLZB25hahyDLcHX4F9ciDf81/Qj05oFLJ4zC2Y3hq5Ri\nVhc6r7Kc8iHmJ+gWbjzuCXpExGO+BcUfBuZl4rmXaDE1WFNxGRndMVLcTuMJ\n45CJ/fZnGxw56OwhiTWW32Ov8u2wICwv24SaAtkZPwwQ7xa7BqYoNQ/vnuhk\nhstE\r\n=tvjg\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a6ff3af6a56310511f902b3127f3184e99804569","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.59","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.55","@balena/jellyfish-logger":"^1.0.5","@balena/jellyfish-metrics":"^0.1.61","@balena/jellyfish-environment":"^2.4.3"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.96","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.4_1612576402628_0.16583256131601165","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.5-created-at-timestamp-index-e3f9876f9367f630689ac149d88b40c3d9720a5e":{"name":"@balena/jellyfish-core","version":"1.4.5-created-at-timestamp-index-e3f9876f9367f630689ac149d88b40c3d9720a5e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.5-created-at-timestamp-index-e3f9876f9367f630689ac149d88b40c3d9720a5e","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"73466551f62d7a49ff1ca1971dead6770f7c3390","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.5-created-at-timestamp-index-e3f9876f9367f630689ac149d88b40c3d9720a5e.tgz","fileCount":96,"integrity":"sha512-dNyOgkQVWo0CmhAFWuv8LXUBm0xe0VPSxntLnarfjFYsw0lCsKNgohIGcf0gBT5bgItcDhrRneh9ejWpOu0BWg==","signatures":[{"sig":"MEUCIQDiF00T5fMAZ66ZQWCtkIkEU+EONEjLRsT7TkLxJRqbNQIgGvfuR6DIEF5gTc9Ub/0gxnrymbxUdRbqa6D9NRrniLk=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":594977,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgHhdyCRA9TVsSAnZWagAAGuUP/1D59W1K05KtQzAqhE1I\nV+zynoV8iJON/5gCn/4PfxHzPM3IgLxkNGdVX4MgGkQW62pxC0lHKhBblkEE\nlOKrU94RYWIGJGCC/ixddhlvbaI4vYxZXRyQ6JO7zdJ6JMUSkNHVf5bWMPBg\n8h0IKOyNj2lclCb0aZvjhde/7/76mimiUtmmwTllB7+dQdZtJ0XLreut3i6F\nl/xTbTOa1Xw4Ppti2Kx+DlWH2Ou1WnGMVjIQWcXp2SVFB9+o5EQ4coxt9+3n\n0Uk3RILaOowyZsM03vukcSxC5Cpk9t2V4ylhRr3/a0o+0pRC+18g6VUt72aX\n16Ph0g73Nh4GZf1RJEdgn5KIEtqABiX+oInAggpilaAz14REfnGGW8RmhSKR\nlMyABwstTZSN16UZHMckIQ2vfSVJLOMuLusMy0qZrY7SFGNXAG+ZP5lgV5CN\nwnA7ntSAEGSghq3BIxm7jp4skyZ+2Usj7/2fb24SVMCm7j5SNNMqzZ4DBE5n\nz/7Bs+TTtcGDpFyt3otfO2TP+jnLL0w/sZkAvL7DOG6MrN6kTwuItOQqWbpH\ntpdbKygW116l/72Z613zEULNnEtz4JE1wdmTeCItX67Xo6qHQ1jvIVd00WHB\nNGRV+luQFwkyraViiM3LElyU1SE58lLuHcnagsdmnfhDzs26JTnwkTKU6Wd1\nhDCn\r\n=K60u\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e3f9876f9367f630689ac149d88b40c3d9720a5e","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.59","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.55","@balena/jellyfish-logger":"^1.0.5","@balena/jellyfish-metrics":"^0.1.61","@balena/jellyfish-environment":"^2.4.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.96","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.5-created-at-timestamp-index-e3f9876f9367f630689ac149d88b40c3d9720a5e_1612584817452_0.32498121742208697","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.5-fix-flaky-test-4c708dae4cb67a1606da068494ede21a339d10c9":{"name":"@balena/jellyfish-core","version":"1.4.5-fix-flaky-test-4c708dae4cb67a1606da068494ede21a339d10c9","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.5-fix-flaky-test-4c708dae4cb67a1606da068494ede21a339d10c9","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"afaba570ba3dea8a2499e68bb2a33cfafbb31b82","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.5-fix-flaky-test-4c708dae4cb67a1606da068494ede21a339d10c9.tgz","fileCount":96,"integrity":"sha512-4Yx6SrBG65LEmZFiD3/exMld1z5btTSM5P0V9ozUnt8gmrXxfDaQOtJDOIs5Bgty7GSZTiyRVQ23dbbn/MHjFg==","signatures":[{"sig":"MEYCIQCRJDV6T4rdtxOF37TQaxeThA9jykbvJ719Zz/WhnIvbQIhAJJew6hLLA3jT2IcNrX2GdoA0BEO4pSNgKE3VXKjW3pm","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":595092,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgHiDnCRA9TVsSAnZWagAA1KUP/2e0hEuTg+HI/0GPsx/m\nzEjL7+d80Ynez6xpM0mHAATy8Ysf6UyLbQlWhftqBlELqLwpGSpOXlf025Le\ncBFRt4janr+L5AV+GLbCkCYivkZTLDB48A3TaWiJKewI0/Ue5iHBwUuJqcjI\nzwg7Zctcaigz62xH3mEbQFv3fD5Aw0kt3ijQDXKTYR0NVy9A7dr/IVnrEZMV\n2XJ50udR8F1qJex48rx+E5jG91s+hLWWE5yqgkwp5oRflqdoSxk79iM/oUfD\nK/Rspbk8GuQV0lbUwlQjf/uLeo+iHR6YrZwpkfz/S8HhQYv6EEilvZslO7zs\nph2yBDkKcvs2AknzGmTk4nSJzKTRTHwLmwYBqmfBFOLv2nRP/Diqc1FFesPZ\nsJu5O2TUI88rJHlF97Rvg99V0h4TIK8yJ6RkdF2nje/pxaeXYn2yX7vmvwVA\nOSU+7MvFekUIttkJ52VcJ7JDpEVMywlED7FnVKnveus/En8XPtr/OtYEf7Y6\n4LUZCMpfvjBfUJYNAaEyPN7FX3vYeN6BqGRFmUi44FChLz7paGTQkdp/kkt2\n9t/9gw26Z4CYXMei3rf8HH+2HgN0xJZJp4PvWLoaWgQC+bAUjS6oFW+TKT1I\nTbcUC3m28L+coe8jT3Oo8K8VR9G4tUwui0Fm9Hgh1jws+3FtwAF08jUcdyNr\nLbRf\r\n=XDvh\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4c708dae4cb67a1606da068494ede21a339d10c9","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.59","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.55","@balena/jellyfish-logger":"^1.0.5","@balena/jellyfish-metrics":"^0.1.61","@balena/jellyfish-environment":"^2.4.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.96","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.5-fix-flaky-test-4c708dae4cb67a1606da068494ede21a339d10c9_1612587238821_0.4781496536717582","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.5":{"name":"@balena/jellyfish-core","version":"1.4.5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.5","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3c06cb324851208a686a78e01d38a8ab8748ebee","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.5.tgz","fileCount":96,"integrity":"sha512-lSuDj2ZFI0+TDj/hfzNOkMRKoaV6C6dskJsWK0l5hABvzDQiOStcOLwoDeTORgwBeXX2q+oC4j7BMtLXBVzVEA==","signatures":[{"sig":"MEUCIQDYwOuLF3x2Iv7xPJ5FpYzBFLNxi8lbnfa2yufG+Q2B+wIgcYUfN4vGdTxQiVb8kadYbMPUAZgUoSVOMAC9733ghI0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":595036,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgHiH1CRA9TVsSAnZWagAAz+4QAIGMhZWqrKjtLZ0xsiAA\n8s0GkrogU/UCfrmXU541letJLQ8E/Joo7VjH5ncPEvs2P52YQgoE/ApqCaFw\n3WEprSFplo8jekuAT8iOe5cogVmPg9KCrf7AtmARP4WwuirQ7z16EVOnP2Ll\nF6sXnxKZxZiMiMUvCI19JGDhS1moR04jwNlKhPC27SOq2tkpqlCc7g3gszfd\nE0CQGzSff2nryXiwiNzcshe3+HN/zaUUXUPCuOTzUL5A5FsQNTm5bpitoGkk\noAiEMJRBsDZZ4uUskk7tulwQ/AIpiMJ/XjzjP5ioagb7mGamzV/AeBL3bNU6\nmwyC8HxNbiPbo5L++QZIxSWQO/iorq1ec6OkhhKxgdPyvsWpIWMnu7sCTzh3\nVD0LujxfrApGphgV+KQeGko3LJkmZQ5Oc9CS86qGQah9egOBVIHWd/uNlxie\nILBNkGACaBc5Aou5wJFswuIMq8pMKgxfNSjkn9BLbYI2wJu1KOU8W91WG/ME\nJliPMUhNqLWT1P3RAwCBTpX4UgwM7fBaRy1ZTCJilHUhNOqySKV6CaEa4tWj\nlOAn3QyGf6tiNBm2zcFKMlEcGrXn9DwNhHaFzyQTwxxrUGaLaxiTRu53/EAO\nRcD8IghiydLoXeD/G9vJd6Iz5kRx8N8Y7ScDf1sWsS3LXE5XiA51nbRhULvq\nyPDG\r\n=4RAl\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"fa675184b312f5ef959744bd1b9d3e8c5d591122","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.59","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.55","@balena/jellyfish-logger":"^1.0.5","@balena/jellyfish-metrics":"^0.1.61","@balena/jellyfish-environment":"^2.4.3"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.96","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.5_1612587509015_0.4753117454589615","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.6-created-at-timestamp-index-c4066188c8edb35c868e8946b495b079b3941750":{"name":"@balena/jellyfish-core","version":"1.4.6-created-at-timestamp-index-c4066188c8edb35c868e8946b495b079b3941750","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.6-created-at-timestamp-index-c4066188c8edb35c868e8946b495b079b3941750","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f07b2d40b520384b9236c04eba56701d9ad0d061","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.6-created-at-timestamp-index-c4066188c8edb35c868e8946b495b079b3941750.tgz","fileCount":96,"integrity":"sha512-Jcu/OaXXw2dwcuc4IWHhzleB6d7rNYu+7WjuMqZ0oWKQNy0sqqQZxam11HxlmfnE0GMae8QTBK74GD9jyArXIg==","signatures":[{"sig":"MEUCIDGQRGT5j6AzlTVbuLBTaciLy+GiWueiufaG1t/ix4KCAiEAv2JysFMCtYaKA41qEHsLM2tUpESPjcnAuZgTdS3FzI8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":595259,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgHiQwCRA9TVsSAnZWagAADTwP/3xxAMdnJ+ZxLISFLsec\n7x4+MAqLaqhJMvddbYNPkd9RXW6ocoOjQGp+KEmuwzF3rz6mAbQo8G8K1Bew\njVugSMY/NS6kokoOVS3SazZlvCK8S73+sb3OhufLVYaK6+3/50stFZhqrJdX\npT297b4zZZgUWosPPvjS7tVmNHpH3eRVOecuLlzDGD9ltZLLsTSbJVG1fukU\ncC6kpqFQZ0wP0AErOf4y/ITQP/4QI7ywd33ibD8dxGGXf8ImHP2d6Im9wm0Y\n5BP3zdD007/9gl3IwSr/pbdfkNQfXrpKkVbyF0Pljku/eZVg9TTKvNqMBReh\nlVrZOlIE1RxFQ47eseR0W/JLBjNtZYg+m/1d3afNXxN4QVKTlQIZ60YH6hx7\nBy6Ie6/HGbYGg97VdNU50EcVJtMgm+9N+z65dgm6F1gLG0Mj676AbLDu8QGx\nfAtl2v/2IP5L9Q4Laqz4kMwGL82ZUFjvxXD0OtF9evcKmBVXohurkUBqoiHC\n1MGRS33y2/YheFLwhjOtUWtmSc03Dx5DcU1CjXMOjg4bdKLDTgQpsHisNloG\nTc93FC1kuD0HQyF/suqHZ3LF2qaXbQlTjIKGUaDemIEm+Vg/gmI4AedqMgAA\nruMDL8bHJuvpWrvdQLyUpQv8aPYr9aj4anOYrWEae+5Ydb8JGPkVeFiEXUrd\n+qjO\r\n=poUF\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c4066188c8edb35c868e8946b495b079b3941750","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.59","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.55","@balena/jellyfish-logger":"^1.0.5","@balena/jellyfish-metrics":"^0.1.61","@balena/jellyfish-environment":"^2.4.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.96","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.6-created-at-timestamp-index-c4066188c8edb35c868e8946b495b079b3941750_1612588080117_0.7931454972117782","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.6-handle-deadlocks-f553fbd86f6687abe9f4a804d84f136d9e2baa1d":{"name":"@balena/jellyfish-core","version":"1.4.6-handle-deadlocks-f553fbd86f6687abe9f4a804d84f136d9e2baa1d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.6-handle-deadlocks-f553fbd86f6687abe9f4a804d84f136d9e2baa1d","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"7da22d6ee3187d4567dce496d2da90002b7d59fa","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.6-handle-deadlocks-f553fbd86f6687abe9f4a804d84f136d9e2baa1d.tgz","fileCount":96,"integrity":"sha512-L6XmpLabVfZEftzkz08TbHvqc1kAf8KvA1MOH88oAddSi0bebcmw7AeJA6lIc8oS9+4s83z2dmZVdDJDXHvXAA==","signatures":[{"sig":"MEUCIFkZ7m666WIxZE+GOPlW+32fXCtrFDY9CH0u1z8dhsqWAiEA2CLFI6a9yjVEoCYIxwCvf+N0gKFIWIWLGuP58SNB13U=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":596489,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgHiXWCRA9TVsSAnZWagAAasMP/RL2JRr+LzXM8+EqF7Fs\nfPEOM+GmJj0vc9Nkm7Yyesi3V2XuX5We4Uql6IL2OOHV3AJmK1d9Ey68dHr6\nlNThX/YEgT/xNYzuhLE+pgruDLhz7AJ2yAlkcBtvTxSa5oeKqmlaeSWB95vi\nEXlOaQwg8knpClvW7GDkxtXIasqHT+CLH14V36xh8Mbn5660e0RY2/vjGTQ2\nTYr5cryqUOxarLgbSFCjvyt1E24pZ6RZfKPUwETk3slFd7thDBMyCMteJ1FE\nuIPgHapo59rx76oxt4sMLRVfTtWZUw2Vingb/tdICQLLr2D/zuuNubKOX/mx\nZqbQ+OZSiPouvR9qJU+02YVa9oLTPotcFEAwbFI4++PlMMA8ZvZvu8GFW9vw\nEgKKrhzfQyjEhTGzsOvNC77m1d64nAOYmqAJs1DBoM+XoyaZf0YuNJTkA2KM\nSuTttpqllzxq9cKhcqYeJjfMcO558TENmvOyu0M8bWvStEAl739xRD4kXbXv\njLp9PUAILlcPORpJym5LWo56VQ76Uz0ckeya0kwXYEi3TzyI+0IKdn05A9to\nCMMIEYkBhyIA5BoAZLFHx9WmYEqUfaJsw6ctE97ys26UcNubrATOlR16nMIR\n8TeVyQDHn5CRw42cdbtXoBy6JzPWwwaxW8MoIL9iES3ae0INXTDWFg7B6PQJ\nXr8C\r\n=PAr+\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f553fbd86f6687abe9f4a804d84f136d9e2baa1d","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.59","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.55","@balena/jellyfish-logger":"^1.0.5","@balena/jellyfish-metrics":"^0.1.61","@balena/jellyfish-environment":"^2.4.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.96","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.6-handle-deadlocks-f553fbd86f6687abe9f4a804d84f136d9e2baa1d_1612588501896_0.225287055345069","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.6":{"name":"@balena/jellyfish-core","version":"1.4.6","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.6","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b2400f7e83c52823470b92176b5f1f3e2dd55794","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.6.tgz","fileCount":96,"integrity":"sha512-U7xXqzB8+ZCmMtUX1NjtMeR2X5gtLMgX2Xhd9N1qbAuE2rlfw6xj+AXHYuLCLXuPmLjC7UX7Z/jdrhKStqYpdQ==","signatures":[{"sig":"MEUCIQCExxPCJ1X50z7EiIZ/tR9Mck5QAFjBqxjEedk8KXolgAIgMKKJLA846FPrOVm0Jntko01OcaH+xBo8kShjGqqXzvU=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":595191,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgHiZfCRA9TVsSAnZWagAAX6QP/2cQw09oE1eze+nhVyJe\nWHJjbRV1g/eZfgLBK0Vi+3BHShA37O2qJPdNrukwoKF0lqG1L9XDDg8GZ8GA\n7BOCofWCNUN3b+45e54mB9SusG6T5MaDAzqxmxLnJD4RoNvCK6UtrE/n+NIs\neJiLiDObz8H9PGDyPY/IIY/jY1LZ7qq2u1/FW352Dmppw5op5NlWrKJQ9/KY\nFptWC9aHAsypYn/6SBVYopedayhx+7fFjEYq66pETDcg9DAvb8HKxfojg1He\nWnRbuQON4IDXX9ONA86MS1nmdJejYC4IKjdBxQdLvQE1p5EPI2ecKUFlmYsm\nzqCcn7x5HRZqtK5QsAgCf9C4N0HvgDNovhd9kylslmoiao4BU6iXl+Nv5Dkq\naXjfvwkmtplw6uFODLyullnIhOv3sDF6MtOFFVfLnD5gv9QE7vl1GYMFtwj9\nUzG9Vjkl13VNt5DAQB94ywA6Bnz1ivkkBZpOJ6gfh+FHgq59nETPAmDp194l\nhoLL8BodtZ3jQnO/sRGiwdrwd/OAQKHCgFGaXyB7RU6+22bJ7PqyBi6rl5qF\njsOjJ8DmOr+42m5vI6cBNF+ojl22d9Vit0FwFpoRioEfRj4e6Wp6dJtrGHKd\nDVNm7TANdSQDkQ0mOy+MBTmBFsBeQzVyvIrSQ6EJuAoeHuz8KiG9pHmXtmxq\nabHj\r\n=8vJ9\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"acb08d5acbc79e224c4a7d67c2dd5acf11faa8ce","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.59","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.55","@balena/jellyfish-logger":"^1.0.5","@balena/jellyfish-metrics":"^0.1.61","@balena/jellyfish-environment":"^2.4.3"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.96","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.6_1612588638707_0.33125849886637093","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.7-handle-deadlocks-06743b7888183be54e07b536d92818640a9ac5c5":{"name":"@balena/jellyfish-core","version":"1.4.7-handle-deadlocks-06743b7888183be54e07b536d92818640a9ac5c5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.7-handle-deadlocks-06743b7888183be54e07b536d92818640a9ac5c5","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"aacfb62d629c2fdce6fbb966f09eacd709072fdb","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.7-handle-deadlocks-06743b7888183be54e07b536d92818640a9ac5c5.tgz","fileCount":96,"integrity":"sha512-RHFCOtJiFFyLyOmK8h0sYKWHYZgdUmui+U5BmNivRaB9umYpzbpTXHgBeh8kXXUVCokod2oxbHNn/1GwxVYxuw==","signatures":[{"sig":"MEYCIQDQOkvi8PudAidSqzItvHd4Y+RMLNDD5unmcXv/g7eliwIhAL3wZC++2d+lDZL0/7SuJv4FNOfqVBBa0TMs/M6GnMnD","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":596644,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgHijHCRA9TVsSAnZWagAAdZMP/1FeH0h63vumt1nubhRs\nHFd9DkrfkDtA0W9osWHcf+QQpzd3LAbL4jSzqjR9QS3irL9rAIZlQd0g48SR\nvgj5yD49TvHaGca0jh0FU/YVk+xCEpCyBIV8vl9saOQZ3TwcVIR2TgDzWzTW\nCw52RN5OY6oU0PK36PSX8ZZ8AkatoJhCFxAmd0Y8ObLeAh7OV7BQxtDPnqQ9\nknFuVMjTofrgNF5L1YajY3/f4oe0qEQEdIHCsoS2Mb79jUwkhnV5x5HaZa9/\nJ+nczT2PJ7LPdkaCUATzNjIPJ5ftAyWy2GkMId48+fGzEYU+QyZ1ZGV8av4c\ni8f/o+DUiw2uik081liuyfSeyW8IutpivD++6CHHnmkYQxCSrYmLGymgpblx\nh+VheYyJaqUjPeN+z0Sr7cJsU9dIr6TIqNa8N74Rdjd9XNqBUOdu59FEMpVA\n7dPbBcX1W4ocLqxkOLyfYViKXaXNd3X9GeC7SznbALePeo7HR7dn26wtWDU+\nvdAyxmSvgS45jkIVJw9+M7qz2z66XaEA3vD3LU0yOoI43kanL5WqdSD6U8Xh\nUkbaD/PVfHmL7t0TgDIZ4Fy/6OLU8XV8XeYIhA0QPae6SXQsRpesM2Mpoyfi\nj2dH3ZwbQ7Wyls9/WS75gVU5bye3qckydFIZIIMNNByhHnFYq992hFFoXUER\nm9X5\r\n=Uekv\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"06743b7888183be54e07b536d92818640a9ac5c5","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.59","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.55","@balena/jellyfish-logger":"^1.0.5","@balena/jellyfish-metrics":"^0.1.61","@balena/jellyfish-environment":"^2.4.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.96","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.7-handle-deadlocks-06743b7888183be54e07b536d92818640a9ac5c5_1612589254699_0.86814985491272","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.7-handle-deadlocks-c41b439989da01197da6304398253ebca28438e3":{"name":"@balena/jellyfish-core","version":"1.4.7-handle-deadlocks-c41b439989da01197da6304398253ebca28438e3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.7-handle-deadlocks-c41b439989da01197da6304398253ebca28438e3","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"de11ac30779428326e7c54b43ea82bb4235a5c60","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.7-handle-deadlocks-c41b439989da01197da6304398253ebca28438e3.tgz","fileCount":96,"integrity":"sha512-vfQkvkF2UfCO0QVXqJT6h97xinx58UHkNy9/C1MChy4a+VSqgB1FFwI1+oxNGEolzvighlzEBq2EZ9rTdPlakg==","signatures":[{"sig":"MEQCIHyd+UlVrYLvIBpWsb6VMNWRexszYf43YTgC6ANdWF51AiBoSEirf8ZUGYNv22v8tRGgF4xonZ/FeENaewYoFNFCSw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":596593,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgHkW3CRA9TVsSAnZWagAAP+8P/RPB/Tsq7PzdfoNQaxyo\nrqfHyRo8oBCjT33T9HCEOqyiudm8NUjleQGOQfpk2aM1QhH0zzOiukfDG4qK\nZi83n8ecwLKUDGXFlYOJk6XZVFjz305/ollxi2iNqwtElig8t8whSs0Vyupb\nUr32hZg3hFMR7uZrzLiEJkIP4s/Rx+08n8WP+dU8aeu1D1xRajGdc7eDxPOU\n5wB3fsVNgUPOyXdQSZyv5d/4YscTaa+vZE/qC2PypzXZvYtnVg1GjUxBU1/o\nq9f+RrtCpt5UojzIdcjxoKRUWeyEVTyQHltvJgpiPB71PAjmQJsWcaahvVdE\nbc7jq/F6UJY3E04O23gSMJidV7ZFb/ebBReUwjKRviu423Q8g4hPvMHY2IfP\nzWlcvcTgLqcjOBEKC19G/x9/HcBydBMW1tCcR+WG+gdjDoHGjBQCgrgcPbi1\nf07q3GfrkSkxDc506ZwZRc726RA1V0h3V0jJBABMlMRAvNbPKbR331IiffyK\nlqu1TSVgf+OBvrsri70g2iX/ZPANJ333BdAR34p4zwq/ZcnZbUh0q7b0uw4F\nxQv9quCO0nxFERiGrBvKwX5WkmOlCoDUINnuweKKj0uSupmDliN0sLjn4/FV\nRo1yueSU+k8ZhzuS07BaWdTJPkaxqCaRAey14aMVcL+9niYMFOWtBoK3IWnn\nQgY1\r\n=tYjQ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c41b439989da01197da6304398253ebca28438e3","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.59","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.55","@balena/jellyfish-logger":"^1.0.5","@balena/jellyfish-metrics":"^0.1.61","@balena/jellyfish-environment":"^2.4.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.96","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.7-handle-deadlocks-c41b439989da01197da6304398253ebca28438e3_1612596662781_0.5692132720083429","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.7-handle-deadlocks-4bf9c6e2d945630acfdd2bc56ca2f35ee6105dca":{"name":"@balena/jellyfish-core","version":"1.4.7-handle-deadlocks-4bf9c6e2d945630acfdd2bc56ca2f35ee6105dca","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.7-handle-deadlocks-4bf9c6e2d945630acfdd2bc56ca2f35ee6105dca","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3963f28c41377280cf23776b797fff9d708da9c5","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.7-handle-deadlocks-4bf9c6e2d945630acfdd2bc56ca2f35ee6105dca.tgz","fileCount":96,"integrity":"sha512-KFeH8iiOkU0VkNrhSIAxg1m/xKqxNwUrBoego3kepwkySC1khSDKq8m1wi4aRiOKNqA1+vP9HWoajSBv/wQdlg==","signatures":[{"sig":"MEUCIQCKynwr2erFGwS1g0013y3Qg9i9Jibl26tHU2GAqBkwNwIgacq5oRXk3eeANs+k40qcrhYIVzQWOUbz7IQInsunGRc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":596598,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgHkhNCRA9TVsSAnZWagAATK0QAJnJfeiimamT6Ktz5sKr\nsffr/ipmY73pUWVZYoOqDM5cQOf4gyPwlcqGiC2JA3/v9F72tGN0Qo4m39Ve\nDz9hmCB3N06kMFR7BMN727MtfiF4tuC2hh9FyQBRvmIW9GjQkIPyLcU1RJRh\nIVW6AY7a4gTUz+SGLucidx9zqXCLQONSXtcaReEZWr75TBI3kFvaJWJPGqwc\nDH8hgE/glSGc5znLQMHlltEBHRSlU2+ZQ/yo8OMrxno1gHbM/dYgNjcJCk5K\nW9s1CXnyK8EA7E97BlAgNg6MKTX3lbj28TZr0mOhAG+8swLYStQ6+TbiEBHS\nH/hpcw40H4XOVBwHdyrn64o0gempyUtFr37DwGLHEPyUB2lAemaX7HDkvfWu\n6IYwqfmom/cBDI4wWC0GRqeoXTEmePy5ZhiohvCVHzvcYpCcarz/M1PsUxWj\nOfDFHv6IW3bYxKXm3VP56ZjgXh0pnh/UZSevclM8rOM0rFjiz+uhNxYuZzSm\ntZMRbIUMt43NguB5ebm0i7l8MouHoeqr/z4vqRFUPdztypPLEvNa1oh3vye3\nk9xdyUpf6llZ6b3FrBffA+PULL7Bht2aXQmhth4Q6DMsdR+l9poMmtLA2fvb\nmv5vFWcsI8kbkEDeYwHUBVx5ae9vac5ANcQ4Bqw9/zHuCHMMO6BRmTQeN+9T\nfnkV\r\n=xjRY\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4bf9c6e2d945630acfdd2bc56ca2f35ee6105dca","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.59","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.55","@balena/jellyfish-logger":"^1.0.5","@balena/jellyfish-metrics":"^0.1.61","@balena/jellyfish-environment":"^2.4.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.96","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.7-handle-deadlocks-4bf9c6e2d945630acfdd2bc56ca2f35ee6105dca_1612597324511_0.6315330699248223","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.7-renovate-non-major-45b7cabf88e84c703221d29a284944b357ea8b48":{"name":"@balena/jellyfish-core","version":"1.4.7-renovate-non-major-45b7cabf88e84c703221d29a284944b357ea8b48","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.7-renovate-non-major-45b7cabf88e84c703221d29a284944b357ea8b48","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6356fa15ec3ab38b9fb09691203d6e01e86ae727","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.7-renovate-non-major-45b7cabf88e84c703221d29a284944b357ea8b48.tgz","fileCount":96,"integrity":"sha512-nrNFXSKLr0KS++iRm8ybhFvimEgOhB/b0yWmJOOQqoBKkEd1JmJu7UZk9CtAYmCSZoR0jJMPrsD4nR908nzwlA==","signatures":[{"sig":"MEUCIQDNJUzbPHx1yqXHxvIV0+rmgKUxL2VWYKUROFE25Xpp9QIgBtOyHcodke8vTl5NWcFzN373MisFvtbHG+JHrXWD0AE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":595354,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgHk6WCRA9TVsSAnZWagAA0KoP/2WG4Xnett0PUDpxp/Xu\nkdnyAvBcQm1Uma2itz1enHciUzKg4t4Ufeme5a0NI1pG/0SRxehFlCKAkKNV\njBmoZEv/n28Q/kOpI3kveA3+7VYo2PomIqhJHFMwkVPs7YE8u12lmHj4x4En\nHkZdf60QxbPKMY/TwHw0LmYtxQvdM6sQ6l8pUWrEFu8f0Kn6/1VcqB6b2U9B\n3JEwqoMjbcjGSkRrQI7aHpDsCrKTWw2RsKBC790m5LC0OL+ghRm86BDZNNCj\nRgwMEu9uCK5qAgWt3808w6EaUFNycKBNHTw5EOZniiuQq7bBnV30udN6Kky0\nazuSUHZcZY+t7uKhmIZSA9N4LRnUZXsPrPHL2lNAw6ioFNpEbS+J4+9UU1Kl\nqStwcwWi56QYSjOuQ4iAAtpDPpJf9KKJQ0qoNCbyHT8Zs7Z+QFU9Paqxzd4C\nglHpIN31Bza0zqHuajuOljN96J92kipXzUazHyKvZlGIQO38tVOLM22CDZq9\nAOUBtLO7hbmPrzQHSd1yz+VaPD0lRWi7qM4fPJLd457kuKLKiq+9RSZlcqKm\n+X87m8Gt8Lh45LzJuXnt6ou8YBXUa0RQHgChNrTMkv7ivyDEKeVutUs8wL6c\n2aUfLuedpk1SYvau6bkS9UzWS0KVFnUL4U14d5e7CxYPqPDZ/KTMhLIqpHTv\nMGI4\r\n=odZ6\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"45b7cabf88e84c703221d29a284944b357ea8b48","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.59","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.55","@balena/jellyfish-logger":"^1.0.5","@balena/jellyfish-metrics":"^0.1.62","@balena/jellyfish-environment":"^2.4.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.96","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.7-renovate-non-major-45b7cabf88e84c703221d29a284944b357ea8b48_1612598933587_0.6454778576516111","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.7":{"name":"@balena/jellyfish-core","version":"1.4.7","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.7","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ccad448e7e87cbfe6cbc19a91ec3bec88800e820","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.7.tgz","fileCount":96,"integrity":"sha512-hLghg/qggvTZOzoCk3Ow1lz9EgXXjtlLw2/HDkWFoQH8Gu2K0mDmqLqO70mbYgXQEVxQH5cWbMClH1fnkwuLkg==","signatures":[{"sig":"MEYCIQDT/RUsN73cEIP8uK/nIO1vrbUP/7WyJ+nvmn85nrvUVgIhAM3SgdpfEfHSadfahGCcLS4kMgZT+iN88QjUH1Z9/3jm","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":595294,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgHk/iCRA9TVsSAnZWagAANHsQAIsSYqJuNwAXQxNzzNR6\n6dBoKEbwGJJuluby60abGyCEN+oWoV9hxuPRcKYL02zYWruAWWbnOx7fF0rF\nhuKzzqLybCOsv+PxxxeRfgu6ZjVYfBeKIHat+OaEs+BKQaZBzF2i31LcRFOd\nTDBoIqOr64QAmc4PUO+25hX/XojYp+43t91xmKw+tdKV6gY0uWNbSZd5Vh0Y\n42s9akWcLC3urACZU5+Ib9uWP5fXTnu92MdgY+pYCcKQvNHzaO0JzfvxoJQO\nGQooZXHX9Qdj36b4YOqQjcv540/tEnFahUeih1h1BM2vbJTfEbAVAAg4HSc2\n7H5BtyFxgz+VOReLtPYhLnULxlZFpyuYXGKpr/eGNABNAXPsy75RFEYcuLnQ\nyLUYoAKDeda1kOeoz+pcrCIsRqlWNnprsDu8kNXKi7YAtadRZd/ap4GzuQDi\ntFL5HsHE1xz425+Lfh8TryojKPNf8SPR75mu11iNSplYbNJQGdPmkrnFZd19\naHIshzj5zZGbKJkuV9zAIijAtjPLWYXfQtE+YEUwxTX4Sk6I5HyDPOXJEtnr\nszeevxwwY40BAGM4lWkB0iQFPb6E/R4VYYjFapgUWzU/pKIuOkX3UlKGPvFM\n+DvSin0V/JYS65zMc+2viANoFSxXunp13Uy7xsad72jY1w0ZK+gBSXB7Z+Ub\n1N51\r\n=32Qq\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"219a798218d8ef9c8ca4e510d555a72002c03564","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.59","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.55","@balena/jellyfish-logger":"^1.0.5","@balena/jellyfish-metrics":"^0.1.62","@balena/jellyfish-environment":"^2.4.3"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.96","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.7_1612599265702_0.5507358979871122","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.8-handle-deadlocks-8125bed0062aa4e8cb754fc1492e7b8fe6d51094":{"name":"@balena/jellyfish-core","version":"1.4.8-handle-deadlocks-8125bed0062aa4e8cb754fc1492e7b8fe6d51094","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.8-handle-deadlocks-8125bed0062aa4e8cb754fc1492e7b8fe6d51094","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ab7a91ab5edd5beba390027664cab892c0eb7eaf","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.8-handle-deadlocks-8125bed0062aa4e8cb754fc1492e7b8fe6d51094.tgz","fileCount":97,"integrity":"sha512-cqB7E4cm46fag+t1CdHAdGIYc+FGm2gqLfUIGZGgjGqgcZTvjWX0OWhRrZYe6o7YtyeOpJWZW93vYZNIWC6VWg==","signatures":[{"sig":"MEUCIBN4njkwc3dpVa3a1REMuC4lJ5yLP9/gO2MEVKlX3XzwAiEA8eK44GqQ/qg8Ph8gBOXh2hpDC/35EM28kCXdcc0Ei5g=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":597313,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgHmTgCRA9TVsSAnZWagAA8IcP/RXAqqsYhPerTaGS7iJZ\nfMncdhY8TFH+aRPdz7dzj2a7RdU3rg3EEYOLbzilxBV8B6Po6/9BhwlyOVT7\niPUx2AMqMtcLIZhfZFH+8wpaH+9l6iBo6ZQhSrh+s3x4uPKj954oDio56ywx\n15H/V1aRChs2bJu8dgaIP9Ty3WW3oZj2Md4D+OSTRGf+lG65ombcpDd+ocd1\n3/R7eisXLqOZJh+Z70a0w5WNNCUv5siJn0P8Kj03D3iHSt5yAGkvjTeWUTCc\nPMXUYd5c4x1ljWdEzBh4zDpBob9KokjSK5NrM9YQQV8Pe+yOLQnmmtbc+xbT\njqfzYtQQzOCQOUQ8Yx3X/mgWiSNISARUmIgPDjdfpasJ99LAvk6UMw2OFx0+\n+PbuBcSnUpkN6WFW0aRyAvjpH1EGtDcu8Ajm4O5UP8QtszTZ6bM0DxKNIg2w\ntokqb+y3HCALgyIG+RXGj3Z0SrLiF3s0EEh5YRDQufWb6lZZKS6kIwONl9C5\nduAUiVJk0Rgfqatt0sgsA50/cWQHFJPaKbMX+pB980UWNsAylfXrEVFSyF18\nWC921Fwa9Hci0s9jW+feQTNn1yTg2cgDdyx/CZ2uxIQCHq17kguEiIcQxtOS\nk7MqofnKjpcfoHh7kgbHY6w7xAiWqsqjtBRveZM2M7nsTZoArX4uAyxvqZib\nN3ji\r\n=tkD9\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8125bed0062aa4e8cb754fc1492e7b8fe6d51094","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.59","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.55","@balena/jellyfish-logger":"^1.0.5","@balena/jellyfish-metrics":"^0.1.62","@balena/jellyfish-environment":"^2.4.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.96","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.8-handle-deadlocks-8125bed0062aa4e8cb754fc1492e7b8fe6d51094_1612604639747_0.25230285596044233","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.8-renovate-non-major-f4d602a087585de2600cd79b984ff831c4ddd3b7":{"name":"@balena/jellyfish-core","version":"1.4.8-renovate-non-major-f4d602a087585de2600cd79b984ff831c4ddd3b7","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.8-renovate-non-major-f4d602a087585de2600cd79b984ff831c4ddd3b7","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b17ffaab2e53924a90540cc4c56d61d123d467e8","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.8-renovate-non-major-f4d602a087585de2600cd79b984ff831c4ddd3b7.tgz","fileCount":96,"integrity":"sha512-QDRf1gdr8puPYqwyFBPsRXGPbu5VAc/u4OuHZWdfIGQcM1n+mwhEtOK64gp2mLH14Qxvyztvrs58qSuDlM2FZQ==","signatures":[{"sig":"MEYCIQDmAuyXHkSItQJpwMuGaYkt4lTmEQzOXqGKOgbdK5/vLAIhAIEzc7337kzXLVMTPPLgt92OuFYUGwPPDbCAW7OwTtVu","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":595419,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgHmj7CRA9TVsSAnZWagAAlAEP/03lKOOI7v/Qotr2jHSi\nlV2iFqxVvOTmjoqyvzabXq/2TIDewgWXjNsJrGrfpAm8F0dnWURP9rg1jaaC\nJHRwyaRdw3aCT7YgRk0NpGGN7UN1B/m/qaKw1/57R2KtjisB0Svp01YgT+Ky\nKZxOb2mCliBD6p0T+8MXwoQo4btI/14/AGHtHsHKZHl1dUe7QhjT5FpLtefS\nYNPJpDF78soJ+saITz/QTgSy0YONQiIXZgof7sikowPe6DrWy3BAwsWvCqfD\nsBLfiuLOwFK+gtu5iQQI3bRuTOWslf7qlNRZVHXC6NFAEiSL5EqVrgU7J7K/\n1Q7YWMYfdJTwsp/0ze1B+5ZjJPOiSZ/eejVYK7dH7fciLYYBYUo1iO0TSFPx\nEROXiOQsmvEH0wNEKXK6dcK5cg7XNlUXfGwcshK2kH0fsmM3Zxn9gbfen5Cg\ndkBTL39scWwymfs+AAxKN5FZRgutr//dLu596kf/OZiQP0m2o0JwbereWvYF\nvRnwRkOnlAQjuwj+BVUqWy7wSbxddGanR7Ljfav3lrupabfFk47morYC/KzQ\nWBaWMA1IKz4sx8RCfZVX5smtmlsj7V7pCL62qbiJ1RHjDMqFCF4i/0dJPzFS\nTgS0jIVBhBHbcqDYYmEd5wtXqMzZId/AjjNIFqRYyrjua5XicxLwy2+2wibQ\nLBww\r\n=8nD5\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f4d602a087585de2600cd79b984ff831c4ddd3b7","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.60","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.55","@balena/jellyfish-logger":"^1.0.5","@balena/jellyfish-metrics":"^0.1.63","@balena/jellyfish-environment":"^2.4.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.96","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.8-renovate-non-major-f4d602a087585de2600cd79b984ff831c4ddd3b7_1612605690513_0.4012622905576939","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.8":{"name":"@balena/jellyfish-core","version":"1.4.8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.8","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0f8e729127fc7a6ba622af7c164028c2e5435383","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.8.tgz","fileCount":96,"integrity":"sha512-HDnLhe2DaCjBVi9fPk3TdvGFJap2GpzVAuwri2TO7SU0HHy+uHILRUIpLCKYH0VrbZEYBm0dS9Dqomw+oS4Ipg==","signatures":[{"sig":"MEQCID904PuMEj2xZr9lH3od4MonSZOrRNnYbs47n6XYJjfDAiA5hZH8zF0aIypBgORU6t8zV5co6PcIOfZ3wnrSWWSg8Q==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":595359,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgHmpaCRA9TVsSAnZWagAAzPgP/0Kb0g7N2C2iAt2+Ho/T\n7ogvDnipy7fB3s6cDKHBFPi8PlcLb8zsCXHBTZmCtVe1zU431Zqaj/YJTKnb\nq2PHEP35IO9jUoyWq4EYoQAxEUH4W+VJHOd1i5TxHF2IuWLSU87tGGaf1iMy\nU6GTB6v2bCLJXUQ5hKog2HiSv3Bzwiz8qDkGCnSxDs118FZMpkQyVnEVuZpV\n/2PNHP/t26+PbWzM3oT3u+5TcclRQ02b6bYj09772QSwBmkRqY5gNqwN3mW5\nNy0y+Ci2HTNGGcYsxsfPqh5bV4TTzGU+TqXFXcIHpHIVWv/1o7em4P8E/yIg\nZBZm+KsC/wDKTmWxe0mOtR/ZyT6hy5HcfQi6tLooLnQNB16/YyONIlLwdFS8\nkV/Blz7CI/Y1Lp1atep9RvK54JpWqX4PIZ58ED98oEzP3y/IRfLy/PTaSQCw\nCAjBh7C33yXHM/tMXZ8JxYfwJWJDazLsogS0fklAoe3jToyUWRLAAmJme+A7\nW3T+e8XnXUNVmrqD556zjr83/myHLN2OftYBnuFWrm432jLg5vAzMCjqxxfn\nJPaybIM3In4ZhAJo+k4H3fwZCgw3Uu60uLwcrXTx7fYFaxjruFJpr7jyObuO\nJfiQRR/UMd36kasS0PlRnKgXMBX4OYFcanWHTywYElObQprDiTuqe85ZZVJ9\n77hv\r\n=VELb\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"72f5d5cf3b63f67dace19dd14de1275990a28bb8","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.60","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.55","@balena/jellyfish-logger":"^1.0.5","@balena/jellyfish-metrics":"^0.1.63","@balena/jellyfish-environment":"^2.4.3"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.96","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.8_1612606042397_0.7270350342925702","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.9-renovate-non-major-6b0ca1ccd77336d0ce52fb7eebf5db111b1c8076":{"name":"@balena/jellyfish-core","version":"1.4.9-renovate-non-major-6b0ca1ccd77336d0ce52fb7eebf5db111b1c8076","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.9-renovate-non-major-6b0ca1ccd77336d0ce52fb7eebf5db111b1c8076","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9e4b2c798b8a330682c235c831f442d34f65a10f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.9-renovate-non-major-6b0ca1ccd77336d0ce52fb7eebf5db111b1c8076.tgz","fileCount":96,"integrity":"sha512-VkfUjq+Y/Pp8c0Zvw0iu/Pv87sFvXhU53AdxBz235WIoQ8Ho/s/Hqz06UYd1sabK2PBiGAhHoXeOSKtwDjHxOA==","signatures":[{"sig":"MEQCIEBE2YJXS1JOmJMUrmbqDs3sCe1Jy/wAJhaLvkMg1CXuAiBE4Ts1ia2cDLU1onEfOoXgb8ekdmWOZruAAh0SaeAubA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":595484,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgHmwdCRA9TVsSAnZWagAAtV4P/0oVAoJLtONo6T796LCX\ngzHKEPy07vJK1EqHbwWYu9gLfKD8oyiNjLCcqQpBNy9ZV5v+tFzFcOMUWzrh\nQ5yfduLSjuFMAmUqpr/wql+kCCZwM6UZgdNKWzGKzfHsU059/1McrzKPTb3j\nzli7Jr8cmLfOj4cZaUPTpL+aUnGvItUG5FdUuUJkdIKQrzq5VSF14uExILeo\nximPkjEu3PZ64m9r0m0XL0FpXc1x7q1fTHQwSjpUd3sBfPLWNeSzoRITGlzC\nwzrNrUXiCq60nj47UvCDgRkNoaNttrMFgfapC85pvWZ51FGWym6g8ChKZJ2i\nf5rBOeOd1GPWs5kifmYVe+AfrMQzJuwefkoGA9egKJYTOSO6hjPlWs7Je9yg\nAutv/rujC81SLjYrM7ov3SVPGLrR+ODwjQTz12pPpZgpzO+DCoXRvbyfoDJ4\nP5FsBnP6FyWus3cAV4sKRP5ZKm8JyEs46nXFz2JbCJHs+XZQsKDV4eco87s9\nLp8AhiEbIODpA/lInOtKFAF9FUkO72I6f49jpI+aRspl8P6iJrOdfvEOVJn7\nyyf6jgq7rtvSZWJmD56VQ6/FREjv/JKZ5qriBHe8BYYDenqvHVypUBbj/rqX\neHZKfFsBdbZzl+Ey8/XbnLQ1OypVFPyX7rWuJC1YBg4+F0WrIQ0wtMKYKKOq\ni1fS\r\n=JNZR\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6b0ca1ccd77336d0ce52fb7eebf5db111b1c8076","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.60","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.56","@balena/jellyfish-logger":"^1.0.5","@balena/jellyfish-metrics":"^0.1.63","@balena/jellyfish-environment":"^2.4.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.96","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.9-renovate-non-major-6b0ca1ccd77336d0ce52fb7eebf5db111b1c8076_1612606492738_0.744748354211769","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.9-renovate-non-major-311f1e67800490b4bcfbea5963e41b37cb30407e":{"name":"@balena/jellyfish-core","version":"1.4.9-renovate-non-major-311f1e67800490b4bcfbea5963e41b37cb30407e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.9-renovate-non-major-311f1e67800490b4bcfbea5963e41b37cb30407e","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"afb9081cad7a663026d25f1ab9aff2c9d926cff2","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.9-renovate-non-major-311f1e67800490b4bcfbea5963e41b37cb30407e.tgz","fileCount":96,"integrity":"sha512-S945WWMO8A/W9hr3iPEJOSoFUFqqt7DObRXOBfw4Ydt0WyS10Ca1SqV63wYIuPLxwChnGL47Ekli02pY0uzQTw==","signatures":[{"sig":"MEUCIQCBAfPXgq+w4tNvcC9an88Lka626L/IiKf7g5xunpJciAIgO4lfYdzU6d+Dx0QGxwijqsFDgWkHkY7m+f7KxGS42CI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":595484,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgHm0yCRA9TVsSAnZWagAAW/IP/RWGwshE/aaEahWVgYOU\nHBbQs4jKtWmtanYOp64EKbe7viD0G3INgx8UtXnpUSuraH75QaVKSysxIARM\n4sZzDzBmBWz309lH4XbzzLGnlRhQ9rdpA409luHhvJymP8l3yrFWOBBbaY32\nJfHvCzXK+SpQtcamczFSwB5iQFKxIqPZYoIMl3IT8EpUlc47VHfa8K5SxSC7\n/ltbsW8BoLqmwT47Q1d9g2c+I8uuHf9SCSVaPllu9WaNjhnzgfp/ZEHHDLAv\n1mYDCB7i9iEF29VduxB3Z82/0L+3YgpBsMGZA+a7PwQu5nO/nznihd+ND2s6\nerL2LJJmjQ0+vczBMBuhhaXN2d9M8N0m5iTvK20MBbE7aWSwifBbrts3oKrw\nkoUG9AQarbFGuHqLW5xF8opqLvH+m/uLawVuoIC2LMT8WndfzvrSyhpemcog\nrunyzzrxlJfsq0XBSKRC1tmp/gvnhdrAZZ5Y0dyP1g1wRggmh5rDcI6AlgmM\nlIblDvk72POrCrmyoCjk8Dy9vV+V7jKD9hbnx6UI86xcCAXHyKam3a9iZP5U\nCz5Q2f26SjUM9rvwLUpMh8ryKxSCD87F/IabmO0MyFGvQQBDFTycvahQLt17\nQG4HYwgbL1Djq82htRmT76tH+ODzMVR2fc10cZSrikfjA07KV1XkTEnKuJu5\nthL2\r\n=Asq1\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"311f1e67800490b4bcfbea5963e41b37cb30407e","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.60","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.56","@balena/jellyfish-logger":"^1.0.6","@balena/jellyfish-metrics":"^0.1.63","@balena/jellyfish-environment":"^2.4.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.96","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.9-renovate-non-major-311f1e67800490b4bcfbea5963e41b37cb30407e_1612606769771_0.40765996437119534","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.9-handle-deadlocks-37af21e0966fcc09c03d4d855b7a1bc5e85b889b":{"name":"@balena/jellyfish-core","version":"1.4.9-handle-deadlocks-37af21e0966fcc09c03d4d855b7a1bc5e85b889b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.9-handle-deadlocks-37af21e0966fcc09c03d4d855b7a1bc5e85b889b","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ffe79b2f9c0221c789432e63e8bc5bb8be8570a4","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.9-handle-deadlocks-37af21e0966fcc09c03d4d855b7a1bc5e85b889b.tgz","fileCount":97,"integrity":"sha512-nnoBa8b0ho8hPttlrKLeQrubSHmAjbH+aytm7osKb9t9g6zHz7436fC7dRV3QEUlGINY3WH2uT3R4O9npu9M2g==","signatures":[{"sig":"MEYCIQDcE6vHURo1X6sloRDCE1Ktx7EzBkLGXnyA+lltTCs50QIhAPjPpsU4He6CdylwC75oBGoUNR7FQzmugwTj1+jLSA6x","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":597378,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgHm3eCRA9TVsSAnZWagAADb4P/iRa020gWFlZk2GksqBT\nuYaegBgrNlaUPIcTmzXS6T+aSbGSo2l976PH/itGpp3OcP4NIvA+SsWO7he9\nYPoY+oaCSKcv+ykE0gSCtdMwGvmXZCEvxvNrWQiTphW75hXsaiwVkkenBDyh\n083m1ruHPi+3uzFgc7tFblYHmSre77kB17+Qa/MVvWQvF5SByh20t4bEZ95r\nbewy6la1aDBok5bxd4KbIeSQ4/iV3KmW3qpe+OL5Dcxj28xQ9uBL6lFvrBXx\nH9ruYVIFt0lGL4oOvZSAtyghChB0XcfO1SaCM0AR+wzOfKPjhI0lmd45Moiz\niBnuBjaLLFrW4aZGWTE3s4HvVq2lM8Q1oxm86cUyRYXFka9c6A/3m4O4nUeu\nlRyPLAbImj21qnMj5DfGowVnTbPo7JSrNwIvbBJWhq1XYFdaVTmPidhop0Tl\nMoEZ6dxxNJf7KD8bsIoFFpjFbfflYWOc63aq94X3Yj16dWl/FL6+cSG9L0d4\nlyR+y4NiKFqRviT0jf6FPou00wHT1O0I4F+7nVa3gsZI6O9gVg6zJ25lOLyy\nQU99dGphdyMSITB7WdFTMgfTnAP4Ok/xAj1AV0LEiO9MQyZiQ8Cwq3zwB1ka\nvmS8zXqRHBTBz11oiAInD8eGtXa68XITK8H4xxTkSEbyjKSfLd1ujnPWA28+\nvySd\r\n=T7IJ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"37af21e0966fcc09c03d4d855b7a1bc5e85b889b","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.60","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.55","@balena/jellyfish-logger":"^1.0.5","@balena/jellyfish-metrics":"^0.1.63","@balena/jellyfish-environment":"^2.4.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.96","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.9-handle-deadlocks-37af21e0966fcc09c03d4d855b7a1bc5e85b889b_1612606942013_0.5843416217955575","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.9":{"name":"@balena/jellyfish-core","version":"1.4.9","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.9","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8a113afe49128a291b78672f86f7c771fd48348c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.9.tgz","fileCount":96,"integrity":"sha512-+d6ZltUR6WOG7YSY7o7z5uojeywX+xXlbKj9UVJW2oQk0tFsmehrztUDGHEPROoWYgxwwbY+SoavuA/Rhz+TQg==","signatures":[{"sig":"MEUCIQC7/JnfnHpagND9z5/wDF36HO9eh91pZiRUw0JZRs5+QgIgPTUw2u/9Sgrs3o2c6cbadBJMjtqWbAiLRBrSU/iJ9og=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":595424,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgHm6bCRA9TVsSAnZWagAAQcEP/1DtC+hEGkmIXZsvorse\ncmpAwYbhz78wLiKL9DxOsvgaXgB/ekZTlE5oAEarHrFtelMCYEDTAYEujlp0\n3Wuf7yj/AFXFK+ZSjkZpksMBWf/p6o/gdPAzdnfUSBYTJxlNgjRxmp/QgycI\nzBwdJnDnj5rlaBeOiih4ukJRRY7u5Azz7/teELYsUy9nP8ubcEc1/0/w7ADT\niMGx1uRwOPqcPWkqD89HJUnI8t1vdyun5UsmQbE2XNKuqrk7y9QkZrjwYclm\nICeLaIyvTm9CmvMG4myOWXCb9XKwkz2dHrH5wj6lXUJLvYRv9PsIduYUwhfH\n9bvRGxW3xV5JGX5Q/g4g333PmtkKiZHRlIbXEnTpPne2BEiSlEV0Y3E6TI2N\nh2qQrMZrr4zx9zJwgXk9N4mMvf3JEKaYXmmzl3MWUIdtqNfa8sfmf85Z/9l7\ncTeTmui5XefLv3gQDTqbrhgJpe4aSkAx2De1NqIR6djUaf+FI6IVqrUHSGQ5\nouzKBChmDuc/TnuCzxVs9Qx4KBrMehKi38faWadmS99QtQBqoJExB1FycmxM\nUWTj3RiJZiKDHyXUJ5LWHiYVVjPV/vV1JN2VYfewXKuBmDBx7BfKYVqATkEx\npFIVUiBP+j6jvaQ33hB5ngVnIOUSTfiwC8x9BIhAZtx8IbtkCWMKEhoUgOX3\niVET\r\n=DkPY\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"3636eb4efb127f4ae712dbf4120018778de9595f","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.60","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.56","@balena/jellyfish-logger":"^1.0.6","@balena/jellyfish-metrics":"^0.1.63","@balena/jellyfish-environment":"^2.4.4"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.96","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.9_1612607130791_0.27862587674191075","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.10-renovate-non-major-d8a4937e240214ea752cb0f685974de960573638":{"name":"@balena/jellyfish-core","version":"1.4.10-renovate-non-major-d8a4937e240214ea752cb0f685974de960573638","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.10-renovate-non-major-d8a4937e240214ea752cb0f685974de960573638","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9a1963ba81cf12ef8ffd7d3a15c07becd6d3b27e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.10-renovate-non-major-d8a4937e240214ea752cb0f685974de960573638.tgz","fileCount":96,"integrity":"sha512-pWzSU2S7I/c3qTxpp2HzstrhgHaD1TGguv26qHZb0vbERSXTbTLE/KHaSuGbo2QwdKMkCuRi33Lv7ZuR06skCQ==","signatures":[{"sig":"MEUCIEmoKj3Jd39kstO6kaYXL3IxAiheFiFt+5ij5x5SSkKeAiEAuVGRXQct9PvbcECf7/lM3tpligpu/L13QWQ4jxTNQaI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":595589,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgHoNBCRA9TVsSAnZWagAAgn0P/Ak9H2xHhPMaX005RBnG\nhwtPmIP1CQyQk0RJjWuISskgktcSwAR0vpNfQf8am9iPvKeQq7ZRbsv7GDfs\nrIeYBo7cVvhCH5HBHYoZrSzgNopJU+DD3TeNxd2FH5c+NXSpKnVIXxU0p7Aw\nsgsCq8P6yj9YP8xE45YPWmvEG8jOcnlvxGQJSWf+ViT1zd/4EMcy+9PEm4IR\nfRcXVwEZR3jcqbeAepfHA7vr6gNtjEhSx9iDR2A7/0OyEZ7xCqy8pKLx5fFr\nnO4EP3D1WDp1BVUHCIe85AUYwUs+gVgMV1G8uKvYTL6Z68Bc16Krsw1CZJVT\nn2JqUSr9scXkqqEQbbBQ+fHnkX440PU3lWt4GA8w7LVQi3mmeHe/1SkrZFzs\nGHuOEEmI/gtqSUsgdDVCkKjBt8Iw3im6H7M2wDP1WBxAGrNEguv/nIqBIRxR\nAjt5VyzNk8/F0hZWFbt5gRBzN0LCITdoV8xW25/SrmbTKCfzaTpgaTSc+ia9\nmLXkN+44oYbNoFswTFv2ZcUuFZhrfBd8IWSxe3lVaoSMDMehoOEniXvtVXFU\n23c5lpSCOr7jo9QraHHUU3oQ7j+inTrvSoshl1Cv69deVP3f0pvNWyCFUE+u\ngAFJ2ibmScnXJCuzEWm+tXM9zuHj3xb6c6+Q5xBARVkyuQdoFS/nUPz+NHog\nOqv/\r\n=OlCg\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d8a4937e240214ea752cb0f685974de960573638","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.60","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.56","@balena/jellyfish-logger":"^1.0.6","@balena/jellyfish-metrics":"^0.1.64","@balena/jellyfish-environment":"^2.4.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.96","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.10-renovate-non-major-d8a4937e240214ea752cb0f685974de960573638_1612612416669_0.8416745191713997","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.10":{"name":"@balena/jellyfish-core","version":"1.4.10","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.10","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e31aa5649b05353ed1f314faba6272ae9256c9f4","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.10.tgz","fileCount":96,"integrity":"sha512-UoLCnYlXovtWed8Lz2CtnCZuSFtXYqXJVjZr+u1jg/wTAha8Od4s4xOiG9QNJ9UMI4k0VQhMztTHvLa1rpCHEw==","signatures":[{"sig":"MEQCIHPqyvMvMeN+YVlmIfc7m9k+D49lPZK43JH+6/FwN8WFAiA/dk49r1zy0bV2eNffS8WFZRQySJxP8kjB5UhUnf86Pg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":595529,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgHoSTCRA9TVsSAnZWagAAWAwP/0C1j4QHGi41LpQfl3nB\nzld9zVIPzJG/Xzoj2wZ0QsiN0sDJ9NL/ESunlmyjf8g5G1dyJ4fhO/7Quxhl\nTv4cHV4S2ao9GTYbBB2anYV6OZxn34JgiFIlyIXSQcxAYPorz0FksublA3Jh\nGyKjIDxyE8M0ev201bmArAvMRwUjRaciSoLqGyTPWQ9pQqml8ez1cVVHBMpP\nDAdMd3cWYHDSjXMux7LUZmD1zmpioCHw1mL1bzamJRmxho1QY5mS1uJkVGzf\n2Eiro6Attqz7pl3boavUpvVOlEaEakOQ1cohWs30aEYtDmxzhMp4n2fV6t5J\nphCVzD5zmUkuPqakWtGl+ImtEnjnC5J93xbbjhqEGAK0++Tno2ndqHAQbYJl\nG9zwmuV0ytcl3ytDytP1z8LTsrKer+WSPpXQgqJr1Pwk0rlZDmn3xLxdiB1N\n/jkLA0rJn+/xNJBWFFDPjIEU9WKhXclY67uMVhmXG0ViKEKL3avefD5td0r/\nAdwNH7SRqXSXZLLCsml01QXToD7wTarJpjG6WTvXmZlXc9N/C1Rw0coUivbd\n0zToofgdjNE5q2FzoAx3CplzZ3JoO/nThnqv5ytlQ7xeIXPkSEY7zK/B08Qq\n4rqCcOghFEfcH2Zqd+7IA+e8lkaPvjo17K1hE5L1M3zduekqkgnEfWnq/0UK\neLjS\r\n=jEmD\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"beba7f1da8b033dda465b0e71d26f785129372fa","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.60","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.56","@balena/jellyfish-logger":"^1.0.6","@balena/jellyfish-metrics":"^0.1.64","@balena/jellyfish-environment":"^2.4.4"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.96","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.10_1612612755133_0.15311673676409998","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.11-renovate-non-major-ee25b8f6541491d52d025c795c85b12786125fcf":{"name":"@balena/jellyfish-core","version":"1.4.11-renovate-non-major-ee25b8f6541491d52d025c795c85b12786125fcf","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.11-renovate-non-major-ee25b8f6541491d52d025c795c85b12786125fcf","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"7200e98cd5959ca61222bf4a9cd9fa2cdee6f579","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.11-renovate-non-major-ee25b8f6541491d52d025c795c85b12786125fcf.tgz","fileCount":96,"integrity":"sha512-WUzhlC4lsrbiP3L5N+y+uxs7q2A705arnHShWLnH1ATXVvAU9nhzP0E6ljBTA+yeLDABlUXuKsUomwO1GutVrQ==","signatures":[{"sig":"MEQCIEeVpZdwAuhgrDF1B5NaG4o1666Sw9rN8kRdgAIoCVSlAiBbXdVB6n+vWQ4pvZgKeZWtM5WVB7LjhpHFxQAduRf5Xw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":595690,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgHoVICRA9TVsSAnZWagAAE/YP/1aVRRkJru0P7m5RXL03\nEmAfrnfGz8wNlORHxaDikZp2cWGUPuInuBeZVlIaWSCDg5h7HGQp1fuTA9uM\nf8mFbVCZaGaOkmSeWvCpbzEePSFHOoIrK/4Gcb3+2GCyNaHXl9Hzn+d2zJJO\nx58+m89B9HR34iTB2GTTaH4FDWBGAorwHDGrOtbb7e8o7SASSp0ydv9/E5Fp\nRnSGNxw5rMPjgTuPdDlkbUsEDGYPXX/qUofJImmyHTxo5eUCWBsoIeTLQhNj\nhS/EQOBWi5nLpzwrwmwBb3kWW0HuvgmTTcIb7gBOyU87INexcJ7petpzrgia\nv1l78TFMBpruVP1pYj8oli0exZyGzx69MltnHVDc/EvL4XN8yMHlW6hdNlCY\nMiNTEYiJqytqGdVMnE/ZOsRWI0mkf8ePVpuOGXEVyokyg0Lb/ot4jPrEZWRq\npMwaUTOLvHSxFIX8a1DmOn30MkUktD+sUsOFysToK3UUN71mQ1fFGQ5eHlMF\nAXT0fyzHNzD/YP+8bve+p4DpgmSV/9tS+oR8XzJkN0rQ/+mAMf2pzIPQnvrm\nLGMh+Omc2HiWEvOeNc9humM+sW0D+z11QqJR/2Ib8dIotyD7CgKSvhWviFiJ\nOrbt2ayxCIDOwo7r6jzXcOyDszWXSZPAaU64cJsVoN/v/pT6XS5TOpi31LfD\niFGl\r\n=Axim\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ee25b8f6541491d52d025c795c85b12786125fcf","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.60","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.56","@balena/jellyfish-logger":"^1.0.6","@balena/jellyfish-metrics":"^0.1.64","@balena/jellyfish-environment":"^2.4.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.97","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.11-renovate-non-major-ee25b8f6541491d52d025c795c85b12786125fcf_1612612936151_0.6601883710620684","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.11-renovate-non-major-f2fa1b6e5c944274576337707de673b0708996f9":{"name":"@balena/jellyfish-core","version":"1.4.11-renovate-non-major-f2fa1b6e5c944274576337707de673b0708996f9","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.11-renovate-non-major-f2fa1b6e5c944274576337707de673b0708996f9","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1b4c1ad806e856bf9020689c90ff4b666af0c576","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.11-renovate-non-major-f2fa1b6e5c944274576337707de673b0708996f9.tgz","fileCount":96,"integrity":"sha512-3HWxUp7dcA12TFhLpivL9m1AJ4B7RT2HSn2mWgB6iQBrmfU1lVnc3HJ+Q27qKOXR2iIExh74McP+/C8qfFBkTQ==","signatures":[{"sig":"MEUCIQCvhFvEhe6kJbjPe7WHjFKwbsG7NG0jZLUbUNPJ8zS7jwIgcNNuabUAhYKPx1alopX71ilv0urSd9zxvYcAYc1ShUM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":595690,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgHocgCRA9TVsSAnZWagAAt7IP/30vMsKudZN7G59/duty\nSq6KI+MiMcBAp+ujjqoLlp0d8VRKawC6OEdcSJguYQUqJQ2/yCLOJYfZSDfw\nHrt62v9/PG+4nAmA4v1L0idASFPEczjgFV56v0ZwAZc1nArJpaShR9dRtLF9\nhPUOPe/9LSSlWup4G3t0iRNh+Eqx2K+9kGGtg13MTVvSI0j9oxiA+KmZ80pu\nyYNsP8RpFZspAw1MQf+sn0Mn2kXgdu6k8Qjqxv39bYepjsA+Wg5ohnh2gh9S\nvMivvTRvwvsssu2bjawCwa4BeHCZQtQWT9LzbQ7WoKLmoQQJBug/60xaajs3\net53b8mcT2Gd8uL5yZlOb+c+UK9ML7JKDC6PACCuR0Q+wwRwh52ebPM+fssw\n8lyW8m12Tv8dpBuoKW/L0li0MFXPfPEgXk3V2Ln7l4Tow9ebv08zke70+EWs\noIgRmneoBjAGt+RWM6mRifNopXB9DxThfmCKE/TNcloU2FtEhi0pk25IdwY+\na1nxj2nFfG4v2fmy5I8Y48zVThk5voNQdasCrAyExlaAV/o2/WUOjbGRyKto\ndJzSEH0Fvtcairmmi4BNt58Od+2knA5o/HPgEcAvGMuaQo9yK+NtEJydmGWf\nKNinkdvq/PF21yMPWBmPyAmjae+r6frbrpioSHn8hr/UchYq9nqglYmOtvxb\nQMXQ\r\n=Yr6U\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f2fa1b6e5c944274576337707de673b0708996f9","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.60","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.56","@balena/jellyfish-logger":"^1.0.6","@balena/jellyfish-metrics":"^0.1.64","@balena/jellyfish-environment":"^2.4.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.98","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.11-renovate-non-major-f2fa1b6e5c944274576337707de673b0708996f9_1612613407959_0.9044947889169115","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.11-handle-deadlocks-7e057f48788c358ba39539916487a99354f7e328":{"name":"@balena/jellyfish-core","version":"1.4.11-handle-deadlocks-7e057f48788c358ba39539916487a99354f7e328","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.11-handle-deadlocks-7e057f48788c358ba39539916487a99354f7e328","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"981711374b332da3675479597fbaf0a681c504eb","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.11-handle-deadlocks-7e057f48788c358ba39539916487a99354f7e328.tgz","fileCount":97,"integrity":"sha512-36Xj63bh8QTzONCbCamYxm0//xEulGcb/AZ9mLJeHCi1KvkPoyrODRrJ+ipEKu2SuS+vlgkiGQCGGwsUvSIJtQ==","signatures":[{"sig":"MEQCICxamsvh0q6CnopD0liMUeJJKbnCmrTwV7iuaP1LI9vyAiAAoNmX4Gd4lbEgw2dD3nLOTs5x0eAWkIqsba0ZsTOG/w==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":597549,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgHodhCRA9TVsSAnZWagAAELYP/jRRKZkpaTOW+29ldS2n\nsIW5/S0ApMMKePanURcCcaylpfKS9P3d9O64lnieFkt2RaUBBYWQmaSnrLdP\nO4vis+AbbX1FS+xC8mP5XbzTSXdj+LGuRNdW66m/QmdUXKTZ2ASuRMaU+Dku\nhLmVW4Aw6hTcWVCjbGDBHTMlK5KibvOSdgRGDZwEsjfb+DzyBuu02XL9M+YV\nIXVjsABKDPltw/CgJVBYEljrWpwVh7v/LFPalXS9eNYlNFHJJou7rZFIqGE6\nha7jBLZrVzBA+a0mrRcVsrdi3d1HVwV2CKT8s9O47IcCSrEzDzmkyRZI5rX1\nK1XA/ahJkvAl4sOL0Qck4g4Lnq4PLkMQkRTu24f+WIol22cdXmK266na4oCX\nctaQhShyQXYUJxCbhrZO7hykf5UL//c6toqrUyD8ST2zHwNB+OflMHAxZq6C\nOIT+apmQZkZusRhkUuk31S7SB8hFqDBn2OLFnC6D0GWan/6AYC8C0eNq5anA\nFiaS/mFwzbd7ainBgndCj8dla7q0NE7BMUWgJ433I1qIPixBCkTrXH/5V2qK\nUx/15qHH0lQ0nezro4JpqkL2BG0jmG9dygyGrhLfwArgaeFiCgprzOqWDC4d\nKJtGysOKeFlBDj8g5g9KO/p5jKSTmq03Uwo2i7k2E0iD9+kOatnp7JXmb8CJ\nGEb+\r\n=dL/M\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7e057f48788c358ba39539916487a99354f7e328","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.60","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.56","@balena/jellyfish-logger":"^1.0.6","@balena/jellyfish-metrics":"^0.1.64","@balena/jellyfish-environment":"^2.4.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.96","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.11-handle-deadlocks-7e057f48788c358ba39539916487a99354f7e328_1612613472763_0.7542690213750602","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.11":{"name":"@balena/jellyfish-core","version":"1.4.11","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.11","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5d859fecc2d573ebc9bd3ed3a732c9676b306b7f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.11.tgz","fileCount":96,"integrity":"sha512-/CalIx6+D52ZwYiRnNjHaNW6TtXr2nHAFfn0+yJRwkdnUOWsarU2bvEiVKxVBmzX6SK9PW7KkW+AQVX1bFQ4zA==","signatures":[{"sig":"MEUCIQD2m3Kj8Va+qnXho5NZasXWTz6TOO2/hk5BRKQ0fWHbCQIgHb9ql+guUH57NO7INxQI6EmWFWMHTk0TnGqfmLkIG9o=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":595630,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgHogxCRA9TVsSAnZWagAAlk0QAIQOm7LHwwrFQghVzlO1\ne7uvFJ37A0vnCE3I5H71GC7Fdl6d3o3R9t2j9GIsdfDa9frGyos7/15VoJw6\n9mULhqh3tv7/IRC4b0zMJXVeM92QuZNaYG+gsV+P0G1kWqEOjYXjlZ2k/cQz\nvskc2Kq3Syflys+8MpxsYj04fr5dvDtMxTDrhreu3clp8p3D84O02HVRgCfs\nrU660aBEOBUpVhLN9CkE3iZlzwmDG3jlyhKGU4U+BNsbVT2UrFEZDawiIe3F\nObUbowGiZBG6tIJH11COQTVLv61/TpueBfNYldG/Z9yjN1ipm0OH3gKmicve\nfRGvhE7S1yLnFa8Kl//BKHDfHUtl5EsEA46bzlC19jZj17p8PDmy/61MzeFM\nmPNsn6FqVMX7hXz4bcdfQLzT6R3E5NMlDsD+rbchjxI+Sm96G2LV7jVaoTdh\nnpx/xxey2Pl23vW/e3beW8q/8qjveA3UmOSPB99Pc2MaDYPk/K2mYCyzgJSD\njx42fUE0EpE8yxgEzYbaptY7SxsvJV68sQNa2sonuyBdbYplVKFg1Btody1j\nU+dV504QLXwUpvYKskjNl7usjKVm+LIpZgoRF3dzO6Eq0FiXUfeaHlXQMTab\nJMWgWrw/jsXACET/1yeQWrPAENqqVNC6pP8MVvM0ej5YtKa+XcGHms0T9nhi\n8J4W\r\n=7rJ2\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"86eaae135125a4026ef26c1bc5632877f8dfda6e","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.60","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.56","@balena/jellyfish-logger":"^1.0.6","@balena/jellyfish-metrics":"^0.1.64","@balena/jellyfish-environment":"^2.4.4"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.98","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.11_1612613681371_0.17842049961319129","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.12-handle-deadlocks-bd968e48f7433008f0b937d0047fac947c12de2b":{"name":"@balena/jellyfish-core","version":"1.4.12-handle-deadlocks-bd968e48f7433008f0b937d0047fac947c12de2b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.12-handle-deadlocks-bd968e48f7433008f0b937d0047fac947c12de2b","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"58f2ea7b38dfe34b6b4240fe56e2146b47ec5e6c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.12-handle-deadlocks-bd968e48f7433008f0b937d0047fac947c12de2b.tgz","fileCount":97,"integrity":"sha512-Cwu0rFc1KdOHlzFbL/RVARw3QoFw9lB8nUyo2xIie012ZQWLdMx5R4FiVwmHzZ5EBEnJzX062iJUIrAS/xygVQ==","signatures":[{"sig":"MEUCIFTValAfVnOQFu5T9Yap6Jz9Tp4ZI/eFehJoJ/S5SIhdAiEA3gt7UiG4uXFpEWJImy4D5ux7aWrXopbZ1fjmV3h9xgY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":597650,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgHpNtCRA9TVsSAnZWagAAGsQP/1vrV33g5fzHO9waUF9E\nNHENskrXZOlkgDRkvq6lPo5Fu1w6j465Osg0C1Yoz1Ihd+d0oVPw23DaXCmZ\nSqPjw09cHalXDCXjZGvnenM7D4K4mQvHJiLFuGOfWBDvVem4A9juT3ZWhsTJ\nb30E/ThWk50L2mXY2gOA5wkMXvA7OOxtC3QARmDjyu/qgM6HpC6SZCtWlbzr\ntnZREZkLWOmrzQ6k2RckzPDjkD5FvEbPXUfga9aChNMqBpQqlpG6bKZ7Ehs7\nT54WLCHXTxIGWvnkEuCmmAwqmXuBRhqwXO4IsQZJlmH6BLQKZYzXn5keTDgI\n7vQ5iiXB1o7QZHdjtZaoSTeps7mOpC8C/zJbeE7Avi6/B2bp0SyxhCtjev37\nJV1i2YLtZ1sGznJgV4Fqko5Si8X0xjgDsVhcu3KlLHxZqeP1D9PV+RpguV0J\ncWDJbAmpXcrsAjF6rdm/MyuTgPwnvaoTqZdC5+6I5iZKj62RLfQ5UQcPL2aL\nwb1mvzlyzNmilEPTC7B2Hawa6NlG7ZE+xZ/frEjcFDFJf56q0kppcFSvAF+Z\nFRlmswpV5OzLG5+BRgFHLuVCtRyQJOvvdAOC4dLyjhVX28cmPGn7JTJTF3aw\nS1K6u1lUujbOxch0cjRjZSjQPyuEoPkLFM1nQDhXu+YqXyS4T7eYqJ7C51CW\n8+aB\r\n=dW1T\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"bd968e48f7433008f0b937d0047fac947c12de2b","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.60","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.56","@balena/jellyfish-logger":"^1.0.6","@balena/jellyfish-metrics":"^0.1.64","@balena/jellyfish-environment":"^2.4.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.98","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.12-handle-deadlocks-bd968e48f7433008f0b937d0047fac947c12de2b_1612616557322_0.4855253506568884","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.12-handle-deadlocks-4ff91d80bcaef0e1470c9eab67594a77d5f77bf1":{"name":"@balena/jellyfish-core","version":"1.4.12-handle-deadlocks-4ff91d80bcaef0e1470c9eab67594a77d5f77bf1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.12-handle-deadlocks-4ff91d80bcaef0e1470c9eab67594a77d5f77bf1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c7f5bac70a8c78ca56813a70b4479078f36e2ffb","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.12-handle-deadlocks-4ff91d80bcaef0e1470c9eab67594a77d5f77bf1.tgz","fileCount":97,"integrity":"sha512-bvhHo6Jreq5JC0Dqp9UJxavL4bbF+j42PY2tKlIRIJxupJBhQV4Ndt7GiG65IU9v3YM5VQbOZxLDya8hjndOYA==","signatures":[{"sig":"MEUCIQCb/xoJxFJzgUgKxJD44AqONjhIzMz4jrNAgAY3kgBG5wIgC+x5N8Y8Am824rU32dzJ+psCnsqIq0RMdrSOnBRcr5Q=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":597764,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgHpb0CRA9TVsSAnZWagAAIToP/1onA0EuSpW5XZ0dchHV\nCt+tCVrrxcsvjS5XAE8MGG8KCF4CmiIeGxZ1zGdJzPWt/xYiuqYrZ11VN2GN\nt2wKpItcD5FjjjuLSjzqPSIpusZ+eosPAdXk38kRS8l/6lru1RVjpxfVOCvq\nca3ExSQtf9iwwEvBfgayycKQKwj1Yt99QsF87bkisx5VWh/rf7xYG6NF29D3\nlyeS3YJAkSi0xkBeV8Vz0xGxYLSXo1WFgP6DsuJ/QUxtT3n5JCHsFrIebpKm\nKJu6PbFiuVsxzFZS75oVKkTrhZc89q9mscrZjQcNuf7qLJYMFNd2pcYOXeN5\nzO/twAfxo2TCBlhUOYbHRrV+wOxS/bDresoXPMkRubT5TREWKArX5IvQwqXU\n8sx008OPx7NW/fPz0rFafpz3y7PkxoH1KikVt12eHHI+ZCciDnO4clbsrTQA\ni6+UCXZNgcMPqUmI4OLz8ijGMVZ5CyMplksGdaO37vl8N4tWkHjGMmChB7dD\ncLSHIxd2mAxXJvPBf6Htu3P+i7kvvQxHKEFMSpimwpYKdj6r4iRME71ycXHR\nO1i0WgOHArFQ7dZ+eKcYgOxeZ5ClwW1TO2WJxHeyQbTKI67F16aVh8whKs7d\nReGylWHYMwwfzzQr0U2r5WoPTLqOp7bjscF8BOoG6YNDFUVF3s/uRLauY5EW\n1cb1\r\n=bDzu\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4ff91d80bcaef0e1470c9eab67594a77d5f77bf1","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.60","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.56","@balena/jellyfish-logger":"^1.0.6","@balena/jellyfish-metrics":"^0.1.64","@balena/jellyfish-environment":"^2.4.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.98","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.12-handle-deadlocks-4ff91d80bcaef0e1470c9eab67594a77d5f77bf1_1612617460499_0.6589187527771447","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.12-renovate-docker-non-major-d2a712066cbf0cfcbf8ef010b031d1b7900b51c5":{"name":"@balena/jellyfish-core","version":"1.4.12-renovate-docker-non-major-d2a712066cbf0cfcbf8ef010b031d1b7900b51c5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.12-renovate-docker-non-major-d2a712066cbf0cfcbf8ef010b031d1b7900b51c5","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4040f74d7b4a6ec16a3976141cf267ec1e37fc27","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.12-renovate-docker-non-major-d2a712066cbf0cfcbf8ef010b031d1b7900b51c5.tgz","fileCount":96,"integrity":"sha512-+kK4DZxkBFPRxFIjQMwFhyFBh9rLwQoU+2GctGUilfY4jIBjgswnPL07yTHGpNwAH16jc/7rc5yX/fXs4gCjHw==","signatures":[{"sig":"MEYCIQDBOg66etYyjAlMapDjEN83ALVfjqsxqX+2VG+zij7NHQIhAIz+8b0JepsLrRXhmTOh9KENTZPa1m8hW2MXDIemsM4k","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":595798,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgH0YBCRA9TVsSAnZWagAAn5wP/01An61mvFTcoZnqfB2d\nODqc6y2kEnLx7IWmx9hYWmj23mWEiP1OgzFNweXfaBKXx/i4utu/8A8QVnM1\nDZa4Z/eMMXZ+8qYKQvAalbMf/mQM342mdS+xMGYlSYaiD5mX1FAImyU1ytKW\neo4XA2NX2BspLs/LjUYhYLXbgTkyvaRCy0pzNANZD99CkRSSd79qyxrINV/Z\nhRbtpIT1T1kZfDw9qQRaNpfCo/yCGs8FaOD3zpV4oaJmHS8Gu3iazvs0qXQK\nwQCCdYPk2GlaOb0AD3Ji2EXZRQ+mjn/OJztsy3y6kT7EYbiQjpGlMbhj5pH7\nQ30wTggb7exXqhJSy8sta6LFxp5d7b4NQcSebnAWLGdfgRrGx6tEsdDazgKU\nrMJ1Hz16s46cZKo77IxQkwTy2Iym58FLgi4WxdmG+ypmnBH29JqaL41LD7RU\nSt8TCfHMhCz/Kw/GBSgYEpvADytPi4MC9ZsA3MjkFYGpDJx6Gd3Nck/Z5EVd\n3+KIXyXCwWdQrfc5uWkRfgkt1XsRBoto3Tm7QAuojzACTMgRFp2+vL/WowrI\n8oeGaf8t3vfhNSeT88/I2cbLd28upyniOs6yZeZV3A/Y636yTnZj8GX8Hnt3\nKNj/7RUGuwoqQ0th8vu1CHWBHtryeieJvEnYSZAhpEuq+6lS0CcxORaMwix+\n1mnc\r\n=glFF\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d2a712066cbf0cfcbf8ef010b031d1b7900b51c5","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.60","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.56","@balena/jellyfish-logger":"^1.0.6","@balena/jellyfish-metrics":"^0.1.64","@balena/jellyfish-environment":"^2.4.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.98","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.12-renovate-docker-non-major-d2a712066cbf0cfcbf8ef010b031d1b7900b51c5_1612662272467_0.8405721468872225","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.12-handle-deadlocks-afafbf3e48043a58688ceaa2a592cfbd3f2bb36a":{"name":"@balena/jellyfish-core","version":"1.4.12-handle-deadlocks-afafbf3e48043a58688ceaa2a592cfbd3f2bb36a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.12-handle-deadlocks-afafbf3e48043a58688ceaa2a592cfbd3f2bb36a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c9685344b250cfba88217a415c8f8e561d55c347","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.12-handle-deadlocks-afafbf3e48043a58688ceaa2a592cfbd3f2bb36a.tgz","fileCount":97,"integrity":"sha512-AlX7gkdR4ENwvqIbaG/7mFSj7Nw6C2f+l/+bb9xpyl0zZp611Wvug/qPAvNO/AFYFEKEldckc0+f9fywaMWGZA==","signatures":[{"sig":"MEQCID5msql9Eh4i4Gz9eTyoeWT4Mvhy7+9Q9UeKKxnohKE2AiAWbmftT27kWp6WuPdl5bq5D2P8xyVB7MUqQQb+dxLW1Q==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":597779,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgH0b5CRA9TVsSAnZWagAASfcP/Azz3uomc0hHa7uHYvMv\nhiJ5K3C6UYDxEhjvh+YVjaJhGlgYaW2veH3ZMpHH6Yv7/BwB5DxZgYZMv63N\n34HQp/8L5EKSNqNeOt6kTsQvkx3R9CNmt5syCsi1XAFZ2SxHAXMMvDy9+D1p\nDHRXw4zFsQWMQGNG+CG14V6H4dhGHT2hGWzfzz6Xb4KRAHLkadWSg+xRJAlA\n1w2WwxtQpTX0uG8DTLmkPd7D/nbH42n0mcZITh8U69pwOpRuBvk7fYNT10dL\ntyTWAM8yU+dOG3Kp/DdGosGqClWYEM0ZssqYKtnQ71wvJiMOnzqLZROrWczu\nEI7cr95xFjiub099dhPNz4hq60wAh5IjNKbsTWoo4PxOOAIuHAwiCkPd+cQk\nhTulMBo1StJlyE9ajSu38OvTMcJ5ybyLWFCUDR1H3CmkqmGn9Nvt51YGVJmA\n9xuOrfeFhCXV5MA/TGY4DCnXenXK49dxoz8mcqs07QVfraJ8GJBHfm7Kt0TD\nuliI5l/gmgIXNFr+9yuEOcFCbma3Nm0Uj18FxJzraZ6zndtnKs/yGupdXVhr\n4/Yw+5BTeNi8/W30ovwLPGGh793UBKplYuFrRX4u9rAJJsq4VcfCB3o3zCfT\nrWsmEXmazGMOMW/lN1+upVjd91AC4FLA4GzGOJ/mswRCanCY/bqKTddMedk4\n1P+i\r\n=ZLgb\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"afafbf3e48043a58688ceaa2a592cfbd3f2bb36a","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.60","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.56","@balena/jellyfish-logger":"^1.0.6","@balena/jellyfish-metrics":"^0.1.64","@balena/jellyfish-environment":"^2.4.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.98","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.12-handle-deadlocks-afafbf3e48043a58688ceaa2a592cfbd3f2bb36a_1612662521057_0.7977105199436902","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.12":{"name":"@balena/jellyfish-core","version":"1.4.12","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.12","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ae2cc6db2151bddcb864c4506318371ef115c098","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.12.tgz","fileCount":96,"integrity":"sha512-J6jf1tXO9KQAJgtSKsx7I16BgnOsfPCEcatWi/jWpzHJd3HbI7C1eBVZ1FJzkD5TFGRYJS04zWe3lcDKgHdEVw==","signatures":[{"sig":"MEYCIQDtB0880wkXqLVHUNo95mpEAJubilfu9u70FCasGckOZQIhAOBDlunXC+x2S+NYzWETtozjZl0gL3f2IyJGicW2qc6P","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":595731,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgH0dMCRA9TVsSAnZWagAARSIP/0MpjXoFaKtsXYK3icng\n1oSwWg9lcNtU8aeWNT3UnZ4NjqQGOOEXjiB7Ye0Sbqp4dA4LNYUMdEx3NEYZ\nDUe1f3KJyTOagKkTW/ZqI24azRsxCFpO5fTao4bhvOVp3lfcYb6jsEztc63B\nlgjKruocM+sPwj2agquQaxsBQ5foClXJCYqiAHAk3ZGkNHy1BeH8gyrREy8P\nw7HcZyJbieLhmXzNmMsTgYbeRmgq4ENP4/o9E0ZsYHYnn/gqUsmGbk/e7ZKu\n0F0+CWhs69HZH2bcaPwIE0j3xiMetuKiLr4W+ImGd3ciKEGAYud1cGdDuAXB\nJxzcytk/xAo4XXuZxCFTkYekWKmLav8cHbRfYYweE7c4zn8hlCOwlYRUhx+k\nQmc2/20oN4bVMPdt8+HCClk4pmhI6owrhgHiHGwj8YWR4D8EHQ2lLJF5Uf4L\n/Du8z0RNyU3eWp0kqe1ool2saM/Y07h8k4RkPfDz96+kPHVKcz/EffYAZX9Z\nXmCQEDJ5bSu7g7xAUjrxs2r59G4562IKPuDqbbjcq6A4z8yOgME88gVdCpoR\nsWPQFFkqiBinsN6gugul25b1SsyzUmPRv3jbp7zLuNHIW7MwP8Q2MqfEycVC\nhS1jn1twVtlI9qHn8daXN98SuFFznjgS0pk+/elwAkfQWwRs05CLBN3US9dT\nohMV\r\n=wHdR\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"db377759d44337827c4df9e08743a31f6b0b3c94","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.60","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.56","@balena/jellyfish-logger":"^1.0.6","@balena/jellyfish-metrics":"^0.1.64","@balena/jellyfish-environment":"^2.4.4"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.98","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.12_1612662603524_0.1253725403751711","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.13-renovate-non-major-3ac81ef15543cd037cb359a6c3f171782b55032c":{"name":"@balena/jellyfish-core","version":"1.4.13-renovate-non-major-3ac81ef15543cd037cb359a6c3f171782b55032c","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.13-renovate-non-major-3ac81ef15543cd037cb359a6c3f171782b55032c","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3f74c6be00cc13701a5b470bebead207b42f26d0","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.13-renovate-non-major-3ac81ef15543cd037cb359a6c3f171782b55032c.tgz","fileCount":96,"integrity":"sha512-6hqk4VyioQRVyI03urvEkxxrdQ+yE6rvPh/HiTw8mo/iuv5QW7NBbB7P9Ggh8SVhac3/zHw23g+2bjg/6SIyww==","signatures":[{"sig":"MEUCIQCVGM3DEC++VjMzEyfo1bnTGW85jwwrPymYVcogdcmC3AIgJVyzyYoK5NU5IAnpBXzc+XT0v5uX9fc4cjUZzo1CydU=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":595893,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgH7VsCRA9TVsSAnZWagAAUPEP/1c1Jcom2HgI/xevi0c0\nG1HPDfbBzNGIYwliTGrgkEIBE+tYP5HydvPJwPfQBYyaWZgrUVpgzjDdKqzw\nBIqBZxSDUWmXL+m0FGfcI9oEkfMPq1QIM47svygTvFMWOlIC8yM154hN3MRv\nf/NnAUfa8mRXoRd/BgHisdhSpF6dIl0jzyIY0dtNgp8MVPv1kBloD4C8wVt4\n25V3bZe3XVyIObqO4aVTpk8z9QX/e6LQUZCOQOOtODJ1/uzt8Jiwvs6anSe9\n41hnXPsrFYrZjE6I6gvcldChed1C+U1i+PdRfDBBagtSSlIQgIylFd/mcMym\nwOJ/YepmDzKfH1sOCtpjQH44f/Gziy/1Ewii2eYIeAn9eWlZhL8rYOF1215a\nqAZ8zxIw/G615Z7eaEJ5AceQLq5rRwrWzo1XP3iExuNGSPpjB53Cl34Ybao3\nLzRwKjOpmG8AkzE8seFqs8HB5aKj6RNZFWY8vd7SNI7OhaU8GW91dPHEypbR\noGExXepsNdaCtxz1Co2KhRdfgfX9o6DVAuIJLKt8E+LacDPz2fyHix1yKhEf\nICZ4QOvScAemOc0fOdTSL5qPqmFJNg9aPYZGlP9IlRgJ9RekpZbDeE5KFZ64\niNVlEG2Do/fOz/eQJtpXYV+dGe5jTkgc9r45ACv6bACcQIXtTlrB/6E7DdeZ\n56i3\r\n=YDcZ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"3ac81ef15543cd037cb359a6c3f171782b55032c","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.60","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.56","@balena/jellyfish-logger":"^1.0.7","@balena/jellyfish-metrics":"^0.1.64","@balena/jellyfish-environment":"^2.4.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.98","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.13-renovate-non-major-3ac81ef15543cd037cb359a6c3f171782b55032c_1612690795576_0.8219189962776778","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.13":{"name":"@balena/jellyfish-core","version":"1.4.13","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.13","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"69c1e4731dba2f6592c141525bcb82b7f0a9b8c0","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.13.tgz","fileCount":96,"integrity":"sha512-i9DQCpmm+k0zSOMktcd2a1FFrtO7G2oFTEjidyxzHUyGHJAfHLkB79eD1KTeN3hlvm4N63N3i0rmKvbdGML34Q==","signatures":[{"sig":"MEUCIDvsg8GdZPHFutiTtimZ9ieO7vV++ge8uzQ/yGpb6jYwAiEA9qhd6q5gZsmFWlAqPKPehxHFrljZ6FzEBO3pRnmA5U8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":595833,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgH7a1CRA9TVsSAnZWagAA4LMP/1/YpOULb0V0dOjN5ieQ\nOe8TDrUTzJUAsha+xcRQ4VkmF8PR4GTQkSEIKqjdYcGxdoYNmAvYPVczG1cq\nx8Sgvh7TV19RZfAhQAJGPBa9Mr3odh4Av1P7Z2GDIcWFgJrgRrSYA7AU90tN\nMM3BTNu0ZZ3ROsd8S3Vbh2RuCV6axWKBIFuPer2gKP31q790XcnedUhHgLK4\nLWhO707pMkedAWf5Qt+7oZtzI+yfNlWLKp/Ja7HaBNrYDAHoRJ4BFJyLBQ6n\nsN2/nx01/YTzNvRoVGjyouDEqTTee09ypyIri0ixx4Ja0S3/kWCIZXA0TN6P\nZVvwUrYNlUAepObTHFSt20JnpCgWE+VQ26Ay+gxt/uWXNDi4ESAvRw76KZzE\nWnXILBoKQT+MfmerqeFVg3p7954tl7nK3JeTdIKweiIAgqfTOJxGn/JpwZz4\nWkleveqb7K9SLyhB3VEi7qZ4Q/2/A9dMBczS0ckvt1HNGzJZEXsKBzrtUsun\nzbauuxhVu/Na4ilUFHxJlmqg69/eIVVHAdm3oncq7lW9qjQzrZ7nadL6XP98\ntKBMicpTKvBtI8B/+6CaouAgJQ7GFS50r0dxJBSSSxg/+QxPce6Hm/lkSu/R\nzrUur7Y6xlvkvNMGWJlvxfOTl0sOryoEGYEAYHie62rSja/0zqU+yP9x2Dfb\ngh7d\r\n=xuOx\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f09d2a206d2d673624979c6af2237f046344fb23","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.60","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.56","@balena/jellyfish-logger":"^1.0.7","@balena/jellyfish-metrics":"^0.1.64","@balena/jellyfish-environment":"^2.4.4"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.98","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.13_1612691125186_0.7392844348234529","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.14-renovate-non-major-814197c9232eae169a7263ccb2fb9055d4d00352":{"name":"@balena/jellyfish-core","version":"1.4.14-renovate-non-major-814197c9232eae169a7263ccb2fb9055d4d00352","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.14-renovate-non-major-814197c9232eae169a7263ccb2fb9055d4d00352","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c0c86a56b9c325ac07b35961e71aafe8e6e18869","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.14-renovate-non-major-814197c9232eae169a7263ccb2fb9055d4d00352.tgz","fileCount":96,"integrity":"sha512-8b7gIyQ5GrrmVzB+wHnWAkzSt0pSSOMhv+M2HI5Uq7BS40aL+sNvDmQB6OLcsR1bc2Zzd1h8rwws5JAEefeZLQ==","signatures":[{"sig":"MEQCICDr3MMY9reNTBrfv9e1dQGGl6VqQuLkv8gL1RUDATnaAiAFYfCu1fUk9hE6J5T6ZGibP+2R/f5YAQumASpRWyA36Q==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":595997,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgH8EWCRA9TVsSAnZWagAA668P/iNNOvEL6L3ArEcJXZjr\ngwtLQkG7UPZXAq8ZC5Zp9JgcmHmMkWkj9kXiDdFMT5F5Md8PyGOHcOPAE+wO\n2jD6db42xz4viQqPBrnfVQNqaVn6CXQ/aFk6rMWy4v8jhoqPIpzA+ps93iH6\nadUuaZw+yy1824TAL9GR3f90dJ0bmVBwApCAKUTfRLzYjip+eDe2qbXFng79\nZ3ZfUZ7VOdF5FUllfdSiXm2gIeJPSgGtLuYiWUjZZYvkHsM7sdttjOf+7eBK\nh9PDqvWZcxIL+faOWmvpp1o6ONj+Ub/P/Nh3lFw+n2pE/DAuoLoHHn1hUqPn\nA1xasQXKirjFIyitlWZ8PhTCh4Etx5FMr6//p4y4/WqmqRJ0ZGzwOr11orb+\nGuM+Jy6PA8Mkt5jlA1LYkzYEYICZv3MT/cvqQDjjWOEwAM4VmCYSMJW56Z6x\nqhWSCkEnAdRRHyIKr8P3PJZbVZZRw+SXXeGMhkOUReusK32A16ZZQGbnxv/B\n+F2/arM3ZAdVWQeUMpqj86yU47USfF2K8d3lKGTL8HgwgqcpPP9RXMBKN3D8\nZrqC8CjJ6BLoiB6EimgSISckFaGWfh3aSg2yfPGE0yOXLihtr3FR8BEYQc3H\nP8AR2tjpv95qrVlCv2/7YMfpHH71OEkJPCCyQv1aUGXRdRbuGTfNCR7jVzpP\nKy5B\r\n=TwxY\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"814197c9232eae169a7263ccb2fb9055d4d00352","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.60","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.56","@balena/jellyfish-logger":"^1.0.7","@balena/jellyfish-metrics":"^0.1.65","@balena/jellyfish-environment":"^2.4.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.98","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.14-renovate-non-major-814197c9232eae169a7263ccb2fb9055d4d00352_1612693782290_0.2160792580839166","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.14-renovate-non-major-840d51b1245f96a13e2bc197d27ab445cebecdf1":{"name":"@balena/jellyfish-core","version":"1.4.14-renovate-non-major-840d51b1245f96a13e2bc197d27ab445cebecdf1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.14-renovate-non-major-840d51b1245f96a13e2bc197d27ab445cebecdf1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0682ead032d007f39201c77d1e1f0b781adf29d0","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.14-renovate-non-major-840d51b1245f96a13e2bc197d27ab445cebecdf1.tgz","fileCount":96,"integrity":"sha512-Q4nKV7GNcbyFKClG2+Dzfe2Wi2ksoCHo4S01zoEWCBpMTi5MKcy3oqDefypemVGVUN2FUlP8XDRbR1gKEUFv7g==","signatures":[{"sig":"MEQCIDTkDQx+S/TZouB94Q58baxWHDTYgDTIv5s0VHIQSOvGAiBf180efTjzh9fLzhBPT0f6FNKBgbCPzCdHFL9uPSMY+A==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":595959,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgH83jCRA9TVsSAnZWagAAfQEP/jhjFdDk9+6AfXAeuKrA\naXYLBJGvtzfRBw3N/xlycqXCWqGJDOoEp4BXg0XvJGw0KLRBIAO9OmY60tim\ns7aKVnMV3esD5rakoeZsXdoM4Osvlb8kMTFB3kxZcr236M0gEvAsOasDJhxi\n4eUC7uukj8YieIi7vzmEVqmHncecf0HYa+5VPZNkzdOdk9i4eWSksj4g5JUU\naIds+ac8HaPHPw9xoRXSXHUpVdGlaFOwq893YQoq6F55DGviCtYp0mPivAo0\neylDI6FUO7P9aPsk+IxSmPQnusVkKLYXa49iipYYc5Z55VAsw+LoM2BGctjw\nQHpaD8yv6k8c3Kc57+8Qqz38R/raOseIRlCejv5FVCZHTzdh/ISGTwf1YPPX\na6uKzAjSt2Jw+2IHcyJk9XGeBm03T2VqUb5t3nsBAAz1+Hres75kAVeK0Xcx\njUl+S+ixFaH5IvzTmWzR8x8nRky/TcTzIg1uqGzgwgIZ1/SMeSyErQyPuo4v\nkBsBCY2/QcuO2bbqYt33y5TQv+TQUtPKUCf73IEfejUZ3EFzgQjSFgDHL0g4\nUSVsilJl59uIZP60c8Su6LM4o+fxOo+fxFFaYA9c6O1sBxumxvkZbtsmQyyQ\nResgFc7S2vP4n3io5TJCnmfAi6cJ+yYdSu28YNL7DaxpJz7RE0iNvd7JBOc7\ns7f0\r\n=Wl9C\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"840d51b1245f96a13e2bc197d27ab445cebecdf1","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.60","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.56","@balena/jellyfish-logger":"^1.0.7","@balena/jellyfish-metrics":"^0.1.65","@balena/jellyfish-environment":"^2.4.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.98","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.14-renovate-non-major-840d51b1245f96a13e2bc197d27ab445cebecdf1_1612697059486_0.3986816777565758","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.14-renovate-non-major-5afa1bb8a7e525172d081afe059a28d4c5668c59":{"name":"@balena/jellyfish-core","version":"1.4.14-renovate-non-major-5afa1bb8a7e525172d081afe059a28d4c5668c59","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.14-renovate-non-major-5afa1bb8a7e525172d081afe059a28d4c5668c59","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"123571c13db95e84fef2b7ad7118a414e3e76903","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.14-renovate-non-major-5afa1bb8a7e525172d081afe059a28d4c5668c59.tgz","fileCount":96,"integrity":"sha512-9GW3EbKi3Dp5tSJRvLoXmvJUGT0rzCTztKee9w24DR0+xNuk8wLuyTPvIH1CAsFQ0uNXZwFdR6XR9GPNpoq2Sw==","signatures":[{"sig":"MEUCIHCEoZYzs44iPw8ZsQLWrUlwv/sIX4btX54DufothN4xAiEA5IcQBABFUpZO21rGB4oSs/L7KHn4YZzp65+rUZT6TLk=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":595959,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgH+QNCRA9TVsSAnZWagAAcVcP/2K6sv5zK5BhFw0Swptv\nGv8FzZ33w9WOG0BpUo3oJUdzhwHLj22VuMNespkcTR+RdRyhvKdj8KC2AEtK\nVi2LUBc0Cmh0hS46BJit3Jx0kBa0eWGwvJ4ShZUIasQbllhhXDsoEQ0ctH7z\n/UEBZktjU6/GHre9k8Zd58FbzagWZ+OiSCFLid83IUHEZR6LImg2zjrRKd1/\nxRV5FXxtxzxymxBqBwukec54ZL7u2RPdyOYjLU7X6UbPWEc9Z5lVpyOMnz+h\nyI/aJ2E8P/fhAyWeo+BTrj7I6CEHK7xOUh0Lq2iL1ftf1Bao0Yd6v2+lV3hP\nlVqzQAN4Q/G1bOuRnP8WQ6PVUGrUPxETRqQ1jQrhGpX/tLjQ1h8Om5gdPIpy\nmNg4VrDUtAb/U4zu/mUAUUihNeFMzb/xcW1FXsI7/kVA3bPulymWWDJHO8Im\nJ1c3UrI8jHYkHI6y6fJG5WWqEtJCTQFqUJ3nYjInwfHv3GTOlvOjOHiDRl+Y\nnSFjduBAI3d3uJ5VmEanIy5sXMBldjwi7pwDhObaylC/qo16VDriylxeyxiB\n3ANdCFQIw5q6xeCmANk5P9Y13SCJ7PWpwereYJxW++2FOSAfM7CiQOjuHvD3\nRSyfgoDwo2HBxgRqKxxVTzQl8P1v0iMFFJSN82gJoMPOe32jaRFlsza+QaCe\n9BbC\r\n=K50H\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"5afa1bb8a7e525172d081afe059a28d4c5668c59","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.60","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.56","@balena/jellyfish-logger":"^1.0.8","@balena/jellyfish-metrics":"^0.1.66","@balena/jellyfish-environment":"^2.4.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.8","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.99","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.14-renovate-non-major-5afa1bb8a7e525172d081afe059a28d4c5668c59_1612702733015_0.19099981476671757","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.14-renovate-non-major-5bbf3bd8121b52becb2b34e3f155fb9297d0688c":{"name":"@balena/jellyfish-core","version":"1.4.14-renovate-non-major-5bbf3bd8121b52becb2b34e3f155fb9297d0688c","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.14-renovate-non-major-5bbf3bd8121b52becb2b34e3f155fb9297d0688c","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b0d23c0fdf0e9aa7ebdd3e5c554cd83df07fa977","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.14-renovate-non-major-5bbf3bd8121b52becb2b34e3f155fb9297d0688c.tgz","fileCount":96,"integrity":"sha512-MXGhmFlNuLBUYOsKD7ETWkGdFhe2g3YNE1Wx6vFcYoSG2MXEJ6sc2s3hP6cTKs0qq3tiG833M9NAn7QahgSIYw==","signatures":[{"sig":"MEUCIQDB4yVglA9ur0T6YLc3+5OdyCOwZ/iRVV/HBii1eZTy4wIgWRLhaS6aRFEguz0HF1DH5guyeqgXkjc6nk+41NrfcF4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":595959,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgIYqbCRA9TVsSAnZWagAAbDkP/1vvGd4IUD/11SgRE/5c\nDSCpF8PXEn9iGDte0rIFnUy31BC9O50T0Qlsnwap1W6wDRabXGGzknsWxfA5\nKeD35AOyCRydoD2wqCyDB30jLinduTQctLlWXokeHTb3kbJeFlcznNAV33xy\nDXv3xTEbXXoDfSH9XLsyfx+gV4fBOEFGjEqjR1x5Nf/krNG3H0pgqkP1jpOs\nuRE34KC1n1zrt84Fn2M8xcqEoiEvHq9jR774bud6AcZ5rlSVwA87jiGw/rbg\nLEY0/otR05YdvQNYYjdGAV0tZEl8/oDIFed4iOdxhFCpiYusugSzVVHH81r6\nit6mwVqqJfj9KimrhnkvrYPT0lNcyae3ghoMSDivjeae+v68if8n38IE5sjc\nSrcYJU72fAP1W7MhxhRRzLQjeJ9rWo90GhSHfaSlw21KCm/TyoTkr4xsNPb+\n2F15GOFGIGkIpnSMN5KYV7hv42nnhOlwOsgzZVFULrB7JxbKJljPQ8yXPSfp\nmbUp77jrqlutR+ET0ICazPdhQpCTe2MEgtoEE6O1CzYz1yLrYpiv2DU7aS5F\nydi2YYHPTLUZhWLF2oNUjXX5zTn8xkeNojli+xFiBShDt+R5ldogK5tfUhC2\ntktvhH/k2fSMLJ+07fkD5lTPaDb4/x+R3ou8l2sFqSCaMmNjdGkDGGM0cGr2\nVlmP\r\n=PhM6\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"5bbf3bd8121b52becb2b34e3f155fb9297d0688c","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.60","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.56","@balena/jellyfish-logger":"^1.0.8","@balena/jellyfish-metrics":"^0.1.66","@balena/jellyfish-environment":"^2.4.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.99","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.14-renovate-non-major-5bbf3bd8121b52becb2b34e3f155fb9297d0688c_1612810906832_0.3367534687049054","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.14-renovate-non-major-461882a44e7d22e53440a85b361950cc9bcf2b8f":{"name":"@balena/jellyfish-core","version":"1.4.14-renovate-non-major-461882a44e7d22e53440a85b361950cc9bcf2b8f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.14-renovate-non-major-461882a44e7d22e53440a85b361950cc9bcf2b8f","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"39d85fce9f79c6291434a26a0cc628e9f06be469","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.14-renovate-non-major-461882a44e7d22e53440a85b361950cc9bcf2b8f.tgz","fileCount":96,"integrity":"sha512-+HbrulNWc3oarJvGHZrgQjUZwLAuA4+NDNLUdYkaDEUlqyef3cN9iMcvUg7WopMsXKPW76meIIs6jDbW0f7gTg==","signatures":[{"sig":"MEUCIQC47K/BpxZUft1lSbK338dTN6HwL4kActPRAFBSv6IO0AIgerdOvV/qseOn4HlMvAbwhZ1s4+GgUixduseCH9XtVYU=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":595960,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgIaJyCRA9TVsSAnZWagAAWl8P/3M/ACrxMGS8HtP6ErjU\n0eGKAPaqrI3Y+HtFEScFkexFYimLShTFcPZwBMelGLCXaGionXAG8GkToQ0H\nRR5smIq8bF6txFGQWuvDA6dDtzA++7zBDGxctkW/ywTpXCUI7M3D7nDTaKDo\nyO67WZnQPsM0IQmNwu0QhoMHHXtJ6W0VlsrtWpLvuEs84pwThQ0ZnsmZR01n\n0ZIutHjUzYJUry5/DpfrO7Zfa4vWFMCKi251h46Aas8IU4N2mcllpB69/pt1\nsm+I8PBSUMVy6xx4XGRMWTe1gf04DcAUiUn0NCkXA/panBQJmiD4AM5lMaDC\n1whO5+7YbCsZuoMRe8/MwrVkpy6tebCZmCBQpHsYdqE+c8paamnK4d0chl/P\n9PUJDfT359thv0R5wkYRwbHH8rwPfVdXgiqNIC4ErDCrDrlZoYMOrqVe9Sa4\nOULPulgUiHijkESuwArJst7hZHCeUaDHZcC93/V0CtwJVqm2AICy9u5MPXGV\nOd9RDy7Idfc4psCZuNMP9l01kaKzo+2Pi97NppzIeN17oc5x2u2okm838T99\n8zLjrFeZaYS65AlDThYOlFOQGxxFSK3NHQRmvMW3atGMXJKx0jrkUn9Qx0Ms\ns3KBiUplj1Ch1vSe4m67ruiH8IkNWGkyV42/e162FYVQiS4qbRFSMCA+AvjS\nzTUf\r\n=aRnw\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"461882a44e7d22e53440a85b361950cc9bcf2b8f","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.60","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.56","@balena/jellyfish-logger":"^1.0.10","@balena/jellyfish-metrics":"^0.1.66","@balena/jellyfish-environment":"^2.4.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.99","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.14-renovate-non-major-461882a44e7d22e53440a85b361950cc9bcf2b8f_1612817010001_0.32867501496778107","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.14":{"name":"@balena/jellyfish-core","version":"1.4.14","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.14","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8c04027d7d420f093597d8c25eb22eae80fb7064","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.14.tgz","fileCount":96,"integrity":"sha512-wZTCG2IFqoUVaVUe+H5BkoPuNfjBOf6c7dVnjVJMm/46GgfT4xyf+Am5E0vsSbgz5Rn3oMi5X2z0h7DcpNDZDA==","signatures":[{"sig":"MEYCIQC7M1RzsGYDqrwUsuJpMKrYo2JvwPQDsSuh/xzWB7HBKAIhAIKxR2rlx0haCDm8J1YZF6nhmRTLGhYpMrbHEDLMpVJ9","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":595900,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgIdhFCRA9TVsSAnZWagAADhwP/1ShJQptZTL45tJZ+kXM\nbvhgc1VPZGYAOjyD6ShvOEvZ5/Y6tfU88ZvDyZXRAtzRqEbWLkWfUnV9JIRu\nuEdysG+mw+RgqgsCprDZmGjZNYN4NlaTJwCYdEbCKpknDb81an+WF4Zcymvx\n50ApYtEgqqd4y0Tjk6BGueRii8p93gtbkwHkx+Jq80XgBbaqAL46MfJ7yUf5\nR4q6ZEiEGr+LkObPurJyWc+dnYcV8ZFwJjOHFp45+eRrdLBaKg+XWG4pbBTy\nghkVNqqiuFHSIGXs9tTJgCn99944/6CfQkIFHZ9UahfObEFEHWoRqkh98sBX\npAR6E44eeocEC9TyDrAaQIOhFOmUIwjrcoi9fZTBs1TCMF1m/KIpBKkGw37X\n/UVHYlGcjrDtkPeGbsJfXAsR2kIed+YKWpaVHW2B3B8IYTdIpZoxiJQYD9cG\nW8/8NG9VFMEm+WHkwVFnnnWXwMGR6IkAM8TJogQroymgPyAPjxtzZB6UJZCD\nVUoLkoANvZgXEhh4aIXe66eJQPlm/ZHs2UMju1hS7y9KvWS361DDBwzZPDn4\ng/gZVYfOXhEOIcXPEWr1m6AWMR7hvWNF2b6M4kOpLuovqZcHhseafBxwjcyN\n19m3K6+BlEEvPa1ElmnYvRi3ANCP+gflxieT5ymBi524YVEUIwUrmp1iXHDD\nncWu\r\n=G2P0\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"bfebba49fe79bd447d844cb7fe5d8162bf1f8e2e","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.60","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.56","@balena/jellyfish-logger":"^1.0.10","@balena/jellyfish-metrics":"^0.1.66","@balena/jellyfish-environment":"^2.4.6"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.99","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.14_1612830788576_0.8644346043546254","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.14-renovate-non-major-625f553291231a3b7bf7f371598209063f7c7d41":{"name":"@balena/jellyfish-core","version":"1.4.14-renovate-non-major-625f553291231a3b7bf7f371598209063f7c7d41","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.14-renovate-non-major-625f553291231a3b7bf7f371598209063f7c7d41","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2dde2bc7dfda917acef4feca1ba96e3dfe97b027","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.14-renovate-non-major-625f553291231a3b7bf7f371598209063f7c7d41.tgz","fileCount":96,"integrity":"sha512-AtUWYSkbVf4luyQ1sRuNbdyclWHBw8gk+UEiKa/sIGNfYhaJ9wcB/1UQqGefuQPV4aJUDITjISqF0bkVJe3L4A==","signatures":[{"sig":"MEUCIQDmE0nZaNpBnXUe9U7UjruwN83rm8OeCmS2UBZR1vnqmQIgZ9jo15KybXXS6tV0tzLpUHhwd0HLZvP3Gpeo+e/s1FI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":596038,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgIdkSCRA9TVsSAnZWagAAgBEP/i/lU8KVzjQ3poWhojYR\n9ORQomhn4vB819jBW8ySfQ578vX8ccDbndymQZ3hDg6oIFxYQde38urSrCK6\nZkFomb/RxlTbsHoJ7bNjSqmm2WVbrtKJfyBqmsMgpSWa8YNni3PhiDVbWldj\nC4cjBvDQsNuvoA6iw46EDh4VwwVCmHc2TJox6LziPatVRKyS3nHfkkeCuCm5\npK5nMRnE3/xor2mrnATJbCwZw5tpskgq04ybvQ8Zc0uR99nB2b0Enp/NLwnD\nUMjrssiFAlFCiSLBRkC3tarSkaSjKEQIWMlanEU72R4TeRzc9OGYfZWQpRDf\nBmGGpGrFn63Kr1hzTEI2z4NewvJi06OgUAMjJdPdYUhoKSZ51gpa9VwhdB6f\n5AQcTkcXpzBm4R0S0fpnLt6OOyMVYny4AI2BjZ2vx9l8hJzB1tK0PBBjpERb\ndB2MZXq3twENTy4aELvJAy9yur77Amy8ssCIios/w4yFvYaWsq7kpCtoSGqO\nmxkSwN54fetW9MFzCrCmer8qS4s5yg2iaBBsd5xUJcwOFjYzXxhoB39QkGSr\nZ6pAyPhtqe+U1krGNVw6UHB0ISryYu+R58fmrSSJqCeoSXWN29iAw5nyu+Dt\nlY4ZhPVf9Fr0le/Tb9ECx148KS+av1FZyDG/P1s0rAP7WUCRlcBk7llKa1oU\nMwWc\r\n=MQhh\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"625f553291231a3b7bf7f371598209063f7c7d41","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.60","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.57","@balena/jellyfish-logger":"^1.0.10","@balena/jellyfish-metrics":"^0.1.66","@balena/jellyfish-environment":"^2.4.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.99","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.14-renovate-non-major-625f553291231a3b7bf7f371598209063f7c7d41_1612830993805_0.48736954851459857","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.14-handle-deadlocks-146b2bf8949182a935a92408364f39eed6d9bb49":{"name":"@balena/jellyfish-core","version":"1.4.14-handle-deadlocks-146b2bf8949182a935a92408364f39eed6d9bb49","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.14-handle-deadlocks-146b2bf8949182a935a92408364f39eed6d9bb49","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1ec7dca9f422c285b1829284f67b015ebacffafe","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.14-handle-deadlocks-146b2bf8949182a935a92408364f39eed6d9bb49.tgz","fileCount":97,"integrity":"sha512-LFh2JGA0+1Ecb1CHVSbdtK3CgvARJL9aa0SzYM/X6F6+QAShY8KHHBfTb9TIf5CwEznWrNq9ajQxnFJgtGk/DQ==","signatures":[{"sig":"MEUCIQDEt+tORzfU0NecrXsk5I5RhRP8SuXwaAZDfKUl6gyN5gIgTH/gNyj56caLjISMnONVEdNBQ3BA962kViV6OSDvt28=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":598054,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgIdlLCRA9TVsSAnZWagAAEPAP/R0jsm2t+4827/9a+U7t\nP2tRM92OakbyTEzrIcGJ5ik0WGnLdrk921F7zEhEN0GKw8cbdsJQmRIbcd8p\nS+fE79TajuGyUii1LE2XM5wE93WI2DOt1ABV7rk/gv64yLvxOqybJqm5B/bG\nYndUK6weR4hUIJv6X/sEWqobgsfMut89k0XuftXOgM+HH2h60G1W7Ij0PkXH\nhLnEc6RrqLamdUFSFBhEof7U/gJGvrUL5+vX08o7U7DR7OyDeA5Pd68gXWT3\nfnS8wbPeN1Eum/N5coKau1dqnqCPiYQDdbPHPVGXSk+ViQGa3YMehEzA9Kys\nU33XCm6fdSAG1BkMOCVQF+2jnVSGCyUR4UsMZn6mdLrPqZ8nIqa+6u0bFqZH\nlUSSSgC/vCA4HykMWFx9pKjTJWtchhwUQewdBkF4L97op2je9ZRlDLHxhw51\nNlQTUFlz3FJBmqlST7pTUZ/xabnchiG1dZlwHN5qW+f7C9/vAVEweC7i/seg\ncxcroktBKjJSssdnJJPOaYWEUNw/6RYZzBZq2MHwXIEQrydTB0Fqo8pmmJfN\nREXf3qjwQxvYMy7MBsuvMP33pwJHNHzCnDSy28V/N/1YzFT1Zs4lnDbLvGmP\nNBRLlWslGFPPS9eY2IfqXFKuqFqyxFq6QSOieq3HSgw5DOZBHtw1VMLQ479w\n+wXh\r\n=ZbJ7\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"146b2bf8949182a935a92408364f39eed6d9bb49","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.60","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.56","@balena/jellyfish-logger":"^1.0.10","@balena/jellyfish-metrics":"^0.1.66","@balena/jellyfish-environment":"^2.4.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.99","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.14-handle-deadlocks-146b2bf8949182a935a92408364f39eed6d9bb49_1612831050440_0.8294704601733403","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.15-renovate-non-major-76803c8cc2d25a5b518775b8cd90b1dedf7736c4":{"name":"@balena/jellyfish-core","version":"1.4.15-renovate-non-major-76803c8cc2d25a5b518775b8cd90b1dedf7736c4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.15-renovate-non-major-76803c8cc2d25a5b518775b8cd90b1dedf7736c4","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"49100ae226ce93e1a6a2ff94fc8ede310a1f301f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.15-renovate-non-major-76803c8cc2d25a5b518775b8cd90b1dedf7736c4.tgz","fileCount":96,"integrity":"sha512-bcvWeCvh5+xXcufxSn837VHh8stTa3Ri9XdoZMPw/uvvan4L5aTMJFQnAwzTwKMkaYQAq/faDYJbEgmM7GV70A==","signatures":[{"sig":"MEYCIQCKpwMGJpkHrJpOuqmw6dp8mCp8Wd/WO6lr5FVmYHPk6AIhAOOqXwZz9cTfcDoyIImY1mr52LRuDNWRE5s/QIUnyqpG","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":596026,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgIdmYCRA9TVsSAnZWagAA2ggP+wVZKEexPwoztPMvrn3y\njX123ueTqJw9UQjCee5uqHjPigpUfBEyEkXD+FJTwIpsTz7QV6e9NV5CqUmK\nz5m32xZFthTWqFc7vwOblxD1v8XkSYttbzgnlwbvmgOZGvahPvWTeqAtJOPD\nuQNy5pNdlSdP4a8hCcLHfZ2/WlZ0Tk7cv3XtJYbF0lw6q0Wp8+IDeBWSc7AI\nGpRDcBQIgRK6aXmyMaACHF460z1qj1SY9uWdBQ2AnuVhpxnHTQIyfbtEimFE\naIQ9Gk4/QEUB0fuhzhQ88ICPSMxxpU1kcopDPGqNQXIDZL1041t0kTfOHWzp\nxQZ+N9b2H6JbI+MFIJjIw3lP58tzHRkcybgJytb1MiawicMKlgZQiMhN8+48\nCxx0neeBoWEluJVrSZlNbr+mCqM3hEuGrVvMOz+Ro1WttNAgClL9N9tUdz8Y\niczLCNP/1cF0XVHIidTn0jFgCaHZd2ANtG+dvMlFVA3WLZ/KNO5naB8UAHzs\nBK3GHaFS7RYB8OUeO5FwU35L61YSutyDg0qWgZO6uZOGLkzZpkM3TFBUxqY6\nWTBUtqZjEWxnHz4KzKsZG+Px+j5dF9lm9hYcHbPwIHfOiJ8tmD8bfVyEnp+3\nch4ZUvKSYcPHT24P80/HaAhYsnyEt9bYdqb4VzDGhqXwpTD7zAWxA+Bd7gKO\nuej+\r\n=RScB\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"76803c8cc2d25a5b518775b8cd90b1dedf7736c4","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.60","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.57","@balena/jellyfish-logger":"^1.0.10","@balena/jellyfish-metrics":"^0.1.67","@balena/jellyfish-environment":"^2.4.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.99","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.15-renovate-non-major-76803c8cc2d25a5b518775b8cd90b1dedf7736c4_1612831127459_0.5181615245038618","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.15":{"name":"@balena/jellyfish-core","version":"1.4.15","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.15","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"647c76c23836ab640efb0ddc377fd1bb4361b00f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.15.tgz","fileCount":96,"integrity":"sha512-tWl93PzH4wa02fgMfkskTWD7YxPCVWEXaQlK70CCSXmT3pt6ZVbYCJoDTZZyXuHikNyqYAd5rSEg5mdZ/NZSTA==","signatures":[{"sig":"MEYCIQCA9c0nmCl5Q3VQj3NpOTXI9iVUDza1d5muGomeS3va7AIhAL+ZHPB76THgzE8UyRJ3xhdR1MQ23LQOI+6A+YeOi3Hb","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":595966,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgIdrYCRA9TVsSAnZWagAAcJcP+QDepy0k5qANE/oFiBiD\nI6rFqpNangn6O9jeDwgWytcfA3TLqSCKnyuqq4bsxJxGtMDK2UZjAUncEm8g\nsjuMyJ07NGvGq/AYD1qPj6Lpx3ySzxvogOB5iJzz1Wfjrv2ft+8+F6vK/crp\nX6GhWkpR4mSz4In27pNe5TjqpPsgXliIgRweaxKiKHp7sL+5L0zDq10aSJLU\noyiYhmkcZ4yWwYTZDn0psCQuEQt/5+N1GLfQzfqW+pwRyv9ixPuuOPA3i1Mb\nIElXdKn8tBAsQ4dHHOFozyOcKmZzGeVOAtK/bx+GsHIYhFf4Brhv0n1Nw5Ci\nnnmzKUSxbg2/1Q8CcnPmBx3QTl0LoePnNZZ3OcjwsDr9Jo1pS7Ok1konfJ70\npGagAacXMI9clrHdMS6ALgb5LZJ7hySNaBoI7R1ZAXlvBNGXDZokU4QSYgpR\nuen8VsYrMvLG6axCWzpjCbGT1GgoE0D0TpS85qGi1+9nOuSg1aQcDAvM7XSy\nsOiSOma+eyV+3JM9GEroxxzuTi+wFPq1YtAGHOVGHpimF8GW9Oe4jRdNilgl\nOqIW/weE4Hcr37Glr/WTVxzsbzdCK8ih3znKBo4LacpFiYOb9utNRtw73Ek4\n/i/2JQnqV2ORtk7XlpS4buTGsxUKyF8DPKY76pJtYIf2oCL/s6W8BPECPdfy\nllFo\r\n=Pvm1\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"df80108c14294dc0cdb91dd6875c763de5fee3a0","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.60","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.57","@balena/jellyfish-logger":"^1.0.10","@balena/jellyfish-metrics":"^0.1.67","@balena/jellyfish-environment":"^2.4.6"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.99","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.15_1612831448268_0.290805141291008","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.16-renovate-non-major-42ab956b26e7ad9f0202f04a5dab02b66a58cc1f":{"name":"@balena/jellyfish-core","version":"1.4.16-renovate-non-major-42ab956b26e7ad9f0202f04a5dab02b66a58cc1f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.16-renovate-non-major-42ab956b26e7ad9f0202f04a5dab02b66a58cc1f","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ebf6f520b616e3972fae5b3e51cbb1f2e0e0c794","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.16-renovate-non-major-42ab956b26e7ad9f0202f04a5dab02b66a58cc1f.tgz","fileCount":96,"integrity":"sha512-pluNYXozLmN/FGwCvbMmU77LVxIR+vA6uzG+TuAdKGAzmOEUlyFaHxHr8/rMI/dzJ4NDoTOr6itMY5god/oktQ==","signatures":[{"sig":"MEQCID5pXcEV2C6xK9j47EKwbBDrGWp4oBxOwQK8dYbBWVGEAiBp+kFnqprYbyvFRT2YHVN4ZyPBeZ3OqtTma7MHCJXfIw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":596130,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgIdwYCRA9TVsSAnZWagAA/98QAJVmMG4UoER245WocUN3\nBAG2cP3lLoElthcXYrEI3Zj+MGGowqdf8ZsXE1s+zeG/YGP2/RtoEVc6AqxP\nIFnvED7zIGylyR56sGYl4S25m1qmDpr1Csrrm/fQtEWyhUlU7SL54Ou3dU7R\nwlpjxNBRNfcH9Fh8Ly+m4fum4GeOxA3T5wc1yZQEMmR8KihxJqrtdzyoQ+A9\n4+KDwmG+Egq5zgSHnH3n1KMBNHiUzjFue4dqcjs5Qug6Dcc/VsrNBtgdQt1a\nGtWtNJjA27NZKgZUlaiHzoqytUx67bN/cV3sazl+OK+JC02y/GJASyUq7oiA\nc660lc1LE/1Tai67LZp/glBZC74Av10SY20PKpuOFemu6ilDtggkljJaNODX\nASqe6xEeFt5W9xoxsiReyYL1atP3xcCIOegcnRkzFv5caDNhPBOmHz+HO2tp\nr9S84TTudVXzEntZKBWQ0gA583SuK00sqQv5AEYDLmCS4LPOMfNnPhTZtaEv\nagDxsaEB6JF9FRHIqN/hoY1cyYE3SeWT5MbSjg2M7WusuLDockwvRZgHSPfU\nkixqHh8UJr9Bi1LFVB3hp2dIkm3KdCnx/y12skeNEyyajbAaipwPFDHJmpSJ\n9732SkqjEES1lfM8gg3Db2gIVBxRW7gzA+TKHupB/bi+lwPRGVNvD3lG83Tk\nobnq\r\n=T2jd\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"42ab956b26e7ad9f0202f04a5dab02b66a58cc1f","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.60","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.57","@balena/jellyfish-logger":"^1.0.10","@balena/jellyfish-metrics":"^0.1.68","@balena/jellyfish-environment":"^2.4.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.99","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.16-renovate-non-major-42ab956b26e7ad9f0202f04a5dab02b66a58cc1f_1612831768469_0.9887941084925362","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.16-renovate-non-major-c903e7d465c58a8ba7eb8c0028dc2757a78f02fb":{"name":"@balena/jellyfish-core","version":"1.4.16-renovate-non-major-c903e7d465c58a8ba7eb8c0028dc2757a78f02fb","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.16-renovate-non-major-c903e7d465c58a8ba7eb8c0028dc2757a78f02fb","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"fec7a4869f4fd54f0dcb2aae08850980444b4be2","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.16-renovate-non-major-c903e7d465c58a8ba7eb8c0028dc2757a78f02fb.tgz","fileCount":96,"integrity":"sha512-MfRG34FrXZRxQE18HmOeOtm9Pp+x3PAkdob9RWRRzcrukDzcOpAHd+a8f5s6hNltV8EDK0ZpCNDxMfbvttVnHA==","signatures":[{"sig":"MEUCIFFcnrurC7pyw4tt+t/D8jj/uOMJT9w9OxsHFJqO6UT/AiEA1H5yfScD+UtvQ03+2behvtxoOkjD3sZyyW6dbOTwemI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":596092,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgId0ACRA9TVsSAnZWagAATMAP/1+YXLsb+GUVL4Ci2oXn\nAyKdfpm+j+EwVphT49l4TEJAUxddUYqedLHpPdIBkdCzX/tiA4T0HeM3z7+S\nSOVGVun5F9zPqmQrDgypp3eqxWgLsHNy+OsAiOG81U2+76Vy7VFpYkVjljHV\nLClvE4EOGmQUN0RsC9YDWS+PiKQVzGrd3IWejFC1NI8o5QcFyxXg4XQDP8IU\nMCev8IcdcLAlq8wjy70anxjaco/MiCR51Js8vIoG6qBGzbMHTOfomDCfgO/J\nzGDHGrlq2p9Hfw3z/Vkf3nHtWscf8cYLHSsLi9XVV7Y4V3HLtQcTNtwxvhZZ\nTJM6gWmXL0uqe2W+NIa0OaSA9BZfN4QJUsdUYwz0RqvvqdqYKJH1+5dxyQwg\nI0hAUk4ZL7+arDww13RrudVaznSogmHfg9/ffcFUzObC+iprbG+O8CxIWQzV\nq0lEOg5e/R2+WOpbQBITFQ3YJolMyRJDJA1s0RbNqRI6S9vl3g1XN8Dtc9C7\nEgwcih6opK6m/iNnijiWLmepoSGZ2OP54oPOnZ/x0PlpJfj9knFmyd4y0wsy\n92JubdxasXXjlJHtFiY+QHDDz9OeX5s+8B6IzOhAPaUg5J4XjIz2sCm6pt/w\nGbLvBKmgbYyrNJjX0ga6/4btZTH47yVWjNB8c7Wg2QJ10fxGOJ6I39lLl1HK\nuQZQ\r\n=13jb\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c903e7d465c58a8ba7eb8c0028dc2757a78f02fb","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.61","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.57","@balena/jellyfish-logger":"^1.0.10","@balena/jellyfish-metrics":"^0.1.68","@balena/jellyfish-environment":"^2.4.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.99","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.16-renovate-non-major-c903e7d465c58a8ba7eb8c0028dc2757a78f02fb_1612831999724_0.8282719424021265","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.16":{"name":"@balena/jellyfish-core","version":"1.4.16","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.16","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"588111b984b6c476ebc4dcde6e29b26e63bddd16","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.16.tgz","fileCount":96,"integrity":"sha512-lZ+k4z0G9KaQ4a18OIyP6+uNUo7gM1PhbzZoOy7e8b6REINaCHi9WnbpJavWRc7bUxeu2h7jemiZ82+5g+dKeg==","signatures":[{"sig":"MEYCIQCgbYBDWgX1/Yf4DMkY/MR947JuvhcouEXG4CJQkr6f+wIhANW9JcQ2tPNEwXkX2WAoqYH26kOte3VfkI5Rz9WR0nnJ","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":596032,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgId4TCRA9TVsSAnZWagAAE+AP/1aBHL7C4DRX7Fb8/3xH\noa0a/VHw8sThHSXl0ac61Z1fQ0pENlWoLZfQ7gyFYc2vC1+70ktQ6tqZtbrE\nbTlrPGsvXmsXoOBMCrTViZzTXwcNzN1s1kBx0HpyDxppvvYyCJ5lyDF2tBo5\nHNBw0NcHN7Rzuk7RZf8eSpBwlGVGq9gu8hEesv+4LU5HsYwWBENDzhYzrFWI\nlu6BgYNMjFYngz9W8mGSqs4vqTWw5gAtuJAOZP6lVXKs6AfKxgRnhHVUyJ2X\nzXk3CtDOWLwj0I//Anr6ePn+RXBZGxubTUUJGcy+vYhQpGcej1iNAR5ju8EC\nO4bYoUuHs7ROxZj9uEhi+SmUGDOzzFABo0+QAoxAvFNEZ0dFZdy9hS6u1DCD\n3Zr6wmwSDHxRHhbJ5692bzz7aZmBg4cWQkMCPgJnCpSchEkYNtH/jxsqR5Uo\nDk+WSzCuG6VcLf1PKWD6HIORR4MvaAoMmJpUDthvo238j5npyWlFKdvvSbTg\ngbw6kUwIxtHiokjlQDKfqDo6c98ZzJHtf0OIRnXcOabfLxYtzEyesbsPsgRE\nabWv4A4P9iPtq+pJZ7OKDJoPGPIHj2dGGI3olZAjqJpXCszRTSJQUktir4+G\nLhxyhrMSfE14oeS7W2mOwrPVzXBK/CsY8p8zD6L55EsiublQFquL2iPfXJn0\nQpFT\r\n=dUO/\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7363f91ca0b3d075919c56250ad9732a630b2e86","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.61","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.57","@balena/jellyfish-logger":"^1.0.10","@balena/jellyfish-metrics":"^0.1.68","@balena/jellyfish-environment":"^2.4.6"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.99","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.16_1612832275027_0.38368137550119497","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.17-renovate-non-major-0b884ec5383513ec664713b9c7613422d72b2129":{"name":"@balena/jellyfish-core","version":"1.4.17-renovate-non-major-0b884ec5383513ec664713b9c7613422d72b2129","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.17-renovate-non-major-0b884ec5383513ec664713b9c7613422d72b2129","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e4c11ace2e9ebb88f7ad00255b4470bed8615b53","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.17-renovate-non-major-0b884ec5383513ec664713b9c7613422d72b2129.tgz","fileCount":96,"integrity":"sha512-/x9TubE8iZi21idqVNXh5Z/uvogaaUd+x6QIHcaPx0N12fNxRiFl6AobaR6g8oo2AbMTK/U3W4esgnW+jHObfA==","signatures":[{"sig":"MEUCIEh4Cx3YRv5lwYMjzVCPm4EtHA3fbUnAXPeow4OVUZJeAiEA1JiQ3C3RWxaqdYWSxkRsTpGgSt4XEOOtPsrvGIMWYLc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":596195,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgIemnCRA9TVsSAnZWagAAfRIP/AidEdG6G81aXIN3W8AR\n8f0Wm5PXItIYZPkitLuhE70N+PYADGI669dIP43giu2ffv+opmIUXvncUG6F\nbIFg7YagCSIO6eQ84ov+a4fb8+JglbsS2d3Xqu1mu0iY8FsmIKuqOTkMqCGT\nbzWXh5DpbN2FtOfjB4OIuNaoYZBzhTxbmx7SOG1chcAhQL+xDYAPyirG/RC7\nqWmTphodGRvnOveaI4VhaNVLtw9rqqU+A1Oi1IaH8S+i97OWmnT7y5EuTn3K\ndGGASfxO7HTYkNiu/UrIV5NXSyttVkRr4uJvCNCEzWNEagyATG2nQOQxj/Cq\nyDCoU+qCRu6Ubdq5L7udzq8RJvGvPIWOGLY0Zty0GFBlULHt/zn9bWpQdfsW\nP9jFEqcPUyo/HWF/n9ZMOhYZPbkxjxdK9V2QgIHEkvfn3S6vbkU+J4w4PAHZ\ndxJCumVpw9TM26gRfBlop4mXF8MXbI1koO5lYVWLKcZPEtUKFUcgwz8JfKWz\nTVeRAMLyPBYyCO2tlpC8CAm0HEvGzMhs+G8h1s9gHuaAvuILrXhaoNHPj0Po\ncn265NpZX9ONLCZHK3JRA36x3QdxLesrE9pZev5uShBTlNlcuH1FK2NWlJF+\nzNFrysQI/7ZO31DJdWvwWw1C9pvngoSdr4OctdrsnLVwBeiT/AM3R/5T+DHJ\nuo6J\r\n=yGAQ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0b884ec5383513ec664713b9c7613422d72b2129","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.61","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.57","@balena/jellyfish-logger":"^1.0.11","@balena/jellyfish-metrics":"^0.1.68","@balena/jellyfish-environment":"^2.4.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.99","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.17-renovate-non-major-0b884ec5383513ec664713b9c7613422d72b2129_1612835239048_0.10412863079537571","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.17":{"name":"@balena/jellyfish-core","version":"1.4.17","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.17","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"13b7586003ee9bf1004b7b32cd63bd5fe2d61d61","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.17.tgz","fileCount":96,"integrity":"sha512-AelKLP+kMrlPPhuEBm3nDnt0r2m0zPsu4I4zRJdeYEG7fqzDgQuOLDm/Q8yVVd/c0Q2rET9AsbubiEnbe0QPug==","signatures":[{"sig":"MEYCIQDsJI+G01XYATCQBPPTcIhxIdjKUPbtOvNANq/Pfu7sXQIhANY53gdDGzWGyKSHNCwNrHiflCgm50NUVxokcaJa5xUY","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":596135,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgIesFCRA9TVsSAnZWagAAco4P/j/OUT0TzTIZr5E+TjGH\n1VyFmB6zN/bNb9u5o9pGaysd0weIgsMdWq6sEme6qPE0pC0l6n3CxWE5V8of\nfvgUA9wUW85aV1VpQ9x7hlSQ1SoVYXhR+AS+c61c/ugCBpEmHwrAoPKov98g\nDymkcPeNtBrRqUi0GNtG4YCu3sC98iB+2mmvuZyC92RDQf1Wz1xK71MtlVRE\nfCA/fvJh6wOOGT50o8wSsXkSoImewCSWSzuNJ1+GuOuE1TGb6OB4j5ioxFBx\nwpmLMR5CCDSd1V0RXWfyEmF5KbUKrKpelRUvtsFKiHM36x1ZT7hGMrAfHiyY\n/Y0bv5w18L9wVN6qUAuKeVjH/8ob5Q0/sJ5dsidAWQDMsakmOVd0eg+JCMhM\nQkEtW0AyOyQ8VEyOUVq17TWWTq4UR+w7ixZ7XinnuB7fy4AVAnFkbVnmoUm0\nhZWZiKSAMTUh28xFPWe65M1CX7OQ3cOZdBpBd0OGUhheAwlPL511pJjWuvlc\n2BCjxPSVo4GuHpFACy1G1lJlPzZMwjCWIUKjs3syeTmFgnphJTyQ3nfUARrP\ncM064wXllFM9UP5y8nb+6F3Jnmevvm4NUwVF5Qqo45DjvCRydbq2ISOE/bbS\nkZR/o/v/MX9RmAyY8pVgrKR3/gsqI2U3y2g+bkMo2FZ2m97xMeJx6/1g+2TD\n5zni\r\n=aSIG\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"af1b59c43cf2ebb32fb946e9e2c60694b581b0a9","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.61","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.57","@balena/jellyfish-logger":"^1.0.11","@balena/jellyfish-metrics":"^0.1.68","@balena/jellyfish-environment":"^2.4.6"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.99","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.17_1612835588671_0.41730085927081895","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.18-handle-deadlocks-36b7c91088c6eae3f700a7b0b25f75a7195e28b0":{"name":"@balena/jellyfish-core","version":"1.4.18-handle-deadlocks-36b7c91088c6eae3f700a7b0b25f75a7195e28b0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.18-handle-deadlocks-36b7c91088c6eae3f700a7b0b25f75a7195e28b0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"fba55b2f60a38a89e8452be78ad6d02025785805","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.18-handle-deadlocks-36b7c91088c6eae3f700a7b0b25f75a7195e28b0.tgz","fileCount":97,"integrity":"sha512-fWiSDAHQX3L4PpW6QSW+0OH34uPAyC2zJWi5Fb/q8zWSSBSD9gNDU9x3uYuQIrAmdpbhKPIqeqLOrlLY2YgjhQ==","signatures":[{"sig":"MEUCIQD5GyJOc3gIFc/qJkb1tiaqPot8/d10EtQ4loDJH49imAIgb50bQ8Zr8/Ieq8YvVESO4jVyT7ghd10k+wnAgwgFDTw=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":598314,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgIfipCRA9TVsSAnZWagAAt/4P/jJHI0ujbMyn6Jd+VunU\nNrA4MQaw2Wn9E+OAJrMq/VU8GQivnVelmH1GS9cM6ekZ+KitUWt0ygEgk+hZ\na6Lk8DHoJSumk3MoMoU6CFP+lfP7QRzweeKCRtwlmBb3ciWd9JMFkhMhBhB9\nG2RqAIZ/HUdsGR31VYYKpNXoigo2V0dv4ZUTAjsLq3hhC7JkWSw1TKWItWIm\nFBNx0rwFfDNSLDUCWVrItgBKqT8Xj3eVclvSrHv+HHlQm9A2Nmx/tEV6ZhaO\ngbr1QdVcHlpGR47TOrWmTMcmgVRZpiUS1lWiU3rvmICb0UriAU6BC7HodSIz\nth/YPvprf2XyIXqnv2MTMxbGLmhI7LYf9sFNcxXHJQ2YOOzASyKaYE97xyBg\nU1OinLpd+ely2XtQE5SngH/w8jICLF3hJOwnqno9ZmM/ILSrYrtCubJxlTaI\nDvGoeA25XnHyIWUip8H58yFfyaqTnD0X0wT98HtnTimSUbSHiw07/x5LuCNJ\nyESh5DDA7uRCloLYk8Ro+BV4zaDojy+RboaM14qmNAo+h4CsDjQoY5RrSfWn\nkjEF8+CtlQnmaL/RWM8oVf56Gje2cVdM5FAREdPldyuaEXZTMVxddjOYQsFK\nGcnFRbdivjbzeqqe+4al4CiRBTdhhs5KdGQFEb2sUU0vWG8wdHjrBGWeaXkU\nmicB\r\n=Q3Ly\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"36b7c91088c6eae3f700a7b0b25f75a7195e28b0","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.61","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.57","@balena/jellyfish-logger":"^1.0.11","@balena/jellyfish-metrics":"^0.1.68","@balena/jellyfish-environment":"^2.4.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.99","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.18-handle-deadlocks-36b7c91088c6eae3f700a7b0b25f75a7195e28b0_1612839081336_0.41962745577772","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.18-renovate-non-major-1d2db731d5d21ca9eab93ae97a75a5e45812462d":{"name":"@balena/jellyfish-core","version":"1.4.18-renovate-non-major-1d2db731d5d21ca9eab93ae97a75a5e45812462d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.18-renovate-non-major-1d2db731d5d21ca9eab93ae97a75a5e45812462d","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"08c61d1d05a3d86ad6c3ba7258131c04c57f8ca6","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.18-renovate-non-major-1d2db731d5d21ca9eab93ae97a75a5e45812462d.tgz","fileCount":96,"integrity":"sha512-VlLC68mJxj8mR0Gk4siNJLn78zVGHVTK7Ap8HvbLtANy5TVoQrVcW4ycI+43u/uhpJ7tV6s8gX/8qmZhnsL5Yw==","signatures":[{"sig":"MEUCIG3xJpwTu8Uw+CGTunb03wTWNDPEOn8IcDzoHJjusCMcAiEA9++8vb2ft2kvoCEWehdDc43Jxpp8dHYjhotRVdlorTc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":596299,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgIfkpCRA9TVsSAnZWagAAQ7AP/1fDEkFoyyo2qWgg0w3j\nggz4VhyQd0VhXsJkJCKEZjXcQF+60SYNyM3SjyrxfauwwfG/NmVUahYpQ1Zn\npHNHYx3Ee2JJ5NebnXA2KYMVLHdfjC8H+ZuEktVZvPr9+FGtMx5SynPpZsnl\nDK3ttdkbnz4FIE6qrn7jzfjZ2GWCiN9QsBOrf/iRQcyEl3zkkTy6y0fsSl56\nHdaQ91RAMK6RTjcU4roGqcZOw0I/qRfBD00i5hVj+taF4EZeMiw+gFawA3XU\ny2MMqzVPh4Bx5lYy1PlCb7n5VlhNzKmhl8Gq7sHRwr6AL1lf+EzAbAS04CqJ\nNRws7K6fyfNSpXphpd/Coryv3TqRjhz6BUMxR3oOYxYbQzzSbmZwPvvsGbuS\nhOilT/HWBlO9wycoEQukdPWvkyZktVVxbLp4TcPwx6L/PDrXY5o7FDHjy3zh\nSnxWikFbq9QzS/28SRVjKaSQQX/8IEhSt+8dcuqicPa7vrP+NE12Mjqa+FcL\nW8trWinT17K4m3RWBC66L6vhMsA9/SeZT9Usgt2wbjgXM9tx/RIgTekhgy35\nXWNkY4H1n3qPeFOJ3yqKVS/KVJ2cB/PRcA2pRBxf8JEhb/vvhCYzrllMAnD+\nNwAkARUpXr4IxeJlZ4ewANQB1qaoF+VHhB581Ah1XjB2NNF2fTh9aoExggT4\ndWN6\r\n=LlrA\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"1d2db731d5d21ca9eab93ae97a75a5e45812462d","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.61","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.57","@balena/jellyfish-logger":"^1.0.11","@balena/jellyfish-metrics":"^0.1.69","@balena/jellyfish-environment":"^2.4.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.99","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.18-renovate-non-major-1d2db731d5d21ca9eab93ae97a75a5e45812462d_1612839209159_0.41015554949955435","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.18":{"name":"@balena/jellyfish-core","version":"1.4.18","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.18","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"99f8592385ca0ee8a8119c069ffb580b7d9eecf4","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.18.tgz","fileCount":96,"integrity":"sha512-AnEW5j09jxaQYHiYS4UJYO14O6PTGB4yVj3H9/RvuAqLVDLd1vGO9mTOuhDXXlkDKJNHXONAwDXI/BKd7z2www==","signatures":[{"sig":"MEQCIAMre6JRPEywc1dWOjgpe+NSw6QgyfJ4quXW1kbGlKfGAiB3pku8O+UJqcZZvAAyXPzF4jXSGNOzxpyTc4qfxSRD9g==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":596239,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgIfsfCRA9TVsSAnZWagAAs6QP/A4vSl7lFTnPZZQhWLEt\n0E8Z2Bj1EnILEQmeJDOnMtBV1khtyqX5WeJw1XiZCNrwb0Iu4CEj4kbO+B1K\nygNzL0Dl080SBduRKjiLZ94F1Hjcefd+ziaiaKWe9a3001BatHtozqbiv2VB\nqBvAE4rE8UZ/dKdCiHZHQDulzQo/RXIzfJWse5eWLvq++szThlrYw1BP6xKY\nI0jEfmhPgulDIUGxBza6kDqewEbsp7txR5KT0k8q+mvnE/N/SWt0/DRyq32e\n+QcLfFqwSfeeo7DMBmlpXAf3lXtAkaopmeydOiTUPEoVWFwhLpxvQyqqbyec\n+u5rbGR4pTslnsqUING7FqCQf4mMAv+S3yxPqHWrtkGUC/zKlq7v3ooh4wkP\niTrE2Wjbtd94CdCAgLDAVO9WYtzBvZBrzKY/sgQb9FBVc7k6qN9GuuE4feSY\n6G9F3JXOJphdb2KwFm09F4oBf7xMPb3jcatp5vSXtiDJzfRF1LIPoHynBcbQ\nDF+k3A16hJrVZfGGuxfQ+MCPkzkpQ3yia3BtPKgMIAgztiTIvqn3pGKW13uK\n0y/EtrgrnsXnUtg3i4RxsPk1nwUbcettiwamdI8g2de+lObUcVj4T5hXg8KD\nk4lW6pTlHKhjw8l7itBl+e1JMkqq2W1MYWQ1b0ZeNJgFqZs7OynSOfQ6OFDy\nRDOp\r\n=qlQB\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2c52baaf05e5130f2d49adca280df6167f4742dc","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.61","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.57","@balena/jellyfish-logger":"^1.0.11","@balena/jellyfish-metrics":"^0.1.69","@balena/jellyfish-environment":"^2.4.6"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.99","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.18_1612839711163_0.29712749538466876","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.19-handle-deadlocks-4a957b4f074141c7b6480a5623b780ea27dfec5e":{"name":"@balena/jellyfish-core","version":"1.4.19-handle-deadlocks-4a957b4f074141c7b6480a5623b780ea27dfec5e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.19-handle-deadlocks-4a957b4f074141c7b6480a5623b780ea27dfec5e","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a197fac18899b90f27e16a3e8390b81668f8e140","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.19-handle-deadlocks-4a957b4f074141c7b6480a5623b780ea27dfec5e.tgz","fileCount":97,"integrity":"sha512-hS5E1xmczI/bgsNQK/D29Nfdb2T63uUZDuUCDJQkQJA60HKNNdLgptUPmwujodDeUjUrRWa5vGCY1Vt/fnB5fA==","signatures":[{"sig":"MEUCIQDCB2yMFgWnT1go4nX4t5tFeJEX6oNmvNnaidDVbEUtpwIgaUA0X2WUdJdu6oEmHLFX65KCooZwU1bUmiD9kFAVqDk=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":598418,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgIfwxCRA9TVsSAnZWagAAgIkP/iQkOBeHJXGl3lhruOfu\nhSOVadbAt6ofEEB894TUM/DXNOf6drHsVZwifXJ8nUeltEpbxvMDYkghjy8F\n7jLv+dpqzLowUfHJJaU2KTMArstIo2C3I0kjydl/tNTXwgl8ipKxCsPZrFoR\nXq6IMw0RCsqXw0I0Tvt4VufjSUzK+zTHYJCP62YXAN7I/3AV+fZckHTJSg0s\n2taDQjUX0JAc81HC1vyYrNlS/mnfiyjxVC/C1072ZoqJCP5tGXNFs9igQNqG\njCXxwB2a0STsvoSxdYK5NNsK+KGJuaIXvEx6rY3VVBkbYU+ykuOkiYDQLl8q\ng9OHoR8qc8lS1TSGIsVtnbIDWT/BauA+krtbL6+RrmxJMVooqIVo/rgzx6Cz\neytq72JqJj2GQ1zjGxZhZ70B7nAeP5C0KMdF6lxYi6+fvhffNSEwXF4cILuA\nysirAqXSM4Ec1XfWoAcRpAsEgm37LI0AsWhkgpaIrvbefwczD3/I+GuoxMB7\n/aPuugBUcVVelo2bzHpDNLdCqZwq0EUJLgcm1vvLod71lRcYJWy1Vmkew698\nGJrXBXVvRxMJ6q4NqJnh08bZ296909vawMGonv53Fs5Kn7rojHq8GVIBYGsS\nPzNsb4IDqok4qzueV2HIt20PDHmQ4jp/wcBy97wUAsV4OnEYV2u5MTJcHaeU\nyr0C\r\n=1hNx\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4a957b4f074141c7b6480a5623b780ea27dfec5e","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.61","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.57","@balena/jellyfish-logger":"^1.0.11","@balena/jellyfish-metrics":"^0.1.69","@balena/jellyfish-environment":"^2.4.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.99","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.19-handle-deadlocks-4a957b4f074141c7b6480a5623b780ea27dfec5e_1612839984536_0.21405458473831085","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.19":{"name":"@balena/jellyfish-core","version":"1.4.19","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.19","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"dbdde9adea2c37f0119cd4b2fe6e516dd5d2e531","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.19.tgz","fileCount":97,"integrity":"sha512-2xLjQn9d7/NBpj88l08UUnkCfozprBTLOKY3Y10x9biduEaeZm2so8zbkBzBC8D7yIL5FLF7OfJ0Vhwk96aGQQ==","signatures":[{"sig":"MEQCIB+sqAQikVLQwi4DLO9WxGIt94EoOpmpYawmWJGprUt6AiARUIK+S4vuidNDyvaIErThoBD+hPiHdZSZoTLaGm2W7g==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":598360,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgIgBnCRA9TVsSAnZWagAARS0P/R7zq5OJdNdlSvRO82tq\n/pQT6B7iLXcegPXE70++U8UCNMZKYMIjp3HLigFr7baxgh+gtiL+jH4BpDlk\nWfcIDVLg2sXDxHwhviFh8Ui98RDkpMNwyklbVMPMqSL+okHohBrdY0PwgTgI\nMaWxiH3lnZ4X/PEf3HkqeKrsNQBBvYVWk3mZ0K+tP8zHrQFy0remO6qlr6DW\nV+Fbb4QKyQ/jhfgANViSrbGR6Ji/owdO72Y6Y+coU4Oes/G2hL9HnslfC+3B\n0EhFQw7bEm98ujEpt6XAez81bdy596iCrd9fDaeVlb3cpeUrU97vx2XZONfq\nCH0YuxI+msoWku07E7NH12x17ep2QWwGVzyh4DXSZqcafKaHlAmn0kuchJlj\nYy25hGXfRhza6LaYDAAGj0/Zp+s6Jh92b95GIMEmY1bLBlkpsOb+i2CyuL9z\nP+UPDfn7ykPcICyn8vW13BhYo1ELn5WjdHWme1WYE8QrZZjlV+lOjBUlFkRj\nE5y4C/VSX8FjVwzPVZcqJIhFciI0SYJP1zANf6gfhJNrPgKM3bzO7HbwV+L9\nvKEzJ0xsiKUrYx+97byR+7vK3KxPIxE7VNjNC1Ycam6jTj6oZ9Ouh4t4qoJs\n+TT3OpQob6eXpmVX04HscMpC3EDe8tNyVlow5Jn/KWEVNWtYlFxFCQV6I8c3\nPHOj\r\n=OOPJ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0382a69020dcf56aebd0849fb20a4f2dfa081242","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.61","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.57","@balena/jellyfish-logger":"^1.0.11","@balena/jellyfish-metrics":"^0.1.69","@balena/jellyfish-environment":"^2.4.6"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.99","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.19_1612841062595_0.8484558527548482","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.20-renovate-docker-non-major-27c3c42da20185ead5627ce7b8e0cb9d9e3b33ac":{"name":"@balena/jellyfish-core","version":"1.4.20-renovate-docker-non-major-27c3c42da20185ead5627ce7b8e0cb9d9e3b33ac","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.20-renovate-docker-non-major-27c3c42da20185ead5627ce7b8e0cb9d9e3b33ac","maintainers":[{"name":"lucianbuzzo","email":"lucian.buzzo@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3f4ab9a0a02d5feaeacc996132b2fa5cd8cc0ca0","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.20-renovate-docker-non-major-27c3c42da20185ead5627ce7b8e0cb9d9e3b33ac.tgz","fileCount":97,"integrity":"sha512-fk6Hy9eeGCJMhm7NFpEtym3tlXdC6ubq31GN2CeGyZdZXnh0Q+cQqMhZ2BxhQqinCln1ha2tfws9EHTtDc0x2Q==","signatures":[{"sig":"MEYCIQCrAS2Zw4B3CePaSofPhUe2hxZJB26+LuKLGclRUiNcIQIhAKC7KTBKChKZk8jnSm0TAx3NgMYOaHEf9VzvsH1ZktGL","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":598528,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgIma6CRA9TVsSAnZWagAAfS0P/0G1+3W4JxD9oz+0ewR/\n3aIVL/v5inEceKnBtD6yShF7vV26WfTyqlXYVd+1FCJH0iRIW7O99K7k8cB6\nKxmghqbfo/5ExnARtbIr9C+gk3MPiJ8Q9oJFqpe/mMKrVq/UfQy9jmKx/GEF\n6NzYNr0v5hQvGndPwUbG0Y6/mRlHfEBHxPAh0BZPYhA5rVzHM/dVjp3BGi/C\n039UK6G/mnuwYVnveLs/hedYzk1CjiDXh53Cd0mZQwjjXSjpQ8HGWCDOuVXY\nCR25TgIXIn/fDjO/zwF89trFJ+VLsAn9rQVqtvnkjxIgzelAv2UZTBjYYOW7\nyAEweWZgQ1w1a6vLFKUeVykjs4GmQbNUXIz6iSfgikAFr+PrslbiBT2pz6N+\nNa1xp5WLZkoeMHIr/HfAcBeFjPtEUXNGWf8T3X5mV7R+zGWxmvdIpw0y/59G\nT9galOQfigV+FwtomjcCeLTKqzZkbFP/QRSNEd2P9j3878uQYpE+i7H3MJRM\nhZQ6uVk2Ec+Wd74y+KDfWEtPt0c6Qz6olT49iIHNAEBnDdys21n/fZnaQb2P\nYH9ftCHK5n3QxElVbfcAe8XXNzn9G7fQiaT01F07lbbMQEyx9GyD8OzMi9vZ\naYks0IqbwtwV94fHKwymZucLQXq8e3z6bwoS5ocLAZHu4wekElTxETnlwYUm\n02FC\r\n=eR0Q\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"27c3c42da20185ead5627ce7b8e0cb9d9e3b33ac","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.61","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.57","@balena/jellyfish-logger":"^1.0.11","@balena/jellyfish-metrics":"^0.1.69","@balena/jellyfish-environment":"^2.4.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.99","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.20-renovate-docker-non-major-27c3c42da20185ead5627ce7b8e0cb9d9e3b33ac_1612867258264_0.1718489716825058","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.20":{"name":"@balena/jellyfish-core","version":"1.4.20","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.20","maintainers":[{"name":"lucianbuzzo","email":"lucian.buzzo@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"fedc28a5895c4957f0d4f2a901913abae23ce27e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.20.tgz","fileCount":97,"integrity":"sha512-V8cCJLRlE/gOvRz3enXXUTkwn/fv+AaPmlmNlNFlDa+PU5UfnuSrPhM4jm2BbInOAdKh1WV9lMgZ8ELQxhLqkQ==","signatures":[{"sig":"MEUCIQCcsNNVC2QrJBn50dREDV+uAPvt2PrF13H4XQjtoYiAzwIgMxeh8Glhb8yVx8v5SHFVpw8OlJA7YVQlDoWYPpwKMqw=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":598461,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgImiGCRA9TVsSAnZWagAASGUP/RDnS4FCdZGZDhblnJDu\nEvpVgJKjzZweVqdT/JEyYPqF3ZoQSG0DyAsuV0PWrzFtB828uZmSigM3utJ3\noLbybi/0JPgohUsYMuD+yp+jH+pt0223ITuTGmPVmRwlfD6XLFFhPvAL2Sct\n9IfBmR8r8dp0vS/2p1YWYzR3QVc0imM1rzUwFRr9n4dyd9Zx/EJf8q8t/G7h\nkr7Fq35ctTGj4oqBCmSyzZyLM4/jDBNRpxVI8MIvvyeuE4RNzHJJij7h56ws\n74XynOPzp0Zdx6DQJfxjowyu4pHWX2T4O+gbsIiDgenlqMQ11T3reWmrk42Y\nwZNkYZMch9A3pxz7uKjQvgB+4gb5LHxfGzkKcfooNneb2+T2yACB4VmB2lVH\n+JvDJy+t2yo4TZIL+hdVely+mocjTCy7jOrUiNBV75jcPVuIPKhtQz7dYZis\n0tuo5GuAj3ELwjTpp0V+0MddbCZSUD32ZLCfKYwuhI0K8i+mkgHI0BgcbKCs\nr+iuLfF6qkjSOQgVt/BPkYhvlIqSTRcM8LbvJPsQZbGXNnbDO3R+2NbSW2na\noT1ilvqI72YZ5pbNBZev8pb2O1ICtYwLn6mNTCoWqBeZHJphuemUsDz6DAY1\nuuMr7LwY0Yn4NcqzVK/ePfW4x0HtQhOs+GRQfnkl2zyZebgUrp/9x7yxuVqH\naDvD\r\n=YUDa\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"319952b2d3c8f13db17ddcca1395c24571d89a4f","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.61","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.57","@balena/jellyfish-logger":"^1.0.11","@balena/jellyfish-metrics":"^0.1.69","@balena/jellyfish-environment":"^2.4.6"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.99","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.20_1612867717735_0.00944341337282939","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.21-renovate-docker-non-major-36b1753718afb1f599babdb7a031ec2f99553e37":{"name":"@balena/jellyfish-core","version":"1.4.21-renovate-docker-non-major-36b1753718afb1f599babdb7a031ec2f99553e37","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.21-renovate-docker-non-major-36b1753718afb1f599babdb7a031ec2f99553e37","maintainers":[{"name":"lucianbuzzo","email":"lucian.buzzo@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"bee13269c67a253eb6eb4c64e97d726b7fa50742","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.21-renovate-docker-non-major-36b1753718afb1f599babdb7a031ec2f99553e37.tgz","fileCount":97,"integrity":"sha512-mb9ANHzh7YqE0r2kWdzK0S9osXfhl3REyb3r7pbEbfLOp7DO4E09kFmHmjta0HkaIqAAvbyvXHcrmmoQ625T1A==","signatures":[{"sig":"MEYCIQCKcq3v3PpuXiO2fAZXa0zwHm8ZXtdJNVjYOPbaTeNChgIhAOpRf0Bx7DLwcgNZbFsbM2bg+1c/0CParq3RsMzpLaJ2","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":598629,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgIp54CRA9TVsSAnZWagAAmroQAKRWhc1mZC1cd4HBKK6A\n3NXxSuVQKiSHsVGu2CKRD20U9cLot/bIpeezSFzMDQMah9PGpIYj/EEcseRU\njsTU/RQjnDQXMEbDDv+kXTNwpofTWG612jkUBMlpbdyjc6vIEHx7cOpYUNYM\nEQL5avSoDrNTk4EyyqPkA0xxbevj3RsORPk+0zi7t4pJgjIdY5RA5/PKW9TG\nF8WQhwHDnehZ/ecJkHFw4ytKWpgaloNW50u8z7h3WJPMse1pvpUepYjf/3WF\nIn+hVrBP79W5X2YACLohABcgVgsx4ZZyJ3zdzCnFuFzB+tg9rMiUJ07XuGzd\nHT3s/x5B8jIIgHivoXnbrQKIPbSFgrc093YuPaDSHfTjMipQZa3yj8vR3q7S\neGhrBVakhy1JXcniL1CipND1qWER25NxYiR5wfSuo/LbarjTT/9OFQSiEUIF\nv37Myyjuvh8zQbUb3SylikPSpb6bAZhynn2pfO4LKA5IgqW5ZJEzeKeNVr+j\nQq+tTY19JTqjnCrBtDLCHvcTwq6PBtoUn2Lu+XbkRGVnS6hc0ukysoJbsSR6\n2s/qVjfVMzL3Dbfpm2Q52icu8Dcc95kp7pT4/8fTlu7emOstv4eNc0T5ssFX\n429LzOB+7fHcxzcH/247S4lQ0lB4XsHmLS2pSEWKcuZZD9pD4pf2c6fDMlaJ\nT3I7\r\n=nmg3\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"36b1753718afb1f599babdb7a031ec2f99553e37","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.61","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.57","@balena/jellyfish-logger":"^1.0.11","@balena/jellyfish-metrics":"^0.1.69","@balena/jellyfish-environment":"^2.4.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.99","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.21-renovate-docker-non-major-36b1753718afb1f599babdb7a031ec2f99553e37_1612881527786_0.8257369376095085","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.21":{"name":"@balena/jellyfish-core","version":"1.4.21","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.21","maintainers":[{"name":"lucianbuzzo","email":"lucian.buzzo@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e78b799abd00f6c2bfb5db977389fe3a42de0e0e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.21.tgz","fileCount":97,"integrity":"sha512-Cmi2n4am1OZv3ji7xShyESOQD8IcYK2Shb7ofsvpxR3EgPvxV1XuK7OLQDaoIt4J61REkgXZvsx/zTdefOemVQ==","signatures":[{"sig":"MEYCIQDLmuYijumtdX3Os6dDiTiRy2h1w95GM3OuiZkNjIE1KAIhANsCHHjZKh3uK+ZtV0D2FOtqgb791wZJ1tquMKXYKOSd","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":598562,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgIp+rCRA9TVsSAnZWagAAM0AP/i2KZ5IiG6+w71ns6TVU\n3OTDvL4CLj4tQgG7rn3weGzQ49B7RRppYndFGO04sV+sfzQu9+82B7db8DrM\nOyQKdlEjawadAogeeZsjkBTIT5+pnXOmgiQJek8lpfhkLI6MPsCjSydBmuFL\n69uaaMneM9wkheQFbgqlhoZuhIgIFQZbg7Z6zdIVz+Pc2PMX5ZjRlWUBc7jc\n+JP6fD9qaEJRaYS+11syur3uJ5lVN3N6adpeoonDW9a8qQIr5Oul/1y9rwjI\nB4vD5+a0BAqmcEjJyyDTjCmZsEr4PspNLGFgxy/5OelLK3XyHiQOdhELN+UH\ncpQayFqFkvkijY7k+LG3HugqhJ0YYNQFEkwuBt6NQh4EL3aYeiw/W1ACop+q\ne4AUNdti1p4Un1pLryEU9eEOJyRGvrnnQzM1Xwek0VkTf+gSRme4ZFXHoGhZ\n8Q9x1Y+nf5J+9ZItFvtTMoKo6kMBiEhs37FdjpIefmrNXdjfUdH0DuSh5PUA\nise+j6N6uLSbeOi2HqiSa9zyDsXwVjc6Yo5wtQnrnCWjKNOrG6uIJwTSi8Ji\ngvq5EmUHjwW/emcZXZ7iy/mT9KrIruGZErcoT6zg6o8Y7An3lvLxEKfxxfb8\n6P1FmmEQNnXP9g6jrsN/bTUXAkXRjGeNQqWCu86sT2lASu4wDq4EjkXpAAED\nYy4S\r\n=tZfQ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8f0ec997e9a9b39000bee1ff5c812840a8cbbe1d","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.61","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.57","@balena/jellyfish-logger":"^1.0.11","@balena/jellyfish-metrics":"^0.1.69","@balena/jellyfish-environment":"^2.4.6"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.99","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.21_1612881834650_0.8615206657806307","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.22-renovate-non-major-823eb79d6d13e14b523a2e56a76574e57b51e1f0":{"name":"@balena/jellyfish-core","version":"1.4.22-renovate-non-major-823eb79d6d13e14b523a2e56a76574e57b51e1f0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.22-renovate-non-major-823eb79d6d13e14b523a2e56a76574e57b51e1f0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"85313cae36f316a5dfbf92d544f1aa7863c46a44","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.22-renovate-non-major-823eb79d6d13e14b523a2e56a76574e57b51e1f0.tgz","fileCount":97,"integrity":"sha512-BuHPXql19irph1Z8h1C9gU5Ip9E1a6zMc4iprlZAmKurxOVTD3Kr7ECq8rpnByPE1MNfGjfjL9jZeHhrlZ/Z6A==","signatures":[{"sig":"MEUCIQDl/p+KgCeT2TfEQs2BbWFnSjwUGuT7Iun8ECl823ix4gIgad+Sl0OG7IVpPSp8ZosY88mHelL6EijQ3B3bBpg3o48=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":598721,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgJDcCCRA9TVsSAnZWagAAOR8P/0kBcff1NIvqT20UzOm/\nxi3F0KlqFGedyVf5NbIiidHPZKmGeISdMWrc6vJzycsnmlZWqpIXUQ3j+bVy\neWuFMzGWTCzTdcYkAKmI4vbIjUrbBs17dhPX2LpcOnLefNeoTFMWegkwTeZg\n70nbM34zSp+pUjuHUHwjfj6N4wtjCwTbAEuvCCEhzmRaqJsyl3X9gil/K/Da\ntoXu4/m+xBaUwPILpY1ZOy9BBd2Tk/tLNSacVVeQQhl+OoMiye2kO2zLq93R\nBU3fl0P7/5cDOieiNIzUlDmWTjVeP6wtjBYlt6y9vdhXhWF/Hu+R98m9o316\ndIMCbsF0kCOfxBJayxno0y6P+Z59Ln8Pf3yuQWqJv8v5tMntxl4DkhEZLwK/\noLOifx4DtiWgBgqVmXMU1xE4CZHgayOjgZsa+N6VNlvkzknBuWm6r/avLsqY\n3DiJcNnGMjUFt9RDfeirPk1n+wzOhknr5pjvZeIAI+j/9Z3v4BHBgBgHQ+Yu\nwsmsxpEzf48USapLkXBNwf1B2T/avYZAJq1XTwJX3xUdnoI4kTgqzH+mkgZ0\npCXU1QITZaHT242chr2BMSgeQiw0KI7il1G/KqcX3p4NfjvbQOgPPH4sck0u\n92yTQWkaYbglotKzD48OMZtLMjlL245sDBOmr5jslq7MNUwu5R+EqMjH3Pmt\nwhUC\r\n=OP3/\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"823eb79d6d13e14b523a2e56a76574e57b51e1f0","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.61","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.57","@balena/jellyfish-logger":"^1.0.11","@balena/jellyfish-metrics":"^0.1.69","@balena/jellyfish-environment":"^2.4.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.99","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.22-renovate-non-major-823eb79d6d13e14b523a2e56a76574e57b51e1f0_1612986113505_0.8178136383593202","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.22":{"name":"@balena/jellyfish-core","version":"1.4.22","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.22","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"68e618fbac25a5f164e111d0169f33bf8b0b00ef","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.22.tgz","fileCount":97,"integrity":"sha512-3udfvpvGrwmttBEDl1qsH6+c8oF1aCaKOb+Fo5NsWW0tdvPAnmlH9A7Q9MSwv3DyQQuHjl0SD6QKCvlvJGXmFg==","signatures":[{"sig":"MEYCIQDnUONg682hBVCVMfx1pwjqWLNCbb5EYd72cbaFChuwewIhAKuBFbayQfLy1HTf66h153DYVVc1pP+Ix/Iiu9MpfMLP","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":598661,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgJGsyCRA9TVsSAnZWagAALbYP/0efDCQU593s+QCZ5IeB\n6PE+1Yg71i4GZRzDL8goNCrfz8D7TwPQTsNWxskv/zz45zxVfUVupQ9VhIki\naqJe0xfV1naUxfp8MeThz5PEIzR8xb65fP518I0fSHmz1zWEZowSj4pNTnN0\nGKKUvoyeymQJ7SQTHgC4QgjttzoCSQ9S8srPj9Azxe+o+RF0u7Oet190+jZP\nQXGkjwCnVUrsxKVoY5cCn1fTyjKZC1IK6bAussQbamV1zn9VTdpv6yVlFam3\nxrx/wPCUYmoiaO4rsv2INA/DghEdDL8Qj++T0HpH+v7tWf8Yodi9oCOljmuj\n7wYgpaUtO9Q3QsmvavFawv83a60EBxuIB+4w3wDgdI9r9sj7sLOc98OJ1DSN\n5FDqAzwG8D8zxyNe2y+2zS9sfop20wrkML/SIA72VggsxTMri8SQktYcpZJ0\nqAmn2JugA0KWFWxGiwemglnfLiLj5f20MWnZw6L11rKuky+tu3QHRslKJ79S\nz5I+UXDUb211JQtjd1y4261tJ8p+suGh4ERwRRAHGALed0smH3LA8793wWRm\nduiW0ARFD69eeM6bEge5S5JWTbnL+NvIyF7AF/NSvIsWzx3ZMdVcOC/YpEGJ\nj90r8F4AxItZP5ygbXzyh3LJ0Xd1VwmOt3whY+vA1qllkdXvGvQ3/duf0tuX\nwoKq\r\n=jpFZ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"eeb5dacf036eee7dc59082e5b6dac54c262040a9","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.61","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.57","@balena/jellyfish-logger":"^1.0.11","@balena/jellyfish-metrics":"^0.1.69","@balena/jellyfish-environment":"^2.4.6"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.99","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.22_1612999473550_0.7233003241173879","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.23-renovate-non-major-2fd25203120e45acdb80c4c32c6b509ec927927a":{"name":"@balena/jellyfish-core","version":"1.4.23-renovate-non-major-2fd25203120e45acdb80c4c32c6b509ec927927a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.23-renovate-non-major-2fd25203120e45acdb80c4c32c6b509ec927927a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f9500af861b10f5474a4157229f1ac81e9200573","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.23-renovate-non-major-2fd25203120e45acdb80c4c32c6b509ec927927a.tgz","fileCount":97,"integrity":"sha512-yPSbz8aNPRRC7oiSSbdGN+nKaAVPTXSzWLhSc9xwgUZ3mAKXHVO8HAJIbxpXvorVqGLZ2s0zSLwVQStXlaaXNA==","signatures":[{"sig":"MEUCIQC0hc292XM4oX2gMRvZKadfRFxa5frKM34D0CXZXEbdLwIgC86HXLtelGdiI5O6rnVxBdAbx/dQ08iagKrTBEktSlw=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":598824,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgJJdvCRA9TVsSAnZWagAAImwQAJ3fGq3Sg1duPvSJd4aa\nCAxPSoXzMqJnPZDE6A7E0JHHAdBnJyf3N53zMyTzrJ6UIX+5knhFsdsOMWet\ntOnxzBgudwdf+daJZ8lU8Rxdhh5Z2b9pzySxMwSfuXEqwgsQNEdTVERqH22S\ngZbCCyT+SeObPkLqiPel3NJ+Td74OLquTCPfa5pQZ6r/mKpAKA67NcJLEaqk\nkD7QkQFUDAE7RwlV5VDvhqVZf298A6eDfp9izp1xRFd1RGEfMy2+WW0592er\n/5p8l/1IH1+7/AQiIT7ytVhUCE34s1cgHaZfsifV3Ggipxazpv/jypBYFcpT\nMlafA68QvmrBiCTatjAxbXVELBQbGjBqMJRH86xEaCcwppouN3J3HbGc5jGW\nN6DI7i4OffKBvj9dExesqPzpiCA8Bo7rqt/kVBiNei8tsZPOUw9V2mP1V95a\nOdQrEwSNgqThYlbVx9872jHodY8ry3rWbI01DHXaZPTmOetR+zvo74WdX8zA\nHIag/4a10IL3jwhZJqX1A5tpm4f8zgbjSF7cgJhfoM0BT/T4OCtzrb8IvHjn\nliUt7fCMTkcTO/QOt8PCbHWXml+4MNlzCMNbwdDSglfWnwU2eMtp1DP9e1sq\n8SnAco7SIpoIYBZyJtKDdxwjBE+dEQ2O3BUEDeQNHQMh6y8H1z+J//MTpuoE\n+NeX\r\n=oC8I\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2fd25203120e45acdb80c4c32c6b509ec927927a","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.61","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.57","@balena/jellyfish-logger":"^1.0.11","@balena/jellyfish-metrics":"^0.1.69","@balena/jellyfish-environment":"^2.4.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.100","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.23-renovate-non-major-2fd25203120e45acdb80c4c32c6b509ec927927a_1613010798778_0.8248461562111817","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.23":{"name":"@balena/jellyfish-core","version":"1.4.23","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.23","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8d8ed20c5f98f93fd1c0fffae2b6c47bed801c52","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.23.tgz","fileCount":97,"integrity":"sha512-yLA+e0c4ApRQ81wF1P/bXq30bzd3frctlJ/NTWDbYMrZoUTm6EKxRE7S8zd+0zmkxPZva6P47XuZPS5p9M6pvQ==","signatures":[{"sig":"MEUCID+D0bNY85rffuGoPavUOvJXLWbKahr/NYkeIN6O3sAbAiEAtXy4ZvoKiW3iGNyopFCkOQJiZlVHPaZMQCWyjXWnpBg=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":598764,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgJJi9CRA9TVsSAnZWagAALDoQAJnHQk6MaiAKnUIF2GXR\nZs9ahLtxYMkcbqzuApFsa96Hl9L70GCDHkoHkR1yaV5nW/xM4zG0etwn2QxJ\nTdgKWIAYFFtpqjpyZ+TrnZFAE9hsZSJ5i1P6us4HAYib9jihr7JDsptLzBUr\n+B7SGel1/IFtUfMqLUQeOY8uRpA27yhDP8zp/5H0JMoFSJ+7+iScr7wE1iew\nnbzjLaqdXdypDV7Q6ssz7rc+rd8qXdjuAKetrXCOtWvmo4DLo/wLJX2PhMkI\nZ9RqTLoosuzplfClHrbKXKHKw4Fi0wj8USPWSKMEyNf5h80mfrSRKKemrt96\nagQRVYmnftMXiB/zurWtFCbPQajuwuzMINloT3emljVq2KPuqD6Qyh1Y4DtI\nqR8cwS7k3QzvOt0N1nebyoSgfWZZ5yr82S7nZvjMI8i/Ipcio3pfZWxY0tKa\nLIutF/rKcFvFsxgbXF/XsLEFAoyja4l1rfhs2RpfgnaFlX/dkacfkgWkUc6H\nX+qfDJF/QepErbKvpODgO4QSTzdoy41WM/F1v3ono2EzNvbMeHs+F1RSmDPt\nU52hwxrrdEMGyMRIQeLCUdTODwOSfhcWs48Y+6eBRQzU2/07JedxPGhd0Cse\nnjgPAutxrBM1nichFZhmiw3sbjBiGxl7Fl1DXL7ZX+yXrKI9zeObpJMtRrAx\ng8Fe\r\n=V4RM\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"3f023e401e72c5601701dac388552dc95ce5650c","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.61","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.57","@balena/jellyfish-logger":"^1.0.11","@balena/jellyfish-metrics":"^0.1.69","@balena/jellyfish-environment":"^2.4.6"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.100","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.23_1613011132874_0.25851417435976476","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.24-add-fullTextSearch-b089cd5f6540c608d3ea34960e12ca81e1fa948e":{"name":"@balena/jellyfish-core","version":"1.4.24-add-fullTextSearch-b089cd5f6540c608d3ea34960e12ca81e1fa948e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.24-add-fullTextSearch-b089cd5f6540c608d3ea34960e12ca81e1fa948e","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"285a9ac99075a52a08d47f8d9f9d25080a121708","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.24-add-fullTextSearch-b089cd5f6540c608d3ea34960e12ca81e1fa948e.tgz","fileCount":97,"integrity":"sha512-Mhvk5qlR/2MSMoupMFs6w1RlAdhCG2UzG/+lMjutZCWg1fPscOKfOgSoItujm8K0jVsjvR3OCJoAbs1q20g9Eg==","signatures":[{"sig":"MEQCIDyoDoyrh+EqDcx6Gu9nu11kKHe/YJOBckbxEfbOZ1YIAiBqCmQIS6GY46m1dn+HF5HrVAfUhl1yIc7zdPvpGPW6mg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":599056,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgJO3BCRA9TVsSAnZWagAAd0sQAIbUchlTG3OfpguNCAvT\nabNUwCpM8IwXdvW07vpgr0GHY1RWDpHA+TvRdqb7YNHquULp/o6/uLKLjdK0\njVkvlTaIAYERMoVFfXTshc4pBzCFQwfLpAKfml/qKjdcxxnwKj1MfAPF+Zcv\nmARVVr9AzKlzpaML/uDQzCc1Nb1Fc0qrZ75JsJunsEKjiNDkUczG7zFKTGGT\nX7ZgrTsLtHBeHSYmby9oC7k3IC0HaPfzQ34b/yjVIxJpD/dKYlKttaNUle/v\n50AdmYVz7kiRcFqfKNdDgrIYV7MPhiBcsAIZuxLvDfdOlV+zaD7OkOcdvjmZ\nEeq88JFKh2SMAAyySFpxST1H98A1ZYqhhWVIHMLwpNMoPWBmRyRwpamLtepQ\ncvQ4oqL+cJUJbklVgSHM1aQL16mdkcqItyKA+Xdi4ovu0GGno5aR2/t2O4QO\n0xrB9sJT5tFgL0Dy5tJELvSLpDFLPpFO1MF73JsRs+iuMks1wILJmn5TkEOn\n6rVo1gL64Kl3EGMSfxTluUL+JScjzNxZNNl5F9DRyHDfJPMx6p9Tm2rcfleg\ngx81/K19ju5QxJnabzZ5HkpuuJLYkQFqCbduvTU8tLg/EBUWWOqOFdv+tDdL\nPT25XLxK5uC98/h37DIYx7FeEZxNxVhT/OrvC5BJCqD+uVmpHW2WgNf5aIbb\neD2g\r\n=9w5g\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b089cd5f6540c608d3ea34960e12ca81e1fa948e","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.61","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.57","@balena/jellyfish-logger":"^1.0.11","@balena/jellyfish-metrics":"^0.1.69","@balena/jellyfish-environment":"^2.4.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.100","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.24-add-fullTextSearch-b089cd5f6540c608d3ea34960e12ca81e1fa948e_1613032896521_0.1969881235090043","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.24":{"name":"@balena/jellyfish-core","version":"1.4.24","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.24","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"036b3a7bf7f2f9657c0f7f96ed12bd3df686adfe","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.24.tgz","fileCount":97,"integrity":"sha512-MztEkE8Kan++lIrEid7zryUGVwztgUlB+Vma3Pd46tquloRqGPyzdYSrb5F80YTjqbfRDl468uudDpyLjv+qgA==","signatures":[{"sig":"MEUCIQDwaaBubcXSNehLH29KHlI95V9O6/bcadz5zUzpOu8WuwIgdlbScEfbymEaIobIxoOXP1GanzOsiwQGGN8OPI1CtII=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":598996,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgJPRiCRA9TVsSAnZWagAA4UMP/2AMmMxNLqq8z+I2ZGPx\nLlrlGYnysJzkWFU2Sm0mwdDLPG0EagRqfSrlOWWJp8MXUoYXsZhD2k63q5Vf\n8wc+2BMrCgp4XtZ6seEoCtQaLKW8m+i9I4ZXBFZE/Dnp3VoTCY9IhIq9zMBI\ni0NtYqDHbGqT01TmPtcv2pYIPnhj3UadhOyaiBkHfjpOxp12lU7ou2dusnl2\npawuO06SJZw7ujlUfQKbQ7z1oHBrQBBXtMoIj75nJ5W1hJoGCa926tUJMbNk\nOTBAJPhwg+0Ica9IMWfYoQnDjZTc9ndu7RYoE1FVosK+YHCpOFaKMu/UsMHg\nn9Pw1UOrGe4+aOV7qtKdHd02jQg1vJhMAQw1MIhvCD6+TDFxyJV9+/OyLkz8\nVtYKINqaX7AZrPS2yAU+Jv3PzHCSEd4QNo81/MSlTKIVKk7WQ2Ar9BRV9lwH\nTRSVYbEgn82v+J9F1vc/Ul+B2nFYnkP3YssUj82dfYkIFLmuY+S06qZcRV/s\nRTtTC2fj2/LRF/JaCkgY7yXIVz9UEC4mGFL30R5ZbXGAGF5BhZPE1LNEfLlO\nA1/+3KlyMOuHHQ1YX9GXcSZ0x/kLN1gt2KWxHQu+69OpWS6ktwD8vbcVHjv7\nTLy3F8NfLiBa01UShuSe6urM75bri/hJJbnVKiUdqbypGMkEZcxR3EyJ1OBj\nFh0/\r\n=WP+O\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ae55bb39604a0946294e6b8b68cd08db260825f9","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.61","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.57","@balena/jellyfish-logger":"^1.0.11","@balena/jellyfish-metrics":"^0.1.69","@balena/jellyfish-environment":"^2.4.6"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.100","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.24_1613034593773_0.08590560445074535","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.25-renovate-non-major-ceba5130e720f7eab15f320eb696b24497dd24fd":{"name":"@balena/jellyfish-core","version":"1.4.25-renovate-non-major-ceba5130e720f7eab15f320eb696b24497dd24fd","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.25-renovate-non-major-ceba5130e720f7eab15f320eb696b24497dd24fd","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"98a87c31bb7b2d0486a211f7f4fbe70095fe2a9d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.25-renovate-non-major-ceba5130e720f7eab15f320eb696b24497dd24fd.tgz","fileCount":97,"integrity":"sha512-P1RvxlPHBqzcIrHEwacBaCfSCVQAxu/oaeYe6d+jjBUs9zAuMLNL4Z+dmdv9AmCsdT8cn6YkzNkAUm7HtfVy5g==","signatures":[{"sig":"MEUCIHOgbfykpis4eQnm+uSvglSj+4F2wEX3Hr7Gyh5U53VcAiEAukv9BMBpN2x69QpCiZDEwYvd22znBgqm5Sxq6sSbfKo=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":599122,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgJPWRCRA9TVsSAnZWagAA2bUP/0a/RzHwQYDwRctTbXPi\n0kfso1R/vI7UHPxmwZoO9/zDiPGqjI0cw6fxZ71XVSC6Qqvp/eAylSTrSyHi\npX9zE1gPS1Pt18EVSedLDE+lKkgivMr1bIZbrG1aLKVrq+EvIlgCNAHjTSLj\nmXFlYnMQvfShp4HxInaOEKFLQJMnrBoM7Yepg2UB1WV55hd0etDEH3alGTVc\noAZ39R9aNh7ToZy4UzvLwaeRYOK21ZtfmylA7GXoXHf0HIhnwWSXxfwhO1A2\nQJYbfuveNzwxy8KolCyIfozOSiU1bgiscVRH8XSISBd245r+6ungdRSOo+2U\n0spi/b9dKM79v7xwfrkhyukOFWRN5eyJ6NGPx6xdkhkwpfahGWtWI6tbhgyk\nTl+RxVq4FCkYoz/RuKfY3wU2h47ERPmQEHkEkY7I4bSabm4ZovBqtPvX5hR/\nc4tj5jxFlH8XE6MYG8JN4+gjiKIzq2UFin9172M8KS8MOfffcxrIHTMy9svf\nMI7PEV/vNCK4tNpVL9kBnVKwV/fv9jTy4AlJp+hgg7Wp38DvYA1fu7MY0EvD\nxPOXLLgNHKnHGYe2vlgcVoU8bI+mdJ0D1m+kvOrj3lzaFA1Ch4A8h6kFBvNz\nOB6nTRXQUEWuPWEhxYAAys5dQyVwLVijb9vEQyKHhdFuw1LihsysVJ5/C+M3\nJ7OQ\r\n=hyOu\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ceba5130e720f7eab15f320eb696b24497dd24fd","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.61","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.57","@balena/jellyfish-logger":"^1.0.12","@balena/jellyfish-metrics":"^0.1.70","@balena/jellyfish-environment":"^2.4.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.100","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.25-renovate-non-major-ceba5130e720f7eab15f320eb696b24497dd24fd_1613034896747_0.3749978717307654","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.25":{"name":"@balena/jellyfish-core","version":"1.4.25","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.25","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"fd02dc016165eb36daef56aeedfefb67d75910e6","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.25.tgz","fileCount":97,"integrity":"sha512-5m04pa1Ee9yHO0LN8ljpBWbi2MEqbb1CE5XHR0uNf16vnkKNunTQoo1PnfcrSPx1RVq/T4mK8jLad4i1B0OZyA==","signatures":[{"sig":"MEUCIGAFwC8eiT1qrYNUTTjZ4eTigiRFAsYrmGxtTIPwKOWiAiEAnR5CRjND/qJPssy0rC2eZdqICuSjZ55CFfEYnlBKukY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":599062,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgJPbBCRA9TVsSAnZWagAAtOMQAIfNTKfqi7ke/NV0fyot\nY1kX65oPL1Ql0BBRlvem3YAyw5+fmMm7CG0R6vcHKOR8zaLF9PC+TM0TjyGJ\nSO9ZyeiJewmMvyeGkM61uFkQsQy/jcnnZhWp2SGof3XmLEHrZXTG8cPGZ2dQ\n4tKRctcQuvZWEl5ZzvMePSv2TMcBUNy851RJToUY2ocBWdODG/7W0DVkFOIr\n/vRZ0ddFmPPW6KakX0V0AxYoye92LBx4YkRs02/L5NohOQXi86FWh4p4fD1y\nMdJ11W7/egMzT44LL3XJzxx7VHb54wBlSaPVN8DJrgWn+PjIYtEaPgNu+GTk\nA4u/Mud+UeHPYpJ6eqXyTONJb4/AchA/3Wj379pIojVc5hHEXzR+3GyjJobw\nwdOdljhoUG+dS9EocZStWaUt5W8qP/5Ih59jEeeUcH10sPzFj2k321cJP1ZW\npKWLda/6FodXKMVa3PBwzTscEidEmZlZyZvTWo4sax+AEwUiTj9Oqgp3Fy/m\n7btWkcqD1cm5bisrDmJC+n884JV7Xdj0wclyePZlpROGp1023scV2PSPJr/A\nXtEUnOsD2HUf+XGqPY/qSI5gEQbXUHsCroJZrLjp5Toc67Kq3MemK62DaxAq\nAGbzaW09F5FWsyjQhDIfoKCMCTiPCtw8VTV0zZgoMaRU2b08B3yW4LKPO3+Y\nWlmT\r\n=whD9\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a0acd6fb16dd316ef682ea13600e211695b35a6c","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.61","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.57","@balena/jellyfish-logger":"^1.0.12","@balena/jellyfish-metrics":"^0.1.70","@balena/jellyfish-environment":"^2.4.7"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.100","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.25_1613035201346_0.4780936551263544","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.26-renovate-non-major-71e713b2aceb33412d463a547de8d3fb263fd56d":{"name":"@balena/jellyfish-core","version":"1.4.26-renovate-non-major-71e713b2aceb33412d463a547de8d3fb263fd56d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.26-renovate-non-major-71e713b2aceb33412d463a547de8d3fb263fd56d","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"23820ace1be6ef9d6b6ab1bc3b5ababedb7e16c4","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.26-renovate-non-major-71e713b2aceb33412d463a547de8d3fb263fd56d.tgz","fileCount":97,"integrity":"sha512-8g+wVk+ciGWFwNHli8c0Wj6ZavLKzec2ZdOsCVsuvZSrRlQhN3coTIMslAryEw38dDclCrgtaNGVeSMtVYtDbw==","signatures":[{"sig":"MEUCIQDI8kABU4UvHhWlSWo2Qlrej5WfZn9pGk9z+pR5J40tEwIgJ5XdOYltFtc6ztTxnNlFGEM91esdrFnoziTrnRlWUvg=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":599226,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgJTr5CRA9TVsSAnZWagAAHY0QAIjc8qVLHQfjwMoytWS8\nNGHeR0xLCIkB+lp4dXeMGJ002hAyWE4I4iMV9K76pbZ8GrcacdZyVp0gygeY\nRMNamBpo20YmSwJRJ4vWEE5ZxwAfqFa9NWsGrsIFwBfum7NfiuaLQOKg9B6i\nrnKPp0dt5HNx1vUyaxtzHG5H0O00a/AxZdZAgJCoqOcnJOWTAdEzvjScMEeh\nVQaXZIoNijqQm9jlXDS+JDvGAFdsGBVjeWHiGcCyzmUN6New2XtC0rhrj8iQ\nnlVf215DlikNzD2aG8rcjd7+qbBpgvWYL0FLpyvB83vb6IWAT55Zq/kdIPrz\nbSsnmeyB3eUjubPWnUz5YGCwdES4ggsh6ZGBEu44C183kjjuDaT9YzFQh7io\n8uZF6kCrTZ0S2CVBLIKU67LPeP2TueZ3esWOfW7zs9g3qwov4T3qWTsjdBzk\nJgndPrAURBKF7CbaL5UMdpa5atWFUIvJ3Oon5dQqqzXU+ZSGBnNDdlbqVpkm\n6S9iTLa4lKm7F7wcbQ9r5CWhvLJ4LcsHlnAJQPVQ1rUhy10G4hWNFV+VOPb/\nytMigzBvmdqTb0JXU6nhIPLOq7hg8staqZy3MTAFpRDDMEEo8HSRZBjTQO3p\nVY5BCkbXVvbghdyR0wZP2gLkMqYVShhkXIZNOxY/9VG18DQvC2947rHu1F7U\nlVk2\r\n=abKt\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"71e713b2aceb33412d463a547de8d3fb263fd56d","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.61","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.57","@balena/jellyfish-logger":"^1.0.12","@balena/jellyfish-metrics":"^0.1.71","@balena/jellyfish-environment":"^2.4.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.100","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.26-renovate-non-major-71e713b2aceb33412d463a547de8d3fb263fd56d_1613052664975_0.5362104053144581","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.26":{"name":"@balena/jellyfish-core","version":"1.4.26","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.26","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"caf9dae1e4c8613da368787f9bf4aa78fc31274e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.26.tgz","fileCount":97,"integrity":"sha512-clKacuPRdRt9ekmj1OjpRn1BLXybvEsTizFTeeCJIQgXMJ9Zt4/IRKXW6G7DISvOpp6E3/HhdyO4cCnOAKIEPA==","signatures":[{"sig":"MEUCIQDThcx1ZtjI/TKFu3MILhIMK0z7kvZyPTpKPvXiSif63QIgRymLrFi7C84zj7FkRxNQTpD3lEJbc4as0YJ9OXFiJ64=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":599166,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgJTwgCRA9TVsSAnZWagAAZ3wP/Az2n88fQitnhCQMEVaI\nOiYEOYrkpLSPn1BEIVqUfzvKC5AsLAYv40tJlWna18tlIr6wepTLU5OCHKWx\nEBtfqcFttRkFAkvLAbvLzBkg8NnGUhpqjYHMPzSVRj8Kpqe73fdUY9L7S/oF\ns7S3NTCJ9hxq40Lf8Hz6gH9kl5Dg/UKssA/c4B/GbyvRlHstfXXHaIWu40kx\nX2DCMVe0om6+QlmpXtYZlhEC10q09qqwnzc0QM9w5TywfxMYe8HkbXoAY/N9\nX9OR7h7Fp55YjHOk3lof6nJe5cgxhw4k4gBWDMzVTuyB3Wuro99KyxcDAUjO\nCIGlsNqvSk+RI5INsRTAmJF9QQutvGuDG73BnrB1pC7Z7ObJH7pRfY2e2tgQ\ncQekhSzIiZx1DA2yJIEy2O71EmaQwmsxbRPBpC/H8Ey/azd+dXBKLfi5rU3m\n3ppX0rnVKMvwh5DY+gez8yJFpelqeCcJ6zuLu3MVis2R9PEeAP0csfGKUxmj\nMCrhsSqqHdrKpQVHZX2BB5QvF2I9H4nUv+qGnCmo2MNvCuY4ke79fb73WToU\nY2T0gsDuTP6zqDfUXOM5E3ZZb6SWRmnWd5t6WiHkgNoVFuAIj05PpHRgd4Pj\n1W/7PXAUEJ/dDDt7GGMDM2YsqhKXgkkS1j8DhkiQaO5pCK9U3MTobQPDV4+c\nBexJ\r\n=Llps\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"94be81a7d41a47167322bc3dfe908b4797ffddbd","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.61","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.57","@balena/jellyfish-logger":"^1.0.12","@balena/jellyfish-metrics":"^0.1.71","@balena/jellyfish-environment":"^2.4.7"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.100","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.26_1613052959998_0.9808600348441354","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.27-fts-index-concurrently-f74fed198e9fe973a2d13a1759f76a765626fe4d":{"name":"@balena/jellyfish-core","version":"1.4.27-fts-index-concurrently-f74fed198e9fe973a2d13a1759f76a765626fe4d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.27-fts-index-concurrently-f74fed198e9fe973a2d13a1759f76a765626fe4d","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0558028c6051f8ab7abb551e8de7a013fd870cb0","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.27-fts-index-concurrently-f74fed198e9fe973a2d13a1759f76a765626fe4d.tgz","fileCount":97,"integrity":"sha512-4tVmMRaemBhapIz6zCnD6cielVueKNrBJkTYbh+SBEXADnoI5cMGt7FFhNtK3PE9DaOElWmGIXGcbRHjKwLc0g==","signatures":[{"sig":"MEUCIGB7Y8YZCgk4uQ3sI0V+h5gsMeIGRE60YLoh2LFOAnCjAiEAlmiTolfLBU1XY+ayv4R7ICGnxYREnjZtKgkWhcPqM+w=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":599068,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgJfFoCRA9TVsSAnZWagAAFq8P/1U/R5sRSeTVcvZ/TxPR\n5xKHWp5tqrCGyQ1eS566egJ195q2tnPbVmyhhCyjsTtXJ0Qnu7Vezn9GyUaE\nA3k4vUSROYxBBRP3zwQa1SI68a3FpzobRi+75GsBEcOfxKP5LV5+sX+JvYB8\ngfoXu8p0qpDb6gum1/Gvx52czxfD2Sp3pgaMLPTuFO6Rnzh9knPoNP8106cH\nG2JngdUuZnKLJ6ilAedS9NlU8az7CfkGlmJ5SGLFJqnz1umpdVJCpcDbS6CX\ntsC/LSvn87CktNWEow2rojaCVCzdEOZAVaqMDYxhZYttjmUvpNqwaAAfqkbg\n2fWxh9zCW1FHmTsoDvY2BN9lVNM+8r5P3o/+gMSoRj2WMqh7vgIfJTsg/DYp\n08mdjOziwi75SfBjKF2n3fZLicC6LaR+RlHo6GZVwC8Tk/RBct2Sm9nV6dMu\njlGu4TemPaxyNHtIYnw49k0H0kcunCjVp0/hhsnvU3HDt7ebjXxRRaXSbgK8\npYYS6nYyh9Wpr3UYvXhcovnlqE68OvUfrN9dDxpjJa7IgrG8kiKltmLKCVFm\nMIKcf1zNqoTy30PxFY1Ia4UBjnNcxH/b10T7Oxc4B7ehRiXg+LQDmgabcfRP\naOxFoW9k0OP9OVACvEMnGzJibzS8pb+03eExmgg1ZtVmnBZ4zdpwKmvwtVqX\nGJsa\r\n=lUXL\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f74fed198e9fe973a2d13a1759f76a765626fe4d","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.61","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.57","@balena/jellyfish-logger":"^1.0.12","@balena/jellyfish-metrics":"^0.1.71","@balena/jellyfish-environment":"^2.4.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.100","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.27-fts-index-concurrently-f74fed198e9fe973a2d13a1759f76a765626fe4d_1613099367486_0.21674668012293496","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.27-fts-index-concurrently-8a245049a3f4e27221e06b8bd528b25422c38393":{"name":"@balena/jellyfish-core","version":"1.4.27-fts-index-concurrently-8a245049a3f4e27221e06b8bd528b25422c38393","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.27-fts-index-concurrently-8a245049a3f4e27221e06b8bd528b25422c38393","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c3fa8bf01a421b225c81f7f0b5faadbb330c72a1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.27-fts-index-concurrently-8a245049a3f4e27221e06b8bd528b25422c38393.tgz","fileCount":97,"integrity":"sha512-pKqHdAbqXzpkmv1w+F4PgPwacId65jXjursl2AJ2ZP9Gx96mqLZnP/U+3yDFIPX5DnHYYsq3i2iy6dqHURMP0Q==","signatures":[{"sig":"MEYCIQCFoMezXGqRqFcnRgE1/ypaLfv2DV1rD7Z6MApLTfF0LAIhAO3Z8i4zDpMeNw12PgXyhFYKufXBhwm02/27Sc3RxO+n","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":599063,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgJfZlCRA9TVsSAnZWagAAXUAP/2k77X2BE0NgRJREOVR1\nmOsrTKKA6yayJF8j8TZsMocHqomQosYj/88mwIOoKRNs3XVpO7tBwAmlaU4L\ngZHpotg16g4CZpQvZ+wgN8KWDLTArxxpWE7fiM8irfDUtPQXPQU3D5d35ktj\nzVBYylGGk83YJwzS2SGgKLTwQZ9c7N91ffK+SblZnNKGBkxR3rJ1qR2fl68A\nCc9oiQvJLdpgT49vifWYWawBxHajuzq43yKwAGz6zDaPbXRVEagFMpJ/BLZg\nFTXzZbmIK3KaKJuezCsCWXZyMJEZRJzfvz8u7lKjH75tAQlk6hDm3yAczdXk\n7THkQ5qkPNgO3XEGtWRtg8ORfFvDJgwNA/vSS2fiO5CHqXOviBdEaIhzpHnl\no63v8GE1FF0FedDOq3q9BSS/0vlMmRltZ1KGH/AHM1fb5U1+kPUX+BdzHu0P\nKF8LJ3BznAUUnWmT4qKqhxnK2U/OaQuyM8rCbSzL0QmxAZrmBVMOTYYJxczA\nZgo3nMZicvjbyvJpcP4qA1DGNRgP5/BoipGcP10o/vWFpzF20OZ4MN6eyYab\nuGVehQP0QydTqnoRZHigaA27rf7aqHRrorgG4G4KSf7MS4MondvMoA2x4RjK\n3s08ihXt8XP+79T1r7AhfZY8693T+ke2Vz1gD8210v2ech+TWmftIAS6O1BO\nNc3F\r\n=yKuQ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8a245049a3f4e27221e06b8bd528b25422c38393","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.61","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.57","@balena/jellyfish-logger":"^1.0.12","@balena/jellyfish-metrics":"^0.1.71","@balena/jellyfish-environment":"^2.4.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.100","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.27-fts-index-concurrently-8a245049a3f4e27221e06b8bd528b25422c38393_1613100644639_0.1400177835085994","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.27":{"name":"@balena/jellyfish-core","version":"1.4.27","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.27","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9c5c8dd5406584d4c24555b92da593a7b3b4a716","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.27.tgz","fileCount":97,"integrity":"sha512-R1bGDJOLzNHrcQr6XykpPGRICNSpkehtJRc7ONRM1A0BNLA0G4lgg9NPhD6xdXQ37EAH0RTlt5T6QqyTI6Tszg==","signatures":[{"sig":"MEUCIEca7H+zQ8Fn9O1vl9p/BiNUisbUtBO5tqnqlOtHNWuaAiEA+Muk9NLAmsk+YIcb/n1oFr5I8ehE1SDWMkOt0Yu7+Hw=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":598999,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgJfmLCRA9TVsSAnZWagAAuE0P/RL1QcI2lciXabEM2S4G\n0yup6aod9OEzzLwfLxt8i3WCkbTFV1G8e057A7yU5XTutmBdp67sYQT5Fdb4\nQw+MtwbR7EEHveA2tbWnaqX6n4ToMEmNPNLOeTszGv9KGJSmDbxR0IH6VHL4\nT3Pv0I3kf6izu2ySi6lqHxv16fXEfDIF0QUfOdaYTvFlKiZfaNT4q/+KUgKd\nzlOCZIjNrUZA5T2j92IyP8+og3BiNn/mXivZIU2hl83ugNtrI8+Dw8Wj9BT+\nW0P/6q4M8LVkJ+do81Zo66njwQDVwvekOImYJCQYLa5oxqxmpHvCWuoFfyXh\nRQGjmDPDNyM1bw9/aIxzr5BdSOeS3CfYAdHOp6B5NWu4slzVlNbObyPx/uju\nE6xWv4o/j+xH1wYoRv/Fk2MTY9XHio/O43JAG6BY2kfsHfCAwGswAhktevW+\nnZHqdxqKbpa+29akPwZgS8HHciFkwndqz9QYFiNFqG0jgLWyE7sowMVKNf8/\nJh25vbu6RWJPvvGczePRvoUAsDBHz8AcHhhR6SEQz0wjuwkryRT/fW23osCP\nG0ttKfrWmEVocLO+1OF7QJg81PYipIEj03z5jKXmCcIsa4DSmgx8ewiYaag7\n/BWUeFaYUQbUJu+TITBFuL9WXvMHrgOMJh2tFeZTnFt3RKiOQWpBoSQrkk/z\nCK14\r\n=WgCr\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a2ba58bad30d6b8b2eb5a105a87fb8c3f70303f1","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.61","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.57","@balena/jellyfish-logger":"^1.0.12","@balena/jellyfish-metrics":"^0.1.71","@balena/jellyfish-environment":"^2.4.7"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.100","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.27_1613101451221_0.5836140613557674","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.28-renovate-docker-non-major-a5c8de597d827d5c0179117993b041e5a8e7148a":{"name":"@balena/jellyfish-core","version":"1.4.28-renovate-docker-non-major-a5c8de597d827d5c0179117993b041e5a8e7148a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.28-renovate-docker-non-major-a5c8de597d827d5c0179117993b041e5a8e7148a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6980ab7321598c4354d1c432e4105abf8d2ede56","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.28-renovate-docker-non-major-a5c8de597d827d5c0179117993b041e5a8e7148a.tgz","fileCount":97,"integrity":"sha512-HExFiG8F12YlaQc7b7aEFZ8vYwl4tlJjy2DcY5QvUo1T3eu6kBsiel96xw1QD2DYXcE8GbqHiJAm+AVrc9llTw==","signatures":[{"sig":"MEUCIQCKeXODMj/WFsSchL1S1Iy5PNrhJ78iBCNnQz45Po1suwIgbjWoGLdcd8nwCUrLEpxzJlPvRjYOvXfgg3DB1Sm5X+k=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":599167,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgJoHUCRA9TVsSAnZWagAAWngP/RFi81oyXGvXlPXytQ72\nBz4u4YPFzCXVmcTsU4tvYTNqt2WBgUa2YjNNaBL15Cj7IRPKEik2Jzf28yED\nkgLT6QC9Z2XpVsF5j+0PDY7I++ftkjrPOHxxAyw/J7UNPNkWNVVGVUQn/FOG\nW92osW0j8sFTDOUVX57saN4QhtHqZ7wcS/DbCDxEGbF4kRcwX9rxR6RfPTHi\nWbEAo2nguZbWj4TirSe4Zl5GOrE2NNl59havTC4cAkinkVr2EVxL5Dc6CvFu\n6HFfQgitEaEsHtgiYXKx/jfdjeShW+m/mMRuW1W3B0HldEyn5LZhQQFovHpV\nFU7sVWujs0f9u5jzccAJ0n6AJTJ9nwxsjkkXERgUrJGf+1RGdmw3jZk33ywQ\nTLnecxBJKfuPNgbsxjyNMh5sXGnAygr/eov5s86QXy2tStjelvvqKSwW8EyD\nSZtlm9gi+3ApUPIqklvUHcMk9fuI6we4p0zpY3PFX6hwptlB434TbTUR5w0h\nq/Vt1Dz6iyyvMgyCMWYbfVD0resPQrgP1AWFOvswU+9W+mCpwcAHgIeDiysd\nOtdA15LuTqwXmxIAnbPRljhU4SZNGjoZ2XY5CefUEv1DR8aqwj1+dEgG3kc5\nD3DGNFTaaRn2NEYgLEiBLnpl02pEBQWg0yaxcTz4zkdvKNqwMJrBXGZzgqPl\nKcby\r\n=SP1h\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a5c8de597d827d5c0179117993b041e5a8e7148a","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.61","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.57","@balena/jellyfish-logger":"^1.0.12","@balena/jellyfish-metrics":"^0.1.71","@balena/jellyfish-environment":"^2.4.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.100","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.28-renovate-docker-non-major-a5c8de597d827d5c0179117993b041e5a8e7148a_1613136340125_0.3871907131046648","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.28":{"name":"@balena/jellyfish-core","version":"1.4.28","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.28","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"41ec810432d5a3dde4cb001b10c972eb2a6a9231","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.28.tgz","fileCount":97,"integrity":"sha512-dacjv2hKOESuV5KSARdQ1rzRoWIAUoNhI5yHtcLFgGpNpHQSurSp5YD5yuqt5BMHuYORpswV2kGZwE7Q3aLPAQ==","signatures":[{"sig":"MEYCIQCi+PZVN21MV45yvCzC+dJPp2m3YwEstWBsn4YydSm20gIhALzdWvK5sTXdi010goZc+KLwpzGaoz2pOu7YPubkwU2y","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":599100,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgJoLhCRA9TVsSAnZWagAA564P+QAeISP67YhmSBuuWYrB\nEFsdrII4+X8Tw2IdD6fs2nqhrHHyF9JCcYq5yxrTS73ixqebQcx6aposzsrT\nTDG5CsjJs4FDCqUN2UfHk+FyC2kUjDeH//M0yjfN2lhFky1ZamiFY/Vu/br+\ntIhqxa2A8Ja1aWTIDeewS9yLK0SH72SyDo4rP5mfi6qIDJ2G0YM/n+nQDTIC\nVXgA1OMyrGGPVZMBcTt7uYG/EY2b40S2yFuR0G8GDY4EFnz4YbfU5B2OO5rG\nf619KZ0VaKoM3LCQT4eat9wRIj8x9h0f35MK/aqMYUGarpgVwIRZHDmjwE+8\nAMnWUDmzYkZ337utobPLFdTAZy7YYN2cXTg6gafRVoDei3LJPajt3Tn3Y+67\nt4zcy8UoY/NcifyVKO/f3y+gyVLeFlGpiVhZt9ZwoSl3WoptpKXSJMCIB53/\nTME/kk+riWX/dyozhQnD8I9jU4OJ71HTH59OFJjfD8b1VcjI9rlysOpC4t4h\nMcrJAaf3lF/BwPu4ptQTgjK/zB13FxClzKhsSBI1KTsacV4/VsyhOthNNweb\nKR64/3Z41BMx0G6WStOtqF/PDP66FKnT1MuAvWPqkBzxYyQNrAj2tuXmjcqb\ncyNjad8Lvhqqm5H2DwY97/afkXEP6p78d3at+WSvSwq1fsmjrJZdLF16XwtN\nBiDO\r\n=rpD4\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"acf8b73f78d6e0ff68362f24f5767aeca512da78","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.61","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.57","@balena/jellyfish-logger":"^1.0.12","@balena/jellyfish-metrics":"^0.1.71","@balena/jellyfish-environment":"^2.4.7"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.19.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.100","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.28_1613136608817_0.8198056372571427","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.29-renovate-non-major-78f3691907c57bccac280284620abf0fc3059637":{"name":"@balena/jellyfish-core","version":"1.4.29-renovate-non-major-78f3691907c57bccac280284620abf0fc3059637","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.29-renovate-non-major-78f3691907c57bccac280284620abf0fc3059637","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4f309cec4185bcd78dd5e6d2787187eab1bf7b9a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.29-renovate-non-major-78f3691907c57bccac280284620abf0fc3059637.tgz","fileCount":97,"integrity":"sha512-1FrneAnAgtDSk6QIlh9n2aDof82R+09tM0OF5fHmWklheDZo9PQUeJWJjxtM1/2BPz8reXN9NlLDoGwGzQw/xg==","signatures":[{"sig":"MEUCIQCNPcGmOe/dSDxv+MaG7LMpBCN9GtWYeqic8k1RdHbBXQIgZ2TVWKriqTZF8cJq6UxC8fM8sej1N0TC6qPBuZh13no=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":599245,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgJwzfCRA9TVsSAnZWagAARFAP/2ZEvmDuUDZectQeomWo\nwBHAwHBPAZD7cJ0kR0k91y32776kjSoTKnizPn3eEeKvQkpA/Hg+tO35pfQl\nkxyJWpLgIFDWO/GLBs3OdC6bGPFlVRU6mBUecFc6yAfijTjyj9w0D45xWheF\n4tyxoKu/1pogGIQEOD2DvtVjKboUGUdpdwAlhyEOKvPO8kOBvzlWIkIpGqVP\n8VzoglgB35E/HT5ysdvRCY7TJ05v/xkTEaInJkzj6LJd+tswHOCKXJnsWWxc\nSMgh6l/rRT/N6/sz06/qNM9igU4jvWEfYLeFRp8Zk0K4Jc7P5ZyZezurjGbb\ndBBJfJLGE5pZCDe88zEdeodwdWSlMsajvMT5QpFObiSzKhHKu547MSK+cy1O\nbJSUxq+E1RtLM4U0L72OLG5KelKuM9AKov1NOigKxe6dFQEwa1rX5E/C0q2G\nSHW01oLQWIBgkmpbF0pi+RJTTMl8YJfsR68+yUxtDuBoLPabSd/ARFKGSUVS\n1WpDbMp1xYi4hVTz3WOLQ4CS/mOAnKu4L0i0z8+6X/Y0pHxeoF/8998P0Gl0\n3JATEjY52tpvmoPGbqaLmnZe7OMrapCE+LdNk33wwPYxK/8ERocq0ND5InTB\nnW9pxTOCdfBkwti+0XPvUoYvkfVMI5uLEMwQaT7A2nec16pAW8GYxO4OYC2f\nEt4x\r\n=os5G\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"78f3691907c57bccac280284620abf0fc3059637","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.61","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.57","@balena/jellyfish-logger":"^1.0.12","@balena/jellyfish-metrics":"^0.1.71","@balena/jellyfish-environment":"^2.4.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.100","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.29-renovate-non-major-78f3691907c57bccac280284620abf0fc3059637_1613171935419_0.4001769498788026","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.29-renovate-non-major-982c553fb64e385b26c2e09c59b9f194d57f4d4c":{"name":"@balena/jellyfish-core","version":"1.4.29-renovate-non-major-982c553fb64e385b26c2e09c59b9f194d57f4d4c","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.29-renovate-non-major-982c553fb64e385b26c2e09c59b9f194d57f4d4c","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"093fe24d0ce9c6ec1e6007f42d7a01c6c1088561","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.29-renovate-non-major-982c553fb64e385b26c2e09c59b9f194d57f4d4c.tgz","fileCount":97,"integrity":"sha512-SmpSLYQBz+GhkkZwDIdYJO3UK3QdcapGdJbKqLgAX/ek4jM0LzTwvejH0uZPsnT9BccpQ3xLIw4OFBz3APuHNQ==","signatures":[{"sig":"MEYCIQCFyjgXX0cINA7h8AM5VvoR2W2+PUldOZhj3lI92ZNOkwIhALp6GKUQXajNeDmibXArgdGZmtpVXjXgqncCyPMExCE7","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":599286,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgJ0sZCRA9TVsSAnZWagAAJbEP/RhIvVm4msj0wVEj5Tu4\nN+kT6lWC8D4l3GlqwglIIJ8xGgp6/uoKjR+GWHUB6iUJSQmyfg8JH5PstxaV\nT4SEapzLQ7+Y3Hjcm8FW1qxJmTLCpoTO5XnIQLEvjI11wR3uNVuBy3iQHceJ\n1kIqBmklVC9aESj9ROzU2GsfQwHF5SA2PwkVakdIcx6xtBW6COt/WXyRuSgV\nIGYbiUkPW2uIIQU9T0E7MLUYW4Gcjnkv2I5mVWF2pBMEzoLVEOb5jm+9T/XE\nbesQIAvBPUVaP0rWDOX9G2ORMaK/Z26Ip3Q8+4KEc0uhdAp3c6u6Aavuimgf\numouAGRlLNOaeZolCQ5S0awbIl/rWn7gjYEiItMu45iA5zDshUCQoSVCV7g9\nsEXZCL9ZLR+Ms7wjVqlG25RHQE13UkcmFuc0NGBV/kd3qAat+95ZVA9sGxdQ\nEZuHHD+XsfJ8HIiKuUTfwEayegPP+pn3p1z8dDtsn5OPJb0IMBw7P+PC9P7z\ntZe6/EFsRKdBigAhueMpdKM/AHUrhe+qKeJ7gZGg7J3q6bHruR7cOFwh7aev\nv+LBzdPOOINHtQP8Gbg5KOzHZnzriXfXSDfXd1AAQp58b3Ebge4s9c1Y19dq\nBGYL447P+W3IKs+Pyletkuaz3JY1Xrt/ZRnSeKLOSTJZaN+96r0s0BjSa0oX\nr/CK\r\n=LTeT\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"982c553fb64e385b26c2e09c59b9f194d57f4d4c","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.61","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.57","@balena/jellyfish-logger":"^1.0.13","@balena/jellyfish-metrics":"^0.1.72","@balena/jellyfish-environment":"^2.4.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.101","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.29-renovate-non-major-982c553fb64e385b26c2e09c59b9f194d57f4d4c_1613187865083_0.523606110404341","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.29":{"name":"@balena/jellyfish-core","version":"1.4.29","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.29","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1f23358e4eaa8f3f70354344f3dc99cc2d2223e4","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.29.tgz","fileCount":97,"integrity":"sha512-FbgkxILDaxY46qRUiV0i20vO32fNwqNOvXaLrbdA7LcHsCtGRUJ2pFKylBEsI0986tI/VQbQRiJjBV/dvzEqIQ==","signatures":[{"sig":"MEUCIQDd5OmfLCV5yF9MIS5FWJGNZ5P7KedN6chL8XqVRzdrKgIgBdiChdTLBaZj1+hwVbWK0EMFXIxdWw8p/Vl+ZZ/nvCM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":599226,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgJ0yWCRA9TVsSAnZWagAAo/kP/3bkLgXmxeT6Ix/5mD6T\n6/iPbrElGdubDlZvNG2SSeIRQUKNQ/p+lE2RcTHAczy8ETz9zO6iRSe51weZ\nPkMvouu9zEz9Bu3h4FtGIVOR2MTeYvMqkoWha5HNOkPCroPtfTC9ugAfMd1S\nGOdMM7M2+mvGb4N+XaChSgX3WAXDlQHwMuOdaWrMjxv2jJSWRMkYvhBfviQe\n2KsKPE+kbIV21GWAZUnsMpceZ6j+4k2ou13dtgBuzJ4CoBTlR/90anNNlsnO\nBSF93HdbwiB5beOdF50NVTxc3zj0QjGVg/dw7TbFl+mnaJGFMYSx/HOCpUdc\ne1uRN22s7jAwFjaqe3jmDhaapIhREd/QctfEVt5WftORkMyDWg1iSdQwBi5/\n9OAIpT6dW7MqSgSoVJw9uZmjl0vryByZLITOokq60nU/zULrJRqTknaYLSnh\nJVURqdnjAcJ6IcX5PLvkpcefxY+PsiKObw+3PzAFJ8MFyUEJ9buaGQNsx8lG\njUHYYnLkhXvQPHsd4o9h55PQu4HeDUtoWWWZOw/PVOt4pQH3mOGf0JAHhGbs\nhNOLs6zoG8asogyjBF0ovSWVGk/wXSvDqHgNWQZEWZ/aeI6WRP6ZY9uE1+fS\nK1uQPI0n8c+qaNSachYBO23eWhX0nJWziGv6ToV70nRnK+jxqT5Uayuo52oN\nPGFX\r\n=7IJq\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c507a1f3117d6ec10582157c9411e7c6dcb719cb","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.61","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.57","@balena/jellyfish-logger":"^1.0.13","@balena/jellyfish-metrics":"^0.1.72","@balena/jellyfish-environment":"^2.4.8"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.101","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.29_1613188245475_0.97593165509351","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.30-renovate-non-major-b42f3f94b1bebeccd290d21d958b6d516732bd95":{"name":"@balena/jellyfish-core","version":"1.4.30-renovate-non-major-b42f3f94b1bebeccd290d21d958b6d516732bd95","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.30-renovate-non-major-b42f3f94b1bebeccd290d21d958b6d516732bd95","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"57bfeaa016eb12dde20a875e11f4ac8d8c8c36a2","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.30-renovate-non-major-b42f3f94b1bebeccd290d21d958b6d516732bd95.tgz","fileCount":97,"integrity":"sha512-9UJHf7EvwpPRuCtoSKtD2oFiILFDblbXs6o23OoG03/meVToyLjZeMtR2UQOUwL30JMjtMI848CVir19WAKAQQ==","signatures":[{"sig":"MEQCIAH4NK6ofQiW0b9MIlzMpbF/A6IW57B24aXZUiMucnwbAiBdHQ+VwIAFJisLq6uTpUTzITKuY9gi31VorkaVJaMliA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":599352,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgJ19/CRA9TVsSAnZWagAACbQQAIlWFt1K6jW/IF5qkWGR\nTk95OB5xgLoqKh+rfP9XlHBwOwkuR07r2syQTqvU68WhZHTi6tXvwg2W6lV2\nqgo97LF/wB6DUtBYmgnXLH4VdtAXbbXBA0B6RexqCxE5hT7BzrqjNh7/QHzv\nndSjeTUIa1GaHw5yZO/KPPVC0h6XibNm6u+Uu3lhoWbtmdn95rrzN7VY3ms8\nNlgbskhNvVetOOmt/UjYwDH4dq8dB9z6o5X7PVTx8qdrO4qNn5KDC6lhJ6N2\nSeIhfa6ZVtbvXpzN9Nvlqa2CD0GQD7vDU8K6d0T7+eWcFHC0Rp3eC6HALGDD\n6VfpZtsWhef7re7NouFDsFa0fG4UYZ+bAwdCe1N9CjTUorC3v72xZRo6DPlQ\nn+aWlvONEm6j44CAr4ViH5sccg/qsIDrIIiYfKY0eAh747l+ufGOAOIqG94C\n28MnQ4EDNnXpOPVWp19cFjgtsCeXqKgUWEJ39KiOiAP4o0iXRMkJj+0es7ZX\ne/OIJiZdGJiyy68SgQYyN6vgOYlKzqtIAzhWHgKNKddZ/rbMjmrUtE8DMNo2\n4TNJRRdooAeqcX1vRlktPO8rBqYOC7Eqe0Wpb3R4M7UhpmzzEY82NFbfhf6/\nbnFl6KxSSt9jRLWLqXAn5rLqxi8jaRtzjEpf2iBkRdR03U/jwnwTZ5ERGoBF\nMfum\r\n=44cx\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b42f3f94b1bebeccd290d21d958b6d516732bd95","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.61","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.57","@balena/jellyfish-logger":"^1.0.14","@balena/jellyfish-metrics":"^0.1.73","@balena/jellyfish-environment":"^2.4.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.101","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.30-renovate-non-major-b42f3f94b1bebeccd290d21d958b6d516732bd95_1613193087302_0.515822921235809","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.30":{"name":"@balena/jellyfish-core","version":"1.4.30","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.30","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"cd5de7497842daec8100cabecec61c60f6dbf1d7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.30.tgz","fileCount":97,"integrity":"sha512-cj85Jm0MRVTU5zhDB/pAjKNaZ9RT9R71upfsEEB12mhcohAiaAa1t/4w+chkxyj2ZbtKTQPucpF7UIAQ13cr5Q==","signatures":[{"sig":"MEUCIH4dAvHb3bRf92ZC+Yt8RglA28XBLziUiMG2rx4tjgV+AiEAqVN5AQeQhKzkN66FpXLG1VR7Zl22+rbYgBMb8Y7rZoI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":599292,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgJ2C8CRA9TVsSAnZWagAARtEP/3q2wEMYKw8cvArxVouQ\nN9puc7tQ173NwPtX5wBUpwJyPJKnmeXwo93NlqJOQfVDWc02poVaRY/ncbuB\nFbxH8IGFzKdnGGrOhksPREV+XBAPclUMSlpOIxDsmnlnGtodAnemzKKqGazy\nvpNOfUOv0MG6rfToFypanIMM2zxx6pY8qkY+figi7s4fjNYPPO5itvQvyAK/\nwe7MknUBjJa6IHCeaKKkrSb29aV8XamAvGuKirWi4uYRKfwj58lpuyoz35e/\nKZgt05tH8pp1P00LndjnuQRtdlHwZk5VQCP9BEBJQyISK47TxHriFPmVXCVm\nGb8dPvozfDgh+AEMh2P25VJdNwPZa2ftQMBcohEj6FWwdLExNnbvgem3MoN4\nNinvMYg5hMa7w1Vqpc6Cv2Ovo0sedkbEymdZY0Qq7507+U2G6kSdzzFM+pGn\nPFIqsbpuv0KD/MTZT9YOLy8G4K1AaMAJddPvW2tEW8SXpsjeno+iX1J5Ldxw\ndxZlrsCiG6MdbnV8uZfO1OBI51mkdAF6zkFcwnt/tcpV/ENUolYOe8cbQDV2\nRMXdD+XMvEzQ028d/dnXGnRFAw8OS3KYD0Z01quhYJsl6mbnfUtWjzkC0nr2\nx2D0B9D6+LdG2Ljn80envvk1rcClJodyZezvRA+2wR3C88FHjqeqen+yPa99\nodYL\r\n=O8kW\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6f0ed8883d4b19502bf8cc86c692ac79fb7656f8","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.61","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.57","@balena/jellyfish-logger":"^1.0.14","@balena/jellyfish-metrics":"^0.1.73","@balena/jellyfish-environment":"^2.4.8"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.101","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.30_1613193403069_0.1368413459181721","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.0.0-replace-date-columns-426b5d8f9c0df4cb7d8722bd089e3e18d3017498":{"name":"@balena/jellyfish-core","version":"2.0.0-replace-date-columns-426b5d8f9c0df4cb7d8722bd089e3e18d3017498","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.0.0-replace-date-columns-426b5d8f9c0df4cb7d8722bd089e3e18d3017498","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"91020a12d89028d33706da23e2e31d7a134460f4","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.0.0-replace-date-columns-426b5d8f9c0df4cb7d8722bd089e3e18d3017498.tgz","fileCount":97,"integrity":"sha512-CSvQ0LcpE7OiNlHEIW+SQ6IRyMzg7s1w3MJN/mJytZ98oNhJ6NbwC3pf6suH+0lDkpXTpydFjSZWidqbVkw1cg==","signatures":[{"sig":"MEQCIGiSA+EhObjRi/QHLb8TNbpGd2d2nRjgCOmwxFRuLLlJAiAImjZ2wCfCA+Y4l0+SWzhNl7CWh62xzxST/G+Wj2HRhw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":599334,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgJ2HtCRA9TVsSAnZWagAA8LAQAIRs1qr7PbYF7R9Rx/Sk\nJwzSRL+n/ceVaQrMn8XHK6RLm2xZ57pT4Ux3pewKKn2RO0sEStEBQxd6FWbL\nRSz7JloRgLKzJMtctVbWTrR5n/sZIlr89U/ybS8tGsy2qEr7C2cV75IipeK1\nyefB4pEq11VYG4Vx2Rucpbksbo2nMI/etd5Y1/7yM5VatT24hgfp9tOvGwtV\na4NkmsCupdQcEc6LJGYPGs93K/6Xi9myjzwfiIVngkQUmdi4X/s1X2nwB2f0\niw724zF60llGtPpY3SUXxQvA9Nf8jMMLtN2UxrYRbnaKgXI1Fimz03nhgJHH\nq6pd45YJaA9tjAjUEtVdKgMtQ7rqnAuMryVXpsYs9W/8mloWPjtd43/C1pme\nRDaLDCHn8KDNeU+NvR44vPY5TPybHUOFGuLV0Ls5AM1PBDpTnl6g9DJRzmI4\nyKxKh2mEZNBQKMwtGvP7NYT0OYR5TkFyD/aGIhpSdblHYlqYnmDYESqo9aEV\n99Y5jQO3+6NWPCHU7UkpCU1ptPl9y6nQiM5V1RdBGw2yHq1sQMj2H2pZ+Dxc\nIROyb8ugw1gdlPjiBCIQpC8ei902ExNbAWq0ekC8XqsZAOCsFm8AzYVXwqQe\ntPFOpa97BbasQ1fad7vXHTnkvwxDvbkaUEltZUds3SmWaOFWlAHnfwZ/1uMP\n1RM7\r\n=K/zs\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"426b5d8f9c0df4cb7d8722bd089e3e18d3017498","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.61","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.57","@balena/jellyfish-logger":"^1.0.13","@balena/jellyfish-metrics":"^0.1.72","@balena/jellyfish-environment":"^2.4.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.101","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.0.0-replace-date-columns-426b5d8f9c0df4cb7d8722bd089e3e18d3017498_1613193708521_0.9810347703731297","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.31-renovate-non-major-0bb73f9a98726f4ac1a362c74a7f94ffa6194162":{"name":"@balena/jellyfish-core","version":"1.4.31-renovate-non-major-0bb73f9a98726f4ac1a362c74a7f94ffa6194162","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.31-renovate-non-major-0bb73f9a98726f4ac1a362c74a7f94ffa6194162","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5cdd2170189da4c3a3b54de8be315d8ad40a8b71","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.31-renovate-non-major-0bb73f9a98726f4ac1a362c74a7f94ffa6194162.tgz","fileCount":97,"integrity":"sha512-mIm225s6F9xd5yRGWIRxnYUEbtB7ibFKHbpfEb6LiTfKY4xHqkS49QE1cOgJJ5gSwd92tXagUhlFYSWNpAdhCg==","signatures":[{"sig":"MEYCIQCgVQB6m8j7oSorIThEIV/DpkUenrQGKZgkv3rVYE/m+QIhALXm92APKM3ttzXg45GYoN9Sz5Dnswpju6Qy35hbAa8f","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":599456,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgJ3lWCRA9TVsSAnZWagAAYrMP/2aepqDgojvHKVWX+OMe\n4PxdEbFyzw4e7CzjP5OPS4lyFdz/0leYGfKTbX4okBT7ENAvbeKnyjuS0Vaw\nabyGs/lzxLY9XI6mON2UwFJkqwSVkBgqq8BEOKOPg3j4OwsCveqDAykNRfSp\nxYNdsKtDip5qvJD+nL5hIInPTkobqe8oHWj4XnJEj004bjD2sT9J7/Ia15y8\nIsMon/uA6s66ZlpjT+a95SlAxWTxiqDfCmFMX1KoLMWFcITse2sm4EXrNZ65\nZxvDMAWMRE5rWLx2LFwgFiq+AqKT7A1ygIfiv0soLQ1DFKvXcAs5+7rDPhpN\nIdRVYTsrVaEJN0J5AYOYvBtnXW5KX/Ch4DsG57AR4s2d1vuHfP/Crlf6NImX\nnKACxYdbucXy58zB6DNeX7vEm/8cBGe/3h08n3Ytn6qNawegl8RsZIVow0tA\nY5rNErpckoJjCJtcgqzlNI+eK3yp1yliwl574wo+gjqv+na3tU9j/P/K8hci\n+XUuuyKPjPBaMNKAGnkzqQCZYm8IRlposIOzH6076dmPnr6UhTUTLuXLzQ6A\nGB0MexBIHpgNol/R/hA9kbMuKrIk18cort4tUBQAkyRxoGCRwgtAj8LFcbEb\ngOFr8f+tIOWvnswNVW1FI7fs0TQWwRPmitJlpg6Wp5Yw+sFbqsvGtQiqrVU1\nzE4D\r\n=NYDl\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0bb73f9a98726f4ac1a362c74a7f94ffa6194162","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.61","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.57","@balena/jellyfish-logger":"^1.0.14","@balena/jellyfish-metrics":"^0.1.74","@balena/jellyfish-environment":"^2.4.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.101","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.31-renovate-non-major-0bb73f9a98726f4ac1a362c74a7f94ffa6194162_1613199702297_0.5942730853313898","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.31":{"name":"@balena/jellyfish-core","version":"1.4.31","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.31","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"154174bb9c8a03dafdf8c53876646bc3d1318fb4","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.31.tgz","fileCount":97,"integrity":"sha512-HTvapP2bGOGuomc0kr6na6vTOcbINsJyWcvhFYi1XX8e7b1/UQpANt/a2JKaS30h5WF/TJ9AuzjDIzGwgkh0cQ==","signatures":[{"sig":"MEYCIQCRwBlJafp9xoAGvOl4fApFYQqRo1LpyeuChb1ONwy4sQIhAMY8ryjn8MFjLoTPPAphnFrYFTXeoZOYVyumEFicpnNl","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":599396,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgJ3qyCRA9TVsSAnZWagAATw4P/3yuLudczrRLM1XuR6UR\nmoskPS1wXCWtFzWh2N7ZewxOPqn/KzbmWa6c7VXlktQemDpS3SfaEYln/KY7\nsBZhzAcnYKNxJWqhHlIsSB+wOiYgIlnWZ39Okaa9AwdFjUZ3Tj9s0NPur+cT\n8/KeGu3XGoIajXDv6llbxHiwe+mutELuZWUL0sD5Gb/7swkghf1l85ARdqw6\niMxrKDrtV+dsjOxwJHdnH+afWlaD45T2tKx+nykynGcBmY3HdX0trWr1pU/X\njUESfXGBh2iYLGvionLTbOV8Sawu3rteByPT6W/Kw09/xQiBn54PRgymSFzg\n8QfCjeu4VfXMoxf7GKK8ncaZccXmzB8Bwmv/7iZIhSkCeWOMGCltgug/36V2\nVYfnxyI0YuKpLV4pM15DP1qYgKg34U+ctpYFPhNSQNa/luvrZHiWIdWJVNqV\nY8eShlCI2DHITBN3widNf8KCiuBLRl4VdPPlDuKt/7i3TCQN+BXXByGRA51J\nyA0nU7A8m0guR0O/I39JoD87ccOHjsT15C1jcRUri4aPtNEjIfM/1NlTcIEw\nbNGd/fF1JfmwcYr0bm7kP/1sdxz8gT3pdwcWsyZH1lICNmTLOoI2/m4GylRB\nSlmB6WMNsgNDzJtM4gJMVv5rXci86S5hXHw3QnzkppzuObGexI/LOcCwjDJd\nnZby\r\n=LLm9\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b13f12b40bc7287accb9bfa984a6d3ecd094e89e","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.61","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.57","@balena/jellyfish-logger":"^1.0.14","@balena/jellyfish-metrics":"^0.1.74","@balena/jellyfish-environment":"^2.4.8"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.101","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.31_1613200049558_0.9991961135571805","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.0.0-replace-date-columns-2d9f185741e075b5e48608630750798e27286262":{"name":"@balena/jellyfish-core","version":"2.0.0-replace-date-columns-2d9f185741e075b5e48608630750798e27286262","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.0.0-replace-date-columns-2d9f185741e075b5e48608630750798e27286262","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d6e13c89461af9a5b5686b9908b3e36ada803178","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.0.0-replace-date-columns-2d9f185741e075b5e48608630750798e27286262.tgz","fileCount":97,"integrity":"sha512-xKalvfTPTyPDMzNIO5+KzDEfCXyZe1knraCC2i2oy5wBAL6VQ8Qmx6ivFuNi3EmbcR8DTajLAr6DXUJZW5rQRg==","signatures":[{"sig":"MEYCIQD6q2GTYXb2klgDu10tbE+Lp9fgy9BE9JXbTlqnKyWiLQIhANO+F8GULkSXRcB8eStJd1SK/Iba8+gd9X0YoTQTA4eT","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":599664,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgKfciCRA9TVsSAnZWagAARUIQAI0lRlYDBtlsiK0wFcab\n/uTgmdneqkzvXg0klNCje5mduXfadfn5u1ZJbyrY4aiKXEtt6Z2i1HBcvIPA\nO+8PIbmrLnU2Fs1aMgWhYIFYDf/8JBGtzy5QNqRln9Lrn9rBF430gJlGmuMI\n3Zaaw2vfIEM737Cs1PfSf80Nfd9sU9mA6PY6ntNCpOyZLcsSsHH0mx2mn7fs\nd/GTeMdn9Qt51AKa8cS+hOhd/7NEIpYOIQe0Z5FyN42rquf2ue8GG614NkBq\nvQFj+Db0BVG6EV07yc2BNQeN1gxaeEPdxwZ+On2VvrPtry/35divZKd7wzqW\nyAw1tJq/oOoC4EdrqF/RTa79P1+6zZhfOnunOYU0pvo1ystgUuopx/6UefqE\n+M/6OB3D+8O8gBdxqQ28EoJ8xZkLih4H74H5dVpQlbsfv2LSDbFV+RKt0BWJ\nsI8l6c5PEE8c9EwxRGevtm8W4CjS9NBTEDpqfEQMO3mrnqz2lKyoeCaESroI\n51F8i5+9cyw8A0t8Srxk+rt9QEk8kFgTeOJZfZUyMKqQP2IkyM5PjdhQPEfz\nOoH1t7xWVXV6QqOUjYl3vc3OHvQPHo8SJzFGehW3+pmHjXfacNb12yPfjI4e\nMptkoC2GDAN/8zh3AoO5cFTtgsys/zgg/pLbdO8gYYAMCCNHIaN4OCqTXf18\n6Cxu\r\n=tZep\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2d9f185741e075b5e48608630750798e27286262","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.61","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.57","@balena/jellyfish-logger":"^1.0.13","@balena/jellyfish-metrics":"^0.1.72","@balena/jellyfish-environment":"^2.4.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.101","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.0.0-replace-date-columns-2d9f185741e075b5e48608630750798e27286262_1613362977987_0.022167098173803357","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.0.0-replace-date-columns-44379a3f4592b6cf2687e97e1e46986d29e8723d":{"name":"@balena/jellyfish-core","version":"2.0.0-replace-date-columns-44379a3f4592b6cf2687e97e1e46986d29e8723d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.0.0-replace-date-columns-44379a3f4592b6cf2687e97e1e46986d29e8723d","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"373358d7744e1f2c17b61f3305076c5dac48df5c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.0.0-replace-date-columns-44379a3f4592b6cf2687e97e1e46986d29e8723d.tgz","fileCount":97,"integrity":"sha512-jRzFiDpNYSuqUYLB7H0YPk8k6lnuf+3VYFWL/FP+7JwOoJtgsusKr2rLUlRY8Nlv+snDUr9eEr77BKpeJPvtvA==","signatures":[{"sig":"MEUCID3g6XuWRaP3dfcPLq59npDLauh1MEk6/hOexb2SFWRVAiEAqq8Z62jQK3oW57/TutLkh0dvRX0Y387cqM0ez3UGH5s=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":599694,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgKfrCCRA9TVsSAnZWagAAxa4P/1ACefqvJtn47oxL2ceC\n+iQnuP76qvRm5Fp+H8F6qu1gtBNp0KxPIh0fB3a2HIyiI2CQkVa2ZNRZQ8ir\nTJeMiO91xeFBBf2DWr7nWO8UhCUP0Xqz0nsO44s1j2/LrGuHyBPKiEjAEabo\n0I/F1rLCHTTwfaw1nmGm9E5J7JwR/aZ0so9os2suRzBmMbaWd+AGe+6YW1wj\nkmtBRu1y4AecsPnjkyl6+QaXtjXbkbrQ6PjWjo5+ZxbGPOu5sDn988XG0i+p\nDddPwx0Bm3eza2PTxXdbuiFDqW58jHmQbhavrDzgO47r0DxCVa4pr6YeyWFf\nN5qA2K3xv85oEX5znqcdlM7jQFKfQSqY8V7+1hr61HAt+XsRHVXpxRd6DeOr\npX0Xtv5iNsRfSdF8TMZbnNRAqNMd88FEmG2YnYjkggnH9a5z5Uaj70c1EF40\ndJEBnlYOAwTeQFsnC6QxjA9ksxfct1O/J81AiiLH82X3tCIiP6lIm6qnEo/d\nrwSjbrHMAzYQbtC3VfEeSTM/qvKlH66vunvThyP5mk6ybxOISHQiHEK77065\ngeQ/w2J50TzCjhaSFoC8c/LxWgq1yWl8ryjk7zNIUP1SEbODvwKid+hRDekv\nHr8sXl0Y+BdU83csi5Zb7OT8LskCs8/oZNAjggWiIinLHEE3mwuhAiNL1/o4\ncTkT\r\n=1ZkC\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"44379a3f4592b6cf2687e97e1e46986d29e8723d","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.61","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.57","@balena/jellyfish-logger":"^1.0.13","@balena/jellyfish-metrics":"^0.1.72","@balena/jellyfish-environment":"^2.4.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.101","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.0.0-replace-date-columns-44379a3f4592b6cf2687e97e1e46986d29e8723d_1613363905531_0.9361584158893119","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.32-renovate-eslint-plugin-jsdoc-32-x-1c430f0dc3ace78fa6df312087ed19591336bdeb":{"name":"@balena/jellyfish-core","version":"1.4.32-renovate-eslint-plugin-jsdoc-32-x-1c430f0dc3ace78fa6df312087ed19591336bdeb","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.32-renovate-eslint-plugin-jsdoc-32-x-1c430f0dc3ace78fa6df312087ed19591336bdeb","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"bcfdcd85ccd0383774b50827bc9f600ee8aca51f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.32-renovate-eslint-plugin-jsdoc-32-x-1c430f0dc3ace78fa6df312087ed19591336bdeb.tgz","fileCount":97,"integrity":"sha512-aju1BH/22UxefFWg/y+w66A6gxzyUmg6aIpC0Cjng305Nd0pZ6FkFJkUw52vjPcGFqzbW6KKW4Hrt+Q2uTdWGg==","signatures":[{"sig":"MEUCID5fXEIz4KAGJo6S0Q8YoZBA/17lx8Unq7mfD8LDP1pSAiEAmMEvUypj/QmnOB6cMCOhTWx2p3tooV/4MDSnX2GsV64=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":599565,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgKfwiCRA9TVsSAnZWagAAg1EP/3toCNalRU4J+MdX+35l\nxgGrG+C2s0m7sBIFFbOaKc685yqsTlqqvpCy6zjyHoC/OWvGpeYI4rCFL0+k\n3V9fZ/zTEZcgZK0xgwW5jXzX5W0BcqFhqIXOCToeQSFV9U0Tm5OEifmvpP4z\n4d4t3WG13TzyUOI+lJbobB7+ViCpafAljKSW6fiFyrXLSYlfmGDvcvHBXHVM\nsAzzqG7yfnJbP/za+35Sjj1dMVD/AwWMEHkrRzDUpQAt+a7lSUSUzYFqMgYp\nx+MdvHWLfZH+gYdxCuEKad2mPzPmh3cRzfnS+Y43i/cLVbFCL+CW1XTrt1jh\nyL2y5hQVtYOnIB9fnf/OyfnRrwxBWugfaS3avRlUTrcZEjLdvN8qMR4/7QJd\nT/HcPlSeYiJOFCXDzdSUiAIex1WERlqgRBMzVV9f/jFGCcimTYTornb7p2Uq\nyVnj4BjmndiOVQ1GpSbPWxaR8rJNl/F+vlxTdhj2Xf3ZHyN3GRVEAhOtD7Qt\nCCZHUUW2JrBWBb5B2N3OVywF3EBDqvMBokNNbwu6Gf+fm70EbYiFqEK2oA0o\n+r1gzvFIL0/f0vBqIga3g3K7uN8CVfD9ezNYidd35ab766J3cz7qbLqBjg7y\n2zACh2hewrbJcCTcoJKMG+6FN1bINn6fc7o7/m0KFmKHxFDOI3ushnQ1/DPH\ny4KM\r\n=MxN5\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"1c430f0dc3ace78fa6df312087ed19591336bdeb","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.61","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.57","@balena/jellyfish-logger":"^1.0.14","@balena/jellyfish-metrics":"^0.1.74","@balena/jellyfish-environment":"^2.4.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.101","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.32-renovate-eslint-plugin-jsdoc-32-x-1c430f0dc3ace78fa6df312087ed19591336bdeb_1613364257620_0.3416284651008339","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.0.0-replace-date-columns-d800884904c486e33d2c2fc2e2f4017ab3e1cd8e":{"name":"@balena/jellyfish-core","version":"2.0.0-replace-date-columns-d800884904c486e33d2c2fc2e2f4017ab3e1cd8e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.0.0-replace-date-columns-d800884904c486e33d2c2fc2e2f4017ab3e1cd8e","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"24b6cdceb2524091175a709f67c9c6a79e83d2ff","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.0.0-replace-date-columns-d800884904c486e33d2c2fc2e2f4017ab3e1cd8e.tgz","fileCount":97,"integrity":"sha512-s/y4/FfK/nkBcTsLlx5yw1BU8uo+GIUzfPI3EoLgjL673ah2RuA9CfdVd23ALkyoZTYU6EiFlZxYS2bYhIEKzQ==","signatures":[{"sig":"MEUCIQD2U9CGDIbmTUZzGviK3/nhE3+Ep6t7IvXlTkfJ8s7R7AIgbUX2qhyjwegGXVt1C2yMcnW2YZSsOBbjSu5V9k5ZNCc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":599559,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgKgmeCRA9TVsSAnZWagAACO8P/jEFa+TA7iGWQ87Zou1p\n6M9wp7A/UhNztwUUDquWR3B2+mzaDNwex+ULRF9XBmgt3QSd9PITAPFhJw+4\n2AOldjcE9oq5QvlcuB7IYztTE9qjc1K7caF95Fva5ehkvB0mOkRPpVBfg7jE\nQsb4LLWL0NDob9gAt1616cwEGIC9miGzjkG2NUMvt8YO14BkM4gKqSdXnsOq\npu4elfUb+DAaRwNuJHjpgTUoTx+fRvE28dDj3NA335ArJBaX97gYII67h+bc\nV4b6+dVnIPzSLo0OEZr1Ppu5SZ7c0ZpdnIbgkoZfYXQN9L3F6w23MlKjnxmW\ntwqzVbVwPbzTdu9swmXCmavTjoW9oXwSP1+Sxr5t3/eX9BTOhN1EYVfUYdid\ntuTUn+ISmQXucoACAy7wHUr2DXmLsVcW2aMYb/3ZF+f4N9VQAd1gTsEOpJw0\nImx33olJIZg5oeaXt9i16kjcaKkkcTpta/KZv1716NrGBGUXNSteaVGXY8/k\nvZRlXJRHBAOab/MYMAr9h77punmHoqqLgbzVnQjfx0OTfvBvzytthgxeBw12\nH0ZKuYe4DMt6k8/PMsq4ibG+r4KYS8E9kaylfNcCddjWPwFLiuSUDyZ+nSRS\ndJmGS/z0o+4s+SFShucAMjxmw6RsRbL1HFboKYw1ZyLJW8t9ZTNQYsbWdVmf\nvRJU\r\n=YYKx\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d800884904c486e33d2c2fc2e2f4017ab3e1cd8e","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.61","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.57","@balena/jellyfish-logger":"^1.0.14","@balena/jellyfish-metrics":"^0.1.74","@balena/jellyfish-environment":"^2.4.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.6.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.101","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.0.0-replace-date-columns-d800884904c486e33d2c2fc2e2f4017ab3e1cd8e_1613367710238_0.3919553281503543","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.32":{"name":"@balena/jellyfish-core","version":"1.4.32","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.32","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e9e0a4d3c949384e79fa566f76c361ff4a256aa3","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.32.tgz","fileCount":97,"integrity":"sha512-NGSzkZ8FNUmnnB8l6D/ZW2Kj529g5XwQkiFiTVsFCs7ZyYKrEnhygHQCzfS8IjWoFebzYDN1BlmzIe0nVzwVrg==","signatures":[{"sig":"MEQCIHXhplVj1it7JUTmewj9bd53Js5uJ8BJ29QlmdMkYJISAiALju6a2BAkbifruhMcad+dHw/n30HPTcTpddYNIEz0Kw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":599490,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgKgqJCRA9TVsSAnZWagAAd9gQAJ+iDrvYsE3dWGNlDtWf\n1xnmsSfJgdtUNxGpIW/iwJnvs1JrpIYSQXayTVA/XeB5ch3r0fgjnitZgapS\n7gnQ1NDakjHqSOn5DcO34vnQa9vQPwFBe/NAfecckQvEcX8PLwV71/HgCniz\nnEsc1mDMS6Yvj2sUvE81yZSM1REMoO0LOnH8qP3PFQ43XlCThvntiYJyBFac\nXfEjlTYG5HGeemewIanqyJCeeoyZXSKNB2oitFxiaGvJbMNTL7KQubm5h6k1\n+ViP3xmr0kIl0+++einO+a4chdriXLA1ftKl0rWmg9uh0hK885B3EVH9Ugt/\nfuijFL1BYeMc4murIx63iJhosTmC5XWgll4o/soKKXtL2cCD8OUZI+l3G2hq\n2eBQJZajo98F1tavbnoAdsnGZslHVKmVgv/Rt88A345V6hCx6MogFq/2DNk4\njabCCV3DxbJmL/z6nJ1f6sQR1KMyNGB3uTuSErL5vf42Xgd4/QeJntcjpbsv\nnNB0trpFiGb/gVxU8/zJqVdw8At6PAgv04za7m2EW3pNLvQu2XTtsQfSIP5u\nBPRjCi+RP39iI7rfu6GvZHxaT6EjEkUoYxCX+UPFz8DyuHsv+AuoUQ4uTA7l\nPlPTJzjDJtP/UifPX0rk5LRSS1JJ3voaW7jHIy+7GzjQZ7IrvxMtu5AEVRUa\nQYzs\r\n=LGle\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b705ce819814aabadd0ed86a54fb3f20a5036a6b","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.61","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.57","@balena/jellyfish-logger":"^1.0.14","@balena/jellyfish-metrics":"^0.1.74","@balena/jellyfish-environment":"^2.4.8"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.101","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.32_1613367944410_0.2113359847456191","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.32-renovate-non-major-a196ea7fca733f14943edfec3bc818c543d9364a":{"name":"@balena/jellyfish-core","version":"1.4.32-renovate-non-major-a196ea7fca733f14943edfec3bc818c543d9364a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.32-renovate-non-major-a196ea7fca733f14943edfec3bc818c543d9364a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"536668fbe10c12d1539ebaa2b218aa562560ea35","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.32-renovate-non-major-a196ea7fca733f14943edfec3bc818c543d9364a.tgz","fileCount":97,"integrity":"sha512-7IuCdycAJqdphaOVCw9hCy3S17Etf0t1x82WCy4OqwfEL+IglfHdZedHwXlCqID0ajWZwaSkLwMHRLciptg2Fg==","signatures":[{"sig":"MEUCIQDMXhLhLK8Qv95vXN/TYT33+OyPJc/u9W4BWrt87iWtCAIgdp0ltyQr3vmJRybh/bsjMfvs/0osmAoyRkfc81ZyozQ=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":599591,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgKgtNCRA9TVsSAnZWagAAxT8P/0yIfeRcTgGizR+MpF6I\nRCZoar2rP5MN1c3Q9yvbMQczm7BoWkZSkUh0iy3OnBMD0pD42ARwo/Ffa8m/\nwo3oFwaBbNAE3sbc4HusLpXJ41ITj68LlllzO2nmk9wdZsw2jNMghEBQ2Tyc\nG7rPSe5ZDxHAaTh+ioqDR+yyoTrZ8uNkAxUFLKRgrqzmSPYIIbXv+E7JkNji\n8gBlM7rnnH6/8lieiRLDntB61CyQy3FQvnmRO6HgUK7Zp/mmxYWIqiT70sZm\nAjrDNJTm5VJqwTPcBWXEH6NCqYFL25x+h82v0qLGzh7ypla/U3lX94/j+Ix8\n2Ova4RWZAUvVEhzRYC0/ffTX5bnRUyfb7n6VLIsQNfWnDu+JqC2v4sUIPONi\nhdSGQbBTiwBZ6TxWRswmMQg6atCI1IKDOsqkc0/pNkU6DarREscBigZndx4q\nByvR/pF5S2jFrJgoRMjLRVV0jCaSkvw47KICjI3eRZtPGpUzYQqpiJdE0xQZ\nhnLUEhN4GEB1e1uWIX1kABzMy4KklmU3TwLc1f2P2tvwaxY0ZpNvYuDqFtMi\n07Kjx9mCyNILBgCTYl12DcZpMziqvYJjlr0dA99PiUNTry8cee3zXebOUkGi\n0p+4IReblDs5XkNyJFE9I16X81LbcMsxFP8aHt1OTZss7SiC2jz7kqgqs0Fl\nxJiZ\r\n=WDFd\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a196ea7fca733f14943edfec3bc818c543d9364a","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.61","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.57","@balena/jellyfish-logger":"^1.0.15","@balena/jellyfish-metrics":"^0.1.74","@balena/jellyfish-environment":"^2.4.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.101","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.32-renovate-non-major-a196ea7fca733f14943edfec3bc818c543d9364a_1613368140976_0.5227659191443865","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.0.0-replace-date-columns-f525b0648e99bdc114fc0dd9e867f6b7e2e6d910":{"name":"@balena/jellyfish-core","version":"2.0.0-replace-date-columns-f525b0648e99bdc114fc0dd9e867f6b7e2e6d910","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.0.0-replace-date-columns-f525b0648e99bdc114fc0dd9e867f6b7e2e6d910","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1981beae69a87c447e8f86f4dd11099bbb123360","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.0.0-replace-date-columns-f525b0648e99bdc114fc0dd9e867f6b7e2e6d910.tgz","fileCount":97,"integrity":"sha512-fcsyNUap6kWibVgZIGswv3omx/IGHZD30KNy720k+AbG4FgL74HHI7ad6AIc4PFXvA1CIdozJQMq5jClFiXNrQ==","signatures":[{"sig":"MEYCIQDwUu+wSD9LWVdt8zh8kwaoW5Bgwx+m6UvyZspMY4Xf/QIhAJ/YgYoB6Z3d7uKg9mSTlowhCEnqhq8WgmgpTZZFtt2g","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":599653,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgKgw5CRA9TVsSAnZWagAA3zsQAI9A8OBao3+gmjGIQVwy\n1hvRVbmzHewKbo+dx5gtDrEv/EFD2uC/h1AsfWUWnmm1XdGs2UyukbXTN58P\nhRe4UibOGqOaOE+9lvoFDUNlJphiiya4Sq/wbCYL4HHRSoHQgKxLyBAOptWI\nSNyNt6iQ+kDijTmxyky8Vg7252bbUqVfPpfh2cKKdlsHSedJMsKGHszIaDnA\nIUusjzK7izZXgcgPWDoO8aq+2/LaGjMgRGY5aL80+cQlV0EPBDLPcegQopHu\nNW7PaD1yb+0tZqVnhEb/8CJE/VFm9BlCvmlyyBRyn2tyEI+aMfHYzh2JB4nA\nZRvDJ5sxuq540GBv4b5Gryt+IdNSOi/r5AMUnpIs/QJGHqWEAehoZY0EvIlo\nxdCr5ubfAiVpZcMN+7u/domVJ0AuN4PLdH9l0o3aiL1vCVbYU6aMMwr4RXwy\n/qUcDQY9oeIete6Z//UNpalbiXq2x+I8piztXB3U3OA5ko6Q+iutvBXrbIpP\nK91LuQWUfs+PEykr5Szkm6Qum1gocbzMiBHkN4rNRHOp28tYp5hcXItZhb45\n0vHER9z6mc62SOEftClyu7vmByjyi4ARjfcAZf5wtbu4teLRQYwhWranYdzf\nn3KdachCsYxqhyEQ5ih5gVES4YSQSByZAnoLTNI8DF1R11xfZEKWl4y6tMgW\nlAZ7\r\n=ZHBI\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f525b0648e99bdc114fc0dd9e867f6b7e2e6d910","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.61","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.57","@balena/jellyfish-logger":"^1.0.14","@balena/jellyfish-metrics":"^0.1.74","@balena/jellyfish-environment":"^2.4.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.101","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.0.0-replace-date-columns-f525b0648e99bdc114fc0dd9e867f6b7e2e6d910_1613368376860_0.30431530698832066","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.33-renovate-non-major-c25071a787175cf70400de67f95122d52e5c0936":{"name":"@balena/jellyfish-core","version":"1.4.33-renovate-non-major-c25071a787175cf70400de67f95122d52e5c0936","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.33-renovate-non-major-c25071a787175cf70400de67f95122d52e5c0936","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"be34cb8931eeb5b537a6d00ced7b3a04d3c07b8c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.33-renovate-non-major-c25071a787175cf70400de67f95122d52e5c0936.tgz","fileCount":97,"integrity":"sha512-VeycXI1NjLIVnbaK0QH0ggTbz/KUa5v3R5JGboSLSGxSrSSvykhe2BP3G6B7ys3rmUPE9Irx5afaWR4G1zKb2w==","signatures":[{"sig":"MEUCIQCxdtQ/wJmvdHYNRwFb8Mfu6SSNwsBaKjiyif/qoMbh3AIgValIYyYuJT7Q5qCyYK4yrP1YtfxvwZpmZ3sdTigvysU=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":599616,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgKiJiCRA9TVsSAnZWagAAFiMP/jAtw9UcyL714b8rf81U\nU2y5cZP+x0kgvjKtCsJQT0pSK3LiT9/8m8JisQfivorPXNa73rtSY/3nuUNf\nBbgUu6a+dM24oStja1T6bROJNqKGeF2x/Dr1FhePu/ZJVlFD6J8h5ODQviez\nh1TZ7U3Am/2nFbmtKX4hmV8nYG7ASM+CL9jAUL2krB8dxheu8RMmWMuCQIR/\n40cmluOvqLavZ9tOI6i40FaxL/r2Qx//MNj2ucglbgj2jp+3B8Cmckyn3y/K\nXXaj3PjAMt3Upr06scLMsdst2ZZA4PzhVFgwdCvIJqHO1/FwKFUyPSIpmfvG\nQoW5pw6AMWiUt2NqxiJS1RDfgcn1U08jjBrSd8K+ObBL0Ch/+nVeEciu1PIR\n+SuqTgAlm4iRhT6hpWinukE4FFwdVL6/2h7mK6HNXPCXKGcweMV1zmNCsOiq\nkl4dGN64Wzjp+IKtm6togFvWgNvO82Z+TDd0CSJ5urWD1Wp5cJlm4E1Vvd5Y\nKKMwmxLfs4qgJB9VDncriFDb2sK+FHLGl8ayAYP6olY6bvWNaNYbvbkIRSWz\ngG5h7Gnp1ok36eunHke44QDmP5sJ8KEeADBvIZzBHPc2mIJyfFeHEjJdwySJ\nK3zVy9IYylnpBtToB7vshSowPp3hSnA8JbNHoh/sCy061uAZsZpN8zioXdI8\n/nj9\r\n=+JHA\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c25071a787175cf70400de67f95122d52e5c0936","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.61","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.57","@balena/jellyfish-logger":"^1.0.16","@balena/jellyfish-metrics":"^0.1.77","@balena/jellyfish-environment":"^2.4.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.101","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.33-renovate-non-major-c25071a787175cf70400de67f95122d52e5c0936_1613374049819_0.9707608616039507","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.33-renovate-non-major-8473fceb35817fc5cfee8b2bd70b1ed2132d61d5":{"name":"@balena/jellyfish-core","version":"1.4.33-renovate-non-major-8473fceb35817fc5cfee8b2bd70b1ed2132d61d5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.33-renovate-non-major-8473fceb35817fc5cfee8b2bd70b1ed2132d61d5","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5c13560cb4d8951a0c39f89241fdd58897a2b6a4","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.33-renovate-non-major-8473fceb35817fc5cfee8b2bd70b1ed2132d61d5.tgz","fileCount":97,"integrity":"sha512-OkkN5agglRUoSWbjETJ2iE5bZ0RDPn1GhlSCuSHmJDM4MEoCTlRAOB0kGaYvpDvoXuYutnyQ7hry5PEnUXuVBw==","signatures":[{"sig":"MEQCIHnmP48iBl6ydz0wk+hyU4OLtBf76I4mKwNixbB3o9T/AiAHV1bwJ+Cm/hq1w/ZoNghOyld6QwjXr4BXgjql4taNyQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":599616,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgKj0RCRA9TVsSAnZWagAA/GIP/RmvO0L/S6TvXKqDxjFP\nPPkdFixeAIWKSOYhr4/l3xoJJxho2B8ocfHfySnEJa1vslXOt9Wp270i+Iht\nJDQ14z4qe8ODXPtpaN5B7cNuwDxJNc7B+lydsUFLOIq0OQdW514x6BGznVVk\nV47r2tuusV80kABlQEKZDH9izer/tA5tV3gCaEm9+So02aavWYrxYd+JyE6L\n/J/orpZ5hr7/p2wjwpeNDFwI9VpKnjay8bi67sCtQ9ylgD/UgtUx1GsBljXQ\nVPXM7x72uaDcgnrPw1f2rYVmiw+hStjqYYEhmlQmznZnkFLUCXLiSNpLo4lF\nhH8pTSr30sISzqQ3d9FObM4Opj9NhYD6hZv8ZMY5FphgG6ceJksq7TEmMc2L\nquAk49xftmi6oYDYlRJtWAGDGtTi1hl2uCmu4YfIUZwhJz0aCD712GXX2k/+\n6sF9abWzIVRMBTf/F+LuEgpDVpUhXFOvrg6GkjUCKgRMJjWQ6QrPbnpRQUTf\n7vUKiIkwUY9pDup/Z+pHRxa/lopmWK3EISri6WdlUm41MQuksbLPwE8oG9+r\nGrDZmW4NTHS6SOkCTVcIjfMMb814uAw5Lq/+ykZLhMQqO0F3cTB3V0IirAU5\nHEAgBAZS7kdhg4hEGdGK6zWkDKkq9BDL2dNj+3ki68QO4VOpSFrdqvsZNoz+\nrNww\r\n=IzeG\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8473fceb35817fc5cfee8b2bd70b1ed2132d61d5","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.61","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.58","@balena/jellyfish-logger":"^1.0.16","@balena/jellyfish-metrics":"^0.1.77","@balena/jellyfish-environment":"^2.4.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.101","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.33-renovate-non-major-8473fceb35817fc5cfee8b2bd70b1ed2132d61d5_1613380880826_0.181243832105769","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.33-renovate-docker-non-major-425cddc01e61e874294de224b0ce6c442f60e141":{"name":"@balena/jellyfish-core","version":"1.4.33-renovate-docker-non-major-425cddc01e61e874294de224b0ce6c442f60e141","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.33-renovate-docker-non-major-425cddc01e61e874294de224b0ce6c442f60e141","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2784b5b889616c4afa3429680a2858ecefb6a085","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.33-renovate-docker-non-major-425cddc01e61e874294de224b0ce6c442f60e141.tgz","fileCount":97,"integrity":"sha512-SW8Ft9MMo5MeFkk3NpEnLnf5SJ0YpjzIxR/tpbGCXjDeb2t+aQdsZs6z15XQN4KZ8ZDKhlcE8T9Dify/tJz8sQ==","signatures":[{"sig":"MEYCIQCiNj0IrlvokxdxXqGYQ3gB6c/E+MtpEp5tDTBSDNWqagIhAOnOtOs9D++ZnKJHQKG1YxueOnqyrmeRCAqINdETVlmv","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":599659,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgKj0/CRA9TVsSAnZWagAAroIP/AgwaOaE42uBrYeXIY93\nnUrzDkJYXVdEviCzJ9Nn6CyFUROdvey06w4I3Cl90P8tlFGI92TqPPY4+9X/\nvfXNyF0txGf6HkKxKGBbur81lcvMDcApjC3QDsFI9Tk7MREaGc/lME/340hZ\nnYuYoS7PNrwcerA5KH6WCEC+H+3yR9HTd0mny1n12CP9kgeh6O2A773WzUpX\nyIlNIHtmGyrFpEEufWyXBuntekFJBojtbN+i9NPDS6zq6BLx6SVHRbTCyCwf\nA7z4ZjEizC4KLoZig/rRSU5DqASuSSU6aB7sExGEm6TYGl+dXQZfDxtnPSaB\nR7Len7iN0qwFt6AraQLQGg925SxHwoB7HXHbt/SQW6/uH6MJ0aV2oxIZGEAs\nRemZswH7vqDO7shJxFoaq9KiU6FeijJ97R6wXGMiymwgzsNckdHJSuxgxx4B\nTKFoTvYKeHjKTj9HMtkaFourQmKGZ4XT09jWdwo6xx/dBqniyKlqLBMTmEQ0\nC41GppH4tMq+felXpcm1wQEcAwu3CmQSt0N1gKLleN1Tk511aOtH0RuUg9XW\nr/oYS9TGePOYQYwyulIvar4S5X/C4NyTr4lRj4KjT3Y52qCIl7Cyb2Bs5XQd\nhHoD8G5KxzO7lZithDiw9L0FTu4dp9RI3Zao/mPQ7AR/RYM0VIwkl/nZkGvY\najZ2\r\n=M4FY\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"425cddc01e61e874294de224b0ce6c442f60e141","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.61","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.57","@balena/jellyfish-logger":"^1.0.14","@balena/jellyfish-metrics":"^0.1.74","@balena/jellyfish-environment":"^2.4.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.101","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.33-renovate-docker-non-major-425cddc01e61e874294de224b0ce6c442f60e141_1613380927328_0.9084627598562252","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.33":{"name":"@balena/jellyfish-core","version":"1.4.33","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.33","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c8555193753a9a06ab4c45fd4b08bf08c6310945","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.33.tgz","fileCount":97,"integrity":"sha512-ERSE5sutXWYEPelP5py4VEj7rm6ssv/TGkcl4OuDtH52SOruE2iodDrshWnINWsQpykvGKHmYdzNL51RzE06Iw==","signatures":[{"sig":"MEYCIQCpeDaj1ln+lRuVhUqX0xlzzFtJy8BHZE/pgUKt403HkQIhAMyqz9jGZ3xF0Uk1xCe+mz8lKCkIEgkOWY9veXJRr8bd","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":599592,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgKj8gCRA9TVsSAnZWagAAnqgQAJkDLAqWnJEyxoameTgA\n2yFZHv8t4dn5q0sQ8eEc9TpVOfnUPYMr5KhbxDpoCLe4xm3xsYLXEq6AqMIS\neQqIBL4EIHEGXkLt6kSsSb85BmHT44PdaUB3VfpdtmIssuooqKWbWnBNAdl0\ncfSnjxd7sgrPeF8otWw0QJsZOnH9iAbXcGCeBQA7UECFdvCpI3b7C3TSkiRP\noqZtsSro98oEhYCmIgTxfPmTm6fOwd5Yb0UNZYZ7NGFrAz1vCgMpHnDhcjV5\n2SQ/LtVu11Wp9rLQiLScxkkpBEjRd3YXak0c+tIIwimy274PHPUVcmy0TmUq\nAj0Nm5gjDHnqON0svBpL/MPl11m8/Gd1nUwypSVW5/WYvNrSrmnTz5RSAS+p\nQJ0FBuOIcuEs9GyldX2TJ7fvN1iCg3zQOq/FWntzFLcZGfTHJ8eYHipcGhUz\nBJf1Un79UOxX+BbegPlh2cH7ww8t3pZjfHUo4v3NTvyirehqEp4xVJ0vNswE\np0CSRppP4GkZJsesMYfk32ob+KW9GXfB1VE2VWmm9Q15azMBG2zMr9mTtUUv\nkqJgPdFE0PMEd23OjcdMYnNgTrQ5jb2mnQQfRY0mDDBSMSd5XnYqvqXjmj1L\nHrf+siYFIwAFs/9d+VCayjT23TrQSB4jpoxS+Htxva6ulQbYesBcu5a7r9nP\nFyIT\r\n=Tanh\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2679060501575311a68eb21d3067c58f7c96e871","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.61","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.57","@balena/jellyfish-logger":"^1.0.14","@balena/jellyfish-metrics":"^0.1.74","@balena/jellyfish-environment":"^2.4.8"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.101","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.33_1613381408051_0.18961065869794091","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.34-renovate-non-major-4012be64bde9d5b13cab443a12a0cd2402b860c2":{"name":"@balena/jellyfish-core","version":"1.4.34-renovate-non-major-4012be64bde9d5b13cab443a12a0cd2402b860c2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.34-renovate-non-major-4012be64bde9d5b13cab443a12a0cd2402b860c2","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f5757eab811e243d6a273f6bde44b0d04957f5d8","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.34-renovate-non-major-4012be64bde9d5b13cab443a12a0cd2402b860c2.tgz","fileCount":97,"integrity":"sha512-cIP253RVnykyiEdNVL4wuZXtXjDTBwvEOVlyKQTZl6PSqcXBiUJxCxmuLcxUpUHix8T3Bfm/xi87YM5opbaJtg==","signatures":[{"sig":"MEUCIQCjM0R5gZSHQTaXpkcuFVKdyVxSBn6QlyXkzWfzrpjWUQIgeHMJ0LPmxU8/a1t5JKT7MlfbxB7KBl0EkOm/EyxJo64=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":599718,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgKkAOCRA9TVsSAnZWagAAiUYP/Rewg/UpA9KrjZjDQ9fT\nuyq6UOK/Y++dxvNflKhBClwiEiRAZ9o6HNMh0ivh03gynvwnwJsc3a2y9qGs\nEMEJHr7DwJYKdYPPc61keEeiset87eqdb3UFC8FSAo6SBrwBW6w5dFa+l/++\nJD+ZQ4fvzTGMOGvIZKLNVLsXQ/RQcvw/7XHCTwkJjOfkSwpe2gEZs59BK3c7\nHOOzFfDJSoxSENVLh6c3ylm1Q035jh7BdDnYeFxpBv3BDNObLlZamMGQTMJO\n6YkTsqYVk/AxMzJes2bzD9hG1SOJ/AOJpZ0/ZrIya44uuWWNH8x0wOhYZ5pD\nfanTGQfRQvRoJ22/iKreMd4E0pgsHsqfvOP/+6MZapZTyyYof7bL8ZvOaaMH\ntdJHYQ47OcXXMFBxXuE3D0G88WAViy7MWds8hj/krIEjSxs+APEYu4uBR0Id\nudHlhhDRLfOy2od+nN072MQXqiDJpijHnPqUh+WefPug5ahaNdlPeYNVPtj/\njDbbbwCuwn+9hTzH3MY8V+b3lZkSPrynys/2eML+mM04qGdjDfjt29pAsn5o\n8A10GZf/5yyAdsCZ9CfOIYIssSW74Dwo5gAbYemnEmSXBnUsIpO4/9kx1D8q\n1aq/RI1Q0WMniLIgVozPV5jPpwwabvEkZmrVqLkmOeOl6Rs3QHeOS6qbsDo/\nwB7y\r\n=hIEz\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4012be64bde9d5b13cab443a12a0cd2402b860c2","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.61","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.58","@balena/jellyfish-logger":"^1.0.16","@balena/jellyfish-metrics":"^0.1.77","@balena/jellyfish-environment":"^2.4.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.101","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.34-renovate-non-major-4012be64bde9d5b13cab443a12a0cd2402b860c2_1613381646078_0.04597468397664395","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.34-renovate-non-major-e245fc58c77815e4a0e95d0f747095db9e12a8f1":{"name":"@balena/jellyfish-core","version":"1.4.34-renovate-non-major-e245fc58c77815e4a0e95d0f747095db9e12a8f1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.34-renovate-non-major-e245fc58c77815e4a0e95d0f747095db9e12a8f1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2339b39c9fc1d3433915541d9ecb3737b4126d33","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.34-renovate-non-major-e245fc58c77815e4a0e95d0f747095db9e12a8f1.tgz","fileCount":97,"integrity":"sha512-b35AtjL6fj5CwRZPlk12rereEIxH/OjvzXBQ9tDIF9L48B1SpyvQqJptesDieygnVf/1dgtW8KWNsT29xoXmIQ==","signatures":[{"sig":"MEYCIQDZA3S3+Ttvh0Ad0w2dlMcQjxR4gpm9XBl9tzJeshxcAgIhAJbQKvRZfLXr6LDEilhl0rFAoa8Kjh3d41i/I2RuPru5","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":599718,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgKkGBCRA9TVsSAnZWagAALWoQAJY0KjW45DP85scyVK5y\n5sj35cr5fdEU6L/MNzGB4uj9CPTBXieY9EiC6cmFgs2l0P3j4PphnlhF1a9l\nRR6AGS5+UJH4ZR/kcFoqYKBges2Gz5EyOEK18nb5hH65HxhVyUbB8VJTyuhb\n4oqIT3GW2SufO85lLfDQU8Add6kE4QB7OYRURazBvDz31QVTwUq9Yo2L5qHk\nJit7v4Dp7R9ZmfO/JxKPx6bgRXE7xSB0YJ/mxINKq/5ruGDEJnv/8ZejbBd7\nOkhHMtwT/tgOZcQfFfCzAKFhRMktZ4zr/iUpMO7tUjAORtugtKZZ0JQNMFcC\nhv9CrK2ohWDAaGldSsYT5SB7cEH4fak8H74Td5MuioFhO/t5HNyoVbIxzYVU\nRcRgMbucWgf3SHXysQA73oLXcdVbmU3rASDVhPR1sC5KiC8VsyyvzhLJHQeZ\nkky3xDLsjA9XZ8Z7CgT4YZIUB9ZXqjEJHA+KvBiWzb1b+vwvAy+I1RifmiAf\nF8JgnBwYy/yYDTKkuu8ul3CWwWFIMXrFcRpXKYtdBxS9g99k87yek4vFqboe\nVTtc0AcjXqHyITK85/vYlQ7x5+fZdnJKGm2JwIfTBe1E7TRuhjpqY21sXl0Q\n7loutzfy7AZ8hHlIN57ShZWaMnY5gGh4GtArF6tOtPBtv1Chjs1tjKINrRDF\nmo5M\r\n=1vop\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e245fc58c77815e4a0e95d0f747095db9e12a8f1","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.61","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.58","@balena/jellyfish-logger":"^1.0.17","@balena/jellyfish-metrics":"^0.1.77","@balena/jellyfish-environment":"^2.4.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.101","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.34-renovate-non-major-e245fc58c77815e4a0e95d0f747095db9e12a8f1_1613382016434_0.7417983473388472","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.34":{"name":"@balena/jellyfish-core","version":"1.4.34","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.34","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5ec20b11e741a04f767b83e61df511d56f51badc","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.34.tgz","fileCount":97,"integrity":"sha512-pHgi5+z7iq+bPZfsIsQi3+TjVBYy+fj5RWQ9Fiov5taOfloXGtPV8q6Px4VCzFlgb3TXJsWSzr3JcJtIBT1NPA==","signatures":[{"sig":"MEUCICk12kRaHrOUGqhNwX5hu2PUKzRNhRrBY1yw9fOqIRP0AiEAuoi1i/wotXJo+rwfAhZYrrXQAlton5muHj2N1V5E528=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":599658,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgKkKtCRA9TVsSAnZWagAAybQQAIR8xKzJqYpXACl3HF8H\n+Hk56p9NzARclvtvVxCmMOfzX5klRd+GOmKB72Z/GBaUvLmrE+Q7ifsx7sB8\n71sb/4n8T0Ry49ADlj1HNDUB/CdIaaSVKdVDKrDFSQT+9PTodTYHR2ER/BL6\nfO0RnZUD9sHwu33XSRFBlbRNySajZuv/9P0je+ogo5DtUDej1yWMODsPAbP/\nvwZdLZWfnbS5J0x9z8v+IOVFy4SMwKGJWTlwjYhu8C0tBEdUwM2ZvuDFLRyg\nIkBerkln9LPKdSe6BnotImEEAJGTin4drBhaPYLMKXPATQbqegkaNdrRD16c\n4LagXvdLUbwXC3uqBiwysU4t3/KczZT2g2XJwbE+XUo0zcaNsF0+sJ9/cniY\npp6lUbHVCkSwlsJNvyOMdrCKxRu6HJJA+R1w9VnfScEpQ68leLoxQvv6psz9\nwYcmaoGEX0oIiFB1ocWpfJS8pN44Sar9wXOLUJbnU8gf2tosb1QQiVxWFEPr\nA/M7Jm8h50gjVV2GgxHu/mgXtrQUEBUJ25MTQbVWycQ9vAUvDIBwF8hRBFB5\n6R+Wc9a72t4LqUmO3Fdd9q9c+JgxKrmXTHtYx0nmIjFgD6S5iAfn19KtrbHO\nElKzycDFOn2Efpowohs4uQPy6C3cPbPQwhUfEMw//E+tPuGGp2enruXrmSDq\nnWtT\r\n=99kp\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2958960f387666b1871ed0f5b0fa5a09838ad519","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.61","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.58","@balena/jellyfish-logger":"^1.0.17","@balena/jellyfish-metrics":"^0.1.77","@balena/jellyfish-environment":"^2.4.9"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.101","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.34_1613382317312_0.339409688254104","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.35-renovate-non-major-d5b51f9715243a7f5878aea2fe0d8014130a1a4a":{"name":"@balena/jellyfish-core","version":"1.4.35-renovate-non-major-d5b51f9715243a7f5878aea2fe0d8014130a1a4a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.35-renovate-non-major-d5b51f9715243a7f5878aea2fe0d8014130a1a4a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f7529615149c1b8466801014a86cb498b3a6aa01","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.35-renovate-non-major-d5b51f9715243a7f5878aea2fe0d8014130a1a4a.tgz","fileCount":97,"integrity":"sha512-iiUsVppaQgjWCWg57JiRReV4d6q3wJ4SAOMiQysKIMfrrwLgLVQ+u5b11TemosB90I4zKEXc41EZcI/+5Vw81A==","signatures":[{"sig":"MEYCIQDzJ9r+iayAS6KhO/Z7DepewT0SzxaUngpV/pV2x5lPVAIhANUjkZF6qUyFANP5GRdyXSJSxPocS8OY4YFnXZ839z+F","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":599822,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgKmVkCRA9TVsSAnZWagAAehEP/jXEABrAIw5w7Jxq1Z1w\nI86yykBGSXAQmwqW5KXAHYHC/WHmwqHiFrrNf0H2yNd4eV7qtL0U0N8PUkls\nM67nXFhfDmMaIY6OzYcxFzjhftICkOK0zWaM0mpxu/Iz+L1NSJV2ukU9oE6U\n7QNm7ebH2FRXVUNrROgngJTPtMp64/NZaEBhyJBpVGCgp5vViCiJ2kb4Bc4i\n828/P+TFD4MA0oyNhYN6Ab9Mg6NPxjA7NiXg7bkJ6CdRcgckjyqWlq7OEr+x\neOsj7s3hDlT67fRZ5IeFq+hFBnrT80irDDAlptQZ8Bjkkc6NFWwUNquQGjbE\nKcfbrRzSoCMdRyncWGOt1BK0K+YzrZItPAcE5yNwTAUtyj93MJJ+gFAYgN2N\nZKxInuW8cKBRHrkBdZJboM467zmBWuTY3jnA/IdAt7ewAtLM0zrlZvWKs0+8\nfp5kyUrzeNxhwhzLR+QLXY3RZ3iGwbZXP+fLoDVQ51qh96pb0IUqfNUda0lq\nlW2uzveFM8kM2AUpvlXVxdK4V7eM+r6jY5XjsHC6tC6rhuT+5hZ14v7Cz2y5\n2Hp/zhJUKyfSvxTRhu32vMhObL3W+6fYT+gFRo/Db/18Kn+8qJGId9PeiTYW\nKiY2wLvXA8vN+mIHSTuMD7/5iBTuolRClPehoXyEf91TddxY0VdQIm7zrmjT\nXRp8\r\n=xi0B\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d5b51f9715243a7f5878aea2fe0d8014130a1a4a","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.61","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.58","@balena/jellyfish-logger":"^1.0.17","@balena/jellyfish-metrics":"^0.1.78","@balena/jellyfish-environment":"^2.4.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.101","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.35-renovate-non-major-d5b51f9715243a7f5878aea2fe0d8014130a1a4a_1613391204055_0.8674463440756801","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.5.0-generic-oauth-1c7c0dedf2c52a9fea1ecf3a50b9ed50c2438454":{"name":"@balena/jellyfish-core","version":"1.5.0-generic-oauth-1c7c0dedf2c52a9fea1ecf3a50b9ed50c2438454","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.5.0-generic-oauth-1c7c0dedf2c52a9fea1ecf3a50b9ed50c2438454","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"76125c8abeeaeafa03cd6e326b81b12232658395","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.5.0-generic-oauth-1c7c0dedf2c52a9fea1ecf3a50b9ed50c2438454.tgz","fileCount":98,"integrity":"sha512-92zTUJSthawLL9MoVM0fLc0K46Mzin810V+3/aYztGbcau7c5cmoqdAGfKBLdCohT6zs/fUzOkIg2Uy3qBNZJA==","signatures":[{"sig":"MEUCIQCJKNssLCoRx/m5L0phz245E7Eq/zSDEmDEIMVBfoNGkgIgAVc+Z+i68aKYmhet5mKeUF5kOOFKyhYBwrmSy4k/gMg=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":601141,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgKqw5CRA9TVsSAnZWagAAkPcP/jChu1l1G9pQ3nZ2iLpF\nuadwN3SVPRikkaL+xM4vnUF3z7wuwkNdCLDlPPk4WtGUhpP13RHbqk+yRtvS\njG+k4bzLOVK5hPK7TEjSAs7UD15k9YON1KBtxQwq+e0rYpbimWSXsGTsB4OW\nUPiV2VxhWvbtn8s6edzxk0wpqfcJ+QsDjWaxFcswFvuPMDWrfrGT1WAb8hrE\nDzankR2WQFC9itTLu+59jK3WLwv0aBxghmSNclwHczkth3mgeCfSB5fzYpUg\nkxNQhZ1bjLPNEqnFGKzaKjd/8Uv0IPNBFXiy20xjZpIpv3acRTwV56x+7Rww\nPvtQt9ktrzPpR6lsb0mTG39T/p+rbhK3hNqvJaI2ykwq94Ih1Q8Zimiij/7V\nyWltQtAJxk0IwifY5s2XYIjbTdjAzrn7TyFdqtSR+oW9B0QwsUVIVU/HkCSB\nK7T48MvUCD/BWm+hn6UAvwcaH97wQyDkPax76GWcSmUr9RVOwthMOpVKZZpQ\nkVYq03ozs6VSfjCx+g65ZU72513Om6i+3cH+crIUIB3aWKvfHCzO9ZcBN8Et\no8rL9l1yhzG9V6Gna7p6KR4+Y+LLl5QPaz/8JH2qwEVTynH0BSyR4yIkTtCs\nHDzvrMpHzqF9CA8eEPDGhoidx8wqNF5Il/aj8x7vTewIPto7tW9Zec6wUWCC\n69VP\r\n=4DsM\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"1c7c0dedf2c52a9fea1ecf3a50b9ed50c2438454","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.61","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.58","@balena/jellyfish-logger":"^1.0.17","@balena/jellyfish-metrics":"^0.1.77","@balena/jellyfish-environment":"^2.4.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.101","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.5.0-generic-oauth-1c7c0dedf2c52a9fea1ecf3a50b9ed50c2438454_1613409336662_0.3473832700752115","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.0.0-replace-date-columns-d526b6a7cfc7f89126e11241ae1029dd938f22ac":{"name":"@balena/jellyfish-core","version":"2.0.0-replace-date-columns-d526b6a7cfc7f89126e11241ae1029dd938f22ac","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.0.0-replace-date-columns-d526b6a7cfc7f89126e11241ae1029dd938f22ac","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"513889c4bb0a4604ab53eda722668642a2909d81","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.0.0-replace-date-columns-d526b6a7cfc7f89126e11241ae1029dd938f22ac.tgz","fileCount":97,"integrity":"sha512-68/B0Z/L0JYZRmUXNple/0/o5s4eOyamy98QNVmN92DT3JG2Bw3uC22j3RvxVa28Hcg2bjARkV7f35+dEzrrNw==","signatures":[{"sig":"MEUCIAapTiD8z6XjccqwJxTP5lKOgCJmEWVaISQ9CQXY30B9AiEAyB4ewCNRYsvl7SdZDL+Q+xKlOsC3leEl1KRyD+S8KWs=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":599821,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgKvsdCRA9TVsSAnZWagAAGPIP/j3LSt11N3AubbVbyPuu\n6YW9xTtxRoMBDS6dQnul+rVPsjHTC+k62Grn+98Z48DRqPNI1gvIAS2wA7x0\nh52kWTJwa0Nf88z1iArRGRYt+8tTqsAgAwVQCg8Gr7+0C/3hS3QOKgOtPfhh\nspMFxHZwBO10mklnr9sQfUPYMLtV45Q/xr1pJh9hEsQonl46ar9kCCxPFnMM\nHDmIblvm55z8gHq5rITyj979F33W95ELWmhniTHCqNMFuVOCXrH1/72I0lt9\n7XNVFFBuSlV9BrsNRDUsHLNXeYLoZK1DNSiPvkAQRL+rdM2jOE6QQweMuXRr\nMC/v+hLqNo0OLLp0iyh3zByjYz4CEDmBoQHh3xZ6tjyvkTOinpDeHXIt3OOu\nVMQy/qLXCedXGR2dXEZ+Rt7OgCOelt71EEN41nazGVFMOYQrIKv/VaY2GfAR\njHErQZi8QwRijbDd6XKntj1A10GcGjgc8fsTW/5quTfMikdnpPhmMg1xPqzh\nL42JphQVWDa4ojSTUUJVHweLRRDAEtn1utK/69pu0EdBUSfdCNrWDEvG+BSS\nsSof/QRR8T5eZtpdWLNax3qUl62aSK1zlT7o/+2HCFwOTzNhdVfWCg0+0qbA\nGQlvMkUyQHu8W8uAGLKpu0vxZVBpK1g8TlwX3VXFg/3/qQ4OoI8H03sVAtd4\np6Rf\r\n=aoBf\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d526b6a7cfc7f89126e11241ae1029dd938f22ac","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.61","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.58","@balena/jellyfish-logger":"^1.0.17","@balena/jellyfish-metrics":"^0.1.77","@balena/jellyfish-environment":"^2.4.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.101","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.0.0-replace-date-columns-d526b6a7cfc7f89126e11241ae1029dd938f22ac_1613429532437_0.615291306921748","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.35":{"name":"@balena/jellyfish-core","version":"1.4.35","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.35","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"685d3244aee554cbcc9fc94e783de3186c94b063","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.35.tgz","fileCount":97,"integrity":"sha512-Cf023UXnMIGJssIsdR96B/Ml8/mDntUuDFgnBvGloUHQh8uC9zQamD9iEBdz7KqzBJsgEM8xQfytQvDCI3DK/Q==","signatures":[{"sig":"MEUCICT3WheK8fEAo3GRl1fI8GYQPkJqzMoOLbNJL7lMIChYAiEA921B6PpMTaNolivBGPBsqGGv6cR5hcq0j8dZrmsann0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":599762,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgKvxWCRA9TVsSAnZWagAAwR4QAJkBsX7dc6Ke6Kpet/HC\n3S7RZJlZ3N8nJUyme686KQ4M215kJK3LuUruAAgjfh02ACkxqhl1eukJZtcR\nc/H9PTkn0gS6tNwN1MmmbiDDuvTj6pHD1bNXCd3jKfYRchLaXoEpIOWGQp1b\nbuErI2AwJayUEcoCVR8Gfkf8dbbG16DdOrPIf6GXqWoNiMLkuPXkfncyOnVp\nsMBmAyoxDRMNKaRr4kPEppajx5AxzoK+M0OyT/9BkUHt4e2/0vtrlAxjkT3X\naC2NnO7Fx2lPmtxRryB70LRR4f98Tj6ZG3a8Y57UgXP5RY1NVuDycsdmSNkW\nd4w5oeSEFtdXeWUdduN0SR+VQQV9LyxIdZfN+niCMXmv6yy/3bde5rIYHzH+\nFkvfGQ6wNA570eyXVG35yBNKxczYsEQeJzRIvR7mZ1XrhfVGrS4PVBp/aI2F\nuuSskVUpaUYIWQ3FLWTGk8ByNNbD7M2n3xOgEGexbbsGrMJ2evvvdq8m+3KA\nYg3ojUM/5HuSqiZjNGuNEY9VH+RcWu1RRm+SMlAzdw4keQY7IE/jiFsV8n03\nE5WMph1IcGdj5LPAA4PdmN/Foh5pJZ4RBO2Bj0sgDnOSOst43mKJRpYTbqYN\nhwzW5Xnzw+47FHnXHpiRllMJNhngHc/fgvuPf90prfPaDUHFv28H+mBHTrWw\nazad\r\n=DMR9\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"20360a701bb079c0f7708ddaf9a33eddcecea24d","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.61","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.58","@balena/jellyfish-logger":"^1.0.17","@balena/jellyfish-metrics":"^0.1.78","@balena/jellyfish-environment":"^2.4.9"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.101","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.35_1613429846229_0.5832693690082786","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.0.0-replace-date-columns-9ad4cd83f2d2dddc4e248127dc8ded1d54b9dc64":{"name":"@balena/jellyfish-core","version":"2.0.0-replace-date-columns-9ad4cd83f2d2dddc4e248127dc8ded1d54b9dc64","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.0.0-replace-date-columns-9ad4cd83f2d2dddc4e248127dc8ded1d54b9dc64","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"993d8abaf6ea0756fac86e83e652e1a2a359a847","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.0.0-replace-date-columns-9ad4cd83f2d2dddc4e248127dc8ded1d54b9dc64.tgz","fileCount":97,"integrity":"sha512-bqlwk1vO/Zip20DcJrOwt889XvYiZx1N3htmLSP8BqKxUF8xPkUBfaoL3eyKs3He0TwatRx7Nb3jAqyt2wCWNQ==","signatures":[{"sig":"MEYCIQC2NwfK4f+8MAwgVWdZwa/tf4tpBlZwCnk6s3HSdSLD3QIhANn7kM7VoZwL+EQvWANAuNzcXcliZjV6IoAm16++9azR","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":599925,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgKwNQCRA9TVsSAnZWagAABHMP/1g6G7imx+uee1NONVEx\n735KSHCTQCP+PVnGQ3ChjYxcW0BQ9FA0IdJF94DUmp9GP0MtbUUuO7oa6hPD\nL8i9vhAYM/VgZ1uQvO3RWA1zX06bSQyP8bSdxq3CV60HXvSHQULlZUuQDD4m\nJALMgV3p6su0bfOjl2jMpaOEhkCGFlu5qlzVBPj1kRLr4tpzohMwo7FcoBRF\n79kmc/NzJkCxe+4GQjlCo35DI8OzHzsVTAKSNLC3z2t5orAJiy7Azja0Pgpe\ndI6NcCBA3gzZRfH/Ez7WbC+LtNKnG4cuo/oXUQ79kZpwlqByGVNCS0a8x1uu\ntvvaOZzpQl9Fhk+T2QJo8IHGD4iTFFOJxvOdfDnsqzgKiq9HFrYnbBsMgMBq\n1Cos0Zo3V5uAlDAHKFhOpVSpOITr2kF/LMtPAvdcZOcfe1dMjLkUZhCza+MI\nblObF8lhemRddIGzqaRXMTMhDGNkvD9K9NK3GFIjZHj73576nvDcOWMuw6jS\nf+DkACAWNrdLd1OHMnQKHxBtNqgizcnRwNgb9nRe+SvBB2cXyLo6kkIbiC/O\nn1P4IKFcEYWEzDjk74CZqo8AOOQT87T0Z+y9UEMM4pQCX8SWclVSwk3aZevc\ncdx0wIBI53vaBpe8q+7k5/Gw0q5PtpwFUUEWMLzov8QF3/mdTiqlk6iRFwvL\ntcre\r\n=UZdG\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"9ad4cd83f2d2dddc4e248127dc8ded1d54b9dc64","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.61","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.58","@balena/jellyfish-logger":"^1.0.17","@balena/jellyfish-metrics":"^0.1.78","@balena/jellyfish-environment":"^2.4.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.101","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.0.0-replace-date-columns-9ad4cd83f2d2dddc4e248127dc8ded1d54b9dc64_1613431632516_0.6629194912379965","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.36-renovate-non-major-a15b203617c3a1111bf160100749b0c9b486cb66":{"name":"@balena/jellyfish-core","version":"1.4.36-renovate-non-major-a15b203617c3a1111bf160100749b0c9b486cb66","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.36-renovate-non-major-a15b203617c3a1111bf160100749b0c9b486cb66","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4130b7dafcc06a27ccef1245704f79b68a70bc71","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.36-renovate-non-major-a15b203617c3a1111bf160100749b0c9b486cb66.tgz","fileCount":97,"integrity":"sha512-aYn7zVx3+FXbPbM7QQ6HSEBkekobAzx2XihEPhbPxkJFszaCIood6SQO71iWlJj3b4QV7VSqhbWRBLoQCIvEQQ==","signatures":[{"sig":"MEYCIQDQs2usmvMrSoS1UtZN5bFcxzdA1325HkwgpJZCO7nO1AIhAPRn1vQz3L6WB4KWZOyHjevxW1nqMBlzzNjp82uIfYe2","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":599920,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgK067CRA9TVsSAnZWagAA3VIP/R4nCMPYH6y1k98R2Tky\n04HEiin05WSrbw+NiZpMRDwj+f1dz1OW4ldObbUsMRuLjd0uQrvjqVUMdA0S\nJx98BsY/VTrxfOwEwkbR5WoBWyA2DWm7bm9msOHpqSHjRMfI14Hed2oyvGLV\nfV9arBzwVTM9oxTi2mSNLQyHXuZQwqVlrsbryfE53T27fPOjH11/qLgzGsHs\nCAYSOnFBwDDy2RAIZwhqlUoAUauQkgUPHydiF20NPl4MdCAnJ3gklJ3qmpKZ\n3s4M6HYyeXMJ0blZ0w+OEHpqZo8rSMq6oVAnXyv71u9SvgaIyy3EOvfBa76O\n/BxMvye9CqLqzrVhL0r33nmAlhFq7RWW1gkJGCofWZoJoyVlKcOG/ZjkqV0X\nFnlcytAxKsPnMumOBOO0e4Fh5rOBGm87VS5LQf5r9i7odm7r6ZK8Xv9ULM2a\nISGyHlzLY8v4cu/gLyUyXVDLrEks8mdKIfATZRejSxJiiDRtTjlYGTcx5khJ\ntjtxU417Yc8NmCDzD4m9zX3SM8FM0swudlupGUnxHlc38pYN9tq2gD0EyBim\nE6oyfypuRDZWtuoze+4RdYALH//wpTMZXzOfjM83krg36YDrCXFofaVL1V6K\nGcosX59cTmDNbNqD6Z4ReZhPaSk5V+rwJHheCwvnZ7C+uM4Z9APY1Pubnrwc\nKSsE\r\n=2zIe\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a15b203617c3a1111bf160100749b0c9b486cb66","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.61","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.58","@balena/jellyfish-logger":"^1.0.17","@balena/jellyfish-metrics":"^0.1.78","@balena/jellyfish-environment":"^2.4.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.0.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.101","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.36-renovate-non-major-a15b203617c3a1111bf160100749b0c9b486cb66_1613450938875_0.4961128900688876","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.36":{"name":"@balena/jellyfish-core","version":"1.4.36","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.36","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"db2fad60804382990ff653755b04726c286ea967","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.36.tgz","fileCount":97,"integrity":"sha512-GWvcrD7H2E8Cwj6lIGcwaUpFF1c30eawHmVM0X7C/m9OQD8DkmSc+dwKDSDj+Uk/y33rStVAxBvnfHlZtAdz5g==","signatures":[{"sig":"MEUCIQDH7O9nbgjRskfIduXIZFEZo52l29gxSwQqZx0X7dd2DAIgReW7J6OmCkg+jhzatycTUE35OXBFMUMusE3gFBa3SnI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":599860,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgK0/7CRA9TVsSAnZWagAAWQIP/3gfQHwKIYtyDnOa29YV\nObbuzEDzDyjYwIpThxvg5yCVnfiJrMrZ0ppdktiTTneSbNIgxg6Llo1qJ2G0\ndXXDrj/jtPWimHuLfal1tOoT0Uu9n7BjfX9Mqcx8N3KDINaq29mzl+9fr6Cn\nyeavbFRvYcwJUna1iRr5597sGhT3hsEnKL70V3KwJ3h8TyMpStBTZlWY9YeU\n7hetbwqTZ89zxE5Tm2q4uPy8c2+WbI9X/oOL7TYa/dZz6TvXU+F19bRBIpi5\n9/x+fQdSfsnaYMUMlA8D/5EBByPkpszDiZc4g6nSwhtaamwBaZO5UOGcKOdC\nqcGfpt28jUTI6J2Yt1fqVfcQWoNAIjPUsXWhsB1414823/1QXbg+iDoe1UlB\nx/Cew8oSpNW9paXIk+Zo5ZYMAVWb0tDfVtvBLbXMwsRqN3/7Ez8Nhf9X5qc8\nX5/GGdabTzrFJxgVhsGE3rxjadPOpL1kwJcN5+Hf+e6OSlmuq5k1yexL6x3t\ni4nJC0Ob/pnydSw2YDXpFZjszRb8QyLyW/XfZJ1l1b97pRNra/k0vf7hJKue\nzxfXE3n2cJwTsX5f3f1/n7+53xtdC4K0L/cjINtenAfYEJzc9WFND19QqWH2\nUjjzU/B8veS7f7IbSR/YGJu1QAGZ03+7HTe++gppUO3bUl3CK587jrL5ld7Z\nSbA3\r\n=wnAS\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"5b41a349237c34417cc4c11585883d38a42edde0","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.61","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.58","@balena/jellyfish-logger":"^1.0.17","@balena/jellyfish-metrics":"^0.1.78","@balena/jellyfish-environment":"^2.4.9"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.0.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.101","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.36_1613451259347_0.3531964352901351","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.37-renovate-non-major-f8ff72947d4fb9660e8cc80a55093081f912251b":{"name":"@balena/jellyfish-core","version":"1.4.37-renovate-non-major-f8ff72947d4fb9660e8cc80a55093081f912251b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.37-renovate-non-major-f8ff72947d4fb9660e8cc80a55093081f912251b","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"42f02b47b4614070f6e394f52d9030236cba186f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.37-renovate-non-major-f8ff72947d4fb9660e8cc80a55093081f912251b.tgz","fileCount":97,"integrity":"sha512-jPuaTQP2ps/bfQvP/43AEzsOJQ+baD/Z1RTG8epo7Rclvm8lY04qLeC+DdvhQbvfytthNWIpsvDXJ9XmEjYFVQ==","signatures":[{"sig":"MEQCIGzAs7Rj89vh6KPvZmnAH5LXiWFoitwGVnhV3tTiRRegAiBbuzYkANpP6SHtG5jiVTlhHgzudwW4xJb69tsW9mpcNQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":600024,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgK1CwCRA9TVsSAnZWagAARJ0QAIrFEO0IGpUiFzccaIgJ\ncVithdeFiysvC5y9fR+5ECHX+zLJNMqOotthi1iJPMuq/Bib5uiPPkMZOJZV\nTAV1bBdBo1bcmFRv7kB6sxcxDxjk/i/AOg9X7mN8zt6EVkNByP01HRY+/bna\neveP6F+0IYpqeLCoyvfv6fXN2iFF8jMddNtriOeIv7B7s3sDqa2b2AUP4wMZ\nU8vls6ImEUyQQvFkhboqmR4gFrjDaPuGWPb+o0aWyEFIZgtlMw++vxPgLCFm\nbhmBatLjfTzT9kWcUAftLnWzZt/XTctWCdzqr9Xd+we79n3jSpwMsZhw+plp\nj1HwQa+WsStstWrjXdgZiSfNjojJ7sy6fmadaAgtS6krQn6q7bXT1n72O6SM\nTWiKajIaUFQXYNLxahpMi7NybnhYJhFG7YLYP8tLxArRgcCHQ+uaT01801N2\nd+uyBHraZ0/bgyhfqxcLb2/PETRr5bUMsUEl2VFqwyBb7fN+0tU6h6ZlDWPr\nFGoZmPRJYawVRU7iSM5acNQZEZbygHO/a5z65D3klisOG1LPdn3LAWUYYRRK\ny5VNEi8hwCtWKNb5Iati0Z5+eah9G9eNiMLLYbEYOqyLTS0XJDGLBsRksmp4\nqcTUzahXIVk/iQVuxhXGoqGHr3TMGHOBSz94A66Tyo4itc7eq6vQjd8AfHad\nmjJW\r\n=/utT\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f8ff72947d4fb9660e8cc80a55093081f912251b","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.61","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.58","@balena/jellyfish-logger":"^1.0.17","@balena/jellyfish-metrics":"^0.1.79","@balena/jellyfish-environment":"^2.4.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.0.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.101","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.37-renovate-non-major-f8ff72947d4fb9660e8cc80a55093081f912251b_1613451439927_0.9143336359623007","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.37":{"name":"@balena/jellyfish-core","version":"1.4.37","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.37","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a556c9a5d75d2581205a37856b04fd871083d4e7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.37.tgz","fileCount":97,"integrity":"sha512-u0dFTQBYbclyFq3HwJsWNXNgSnslDKog0J0VUoai6jd3fHVUpavLdtXf3qEZk4sXLSZSBhXSqVvcxzXAbZtXQw==","signatures":[{"sig":"MEQCIG+eJj0v7akluHqbs13G+Gc7ZFhok3cmc3LHDQ8hpUP/AiAypwbJFoERuW39LiZDoLeib4MbRqu042beSbFOUC8fCw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":599964,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgK1KeCRA9TVsSAnZWagAANFMQAITyPLRBPRQDef6ERqms\n/KXVncH/M08RzRGbAZilC+6Z1+fIwz3vg0MKgOBheR91MysY1LJAUyoE25dH\ndEIa8EwQIdlBtCyZ+bLinu/ct1hqGVA0prIPFfuu/LKyqer5oKuQiLUvEPJa\nn0muj4aRTODgLOtcYpwYyxeQTxdGNjvOKudV0fMM8g936bHfStxrIiW+zWTD\nDGpamSYP46h3ScuwAr68JdCgwx5XWOdYpH8XPmkFKic88TLn2RE381TxepCp\n68OnX6HWudKEyToAUCsRtoLQa87no+saYmVAZVaq2/sgl3WAJFGP5qHaYHjU\ns77avT8A1HNMd484PIPf07Wt93M9B6O2G4PNMNHPTPoyF8px/Qz5aDmpIuXz\nRe53Zn2AiTTIX5KtOFZ4C8efWGiP2RrUFnQ7Dje2h0fx/6lwpezavcle4Uy/\n/rZnKGeITFaPCoWrjHFv3oifMzi0HNmcTEP6U+cUbjdxx2YPHOJxf9v57SKW\noLuAWSxwPfvukS8VoTI0DL86NMWJwuVCiny6+Gk4Vw4dRMZYubMcBpuap2CY\nIiQiJx/uXw3U/OCs0u2Czw0LshLzdlGu2pi6rbI0ylse7X8PAitCrU1o47T0\nAeR5/AACpYMdPVwwFG/MGSIriExff8hwz+iOnOz/PExZr0+ljbtpGOqFilsT\n0ulR\r\n=7ZcD\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6d7d7bbc40e622f5b394e864e823d70a8c5e79ad","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.61","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.58","@balena/jellyfish-logger":"^1.0.17","@balena/jellyfish-metrics":"^0.1.79","@balena/jellyfish-environment":"^2.4.9"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.0.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.101","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.37_1613451933475_0.021546212095874262","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.38-links-index-deadlock-fc5d0bb4e9d98e33424df2062e1cc99b82f51248":{"name":"@balena/jellyfish-core","version":"1.4.38-links-index-deadlock-fc5d0bb4e9d98e33424df2062e1cc99b82f51248","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.38-links-index-deadlock-fc5d0bb4e9d98e33424df2062e1cc99b82f51248","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4e2f6afa299021a6edd5b35642813ee0c00909c6","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.38-links-index-deadlock-fc5d0bb4e9d98e33424df2062e1cc99b82f51248.tgz","fileCount":97,"integrity":"sha512-s/Cp1czImcR0fUh/flOAnifoxrJCE7/w5jabRAQrKVMWmvCGuMln7J/zueGd5kIXlR2YkxgulrdigA5u6KPx1Q==","signatures":[{"sig":"MEUCIQDFYIRXf05sKvksHfqMOLZhg5iJBXw5Se7ne0q9BhzThwIgUlbxYNCRtnAVUCKoiC+a+M3qkR5qZut7qRcRRR5rFnY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":600355,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgK2AeCRA9TVsSAnZWagAAc1EP/A9aOYd2nQj3i/7ilQ7/\n9Px6nZfrazcZ4e+qmeHCpBNSSxkw9nF3GqkZ4RmzcLlXjompZjDYsAfhI7Me\nwjU5od/430NgLuCO4WSZc80m4OG9ja0c77IqBrwRWlm3gLNIXHvdVTfgt7XZ\nKy21AeNfsBpl8+8i8EM5JVWvYhJeIR2JlyUJs5d+AYK2+r/ovYCXgvsVZXR7\n+3MFnsjZAaLFojacL+6Pr9QbIntgsJ4zbiI4ZNvNdAfLHOTBjTTrx7LsTVmm\nuswyFvo0alZsB8CxGAjWhu9MGcipucMeOwqFbMvtcUckQTzE7SMjPVhJfIkE\nai0TIK7Qvkh7r9HdV3rvjxUUlUt4c46uf9tc2sjbFwl9gxWqrHTPpcE3IR01\nWMV3AHfJ7MxfC9Cc4wuU+NLTyex+oteN+YsjDZ7jqniTATEEf4JYT6f9y4aK\nyIGOEhdk9Tgzn1MiUEiLr2TiDkpDGbc0GjnkuFhx/znTmj1wY/ETsK7Lj8y8\npurdnzoHP6SZYbyN8DjBNUAfI6cx6kTwdHnytLUaSxc4jogt0deuBS4De+/j\nfP8BBscuHckt13/bDPF2vczkWR9+Jx7RLzc+H9oqUFdUy77OFkaQJIP0PDkH\n52vWOAn8ebZOtK0vtTkGr4m0vSReRY1MlGAGz4wukSBkCy5LpDpLaKlB6Jta\nXlPl\r\n=viel\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"fc5d0bb4e9d98e33424df2062e1cc99b82f51248","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.61","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.58","@balena/jellyfish-logger":"^1.0.17","@balena/jellyfish-metrics":"^0.1.79","@balena/jellyfish-environment":"^2.4.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.0.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.101","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.38-links-index-deadlock-fc5d0bb4e9d98e33424df2062e1cc99b82f51248_1613455390180_0.00008956027713291626","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.38-links-index-deadlock-ed7c1d653f3dd16b4ebe189258bb7b3a08053aa0":{"name":"@balena/jellyfish-core","version":"1.4.38-links-index-deadlock-ed7c1d653f3dd16b4ebe189258bb7b3a08053aa0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.38-links-index-deadlock-ed7c1d653f3dd16b4ebe189258bb7b3a08053aa0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f7aea84188250d57ee0d75413e3a9da800fb3b14","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.38-links-index-deadlock-ed7c1d653f3dd16b4ebe189258bb7b3a08053aa0.tgz","fileCount":97,"integrity":"sha512-Mo//x8Xy1UdlRZV+QJvXiJRxSdlxJS35LWfaOKCCKO4B2FS/R0NxTz7rfSU69HIaNACqEH7dNAt6z1HBndK2Qw==","signatures":[{"sig":"MEYCIQCn9nPybM1AnTtLpNFVjAh9WbETO43oFEWuzq3tqtLxHgIhANPdGtfad3obivu0nxkMVCgEeBUUMRRMtsns/TT529qd","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":600271,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgK2NHCRA9TVsSAnZWagAAf1gP/0ouGNYO0hMkuaKNzBOO\nl0W55Dv/ae/HocZekqCsevzIScFhsuvaVJCL1zWSYJEeri8HO76WkGFNUX/I\nsgqNGCGwbO4QtFLpGQeDrl+IJEO33mF97byw7MKQCk78h6KQYvDEKXye3OBd\ntCeTilIq00hYqc9aIl/6Ts+mjOoDQ/L59aVFSWXo/dvpbjHoLM3uz2foKH6+\nXjUqOfHHx/lLdjduc8jWIXL68i999ISmqiLsZALUBqPPv4FeTQa4lOIFMsSp\n8qxQ5hqi8nO/ywlZYdo5LwrC8XSQw1WAPCbwHGs+nyuqmloPpohZByYmQDrV\nk/OnLRTyj1bd8v4V7/mjLIC0STDQrowEKjwT2FaZBfMqjBbQPrgpckuC0XBS\nCs2VXuFU5XZQGtLvPDZBlDNbBtBnHaM85/pnMILLGs0MESqNnvKQ0t1QPiOA\nU8/t3MkIsW/klZxklzcGbC9Uj+qLEuyStVzaVDL/SMJVyhReV04CH29lJUGG\nSyMN1/oyw+H7teTTeBdUwm+4ywOaNy81d8z/fet4BhOkocPqoqU4261hc8+W\np65CrNxlQQTnHNBf+EX0bYw4b1AAOG2Q9R5bmTJBKO6jc0MajxkO3jMwGbam\nahBVddVUjOx/K8FFLVw87QybXe3w4Na+TKMlgvxRZQFURwSqAcW2/lF74SSj\nmZxi\r\n=TGBv\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ed7c1d653f3dd16b4ebe189258bb7b3a08053aa0","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.61","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.58","@balena/jellyfish-logger":"^1.0.17","@balena/jellyfish-metrics":"^0.1.79","@balena/jellyfish-environment":"^2.4.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.0.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.101","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.38-links-index-deadlock-ed7c1d653f3dd16b4ebe189258bb7b3a08053aa0_1613456198665_0.02993719335935663","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.38-renovate-non-major-38c42070489ecb0a340c68e457c7fc234444bb4c":{"name":"@balena/jellyfish-core","version":"1.4.38-renovate-non-major-38c42070489ecb0a340c68e457c7fc234444bb4c","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.38-renovate-non-major-38c42070489ecb0a340c68e457c7fc234444bb4c","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6070bfbc8967a115ace6f2ad12675760c36a642f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.38-renovate-non-major-38c42070489ecb0a340c68e457c7fc234444bb4c.tgz","fileCount":97,"integrity":"sha512-OoSRlEvbFmgmzwNFh0VJaquNulI8v0IsyCq76ACYsvE/I8tnP+q/38e8JZXSwdPsCcqR323b8psQcOiwzL+97Q==","signatures":[{"sig":"MEQCIDGh7OhRkj/Q9wwe9WUuVOS8g6IUfkGnW+WRrSswpJDMAiAtHfzr4LxacnGdA1bc7wCL7eNV6y28qoUkzr8uIau0xg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":600091,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgK3duCRA9TVsSAnZWagAA8aMP/1JuECSseTsbU58BSQGs\n2EgqhtFQAuZ7ZtjxSM3qVZOoB3VMrLfCpeSyukPQ4uwqNkYbn3XE16wiBhCO\nurIPUVRhdur0ybDNB0FNs3s4LMMqihtlhaV9+JPg0Tc9GZr9zexbpnSpigGg\n0r7Fgzmf78EwZGU2Qe7Aw0sTJAmqhOvQLan6JVv+UQ6GxMQVLl1FuvlQwRGP\nX50DX/622ZhkxOYTTg0q5MOczOO5tqpaL6ZJYm51SbSqSORmGOufzcYpLYCp\nMhbZ6GdkRLzBIEuOYtf/U7fS0NednVbgDJ3M0Ud46iJvblRxlhTElw7rxxWL\nmoCJKzan3R1WRotjRIRQT4ewJ6vD2mLmc8p77BUUoge/S+kBjJDFSmLriTgW\nuLjZyuxIr3yjeTMhuRA3or4ZDfw18YGJm0U23mNArb2rHCL+Hd6KV/0MTWTt\nsG/BPq6zz6PhipHqtLTzjQUOj1tblUFy0JmXmMLHcW7V1lpU9eixYNM2HuuU\nySFhWp2VR9imdlAjOeda1Rne9hY2Gaqcgz7EuLdouADt09sHVQ55v+tPIZJg\nVUtvItOFi4JLnBj8I2FNCI/Yt4rtsZLfnYmLRlOeVPhdbrClJ0tCPYWrbk6a\nECZ0WFiDyBbmbZKak92zASIt+jgRGOhz6W6ouChUp/dftDAdxjIXSiLvnww+\nOZs9\r\n=dg4s\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"38c42070489ecb0a340c68e457c7fc234444bb4c","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.61","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.58","@balena/jellyfish-logger":"^1.0.18","@balena/jellyfish-metrics":"^0.1.80","@balena/jellyfish-environment":"^2.4.10"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.0.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.101","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.38-renovate-non-major-38c42070489ecb0a340c68e457c7fc234444bb4c_1613461358321_0.8794743475824249","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.38":{"name":"@balena/jellyfish-core","version":"1.4.38","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.38","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f6d785f77d99cf5d4b080f32f0a4c05858172049","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.38.tgz","fileCount":97,"integrity":"sha512-btd/pquws/WLhTUwrg7u+trsIzXT3Z5QTvbeJe4iiG6wupdRKfiYoJtZ5k5vUxji7SgQ/MqbNlUSgRM31hrRUg==","signatures":[{"sig":"MEQCIA6cgGzqYgK/yV2SKJPs1Kemv9xj37Z2oo1SL8Gq9h9KAiBFSpGcoXHOHMFwr72S7huCJWqTQT338gopnhhuSq25Bg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":600031,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgK3h9CRA9TVsSAnZWagAA9x8QAIQCioWMA9F98YW0rtV+\nN158rCWg9Atp4ya3dLxE67Jrc244hxntCDnnizLA9VlRajpP/K0K8TvJ6Ezc\nB64Jv3Y5nmN9h78j31ifP+eIurD0+bLbIBmbUh7/2YjprS3GkE9GEW+Ut1VF\na/3DeFu3SzLmPIhBs0UMyGU0lB9TRCls5b89zFKnd98QqOuvoxfKz1M6uYeH\neY78JsYne4y3TD5rYTasqtkIF3//FS6/5Nt23j7s4mVDJri4jkX3+Bfk+mXx\ngQqZ0y0agqosfMxgzs3xSC8omawMvjuhcYLUn1rq2zfetPA1cvsN+9AXpOed\ningJg3eIuIUxWiw5xzL8Gx0mCxGgvKP0mcG5SU0KQcmg5ktvXlGCe9CFUOWz\nUnliBvDpeRKwRGIcgPfvmYdkRRs+5N4ajv3iyEYa8h17DNEWxVm/n6S/cVGJ\ny82R+cK1D5aKIUqljF92YPUO3EvcmDbTXtUOqM/J2xA3mIMm1rmfhlRP9ZtO\nCrv4qRkpiwEy1d2lrF4RNYzxSLY12N4zBoeURSXsfr5KpGT72TJe+qRVIKLu\n5qfhE5GhcYui0dHmFazlc6KLVO3Xpkquq4MmixFfih5qxaz8o/kHUml7iq2n\nvC+ja5Q3qJhCh5kgWAxYfGbePtO0jK0jpo/gj1vBpA5VkqSLFZ1NTV2y2BlU\nAaDO\r\n=eGWe\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"bb7d01ab637a1cd24e813766656b96dec6d8b607","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.61","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.58","@balena/jellyfish-logger":"^1.0.18","@balena/jellyfish-metrics":"^0.1.80","@balena/jellyfish-environment":"^2.4.10"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.0.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.101","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.38_1613461628564_0.38179230555024657","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.39-renovate-non-major-489ce933ffeb5b1e4fb694a8c421ba35c5742ae5":{"name":"@balena/jellyfish-core","version":"1.4.39-renovate-non-major-489ce933ffeb5b1e4fb694a8c421ba35c5742ae5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.39-renovate-non-major-489ce933ffeb5b1e4fb694a8c421ba35c5742ae5","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"27d9da612891d86c4ab3c5d9b7f6350e6ecb464f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.39-renovate-non-major-489ce933ffeb5b1e4fb694a8c421ba35c5742ae5.tgz","fileCount":97,"integrity":"sha512-FHCd3Um+mqK1P8X80zoFeU+HOxROL7r+XP5R1ieB/ckoSBCKumuKJ8DUwBCuzrtIoWuMsR+EPArECE9qvZifPQ==","signatures":[{"sig":"MEUCIQDrOId/r8M7lu1W2xqPY2SGMUXmNZ15umq/lE/GI+lxpAIgMUIaWQXj/Nz6+iek9t0fipd8ag+xUAKAliVf2hAEp+M=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":600194,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgK7HFCRA9TVsSAnZWagAATxcP/2CjBtPAHZQH3Ct+uHSv\nDA3ctVabIItQNkvQWOo7LTHRaSRu0FdBCqORBtZ6L2sxwf9NX/r6Zr6kiUwP\n9DuJ/3o3weCUzc41DPH00h85v9t/R7SvxXaVmVzmrQRNdQIt+dYMwMK5ZaTb\nW7eEjwXqgHDRVffcDfHW92haZRqZHR/vadZut1P5H/ch23QPlFtcdQHNPstu\nLb+Zr8Ek1OY0lw0Z+q+wVz8bEDTFj43QQ2LyGFPppOQa4I18Y+whJ3pg4zyW\n9X5dYXeF6jKgxdxVmdoUN6FPqQaTIrsunBH2LFkoNdwi32iRxS7KGaCIW8hg\n4D5bC6dd7L5kWQUrvUc4nAYUKlGIMajwoajXWS8fEWcdobE8ienZqoqMoye1\n5jxEofE3HTRojTKZmiITE7FnauEQ2j+mDpFmjoGIeP2cc4AdowYEhSjQdRVp\nKFvN/sCOmnFVjv8K+66gukHPB71F4MvWEz2W/rrcXehgDn1jiAcWSkNGk4nf\nugjyti8GZfdO5PCvtU+EIHU2OmAZahQUNwU9R1CBYkxk3TpJJ2jFY+mrxuTE\ncWHqDSdSKZYUfztkm4frTUJCH5rhOCpxt+r2b1NkNXDZQVzUY3wHL5ry/hiL\n/H1V2/j/No6miJf4+/OTyJsBiAfHq0E9u+b8ZHg23YHMZqu4k2uZuVRqhJsA\nhThL\r\n=N0ug\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"489ce933ffeb5b1e4fb694a8c421ba35c5742ae5","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.61","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.58","@balena/jellyfish-logger":"^1.0.19","@balena/jellyfish-metrics":"^0.1.80","@balena/jellyfish-environment":"^2.4.10"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.0.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.101","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.39-renovate-non-major-489ce933ffeb5b1e4fb694a8c421ba35c5742ae5_1613476292697_0.027087637230418737","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.39":{"name":"@balena/jellyfish-core","version":"1.4.39","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.39","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"eb947fa514a563fab6a51b2978439637d70541e0","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.39.tgz","fileCount":97,"integrity":"sha512-4be5xgEbQbOE5RzWUICMGNJYzH7s+Q1/7OOxew07ZTxq8Tv/tP1zMYTvdmj10UglVJWdjwrNk6xYIGuUekZ8Mw==","signatures":[{"sig":"MEQCICGDAMho3BckOEtaL+jM+5dJkd8fc2+w59bCLV1XvlLGAiAf9GtlwZowmRSQFFHFTEYAs2OKcrql0xoM0jfjjxSesA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":600134,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgK7L7CRA9TVsSAnZWagAAtMEP/1+Ni9MM2KKk4Zh5KOfQ\nA8vTOoXfXrPp6kMZMav1c+klYOQjN+Wg7duPSOjKU1DaarkKT9gpa6d57hNr\nn+4fof9AWLZAI27BSrVc95353oAA4oHSfcjFmNmWDnuwO8JkkXeZeP0GlzDW\nore5b7TSalnxQ+mmvvZKJB+fTytDCMpM3BQR2GL+cIw4UgiM2vByyttgfZnv\nvUiQskNbKP2CTwIdUEA4QC4rLTUUpTxnaDU5fB/u1Ijdt58kyQbirbl58CQR\nEF6wajL+3nYccX5FXeoFpQWijYmOsFUBprC6zvqj94PtFaFJ8TWEgvxLdDCw\ni04cuthUkdhxZ4vUY34pBl8kR/E3xC7bhPN/JGZAemLKLo88HlzIYImHmjtx\nkbxbXNVfn68XuvqNN4XXbZswyDlFEHcDCZpXYw7t/VPNbnANIJ3AYZmZRzL9\n0dZTK+UK6zBgOvJuSwwhgtut7meJbOXAqg9YbbjaGutHbg2n/q89gGps1MJ1\n/wxTX8O8zQqz5uWG4qbO5QLieZauJGki7Y+/dtkx+I0FxHh2PBSMOsXjauln\n+PfNc3f2pkJbmiyaNJ+hBISECss+Ycbi/LxrXfr0akHwmbrc3uJ/hME1nlG+\ndmWu2d+WWF0EH7XLkPDMTN+95iDEBZZPnPPZtbv8K2X8QWLlbKbuuwHlk9H2\ndFPC\r\n=K880\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a3d0b5eb3022ec206187a088c94fb46c8871d6f9","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.61","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.58","@balena/jellyfish-logger":"^1.0.19","@balena/jellyfish-metrics":"^0.1.80","@balena/jellyfish-environment":"^2.4.10"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.0.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.101","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.39_1613476603337_0.2828489846714164","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.40-renovate-non-major-65ec93f733a45194633930935b56c6d0c256c3bd":{"name":"@balena/jellyfish-core","version":"1.4.40-renovate-non-major-65ec93f733a45194633930935b56c6d0c256c3bd","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.40-renovate-non-major-65ec93f733a45194633930935b56c6d0c256c3bd","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2e40bf9e5f3be24b864dd1af67baab3f587d2dad","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.40-renovate-non-major-65ec93f733a45194633930935b56c6d0c256c3bd.tgz","fileCount":97,"integrity":"sha512-clNlisISaFfyALEFhkUwspoHFk+zILXmibhlIL75ceH2kQqsXLQ+Ra1LIekq6w+O/dkNvDcU1g5l7a1rlFWjNA==","signatures":[{"sig":"MEQCICVu2ZZ3Rv3CnQrTV5+QLHwJPtXYnTlrX3Uc+Du+Ch+BAiAoxKeE02rb81fDFdoxwS4neM8xnsuexCtLTubCzjfOlA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":600298,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgK8K7CRA9TVsSAnZWagAA4MoP+gLfJPh4ugygyyyg/qIc\nKHFOvND5NCoa67Z8QCNfgbxHfmBEpM8mV+1rGNdHiHl/d02jxLdXGey6nDW/\noT4v2jzieGbkLDg0rJ+i8SAYhDHgQD96O8INMWmLrEVAsVMD6RoKslkLzk4L\naVjwrIGL4CzM8gWk3NAYuOeHG+h2e8xKcVE8mgNI7N8h0Ie1VXVib3EmDycP\n5cix+AFS1SIkm+hAJEduKxTA78cOOjVSjTREzqwa9pWsVaKNI5BxSOqd7Jnc\n4Cl7IC9aokSWABd59AJmZ/uneaG9x8WNuneOqqjWrfhjxHpHewBLDbs3JYuQ\nePFhIXCr4Vr8XySmZ9yDVM+65mRQCLTDQxAx4tP7dCZRHFx/KQzaXu2ZMbju\nUGOKDK+nc9yYKLGXcvw6McgXvZtv+yX0CpJgu5ayjKUpRBUtdHR2+oqf/rb+\nzgaLqfgTdjKJLf5dI0hX3lpwT74EQKXOiPaftCgmSzYNRNhk0Za9cTOtfd2E\nlwnG/kUsiXwYK9C1ObyFXqLmw4tSN+oAPKZ/XdyuXTfe2B9HVAI3K7JBFPAZ\nO0+Nh7Nj9bfPfDTf/WVX/wKAoCHrQmtcwrljOuZFYV7EKlJU75jKBEyhn1Vm\n0rpjKP+ZqkygHdsCmcJUiU95ag1CdVxqFmBQLYWUFgzJgzFzNiYBLWziTZeT\nfrYS\r\n=BWWR\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"65ec93f733a45194633930935b56c6d0c256c3bd","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.61","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.58","@balena/jellyfish-logger":"^1.0.19","@balena/jellyfish-metrics":"^0.1.81","@balena/jellyfish-environment":"^2.4.10"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.0.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.101","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.40-renovate-non-major-65ec93f733a45194633930935b56c6d0c256c3bd_1613480634533_0.9432932446873208","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.40":{"name":"@balena/jellyfish-core","version":"1.4.40","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.40","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"918ff1ae16656ee3791670cf6be0a34dc3767392","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.40.tgz","fileCount":97,"integrity":"sha512-AeaJCjjIqhi1akJnQU5C+ndHA263CZPrwWIwTEGcqSgDJYEcvH0he61Kh2IFGKF+O/0OsuZuZFY86vM81Dztxw==","signatures":[{"sig":"MEUCIA1eT5RbueT0S4Ns1m9XQzEKvUm/iZKBNxHOmtcdSZi8AiEArTtgMd4+0eCdBesAQyCw16YlaDvfTwWE5RIT8qmoLm0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":600238,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgK8P4CRA9TVsSAnZWagAAv30P/igm0ipFj401vDBppyuw\nbgV2K4zaClhbMFAGH3Z4Ocos5RMgXw9/E4XXjUZU1HRJwRs9kxOV0uhGy4os\njW1bOZPviRdCulzP+eLVDCBofaFyh9N5EgNTWhV5uA2c/JS4J/cyUjUI7In1\nD8TzGlIP6dcGlgAbaDJefImI/sGVLiIIrPMfTS65XTAWU9yYRm9KlzXl8/4Q\n/iCAq+k2CpqzAk0N4nJiRt4gxTheB7xELa8Tb9khwIJ8peaSVSQj0hHNqmvr\njXp8a/tv6QAol8buyt/+gvCFyZQaszg+NIQzi6jwq67C85N20gxOkZbnQd6p\noIKsyPEp0JQx8Rkni72mHe5uv5DTdOO8RXUEr1egD0FF4GKqZV0xGy5pr5BS\nfzrDnKMnKtFhuKtBIBumif1FXe4oB8fJPbFcSj5Ov0qCUTblafTy+pRwD+y5\nH7JVO4XIw9BKPg/Ir5qcIPGspJNOVqX5EG4TViIemdDUjgfl74e5qE5ji5f4\nap32/Gv/dnkN0W0fDOAj4f2z/fiaDxEnzBdMtuNQYdRUlnDzIiXeIFKUoHL9\ngeZ25l5SMPq4swhr1bkUfnyawKAnFDKe/khEkspv7wPKDkJ6i4oUNR9Pfxce\nInqi+ss0dKYwicutf61ailo5IPV8oUGmgckOACNbWev3D7f5uiYpwvnuJClt\napD3\r\n=IXPI\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"15a615e452ee77061bbae4c01d5d88671a1861d5","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.61","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.58","@balena/jellyfish-logger":"^1.0.19","@balena/jellyfish-metrics":"^0.1.81","@balena/jellyfish-environment":"^2.4.10"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.0.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.101","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.40_1613480952044_0.10652109109884922","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.5.0-generic-oauth-f430d26247bd2b5ecbba7d20c2633b1d52cc2150":{"name":"@balena/jellyfish-core","version":"1.5.0-generic-oauth-f430d26247bd2b5ecbba7d20c2633b1d52cc2150","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.5.0-generic-oauth-f430d26247bd2b5ecbba7d20c2633b1d52cc2150","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e260f39aead2595494b5d60319e0379f0c815253","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.5.0-generic-oauth-f430d26247bd2b5ecbba7d20c2633b1d52cc2150.tgz","fileCount":98,"integrity":"sha512-h3zyaM7H2BQpkpZKr1L4CimUXzHEeS8HEGusT0KeDuTvg5COd2D281tH0E3tIbjWEum0qhfkwpzR3XOEhRh1nQ==","signatures":[{"sig":"MEUCIQCN5SGuu437+9S9XnoWindE29MhwQpZDOc2zm5VLOvf0gIgJ8eLlvwz3zr3+DLkrFMnED/sRsrdlza0IuanKmLgp3A=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":601210,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgK8QXCRA9TVsSAnZWagAAWFsQAI7imkk2YU3ws1AmK7aJ\n1OwJgTz0K/wrs91KokwI2BVZhwkSMZCy4TNMXI4kei2eZCNBwxl4t3JwjXJ/\nAQMKelPlskHlBi5p3xdQqPmXMkjrUN/wLOGm1XxpU4JjjhhD/+T46ePTj5Fq\nB/CU4OL/DMsUNOnKWyKxmGVsX2LNHn0etBqB5OceRoHQUqlEcA3oLnoop4tN\nM7OSD3J8LOdLJhbpBe0ds4B+HU/h+kb13IfHWoqC8UjYJ/YdA72gX68w5phm\n24GMnpqTB14jNGfdKbH8Yhyrfhpozn0JXwZnEfc+jR39UzBtggyp2paaMLy2\naMfDFbNAPOdY6HVWRAFyM0yfTU+VFbCk6KaTHvQwPbxoAn937qpcHWTmCBPv\nJBjT5Czn3AKXIhmm1ooyeGCY+g6O0Gox5WkzhopBx1sQT2K2O+nIYJ3mLR0e\nypzHDYMLupHhsfzrPLJj9Y7+2pqBDwX0Rmn5ZgP134i76V+M7OX9V5HAWcww\nov105lg49FY9i2vro+wp8QlLm5PMTw/UYWM32J0NCE1/4ZX3JV1/qEBL/9QR\nI+tfnHMV4YjzQaPJKfeo24Vv3w3/ZHqkpMjSQgmzC92WsMRUcP3MQOyBnWPq\nzvezesAzOo/R6HNo5/25DJ2vOUiVXu9oHutES3ZywjhQUAmwIi6hC0pAzWxf\n3+aK\r\n=hZnO\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f430d26247bd2b5ecbba7d20c2633b1d52cc2150","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.61","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.58","@balena/jellyfish-logger":"^1.0.17","@balena/jellyfish-metrics":"^0.1.77","@balena/jellyfish-environment":"^2.4.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.101","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.5.0-generic-oauth-f430d26247bd2b5ecbba7d20c2633b1d52cc2150_1613480983336_0.6878694061093102","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.41-renovate-docker-non-major-dda67c292f0c8a888208be6d1913a4504eff44bd":{"name":"@balena/jellyfish-core","version":"1.4.41-renovate-docker-non-major-dda67c292f0c8a888208be6d1913a4504eff44bd","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.41-renovate-docker-non-major-dda67c292f0c8a888208be6d1913a4504eff44bd","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d5fb9699438291e09ef1db4fb0b158ae0dc5b85c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.41-renovate-docker-non-major-dda67c292f0c8a888208be6d1913a4504eff44bd.tgz","fileCount":97,"integrity":"sha512-eZsTix1vPkfAXWJFAJ1zOqyAVjCmoNrc09Aq7b6meXczB72dqkK/U1LbojEkXXgGIOWLbZQETxVKs2Qa5GmcjQ==","signatures":[{"sig":"MEUCIH289cKFonhBoKF1Gh2jbd3M8Z+HY1BXRFn34BTymYV1AiEAui+3kJhehpP/Plo5CFYP1AuSoIPWrgh7ABUQWuERVkA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":600384,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgK97GCRA9TVsSAnZWagAAqT4QAIaBdudEq2Wr9nbmeMzh\nk4qws2LbZrLcVtx+kHA6PtJAhgKEWQvG3Bj9KZDp2NXN1QeovZOdCFr/x08D\n3l1bTVhxXRLbo1nIZ15tSSIhhyRs4vToVvG7oRc0zbsoXGp6WHcfSbPf9dQe\n7VE6ZU8krX+JLRYMv5gap3ossZQ3ryLeI4S/8d6vPSQNIMYm9aUbR6sENqll\nSXv+fFHIk3Qx5QcD+VNOojv3m9ZVy8k0H8Afqn5rb94lEILosBk7ryDgfOVk\nMTSZEoCYynAJIcGF6o8ghe7+3wVT/qwsHkT5ol20PLJMvr3i7wKDM7uT/L5H\nCMGUBhJhdJz8AfZ0aD5h4k8xU08IyVVi22GOXZp0rXXqJkc/ZBaUWT1cgwNw\nwtbg0rYIyXJWfCTPr7qq2h0s9Qx6Q0d7jd6selj7k5x0mAqqCN+qyTZwe5Aq\nGNfYxj3zL45yvSQ5gnCZKVbUSo6bCJaC77Hsus98vwNt3LfLI20au3TW8YlM\nv2DwXE9BVgP64/fQiWaXRMOJzPVNkpMPndG3vkXdcfisKcEwZIDVIOEeCQPb\n8rM7u9bSw7BQOHawB2JXuYb93z4Zjm8/jQ3MB+sPRf9Ywn1+Rh6iSezilO0Q\nFGr7KOu5vn8AE0KS3LNAZx0EqyZyV15+JiLnPTiCh4V4R6X+LGTEIWfcR2bQ\n+5zu\r\n=8rrz\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"dda67c292f0c8a888208be6d1913a4504eff44bd","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.61","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.58","@balena/jellyfish-logger":"^1.0.19","@balena/jellyfish-metrics":"^0.1.81","@balena/jellyfish-environment":"^2.4.10"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.0.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.101","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.41-renovate-docker-non-major-dda67c292f0c8a888208be6d1913a4504eff44bd_1613487813788_0.0032950957912913914","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.41":{"name":"@balena/jellyfish-core","version":"1.4.41","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.41","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b630d7a54aa37f1723ded28f591e61d23ca14f2c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.41.tgz","fileCount":97,"integrity":"sha512-eAloXalngCjWoH652/OX2DVVmBqUgmWV02YtiR6c9EZ8Bsb3hYFjBGTLm3REUOe6AUQNE9KMwcXTdnlX98WqVQ==","signatures":[{"sig":"MEUCIQDHJ6+4ejbOIaKL+Mh5iicJ1JUwko/VayZ8B7boKhR61QIgLoFuMQMT7YekUP82j9A59X8moACfVQVAEJ7TJgR9gHI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":600317,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgK9/jCRA9TVsSAnZWagAANIkP/2Xv7MdAQC3XkHWbd7c2\nnqDwXfp+irMGxj6l3y23vH/cmVHj+ejZHaUZQ7VX+j8C4VS+VxfcJQ1y408R\nailBCRSdZptzNBtwAPNB9p7eALDZvlNDwlmnQJjQ865Ay1iTpReS5i3LPwLl\nTGBb4Weq2wdpLddcE1TunuX5snIgy70YUX/Uwvr/edyJLTlyWQCK+piEQEc2\neXqRe65Q66FiABwcg557rhRU6/rKtvxGI3OOxNv6USDQgRLgh9Z5oq3gkX1N\nGPNFlOjxoiB4Zwi/nT54UFHz5gd0Hi+e1B1TFEAaLDu36QU/F5RzHLseIl8U\n2YIJnhZhbyn52YT+zKYp9pOOpxxpaAMcxcyMzRUjPVIKgfqNEL+VT9wDOIZO\nzekKRVyQz214nKBiX0f8vPNqyNc/0yrvbScYAwH4WcD8gxcKrQWpw+/YlRRP\nB6bR0Y945YXCBQV4N1NMhzISibJw+g9T+78boRPgZi1K4wpvD4k2LZz+r4Lg\nX0/FgThSuOZYVxJp4rZG+Ua2zV/PSeL8s68qzfvh4f/xnbUOTNelfClCZ0kv\n1M5ijmXEYA5PLPwwYGwXlNwqGpSpwe7RYmksnGG9mfzylU/kR5ygxeAA7mQ4\nvE4Rw+wG9MXBvVVNgkxezZ53FwI0BbrkK/fB4aqKysVrJok1NKuh8Kfw2dkq\nBKdN\r\n=tXBy\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ad9bd8e781d5ed22ea4e37347cde4c831141ddc4","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.61","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.58","@balena/jellyfish-logger":"^1.0.19","@balena/jellyfish-metrics":"^0.1.81","@balena/jellyfish-environment":"^2.4.10"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.0.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.101","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.41_1613488098729_0.46560292358437927","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.42-renovate-non-major-cf0cd445a2282889e377f2baa66aebc53e50574a":{"name":"@balena/jellyfish-core","version":"1.4.42-renovate-non-major-cf0cd445a2282889e377f2baa66aebc53e50574a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.42-renovate-non-major-cf0cd445a2282889e377f2baa66aebc53e50574a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3a0bf423b50acda8d5cb36734aeba9c4704ba598","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.42-renovate-non-major-cf0cd445a2282889e377f2baa66aebc53e50574a.tgz","fileCount":97,"integrity":"sha512-wIp90KDQrswal54lp/0sFR9UPUNgIkGqh/e8bnG+56eqTKAF23vyJhKPMWSSOeNnwqY9UxH3c6+TodVtaEd/UQ==","signatures":[{"sig":"MEUCIBINkbfCy6ig8DPRjSADCgxOnwXaIqV47dK4CEhkTTw1AiEA9p6quwF6+ohQYEIloeXYmkpudfEt+VocQtptiMoIq5s=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":600443,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgLAIJCRA9TVsSAnZWagAAmWwP/jvgdUsm8R5B9AhQbOaK\nsseZgoXjiVzzGrvAMFyAt6vXlSgUSUI/QCTP75wpcPXbw3VK00Y6Gb9IJdNu\nupaHnZvAOADq92E7cn7+sweur+/RPp+jgToAGqK7G6lZC8S2dauOeh9IB5pe\nZcFOlZKZpT+ZJcrg6cV3ApKXavzdvVfyYHIg6hZ89W/TB8E5ArlnHb7dtXtK\n9WElgcZM4C+3+KDpyFmRF9shS25xzvHQ3UyQnyxDTVUDaGiRLvfQ2Qg3mQwT\n19Ova3s/I78XIGv5o2a0u0oiqkbZshcAOjODgWF6Vgg+CG1xkm2taJ0mgo9X\nSvdl2ed8Hcbu4W6MRo9/Ml7coTkScunhISXRs0usSCu6hyEs3uCcrC9cS8Uz\na/0Jp38Ef0XEgOKOG+i+sjI+Ti7omAme19D0K2g3tjm/Q0a/q7xlGcojqCkj\nM/qIcSpaXwPwWnfPdGV+M+GBkWR6NyZJZzHBDgksIB1bdFAVwbpol20DE7ty\nSHShJ6tzsXlfQAn1c8md8kVwFwsgIQyQJtlVmrgQTk/D7CfwCGJUKMIOZ5a0\nlskl8GEUh0jrZXP/AKxtKg8YpvI/6sudQAH4NDZG5eEVs9MuqMrJ4cmd9LYp\nWnIyKCGct5ens/PtERiz9WVDMvrP4li2z+OBiELoA7a8ub8u3aEDfLqGXkAl\n3SlS\r\n=hK+X\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"cf0cd445a2282889e377f2baa66aebc53e50574a","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.61","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.58","@balena/jellyfish-logger":"^1.0.20","@balena/jellyfish-metrics":"^0.1.82","@balena/jellyfish-environment":"^2.4.11"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.0.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.102","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.42-renovate-non-major-cf0cd445a2282889e377f2baa66aebc53e50574a_1613496841235_0.6128184062235755","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.42":{"name":"@balena/jellyfish-core","version":"1.4.42","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.42","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b543452e7b79967a64cb29dbd6f90bdc0fecad56","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.42.tgz","fileCount":97,"integrity":"sha512-fZTL83caKjjAGWvfbztne1nhRuFy3HwrEYJCsQR5UQ2DghDuVUbLA50P1krsnQ4GL2oYer8xoQZvOwO/0YkbOQ==","signatures":[{"sig":"MEQCIBYvkHUxOWpcaXYfDwWgJqEsj2eSQb3clsIOr6n9dI71AiBQ2HoU7lJ34Y30dR2q9DAv+GP+9DNgpiDM/YtJDLoK1A==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":600383,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgLANYCRA9TVsSAnZWagAAJ3MQAJG+SO5zLF676qhOrziL\nne5ApFo9hXEllQFN8VmehRzqOyVB6D+v2r3Z4aDMM9RHb77nD7f93EXImPFN\ngXZk/44BGoNYCFI3AHhHOAXdbze8X0s/VT+YcmK6hRzZBFHIMSfZBzPZOvFP\nwtecj9G+GLY9WGYlNTsb4dX8/E7TKAx8NSzwLMTrSsX+7Uo9k9hTR1ns5ztI\n/xyyQAswvR2ic9nPJ2k4xxzie1LtjtweMPNeyxyZGyvshrs4yD5USb31ECIR\nuFWvmsSDIyQxT8/AOpeFZT8H/FAuxp0E6G1Lb6a1v7zPJHiSyEKBwN1x/kp5\nnHRFgGGH6/jb/zKKNnKFXqsYHp91KoraQTmOydR4PMffG4IhfoGGBfL961cz\n+HCbk9ehEt1SvsshVVEfApzU+vPtrH7d+UHKpqc2qFiyQN8Q7/xXKgU0NwlZ\nWXh9DlaOc/4C18nwi62TAquzshUf+9emMW8Yu8KoZF9hHOZkANaOWPxp3lQe\nc1sXhSX97/uA28Vn+0ufE9s0icw7XjcaANaFyERe8RZW2PVK3DkL36farNza\njBPzdNUo0Qz61NTuQnPcI/GE7JfJ3Uxvex9aiXgbAGXc3YiTw0GW9ee32cDA\nB10MMDtpLNUzUKQbsDMloHB+4tFB/FHK0eJ1BmBo0XijVjKhyV/Q11wrb5yn\nU0z9\r\n=p518\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b5e88896e1256ce473fe175b8cbaacf2f8874db0","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.61","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.58","@balena/jellyfish-logger":"^1.0.20","@balena/jellyfish-metrics":"^0.1.82","@balena/jellyfish-environment":"^2.4.11"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.0.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.102","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.42_1613497176207_0.5154879100946073","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.43-renovate-non-major-294b268c1cbcd428fc561f13fc49dfb231d3d403":{"name":"@balena/jellyfish-core","version":"1.4.43-renovate-non-major-294b268c1cbcd428fc561f13fc49dfb231d3d403","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.43-renovate-non-major-294b268c1cbcd428fc561f13fc49dfb231d3d403","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3cd2526a430f90dfe949fe0e48462453a4f793bb","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.43-renovate-non-major-294b268c1cbcd428fc561f13fc49dfb231d3d403.tgz","fileCount":97,"integrity":"sha512-xyNrG7kHiAi/p6163EUlaekOySBzWWk7nImpEkGMdousnp/MH5WB162h2uW6VDEpLGvQY07JUAZ/bNKoQZClsQ==","signatures":[{"sig":"MEQCICnMKtZnzj7M0hzddhuPaYH30NfuL0QhPmPFN58DOfxSAiARs+bqQlSVRwz/SErJVSNem3XcDBIKS0fRD2fUEwjcFw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":600546,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgLBeDCRA9TVsSAnZWagAAAL4QAJSg9SPBUUkP3pCRdStx\n2mSDM8H+wcbl0xKT/RYy+scKkDkpGRELbZ9no4oFzqF7izDxohCc2N37Aaw0\nOfzkWjSeFhePjCbee4nhlRvp8u2znu51gb6W1EPSW7TL1ituQsXbWw8D/EpV\nruXUN+PXEmfK9KMhIgllqshEkbdeNgZDjpRDMexkWvgcYondD3ZGbo5gB3iB\nJUBTDB7hqwueVocmZrIlbPhVHCpkzgKyarSKNY3RhDZ9AOvwq1Q3Nm7FlmfS\nP4G2uT2QE7c0rFe/8BGYr6Q9RegAqIwFXfEHDwJDQ9LAv5RvzoDK0NYBH+Q/\nZzZBmOxbHXHoBHGWA5WjHZqFQoEarKC1vBxzSN12R+AWuHeO6aDsRnOrcft4\nMj3TgCT6tnZmsbjBowUveyG+7AlGygOUMLoaD73gp6VLIZyGk91PQyVW1w7S\nGgNxrUA/+HrMbyiohMvgeTccG0rcOlObSQiFuBJOz17dLZ/DJLcAuzHfO4tt\nqDcN+i8Gcpp48cmS8Hwc4hNj21IJXKymmrpSIZbhiwBwqoEUJGASWDIeNXbj\nkh5gHSKDWsk8/6jCyldiPoBN6BLsBrZ0RwlcoUwoRDunA5P2lqKlHoBWw1hY\njW7Z6GI5ObXHS3dNBdaWG63RIIagKrNi3QmZc32yZcKrC44BDIVROzfaE5u2\nzdEg\r\n=62RW\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"294b268c1cbcd428fc561f13fc49dfb231d3d403","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.61","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.58","@balena/jellyfish-logger":"^1.0.21","@balena/jellyfish-metrics":"^0.1.82","@balena/jellyfish-environment":"^2.4.11"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.0.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.102","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.43-renovate-non-major-294b268c1cbcd428fc561f13fc49dfb231d3d403_1613502339108_0.6875142559809782","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.43":{"name":"@balena/jellyfish-core","version":"1.4.43","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.43","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4cb5f022c93a12e3f1ed0ba32d3860639c20f817","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.43.tgz","fileCount":97,"integrity":"sha512-LT/5ZhWRcZpacn2RHp9dIFlKipixypHP5YqPC5bf8pnzQ3lM8zbvyQyNZ2F/cQin0sNKOm5vc7Cox3Y9E19LLw==","signatures":[{"sig":"MEUCIQCP9imeOeh8vBQQduo1cNCLEMLpPmWRDIFhN6czPbvwxAIgQuqxMX0HI16sfm4nsQguAb9fRv6mPvUIP5VLVnvJvts=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":600486,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgLBi3CRA9TVsSAnZWagAAjp4P/iBYZ2c8VXo20Sjx7dvT\nZIqfGWBw354wWQqhO+m5YgdWj7NGeIIG0MVcqLssqlGMfTuRjp0B7jczSVo5\nkTZ4gQ08OwQ2V+Un88Qj8P6bn1mpkvCXSvxYZ1mIz9GT5CcKwro6FTR7rk7C\nO/bfdepR2IGBt+LXJJlHm/swvzEsAfc7IfPKz3A+OgIJNJhMLgmBlkgIe8TJ\nvhnxwiwdzhxHSeEHXCNsQY0TCi5lrKApCoxryC9/Os33WOchSqgj7lKWbXlz\nG5eXOW2sKp3UrdeyDCFTTl0gomM//OkOleH4cxdD7bGnk/f96fEOEsQ0HHNd\nt+p8LRZxWwJDx1YZdAbCoj8EErWVbZKlTTSd464R/ifvZxY6o3VXvl1AQnzM\nk+MMFD2tIqYvHZTw4EgrV67SAv5cEzP88rfcwZCWchQ7B+AjJBtYPAF1zaA8\nc9BVB55IIuNz3NN02p6NW+NifmXru+NOACKgx9Dp/WBnBpGFauIVrwW0dtQB\n1+L6OWLbghJcwQaTTpIHKnVxoRoJVUm8wcAl7JEZ7xGVPrGroGO3QukyNaWj\n2oUkbLi6P7Fw1Cys8TTxeAmLccHFzoTdWijIsiBj/Fb/a8SQWRsFuBmLh+eT\ncE/Hr9EN+C0/BeiroNb+Wcvdx6116GcasB1vqSXBshMA1D/5aB5q86yyY0IA\nOL0R\r\n=b2rA\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6599407b77cf2bb9ee9a7af2927cc1f92d625583","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.61","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.58","@balena/jellyfish-logger":"^1.0.21","@balena/jellyfish-metrics":"^0.1.82","@balena/jellyfish-environment":"^2.4.11"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.0.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.102","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.43_1613502647296_0.7666407754824771","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.44-renovate-non-major-28aa69d2f0fe70d18c51dfe0eb7e26181c24dcf2":{"name":"@balena/jellyfish-core","version":"1.4.44-renovate-non-major-28aa69d2f0fe70d18c51dfe0eb7e26181c24dcf2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.44-renovate-non-major-28aa69d2f0fe70d18c51dfe0eb7e26181c24dcf2","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e8c29855e6709ec96bdeafafd3e7d539c229a192","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.44-renovate-non-major-28aa69d2f0fe70d18c51dfe0eb7e26181c24dcf2.tgz","fileCount":97,"integrity":"sha512-HrHxlNrpEH5IJEiojfIbQQJADBmWFb7SM7LtqGgpMHg0XwI3hHBoNN5Elme5bYnwaEjJxMpJQwWZ+qjOpIyVbg==","signatures":[{"sig":"MEYCIQDNK3FL17LHgflL74wuVDEK5ZZUU+DDyXjNat7iGnrhEQIhAMn/mm+4Mi59lnS8E1r+1Ey8s48sqG8y2XyqvpNnuw+q","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":600650,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgLDCDCRA9TVsSAnZWagAAP+0P/is9tWfjslf+NlVT9lm2\nQHfxCRGDBw4hY8CHmrpkUAXeHB4qSkbQ8gp4FqaKSWINyQHf9P+JP60iMz4+\nFmP4/j+5lL8vC6B790wOD8KTnRy/AXESpSM24vVRyyP0bS5NspMosTY+TpjP\nARMe0NUf9bZgGgl3VpoeinkZ+2pbeglPx5UvY2waECSzIUryt8qTegi6iPfW\nkpoU3qnqijS5jU1qblTWFrsR1WbDYHTDSSK1PN2ml+qM2wq+WMoLr6fY8x76\n+3USMtJ+mezY3Fj6tFUtpvbXhi+NQYHD7u6wnxGrEVlq+MEbv7RYelsuPrr1\nzvygpBkJzs0EUTt0xnJ9RKeahonlN8aT7xtzpJlr1eF40AplolUhQWm/V3HA\nk7BHVTHN9+R8GFwE/vhSeVvULg7yGVrWrQ2RsmQ4+SS+lIRWEMycDyrBRr5P\naK3o6CHK4TyodNB1/BzMwE+JZy6TMXjPqZebQarslsUEeSjTgRG7mWxw2CAr\nOKwigV9JL8rN8lSGcZiEW6or3TVizOsemKDdJOtSHpwed4UAgBoOm0cZgzES\nvF6jOyNi/YHFtl0mG6R+oy8pwrmKu5x68w7ey36QE4g8/c7sM4rOhmPN6SlX\n4xc7cLa1gTHHzQmfdyTCyQzEnPwOKPFlgmVNHwmpmiqTTomsvxMa8PM5Cmde\nXhJA\r\n=drpG\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"28aa69d2f0fe70d18c51dfe0eb7e26181c24dcf2","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.61","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.58","@balena/jellyfish-logger":"^1.0.21","@balena/jellyfish-metrics":"^0.1.83","@balena/jellyfish-environment":"^2.4.11"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.0.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.102","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.44-renovate-non-major-28aa69d2f0fe70d18c51dfe0eb7e26181c24dcf2_1613508739016_0.4059592784399133","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.44":{"name":"@balena/jellyfish-core","version":"1.4.44","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.44","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0df8dc0666d225e911791d1ba93489639567297a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.44.tgz","fileCount":97,"integrity":"sha512-m8Bh8WCRf5DQRXYJGC/WENctgRmyjmF94kdEfWFAOVK8srDwTl35FjkXc+rZviUQKlhFNEp9b+yqlg3nlhtxLg==","signatures":[{"sig":"MEUCIQC/07sU+LrBtMfymPBlve+HsmzrUiPbT4J3qCgtpK2N0wIgbZqpxovsyQJPW1wGz1JEdDbZ52GVWqpHqpig92gBDck=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":600590,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgLDGqCRA9TVsSAnZWagAATfcP/iOVmDQ6zHJLgNILr3X2\nhFozdU5tEojbOFI1UTMiZhXpdG8N4n5E6plcezG2MBfDKI/pbCcviGbYyILC\nP6UjYXgrOO3BF+1r+C+x4vkJpDlkgYzY8JQnuF1z0Zo5KJXTCSnNUGO6lTPn\nrOQTJWB5dcR/I4SA+BQ4CSHKNHgZzWV/yIK8pYXbRqoPVLQ2p8Lf9bmbZQBo\nB5Z+EUiHPx4JipoBkvMj3W3Uy1c0CToAKVgW7Qm4JalbIUGi1MvmCMH3UAZ8\npBzGlVdpJSBTzlB40dWWd4GntHzyzzlQyGkxehnQ2U6lmR270XQwUfmG2W+5\nuZa13o3OYDaBSn+FgF4Nu6KfcvaQveiJSp1JKFvBiMiX3376RMmrYOLq22ty\nTWwEcPV6EuB7i1GENu2b8uUwC87B+EEAJ4SH7R4c0wWxmibroiI/ZNw9jac6\nzLg7ybl+EKOACVC/PsZxaz7H4iq2cO32m/f4ByT5nfgBbp79puw6w/1vGNQJ\npzWcckml0U47XBk6owsl4z40YGdLigkRQsNJUsZ3+660C0VI+PVk8u5gH8T5\nvqMltJU/SmEaw+FIssP7vxlfan5JTrmmMfsTODy36yDG5ayXNaVsgRe6gukp\nv+o8aBpPxDYL6aQlFN1rIItBQLhqxsgJpChOuAV45SHT0zlvI4CDev4CEvUS\n825h\r\n=ZjHF\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"df79806f7dfa1e96adae08b7bd6bd1fb3470dafd","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.61","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.58","@balena/jellyfish-logger":"^1.0.21","@balena/jellyfish-metrics":"^0.1.83","@balena/jellyfish-environment":"^2.4.11"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.0.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.102","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.44_1613509034001_0.8690892840903712","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.45-renovate-non-major-2ba811273b37ae1173ed01a02a594490095ea45a":{"name":"@balena/jellyfish-core","version":"1.4.45-renovate-non-major-2ba811273b37ae1173ed01a02a594490095ea45a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.45-renovate-non-major-2ba811273b37ae1173ed01a02a594490095ea45a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d4a90c6d6a78e8d338b9ed4c9f401b2a4c08624b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.45-renovate-non-major-2ba811273b37ae1173ed01a02a594490095ea45a.tgz","fileCount":97,"integrity":"sha512-oGDGOTRo6jKlIkILnti9ixUJUiyutP2i/YDxKMz4S+JdkyHxm4mQIeZsUbl8WoKVfKzvE63XpU0u7KcUh3ORpg==","signatures":[{"sig":"MEUCIQCHyjChWytNEBpiAoEO3IzGe7XI97mBaURZJTJwgpl+6AIgNlH9cgLpUAnEWJah3/ezqTrckjlpiA/84USdYn3mkY8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":600716,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgLGH0CRA9TVsSAnZWagAAV5IP/A4eAd2E0lWHrfvczzYB\ncuBdKCgqtQZEzSoZL16zAVnaynYJypTM/ujYuSoa29uzm97kl4+t9JA3OkcE\naLh2SQRIFAfzcPpErovO7f2Co1D+holQOXkdjXPJY9H7Ok1HpjygRm4HUAwx\nYJMjfi+ITVVYzuJtMBgm/LJ4xAqSn83lO1xB8SlUt0i4jHGET5uAH/0d42Oo\nngR40CSq0DuA0QGwc4r61OGdUO6UY4pBRNOPU0ehC/dU/69A/5NYByWdBp0V\nmQ/0JYImQUcYvkmiYdBzPrbhPEEbcneWw0f5JDJ7nWm7QVdrdQQVUz339gYE\ntOFJD2ay0PQnbP7OPhW40Yf7Pz1kFyqoMdzs4LmbPsxCzsOdnJZB1/N98GkU\n84opN+m0kGlfh1v/P1HMczb0nMaCIBshO3QZQ1yGqOAdqkUUM/zOjRrzdpDH\nX7uu6/Sq/WISPMUNTZS2M37oYXj3vhnxwmLa1dq6OhoBDicSsA0tYfm7FHYs\nrGpF5XLjeR1fhBCYTiD3KJx0QvvHiufU/KsSa/agggrOCuvbtDBKX6yQAot7\n3/MJHy6QYdmMA8LuE8pf4u8MsBhy3Hv5qKgMfvOXcseBZ2Ia7RbS2TJpOLlT\nnTqXQ5t8jDWQA6An+Lhc4aHSyxUcBLWkPPOvmBn/qdauRbzPw8QmGAeoTYpX\nDYxe\r\n=/iwK\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2ba811273b37ae1173ed01a02a594490095ea45a","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.62","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.58","@balena/jellyfish-logger":"^1.0.22","@balena/jellyfish-metrics":"^0.1.83","@balena/jellyfish-environment":"^2.4.12"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.0.2","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.103","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.45-renovate-non-major-2ba811273b37ae1173ed01a02a594490095ea45a_1613521395881_0.6424865085271567","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.45-links-index-deadlock-0459e8da7a823d8c40c2e83cb9e2f53fbbed6b57":{"name":"@balena/jellyfish-core","version":"1.4.45-links-index-deadlock-0459e8da7a823d8c40c2e83cb9e2f53fbbed6b57","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.45-links-index-deadlock-0459e8da7a823d8c40c2e83cb9e2f53fbbed6b57","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"7422e139a7e6ba7bf5c74c26ff4014f3969aa66a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.45-links-index-deadlock-0459e8da7a823d8c40c2e83cb9e2f53fbbed6b57.tgz","fileCount":97,"integrity":"sha512-eaC0/bKYnjv+71qFdb1vYyMl+C06288dvWaGxTrhUjcqxjEqWwmcWpWnTMHdXru7Lx0vASdHOF01LJliDuuilg==","signatures":[{"sig":"MEUCIG4EukR58pt/evKeYpPZyDE8B9VV09XY7DaWIr0vCl0EAiEA0f4sJSLlAXH+ZE0L7Az6Orcnn6Ueankw04qHCNoqsd4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":600961,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgLGNaCRA9TVsSAnZWagAAnNMQAJz2d2nCFoBpHB2GK/dr\nAYyYodM0YTW3HT+8sWUDpIIBEZjiYVWxCCtdVws3hxuBnq1uh8meMKap80CE\n3N72FsrdC6Em2N83lToXYMZT0JqUXzZapomEQEWlmXBS1twO7OIuf23SsvWo\nEY4h+L+/+Jixf/d8Z2Zd+lqF94QO5hWD0CHbwM4m3o8ckb0BXKrorPbAueh0\nFNVq3jftN51YSjZt6C5hPMIYBwuHKKHkhMo5KEqiJh76aZMg7Ktv37cSHmrc\nV1zBdc6vB0z1K1c1JDPPWIi52O7mNDauqIwfDvS9cVhpZcFreaFS0yrvyeaz\nskSFwgqoosp4zeBTUWufI8E8ifTY7BVkK7fv6wvke7pHRHFCpl9nRZRwWiX6\n7ozUzZzna1NNwfLN4vAhdIhWlSWujhYJwKmbxtKjMXq2mSblNWCQq33TnwVG\nH9Si7ijZgcyWw+7xBhN/GlZrjPLWX3XnL8qFqfvflk9focfthUz/tzib12uJ\nUxXPpKoPZsW3KI9rmuiGonNYN4/rJeHGdzz6fUSGvSFy/qZbmetx6J10U7WV\ncFzAgn3GFthYj53wN0LeuYmoynfK1Kl3LYwTDvbFlKbaSC48CbjXVPX2FvOs\nj8Svs5VwUWIU1IJKgxMmI3nFi+rgyjPZ0yK5iiD425sxeu+PULpNiGNUZLO9\nvyTI\r\n=ZHLk\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0459e8da7a823d8c40c2e83cb9e2f53fbbed6b57","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.61","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.58","@balena/jellyfish-logger":"^1.0.21","@balena/jellyfish-metrics":"^0.1.83","@balena/jellyfish-environment":"^2.4.11"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.0.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.102","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.45-links-index-deadlock-0459e8da7a823d8c40c2e83cb9e2f53fbbed6b57_1613521753915_0.802669304838515","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.45":{"name":"@balena/jellyfish-core","version":"1.4.45","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.45","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"bcb36065c36ba445d4673c82e816c3bc9111df00","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.45.tgz","fileCount":97,"integrity":"sha512-bE8eUdjWK9XtGveDjDVSZksKrSLE/4OZEFFVLlhQyA3KiFu/kxmFnhb57GI3XSHtU9h7jYmaZRo2jadDCjv4Lg==","signatures":[{"sig":"MEYCIQDQmH1Jq0BvM6H2LqPosgwVhQPq1R2AKSwwPAfwY7kzcQIhAKhLKan5LRjnf42bi7KCDsx/vPNnbwIh90a3CcF/fNrL","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":600656,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgLGNyCRA9TVsSAnZWagAAjbwP/RLcz3UyHzsVV4+V4r+G\nVvuXqMPgfyl7FdGN+YATW//E90OH+DTvlXDtQygdzHLw+yb0D7r9VRH9rTzo\nG2PLCfCwgGb1jV0aLC4B5+VH5XU4pBzvVkBr1Jk7Nz/ZXnonw/UC9TytciIA\na8pCVcGlUVKjdWz/ul8LCV9XCEHxVsNurShkZ8dCdARmDEY/C891IcKyrpTT\nMzA9Xs9toIm27ezdkc/2fRNQnPtUxFaSY3u1XOOkLcdyevL/e1U02E+hv8L9\nDQwgAumEmkhZ7st8L5JFNfYg0UNp0kocQhtmQEnP7qISOYwtD6v0TtTZPpM6\n7thAqyat86wh8SLmy1Lr0HkKGpWCX2dTpRnvafN4Qvr0Z51rwqY+ExAj/qz2\ntdUYSu5rHqggESTGH8x9AeO73tYVmf64qrRq6Vmq/pkeGu9xIDzht4N89fOm\n5Dxzyx0o0K43OK+nJwb1aLGDCdBsLFMJPfRVANdjPjBVIqMUEMAKGu17YmNb\nU2mxA3uqK44R77b/jbyq6Ysgs0Hr7jsDquQz9KqXWB/3n1ii/ZDN8kltivBv\nYK6COdxO4kgyM0SblI8QehnZrtOL8iom9ikAawCD51uFTWWUsU7ozHoIMasc\nlWplV+rxmwBtCC7ok8pCCJRjG7Yw7l22OcyBN3TmVwCw2ERKHwkWylEDhI5Q\nTM+T\r\n=Uppv\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"cf2850653433f378e735dd7835beb6d04bbce614","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.62","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.58","@balena/jellyfish-logger":"^1.0.22","@balena/jellyfish-metrics":"^0.1.83","@balena/jellyfish-environment":"^2.4.12"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.0.2","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.103","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.45_1613521778027_0.3533875182243216","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.0.0-replace-date-columns-cf9f2d83ee3968391bedd7b7dff29ba64a55650f":{"name":"@balena/jellyfish-core","version":"2.0.0-replace-date-columns-cf9f2d83ee3968391bedd7b7dff29ba64a55650f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.0.0-replace-date-columns-cf9f2d83ee3968391bedd7b7dff29ba64a55650f","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f49b637feab0d6188bb718f0c0a3ed4a52db2558","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.0.0-replace-date-columns-cf9f2d83ee3968391bedd7b7dff29ba64a55650f.tgz","fileCount":97,"integrity":"sha512-9GEouNQ7R0+ZoMqVsE3CqdicrP8fi9Rk0++lTP02XGylo/hmXZWVMcgsjBNoV4n60fJ9UBwZI09L3ZG8z1rmPw==","signatures":[{"sig":"MEQCIF5OIPHo3p6r96tNUPoKa5gDA1waOaAZCrLq1k2duQuuAiAppeqcugZ6OMI44Fl+P9Tfr+lmiT2D+V9MFoBvkA+HeQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":600794,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgLGRECRA9TVsSAnZWagAA/hcP/ROMLu3kLd6NN55eatdi\n2RdLMp4hCJixO+Q/V0H24DUEiuD6n8J18fpc5Og4Kn0rUA99fkZXEIJT+lj2\nfS+QYv9NuRRNuUvkmpLLGZQrVaFgNmooVb5Ctr74YPWVuVk0aIP/3otN69CH\nmlh1Z6MbmboG+Ph0thNlaJvoIWmbCPT3RBV+hj11OpjHz/7+47m6EMYgld/u\nw7p4U8fQkMuIYRdCvWBVY079EfSyXQEPK9cLe07JmB6hBLa3ooGzMtRTA2fK\nTNmVNV/I1kuzAqpIsE6hy/q1hR5+IqxObWvf0wUJy9zY2zyik/e+FeS0eO0y\nkHyHb0JDwRjAh3YKpH+UMtzORp393wVCexJjlUaxl7VVGgHX9bJpVxgf1Yv0\nwYLcbHAxLSjXTq0Sio8Iuhrh4mKeDWOtlhR17QyrWxREeoRB3KDwX1h+LOI3\n5QFKCXZkTZi5yuQ7tQLFDdXS53csK0m2KF6YZRVvvqdtJgVkVu39pvmN9VR4\nR+aWgCEf48rLeT2e6YkBHqVZ1tPGERpeAK7mH5O1oRdvNJ3RMXYG1fTKJ45l\nzyzLJ/logMGn+AnNy2TzqZMjpvZClOrrKGqL9SDISdUSJsFidymEl1h3KsMf\nNYXTiowKmjvxUwaao3Fyjx2CPMyQhPkEYDAiScUVTJMWZV8W8lw+3/naLfo4\nya7W\r\n=4ytY\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"cf9f2d83ee3968391bedd7b7dff29ba64a55650f","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.62","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.58","@balena/jellyfish-logger":"^1.0.22","@balena/jellyfish-metrics":"^0.1.83","@balena/jellyfish-environment":"^2.4.12"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.0.2","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.103","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.0.0-replace-date-columns-cf9f2d83ee3968391bedd7b7dff29ba64a55650f_1613521988053_0.6238904329418375","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.46-renovate-non-major-87d4616296be111967b88617003242d48bae8644":{"name":"@balena/jellyfish-core","version":"1.4.46-renovate-non-major-87d4616296be111967b88617003242d48bae8644","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.46-renovate-non-major-87d4616296be111967b88617003242d48bae8644","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f1b2b5d61bbd1cd863bcfc93b97ad6988751748a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.46-renovate-non-major-87d4616296be111967b88617003242d48bae8644.tgz","fileCount":97,"integrity":"sha512-gnIwyUBzdPx1dMmQyfRC+f4uJ2ritHpyu9eDWjcZ/HcxDly3EhDxBTX3CI0it/jKi1S5mW5/zu4NMYPszgxpVw==","signatures":[{"sig":"MEUCIQDOxuvK2l+wCOhk2RWrfPR1z2leNHHeUGCNMe/YZvTqGwIgNP3aXQuJHfIpSOzJft/8WQBEmpNbCq/Ti59yC2lb1dg=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":600820,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgLJUtCRA9TVsSAnZWagAAiSMP/3/n5dDmMoRc69L9JrxW\nuEg1QkJmlliK+6Ai2s/TUE75dCJBqolj3vbbeuMMULgJ7XMBCA3QoIp9k3Ah\nJ7SCTPk5DGdhaqXDeY0nSARGqTluQkI97pnp646aqan0WvhNhJe6Zi9u88XL\nrQ8EJrNy2RVPAUECrKR7srgRTOZC5lYBqZOZtvWtQGFqiEcvpPHwKbpl92ir\n4gpK3459kcMbkfD0ti2r3VkS7mG14tF3Ftn2e+TlSrHjnSnP0zIluo+Er7PY\n8O+t8kH9HoDT1VYJ/YTHwPEJ2/eHtR4VfuG/Lynag7+Cp8i+MI2/KfBc9VBN\nJdSLnf9N2xmg+rB/W7uWP3XvxTMyIBYnxSChpJHfak1m4GfyHYEa3RU9oqrJ\nDZ9tWrfpy5iJyuubmGQC+TCOAq3JIRqtCyNLkOoAqGl0vNExMTUbRUPYRAAl\nAQY7++w5y3t7bny6SmsqeY0/iYhg9Dlmy9AVtpwTrq4hnKLq0IgCzO+ylfOT\n7XPBv7B90SLDuyoc0bbCpUNny8rDOfO3J3WuqCaNOTWwt7pJ/5b7x3w7oXwk\nd589Mf06D/Uwsq1QrM3LiLt66+wyfKxC9Ii3gleJyCEFIgXDr4CktWd1ufL/\nlqOqXu7tfg88nIjDu/hvJhXFN3RmAp3QL/JfpG40zufA0SHnILRLGfFryMDb\nSvzS\r\n=g0aW\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"87d4616296be111967b88617003242d48bae8644","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.62","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.58","@balena/jellyfish-logger":"^1.0.22","@balena/jellyfish-metrics":"^0.1.84","@balena/jellyfish-environment":"^2.4.12"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.0.2","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.103","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.46-renovate-non-major-87d4616296be111967b88617003242d48bae8644_1613534509072_0.13141884282694183","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.46":{"name":"@balena/jellyfish-core","version":"1.4.46","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.46","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"495c9ffbb5f6112d9bc892ab961118ead920b9f0","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.46.tgz","fileCount":97,"integrity":"sha512-48AggACKJg1VdRcc7XAgMRcCpOD8W43gn95o9X8CP3eb4a/801m0u5ksVbk3VcBwNc2n4lTlE0k9nQjY3H85TA==","signatures":[{"sig":"MEUCIAeGmc7ILzOu0F7rk0gsm96l8KxcivZjj+c5iM59WPVcAiEA+AOngXoydBkSwaffFswDYubZgDtN+3orJ/zrZ8ECNxE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":600760,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgLJZgCRA9TVsSAnZWagAAEZkP/A9v0TPTzowNBLls4RIL\nGsgtm0JcJe4KhqZZZl60nBIzZokBxwupF1nlyoA+UVIBB0GqpkDlSWVp/4iC\nexFhXKJ3KTzBnzxWd9EfWuerAoHlfs0V9c5tzv6oiDywjdVo64Ai1GCKAZ+f\nIa5+tpQGl13NkJ9tGV+ykmziLaG2og3ZVDMPEm4z1Q/zBbIwrRkzo0WdhfGV\nupBB2pqlk/qgFy7a7ahRvm/DRjEABEEyAcjQRhMIcQtSdUAt4b5ZidqNqpIu\n3QH6D/DHBWejnJs90ZqARVPNmgRZozLV9Q8OKsP9GhQHr30fnsqOVwyp3WWv\npdWElgi7j4dDjB5Ri+dini/drpWOKcuMoZlKTCSHLpFg3YMf95WQKCa3RfH+\nuUSny4/uznSER1hD4tHOK6MngWX7XdUbnWibHYHmTrFx20vJCDv02opyavWL\nHVlaZqCa0pc0h/cJ+fxiv4y2a1Q6DIBKbAQaIcti70wQ4r19hJbD2E220l79\nUP9a6Z/ltemcAaCcUU3TsquCOhnEOwh4JYeqO8AZfisBly6dc8Ao24VhYeC/\nCIE3+PllVo3ZuIpLNOBNT+bbEVR0r69718TCbU8l30QyHUKoekkXf1Taz+5e\nM+WGaZY4W+/4itU0EgpsRU4dqIKDsLrln8FpGQYiL8VdOuONF2RG/GLBP/Ql\nIC8U\r\n=I4gk\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"cf01ef7cf9737854d7aaadc7a91e6dfe40ce06e5","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.62","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.58","@balena/jellyfish-logger":"^1.0.22","@balena/jellyfish-metrics":"^0.1.84","@balena/jellyfish-environment":"^2.4.12"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.0.2","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.103","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.46_1613534816398_0.2848870633318008","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.47-create-index-not-concurrently-80ab3500eb8cf22e69499007ccb22487fa56d1d7":{"name":"@balena/jellyfish-core","version":"1.4.47-create-index-not-concurrently-80ab3500eb8cf22e69499007ccb22487fa56d1d7","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.47-create-index-not-concurrently-80ab3500eb8cf22e69499007ccb22487fa56d1d7","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"12eeefe6d4bada23e68842ece83b137bacad366c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.47-create-index-not-concurrently-80ab3500eb8cf22e69499007ccb22487fa56d1d7.tgz","fileCount":97,"integrity":"sha512-MKyAl2Hnkb/xiX228fQj1CgkpUMmzYpAeNfZzuqqj5u6yUG+AtokRvZ/mR33G8i3vkI4UGYlT+Abh76ZJWFZig==","signatures":[{"sig":"MEQCIFvZzUAuuyz6TZrbp5+rzLOgZ1ebbuXTR7jtDxp0bHIMAiB1oBcRRwh9BLnTig6ncvQAigiZFftV+zgk6AD/VG/q9A==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":599991,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgLKBiCRA9TVsSAnZWagAAiuYP/jBAOtekPUylAWsvDrws\nwc2XWTggJDmt4ZuFtLqkEOZUOHdhqmxFRI0/esPM0huwIL6fyPU6wctMpEcf\nsGgcrtesCJWP5TBchcwPB9XLZLsuWlOWAcunj3s6Y5ZVI+g92bd9mrYkQJ9g\nB2yU8VfeYfG47rwmO+lVHvrzuK9OuebPA2Lxjtrfwi8yuzZ5+ihDtKvTEjUm\nuThMQVW+AF6ZU45F7U+tQb3AiSYaAmS2gg5yGcOODxImcnZM6C89VMX8Cwkh\nP+oE554pvJHgqcCHkJZYwhaQXplWJmy97vTzcQ2z+7m2RVW1HsA4t5cBMI6i\nm1FbgsDxHR8sFT3WJdrl6n5X8qOy6tmwDCYHsaSt1gCKfU+pVrrSfH0jo1Hw\nReTr7VQGKRdOP6DFA27jYsCiFJ6NoIbSCkilEoyVg6JpqjzdQwW+SA/xvlpI\nujnSjOPhb/QDaYLm2/X9Rpdxg7BDQyN8c1Jg8Fme9xcQ4rsDtvvO+ET3Xk32\nqQqflyy0PR3//XmzDK8Aglc/O7RxgZeizf7yW97ZHveqgp39z+/2621gmTl9\nqejMIoo9uIcEr0dIC6JIFwlhKMvNqEisKjuu8AqETkMHFzo+L6YiTjyuDr/m\neM+qWd/uwKNS8gMoTyaYCxpEPJ9hgDeBKumhM0xGoDRNS/l08tivKfzfDoqg\nB/ic\r\n=LY7b\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"80ab3500eb8cf22e69499007ccb22487fa56d1d7","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.62","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.58","@balena/jellyfish-logger":"^1.0.22","@balena/jellyfish-metrics":"^0.1.84","@balena/jellyfish-environment":"^2.4.12"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.0.2","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.103","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.47-create-index-not-concurrently-80ab3500eb8cf22e69499007ccb22487fa56d1d7_1613537378118_0.9038790128153527","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.47-create-index-not-concurrently-187f2ab560a35a5b927ce8ef2bbe720dfee34c40":{"name":"@balena/jellyfish-core","version":"1.4.47-create-index-not-concurrently-187f2ab560a35a5b927ce8ef2bbe720dfee34c40","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.47-create-index-not-concurrently-187f2ab560a35a5b927ce8ef2bbe720dfee34c40","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f24fc814d0881dd55dd541c078e6c817b636344b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.47-create-index-not-concurrently-187f2ab560a35a5b927ce8ef2bbe720dfee34c40.tgz","fileCount":97,"integrity":"sha512-z83OPgXp5Tcb+ySQd8Rz7DXNNfBL5vBbyMQ/B0FJxLTh5lr/bENFEeDBXkAmP2gSgCp352MrL3WBJixb7HKUSQ==","signatures":[{"sig":"MEYCIQCMy7BHdOBOrYtdySb2uGurunS0MQclI7ioYeyDA9YgagIhAIBtuvlgV0+ensOs8b5RNSuikhMgh+JePnY3GdowXFFI","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":599979,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgLM95CRA9TVsSAnZWagAALSsP/iX5YHEyWMd7+O6K9SQ+\nhD/gaYhqDNSa6IRuV0JQ4G+sDmuZRuNFVbflLIRaYI0vYjSQ3RGjEFHb/1Qg\n69N3S6u5Z3eEM8CJbgp04xfv8BlAmlCnd0v/rtiYr5y0Yh2fjlC8Ml3PJAey\nO+z4RpHZE4CQmA/fEJaFDRKzfv0IxNWxAG7HuK2Zi3j/Xi8S6wpjIUVh8R7z\nz7/4FEyWshzDB4b+Somn4FGENEiq+/o3vHBwAIhn0FU/GqC5lzQl4dcmA7Mx\n818tPLRqg+zNaO+N/XJVfgL3P4qY8tLhITmjnd5+2uatOSWk/VSBJ5SEEb50\nJvlqHlhoiEc5APN2EcpUvHyXly6Z7LQnU4k1VbxeBLhKwrh+E/+OKPwMmaTL\n1LuB9+bpAjGy/cyU2J6HS5g3QWQDNvu3dqzKcNDCkgE9VjShdpLg/Q+6OyiR\nm9aKuip2LaBmxz/WFOFHJO9+kO/c2StSK4hO5bjMN303t5TxB4UzndPLnzUg\ng7mSg0UqH4wUFLlql6arnUXfzKfBAsYhMkMzgnODMB9Q9pET3XCKMhoXaeg3\nt/jQKfxmUGEezuDrWPSABGpVHV5DiPaNxd60tyxpUeEQJCJeOPxy8W2An63m\nxHfAv11rxzvbcRPVTUX2p97vfKL2R8/69FYK1tHNZCk0rpzpa7qzB486TWfw\nLe9p\r\n=5C8e\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"187f2ab560a35a5b927ce8ef2bbe720dfee34c40","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.62","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.58","@balena/jellyfish-logger":"^1.0.22","@balena/jellyfish-metrics":"^0.1.84","@balena/jellyfish-environment":"^2.4.12"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.0.2","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.103","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.47-create-index-not-concurrently-187f2ab560a35a5b927ce8ef2bbe720dfee34c40_1613549433273_0.8168261922125235","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.47-set-upstreams-5a9d9389d730545ecbeba32b2131fc776f6e4682":{"name":"@balena/jellyfish-core","version":"1.4.47-set-upstreams-5a9d9389d730545ecbeba32b2131fc776f6e4682","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.47-set-upstreams-5a9d9389d730545ecbeba32b2131fc776f6e4682","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"aa9c6a094cc6cebba2ee9911e2f55d795a45b0e6","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.47-set-upstreams-5a9d9389d730545ecbeba32b2131fc776f6e4682.tgz","fileCount":97,"integrity":"sha512-dzbUgwohqh/k6lboXaaXmhkrm8inSvvYRCvkSWS7QTz3Hn4pALPqZoHXkvfnTkWng0T0jeixuKZnzbYuVGjC7A==","signatures":[{"sig":"MEYCIQCM9GrhVM3bGrOaJzlwC4KimiPC+hmaaWWM106pyl1DnwIhAOEGhgYdJ27QzctTW/vjsZg7xYWypOITtKIHYXwxKAXg","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":601429,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgLNp8CRA9TVsSAnZWagAATkMP/3ZjlMrq3GHuGR6b1vod\n6srsaoGIVulwArbnMKYqUuBilSCoeDmLsNRrWmPwzWUuB5ytIw9UYTGOGKlL\nsW1T14YRgjUxvDYI1eELKZWV7kPRAZn39DPZNdFsbd4yDY4S0zGW28fCo9gL\nwcwggd/RDOSyWDgb0FDjY1KX3F0bnB0MmcCnKQEZ9KAx5n6F9gz5AKsbD3G5\ne6LuYEkuWta+6lVF9hlEalQrohXBQicLgddkGzqXNHLhRfVi59EIgYRm887l\n34Qt0iSHCguIi3eewwVPFHnKEYovCy77JtkSJtCN/jl/QSSlORYhrihqmaHo\noIsTBTjzw2TEdb8GkT8zb/EgLoZginuOy4jUXVT3lIgldCV3tAA9qtbJTdCy\nZ0kgppJJm+HOcYOp12cO2u9kyIU8kwxd5GlcGHw9cB0UaR4eqc9hB5XhHQFt\nRWCm3jmACTpm6us2N1FXQnRVJ8PIMHfCx2G9nDLSRnWY2ChAyHC9FafBYGlO\nxGWokY+RYfkIbSMX16WuofLIrIOMR2gE7bJphosnaqUzugtNYEgClaMczK+r\n2aI0bkE2P+yH/3xeNBWH2vgQ0T+IkG3ytNCvMie7QfU5sLaXj+0uTLuuCR+D\nLT7T/R4edM8oW0jTSGAgfQ/XCr/SycG8M36F1ZM75yFr0+mz3o2/LJj78l9J\nVXm+\r\n=/+cv\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"5a9d9389d730545ecbeba32b2131fc776f6e4682","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.62","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.58","@balena/jellyfish-logger":"^1.0.22","@balena/jellyfish-metrics":"^0.1.84","@balena/jellyfish-environment":"^2.4.12"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.0.2","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.103","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.47-set-upstreams-5a9d9389d730545ecbeba32b2131fc776f6e4682_1613552251558_0.6129305205475657","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.4.47":{"name":"@balena/jellyfish-core","version":"1.4.47","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.4.47","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8b3c12a91dd1312d6fd864be3cff30010f9f4f51","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.4.47.tgz","fileCount":97,"integrity":"sha512-aL4YouQ00btEHVxqiVJdch2vUdv4mBy+Tgcco5jgIomrJ/LhjFwTgWOTPxl11nqn8NnG65L81/Kr+1fYnwtTrQ==","signatures":[{"sig":"MEUCIFMzSiNqQXqwztAQjorJvjc/GTzMh5pYrBE8Y4owMsG6AiEAlz9MBlgrDWd6Rwf6lAVtENW/3xdq4VTKbiaFCR1Ndpc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":601374,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgLNujCRA9TVsSAnZWagAA8Z8QAJo5CfkpjIvF7Bp6yiuu\nPN22zOwU0TfrN8YLerO1wYiRXmHLy8EemgsuKb8X7kSiXlzimq+kyBLNEyjy\nsuPgFaGi63xoLnDKD6wMctYZPk7xaoq/H1LStPQ/4o5+NsMuuaxvz/R5Sri0\nwrZNdWVN6rc1plvbYPbkMeYkaxKIOD3q2XMLq7e+E0E78iL4J+V/7zTm1QXk\nsPbF9c4V93pHZEsL/ZmzkcXgOshitNVIa9YnI/MyX8VEHMBgDAqS7iTnm4Vg\nhcs5OGqJYB5gRFZNVcmYsSseiYsfmzeVsHRT6nwPsCdB1kbk+3Z8+pGmXnIx\n0A2JrsRVCxw8+iJe1bYtw/RmLuHKN4piDE1FKGdSjMx1z9fwio6f8CeXjnDy\nATkZWx3Tzt86ue8Sx0FhK/Cccr9Ei4HWXj8J0rYiew5sF3syVjRb9TSt+sY4\nPZzCbZRChLCPK+KTXx/EX7wYRG9WsBiAt5CrgxrmV8xcUWACaZds66YKo4Kg\nfRGMxAGa4gdmYCDc6uJTH4mXF/MLNZNNqbys/ex75g9p1CAax+bCmTwr+fhY\noacQagzwnU35hkND//ScWW5dx4810CLnzKoV98TPxu6JKu/YVZvNaMgBoEwt\nGzFfgAHiIkijC0AvdIFLMu0Lq/TE6LEVdRLI0gPfTywhVPsmWuNUS27CGwr4\n/xik\r\n=3TDk\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c99c484958e76e98f6703cb0a9b4ca4d0cc2e4d8","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.62","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.58","@balena/jellyfish-logger":"^1.0.22","@balena/jellyfish-metrics":"^0.1.84","@balena/jellyfish-environment":"^2.4.12"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.0.2","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.103","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.4.47_1613552546594_0.1514030392393464","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.0.0-replace-date-columns-d6a4d94dd6d5e5c51ba720c484c24e75b66fc7e8":{"name":"@balena/jellyfish-core","version":"2.0.0-replace-date-columns-d6a4d94dd6d5e5c51ba720c484c24e75b66fc7e8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.0.0-replace-date-columns-d6a4d94dd6d5e5c51ba720c484c24e75b66fc7e8","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"831b952116aa1492fb5e49fa571240e5c913c28e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.0.0-replace-date-columns-d6a4d94dd6d5e5c51ba720c484c24e75b66fc7e8.tgz","fileCount":97,"integrity":"sha512-Pe7nhu6D4LmS690nVqUYq6EmY+Zoep06mveOQFGntq6o8jtu6jnoWUT730bOiIU7u65Z7hngiJ5ttLdJ6t43Bw==","signatures":[{"sig":"MEQCIF1DAEXVmwvPu677DRr49ocbyigOkw1LDsJocbWxrlg+AiBj49qId3sZKqas1+Zsxt43uFZK1e5P1N8nb5X0bhdB8w==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":601537,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgLPFqCRA9TVsSAnZWagAAULcP+gPN9jOJfUEW3SlKBVeK\nQZq8T/TbyaAu2qum3Vv1x9WSgHPOJ7zelObntqiUaSSjmqwZMajso/AGH7UL\neA4dCKZmshoM+woDmbJW27VApKUvP1QR5F98yE9miPGCpUTlmwy6wrFWG7iz\nntVTNDgO+wHvScpGJo5uTKvdtOI8Q1SHV1uFFM/Faap4OGGRBvFoso606vwL\nF8Kv3B2yVLB9KarC5CEQJbVNhTMnMSJGbPYfm6lu+3NQlnB9ZMawCX89vUSn\nXXSpG6AMUC3YneQKfwciwXQ2yhjV0XHwa0dw0HAicIFp2tkmg5psR/IbzyKP\nD1AMYGvqNPkZCGM2eBu6aaBrv6HDzxQx5FzJHqqfeDggaM6IM1P66tBBpRmf\nwx/DInUrD/s8TWLPDXmXu3kZ9raGdzctHvc2FWjiUwrPUCPbXBxROBiOyC3C\n0se1DSCmCEMBePOOIexk7GwXGnt3PW8vIca5obNVjevurY0tnqnL71vkcwkR\nBxIlPF7+i/xyYBfGLYT627DmRnpR+s6QeNcJE8u4GAT4AUs/z4WK7pXtlvDD\nDaKX9u3ACD+oM9WEk+yx1rEEDnzEZHlHSIudMRdS3/6A1uGxEbiHjsKw1/gv\nTw/EqxHSY7mFClWjGhYKImv92PYpRRQ36lcGiFPKm51A1F4uMjTy8QFFnDAj\nNAX9\r\n=yE4Z\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d6a4d94dd6d5e5c51ba720c484c24e75b66fc7e8","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.62","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.58","@balena/jellyfish-logger":"^1.0.22","@balena/jellyfish-metrics":"^0.1.84","@balena/jellyfish-environment":"^2.4.12"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.0.2","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.103","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.0.0-replace-date-columns-d6a4d94dd6d5e5c51ba720c484c24e75b66fc7e8_1613558121610_0.08321821351121383","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.0.0":{"name":"@balena/jellyfish-core","version":"2.0.0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.0.0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"999f06032ef33798bc61dc89ccacb8a1832d768e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.0.0.tgz","fileCount":97,"integrity":"sha512-xpHc8aLXp2FJzrTOMKmuM+bMpUWYKlJrZIdv9vgUvU/SgUG7nigsZm4HwYaPK3GwZieCWKCNKF3ClcQdrCiqug==","signatures":[{"sig":"MEYCIQCW7oxFxrX5Nz7nFHGPRwyDHGxWVaLsFdnVHSAiGs6iGQIhAINcxuHD6WJ3EoFA0MryogUTjE8rIwKsC0F6ecXXVMwh","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":601475,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgLbuGCRA9TVsSAnZWagAAL1IP+wV5fskwvUeo+DRK7VVW\n44AtXZlFl/7SfCy8t7w54XmDwEfjN9IlLF11uw4ydbOascTtctzGzyMtKKwJ\nYECZs6pmWFMesJHUigD5opC7uhrm6BgVHrNJftYCtdpoGx4L4XIHDlKmebFV\n4dkzeIyz/gjKLbYb+0atrGfbeGR6vEyhST+jovHhhlpzveKrQdnxeCWYKw3T\n3AmchGaTRDCuBv7xeyZS7ByC4xgWsZQAVWWIWiXfsSBjGQhooPx10HXFuMpC\nVDh5d5DIOC8wPn5iOQEmoIgtwLwK4P9ZBGHj6n5+LAe+bU6urY/kOdsBpjWM\npd1WrWJq/VdyT8d3ZLCi35MqlNYni3Ocjb3SYZxIdSU2CGAgHGsouTqFRmol\nyCPy2weZFE/+20SKEDAJ0MumI3FD6zyZJJ77hBARcl/mqagVg7bfFSSZKlIP\n9xB7VxbydF6/Gp7ZPWPSDBny3huzlEMMqPMxTJiY+zac4l/XflTqnlTa4s+K\ne/d3NLZeGn0efkwviEeN9/PMT9kwIL5YGS8GXBN1ovF9c7olJNVikXiLemwN\naNjzeiJRjFQ3kJoVcb5ITP0M8RGHusa1NZKuFFKQ2Pm8TDXtk+TFS9IFEl3w\nhG+nQ04vd/KLMxRLz78l/ayfmJgIuP33TQm2mCMsW8zcioOHm5rL7uANeTxD\nh8Uk\r\n=RL7/\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0b34d8b12ae18ec133b5758f7607f9326d37a2c9","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.62","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.58","@balena/jellyfish-logger":"^1.0.22","@balena/jellyfish-metrics":"^0.1.84","@balena/jellyfish-environment":"^2.4.12"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.0.2","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.103","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.0.0_1613609861701_0.1273384385975267","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.0.1-create-index-not-concurrently-5c9c433e083223bc6e96582171e58bf626810ba4":{"name":"@balena/jellyfish-core","version":"2.0.1-create-index-not-concurrently-5c9c433e083223bc6e96582171e58bf626810ba4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.0.1-create-index-not-concurrently-5c9c433e083223bc6e96582171e58bf626810ba4","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b8c9821297bb7989eab2d9feb5fd13bbb1b295b1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.0.1-create-index-not-concurrently-5c9c433e083223bc6e96582171e58bf626810ba4.tgz","fileCount":97,"integrity":"sha512-zkc4EfH05Xp8fksennYw2cMrZ+3FxIoAk6D4vdNTOLw6Hb7INn2fJBYbaC37aqkVID+pjnSjrxrdKGkRLAYZug==","signatures":[{"sig":"MEUCIBklf9y1R0Rx4bh8WKM0cr5WJpI3X0iA2ad6duUJnPwPAiEArBOPTUmewwHmeBE1OGgRgxF8wW6rd7bcd9HpHJDE3+Y=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":600693,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgLddvCRA9TVsSAnZWagAA/6EP/jo37KhqUsNICEUy6a3V\nefVUQITiu+Ex4apmXmcYcKZ9PWbUS1wO3pJh2w9nVuAizFgfDGl97qLq2f/D\nkr+jZwERMaavF/3ci9CsJgPccZfIO4qFtRvLp561B1fZjBGcCkmu+TWIxI5G\nNhl6uFpgOJqxgZdjdMgRKNStiH8EqxacGcESbKgS/UJN2I32nI5i7kZgzvZu\nYtIFFuwwDqoVVv8gpTpM0CvbjdDgP/FGMOf3SNVLrgcmqF90DFfR+F4Umb9s\nHcd3RP+lo7NHxzJp3BO1MMXqP2V6xvRm3Mr3C5qqWbyHnnF3PbG/p6VKO0wA\n7Y55Tf36l7zqvlxQbe46ianadMeUkmKDuyOgNPTJOmD08dgqGFBgLqxQoec3\nrkuCw1p+/Eep9yFDsvcpkYjVUqdba3gqZ6Xf6CSqvAwzcH8vf9rI+Pwwx38m\n50C0mRTLzWykDTYtDX2JG3ic0af8jFWioX+4Kizcxu9fpqedUlFAZuCTJu1O\nuq3j2VD6vOk1bE8sLAj1Xu4+hUiULRMyB+UREfvnaYojiXBOi94uU3ssTBRd\nqiGU3vU27tH3oNmCy8KOiWxJng3Aw0K2I4Ma5Qi6kyka9/oWy9EQjEMinyPp\nSi1Rfdx7usR3o+UhWzDgJbBEy3RXUjs1EikaHo0x4IWcE1+GJeDoUfdigNAL\nWYhW\r\n=OJjR\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"5c9c433e083223bc6e96582171e58bf626810ba4","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.62","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.58","@balena/jellyfish-logger":"^1.0.22","@balena/jellyfish-metrics":"^0.1.84","@balena/jellyfish-environment":"^2.4.12"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.0.2","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.103","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.0.1-create-index-not-concurrently-5c9c433e083223bc6e96582171e58bf626810ba4_1613617006364_0.0990605648538816","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.0.1":{"name":"@balena/jellyfish-core","version":"2.0.1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.0.1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6569919bd424035fd6e959399b9296761ebeb20b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.0.1.tgz","fileCount":97,"integrity":"sha512-SLyJD/+NmKuCo02iZZXqipa0mE53G350VM5DYWkFeI93OHXu1GXWHGNPGsNhHt6sEbTMiy8RazccP+ymFekqCA==","signatures":[{"sig":"MEYCIQC5xOCw8Ox94+SUCMw5b/tPEoES7wvsi0LB0FeAaKcmXgIhAMiiebnMgI1WinVbx+sTLzKP9aK7wbI8XpsDPCKbXH77","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":600622,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgLfrFCRA9TVsSAnZWagAADDkP/0rtz4pzvFO9jc+7uoWa\nvw6o2uTiP/gw3UmhkzpOqWwhI5Weto7ziyTIHrNtlgd1Kobxh7BjENbN143i\nOrc8macjEfSBqkxl05gRkHwrc7JVmw8SZbW3VwnLJ7z1JJRPKXhXcLQ10nNH\nBnQVrOA8eO8mAVOWPZYiJhvD0SXaMBWsoGE4KhmkJxBevJodDLYb8BXgxpaf\nZk4vFA8SqucNnMNlj+UL707reHUPaX2KQHzR9hNKAUFJQ8Nhithcou7Dx4xv\nT97TMebSQ7DQrve6fGFu7Z14qyARI2VXBUjEbuHmbPaUzAHexChLF+vpcTl7\nuREGPtne9mjrlQKQnwiBwmMpyiGU8CSQCdvdoF9EdwVXrasDG8SY4W6fED84\nYwvaB0swptIOHvvR7E8QAIuhHafKY7397AfJ80JASunAVzmjmNy0ygVb5v6O\nzWt4lROdgyI2QLs74HAPyJWcpxi6TTe91DtX0wyo0N/w6jb4UGBhT4RiiVRa\nQO2geuzFwhlwZHGRgxpYyoj5k2iGwfHFOqiztAW7drLn0JYquFYTHpHQZkxx\nGTIt1RF+Gz+/8lUvLzlKLbXDxwZqeK4hAWdOWYXV1DXDIG1LzFueb0gB3xYN\n5q7CMZDPkdwIOD6kaVkzWEYoVZ+nQPnziMiqX8C+zPc0H/hM/xpR3euusp/D\nl88m\r\n=XWnG\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a860ea2639a6a4b9905e8ca00fc725c05e59a59f","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.62","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.58","@balena/jellyfish-logger":"^1.0.22","@balena/jellyfish-metrics":"^0.1.84","@balena/jellyfish-environment":"^2.4.12"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.0.2","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.103","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.0.1_1613626052828_0.12084146633514026","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.5.0-generic-oauth-b6425ad6fd65add1dc6ed054a70a14ed8678a6ad":{"name":"@balena/jellyfish-core","version":"1.5.0-generic-oauth-b6425ad6fd65add1dc6ed054a70a14ed8678a6ad","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.5.0-generic-oauth-b6425ad6fd65add1dc6ed054a70a14ed8678a6ad","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"35ebaa7c6792e915fa77e25cbacad30eaf5876cd","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.5.0-generic-oauth-b6425ad6fd65add1dc6ed054a70a14ed8678a6ad.tgz","fileCount":98,"integrity":"sha512-ElMOMyZQxwyLLDcISAWkkxvd8rBOQvmjNkUgt+ydjrF6Tkpbu4nmZGenPYp8lC3I19X74iXEFBhP+y5ADNsDSg==","signatures":[{"sig":"MEQCIDE57mE4Z6/7xNLSjxqbt3zdc0TfNCsx+Tuu4Jv2lrwUAiAiCC4v6t69kXcmLBFyX6BCOrqblZkKvl+veZ7LRC9QHg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":601238,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgLiHYCRA9TVsSAnZWagAAiXsP/RojOQXPOokjpl/BPgD2\nB/H7c2KXamEfBe9KQiyGFZsoLuOc7aRphNuJVIstYRYBwu2Y/LFYkty0z2kG\nt5iDGxzidv0t+zax56Wf18fJCQTOXLYEqvFnnmH0GVIM0EZYBvlbyNZT8G1q\nsj7L2MQIeaPrfan5nh1VPkIGDBODDIjcP6Iin6wuNE4PGlReMkOT80qlMWYS\nCXU2v6tEiQKlCqk+skxeVj0C8g8hmjkhBog2o64nXlPnKphpwpu1mvOM4geZ\nht7SHRPDTsMiT/g04z8OPx0qzYi5wLVivMy+gyERD87LNcwa/eFEMOJgXwll\nmM3MkVrq51hEccyt/Q/3D1n1Ls31MYSc0LtwXoQm3lcFBOWNQSqztAWWl/59\nHYlVH4plRTC/CajNvWLoquPx42O+v/u3wf/IUjzjrubPm0yfXfXldug/jgRU\n0TVpI8Rsa/qLx2JTkvi29z9ILEmRcAlJQcW1rvOkqw1fF5xcHYre22SUMyAg\nAbelJEBxCWYZaQDTHhU0X5pzgh+148Vxggs9QChskl2v6tMoB3070SH8VYjE\nM55RDnFHFtjoCn6NhLCWoGzuaeZxWCzbVVedBXIuFeURtHCUcRUtNZOhgUWf\nwe6CNxrXjRNYwvT/FKrJfBH1CKf/esHkqBe1zr3T/S8spbOFSgM/hW0WDvMa\nS17H\r\n=6VLz\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b6425ad6fd65add1dc6ed054a70a14ed8678a6ad","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.61","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.58","@balena/jellyfish-logger":"^1.0.17","@balena/jellyfish-metrics":"^0.1.77","@balena/jellyfish-environment":"^2.4.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.101","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.5.0-generic-oauth-b6425ad6fd65add1dc6ed054a70a14ed8678a6ad_1613636055808_0.592069022075908","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.0.2-renovate-non-major-abf1556e4109657527dfab12a13c32ea7dc8e19a":{"name":"@balena/jellyfish-core","version":"2.0.2-renovate-non-major-abf1556e4109657527dfab12a13c32ea7dc8e19a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.0.2-renovate-non-major-abf1556e4109657527dfab12a13c32ea7dc8e19a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a01ff1a994b03af8254aaee631ef894dee163473","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.0.2-renovate-non-major-abf1556e4109657527dfab12a13c32ea7dc8e19a.tgz","fileCount":97,"integrity":"sha512-5EIkP23wb4Tf+obQ/QVQ9ojixqTPgAcwhWQ8C4lOm/dXSwLbzsHc02N+FmUqzV9P/Fumz2f+fqivLYgblmitdQ==","signatures":[{"sig":"MEYCIQD04FpcZshXcveZLpoNl5tsJJ2t7kgJBpJSw+akbvFZgwIhAPxHSiuqI+U9RpvFNXnp3ZzsbeM6sQUZuC9r4AFR2dC3","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":600785,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgLiptCRA9TVsSAnZWagAAwEcP/R+yf8DJEwMDxWx+0+uy\nqQDFaGqTBVMnxXq7fL731bHDTPs5hwwLdxXZLWYiOUszk4QcWXi50L+f3Yhz\nD4Fw1kuXVx/L6/Rdr5YPnpEe7lwHQCjyduHoXcY3kfBVAeLKlj16d8saVdO2\nWjHf+HU1FEWQa/491zXqikQlr9PYleH9deT0ohQ9AVBAqiUcx3ukhtYT5XPs\nir6iLA24wOKZh0X9XCvDS6ugp5AGytyWzOelimG+rvKvZEwXQCfia7vd8cX7\nICZ76QjuamTwHQbIj4WUN5XXevIEBoGRd7PVOdkNs5M/3vEVM8oO/7dOz6uA\n7SiIazjpkG1mznfjYxl7geltJF+hYV28xGeLZ/FwgB2vx3TSIei8k7VLf2o7\n/hkps0eEbDK4yXnLz8RA4lX2d5qWjxlaZ06tKP3bBPTi4BeAFGC+Vk6fpENF\nQNONLFBOAX1JDXV6RsDKEHeExokO7aD5mIZ/LXVV+UC8f4QoKHQ5N9ESo0AR\nyvs9aiSDR216d+O3kpACOcQch3FcZgUDWbVte48a0FLLo2EHciNgs0NH0EoV\n67ADc7sxv4kih8S0R98YdIGONuWzLnbNbbkQEBZTt8EEQtPENKsC2dJF/7sY\nhrwOVVB0yZmNYlWGM+SIoLJklPkf+z+UCtB1Q4B0uuxVuBEcu04bcaGbFe38\nlpii\r\n=ULU8\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"abf1556e4109657527dfab12a13c32ea7dc8e19a","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.62","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.58","@balena/jellyfish-logger":"^1.0.22","@balena/jellyfish-metrics":"^0.1.85","@balena/jellyfish-environment":"^2.4.12"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.0.2","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.103","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.0.2-renovate-non-major-abf1556e4109657527dfab12a13c32ea7dc8e19a_1613638253386_0.846881961963672","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.2.0-5508-pre-build-versions-96d29700400169b9f1cb17b0135ce7a020c10c6e":{"name":"@balena/jellyfish-core","version":"1.2.0-5508-pre-build-versions-96d29700400169b9f1cb17b0135ce7a020c10c6e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.2.0-5508-pre-build-versions-96d29700400169b9f1cb17b0135ce7a020c10c6e","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c4bf4e4d0443db434d2b68522af54c99fc9488a0","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.2.0-5508-pre-build-versions-96d29700400169b9f1cb17b0135ce7a020c10c6e.tgz","fileCount":95,"integrity":"sha512-Z0xY85T2PaGFxzjsNLeRZgfPZ/imfSIul54mKrNE8fvIxjTRzoGGsi/dcbAVgfxl9Z+dG9OHwhomaqEupkjqJw==","signatures":[{"sig":"MEUCIQCaBXFHgd4h1P5OsG7PnUFqoHQQecNrQ7fNB9BictkWpAIgN3hzzLM06oDcsKCyKH+wfFoMpoY38IpY5Unf5erEk20=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":593611,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgLiqeCRA9TVsSAnZWagAA/OIP+gOsGXtHedC3w7//Ce8J\nrkYOjMhLYk4Aplwxn1dqgm103A+p1A/hdhDeN4d4YK0x1L0W/wJRg5cvSCR0\nLiY+lRZ9ElNzjcdkRAkgMp9YVcaESev5EgcF50seLqN2koBytSPoaGAbH1lp\nvqRDpTOXljEnEb5wzoNTD+E3KDNmKhznnJpWYI16UMroxJQFVNeWOvOUL/5U\neKAIXrBz6NOnboD2XdcTWfhzUSb+5m+ZVVJHLGYBchDbCfccN+lSLy/mZ3EY\naMkw/zWk4oiyEI0chLE4GjGYBcIyAW3ymzlly7ktWEfkt+1kkcwTRg6e0nql\nzevdHwM5pbVzHzKKvCO1oFdiM1iFTMuOiHQwfAE9/9N/D9EdLJ3o1RiFXHf8\nYCG6WNDW31ebNNPY+n6rpSq/LagB4pi5OOaQzAKMYi1LzlJgCdUF7tFMiCuA\nn/CsXJHbcXOJFc0IUQpDP0c33+lTAK1f3QjxmhUFZuRctKiW1hleWeF6x56d\nY6vFMEghgDxmZHVehZ/mv9eZa0IoybvRLt4nECIVn784mQGzQ4rylX8fAZY2\nJpKuoLMLbG01oD1Mgs6XF5SntOv8zerE+yba+B4Bym1Rg9mUXwUqRQGVyb5C\nhSp3C/OugO35ojcBBx2QH/UglePijhcjtkx85+6i54e0tIUZFzUV2RT++57x\nZ7bl\r\n=QtMb\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"96d29700400169b9f1cb17b0135ce7a020c10c6e","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.55","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.51","@balena/jellyfish-logger":"0.0.207","@balena/jellyfish-metrics":"0.1.21","@balena/jellyfish-environment":"^2.3.25"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.6","eslint":"^7.18.0","deplint":"^1.1.2","depcheck":"^1.3.1","lint-staged":"^10.5.3","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^31.0.8","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.2.1","@balena/ci-task-runner":"^0.2.80","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.2.0-5508-pre-build-versions-96d29700400169b9f1cb17b0135ce7a020c10c6e_1613638302253_0.0044809945510957405","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.0.2":{"name":"@balena/jellyfish-core","version":"2.0.2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.0.2","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"219643e40d740286873eab292ac41834ecc46cbe","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.0.2.tgz","fileCount":97,"integrity":"sha512-XaXSq1vYKL20jrXLArztHuxev4N2nZZrICig9+YxuWmFHoXrzjzUq/wmVbQMOXBt9HTmy7LfiHzrZ1X/DOI9Aw==","signatures":[{"sig":"MEUCIEPv6Mik5YyYKe+iCiif0KFkUObH6YwfYhxUOx66kVaAAiEApWkgu9yXY3wBlubkfZMZxzgJsZ+/9r85BHoMY6GWCvQ=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":600725,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgLivvCRA9TVsSAnZWagAAjYoP/R17E1YpcNSCvqiE7Agw\nZnfvW1pXFdgZ4J1KQJtC+/wOwK0OcUqER8Gd6kCygImotdq+yG1QTn/QFyX/\nyKF0S+kqQBRTqHN3x/GzORMv7sALFQ/IISU4NHo6mwstUeJPjGMdn5QK3/Qj\nETx4Jq4kx/VCKCRUyjLb5ZoE5ex3lsvMhSUZWgiuj4ZeC5lNK39slXSviInq\n5/cknGyToaulJsBPWOUsvTyrwmmhNpIlW5quAg0mh9NGHipu51GROjWbQZyg\nBuMZEwvTK5OtzzssZi1bWi84cZbShUSvkTLSDOnZcfVh8crqPa/1ZqlFwiv3\n4pUEyMih7qko5povE56T1N3SkDQgPBeCmOYNvI4daM4+yEXt754T9kk1fR/j\nG/iAkgwdvdjqI38kYBs/XCD2woE4j8PiOyfUVOXlQi6RZFD1HkyPN8l6NwMx\ndr/jpk8+pNYebrpJup500sGr+ZX97jx2eK3VnkWMz+znEJaQOlQroLH4rKRV\ntsPB4Hb9fqnjWIaC9SRwaomOO4e7Nyfh9+ubddTprWjGicOzjwXvijVnM5h0\nPoeO/B9EVo0UhlmkzvFz1aqvzb4r7xCqqHwy724pUp3isRtmc+tLYhK71+/g\nKQJKIzvmfrzblywf1gYxo4JVtON/JWoEPxO+JgUHv+D2WY5GhFsLgSxHIddL\n+/cy\r\n=fdOt\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e5ff70d3f3480600e571c9bc199ca62d6c3a19e2","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.62","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.58","@balena/jellyfish-logger":"^1.0.22","@balena/jellyfish-metrics":"^0.1.85","@balena/jellyfish-environment":"^2.4.12"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.0.2","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.103","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.0.2_1613638639098_0.13400475174099635","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.0.3-renovate-non-major-f3ad5c7ea2425c31894cf52f2567f0b0bc70957f":{"name":"@balena/jellyfish-core","version":"2.0.3-renovate-non-major-f3ad5c7ea2425c31894cf52f2567f0b0bc70957f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.0.3-renovate-non-major-f3ad5c7ea2425c31894cf52f2567f0b0bc70957f","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"aabe854a84d7e601453a931419614d9e3b3f87db","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.0.3-renovate-non-major-f3ad5c7ea2425c31894cf52f2567f0b0bc70957f.tgz","fileCount":97,"integrity":"sha512-IuOesMOadP2+uvl/VscFq2BDMelKsz6IE0FL4D0aOdNudZaPTDJEYNynR5mEeXCZe5hKldau9mmtBnY6Z9si0w==","signatures":[{"sig":"MEUCIQDBS0JwfWp/g5UbdSeo+cEobx8We+vKuNBe22K4YZs8xQIgKmOpYT9wesfLHrCDO2nGN1tUYoq0x4l/0Uw1OmYUD3s=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":600850,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgLlaxCRA9TVsSAnZWagAAhXwQAI7DUxuN1LuRztJfUR6A\nDDThaE8WLCSUAJERj8lLxaZtfCxgKkf9lWfD176mpn85nKlsm5VgAg+Ts8Wq\nsKLCycBohM+MifOEqIn8wI5i0h9rz33gLp3zO1ne8WFaY0f/ETkvKbjn6gje\nZjdxCZvv57BHoOVl7lfsOIHvmgNq7x0F5ahJO8F9ZKUALPWLUg9tWx1I/brc\nC0aqvXVb5da0nVbFjAGtiXy24aD3mevgwkSTU1sLbnAFXJZbSz/JBhE+g+9p\nDRuCSHLJ6GVu1DhjLPIhjdbdoqYIQAcsMGuQSo+fvnPxiM9V5UKHMJ3lvEaL\nVHGs7sBWqVJhcww9It8kZ2QK4xonyOobF/BmqARMWzAll0OXRibMztWi0WXV\nNBGOpN7e+er8fxSvGXZmGrFUwmWhtayPhiT+54HgGi6mB6UNqmdUfIGRWsgF\nLhuibSSRM658tDqxI4DMeOVq+prYy9GEMWsISbVeatBXS+hZ0g2BI0MxEdHg\nSePrG+8oDRvm/5NOBPbBN4QyT3njDwDjgwYXBah7yTILUnMuzYay9AZ7eYdG\nJsFpJp1eiwUVPT4hOn9eBtVFklezDd6XklrM+7JVIE/qBFrlz6ms2ZjZhvk8\n8LU+gtxuAkRvFLsrqiR8r358wNlUghPMsie51B0J5nkAE+KLKu3Y8BOkuZpo\nWJt2\r\n=dvcp\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f3ad5c7ea2425c31894cf52f2567f0b0bc70957f","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.62","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.58","@balena/jellyfish-logger":"^1.0.23","@balena/jellyfish-metrics":"^0.1.86","@balena/jellyfish-environment":"^2.4.12"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.0.2","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.103","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.0.3-renovate-non-major-f3ad5c7ea2425c31894cf52f2567f0b0bc70957f_1613649584561_0.5915855233001261","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.0.3":{"name":"@balena/jellyfish-core","version":"2.0.3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.0.3","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"40b6f90bc86aa4d9d82702eb83e89ea3cc01803a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.0.3.tgz","fileCount":97,"integrity":"sha512-gfizQTk5ZXL1p4xTCBQ/uEWwPKXjMJ8cKe7dKxqbX9XVh3+FRguL8RJu+std82yMK2119nKmJobEcMZ+9wkDnw==","signatures":[{"sig":"MEYCIQDFqtjVT17fZT7O4cNSrIZ7luD0hUGpbzQKpcK33mDnMwIhANA0H9N4A+oIPTe9Tv7dOrL9UgdWJ7VH+EsC10AcS6kH","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":600790,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgLlfYCRA9TVsSAnZWagAAFX0P/0qwB784FgfO1aDYPXNh\neln5o3Z7324urbmwM8nB8Zj+9bhyJJfy2KwDoZZGan/THjX8KgAOBRF1K4AM\nxDLRUb4z6ZYY02FPYc+JD1mALMqReqHeObbQj3R8dQdssTjsvwC1gzCaY8x3\n7RY/GGSjNKG2DSFNKAiFSQXIC7+vJ/alTdkTW/yA37WerWDs67YoF2xIycTb\nSYdpm9/BfxuLFVJQqx8zMCHJef6Gv5UbGtzG5YFVhA3rU8Pw5hykdpMYlDta\nf/7mog2HmwlP/JBxSrBvK4Hp/189f0pr+aU6k7Gz+dxloCw0dpIWH/tRX6Wx\nE4azQ532ReJsw3M8JcrLrXgrTjpweIsU5xvoNiBgx8gaEpsx4Yp7++uLmclM\npSfJwWmY2xId954hXW8sJb6NU5ExWpNWpnvD4IPrQfZHLNHbOjHAHGDV/bnl\n84nimYi8i/HECpGJgRJKtxhERIBBw708e7+BcVbzDkO17vG1oeAxfMqd6JEO\nNLrl6KET5k3ynpZA+vpe2pO/NdnfiKybBVey5YQuyNywcYYawwTaqNs/IGf1\nktv4r2CwsJKi0e8K3iogrhfil5ThZgN46zfhvpDSqxHpAe1w0Myj6AcQkAj2\nns4JqM7TJSMJRqMouJ47+DxLZ1kARceLwmMa3DDq2SLEvOaB1nKMnv7ZguNc\nW8d7\r\n=xbSy\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2216a8b00a5b67468c92e64625316812bda2a5d4","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.62","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.58","@balena/jellyfish-logger":"^1.0.23","@balena/jellyfish-metrics":"^0.1.86","@balena/jellyfish-environment":"^2.4.12"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.0.2","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.103","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.0.3_1613649879645_0.9258922591429519","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.0.4-add-versionbot-dir-53fd1c673290529f6b6179435e594b90d6c12613":{"name":"@balena/jellyfish-core","version":"2.0.4-add-versionbot-dir-53fd1c673290529f6b6179435e594b90d6c12613","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.0.4-add-versionbot-dir-53fd1c673290529f6b6179435e594b90d6c12613","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8f62d4c1c80c30ba05958fc15bd0409d393f3897","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.0.4-add-versionbot-dir-53fd1c673290529f6b6179435e594b90d6c12613.tgz","fileCount":98,"integrity":"sha512-WOlo2Gehr2c/0dBtUr3XjAEb+T8YC82m8O6Fq9TOIBLZ6yElOp0z+KIVwCaCl8KxaSS9sBXH0MX/LN07gcOLFw==","signatures":[{"sig":"MEUCIQCwWoEuge3zZ68EyGaoXEaS0RAiouIozJx74Rjz1SP8hgIgOIafcyL+lxQiAnE1THw0kJptO70vSTCGsmq0NsB9itU=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":600917,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgLwjQCRA9TVsSAnZWagAAetkP/3arRQ0psJTmqjle0SwV\nXQupKdG1pHzaEYDuD5aV3HKPvp7dsGnUASc0BVWahsDZTsgzV1t4fPyp0amz\n8Sxs7dLPAdrRoMjlNYTW137jAkGeQ08nc2xqvuPQ9VnsuPw9v2dwUG77p+dY\nMBc8762ELsppnuvylky21y/q5q5L2x39ZUCAiYHQ+0Oos5Jr+Yuo+aMKPIBK\nEFG05EyuUGfpR/hOd7tTqELCz9R+BK3EnjlMUzj8ZFCdIOdYIqcLhArfyRVZ\n6Bf7pyB+vzkIL/lvl1mJSEeJdaacIM3htl/Gz/U4lTDMzfFexlhxoFSEA9J8\nQyM0pPD2pGWwGaWoaUeat6CAcoU+QziHTUKq9d8Bdw6mxnybYWzKiniLhjpe\n+5HFJJVpUbYOyUhlo2a2RJatvuJ8gJW7OrcgKnVQacOXPZY9TtqYlRcfJQBp\nm6aK2RAOJh5XutUOXJ6I4RUiNAjsiSN6xxo0OnCXCP2Lv9qUBKdi62opqq8F\nPDhWwxQvKumC3GvtvLQioMWCG+XkZG0xyeIXjws/DlN1CNwSwr1vSULXWjJp\nYe9PPwi+RtKb7GvBiOwKR+KIfr3BqXxBx9KIj6iCzTqP6IxnE4OKTHpVmawU\nwjRAzaUbqiEtcbKtA0ynCc1mqswffSrA0pxi91M13C5fT5PS11PoSJT3sDSv\n4XfM\r\n=CA/B\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"53fd1c673290529f6b6179435e594b90d6c12613","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.62","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.58","@balena/jellyfish-logger":"^1.0.23","@balena/jellyfish-metrics":"^0.1.86","@balena/jellyfish-environment":"^2.4.12"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.0.2","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.103","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.0.4-add-versionbot-dir-53fd1c673290529f6b6179435e594b90d6c12613_1613695183742_0.41228311657422534","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.0.4":{"name":"@balena/jellyfish-core","version":"2.0.4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.0.4","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ae7ef81f3d9e32e5957f6e7dd2e862abfd3e8f2f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.0.4.tgz","fileCount":98,"integrity":"sha512-vS9rxjEl/BwbH4bF81Sz9XRW1eG3XM8DA2IGStWX+KfVKxep8q3St7HHK4tovLIKtxJE7Jkb4NRbJS9XMj+a0g==","signatures":[{"sig":"MEUCIQCRibSdtVwZzVZBogPB9tMhPj0qeqppNctFPhgjgIPAwQIgBknoClpzQf2l3l4qQ55/uHGPPSkcKyzRRzsuSYVr+C8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":600857,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgLwpkCRA9TVsSAnZWagAAV0AP/RUTODjaTBBxGx5CUgwK\nlG0ZxSQhaB8wsGQ5SrY7pHphETXqAIEFbhNnpkkpLunlHLnRfEK1/Z9w5BB/\n/dkg3vireX2A9ZdvISQ//yCmoNHYcaV7fkqlj3/xkja9yFQ1zqFhtTf2VnS7\nainzTQxfyxp1CrwULC5RTUcLY+GStFAMw9p1vVIimOzH0d8yBAcayRW1wt5y\nKFxkNF7/ZzcCrRKtWiad3NCwyUTZaI8rT2NEpF6Ymf3cQSipWw2Ab4YvYt3i\nYEzRJ/A8QGXznAltJBnCpyxFxzDrpwxDfdFyxkzqaBiqSUUlYz5G3yfHOMAf\n3QObSFWgN19AJjzWjgli13idKAVbnk2R+Aombde/5uA5rnHmXIrolDwv5oca\nZvtYUU6eit1rN7Mo3pJ4KGtaQGJBHxuaq3p8YvsTj8NjHT3aMZ0jQVQMSmz4\nC66F8U8FAjPMqTZKU9WMYl8GZ+fIA8bglS/3r2kxyoAoeqAS+q3TSAU45GHt\nl8rMjiS1ekVeGOSWDLUlq/BKdWQFLHTIKBf+UICmZrurX2eQUXmCnw4Re5kR\nOqqx61HlBHxiz+Qa9y+Nw8rriIg18Q+Lnij69g5NMx4ZoQ9FkXh/fDApbQVg\nw4cBRy8qcq7467iWWPCpCslX8JmCnApct9mgfqoXOdzrRMFWJbwu1DIfl72Z\nUkCN\r\n=p+t9\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a0cc64357cb0b6d70eb4febf46d600cc6383f462","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.62","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.58","@balena/jellyfish-logger":"^1.0.23","@balena/jellyfish-metrics":"^0.1.86","@balena/jellyfish-environment":"^2.4.12"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.0.2","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.103","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.0.4_1613695587879_0.6697953062972584","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.0.5-add-changelog-yml-a16eb66eea661d9902728aec0cb8f12c6cee18fd":{"name":"@balena/jellyfish-core","version":"2.0.5-add-changelog-yml-a16eb66eea661d9902728aec0cb8f12c6cee18fd","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.0.5-add-changelog-yml-a16eb66eea661d9902728aec0cb8f12c6cee18fd","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8f3070de85a9ba6c4c0caa989bf531522692d119","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.0.5-add-changelog-yml-a16eb66eea661d9902728aec0cb8f12c6cee18fd.tgz","fileCount":98,"integrity":"sha512-mC3U75qbtM67qwOJx7fDq8QDHnOKHMBjOA/sxHFW/EmqSMV9AxvKmjTgbisNDTk3A6Bp7jgGSDublzov3Ijoaw==","signatures":[{"sig":"MEYCIQC18XAanqSVeF7tMmCueSiPvYJ2kyPA88gxog/sma+q6wIhAMNhD7AtRMAsKdZSPDJYxV3bbaW0/T6lzg6Ly2l/slA6","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":600993,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgLw8CCRA9TVsSAnZWagAApUMP/0LgkOrSOa+nT2mRIPht\nWyFUO/fxNbnzECA2SAfCoiAA6haHhIvCbJYOKB4Cv9/Phm9L8vToCdpVKpGt\nFqIHrllcc49MRUTmZXfb+7t4cgzMHBOnhDI5LumyYVTp4m9/fB7RcPGTfF9C\nddZQVxtEhjrrLlTGlsopmwW9bTHDdwT+2KRMwBFELz/d+s1nnruTdVB+zBWs\n4JMDj/UBQEU9+o6u/W9HYALBiw92U1d53zORAHn3Q6pYQLvQGJuNzu3pyJjl\nJRqD7fU9n6oZM0IQgx6Z6lBPjOMjzIkO481FINB0xyQ9NRonCJhso2YuNAgV\nWPvaoy5ssgK8xB2oPEyW0bAFWYgEDo0/ikDKIsBWGi6VUHzC9uL7XS2CRB9+\nDYS9XBP7xjHwHxw8rhLpx9sUTHQnKShJ0IWUtbDAA38T4pm0Rjw759UHfG5A\npsX6+D1cWMxUMgta4OfExfRxWBfHH6Z2Oujh4LhZFhmYSgp7TrM7Nmvy4mOq\nW+JRlrxEhBuNVMDzZsn8akXnoZPJ12/PVfltKhED+WoBZBwoysLIKLPS/+Rb\nP8UwNmnPFrw9QroFdyxRdUfyhID2a21l6vjMIahd5miAofn69ai4QYE3dDD+\nccDS4S4pWhVlVzoA9HT0DcJsH5ynJZWYiBjDQUMgsYR3O+yp+vkLfbg/L8EG\nfPLe\r\n=Sl/E\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a16eb66eea661d9902728aec0cb8f12c6cee18fd","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.62","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.58","@balena/jellyfish-logger":"^1.0.23","@balena/jellyfish-metrics":"^0.1.86","@balena/jellyfish-environment":"^2.4.12"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.0.2","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.103","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.0.5-add-changelog-yml-a16eb66eea661d9902728aec0cb8f12c6cee18fd_1613696769790_0.5890866244613573","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.0.5":{"name":"@balena/jellyfish-core","version":"2.0.5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.0.5","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6e0cb5d5bc2723facb51f023110591adcc2c8d08","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.0.5.tgz","fileCount":98,"integrity":"sha512-ifMb0S40FgqGxNwJjhrbUXhn0s14HNDnyGmueDDLVmKSmN+xJD1QE1hbaXZy0vIrWpFEgxF+gFp/UBkD2ELhBQ==","signatures":[{"sig":"MEQCIAPMJM+S2O6Ie1N1alkxB+uk/iF86F+ZMKQvONI3tvxnAiAWHt8uMUWbc5kZoDMjb5eyO0o9xfem1Od5yzrF62YRMA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":600934,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgLxCpCRA9TVsSAnZWagAA3dEP+wRPXeiqg1B/1T1tpxJo\noBOh1hqY4XkAhcUwyaP01K4nKfekzg3w03g3gpqW4shfJ24KD73keAT74iSx\n337G2p/YJckcg1DHboKEu7R2Sw7dg6jCMdYigZXoqt8050SGusNgZjUX2UPn\n08pYzIYL6yN6AKo9VY30Xainy9oD0h0OLLAHgKA+lyiURNZ6gszytayofoOn\nJHqgK/ur0kQjYJ7NlITP4VNPwgDePuwUgrh107VSqYOwriYff6eH80YrgHgF\n7GVHnKq5xd1rwXneYfxYWP2KxJnXIqoCNu0EJcaIGIMkh28vQxk6DVTpwfoy\ndpwLn0sUwcXxvvJta9PSPZOw36fDELTYS9Jj5wZCSxFn0AYEatPXddM1rnil\n3kHV3cF3HNSpZZRzLwHRRhC0rZERCPDBhpJNRm4Z9jk22Q4qnW9CE/616Myb\nZCnkTESsfSBhyhfi7k7Ay4FlajTr+C/8Rikp3cVA0Rq7BVArymozTsOploIB\na1BeCF93O4gNM/wNLD4VYpx9Uo6/ubQFd/o0zWiN/yyAD4LDzSFWCNKqyBqN\nGDMAIOEvVa9YgzSV5bU0Ijyd3l9+kMBsdKH7MiqZCGtLWtNdTC4FuR7M1m4w\nLje0df+2ZwFg4k6nnVa/s2nWqxt+MXfdNhyH4gRRFDtzREjSr2FAeyCxOoMx\ndVIm\r\n=uAm0\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"002952843ba8156b0dfac5e17a7cdc4e7bc86712","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.62","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.58","@balena/jellyfish-logger":"^1.0.23","@balena/jellyfish-metrics":"^0.1.86","@balena/jellyfish-environment":"^2.4.12"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.0.2","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.103","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.0.5_1613697192646_0.9294315010877665","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.0.6-remove-references-to-old-date-columns-86b5f01f87598b972c87a04346e1a516c9e61a09":{"name":"@balena/jellyfish-core","version":"2.0.6-remove-references-to-old-date-columns-86b5f01f87598b972c87a04346e1a516c9e61a09","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.0.6-remove-references-to-old-date-columns-86b5f01f87598b972c87a04346e1a516c9e61a09","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"698858da446935971a3d51f799798592eb00f4ae","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.0.6-remove-references-to-old-date-columns-86b5f01f87598b972c87a04346e1a516c9e61a09.tgz","fileCount":98,"integrity":"sha512-6Aw004ZjJjIXaXeYj65kzXFPvTGiQVWEQTBI9Ew5XnYndXskeR0G9A7xoa+gJzNW0Bm3jSZ/0GCoWwlpbhlFaQ==","signatures":[{"sig":"MEYCIQCkPdKUb1HBJz5DddceaFjDqWyfRnt8/mRsyFSbABgV8wIhAKbq1BXv+tOJNmVS2h/kcdn9/kIkK05KnGqsp+l33m6+","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":600370,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgLx2pCRA9TVsSAnZWagAArCsQAJ+D28bSS5EChpVTXhSA\nomDKrOhuT+svUlI9ypBD9K+/cob5hOpRq72cRYZ5GZmDYs3Vg/azJovo/7FO\n+0BIOeUffh37jX0vrEKZt/5qjEvFXnlnIA2Ek6jXOmDT/q5kUPYhO7rblDCs\nem6Al4UG9AphPFaJWik5hvCW2nsQ8BV0kS3PrQk4Lkx5WR2WryxQWr5RdSDf\nWdjY6FKfzdgsf9DSmm3uLiO0gPQIWGV1OMBjJORU+LSJNCmst6+Qv7N0h4US\nRUz/NnJVD6LoUvuFKx0hcQrj/dHqURdX3EAv3yDXLbn609F2BwNDc4CVOo5j\nU0BaWCp9P4BoXuVMjHg7QBr2nXusrfDivKd4PeMEaavsDIV6E2u075XrE3UY\nz/hNS+tBcoPn6WY1KouftbriyTtiV0+5jcoldmfJaJNYEq5sDhn6OmXcoto7\npCdGSXIg6g7JVfHCPAs5vDU5sQc/6D/WkcLuvDm4lwflUC3bCqsDGbbp99E1\nFbuAqTK0VTfFu2CUBPqKgq8v16CtqhOiyM33HYTTBK3fNaeW5PWiR3FQMlJp\neWaqTf1yKTEtyB4RmEx+Ht+ZLTE2NIhu94KHik4MP6/5b9TyrRK7PbRzW2FC\nOu5m9v+s+g3IA2BC/PQj3ZDuE3iOpqpEoqEPWDn5O0Uo7XU4+Rw89Y4gMWnv\n78JK\r\n=ALmD\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"86b5f01f87598b972c87a04346e1a516c9e61a09","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.62","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.58","@balena/jellyfish-logger":"^1.0.23","@balena/jellyfish-metrics":"^0.1.86","@balena/jellyfish-environment":"^2.4.12"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.0.2","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.103","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.0.6-remove-references-to-old-date-columns-86b5f01f87598b972c87a04346e1a516c9e61a09_1613700520368_0.797200037132006","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.0.6-rename-changelog-yml-aedfb49fe211fee98d185479fc8d60510d8e9ba3":{"name":"@balena/jellyfish-core","version":"2.0.6-rename-changelog-yml-aedfb49fe211fee98d185479fc8d60510d8e9ba3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.0.6-rename-changelog-yml-aedfb49fe211fee98d185479fc8d60510d8e9ba3","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4b828c752fc64736c5ddf6780219ab9810d63049","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.0.6-rename-changelog-yml-aedfb49fe211fee98d185479fc8d60510d8e9ba3.tgz","fileCount":98,"integrity":"sha512-hN95+aBgU4zYthMBzRLXtgPGF9qbxU0iHXGu5AjQctEML+qaXP4QVPUGa2FpFviL8Frgf9ACjNt3NlT1URrz8w==","signatures":[{"sig":"MEYCIQCkUq2LZWtCRHfTJPvFcSGSqvu67c2V9NMc5VaqdYVCYgIhAJ8c822V8an7TqN4zPxG06lVcDpCgokqOGt5XpjUi1am","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":601485,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgMMUoCRA9TVsSAnZWagAAEksP/j+8XfP1flSUC+lIaBnm\nQSNYMt+VXil4/bymOmvy88ibBkMoiFqYSI3TqgnL0hIRs/fLIGjt5Q3usZrk\n9SZ2dUyvtsfU2kpBv2ThsPWnh99mtWBUs02iMEpoJsDo5Vgms9Ni4JFNwBeH\n5TTxTl/dQdmAJBMlF9izJPg2u73OmSc1gHraEZpA96kjGWL9AxDtVsZ2GqAp\nxXNW0tvraOi0g7UETTTbReIWWY2Ydkdy1RDC6+fKYyI6js/06wj3xHDyqO5x\nWdPcRN/4FFuZkBa00MQ9VLtPNPKUjHkDHuAsVCq54ZFJdfU6FtwYvWJxQ5+v\nzQCQfdNej4ei/rI+W2rOr/M1LaYog9/REbPnKgLbb4/Zz6OZFcOEUCcExaU/\nmMVKLR+qwpyD+DFaXX3HRHTmMCqhCXJJElUWxP7EG79GtVVQ+x3H0lGWbx+m\n/81hasD7XoSm0eZ3OyWog9K71goXLBWqlrwMbJWtdd4I4Z8gBQrh372wO9Im\nfuA0OO9awIpmLXRHt1d5FfAvqGZf38oBsr0ubFju1mk6hdIvbAplj8e751cg\n2gDew+EVJ3ZheVRhX8hxZi1iFi97bZHqTDvpo/H137cF85A5pLUuiqpQ+N5R\nV3E9Hnp2Dj7KPjq2HjqQ7f2OVL2oTj28M2ZSDt6JOp3+f59GtUpFtkYz90sx\nfkL4\r\n=TkDL\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"aedfb49fe211fee98d185479fc8d60510d8e9ba3","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.62","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.58","@balena/jellyfish-logger":"^1.0.23","@balena/jellyfish-metrics":"^0.1.86","@balena/jellyfish-environment":"^2.4.12"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.0.2","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.103","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.0.6-rename-changelog-yml-aedfb49fe211fee98d185479fc8d60510d8e9ba3_1613808936304_0.37031769231038525","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.0.6":{"name":"@balena/jellyfish-core","version":"2.0.6","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.0.6","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9ca67a2a7614844651b88c549fb265587ab1368e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.0.6.tgz","fileCount":98,"integrity":"sha512-1WqD1FkYH7bJG+Utxq6Cb/Qe9JjUou0v77rsu1wdzAVynOC+9NWDTMZqNFdYKZNKeJrKXylz57kQ6sWy9uMvFg==","signatures":[{"sig":"MEQCIBK8Qb5ai++eF6gi1WCbqHTmTjKVkA7l6XpM354H/ZrsAiAswaiT7HkuSVmTQlhkEFF3uOzbo042dqL6sCVIAzN/Zg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":601423,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgMMZyCRA9TVsSAnZWagAAndAQAJjpyVGm4RRjxHxwHL0b\nVNUoZ8YCO44jI2QogEWJT/l66QUbltMLI6q/g6IYG/KpgXwEN4Z0a0Xe4YOP\nHMBRk0Yl6fvT/88Xdyw4ILI9uxE/35CcrheqRgxEJOduMEnm394QgAH7Cpy8\n4pvbOTLP4tagDhobajnE5yWxr+Z4KqP4IOO8aojjEugcVg3wpae76Idn9Efl\n7rryCZwo+SSIlPr8FgzmcfZyN+fCvHwjQa/pXqHV63tf9ZBPOBOl23AjZPwj\nwOL2abGQvFrtNa43YXAbTcYNaM7ubV1jZjDRY78w+4gHMDPHJ25jvYM9AIFy\n26lunUrRzBqFzK9Klp+5VZylJAqEKZGENj/gj9FUI8oPfdfqvwjRpY/AW2nK\nx9GR1uqg74BztB8yUKwyKsWJiXGJzuMAO8KTFK3XOHtaRtcwp7sY0VuvzGD7\nXEgOVgHepBqWtpi5WG9FrX3B2CfaHmpri4vBMvjyYmJ29t/q+qO8/9hgPq4K\nB01DB+FKE9sFQxiMQ2tprH0h8v9zkjQaSZ8lbWz9Qc/mzjGg4MeuF3oj5WIJ\n/YJeS0Tt1FG/HKE1bWclOz34q3LZdPI98YBQtxtrZ5W1aHsifZuGcu99YlzS\ny/5exSR86MspgdPJNgzg/jsSJ/yPEEgiUKbMsTAD6ALm4mz0FFIh3RyrHV4e\nkvjM\r\n=+jfb\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d833c7f21661c4e45d7220ffb4b8b1d729ef02c2","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.62","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.58","@balena/jellyfish-logger":"^1.0.23","@balena/jellyfish-metrics":"^0.1.86","@balena/jellyfish-environment":"^2.4.12"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.0.2","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.103","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.0.6_1613809265927_0.7958280591146547","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.0.7-renovate-non-major-0505bb5b6a7a275e258d0e30c19d7d7f48a94b4e":{"name":"@balena/jellyfish-core","version":"2.0.7-renovate-non-major-0505bb5b6a7a275e258d0e30c19d7d7f48a94b4e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.0.7-renovate-non-major-0505bb5b6a7a275e258d0e30c19d7d7f48a94b4e","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"97fd98d997564e739b7baf2ad979c8f79eea1e96","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.0.7-renovate-non-major-0505bb5b6a7a275e258d0e30c19d7d7f48a94b4e.tgz","fileCount":98,"integrity":"sha512-auifZDbcJ1Nge95zf4LSSvj0znNhJcsthLUCU4FWMJWRFrym/ncmzP1c3TCUKr9E2RzGdiZp8WYNnIqVCCCC8Q==","signatures":[{"sig":"MEUCIQCtE2x33VWwNywjnXlLnVqIG5B1xgGsXxudhEoPPYjm8QIgOy8yHwJxubcBkcige4ASVD+RImjv2X/i1KEFrx6Hif8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":601830,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgMNuICRA9TVsSAnZWagAADuAP/2fR85trpjbt4TXjF2Li\nkpYiIOUDeF3/z4xzl23ujKZ+2Uvn19bkNHSgL4DsGWeX+h9XNoyrBngKVrWd\nhkfK9RaUmmSsUw3A4+9WAG3hHftZCJd56sBaeif49xSpJJO7qyC01rwqV+Ma\nf7qhUpM1phPPUGDxP9KDnNpkbAgkWiTNdYHzOoJLrtRDofNR+9GiHZ1AO1xR\n7nAUR8D5LyLzUuK7HInNdZW0+b8TVDJZpUsT4k9P/7SlgY8ceUfbOobCExOw\nKLv/DUM/D+M66e53qRp+RUiX2sf+pEpcedZaP2arX5UYdqhgpNTJb7jgx53G\nRo8YHZXYZPUWmA1okUAr8OnOGPVQe2tSH3+u1zWoDIzIByKZOoW/QDqVA5pL\nMOlFu2irNUCJc4DYsAVWsbCAW5APSxIc+/TJoi8gPHxliEGR+BYtI6U/jv16\nJZNuP9SAkgOtTL8Jnuiab2Hf2Hv4FT0XgqrrPzqgjMaypMMgyMJhd9FWiuhX\nqHpfsGeozceWHYsHJSdVnPY267qsa04ziAgEnkzHgkJiAA+hQWfmD9zwtGNC\n7jmassRIvwmLbGMYxUVoWBKFt3hdBCwNii09IkrJV9x0uQB5cCBJqh1D+WFF\nb7xPvLaVlVBh+XwQOTX+gw/6WKlhpiqb/LWBMrFHLZQMHN+BuVsSv6hK6DZz\ngNng\r\n=h0di\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0505bb5b6a7a275e258d0e30c19d7d7f48a94b4e","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.63","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.59","@balena/jellyfish-logger":"^1.0.25","@balena/jellyfish-metrics":"^0.1.88","@balena/jellyfish-environment":"^2.4.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.0.2","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.103","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.0.7-renovate-non-major-0505bb5b6a7a275e258d0e30c19d7d7f48a94b4e_1613814663480_0.20879594958656877","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.0.7":{"name":"@balena/jellyfish-core","version":"2.0.7","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.0.7","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6bc183d0c754d66d53a6e1c665449e12c44526a4","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.0.7.tgz","fileCount":98,"integrity":"sha512-XzMekXlZn23c4l20relbWLrKvZqwJTTno29HhgK6noZCEWmaEh2nGPPNh0fnB3h8FqdrXPwuAPFNixjtLNH8cg==","signatures":[{"sig":"MEYCIQCzNbeUqNCKVcJ7VBF68qO/B6hw09wpZCeC8OzMetRClwIhAK77zyZQTkSDyrU27C51OtjvspJGS1W0ZKAWV6gyijL1","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":601770,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgMNziCRA9TVsSAnZWagAAydQQAIBCAhCSfnouUxgC2z3k\npnPvNzxSMJcfIGLreh43MxICHFnxQVtq7B4rqZQTRE7DI0v+Oo8E6QNPDtVC\nK8s1HA7aznnTIFMjQ9UrYeAWOg9MZLnA+l1xjuDvdzUhC3K9z5veCcO1vDu6\nFJ1+UdzBX1AIE/fuwsoJPardtxHpjTWkcvb/CWAZxH0Bbk9p+LWDq6GCP9Hw\nHeF4QkSqHzQ9JGtXSUCZhMuCOElX5QURutmmMswTS5UQgPGyjK+Uer47DipZ\nrx7rvpCsEEkhq1swXUl6kjE6iL4b7P8Tvx19mOIaeUtyJHd62SvrrLHJM23l\n+BDFC42leyWVRy9Nab5hmO+Am47BlBthyxoKlJPduqwdw9g/fs6VS1cahX81\nRJgNpZ7BCTHzv7V4LwWjIJkgR5wUlVVnYKMaULf7sR2bLC8Tjs6TGLWbGf8Z\nTvbh0sn3Gtv4dbNuf0UP/HfcsTFPIJagmKKnmOa1MRiA7KjMTd+e6BJnqoCO\nEVYx4K2ZXmZcKehJ+viJWwmNZ520JyODDJYwBqwmRPflYnQ5VQ3oL1qXwXL+\nDQOh2uD85r93Jm3wWnP5/xigAAlEkyNIy6U3yvrUSk92JEa/56odDoTmO8/j\nQ+edScZ6QsHWZeOGIsrXNoJbSuzWqH7yYzJCWbDXqA+jEXKaGsXvj7kh+Lia\nycF9\r\n=1Jti\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"bf4852f38bf900d1202a4a09f39fe4e3f124aaaa","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.63","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.59","@balena/jellyfish-logger":"^1.0.25","@balena/jellyfish-metrics":"^0.1.88","@balena/jellyfish-environment":"^2.4.13"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.0.2","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.103","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.0.7_1613815009780_0.042281984868767886","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.0.8-renovate-non-major-2da079d5922e39e93ade061966ca6d019f76a82c":{"name":"@balena/jellyfish-core","version":"2.0.8-renovate-non-major-2da079d5922e39e93ade061966ca6d019f76a82c","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.0.8-renovate-non-major-2da079d5922e39e93ade061966ca6d019f76a82c","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f0fb46d878d33005e7fbbfa6fd130e1590c28def","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.0.8-renovate-non-major-2da079d5922e39e93ade061966ca6d019f76a82c.tgz","fileCount":98,"integrity":"sha512-6+PM3e/sWUEnrHjXmYPNZ2DVOS9xLWUbMUHoBpgBapKCArdtaVRW8AAEYHScXU8HYLjF2syRu3fArsNQGNe/aw==","signatures":[{"sig":"MEUCIH6X716OF22Q7GDkO+GOK0mNrZWNs/NVGqxA07LXO+aQAiEA2+WOpBNN2fTAcgHTA/lytAKy1QJUGG9BwdJnBe0eByA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":602253,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgMOOSCRA9TVsSAnZWagAASNYP/0lAxg0qh/284VRtpxBn\n8KTZKV2+3JPUqZQVLh1XoLRWvXJiXTuOVCwZW+Qkt3YFoqzv+oO0fjHpdnp2\nwWRAZBJVzvpkN4gxiIuIYdovEA0aalE+VMDc9IDW5wO/K3Z+OI6BvXXsSgoi\nz9woEUL5SGTN9Yp3cPo9/BCZ/4NfryxS2nsCBOAYOyswB1945pJXtbLSKmnw\nl66/i2v56BY88q5DeIc9ACsKPxDoBCFoDcGcUDXawjgw0W52FE1TTBJOVd1m\nVcbA3VQDT5LW9RAnMxn4WlFHEUJOOK1RMk1NDWvu+fhPuZAufuvDc+i5gNWP\n5vUtL6wyKEKEgelFCGdbkKAtaIYvxwoXJJMye7OL4lyV5obx9Bzc3X0GzYm1\nd78PY54tNQMnuD9y9txQF3GJZrk+4fitXQfrnrhYS+t1fBHcYvAledHGaT+B\nVcJffuccNDiPgbzFvUCtzXeAmO+wdb+CGyXmio2RmGyKLLJIPU+DxgK2vOFE\nd3fY6s+7Fwdp53y824yvX+DIUeKLCLYTtJmcSOBnJ8yvfHyVtpgufbWMHiCL\nBJcQY1kKqJ0AWlIjAS2T8DRJIROs4cl2Ob6Gyhpz/o4q8UYskzRrvzlMcnSB\nz00U16gWpmZ6/AkdWZT97AePJJxVNPUysuL5tWWs5P91I+NYHc/2g0kyNuG2\nooyk\r\n=OEdA\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2da079d5922e39e93ade061966ca6d019f76a82c","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.63","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.59","@balena/jellyfish-logger":"^1.0.25","@balena/jellyfish-metrics":"^0.1.89","@balena/jellyfish-environment":"^2.4.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.0.2","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.103","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.0.8-renovate-non-major-2da079d5922e39e93ade061966ca6d019f76a82c_1613816721897_0.7453716816317661","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.0.8":{"name":"@balena/jellyfish-core","version":"2.0.8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.0.8","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"45782db553d48224fcd15ba9a41cf9fb56199a66","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.0.8.tgz","fileCount":98,"integrity":"sha512-JA3I11S8C9J0MIPmS8NB2U2tjOqdqAlNhqx3i10FcJRGwfrzh7oD3TywX2sUHCb0wM4UjL3WugagYSHmtIYH2g==","signatures":[{"sig":"MEYCIQCIHb/raPEmSa6GNw4lxNsGWxwpVBehxUVzuQFB1BKe5QIhALEZdf67gnNUXjs1GLFhs4rFinXyC2yjRG7CVdCt4laF","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":602193,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgMOUgCRA9TVsSAnZWagAA0f4P/i6CBDLXEvC3Adfql9jW\n4jct/jBXKWyNhDkrSmReMb8Lqz2F2wJCfzq7IAlg68hdf48fvZkF4SHfRnO8\nCXcEezAsADOI9h/UW2DDAYYnHxvR3BZTe0ea6mw61qiQEKhauKb58DCyWOIZ\nZ17tWjKuGVhhihQT8Bluzzj0scrbmYZB0PdnfJA+Qa+wMEaiwZSFFbH0hPEc\nw7OTEOgsjuNiy5wBcxKhcIrZa8vx2Ycj9rVqg/I/4t8U9fWwOsuG/EseEsyz\n8ffS3qsvCeox+fQcRRR7VkS0PX9wt3aOPOASGRopJWCDeRMPSbq0Jir/DFoi\nz5pEBOIJxhMmBWcnu2xDm65QaO+FY9OKTBGFu6LXkMrDs4DsIh0fBnmA458O\n0u5EHGnZijTKdHiLmeT+Ph17rqf2ZyVGkTqCsWhOX4apnjspy9N2X1V8KxmU\nHEvpUYxdSR75fVLSS4H4EgQx2YEyPjW5MGqxdG+16s4G1SCCajqEwpE/lJZX\nkyQwAsXuQMRiX88chq69Ib0V9wrFEBwvnGFphrvDUCrA9ChXNTiSe/MLZM5+\nkS0ry0Y25ueiVwRqobCsl9skT22AiN1tkMQaoFHNLrAGNHduPogVQsC+lIuP\nBqWzwAdBziOLkr09LMAQg20fXcr98SpKi+433vylTPWzWXPPFoPOA/rMbbFl\nUU/g\r\n=xkAy\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a6443c3d38b215f7563918a9afb0d5b5bc3b1167","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.63","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.59","@balena/jellyfish-logger":"^1.0.25","@balena/jellyfish-metrics":"^0.1.89","@balena/jellyfish-environment":"^2.4.13"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.0.2","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.103","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.0.8_1613817120314_0.8988842498646925","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.0.9-renovate-non-major-0deddaf7c1d6b02f9d00a62e154b1e4c59a7da0a":{"name":"@balena/jellyfish-core","version":"2.0.9-renovate-non-major-0deddaf7c1d6b02f9d00a62e154b1e4c59a7da0a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.0.9-renovate-non-major-0deddaf7c1d6b02f9d00a62e154b1e4c59a7da0a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"7cefe995099c23c4f2c0d3fe487ec1431eceb329","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.0.9-renovate-non-major-0deddaf7c1d6b02f9d00a62e154b1e4c59a7da0a.tgz","fileCount":98,"integrity":"sha512-T8y69yoEHpbJrC9HhGd5IZAsN0HGmpcdZIRY5B+LLLCL9KetNH47PFDOAxMEaQqbwLZICBDAxJJLj2Op3TFXEA==","signatures":[{"sig":"MEYCIQDmpDNJhV9Pvm9hpFEmfUbSeSJ0CGItKE22qSlI8rMqQQIhAJmhRmuWnd+UiNQQFh6GPxBbf7809dVQ/PpQYz/iaqki","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":602640,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgMTymCRA9TVsSAnZWagAAjfgP/AmJynkjJCniF2t1b5rP\niAhR5vDas1cVSru3nQBffAUlmJIyTyAb/B4KCkradaIA2GLucMm2hdPxgpQ3\ng7qehNXWTy890jrKrbm2QSaci8R1ild26TK0r+fP7Bk33GXBFXN8SIgbHDP/\nbQvEFq6WHO7PGqqG0CvdTbz/aThhkYhG4jN26duVZtlMnoLVfMNtlwQB4lJy\nz2zq2R8WnJnLyTdCZ8d9YPtYVMhxlXziez+ZHJMocPr8rIf3QpeR6B9nlZFN\nXUYDuZpqTPgNsHEqYaPwJ4h5zO34ykATEw7DVoUiLfHeAW/+fH7QrRUhBI68\nSOi6ff9tYHUTXVKTL6zQowEmYUPkfcYaf2wLj3eqSwyoXc9ANmnjZkmbvxUM\n0J4M4Vwi/EpCdrxXMw90SgcuVc8F4DjW3jgrlKsuVbq6EDx6VNvOh2QNUROB\nNFDnIwNhkAlY+pG7uNMf6LO4d5GK5BfMhsTVonYanpydaveqk3HtsOegjD+/\nhersSs1VMy2WwtT8d8HB6Bc+dC98RHR63jF8ZwBLhNsZiryKdCyONjMWHZI2\nBxVOb67tf2IBfpIAFGcbkcsQDNvdoCbe7csEzILj6EfzKsJFVpelot1SWe81\nIzgnkOnn1FsdOw+hTofRCQyjxfdgM/BbRfWESVjglvgdoGVfoy1O1//SzZRA\nnMh/\r\n=FxQC\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0deddaf7c1d6b02f9d00a62e154b1e4c59a7da0a","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.63","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.59","@balena/jellyfish-logger":"^1.0.25","@balena/jellyfish-metrics":"^0.1.89","@balena/jellyfish-environment":"^2.4.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.0.2","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.103","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.0.9-renovate-non-major-0deddaf7c1d6b02f9d00a62e154b1e4c59a7da0a_1613839525333_0.3383542293939783","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.0.9":{"name":"@balena/jellyfish-core","version":"2.0.9","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.0.9","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e2b78096bdb80f8cf4934a610e33420ef20916e1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.0.9.tgz","fileCount":98,"integrity":"sha512-756Uo68opHluPNn9UTQ163vcQ1W+5h48e2CBGkZcICF0YRdfAhw/W+17NRW04/oXp5IkbmnO7Qqi4jdqQubZzA==","signatures":[{"sig":"MEYCIQCrdZhoVAe9gWHitjc3ufIU75bQjsBNfreHhgq+Hrk6lgIhAJpSNOAm4KHLhDQp1Ty53mLRw6O1gOuc2UssaezSeG3x","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":602580,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgMT4TCRA9TVsSAnZWagAA7oEP/jslfrtQPwhuHSV+PZ9K\nwn+czZEJSejqMyMIH+9+XGuhfHN/Eo7Or5n6/gC3JpFY2mMGRIWE8U3EXYyE\nCSNY0sqPS0AExmG9wY3mtjZF5QlULtWlLXzPmNaV5zhWKOh3X4zeEWTlc84H\nMCR0R8reUql0VgFOZQu4To26syJD2if8ptcmGeis+bmjXZ3ZRYHbbLz7xwIh\nz2RQtGAH2BjKg92GXsYRxhnjmyW+GYERLBdNo0XP4SN5U9/Q9PaXn5YbJ3ns\nAu8e6+tLefxCs7Btmbo8dKSD+wXg3IJoUVyRb1UNW3sIDDjJoqhX1idKbU1Y\ntyDP8bFLEa3NihRdL96+G5+Z1geJ71GvFTFKwC94eeUz555x0+hobNW47n8e\nQIj5r559i4ntgMoONAjyNJaPHY/ZTs9BLxA8rAYUy7eVD7CsCm1HDVezQ+H4\nLZjMrCGpqNDiXGSujYwWDrXn8NxfDpqBzHwn3nM4JyJIF6S4zAUB4aSA6ZoX\nQnOiJaeUxKJb06q1YYNYghBcjddgn6Gfe891/IPNpP2nj/+rPR0cAl4H/w+i\ndxDuMRIEhgTp85I10uvtneHEI9Q+r07uIcJGICk90VdEdbd8kRtkDE0pCAII\nXykDwcpJsNYrQ4RJN0oXQkiVplw7JEg7wEUiYnLDjcHasR5rx8C3xV24yL2K\nsKMp\r\n=Vm0k\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a46ac7a1cf24c8ae5c67a4db1cba96cbe6d5dad3","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.63","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.59","@balena/jellyfish-logger":"^1.0.25","@balena/jellyfish-metrics":"^0.1.89","@balena/jellyfish-environment":"^2.4.13"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.0.2","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.103","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.0.9_1613839890732_0.6468580600463385","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.0.10-renovate-non-major-ef3e11f60bae284d350b18d7e6fcd097fddd1ba0":{"name":"@balena/jellyfish-core","version":"2.0.10-renovate-non-major-ef3e11f60bae284d350b18d7e6fcd097fddd1ba0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.0.10-renovate-non-major-ef3e11f60bae284d350b18d7e6fcd097fddd1ba0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"fa200b84d61ad6587155580ea7478bb11c8579b2","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.0.10-renovate-non-major-ef3e11f60bae284d350b18d7e6fcd097fddd1ba0.tgz","fileCount":98,"integrity":"sha512-1VvqqDcRKeFgamYFtcjjsHP1CEldUkbw/xvqaJyD3NRlf6l7GAa4/KsB7OcM0NIiawbdHxIXMhibElCGOSpLRg==","signatures":[{"sig":"MEQCIA+VULtE+/RQkP9JNO0Je0FNR1hGs+KqE5XB1j+iFCrHAiAhwUcWPOU4LMobcny6v2AMC2LbqSFCowmXdUE5S6ae+w==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":602990,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgMT8UCRA9TVsSAnZWagAAR1sP/267LWAoibev+/gtMyLL\n7vAXnLF3vD88MMiVYeQgy37Ck43I1VpsZZ93UpZHuaYv5ObgZieREhVvc2dd\nyYxYDQLnXNImeY22uzoxwY9c3OozYqaLz0Lzi7XRgTlI4/+I0DLtxVvMUL56\nIPUwAID2qJW4oOFC1rv6oxkiIPEjp4T5/dQGHqRje+aW9+178Q9uBj7dCqcc\nxKqSpxUEXIqLFPalb1h5xJARCxTPuxlgrnKMRdbrTgsmPTnYTNNZ13UsYnkU\nSxKA5lFkJV7soQPbAJX55Y6DJsh6gpiCoQUbNGrcDdsJ7eP5jPsT9woy06eJ\np3J2w0tqhHpBP6wu7yB8Mj/Ho5pxzPAp+qZQTzLMaTGjRO+c47JcIWkNTV1a\nSTvJFPyCrh4o7p50vWIXs+/27JDxuScQ7YjWBoBYGXIT7WcvuddTHA5oVHX+\nKK2OAuaJmaAq4OeJylBp2a0J0q2WvspipOHyHxURD9MFQj1EeUiLv5lIbQhb\ntksAq0vAkDTjD8VwRzumRzGCln/YQExjLVacBbkFux1MJreSNCpuE712bt5O\ndMDinAOfxYP46ZRxuR5j+JL/ipLrAoDwRn/34/WCWj3DUuRevMNbRyXnAECf\nHYDt2SgBkCVsa2LXWer5/jlW9IoeQlUvXwZI3Gtw+6QFZvd0ArV72+s184ok\nUCSb\r\n=HolP\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ef3e11f60bae284d350b18d7e6fcd097fddd1ba0","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.63","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.59","@balena/jellyfish-logger":"^1.0.25","@balena/jellyfish-metrics":"^0.1.90","@balena/jellyfish-environment":"^2.4.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.0.2","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.104","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.0.10-renovate-non-major-ef3e11f60bae284d350b18d7e6fcd097fddd1ba0_1613840147737_0.39706804281430874","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.0.10":{"name":"@balena/jellyfish-core","version":"2.0.10","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.0.10","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d7ba72d5df40378dd4efee512ba00bd13114e379","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.0.10.tgz","fileCount":98,"integrity":"sha512-4P3jD2Y7cNequeJzUTTGuBa/OE70Fjs8GlAJVgCoGEVQD49RX+wuMkLd4DWmarN81vysLKwJGGGQFuyxNItRfw==","signatures":[{"sig":"MEQCIBGrqMC0+Qhg95F3AnT/Tp4AhYYp8vKsmXw7FDP8gtJjAiBx0ZrF301qSjG/Ju1jWf1b0NQwstAV5CcKbG2Ew3tWqg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":602930,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgMUDeCRA9TVsSAnZWagAA1QcQAJ+GVSNjf5GRlq5yy2or\n6HnPp+vxq0r1RBNvuNn2yOpcai5jfFEcDbuUZqZ5UteU6x2+wMjyMR/cQuBh\nC1CfyWtF4QEo683J7oMfln5LWn4nMF1UdsK/C+9kEPfSjKN2+SFRRePQbOgF\n9mVKT6mT5BykyR5QmNxEGsib7m3Uvk7aPzPDjNaNLJPXonavKVmNSQPPMyU+\ngQiPYBKPQiEygy6fyUqwaGQPwILT2gosQeDESLu/B5xZMqRzTbr2ryLpyKOt\n/4jnJuu/yiA4y3ufK9O4qh8zRSMd6B8YY0J5ym7d9+OShxQ+FQGunt3td2Xj\n/+w9/Z3b6Azn+rlniCV3X62KHwcxYGgNdK6Y5fS1UveTmzx/QhkqRMRq4gUu\nURMGKb0eyfeESDm7ptJo/3QoykvgY8ho6eudGDan/a7AfbYQ7PmjkH2rPBtZ\nyPs6zDPw34qHC6E5Cup/e5fONodi59PBqFlGNNSEiB6amDA3xW7VhBB7fnnM\n/zfFgq6c9E8py9J8J5JzlPEZcvUOUn+JAs1nllIF/BuP328tA/FCkpRMZH0+\n91gPw4XfwRHBmAPWSjAWJRHsz5fTKKs5VA6wepNMzfDDuf1a8VPNQ/Ic6AoE\nZXwMZC8/iSHKrZmdDhDuX17qCGor9r6Xsmqt3a8LyomxzKdsuQzea0r+vUpg\nZ3ii\r\n=dA6T\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"136ecbaa05d68e07c142ec9c191d16abe467a648","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.63","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.59","@balena/jellyfish-logger":"^1.0.25","@balena/jellyfish-metrics":"^0.1.90","@balena/jellyfish-environment":"^2.4.13"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.0.2","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.104","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.0.10_1613840606297_0.5825191345209602","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.0.11-renovate-non-major-d036cdcf1b7465da7ab184459ffeb201e642ef4d":{"name":"@balena/jellyfish-core","version":"2.0.11-renovate-non-major-d036cdcf1b7465da7ab184459ffeb201e642ef4d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.0.11-renovate-non-major-d036cdcf1b7465da7ab184459ffeb201e642ef4d","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f2d03d987e66128a20f87770bfcaafc6395b6f63","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.0.11-renovate-non-major-d036cdcf1b7465da7ab184459ffeb201e642ef4d.tgz","fileCount":98,"integrity":"sha512-uOTNyLAzJ/7QsV1wVxUN3cFi4GQ5YGCDSccLiPcxgWWD9k1Y9RIPYdJ2d3h7Xpc94J4JoEi7n8tH8pEdCiGFvA==","signatures":[{"sig":"MEUCIQDrlBFh2QNHd4PPUk5vFH7XsriGj/0YRAhWYkG8Mv3ovwIgNW59p2O4UlsTRIfTR9ti8iPFcsZtLZaWk0V8LIKSTeg=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":603339,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgMV5rCRA9TVsSAnZWagAADMAP/1/hbn6VSAdz/yhN3cVN\n5EEpNoU2JmuH6933S4KwJ22IlaZ3PlL8sXqbjimtFy8ngcuseogJKDaxI9Dm\nRQRbyTdabLFwNZpce3rZsBqDKIVKB3sIm2BdJSZNQ0484ViFxJkAdN8eBijR\nA8BRPS7K/NDgOfqKVcwvuFGc+iDR2IU+hlXN/GF8dE98zhWXOF6kDqr9jzMy\n3qMh3iA0NZEU5rDR3HY5rGA8fu8UbCSzhJCCNiv4XJ+jX2S9nAYxi02FcyWE\nK3zkTwLLukVv525TqUZHV1jKIxGPmwKA945IyXRaY9y1PkWC79OoLuQbeLm9\nljJYmx1LIHytZq78lP8gZ2+xVZ3oQZd6jYyn14q+pQp5f4uLY0ItOarOAatg\noHJnEjfLwhRuLF6igdiJQnwQnTiaI7WqKdwQJiRFvn0qFWtsNc/CLh1SnQgx\nBq1I9uq6YLaBEbukXbTQUhTeVPmTmH12mFN5ze/dQswiqrp5FHw45zxbWJw0\nM3scTghTbgDLwKEWoF6p06XktoTax6TvuCtJTlOE6ZsYlPuiNc9sfP9eR+eB\njVy2q1b5JF9J/QQdqNkAoVOW+YzVlkt/qDf6RAVeekTJGZvJn9GYcDo5z0XW\nx2uiRYixxKkeMlGlOoER2lvcuZhdZL61tG0YmcWdjJ+AGejHedsLXoSJ2IiN\n6/Q5\r\n=zWkn\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d036cdcf1b7465da7ab184459ffeb201e642ef4d","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.63","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.59","@balena/jellyfish-logger":"^1.0.26","@balena/jellyfish-metrics":"^0.1.91","@balena/jellyfish-environment":"^2.4.14"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.0.2","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.104","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.0.11-renovate-non-major-d036cdcf1b7465da7ab184459ffeb201e642ef4d_1613848171344_0.7285547343307428","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.0.11":{"name":"@balena/jellyfish-core","version":"2.0.11","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.0.11","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f9d180df404e649bcbbbd86085acfba211610b72","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.0.11.tgz","fileCount":98,"integrity":"sha512-M2QfAXORfK6O/VID5jd3DTtXIoQ7TxXsUO/jhsMoAUP70ZQUWgIQNXpJeFzV2kAdSeLzhBAvaOJojpN4n0loww==","signatures":[{"sig":"MEYCIQD46yac1aa/V6VUkvYqAhumfwgJsko+BB6ZolFp8U1t/wIhAIMh+D+GX1ol553pR2ZlD6K6lwm7W1X0Prj3Ip+o8R3L","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":603279,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgMV+6CRA9TVsSAnZWagAA6iwP/3pQO01Z62o8lLZ4l7tR\n9M5m/oDq19izu9XhmgNDMLmRppHn1GRe7R01UwDD4fgsYG9gaZXyPKgA15+b\n4mVBk2LBcQvV4dJMmeTNAbxiC3qDXqDWAc2F2Il9zAkRaJLQJSyayK5uQbcG\nrZHQsYp4kljIA/xFlp6dp0bqkAwLWWudTYsaKO1pN0mzKduTP1YoVAmGRx6V\n4FA1qoiB8GmK+yfc7zhm55IEGgG/kHtiVT+QbEaigGZtJQMVNQccUEzCLycv\nWbi+HY7s2Lf/RRAqDznyGpU8yiLwuUOOIsFoS0ofurnH2/7YUJK5QDooc1iW\nazckmMJGKePpx/cfQfqp5Ctj7v8l5OBT0aYVDSEcJuHmttT+WytOXjuoGdo/\n57tNItE2mam7vx1GxpyB3AQN8qYVZAfAvOnK4zqIA4AqfWg4DfLHxWyWcEW7\nxkzgdjO/v/QM5WaU5XnKcbVCOQe5P6wN4q0iQh2tugsqJ1voqJ/r76ArqmS1\n5dbHyiE5PilLIU4SPirG7zN2vUfd187v2hrEmNfcILWbcEtzDo5z3S5lUXWG\nulu6LSx65PsNo1pnpFpgkxhoSdLr/ImaOTSMFzReq1seVdM8kwQkdQa3N595\nQ/Aw2uEaxs3untgyYUG5Hm0oW59mTobqjgB89Dg6gBnB1tNQmYwSDaduCJbE\nu5cQ\r\n=n74N\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"abe6e7fd4d63b6be7919a749ad76b47c15c5b64a","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.63","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.59","@balena/jellyfish-logger":"^1.0.26","@balena/jellyfish-metrics":"^0.1.91","@balena/jellyfish-environment":"^2.4.14"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.0.2","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.104","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.0.11_1613848505600_0.2399553392030791","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.0.12-renovate-non-major-818e13c9da713a5cdcdf94c23c48f1116ce108bf":{"name":"@balena/jellyfish-core","version":"2.0.12-renovate-non-major-818e13c9da713a5cdcdf94c23c48f1116ce108bf","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.0.12-renovate-non-major-818e13c9da713a5cdcdf94c23c48f1116ce108bf","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e57c1f749a048db29c672722126ed719033ed82e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.0.12-renovate-non-major-818e13c9da713a5cdcdf94c23c48f1116ce108bf.tgz","fileCount":98,"integrity":"sha512-pk+WGAQnLzssdBYo89q1BkjAAYerdzkW3xG3UF4RK/A7AoyaDwqBld8Oibcwgl4FiX7veqtAIbuIoalVTAh2yw==","signatures":[{"sig":"MEUCIQDrWLJA3T3qAOaccMl+yf4TYipmjFAw9HgL6jHDvGdemAIgVtxs+fMqBNsXsUMvBF8ZjWWwUI+pbenM6yo8DfGktAs=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":603688,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgMeaQCRA9TVsSAnZWagAAU7QP/A40yrW5daaV7dXZ3ASy\nGBPiWEnxnT0RRswW4CkpPUsoTWDJCzw2prDAzDA+5WhZd5jQqKTUO3V6dOEH\nwDTlNLPrdWnH2IPzjfHo1QHWNh5zYhKDNPk/nlAjA1zEHx8TBulO7jGqn9XQ\nMz5IpARlQMl3HsnNtZHKzpCEbpjwZklTFiuQaUlaWpc1Qy4QydvUIl0UAKwZ\nGZz6efs02xfEsKh0Rb9rwf3KNh3DPccf7gXDBhNE4l/gqOLqA2FPuvPNrPL1\nNkPXmwTOrnw0EbxMJnTfd7bygbSS0+YFdvnpUHxeKjMVTk2YxVkqFm/wFfHj\nF9+g86F+LRDUKOzBf17PW88NHO4I2+MRye3GDCashWcCXPzKRl5WSxg+YGxk\nWa/PSSE1zk0vI0JPHrdiiUBhYctxDuQt8/vu80ohiHrk0Rz0Z8yuc9botOlk\nHib46XLIJLlRILzL7UZ8W+c6tm0SLF0DZZT60HdpGE/SEOTmkAjrxiwY/4pc\n3HXD8Blt5hVMPYM4mvMtr5B51i64LC8iGLBZy0VqaV+y8qohV0zGZgQ2NOXN\ntYiMypDejlU0CsWAty8WFxeki4HvyzcygO8U+b8AHEYg1fBmj1OmsDBLkAPG\nk9BLjfGlPlIw0ONHDu/UzKBWDpQF16phKTodrSCPjRben61qnD9sordXXbyD\nF4N0\r\n=1ARk\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"818e13c9da713a5cdcdf94c23c48f1116ce108bf","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.63","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.59","@balena/jellyfish-logger":"^1.0.28","@balena/jellyfish-metrics":"^0.1.91","@balena/jellyfish-environment":"^2.4.15"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.1.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.105","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.0.12-renovate-non-major-818e13c9da713a5cdcdf94c23c48f1116ce108bf_1613883023691_0.9325956603768506","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.0.12":{"name":"@balena/jellyfish-core","version":"2.0.12","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.0.12","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"76383a865e4b535db8f36850295308465022307e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.0.12.tgz","fileCount":98,"integrity":"sha512-NqPn8u+RZREtTEnNoIKY9a12MTVOun46WG7iZL8QHkNRnbxL6YcHyOigUPeYE5cpBlHnC+krjtto2EsMG4U6gg==","signatures":[{"sig":"MEQCIHa2HegWIZL6toKAWh4JfCt6W5wfRvhaqFhvTfIq1vNdAiA2kRg8yF0UC1V6hw/mi5RRX0gOZAnZWkK+6a1uxHwAlw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":603628,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgMeflCRA9TVsSAnZWagAAZJUQAKJ8Xwr9cU4bhjoU5SQd\nQiY/inHN/PN8YmPUYNVzSyDCC+Q73K2gSs+RePIFOTpEmJzIqlH7NfSBKeIv\njZUu1ctvLyt3aDt9ihbyXFfmHLBjWSinell6WEJNXlhnv36GMkMIZ3UadAmN\n6r7iNzQKN79cJa4knICGY+5fdKfm/UmD0BWcbQolKVx+8o40+7wN/j+oU9QL\nfDaBcSa3L4ckAXcf1VwTvS6/qnhPPMsV9trWnlqXiHpyNryzg3VaNNV0bMsd\noV5MFD9lHHBrWmgvO1KMuEbCPDyyRW6lWw9h6OftOATjs6kpTLPISJ4CCF2G\nltXXaHIwDeLm+7D42dRi0HtkDxtBqROxHR3mvumQ4CJ4JiXNDFs6g+iwvJoO\nKPmn9W+88vjmitDbyb9qnrgKprnrbS2uxzhNwIhc7uJ2fUYiPHCsdzhfPJY0\naprm1RwozuHvVUcseWpXvD7H21pUJbQUXPr41gjaFFSx4+QlplMAZjOjtApe\nDFf8uugfBUP48BWOsziVsIR4hu5clJDw5iUA23HzLvGXqdaXuB7TvCyj2Wt4\nuzPV4E5T96NvLUcePsJ1FIkMfhQpfU8/hu2f4Fns1arVpgZBtigLdX75E+sL\nEMNAe7nLkkeYAnZgT/Zpid0R3BgRyec4fyW+rkAxy2p5b4S9ly660842EFzT\nl0n8\r\n=8Y6v\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d6d72eff79d4be254ea596ee84bc4045e6fee07f","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.63","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.59","@balena/jellyfish-logger":"^1.0.28","@balena/jellyfish-metrics":"^0.1.91","@balena/jellyfish-environment":"^2.4.15"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.1.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.105","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.0.12_1613883364557_0.5417537802885826","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.0.13-renovate-non-major-2fd8f88fe0d70a5fc1eb896963a22f5fdadec77c":{"name":"@balena/jellyfish-core","version":"2.0.13-renovate-non-major-2fd8f88fe0d70a5fc1eb896963a22f5fdadec77c","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.0.13-renovate-non-major-2fd8f88fe0d70a5fc1eb896963a22f5fdadec77c","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"24bef4e210a384dfc7eb9d1106982c6c4199f403","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.0.13-renovate-non-major-2fd8f88fe0d70a5fc1eb896963a22f5fdadec77c.tgz","fileCount":98,"integrity":"sha512-GtB0XmQLyJKuXYi9vD54bt19oUsM/jtPF9kGFxJC/eeh7VIcDtklDSFIiBEBSlgNZ2Y3VZFU4Wr+Dd4qiNf+aQ==","signatures":[{"sig":"MEUCIQCq493M9YDZyNtGtxlz7peXSsZV8BDculA7dpp//xa5jgIgII9GC1KnDK0gU9dLl1OzO/f3/CDQxSKdOmxgfoEH8y4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":604113,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgMfkmCRA9TVsSAnZWagAA4KcP/2LJpKq1Wemd5HcYG0RN\nbeDIw49Zm4ONfNiCyOWK+qUxcKgTM8rk/onIKO+gexMCn8BD2FwGwhv0gM2T\nSbXg9aRf5k4P/XHhyReWEChLHaaIYlvKxRbupfsI67AX9FF6FpinWwxHTaAk\ncYgnNAJA6CRhMdDfmMEQp3AFr5FDIVP6xYHDBveX2vB9oKtGLQKUuF+vhxKy\n+V8oiT/p6D1XgWra9223oDKR3UL2X7bOeCU8VbyKiYUYUyZchl8xkTJcHr38\nS9A+dbmE917a19qofo87Fq2XpVqcwDf+/NE5LXx1V36ev3xhi1ONOgNp7Ba6\n8APuAEO5N/01yOt3VsEwvf0fA0UpOeZnH9XgyJKC/y+s3/Gw+Tu4JUFubWEl\nsNDYKeN6WLB1puuGi2IGyK86T8UYABwHHyjkvB3ubQatIDM1jEFc6gXx8wj6\nB55EVafDWDlhBqpLUdY0vDVYUw9Q6HNr8E3n9eOS26VfmGdZKN/XXsCLDPqv\nq7/bD/zdqj5SQi9Zuw7g0qiTQAdLj64d6YtSEhGXXGArcblb33W8FYbVJPbt\n2/N1069hCfi1tSeET+CCF8i7JxJlln555ACa6AG+KFbCrvKmoIsOYWc+UL+D\nvTTPTC39ktjbGGfOFn/MN+qbP1rVOno5/nLz6coPbF3DhXUi/8ik3s4hJDRS\nq18V\r\n=l0yh\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2fd8f88fe0d70a5fc1eb896963a22f5fdadec77c","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.63","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.59","@balena/jellyfish-logger":"^1.0.28","@balena/jellyfish-metrics":"^0.1.92","@balena/jellyfish-environment":"^2.4.15"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.1.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.105","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.0.13-renovate-non-major-2fd8f88fe0d70a5fc1eb896963a22f5fdadec77c_1613887781545_0.6026295657322835","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.0.13":{"name":"@balena/jellyfish-core","version":"2.0.13","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.0.13","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e1d5bf52e3f0166f263024fb3a55637a4f99d2fc","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.0.13.tgz","fileCount":98,"integrity":"sha512-sHdoL3tnOMfIWneENS/CEEA/y/Vy7hUaFK4MZctLIP8i7UAG9zTl3WAT3R0yg2Huqotwe3qrCV0zi2tMj+cSXg==","signatures":[{"sig":"MEUCID1l9OQsqTD8Kc0uzQN40sVmmIfJ7nLUk66O/bFZckYjAiEAv8LVChng8bM68m04nH1vJco94KN1vnqnWlk3EYFAYUY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":604053,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgMfpfCRA9TVsSAnZWagAAJjYP/Ak6n12FfuCPvcOWbw/p\n9PNax2yfbC3F4t49TqeKfyBLeTQTOh3FZt8WoSUqrHcBj7NGM1CSdoazjska\n2oYVA471DW0YzHihw1G+/gF4G7hFqIYxJPstLlDNr2vD3uKwsnRigjX0FzEg\n7SFxyJrdnYWeG5R+nH576r2eYxn78cTxiRrpOUu76crF5o1S6MyUcgNBNMgp\nPrScGqeUmHD3xkVtB7m/F2DVcMsuCxJ5wkh4b2hLOgjLp1OMpZp0rIVFu4nK\nAIP6bJr15Pfi/1K9AOxB0yPw39tuiyPFtBP3bCILBmriEUJ+/g2QN5VgLsRu\nDws99OCYamaKMOfO40+5bgsw++Wr9BoJSTNdWosuIG3cUNrL404nZIsccbAu\n8P2jCZt3IwEYixZ3QO765E4ZOpX7f/+9JWXyRm+X0wRp0CXW7q74mi08kGdE\nKF6CM51cZS8abxOb+Yi+VSs4hWLE9W2i/8H75D8RG0R/gtt4SLNFlKkUtQHG\nSgkW7KipDEsTtqAHOmhS6cBPaKKPZLumwCcd/ldyrDsc5/CdCnqkhRIrA5zm\ntzzPxQXiycrDXj3lIRRV2mutfKp8wcRbMt3E+H8TWwek58JE4TYIZ+uOOVOK\ngeEBphf87sIayF9oFhmjqsrbEv2AIF3b3WTLTC081G2Vf9NWqHPHGE6Z0NM1\nH0zK\r\n=eDK5\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0a81acb13d141020505a00501296b770dfeb3a81","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.63","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.59","@balena/jellyfish-logger":"^1.0.28","@balena/jellyfish-metrics":"^0.1.92","@balena/jellyfish-environment":"^2.4.15"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.1.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.105","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.0.13_1613888094862_0.5559225463188542","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.0.14-renovate-non-major-36642058c18426d842938fcd537e32e16d86aab8":{"name":"@balena/jellyfish-core","version":"2.0.14-renovate-non-major-36642058c18426d842938fcd537e32e16d86aab8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.0.14-renovate-non-major-36642058c18426d842938fcd537e32e16d86aab8","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d33b2eb486e9de7f7678feb5d9e4332a3cb3c24e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.0.14-renovate-non-major-36642058c18426d842938fcd537e32e16d86aab8.tgz","fileCount":98,"integrity":"sha512-yhpz2ntkMyBxw6w1mjoAV9sV5Wv/O7b/XHYyUR8zbbyWUuqlRLX8bcp2+Z4Mt61ovaqcvrstJCzyKvqPwbAmKA==","signatures":[{"sig":"MEQCIAIHTe1K6GamGTNTn+cNMJtoBsQSZ8+X5xe+CKjBRV7mAiAdVJx+noYikHx59AMonDbOITcYO9iboyLTNTl08dKIww==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":604462,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgMt9rCRA9TVsSAnZWagAABxkP/ihJgifNVG83PKNnzpK7\nakRQE7qFqUFG5C0AQxIqSMr0tQYl54m0gzSaSTJSc+rVs75nW2TTO6T/syiC\n7XSR2BDNfI5Vxr4JLxHOZDmur86fR0lgVr0KS85f9J1EYEBK9TaIhBohBSmv\ngkQ9iS/YEDj3+SMode7SGJTGQrx76uDs5J8OSjjp5rOqkmsZ60eLpfUwqhbR\npxAB4pUTAZnxWIAstSFYb1rDYeAyQ/Ve6YIZVu+72cwxe6utnPVOUItwWzGa\n4WcQZh/tCLXILUhsmbsYadTRcGV541zPprH0DnCcsRGTEc3TUeeeqIioCwSr\nYxZvKgJKvAIKHnET6VPOZMj8prrlP6zeCDp13reg8SP6J2nht4weOLy8cLlk\nHPubTqbau+oajXyjtY/9PZ9csufx+N73y3NyxbTXlv/r3VJDMp7b/TnW7Glg\nqU5fx2lR4/PervJzQYJQse4DbXYXZcyohXyVQPcFbUr2U7sSdBSrGYn+ILt7\nUL3vjKy1TP98G7OovThlK7J7fHqoXugQyaTohrGZuyED99U1u9fwoge5eSb0\njhXHJCfgPE06jVIDVu5rQsSisG4mWmN5mLcBljaR82XZOUiN+JtT1m08Swkh\n8qdFotk9j+T7MUiKFnctmenddUs7uW6NxGJIAuH2+3b7ELmtWthz2W20JgNR\nbICa\r\n=EQvP\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"36642058c18426d842938fcd537e32e16d86aab8","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.63","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.59","@balena/jellyfish-logger":"^1.0.28","@balena/jellyfish-metrics":"^0.1.92","@balena/jellyfish-environment":"^2.4.16"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.0","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.1.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.105","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.0.14-renovate-non-major-36642058c18426d842938fcd537e32e16d86aab8_1613946731021_0.1033108758788781","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.0.14":{"name":"@balena/jellyfish-core","version":"2.0.14","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.0.14","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f9ceecd404ad08002a51ba8f1743637f4152cdef","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.0.14.tgz","fileCount":98,"integrity":"sha512-mcOQr5ZV32W+rhQ72CQ/0GWRIh6pMVbse710XwRvR6XxkumqpGWLSN9v+JeqRm7nb4fNkCDDsf6p84YNBq8DHw==","signatures":[{"sig":"MEUCIQD6LTPP2sr+hbjJ7Bav6IrUGeAUFrgyza/IZD4dgMW7XwIgEsAhHmAjsPNaE2OC9dtADxIPUef1F2OYOcx8trgjJL4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":604402,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgMuC4CRA9TVsSAnZWagAAtLUP+wTEBeVljwVGM/Q/jau+\naxuixT0Xsl8E5TUWO7QIvusqadRcDVMp9vOmYU34Na+HHk6czadO1QtHtUTZ\n8m4PKrvo1gNxLeuPbULOK0zqxbuxm/8SzMhSoA5edSKNVoxtKoH8t+d/+XZb\nqQH0L5dvN9OD9bsA/OwJyaginTevPzzxskDOTFfMmDVENDYIXl6msrLMXmaX\n1MhTsF4mYhvzmx5Bbv6d4TjbySIyaNHWpvNUvdYVJlpl/Bp9txA8BWbtH/XH\niqPymlmoMHZ9LehGW8S8yShzAOeLkG1aifvZ4H/nAiykGOIlk3ag7Y0UODgK\nkl5hgcy3k1zt4t8a1YYyXThYWQTyUOxVXl1U90goYCgouSlfvRwsDbk+mvGN\n5ps5VHgw6TCo4MmHSlbrGHZJMcCGphoApmNfLVkGkA1KEH7wdKnxaAMbBYuU\n/n5IrS5yjCJHzSlYOJOhihcsAbgWPhe41DjwhnVTsphx4U338l2PkHbS6s+F\neCIayhuaLyGcIjbQOpDF8yXgNUHvLR9WnIEW2qwrPejljyp5kasLXlxug2PT\n2KrQ7LCI2yBSsZ0D4+0TZUNkGHlAlzWxdpa2hC1TuT75tbC7bkixcn/FrwMJ\nb2T1u8swhZxlK0QJ7G1IHFFuRV/b0BgCqib1rFEJ8LcQBMHl+OtMA2W0Kg8e\nKHxw\r\n=fLEW\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7a8b9f834a96f2b4ee9fd18dc2add4dde0fd8897","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.63","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.59","@balena/jellyfish-logger":"^1.0.28","@balena/jellyfish-metrics":"^0.1.92","@balena/jellyfish-environment":"^2.4.16"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.0","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.1.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.105","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.0.14_1613947063701_0.8067525963885163","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.0.15-renovate-non-major-62e920ee879ef3bed393599fc0135fd9e4b549c7":{"name":"@balena/jellyfish-core","version":"2.0.15-renovate-non-major-62e920ee879ef3bed393599fc0135fd9e4b549c7","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.0.15-renovate-non-major-62e920ee879ef3bed393599fc0135fd9e4b549c7","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9fe5012b1b1de3383d8f6b15b5e17e5f570b2bf5","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.0.15-renovate-non-major-62e920ee879ef3bed393599fc0135fd9e4b549c7.tgz","fileCount":98,"integrity":"sha512-KuCADDXqyiye6oYcK0bEWUXG7fDWkpecMNlKIBhNS8e9uU1zFeU32SW4vb4NYfamKnpHNpbr5pPCgsaFvb1/JA==","signatures":[{"sig":"MEUCIF7zwEdLp/eiHpXGmsYR4gzKrF3YHhBjVfP/5VhFvf3hAiEAk/SCvB1GzWF/ahr9OmFutWXoIwzWpWA1mxgjQZQoHpo=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":604811,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgMwEQCRA9TVsSAnZWagAApdYP/1MXgnOT4pHixu9VtCrF\nox1PblgZcwHBkX+w+iEUWnZgSDEnu+r5lliSclWWXN3bCQ7/LwOnwGBtOHRt\npCBcCZEZV7yJcx55s7nRW7wvb4B3EV999awMOUZeL5NHO9T+DoVY6b1KcxUY\nVZ4WaEX8KtuOjKCn7ExkbAW00ljfEX4OZ75cnu99cJW+UB5j8zrorTys1W3l\n9IoZQR+2boS8GE33sk+FuOKVn2psrJiVvjxmSwUHagw1+l3vJ/75ACJWfL6D\nQ49ROQSLBkx+lQzyN3omgC5TktOAvKYyMwjFqEzCYNaPKLKVdgraQjh9/klL\nAjzv0XE5DTXXk0JIJed7mn3PH/iXBm25yg+UeQZCZc5/5x94I0i/1VwGBAmA\nre0He5zizC1qyYzBlR77+5TuoV6RXUNd9QgF3qyILWqoWYzgeOJeM4iypB9L\niZ9o2+oJa/wmdPuqmt0f1K85dX/BcQt+Jb2g2AP8wgt4O7iCVD3URWNE8Tnx\nbx2i8KL6ZuTzczRAVaxdvaAZvIY6uXNSfRQaTbdD/q0VzVWbmd+j9QPls3yf\nlBHpop/KXLbDi9g2t3EdSG1/MqN3FvLGsvUmfucrY/9ReS9v6t0TWxDLolQf\nrqTMjo2/4EWu9F05oZ0EAo6vyjoTZY+WSZEPQ2Lz8kIsCOHdupiSZvRRMq/s\n9H6N\r\n=tGV4\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"62e920ee879ef3bed393599fc0135fd9e4b549c7","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.63","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.59","@balena/jellyfish-logger":"^1.0.29","@balena/jellyfish-metrics":"^0.1.93","@balena/jellyfish-environment":"^2.4.16"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.0","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.1.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.105","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.0.15-renovate-non-major-62e920ee879ef3bed393599fc0135fd9e4b549c7_1613955343705_0.3810392536629468","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.0.15":{"name":"@balena/jellyfish-core","version":"2.0.15","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.0.15","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2a29a72bb58116a6ad05ac69a7da9a8b8756a7cc","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.0.15.tgz","fileCount":98,"integrity":"sha512-7G0C+KxXAupY88QzjzOJyNwUGJtWDXuogvJoLNBI7EOPoKauhdyOvWR4YQy6LqIpUukArU4kOhl83uEqd0HXew==","signatures":[{"sig":"MEUCIQCnMUkmWtq8yxJC3neZbSH3WwEbk6jxt125Ee9GNscYkQIgE+sVA/MygVJ6PZF0EDYq/dkCy6VAyB3/CjUtfm7L3UA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":604751,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgMwJHCRA9TVsSAnZWagAAigsP/3hJhAHjn3nB6BK9WllW\nPdSwPW0xvMrAR2j1gGFtG4Sjz2LpLqX4RwkA12ANcSkUSqba04nh9+Ud8QnU\n66+nm8bWt+2aiOtCpw9R+gbjclJkWV3rxEIo/V/PqZJKI5Bg8a5EZBR1AmUS\nH+ndrlNXOHgjcv/fJRP7NRHlROfNMMEHEAT9TuPSoF4p0WLy/F5xmFQ+lDfN\n412NQWUqm7p0P4N6M62WYKOpsRqjAfBWNQrXFIT+JoVf3udO5iDmSFNBuaNC\nWC4YOUfH9TNmtCedyMAn1cndBIu1LyJAjC1Ze8bbwZnRpuKaCDVn39gZ071T\nIzISHoeZ2NvipKPaMIBmqexKZuT/ADOG+qGeiEQIkYF+pvk7aI//31VQaSfX\nhM6GClK5fmmE/WGsKsim9DkR0cddvgec1hFGOih+EMnWAvicL/hCiNlsUncu\nrYgeO0t+OCTMLZw7uUUlX02+p+CVb5Dq7z0X7paE+DWldA8HlUpsqjyfG5HD\nHDAKun7YsexchHv905BtU1ksg+CHMmQRIhtUdP0bXhbIKBFcrkK9RK/7LPSm\nbXQ7WuFS2IWD3PaTcvYKtHsf4RW+knFPTcgubjHvEw4h8PkSNmP6+agG4hgp\nY07UsODFNKfRq4ZGaJSy37DqbpGUv32xq6xYdUljlH0YO/2pWFx0qHnTCHYK\ngApr\r\n=bi1Z\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"981f629a37a7698239edf95bc42fad243e99a4cb","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.63","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.59","@balena/jellyfish-logger":"^1.0.29","@balena/jellyfish-metrics":"^0.1.93","@balena/jellyfish-environment":"^2.4.16"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.0","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.1.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.105","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.0.15_1613955655467_0.5724236258423423","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.0.16-remove-references-to-old-date-columns-ac2618d2a774f0c80279ffca5267dc33a3d9d86b":{"name":"@balena/jellyfish-core","version":"2.0.16-remove-references-to-old-date-columns-ac2618d2a774f0c80279ffca5267dc33a3d9d86b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.0.16-remove-references-to-old-date-columns-ac2618d2a774f0c80279ffca5267dc33a3d9d86b","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"60e38adc232a80daf9bb51ffac98cb6e78424f08","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.0.16-remove-references-to-old-date-columns-ac2618d2a774f0c80279ffca5267dc33a3d9d86b.tgz","fileCount":98,"integrity":"sha512-tAl7aq4xq++ZlaMmT9Z8dJ4yYSIv1h6WvBarXNMaYwVs9mfwlWGGFL8yDuswNe98+f/Fik2kOMpDcn/NYVt/Mg==","signatures":[{"sig":"MEUCICaQd0opk2z0lVKwHj8L0Vz2RSsHpy3ppj2dmAGADfQjAiEA48EzF2h+PCLvI++UEX07mnoWHqbqDKWro7RnVlo/Ets=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":604594,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgM2QECRA9TVsSAnZWagAADvcP/RXLyNmjJCMchzmE798a\n6W6A13d64bEiF726XG7UO/JhtGWF7dzdlU6fBPpzMHyjTquVl1WoOeg05izZ\nRA7sCmhEpmfeFkzgQjPkk4bP90+P9ZjJ5IUgcRsTFlKLBnrgGIi5gzxQodLK\nsTMlzNllVRypuIdxgC3GVOrGY7O8aovtAnvNIolbm7lxZRbns32+77ZyOwuq\n089AKAwzUEPa0WeTkb7B+KjvARwFCB1lG94nWP/VpMkF2CluTq7cLTHRiWVp\n3rJXfKs/SkOql+J4fyRwAVgQ+0kgXsSk6Dc26WuI7ocu347GKcDOzopGyQKW\nbZliPkCmFtmYwWSJJi/Nol/UQW0Bl6j0zjKPa+z5lraN1F6UtJuTiO+9Y2Ax\n3Ih0sUmM2rHuYtW9L+VwJvwirtC/eUQnGwNRvRH5v32u0FObaBhuOT8wLapY\nht3D0gR7t7Pu1/m8SE5x39b33kN4cGcetdoAm5MOL277vFKPOianoCl/9vyh\nZ0LyyDu+CoRlMe6BpXI0DS31DhvJi5bREaqqnCtFW+BB+B+fS/GLCwwUjg6P\nKQlxB1iUwNDOlRLonZ6J1wN2pY5hK1qAZf93TpTNkhGs7cprmE5TlQsDShiz\nXv52hPDijIaA3mk27dI/EfUeM9BZVZuTQmz9souHlHirIjfRVfMsiNJ+BCG1\nhGfD\r\n=Yprt\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ac2618d2a774f0c80279ffca5267dc33a3d9d86b","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.63","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.59","@balena/jellyfish-logger":"^1.0.29","@balena/jellyfish-metrics":"^0.1.93","@balena/jellyfish-environment":"^2.4.16"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.0","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.1.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.105","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.0.16-remove-references-to-old-date-columns-ac2618d2a774f0c80279ffca5267dc33a3d9d86b_1613980676054_0.30120779908930695","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.0.16-renovate-non-major-734e82ce82b1bb6fecfd255be358cc9c76552c73":{"name":"@balena/jellyfish-core","version":"2.0.16-renovate-non-major-734e82ce82b1bb6fecfd255be358cc9c76552c73","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.0.16-renovate-non-major-734e82ce82b1bb6fecfd255be358cc9c76552c73","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e7a2e7d21c2e3987571732f38f8d9468f809b26b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.0.16-renovate-non-major-734e82ce82b1bb6fecfd255be358cc9c76552c73.tgz","fileCount":98,"integrity":"sha512-qPEaN2LgMMWnyPmAMriTtRDpDhdDskGjO3yLriFXvqFbfzqnqMNWhirjSppnwxfOzcpRjTCLxGW+itKF1ZXU2g==","signatures":[{"sig":"MEUCID2uvRHLgTdPT6TY3Qf/l56QZvCcDMab38Wl8Cp5nlXFAiEA52JZ+rIrfwiour67ypNo8sLFi3ncorxF6pQiw1dUsxQ=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":605160,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgM+VoCRA9TVsSAnZWagAA1DEP/iwz25ic1XD6ZpgxxCNF\nbcZ3qz9pdp4isfEfZgzYx2pHPq0jq6SZIfGhUnhrRd+t9mXuCrSwY8K/Q7dE\nytVquYG18R/HKaL9rxhdKeo9F1AuAetu2Kz/Fjx750iF39WC0b0YReicli0K\nFG0u0wxz9kE+UVrYhYZGHSnzVMjLdHyTOCFClqh0hSPWJ30+9g2S+8A72qhz\nP1fdnUsn3hFSi4TkWacC5pnpOSIQtssxB/5c4XGnDNylf06S61dQpdDQWG4t\npOTxzmaf/z7uXb3Qd91NwvkBGKSqs7uh80P0wfGb/IN7yzRrhbvRLK54ipY2\nSmRPHfjpyyJoVvDvx5WA9qaHD9SsPEhVPUd1X/TdwGlnp8BtwqubcmGyLNU/\nMg9Chc2JUhh8l4yhdWWx4GEXTDbtC9C45qfSK/BmiDffnlwGJOmGk7mJ7dUQ\nGtLFXtZu/Su8kX5gut/dB5+83dfFOZOewDM88PZwdeC7C0cCklS5iQS9akAH\nS5cG6zZqm60LXRrAiiGWeoinV6TODLei8++U5JzT6WZMU/tu+bAtlwysnwzQ\ngGPcvALzDUJ0yflkID2tVtRecD0iE4CZXm7X769AJ4oH7kDXGygz4jKCDOyd\nGXxN7Of9zYZeQ3b3M3oS8ylm2y/9FvyaTtjaYoU4AQop8h8AisbFA/k/2HAl\nnlOf\r\n=xYjv\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"734e82ce82b1bb6fecfd255be358cc9c76552c73","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.63","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.59","@balena/jellyfish-logger":"^1.0.29","@balena/jellyfish-metrics":"^0.1.93","@balena/jellyfish-environment":"^2.4.17"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.1","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.1.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.105","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.0.16-renovate-non-major-734e82ce82b1bb6fecfd255be358cc9c76552c73_1614013800377_0.9946020436794056","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.0.16":{"name":"@balena/jellyfish-core","version":"2.0.16","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.0.16","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8bfe675eb266451706e52c7924a8f4370d7fc9d0","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.0.16.tgz","fileCount":98,"integrity":"sha512-ds8LolqLoBpYXsu685RkYvSjR68vZ4RZekLn/F0J6w7XQ2KiaYxB33QBlD0JrO7JRs200+IudQa9Hwoqc7xymQ==","signatures":[{"sig":"MEYCIQDc260+QSO5YsCsaYdGsLeSSynLvmkw/G662C9IgYuTWQIhANG16MNuvVf1VS5oO73a+HjB6vp3VQYNniHg8ytbrJxk","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":605100,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgM+ZoCRA9TVsSAnZWagAAumwP/3p0Ba5IrAl6vWGgvyYp\nyek64sPFDCoWc4aBMinSstPGReq8c+wXpsIkBtRDCT3QAdHfEyUxQ5yi3Vz4\nsxUXhZxnOqM5kRKM7T4AXdtFN2R39JP+yGCQW1uQN0xbgp6dzoQORDEn6o3y\nvQOFi8TSUyvDeYr0FPx+wq9TaxsCwd+nTWyrJ7tghs+fxiNEPCGtxFpGJRip\nz3SQf8HehXYaZjdFRgugDjWoK7NVnhxKhc/GgKl886usFx4jyN1/g7tdrYlF\nEGeVEDXa4kXU4sVX8ATYcpb1tEcCzRCv0tEyGkfMN95zclhqzrHWfMpcqiuI\nqXcdVuRdf4PVJIpQ9wwzVvhUktsRCOhCFYEdQ/ToBxiPCya1q05w6BfvH15C\nlqgJj+JL/yZWPHTiM3xDxdGIfzeLpamSzArhkjEGLPjs/KsXctYZ0Mpl/njI\nQ8krEGDuqprWdjshFNBa4ZiFH3r4iErIu9F3rdioNxJF4RPLgp7AohXExk1D\nadFhBmVaqcaekApl8MMojK2FMjGdh9ekPtJyNHjP+QuJtqcDKV9M2QenVBDG\nH8aHcwh+azFVpiuk9vW6EjAYVXn36k5iunQvthk9yeu57BtkLGPepwws+Qf6\nEQWc+YdHftjRsyxgDUH7CpV6ItBe37obzydKSipVakjb56x62e0MOoKziIrv\nFnHd\r\n=CnxA\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"57d326b2c9640d751fe34b38c9631acbf8af2b64","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.63","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.59","@balena/jellyfish-logger":"^1.0.29","@balena/jellyfish-metrics":"^0.1.93","@balena/jellyfish-environment":"^2.4.17"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.1","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.1.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.105","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.0.16_1614014056071_0.14251459159196678","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.0.17-renovate-non-major-93bd20a6637bd2fc83571564cd215061ed6ca90c":{"name":"@balena/jellyfish-core","version":"2.0.17-renovate-non-major-93bd20a6637bd2fc83571564cd215061ed6ca90c","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.0.17-renovate-non-major-93bd20a6637bd2fc83571564cd215061ed6ca90c","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4f992a47bf3586eeec630dae43b9ff57ca89a7d1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.0.17-renovate-non-major-93bd20a6637bd2fc83571564cd215061ed6ca90c.tgz","fileCount":98,"integrity":"sha512-N4XgXx33tp3PHnVacIl1n9Xld1oKk/qc30vFRTaLHT9CO+ge769CqDa5dWMPvM29MjuPBlCLnXHj5EMrqdY2Hw==","signatures":[{"sig":"MEUCIQDqCsxBExiZNT+8nwfTXCSzSm0BpW7/9CHffmjDH0C3uQIgIoCmVHT/C0c8LO0G2j4pNwuDW7QvuAXYorOop8h6BIc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":605509,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgNA+xCRA9TVsSAnZWagAAPrQP/3uyyjjBqKyw/i57PfdU\n8wgR6X7QAdvZ3GerbH6D4yBusMwz4AtzQpiMCqZ03gYZ+54axhwCAOeGfPWz\np+KqkWh9ASTryhpjPw1DCxsXMPVu6P4WAwwXUMUO4u52aNIH5rtfBD5NyRIV\nc6cQvS9K3WkvanMC4Sk847qBNFz2iwgqMz+g+NCz8U+lW0zmqu8hQV5iua2P\nD9veEXkicApNnZToslW8NyPJWLzy2iWAb6fDh9tDsUYC2RGO+K9VdHViDgRa\n8AAnvSzPgtAZrmBxq4bKSizH1P6K8dKARsEpKCoRmLIUKY+Cushwdp83e4L9\nwWyXaMMuIHWzgYmq0QgFhGS4p7yH2gE84hhXH8JX7KYdMWDxdO6BnhUi1i2S\nEfmCvtM/Kr84JJOYb/WVjr33RSYlwjd+gMdmLEYY0SCv/Qzt+9MkJ6CZSJlz\n26NizugrG4+5uObhmw8rIZEivRNWZPRQ+jmsEtKs8YRxON/vAy2FDWbUCUsO\nMUu5Cz4KgMuTnmHdjE+d70Te7bmodJk+F1wOqNbC/ZEsuPFMDmI4uQtEPpo5\nS9nbTtD5e5484fMCYPx4mQBSuPBcFzL7GoJmZ/IvZ+vEIAUxw2D74XQSSe7J\n8y56Po9K2MdHGtoxBO+uvfkvF2p0nzTRpyPvDy3OEPVzInlM3KeXlR/SaU0f\nWRhK\r\n=Yb3X\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"93bd20a6637bd2fc83571564cd215061ed6ca90c","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.63","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.59","@balena/jellyfish-logger":"^1.0.30","@balena/jellyfish-metrics":"^0.1.94","@balena/jellyfish-environment":"^2.4.17"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.1","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.1.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.105","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.0.17-renovate-non-major-93bd20a6637bd2fc83571564cd215061ed6ca90c_1614024625017_0.9738796780392018","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.0.17":{"name":"@balena/jellyfish-core","version":"2.0.17","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.0.17","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9b5d21e75eb0c1a7c9ca093b9213e315bad9c556","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.0.17.tgz","fileCount":98,"integrity":"sha512-EKJsj3nRdSUFqiOs44wdx0/R6tI+5WpUcwKelRRXVG/LT10rEB/CH+vtbahOsU+U4MnE7VhoQck/cnAmT05xQg==","signatures":[{"sig":"MEQCIAjfadoO/qIOhKW8Kw9zufNSwnz1Wdtuh+YXW207x/wGAiBlRqwK+YgOcDpYeUSk21Sdrqax/uZSDz3j0YADNGamKg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":605449,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgNBCACRA9TVsSAnZWagAAT0oP/A9ssJZ3eIJ+blcBUbHp\nXU829YAwRvC/G6IoW+ftq/1SagFoUkCBkuaMOYu+3WUlWmFw6+xgLnOnT0wC\n5/19jQaMEaXlD6fRUHsSQ5fNlazFbxCsyrLuA7aNCzreIoCIbFz5FG3U1XPI\n8dqiR3i3ERJMZQESaQsPFlkaSNMx2J3KM8I8wpktekyPF+uBWWXtKzYbPiKA\n8MvajBTIwtga18zBWPJgtNYJJdAFdg22bOu5PtYxk5BGIs2edMCGLsWSadqy\n+qcUoCHGWtt8Igw4/Ncx/zvvRWMhFr+/hQcqza+VS8fP4diqG1z4qvqqPJnf\nAe38ebM/GE599q/iP3V8pc28s1aKGQnGaEbT8OAdAKnME9e1Kevnm/V6A6Bi\nBFeNfdJQZCVRkl3IlXhPHVjUXX3deV8PQ4BdxiA16G4PUrD3igpFbJjW1jCA\nonbc9oppYDW74AHzpIRkd7zP7ItU5D0/fF5orn7XfL+c7C8SfdKN284Rb7zR\nn5TkKTYcOAPPCCFp6PhlyBMzVq2gGp1muj+oR67wFvOXR9rHBDJJNtmvcBex\ndsv9Thl+nKqlfGSdSaKGq67pFGcjVfI+C1CdCdflvyjiCIHxUxESL+s8oR/E\nugJj+AtEVTTXO3EqBpWz4Cqf4tdTXF9Pv8fCCTJl+aFjZkndfO12orr4mDCE\naWey\r\n=Re3w\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7c715b033c756a4f3526a627f9dfb9ccb00ff952","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.63","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.59","@balena/jellyfish-logger":"^1.0.30","@balena/jellyfish-metrics":"^0.1.94","@balena/jellyfish-environment":"^2.4.17"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.1","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.1.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.105","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.0.17_1614024832159_0.7417117941004916","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.0.18-renovate-non-major-5ddff8252e902592bbf887c1ec7728a2a98fb6bb":{"name":"@balena/jellyfish-core","version":"2.0.18-renovate-non-major-5ddff8252e902592bbf887c1ec7728a2a98fb6bb","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.0.18-renovate-non-major-5ddff8252e902592bbf887c1ec7728a2a98fb6bb","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1160dfab40bbf1984956d7847d052b864900456b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.0.18-renovate-non-major-5ddff8252e902592bbf887c1ec7728a2a98fb6bb.tgz","fileCount":98,"integrity":"sha512-Ge/ma49/hxzBh/z7/xGxitti8TAXZ3EsqdQ0W+8zhA2oPBtnCsYEtcQWkljKpSGOldPB2oGN7y+dCoFUHd9oUg==","signatures":[{"sig":"MEYCIQDzP6HNy+VPjjsWKhhndASV2iTNuXHqvz4D12TdO/oXewIhAJes/ZNDKfZ77CvYSqDLSCfd5rZuvYlQBMpaPYSV4i/J","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":605934,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgNDTPCRA9TVsSAnZWagAArVUP/j6IAe7s2RcHxazPNXJx\ndQ1Wo077p6hD8tyeLZy7HpEduYTSwyGPy7XsX72hp5yaKDpNR4hJgu6JBfgA\n4SSls7aTltjs2/oDsfDhanOJ0iTNPJnJrRF9WW7QJZIIljck137Xyg57hQ6K\nXehOWuDL+r6+ZxwnU5TF4j0hnTPxReRj7tlrY1ZIEEUB4NO5y1r/OaxSxY90\nK0zaCQyfUUZ5ZaN4gMfGxLVFNr5B4h8yHdArPoPsg+ctV4H3+yewB1tywr2p\ngbapg529QrrXinriGdPnWZFHIAgxBuxBx99QyRpz7AlSiEq4LrILeVlXixxF\nPlfRE9uZqNMUw9aYppRMdxrVmIoIqkgHGUx1V2HvW17K7T/olevoxPh7nWA4\nmOoT/CqUFMtzhB7xEPY4ZH0up+zHsEN49Skc7GwM6Mch0N/PGb3mX1S37sVe\nfTgAjVol0zPub9kxYPimga4WUPeMWt0k1X0c0zJ/niMc5HfxT+IdIVvzP1KJ\nDu9V0/YVOxbH4GrSJTM0QZbM1WJp6wbCbPMkzXXLpi6jEnj1T79xYZ4Vit97\nR+WyN7t7XpY096SToWfyZEgEl4BgbAQzLQ3+8cOGAjM3lpzPgOyV0v9nx9H2\nl7jUYb9kewMQQzFEutg4fBGpoTz1vcBbmdkldj3j8u4fiNqU2U2Gm9o7Hbvf\njn7T\r\n=3JxD\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"5ddff8252e902592bbf887c1ec7728a2a98fb6bb","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.63","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.59","@balena/jellyfish-logger":"^1.0.30","@balena/jellyfish-metrics":"^0.1.95","@balena/jellyfish-environment":"^2.4.17"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.1","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.1.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.105","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.0.18-renovate-non-major-5ddff8252e902592bbf887c1ec7728a2a98fb6bb_1614034127377_0.833617117565854","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.0.18":{"name":"@balena/jellyfish-core","version":"2.0.18","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.0.18","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2d36f926b2672d37293604753e0a90e192ad7d4e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.0.18.tgz","fileCount":98,"integrity":"sha512-IqKjPXoBRyiVaSZYFB9VpJ7N5nYsj/Uisat0vQRQnWk+AG3IS6G+zsjCIvmo1g9p1r9WTcELbJ5jdvAh0KfLSQ==","signatures":[{"sig":"MEYCIQCL3TBD32mcljqx4hG1DqDVOCCLes/SwTm7nizk5jZexAIhALHruOeTM0ALprIFU42Q52za/x6ZuBIjZ/3YbqgWf9P7","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":605874,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgNDYHCRA9TVsSAnZWagAAwrgP/A5oyGW92cEZj0bb9jPa\n3dz2R8+IqrvZ++XUXevaKcNiatH9g3dcGqwkN7MlNO1lN7LGTqC1qd9K/AhG\n9EDkHQ8/SwYGbi2YWCG8ZYC/RdTqHPaEiw2C6r9bAKJELpqZLoGtRGjZkw0e\n4JNeyhy5kT9p6wiCkhbBD/f/zCxcmFMLnaaKnne1Ie0LiWHI/zT/H9LbGTwS\nYRN7zKMuEMktdoq6l7VsesxX8kwFDveONIlAssEUE7P8wErRZ6yvS8sWphhN\nRCP+kAfoCyAEc0wegGo4yZBrUgvvSUkz7G2NCjXs474bq6ELt8j7QBbrS2h5\niC56kHqW5b8/N3pHIEvl9X56PYKEgs1cpDMzHXOWoRmoXoFNCMoGTFP+I5W6\na4vscWC03qjV3IIIjgTa6sIi4h/gafIYe8YlA8Z/dwssNyDI75XaGkF/WA0K\nWa3cTCx4N4RutuSFYzLAtC/tRL/4qkGNmvxLC7+XKKzxvimYqSu6rJX6MedN\nTgSEtCbM65iVCIE0kCIj+W4/aYl61StyJP+Yb7x1aHVeBlJLs+yYG7u5ngq6\nSZ30j+/H2LC9TIupSOdz0A3VmnN9k2k56lZLTFDufsom1K1/9tnckUtFnGaI\n66BmEo86Kg/zuC2V397YgQyOSh7olh7YnrwXPF3mD96fpP5URWZQgy6eYoMo\n4V5J\r\n=bdUk\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e57374448b8a37c1b26b2587a785e9f670a5d971","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.63","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.59","@balena/jellyfish-logger":"^1.0.30","@balena/jellyfish-metrics":"^0.1.95","@balena/jellyfish-environment":"^2.4.17"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.1","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.1.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.105","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.0.18_1614034438676_0.832064242792619","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.0.19-renovate-non-major-32a05ff06756961e11ba54f2982857a82b9b0ddb":{"name":"@balena/jellyfish-core","version":"2.0.19-renovate-non-major-32a05ff06756961e11ba54f2982857a82b9b0ddb","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.0.19-renovate-non-major-32a05ff06756961e11ba54f2982857a82b9b0ddb","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"91b092f844ae42ea919c01fbac9954b995112de8","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.0.19-renovate-non-major-32a05ff06756961e11ba54f2982857a82b9b0ddb.tgz","fileCount":98,"integrity":"sha512-9COwsnvZ1PfdJQCjOi4GF7HL63lmI9RpStwDWeWrIF2IUO4aPJVgEY/7ceqtux3CTWvG73tHUscqTkttoLsQwQ==","signatures":[{"sig":"MEQCIDjA+t/MCde0pOUdMNzXqIU0I3J+qMNAq6joH2J1CzKYAiASiWdvbWIcmLRooj5S5eoFEqU4y/QwZrLPTuNyuA4cyA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":606283,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgNHDACRA9TVsSAnZWagAAN00P/A+qsa6Q0ogy3ImIsobN\nlA9OPPHdvhHQUjdK3VUIMmNcN4BgFdMfX/MG6Yb5CZqBIZvYNtfbof0uCa4+\nXIdz+vbkwtZdWhY1QyuYA+dRh8LxXldpNQziaQJVwpshVbbcTC/AH2pD6pJQ\nqUQVi0JyKv1+RkR2ZiVslUluP2j/EqJfLNcf7ERXpQM9XdTXBeZaMwNEhZna\nHHDxl+Zyi0XER2qkOjtZRXJqJO1iW5u1rMFiHTQBw/8+1fCnwlcNXCkfQmmo\nIhpDXSic1+7u1yhQuKwaht8zfxx7TMaAlYX7hDqgi03cSQgBBi2OeM+fxEXR\nfXjCvhdNVop6AbXuG2QoYr8gYNk21cM7TsO5LHVn3suVFcpbsh2WgR8+X6fK\nkxzDUdw9901soIsZxqySQJv2OtCjTmPvN3SF1FKPax+DyZwiLmjl5l0/RrQt\nsw085Qc5c/mJoyCaJvQrrTaxHX1QKEgbM2QP7p/fbfv6FMwydyjnxwD/U5+T\nz3MsqUB09GECwZLM0O9DlxjyUfVZusCBEyhXwSJ2wP38NYlS9pMoRdJ8goyc\nSjnvveF7s2xlMT16/UKP4lYvGDiRbNmK6S8WYMoi92st3GKle3uNzapWj8FG\n2o7kGZDZUiENySonKsUiB4ylsO/TSJ6rNqdt3GfGxfn6wkd0miLCjCMWvvk2\nUBeL\r\n=fVtS\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"32a05ff06756961e11ba54f2982857a82b9b0ddb","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.63","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.59","@balena/jellyfish-logger":"^1.0.31","@balena/jellyfish-metrics":"^0.1.95","@balena/jellyfish-environment":"^2.4.18"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.1","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.1.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.106","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.0.19-renovate-non-major-32a05ff06756961e11ba54f2982857a82b9b0ddb_1614049472451_0.19825666038117906","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.0.19":{"name":"@balena/jellyfish-core","version":"2.0.19","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.0.19","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"7725810932812504649696559501baf449c647ff","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.0.19.tgz","fileCount":98,"integrity":"sha512-4jClcEERjPJDrFNMYCw5+yrhB/HIszcLhhbCqVew4C1IF1sLTygEBQwHlr7L4rmgXbp2ygfPqU7ja4mKm2i40Q==","signatures":[{"sig":"MEUCIQDyNeESviF0mB0E5i31mifPqlQfbGGoEBdNvIkMIz3SdgIgSubr8+bDvzpbrLBeNBTG+0Qf7OqhvbTIHncojNELDes=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":606223,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgNHHcCRA9TVsSAnZWagAAd3QP/3xs03TRZpliv/nwdVVs\njlpvE5riZACQKIk2pRPcsuQ/HQZr3bGJMVY4j80FVh+ie34df5CG/wAWIwiV\nFGDknooxLwPDzMzEmVhw6+MHYXiYssKo9r24uno8kjmHXQ4+mx3A6yztDkTv\npjhgsB+iu2yKLeT2uzgJEtQPhi5tFJvkiMyon8iwkFw9r1Nk78x+BVRCXADE\nLE2jSw6GDx0aW1YLaS0g/5MOwd0IksfzxDqep8aYg7+PiSevIG/vtWm0Z+aX\nAxDVy6EdBF/Orx+4M1HF3mlRHHjzytoE2NmcEPk0991jPCA8XdiI1ZWAAv0B\npwMl7LH4Is3tNuKy3mTm7bqKIypF42CtYyS4IJKAwfZvYM6tPYxRMd+/lsAU\n08wK6ywKTAryJldjdBYwgz8KsVdyUz/xmhsSxiEUttmxEKiEVEu9S4R5aOY8\nLgaksKd74zJCa7pk3t1+lMn/0SnvlpWV6oGKRjnGEWpJeXAi+fwM4AVetDSe\n+d0xg0I3+GqdEnQafTvVCo5VyA/RFc8rzcyr2eb7TFJdI3AIMNpQxOnd6H+O\nGoABm9wabLG9Fz3ka5hrYneUqwL+bzVirpfAH+sgbPXIRMu+/f39nTZ4sqpr\nnFwEBvrR6j145KSw6BPoknBrPEo87FieJJTyBQZ3foGgAY6Hu53M1VyROvDY\nJO4/\r\n=Mv5X\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"fdc354b31aeb36a2a62dd6a95b9fa982b5a3c927","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.63","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.59","@balena/jellyfish-logger":"^1.0.31","@balena/jellyfish-metrics":"^0.1.95","@balena/jellyfish-environment":"^2.4.18"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.1","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.1.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.106","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.0.19_1614049756246_0.25658520564079446","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.0.20-renovate-non-major-010578f906ea2231274cce91ae590111afbeb88b":{"name":"@balena/jellyfish-core","version":"2.0.20-renovate-non-major-010578f906ea2231274cce91ae590111afbeb88b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.0.20-renovate-non-major-010578f906ea2231274cce91ae590111afbeb88b","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ec3909cb192a4f10707d3c50e2695fdacd732dbf","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.0.20-renovate-non-major-010578f906ea2231274cce91ae590111afbeb88b.tgz","fileCount":98,"integrity":"sha512-S7KpD8NSRYB0uSRWbrE4B7GRq0IG+K4mKAnUAde132zEWq7pbVzVnFCNTlYdKouYjZjJTJz6P4c2LT53Vjh5mg==","signatures":[{"sig":"MEYCIQCoO7aBZdtDuMj5FVe3rBz4MPv+/GjgnIaMD6fGDe3wbAIhAIYHDSy8TJFy2lwg6mb9fn8GIholTeq7MwDb1s2WhcLW","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":606708,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgNIbaCRA9TVsSAnZWagAAy50P/RnIqWQcv1F+gl8IIY4T\ndSdTDio8tJto78FhbjghrJJBTqUmfJUOLQVrTTS4Sx8Zz1qkVFTOB4+WUHtS\nqnqFvCMmsV7dASXYL23eS3fBJREsyfQGTUkHB/7Iyoo7kgeBtw6+Upy3yaq1\nweo7T7LbI9MuANmyGFTqTEcYOrf7hQyevxpIeg1t/onc27C3oqJG0t6jV+yt\nrxoMKKv27feNusBRtG/iq3CpaNmKgIyEIeqfRGtnpDO0hzayvanDRSl/E4yH\nsKdScch/0OxtswQshHuHy6LlZaQI3uGwm4KS9LbbMb/mH2lqZh4ODfGxrYA4\naA0xnnYb7RWzx5LkTK9OCSmAiFaa5dDQzIbiZTYZ4Xeg6Cf1FmRvC59QEROP\nNc9ohh3qx+R3b594l6WglcPj7K0Smf2woy32KKy8Bl8aGtwA2+ss5RqT/lCw\n7qWBtoyG0Z2P5sg8pP6H2RcjeCsiO8KGzfzKdiDPG2tNusZoavXqsh7dxoBV\nII1a059KWsxanBYAVwKGx9l+2WgsgK1i331ZT0QcxJ3x6a+pTFvHfw75Ny51\nXpfnHH/lamAaoqdblFcY89+m2GO/opMucziG71Viq8dFRjh1o5+2Xl+CMu7M\nkM5u7iJmOCHWB4WBjhWuy6eLj6nZ08sxdV9QO3aqneNOrU/vtXGNIL02vANQ\n6xBi\r\n=Jolp\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"010578f906ea2231274cce91ae590111afbeb88b","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.63","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.59","@balena/jellyfish-logger":"^1.0.31","@balena/jellyfish-metrics":"^0.1.96","@balena/jellyfish-environment":"^2.4.18"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.1","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.1.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.106","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.0.20-renovate-non-major-010578f906ea2231274cce91ae590111afbeb88b_1614055129240_0.07315553173943079","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.0.20":{"name":"@balena/jellyfish-core","version":"2.0.20","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.0.20","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"afc6908c04445a2bdfb79ac602c3eb92cc5236fa","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.0.20.tgz","fileCount":98,"integrity":"sha512-8bUe32aYFNqdDbcQwLMhr9BXCZQotWr19eq1h9ImA3b2Nn0ZGQ+N3K+GLDbaEfjgF6wX7fKg0Eusb1LV8OZmxw==","signatures":[{"sig":"MEYCIQDtIdgqNDuxK81k2br1WTdOJohanh1voTcy2xaBjHBYDAIhAKHq3RWfyN2M2SlUJWT4WdoXdt470NX5lD79j1dnXA/o","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":606648,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgNIf2CRA9TVsSAnZWagAAXnQP/jUJuwVKAQ05Vs8XGo6A\nWQUfyS6WDb0WqlrFEJqT+88cW55Um1m/Y10/+rFOKnJwBdNib0Vyjjoj0sZj\na9HXcvogPxkJAfeadUK7TSS0qthux7KGEoudr8sJUsgQeRXLFTy3C7SU+R+3\nf9LR+Yu9Yf53B/eArueXq9DGgUA6L445SMp9RzfLnmD32Bm+iH8VzisMEOMi\n7euy7WYKVBIrdlpR/C9uF7uwFWSlLzk0sHglRabnboOlrak9LQFF7Kbn8IHh\nITygU47zSNZjZ0UQWSgF5CZABZQ6CIbmb0FhNQQc44E4gjrCxw2Pj49ymWTG\nsLM2h06H9wl6D6x03UuhowjmGuBXRayJ26XDiXXxvFcQ5CmPAy8R64CRA4j1\nnKuIwEeODaT3iSPFWmPo1rA/uXPiNaDZWWXFq1+nBxN/vDzylpCaxa4W0ino\nIs86wsLPnWLHEMzq4R5z125AUT7WZp/jGCwf+pQ1hf5u9mP6OsftRQIwmvT3\nK1kRoWrbKpeueTyponBVSPmSiQc3zqaw6z1VSg7WEVA8/HV5a4rU8pdebXJ/\nsNqoLv4HhXpNc0oA3G3qFSImD3j95dKs2ymk6z5RTBVBZZELsGkHm65Geq+Z\nLSAYJ4NVnvB4EyX5QfeAugGPYwP9+bjLu6vqArmOsqHWMyxSnqdyRWihY4RP\nOtg5\r\n=sg9i\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"fc8af6e9de32af23ed2dabf7bba3130302184edd","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.63","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.59","@balena/jellyfish-logger":"^1.0.31","@balena/jellyfish-metrics":"^0.1.96","@balena/jellyfish-environment":"^2.4.18"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.1","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.1.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.106","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.0.20_1614055413457_0.32797713001366624","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.0.21-renovate-non-major-536fa2221f0a5a5bc98ef61da3dbcfedf01b2433":{"name":"@balena/jellyfish-core","version":"2.0.21-renovate-non-major-536fa2221f0a5a5bc98ef61da3dbcfedf01b2433","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.0.21-renovate-non-major-536fa2221f0a5a5bc98ef61da3dbcfedf01b2433","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c7e9b52c92f187211aa289996d4b896eb76ea1c6","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.0.21-renovate-non-major-536fa2221f0a5a5bc98ef61da3dbcfedf01b2433.tgz","fileCount":98,"integrity":"sha512-aK6n+bIoVsyW+keyWEGa/GzQRBFgmzQg2NoN1T1YLshJdK2Y8GZBCcQ2/klEa/H9+bDDNp0d7ZmJzTvr4T8K8Q==","signatures":[{"sig":"MEUCIQD9bsIJPLw5GM4K8x4rlGyHzxXj5jhpvWI0kH1/yVYl0AIgVOADkOcyoGB74eYV+6Vh9quVn154yCEKOsRIXIPw0B4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":607057,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgNOotCRA9TVsSAnZWagAA+zgP/iRedQWJNB+fL2Gd7hG5\nYTglnsYW72P/evJSjpt4gB558b7k5HyZeVCX6eHmwfWigZr7u0d15GSs72eS\nvVzDMKFeFSqHcmWHRqbT3HE+qt/cYGeb+jtSJhfubOSwRXYU+24YHW30cSWl\nTEoKMQqQJEfdCnaCtQdKp6vDQGXHTa+Ac1r+68/TC8LnHZj0cYrAut/I1YnA\n2qSEenYa4c1UuOUcBBXefxJMLv8vzpVwjoxaNt1hNhU+aH0fdu9+fvjdZa+U\nZCCBAz6BKIphy99AUq96rxkjR0+LCp+SpDoBH5lNQBsfgYPiKWYFRSPoezyQ\nMBCGkrjF7uvDW+P5PMJe1mJMPWpMftIbn1it5OZJPJzWQognWr6p463RSl82\niVcymGMlHzoT/ZiercsxEgzL1z5GIXSLQudVjJ6NnQbL5hBhGSwVzDvEmWcW\nwlrJ2Bcp3nkfw6pQEwJnao0fbYK1wnEuxLgcHkgFkD41BnmC/i0yz6u3WnYV\nv4ouC60ESe+e1peEAaESW2bgGfGWEK9ntD/H74RjsOHrkVoLzQz83um4rWmY\nISRJeMQaDrFGYnWn+fdE94OzTJYTy/Q7K2a+y2zVpNeV4cRUl4nW+xNeP5zp\nEzS6SyHDYq4wzmTVR0oLyCKOgIZfSDfMQK8sWeqcnP0tc7BjefLkbrY4QHsZ\nSnxZ\r\n=xRgj\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"536fa2221f0a5a5bc98ef61da3dbcfedf01b2433","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.63","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.59","@balena/jellyfish-logger":"^1.0.32","@balena/jellyfish-metrics":"^0.1.97","@balena/jellyfish-environment":"^2.4.19"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.1","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.107","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.0.21-renovate-non-major-536fa2221f0a5a5bc98ef61da3dbcfedf01b2433_1614080556685_0.5457873761555905","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.0.21":{"name":"@balena/jellyfish-core","version":"2.0.21","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.0.21","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"869276d5db7578f8da6be11ca01e8d87b6a06979","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.0.21.tgz","fileCount":98,"integrity":"sha512-3V8OOgyGRiqBuGseAU3VBuraQcuxJWkPqrwxVfyHh7iRvDNfAxslceKQT9Y1lB0yM/SDN6AsRnwe9oamfw62cw==","signatures":[{"sig":"MEYCIQDQIqzdYfoyH9AAoPKxz0YlZONW3I7LEqZlNtn4TKhXvgIhAKz/LJMm3kDS9F4jYYXw1cd2cqq7YmFFNyz2NetLRHh0","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":606997,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgNOuACRA9TVsSAnZWagAA3r0QAKPw5hRjS/BexGSAYZVb\nAfY2ZZZF/2p/yh7IbS+NZNX0oj6ri9uusArYBXdAEpLcn+XsEwV8uM8bE9NZ\ndzHXfmca79PSEFEdSFNdaOqTTTgeB4OSS2oqFQgXGKyCkL/F9ylUavH5xU/D\nrUmKytIt5C+xgFdAsgFy4Mjfkzir2vSDJmvr/oH3A0dVSa/xLZcCvSelLd8z\nOa5XwTR4iH6sNzHGnUUsvYTRMZMpUYxTUMftv1WsAc4I7K/ccRymD5uLevy1\nDU5JS3yHU06QKWHhOAyEbsboh4dgLT8mpVgf6caV4+tunrm7fs5je+zE3KD+\nnSW6qtWVJoghRkbCNrTv66GHcJBeSsXS5aSpblzea1RpNhF26AnxQ2SCIjFT\njGvODV8PbWatuCV30D75GS3Olnx9EhNQqFu3jhqwBDiUI22nKyBlbQYcEErJ\n0oFm2l/w9Oz3hLpkbYmFAuN6Gfvi5B6cL0F7AGwhblIaK46852VSQ9LlpG1N\n/lzodrS8RhpBMJam1tjjBbA2lOTiwLraMZkBLtmO9vx4mz2If1yF5dfMPt95\nA+x+xINSFJH07AcD31s/Hp0rwY5CxrnFTCXcj5U58aQyhs0DTfUMBgRsg/vN\nVkOLIzxEfCnlpNhhLQHeKNRGh+rV5UaP0HCBhqy0YrXpt7eLPU6LUGNyKyMe\nOHkV\r\n=hTar\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"082e9e02115b0a450485d7229a01c5e30a26b73d","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.63","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.59","@balena/jellyfish-logger":"^1.0.32","@balena/jellyfish-metrics":"^0.1.97","@balena/jellyfish-environment":"^2.4.19"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.1","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.107","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.0.21_1614080895596_0.8768840044211477","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.0.22-renovate-non-major-7da77aac8e9eb924cc25fad343b1d7ab930a9542":{"name":"@balena/jellyfish-core","version":"2.0.22-renovate-non-major-7da77aac8e9eb924cc25fad343b1d7ab930a9542","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.0.22-renovate-non-major-7da77aac8e9eb924cc25fad343b1d7ab930a9542","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e8a1ed124a66fe4f73013ae2cfa9fc9538ae45cd","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.0.22-renovate-non-major-7da77aac8e9eb924cc25fad343b1d7ab930a9542.tgz","fileCount":98,"integrity":"sha512-TBvIXN6rZSvKZcMhnfKU68cWdrPiogKohvGrHvvlPbSwh5j+uKlAihkOaq/8BguGSSthw9lS7FvzOoZW7MGEKQ==","signatures":[{"sig":"MEYCIQD9u/WPAz/gTqo+xkwngul+ise87Z9OxK+iRbwd8XjXsgIhAMTdViRM8PQkZr6bP1+Re/pstu/FHUQaeXhEmNqgSYH6","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":607482,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgNP1FCRA9TVsSAnZWagAADgAP/1ahsvgBAsdGacPWqU7t\nFN0++z8/bsNOkPZYXKDLoAX37XUBLTBl0kYaiL6q95drNzi4ay9N98Q8nKwY\ng6rZckuoXYJrM5fyGqblx6Rtlbze5wv+qHc6eFHKa8dagsTkoKnIE9fu0vwv\nXrwXMKiiHT+pA5Tc4iCuY2GKqrptNIc6bHSC+74zJipdNoj37esb8xIB2miy\n0P+qYAaF8d+yA5lJtUZnrPuXc9d3Kxp7AdZtw2nYyXdSebH/s+SvvdZAJS7C\n14ynOK1h7qFdX4q0kMPMZ2HmVeXlUNzjIDXKGksAJwgUynFjE4+Utg7sW2bb\no+axarNNmPqkmxY5Qy4lTmkkEsswmNrN5WCe7wyGC2TpSv6gcPNjuyCCWh2h\nQ1/A7xipCdk36Fxo30Rpv16ALUtxGjvFXpKiZIvu1vfTnSIXyHpbkRW0dG6d\nzvI8eMBIzwpQx15k/V91G7+r6v7DOi6ToaAcKLKfVHYtkJcfvVHLQ0NwK4xP\nQ2tB03ZGgxXUNYcGiWkTuiLwpFURrdmPML1dYVpx9S05McA8mtp3BzpHQwoE\n3eCVI3+R7cCwWjWXLL9AQ/vuz9mPIHglErdKAmJu4iUZqbq+hJlWq8RdnjfR\n0ZMHlNNpAHj6UH1qWIzQkd0SmWkdTaGw+TOdYDoW2Lcx9Laj6+EXzyuSBNc6\nRt4e\r\n=Xh0d\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7da77aac8e9eb924cc25fad343b1d7ab930a9542","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.63","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.59","@balena/jellyfish-logger":"^1.0.32","@balena/jellyfish-metrics":"^0.1.98","@balena/jellyfish-environment":"^2.4.19"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.1","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.107","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.0.22-renovate-non-major-7da77aac8e9eb924cc25fad343b1d7ab930a9542_1614085445311_0.04398977553057404","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.0.22":{"name":"@balena/jellyfish-core","version":"2.0.22","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.0.22","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"97f29ce65ac364134186478b738d9acaaf5a41c4","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.0.22.tgz","fileCount":98,"integrity":"sha512-NhZjqVgvOPnVuAbZqGu/48jH48qywsbRixLxyybz+Va+oqjzlEpjJYG1QY0FQvfVwpg2sRQcqmtZAnETHYleAg==","signatures":[{"sig":"MEUCIQD8jaNA5jXsnaxk+/p314orOuesjGWjxzSLz2jvKjTfhAIgMgLj3F1Xoy9PIAdipqAo+M0pYT+6hgTmxvwaQU2cRhk=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":607422,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgNP7qCRA9TVsSAnZWagAA4GMP/RXsdx1o0KYTbJjpBUi/\nt/Q1wz+D0Z4j9y6smpYPb2uCtWmrq5VT9KTa0Q0CzyeflPAcDvuD33CD67nU\n0U6odS7Uf+YLQnWHoRIk6w88zQrTTecfaIaHNIaRa77+tnchVN2TS395DCGd\n9Lf1kCygtRajoCTeTWEUq4yey4ZvefJiifbMMqkfaVb3PYfkx8JIDMHKzTFT\n1D5VI3K1tNaSkgIoa6/O9iaKeKVOI9LgYh+Cjw/hZJxiN6Pani5HKeXOkFXg\nDhiVH1YanR/CCKwpkZzV9GrkJGvfa3Vufxnc0HNxJTQ7SYIMPcLyatqq0kOF\nBYSxK1EvB6sjhCNKyi/EN4zBwBbAbNQiAP4o1MRqirKm01qSjwVu/APEyCy9\nZWj9Ac38I/whWjDekoEKGhqIyeCBHkhVdgNhCV5gF4kDknMFfur75YWx2wk+\nGAmMlPHM6axrzH8bzpfm39mFDgJbLFOku+9k82qFoSxkrzgd8JoIZbXow8b+\nTA2mrqCYoreFTwkntXZVUngie/nCwvltQt+h3ETPuPcOwOWkfmQ1lal7KTPy\nQiFLWxK3RnNjSZiJw7V6B/cFD+qHK3MBl9LqGCIpytz2qOjDTkEIkvbSxMdY\nU/mFtM0qaeVHaqMms37g3x6o2NULDmfg9noj3K712KPWd/BqvUAFuE4VllFK\ne0jV\r\n=RiEC\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"49fb594bb2a6f9ef71a48f01ec6d6dc59aa1c12f","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.63","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.59","@balena/jellyfish-logger":"^1.0.32","@balena/jellyfish-metrics":"^0.1.98","@balena/jellyfish-environment":"^2.4.19"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.1","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.107","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.0.22_1614085865962_0.8441115529289056","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.5.0-generic-oauth-332c516a482fb82054d1dcc1dc1c77b07f208130":{"name":"@balena/jellyfish-core","version":"1.5.0-generic-oauth-332c516a482fb82054d1dcc1dc1c77b07f208130","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.5.0-generic-oauth-332c516a482fb82054d1dcc1dc1c77b07f208130","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"855e9cc14593037f9643ed51e166e31e9610c6b6","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.5.0-generic-oauth-332c516a482fb82054d1dcc1dc1c77b07f208130.tgz","fileCount":99,"integrity":"sha512-JE8uPNy6YPfsUazviyBe4xi5GT0lIHfD+ZsPBR/orSaLTENUigbIJJq75qFq9AAIxiuNodzWMp9D+/VeAwOhmA==","signatures":[{"sig":"MEQCIBYTLXL5nO86CQNdJdIa6wNnLeRtjTk9Z6im1CF1dFaeAiBgkGP3xE7cDLxDt2UJEdAQ63TkOdaw40Xemlj060YGGQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":602143,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgNSR6CRA9TVsSAnZWagAA3H8P/iGq7Ogc4xu4+y3ZxoEl\n2e8QAIuQDUKxQUTShDRI427rEkljRrks2mN2qA/Z0tUPb1yMFi5fWLHVsvMu\nL4Of0kM33Deo/y3WGDRPi2oXpCMuV/fYU7OTkn6np+N3mxDOFqQpCg/55/5B\nzU0MFEAOdCIsTFbFLjguUZZVmK6qNaEQCaMDmwfch7zoD/nJ7dQnLrsV6U1C\n1Qum4mojwa+Rx5PxOOHxNW0eVx7fXEjvTtkL9a1bSPlXFPtv5WfmB3Yj9ziT\neVUuov978ddiIK9No6kGQGo1yNg0K66qe3QOWBtNHIFbr0owTje7N3FuwhI/\nPrvcKwDoFad/OFkbGSHAneuK/XyWzN+3RFc1XeUA51rWZ0ARcMaiWYyRgeFH\n7yBGq1JXMZsBuONzXNL5U3bZqNvHR70KAvi1DUWLoIIbMfc5dFgVOLu61WPq\nWSFlpEp5M0WMu+x2QyjzlHqKaCVxHCMSMWTOgI5gYkCIYJXVpc0MEmKqL+Se\njEJzOy4O5JO133WULgIb04dVcbKu8TeLuWSpfFOq+C8sTPdpeF4umj3WNtiH\nGDWlkYoJU0JOwc1IPGfg2/tfMlCpn6rcEQGKDwBLx0qJfps298xFCpqn2gmM\nsQ1/VOgyfmUDtTYv8VG6h2GBQR96tJdWqe0Msdaw1CLspVn2FCR5g+7Uo9PW\n3kys\r\n=/Wts\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"332c516a482fb82054d1dcc1dc1c77b07f208130","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.61","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.58","@balena/jellyfish-logger":"^1.0.17","@balena/jellyfish-metrics":"^0.1.77","@balena/jellyfish-environment":"^2.4.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.101","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.5.0-generic-oauth-332c516a482fb82054d1dcc1dc1c77b07f208130_1614095481963_0.11255223414298143","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.0.23-renovate-non-major-d2aa6345a204166342fb8559f32b80f23bd4eb12":{"name":"@balena/jellyfish-core","version":"2.0.23-renovate-non-major-d2aa6345a204166342fb8559f32b80f23bd4eb12","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.0.23-renovate-non-major-d2aa6345a204166342fb8559f32b80f23bd4eb12","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3df8ba52f889e150d9f00bf62326eacbc6ba9c1f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.0.23-renovate-non-major-d2aa6345a204166342fb8559f32b80f23bd4eb12.tgz","fileCount":98,"integrity":"sha512-9WAWkJ2eJ7gQsijIGzqDfEeINO/cYEUg19P9BjF4Yr5BgIeTFwp+XBI8FfgY4W/7GkvqnGUo0n830Y7oVf5Q+g==","signatures":[{"sig":"MEYCIQChcoAmCv78R2tegyMs3Jj82uiJLJaOi0PU8q0NaBJ9qwIhAPyEZ7yVOy5VGOSUFSxH0FXDMAZVMM+hEeJ3fdNE/tfG","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":607831,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgNSgaCRA9TVsSAnZWagAA4xYP/1FsVF+HbDfjw6yxqm7e\nSIMrloxdrxYnF+H8mcjJ3BQYcujr55k33zuZ0ZhnUWI31K7uB6uFuZIKN/5B\nOHFJf4Xd0iIGz6k+EuiunQ3X6xR3s54CiXKQLI7q22VoAeiJFHFpLwdP6Ros\nXb1nsis2ePNz7eEaNLclLt29Zi6a0Jdr8zZMIOVlxx8zxQ+3rWKF8C04BL7m\nF5mKY/QrL37WlE2Uzq+2uOF31pO7U4Ry+WMsnuYEgRPCjjzgYwujpfEcvsaV\nh6CQBSmkra8mPxc9rMQS36xX/0G97xVd/FB7GJM8xeiusErLPr9jwNd6fblS\nIZWjJOX/3mpB/kJCLLDkGJOjTJ7DX4TusaBhCvGsP0fxLUJRqGBdcLpUett4\nrZHYFXx+wyAqUEUytDN/SarT+wIUS1mpPL7+fCS0FWhY5vv5URjkslIAMhlP\n2m//hwe30NpxTIljOxgopQW7T1Iv6qqj6E2GAI35NckmCAiBuNqOTHX4sXma\niRFxIG5RS9J/mLdVETbIvxcamCNGJER0FP+jpobyC/exeamnO8pDcDy55jrA\n0VYImoA+Bnt3ik6oJ4BRHaQAsQEEpL04u3WZz/TzdYJm8cjthW/E+RYIjaO9\nKXuy9TfaSmAE77D1FrZZBYDbnW6JIFvhn3QJ6V7U1X2Xa8eVj3IviwMhjQjf\n+nVb\r\n=FIlT\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d2aa6345a204166342fb8559f32b80f23bd4eb12","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.63","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.59","@balena/jellyfish-logger":"^1.0.33","@balena/jellyfish-metrics":"^0.1.99","@balena/jellyfish-environment":"^2.4.19"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.1","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.107","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.0.23-renovate-non-major-d2aa6345a204166342fb8559f32b80f23bd4eb12_1614096409816_0.3378144905573699","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.0.23":{"name":"@balena/jellyfish-core","version":"2.0.23","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.0.23","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"bf89ce7db042bb3c2eaf332574c5cd5e09fe338b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.0.23.tgz","fileCount":98,"integrity":"sha512-Ru1c3HxiCoGWeVf6vMrxu6TKQCDNSD7tbCI0esy7swVpuX7KfAailk8iKeSdT7tS7fZXQ2gcRcK+Sy2mJMhKYg==","signatures":[{"sig":"MEQCIHWra1iEmkMmkUdxDClr1l7+JaWaSO0a1RC18KL//FggAiA5MUtZFSgtrFJLbyWYDsj56AzRshVJNTaZV8Fu4S1UZQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":607771,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgNSjzCRA9TVsSAnZWagAA4ZcP/RfSEmOGUI8tiN0GTJCj\n7BIHrGlewTnjZMGg8nJdlOkToTaBS5LcZjlHel23/W6ffXstZlXOctt4Iuar\niSU5x0+RH/ja6wfcCbliI/bt9+3aUeWoinRIidkBy96sU6xwB45StEPu2CxE\n4Mo1MpX1JyWmdGJHmmYRYumbsnoajqTu/BdWObNBinwOzHUGNkN2PhHAG39e\nUWrUKNG0oHXBI4FKrcg+MW8CQpCahTIvrayAF5LSGXTwR4NwclErvX813CgN\nSdoOrEcTFxvBwakEAr03+2kloQV8I38EJDSAxoanCMMLUnIQf8Q4xZTxje5a\nlPLxbSd3lw7a+SrOyM0RUP7a8xmDHre+RcLjJ2PcnvvsGJWx4Ss0CVl+0678\ncmsb4nsi4H4F/0bcCIEyDIpYN2CaELNJyZonHX1s5TbCBpvZc0/gAlioyisw\n6T7b9Q7WIdLTSXTZErQB7vDquYB0pv0qLgOSK+QBVXwtqd3IYiMko/8gS3Me\n5rfly9Z+SPcaXXGgo5dNkFRZjHf17XH46FOIp8Q3XhUytJFqziE4X4mH0VQF\n7LtN3ceW79nvb1aEZDBRn0aWFVqUi2SA+LLgrUoYGOq+6bvGimGyDLeCMgDZ\nAC6kCyehtfQxkdWpOpXBT6WIb7hNlOw43/c0W2LEUsqHwFU5/I39dhg7Hj53\nfyhT\r\n=N3y5\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"033ac31bbb86f4a9f441c41260de3db968594437","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.63","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.59","@balena/jellyfish-logger":"^1.0.33","@balena/jellyfish-metrics":"^0.1.99","@balena/jellyfish-environment":"^2.4.19"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.1","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.107","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.0.23_1614096626884_0.21470934950313136","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.0.24-update-date-time-regex-0a8a80e00400e27a73f06e0ceb503acc73c75f71":{"name":"@balena/jellyfish-core","version":"2.0.24-update-date-time-regex-0a8a80e00400e27a73f06e0ceb503acc73c75f71","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.0.24-update-date-time-regex-0a8a80e00400e27a73f06e0ceb503acc73c75f71","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"cf2a8cd7096e35e14a12e04a29787789ce850c53","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.0.24-update-date-time-regex-0a8a80e00400e27a73f06e0ceb503acc73c75f71.tgz","fileCount":98,"integrity":"sha512-hG8x0oSnoUecSJ/h9TnN33Vv5fqyKck89DkZi++C0faZwdcX4pZ6iSRQIzqy5O+WS7TKGyMbo57YQq4Ru40BHA==","signatures":[{"sig":"MEYCIQCGTx+E+wDgF4MU/XLaEZeJ1TijniT3MvMjoZzGu3PoZgIhAOQNVfrZLejrK8onNDNyikmVZVx+atI76aOXz1RRj1NS","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":608375,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgNahPCRA9TVsSAnZWagAA/k8P/2QeXcY4TimpOBlKPC6C\nIlwToMmaxnl02jCJrF0s/ivLaIB+QrtypwAyaT8/tAshqaRJ20tOd1i2sep+\nY/GDLukrhknZR8s6jVjMX559PQGLoyuMFmIrea61fHCXkmoHpnYK0sj60kwG\nu61NjRjxjGgnL/M+l510nJflG2rMu6ZSIGyVRANj/4OdOOEcigOzB43dIkXV\n8F/cYCjAXhVxrRqkad6+bVedjfkl9IJZCqwSnpGFETEOKFO2H6S63UrDmNIP\ngMx863oyRVUiBB/pFb3XrlAoPn4o0dWmokakOgaB8ID25cGZoMHvXNsxatRY\nCWEd9kuXwxUIFnaoCyDlbW7yjNk+c+fYyf3rKLXaxcaWqajNuIXCP/mF47ri\nqTMne77VEFtI4hgZX0HlueSyiSNsq1ZfELXKyF2ttj4K0cd5CcyoNlQQ9I0x\nbQapmB+aWrEV2cEvkATNcr6h3n6Ilu37us2uGqENKaQm2Rf0s6y8vv9YR4zC\nBaeLlCrmMK2WbdepXLZHBjrd8Xh8nvfM/i+7a+5aw/4cvFkxPddejxCgOHEH\nfzdV77ysznAwXi2r6BUPLCM36s63Tv6VDo/NoogSWXRImr/41vgKwPK6OjLO\nhUNs5zNFPnh3aCPkmSERoHoFRDOfT9r/+HQTynUb+1Vg+VHpWZ80VbU1IKU2\n3mH5\r\n=dU5x\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0a8a80e00400e27a73f06e0ceb503acc73c75f71","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.63","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.59","@balena/jellyfish-logger":"^1.0.33","@balena/jellyfish-metrics":"^0.1.99","@balena/jellyfish-environment":"^2.4.19"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.1","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.107","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.0.24-update-date-time-regex-0a8a80e00400e27a73f06e0ceb503acc73c75f71_1614129230806_0.5777662229452749","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.0.24-update-date-time-regex-710f2563ae6b4d8fe780f20f9c2afd7f8e8bd68f":{"name":"@balena/jellyfish-core","version":"2.0.24-update-date-time-regex-710f2563ae6b4d8fe780f20f9c2afd7f8e8bd68f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.0.24-update-date-time-regex-710f2563ae6b4d8fe780f20f9c2afd7f8e8bd68f","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"52848231adf398a8434cb89a663152429ff9021c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.0.24-update-date-time-regex-710f2563ae6b4d8fe780f20f9c2afd7f8e8bd68f.tgz","fileCount":98,"integrity":"sha512-yl61YnqmkJT1lXc+9hEjNMhPUUexj11OJAkrxplPoyguSIR55YQjK+sJwXNfGzShXRGKHrB1coOJoxJ4fvVDhw==","signatures":[{"sig":"MEUCIG09tb74WNKAFsq+apoYgu25kcZxDG2jBqvfjOx5S5JKAiEA9/5A4OzGBpc/Jg+kaQpYgQwX4zZ/CF5EbZsBRLJzhcM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":608387,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgNa5sCRA9TVsSAnZWagAAHlQQAI8gKPgwa74UWL/D1LPM\ntLI6f6bV5xlStaN1Nj6npJbXumPcRMBabJc+LUOf0AQii29xdbCkGRPsYGlN\nXAftKKeK6bafiAvhQoU2rx6H+Yel6ToXclGtY95WJH5c2LVAUAZPLAOU418h\nVdeZ8llGIb0+ncMmg192O9lKL2z/RldDrCuTwbhjX0iLJV+EX48hswCmUxNx\nVmFgZYlXJw7QaahcQWlZav9Jd3VcwCTGzLXX4TbFqnUyrp37XHq9lV03H7FZ\nDIZj2cFCkczaQf0oUIQe8z3ZLYyfucr4/V5escgW2IwkBVqjwAZyRBK/AGbV\nGnRkw1e7Frb5f75TskwWOx1boNrr/tiDfk6mM1I8UMmXtOwNMFlwpXyH/Mph\ncF6bdl0xGm7r/f5CwJvVTz13zXA1dYWjWlP/VYSI8oRvmiu1cpD8iZV++2Wn\nNoJXKM7MDks+EvJY1bRqTSbySGUOVsefIE9CvY7elUm1WanffSXUGopEePtS\nwjz532urMgbRGgxqb+y+oAAWvynb74qW4Pc8wCYWgnu+3nXZrL9amXc+CVMb\n616U4EUCbg2LwGPlYFQP2sBWV94nsenPDZEq3V2YcjoYb3DN2RA2M6E7LRUa\nxodypDMnV1MZ7t04RmqbqGfqFYKhBpeahGkyK8+gtGRmX16ehUhKRQAYKyCL\naLwF\r\n=SBHr\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"710f2563ae6b4d8fe780f20f9c2afd7f8e8bd68f","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.63","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.59","@balena/jellyfish-logger":"^1.0.33","@balena/jellyfish-metrics":"^0.1.99","@balena/jellyfish-environment":"^2.4.19"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.1","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.107","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.0.24-update-date-time-regex-710f2563ae6b4d8fe780f20f9c2afd7f8e8bd68f_1614130795930_0.41419173587148084","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.0.24-update-date-time-regex-8b971ab980f173835ac536c75a5e8b2c30e47764":{"name":"@balena/jellyfish-core","version":"2.0.24-update-date-time-regex-8b971ab980f173835ac536c75a5e8b2c30e47764","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.0.24-update-date-time-regex-8b971ab980f173835ac536c75a5e8b2c30e47764","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b84f27df5cae3676e404a658a2cc9599d51d6b74","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.0.24-update-date-time-regex-8b971ab980f173835ac536c75a5e8b2c30e47764.tgz","fileCount":98,"integrity":"sha512-Wyoj98ksr4f4FJFhpJMIvZftpNUzfv3EWy8mWyoH0fQdXb5iIebdDXmMLNzVpbaIN5OJbzeSOzYmXr7QyYwn/g==","signatures":[{"sig":"MEUCIQDHBCRpdXtOjpHXmi1KhdSgkKYJf+l3b6iGFVupZEvOdQIgfWaAbEKaMApOc2JZCCwOqDsfbhE0eo/w3db+66SdiXk=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":609327,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgNbSjCRA9TVsSAnZWagAAeEgP+wRr2Tu3oosd7ASdUzv7\n9vhOGdQcqM7XH6KZGKimuvcWqGHZo7oEm6Zw4FmcJDW6dcHsXFfMrpzBKfNb\nYQLBYFo8cjtDKSGXMNAMZPqtidhqcusuramRDrkP5AvOai/Bq8glVhJ31/CZ\nCHv6YjNhloveu1fpur4ePgRAbQA7KQlunGB2J67yWBJm0wyjHQePJ4ljzGJE\nsc0ZYc9sSAoM3tWnOjqgpY2SK6iYWnu7vBDYnkX0WZ+Un56vLZPKLekq+Pat\nr1bQ+Zvc0kAi5E76puKcEr4sx14KbxeA7StjmRdVitY7cPrcSZXzRM56fF7m\nBIabLt/uFPSQconQ8POiHljjm6MWWM3k6I7HwcdTbPG4N/IVpqPhzjScQ+0K\nkjK6C4z53q78Zg18S4/PMOOrKS5aF0cVvhrS4wGna/SZdVlD0fd/72CEY/q2\nPOPeiRS9Wc/K+nIHbKUARcTCFPKMZej/y3EP5NQoxorxhEwn5tsra2DI6PZr\nT8ggOOrJZtlfDUzUYSoO2WEIDOkgYxKeS2AhtlOxQ/jAhxfFgeyyEjGq2FTQ\nH+nKMFtNP7VSJUVqSo5LBvifh86rdRNri7FLvruAoTfjQysMkO1K6dfgx1n/\nuUTRoq9IMJEdES4rI4eV9GuU3yTK8ROvoi63yVH0wKfezXIcIH6ay/5R0Gkp\n0NAE\r\n=YOeU\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8b971ab980f173835ac536c75a5e8b2c30e47764","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.63","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.59","@balena/jellyfish-logger":"^1.0.33","@balena/jellyfish-metrics":"^0.1.99","@balena/jellyfish-environment":"^2.4.19"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.1","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.107","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.0.24-update-date-time-regex-8b971ab980f173835ac536c75a5e8b2c30e47764_1614132386710_0.16390399312778903","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.0.24-update-date-time-regex-89a7ef7aeb00196684b033dfd44eadd1a15a3dbf":{"name":"@balena/jellyfish-core","version":"2.0.24-update-date-time-regex-89a7ef7aeb00196684b033dfd44eadd1a15a3dbf","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.0.24-update-date-time-regex-89a7ef7aeb00196684b033dfd44eadd1a15a3dbf","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ddb0ac095bd5ec605061a881a55adc9d21d2a7c0","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.0.24-update-date-time-regex-89a7ef7aeb00196684b033dfd44eadd1a15a3dbf.tgz","fileCount":98,"integrity":"sha512-jQ3OAOwrEeQV+9w64ela1eaZts71KQo2Cgbg4WKi02tZnR4Xbx42cuxGMCOtZMhnLXhf5GDqNHP/OuzzsSf9XA==","signatures":[{"sig":"MEUCIBduH7QooTb7+wSpC0fCcC8XXHGQIr47qf9pdNywPW7xAiEAxDStaFloxJoRg2kcYbVJ6uAqL0royTyYuR8KvexgN/g=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":609327,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgNboNCRA9TVsSAnZWagAApwMQAIVwPKh/zVQu5kQs+MdQ\nzzpv5WFxqEDB4MsTObmneiCry7Mt5TvWVNiWlW6nWKqgXPMlnBQEpXYUHW7n\nYg9mxy6De/QghrxGQ+Yq36DZKOnBXpg8XRdrk3YPL3yzcFPlXqkbZXIzJmq1\nK8iR3idhOkpHfjOmtRNvAZG4yseTZtirGF8qTUL2ZvrB1NsJIrirLHTTKw7r\nElYc6u3UFSBqyOJ0vtLFp0yc5Ivlu5hTTvTic0SJQrAbw/JW6KpkM0IarMOB\nAvR7DOFHvqNsr1BTVWuhj83VinGYokHx2sZXw5qtniGMMgl2eHVBK/dwGlIU\nKGw4lrMYtg/QYPOWC2eUmxuU2PwQKwlWj5aMZ7tlW2i9Ar6kW0qw1JAMdR++\nPSaaqFuxNujr0SnFyTBS8m/KS45dx1ZfjlfBjT3NRdibkSfRgXd8d41OtlsX\nOb1Lml2xmoD2/tTwo8rmRNhF+j08rSBCwThOBED4QCn0XEYISZSuDgv7TC8n\na6Jy0YxAxCNH8uiwq9ZYmID5eRk2wlFjzkkPxO9tDudSgBTJQ00BaCF2+5hl\nZfrP5jxx0hp3yMsWTashdX5/reqIqafioQ9WTMUuT8jx8zk1DXpETwf/ThRe\nTCPnF7zbGnugI3R+PGeXYp/ZaLMkBTgZJyjTitwIDCymAE+ltncYTeXlYPro\nXRp0\r\n=/u7h\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"89a7ef7aeb00196684b033dfd44eadd1a15a3dbf","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.63","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.59","@balena/jellyfish-logger":"^1.0.33","@balena/jellyfish-metrics":"^0.1.99","@balena/jellyfish-environment":"^2.4.19"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.1","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.107","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.0.24-update-date-time-regex-89a7ef7aeb00196684b033dfd44eadd1a15a3dbf_1614133773346_0.7518366691414147","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.0.24-update-date-time-regex-3ba08bc9d13d6c13515e42876f5d3069addad775":{"name":"@balena/jellyfish-core","version":"2.0.24-update-date-time-regex-3ba08bc9d13d6c13515e42876f5d3069addad775","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.0.24-update-date-time-regex-3ba08bc9d13d6c13515e42876f5d3069addad775","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a280091b5ccfbb8df7c67859967b28c08d7a4ed3","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.0.24-update-date-time-regex-3ba08bc9d13d6c13515e42876f5d3069addad775.tgz","fileCount":98,"integrity":"sha512-AjQuYncr03NwI+2Ql8XTyvVPIjHConFH9agjzC6Bw3XDE33MTLWyRSw/v7wdxoFijP2Zj9oHe/EbXoaCXxZC+w==","signatures":[{"sig":"MEQCIHrZ23l0iV+BI3rFA+10L1J/TTR04VLLgz0Dzz4dLlPIAiAw/lXL5ddgREweFFumbZku3dGM5oPdb98KmC/+ywhPzg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":609327,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgNbtLCRA9TVsSAnZWagAAkDsP/A+SIlukbiGf2pmRXUyd\nDMs3179Gead5XlzBZKJtPKdkRTTOqFCbVJOyRGZ2HIjhhk2x03JgTV2SUR/Z\nGujx62w3DDpwtdpnX77860m8RTG7FJg3fv6G5oO3YzyxjcXSMBZt/B9yqCo/\nzuj86aZm6CBC7g/kTemOoXdbjDNcmJdp2xQ/E/MNeF/ml+GK65FhZuWbcRnq\nYrukGaAPti+MRyMKChxSVR5Yy2t+w6e++PlKvYHhQRJkvZoBCobTNNCnLeBd\n2hZLEuYJo8zIJftQCCSemsxnTgYXWEKClagkt1xqXMJmLfMZaTnOUk4qfe5b\n0gajSVSJfcsdT6RRixN/YkK5E9pCI5a5ctr1pJg17aeix6focrnKLYxW03wW\nz4Os09PaNffAYgOA6az/Ja4w8VFX9OYk6X9IBHo2aGtMLclSJsn6Gsqx2maw\n8OXpaH432jj0oAHGGsMui/eAKTeNCJKLX8rP8HazoecUQjBgTIRHhHcB+Ke9\nVr1bvNjyRTShLrVYqSJyAvNYJh8wcB1uVbFvmYvWBtnKLJseRGpZJC9Skkxp\ncuPwX0dZOAdTSa75FV/jUP9YZRiVK0vY+tdOXLeiXSZVX5bw53SZUWPCQvJS\npwcymBz0A/4DwjpOCWkmTTJxIDCGKJGide6hCCO11/Rb5o/SjGWZtS/Crw/V\nILaM\r\n=T0BK\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"3ba08bc9d13d6c13515e42876f5d3069addad775","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.63","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.59","@balena/jellyfish-logger":"^1.0.33","@balena/jellyfish-metrics":"^0.1.99","@balena/jellyfish-environment":"^2.4.19"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.1","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.107","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.0.24-update-date-time-regex-3ba08bc9d13d6c13515e42876f5d3069addad775_1614134091247_0.7047511582300505","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.0.24":{"name":"@balena/jellyfish-core","version":"2.0.24","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.0.24","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"34f0e014d21cbfa3dcad66b47e9e174ce10a3dac","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.0.24.tgz","fileCount":98,"integrity":"sha512-PCP+G+Zy7NG93/d0jUFyrAMcegyjflBzvU7UL+FWWnc1/yNrjtAdJJid3LhxtE44t+aDQHB61DdG590w70BeMg==","signatures":[{"sig":"MEUCIQCjWpBflTcFU61qFTOExA5e1ju/PSuZ8GOdgSUPd/zU1wIgDSU2obyN9QGq5FAgaLpbcSBUNzrHvdEoC59+6G5kkQ4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":609263,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgNb2NCRA9TVsSAnZWagAAuZoP/1n03yZKPkqdIAhNspJK\ngtv0bEa9Bt6zvZz4CYAnug9Y1hNSQBJ1P6VTpYQuRqteO4mvwdnbCvVZNMxE\n6lEOaJFQ9XOgpZp2+MwFpiPfyqrsrM4g5mZcLGkSpliUllvzB1lJQrIHDfLd\nLAX1m87UznJbTSDlDIbkH5zO1hMpBg+gaBFtJ6BitUJKGeS3fRYc7yZ58fLm\nDQA8B/dKNPD2YNwNLkI5khsBLwqTeHaMaoiO4Z/Z+pUDpF/R4y0GBYsIU30f\nQCws9yEbFqk+fPjUd00V/ay701wmLGFc+NnZrYgKCOIsGiTuGGP7Jji+RXA/\nPEFrJa1ZvtrNv82aS4x9UEQxdBmrtdjpNERM1Hlm+HC4c0TBiFleIu6VNGNZ\nN4Jlnuy6+rM2SRVJ3KjgE5LpnDT/4VdD7NmTBsZicV4svS5rGv7AdWBDTHbz\n/E3T/1+b6pxRGDbGSOyuoJyaafSmiYStCOp0IaJseRV304FqJ54lNG4SIzU+\n5yZOr4YTmnIPmzdF3mC3V7Pd9nfhuWbmmMbmX3adsTl7zTTonAd7tQK0Kxga\n8QTGRSG02CSRevnz+rUuzSnJX+pjDFbJ8ogssJXqqnMa1xz+vSGVgD3SwqMs\n5SS+UAFFwHZhbD8/kN3+FMQ61pX2QAJ7GjoZ63NnfetvAJ+iHUMjHj8I7Zby\nTVhb\r\n=yoJQ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"640699e682425935602ed22f43abaac7b514fab1","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.63","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.59","@balena/jellyfish-logger":"^1.0.33","@balena/jellyfish-metrics":"^0.1.99","@balena/jellyfish-environment":"^2.4.19"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.1","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.107","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.0.24_1614134669070_0.4831293751210801","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.1.0-remove-lenses-from-view-0e311517c1e18d01db78e4c0763d2d4d1eea525a":{"name":"@balena/jellyfish-core","version":"2.1.0-remove-lenses-from-view-0e311517c1e18d01db78e4c0763d2d4d1eea525a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.1.0-remove-lenses-from-view-0e311517c1e18d01db78e4c0763d2d4d1eea525a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"78bf97f2b2d36d517053ae7a60425d5a3e9fa4be","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.1.0-remove-lenses-from-view-0e311517c1e18d01db78e4c0763d2d4d1eea525a.tgz","fileCount":98,"integrity":"sha512-yOwuOIlLNSJH/pbabmC/Dbq/7IXirFLMaL0E35t+lztjRFeSlHfOW6ZYrzP5Dyzs702mQavO9RZ9GmGSKhEmbw==","signatures":[{"sig":"MEUCICH4/y3bD1/D71SjA2RumFeOlNWT2+apcKdwk/JfvYf0AiEA4apgxDsudJ4vQVXwfz2DwymxP1EXSTqaXbJ4L1uRjNI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":609846,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgNgOHCRA9TVsSAnZWagAAVxIP/A4Mht3/olLw/GMl/gcG\n4Xu6CK3fp3G/1VMpBNI2+5Qr1DS5O9IH9mSF0xjKMiMifuppou0+UVVjuhFp\n05dR12QcrDw7WQlS+1o934f8TUuzR+4r+O22ly5NS002fRRosilXeUfgh3Ws\n6Mo2MdCO1pJsw2tB/1FJz3MCphCZjY9LNn/NbM3z3iSki0oNnzJSh717Y8Ht\n/5I5lBnMqcDZ7oSRqwFyHyONv3BfM0mhpKVqBYGFG8cGwmq+JGuVXxGMLGBC\nZTUbU0ON+d776gL8c/4SI32x5VNX2qgYWx3QlhJ0f2woZCxtobmqA3RhVvtz\nlKeJytxT5TPdNlT5m3dPsgUTOqTvpQojnUGxGhqRM0WMIOs4xTXRM2nlD/3j\nFTDpqUe5PUzJUVx2brV5IUHKjm5qRcR1O/2rChQZysdVhiR3WL/EhyVrJT7Z\nOwed8EKeMxYtFy2Q7LCwkRbPpHuqSUfbPBFtdfub2LNC6YqJoUBIDy+7soyu\nCc8+zm2kgDUMVXuBT0+xlX9mhwsmqoyRJpbgo5LD6M2YJYco1b456JPplKBf\n4OeltydO/0IcfRQmjyNj1V79Buvn1dC1AhL+6JV3DamvbXgkimHnqyweQb64\ndJsimjSpIA/EhphBaHQdpYvUwt/b8RmJ6WbGCovq83Jn4kOOSEBvfbzElmeD\njCwI\r\n=2LG0\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0e311517c1e18d01db78e4c0763d2d4d1eea525a","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.63","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.59","@balena/jellyfish-logger":"^1.0.33","@balena/jellyfish-metrics":"^0.1.99","@balena/jellyfish-environment":"^2.4.19"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.1","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.107","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.1.0-remove-lenses-from-view-0e311517c1e18d01db78e4c0763d2d4d1eea525a_1614152582465_0.6926843841231947","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.5.0-generic-oauth-6a8a0f9cca88c4ab5fa4075f00f17b94f5769c00":{"name":"@balena/jellyfish-core","version":"1.5.0-generic-oauth-6a8a0f9cca88c4ab5fa4075f00f17b94f5769c00","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.5.0-generic-oauth-6a8a0f9cca88c4ab5fa4075f00f17b94f5769c00","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3228b321f58e8d37f359a48863facc94599186ee","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.5.0-generic-oauth-6a8a0f9cca88c4ab5fa4075f00f17b94f5769c00.tgz","fileCount":99,"integrity":"sha512-QN3dTMzZ7RJwRhkcbqn6KYW6t0OJ0J3INKuRWQuw5f0V63+v35n5aJ+RW3Bu32khRXCJYnTWfywTK8uzIvZuyA==","signatures":[{"sig":"MEUCIQCGFyPrnXAS6nZRrRlNWTw/BpkQ+/k8zh5sDy8kY0gyRQIgQls7a8dn7HWnZGNOvJpwSoXvGwMmqje+ajBgJ0rXdtA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":602203,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgNmGVCRA9TVsSAnZWagAAz0EP/1pjkEW/Hnu1drmW2hHO\n58j1u0do6YnMykoNqfcrlf7Am3hZl4vV5WZ8r8XIPvEc2GqunpBFb8/tAYXd\nTqDCjh2YWiBlcP2NgALvOwEtvNerzB4u/e7MdXgMJCkpdoymR81jHHVoDCHk\nKUxEWaeNFSFg50kFXPi+emDJBL7r3xSCPG9bTJuivqbXltR8K58/N4KundWI\nKLufyHEyOYyjjENOOMXHY7h1XMsWPMhaTI2LbLPUcvPspyric/Qerzh5FiU6\ngOAPbfl8XI61c85UFHyJFVTTBdya8hpBobOp/8GdfeEcF96pLgz8V8hV9dTY\nE1Ix3h9P4k7cuFf+1WfJ8GD5Y6Qs9AidaINKGENxdYj9Fjs0jaTsQtjQOjPu\nRS/hszhQhG5fL8dAAuiCbGXtkIJfqYUll+s8IfRndRFZaJEYeWolJALxE8fo\nwhE1DjBsMKAQabtOzRtu72Es5cCABMGEuMTDQ1bLgEJnrkoXMvjflj/kBxLb\niF/M/9CwfZ/gfpiXHfNMRCEaZZ84w8f6a3VTeC6ZW+jYIwv35ec3iEofq83e\nerZC0igbBYf9mu+p/yPB5JcCwvGhXtIw2koVGYBhZ9Q1u9YHdMue08FetS0/\nv+MsJITvjrVWzI6MrWONfFsIZz22uZEdLXJr15oYqbPP7Gd/WZ2Y9v2aZOk1\nBynu\r\n=WMCi\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6a8a0f9cca88c4ab5fa4075f00f17b94f5769c00","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.61","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.58","@balena/jellyfish-logger":"^1.0.17","@balena/jellyfish-metrics":"^0.1.77","@balena/jellyfish-environment":"^2.4.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.101","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.5.0-generic-oauth-6a8a0f9cca88c4ab5fa4075f00f17b94f5769c00_1614176660877_0.5903558390557075","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.5.0-generic-oauth-edb38f80aba4c9c511a1b9f93892bbc502f6a774":{"name":"@balena/jellyfish-core","version":"1.5.0-generic-oauth-edb38f80aba4c9c511a1b9f93892bbc502f6a774","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.5.0-generic-oauth-edb38f80aba4c9c511a1b9f93892bbc502f6a774","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"74fbc5a45a12dd5a8d9dffe42ebef5dfdf169fb7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.5.0-generic-oauth-edb38f80aba4c9c511a1b9f93892bbc502f6a774.tgz","fileCount":99,"integrity":"sha512-BvQu5qIB3qWA4fD4VnQXFrg1J/dxr9tbj5rTTehpT4R9Y7Cip8PW83+LjhJwIciYsMaHgxtFW5TI/LJQHpuyoA==","signatures":[{"sig":"MEYCIQDU+qVwoBiz04g6xJmg9laGWtEQpygQo1aAzhBBXYrBNgIhAI2vUn9Oh/Y+xwGCCWE6JTBpLV+IeekBti0FUcyRup8a","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":602240,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgNmSXCRA9TVsSAnZWagAA4hAP/iKLgvHt/7R2mxu3aP4p\nE/CdyCEuD9YDSEtnwZE9bbMqo7fNAV88Hik5QkT2IIR4sEk8Ep2Fe/sHcvbc\nlfIGcn73HOW3V4imTQwNQC+4yF9QYYrIurPsEruR/BFB7XP58K02pp+r9MKR\nwIk/M9O6Qknr3OlX1z7hYXAD1KaZ0OLJ9VBovxfQDO/ZzziAdDeUCf2CkjR7\nxfW/7uBVx9FnPAa0NvznpRltmy4Kg1k5HQN7fX4njshZB4vIZcheXs6R0htp\n8t8ZICIfVghoi1Z3mEgDvblu1yZCWRmzfRbnvU4cecGtq5ZKwATBcXqy/PZd\n0Fx7SXeMQDnuqdvbxC0pspstB2DG/COSi37O6CY/bt3nqHBfe55ELOE3XJZE\nhCBTNSfjmUU/i+MWp6ivUJoeLifP/rcN+IPSZdtJ8nFoa9MRoyEys2CXFKCn\no7aMf3CxvZCbwgKS38to+nl2TgNo37yGtpdUR0QFZc9qSb/IKwJmJP8T4fWQ\nWi21Y6K+JB5kuDl/1252R8toOdnoHZLU0NKe5fWNl6FlCOaKqBJS9pEdBTWQ\nZpHa4v1fHljSSDtwOOjcNK4vi6a8l6F9aI8WggdPJA3KrTeWlaBzpwTtkZkB\nK9lqNB3ZU6ve+ua/Hxw2C0aWqEYPWIKUqC7wFu/JMadrd9D+xZc+6+hLub6S\nJoqR\r\n=pb0r\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"edb38f80aba4c9c511a1b9f93892bbc502f6a774","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.61","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.58","@balena/jellyfish-logger":"^1.0.17","@balena/jellyfish-metrics":"^0.1.77","@balena/jellyfish-environment":"^2.4.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.101","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.5.0-generic-oauth-edb38f80aba4c9c511a1b9f93892bbc502f6a774_1614177430635_0.023852381135492484","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"1.5.0-generic-oauth-b73c422bf4d1f704206757993a919bcee4f06c02":{"name":"@balena/jellyfish-core","version":"1.5.0-generic-oauth-b73c422bf4d1f704206757993a919bcee4f06c02","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@1.5.0-generic-oauth-b73c422bf4d1f704206757993a919bcee4f06c02","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0c771ffb1f041db4fe2b122e73887647462e933a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-1.5.0-generic-oauth-b73c422bf4d1f704206757993a919bcee4f06c02.tgz","fileCount":99,"integrity":"sha512-VCCUlpElqj61sEKlDUVEMANWfkh8/3EX8zUHmvZXa08qRHpC62sMTpwu89rx/mXlvXpzj0T4ETgf1626t3LXuA==","signatures":[{"sig":"MEQCIFG7Tpp9pgG93t8F7oDmbS1tzU9o/PJcMQZ2BuyUJflxAiAUukErHk+lDuxTWMfrFJD8h1ren+tGN5gDFAXIkaSguw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":602241,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgNmiLCRA9TVsSAnZWagAABhYP/jSlhplsmgzChqWvbr3A\n+fhOustDDAJ0g/f4v/TWzlfzKAB/mUo18Vw7RjVxfNjKsyFz92HB/3PL/YYW\naUn9E6U+C5nVE1KK+id0RZeB568BRTeEaELJiUR+ghszKYU3vq2s4WDRjcol\n6q/kTj/67x7U7PG56He/pnlAEUYWd7Zh3YRr75Pa95pKqhboDu90oEakRYjE\nHF/JgTj6hren07XeBCEb4O8fTGLTl8wr+/Fhrqlzj2Vbuwp5PMx+ya9ueAgj\nGno8guIztdv9shOdwp8hwTEJhlgU8Ek6JULgFcidwfS0cic+ffQNI/WKWHD6\nEQVl5eemrW5MbnvoBGWVK1RIorErVFigRfBfr/pqV6Pc3mOxczCZWqprIW36\nQFNkUD6f45fui0vYmYNz/sc+RuiNfawa+uviBfOfUsi/naiaVWDqKGbRLFmm\nQRYYjlTahrfu0D/S5kff2ugkOMU2DPnYARKDVwkkFS6QdohlbNXr4N3VfM7j\ncnGe5v+ntp0uPKbNHmg92Hac3DNcpxtl/x3MG7d3BCye6mVFSp+vV0aiVgBF\no0jSeSjeKFVGgGYIcpI4aG5nfqJWmM/tcIRsC/W4qqugr5hzrjKU466aQhmC\n7BfSLf36LsPHre76fURCQazWHIASDxCx9tkACl5JtjAkzNEK6UbKm0UaAQtF\njYqN\r\n=VJL7\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b73c422bf4d1f704206757993a919bcee4f06c02","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.20","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.61","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.58","@balena/jellyfish-logger":"^1.0.17","@balena/jellyfish-metrics":"^0.1.77","@balena/jellyfish-environment":"^2.4.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.0.9","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.3.1","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.101","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_1.5.0-generic-oauth-b73c422bf4d1f704206757993a919bcee4f06c02_1614178443079_0.9225955020341212","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.0.25-renovate-docker-non-major-794c536eaf7530c1019787dfc8c7a77e1ba5025b":{"name":"@balena/jellyfish-core","version":"2.0.25-renovate-docker-non-major-794c536eaf7530c1019787dfc8c7a77e1ba5025b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.0.25-renovate-docker-non-major-794c536eaf7530c1019787dfc8c7a77e1ba5025b","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3ffde7548d2e66d2a19fb75a32791e5fe1bd16af","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.0.25-renovate-docker-non-major-794c536eaf7530c1019787dfc8c7a77e1ba5025b.tgz","fileCount":98,"integrity":"sha512-eLBHqQ8D9/n1OYgXkcNWbHUjrwWGNaa6xW8mtejjlhomz3vuhNNJS2LbHKcnH1Un+o5lGYqixmN1sksFk1I0kQ==","signatures":[{"sig":"MEUCIFIgiHt8AgKHEbtNLjQb5DSq+mulBSUknsKEgZxRef5MAiEAt8uF3CwRtGmLKbkHdx7XyIau5cYYWgCgjmFi8Z0nlZg=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":609751,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgNn19CRA9TVsSAnZWagAA4NgP+wVzZlaPPtCcwzJVdeg7\nMEiq+RvXicZGMc4tbEPaj3+oqgVJBMvKY3GeUhhZv2OQxdbd+Zj2Awt26tLu\nqL2IfM/uGqXyC2+8qcmbZ3EKo9W+KzScxSRgOQt5mEP9oM/LcaLJvbaclGHH\njmd/6M9kdEdyT2OceFqcXwl8kUYTE5YO52bIEJYyHj+6lzutGAOUJeEVcmBu\nKeLb/oMG8kWc893bYUo+akZMD/ZFp7OWVzn/CS9IlYSbGMap1n5CO4FYYNw0\nSO9iJ+hkfVX6M2bpmEWFt8T/eNtocWPIgWTiSyPODNIAGPVbFUXfpD2yYQBY\n/XyaZMLxQ4ffRIWSUJX8CoRN4d+7SVHmgD0SZkuFv3EH19KKU3q834eT25oR\nIyStnZ90/sa+D0i9QohFYeAae9Hs2M+ucYTnKjHbKnDLvzWAWygI2KlqQXOC\ntJmGDeAjEQvvtUm7QVHOiYweZ+ILMnbMT0YvTaSRkkOEKMuqNyk2+ivwMiZk\nwvTO9qvCWENUINBECHCcCvV0oGGWCE8BRlOlNogP7YA144VdnSTamCSMDpe1\nwg728wD2607Q8qbaYxfoArORoS+fk52+bHslsSLaPhWQ0zJBsmWk+/dIGkhQ\nkhevrQpBKrBhe7tV8GUaEky/6IjXvZ1xD4pyxw3eTzj/Uh11i1nHUGITSgOm\nTofa\r\n=mWZ8\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"794c536eaf7530c1019787dfc8c7a77e1ba5025b","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.63","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.59","@balena/jellyfish-logger":"^1.0.33","@balena/jellyfish-metrics":"^0.1.99","@balena/jellyfish-environment":"^2.4.19"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.1","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.107","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.0.25-renovate-docker-non-major-794c536eaf7530c1019787dfc8c7a77e1ba5025b_1614183805361_0.22654041223678179","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.0.25":{"name":"@balena/jellyfish-core","version":"2.0.25","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.0.25","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2739fbf585bd03c3eac882ba0961223b95613bfe","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.0.25.tgz","fileCount":98,"integrity":"sha512-82Wa2hdMEHFr5Go1tgHHEHwIILhoT6ifPxtN7cOvQ3/DMrwqYKRGCLMDHceo4a3ccHb49pvVlpJHdgzZT9WdYg==","signatures":[{"sig":"MEUCIH7dSI5vjjy5jDXsP/BO8O6ygKDqPBMdOiHA9JGag3HUAiEAw7zW8G5j8KAturjsZjnu4Pepyx3IVCuT3rtuW66REGc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":609684,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgNn7SCRA9TVsSAnZWagAA0GsQAIHcbiO+j0HFLwmO+FXb\ngt9K+4j/vUZsn3C0b1Z1oiH3gGxS2aOKuQEi65sxBwFY9MycqZzlgw0yfYkm\n+AL1KoE5DqKaWsAmBb5adoj6U0XoaIjHdY/roodscxyfg70NxSOqNWlbXS8K\n4up7zeqyb1VIknemkJI2I3W9NK435jVWdkcfllmAJLU8xxoSjji+di+JUK4T\npwmLA1C58Pesqp42cksFZqkBcBh2wv+jUdMEiN0OgzeHxy/Njj25LHssQgqm\nhN9Md7KTPjpsL4n6agLt1GVJnB5gU0HWkLq3XLy7gy2/S8M3lC+DEDfQ8Pr1\nN7J9fqkPvtlAatyj7bPm9KyofEFy832+C17uUVA/QbXDDTlUreH6wPcN/vU/\ne072FmIW2jFUj6sD80mIIJItQ0vQbXUuWkJWCvopeJeSvZTn+0scBtSrIi+C\nK54ofCV4qsus19RZshfZgaI+qRArYEij2p3MUB0KAI05MstcWb/EXW5LKILU\nWFnFa/Zb11IYxGTVMm2ewXclfZi8EvLuuzrDrWEqkM/RWqoNPAfXvy6jEImf\nDBm42DzkJu4s9b45hJKxyG0aAqsCoB1zFsi1YEbJkd9FSwFJhvJ+RV1yxYct\npW39gyLVZDmlszKaSnkS/Bt4PpOlXUAB+2xPQMt7PKwcK5FDGoL1o+iHhWr0\nIGZD\r\n=5mUy\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"96f93b43945e8be25a598d510943218d8b93efc1","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.63","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.59","@balena/jellyfish-logger":"^1.0.33","@balena/jellyfish-metrics":"^0.1.99","@balena/jellyfish-environment":"^2.4.19"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.1","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.107","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.0.25_1614184145662_0.44177679006301074","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.0.26-renovate-docker-non-major-2a2d47e36a888d6969af087d2b2e5dc45bf05c8e":{"name":"@balena/jellyfish-core","version":"2.0.26-renovate-docker-non-major-2a2d47e36a888d6969af087d2b2e5dc45bf05c8e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.0.26-renovate-docker-non-major-2a2d47e36a888d6969af087d2b2e5dc45bf05c8e","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ac0a51407fb2b733a510a6ceaf8dcb90fb75e5d1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.0.26-renovate-docker-non-major-2a2d47e36a888d6969af087d2b2e5dc45bf05c8e.tgz","fileCount":98,"integrity":"sha512-DQW4FgUeuZDDq6Los5BfkzK5WTAssaZUcnBtfmmXKUXLtC/LJvof2J+7btAfeK286RJZ5Zj6F/v8M20iuLczsQ==","signatures":[{"sig":"MEUCIQDOr7BO3ZGDSEYpNOmV0Hsn+0D6unWxtuXhA64NM40vKgIgESdZY+Cs6fUqbOkChzcVHJEtPJFbyZHPFJdElVKNX78=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":610170,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgNqKRCRA9TVsSAnZWagAAa6IP/jI9PPv2pRSQdhkSfaVC\nRjEdxX0gKeorGmV7Z1+IG1HpSjbd+xftsp98DcKbM9DgL/UM2hLVfaYQU+xS\neqsOSHAo7DmVP45Bm+FI3mO0QFfJaI8gLaKtD4R4foXTL/M9iod4QJ911Hr1\naZ1BLzpQGz1FVcq2S/JNxvJc71yGUQC3/mTjm7eJZxA65P/CgQEHojV8i8a8\nFmvNZDmBscNBKlqjHV3dtpotBPQSuwQjXWoHpiPsknqXnKa3M6EuOOKyTQk9\nbWzn+VBYJdc6TuiIPd99m7K/Bf8LjG74hj12iZyqmeJ48myi3m5ZGBsZHV8q\ngxlPP0xKT5FEcrwiD/lKwKYKqSjGImD88J+L/JQJ3nGi0XSfrCeOqWe4rZb5\nmu8E81c1SjntiBC2vdH3/SjJdjHy+I2Vtv19JaMWvD5rl32UTqs7MblGmHfN\nZCHg9mB6KM8PbkOYjIUcqZUP9DG45gpNEdvEijHawASgc/53sgDKTlXLG14w\niM23xODp88sMuj5PmrLzJG/c/fgoh1ELNjlh9Snp7OzZYnsVqn1jdQB0wIIj\nFLEG3eR3fctV1lvZcD/leu9hwa68dRye6W8ee9XCjnHFt18t97ArsafTN8Jz\nj7qeIudjIA/6weeRTUWdc9VrlO58g9tpmvojcrrs0IBL6dBK9xMRHaqrccgL\n9m8k\r\n=aIxf\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2a2d47e36a888d6969af087d2b2e5dc45bf05c8e","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.63","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.59","@balena/jellyfish-logger":"^1.0.33","@balena/jellyfish-metrics":"^0.1.99","@balena/jellyfish-environment":"^2.4.19"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.1","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.107","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.0.26-renovate-docker-non-major-2a2d47e36a888d6969af087d2b2e5dc45bf05c8e_1614193297344_0.5803872518651463","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.0.26":{"name":"@balena/jellyfish-core","version":"2.0.26","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.0.26","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"603699ef4426022b42536e2b507ff22d06bcf387","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.0.26.tgz","fileCount":98,"integrity":"sha512-mKfNol/SiC7mqptre2G7IBXwabe/vQhJihYGARioAUBn9ppvbY0uGFkqCS6Xhtx3Age917oke4KihqNBk2ua1Q==","signatures":[{"sig":"MEYCIQDlzi62+EeElOYJDKqyZdGjhGr8Q4sCubNzP/rZ9pPaIgIhAIg9IWwo1szj9Tk6OhnTuBhjHVxk5iws6TUKrEyJ/pNi","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":610103,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgNqwJCRA9TVsSAnZWagAA9N8P/0u1UJPJ0kJmLu9+F0ok\nn2jWqUCcA1D6JWW3LuXLJJGAQTIIH5jx9zOaF0WTtebmMPX591HGMMczFTMn\nw7vO2d1ub5lNu7c6JXAuvjzCL1AiERKDIlqp0bAr5DS80QT7Jsbb1Iai5SYd\nIO0EFfLZhmz6Q2vRrveuH4U42AGfqNRnR6S0UkY/oWpAftLMOsKrwPi1WKVi\nRk/EPcJICcaBiU4Gpii24RfTmwYMV40bPcu7Z0xiWQHsgOCs7ARamTw/PX9Y\ns+vXByJoVa9zZyWlm6xU1/q4dNtP8y5ddhvBUX7tAvNsjNxkCSuWsQ/2HkI1\n65EbQEwbgvMufKco1NiGGq1kh7rLKYZ5B2a83dKg9MjOOqPlRYumSzXF0/JD\n+u6ID80qriRULiTlrVeAz/Ir9Fk48WITVmabTuxh0RWE/rf9zpztGu0btSm1\nMJNI45Aqlg+Hf4LSRsELhg+LqlZf6RjalQaOtZemZMkElys1uJJK0xuxtv10\nKbFlCzFFK0Ktl0p00K8fwirOCag3/OFF07GZVSO2S3T8s1y1NpBXI+mnWT7T\nH5aOWTJALmCnetD+Tl4HNPm6Z/fwgfpKOPLveqq18E97tBOUxtA4O+vSj9Ax\nN0l6oCPlkZvrGN611+UoPlbY5tDQRyPE7u5ISjtRGT7E/ax71wsxmkklZSwa\nmcU/\r\n=7eeD\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"63f3937bd637dcae7da1f90f8c3e66ce8a54a9a1","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.63","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.59","@balena/jellyfish-logger":"^1.0.33","@balena/jellyfish-metrics":"^0.1.99","@balena/jellyfish-environment":"^2.4.19"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.1","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.107","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.0.26_1614195720634_0.9097978043831774","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.0.27-remove-references-to-old-date-columns-ec0c3dd0cc161280d0a13d4f872b91804e370901":{"name":"@balena/jellyfish-core","version":"2.0.27-remove-references-to-old-date-columns-ec0c3dd0cc161280d0a13d4f872b91804e370901","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.0.27-remove-references-to-old-date-columns-ec0c3dd0cc161280d0a13d4f872b91804e370901","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"32c58c93b4c56c0ade6b094c80a627fbe9aa4c0e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.0.27-remove-references-to-old-date-columns-ec0c3dd0cc161280d0a13d4f872b91804e370901.tgz","fileCount":98,"integrity":"sha512-0it73Y9WBocOfbLbUS1MGNO5wBmY/5rqtcVHJ4uR2nKmK5F5nI+hdtsD2elXK8hSxgw3DlALvp8tPO+zhIMYKQ==","signatures":[{"sig":"MEYCIQDJeo6PTJvKtTplgEzR4gutlchmWy7gKHV87ISmimryzQIhAJ548bHbmaYw6zfRv3h87Ok5C9o0iycxoYKUz2kN4Vr4","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":609946,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgNuBkCRA9TVsSAnZWagAAm0YQAIzWt2b0VUcuZ+4YFcfy\nlFCY8eHTgiLv3xyAQNLc9lghzfNketI4XJpMCAUuClD4TmKnX25O+eEv9TxU\n5ubk3RbTdhbl8sHkf9VT/mozDiqCmzmKH7VQFrZ356JcFYvgaSc4XC9LRFzh\nUt5DvJr6iMytm485H1dnt7gPB7l95fDhrqPdRMCWZ49/4kYFXINc1GLdm67Z\nN/Ckl1RBufk/30gXQs1ODyWb5AyrZUgmrMZ13BXnrJ9HlqRBgJHgzHHywyMS\nPjxe2v+f+MpSFlj0AXr+3gk8aCWABWjjJToHzSsPgIeE3mvQlGyxC6gErSke\neDh/zErisqdq9Q0IpGJDUcBkFBatgjlnSN7OnrTtcPFPAQssphQ/zXipFH6P\nHM1ppza92XAgMtdz3Mc/HbbBkyENzm9zQh6iUpQYS6teWjtfhY448MyanEnT\nEu6UykTv6706vPeQuSStA6+Beu9uVxBVS891iCqeRVG9Kck3FQB0JzepwMO7\njPSgstgwuTHhhF/TtDVwzrf5kAvvLLDiaIpddNe2hIxF+NVLeLmKoHlxTVN6\nThB+draHV1bKkNwg43N0PI+dkah+RAf4QjJPAXpC9tEg1CFuckQZ4qkdBV3k\nQ4ChFt/6kPFgy0MZw12i1/JLuGjzj2BRByxql5dNVknA7skWsVHIYwF4Nu4g\noHKb\r\n=89ri\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ec0c3dd0cc161280d0a13d4f872b91804e370901","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.63","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.59","@balena/jellyfish-logger":"^1.0.33","@balena/jellyfish-metrics":"^0.1.99","@balena/jellyfish-environment":"^2.4.19"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.1","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.107","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.0.27-remove-references-to-old-date-columns-ec0c3dd0cc161280d0a13d4f872b91804e370901_1614209123453_0.042294782987993385","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.0.27":{"name":"@balena/jellyfish-core","version":"2.0.27","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.0.27","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9eb91cb51b9a28b9691fe3e2b8fa90561c2fffde","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.0.27.tgz","fileCount":98,"integrity":"sha512-7uFzDoddpTMGR7mHTfYUN5vc5V9ydwIV2kqmOQQm9Gl903zQPHOavFLyVnnykChEP6XKj5keCGmOE1Ik9VRy4g==","signatures":[{"sig":"MEYCIQDXAYAhGfogWLW+oUSey1tyIyYw3OQZJIZytjF587WHXQIhAK3GfEr2zFBY8gael+pdhFIb22vCRN0r/VmrtZnxDyti","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":609867,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgNuScCRA9TVsSAnZWagAAmZIP/0xki9Qo6GPnMWA8e1h4\nqv0sq5KxIFs1LgWYdTSCD2HXXSGp3NCGZDajQLJo4Qp1sbsvLSKaVE8wVvjp\noDUm91hIVF1zdiO0G0oS6tkganq5m8TIQtTmRyZw3Es4kr/0rCMTbUiYHLO7\nWAnj1RV/7PKONrHeg2HKko4H2uvK83fuhcnjW9TQcEVAZZsjGeZT7Y0JmZ5X\nC1u659ajIkoIRVKGFlTSAsSt0lPfri9Px+GwVCQc9g1ycoafxuBQcB79rV5y\nkzK1RebKcFmnFNdhm4Zvg8eOgCks7g3G0exJXggYh/TIRUVC/nLwzY44oKMH\nPyKAV1QSjogJKPP31HMTBw9EHMBRHvvDnwx8iUJb+zqTBL4KmgWf9p/IQ72h\nxtsTE0rBbHOA+UQVRtiWvmn7y7dbEMHOOl7epG4uJ4QgBPAfqa14BYwhAaOP\nAriVP6ACuMxjk0UzI0MOQsopwDJTOpeeFih8oUjbBb6uc43U0P8JxMXa+mAF\naM4iD6c6/4LnxBmPN03V6BnmwbCrRGVDuDOr9n+AvJnVfu3yZPIbvIODczUV\n2GVHP5VzQbPoGUzC2EbwsF0QXsAfcUzKVfRU9tuyzu6wGikbAkFCs01dQg0m\nm2AcJxpPOrNWabt/0ojLFc7fWyJiI+V8VqKuAxbm4uhAYOEBPy9OBZU6I+Qf\nqtoX\r\n=BrnQ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7901bd1ebc6b77923433ecb1ae69e058fd0b7431","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.63","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.59","@balena/jellyfish-logger":"^1.0.33","@balena/jellyfish-metrics":"^0.1.99","@balena/jellyfish-environment":"^2.4.19"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.1","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.107","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.0.27_1614210203349_0.28266274072633535","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.0.28-fts-index-check-exists-cd309bbaf18f955badfbbe660f53b772e1913b83":{"name":"@balena/jellyfish-core","version":"2.0.28-fts-index-check-exists-cd309bbaf18f955badfbbe660f53b772e1913b83","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.0.28-fts-index-check-exists-cd309bbaf18f955badfbbe660f53b772e1913b83","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"13d7a5d99fd0cccc0d877b1aa762d17183222f81","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.0.28-fts-index-check-exists-cd309bbaf18f955badfbbe660f53b772e1913b83.tgz","fileCount":98,"integrity":"sha512-1w38LyzpwIfwf+a0LPybQLJToAGU5SbPoObmARZlM+APHcLF0pHQ1ct95T8CT/hf/W9v0zB2ijMgPOmideYoIw==","signatures":[{"sig":"MEQCIFwq1cCKN5PAudq8VsVA47jJQe+zYfcFK3EE7jzRyShhAiA598r2a7llpOEEsBuGzgUK7DoIBndKxI72qPgtzbxEZQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":610656,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgNxdqCRA9TVsSAnZWagAA2/UP/1wgFUrai/AFnrCYDny1\nbqOFAvsn8vh1r6Jj+GKqyLgt5qRX6j4rGIysiQspYJgpV8rYZI5K/AHiZ0Gt\nj53eujiDAo2/e/mqbkJmb4h6uLfjnuyrnHxhY4Ht6vnH0sb99mzZJYbJ/8Io\nd6raFEPRpwDHoXoZWevQ5THQW7FN41LBZcZ77JHpNb7cA4wN6tffQe84LjcE\nuhg/pe1PS8+mO4sXOuHsDqzwHo78nnQIi21eW1w920RqPzZZMXSbMjCiFEtj\nrxjWN6NpiQBeBf5RVxKhutzBVneb1aDNM6Vbyp4Quc6178IK+AgdvHZS95iM\n1JoVaqlZPwuRZf1ERl1qqIf9P7ANPr+V/E4ZBqtt4jJ4Y/6iETlBq8jrfGJx\nhCexpSlYdryEEUapX6G8P892y7/nF+fJfCcUxbZtLIU9QSxICsSf/7Pg/E5n\nfDKrhMCVTAais8aFOYR3olnIx8hG48j4N5s21ojxtM31T9UYL0ELP5E0tcgS\nFhF0/dQOMCUiH12CxN6reGE+QaH/wlM8tquvZmEv2FU0AtLCjBqTOz9isHKQ\naIXMddqTwU+GII/Wf2FmnhgA70ao+Ljv4k/d5td5Kw4kQc+PtUFYh1Ddmr6X\n9e60H1gXmzGZWSJRXLygvKBYRKPOs/RLNORrPoGp6FMe9UWKhShNS+TJpCfq\nFrQy\r\n=IeGg\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"cd309bbaf18f955badfbbe660f53b772e1913b83","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.63","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.59","@balena/jellyfish-logger":"^1.0.33","@balena/jellyfish-metrics":"^0.1.99","@balena/jellyfish-environment":"^2.4.19"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.1","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.107","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.0.28-fts-index-check-exists-cd309bbaf18f955badfbbe660f53b772e1913b83_1614223209386_0.21821495926145884","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.1.0-fulltextsearch-on-user-name-9805651b6c500a61e5ceb1380612387fc1f152cb":{"name":"@balena/jellyfish-core","version":"2.1.0-fulltextsearch-on-user-name-9805651b6c500a61e5ceb1380612387fc1f152cb","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.1.0-fulltextsearch-on-user-name-9805651b6c500a61e5ceb1380612387fc1f152cb","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f81bb79a08897d4de6ff2934c8d6ea27e0463e58","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.1.0-fulltextsearch-on-user-name-9805651b6c500a61e5ceb1380612387fc1f152cb.tgz","fileCount":98,"integrity":"sha512-RCNVxfFUbPvUgHtqdyUKhe7AfsJAa9xjrks7CnAIKrUDqpi+dHP9mOAwMq07t3jlj7SWv+cDX3i1fitntgTDKA==","signatures":[{"sig":"MEUCIAD/YvM5k9FbFm4IFJ60rYZL7PbA/PjAsMSUAdB4+uJQAiEAlNvybtlE6cw4POdBz81DpB3U9WmfOlest/hVAOQ5kLM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":610534,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgNxsmCRA9TVsSAnZWagAABTsP/3kThQZm3MCEfTQ63Zaa\nv+TgZ0eAvPNVo8ptjRFpnsHeJNh1aaxrgtnXeBKwNF5gZUtj7Xxl9EFeSJ3E\nJ7+LoU7ftmiHAWkZb9dvLDf3hYj6BzV93Ud1qIsx04Sy6DBl9R1+PlkBevpU\ngcqn+q2lBAMk5m75qZ7WbZWLWvCks0/S+O5qivR/vMWmq9wpI0u1PWCkyGZb\nJE2o5RGBx+cTufLn3yCFQWDGUlahlSRwzpLnN6pb34KagwNxoBPazIWTsJVC\nrDgF0YEfGfufrWO+c/I6Vn2ZwArk1Y3OC7fkhU5XqrULS47mkvoQzCzbz7Vo\n889Z+t3aMYh1A0m7kIMTWMuO3P7q0pqV2QRywFGelm4+MoXjryR96VZwaUtq\njLomYABJzPukmMchOl0E3tj12g6b3UOl3fot4XnBDqpnnB643XL6dyiiqjeR\nh8U6oxKGO+7SPGttAR/RSXh0wLCCI/8/Idm9eMA5n/FsPlNKBVWizS+duKFn\nN9/FHgJ+uEmEym1BuX0vHI0AUYurzhuDytlalCyYvdHTPI/1dnx2TDL2N6wz\ncuNDaeeD9S/MJTG49jdy/zKDqSU0mrHqOaYpT9gy1ShORNW908s06u5YsOoD\njcnn09gXTrwUBE00b6CvKT4mgA/yCTVjAjz+aLvERLMPJyTHPcpDNqf42ZYR\nsi9U\r\n=fZzT\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"9805651b6c500a61e5ceb1380612387fc1f152cb","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.63","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.59","@balena/jellyfish-logger":"^1.0.33","@balena/jellyfish-metrics":"^0.1.99","@balena/jellyfish-environment":"^2.4.19"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.1","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.107","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.1.0-fulltextsearch-on-user-name-9805651b6c500a61e5ceb1380612387fc1f152cb_1614224165888_0.6378937939392606","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.1.0":{"name":"@balena/jellyfish-core","version":"2.1.0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.1.0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2b073257145a9df5630c5b9f3170895faa60ad54","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.1.0.tgz","fileCount":98,"integrity":"sha512-+Gqj9d5NLITIdTfaYZuGlPp81v9jNbo7XxZNypi2gHvt2G0HBfqwyUUaKI6mPutfF+DLbvmIV/ShKtMtMPKqNA==","signatures":[{"sig":"MEUCIQC31nxNl/KPd6REsbZKDR4zmfqjkVoHCtYPMoGfIEVAmQIgGgH4ANtBOqt1vmpn3A/HDmX2Imw4PzzbiYkyQ3ZTd5w=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":610465,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgNx73CRA9TVsSAnZWagAAtY0P/RtbrtQSppb0qXNDg/K8\nXEbAMQ0+km0pIYgwCjhAEn4+lfaVRJsXlDZFAvFnLDizYQ7u+GkLQqu7RpxM\nRizB3FHIe7DK3gqp14ZTHcNly/nt/1QOpHccWWdbhjg4S9KLlY9Yei4mPNDt\nk9xK45A3nhTjstu6dm31n/9VQjwTTjMB849RFzZhP/6RFvIgU/W4HerW7a0J\n2WmWbdjDmDFh6GOMaQhodhi40RFTGVOscQLA1j8HouGXWib2FLDWHLMwtaHj\nvndwVuvvMNFtoQG/YfNq641hLNvxzalrLhYfhW7U3Qfbibwqyk8Spt3XvR85\n1LQaHfJqePARYiIbFoMJvuQRowaD1P5TCyqnk6gknfDffFaTzUm+EFER/Npv\n+dl3XweYnPmyYC4tzKglWAMgXyxwh0iVbXntXHkjsLBO/oxkZ+alX2zrQhxX\nODNt1qDpIBJgURFj6K912HF/mLWfOb/7dnmWoX1ypXCCnBGOkLkd+Uuc+6hl\nxO+Ep8WXu1R+mtoqFywVOel84vpUqNKOiocLrcZ6RBY7oN7+pMTxtyliz+Li\ns0N5BxLHZqHXKHSQJblas068NhjlCakEhKiOpETAv/83uVf7nirB0qPpD3A+\nNs+nrLuvOSC9HhchMjoy9tdsjLf4BhWPZyimPhOiPtQu5WdezNegkapuhR8d\n/axN\r\n=GnAe\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"275e05f21b0da18ff8414fa14b808b4ea0068602","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.63","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.59","@balena/jellyfish-logger":"^1.0.33","@balena/jellyfish-metrics":"^0.1.99","@balena/jellyfish-environment":"^2.4.19"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.1","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.107","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.1.0_1614225142754_0.41549618264375376","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.1.1-fts-index-check-exists-563c7aa0df8b30eed0547ffa43bd0c656c712845":{"name":"@balena/jellyfish-core","version":"2.1.1-fts-index-check-exists-563c7aa0df8b30eed0547ffa43bd0c656c712845","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.1.1-fts-index-check-exists-563c7aa0df8b30eed0547ffa43bd0c656c712845","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"76c940ade5e7d112f82d96beee1a3c7cdbb40292","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.1.1-fts-index-check-exists-563c7aa0df8b30eed0547ffa43bd0c656c712845.tgz","fileCount":98,"integrity":"sha512-sCz/pvIUW7U3jhEGiuek4ZV1HQiwNBy3qV7IxcMK/U5jbVcD54BKcR5C1ymkQlUv5cTss7GubFGkZtgRpsScwg==","signatures":[{"sig":"MEQCICGfuLRY3ABrxCLH//rzzGS5xmnu2KXjBQJCozLwMUV4AiBIrNJzLZsdafZ5eHiCo0ozHPG5mdsGdT47Ja0LtQPeSw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":611252,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgNyXICRA9TVsSAnZWagAAXHAQAIiXEK/ua94bp6ZB1qq9\nkZQ5YGvULuIVzF8iSGGI1GPLgQM78WzEhbiFiDo4Fw6saOkEcdlHVvn/aJpO\n/h80Gnx/fNIFDM6nKj8xPWoYsJH7CaUZxw//gs8UXh0ASBEGMzA6BPctNa59\nkposRh/V9y5xCpTifnpRfePAo7+biLfjHj1g09g4BCFuB5bAr0GZiGPufleS\nHSPPwxCgWdhAdGp0dRAFtawlOpgCrA0jOtrYr1YhHMAJqQnfEb4Z2e++diEV\nTErCoM4wIsIZtqQOccJsFHeFtKBefHrTqqwEjR9JN4UDpw+5euVkv8TI55E3\ng1wSXEL1ix8B1wn+ToyUjISwCBazPrRTE6uoREcREDihMuFHd0PRVAFeEqkH\noZcpWEZZViz72t+RAP22J4vlFe+K4W+HbYANVg1TkXAuD6jCV4ZZimAmbmGS\n2aEfsIwEVzcvH0kz22iKyDeKEQ67SDYi4G77blX15WzSG/guSbFonr68uV8W\n3sZYzoBjEUjdfTxHBoHvcpCLsqYMYNb8U4sthOR+V74SMLwtG47BKWTWO9XJ\nXajzXhZ/CxN7t/qxKR025QdNOT2bz9W8agzCPHLIRRHC3v4iwVGdeJwAqD5T\nyvZJBb2pc8Xf4Koy4bFi5xxMdxy61ZcfBv9plGO6LaU51cjMOeilwMw+txmG\nCmvo\r\n=YQkj\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"563c7aa0df8b30eed0547ffa43bd0c656c712845","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.63","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.59","@balena/jellyfish-logger":"^1.0.33","@balena/jellyfish-metrics":"^0.1.99","@balena/jellyfish-environment":"^2.4.19"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.1","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.107","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.1.1-fts-index-check-exists-563c7aa0df8b30eed0547ffa43bd0c656c712845_1614226887633_0.37321919936790837","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.1.1":{"name":"@balena/jellyfish-core","version":"2.1.1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.1.1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b9ebe5f10a801afc8d83c2d38376eb7950b52cff","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.1.1.tgz","fileCount":98,"integrity":"sha512-E8qZfQbkSJDC6XQyDga6QKCBFQuA1txk6OdVP1YA+Hlp17TEwggVl/6ziLszURi0tPjsTnulHDok/oMdeSaaqw==","signatures":[{"sig":"MEUCIQC85doj0HQpgguIQ3nTcqBEaP1UeEnGd0z13k1EtQWxHAIgKZgNMppnwoJ84wTExPX9H0XN0w3gprlCtvRdNFuxxiQ=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":611188,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgN1J4CRA9TVsSAnZWagAAa60P/iKESMFWvTflhJbe+HeJ\nK+hqnZYmVJ4C2808RHJ3+VreYrWDVa4L4BVG/jPDB5lYZo1LKiYtb7kN4Mge\nrAYeQXh9dgisW1ak3hw8wL7Z1KoIrhws0Nw83JINkcmaChXwyRaovoagZjDe\nqfvMs7cGCd0bG8MwapFPFoc5w9/XA1xDpSJkuq8HD6to1ccXLFcsoT/ogTcV\nHhWSGnUMu9QeAP4+hllj4VIM9DFZNNzQz30I2UYZTDnpOLRDRnj4Bei/UlhG\nnjqqgE4TAC7v/yPLPeGvuwoW40vBmN2DmcANfcgKlhMvstwQqAo8bctsfyC9\nfau/3961hFTisfATlursRprCviDuQbWBGbqJAiQfrE2+7T1qDy1vF6tKrrRW\n4OTfeptjrUEiEJJiWBN4279YAG9lw8nCFBIWkm1sT5nGFewYKZ3NXJcCVl3A\nQ6Bz88dBqfTrfB0kvjrxASotBrxte7oE6LKuw/Lm5q0dAsUIMh1PO29jTUNj\nlXyk9CHou6FA+C2Kb0dVK1sbnt9sZLbV+6oVH1fxK5z6rVlbXunQWMQXfvFD\nGa119I1G1px2UXEC1hJY9WSYEAVPFnnOOU/dpVkI0HTppV+W2/jKi+bye9U5\nar7vFKfrZmoyrsMcxfivX4Me7Sk721qkYVuhbbazfBr6hkqKP1vKT/cbouaP\nvl0p\r\n=zGkx\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"967a72cea66cf87b13ad19e27c88176856e47db0","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.63","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.59","@balena/jellyfish-logger":"^1.0.33","@balena/jellyfish-metrics":"^0.1.99","@balena/jellyfish-environment":"^2.4.19"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.1","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.107","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.1.1_1614238327617_0.14663932976115635","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.1.2-trigger-ignore-versions-0b304fe28db848e244ff44515295b07aa4258b07":{"name":"@balena/jellyfish-core","version":"2.1.2-trigger-ignore-versions-0b304fe28db848e244ff44515295b07aa4258b07","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.1.2-trigger-ignore-versions-0b304fe28db848e244ff44515295b07aa4258b07","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6495c6d29e501cb3e32d70f42452217acd99db01","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.1.2-trigger-ignore-versions-0b304fe28db848e244ff44515295b07aa4258b07.tgz","fileCount":98,"integrity":"sha512-6lCGwbNMjlhmOd61M/WGzHDgmVajw4k/LfQhj51VviKBmLSq2q1H9tZ0g8TP58zm0Oai0pOeyon+dd93a/nlrQ==","signatures":[{"sig":"MEYCIQDr1XA82tb/dauXqLpM77jwfyjJRljJgYs5ybYRXd06/gIhAOEAP7oWvxXOZNtcaI9LbCM6bJNI84mybG0zAenya5dt","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":611714,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgODbuCRA9TVsSAnZWagAANqYP/ijbBj2XGeCEihc6+eV3\nChkYRTtQ16apwX3HP6OrJqx+E4MBQdF9cGW7q55nkc6kL8uLWCeVDFQ1ZHBF\n8VWwhtcpl7Kg2pgqnUQwAH6jk5JieKrwyiZc9HRcEP7tOpoMkYD8vI4RQpB/\nMcI+fLaBahsnRBhukWHJvsiuaFo6toLcpQqEIfDRGo3giMrDKRKtV4ARKf8i\nkiYNAapOEv52S8/2v/fl1wQnGxz0tddhGQr+81FuC6sIze3iZZoXkzB4CwmR\nzzZNkRMIxBQJAPGYkYbEGzEa/I4VjNACEgks5HMJo/h0w5cQpWcmFXBDMh8E\ngkDmET9a/mnbeXF3/0TycOyW4IOx66Y1sj3FKSmCQuHosnGITdMESLzPLw4k\n3R8NQpsTweE8GN5xn1IspMnZNd9mAViu2v+NmdChmObxkuAbEBBiP/o5Sne6\nvP8M0gphA5b4eZ8ZXxU88pRlLOKYBRSG910dgADJDnVr8SAxxK/IbFl6KTIS\nKs9omRrpG9KVZHO2Vir2Z9BmqJvrcCR9KnvaYNDeIE5BVrf+a6mbji7Jkoyt\nKTdN4sG4CzJtc7oIi/kkqYANtT9RMXFu95PAPkgDIv16cvzYH/72Q1NWM6C0\n31FcC3tqfVDyb+pqcaoi4mJp5eUr8a0HPumTwv7IzZFS5LLgpJSAf1tR8sew\netQq\r\n=8An8\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0b304fe28db848e244ff44515295b07aa4258b07","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.63","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.59","@balena/jellyfish-logger":"^1.0.33","@balena/jellyfish-metrics":"^0.1.99","@balena/jellyfish-environment":"^2.4.19"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.1","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.107","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.1.2-trigger-ignore-versions-0b304fe28db848e244ff44515295b07aa4258b07_1614296813812_0.048132085069872765","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.1.2":{"name":"@balena/jellyfish-core","version":"2.1.2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.1.2","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"535ee69c36fc3d3515e8b41b2170e108d14e7bbf","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.1.2.tgz","fileCount":98,"integrity":"sha512-Ak501t71JnNM/WrC1MkuOJ62kp1vxkDmrNI42QVa7jqC5bBom8jKO10AJN9G7Q8HPzJUR9iVhFOnS5uLjjkVKA==","signatures":[{"sig":"MEUCIC2tngiYamNUgSpT5i+d7vLWtVaEfcJ29yBNhbGraoXWAiEAvi/1Oyr63UU+IQekjY1nZ3lEBPOrWaBIMXgK1e0pCfs=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":611649,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgODhyCRA9TVsSAnZWagAAgCoP/idkrPUEo/5+I35uhryt\nDpFVxdjJBAr4nJOvts0gfP7m17Fgm6r/C6xgT+tA3zoC1LWFySgyDxTRaZU9\nzBZ0ZYexag0GD+VM13WFbVTc6roTHbQvVtI53m338nVzY2uB+3VkZrV49EKc\neeL8PUvcdEMtoo5rYgiQVsBx4dCGfBlWruXA1prU9EAK4pWJBK4F1FnUu9aW\nBFC8woXOUWB9sBBPKf5zpxEsd4pX/prd9/LxS/I/5PaR5cfYQ/4YKATBNOGM\nnRN6A9UDR9hCnbii7x53o8oRedwwDNCJR4udYikC+UKu/W7zEWlnp42oCs0L\ndEUSNvt6Ww/1PJRswZeP+h5BfKOe483fxHlaU3Lpl8cmlhUKc/ccqFsdrpAF\ne2c94QA5wumDyuc1WGnE8eATMLWCJS4cKotfF+8voUsE272TJiBXZn1mzAaZ\nx6NSnpLRM8tRSb0XyOYbxJ19zsYXTh5jwIhBypMdugiG8+RMsn36ks+Q+8UU\nGEgVM7C48zsaacR8VCd1EzuKBgmMcGwnY6GCjVmji0mgTn3xr7AUKDnN24k7\nv336rFVuJhBfecklwaOIa31MRO3Kw1eASufbc9K3IUru/BIa9M264rVtc3On\n0RfHXpc47IqPefDbPwscqLRbHmUzPDlI2WUsQiBvSauiidDIIVmQmbgvikte\nij87\r\n=qD4S\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"37d0d72494b112102852690dd197fcbba99699db","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.63","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.59","@balena/jellyfish-logger":"^1.0.33","@balena/jellyfish-metrics":"^0.1.99","@balena/jellyfish-environment":"^2.4.19"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.1","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.107","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.1.2_1614297202359_0.416252367858581","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.1.3-renovate-non-major-9ac87bed469bd63edf444cd05ab7a7006ef6012f":{"name":"@balena/jellyfish-core","version":"2.1.3-renovate-non-major-9ac87bed469bd63edf444cd05ab7a7006ef6012f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.1.3-renovate-non-major-9ac87bed469bd63edf444cd05ab7a7006ef6012f","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"335b5cfdfebf630b227a408567cdfaf999fc6277","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.1.3-renovate-non-major-9ac87bed469bd63edf444cd05ab7a7006ef6012f.tgz","fileCount":98,"integrity":"sha512-srT0Pwev8A/AqH6PYpSveRoX1HGFFXYCqOUCk+/1wvAIHGWdy7B0JQNOYSnM1XQPMJb24cPcHJa54JEX31PyOA==","signatures":[{"sig":"MEUCIQCB/eki5qevfdWbbxff9VEn1t1vm3eOe4SJ1CL6bzlCtAIgDHaostWlYqPCJrSn+ip05qZhQvamyFynsJeT3/qg+p8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":612126,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgOIuNCRA9TVsSAnZWagAADD8P/2dI3ejigq22oRNj54tY\nNM+HDzaG4q38yWbJZ8rPsysr8qFA5o9hjRea3JzAp8bOyby2j/3wDJnojQJY\n+eA2t4EVXwl7zk8ImSXxSd/HfECzQxwi1kDBWxB1hjtMwPDY+0mvsXUNfiDj\nuecdZ/JcpZUKSuOAQkj5Wpw83dmVVQYnQvaEGb2fdI7dcVgbptvduMkmw8eq\n6qF8xdq7ZU2T+IPNSZpexj39YxKFJMzRVHVlEXER2H9ZDWe48m0jvy15yMDm\nzVq+3GAasxIuP8J/gXV+vCGMMvayxJHdo1LAM5tUwU+4xXscViA2aObD8VXR\ne+h3BEqXrldQGKiT5+7g0qJt2fVK0Z24YzxL6AYvEkyDNlJ3NaiuwG1OSGa7\ngnKnquufK2k18XAQ8DRS/dmNs+d5z/A07eLkjBlv4ETiBsRr704c7mAYKELh\nIVCGjRmX3/Vzw7B5DXCjyLZf18XPFmCgMxgcxiIiLjwM1SMWtE/Tm+lHyMG1\nLQG4BzyXUSDw98/rUzELHdzr8OFfFtjOzoQN1VSoziENxJ5pB8NH1nromQg7\nxQ6VRMQtNSjCsvHixqvcwKbYVRKBr/EIrpbL3ci0OFaqwisUP386cojT8AmG\nurRaH2pGogWIOM6cjYTbhbJHqP2brYtkkka4+6hrlkFXMGFMQSm4e+9k84KL\nUkmZ\r\n=YvMU\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"9ac87bed469bd63edf444cd05ab7a7006ef6012f","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.64","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.59","@balena/jellyfish-logger":"^1.0.33","@balena/jellyfish-metrics":"^0.1.99","@balena/jellyfish-environment":"^2.4.19"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.1","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.107","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.1.3-renovate-non-major-9ac87bed469bd63edf444cd05ab7a7006ef6012f_1614318477068_0.6390423082161973","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.1.3":{"name":"@balena/jellyfish-core","version":"2.1.3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.1.3","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f0f21029228127e2b4ddb6677592a6a5a3e817b0","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.1.3.tgz","fileCount":98,"integrity":"sha512-b9cUBfE7g05JE4a15HQEEBfbudV75cnFOcbmcsU3BcewHFVHwHUbpeU/W5Pdg3dv7qjG+/F3GFSyJDvemp7U9g==","signatures":[{"sig":"MEUCIQD+Yfn1zuINO32QlCwWEx7fTvFHqGEYqUmivsntFgrVgwIgfktRtEVa4AJKDaO/cme2jXwv1I/7BYlpUUw24+9BMMI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":612066,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgOJERCRA9TVsSAnZWagAAG8sP/A1e1qUaT7TTAUZ0Em2J\nxQYSF1KEgjphMGT+s/NXCTaijD4g8KJINto1zL7Acst0PYlJlZo4mDHebhlK\nCmXpvEefjgaNA7F3zm4xiFYmBZ1BeLlCkPaTZ/4fbJdklVqDns1/RKOp4eXB\nwB2ENRZrZmxNvZk82GDBenSx6+xmj4uF/taZqt7sZvXyKFwBwEC5L9sCGU+J\nryYQ13lvsCO52KptOdShXz+vBoQrD8TpodqkDwoMsTMQrjXzIrd8PWt8nB5l\nNwzBMA6hy0iM3fhdrs+4LRi4aSKYW5QT7Q/NFauVQ1N9yRZfUelaGgwfWkr4\nrqkebGw+PhJPf5GAABQZC/Ce/G8U/7pro7CK9rG8F3aaymwdzywYtot8zrzF\nsjtGYNOEjoDJVJlS/xM0x8VIcqIeu+udhEKRG2Z/wdMTZKy3riKdhe2mhfae\n4JQ9LRMzykGhkqXU4Q4eqKrZTUjsKq6cvo7qXysOrTcs+VnkSxTlzHCEvyJ1\npj1p2YLXANL7NpFjbrAbuKbEs/IYLhPovAkYQ9I1j+rK+QffkO8GUgIN0uMM\noUbLQrQAgAqz+Z/rmMr3IyOs95K8R1ttwWlBlHhupdbvrwARUTIehCd31VH7\nOtyu81HsnLgG0ldeZ00HJBE2mJE6c/f7+9gyWDNd5asjmullf7Lim1W0oFeP\n055d\r\n=hJUP\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2f9e47802f4bb0468d3f1d43ccf9c6cdd58a6d0b","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.64","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.59","@balena/jellyfish-logger":"^1.0.33","@balena/jellyfish-metrics":"^0.1.99","@balena/jellyfish-environment":"^2.4.19"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.1","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.107","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.1.3_1614319888403_0.33020180125782983","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.2.0-add-authentication-token-58edd7fdfd604936be81270a67ebf30d181bf6f8":{"name":"@balena/jellyfish-core","version":"2.2.0-add-authentication-token-58edd7fdfd604936be81270a67ebf30d181bf6f8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.2.0-add-authentication-token-58edd7fdfd604936be81270a67ebf30d181bf6f8","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"7551c0172c0e8406bf6620e8bc2c43f6f2c26f65","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.2.0-add-authentication-token-58edd7fdfd604936be81270a67ebf30d181bf6f8.tgz","fileCount":98,"integrity":"sha512-BXggAO9vPwCAZRM0oy5sS+NdPNwWzDpMII8C6h4LfQTFwzp+3SOmMY4YJxj+2Y75ZM+yuh6Zi+LMY/4/Y512Zw==","signatures":[{"sig":"MEQCIDPhWwPfYB52DIw7onV/h0mnKOmRczZMPthbi9NJhRCMAiAw4aMO92HZP9dYm97LSZ6pJL/mjjUsJvMhfRLkTV0bNQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":612762,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgOJktCRA9TVsSAnZWagAAm3MQAJwJ/cgNBlfRGoz+GQ5T\nfgKIIjjlSCcsNRVngor7M6D829hrp1g4cmaYZnzlPUiIVkuEqlFIpsiGwS39\nrZ7OIOwdiQFG90Yj41gdICcbJ9x3miUe8xhBnIV8CtIhJn4XKmk3ABCItK3+\nGhAa2FjXCvZqM/XHF7i659VS2zA02pMwcprplF9ZXaHDnCDbKbUe+nh4xfsm\nMhdvI9UY0aDENcw3ilZpegBAHRVuwUAZ74YEFH2ak7TThMF4HKtoQg7FAz0V\nwC96yhFNpJj+gpj3r1Ey7U+AOW6UGICNqTj7QYVMorZ9Ae/TOSKCf8uTEFmm\nxfiQGsMA24c1lkRTDmQUXCd1jWBVfXNVXGZTWeL14f1O33l665OdoSskgWvK\nZhged6abJtzvodRDlpiMXryLFfmByizjGvvGRLOMBebKcNOw5YvUwq3yr17G\nNiYxHvXrj/LUJf3HfjNBkwEZGRKrMp1suC0jPLpj9b6SRAumoffm8FD3y60e\nLWK224QU6EVtc2upssEqod3QK2FG2zch4fKj9VtlReR5cbVJ2VCKaJhk9FAB\nxeHZQhO5DIyknWSsA3a2yKEyJSAaUuIchtdzbavDQAZoor3GXDtEoDtrFrpf\n0Crkgzh1PJ3vFMm1V/4PkgG1ZBNftI4VscDHOztBiGvHunP3+u1vexzXxtTa\nh4KO\r\n=aTYl\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"58edd7fdfd604936be81270a67ebf30d181bf6f8","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.64","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.59","@balena/jellyfish-logger":"^1.0.33","@balena/jellyfish-metrics":"^0.1.99","@balena/jellyfish-environment":"^2.4.19"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.1","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.107","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.2.0-add-authentication-token-58edd7fdfd604936be81270a67ebf30d181bf6f8_1614321964738_0.3880425518102779","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.2.0-add-session-scope-5a39ce1bcc9d174e0cf10d5a9c7b7f4cc5b176b1":{"name":"@balena/jellyfish-core","version":"2.2.0-add-session-scope-5a39ce1bcc9d174e0cf10d5a9c7b7f4cc5b176b1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.2.0-add-session-scope-5a39ce1bcc9d174e0cf10d5a9c7b7f4cc5b176b1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"27cc58b6f826ffa8d0ea88d72330e4a5c13dd3e1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.2.0-add-session-scope-5a39ce1bcc9d174e0cf10d5a9c7b7f4cc5b176b1.tgz","fileCount":98,"integrity":"sha512-FNIPF5Aknj02l30MK8VpECMuW47zVXD3R5KI50LV7lt0D2aUnNK3a4Jph2M9Fc/BTHlBhsLBeViNQPqIjgR9eQ==","signatures":[{"sig":"MEQCIFoCgCPaJ4J1Oyv3FkrXT3WdIFEjWTe43zX7s7aScAa1AiAUZPXeGjML0z6Rt9BA9isxi161UkulMHEpIPHk+/9gEQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":612691,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgOJt/CRA9TVsSAnZWagAAwZ8P/2aV+r4JFj6bbjWhxrNz\n4tTo6bNODUeLiURQrgU6VbFauQJr7PxXToBJFQJ2pyrX4hGrjeItj8rlPD8f\nli37zyCHXT0RUBntlbuCXzEo3yr+SU4rBv3h0oSDcHXIIVC0TdOFNpQ9qxmQ\nBYpSndkIrWSEQ1N3gADKgikxGzNxeXF7baGJO7Vq7dsF32Tmh8WzwtJStDZZ\nVpNaK+GReHE+r7B2qu+VEE/baWVuDC7OYk/EM6ZGxBIc6APXCIpkeGSGNJEM\nQ6YPmP+5Pq9Mjo0GtnUmDiz2S4UzOTsPGQt0xLyoy+cx+X7g3SdPVQdWGB1N\n3ipWGVtwOgcSRAejI/2MCHxoMx+AkC5ryQvilVbv1Ohmohv96IXDpypLODck\nAaV002EwOuS2Nj2GFeEf+NlYaUftgV7bEloadPgsl5lX0A3xsxpkxYKq0NXI\nUmh4Uk2JEl0GXwpgK2shtjB9V0n/efF9jNI+INe0+qR/K7Ce71Q5blYJbDhC\nCu0eZWlQMX+Grt47OZ7Zt0DyCvc9ZNqQwyXTGqXp0wDZzlgQPGNcY1nMo9Pp\npFFEOhqtCe4OoCmQeUEUXO6mxy5q7zKN6KebGb9ssuq2IRqTnkVdT2UTbgWR\ng31hohMv9Hb/MBEX04y9dUnXegqhcZw0EDj1pjQRKfudBvuSgLsXEdxsZZZM\ntfTH\r\n=ccPx\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"5a39ce1bcc9d174e0cf10d5a9c7b7f4cc5b176b1","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.64","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.59","@balena/jellyfish-logger":"^1.0.33","@balena/jellyfish-metrics":"^0.1.99","@balena/jellyfish-environment":"^2.4.19"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.1","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.107","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.2.0-add-session-scope-5a39ce1bcc9d174e0cf10d5a9c7b7f4cc5b176b1_1614322559092_0.4629838280131946","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.2.0-add-authentication-token-05415ae14b3e502a05af9063a6c1728c2df0782c":{"name":"@balena/jellyfish-core","version":"2.2.0-add-authentication-token-05415ae14b3e502a05af9063a6c1728c2df0782c","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.2.0-add-authentication-token-05415ae14b3e502a05af9063a6c1728c2df0782c","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5dfb8deb3416575170037a2f78126e03ee58046a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.2.0-add-authentication-token-05415ae14b3e502a05af9063a6c1728c2df0782c.tgz","fileCount":98,"integrity":"sha512-YfAhmzsV4Y7Rb3DBueqwtkuT49yry16e/MRzuBePXl21u53yp4Uq86uFKQ2HQ9OVls/1tZugcaq2d5p42OpDdQ==","signatures":[{"sig":"MEUCIQDcUdCxAEYKB2oln/S/33Zws+1//S4fOQZPbjCCsGLs1AIgSh+X3rGbwkCXS2t5138p4Agvf6BKd/CAfwFw3aGt3s8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":612831,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgOJvzCRA9TVsSAnZWagAAzrYQAJv0/Ssx7md1SV+sKsQd\nYdcxQ5y5tpg8vxEsks9ViLcZKbynYfJTmDuhW3+5X5ZO36N4G9+a9wezUJDG\nckrmH0npCLoC4UTpCUQVw7TkyhtAWocuk/A9z18eTz7uuY9heCa2dpSTVupf\nzK+t2VU2VEIra9GbeieSlopE90958Fjxk3eHxSTHEuoyai7JsHAHp5OoLUxQ\nQJAUD90w73OH3Nt7ILMby+nuc5dY1wbGuzD6JF1AMypKMjHru/sjysaQvVgi\nyAqzEGsV+GpaBoZMu/smyggVnl8rrn5pMNUiKM67N7ysigw1ySGov62s+kEf\n863mdPfUObioF9nsXUxrgyFAOj1qslianL+ID+924ydMcvmNKROFBQqsoeE6\nvGrVnezkn48xTeICtV4AjwgQ9zhdx+Y0DSe8AmUvWlOQVbsKR/lF4iK8FGIM\nDTIJEz3w7/cImp3DzEgPa2bhWLbvNYKUuQxjskDxFGd22egp0N9GdWkdpXQ8\n6vj5WdYjnD5DyFI2cLPbEDYcOjOTet9HCjfSQ7iHjVy977HagQ8N2KHhhs68\nbkvTcV/57bOOzKjFC+H3mEpwt7G2k8RhYCpWWAGSAd2KJL4YBsdplGtwVNqj\nqwi6XNaDZYdDxR4xRDorDqB22eWPy+G8yLCb04i7Dp0A4o/phuHa/4N+zcep\n3XV/\r\n=P8ul\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"05415ae14b3e502a05af9063a6c1728c2df0782c","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.64","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.59","@balena/jellyfish-logger":"^1.0.33","@balena/jellyfish-metrics":"^0.1.99","@balena/jellyfish-environment":"^2.4.19"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.1","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.107","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.2.0-add-authentication-token-05415ae14b3e502a05af9063a6c1728c2df0782c_1614322675536_0.5631607950127584","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.2.0-add-authentication-token-1b858902261497524135b1d442e0560cc321edf0":{"name":"@balena/jellyfish-core","version":"2.2.0-add-authentication-token-1b858902261497524135b1d442e0560cc321edf0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.2.0-add-authentication-token-1b858902261497524135b1d442e0560cc321edf0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"40c60d59c25eef4d88613af194ec72202c20e2cb","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.2.0-add-authentication-token-1b858902261497524135b1d442e0560cc321edf0.tgz","fileCount":98,"integrity":"sha512-L0aBiw+Yy91aB6vvGnbdT3g8dj8kAL55nn943/Hu+gnoaP40mg+47QFX/Az/ubeT5CLoVEmKyKrgN7AYFjpFgQ==","signatures":[{"sig":"MEUCICM8uvtRFD+WG95xkhjrtO7vaR3hr4Jr+AkpdPZvS552AiEA4BfM+Y2jzx5TzTgKfwJ1eIIuU9aZsUBU9zRIq6tPBfM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":612816,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgOJ4nCRA9TVsSAnZWagAAZ7oP/RRXH0OOS0RIPcLJOfOI\nJreVOcMLAaYyD7z44fItnzT0JVOZZbgXN6dEbHiHhE2W5E9E+oBQGgP8O4lQ\nHnl9T/ST/mBCi6r0UjsB+3wyiDKzEmW4CEDaQ02LdcRS+9YdkwbuXLJTAhO+\n3Ndga+Jk21P+OVGTJ9WPAC4jsPQdFRZuYwT+UquwW9eDB4/Vv8Vawd9ojMje\nzrmCWx8DpmuaTpWwN3vZczL+KTS2TOZt+GrZJg9/EBCUDjajMxPm4mBTGsNO\nenQvodeCL4nyA6LN9shI6ZzGLIwry4gdV22XrOPiD+QvEXNOJalCW3uWjqeh\n0+GeZzYvCQWcxqBVxR4leJDWE61A3F2RCbMrOrbheXEtYAW8DqeYSwlR+sTA\n2L5KdqY5yrACLh1Dvaw7NM5aUwIZJAfx0xwhFJzxfpzAufGlw3Npxe74BSiM\nnqyLd6th4BPsSLq7C8l7eNByyMBbiHalWb5zkm/aszMsmu+Q9MR22mqiaUTO\nVq4SpWaj7krEGj37klDsaf97tzNKjxcIWQoOIj1IGniW34CO4DHUlHszUqpW\nH22+9HPJi3Kpzz1+V71EJHDQHu7eW6p40+mpfdDnQRBxp4nsFwzi/EdAHQDZ\nyZP0DVi0f5SbBe+GHbsOtjthqmblHXeRjmAL5p2f1HSAAUtAjeV5Fqq7r3dU\n4SSX\r\n=Y9uN\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"1b858902261497524135b1d442e0560cc321edf0","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.64","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.59","@balena/jellyfish-logger":"^1.0.33","@balena/jellyfish-metrics":"^0.1.99","@balena/jellyfish-environment":"^2.4.19"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.1","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.107","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.2.0-add-authentication-token-1b858902261497524135b1d442e0560cc321edf0_1614323238996_0.22541805607106613","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.1.4-renovate-docker-non-major-f5e0a3ee3009f6b5185ae81e584961d37cbc4d1d":{"name":"@balena/jellyfish-core","version":"2.1.4-renovate-docker-non-major-f5e0a3ee3009f6b5185ae81e584961d37cbc4d1d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.1.4-renovate-docker-non-major-f5e0a3ee3009f6b5185ae81e584961d37cbc4d1d","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d2733cc21c3ad954c505272de93134271dba8db3","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.1.4-renovate-docker-non-major-f5e0a3ee3009f6b5185ae81e584961d37cbc4d1d.tgz","fileCount":98,"integrity":"sha512-tchXQYGCgcZACXpKgHY0A02NFC/UCx9uOR8bQihtIwqL30cCG3y4WNy7RIgZgJN+VVihJAQ3N4UaYYd0Y4DLgA==","signatures":[{"sig":"MEYCIQCRGjWuONsgpaXoiCzDybGsYfsmZrTxdr9NMvXD8sHk8gIhAPWipGWtdhgZQ/fl+VPz7rZONHNkZ2iyeHNUU1sYIBe3","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":612550,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgOOl5CRA9TVsSAnZWagAA78YP/2aJ4KSHAzFQEX4kDBYz\nufp0xqFHC42NH6nUSM5Vbri5ZTjib7aSWJ7OMwigvHUrBsis8c8zBKkDDIp8\nPFPf3WuQCAOUev3zsNwNZ6tMzFzl8kbcXaIOZkI8TtWsFDPlR/LL/D7l+jvA\nodQfDVAQFcTPpoqyk+nkRfG/0CkJeicJQw08hv++spIyQFus7r4E4BUpUvbG\nuIXJDp7J6GBWyDno8PuC2y/GDQ2zOHX9rUdFulkpnQdaxIh847m8i3l000nZ\ncTCHBxBfjch2vfvxOvV3SaAOOiLy6cexS+448sgGTtlJa3Pl6iHIOGjfDcQc\nDWU8LqpfrxQY6SlXvpgKGY+kt85Omwv8zXjC7Chtps3ubn2z5DrDKR2Y+WZo\nF1RivPKnhh6raCjxApeZfas+sxG7W5w34fSJ+OGphJQtG0LxOsmG7WF0CLVB\ng4zYYePh+y1u5NIQ6cGEPDtGE69mY2iY3k0usthY7PWk/e9NUEnubCPu/zVw\n4bMojBlqAhQ/lTNBi2Btr+90JuepstAd03bfKFksAt7gqu8jfVM/2A66neuV\nHQbNYZ8qtfZgFQurEL4FByph+rg8TsVlakSMhwQB05v3f2nAVFNQMzsDx0Qf\nNmSAeM2ux5mt06nJuwLblRN1WOSOpm5vqkG4svZ9VrYhQPDt9z6YSl4kxvzU\ngIk9\r\n=/DlV\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f5e0a3ee3009f6b5185ae81e584961d37cbc4d1d","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.64","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.59","@balena/jellyfish-logger":"^1.0.33","@balena/jellyfish-metrics":"^0.1.99","@balena/jellyfish-environment":"^2.4.19"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.1","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.107","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.1.4-renovate-docker-non-major-f5e0a3ee3009f6b5185ae81e584961d37cbc4d1d_1614342521339_0.9076442691161639","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.1.4":{"name":"@balena/jellyfish-core","version":"2.1.4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.1.4","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"bfed86e516db2e4199976741341e2e84c8545eb4","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.1.4.tgz","fileCount":98,"integrity":"sha512-tA5LHm8D5gtpuw3uDUdnaMdMwBmGSQpcD+/702mfuUR85RHKzaPMy2G7VnUZz1mw68vcxvhPryL4if+dNIQb8g==","signatures":[{"sig":"MEQCIEnmzbHoBw2miBjPyQlPteUFPaAygK8WbZyx/qkWd0pbAiA7AqU99hxq0JV/dH6iElwbjhq+15ko4xXfgPkDrZRufA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":612483,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgOOqlCRA9TVsSAnZWagAAlacP/31T9fI4tTJm9e7CPtzH\nSb4Y1yJeO0Z0Pu2l/kZcztya3DVXDgAR8C/oyiEdOZ+0Jh0AzXk1JrTL1gQw\nwZtPbsm8J0MSc5hLl+h1QXHe9BXoQtmqQ4nOoQzHPTXwckY5c+X37cNVBFno\naXp9zKbNIffzt6adk9+RhuNiabxNJiX0ZhxbSXMoAlDyOT/SIXdEuduLuwhu\nR5W8k0nV7mle4IlJ5fxZzIY/dMAH75BfwGgny95MlCrCyY+bBP5CovwgAOWq\nV3+IDaP/02/jhsjCAcgXVj8Wm2HD1SzmpN5THq2VECvJ3uasM8CnYym4TNPU\nSP/+OJ6nLEP8k2Jj01YyKleedN34s9OT0tmkp2P0Ly2C0zVmpZ+QhjQjFsOE\nMxwwk6zg6wURlljVIlr0/aElNeMFGlshzsQKJ7w5Je4xbe61VAWqMMbASn8s\nzFa2BP6fPoQwvyAYKhaAiCscTInOa52eeT0g059d84EXPfwcRa1otVXGkHub\nWqTuDXbCy5y7fR2kaY93Us+XytzyqCOYibF33wS9w58etDFFqkhOkgGNfl3w\n3PVYBMH7Wt5Dwjq9hkIvvJfPtUoIPwxItndhD6wnLaJC2zlo7E6eYMCjbJfP\nXnUe14KFL9qd1udC8eYBmwR5zoKN2tDA6fhWwhafsD5rQt8D0ode270ADppr\nPSL/\r\n=yTyO\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"96f220a2dec66fd3965c5daa4db80357af8c640d","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.64","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.59","@balena/jellyfish-logger":"^1.0.33","@balena/jellyfish-metrics":"^0.1.99","@balena/jellyfish-environment":"^2.4.19"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.1","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.107","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.1.4_1614342820616_0.6688245144090685","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.1.5-renovate-non-major-e6ea857ea00bb83cf1549883bc3d79fa7028ce62":{"name":"@balena/jellyfish-core","version":"2.1.5-renovate-non-major-e6ea857ea00bb83cf1549883bc3d79fa7028ce62","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.1.5-renovate-non-major-e6ea857ea00bb83cf1549883bc3d79fa7028ce62","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e506e18e7c8d752d060ff7aefbc552099e298c5c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.1.5-renovate-non-major-e6ea857ea00bb83cf1549883bc3d79fa7028ce62.tgz","fileCount":98,"integrity":"sha512-VFKy+iKPwW4ubEyx+o/Pz2Rz0D7/BB+F00T6oRf8BjzRCYL8oVYGrUif8dZLR536V9uBPROmuTqItvePwvV/WQ==","signatures":[{"sig":"MEUCIQDlnRkqOFP7NOG0rtTz/f8gbfh++c9ig6j2YEB6AykwWQIgRrcc9Z7VfQXFH1XKcVXSSnnQhKLrydSLSxToesIODKk=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":612890,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgOOv9CRA9TVsSAnZWagAA5IIQAIW9fcKyggRn+hTuvX+q\nRgEfnfZLrjqWhutr/6C/gvtheujfEASAmyEOubkKUC8YZCjKemXb/TEwSAwA\nos4VMmJFtGYRzsv/YUZu1qib3mqogK9u/XWUqD9uvb17PzJNVqhwuDVwdUcz\npv5F5tShB9zV0Anv6C+3YY9S9+QKlAXo23c3T2nx2VeePyT44He5kvZBU+ea\n8eiCD65pvE1QNK+zd06wkv4JVVaTqxRwdw9IkN+dLTEmlK8z1HDQHt8P/zwR\nawo47KhFD+OHZXlNxsRkqngUaFXa431p2YyR4ZkC6F1NAQsCmB+U1CfcGgbH\nEQ3AMHwHbn3aBT8Jhpy5rKHjazdMEjVK4Q9YsJL39YZ3LygxkUirIep3W1HO\nOPqfy7rduKANsb8UbQOjJRWcddoMvdeQ4cAM8RwjcBYSB5Q2puE2f75DY1Yb\naAIvimci860Xhm6stznSaWCy5Aol/3PkOArHxxYQDPmh5UMuM2dh+4UBIGO9\nHkdFyNqrt/DepKz+nBo2U84hpDHIO0LcDaTbmDwOe8D0TJKgxbpwl9QwvGLK\nuhYRE3k3MljJwww+c2J8DS5yX0KIfu9Y68qIl4blcw3cu0D6txqmUTkca2e8\nHW7C43U+JUuKfwgd6zRLAbuG0kZRLw7bQmqW4XVjdZyun8uqxwedOrvnwkHl\nuPB5\r\n=pCxf\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e6ea857ea00bb83cf1549883bc3d79fa7028ce62","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.65","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.60","@balena/jellyfish-logger":"^1.0.34","@balena/jellyfish-metrics":"^0.1.99","@balena/jellyfish-environment":"^2.4.19"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.1","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.107","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.1.5-renovate-non-major-e6ea857ea00bb83cf1549883bc3d79fa7028ce62_1614343165212_0.7905269404363859","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.1.5-renovate-non-major-aab7f35b0721576ef244008309e89a09959d0212":{"name":"@balena/jellyfish-core","version":"2.1.5-renovate-non-major-aab7f35b0721576ef244008309e89a09959d0212","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.1.5-renovate-non-major-aab7f35b0721576ef244008309e89a09959d0212","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"140b475361c15290978f7fcc7a6c6424c3be5303","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.1.5-renovate-non-major-aab7f35b0721576ef244008309e89a09959d0212.tgz","fileCount":98,"integrity":"sha512-9UKQYIOzGOadLoxuo9zTWoirANMHxD60BaBHtcegrmWd0sXIJN2ng47POIGNJIgTUo8A0UK5iTJfRPOWbpphrA==","signatures":[{"sig":"MEUCIDy41PuDP14G77c00CclXNowOQJXn5ke3p3yKXskeb7xAiEAuMhvxAFjLWPRmLqQtqulnQ/DQHBGYiBLvwfmKECSUHs=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":612891,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgOP0NCRA9TVsSAnZWagAAm54P+wYOlmvE7CzihPfNoQ6K\nv2lnXZ7H8V6Tau/CnBQcRha9Kz6O7vPYv4t+zJ//G6pqo77jqHIqaYJqi2a8\nwQDb3KFhcpEWldpOsG/F3vJOejFdrVIgLs774ZuGns3EDHW6T/Fc9Vl/SLTl\n6FWfkhoRPiriqBNyWzxs3v56PccDYXwDib8yokHyxYtzIAS2FWptggLLso68\nb9s1dc6UFctqXNoHRAWoUq1DzGn793YZ9UjUHX2E4xlfTefqU8Y8pLivQGXp\n0qf7j7SKb5T1oqHocvB0OjvE+X53isQcFdO0S+fQMY2+/UqEfjZf5Y2CBeAD\nhVwxe3scIEatO4H4w3qp2xcMdZodP860tjYyLT5wLiYKwa3bvFCBo/wH7EpT\nj9uTB8oNfO3BAoFiccnOCAJeORQYLMPonTEutXhiLUtvfKy7bdiZEnTS0RPI\nVluSFRUXS9n31/0vSYds1lw7Jy+Mj2ajv5Y4WVNeoB0eGPQ1xu0D6/aicZiT\nWT7dVhTaYhpW3yyb12EAt6w0JG6GXvB2EFjWfTA+hnUENR3sm4xYO2NKTbds\nZ+3v/g9GpxXnNljsEzDXMuHjjHJIuFs/Uk7RSP3XecPcXwxYS1SBdEVllDlh\nPKq3nv8B/ybnZemCiwYOLfbDrSrL/UXa2skNgXvL0GlCWh2ADkgloixgeWSn\nUoSb\r\n=otYo\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"aab7f35b0721576ef244008309e89a09959d0212","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.65","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.60","@balena/jellyfish-logger":"^1.0.34","@balena/jellyfish-metrics":"^0.1.100","@balena/jellyfish-environment":"^2.4.19"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.1","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.107","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.1.5-renovate-non-major-aab7f35b0721576ef244008309e89a09959d0212_1614347532819_0.7587435212189733","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.1.5":{"name":"@balena/jellyfish-core","version":"2.1.5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.1.5","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ddb2333a78cf133cc3c4170455aa1361cd9aaedf","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.1.5.tgz","fileCount":98,"integrity":"sha512-HSVhiguMHdSHmCj0KI7rrPuOAew5Gwv+WR3XjSe9FDUT1D1G4b8JW9RNIxYxy1QreekPTiQ37BpWrbHZnt/wCg==","signatures":[{"sig":"MEQCIEadMSmcccATXFR1gs6fxANGLxp4nBORuYrxaeUWtMrMAiBhGzvyPTyrS3a7r7oSn8RsHiESUYw5IdhpnGFUHJZ/eQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":612831,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgOP93CRA9TVsSAnZWagAAvGgQAIqfADo+QPPGWEWNw5yN\nVrRTXosCRzkxBS6Faxx86j5rmMMxIBALzSfsuHZ+ZXm0JmPTX6DjAZG2/gVb\n53h6i0AAe8ELvsQFWq3w6t2Gq2QSm4+huOfhoms1o+PneCVegsv2m4aanJnH\nm3yVoVjbQsJ3z0+QVVGTIARsvCU2o0e+wsSEOQtGI/bUqRaVDGq9Fo/VWAH8\nUQgBkvjSGVlYQgyZaXVJbf0ZNOH+CpwZk01hIUoOK3ICu/x1eVygNvHo9jbM\nXNLfi2/yTpgr2eFV/85gsuJb7Si7isjCGB11weh5DeaExEGc0DL+dbESdoAm\nGp4s23xzHOWRvTjGwRwG657OWZrZuLblHqdkLEd9Y5NujEv2ASevnHxfDDMM\n+4tio87lxtSbTZGtM3hpzZ2K8VBvXj0LbgBq5NxvFnBbRJ8tHcTCMXRo1vOB\nTFHVPeoZvo71FFGXBhY8EM3bRx2xGHwMRzHNiBDYwWCutHBZlYPTJr34U4Jd\no7x1r45ZphoPppOdlkIlWuDM8cGDA8ApTrttmnDCTz5seNL+cFOJYzE1FsaE\nzrJ+u7Vd/AIGltaQ+63avZTG8YIlH8NcF1aMvAHRaAkknHD9Lm361khYKKPv\ny7RFUOvQX1+wQ+CNl+x3Is4moUBJzcPybwTf0f33Z+8dc/II7eQE4g9eOLf/\ncLSB\r\n=egy+\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"3ca29f570b8534e640a91428dfac0538ef5fcd95","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.65","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.60","@balena/jellyfish-logger":"^1.0.34","@balena/jellyfish-metrics":"^0.1.100","@balena/jellyfish-environment":"^2.4.19"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.1","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.107","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.1.5_1614348150544_0.11146526897776843","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.1.6-renovate-jsdoc-to-markdown-7-x-5a123ecc78c3ab1c5c2e2e978b21222a6a42af39":{"name":"@balena/jellyfish-core","version":"2.1.6-renovate-jsdoc-to-markdown-7-x-5a123ecc78c3ab1c5c2e2e978b21222a6a42af39","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.1.6-renovate-jsdoc-to-markdown-7-x-5a123ecc78c3ab1c5c2e2e978b21222a6a42af39","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"30926d951704b4014202ec993ddb2f0f03049193","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.1.6-renovate-jsdoc-to-markdown-7-x-5a123ecc78c3ab1c5c2e2e978b21222a6a42af39.tgz","fileCount":98,"integrity":"sha512-FQQvpPrmmG9PEARSDFr2NKsGUmbVmaF8VElgVPuAIapXfIq3Nvj/a4nyf6Lqp88bj6Owv809doCMutBOZS8AYg==","signatures":[{"sig":"MEQCIHGwkYQbJt1VZiSdKVqxT/zL9MfK2hgroxumnBZQpU4iAiAY7pPgm4QzlV1Us5zcpdfY0ycNR0C1ZE8616ic6UuhOQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":613300,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgOYFpCRA9TVsSAnZWagAAW0AP/3tC60ER0Ozmsp7LaftX\nkDlsJ6/0X2Up8nOLf3YqzfRqcMVPX/fIZA1+iXKMK3BvXZNZe/wwd51O7WY7\n7QqtrL4xf1Q6BPRFLYl1VVJhiL2Zlozkx4T4UDEw7k97+Q/fZPh3lCmu24Nj\n5vm32ChEDJw2JQ6sthWUPIZ+ySgfaLupkgClOSTB8V9Yqqju3aS/tORXkTIV\nzb5puCJqTy1bP5JhYmckAloihDtm9aLNS0oqHyzj5v2X6Z64qURCOgdrIxbg\nPTG31dc+FmOe9uF0Ob8dlfESOtSk46H7ncasSb5Yz0sBMW/vbjrx0eWvBkuG\njk+T89NUdrxsd7GVzX4FRjKQMo5UuJvXNadOzFtLi56UbHINx9viJIxJ+soG\nieUrSh8TzWqAtJqSfUGaEnjbZJ6+YCwraXq3VajK6ju/6wZKc2wsiXkdBfRY\nvb6KOaDDwlQOlL9WTrmawBHLaT0r0v/CMTKxlJ9nWDrtLyniVlqyigPsIuRf\nNj7b/qTfngknN01a4rqKHEjOZuGRaXWA+kIz9rZKpin2J0Q1DMwM7pyDyaYY\n9L54tGMEHpdiPDxzGIvGjCQwsLP8c/5XVGDdraz59/sXnUibOvrEX8cNfzd2\n/aRo2vlGYGgB2yzyiUnfe4EZ6fTJ3zwpqZFik+zQm+lBbojBoKEchFFZkgK7\nYLQv\r\n=yu/7\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"5a123ecc78c3ab1c5c2e2e978b21222a6a42af39","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.65","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.60","@balena/jellyfish-logger":"^1.0.34","@balena/jellyfish-metrics":"^0.1.100","@balena/jellyfish-environment":"^2.4.19"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.1","eslint":"^7.20.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.107","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.1.6-renovate-jsdoc-to-markdown-7-x-5a123ecc78c3ab1c5c2e2e978b21222a6a42af39_1614381416972_0.30192898702512005","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.1.6-renovate-non-major-83b30455a74fc62e146dce5dd833e48961f4b5c7":{"name":"@balena/jellyfish-core","version":"2.1.6-renovate-non-major-83b30455a74fc62e146dce5dd833e48961f4b5c7","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.1.6-renovate-non-major-83b30455a74fc62e146dce5dd833e48961f4b5c7","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"fa9f3a339b07a4d2609fd23c4a9cce18aba35ae6","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.1.6-renovate-non-major-83b30455a74fc62e146dce5dd833e48961f4b5c7.tgz","fileCount":98,"integrity":"sha512-qN+lU3eDa1rRYsC29fMNbkSk8OqNSIoJ/SPZSdnhwHrPnWZcmHPvVeskkCA0euPVuA9tik/AuwKdHs43aqO65g==","signatures":[{"sig":"MEYCIQCJQ8Z0su8LHou99ycnSMbnJNHVkE8XOGKfU34i+TtFzQIhAM8xGgEA2qw+BXsuExxVOWgpYl8Qrbt/embRMaMpW9Js","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":613238,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgOtmMCRA9TVsSAnZWagAAaUwP/3iZ0FE/lhxgj7Y+UuSu\nijxLBrTg7ykIiHqtoWJ1b61n6vP8HtJzlC7aqw/NUVqw9HgEeofzYsg3UxYt\n7EUYVCjbvmHINLfFQhYh8So6CHiPx2t5CgIQAIG/Ai9tm6cv0tDIhAa4wt9e\n+0zfXch+8m2zH+56lm6u9lKlnT0z5o4Ad+yi9qP0DbuNyKWqYvmBy2zOMPME\n63serPswP4lWqr3zCMWFag02o2hb1ETUZRpTb0SDkTijhetnFC0Qh25VmmdZ\n115DYPh0Mz8qsGS3YUWKYJtTNfcxAS81qap+G09gRHmQ61jYCiDv/EzNrlvG\nq2spG5zF/vYy6+f6FqzjO1c1KqvploCT3yw41fKDq6ju3qSZuyfYJMbIImF1\nax+HmBVxnu6nwBrGvaP+TBW9kZKjeUGTW9/Z0OQ3CdkpB5C4DWngzkmqvOmp\nJD0AZn7npFl99YQI9zkA/Lh/T9/Rrl6cN8SFV0BwXyUSNHzPrZ2mOL5iltz+\n3gPDM/6BjGFj6UYwdV/LxwuA3lFTEJxE8ate/rOjV3KxxCskB2ZRdAYJgopn\nNXQUz/momWakz9noBHBmlh6I0COt71SwkmpASmwxBGl4PMN4fh7dvf1lfCBM\nrUOTQPg3Rre7C6WTc4Db1a+6x+f37FLw+Owu2njshDpmX0ckQu1ZlekSldfU\nLRoe\r\n=Uox6\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"83b30455a74fc62e146dce5dd833e48961f4b5c7","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.65","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.60","@balena/jellyfish-logger":"^1.0.36","@balena/jellyfish-metrics":"^0.1.101","@balena/jellyfish-environment":"^2.4.20"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.1","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.108","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.1.6-renovate-non-major-83b30455a74fc62e146dce5dd833e48961f4b5c7_1614469516122_0.13670723489062464","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.1.6":{"name":"@balena/jellyfish-core","version":"2.1.6","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.1.6","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"235641deff35cb51838a4c11837fb469f90bb9cd","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.1.6.tgz","fileCount":98,"integrity":"sha512-jQlCUWh1iPt7TAFc4dXxwZMYcSuyurzmKNW2Ry74fgIPpP95jvezsSvHkIO1kDwjQ26F873RLVNbz6/sVkV3ug==","signatures":[{"sig":"MEQCICITflWmiEQ8H7U9PwT6/JMp6+D472vxgsHmrQdnsJJbAiBoKXNZYJVCW5xA6wIgAPlr2q7QnLe8aEAJVW9cBHr1XA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":613178,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgOtsLCRA9TVsSAnZWagAAo0kQAJ8DVJuLc4dTg0RAQI+G\n0J8D0jlh2LI/20Duwn26n6fH2Y1HCGG1yd3Pm5YJU3NAhsM56sQrrvizsvRo\nO9caTEUIewoeAiY6xK+wMyhaCHvYs85NAdKYRuwpyBcFegm3aEyR3S25Yh4o\n7ZbOOOKBGsIP5TEVYlRO9Ip7PXZAznzcl/TDUaNjT1AKnt7tBarnDO3pVLtR\nJ/Wb21vMdDTPLclk2PXk/sBRnOwcb8GzvwmfTsVLIbOOcPok/suO571+fzi/\nWIK0We63Pui00t6Knzke5bcSnoMEUVvOI8gwVHceUeZXbHcMqbBorGwZW9R+\ng2sZHuQemQPhOJkVpll/eWZ0US3vRmdcNKUyS2/8PhYpVAUy+tnAXE3MUunj\n3+yjvRNYiC5PQNmCUutW7jHuwSztIQoWRlS08zkLqmwP0YoSooLsLLAY+/J4\nZlaCeHkaKXmmEZJRwFMhja84HrjJS9loiL7GP7RlpwUxv6aKb8Z4o25Bm0wl\nLJQAZF2zPEJbJIpVia/VH/yjiy04Qtlebv+HdJd2qgLuYX42zcbjkowCqBUb\n7L6+EzxVIiI1xUw9xvwCYpVECR9810M+QQYBn3Li5g9qV/y8UJh/gzkOxZWG\n+w/zyTV0JrHxWldO1ihxmKZPVqyc3WziM2+jWI2Ch3tzmqhaowxDQSAYTL5j\ne/P6\r\n=G7+l\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"fb61a2af56e9cac570c8113ba070cd9745a23178","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.65","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.60","@balena/jellyfish-logger":"^1.0.36","@balena/jellyfish-metrics":"^0.1.101","@balena/jellyfish-environment":"^2.4.20"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.1","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^6.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.108","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.1.6_1614469899106_0.9805483477106258","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.1.7-renovate-jsdoc-to-markdown-7-x-aeb530c429842d9826cf98af020ace1dfd549960":{"name":"@balena/jellyfish-core","version":"2.1.7-renovate-jsdoc-to-markdown-7-x-aeb530c429842d9826cf98af020ace1dfd549960","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.1.7-renovate-jsdoc-to-markdown-7-x-aeb530c429842d9826cf98af020ace1dfd549960","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"00b95b397918267973cfc762cf71bc1e27787420","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.1.7-renovate-jsdoc-to-markdown-7-x-aeb530c429842d9826cf98af020ace1dfd549960.tgz","fileCount":98,"integrity":"sha512-UNiVv/HLCHAwQ2Tcibo8St0wdxFm63XM8+Pn3LN/rkqiMNefl7l/XrFZvk+cRujUHVYcimITtZElaydQc1qrqA==","signatures":[{"sig":"MEQCIE/cLNFGrYR18078w9IIJMkLunO27h76EoMvKBUzha3XAiAh2pLRk4Ji/wRcww4b/Y+ZStoC/CCfxCXkiLM+TMwS6A==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":613647,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgOtvuCRA9TVsSAnZWagAAatAP/3O/VxdAqR6zGVpAsbXd\nDZkDhxauxAgRezQtOxyQyds1O1OlYw0b8J5ks7K9CTY0Tff43iE+EwfSKcsn\n0cWN0qBaRLr50HtqcB5BoIDxcg0BdwvQ7ZveTG0E371nye5Imk/PbIrI+v7N\nYbxRNGWssteWm+6Znrz/yibi/ERLQyIVMUkyFcDzZfmT4PMV2qfhD06CDdjt\nEsGWUjqk8jGgRm+yWOCM98/UpLj+2+JkIC/MTTiUV9QSX4FMPzPRi9x7CZmk\nKTRG3PnO/WIiX4eoqYo6XNEuOw382rKeKoG00fbJztnyNiHYr+4R7IkL8/f5\nAnJwVVbx5MmSak9a3NN53t2iegkefNO03mY/xS4srT8ycbpHEwfWLjdMEhnC\nK3O75GLDlJCmDftoE4hXHi0wi9t5eouev0DQKrtki2jb+9HjlelGcDig4qCu\naKWdPrX2LopO1XgOH98dx9kgNPs+h3babj6Dxnba9Ix+vZMX74O73aNDdzEH\n00zTJagEWYOQrHojWl9n0ZJGTQyVz+VpfOQHWkHVfhB6R9JFAQ9zqVkQICN6\nTGRU/SSDB2RRXgyFUIm9O6Fs0v5rm2gXqmVeHWgvOa9Q7gUd2qldCInAsK4D\nNDfnUHhMvyTwoQnBDJBu0GEixzM5UxEjCLz1er66qFFzzjGXeacRpu1UiXc6\nZ5vA\r\n=3jvZ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"aeb530c429842d9826cf98af020ace1dfd549960","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.65","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.60","@balena/jellyfish-logger":"^1.0.36","@balena/jellyfish-metrics":"^0.1.101","@balena/jellyfish-environment":"^2.4.20"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.1","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.108","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.1.7-renovate-jsdoc-to-markdown-7-x-aeb530c429842d9826cf98af020ace1dfd549960_1614470125744_0.3714238591191166","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.1.7":{"name":"@balena/jellyfish-core","version":"2.1.7","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.1.7","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"55383f3c2941545bac09c8e3b636434f495be947","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.1.7.tgz","fileCount":98,"integrity":"sha512-jLMM6wsHDzz2p7T3/a4RGpQn5Mvgqvm8xFk3IM/Su+BIF5HLGK6ZlrVYHtVBUZgoIVJph8pU579+ordquOkaqw==","signatures":[{"sig":"MEYCIQDAq1BrWexhhyhX5Og+hpYRxB3ck+1fwdNvIvUKPnrL6wIhAN8NqgsXGZzjMkmt/4m2SyccDIm5LV1E2sYYRRVRDUiC","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":613575,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgOuepCRA9TVsSAnZWagAAspsP/jOzdE73EU78kGvpCjvF\nI5V4OnqgWJZZici0xOFioWEQXAn7o0NGN3FAE6LEfC0qJTzoWIYQxYPtMgYL\njn02rRSjrRjRt1KrlyA5GYTJEqxNYAW8zjBfOFaSZPDRubHo+ApkELdMGFeS\n3IVP19UmRu/U7ad57lCAfDxfF1ZU5tqX1Hu3eUlv0BjH0M6Ujxtles+YE9a6\n2fVgGTZrhEftIC1rNre8fBZNR36+hoD2J9CFVi7rEB+087OIB4QrQ6YrDSXa\nvrASw1WT+mKKAR5uBiKKKj8h4yr6u8D7ReIy/dTiN94gDxclWXHXscE6OqEJ\nw2sAatiTSx1R+fC4q4t7vcWBBzYavXceiHkeAYP3l7ny5GqEim19ZCmIcm38\nK/A+Ks9ZPe0K4WUJpDBS1TzynKqgEzNImWwTfHTOlczG+GDaRrhepY1Zv4q7\nVTzEls+GO+G7kzKcinJxjCX8TNw19M4/Xpq11BaXFOUBXsoeyPVRs1t6nu92\nNvhWNjZGdCf5KKEBfIQ7+stUWdaNWaUyDNjGmj6OJgdbLAK7+GDWTS0ZFvcJ\nYOTtt/6zVOPg9doYhGKe+Kt0RJGqICeP0+kwK5hvJOmoSK5b76g5sweuawXF\nGEq9bZrWfcmQ0FPziPrcaF9p2cXNFQjvHnRnYXwnENIXZcQ2ZskQpwGPpA2k\ntlm1\r\n=4r+2\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b68da881a856504806754508b9e606abac5b0741","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.2","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.65","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.60","@balena/jellyfish-logger":"^1.0.36","@balena/jellyfish-metrics":"^0.1.101","@balena/jellyfish-environment":"^2.4.20"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.1","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.108","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.1.7_1614473129112_0.7045458167683829","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.1.8-renovate-non-major-7c4c67a1c3c11d99fe2be84002423f5c1f4c80ba":{"name":"@balena/jellyfish-core","version":"2.1.8-renovate-non-major-7c4c67a1c3c11d99fe2be84002423f5c1f4c80ba","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.1.8-renovate-non-major-7c4c67a1c3c11d99fe2be84002423f5c1f4c80ba","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0f43bf776be710174aeb3c013e1aabe02e8c2e6a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.1.8-renovate-non-major-7c4c67a1c3c11d99fe2be84002423f5c1f4c80ba.tgz","fileCount":98,"integrity":"sha512-vsNP+tHyu7kG6g4UB3sek6xCmXWHo3vHv2XLrhgnZ9dU+GVgWwuuMEHiQgpYVLr1iJlZxNbUuDq/SmGao1WZHA==","signatures":[{"sig":"MEUCIHWa9CRXZM2Fwn5FByAczgHSgrJAjuKl69pITFCrQOirAiEArrfH4K+ccUuOhw1gXdN9sGWcIat1AZr5zcnPWLgtif8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":613982,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgOwNOCRA9TVsSAnZWagAAAKQQAIWhe+M/iv4E0grLULG0\n1Rr5xpA2u87LaecaRVRLmar6a+90d0tzn/BwHni1MfQHL33rxVXbZz8kWQCo\n15wu79TMrvlCAWXoHz4SpWpb5zMCDK/nRWDZ1WgG260Mf6fTFLvbewjvyq8W\nKyaBMPHfWPha9ADww9l/NJKtyOjoXBZHGi6/qqTq1QBvn3Au8G49hLEwghT0\nRa7ECaB6vT9YZ8niD/7nhNfMJ2FJfMBCXrQJc/ZTkBb49h2LR3969iYWY5WE\n1aq1KUE4Q9t6sg2Xg1Zi7INGDv7os68qkLaIRfENOAKltrpX6MVJgev4+ih7\nJFFK3oyAfEj3yWsUHRt7LkuIbaFgJKwDL6PpsqCmSjZsQYXBOcy7bHAArlmp\nku2xNteeXRRBSwAh9lfSSXg+NHxVZVMc1VxRcLsx/mhlEFVaqjqpNfK2tX4k\n3Y+zZD8JE3QZMp8o1iPj4uARSLWS3BfM3ZjGhT2GHQ2mUBMPH7XgNaqZ/zab\nnrF1WdQwHlhgz3NO37vs+BrMu43Zk3fVdFqqBfrzQcH7XVX4AqkJ/1LgTCxW\nE2jEftgg9Q1CKclJ7OcOvZ9V0CqWlw3bFYkT8t6Vrv/Sl841NB4XvFlTcbAO\n9mzmu/0NHt5yAln6JKMZGXJ+s3pYkCmTJF2OZ+jqiDNdmyLf0GOfq4tRC7ai\n8wc/\r\n=weWn\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7c4c67a1c3c11d99fe2be84002423f5c1f4c80ba","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.3","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.65","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.60","@balena/jellyfish-logger":"^1.0.37","@balena/jellyfish-metrics":"^0.1.101","@balena/jellyfish-environment":"^2.4.21"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.1","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.108","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.1.8-renovate-non-major-7c4c67a1c3c11d99fe2be84002423f5c1f4c80ba_1614480205546_0.651843926506857","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.1.8-renovate-non-major-71868f9283c2adc063718d3caeb4e32c57052858":{"name":"@balena/jellyfish-core","version":"2.1.8-renovate-non-major-71868f9283c2adc063718d3caeb4e32c57052858","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.1.8-renovate-non-major-71868f9283c2adc063718d3caeb4e32c57052858","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"247633e798fea2b2e3f941a78a37bc46366bc14d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.1.8-renovate-non-major-71868f9283c2adc063718d3caeb4e32c57052858.tgz","fileCount":98,"integrity":"sha512-5KZf5XIMJVjJkuwgPiiLd6ZkeAbDUuk/KmWDok0KlavK9nxcEh7MV1XH7Xo+XYEeqNbjrqEyrM0y/ltwH5d1dQ==","signatures":[{"sig":"MEUCIC1fR/iE7UUbafEro8UfVRD8Dhon/Dx32CbBqHRRBkr1AiEApYI+spxyZXK6kKfOEPWfMr4WA0H4jHoTqjiTcHz9GBg=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":613982,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgOx44CRA9TVsSAnZWagAAnrEQAIzmt/BgwXPmxOcZTbOJ\njdc+9/hJbPi0hTSPZCgC/sRxhtMvbvSnzya8yPRtZriEUg85f/FPz8CR0Isn\nWPFCBzubH+THRz26ZOmY382rhh9Lfccnc5UPqMJMufazHuU8hIH3JJUFqgQP\nUl8pjXR1lSmwtWBNLB0UgflueP+WEgiD+1bX18/8bMI64lR1badX/MpIZWnW\nRgD3N0DDaLBwFuH54fRg3545Mw+vmPU/SLtnItrF/dIRpr05Jzp3YHh47tgq\ntQvKlQGZOPbjnJh7ZdU26ow8CSZp6xbC5GWtjJyA22fDxBMae31H4P8SRBJJ\nBjwEKvW7flsT/5+nmkPAod7ctGHG0lsOsGmxjEftetiptXZcVWQz5h4BnctQ\nbwtpiH1mk32OX/ERMty0K8SJeQutO987/Av32/SMc+n4Hyvd0Ub+KaqZDVjW\n1VGNASvBdjnGYnAbFXLjYrSE/TzNzHkOJEC0UVtzy/8mW9NiL6YNB9fQAYvM\nbWHKZyweRaP5aMuoKFo4IVu6mXTdWe5J8widAxNiY+5Owsc4mrp0kkUH88Nw\n/pnGx3qoLspyH3GPCsdV+RqdU5L6AzVXz0LWZrZoFvIAxpZNn6xeqI8iEwUC\nODzZU6t/4CbNaElPhcjD3Hmw3f2VmSgKtFhehmPlLJ/vU6MoR8WcsWNjCD7O\nfrO+\r\n=fSgm\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"71868f9283c2adc063718d3caeb4e32c57052858","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.65","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.60","@balena/jellyfish-logger":"^1.0.38","@balena/jellyfish-metrics":"^0.1.103","@balena/jellyfish-environment":"^2.4.21"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.1","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.108","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.1.8-renovate-non-major-71868f9283c2adc063718d3caeb4e32c57052858_1614487095939_0.4353875244188443","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.1.8-renovate-non-major-36477c225c83cf589c1d4adc4fc5855ff4608aae":{"name":"@balena/jellyfish-core","version":"2.1.8-renovate-non-major-36477c225c83cf589c1d4adc4fc5855ff4608aae","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.1.8-renovate-non-major-36477c225c83cf589c1d4adc4fc5855ff4608aae","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"34a78e4be6c68014ae024c5ab98111431e5dd685","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.1.8-renovate-non-major-36477c225c83cf589c1d4adc4fc5855ff4608aae.tgz","fileCount":98,"integrity":"sha512-KmOQuzU2jM98iwq19A2eO1Jd+h66SrAx02BFlJnY8QWfLnte9FnIIlWcnDpr4e+CFnFkzIbeEYSQGQUPqwUHGg==","signatures":[{"sig":"MEUCIQCClciaEhV/LYA7JGbdo0KXrgeqb5eJbCJF6jv8+BfsTQIgap2s2geUJl/k0enkc6dSMRr5YEF4DpdPMMljcLtI6lg=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":613982,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgO1HkCRA9TVsSAnZWagAAH9sP/Rol8XqTGlhKn59UYxUR\n2NUiQAIBzZt7qrjCFdJrcSu/+hOvlua5iETTpOO+WqeVs6jkN8DehU1pnUrP\nxlKSP7zcRJjCoHiGOhPG6TD/3S8khLA8Q9vErH9bzU++obwONnxBrH8MGIql\nE8AA/eY3NUphgJBW4a0nWtJAJsKVna5SPU6go39dVyTwTk5/OcUAwKWfd2Yz\nMNDEdoelLpniPr+DNMV4yMBsceLEu+M/7Spj91qgiwzNPlt0xGQ6L3Qy4tOr\nhSIjgcLqR6IPmLyAyL/ZvMmqgX/96OIz8NzH8k6biUCIJ16QVjCn569woFPf\njuBWUqOWc4nTMruH8Glbssvef+tFFXSsEK3nHkj+oUAsNCKL/+gvt53/np1H\nLE26Ddr81SEM0w/ou9Zsx8Kv0MMXgB/3KnpK8T3l0tNPsWvSOiy5SYFc38+7\naXYaTpjWDfrQ4R82sIfWmXuYNvCH3cS2EkOuNqGg5wYwxAcBJWnp75GDPZaB\nd8cSMT89yGF7slAUYYNEbo6olPcP60A30Go0mjz05hgBElZQRsGtfoGYDBqY\nKfqm7fIve2YwP70czTUZAMctJFF2zIw4foxC5S9jKHfBqJ25oDVwYAyxzcoO\nW/ECqZ2RK3h0oYw33s7y/ZEfStkX8WQOfCIhPdUbKoYCDTFGLib72Ozjqq1p\n65rs\r\n=w2WE\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"36477c225c83cf589c1d4adc4fc5855ff4608aae","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.65","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.60","@balena/jellyfish-logger":"^1.0.38","@balena/jellyfish-metrics":"^0.1.104","@balena/jellyfish-environment":"^2.4.21"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.1","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.109","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.1.8-renovate-non-major-36477c225c83cf589c1d4adc4fc5855ff4608aae_1614500324082_0.8806643067403357","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.1.8":{"name":"@balena/jellyfish-core","version":"2.1.8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.1.8","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"216287b1a56ab7da087168cfe8f0a9deb749630c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.1.8.tgz","fileCount":98,"integrity":"sha512-LDRv7RwjCuv+UXm2OL6/U4fbxWLQ9e5o0GPwcqQThG19dKfrajGFs2Rbi5iwS9k22T4n7lT6P8vQhVEoeuAE8Q==","signatures":[{"sig":"MEUCIEJyMC7cIjspAUP8LtdymwMW/zSd/GAcvmMB+nNoPtsUAiEAry5xODMRV208J/7UHx26l8l1kzOW/HIAPMj55ibmR6Y=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":613922,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgO1MCCRA9TVsSAnZWagAAWnYP/RlN+5Ap02Lx16EYdZtv\nalFF6ZW32plSSX6Ung+NGu4/ccG3LgWO7Fj9aLElSGxf2YkEpSZRuAzEsLz4\npKPXlB2glpL6tAHYyVvGnhAD5frCHlFy1rmRQ3cMWhzGb34mmVt968U/EgQf\n9gTNkyNSv2EqIqX8ta0Ua7SKuJpQR2oRv9OVP/od3cZybWGSCIK4cXRA/xgV\nDCQIHhjUYs8qaNO0o1QIeW65r9FUBZn7iiOFZMQ3qIdAU0HWnQRle5Ix1Hw3\n7NFXaOeRe+9CmcKgfdSWbgfuBLYu2FyqkSUNESnzqYTDEt99XKVnsyefYzez\nFuR6piOrIsI5dhE7ElBQ0WjRKWJwn0OBDKVbOO1Pti1DLsptjrKT3yuTmwEL\nOU1ZSksoOhErc/7SQnOu8HT+eRXVOzbIRR52GjFzYcewiGluJe/pPa7grVdk\ncC5KouzwSsv/wG8yrpMELznG8F1a9uCU9tnKAQsmMg9/F/lLrbKhexYNFeVf\nS1+dNeDnU3gjE3j71tHh4tjcRXbqOfdlXog1hNB7yKvkx2T5TTkfejG/WnTW\nQaeDgA+2A7D+UifWJNBXdnLMA1+DQnbIeys6NnVNtCwNzUN4jA0KQdp1MOsV\nDdFSdM0nU+SwOoZuwGW3EHBcoK6f1NxX8fL/USFuVbjdytyGt/8GokTfv1pq\ngb7d\r\n=UEoO\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6f5bd04d371bc073d1c6cf14bf31e27133cd0073","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.65","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.60","@balena/jellyfish-logger":"^1.0.38","@balena/jellyfish-metrics":"^0.1.104","@balena/jellyfish-environment":"^2.4.21"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.1","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.109","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.1.8_1614500609820_0.8917827804634233","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.1.9-renovate-non-major-51bcb6ca6ec4936c6352da074d1ee8372cb5d543":{"name":"@balena/jellyfish-core","version":"2.1.9-renovate-non-major-51bcb6ca6ec4936c6352da074d1ee8372cb5d543","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.1.9-renovate-non-major-51bcb6ca6ec4936c6352da074d1ee8372cb5d543","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2e3cfad20713d4343ecb171a11e203dc509424d0","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.1.9-renovate-non-major-51bcb6ca6ec4936c6352da074d1ee8372cb5d543.tgz","fileCount":98,"integrity":"sha512-M9q3/c2vUafQTvF83EMvVsJNr7f/uh2QpeyVI7qZu0F7WjKJp4dHo8md+WCKW8o9yfW5QyVe4NsoIWVlMLJOQQ==","signatures":[{"sig":"MEQCID8vxxmqjpQjho58qj3O2NuHtZBXgBBm9g7XwmJnerDLAiBHDLkVWImnO7o1huE9FMbrvB3zWqhbM0AaUSAWdq6Axg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":614363,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgPE+GCRA9TVsSAnZWagAAxisP/0r7v+Ga7pxAaqKJUnhe\nTo/ZVGFNTYdl0fwTYF6JSYqtuQ8MaKkLnzDCG/Q/LJeFKoStwNxxtgz8d60c\nhydTNS6lGBlVQO673ygBKc+yRCzJqsOJwFBQavrxX6n86gt0frkSPv1vWH+w\ngpQ5e59a1VvCXhwKNLs0GSRiZ3e8QbMBjn9Bmf/qi1xj+HfHX5G1xOeu16VZ\n3UM2Iyw91ZjDegGNqKu6h9OK4Qdr+Q2Iz/RzBtk+HbBzEYVNALevHZxd7dbG\nKEM2dQbQIGEc0ue+GI9hDKJE+JOpgUQALd8Bw2W/uBUT0nder0Sj48tE8UAl\ni6vPwgNvj4CtW79sLdj7IHNmCTewszXgWI3bvyIH5hKiEnDimIDBq0ZMDsoK\nD1Sp/5HPfmCZRvN1zOfBgb3/Aeew4Wdka1zxKyTop6hyIz7x4h0HWNsEjwAJ\nTTfG3MnPufa0fXSx4uHOHEu4gGhjtLRF1ZoD8NDo0dLP62FqHhpS3vVWqNFZ\nV+FA5e0tMD8bwnNbgf6Sw/cyeryB7LQzRuV49+ckm2uACsSdydD8SpyLmROk\neP//LqiF/vNl4SdC/SLj0mWY5vGs7JIEm5NT/m1l1awFZJGloDR3eEnwbPpI\nAzLrFTmyvNPTWuIT8q2/akMn4UnwhxxlXODjWVlN0bjuagsaNEqTvAynvmXJ\n5bMD\r\n=wTOP\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"51bcb6ca6ec4936c6352da074d1ee8372cb5d543","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.65","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.60","@balena/jellyfish-logger":"^1.0.38","@balena/jellyfish-metrics":"^0.1.104","@balena/jellyfish-environment":"^2.4.21"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.2","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.109","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.1.9-renovate-non-major-51bcb6ca6ec4936c6352da074d1ee8372cb5d543_1614565253627_0.5602786230332943","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.1.9-renovate-non-major-2351fd045b889df0f920062a6eb7c197b4d52233":{"name":"@balena/jellyfish-core","version":"2.1.9-renovate-non-major-2351fd045b889df0f920062a6eb7c197b4d52233","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.1.9-renovate-non-major-2351fd045b889df0f920062a6eb7c197b4d52233","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"60578072c32a11d74f3122a655ff7df69fe43a21","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.1.9-renovate-non-major-2351fd045b889df0f920062a6eb7c197b4d52233.tgz","fileCount":98,"integrity":"sha512-uD8Q1rQGN6df40KtDQh34l3wYL0Q1JcpUM/ge18D7i4hlt24Bp6pBgvVhvQUa5dhckAX6pHsPQk34OHJcwmWZA==","signatures":[{"sig":"MEUCIBXIvYtERrCcEbMbhC9XqaaO/CJjwAAL8tsHF9TrIDDMAiEA+wUnaLPCm8n/ij5Q/lfcTbUr6zd+u29CzgvBsrgCIGM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":614329,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgPFCvCRA9TVsSAnZWagAA5hsP/iH1CRpGbh7hVYLjiU0k\nL01xQ31opBmxprocgScu+7RDLEN3VkW/muntw/Wr1Dkb+Z72rLwF/EJM0eDa\nsVaxFvIiuvBHyerrPhZhK/vBIkPWL57ihU9tWNB4WeZE0CLFqVicSMiC6ENm\nYY3dTXl3NNW2dfacGTJmh/zV4keGLKGRr4Kb7a2hxPd9f4Ct4VS2n8qlYcYR\nThLP642jM98PqLZgK25zifKZgkgLeR1K6c1uzSYKvD0+At9hwxy4qiRS9Oh2\n20RK2QKY7hVONl8eJEfF1DDhatMvzinM55mKMk47yrGvFXif0TO5vOe4t034\narvZdsCdjOHbM+W8rGIvU/0lB1JbqnBSj0KXcSeaNvjHAkNyRYQqj166HBOJ\nBsGJUu7nDqfNBut6b1gjiZ2robuesd1Ztg9aKmOWRDngVN84CDr5JcwH2z6m\nECFoMoGRcJmqSP8ym4VuqkVElvh7iM0WDAqZ4uWcUqXMP2xOaUuIjLmvtVrr\nlnK+ngPqyCn593+wQyt/d/uLZ9IxZK9joEJav7xT5/HS2y0W4IKphEqpW2BN\n69Du5VTtggG4XrPFt6X7VvVGBM/Z/8X0lDH2PdFoSGstprcjGZSqkNX549e+\ndqIbs5ZM8N3fa2cF0MKRuKUWOHxLRAe6JOfVlilgp9ZFGJJP8WQrk+mPGvw1\nSCcu\r\n=Oa1c\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2351fd045b889df0f920062a6eb7c197b4d52233","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.66","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.60","@balena/jellyfish-logger":"^1.0.38","@balena/jellyfish-metrics":"^0.1.104","@balena/jellyfish-environment":"^2.4.21"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.2","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.109","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.1.9-renovate-non-major-2351fd045b889df0f920062a6eb7c197b4d52233_1614565550920_0.9385110466117754","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.1.9":{"name":"@balena/jellyfish-core","version":"2.1.9","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.1.9","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e7a1f01180dab567aaf78a4f6ed4ed77acb7ed12","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.1.9.tgz","fileCount":98,"integrity":"sha512-+wYhnl9+ypvcVvpuqi8LyWQktuARTqK8JAjhCjweV0f199r/7Ha58vCoGzMqfzWTwEYdCaTqP7sSvQct+TAMmg==","signatures":[{"sig":"MEQCIBfLhqBVu/xUYQ9upUKn+SL46vmDUvPQIs4RloljXonuAiA4tA0sEr7Ljco8rVP/2y21LM21tYtUjLpkYtrrr++EmA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":614269,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgPFIjCRA9TVsSAnZWagAA9oIP/0JICWMXP9OV7oi4QyB7\n6cf/nQfZHfdgcmOHMNCngZzuA/X/q4rQkE7/L9XxhY5AVaOeAy0KOxa9NmRH\nPNLXs0vl2UKoKn8lIwRE7sPF77U3GKvIhbyN0i1R5s9LdYeqlJ9WrlVzPaew\nCCcc4g+Ar3SrxUqSdo+XrFTOOi1zMERE0Bh4tQ/Fl24Cq3Sc0gq42Et1lfiP\nQLq29YHT0t35u/Bw83zG55Qp6iCIz/gT4jFllocxbKaDw9TkwkR1FGmbAGcC\nEjcthvhAEPD+DEj0OAFP0x1zvXxIbvuuSf7Rn/HrMr5BJS1sKCo1RnQ1S9Xp\nM6/yp4PxZoDjKVlYu4ZazmErzs0jTkqY3rJFbIR49Iz1zl9jL9TL5qkZASCg\nFwDp0rne//NVMcLm5spH/dKDlwoLefRfFH6ZQl0sUWDtzWw55tg1y5DHw+1S\nGbLGxPuANpOfoEQdPpUpwE6WznpJOpAGCx/W1645E7ZyhXLplAjr5rNrXLA4\nMp5Vnv9ei4S1t8kaWk8RCApOYP1nc7Pt7aMi7tz6GSBLcAWVMm/Rfppwtc/4\ngsNXFUFYef+J3vLzhRlyEePyUOqG+h8cQ8okRaFPZ9wLIIj6+AsdhSqCvzB0\nbMz05xSuGFbiEyDaUGpAsxCraiGNFBWo04Wna5zXCXfj2KxSQ6DWQVzLr+vM\noucf\r\n=gnan\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a0dd9d5eb8b5c68bed803a9dfa03eeb33b614e2e","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.66","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.60","@balena/jellyfish-logger":"^1.0.38","@balena/jellyfish-metrics":"^0.1.104","@balena/jellyfish-environment":"^2.4.21"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.2","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.109","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.1.9_1614565922545_0.1483800933598185","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.2.0-remove-user-slug-fulltextsearch-162f62d3b8366418372d16f88ef0a8b63f6db8ff":{"name":"@balena/jellyfish-core","version":"2.2.0-remove-user-slug-fulltextsearch-162f62d3b8366418372d16f88ef0a8b63f6db8ff","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.2.0-remove-user-slug-fulltextsearch-162f62d3b8366418372d16f88ef0a8b63f6db8ff","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0561ae2d72afbdfd302e0484c6acbb5ed3b694cb","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.2.0-remove-user-slug-fulltextsearch-162f62d3b8366418372d16f88ef0a8b63f6db8ff.tgz","fileCount":98,"integrity":"sha512-1g5vmOao2Ew5n8cYSBAlhkdXHwxpuywGu1e4akPXBPoHDTSySIqRQBV04ltZJDJOCaUXsJlMXQ+1j2+ye5H73A==","signatures":[{"sig":"MEUCIGBizlajuHbKWWekV3cafQDt4BjRUC0Zg/M6UFRnoSRpAiEAobRDtwKLwWRjsUMibp76fPiRJ4KfyhIgQEdDAyaegsI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":615237,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgPFafCRA9TVsSAnZWagAAMtsQAJ/n0YCirGG5O2ZPn+Cg\nArp2eCJZ032yHUSC2M4j2tpBWZYVorCuPBbUXueeKZr0bikQRK4+FLrkmMKT\nDuS3E7rgvuBIuaKoVEUUwgJTmBginUDefJPgTPaw4AB84JnvBI5YyWJPpCkA\nEFEuQlU/Bm1v5q8O3pWBvsQ58qxJ+wWPFUI1eWS69evu4a93k8QuJAbYcePF\nxYJ3FjovzJSdiGEcm4nWECb8PpUDH3D6QK9LGcQCM9zuRVlN7jlKzIc+6VWT\n+nvXb4D0sAN+rd4DS32ZZiV9YeBNOeTBS2ech0/nfaAzrO38wIHnR84qSMpG\ntFtvH/xh9/+6V2oI+WZBKtc//J7Zu8h5emS1lnHL/C6CUoJeWmQ1qQ4+1o7l\nTgy8V10MaDSWhp5Xw6cyNUhW/wRnLGv+H7nsZ50k5RyVZ1Rh4xhLt/taRKwr\nmIvsclLi7mhNHpS+6Wksqlo1jSXvgY88jCzk4t28pJuVa0PhBJ6/OCaiqqHY\n0yfO8zeHlA7ndauXJIf07hrOvrz+gSXogUrPhjWgRkfcEliaB41P1zRxSiHV\nuqoURaIm86PGxzQ2BFszQ8TQ41/HNgSgaciA1YoWw7skS2xO4V+wECvwdYIy\nehj7/RP/TXLp/boeWfYutDSld1KwaxXvD1Rks89XDxAjz050TA7wrL3BPvd7\nojAG\r\n=Lt1T\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"162f62d3b8366418372d16f88ef0a8b63f6db8ff","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.66","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.60","@balena/jellyfish-logger":"^1.0.38","@balena/jellyfish-metrics":"^0.1.104","@balena/jellyfish-environment":"^2.4.21"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.2","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.109","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.2.0-remove-user-slug-fulltextsearch-162f62d3b8366418372d16f88ef0a8b63f6db8ff_1614567070501_0.7292012120390885","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.1.10-renovate-non-major-04114a92beff255ac09ff8e4e76757c6bccf721f":{"name":"@balena/jellyfish-core","version":"2.1.10-renovate-non-major-04114a92beff255ac09ff8e4e76757c6bccf721f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.1.10-renovate-non-major-04114a92beff255ac09ff8e4e76757c6bccf721f","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"436a50f0a25182995c0936bd107d0b9a7a4da322","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.1.10-renovate-non-major-04114a92beff255ac09ff8e4e76757c6bccf721f.tgz","fileCount":98,"integrity":"sha512-v0/qa4hHBuJ/zv1D4bjb99HoxPMV6VuxRLCGwRV0/zg+YOlqCLg/pxanU60I/jyI9bJbMQJUoe05P3Nggopnow==","signatures":[{"sig":"MEQCIDq9wMH4EAm/AKCvdkfWEImYPS4Ei9eXMXXKHMtS52f1AiAUu+w/gsTz0gVSZH7J4cIAPekQL6N1HBRP3oRE94M0Tw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":614679,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgPGv7CRA9TVsSAnZWagAAeiwP/j93D6smGq8s8tKouuRT\nGr9RUmgG9XYA1iQSoJsQDnJdni4YChIZXl2hW2eCIrIVIdBy+PUweQB9q1y1\ngLCFvadBhunbmKRfUW+EoBsFXLq7zb3dRHTT02mbfgGk2GULc8+ebof8AMcZ\naH/1Q6xqejGHpaUQHWyXRyQvXHJUO1iGPz64EIN76SNv9O9gmh4vtVRVGU4/\nwCApIA12T9olk+IhAoHmVtqqwOa6T8XO7Bs5MjBwTDICP5ItXqqb2HroLrD1\ngSEEzAxlUJvvb7VeA13BeHRps1w4ijREeXs6UfRNIaC4y4dG/jeIs/qdjshn\n2vIMhFJ41wF+Tq9eRrWMyFLDwlOuZF/35M6QqAbTk0MrtmRVqsAjnCCK5fH+\nOOkjF9cJ3+nfPrOVfJdH/NZZLmb4IpjE1SEc7HLeVoUnelqqllTRlqsQ+KTW\nR3QpRDDgYLf0Y76NE3P+W36BUmzRMYwxxcYsNmnGbtCvcbLWEKm4FIy1l/VA\nu522riwsHtdecFoL0odNUSOZmHJnmWwPneI3IdEe6ubErt8rpxcflxyd4Cb1\nxrdF0lnHANbIzscV0uhwerNRAFNhHEtulcpd4XWig+O56tmusWPKhrusaAu8\nyJUcSMI9ksm9BH8QsFFCqXLlf6bGw85l1uT25+ZEQuYdyDiExcZ7y79C+n01\nZ+zJ\r\n=9KgF\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"04114a92beff255ac09ff8e4e76757c6bccf721f","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.66","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.61","@balena/jellyfish-logger":"^1.0.39","@balena/jellyfish-metrics":"^0.1.105","@balena/jellyfish-environment":"^2.4.22"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.2","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.109","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.1.10-renovate-non-major-04114a92beff255ac09ff8e4e76757c6bccf721f_1614572538555_0.5009743061670395","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.1.10":{"name":"@balena/jellyfish-core","version":"2.1.10","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.1.10","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c0cb8f52d666614df8e98b11c5fe98fcdae62d33","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.1.10.tgz","fileCount":98,"integrity":"sha512-tQrFMP7dtqmqZS79mTp0ILK/lbFQ+u5H4JGAK146a3ENSnTUDqgBlmkS1ffaJLKAVLA0zYoiXrT67pg/vaNHNQ==","signatures":[{"sig":"MEUCIFxGmp4Cne74xGRrZS1oEiQ7s/DwmQX2tGRjmkLnNmtEAiEAoJio3tvZ4guT1wHe+uwOoiHbV765GYr3wFsf4/FEPFE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":614619,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgPG1cCRA9TVsSAnZWagAA/bEP+wTxPRajvmoJOLQYZqb4\n896hdloY3BUxg190CNz6cIScmS4SbG03tZU3xaNyXBko1x3yjPvUGg966Joy\nIqRJk1j0SpJ24s/FJ+uTHnvVyGB6DTK7x3t/nI3oRmFNIjuxDa+hoHpJ/PJY\nR2G+bbheproYcBe+obT/A/TronKR9IMk+tKudpfZiQbLSPEP3z8Wtd200KkT\nqmbT5e5MikOa+GH5U5QMSHjTa3NmgdPMJGVC+OoGa8lgy97Vb1kiihV4b4L9\n6T3d5jmK9S+Cj+U9zNDE7SPH4jsz+y3oWaJwiME4Rk27Pg4b0348EzXG+c+L\nAQ45smAWXwKW4aKB4VkRazl3IHaWCQMDcCUaSERgP7iwwN2dOAt5Kjy6dk98\nnRSCSrPiEwUw4MbFIZoNSL6jKABVR9BSg3GOB8KdOI5J3nTz1fKywoZCJLGt\n80hGJp7GcTwcvk2TkzgIerg3iySKki6lKMTLMLSXveV0vPCAPM+hNzcGKtTC\ncA48kIecK09ZYWJqZ5az7q9HhgKrWorAUZcuW4Xd3+OKEs/ZvTEUMvKhKAki\nHnfIOQY0Ve5127FMwx5ynkBudMf8fJ6cRuKlr+eKUiuWHhBcF3Cdjx1/WafJ\n2kS1T/aFSvwxOBItTlzSAjHcgplkP7OYzairUK0GouQmHkxw6Za6jMPU2rTR\nUzjx\r\n=diEw\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"423a31dba3de55a6d9b4910204e5ce22b1a87797","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.66","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.61","@balena/jellyfish-logger":"^1.0.39","@balena/jellyfish-metrics":"^0.1.105","@balena/jellyfish-environment":"^2.4.22"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.2","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.109","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.1.10_1614572892204_0.4386913440720501","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.2.0-remove-user-slug-fulltextsearch-6afc2f4eca1f9825f4fdbc52d085713d17c38562":{"name":"@balena/jellyfish-core","version":"2.2.0-remove-user-slug-fulltextsearch-6afc2f4eca1f9825f4fdbc52d085713d17c38562","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.2.0-remove-user-slug-fulltextsearch-6afc2f4eca1f9825f4fdbc52d085713d17c38562","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ea46c344a79c2edca45f991402bc0f95e06ea5fa","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.2.0-remove-user-slug-fulltextsearch-6afc2f4eca1f9825f4fdbc52d085713d17c38562.tgz","fileCount":98,"integrity":"sha512-6g7CyohxOWFoTqF4yhigfIC8/0OffnVfnXc05zNi1DH2bhu6Ln7dB5FSH8iiwZbduzIWyyC/IsGv+KXthBIIGA==","signatures":[{"sig":"MEYCIQC/q76seL69PeWik8g4LRypFzA42ckhsp3UJvqM3Olm+AIhAIuvl6tJ/gTbzDKfzCuu8cFod8KOEO390/abKP2wkpdd","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":615586,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgPKrTCRA9TVsSAnZWagAAzooP/2XutdShTUsLYrTLIScy\n4XwcpeZU7RaoLnCdOQGThZz50m5cN/qMMe8W+T9tlFIRH3RRJKc7orf2WjaW\nrbE4JYVeE8Exw8unhrsFt0SV/Urh5gAp2+5kIK//EW5myk5cPh+eXPf18oqk\nk0xlFKL+d0ssk76DPX8xxVISEgwHCD6ShdewAVEuU3hS3YkZAeqU+os0rh9B\nduc4435heAVbIYm7NcgQwXeW531O6s0uRp6FGK6qieQV6a702ndWoBQrqTua\nyMRF4OI7IntCz1j7VY87C2dTuvcn2As2Qy/u2s6JuCIYWP14tRlp7PAaN4ZB\n2SXb58tRrNMxU/318Hw9ki81kMeOFKAqK4kxU0l+49vAhAzb5CuB/V5VHr6f\nRhHL2T9NMLgTsttGHuDc6clhfI1k8jAocQRB1kAQxmH93UETg+rIJ6hDzFMT\nargFvS9/C7S829V+2ltIY6mB7mRbGTAQ+9EUlWeGj9VinaGsC55cPvO/iRjG\nHIGRfn0E/9gfsDSpGdDqbGR8wZPg8Y1BLGWPZfnw/Xqgn/NB2etf/BOv0aBs\noZ7GdW1Sna89j++wnAKDyWtsUetvIvSIVawVtDjgISFSA/M/sPjCxK+KCOPp\n79sfmdUsA94gXTicXfsOa5rmrtxWnIsvhDLPIg//tEauKoqspzINMheTgrb/\nXw2K\r\n=RIr7\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6afc2f4eca1f9825f4fdbc52d085713d17c38562","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.66","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.61","@balena/jellyfish-logger":"^1.0.39","@balena/jellyfish-metrics":"^0.1.105","@balena/jellyfish-environment":"^2.4.22"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.2","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.109","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.2.0-remove-user-slug-fulltextsearch-6afc2f4eca1f9825f4fdbc52d085713d17c38562_1614588626422_0.9358658350444935","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.1.11-renovate-non-major-25248fadaa0918b17707c4050d6fb8e6769bec37":{"name":"@balena/jellyfish-core","version":"2.1.11-renovate-non-major-25248fadaa0918b17707c4050d6fb8e6769bec37","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.1.11-renovate-non-major-25248fadaa0918b17707c4050d6fb8e6769bec37","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2818b8c08b0cd5e11528bb7d8a1fcc250ec6f656","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.1.11-renovate-non-major-25248fadaa0918b17707c4050d6fb8e6769bec37.tgz","fileCount":98,"integrity":"sha512-x1PpJcSTNiW7ERj0DqgCP8hLpQgjzxTZ2gTPaYN0T+Mo95/s5kqZvDF0dFfE4Ey4PTdsQbB9mfceJo8ufQ9ifw==","signatures":[{"sig":"MEUCIH0StZ6/6xViVcUKWXyya+jvuqCkTQRqSIviad0VqXl9AiEA5XrsPgS/aHuMqyDCIJHGdw56WiAsOFo8pInPV+t0bHQ=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":615106,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgPKvCCRA9TVsSAnZWagAAF1gP/3kfBDU1b8y34LZOTEx+\nfcJk/C4XgNUS+Tfn8LC4iueq6s8tMMsMdoicMn8BWEcpeSTa8iqutHWBObh3\nLWq8o19a3C8ETmiT0MCb2ZCRCAM5qCSmEYtnuAho+a1GEtkXPCUm2bPTHdbi\n6ySf/gmLIsTwkFYtja7qtbJQxtU622olxUkjwtUt/B1sESJUd29geIQc7LYn\nbUsN3dl8gFtXiLSITpS0zAAdtYdlfIAes5mmPvDXiVAbSxWv/sYI1TSuGSdY\nydDEHzuEcxD5gsgcLry6z4IgVKl9JYEMclfOo2azVzl5YYzotQBklvqppS67\nu+5CepidkekX5NzL8livCJ1ER1Sr2Hl94LjgkUgKtDZAiPSQfgsGc9MA32GJ\ns139l7gC3AAfHK5KG0vnGG+RLrbrBTwZBEQJbnPV9ugGv2WYvWFbQPIjU93i\npwilZGAEeTd9aG7NPcqckpcY+rv8eRjdTbjrMHi+O1DEcIqna7WL+/q1Lse7\nyDZbp+h/q8XWXhNGLDlLTLCEJbtXdJW/qND9JxkPBm1QUOtjy9flckSPD1WF\ner5VsrQm6N5BaeRdKFKoUq5ethNsm63kXZqb1mL0JRm1nfPJzVOr8b1GiHFu\n8HADyBQOqn+/TzTk6rxMXUtW+yi1w+MpnYuFdUZX2rSTVKNRIBomFYn2o/TN\nYrW2\r\n=fHyd\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"25248fadaa0918b17707c4050d6fb8e6769bec37","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.66","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.61","@balena/jellyfish-logger":"^1.0.39","@balena/jellyfish-metrics":"^0.1.106","@balena/jellyfish-environment":"^2.4.22"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.2","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.109","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.1.11-renovate-non-major-25248fadaa0918b17707c4050d6fb8e6769bec37_1614588866218_0.7582957083261035","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.2.0":{"name":"@balena/jellyfish-core","version":"2.2.0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.2.0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"66de19972cd53468bcf26b120734a35c0f58d86a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.2.0.tgz","fileCount":98,"integrity":"sha512-c5VNzwBwUazNnEvjCDDJzB+BXasxOUbLYJWquehzVxEt0ZDQTDD+7hNMroJViD3Ix4AruV+FuJUoReCo7FdA4Q==","signatures":[{"sig":"MEUCIQDsaqNfwIiSJURJ0f/EprWQGhG7hKz2tP7lqzThdywE/QIgS8uR58zDvZ3fjL1QP1ObOVNfdvE6GiUiiG10pGrT2+E=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":615513,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgPKxACRA9TVsSAnZWagAApkMQAJA6Ob2vrPk3r/t2/CID\neRTGqvBEBlMNpMLS44shUWzISOZugERwv+tPq5wNZHuACAa7uY5B5TEs2YUF\ns3IEE8TpxeeBVb71/nRt9G4Cz6+kgHTN0yOpTDWpRxkg2rZ61hCOOFkzlOj6\nAuBRe74XNP2fbq6GYuzVz5xxtWSMsyPlp4Ce9GANUvyHDOBlXOKp8zqBknC+\nG+iSn2zwinlvViGYu/4sp1xX8VGXMtgTcdpE315hQJwIsEKQqYuFwbc17nY3\nrZmEz1iIBzUbg2bkqljrETOaA4wfTcEajwXKIhGUXdbZGm+RVBTT6Q9ChBw7\nKnh1nQw6GqJtlGWviMp1sVZrRJvVzJHefmr1PvnTdDbk/1OWBpICBstZE+Fq\noQ6ZJ63kEuKf6Spe9Aw5Mh79auYV4UROHFJQLvMUBmmJRLzRRQ3qEtIANQIM\n8Tl050S7G4GrT1LmgVJipS0QEhnrTJouIz6lMIGwPcZTrYPpt/+w8YZqF03J\nBuumBqB6APo7+tjDty62EDJGzVmkgDjx7XUOFO12P1xmcYLojwcE6GIhuLhX\nqS0Ugw+3l35uxTa3od985LV7V0uExEx9nmZ3yAVAhNoaD+vkos9CdR3Ps98o\nIivZ02AU1L2bSt8vWolMl44k7wOPJ8pHKY19iEv5EzkJLOEnTCaXtdeb/Iet\nGKQY\r\n=5Pk9\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"545bea11a3b4361cc325cd6487b9f5baa6c5ccaf","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.66","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.61","@balena/jellyfish-logger":"^1.0.39","@balena/jellyfish-metrics":"^0.1.105","@balena/jellyfish-environment":"^2.4.22"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.2","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.109","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.2.0_1614588991967_0.9659394497618425","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.2.1-renovate-non-major-c40e42627e98704e35d8319bf25ef3cda2240302":{"name":"@balena/jellyfish-core","version":"2.2.1-renovate-non-major-c40e42627e98704e35d8319bf25ef3cda2240302","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.2.1-renovate-non-major-c40e42627e98704e35d8319bf25ef3cda2240302","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e637b011864e8e513bfbce354feb321c12d59643","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.2.1-renovate-non-major-c40e42627e98704e35d8319bf25ef3cda2240302.tgz","fileCount":98,"integrity":"sha512-jf3jixMdFxYkoRjsDz0ygMa2aLmMVCqB4K+FSdn3cOnsljbcsGgCfoaRNk+vK3ToO7rEeBL5Krs9W34iSVR2Mw==","signatures":[{"sig":"MEYCIQDDR6Y14hhzbaAe59R7okX0BawOJuSYXVEA4XZi3lS7+wIhAI4/W+931BQjhkXnQ+tFytlnJ1x+LZ6TJQPNCnSFF9qK","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":615998,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgPK2yCRA9TVsSAnZWagAABZoQAIRbWIE5GlldP8WvMnpK\nS7MvvVABnnu4v2Y8Gi5TD4yPb4bGDc6l65th/5UZerR7/TSs+5tGrp45i6gN\nV+VUbl0bcEkTYOO1PvLu8RMGEklli5ILYNnlHASukfDmdFBWHIgQ84HEVxOg\nVMmiH7LgHhG3qQ9dqujeYTOYKjcBWnVwlGGZqARkaE1BiGFY8CcMdRyl+gdJ\n07M+ZanRNyuT1h6m3Z80e5cMM1pDRgTfune94COlBtxoCESnrF9m4atbFTZA\nYIql7Ncj0hvU/vSD4oHcPC141D8Cz208qnzo3AtIl/JkY4OvA5CIlz1mv25D\n6Br3aTDBxStRBXmuZWC4Bq6JTe4yFd3e5JFvypZFGg5dqzCyEyMGQB5FAUz1\n15KrVjMc9PlwcUMLmIMOJ8S8JpluKKfgxc2vGly9Ha2bx6U/kiRJ4FLogw2f\nS1aTVWUiDEL/f5xKdj0+dJ6x6NArOd88jhhop8dhfBs0BuaI0IXUCZDSqiII\nTGyjVb1QhKeMyhLDXDLDfWt+2iO3ruPcs911ZZ9DuyCTUjJ+cZh9JzbRGiCL\nhVUwdphzhQkOODgleMqz+4rSdLQaJIbDJinaPl96Ot8kFCYvY8GwRuN83Xxj\n/DxSbQhdcv7HNpfm39YJ6YvxoJFZN/qfdJcnM08d0slvcROmkE6JbwybQ/nm\n8OLx\r\n=9K42\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c40e42627e98704e35d8319bf25ef3cda2240302","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.66","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.61","@balena/jellyfish-logger":"^1.0.39","@balena/jellyfish-metrics":"^0.1.106","@balena/jellyfish-environment":"^2.4.22"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.2","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.109","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.2.1-renovate-non-major-c40e42627e98704e35d8319bf25ef3cda2240302_1614589362341_0.07040136698600863","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.2.1":{"name":"@balena/jellyfish-core","version":"2.2.1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.2.1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0adbd7d25f0628fe7cb4fde222275deb55efd66e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.2.1.tgz","fileCount":98,"integrity":"sha512-txEVeSjuHZHrclXzgpOkgbW8gR8xOwcGOMYTTus0ymzIOlKeP7nEkMj0coD53lhdab1hUSuOuV7LSapzx/t7Aw==","signatures":[{"sig":"MEYCIQDX32TjRjAwvmC6jhmgowrCcWEtP6BB/MHBSw5smKZIxwIhAN4rc9Na/X7eIa7MbslbIaMwzkH+I0Q7YWexZ0TKUpiC","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":615938,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgPK7RCRA9TVsSAnZWagAAbiMP/jE9gK5jV6g6o4aOXh+v\nXlJOgfChxoU/7nWE/c3V2AXJ0HOh1Hrp0La3NYlKhgA+wLwfUmIwjiHcKEY7\nygw29DGSmXd+QEpfzaLj0+Sj02YttJTxyAIKgtOTMfZewDzpJUCRZxeoJYLR\naEDsfq7h8OMPmcl6KSn+1/Pzm+x8kL1wYwnBAB/iSpFG0am0JAr4Rg7YqeFO\n2S/+aFc7KKQuYvuvcWlky3suiEaHUnlRgGMgQoVhyA2YZ0MUdKL53cu51m8d\ntJywCtNMrO+WUxbuQV7QAWsEqRqeITayx5p6KBdI+bMmEZkMB0oIqjxYVSQg\ndJeFRUV+mx6AHgdv3jH6vPCJZURPPAnhvpFPpOY5f6giwLo4XIdY0OnLC1+J\nJ1/JWY/jE5p9vW90eQXM5p0Uh/L8p7HL5j+gDxVaRDOdEA+tZDJtTUn1OSuD\nR5fdVZI8iUJ2TCBIAlSS026KXk3Yi9bJG5po7AczMpyDVdT4FfVAWfqrAlb8\nQTfHjdrRbvr5JE2WEW1SJKgC99mHf9FPuEFPtCxK2dcWW3l6ovyo4sTO4REa\noZzGu7yZL6JRFT1STgwYtvMsMXdLvEtmm5Q7p/komvX1kOugtPoPHihCbHtu\nWZoc6wHgY7d8v+pdBMr6AwG3d2igdeDIy9IpcRn6q9FF8CHtiQffebajlAqo\nYdJs\r\n=NV3H\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"cd0cb2c3e970fcc2d9c1abc1b8a6718299121e41","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.66","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.61","@balena/jellyfish-logger":"^1.0.39","@balena/jellyfish-metrics":"^0.1.106","@balena/jellyfish-environment":"^2.4.22"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.2","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.109","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.2.1_1614589648207_0.050473076429009334","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.3.0-remove-lenses-from-view-ba57b51ca19c3f9b90d12e6d830e4d3447bd91fa":{"name":"@balena/jellyfish-core","version":"2.3.0-remove-lenses-from-view-ba57b51ca19c3f9b90d12e6d830e4d3447bd91fa","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.3.0-remove-lenses-from-view-ba57b51ca19c3f9b90d12e6d830e4d3447bd91fa","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"17259ff199d151db2cb0ff9fc7e953ffe9bbe9d3","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.3.0-remove-lenses-from-view-ba57b51ca19c3f9b90d12e6d830e4d3447bd91fa.tgz","fileCount":98,"integrity":"sha512-tns3PR9JgXCe0+Ux9w0QXjlywJYUNR2wLFsKk28m3EPy8B5rZKpWyEkYo244xYN+1ffL7bXqbhcKP6qt7bcDpQ==","signatures":[{"sig":"MEUCIAeyTfw4do1X81f7Zie72LD/6hrDqopn/Vk/BlqACkXiAiEAofxdbiw58yYCnIYf/eD5QZdexPjCtNsivPAlbUiGpA0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":616522,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgPLBlCRA9TVsSAnZWagAA6FwP/jzUu8A6db7cBrC0vLtX\nMo0dXDrkBugJ0abf/ZG6yooqEwCIElBhXc/UiRxgQOtawI1EKvSBo9khETfI\nsrF0sxASVIzOyrINAnJ8ySBvBlLadg2SSlu17mU8B5KBRDQrLr1xL+en19oO\nu52VcKD6ic10uD+4DhbJ/eVMeaJWUZ1LLmu6T6xjtt6ycRmjmD8Wp73N+H6H\nY+KEYu2KGScsMNS1Y+oKm/76ISG1lCkmCkjTeUFDQkvG+1fdgqEohnK5aWs0\n02A6skPlnosq/DH8itZLo4IOtJTXBy+FRenY/3+YIty+npq9j5cGugua1V6y\nXLYIROR4a0CWYrr1zTOLP/Cn61bcmfRZgf6/VZes77BmMzi9kb8lp5gvQUvJ\nk9lkMEUg6a1U91vRu9Nxcqier3S4YOg3TQeUaEflfHQd+wTbl3VmU4I5Me0L\negxTujg2C8b6ReKow+YdHpuMOHiEuGGUcnOw2GBvtpVQiqDPkmDLbe+QTz3M\nkXtM244zsalmkc0hUIsIaKmLJkLXhO2DaaTX3xQ6GF2RqSwhvcFFQQ7OFyJb\nLG6OuL0Xmzq/MQP/B3pvu+U7fm0v/pq7SP9rlB6a/7aT2tJxm9YFO5LuXuUZ\nN+a9nF+OAGy9Vl1LT75kGDyq0stohS3Fyn6HJD0pak54Vs35OU88bJk1ab6L\nuy76\r\n=k6A4\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ba57b51ca19c3f9b90d12e6d830e4d3447bd91fa","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.66","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.61","@balena/jellyfish-logger":"^1.0.39","@balena/jellyfish-metrics":"^0.1.106","@balena/jellyfish-environment":"^2.4.22"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.2","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.109","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.3.0-remove-lenses-from-view-ba57b51ca19c3f9b90d12e6d830e4d3447bd91fa_1614590052566_0.45041627219881164","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.1.11-handle-error-events-on-stream-1121bac66fc06460aedacc5cdd55f6864b19a6db":{"name":"@balena/jellyfish-core","version":"2.1.11-handle-error-events-on-stream-1121bac66fc06460aedacc5cdd55f6864b19a6db","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.1.11-handle-error-events-on-stream-1121bac66fc06460aedacc5cdd55f6864b19a6db","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"de6590cd152cba41df279e3e72941d6c1941fee2","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.1.11-handle-error-events-on-stream-1121bac66fc06460aedacc5cdd55f6864b19a6db.tgz","fileCount":98,"integrity":"sha512-wd6vSTcLjnMCrwp1GyGT8Qftky2vwX+HJNJWh6ANyjEna+SnxImMrpVwUa4zM/VDw4SfCKc/beTByswhPoMZfQ==","signatures":[{"sig":"MEUCIE88jLrAHVmhw/SPNk27hB41v5FGzScV0VGyUvC6ie1XAiEA1fSxi43DcrDTK5x5K8slo2noQYIuJcHUMJhFbJZJnWM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":616262,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgPLxLCRA9TVsSAnZWagAABCEQAIo+K9/H6rmP/vx2Nisi\ndhjDqB0yjVXOCIsOSkrDZaJdPcYXq2NZqwzX1Y+G2nZDkgAF9MdiY0NVHTU1\nBCOHJ4uPGdkVMSkOIBwIxanGiJcLuT3k/p+VYjwkj8Zp0bUY3vgtwxlzi9Gh\nuFUZz35depzrX/uGHn+DgHlD3KIK/rnX/lju3uc0PqlNkbhpIYLYCotMK4YW\nhf1Xn0josvYvEovv1l0XI3jzDE3xasj3UX29XZnTlzx6VpIrBcQf3YWkBOtc\nVuKODdVaN0pBUQrJ4HEaHw5OobrDQxRcro68R0MRqmwxm8QffKftENdV0iSK\nOJgq1SfpoqHNR5Ibw/tUZJ/0cAKupreK0gWZt+mc+s/Syi1ZphrlVHVDcfzR\ndRWOfnMA6VlQD+/Tc0qNlma7tVqXpoPE8GoVsPV199T8YHaK9LLl15GbsbTp\nFtnT6920Kud70gWUggeXn5lqlESUb3b8ydA1dK+9Ti5Baq1nhO+nZ+5z3OKX\nRkobkzB4Cd6P3boMkoIrBrKNxwp4nN6Bt6abx9CYFBVflZ4vQbR43tRJ7by0\nM/RtsDN5vDPVMqgVo4jNjCT+K7rawKVP119Xejs72LH9NH2iN6Of1zUlO+NA\n442MOXDyZHqBxk/HMd2mq0I8epqYZ8/bwtZB76Edw/wMS1vYFq44jpLDpNsk\nFeN7\r\n=R9+F\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"1121bac66fc06460aedacc5cdd55f6864b19a6db","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.66","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.61","@balena/jellyfish-logger":"^1.0.39","@balena/jellyfish-metrics":"^0.1.105","@balena/jellyfish-environment":"^2.4.22"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.2","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.109","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.1.11-handle-error-events-on-stream-1121bac66fc06460aedacc5cdd55f6864b19a6db_1614593099239_0.8500193838258598","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.2.2-handle-error-events-on-stream-b7d7e526e9b476e26b35f6e501861cbc0c78e0cf":{"name":"@balena/jellyfish-core","version":"2.2.2-handle-error-events-on-stream-b7d7e526e9b476e26b35f6e501861cbc0c78e0cf","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.2.2-handle-error-events-on-stream-b7d7e526e9b476e26b35f6e501861cbc0c78e0cf","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f741ea45e6f2fb3db0a79a1087c5c9ff3086baff","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.2.2-handle-error-events-on-stream-b7d7e526e9b476e26b35f6e501861cbc0c78e0cf.tgz","fileCount":98,"integrity":"sha512-R8o4L9OxL2chFKVjeOf9rmsI9eUgsg63yv5HmOp6iaqbedyaWA6B7zrX0eR0agSRyZgF+DcCskOejYqKDX1Inw==","signatures":[{"sig":"MEYCIQDtKCejpHL1NMyaNS2M+rLH04ntL2Zby2mCvwDM+X8yIwIhAP41hfblnAOYcVSnY8fAUyvNEb1SQqspqNOFIHYM79Fs","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":617579,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgPOYjCRA9TVsSAnZWagAA3ewP/1ksfVSxmGT7FeAgagxe\nPhvmcSZ9UFBCKnVrrPC7M9uvsZLDb7TM5FScdNhDZhxmI/RSC1LeXB8kwAQZ\nseP1bckQ4Tr00ljz26l2neYATtWu47GeeGo8pcWgn4k6XddEb4ALX3fuSIv3\nW30ZF+F8KXxcmvF/vDKZcxKWXW31bKbhhOh+6omyNYhtM2d8Zz5VADYed7Jb\n70rlfCjNvqCOcO0vvxDCzrnC2ixBc+5hw7PDKkUua3+6udtmCPH9VwJ8C8vx\nDFYoNcKT7O8oOOrpkY1UgeMOQ4Pt96aqj87jBctk/lj4xJt3o5bdtAUQLyFc\nRpSu1ffjn2DdL/sg+CPP0qQqh3K3rZZ7Cuvt/915Kcux7XR7S1xO9jnrrL6R\nhME0NhNt8WQ5ULYDt3Tyxi3AGAeygk8USz0+vtfJWGJK358PUvq/kSx3fDrv\nyb/bMApCHUGv7aRIFNUocwvz5iJbLMusPWhKoeTFh8uxI+BdnNvgV1zWJO2U\n219bv9RemWyiEmHVRs/ahKnjR4jQ1shAQh29gEJ5LefwEKU2rrkLfiPGV/9+\nJuqh0KbT9baCxGr9zH8bigP1+xhBzEGBIQ5wgt1ah1cvYKoOxOxrbLsTCWb3\ngfMY3DCKQ1xWs10gTwiqWfbqFBUVH8pVfzU8WhQLsaBWiwK7RHHe51sR2osh\nX3vq\r\n=Hlrv\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b7d7e526e9b476e26b35f6e501861cbc0c78e0cf","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.66","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.61","@balena/jellyfish-logger":"^1.0.39","@balena/jellyfish-metrics":"^0.1.106","@balena/jellyfish-environment":"^2.4.22"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.2","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.109","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.2.2-handle-error-events-on-stream-b7d7e526e9b476e26b35f6e501861cbc0c78e0cf_1614603811348_0.5196311676284922","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.2.2":{"name":"@balena/jellyfish-core","version":"2.2.2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.2.2","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"08ddb9b686e34bb9467a0a209433c1accdfe87ea","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.2.2.tgz","fileCount":98,"integrity":"sha512-FiOaMHuMbgZthPvd5+OI1vFSIaC1g0ioSSZFWbwPJukXrVN7axHs7gT6GwWpw3HX/X5JysjT7EmLkhDixwNKkw==","signatures":[{"sig":"MEQCIBQXMvbY53XJOrxQ28zRVhT2Lghx3Dr2sdzNIRhzX82TAiAwAZkKQSyhnycBzfIJWBo5cF33329ceW+h5gTOigbIrg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":617508,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgPOvjCRA9TVsSAnZWagAA5ccP/3SWNqu06fCgkATGBtwa\nrZKpSEDyBbuqOwfZJUAPy5jJ1GFm+nHlF4ZIcCZV2WGieGqNtwZkDa6j7bxJ\n1+KsrApas3YtXYAdXcIlMQGFR2Kjv+IQjtsTXakTZ8j5gKuofae6O4DMMPFx\nE5U2bWcoq193uyCvQL3wGwGyvFyBeXBVsKPu2IrY0u1EITH2CmGtPgY53Hwk\n+cyD/I/92UMlnI+1BVQCCo6HFvGg5uA3GENblKTurOKZrOLw7sSvqKkqING3\nLKJFElfEgjHL5kf53jRkuOZaXbIj+GmF1Ny+qJt9pGF107DWzbzUhSKhQZ8k\nHCONlCJ8b8iBKDpjvwYRipoO5tNXUK+k8DGDdQjHitIi95vI8zGBl3iSGOog\nRgBIinl6wFaCwOylgxZj8Z+QySDj/x73HFini4NsJE2Hfbdq468MmdJg+Hq4\ndIIi5z19VfosIbjnE1LxVURGlUQpIJqwNfB9DUpoCuQQenOQT5uWrAp+Hj5m\nT5/r0eHbUdI9pl1PEgUzDOhjrCheFlYcbIFnrvspl3l9q7gCUAWkBJmF4i+u\nenphS6d0gnSJATMGPR2p10LEPgsRSU5oNq8dUyErUYC+Ptr7oItMdne1hk9f\nhSjRfM9pAMa08U4KCzDHd4FmTQRt3+lbjXwG5PBjrQQeZYWEtV4YwrRMPtes\nOm0Q\r\n=IwgA\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"18bfb85790baa9a514d97048a18c2e6c1c1dd7f2","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.66","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.61","@balena/jellyfish-logger":"^1.0.39","@balena/jellyfish-metrics":"^0.1.106","@balena/jellyfish-environment":"^2.4.22"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.2","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.109","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.2.2_1614605282614_0.040525844708924064","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.3.0-remove-lenses-from-view-8fef319d47a8ff8e0acdd58c71ec1d1610d1064a":{"name":"@balena/jellyfish-core","version":"2.3.0-remove-lenses-from-view-8fef319d47a8ff8e0acdd58c71ec1d1610d1064a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.3.0-remove-lenses-from-view-8fef319d47a8ff8e0acdd58c71ec1d1610d1064a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"75f6c8ca5cba4def273015c5f5a5bcf88ac5f264","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.3.0-remove-lenses-from-view-8fef319d47a8ff8e0acdd58c71ec1d1610d1064a.tgz","fileCount":98,"integrity":"sha512-4RSuDeVvB/bpv/cKPyn4oYKhfDBuvZxneiyd3cpvZByHNiMsIzS/2hLLqOhTZZK5qRCjMRELXiBRxpGOwFt3TQ==","signatures":[{"sig":"MEUCIQDFFv7dGoUK9eZGh2LukX/FOXOdo4J/8Bq5HtvnWJVmNwIgNu9slNQATok292x00yCcAEtTyjL/7aPDZMEKTsA429o=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":618092,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgPQ93CRA9TVsSAnZWagAAgtUP/AwCjpqPyRB7vzxQZspk\n0Wk+uWSWtmyIeuvqaDn5nm+I1ZW4QmKdPQRkSFhqH38HSh4X8RG9fIMqtsu7\nJ24RA7YYyFj+gxnsB3apxIoQ4jqaX+j7WX2n6BZqwxiTmNSl1QIChFKgJ4Pn\nwsrgN/ocaeXJM7HE+PI1XMV6YpVWXILWQP/ESW8Zb2T+Sd+O2K6N1VAmcGvK\nt76PtUyEc4lUr2R82B6cfd1cyjqA79YGcqYV6Wv6b/XTfWPogS7f3kZwMbU/\n1BDmxdlnI1ZOW05CuL9zZdK5cWqtprieHjYH8FqYIdqaQEp570QbLUBPlNLm\nvIyb6/0U0jL81bD7/64zTysrsb/GRfUT2gRcL4MW4a7NH4KGISdJfNlPHhOw\nO7UIXndpK6ixcUGfjveevaFrsyoGzsXxoIehl3K1ATZhc7n0eS0S90C3PS9o\nZxedurLtI+nv+MCki5CD2t1Mv+WoE1cTz8/RUIiFK/1MG9i82uSQTb4YYNHw\nQf3aGQjjkIn07Lg3x78a5cCC3Zk8biJ+SS4pDJ2iJa4eI7V9pEA/3A6IayTV\nSdltMDT2aygx0x8ztEfZFsamEjdPCy6mVmNtwNIAzMCnyegbI8GTB0zkg7rO\n8htSEfI8WXFqXoeITHTfiG5hm0zhIfjpMLI8H+PBm8WmiPwcN7gr4WpRNSzA\nWQ3K\r\n=zuRp\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8fef319d47a8ff8e0acdd58c71ec1d1610d1064a","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.66","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.61","@balena/jellyfish-logger":"^1.0.39","@balena/jellyfish-metrics":"^0.1.106","@balena/jellyfish-environment":"^2.4.22"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.2","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.109","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.3.0-remove-lenses-from-view-8fef319d47a8ff8e0acdd58c71ec1d1610d1064a_1614614391137_0.5194371098186545","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.3.0":{"name":"@balena/jellyfish-core","version":"2.3.0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.3.0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2f384467d97fc765ecf3ed83f2dda0753fe9c45d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.3.0.tgz","fileCount":98,"integrity":"sha512-emqau52JvcRSR+qbxivuJkJ60UW3URJRAZXghX5QYaS/tqnewMQzp6pHxUVWKUnrEY0GHRxXEMqctacOaELSxg==","signatures":[{"sig":"MEQCIFMB0TPK4PUF7aCwty0n5WfMP9/zzwKMpKW8SnYeI/qaAiA1qwHTu97z+TCCMCdSKO/ojQ77ZK6yqne3FFkQF+tLVg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":618027,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgPRDECRA9TVsSAnZWagAAYeAP/1wSJGg6Lu6aOA3febsP\n9pJUuAIPyU71QKism6HwPaRgd00+BcTP32RhdSQsyKwF369mOSCRRM/BYO6G\nvtjoBV+cyResxVFrXj+gL07DLQbKnZyUHk8LXpogOOrVSXqmtJloylzoi3D1\n3ZQV1te4jF6c5pjC2R6hyRQ5lfuA/KSvKwhAvvcM3Z0LasxGzcgSa7ICManb\n+acShwvLuNLFxx5ecVwqIu9uiVDHT4IMWxRISnjh/ppn0v/eSL9Gwo7Q+q4k\nTZ9KNm7dMzLprrRw+eI8X1pBnUYdiLQHMnk2WqDeJQI0iqpFZIxa4TWd3OGj\nPTvkt+nHMJ+jWWyvXgiThIXE+IbzeDhqTeGkmj+FSKleat5Ogl4YyYfICA4Q\nIGQIlFdTfZzoFHeQ/7SXTiSaK1SR4JK/AKy85Cw8RgOb1ynf8Y1DSKkPm+8J\nYF37YFNgkk9g/exjXoOHOdR/FNDbKR7c8DZJVFb2kAqOjCJZWkgBGxKTJNri\nWKTYcFjJ2oqoKPfjyQpTcVL6BNQgk4zJwHkoomkLq0XGWwMfV6mUFzW2eugA\n7N+vqsWwDVxWlxQJO1SI/EXxaWcU7eMnpqwpOzT8r20f4E59cgh/bPbUQzqv\niOMD9BC+Fr8T7diFwZQzRtQNaXgicCajJ4+OnYfmAxxTHHP55xePnQrVIEoR\n8zVq\r\n=ib6W\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6486391b51b8cd8c9608acc58ce30bf839e849c9","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.66","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.61","@balena/jellyfish-logger":"^1.0.39","@balena/jellyfish-metrics":"^0.1.106","@balena/jellyfish-environment":"^2.4.22"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.2","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.109","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.3.0_1614614723798_0.5302059709715679","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.3.1-renovate-non-major-d0faab464230debd406fe2899c4615e099f0d5bc":{"name":"@balena/jellyfish-core","version":"2.3.1-renovate-non-major-d0faab464230debd406fe2899c4615e099f0d5bc","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.3.1-renovate-non-major-d0faab464230debd406fe2899c4615e099f0d5bc","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f238b89ff4146d84b98f4527209c48042eebedb5","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.3.1-renovate-non-major-d0faab464230debd406fe2899c4615e099f0d5bc.tgz","fileCount":98,"integrity":"sha512-VQo2F2b1IjzNDI3kSWy9YRSerBYgcPWJqBJKjqijAbmMrV8dgYF4JPFfa91q0OAfxv5bqv5jBI+S3xMk97Wwyg==","signatures":[{"sig":"MEUCIGKEL+N7F8Efwj3Cp3e28OWyZEBRtjL/1O2obYZwCvHIAiEA9GgLV9BfeI9Xc2ENMzwHmRiei7k7aOUGMGA/dq7ExEw=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":618508,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgPSvfCRA9TVsSAnZWagAA37YQAJl3LkoiA0GwPEoVO4C7\nnaz5KTSxalfUexeshk1tubxDKrsQoI1rToKBNfYRGIcBFh3y43wdDtSx15ga\n7UhkTNbAesF9GYzBG+/bUsIdpz4nM/F6p1tIQnjEcF1w3qtNIdNFZY28Jctb\nvKzcES5MfkcWPBTb2GiP5fj6CAI+Gj4B46X8ySNwTgvMkV1aqZADJDvPYS21\n3EXNmnDKXscY9VLWOT2wMVmXsGlL+ONySvSzgD/J/NHswP3F1QbKqVVUqTFV\nuiHYrg8MnzQdg8y4K7Ff8tSFccFvaR5isWLzxpivL4tyOO8DwC9FvzYBjbBe\nTf+Qf+9ia78Lu5yaWkEh9dgGfuy51BwOVT/+pAXqZBr+VsF6Dj4iyR7Yib0o\n2NtpeH5q8pMj3Eyq1DM3o320TlDjxcpWYyfOxFfCyhu+GXzpFu8asyHGif8w\ni3TUCvV2IHUJqoZKHQ9O+36a+fX3qQfX989+YZdlMec+eKpUuYXjjsmTUEia\nOSSoYYThQqZQpQCIozJvCbCu3AqjCI7FzRhgJdT1nIkc/P4IzYE3eDEtrshh\nbG4RCmC40EDNKF5V4cM6KWkVjdcqblv09Sz84Ms5a/m/YETy6KDCkif7FnAh\nwW12Qh7k8Gak5hnRxH7pBXwkWmovlyh+KHXKB5bD3m0hGuHzA5oOMsfWIL7I\nsZ++\r\n=FXY8\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d0faab464230debd406fe2899c4615e099f0d5bc","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.66","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.61","@balena/jellyfish-logger":"^1.0.40","@balena/jellyfish-metrics":"^0.1.106","@balena/jellyfish-environment":"^2.4.22"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.2","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.109","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.3.1-renovate-non-major-d0faab464230debd406fe2899c4615e099f0d5bc_1614621662877_0.06276074378053842","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.3.1":{"name":"@balena/jellyfish-core","version":"2.3.1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.3.1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"491bd5278f4607f58c123ad5dd9b3560b623d861","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.3.1.tgz","fileCount":98,"integrity":"sha512-R5nxn3N/9sM7iXYm8ejxwElef76EyM2f88giELZDwFhzV8Jh0LSTnMYIdWOuNZ7dFM5M1zfDfZVewncgb4ENCg==","signatures":[{"sig":"MEQCIAUakXb/0eTMoapbQBMT9+97BEOdX2EtDIKJj2by7sj+AiAKhyG5sBoC4puFN9dn79Q+wHiQTz3lSmyAVdJ2DYxGfQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":618448,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgPS1QCRA9TVsSAnZWagAA7RMQAJbjiZlg6ftCaGZnQNR2\nVT0t3j0yv5nbndTGCB9DvY4jV04trQICqIY9dS5lMPaEssxxTCrU8uc3QmqI\nkbxx1PoWxtu9ZQrXWt5Xv3WdbtmVnmDzTaTfy79IQ59mn0hrrO/ZMFMwEHBj\nka2G5Cdvd07PTHJJoLXlUQhUsS0C/GJBfvWHwz7sOkvzIiIvhg7gKp/X3oPe\ntNx9Fzt0oIzSbQlvA3kLtoufGAayKJG5Zi3mSOS1AHbK69uD9Fl9mm5lX1Mc\nplsatd/MY0fi1P1WH+cVFSQvnfOm2ko2wOAsX5RN6sirdbNxVl2h4EzdhBAa\n1/HS7OdYMsoMCDgnFSTuoAHVgOqqsj5tZbrYZ18ByifUyZ0VLwLzD2huQjKv\nRS3BAMTFUnpSK1+fCJ6d0sUJ2OpEoGhfFPNgeJm+P10Xb3dvrwiAz4siJYRQ\n+Jow3jeebxEJdk4FanArts69UBfz/25brfqiZkW0u1wgDr9d9ZKyto5CT1Ic\n0lIN9+WlsrAH3T/gsJUgpdNRb9grPpDAW/0PCEYJnmvib78jT2bni6hROjeP\ntub2Don+m+zrkTtZ4WKTmTpNTd45zpbZdBVgndlA7bv4AZbTxV3sA4ctIKaY\nj9/Zu/IK7CiaHeMFXYe8HJT8QNMyYxOXT49JWRUH3jeHP66J9DViLCG2p0gr\nUvUk\r\n=NuOL\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d6fc18294e6a7356bb522f798fd97a2c4c102fc9","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.66","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.61","@balena/jellyfish-logger":"^1.0.40","@balena/jellyfish-metrics":"^0.1.106","@balena/jellyfish-environment":"^2.4.22"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.2","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.109","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.3.1_1614622031539_0.0768514138457792","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.3.2-renovate-non-major-6600b7fa397317fa775bb9921ae25fe1a2b9ae69":{"name":"@balena/jellyfish-core","version":"2.3.2-renovate-non-major-6600b7fa397317fa775bb9921ae25fe1a2b9ae69","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.3.2-renovate-non-major-6600b7fa397317fa775bb9921ae25fe1a2b9ae69","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"bba46430a25a3d74ac15934566f80a276f9a74ab","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.3.2-renovate-non-major-6600b7fa397317fa775bb9921ae25fe1a2b9ae69.tgz","fileCount":98,"integrity":"sha512-qHRCSb9HVw+KUzFIS9KHDUYpderkqWXvPqLa9rHb+ox927U+Nlh7fl5dEo7Ms+udUHo0PgvzjEjq/OFwjNR2xA==","signatures":[{"sig":"MEYCIQCNeamcwCtYm93w4GZ1YhlP2z3pXeR/N39wUBHaP79S6QIhAJ8qz4Hf11vR7jmCKbs4zqR11YpsOY0Ff+vK1iwGlXEh","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":618933,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgPT+uCRA9TVsSAnZWagAAyR8P/2YdsXxxJFqsbctOkj5x\nu80siqEPwE8VBUcLCmBo4u6cIEA5TGHsbdu3JcxXlLmHeJRUkpAhVaQLxXU8\nNz8ELscXR6kjC3YGePWjoMuSFKPJtPSw0ksmUkvMW9RRrZMZ19CxcKdrjxw1\nOqo1GPU12NNZbKbPdnSQUYTT5pl264jr2FTWy6mL6+qaiIi+3fjkNMjyn0F5\nuEmQ2zNUzBYzMrCFB/HtNTG9nNpgof7xxxPLR3ONlB3+ta63D019G6yW+gEF\ngJShPWvg2oHEczV+9I5UQu0bc8B5lMzHpFlulWxYArscawg1fSbEz52GAW8V\njijmMDbhEWOtT9t140NDMsSGch2VHQuzro+vN2PeNbLtXbYh0MGnP31VuHdh\nVCOXsSzv/lDRyB/mPZtOMt4cE2PyLcd+40HUYTxtTXDI4a11WKdtH0/SFzFY\nzoMz5zH8rTVUdqJGliUzrZ6iIlglxfXMtwbtWMShJUIcX34VRNE00g9W34te\nCT1Zu0jshKMcCuOBduoGRXBZAuLDSxfuxfFrXdTsFM0o4CUkFGWrdS3GLP0A\ns345rFxdlZJ+NgvyhDGsSwCDp1fBZu1vCWbTVws6f0gJc21Dq7ZaVb9rIMcK\nKYkRZGRlHtgbmXeBsbclQ7ZC3dkM1KZhz6J44y/6Bvnk+RjNl7IOY2piAtKV\n+f9/\r\n=vK26\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6600b7fa397317fa775bb9921ae25fe1a2b9ae69","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.66","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.61","@balena/jellyfish-logger":"^1.0.40","@balena/jellyfish-metrics":"^0.1.107","@balena/jellyfish-environment":"^2.4.22"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.2","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.109","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.3.2-renovate-non-major-6600b7fa397317fa775bb9921ae25fe1a2b9ae69_1614626734307_0.6570094318054531","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.3.2":{"name":"@balena/jellyfish-core","version":"2.3.2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.3.2","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2014ec99952868ec742ef687aed118f19205ade3","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.3.2.tgz","fileCount":98,"integrity":"sha512-fqyWooMbdXbZ8RH9Xb52osu+LvNrwKmHtQHJxhq9dRpcUk3V+NaQ+gUXovZyzJFxtrFS9xJ7vW2VUjhJtRRAFQ==","signatures":[{"sig":"MEUCIBjj9yyNoATJV9EM7undpnnIEXTDJ5Nm3v/CJKz+bV1tAiEAtPKGyY6ZKLYvLW6NOh9aIZ9NbqH/+cBKkn0HCWg2G+I=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":618873,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgPUELCRA9TVsSAnZWagAA864P/3JApKuGfsjbM7n1RG1W\n4F7wPSDHbPb5Ljx+lAcwy5g6leVTn40RLF7xPmssIbWtMa6wO5LRq04mLjN4\nfOY6hlEM7t3bd7CWe7Fd7BKgpZfFMbvoSgXyvK5PdxkieH3at1jU0DdkW6KZ\nPS/WVQRDh4Z9GTzcqpsjy2GOObWqXqxLV87q/RFPzXHFTU2qd4fsavRUCfuq\nUYuIZnxu0ltwXq6cPxxiXPNdhgsZKEWppkyvSiDCH10IrBJ3fxq/KSZetAcV\nGC4hmizCoISSmpX0j7p+fWTQFKeKkuLGw9Xxsk6OwAx6H0AIeFjaSFzkt5np\nAnr9222YC68cuMH+tr8SPiH0+yXbjKh9sU0Nf48qNflDYLTfD39xS+v7MWxz\niRNHoUnCsvC6BeZ4mCFnXg2xoLQn6aI1jZBSCKlj4r+rKIbXPa+DLxsFj7+R\n82SWPx7nOReXo37xGu23EZdmDKFh7yBwzO3eL/wUkoiBT7rXLK2teXod6tX8\nz1KXIvQ322LJ7h/c8H/oz0QmIlG1PLCFsW5Ba/acJ/ZCr574tVmBlMP0bSY5\nA9pMePxrXSIGr/NvvANwMA9wvPiukEV3MEBMz2Vom6TXvlqVgjcxufJZ76gb\nCraB0Js+bwVf5U/t8KLxxsEnHmAUM2LT7w72rCpB7o+U6/A+QBe3P1Y/rnCJ\nTJTa\r\n=H8Mw\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"084e1002faf8fc4f46b72ea7b1cf10360f3e22f2","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.66","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.61","@balena/jellyfish-logger":"^1.0.40","@balena/jellyfish-metrics":"^0.1.107","@balena/jellyfish-environment":"^2.4.22"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.2","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.109","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.3.2_1614627083379_0.5183385128751092","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.3.3-renovate-non-major-991e70371362ec72be9c43aa68741ff62438edea":{"name":"@balena/jellyfish-core","version":"2.3.3-renovate-non-major-991e70371362ec72be9c43aa68741ff62438edea","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.3.3-renovate-non-major-991e70371362ec72be9c43aa68741ff62438edea","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6ff0b5c56d9ee04ced14e66032c977b6658644a9","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.3.3-renovate-non-major-991e70371362ec72be9c43aa68741ff62438edea.tgz","fileCount":98,"integrity":"sha512-ZZrOcc5iP/ViHsWNLRAxDAi03wl+MTSpxwxr4x8gTFSmhxfT454EbltXCuWbXeAoS9g/lQCpIQhU2PTOsVYkTQ==","signatures":[{"sig":"MEYCIQCAv8p8BtG/st09n1ttNb39c2Yl3XDr2+U0OWdqu1h/gQIhALVbml8mll1tHI8yymINljSrtBFyq6AUL4RpKhYg8Irq","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":619280,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgPb3CCRA9TVsSAnZWagAAbw4P/i5st7gnmG6Ew9zZWm+0\nwLad/GRWXW82IfR+GQ+1MEG92+v0YKQCvbpkwjNUw/N9QXIsC7FKF9ZSZwHk\nu+eNiMcbXiReZ69JhsoxlzcUK1BMJ6rKRvOQDkGyyV+3ThZzeXRIQtT5AYBB\nEmvBWbeWrsJ19Fs/yvDLDbIRk1HrJOvSXhmfftNQbcIQOyI7c9D2z5kAo6L0\njwafPpOQDi2BzRGy4sOLPgiuSZeFkc+umYgEG6KI4vRFWhbMvczIpkbc825N\nOWIZlvyxJFmG+kdoZvGOwdzKpsZbHG7JO+iuobZjueZibEi6IqPD7WNHvsKH\n0Jy7M0+NaRNEuPcY6ovzNvC9xvppfAgTxrl8wiXfQyh/HzxwFuCmd7lMLhL1\n95zgWWDonoTW0gSdGUM+bi0VGrpxEGpbjbiOkJyQ+k/RG7PB8eaYyzSGtfAn\nu3fC6R9joBxLgt1/d/HqLil2u3b3WpUXC/7+sJOTtW8zUFIRMxDzBbTQaWqV\nYc39bb5W46Da8DSbmWs0KLXiO0fRtL5H0ZbcEdBpE1h7ki4xgFd+pn77IMfl\nLe0xXHsfLxRD/tTozLRKSuM6K25uplQ+YGv2llgRk6j4Q5sfnBm1sbhJljHg\nV3rZCebu86sO1jpK2pfh/wVGvFSIyyQKJYr9Sa2dOcGPjjS4T9AClsb56Ing\ndYsN\r\n=Fgbv\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"991e70371362ec72be9c43aa68741ff62438edea","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.67","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.62","@balena/jellyfish-logger":"^1.0.42","@balena/jellyfish-metrics":"^0.1.107","@balena/jellyfish-environment":"^2.4.23"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.109","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.3.3-renovate-non-major-991e70371362ec72be9c43aa68741ff62438edea_1614659010038_0.9040373604104437","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.3.3":{"name":"@balena/jellyfish-core","version":"2.3.3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.3.3","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ebfd1c5448d7a8ab10b7eb702b0fb45229bb644c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.3.3.tgz","fileCount":98,"integrity":"sha512-2Mk8oc2v3lPwGqlINBONK0JVpiq8RdSUU/FPI8zhLhmbuU6PiCIuM9L+lye7wfewkiADWPY+mVfy+uKwV59ugA==","signatures":[{"sig":"MEUCIG7mKtTCXP+RQibN80z3ChUSzMw4vIPVXj1ZszJIQMJZAiEA7fOwrPXWv/rFghNacnZBLqeHZEVwiH/IyJ0yhSCn8Zg=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":619220,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgPb70CRA9TVsSAnZWagAAF3kP/jVFt6Diyr73uWWuxaii\nEvtB4ki5aFgyJanQ+0+bo3jQVP1nkBd/ydT2Pf7wejV24HR/urBybqooLcK5\n8I/NAYsW4Pk87JFe8wYDFWJZm4aHuHg817Fd4uCBlWVGZrhSO1vYiNt5W5ix\njvHQD0dwgNsSLtQ6Q9NEtbDkYdn6ZFZ0xvQTOU2vX9mj2poaR8ck7wlljwQk\nihcbWtZw2X2aEbgfBsIinGYQI3OKksVuWmXB7wP7HVWrUCJjlfsmE2jOTrIx\nkU8f2w89DyRGuEqsgFmK5grobMQrjvD/vIuVPVYogeKpYcwbNrayPeCCnJ1S\nMvcXUNGPvyfDiP856XyoRKn5KsCE/a4N0bdjyu7uzpbs1NPIOucwE1ODAh5o\nFfqulKWycj/Xos7Psg28nrIJewstIozZ+c/rQaIFcQMbb7MFQD6btg/HtBJE\nxvasqGr/XfyfD0kW0PNObh0Rc+evVF8YOVqpEDNvbuCP7/IcbBZaWERTrh8t\nNmX2G7AIrsVAhhKJ31t6i9vcaz4dRwrLhQENkDbVsCvKX8AOieqFH2EsBHU6\ncUmmflshUSzUCMaEph4NkncPzJB2BeusGOdToIPf+quRGQ1cpb2peOpoChi/\nsMY30ixHDROZoZ5hQd/XuPy46RApswihkMg1TJ/WPpaQgRVQsw+Q2+kdluI+\nDV44\r\n=Bm8R\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"265f9fb626250a243db65d4c7c8814b19f086e60","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.67","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.62","@balena/jellyfish-logger":"^1.0.42","@balena/jellyfish-metrics":"^0.1.107","@balena/jellyfish-environment":"^2.4.23"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.109","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.3.3_1614659315752_0.3437257784107166","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.3.4-renovate-non-major-09cdf8ec82c144c799ff7a5dffbbab23dcaa62d1":{"name":"@balena/jellyfish-core","version":"2.3.4-renovate-non-major-09cdf8ec82c144c799ff7a5dffbbab23dcaa62d1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.3.4-renovate-non-major-09cdf8ec82c144c799ff7a5dffbbab23dcaa62d1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ff1c013bae74056f40e34fa8fe14fbd5305620e4","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.3.4-renovate-non-major-09cdf8ec82c144c799ff7a5dffbbab23dcaa62d1.tgz","fileCount":98,"integrity":"sha512-f/UA9AZMpyxNAyXPDciY4r4OR2V0G8/TVkKt0++3V1A7wr7NqRuVvvk+RymsQmHxAgzqpOOzDSKYOT9ZX9RS6g==","signatures":[{"sig":"MEYCIQCRRHFZYHhXPclHfp2+BoFCXkwovU4UV4OpyN9HzozI6QIhAJqyyNw/L04vEkRh0mgCPV8uewTxkCEXZRpLpE7tKtoJ","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":619705,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgPgb1CRA9TVsSAnZWagAAfUMP/jggDlnWElMqFXho62ww\n3P5kTBEbBJipk2AVYznjLa5OflpbDcvb9a+uICAdGc3edwwD3rADyNfB2NCt\nbgOq2Kccu2C86fKiLW1KdCmvW1HzLdX5Xx4A1eKKs7voq81D1D8IFEihzvUR\nf29PkgTOVKaI94wZggij3a3CxgjnVEOXWNAjRTVUnFCjjC1vv3xof9hcSENk\nc6ZMtg2JZvFUtBhCiIOgaAxfIoxJtWC4bVQUPLbZ3NUtRAfjRyGBzeTmNzjJ\n/UakntjCApuv1xhdCR3ZMLSFuJyLysYYf9JXRgP7T9sNzwjPY3YsBEzHyA6P\nnttA9vCF+XFccvuCM4ieKL3aNHXbucMMMVbR3zX7lgqEszYRNP4IdYKfVVhE\n9mpV99FZN9Bk2Xz7pkWqBp6psd31BJotTynBPZad8+jPLDO1XkD5S5enRH30\nBvO0nXzbPikW0B5az8BPo6OAVeRfzaYyZ4mqa4TLRPbkwjswrGRKGEBMSQva\n8DHrtC8M04OpInV/r4Ad4A+KCdjqXcXy+cYreVhASR/ohMbr2JTXQWxtUiAd\ncb4wiDJU8PoArTu4ctFzYSVmM8qnAYffwi4mPDW75sod/qwqV8kobtjhKOPG\nZrlGm9sQi2Wqj9bWBLOJLP4mkAn0BUaIcW0TedBkXfILig1JgMguJvvsEh8M\nM46o\r\n=9AQE\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"09cdf8ec82c144c799ff7a5dffbbab23dcaa62d1","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.67","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.62","@balena/jellyfish-logger":"^1.0.42","@balena/jellyfish-metrics":"^0.1.108","@balena/jellyfish-environment":"^2.4.23"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.109","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.3.4-renovate-non-major-09cdf8ec82c144c799ff7a5dffbbab23dcaa62d1_1614677748826_0.4250001072638112","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.3.4":{"name":"@balena/jellyfish-core","version":"2.3.4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.3.4","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"00af78328d5e596ff58bb54b0397b8e38b7473be","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.3.4.tgz","fileCount":98,"integrity":"sha512-ZqSBL6ZNnV4V9bHbwSxot0Ni6nr4JVxhvC+B/74qkiISPw2kdvCXyQVUnbEztiiCbuDbrcxZxqjOtV6mK1yy4w==","signatures":[{"sig":"MEYCIQDJHJGS/dHQFm5qaDmHnoWBAxe/A3AnoG2O1299SkLhhAIhAK1NtB9LAFliMorT/wF6vCj/tuC5kqJBitpK0b9PGD7d","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":619645,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgPghrCRA9TVsSAnZWagAAcO8P/j1DBegif7ASZXzRdMSL\nc/vaZU+C4eyFgWtAVIxTvFHIDl9Vq4p7f4FasZGmlHb9AsrOl0M9cfgtSBkX\ngHF3ibhh0tzq/p8eI8AvdApnXHlHM09hhGan8OzS/rG6SeH2EL+JEP+A8Elk\n1L7Sz/UDHszOo4T5BJFw86I0YeXFwc6P4gaPG+6I70n9WDg++63TeIRREuNJ\ntxEp7wT2aG+T22isrP0krihskynd84RU86GRjs1imuJ51KGsf3jpYnaEA8Q2\nH6X0EFpPSOnAhKMFQPm/n7dRdm8EdpvSBMx+3im7pTFEWuKx2gTgQVxgS/+m\nKmsXkc+Kz9kBfkavuEAtAoy5mcYzztvrW9XnYfWvHHnr5iHkSf6idYVv9jQR\nbvlJGtfB5e4OKcEXrHyplTyk1RZ2Aqjl6KfKzkvMcqR1B7zuFwZD55sQ5yZh\nZKe9LtPUA3hdnCVt9nMKIOjyxCQLRwowd1/y3aDRgnPHwWGfH+tSOoFpDZ3J\nUF/22gIF7O+5f+yFICmN9gPjdof1vNIVf14MahfGjbQPe+13N12TSe0XNh/s\nWACaqoHzWhIUbdBVH34HDybrECyhI8L/r7DkmM42tBH7ySoFQosVit73xkuB\nxFi8Rz//A0CZNAQ/uxgElYOp9WP1UcVQIxYM1H4WK+Fmzn9GThaySus5Qgx0\ntBI4\r\n=zNec\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e4e8f585adac405cc15c91a44479b9ca2b4cb928","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.67","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.62","@balena/jellyfish-logger":"^1.0.42","@balena/jellyfish-metrics":"^0.1.108","@balena/jellyfish-environment":"^2.4.23"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.109","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.3.4_1614678123506_0.5183936141771117","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.4.0-5508-pre-build-versions-d2eed7c182d4c3d1f8bb76cc39c1d7dc00924143":{"name":"@balena/jellyfish-core","version":"2.4.0-5508-pre-build-versions-d2eed7c182d4c3d1f8bb76cc39c1d7dc00924143","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.4.0-5508-pre-build-versions-d2eed7c182d4c3d1f8bb76cc39c1d7dc00924143","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2af98463e6424993e73f3ab869234cd734a587a9","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.4.0-5508-pre-build-versions-d2eed7c182d4c3d1f8bb76cc39c1d7dc00924143.tgz","fileCount":98,"integrity":"sha512-NJHMr1pv5SZfNYy4AYzSguJF3Zb0cnscwRviFj0f0BRe5S2hAfKDlJgFiRjqn8+X+R3VIEBk+o8iqvVjpmzg1Q==","signatures":[{"sig":"MEUCIQCYj4RfEle/+16KWulc+MEEQaIWtoaVF2nrY5pFst6p+QIgVe16970oXeqiK5muqc0EqOTGlBPBPCTHOzZATl2rrM4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":625783,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgPk+8CRA9TVsSAnZWagAAixoP/RPT1V8ne/cM5WbqE0Qt\nfqvUpdcMDjAlMunJxRCnD1LDcCqWT/7X8sgQBxZdX1U9+1J/CuZA+/KEHdWc\nc4cPKsEV0kJcqpIf4oqT5fXUH/fyGeo57hzsX0Y57WLiVYn6/MXL6WLluNlq\n7BDWGU5aV01Q75h7qclurZ+aJY9c0rRYvzMTXffu2xDa/bWvQDZOjkp5Bfv2\nA+gQf01nlAZ1G34QaA43T687PLOTcjIgY8J3KfS1n8oRxJsrzaLjj9eTeRhx\nBJwGZTLyeT/3zqSAPi79D6F8trfWEGe5NtmBs1pOcwucMc8a4LPlfsgmIVZd\nA8dyj7m3qVeuhrz2HLHeA9aykGXWrvYcwJBNyXEItO0Xuoed/LaIp9DjFmLB\nnqvPeFFZtqrFjl8rR73PEGZuK9wVVJyX4KzWCiRniKMI2+nUxpdv41UDwwpK\npfdjGxGal/kfnEk3tCf8xlSpan/b2hboJfxym1TdGQWWxlJVSPq0yCCwR5Pr\n1QJuUIhavQ+zowDwQgl+n6NDoVIZchr3qPvhxxCMOcuXYko5KpzTLg6qLg4b\n5usKPPUnVbLttfjFa4dpyr9HN1D/Oqsv0KMJ56AsoqgqYlW+ME76umOEvSKn\nRup2dhDLuu0ffk8pUANWbA+u6Z7Fxb2NOEp+rr8hlH1jk+3dJ9guYEuikyJ3\nITS2\r\n=bG7y\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d2eed7c182d4c3d1f8bb76cc39c1d7dc00924143","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.67","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.62","@balena/jellyfish-logger":"^1.0.42","@balena/jellyfish-metrics":"^0.1.107","@balena/jellyfish-environment":"^2.4.23"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.109","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.4.0-5508-pre-build-versions-d2eed7c182d4c3d1f8bb76cc39c1d7dc00924143_1614696380163_0.581855226085509","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.4.0-5508-pre-build-versions-26fafe7cf76bb822efe0b733c40dc337ee3e628f":{"name":"@balena/jellyfish-core","version":"2.4.0-5508-pre-build-versions-26fafe7cf76bb822efe0b733c40dc337ee3e628f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.4.0-5508-pre-build-versions-26fafe7cf76bb822efe0b733c40dc337ee3e628f","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"7a9e4a1c5ff5a572546f8c22bd4ddc41d5e37a72","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.4.0-5508-pre-build-versions-26fafe7cf76bb822efe0b733c40dc337ee3e628f.tgz","fileCount":98,"integrity":"sha512-YKGjQwapZmn6VEM5sRlBpMpOltVP1+bmc68TYBzf4N3zl9isMKLha6/0qrHj2ZF+KRnWuVXY3E9Vn5ZDXhAa6w==","signatures":[{"sig":"MEYCIQCWpZe5SWc9bE4vWvM+aYtbtKJ4q+vB7LaWasCYqnvQLgIhANVUc8ln4DCmOT9R+FPdMcZnhaqLml+ubh+vsAtuYCCx","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":625344,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgPlMhCRA9TVsSAnZWagAA/uUP/1AWAUMRHf9hxafybXXx\nb0rxnVUIogbELK8J0fW7zwnFzGppw0ueLirQBxYCQdmiS9fQuJTwD7lI/2Y/\nx0IIYqTeZO658RyfE/NHZRS/M6/jckITG6cXv296KrOllsi8hKA+VNZ1WRo+\nZjS7QUc+NjGq+1dvQYKAHuvQrMq0nxfpK9FCDwBpGVMO0m8tXrP8F7OrjmUK\nnu9tE2WBDV8qqWOj43e4bBfAJ6iKcnI9evPBHpfo7D6y6J2woQWbECeA2ySJ\ng8bYS8yVdorRMzoi113EeTVD52oeXQZYo7+ZUwVOXqCQV71ro79gJiS7pY+m\ncMjb1QhqKYQkjQQ30YVxxkOxCov1cmOWlfYh1droB3I6VQETEVbiM9noqHJt\njFe+Oj1V0EBgLT6T4hOKNWfj1rARxEOCmoipTBvI0QWkO3NGGr1ssM79pRuU\nWrDD6bZ2hCV/7/dlhneBVsI80MqrRLn8eBnGWB94iL5ekyTKLUtIw2JAz48t\nA3M/jRA0Ecejpuui+1uwxBEPBjv9s+pi2Ztg/hkK1MtkwOgqVWjliFlNWJqh\n55UeYPRKR/9w7nUjUc87qf1bAWIZbmhi52yjRr9nG8OVxZU+DgERi6e1CS4y\nT9wdldI2g77G++BulpKiCm6gPogkKXjDEmHqXJ3YUJl06nuNYUpK47RVh/oF\nPW43\r\n=CW0n\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"26fafe7cf76bb822efe0b733c40dc337ee3e628f","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.67","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.62","@balena/jellyfish-logger":"^1.0.42","@balena/jellyfish-metrics":"^0.1.108","@balena/jellyfish-environment":"^2.4.23"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.109","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.4.0-5508-pre-build-versions-26fafe7cf76bb822efe0b733c40dc337ee3e628f_1614697248615_0.755870968614708","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.4.0-5508-pre-build-versions-370416e7e775acd5445c12b4014821d16abe7f6a":{"name":"@balena/jellyfish-core","version":"2.4.0-5508-pre-build-versions-370416e7e775acd5445c12b4014821d16abe7f6a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.4.0-5508-pre-build-versions-370416e7e775acd5445c12b4014821d16abe7f6a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0332e839a7742385cd5b2b215e803be4e1be47b4","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.4.0-5508-pre-build-versions-370416e7e775acd5445c12b4014821d16abe7f6a.tgz","fileCount":98,"integrity":"sha512-xS4mMMQ36/Ge825LpdwcIk5mPibgOP472V6u52Q0/3lIKe0JA2leClmd+GnTPhJJTohgkJXIwbfiqfdJvEEjog==","signatures":[{"sig":"MEQCIAszFiH2IEmL2bSLnKhwH7AFC0U/77Q95LviEKmf9eFjAiB2WkchpIPrUXkra5nRWaznJMxqLBGuSghpAmLzJ1dRNg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":625305,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgPlfCCRA9TVsSAnZWagAAlEAQAIdjLFa8VqAAGfiu+dJD\nA/Cu2mHDQ387nFrdRPFqSDMj0jdaclkcEDOjSnPE0SeSPBSeEPf5x4lYtXnF\nWo4ZOI6349GXORjcQUhBeHyItjVAB+IgerIubE6COzqw4+KOpnUIktkPKfSt\nn9Az6QQV6BwJY0vX0mRbzjwA/CLmQTaQ8/yTesj/32MUaGO2d919q18tQ+pV\njz3LYKO7zEQVpQIw2Yrmg6TGOj3sGt1cpWEt2PbMVmWU4jtrblmJKxDSjF1t\nN+/h7w1sMIVNvBh46k+D2KrHRv79FYFHsD2yonzVdvt9BkSxsDN6KFtLPdE1\ngevX1BNs55fLB0EUvqjUwjM7a7tOgpbO1PNQExmXcdwZ6VkmARCt+e7Kg81z\nx0nU7j9aKYJdomRcTPBaKo4uVQ/ukyU/5AcgGqrVnBaDtQieM9tXifrIA34V\nUc7f/3RLyWUXqQV/4sJ8+a2GpayIw9mfuWFVY9mrAHP7BJGxWQWjMQVXOAqL\nQg8r7jdpeGOQkeftrlI8FHpeFa10WPSGn4qLoqGq5kDKr8IllKd4jvKzqdF0\nfJ5MWl9exiORAvE5DJf+3T52QDddM63MXbBPhUZ9EqlxccVAdU/c1HHYoyyb\n3ir6AZTXdq04F67IDRP/QoG76IzvIoS+dCgyuMXbkX5Vqnk5FsMRjTB4OiaD\nCndW\r\n=m2LE\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"370416e7e775acd5445c12b4014821d16abe7f6a","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.67","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.62","@balena/jellyfish-logger":"^1.0.42","@balena/jellyfish-metrics":"^0.1.108","@balena/jellyfish-environment":"^2.4.23"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.109","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.4.0-5508-pre-build-versions-370416e7e775acd5445c12b4014821d16abe7f6a_1614698433337_0.912742711586445","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.3.5-hotfix-test-error-959a174eb83cefb31fa7b38ab36ed180e8225787":{"name":"@balena/jellyfish-core","version":"2.3.5-hotfix-test-error-959a174eb83cefb31fa7b38ab36ed180e8225787","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.3.5-hotfix-test-error-959a174eb83cefb31fa7b38ab36ed180e8225787","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3b843c2a81dbab1e2167589eaa749a224ddfd76f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.3.5-hotfix-test-error-959a174eb83cefb31fa7b38ab36ed180e8225787.tgz","fileCount":98,"integrity":"sha512-ZTiM/zVnGLiSK/I1CCY4HGzrv4P4UJslv0td0WMnJ+EjydUbErBVTKOHkUsKw8xJZ24YIi1Cimjpcx65TO+cFA==","signatures":[{"sig":"MEUCIQDXtEcfcpV9QjJXkiS8WsHz5RU5xMmM+iKxhAGN3aetpwIgN5f7w8wWTAcj89yHvOVTCBoxzALAPwn+l/dzMvjG9P8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":620192,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgPmEPCRA9TVsSAnZWagAARGUQAJFeV96aQx5NpzeUAKdc\nP0CK8tX7usMMmkJFoXA6xvAPTzH7orJlXR8cAfSKFfKaWOWKDxE2OPNTSR+H\nS1yeCDtACq5cgZg6Tm71v8WeTChJwAiwTbeXMm0Js5r7G5vOXDSC9Rnr2YKe\n1PBp9OJT3irejc1lDmxitp5gSzmVwdpWYT9nSGFHqbTbrNMrfSZ+WJPnJ33m\n3dDo/1JFBNMd8STz0HUXN+WOhFwfJGACsQKVwa13a3MNrEPTlhUt2bINAvjn\nuTN8ClIGAmqq2gU3CHiaVGdNxsvF28FHkKttwCwq4qp0Li0jiwtp4v2rGP48\nceJ5nQ4aoT8QqfP6t+pkLgxqLJFLlD0NlZkHTNclOI6hThRKYkJjupZvo+nZ\nqzTXxkQz8bn2X0PjoFyfO59Kahpe8S/vNA2GJvzLuVWIamOm/HdQtmCq9yJb\nztZAqv0Ieq4GrxFQyW6Q2KQ+omcqFJQBKInIxAqfhFiUNlO3OmVGTBejQ9vG\nSZcbA44hAlicEUdQALjkWCVGezW/W/BAy5DYi2iyX5vKSWOSjd64xX6QrE5+\npXyrs27Kcots+VioKhiyLxSvaOuQ+V868EwWJxGKMxojNLyd1+WoTuKshtFH\nsKpt4GiN4nkmAMHzGZLAeBPT2I+eSiz2EYtyJsyMe/zmb1F1avr8zmAKbXCc\n0qvi\r\n=+iT4\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"959a174eb83cefb31fa7b38ab36ed180e8225787","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.67","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.62","@balena/jellyfish-logger":"^1.0.42","@balena/jellyfish-metrics":"^0.1.108","@balena/jellyfish-environment":"^2.4.23"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.109","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.3.5-hotfix-test-error-959a174eb83cefb31fa7b38ab36ed180e8225787_1614700815024_0.009326989582077783","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.3.5":{"name":"@balena/jellyfish-core","version":"2.3.5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.3.5","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a037c9dcda53acc3dbacda31a5b777e50a7e494e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.3.5.tgz","fileCount":98,"integrity":"sha512-EVazw/nu/qWt5QkXbLVbzaG6rSPBvHyuVfGmWNyMTPaSlqrFcFCMi9z3BXPqHqgZPvUaQdrDB0xw5f8SNUGc/A==","signatures":[{"sig":"MEUCIB6/LRatHGjXVAE0ty3omYh4x1xs3EwjnB9SUU2b23G/AiEAsUwhXGxGidOr2GVtgr/giZeeM1wjhyypkLvqW5qtJs4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":620133,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgPmOWCRA9TVsSAnZWagAAGIgP/0ojoEcUtz2i+xYHYEjb\nrS+53G/tzVdR03LDo7m+tS4DznPuz3jM+uWY8zue5gdWNZSZ8pwQ96MgaLZC\neS+MS43jcDjCia5M0WyMlW57ytaigVc5NaFE6xejvt6h4fHPoHxSoOrkVHCG\nwn6V9tGmRhVIk2JgIxo4T5S4NOX5JSfHHggAUdMBQk4RKag6lgjsBXmHRA1y\neuEP49TZC+BWvrn0OJ75Dc5tgPGff8IC/F3nxSXMZIVRlanrJcb4EYooVSTr\nOeSmXfYYGBMGhxqFGNKAqo3pSLsQ6tA8l5UY+QITb7l0IcHjuS2pGQ5LpNVq\nc69i7Y0Ppwy6i04/PnYCY6YJtZl/jsVvQVp5C8lvVn5KQC9HVemdlZxqzZ2f\nvz31DJjwBQvbm0nJZqVBrFiA645fTMMgLdGB9YYJYTyXlJ1jV5vICQCEDnh9\nGCrJnASUVvy85cK01XFTmR8cE9AY10Uw1W/FiaFN1cfP67aPyRwZvw7GeIgm\npdGZMjlUUKavP5HYZfvjNiqFgVvO97LTjzoyPgR4bJ17TgGrpn/rtoHewoUW\n2seZv7awbpgpj45ein6CT05A8j2qesUml5othnbo4nc8uuPVdu2pL8+aURZ0\nKcOf1pSEGc/XBwasn89Zcn1iHler4mV21oxtHqqjvMjC/p1IBy7/Kh4cM7SJ\nkKRL\r\n=xXX1\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"9dc9c0b580d9935be2a283a82668679a58c08eb6","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.67","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.62","@balena/jellyfish-logger":"^1.0.42","@balena/jellyfish-metrics":"^0.1.108","@balena/jellyfish-environment":"^2.4.23"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.109","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.3.5_1614701462372_0.01582878423456968","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.4.0-reinstate-user-slug-index-0d3c229927a1c418e89ae134fb18d0c62ed16927":{"name":"@balena/jellyfish-core","version":"2.4.0-reinstate-user-slug-index-0d3c229927a1c418e89ae134fb18d0c62ed16927","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.4.0-reinstate-user-slug-index-0d3c229927a1c418e89ae134fb18d0c62ed16927","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f8d3108cd95a4b6af21eb58162d4a93a867f64f6","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.4.0-reinstate-user-slug-index-0d3c229927a1c418e89ae134fb18d0c62ed16927.tgz","fileCount":98,"integrity":"sha512-fUYSwos3Hf6g5/TtMhqJVzuCXL6IVdFmveWFznkUq80+3CYvQYQqCAQ7+0kGuK+gNDhzi9Ygy28tCG1HSDUbYQ==","signatures":[{"sig":"MEUCIHueedm+KBpfem3dELMFijM6Vrk3QjwWLS0jMJZoiCAbAiEA9EPdSaVzD7HDIKg9s6ZSDFMCMOnUv9TdEbERPT+FF/E=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":620885,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgPvqwCRA9TVsSAnZWagAAGs4P/123byEHblV4KzUn0qP+\nJO+zdpMoXGFnod24PZ53jw+ggc5LAtFgQdVT19gfrlwkVl1E2ksdBa3CFoyK\nmnNoBP2DxIicp6/O9iAecwuJy47i5dB1KcEaRD2LE9Z4lrYZ/OD+oKsj/tMK\nBvY5ysMeIdkCdatTJtwHZSpB26V8X0fjhlIbZLnvkO97nAkJLgJdha448rzk\n2j6CeLpHCfmaskFHX5m5ad70k+yofFazaK8BeBdiJpVzzcZy4AlVuLVfbW45\nCtXjKf5VMWzUa7FmvrFS5F9uWsw+9d+grbg2qUrzauRBnnxKP5lrTQWbwOKh\njCX9xbNxIIGkulrrPc+4Vf8msANJayEGkHKkZL4OF/WAt+q4BQeAHpdc2uX9\n72AgIWegyAGrbQXPVusMITomNEQ27CP963yB82NJkCKzERTaH9n/13qFyrKQ\n8EeF+0XpfUNCVzwP3Pc4DSRUdVVoX40X4Py+sVDca3EjbjsFZib9IK2PX6nm\nto/c7ZspXhx/JQIhMThFNppNknQZAxtFsx2zhZfGFRblB7ud1BTPDGwXulgb\nW7F1geb3n3Fy4a4eVgc2Hezo8t+1vJS4ZhaIs7frc3KXIEzfZAT1nIY2R807\ni0xgrO82lknzTohS6/m9g0Ryf1XXWCEqBr9Z4UpbvF7Dwz4KpXu8MoYaa05K\nQK0k\r\n=Ebpf\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0d3c229927a1c418e89ae134fb18d0c62ed16927","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.67","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.62","@balena/jellyfish-logger":"^1.0.42","@balena/jellyfish-metrics":"^0.1.108","@balena/jellyfish-environment":"^2.4.23"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.109","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.4.0-reinstate-user-slug-index-0d3c229927a1c418e89ae134fb18d0c62ed16927_1614740144140_0.32412489350816975","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.4.0":{"name":"@balena/jellyfish-core","version":"2.4.0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.4.0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c51463802d776520028a72959151c7bcabe16547","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.4.0.tgz","fileCount":98,"integrity":"sha512-mtflgSQdWyutLsPTXRcDb+2GByzXxhMf3RCIVWUPG6ZLp5HUuhLHs/MCpEu+ZuujxseRRTFGFCUhqUDBfaHeMA==","signatures":[{"sig":"MEUCIQCxrk593J8nd8En9isOBowYA8xzhNNGWgzpQB0dL8yZTQIgD5f8770uO5B1UlAX/6g4wLWJ+S1Epz2LHUxFE1vTVe0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":620818,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgPvwYCRA9TVsSAnZWagAAl6wP/ininV2ocQGb4BQ/YuGc\ne5fLKNUhzI89arUe4nlJSRHpklu6xxDoP2haeWp0h6gTQe9sdaiPKPs2MXs3\n+vluEieXk+Pyx2ZmYSuWtwkegLVdXNxt37HyjG88kzNQUl5PGcJ1CZD7EiSB\nlvIOJczj3JGTUCL+jxOcHLka5u54h1UkgyB1dX/H9xND3pplwtUAPupmr227\nttbf5TAf/rMcyMuB+DFK6j4Gepo+r9WhH9HAiSLhzF/U7T16esrT8qxo69AR\nxbv+b1ang8Xms3EYVwHCTNE0wApg7m/nk7GvWzSEH9hp6tY3fT+1dzErVNvU\n5IHtsMq6PWyN3TfpsAPI2HPp4lCjTi4Qd3Nd7tU8ITb8Kx9j0vaAjcm7sJ/i\n1/6dGeTx+FwXHZhGSfRn67UfNz6bF36zqKYAXT6WLvZYtFI9Mw8I88271tyR\n5Y+CHIFIQ35socMYfdXFOEZ9tj4iEeoFaCWs9uHV89sjQOBV8494W2rIo8hh\nq0kzQH/gMeraMT4swmwms/LHi+k8n93gU2IbZW+cFDJlD3LdowMWaTCWslIT\nJh5y8LUAk7jiMQyfprlIdfNmCZsOI3dkPx78iaGakZVG7XNRvwAOBeQcXvfy\nahq/bDYMLq0drBHfCVuqFvVbebbfkyyhtXQz+KxfehBBt17cNKZ0DJBcOZY5\ntvDL\r\n=0iKP\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e077cb37995a847231c331970d2f95c9de922108","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.67","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.62","@balena/jellyfish-logger":"^1.0.42","@balena/jellyfish-metrics":"^0.1.108","@balena/jellyfish-environment":"^2.4.23"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.109","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.4.0_1614740504128_0.2205875424374346","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.5.0-generic-oauth-0ed63c3b0373cabf6b2f138900883756a89dfa0a":{"name":"@balena/jellyfish-core","version":"2.5.0-generic-oauth-0ed63c3b0373cabf6b2f138900883756a89dfa0a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.5.0-generic-oauth-0ed63c3b0373cabf6b2f138900883756a89dfa0a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1826f5d2d895e3f4c6d5cc4fba7a5bacd742ad6c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.5.0-generic-oauth-0ed63c3b0373cabf6b2f138900883756a89dfa0a.tgz","fileCount":100,"integrity":"sha512-AvUH7Pvo3Qwu/9ZX5UghnQhSQHwkvNxl4gEXMOw51JE+reFzI41D5NQss/eVT2xBZjw37qeqNaUdmmhnXGpSjw==","signatures":[{"sig":"MEQCIDrBwj9gFxNkPo4+cKwbKBc/PoT8+wX0NMl+8g5BXRK/AiAn761/KOWCIxwU/EXo7J0K3ONlBdM2E97vev54/0IU/Q==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":623678,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgQBYfCRA9TVsSAnZWagAA6t8P/3/D77muxh1Aa5UnI1mX\n/Ur6uAxrPndyqkJsAO8BBrNxnrVCh2qa/cW83OtP2/lnqF8S0Eegv/mpqqbC\no6uz7k8D/TO7Xws8GFwXTSWy54/g7m9KjzsQdkQSKeha9tNgdsP2pR9Nagfp\n/chNMtjWDNG8pXXbR8YVyoM7nQ5oOP4MFMka2DqQKcKmm+DM5AdjbtFN1kno\nCwxjVRh6MQBn5TE3R5oa7iAk66oP76M668OlblCSx7LrSVk7IqgxVw/ETUbO\nF4ioK6gBRCMG6KwyUuta6VrbmK7PzApIx1SU6HKlbBXcgfS0kpiQfZvqEyfo\nNmQbdfgHi3m46CRf0dTLogI9gNnLSwTSg6KDytpmZi8Egu4NJ7L0N1lOCplf\nQKQFt6OfZyR57lTjDE3xy+v0TxtItU8vrIsKzGYHjVOm5MiR0HMew5GTMgmd\nsJ6zVrpp4A8oPPmiRgNlAh4Pgvw6G80w6+3fBdt5D581fCCnF5bMvd+R+djp\nfT1kCJRFrbG2d+/Kd8hSd4vSo0ne/AStaDCND5wAXC3N4mjbKTp/U/AahA3c\nhCuXuNHqJP77qeR600R+rFCmTWdRbTKs+3NMqwmQNY2o8Ny2wvP+jDawVJ6+\nM2fX1cN1AwMx61z29IfXhdwA1Sjc37hqFU/pOXhWesas2kwjEoLa9o/GydFK\n++/u\r\n=3da9\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0ed63c3b0373cabf6b2f138900883756a89dfa0a","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.67","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.62","@balena/jellyfish-logger":"^1.0.42","@balena/jellyfish-metrics":"^0.1.108","@balena/jellyfish-environment":"^2.4.23"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.109","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.5.0-generic-oauth-0ed63c3b0373cabf6b2f138900883756a89dfa0a_1614812702479_0.02482324830537319","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.5.0-generic-oauth-188d212d7367f1e703e284193f04c6b0e7872240":{"name":"@balena/jellyfish-core","version":"2.5.0-generic-oauth-188d212d7367f1e703e284193f04c6b0e7872240","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.5.0-generic-oauth-188d212d7367f1e703e284193f04c6b0e7872240","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f1b8fec015d6a6c616b8cd45e9f38f393c9c5089","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.5.0-generic-oauth-188d212d7367f1e703e284193f04c6b0e7872240.tgz","fileCount":100,"integrity":"sha512-EPq2w5qfn9GTv//lCipToefPSfS4bX7CMNQ5Ys19RMqqSCoUUpuNBErGrY5Dt9ztkar5OMyUmVunygpTnSk+gQ==","signatures":[{"sig":"MEQCIFKmam0uYeoxhqsKeNuBgjmOc2IO8yFLWKYeizRlOeDnAiBT7QEPwop+e6yhMqD1UaFfFfn08m+EVfLltqlXfaFxrw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":623458,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgQBiGCRA9TVsSAnZWagAAiN0P/1C8p0hYkDjL6XynIHMP\nR3oOc1xkmgMCtu6kcegtNzFqW5+rVrcC9NsH/7jHTc4mcpjclaZuXAwag85X\n6Va1004XlurSbTO9PyTnA1FI4AUHKn7sEC5/CsopnynxNv5t8LusxKUlN7gc\niD28Ryo4na2yoBTfPnc7fQZ/ha53nV42U8RcvBewkZYlhNPfXwCqtQeBhXeo\n+lAmLSgiofEvCjmLULUdLldF2YwM7nJBK55XSrkVsvhZCwn3/Ohfairvi37f\nuvCvRTM8yumLY8rkv2b9W27mxtbFseOPnaNskcECag+DoiojNOHXounDzbe1\n5anlAnl6Co048ubihc/JyRMVHzwDbd28uOAzJ3O+tOtTFAQC6MAAOynZWrXq\nczSLNGLDm/qxvOdLgm8QLEoqkyxJT5e/yb338kwNAQclLFoRZEPiAOHk8w7q\nQZEhtfFuLAotLAeftEnJX6yADdM06AA5UsdzQaXJvhcd5rKH0W1uhuRlHIHd\nlomNq9DigPg9n+9mgEOyaiN0kKpW+km0DzEmuG0xuqMZfSGolpwzFn4QaAnb\nbRpnfMGcfMZRxfOd/aRqcXYDd5c+Ta/MZre7d3V6mKjNApoMN9T52axqIM5e\nqkt5Lw7Dhhrv/Q11vxqKE6iq1Xx0nJuspe6ks51+TWK70eC6XbeveAfPEbOG\n51mh\r\n=kP7f\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"188d212d7367f1e703e284193f04c6b0e7872240","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.67","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.62","@balena/jellyfish-logger":"^1.0.42","@balena/jellyfish-metrics":"^0.1.108","@balena/jellyfish-environment":"^2.4.23"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.109","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.5.0-generic-oauth-188d212d7367f1e703e284193f04c6b0e7872240_1614813317708_0.14849348869978085","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.5.0-5508-pre-build-versions-a94cbb1bfaab74b543ec4418229a5818763a2c87":{"name":"@balena/jellyfish-core","version":"2.5.0-5508-pre-build-versions-a94cbb1bfaab74b543ec4418229a5818763a2c87","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.5.0-5508-pre-build-versions-a94cbb1bfaab74b543ec4418229a5818763a2c87","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"80fa2d742da9360e473a3028c956373f2c4262f9","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.5.0-5508-pre-build-versions-a94cbb1bfaab74b543ec4418229a5818763a2c87.tgz","fileCount":98,"integrity":"sha512-ZcQx3smRq/VUjyDQhHOwNqiIaP+N7HN6Z0AD5suO14XLmJkySh5+KCmQ/1TJi9k7R8YPgquiOwkg6bHxigD2kw==","signatures":[{"sig":"MEYCIQC15yPr6YHEdTgerXXETb9qRq1WoqFjkxdJ/TtDTfqmTgIhAKNZSxkTraTJZgCzb1E9JlMzr2RqAbgkDAO8s1Uh49gQ","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":626478,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgQB1fCRA9TVsSAnZWagAA+pUP/RSKH8CbH4Q3UXdIB3ft\nd1OvtFra/5Ll7BcVcf93RFE9ltbbRcfP+wg6ZT6oHj2epq9Cs4pAhf3PcJh1\nlOdU5zwTN5jkf468WCTlUsJVnTfAGDGLQQJkDu3+nWZBjMRUjq76Bze5jBND\nYCNJwpQ5Ix1ER5GhiZbE//DupelUav+F+t1WXm0Gb7GnVxZNH9NuTh4jEfoX\nIk5xn5VJO905PEHHcTj+S2dPI8XpJP4JtIdmdeikjEpJQUU7tQluwWxtd/cO\nTFsjyixLzaGmA/OmrDq/GI8H2Y/h+lJntHmbPyhaG4WRr3MPUH2erfvfeRwp\n5j4En/MZem/70a2c4oNaft3Qm38i85mbjc0P2yoT9TjZ4CAovMdZoXH1s6Qd\nYIRvxNZuT7oBJUkmd4Y+wSHaJBRgYoqOPf+D0arLIyQwrtSLCLkqghWOv00r\nzH6QlJHxg8j7lbvvRWDHIk4L0Us1LRY477VW5HNSBLrk8pY45QJ0fmGI6XF2\nfPCYwqtaWD7NYVUWZZkC7CpV/6nZq1AK198HrIpbTmZIOU0HjSgyFx2quxYJ\n9aMOB7jHL5QgC3KkHtxD2iA9809+FOYkry1pQzBuAFz+UDS0GhPq6L9o1dDq\noE9doEthDibxzxCuyYdHRKGpEBqFG5+BMrWZS/1adzz3qPK4XbtmMQCdoEFa\ndfy3\r\n=t5br\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a94cbb1bfaab74b543ec4418229a5818763a2c87","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.67","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.62","@balena/jellyfish-logger":"^1.0.42","@balena/jellyfish-metrics":"^0.1.108","@balena/jellyfish-environment":"^2.4.23"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.109","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.5.0-5508-pre-build-versions-a94cbb1bfaab74b543ec4418229a5818763a2c87_1614814558617_0.09305557102142159","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.5.0":{"name":"@balena/jellyfish-core","version":"2.5.0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.5.0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"72a937242ab525c0fd35d10f33edb9b41734f656","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.5.0.tgz","fileCount":100,"integrity":"sha512-iuoDpdjzZ1fu6a61XDxZPfoKzCL3T/bSSWuxRwu9m1TQcARNCXVjvmU5n13gl0N/4es5lBw/kY+nsIPlSBalDQ==","signatures":[{"sig":"MEYCIQDJzV3pi0NRkDTLAWjVaf3eB7vBTjyvINT2JUS2i4JLoAIhAJKF+jcbYvSK/CEfQk/nRLr0VBejS253VbFWpKG26A4C","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":623403,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgQCxoCRA9TVsSAnZWagAAZmYQAI64d3i3J21ummGgj4No\n+9jI87/0bbcG0C/eLg+KH1M7ii9NpvI+MQCUCBQYfAAwvmpGtrttWh1cufl9\nNkPjuXJpuRy1CA/bTCgDessT2UDOhDhbCXP62m1D7qFF/H3fVhyYMLuOXDO7\n9b89gqvcjmpwYKEAWnkVkmNJcAE271Rzu/Qfy49FEyRPGNldYsqm7Ma5K9av\n35NgrXFC6Y5r6sUDb4v7b6tsrgK+Hu0PocjzMcP4bCBVREbVOUv0XUGKPigN\ne6wsiGHb1ERLLvlryf/uOsbv8JlOfK16aqJc8LxHdRc/q0fCAAu2zfDE8g9Y\nSsgunUr4uSVB6lrKV+I+PE/nbVySHczLhRR9VgAYxJ55H9S9zPQF0ZLxAN5r\nM0PqFdf1Mq0Uc4Pgppsv8v6az+WwwyrXmzkWazgUqZ1xweYgB4TtsLGvfDJv\nOf20h7LhYoiiTnEJzD521OXUK20waU8HK8d/wGG8kTIoMfqi6ftffHlWo21u\nk61S0m9jqCaDmUKqI5f5iP+GEgHe/EUbED7t0ZbJmS46JusHLYrhbilIGAkG\nDU82jV0SQu7MJiSqhMti/3dced68hILc1O71chavMn0GeDNtIpvuDQiE2Uro\n/emdMCCcQcJJRNg9oQi+jUGmNfB802im0W/Bx3+m7NQZqZqdNDGC6PtDmGC4\nGDwl\r\n=2ld0\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"cb01eea8bc6ecd5beeba6e0c75cf5b04a4cdd88d","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.67","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.62","@balena/jellyfish-logger":"^1.0.42","@balena/jellyfish-metrics":"^0.1.108","@balena/jellyfish-environment":"^2.4.23"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.109","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.5.0_1614818407812_0.19392058356307618","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.6.0-5508-pre-build-versions-c15bcf123db9df0bd64f4804bf919ee1012f35b5":{"name":"@balena/jellyfish-core","version":"2.6.0-5508-pre-build-versions-c15bcf123db9df0bd64f4804bf919ee1012f35b5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.6.0-5508-pre-build-versions-c15bcf123db9df0bd64f4804bf919ee1012f35b5","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d634640649c8a5662378cb149517098818af5bab","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.6.0-5508-pre-build-versions-c15bcf123db9df0bd64f4804bf919ee1012f35b5.tgz","fileCount":100,"integrity":"sha512-BQXj2bFwYd9XpeUksGqh1Ige6Iq4jOapi5TZQPa0KQ5rE4A4VGe/uR3KvmJTVWqCVLsX262Vo55Vw1mNaEOxSg==","signatures":[{"sig":"MEYCIQDA1amz41Q7eSa8CNHo6IDLTwG9sSv7T/z7SPdOACjkkAIhAKqhhjA/RR0rVIf/C47ug6kXpvyMM6ky+fBAtvm1e7re","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":629063,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgQC5nCRA9TVsSAnZWagAAvKQQAJ2XYKJARO/iw51/YUQx\nE056hAmPATKqsEgPkHzUG76RbbcFv8G9vkyYErPaa0Vuo4XO7nd/X7JhMa3n\nEnHyAJEjl6ewHZ/obvOmXpAFp8EGz7InSuN3PXmzHMgfWKv7wRmGEbBpLSHE\nB30O+3fc8HjZfz6ORNY2dm7NaJTNN56OvY/B/PlCEBJoKwGmPl1p+R0Bfacx\n9SdbWgwZ04NUlRHzaa+1uGOlj6Jis6oUuQuas13seW2MNyHuKfTj5KUCfBbv\n13Yw6sEAlomyTj3snLhxkxMVZ30PUHsITI3K+zVLbiGGf7Qq72+QUtE1FhIq\nFn+7eOTFXfNrytQZ4px+UubTB6W1aEbuwKqm1nzf9VWjI1YOkyTSWGULkY3c\ntLEKHkSzH0jBEEF8li87HS8TtzmSAmSbgPQWpHIfpJRthsgxiP+gJvtXKZH9\naS/BCAwpUpyB6fD/WyOWzI1uXutwt9LrZKYkEbBTkKkFWJozotUin0Tqtsy3\nt8b53ITnQYw114NPpLK+II8hAbjQU7BxHnHhTMbQaBjNFEzUHhC/mZe5TQqc\nbDUbrUXZEGfVAz/SXVSafcbhaE46dJkJCMyJD3hh/dUNlWKuoy17sYoXk5BZ\nJXTDaa8+7CnVwwY7A8hrkaKk9hf59fjTp2Y+6yPtMmg2pJ+zjEApEZAxPHUo\nIOuo\r\n=aZTQ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c15bcf123db9df0bd64f4804bf919ee1012f35b5","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.67","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.62","@balena/jellyfish-logger":"^1.0.42","@balena/jellyfish-metrics":"^0.1.108","@balena/jellyfish-environment":"^2.4.23"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.109","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.6.0-5508-pre-build-versions-c15bcf123db9df0bd64f4804bf919ee1012f35b5_1614818918599_0.49409509475177327","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.5.1-renovate-non-major-967c6308b933f426c35b89907b17e277256e80ec":{"name":"@balena/jellyfish-core","version":"2.5.1-renovate-non-major-967c6308b933f426c35b89907b17e277256e80ec","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.5.1-renovate-non-major-967c6308b933f426c35b89907b17e277256e80ec","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c5d9f9f3299811c780dcb0f1373bca33e68d1b3d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.5.1-renovate-non-major-967c6308b933f426c35b89907b17e277256e80ec.tgz","fileCount":100,"integrity":"sha512-IY3NaHeZ0GamJz7Opy1q5gB+CC62r8SZyV5aGlGz+2jtWLEi3UFG0d1h4kPaj+jp8Q7jBtFqS9tFrARmlOtJgA==","signatures":[{"sig":"MEUCIEZgXsiAb6R6ifOfbrp5bxCMRZgDrdaeovOxUFq44GUQAiEA0FaslZ5BhGJce4LBoAwnhTuj3lDkEns48J2tjvaAY14=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":623880,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgQJBECRA9TVsSAnZWagAAhbEQAKCFAlEReHsfY0ucu6Qy\nDEwiA08bWtXay5NbsiCXDXDkbqF0qamtWsGTkvebTN5vM3k3PN/vrqjGr04c\nI3sVmS0G6doiduQ90fo+lXL283Ytt+Lba6bUI8hpOKsz+IdEyRwpmSBC6xZS\nU8XXExbV9/Lb5AX7hU9Iqj9mV1S+zglW9Zmj810J1dhm7rMfJfT5AMd3KrnA\nAL1JiGKCRgONxItUYHEQhbbXN/JMLQmExUuqB0B+sU+HH0AUeWRV0cP6t4ul\nve0imSI3uNT6KHVg8o6jPbry7iIjQmdEgk52riVtGL7dcBxj/amVSUkvQXtX\nRpGnJNduf1HM8Luin5MBcv4FI75e5mRqCaCNNkC9yB8wH3mYCMhwADGDXr83\ne/Q+mcp0KPkYzN9+dlddAGkt97elS3PeFeOs/iZGG2naKNyVBqIBSe/snlhp\ng++uQ96oJMI2cKIDQ8PVokL34PFzaM6EPlaH+pa3wvIDeFBTpBpSopLCqV4h\npWeN2+nkumncxPL3ovZOLopsgyOIk58BqYsRotWijE7sxgqAVqn9ZH1qGG9N\nAX/JhlmEGliT4g1iKEJgq5KMyKgi45tfUdb6ogeEgmm9LSFZSxUdf66zsRuU\nlYyzT87IqBfFrfN/Q7GhK/9q5HYEIef4L6TpxQNK+Xo69LS0vA6sR03KRh/T\nA6x7\r\n=6kQa\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"967c6308b933f426c35b89907b17e277256e80ec","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.68","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.62","@balena/jellyfish-logger":"^1.0.42","@balena/jellyfish-metrics":"^0.1.108","@balena/jellyfish-environment":"^2.4.23"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.109","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.5.1-renovate-non-major-967c6308b933f426c35b89907b17e277256e80ec_1614843971869_0.059927798674508814","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.5.1":{"name":"@balena/jellyfish-core","version":"2.5.1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.5.1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"460a274a53a39a9b41f96a355956800b2c8cf551","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.5.1.tgz","fileCount":100,"integrity":"sha512-oVJJd8Ki9eEwGWvs2CCnrqQiXQxN4O/N2LbhpVM5hVF/1mVdJ7XSo0vnUUjf69DgypwGBAd7rqmQm6NotKwXBg==","signatures":[{"sig":"MEQCIG0VkoVXNhil0LiBFYMz3lNb/fktKKTNr+Ix+vJ2czH6AiBrzCDFhfPrSgTot1EvHgE2lZpNlTVHi9q/XN7zHAIEfw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":623820,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgQJG6CRA9TVsSAnZWagAAbioP/2y97CEXUq59UcqtEHmk\ny5A8E7vNO/04eufEn3UEMOKKxcx3qxZtvPmXtxWOAI2c7dzlWKAb53IOTiCZ\nizltyXi71nAd+8nfiAnB3SkgB8ukBWLZKPQs5+bLp15yHxzMY87tyuLDcaU2\nYr41m99S/7PP0m8mIP3JFi2V1nXxH9wRczLfNdgCaHmFNnZPy4D8lbB+iMyz\n+9lzvEqUTnSNSq0xNlCrd++jjv14K8H4GIjiIz14iOU9Bo18Bw37mYYNYQfu\nAwJ5DidtRzXwdBM2n8aDWG4fn1KX15irXjN01IVJqi0tXuL7AGZwyp5II2pO\nqhitOSDwGr7U9/WDOK7Ponz/RTBQHp4IZR63NEdC3E50Ew29b/KjofDEJlEq\nhH2PvR+6qOa8JMUm3snDgJNU6hL2nkGh9TxUSlgvOZy8quFChqK1eefaSF4R\n1CPAOta64xs2c469uM5RAO9O9jT5SGohh6L9cJxpiC9Ta2/SCCcchx9n6sUa\n9OFiZc2Arbu8lrtFzedlL9oAj94OPTtqOOwi/VqKfZx1W7igBZAKPb2UOAlP\nMV5xHmj2pktmP1IE90skSf2UDXWWnxp+/qY+HsGB7wpjpjlb3PAMXAFfLvUq\nDOtQZM7gnze/VfiYA8oMzlzlSsIEQoSFRv+bljD62Ssfytsz6X08dbYNqFmY\nbJ6w\r\n=vhTr\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"9764453a311790ab7a8b34635b3c1688ac4034ff","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.68","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.62","@balena/jellyfish-logger":"^1.0.42","@balena/jellyfish-metrics":"^0.1.108","@balena/jellyfish-environment":"^2.4.23"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.109","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.5.1_1614844345756_0.707746883199752","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.5.2-trigger-include-versions-0f9822215cfd16cb88009f335b8f3d1ccc92d8c8":{"name":"@balena/jellyfish-core","version":"2.5.2-trigger-include-versions-0f9822215cfd16cb88009f335b8f3d1ccc92d8c8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.5.2-trigger-include-versions-0f9822215cfd16cb88009f335b8f3d1ccc92d8c8","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b90a28ee3c30d3f45078317b53aec738ffd32ea3","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.5.2-trigger-include-versions-0f9822215cfd16cb88009f335b8f3d1ccc92d8c8.tgz","fileCount":100,"integrity":"sha512-sio7iTKvCgRJy7Xz01iqBdBNimco8QmTFz+DMSO416aIcm5x0xH84tnA1+mUd0cM/WsrkaxldzOedh9Bbgptgg==","signatures":[{"sig":"MEYCIQDuXI9G1lcxxdRkj++0FpObg7Ve9I39VWiOJt0pDI/aqgIhAMC2xfxEyFycnUuhYJXAKmRBQRzhY3TmRIUImm/pVC4m","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":624433,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgQJsGCRA9TVsSAnZWagAARKIP/j0R+nzW9fN2VFyRsHUx\n9/3yo9+X0QGrqac4ih6qpLadUE4aHIilzPreVIrX+g+M26qrruiFCXdmfmvO\nW6d8LWgUjR+YkGlxmD7U6M4F5j1HEMgIn7lMx6vxjjdyLdlC5gxQxS/7xHNH\n9cbDC7dkWpXvJshwX7vfwGjVcL8eucZo1xeYGgLmeVIMG2ozLuAxAyrlLzYO\n1vM3Ab5SM8ISvxEyCulgYqEl1dqz/6qNsavyczrSzBfh+rrb2vd6bHZ3jb5F\nnju3PSkr0KHelFpOt8mNtk0/CgIW8vQ/e+VKegzFEt5CapGZ3YkQgoR93D/r\nlT745nx4RlU5csTJ/8/4hlrrE+/VR2A/fwwT1c19O+Q+XeZ2vhnZ8vLcdzwO\nkjX4tToKnguZPWj9ohnEbxFdHKUIEpc/RDYn23DXfpcJqGPeinzQZUWKMK4J\neZoI3ISECTAW6JONu7t2o2Tm19acecnKBhl4s0lcLsbAgiwoLb7ah1LJnycz\ni7n7bc0ova0KeZxY2KuY6k08QT7kRabqvdglz5JfX5EE0xwLNaQdffjMQ6Pm\nweTeYUu7+JRiCftbcEV39ilTzmOV5nSIlk+hVKyp8VCt9Nz9sFilfbmiAgfs\nH0ypF8A+mpa4QgWdEziHiattoJWZRav3FA9xfB64B6MlzooN1xn8TBT3LK9y\n75f3\r\n=qvFT\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0f9822215cfd16cb88009f335b8f3d1ccc92d8c8","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.68","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.62","@balena/jellyfish-logger":"^1.0.42","@balena/jellyfish-metrics":"^0.1.108","@balena/jellyfish-environment":"^2.4.23"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.109","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.5.2-trigger-include-versions-0f9822215cfd16cb88009f335b8f3d1ccc92d8c8_1614846726368_0.8741213109353596","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.5.2":{"name":"@balena/jellyfish-core","version":"2.5.2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.5.2","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ecfa39faa06604b9acf07b846cefd5e5bf3b58e1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.5.2.tgz","fileCount":100,"integrity":"sha512-bjXYCtBaoewZcm3Cuq3X8bNCKGtsdSUCmR3aPwyV9R7OKTd9OeVGhDxyk/+PT9b6AhHOj8WRnTURwCBKsTkw5Q==","signatures":[{"sig":"MEQCIB17K8eoHh2CTUDJa/21NJJZrrljVSFIN1gj37jizefWAiAT41T3dB0tlYDP40cMfh0kxD8TRtZrHnuv2hBFc23EAQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":624367,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgQJxNCRA9TVsSAnZWagAA0n0QAJTpPxVLCntoySqWk1Vz\nZ0TZdtMINPmyTX2JOE57uuRdt4ALMZHwjy+VReLS8fulvYRWexsCdtRUEXOG\nLLIIEILZYcmLsOnAM4LNXFmYVuCWPzNFJDM7LOdJG7ehztA2t/8NPLGAIg28\nZyOe/Bzsg+46jFOfPDTR8Zy6zFuamj1L+tOrGwq51M2Ut7wS7Pb1QAOaduFc\nCNzHogv3Ei4gyz0FBrKwDM3QZRLmYpKrOpgbnAmKchJ5ayE6F0fcpM2QWmw/\nqyfGkc56CePLn14ieiy4XDuevRCtN2IDFcJHM3F8SGrDSQ4SKy/R1Bs7EIth\nFF5SweDgxTUVuUmU3H6+Qs2Y67xR3T+1BCBoSMfBhO3zzlE7bPqOIRcN1ee6\njn/wQU7/vKd2iNQRGsger8S8RRD5zvovP5OAg4dIJVG1jBINwHAu8dL5xjbj\nyNicpAB+iA+lNbs4C0c4QM8ljXAUc8ahj80inFy+g8znVZAlu+WMmj/hAnuZ\nfCsokCEbgYPclPYyFebEnvfGify+AJMmPbI0x5RS94oKmp9HBixhzbeARNAl\nlN+ny1s4FP2vWXD/2w/XcPe61LndvKiHAKCWWBTOiIa/T4M8lG5zsTmbG1Ee\nsiHGscw3eT+K4RZciU4n0R+VROvJ7oWu5oUxPZ8Ptf2sypx0XkKhGgarnBdR\nZWe0\r\n=H3w0\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2375e80ce265eff287b8e574bf89e5bf18a48b5d","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.68","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.62","@balena/jellyfish-logger":"^1.0.42","@balena/jellyfish-metrics":"^0.1.108","@balena/jellyfish-environment":"^2.4.23"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.109","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.5.2_1614847052953_0.6727162809453753","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.6.0-5508-pre-build-versions-1c1b01cf26fe99677ea729f379542a7b5782e08b":{"name":"@balena/jellyfish-core","version":"2.6.0-5508-pre-build-versions-1c1b01cf26fe99677ea729f379542a7b5782e08b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.6.0-5508-pre-build-versions-1c1b01cf26fe99677ea729f379542a7b5782e08b","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3c6b94472fada53cfa85ec89ed5a33e1c63be35b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.6.0-5508-pre-build-versions-1c1b01cf26fe99677ea729f379542a7b5782e08b.tgz","fileCount":100,"integrity":"sha512-mLnDUEHByjlGpaWyRbR51rz7tA599nytHvU2OoediOWNpLHozX/J1oN1VM6T2fpBEZprwFmtV+o0g1aVCfS9SQ==","signatures":[{"sig":"MEUCIDsLVNlmXA5tPTldjNsmRdBWOBHAwrqcglbYUA4IIC81AiEAtbBVDBlfpz8CPvn1q5sE2dEDjOI7SyLVmNLkAXRMeYc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":630027,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgQK8pCRA9TVsSAnZWagAAkJMP/3x1ROkKeI/E3Xb4KxpC\nTp2pCA+W8RNWTX3nOHVpWzUq9U6mYScvTgzBvHFiGbVPf6ErGd+6w7akfZ3b\n0iTEeMTg3+j1dqTRFJdLNBqtt+QrfZQWXIoZcF7i1+4D+ox3K/KQfhLXKjhr\nLcmSdZb1dkOuSQ0RCfh1P1BGjURDHAvYr3lr2WR6QYOA5vWsDzs6n5tllZ73\nqlH0B+6f2FlQBaeikoQPgtie0RZfzOyAEvZv9ayuUntxsUs2WW0pH3O8IUwi\noEzpxynWLY4/l4QxZabQoUsZWtTocPuBwFVOTws3Au9eQ3Wn3JoGWuEJQkDs\nVMsEAW6m1Z6pWiOpS6op1ZNScG/oWqjuAliGA83LGz9MbGpr9aUvB3fxaMEo\niJzX57Zzq9Zfb7ettcfOp8UIOP8l+08aEuUnVBREvsDubV5Nz7xvXRpa0Cyp\nHZAR1mW+beDnS1/TH4wS2+Znw84BktZWmE7aKNXWQOAQQ2eb4SYlbxMy9LVp\ngcbxQlAzBGRTST9s5VCMnRnaws/9fMpb/QEFDhZUXIdkTi//ghkDkHd0qKj0\n3UoFnSKigBlOMUAI9vrBctAgY/ZRpswouOzS07rG+8vOHUPVOmyNWpzyCqf+\nuWQaFjyy2eNnjzp/E4XwmPc5G03i9tLlojetiCeESN7rnnAx0ZYyXD85VdYN\npe8H\r\n=6Tv6\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"1c1b01cf26fe99677ea729f379542a7b5782e08b","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.68","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.62","@balena/jellyfish-logger":"^1.0.42","@balena/jellyfish-metrics":"^0.1.108","@balena/jellyfish-environment":"^2.4.23"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.109","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.6.0-5508-pre-build-versions-1c1b01cf26fe99677ea729f379542a7b5782e08b_1614851880598_0.27350046067743294","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.5.3-renovate-non-major-e9f2008f0eade0de03163df1cf9a58066439be34":{"name":"@balena/jellyfish-core","version":"2.5.3-renovate-non-major-e9f2008f0eade0de03163df1cf9a58066439be34","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.5.3-renovate-non-major-e9f2008f0eade0de03163df1cf9a58066439be34","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a599351a7b9b54706f420b004c3d334f3f635fa9","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.5.3-renovate-non-major-e9f2008f0eade0de03163df1cf9a58066439be34.tgz","fileCount":100,"integrity":"sha512-+u7VS1pZIiX2kLjtgNetenEida588Vo7sp5hYP0L6J1heCoGiBPdj6tcgWZOAkRP6MoXzev1eSFsCS8ezoPDlA==","signatures":[{"sig":"MEUCIGnK8/CKRM5+yZxY8PllOpsp1agLhCBYhXylsTt0qAhvAiEA3azRhWkchTIh9fO1f11cTtg/Bc8O4dD9MqkqdZzH6Mo=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":624844,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgQLDeCRA9TVsSAnZWagAAyE4QAIljIxoqFaCwyrTx1P7O\n03H3AJABuBdQpT9H9I+8wLMq+OdrgjwfQ9tVIF5Bc2GT86C07xvQfOrQ+JJ/\no83Ypn6MMWmgyNJ3Fka7webw6IJIF7nrAbNp2yS8reBmJYi1atwTQlF6SFjG\nZhWaxJPCfcFfzHyGoH5RCy13VzlACqvUdvk7gufeGl41xpvGUK0GSuRn46a1\nhm4DsnogOgSZTbIvMQvXUmikWcGmBVqLdrAL+WK7Th/acbppI6feRyLYWzk7\njO2sdEDViXcrZ77DA/EGcxKR01v8hexBXc8WRMOuoIXke3dsnWsrW6oUJZ3r\nfapwp/nkbltiBsxGvtd2Ur7tEdviOi7pE/ENCFaDTkYS3HgGzqAdBfVBOz2/\nmJAlWRAec4kOmq/Pp/k3efFAlAzArQU2f3R0M5FP5xuUPExWwKc2Z86piTAN\nhuvg88IOB3FklYc6nmf2X144UhUWHclKQ9dVWYrWY4024a6v6rhGF3ZHaMh0\nVuNu8RXCQF01mtyrykOqkldZcJ0qgTz60JGIEG4pgfgy65XChqFxZez7fu1d\n1j1la8UYSizEoOdpnGNKwI9qs5mbYuPDtBh/IcVfgpllEfCkIvQ6dWrEkCz0\njOYC/HJ8OdWUbNIrk8gSSpQxedtKCWg+YgCcwm2QpVAc6M5mMwy5YHoP5dHu\navUt\r\n=NvGO\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e9f2008f0eade0de03163df1cf9a58066439be34","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.69","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.62","@balena/jellyfish-logger":"^1.0.42","@balena/jellyfish-metrics":"^0.1.108","@balena/jellyfish-environment":"^2.4.23"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.109","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.5.3-renovate-non-major-e9f2008f0eade0de03163df1cf9a58066439be34_1614852317590_0.9515352611935595","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.5.3":{"name":"@balena/jellyfish-core","version":"2.5.3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.5.3","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8ed94f37853851c970215a87e33faf13b46300fa","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.5.3.tgz","fileCount":100,"integrity":"sha512-BlOhS6inDxvQq7+UN2j2PKf6Edvh9gKZWWywJ+USyMrzcb8wjoxyF3Vdds8/gCTxRUyErwPQk+R+5XqElfqAYw==","signatures":[{"sig":"MEQCIGT9YjRrNsSM/44cJlMZdwpYuqN1kvbUWDFa/7WOj0p5AiBbhnqGp3xs+RLbI64eG1MERI/AYYsfHBd5G+gRe5LOqg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":624784,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgQLIUCRA9TVsSAnZWagAAeMkP/RSjz7OqY413fIKKeYV0\n+DD6WhkUVjdJ47vNij+cGPdzd6DOpANChyE8mxCir2p1LyAFTeNHnn00hqbe\nbHNtsZHT0kafbDo306a/+HvnNeSaha4Pil3kdeW+QgIHcf87sizhrsa5FsZO\nX4GtpQ4JzSYrnd2Hb0/omMhe5FnsJAp1LuKlHxv19qaoa9PY/674kRNhgyXj\nNUhLFi1edgcOUExEXdBy9p8PQ6YBnCchQcIK6EU50lRnW/LUJXJ2ekry1Z3m\nF1uDVTybjnv+xnJE93DIZ3fE2QiK3H8pC49D1U/iHXA1U7giAWU/iG9vAIk9\npN71wJ3y7NhbL30zlJ/3ezliXO1Ecbj3cLRzUvg8J4EWeChTHR6gdude5Ovh\nX9Vgdh1GAMsWROfBw+S6izjIXfNFyDdNAHDnu2J66jqN2uBARqQizFY2eHFp\nHboRXb0GKlRm1gi5n0IPPRIzmXzIATsKaYxfMNb+XrnqSzLnjx4zE9tteH5W\nyAXVCVHF2kZc3bNCNWIUZvL4XR8rDBGXFg1vyUCzh2e+3nVkqz45UWCsoM8G\nRFE1yKGOlZXQqGyRjDF2qOSaGZR1Z4f09v7ioAuxm2QjTo1+GbC1n0FXnQXt\nk3MKtck2rEnMMA/4CJ/WQsFYaxHmq51LS5Z5Wig3AeMPdBNMoCSVGSWgD49B\nwkrz\r\n=ObtA\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"872134cf945d05ca8000a466190f331497da3a09","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.69","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.62","@balena/jellyfish-logger":"^1.0.42","@balena/jellyfish-metrics":"^0.1.108","@balena/jellyfish-environment":"^2.4.23"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.109","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.5.3_1614852628043_0.5683151834385463","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.5.4-renovate-non-major-4008a66dd362fa23724b0d7a0ef85ee852523d21":{"name":"@balena/jellyfish-core","version":"2.5.4-renovate-non-major-4008a66dd362fa23724b0d7a0ef85ee852523d21","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.5.4-renovate-non-major-4008a66dd362fa23724b0d7a0ef85ee852523d21","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a230cf37dc2eb4fa575725d751a192cd69c2d915","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.5.4-renovate-non-major-4008a66dd362fa23724b0d7a0ef85ee852523d21.tgz","fileCount":100,"integrity":"sha512-OYlGCWlcNu1K4oZoDqXQ82/qm/n6OkQCZIXhrsrLpYDcoYiUof0EFv/Vnr9CMu68bPARJd1r9+un2gFcKp3fEA==","signatures":[{"sig":"MEYCIQCRLuIZvu8CxK28PJr2uFZt+FdhYeqMsKqOA9uKk2W2ywIhAJaQbN8f5124BUr7euJ0yAorN1+eWGu5UhCe7wvqu28t","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":625261,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgQZZwCRA9TVsSAnZWagAA7rkQAIMrxrgmtW6hywBlSlLw\nwUn+nNOvsFFpezb2rCYpfYQ5V1eqTzf2mEnCUzT4YglFRna8eo5+tFulcWOX\nU1PexaG9Ih2OTWs7YHuEk+6REKwNVWKUgtPndc3AptvOM1GDunNC9u/xSk/j\ny9ArphEk70B4xLZl298yDuUWFJ61G06eMVhwaKkbcYdPagYHqj+g7ZzQN8z/\nox5tSevQ2xybtQAVMp/QJuyWa8Todqe/d/r/yRaWnhRGmqSQQ6R4+da99XYv\nJrqkQzrl1PnvPoOjzdRhgmh0plYf1DGXeIYbSSq6iq2wRmDsKvKJ+6k9GDRU\nr4SL0G/ld9w7Bt308W5bz16fcXIrMvCiRiwCSKExREEP0fMwxwjfy0KepEP/\n3vCOKDbq6NJTtoKlNTKsKIL+0WWWdT2XnOff+LrEa9nGuWhohJGpkkb5CwFH\naxP9Y7nKyEkTWhqwcPBYJ5bWrzolVJmUwhz0dc8G7T6oZR9I4WTwhEVDMg7Z\noSta0yiFOKZN6Rd+4kHqsalvUbl8HjiCrEAiZdrKVn2pFKq6hOXmIEaCdqvC\nIkwsuzaUDtCSMw7aQMQrt787wYIRPnle0UA+/aGncl7jBeoXhydT8glBjcQ6\n/JHng2wqDT0+uykZlTkQiPnfVHO79vvBs/bHJ9xeY9DRz8OAAGueh3wCCfyy\nBBD5\r\n=eq++\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4008a66dd362fa23724b0d7a0ef85ee852523d21","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.70","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.62","@balena/jellyfish-logger":"^1.0.42","@balena/jellyfish-metrics":"^0.1.108","@balena/jellyfish-environment":"^2.4.23"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.109","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.5.4-renovate-non-major-4008a66dd362fa23724b0d7a0ef85ee852523d21_1614911087663_0.5381766238443118","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.5.4":{"name":"@balena/jellyfish-core","version":"2.5.4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.5.4","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4370fc0c509f06ba157ff962768313cc5c84ca33","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.5.4.tgz","fileCount":100,"integrity":"sha512-jsKs8MViP+DtvtVl9BiSwfg+OGKT1ooZ4JGP4tC6n5BLfdlUHX36gpLyPeXaleJHordv78euQP38YS9u20XAxg==","signatures":[{"sig":"MEUCIQDMCw9c0WWynjtxrGfR8yQKIzqSj2WO/1HqzCMfTYt3pgIga3Tykq4isF8trUpsMwtmJ16qhv82UsPF2sMG57UxMh8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":625201,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgQZffCRA9TVsSAnZWagAA/wQP/1QRRPFNHTFL55aU07E9\noDUvyF2ycWHbR6YD7/j84M6yAu2o1mHT7ewy3mVjZpAWmQMTZMMXq4Fa5jMU\nczyuGkTCW5ITAxMcY4B0KES7L68Sl/0KZg50NQubm+7kFjUQs5mu8jbwU7go\noS8BZzDspQPpbxoA/RciWldi8CQR6WP8ptYigBuhRsiAl1w2SJ8PvcG426cj\nRdxwA8lNzWYaDNX7X7p4dSDyduQtFArZB426qKgKmFEYAlmhjeBETeMpFsmX\nQMZhFTetJW1r8hCKPpAATtxlJjx/TIDCuL3+9EkMj+PK0kI59Tr01W7ezv69\nMknoU2FbThnJsrvmdEqSURkkQp9Hxh0IO4+kzZvziXSsPc9UOGjEnsIFImSa\nGDOPX5Va1eVrxPhFGlURhhvpbhb6WP1W6Lm3XieVlZbFDpNYxeRmHzQoJ74T\nTK+dkXIkXdS1k5Jyhn19G6vUsJbLUWIjAhWy4SCIZE/fB6s2cVxm6dYDQjFe\njgJahsQKIQ3KWZ40RbXXutFdTc40aIuVv31Qi1SAe4PELNwiEXIBsQCWjj3y\nMSy8RShODsfgoiWZLqY+3VhoG0fYbxaNT2SMSeonNTSAd2TNsq5JayaftPX1\nST1ACDV1XtggEBF6pYI2XShGu4dkcMpsB8lksdFGU3LHWRMG8wq5v5jG9sSS\nQ/Bj\r\n=ehVn\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"002da36d8296d53c3fc3b04d67f56edbf0ad8f60","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.70","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.62","@balena/jellyfish-logger":"^1.0.42","@balena/jellyfish-metrics":"^0.1.108","@balena/jellyfish-environment":"^2.4.23"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.109","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.5.4_1614911455079_0.43248335862303433","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.6.0-5508-pre-build-versions-a11482ad7b48c760366fc8a3244ea9c4d3dde694":{"name":"@balena/jellyfish-core","version":"2.6.0-5508-pre-build-versions-a11482ad7b48c760366fc8a3244ea9c4d3dde694","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.6.0-5508-pre-build-versions-a11482ad7b48c760366fc8a3244ea9c4d3dde694","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c2407d080b54940c619979d1e275df769c2a16c5","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.6.0-5508-pre-build-versions-a11482ad7b48c760366fc8a3244ea9c4d3dde694.tgz","fileCount":100,"integrity":"sha512-AgZvZjRspGnbl5XQ3+CdCjrxl5J/ZjVqeTTiiKn4GtiKtVZ54lltpv1gvxKfSRkbwQ37tU7tzlP/fvu1y1vphw==","signatures":[{"sig":"MEQCIEg9xtOR56ydDVgt5jVJr7fJQ8tLhtIpPOqiVtD4NOT9AiB2jwvj2/SuS3vwo+TdrGPRW14YwLxS29ZQ2JWmC5kb3Q==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":632692,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgQfjKCRA9TVsSAnZWagAAAioP/0f23o5mlw4en72eNxqt\nnOVJds9TzOzrszYfKke9EYYYXp/VEjJMLTBCEQYVuslxG+DsK8oSnqQ0phSV\npYstbT9yml6taL6OINlf/zENZnfQ/cIxzwSB4no+qLxsz5n8FcrBkBsIOJ5G\nQLQpLNXjO3BEowjuLPd250Hneu5vuXbhkrN0Tb9h0dM9wISZNAIhDF9NjhF8\nOSpJehGTVh4uyKmb8DzhMPpt+2I8hwnZDYX+urjKR7HKPYPVog6XP+eEww5f\n52C21vC4giJ02q/xP9iSAnoqI9iKInq5hGwZ3xcJKIZX/kOnKnWUmAqbrbVr\nsWaBuWAbO3Tq2Z6VdzwEFK9MtGvtfas+JG/KPTt2qF0zzx3jb08seAuQDNQr\nzZo01bhBt9USvlFVeV9mszwc4T71gaI/S3BirHeLruC6EXRvcsZQInzd759f\na/chYeUfiCRz6NCewOvXlJXIQDWvxrhENpI9xx2YwobCOh6gxqkeL2Mj39uq\nYOJgKhb1VJiHXtFGp5Wg45FTnYjZ/jVultJmbl0Zh7OTMmgRIzC0BD8auQAb\nz32UYXEXCk9tAbOW9ZWiqWvbGzaYkckRzRNvM3cLjzWEr4Bu62JnwLtaqNhE\n0qC2cu2UXoJ7HnUF102Yv1oPk/AuMpSemkfFVUt+u7eBSiBXhbL4WLvs1Bo1\neYwS\r\n=nndU\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a11482ad7b48c760366fc8a3244ea9c4d3dde694","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.70","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.62","@balena/jellyfish-logger":"^1.0.42","@balena/jellyfish-metrics":"^0.1.108","@balena/jellyfish-environment":"^2.4.23"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.109","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.6.0-5508-pre-build-versions-a11482ad7b48c760366fc8a3244ea9c4d3dde694_1614936265337_0.0552808956091817","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.6.0-5508-pre-build-versions-99bc3a916b4a6b1068eb867244059c9c2be32856":{"name":"@balena/jellyfish-core","version":"2.6.0-5508-pre-build-versions-99bc3a916b4a6b1068eb867244059c9c2be32856","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.6.0-5508-pre-build-versions-99bc3a916b4a6b1068eb867244059c9c2be32856","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e1b671a6644e29d13063a8f39fca982e9bb1a4f2","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.6.0-5508-pre-build-versions-99bc3a916b4a6b1068eb867244059c9c2be32856.tgz","fileCount":100,"integrity":"sha512-zBcyPPoUalY/DvgXBdBw2RV1XZxrj7OjScU/K6mZZoj0x5TmbdP/CF33w2cstjdtxjPnBc8nUUWrk8WlAckv0A==","signatures":[{"sig":"MEYCIQCKo0T84+EL2+nLD8aTDQfzbA4l0smcPM/TOVQOis7QdQIhAJxxPjYpvKVim+70ieGEkWzn+NMXRpcuHc04jB3qR2ZB","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":632702,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgQfrhCRA9TVsSAnZWagAAnNIP/36JY5W3hPZFPKL9iXVr\napC9p6aAz3urVGhVfZrfdrVPr2xR+Ux4ErI2c2BeCKAJO/6bwhVYsJnqTsyh\n4xPX1bB1aYXc0LUtD66h66nE8auyeKj57Lla0CEhzXDASIim7OwB7UnDqniv\nOvJON2nZaU4Y7cB0nljGaUlUQkqIB7+I4enjRup5iDgdIDcglmossVN7a99d\nNWG/Ttml4DEDR67z2CFSlsAopi7vwW8OE/nu/2FEpDOLAnIo2vU8as9a0Ywr\nbJZQUExOmXfOwB7Kzr7u5cwnhwS99ASmyQIKPmLrS8g3AIzYeB6Op+4fTHFr\nhgGnIRlpFK7SziiLIBDq1zQ0gxBFVPrM0ueOB5WeIvxipQQ4u9BRLLPu1Ofq\nzR3P32ktFPbXY+Eg+mwG7fq0TV8se4c7byZrhw6Y1LGLfKZ47sqKZtFnPmCj\nZu0q6cMLz29bbwNEbxk4gPiGpR3Ku97EmlvREMCH1y59ZBc0F3YLE0x9JEzm\nQvx+H9cBWRk4INngbk9LrjVAwW1QwDOB+uAt/rKXLXtmHRIqLyPUJNDYK4rD\nYRIHIuKv5g2WwuQ0S+EJBMNXmhF+givLa5VjxswhGS6Jd3BbxbF+s5vkOlYN\n9JdtMJmcvn6rPOnrFRInK89uienMxchPKLULtywo/GoEvbVjulLWXzEZ7eM6\nVv/Z\r\n=qU42\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"99bc3a916b4a6b1068eb867244059c9c2be32856","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.70","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.62","@balena/jellyfish-logger":"^1.0.42","@balena/jellyfish-metrics":"^0.1.108","@balena/jellyfish-environment":"^2.4.23"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.109","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.6.0-5508-pre-build-versions-99bc3a916b4a6b1068eb867244059c9c2be32856_1614936800536_0.1077268343227964","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.6.0-pagination-fixes-18c1799eee418c41d33c2f8846c0a6d3355ee81e":{"name":"@balena/jellyfish-core","version":"2.6.0-pagination-fixes-18c1799eee418c41d33c2f8846c0a6d3355ee81e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.6.0-pagination-fixes-18c1799eee418c41d33c2f8846c0a6d3355ee81e","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d0394db0e0e7caa2fa540042e09e51a35c05f539","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.6.0-pagination-fixes-18c1799eee418c41d33c2f8846c0a6d3355ee81e.tgz","fileCount":100,"integrity":"sha512-5xMje8/M0AyfW9SccKp9qQLZuNFC2lJQQ1JDdlD0NBWTKp3KxX3Eek+tY6M8xK/LvCST/cV8BojXvhVrDWMsOg==","signatures":[{"sig":"MEUCIHT9HNtYAP+PqWwisSGkXnEBb1qoXHW2XKY2UPaFQCp8AiEAi9zOHcAQ6TPG3I7uR0YWyb+0fhKFN7QUjDNgN5qFc88=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":626119,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgQg5YCRA9TVsSAnZWagAAQTQP+QB8Xss+3T/V+cLhfnwM\ndXWdcyp40YsOYvY8LwqtsRE/kpZ9LxYrRjIarbcmtQX6FRk580pzpIU5+caU\nOiS5YF3vNWgj0hqshLepRJt8+2e5Ukyu95xoOzmXhgseA0sFrBkh+g51kMxq\n6T0NTeT/cIatHoX06Oj5pjPetMV2whBirr100KBMpB2lGG6Jysx5Tl/wpE3Y\nXLxvtxhgs8iVp/oSYeepCyEVmzvn1WMEinKoFroUt6Tc6Mq/szsk4PysBqYY\n0PEe6lUBP3iiq0Ktx9rT5Dhiva7X3oOro+Si/HW0d0dIwTHKO/hL/wQkfiVk\noFeaHyoUklXx/FfvyT3yGZfaPkJ85NNqhjx2CoFA8DDnc1/S1zNH0wtggS3I\nqfMHF7muTgNHZcwSrHndrhmyVuTOp7bf5CBgJi2uGEuKthB39NwM5T0enV+f\nJuHDKYK1RBZJLl0PojthGRhmaAldEKScHio7MaMQ4rbllQbQVFCH2TcLTxZJ\nThR5QqiYgv+xXe1btH/FtcxKbFuAk09pmToGpVGQpmOelB8Z7yUxn38XwCpX\nyCP+qO6eOF/mOuQOzactA9uayT1QKP6miGbOYfEO8+AyGnjH5i5LS7AGR9um\npeG1INdyd7CxZkHOZAAcVlPPUfP21kvqXtr62O2/ZXEmwm1Ltz90WebXFnqO\n2FNW\r\n=E6kA\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"18c1799eee418c41d33c2f8846c0a6d3355ee81e","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.70","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.62","@balena/jellyfish-logger":"^1.0.42","@balena/jellyfish-metrics":"^0.1.108","@balena/jellyfish-environment":"^2.4.23"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.109","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.6.0-pagination-fixes-18c1799eee418c41d33c2f8846c0a6d3355ee81e_1614941783762_0.7886912373709041","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.5.5-renovate-non-major-d31725bd46d4d0e85857f06dc15c588c0d3dd666":{"name":"@balena/jellyfish-core","version":"2.5.5-renovate-non-major-d31725bd46d4d0e85857f06dc15c588c0d3dd666","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.5.5-renovate-non-major-d31725bd46d4d0e85857f06dc15c588c0d3dd666","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5c7cbcef134d1161b80ea4b59d115cce4808b792","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.5.5-renovate-non-major-d31725bd46d4d0e85857f06dc15c588c0d3dd666.tgz","fileCount":100,"integrity":"sha512-JSZV7nMqxteyVj/jSKI7HokGP9x7EWbE9psXTZo+WMX36rQcSuM7jHBRfgJPvJ8+gdYgmYo0bJgFXKnuz3QBEw==","signatures":[{"sig":"MEYCIQCksz4AIdZdrIbaB1/VZbymBD/hC+qJghgMjbax3Rz99wIhAJNb2lJNQRpRG7VgGg1JOkKp0WrwWZjPcIR7+QDmWcrY","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":625678,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgQ8nxCRA9TVsSAnZWagAA7KkP/AtcVAjHL9iExRCEZnYQ\nkGhWd/ZdF8idG2anbQexhY83blS8JhIgDN4bO5ANQMy7KKiPvdzJEdNqcGbS\n2NbmUZ70Okx8EQw/+IoS9qqd9nXI0OgNEr1sKGae1z9uYEVdIiY5F4wmbIAd\nFCaEOAH8E6Dg5Z+5+iJn+91mP17A4bhB7h6E5vzM+AXgMZA+g/ZX+CObB/uj\n4ccVH+s8jZba/DiaQkLFkWo7/ZJqdj8Ntiki3Z3iY2HBqNzQvUbGnTeETmNn\nux7/FlYagx/EhdFA631TidoHcxdr748aQaMcCV8PXfK4P8DA5h1aO3V+LRCr\nwJrcNjYqC8xNqjnELAhpEDBgkFjWOgVaOBbGzwO4vGym2FGrRWDsD2u8n0zj\n6XJO7XiToWWITrXCn6A1q2c/a8m3fn0z4oWjB93b2nDQ8DM5qa0UHyWmv6Si\ngS56+qPP3HhCt4erKX/N8mu7uLXbx39Qew2ulvWh9PpqvAP9lm5NU1Je8TXV\ncidwf2cttF1YOKiq6VauI0KR1o5lEANkxrNSmkXOnQmSJUDW3hpvA3PfGTZN\n5/X77f8Q2eWEGkr1tLoMCzkmUL0v2Bspd/gvjOc00NbOTvjfXPEPhRvjQmNs\nryDYCxQtziGxVi6fRstP8LoU7YkN4uu+svE2dfZsHL0PmaSsCDdujzQgqy6q\nSwnb\r\n=kBrM\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d31725bd46d4d0e85857f06dc15c588c0d3dd666","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.71","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.62","@balena/jellyfish-logger":"^1.0.42","@balena/jellyfish-metrics":"^0.1.108","@balena/jellyfish-environment":"^2.4.23"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.109","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.5.5-renovate-non-major-d31725bd46d4d0e85857f06dc15c588c0d3dd666_1615055344997_0.7972465915132585","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.5.5":{"name":"@balena/jellyfish-core","version":"2.5.5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.5.5","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8a0b8428d4f5957c76b63d3418f1038c2b9fb9de","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.5.5.tgz","fileCount":100,"integrity":"sha512-a7dPJGfO98afnDnJkzT/+2vLGoZl5+RpHoq4KifLmyQVrLcM90dAzDx1Oiiyd28dUpu7q1WbvLQbTvDSv2CwaA==","signatures":[{"sig":"MEYCIQCO14ODnPFsYxv9+HiF6KK6IRcYm4XtWR0Qi9E+0rtr+gIhAJpQdslMpE3aqrmNwZ76k1uskKQo7nxtnMJ62SUhHSmh","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":625618,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgQ8tXCRA9TVsSAnZWagAAt+QP/2u6ht61fSuSNu0kbazf\n8JlsJwXF0F7i0HWG9fZ0TIOGWnPFJMW04+FT4Uxkczmc97hct+AHhAIeldC+\nUVkHZOkGXSAq/N0ZhGJqLwOE8egbVAcpbju55E976WjgORxghGXaOMpq3WBX\nZodseDBAHum7F1l7RskIr+L+Pwy9FN93sbwieuspQFcWhCrNJkq+M1XDwqPP\nqfpeC3Yq8xOZRxzesD2b2a9GnuaoCkP4BC6I7z19xOIcCF5pfzjOMub5eZ4m\no87rpuJcVrW5sWLCTQkdIEkx2uDQ8BvBGHqpMaTH0iqlm1WyqXJhcD6sE6r0\na6uU6o6oTcWi0nifeT98EmVc6LmHCjC8mEmDjTynLBrBgNYH3Ab4xoaJ9qGa\njZusvlsmVSVf3th5yCUi4ffDEL7ZiZkRZbOx5XuEWIWefp9K4gOdUTUCr4AE\ntWrfMytEbPfuD1ciYx7B6ZcIM2dWv//xr7n8TZXqDAHY71eeCT3LZBBNkRz4\n1ZYw58T0Y+SBWOF+E/X/dNKxB151vb7+K8eyAwDbl0rduZiC1yt7UZWeKvdK\nQ3w8E6LQmP5BgUPY9m9mmyn5tqQlwTumq8AwQx2PA9L0/wAT7F6zkiDBq8k+\nj/5tP6KffEL5mn8CvG5YMBHO8DMAIGSN0NjfjehRbfqreD+YMc+11+vNC94O\nGfE+\r\n=pP02\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2473a325cba513db0c133cc8de01f52979b26340","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.71","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.62","@balena/jellyfish-logger":"^1.0.42","@balena/jellyfish-metrics":"^0.1.108","@balena/jellyfish-environment":"^2.4.23"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.109","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.5.5_1615055703028_0.25781096732525044","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.6.0-5508-pre-build-versions-ccd4cfe66ba9453149322805189d126d5057c83d":{"name":"@balena/jellyfish-core","version":"2.6.0-5508-pre-build-versions-ccd4cfe66ba9453149322805189d126d5057c83d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.6.0-5508-pre-build-versions-ccd4cfe66ba9453149322805189d126d5057c83d","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e2b5768b1981a17689cac0e5399c1d457b74998c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.6.0-5508-pre-build-versions-ccd4cfe66ba9453149322805189d126d5057c83d.tgz","fileCount":100,"integrity":"sha512-J0YIsZSi5t9d+SYp2HB6mxs8FKT5J8UtYG9dy9hJTnFqORvN2B1E5hClbmUxsK9nw+tbmOp6md0WkhkbGtLe8A==","signatures":[{"sig":"MEYCIQCw+Tc267Wj3QTBlOMcA0AeypojD/sf9BgTEJjcRMdOXwIhAPP6Maiat757iNTXUeDva+dX8vuG2E9EpmaiV2sXOVdb","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":633119,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgRa5sCRA9TVsSAnZWagAA4fkP/izUqIOR4Yq9bObf04Zt\nyHtUR7KPSFNiwtwsFrrrcthi21g+4FPMrT1W1wviI4OugladXX8+4G+fDjCe\naUuZTy8RSF4uTt+b0UxqI7nBCWKmFZLiE05K3qCwkG86F1Hudxo6egjRAKqy\ngcOOw3d0MOsjXNhnANTWu5Vy6cLYfhUAxsFk5Sq8qJyAsbBz+hOjhP5eS6I9\nM+QpkX2gjH2Unr/2ZPyPBW/LlPJzAUrkX+2e7K2EyW0T8d+Psj6oHnfsR1lu\nK0N6BD9cobscvfc+YgvhyEpPUCmE5Jm/o9z5xIKPZKUpOW8sXcQXZHKZtcww\nXpVSrHzu/90GQDJ4K5CgyNf6kqJeYaT00noQV86zurL8arNHxQtNgwtDPKBw\n/N1S5fnWxm3n2TxOlmue+YBrW98d8hoXwrviv6kJPcvA3lAGD97c5RbxGoa5\nymCreZpDg4rKLG7auTXKD/xbJqfJdpyAcgbp/DzlMOJN5LKD+K1IXshUJK8H\nTdrm60oc3YTJwHh9muE3HAikb8+ENVJBdCfCa3DYPO4MPcI4+LSdkmTl1mvP\nQXb7LzQqx6hY2lOnRuZKFJzWHFIHDLAE55xI5oG3fM/w4pw172LyBXB2zjtb\nHH1lAaiRN0rcyFLQIEhWoU5rMpZ6FkXHZ3aJ5N+/L9qLhhlzs1Nil14UQatg\nPmgs\r\n=jdkt\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ccd4cfe66ba9453149322805189d126d5057c83d","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.71","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.62","@balena/jellyfish-logger":"^1.0.42","@balena/jellyfish-metrics":"^0.1.108","@balena/jellyfish-environment":"^2.4.23"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.109","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.6.0-5508-pre-build-versions-ccd4cfe66ba9453149322805189d126d5057c83d_1615179371811_0.9725944835267688","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.6.0-disable-notification-sound-486623552df2d68df0bbce1d48bc17c08ae54741":{"name":"@balena/jellyfish-core","version":"2.6.0-disable-notification-sound-486623552df2d68df0bbce1d48bc17c08ae54741","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.6.0-disable-notification-sound-486623552df2d68df0bbce1d48bc17c08ae54741","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"43575c894c62dcb0d8131bc23571166d4ef68f74","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.6.0-disable-notification-sound-486623552df2d68df0bbce1d48bc17c08ae54741.tgz","fileCount":100,"integrity":"sha512-ON1FkX7MauXXE7Kzr4+Qco2Y3r0p9wzrakvlC2GRFICcR1fJ2wKS2lf3TgqBwQIX0+yjZkfwlECDEAGvz8WGsA==","signatures":[{"sig":"MEUCIF5fKBLgrUNacs7MDq3WTu0GNw8QA37MFarud8/6KaEsAiEAor9dhI4SGXSW0ixSTBisOGXydcIlxhJxNHMD4TMcGBo=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":626565,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgRfLxCRA9TVsSAnZWagAAXewP/j/accnytlcRGBsgA+So\n3Q5mfsLjad9z11rLPruWZC+DQjrR2BaZrBL0OFWoMVY9m/vXwNZ8knf3BF3j\nPYTKVlXZPrGa9XcRcMA60FNVNNFls7sxjiZ0xbJ2p1yIRLaTlop9ZRU2t8hO\nQBvhbKySJYQsUrO37maxmh5B9n5NkDWUp4tDOr2HnzZAJjG1qJUkcEtCvYzc\n3WC12T6YDC7LMts4Nkjgr3fRWTg2yUOpOw33y+eeN3JAU/FFZqkRnMYdajNO\ncMjxKnHwyXRJvBMDyc2vT47PFYGxR+DYqhPNKxuIgqOLXdRlsZJT+z+I8M9S\nqxF6jtpdwhIN7d2WNGj5Fsl9IvRUvhfZrXqO1i+bAc2NefwF2H3MTbcpM+C3\n44YkIQrObFKUyrs7bWzg8+pp1ECCvxeNZpAxwyhYQXoK5WbcR4nub1nHb39a\n4wWuG2gwepUarD0ZF7EDOlQ5hioVBl2dRNCUFTEXOE5ToqOaqNfDHVV44lEm\nG99oz/kRqU+tNh2ODFuOBsOiVbfYKonFST8WchG/lvvvHBwBv1FAfOlnYUyl\nXfw0/t1WWx6QqlyZNJIjTW9xvthqW5gbjh4IuBcimuagsHDJ7P2Ql3Azsx/D\nudEDfDe34jvs8EhtbGJo9sFkzlG03tl1ltT4IHRjFsOYKMZbDh9IfjVRdTHe\n0Q53\r\n=ox3Z\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"486623552df2d68df0bbce1d48bc17c08ae54741","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.71","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.62","@balena/jellyfish-logger":"^1.0.42","@balena/jellyfish-metrics":"^0.1.108","@balena/jellyfish-environment":"^2.4.23"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.109","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.6.0-disable-notification-sound-486623552df2d68df0bbce1d48bc17c08ae54741_1615196913240_0.0417983669616806","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.6.0-disable-notification-sound-6aef2c24c38bdb7a3e346af7005e7b13cea9fbea":{"name":"@balena/jellyfish-core","version":"2.6.0-disable-notification-sound-6aef2c24c38bdb7a3e346af7005e7b13cea9fbea","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.6.0-disable-notification-sound-6aef2c24c38bdb7a3e346af7005e7b13cea9fbea","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2e121c934bf396a276c088a077ecc0c03330dc46","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.6.0-disable-notification-sound-6aef2c24c38bdb7a3e346af7005e7b13cea9fbea.tgz","fileCount":100,"integrity":"sha512-y2f1yMccDXZhFD5pjLCO0QZKZXB8WRvTkfsRlr2UJYGGzSiI5bYaljqMqdVaBSVC1YmPUYLXdJcODAJx8dr7+A==","signatures":[{"sig":"MEUCIQDepoStlOZ4kUciV+mMiUX87ToAiE6ql6lZ5gCArIeeBQIgLFhXnQEq1+KbxXOmG/8A7azCFSVbqpSj0jpSwuMoxsQ=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":626643,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgRgHuCRA9TVsSAnZWagAAsIMQAIfd4NIg8Rwxmbj12PMw\nTtpQGwe3UxO45UfQP0eMwmlTdyDq1roVvoHS7zTSyfCSdk900wfqgk1GSAKW\njFZ3FF7QJVPPj9AhLs8g7JP0ZWqcH4fRJ1iiJ8cs8sl5DADboZqXQp2FE49h\n/G1xcQZ5YL7efcdPRd3xJUW6TNcoV5289W7TMo203xGji2IvbBswZg43htzW\n94+RHRYYqzNeMcIy31bc8PAjhkUSS696+hLdVyqvvZKcqYNVgdKi61u6YzhB\nnUH4JrlubxZfD3FZgqxlAbuYPcd/wiDQdrRipSaPBRJPz7U0RwvAREMQWi9A\n68dUv3iKmXeKOprM/fJpHFft0875acSvWazkt+jd4XzGviYHFrXAHjl1U5V+\n+KZUgzzmgUWKsmv4YJzP8DOu3mZ6bMmZQaXdLZzaYxjsUraBkBHVXgSzygTp\npFR0LoVCCXhe9U9d79/E2xrT1n8sAV/m4X0x8ZH0fk8+ajescb+k9Uny+1im\nwA3NBrexa5XNiJyv0Fdqbxi1hhDDcBGI7tsbEtLkeeg0VPuZAFE/A4bhgxwl\nGlgUcbTzfv9UeLI0D88vim1wiMPiM6MIxR7tQvFkQVBcR+ECuSeLjGmebulO\nPimBtIByuYDtqqzehoVvS/L6Hwc2ciuaVb6IsiBOiSiShogE3seo73nvp4xG\nw0dt\r\n=XUNs\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6aef2c24c38bdb7a3e346af7005e7b13cea9fbea","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.71","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.62","@balena/jellyfish-logger":"^1.0.42","@balena/jellyfish-metrics":"^0.1.108","@balena/jellyfish-environment":"^2.4.23"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.109","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.6.0-disable-notification-sound-6aef2c24c38bdb7a3e346af7005e7b13cea9fbea_1615200749582_0.37475224101611015","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.6.0-link-name-index-c8aa052d28f9902b5ff81282eb93ae6390a88452":{"name":"@balena/jellyfish-core","version":"2.6.0-link-name-index-c8aa052d28f9902b5ff81282eb93ae6390a88452","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.6.0-link-name-index-c8aa052d28f9902b5ff81282eb93ae6390a88452","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9422b94c0a2163a62d757672a8c8f5dbd02c1db8","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.6.0-link-name-index-c8aa052d28f9902b5ff81282eb93ae6390a88452.tgz","fileCount":100,"integrity":"sha512-kLaQameviV7LNQYtwZoJjakTPwHsbIPufLyxxyUkEXFRNOAKTULMvMpI7wgnOwWLZMsD3Gu/PB9ByIsejIW9jg==","signatures":[{"sig":"MEQCIGlURTTNAcytx6YYJE5yf+w8mIsc/mSZJmFPKga7esf8AiB7vmSju8zEQBz7VzcXGiEr57k3qRtT6ZyM1VpWqqtd3A==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":626498,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgRuN0CRA9TVsSAnZWagAAAcQP/RoAUMEf3gygMhr+Kg9j\nelQF0JfQmWCqadwtdR5hvNTYGcQ2X4V3cHDMjZ2UHjUeNSxyKp3a+jfSOkEf\nPHA/Il7QRg/EvkeHb3ZnGdwV/knvEJhT7OW19UdvpYcZtd027MQtPZHSoHeI\n/CWWG/8hSVf4MvKnVp4i0sICfQOyZ+MQu/div0Uu8S1AYRlqWTKrCKeu+Uzp\nOQp9kL/9S5y7wTL5ECSJ0Rezr/UbVRFfDH8IQionuvX+2uhOuEEzffYIJ4Hz\nQsWhtJutvZMn/i/heY4gQQIzVf31LLP1l/A6SKlBQOWoRZZ/q3f6LJTWXGt3\n4CBaqYZcI8xp7hpChG4lACXOvtR5+Av+RTKcKJ9iRV6hXfSyX7pwiQp1S0Gs\nWAlOZrGqZl3C2B6ssvxPDckptk25t5xzHlUCNm9PA1K1P9uHCydOonk0T1oT\no3E46VFrRtWYq3Ym8Lm1++uYIm8OCKxADO+pY/OVjgsWhWmuiwPOleXsguD/\nLmKLWBIUPYDxmvxPJlpnuPZzXP1Wnw/kxcZMc0v0K3L8xKn0RVMSLujJHqO7\nq4SEsoekEXuRB18mBQhinWCZ5wIGzypMQtj1Pbl3QBzt/TzJ/gsUtlfp04ed\ng3QeAuwnc5A+bX7Qr5Lce73kaR7KoErOj2spTYRIbTEHpqtTGMm9HVH/5HsM\nAVzF\r\n=lPge\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c8aa052d28f9902b5ff81282eb93ae6390a88452","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.71","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.62","@balena/jellyfish-logger":"^1.0.42","@balena/jellyfish-metrics":"^0.1.108","@balena/jellyfish-environment":"^2.4.23"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.109","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.6.0-link-name-index-c8aa052d28f9902b5ff81282eb93ae6390a88452_1615258484189_0.07671433638717073","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.6.0":{"name":"@balena/jellyfish-core","version":"2.6.0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.6.0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9a38b69f208a3b64ebe28410aca36446a5eea703","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.6.0.tgz","fileCount":100,"integrity":"sha512-zVaqCFTFzcgMdAy/G+HlzpsDWkfHZ7PkUpOSIvG9sQnT08aFrOop+Gbn+xWDwbT2hTa/J29nYIKp5+yWOf0hUA==","signatures":[{"sig":"MEUCIFExTxmLL8rZ/Itw7CtcMq4HJw7toU4h8qMx6up6fIcyAiEAvJ4hUALViE8DW3ysVu7C2lLacSauCz9KpY7lWFUSCj4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":626441,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgRuTdCRA9TVsSAnZWagAAaWYQAJLNA28h4oMbt+O4xy9V\nk7z8P6b4ADbxHfnu3hWhHgA2PKtTiINhV1lnFqVm4fvqAinP1dq3Tyfki/lN\ngmLrUkWbdb1MCV9aPXlm0cCGGUTYBxlUDhsGG4cPLwraNQzBjb4ozDzEBjZe\nc1ux7IWtKlqLIaWwnTeyqbn8qyueeLP9jL1f7VMV70WEEaWLcm8ByIomoQ+d\nrRk/6vNBQX0hHRLPqUWjZhIjpXA5dXq7Fdt/fZlCT6cjGFG8pKrlb1olTbuP\nTmmyOqHt+9zaORoBOLotwmDSzvgYk50P+KF1QXOAkYBXZdlZui0QSArNsDNZ\nXUVcDBGXhsdE/O6UETnlPwYa7EUNy1htEQ6zV60bO0BicVCr8BunwJAmHpdZ\nn8cM62il0im9XzoL2kk3pOPZXj6nbIx2egLTW2tjS8AMWMGMiqcqck4sPquV\nz/9vnHD5Zh43hQyvogAqtr93wzc4fHmUErsfJb5eGwoRmH+PMHoBk0YpD40e\nfJXKJoiy/1WLi+0YMmvhKXvkwe1J7chpL59LMls8scOWQtdQ3C2dwnP9ueoJ\nrA+e3w+X5QI2mSP/4sOSqHA6kP1EV0QfHvIo6o29EFzqEDDMDLrFsU+9bBLQ\nBNG58h49LEKMfzEv1ZIS6IVILxvUSBtfHEDE6pDSDf5bbaQN8uZbyGSmvc2l\nTAMC\r\n=D1+w\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"291e69d98a40b40f2df5f55f0e82de349bb4437b","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.71","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.62","@balena/jellyfish-logger":"^1.0.42","@balena/jellyfish-metrics":"^0.1.108","@balena/jellyfish-environment":"^2.4.23"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.109","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.6.0_1615258844838_0.31379118946909057","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.7.0-5508-pre-build-versions-325f81642157acebe265ab6ebd5eb4f56d183e21":{"name":"@balena/jellyfish-core","version":"2.7.0-5508-pre-build-versions-325f81642157acebe265ab6ebd5eb4f56d183e21","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.7.0-5508-pre-build-versions-325f81642157acebe265ab6ebd5eb4f56d183e21","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c9e33efa83a7bd700af4a5cd1d77d24a0af1fd5b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.7.0-5508-pre-build-versions-325f81642157acebe265ab6ebd5eb4f56d183e21.tgz","fileCount":100,"integrity":"sha512-yfH/YPaRuT25x4+VhyenClsTa7NDYRT0mFEfRxumWB2nqCLd73Ba9o1mh3t3oPgW0OSjV+WXfyqzNJxLxhCXGQ==","signatures":[{"sig":"MEUCIH5xDfIsC0ZWBPdUoCSGB4d62EIbG/WfDAeyzh8oWLOnAiEAt380C4CV3Vn0ir7oTIyq2Joq2CxnG/p4rLCV0mFV0I8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":633942,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgRu4ACRA9TVsSAnZWagAAQY8P/jWOMPlPgJGu3Lat27Ep\nvGC1RMTwF0lWLhvFyUjY+Ts4o2lrA6R4sf07llNGWGy4v8sen6ALrHyL93RC\n+kF1BxTBzOOvG7ODwPKFkR3DbDdF72J5x2bMm+cjI7UHEbaVcdOuio9Le47X\n3n58tRjdc5oLogWAlWynyT+Rv9h+y3COaH3sv5GgbcNvhLAduBXEOgYsIOXB\nMBBoSevc9kycmHCM9pt/b6Jw/F5H1x7zAcrJDbE/UMzPuFXxAz+4qp2Ri9dQ\n1W8whJa9KlGw9vTYMDJ0NrVJWlIc/aytVgRB5V2s8aiadoD0xoHoLBEWDV5x\nnoGQaNioHiUwkZQWjD7KmEHrDYZXctZyZJMX3MfdtZqd3Jj1D6Sk2Fykw37S\n7pf/OIy9/e+LVbk72ZTVn1wphyyqFgOyEIC4mdu/wQSEfrnqzBesV5OO7Xm3\n0gEcIBcWCTJf6kyYGMMg9uslns1LRuocHohcbusrogu79u/mzCO2C6+Eiipa\nORYTtgbEAxFQyOwDUTMBiqRT0lkdtsE3QD1ZLa7m3KwtFnIJ0r3ZmueDHifV\ngERdldD/1iaB1yEpY0N7fd195BkKTLCGLeK2NEJyHKJfaDXXrzeTzIZy8fnp\nK3zjG6FFLJ/ltX9Ted5KlOc43iJGuuGKet+HdseuFmnctnVMDmmVsXSQ+dPp\n18bp\r\n=B6cI\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"325f81642157acebe265ab6ebd5eb4f56d183e21","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.71","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.62","@balena/jellyfish-logger":"^1.0.42","@balena/jellyfish-metrics":"^0.1.108","@balena/jellyfish-environment":"^2.4.23"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.109","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.7.0-5508-pre-build-versions-325f81642157acebe265ab6ebd5eb4f56d183e21_1615261183816_0.5706716853282598","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.7.0-5508-pre-build-versions-7d71a5299004fd67c4bf5e3367384f597969d9a6":{"name":"@balena/jellyfish-core","version":"2.7.0-5508-pre-build-versions-7d71a5299004fd67c4bf5e3367384f597969d9a6","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.7.0-5508-pre-build-versions-7d71a5299004fd67c4bf5e3367384f597969d9a6","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f5af61a2a7026fb5e83316f68163280d7a460f78","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.7.0-5508-pre-build-versions-7d71a5299004fd67c4bf5e3367384f597969d9a6.tgz","fileCount":100,"integrity":"sha512-n41OsTDQRslaUYwcB6PyBVFdxK/glVg8lVgZOAeR6Y4yjrbZ4ko1KKd4HbL+qCX0H9kSLz0WiAfcoTtEVZhNTw==","signatures":[{"sig":"MEUCIQDC/ag9mLuRV2z/gEzRLRdK6G6kQE73vgW/5QvJk+i/9AIgVXlkb6flHgql3wecQvzBep3vVZtw4FtCY54CFfetIk4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":634746,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgRwFFCRA9TVsSAnZWagAAg9AP/iYWKccW7+WYGGFKusjU\n+uLjut1aCbQGluMHFDCxu0pmjr1A3MZY7bvaiFIDA7yGiwee/sAn1vR9lm0y\nzitSr68ZOlngGoB8QhMa6jx8r3mpEgouLjlS4pkLnJ0RVZTsuL6BdMOGAIY2\ngBmv91+0kn1HwT4rx1pH6LHH8vdtt/I1bHBwdIwJGweFpLUAf9gnjG/PQY+U\nM+fiBvDz58WaroDmp8S+PTzjToEEuuAsEvkW+8N1dQ9+n6DI0HwdNnUMyDri\nP4kZe6No/Vn/2peYQI/Kwh1MLOI+Ekm3Y2qiCkoec7bwQMuCjz2ssbQ4kK3i\nZRfl2SAjPN5fVWCSqmGbuYzpWjbWwzg0rV9L8i431VM1K/2YRjsuzEwb/KZG\nYgaYLEtC7/8mjXhir7Lmw5rnRm76tJTYbqXobzM7Bsf8OrlnIzDfZIFxsNRE\napTKcqsvpO88vmRuu8BcUdM+3L3z/EK9gYVpKV+tTSBToCl4xpHM874uc9G4\n7CKKZjYnMgkVbUewRG1mqIg4AGICqyXLsXkfy9fL0kNbDH5dZGjJwWwmcY8d\nhT1buY5F5UBvzDf8HC4OddFBUD0Vj5eXkzfsQMZAyBpoWqz+ln0Qh8Q8coLV\n/TK1+bWBrMH3PZ3JGPW43mqUkm01Na5m9+aH4arPg4xpufzuhxhaFTz5E/2U\nCR72\r\n=fAly\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7d71a5299004fd67c4bf5e3367384f597969d9a6","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.71","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.62","@balena/jellyfish-logger":"^1.0.42","@balena/jellyfish-metrics":"^0.1.108","@balena/jellyfish-environment":"^2.4.23"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.109","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.7.0-5508-pre-build-versions-7d71a5299004fd67c4bf5e3367384f597969d9a6_1615266116312_0.7611188397331106","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.7.0-disable-notification-sound-661f3fbc8cbb6bba63589af80b771abd8628a888":{"name":"@balena/jellyfish-core","version":"2.7.0-disable-notification-sound-661f3fbc8cbb6bba63589af80b771abd8628a888","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.7.0-disable-notification-sound-661f3fbc8cbb6bba63589af80b771abd8628a888","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"cc904fc507019f2f306ad584cfdc68084887ab84","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.7.0-disable-notification-sound-661f3fbc8cbb6bba63589af80b771abd8628a888.tgz","fileCount":100,"integrity":"sha512-f2jQ21tcya8iBKDmvjUlG5M7jJBbLWwwV+ws0GaGyzJmlKj7jH2LeJ5uYlQg8JKRvXmHckGpSDzRavcIfeqLBg==","signatures":[{"sig":"MEUCIHXmZp5PSb9xBPu/fi+528m/2sEDnILqGnZACDLZNHxAAiEA2UsLRq8/oSOj9ByHhT1SESRv8QSqbIDputcKOmoCOek=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":627466,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgRxNFCRA9TVsSAnZWagAAZPIP/iPws/Z8TuI8jC1Mnvf8\nGvPw5OXltq6a77Qq5y4kU2siqUdQO1m5nU0vjLfB87f6FvLDHpenju/brrNX\nv+4loCWmL5lVZ2nH15rS6LX77ff7Maz0bCh0sy9wLp8azZg7KSXuhS3nnvEz\no5e+upkofjjAZTCvgZPb8ik3LUlZ94bQIzU46DBS6zu+Vswmurz/QR+KO1lG\nFScrdQgV0PHNKloIT+jm2A0qWYV/woRw3NMBVajU5vO0nmHScU8mA7JJp0er\nG4LJpURy4iUx03ciqqcHcJU2w+eTn9apu7MhvK0aGQEuRmM1HQ51QR8UZkj1\naNOC2KRZedzWXI2d+/PD0y6AAp4Bh01obTw8h6Cm1IAoxM4M4gTB1BPrguZw\ngGm0n6l6L6RM/dL/Y1RIqlVXFvzvlf7f+FpNcnfHXeTrourjM+zeAF79+pTi\npMxzlXfcEO7k+feJylogK7zp04tNdg2IYL6fbaMvHt6wV7Ww4VRKGOt7g9S+\n6VKZnjx1MHVKqxUfSjvqUsFvzyshw+OD4tVm8sJ2l+SshGZlDCvIQLROBOT+\nS0HU21dXXUFlSw6Z0Z1/IWm/yd8og0Qf+6NBXrgWdq76qA6uLK4QRRUtMk8P\nshiyUPQcvOrGfruMlhmX1lJ3lLhMXluhRVJRFJxlZUo/fBfIO1x+9MyqJMHs\ngBqp\r\n=R2nP\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"661f3fbc8cbb6bba63589af80b771abd8628a888","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.71","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.62","@balena/jellyfish-logger":"^1.0.42","@balena/jellyfish-metrics":"^0.1.108","@balena/jellyfish-environment":"^2.4.23"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.109","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.7.0-disable-notification-sound-661f3fbc8cbb6bba63589af80b771abd8628a888_1615270724964_0.8716032398079587","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.7.0":{"name":"@balena/jellyfish-core","version":"2.7.0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.7.0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9395837d4aa533d7b7e1546ece622f89715a02e3","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.7.0.tgz","fileCount":100,"integrity":"sha512-nK8vTMTX9lZbqjq2auGBMbS8biwyb026NGcc/+g9LlE1klcqHfhjcGMxipPzLThBmzyYR/0LFly77SCRw55fDg==","signatures":[{"sig":"MEQCIHIkyqBuI1k3XKb68s9BYZmvhVMm50I7ZzHCh5j2dAjdAiB6WjwRhdfx7Z6JVBI2yrP2w62BePkKtJaGlH1sN6AxEg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":627398,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgRxVxCRA9TVsSAnZWagAAFq4P/2WG8b184rZSYUDnIM82\n/jnGBgsOFRUYx6UKjhYB5Dsj3kgnkaG9B9Cu/gkOOzTmVUWrQAepevInwTfB\n0m2EgmCQ7cQUCQnXLaBbKwB8f3v9ZOE/C/E2dyLg9yI+LFXsSwLyzFyITm3Q\nn2pcJjlrtroxLb5VSn6nlKl9QfBlOjpYoU5hJ4yTVqM7YFo3CqCQ2Mu8U4KV\nLOuzAd5tRDy6FqtM2/xKph1rz1VNeuO+azeY/pN69gWyWXrvOyAExyrEkBd9\nVb8rz+BpnCptdJsUuE+PYbzzmA12HoV4UxgTudt9Nop5TfcvSFiInzXacl4O\nnI/nV4oCG0yJYK6/FIri3hVe+OY5AU1AGZQr6g2pgEIq+qufJ1jyihqeuFbw\nMxYnmXeCjkaL3ZvrEhsva3Bqvk9xaP8kaegdvz7Jt0wFMH3AygLItrh45con\npcGpg3SGOvf1dYQC7ARDN4OQ+w7bgJ0wzyuxEfSgeTkmVe2Qy+y5VF3W/c0a\nrihdR+yTafLdcysgjAmqfeYKaUuF5OPG6Gpk/sCCSdYH47ODnxvpfzq5tJ0V\nM2wVJZcdVCqPv09nOCT6hJQtasWyzkBUC1IqcOXWglXNvmod7fwYyz4KFHiY\nDbBlJNdhMLrMpDX9cW+YnA6fxS2y1lTT9fgJ6bpzhUKAiMAULa8q2iecUpL5\nLiuR\r\n=6eQ2\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ffc9016bb2fc70468ae632d0678d7ae8b42481bc","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.71","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.62","@balena/jellyfish-logger":"^1.0.42","@balena/jellyfish-metrics":"^0.1.108","@balena/jellyfish-environment":"^2.4.23"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.109","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.7.0_1615271280826_0.5575456201234243","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.8.0-5508-pre-build-versions-ddc85469f359d17404888ac66b5ce0394c324676":{"name":"@balena/jellyfish-core","version":"2.8.0-5508-pre-build-versions-ddc85469f359d17404888ac66b5ce0394c324676","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.8.0-5508-pre-build-versions-ddc85469f359d17404888ac66b5ce0394c324676","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e8f69519df611551a075093cdaefeb1292f5efcb","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.8.0-5508-pre-build-versions-ddc85469f359d17404888ac66b5ce0394c324676.tgz","fileCount":100,"integrity":"sha512-bU1PtwMBFf5LdklAGa4W8XF7LqB/bj0UBoylFluGR9FTGoyWEh+tzHSuyPaPMQg/Tzv1rcMYkDjFq4GkGzMY9g==","signatures":[{"sig":"MEQCIGxo8Oy58w3cYdjXssw28M7G8sfukkXx9nULJvEgekdPAiAZhSd4GCsUIq9z4lxdoqgzsJO3z7WN1uk2MTqCQoLNkQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":635303,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgSD9qCRA9TVsSAnZWagAAimgQAIWjvLYRf5GMyPSrDzKP\no2Cc9Wff0Ru1AJRJxCekU5Rq90TRpHHFF8tL6KlrX0Y3wP5yk3fHaT/ffItt\nrU+Fegz2QNA4fCaexMUF3A08kRFTg6zDDvkK5mbWO1w0l13YRvstiBUx1Lbb\ne0rhywDK5aoknwGjNF8NgEq9HXd+a0bGG2Z4oJ6rU8HZEXRBMWVv0yY6rKn5\nr/g14g+3OMimwgggWMVdpFTX6ku3/gV82a+l+NntuoAlS6HOr+eg0EdCpbmD\nfEaxZ5E2x5NzadZYzKkDE0iVzMvN02q+f3HolmYI/5sz6RWOKoxqixeGbKx7\nRp4XDFpFOB0L8L4muw87RY21Zp6EbmdOvqAw0I4aN1AbNDmP8fq2h4J+uc8s\nFsLQIlaQG/B3o3VPfOlEHtLKqyuW6oVtYB7bI8XA98h0R6zF8lCE4DVx9Vne\nFRSyl+P7Z0Rxy7RDELgqYOgq/TVS2X+o+pDwwJSE7c4CxNXbFv7973+CPhjc\ngEEj/zkIYuS76VZUBbyxYDY5VdAWSMIOJci9AfVF8G8PVJeQjRvgz9+gGedc\nKS2LN3uzN0wNeah0R/zlriOoHh2kC/66Jz1T3hTUapUhtB74Ne8IyaIDBPnp\nAqh+lYUyi/eRSxNVJWm+REzhC93X84UOjo1gS9M+77T4+T0wHRWqz/p6JePA\nwCbF\r\n=n4mO\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ddc85469f359d17404888ac66b5ce0394c324676","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.71","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.62","@balena/jellyfish-logger":"^1.0.42","@balena/jellyfish-metrics":"^0.1.108","@balena/jellyfish-environment":"^2.4.23"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.109","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.8.0-5508-pre-build-versions-ddc85469f359d17404888ac66b5ce0394c324676_1615347561853_0.7860350194141601","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.7.1-renovate-non-major-e211a8fb90798c1af3887db49d4c21843a7a781c":{"name":"@balena/jellyfish-core","version":"2.7.1-renovate-non-major-e211a8fb90798c1af3887db49d4c21843a7a781c","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.7.1-renovate-non-major-e211a8fb90798c1af3887db49d4c21843a7a781c","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"50f6822c13a573f0746a088bcc85a2db18708176","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.7.1-renovate-non-major-e211a8fb90798c1af3887db49d4c21843a7a781c.tgz","fileCount":100,"integrity":"sha512-EPtz60X+PeHBvPcPY3OJBq+gcXoqVZRyuR3/+ftqdX124P02xamSOo2M0kS1hKOY8E4KMqZvEffTJJhxLfXY9A==","signatures":[{"sig":"MEUCIQDjubxxzP8Bl1Y46wz/YZhNl/RyOoaDeBJPqBMR1o2elgIgdKD/v3WlFT40JB98qp1IqYavpJmVr1oNxnNdOHCUK3M=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":627805,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgSJKXCRA9TVsSAnZWagAA/f4P/iI529MlIhZHcH6ndzwC\ndDMe4aTLkcilro6qqs9uaBw5YdOIMUDwpUK3dYMaIxdUxqsS9yaLlgat4GQn\n1T1a4dfff8RoaspkyPXgpxgX65qV/1eHLh+/WhW1dBbbU7vxJ/k7R49iDeV7\nYuF30qt0S8BJcS5aRNI926XaM12RR9lJbA7cc0HEKVqPEcKbbRGNP+5BJQXU\nxwhruFvCYXVjKerTJMZMLWeIMSDM5Nunw9TpDw9gR+YQngSdM2PCwvjlfX7t\nD1n9xFTM6MJhFBfPzyjTUKyZpoqna6wYzLk/eVen2ZiRaI0Q/0Q/J+6WsOK0\n8D5I9PO8tTHIVpTOdpIEDvVwlFDiyeksndob5zKhfwI5Wbg2w1uIlmrLQv9X\n7CPrtqY6WrBKuOu/SsGcnR1l384df6Z4UFfK7ZdE5a4yqMnnUWh0La1i99iy\n85zkAqjE9nG/oq5QZ7schwQhbDmI57+Z7UdqBkSiwg+Af9srjzjWy6JvHSmt\nEcelr5aYz6dDhER41Zb/9hHjuom0QqDvnqaCFctnagJu3TyUpQLF7VKsApID\nECamX6po7pnDkW4cjqXIng28zzbBms4fwWZ0FHJsZ6sSzs/pXErpWhAy7VGs\nVCaFXkXGxm7TYqbqSwF48Sk5rkSQgx9ROK5WVwnWK3AD00eZVlRG+r1HNuA9\nIp5f\r\n=mlUs\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e211a8fb90798c1af3887db49d4c21843a7a781c","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.71","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.62","@balena/jellyfish-logger":"^1.0.43","@balena/jellyfish-metrics":"^0.1.109","@balena/jellyfish-environment":"^2.4.23"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.110","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.7.1-renovate-non-major-e211a8fb90798c1af3887db49d4c21843a7a781c_1615368855102_0.6455033388097433","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.7.1":{"name":"@balena/jellyfish-core","version":"2.7.1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.7.1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c6e2ce807abbcc67b708b712d7e2a1ac0ee214b7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.7.1.tgz","fileCount":100,"integrity":"sha512-h5m9FWnpEIMwkdzjczEPOwI7ihKmh7tuo3e00WQEeZQI0ZacydA93fNt3gLt9GKY0OaBU8jQwm5LC8mjKb5pww==","signatures":[{"sig":"MEUCIDZV/EppQXcE0byKsLuPa+CQEzEEvpKQjfUptVKX3GV4AiEA55lX6P8MPrse0VH5SoByjvdhi5C+g1p5S/hnPfSdixg=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":627745,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgSJQRCRA9TVsSAnZWagAAY1QP/3LuPxBvuwLHpI+9b8tV\npbiciu6BKy8kqFJldLfjZ75miQ7/phfw1VtcH+/PDVllrki5Hqf8ebTfscOc\ntDJ97wDXB1f3QgzgqezpPtq8M4blV8URxAuUKQSaS/8GAJQxQ2bkvPyjA+DI\nsHXzZTcngRnAsIhqWXND8eboWuN2nGwdSiAeRzYml1FkdDpRkawBEyfwuwF1\n2k08KUqVcb7KgKvg0sbg97BGGm/1hXRioAFgkplbGVcRygQZPGnDY48S0oQN\nGrntR0uWFqUDEkv2vpr//Tnh/Ak1MV22d49pO84ynx44n1XULOPFQiLcWy5P\nOaUlhFPoR1nqKZoInA6BTa5VboEovJJoKDQvq8mNHS5PdZI1Yp6lvfwWJ8GW\nAdz6wNxQjUGgxihH6lF9mCREi7p66Jdmdam5tsPqbndp4E/kTTQTv7QroKB4\ntM1Vo5gaTSZ73ii1R8fiDNDVDW5VvCwBwS87xGLj9cnHxL3Paa52wP8ZbgpZ\nsMwFuiXBHzEkftI+ly9VmqV/l7XZntlDQZYj4NfyPlTWlywL4S5tAACA5MNH\nyCfBiHevZ71KvG7bfgb9QDILhYTAW3c690zbmU+FF/qM2Bao0AkUe6sEOkAM\nrNw4EDFE3R7BIjWo6G8jeyi7D6g9YCMWWcxjexJB47oCFzbkkjyhEm8Mbhi0\nQg3V\r\n=07Rg\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0ccfd8a4a3cdd60ad5b195530b21d4a628c263e5","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.71","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.62","@balena/jellyfish-logger":"^1.0.43","@balena/jellyfish-metrics":"^0.1.109","@balena/jellyfish-environment":"^2.4.23"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.110","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.7.1_1615369233022_0.9687926293817426","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.8.0-use-jellyfishlink-widget-0d722de501182049612e639adb40ed555836580c":{"name":"@balena/jellyfish-core","version":"2.8.0-use-jellyfishlink-widget-0d722de501182049612e639adb40ed555836580c","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.8.0-use-jellyfishlink-widget-0d722de501182049612e639adb40ed555836580c","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"fa49bad45652e28fc47122e92b049f94283df9e2","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.8.0-use-jellyfishlink-widget-0d722de501182049612e639adb40ed555836580c.tgz","fileCount":100,"integrity":"sha512-cvHy2gp8CAs5usFo13sD0emy/5Yh7aa5750BoPKAqU+dts14bOdSnrwsM7cgvJhMwimLyN/Sf3tOV26mLlw5kw==","signatures":[{"sig":"MEQCIH58WU9qTo52vD+IehiZ43qhwGWhgwPI3YrIPSiT4yLlAiAPblINnaqMpO2zQ/g4J0Wj5pbuQzjjBjJs0cyBGTnFBA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":628594,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgSJnECRA9TVsSAnZWagAAgFAQAKF1v2VbIdDhPVL9dIro\nQWXJ5m2eG+juhGst2wm+EkovCzKlIANYHWyTHZarCTxN9XE5N3iX8IBWoH+z\nMOnMzqwC9Wxqrb4fRNUSSN2STNw4kXwSVv/QZrRtNjzWsB3PkaoQpndfKixZ\nDxYFJZU3ZuQY8BQQCAjexDSfN4eDgOyuCTRIAtXKA7RmZNdqgiaUmaUYw5AL\nUDXWGegE7NAi8RD25yXtk8Ph4yQ/4OPN4graz/fJ984qjlNCM+bNaejaC77f\nZkM6JwRnMMlCMHC2Dnty6WlUE0AeZXrMwHG31iyEXwD+wtFo+F2aEuHPLEVM\nBUyCwwwsEWmurD31wsDTzpwupx8hNPmPwqYs6DSYkjb9GzWqwX+ggp7GpZnY\n1oWGAq8g0D0L8GLn1LaA03LeH0lC4HEn2XrTh0ufLDvEufsa9HtQLXxV9TT/\nFj6Dcs4+u911UxnA1ZFSsi6pnMoXzRI8B3VVM/uvdYxZNxOcjeKxhnNC+ool\nDTdwxcEtJNs2XaqpEmiPhpN/BOap+zrpq6uG1dwmmSW9Pys8Zpsb8p/pMlk+\neYz4hrIGmHs2M/NY3vrfQ7o5to51beWI/GOVwlVJBMAVyqQGPAY52wwC8WfQ\n++4wOEy4Gfnnmt0IpybJjs/gOmEOAUVBZvqcC7GecQHchgitH18EICBuKU7E\nODOv\r\n=5l6M\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0d722de501182049612e639adb40ed555836580c","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.71","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.62","@balena/jellyfish-logger":"^1.0.43","@balena/jellyfish-metrics":"^0.1.109","@balena/jellyfish-environment":"^2.4.23"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.110","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.8.0-use-jellyfishlink-widget-0d722de501182049612e639adb40ed555836580c_1615370691828_0.01592453184689968","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.8.0":{"name":"@balena/jellyfish-core","version":"2.8.0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.8.0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a1d17d4e78a5cdfbd82b9c5bc681f83cf5726344","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.8.0.tgz","fileCount":100,"integrity":"sha512-w4EDInIEdwARf+bvQCVM8UmcjpP4nqPW7cckVrvwZTxMEA0NDtkYYgrZR9sN1eZr7eQKyLvD+mhBAzfm3ZITLw==","signatures":[{"sig":"MEQCIGJp+lJR8avL5pXcm9n4c0LhxeR/T9awnyy0ppzMXcqgAiAW3rRwHf+EbE/5VQUPwnHwWq5hfVhihf4s//I89AhHXw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":628528,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgSJsrCRA9TVsSAnZWagAA8a8P/1F05gZSOdlwhwxfQUrD\naRJzNyhbF61RsqZAIZBbnv+yAwUmY4TXrMH+3wvLh5X4ch24rkUe+FyVNUZW\nrwzd9LE1kh/0yKFvbDoBtT2gGD9e46FbpWn2OkvsfTurZRfTecr85BxBSfLA\nsS/H7lIpxcqxrDprUjBRdG76/jPjHENRVsV/TFj6zkxEwMGWliUc2OvLHqxI\nZJ4bScvxzeBVw6WNYNWFdqZdYwNUl7BsTwAZHzqdCFmR7KfT4faq6AnBVlIq\nBnckucHun/pM9CpTtajA6rrfWySzTBY9j88M0wYWmGoM5hCNxRFbB1h/Xma7\nGeQQyp2y0gBevGPOwEx131UFOJFceWDldmipmKJlMi8AjfmOawVv15Pgid1B\njPbYU49k+Qj6VoBH649jPZwC/Da8Ytzl/Vz2B0cxxKdfvWU3z5JBn9P7wtv/\nDb2F2xzavZRAaHnHUnjb2JmawBj92KCKuIgWySRWlt5toLQx2oAu3d+pug/2\nr1X6CMx+fiCql8CQ5vIL1URsQwTovAFSIojK1dMPdKO9BUl/Bl7xonWqAeJa\n/MoAhTaRyDTjKhpBAmHcVlHnoCSCc3Ff9jTuHl0Re/KP5wFtrZ0xMQicRofP\ntQFg2eH4XiSXQvOfC2oVkcEj+qQkxdL9UKWkxyOz+2YXzsCAc6+FFT94enQW\nILDe\r\n=TQHC\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"9f1b01555a18a94189df0ccba37ca657a4456b8e","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.71","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.62","@balena/jellyfish-logger":"^1.0.43","@balena/jellyfish-metrics":"^0.1.109","@balena/jellyfish-environment":"^2.4.23"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.110","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.8.0_1615371051012_0.7345677721041342","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.8.1-renovate-non-major-626887742da78e873412df341953e7ed98ebb755":{"name":"@balena/jellyfish-core","version":"2.8.1-renovate-non-major-626887742da78e873412df341953e7ed98ebb755","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.8.1-renovate-non-major-626887742da78e873412df341953e7ed98ebb755","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d454a9cf150b7386ace0957abb215d9e5bcdff8e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.8.1-renovate-non-major-626887742da78e873412df341953e7ed98ebb755.tgz","fileCount":100,"integrity":"sha512-oVqCtiK05BPTIrCqVWxaW2UUBg4vs8AR5y/uvtlBn82BWc5i+tqPZ3G9ptzi9RGI5ADyGCeDN8MQdlWjUh0jzQ==","signatures":[{"sig":"MEQCIGH7R84vvGScc3Ao/VL5feaD4XImL8Zop1CKWSb5QjfaAiAzXjCwPvZMW3nEAqIMmLpJC6i4WVyYEYJAvdHmt2CELQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":629013,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgSJydCRA9TVsSAnZWagAApCwP/A/lVEXQstNy3HEFY+Hp\nPo4riX1YeIWY5HQZjleHRaHRkNtk+uyocEs79bkjeK4Vvs1dKms3n65xUCII\nqqfGis7p86wLCX6tQ3IDlYRAw7xfCMiaXH30YSJktqQbRT9sqpBi1ncd/VAx\nluXBkurZ6spt9tvRyoY1OV+VsLQlK3+fVmYYmiDsdxa6og4EGD+kot9qZ1tk\nBghy8Wtu2riB5gYB5wZ59lIdRaTCwzdz+nVm9R24f3TaQfQbRpy9fJ2kt401\no/UkuCJibENi54JNbLBDzY6ca9hF0ZeXoXNXRc4Dcvk8o9XoYHlxGMflmdsS\n/a5vLWszSU8Bnh8QljJ2g0CV+fqLeCGYc0zc5hmQ2k2pNY9hAY0kPRL6vcK1\nYioX+bDFck37XlZcD9PTpuDKft5NEHuWlbb1Tju/r5SRO1um4rfbioMdDda6\nX62+NfgBju9UKy8GP8bgD5uqCAqrZY//Fs3Y1oioWzuxiE1hvYrMSATWAZE0\nKwILfDezFh7ZkFS2QtLZZ/7oE0QpChMCKw3WybVJUQHxztH/01qZ3Vo+1XYn\nd6TswRIZz9pYihwJFHm6vepdaokUG+z3nA6rzjZbk6N9jQb8DzgCl2VhJP8m\nys90iEiDFXxiZAT5eDIbPTrAe4Qkphr2uMqwNqAomQKNteONfZ/ZT+8vQNsV\nQA+4\r\n=Cd07\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"626887742da78e873412df341953e7ed98ebb755","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.71","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.62","@balena/jellyfish-logger":"^1.0.43","@balena/jellyfish-metrics":"^0.1.110","@balena/jellyfish-environment":"^2.4.23"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.110","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.8.1-renovate-non-major-626887742da78e873412df341953e7ed98ebb755_1615371415684_0.03166249801724419","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.8.1-renovate-non-major-849d7802ce4a071e9f0c3ce5a79e087ed9c5e6b6":{"name":"@balena/jellyfish-core","version":"2.8.1-renovate-non-major-849d7802ce4a071e9f0c3ce5a79e087ed9c5e6b6","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.8.1-renovate-non-major-849d7802ce4a071e9f0c3ce5a79e087ed9c5e6b6","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"574b913a5bf37f4ea411f5ee1bd210dc348a8f3d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.8.1-renovate-non-major-849d7802ce4a071e9f0c3ce5a79e087ed9c5e6b6.tgz","fileCount":100,"integrity":"sha512-aXVwbWO34F1hzsCLoqDdi6hbLpErOEW/dZbIX4ZPxzG/Q09PG2hjpc9n9wWgeTApYNd4vabA3vfCVXFdj0waMQ==","signatures":[{"sig":"MEUCIDe/juow4BypsO3ksT3N9wbhbaNw8lATy7vsTwHul9LSAiEAm4UTKZOgKmwdKqO2cmei+/4w7Yd8TJ/I9bjfQAc4QYg=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":628935,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgSWdvCRA9TVsSAnZWagAAmMgP/jkiHBGnI+Bo6xDXwsWE\nNII2AcHgTR/X+6jzfbppRPlrfcr64owNuTn7ID0lNdSW2c1E0lkfagB4NV7s\n/dMkSbqat4vkFsvgiu7LWWrddK4MxEtD6nEx/6rwd6aGjWxa1sVXFuF9Zy/R\nrPW1huB2ZMfhqK0JJO/q3xqmh3O4uTuT94p0RWWyX7lg3CREEu6L/RVfOEOE\n49qNK+/H7lDxIPAUa6HSmjK2HWwJjn+lJMqVHTcqvi9XGEMKVL2YA9E+G50l\nLImMQpFC+8eJrRRDO5qfXE55Si6dRrzO13zZlxzD/eb3u5nss0M/mYfem1rJ\neC4TPozvG8rIhkLqnEddhS8TGxqSkGtpTdDxu4eZJOJZft4xGVTIrEJa61Pm\noyScGVtzo8J0KwX2m2l+ZMgg+pWkpWH2utaPbQebllFmCFIgYr3vC2CYpSlX\nkOFhaYLAvyadM7FGmb3X8CILghnVj7XkAoxAVU17IbYnZxWsvLVz6yGNO3ex\nL9hTTALiZi8D7au7QREz2Sp48CbvvhbHruXdngYIr6SI/O99shmrpMko3s1v\nhiZWUM4bYGZ7aPp+04asUWhifvScKbbYn6QIDWl5GoC1VYnh484MbGJvjob4\n8kh6vHRL20KEWwWzmuJLNYLa9he4BSP27a5cdVhwq4jKGOkzRsMkVKfOv2BX\nuzJi\r\n=mCdD\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"849d7802ce4a071e9f0c3ce5a79e087ed9c5e6b6","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.71","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.62","@balena/jellyfish-logger":"^1.0.44","@balena/jellyfish-metrics":"^0.1.111","@balena/jellyfish-environment":"^2.4.24"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.110","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.8.1-renovate-non-major-849d7802ce4a071e9f0c3ce5a79e087ed9c5e6b6_1615423342849_0.1713739837084547","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.8.1":{"name":"@balena/jellyfish-core","version":"2.8.1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.8.1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5ac6b24f3077a95a15043ae7841251c93a6cfc5e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.8.1.tgz","fileCount":100,"integrity":"sha512-Xr6Zcs10sruU+tJElZ9kCgWZHbI7oYAYumil1MlFLywhGrPT3NzDFW9m6der5ygyzOnNX2z2fcSrKj0PxKQUBg==","signatures":[{"sig":"MEUCIQCrIB2SLZvfG53Xp3zOnrSqCfAqQ77USkOTuMQLyQSCzQIgCuYw/A4C50RFGOin4lj8HJL2MkE8YFYeHqoIyKSwIDg=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":628875,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgSWjFCRA9TVsSAnZWagAA7RQP/0DMwcpN0ZSUU5g3aeh6\nsWiA9XlpJxE4qSreac6l+Jz8nKdLA1WGnhyV/VjQ5hOriDgQrAjkH4TZ0VHW\nx8zfnTU9hw7hxqkI5ipqi0aw2G9Kz8DbJTluKkMihHf3tZNNmN0R+B4gLa3U\nLmZqa/idmA1f3fIVh5e18Mbx6egZIJSTeuJrENilqPleZw1s8BbSWCh9UOty\nm+er4PoFS/XoeWBReHc8BDFf52BXixXOztYaFtMx2oNbbOIOHOduxuOG56JT\nVkA+OFc4zH7zPIEQLNgK+FmuO1EoadxRhTtSO16F55hO/5fQIs555ZkyggAA\nhrN0PS4wFlgwR70RChTXP5SHr4cjipSiyCm9xJ7IXNhguv4yWQrAd6TDpbRf\nAPxpCSJ48WdxNEE7R73V4aX2Aexbh3kWvm4gP5PJEDb9B1ra6U6E4uEDKz27\nfAubue0oo5o/ecGENNOOdxmvE/wD6W2GtkFwjkp5Dv17+V7V6SfMhwrVp5AK\n8eYVXMBWQsHSyToqRddPJD0F/CvVwfExNJA/Ni9y4+WSyHpBfZZyPkAKU2SU\ny9iIWh77oAyvMa0ZHjRodT7HFuJ1zvR8utBNsPie6cd3IKZdavOCfZYAUglp\nFuW+lQJjzoRO0IynbtNAIq4DuU1mXf87/0wX65L8CvEyb5rFs9kFhPfAsz1A\nDs69\r\n=Vc65\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"3f7777f9717c87a45caa56d74d9a47dc05f099ca","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.71","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.62","@balena/jellyfish-logger":"^1.0.44","@balena/jellyfish-metrics":"^0.1.111","@balena/jellyfish-environment":"^2.4.24"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.110","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.8.1_1615423684946_0.6332751146566875","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.8.2-renovate-non-major-696996055950645ba7951fa560da599207445dba":{"name":"@balena/jellyfish-core","version":"2.8.2-renovate-non-major-696996055950645ba7951fa560da599207445dba","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.8.2-renovate-non-major-696996055950645ba7951fa560da599207445dba","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d0051dc7c30bb927cf6cc9adf5f0f0e473520078","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.8.2-renovate-non-major-696996055950645ba7951fa560da599207445dba.tgz","fileCount":100,"integrity":"sha512-txi+GXO1uujhe5EaWXOBbvFBOtd/MgsGpJT+2jfoP7216fgMa0Rd2UygR4nvub2ICrR1dqCFPrvsohwOSexmig==","signatures":[{"sig":"MEYCIQDWzL0nQ7TCpv+SEnysMRilcXb15zVrVFOS5NE65GXAAQIhAOd7910QGg0H5s8Weg9iFOf946vEy6HvId79JJiW/lIb","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":629360,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgSYOCCRA9TVsSAnZWagAA/4IP/R9MtsVA/VBnSc/LhTgl\nYgJr74QWsnDWtAw3QOsMTv4FDImzlbZPbTIkbP1eDgZNCvAz7glg9Mg0nMzW\nZUI5iWKLy5WC7MomJOYN4ZT5TrvvZHwOfRBro4sfhKRlJc4U+VG+BDbko/mY\nexopgXBElgdpHLwWAKLMSKgRjC74yP27p8ze7qGXuiKcfzSGMg44AxAW6eqz\nScvr0jG3XxJKxJ2NvqAfmn4ceusXOrh6BPYCsAv0dLlq45+R3OCD94L6HeE0\nBkWtMYtOKi1Qv/IA2OAsnWUluBDtl+KFwpiJ7ZfcvzSgmThJzcq4N0mT96qv\n0nW82D336BB0GJOCUD+ktmO1IZmHsF/9+3jMYUW3sCnL4p7TUQc8UxJwKG9d\n2OFtaDkaz3Q7w+uDWDtbxrW3JovdY3H4d6SPREEXChNsQOfDb/6E8R1KerBN\nZVdZkam1Sbw6qGNvTThJkuf1BFP9OkA+MGPI5JxscegNW8Qe4DIjFv6Pmjkj\nuhlxNWaAyZucR5bzaQBzEOy4+r6EiFQ/aFSCSGXMEMfy+K6z2P1HG8f7EPty\nJs/eN9o6exNXxU5Avb6T3/yynl1Wuh0VCNC1zcG8L2s7226BDjUIcOg0DKlI\nD7NxQQFC8y1C7KMuHRUQuGTGeP3/F89S+mXWr8+8Vh3UVPNjZxTgD5z+MYjA\nHPNn\r\n=Eiaa\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"696996055950645ba7951fa560da599207445dba","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.71","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.62","@balena/jellyfish-logger":"^1.0.44","@balena/jellyfish-metrics":"^0.1.112","@balena/jellyfish-environment":"^2.4.24"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.110","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.8.2-renovate-non-major-696996055950645ba7951fa560da599207445dba_1615430530014_0.09447812689885282","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.8.2":{"name":"@balena/jellyfish-core","version":"2.8.2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.8.2","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f4b89df48d3f23dc668478517ba340b853e13658","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.8.2.tgz","fileCount":100,"integrity":"sha512-AF2uztWqxEo1WvsArhb33KtaGmGLHvRNleflLF3KuUjah82byakqsLkEzK5Frn24Q0sY9sm7qWbAP6PFLPQagQ==","signatures":[{"sig":"MEQCICfsZ2ZtRI3RInyRkaDZpXxkBT8QZ26QMPLMVK5olKrLAiBngn8Ds46VnepQ+I5ID8rIPEptBQ9rHANSHW0SImIVsg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":629300,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgSYSpCRA9TVsSAnZWagAAcmAP/RS2Zb/Z2XLgleLXf7Yy\nUDsC//XdZPEdwesnMl71SnGJevkcOzdUuqdsuG0kUxownsvNMoeHbguX1kE8\nvxmQtczWoQ/Vxhhzn66FH3C/z0KG2Jnyb/mpfBAKgZvv5TZ/c8qeT5KgUfJy\nJ/NRS5lVmbjFfteJWIFiq1yklTkebARhWtEq4rt0XDNXNJ+/4kzTRLoEv+mQ\nsVPUi67frYyG55s0LmujvTtjLCGFdGxsY6w3swB5qROgPVuBDqMN/rkH+gkh\nLIGuQUPef0m0qAA/7ej1+43kYTkyImrVgluASZGPHc7CFE1lncYmTTydHlja\nJ3mySM6EexeLsaKSFWwdJXbLrGDLrfNERpt5GqrvVyT579WK9yRU7Xze/1MS\ncD13KEX1Gig5SlKMZGbf5fdACu5L7LmXSzy9QwKIOoxjqvXLu8mTx65wQ7YA\nk5DcnDF1kxlv8vBSaexc8cJO9tUcUpzjRi0ITyU5tXtgOFYY/9oVvx1Mvgtz\nFmc57wLDkaDmFNDvSnLW348ooCrdf4f5KKIHBKMuN+utipgjvECT0J7yu0iN\nmaPNF/MQPkGcq9AaEELSpHt/FjD02gmHGqcszxQOFDjOh7YKNy0uNGn8hFk5\nNx0eIm59BRE50KkPf3vVjMzGmGhSGo44qHpPxzxOrCEwyK8Dl5BiUD0cD+xq\nXeB0\r\n=2SgP\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f6e583def26eea37950b99372162c10fec0d1659","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.71","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.62","@balena/jellyfish-logger":"^1.0.44","@balena/jellyfish-metrics":"^0.1.112","@balena/jellyfish-environment":"^2.4.24"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.110","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.8.2_1615430824817_0.35022300846074916","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.8.0-5508-pre-build-versions-f63c974af79ac7fa5bb2b2857669febe3e9062b8":{"name":"@balena/jellyfish-core","version":"2.8.0-5508-pre-build-versions-f63c974af79ac7fa5bb2b2857669febe3e9062b8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.8.0-5508-pre-build-versions-f63c974af79ac7fa5bb2b2857669febe3e9062b8","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1f9137b95e41cf71909570be7d06ae4d0a01a9ee","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.8.0-5508-pre-build-versions-f63c974af79ac7fa5bb2b2857669febe3e9062b8.tgz","fileCount":100,"integrity":"sha512-G/f8YdMTf5TDnHZ8oWP7vr0Ynqs9uJf1thj8fUYbPHONGSzUdJ6FGPLic3VRteV+2Q+NYpyaPMAKNiQZWhT/dA==","signatures":[{"sig":"MEUCIQDQmbSomoEiD4JNc44yZD+I5i/DZbb+ZWUngjkEgwYx1gIgIp9SmNTenot5DevLWXLNL6KQneauqj/eXu8JSbJ35zY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":637865,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgScyoCRA9TVsSAnZWagAAgkgP/iyC0p0lCi1lZEIMRSNc\nEnapCUzKBFvJVobIdF5nWQ1saidslsYklhpnr+6nbvOKJYppBNtSLT1g41a0\nMnvrJ4kNgh/3poTb8g2BM3Q7uhX4UB/NuRV0j8jgx5haKShFMpaVrkd60lE+\nPNLvOQBFjWqGzmDqJFTBXPxjf9TubvRklNIwiGsQLVvMJUh6ShvG5+ooLNIa\nucttT/tmd3SR0vdXXqf4Jbfx5x0cyvIKY+edkzigVVXM+Ve8vUemKnDmmBUj\nxC0kTed0apMN0p8q1mATGipgncAdLU2nk5vPOP3r0IvLf7fiHAq/eCyJqGaj\nhX4LpB0dhEMVYg8LcTbt876vHaE/RNXSddrKPfcPYwYGK3VgxrDyMYrnwyOg\nQ4ONTMHFyJunZkatfu51XSaa7dm3iAqcFAfBbpKeZOIEJKlhGN0HxifsThTx\nb5fKsmH9gc/7JKlOUNbPWATxv+PZUS9K83Z7Hm6kcdU+xleYfUe6nZA+Mg2C\n47xlLLNACKUbKO0krzhy/DLw9BnQp3trtYbvlhqvRvRW7mst0FiDfnYGrOty\ni2TaKK+UB/B+WS/PaOtyvAcdMrdtEU1rRcvE5XdJ+aTD1NpQagOyL09mDYJz\n/G01TceFaJSNmD8bhmIEnOeYIlDhQssY3AzOoWo/bq4omeFpaT1Zu96fTfOS\n5ZuA\r\n=Awxd\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f63c974af79ac7fa5bb2b2857669febe3e9062b8","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.71","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.62","@balena/jellyfish-logger":"^1.0.42","@balena/jellyfish-metrics":"^0.1.108","@balena/jellyfish-environment":"^2.4.23"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.109","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.8.0-5508-pre-build-versions-f63c974af79ac7fa5bb2b2857669febe3e9062b8_1615449256214_0.8228565142733144","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.8.3-tidy-cards-export-50696fcef4e13db126cff42424d727c5ca19cf4a":{"name":"@balena/jellyfish-core","version":"2.8.3-tidy-cards-export-50696fcef4e13db126cff42424d727c5ca19cf4a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.8.3-tidy-cards-export-50696fcef4e13db126cff42424d727c5ca19cf4a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1fde56230dabe3ed57bd26a47248d9aa4f8992a2","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.8.3-tidy-cards-export-50696fcef4e13db126cff42424d727c5ca19cf4a.tgz","fileCount":100,"integrity":"sha512-pl2Rs6CRGU+oz8f7MupnndzYeTbfArIZraZMZqk8fqZHhpaZ2Ejuy4Z8XP+n8zxRV7w6Jk54rZJM8/aGruyQYQ==","signatures":[{"sig":"MEUCIBMKGpE7zUu6uoYKEP0aDvC7rcfWlnDCo2OC8kaSBVStAiEAkw5eTSKm2ot22Hzx3zmY0z+Kuj7l/NwcSoCfSfSin1k=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":629756,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgSeaMCRA9TVsSAnZWagAADJEQAJXa1wF2VqsAK+bGs4lc\nyPTujRivn0x4kCYhkdhq9sSz93HUikL78OX7Fq0h2DcopdRYNo1VbH40W6bR\nUvyG2AG1zqgh9rVaRo5wNFcwRt/qXlbCmNrvD66UFU5CaDL21N7u2bpTPs5+\nk++YVdnGWmdymi6IH8hsMOL0NwckIyuOxtQPKsRw+w22H1n2FyMuS2Xf4BIG\nTpBwzg01f8wnQBoFcChrbn+DWCyzM6+y3U8fITLzRsoL5Xe7g5MeZKr1ApXk\nbKdYceW4oO7Jc3Nv8tniHhf/tcNe19Mb5QZmQIK1bvUb3QqyfIGS9T8/YPUn\neC4MtKJiyoVcqk9WF+Ra1ikn9wWPpSwvVhTHr2w7eB5vzhIAGBs0Ei+tVmzM\ndBqiWy0qdJyAGEZxz8qVBrwW6o9VdaV6WfOQxaqCQO+X0ihU4fenkMcaKocY\noFrgT7+KZ13aHI8QoYpbm70tZHDLocimBFoJPUCDWrgrbvOfbRoZnHdJtJw5\n16n98/Me9aA+gPAGspV7LYF9pvLdO/RSTbaemEIKKBJElieW0rhcAVy5kQJ8\nlnXQ0GMzA3bTiuEBdmGOW3nYCgzgdMN24qfTGt6QgGemzpFejYs6yAD3P44l\n9czKsKgf4exuBwX3KU+z8gTFfhlKFXtJ8SrulwO8lZanHH/WROzm7zaDA6nj\nMMKt\r\n=7OA4\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"50696fcef4e13db126cff42424d727c5ca19cf4a","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.71","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.62","@balena/jellyfish-logger":"^1.0.44","@balena/jellyfish-metrics":"^0.1.112","@balena/jellyfish-environment":"^2.4.24"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.110","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.8.3-tidy-cards-export-50696fcef4e13db126cff42424d727c5ca19cf4a_1615455883667_0.42577509013754655","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.8.3":{"name":"@balena/jellyfish-core","version":"2.8.3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.8.3","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"bb6fa6596030f32c0fd8ef2916548522a097aa60","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.8.3.tgz","fileCount":100,"integrity":"sha512-sOr8GNomwjZNpk8Gc5+IA1uiQ/+9Nxverw065blZpVrzXAVa3+VakO5EbgjYO2GlhuBNJRfayRvn+jIyvsmbVQ==","signatures":[{"sig":"MEUCIQCNgy4mFP/E8erhPSQTmYQgCXmTA1yGQ79seso+jIK3fgIgLPWPexFFW5Gjij10VDc78wmCULYG0bPYckQHSGAXF5o=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":629697,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgSe+4CRA9TVsSAnZWagAARcYP/j5AzoIvppDPXbeYwrvl\nhmqVfvGpgH7HQ89ocEjXUGnT1vu78XGqu7uVoRX7xfwacYi0PE1EGmjKy1PP\nKZ2Gd7TSVuuEOAWcksOFIfbN5qc1+fP5FkR/UBtOy+JeP5vCvLEsFIdO+tBZ\nsW+sGQcBHXOAg3yBpNQdQRUu4x/tkwQBg6tMNrM7r6/UEejuc/70sjZ20Kzx\npDws/0efZuB6ltm+54fRlN+tkVNjjtOPHd1HlQSSfR0nxO3M8zabGs6qU8LM\n3kcR34fJHm/lCM7WBt9rKDqnf1xhaCU31DoLWDViq4G7XCGBa8mJxiy7cKnt\nPmlacA22JMTZNDPLBMlGozw1W4HSpyODdjXOlogP9e2i9vqZKBzgRv+9WinJ\nODVQWKcLYjoIfVfrhh+Ueb9xMkjCakiHxmcNJSfz6bC3CY/Q8PnUc/Qdnh76\nIDNZulvJpWJxFkLaCKLDjgEQJuh8NGsEOZE+kyBO2t+LXbpCS4OZEgMbL1SM\nT3e3SdPUEq15RYi1IZXHdvzUXwqGcYUzqukapLVFj3h4Vb7Uwr/iD9gH9rea\nKo/XLFOl1gZ+1fei8kacAYALRJ0XE8ghML++uUC2jv9y/k2jk+hwfNhLGcyS\nWC31Kbd4fp+RbJQ3i2srWYxwL6PdK7eQzQzH0c8Nw9geohKz0uNBz0u021He\n6f2L\r\n=ww4M\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"731096775095ff020036a3782c07d325fab3dc2f","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.71","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.62","@balena/jellyfish-logger":"^1.0.44","@balena/jellyfish-metrics":"^0.1.112","@balena/jellyfish-environment":"^2.4.24"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.110","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.8.3_1615458232011_0.846234114602177","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.8.0-5508-pre-build-versions-4bef413c4329a57206e11a4421b957b7989b8f9b":{"name":"@balena/jellyfish-core","version":"2.8.0-5508-pre-build-versions-4bef413c4329a57206e11a4421b957b7989b8f9b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.8.0-5508-pre-build-versions-4bef413c4329a57206e11a4421b957b7989b8f9b","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2b5ed64d260620a98c24bd6d51b9bbb7ab57201d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.8.0-5508-pre-build-versions-4bef413c4329a57206e11a4421b957b7989b8f9b.tgz","fileCount":100,"integrity":"sha512-epxc67jkaHTQjlWJJf5T1i7+uXRzBmNqT2q/VyRbUSpAXIa1O/na2O1v4Jcrk5Qvl49Tinti5rY5IqXTwMc2AA==","signatures":[{"sig":"MEUCIQDRme1RT0CfA7WuRGjCAe5LshBP5WT8jNqCHZVbxKZvEAIgKi/am4+/OcRGmZwk1BAyWykjFpHkk7nk/4OzZc/J2+M=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":638433,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgSe/lCRA9TVsSAnZWagAAEggP/jnoA4+hJuZwc3EHxF7O\nt++48/PQMD80DowSIwe9kHSB+HgGLYuJ672QMU/pG3TzEgMOgRlHeriaMu8d\nWZEfoV+ChQ4qP1pridmdZIc/4zEb2EhoVIkj3pfSHCq1oHhbSx97fs/N/JqQ\nxQ7Nn/JBkCsKliLy+HJGaDeGZEKo5UPTxPj18iVC+HBFG+K6J+Tyzq3qiHRp\nE+Hbh3iocn14/TVL2LiMQSYmPBqB999rAraxaOE0GcvxqTXaaHqUCp3uagjP\nmunb1bvT8N7P1ybmAY/awo6yEKxhvRaPjFZYbBLUUci06xAxRpfpKF4RDWyp\nPSkO3VxDdI0LOdrE7Yv4LQxIfqfMVT2PUm9HlubDnRDwRBnE4n6BJd/VriFO\nfopQUF+dGPhmIp8BVPwH5iQGR5AofDmdzEvXvoszHOI4P9yM+z2N/SdjhvPY\nggspQhsgFiu5xHupYdMwQrSpnGL/nGvIzsW8BCDWpb+u/o7svVPqBilQzaYJ\ne2ZpbS6i2SdGQ6RqnikWV5gv0U9Nr3Tp9qpEEZr/DKdfjKszLLLDEMMhzzzt\nER+JiQVb95O/trQUfBVGggInpcBuU9+rMCNLqSbRGryL1kMm7LwqMERW3U3n\nBAmA4Repg4V+/dWvtl1gx8+Wgtsqbt1Pt6qenRsoklnhdwaJ9v31af/JuDbh\nsU5f\r\n=d51v\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4bef413c4329a57206e11a4421b957b7989b8f9b","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.71","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.62","@balena/jellyfish-logger":"^1.0.42","@balena/jellyfish-metrics":"^0.1.108","@balena/jellyfish-environment":"^2.4.23"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.109","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.8.0-5508-pre-build-versions-4bef413c4329a57206e11a4421b957b7989b8f9b_1615458277364_0.7606603051477723","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.9.0-add-session-scope-ccf308c644fcbc2d9b869f063dd96d5838447552":{"name":"@balena/jellyfish-core","version":"2.9.0-add-session-scope-ccf308c644fcbc2d9b869f063dd96d5838447552","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.9.0-add-session-scope-ccf308c644fcbc2d9b869f063dd96d5838447552","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"34d0a203e832218a902f9499517935d74f000a08","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.9.0-add-session-scope-ccf308c644fcbc2d9b869f063dd96d5838447552.tgz","fileCount":100,"integrity":"sha512-c6GE/hXU5n1YzyrTN2/4FlFL0zZDkXsd8DKEXLYs3PYzkX/PZNf9LyQvDRQvEE/x/ojKvYiUH7Y0qQGWraEP/Q==","signatures":[{"sig":"MEUCIQDnuEXpsnDWmC7fJS+8FNL7bTc8coy9XQOZMyPL7nrf+gIgJCcxC9lVuaRkq5rkbD42FLIS+1idbxwQ+ckm1QjgfO0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":630322,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgSfPZCRA9TVsSAnZWagAARTgQAKQ1yQ+zZk2pZmKAi8Cb\n0Ct8ZWm5bULpZu3BKfwVlrEwpjY6IRgP5YmqP+3Gz7pm88o3ToX9+0xUyWUw\nSCFN152V2tBYDCR5N3gpUoEd+eBWM8M13G6K58e0PLX/OODRW2OJYCNv6Q7v\nWVR7EQzKONq8ch4jvAXunPaYP5hkEzn75SwwJBGnkXTsJR7N9ZwkC3JO/GwF\nQrG9LT8w9SxbleQOT9u9YTjzMrtT8qzzKrHzZATMkGucYs1WMZDz6/oyhCZu\nQT7a8r2VSAfFgF8A/Z+sKsWijx/MUwkMqUs0DVaU5PUlUO6nxIe8IXf0c4rf\n/TR3o+Vho6ukRN1hblEs9TSVg7YNNwSiELoLmX6C5fY5yTfxUVpvT2IRDjvo\nRfC7SXXstUizQerxmo8OlfiaYdJ4QC6zJ4LOu19NHhlCQAe/ghvkJ9edffZZ\nx0rR6WLUw+ZbVFjQOOjnBFs83ebClyaW5l7A5fWUciVR6xlz7I60wTP86SPr\n2tEJmhK0lcpfyDOgT0xRUt+CqLxZxzrJu8ZfGn80ZI514WYUdH1jxs2OtSV8\nevTDyzqlWhmUsfYsV9mHl+F1JCa5+m56movsShIYThkqFrFnUCaU+5gEJuPp\nHu7aljHLewQJncr7WmUXwqiEsQ/vJOrOJK2Mp0yxBOtjCtSfhkE5svM9moxZ\nxrn9\r\n=BP2l\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ccf308c644fcbc2d9b869f063dd96d5838447552","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.71","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.62","@balena/jellyfish-logger":"^1.0.44","@balena/jellyfish-metrics":"^0.1.112","@balena/jellyfish-environment":"^2.4.24"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.110","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.9.0-add-session-scope-ccf308c644fcbc2d9b869f063dd96d5838447552_1615459289193_0.772013272994484","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.9.0":{"name":"@balena/jellyfish-core","version":"2.9.0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.9.0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a361e40359530b8a5769baf18f14f191f02397e7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.9.0.tgz","fileCount":100,"integrity":"sha512-GJdTvN68wVsh5IqPKVL4FgM1TGDYFrkEDgNshUO1dQ+7rQV1q4+pAXFH+qla6zPOt4cVevyV7kqL24WpThRiww==","signatures":[{"sig":"MEUCIQDjnsyyQ12jaKP39i5UzbQQqhABfme15b38AD+pbgenywIgPW2crrWY+X4+DKbAE0rNqM0LswAtVk0UmjBUFXkfGR4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":630263,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgSfUZCRA9TVsSAnZWagAAy9IP/0JquyA6olvhSkWb2EfV\nD34jT2ZtczxtK4lMsqq4kgkbOlEGJna14L2rSTfPHv/u2HtQLmv841d0puIV\nv0Y4CwHPQ0Q5sdXru7u2j8n9LfgLk+zbd+98wJ5Cx0lfGRt9dRfqGVuYxBdM\nA0iMb00Hfe3UZlyEF1ToGBdU6l69JHm0RWFjkegNN21tHkcS3cu425U2u35+\ngsI40IgBgT0dHcPVStV0hHagqvz6CoLY9gVmG+DHRJ828AeWPnPohjyjZW0o\ntwuqTFCallC9+dPY5STbiVJKh2XU5qZ/i7iirvBfiQg/24X3GbDYpFc0Foss\n0MHeBM6waQ6WK02PuMmAH4OExcb7iyqB/uR7iXiKrdHUaiH6vtDufGEwTJKY\nEuk+/ZJkqjU5fxKmXvW/Uoz829DrjZSRxbXNZIUWWshK6aPlMNyI/l3MVGtG\n9H/qpqv/wQk1uBlSuc/Tiw/uhtx31GTeOhBG1eKtwByHAmzMfF8rl6zBonYe\nFaHrVwd0fkXb4dL0TI7Jdc4QIdpPgktMwUwu1nAgC9IASRlifzGO4+wkH3Dq\nvMpUhWN3tCK84usszkjA30eN75jV1xyHd+ynuWWCUBvYTxMkRXuG8tQaLKTV\ncYMDjmJHtqdbHHt3o8tzyKlxkvzhlnyTZPutQ7mZcQH3TdLpCazKzNS+yI2d\nwQvR\r\n=rnk2\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"5c493cde7c52977ec0e89fa72bbf7da863371257","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.71","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.62","@balena/jellyfish-logger":"^1.0.44","@balena/jellyfish-metrics":"^0.1.112","@balena/jellyfish-environment":"^2.4.24"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.110","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.9.0_1615459609250_0.7978143596936416","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.9.1-renovate-non-major-300223f5649836d61e9191c849ae8788bba31a4e":{"name":"@balena/jellyfish-core","version":"2.9.1-renovate-non-major-300223f5649836d61e9191c849ae8788bba31a4e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.9.1-renovate-non-major-300223f5649836d61e9191c849ae8788bba31a4e","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2a1386fc5841c798911409e13697bdc128663ec5","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.9.1-renovate-non-major-300223f5649836d61e9191c849ae8788bba31a4e.tgz","fileCount":100,"integrity":"sha512-sAqVYJgQoIjChjpH7lt6+U5z/jFneZa9DYNjtbfZlJxrWoEO6Fsx/6j0OAnNYlkEr7lgEK+7930Z0LX8ajMreg==","signatures":[{"sig":"MEQCIBsPYcmF67k74eLpFXtgUPrWkwMbpWmqhUqTZGxU+gfGAiBFo1Rb2mspI0BiinwYpRdGqfjCYc1qcI9NshTiJLCLKA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":630744,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgSiJcCRA9TVsSAnZWagAAq4cQAKFeDu8o61iK+Bbg0Mno\n8ZqquEjKdA7w+3IK3V9H2Yhfb9w+LyqhpmFwz1JFKQbeiLfMWOe+/kYSDE4w\nBCUZN+KxPfW0aT4ICMCWWya0kdYV3mkIOr8GVstm/5/Djm7YbvMoxF+F9BTw\n/dknzH+I23e0E92q4IO/0xweoly9lEuPFa9LKYh/sRp19/As3hnHXTe14SDx\n2Ill5dWeH+uL4xfr9Z1rC0HB7fV9Kh6jNfaJKsy45OC+JKkkqCCFnXY5+94u\nh2Sucf4+GbahnpCOHuIB6SK+BvItkR15SiOjc8yKnr2DqIoRVO5G5qBzpOUo\n8iCjoV1R92MCvxdxlrobT7egYleSXmHhSx4P5KlTScDJWZatvXmx9M3SsMGD\nnmBX3pFuSZ5c/KZGwNmA9B2i8bAUb66phLM9pqSt0e3rqnhvvld78WXtuqL3\nYHQA8XxmNqjFKjOnxjDyKs/deG3jB1DRbYiDCfCr/TYYiWmNE//f9wZEUsJD\nfPaEdyPVV9sOlC2WJ3CVhNmlbJmz/kynr6HPxpqjUmqZh3k+6PHC7gHM4glf\nx4WH2S3Vh0zcuUlU7nR+Q9TMe6U4IteTAmXcU9TdbbckDKjIAd1taRMNWpbI\n6H94tPHocBu6dzWsLqfXZhrdPEy8+30mJWNSGT4rZg9qjHkY8FmKnOvOujH9\ncnoW\r\n=Nv7u\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"300223f5649836d61e9191c849ae8788bba31a4e","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.71","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.62","@balena/jellyfish-logger":"^1.0.45","@balena/jellyfish-metrics":"^0.1.112","@balena/jellyfish-environment":"^2.4.24"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.110","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.9.1-renovate-non-major-300223f5649836d61e9191c849ae8788bba31a4e_1615471195563_0.7958950789330581","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.9.1":{"name":"@balena/jellyfish-core","version":"2.9.1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.9.1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d95e01d18253a2ba76a9255e9d7e72247e181761","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.9.1.tgz","fileCount":100,"integrity":"sha512-KG/d2ZIhvsvq2ZaX6U6vj2FmAIJdpqUpyyM735BK+kzM5t/fg40GvO5OZJWJLTvKUYV3n9JmX+yIYwiBsMUqtQ==","signatures":[{"sig":"MEYCIQCOinFYkaaVTGiQNo1htwpYw+DtlbYlJgiqEhtmXiXargIhAIhBg2GSOQUbFUYa90LBATrXesVp9/mwBiTFpHOigAl8","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":630684,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgSiOhCRA9TVsSAnZWagAAaekP/1S/3BVvuv6fi4E7X0T6\ndC7Hyd0Yoh/3FQx0q+VfESboqSaEt3MWSJCAXgUy5DBMvd4ygR4etGzxpQLK\nR+XVxgvCvDYs8vWESZJvZEo5h/Q7u4UWKeRnptzAODlQ+ThfhpfD5vHi7tlx\nh24UulbN302VVxwVfJf79xmda6QwCCrSZGbp9U5+7Vy3MsUbZDrUoh1FU8lN\nypEbxMlDJjgIch82PswUeAeOYT8DdQUlM0s2Iz++op3Ixl8ENksBBeRrqsaf\nKNDOXZYjCCvT6LGfUBCYzh0rD0oJdEhov0o9x1M6uW9doCeuEIdM8UfW50+S\nASeDT5Wziexoss6NVqKviaDuPgLI9cU16KC1GCCFNW1WTB/jF2pDx6QthD8B\nxxY0gEqMOtHOZBopwiucdrO4y0MOsL1UHUoVvjXOdbSykC6j8/YhUCEDs93J\nPQXvqgm20IJPQHp9dhnKYscPtcgiwOrlocj3jyrjNvJHyqe5iSfDRvbFJE4N\nlnJX+xasuszHAgp6lR3Vwr73LZGCm004h0ZMT1icx3wFGuyO9YWvgSRsulHQ\ntAQWYXx+EL87fbYlHdnVMVj/uNjsU+/sKAzTjWpPQMVDYWq3BlNBY/iwXzjM\n3+6bxuOCqI00Sw8utah8N/MYqJhJUTeEd6pl+JM0I6ItO4Q6JPncRlg7+sDe\n5xza\r\n=p/0O\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4493a535766abc0375efff82589f0ea25ad94dd1","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.71","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.62","@balena/jellyfish-logger":"^1.0.45","@balena/jellyfish-metrics":"^0.1.112","@balena/jellyfish-environment":"^2.4.24"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.110","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.9.1_1615471521176_0.4144914866555798","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.9.2-renovate-non-major-e78e5862c36b72e730e1c704c1da5f1f3556ac63":{"name":"@balena/jellyfish-core","version":"2.9.2-renovate-non-major-e78e5862c36b72e730e1c704c1da5f1f3556ac63","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.9.2-renovate-non-major-e78e5862c36b72e730e1c704c1da5f1f3556ac63","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6dd962bb5dd4bd52a6329cb941520462ab359107","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.9.2-renovate-non-major-e78e5862c36b72e730e1c704c1da5f1f3556ac63.tgz","fileCount":100,"integrity":"sha512-fhlkN+QJ+K95UN9bKp6NbTERLCoWx00rGv7lQM02qFTAXOhFjU2e6uOBRBLLWa/Rw93nTskSzyEGT5I39Hw8WA==","signatures":[{"sig":"MEQCIF0PBHoE9vWuulGhB5//1iIXasEEI5zGeRz9/kLe5xe9AiAz/f8AmiAmQ6o2XzqeNL0Zl1+unTAJ9WI116R2ubRJ4w==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":631169,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgSiWDCRA9TVsSAnZWagAAassQAKDBwrxyGzE2kgjo5YKR\nmU3FtimcALRbZJPCmwYZJsDSpmWg2Ot2VS0bx3ZuS+QoAhr0o/XjT2DxheMJ\nOwUnS2TtAGLchacwcYk4H5MjtesdJvbthZqZWe8IQPbIZFGJSLUemJzqxdpt\nWuaJ2P81kV0ya+gZPzuXvaGO+SLMzOkqzgkdteHA9mLSyQpnvlH7Va6d82qa\n1zB2MN/QdXVw1vyiaXM2o7rc+Oxixb/Ibo87PpOlZ15RygcNkX2R8+h17JHB\nQokBiSXd3o2nsmzWIyn756bIko0OvCsGxGMqKWsYRKGMaD5ZlPCnCKWUyUkG\nqkX8EprFPHKLSu9NM8VeiTu6DSd+b2n+1+LSqCdAFESZqA+NTPVNzJGxwNVU\nOwCVrPZvjxzHwD45vT3UMkdIpYk65h1nqqKb1djzzsmuoERjzYF5arHqXLXe\npmoas9VnSb5cz3XjEo1xuRrOi4zmc/irrGKh+cFV2McsE4OpO/GmuH6cm0wX\nMC5EttoP4SeA4aMRfP5I7nTsbXcsNJQpwO6PtZjy4vBm/vFQEczaMP6yqUmj\nCvv46aWkmGrftVSimwKTxBUC7P/3mAqsqgOvi8j3iAOI7xgay5lZnrszX2Kr\nICFxAHvH4wE+2B5LzkftUG6huaGytlK6LMAOI0FH2QYvM/igJTGHI5/6Mo6n\na//k\r\n=zZ8J\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e78e5862c36b72e730e1c704c1da5f1f3556ac63","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.71","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.62","@balena/jellyfish-logger":"^1.0.45","@balena/jellyfish-metrics":"^0.1.113","@balena/jellyfish-environment":"^2.4.24"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.110","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.9.2-renovate-non-major-e78e5862c36b72e730e1c704c1da5f1f3556ac63_1615472002673_0.9136006807226618","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.9.2":{"name":"@balena/jellyfish-core","version":"2.9.2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.9.2","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"546374a422e7e9561cce5ef78932e96527aecdf3","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.9.2.tgz","fileCount":100,"integrity":"sha512-LmHku81u2hEpDJFuTnKgrwz/OiggNiXHhSfijcnQC0SBogeA4KY/SgwYxyDXXDWHHZhWAeZie32fnXvNXJG04g==","signatures":[{"sig":"MEQCIEgbETyNJ9UDPNyfEWz1oWD+Xwy/f82/xz5qeRWNcqUfAiBTIjYdC8Vh9xfaIKhAB55+4QW2ZXFZ4tqZM6uGFYvTHQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":631109,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgSqoQCRA9TVsSAnZWagAAK4YQAJwmn5INCfKPup/oHJON\nyYJyqDy37DKtJuMwvaqtoBDhcU39H2GlkCbKWBkwVlBc++xF1yF93w3pNI8Q\nUUEl0Sks5VtBY/3cjfkm+aEVDFlfifdeHQgHamIGOlYmfAwJgWS4dSJ8VI9y\nDXqA3nAVBMz+P7FdfixTzH5NpD6dhaMHZxFqOJJO/Vs0KEmoYISJe2eYk1gP\n6bvRJQiDkVXH8WF8GI5O23ayz7FJdY82bwXHCXqvSStDerdpQeArE5Sdni4R\n7DTFfOiR4UFUMESpPJbA01kTPr4KymLNvXK84MBmxwgPoM1Tun5rOJ9dMOWc\nqihxGPNETNlg7XLXdbM0niE/zs8TCnLuFEM0Y5UTi+/B0q3mKmmLzcdkF+7O\nV4cp+EyCv+dYQ6Mc3dzvNLZOS4FSv/mms9DApRq4eeLnK3iEm/dvTj2dwUjG\nVaYDjInMeoWokecEMywATRUwBuDmVo2owEyqegWmAtsosk/2HMhg+8w8kGhq\newS3mklseviBTHgXb0RNCZenSvDhfZ8JwO97qNydBsVcs0NloCWvnqahpOGm\nVEZZIMu9ltzD4cupZ8X0Tk/EeHJ+pmWkHV9wVCi3QMle5C9bqmMW4/fcSi1q\nFvJgfDoGaHC8b4HplXK776Gcy95xVWeJ023y4BscqQByUdn946leBivqjXuq\nM9tk\r\n=Ztpv\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7e3d5440442d0dd5e54a2d8ab7c5ce2307b11321","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.71","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.62","@balena/jellyfish-logger":"^1.0.45","@balena/jellyfish-metrics":"^0.1.113","@balena/jellyfish-environment":"^2.4.24"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.110","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.9.2_1615505935649_0.7140115791749082","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.9.3-renovate-non-major-9eb62acb4590bf40f05167d881b84b6c572d5d84":{"name":"@balena/jellyfish-core","version":"2.9.3-renovate-non-major-9eb62acb4590bf40f05167d881b84b6c572d5d84","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.9.3-renovate-non-major-9eb62acb4590bf40f05167d881b84b6c572d5d84","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"aaba044bc177518736cba63ee7e4869250de1c7c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.9.3-renovate-non-major-9eb62acb4590bf40f05167d881b84b6c572d5d84.tgz","fileCount":100,"integrity":"sha512-LGvsmRSJVvRZfgAVGtwir/iH93lHxZi853qFqznvQvZnyFZtQwqRJWMr837WBQKuRbXN7PE9qr4OpH5+XdCW7Q==","signatures":[{"sig":"MEYCIQCGl1qhJwgIUXCOqcXWCDy7A45MIi+wqX5JhmFzRh41qAIhAI5bQunl/h6jdCHJPsnClKnuKyi3D9o0SceuDkmYjlw/","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":631516,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgSsq2CRA9TVsSAnZWagAADMcP+QGp8P4wary4VdhOlJsc\nrFtqR9c/Gb1AgJEfhlb0eykYlKQeMkKLtJqz8yppEHNYF+zoBY5UUpQOmDzJ\n4NLGF3tutAIgON0NG8DIAffZNFeR/qO1MCTwmPklvpu5vkaSMtb/IteqcnfW\nqTUit4WOzyCvG0m/iChm5pRag2zVlM3MhvNERScnHeutJYcCmK4deGEByDk8\n+a2yRoeQNvmnuYEepnJhNLXyec24SdmwUlNRnsTU+wPxmKy/6Eib2ebyJ2az\npm1zIKehEJDR1q3/r41ZJ+1IWWjTGAqnzVq552ZqcU07L4lQ23hSzzECfRxG\n8c6XUFXSUSwXZAF5GKo8ILNLey8lsESA5pSmitpbPq+qVV5DZELdhCbzt7nh\nzvT9dVmlKEoO0oAu75fgBg/A6dp3MOzSlef9ZRYY7n52KR6pd2fRgc/LYca3\nOsOQOpCD57qSVAo8R5SmX8eruM52MHI0ljZHEXe6VCyJDEYXoEdaSapPhBON\nxgJs0N9ZahJdbCeEmwIml2x3NQHxAbTfszzoNFLWkgrHNWQotjwFN4iKXDkd\nypgqV2BowubZJqHgQ77bMYLrIFulRYavoUVVqm7zy2aiFrZ91aFbSvkB6wHq\ncnUEXRdulGNxIFRwo5PHL++nVYH6xEpTFIteUJupWiUlwgroxEX/XZ0MbUci\nXhti\r\n=/WrP\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"9eb62acb4590bf40f05167d881b84b6c572d5d84","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.71","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.63","@balena/jellyfish-logger":"^1.0.46","@balena/jellyfish-metrics":"^0.1.113","@balena/jellyfish-environment":"^2.4.24"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.110","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.9.3-renovate-non-major-9eb62acb4590bf40f05167d881b84b6c572d5d84_1615514293478_0.18732980855157821","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.9.3":{"name":"@balena/jellyfish-core","version":"2.9.3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.9.3","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c8470af3b919ef860f37a8b5821f096b2aeea9f7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.9.3.tgz","fileCount":100,"integrity":"sha512-HmotOadzA4XDYszzCr722b9ZHX33q/xHMdLJWILCMS8NxxPpZM+4q4ZCdlC2WPkssBeR7bjdtgmyOFcDGPAyjA==","signatures":[{"sig":"MEUCIDfB45cKFTVstJFBSltqPDOS61xOUr4qVfrGVzYgPKSnAiEA8Djr0MrJQ/AKP5xh3Cm6Pu9MHkVzVbvT/KPLjBGgeD4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":631456,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgSsvuCRA9TVsSAnZWagAAAS0P/RprXS1tiyFf3RrR0aby\nBz3RXprngVUoP+RADSZm7gk9jsMNMTr1xCr6G5dQT9pw50P/LvZvNp+nE5Wk\nfICNmIcEH6QdWYxPWGmkA+FegsBSZ/xWKiXdQZjcwoEGRoCmZg3fqOhIu6Ku\nhBau4+RnaiV+xe4hczp9zQfNANbzJOLOv++Y1dGF/61EtrTjQTGNcHJ7pfVJ\n1TKtP9eDb6mVxcpqkIHeWHV6pvTgWrjuv8HMxE/EQ8GsKvSBc/MywGhZy09P\noNvy2asXocb0kPszPZcAJAXlDegK4sSmopDQS9DSe9ig8ZmiWIik/tk7qRbi\noyZzzew30ANT3BkFlJvGV3uFK+3Yweywu32Zu8Dm4Cd4RcEwy/6HSLQ6b5cK\ntXyJbLcs2wpsrmWJVJE/stoF/YClxWYsxCBBzNnmuadOP+9xw2rAopRy/Ua7\nfGT910X7PWRs6HSedyL/j9/gLwpQG2vRxFjavR4IIlGY4i1w0jDzyfkrzv4H\ncEV9EbQX1RYejxH2gGRKKAYGJfkVrjbTKEh/I0ism00KKCfTaEEYC3ZcIOjY\nykad3foAr36dLxytkq1tq1pNPQB9zeLJdGyaXcnYI68gZIBQiAFXKoDJtUna\n8piTjVBZh5WxFJtDIWCfezgmMZu5+RGkwrcsROFWGxkDoZlqL3nUt88syyZJ\nJxai\r\n=6Gb0\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"991a07d6786e99bf547a07b824bcb40d4eebc6c3","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.71","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.63","@balena/jellyfish-logger":"^1.0.46","@balena/jellyfish-metrics":"^0.1.113","@balena/jellyfish-environment":"^2.4.24"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.110","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.9.3_1615514605543_0.8175149591262216","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.9.4-renovate-non-major-04424fe349564d15b7c773a496b2f85ba5ef31ea":{"name":"@balena/jellyfish-core","version":"2.9.4-renovate-non-major-04424fe349564d15b7c773a496b2f85ba5ef31ea","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.9.4-renovate-non-major-04424fe349564d15b7c773a496b2f85ba5ef31ea","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"fc7fc4f1255a14b1d090a56d409d89e3adf5277d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.9.4-renovate-non-major-04424fe349564d15b7c773a496b2f85ba5ef31ea.tgz","fileCount":100,"integrity":"sha512-ChyfQIzRZWjTQdW7tQWr0yFuWsYGi/uItA+Br1CNwyB+jN/e5zBbazt24u6mfpmuo0LXM/W2r8ZJJXfD+EAHNA==","signatures":[{"sig":"MEUCIEfkQia94lP7k8KtUX2lsydWSLmpAj9zIENNnUmhx9eYAiEA1Tbm5T1u+YiaZ/my7bsRfBo4H+9rWep4VFkhgdTQHl4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":631941,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgSs9bCRA9TVsSAnZWagAA/mQP/A+a7ruUcNIq/6GfA3qt\n3JYhd+cb8T2xSN7aXzV++IoFwAqqo5hGN6QSp5cSyJw8Xxst2QbuE8wOAuDw\nSzJ9OAPnjdY2BzsVqBiAyXzLVAwLYCKJ2ke9TEcm3o7tKePiDLT6YgbAInbR\nYn0k+te/2Dm8gLuPPOkr3V9ovBwGOBL6/z6qOJ438xk0un1DJCmC2UUT1Dfp\ntKAFp6mOBApczJbeZpmMkyjHxYMVbXCvd8uh/MGVyJ4l32hzM8JUVafmbaVs\ngl9bH/qlxihM1xMwA5qdhE7CJI+MiXbM8EqpdkhA1BA1PCkzdXGpI0/YjIhe\npVWTlxwwhAY1uCUGealawC/DCUkb14ib+uc1RuuRcRbn7mi5uAse3q8MN9yu\nC98qu6zXeI3i58exn8aXbSy34kzuUl/51QjJ2pJO3vGv7CtGZhCmnsmW0UU0\nNzaWjY1RX3PnMztTqtZ0Q9dwzmt5b+vdPydO7CGEeLpe4uc7obdxaoDurRFn\nthpZ51VvH/wLS4ePtDIhC7W4az7B9NfxI8GnLrqVtZlJqcL3S5AU6v8qKyI2\nAMddpqvFNq0i2wGM+bNyrhtrSKrPpwHuM80LawAsFuy4oe4wN6e5mnFzT0g6\ngynH89lrKru3Atr2Ai4ygn6CB030CUZyxGgvwH9niK+1WgxrZrHqNo5dK9Zd\ndTdC\r\n=vqRN\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"04424fe349564d15b7c773a496b2f85ba5ef31ea","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.71","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.63","@balena/jellyfish-logger":"^1.0.46","@balena/jellyfish-metrics":"^0.1.114","@balena/jellyfish-environment":"^2.4.24"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.110","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.9.4-renovate-non-major-04424fe349564d15b7c773a496b2f85ba5ef31ea_1615515482699_0.28655234045340117","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.9.4":{"name":"@balena/jellyfish-core","version":"2.9.4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.9.4","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"accc8488aff94b89a9d9c2046d26b8425fa820e0","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.9.4.tgz","fileCount":100,"integrity":"sha512-UlamzkmqiTVKSsdzBk81FFqTJ0PBe7vm6wWfPKyUmhrhw7mAJaBVEBtlTRDPk3J4e+8EACP3cgWEbExcUWdqNg==","signatures":[{"sig":"MEUCIQCuTrDWkvN1yDyd4QHrYhNZ7vt3NpXeeqw8nNi/RXCA9AIgJxf2PlGB6ayEjdJ91k2VDV30S+0jNUKmVoDVuX220B8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":631881,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgStDDCRA9TVsSAnZWagAAVwAQAJYJGdQyihKpJz6y5sgS\nKbgXFPuU0Y7AZQPRPFULme7zw11bjfK3qJkX+XGlxaDw/oaLRVBTWtAhhqsG\nCwZeor7agzVkfQcopw7JIVND3TJCSVZW/VY1B1X/AVNwDLjRL/yrMRQYlSXC\nqJO/DN5lUE0E2qadpgTse0iU9n8BK4JH5+EqwGaGrL4M6gGzgQcRiciJF1gb\nU0mu6z/vElwHUkUpuFajloGPxx8iXRf/jpOdXYOcYeZMopUuD7tiTIreHmP4\nYat/z4l8O3TZNSiKfbFhnzGrXKLM6xKAPsSIw5nGavU85LtGs5CtqofzB+Ka\n4k5SMKSuvG5d5aWFimKtbr2ySFngmYZFvbdChPWfdlzkgb5QipYyUeDrcBKt\nBUantT7oC6onZ0sFK1+GlNd5H2S9qcq6aJuqnH77kdKiVbMmDu3bczOSdBx9\noULgkE9gakhdl2BmcxkMlbyFDp0a8lYVSHyNrVMXxD7olBCo4hVpOwdpqhLL\nScBbVgL2Ixco4xx6rXvEWzpryrbxcuCfftdXIZE4F+uR5r/LjsgIFZWe8Bx6\nJII86rLUWKAoaQP0Jk8gBmFf+zpcbjgn3B7+GPuh6hlohX6YIWewIiGtRHsv\ni7MMU8ZTcYQjuzzRfS7kXt/E2L5tZhchQQGZoNwfTgiNL+D0jO9ptksfNfAQ\nPG1S\r\n=B2zK\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"323fc15a8e0ba68f607e5b4d76988dd7be0320c7","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.71","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.63","@balena/jellyfish-logger":"^1.0.46","@balena/jellyfish-metrics":"^0.1.114","@balena/jellyfish-environment":"^2.4.24"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.110","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.9.4_1615515842358_0.28519080366805816","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.10.0-add-authentication-token-2ad4a2ce61ac14299c357a9be9c95571ed551cfa":{"name":"@balena/jellyfish-core","version":"2.10.0-add-authentication-token-2ad4a2ce61ac14299c357a9be9c95571ed551cfa","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.10.0-add-authentication-token-2ad4a2ce61ac14299c357a9be9c95571ed551cfa","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1a26b8bfba91305b8378e8385443c3526b0d6cdb","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.10.0-add-authentication-token-2ad4a2ce61ac14299c357a9be9c95571ed551cfa.tgz","fileCount":100,"integrity":"sha512-U7gW6jR2HfzeDa+rV0sXb05Cy9QTFi5QzYJdxMHIpsZ4vWMrh6DxlZHaaYWuBXZsRvEuo8O2VeDEdw9nkS9eHQ==","signatures":[{"sig":"MEYCIQDGpGZVSAdBgTyI23cpZwrxlejqpVxs/2li0XhKSdXKgwIhAO4+nStoAuiIG1pzj+/aa2D/KHY5PneJNX12KsTGkutp","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":632580,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgSvXvCRA9TVsSAnZWagAAxoYP/jQNz/ZEgL6pHHJplDjI\n9Zx0vvKirdtRRr3m4S/g/eqVd1zplhmSBapY7VMVcQXs8ZkFqplklTDvgQ5U\nHKmCOeSfg0cp5mJ+TcY6PbwjKGtaXt0md1x12qnzB5pUMstMqtBLZpn4OPzg\n1b16K9l4piHskgHCYm7KVnEfgoBb+C0pnXmwc+czWDLzzG13I7SvIH90fK9B\nfJJjytIHj+3eERlrdJfbQnQBkeUb1zJfMN+gM7pk7+qPJh6Bl9ucYv7Iw0KQ\nPSSAkUv3WsqiHGj9i4JmxrFuyI8qC1hxh4Af/Z3cmhZC+VGyKMX6UxafJ4lA\nJcGHhKrj6NjfpKjipfkXyZ0AAaltUnp8yP1pr+uBCSFSX+aCQ7YCMhjM7dR7\n0V6rxpH0Xf7vCTcJQi2gfnZ734xEzp3DHH7l99hWmh1gPJeTlKAULGSXyt8J\nWrotP6bHqIEtOhZ9qk64xnyo4mZs3+N58wQCi0w+rShxT1PHTb5WnYFX/ax3\n8qBBBL6+1x7xrTO8H76Ro9iP9CHlS+Wp0Onh598xsWUobO9sqo4w527iU/sf\nuzm3yw4Y4VgynU/9WgcHLks5VuKbO0YPxKybnIZwGHYtj9n555ahR3vd0Jrb\nCt1YQzkDn6rYCM3t3gmZdae/S9cStQyMHQGNT808/cu3vfBkYYIJ+1iTuaaH\n15XJ\r\n=XYbJ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2ad4a2ce61ac14299c357a9be9c95571ed551cfa","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.71","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.63","@balena/jellyfish-logger":"^1.0.46","@balena/jellyfish-metrics":"^0.1.114","@balena/jellyfish-environment":"^2.4.24"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.110","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.10.0-add-authentication-token-2ad4a2ce61ac14299c357a9be9c95571ed551cfa_1615525358977_0.010375076252171267","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.10.0-5508-pre-build-versions-5d325a5396ac2644ccb8752c516a38e8b150bdca":{"name":"@balena/jellyfish-core","version":"2.10.0-5508-pre-build-versions-5d325a5396ac2644ccb8752c516a38e8b150bdca","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.10.0-5508-pre-build-versions-5d325a5396ac2644ccb8752c516a38e8b150bdca","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"482763580484b0320475e81b2060c465e946d580","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.10.0-5508-pre-build-versions-5d325a5396ac2644ccb8752c516a38e8b150bdca.tgz","fileCount":100,"integrity":"sha512-pKusVyQtP8463hqjQWFKxA9d1QcoTQdTMKgOBRFFo0/eNsSLOkLPTv+ierBQhcDlmcL44nWhKCyrc+qDZ1dHBw==","signatures":[{"sig":"MEUCIFa9qf3+vf2CDvVAXYTNL7L490ZCSXvUwmMH8cCxZP4iAiEAw/bGR5RO4krifUXTsEP+gyKWeawyLK59+f9a8sm6arY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":642919,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgSvY4CRA9TVsSAnZWagAAInIP/0MZbsmbOTvY2v7qs1vt\nBwmPVvDtlpVcUlUg5tyK0zcP6/oQ+MUM62W5fNByxwWia+vC8Pch+hH9I18r\n7Mrs3oC3m6/hS9qSTy91V0Qr489wmyLBh9oHrzPtAah4y3bqVQcO6ShBBsy1\npGYegdWgO0CaropuNR0T9NTUlKhEX+qlfNYxXTJPt+SkA5CASRjl3m0YZJ+u\nugPingeQ4izyrFZVpcvNt6sR7/4gp+EGN/rmUkysn+0hdyUUftHSpNy/goo2\nsv3jJT0MCCCt+KUY6rmWVjT3UdvK0NVSLgGY9XoJniDsENJn1avHKbjl9q8Y\nLO6j8HYYQWllvg+qT+nEa5rW1DiA+KKsZESy47YrFtPxdcWq6/ZrTr96cNx0\nubPPtq0iPqcN9znArnO5c3oGl7C0HPtCH0spE7qaq1sOrxombDQoUmnGbmp/\nvPFxzeNxVUqwgnjW9wtw1Zq+tsyvXbz/OY5bBJ0dnjwF7yiZZZ+M3l9TQppj\njEUH50l0vUBW0WumeHi2Gw5Y+j8LdXCcBxBqmxCONCy1XDLMH50mdO8BA2QD\nrFaMfitkTz4T53gO6WPrN8pCZx596bqKT7rC838duct70vxqvvq7Zu69fdnS\nz9e8ORtL7H0pcv56e2M3w4h/AMAK4TS0Exi5JJzd87id3cJP9gbsbtARGPOr\nig+/\r\n=EmM3\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"5d325a5396ac2644ccb8752c516a38e8b150bdca","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.71","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.63","@balena/jellyfish-logger":"^1.0.46","@balena/jellyfish-metrics":"^0.1.114","@balena/jellyfish-environment":"^2.4.24"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.110","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.10.0-5508-pre-build-versions-5d325a5396ac2644ccb8752c516a38e8b150bdca_1615525431537_0.22385953031778105","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.9.5-remove-new-version-columns-52033885c8dcffadf5366bf3baf89bcff904afa0":{"name":"@balena/jellyfish-core","version":"2.9.5-remove-new-version-columns-52033885c8dcffadf5366bf3baf89bcff904afa0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.9.5-remove-new-version-columns-52033885c8dcffadf5366bf3baf89bcff904afa0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"7ae00b11bd7285d643bc44322148207a8dfb625f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.9.5-remove-new-version-columns-52033885c8dcffadf5366bf3baf89bcff904afa0.tgz","fileCount":100,"integrity":"sha512-dX3rahS5FW98Pm6SwtgiObFf3Rztgsh3PxWqlR0/h4R1DN1jSfH8lvUwbgYNloZ6Vg3uaZIEh66nKFQIyhaXkg==","signatures":[{"sig":"MEUCIQDZh1wMTogRxNP6ZfYF+3/bi/uazW9wKoNXtqsT1epeKgIgMe1xSav5vQwRejpWqrRFrPQ+IRmfkBuPw9kixZNB09k=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":632545,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgSwQFCRA9TVsSAnZWagAAv9wP/0howv6F/94BgS71Kj1K\nv9WIShFzyhkygSNQRTJqNQKdEgu13cHTQreFsBR2zNEuz7Ya+FLgiJJmCPUi\ne3fSmHssJVdE1Sy82a0ym5+q9CeD3tzpbCkI4IOfgjI0agTR95xExoyN32E2\nDIg05/+ua/qUTMVtLmeiIWegLQc37eyCWmDg4okCk7w/P3EE3pBwPMul9fB0\ng+UOiVUZVmM3hllL/mo63aydu0LjaYvEhE6KlHuqgpV/OIvjRr+IYyaCMZRh\nW5D/iaVUXTkBVaPmZfLnYo8XUyv1IV5CFxwJB8wMbExnRX5QswyWokeBlmko\nVa9A+USEU9riR4XvR9EDmSO04NKTUT9t8llmrJr4ilhlcpZI8c4SgcpnO7/w\nyO5zLhI2DARm1Qo2T2XhO2VOfBck3dysK2tFnHMxb7fK9sBzCiF64HAuZySQ\nk3i9omFtfC1bQp8QuMAFI6+OkVbt3oG01UDb53+tu1bYGMR0tLpiuiAuHgM5\nfkFvIQsYLmGrT3H9cbB3CMcDgUy3Fp8K8B21t7UO3stx7yJRo5P67ALa9+Ul\nRnQ3IJzUcNIVMX/ueCY5ZYyiZoULrAl9vx88cMDqXTrJPDN3qzl0P2Ag2j7Y\nXn8gVTdI/8OHWYeNPJ6QutvDxPQv5BxUKkSrFK4oVaBITDvlAAWTBISQYVGg\nRCZK\r\n=9+jX\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"52033885c8dcffadf5366bf3baf89bcff904afa0","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.71","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.63","@balena/jellyfish-logger":"^1.0.46","@balena/jellyfish-metrics":"^0.1.114","@balena/jellyfish-environment":"^2.4.24"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.110","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.9.5-remove-new-version-columns-52033885c8dcffadf5366bf3baf89bcff904afa0_1615528964832_0.2643629419058682","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.9.0-use-jellyfishuserwidget-1d9028e2143d295b9a52f7fdbe8af6a5fc1f36cd":{"name":"@balena/jellyfish-core","version":"2.9.0-use-jellyfishuserwidget-1d9028e2143d295b9a52f7fdbe8af6a5fc1f36cd","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.9.0-use-jellyfishuserwidget-1d9028e2143d295b9a52f7fdbe8af6a5fc1f36cd","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"20ee118ab2e92f96e152fed7e4aa551d9d448631","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.9.0-use-jellyfishuserwidget-1d9028e2143d295b9a52f7fdbe8af6a5fc1f36cd.tgz","fileCount":100,"integrity":"sha512-NdGCOGlJ+0Q/402YB6l9DwTkCQ2jRcoMlDEClyvDvSd5BT5uKGoZV+sxrLaYrLDDBH6G/X1dqJ95eyFL631Q/g==","signatures":[{"sig":"MEYCIQCxo/CNMsQTwtVC1/vbCPH1EEMQGNUDsbbB8fzTunCmewIhAOfIMqRYXFBTmKilWCbdjrgaHNLFH4OJVp+aPeUW/L87","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":629987,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgSwqaCRA9TVsSAnZWagAAJTIP/ibIiJwV9msmjTkFRX8N\n7gny4sPXaNtN1oMOVpw6TCHmH/ymqioVk9AzYt0wLMTTSzsf54ZN3Jplrvim\nErPEg/0RtQpOaViUka6FJ37zAkVpQzZ8hQ8IgEmbwZmUBBo5MPIWq5AChb8u\nzTchzvO7Z0omLReKRpgiJhapTCervS3zafzN6a59RbwjeIsi9AHA58mZg+ii\nhYgttMkx4UXdrJY/vInX9BWZFGko4XFSBpUufQPDQx9BCnvZv/hOzt8O++q2\npRO+1WZHWle5OD/j4JcFqoKJq+PHuxRi0Wvsv7m7+oRTSuRIhBbQfWREsF6R\nHvvrTb4uj9TXGFG2FAgNMtTxWLtONc/4qBnY53fJLoXZAcgPRkBpsDcc+RS7\nF6JbmQAdfeHlXguApvvnBA4RlBV5gHq4WiUt1yzFXtk9IuhhNigW2GrbJJZ7\nAMymTDcSUydmQqQurHKcg9lJIOt19fknVBs5dwNqtcU3Pi3j+QqZVzqrmE3n\nhS3HfnTpuAhRfZz+VGb6VpqFxlDTVrukcUdWCWHB6CSTj7+SNRXYCTTrybyR\nizoiqlpVWtr4GT/arrWY3RvF4IYR2yDna7b+n19ySJdZNliYDPO0/bpl53g1\nGLGYIsz9yx6gcw5fA+BOsu388BOyO54wqaqn9RDcRYOsaEg18Nl/5ZZRoUkp\n2Bvz\r\n=uX+r\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"1d9028e2143d295b9a52f7fdbe8af6a5fc1f36cd","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.71","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.62","@balena/jellyfish-logger":"^1.0.44","@balena/jellyfish-metrics":"^0.1.112","@balena/jellyfish-environment":"^2.4.24"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.110","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.9.0-use-jellyfishuserwidget-1d9028e2143d295b9a52f7fdbe8af6a5fc1f36cd_1615530649827_0.9525990632291783","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.9.5-add-order-by-version-test-b71a069839cffce0685bf8abd56156494e8e106a":{"name":"@balena/jellyfish-core","version":"2.9.5-add-order-by-version-test-b71a069839cffce0685bf8abd56156494e8e106a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.9.5-add-order-by-version-test-b71a069839cffce0685bf8abd56156494e8e106a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"72ac233683e57cefc22b246130d1c9b9bed25a0d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.9.5-add-order-by-version-test-b71a069839cffce0685bf8abd56156494e8e106a.tgz","fileCount":100,"integrity":"sha512-JsN9BgRW5EKY7ib/2KTtt8X1xasXUo/GFsL7H2iiOcHjA3QYycMbBxNQ4s8xOtU1VAaqVpnnsdM9rx9qf2jXXA==","signatures":[{"sig":"MEQCIEx8h7M2CKhsaXx7F7Fv9mDyqPaewiwJzpiDvQoKC9XWAiB9QrJc5+54CCakXZKP/djul5JGVRC+AKe6JcHpxKMkvA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":633473,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgSxEdCRA9TVsSAnZWagAAMaQP/ihNentjS9Te7Jy589H1\nHID2lgfjj5gemqXHrmXFqNoGe4vr6oQ3P06U0GRfABwuxblpJyEzI+W0p1aE\nX47IpYfcwOaCE5WKm+SZGTVQ/IBIJjlVebuE2J5dhqeCJUIHz4yBxtDw57cF\nLLugYaYZYiYiZEHJ9ZhG4mcPOXLTtOmajzSARIEuDhP+Q+gbImAv4yNfT8sR\nLY7vPynS7A+Wx7JbO7FgCFYCmVFZyFpriprUJdGqCEd9z2TsiF4j8URjJCc5\n9RHrEKu1XqvvG5x1x663bhf1mpmOwy43CUMEgDuNQVoSXJ2eCghxap/2rsXt\n6Sx45rWZEHSLRjGup4Jfi/VpqJSt3fIoF2RbZtODRcNciU8pvK0QXo2gA7xM\nZti3Pb3AiT0wfmJ0CYWYXch8ZV6X/utUzlRhhS/LDJEhBFQjTiELujVzLjjl\nnYOUgSTtJjFj9UoXC2CL28g7hZsMqWZ2PbDL1IHuqBVX6fHAZKe6U2IlmFVp\nXQ1dbTcQjFXUsAB5eTEOIi2ZcCUB+E1cb0VhD7QFjGMGduVtdVLig9NcHcbA\n+qhb1WaaUBi0bN7j478xQyC0BF07AQMktIPMoqF3JQWTXcFqMgAbKlJi0VcQ\nBhZ9lbvnZaaI3pdzxpQHHktSO4W39JQxxRdyAp/KueF8tNQRfAwNnDVdLqYP\njmog\r\n=7d+o\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b71a069839cffce0685bf8abd56156494e8e106a","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.71","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.63","@balena/jellyfish-logger":"^1.0.46","@balena/jellyfish-metrics":"^0.1.114","@balena/jellyfish-environment":"^2.4.24"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.110","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.9.5-add-order-by-version-test-b71a069839cffce0685bf8abd56156494e8e106a_1615532316778_0.47951374479801934","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.9.5-add-order-by-version-test-7a1a0949b043ef265c2f156cad5f0899bd9609f7":{"name":"@balena/jellyfish-core","version":"2.9.5-add-order-by-version-test-7a1a0949b043ef265c2f156cad5f0899bd9609f7","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.9.5-add-order-by-version-test-7a1a0949b043ef265c2f156cad5f0899bd9609f7","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e3764070b0e0a6df3c6e3c4529305edf206f61de","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.9.5-add-order-by-version-test-7a1a0949b043ef265c2f156cad5f0899bd9609f7.tgz","fileCount":100,"integrity":"sha512-mzjQcd00T6dYY8X6TvFGWCUwzBnczIiCBhJNVJhFKnJoJnqq66Jstd/b77u7Y691foGTBvlsJ6tSGKG+u0KVOw==","signatures":[{"sig":"MEYCIQDy49f3s1eN8B85Q4ur5/zUkVnWk8RXBgEYeGmccbXyUgIhAO7Q+SmyDTkBEhEPNBmfEyh31q9X6yAM4/M6Sts0I1qm","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":633467,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgSxVZCRA9TVsSAnZWagAAGFgP/2I146AFuYJ2cEKypP+0\nd5iiqqI00HXqkjsrYmNI2WeD/zA43YCR2uiv5cqZ22SoOIv8GwS8KFy7TEeC\nQ3VYN+Y+uMsjEHCcQVVnoVp6MHrwpuu0jnr8V04iLml3OKNF7XeGzzHHOKus\nysFeTspBxWLveEQtEngCueLbJZn4hPK1vacjg0Zjsvareby3uJSYglg5Nksn\nUHWP3nWFiYy5VyqZLQFIr8gLHrSZ0zrGVT4zhNftycDl6v5CFL6RB/t+Ymcu\nIcV4jXtPiqBeB/U9/ZfSHKEcl2Mw+iEjSeyPobTEaNkHQEFFOQvxcnMwf5nq\n4inckOG6RquiRLEuV+wPYf4qJgCS+ipouG3MOxjun1ALQ9OF/XGC6tjIvPqi\n2DZIXH9QSfY1RCT+2McVXeiXDsJfUiMh0LcuRAeJbxVWaOk0KvDZC75eKCUG\nzMMt21XjUhnM9zIBd0UJEQgA461k10RyKD1SoZFm6nA/Gi3SicsXUVTQRQ58\n8HOwPdxY7V/qX1OK4l7MZmY9801fuStwD17ktQ6QR/q0dDiXKcGeW5QuUarS\n/EyENgDyQKJUyxdjc0vvE6UEw9lX0siKDi1p94i03Kb/kKUmirHr8SrMYAd5\ndrb13VSB0veXhYtReDHCuYgwrlcHeWQpgkZP5RJvCsWf1lO8bOcpjrtG1ZZK\ntOgy\r\n=Bz5J\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7a1a0949b043ef265c2f156cad5f0899bd9609f7","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.71","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.63","@balena/jellyfish-logger":"^1.0.46","@balena/jellyfish-metrics":"^0.1.114","@balena/jellyfish-environment":"^2.4.24"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.110","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.9.5-add-order-by-version-test-7a1a0949b043ef265c2f156cad5f0899bd9609f7_1615533400697_0.43383981575636565","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.9.5":{"name":"@balena/jellyfish-core","version":"2.9.5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.9.5","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c07d5a15a930e7439874ee537f619dcfbc397e8d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.9.5.tgz","fileCount":100,"integrity":"sha512-xGdFfnnGFajAwCYO7I9ozm999d8wYTyp2fVcM35qL+Gb2C8wFn6W83WL9pt/7COPLrXL59OX781qCxnVUFgvUQ==","signatures":[{"sig":"MEUCIBa19A0HOim2fqL0VIqO3VevCzmcrXOcXywsdwNeyGc9AiEAhrY8HoG+qVNDLAzhg4L8NFDD2e/BwH6EoEzP1HW+7h4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":633400,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgSxaBCRA9TVsSAnZWagAAv7UP/1xV24GZXHRUmbreqNAR\n0y3kZmCXbJClf2DFjPhpoRgVM+yXEYgVx2+JF+gKcpfkKM+kDWs25eofSFq7\nnFnvomh0fTOurXAB1RdpcvKwNnvcMktid4ky2wQODeQRuqrpPzmkwxH74dKh\nDMNIYfIcyr+QPDbKo4MWMOx2RDDQjsCAl7vAXTVH8MWHojHiWOxS0+MzqEzC\ntcfHNy9BsbakGyKcOekZ+OKRoOdV8VfPMfUgRmhksGDECWV151f4zsKJRdOS\ncybC4y9McQo+UpAbCR6nGuSfSn+qY+ZbeikB3+h2XOwqIc2Jw+vJhnUoK1fB\naQgDDN3moyCdG4fDkzKsHNbo4/ma6fi+2sIf2NBrkFHRn5pwIU+hKshjgIGZ\nBPQt+WanOd39jxPAS0gKEsDQiLJLBrakf1+bxHOxO3O5xOEZ9D60MRcDgoVD\nr1nmNg8B1jFTeiVBkotfBmXUWlERLe6A/K1qVcY0CmAJHR2fiCK7e19PGZMl\ncBMpg63fmWCl7rL+W5u+f/ExfJ0TukbOr5erolom1Zh2KuidW1JAA41iwrEb\nOgoGeaqBelX10fAF0vj8NLxqs6P0fIFkyxVcmeOShm1ggmpWTC7xqLJHGcBA\nwOfQRsXmah6TGNbQbr+CpGnPH00VWMaRnqmBP+USK9fEA1GW3VSNLFOTxL0e\n/KjA\r\n=g3e4\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"64dcd26c210f2367ed67b22284b0ec024a2ef3ae","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.71","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.63","@balena/jellyfish-logger":"^1.0.46","@balena/jellyfish-metrics":"^0.1.114","@balena/jellyfish-environment":"^2.4.24"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.110","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.9.5_1615533697414_0.49278834766368784","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.10.0-add-authentication-token-5a1aed2bb4d78a414e88d9e44ab80a9d6295e863":{"name":"@balena/jellyfish-core","version":"2.10.0-add-authentication-token-5a1aed2bb4d78a414e88d9e44ab80a9d6295e863","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.10.0-add-authentication-token-5a1aed2bb4d78a414e88d9e44ab80a9d6295e863","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4f22962508143ad8b3df2b9e28da9b001ea41ed7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.10.0-add-authentication-token-5a1aed2bb4d78a414e88d9e44ab80a9d6295e863.tgz","fileCount":100,"integrity":"sha512-xfsZ1ganbIU+O/R3AxSLR15QRUclXl+79mMcbUO9CotaCT/hXudOOASwfp6uzAodFVegaKvR4B718d9BpP35Dw==","signatures":[{"sig":"MEUCIQC8EtpFknY6caWjk8nlUqqQsFM/G65FTFuo0shVITBrRAIgGWTcGaa9HLw1R8yrfEe2e2PjTccthtce+2dcx/KxZmA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":634099,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgSyMHCRA9TVsSAnZWagAAKP8QAIIi8+3Eult3Xqci7iid\nA3wDyxXunNN5n3GeNYbeVsce6RY0kJwLcMDNkYusWW1oOvz9VWhcd/Q6pbTX\n+1duTlfzYC1O9XE4o+X8VBb4Kf55rkGyUUzp5q5D5JXEZcHMGkeCwt+dXZHP\nGmChlHJheQHiXZdQCwl7Zul2LwkZM+SlEA912OrjWlo1nxwN1HG2gJAOZHfG\nhYNMf3hzMlQXLxmhZ14MaJ6ZehLxHA8n3d61fbdluKk9AS+TUGXQWXDO0fJk\ntVcdpARuQVbxMtgE7opQYoL/xXsqcIOMtVezjKLXem2zYatpp5wqdMUmHRJ5\nItOCWhi8zXue001e+qciXiwZm4lIxgqtqJctjwlcXbz8xAOGox+nMkaEklYi\n2f2wMqD4GTDFH9uUB7mPgJi8PeYhCjFwrjNApcYTboES7ekmj3r6HxPsdFmP\ngxtLEKqBvIrJF2BZ28qXzLPcMIS3+KzsUoZ68U4/rIoDIkD+PUqlafqVNj0C\n6A1wNJOXzsdEw5o1/183dQVR31EbYUQ/T6EnAgkQkLKyBx+RhZi8DKFhaMSk\njKAKVqWneOa3UHiqnLeRB4XqmEPj3Z99QXwThPTHS5rYEue+UIBIe7dCQtf1\nhKFOHJgLvHNWFbU0I/kJiOIOVfrt0rvT6WTWUp73atuQ1zSfDvwaxnhS4ncp\nNN6d\r\n=k7To\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"5a1aed2bb4d78a414e88d9e44ab80a9d6295e863","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.71","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.63","@balena/jellyfish-logger":"^1.0.46","@balena/jellyfish-metrics":"^0.1.114","@balena/jellyfish-environment":"^2.4.24"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.110","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.10.0-add-authentication-token-5a1aed2bb4d78a414e88d9e44ab80a9d6295e863_1615536902938_0.4903220833113613","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.8.0-5508-pre-build-versions-19f80f3e1350709413de9173d06ace3463b215d2":{"name":"@balena/jellyfish-core","version":"2.8.0-5508-pre-build-versions-19f80f3e1350709413de9173d06ace3463b215d2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.8.0-5508-pre-build-versions-19f80f3e1350709413de9173d06ace3463b215d2","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d4bed7170c38f5c461861781cc32c9c5f83675a8","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.8.0-5508-pre-build-versions-19f80f3e1350709413de9173d06ace3463b215d2.tgz","fileCount":100,"integrity":"sha512-TfceX6yeR1aT7azN/Xx/IdaGkHesGraufMKKACOQ/YZFLzr3sT3SW07gxIN11TwlFsuSOl5jwfWkEGMm+CHE5Q==","signatures":[{"sig":"MEUCIDDij9hLINcmzSfHA0vMC49UCBuPkm6vkTjDE1xGXJHRAiEAh9JyAaGB/rPSMjtH90z9xq0ZUqjEvg/MoVpxgrBdOLI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":639984,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgSyRSCRA9TVsSAnZWagAAhCwQAKQoVaID8oF4Wjfe7gqY\nogbK5C7LVDfk/HWGzfiSenTVpNdBNooQ6rsxsTtaQJXjtc6rNsxTf7W9ctO0\nOFf1K1Sy0DDq/+053zc7zgvX1w0WoYYsZZvHot31hsuD2JlLOECRkel9VNzv\ntGF0w7/F67Uv3oC+Lx7TJl3sQXo2Kwt+P2pUfTrstI3m9CMG74Gw+xS8Ffgw\nMWf6K2PETNuU9ZTeyY8Tz2+8v/h+jcyZBNiSGhTTFQpntOsZKq4yHa6+EMBU\nQ+s1vFhW9YvG6Y5ACM2JMpsslN+8T3Ugb5faMwEMpZz5EhfDYyQhXIuJWt6E\nnCeKUN+zB4WphXzd4rFWDZxqbfNHDMAn6Qu7Sc51orYOUgx3NEI+G24iMxMn\n6BQzTEzvEo5EVCycnRYUJgTg0L6p4UlcmD84XbGT2i4bAbHWP7orfTcKydyZ\nB+DR5f3sh8++SxBr8Em2CrGUrvmE/WENOZ/4/b3UnYCFbLCtKq72En0ibAHm\n5/eoqZG9a9gUMEb39UbTXnQYu8AvIFlh4HKYHiP+MllyLOC5rA7swSjm+F//\n091OdMKNVKu7Q2SowJtwRXA6isSO1TFBz2sfcfMOOrrlgLQv5twJmvFdpJD9\nl91WvMIARJzUuuO62K1SbEsUx2rKIfWV8ffscmOSqo5mZPe/2slDVIgTcJc7\nAae/\r\n=HD5H\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"19f80f3e1350709413de9173d06ace3463b215d2","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.4","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.71","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.62","@balena/jellyfish-logger":"^1.0.42","@balena/jellyfish-metrics":"^0.1.108","@balena/jellyfish-environment":"^2.4.23"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.1.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.109","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.8.0-5508-pre-build-versions-19f80f3e1350709413de9173d06ace3463b215d2_1615537234393_0.43927617395935026","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.9.6-renovate-non-major-a449bac2dfb0164628792a29ea490fdbd4b687ee":{"name":"@balena/jellyfish-core","version":"2.9.6-renovate-non-major-a449bac2dfb0164628792a29ea490fdbd4b687ee","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.9.6-renovate-non-major-a449bac2dfb0164628792a29ea490fdbd4b687ee","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5e17d9687136afa8635f7fff5844dfab16ab6387","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.9.6-renovate-non-major-a449bac2dfb0164628792a29ea490fdbd4b687ee.tgz","fileCount":100,"integrity":"sha512-/dAlkuKbMhwIn5PVVJ+H10rBEKR956kWZhcZvE04uUZCAbIWFONhaUulPHbru41NRmEDT+R9OBZqYmn4Zy+5kg==","signatures":[{"sig":"MEQCIBhkto7Cs0JnqASLgX/g26m1sZ/IDQ45KaEuH7s7fcdVAiBLFWjwEX0pSMVXGepd6+bJXUHhC+jhVshdQ3cYD6Zk9A==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":633853,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgS1DyCRA9TVsSAnZWagAAOTUP/0zM9C0NN4BWw8cl9Abz\nrxmdSJl5sFg0dX7FEr57959ZFyF71VnkX3n9bD4uL/wjrdMvI1KEJdvAe0LB\nCj+Wg8soO1unA/A1tPc203wn4XulTAHCxhYyTmusFXZF2ovJnJV1gNDK8eUx\nZdIUi24SQjqu5yTWe4lcvgihzrHwTUA3Bs6Xp1RbYs1QbDwDDcDhuNpQd5E+\n7B3oTplHpUDQonDJ8x/CQ8+Wz6L6AvSSernq6jtl1T76W+MSFfZCov9xQOch\nD9tk6q3LSQi8mx6vxs8bkouJeIoXgyF5/Z6c8U5gN/nhgBjuYTlETujStULW\nejzP4a5AE4TF951OEh2WzUJom5QcFtAQZLsjjtuDCKJOyaIoukL3zLu6hBsv\noykrLupCI6i7/6EA4V/0IgQt5utsCVB41EVUwBmkwjz3BxIwl7tt+U9HrjCo\nZLNYYacKmrUuD5n38KFSkzMY3QY6hsweMWaBKx/i4mnpMswdsdsv3FbyPU5A\njtVwJvsP+HoyAscpCIdEPrrGiz63FMVc9PhEaP6HOSiYUp/X34AiVyYV0Wgx\neqCviV/rDTSI/PbfwHUDBxq5oJ724PSdRFod4e6rGtmnbtyOkmHI6+jmeG9g\nV/Ce6V/hzWhPR2OdHwfyf5QToTYsIQLfcF5ZyevC9lls3NM1EVIktqpn6w7y\n1l0L\r\n=cSbJ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a449bac2dfb0164628792a29ea490fdbd4b687ee","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.71","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.63","@balena/jellyfish-logger":"^1.0.46","@balena/jellyfish-metrics":"^0.1.114","@balena/jellyfish-environment":"^2.4.24"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.110","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.9.6-renovate-non-major-a449bac2dfb0164628792a29ea490fdbd4b687ee_1615548658194_0.3410473341150637","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.9.6":{"name":"@balena/jellyfish-core","version":"2.9.6","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.9.6","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8b5d05b39937c6814532e112291025b624d21199","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.9.6.tgz","fileCount":100,"integrity":"sha512-Yl7XYW4NNkSsUH3p/RCYIXGQUG0OrDTZEbnFCxXCQOMfD8Po7TvOLjQ3J3j8fRAjK3x5BmFPsx/UUfMvq/5YKA==","signatures":[{"sig":"MEQCIAjgYZzRVhxVLkGYUP1kaO5QtYTqbySK6vFwzAjjL1NXAiAt0CZrxknogm4+3qU8tCva0qG13zH4uN2q9zosQ1gfIw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":633793,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgS1JsCRA9TVsSAnZWagAAeWwQAJO1+SHJGYuxcgT2IOsB\n5VNkgXgiZz/RsVnu1WY6qh5DTXhbfzbqjiYaImPGwpMKhHCfa/TV7oVQs+iK\nQa5rHnBBHlIhsm6kh1tDJawjvfbVDJNx2R36EyinUHX3WyP9g/g7Vb2poZtr\nTfYWNGbbOVaiFfzHkBjHU/ZgozDIje0xv4xMre6V/A8U0heGV2XegDlTVdkU\nQ9zRRcyi6JzNws2QCxJhjtNcvvbojl3tk4RVwsp1EAtAwHz3YSeLFO+wlq5U\nxgJHKwIxUXi3KDDgpXKZQyg9vodyXhycoe9MJkFJunTUfimiLknhIMXoTmcA\nKvm0cehxhM2Qax2Mnh5kgjcxTOchFFIBQK36hHeJduXN/G4e3055Z7p3hUhQ\nqKnNwLFVD4wR2gtZt+yfhZfUVv9b47gfF8L6oD03rYunlSg/b8B7NDG/PzHh\ntQVwlXVWY1Fyynj3QhN26onR/tsVZ4V/ENGCgVxLkgs/J6qKPwjIOIUJPg+w\n/EZuMtfhtdbVIQdalXawTyfjRsbLw+zSygYOkkarAn6DLGfACflkXUC4tXVY\n8MaR9qNnfkXiv8XSSdhZfI3X/Ggq16kTu4hniDzisdemMvg08uOOEpxrvbgF\nKBXAXz5gUTcMW8Dw0T5IYmOwpzKVY/0bTlvuiR0eK/bucXDn7XglczS4dGUr\n5rW0\r\n=8yBI\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"bad4cd0fad20f3e279bd96b3ba4242c4b4e09c6f","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.3.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.71","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.63","@balena/jellyfish-logger":"^1.0.46","@balena/jellyfish-metrics":"^0.1.114","@balena/jellyfish-environment":"^2.4.24"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.21.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.110","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.9.6_1615549036093_0.180129165244163","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.9.7-renovate-non-major-565f4d7cc71a552cb04f2c0e020df7504efba2c0":{"name":"@balena/jellyfish-core","version":"2.9.7-renovate-non-major-565f4d7cc71a552cb04f2c0e020df7504efba2c0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.9.7-renovate-non-major-565f4d7cc71a552cb04f2c0e020df7504efba2c0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"05b8009bf63ac2258f56af0a95397a613c90a29c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.9.7-renovate-non-major-565f4d7cc71a552cb04f2c0e020df7504efba2c0.tgz","fileCount":100,"integrity":"sha512-ek+cEH5l7nfNMDIHnEGVYPm97dKFdjaFPQbLJqXDnbIA1JVwwKjEjVCgVUe4JXf3S8OrPDTWCWLE+19M/zCP8w==","signatures":[{"sig":"MEUCIQDKxVjhcUQntUZn/E4l0GHVVx44aoek7ey8h+PKQLM2ngIgFN8M4mQZyDQydpxjKncUf1Kj+hZstAi+nTTMw1Knn8g=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":634200,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgS+lSCRA9TVsSAnZWagAAtmUP/3dyePT2OfjeROfQcUbt\nFYLDeRKikSGEaw6x/a2mREm4knOplDqqiG/Yz7al/kOKGBNZsn7k1YoN4b+l\nMOw+CtQ3HTy5lC9XNR9BRnWyASAVdbT5iG55DhMSELXDTcUheKxgaw2EdjNS\nkB3U35IQaLaedpVb84jicZ19FN+MJbWV0tordTB0Uqz8FghFZEfSY0bicGdc\n1DIfUEc0OfGHSKk+7NGjE8MM7TLvfhfKXXYyuZqLuZNG7/QLxLfEWrQZTtn0\nS4zyNbUDwnAiaBm6C3kwl/YLn6xALb4VUoIdcr53dkXNEF62KTJ683HSxgVu\nF85oWmfTH9koLwLc9ZY2+eIH8rVrUptWtOON6PqjH376o2wO9WWn8L6seYp9\nrJmvY3iGnqPtjVUorjxOklTRmnQlxSXtExIIQ7ojgyU1VFqxpGGHZ2T9kudP\nN4Hlm6QezSRb0v1A3zMQwalcZt37vCZt2cgIv54xYCK/FVUI2fsXBk3I08Vm\n78IsG7HZR33CT9z+xnbV1Mp9X3FxLhgymUT4YVVsaVP9BWm9YnMptSTiPnrk\nO/xVWoi9ILYDGsMgrlXC/h7gR//6Oup+DZqhDBlTkxbT+IbA0YBhSj3rnFfH\npJeHKp40nBTUpYuzY6I68TXvz7dR79zKOFmHVt6OT7pjzh5UX8hOJCKm5bQN\n2JsY\r\n=ygxo\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"565f4d7cc71a552cb04f2c0e020df7504efba2c0","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.71","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.63","@balena/jellyfish-logger":"^1.0.46","@balena/jellyfish-metrics":"^0.1.114","@balena/jellyfish-environment":"^2.4.24"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.111","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.9.7-renovate-non-major-565f4d7cc71a552cb04f2c0e020df7504efba2c0_1615587665664_0.2618935581190711","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.9.7":{"name":"@balena/jellyfish-core","version":"2.9.7","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.9.7","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1dd89aa55b3baa93309b11edda9cc21f06a4eb06","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.9.7.tgz","fileCount":100,"integrity":"sha512-DtW9ZwuxKQnQyZ3fg9TawW+6ceB4FCg1A+Cz31oCZQ4NVpeOBT7nsuuDy7kwgSWCGPm0J6kJUx8Bi2tQz+fozw==","signatures":[{"sig":"MEUCIQDOJurS3hVDaDqZbahTvd6uDJG9DcJj0R6OokGQfuz9zAIgc8urOLf6l/CpYCDe5ibWcIp+oS5kSX2ynKBX8F/9eX4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":634140,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgS+poCRA9TVsSAnZWagAAfGIP/iS06GmZLt1tfn3NdnRv\nycntSzCpeoDY3Z+RmRh9nIOr6UShdIKPYzuzKQFbbp0+qHjn/vdQ5whw5XF/\ngH3+9HyPLaPNRPn0OoQZJzQD6lnfR/gTu3y9+8pfQxiWAUkpyHDeVyYPsLuE\ng2klJMeiMJ9fBrVRYQ4EAZsAk3rfsJ0+Jmr8nfjAos5Xu5t+WwtwlqVlbPRj\nMRQMeOU1U4I1bNcTtAaWviOPsibdYt/S1Ik+OGoikJzG4yqVHFiT0mOi5BYH\nCpINgaHhCmfUGwSfvv9eOQKW8y/N51YJAwbaRJgcKt9O6dRWAj5CyQSWlo1W\n1WRP0LW72PMaQLubaKl3OnRMUJcJ4gVSFespIRqXt8ZFWdE4g91Zfh45MJLM\nEEhhl06YSHhFWgLHJnHAkGF8afGDA7F86IxUZYEMYKh+3nVXoafJ2FPDY/+G\n8vIpFYGCay7zdyLBrrVlKcDM12ymQ/if4mQLBOdMG9FBD/Orgm7sAApIU+iD\n2ttwKRCFS7M/uv1brSdFnJgVgyD7fw5l3G+NOr/l3y5gwq1j3ZpbQxEA5cR4\nlmztHYbawyOb9xp1I3DR3fjkd9WLrnPO8dkIrJT24+ASa2SPsbu+2JkqGBNI\nZqfmo7Rj/KTBPd8IX4DxE9gZLGxLRV3OMdDdQT+yvuGws972prDriC5wn8Xw\n0NsS\r\n=zf5g\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2422bdb0f78e5f515fa7fe39d52bf6c336d803cf","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.71","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.63","@balena/jellyfish-logger":"^1.0.46","@balena/jellyfish-metrics":"^0.1.114","@balena/jellyfish-environment":"^2.4.24"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.111","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.9.7_1615587943617_0.7552904214973684","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.9.8-remove-new-version-columns-d988e0746c186d47452d01ad1822d90056e43fcb":{"name":"@balena/jellyfish-core","version":"2.9.8-remove-new-version-columns-d988e0746c186d47452d01ad1822d90056e43fcb","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.9.8-remove-new-version-columns-d988e0746c186d47452d01ad1822d90056e43fcb","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9588996fe82d12397811a014357b339484b16b84","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.9.8-remove-new-version-columns-d988e0746c186d47452d01ad1822d90056e43fcb.tgz","fileCount":100,"integrity":"sha512-JRmRm12l2mFzr/BUpeG2EtBBCDxBzMrHHV/DyyKUuBQXHaxRcsFyok2gfAXiUc9v26Q3qFAcNelrdT0c3NQD+A==","signatures":[{"sig":"MEUCICuCwgnC0aUxUevq1ohQE6CgV2jXF9+02wkRdBBZaX0AAiEAs5kjDGwQOIoJQIzUkOKIuLszhTzFG8avMdWwzNmZ7g4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":634804,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgS+1BCRA9TVsSAnZWagAAWKgP/R/peMALIgtgUihUxYdj\nKjw+oYbN8G5K+iVpiRsKlhtUyQsKlQWBTUn5Z1O85Q+cA/GZYdJBldyPpUPK\npJNCBDHeykCEPyfPl/ggCGECoUZA8QLQBThqCV5K/+S//zyAZsj6w7qt6hmq\nV7RZBBpuZORRDNhAUVHm9WXBzd/bQ2KKf6fm+Yb2spl+iWXU05bHD/5oCOTb\nOB/GgEMQ8Hfy8fxQZ6FmVzPnFMdRZ4sAGm28vNQjDJi2Mbb4i99ZruXKIhLc\nFFE+NO+4OKL0WCbRnXW3mgKCPYMeN4c+14/g3N4Y3tIPLgmbFprUaiaBFcBb\nyN+dyJ1cNpLkvAdb7SfVV1jHu9illCwW0xwZ9HX2PmJNMvHjzKF8aI4uEfa9\nQsd5v4jfVynPrkxnpFAtAgrp5271U4fgYnSpQeIcDwNE/9Say41knE0YFKzn\nWIkNQARvvwWOe9A0UxZ72UJXpOpVlPsSkfZcXpgDsLG5XSQ2EQJPAh5tRZDM\naUUYFub640NoGOhNEcPeaw+Eh2SePyrbM9HHGU46t2hp5KMmYIvB0dDGFURq\ncy4Dba7UFRb1/gB/Q6luoCx2r/Ecq0JGSAQApDy4CQdAQSn9P33xBQJfyYD+\nGVT6IwbAi7E42Zdde70xAznHETviUpnllwaIgokdSl7Y4fYjXHb3czJiz6xQ\nI3Tu\r\n=Pgoo\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d988e0746c186d47452d01ad1822d90056e43fcb","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.71","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.63","@balena/jellyfish-logger":"^1.0.46","@balena/jellyfish-metrics":"^0.1.114","@balena/jellyfish-environment":"^2.4.24"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.111","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.9.8-remove-new-version-columns-d988e0746c186d47452d01ad1822d90056e43fcb_1615588673102_0.734154095749008","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.9.8":{"name":"@balena/jellyfish-core","version":"2.9.8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.9.8","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"98c1c2a45e978e9f1dd2dd15779d75b96ee3c338","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.9.8.tgz","fileCount":100,"integrity":"sha512-fd+q/MPoq6C0+xRRIvj9iVPwwNefuU/TgAUwl52gWa4wB6gIuhcQdni6iZT6wYLM9yOUAwbtXZr9z1lcaH+3gA==","signatures":[{"sig":"MEUCIQCTSsbE391BOVDdVccGUSEDSXKZVz5UYUhA4U0Sfl7SRwIgJZPSrqNvvqTAvIVdk0M7qqZm5tQ8zFfVuAerC3pK6O4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":634736,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgTAA+CRA9TVsSAnZWagAAQmsP/jkvvuLapTIp65fa4J62\nDQLcut1Hr8wqkYezagLl/x0xsdz+jYOXJ/FY6TWbR8VtRSeTJplpBjALjPgm\nhWo0DkbHeaaD7x/unUpGLYj70tdLwI/Tckc/z3euVbCEizxTpWNJEzKi6545\nej+XTYOCmb3zB8CQvJMBslC2sLn2lEb2wSoa+5p639jZlLJX7O3IKRyaWGCN\nT2h9RXx2/GeSUBfKaYUtjKuy+ofowDzLArk4aCtwW0E6VXv0sjnvLwQadMIQ\nFUsfMoVtzNh4kEQ+BT8dLcL4Kunzth7000hI+ynpDI6/ibNMjxpPKxFo3jqa\n0m2GUjAE7yj1kr5ncmwBi4KP1HMtfmAxRzYRzDmUJY39ViQryFukIqX40xna\nRhI1yarR1DNqcWhz2D6Cj/TXCnxZBo+MV7vxV6R7ls9fLbgUqFBNoCFEcdHI\njvDWvTGsmTiIwIQzwFrlM1Hqzgg5VuWmrIEe1Cy52eJIYttQ8+z0gP+BXcBq\nayE0b4SEm1GrMCYKGcYjNV991g7ctVFBF01apNUVo/U7nKfXJbFFJWKmxgwK\nsZ4InYA7euRfpCcIqivUakARxWDL/mJ1Sa2VPqREWznUQFwKoQEIS4cFxZhX\nu/GaEdKME2RT3OPiKlruLbUqWSqlL4j6+kyNgE1tpWd9yRh2i6usUhvQZTDb\nTcqE\r\n=cgB9\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"adb21d7aa98ff10ab48766d1831f9462c24db0c8","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.71","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.63","@balena/jellyfish-logger":"^1.0.46","@balena/jellyfish-metrics":"^0.1.114","@balena/jellyfish-environment":"^2.4.24"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.111","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.9.8_1615593534288_0.7171684576008504","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.9.9-renovate-non-major-8fd14b183d45150582cfda6f71074656371093ec":{"name":"@balena/jellyfish-core","version":"2.9.9-renovate-non-major-8fd14b183d45150582cfda6f71074656371093ec","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.9.9-renovate-non-major-8fd14b183d45150582cfda6f71074656371093ec","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"28066b3b72e804993c8e4767ab698fc0a31a44cc","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.9.9-renovate-non-major-8fd14b183d45150582cfda6f71074656371093ec.tgz","fileCount":100,"integrity":"sha512-cVgkNx9lITlS/45ilJ4UvM9YlHiKbHuqyfhte6NZLAj1/vOVPnbm5tTdT0qCVFAvUajBavxRowilSyXpl4q+/g==","signatures":[{"sig":"MEUCIQC71Prf9DM3dpKeSVLTZgBd6r9xjecw/YcUem0GRdYeFQIgWkKaMXZY978y2pghV+3Zw0GnII0Bikcin++RXUInU0c=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":635143,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgTAFyCRA9TVsSAnZWagAAPbYQAJ2s+a2SJN7rIDBGH/zN\nN1sVpLoCM1JfLDVyVOZnSkbQXfKQIISJnDkatLICv7BebTW7eimVwW8iYtA/\nhO2ok9Cdvzszdm7oNIA1McpZxOSVokO0DRT9m9AD7OJkOC2usDeaUYtOyiti\n6ozALpUIgvn0b3EkZpFccH84W48+SKAfE+FRfLTTjUr0vj93/CKBzsnBj3Kz\n9jW0J7sJwORmuAilsmGmmZ1uqSGabyqqzLpdPo2jVVXLGX+PW6Qt6vbQCWE4\n5wzG5ECKQ/Inq44Iz2UtEyPgyEEBsuFoGAXOSR0VCbEekpVgTzRGl/PMDu6F\n6sfgYSxBoZVntRBQDmsufNA0bNBeJi9w866QrTqhVlCWXZuSrxornHiSuI6A\ng1EfHNsibPheeMWTSSYl8pgYaeaefzcFnBI2AXWk8/O36/vWJcFzhMIPYF2B\nNgmrYUcGiHnFYELBcAMK7ErqOpfAQY5IpQBTD1HXedey0mHQ5ZD16tRyKZId\n0QCd9byDOB8q3qYSKkC4IYf1K2susG/UPc/wsV4HQTVb+ccF5Bw/TPG+RXft\norrOmLDWQzTB63EM+neK/XEEQ0s3a7Pa1yVCUD8kSor2MWiyyBDIOMQiH0Vw\ntFXddVKrVOqvlgh84xHb5jQHQmv8GMazFBH1GnW1YNw/RVwLizriEgDBBmab\nJ8iB\r\n=iUzn\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8fd14b183d45150582cfda6f71074656371093ec","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.71","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.63","@balena/jellyfish-logger":"^1.0.47","@balena/jellyfish-metrics":"^0.1.114","@balena/jellyfish-environment":"^2.4.24"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.111","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.9.9-renovate-non-major-8fd14b183d45150582cfda6f71074656371093ec_1615593842292_0.3484869762114866","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.10.0-add-authentication-token-370922573c1152d370ae9ac119346b1e6f6598ed":{"name":"@balena/jellyfish-core","version":"2.10.0-add-authentication-token-370922573c1152d370ae9ac119346b1e6f6598ed","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.10.0-add-authentication-token-370922573c1152d370ae9ac119346b1e6f6598ed","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a2d80ddc8953f99b6424f3b96faea3b2d7d92802","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.10.0-add-authentication-token-370922573c1152d370ae9ac119346b1e6f6598ed.tgz","fileCount":100,"integrity":"sha512-XDWzA34G4+kGqj7RMlgiNl/AmYno4VJpPOOnnlP8I+bfaskmcM+L4qM0BnPRUEFTDGTHVnKPzh8f1jf2H/duvw==","signatures":[{"sig":"MEQCIA6Pcd32GukrcgYq5wI6quUhAKMvlK5X00FQl+qEqr5zAiBWvtSYrNXNrYvBgBW25ed3fjUiVhX54uNL0tG6BIYWpQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":635435,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgTAOKCRA9TVsSAnZWagAAthUP/3KRftDSV75BzkmWloth\nMHGf640yjjFY2QTsLxXmsYOYYQR0fOlFGH/kZiE/qNAxRIogyudgX+Jq/kob\nEUloXIrS93W3jh6x6b6L/C5I7IbCpHiU7acqjoP4/GJfDymwVr62NcJhD/Ef\nBZ56ocIY/50jXf4EM6gSAORY6Q7H1C47TDhsnMUhXWbIyPdP6WSdHEfBUnw6\nFKgB8ClTT7F3aGXyncamWgUwDGj+q9ahH5VoPmcMk/Eb+P1abyEZzM9i7hIy\nHqOb7LOrRdn0fPumsFiQKZP+NI/M/P0LoGmCPfeETlRnpjlHKVamxqZjrqzy\nGBavq5B8HfcEiCSvY045YMyOR8Mv6i766uh2gUcJuvDVHzrLtJqB2wkDeihP\nNbJrqJLKdnGOykICPs1Bsk4Fh2sqBdfLSresLt9IdjKLqS4kJolm1pqzlIDp\nMOkEL+F2GSgi90ZQilI4Pocwbnj83CIY5hTWQ/ls51t1FDJInuxENCA59hoK\n7miHABfaRG17/RWVzm/joADlvE4pzOic4k9QF05nWm+HpIeJ2NGQGGw1Wg+e\nHhefDrjpf/2v4OPJG0vT4qBT7QsjiQUToTJJ1x2EPuZqf+IEvj+ihFDDr0SM\nt5Nw9nhtDeWXxXuuSvtNzrqzUpHAvWVH3l7WnTuPARTi+pyCF32VdAMN4eSa\n+cc+\r\n=UsAq\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"370922573c1152d370ae9ac119346b1e6f6598ed","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.0","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.71","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.63","@balena/jellyfish-logger":"^1.0.46","@balena/jellyfish-metrics":"^0.1.114","@balena/jellyfish-environment":"^2.4.24"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.111","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.10.0-add-authentication-token-370922573c1152d370ae9ac119346b1e6f6598ed_1615594377753_0.8657851276178878","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.9.9-renovate-non-major-83743254156ef242099eb5748a91ac03202717b1":{"name":"@balena/jellyfish-core","version":"2.9.9-renovate-non-major-83743254156ef242099eb5748a91ac03202717b1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.9.9-renovate-non-major-83743254156ef242099eb5748a91ac03202717b1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6af7588e2492ccd5d9db2ed2ad423cdc3ec6b45a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.9.9-renovate-non-major-83743254156ef242099eb5748a91ac03202717b1.tgz","fileCount":100,"integrity":"sha512-STmAjfzjoRobjVRFRfJfXgR70KhQXIS1KqtkbOgRj2YYpKIo0d156UJKgvgUcqAftreT6AOKBD6PjrucQlqTPQ==","signatures":[{"sig":"MEYCIQDQ5DkfhNwcQ1Jw485TGbKEISDlMUL0Ic/T7L2wjyJKXgIhAMN5gWg3dYwPAiRpBWvjFNpfEzm8Bq+F5n8STTOeFz2b","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":635143,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgTCSlCRA9TVsSAnZWagAA3NAP/1IRB2F355Fmve+DVWuF\nRtp4AU7QI4jKmEzm2IiY5aNj+X/o5NyBGlBeZtBKNxl6h6w1AcB3IFFR1Pmd\nezisIb0glag0pXmF4iqXRfwHucDv4tNb2PKgNO3xrgQQtQ6BEenWBT68NIfX\nFC48j/cYvK82s6WnR1TPWaHerSgP5JB5TJ68LqVDdrnoQASZTqRJciNHj7q+\nOYEGYwWwuuMQ1rXeRVxy24YzvdcUTiPTTu7ltqmzCgS/UpvlGegAnWn8m+tR\nf9ZdlNlVVZ+OCwU/aWf94iX5uOxCGzVVD9c1hWm5YksDW2ZfwjwBcQmnH5Uv\nNXz+WABtv3CItt1gHdfwwL1oGE6/xzeh3rWrQa6TB69YKJdgccu+rsT5zPYc\n+sni9KlkLSD7F1D2pPK4cbkAfxAoQosL27PLCqCpeYH1nDs6sW+HysHpeILr\nTvOxZnZqWCKqReSAGhNtkU2tJCwvnWdQNYpzioXghua38H/pTjj0pr4kfyph\nXih1xpsgyr1sdUn8uPSMGMgETQ2iAi5kFljY/TARmoZKlW3ZnyXkCOhO0VCh\nbUbCI8KrRIUlyLXXodlYqFgYuT410iUoTCu/RuZiMisTFLAxUtmcGO8pNrdR\n4jAHOq8XvgBBMSB6D/RpNwrkjRr5SYlpB6sWenT8f9lactJ99LI+7Vokowwu\nUH49\r\n=b+PM\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"83743254156ef242099eb5748a91ac03202717b1","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.71","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.63","@balena/jellyfish-logger":"^1.0.48","@balena/jellyfish-metrics":"^0.1.115","@balena/jellyfish-environment":"^2.4.25"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.111","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.9.9-renovate-non-major-83743254156ef242099eb5748a91ac03202717b1_1615602853045_0.5987212628129694","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.9.9":{"name":"@balena/jellyfish-core","version":"2.9.9","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.9.9","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b62fa6c32abbac642ebf806bad76147ad785f233","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.9.9.tgz","fileCount":100,"integrity":"sha512-takTuiUD6pfKiYFa7IVV36Kj7LDxcinXlQsx5Ou+OPTD8ZVsBX2+lkMhdiw+usTBiGfkUAnTkAMmQnvZ5IDjtw==","signatures":[{"sig":"MEYCIQCmxdFgC7T9rTlbe5EwpNZzYOipQml1/J6r6jsQDDyBdgIhAL38g+rTe1Rnvw2d0EEqCecSRlO5CXg33EVclopQE7b6","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":635083,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgTCXCCRA9TVsSAnZWagAA6IIP/0mtMaqdhEb/77MAyn7i\n49PxyI9F7hEBXvYn+/cOM8wTUWqRi+R+9UR1qSBfmp5XMlZ/Izsj4sSw0T60\nJXi3twW2Pm0/uBEhu2aiKUccu9cZEkOki943RPMiuIBfkjdQq+hcvISpe+Qk\njM/i/OqOGUXROdKmmt4rFaaC+oqGKrlX41B7VaSldINAP932YGet8V/F+6AP\n3cpnEt30a/xiyvNXH/KgS36YEF1XHRFHEA/jMnU+vpLFTVY9msjKE7x1h6C2\nTwgwGoXgx9F7jIhgqP8l/AfklEjZekesoD+53UIr3Uwp09By92ABDSkSIcxz\nSNXRsblojc2dL0rL5/82+RsFKfjJFPDP/G6qLGpQSj1toxKrGxTpYm7/8zJl\nM+SaOlaR3EgD+YFereEjRsGLK3kn29lR5Cr/HiSNWq7hJSNQhbWTuuJDlxA4\ncuILFmxxM28SLVuUYUnyZ0/5nE+eidzbrw6ficCXuc0CJJm0ezXsWVf7G5PI\n6kHtqbmv52vner1D0EiX8Xh+nNJWhHQ7+76EeTwPNqwveZLDRnmNDamzZlxT\n0ShSjrqhmR+BLLvWrYb7cVsF/2j61INif4cTIlC0kFV4wqY87QI2/EoLe6L6\nmhTS8gL9H/eBFrZiRIlEl81O+3mSJk5BmowYEINCcd5SCU0jv0d2Cr1b9YLO\n1hqL\r\n=ZJgB\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2053921db26e9b8127d81710f8c689bc89b30bd4","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.71","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.63","@balena/jellyfish-logger":"^1.0.48","@balena/jellyfish-metrics":"^0.1.115","@balena/jellyfish-environment":"^2.4.25"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.111","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.9.9_1615603137511_0.39793852586676803","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.9.10-renovate-non-major-fd5976185cb9311e7dfbbedbbff2747e0e4bcf79":{"name":"@balena/jellyfish-core","version":"2.9.10-renovate-non-major-fd5976185cb9311e7dfbbedbbff2747e0e4bcf79","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.9.10-renovate-non-major-fd5976185cb9311e7dfbbedbbff2747e0e4bcf79","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a0fb59a1e0f3c54bb509953d3105ab9dc9bc447b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.9.10-renovate-non-major-fd5976185cb9311e7dfbbedbbff2747e0e4bcf79.tgz","fileCount":100,"integrity":"sha512-EeKFg4QJsiFVDyK1/M9C09UUYPwk586+4e3ro0Zv5DKsCil1FSduiEstlKlbQQDMyn6ETko0vge1do9Z7T0crg==","signatures":[{"sig":"MEQCIDHV4aZUZMfEPNWIRlGEt2fiD+Y8DrXjvbow8jkvxf82AiBu9MnKwTsIscUpXgytQiY03vb8D0e10u8HEoxZpD1hoQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":635571,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgTCa/CRA9TVsSAnZWagAA9ekQAJ4M9jIzfil286ZCzAsZ\nsFszeit1s/3N5+P0U4Gxtn/0qBVyVKmIRCOZVIVTzNCtPkERZyuT74leNKV2\n8Smpyjon6kKORJz4831omtPm2zsZPcUFZVZrZd3JUfDW2We8MXEabWvDi4mX\n7Aoe5GgCYFZXxruyrh+pgxyV6LR1uOVckhtFyQVWlQYN9r2o+ki6X5t10CqD\nIFf1btQPtAtxc7N6wdsXqgyCZbcpxO2YeRHAgmeKADblsk8iCRrACg7fBcni\nDt3pxzWxvxnRsQGeFDCQtfJipy461Bo7Xs/qoGBteoOYAexyAZjnzUBs6+M9\ntwFT6pNYBqOLM1yXBMsmMuV1BvmNrecAzPNWGhfRdpyosene1LLhRkUQcIH3\n70I4KMjXIHYYwm59/gwiye9anZ3a6Qg98VdSwJrFqtHOIncUOg/g4XM/Ygqa\niqypC5kaxV34a6C4ztThfA15SYU4GVTS3aDiycVHqNdgqWXSdxrMwNZDYHng\nTPnwnUjh9Ys09LEMS1RZq5J7UTRqxPaET6uhXikstcO9KPGvpJTJOOJwyDXP\nqE9aEEHrYqsj8KOh9D92CPIcPZ0OdA/KrIf5+BgkLcc6rNg50fnpKOwS4NqR\n5JdPE2rpod1aHz3Dq1oJExrs0FKJydPeZkwR0XdJmiKx7NuTMbQckbELmzyy\nTI+k\r\n=76Ha\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"fd5976185cb9311e7dfbbedbbff2747e0e4bcf79","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.71","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.63","@balena/jellyfish-logger":"^1.0.48","@balena/jellyfish-metrics":"^0.1.116","@balena/jellyfish-environment":"^2.4.25"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.111","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.9.10-renovate-non-major-fd5976185cb9311e7dfbbedbbff2747e0e4bcf79_1615603391408_0.4838983508164054","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.9.10":{"name":"@balena/jellyfish-core","version":"2.9.10","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.9.10","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"40e86a561b597c461d07c51fbc701ec65980bf63","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.9.10.tgz","fileCount":100,"integrity":"sha512-uyudHbxgGnRPqjw47KheeSzNWyioxpoWA8zPt66wdWHqVUOP9LvHuVcRjXU6oqSrNY9Hmko4pZJhXl1a68Zg8w==","signatures":[{"sig":"MEYCIQDjN07i3lEihPhaUY8eafIKGsN2L9hPJnWk64ZcI4amxQIhAMB49psq34lHT29xL/1nS3PxPsiOYkQGtOk96PHURa54","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":635511,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgTChLCRA9TVsSAnZWagAAzFIP/RQ2kBAzWiRHHfZ5YR2Z\nQLnvViyJsLQ2Xihgcue8R0srm4e4tzFrPA1fgX91eut05G3xXKAFPxgXHjPp\nIcMDjL2S4s6Vt/h6QaE9ePZimnQa+lXerHHXfBQNKiOIoA+o/xjaxflG1Zus\nV/idgagnz0Oeo7/PBWMDVIJzBGHTLZx+MEPaAZ9WaWtgDlIvXvV6fHfkqONo\nXwskHPaYJsOO58NNB5G5EYGOeLhe9oVRkmkP3sVPpHvDMPEmX7PPFL7/Ayog\naXlJudkn8bYRs875a0g4qJQPc0pNBh5T/MzBmDJz7KZY8MoK7f9m3/3WzC/1\n8YWHRxmMKzghmZg1jV9zbvGuvCnk577G+QH5qhr7yYL9WzdemM+eC7o9wZoT\nUWE74EbuVjWw/Ms5OtFjTXqCPssxtkms2xr5IPJPjlkQwWO1CC8lOhwLe2ll\na/bwVy+TNda7Y2MFX5wnrsmWWJRZJdZWdL28aM2gesGI1PvmP7DAFfQyJCBM\nyzvOTre76aeM4JB5urEtP6Mlvyl+O6LwnhABLJbQ4mxXe54i3ySr6jQSPe2W\n0cjeDcKPDBWciZNcXu7PlHcaNI61qtsFy7puYztaXh1SPCF3aYPl40Rviv2V\ntkktgTWK1fue2ZpXrquRmtaOZVSBa5Nhivorw3XUMR+dvxcq/EarpMWXun4J\nJXFc\r\n=HyRL\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c058a61a79fe42795dbbca8b2be446063b29e8c7","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.71","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.63","@balena/jellyfish-logger":"^1.0.48","@balena/jellyfish-metrics":"^0.1.116","@balena/jellyfish-environment":"^2.4.25"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.111","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.9.10_1615603787380_0.3385950079253164","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.10.0-add-authentication-token-40c36bd75768f562c82148e3da89778a721591a9":{"name":"@balena/jellyfish-core","version":"2.10.0-add-authentication-token-40c36bd75768f562c82148e3da89778a721591a9","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.10.0-add-authentication-token-40c36bd75768f562c82148e3da89778a721591a9","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f04c431183267fef8d87cbea6da63af6417021b3","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.10.0-add-authentication-token-40c36bd75768f562c82148e3da89778a721591a9.tgz","fileCount":100,"integrity":"sha512-mipuWOXJQ2U3fiu6NDt5tLaDLmDH9NE/4GvIBg9EXZhN1KJFkcoze3tlAMQT1m1mFugmDPnJDSTfx1anwydW2g==","signatures":[{"sig":"MEUCIEqStMFjKNO0JATfoWnFF2WWgrBA57pV3C5x6iFCiy9aAiEAwiZHdqJwnnyiv7bUpxMcrRzgJInpv1fJ9qPRCVFjTfs=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":636209,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgTIl3CRA9TVsSAnZWagAAlR0P/1HRfLpC9tZQiNrJ11EU\nIBQQqo6vPvabz1Z2aKzlC7c6qdJrbt/c6KEOno+eFkL4kI9mVd8PduoH3UIF\nq0eq5oVBev1NfzU5C77fD+m8qF9vf2ljwhwQQYaeVG9qzW7l89sC4JQskRgv\nPjwe20RPsu3srFsD3oUPCS/7khLBzsC6qJyqaNtWeIAdRX6eNpI3ykQQTgRE\nCfMib7vGpCG3D4sV9kK/C2kfu+iqU1rMWcU0lh6VAqfx3ElUGcoPqJQWQ9Oo\nLRQHe2hWNlkWRjhe/xmn23YItFVVtQDCG/aCmMPMSUSz7n1MqFhrlc+mnxBJ\nrpSBsb6P50OSLgGLM9QSdnoY2bVMyl6wJH0g+M6pm9t3IRVcflsUEhBIYpvv\nRMEd4ZcSz0omArjNNUlK7w7zQ+cD61u4HhV6/fhV6JbljwlleAIZJqHNKrii\nbSbcRreQZGaTpNZekdYGXOWj7tnw5d060tG4lPqfsrob573god/N1xHJvdTs\nSaUoVACV5f3IE9k3BAJzPtUenRqPC0k+VkyN0WOsS0j6gb5bQh7LnYUpmTU9\n6qWkVikSuS0VT6M+6ny3en+piMeJw6jJNRlSDtwXl+Y0JQ2jWwM16/Db5Q/P\nG7hAGXGNc+TXGEk48IS9262CXTjICW2ZYyUHJp+CORdBqhXCKGps7eCGn71N\nD3T7\r\n=Qo//\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"40c36bd75768f562c82148e3da89778a721591a9","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.71","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.63","@balena/jellyfish-logger":"^1.0.48","@balena/jellyfish-metrics":"^0.1.116","@balena/jellyfish-environment":"^2.4.25"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.111","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.10.0-add-authentication-token-40c36bd75768f562c82148e3da89778a721591a9_1615628662848_0.7215186596114973","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.10.0":{"name":"@balena/jellyfish-core","version":"2.10.0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.10.0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"05a599085dbbe724375869cdfeaa71c9a71e676a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.10.0.tgz","fileCount":100,"integrity":"sha512-rebo8+mU68FR6HXYYs0XpT9Z2Jwl21ZJaudaX8FEozcoJka9KNqC0YC40NLxt096VKkX9SvDjwPbT1gD/lbygg==","signatures":[{"sig":"MEQCIEWuJ/8sJkI9yZUgXTpXSinwtL64GEb+0jFPAd8oQp/WAiBLgV3GQCV1/kB77Io0TyOav25QjqlzrHvDitLf7A/zUA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":636143,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgTK9MCRA9TVsSAnZWagAAmskP/3BHNc7e0ZWP0Ebp3Sim\niQAIhBjVSCMM2rS2mGeoWT9igN9ggBCaxnZ2yMaBktk4qDg30MAK5vCydMdA\n5QIhcV7853ubwfGRrqM+GuEPNn3E3F/gWmt7dFPI9NF7/JUaC+9fG+ya8Lvn\n+lIp39L4fdksX9cffzcgWibS0E5qG5HbISfvII0MOGiy2G5Jp4LTFu6YOXdu\nJhoMg45sywjflD5nMU27SEnypQLwS47S+TpPbGrGcbg+qNkSo0XqvNRt9eio\nFK3Kn4QVg0THHzQIHoWdJ0pwe1X+0wPGYjV7VNEGxj92QG0epExuwXJGf38U\nYS7Ug4C/s0wmG+vrtMvXx9p5lTUbZDY4GpzTWrAPfL/uifpYxjSQ5qi328GE\nLF7gb1dNfgK08QZTLZMK6WaFs7WlsPavqkIGthifVV/KIuLxSs2onXnlgRND\nXzGg/BsdzpSVRkEIfRUfbcummHldHwqsSoW/RnaBg3heTTkJ4MJxTn67pIVB\nrjOwcnMn/uOvrMwL68pa6Y9NtL4MuF0nNJ6We/iXbZ5LwG6KSvSsalMsJQ1K\nQjwEqsa4IQq+/nwOUXQy3EZEnx+QF31zQPUhhfJYgkVi1cs0CinQ3WAkbQaw\nRiG2OEih4BbKnxQon0HitT9e2G6quBBJVTdMZMBE0gaTjTIRkPPUw9edQeEt\ngr5n\r\n=wA7e\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"285784333542c9f50cad33bbee6db5f0357cd22a","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.71","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.63","@balena/jellyfish-logger":"^1.0.48","@balena/jellyfish-metrics":"^0.1.116","@balena/jellyfish-environment":"^2.4.25"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.111","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.10.0_1615638347202_0.5360744913389872","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.11.0-5508-pre-build-versions-7477a2ce1ece38319bc4986612699b0a9c22f59e":{"name":"@balena/jellyfish-core","version":"2.11.0-5508-pre-build-versions-7477a2ce1ece38319bc4986612699b0a9c22f59e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.11.0-5508-pre-build-versions-7477a2ce1ece38319bc4986612699b0a9c22f59e","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b15d61554cfea25681c3972bff29043269d2e600","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.11.0-5508-pre-build-versions-7477a2ce1ece38319bc4986612699b0a9c22f59e.tgz","fileCount":100,"integrity":"sha512-GnKAtZidYUzi96hj9WpJ+brbuGaxsVpIcHEMo+sez2Gd7OuZZcB28LArErAgZLqvT+6uvOhwB2soFg65Za2nSg==","signatures":[{"sig":"MEUCIQDOr1Iwcvmn7BaDNB9fkR6JGvMTdqOgSsP9g33V3LDQjgIgFxD0ErIaS89MMTs5hz8L6eFhzIsUxUnlPsL+Tyo+IsQ=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":648918,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgTrjPCRA9TVsSAnZWagAAywMP/iM2VIuWJecEMPt7OPUH\nkvUyyh38UrKx9a6hupy2DyWwbpyJggny4ElEqXCyc7BhQMnMceuuiyK8xZcZ\nLHB5DL6cUrUiztlivY/Akev2FJNgMQyr5KXT4EsjzKIzWKTmkDC8c0LRGpke\nlrN34Mq8BRe6snmtPgvKRio+jYvbz43Z8u7SCQBqKzh3b+/2RBbHX8GVBGZs\nVSir466QDTbotjAgDwVgpw7nd7IHepb2xRusTxk9Z5FdtKZZgEdcEZxf8hGU\nni7NdxXRBDVrRgWfVaE7qX1Vj6Cea7hOzI9Fv70PCcM41bpatjODcxOE+QDJ\nrSA0zXAjNJBmVdnzWRZaTkm0KHPaMUDeao4qmjxzX8uCEzdBWC0h0I/9zrKv\nERp5D8qk0GeF+qDYoF0hqm1hkv1pxn7zI+waX4WBPcK+f66ox62i0St+7qFS\ntnCD61/Wzg7AXW7d/JX1yO46WrXJd/rJRzVkS7iinaOqiFk1vQG/CfteEf7X\n/FUbIP1TYpLPBSASHSEu/pgoATKR2HbW/LoKLMbLXttRepnldMT9em+3MMR/\nqpCdQtQfL6eUuN2ntDaxc8DbfWkp1BQ4/bn5yRITsVoa5RYS+DG1UPnba6Rg\nYdxcm1whtdkViUC6QfrfCVvFka2gZde9EPsmKEzVuek1kVeB4WxnR/ieqqfp\n7u2V\r\n=h0f1\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7477a2ce1ece38319bc4986612699b0a9c22f59e","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.71","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.63","@balena/jellyfish-logger":"^1.0.48","@balena/jellyfish-metrics":"^0.1.116","@balena/jellyfish-environment":"^2.4.25"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.111","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.11.0-5508-pre-build-versions-7477a2ce1ece38319bc4986612699b0a9c22f59e_1615771854453_0.6233221486468581","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.11.0-5508-pre-build-versions-43fc98f5db7d9d34c0dfb01e26b2f3eab2b31653":{"name":"@balena/jellyfish-core","version":"2.11.0-5508-pre-build-versions-43fc98f5db7d9d34c0dfb01e26b2f3eab2b31653","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.11.0-5508-pre-build-versions-43fc98f5db7d9d34c0dfb01e26b2f3eab2b31653","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"145916a2530e06503133ec9b79a011ef71f20e1d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.11.0-5508-pre-build-versions-43fc98f5db7d9d34c0dfb01e26b2f3eab2b31653.tgz","fileCount":100,"integrity":"sha512-3W3nx8Dt8J9HdUCODkYOdKzE8GZ0F8wXkrAe0rMe8cxxlQEvJA+xp6Y370IgJXgjx/Nf7ySi0Ks8alxH7ir0wg==","signatures":[{"sig":"MEQCIGXjh3qlf0dBVQRUcd+9uc6jxQkPoZZ7BhdSTOgyyknaAiB8kfDT/DHiKYo1BlV1ubc+uRVfMZF4S5o9lCSClDxF2w==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":649327,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgTrtMCRA9TVsSAnZWagAAs88P/A+gDNSqXjF8uDClIQ4v\nmtyfs+XaWeqbEb0tMrUlmOxR8/+C0FlH1ErK7bjksjrcSM0d75FOFuMShmMi\nTrKjcmRsB8PQcu5p3TR4k8JS6l6q91v8eHObAzJCG6tKmkT/wwYCGNb8dcW8\nu0SwQ9USftxU5brfa9tGY6K+xbfIQbYBjmsqXSP8E0z5Auq2kEE6zYZlLPNR\nCuzidOR474fZdqq8LkLoXN5VkQJL0xR8+KIuX1Bh49Lm/i7ojn38OsQyel3p\nAEsV/kz6MxTn0t8aywsrPDwQa9XNW3BLraPXl6U1uQrVRL7hbneNLXIBbk39\nHKiXX9Sp4A1lduGlx8cqMGuBiiIL/EBx+SxAzN8zb3+qsjcu9dP2SgymX0Iz\nlK97nfL3KguEs5Jlbov3bs/Ft87CJjSno+4fd/H79rcVULhUN0J7sGNti8k7\nde1FlB1SAd+UhM8Ab9a7gufwjXIoxvtXcVRNNwx1Kp5fpLMZrQEuaSbLbq9x\nIRewzd94bOFsGNr0P6FZgpVNkUsjlWXItLmBxNu2I3JcmhFB+pnE1oBMlD1k\naeV0MEo8GkvuJOOZAshrkRT9I0MbyAjsyLl8XQe7yQYdbHVOBnR3HSDVB1Y3\n3deAECr2mv4AG7TDUR7oz3P+UHZPyp5QRomgN4Gt3fqEJJm5ufGRYbANdDCk\nHE9H\r\n=3nzf\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"43fc98f5db7d9d34c0dfb01e26b2f3eab2b31653","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.71","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.63","@balena/jellyfish-logger":"^1.0.48","@balena/jellyfish-metrics":"^0.1.116","@balena/jellyfish-environment":"^2.4.25"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.111","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.11.0-5508-pre-build-versions-43fc98f5db7d9d34c0dfb01e26b2f3eab2b31653_1615772491475_0.5259086076958397","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.10.1-add-version-sort-test-f10bfccdcf38c0a8bfbabe9871cb19c69d2d0a27":{"name":"@balena/jellyfish-core","version":"2.10.1-add-version-sort-test-f10bfccdcf38c0a8bfbabe9871cb19c69d2d0a27","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.10.1-add-version-sort-test-f10bfccdcf38c0a8bfbabe9871cb19c69d2d0a27","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"702d7cb3dbe7a7c69a0aa88f95de44027210f02a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.10.1-add-version-sort-test-f10bfccdcf38c0a8bfbabe9871cb19c69d2d0a27.tgz","fileCount":100,"integrity":"sha512-Uu4gKBN2lofk5yFtm1z9Ul+ZKjHchlGPay/TvU0zNYkC7CFH7sYl2+pXGDk6rgnNR1BGOrued6bUp+mmpJ6K3Q==","signatures":[{"sig":"MEQCIGC+9FUhqwxaZKukyWTsuMr9VjNqKU2Lk7wS7/29EJESAiB92OaN7HWkwd4YACVKvp7pVw2ojNqfnIvvbSd8HiTylg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":637835,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgTsFtCRA9TVsSAnZWagAAzWIQAJugXTbngrK+qUIqO1Bx\nRAVtHGE9c2rUsu8S9gDuGkypGoObF5CMxcI0uVHXzT6K93T8iuyTgdH2CI3W\nUYbz9aHTk9PaTqj+xn5Lclzn8PvyILji2Q81cSdBN1kD2BVCOej1ayaJlgnK\nHpzyGErluJ/vFzXpEKyGZh70JHRbJNrZhT1nG7nPW851JVyNYvsFF8eTH7r1\nM8ALZrUrx6WCM0CQyFbxUaIzfyyQpAWNNWvsRHADFjRltQp6RnJmBXq181mR\nE7/k/QkHq0NLqxoZk5LLCbeQ3wnWdB949RU0caGamPdmDFQStqSbkxXPiDFO\nCT5dCrnhH89yTz7MVDyvf9xKQGq/4timiXvkeMn/47gCC87x5rleYwtgSfkC\ncXuZwgE7UO3+2c9qSl1j7khCkb8WckvhJxGD0Zm2e/rIroApbxknu4KGzDug\nD3Q0bG04ocK2mnQwUztGzhKnSpogwdbneU8s4uAoUs760C6ejdLo0npdu9/P\nt5HJbmMrqxlJsgy4JWEgA/4vDdkCYwLAUkMOEUuv8A+tiap8Yrziq+GXRRXw\nUaI/LT/Y1zrAl3voH1CJ58aVmoGsQQLN/j5MydaaB66zqNXH5hwljIbpPW5P\ny+VTi+KY/9bVUaW4/i8CMJhcIOzDL2eiQ8fL4ykz3B+uOnXORzEimnG1bGUi\npUmA\r\n=7n3c\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f10bfccdcf38c0a8bfbabe9871cb19c69d2d0a27","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.71","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.63","@balena/jellyfish-logger":"^1.0.48","@balena/jellyfish-metrics":"^0.1.116","@balena/jellyfish-environment":"^2.4.25"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.111","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.10.1-add-version-sort-test-f10bfccdcf38c0a8bfbabe9871cb19c69d2d0a27_1615774060548_0.9608300594972219","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.10.1":{"name":"@balena/jellyfish-core","version":"2.10.1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.10.1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2daa9c69349c22440ddbcd6a5cb30f90f9526e6a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.10.1.tgz","fileCount":100,"integrity":"sha512-Tr+WbvtDblLl2UUjboLa5d9Pg+4TjrOXBeZLU8mVylCvU/C6kZf5i1m9qlEB5oN/IBRJbJLgCp0rrefH6yifag==","signatures":[{"sig":"MEQCIAwRhbF63YTyUaCjvlQobDtqO7f6snrD1IFDj8YI3IcnAiAjucLu+AaHPMEeeMC2ckLyEyRGgueZmmdq3X1xvD0U1w==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":637772,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgTsKjCRA9TVsSAnZWagAAapQP/RQ4VZjrpuGd5BNuJue1\ntKzMq1zSnoJGuT2M1jhwcMNPtXb1cMgC67L2or5IXrvjcXEdnfe/nC2G9rI0\nxuNJtDUzsNaGFb6xzwXURFAzDRYzhautIOGSmdob+tC4+AOdMcNHfax98HTw\nQR0wWcSmpR1suunsIDI3uDjKkyJjKreWHskjCDt19FmwZgbJV1C5iIKYxavI\ntVy9QMnfTNDFIYuFoWFI3HUUiGoxQiXT9rBA6xri+81eSX4aAuzf+/isVA0C\nQTKR6oLknx3NARQ3V5EZJUuto9Y5bDJCfJiVoOst3paobk0rLT3PtDmqarYj\nPCBnjVySS3w7VLqfA1+/CPQR2OrNfISMTBeLuElqcssAazajYhFbJjiYnahZ\nBZytMNkfdWA/KW+FCgQWTmEsChpiDlubfZNYFr/FuTjJy8lpdQa5kmHSeos5\nRpqpG+vkYTTTZkWrW0/dF469Ds5oMu+oAcl4RRP4kiiDuA/iUL8BNrnC//wX\ngFS8FZsoe4uTGMBy+H+oZbWXvjLEcNkJNSx4MefqsxP8UdVxm1Ff+X1uTdDV\nm0fkPsTX77kcRd28Pvh8774CnwzC1igHBj6PK5mQ2ZbloM58DEkkwK7dChBX\nOnA46i577P9EOt/aG9MS3Rg5PdWYovlEUFQsLZIk0hPNLeLi1McXAX+ZsGyi\nmLY7\r\n=fFEO\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"55e657206551f3cf00bbfa071a4ac6bd9ad3ae1d","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.71","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.63","@balena/jellyfish-logger":"^1.0.48","@balena/jellyfish-metrics":"^0.1.116","@balena/jellyfish-environment":"^2.4.25"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.111","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.10.1_1615774370674_0.7664083936714359","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.11.0-5508-pre-build-versions-f0cbc2cebfb1f779795facbff7aa6656d92b50fd":{"name":"@balena/jellyfish-core","version":"2.11.0-5508-pre-build-versions-f0cbc2cebfb1f779795facbff7aa6656d92b50fd","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.11.0-5508-pre-build-versions-f0cbc2cebfb1f779795facbff7aa6656d92b50fd","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"7b2c6e946cc8b2fbdaafab59812c8579c5f896ce","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.11.0-5508-pre-build-versions-f0cbc2cebfb1f779795facbff7aa6656d92b50fd.tgz","fileCount":100,"integrity":"sha512-IAqYnM5vAJWKkRv0SBMbacUhlz+LaX+zysrQaHBwmuRCdOjgq8WEj/m9K5lffC7qUQaZyhUkMxpc/rwBZKmyeg==","signatures":[{"sig":"MEYCIQD++i6Uh/G6Olt+0FYMxaBZSHUWgK/whX70GV4KlhfJGgIhAMnzCM0wvza4a9VH+WFV8a0Toyza9S4rhLazdS6m40El","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":650869,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgTsNyCRA9TVsSAnZWagAAXtAP/jsNFgKNpZ5JApfzIAnC\n19yctqebeLL+BewogUnnIsu8dkNC3HH3tPb4f6VuOd7ZashrXmncFkrWtz8f\naGVjkRwRr42naLgRRaY/QLQRyf+s/LQdqsQMaIdG4UYYRjoQdYAua2yIKYoo\nDizC4b8IrZZ5K28+3KFGKRktRS4RfVKE2TPCtq+qUo1oNtYf4Gz6evTtP/kR\nISjkhCw1rRcz56uIxNjPpKOEpDY2K37S2DGtLnE6QowwT8RwH7TSkRW5vApW\nwbTdmzgj2XrBzc1wYOEg7W7AGj7krSyRZW63qH4QMNh+LXfZLphYFIsXTcXD\n5Z9nR5Q8fW7reaDDEAGuOOlYS7RFxBFpN7tFocZ8fxNE6kDEL5r6RUcXAi5B\nBb7cd1e6SyvRn32sN7ivK5YAJo0xyw1KPBitSTtppurSA0MJKC9KLW3zPt79\nCpx/SaAfxkJOKZvJ/LUfSvxMz/sHcKFyE0yZvwkV8yFWgGHDQg7L6R+R5MKQ\nsJ0OcQ9Do6omBlvxk+r3SqIzz9ws1mu1QrPpX7k0sfEx/64TXmyeZ98D6YzY\nt27DRRcU6qx0nU3JyFbLMvljLM/iinJy3ZkCcNDeseWZJsIe3TWpK1hStJIi\n63fNLQzIxzF1lQkEsuoce39q6GAthTTuYnmPppvELbJL3eoTLhnAyodk5Xcm\nBOFi\r\n=D4ir\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f0cbc2cebfb1f779795facbff7aa6656d92b50fd","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.71","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.63","@balena/jellyfish-logger":"^1.0.48","@balena/jellyfish-metrics":"^0.1.116","@balena/jellyfish-environment":"^2.4.25"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.111","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.11.0-5508-pre-build-versions-f0cbc2cebfb1f779795facbff7aa6656d92b50fd_1615774578277_0.511623865170082","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.11.0-5508-pre-build-versions-6c324acaed79703c1aad101938974060117232bd":{"name":"@balena/jellyfish-core","version":"2.11.0-5508-pre-build-versions-6c324acaed79703c1aad101938974060117232bd","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.11.0-5508-pre-build-versions-6c324acaed79703c1aad101938974060117232bd","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6e10b14564c27de2e053570288a6c56f98fe7c71","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.11.0-5508-pre-build-versions-6c324acaed79703c1aad101938974060117232bd.tgz","fileCount":100,"integrity":"sha512-iyVWgRFOCIZPYLX+qQJMTtNtCrY7Noo3A84i4xWtqortsgBqPjUw1t9I/AlfnR/3XsJ9sTpJAOR0nWACk2SEZQ==","signatures":[{"sig":"MEUCIA9QLaIgRuz5VWqBLJ3ZsteN7DVFu0eDRmoIO9rlzeI8AiEAxdfNIiWwxL2ejNERLJSEoVbQS9qlZ+oKLVxBoWp5q7c=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":652161,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgTsZjCRA9TVsSAnZWagAARMIQAILOljVLm28aBoJwvycY\nOjJK6qvpInRgDbTO3XQRbUjFKVRZjA/pdwBlUQKDi3bTY8NSkoY2BiLLqKbQ\n3lUDoxOADpwYe5CGqai4AtfMBz9SKEkdwP+32VLMjj5Rbk0bUlakncW4Z+24\n2lRyjjDsFhYwAQJb6TouuujSmEV0cwfSuumBtT8TmbrAGsa2KaIVWgXrYg3e\n9J2ewszSpq96FHTb6rvij5pqhK5E26aC30cLR4fTBYA3L5k9FKWNgQCvJ+fR\ny+2iRdbvn/RH+8nfd8y8PwL07dP9Ojt8BqK1rb/7d6QV2t/aQ/K5hHC0Pr+7\nVrKOiaTHqwSnjiMJlQJ8bKbKvdc2IY4lZyb+XBDmLxyM7Vt08mL31d/OY+kS\nIdT+DRb7qYlOCRTY/0T9WpqmJI3S/zjq4BDuAYMJByCcwsY0siBTQxV3hBKC\nL0jShu4DQUW9IqojQIVDx4H7lGXSEV8bp2ETD/HO+Cv2xXdGxCyLnfJYu3Er\n4JuEh8tw9u2QIt6sCobG+zUVtVKM5ylzZ1FK8j6X0Bg2Tcq2TmLMiqAVZx32\n6mZgYbudogXiZEvkdNlMMIQUXc/YS570dNH3UReZaExnRKA3KtbJhjbM/f7o\nGb1UQ3NGDhcE/uPmUWG63Ln94wEYxSf7l/jc2aQMEatAqH5/tNt17tHdlGa7\nYOVN\r\n=5m7A\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6c324acaed79703c1aad101938974060117232bd","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.71","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.63","@balena/jellyfish-logger":"^1.0.48","@balena/jellyfish-metrics":"^0.1.116","@balena/jellyfish-environment":"^2.4.25"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.111","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.11.0-5508-pre-build-versions-6c324acaed79703c1aad101938974060117232bd_1615775331039_0.1507820773944788","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.10.2-renovate-non-major-8e617bf24b2ac76794cf56c701e0db6c22d5b0ff":{"name":"@balena/jellyfish-core","version":"2.10.2-renovate-non-major-8e617bf24b2ac76794cf56c701e0db6c22d5b0ff","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.10.2-renovate-non-major-8e617bf24b2ac76794cf56c701e0db6c22d5b0ff","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d5fc247121776784a7faa7a07fd0b7ec8e42376c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.10.2-renovate-non-major-8e617bf24b2ac76794cf56c701e0db6c22d5b0ff.tgz","fileCount":100,"integrity":"sha512-7TaXSJIIU1k7U5we5pXiR1rffqOa55drpFHDZhB3uy4beV/v5rRvJKJAoyFF0OUNpFWMNxqSGJVCdirIgHHVgA==","signatures":[{"sig":"MEUCIEH69RMkFyS7mxbOAHwVGBof88R0ESpCi5Zslu/n3DxaAiEAv5F1kFazv2BvrN4dPQg2xeytx2uv8LGM+ypCEKNT4tY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":638181,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgT2JECRA9TVsSAnZWagAArZcQAI04cGIEyScMU2c39ns7\n13W/WJ/120fsiFAuxDDIucpP9/Bum13DftCg1z8WyRHHNtlBNQfGa4ZRsZpw\nQj4MBwzMr7nq4+veG7eV/NjTz5m7jvXWIlz6pycChSIy2eKOnmtZOnwb0sdU\nTxSyWCXX8FrsqLkMiJVkoPf9CSqjZ5UTRMTgHwV2e05309TXb3EZGvl+zX16\ntqwqI+5TVwysZ97wObk0qEnCmoGfgiQqDlmiRmu0dZ5ifbWw9g9bxQiWHsXE\nJ8OOd8nltTqBW/1qkXyjy7iciKAhuWSy1+3qPEMt/UCvF5F0P5ZSYsBZp3RF\nDiMjGCuYQ99muLZa9HwWoIOtYjT9OwwqZwZPVtYAoe1g9T3CoUdKRVIAYXeh\nNGFeHtwzwi9Y+PBXTTegkjJ3dCA3RNNw8iOP+uH8WDDnMGdA0IE4JJYf6KQF\nnzTbyk+BgZxf3YE5laOQeQ/gIxfgmFFD+puIyYeW6rrkwdPaHXROzJhcGQUU\nAkBVfbNGqyHdl71zxO1i9Ow8aFkA/2X7mNq3pt38b9RCN6zUlrCyCUCeTjGR\nUGcbI+s4Pf1kk4ZUO3DwJV68NdSkQ1Mcnh1HKo+7KVyssearntD1xMenPWIw\nLt+X7oX4BNVSWOe6NHNMVUUsZ0Vi9wItkDDtRDoiRcJXF/vVZ4gwkr5QWJaX\nKnPB\r\n=wCj8\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8e617bf24b2ac76794cf56c701e0db6c22d5b0ff","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.71","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.63","@balena/jellyfish-logger":"^1.0.49","@balena/jellyfish-metrics":"^0.1.117","@balena/jellyfish-environment":"^2.4.25"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.112","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.10.2-renovate-non-major-8e617bf24b2ac76794cf56c701e0db6c22d5b0ff_1615815236175_0.924159440464086","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.10.2-renovate-non-major-4fa134862af876f977cb773213f7cc1f8b3f67e4":{"name":"@balena/jellyfish-core","version":"2.10.2-renovate-non-major-4fa134862af876f977cb773213f7cc1f8b3f67e4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.10.2-renovate-non-major-4fa134862af876f977cb773213f7cc1f8b3f67e4","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f74f9a77bbbc72bef2c25f2300307cb0bf93266a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.10.2-renovate-non-major-4fa134862af876f977cb773213f7cc1f8b3f67e4.tgz","fileCount":100,"integrity":"sha512-CfxqhEcTTy9r/dGeErsALifod3rz1F79ZC8tklfdBGH43Z6Ml1X4LCwOPRmT1L/YWHhEA5lqpbHuVFcCwJc5yQ==","signatures":[{"sig":"MEQCIDPVa1cejkWaIUAFkNekWVaSET7KpbbHNYOcyjFUDiRBAiBYLayDJ/0r4m7BWkKhi4x5GHNWF2J30Av2eIB6oNsjWg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":638181,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgT2OkCRA9TVsSAnZWagAAxn8QAKF637m3FO0cglS3Loaj\nFE+vzKGJTpPcgdD1E886oIzgwCGgaK2eq4Du2C7xLA/hP9Q1HhZg0cv8Q9FP\nnUnu1Wyf9tAsT6cflvPyMwJkdyIj/rd/Bhk1ZK5d9ZH/YZBvFtl8LFDSmcrS\ntp/k+fBIejxzg9yBa98ToCbS+rQOuakzxtJ6Igz1cQ3UsCHY8O+Q0UCLd+yf\nYKGuSFSbo6oYujJYLS/b8CtLvI0M13+70sI2ovy0FDolbAAZA3gva2zGejNi\n1EDoDejYBiYA/iuPZoZWI7NlktaqFOHW8KSul5bJfkFRsvs9Ud3tDElH1ikO\nGkWHS19wODP5yuVBIuNKd9X+NaeVX3pUbcmgrrPMZlaD03o97onoT6pYv9qb\nye/zODaQTA7JDamFXoMa3iABXHnAx2pfbGWutN5XWix2KquXGi8yRagnl/Zi\n80/wj1ABDsa+veOhdYinazVNB9vsWdaBOnYoxEybCBSPXJeJLQGwsUEfuyxV\n3yRtSQVKTHgz4pzX+XJlmjv8eJk0chneeT2G+TIMDPfHmPUXny11ElmYyW82\nQFx1SZiRu/LDqXljbqmXzIS3T8gnhWcp7uzUNzhRCRZehqVbxrT3nSiQKz6P\nEWdpAyJjbh6m94AwaVXLrxJKppDQmKHEEW+oW4ci1/Nel92WpfLJ6qknkgx/\nvgv4\r\n=NELd\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4fa134862af876f977cb773213f7cc1f8b3f67e4","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.71","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.63","@balena/jellyfish-logger":"^1.0.49","@balena/jellyfish-metrics":"^0.1.117","@balena/jellyfish-environment":"^2.4.26"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.112","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.10.2-renovate-non-major-4fa134862af876f977cb773213f7cc1f8b3f67e4_1615815588065_0.17621756432791136","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.10.2":{"name":"@balena/jellyfish-core","version":"2.10.2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.10.2","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"20b7c5d4e60d147f5415362668faa63cb7827064","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.10.2.tgz","fileCount":100,"integrity":"sha512-wR2NIX7mfRybaxginmfbPm23KCK8cFSQktCVW7h4JXnXl+TFKJtENfBtBdtjfbo8G3nnnfMeTL2w/TViNSmz8Q==","signatures":[{"sig":"MEYCIQC3GeNfExcZxYH8UFJZHqvp2bB3+PO9qZjC+08kvU2XOwIhAMy3U0qtfQJ6LSJ/F7TaTDhKMyk0SPZs2yZhTJeUdC80","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":638121,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgT2V5CRA9TVsSAnZWagAAiGMP/2ahXdirv2Oe290IUxRj\nvRPOs4n4+213RwJH8pjoFVaaoX2bBNc+FzfJkEZfDEQDfm4hprSNxKtj5sME\nI6qJor7yHc2iy9+cpf6FLOXeCF77pSR+qhXrq5wIbH2Ax7UzSN7VvOl50P2h\nxARZZbj2ltL+SNtIZwUmQ68uu8YQ7IQKxAHra6dVQsa7eIknnW53RHsmWWTN\nc9n8pLAsjQ6lOkpO5SJJ7zVfLZlBJUsFfHF/udlOT2yvfcre3VAFbtGbiPX+\nJinn5XqqNNX9EQq5Il0XFSStI9YgbuDjq8gZF6prNA6I/wxOgAC3HAdYcfmt\nrPHFd/AdimF45a4NUUM9cLPKqgMt4DjEbGjrRQKh1VNssKYO4EbhudtWASFS\nwVGm0Gv8VX51lYjtIOiZTKTvoHfyFP/E3bR1MODL++hBwGlQe6Eq4IfcbJbT\n6aoRylDrxjnlruysnx2pCycAq4OOKo2YzK3CR7CSKRQ7sgcNr1QPJONgGh7L\n2PeMB7gj5YFrcsClUn6sKBcFt1afgNDHiIozz3tj8RWK5FIlApnx1/8jAHwY\n/3sAuyojvSWyU7sBLtyOq2wD3ho4bPiQ28OmeutHOOs33ROadf9VzDvI6SBD\nhUY6w4vRrmyWt20SQWPjQyuN4ZkzDiP5BbvdyFhxK3c9mtZfpk17SbLCwFtC\nry2M\r\n=r2x4\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b3b97ae675ed6cd0afffd33bf1648cd025cad591","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.71","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.63","@balena/jellyfish-logger":"^1.0.49","@balena/jellyfish-metrics":"^0.1.117","@balena/jellyfish-environment":"^2.4.26"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.112","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.10.2_1615816057332_0.7318992091336605","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.10.3-renovate-non-major-183b3a05f986afb29bf80ce5428f5f60faf9aea1":{"name":"@balena/jellyfish-core","version":"2.10.3-renovate-non-major-183b3a05f986afb29bf80ce5428f5f60faf9aea1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.10.3-renovate-non-major-183b3a05f986afb29bf80ce5428f5f60faf9aea1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"02d0cffe35ded8ac2e420a4bada299f1e62f8fce","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.10.3-renovate-non-major-183b3a05f986afb29bf80ce5428f5f60faf9aea1.tgz","fileCount":100,"integrity":"sha512-yZUdp9Dd+bfSyeZH4GDcclmwQEzOFfg2cJnNF2W9FwHfgA8lM6sWTTkO+h+kGRF2Wo3vuX/UuvBTT4IZm9wcZQ==","signatures":[{"sig":"MEQCIEc5aWtGsZFOQIbcUBv5jS3d7u/jxsMq/RlDnkoUNf9zAiA1IGU6M7soc6q09FcdcieRoT0cZjjkFisYeVhT6nCN8Q==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":638530,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgT3+7CRA9TVsSAnZWagAAQS4QAJoLMRO7Eqddbl+3RIiP\nBkkyQprPv3lt/Cy0OKJP7d/UDPeMwt/H2MpV5wyBa45iFAhA0ZaYx/J8Q2jV\nYIih1n2xYPEJuKCS1aAyPneLl6P0Is9duBikGeNYx8DEy4Xf7IIrpYk2KMOt\n2DzcE0Wt3JHmN2lnw4RnSrks8jEn4WDrU+Wo2tDHw83DdUNl9AyHOGRTWNiS\n4du6NZmtSmQaQ7ctDrpHFYyGYkt8nF0zTUkx3CjXT+Rc3t4deV0j7XV4Ej3D\nt6uaGBfWp5L7M0mTj0udrpsV/Ui4xaQEEFxIUg2iBC77WgxM79nyfGv9y0db\nR/xLalw+ftBtZw/dmpFAoEz8tKkTnhx5zWu/lrxRCKsFDe1oAqFzl0K+nmXc\nEzmyrM1TGeo0JsrJILuPL168JERUAt+Ngy9HrnUJXswYKg2jHU7qNiwijrdR\nzhtQsipL9D4toC2HQ3+B855ypPBUTufJogfDslFyEVh8NQTnwxCpVhTa5SpW\nICRFoKe1qTasSz+Y5LbqLzPBVHVElC3sGP5LWjy9UrPDgGdE9s2LCOQYA4aB\nt61CCRlBy2ae3fnCCQQ8wIgilRXdnqDhANZ+yrL6vvkKHsumBOwNPJa89tx3\nTNiKMERqUNenbRXruRZEc5L7RKuYLO61M4XCPbE4iwAFYdMFBM7LxAMQxLiY\nRegi\r\n=rcED\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"183b3a05f986afb29bf80ce5428f5f60faf9aea1","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.71","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.63","@balena/jellyfish-logger":"^1.0.50","@balena/jellyfish-metrics":"^0.1.118","@balena/jellyfish-environment":"^2.4.26"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.112","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.10.3-renovate-non-major-183b3a05f986afb29bf80ce5428f5f60faf9aea1_1615822779202_0.8124167370003539","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.10.3-test-env-1bb68eafba6b7743976d98830410c210d9a4e9ae":{"name":"@balena/jellyfish-core","version":"2.10.3-test-env-1bb68eafba6b7743976d98830410c210d9a4e9ae","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.10.3-test-env-1bb68eafba6b7743976d98830410c210d9a4e9ae","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9cbbe5cda4e0df362695b11fd4e39a7a8da2b371","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.10.3-test-env-1bb68eafba6b7743976d98830410c210d9a4e9ae.tgz","fileCount":100,"integrity":"sha512-TEKmvdmqWI3KShIYqGvon9uK6Dm98VfGkIT2GtInlPQlI2gG2wpT8szOqE4++BLGKRslHqzn/t/DtlcXCTq2Zw==","signatures":[{"sig":"MEUCIEpRe78TejRK8MOEEVPvPnjQkpPYNJDrIm8t8hs5Xiq9AiEA9awlFSpLGoM//LG40LmnDtfmfDI44xz9/eRBlAWDfuQ=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":638695,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgUAdxCRA9TVsSAnZWagAACMcQAI2PERU2BbDGJpVNYehw\nfy9PPS8wsunnyh46vpiWuiu0QAAk79/hHB5dKq5QGQLjrtDFrAH+7ucToY7P\n3sHBiAMvJ9IbxhdGQ4SCUQbiS7QNtqnxBxdfeqSxNj8q2zh28OK0FmOPuLnx\nR3PfTzle8wEQV9aZd10sA86V9zFGFATDMBGgaCtiLewO81Sa0DH4GQ3wK0Wg\nA2zx+Gwr7KgkktjaRcCLCxgHuJwDzeKfOIJ2Cm3Gw7M4O1CypOokWBiqDLkV\nADSRuMk9lIkGoR+rE3jphZlVu+MZj3TMo4S7G9IuSA+UJhQyktbL4/UFYYOD\nl+mHmm55epD2tu5D77r/4oWn7xLW3ZPzt4mtffwRVkRK50VRzfcDWFMSR0Uv\n5P1e1VPfp0W/gcZmoDYq8slh08g8TPZLizax2dBoH4JhLa+YWeU7FndhT5ZV\n+dQWQMch1eSvrF//FEPAvhmeeWBZ9mh/tbhRLorkQ4kaoLEjWhPj3wcUcX+M\nF6P3KoyHRiZJakQVC4aWAhAJF8Dc+vVJ/WhvilULkUgohXkutPTVRSCxVEX0\n+84ABqsQyjTwDG4/piKrAYYKy5yy+Slt7YWwVztnE+a0ct0RGAwmNOI+kXAU\niZGNdvLLQ00uRz/yYJC+zmMTToG2ICMPgMFFoBlRzQHO4YZKQ2aUIYdr1RPb\n6f9p\r\n=WIc3\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"1bb68eafba6b7743976d98830410c210d9a4e9ae","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.71","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.63","@balena/jellyfish-logger":"^1.0.49","@balena/jellyfish-metrics":"^0.1.117","@balena/jellyfish-environment":"3.0.0-use-env-c22510a4bdb76bc44eb7fd425cbef8cb77012f82"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.112","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.10.3-test-env-1bb68eafba6b7743976d98830410c210d9a4e9ae_1615857520439_0.2833204854584057","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.11.0-use-jellyfishuserwidget-beb2de350e7f7cec95ad5a9fd3b70c98e577b0fe":{"name":"@balena/jellyfish-core","version":"2.11.0-use-jellyfishuserwidget-beb2de350e7f7cec95ad5a9fd3b70c98e577b0fe","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.11.0-use-jellyfishuserwidget-beb2de350e7f7cec95ad5a9fd3b70c98e577b0fe","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0355104305842928df58a1ca059f179f0cd93d96","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.11.0-use-jellyfishuserwidget-beb2de350e7f7cec95ad5a9fd3b70c98e577b0fe.tgz","fileCount":100,"integrity":"sha512-77Am/JvpaYc7uQgSg36xeu8rhH2ID9SrlMacyuiA8R3KrjDt6TdBXQEPl9gEuLpNDcS16pATP1zy1wX38OPb/A==","signatures":[{"sig":"MEUCICCpLq8aTBXkHGCLyIR3GBa+uayxf8Uk7QdcyWj9BZP3AiEAhxyflxlMd6RXhXWGD3DfZKVNg6u76zuB2F7H0rHL4YE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":638810,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgUAq0CRA9TVsSAnZWagAAYx4P/3ELLF5rPQVyawmiEG0Q\nEF0kqTCkOkl94yvJa5iYbm292qSsoXCv2+bOMk/XJvj6QayxtZ9q5pXTZYr5\nmLN5C4r/UF962SeNzGbb1peVxNxfoFcm/7JYVb2v5NZS70dpRjD30jVMkFUa\ndFtMi/ddWTRS/mM1ULIrs2c1wMFF8/ozqrhaCywOnTa3Z60H+8oVvwoqLyfB\ng4BRQkfj2RfAvbTDzbPyH/EPWVbJ2VUnUKc76UPGsGYe0/lPJw0rRwK0HStZ\nZ3t2LmOFqaNPHhm+6BzrWmM37lUC/t8m6mKE4dxHqykxi/iXPUkEM5HquqLw\nU/yNH5l7apSs/cLKpbplXGbM/ljVLyqyamMXEaZ2OW4O/TDdvUOWGlVFVTBV\nxaTaMAvaaKSaOdJkFf+uOfa4WNvM4U+ouBdyqcsbA9OroRAXWtcXNp8zVPDm\nYrI6olrmnqu9E3cYvpdTVCGwJgSYgwYKXC7Uk+XeTx+1jX0m6k3z5I/NYFWB\njJhNlg8FiR81gXxZbnYqYeJP3k2qIwMLhMSa6R8AG12lijOInQG5BSHtzZ2v\nRtrX/mu1mJ6DYvMW540YVAVQLHeFIE3lIHdyPVLCWq6o30WinzjoqRIcpiPa\nPPIDmw9KH8FTuoIlIlxvBPNWgStofTQEm18pJOw3tUabip8UDd357svau2aD\ni/RF\r\n=Walj\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"beb2de350e7f7cec95ad5a9fd3b70c98e577b0fe","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.71","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.63","@balena/jellyfish-logger":"^1.0.49","@balena/jellyfish-metrics":"^0.1.117","@balena/jellyfish-environment":"^2.4.26"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.112","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.11.0-use-jellyfishuserwidget-beb2de350e7f7cec95ad5a9fd3b70c98e577b0fe_1615858356071_0.6619731232092734","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.11.0":{"name":"@balena/jellyfish-core","version":"2.11.0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.11.0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"66e546bf90a7a5de20bbe9e54f5fa422ebf605bd","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.11.0.tgz","fileCount":100,"integrity":"sha512-3r5euwk3QRuTHJmEoGHE70hvgXX3i2uxFzaMMtFeL4lSv7FSPr6Xpp2HugH35e9Bs0+YyPIkp+3eJydIKZiudA==","signatures":[{"sig":"MEUCIQCh9PdDCcMAkE2syOd5xeRh+uOAvrHtjoRnQRrGJCn5iQIgPTUh3c9MaQwJF6scyMjnXVYbXxkA3cuw/c3Ei29NN4E=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":638745,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgUAuFCRA9TVsSAnZWagAAPrsP/iQoQ7RhLeDfKuRfO819\nhUwwZkHgtflEDtgzvprmscsBG2ilKjb+SvCiNrgpt/5fG514Rho+97x4C4UW\n+wNmsnIxMU9X5XYBdEhNg5t9LHWrMg52zaaQ9R+ilKTmz905K9IM9VeTvlaE\n+ZN+iLhEqjsBsGefGM+VvA+SV/cfLcuL7nXpMSER3G3evhi9Y1J7c0Q0INbN\nD9K17CiTxxlo81t9Kgf1saPvk1L1br/QbhjqxcUGV/hV/FRwBN3WJ9f1OzWx\nmO5FNkATxawYdwE/K+lMqxH2pbiIezmq+2EvhDkZYM4TeUqCqpCs2NpMJc7y\nQ2B/SgDsId1Yq5Mq470Xd1wl7UC6JQzq/wNV6nCH11VYooc8hZ5bNE1+j94t\noIOvMhNy/gRm8sKPwVPsIRNhG7uyYzhNKudi2JCWRFJMgbX8qdHg7LSBwYc1\nUdG4oYeV9BMKxEb7xdWJDCq55BLex6WGS2lWp9ye5lD6/qgqTNo3AAX/Cj2V\nDTgSZjtrg/Z3qSMiYWXcC8Oicy2Q6ln3//NCGa3o99/dIqV/Tdk4BqsrrcpS\ngoSlP7vvbYsmAW8QPPq3ZzWsBOX5R7ZmQuAfyAFXAz4WStPay0hvwGusL2zT\n3F3JUebxUj9afD+nyn/VRnbI2s4Rny+BWvBaKB8Nu60zBoPawTRg4rjZG3gt\nDspN\r\n=UYvS\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"732699efd1fdde8e1d50a80f3c76c3a7769b8feb","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.71","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.63","@balena/jellyfish-logger":"^1.0.49","@balena/jellyfish-metrics":"^0.1.117","@balena/jellyfish-environment":"^2.4.26"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.112","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.11.0_1615858564787_0.8676905683956082","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.11.1-renovate-non-major-6ef2dffab4c246c2de42adc2a6cdbaca8338e93a":{"name":"@balena/jellyfish-core","version":"2.11.1-renovate-non-major-6ef2dffab4c246c2de42adc2a6cdbaca8338e93a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.11.1-renovate-non-major-6ef2dffab4c246c2de42adc2a6cdbaca8338e93a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ac454274a51ee4c52c7c31f0a0b72aa459530c82","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.11.1-renovate-non-major-6ef2dffab4c246c2de42adc2a6cdbaca8338e93a.tgz","fileCount":100,"integrity":"sha512-YoS/VL9Ja4tirs4Vb1xoGzYsodQqBQrgBYygFB0lOtC/RBtsT4+BJQcsDeW6ueJYt12pZOCeDh+Cyzxa9lVLCQ==","signatures":[{"sig":"MEQCIFNeoFQItlIbDLK2FZTw32ltK9cdSHvMPijd2I9YIFM3AiBcW+CXw/aUzngdsrlW/d2v5+Qt9djTTrYvVeMcBV0JXg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":639154,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgUAz2CRA9TVsSAnZWagAAkwgP/3Dq8/5UiPf/36z3XEsw\nHJttlGZYLNRIRe6z4CjKgdNZGCOHNNizWcUxFUrTcpB7Z2x5oDD6k1n3FISQ\navSJw2iCs5myQ15M2q4wBXmaQPykkk5NOEpyPWqsXp1QtkW1/D32nNNxhaqw\n+5s3SEZ2xM25jYDDnd2vMIg+KNqFiz/pdPm6Jx6U8qtpGSG9VW+RlKiHf3PS\noZ9ocAbZbyPbKWrs/WCeYM4i2AJi86LiaVxRSvr9dWT/UZaqyH1XVnaFW1vW\nd8lLIWH6n5PIT4oCuVvU0KFbgHJCD+dGcdHVCDYSoZ1phU9ubwGgAo95SVSb\nsXKgbe+2Jm/mzK0OC/vKMkzVg1lk22dnvZrJ7hG9xSO4G4iOT5g9E+Zhkc4W\nBQqV1SBvJlYVeOMoyZ62kNRUU3v9apQkkWR73Rjdw8m9meEPoFn3gQ2NGOCx\ntXSYSYAKoaLKyF5QTHfnQbdwVoqxwh9EO6ywWDdbikZo5sbGLAfteDxt6TvI\ns0prONTi/jbsXBs0bjOJfvLpGmme7PaERSN/bTwT2haIiF3R7oBSt5ki4yzm\n8AMNhMxGuxNMR0us+IoqtSRfTO04Gx/ZHKe5FnKELRrU3uR7FHn8Yg8p7cQO\ndA4Ij8HmgDHPK/ON7th1yXFzGspkv/mhAmIn+L+CZQGPIStwHGkzSS5dBr3Z\nrl7O\r\n=qztd\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6ef2dffab4c246c2de42adc2a6cdbaca8338e93a","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.71","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.63","@balena/jellyfish-logger":"^1.0.50","@balena/jellyfish-metrics":"^0.1.118","@balena/jellyfish-environment":"^2.4.26"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.112","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.11.1-renovate-non-major-6ef2dffab4c246c2de42adc2a6cdbaca8338e93a_1615858933772_0.27368775973822834","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.11.1":{"name":"@balena/jellyfish-core","version":"2.11.1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.11.1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c84fe46fa0527197c2b17f6ff536c88291959f26","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.11.1.tgz","fileCount":100,"integrity":"sha512-Xf9JwSumDvX2SU2QGO4fHuioG0TJj7h19JaEHrr9+seu0/l+H18MfMZCySnzsa66ORv7mgEiQ2EGF0ctK7Fnlg==","signatures":[{"sig":"MEUCIE7Ydk6Qx/115s+7iZOtArK6rkojKGHc+d97biuoOP8hAiEA4UroUzkt7kUjHrIkKZV2By4K3Ll9ns8yeqyLyTyEAPg=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":639094,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgUA58CRA9TVsSAnZWagAAM1EP/1HyEPIn0OVEw7mTF8/H\nRFALSbiCqZknoN5W8BNGu8QDqwNJniA8o+4X12U0fzb7JnyAveCDl8yYcPwq\naEI0iR5IhEknh8h0murcNnU0wOtbUZWAAPdNtCcWB8mBbibyXRJG8xHlyvkg\n7neJ2BGkYJnZWbaYtwtxC3BH0o9an/aGPoV806sxBPCkQyfmpO8UG6IBw0Yq\nZppDDnMqEltycdfGhCSe4ZcWv9yMh5u3C+Q1htlnGzZxSsZllFlUSLn3nVUa\njO+ouP2C1aRtkTQbu+o9xQf6Frxm2m0CNjgeUnu7ziEuZMX+1RdEKFD5bO2X\nAQWKDob5L+ibyWl0HkH9nhqsRNv6m5VyBnhesjlPaEqb0jqjSi8qJSlyJBx6\nfv/OH0Qbqnm8QgjAum1XXMufIQsHiEg1FRFABPoKq4dGtVjJRHOl3xXoXqKW\nX+Y7HkvrS5cdDH/8cY6jIX9Xp3XspvIPLoMAaWv8EL/v34iPsI1++jYhi0q9\ng6n9y7fvIc5yzZjHBC7PeyJzCHVKZLPQ+3DcOwkKuHWiopd9Rmb7U4+Y7h0f\n3Zou3z39UG/Gffn/CdLVyFeliq/hQES+hKDZ4gDV1ruGWpXN7yhLSOutakiI\n7HjNsv2xMSzPryp79FIAf+RAaP1CtmMtJUp6L4CqZl0SSPDy4Oy6W0BCFxL0\nVvbI\r\n=GEu5\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f090a2700dfda9b35f15289c1189c10902bc57c2","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.71","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.63","@balena/jellyfish-logger":"^1.0.50","@balena/jellyfish-metrics":"^0.1.118","@balena/jellyfish-environment":"^2.4.26"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.112","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.11.1_1615859324029_0.9067333499252412","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.0-5508-pre-build-versions-df6d57c7711b72b16d1785ebdc72b4d9ed92d633":{"name":"@balena/jellyfish-core","version":"2.12.0-5508-pre-build-versions-df6d57c7711b72b16d1785ebdc72b4d9ed92d633","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.0-5508-pre-build-versions-df6d57c7711b72b16d1785ebdc72b4d9ed92d633","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4e12f07ddbebfa85bf4257c234b04c2e7d510a68","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.0-5508-pre-build-versions-df6d57c7711b72b16d1785ebdc72b4d9ed92d633.tgz","fileCount":100,"integrity":"sha512-MKUp9hOs+V1FNImGb/uwnrtI+5sQrCfWzIffi4ryB8Hpal4L4vXfTBaf9X5lxorUGzumpyNn2PeyU7aVQzjBzA==","signatures":[{"sig":"MEUCIQD4b7m4o3Ih0t3VLa38WxHARc4YU/aJn0mJniGBLLb3HgIgU2NCbx04nIOgzvpFEA1Myk7JR1fUX2u8tFvf5xfXips=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":653570,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgUBMhCRA9TVsSAnZWagAA7LwP/0qLkZFDJPlA0U5fLzlL\nuTVuqvqPnGkBwGPk96I+vcsp0ZNbeAPrZ9v792O4ElDAb+aoEPiqUKfzP83Y\nHytkUKVgymYPaONZGcVq+LQOBQaBIyzlbRqQRkSJq2plba6qGbPrtw9yPTMf\nrhpRFszEgLS8CsvahioKKZw1Hdj+RBuBG7Mh7ec+dx27RSTHYdh/YfrxO9M4\njAg1uIREFm716Pdv4ONFp97LIb74Y5rWwmXb4DfX5td2viVSSx7uinShEcEH\nXJbXyOILXPkvZuK2rZsDYtyS9fN5xGThbvaIBXcxoefzOq7+ysa5pwDPfBT3\nBP7V/N55Fg12ykW0pr9spXL4MQikHfh0sKB1IwqxFAs3zZbqiPzCQs22a8GV\nMgFWIiznvvwRjYIRc/2M3OuJruQCC/TdPzAhPOVuO5BafS+UGcRzO4p2e+C+\n8hcuVkBbwJBx4ggVYUVPwDoPXQdLeN+ewezWdmxYgWaoFC/XTEjS6ELJwkgU\nQojqKHVu07XFjwmdUP8Wa3RckOLkwckrhhaefqqWvIex+4OBkfVAlqevGqfT\nBA4jHygEAckAgIrszoJI3G4pf1QRHyPU3drOdQUREA5JahNglc2H3g+WcmCS\nfCHu51VsD6Olq++uMulyFLx7Cy8g/Y9GcH6HBJoOsF/xFfG0WXvkDY+2I6Nm\n8ubm\r\n=C1iH\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"df6d57c7711b72b16d1785ebdc72b4d9ed92d633","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.71","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.63","@balena/jellyfish-logger":"^1.0.50","@balena/jellyfish-metrics":"^0.1.118","@balena/jellyfish-environment":"^2.4.26"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.112","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.0-5508-pre-build-versions-df6d57c7711b72b16d1785ebdc72b4d9ed92d633_1615860513265_0.8530771097727021","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.0-5508-pre-build-versions-d20a679c13f93e1649ec37622eb547809207d290":{"name":"@balena/jellyfish-core","version":"2.12.0-5508-pre-build-versions-d20a679c13f93e1649ec37622eb547809207d290","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.0-5508-pre-build-versions-d20a679c13f93e1649ec37622eb547809207d290","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"138eba4228634ae9bc73c7029a49afa9047925b1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.0-5508-pre-build-versions-d20a679c13f93e1649ec37622eb547809207d290.tgz","fileCount":100,"integrity":"sha512-h1I/ceCeTel6ZkKPnAUL0skA64g6hMfPDoDZ6JXkKW3JFcQgBN28XaLxLGLsDMTJni4vljxYARXrzBz0JKdHWw==","signatures":[{"sig":"MEUCIF6LuBKyejRo+PJ/CCJyPXohCr7gjGkOqFXNYYIjkBZnAiEA3k9b6RCNO0O2irsf0eNsOTIjEWqZ6rJJOyR1fuX2IuU=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":658501,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgUCbkCRA9TVsSAnZWagAAbqoP/2waYs0iPwVBWjWJGe4E\nccLXwFdrPCXbrulM8AT1Hf9MLJ9BLfZnStVOule+GErv6kTrvzO3vP/wceRy\nxo4z2p1+mLxKDEGUz73rpKu4cVtfxH4ZVY0AKIzCnaO3pNmEtNQrC7trdLbZ\nUvErBe5g4b/aEa4sDDr07JzZAk5VV7u6uO6HfjvQGFHlA3aWAN/90Q0af+uB\nDARri7Jra3WiC5B1YkquLFqv5yR8VT1Pav3mI3FyKqAchwzISxxGtvGvXO7P\nAH5VM6Grpq+30SVeSaoNM8GEm8gTmJyys/Gvvp4HxjyDjHUaaJt3Mu+80YVN\n+pgNt8Z1wUb9McC/qq8BrLOjjhDgbD7Z+xWLltX8/ezUk+f5SrpRlMJsHNPU\nDcgxYETXylkRKV3usmMNnrtEz0ziuJZcIIKZwR/nrk/xkrD0HvTV9LDxXVpB\no8UHRE+mC6iYgx5MkfQZs0ILWRJugFa+CIAK4wUi3V4htUSLXEeXY2TJDJ52\nnVnPCP3BWXBun+BPzwLiM3OCeShI6YdGXb1CBm9oV6i9Tw2PjZ9gNZgVRJbN\n5yPDo3Fzwwpm2iFDcRBYYQlvDhB5ozni3+p/vCCH4WTeitJRztFU4FA9IVNC\nLt0VSlOnaFTvp6wOnhtXh4dbLvQ21qgvPRp+YlJVNGkFXwnS2B82Z64E+y/b\nczjH\r\n=gZuO\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d20a679c13f93e1649ec37622eb547809207d290","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.71","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.63","@balena/jellyfish-logger":"^1.0.50","@balena/jellyfish-metrics":"^0.1.118","@balena/jellyfish-environment":"^2.4.26"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.112","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.0-5508-pre-build-versions-d20a679c13f93e1649ec37622eb547809207d290_1615865571192_0.4301849776209403","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.0-5508-pre-build-versions-896cfba43ceb6c74214a53b11d6c0e3cb6c7a12f":{"name":"@balena/jellyfish-core","version":"2.12.0-5508-pre-build-versions-896cfba43ceb6c74214a53b11d6c0e3cb6c7a12f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.0-5508-pre-build-versions-896cfba43ceb6c74214a53b11d6c0e3cb6c7a12f","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d1cb4639532022d4e227b1ff867955e886ee2886","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.0-5508-pre-build-versions-896cfba43ceb6c74214a53b11d6c0e3cb6c7a12f.tgz","fileCount":100,"integrity":"sha512-Wqw1uZa8ijxcuEG6XtZpfJ/A4T4zEfwwUQ9KZ6zCy+u6RiWc+iWC4wbGvbOnMC04WGlLOdiWb4Xo0qt55g7lNg==","signatures":[{"sig":"MEYCIQCdjh/Inwv1iYexeACK3jtUKSppl0bYZqawMvZmNaEVgAIhAPW7ucx/ySpN5QLu0hdYM8rJjPjqoPpn8qvo5y4aE/uN","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":658501,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgUFZ1CRA9TVsSAnZWagAAyoUQAIzAXRIfyaD5frSSAOdP\nlWZY/IHHmLjSy2M9EI9IgYSFZo+wCz1LviLlFDJGDVNn8V7yKjyBELTh56pU\n96+agfO5ouOMn/+eXT4mOY1fM2qB31O+j7ciGiys7wTzknrprVMywF3W+D+C\n1yHWgTb4nOe0x/6zkDPTv3CvdbJMMCFq+j7fTP/aVNq+w4a23AvCNv2WHKdV\naGtolmOJy9daDSFj0TwnxYHNKkszyF5eeR92j+6XUbQWBDErf/m+373B6eqd\nDDSOT4EX+HjDc8xo77LK6AsqfIhIhq3Qw9LbYNQF+xi3KfAE7LYwj4DOD4gu\nSaWedVesEzEp5kEiaJfLzUxbOW88ycVn2s0sCPPEGu3d+AZfBOEqndxcGQdI\nSztXcRR9w+YogRuNFwFUKwavpfFsWicV5WOwflehcXFJ+ADBZcve+gabVMoi\ni5d0zxtsrq4M7uGPEK9us9DMH5K/lJToV6hrXYr3TdUbrtY/1KKtyTdHmhCS\naDpha1WcdLKrPmp2fbzS1buauReR1uZzLXzTCvlnP7P2BRnFEF+U4xwZqMPx\nCjmnBCuTyaRkU+hbQP2n3Yj+/OWDEmm5tXBbqJRllSDyPmpKhs8Xn8tJrhMh\nuOvhKjpEPB4R53Z2qYLPOFuUY14ehc7dvpmyBIEJKMNTpMyPkxI5MkOjFNyR\n/EeN\r\n=cyMr\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"896cfba43ceb6c74214a53b11d6c0e3cb6c7a12f","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.71","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.63","@balena/jellyfish-logger":"^1.0.50","@balena/jellyfish-metrics":"^0.1.118","@balena/jellyfish-environment":"^2.4.26"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.112","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.0-5508-pre-build-versions-896cfba43ceb6c74214a53b11d6c0e3cb6c7a12f_1615877749140_0.29626604714037996","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.11.2-test-env-c02d1447f3c4045d6433f16111c67933ca2928ef":{"name":"@balena/jellyfish-core","version":"2.11.2-test-env-c02d1447f3c4045d6433f16111c67933ca2928ef","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.11.2-test-env-c02d1447f3c4045d6433f16111c67933ca2928ef","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b9b2f84bdc61e080aebb1a43fe7d15228ec51cea","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.11.2-test-env-c02d1447f3c4045d6433f16111c67933ca2928ef.tgz","fileCount":100,"integrity":"sha512-JuIJPwtS77g92Bm4XpRwe5pNQxi9kiV6klqT50bFhuBxcHNiv/zUfzdo5VP60sW0RDT4Cv1LEKUA3Po3DRcbzA==","signatures":[{"sig":"MEQCICvV17/RfkKdc0SLC3RpVZO3CflhT4MaCSwFxIA91eenAiBZtP4VXiY9RMIMyvHK9Xzd4JrGOhrRQx4QGHLrBTP7oQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":639668,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgUHqkCRA9TVsSAnZWagAAR6wP/1NjoYp3hmcayIJt1Etc\nv5rqtwJzpbiXE4nHTZGo9w4T6LGErJJlASgX0uxyDdwi9bnmXcko0KjG8ZpX\nuBHOBhaCxf62HTTfdcvoNlhAJGlVcSU0Qt0+cEm4W5YouiOx83Zin1hWYXQK\n1xQwMCVmalfhX43V5bdCGEV5k1LxAum3+ww0vD9JVDIY3YCpM9IQb8qqo+8z\nCOJh1CYcqG6EKWO3B3+tBxjpljEfPgTnxtl/ROJvQ2bKWcx3cIGRTcMxPMng\nliLpixL1BXKUXR8PY+bWnfNvjiw362G1KQmkWrYmM7of+YWKDdhxQcH5MXTv\ncFQ9F/JgBf4J3ZeXh8O6E8yyaT8nNgLs1pp7nzkwT3od5LLemDJKBdCqa3cZ\nZMx2e1DgslGd90TkmxEDQjTpB5wMCcL65TDcZzLyqyAotbGANkv7MoJwMm/w\n256naHcBZpiWqsU7tvEPjiP/RbS0SiTwIypUK8Zy8tT0NETH8+Cl5zfgSQGW\nwg6qAsBVEyMFy3SUUI7zrv8SQFpM+sIf4Ee/xTvXso3J3IueCtDcW+OCjdpI\nP/6Fa/LYgwTDVn0URVOTxfG+Ev6m7MQxl8l9Op9nLdzo2y5XoCYT3UQLvL//\ncSI/wbLhalf7KjNu5ALI2smDzu6e7Ns0hvXGZ+dk7hsMCvT8YoQ2og9FYEET\nq3Rc\r\n=A1gc\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c02d1447f3c4045d6433f16111c67933ca2928ef","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.71","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.63","@balena/jellyfish-logger":"^1.0.50","@balena/jellyfish-metrics":"^0.1.118","@balena/jellyfish-environment":"3.0.0-use-env-35c18fce3fc89bae2469dfd8e3ea7bf24d00a2ba"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.112","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.11.2-test-env-c02d1447f3c4045d6433f16111c67933ca2928ef_1615887011785_0.20966816108428987","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.0-5508-pre-build-versions-700434fcd514ae46d516c7cee002dd5107b90f1e":{"name":"@balena/jellyfish-core","version":"2.12.0-5508-pre-build-versions-700434fcd514ae46d516c7cee002dd5107b90f1e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.0-5508-pre-build-versions-700434fcd514ae46d516c7cee002dd5107b90f1e","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"eead67a5cba5f90b7330541f23a200d4d115725b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.0-5508-pre-build-versions-700434fcd514ae46d516c7cee002dd5107b90f1e.tgz","fileCount":100,"integrity":"sha512-K2FN4mZMIFs+wdeBCBrPPOhqLeAEPYnIWKJYi6bV8ixU22Ek5k9MS83I00Ncs0BoppDk/AQSXBhgoiUuNeiGMQ==","signatures":[{"sig":"MEYCIQC0JLyou0NogNr6yyoXKBTEe3trN9teYgWWyeePiVG+MwIhAJ2+baFWe5WwKZcx4ZgZrVFpOt6Y7WWlkhTyvPukM2XT","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":659239,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgUIpzCRA9TVsSAnZWagAA5dAQAIkURjxh+9c/boxGmblm\nm9QVgEITBu8EJ/HoamQbJqsattvtkLOVp8BMk7h7GGo4BNpabHn4q5m+LIsY\nBuRCZhx6iZFTXuufxCfR2YgaRx52B26Fbt3EhQebLCjPt5lcDhsWUriWpf2Q\nlNGrwuwHAeEmpTF0hd1l+rz/EA9UQFpNQXFAQJi97ikmncIl5T18WbrZc2SW\n3RBZrSpZTM49bCTmU1iIZ79uAaTZYeTQ2Eqm7XwfsRU+P011EzmvoGhQ5hLe\n3ctVe83OsLFIobtmzGhYwDWubtQAZqA2F3+u0KyXJukosF+GZtFRp5OmSJ6s\n1GBORD4I17X6EvZIT1nesRQkJfYxkBbKNafQZF4YEPbUQeTb334Or8uWIjeM\nQW7HwUe6g7j7E0+te+JVXWZ9fvhUm7e8S7Rchm/PtD+oITYwwwYzn1LaZgRl\nbDsd9btZAoXCooYvWDwVhI64CgTuRiofxiRSknVSu5fLkWc6KUyO3blJ0pSK\nf8IES9u3qFYMrQlwRIgHziG+Z+0KkKZ3c14bl101Eyi/ikalJlozdAo3Wq9f\n0LiT+WlSxWedAAygLWcTvsTSRvq1BKTCjgYZ7EjJQ/iN8UZUiQ8uBSGeRGlO\ndbZYMtPK6V9C2+Cj3vhzFfsiZasoWyT9RdvPpgA2odASM+Jzflb7A+JFDlAN\nPvKI\r\n=P/dS\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"700434fcd514ae46d516c7cee002dd5107b90f1e","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.71","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.63","@balena/jellyfish-logger":"^1.0.50","@balena/jellyfish-metrics":"^0.1.118","@balena/jellyfish-environment":"^2.4.26"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.112","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.0-5508-pre-build-versions-700434fcd514ae46d516c7cee002dd5107b90f1e_1615891058851_0.0981992653858117","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.0-5508-pre-build-versions-0f7a241bb8fa57dc85198af05c154b14cc1bec6e":{"name":"@balena/jellyfish-core","version":"2.12.0-5508-pre-build-versions-0f7a241bb8fa57dc85198af05c154b14cc1bec6e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.0-5508-pre-build-versions-0f7a241bb8fa57dc85198af05c154b14cc1bec6e","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d9888241e90849817c58e09f5e831d6cd5d0bfb4","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.0-5508-pre-build-versions-0f7a241bb8fa57dc85198af05c154b14cc1bec6e.tgz","fileCount":100,"integrity":"sha512-aMO+gTEPCPgLCwFORIqm6/EQXDYDKLrdTZsmuhw1Hggob9dTaQp/ML6TbN7Rj0rj7qxL+yXA54MYQUqVV5z4og==","signatures":[{"sig":"MEUCIQDNKkhVT3xd5cMK+bNw4c8FPblqyc2DeQPkd2Ev4Dh6KgIgbSykyDZAta6k4jAlWpaRSMEXErdloTW5Eur8vZhGiC8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":658884,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgURS3CRA9TVsSAnZWagAAJFYP/07r8ljgKCFKOl48V2h2\n3g/GTGF5PQNFIcQk17CdtPEtTOBeVac+eXMQtlJfg2XDUKb7lTRBtwM7QdhT\n+O28qkyOjMyxPe7PwUOHphzOnrC/c+6Pk3OvvDZqYT04N+klW7e8HlZoK/RE\nccVAgT/mAaFk68//dIoAIv0seq/4RVTBZaQYRb8MPPYBsrFymf1M+zxYOKse\nnnqOei8R/SNGCN2a6MCZh62CLUt8N+z1xFJTbQ8P9+In49Th6jDNptxSpGsQ\nzuoz4bJJrPOd6y3xh9CWeatt3B6Yzuw1WRcS3b4K3zm6uWJyrKyzTsfo2jpa\nPJi/qS+rt3AMzAlcdleOAAw/dyyCghGvuO/zLyMOo+smlDWco0D5qozwr/NQ\nwzKcoOTV6t57hwarjQ3XDNtgQ1KN8kU+hGt1ALrIsoMqA7KvLu1xH0vp41oR\nDQdXbb6o7kU1fxoV1BMqT1vCq+Es8Yut5mSw/fNH7QUZ6/nCSgq+ZoIdzdEb\nKiQ/ksmkO3w7Jt7Ire+culcq7ou1B/XlXc/a8O0dZ+WpwQNiZvop/URSTbZg\nBb8Hzmh6s/8h6Jo4bbZpoHtvX/0AFvd7Jbii4ZwhZCo3DK2dS5ugK8lRfq99\n6evq/c5jAVjmdIi9Dl8hxmEgAwjX86GDscRkKd5w+Nnt6XV8MlNEXBziciei\nWjwn\r\n=oPxJ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0f7a241bb8fa57dc85198af05c154b14cc1bec6e","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.71","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.63","@balena/jellyfish-logger":"^1.0.50","@balena/jellyfish-metrics":"^0.1.118","@balena/jellyfish-environment":"^2.4.26"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.112","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.0-5508-pre-build-versions-0f7a241bb8fa57dc85198af05c154b14cc1bec6e_1615926454911_0.9608598180941053","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.11.2-test-env-d7c9e7fa6446f2e2dbdf00ef65b7651b59e1f55e":{"name":"@balena/jellyfish-core","version":"2.11.2-test-env-d7c9e7fa6446f2e2dbdf00ef65b7651b59e1f55e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.11.2-test-env-d7c9e7fa6446f2e2dbdf00ef65b7651b59e1f55e","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"edac1a3ce74d73b99e907eea6798e42006c548d5","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.11.2-test-env-d7c9e7fa6446f2e2dbdf00ef65b7651b59e1f55e.tgz","fileCount":100,"integrity":"sha512-u+kXkaFyG967hF5iKhIf8ELksTkK6lZciK85KK6ExrMnWHO/5Hn4dH7XkFMq8ep8nUInxTCUyjZzlEF1RoNzxw==","signatures":[{"sig":"MEYCIQCUyQ8/KIelkNAZe479PiogLIlGVk6ejUaNFAfWFveW5wIhAK+J8qqOY+V4qaobcyqr9/CemG5aoB4b96YmdPrQcJCc","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":639668,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgUVe9CRA9TVsSAnZWagAAwPwQAJAXeVFXEZzGorkr+Smi\n84QpEqgUac4lwgkjPpkxq4OZsxzJf2gjZvnSK7txtcuEtvYD0yTxw2wrjTBy\nmr5+djg1xTUpWgBNSPOjqBzbTbbiTdKWCNwzoweTdMwM0jMF5m6CM6KgLuWn\n9GBnmwRWdaWB8RHbQB+4QgBAE85ZU4sIefxdOG0IosYLIlzHFfcQE0GqX1oE\nl4sT/+chQsXTkintF+mwH3IgNWrWICyKvtQZavIv4CbPOe5Q4bT+4Dw926MJ\ntBrm05iy0iCv7zVI9WL9jBElx4bnxTLf9i+bkhMZFhos9LG4tGsS38BxZgPv\neAGFvP0YSdTOyoaqLSyOUcOgKcY7VM5ecXWq4B73y/JHc4jIwmj4D0VAtjsW\nAgV8N5EIEzxMCMPLs32g4pUHjD39+Xb4FlayMfLpqY1lcYqgXCOc6uZ9JB75\ng/fz6ii7V1RQKaseq/TXyp8ecrLyRVPazSsXDQDBA/qUQXOxV2vLpt5BZG6j\nfKNMdgOb4od4+ma7pP7ZKQi03ssbeLLWdF8IKNm5DsZFoWnCKUWM9cz2x+92\nKsgbUqR9UoNXp5SqSmu896QZGhsHiLnJD7vx/Im3jQtGKO7MUmog4cOZLps7\nqkYf9n2mXr/Teh/sGow0mNmAl1UIz45zWPxaoh5r0+coETptGBIMfQuLvzN9\nRQoX\r\n=KeiC\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d7c9e7fa6446f2e2dbdf00ef65b7651b59e1f55e","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.71","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.63","@balena/jellyfish-logger":"^1.0.50","@balena/jellyfish-metrics":"^0.1.118","@balena/jellyfish-environment":"3.0.0-use-env-b7ed20428860fd0529eee6892dff41c005b8ad10"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.112","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.11.2-test-env-d7c9e7fa6446f2e2dbdf00ef65b7651b59e1f55e_1615943612720_0.675773895226162","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.11.2-renovate-balena-jellyfish-environment-3-x-c9f9a27a600d6be539b4a0e8cecb4f8be02b1a3f":{"name":"@balena/jellyfish-core","version":"2.11.2-renovate-balena-jellyfish-environment-3-x-c9f9a27a600d6be539b4a0e8cecb4f8be02b1a3f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.11.2-renovate-balena-jellyfish-environment-3-x-c9f9a27a600d6be539b4a0e8cecb4f8be02b1a3f","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2ba780c4011b009a6e38b2b8e24c42c107969cde","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.11.2-renovate-balena-jellyfish-environment-3-x-c9f9a27a600d6be539b4a0e8cecb4f8be02b1a3f.tgz","fileCount":100,"integrity":"sha512-WVmYnOltfuLm3PmSDasqAftYPydazy5m3u6ZXrW/7flXvvy4g/FNYkFvFEYKoPSS9fKBeq5ocxSIKGCVZXa5OQ==","signatures":[{"sig":"MEUCIQCjyeubDZpN1aayQ5V0nzRJ2LAjmcQmmmHMy6GgC9SuqQIgA6pQ4eXDXjS1CrpsYCeE4Od0oqm9YAtzWp7oqCdhY5Y=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":639599,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgUc5wCRA9TVsSAnZWagAA4iQP/AtNRc+a4SOUr8IjmhAh\ncu99QPxvsdbFzZYPdhfcVap3W4cIkQSIg2/JAjVqxhKfdBfSgWH1zwz/8q4p\nUaNPVbcgbKS5eIEsR899O85ZaMt6n+YOQHwhZzzJI7H9F0zju8fHY2m455ZO\nnWx1zXoVlbYYf4w07286+qUB4bnrQ2mXtCMGTgghzv34qu2oHoAW5Sk9ZIlY\nxLwt76ttyCFcYGPlW4CiKfA/fcoh5FPmAUqFxbAo6bipbL27zoSs9LakBfWj\ny2iRjAUE2i897Ny8m3AAOZTPJeseVI6Tqjhs8EXbhkWA3jm5SsTfTBd4dSDJ\n1HBYHX6YHROjWktsXJ7Gu3xVlT/3jei2q33i8lM3U7QDcGYZQH/61pCEDeFv\n8QlNHsgmzS66OgPVK4kySnejlbIMu4aNQYNX+3v7CEe/Atc2mFs2QESdvvRT\ns0P+rvXiUzIsITo1krikjztccIthA+YDp+jTeJv/4CF0rFUpokHSz4Wq2Ru9\nREi4dRLGqQxLGQ7pePYhuCKYqF61G6YmcDxSYNjROKX5OL4iPr/Ugx+8bN9i\nVJdRCx5PyT+t/h85Ut+0ILHw1flPu2ooCCrV2v36GFoby8HnUyMKqD6FDEB8\n+Zpo6g4/73JHs2sVg6zJh7aPpS2x1Q9RDiHmVNUavDq2V0KdPnbxlZckFpGV\n+3l9\r\n=1xLv\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c9f9a27a600d6be539b4a0e8cecb4f8be02b1a3f","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.71","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.63","@balena/jellyfish-logger":"^1.0.50","@balena/jellyfish-metrics":"^0.1.118","@balena/jellyfish-environment":"^3.0.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.112","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.11.2-renovate-balena-jellyfish-environment-3-x-c9f9a27a600d6be539b4a0e8cecb4f8be02b1a3f_1615974000042_0.9922210179113409","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.11.2-renovate-docker-non-major-2711eafbc0c0ce98662cc5102680697016861c94":{"name":"@balena/jellyfish-core","version":"2.11.2-renovate-docker-non-major-2711eafbc0c0ce98662cc5102680697016861c94","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.11.2-renovate-docker-non-major-2711eafbc0c0ce98662cc5102680697016861c94","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6e14e3e6b993ed1de6577e574f9c9b0742e9b112","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.11.2-renovate-docker-non-major-2711eafbc0c0ce98662cc5102680697016861c94.tgz","fileCount":100,"integrity":"sha512-FReNryvGAQI+TYKkWG1Ur5PVrcAxBXteLavFb0OeOgGPH3b1QqoI7ra3xyStwoD2/DHXvZAa15mtFEK8QYaD5w==","signatures":[{"sig":"MEQCIAyc/vcaOzN+0BLVZW+FdAGJ6o6fzQ75ZNFvZYcABZLaAiA98R9OPTB1QGWiL32HMTSnlZxqPL99/mz5J4ACbIkwHQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":639534,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgUkhLCRA9TVsSAnZWagAA9U0P/jGk+xlk+BjTLmZBU0uF\nsjB9qPo0zdnYb/XeDAOUsgO7Bcz2t7TybR48ycrKeqU79lgnQANLTf8KaRF9\nKYZJtX888PRdI71Gd8btTFs1lVpwH4HmIcLHAMfeVYZFgqotyDLgBsX1kTt7\nn4RUZGevRLCyqQClV/FJ/TU/FSr3/pj6micodMCdvSsu5xKf8+80IQ6VinL9\nH+4XdJ3W4663JSJqrXH3Fr/fYCpiyTmLdKOHyoCtVVg9EzettlyeRdOmqQWX\nwYpUjRhZTEsN9vpdZv+hH84M4uCwMhEMLM4T9etwtaWWtn4Mw299Mz/UdOJK\ntcaSBYzR8RoJrqjPB17cd3Nqd7DUzZGrlIzQNV+pKXZX+RJgOFImp83q47El\nLML4GJxzQSYyK61x7Tx7o+PI8feOtEV1odn+O9hD+xjYQNN+8IlVmhhlpF8y\nbWPs+GW3Os9OBzIpDnA68YVWUw4Fn47MJxdt8vzipjjIXeM1PLI1ZdvHDtg8\nBWJSA3TQDE5KW26+41OFHeNz0gfBmuxjFUzh1SWMFrh42J54MwmcNc/rkpOI\nYA/5SfuMFbu+5K2bhAAmj9/aObT2PtJ/SGTYi0Y0qUZjtpk1PcvcLx+rhOIf\nCSf3udbMpXqdkQYGoEYx96+7pev2uP/C0+tQ31sQi3jcyTqC6PTETEafGRdT\nokUY\r\n=zKSn\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2711eafbc0c0ce98662cc5102680697016861c94","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.71","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.63","@balena/jellyfish-logger":"^1.0.50","@balena/jellyfish-metrics":"^0.1.118","@balena/jellyfish-environment":"^2.4.26"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.112","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.11.2-renovate-docker-non-major-2711eafbc0c0ce98662cc5102680697016861c94_1616005195385_0.3895488312110651","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.11.2":{"name":"@balena/jellyfish-core","version":"2.11.2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.11.2","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e700c3b7762df45d5545abe883de6673a293652e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.11.2.tgz","fileCount":100,"integrity":"sha512-TeXfJyF4txDIK3vVtI4WVghdw56sDfHLWDt/smBKeh5KFLyCoMCLpdxO85rJoXqBwISTqnwxqzOCfCsnZvyIZw==","signatures":[{"sig":"MEUCIQD/ezLqQVvjT09f2gkL60MGJtNDt4SAKZ2BUem18r5pxQIgJK1y3/wC8ZYk0wTK/BrogGyhqFw+wrD0HGQDptWArnk=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":639467,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgUknGCRA9TVsSAnZWagAAhi4P/Ah1Ttz2QXFEHBhenfgq\n2gBQSOHkWWAOV/vE/hjpwVVKwqF2MmAYK5WIxFuRVs7yjP5J/YnHq9XKve3W\n027f8WTfAXADWpf5LWdSUYYOR7Hx3FAh6Ts1oKDLJ7Pwf9sGJHcXEdbGK21b\n0QbJTrcZmeMFyGhIocKWF0TlWp41NrxuPNzOLvnMXnlESWUFW6T5oWHePdwY\nqFXSUgNyMAfSvIDnwGdx9O3BOSV8IjDL33dVzeB529cHErv0+qh7VHzS5X+X\n1nJVidosagwa6lvT/0juyzh5pDcXM024K6PhzVRK+00OCYZCxY5KQav1nRQM\nRbKa9oZI6sOA/xpQNZ6LqT7+9TR+Md6mUdzMEYuYpomO+YK7p0SECeeC6/ZS\njRXngl50b1s40zx8nwQeMulro+INxycAnyhNnD5l/mYeiAXz4b5poBELCu8U\nU0zCboTfSOn3QcFKXiRFOIwWuLboezkZrrn7NhDGwhrrJUdqNy/bf2npyc62\nNrfhgg9GEqQTUuRPmu2426oyofiMFqcAkbQ14mW33gBjHLLyWSbXDYlWjdzz\nktOUT7Qd+T0Wud4BQaftkL0bYeDUSRGkmPsUplDKWxCOIdIw3FXlF5VkOPGY\nQJraYXvZd6NK/sU+Ta6hl82DZGkBVbfYof7orQXk6ebsqxcJ8X1sJ6me85Lv\nMQbR\r\n=SFiJ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d55796185c98dc5eae9b14fc67a7512d17b6cc2a","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.71","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.63","@balena/jellyfish-logger":"^1.0.50","@balena/jellyfish-metrics":"^0.1.118","@balena/jellyfish-environment":"^2.4.26"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.112","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.11.2_1616005573705_0.4556028002348109","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.11.3-renovate-balena-jellyfish-environment-3-x-3248a7d693ab90f4dd4c485f8acd965ddeeecc63":{"name":"@balena/jellyfish-core","version":"2.11.3-renovate-balena-jellyfish-environment-3-x-3248a7d693ab90f4dd4c485f8acd965ddeeecc63","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.11.3-renovate-balena-jellyfish-environment-3-x-3248a7d693ab90f4dd4c485f8acd965ddeeecc63","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"68dc26a9c2fabf7531758c85f170ce09caab1d1e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.11.3-renovate-balena-jellyfish-environment-3-x-3248a7d693ab90f4dd4c485f8acd965ddeeecc63.tgz","fileCount":100,"integrity":"sha512-KzTsoHrjF2uh4ltvqX5+JNpX+PETVEeYSwWH1lMzMtu10Q7jQeSbyV037dAG2eHBnZ79LZQUhUNeAgkGtaV38Q==","signatures":[{"sig":"MEUCIQCO1ckKyso5VSTRSWK/oRJ6VYNpmtkZEPj332x31MVxrQIgbtLMd8sHr5iGtLGsAerAPbMjje/h3PdQjOEm1cv4rFg=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":639972,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgUkrcCRA9TVsSAnZWagAAWWYP/1TGehOdorGTqlwmQBfC\npIi8VWo+K1Uauq4E/KeZIUjHu9YoX0h3UJZfs+TTqqDtiuy5Q1RsAxNQ5j1M\nCV28P0dG+RtPwXtuSec4webo25AjOugLQc2pkYD6nmcjmxmJFhjRPmIt8I3M\nbkFKjBfmvtefKNf7CO9Kd4dMuCwCT52VDyTk1bfMAAucx+SCjZv6JMcl8tcY\n/q2lzm5j9tSxd0RpR7xk4FRqDZApjuoamDJW9mo+FEEk09beRYaBBy5j7W9+\n2rj93ZgJf6kxMn+N3MBmThJd0WWl86/MoSBCBfOf3hl5n+wpYe99lt2u2M7w\ntto7muaaOlMq7z5lwc59eTswpnb3Y4dNoKg99Z6k7ZcnTffATjH5dXQFU1Yz\n9IasplOi3t42neBXDtE154IapZAOY8dSLJu5dOev4VECoj9H3OpE2NFW2mp6\nSSIdr7FDVGWV8fz/OzNRd8sB9a8nd3i6Bq2aFVweUdYiuEHWW0Q4NZzra1Uy\njTA7BdqYEIlb91KAbpw5s7ZLYJZci5LWoutW8TrBT4PAedE0irIdAcrlWGae\nxYKckWJ4OnbuyGARi38fbFK2hllWbdLOZarIa3psnYwXrgz2XbcU+6qyzIzj\nC9G/yyb+9s6bXYPGM18QfiAVUrMgKKYyj/8FHWDEOY+qEeRBQrrof22eqRuG\nhYJ9\r\n=9+86\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"3248a7d693ab90f4dd4c485f8acd965ddeeecc63","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.71","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.63","@balena/jellyfish-logger":"^1.0.50","@balena/jellyfish-metrics":"^0.1.118","@balena/jellyfish-environment":"^3.0.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.112","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.11.3-renovate-balena-jellyfish-environment-3-x-3248a7d693ab90f4dd4c485f8acd965ddeeecc63_1616005852282_0.18019049800706033","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.0-5508-pre-build-versions-4fd557a0782a5c4ff7555a4e6f0a4ce7944e6029":{"name":"@balena/jellyfish-core","version":"2.12.0-5508-pre-build-versions-4fd557a0782a5c4ff7555a4e6f0a4ce7944e6029","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.0-5508-pre-build-versions-4fd557a0782a5c4ff7555a4e6f0a4ce7944e6029","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2fd4e63cbea18c728021fcc92a1c2d7d4be78da6","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.0-5508-pre-build-versions-4fd557a0782a5c4ff7555a4e6f0a4ce7944e6029.tgz","fileCount":100,"integrity":"sha512-9Nb6UVhKLUwdeF20gRuqAL6KAig1qY9vZ74ZSmfYzQkyst+0MSJm6Zy7///cwPKXW0iz3VsQeOO0XrZ1sn55iw==","signatures":[{"sig":"MEQCIB0KSOgrKHJOHvY5/dBTLQHf2FRP1r+csQ1wfNgQE9f+AiBIZL9M+vXQaL4Do7mNUVQORt6hB5mWLRrACfhzY5utyw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":659257,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgUolRCRA9TVsSAnZWagAAM7kP+gMpb+oSt9DxLbR89BpP\nbSR91ZQfbIGF4+mMeSsJH4Xgy1/S0f/AhaNHk3pS3/XDRGSe6EUODMMird6d\nfU9ACILoK7Syr3iVatpRXzgawphiQhKzcOn/fKCPlt56ujdyzR8mIr0Qiwgz\n3K9So5tDGFDI3UrnN1cMJodoPQhd3PwUkNXOY3frbz29soIZ02KHwugQco7j\nWnmIDN2ZnvWumVQmem/dGn0fzCatC0aVtp5zykLXK/QLC3pkjeKgRRr/oUH3\nMGpkVnjV4/38lsnGPzsEZf8WZ0plEIw282yh2iHqv/Cop4+QkM3ygiEvSnOH\nhz4TPInxQqd+O9XAg7vIkIC2Ej4PvBKDL2I9rw/PZcE9P2M/icQ1c6qd5JoF\nmyE9caAoYxaCcElhiOOiN2HLJC7wJa/qLCvTPN7BauTZ/GKb/iznJz7lu8f7\nmXsyfq4lsqOOfZTwdqCBfJC5WoGl9IU/9sSVQ6lILKajipc9FKad2aqS/JLp\nrBtNMcLL9KMb6cW0v9DpopLsTr+6jofsscjsNJBtAwBRAggOphSGA1Th9Kvx\ndn8D7KethGTc7sqapo906zDKuNSR3d9kKmVOsVdyPCuLRaLjUNxifNJ1iUbq\nIJZpUO2J/MSZp20J53x6BpQMEgo9fif5LG92PGe6jlfliAbzA6VrUhBdDM3a\nWT80\r\n=n5/O\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4fd557a0782a5c4ff7555a4e6f0a4ce7944e6029","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.71","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.63","@balena/jellyfish-logger":"^1.0.50","@balena/jellyfish-metrics":"^0.1.118","@balena/jellyfish-environment":"^2.4.26"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.112","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.0-5508-pre-build-versions-4fd557a0782a5c4ff7555a4e6f0a4ce7944e6029_1616021841131_0.7527315797825587","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.11.3":{"name":"@balena/jellyfish-core","version":"2.11.3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.11.3","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4166dbf7cf62341f07ce30b810161374fc3543d1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.11.3.tgz","fileCount":100,"integrity":"sha512-UPyqwyun0vPBTsznmHXdBFU3AD3Pq+w0Zf5BCtxIXUtPJ7i6+V6WiGt5ULo8ni0Kpk1WP6edJugAo4vVsE4geQ==","signatures":[{"sig":"MEUCICJkiC06jM8hcs5Q2limDFuv9ofgThjK416GcS52f5umAiEAoNEFruAirZKG8xTQym/5AyXwem8Pv5rZXh4RdYNwGSY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":639889,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgUqGzCRA9TVsSAnZWagAA3sMP/1RahU/6ZvEXbutSmmg0\ndWBJVEd/pmRzIrNzUUCPmK6QvL2Oqu2kgucwalkcSxrm/sTnb+EwzIzj/V9J\n+uKIif3b3c8ch7e3Sqll50ZwldeR4//0/b/uXTuYLekLVGzBiI15QeQ8u5d2\nJRqK6E5wZs0hw53OsEUd5Lj1vDncOl8wQ+LkpAXCFBCne54XiSnpW3nmlu69\nvANgM89bo6m10Fib97giy0pDX67EwmTFcGZmmUAk4Q4GLvI8ssGYthTqrg5Q\nogcRd3DIDf/iFpGn6R18QtLK0KwdqOO7Zg+q5MAwQFTeVgIgbFUjMXCwTxqE\nqysZhwpMpXLUS2Ky8TSCZOUkeHBFwXuHFPSxEm4lW41/CDQTZLvbcGYAstIT\nSPbf3PuegUX5QctaRS57d/8OkE+VN2as+ZXEtsRyLPtxPOGEtC7SzGVb+Gqc\nC9zqPktnPp9F2k9dSP0BUr6taJgYDnf4mU5Ul58mDn2Ufd2qLra+hWxO+PJZ\nddf/eI/ZPCZN6nWFDANIQj19c4uufR9Iay/xbMsGNYeUwyrjre8BiY7H+5HR\nueUU7D/eiiTnep/oxGbKWqefPdq0CebwysMu/VAJXymL64brRDSiQaAw51Si\nmAx5ER8Nl5OmqknQZ5KEtLosAboK1skwZBERQqGZaXwmkUCp+iA1oyfeHA8B\nrq/B\r\n=o5IO\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"24991aff6ecbd2cc9d7d188a9e6da8b96eaeb7a2","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.71","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.63","@balena/jellyfish-logger":"^1.0.50","@balena/jellyfish-metrics":"^0.1.118","@balena/jellyfish-environment":"^3.0.0"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.112","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.11.3_1616028082443_0.6473075534472341","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.11.4-renovate-non-major-07cf39f3f168feea1ef0a8c105dd22ef121104de":{"name":"@balena/jellyfish-core","version":"2.11.4-renovate-non-major-07cf39f3f168feea1ef0a8c105dd22ef121104de","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.11.4-renovate-non-major-07cf39f3f168feea1ef0a8c105dd22ef121104de","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"bf1412af5ee45a350843e84db61340dc97ae4afc","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.11.4-renovate-non-major-07cf39f3f168feea1ef0a8c105dd22ef121104de.tgz","fileCount":100,"integrity":"sha512-jslxLr3VN6rt7QzDSbwNAq/BTY+2BVcAy+fxlC8LhTtZILJljPbfpFqbbPINKzAWVviTXkFWGtnGNPQYxSHT5g==","signatures":[{"sig":"MEYCIQDXIpRMOZYLZcAue3hzYhh5CV7JZF0Z/SUUFaExurF48gIhAOz5qcb8zfsQ6ZQMU6F6fuQBlcuvLHkp8sPKtOZwV7MI","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":640298,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgUr9YCRA9TVsSAnZWagAABHUP/RwH47CzPXXV+vuXjq5R\noGR3wVyUxiqetq6/XrUIV81GV+o51h+IW800gYKCQCkhKHQeWdEEQisMJTa+\nQgeoXEuHHoLsbSLZ8q1m7m0zRlIWGMaTSuVPDydaptbr8FWWV3GRSl+LF9Zc\n3IbCX6A+qT2Z02tj9p72M4xgJ3FTPJAEoTjAKdJ5Vom7Xg8edjB2lzAQx3H3\njiIiLxMsX7B5hPS51d35txrGbFhgPnUdPMNpEY5h3YFkOOh8D6rf6QwG+7il\n60KKOADx0rDOam996gDQ+wkLTlIzfpZXWwE9Ztu8c2Cagg50a5Xq5unYJ88S\n3WE5Ep5ISEyxeu4QyeVmNXY4hrIRO24BKxtIeRtxvJ472xUNCqSwYA8xxc5J\np7EA9rROnlhlatgu2z0FDgZ2I5QtG1M3bwGRtJ1DXURJyFYo7r5ZEKdOKSmn\nCyNIhT/uLXhp4l5r6fCXTVDRx8a0GhmnloZ6O/u940H65f6oBiAP+Hx9ASdv\nRhOd05j5Tm47hsuzJQN6nqLmVXxClD782zbV+kkhHTKx95mRnZ3xvfo05DOZ\noxNIa5BFwNUWKFX5xtp3y2BMMhBD/8BIeR0AliMFCfTMAopuzh/9H8HssDQg\n8/2GLw/pOjDfZE61fPB6AchYGdLxpQZ+RrJsf3/lNxe+LVmuPXqCSB11mKrU\ns7PE\r\n=ICLx\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"07cf39f3f168feea1ef0a8c105dd22ef121104de","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.72","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.64","@balena/jellyfish-logger":"^1.0.52","@balena/jellyfish-metrics":"^0.1.119","@balena/jellyfish-environment":"^3.0.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.112","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.11.4-renovate-non-major-07cf39f3f168feea1ef0a8c105dd22ef121104de_1616035672461_0.24500264655809234","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.0-5508-pre-build-versions-1f2429c85aa65ede8256d59bb5eabaa666623ca7":{"name":"@balena/jellyfish-core","version":"2.12.0-5508-pre-build-versions-1f2429c85aa65ede8256d59bb5eabaa666623ca7","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.0-5508-pre-build-versions-1f2429c85aa65ede8256d59bb5eabaa666623ca7","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d7ff1e88d41f002a0dd8e945aca510f39c0c3777","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.0-5508-pre-build-versions-1f2429c85aa65ede8256d59bb5eabaa666623ca7.tgz","fileCount":100,"integrity":"sha512-mzWeOr1a5yiUTrDwzn4L+BTqBUT4vt0vxC/q+fkwWn04i9t6HyxThmb9sJJuko4WYwRyttLebJD4Q2kJ68t5KQ==","signatures":[{"sig":"MEQCIF9a8Ou/WXBJ6LMe9fYjNvwm1S+KZiTq7VILZkisyY+pAiAnIAOXDkMSnymjXIegiSmRlYQn7V/XQXqv13M53t8ojA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":659679,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgUs4VCRA9TVsSAnZWagAATDYP/RjEL5mGh+57to2Txi5J\nOB2y3TOfaIOSeDKmKU/DOA47i2T8mLObyK6BDAvMaFzxvubGrOP87CuwOrJ4\nVmqGhvtvrbHVHYMfXkKm3TtF+OjgYE8W9aHHJ5YnYo4JjoUX7Y28hR9N6bYl\n27T8JXIztByWmMqCAv/7IV7VkqnswG0/qANOQYP6OTK2ghNKSpPo+POfeuHP\nM06Vlwp1PrKT23iLfvPhMHk16HWzMnbZc8vEvCoaShZtONypB2Fh0cyDXhRF\n9BwwNBdAf2+uE6B9zE8BXrv7vCpXf3zxkT3ouBgxu/9yO7R3cYBl2EsO1DiQ\nYMGkU8DWXLrlwUnk0/3xbXBJc01pFGk52iMBEsKC2rAR/A8g58PCfM5EkoQm\nYrQLmlHxmeNhugcZPPw2DnEEB0c5NQDaOknsDqughjStOwJOhD72OqXTXH94\nDemRETz7N4/SrnifVm0eK9sWQQ65prelnd6hUj1Q1pSQ2PvDzSzf8MwY+PB5\nHAVktoILiybCJ5Raf/BQ22BfiV4IMVffk89xR06ZvemKfjpbpBl7oYMu/oWn\nXlRoKUqi4hkFet0yqk5UxqEGurVGrqrpdNKgKNvtS9bF3Ah2sLvDWm+SfUg9\npErM2kWT/C8b2jYfEy2XoZ5+HJ+DFJEMrrdGkXw4b72pU5nyCMTXvSoH0ilP\nytow\r\n=ZL6c\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"1f2429c85aa65ede8256d59bb5eabaa666623ca7","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.71","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.63","@balena/jellyfish-logger":"^1.0.50","@balena/jellyfish-metrics":"^0.1.118","@balena/jellyfish-environment":"^3.0.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.112","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.0-5508-pre-build-versions-1f2429c85aa65ede8256d59bb5eabaa666623ca7_1616039445085_0.6593688756971341","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.0":{"name":"@balena/jellyfish-core","version":"2.12.0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"385c3411190cecfbf0e0e04705ab8c613dd7404b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.0.tgz","fileCount":100,"integrity":"sha512-1mDG0L3zAkGxZJULUlQBAlwJglt6lDXKG5Yy9OIIPxpb8yQAQJkui1tssCJfRSGiKeG1GQpws5eaAvMpCpDAMQ==","signatures":[{"sig":"MEYCIQCPuEy7g/P+3xUia9nupaxg2OcdeLHQvP1tUYlv6+5I1AIhAMwjOSLQmQoOZ6OV/W5+ru2vwtv5kyYB+Yu0Mp7iRlBN","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":659614,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgUtByCRA9TVsSAnZWagAAHsQP/iXEhSa257WRkBj/fx+l\nk2Bs4u3mzbQ0xWKOc3T8HVkoVdWQgIsw/eyT0H9tVuIZvTahmtSOmFLl8/xG\npQU1emu50c/28N4L2tBGu3QJiHUL7kI+WXGRr/heLtSJy2ytl4LHWmLra84y\nuDGqUp7cGh17HLJFjtJa9nkpIVvvKxIIuZY/11XojwKHpQIMGMf/2nTgd16E\nog8JV/JVbw84owu7O3uSvz/a9LYw8wPeA039iNEZBVTTJwspelk2ftGyfeGt\n6KBzSW/4719a4118h+4QGR5Q831LT9bcWn1Bs2rNvhgZdSn7JGaAU4Ne4tUI\nifsqqk1sCmB1zTyUENA8UO4qBL/1ZcJvyoIGtzYY5KBv0gzeOkzKjgk6RTtX\nvZaam9psPKy3M6Wt4792+gBJL9kajH4NlSg46MZLO466UFCi58wLpaUfbqzq\nj33Qdg1ZjqzQSsgzNfD1m2k83sYMtQ+XgxZi90RLG4AYdOQZJsZlkfUPXviu\ng8nwiGYpI220fYXdpRf6I1fZQdRsHW7hu9eEf/ChlKReV95BIU8DAIx6Iy05\nKjJAWA+cYDFwtzmowBak3fWd/DGzzxqGxrkAia3M0Fxu6YcfkvbLS+v0fEpt\n49K1AAsOZMFNuYSaQ1FjRdIK8MiN4mHJrUxVc8IYsW7HeyaKqQ+AuD+IMRQ4\nMXT8\r\n=RyEf\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2029b643b808b525dcc8cecb29033e41c126b330","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.71","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.63","@balena/jellyfish-logger":"^1.0.50","@balena/jellyfish-metrics":"^0.1.118","@balena/jellyfish-environment":"^3.0.0"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.112","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.0_1616040050029_0.1835585440298071","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.1-renovate-non-major-6ea838b4307a4190ac6048f21cf02761e2f09bbf":{"name":"@balena/jellyfish-core","version":"2.12.1-renovate-non-major-6ea838b4307a4190ac6048f21cf02761e2f09bbf","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.1-renovate-non-major-6ea838b4307a4190ac6048f21cf02761e2f09bbf","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"74b7492311f238997580b08c4fdb2166d8fdc49d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.1-renovate-non-major-6ea838b4307a4190ac6048f21cf02761e2f09bbf.tgz","fileCount":100,"integrity":"sha512-aQFfEWa66e9kww1H2DeMBgJbT8KdrhgHXTvqNBmPLrlBFtHvjDEBFeOGLYf3oA+TX6dkL9AGW0dob85Pb+pFmg==","signatures":[{"sig":"MEQCIH5ICPwr59c5cgPXHD/51S0jMr50cBCE+BG0IeYQ5csmAiAm40jXldnPGZ4ccw4sx3GGdzmWpESlD7jiA0BIIh+I0Q==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":660023,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgUtUbCRA9TVsSAnZWagAAX/IQAJEHzDwMcYlIOqOZW7xo\nv1FLMdwHjwsWKvPoJnyACkFykDQIiIKpwS/0F0psduf8nvX4MX61qAZHg66W\noaBjMC1Qn5MYGBpN+H+z3n73UYXDAnSeYmQRSXZz016udiwgsQgjDLRnyUGN\ntic3AHiCM0xEeqlv6QU2AzcxfRvrNdXq0ZJFCca0Y87RaNXqEcrJb3RKWC3l\nmdfd+QjtNk0xEqpOb1WRBOwuzsgPkyc0IivBDzM/Bh9QBBCdF7tLnZkKbDFx\nKXWki/auMV6jusn2pjG1XAunNVMTNHNd54DAA7Xs5EISlWe6HoLBenLHZoX0\nwg3dG9mm3WUPA1VDhL+W5uoCsPhoCreEHLbJBkeymRRDObk5gBS7gvhAfi4u\nqDKyzOMb+T8ndHY4FYChnJPFqqb/PlSp4GzWtOnAnzOsHth3Ql8hDvz8uAjj\nvKQLOB21Fm9K1lpeBBipxf/Z+l0uA886KB9EghI9c+ucnxpN4Mx8FjtogaJc\nJLvTm23pUamLGMZeHQaHLJToIg/l5DtArqvnFhnWW3SjbZW2uX2qiSYu9Z4F\n46cK22udEBnKPpBt+wDUfdkFJaQQ7FR0730nS7Fe3Rv4RHDtIBlhxlVKNfiu\n/qmYaU9dzBtnPjIkyd0mKzhetIZowru1Zpv6Fswu0nOKukHv9rq21RkE8KcN\nu+mD\r\n=cAgi\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6ea838b4307a4190ac6048f21cf02761e2f09bbf","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.72","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.64","@balena/jellyfish-logger":"^1.0.52","@balena/jellyfish-metrics":"^0.1.119","@balena/jellyfish-environment":"^3.0.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.112","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.1-renovate-non-major-6ea838b4307a4190ac6048f21cf02761e2f09bbf_1616041242638_0.745567998136424","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.1":{"name":"@balena/jellyfish-core","version":"2.12.1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0075c0a266f738ad750054fd1b7e9fba1c51b3b4","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.1.tgz","fileCount":100,"integrity":"sha512-sUn+62ghRecP3LetDRBR1XTyJ3/w2UsFU19epYUXpssgsT7MtPC+zGGgdfM1knNUdklIj6q72GFF6eAQ9wLGOw==","signatures":[{"sig":"MEUCIFZAmCUu7XQ8OwKK1HQrL+pr0sDPFkM4fBs5do+qzh0QAiEApb9PQRGMgBFtOlYy+kAYQlDpiLDe7k9iaH5KxXLMKdw=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":659963,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgUtg3CRA9TVsSAnZWagAAaBwP/j0S2/Kh4oaSD37P+f36\nuOlirT7G6b0zHYItCowEEo9jmqdUjT55wKJw+4X15Ve1IkJ8Qt8aOElnJ1JO\nP6Ht81MFZ0sV7VI0ZW5oEtLs45dj28eLoPre/V8DAZVmE6ZC++n+pQRUZpH+\nHN44/g5a5JLA3QoNsMnDzQtd7c80fMKBoyCTjjP+vA2DZKhFOcdG/iKV7ByM\n3/MDExJry6caqeGeO1q7WOyi9AB71trEl/GjeOmLCcAZ3vIYKs6DeTZ4VuJC\nZtEDssBrH2hDsf1T7CM/bnWCwhih/ummEfEl2YhLqVRujTUDO4+vPBn9aH53\nwG/MVQHICQHz7Qpn8GrQp8yEm/IgkJiXmgswP9tKEusYzrjD7rTT3tdGT5tU\nQOyWcfWslZhKPjeJWAZbuJh2BxNvAs1GpNg5WnIyFiEjDVhz9XF2tC3h9ON3\nvTKldzJy/OWrzoQ4kjYfOzWQ+suGMDHSWYkvwxhkuhp87ush0zORz35SJV36\nQlfGXoLUyJGGq0nFkEjRRxlIhW2+/S+X9qHkDWJsZPuWwFWJjU1zbzL/XjWI\nyx5n4UrzLqp2dsiWIU2OowOn3vVbJQKqZsvRyX7F3kLUuIe/JDb4tTXxfGtW\nOOhgcgDEo9UI7wDzIV7Ggd7CX+iQP9I+WyjjBN9fFEy4UX6+5zAdi/ciih3Q\nhqI0\r\n=jmEj\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"3847f94285db110f502bd5c1320831e69933eec0","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.72","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.64","@balena/jellyfish-logger":"^1.0.52","@balena/jellyfish-metrics":"^0.1.119","@balena/jellyfish-environment":"^3.0.0"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.112","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.1_1616042039068_0.1802800426594524","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.2-renovate-non-major-37f51b036c92e4c3e98091dbdcfff7ca88a3b02b":{"name":"@balena/jellyfish-core","version":"2.12.2-renovate-non-major-37f51b036c92e4c3e98091dbdcfff7ca88a3b02b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.2-renovate-non-major-37f51b036c92e4c3e98091dbdcfff7ca88a3b02b","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a3e130adba313f5cd175c5b861607a762d2f1733","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.2-renovate-non-major-37f51b036c92e4c3e98091dbdcfff7ca88a3b02b.tgz","fileCount":100,"integrity":"sha512-/yWbEipgqlIrPokZa97rjmOWb/cOUmpSBRJWsdKQBEzep7wHTrvoAjj4LZsM8alo0a6UOnQGW3CxO2aeyOlX+A==","signatures":[{"sig":"MEUCIQDrATC/BlUQue86CWcun8Eg2HwKBk7ia+eug8IkkqMV0QIgL92r3IbAkitnuiRqhOnSEGYYaSGv89KswVjtFDk9F28=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":660450,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgUtkoCRA9TVsSAnZWagAAYrkP/0lxzRNkbb+6lbJVC+ud\nlVvwq6jo/3KfMe8140I+XC2ZNxp4U0pnU3o5HwRSN8FLBH7Afcb4FICnKkF5\nOoFEKyk7PMTDxE/vumjpWeS7iHKs9X0l3Fe+EQTJrXpGf+sF81TXwV18EgMo\nM4+EO8st1qxXFJxuAISgfN9ifUnZqc/23dBL+suDusVek/L+0YiUCOiTNwqz\nBuTl+3B1yT6GhzoRndmkreg+nfrePALq/hWFHErSF1l2Z4wB0GniuGYYPytP\n02Pss5SwUSwtv2a4WfGBGoZPPLuOtuz5KGhjqSDXAEFjgS+dyMLhbtoVINeP\nB+WYm5a9YsqAffJmDhlgYDQfKvlU4Ti4it8PIhTQs4Rb8YKsTHNSYvcBnKMs\n4BR6CEgnb/o5R3jCkZ99eT6Fkopk4GEBfwcKSJcIugNIjZNon5B1md64yy0n\nZPogbpBj2l+GTYRhdlahocsDBUqLsBxZkboTaGJwq4b/yLrp3bQEnZPVnKKz\nAXYXUv1NyteHHIl8JecwGnpyFu/S8yUgPkGvhFsC4S9T2Y0zL9KzgEaZX302\niScMOmy/USjcCuKCGmFyHXvVLXEBHSbxUpP2A5j9w+Ez7cTqbu0Kw0s7ZJY8\nIFDwuIZtJOlNhj0XVxfSg/v+q6SjFMQPdnnLUMpieZFYOMkP/r+zfo8rLVhR\nfe3L\r\n=Znwu\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"37f51b036c92e4c3e98091dbdcfff7ca88a3b02b","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.72","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.64","@balena/jellyfish-logger":"^1.0.52","@balena/jellyfish-metrics":"^0.1.120","@balena/jellyfish-environment":"^3.0.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.112","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.2-renovate-non-major-37f51b036c92e4c3e98091dbdcfff7ca88a3b02b_1616042279987_0.003641126782210735","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.2":{"name":"@balena/jellyfish-core","version":"2.12.2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.2","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"104498eea19d1c59b4951e2522e44ededf60c0f6","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.2.tgz","fileCount":100,"integrity":"sha512-gGv3CnGksIyorLZweeWNJV5q6iGIeJEeVU1dnnlhKl9y3z0rYKxh+YxBB9hO41DmB0aJ1Pk3dZY/bRUYP7ioew==","signatures":[{"sig":"MEUCIGqZp7NlImcawQPxFI5KYWtrxS7ZfZHURyYnvZJl6z0dAiEAlwPdSfr9v1IF8AYscdRRfU55MHfjOpfvKiN1BBiTNYg=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":660390,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgUtt6CRA9TVsSAnZWagAA008QAIYdGecWw8Qr1+Dlo+Zn\ncHM0qKsSUQ7kSlluv7O23DqP4jBLSBBzpvMLQ537r5F5t0Gy6i2R5C0SieDW\n4AyyX5Ux2bqay6apPpz+m/m/vGc1XaaxvGf6gwN2oVjz8TyR4EKg2yao45bi\nzznWy1TQ760CkXDFyulv9cEG1ABcnVKXdlhGPdczFWc0q++lZZzvxozdUGyT\nsiBCaiUC+bNqeaLqK6YzTa6bvfuTaeBqbpIlDE2VUCxDSlQlrHUfm0laQmw8\nadWWfIw0jDq1TcdqMzGvhkucQFxXnDYjR/mXEm70oi8ERDu8Sa/18T2rqBWH\n83G79ssmbz4lhVY+NlJOidC5ZRhoqBh3pjUJhjdmYVqjj9m7BK0ZdH6YTY3+\nOVcyP5QkUDJ72NBr8WhIQ8gl1Yto0S6o5q3n/r1kCzuPxhg4wd90b1UarOde\nIvcoOdkWX1dhipEMWq5wRPmPt8nKgVfVFbOs9fA45LExWjs1LqxixmkD0yw/\nqG5hX+hrYOtot/ZQkLQmLYdzb/Wo28uR7I7ik1y+249G/WMwaL9Z+jQFDuHU\nl4FMhwQyQRCdfrsk/8xt+5fAHDr4ZqRuCfjAiu9WJs6aGc4G+9fT3vuQsAPy\nJeKvm/UJ6RLxcvrMwtLiWN1G1uhG3kKONRWnoM7q8j04sBg6c4zjkFngX2rM\n7pw9\r\n=yzBz\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"14c7e6e8d3b44a908d401e20d07c91e915f7b373","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.72","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.64","@balena/jellyfish-logger":"^1.0.52","@balena/jellyfish-metrics":"^0.1.120","@balena/jellyfish-environment":"^3.0.0"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.112","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.2_1616042874337_0.7272699294873224","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.3-remove-version-migration-3bc335f8e28bebb08739f781438e271ed605392f":{"name":"@balena/jellyfish-core","version":"2.12.3-remove-version-migration-3bc335f8e28bebb08739f781438e271ed605392f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.3-remove-version-migration-3bc335f8e28bebb08739f781438e271ed605392f","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ed06270b69a5098294ae720b490bc21c3d26874e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.3-remove-version-migration-3bc335f8e28bebb08739f781438e271ed605392f.tgz","fileCount":100,"integrity":"sha512-01dGB32UoQhvGQOqKd2tl2wVOpcvpQO96LsBqxAuUTrUY9iruC8VsL9y0vckQWmTHI828RbURe88BE0RuLoE7w==","signatures":[{"sig":"MEUCIERYy5dCrFM1esNyyTnl5w4TsiVcZ/Xy3JnMcpSvlXYHAiEAyJZYOl53L7txl/IzoJrpozQcV3jDIkwF+P9uQF3QkBE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":658940,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgUxZBCRA9TVsSAnZWagAAvisQAJ0HlO9wwrxMsUdYzEFL\nPd7WFzKbxCCcnxN4IXCiYKidYuemfNr0/trANseICNh7kKDMdDKRpbTxC9HS\nfeFVshLmiHjKmNXqc7dx56bxU+WEuAXoIT74oAkIrnpOb4z8/Z5uaGboZoww\n8my5rJx+oUZcQJ1JWqs7eHGUSzOIDO+fl9GAVSVGhi/VkY6BMnuB3G1ZqAhE\nU7+lLY6ca1/aJu8pTu0NiPAkflG8G3QqqcNp3zHV0xRvAOz8mXT+OnVk3kiZ\nuCzOWc3ngCX9MPXFxpSNwSoGeyFyONPc6ZBnCQhl28qoUze3eGn+rzj2DEgO\nQQEAHx2mKUIMoBfxuaenURMIRRDVPUnXuGN403DtWhjm7hGZcF2LrKi1yZtw\nyXi2qgUNDZeSClvY9wwzBfMm6FP0AKgK3TVpa2WJ7lqbSVapz1Tl1j7+I5w7\njvJm2XLtlAFsWxuLLbuDBfyMOGrF02lZUPUmTNvIyCb7vimBvisBorewEORM\nMpzOXSIeEIi87AXR+snuHQL4U3JNHffR0fQhOonQeWaaHemIH/zDjVHLc6TT\nnQoizzC/Jex5tX/qU6elLixvHFat1yLWeJ7P01wCalZxeIk0sk9pSPD/QvWe\npG/AhycW/ZSj57ZoOKGleKvrgX9XMC3UH0pu8Z+/iVWWG1gRPKISD15PrHOH\n7w6M\r\n=7tU0\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"3bc335f8e28bebb08739f781438e271ed605392f","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.72","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.64","@balena/jellyfish-logger":"^1.0.52","@balena/jellyfish-metrics":"^0.1.120","@balena/jellyfish-environment":"^3.0.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.112","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.3-remove-version-migration-3bc335f8e28bebb08739f781438e271ed605392f_1616057921287_0.4361325772987028","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.3":{"name":"@balena/jellyfish-core","version":"2.12.3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.3","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2beca74bd2add8f63d5ba53d1861695eff247556","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.3.tgz","fileCount":100,"integrity":"sha512-RgGzzMAHyMZ6z2JeTqNczCWTKQ+9Q9apA2g/bEPIwq1ILoMp5bsEhCJ2fOjCNKudcShIaarA7R2KGEhaLfSCVg==","signatures":[{"sig":"MEUCIQDrhk1/LeMtypzvJQY0G8mLwYrlAjYLPKHimN2VadV7gwIgToKfoWSE7ADxFmn1Zg1i6/PW1YE98keVKH9GB2EwftM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":658874,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgUxggCRA9TVsSAnZWagAAzA4P/iZ+8cWK1UemWu8WpU83\nwEY6Q6gU3n7OyIrYJuGnkAgkt8Fo3j2eyrSpRENeqJhabOirThHc+EkE4kmo\nl5gucTUgoMn/bqVj1gn/27tD+yxLgbIVcLLSnf02lRAmYlsDXOGAGIMEWL8v\ncrwWtt/o/zebo+GIYah6xPTqWQAyNtwKkk3Rk+BOeNqU3ZAoXeod6PPo/HJg\nH/l/QhamOaP1Vt5lYPyBXPDR+gJKShqn1zQRRg2e/UnFdUWs79NWiDdYJt1B\nWOUQwqngetwzE7g/W+hvfIm+eM7zYgCullFQhngJ0j0wMfkTL4y2OWivDB59\nx00rtw5GlIzS0DjJBspt8F8qFrAjETFu9+XXi2AXGMPI1N3mCTdDZbZiw4gl\ni+TrclGr/NSzsebge89h6nn+2Oxb6fV7YfvFlpMDQWbIOaXGf7y/lKh3p7tP\nkqlOi3D+ZOLn7dfQwQCQaKcah/xehtWmoCgWrLiRZXo0EQfXBS3OMZkpzYJ4\nG0h+heM0WJxNtAM6+NKA6J1YEC9GNqwHW7n82K1SHk7YA9WVOHb2z9QKaNOi\nieieFFrBhxtiIJcjnxq7lRd2fDyKLXEC/UxDlaQLwM8Ad21g61Ys9wpH9+5z\nP+aUBEVSoKqGMj4PHK+JMfP1QTp8aweGRtRsdvWkSge493zDTZfpf1ccBjQ5\n6IzL\r\n=OIFJ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"fdb28fbc885192ae2f466f83f2e7e200f99e7bbf","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.72","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.64","@balena/jellyfish-logger":"^1.0.52","@balena/jellyfish-metrics":"^0.1.120","@balena/jellyfish-environment":"^3.0.0"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.112","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.3_1616058400415_0.008689086571219251","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.3-add-version-triggers-c768bab218beb9f864fdd6414eca90b102fbdff7":{"name":"@balena/jellyfish-core","version":"2.12.3-add-version-triggers-c768bab218beb9f864fdd6414eca90b102fbdff7","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.3-add-version-triggers-c768bab218beb9f864fdd6414eca90b102fbdff7","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3a1d613ae710696419d4b5e9afea97affb35c18e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.3-add-version-triggers-c768bab218beb9f864fdd6414eca90b102fbdff7.tgz","fileCount":100,"integrity":"sha512-40Ax46OO1JkIWBXr44WNio/IvAlfRaxK6Snjd1ch9Ty2FYpLJ592my6nLvXxYUWVl5qRher+hsrDU9/LyPsibw==","signatures":[{"sig":"MEUCIQDINCrNRZ8xmBKJwQkKhsXuCrBm7irhI8P3QM+RpwROkgIgWRl0yX9zb96LoR67WYwnvDz+MZlVIjQlQPnWDPcq6m8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":661018,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgUxnfCRA9TVsSAnZWagAAw+MP/jcrvhUm3SIitKJboOSn\ndMBCvwl6GH8ZA7LfhCis0Y/AawpWkCxJOU/4STQxfCYgvIEZRFCFyVc4OUuk\nurPXZLiJX/ExLd5E4yrDSJlCWEX3jMH5NhVw7Nx8U4+TRBpgAKIMA8ZMsJYx\n4mnmjdi15mg3ZpcNwSTNmJOP5inzKJaPHdYIsmyXybhrHDA+Z1iLbTVowyQz\nJBySwfP0Cc7SXeI6dKRl7WiPRleLj+iWg7MssEmKXEs55X93rreoVnugZeUR\nNXP02yv5e+BTSjBfjFEY1G6MDIgTmJVrtmfgplik4jr8GZqVmfwe0agbrCmz\nN66a4PmCNKLkmhllnfY6SEQnYB7itc9I1etaz5ToD0F2TvjTt4r8HzdEeYn1\nZZJIj9ymS15FGlnyYb1ahMEnKqwr6JgZ1QLJNxIVGkVZiF3lShldoFl6w8HG\nh5HmKXMEdo54CLkG1AXzStHlNM2Up58ypRRmQhdPgCvVD47ncFUA1fdRM8Kc\nXg44hHC3Lga88v8stjjga7vtupcFM/McAsXmnud2rYhokFQdQuMXyUqHr86N\nqF6GGjdzedcQeCsxK69EnTRf9AVcRdFXsrSv2LLG3weitsHwodOhWlFhvEh4\n+EzKeY1wZqeLv/JuHrcXpNmkHR5DSlqa23FKxOM0/YHoBPVFiu0HZtg9oVfN\nxvAM\r\n=GS+y\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c768bab218beb9f864fdd6414eca90b102fbdff7","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.72","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.64","@balena/jellyfish-logger":"^1.0.52","@balena/jellyfish-metrics":"^0.1.120","@balena/jellyfish-environment":"^3.0.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.112","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.3-add-version-triggers-c768bab218beb9f864fdd6414eca90b102fbdff7_1616058846818_0.8400112281593641","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.4-add-version-triggers-8a48c5b9562142d5a129d98c82c0a6a118d373ea":{"name":"@balena/jellyfish-core","version":"2.12.4-add-version-triggers-8a48c5b9562142d5a129d98c82c0a6a118d373ea","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.4-add-version-triggers-8a48c5b9562142d5a129d98c82c0a6a118d373ea","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"430a8b9c6a51a14731d6f1512bb147de575f6121","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.4-add-version-triggers-8a48c5b9562142d5a129d98c82c0a6a118d373ea.tgz","fileCount":100,"integrity":"sha512-uek9WwPhn2KWGV+JtNMFUzQFon1R5l0d9A2+I6/mEFHkfZ3Uwmnk/nAoIiRSKJxMcYHV9v6CwVDeGtN9mSTenA==","signatures":[{"sig":"MEUCIQDiQBhOUN3sFfTGxRfY1xTt1ESmHM55RuENedvVG6qcOAIgbgvFNBMqC7SxRSLshcaNKO7O4bblQcgfPRpo2XZgmeE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":659502,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgUy/5CRA9TVsSAnZWagAAY+gP/3W5JkubBnJhtuXRZJNG\nHTNHx1mLp512lYUivFRh2kgkd8vk/iVU7q9iPvEHd4T1bwuEIIRWjuWCTx1U\nI+Z/MVPKVsU+xLXFLiy8AyY2ih12acncHbR8sLl/qQ+zZ6+2tvcmZVBsqZEe\nnVm/xPtztCUl5pVeLC4XHIWEL2E8J6CLsLagCVhRTWWHc8xjB1bMYf0Rqpv2\n1h7Piwnn0Rqvd5IWzg/MjhcfptyS4qYoS22HUuoV7WeykQbYT4/4M1qEII/x\nhYu1zqz2T1vW4L9I0tjCE5bcw2izasUf9eRlk3WixJ6D0R6JsPS+yQqf87Ak\nyzEeFNgVPZ5u2NyMITd4CWEOKyUE3E6XnK1bWhUHKEQ4VWgmTijlioWq6YfC\nHyHQlUJJR+8ZPvW4/bfYhMRlZr36lBmuNd3rhDjeJjUu/f/rOONhnBGegPZ5\nrRNNYG44A0F0odwe8MCBNp4rcOLOAYnh2kkvcGzBJyWtI2cHDxbKrFGflPds\n1brggZ+KtX9mq/UyQEp6bMcTUYVLN38+zmWU5jq2NVJr4j/YwQtWKABUw++1\nvaFyiXtqoBHQQSOPQD+EmUvjkeyWyiUIjwonNnGOm5CKvyV8l3GqSp+whYxZ\nddymOrQv5GqZdY4yMLPROtVQ8h06CJ9bjonAyfeh3ar1gF4xYCfzza5LRVB1\njmDW\r\n=EqJa\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8a48c5b9562142d5a129d98c82c0a6a118d373ea","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.72","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.64","@balena/jellyfish-logger":"^1.0.52","@balena/jellyfish-metrics":"^0.1.120","@balena/jellyfish-environment":"^3.0.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.112","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.4-add-version-triggers-8a48c5b9562142d5a129d98c82c0a6a118d373ea_1616064504854_0.06509870409043939","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.4":{"name":"@balena/jellyfish-core","version":"2.12.4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.4","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"54a453f8d1e2ed36b398388dea1033c7eb1cc486","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.4.tgz","fileCount":100,"integrity":"sha512-OOFUb0qPSgh7cdXj3BFraJmojv+LHmHN8JghFKOLoGhNHVVFDQG3RF0L8FBxrZIF513caqM9oPpeWrb2y6sLcQ==","signatures":[{"sig":"MEUCIQDmxP6yspo87DGkXEpuNv2wC5J1mZwRr/dN3/6bHwf6kgIgc8cdpKl6QwY4n/FOoWKmGuYy+mnX/p9CBYup/roh/bI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":659440,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgUzETCRA9TVsSAnZWagAA7CoP/1P7glQ7ExRkSsmshyNJ\nP1RoIvyHdmvBLzbm9n83fIL3F81fldK3QjxYgJwmGqVNM1JPvp7DCgLDc1Ok\nLLSCRZqPmXy+/fzAGcafZINnCihtp1akbOXgEzQZ7XwNUwaMuPTdewY9DprD\nOakOEOONCyer8MSrrsoLQ2PHIBYIbIT9iSRwheeYMe4O2W7tDdh6SC+71CYC\nqP9E3Ox92bEz906RZY7ODfHNnHA+1CJn04TtsV4OVWWx99HH5uGr++zzcEoO\nGQPXw7duVKumNv/UnzPgtdJ34hUpBC+0dU1eoqlcKWaYTNnmId71KIaFNqNn\nt4wRrWq/SYw6epLjbSjIuz3U5aeX7idHaK8cKrfhEGNes1Kmafa66bkgv1Oj\n2D3t+IoYlhur8aXdcxL2H2JNJCfLVm5Forc0/y/+ZAoPqiA0gECrUdMupCaW\nptZzAYNJLNJzODUqMGQPN0jlGDUER8uUeC5Z1apEsoiJ/ON7+IvkQoZ4UUef\n9Y81ZTQ6kAxplKr16/HWxuYfVR1AzUGixwP5uTnRk/xtjG+dBjGvyhrNujeQ\nzu/V6KH4AZIFfw52JizyTKvYg+IE7qKwPR/au7nKIXRxBu4VYyZmacav9zLi\nmjQimYPxJVaSZwB1GVFrFlPsw1VfdGJoSmeoFAJO43Lo9NNElg4Hg//Ua4lM\nHlss\r\n=Stwh\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4ad931657dc6b8b69f497d6b0c4176fa72f3e5d8","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.72","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.64","@balena/jellyfish-logger":"^1.0.52","@balena/jellyfish-metrics":"^0.1.120","@balena/jellyfish-environment":"^3.0.0"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.112","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.4_1616064787079_0.7692023396111327","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.5-renovate-non-major-0a9981d97ae92fb00a950818842997413acea783":{"name":"@balena/jellyfish-core","version":"2.12.5-renovate-non-major-0a9981d97ae92fb00a950818842997413acea783","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.5-renovate-non-major-0a9981d97ae92fb00a950818842997413acea783","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d3bd0c9c2619cddb5ee7bf7b0d4f0385d92b710e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.5-renovate-non-major-0a9981d97ae92fb00a950818842997413acea783.tgz","fileCount":100,"integrity":"sha512-k8l1IPL5jeBY3s3HE5y6yb5Nmn4m4T5sC/Ca/awEY6JYcrNBSV3sgc5cq6ENiTaXfkPxwORUNt8n2e488U9bLg==","signatures":[{"sig":"MEUCIQDNJQBMeTYjarVqSvikDq+iGvbbgvw4l6lxcNYskO+CDAIgQOuQz3ALnWBIOeM7oVHPpXBtrRHG3qAGqzSTCATjRys=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":659849,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgVRgZCRA9TVsSAnZWagAAYTsP/Am+zej1xrjs8FneQ97l\na8BLBuo7IQkuer5nO2AUmXVChNvHLXmxhakk1piRoDXgZpwfy2l5B02Md5qh\nu5PCsjL8L7alzkRo6/Sv5IKzO2FH+i0hrrDhxxyE/mxaZVNAv2QN/4KAsaaX\nQXx/agPmBSEVfI7FvX2pIjfsCNhDivh/P/rvAGlACzLBaTSRYnz9JrXPHgq/\nuvblD/GRe58fEoFaJFZ60PoJJ4BF++7+0mKSPz6DTU2jQzXQhFbmdEvf0tCQ\nk6pN9bg6voNccf64sPfoKSoFe7nXKMpe2sfykKdJNlmoUFWv5OMssi6xLjOw\n7u6QBj61M8FO2Ia4YeyvBlvcO2+XTdWdDre9pealm3bVPuSF8XOzCAe5QkWz\nWSU2eZAVM/jfykaixGMU1nZ7zR1+RGf5XVdJWTgpWplurZFgJNM4g0FFyTAG\nooYTRV4XoahcpAgA6UDBbpWqnb1P2ZPzFpt2djpX8/34mO6Aito5YZgIW6gq\nUsiby+KnHhmBiTWboVIsCbUq3CJvNK3P58/9UPPQ672FXs4Z8DNiCIGJCig/\n+2A5UNQ4LK2qMax+k03svlpSJi/WOkv2fEQ0zF7WrjB2YyqHgLVs0Q2Gx7L4\neS3P3EdwS4A0Pi70XZS698jAHdaJwkEw4/TIVy8IxFKnCrqXnm2Av+Si8cZF\n1izF\r\n=TRLG\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0a9981d97ae92fb00a950818842997413acea783","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.72","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.64","@balena/jellyfish-logger":"^1.0.53","@balena/jellyfish-metrics":"^0.1.121","@balena/jellyfish-environment":"^3.0.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.112","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.5-renovate-non-major-0a9981d97ae92fb00a950818842997413acea783_1616189464657_0.17702909722593096","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.5":{"name":"@balena/jellyfish-core","version":"2.12.5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.5","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b1ea1edec39c792b7fc95f6b2b94a2be6b848246","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.5.tgz","fileCount":100,"integrity":"sha512-VL366iVv4QZ9BWu6pn9hEOGp01rvCQErxIepEBjRIbpIb+pVe3ETBTR/TDHsVZELtkHy5D+clq8/kVVlUmrMKw==","signatures":[{"sig":"MEQCIGTFciBSy7vYwwJJpXpenz4BRLkBixyDt386ikLUG5giAiBf6bhS2rh7RyH7LhNVtP2odKHLmx/739OWRoyagYX54A==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":659789,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgVRmMCRA9TVsSAnZWagAALmkP/Rj0zeYpBxwVizq6D2Sz\n/BXcrmKRq7d8HsVa5p72lxYbU8P4geBkizt4OGbn9+FHD3F2+zxP7TekvsoG\n1LJWHaJFr5Ldf829CZAyNgmYqkv+iias5XYX4yHwtPlcX2L6rc1kh2/cIVo+\nX1k6/u3jVYbMLpqdZtuKiR5N5Gllecn3nOuGoPWQhoTxd8Sybw0QQEmZlmY9\nyU3rK2pzJOGStHefKyldFGV2thWxzUZoMwFgQdxgV5XibQ28xNWNVxaU2thj\n41lUSDc8e9ulJB/If/canmPzDtDNDehFNsl+nZxk+SKZrFcPUlfhuvopYeaM\nyswiNrXnEhDCEl4ZsA67mZ9b8THY5ekffRwDDnH7Fzk43eFUUJcblLplnUMV\nQBCLr32yqEcOFw7tEMjX2uMleu66rYAXLPI0rp9rSJbawDG4pPIuFvJxSU4j\n4ZUbXuvl1VLJq7N8M1aIH9Z6uiQh5fkxNdpfL7XoK+FRZiHifZ8HypvZMquz\nzZJ2le6av4z1rS/onU2ND9sKnkDoaRCHO0t1TrJLOhg4xQKMmMxRk9novh9r\nIvsFORGOu3klAElwGGKvtWVhBZ9mjqrZQGyXv4LAsHwehEIgrXZ5EYw+lSiI\nmdF1B5XXUDR2pMvp8wXvOAiB3lm9wXTY6lZcDp/dHzGyqPi6Aq2r8Be8sFn4\nG+iq\r\n=sPAK\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"00242756a888baa7faa6febc3e1f2968ba089938","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.72","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.64","@balena/jellyfish-logger":"^1.0.53","@balena/jellyfish-metrics":"^0.1.121","@balena/jellyfish-environment":"^3.0.0"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.1.3","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.112","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.5_1616189836300_0.8954980759654603","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.6-renovate-non-major-f148a7dfc0a88c5fb0a50ec9a4a6de051043302d":{"name":"@balena/jellyfish-core","version":"2.12.6-renovate-non-major-f148a7dfc0a88c5fb0a50ec9a4a6de051043302d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.6-renovate-non-major-f148a7dfc0a88c5fb0a50ec9a4a6de051043302d","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e8c12a9227e5c7737be7193bea8087eaaa8501d2","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.6-renovate-non-major-f148a7dfc0a88c5fb0a50ec9a4a6de051043302d.tgz","fileCount":100,"integrity":"sha512-rHl//A2P5cAyi4nT/V06CuLzEUaUSAdPcFu1IDw7VUCBCUUAPh8cisIwxZeFa7h3mN+UxWV0Aaa/m5OjhMSJoQ==","signatures":[{"sig":"MEUCIQCBQ7ni/BemrcsP8MQQqQ/qbw2g0lpcaSv3ySBVXUd+awIgUBZ2HgiroxkZV55mZnPNdFoID+8zc+SC3y1c1/SsEJ4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":660198,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgV7F6CRA9TVsSAnZWagAAZiYQAIyPZ1D5ZrBBbnAbzm+6\nWMbs5mav9OCPc9bLhHJi/P0YCMag0I1ynRX6PPrV7aIsgbXNMCXEMSx09cZ+\nvOk6xpJJbm7fqef4ghG2fJKIFT8Z0qw9vmhnNTg6JH+a+4JPHQTDa5OUQWgN\nNIs4sT408RaFkk/mfSZzpxNgZ6kJFAesHx8VrEn+/goR6t2WkhbeYMRZnJ0t\nZcXRRHfX3bYb5hBmYSs5pxMRZfyl/RVjYZjGQAugbJq51/FWZgqKWt+XBGxU\nQLbg1P/jrQhlSiduIEHLZt6sai0C5B+dDOrC+/6mGvPQaO+XHC2utQOHbf4T\nWidlrHi3gPLpernFL+k7pdIEQtiSe4tvMlLfMcDDg7Qhgc5lAyIO6SskpFQj\nNCB3iH9MeKwyUbuuk+BZKbdAHVRpuAuvomwVu2LCjgGnI6XZ6ZApE49qbaBk\ng7w1Y4hB0GFAgifXfjq1Vg/d/8izYX4srRjsdbCYbA4fZbC4pFIRVSbjyJmU\nhZ5vm+zGtNfauBjK8hCeJALsfhNKHWQ02YDw0gvQEjQHOAaeIXhfwZg4wMmx\nz38fYxPWv8OoUEyY4j4k0L8yHo3T3cdno4jaBVA3VLQ3K14BMbiAKs1op7v5\nCNHP6ECS7N+FHx9I2UaBvFsvgfHIMaaONFc/t8y+yfziYVc3LbsSh5NsvMD5\nZTCm\r\n=AP6s\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f148a7dfc0a88c5fb0a50ec9a4a6de051043302d","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.73","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.64","@balena/jellyfish-logger":"^1.0.54","@balena/jellyfish-metrics":"^0.1.121","@balena/jellyfish-environment":"^3.0.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.2.0","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.112","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.6-renovate-non-major-f148a7dfc0a88c5fb0a50ec9a4a6de051043302d_1616359802241_0.3747518551099873","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.6-renovate-non-major-d0d8c8da1158cff79a659271696412544151fcb0":{"name":"@balena/jellyfish-core","version":"2.12.6-renovate-non-major-d0d8c8da1158cff79a659271696412544151fcb0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.6-renovate-non-major-d0d8c8da1158cff79a659271696412544151fcb0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"52263bdd0cf25d3c250d2abebb98d4952f0bd416","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.6-renovate-non-major-d0d8c8da1158cff79a659271696412544151fcb0.tgz","fileCount":100,"integrity":"sha512-STowxGtPsSqmlgZvbiTLpYGyT/ny3E92krVfv05zZEgMXD7Ol45eVheXD2qmHgcoCCzC+cMy6GGU39bQZjvEtQ==","signatures":[{"sig":"MEQCIQCG3x34NRyTn3HO1pF1Ck9sOCu5KocCixWS53M+2xakfgIfVVFgYRpLJYQePxpDIw2D5oFkuVNft8xief9ZtbJ52Q==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":660198,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgV7MGCRA9TVsSAnZWagAAYVgP/3lBUyRmpKTZItAxbJMR\nb/EqgMhsHdUdIySNX1u38b+hIQNBJXeC9CEMQG6w/8xxDKF+6eU5PpRfQWZv\nzUtvuko4wv7zQ4UNZzddlX7ewCjirFrsOF9rNR2Ig2I6RoF8RSTJ9aOf8jpE\nNbpOb584hJ7A7WttzWx7qjQDGhKt8K+xVVZcCDGK8a7SuC3SBMOZ8Ar5SisU\nBX5RZRZLsrJnOclDeqkgBYCsJvqsVW76jLohrQ/P4KfQidrI9+5V/CHooHsd\nmRkH8CZRNXpVs4+qRI4+2FVkJkYHF9U/U5eqEDgnPyfpgljmooNcy2Yyj6O6\nB/JyC5NO10zBbMeyQjgeDsAed14Oq0ts0xDAbwHKX6rtUdJfJWKRZPhG2z4L\nKftWisb9e7soIxoLqU5QQg184SO4r4sgF4mJi7+4XKCf+1tIrJKwsC742zyY\nH0kzRw5QfoHTmKzzD4d2Qnc5Up3qYeC/yWHkstO673Jc/qFr0eL2yxVEwDSW\nXFVU3UfkLzMYoc1QrHKBlTam2XtmPPsHG/hyVVYAJgf3ANO9aLwLswyiRoln\nwGeL6cym2RWK1ANbN5IIolfUDva0J5cys2OOVV13ffJnyaKDCBXChHgjrk5B\nkLxWnx4skE/zBCbxxtMIjWxAgcwoWAPjkrHXrdRljxJbp7fJaKR4dUXltn89\nWlY0\r\n=I596\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d0d8c8da1158cff79a659271696412544151fcb0","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.73","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.65","@balena/jellyfish-logger":"^1.0.54","@balena/jellyfish-metrics":"^0.1.121","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.2.0","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.112","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.6-renovate-non-major-d0d8c8da1158cff79a659271696412544151fcb0_1616360197565_0.3882965630225359","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.6":{"name":"@balena/jellyfish-core","version":"2.12.6","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.6","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1f5e75fa11752e50c1759b567f649a1bc72b1d7c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.6.tgz","fileCount":100,"integrity":"sha512-8nNvUOyBXBqiw+cFu00RvpiZ7kCcQHzd7HvyHMuI2LShaevHDweQb3AWWm9BUeLnzXp17lwUJsob37wiah9o4w==","signatures":[{"sig":"MEQCIEAQuhF+1S5qz8vOFXsYxEhhTV5ycf6i3/mZ5wkCOiRGAiBLI+2XtWP5BLER1nrxjglySbCjaDAcUrdKm7g3URJINA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":660138,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgV7R9CRA9TVsSAnZWagAAA0EP/0CCaYyXWZs0Pk8AARyR\nrXC/8/SGixjfOLsdXCkstxjT7iuqMf7ywTWld278uTChBK7UeDhLFGxHG9Vl\nwXk5PTh6ba9FMmrtnVsQZfMQ1KYn2pW9j7wVOP5lTsQj/R2Ps/xYx3tAX08+\nuRxwsACPobdrfgQr2irzX1UZGeHgGSs5et0ZMu8a1DEKGGivLWs21nuPFu/w\nIocXeKIEwd1FOOcmTRCdf2o0xjdFw8L5txdLVJG15eKcmMj5z6gZUEp4P09z\nMtlpG0ejH+0//z2YN98hlVwF1qd5/dzsX4CRljnuu6bs5QqoE7y99sVLp9nJ\nuORUzDcUPwfG79V2BpVVtUSFhse0aL/H96MdP1n18Rtxz9pFpt7uyQO7qRSx\n/Ggmt1Gw75z9IW5+/b5mGoiOzET5GJhgvs2/9Obw3+cuSYybTETeITz003ij\nbQgGsKr2C5niuOh1nBWAmTXqnqazcahKu/E3+ijo+f11MP8Vo546zuEFqF26\nwlLT7o7E9SCTYql4qLkTVD63lW0IhfOtSckvynKDvvx4imV5vt41d5YFRceO\nXN2IhGT8BGi61hh+2q9RfHi7SO9m/xdNkg1KOg9UfmgVeIgZeE9jzu+KPBYO\nKKpZdF+mMNFzZlAQg17pH14ox/JW6YY16+zaPSCzhF/TBCPnnlTjjpXfXuSi\nESub\r\n=5iPI\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"44e63481957c176f048be2ed3605560f6ebe49f6","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.73","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.65","@balena/jellyfish-logger":"^1.0.54","@balena/jellyfish-metrics":"^0.1.121","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.2.0","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.112","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.6_1616360572893_0.8371865220807815","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.7-renovate-non-major-a64d36a0c11d31443307aef469235516d8f5ef54":{"name":"@balena/jellyfish-core","version":"2.12.7-renovate-non-major-a64d36a0c11d31443307aef469235516d8f5ef54","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.7-renovate-non-major-a64d36a0c11d31443307aef469235516d8f5ef54","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9b8505f51a41096597e06d3212a71a3c1e6dc793","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.7-renovate-non-major-a64d36a0c11d31443307aef469235516d8f5ef54.tgz","fileCount":100,"integrity":"sha512-Av1g27xmneHW/5r05M/9f3QdzKlxgWipHpCDzOOSJIt++9amCVZEdnk+hI2b49tFdth9MRxKK8LIKg/xdX1Vcw==","signatures":[{"sig":"MEYCIQC2EkwYM5NeAj51tVGqytzuDx/j6jkUdjMXUQditxrnswIhAI5w5nR+4y7JNnoszXHVISPSMZpYeejLmmJI3XK+fuCE","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":660547,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgV9MACRA9TVsSAnZWagAAC4MP/3g+cI8CBg4db4UfzoFs\nzsl2DFxMuXhxPT18CpABK8hyCqg6wUftHkROkIsGx9PydgzZ7R0ZOa5KqzvG\n4pR//e4ydoyKDR887cb/Zyp19L1VFyIu1uPGLTCWnZLdUO6GXl0Ekod6FUqx\nXCF1NW8dLBlu//o+DSaEL0CwFJ3Y6Kwt9yHaKBoBfImgbzLeAoedi/j+JXKs\nxRJ1pgqf2yBzBnjVbXf0O2nCFaFDBElHSaNTPx/ctEHD1wyRO1irMVylp/x0\nAhUQriWabopLIJrkEfrkudmdslb6+tRUOozW79SS6M6uvlGHEyCPBhfjB+Oi\nscLxZ9ZCaEbQ5Pe5BsSCyfrrMN2seoeKZ+wZGi49mMODe6T/BVyHx6rPC09N\nqNKQBXJrGw6rxdDYeB5WPQWTQsNYi7M7O0e2MpbX0E+NhOAmJrKtXUraYLnE\nmXcDgTu9T7eEoUr5Bz2PIYDxv6kUfeY7IKSqBWIXyCHhczgEjWW034TgXIAx\n9bTDv2tueWPG7G6K/Sy1eblNd7tthmydN7AnHXVHSiqOJdDJ+DXTBq862epd\nAz8KrLmw7Fap7QNo1w+YcAAKuAenzhGJSqFZMSnHbd8pKGDmCjJn5ogcLNKb\nX/q+twBaUa0wN49c1sXq0oRAU96UdEdCvsT6YuXDr9WvZBXVeDpRn7SakQXm\ncgV9\r\n=PLXP\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a64d36a0c11d31443307aef469235516d8f5ef54","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.73","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.65","@balena/jellyfish-logger":"^1.0.55","@balena/jellyfish-metrics":"^0.1.122","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.2.0","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.112","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.7-renovate-non-major-a64d36a0c11d31443307aef469235516d8f5ef54_1616368384298_0.7993256001436746","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.7":{"name":"@balena/jellyfish-core","version":"2.12.7","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.7","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"46e1e12814581b332832c8bf2361ba87c3701635","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.7.tgz","fileCount":100,"integrity":"sha512-WypOv1iMk63gfh4yBlEiadTSjOCsudU9W4ge0QZr+1/SvFILQv7QkbQGsoHZ5QbTHSfwL3Bscm/NrzUaBmnOCw==","signatures":[{"sig":"MEYCIQD1P2v2vPHZPIY6J4gHNGLvcLqw8nRbhutMlpqMBJxeCQIhAN7tQ+yVRAKdRJuNiz4ws9r0m3ELAFfTEkYNr4HYSt/C","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":660487,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgV9RGCRA9TVsSAnZWagAA4kcQAJy5mo4VmCYUx57lbwIw\nITCitQdSoSFxIWT7T3ZEDSLvgYZKLJOd8DFfmos+k3gzNl3Zv+30jLHluZO+\nGCtgavsfTrOnMijz3OKwK/xApoKxlikKp1trC+a3B/930Ws6KJMzCkWhzqF0\nZFdQVz3wtN+lMm31qfQgf6VBEwuqlRQIxz4s9M7vnYYswyii3A0u0yM6K80+\n2FmJAu4a67rX2Y2Hx6PllxHyp8VaIOtCDVTOsr1OBD2jT0Prf+aj7ZYXg0QW\nkGsDMkQHDQ54HqWiL9F4JuwEjhqq7yracH8EvNF6HzGokeS2inG4zoH3WBir\nkvis00J9CA2b0YsCS+udl45jroRu8oJs2ktl83/LBJHB+5ExtCDJ7WVzsfdF\nAwAjQpNYCTzHSfgbZq0cY9yRizlCQqeQjUhzgyMyZMEmUmXTEQZEHgdBsX3a\nDGwtiZKyVI1Xz9tu0SpKRTZba3P6nPEjn7OcQQVteZLXEbJqn/4gFC5hff8l\n1lSRXw5XxxmtOzt+Fl4v7pxANyKRLsR1fv6XWm+5ua/GxBL75C38tKHcqhcW\n23imZbc5jka6hKshZ/eTuDSR1H1qgEwJVy1t4GSXJgk9vzyptxGQx2cgFDqv\nYBw0AH9Hn5UMqHANcvx2OCLEiw0/6rUXloWPBBKP/qDNLpN8wPEU6dx7yn31\n0Lsj\r\n=gjRX\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4c69963e0ed93b2bd675196544ab496f7861be13","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.73","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.65","@balena/jellyfish-logger":"^1.0.55","@balena/jellyfish-metrics":"^0.1.122","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.2.0","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.112","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.7_1616368709810_0.18410108526892976","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.8-renovate-non-major-38852a566432bca645dc14263f5481428cfc79fb":{"name":"@balena/jellyfish-core","version":"2.12.8-renovate-non-major-38852a566432bca645dc14263f5481428cfc79fb","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.8-renovate-non-major-38852a566432bca645dc14263f5481428cfc79fb","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8a62b2dd2ba99dc30a8a1220f4b43ed4523b4cdf","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.8-renovate-non-major-38852a566432bca645dc14263f5481428cfc79fb.tgz","fileCount":100,"integrity":"sha512-83qNxATpvPEG/iI9CiF3REGQMgs70P6j9X5aNXE/djMrX9CJVRV69vI8xwJyV6R0eAmBUbSD7olkA53VFN1OMQ==","signatures":[{"sig":"MEQCIDi7LPHaUfQ/9eOi+SyQTfA93JEyk6QgTdu96u04Afz2AiABC9dAiLW1lfWdEzYizNC7I/3vUt8Uka/ysHe+Eq00PA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":660896,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgWB0ACRA9TVsSAnZWagAAPgQQAIp6nq2Ru3/dalCh1vH7\nXIUCTEtd7IU8rhrJnHnGJQZUR2UAhjXHiX7SgK7D6R/G0V7nmwrR4+WC1e17\nkiyxRLuzqbddXYk36xtG5dwwI2xOWw9V43OAdhKo6YKiBCUN8IFMvsPrUV1K\ntpMH1Yk8YOye6SOuTs/9Izy9Nm3Ckatg8vGKn7aqMoslCRpSK/bzoNLU5hTN\n+HcLuoLfCC2lc1HyoYybXkADa51fRVsCulazlBrK3oy2hPHgVY7MszJCjh7l\n+TsHwHg9Ajct+LjPKtv1WI1aOrjJaVKqru1LLTupJu2eHt5sFrrOHc6UPpjE\n6Mzsdo4Q3drgJUby4Yhk+beQcL7Kn62z/eATXeeZV4U6aVMLsbCf9pdUMQ7U\nV9IizGYEg9U8LV7uyohdIvnrWZfJ/gUKW5R3pcTuoDTtntpY5D6cnXBnDSvv\njTv7gRq5GK4wCFHKgFYZz4InH8A9+RcOuV3tEi4/BLZip4Eq1B1YJmrdZqHY\n0hG+kIwW/6OYc6MV7KeN3i4zOjdkD+HbIsoE/9NMfcVU+FWgToRfWDdbbOWe\no6xj+d7HZOzHG5e2/wwvPVDcqtQT4oG4GRhKeKBAEvEhtyF4y6h0snkT/yYE\n3UkTrtjzZlPcal956F+6wvJYvR0qYRqcJ8ieUWqWvegTjPaPVY2KUAHeL7JZ\n8/07\r\n=O8Xm\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"38852a566432bca645dc14263f5481428cfc79fb","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.74","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.66","@balena/jellyfish-logger":"^1.0.55","@balena/jellyfish-metrics":"^0.1.123","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.2.0","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.112","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.8-renovate-non-major-38852a566432bca645dc14263f5481428cfc79fb_1616387328247_0.39865852422864334","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.8":{"name":"@balena/jellyfish-core","version":"2.12.8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.8","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a462cfd09fd327d37820a955323ea38b428462a8","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.8.tgz","fileCount":100,"integrity":"sha512-EJOGX/kU/QWH8SKZHdsScxh4e8t+rM5JGzxZmJKLa8Z4yjAIeLDyRu+noNtIcPEdwtprsieI6+wAKpU9sIC22w==","signatures":[{"sig":"MEQCIDLSCHGPGUuH9xRFUpI0JLtwoV7JLvoWJwDN1qPMY+sTAiBoOpuetxKJFM9Hor71Adexn1o85hCkYUz3wLSp9oltgQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":660836,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgWB5ZCRA9TVsSAnZWagAALEMP/1G/MrPQBx5lCFLWt1/B\nMaLANMl9Zxrvog1pRc+PaGkmCrL3ewcGoBAX6opY2/t0vEZTE+grPIwSWMrL\namJWXo22LMVvPZJavJiuI/g62AtnbBTmKcilxFa3wzTROu/n6y8PG+gXF9EF\n91sJnFZHwQq8UT7FFYRCq2ZBgXQttzdQ5fFWGJxuwEYsu4bU00PxHZO/t+o3\nJbuzZwd6UMmNp0QfYFTn9r1bFH4PJLrPO+T5DH8LVmzENqbt0vGDxIeRI7TY\n/a+E+RGnaWZr3YECVwIMnCSHxBt5wxgPflzPUtAluLBCszvZ0yk3s2thFBW5\nsFeZdiv2iN8MkiTQpQwwz4936ML7/Jk5M6X9MfuKB/jYfS+8Ub1SCz8N7Xg7\nWgTdV5cfy/pgmNLXmwwkUWvADOg8Ud+wJU6M+MmfSr0QdiVtEvftFzXnNjTo\nza43fskMieSBaFtMuZdlCO8P7aVZNoBo4GdRTV2JxZ6UJRM3x0PXT4Ox0BK6\npLCHCBamaAMPb80YgIuoFT82mPbsXinNvYAbdoGTkX9kBv/v0IrUn9NIoL+B\nIoOd17i0Lxq4s3/M8U6Aunphol9lmkAGr57uwoGu8natEbBV6NBstdO3ENDg\nkP2zsA+rRcYlV9jZcfnbISICdhkJhLO/Jget4O/s8b7+ZsAginKi8PNIY9NP\nYGFT\r\n=yrmP\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"5a78fa0e7b15d694cb2c1dd3990520c13b40b265","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.74","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.66","@balena/jellyfish-logger":"^1.0.55","@balena/jellyfish-metrics":"^0.1.123","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.2.0","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.112","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.8_1616387672585_0.7445995825981446","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.9-renovate-non-major-8575625438e8218d4be6e9c268186c8a99613f9d":{"name":"@balena/jellyfish-core","version":"2.12.9-renovate-non-major-8575625438e8218d4be6e9c268186c8a99613f9d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.9-renovate-non-major-8575625438e8218d4be6e9c268186c8a99613f9d","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"38023fb9d970eb37c91986f6f75777dbf40ce2e1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.9-renovate-non-major-8575625438e8218d4be6e9c268186c8a99613f9d.tgz","fileCount":100,"integrity":"sha512-wSxwd73QPXNWIeBCG67v4ZNf1rNEVRaQe8eakN6tFRHDpAmd1XnlKCAutDIYnzFgCTmY85UpASWvZsJeVWACkw==","signatures":[{"sig":"MEYCIQCxPjX1TvayjcCcwFPbJps9ZkqkJPHFZRFssnDIzHQWOAIhAK5420Vq8+fpIrfVz7N4zw0yxPff+5MCtJ4WwLcljDvJ","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":661245,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgWGP0CRA9TVsSAnZWagAA4yIQAICVgicqDzkhFP3Nul2l\nPtTaVQjBXWaY/BGGoiodGlZQwiI58NrkmlOQ3Mo/ainc+UOrJAESGg886TlC\nZScfQ6vhrf74R9AHVOcAngVDhTYcJg89u0XrPqLXr/1ybdHblAo4xpUm9zpl\nVCFjIqxgh+W35HO2IfrVrQhNbGQ/NWutj3sT4GuuC8KHVFdOVitGrVXdmclp\nxWw8DuQXeO5+K+xLAafsNVysiivnqoiWE5ZajCWma8e2D2rZESHNmm4vfyXU\n75J5OQV9n0Srh+7erHc28FFKfqDUStStxivw2+gua2bktMTCxMPCq+D1VyCr\ngLAx2mG3FS4DZ+Uf4lOuUDaxQdujZBy9IGe+8A2oS63NJk/BdbvM0kI9a6UL\nEaAY29ZaLcpxTGHQqDlG4RuEFnGRUSC+lrdgfQEIzGC9FvJln8ZDTml0xS62\nYNqZI8O4tmzq6o1V/QrH1BS1+MtlqftCPrFgYsqM9h5ozyJDiFXzk0v2Qm5G\nMwZW91frs6pLjlAaP8LOUIHxC4QxFn7E3PtfM0pEI8WlXr9HJvAFYr25rfyY\nPqoY4X4eXOiNre1E/Vxqmz5K5wytSU8BkwOvVgnGmyYg7JAP0FwnvFfxUTqw\niburHrogfU5g75yKCrn2sI4l5T8qc+zDcaA6ITsQJH1WvQdAbaY8+LHh5Dbo\ntJgB\r\n=QuqF\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8575625438e8218d4be6e9c268186c8a99613f9d","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.74","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.66","@balena/jellyfish-logger":"^1.0.56","@balena/jellyfish-metrics":"^0.1.124","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.2.0","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.112","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.9-renovate-non-major-8575625438e8218d4be6e9c268186c8a99613f9d_1616405491969_0.31514308303803085","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.9":{"name":"@balena/jellyfish-core","version":"2.12.9","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.9","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"22d41a56057977289d9161f17b6a5b8c8f260d77","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.9.tgz","fileCount":100,"integrity":"sha512-MDnF2m5fpJE+tz6jrfAfi+L3jf/m+rpbcE5dNFeh1wUhNjCX3jpJ1FSUM3tBpbH78GTDu0NO7hVeUkF2DTCmiw==","signatures":[{"sig":"MEQCIHTKrYtLnjnNJr7j92zIgReCYIs+JY+sRkebCGP7nefPAiAuIbniWbfByMquirusxYKIQemRquuk4uGzbt0y9qiMkQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":661185,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgWGVlCRA9TVsSAnZWagAAh0kQAJGJxQylK9X76hhzWI7q\nXVkYLUGcZqRgH1OQgsNcQEm67og3bipBcqcFZ4AS1xkYIKtQhGAduhAhcq94\nSZEe0zWTRviigaEQP0TA8ROK2Km0j9e0Sj2K8u+jMID1oapPRv+YRW9xt2c1\nc3YE6g/BTn/0gvV8h80tbSsKB8rQ+iuEYPEfpWi4PhM6Zexte/82K/q/5EBx\ncCC16sJBnErXGpt1BDTInVM9PGAC/zFN2qqIZ2tl8cVeyS4qnZGm0lV0H1QI\nk3iCUECAbrEQbaI+vlb/Mfjvf0WRVP5doom4KjuZg33sLyxn6+n60mfj4V9D\n7iJK6v6WT8/HHbgk6/N3ZkCjmOU4X74zqIlF6mM09XwLWfzBSLbBwCHT26Br\naLXH+ebV5/9C4soLQ94JXzXRfZa0RAgNwVC07o+saJoai9/CjJ5gkZuz+FX9\n0GWgdwJHi2yOsLVIzWN2VSWzj9W9x2nbKhIErXKiVakQZvLIfyOR6eO0p+hX\nRFXN09FfnDaL7eptWnlpgDU8XMIs3t6I9DKk6dMpxNSdNInMxwJt5+2kxL98\n9XWZfCiDYmKbuYKenGwmG9+fZOk2niAJtYgEoDG4aJeRo+D1lN8gOfjMtaZN\nvPkAhonhmFWccgZ/6coZCEq/T1UEcGDbtKhPTQSHigIn2mLM+Th1+JlWKMk5\n5bfc\r\n=KUIH\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ff8561b21048a6dceb4d6331fb39181d5be8a68c","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.74","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.66","@balena/jellyfish-logger":"^1.0.56","@balena/jellyfish-metrics":"^0.1.124","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.2.0","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.112","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.9_1616405861293_0.2683354483138598","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.10-renovate-non-major-40691470b4d981b7b127eb54dd186a951927a120":{"name":"@balena/jellyfish-core","version":"2.12.10-renovate-non-major-40691470b4d981b7b127eb54dd186a951927a120","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.10-renovate-non-major-40691470b4d981b7b127eb54dd186a951927a120","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3020c97c3f9c81591adcf7c1bbda2c29cb52ea4a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.10-renovate-non-major-40691470b4d981b7b127eb54dd186a951927a120.tgz","fileCount":100,"integrity":"sha512-YHP+nWc/jSC/f4T+h4CiB8ymigMRbX5QEjtoSvO6UbnrOk9cvqFw/6YsPaGjo/0kaSMJj/hB1fSSIp/SXqGKOQ==","signatures":[{"sig":"MEUCIDxL1PRVdl3mGg15EfpFZoU4HueDHRnXN9YJ2UW4+ZaiAiEApVEex4PcSrHMR24aPI/PKBVByJaUFiKlZCqir3l6h/o=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":661597,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgWIWPCRA9TVsSAnZWagAAWUQP+wewJfJtjeuoWi0uyuY/\n9KfjG41ERyd+VkGTDxfhcFuIO9VCqdFaLP0Wc0nrdcS02/GftV4C5pTiPwmD\nLv28flqArKwpmraXHRNu4XZfP55Thl5y1TtEhAjvBpf5zXvbAd2UcS1L/7v2\nfGE0MBJvTImQT+nbefockJ4E6nsbfhIJ1hSBKIjC9LevYLTDj8kPqsU92UEF\njKQpwJhtGr18UcBQJQEwzBQpJXMGFEl9+yom+OglUGwB9QgByRS4u7D3E2YD\n5SHZdija1xZLobn0LPA4EfTowFWxcqZ8vBph36uCKNQ0cBGZpQxQKf4+07Zp\njGDd38R9WHYCXGLCm+GKJbeUn6MIaWgZ17+u7KwbY2+MRtjKDw4G8E3x1kzp\nm5EHNzilOZN1jf/5wbGk3vN3KUpAERa/rGedF1sJ7TMwOw3wjKjz+MSnD5e+\nNdV/v3VcPVXgRTXDmEzGJejFu/p+kk8pNFgB3hHSIegXzShPWhZZjU7mmEJZ\nVJv2JqfZZ41Q/UeKXIoLocJ6gksL2f4/Erlx8sWkeB2Fci6hCzwYQf8lEkmj\nxLiGOFzfiC9Pqtvu7eX+lDZUtkzvlCBkUhTCK5Y4D7oKOdcWNAX33kUai5O2\nITJIZHwS0bRUy2IR3x4NdfvX/NLfnwxW30iuNaCcNWNtHzktSy8Y31diq1qQ\n/GdC\r\n=qmJW\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"40691470b4d981b7b127eb54dd186a951927a120","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.74","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.67","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^0.1.124","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.2.0","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.112","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.10-renovate-non-major-40691470b4d981b7b127eb54dd186a951927a120_1616414094840_0.7754184193934326","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.10":{"name":"@balena/jellyfish-core","version":"2.12.10","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.10","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9c21585295f8cb940927f9750e45a3cb11e441fa","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.10.tgz","fileCount":100,"integrity":"sha512-5wHRtrzzClFC1XGPjpX/iU6Nge5glF4yQuVsfkBJkjEE0syaC0b1GkmO5r6Hz6+EMRkYmfmCnRHsFFt2HTfSjw==","signatures":[{"sig":"MEUCIQCClgPsJ4bNG3rZZMtDqNLjWp8j9+S9Gkj33zS7tTZK6QIgcDoctX7opSGUl1/NHT3F/HOhI+tuunlDtsIaHqJEYhA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":661537,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgWIdQCRA9TVsSAnZWagAAMk0P/1UrzhsGGPgVOjQxOl9U\nq6tnSNqepYcWzYjpKTFF0q4CIdYLF3jB1A9YsioynR2ojs8uq3En5M7RO0JS\n97xnKdb2YWn12x7rAIO3xbZKpAhSr8Laj8a6/AANjX0asj7VmBmv7HDgtUCL\nkWf2io+hMgmkL3YdtlgNxFQxwpUYQKD6zt34U9rGVHidm8O7OCDz0HFhEPQF\nNeohP7fJm8RLt+xo9fu0DYCPe0/riBQQjbnT3RN5vm28tUwJjRuEO8SUsA+H\n8omhjdhFfcNNRCZHrWjcLqO7M/yW3gmcfCPJQyI+n1y6Moe7UHb8InvtirpR\nUl+skxdjMYmnXoQ6NAh+wytmq2ZLNg60kXYNUjlduRvb5rfO2y6nuGalW8Gx\nrsswVIu9WGKLehuV+fTotoQMxwcxlXhldACA7IJTVUVn9aIBNLvxz/CM3WWP\nW7q4j3usAADSlLCXf29BGmcYqYpiiFsW9mG/hp5tzrUMSkMZj9BnCM5H77r1\ngIvSaQiHclzE5dRXanJyAurxYeQchH8Ju6jbgBCbn+XYjRnK7zNqPn6V6YK3\nTwwdW3gLcIsc5Y/UYGRN4SC3dvZTcUAoGDihQCxPjya8ZCajs99ws0hjF2Ai\ntjM8mPgTWQqsnqyy7CM9l+/MM7vpZDnPe1xnpegwHut1j6aBNEHYE6RDeaio\n8ggA\r\n=xnIg\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e060857ef2a123c102ec6e280dd40bce7156623b","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.74","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.67","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^0.1.124","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.2.0","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.112","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.10_1616414544135_0.05915173981500921","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.11-renovate-non-major-68c1093b2b0b0e99c1d69943142ab3c9420c2a5e":{"name":"@balena/jellyfish-core","version":"2.12.11-renovate-non-major-68c1093b2b0b0e99c1d69943142ab3c9420c2a5e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.11-renovate-non-major-68c1093b2b0b0e99c1d69943142ab3c9420c2a5e","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"882dc611ec5c77bf5ad2c644c1c3c378cd37ea01","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.11-renovate-non-major-68c1093b2b0b0e99c1d69943142ab3c9420c2a5e.tgz","fileCount":100,"integrity":"sha512-1d4BuPbxfiAKp4MAeuvUHKilTJOV4ixhN1bEaWBkyzmpBKPHLaqqO0maW3QxR4zzSZzNf1ncEqS/fqiCGCQOJg==","signatures":[{"sig":"MEUCIQCsrQJBx0+IvRYE/RWxW5hOv/1LB5Q7oF8rHwUkv93TawIgTB3WB7SADDoc/JZqQWplwXqMhgbnowcEA1NfsgaW/Ic=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":662026,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgWJ6ICRA9TVsSAnZWagAA8bcQAIE9Jp0jZIYz51NWmiWA\nBox0aD/0CaHl6hGEP79df6uQ5U6U8T40i2RmSQzisB1Z0l9K0h0VfUOUp+PP\nlDSjpgnTQ37PQHNje/5hfujXB/jBvrHKeYAPGjqls4Z4qpXwX3s/HsfTO4qv\nqcLkwuGMQHIl37h0/KzluI/XN9cmKUpNllZr9d5BHpkCGQDO5wmCLeDDyACc\nHfviY/Oe1quQRVKo8OHtbalLtjAF+39Z0dv7F0x3GDkyescdZsZh+cSCOq7C\nIA4K2KP/79Q1GsBoVDwDXIzTlv3roadhUXlPnDizb53pw1Hy5pcD/zz76Ycb\nm4H9VAyJbpfTvXRuoRDMwnH4TC7l39KnzuteKmVrZxXL1Um6yXCNe8KHf1W+\n5S15PP4BZy4gcWIe7Y+bsfXK2WfF0R6lrB+RdW8uVA0j2Hg7xh8Inue0s74S\neQfNBK3d+zCMpC6See9W6cVsaKg6rLh1rIXUMAGs7hphkgLydh+h/JZOajNU\n7mHUDYvlf9xVxjRpqmjUCSDxjGPjhbwJ61w6gpq/g+78/X+pOMViSTF46lqE\naPc2KNAzKIijsCxRpLl0Oo2bM9e6AvmgS4b/MMUm8F6GpeGz6c7FBcJiH38X\nRInlsVGAJgtOLfghSkbVnmknk/GgZjGlm4VqHIQyVoynZfUTTIO5E9zNXVFo\nqb76\r\n=IvAr\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"68c1093b2b0b0e99c1d69943142ab3c9420c2a5e","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.74","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.67","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^0.1.125","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.2.0","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.112","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.11-renovate-non-major-68c1093b2b0b0e99c1d69943142ab3c9420c2a5e_1616420487764_0.2936080605448208","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.11-renovate-balena-jellyfish-logger-2-x-675b1730c5c2156efcebd2b77571fb3b0cd77c7c":{"name":"@balena/jellyfish-core","version":"2.12.11-renovate-balena-jellyfish-logger-2-x-675b1730c5c2156efcebd2b77571fb3b0cd77c7c","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.11-renovate-balena-jellyfish-logger-2-x-675b1730c5c2156efcebd2b77571fb3b0cd77c7c","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a49cfb12ac0f519a7a0eb60f3ba7babef0849452","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.11-renovate-balena-jellyfish-logger-2-x-675b1730c5c2156efcebd2b77571fb3b0cd77c7c.tgz","fileCount":100,"integrity":"sha512-gaa6LNxwworGmxDu1pw93sI1gzArnhquemOPHoghwibHvZthlbG2YS3pDUpTk8hjek4ACqXesw495B2IkaaERQ==","signatures":[{"sig":"MEYCIQCj8O2QnCDhwLtHk5kGxybXBSGAQrRLWfgrLItsBpDBVAIhAMgr4yaBsADjXcxICwcQHwE2FDzPyDskv7p4p7rPtXLt","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":662029,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgWN7iCRA9TVsSAnZWagAAaecQAIoaaKIie4voBsY1Yne9\nr+52BtYtfvAkdVTK+sCrYOsC8KlAoTJBvcSlDT5Q0TMDpkmlmQPRGqbEAseT\nV7ilk4vLxx43Uf137ctk4P8cMDF9Jm0QYUY/JWfpyHvYdX7Nhl6ctbboo/Bd\nD/OjEy2OzfLdF6oex4FmM07z5RgjqmKhx+GrDB5IJeQTka45wZpSJCNN2fKq\nqNiUbsMcO1ZUHZuAvDpe/mVRXAY9WyjSwiE3VPOmRApzKhIoVS1bMuswbBzd\nANoNx5ZOmq+8TSyJ+F4CAhhuGBn6VQaJgm85kjqnSkbhwEFdAC8+sFQzf9nZ\n2CmBZLkam9fiwAqi91UOTcBz0sK+pA3F4oIsfmB/WGCl/1KHjHu1idmkKln2\n/RdHYCu8bZosdevpY/he942OgVO6OOE5S3MVtqLzZvHWP0jKjRI+SilAG8Cq\n4+lVtXP5RNiVIH2DnpoPnUma/BUQa0TjGvRsmrvBuG2UxSkj6H+yzwBkgb9n\ncUhB5+LArMIoHZneyIaSsdvetkbt01gbCl8eVCYOdfFiIs0GsPMKZAKXuj7i\nn6kqzt1m/Xxc23tFntL9L0PPGgVIAbzVElmV+237qhk4HeueslzdsZochyC7\nyIjALP4pXr76y5Z1NtjJwXqtdXxfiOGDB+tPewRHOhG56kKXXEZm7DK2pBcD\n550U\r\n=CwbZ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"675b1730c5c2156efcebd2b77571fb3b0cd77c7c","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.74","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.67","@balena/jellyfish-logger":"^2.0.1","@balena/jellyfish-metrics":"^0.1.124","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.2.0","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.112","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.11-renovate-balena-jellyfish-logger-2-x-675b1730c5c2156efcebd2b77571fb3b0cd77c7c_1616436961913_0.584892366100747","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.11-renovate-balena-jellyfish-environment-4-x-3f8facb26d0ead8be819218df4c58595e46c5ffa":{"name":"@balena/jellyfish-core","version":"2.12.11-renovate-balena-jellyfish-environment-4-x-3f8facb26d0ead8be819218df4c58595e46c5ffa","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.11-renovate-balena-jellyfish-environment-4-x-3f8facb26d0ead8be819218df4c58595e46c5ffa","maintainers":[{"name":"somombo","email":"somo@mombo.solutions"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e0afd478e7aec1e9168304bb425ca5fe3843f33a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.11-renovate-balena-jellyfish-environment-4-x-3f8facb26d0ead8be819218df4c58595e46c5ffa.tgz","fileCount":100,"integrity":"sha512-71JK/NRCw+xRX9eDuFzlj8zNWPb1qEi6D/Rj0XEJvezvo6rC/I87KTqjMz7pP0dYjpqsUswTftP9IWPzdsio3w==","signatures":[{"sig":"MEYCIQDGBAJYd6jwttT3PaeBrPTX2EQ94D+2holERZljHRSZdQIhAPLPeTMfJkb+jiKVKvjCvqdTgOR0SGQLCleTDQ25bJaM","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":662045,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgWUXtCRA9TVsSAnZWagAAiUwP+wf45sCWCbBET9qkIq9l\nNwIiNPTgJ5yQEQnB9UekM9i5QaTVNdBGxbSfTkZZUCrtD5B41Od9GxjjLCkr\nxZm1GSvYy7eElIc/gLOmZdt+cyNCt8jAtGNZu/uaHA1pBBp3gsbpTexdSiQa\nMUx3bjyQqMC+6FIsLNMz7ZBWnBusvxs1UG2RMFeFbJwiIQAr3x/MSKMRlYAV\n4maL5/U8jm7rzM2WvmvkZhwY46ipI2uPCnjHStgQmqF61QsevDOja59/bF/P\nzLulPfL90XCne/gl0L9EAW3eaS50Pbu0YrA1OlvuOy0Fa6F0dW+wLEd60XMe\nkd8PGA6SQgTs7oTgfke3xQG0pkwd/MSjca2b/aFQ04Rbvlk3+e9XukupU0yb\np/9WxF28rRl5qvjifr5n43RWNlJ0PVG6HQSUj++VlDprj4oo0JhafUb4SQKu\nVS8W+mBf+MDC71jVFloPsD/hDEHSzDLFSutwgxXTGX9VI/hESncjFizPCMIm\nc9yNA+k/55PPmBp6pELn7TMrQ8rAXcHFDn+QiHQOf7OWTCDtv7fWKnmT59aX\np98FEhK/bqGSoUd0nZyyYgkhgwBVQvsq6iabdNvVGKoIu7CYK3YD5MHYVBJ2\nFNgGcYAM9BxzGR2HJx6fweYo00mlqYanyu8krUNAP1rHZSAdEs7niLi057X0\ntJxX\r\n=QdOc\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"3f8facb26d0ead8be819218df4c58595e46c5ffa","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.74","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.67","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^0.1.124","@balena/jellyfish-environment":"^4.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.2.0","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.112","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.11-renovate-balena-jellyfish-environment-4-x-3f8facb26d0ead8be819218df4c58595e46c5ffa_1616463340499_0.4389507077520616","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.11-renovate-balena-jellyfish-logger-2-x-c247a9d50d0b5a4dd1498d599a711ae4d3453509":{"name":"@balena/jellyfish-core","version":"2.12.11-renovate-balena-jellyfish-logger-2-x-c247a9d50d0b5a4dd1498d599a711ae4d3453509","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.11-renovate-balena-jellyfish-logger-2-x-c247a9d50d0b5a4dd1498d599a711ae4d3453509","maintainers":[{"name":"somombo","email":"somo@mombo.solutions"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2dcdb88c4cac7075cbe92089c1643ea94a20017b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.11-renovate-balena-jellyfish-logger-2-x-c247a9d50d0b5a4dd1498d599a711ae4d3453509.tgz","fileCount":100,"integrity":"sha512-Tj4d4di0WaMrQQuU3kgMstebinB3QqvuJfI2rU+w0E4KRnSm2AlKqUy0huBWo36PtDpe+8i1+gD/DYLnynPyvA==","signatures":[{"sig":"MEUCIQDJRxu9chcDBG6xEUbEMDlUa4uiAdX6K3GDD30T8XUTtAIgbSL2DvK9hIG1GqhWQn0jB3S5eqblzPsVPc52phdCD70=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":662029,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgWWSMCRA9TVsSAnZWagAAY48P/0AgJO34ALktvWo/AdK5\nBB5riia10A28vYL+7G/HCpqyRojuR50QgcQrdoCGk05wLZGUCjvrN84NUQiP\nuN2Vhb2Yx9NGU1TgkSUd3qEfZUPwJ3bVyy0fKUxEJiGTA2gk+LyHjd/K4wXv\nWS8R9O/f4OMnsJ6UcUtEsMzX8yax3UTq/0nkBQJg2kVRLx2vpNruh0eGqSWh\nXSy/ccB0yCjTg7B11Tl+GKZby8iJw6dLXrKimc1/5EM+W2a36eT/UFrHkJOf\nyIjsHnjFbdZl/eC4OUUDerl/19UMR1ql++CRx0m1Hm/hcwTwgz3PWcdEHQ/N\n+05NjVXz/lLy9eTv4xKcjYHF0iZhP+aEnoVRK1X5jMq+r0KMSt1tNN+wUUYo\nQr0Qf6cjPRTNLoKXNgjEZBizaelrl3rSWP2u8F0dE4yCAiRpUI1TYQZKKTE9\nwI+WWfjddQ7jKzbU1KSM3Ixq/ornxGFaS07eaIXysxRXDJktclqodQZdw9nI\nWbFZqLGxFYV8secg8zQWe6d0jFgMWqUMycnAqka/eLSxfXGLDunFQ8XzGqR/\nd8d6RnVxWwAj3jDRNYWGOWQFy2RgRkp87mWbVoyPRtLfK/pES9elKgmxK3Gk\nSEBsYt7+YIUnl7ra/Y/rOk5p2e+jQYvKxEjEooxonemCXPR83lNnCqLlDk3A\noPUs\r\n=gwE4\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c247a9d50d0b5a4dd1498d599a711ae4d3453509","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.74","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.67","@balena/jellyfish-logger":"^2.0.2","@balena/jellyfish-metrics":"^0.1.124","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.2.0","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.112","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.11-renovate-balena-jellyfish-logger-2-x-c247a9d50d0b5a4dd1498d599a711ae4d3453509_1616471179923_0.5304562186418964","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.11-renovate-balena-jellyfish-logger-2-x-2f9eb839664a93ff83219f6820749d800b3ddae5":{"name":"@balena/jellyfish-core","version":"2.12.11-renovate-balena-jellyfish-logger-2-x-2f9eb839664a93ff83219f6820749d800b3ddae5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.11-renovate-balena-jellyfish-logger-2-x-2f9eb839664a93ff83219f6820749d800b3ddae5","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"991a0e1d3be95fadc5dd41450987098b41d6ea2c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.11-renovate-balena-jellyfish-logger-2-x-2f9eb839664a93ff83219f6820749d800b3ddae5.tgz","fileCount":100,"integrity":"sha512-K+CK1Vwt4BuLmapAa2xexWsCXpnBtXk2GeoGmx4fy0zy73nbcXgrKzhaNFOIdQUv4YHafTo9eyAOUZjKCaHViw==","signatures":[{"sig":"MEQCIHMBlHfYbkfL2bI9ctd4hRcW31TR38SoOv/LjQqvex+ZAiBPOITGDX4cIYscp1PsNVgKZzN2SxoU2ai/hy3Vd2mhwg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":662029,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgWrPKCRA9TVsSAnZWagAAPO0P/17cwvr0+FWxpGJkluBh\nZLFVCt2qKnVKlVnmWhIMtYAueNZwaNH6PCBUuyrk7aXMVWuJJBhbr/a+X/66\nznbIHs+JNvtaqb3ctZE/CTM84xBzR6/EDdvpbxRB1FdvGopAdjb+TFm6xrwZ\nu9YA0z/4nKViEB6vG24v6YP0UGgSRJF/J4t5aYV88h3nhNY9MKLc730i/xp1\nAhCaaaXWDoa8KleV5b9wpTOE8E7wI/lVHoNovNSKNdXuAvRj4tKKf54spEaD\nFD83qWgkv1/1KhR/AUCKF8YZRWIf2yV1Cvmj/pcNzAAprKKYuKGGxlcG2rrg\n7jmVKAv0D4MvvcmKM32og6tUfH9yU55VTvctWqF3+SOFpeGjLpSp+22KH1GE\nEySwc7pmghIn+uSjeNEuht3e9j2IuETUNqalm5r7P7zWPi2utnx7C9UL7aTN\n+OAq9tciS7Kw2FSwCmH5gLs3E47Zt934YDAkVw+3jOXSVWyGNIs8f5vSESVL\ncPnR8IIqeRixYHaN+4bEDsesHQYf1OjqmYbzljdaFJSv3MIstYfiUurvaxyr\nUpEy47Ep5Iss7KAP0HqQVzqBvaKRd3tVZpBjxyYHpIfhiXXMR+q+N/4j43Yx\njVfLYkF428ROBclwgzhWLWaZV3J5AG8Ub1QOsZV2VZvCFm7qLyXkSLwSAyAb\naCxH\r\n=Pw+z\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2f9eb839664a93ff83219f6820749d800b3ddae5","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.74","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.67","@balena/jellyfish-logger":"^2.1.0","@balena/jellyfish-metrics":"^0.1.124","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.2.0","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.112","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.11-renovate-balena-jellyfish-logger-2-x-2f9eb839664a93ff83219f6820749d800b3ddae5_1616557001751_0.6089314975575211","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.11-renovate-balena-jellyfish-logger-2-x-9e7dddf4637c0466e1a3a3a2a122be42c493c1ff":{"name":"@balena/jellyfish-core","version":"2.12.11-renovate-balena-jellyfish-logger-2-x-9e7dddf4637c0466e1a3a3a2a122be42c493c1ff","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.11-renovate-balena-jellyfish-logger-2-x-9e7dddf4637c0466e1a3a3a2a122be42c493c1ff","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"895f7041dd0605656106c643278135210e8b2a3a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.11-renovate-balena-jellyfish-logger-2-x-9e7dddf4637c0466e1a3a3a2a122be42c493c1ff.tgz","fileCount":100,"integrity":"sha512-9dEW/TK1JqyfwQcexbnC22YJjZnqDDfn1I4msfJL2XaHabGICN1EkuK9XE86Z1V/K4+yfo7XbUlJUc5xBTfK0g==","signatures":[{"sig":"MEUCIFdDz6/6mviF90xaPmm+z87UqNJChLV/NlK72niWxnYlAiEArBkmcHpvrmYBVpBBsVezTqszbm99iBNsHNOyMgEJcOU=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":662029,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgWtCKCRA9TVsSAnZWagAAR+UP/1VItQXLf5su9CmrTeji\n4r2q0fbAym1b5RwHnSzH1lOT7BBqrKp5YO9lWHuvmTmAmFNhCVrmNzpHaUoD\nxIuDthWUjpExgoDhkVK6RH93YFiuuuGnUBH26csxj5lfgBhY6mmeBULsmMcn\nmut3v9JUjAH2qquuOx3vfl3p5S1kLi9B7zuGLRy8ebnv9MsZS5Ok16qF/CIF\nTT5WbgjnBxXRYGqiAsTb1344P+ZhK6GDdGNOUOUQBIc871oa2iqnW7WUgYNw\neK2WqgP6RAhZg3LNHYHJIe3qT5XatNsvaDEfMwhfYUS/x62PqeIZ1MXEM1l0\n3xf7cZzhSNHCIueil6xSO8yCpcIvcULwHNUPEtHaRL90JUPc57YK/4QzoTp3\noiDKG3PDoSKzLZtIzb0spPVBIgRg4L6uWYKlXiHwoxpLCExdbArUdMBjgdmr\nMK5CbN/np3nAzF8H4lJh00dPNdE3ElhjI712XZ5ip5azUgAn+hdS3sas9fPH\n/xLYQpuqmMs3YzFdsVgiYgo0YE4hYqViFm06ra2tzG5zL9+du4IQKyRMOavJ\nihG9hvcwOtMJb9KZBN9GOLHDuglsRdWvE5I3naQqYca4QC6zHBLOwe4yhq2x\nPaU36GIY3/J7mfvxJIC9jr1w/4w4dz8+ZVc7IqUVipZZ6Qy/tEOOqbBgHl5v\nhP8K\r\n=tHBo\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"9e7dddf4637c0466e1a3a3a2a122be42c493c1ff","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.74","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.67","@balena/jellyfish-logger":"^2.1.3","@balena/jellyfish-metrics":"^0.1.124","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.2.0","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.112","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.11-renovate-balena-jellyfish-logger-2-x-9e7dddf4637c0466e1a3a3a2a122be42c493c1ff_1616564361706_0.9865000006929265","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.11-renovate-balena-jellyfish-logger-2-x-8f4fdc74740009ce2dbbdeaf4c2890839fcfbefe":{"name":"@balena/jellyfish-core","version":"2.12.11-renovate-balena-jellyfish-logger-2-x-8f4fdc74740009ce2dbbdeaf4c2890839fcfbefe","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.11-renovate-balena-jellyfish-logger-2-x-8f4fdc74740009ce2dbbdeaf4c2890839fcfbefe","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2dfa24534648d6069136feaa9ba277932b912508","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.11-renovate-balena-jellyfish-logger-2-x-8f4fdc74740009ce2dbbdeaf4c2890839fcfbefe.tgz","fileCount":100,"integrity":"sha512-yHOHheNkq3UTyT/LU5qPinTdN57792LT3eLlzq/LWnv7WGrWdWrNLFs4SIs6n8CZb543KXVGJfURV9X7Wl8Jog==","signatures":[{"sig":"MEQCIDUg7RI/YajNnrWy62jIaxqHdFJqPzC5xfcFg5PIS44bAiB3+6aAFq716EtlzuimnztyJ2xhRREV4H6NvUyvaiX4Rw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":662029,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgWxiZCRA9TVsSAnZWagAAMKQQAKNFWDlkH9ZTtR2Vj2+Q\n4JusRJ2yXPlf39Wawmlc131E0yw4rWQMaUWQhL/6IVMXPeAItxXQOwckby2R\nlCdDB20cRgi1MwJqs4YJeKP+MyTMlvxCgiuYmqHHQG26nEtNaECAxrt/gMtY\nLV2Wbh42uRgK+z9QSXKdU7aKTN0i8kKtqc5G02Xqw2VOO1zVpQl6M3tUVEhD\nXKP5abID+o2v1jQ9qxxb443kMfINuGIV2oTXBfXKN9qPWowkG7EpCiyamR3N\nLc1LW5gLX3lSJs71NhVDI/bzVF8ekxWuaqDOIKtVpqd+OOim+uCP8eHuKwmd\nb5++9UD8k5Iss/6L5zqZVoNPkD41dWwbpi5KL2N+e1SIIreccIHesDxyekvv\nXqaX/emljmxnX0Knb8fNbgV2CmZ2EXLBSnzqud6Bp0iXce/R/jiXrqxS5EUV\nDcL7LqR0Z02/fXjDKviyovVQGdbMiU8AAk3+o37IXumsQAoywIZSs7IOUOPI\ns4pdAqpPy7f9GR8RjgnbT44Rsa+aovWaigRrj7t3IpbkQhXofwwhSSykuZaT\nxOv2OpNUUA+tsgYG0HdO7+ZfmhWZcKbkA5MYtwVWGgEECUj1vtvUj2rYqXos\n1EHt7OicMOKjk7ZWDwtyeCt2X+a51NArIroqgRJ9bWj6lb6TgfdMtVkLhwtS\n8QHc\r\n=kFrD\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8f4fdc74740009ce2dbbdeaf4c2890839fcfbefe","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.74","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.67","@balena/jellyfish-logger":"^2.1.6","@balena/jellyfish-metrics":"^0.1.124","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.2.0","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.112","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.11-renovate-balena-jellyfish-logger-2-x-8f4fdc74740009ce2dbbdeaf4c2890839fcfbefe_1616582808825_0.4232705878259293","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.11-renovate-balena-jellyfish-metrics-1-x-40ec0069938950f71a9f4f868240e8537bc3b24a":{"name":"@balena/jellyfish-core","version":"2.12.11-renovate-balena-jellyfish-metrics-1-x-40ec0069938950f71a9f4f868240e8537bc3b24a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.11-renovate-balena-jellyfish-metrics-1-x-40ec0069938950f71a9f4f868240e8537bc3b24a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ed4a7f0af1aebb33f96814e0a540c8494100feec","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.11-renovate-balena-jellyfish-metrics-1-x-40ec0069938950f71a9f4f868240e8537bc3b24a.tgz","fileCount":100,"integrity":"sha512-V8AhAIfHxWJEuYoD5Vlp7Al9kzJv88ZX9TI8M60Yanz0/9JUIKJogP7zjApV3iqidzJ/rfnATUJsuduU66f+Og==","signatures":[{"sig":"MEYCIQDiPli57hX0dpV1eaS29oa5Zb/t+Ur3tZIJuOBIlhJz2AIhAMKkNXQ5DzIb6QCgkZ9NrWFLq5nc+6i3ScEgNcoolk4G","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":662031,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgWxjlCRA9TVsSAnZWagAA2UMQAIVL9jxDOnqmkCOOsL+z\ncn6n0YVGt/J0i9gice/jOyrTCPLRRr8DFu7zEccMXsUAvYroXscqqtn61HQL\n7Djj4jJc/sWj5GkfdbXdEE6TuFleOCcxAeiwVTZLmlIw+aR86MjeFwsXe8wB\nqc/zLQAu2pQZ0fP0WwXIrD9mTxFchopFS3vk/lOS9If1uvRMQUU9yjfRKXPd\nMD1Hh4BHRWRY/B50qx5FKYsOh7yDrgV5QCwpeTVQ+djCSldZ+np5ftwv2rnR\nKrFVJWT4tmAnVp40wMqk0+SrsQwobGczwTPwn7ugMv8+oW+NVTE7qlrO1abn\nBGZOuUKqwTD7D1k0yMr5/4eW0vdJrgu+j0EmuEFX2oKTUcOB7czNCYb4OOwq\nnLOLdzeC3jrYrJgAD8trpHdazJ6LjFkYitEZYFLCmDLaxdagYVKH6s99iGzT\nf0YqujhMPgbe1jwbrLnCXIW3pXQ8Etjg/0skES/7/h6sKmtep92aAvVO4ySt\nP0heEcdkn1VGH6vhK7xDkwY9a+4M7vzyvmoEpYAY15JcSZwhAQ35odn8vRDy\nir8NRLSACQnq9hB+nSAEo6wxjSEwci3LWaAnuVvrIKJMcUQUVOFpVdxiGang\nRbq2mVHG3kVIaYsA0wqlHqA4VRlf4B0ZBn13Z5Z83fvT3T6aw4Fjz0moHi9i\n8NUQ\r\n=diQu\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"40ec0069938950f71a9f4f868240e8537bc3b24a","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.74","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.67","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^1.0.1","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.2.0","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.112","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.11-renovate-balena-jellyfish-metrics-1-x-40ec0069938950f71a9f4f868240e8537bc3b24a_1616582885163_0.08731330468034071","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.11-renovate-balena-jellyfish-metrics-1-x-81c114611dba7211521e5af3c3f007f2c7139746":{"name":"@balena/jellyfish-core","version":"2.12.11-renovate-balena-jellyfish-metrics-1-x-81c114611dba7211521e5af3c3f007f2c7139746","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.11-renovate-balena-jellyfish-metrics-1-x-81c114611dba7211521e5af3c3f007f2c7139746","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e3a719127c2230aa3ca73204049c50012c3b824c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.11-renovate-balena-jellyfish-metrics-1-x-81c114611dba7211521e5af3c3f007f2c7139746.tgz","fileCount":100,"integrity":"sha512-VV/0SnG7ksG6kN6niqQQVz2ICTfgFqur907BXWuBZJt2VUT31UlLpKsoIlT08wovQtrOyZbfaT5kAd9alc5vYg==","signatures":[{"sig":"MEUCIQCbBhY/k8vEDmam+U5h1YkiK6KxOT9byAidH5QYGcEJ/gIgFSNgNH2EjkWerDwmoxkA6Ho605eNkueVdgiQTkYZXrg=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":662031,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgWzpzCRA9TVsSAnZWagAAzkEP/3X0SocEpfp80qDO2lmH\na8N5tIgszZlmAfgRoQqpQAscdj9l4QMFxMhfWk1QlltdaE282V/TJR5sRMj3\nDCgyMxFgz9ubioSYnqGtj5AfFu8HrPoL7jLeTbSW4uzwNme4UgwVFTdK28qi\nrKxAgp+ZVNfCwWkDeuXGMktrQujzvgGhqVzimgLbsLZGcvLl4c0kNnhX08p8\nm4p5Sy/855yo9nxhLvW7AH+B2wMyTBDgRNMKWAntxn3i68GmfXb+PAcVsBMk\nu0tDOktxhcHSJI0pgTqiBC557u2/HVEqKGSy7hmkng/e1uA5TxvA1Dh89FFW\nEIbz3OQS5ZlOId+mrmmt/GDTwWAbi9dWs2NSC4U6a0+QbXHK1QWw3NY3GAs7\nW0mYyx+oNltnUsbpe1D3tpTKG/o5RXLRfte4maKVxLVoALe+j58Cp92hx7EC\natbMYCvO7KBoVCN2CtU2O6qlYXqbrWNgAm7HGoJieQu6RZg47+HViLrAE89q\nsmTedd+oHmhpSgAL9gzyBZzcm37HeDonVF3TaODf7h0F2aKeIOxxsiztYz3k\nPGBLvoB0Hk4kqjfqYpyBleuBaHe21l0lwfGXmEzirjg1l8cGqFCSpfze3WTE\nc47JljPiDCa91Y/Pn6enhtBP9lqitmf+MUSgwJ1ICq/CTMyMwETk6RaoCbcH\ncy0/\r\n=SJfF\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"81c114611dba7211521e5af3c3f007f2c7139746","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.74","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.67","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^1.0.2","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.2.0","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.112","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.11-renovate-balena-jellyfish-metrics-1-x-81c114611dba7211521e5af3c3f007f2c7139746_1616591466529_0.3127412137251444","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.11-renovate-non-major-b5b9ad91ec958d04232cd28395b63c5864098d7b":{"name":"@balena/jellyfish-core","version":"2.12.11-renovate-non-major-b5b9ad91ec958d04232cd28395b63c5864098d7b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.11-renovate-non-major-b5b9ad91ec958d04232cd28395b63c5864098d7b","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a9b03d06fa1f2c16f3595c660525ed28fa47f426","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.11-renovate-non-major-b5b9ad91ec958d04232cd28395b63c5864098d7b.tgz","fileCount":100,"integrity":"sha512-p0C39Bi3wIu4PgEhSPWS/igMvFV81EfIof7EgQgx3/aq8Xn195tyEYfl0P7OA7bjtriZRJLTreq6dJFpS+YKJA==","signatures":[{"sig":"MEQCICoRudnHhRsr8+ScV8tPJd2GNNDp/MaCxRtRdshmfEddAiBEm7vM1roehhK8cmOnSLM75jYaAgk8ucZdl+/oY8jmXw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":661948,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgW+cKCRA9TVsSAnZWagAAPhsP/1BUmHH2Bs92xH/K0Woo\n/SChDI+BqrZluDDp3KAcJDG+/ho6SgNvNovW0WR4yi5DpXJp5Uu7DiEt2V3m\nIQbqKuYMVuHctzKaE5l2GuhsQHhPba/P8MmsLBdtu82SssWrjschaDN1hwLq\nqlwJDBBwlrAoHhz4eWaz4CW131Efwvg3s/UJBAb8VJ7ZurYwXgVVFAqnKEcc\nOxAkAeN4+agRlxVoVBHCmuuEESZVRzeS6Uhij057GyoHeMLRlDxCR618zPly\nIOleA7UZ7YBt86wHUYOmvkXcSvbVZB/FpxA6IwGD2wSvRdqrtKx6QvnABvI+\nIKvlfcjPDL2zWPzM/N6BVl7CPvAK15oY0O/kJUr/WHU3dJ4eNxdkDC1Sh8Rd\n4jPIyFVgFyQDdWFl9AcSUwkN5TOBFiIhot3LNFOW8iHesKflY+dz6YymbSHz\nO8+4t3T2tZFGFeELX7RbI8OvUzOCJtx8+Dd07HoFrDAp3FhUgx7FMHbpNbOo\ngIs1IiEbB/+CGfgkBioBEN52CGRQ2aDdItrcC/2D5fb8/NnxUlMWDEpiHCeW\njTRGng+2xVqC1TXCvzw+EImAF6accQ8M+mKophvHTIDQzbfJJ7abtFYFCrV8\nBD9e1D/2VYuKKse/935o373JBZpYUflPBMo6lW6e/6GlpE9mxNEze5dr+YuO\nRUJM\r\n=rM3T\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b5b9ad91ec958d04232cd28395b63c5864098d7b","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.75","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.68","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^0.1.125","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.2.0","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.112","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.11-renovate-non-major-b5b9ad91ec958d04232cd28395b63c5864098d7b_1616635657483_0.4783634598235662","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.11-renovate-balena-jellyfish-environment-4-x-15944305233e6e95c6d63d8bbe749d28b82f162a":{"name":"@balena/jellyfish-core","version":"2.12.11-renovate-balena-jellyfish-environment-4-x-15944305233e6e95c6d63d8bbe749d28b82f162a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.11-renovate-balena-jellyfish-environment-4-x-15944305233e6e95c6d63d8bbe749d28b82f162a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"af361a359327e7b9585752e1882e4f5bb4ff23ef","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.11-renovate-balena-jellyfish-environment-4-x-15944305233e6e95c6d63d8bbe749d28b82f162a.tgz","fileCount":100,"integrity":"sha512-gzbtx/5j93EniJyAh7/FOwAbumc1gcyroBEIZ+MKx2v42y/Nv8Eg0FdkeNT640L4ZtlGf3gGcpHODfqqlTHwgg==","signatures":[{"sig":"MEUCIA41f+rRwkv4vTU27gYo05PlqQV8MhthDkawjTEtcIrdAiEA7ogBFIfOgphtIf4sR1raR6wRCmajhZc5RTMKYAffLlA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":662045,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgW+ckCRA9TVsSAnZWagAAq5oQAIQDvDrhIdUfATOB9Gdf\nrZBoYkQ7GKE493tqkCwNOgo0/3sq9EJMgwNbL+UxXTCUJ4exg4HuyIuQuaL2\n5JOnh/XbyfmpMAbcEtt1rBKnr5BfU5gcWxsWIL5yuKxs9kqxcdfEJyR/HH/W\nuYjxmNIQzFIHewn6qYUKuKHy/AEm9k/CeCBNK0IYiBx+LY207f/6bBZTYQkd\ncQJ3D1MX/ssu7SRqPB/v3RBPhwiIIjXGnx9mHJL/aW94vBLdQ7qNmgFPr40c\nspCMQ4VTJNsIxRO239ZUoNvLd36sjpzMl8bzdhcOUtbIgmWJMJXaeZROs4D3\ncKA0Aap4K4aBXLnlHH+h2sVxqeHawPEMsF9srS4Atp9ySjAkF/sKDFW815Kg\nEIEI5Xj+QRyRjQ/mE4j48bfhMOG9FRSPe5F81gAZ7uk9ma0IqnBZEh/emFX8\nX0ZxpRqQb+g1GEMIozxgqAvEHiZ2Ctwrc+8BuS038GQXsRaxy6vpeNxftNUE\npwbar3VqZ1sJwdfMWWt65TwdwYxlvocTOZ4nJCgbFwhQ9Xh93NpeZnCGEpl+\n2hfC8TW7y6yFJRH2hP8q0MaNWqe8MgUmerJsF+dxnLFzTYwQZblq+KYCAjY7\n6dtxw0bQMopOiglxpalBxLtGv3XSM0KM411ye1DswFHM5fMlFt3Oj8t4J+t6\nwlBY\r\n=8bxD\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"15944305233e6e95c6d63d8bbe749d28b82f162a","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.74","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.67","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^0.1.124","@balena/jellyfish-environment":"^4.0.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.2.0","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.112","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.11-renovate-balena-jellyfish-environment-4-x-15944305233e6e95c6d63d8bbe749d28b82f162a_1616635684306_0.47906375911969623","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.11-renovate-balena-jellyfish-metrics-1-x-e3e24e5d18f87edff0cd5ac18e2c78a67ee2361c":{"name":"@balena/jellyfish-core","version":"2.12.11-renovate-balena-jellyfish-metrics-1-x-e3e24e5d18f87edff0cd5ac18e2c78a67ee2361c","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.11-renovate-balena-jellyfish-metrics-1-x-e3e24e5d18f87edff0cd5ac18e2c78a67ee2361c","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"44141713ed237558d5eafaad74ab7fe5258aba00","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.11-renovate-balena-jellyfish-metrics-1-x-e3e24e5d18f87edff0cd5ac18e2c78a67ee2361c.tgz","fileCount":100,"integrity":"sha512-fmn5fiRc/Jx8pkVRZD8FSqtHXCdTgUL+maqSJZW8lnJ/3MHTQSF8nwZtlIGmgeuo0bz6ZWSkUbGDgQxxfPZHiA==","signatures":[{"sig":"MEYCIQCM+rGFDKizE9+F2nwTTTRWXiMzGJMNizkJtYXYdWt9HwIhAJqnb0ILICi9Mbl8/Qe0hPtSF1IZoBwhZCIFxaiNcZks","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":662031,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgW+hGCRA9TVsSAnZWagAA+tUP/jT8X8f/FDnqgjuIHMlx\n14vpX6qEv0D7x8VwzWO9bVMUUulR9xslnD5M7UWhLUAVKAN1GLTzCSfH/jZn\nQUG4RlsgblAKy2WLYB6orf1oAhh9jdM9hcdL+UeaITk+sV81y9RwHFG4p0zs\nQYPOOXllUGjmccRpCx57AFfZntoKy3pnzFRedNRwpNcBx3zRt3Il/p3sk5Tt\nf2foZS0MCcaVm1/f9BwUen5CC3biran53bLrAx6QNKxCku7oxFA+5SIg8qsr\n/QysnLV8H+ZQIFUHl8493AjcCDDQNmPQUdHfTve32DppiFnH1f5lm+i+YxvQ\ntLG0nSYryDqJCjVkFXPwyxWRpPRO7LOcEBT3ZPejfhpHT9+/pqScxhfShqTf\n6j1jqFXGI8VvS3YGp6XWcr/YRdnHYWx0IObEy9qQK82j8Lp/V7DdQaa5WC/L\nkKzBI7wbooaPNs+IghUT0t2JhwJBt5AWhED9/A+M7NitUP0t8NcJJ1n0tyxJ\ncCZxWd6mknVrMuPGUIv/H6Rp95BcnfBWWVOBB+Gid1R3sNRTyImROuQnf6on\ni391wFZNBfD2/ZG8N97H0dOjnR6NLE1hljU8ucykIKyFW3dl4vk0a5MHtQ5o\nMhfRwka1W/C6JMw5XBw6yyugtvwUE7N0HrcI98S5PKxQZ9maRLcn7s+pwIyM\nIXkj\r\n=Ur6N\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e3e24e5d18f87edff0cd5ac18e2c78a67ee2361c","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.74","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.67","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^1.0.4","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.2.0","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.112","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.11-renovate-balena-jellyfish-metrics-1-x-e3e24e5d18f87edff0cd5ac18e2c78a67ee2361c_1616635974274_0.6240192095666812","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.11":{"name":"@balena/jellyfish-core","version":"2.12.11","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.11","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b0411b4305b446d45f00dd6fdcd0dfdd2a51b56b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.11.tgz","fileCount":100,"integrity":"sha512-ZBMlBmyLQhdUI+fIXa6eTHNMQEIrm+YtTofb3GSG2x+REIk7nO9VRxn2h4pKGlmmbTcyN/dfFwSlKRIZS6LC+A==","signatures":[{"sig":"MEUCIQCqD3ng95SRTs0fK+QSRHkFBMUKhLG1e529KdaxgpCMhgIgOIkdZhmAH9ewXdkehevHfbU4gtsPj03I2x3RzAb3T28=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":661888,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgW+jRCRA9TVsSAnZWagAAsusP/3mWK8OQbNUGktGodzFJ\nJTo4jApJ9zM7zljAm8ftj9LDC0oLGAp3CNxLNq4m9W69DqLmFaxkq2NNZS+g\ngVUN2SAzTlWqYVgECHOI98wxxwAWCLvoM3eJOe+uMkvTzRCjkCFSbZgUYuta\nqvhJrsRpY6Q7BZCtMe648h9aOEqEyydZKfNvP+IVeDKnu99PbstRw4RWFS5z\ndP5OjZJPN9lBWWFAMJ8SGqzaVHjJijRmDU06Zz5qLjueRAFxqDjUsHXz5LUh\nIiRH6Qbmz5QfWtGGaQCsbyXTzEzdV4TE1yCtzUl5Max+ABWW4jjhVPw1Iyfa\nMGMauvQb3pYS+WLpvyDLnZubF2YjN/m5tH5ItgYcwSGXEdKmOFYBHVikhoXD\n6ZXjVzuCL8TPmV4f9hY5dMbrOBH+b0LFBetlR8TqsHBv4pgnE8Nc8nw7Mxfn\nyxfNOv9/hyUlZ2KQfId6gcS8PoWRLjC1zkWgJqnBgCP/4NmmyVwCRNllmd/C\nT5MygL621KOoHRxTF0rHW0LAXWFnS4hB4yjfNR8HJMd/8PtuCJgH8N3t68sY\nf5iTZP8DFnuV9MScoFaXKKAi8Uoetx8sTp3Tk7QZYMaSoBoDL8n8823ypKzD\nDMeRxaKjG7APzvrL1WFchdXmIHhvNt3aiU/hdqBO19QGWSVUuRSrlK0J4qLf\noliH\r\n=JJIU\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b625c3f7c0fbae9b3996d5b4ae71c17d94a38e30","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.75","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.68","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^0.1.125","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.2.0","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.112","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.11_1616636112828_0.8707949277302087","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.12-renovate-balena-jellyfish-environment-4-x-440f3138ad7f067b0f9dc864d75fadb832afb487":{"name":"@balena/jellyfish-core","version":"2.12.12-renovate-balena-jellyfish-environment-4-x-440f3138ad7f067b0f9dc864d75fadb832afb487","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.12-renovate-balena-jellyfish-environment-4-x-440f3138ad7f067b0f9dc864d75fadb832afb487","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"572151ec13d5b8277972f94854d8c649a36c3614","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.12-renovate-balena-jellyfish-environment-4-x-440f3138ad7f067b0f9dc864d75fadb832afb487.tgz","fileCount":100,"integrity":"sha512-0klvE5dJ899q/cEXaRA9epqUkO0JkrEa7mRpkcQsWUgUP9ODVuEHigbiWL4bvo5APLqY3yAsZWwH8cgqiuelDw==","signatures":[{"sig":"MEYCIQD6fUAdu3VvI0jKhRMr9HBzPnhw00LEGf2K2gXH2W2xcAIhAKm6kgA+gbc8YRjhf49awhLkeo0bzXjl/gsOMQVeHkfY","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":662396,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgW+nlCRA9TVsSAnZWagAA+0sP/3zpef0DV5l7v/w2lkxV\nRggjpsuoJifhcK/ut5rAyMOzpT0WjXh7qc/E0D0Ep1yU72omeFkBogGuIyM2\nsHcqkBk7pkLGGN2wznIbY2EI5dt091ePFIXMZI+sjhRn2QdYaONm+jZ70CX7\n0uD6yNsJq6pQ34yB1Zzz1cVuxbVHbzL2NyLYy8FNeOoMRlo/IE/Hz00k605l\nHl7heFJfJi3m63E07eE8e7If67d3IR2NcNlbDYWdX8HSNpiZgWFprckbZGqV\neL7Cf5a1txkodzEZtxYZFqCGRNyh+FKQGbl72yXiqT0ip3ZmUr44F6jeYTA+\nleBVoeVWiveV/dvTlytwBz7mrbY8SsBr4btayCPKiRP/azytZpbU6NUpeHuw\nuCS3bRM6i9WkqI8T7hy/ZYOEQ+W3s8uwHLGbtYDXEoxwHof93gzGUiPqLlVB\nen8wAojhIyx3SSCQoPLt1yuJpBWePs9lBzKOHjW0G7gUON5LuMGr57yf9m46\nB/VPC+AmMYWNc9cjY7DBZEQeZBQfhD9DRfhw81TIsWe07RqIcigb+g34UHxX\ntKn50yDgdOBXdlmYgr/OaiZuZagvju5+RupEcuQfswSEV+gvektDqiWnyYLk\nGHzW68BpzpGFKnaopXVaCnvhOhO3K2m88C89medBX0lVsg/YyWp6WMyLSVd+\nUMdv\r\n=Br2I\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"440f3138ad7f067b0f9dc864d75fadb832afb487","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.75","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.68","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^0.1.125","@balena/jellyfish-environment":"^4.0.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.2.0","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.112","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.12-renovate-balena-jellyfish-environment-4-x-440f3138ad7f067b0f9dc864d75fadb832afb487_1616636389020_0.26353774419215337","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.12-renovate-balena-jellyfish-metrics-1-x-6d0c8863d39a66b90fc148ac5b385a6ff127a530":{"name":"@balena/jellyfish-core","version":"2.12.12-renovate-balena-jellyfish-metrics-1-x-6d0c8863d39a66b90fc148ac5b385a6ff127a530","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.12-renovate-balena-jellyfish-metrics-1-x-6d0c8863d39a66b90fc148ac5b385a6ff127a530","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"89002632e9706d5fcc3e3884aba130a0291b0bf0","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.12-renovate-balena-jellyfish-metrics-1-x-6d0c8863d39a66b90fc148ac5b385a6ff127a530.tgz","fileCount":100,"integrity":"sha512-vE2LMuyZo9mt/K70+TT6tXCZCLJN+Owv3PmDtzLDeDzlxi6Q6JpG1osXyZtc6WFOyLSJERf1TGHoy6hsDZWAew==","signatures":[{"sig":"MEYCIQChV+Tehz5jk8S7os3lTfWi/h4sZawLikD0lphxWQInGwIhAJkMKeuz0zBZ/HZcTg8XRN0bhuBEep6cmjUmfhMIGV/0","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":662382,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgW+pPCRA9TVsSAnZWagAAhrIP+wQIvVoY9WDrArMMTpv1\nPAcnkgMwV8mo8pzbSdScEP1eIEPcAO75hS/PdS4xBFNstYCEctSepCHusRDt\nSoUa3zuXElwATVvw5m86yMIE2H38IzTZtjHIBV1McF4fw9OKvUZVhNSNlHjG\npK2jdf6Z7yptLJCN68VTD2nSjy43qPoBGh3eY8hjq/iepuP9rQBfVFCWaeny\nURZBuiQ5mS98P246MUXGdF+eye1YskIwNgB7UkGQA+zjOEOVp+4gWVvxt0fI\n4RgcsR2iTDo4qgC961O+qS1B9S8NLsRqBgf1ygrXvQu0xFzE5yczleX0t8FM\nhLJ3exFK33vbbIxicvBzpOOqjwQpaBv4ol3uRd3JN7XH/0K3UCTaXks28oA5\nLBHfNz0j4ms4u8Sfc+Y5yfvHA6ucHzbRCQshaE1rBLsGjEIzj+bNnYfcYOBZ\nvw+S1yOZ7QwJgazvXhK3EUCn763xtfvdAyD0YYKUT1cDbjnYFKWkeLJLPKen\nfyu5vvo6wW7KO4KZbpv5AOeeDAvGAZvL+aolassSZmpuTH7pPElk+qCxJEus\nJmQQMlwB9YZR2xvwR0fRDP9prJ1wVjDSWNw+EEf/SjQFnNDXsS4n2LsNxJfT\na/c4Om4x2EFV/gmYMDggpwkg1j8BKun4HvuNVIh5iZxvYhjF3pCNMv8jvAl1\nN6UV\r\n=6kni\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6d0c8863d39a66b90fc148ac5b385a6ff127a530","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.75","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.68","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^1.0.4","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.2.0","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.112","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.12-renovate-balena-jellyfish-metrics-1-x-6d0c8863d39a66b90fc148ac5b385a6ff127a530_1616636494403_0.27818728076194876","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.12-renovate-non-major-b0a9cdf254ba36d62b0d1b960b6fded5a479aa85":{"name":"@balena/jellyfish-core","version":"2.12.12-renovate-non-major-b0a9cdf254ba36d62b0d1b960b6fded5a479aa85","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.12-renovate-non-major-b0a9cdf254ba36d62b0d1b960b6fded5a479aa85","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"387105415d46980dcc8f959d4e056840f9604db5","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.12-renovate-non-major-b0a9cdf254ba36d62b0d1b960b6fded5a479aa85.tgz","fileCount":100,"integrity":"sha512-zzar3gAsQy/scZLsj4+hxPb4wVcSJlWpY/Ts4hbsOxdzobgFPb7EczfLZs6tptLHY8+Y/oE9yPnxbTz32xIQxQ==","signatures":[{"sig":"MEQCIDKS8//gom2A6ozjFaTZLIIt2PcqotJQ/YLOPVpQQndiAiBJbSt96rOXHIbSfFJCcoK59pDaNjPSjN69wosD8kQ78w==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":662299,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgXAmTCRA9TVsSAnZWagAAGPEP/i9jXsWNGOqQhFiQ4dyM\nhfo9DzX8R1SMwb/tSVNudwI0wRzB8/GqOS0dAy1s3cnIDytCePZEM6hKgPDa\nBLUMYSdGqkI0zMDHbu1IH/lK4Y58s0OB9yE5RPNcbwvetx3hi0In8tnaUPuG\nHC0EIpuUe/QwsuZ4qCdP4GQNpfg+IsIh9ojmI09urH5FQhRYMz4qqGbp/X/9\n4UHIgDPmOdsQASmvcFir0gTj+Jz3S0aDpedxMHWL39dt70YXG1Nh59NqNMIH\nlKM7Y/7YGkHnXp6/qcHgIO9WKM7RVtkQNeIxyelrjTfu5ISZhicZGih5UDpo\nUkYJBHTjuAUDH0aQoXk/5yTxYE7rUbcF6qsv24OeHXf6MOgw9yA9TERmxjgr\nec672PKk7+9zV33/XaewvKs4rF26ViA7xSXv2qYCp1gECbaXJNpXXjwkPDg/\ndCyaUUYvoPrl9FnpwCT4Z+t2qCxIROKYRm45IhKCyThOgXMDrXsX4IMS/Qxc\n8UzFpyHo53LnAJ8wxsCiy6UFEjEr7EF3ZnKhVPcySYYiHPyPjgDnFo7ZxDDk\nV5SYuy1lQOV3YVBHbwtOfVN89wlDGGkvrg3m4cRtjRtg/M4iNFUse20C7YyS\nK1GKn4N8dVFip/Eyw7dCjKoQQyir7q08DtnI/2P0QDh72oRELtLVNAMPFcmq\nHkLi\r\n=AYeJ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b0a9cdf254ba36d62b0d1b960b6fded5a479aa85","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.76","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.69","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^0.1.125","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.2.0","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.112","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.12-renovate-non-major-b0a9cdf254ba36d62b0d1b960b6fded5a479aa85_1616644498506_0.1964724529330737","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.12-renovate-balena-jellyfish-environment-4-x-c1c4eeb1b8b98e4b2059ad2e7b36baf47b209e1c":{"name":"@balena/jellyfish-core","version":"2.12.12-renovate-balena-jellyfish-environment-4-x-c1c4eeb1b8b98e4b2059ad2e7b36baf47b209e1c","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.12-renovate-balena-jellyfish-environment-4-x-c1c4eeb1b8b98e4b2059ad2e7b36baf47b209e1c","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"26e3aebf78c60cb5823f25e0ffd24144c2110142","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.12-renovate-balena-jellyfish-environment-4-x-c1c4eeb1b8b98e4b2059ad2e7b36baf47b209e1c.tgz","fileCount":100,"integrity":"sha512-MApXFeJk5dwdesDybV6agm7AwPUGdcbiK6QUNQV4PKjLqZRNWtGs29lInpPTYQVlPR7oYVgeoEs23XiuJnKF0Q==","signatures":[{"sig":"MEQCIDj/nj4iM/nZg8M+0gvLRsSzkEy12OJlDAlBLzYq3HZcAiAbTccAjhHkpGa1vLieBAyWjMa6Mcg4RxmyGKlM0od90w==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":662396,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgXAmjCRA9TVsSAnZWagAABQgP/A2QZPbFg3rwJW2j9RzQ\nFvkP3NwoIDf+OjswRDp1ttUFokydorjbqOKhTeG5sZcLq6GhJm5BTdrw1l7E\nOM/q0yNOTeKMh4iaMqXIg0dR8EfnMN8tYwqxY90iJlg3+DiWQVmUrYPpXdtg\nhljE1p2FRxGbBTT7XnzGlnwYyVfRi1/a2Wll3cspbcyBhC/egCtseEu6qdFN\nbNDBb5nv+xZVhUvs2lxEm/aWgv8fiz2x2OOdJtGu08/63hD1d6uGC9DCGY6j\nS3renZ/Pa08LYd+EX5HPUW0u0rbSPr2Wmj3QWjiUT8w+cI8PXPflx7qDiB3w\n0bptMGsY9zqij/Fz2kUhDzr0T5dOOZV8ZNGliO+r004AHkbCVwWO8jDkxKF8\nYv8N4fM86DGotcOTMYmlWly7idK8pzWeYhf7Fnnojd8m1ix4NruyjGb809pa\ndaqEQRSJG6PaMibP3ovTwXDfkeQl3ggeBCgQfxTl9BWIp26MRqUpdi1Wkc5+\ngFNw48+nHh4kmBxDzAJm0JcnCSRo2TXsVtcqUa85SSNsl0E57dgOAoOQpsBH\n+NrcbBHerVxUkW4lPISWbo4ljykUsLiksqFXqqm/E5ylCjqI4oT4dxsvBr3T\nihCfjlSSM1EW/NtSZ624rTfFPphoHg6xr0ktHIgwXQFO+5zFWYdrO7pAKYyc\ngzGs\r\n=uP3s\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c1c4eeb1b8b98e4b2059ad2e7b36baf47b209e1c","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.75","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.68","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^0.1.125","@balena/jellyfish-environment":"^4.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.2.0","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.112","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.12-renovate-balena-jellyfish-environment-4-x-c1c4eeb1b8b98e4b2059ad2e7b36baf47b209e1c_1616644514934_0.4567052963739848","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.12-renovate-balena-jellyfish-logger-2-x-8788913981f11494e384b1fde799d6d955d3a0e1":{"name":"@balena/jellyfish-core","version":"2.12.12-renovate-balena-jellyfish-logger-2-x-8788913981f11494e384b1fde799d6d955d3a0e1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.12-renovate-balena-jellyfish-logger-2-x-8788913981f11494e384b1fde799d6d955d3a0e1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d0024ede79b606f3ecb940e934c7b05d6886e08c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.12-renovate-balena-jellyfish-logger-2-x-8788913981f11494e384b1fde799d6d955d3a0e1.tgz","fileCount":100,"integrity":"sha512-KRlmZUjgNYzmu57gZMaRe7ddueM/tBge6MBrNz0Vg4+zh4mM7iZLNkEHr9i6XFYL2wqHdBCsNMJn40kyFNcj2A==","signatures":[{"sig":"MEUCIEFBYvd7vXihI0GFTsVxs9KUAEHyAu8LuBak9gyPgEBkAiEApAvL6GXSBrsBwX0a6Qywg2UUAeNi6WZI388PHOjTdkY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":662380,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgXAn5CRA9TVsSAnZWagAAzkoP/RX4ZIhlAhqOVmb4UvqQ\nfgmu/pzA9wfPWM6/C7La0L6/hR57BYUPbjxB+cGAJQ40anq16cDJeh9uaM4s\n1XWOldx0T7s6UvFBE63Bq+HZ0OEZXXmiVg+IqmVxILqj91Nwq2wCmEd72qvX\n3S9Hy2GNuKVrdD6mc7md0p4zvt/rdygZyxkPt9+AeuDH9POtvVzc3WG9jBZW\n33V4WuWmOKc1qGRgP7iq+e2Z+sIoCF4atB9NZ09QmoyoItLLcIxgaxSV9/Z/\nCm+kSMgSUo6S4l3pBYmpzm2OfzQu5qYy8OoC3U4qEfWrjyYFLF4GTMYSRQkw\nol+MFFZUsfM/4UnIT6WngxfwlY5WjVrzgLfc69wQD+n39Ds0hoK9UChAmFLW\nm4GXW3RHb0Mqxhp9oMIggKvE7k3cblSOKdAU2tDedXwFoR9qvrrFX6/3VTRu\nmQsoSwEBiKTi/UywEqmPqjTfUAQwF0FAZlhNy403lfE2lCco1F6E+r8LfwMM\nUJmNU9Ke6loHtwOF7cK4p0z3Lfaq5K62gMwshDKbUilxHKA3Si+UfJaR9HxK\n32fb+VXH+Zf1efbi56rtMyWuWoQMhdrskcaeE+R3DK6fvJ9mqKRW4FRMJnWF\nBr7Gi4gsVbsAZB7pB7TB0ZKksn25E0zm8YVyg3s1egU/9tJd6u1XKag2/PRy\nr/CD\r\n=PXrs\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8788913981f11494e384b1fde799d6d955d3a0e1","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.75","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.68","@balena/jellyfish-logger":"^2.1.7","@balena/jellyfish-metrics":"^0.1.125","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.2.0","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.112","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.12-renovate-balena-jellyfish-logger-2-x-8788913981f11494e384b1fde799d6d955d3a0e1_1616644601354_0.1747618004127791","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.12":{"name":"@balena/jellyfish-core","version":"2.12.12","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.12","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"dd7b47717771d80859acd6bc3605a7b65b96709b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.12.tgz","fileCount":100,"integrity":"sha512-30VcNY1V6Xw3Qi4iWBAYcztJahGQAd7Ruv4FNgaL/gptfgS+IeXel7ADRi/hYwB7og7DmQsF2ED9NDSTtLh/5Q==","signatures":[{"sig":"MEYCIQDfFCkrPUr/E6AUJFAeI4omCumvn/sghL95e8PAY4TGSgIhALjs1nVCaOzmtakPyi4x/hiBNmPiG+yca1Hv4rDAG1pY","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":662239,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgXAskCRA9TVsSAnZWagAA57MP/jtMeqKKKSpdrU9PjkW7\nUpH1qgOL+o2/0NlaGkSXQUGEHew9RvUM75e0k4YwaPZ1W6stNxOVihqXyNS9\nRB9pbgzcOAMFaPALd2D/Wa4w2JbIRkCn1ABk3v7NssDygBScPg13ijVqJ0DE\nCmXHO0FRhYP9kFWGbetOj98PkKSpzhosLePijDVqTWArKocHZva+eBlTd1Hp\nXhNVxUXd99MUp3VreXcYpNdjnxWXKVa8okMtR58GsJw2n6hzv+ZoRPc74SEP\nUbnHm46IEtzMMhCj+gHz55zpiamve8fwSERhSslyosRpXW6kMexmc9byRQuV\n91SFLS/ot3IGwTYpDeRVVOsq6sAPmaXE8akv6rD63T2yid/z641COwluWt+/\nJnXk3srioMQmyZ0Oyk3PVfYSgBmJYcq/vivI89faKiM2UhEF7MwefdCW8t8n\nGGANRIoQeGhcrskqCmm8bXLu5644gAJMjjv8IKKilHDaeSnVrwA227RtW+ZN\nvO5Yow4wnCsGKkW7a3qpLhNTqWKsqg4r2OHvdzkm9e3KvPWu3op9zwHtmmjh\nkmunMESCVKjfrwaczMpswvDsqtl/foAyNZG3Jnw9OnwdDm/lABU4scCld1jJ\n7OtOU0zPGK+k8R3DZgBJW7PnLNAWblCvPSCPHzgcNrrSPcksSHOFz2PtGxOF\nt+Xr\r\n=TqXR\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"bd856a60ff60ab3c0c7a60372b73002957a10ce7","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.76","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.69","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^0.1.125","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.2.0","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.112","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.12_1616644899611_0.3127652312231268","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.13-renovate-balena-jellyfish-logger-2-x-1258cdec2f5350388ccee8ae5dd0d6a18bf88625":{"name":"@balena/jellyfish-core","version":"2.12.13-renovate-balena-jellyfish-logger-2-x-1258cdec2f5350388ccee8ae5dd0d6a18bf88625","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.13-renovate-balena-jellyfish-logger-2-x-1258cdec2f5350388ccee8ae5dd0d6a18bf88625","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"174dd4818b16bd3044cb74ca012741bc13a8faaf","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.13-renovate-balena-jellyfish-logger-2-x-1258cdec2f5350388ccee8ae5dd0d6a18bf88625.tgz","fileCount":100,"integrity":"sha512-S/o5GJ5O2TYn7twBaWPOPCQfH9bUP4y2gn5QEbX60rSEF0WvnhrR8Qf9KJccs0KUHvSvcDZ5K4uvqSzZMwJPFA==","signatures":[{"sig":"MEUCIQDhdkX+ELunOFBwAacQxvDLSQuO6wMNZNKLCfmvejyfDgIgYvMn9bEJv1qymhROKjnmuJ3c54E4v5o/4ZrEdEK3uL4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":662731,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgXAwqCRA9TVsSAnZWagAAWB8P/0++2366g+Y0pSDLTAGc\ndKHyfnBpPlCWsyrl4bzBmq9CiLt1Pl6EqkbGwjHu/4ZgiV6LRW6W2yYf9xM4\neFaT9kcVfrb2NZ+ANvwLht8agriMIBjgdcc46zIfBRrlf+9XGsWFq2x8EAe7\nhFw5S5Mz61lwsUA2dUbfrxROsrHJCj5sA97ahfRzwSBKoR/UYNZZgbgTTrRV\nBKP+7bPRhLXBrrWjRm+kiLQTDSKFV0/sbg2yVu4o3+L20VShkB6doQP4M+PI\nuEBfp1LxGarQ+iIAix46agm552ItaOm+++lLFLZg02yZUIAAenpRY/9ukz/B\nTPukVz5j50ueqNeHuPFY4Orc1p3r87KTqUdjK62Z5PlHZ+6mSftnWLiv5QcH\n9OcAoJEipiTFCxsCqg13sE5AFLzbJnSSnZ4ZshilzDMPUa4jwA9VOJyyEuHz\nsPDa11XE2yBLkfZ/+piu7BFaFa5WWt6n6IOe+EcA8GXS06WWMVcIxL4ddSsQ\nW0nDANKkWRcjymfQQRXw4AbdtZfI+klH2jO/pQnnTM71lfTWA3Rd17r4OYXQ\nil5zqg2J7ysbuxJ7HLbyIuWZY93Va0Z6mVDoPBXFTFj6Ijw1Tl2kNYvvRIpR\n2Jt4bwPsKTZ10xLqqivHOUzwiHm5BrgryGu34ZWJsQ1vbODMra/bQJFSPHhT\nlpPn\r\n=2icO\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"1258cdec2f5350388ccee8ae5dd0d6a18bf88625","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.76","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.69","@balena/jellyfish-logger":"^2.1.7","@balena/jellyfish-metrics":"^0.1.125","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.2.0","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.112","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.13-renovate-balena-jellyfish-logger-2-x-1258cdec2f5350388ccee8ae5dd0d6a18bf88625_1616645162257_0.1369021896137328","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.13-renovate-balena-jellyfish-metrics-1-x-41ad835ab97058e758dd369926b3a4e00d6c589b":{"name":"@balena/jellyfish-core","version":"2.12.13-renovate-balena-jellyfish-metrics-1-x-41ad835ab97058e758dd369926b3a4e00d6c589b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.13-renovate-balena-jellyfish-metrics-1-x-41ad835ab97058e758dd369926b3a4e00d6c589b","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0cf72094a7574956f764885521bf8f3517cacdf3","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.13-renovate-balena-jellyfish-metrics-1-x-41ad835ab97058e758dd369926b3a4e00d6c589b.tgz","fileCount":100,"integrity":"sha512-Vb9GhuqUiWRrsZYlyxTlujUulbW6dIJvA27fVIZ3bgkSpokbgRjllQKYZOCuKgRa6f218pGbCSiOcnBc1xZuxw==","signatures":[{"sig":"MEUCIGUj8H4frJJSKxH0Fh6fAt43GCHr3CrX6vFE+iRdmPyVAiEA0FbmQIZH0Ffi9huO0+sHPO1yw6hgiKNg64dnR+Mx3NA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":662733,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgXAxECRA9TVsSAnZWagAAwvEQAImwudOF7uK/3tUe5HrV\nspMCLIYLMCntuLaY5HiUUmJenzsR5azI5oW2YfoTrpjc5goAsQ5UB4l838nh\nIN3sBpYvPclKWqDF48mqwLHgksnUh4kr3iUS5b5IQihpfHWxS8IEB1YrE57T\noBMAJ8i5pgzvL+yrPlbNm/9RyBCjTbbgrzxVpQDfgGEoZkZyroxCBw9MA4pO\nqb4nBlekEReMqmCdn5uU+DzRZdWDEZzrTeWN8v5AveP09/rA1nv7WSzqXGNK\nMx8K7dX8C/EJvHWMoOdONtCNDsWaq8M/bGa+EU6NU+dJI8VDcz7GXBD3jBpt\n3XPFfBWz7USAYbCQ6Xg2SkKh57KpOWJY1daQx70+ypvxh0UQe16ko/fyZv+0\nyE+QS/hB1J55ck6G+uQqvzvddJDjRv2tJiwku7tpP/oDJc8CEZppXcO6C2KZ\nioRe61m9VR6VjDh/HM4xmn++88TCQnYxgS+8KXPSy1ietiOo2EYz7VDasdbc\nJ5PV/rHX/kGzhKqEiq4afQfaJcXGysO4wLuRvvDBB9xHSGVnrT80y7EMN2rE\n40hcGHcgmltzKRnBQDjWgnBg78U9gibDmTIa+EWzKh3FCTwFQzXmJ25lPlBn\nvzoWBpLWmi+ssPVUm+eo4KQNM3QizroW5hB/uKi26i8TGPWlJrYQGc1jcXwC\nTFcr\r\n=QuzJ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"41ad835ab97058e758dd369926b3a4e00d6c589b","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.76","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.69","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^1.0.4","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.2.0","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.112","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.13-renovate-balena-jellyfish-metrics-1-x-41ad835ab97058e758dd369926b3a4e00d6c589b_1616645188345_0.23799996187539119","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.13-renovate-balena-jellyfish-environment-4-x-f62d86f7578b64be338fa82ba1f9970d8c81b4a4":{"name":"@balena/jellyfish-core","version":"2.12.13-renovate-balena-jellyfish-environment-4-x-f62d86f7578b64be338fa82ba1f9970d8c81b4a4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.13-renovate-balena-jellyfish-environment-4-x-f62d86f7578b64be338fa82ba1f9970d8c81b4a4","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f767a7572b0f91204cd0eaa6fad1f5ab97c4da77","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.13-renovate-balena-jellyfish-environment-4-x-f62d86f7578b64be338fa82ba1f9970d8c81b4a4.tgz","fileCount":100,"integrity":"sha512-+uKFhxlrdD5qVJN9A3oHYWHvs+tbDx9EPqDZhLJsuSTTDyacO4pq82/+LHwbJCY2oPc7GQenpc2PezbFSxVcmg==","signatures":[{"sig":"MEYCIQDFrUSNS5Iia8InC/Qv+2i8CBhKCmtGPnRvK/SdOlmsCAIhAJGDxQuWekURFIJo9FhwOOBhs8iOKHCfgX4DyAM2GmyM","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":662747,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgXAyoCRA9TVsSAnZWagAAlFQP/1bv8KcSJmiY7GNVhhgn\nNs2Ru+kMc3jps68Txu3Ug85LGc24PhsfdUIeR9i6v/PpZPouQqyAP6i4dmPQ\n1V9WvapDl4xpeJGh0B1RTWzTvkmWRQVZ+H6DKF75YJgsLD19dyLQPvUafqQO\nfsxs/EuSNMn9EgKDGLvAIBnOoLKBgKgdo1fOZ8F/ct+aC9GNGp7AYQ1N1GBc\nqoGM0m2lzGqksSWucwpxWkbDsHjWZ/pcXxlDodcACM7RasulB9MqPoYANEo8\nKGPt/RIJ6LWQwRHYVCyFKKexBtKfN35j39qdXwAjgSaGs7t61T+j56KmaGrj\nzIF7UTQLuZIZREmCjLmqKlZWQYiN0whYkLabVjJRoVEqbYWtqRiUx5/Lrozg\nzyII6qS4Mc/6VPaq25PPlxubBoFyp814RhtK68yBFEpACs5rLf5YvNf1dLrm\nBF5+a2d6Yilv/rdku6PnOnDFfRseEAdQz+A0PiisTDcbQH/xRfukxQvS2NHS\ne026w91hM9F3dkR6szvjnN95K42pC0f3z5a3OELx5Ex2+4cAZR13WMY6KsH9\nYfG9J2VGTSewlViErpphKPCI3ji2ZxiZeRLPRa7VtMwEvmyf5eT/mM5s1ZFr\ntqKPT3f2Api+X2reB70HTtO2YhyTbZxwZxvtYI2EeTI+nfyALoqSwfRVKMNy\ntbAf\r\n=Vwa3\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f62d86f7578b64be338fa82ba1f9970d8c81b4a4","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.76","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.69","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^0.1.125","@balena/jellyfish-environment":"^4.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.2.0","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.112","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.13-renovate-balena-jellyfish-environment-4-x-f62d86f7578b64be338fa82ba1f9970d8c81b4a4_1616645287760_0.9275871191126721","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.13-renovate-balena-jellyfish-metrics-1-x-2ed56136e50369ad0cc2a111d46033c675044143":{"name":"@balena/jellyfish-core","version":"2.12.13-renovate-balena-jellyfish-metrics-1-x-2ed56136e50369ad0cc2a111d46033c675044143","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.13-renovate-balena-jellyfish-metrics-1-x-2ed56136e50369ad0cc2a111d46033c675044143","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d07c38306090bf74e8b7240fc4a08cc0a931ded9","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.13-renovate-balena-jellyfish-metrics-1-x-2ed56136e50369ad0cc2a111d46033c675044143.tgz","fileCount":100,"integrity":"sha512-fmVUzYSFuxmZivmknaBnLqF1KXvk2peSHlU7ArQ1ag5jKyG8UY6mAJS8yfBqJ8MZjw85CxDipSTQ+51LVpWubw==","signatures":[{"sig":"MEYCIQC7+upbWH3sMjGa7vAXtkGkdfOYBDdL/NUbonROCzVj5gIhANXz3/WJyg4qv4yQrIMflqrjswPyMUtjJO5e3T1GQnOz","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":662733,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgXCOECRA9TVsSAnZWagAArlMP+QDZGE3ZiyC3xmB+H2dU\nJoMbm42wPs/l+u4vnJZahcCX190NEzB1VVCilMp7dHpIuSh61TrHEAN3fUTh\nMGXb3LZ6+6x/wIFrA7vdn4bnvweqAQo30mBNLuNGM/uJA/sJkjam2n6Wc0Oh\ng66Xjd8K/B+gCF/65hHr9Szkj1W9o2gr+mFMtXFNk+e+qR+k7Qf6fQU2wPYd\nD8gz5l2u/JyXANHWT2juQlulJFUa0cz1O0J+dJclzNt4pq++uzqQZbc755H6\nHasGRmgNE//QFoHr80M2zmt6mc2mGvNlze0epqIhH1Etc+w0uk+ewuQzMN9P\n0Sx+7kZI4EafpG3f650NmwChNP5SmWDbP9c6Y28sTMbcA2x4rMy4SOIlKQZG\nplYwZ8V1y3mNCcB78G6n9g5FzvQr1t7/ETCUSuHXb/27Cn0Wj9/52SwhDkJ7\nlJrVHe0xc+ugiBSclL35MBk+rHUkSZk6LPmhuheJYTufDxidp6vZo4rS/bsk\nmTleTEnKeLhe2+uhed5Bgrts05EKeNBl+W4Ci61q7GapMpgjMGTiFdrBKMaL\nHKlEDYTGrirFqiOFLGZoYo7he2/NhT1DfPeCytQPJOKHROwrt0rHOzjHTbr+\nyH5jXZ7muFDpJVOlyClHy26WRQf0Yr0kTIBns0XMlu2FuJAe53mHHUiqG5oL\nn3UW\r\n=AP9n\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2ed56136e50369ad0cc2a111d46033c675044143","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.76","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.69","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^1.0.5","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.2.0","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.112","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.13-renovate-balena-jellyfish-metrics-1-x-2ed56136e50369ad0cc2a111d46033c675044143_1616651139923_0.1530833140680714","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.13-renovate-balena-jellyfish-logger-2-x-7161784381e7b71bc74d32bf38e6b270eb5813c0":{"name":"@balena/jellyfish-core","version":"2.12.13-renovate-balena-jellyfish-logger-2-x-7161784381e7b71bc74d32bf38e6b270eb5813c0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.13-renovate-balena-jellyfish-logger-2-x-7161784381e7b71bc74d32bf38e6b270eb5813c0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"65f4f4f930d08a4294dabe07e7cc33288d0cf53f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.13-renovate-balena-jellyfish-logger-2-x-7161784381e7b71bc74d32bf38e6b270eb5813c0.tgz","fileCount":100,"integrity":"sha512-vMjCug5ouH5w2mYusmnFeR4ynHODLtCcsWOWE643AKOoYarv6IpDDni0lv81IzXu56m9FAKxsh/jgpQ5PANYOA==","signatures":[{"sig":"MEUCIGvjdtDnCjwdwAH3rVIApSioFLSLmKYIKnzG25m6EhKLAiEAqEtjw+UT2p0Rm/dHkgSjyXmumO9X6LuuzTCw2Fci0jU=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":662731,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgXCOPCRA9TVsSAnZWagAAq8oP/0PtAIkQnOuSwuNC53TR\nkG8aJXcPztp41RPPW9uRqU2EyIfm3AJFQ2akvv5ec8QjMyjIG3y3isYA+kU3\njSF+DG6zdy23MzNmU680nFnMKUyPk09mGo6Fw1jDu3kZxSfiKtUosZB1J71t\nUc7Hmoik4APan58RGqjCFh1UgI1H7jSEjJvkK5yEPC8dh2Lov+++Ql5c9EqQ\nSqyZh1kOWQedNLIgrO7P26FP5y3WGlwOB8fXGJylRfe2Kb360wk6moOpHd11\neLHOh+iF5i+jEXCQi1mkrRZ0xjEcrc6qJ/OXn7oGxGSdVA3KSdr1bkgW6vLB\nxz5IlnbEwbu+8ufEHtaB8Bzxbq25SpypLJL7o8SIZySMu+SXyPZJKj/Bwmhd\nDeONR5KzRGiXtlBiUvqNZ+sOH92VSNButT7DYvPw6R4UKwbYR5pyXkgVSqiH\nCV0b4wXXkISKczLXxZlQxbtEEIFxSdl6E34w/vjDHpnnfGS2EGrrx1RQ91+9\nXuUUSehVNrZLPJYjY1sQAbjvixY3ifGSXpPlHw93oBS3m2jqe/e/bG0zc6+X\nYmIrJxScAJxNdXzKnoIeTTNgup7xAaUhq8CPHxp/sCYApdY7oKZ28Gi3zx6g\n0EQWViRkLyMySTsJrNybnAiWiI466FPHF+Msb9VpcPp+Q5HL7btPapDcZD2u\n1KuZ\r\n=UawQ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7161784381e7b71bc74d32bf38e6b270eb5813c0","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.76","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.69","@balena/jellyfish-logger":"^2.1.9","@balena/jellyfish-metrics":"^0.1.125","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.2.0","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.112","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.13-renovate-balena-jellyfish-logger-2-x-7161784381e7b71bc74d32bf38e6b270eb5813c0_1616651151392_0.05888139420861149","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.13-renovate-non-major-76c503bdfdd15a87e68883e98189112cefa7fa81":{"name":"@balena/jellyfish-core","version":"2.12.13-renovate-non-major-76c503bdfdd15a87e68883e98189112cefa7fa81","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.13-renovate-non-major-76c503bdfdd15a87e68883e98189112cefa7fa81","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e42c09c8e82fb9ef341106cd19f0b0a67aaf2a42","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.13-renovate-non-major-76c503bdfdd15a87e68883e98189112cefa7fa81.tgz","fileCount":100,"integrity":"sha512-6n9F8ZmeTeGNqtptE5Kq4gpAu7Ttg/knoStWEyuZoX+/opHvLg8uDiVGtzsoCBczTvapclv+f4NS9L4QvGDzZw==","signatures":[{"sig":"MEQCIBxN57wv7xvbtvT/PF1I061DOe/Ai2/qQgkarXlwn23WAiB10OdgLH14L9rwuEtyqpmoHGS3o291vYPXwejEff1zOw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":662649,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgXHRmCRA9TVsSAnZWagAAoyEQAIK0qZC8XuSgdao83433\njWYXAtLE0NbAFF6jMAPEdrzfuomzgDnajJ+lLmxtEzy2Yn2auL+0djUO/e4D\naoxJpB8hHCZ7YWp/M0X/kScCLTWCUgXCXGvGpq3FHCPt/c6KL5DZpzOu5tlL\nTGsLyEhbY+2ERyuN8IiD2DAoXDEtUuNpZwDnyYUoQYEoEA82bSs+MtBTDRGz\nQf0P7sxpueqdsyl4iTg2MKIuMSbsdgwB6lor9mxr+mjxVFAIsWH2QxTWtSwr\nfrcMR4OilwxAU0fX5AUfyhW9WTSwi7kTbltJ+tFBo3k0kSXeDlDnkM86KJog\ntx5jwZxi9zVfYfQztoe7TkWyeDwg/pilrCSoOv+BtCRp40IxUmureeu9ffGB\nN66mxjUWWKBNdNqeaRzXziLqWTUrcs49qiU/nZjIX3DZJydZdXcMRx0Heend\ntnY49L1FslPZGTFdkZRmvPwSFQm9I87QMVSDbm9ZB0TNwzYs1d3tRiBUfIyf\nr/MbD8EScy0V65mHDKLMZNJ4vczFk1o1hsw2D4CzqBetdcp7X+7mOwEi2apy\nYQB3r1O1pn+SXRolXKWWbEonvq/yE2JAJ5p9b4obLyMHfyFig9H1ZUd+w+YZ\nfOqk1QJQ8O0QzCoBoXwRUxsxRdp5qCSiO03H9ckiSV5OtZ+jHbtxqV898ClX\nhXPZ\r\n=yMXt\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"76c503bdfdd15a87e68883e98189112cefa7fa81","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.77","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.0","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^0.1.125","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.2.0","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.112","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.13-renovate-non-major-76c503bdfdd15a87e68883e98189112cefa7fa81_1616671846205_0.8497326302858637","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.13-renovate-balena-jellyfish-environment-4-x-a5f46f36d221d2d221c0e446a54544f446395d8b":{"name":"@balena/jellyfish-core","version":"2.12.13-renovate-balena-jellyfish-environment-4-x-a5f46f36d221d2d221c0e446a54544f446395d8b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.13-renovate-balena-jellyfish-environment-4-x-a5f46f36d221d2d221c0e446a54544f446395d8b","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6cc8a12a23e058f1d8c94c787015540cb1df9bae","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.13-renovate-balena-jellyfish-environment-4-x-a5f46f36d221d2d221c0e446a54544f446395d8b.tgz","fileCount":100,"integrity":"sha512-bHDlEryJrOeFHIx3WJ8oQJv6dfFNoHXUJvTv4vyZLpNGw8RKwe5NV3q7YolivkkIZDHjyOokYXzJbXbm9Xr+HA==","signatures":[{"sig":"MEUCIClm1iydmZnvMfLUBJ487rtfW7YvC1Vgxb4fG3r3qI7VAiEApYlYnVQmapThodjSWZnCLNGE4h22lE61BgI7OUqLrKg=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":662747,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgXHS5CRA9TVsSAnZWagAARz8P/A7+NB/1QjLNGaGRRQC5\nq+Q3pXmr3DqTfGqzErThJo8o4bd/8SznpVSLs/PCB8/gJ22Fcysrsj/De8TN\nDadta69kXnTZ5ICYU0pSItiT/XBiwnIrYSm/FTTzUC/lCcf5nGP/qKZUW04R\ni5QmtwjWrLrB68mLDoev0GOPEf2r6SoxDgjdfeCRSvvTrPwIWKQm1RoF3XBF\n0I1HKj9bWP0S35wt9yFhBYGVK/LAO4apxbIxtMx1k1EQT0kEj0HnT2Gk5eoQ\nAMLZ+YkA6ngqH+7kRv0dnozoEy9ISjZ8kf90NigcK54wHrhy4vlL6XyD9VbD\nPuBpl5mikgeK6mcBfXr6vC4VjioLH5CgfCGAD7F6cPzTxSXi+IKUmzCtBlu3\nm549XkEliHwrPTt45O6Pk+eHfQK2s9MqqLKU5CZawB2flGRdoL2Bi4FmwVzW\nsHBBobqC+Ja+yoVGhslmTpg78O85fjtL2FTJCYzHHeojodsYYLNy+QQPv07l\n6NHB+2/gNRah3E+G60PmOg+R9YKHQH6cg8zeW6aG36fvzbXPeH/tpJd7jzCk\nRkQdHlnhuwVZT3arDuciV9Dd/UrsGh4PZPvxlZIwWW0FDwxboao822WZt4mk\nKt/fZNcS3sXJm6rm/VkrPN+D7kkXyN0/xFhH28SL/IPVSPvNMnRV3gEhN7sB\nPLqv\r\n=WFFq\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a5f46f36d221d2d221c0e446a54544f446395d8b","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.76","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.69","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^0.1.125","@balena/jellyfish-environment":"^4.0.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.2.0","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.112","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.13-renovate-balena-jellyfish-environment-4-x-a5f46f36d221d2d221c0e446a54544f446395d8b_1616671928779_0.6289574912367111","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.13-renovate-balena-jellyfish-logger-2-x-086b40e0181baf4bc10b70a3c59afb87e16edc45":{"name":"@balena/jellyfish-core","version":"2.12.13-renovate-balena-jellyfish-logger-2-x-086b40e0181baf4bc10b70a3c59afb87e16edc45","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.13-renovate-balena-jellyfish-logger-2-x-086b40e0181baf4bc10b70a3c59afb87e16edc45","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"63f897e432db3529caf7efa3f0d699ca00bd47ee","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.13-renovate-balena-jellyfish-logger-2-x-086b40e0181baf4bc10b70a3c59afb87e16edc45.tgz","fileCount":100,"integrity":"sha512-Zrj9xe6oCJUiTgzEEO8UG5mkFOW1NjGM4p5LL+dODSYKZbZYWfcMJOuED0xNpbFHeWF7ODyQZ4ZUau3DWWdRug==","signatures":[{"sig":"MEYCIQCesqAiAonVaxZskryGGwcE/0CjKnMCtdRJimlrSIbzvwIhAPikFk1m4zJixq/cWc7HE9vhmRVFrF7ABfJMs0Upqj0T","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":662732,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgXHTrCRA9TVsSAnZWagAAJC4P/0txVHZ5ExDQWbcEiw9i\niryIBRfKGDitk3RZr+U6KOM8JR6uA+6IIuXjs3AIOM/xnZEB3GAjTJ6i4Pbj\nVSbJdaQiC8ugz7TSMWelDPWBy0PMQlESmoYcsPXMJVBjpuv1tl1HMb4lA3mJ\n59pDxMeHettxo0oaHw+Qc59gPuGrm7skKXa6P9jo++mTAx0eeQonTzrZwcbX\nAI0dnscC6UP5tVGzRw+2KOdKiZFiVNbOl0bPDhJRgQzvBWvTbk3fiDGvLOHS\nWXmNwwPV9EhuhWId5RDTimC0aWJm1gDORQZ9fuvjK7s3NyxYYCmLuiAHaS9Q\n4ZGiwxqYanHhakptembM21yS4lHrogGudhl/GvqOuCli5NTZYv6LKcFewbmY\nXju5larzPJkxsGJ/aNqMDNvZyiWlRzvReJiHSYwWPFJgVw/IU2KvtbyEfbyT\nUSHmUwu7IaQcDxBkX6qMf6breMqetuo+uvfYz7MjvPoy5C7NNfewJygFExbf\n2Swe4Dfz6WR/U2knIwd9VOAg25Lokf+L2Zz+fry5u2HcF+XiscVjAB6yZaUM\ndTiVwYbjfx460+1c3kL3TOvPQDfkI9MoeI978Er+12R7kAzkNCPUKnnsidzl\nf6oYAz5z2I9T3B8VfWaLhPUhvDMytFRBVlwT2M8qih0asIgZniSR11OPpY4N\nENp9\r\n=Ecdz\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"086b40e0181baf4bc10b70a3c59afb87e16edc45","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.76","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.69","@balena/jellyfish-logger":"^2.1.10","@balena/jellyfish-metrics":"^0.1.125","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.2.0","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.112","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.13-renovate-balena-jellyfish-logger-2-x-086b40e0181baf4bc10b70a3c59afb87e16edc45_1616671979116_0.2221726390654799","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.13-renovate-balena-jellyfish-metrics-1-x-b889446cf40b51789172e2064258815201ac8c1e":{"name":"@balena/jellyfish-core","version":"2.12.13-renovate-balena-jellyfish-metrics-1-x-b889446cf40b51789172e2064258815201ac8c1e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.13-renovate-balena-jellyfish-metrics-1-x-b889446cf40b51789172e2064258815201ac8c1e","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b2e94f744c2d40d65ba399f1dcaababda7aba883","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.13-renovate-balena-jellyfish-metrics-1-x-b889446cf40b51789172e2064258815201ac8c1e.tgz","fileCount":100,"integrity":"sha512-/a8IDm9m+A/8+5gJseooQ7U41KDlqpfTkGIWHGKVzMnyqM1UT0EjSEKAft3Vv5e4iXO5iKsWgxuQ6nKgcVsOTw==","signatures":[{"sig":"MEYCIQDyeDVW3vH7gs0s4WNWcOsaTEp63zJ5OHDc57wOBBmXygIhAOvgWDQkBON61Gt4kGzrK263bWTyhrEI2CosUUp9eptZ","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":662733,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgXHU5CRA9TVsSAnZWagAAbRcP/jB0HmIRQzTG2R3ZN+f0\nlzBd7TzGRjzWfbHMjqw+QureJsETFLf+GfYzQEf60g2cliO+cSl8fr9Te9OV\ne3+6a8HHfqN0wj4bNEUb35B+3S+VjavCR+LkcvWieT7qe642JAA3ZVMNI1ym\nyKL8U/e5fl/vh/9zQhYpFJL07IG5yiJwJq7mByfuLURTqpQcmHQYmoNTgcwv\nI7p4cJ7hp08DI694rLOej8kiVOwUM0SLqngbLjXNAdLGqLvz9tTCJ8LadsaR\nNh+1vz1bgiKmzNMj8IpYyLBspChacm8ZkwiUauog7ihqTOYEd8D324tYJVDb\nzzFeesVP19W288H0yUWksqJClEOmGk1qp8c4rHDBPZwy0/w/U4VxBncQeeIm\nw0YnOw9xhiqE6L7mwi+Xa+ax3APF1RnvUmL0HTlwHffrJaNwrKhInkTi3INL\nSnR95GG0tF3leVpQSdh5FwhdZS6GKWDSh69Mjf9SZTzRC8zduPsws9VZsgZ+\n4/4cWu2DlLzeghg3hXwDYxvH3bo4OI9CXmAC+RFVurlW9jBVeHN0PYy5h9L+\nZgFcbCfJQVGh/M8wKvd9So1EA+KMxOiT4CQPdywrQf3G+kzPu0s/32lWWMQt\nz94tLshCSOSHw2+PErZCeEOV55Agb5qg3R+aJ8yTkigYRRFJMbvK5rVuq6po\nHU1S\r\n=Pjaq\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b889446cf40b51789172e2064258815201ac8c1e","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.76","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.0.69","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^1.0.6","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.2.0","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.0","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.112","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.13-renovate-balena-jellyfish-metrics-1-x-b889446cf40b51789172e2064258815201ac8c1e_1616672057482_0.9215791751094693","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.13-renovate-non-major-edbf5f0cd6e4fbc6b3fd267a7a8d5fbdea0eaf09":{"name":"@balena/jellyfish-core","version":"2.12.13-renovate-non-major-edbf5f0cd6e4fbc6b3fd267a7a8d5fbdea0eaf09","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.13-renovate-non-major-edbf5f0cd6e4fbc6b3fd267a7a8d5fbdea0eaf09","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"077e48b8bd43fc6449773ce607303096202a9e5a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.13-renovate-non-major-edbf5f0cd6e4fbc6b3fd267a7a8d5fbdea0eaf09.tgz","fileCount":100,"integrity":"sha512-lz+CreFqkBcqOWgkBQLv0vHIR2fAIX4L6OeC9B/zFWXJpkgXsDzyVSeswl6bNv7tMM58l0n9g7CDg+ee9xN4eA==","signatures":[{"sig":"MEUCIQCQx+gRvLKPPNyk0zs+JtmZ45Roqq936xzBJ85mM/rLOAIgH/XcPsvCZtd6i4Ukgk0ccM0p7zhPPmRDzoWEQUfINwA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":662649,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgXHWiCRA9TVsSAnZWagAAqYIP/3Mo7+fi67zgTqDPlx5k\ncaEp+5cq7poflcV9I+BSLQLqI3YYxXzzMgxJSHMq3urE+l/RSjj6DPkqjkJp\n9FOidQIetFEuU0LX04ACFGAdkKE9fZMi+MXazSCpzK1XPYtqQJcovq8Mewvo\nd/Qj/oOSkxbcklALnZyqKW1+UFOrr9G2kzuvVLDEopLTNBTkkqJ2QeGccUF7\n0U3aKsPDYVwu0OGQcsK7tiwFVFn05MuSv2orKM86DpFWH22CGGN2g1mirSEz\nGXIoJ6Oxjbi9rywJznTvltp3MEVhXYmxVZellbb2iU3KHs00IcO8z1u9ryFN\nvM82sKQ8mRPct+DmhI2skCN15dYS+O1dtiF5pYKgSb25uT1Xri5K8ytE+i/N\n3OHvbHQpObb9tRBQGPtOcoRDHbi9LJ0JU//XUt1IwqTjkwzptzayQ7QuyWXP\nW8VRaG4SJUk0RESmPWPn4H/KwPl30Q7Qx1/ssBXbN3wQbRL5nSFtSR0rU1qT\nquwYI4jybm3caqvYJC8eLwi7bcllVFXVkQAY+wurtnUZQhz3c3Y1+KDI77m8\npGV+UZpS9DxzRMfuef4M0DJGS7qgFsoMkoLi27IHCigduoCldYBTIo+oH5lM\nbzI1d87BGFN0uG2jEcRkoFnZqbeVg7vfu6sqO8UxmLk5kK0UBt4Wfs5vKKF3\n3tzo\r\n=Pvob\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"edbf5f0cd6e4fbc6b3fd267a7a8d5fbdea0eaf09","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.77","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.0","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^0.1.125","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.2.0","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.112","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.13-renovate-non-major-edbf5f0cd6e4fbc6b3fd267a7a8d5fbdea0eaf09_1616672161548_0.008148968869599704","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.13":{"name":"@balena/jellyfish-core","version":"2.12.13","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.13","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2f186084601d944bf5d81c859211116080a6006f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.13.tgz","fileCount":100,"integrity":"sha512-Q3N4/RZjJa3v4gUw9Uqj06mCDCnS1bKHwOsIMSngbsAfU+Se3nsIfFJmJ153Lm8ZYMmUKtDmbtAn6sS2RERsCw==","signatures":[{"sig":"MEYCIQCOWXqDLcnPeaXG5q5Ec08qBqCEVW6OpK+MifUzeHtB1gIhAN5l9N9XAZlRBGekIC91ONJfT1SJ3kxMk02P5SS0BwzE","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":662589,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgXHb6CRA9TVsSAnZWagAAZm0P/3lQ5PICUfJp6mrhi3k5\n3YKo+4HSJkAfj25Ylj2fuxiKIGxxyraqQUkumFuOfpvSe4jRJSyjlSQLKc3H\n0C9lpVWk+WhxnUZ93raOb3uTUB25DowSzVH6U9Nulv+S5SVyPVqB5OCD9HSu\nWr85TRAdHOTUuHeymcUCQOMFsQwYo+FhpRNvbn8jI3sblo+V3QMjvqj6bvYs\nL6c9Xv3gxclT2tvp5kcQCFEH7HrZfzEA54diMLUx5Hyw+iHj0pDbi+MwFDzt\nfEs8bEmhD4u69YTnaD+XIZenApAMraSBzQnKNwAXcMSqBYDLqR0eskbc3EQT\n1HL5t3osiUL06i9dgftVMKqw4Cz89u+tddD6pk5YAsrjnCqVoS219Fp3EpJJ\ny/mTHCxMUNJ2hl+/dnrfk6zXc1YvkGp+dnJLgJuIHODBcO+fgaefkwuuM6iv\nN0R9VxGtisTp2l1wt5oBMCDBb2VqwVT1cXWT4lWYOkCdZgZMMya076wdT0jD\nPybo38wgEBQSsQS5U5pwycPNBFt2yxtoi4X9xVcl18LxYJsN1QDBOWDZXMgD\nuI69EwhPLyhNlvDD4cSJG3b+wFsz0L6CNUYWR54keBG0B/BrEwhPEIxvgN+j\n0b0EDrMFZf4qlKF82vNJz+T3sTgEu4JyF/Lmo+ifVTd2d6jNzt/nkCmjBhrQ\neT+z\r\n=Sd2J\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8d059bf6a32b8ed12e6d7f50f817a3fc3cc6bf5f","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.77","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.0","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^0.1.125","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.2.0","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.112","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.13_1616672505575_0.9979495408757306","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.14-renovate-balena-jellyfish-environment-4-x-8471870e2e5472dbcd41cafa912bf72834566e62":{"name":"@balena/jellyfish-core","version":"2.12.14-renovate-balena-jellyfish-environment-4-x-8471870e2e5472dbcd41cafa912bf72834566e62","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.14-renovate-balena-jellyfish-environment-4-x-8471870e2e5472dbcd41cafa912bf72834566e62","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"64c5b5da1417f58eb65e3b7bf9fc462b0cabc3f0","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.14-renovate-balena-jellyfish-environment-4-x-8471870e2e5472dbcd41cafa912bf72834566e62.tgz","fileCount":100,"integrity":"sha512-G1QR0JmBr5vERxYBhxtyx2WhwUQs6m7lI91q+qwgpT07I1gp7gnphBMx8o211g70hJLFpiH4s1IEXrCexf9TVw==","signatures":[{"sig":"MEQCIFZsF0GWrbBf2Rpu9VjXgCibxYse10lJoQQPkPTbduSKAiBwKDTWV1wmhLnDlyT4sw38kPmypgTwJxNXcWSYy5K94Q==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":663097,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgXHgKCRA9TVsSAnZWagAAiRoQAJMf29UodQ+Tr5v0Jc8U\nGErcbHUTpuoocuu3ZOqiDj6SkFrMrCdTEg6ktv8u16vz7k4iEeDqT/xIs8Je\nGmmFBBKtByGUU/EKolIAhNsBE5and8w7N752ivrvZvEo7YmqeYscIiAy1mLu\nJMrcFjAguBOSjeg3HJe/KbSJjqoX1LQA0MsTDwDCx6yMRXM1vxhumEs/mzne\njuo2lthcEiSe4FkBWTAu+6JjeWv0WRuLwPC+g/mD1jtrN0oRIZc8f7l+CUt4\n2p3ekzFykXX9T1NZiiIoyEVc5eAM5Eo29UpCHjk4XfDrOvT8Q533l+IXX2Ai\nDm9zAKvZRxLYYvvGoxYJBxhFyKiYfzfv8nkVwRvKr8BhuHp7qbFEv3+FbaN1\n4fD5QyN0xkbuTCnZp/0fyUVyLCzu4CHMePcetf10Rx5Uk5x8H3h5EeSgA6qQ\nG6nF4MPOxDwF5VfflIP5XFhul9xPH0/uDH0kRRsGMG5s1gxSD0s6c/KlMtZJ\nZ14i5wADIVSh0iLzytMglfAYMooK1PkMj0y9daYwvlEhoFj2DHPQrERcNcAY\ni/a75tGBDizUxAfGDaAy9u4KdnAN66j/xtM5Z6xRNkguTQGqc+rL6ZbCEyLm\n5E9vo1zwY4YNW8tpBZP34BcSDp8npid6TpwOysnmBtiJHvdV1QVR9c+vLuoM\nzV6/\r\n=OwH7\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8471870e2e5472dbcd41cafa912bf72834566e62","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.77","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.0","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^0.1.125","@balena/jellyfish-environment":"^4.0.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.2.0","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.112","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.14-renovate-balena-jellyfish-environment-4-x-8471870e2e5472dbcd41cafa912bf72834566e62_1616672777902_0.4869891478095014","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.14-renovate-balena-jellyfish-metrics-1-x-308bd3f5551523d70eaf4a943a33f3ad8bdbee4c":{"name":"@balena/jellyfish-core","version":"2.12.14-renovate-balena-jellyfish-metrics-1-x-308bd3f5551523d70eaf4a943a33f3ad8bdbee4c","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.14-renovate-balena-jellyfish-metrics-1-x-308bd3f5551523d70eaf4a943a33f3ad8bdbee4c","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6cece9cdd90f38ce921ea2e3d2a2da69b28e3410","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.14-renovate-balena-jellyfish-metrics-1-x-308bd3f5551523d70eaf4a943a33f3ad8bdbee4c.tgz","fileCount":100,"integrity":"sha512-xqImEOuXRr5Pvr1LQ0TzyGAyLylvgVJdMnmiCKZGKf5SlE9t0vp98fYdtqv5Ccpthl/zQ5ptVW5ZL2mSNPvj4w==","signatures":[{"sig":"MEYCIQC9W+AfNB2btuEkxJe3fijlc0qg3rtWqkYUbK8323VexgIhALpz1mbpVrj9kYUeAyZ++ezIJktBKMH2pi7AENWvygy9","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":663083,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgXHiHCRA9TVsSAnZWagAASHUP/iLAjWjJoyWZhxohoacD\ntjzHKZMR3G2UbFON1KC8Mv4uY2LsnpMXENnxT1k79FZzfK2m0avyQHAoJNtI\nVYiozx84Gh9iK+fcrcSfPCyAN5eipaNOAdQdJgphOqw0Y1dfB/k3+zrMZSrJ\nAf2/cnfzaWMjzXVW80FLg9Ey7we9F4PiIexmHi6iTCd1xBrU/v+Zs1VXM3eg\nB2L/2yj5ESjYt9Q4Ybpn2l76hH3HIK3K/PBTtJnnYnQrwxfsy9CVZ9ERfu4N\nOFAt0SNAUA9iw7Bqwt//ghdBsuNRRPwwBqd33nSkN/cfmxWrnsl4Id1ZcAgn\nWD2kYtY9PPcBPv7vNOHGFgR1XjPpkPLetu2pIvEF64NsnAEz0/wWPBmxSIrD\npACwsntSSVdrh1jNnjNhQ1ZKd76s9k23R+km9gScwrrczn4davBZzr6bGiDf\nN2HepJF+FzriQEoy5M4rduFOy2It/pSrD6DylOD30boxwE8tB4clE19JtJv3\nTVTOChknukvnvY0xG0dH2KPgE0Asv3DalH0aJ8lOcK2jachkTt09gFoNgzQp\nhQXx246pxEldcOCnzodSfxL/k1M2lPuD72tvzv2k4JfTJgEY/WB5VGl0VyaX\n5XcNckyvRBcr+8XwoAog5ND1CoS4JA16r00pCH5XSl3P4885mdunV5vGr6aP\nTwWc\r\n=8gKo\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"308bd3f5551523d70eaf4a943a33f3ad8bdbee4c","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.77","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.0","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^1.0.6","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.2.0","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.112","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.14-renovate-balena-jellyfish-metrics-1-x-308bd3f5551523d70eaf4a943a33f3ad8bdbee4c_1616672902972_0.6065801271749969","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.14-renovate-balena-jellyfish-logger-2-x-6ba38452a0045487e814bb249c8c4e4f49968e04":{"name":"@balena/jellyfish-core","version":"2.12.14-renovate-balena-jellyfish-logger-2-x-6ba38452a0045487e814bb249c8c4e4f49968e04","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.14-renovate-balena-jellyfish-logger-2-x-6ba38452a0045487e814bb249c8c4e4f49968e04","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"721b01603a3636c8641ceb672e96cd372322abb0","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.14-renovate-balena-jellyfish-logger-2-x-6ba38452a0045487e814bb249c8c4e4f49968e04.tgz","fileCount":100,"integrity":"sha512-UDaV7fMLTst7e6no+u8JihMbcY0DYFr1iMxU9/GrBt9r/ZMJ6c2Yu3EZAiXMUUIaS+Az42sn/yXS8B8v2/JBUg==","signatures":[{"sig":"MEQCICSwYy35/xpad+cyKbmbr/1xPjgjToAavkGwGXXRoR20AiASUAdHIHO0WA9ST8JK9rOm36Ipv48ZyTmSzjLdkJELNg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":663082,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgXHj7CRA9TVsSAnZWagAAF58P/iHy5JQKpKSTYE6ShT6e\nM8TGiATY8bNdF9M4tQtY2zOnDHQva9qC9sNPXQ2rGQfgVcZoPtcJElY8xz/h\nly/TgnckrgVflbXgh7l6+lM+P/L4F6+4E2ijmDrxtCj6ub6F5zwlCCNNxtK8\n5Cb5sTwb0SPyOoJEKUz7KdS2Xr/KHr2aKwddBmPZ9BYFaj7Od5itlakhY4iL\nHY8iY0ooapspRZTdhb3ldLUTTTGkAwNiVqtHFgNjIBtDU0J6M4WpSNFD5ZnH\nPjnYWqGJYLGfqPoDLJ1ChJbYP30RzmkpOxR2vJba1CtO6muMst7Kks2PGMik\nQEEzTp/8YxC2tL+4N8tSldB3nCeYpuu0/baWhgQ8qF8Rj5Q8HyfqqkeFpjIp\nurG+17YC/BEeBTTvapxPpr3JFldvPx9GVuqUdzC2eFiYUw6L1KPXR2hQWbUg\n6tvaNjoTMzO0AYwAMEBWpWMpDMw3qbYqInHd3fl+Oe/zIpOouGcIsvdrnsjR\nW2pkjgVy+IZaJtibcww6evQI8F1H18qGBzYbJrrZ+fhn3cHFNUWqNqlNYuXr\ntQRnXqPEIb1B9UNcdGkZf7STCwuWeH7npcXrlyaGDMqDCp8y3flsOarc0n7M\nHANjO3EXztTef4TjAXoV/9NZt+G4eRKj38SmZYoQOfVceZXyxl9H6Bn9o9vK\nCATZ\r\n=gg1t\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6ba38452a0045487e814bb249c8c4e4f49968e04","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.77","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.0","@balena/jellyfish-logger":"^2.1.10","@balena/jellyfish-metrics":"^0.1.125","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.2.0","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.112","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.14-renovate-balena-jellyfish-logger-2-x-6ba38452a0045487e814bb249c8c4e4f49968e04_1616673018441_0.3194142242737723","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.14-renovate-non-major-71a11d8f50701305e85dc28c1bf3f44f3073b690":{"name":"@balena/jellyfish-core","version":"2.12.14-renovate-non-major-71a11d8f50701305e85dc28c1bf3f44f3073b690","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.14-renovate-non-major-71a11d8f50701305e85dc28c1bf3f44f3073b690","maintainers":[{"name":"belodetek","email":"team@belodetek.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d8b3ad0f27e108e5534ce7dbb1b305a2dcfb7dfc","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.14-renovate-non-major-71a11d8f50701305e85dc28c1bf3f44f3073b690.tgz","fileCount":100,"integrity":"sha512-YfMUmIQXofvQYRIu+QhT6tutooSvq1f98kbDUt8b5V39DaV84wOSpfpgntRdhAqE+miQsKSNLwR+4oa+Jnkj5g==","signatures":[{"sig":"MEQCIHKu8s+ekDhBHN/N7KSW2VkJGEp+t0NHsq9ATaIs/8KIAiBrtmxajSTdYQUgclJeQL5LS7RPpAf0m9akkTAxtw0fcA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":663072,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgXKUuCRA9TVsSAnZWagAAtSwP+gJEJcsli/ZsyKx9Fffq\nnnoGftFvZMreB2dJACeCtIfFqlaSbeBOvMxZSxqkIZ3DJzBhr5JDudOdKV7i\nXSU+2bkmIJZ+nm2H+BiYLyeNTSYRHnpApdSHI2AKTmCk/HUMC5LVDosOiOzM\nULY3u8jpxpj4KISu6R1/IamIKoo5sZywY4reqnGCqHrH6F/XESEn8MrNkDkK\nS94P3Me/MV+RLi1j5PRVqeZqy7mohopUd41R6HIH8mRX68HjVVfODbF8GtNO\nwCWXDEEBRb9/VlA4Rfw1tZF/g+umbEyo42GmuOZXtMPdmaivWj6JGXjGiGCe\nxiff37np4z1s/9ExG6TdLCjcZ2WGOTgOI/uDP8nzZ9Nj6m5vsdJsn1DUZhXs\nSmqx62lIOyt0g0G6DwmkaiFtmSdQZwkBOWqHmTNJwWVP1yGPGtoXsYgrsth+\nrBt6UnENefAyYQdBiYETWxGy2cRULX65uDEttdnI2g+RvEdFAGt7EpCQud/W\n8me23J3kVCNbCXE04+XcPUhOqLk1Vpj/X/DfN06GKSBYfR+k+ZpjocwxFn0J\nKDDptvj9TLFGJWvfX97vHwmlLk9ew87KHc3jP/2teR3QwUEpToe2ZhsPMpPl\nJNHKuRjTe63Kj1JK/j1oxx29nsgXvR0KYUo9WZJP/B2D9Po5CeqmmSQPTrkM\nN7F2\r\n=L6Hk\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"71a11d8f50701305e85dc28c1bf3f44f3073b690","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.77","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.0","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^0.1.125","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.2.0","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.113","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.14-renovate-non-major-71a11d8f50701305e85dc28c1bf3f44f3073b690_1616684333445_0.27028082195071934","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.14-renovate-balena-jellyfish-metrics-1-x-062c52276f82980cea97ec070559211fb853ee5e":{"name":"@balena/jellyfish-core","version":"2.12.14-renovate-balena-jellyfish-metrics-1-x-062c52276f82980cea97ec070559211fb853ee5e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.14-renovate-balena-jellyfish-metrics-1-x-062c52276f82980cea97ec070559211fb853ee5e","maintainers":[{"name":"belodetek","email":"team@belodetek.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9cc373c19447e0790b7c9537f80104964e5e0645","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.14-renovate-balena-jellyfish-metrics-1-x-062c52276f82980cea97ec070559211fb853ee5e.tgz","fileCount":100,"integrity":"sha512-2D/4/bR+OUoRXMCeuQaLxUNVdkuiQSgF52Cr4PVT6olezCHt2IXIXzC8Up4wux8Lh9U2dJZPaNEhZz9vJTtO+g==","signatures":[{"sig":"MEUCIEPzqJxnAfmEAFyjy0mqAPiB7g7w+AtTkv09W65MKlQhAiEA9KhzXylSiKPKbqeVJXE3wCaaw/F/dml+KNFxKxjwlFU=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":663083,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgXKVKCRA9TVsSAnZWagAAnskP/2lmxwXPgqhHtlNCbfbd\n4/NDUYQHzaa/7H14JzqVjmBtzVWRSrubPpSGT6xb41uLIVbkwTIqhFgzvCMo\nqmG3WudymA7sGQwQI0NUcuBvCbGbL/Ygrl1aRO5AfLvsdNvrr9Ugmh+RLpfE\ntsls7rPtEJIHN0cAmF077Y1aolfmklQakvBeT5hxCCnE3Iyp9368aXU+K1p/\nXHzUEmsMhx8np7CnwCw3AKxp4/X75yTZm9dRaonQvGqcR5Aa2F8T/SOQ3W8e\niO73XYn5YyEuokldEy6eHE2Pm5/73+D3eeU4SRaqCLLU7Z41esrbKun1mWQP\nYclnim6aaCdBJaJLcfuou9c0t7kNXWPbo+Vx9LR8QyFehsfmZa2kJUWkBC9+\n7iq/GGSwQeKn+HQKOkd7OHIWmSAyuUoXrVpcLKioMT7G1TOuvQU/l99PnEVI\neSKvlGPg1zrw0V+i2Upx2bMkbyc8sT2waCKy9MMVufW7iS4LNxzY/kVqp1hi\nMJxakXJhQ+HMDsW79BHsIpnhvY9y0gkqN+2vK8NL4rq3YV+P3D3xUDyCggxy\nvBaLsW1+O02P9zU25rTTq9x7rBBiRYbYfuGa8CicJUsZXZERx8CmqVImfvg8\nrrZjqW173W86TkCfv9lmzCgq3Bl6Uo6kUuLnfq1oTf+SlC1oYthWIiMzdk/H\n9DYK\r\n=gpJB\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"062c52276f82980cea97ec070559211fb853ee5e","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.77","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.0","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^1.0.7","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.2.0","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.112","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.14-renovate-balena-jellyfish-metrics-1-x-062c52276f82980cea97ec070559211fb853ee5e_1616684362345_0.00565666446732882","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.14":{"name":"@balena/jellyfish-core","version":"2.12.14","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.14","maintainers":[{"name":"belodetek","email":"team@belodetek.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3abf348eead799429c7c702e0bb6fd46064d7cfa","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.14.tgz","fileCount":100,"integrity":"sha512-wRP9/5v//usiZF3/4S0P3QS3w2LXVmHte9fA8F59ZkFpE6v+afv04d9gv8Tv8sTrrc1/KG384nMqDf4eH0iaUQ==","signatures":[{"sig":"MEQCIAVjFxgRWKcMXC0efmrEWlvG3nLXKOwTaCVLBXvHl8G2AiAX7GSvAziXdIWTQkGR31JYTIniAHuoUED9IvMtT/nACA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":663012,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgXKZ1CRA9TVsSAnZWagAA5roP/0bQmXkb/r/465XRvRgI\n5U5+RKPsT43a/BYqXgBtxLLSmNJbsfY/AectSyhLk/1iSOpEHRud8z1suRwS\n8BPpSUc936FhGUdbWfLTdGg++QcYuWqMqtFByGUbkouGyqz4QaJrgbbVrpLc\nROnLevZYLWwAwt/po3LMNNEfDSxH7/2GMA5PzkoOJy7aUFtw7VxisVv96o1l\nTOOm6QiXRsXQplGaqAumgcYcVVZRixbYoU8pB6oJPCKGfOwisQFItvctHyJf\nPw2Sdbb+HiLXFN87jDSSqr1t+0lJHo+X8nG6iCaLEZWgHwvWe3U2EuslmEjK\nFIBwZjzxsImrq63O+9/4i7APIP1nIJFBYi2xcPa5juKFbisS9QeYB2w0nTQ0\noNcV49u1t/CV0zbiXck7yz5AkMV9dtlq7Mrz7OJHFmr3BePgyJ3lf0opSk66\nyAKrFAk8SnTeA+mfyfqbbAsQLbGir0OrfhhyKduCEQQvJhhdG7uvNzKmhMgz\nc6UKjsnQ5yWhKaNcYZAGTpJfFZEvRgnzQ3ia9Zn7bOJXkYKABysLsa3iFFpd\nRasuRjo/1b1gP1BtLXB2nY8ewE3qp1q166JZx1XlGkda8nZ2CJVkyiRp6a/a\nfHlj8q8tBJXqRmpVtqhcvW7N4FfdvEoglF2/3tjhWU971MKBT/MSxuHK5R5u\ntVqO\r\n=iF8T\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"5d3e057ac1257079bb7c6df166aa5bc0d08b36b7","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.77","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.0","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^0.1.125","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.2.0","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.113","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.14_1616684660378_0.7220951897248873","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.15-renovate-balena-jellyfish-environment-4-x-a0a70ba9079e7b5c2b35f5e54c3c929ff69e73f3":{"name":"@balena/jellyfish-core","version":"2.12.15-renovate-balena-jellyfish-environment-4-x-a0a70ba9079e7b5c2b35f5e54c3c929ff69e73f3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.15-renovate-balena-jellyfish-environment-4-x-a0a70ba9079e7b5c2b35f5e54c3c929ff69e73f3","maintainers":[{"name":"belodetek","email":"team@belodetek.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9d534617e8f524b7aa248f4657990f4bc543a02d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.15-renovate-balena-jellyfish-environment-4-x-a0a70ba9079e7b5c2b35f5e54c3c929ff69e73f3.tgz","fileCount":100,"integrity":"sha512-IWcQQigP/vpqMtuEvc4Vdg0aYjvSxFkVKa+nWv83K+sHg/iJPlNttqVn2td6QfELjJkeVLOb3Kg6aOMi3LBSnw==","signatures":[{"sig":"MEQCIDJP8nkDg9KU6tqEaf6qKbC81G6GjFpKrpIiFcH2dTd/AiALIJX2s07Wcc9pG0PRDC1eK+5csrF3mEwM9EHMpOWTuw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":663520,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgXKdpCRA9TVsSAnZWagAAInwP/3QzSZ573qgLE3dnAYcC\nLevg3lbJE8rKAUJhvrUZxumKQ4RIPRJcC0jNuXKb8kb8ANLhjNGOsBmDqxWi\npt99kWVUfDOAcjGGv/Rv/rDUnCN4IsrMntwmpwUNTJ7q1yaeTEcLMcjnA9aF\nf9O+vjPeCpUQZW4YAbdelUDEPZGcACd+1kxF2yL12JxKwbPgo2GLPSUt3gtN\nX/E8A95iNrgWucMK34asZ2t0JKCTvbub2GxaTwlcQdQfCqzhGcElpctPr4NT\n4EJtB6s85CBJjOtNZOjCP88UwsdjxVdt8yv6J0RH76Qut1jEFarEovidRrm3\nycuK9yqgfLcMw36fMVszhUyHHXBkKBHc22Ilx5YUtnT3tj9EIvBQfVTWbA2y\nCOtGLrX+CpNPhoxnsj9GfX7PpgAljSTDmzfUbF0liwsS3lKhy8S6hx5+3v1p\nl6xWmEQvy3Lry9GUatG64PKixrKLAD24DQg7VpIYdpP6BBr1Ebqin4A2SiqH\nhYGrbqHXYcRb9CC4K5WHdcgBeMuhnw1fZeV1WDu9x73escp6Z5/6RTfO8Ruf\n6BIn1Jn+5qBrtdgykkuE9uCmVCVWgYAXQiXp9VD3cUn3ckjREncWXbDDXXj4\nEKH/mxK2WoGjkJPa9/eM1Mg05Z94hwjUOHJx/IzVeVdwDWE78/FIJkL5Vk8W\n5JUL\r\n=mzV+\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a0a70ba9079e7b5c2b35f5e54c3c929ff69e73f3","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.77","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.0","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^0.1.125","@balena/jellyfish-environment":"^4.0.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.2.0","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.113","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.15-renovate-balena-jellyfish-environment-4-x-a0a70ba9079e7b5c2b35f5e54c3c929ff69e73f3_1616684904675_0.6561622132084473","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.14-renovate-balena-jellyfish-logger-2-x-cd346ea7f0ae04587c208a37f579927761cedf41":{"name":"@balena/jellyfish-core","version":"2.12.14-renovate-balena-jellyfish-logger-2-x-cd346ea7f0ae04587c208a37f579927761cedf41","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.14-renovate-balena-jellyfish-logger-2-x-cd346ea7f0ae04587c208a37f579927761cedf41","maintainers":[{"name":"belodetek","email":"team@belodetek.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"cbf3b3ebc4f95ec195c89d05c90d8cb780af91e8","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.14-renovate-balena-jellyfish-logger-2-x-cd346ea7f0ae04587c208a37f579927761cedf41.tgz","fileCount":100,"integrity":"sha512-8ih3DhKFTCqiNozshbI9zWvKYRzpuHiaXZ6JhepDegLOH+ETgN3ALhXwIUhFK67X3Vzd25UXjLj6jCNzcFu/XA==","signatures":[{"sig":"MEUCIQCsSTr+7FSwmWkBhXOjLrDeVN0eL7gzol2MutXjwEkdQgIgMiofU75n2pP1MzpC6Z6dJ2VfH2a8NljMyZe8guEbPuM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":663416,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgXKeECRA9TVsSAnZWagAAD34P/35gf7CIhOCKoo9Z/1Y+\nop6lbxVMpu47tfWhR2AJKC7TqxNYin3weVsA4h1Eb7/O0hnjispmNQAnnmz6\n42yV0rfYpRH1wDR/qWDV45ksR4mj+9IEB0LGAaPrD9DmtdDKsaQopYFKitxd\nk6+GgR9c62srTB5YfAznPqet+s1QdEo3+cfSAVc82UdxznwE4Z18GzLj/TXG\nC6DcEIstHdYqKQX0vWBHaei5UzkkHV8fBwZSGEKQs+/8q2pVRpRVdtFPu+5Z\nvSLDKinnm3+3GhXmDBf6QkOjByPlwWlq8Vf0O2Qis/DsHoeyYJqTrul4UEpz\nxrXT9++GotPGD+ZE1kOlG6+D8YMBIoPsUieUdDp20ue8MdNdJ0yUtX2gwi6k\nqFd5addyE2ahwYCkpLSMk3VuKlSPHbO/NXUbSlLDBOMwFkjVa5YQLMaFfjRa\n1kYLuMfdi1WgJJXHyR2EzjHYQR0wUpkknjlrjXfds5DKj0e/1/9R5ww6fCi0\nSpbbda+lLW2NiTpm5D6c17ULmEnG6V3J2ENhCGXd393MT6y9ttxZ+Fe4elFu\n5A+jp4WNdDp1e/P/X0QdbqbaygMUHBzwvZypLOFe09Tqa3QS+LmTM+1pob9S\nE0GmNHO668mGYHx/rKkYpfNaCkHGjdeauPcyitIDaugM2/7fd8bw+CZiQrj2\nmMEE\r\n=aAoL\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"cd346ea7f0ae04587c208a37f579927761cedf41","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.77","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.0","@balena/jellyfish-logger":"^2.1.10","@balena/jellyfish-metrics":"^0.1.125","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.2.0","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.113","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.14-renovate-balena-jellyfish-logger-2-x-cd346ea7f0ae04587c208a37f579927761cedf41_1616684931679_0.21437414495561802","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.15-renovate-balena-jellyfish-metrics-1-x-a29200eae0c286cad5adaa451e1e7501d4df873a":{"name":"@balena/jellyfish-core","version":"2.12.15-renovate-balena-jellyfish-metrics-1-x-a29200eae0c286cad5adaa451e1e7501d4df873a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.15-renovate-balena-jellyfish-metrics-1-x-a29200eae0c286cad5adaa451e1e7501d4df873a","maintainers":[{"name":"belodetek","email":"team@belodetek.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"658d759ca6ea039804043f405e19a1fcf9528ba0","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.15-renovate-balena-jellyfish-metrics-1-x-a29200eae0c286cad5adaa451e1e7501d4df873a.tgz","fileCount":100,"integrity":"sha512-JUjGIXs9pzMXntpmVUZDYc5j/fDPQBst50huUSxJ2R7mj9u83jiGs+HS6THDJaMEPje46zgeMnbVIWCZWu8s7A==","signatures":[{"sig":"MEQCIA7kQsSYl/sz01Sbk3FzMcfzWOhfdR2YeIgtd3xLd++zAiBkM4xnnop8638mxUDEOTxHPjbpIlX7HbX7j6d5x637eg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":663506,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgXKfoCRA9TVsSAnZWagAA9D8QAJAMBNzyWJ6NnaYPAcDI\n2USGUELC//G3Ckbss4ymEQhD4/E/zJdGPQjm4BtZ29WA/55nVOWaNk1h9Tk3\nTUXhNty89w+uAzhH4E4i8uKnPt/W1YjD+r4lkk/kR9ln04N3I0JJ0eIKVALB\nQF19on2CtVBFSlrGPiFmbaPKtSG/qVGOnJCEr4eRL6anKCVifcQVhmJI4XhI\nveS3+BncwJsoWJ6O5dTcW3gH+IMPGXgRaXkAJhlDvij6oim0Ndahq95/2pHa\nB5/HAykVCx4aonMkUx3YXn80lLgIHZDQe1aNmMw7OuewsOnh+L82OBb7hiJL\n6rN7w5g861Sz8ruuKsWKLaGo+sq7RGFBYO929N8jXfx6cQHIDAyR0d65eREy\nCE2f8Jm/BuUKfjVnVTpSxQS+XwFZe+45JnNl/j7l3GUsF9yogQyY/a1/cSjD\nhY8qlvpZTPlrsApCdGXhJvSO6j42Az4qHZ1JpMBiLW6RYOuvC7NgMsStjeDY\nJb7gVP2ihs0ENNq9d2hcVAjXRBdcCnHh4efDTG0+w+SQ1P4kEEDR/ucgW/GL\nyXXVdMr6rupxNj6kwTcz2pHaKsbVc698s1BAWLIB3EYovY9UXHxOnPaLznDf\nYwhne0y3VJlNxh2VTNnQvauNwzROLwDqJix0aYOrUHikhPi268QzG+vjpXyx\nhFJm\r\n=iPFj\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a29200eae0c286cad5adaa451e1e7501d4df873a","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.77","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.0","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^1.0.7","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.2.0","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.113","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.15-renovate-balena-jellyfish-metrics-1-x-a29200eae0c286cad5adaa451e1e7501d4df873a_1616685031656_0.5971587580981255","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.15-renovate-balena-jellyfish-logger-2-x-84e1d413b57d67da57ff913202b5f1a8972aace7":{"name":"@balena/jellyfish-core","version":"2.12.15-renovate-balena-jellyfish-logger-2-x-84e1d413b57d67da57ff913202b5f1a8972aace7","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.15-renovate-balena-jellyfish-logger-2-x-84e1d413b57d67da57ff913202b5f1a8972aace7","maintainers":[{"name":"belodetek","email":"team@belodetek.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"34307e069ef07f1fa40713bc47d5b8b81668277d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.15-renovate-balena-jellyfish-logger-2-x-84e1d413b57d67da57ff913202b5f1a8972aace7.tgz","fileCount":100,"integrity":"sha512-YabBHklZ5Qc1GvxQuGKhy+nDhlDi67jYB1GuF5cioKvafH4atoYqdg9cmhng30d+ercjywEbYUZ+kxudbdXf5g==","signatures":[{"sig":"MEYCIQCvDBnhqpea5D/D5MUocHIpR8wM6XKbRunAOBjvr/wsvQIhAMyMPyEV8N9JvOFzmtEPGr4CfoIWs8JN7vEQ9ICgZ00/","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":663505,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgXKh5CRA9TVsSAnZWagAA1qIP/jDB9D8hAo4694Gnd662\n3VJzSfdgq2807wuiC8RccjW5mC0ydA5V1y3lucNbjt+SkX66RnjG9VKzL2am\nYMQLrdGFwxX1PCXoLwnWcsfGdYK0OdZSzwH2jhKz3/If5wKAPwRMeS7Jra00\nomUvkNR1j2RuxQxzXWkxQRoaKkR7DZgBjLa466Vc01hPTBsLa4IR8MNzdEOQ\nUi67MRDGEDbvHTK+dVt65QS6kvtJ7/LmY6ZI2o1EJbIl6JbOLrRUInLpPkEU\nO8w6embZPcEwgLiJkYfMi0OAzdwI3MNEBCydn7pE3FAkZAPAJdWJj3aiDUEn\nf871OGSz22dNcZjMF4OGKipA3vaND/NuBj6KhPY2cEwFBBNBADCfdJovDiBH\nwwJQIAoKXtF+tv2n4SQrEmgh0+hC6EoGGsnI9GMpdSh2WnExofskgRq7aVP1\nbyvEGr7NHFQapRCK6Cto8WAZPIP5f6+mCZNZOVxlBGF9VelTc64QKnilWEN4\nCRYIwn0TDLlYi6nUWAZpjVkNPPIQuHNp67wAq/RZRzeyKz1e1GIe3zx/3pfN\nqvMsEKlcXiEwqnMogpb57yVvJYtnIK4APAOvljvu9ssy/jYLBsaERrCnIWUf\nkDly50GFbjKwFsztFtcANL9Zf9gmvOhv4x4f72sFusb4547hYc+c6xH+4ixs\nkarj\r\n=yKLm\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"84e1d413b57d67da57ff913202b5f1a8972aace7","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.77","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.0","@balena/jellyfish-logger":"^2.1.10","@balena/jellyfish-metrics":"^0.1.125","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.2.0","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.113","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.15-renovate-balena-jellyfish-logger-2-x-84e1d413b57d67da57ff913202b5f1a8972aace7_1616685176785_0.9607929529435351","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.15-renovate-non-major-b9539893310f72433383ae8ba3d06ff2ae386d4f":{"name":"@balena/jellyfish-core","version":"2.12.15-renovate-non-major-b9539893310f72433383ae8ba3d06ff2ae386d4f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.15-renovate-non-major-b9539893310f72433383ae8ba3d06ff2ae386d4f","maintainers":[{"name":"belodetek","email":"team@belodetek.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3baf8622638e160128c7a6de865ea6136ba31206","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.15-renovate-non-major-b9539893310f72433383ae8ba3d06ff2ae386d4f.tgz","fileCount":100,"integrity":"sha512-Njhp80L/3tw1krHLGVp8Ta8Kr+a/n6bTO3mYnGUQkF2yBuJVXN0S96hInruHqpE9G5avyLWV3RCXT3exntK4Gg==","signatures":[{"sig":"MEYCIQCR3mqMNEvqtzna7h2cDwCKivGnAFwYfVxHCPNY8/gnlQIhAKdU81RZrN5JmT8VhJfSGNF4aqPwTmT5mh45MxHqi2Vo","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":663423,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgXP8dCRA9TVsSAnZWagAAoVcQAIPv8p2WlH0dPtD55XAw\nHt3RDRcIfZXa1XIEu1O52r3MbPXOag+lxNg6HZy1i0LuPsvXcfaVz+xnG2yu\nzXnT/POG678sAOrspr7eiZiPQGB2/ZAH+L1Dgl41NaAY8NjATQyzVbUjqUne\nXfgC0Yt/Ok4nHtQmi17tRquMBPliayknTk6lTzzQ87s4BYm/Dv2QH0B5r8X1\n2ASm+og3S4BlYAR9Re46JyG3+AdkHridhc+obKxVpGAK4hNn+PuXwsM7HDcj\nn0ZHizAcj2eUOAaDMKk4mFYrEncJYsdpLrjFvYVj/46a3XC8dtJBxHrXnk3x\ng/62fcmSWxXg3KUJsinWybyPRewluqT4S6yP09dkuX2UcwnfXxXu+7ZCBOYE\nlAGVLHs1DtMMLXvxgL6lYQdd9h1PAcgkiE3S7EZPRUmEsyJ4vTQnBbNzQ4Sk\ncv5cmYjP1sPgrf66pMgiWvaiNdOLds7te7ZvNAkyU908A3RIajfpXHvCpj6C\nsrECJS5LhlEUKZgqb9eSKge948/CMVwzf9qXx4D9Xmf12Pm9laj8gww1poW4\ny8U5Q5Zf7EVdTkPtnPPFm00pic27q2zG/VurpeJnL2G0STuqNUHw80tCCNlN\n9KoQBeO/w/SzNfIp+9xWMYek2gFjke6oC6jT+J8Hy70UkIUCparKUxqr6FOD\ncz7P\r\n=Y5uX\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b9539893310f72433383ae8ba3d06ff2ae386d4f","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.78","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.1","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^0.1.125","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.2.0","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.113","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.15-renovate-non-major-b9539893310f72433383ae8ba3d06ff2ae386d4f_1616707357304_0.5850222112980175","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.15-renovate-balena-jellyfish-environment-4-x-462d6ee999553bd05376f3148a70de27e854e455":{"name":"@balena/jellyfish-core","version":"2.12.15-renovate-balena-jellyfish-environment-4-x-462d6ee999553bd05376f3148a70de27e854e455","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.15-renovate-balena-jellyfish-environment-4-x-462d6ee999553bd05376f3148a70de27e854e455","maintainers":[{"name":"belodetek","email":"team@belodetek.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"904df52066079219c67774500a8c0a166d4c5216","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.15-renovate-balena-jellyfish-environment-4-x-462d6ee999553bd05376f3148a70de27e854e455.tgz","fileCount":100,"integrity":"sha512-5hgciIeQ8zj0QnInM5AKOpxuCaCQ3awNo3BME4uuiTgBlm9W/GHdKYUotWVw4WNeNOPxcxCsQoooPY4L6Uu8+w==","signatures":[{"sig":"MEUCIQC6/rHwHWdK3Q/rHZqcY6SYeh5Ib5XQRPTChkOqkckDUAIgJ5Xddedn7CXuD33KFZklyMuOnDdnma2Znk1R3HCrXbQ=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":663520,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgXP9VCRA9TVsSAnZWagAA6aAP/0ImkAag0IGrF8Mw3K1Z\nhZckYEcAlZE6LT9lEZh5SVaFT6B36atlNFRFRDry+tA+SvbfmNgJHG5QBPzr\nBbd4Y98v5Bq6muQ3rgPbhRbltjjG6uH4PW0Xb3bt7ntQC1OLSeoXIlSPLS3c\nUileVPEGVO2xmRxlPiy4sgJNOllIVVuT0pKrWnetsC+BmdnKMg3IexPTPLz4\nyarFMIt8M0lc0jLeJ6RT4C66oGZ/pUmX8YuqOFWdlAqKrWMF4ppidE0CeL3e\nDGlx1dHD383ZSYJ8ba1oKcpFrU3LZcgWNqGSpMzu4sTtbq6DrOv3ybeWPVww\nbQIDcq8wUelO4EzBr7F3XJ/DIlABJ6hj7SwV7XHELdX0+mJ6KBWBIb/HXpmf\nuUcy7FZ5XK3j448MaqK5CCiaIuTVhzaPFy2uwJXxIMyufessd287VIHxTNwv\n5sbHObO7APEopMzUDS3Nz9t7K41UEAvK3EVKSFh2REAVKg5igOF1ixZUVQ3x\nBSe448ISY5/x8IUYsazXis9+YAUY/Sm/sAJKhOofQlcSsbbKIzV5+RsypuB7\nJX4NyoiUONk+cpMuJ4J8Z5zifmC/bhVficowHL6cUp1bIL/n4Mf/EMzjv0u/\nTU5hw8EZhNN0KdBHX9u/vygesjjtmE6WYt+wVGcYztLuiudOZC+OxI4RyM3M\nkCyV\r\n=uNn3\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"462d6ee999553bd05376f3148a70de27e854e455","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.77","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.0","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^0.1.125","@balena/jellyfish-environment":"^4.0.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.2.0","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.113","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.15-renovate-balena-jellyfish-environment-4-x-462d6ee999553bd05376f3148a70de27e854e455_1616707413071_0.3533980703971882","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.15-renovate-balena-jellyfish-logger-2-x-9169a26a3707684316f78a46d7f12d00d0e03010":{"name":"@balena/jellyfish-core","version":"2.12.15-renovate-balena-jellyfish-logger-2-x-9169a26a3707684316f78a46d7f12d00d0e03010","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.15-renovate-balena-jellyfish-logger-2-x-9169a26a3707684316f78a46d7f12d00d0e03010","maintainers":[{"name":"belodetek","email":"team@belodetek.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"cc5c48f2c871c16968d29106cb18cb6a59640812","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.15-renovate-balena-jellyfish-logger-2-x-9169a26a3707684316f78a46d7f12d00d0e03010.tgz","fileCount":100,"integrity":"sha512-sk9cnPL28vxbrC2z9frROEu48DfqvBQQS/iPV/luCPXG4JPJLj7BF8eqAR5yg7uIBzVJJDTEM735oFDIkYFWhg==","signatures":[{"sig":"MEUCIAbUnvrzg211khLdvb8ehzIf98EXphHCg/SS4xBT1YAWAiEAk/inyby3oVSx6e4UEyDMRqFbKPEr849Jg66VFW4tFLA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":663505,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgXP91CRA9TVsSAnZWagAAd40P/iFEPM2mNZJ23kLCD/fR\ni0YOolYztUmEx/67qqOnClK6oBQ3kWf+yejAoFIUPs6shmRk5w2OEm+3axQ3\n39lx1/lN20xp6i39mO8f+jpaqTwY38+wEN2hq1iMHTs+IpeQR0nPK7VAUiH6\nW6c0JCfTgUrDtOmBy4mFTFTUUDmLSff+qvSBcvF2sERLO+dg6m92X+vYf9g4\n7f52tlqBqauWuPU1lFtYc3gLiq84PfLfgwSgZNcJ3oKZ3lo5baV9jA0oCriM\ny7+dxp+twtP5qClIdq+vW3gJrPx9gfTShduCnFe74TU6JPuyDCFs92ZnUF7o\nn1y32sF11RawbwFqdWdzx8gk/znQRz8IvSJl8RK10wsk7/3yzo21gLlLEfxE\n/AGB6+ecWRyZpBGPtd4DhpiIIZzlvWUOKtuvf7iY8bcDDO9lMzBf0mChHeBs\nBT4dLwuEtTV/WsIdYGdyGPIhbHUhRhyUWVYNeFTVJG7Ggwf20+8FTDn2XmXf\nmpN5+cmU4rAee7cBbni1H3YwXRYmDJ6jLL4FFTZsCX1GmH5Yy4rzcAyvYqYS\nZGhBEQ2hvu/bYxLvqLqnIag0oZzBWeWltDsCusWwfxb6+VQ2Om77nO/urQ2A\nOrdTWaSgGlB5iqCCtAtGF2G+sOPQy9wFTzfiQbm+lEDS70fcplTgzDFYF6hv\nKMLW\r\n=uzu1\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"9169a26a3707684316f78a46d7f12d00d0e03010","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.77","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.0","@balena/jellyfish-logger":"^2.1.11","@balena/jellyfish-metrics":"^0.1.125","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.2.0","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.113","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.15-renovate-balena-jellyfish-logger-2-x-9169a26a3707684316f78a46d7f12d00d0e03010_1616707444270_0.4100888671242613","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.15-renovate-balena-jellyfish-metrics-1-x-eac9c722a1a6883d4a24117f4f288895c98e884e":{"name":"@balena/jellyfish-core","version":"2.12.15-renovate-balena-jellyfish-metrics-1-x-eac9c722a1a6883d4a24117f4f288895c98e884e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.15-renovate-balena-jellyfish-metrics-1-x-eac9c722a1a6883d4a24117f4f288895c98e884e","maintainers":[{"name":"belodetek","email":"team@belodetek.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"11c21ae42f70fdd2c0e267f4768fc804d807992b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.15-renovate-balena-jellyfish-metrics-1-x-eac9c722a1a6883d4a24117f4f288895c98e884e.tgz","fileCount":100,"integrity":"sha512-saRJSUSZjAAYxujBwh9GLvKz9XHxWIakxuWFiD74ycZRUTxuPhGIf0QwCQHPkwaKvpI8dLsUWHeE5goEAsOyew==","signatures":[{"sig":"MEQCIH42DstDFa4Dw1N+gnkFjsq6VLWoquFQ+1ODiSxTitf7AiAZ1xqV/A20gvtQI7FTUHV9ZTC2Hy353HImFWF0X71RfA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":663506,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgXP+7CRA9TVsSAnZWagAAR5cQAJtetOXRIvWPb0+Aqf9w\niw3fYIijtP2ekmZpqO01Cp7juW2MpEOuWDPyHTyj6rtGb1ZQweBxAUnTjvTG\nUPdMEW2rThrBSDx/tyblt00csDwiw9hdWRXIPUPfKIlIPDMMCULY7EctznRV\nsl/aD4EbQLTsTJzRgY0XPfL0B0Prwbpiz2YnDv7NckL3yByjPriu1rAL4ZWF\nPUPC1lForGdNr9f6sbiqadl7U9D3dtBpxS4Vf0lahh448xCBvkniHpVhuRp0\nRchNTiFchDTlco/66BvPYUFwz5EcRzMPCQ+qDLmH4FUlkAd2xxOEE5KxscBe\nY/PI9vvGciKXqdygaUmRIbYocqeGJqK9bXelIMxXuNzEf3BxHxOItsOvThbj\nJjZvFe/swCQPOsSbaq30UvxYOm6NFz/7ytagHm9E2x768++eS8/5MaTZnsbT\nMjhi/Mk17sQEVcomZ5gGhcu1VGquAIRwuozg3jtNtFaz7uaoZAQlPZNWzRjd\nAFhc3Zrv3ZPvA4VyCv9cd8IEoXOXFctteei2gX9xSIyUwg5Jui3Hmpyg8p8x\nnUJ9WMNrHJX5U7By6WGeaqCdBP9DovTuNNOhTm8pSZe2HDhbAg7zznwuy0Kl\n+9v/OJlFTnTgALzAf0EXMsuijuyf/kuLjFOyR3RGLmc367DAdaCj0U+6eMvS\nafZj\r\n=z2f3\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"eac9c722a1a6883d4a24117f4f288895c98e884e","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.77","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.0","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^1.0.8","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.2.0","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.113","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.15-renovate-balena-jellyfish-metrics-1-x-eac9c722a1a6883d4a24117f4f288895c98e884e_1616707514716_0.19288482237270177","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.15":{"name":"@balena/jellyfish-core","version":"2.12.15","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.15","maintainers":[{"name":"belodetek","email":"team@belodetek.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d691a32c8a3687928281dfcea70c8c7256301b4d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.15.tgz","fileCount":100,"integrity":"sha512-t4qQX7scjBCObbfZ0CXGM69qaquHaShjC3b6LxYrut1ylevApz9k55HBsyg7xTrExze2CkyD2VRLJ451CoDO6A==","signatures":[{"sig":"MEUCIGHJ3fgQbYxjMo9Oly/kYYAloNxDwXcOYLjnA0R2BdcnAiEAl6ikfUtNC9LztK6MbbYV4/bFC0RFYRsP3eaURqP707E=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":663363,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgXQCzCRA9TVsSAnZWagAAihQQAJsbPmFNbsc8K4TuhvXr\nu2VGfWP79qXYdeQYbBdfm1ubrGLJcNqGvgZTJsImgZehKMctEEqMUlrI6Z/K\nfpebGomDHLJ3+rdHJh2g6Eos5HZFmdDyWosRNHnLw3oSyP2UyqQ/eRDBkWTr\nvnvr2UyQmCgH+bH5xObOA8y0eQ19C/+qmSJ7tzcWDVtkWJhwFGtBkh3jyW0R\nGjCRSL9el+vV1VOLH3tqlcDTYv9ExTUO2Pks/9xj4iQf7s4Tmvwy+Olms15T\nH7vL5ou0SybKNbBWnSonWtRG0BcLk9stXBjyq6nmN0I1VR+k5u/GtVxNYrCj\nXtEYb40SdhIPSC4GfOP3AWgxb9PaXUPdElIeYeM0DZwc2tyixe4g6TI4bTtt\nJ37hXXIH7ODWEFal8bZemZyHFtrC3fRQjQigPRJmYhBotwnAzE5nE/ugzs7U\np9HKZ2eo2hzu/Ntf2X0GBTRmAoduj42CtTQTvI8LrGjmtTCAbfLZn8AV6otB\nxVnNlPt2KGpHX3qGXHp63NZCYPjRZSopPpqU6Srlh8RNeJ+PRoxZQMLm9DfM\nWcGpyrJBlNEAp/VrlXdTK4vpTfXfArwnL1lFSAQLSwp838GkiM/vlMQihfGc\nmj0mNc5rSeK1roJsAZWcyPGlc/51fMcriCmbZKJixmT8feZEfO5psh2AtpXS\nEvKu\r\n=FTZl\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e0cc70ddcb38e936abe6eb3c77049e40e4e0fb82","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.78","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.1","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^0.1.125","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.2.0","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.113","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.15_1616707762537_0.38672251643670785","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.16-renovate-balena-jellyfish-logger-2-x-10911cad6463b2b9339fda28c91c245e1d4255ed":{"name":"@balena/jellyfish-core","version":"2.12.16-renovate-balena-jellyfish-logger-2-x-10911cad6463b2b9339fda28c91c245e1d4255ed","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.16-renovate-balena-jellyfish-logger-2-x-10911cad6463b2b9339fda28c91c245e1d4255ed","maintainers":[{"name":"belodetek","email":"team@belodetek.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"192bff347092f422e6188f9f7a615bf9807b10b1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.16-renovate-balena-jellyfish-logger-2-x-10911cad6463b2b9339fda28c91c245e1d4255ed.tgz","fileCount":100,"integrity":"sha512-cL0OzfzAh01+bnRNwNA6eeqZWEh8oxXq8Iuqsd6rNt/hPVYhCmdlBcWryPb9F5IcS5RBk4DGCeBQ+rxeg4usbQ==","signatures":[{"sig":"MEYCIQDWh8dvyP1KnOoyoPyyEPw44iqg0c0v5CJ3vEZ277NlmgIhAIG1y+ygIUrew2t7Ft0yVopEYlWC4tq9/sONEXaoZQmR","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":663856,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgXQHBCRA9TVsSAnZWagAAKxQP/14AGjq7cZHMxKnhPSH/\nKuBvlRnqS+Eq1EBP3JD26aeq+CEu/82tsfCwCK5P5g0mTRTOQd3yvW3uk9fQ\nMrO5blTtS5IAnxQ29OmLFrfW45CB/fydg3NjThaPyTvznKLUfaWtdJy72DZS\nE8zw7ZWTFIu8/D4BUaLk+k6qR482MRKnWB8duXaCIslGqoW4MmvL5PEeQeu6\ndofh2bBvRFTy6ZVW1aEYJJ65pCeLjALtg4/Eeg4Tsm7VOCsQ0TzwtPRWrkF3\n8Zm5NR4Fp7Ng+0hGLdBULZ8/FYPW5iyxQ/27N+c2i9Vqn3gu+cdvn4gVzzlZ\n/UWsThq0KYTWJiCvvE6FTOvYblnnOxBBB1CAG/2ecZluOO2ILhh0gu6vJYrU\nkBz5k5oo78Nsos0oaWTGefomGNrL/uFyl4CwkkFSiu8aAtvZCQIQs1Lg90k7\nYw08Ukf9ZunIEoOI4BqYDaec2vFdpDgXQlwRRPbyqVVWa0iT1Vobe/HuxBay\nOODXhGsIB1vmODa21GWNQ909wCIc/a/nExHLa1wAOqS0hFQq8T5Y6BVIIPLz\nMPgTL4Zkv2NRngxGvedkkxlzeiv8pOwtD9JECV60StP9m1znryv2ET3lqZtm\nP4kSNod34/4DkweleEz09HRg7zXdlGkYJbprVQPNoKfB+0xJ+mqxBO1MsT1m\n5JKn\r\n=6kVV\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"10911cad6463b2b9339fda28c91c245e1d4255ed","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.78","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.1","@balena/jellyfish-logger":"^2.1.11","@balena/jellyfish-metrics":"^0.1.125","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.2.0","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.113","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.16-renovate-balena-jellyfish-logger-2-x-10911cad6463b2b9339fda28c91c245e1d4255ed_1616708033125_0.496427990847337","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.16-renovate-balena-jellyfish-metrics-1-x-55efbcc9a70c8b6562901323bd63b72631e5295a":{"name":"@balena/jellyfish-core","version":"2.12.16-renovate-balena-jellyfish-metrics-1-x-55efbcc9a70c8b6562901323bd63b72631e5295a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.16-renovate-balena-jellyfish-metrics-1-x-55efbcc9a70c8b6562901323bd63b72631e5295a","maintainers":[{"name":"belodetek","email":"team@belodetek.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"eff8ce4f6976c27124104b460a30d14e7752357d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.16-renovate-balena-jellyfish-metrics-1-x-55efbcc9a70c8b6562901323bd63b72631e5295a.tgz","fileCount":100,"integrity":"sha512-R1kAea8C6L3TwBTGPUSYRmVcQ/06vJSVd25fsyzghxTPRPCJS/kMyFN2AQmTPYM6fkONI4UANbJkxyXaJqvgRQ==","signatures":[{"sig":"MEUCIQD5H/L6xvFFZ0+gDUt/uqvQlD83QHvftWxBccDUn7x9FgIgVyHuBWP9lPQfIdi/dCvu5zVjoiGTf7mUpqEq2TYLIIY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":663857,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgXQISCRA9TVsSAnZWagAAwK8P/1n0//Qfd1CqHK7qIMzg\nSaivPqYzeZINqROaKAS3z1sqZN7I9Xz42PK7gbXBzjZiZ2xm8rGCujzRRWKr\nFg/Wderaa6iQDvV0lhgb0o/W5SjQs1wLZwclVXkzWNhr0oUyd6Tmq9Q+yQAK\n7N3DSfkrTdYKtqyG2vuvxaa0U5cEqo4QZN4/S80cznETlACUHTRieRkpfwL4\njE4swzFEjZtji87KZ48vOwb7x2/ooLsQEq0o17S0i02LI1GtnLie6Y0sBF6i\ncMvNYZGC9IAbw0GBNlkGuCotT7A86hESpf9FTliTuLQgLAUW/r3znsqd+5nZ\nWFp3mXghT2a0QXWPMc/0ayWWS5egSiNrcjDr31n0k4X68cuKLvMFQzRurugM\niALuIBmocWeujtarPhUWyoPFHFAUlBKc7btJ1FfDxqv7IgZxPAe5yYEFbPGP\nZNvQ1aexkXUGnXAzxh61rlLkExuy2j1MQno2EXxLCR2WVv5nbeQuEkodf6S8\nkOmnr3xbDI4ZHWPkHiPTpcoQCKvKjUrNFI6/KOmDn2b990oL8EtI5sw1lq8T\n4oXmVq49MxT2erGuH1LjRLJoN10vvgle9DZgdneZvIPG2piI5LnbygL1hQ/F\njiOSCF+y2+ntszsaheoEtEvnXQMJvHYhKZqYSgYWr6TbF9tNYLk6W4uLPXHt\nVD6c\r\n=h/hq\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"55efbcc9a70c8b6562901323bd63b72631e5295a","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.78","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.1","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^1.0.8","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.2.0","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.113","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.16-renovate-balena-jellyfish-metrics-1-x-55efbcc9a70c8b6562901323bd63b72631e5295a_1616708114275_0.7036934627945364","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.16-renovate-balena-jellyfish-environment-4-x-c487b57677382e858296d86f50e63fee9b77e638":{"name":"@balena/jellyfish-core","version":"2.12.16-renovate-balena-jellyfish-environment-4-x-c487b57677382e858296d86f50e63fee9b77e638","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.16-renovate-balena-jellyfish-environment-4-x-c487b57677382e858296d86f50e63fee9b77e638","maintainers":[{"name":"belodetek","email":"team@belodetek.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"829050a79ceb7d87baef36f05aa53fd277701cb1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.16-renovate-balena-jellyfish-environment-4-x-c487b57677382e858296d86f50e63fee9b77e638.tgz","fileCount":100,"integrity":"sha512-nQ3u3niK7GIsn7xQTCM9727mQyzJVKe5rIxPo74pFGSUTUKVjQ0RZoKQbtRijYwu5PT7SDRocXIZETvcQ8HjyQ==","signatures":[{"sig":"MEYCIQDxmtVkOcgM38FJOR6h7B+6lO0FNPUy99CkNX97dBAtDQIhAJq7hR7upFUXIC03U17OGsYKt6HrG4pJOwEKrPVZM7XF","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":663871,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgXQJJCRA9TVsSAnZWagAAhCoP/jTxVIgOsXL4/amVCpgt\nEabF1jRUXBs87fKUtBOMum5WdFf9YO3DzxxS5PRpaWWUvIdRYZQpUxuOKIzk\ne5eG3DpfKqTj1qGMzBo6i4VuwReM8UZZ8hG6aPASfVFNGTGVTT64YkVRlqwo\n7DlJ41cA3DM/lW5oDxMOoFir6RTQBPfh6eOAZPUprWhBILpm731NAJMX8Rq7\nSYw4awvDg0bBzjIF3kDPendLEWZw7wJTkQCN6nEmlH+GFyHowFs7mWT2MIy/\nHJlatuwWfjMarI+6uC4dmjk2SeuBepkaQpvRDz6oQNUCFRoL3SuTJOsvM5D1\ng07VHZf/ZiZedB70RExyY9YZ5C9QGiXA2NQ4iu1FwgF3QQpcA7abptKl7mC1\n7eeiOWdip+g8tpc439uR09SrOHJyKCsPj17E94VvHXsN1Jlo/3BVV9lC3CCm\nF6FGRVyhO9XTXNLEYwz97QzaytI6x7yVp0SjlGlLCyyP+mz6INfbyN0CVRS6\ncG1qgtYKySFy7POpFJqsYjjZRTI/N2zuVtMao+L1J+o9ZMC4Cx6LVb+ioayZ\n6y9DqvEI3xRoZrXLMSOOy4UrG8gHbXZoh4oZpWkoZhFOegzonBOxP8WvB8cH\nCezVTyu+6CnQamywV0qsrDZwsZdyB4n9LP7D9xjEi73xHWtxpNkKy8FOyGS3\n7VIr\r\n=4YA1\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c487b57677382e858296d86f50e63fee9b77e638","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.78","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.1","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^0.1.125","@balena/jellyfish-environment":"^4.0.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.2.0","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.113","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.16-renovate-balena-jellyfish-environment-4-x-c487b57677382e858296d86f50e63fee9b77e638_1616708168708_0.6988027467856168","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.16-renovate-balena-jellyfish-logger-2-x-3b8349cbb0667d6955421eeabd638795388b5714":{"name":"@balena/jellyfish-core","version":"2.12.16-renovate-balena-jellyfish-logger-2-x-3b8349cbb0667d6955421eeabd638795388b5714","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.16-renovate-balena-jellyfish-logger-2-x-3b8349cbb0667d6955421eeabd638795388b5714","maintainers":[{"name":"belodetek","email":"team@belodetek.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"68ce5b03f787e0b6bdfeebcf5c7c0ea878b1e996","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.16-renovate-balena-jellyfish-logger-2-x-3b8349cbb0667d6955421eeabd638795388b5714.tgz","fileCount":100,"integrity":"sha512-PghcpPFCC+wTtZYKDudhslj7Od9lRSu8/gA3tXAC3jGDttg3CHWcmlaGosMjgSHCFK2kPRO+xIDJZFFDQ6Jd3Q==","signatures":[{"sig":"MEUCIQDWypMjtAjslE9iw9Zb4+pglORXTQ9EUKPDsFr3SqWZSwIgbI6h6ZUPQ+W1t/12rUm0MWrPDV0z0RCg8ug+8rnNqeA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":663856,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgXQOeCRA9TVsSAnZWagAA9wEP/0U6DhHRfpsa3axfYuc0\n24nNEWi8K0QjHYiDt9LOPzP4kq4LgwdLx2cK0E8SmZa8zc3YzQ4HY92DhK7c\nfI9H7NwPn/rYsJIP17+UyCsbehUfyG7eYqfTRMRzeV56xP4Yl9gylX0mzLA7\ns1E2rRgXhkGQ2HUhEp6kfIz4Xd0Ma8U2G3f62oPZ+stjfgRPPwPCtlu4e8VO\nXbs7xS8qAKXctpebJ9H2o90MsxqJvDqIJfxW2jWeskRDpu0m92yHmEyDr+9V\nQOkip/TcUd6gg+QnekZzHnvPF9ZJlu9+d+/o4AJSD6wbjslrzeGRvYWJqNJO\n3mpbgg983BnG1bd20VwBNPNfpwYedqOvtVn9q+4mvPiU8tIMfrzCaX8Ha7TW\nExVX8jiDzZf6mR8h7UA1UUMJ+yENhW7i33MMSotb6HDn8jGA60CUX8ggiJda\nF6KCRuY415IwJIIFbS+Rxu0OlzRvpUzaIDKq8lixLDqxvXIeoNH4mrXrT3d3\nd/vfeaLPU6R+Wr2xUOaF1SvikRUnnL/Pd213IH9yx1N6Ky/zSEau2ZKqaMEQ\nxVaveqYlA3HvuO1tfnNHyfWx/osdVSsmpcbq/URx1s8TeLjC+A918M4knZO+\nedLnoDk7s/d/2zQlg5JO9UblX4om3trC/TfO+iF9kOUtAgtpX4Gzqyw6B+ay\nR1tR\r\n=REY7\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"3b8349cbb0667d6955421eeabd638795388b5714","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.78","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.1","@balena/jellyfish-logger":"^2.1.12","@balena/jellyfish-metrics":"^0.1.125","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.2.0","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.113","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.16-renovate-balena-jellyfish-logger-2-x-3b8349cbb0667d6955421eeabd638795388b5714_1616708510356_0.7353836715593343","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.16-renovate-balena-jellyfish-metrics-1-x-18a911ad65ecd6eb19ea6474a9bb640b059dec0a":{"name":"@balena/jellyfish-core","version":"2.12.16-renovate-balena-jellyfish-metrics-1-x-18a911ad65ecd6eb19ea6474a9bb640b059dec0a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.16-renovate-balena-jellyfish-metrics-1-x-18a911ad65ecd6eb19ea6474a9bb640b059dec0a","maintainers":[{"name":"belodetek","email":"team@belodetek.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"171731a1010dfedcb37fce2b801f91742eb2c9cc","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.16-renovate-balena-jellyfish-metrics-1-x-18a911ad65ecd6eb19ea6474a9bb640b059dec0a.tgz","fileCount":100,"integrity":"sha512-Ta7pvQ56VgNc5uO0ShNQw9HznsRj7Yz3k4esNMoMts0WsWrwjzPh1vVdhT7wN2XMnMIwfzfgYy8BxiROkj7o6A==","signatures":[{"sig":"MEUCIQC2CpWMqLHYxNlHAAmmvX0SYtVVk/HHHS2rhCqhRXkCGwIgYnBe90+5eTmL2AFtp27f8Vs1u3hx37MNC/rPeJn9zog=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":663857,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgXSSqCRA9TVsSAnZWagAAxBcP/ixDazqexHyBhcG9ynFr\nT8tZLy44KeSPlGN2MbhVlTviFSPQ2f+rb0/JFJ4PjaXZMhYXqsy/c8ZdQZVi\n0z6BTgBojQHLpBtelksTWRf7c31HJfHNsT9Or9UtmWMG6MzAisL1O7HbddoU\noUxxZ5du5A13wYdFBbME+8jy45EIIboqg4d20jbCkER6TWu2FKyNAuXr8pu3\nTVVSHsVIIg4KGdgvFDIShFhBiAHVuuHZsv+9Lg0VQWqaAUdA19Gvu5HT72LW\niRr3a1mRafFWeV3i879LJyzAiMbXV2fhEDvpZlh5CNX9DW+lH47/rCLo5Akv\nxLYaa+u5sBUG8uJe0E2697ZjkBC6jSD2wfgdpcNh6wQ3ADJxRRoZmPbYERxe\nzcJahzvOaTKr3f8CAIP+GcR2BhAS7O69Wb3iLM90YszPFTK1uz8lsb3ddeiM\nGBNcTo6ZSqEbAp7IKyWxW/PDvbZCgI7PZgdSitJX6z9BSyFfbMdTDyGGb2dM\n0oqx3AxtIcnDbRYAtDH4lyQ+JIohCCHdxoSpjUFZvSGvjcdOCLD/yCSNw0AN\n2E5FdWmvTCgXS/GHm8rj363+Bc0blcLl8tDx8zJAoqztvVqgbXBfl8/1jDn3\nskd06MXXNJwzxhWIaX4ZP2T/3v8dCrioix9SDjkZICQxPaJSSW2nXYPrLnAH\n00xZ\r\n=7xPa\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"18a911ad65ecd6eb19ea6474a9bb640b059dec0a","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.78","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.1","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^1.0.9","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.2.0","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.113","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.16-renovate-balena-jellyfish-metrics-1-x-18a911ad65ecd6eb19ea6474a9bb640b059dec0a_1616716969997_0.43205363749767933","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.16-renovate-non-major-aa2636ba7b92603ee83125920fdef7467a447470":{"name":"@balena/jellyfish-core","version":"2.12.16-renovate-non-major-aa2636ba7b92603ee83125920fdef7467a447470","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.16-renovate-non-major-aa2636ba7b92603ee83125920fdef7467a447470","maintainers":[{"name":"belodetek","email":"team@belodetek.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"112ca31ffc00190095c8fc5f8849d05aecf8cdd9","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.16-renovate-non-major-aa2636ba7b92603ee83125920fdef7467a447470.tgz","fileCount":100,"integrity":"sha512-/tXRWedjBYsrbbIXvofNijDze2YlVLbIN6320CIhFm3uEaE+0vtP1fLbyelq/TsgpJgCfQ3cmIqKnQOWlEXetA==","signatures":[{"sig":"MEUCIADM90lhp43kbaV0KI3N90ul6DP7gk+wqu/6kk+oSdJiAiEA/M/S5nmUR+hSuSrfDhj+C6wMIyvF7VeYsgtseNE9JVM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":663846,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgXT5ECRA9TVsSAnZWagAAn8YQAJ2o/H0aWTREdINSjO9n\nSzlkvVoPFFmpAf8vf6N2nVCArkg65PIAOGOOXf+qcjc9FP1N/93GZuP/a+2n\nnbhNd7RzjJkuBcPmHDwjx09bPWmXaX/f7XzuwIEPD5kxOU0P4s9/lXnAqDvF\np8jjPcWPr6Ba3YpKVzXVf0orow1KsOC+o6KNhoAA4FGml35+eL564z7pxpR5\ngeVkixQu4tSQxAxqYisEQ1pLVVLZ3OTYv4Km5znquAUOYxgkmHD4OEh4JDQk\n9QVWhs9k6w0v9Tw7G+YEGWR215xV6Sj6m3gI3dx/DVo4Wrf8KEJD1OIfXEKZ\nOF3O9Rvn9vN8li/5iUfFnOiukj4IEg2ci6vm2k2lsIXcjNnWED/tvaxUubWi\nhqh3Kn87I45vpzp24wA3zEjOvn0Ef+L63/RktzGZ7wiy5PCdKlHThRdIM0aH\nVrJdadDFR8dYP2I5yd1I+368WHvOrBZwTGT97XnzQRToZUJkgmDhAWF1+jYQ\nJH5dzM9btWxbZzkfL6BKk/IoLAD9uG1b4NCMr18xBWJImz9DKC7lEw1wB1vk\nXAG8TbcbUJrHiy9iToRAL7BwTr2lDmQLQkfATBiRWkvKQ2E+tUwi1XUj66Yp\nF+l6VoQWgWjQ7Fp1FxSANTP6FHkbT8SZBMPE7h/BcDB9MqM6ZdRZvsSZLqGQ\nqW85\r\n=O0oA\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"aa2636ba7b92603ee83125920fdef7467a447470","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.78","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.1","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^0.1.125","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.2.0","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.114","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.16-renovate-non-major-aa2636ba7b92603ee83125920fdef7467a447470_1616723523569_0.10891407223348715","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.16":{"name":"@balena/jellyfish-core","version":"2.12.16","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.16","maintainers":[{"name":"belodetek","email":"team@belodetek.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a2203c3f0be18bace1a162e6cfdee57d1179d12a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.16.tgz","fileCount":100,"integrity":"sha512-LO7mseIxbq8NMgK2NmAF4qy1sy3dKIUEYkdn7GsooQUTQl7ySDd3ddp0yjANtduf0p9YsgFGD1dT9o0RW9IzlQ==","signatures":[{"sig":"MEYCIQCHAQGLL8CClzDQLFbGLln0dd6VKD+OG/jjs/nrZHkeOgIhAPK4h+G/6MBdv1sjWobC4/2lcCX2BWmSvOb8kxnWMMrY","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":663786,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgXT+FCRA9TVsSAnZWagAAjX0P+gLhse6au1SzNvghqPv3\nI8OKNlyG7wW4a0f8Pc8YIHVcCbhYXYHLmcgkGWjV6hBcU0dEHUWUvZt0R/gm\nbBwOlAH2X+ymM8QkRdVws/FfFnyi1X27DZVNYWUnbfmlNocUwZzZU/dZW0GS\nTrtj+mf73bnG2qkvvyX/wtM8PJ6A49oUAB37tTkqmGMRpDyCvTIv0EEedW4w\nqDIyLyitNwqqMpEYBNv+DX/iJ1ZhkROn/6TWRpkBBLHsX6EMYb2eyHor5QAy\nBc/HdNzK0+sVjSlIqT3BGsRrL4kl/DRvFm56x+w0Ekvt+W/3mv1Gny2AGvDW\nLcSWloRphi2Cj2OuxUdPIONz/i7FvbHAH4TiaPxOywYEg7Ew2HXxdmuJczrX\nxycmclOc2u8H1V+otjMS2EXuWCadszQWyglL+I2UoCE5wmazXUjnqQs5LgfE\nCRF9etPXGJujltK4doQidiYKBa1JF+Y5zQvWL17ZQVXfTFIhk9wVGNj58cxz\n1ymQAm07IKpVR6wIrjOwgA56fwpYaKQ75dNjGYSlpH/LXNqV31uwpLg5RlzD\njiPA2U9rxeJctq9FnKA+JEN0XKSbH1TCYttsqQNbpE6GlENnv8hmWirxAZxi\n5Padk99ts4tktsuosu71Ene/rKw/W1teZaUD5ZWe2iGJIkDV5cJ1EBFUq8ea\nPL/n\r\n=5kek\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f7eec6e6b0aae70c06fd9187dea285b59557d1db","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.78","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.1","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^0.1.125","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.2.0","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.114","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.16_1616723844794_0.7244497979276168","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.16-renovate-balena-jellyfish-environment-4-x-64bd0ae5240ebb324cf1b997d7603ac10c4ca6f8":{"name":"@balena/jellyfish-core","version":"2.12.16-renovate-balena-jellyfish-environment-4-x-64bd0ae5240ebb324cf1b997d7603ac10c4ca6f8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.16-renovate-balena-jellyfish-environment-4-x-64bd0ae5240ebb324cf1b997d7603ac10c4ca6f8","maintainers":[{"name":"belodetek","email":"team@belodetek.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"785e61858b21cf126c927d723158bec7d3976161","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.16-renovate-balena-jellyfish-environment-4-x-64bd0ae5240ebb324cf1b997d7603ac10c4ca6f8.tgz","fileCount":100,"integrity":"sha512-u0wW6GakSjSFSqtJ9A0FdBx2CBWJ2V/q9C1jxvjEsUbSBubXtF8zEjBEyp7HItZc8jyl2YU7GBnJ/07x5HWcLw==","signatures":[{"sig":"MEUCIQC1e+7l+yBgp7SJ+EUh1Dtx/VIBgzCqY0EP7bCvzpSe+gIgaJUTgFqHSIuY3UpRa/JuXhkFdouo6SVQiFlnKbFGt/c=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":664205,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgXUBmCRA9TVsSAnZWagAAJcsP/A9bdgDv1VEHxhjEdX1t\nm3t/bN8tjHBO8Vzuiqzg87wfVWG19Mn0NB2b/YoAhzzFZFJyEwFbrcLs2jzf\n1Oo3vI9jlN0sMeIygeUESH8NqvOQ3efwZvjtxGhoU7Nc2FXkuYmQYQP3R2Cj\nB9ZhJ74pFp65xmCqefOjQUVUjn8WAzUwadM8wdz5SmnpXbu3FAK6BhHZejwo\n5ZsDzMxWia1vJCZYqQwBaYtEDxV/I9dGmfwuidysDazsKDWHWYw1Slv00Unf\nwC++L1DFSKAmkAeevb/E0JH4EkBM9ymF9jsTpv/TlQDKqwKgg8jvIEgPu8pj\nX8S/Jd1Uvl62mzC4YXB6fcANL+2RLaziJjE8vopmtR4XReq1APQV14fHmmpp\nnUfaAqolMfVNvrzYdKONHk4GiaBimBSbcTQ0Ce9KRC9l2i1P0EyIu69hPlD5\nSaDK0vZkexdiQ12Po4KPphWvnsl+NJ6udB2Bzg3O8tndYl1o/S3OyHIowXbA\neN6vp3gQDxom1W0YAEzce7R+Tn4e2d/wYKDjcUz3qwSGMlhWTxPuSKu0F0+9\nuPWVOLA96A7Zd1DmYvf9of03+cvxKPixNeEcc4NQw9YTEyODg8ei/EkPj8Fe\nbyPd1uXbdQTHhMbvPqd6zpaH4z2Vv0qglREMKBhCIL+kut5hMCBfnGvQDmkh\nuuma\r\n=lcoq\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"64bd0ae5240ebb324cf1b997d7603ac10c4ca6f8","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.78","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.1","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^0.1.125","@balena/jellyfish-environment":"^4.0.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.2.0","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.114","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.16-renovate-balena-jellyfish-environment-4-x-64bd0ae5240ebb324cf1b997d7603ac10c4ca6f8_1616724069851_0.8474987707147921","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.16-renovate-balena-jellyfish-logger-2-x-6cc6050818ed1a7997b3e848f8eb279082d9276b":{"name":"@balena/jellyfish-core","version":"2.12.16-renovate-balena-jellyfish-logger-2-x-6cc6050818ed1a7997b3e848f8eb279082d9276b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.16-renovate-balena-jellyfish-logger-2-x-6cc6050818ed1a7997b3e848f8eb279082d9276b","maintainers":[{"name":"belodetek","email":"team@belodetek.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a9b9c3d250b8fadf433acfd79aa283e53bd87b97","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.16-renovate-balena-jellyfish-logger-2-x-6cc6050818ed1a7997b3e848f8eb279082d9276b.tgz","fileCount":100,"integrity":"sha512-mjozhbh/6D7J72xZfYUxHqjc5eX51QIqOkt8+H3L1yNF8UdNcLS0jt4KhfTS9R1tr1hpvu1fUp81XUQFLKOH4w==","signatures":[{"sig":"MEUCIHuepOgJFbm82PUzQhtnZXp0+7HNM1hlAlIRqraLXRVZAiEAkvYkkLm6hO5s9/oKfjALinOHBYcmEZD8XQBlWyuF288=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":664190,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgXUB6CRA9TVsSAnZWagAA7GcQAJkyqBelvuBtzlWzXEjW\n15tJDuy7ARNXbNFJBd2wIFkfP/JVM/JuKH15ZzPZokb8Vw+1bNsOMPIMaqoV\n23G/GRe7tYVmMhzQ2vaY6kSHI1nH6Sr35y3707r4S4ToJfw9qXh89fELJrlN\ngkmk+ls8o3UZSNNPUYExr0oNTEw+ov2PyuctqcurOrT0KSvRLS/YY402JtpI\nyi5d7KwclaXE7rp+jV3hVhjeZKT3g0Pw53zyKbstXdQ2JM4cskaBU3ABfHCW\nBDR9u4bQw+801KdSfh95lN2NBBPx0s3qp+eepky/6Rv+eKRSZZ2TmHhuUWAc\nGhTmsJ5Ih0TE5hcXuMzSY2PVXzN8FNsezCyInj4Nxou+HB3Au2s9AJuPhI/n\nH9Y3hNKcMeoDRL3a++6Ej0JRBMV++t0vCuZoUCmFxKuIk4F3QWRIah6IHsQ4\nvL44gaL73iOtpqv/58P/4JJN6T1y7LbzJTZN5WAHO1wzC+/SSLu4M/UFYoR4\nOKLwUl+nEdUd5GM2NN748fQ6QH7KNX5HX57BWJJEAvwAIrJRVW56CJdlL6e0\n8DLYvt1WOrO/7RU3FzGrtOvwxKgwQL5sJst65f5+IYKv1FjF/Uo0+EaDUpcQ\nYE3EeCaFhUAzbWwnFwHoc6nBKmMv030aYNwjlmsqfOrW33TNoDx1aGX0tVPh\nPcJg\r\n=/iuU\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6cc6050818ed1a7997b3e848f8eb279082d9276b","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.78","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.1","@balena/jellyfish-logger":"^2.1.12","@balena/jellyfish-metrics":"^0.1.125","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.2.0","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.114","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.16-renovate-balena-jellyfish-logger-2-x-6cc6050818ed1a7997b3e848f8eb279082d9276b_1616724089902_0.7027892305262253","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.16-renovate-balena-jellyfish-metrics-1-x-5b990f0cb4658a853210bdb2dd6b83c7bc6cb9c0":{"name":"@balena/jellyfish-core","version":"2.12.16-renovate-balena-jellyfish-metrics-1-x-5b990f0cb4658a853210bdb2dd6b83c7bc6cb9c0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.16-renovate-balena-jellyfish-metrics-1-x-5b990f0cb4658a853210bdb2dd6b83c7bc6cb9c0","maintainers":[{"name":"belodetek","email":"team@belodetek.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a8a4747b59ef52b703abb1f92c1d170f36d5585f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.16-renovate-balena-jellyfish-metrics-1-x-5b990f0cb4658a853210bdb2dd6b83c7bc6cb9c0.tgz","fileCount":100,"integrity":"sha512-Y8JNYHeXN6ocaoX96D5MDGXqG56ewjBnz+sXLBvx4GvfftH1rrbUwavKmEbqaQE16591D7aDfOV3wckbHQWkiA==","signatures":[{"sig":"MEYCIQDme+7R94QczUloFw91yA64+eyqfg/LSFvtyplN4KoArQIhAJq1Wq0bx9Y5NjG/DziVjlQ8noxRSsR7vzRU2GQKeq1I","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":664191,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgXUDHCRA9TVsSAnZWagAAYigP/2ztg91fQk8+JJugE3tv\naJN139+EVOJ+S3EO6EGLKMRKtMv1ldlTTYRjuRH/NqkdXpZKtk7YCEq8GsNR\nqk/pMRLCVXkMvV9bS/OEpU5EyXtePlIiq91dhEcmG1qjv8hOZbfF61Y89vFJ\nVNLgVBKKwv62kW324eve57kdbjY9sLh4Elvl7i4uc9m9d31BDp990vYrDcN2\n3XFT+4qJI9H+mNxFdf9wqeVZp2/8K6N573sAiNMy/d0HSxnqClBS8hN9nVm5\nyLDMuNojYKNy8SJTzVcFichG2cXWJgEfzrdCbrAeLs8B1oPA28bQypr+97rC\nyJdWVR6SQ1lWuYxayG+hwGLQCnylzRnfPuJq/I+c5Kp9/ElPKHV1wsaUeWW0\nMQSO0jFIbq1P3jJCpV3k3W3C2gMVgw98Ce72e3VybfI0t/uJ5dDpVoVJ8IfH\nKrISslr6jDxt6gkSIUUpFhD2RQjs0aQ40HOlbQSdn41kehkqNvfzyohAxxgX\nvLKDBCFgg+BX/+vEBEjeVFda1QV1HD+xFjYjbfcjl0GN99Hryb9x0ZfK+yR7\noCphnHYD9s4c7DV2wi/g2n8wMunLyoNTJhfatSivivg6PuMIw8vAjzGb6DJ4\nx8j43tdHZZce1Zxu3EuAyE0nN2MPlS/pk55mb2VzW19ORcivmS5laTxlv7NS\nA4zt\r\n=9RPd\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"5b990f0cb4658a853210bdb2dd6b83c7bc6cb9c0","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.78","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.1","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^1.0.9","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.2.0","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.114","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.16-renovate-balena-jellyfish-metrics-1-x-5b990f0cb4658a853210bdb2dd6b83c7bc6cb9c0_1616724167272_0.11701558527731004","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.17-renovate-balena-jellyfish-environment-4-x-aa784006d7112f4439cfb3dc661cef04f2023a35":{"name":"@balena/jellyfish-core","version":"2.12.17-renovate-balena-jellyfish-environment-4-x-aa784006d7112f4439cfb3dc661cef04f2023a35","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.17-renovate-balena-jellyfish-environment-4-x-aa784006d7112f4439cfb3dc661cef04f2023a35","maintainers":[{"name":"belodetek","email":"team@belodetek.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"37b54e0431c26ca8d4d9cc7c908af52922fbb5c4","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.17-renovate-balena-jellyfish-environment-4-x-aa784006d7112f4439cfb3dc661cef04f2023a35.tgz","fileCount":100,"integrity":"sha512-E5Zbhw4ylE66q35qRTIzksPnZ0LqmZJ68oQa2dgEOr4JFwr1VuLqKrO9GLaZwtxAGJaNSZtbAr2vqzC7dg+Xmg==","signatures":[{"sig":"MEUCIDTEFrn9jBAdthAxb6fiw3OKkobhq4KcIJ/14sz9cPqRAiEAn5cFEVRcncWgvqIf/lXM9oWln5m9POpz4u3qNO1sNsk=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":664294,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgXUErCRA9TVsSAnZWagAAZkgP/0JSh4IDTRtUscFj2YVh\nfluDCpMvykj+9UGM89yODVoKwsqOgJBZnx7It0XDQSe4/WYJrZjd51EmjWWj\nqm4kdfeUo04bRlkU05js6QfQnkdmcSD1F8acVjtyvkLBeoTeXch5d5125io8\nVk2auV9Jj3tu8qPdVQK3okZm5qBSCNhSD1uZ9/QUL2mkaXTcI1z8ooMUFzyV\nAZ88T8WMLX6tSOV9jw6kvjjrKhfn038v+hxP+MXjSjQlV8z1evT0M0xHWBUS\nBqoBTMvJLPNf9pzDX7ai5OwvorLu95teXNzWeosPZvcaJRmaY4e6qe72mLr1\nmO9uZF9beFCYZ6XXonDtE5+KgtGNw4ZCTm1t9D4RMN5anDH6zTQVdJQhBusi\nbAczJyd50Y0PehInjEYkiHGLAR9aWFSoBV+QxCcJ3uGYWq1P1BDh00WwF6q1\nhjmk39MGE8V9ocznn0WlczPmnOwxPd3w81uNA+4pZZJw67scCoOWcqOv3Za0\nzTh3HAjrsvm+mm1p2R4Eq7BIqsNVcebCl7WLVPE32NJXjgZ+hAhQQMz4GQqE\nnpakWSWfW5WNUlEfHLuf/tC6U0obVrlpo3aAiSHNXLFVtim5PNrHMN14O/hn\n40hC8vjQIUD1dsxyIWrO/0o1+u/qLe1savaLh2bx6BfpkglSzOmQT8shv5Fq\njD0O\r\n=SpVZ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"aa784006d7112f4439cfb3dc661cef04f2023a35","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.78","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.1","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^0.1.125","@balena/jellyfish-environment":"^4.0.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.2.0","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.114","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.17-renovate-balena-jellyfish-environment-4-x-aa784006d7112f4439cfb3dc661cef04f2023a35_1616724266954_0.05827605008841208","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.17-renovate-balena-jellyfish-logger-2-x-b70efca723c84c22cf2c5c91552526ee81290a53":{"name":"@balena/jellyfish-core","version":"2.12.17-renovate-balena-jellyfish-logger-2-x-b70efca723c84c22cf2c5c91552526ee81290a53","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.17-renovate-balena-jellyfish-logger-2-x-b70efca723c84c22cf2c5c91552526ee81290a53","maintainers":[{"name":"belodetek","email":"team@belodetek.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1104cd89422ebb202ab541ab9762ab1cabc6e25a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.17-renovate-balena-jellyfish-logger-2-x-b70efca723c84c22cf2c5c91552526ee81290a53.tgz","fileCount":100,"integrity":"sha512-rpkqMwd6bjqXJmw55jX2GmyhXwvzhTXeYB/3UWkEaRt83oyQGL3FuEy9vFiy/z88Ux3C2uZB7aTlAeQeuL4Olg==","signatures":[{"sig":"MEYCIQCdnRUfuJ4melmLlA96RMX6iaipTcDsQ1EDBQunwBQzxAIhAJzOi7uL+VGhvjlh+3Ztd+/HboGu+C8xHrxMpGcTVEgo","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":664279,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgXUFSCRA9TVsSAnZWagAAuucQAJ5hCiqg0VJCsiilUQp/\nD2CexGG6Il/S/aNHu3j23dIMTJX10Zgl2ali5e6PrUbecEO2/1SPsYEanqfC\nforgk7qZJhaCjGq7o+9SfxovOvSz7+8V2bu8ZH0ePgAHjvVkX3+UXRWfVFwd\nKNNXXG0fHOEFIrAkwtY0uWlUit1tsbYyGw0hRnWpmrlsmLc/ETnP678+ydlO\nSguL+PlVjr/SnVCJIXPt2Xp/GRn75cCutsrPnW6CdBfUJ2HdD6hXgD6RBMh6\n20gbVCIoAB6w8vuDOvKmkFWDSUdVnydrtkGvUXS/mNb3qFWQiLslSboSs4kG\nw0BQHVXStXn14ScECuMiBhk3pk/aMyQE2WX/DyL5X2f3J5G0PNquxRZz7IDG\njODMGigRlWi3vH6mcUET8rK36eKDZ4+tDTz/WKpQWXE77IN4gTBHIcPiAZPa\nlKS29uVLPbyFQBwgVVbbTMG9nbaN8Uc5RWMWSfvsMX120NYjtlYhuhBYI+64\nsvUOCMxdKXFxkkAmKpiF4TNo4t0ZKoWH5hb6e79dYElnz+hGzcdO4easLZTC\n3aGGlMahtDMDkxP+mH6Hq0GKdU7GGsiPIaL1l+qEhdiaqCpiWUXW+EjZZFGS\n3A/LbVEHF7VLPhNmjtcD5gnb8b4UyTmonJ4THzvZ/p44b5ClHRjg6/9mwFCY\nZVPu\r\n=3Ckq\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b70efca723c84c22cf2c5c91552526ee81290a53","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.78","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.1","@balena/jellyfish-logger":"^2.1.12","@balena/jellyfish-metrics":"^0.1.125","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.2.0","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.114","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.17-renovate-balena-jellyfish-logger-2-x-b70efca723c84c22cf2c5c91552526ee81290a53_1616724306436_0.20109287958751687","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.17-renovate-balena-jellyfish-metrics-1-x-b2f8c6228dbc495b8ef7b406b9c3e55194b5d97d":{"name":"@balena/jellyfish-core","version":"2.12.17-renovate-balena-jellyfish-metrics-1-x-b2f8c6228dbc495b8ef7b406b9c3e55194b5d97d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.17-renovate-balena-jellyfish-metrics-1-x-b2f8c6228dbc495b8ef7b406b9c3e55194b5d97d","maintainers":[{"name":"belodetek","email":"team@belodetek.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"676ff795d57702d25f785987ea45f3bb19cce67d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.17-renovate-balena-jellyfish-metrics-1-x-b2f8c6228dbc495b8ef7b406b9c3e55194b5d97d.tgz","fileCount":100,"integrity":"sha512-4rXhUeMIH9z5Vh7nqrLhKWV621ytawVQIoZP31FSx5dV5IC2O4HK/B/n0Wy83xBEOHTfM+w5zhyOJzghLp/cGw==","signatures":[{"sig":"MEYCIQCA6ydDmMdLt6d2YDtV8oyDgwUZcY3Grj9dZu6/0iD4cwIhAMgLlsZjKFQW8dT/UwzKH0TiE1TYdcSr0mtxhx27hoBS","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":664280,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgXUGXCRA9TVsSAnZWagAA0xAQAKQlZeF+yjcqGPLUyKLH\nue4LPrI5g4w7IY2a15kwwUIErTKvpvAunHbLzTIhIqRcC86Wh71byLcyhUz0\nGYaFHsN5KdA2SBq8d/lhxlfjvLPx0wqB3/iJ2/wSanqulTGbTRj61OG4Z5jm\naqhj92gEgz26m0jJUM51uu3aaVj/mvPsWOvngyI6cJ1RnYIUVyaoDpCthNSe\nQ/FthHW08Kryt9WVMs8HVVHhE5De+pjvAKWToM8fpGxVmZLFaAtkLvM2jB3C\nFF0obdujIsWUK7mAvT80uK9NUm3H9EWkSdXjYt2LCKnf1SdRAQDun3QAnvhd\nVANtu6UWqa4M1Uwa8wtOqt4npR4aCnuWpyFb9vhD/Zi3OfZ0CiFcfyEXJjVJ\nSAeCNrsr2HbJe1/JG2Ju5/12/RAWVFgQNc4aBh5GfPhIQbf7ZeegYzvt1YCR\nB4f64BRLqiCswu5DRDBx9rgregldf34kBssPe5JLz/K9L8eUQVeex8NylRnt\nui4PGS8amZYsBCTGqex5aAvmsJfPQ3PsznMxnsbAbXCo/DGdJ3E32BrJ4F8U\ngI+n1uqxbEi2ds4xPE4rJGySxh7yizlzYKSu3N98536VPDkQvgbDxewYgKPn\nh4hMP17OBmjgiqLimBGP9T8XVSWkX45Ya+cSCrh9i9DvD5LYUJdq/KwYKxyS\n6qK3\r\n=/kxD\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b2f8c6228dbc495b8ef7b406b9c3e55194b5d97d","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.78","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.1","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^1.0.9","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.2.0","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.114","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.17-renovate-balena-jellyfish-metrics-1-x-b2f8c6228dbc495b8ef7b406b9c3e55194b5d97d_1616724374583_0.059946191804293614","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.17-renovate-balena-jellyfish-metrics-1-x-b6836db6c51853ee31f81c185021a8356dff6d4e":{"name":"@balena/jellyfish-core","version":"2.12.17-renovate-balena-jellyfish-metrics-1-x-b6836db6c51853ee31f81c185021a8356dff6d4e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.17-renovate-balena-jellyfish-metrics-1-x-b6836db6c51853ee31f81c185021a8356dff6d4e","maintainers":[{"name":"belodetek","email":"team@belodetek.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"68798f4a504000110adcadf49f2af183b1e9594e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.17-renovate-balena-jellyfish-metrics-1-x-b6836db6c51853ee31f81c185021a8356dff6d4e.tgz","fileCount":100,"integrity":"sha512-yPen4WwY+ht1+Qs8AdBhnzGoCuYZk7kbHYd0671Nv9XHd1lDbWbuofIOdMw8sE/fcT/C5gAcvcFNdjNPqha+aQ==","signatures":[{"sig":"MEUCIHekj1/YtswHWPOdcrJsJIDs52FFxNj7FWYHwuDhVUgMAiEA9eBMVd1LNgkgwZHDGOmJEIywPpYrNDnjziqZ307q6Y8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":664281,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgXVp5CRA9TVsSAnZWagAAbY0QAKSvjXv0ZxrytxVMv8lV\n3lr0rgzq8RlNqSbdDfdd5Nbpciv1GgIS/Bx7M6UKE32otHtaJt4keFJWCJ+s\nocDqtJ2ZDepbNR46q2bXOcYeq8jm/LlcFcO1PFvlMl+mIqHgJ2AixpXN0/Nt\nsOwlHp96mtQkleIn2Kvhm0EHtk3KCuJPSHoeMTzqWzHZDE9XN1kYdEfnz7BT\nm1g011ErRZIx+FiBobOcaskx6pGT2Owci1wLPYEyCWiU2VespZtoj/TidGIC\neHXx0Vrnu3VgyVhHCQ0NashnG7EHfwI9dIfEenpQH3FELJ7QZx9MAz+bqVaA\n2PdvVLXTf+n2MMQwuFYQ5t3en6MDteNQUrRe+mZeDK5NKH7rhN4PGqpbiXje\nN1iWM7o5fghgp8ZoZ+mUrqp8u+PIRY/8Iib9R2G+j8GwehVxUq2hjhbTWxI3\njGYTz2Rbzpmwzv//dUVyP6h69VrNcVYshZBvn9HTISriPZtILRL8MelZUPGA\nHwgTmcrh5UFHkb6XAfyw1Lvj3nG+9SOSbBlT3s+u9yIbQ/4SMhOlUCw1R6Rm\neok+A40DLsnISdI6Hg411fEnLc0afXm3SBh0M18KusNPOtguJAlf0WML9ntC\n493VjSZo2KuZrFIXDL3t1d/bPjvNmVPUDPTCAy8P7Zbtb1shbTfARmINr+c5\nUwfs\r\n=NnBZ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b6836db6c51853ee31f81c185021a8356dff6d4e","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.78","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.1","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^1.0.10","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.2.0","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.114","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.17-renovate-balena-jellyfish-metrics-1-x-b6836db6c51853ee31f81c185021a8356dff6d4e_1616730744955_0.40125234016868694","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.17-renovate-balena-jellyfish-metrics-1-x-2f91e3c5da8fbb59ee37d60a010f8769fc54fb02":{"name":"@balena/jellyfish-core","version":"2.12.17-renovate-balena-jellyfish-metrics-1-x-2f91e3c5da8fbb59ee37d60a010f8769fc54fb02","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.17-renovate-balena-jellyfish-metrics-1-x-2f91e3c5da8fbb59ee37d60a010f8769fc54fb02","maintainers":[{"name":"belodetek","email":"team@belodetek.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b325d18f3c46e365412b81d93a097ddc616acca2","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.17-renovate-balena-jellyfish-metrics-1-x-2f91e3c5da8fbb59ee37d60a010f8769fc54fb02.tgz","fileCount":100,"integrity":"sha512-8rYwVVVIWi+OvRdsMIdUae/rtcNiYpcEabYNRvcl6ZFNPz87Rr8B+odn0xsiMbHC/fXlXkSl/OhQavrJhfNuKQ==","signatures":[{"sig":"MEQCIBhxpRms7JVWJUauGE476gNO4cuITPuN6u6Y4xsHTKf6AiARsLIDNME+/ZJWpda/d6fcuTP7EmtP1al2jOpOxyyyaQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":664281,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgXX/RCRA9TVsSAnZWagAAeT8QAIotQ1bpk5u+LfCdvWY3\n/6v2VRYiddysSsWM+0cu4LNDwb0wc9qVZDE/zMh8n9JQwBdL4D7oB/UfLItt\nV9OSaOXUr5taiP6hv4oNcAG/FmFM9TudS2JyIBZhG011iSPLGkdwB4XCn6+g\nMzT9bpuN0GNke+WecfMS8v/VPiFyLSD+dVhYKet0cveNnAk3mQHqyLIqgncZ\ngTrbxSN+ZQqApHBZYR7eMB5ra+A6YkvTOrN7aPxWckdnpX7nr9Dv9XaKJiDW\n4qSzBq+ijPmi0mG8iuISWkpkS5Ttpdjc/EwfpL/CNlZjdNz41/nyPkZU/gH0\nc77roR0roUcNO97JvC6JIYGVzLW54jnjGzpxPR+z8uhJy6s8qD71xKXS71uv\n0pgh9UZ192J88mE55PtJVeTNJ19Yp7K/Eu4OwkhJY0CmP3B2UkgKHRZkM9dv\ngiekbdmV7xwu8nx39y28m2uAUxHdmogLfTxxg0H2AHTpF/cMT8fK65GCrz06\nqyL2qHFfix6K0FF9590Za2qU2a0ekitYyg+IUMj1/E3UnNHBXN1x6/taG7t1\nPNz78lOeQekNoKwVzodkDKK0mnvshjQsWGDRlhQSWn1Mjqbhr/OhaCFHTwxV\nnUCidYtMbSpgwEiHaxB4mqrfdQ5Seg9ZVZqrry7M9BC/74RJ6yp3mJ4b0+iX\n9hZX\r\n=T+9z\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2f91e3c5da8fbb59ee37d60a010f8769fc54fb02","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit","postinstall":"if [ -z \"$CI\" ] && [ -d \".git\" ]; then husky install; fi"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.78","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.1","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^1.0.11","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","husky":"^5.2.0","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.114","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.17-renovate-balena-jellyfish-metrics-1-x-2f91e3c5da8fbb59ee37d60a010f8769fc54fb02_1616740304939_0.923871996485853","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.17-replace-husky-ef38e9e1445b443ab3800501af2ffd9b4e1697de":{"name":"@balena/jellyfish-core","version":"2.12.17-replace-husky-ef38e9e1445b443ab3800501af2ffd9b4e1697de","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.17-replace-husky-ef38e9e1445b443ab3800501af2ffd9b4e1697de","maintainers":[{"name":"belodetek","email":"team@belodetek.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"116366ef22627891bd04a2475f9b5d294701d7bb","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.17-replace-husky-ef38e9e1445b443ab3800501af2ffd9b4e1697de.tgz","fileCount":99,"integrity":"sha512-Mc3xlLJv6SkQdYa+thJG1Tk743R9Az8zp5MENGX99hUDqdNafPdFrU07gSNFm8U3G6jfX7QdaQYRJgiVNPl1pA==","signatures":[{"sig":"MEYCIQDJ8E7VSDPlzmVke7LdmXKJVMJCG90MFac9tBwIsRQ/9wIhAI7EFOJt9RjXw0yxN8fg/wdTqtrs3SY3kPfkb4sH0PBg","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":664240,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgXYA4CRA9TVsSAnZWagAAYykP/RP33ePzGRApU2ZvefbW\n4GFqEKmRlXlw18krbOIlhtLf31VbrEopzcTmfJ0EVwul6csNbeUuaeis+JOk\naHUh8lTFOm/njSVJTZ8hl+DcUuJM5dPu8rwahu8R1Qje8kLUhpGcdjtP20K4\nt5un8j3rh44XtMly4SOLsqhaEmzD6iPCkpIIPD10Ewu9sR52/Kl59nwgDqOL\nCcRiXAgdU/v8CxQChrU/JPQK8gXk+KwtnIMMS0QBO/ep1XlcoXEkH9ymaeNv\new1KGolZ11tNhxpOafaFezp4PeHx15DmYBtx+JesNpjppvIExASCHtUoz4zo\niO4ZFYIRy9jOuhUan0rsqwC8c9UGWMyjUjD9stauzxGBUgPqH6lPoUBXQKrD\noI+1Ox2iG/SxVXEWl2VhHDLfhDXatZhgWBoXkx7u5dZBSiWXt1viT28te1KZ\nMFHGNkSBy64OlKQ8Kacqtftp0saSahEufZ67PLRMoml5gjQbe++vFwQ4JYZn\nRQZ6arpa+yLv4hVg3WovhIIWLtxazYoRqxKMDi4h2xNdVi1Q6rbY2TITPgvl\nc3Vt1h44P2oGWlw07LUsXBvQlLwhH9g/zNFfgrFfFWGUYW2jsuU9Agm5DkB6\nQJqAZn1meqh1oGGUwlX/cBaSkvBu81oZTba4aTbu4uCIViH3ksZMhrZ2hA85\nnhIU\r\n=DLhW\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ef38e9e1445b443ab3800501af2ffd9b4e1697de","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.78","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.1","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^0.1.125","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.2.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.114","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.17-replace-husky-ef38e9e1445b443ab3800501af2ffd9b4e1697de_1616740408389_0.414063332086575","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.17-replace-husky-3a5c3f8cc3b4efbe0ce8c39e190719ad2b793b29":{"name":"@balena/jellyfish-core","version":"2.12.17-replace-husky-3a5c3f8cc3b4efbe0ce8c39e190719ad2b793b29","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.17-replace-husky-3a5c3f8cc3b4efbe0ce8c39e190719ad2b793b29","maintainers":[{"name":"belodetek","email":"team@belodetek.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"74821e9e1b341c806c987ac214f45b909e80a56c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.17-replace-husky-3a5c3f8cc3b4efbe0ce8c39e190719ad2b793b29.tgz","fileCount":99,"integrity":"sha512-Wq53cXtdyGe3ctYYXZVb266xeMVS0KHUQ6VJ5JJwLf1g6ev1jL9Oiffsi2187enWnbxgsUlV3TBwvRNjwr4GRg==","signatures":[{"sig":"MEUCIAbhiTO7DTIU+1POrGmyNKATzjSBy/af6/UyICEWWZwGAiEA+s8TBUj5KJKdEj/Q9NPTlqhKki5rujmud1uCJ53oidk=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":664305,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgXYHyCRA9TVsSAnZWagAASXEP/i3DtqFd4Mi14XEi0CCb\nEMlPk/UuWQ0wcnDkKliQ3AW3Wgywydfexpr14nF1xpSe/SkGHr1A2IQmKz/H\nltv69bdDzgZZZCUAe2hoR2/OuxJo06LQyaYYDr+Ig8IW73AJIGalvgwSWTOu\nHCBf64IJ9yCAomP+qmTr7RzgksFTGSgkWEdy9/iO5yXlO/Mf4AyhuctTH7vM\npOPkkGLhe4Np4KbIP5AxY3PdwpuuaGhguaadi5NmxBk5FG+tWPCuKkF9V42n\nYO9NwhMHdzk30Mwg6hpNiL2CSTGTZ9IBQMd7nuFc+MW8IFLcFyT0p9/cmnje\n+CLg6kJ5alPg3DwzyrbeqcWrGY9NOUFFikzkD/xfBzpV8uKtXYItHW10nRfk\nodG/TOzXb3DNjJ64HENu262V44qk4syoRNwnu62OzfAKf5fHyxzquVoF0A3l\neQvs1T9Q7x83orXi8DMhgH3dsrF7jRwPcdQPpffJc9cJUxbiF7jFuoRtXSUt\nZ6q91VVZ4E1hz7BS06BzCR936J3eodi74glB6eY/XY/Is215P4nUjSWJeIUQ\nEy0/aV0owlrvQlKMavRVjrNoYzpfo/I5imZ29FKF7lnWjva0fS9oqt8MR3NG\n7iPqDMaP2ZGiZT+pRdFzQxkJ4Lb3BpO8n+Bf7ZmWydb/700WKWsfj15NB8AP\nrzHI\r\n=5Q1V\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"3a5c3f8cc3b4efbe0ce8c39e190719ad2b793b29","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.78","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.1","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^0.1.125","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.2.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.114","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.17-replace-husky-3a5c3f8cc3b4efbe0ce8c39e190719ad2b793b29_1616740849998_0.04533430026630514","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.17":{"name":"@balena/jellyfish-core","version":"2.12.17","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.17","maintainers":[{"name":"belodetek","email":"team@belodetek.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"98bccc7cb122568ecaa20ace2ed4cb606aa958ba","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.17.tgz","fileCount":99,"integrity":"sha512-oP2n7Q5aY0c9rAGiIRXJdA60B08JahwLIrI5ZvXNAeNWVW3IXyz/P6n1Aquluadc2tOu18W3T64HkBcOyFVGhw==","signatures":[{"sig":"MEQCIBlUbVqOmUfzHq8kS5Pmt0MMa0CJKbpxih9FYqo+1zNxAiArEyqzFPjGp68NH001KL+aT5LDYvQVrTBCMGwNx852rw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":664250,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgXYN2CRA9TVsSAnZWagAAKjwP/jLmwe9mAFBiwj1o3nfZ\nJVUaUMg4/ecpuEC0puW0ViKEaEQPL08P+MfsOW9lF2Ly6o3rrLCSpCJUUQGj\nE++zOFRgorrxvEY45iSMIE/FIc6vBcqWiz7hJz0c5ORvZ9FtTbumTtz8Tbz7\nb/6eZibnSLpRvOGMu9h36EnM8jHFrhLwtKTxFCLtCEMwHgrjPjtZ9Hs0eXJ9\ncjvG1us+YFB2BjZc3bbYuKQStG4E5sBFwtcV4He1RmSwqKU1FVw3iVw3uICs\nCHZ/sTxMt8i7m0hv3N2rdb9q/QVnFM/DAtw09jYYTo9NeJ0GRsuNPHcT4Rdo\nFAtdRcxqO4veRZ2v5LukzG/Uxc6QGPlsWztiY8sy2etqAwZkh2JTuEU3+buA\nrLzUSokgEITsr3OPsvwJa5NB311Fao1X1Wm+lFRXfFLFi/1uv7xjvTz+UQ2+\nOxsHgIVn65tTfUV2R5rTFI8pgj8t/aBCjeWiYImM7J67eCGT0pOhV1e7XdEf\n2K9oJU481negI0IxzkUAU1zKU2CRln5tSm0m9tR+a3uRZkCIiSOCvh/OL7bM\nxD/Fby0IxmnLR005UPQF7YUk2X/ngdoC/7oWGKJHSQFzY3jl+nWavK9rN2pr\nyMcE3O6TG55v737krTdX4e8CP40V8wSN8Y3rP+AxNvXNRbA1dhB9O72zoxN3\nC3Nv\r\n=CLae\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"253b4bb3f6bdb01ea7194c6003c935121b016520","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.78","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.1","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^0.1.125","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.2.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.114","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.17_1616741238059_0.46263196793294203","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.18-renovate-balena-jellyfish-environment-4-x-8731f8cd083e98bd60e061986da7a98478581eed":{"name":"@balena/jellyfish-core","version":"2.12.18-renovate-balena-jellyfish-environment-4-x-8731f8cd083e98bd60e061986da7a98478581eed","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.18-renovate-balena-jellyfish-environment-4-x-8731f8cd083e98bd60e061986da7a98478581eed","maintainers":[{"name":"belodetek","email":"team@belodetek.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2c173dd8c6693b14abdc8f9ddef058d92c513e84","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.18-renovate-balena-jellyfish-environment-4-x-8731f8cd083e98bd60e061986da7a98478581eed.tgz","fileCount":99,"integrity":"sha512-SanUc3GmPVqqDUFizlrpXfroOwHZ96kFAT6Z9tiltVOtNo8PNOcoJPwxXMi96sZqGgLIs6TtuAscOE7pRopB1A==","signatures":[{"sig":"MEYCIQCzrZfZtDAoXmFC02i+VD+dPKf/ABtIUl+aodbVGybRnQIhALOpq/9ALijdXMZNwmXB5Qg0x6pqn00EoWUlr7WeltTS","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":664758,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgXYQuCRA9TVsSAnZWagAAbH0QAIgaIDnHaJCZUr+Vc7vt\nqzinbdoRg8qkqUSyRDRIbjEnc8BFbz05957ZPrEEiADEu9/OaPkLHEwvIqOr\n1Goc+hub8qFqyjDLeyu1ejrdxyYrYgCvDpJv6U+demXci2A7QSWACmPY4yIy\nefAY6sVv5bKy/xnRa7CsF/5xAv0DTUzyAsvKILfNRca/IDxK1Hkm2oZVloK3\n0n5o4XichvfrmMjE7t1n1HRLUDSBAhIneOVTVD2QnY0iS92dXoNia4oqDnnW\nW8mRfCjt9PEiQ2LQkY3ECtw7/7c3T6yfyay6GQZQ0veQkRpZSSEP4YaWZ0dE\nXb2Q8EV39JNcVKpHykybQuv6KMTvIkrNYefxzmUs0JXGrRdCU/BlYi4rgujW\nkm9yHTfp3JgK9VOlHDbyH/IG2po3K8JCguTAZAkqYcSoH2L3fPbliMBQb0zq\njyPcYqBM5e6QtsU2iazx0R90kJo68xmj1+iHa48mcPX5+A5p5+XLM0eSplGu\nK9TEKjv7Amwfp+lpWFW2DiZFrW4ujY5FaXTmByukUlazHhq9vXbM+xEZsC59\nQURFDpMe4v5JwTOweR9nRfWqtZTnW60Gncmyer7A073l60BI0QY4TObcIPfB\ngtMrLggKG1zGcc/lXLhu40E0wuq8FGIgt2BLI/pV6KnSge99a9+pplLTA1wC\nq4cu\r\n=DGfZ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8731f8cd083e98bd60e061986da7a98478581eed","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.78","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.1","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^0.1.125","@balena/jellyfish-environment":"^4.0.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.2.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.114","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.18-renovate-balena-jellyfish-environment-4-x-8731f8cd083e98bd60e061986da7a98478581eed_1616741421882_0.019990338799572793","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.17-renovate-balena-jellyfish-logger-2-x-267858fc77741436b00f78748f2190e2ad3e7267":{"name":"@balena/jellyfish-core","version":"2.12.17-renovate-balena-jellyfish-logger-2-x-267858fc77741436b00f78748f2190e2ad3e7267","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.17-renovate-balena-jellyfish-logger-2-x-267858fc77741436b00f78748f2190e2ad3e7267","maintainers":[{"name":"belodetek","email":"team@belodetek.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3d5e8c26074bb04e046d8bdd379824cb062264d5","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.17-renovate-balena-jellyfish-logger-2-x-267858fc77741436b00f78748f2190e2ad3e7267.tgz","fileCount":99,"integrity":"sha512-7u9dSLVaHzziqb7nmIV1n551DjbbtriF4QNUiqdRweKJnzNIlIKepCw7elNFah72Wk9Z+YTMBB3QGObkKkyESw==","signatures":[{"sig":"MEYCIQDwV9+8NrS3Bz6JVV7Fr5vM+vz6S0E54AGOpyiNbf1uQAIhAN3nlhUPFOWG8KPYEtN9TaFFohbBACdbDcj8kD2xUq4K","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":664654,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgXYRXCRA9TVsSAnZWagAAsxUP/RClwfStjfOK+775wAm/\n4G6uuatkldj0so/H7ZV7hbLJ1wpGnGuWX1+lQC9hfIAaRTIbvt2vm+e6ZQK9\nBmc17woyEXQ9VAOfP8phsSZo8SULVhfbt5d4C8Y+e7rY0wghb6s5p3uvXc8v\nKbeiT+J3cxJGX6qp5m9AGJbED2BnGTcFMxHakPFlMMBXa8oO6trGH8rLWDJS\nD8pnk2p3x7FF2bie6mPRFcCbIvS8hfeflaOyVCn34thuVeMoPO0W3dw+DxFe\na5whSgZywiBtSlKPMd165psndWvqZsB4/ZdhJ3pw39Z8lXbYZSt+5FAuXwc/\nPdw3kmjE6stwO0Ki0Eje7XdlvtStmUvUWmsycrvDw+/gQAaSavwiXSMAYyZ+\nA0UGdrmYBjRZz3U3ZVjQpvIG1JxMKISP0hXUQGsiwquwwQjLFliGt4CaUIHP\nBuRY04HOm3uEPqvZ+R9WKvIUVzPPfFZWlTK094+8cMHBzYymraKXlAJ9EwoN\n3wiZDBoHIio5SZ5GS6ih+r8570VjjSI4pM+mGif2WUxqVAzaTS0hU+rk9kwz\nOAg6jxkRcUA+OUf6viOebpyvx0XAj5YzZaCe6mRW8lKZyue7+psPg2HlHArv\nO+ulUovthqmdZU3+CMHgEXn34diKNpGir5knQDHRTeR71iIVGB7ODfKCPNoE\nYhVA\r\n=+nqz\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"267858fc77741436b00f78748f2190e2ad3e7267","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.78","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.1","@balena/jellyfish-logger":"^2.1.12","@balena/jellyfish-metrics":"^0.1.125","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.2.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.114","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.17-renovate-balena-jellyfish-logger-2-x-267858fc77741436b00f78748f2190e2ad3e7267_1616741462680_0.9364467407612351","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.18-renovate-balena-jellyfish-logger-2-x-33edb55687951fc0f30fb293a076ad264f93b12a":{"name":"@balena/jellyfish-core","version":"2.12.18-renovate-balena-jellyfish-logger-2-x-33edb55687951fc0f30fb293a076ad264f93b12a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.18-renovate-balena-jellyfish-logger-2-x-33edb55687951fc0f30fb293a076ad264f93b12a","maintainers":[{"name":"belodetek","email":"team@belodetek.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b45ad891f3f910d8830bd94607e31fd6200e4ccf","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.18-renovate-balena-jellyfish-logger-2-x-33edb55687951fc0f30fb293a076ad264f93b12a.tgz","fileCount":99,"integrity":"sha512-pZ1elMK9KGKriW/FR1pt9gXjCu2GvOe2S1C5GSLg5zYZYewnflFWOtj0S2fqVAqaNbnkuJIVf6FdoovmoVmJcg==","signatures":[{"sig":"MEUCIQCP1+wDe60wpj2rXZbSI+5p/RhSiNbvhab2pozNTze8cwIgAOg/W6eqce49eUliBipHAsYYKDTCApNyVVxr0+0Ur6I=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":664743,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgXYUACRA9TVsSAnZWagAAkrkP+gJ4E6v5g/p4XYMr3LKj\nWeZpQ0F05tmme1O8qgmf/1WlbQaGYefsoYs8akGs6WNZWkAFHvaUC86PN3Np\nJUiXEyBwa9l1OOGUSr53W8N5xuChgbUChNValypnI5JuYBOEgzMHiRUwOGIz\n4dgqPrtlqhPNzoD1P8/QbLlBEAUfvAV8O1CFckdo82pZ9DsUIB4S7H9qYn/w\n4IjXpMZay2kpRhw1GblFhHbEhqZlzthDAa0ijpr8dTZjhlGQSVEA1G2rVM1R\nzNDezT0t3rwjMVX0SsX6WtD/JtnE8JOTm1q2VSTOCmZ9c6/iuJEEfN0yGd8N\nKAkP7cvgUcPVtGjspC9TDsrcDREG+toX1DY+1wo27nZntaqIi0eMOtw9isOE\nU0i7Dathty7CTsxrEFg3BCdmTLXpkbZkq1Pq9JIgGWHDA0l2IbNeeqYTJoGL\n5Zd4Wub9pd9c9q8uZJt5yMdQgOdu2IK5amYodt2i5n0tKMECkl8ex20xL78t\nNflRygWjOx9pqLm0PGVqcBI1sB7CZ7FNv0551hzjRpPRor6oXtvolGal72wF\npyYt6AnEwaKRU3S8nW/r0N/7MU9wDIPuCwYV/ooKNOivprPUvEV7ISnq2NB0\n+I6CiVlhxI8gg7Ib8jM5IIZ6hfYov6eqSRom0tnYq7B2tv7qeCXUIx6rtykG\nDLmj\r\n=Ebq/\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"33edb55687951fc0f30fb293a076ad264f93b12a","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.78","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.1","@balena/jellyfish-logger":"^2.1.12","@balena/jellyfish-metrics":"^0.1.125","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.2.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.114","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.18-renovate-balena-jellyfish-logger-2-x-33edb55687951fc0f30fb293a076ad264f93b12a_1616741631539_0.28170189572649584","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.18-renovate-balena-jellyfish-metrics-1-x-7524add97b394a369ec3dfd13fc69b8de9d8ac89":{"name":"@balena/jellyfish-core","version":"2.12.18-renovate-balena-jellyfish-metrics-1-x-7524add97b394a369ec3dfd13fc69b8de9d8ac89","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.18-renovate-balena-jellyfish-metrics-1-x-7524add97b394a369ec3dfd13fc69b8de9d8ac89","maintainers":[{"name":"belodetek","email":"team@belodetek.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"fbd8426a7216e8deedfd67e92e2b456593fbd8dc","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.18-renovate-balena-jellyfish-metrics-1-x-7524add97b394a369ec3dfd13fc69b8de9d8ac89.tgz","fileCount":99,"integrity":"sha512-CNhTQDFSRJWq+s4pxpL93U+RrtTHqFQKM9qi7ZouSNSOyR8qlQzMwGkn2ZOsLU/2tHmUW440/xKwlk3MgVngVA==","signatures":[{"sig":"MEUCIHTHGC2AZt+LuCYd0dE884InyTP+Q/M/lXv/x1aJqc1fAiEAkcGsuOC4fe1mt8exypruzoOB8XYoMPQ5BfIafmzvoLA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":664745,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgXYUfCRA9TVsSAnZWagAAUeYQAJ1AlQh+e38csvDPE58J\nw9MbtzNBemF/bhetb2EMrNU6xBiY7lA5TZdJGKyuq6i678j1KlrwdnuvQhcv\n2bqGBlsbS7zMdv8XNth7oEHksl8msbWQVKp9yhWXdnuweWldx60SiR0l+hEd\n2F3heGvn0SmaIAZcUWrfxpT5kONByibLe/cc8mlrO/TIRmzRGOiKH2Z4dY7F\n4uOdnbcwdF9n44/XVFs1WxiUiqmxQ72RXaTpNqLs6FakqPO7LOPxRUP0TgkD\nh+ydJnnocrkfiZwhzTarbPtGsemu4iAhuIMDwedsSe/YKLBOgUpGWRkJXRnZ\nVQUVmizCxhz98UXcqK6EjJsWqoNmBfDiB5KX9fZS3NXtA5GzwLp7s3SH2XQj\nq351IQB9mtFrZRWDibUbg0Bdd4+o1AMKDcllhf+isoZmr2An/7I7bNWg/so/\nYkXskdpmiymaQWRZdX9lyYn7dsofLD1b2CsLP2tt0ExkdwRhB8HyS31Io9tX\nu2+G3hWxFOBAzBgZKfjYsIYIGJyQL1vDHmAPOzROMNC49ErNGT27uWjQzAjj\nSw8RMLxGS4P25XdruPvecmV20diC4tj2rU4dkf5zg6c2dREx8QSPJ0svetj9\n24C9yv/a/jJb+qj01HDMi1D1cZf3sR9Hiklh4qKrymyXvJ+HbFuhZ8DqpbCm\nSSAB\r\n=cPL8\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7524add97b394a369ec3dfd13fc69b8de9d8ac89","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.78","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.1","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^1.0.11","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.22.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.2.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.114","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.18-renovate-balena-jellyfish-metrics-1-x-7524add97b394a369ec3dfd13fc69b8de9d8ac89_1616741663203_0.7730660835701821","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.18-renovate-non-major-f55da8d4b4afe5b204b92e5900c9678896be3220":{"name":"@balena/jellyfish-core","version":"2.12.18-renovate-non-major-f55da8d4b4afe5b204b92e5900c9678896be3220","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.18-renovate-non-major-f55da8d4b4afe5b204b92e5900c9678896be3220","maintainers":[{"name":"belodetek","email":"team@belodetek.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"7cc437d404c8cdd3e8536a50e7d06b2e0692ab0a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.18-renovate-non-major-f55da8d4b4afe5b204b92e5900c9678896be3220.tgz","fileCount":99,"integrity":"sha512-E6BU9cAF2uoLHJXU7SBnb8N9i2Wvkn5wVLehlCJgG/fIfmqrmxqGFS+GOaOSZOiVBK6Js12mSte0O6R3H4NMqg==","signatures":[{"sig":"MEQCIGDu/7s++eoPHjN9kYZkqxugcje/jqK0Tk6NcsxIOWAJAiBXF7stg5VapuKKnOJbkBce4/KfbqEB9Nd/0KP6rCI1nQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":664699,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgXnOfCRA9TVsSAnZWagAApAUP/0kim8gZTUK/nPaV6ySn\nLxKpA6UuB+sohpcCFeYUHRLrRyKmLq2/b9d/6BWA7i334JbpeFdjeAR3UKJJ\nQFdsb5eMaDNQ0C7RNKdpz+RaqpUnLkr2nSC8MhcjkTP/RTC8E9OBTTMGF2RQ\nwC3dkUx+xyLxcJS6OcrkGONe0z4ej2M56JtR6hFwEOsZbDTkAaUCEvWosVn5\ncJjcut9XHJElKXGrrm6rLtQMp4g2+iclqgmQy7kt7Sdf4Iwf8puwryOJS103\nhBjsUciHDJ1y8tzPpjEXiOVpVaFmWH+2/ow5O7NiQE757ywSrTELF0URr00t\nj5kEs646OQhTon6rla/MgilKTZNWWbZ3qivVv5CQ50BdaaX+znEWituMgeEx\ntKQfBZC4UJ9z9XbNQWHMMwYaKT/TcI4KsL5/n+LmXASC4sgmuKR/1L/3M2X3\n3/Yv9mT70jhfJ8gSWsmWBdPOv4g69qdQSpzG3pYQqFbSAteqaZBVmUNYTr//\ncT7rLO7bMbOwuHASBwBPQF9zid84JRKc5os6l4nfBVjEGNg4sH6f9udWC/lf\na0PTl08wEzOr11eD/AvxflaGAiaP1Kh6ZiqkT6HtqU11cvzsmPqcnWjwD8yW\nfC8fUf6Q+wpkfuRzH1kVII+ic0CUcK7nIDq8folC9tLy65XjQEsxGUmRyPjQ\nmOWc\r\n=Ltq7\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f55da8d4b4afe5b204b92e5900c9678896be3220","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.78","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.1","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^0.1.125","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.2.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.114","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.18-renovate-non-major-f55da8d4b4afe5b204b92e5900c9678896be3220_1616802718971_0.7769316433435336","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.18":{"name":"@balena/jellyfish-core","version":"2.12.18","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.18","maintainers":[{"name":"belodetek","email":"team@belodetek.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"092de7dd2a9f9613b4472bfc28628621d2cdb86a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.18.tgz","fileCount":99,"integrity":"sha512-bNSddJ9lSpo71zgka1wRFsu16ml7gh9OeG0Cj2BjR5KNGhH02mQefYKaArd4W/kR2XiwzLM8F+vjKaQV2Ztcgw==","signatures":[{"sig":"MEUCIQChH7awNomUve5ClL919jnwhPn9G67xqvlOSXBMlqSNtAIgWIRNO3S+TpCQHpGdApki3khCyKRY8RX+gCL4e6owVgY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":664639,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgXnTwCRA9TVsSAnZWagAASNkP/AxcFEdzbJB+s134zO8R\ntu6nvt3CHzSjBRrhJMOC+PU56bLZIa75GLTXie4hL2Mgc5mEWcVkR6bSEnIx\nJx8dUOCm5QMe9YPFhJ06pxPCDHsiAbaEBPa87f0G8URssOIB7fx0W07WcK8T\npXAUB6EJ7WKezrBqAIQ6MR+e3dh+88/KP2/M8hcb3oRUUBU5y/Gr+sd9scxe\nLIJgOc7o09sasFZU9npqVYhL+2CvF6CpIMJxhgtW2BMsM9mvu65HfVllvIWP\nUlGCnKbeFWUCP2gnRN2XLMzWuQjRHXWtHRX9Wm/S65DAnufvD2htikYBqqb5\nFKob6zMqXzbLqxOTg0/mrCoWzHLWhTcZPwKq20cUS6UBjsAhQHs1inT0AM57\n/g/aIdRWxoZdrmYiDlcD9zz/0lYfnZY/edhX/6YzN2kfqlIPivWiDDBPPhH1\nMtt/U2fE//gSpiDU6O8KmexM1lfMa8r63FG/ITPJ+wk5tiEsQwMhm19qAzf5\n25gIIqVC7ADNSFKTlpkcoYWW5MIqNszsbDH0P3/Tea5WA717iiZItF+Jy5h3\nz133bpYmFF/QUBePWGPobBSk6ejR72EKKiPAn+n2wNXsz+27oSBEmhe6VAIO\nnVNk+y4RAecjWMmm9uhH3WeaqvpcKZ3IcOwkAs4Z6GuFRh1K2wyUtWF99zxH\nykCQ\r\n=4+BM\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c49cb4837b78754e8fb595903c972e41fb5f10fb","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.78","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.1","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^0.1.125","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.2.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.114","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.18_1616803056070_0.8845103585000031","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.19-renovate-balena-jellyfish-environment-4-x-b1e4ce5aebcde1179c64bff81154c2e4d1e81d00":{"name":"@balena/jellyfish-core","version":"2.12.19-renovate-balena-jellyfish-environment-4-x-b1e4ce5aebcde1179c64bff81154c2e4d1e81d00","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.19-renovate-balena-jellyfish-environment-4-x-b1e4ce5aebcde1179c64bff81154c2e4d1e81d00","maintainers":[{"name":"belodetek","email":"team@belodetek.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"51218250e6c6017385200c14e54ae49d17c08bbc","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.19-renovate-balena-jellyfish-environment-4-x-b1e4ce5aebcde1179c64bff81154c2e4d1e81d00.tgz","fileCount":99,"integrity":"sha512-bqdd9eg9hGdDwkaXI3NQvyTxqtps0v8iYxnIK0KW7V4YSxOgv2XgV278vjFNzU6rDlNvFF4qkQzosb8S44dOQw==","signatures":[{"sig":"MEUCIFBMWbca9YBfK0Qv/S/Ay7B+CuXyXUmpiXEAtkrxPIdCAiEA+FKqoD9cuoDaPJ10NfX83IyyTcpvUiA50dzwxogCm5o=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":665147,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgXnXWCRA9TVsSAnZWagAArooP+QFgbyaYmtG20KUOKUD8\nafazRhxL+aeXwW0rRg/WZK/OPnaZN3bZCEKWH3OsrB1PTCSAa3VekFNX99Yg\nKUu0DOrFaRx6zFEsofVyEeLIQswhhFjuwVaORTichYKCHjIV2LzUz+S9G9le\nOnEeEZiFEF/jMZr0SKIA14nwlRhXgoQPNpNNeMujdaEgT8vozfjx7mvi/D9H\neIV9EPGtawqo6Xrt4Oth9ki2AVQIYLLZ5GQAeuW1p+QQIx40tpAxe+8mB+by\nb08lcFyEQ9Trb0sWF48qImyX0Sri5qqb/Mikx/wlFV/Ydu2q6w1sue1HBKGI\nbSL8Ek9CTzEhdnEblzY8ksZoaqz2l8JBiEDNz6vGXI/M1dEfg3SJf2Cs6ICq\n3gkXRx6eRYIdo50IUAGE56x8mmEPsY3ebOX6NQVtIQhdq6EflJEw1LADVtBa\nByEo4H8cGDETeQ3gD8XJQmpQBQ0HR9bOi2iErwEyGL56hyIq2zs9NKlJd6LK\nSBWjpqwp6OAN2H1X5qH+O9FmSjuZ+qo9I7WeSZ9NLa0tDI9PqXBuye+f9J2J\n91fArnvHawXdkMzhosDF4uY1FpzzLmYTtghBeoigY4Mnt2ssARfIhYNhjP5i\nSouV5VCXCJp/5V5RiIHQUhy4fcl54XyYwRZqlbGbUdbRJnIocAESH/8B26xM\nmNP9\r\n=4IZs\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b1e4ce5aebcde1179c64bff81154c2e4d1e81d00","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.78","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.1","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^0.1.125","@balena/jellyfish-environment":"^4.0.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.2.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.114","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.19-renovate-balena-jellyfish-environment-4-x-b1e4ce5aebcde1179c64bff81154c2e4d1e81d00_1616803285763_0.3073261141986099","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.18-renovate-balena-jellyfish-logger-2-x-f674dbd085e2b4ac0f2bb9c253e094fb84288593":{"name":"@balena/jellyfish-core","version":"2.12.18-renovate-balena-jellyfish-logger-2-x-f674dbd085e2b4ac0f2bb9c253e094fb84288593","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.18-renovate-balena-jellyfish-logger-2-x-f674dbd085e2b4ac0f2bb9c253e094fb84288593","maintainers":[{"name":"belodetek","email":"team@belodetek.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b96f20504e8182997192e557ac8b892240a95182","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.18-renovate-balena-jellyfish-logger-2-x-f674dbd085e2b4ac0f2bb9c253e094fb84288593.tgz","fileCount":99,"integrity":"sha512-cVUi3eaC3e6hF7AKGOAszM+Ib73Dym0KcIXhsKVw3T+FxaN380LQMuaUmwc0eJE0zu3wC01QYZmN078OCAVqbQ==","signatures":[{"sig":"MEYCIQCHLviXDC4wq/LMy2iTexpg2IfoLfU07Pd/N+D+iRRQIAIhAOz/Tsr2ESNC5Bo1pv5ZqkeC472nZVW+QyNZ0REgxxIm","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":665043,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgXnX4CRA9TVsSAnZWagAAmmgP/3gveYRCaANTbDe2WO1x\nCTd3sb4f+n47PESxZjbBbmgM+n07NoXoapR0nTue51SKD85s9l61xacxgIzy\nKwQk5j8A8dbliCxLkF/qCPWBIyTI8sbjxY+nyOnZrg0OAyzcfTpA67gcUyaq\nrr2syQFm1xfidbOAWF1zGVinCcwEqWkTDmI74lb1xc2zOdgonparZTpJPteQ\nBvIP+iiPs0taqwxLYUXdqDHf5tmXlT+5ix56g4baXyonJMKEPDsrgPS2EwDY\nCw8hFgCw+uQ/zhWuCAShxoxB8LLzGWhjcl8DRDzWTF1ykksLHsRJOg5V1vyK\n2L/1Aey+sZLRMcOKRavqlW5p93OvEmLMgqA0IaYnn/hZIekVUAIr9yjo7ebh\nHuoeKWxudaD5XQCy5ZfyqEQH/IiCl3SAm+BoNGJ/LSddbjBEr4bPd7hyhnaM\nBO4i5v9QqBojEsvC5dU5GYhwaOtPw1mFDdvS6gfir3MzKeGLxmopPH3l/lc7\nHFSxdv669vjHVzZHSWnEMK9ve2SjJT2HxjtQzX0XMyhn5uB5sVl5oBWsuAwk\nqH2n1pJiBz4UmTo0vZdQdgjYwgubazueNx6PDOy+adqoJmArSn7QIElPgCFu\n52VhEKIXmx8zUehH0Y4uYWG56bcqHYNUQy0FUTXxK/q7GSB4yK3vjRlfbcpv\nd3kL\r\n=an/V\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f674dbd085e2b4ac0f2bb9c253e094fb84288593","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.78","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.1","@balena/jellyfish-logger":"^2.1.12","@balena/jellyfish-metrics":"^0.1.125","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.2.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.114","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.18-renovate-balena-jellyfish-logger-2-x-f674dbd085e2b4ac0f2bb9c253e094fb84288593_1616803319537_0.12500368987299004","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.18-renovate-balena-jellyfish-metrics-1-x-5fa787f9062bafc14e16f794153c90573b6fc677":{"name":"@balena/jellyfish-core","version":"2.12.18-renovate-balena-jellyfish-metrics-1-x-5fa787f9062bafc14e16f794153c90573b6fc677","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.18-renovate-balena-jellyfish-metrics-1-x-5fa787f9062bafc14e16f794153c90573b6fc677","maintainers":[{"name":"belodetek","email":"team@belodetek.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"11777d56f3224a1fb839bb577d8818a2b3233c2b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.18-renovate-balena-jellyfish-metrics-1-x-5fa787f9062bafc14e16f794153c90573b6fc677.tgz","fileCount":99,"integrity":"sha512-pKToVL5LwTC9KqDt7OZKFWn4ifzxpZ/sQSTGCAoMx7g+VqIQMHZk5VTjufy+MQyGZ0Y3QWm/u2cWUskgUv2Jzw==","signatures":[{"sig":"MEUCIBtXhNSDvp5VcSJy7yRU2MEhAg7596PNmVuoJ54c1P4AAiEAvfsqPefNuUH3M9h61md+4b3NlkXmT0CpL1u1phDQ5oY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":665045,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgXnZ8CRA9TVsSAnZWagAAZIQP/31wiHedUr6uEDaNAjqZ\ndLY891d9aLuJDIXUHLACa3HurJZQk6m2CKDxj91p5IEyiZ6u5DS61l8sOK5f\nzXv368gVm8cu7obs5GfAG5XndOlV9lav+0skgFtVad/J7hngpKE8WbCsdFwH\n8FgLo4CACuEZ3cqD7Hv3xOMxgFPCFYlYN3EatOXw2nu892H06Nl0ZYvFvrj1\nlH3Cz5KETt/ftl4kuyF7CCMZj6zv2kd2WmhZYgH97yy+zzZHxla+jdxwmbI1\nvECp9t6MECBQQ+ALzSZwumX7pgYjiuNC5reWuQvcOeIc8tJyf6/WngABJEA9\nlmmXQxpkKJ3yGYYO5W+o2oKiH1GiwKuJKhjf0Ww9sT0HOnVC+gi/2Zk0Nmk8\nrDyStq8RurRifQaep2CTkxeYIX1ohluMs/yvFLHOb+nNrZNTy48FAn+juVO0\neKqWFrwsq8y3zyGJXTlzank1WBFGRxGcWKMbtnYc1gE/nQrpmld+cQvrb8Z+\nvIiwBvt+DmVjq1DB5/h9z5Qp63b2Hg1jqQSwQRUHntf2+SERniawcFzshCxS\nKCvWLCXMlHbWvtTfH1KFIz4DoljLc5AS3z3aBWTDmJXBze29iJqla6WhPtDv\na5r9h+Agnaw/BJ9qAureKM+iDHbNh6KSqoM+MpJi+Q5W8DEVrEQPHY28jYuB\nVefp\r\n=Jkfi\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"5fa787f9062bafc14e16f794153c90573b6fc677","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.78","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.1","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^1.0.12","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.2.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.114","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.18-renovate-balena-jellyfish-metrics-1-x-5fa787f9062bafc14e16f794153c90573b6fc677_1616803451491_0.013525268574622995","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.19-renovate-balena-jellyfish-logger-2-x-cbf29ad396d708adfb64569bca6d1b90591d0b4d":{"name":"@balena/jellyfish-core","version":"2.12.19-renovate-balena-jellyfish-logger-2-x-cbf29ad396d708adfb64569bca6d1b90591d0b4d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.19-renovate-balena-jellyfish-logger-2-x-cbf29ad396d708adfb64569bca6d1b90591d0b4d","maintainers":[{"name":"belodetek","email":"team@belodetek.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"59adc95ad247fec4e91c5855d55a59b9ea249cf6","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.19-renovate-balena-jellyfish-logger-2-x-cbf29ad396d708adfb64569bca6d1b90591d0b4d.tgz","fileCount":99,"integrity":"sha512-+cFDsUhB+cJS0WnATJ1OuzTE/zuHdC78lRhF4kb1K9I+mjLG+XwT03u0614liU3MPLhYj/XZBZo4cNvpfgYpzg==","signatures":[{"sig":"MEUCIBZDGvBhliNegtXUvKtP0vxDSp9Uvmis1UM3wLQqfknsAiEAk2HTP4W27Snoz28PhUm8rUMUnohYlUoKp43Bb9CjoqE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":665132,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgXnb3CRA9TVsSAnZWagAA9pcP/j6P3hbEHtGEzOF1M3AI\nj9qD4d6OCkUQwV1A7FI8pUAKpP3flshW1h8pDtOG0ZMOE8UgOKhA9c+fir5h\nfmq2oi4cHkdZWKZBq2JmvYBca+EfF5MKGf4Uqxhl7Ez9xTeC5EPDozNgDtNw\ndsTe7f47CJDvXgK+7K+gUevtHOPtOmgaYaOgpkIY5d4l16wjwsyDbqje1K6F\nwcScSQhHn5ZQ+THZuIZ/6apTUGkOjwQwEzsTHW5nWyzURcBvJyInyYbbuA0I\ngcnGyQqUWFus/csBT12woFOEoB0Z/HG/G8nRfztdh+zQBpUj5U85u03wr3Kf\nLoOA/GI4osREjhlovnvP5j4kXVM0Vs/erolHSKsI/US8mM7+n8TVXO/GgnVO\nSSmqcvZPYXl5cINVGSQRg9CaPH5ekXBIEgJai56itFowqLlRqTBJNM5tBGsC\n2YrlUKFs1fWaBVcUX8JNYJZ72136kvfxgB49s7NWVGmWi1NbAQmMO45PMOok\npcrzZBMpv7ZYQkLVgv+pci80rbHTDsVqWJSePxJBadehrxiBGEQv8+RLnhyt\nMwK0JwswKMSlFKdzTbiqsum4N4FbmPZptq9BQPZGxHjB587TKOO730FDgZGM\nLL3H+xJ4txK2FYX7JakagJPR38It73P3lz6/s/JfA4f+InsKQhe+BdcRM/BK\n5JBc\r\n=Nm+/\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"cbf29ad396d708adfb64569bca6d1b90591d0b4d","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.78","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.1","@balena/jellyfish-logger":"^2.1.12","@balena/jellyfish-metrics":"^0.1.125","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.2.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.114","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.19-renovate-balena-jellyfish-logger-2-x-cbf29ad396d708adfb64569bca6d1b90591d0b4d_1616803574557_0.08348473583400273","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.19-renovate-balena-jellyfish-metrics-1-x-f9c08c22c928fbf3b4bcac28b75cb90bfd7086ed":{"name":"@balena/jellyfish-core","version":"2.12.19-renovate-balena-jellyfish-metrics-1-x-f9c08c22c928fbf3b4bcac28b75cb90bfd7086ed","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.19-renovate-balena-jellyfish-metrics-1-x-f9c08c22c928fbf3b4bcac28b75cb90bfd7086ed","maintainers":[{"name":"belodetek","email":"team@belodetek.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d29414b3ea399f4b1410273c8b42f5078ba4e859","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.19-renovate-balena-jellyfish-metrics-1-x-f9c08c22c928fbf3b4bcac28b75cb90bfd7086ed.tgz","fileCount":99,"integrity":"sha512-Do9I9E87gxlXn/plfTmGnXBvqW/UpTvmbrTq+315oYCoFVRuUtoGAcsB7J+qXYnHqHm3X5BR5dHUXXQF3dcD8w==","signatures":[{"sig":"MEYCIQCjNp5RH3sSUvuZMHjJ130W7mO0sQmlkwo6Z3+H+oyW7AIhAPT+ynWshzYz9fRV8yeHglpfmgUdbs7PLTjgMGWbVnUV","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":665134,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgXnckCRA9TVsSAnZWagAA8kEP/j5YF5RRxAq4ryut8aJK\nMaFoz/w2SctBbbDLbJ6XGJgTmhjLZEKEZsMm25CAJ2PCbYTTlgC2eenJCg7c\nk1brWIWVpA1mjUmSO/J7ApGmAemyZ1lsatZWBzOU0ovsefycVmNLbLTmyon7\ni0bBt2AeBX9zPgp54v0u3x4GnC8gKhEnSH1wEa1Tqa6P2I6wd4pFeQczGzS/\ngISD9Q9/4oFbqW+gUijYECKE1sPxR9zBJjO0AlbX6eqbIWoT7bEyN3CIiTl7\ntWvBYDunCxqpEYaugJErgLuVAXx1G3h60kqyklQcP31x76pd54e1ye8S9v+2\nI8+o9NXJNxI1F25NZhCsmI22Tsu7N5MmELDo9kqzOL45OqiwJvbqW5caSfTE\nCOFWUlHOARTWv2aV7aqj2MzKlAiDC80WrP1GXYliOCXmVqrBUw+iNY9rfNKY\noEnJ95XrRNCJ85wmzX3M1RxUi6ideddGT+HuaEhbTlLKzUc/v56JibTVacJa\n52Mx5hkQNmfCY2SVyPQllLQYEd8KrW/bYTiQWEChM7Ov6lShglrD5L3gf7+R\nbtRncwmmzDEiSLNxznAZDT0fTd0fKGvEI9jjA8O7XDbiBSB9KYoWtNEaJpiB\no5oeKA5TbU2owGjBSSeQV8cEow/jkRkK7qkSwQn4LsLgtN/q1GU9PXv2zBmb\nJsiK\r\n=maLF\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f9c08c22c928fbf3b4bcac28b75cb90bfd7086ed","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.78","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.1","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^1.0.12","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.2.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.114","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.19-renovate-balena-jellyfish-metrics-1-x-f9c08c22c928fbf3b4bcac28b75cb90bfd7086ed_1616803619755_0.5755338626869517","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.19-renovate-non-major-ac1a27077dfe8554f4ebc0ac856e8dc07a510a5b":{"name":"@balena/jellyfish-core","version":"2.12.19-renovate-non-major-ac1a27077dfe8554f4ebc0ac856e8dc07a510a5b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.19-renovate-non-major-ac1a27077dfe8554f4ebc0ac856e8dc07a510a5b","maintainers":[{"name":"belodetek","email":"team@belodetek.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3391e142d0b548e1d7e8724e2725bbda6c9b6e99","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.19-renovate-non-major-ac1a27077dfe8554f4ebc0ac856e8dc07a510a5b.tgz","fileCount":99,"integrity":"sha512-3IPwLV+w2GjyGC4/C0/LWiyoS3TFNyeNEF5vR4+Qkpy8HQwLS3sLyJYYi2+KIxsxAENy6bc0EZFx45ibrwlj/Q==","signatures":[{"sig":"MEYCIQDYfgsE9qrBVoi0mMm1toUACGYUbjLGjUmh5D4pBKF1VQIhANrotEm4Qusm7yIGex5j6cV4o8aI/nikil8TTaxPPllq","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":665122,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgXnooCRA9TVsSAnZWagAA5HEP/22y+9CCYnVvGe8DfRXX\n1P4Jcvq8IE20Gt1a8q/bYNnH7lmV55upP2Y/UUo/xvUloBXWdWaZWlS7tZtS\nZSN6Oy4F9j4lO1JEhsUHjsjmWDv3YYDvDCPvaNuNgTu/cg+ur8tIWaoql0ot\nmzgvxgj5rlKh8v5YwaZU+m2hKKj5AGn84fFF+G4f4oXOA+kGOzD6jWR7s1fL\nOPJRRAjo5YqghWaOLfuFQm7mRyr0WR07YORzULbvIauFF1GeWy3kiOPQpdZ/\n6zFhvw1yLHwnAfTMxpHnpd4/r4Iz9qbv38gemD21r/Iq2cR3bPa03Vpcg1MH\n+5B2gjByOD2aWwEuZ0HOBx+VCHzqmNNyniZiXKWiDMjVe0+vewSeOnDhnQuv\nffBFdidm+FfdWF7A67Hz6nXXFAsXAHgpYKyZa0tVpqXmn26VZivqAgqXQPwR\nGfniGgWtOhTu3DiOheVVUZMAOHUb+BrbuWrMQaKrOvfIlOEDcM1E/WA5FXTW\n31k/ZuNxC2PRnXf2cbKHS7zpcctLEHS6wK+gjuI84BqzBEZ4UftbbJHoqV4V\nq+hSVK6RfgZbsHCFqB0ZZU4UkKauRLE055zKk99lnNtVh9QHxmkmHrV/w4WE\nytoXvmrK3I3zjMhn1NZGYwX6M4lF2kL6CcVSqhI8s1AWiPN1RHF2Jyi6OUf1\ndPUB\r\n=rNv/\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ac1a27077dfe8554f4ebc0ac856e8dc07a510a5b","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.78","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.1","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^0.1.125","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.2.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.115","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.19-renovate-non-major-ac1a27077dfe8554f4ebc0ac856e8dc07a510a5b_1616804391810_0.7332187273949666","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.19":{"name":"@balena/jellyfish-core","version":"2.12.19","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.19","maintainers":[{"name":"belodetek","email":"team@belodetek.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6cb1e459fd86b7238a60f0f864fb82fc0dd1c913","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.19.tgz","fileCount":99,"integrity":"sha512-B335Bc6w3c4EfIcZqTyIaKEpc2hnBAeAQwX4XT5lZHtZaNWabUxIfp2D+oXsMXWgX4itQqpYBrTKGcAjbr9jpQ==","signatures":[{"sig":"MEQCIAEQnHGpz6MVjeix9ej+4tSAti3DypG7dAZzIxkDtwOSAiAQZhSzEWEM4QqiLyVPLTGbI5TtfL+PQbZ7/MEj0aDy9Q==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":665062,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgXns2CRA9TVsSAnZWagAAzSgP/jfboh7M9ZGfqnQwkMYp\nFj925vpRwBrOCwWlD7al2YLvnMukUglnep2p3iw5tXFn5u82uPeo9LarcdWX\n0/oas5C2hy9dFUKiQ+K2DErhxNd7wqoSCnlV+DuubMtav4/afwSdq5cu2dFH\nyp9nKgNC/KrL3LYg8zcnH/mJwv/Xzldzp0+9YtpYuBhE4Oew/IpOiGUCvthD\nPJ3cTIP2QXNJLzYSlI6PjLRQtC+lWF5CPmeJdEMq6UPh6eH7Pbatw/QTe5OU\nvFdAQRWcpH0ZPCWQBIAy4ZgcuO1Eu0quyB7beFLu8VfX0YQLGtPMoDlFXkqs\nGMBizNiK2dkkGBdNfXVlpyX0icWLdyMOg2HVl6KOmzpkcWbQ/CDKF8JgDCXm\npplNlacxCUIGL8c6KwWKqHDXEdvgVNDpQVb7qXmy5pLmC2KQTCU2GMj1Ns4N\nwbsnfT+H8frEcSzGDWNn9lAuyWFbLAfyNcz98YugEx+7XXX6UZ2v12E7v9Il\nTEVVJ6wj2mNDbvGLueOgj5Tcvejh0/jqiO5QgS88VwZ2znLRCquHpHuMRsX3\nthqbAEg5/7fsHjTdt+tc6JlYRDxgokJaFkc9n8kPhWxfrR51GJdncGREFp3p\nuYi5q/wovYZ1DHhgcow6h8M38+HLXQ/PAMTyY0DSMjrUVq9pCYKmwtSw9Xr7\nlYHY\r\n=GUsb\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"03def3cfc12a653fc4fc29dcfb0c8049d6f615ac","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.78","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.1","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^0.1.125","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.2.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.115","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.19_1616804662067_0.7136788135531547","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.19-renovate-balena-jellyfish-environment-4-x-8d7d244b9097e55cb8184eaf51d60090c3decd47":{"name":"@balena/jellyfish-core","version":"2.12.19-renovate-balena-jellyfish-environment-4-x-8d7d244b9097e55cb8184eaf51d60090c3decd47","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.19-renovate-balena-jellyfish-environment-4-x-8d7d244b9097e55cb8184eaf51d60090c3decd47","maintainers":[{"name":"belodetek","email":"team@belodetek.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"25c4cf4fe28fc6dcc012bb8ecb9b7dd50ef77083","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.19-renovate-balena-jellyfish-environment-4-x-8d7d244b9097e55cb8184eaf51d60090c3decd47.tgz","fileCount":99,"integrity":"sha512-HToTFmRSrjDsTgxCrQAoNdPxyrX+VLExQjja86zFo2Zgsyn2j8Ojjo+kwrLPkfph8CX+fi3YuwL7/o9k3dQG8g==","signatures":[{"sig":"MEQCIDYbp0HBRmhDPqmxkbucqhRtU3wAQhMKBRXLYuOjIAqKAiB20hUpefU6LEb6hbFM+aRb0vx4JVhop0xDgfA8Z+/3Aw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":665481,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgXnwKCRA9TVsSAnZWagAATToQAJl6HcjEsY6sfim2Mu7A\n+csjIfJnAK7q0n4w9KIFzOX5RXgSLw2z0W+0JhfBYpMyxbe+bgOi9zMemX1u\n/F7WevLc+Ueb00Azwxk7tLhbyE6QgiRVg0jHHclKBla7i2W+GscuzMoHeGzq\n8H+YPMxX6T5VuNwo0zlilPkqoYLOfDeoiifxxmda5QcvpBQB8sOzw9UR/IO0\nTLbUzAv4CpY7sKc/qP+WSVCB4C0UQYP0zbCUhPf4RkLYLg+teaKrLtC7cIp9\na6cICbKwNlhsWSa9+Z06s+DqC3IBntR+fpRHGefU8aG7yrzcos8Bg07c4T64\ngVJepItJskjHS2iCeD9eZPskNuQ6mbuCEqECnyMBqrI3bZxxW54K3b7BNdj9\n7w6JsTpvMSnWO6+lVSvB/Ri6PN2xwgFNTpELy+nXl7sknkrBEdXbth2W12ts\nlAVjX9Z5cDjoQ0jy5nAjaIJ8tHLvIylTiuILU2O4vCKrodyhB8z6+dXRgzgz\nWra/9Ue6nOu/Bgx+5eV9RaxAw6vyPBYk4nB/5aka0yIX/AkppOANPsrpnQQu\nPfSomiBKjmtC0wqZYWBEVDMDBmX9Tqz3k9F3XXC78UzDFOYp30X0+jq5SWbJ\nOQ9qqkVBUSBNCWId4gxOZHCHsWRsfAmIXzhquAAvS4Tz8RHZMTBy0L9/4FkU\nW6N0\r\n=Jy1+\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8d7d244b9097e55cb8184eaf51d60090c3decd47","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.78","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.1","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^0.1.125","@balena/jellyfish-environment":"^4.0.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.2.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.115","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.19-renovate-balena-jellyfish-environment-4-x-8d7d244b9097e55cb8184eaf51d60090c3decd47_1616804874060_0.8828020167156729","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.19-renovate-balena-jellyfish-logger-2-x-ea5b463412e863bdabcff44ae27c8bf9424305ef":{"name":"@balena/jellyfish-core","version":"2.12.19-renovate-balena-jellyfish-logger-2-x-ea5b463412e863bdabcff44ae27c8bf9424305ef","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.19-renovate-balena-jellyfish-logger-2-x-ea5b463412e863bdabcff44ae27c8bf9424305ef","maintainers":[{"name":"belodetek","email":"team@belodetek.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"74a6689867577aa5d53f7cd08d14287bdd13c7ce","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.19-renovate-balena-jellyfish-logger-2-x-ea5b463412e863bdabcff44ae27c8bf9424305ef.tgz","fileCount":99,"integrity":"sha512-DltXTqDVh+EXnu3lpgtrPqKxbv7AAadcm6ypVv/Pr0Oj1lsnS5plfRZ6O25xDZY7EZwKIiYuJobuLvjyF47AJA==","signatures":[{"sig":"MEYCIQDHw3JYFhxKhzCNv0eyfBKg4ojjUUQN1m9OngCazSKnNwIhAJ/GLsPgHn5mwv8bHdyyiS5dBFyR9UWxTW21FY/B+DMd","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":665466,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgXnxvCRA9TVsSAnZWagAAuMYP/0OdpAQZLKYup/xArWXU\ne9U0+dcuKivwCHYhMqORHeIYduBuw/mrvDiQpZb5Ak9cm5QYNhwmdvsY6lPx\nCIpW7aQ8eigKZqpDwduSPot5fhf+w2Yf9MAFt2UONnxu+ExxrYkSHsCnlz94\ndYOpA33ATfaCU/dr2/EDXmXKGJi5Obh+EqPJjf4F6UGPIeOMTP3xC6PvEBnc\nN4bc4+6v9d1+iVxFfC1qfptEDsam/3swmosZZrXgedS5W6CslfYMLG+iho6e\nl+2FS9S5jwLYhA7lJwjUI3l7Zkm92b8FL13pPU4B2bEolF2aIpOq9588JTju\nKWGvvZ3eVla9/XlXUyY2q9gHM2wt9ghYdgcVvvwDJ0f985blksPdNlwTAuo0\nKlrWB9mvmCFSFC+FHYadC8ENDJzwMioeMVBpzxOl1ELki6ymRYpXCheYMhST\nq0QjsN3LkE1EojbNBXl9OWgsKmAF+gmpIKo0Yo+lD7mtju4EnTeU8EV11MoL\nztDjn2QLkCcGh060xQ3N4zUXgBk14+d69HybGyjyhZYoqWPLRw131qc0v8Cd\nnEfqpJZaJjmHtQFCW7sz1zp7Ec93oh40XtivsX+JoWsnKg+QcTa1ONDwgGpT\nbWCpWjp4emv+LFjRKtKXSSYg2otTRpa3Q0FXvfsoLQBStinwByWY6819WV8F\nmMPM\r\n=ij87\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ea5b463412e863bdabcff44ae27c8bf9424305ef","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.78","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.1","@balena/jellyfish-logger":"^2.1.12","@balena/jellyfish-metrics":"^0.1.125","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.2.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.115","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.19-renovate-balena-jellyfish-logger-2-x-ea5b463412e863bdabcff44ae27c8bf9424305ef_1616804974703_0.13857616721887522","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.19-renovate-balena-jellyfish-metrics-1-x-300aaa2c833aaa01853102fc06f3957a397f8670":{"name":"@balena/jellyfish-core","version":"2.12.19-renovate-balena-jellyfish-metrics-1-x-300aaa2c833aaa01853102fc06f3957a397f8670","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.19-renovate-balena-jellyfish-metrics-1-x-300aaa2c833aaa01853102fc06f3957a397f8670","maintainers":[{"name":"belodetek","email":"team@belodetek.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"92d2400282b5ec46ed003478b7f28bc90b1d59c7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.19-renovate-balena-jellyfish-metrics-1-x-300aaa2c833aaa01853102fc06f3957a397f8670.tgz","fileCount":99,"integrity":"sha512-+Aj8DdUNwcvsHUvDivma8t04DdzsWZWMzLrdvsZo6Z/imHUx9UZO8GdezS3rXlv77R95uarlVb+7f3lWHkZVDQ==","signatures":[{"sig":"MEUCIFRG9b1PiEbRnGBRS7J//ilNp5JBDctqw27s+aRV4SJfAiEA+c8CcFKNWt5WfVRGG3UxMjOjAeRJpk8E1hRRACCsgz8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":665468,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgXny1CRA9TVsSAnZWagAAcYIQAJ0SERgcBhou9I6Ie9n6\nfBZOCjem+5U3ahoJW7xvdNw625dsu8hAE8ZzXiRZ6j/wPm6XenA17oipniAM\nHRofh3J85pIurEOfxF0kNAMGBYXb/+6WxhyUmR7ydsMSdo7HSNMMRCvBUzF7\n8cHNj9Z2jwDO3GFGfNwaRIWyS86djoAgIXt1NfpQLiMCxrgV0iFOhVL/ihfH\nRjg+dL47/VqOXQpcQJ/2r2RoHYAkBmoqME4s29q0uY3uKl2Q4Ek7bxSNz14/\n3562ku9J2dS1fbqDzYxkeR+IJPrsklwU3f2r8SEld/TN+q/3yXIrGnlPtcCc\nQt1jRGDe+XNieaiWTwr5mwreUJlOun4KVBjQPsa08rk/riQ5ZjdaNmmL7GUb\nNpgadhxx9k5uc55oJl5eayT1n+68nOPQhh1YMcth/MbaF8TjoLeEgxbIExxk\nzhCAksCusCq1X9g4IIc5tYbskp5Zkk33dcaDqVJqs3CsCi3WMaW9hG5NFepw\nD46LU4qECnniVi80YvGqSqwdVqvSp35X6Ys6T+1+6H5nFtK8TDT3wD280WA+\nZ/ivOXxmmFv8E7LRf4zNHhOW9S+qQuJ6J2XTcXG8vTzM4VNtCJtDwoEjZRgf\nc4cUQgMKvaI5cYJQvEFZu64l2vJrR66KbIgv71dQMIs2W0+BOtGhLuFryXe7\ntFP0\r\n=egzU\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"300aaa2c833aaa01853102fc06f3957a397f8670","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.78","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.1","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^1.0.12","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.2.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.115","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.19-renovate-balena-jellyfish-metrics-1-x-300aaa2c833aaa01853102fc06f3957a397f8670_1616805045247_0.31499278872227543","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.20-renovate-balena-jellyfish-environment-4-x-e49acb01859134b6c39edbdadea41bc2b1941def":{"name":"@balena/jellyfish-core","version":"2.12.20-renovate-balena-jellyfish-environment-4-x-e49acb01859134b6c39edbdadea41bc2b1941def","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.20-renovate-balena-jellyfish-environment-4-x-e49acb01859134b6c39edbdadea41bc2b1941def","maintainers":[{"name":"belodetek","email":"team@belodetek.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b40f0a8ffb6c08c4aa60b731d3be76eb512ab220","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.20-renovate-balena-jellyfish-environment-4-x-e49acb01859134b6c39edbdadea41bc2b1941def.tgz","fileCount":99,"integrity":"sha512-ErjA02hPJrlGvvhqx8cyn+jTAptBqRqgwIe/DpMAWSkO0/ULzVmUo3UDpLnMJ2Mb5/eF84XWY7bd6CxfUEbkkg==","signatures":[{"sig":"MEUCIQC0qpkC7uA6B5R8jcqo9oDum++qRbj/xXmmSXEhEAWocQIgf52EDqm/502KjzcnUAli0QoqHf+1SfHegQYaMB/LNB0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":665570,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgXnz5CRA9TVsSAnZWagAAIPMP/3lfbEDKWql1qqFyyIWW\np6GFs/lwXMWUAapxusSO75HK3Njk8lUTZ7gSHA9WLtHoktI34rZj7kihJE+/\nVwu6SKFx5aTbYUFwGi3r7EdK6+yaV61GLGHfDwsiEi0rwKAD2iCkLe3xRphk\nAUVDQm7kPzGbnQ2jnbVxOkVue6j0YT1QH1i4L93MbSu+wvTEmU6enEbMcM+E\ng1wMwUw9w1r2CK7QJHBlMYJ+aW7w768U/ZIaaOFi6j1U6ecNjNqzDOeZkv1K\n0/mMR7nHuJBYVsdfSEQpcizuiQqnGq4566W4+fvJY9VUNWOV4Dnfy0xh8UuD\n9A9U3QIaAsskmPQD4Bw1UHzWmADUbt9nlGKs5BAYXfKqY6dmUXZoOhvEFyAI\n3+R3MKPuEn3m7qTREnPUq0vXstnpmay+VGG8jAqDwycczeWWhhvYtYx+uwDZ\nurj1/r7E6/4LdKvlmLgY4kKNaA0bqhxdWGjshIAPhRDrf46foBKL8uBqBvht\nLsE5qY+cPNnPaHLMzdCFXjaIvDzhjK+VQO6rFiS1K+JNTcjG2ytmxOuYo4LY\nnOoGJFTIPYri+CFrtG5+gKjK0DtCGnubKen6GHC22eMwOXqdLo9kZevtyNbI\nmcndcufS+rYlqRmaxbIsUPO7Nxkr93PaR7iYoYlQ6SeTiV7G8qoNWBHHDBA4\nJx3H\r\n=oA8x\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e49acb01859134b6c39edbdadea41bc2b1941def","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.78","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.1","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^0.1.125","@balena/jellyfish-environment":"^4.0.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.2.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.115","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.20-renovate-balena-jellyfish-environment-4-x-e49acb01859134b6c39edbdadea41bc2b1941def_1616805112883_0.4727402934521989","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.20-renovate-balena-jellyfish-logger-2-x-bde12b165c91c61977fbb6255952f96f10fc1388":{"name":"@balena/jellyfish-core","version":"2.12.20-renovate-balena-jellyfish-logger-2-x-bde12b165c91c61977fbb6255952f96f10fc1388","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.20-renovate-balena-jellyfish-logger-2-x-bde12b165c91c61977fbb6255952f96f10fc1388","maintainers":[{"name":"belodetek","email":"team@belodetek.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"20dc1bb6109b636bc14f4e760821a20fbae20eb2","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.20-renovate-balena-jellyfish-logger-2-x-bde12b165c91c61977fbb6255952f96f10fc1388.tgz","fileCount":99,"integrity":"sha512-LOjgdoXsRoUjOhBgAmfJ8ezsRUKa02Kis8zaN6LMmIAC43R1M0MlCorIR7NUfcyty5qkOUdlEU2w1bq/Zxa8OQ==","signatures":[{"sig":"MEQCIBkTEQ8bnttEUmMSkWKY4hLva0v2sV3NxhdmW1NO9rV/AiBi8IbOmGUM5sAo/j/hG/44P7iH0aKBWE04exxHmOtRRQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":665555,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgXn0rCRA9TVsSAnZWagAAKEEQAIW5Dd5mAATJ6CTaD5Fo\n3NsLLw96hX6nRC1bidqpPrZWiyGR5FioFo01tZ5IF9ArZln+Tl1njWB7A8Fw\nTpzbCvmwylm8fnizaRb2E8CK2JVkc3/+SFArQyb98/e3EkUGvi4ZO03B/FUL\n1hunt1CR52+cLeIz38Z3UjPCwKcypMRPpi7EiyekL9tGkjfuP27Wmm8bv9jo\nyYG5L80061+/F56JHbGVx5dHaclP9ibwiw6OmsJTQNIaXEAroVsYivFa1D2d\nH/8iYOpUfFW7cldjDLMaqwRv6t6qxvlArZemZuO4ArdJXUN+//B/IgwiyC6D\nLfqTlJ51TLmWy+TKWT1JlN5PNGUGlBq6RjtfOn6zZwqE9g8KS7xAfVO+MgeI\nuOdb9/Jrb9huxRF9KSmnLfPmaVLxM/aSL52IhJk43aO21UXNsuwNCTiIJYZC\ntzz2PYJt6Y4Sqdot3xX7VUHBMlU7kC5NqFtd8t1PYDcI3iZTdzAnbj8gT8h0\n7jx5DSPcx1lmuZ3tuug71/NghfZYZtf8/PuL+9hhJ6JZTmtVIadDMwypkIcl\naBwLjwyc4Z2ayUu+qtKQQLxyv3rQs3xOAxngWC64hQfkoo/s6/tSyvgsnY8A\nGBwsjfbgnKAM7Rp9K13twRFuYG+s64Z2fmnZLm6+/ysy+yH8smBnshEI1LPe\nhdYG\r\n=Bxbt\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"bde12b165c91c61977fbb6255952f96f10fc1388","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.78","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.1","@balena/jellyfish-logger":"^2.1.12","@balena/jellyfish-metrics":"^0.1.125","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.2.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.115","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.20-renovate-balena-jellyfish-logger-2-x-bde12b165c91c61977fbb6255952f96f10fc1388_1616805163225_0.4638739475097824","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.20-renovate-balena-jellyfish-metrics-1-x-b9daad3f7d3c2ab2f83e3d5e43c6fc75b5729494":{"name":"@balena/jellyfish-core","version":"2.12.20-renovate-balena-jellyfish-metrics-1-x-b9daad3f7d3c2ab2f83e3d5e43c6fc75b5729494","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.20-renovate-balena-jellyfish-metrics-1-x-b9daad3f7d3c2ab2f83e3d5e43c6fc75b5729494","maintainers":[{"name":"belodetek","email":"team@belodetek.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"81300db093de7909e8f36b6bfd9c8a58c904a302","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.20-renovate-balena-jellyfish-metrics-1-x-b9daad3f7d3c2ab2f83e3d5e43c6fc75b5729494.tgz","fileCount":99,"integrity":"sha512-LEM/I9zwWrXBqK/8Dvu/Fvz4tQDI6NsboWd7h9VEnhQSqiEXP9mBVncjw0ivFVDiIJheUKCaKwYtXRInxS9eVA==","signatures":[{"sig":"MEQCIGVNrLG+dBjjv4h4pMHDd9riFoOwf8AbibzOE90UQ52eAiB3ZpQxgPa2BuhY1oYJW1fdblbr7gxHNIPWDXH6eqDwPQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":665557,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgXn1eCRA9TVsSAnZWagAA61kP+wYfP9JEc2LNt5QO8Spu\nkPlzKk4KRydwOOMhXdeojcTBTZDO46JsNWzLgtgMkY81DJfAdIGH2daoVt0h\nVrV5D/L80CYyl/TbDo2CLhi+Wr2vpphi1j0dSwutgiDHVzj29vbrlCqWs5Rk\nXseYz4Bvj1yBcCgtv8GRZVgS3QIEuAYFaepFaX3Uv+33/zZMz6l5Lom4DuTH\nPOcvEue5Z7NpjaIoADS8nm1oeD6uSSRoO/0FR16EBCrDlu6elkCiLRvH+OVQ\nnuU1FzPF1c/K0YVhjcBPK3DbgzrggSafVMA4Vphg471FRYgYYo1yVN6xFXjg\nwW3NlQlc4ENEDKMBAIZQrg7sBVlCx4PQjGCMki4qzlYVr9I7nud4peW+NBjR\nFiSFH7c93IbGm1fFgDgvR/IqiaYLFbBPh+n4t5tq6PlExven6bOtcV2DKziy\nd7QOQsM6M3M9iwHVdZfSnsMHXnvF6K2ZqN6XJefOS61RKK5VegakoLJEJNUx\nnMoOYEnPvcOprB/Ee0NrrVda6X35gSCp8JjdINsKgLY2eQnghOv6gihyytV9\nJiRuojAq4vU+D49qa7KfJOKPRb9LX4586OHfFJM6Y3+HSfOp7yey7BeBnTr4\nq+yXPUK4abzhZEYhELmX81fahD9Xse/RTreXqJTAZ+6Hq72+Z2O0JtJ3FPZz\n8NHM\r\n=8IEW\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b9daad3f7d3c2ab2f83e3d5e43c6fc75b5729494","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.78","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.1","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^1.0.12","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.2.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.115","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.20-renovate-balena-jellyfish-metrics-1-x-b9daad3f7d3c2ab2f83e3d5e43c6fc75b5729494_1616805214510_0.8188007621351012","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.20-renovate-balena-jellyfish-metrics-1-x-d73c1610e8fbc6e173c194e05b787b19effb3c34":{"name":"@balena/jellyfish-core","version":"2.12.20-renovate-balena-jellyfish-metrics-1-x-d73c1610e8fbc6e173c194e05b787b19effb3c34","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.20-renovate-balena-jellyfish-metrics-1-x-d73c1610e8fbc6e173c194e05b787b19effb3c34","maintainers":[{"name":"belodetek","email":"team@belodetek.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ee9c55282c63ca86d2812a9eef61fee64383d031","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.20-renovate-balena-jellyfish-metrics-1-x-d73c1610e8fbc6e173c194e05b787b19effb3c34.tgz","fileCount":99,"integrity":"sha512-2+KGEB6YbRBvDHmwLbKg5rB3sP3v6HJYd912upvYpaXJGPDpXhPRv6Vpu+aLR+j9J+ohVWQjKawkt4ZBYQ3fAA==","signatures":[{"sig":"MEYCIQCxa6rL2k3FAemTGndl75wUY2UEICEb1Ce1QqvRRZVwhAIhANdeb/6mAaaXOVQwXamSZ01yb2ukGMfemH/JlcbEJ9h/","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":665557,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgXqhsCRA9TVsSAnZWagAAKb0P/1eawbGHlZalS9eH54U2\nlLG/N0ETZu1n4p/WQ71SWxcauxp5dU4KLH0e9TEPB388x8TTUBpay+y956eF\nrqX1OZly1gJXJChfra3lcBgs33ltIDCrWvBwdGVrJ0HbsT4w//lgkFstsql2\nQQZ4dS4cTO5vl3G5/hLzHy7hbiskfLbkWwTLzPE6z6VqjrN+Ja2fWNBllJ3z\nVZ6vxsG1fXcqH18n3MDbCIIH3NujftFA0c/AjxaeUgcDsAWS6XrcvDt1Vxqn\nngBjp/rugPi9PfhogwddF7l5b2XXptirdFraJl0Gsq7ast/3JJEaGILvySLM\nt2uk+BwDqojBxHwEKsq4ULVF+XhD0x8QD4oe0dR9C7TKlVW7qg2JwrpAZDxh\nQDLicnq65FycWz6T05BBsWDiLuTo5G7aZN3sBcQyJZkwSJ6A1xoreBLue/re\nm6TprKxPD6XVvA3tXNK8pV+EBftl6tkEac45J/HOFgPwTo295m7wBt43muB4\nl/No/L715TQIArtQuhUXG2bApCEF3Oe84iAcWCg0WXuHRnOBEJHe+ZNcUAJM\ntm5N1zykZfGAYOTwGDhZZXd8gnDyhNAEty2i7m2StGIRGdfR/txPy1rCwlPn\n1zMUsSZrcaFNgAd6vax3FDUjRvT4mLhdpv9LoELlnGEwxBnjsLhBrUumNFVk\nStLF\r\n=nFSy\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d73c1610e8fbc6e173c194e05b787b19effb3c34","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.78","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.1","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^1.0.13","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.2.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.115","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.20-renovate-balena-jellyfish-metrics-1-x-d73c1610e8fbc6e173c194e05b787b19effb3c34_1616816234907_0.5533720126905723","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.20-renovate-balena-jellyfish-metrics-1-x-4c1fd42c58724d8e0921a97dadcde1440b566669":{"name":"@balena/jellyfish-core","version":"2.12.20-renovate-balena-jellyfish-metrics-1-x-4c1fd42c58724d8e0921a97dadcde1440b566669","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.20-renovate-balena-jellyfish-metrics-1-x-4c1fd42c58724d8e0921a97dadcde1440b566669","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"87764ce7423e022f1ad41120b40ba7cda9713403","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.20-renovate-balena-jellyfish-metrics-1-x-4c1fd42c58724d8e0921a97dadcde1440b566669.tgz","fileCount":99,"integrity":"sha512-KvyGkt0Xa9Rm45uBpN+Fgs+ktkRC006KAPCFHRX98S7Ks4dQarfB7Lb5p3zDh7vsy1UkYFQJ+xa61loNuiz9gg==","signatures":[{"sig":"MEUCIGSxuJhZMmB78vzS60zDDsg/NpQEXEhxgyk5DRzLIIonAiEAwwVc3VcuT78cDjZSl/f9DXR6nyeoNNpsLdoXDNj68p8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":665557,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgYdYbCRA9TVsSAnZWagAAljwP/itYSxXBZ6LJN0Jfq6lS\nNaGVSaPyw0Xvzv9RatvV38dy94hzo0ZUpNasW3z+SaERI3wlVlwaTz5oSpAF\n/NDB5i74jp3e/i8rC6hzGfwrcfQdYoyLfzB8V4poUimT0gYXfJrmWtNXfxhv\ncwpyVBZuj3/T3p38bABJH9GDH+5xpswbQICOYtzw+gcWkwnHsUUMf7yYZ1ty\n89SPPJDiMneNKmP+Gy7uMDPgB78gEFTQi/J8amTHpwCfpRQ081S+l0bSRk02\nCEKSjmwHx63vtwcVL+Z2S5hPulaWrAimqXiJIUzUq+9HeUCiq/DJ+q32ej0f\nWEwNia1Fn5XOw2g4nTFlxhjMIzx73NHSm9F20l9lgCrPrcXbbytB9sfiZpHQ\nuzMSzpITGbXt2ia4fIV3kHvMJKYMpATTUM4mFY3aaZROHYrwHDOSoK7U7u3k\ndxZKhNDmHn3n33eT+Bj38uOYKBEZ/D4ZTEAfvudKXv6bQ5m7koPuG7e2IzDA\nxtAj/doRSFrMVjSILSObwydHrYexn38TrHrXzx0mjIhwbk51CxigSqyJhgSU\ntPhW7wHnP9qXF9lAnpvJ7O4tjQpWTmU2+zIzOktenxmWD6lVotDQqx3fwopS\nvy1u6RW5ZeN9515GLhlu5mkRY3M6iZ6FvTUE56+YgkNLxHW+q0NnNWrRvHTN\nhK9x\r\n=ng7S\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4c1fd42c58724d8e0921a97dadcde1440b566669","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.78","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.1","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^1.0.14","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.2.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.115","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.20-renovate-balena-jellyfish-metrics-1-x-4c1fd42c58724d8e0921a97dadcde1440b566669_1617024538992_0.7316167132776794","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.20-renovate-non-major-52486384bec7f3d45ce02c0cfe84689989cbc94b":{"name":"@balena/jellyfish-core","version":"2.12.20-renovate-non-major-52486384bec7f3d45ce02c0cfe84689989cbc94b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.20-renovate-non-major-52486384bec7f3d45ce02c0cfe84689989cbc94b","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a7556247e6157d443ec205e51356c99409f4a4a7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.20-renovate-non-major-52486384bec7f3d45ce02c0cfe84689989cbc94b.tgz","fileCount":99,"integrity":"sha512-OBceh0i/5LvOSwmoGNSu5pcUL3oNLF0iVUD1jxrTK+QrzehyEyNPYZ3jRcqhSIOkceYkNSpnV+B2tqXUeLVYuQ==","signatures":[{"sig":"MEUCIDCmjJ0mjIAEs91Q7XxTBFb1eHl+c1vuKQA6tmDMBj5KAiEAzEitIrXizRAsB9oX8y9863Y5T6v56HUe7JU+MFpkyN8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":665545,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgYjSzCRA9TVsSAnZWagAANE8P/0eHp79/EKr3fy9RZ051\ncUL+l8fxT7aaL/qlOP7+f90qbZSMx8wp0qPiyU0o2YMUWNDxph86X7GfLz99\nLUvKHnLMmoaVgikehlg5yDq8dZ4JIcXSpsT4sMzEbUtUGZ0OOreYiGTrobLx\nHWT940GvEg1mx8v7khYe+dlOI/KnbiRkHnK+GJXHa2oAgUoyMpLr6WX15Jnb\nAKZI9gCPrrCCXF07HyQj0HeDv+HlZXHu1au3stMs4wVce97TRmuAsUouDOOH\nSx7QO9whTYuef1YTrtXQYBadJrfeFGNij5+zoMjudYQTTVlQWpULCSAtavcr\nToAONZSR0+bJ0fLZeZwFVNhcUcYelpymApcT9rmAVCYgpLICYY20LrP30jec\np9Mq6nSV+TPoH5UMxy1YvWjMgT16B4+k1j7NEGXyeDM7+myt3P4mdWjUWDpW\nP/CH1cEStXiknFITMBJDZDoOwt86P8MV3zGfeLkA3fb2IeS+uMfxlN1ifzbN\nQqRkfNGG1zV9OxebiYoBwoB9TJVI72BYY4nmzV+yulvW+WtebKxZ3z2WTe19\nOJx37BxzZ42HE06M/OzxyHJ8L1FtRuPF0cgss3oxWMEsqcLYViKWSF9Q6PN2\nONPrgXASOih912C6KdaIcLF/xfr4vHNRwGITLiF5LxRFDDcZziEUZiu3Oa9a\nXufe\r\n=B4rN\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"52486384bec7f3d45ce02c0cfe84689989cbc94b","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.78","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.1","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^0.1.125","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.2.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.116","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.20-renovate-non-major-52486384bec7f3d45ce02c0cfe84689989cbc94b_1617048754921_0.8161708104296499","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.20-renovate-balena-jellyfish-metrics-1-x-ca71dba77447cd2b222f09c42677e7f1a2b8413d":{"name":"@balena/jellyfish-core","version":"2.12.20-renovate-balena-jellyfish-metrics-1-x-ca71dba77447cd2b222f09c42677e7f1a2b8413d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.20-renovate-balena-jellyfish-metrics-1-x-ca71dba77447cd2b222f09c42677e7f1a2b8413d","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"30be26105acb95ecb2f639108cbed4c9710499a4","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.20-renovate-balena-jellyfish-metrics-1-x-ca71dba77447cd2b222f09c42677e7f1a2b8413d.tgz","fileCount":99,"integrity":"sha512-ziP6Dk3Lj2P6DH6iWPtyKvXBQ27V8XgamrSb2TM57gDcp0KBxiRi1vSv520dnqjkQBPHE+uz1WIkJm8z9q28Ew==","signatures":[{"sig":"MEYCIQDUx+ztG6Ig7ee1SUnVDu2soYd85V6XxZDAjbswfE0bOwIhANFVrcPvfs2Vmi5ijOQDjf2D934MzQJdtStwFb7rSodb","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":665557,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgYphpCRA9TVsSAnZWagAAHUAP/Awjzlt80rHiNtQWWodT\naWB+I80vtjAtzmwh5XHLk0JYiBdYtmknOIZUeGy1bu5Aa6UvZeh4kZhZOO0h\n/8qIP+CG2A01WIUyolyG4jjXhQ/U6DBY3zLNEskc28ojRQyo1VKwKNSwT5ZJ\nzz1ZW8mJJSei0ojpIfLYQ8bdIBGNIS58uQRl5UGpyaCqS9QMkoyyj7IBPm6M\nlywqM7RQEDGGEAPfRAQr/p3T39kcEsxhSPWBBHvWqenIU0HzsJ/5275eLV71\n97zVBEBvO+GiXth8Xm3vI8fCtDlAJssPqSXsJG/t0gDSMtEZY9tO5snQv13w\nEXEK/o4KCK9zW4jsAiYn2bfsmmNWuCFy7QzVVUcWvtbjZtE40UjEf1jKMQqm\nIbR4CFekcEWdjUSd9DKyqGZz95BvVGbpX8dW642B05Md5KHgS03frhkc0EJX\nXZHs1dnMkG5+BCGdgssVuLoL5x9Zp2o49WB/aELYC54PfHbaazMMZHxhDAQ5\ncggLT9wZeqi2EBz/g/csF2W1EXKzZewu/wJ0UNkFfd8lFQByvRTGTnk7RWeA\nszqhZUWsxN94vfxU70dltv/fG+sZ2Bk66N7SoHKRc/6GS22a/RxD4N+7nEn9\nWcdpgnXujx/8LNmpcBI3WkGk3i5dSOtTtiC6r5bxg/GgfM/IiUy/VfGitYU9\nLViQ\r\n=2fP4\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ca71dba77447cd2b222f09c42677e7f1a2b8413d","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.78","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.1","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^1.0.15","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.2.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.115","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.20-renovate-balena-jellyfish-metrics-1-x-ca71dba77447cd2b222f09c42677e7f1a2b8413d_1617074281054_0.741020201408205","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.20-renovate-balena-jellyfish-environment-4-x-c5ba852198d3c3bcc3502b31d77896fd6ef17b69":{"name":"@balena/jellyfish-core","version":"2.12.20-renovate-balena-jellyfish-environment-4-x-c5ba852198d3c3bcc3502b31d77896fd6ef17b69","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.20-renovate-balena-jellyfish-environment-4-x-c5ba852198d3c3bcc3502b31d77896fd6ef17b69","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4dc411965b3d2d3a5bd897f8ce960d769410dac1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.20-renovate-balena-jellyfish-environment-4-x-c5ba852198d3c3bcc3502b31d77896fd6ef17b69.tgz","fileCount":99,"integrity":"sha512-kibL5Snfs4migADQdkfOZueeY3U0CajBx4+H4Q9kgYOnv+UrlMuDMTsZvdqGCf0pXKHiX+njAa0jeHKGHtHSdA==","signatures":[{"sig":"MEUCIEr84o4tFCPqtQCLFmvAGzSnlrptarH67l8neGm975ZOAiEAzHYWp5xC0aXFoUKvQrerQwEbLEKvmTxtfkYnLb0A+fs=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":665570,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgYu0ACRA9TVsSAnZWagAANPoP/j7Yd1Mz55PKZWcRkKks\nXj3aFP3z6hhgA9BOMRZPPSn3yXFU8rEX3gc1XqEgsvRp5WgQUSOCNoRqOD/G\np1ND7pjC7dRk2ju2HdhRjYzriZApDIyBnwZLFUYeHNS7ia6mVJXFp41VLiLB\nrHsP16l2Q/l7Y6SFJK4myIGxVw0oFXaSILiUVhXy2o3osnn28RKT4xwjkVWW\nJOsILIfaC1D58N6Hmh/6uHMi56SFSCYhYYuU7IqcIEsLD6ylXXh0LaFeVseL\ncTgY+ulPNwSV6Gx5W76xs+Y415NHEum0AYslt9IDNWcwY9eRaI/7In0bCPNq\nU+lK2nhZ0Fg8gNxH/EuyaRbfYNn5QVz6aAl19dWpSIQfIQ/YD5d2kDsLmXH3\nio9bWZUv4D1l90Ob6YtFMYFrUDLdlfCmhtEx+2riOQsqM1JeWeak2f0Eoegj\nC2VEuiQeSS21wCl+AmAm+PhJa7Z6ORltw9GO+uOMiboPctGCAGm1dbF1bhO9\nSvxFFWnwKYCuIfYdY85BTVZV751gppuGROQ7VlmVsDb3U/OfClMkkicGirBB\nVZmRoVLADd0eNQ66fdEIk0QacWAizH7rqHWFncFqPvAvjOEJFcy0DShtp9H3\nqYYicTzdQxxBAfyKCUwRtJf/JQOcmV6tYJN0L+NXgnArvTd4AM64FCNBgscB\nSY09\r\n=6MOj\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c5ba852198d3c3bcc3502b31d77896fd6ef17b69","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.78","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.1","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^0.1.125","@balena/jellyfish-environment":"^4.0.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.2.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.115","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.20-renovate-balena-jellyfish-environment-4-x-c5ba852198d3c3bcc3502b31d77896fd6ef17b69_1617095936085_0.9917189500340022","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.20-renovate-balena-jellyfish-metrics-1-x-c88d3e749c021d8cee8f04d28949be4233d58d57":{"name":"@balena/jellyfish-core","version":"2.12.20-renovate-balena-jellyfish-metrics-1-x-c88d3e749c021d8cee8f04d28949be4233d58d57","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.20-renovate-balena-jellyfish-metrics-1-x-c88d3e749c021d8cee8f04d28949be4233d58d57","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"594bc270eb0ed5216647ab7ded045f9fc2441412","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.20-renovate-balena-jellyfish-metrics-1-x-c88d3e749c021d8cee8f04d28949be4233d58d57.tgz","fileCount":99,"integrity":"sha512-O/Viiq8lywYGyvjP55gyPESSy1BQSPLksCko3JqRYWPFOt+7gwxedXJDPXHF3mIHVQr7Sj9XZCRY60FQwpliEw==","signatures":[{"sig":"MEUCIAHhJBf95cLJWsWaRq0twaAsdIIneycstmNVoBZ8d0tkAiEA8D4g+Rf7xJlku503xl/g+t54kQp9i0KMSINBHdzfLFA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":665557,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgYu1hCRA9TVsSAnZWagAATgoQAJAsv8XqvlyYW86voLrX\nviti2ObrkAEyp//jNhkiN1AYS1x1cH8qPWxPBvyRhD3A14jUFORewLHR2RvZ\nvZ/y+dSVRO20qdhxycEGBaeCqyxKU21+IAIYUYrOMlaExNZ64j3HqwhL5uHJ\n8UJB9HKIwtWK0EDf+VT5STvV/FnJHhWjaKS0wErhZ9Pc9VnPoHZyNtEPfFve\ncKSz9RJuc/8Z9lO7OlFXIQUgcoIHaLaAddpLaOPmXWp2B2klVr0Zi8yNnIJA\nQknNoBKUIW44O53x9/3QuGcrPeq92gE0+Aa0Hzxyeoc1wWH/e8jMJfBvstvQ\nqV8UmSa8nppq/+bJ/JHXZ5R6mdoMd24kkS6W07ZV6/lq44OyOwSILkpzNdht\n89H0931UFfZJCSPg0VJ75vFF3B2APWA6kJVgvpxVqCfgMk4+00EV5zdBkCac\ncWJg919jLzlMiY4+DuMfCXbzpaRa+4vYeN1uU/dwKRyXPzX2fCBCtsR2ucqB\noMxcNTgg5sZWZSQkum+ME1z3hPVvooUMlRZkjrpIZdRhMpcl3tGj8Ebbc6Iy\nYmxrRIScnihYvgFEeE+z2NkhfrFZFcxSY8zWuQRtMCUjuKqldAeVCg8zcvX5\ndQTOH9sJ2fe/HfFtVsjXl9C15IGXcBdAWHa58OHJ1sfgn0L5uROGL2f8t0VS\nXubf\r\n=kBlO\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c88d3e749c021d8cee8f04d28949be4233d58d57","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.78","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.1","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^1.0.16","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.2.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.115","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.20-renovate-balena-jellyfish-metrics-1-x-c88d3e749c021d8cee8f04d28949be4233d58d57_1617096032574_0.13450734407168796","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.20-renovate-balena-jellyfish-metrics-1-x-9811de98070e59e53573aa6f302afcc342a8faab":{"name":"@balena/jellyfish-core","version":"2.12.20-renovate-balena-jellyfish-metrics-1-x-9811de98070e59e53573aa6f302afcc342a8faab","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.20-renovate-balena-jellyfish-metrics-1-x-9811de98070e59e53573aa6f302afcc342a8faab","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"59edfffbdaa7afc472aeb14085e4ea9a3e80d49a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.20-renovate-balena-jellyfish-metrics-1-x-9811de98070e59e53573aa6f302afcc342a8faab.tgz","fileCount":99,"integrity":"sha512-2SX3olYKfm6whBIOMVKBMxGZVwa9WkVsmxZJJBIfy6/KxHq4ZONamRYAwm4J0AcBDkT2tRC6MiT1eWN78IoGag==","signatures":[{"sig":"MEUCIBZY3boJK1bOQdX8e74SUukco6OBy8gKbO7+D6GrpKhgAiEA7AxaA462/4PRxRIt/jW+Bdal+KePjTDesgGcaMseNh0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":665557,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgYu4rCRA9TVsSAnZWagAA5HQP/0Z3SN/EA5ApXDgG4EeC\nBW9G5wpTXamHo8MH4knO821myuuPgR6o1CJX3tEmwbR7eQ+6Qz3BoKJwJdm+\ncHB9xzvAwR3Zj0QCSiOYmQmgY4hBV6nN3iqTeCf4S0iQLhJ6BwzARwnA48A/\nHv948kCohPzWgR4R9qFpVMG2h5ZsROSJYk7NxxzxdSuKjfMLPkHVh1JS6ICy\n6SKD1lldEDDfUt0wsLMdBagB/blbQ2jT23gf6745jbjvUcAQjfNLGRYjOCef\nUdBib+Ix3mxJ9D7+HqqOsQo7RmQsJ6i5QxCOSkYCqrOFR2xgW1YgpXhRDl4R\nfRp+brhk/5RXb8Jy9Lxe32RjghdEPWLBeSnLW3rlwpZ23WDcHmKmob28zgvI\naaNJtMH2V/b5KXnRq+ZfL2XG96q7/INvaLnxLDHLNWrPedVnsuMrLA2yRMAK\nM4o+V3evBiRaLWF45YXFHO91+BNF+lx+/tT88y77x7FXMehkq6nTAskV5Ess\nd7XULx2WLxX6boAC4fS7C4Hu2aX+SPBO2G+fAWwm2Z+bYq6zT5ITTnLcZyrc\nTeAl9iwHEhOcfHLqqtjrcNXwrzR/fbPFvZgFFryjIFIEB72pxQfRmLpW8vuW\nh/5AL9S0LQ5wZN2i2NeCp6necLif8Kq52lxCJ/RgisuaJnUfJfCfkWiUXj7a\nuCzr\r\n=ukzf\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"9811de98070e59e53573aa6f302afcc342a8faab","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.78","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.1","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^1.0.17","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.2.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.115","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.20-renovate-balena-jellyfish-metrics-1-x-9811de98070e59e53573aa6f302afcc342a8faab_1617096235159_0.9291961117758674","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.20-renovate-balena-jellyfish-logger-2-x-b709274f94fd21d86da677680cd0eaf4c6057d96":{"name":"@balena/jellyfish-core","version":"2.12.20-renovate-balena-jellyfish-logger-2-x-b709274f94fd21d86da677680cd0eaf4c6057d96","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.20-renovate-balena-jellyfish-logger-2-x-b709274f94fd21d86da677680cd0eaf4c6057d96","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"91c4ffdbd27383f623a0e693f3bf29362f635fcc","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.20-renovate-balena-jellyfish-logger-2-x-b709274f94fd21d86da677680cd0eaf4c6057d96.tgz","fileCount":99,"integrity":"sha512-QE2R7E0q/nbxqOnII/Nws1qFy+AaMN35vvS2Ug4a57XaGNIB9Qg/kBdJoHHJAeI6WdsW0x4+1U/KZpjHetlTfw==","signatures":[{"sig":"MEUCIQDkP89IICFPgeUj2qcXinQjfq1jfga8CDk9gkgS9GrbmQIgE72My7eQQSaOpBpnBdJmwdGe+gQDZ0sd5HeY6Rm4KQE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":665555,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgYxRXCRA9TVsSAnZWagAA9C4P/R2wQKPRhgD/w8EOvd7X\nKTNUi157iUqosYGvFiSuMcn/nyPr135Tcmmkf8st+s0SMusRSlKPsL/A98eH\nD8CeLy0XBqvb8mdjJVwDrcALEruaQLx8THXGAdyTdPEsr1AUWNaC01TkKdOk\nHFa9Iyu83INumJbBveU3gojjRPnTFMk6/UcHs6IPjrFAmp8rZP746QWkeudJ\nzGeEMXLBnQcqEcc9g425AbZUXFRO04bZ4DihW7XuSEQoL06xO38PwLCUZGV0\notC9IiLi5K17dbB4Q8JvZAY8a5MtClZn2z/2cGizoxDzmjZKWtz1mf0pGkDq\n+0mCEa7vaOdQe+7jKq4icZ8yCwk10ujp5569A0Sp4ARiIa7j+CSDot7KQ2vO\nOH5xbYGaezm2Hwh4Q7Xohz0zj+s0XQoqxSadX4p3Wpu9bTTLII2+4LCLJ1ER\nB+AO2+yQOtjqEHKJs30oEzFKA4/AJUbdDFKtCrWE+RCXLtFQe5W5UKqAke5X\nweFpIoAC1xaR8bcVn/SoTRIMqXFIkqwOkKkwwkxDxnCRDTGDtwJiRwJhdxtb\nLLTQRmpQI4U5yq6u9+H/a6l+QFEFXXIjE3HJvY8aZgTNO/rZSmyW1LXYhbu+\nCUARmIPYxO8g/ojrFhZNkobjwQH8b35qAQ/9Bz9MobIImLtKFQeTyWT3Ge19\nhpT3\r\n=Zvvc\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b709274f94fd21d86da677680cd0eaf4c6057d96","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.78","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.1","@balena/jellyfish-logger":"^2.1.13","@balena/jellyfish-metrics":"^0.1.125","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.2.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.115","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.20-renovate-balena-jellyfish-logger-2-x-b709274f94fd21d86da677680cd0eaf4c6057d96_1617106006783_0.9528832288482942","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.20-renovate-balena-jellyfish-metrics-1-x-e3dad7aee3d524cf29d30eef52357e51c273de1b":{"name":"@balena/jellyfish-core","version":"2.12.20-renovate-balena-jellyfish-metrics-1-x-e3dad7aee3d524cf29d30eef52357e51c273de1b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.20-renovate-balena-jellyfish-metrics-1-x-e3dad7aee3d524cf29d30eef52357e51c273de1b","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3b572034b8c4ca63876858d16439a74b3d9716ca","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.20-renovate-balena-jellyfish-metrics-1-x-e3dad7aee3d524cf29d30eef52357e51c273de1b.tgz","fileCount":99,"integrity":"sha512-2Sl+Yx1qSxeIWSRiEKPebc4T15GeHvpa2CkL+YxJ2S1KIdIMR/st4ylcAgjLeurPNr7DYJ1PzFjmp3B7zdu9ow==","signatures":[{"sig":"MEQCICpO6fr7iV5YrprI/UQ5XIlprKpEE4f3Fq0s59/7NGiVAiBxtDge5prZBNbXIlenVn1ZMhJtDyyt18/+qQ3H+QjhAw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":665557,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgYxS+CRA9TVsSAnZWagAAw9cP/jJX+9I6DqKnd9WxoNYo\nR6u/gQlfIxfGlbhB9AvgxDUieB+NswrgXLmdbWNibjVE6sBND/rE2RBWNcF7\nDIAe9DcIM23pWh1SDzvjZibBDYYtz+RXqhWBipGDPPKEQQ0Se3HXfMLRDcDH\nrcfylav2Ef+o+QyxG++pdSB3FTV3kn9ykcPwW2FJbkP6UkikFoeKBNSw/UaK\nWBxCHIM7s4AF8DEDWVJ5odFcpmoxqyTt3/cuogbNLaBNRDiPIdmsfd4g2YPl\nc/QNmdZcTLjfgJXsip5bXTGie7m2cj6lN3OFiJPdxBlvByRqHu9xoTZL2qgB\nsir/ZGqzEXdLn4uQk+atkZy+aDcFMnp3oi/Ogo/UcIrnP7luyqL16Tn/a1dy\nV+JvK7cmib3zFwM8Qxh/mzK6Ouv+CPkF9eXkzkR1O2NHMTEcjWKxRsnK6O6c\n94ny71No9LZmTKQUpKJ/MQ3ov0KKBKF9DYqfoOBo8Hxn1MEtMCz+gczg8tHL\ntqoaVQc9s1wAcT+hW56yJoqAqKEG6L4K6ksdBHrHORDkRoS4KgHdM3ml7j/b\nDzRSmOsDZ49jQ+c/ro95t79yvC/wb54s3b0mJFc/shwhRiNdXIcG79UirJIZ\nWj0OhbjsS71pWxCDV+JK8kL+7LZj0y8aasHTTUzlAa8wCtlDskmmPx00/4Eg\noFeh\r\n=Vgad\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e3dad7aee3d524cf29d30eef52357e51c273de1b","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.78","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.1","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^1.0.18","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.2.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.115","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.20-renovate-balena-jellyfish-metrics-1-x-e3dad7aee3d524cf29d30eef52357e51c273de1b_1617106110308_0.520781632022892","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.20-renovate-balena-jellyfish-logger-2-x-4e2c23d4dd6f656c0cfc9aa1bf3b0cf3875bd3c3":{"name":"@balena/jellyfish-core","version":"2.12.20-renovate-balena-jellyfish-logger-2-x-4e2c23d4dd6f656c0cfc9aa1bf3b0cf3875bd3c3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.20-renovate-balena-jellyfish-logger-2-x-4e2c23d4dd6f656c0cfc9aa1bf3b0cf3875bd3c3","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f777c79bbd2fdf4dd071910c5b103b16a54c7c93","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.20-renovate-balena-jellyfish-logger-2-x-4e2c23d4dd6f656c0cfc9aa1bf3b0cf3875bd3c3.tgz","fileCount":99,"integrity":"sha512-TkpZhP51k8t0rkoQXr+YLpWg1smuqgM1FPRQy9rj+22eRxpkiAjQMHlJaiCGpbR6BXtL9NNtkqASFiO5abCfJA==","signatures":[{"sig":"MEYCIQCacgd3ZB3UjEY0kctH1Jd0jLAere/o0kLX58Ws+y8l9AIhAJXp1W3CzCIUiYrOEK+MVAdtX0trFE70eDLpzT6IeVTp","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":665555,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgYy70CRA9TVsSAnZWagAANPAP/1heJi6NUXTRvwGbRSfe\nHKMvX12u+KzR1d405YjTLOieo2m+75CJSwIx4yijI/DzmvPAChoheX+GupGc\n9f9orERbEBaFws3ECZrBfZBeX+dGadXZ1625m34cn9qmp4lDnfWQFPlFHZRw\n24VDRlt8kmESB8DRQy86Fgp0aTpTA+WAlk3OOrBXDq84zrK/XEyjUd2v0/RG\naF4J7t48CGGaAWUU79C8nOIObaAX6WiphjHV5hZxHdhNXJk8zzFKJw34sns9\nxu+qlEORM9nn8WDCMb4YV6kDIg8TLUrWeSxLa8FFfQsZRNY0GwHFirVLANxF\n1R6ueWo44e0hAvYFVmG+W3KS95ppNBPmzyC2cS3+coT1FBHPZ56cWroM2mE3\nmRu8pBiKGuaeYLwooWAnG3jGjoJnB7M23k+1iR7z/CovLWQxZ85o9RWjz778\n8yQ6DRzwy9i7jQIIBfrtgBnxQKzA12DtARLNC+TWSjDp6kZu8fWiZC5d58+e\npESE89PyUIF01SKBeiirVP+4pMQWrsX4RR9uZEyQJvB6BKk3I/Qbdjcy5rVZ\n86MFprf1t0ET30bUCXnpGKBV+To3QMmwf02aCR7lRVCG+iaNt+sPoPUzlDN9\nTm+AvkHIIPpRdUCdfT1Q28KE9zOM4k/zPFpV0XkWkmmTDsDwtXX+8KHiJs0w\ns9IO\r\n=XhDe\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4e2c23d4dd6f656c0cfc9aa1bf3b0cf3875bd3c3","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.78","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.1","@balena/jellyfish-logger":"^2.1.14","@balena/jellyfish-metrics":"^0.1.125","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.2.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.115","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.20-renovate-balena-jellyfish-logger-2-x-4e2c23d4dd6f656c0cfc9aa1bf3b0cf3875bd3c3_1617112820269_0.7053200831064115","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.20-renovate-balena-jellyfish-metrics-1-x-ccdc7f3cf92858494b46799515ea8b5678e9234a":{"name":"@balena/jellyfish-core","version":"2.12.20-renovate-balena-jellyfish-metrics-1-x-ccdc7f3cf92858494b46799515ea8b5678e9234a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.20-renovate-balena-jellyfish-metrics-1-x-ccdc7f3cf92858494b46799515ea8b5678e9234a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3b703bafc0f4bb2854a27fc4dfa787aced63942a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.20-renovate-balena-jellyfish-metrics-1-x-ccdc7f3cf92858494b46799515ea8b5678e9234a.tgz","fileCount":99,"integrity":"sha512-/gRRKMzfSNb0d0jFzBQPchaDmogX4VhuUbomYop+OOYWzoUBAbkxvEDL/SXkyYw5Qep77GXVSmXwprHPOe0iVA==","signatures":[{"sig":"MEYCIQCL5T2CMoI8UzltxjaX1Pl/Ru42lnL8CFLgpmh6pp3oSgIhAJUA8jNjREvIR/L/eGzb5i7onMPD/912rdL3TFXWtkfd","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":665557,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgYzArCRA9TVsSAnZWagAAtaEP/RnhuzxfHJlfvRRizMaa\nTOiFUIdkoGywBhcjfn23EL++J/XZhnRuepXFJo1OM2zvUoNcbrrDs6B8CkFb\nUyuZTNP1rn2Y6okOQhjW9bOJbbXJ0PcZ5LcpBQoANh7sAVvpPS3n2marhDGZ\nOu8cXmK+1Bh9aViXk/JGXc8LPzvNW08yfW/DE79c9nAihB719/HhK0mSZW4+\nQ5ww1iZZH3xTZPlMRcQUf7hWC203MIkXbrpyNWj3vgdiCY+V8wK9xxSAGPb2\nACJqjaOZkivgu0phL1FJmEJYOlEGiPorxLPq+P4OH6YJRjfc59RseBwFVnbI\n5JfJnUYORTFtS8Eg46o+1VBpYGhEthK0lvtEdn6Y9wTMkPYTkUO0DzenIaEd\nh1PDrPWDhUoQUW+hXLGx7KmDFUWAXbP6649EkF8YnKlvDIxDLYYlsFZ9zd+2\ns8FPuqWrQ2VEo17pDiexY69I+sOUQPsyyQaTSShUC9JQ6v4WxQtDWRQv3Ba/\nlylA18Hv12iAvA/p5cQPId0dST0uGMiuKrBc8iOMuSML7UoGWvTu9cVY46FY\nIBKnKoAqtnf9JTYAVOynCJ3drfgoNcbS/+yspbxLrcNbjzyU5b93P1EPVFuv\nZHoPUwgFVgAUXv6DRFnycUXAM2gQjlhMHKDyCahuCvdm6Tb/6kPQv6qpEidr\nQ2+P\r\n=97xZ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ccdc7f3cf92858494b46799515ea8b5678e9234a","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.78","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.1","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^1.0.19","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.2.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.115","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.20-renovate-balena-jellyfish-metrics-1-x-ccdc7f3cf92858494b46799515ea8b5678e9234a_1617113131122_0.8170416150577675","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.20-renovate-non-major-3d4b3e264bb81a2d32401e2b54faad48eacdefc9":{"name":"@balena/jellyfish-core","version":"2.12.20-renovate-non-major-3d4b3e264bb81a2d32401e2b54faad48eacdefc9","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.20-renovate-non-major-3d4b3e264bb81a2d32401e2b54faad48eacdefc9","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a2ea6edfbfbff49af85ad3340bc413282e5c1354","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.20-renovate-non-major-3d4b3e264bb81a2d32401e2b54faad48eacdefc9.tgz","fileCount":99,"integrity":"sha512-aW+h6tFCboaKDnpH4Piff+4/u7FgPlBFmhTNQcyjjpp7OjH1Lc4uB17UITHq9QBANa4XbOeDHPJSRPRUWRBMFg==","signatures":[{"sig":"MEYCIQCSFE9OQKTUG8lPd9y2cd6PsCL6oqbgfSQVYY6B/ar47wIhAMx4o/oEcpQTIUw6DhOhI1r72pdtVKlwcxsKUmHzk6yc","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":665473,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgY8ueCRA9TVsSAnZWagAAHc4P/3rnodDZM1ctvqjM8Ig6\njIzWAw3VIfy0gOpCzIR/M+l7IUPORpIb/xQF6nRbxLmGx6ud3yqXqbdqwAeu\nWm3BVk6fczoXtdEuGYCwlA/gpFehEudw2to6UUGoWgIztatDyqZWy+vIuiVT\nagzC7J0nTrEQ8oplU4r8mylp1C3zsRSmmCQNHSsWUFCasTgccYMM17kbmjdf\nnP6rq3z8I6IIWtijcSJofB9Y3HaCokYbpwXkLO89ITlITGJ72AOAWYofaOvF\ns5LpkvtT9ue+rVcvt7uIwtLNuMzCKF+kOSDWb/Bv0d6VONJi/I9B6uzlGE7p\ntUTB0al32HIA2tCCXI1qx8uvTCIbXdVMJDqTdc7NQwrG1+r16tEtuFw97BB6\nDfA59Avp803Ys4++uoOguEizYGxnR8HopRQRnzxrk60IXSjc0Gn6nzLqKW6x\nJAM+e/EO39VvGZY4TeyCeB+vV/UWoZMD3UTfU/y6hWBJunFcn26Se5KjfZbn\nYuLBmx+2GEATCKWDbYByG0M7eIP1R266c5YKPFBlMX7pymI6Xq0VgQVxnMCR\nxH7npkp2iNGwBIQgeWaS5PgRBBcCgfMt6mTFszuRDCu54q70IU0tld/qDiXT\nTMLA9U5ChvdzWsgCMFElj6sCulhaicpU3coV3sAyDaJgIg7APdqcOpF1XdnZ\nPwDJ\r\n=zTTE\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"3d4b3e264bb81a2d32401e2b54faad48eacdefc9","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.79","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.2","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^0.1.125","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.2.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.116","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.20-renovate-non-major-3d4b3e264bb81a2d32401e2b54faad48eacdefc9_1617152925680_0.3785856551007165","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.20-renovate-balena-jellyfish-logger-2-x-9b814ffa3a6d43c536ea89cd1ddb51663a7fcbe8":{"name":"@balena/jellyfish-core","version":"2.12.20-renovate-balena-jellyfish-logger-2-x-9b814ffa3a6d43c536ea89cd1ddb51663a7fcbe8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.20-renovate-balena-jellyfish-logger-2-x-9b814ffa3a6d43c536ea89cd1ddb51663a7fcbe8","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5bff840c993c20eb279565a20ce79c14993adaf7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.20-renovate-balena-jellyfish-logger-2-x-9b814ffa3a6d43c536ea89cd1ddb51663a7fcbe8.tgz","fileCount":99,"integrity":"sha512-TBzO8esNZ9qnJOp2dbTd0nv9Eq0+/c985aIw2t0oyyi9b4mNIjPuAAJSPJ3eniJVr1mZK+gQcLummctR5/Y2bg==","signatures":[{"sig":"MEUCICWTSP3gEjznv3Vuhe2zhlwzKnKH8WFqEP1CZMqburtIAiEA9vdLb5wD09bQmbJ17jUbPbB77ril0fRv5cRnh62ApCY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":665555,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgY8vrCRA9TVsSAnZWagAA0+IP/0Nfz5SNZQkCicISmkpd\nisyxMjiLFDiem7mV2YYuU6CTNeRlr/o6PfwsoipxMfkMt2MblKZARmix+fJJ\nwT76HC5jQoM0+ip6rg89Pz/N3O/jE18pX/yc1J0AlKX3cRGShihRY1DiD7ap\nYLoFODP85SjnVVxPa0tB13N21PSdTmpiGuiDbP2XX1FNOGSkd9RCaxuPyvXz\nzojVXpiPGhPE2FK0l7fO/uS2voRvDAmQn0TKT/HZ1+1+aOSFnOuP+k4uKlHb\nHJbgzbQjJ0d0lghnlT7L9QY6bENOAhIpxiqeUrSpKd8xLMnxJC9SksdBtsee\nc/SXBcG4i5e7Ua9pl/5oox8/67wYz1tW15LLSSu4G0Ttih0qdLE4HuVDCF4m\nHlJ+oaZMWEosfV0UTGwKGGKlrGThbxmIkd6mVOtGykyY5jCPthZAftdL8m99\n45TPdqOMpmoQtW2TbKMSUoIF0RKfz1P+kKAEU+U+DueiobDCKaJXOOIcss56\nqXXLbndeqpciBakwIkmWSAyi4Slf5ms/8rTYWuH0rIztFXOgli650e335SGq\n5sbhyd6tiW5cGTLKKrOXV0NMGiN8XX2kqR/WsstXta7nLYGYevyOjLtVzQ9T\nFgMX503L0sM4SIRwmnajklfhaUDG3SJ0DMJXEZ0D/STg8zrY3r3er8tC3MvR\nVb+2\r\n=x6XY\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"9b814ffa3a6d43c536ea89cd1ddb51663a7fcbe8","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.78","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.1","@balena/jellyfish-logger":"^2.1.15","@balena/jellyfish-metrics":"^0.1.125","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.2.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.115","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.20-renovate-balena-jellyfish-logger-2-x-9b814ffa3a6d43c536ea89cd1ddb51663a7fcbe8_1617153002788_0.06464235114887273","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.20":{"name":"@balena/jellyfish-core","version":"2.12.20","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.20","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f6d77444029904e6e2396ea9b1cf3c29bd6d894c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.20.tgz","fileCount":99,"integrity":"sha512-DKcIohid0glZBbtKLIk/XKPAazGFvYtR8IOtSWa4G4tBJ59Yw9kJoiL4V8ypFACd1eF8JwPyC9+HioUZg4csxw==","signatures":[{"sig":"MEQCIDCykg3sV9yihWT7RKvbfMlkcjSoNI1EwxeVIc0s6FSCAiAhinFj1iDqMzx7l4b70zcM2L37mT51Tbe3PlRpWXZ+PQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":665413,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgY8zPCRA9TVsSAnZWagAA10oQAI8pL0PI9Z0uB9I5tbP6\n1q2Q96mTlc3s2QGt4keca3GLPDv45ZWSivHxWCTGRfQTyLBuJpNzF9ZadNYA\nwTUygfDzQjwcwDuwM9wjsfSpfjVQNwKAX/kL6Wd79nP63tJSCf8CPpNVBSbY\nAUrYigFAj/aPg+dc1rtRHpdk5beiqorkzRt8iFVA1+bZNbdyPeMczQWE5hY/\nqWF/9tQrNB0nXsWSWxuK0Jif/NlQA9/9khfp+AlNANFeLlB7coYojG2A8nG7\nIahoG9K3WAIK1Gk6eGLqcMsZNzX2G0+zs5VZNDGAE7qNt+injNzIq2Lqs6Gl\nqynC2shrXZJQyctFqWseD9ziwb5fiU2Xxv9RAYFrTV7ULR7j68+KvamGLOX/\ntO6vvsupWd43SFt88q7P+4xMzRtjSQh+amov49BIs5jL8VgBWkjdlcbzAZgd\n4t9MQ4IpdvKsqnk6xjWtVHOrjXu/TrtjY0YmEVYuYBJn7xZ6bRbW31WuJL3/\ngd+k/90AkrIf1D2F2CZLIdNMtAqd/FsOYfBMLOPVUbJS+TZVeOMOVZK6t+Xa\n1rfnTve8pfBYqqK01mBYXXFsEbqbo82PY0qK03PhU72OTR2tdysnZmjcpBNu\nVjKDQTUfEyf8RIqp3RlNvlmuP4MrqSCaS9+xh8BFQcTSs5DZ6222GewUefGj\n2FC9\r\n=6nYQ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0fbc6868eeac6f9690a37072d07ef992cabc10a3","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.79","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.2","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^0.1.125","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.2.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.116","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.20_1617153230871_0.23811216757778952","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.21-renovate-balena-jellyfish-environment-4-x-520cdac8484282ff5ad477b8a3b8607d40e3da1a":{"name":"@balena/jellyfish-core","version":"2.12.21-renovate-balena-jellyfish-environment-4-x-520cdac8484282ff5ad477b8a3b8607d40e3da1a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.21-renovate-balena-jellyfish-environment-4-x-520cdac8484282ff5ad477b8a3b8607d40e3da1a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a87c8c296bcbfc0c2fb8f0a26a3165dc39d0736f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.21-renovate-balena-jellyfish-environment-4-x-520cdac8484282ff5ad477b8a3b8607d40e3da1a.tgz","fileCount":99,"integrity":"sha512-+0XwURWJ5XmvxueTTUZnMskrE/RQFyUxNgjQLPFknKy4I0lxSW0OUbd01BvzFO6eGRQwrdD6n9EPkF1nti8nag==","signatures":[{"sig":"MEQCIDX/LJwnh76bfHGiNGnAs2pjX9z88K2Tg0gekJ34SVnhAiARTTMt/b34C0qt/BIru+uVwgscWgCNqbaIQnX4mCnPDg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":665921,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgY82pCRA9TVsSAnZWagAA/T8P/1hip4GEZsw3PC7N7Flp\n/Kv0PRHn23fdxcQtxCV/3SILoKK4XIQxFvuem/u3Dl+tET/pA9rNOSlgjh1n\n0wubY6vzqPEqLI7kLjwJCqdItC7SGL+GYPwebl4Bou4cj8YVtf7WZhj4sScp\nHcuryLtQEqY79BbCJQqyMfUnVTYnuG3Pzg0ZDi+76cQYQ4GhmtPOVUF6QX8c\nezJ8iY/a5z4vJACIq67+zOq1PWsRlxkVq7iiKHeGThqReP4qyG9v3dJfxcwb\nLI5ZGexS5sAF2VQ1y1oQZ6INTC3M96NDfTeU6mh/wy8VXu5M4oTXH1Vjtxih\nkE0JulcQ5rcacfNGLoT8YMUBD1QDEWI7IpYxtklSVuIurZuWSSSYMKp2Rl7b\n6AsyEQPlEKfysUH5MGsBeVWTnJE6/Vo3J0f/VTvBQ3kqqCGHglORRcdFcwzS\n+abqE1dtgmJpn+gdP4QZhXgHoUe2PD1l8BZ4v1VUyUZ9UA5XlaClECKJPLji\nYuxwpT+ueCizkIELp2phin7qItA1WAbW9axtVuWJM0aJ/8AWpOUkgixcSvQM\nv2jvl3m+cFgCZN3I1vB3WDpEbP6Cq2E+5QWf7t7Kr7oeseTMr1wvQs1khufB\njideWpxYvelVZUdpgbgaptQBhi6KWDXF4mem2YFGtq7J8G/7X9RyuTc8dHfe\nFEbu\r\n=nD07\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"520cdac8484282ff5ad477b8a3b8607d40e3da1a","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.79","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.2","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^0.1.125","@balena/jellyfish-environment":"^4.0.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.2.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.116","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.21-renovate-balena-jellyfish-environment-4-x-520cdac8484282ff5ad477b8a3b8607d40e3da1a_1617153449420_0.9487263782268649","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.21-renovate-balena-jellyfish-logger-2-x-c1e485ede023890c083d4477e532f728d79e8230":{"name":"@balena/jellyfish-core","version":"2.12.21-renovate-balena-jellyfish-logger-2-x-c1e485ede023890c083d4477e532f728d79e8230","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.21-renovate-balena-jellyfish-logger-2-x-c1e485ede023890c083d4477e532f728d79e8230","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b715215c3cf2f98b4f6300127752399d3f391127","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.21-renovate-balena-jellyfish-logger-2-x-c1e485ede023890c083d4477e532f728d79e8230.tgz","fileCount":99,"integrity":"sha512-PokFqafXQ3jKJ/VgwdBpZyAennr4N6R08WcJkeSK1Aub/ZXG3iHNtc+HoYn7P2wcpcLM/s4WEfeZgNpsgHt0QA==","signatures":[{"sig":"MEQCICN6fOrVVMJ7iOvM5qT89h4xkFl88FUCN0UTXImbNXLpAiAhtVpoHndk+J3RKK5ZgWo7l+CYskOgxO6NV9EhCv0Jhg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":665906,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgY83ICRA9TVsSAnZWagAArE8QAILErFtc2qlBEy9g9cap\nDNx+JhK2WCeIvzmzbU6txTAoeZW5xFLO65Qp9t9mJfxwgi8Ji3iOUxRF9o+c\nC4nQCfju5L4COkIJwiBNCwn151bpMB58h5nNe/Gwv4eQ6LfijIn6PtiNNUFf\nMnanzJBVsLsp90J6Gig49EUZ0BKmcTa0sDHpVCZcTqQ44x8J1FJXztorpOsR\nrj5ElFvO0H7231ObJqR88XWOuLNMhpGFG2MbnU4B63O6AVv/4Wh3of6pYZun\ndy47sGXHFGpiia6tjN6guaXgDwtw5MbOPKwCJf9i5TofcWSXqxkR06FPS37k\nQwDHYdARKc7HbvdZayizQJIvX40PDesYu/sm/dh2O7jYNdsy5Z7v+0lbrRuP\nGvulGPEm6YAmmmrrbMIwf0ZvgnGzry84/D1XQgZyn4eSIhnaGuS6WrVnvmys\nYK6opERbfaQCks7MoWbTFczK7/exXajd9eBB73q+sZnM20vtknBWXqfRyVZE\npXAWDZVMzEBsQ4Ma7eAO6vxwAhX6JD/V4DrK46Dvi9/x2bYS4i5nZPNWUHhv\nTKJPogfeNMw/WgRqtSpwCukkWQnVhLO/87zMNDn3x+z/8mtwYyTaWKE2qSy4\n2pnFignwRemHtwkxiHfiUiYMGo1NpuZDQEKQcuH/88YfcnIATDz55jx31ffk\n+sUC\r\n=xLJS\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c1e485ede023890c083d4477e532f728d79e8230","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.79","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.2","@balena/jellyfish-logger":"^2.1.15","@balena/jellyfish-metrics":"^0.1.125","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.2.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.116","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.21-renovate-balena-jellyfish-logger-2-x-c1e485ede023890c083d4477e532f728d79e8230_1617153479979_0.9408839391063275","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.21-renovate-balena-jellyfish-metrics-1-x-73ca7a547b55ac6ba36bb1c42a891cee2f3520ad":{"name":"@balena/jellyfish-core","version":"2.12.21-renovate-balena-jellyfish-metrics-1-x-73ca7a547b55ac6ba36bb1c42a891cee2f3520ad","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.21-renovate-balena-jellyfish-metrics-1-x-73ca7a547b55ac6ba36bb1c42a891cee2f3520ad","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b6faaa106fc87bfe21ee37ab3a34d4fc0bc26db9","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.21-renovate-balena-jellyfish-metrics-1-x-73ca7a547b55ac6ba36bb1c42a891cee2f3520ad.tgz","fileCount":99,"integrity":"sha512-a+ATa4oCdLq/1SVzYb3kwiAy3Wua74bb2FZgITMw0FpOLeMe7cXGhd6MeqmxDN5u5njvDBe2iYjT80cVQ121Gw==","signatures":[{"sig":"MEUCIQCwhvYZ7dHCQTY1YOg25bh0jWfv4Y0zKYn/Lr8nqLKUFQIgHAexTBdlYQSx26kZdt6vaGjR0j1bmKANmtWWCMxdDbU=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":665908,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgY847CRA9TVsSAnZWagAAtNoQAJRsKsBqMf7/QvTrxynm\nbnRTxyntyeNxOLotaEviE8fzAAMLOti1ZFukU7NPiGD9gX0fWLCoNmA1EqML\nVnq/950wjnBXd8rXnECVFURVEygKJoyMDdQi/FcZXeVqfcf9B0TpUpkZ7yD7\nE4joyao7SHCw4y8dEJCDNdWmYyYSU0jDdIcFFzyezcx/5xSjPsy6CzmDOpsX\nPmGjhLAxAQdzi9+LBLHX0asOoy2WdYWy2G1/7GoxrGAlXZWT6dlPFGNAoVDM\n3/9nCkztLCskyHm8Re3BQVw9JRw8xnHdziQvOLeA25fv0e63p+UYV4pGzEaJ\n9ZlHvZAuG1H1Bv1fVmxHPNPmV1f/SrYrX7cye02L9Pe/NZ1iVrZekhyIENeo\nHB+hzo9G6SGlPM7SnEl2wLgU0wzfOo4jL2hBfIqUgnI4AY1zmRJDVwTEqgtV\nbv6ZkvOAfE+mSBF5d3pT8mu9easunED2EEdayefYMN5WHiyE6xpX4AiqvKSf\nnNeQka1Ked7wg48iTpwbKn3YRlf+rDO+RymY8G8IJb4eLBq9Yi7NBq7ol9xz\nbCGhCa2Ap52Xxbd28A/YapuDhGRITSHCusx79vFPx80o1gV6yErp4GKFqIGu\nfEWn35OaFrPe1janJaIiN1LgPs8aWwd7Kd61ED/W6cYvh8Zhpxj//Dz8qVHX\nPL6L\r\n=ceCy\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"73ca7a547b55ac6ba36bb1c42a891cee2f3520ad","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.79","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.2","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^1.0.20","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.2.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.116","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.21-renovate-balena-jellyfish-metrics-1-x-73ca7a547b55ac6ba36bb1c42a891cee2f3520ad_1617153594875_0.4057676939988455","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.21-renovate-non-major-6b112faeca453447302c4d954d6f9a8c4386de6f":{"name":"@balena/jellyfish-core","version":"2.12.21-renovate-non-major-6b112faeca453447302c4d954d6f9a8c4386de6f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.21-renovate-non-major-6b112faeca453447302c4d954d6f9a8c4386de6f","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0aad512819a1fd95be4eda3bb13865b26a5b446a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.21-renovate-non-major-6b112faeca453447302c4d954d6f9a8c4386de6f.tgz","fileCount":99,"integrity":"sha512-qFO0cHsPfrIEhHoHNM2Rr4WsirOPsN4Z08xkjsPp8IWU+daOVImud5UjP8anlWfUuytSvzKOeyE1YTydgWDJxg==","signatures":[{"sig":"MEQCIF+JtnxyeVH0OBfgwZynlyg/HvrBoPIbI4/0Sf/8hD0GAiA9JK1rBI2mLAFrU1P+2s4Xf7Ufqaz8VkY4XK85+YYTWg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":665824,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgZB+VCRA9TVsSAnZWagAAu1gP/iviNiewShWK4r8WIpOx\nPAQbC51K6JjFtVa6BwCmU0d/aMf017IMDTB5arcwpfDftmDnFF/xycisAurN\nV8a/VPhbhLujnsFgKIA1z95bGHaPDEODU0UToILJRtWkZYQ0uPlQB5HMVg30\nvn6Qta7dEywMHMZ1KgAn/7czyoMvxfxc3f4k6sU2fTnoG5OiDqAdaOGEyQRT\nuPZqpFYBg6L6bTVn3vCRB6n7KShKkRwTtYfLdmgN/ugm0xtGkE34YD6dZ1Do\nlLegU4aYYGE+max2lcYGbOxQoD9aZHEm3o1FiV5EDjihpegELLpJKiBKmLoe\noneTzMVTNanaxC79kQcBeTxfIiBmK3T9zClKZZLPWjWMwvfShzcdR5BEzjSH\nZAVwK+r9V/5VWE0kEjQWHbW5cxbFd43BERf974TVhDTSxaIj3koltCggsnMd\ncLl7tXVS6YQaiDUK26w3ofpjrTaCSafLCYoMDAY96jL4tzGJ1kHtmMsxYXUG\nKVHUfNhO9jbtQkfswrbtFVeZ8jcMu7TQ4bjCL1LYg/cfnaa4etbph9eoMs6A\nUs2Wpl9yJpPSVjXqE/hUdILvBgwEaxztdigCBv0ibrI01wK2DyKyDFe3o1Wp\nx4+c4Z7ml0sZVVIjRRFyfJT87v60J9zBwmvbGvjFVbAhMyMxB8gW+Z7z1KNX\n8KCD\r\n=0Olz\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6b112faeca453447302c4d954d6f9a8c4386de6f","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.80","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.3","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^0.1.125","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.2.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.116","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.21-renovate-non-major-6b112faeca453447302c4d954d6f9a8c4386de6f_1617174421130_0.6875250106115411","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.21":{"name":"@balena/jellyfish-core","version":"2.12.21","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.21","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8f08770af346f427b895e587074762cf7e52aab1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.21.tgz","fileCount":99,"integrity":"sha512-x1joGxCHxFj1t2+A8J/YArhxdGPEtZNwlYtFdtx3/IfMSNEurVGTFhoqyEQVoDYHIG5lJUpdUS/sZIFxeb0KCg==","signatures":[{"sig":"MEQCIBUiIkgcD1jszJ7Pqe1yZDHG5pkR0mlSEU+4S8bPP+qXAiAjaYQ3YF7Z+fTiUFPVMe1Y7+SPTGavI4dRvUB7P3Y0OQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":665764,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgZCESCRA9TVsSAnZWagAASLIQAI5EvCQNmtfYR5OZnzhM\n1Wohp2NjEnXNSZDhVcI60sG3G0LANkXyM8/7ECh+nmMY5N2DAqLAfDRaMddl\ntHjWWOqJG0CqFm9MNpZj0iflJMvDsx4C9gSEBvXPp9syEO/bGozResln8bLa\n+RchaY1z9uKySGXb6dGA5aRJZFVodOZnFPZ77s+ZcEry0XK23GQVX4HhKAuz\nnun++rrXVVHCZ2ox1QW0mthw8zxdnpKqp1LCjaZwFRAx5gznciBKqF94C4RC\nVSkB5Xftj6Fvv0d3WoH0Q4vhyFSDHpPhDRV9+5h+iILNU+a9CCzOdv8M6wM/\nL2NXr20aMexkNkpaebNH8pyhTXRvHDfsNTdg5E6FUb+2Xtbg26dF8FXctRJQ\nb0ltDzdwpPwUeOCjDhScQlZnCb3/DJG9znAZo2ZRNUOkaLMLc9qQVuJlmjiR\nxi+adWM1FkrKOhU0IaQcJdP/RgI0YXIgFrRSclguFbOhM6l9zS/pRWjj8dde\nEnESFrq5kfLjD7nmv9JQPvh3JJwTi6KKByrpSzNdtddtOiD6nU2nXIwt8+6y\n25tOytQ+tYI/Zd2s8q7j3C5jysZLUv8y8ot7LirVQUqt5PPs8LS2Uzknbp+0\nLyvRfI7xLcOmGGKqBNrn8LtM7mw0JfKfeBcw0CHRlCVhy0eKUoijtWCgA5D2\nEuf0\r\n=8Vwa\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"081e46928e289aa27e5ae89c787be0bf5349f23f","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.80","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.3","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^0.1.125","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.2.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.116","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.21_1617174801597_0.7149490972316153","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.22-renovate-balena-jellyfish-environment-4-x-51694bbf01dc362eb2bc6a17c8f9d1d420e8f5b5":{"name":"@balena/jellyfish-core","version":"2.12.22-renovate-balena-jellyfish-environment-4-x-51694bbf01dc362eb2bc6a17c8f9d1d420e8f5b5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.22-renovate-balena-jellyfish-environment-4-x-51694bbf01dc362eb2bc6a17c8f9d1d420e8f5b5","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"28983ba9c7c2f0f487008a2c351860b10a012522","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.22-renovate-balena-jellyfish-environment-4-x-51694bbf01dc362eb2bc6a17c8f9d1d420e8f5b5.tgz","fileCount":99,"integrity":"sha512-dNoQujAOap7TlQnNmNNj1Alcxfw8aMJOGG3W4SSl750nZ8HLYnROqbHyklmOc8624d5BwY4zqwyO9rFeiXHY4g==","signatures":[{"sig":"MEYCIQDEYvFo3yH+jhpSPiTh5Kt7d8ckuBL2F+sgXKoTvIvjtgIhAIKhcbh8s8W2ICWkg1rssIDA8LDWASNWcLLR/iIo8oI3","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":666272,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgZCH4CRA9TVsSAnZWagAA/L4P/AphJLinozZMihZVWLO4\ne1YOLzayaKygqMgDbe28Oj/nuTtrxLgomi/VIOcZ4a+Jg/BiRNKxaQZDcemJ\nVCjEjcIZAZHrrJZrBwwbNbX/mm+NGNwuBbn7orEaOP+MSfWuPl18Hz6NIVzt\npt9+QQ3Pfm5YcWUARHsM0Xfi7gClg+PtLuHDZwW9rQqmOL9T5q1COQcQXK+1\nXpW0buA7FdGdSCgXvaU7phjB9l0KpGvlykMt4qgIC5AfjjeH6N2fXC7xy6hO\nquwo1GjxwHYv/pPcmN+X0pQkBr7nW1Ixw/T8WOp2zhgiPhy3bzFD7ARWQjWK\n06jmgFhKRDnnJw+lgEaG/C2fHDohjJXNRwMxkiEmYEP5/GaFmazjlON5Dpl4\nCRH4pM+37iPHBvOgISwQjrqM5pDvlzlBTl/zEIV9Ti32F8vtwVR0ub1nbIRY\nGrD67X5RkAMWFPFJqu/+Vkjw1kaTKKC94XuJsXp5GN+OToPz0Ufl1fiOqr0/\n4SYkw8d4v46s28zmbcv4Ztm/7sx+Oi3AT34GFtEur3LQtscewua/Iazd9cyf\n3/vlqpHfLzHDsDs9sr4Qqx4A1/TQc3dp1WFE8nogGJLL7U1Mc/GrqY7p2mKY\nM1Mwy4spd2F34vHmnV2q/43uKITiAgUECpy58+xFPwCpWQafXcA4qEIN1krT\nNYBO\r\n=O1gr\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"51694bbf01dc362eb2bc6a17c8f9d1d420e8f5b5","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.80","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.3","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^0.1.125","@balena/jellyfish-environment":"^4.0.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.2.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.116","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.22-renovate-balena-jellyfish-environment-4-x-51694bbf01dc362eb2bc6a17c8f9d1d420e8f5b5_1617175032100_0.8036835016787574","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.22-renovate-balena-jellyfish-logger-2-x-e0749da4fe68bb0b88a3695a24b80bbd09cf237e":{"name":"@balena/jellyfish-core","version":"2.12.22-renovate-balena-jellyfish-logger-2-x-e0749da4fe68bb0b88a3695a24b80bbd09cf237e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.22-renovate-balena-jellyfish-logger-2-x-e0749da4fe68bb0b88a3695a24b80bbd09cf237e","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"680eed4a3f4656c376b980addadd84222774a2f3","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.22-renovate-balena-jellyfish-logger-2-x-e0749da4fe68bb0b88a3695a24b80bbd09cf237e.tgz","fileCount":99,"integrity":"sha512-thBOYlXmTy3PIXcdz6tCmzxdDmYQ4BoVeLlDCmO36BydeIYQQsfAM7lxYbXJITTJ49gScgyttaZxJgvIo6hFuA==","signatures":[{"sig":"MEUCIQCjgZyOZV/RAloval1Bl4zTXGz8sX4+RFqI7MaKWDxt5QIgDsJEbrHTFf0NgV3BRF36BWufFi0ePIH/1qcbXQOJZb8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":666257,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgZCIdCRA9TVsSAnZWagAAYdcQAIk+KP0YtD8ug/N+vIUe\nh5yvlLOf+04lvwv4fmEL3SPdQ3GfE5xq3yumLdrzG0mfGtPnNT2GuG8uaRKv\nTnhP01rt9ugKUeS2tHMqvNUTyTUt+TKuTCDQoiHt8dRfxI19VElkEfUJ2qp1\ngvFV1W6CLO961aEskayetBDf2KSw6oCEQLUTRMEb0GdQimtjhQeLbZqBvfOm\nH/1wcXu6p5D6haJgxXw2Mxi9yjkTuk1Zqf/o0svldj3TY+aZHYVDivOE90r0\ns2IbsoM9Vm5qMFQdYO2T8d7/2SZJ/UthgFBHmYJ7a1sACZcoPu9xxk8jWTiv\n/ACmlxQhc7EFxVYalm7a2SFIFXZmeNQMqHYZMGUGIF+IB5RwiWxxkWfbPsoj\n0Hr/wBuT9J4sqHSDiKDyIAghKS3vVaBQZRhTUXJi0OIDP+ocBA94V7izi97Q\nbcphgSCVVPmsX03aoq5aITo0857764LGV01qoZyEA2mcty+/pOOIxxW1B5gj\nP7lJTeqUnPzyG3IaOtJVo72rKfOZmmCrt6UpqzWmKQD5PS44F46fUNNHZ1pF\n0icKzx8oFIdJTdbiSwVe2SV5EOckaz4x5PRjOaBfI/7G6FAaydF8lmx4tY8c\nHIHsWi5ptDUNS8JX9a60ohziiGwexZJu/ah7CamsA8kUwZ1GgyJjwGE7BHDF\n6dUk\r\n=zNnw\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e0749da4fe68bb0b88a3695a24b80bbd09cf237e","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.80","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.3","@balena/jellyfish-logger":"^2.1.15","@balena/jellyfish-metrics":"^0.1.125","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.2.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.116","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.22-renovate-balena-jellyfish-logger-2-x-e0749da4fe68bb0b88a3695a24b80bbd09cf237e_1617175069123_0.11696648654769892","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.22-renovate-balena-jellyfish-metrics-1-x-1063e4b0f99bb24f9964e4847a9ffa7bbb83db39":{"name":"@balena/jellyfish-core","version":"2.12.22-renovate-balena-jellyfish-metrics-1-x-1063e4b0f99bb24f9964e4847a9ffa7bbb83db39","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.22-renovate-balena-jellyfish-metrics-1-x-1063e4b0f99bb24f9964e4847a9ffa7bbb83db39","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"52a8d96b76898fc8274186635fba88ae6944b2f1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.22-renovate-balena-jellyfish-metrics-1-x-1063e4b0f99bb24f9964e4847a9ffa7bbb83db39.tgz","fileCount":99,"integrity":"sha512-JdtRbYyxbruldw6IxGJ4v+HrLQgZ17tcnI3F9a5cCx9hmUqGhXR3R4+0PsDtNQfnPD1/G5tfaoWJQWtZQuy3bA==","signatures":[{"sig":"MEQCIGW8W924MaGrC79nvWQb+iipfUR71rH0SZU3Kolyt2kRAiAh/uDkCXHACuCH42vetSKg5IG0uQcuui5qOy+8T7ORtg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":666259,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgZCJGCRA9TVsSAnZWagAA7+AP/RMWc8g81u/rSC4zqncv\nSmxvt8vlx+icY8rTI72gARusSVOJpMYeC3JSJ6O88VxyO7jKmkZFKmcJsghf\niC1k1JAlcHI6ppEa3spMDro4I9MvqAVwjqMluzuxCGKy3TQfRrcqBGJNTaMo\nH9c1H0B9evogCdezhBwbyhZsBO3yrwxYW1ELYhd9j2uRhPklsf/VFA/cTtaC\n+aujpHagGXv7o3la/7HnFPhOhUzXR0bxZVsd0Ms8UfyfqvoOv1oihlZIqGga\n/i84d9khzOHpmbXpFgReWNae4jiAdJKwik844F82KM3Oi5nsrBM1KtbcNa8x\nXoPFE0pwAfNQvm2KhRvcaKVclfONgwoImfeT879eQNKgkq5wb7zKjBnA+3o9\n2tmW7HyVXUyY1Tvb15Ehztef/ZCKD2UE+jE/cdLywGpMITJ0qsb00/w6OAu1\nFWbkoAzKnwQtdADjbKMSJRbviS7brWNWxs0meoF1jfab+2AvivCrm+QHHb69\n9g1XDGZE2dbWJCsgQmTMCQJbxgXtDnjT3KubGlDLl3iwqt0frpDVpk1gPjpX\nzHPKPxzOl9pIrKifOVakNNaOD3eBqbLeB61TBj9X7VpcmHtpRv/HEaEuiWmR\nAMYzRUPQSt6euIahoTCn/Hrl6iRQoNwRtbd6lD5tbZ//VHzp6M3qeUvBVQnQ\nOwaL\r\n=awU7\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"1063e4b0f99bb24f9964e4847a9ffa7bbb83db39","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.80","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.3","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^1.0.20","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.2.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.116","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.22-renovate-balena-jellyfish-metrics-1-x-1063e4b0f99bb24f9964e4847a9ffa7bbb83db39_1617175110263_0.09531713876089976","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.22-renovate-balena-jellyfish-metrics-1-x-b059dcf1f80f5b419a91cbc16d41b494974ac668":{"name":"@balena/jellyfish-core","version":"2.12.22-renovate-balena-jellyfish-metrics-1-x-b059dcf1f80f5b419a91cbc16d41b494974ac668","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.22-renovate-balena-jellyfish-metrics-1-x-b059dcf1f80f5b419a91cbc16d41b494974ac668","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"789174eef4afa4c6c5471deffab22059dec49b0c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.22-renovate-balena-jellyfish-metrics-1-x-b059dcf1f80f5b419a91cbc16d41b494974ac668.tgz","fileCount":99,"integrity":"sha512-6nC4lyn49oknAmLY33NcRkzj0hW4z1WjhFUOr3Wlgcfv1S9ov7b8/lAOEO6TS6zcZMMHYWQ7XGSTKX3MUxd2fw==","signatures":[{"sig":"MEUCIQD5lw4uinLTBh/ZPvwMUdjNCMtt83Plon8b5wborbjnqgIgbdck6rnHuzd8sQ/iVzLkyYMGoziahiBIm+IeFfkFC+A=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":666645,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgZDpyCRA9TVsSAnZWagAAqWEP/3psyA9LO7bskMuFFnU8\nml/P7f+2TKQEbhVZISnJ8C5A/RFs/qYG3tHuz/BAtoxWIsJCY75R5AvUcHE3\n3pKap/bkXnwa1tchpjvK5lDG1Wr74gFv4XYZiuDgIlJWTAjV2h6QTEMyie4o\nP3QHox0BDzzJqnVyLYmpNOQ9VuMkwm/JIJCrx7uICaSHo8zppvHCA8xO0TIQ\nGX7AYqFjlMCv9YmxNsJZ4+1YIBbvsA8KEyoyiURoeN3+tqgqQHc9AqRuysJK\nVNhEThfVxwWVchekMyeSnCcLoVnJg9sYF7ZPcrriHSYLH44Z+Kdj0vA/prlh\nhvQ0hQm0Yp+0gwlzk7tMDCOMuwaJE913D3eMt0RhJU2Q8GqAS97aE4yXZT9q\n0GkoXY9s46dLQzK/U5s81RutcxanifpsBGrJhANw8S6O/yuGQCftzKt/tfGH\nB7kN34crk/WHHpYkPM4Ml2eeFkFk/rtW+FTkft0gU0rXdTbyX63MJKqRkU3f\n+e8erGSXsBIn7+Q0qFZvEztMK7A2u8/txoUiyg0/4ftL4zHwfuku8XJR7eDz\nZPuWTCUHP8c5IXxVL7CYVlCv9SqhvebTxDNIpq4CJQ2FVc8Xn1Cs7J6K8eS/\nhI3G0SQs3uZF4rWzrKueJMwEC0eas1QPivnCGFGEZu3OP+6wPKhpz3e8qCNS\nsUxC\r\n=/Idz\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b059dcf1f80f5b419a91cbc16d41b494974ac668","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.80","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.3","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^1.0.21","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.2.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.116","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.22-renovate-balena-jellyfish-metrics-1-x-b059dcf1f80f5b419a91cbc16d41b494974ac668_1617181297826_0.27082672959102294","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.22-renovate-balena-jellyfish-logger-2-x-c591146521ff50a1a1c80cfbc703df45605e5b3e":{"name":"@balena/jellyfish-core","version":"2.12.22-renovate-balena-jellyfish-logger-2-x-c591146521ff50a1a1c80cfbc703df45605e5b3e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.22-renovate-balena-jellyfish-logger-2-x-c591146521ff50a1a1c80cfbc703df45605e5b3e","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a705731f2ce0da5b7f6a7ef18089ba59cb34e6b1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.22-renovate-balena-jellyfish-logger-2-x-c591146521ff50a1a1c80cfbc703df45605e5b3e.tgz","fileCount":99,"integrity":"sha512-d6Pmpbk2ByqLQXXjwKcJgCOGSl5xW0S8/kI0gOWfNtwu0xJPE54ben3hrdRTJhGZQOpgBMQFfVZ0RVdZT1ELVw==","signatures":[{"sig":"MEUCIHfvZeZNLjHI8sVYVonKT/lblICfPvTnkqssLclAhgA4AiEA/NtOrGfi68KQ9svkoR4PWmibRXm/TczjyIOjAOXdm6Q=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":666257,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgZDt5CRA9TVsSAnZWagAA6mwP/jquoR9PUmt2ifg0ZzVG\nFbgBAt4LFADWxMf5TvVBSsyhmOTWFJc+b/6KdqIYaOaKxNwgKfHpe83xJQwJ\na2zp7bxEC9iNZw6JofXl99xHrEfMjRL9X3SKYXOY6NQ68+xev8+BxYXcmqwR\nQtYHT5oW5ZaZtrTd6i2Umo3AHP+upa/CYLwfEC3+t0bQyzX8luLJaS+K0Kxn\nGjUSrkAbmu6PkfshIzEDVpDknu0MpISROv2SAhBumRohDPsE27Om/n2EnSFN\nTmUhdJf0mtRDbDSrXbGIbGH3plkiOpZg96vIPTbt2vqsrK91XYOH73dDEqKB\nsSl+cEGkYNUjKbBu/EZkQYehNDcEQmp6NNufPxXyAm3qXm/f3moONQZRhMl9\nYHtC1XRsG7HpUrsV6HQ5ykjuwvrkLh0siY9+BrQAF9+ZNrAOWTCWs7V4hZ2C\nUo0LmTJ04xum+bPmEy2rVxBCuqmLshz/FKNt6mKM7mXMkX982E0LZ4buyAZV\nCm1dk+B34EMyniXqpnhgNxl1WxERgHO3kQPgYHbtI2wW2Q1f9CzuBdLb85ha\nZes2mAjY4IquI7Iyjnf6TKmKzL1zX85xlQTJdzqMtXXPxiPeaZ2hEGYXkPAv\naesNlyb8r+3/P6OBTBoTwxtuwYjUqXC2TUen/J6XBW09waRsWJotoIbTEenY\nW/N6\r\n=hJgq\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c591146521ff50a1a1c80cfbc703df45605e5b3e","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.80","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.3","@balena/jellyfish-logger":"^2.1.16","@balena/jellyfish-metrics":"^0.1.125","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.2.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.116","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.22-renovate-balena-jellyfish-logger-2-x-c591146521ff50a1a1c80cfbc703df45605e5b3e_1617181560635_0.42301231845012244","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.22":{"name":"@balena/jellyfish-core","version":"2.12.22","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.22","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"de49fdf17060f9c770afa4fa1ea3cdceec84d7cf","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.22.tgz","fileCount":99,"integrity":"sha512-4IomtCP9nw0A0A2glfxufvip4nUyUP9a/IC5+cf9rSgg9UFMGwADvXp57DZsgkURXbmbEGT9UXNcd5toRmhpEA==","signatures":[{"sig":"MEUCICgqPqM1b8gOIINQ0NFec335ybhHZznhmsp7fM6YHiaLAiEA3945sU6ViasVvlluayw+fEG2Ko9rd/z9KXjzeFuEbZs=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":666566,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgZEKaCRA9TVsSAnZWagAAHxMP/R4Rge9oZ2yfuA//iKb0\ntOXvetIZErhO/ub6KYBScEhoQluE+UbhQvJ2fUrgAWLE4mvGNlamBm6uXe5m\nC6IM0/zjYbFcy1n/ObRo+qh7zUbbJPVRrZsp/hbxqDUDBuVQrEq8JnTdraSD\n6eiC7JcKt9nIpN4AhKCKDt0Gcg2f4hpP7zgkekPytlYhyc2aJDNtZ24xOeg/\nNvxBYEx01JrRy1dWUZDQ7Uj4g8IJgaIx0LDnpCixFz1jYcgXb9KS4WcWrBZL\ndRgfW+9UEq3Mx3Ve8U8vIs4Gmov4vfjsfYGGJBe4loLSlYm0ZA/PdeWNkKCH\ndlSOhVNffJ7C2XX/02Li8MBELbqYPMtudOp62mG/Dflm9xRJbjQdjFgNZXXj\naZX752BmxlSh4EqglstIhSB5YFV4LqJRXBmulAWiYwy47kKmAFKijiLUeeEa\nF4tDfNfoPwP3MUBdJ7eF0G3GoV/MjW97Zl19/3wj+zLXezbNPIZlBs5qKYkG\ngLKiKEb5QEWsfn/X0XGarh28oLPCQ+xgpoiRjM1FePKvam5erkpxekD0/L60\nAkUrs//C1/rK90HwAWHXEQJZmlDP7Rilk4/Mx89tyuo1XH86TWpQcFPp2eS6\ngkNMUh/N5zY3nJzYqkBGp5MSA67q9+/LxDBkM8/La5/b/+WQLaM6TvnJU5rX\nH/kB\r\n=rjLl\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ee0248027741abe9cf41f583fd030641891a5c28","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.80","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.3","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^1.0.21","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.2.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.116","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.22_1617183385758_0.8055384181454801","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.23-renovate-balena-jellyfish-environment-4-x-1463a4ac0d6540e459c82799f3bccb59389f9ca6":{"name":"@balena/jellyfish-core","version":"2.12.23-renovate-balena-jellyfish-environment-4-x-1463a4ac0d6540e459c82799f3bccb59389f9ca6","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.23-renovate-balena-jellyfish-environment-4-x-1463a4ac0d6540e459c82799f3bccb59389f9ca6","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"53def75f53dccbbe98048cc33b5b122b6cc6524c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.23-renovate-balena-jellyfish-environment-4-x-1463a4ac0d6540e459c82799f3bccb59389f9ca6.tgz","fileCount":99,"integrity":"sha512-DmTMeTaGOv57nToUu0Pop6WmMjUpSRz6pTusDOOvCltUxMiIeNP/bM3JZ03axQspQa9u48EnskMB4DXAAaTTaA==","signatures":[{"sig":"MEUCIQDKpwT4cYTaY7l7ZKwZ+asmcpj7hN2s99bU6w/BYsw1VAIgXNi1OvtnPID1z+cClh1Dz1NvYqo/raEYrvU2bBCnbmk=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":667074,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgZEOSCRA9TVsSAnZWagAAxZIP/RuMA3zQLrm1npmaPLLq\nwUuJYvgAAKKl6bcx1bVMeYaerxRlv+/JTTzhE8uceSSZW+EEyIBJK91VJf3G\ncFJygGKQSCsk8ZfPKuMKiga6pBM8eusPlUjqjul8j9JUtEF4D0YAurmXgQx8\nQAO9RGxWiYcEutMEgj5zASs28oH9g0qM9WyILkLPPr2+pi+VUvAHEbU8XZhi\n/3+bNgVSQAwx7s070drds+z/bAGeBSakcs/bxNY49Imk1YGU8NlR/RcV7etA\n4tbehrOOnqaTQOMtxq1JXRpzLFqbDSTifpvuGm+9dXmy+jkhD6/Pmwy35JXB\nxWRIgN2NF9KrOOfd+zt93qMaQzGzJCOPxLappOQSphk7vyR3IjGNnMmVkc9m\n/7XbE9CI8bbwVgNTn7ypItQMDW6JOOBBHC3cUWVHRWwEDP4GRhlJgcMWn7Xm\nCgRX453XIwM787aIL9MkFeqcvgTbkwd/UQTQoDBV1t4lM/06nVARs1dipUie\ncMHr0eoGK0ahfn5WrCwkDKCaKc9MM83Kgl6mqio+lZ8puftHZ/Q8PJSC+u3J\ntrdjP5XG+QwTxBtW2iNVrQxH+zN2yTKfcYzjpufKpZITpQUrZDZJSZYtjfKq\n/L3zGn6OtOIgikQNFJBEIAnNMlLLFQI0lgWO971nqs+t9nMufmZ8rnVou0gL\nDUsD\r\n=+MjW\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"1463a4ac0d6540e459c82799f3bccb59389f9ca6","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.80","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.3","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^1.0.21","@balena/jellyfish-environment":"^4.0.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.2.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.116","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.23-renovate-balena-jellyfish-environment-4-x-1463a4ac0d6540e459c82799f3bccb59389f9ca6_1617183634187_0.5771563432479627","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.23-renovate-non-major-fe88181f32b5937f7b6cd6b9b39f74f8115b4101":{"name":"@balena/jellyfish-core","version":"2.12.23-renovate-non-major-fe88181f32b5937f7b6cd6b9b39f74f8115b4101","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.23-renovate-non-major-fe88181f32b5937f7b6cd6b9b39f74f8115b4101","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"60911ceac9e07f8f098f9ac1d30bc0326e64c7c8","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.23-renovate-non-major-fe88181f32b5937f7b6cd6b9b39f74f8115b4101.tgz","fileCount":99,"integrity":"sha512-ufddUC73XSqxfTQ2WjSfk74ypSZaW5WX8U5CuSd7QRcMdxfMPdF94qOPLRchXKWfLOALiBwljder5rFZ1MUomQ==","signatures":[{"sig":"MEUCIQDXkTAPUdmZoLoxbBAKzUc6S14x1Xf8dksCipSlq9g7tgIgXBlE4tdPZpiAGiSCqq781LNynxdB0JCrHMWSgWHe7vQ=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":667053,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgZEQUCRA9TVsSAnZWagAA3YIP/jfiSj4b8arX6rh6T1iU\nxnitLcXQ3xE6Yhje5uyNI4/q0kyvm0xUlUV/yUExU4/q/4V+I1awXKkVB+hb\n16XUrZbKkRdz9izE9FHHY2RLI6pbrRWhUF/hEtr34AwbdErTdHUZ/3GOc1q7\n6mt0QRxXZd3hNN7gFXJvVYrKJD9tIr/L4cZFp2PzZZ1kJf9bZnn5JNUDs4+W\nsHxXNBt8MQ0iaha6F/kCMrTEDvHEcXim0hHuGurkRVHkmU2ylom37eN0q0Pp\neSigHqqtQYzoVYxXKjEAdzyuAlfVAJwIRADjyrqLrgVW6fPmy9Dw1a5qvXC4\nmUKKYfy3FdbRqQIALLaaRgAodEikyfbHtm3cW/M/ELHK1FUmXe6in4HWlVFp\nW8pob0hgmsCf+zset1sNb/BmmaD1C4n79d/HhbnXGMRU+iCSGJc4w9/yKwsX\nEfkjaquWdrKF89NkyCWfr8P/xE5BSRPTzmfmaDjgrnVb92k0wV69KMoy+41G\nUmVSJ8xV7NxBkC5k0C0ckA8zuJLkRlwaJI7kLjvtCjCJ6vAstuNngHq31Y/B\ndxGWkEF/6Ya2B/lJ/ZSlkQuozazvJO4CcqDfIrgOw/YpltkC02WXDXiNdZYW\nr4gIyYG6cv+/lo1l1yidvj8AoRMD83NK76pPTLB5IJho8gXzeKrng9ns9vBJ\nu4EW\r\n=bDcR\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"fe88181f32b5937f7b6cd6b9b39f74f8115b4101","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.80","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.3","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^1.0.22","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.2.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.116","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.23-renovate-non-major-fe88181f32b5937f7b6cd6b9b39f74f8115b4101_1617183763883_0.18016648702633464","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.23-renovate-balena-jellyfish-logger-2-x-c135a9a9f63d6a6941fe93b55a306cfbe82855b6":{"name":"@balena/jellyfish-core","version":"2.12.23-renovate-balena-jellyfish-logger-2-x-c135a9a9f63d6a6941fe93b55a306cfbe82855b6","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.23-renovate-balena-jellyfish-logger-2-x-c135a9a9f63d6a6941fe93b55a306cfbe82855b6","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5633a99ff931835f25244565f66c6725f2abd095","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.23-renovate-balena-jellyfish-logger-2-x-c135a9a9f63d6a6941fe93b55a306cfbe82855b6.tgz","fileCount":99,"integrity":"sha512-RYCwSNBahFlb6HtkEV7lCOMu78E9ksPpl3hwoCHi+ZOC/Ud03cTS2p4KZv5kHz50bRV0Y1Au8QWAPuIWuVJlsQ==","signatures":[{"sig":"MEUCIG5GVT8/qTbfVoTD2nIeFNKWDSGJAXjBpWdUGx6V9wahAiEAyqPHIVNUIq8YLDRUf49TFX0ODbYvP3ucYni/QyacsEo=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":667059,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgZERICRA9TVsSAnZWagAA6SEQAKGOOnFt80fHjQYxXSG7\nXl9xIyjIjfxldneTRfAQOB5x68cpVY5VA9cikHU1aadcokkaBLCFary2pY1h\n1ZSgRZGx3b06YCFSUcWLG+BtSbUabwCLZUyKts5mDsgHtv4lcj+HiV3gFRzn\n0ycHXc33hFbLnCzTiA9VmGD/3Mg6sQWYFAKaH4YOmRmdTcQmhugh2sOJIuzz\nOLqYEw+GauMIvjwjoKynvgAEeYC1HTOpFlPxMGmZ3OhkpZtMhJ7eQl/HNZuK\nVjDkh3KddJ5axaZ+wYOfSui0n3vJmTfldIvohCyRdm4UyvyvVdxeq5QIk2tC\nfwDgpG1C1cdL3mSKiiRJnh01uvWxquOUCmIlqK44hpMza44UNGs3WIsax4me\nfbSrcHyE2gj0aXPFIcEKH+Y3YhPY5XD22LJPM0EZgJlKtFDHjdqUDMk2cPzc\nBPfCcH32d+9/jw7pOfvhbUuVudR0q0pnIvF3kJtvAPNwjaYl5cRhjaG52kPP\n2TL/cheJl3jDWDaW3Ul9HAs9A7+TzH6nQNUQ/8K8JdTpGZlSR04iG53yK7y7\n9kyKpG4p7/OqaKEge93stMAyiAlzyg+cFCAqjl9/kk1WcKvULDp6EDfKKYwM\nOmHVMWXXmsyHriu68hyQyVNXuQlXprPvISSj3uAEh00B6VJo9T/c/GKP1+NF\nxGjZ\r\n=To/k\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c135a9a9f63d6a6941fe93b55a306cfbe82855b6","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.80","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.3","@balena/jellyfish-logger":"^2.1.16","@balena/jellyfish-metrics":"^1.0.21","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.2.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.116","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.23-renovate-balena-jellyfish-logger-2-x-c135a9a9f63d6a6941fe93b55a306cfbe82855b6_1617183816534_0.6831970944608803","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.23":{"name":"@balena/jellyfish-core","version":"2.12.23","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.23","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"7d67bd466b6ebdaf69fd55f7b4729f1f07ec75c1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.23.tgz","fileCount":99,"integrity":"sha512-PvpAzkVWcM/PLRHVUAuE/MXDOr+6/eCN7f7BAnvx02F+2L67McjwxzoGLAiaxmDgldD+e6wtVAqRZj3GYva/MQ==","signatures":[{"sig":"MEUCIQC83WRR3TUl8C6w8DnxqxC3dmXJuOkKBqgOkP18YiPxQwIgdB6zsj9niOGCR7kYjmovBFbKClZW4s9NEa1hx2Fav7k=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":666993,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgZEWdCRA9TVsSAnZWagAAgGMQAIzDf2jirDT+bagIVETW\njRTzi1qM7BHPeMP75aaHNnz72HM0tiAe9zh+2QPyn53tgODvCeRJFavWvbcw\nV5qYZpshuZXSKU0joUkMoTRq+J5yqcFVR3LOC+e2+KogxLQafYJhPsLZmyD9\nkirk4UBv/68Svo3ch6skb4CmBZA4U5YGj8m3PBwoIgtzCRxtAyQHoOp5o5qy\nYfxnVk1ISuR4zxUdOJY88lvMQktgcI9kZHcEqBVzZfDqclPmK3zLo6DEq7TW\n7xDOyWtXDvSgV3nA8fPtMrdWjrpJ5lP62oS4q8nt9CHkyUJJ20puk8UJtUrv\nmJ/EGh4kXMufY9xta6eByuBOOgIpsp8ZFnMPcyHiHtkHHEGZj63U5cgW4vpe\nJoB+/WU0qVVHHtj/2pivmnQJ8gRPtAjYxhRCWTCiHzt4VsJfiA7+p9Md5ubN\ns5j/He8oLvwJzdrrzdl50i/OjPrxTl2qsgqxzk45172RVafSt9X6sQH5X31A\nXL4wBZD5Gcfvgo6ftpg0ayTZsgmYCB2KadVwMf52IdHaQZQ00uuCRkaSoec9\nKgYAmPgbk+/qrpMCdKbk9jBZL5N+2ScoiUAvdLk1o4wUuHC+gYqQMuUK6dcS\n11krsuIBwARFeamQbn/arJrp6edSrnvsimQp2IK+WBiQ6ZHKz0D+1k5AYZzs\nTmki\r\n=dBcc\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"1869e74bd0d4b202678096a7ebb4af01c71513f2","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.80","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.3","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^1.0.22","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.2.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.116","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.23_1617184156579_0.5619681073384963","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.24-renovate-balena-jellyfish-environment-4-x-0df2cc05f0c65ca7a20242c7a493354bf0d64db9":{"name":"@balena/jellyfish-core","version":"2.12.24-renovate-balena-jellyfish-environment-4-x-0df2cc05f0c65ca7a20242c7a493354bf0d64db9","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.24-renovate-balena-jellyfish-environment-4-x-0df2cc05f0c65ca7a20242c7a493354bf0d64db9","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"50c7279bd47610cc6aa6895b81b72373332b6a95","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.24-renovate-balena-jellyfish-environment-4-x-0df2cc05f0c65ca7a20242c7a493354bf0d64db9.tgz","fileCount":99,"integrity":"sha512-AdeJLn1XrGqlqK8eUjHh8nRvU1PXaDYTFEAYd2oLbALK1h6tpYIOWuOd9uJdhhpgYa0XFuksoKBvads9Cw5ebA==","signatures":[{"sig":"MEUCIQCorwVHr1k1HHgy1J4I/WOHN12FDqhUaffD67aXr51pygIgCcbPb+E39uKLOcisauPi9RMnatgorTv6ZnY+shpjq1A=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":667501,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgZEZ0CRA9TVsSAnZWagAAV7UP/3thhh6EU9GOu+bfvAWK\nfJrB2nzr5GMNYHEuCOgXhtM/oftY1+TnbAozknElQ7lGlWhcsv+72pcicuKv\n4wDQA0V6QXhR/KZSXfGo1ctLVx0AVtSgaMJ+JBNSKld3TkuSKaOIX8V0SZY5\nYEL75AXlVdIfmazh09lbQhhSj5x6zsY+Qu8uTB/uwXMQonyfZiJRmlwnwOrS\n+jgXT86kLG50phugGgZGUZyM00Y1eRTyVVWsOeQrJrlow+dbP62yorwo3pj/\neJx9hoSONaxsKO1n2zhZD2X0mMrlHvo/vkhBtbTpeUWIoWxjWdlaBpo/HgaS\niYcg8wdP0iNjmRW/T1N1rHIzKTaVTgTRfIHG0FmDG9FJKpd5shQJn3LFvD+2\nkY28EXp7fx7GHxgUQd6FR99itOfe+kuJjnzAZkN3VJ/8MBsOTprcQCf3JHXN\n8wEMYX1HLRSpUCmc+n9bxUDfJgeYVunVY/sT8ARJiEm0Zw3C9OcJRv4jMqyk\nL6KAg/sKirBxIf8dWAHRppAcvYTXCeQCQkN4N250DSXqB9b+c+qJSH9kWmUa\nt0dnMCgTbpMBvXkaUSMOnq/hVOkERu3itdJsYXBeJ/pdmZQ0j1IXknw//MPI\ncaLw4yz6+QBrMXCCvCIkRE8KP6PVV0ZP8Q4vbBa2LycA1+Y0pWlc4e0yUuZG\nGps2\r\n=q1uD\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0df2cc05f0c65ca7a20242c7a493354bf0d64db9","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.80","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.3","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^1.0.22","@balena/jellyfish-environment":"^4.0.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.2.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.116","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.24-renovate-balena-jellyfish-environment-4-x-0df2cc05f0c65ca7a20242c7a493354bf0d64db9_1617184372148_0.6759520503812755","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.24-renovate-balena-jellyfish-logger-2-x-8e36f4b9cb18d54f4234bbd6964910c7e68fd6c7":{"name":"@balena/jellyfish-core","version":"2.12.24-renovate-balena-jellyfish-logger-2-x-8e36f4b9cb18d54f4234bbd6964910c7e68fd6c7","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.24-renovate-balena-jellyfish-logger-2-x-8e36f4b9cb18d54f4234bbd6964910c7e68fd6c7","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9fec9e21e2a0bdff4531ab03661afe3a7f7b1586","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.24-renovate-balena-jellyfish-logger-2-x-8e36f4b9cb18d54f4234bbd6964910c7e68fd6c7.tgz","fileCount":99,"integrity":"sha512-MZ2AgP9ZASO4cO4v7bzLHKDVL/adXVEnVH+GIjRhZvzVk68x88aL4y+e/CKbiLthOJ3PTeldQlpLZisuVVi2yw==","signatures":[{"sig":"MEUCIQDa+texJ5SVk8DJAniP2VsGhh5bo0CdIjX9SC21ou7WBQIgGlsZ3X702JEHvtkp9ld7YhT4mWBGNPQRwo27soxZ2Ro=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":667486,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgZEbUCRA9TVsSAnZWagAAebkP/2+bw1b5A4EuxbeuNiPh\nA+wQ3czplQ1/m3avDmv3hrp2aqJCG48SpUUBEJQLYlbqfnfGRGCUxEP95H5M\nvTEhiF+A090RGKsCZzu5MWrMOZGzOz6g4Z52qAnMjbwLAXw+S0VlwWx8IYr+\n5anw2Si5ppC5juQAm52UYfTWsdlXfcjyw7URENpRQi+M6t4iVdzdq/lWfEJh\nVO64TlKGVqkPgCElVMLDIq9fo1C9dkxcwJOBaKjhrYmAH0s4Mqe5eVXx7e7g\nzZO8dXja3SO/sJR6WV+vZP8jyRbLWIwd4OtBYbIbINIyp5e1N2c4CdJXIw9i\niqnteNniQFFvjSMlBwzCBY8t2hwTGeexOckGl3uKYooPgPeNe84zlVdn4S+y\nuj5H1l6njEgGp42Pp0YJnwt2r1oRHF6n+GgNlMqAH/8mNGkYjPO7nlwkjZWz\nJ8TxhjjBk47r0j98cTKaVRxZkA1F96UffbKHUP0a/4b3emD86CnKagoeyYNS\nhCFqLlk5iRJ1Gz9J9U19zO3HT6zvkWKs8nIgVhKpQdg5HAT+UKvak8jd2btR\ngV4rbJIeloC9MZzTL/EZJL3py53qW3szCxX9O/4wNxnQ8RzTzRTRKa0oAfj6\nyToiUNu6WgImme+T/MfvUjCVeYY/Du1eOChaGkI+GJx/E2v/VOUe1MN+uMK/\nX97Z\r\n=/8fZ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8e36f4b9cb18d54f4234bbd6964910c7e68fd6c7","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.80","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.3","@balena/jellyfish-logger":"^2.1.16","@balena/jellyfish-metrics":"^1.0.22","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.2.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.116","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.24-renovate-balena-jellyfish-logger-2-x-8e36f4b9cb18d54f4234bbd6964910c7e68fd6c7_1617184467556_0.2621783413162049","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.24-575-slug-uppercase-chars-0f6e4b492b1131d93a6a685536237840fcc6c022":{"name":"@balena/jellyfish-core","version":"2.12.24-575-slug-uppercase-chars-0f6e4b492b1131d93a6a685536237840fcc6c022","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.24-575-slug-uppercase-chars-0f6e4b492b1131d93a6a685536237840fcc6c022","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1379577242b115a461f80f171de79e99afcfe81f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.24-575-slug-uppercase-chars-0f6e4b492b1131d93a6a685536237840fcc6c022.tgz","fileCount":99,"integrity":"sha512-/zLD4lAchPA/MTf0MLQ1Gb0I5QXp52pZMpEn6qPgmQX1hgDDVdHbNCyy/q7ZjkVUUxAyJFAoIjMJ7NQANUsiLg==","signatures":[{"sig":"MEYCIQCGjWIa+i+WXf21nacg0gDvnOTGOa+u7YWRX9cDcKLIewIhALGmtfo5Nva1h+48qHQu9XqArVqYyzjC6XbRDZjcJ2sR","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":667713,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgZFxpCRA9TVsSAnZWagAA6+cP/3CDJSTmeaSbJlegPacY\nSzz+cUXAJ7evFWDu5EeKxj8Y3swhs00zjZU5fFCYtX+wY/eJo2keOc32bWcJ\n/BPViWMjmzTuIa6x/+VjevpDDH22pGX/HuQJ3nrrc2fCAiKraqaHvfELkwRW\nbuH/NY9zp/KZ7d37U0m8pJsV8cbyvhhGDGItJzMbdwPnE6rSxCMq0QAMZSPp\n8+tbAqiyhTJR9EZRD3qrcxbEcU2X/eekzdQ7ZzDvcJ6JgLv4ZBHNsiINK7pb\nysVTAqQgILkoebrDYtFk+DoSxDtYPSEBS/Ng0CFVzijjgp8ndC7oYl+bkjtG\nlSb6bYaXzPlbnouh5J7n39ZV9+9h9nGuD7Th0bR8ihJeT5L6fXkY50UR/bE9\nJ1Ha0NqjbG9oVhKti6tspxf6ArkBRb57w0WPWZRTZGNjm/WMr3YLYDtnszHV\niuEfES1qQ7oVPKI4x1BqM4pPPQjqIKrQMAYb0Icadr94kizKsDYgfzGviz+o\nvmp8P32rPquxJdNLtv34Bq7CcQyYpzgrjGr+gywSwYzICDzDSFDivV+5emKh\ntAcnn/4P6CgZKJkZFT75+KJ3PZVZRVAsGarMsyFf1ASf+4kE3Jm7ijvJGbBv\ndVuBU7zjuzUrSymFbx+anYRl8wVu4UxJFD6cxKLbwqSyw2hTPHhBqZYTPGm9\nh90W\r\n=xn0I\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0f6e4b492b1131d93a6a685536237840fcc6c022","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.80","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.3","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^1.0.22","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.2.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.116","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.24-575-slug-uppercase-chars-0f6e4b492b1131d93a6a685536237840fcc6c022_1617189992550_0.01750887416315461","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.24":{"name":"@balena/jellyfish-core","version":"2.12.24","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.24","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c7538449611c35915ae425a8c42d7e262fd875f8","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.24.tgz","fileCount":99,"integrity":"sha512-a3bkDIsgF7KpCy0wyh6dLX2060epzV6Z089IviLD62ZA8EPneG9D3+jLzP60lUFgVOjWcYT1vSo/OqzDPePLtQ==","signatures":[{"sig":"MEQCIF5e9dbeOZ/imusfp7q9G0bBJfsRkqTUQLzsfrsJS5TYAiBtFa9MeLDEPYAKIsos27ejKukbkM7wkIKSFck/P2FfSQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":667647,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgZF2UCRA9TVsSAnZWagAAUlsP/2d0dIo7aHZzZfcT5Y+D\ngrMjpGGTsZBi7SXrWhBaS4ewZZSSoCivbalmkaM691bsv3EbdwyY3A709Cux\n9I018v1/V7Js4zYtQ/pskIP86ithzYWnaGLO2ugFMzjGHfJ7gL4KXv+s0f0m\nEHe+CdzTvS5QV5VBrEi9H4D25GSUXCGz+gExtG4HltHaL4wVePepo9NYx31z\nSTcgXDdW9a4dBhHnqscds8EIcBct9Bc7eETdIY7eWBIHmsLbAclk83kbkkM9\ntZ3aYScufxxWclhITUXbJ4MYSNBGh6j7CKvdZ34zzh8aSfeFowuPonrv+c4K\n6l4hSmbf42SYK63XASZBUor0S08sgjY79uKRcZLo/K2TmyAokBnJxcE3mEsV\nu4ItnNqeURkxMrgkdHzBS08+6hCczo8CpJRDX3ovSCaSYdtoF3+IaVmkOMYm\nFlkreZQaYry/2luhT2Bmi9/BmqbwQgfDludJ/BpRzclYnx+nBeyn3c+dfgJQ\niPkXNlxcjRRwgaPssQL6+853zjAWctF9T81rqITXH5lD49+YuC+3Tv9jFoSC\nvc9xhJ+3u25wSPLXT/70P39AbVcRDsIVPAeQcMSCqPx/gGrUoBTIQuEilaZS\n8BK31ycPS+s7Y792usQmi6wj4r/EmGDfslgnXaCSwUPDxVt8QeGW9ut2hPCo\nkZWU\r\n=W91C\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"df1dffa7f0f5145a02ac4a5dfffd5c2a1eed7e36","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.80","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.3","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^1.0.22","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.2.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.116","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.24_1617190292085_0.4793296043687352","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.25-renovate-balena-jellyfish-logger-2-x-02901d29170de27752eaff40f95ebc7cd373888f":{"name":"@balena/jellyfish-core","version":"2.12.25-renovate-balena-jellyfish-logger-2-x-02901d29170de27752eaff40f95ebc7cd373888f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.25-renovate-balena-jellyfish-logger-2-x-02901d29170de27752eaff40f95ebc7cd373888f","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"721b3b030af53e2a59f0461d6b217c9546ef69d0","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.25-renovate-balena-jellyfish-logger-2-x-02901d29170de27752eaff40f95ebc7cd373888f.tgz","fileCount":99,"integrity":"sha512-uu5x+gGBmF4pJNM0s/6QZbccNVpRbhCYzGD5nc1BgI0k/noInb1r/7UEbjdDX9VYi8hnO3HQM1zkliIJneTlcA==","signatures":[{"sig":"MEUCIHPtlafQdVufoucpbdcfHQKA5BLpK/Dpv0323DltGKs0AiEA8nGlsh3S0MF9jOU+ujEPsHHODQfxstycndg0mzWydTA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":668140,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgZF8mCRA9TVsSAnZWagAA32oP/AjCO/3yh+vTfspNAxI1\nMiGW1ErSUam/7ZaD3L49JrX3pPYHTpgPWL8qkGMcdq+oQbvOodLJCB9dnonD\nzLSrgH0hitqdmqL8V4oyDpFi9UiV+jHPBz15+Qbp73mUo5Qey8jlM+uuEgia\nFmAdrZn9qAWfqcgcT1bnpPBfgnTP8veQa4SjA+ecNma70c868SJcjfR7bawH\nyFWFj/Q+g1ntKVhQezkSTz0XoI+SIBcTjX2HGvnFu4OWBgzjby2ZtM/nar6O\nw5CnPvFfsUMwygPF3sQbfrnJKAGOCvPO0YjMlghWmAyKaPzNoPgOV7iyftrS\nbTlmOiwGOcKXRdFm7nE2jk5IS+X+L+qavoXMns3wMjjCsb83mfqUZRpp89Jc\nBdPNoA646oXVALabA7knSTSu7dxhCPKvJXMGCmcRjdSpcMPyq75rrmJOXKAn\nPwfrPYMpbMk9jvgJui1hR6KikYGsgwXLScXdLJdgct3qvnIXk54xa3NUCjX1\ny3iukUlm9yp0ccrfjG/5HIoMBGjWNNQo8w/mM4y47RPT2geBZ3BQAIvX0pWC\nBNgQ7UHshZP8W+4V4f0F2SQfrWoBUowMcJrZKh5+n+u6hDBTsXlie5ndpx4t\nsjSBhz/rDZrYF3bL/BmuO5qyoA3k9d3K85Eb1oOTjlJPh0tZT8u1lKrQTaW+\nEe6X\r\n=CU9K\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"02901d29170de27752eaff40f95ebc7cd373888f","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.0.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.80","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.3","@balena/jellyfish-logger":"^2.1.16","@balena/jellyfish-metrics":"^1.0.22","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.2.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.116","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.25-renovate-balena-jellyfish-logger-2-x-02901d29170de27752eaff40f95ebc7cd373888f_1617190694064_0.9344747267863058","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.25-renovate-non-major-c3610bda2e2f1f979e42c5f2ff1ec10f8f9b7180":{"name":"@balena/jellyfish-core","version":"2.12.25-renovate-non-major-c3610bda2e2f1f979e42c5f2ff1ec10f8f9b7180","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.25-renovate-non-major-c3610bda2e2f1f979e42c5f2ff1ec10f8f9b7180","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"bc214c15ca0595c3aa22e6ec4f00f0e68a571a50","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.25-renovate-non-major-c3610bda2e2f1f979e42c5f2ff1ec10f8f9b7180.tgz","fileCount":99,"integrity":"sha512-694dK0hmd52AbVYdpHSx0+KJgIHHim26JxsO8IhQF6wS0iegQmMTqqG94FxH7WKMrcO02RHioPETh1CYxu0FAg==","signatures":[{"sig":"MEQCIGiVRuU+dK8tJeqjrAIWOHq1zow0O53X4JHTQs6TSQLEAiBPB82E6y2s6jwz1EkuovP/0VidxR2FgA4DK7s3rolWMg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":668092,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgZQE6CRA9TVsSAnZWagAAaGMP/2aOPqDFz3dPbxTwQNNy\nCqoaaaNErwWPuk0WV1SUjBJd4XExPzjykfTH/MgTdnBz63MGlPaUTf8zMKbL\noDU7fT7Uqb7TIzKPlXwvNRrRuAthopafID+mnC7ImhH8V6eix7UhUfPMh8du\nS7YW/aHJTi5neh4L9NcmCG0KhxApyHVMv5n6a7TSVuEFPcOsNnDXqXmflCWd\nwbSBY6+KQ6H4QVbf7o1Qqhz3lgANIZwUWi1rK07+r17tJTjn80TOQ6SL/trV\nJKrG0TUhuN3Qt2noW9sem8kreIIAPHpjwJWxn79b1F2a3Oxm96OaukveL4n4\neYv6UCTJE2YPETKx5zRwVWUzSfY9V33IEtTFQpkCalgHbQFCWRMw+oexwN8u\nYMh3jq4eO8JBr/vxnFgvzCA09O+bkfZakM5Go2ZeffXcKLieo0QRVAHyZOSv\nls5GEka5JMWcVEJIhrwgJ54D/vu5EbByaA2vjzRVu8Q34oDI2L1zILxxUOUN\nsl+hqPRcrVlaPZ8ZFeSMPBKRD6q33fl84Mrc/utQ3q+3eBzTFwfFy6ktQYK8\n5pV+LeMKGuCBW6vvHKxUajNgIoyOb6O5G/eER5rkD+6adNdl0x18+HpvKZH8\n4OWjVY1R2EGCezh/6vytl6TIMb2zqAzbJVN3LuI+aCUIIyIPQ/hqtgL7/1B5\nbILa\r\n=uU+j\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c3610bda2e2f1f979e42c5f2ff1ec10f8f9b7180","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.80","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.3","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^1.0.22","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.2.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.116","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.25-renovate-non-major-c3610bda2e2f1f979e42c5f2ff1ec10f8f9b7180_1617232186222_0.3027982484867662","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.25":{"name":"@balena/jellyfish-core","version":"2.12.25","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.25","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e207b275b5036cd1c70d0f215d78542925504ecc","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.25.tgz","fileCount":99,"integrity":"sha512-d5rKuXesMP6yOqSbEWXsJGpeNZtfzv8hLMFrDLSVD6VJzIm4W7hqm2Lngv12tsXzZqhdQYz2eMp7spTwc9eJMg==","signatures":[{"sig":"MEYCIQD8mIt6AFav55fYJSYrHFULITxQbbnbzTlEc5UcGNn/BQIhALfdunTNRRbfw+6t0/mcrIJhAPuomqKprabxQUt1NHss","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":668032,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgZQN6CRA9TVsSAnZWagAAgzQQAJnXGbtHUY0oUgZqHGbO\n45Dj0sCfYMoUqlaXrSUbPMkxTy4Yg52mlzhQcShPJz2Wt9pR966MHF8EpWM5\nNQbm1Vmn8I3FmLETsA5ZacE5arS+NwiXhB0+W4CLo1HsJpyizNhMWB+HwOyY\nk9VlGoi4RWT1tvGE5XfftGuK0BlaazPFXf9WbhQf/pgnrKxyx68xpNgQr/Pc\nmJ2Td2IRe16dyPVJRYsIi95uNbaPl8P1KjtoEvqiQiyy7KTU2LtBFS7Hz93l\nOX2gz4ztU5RC2bFe4f2c2n9pKL8Ml+pLa+9J6ZuKUNbWjXDH9rhyC6G6zNeS\nZRln3W6JwLw6zG3ylUQWFMX0u4R7lTvXCuoy7fxHYmnKBx1jMi2xwZjGq2lE\n5Jqx0bMdmDI6upxFXpFAVet67SPORpUtRo+VFV6mADlDF+bUmfokRiLbR57q\nEzkrMg73Lw6r3CB7q+FLTTmjMzwdN5IdackhwR+LMMrkSG4CA/UM0cbG1TH7\n7NjxrFS8NLuib0FPqSLTAMnaEXsrxsb+N517jXjIfAUykAZ5jfyorMU+ze9M\npdlZ/9s8NUIhqR7TijNWXZ+W96JwZWWw2K06HkJutzF53RntKZcZy7uT/8Hw\ng2951k3SISKMXjucNrlYDQDpAQTvKVJIAyXFNn2tT1ulktEndFBsv1j8b8w/\nI7G/\r\n=r89H\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"70106cd387c2613bdc7f2b1f3badb62448014748","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.80","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.3","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^1.0.22","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.2.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.116","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.25_1617232761878_0.9917751115277298","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.25-renovate-balena-jellyfish-environment-4-x-bbea043098c6926b1023ff093546d31ee025dd86":{"name":"@balena/jellyfish-core","version":"2.12.25-renovate-balena-jellyfish-environment-4-x-bbea043098c6926b1023ff093546d31ee025dd86","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.25-renovate-balena-jellyfish-environment-4-x-bbea043098c6926b1023ff093546d31ee025dd86","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9330065fa74ca22d3a5fe10c17d1402ebbd10907","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.25-renovate-balena-jellyfish-environment-4-x-bbea043098c6926b1023ff093546d31ee025dd86.tgz","fileCount":99,"integrity":"sha512-Pq4POqhnVZcWhXs686B6O8nq2xOmaRzyY7tJfuC12XFq3WjdbfbZH2+8Lf4e8JL+Cc1cA+Ml+lsJb7xzVHeRzw==","signatures":[{"sig":"MEQCIHBuY1wKAqM89N2wxciSpuCEw7PSTkXaj9LDFMN+XeWdAiBqpuUs3Ej+EqUt3zjTBQ33psSTGy1Of3/sphzYLuHcFQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":668451,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgZQQiCRA9TVsSAnZWagAAnZ8P/2rZDLIKmnb5VOgAfBen\nYRLzJJ7H8bnIjlQed6ZuoJ2DxBre2m7AUgG4R3IP/YZj6C2KZewO/CeG1mfd\nBQgmFVEe82zrwWy74F3XM5FZpvYEuaUmXEjfd/EpyIcyoMzP5VC9j+jFeW9W\nrm5+R9W/su2nkgrzeMG9U/RrqDiICh5hI91wES4THBJ8qNVTDmA1T/psmSOR\nYnpmyTudGm8v+Rn0uQRY3nreKF6n/L8ERY6lN345xGsj6DyzJycKWcOkzRvC\nAyCI2LB5AwVy3/tRRNN4774zR2VlrkGPy8+01SGHBAYpui6VZaXwPI78IE0T\ngHigUuPd5hggSyyQQ1S1KsdVeajOjqGUhUYz7y+xkY4o3uKsIHqnlDdKCKeB\nzAz0WuAZqw0jLTUf7wx354VYVFkSSDYJsQD9twuxZ6HLzdW0Y/mygfumNeXI\n/fSV43o/f5owXrkNfGyJavtHY01LkQvAx/6yZp2ZCPI8sZAG3RLGQThm4xmh\n6nfmE6FamNJQGRDPEuGqprPvtIm5KfSk/Ml8GDI+4j+xMfwxmbMbF41/WH9E\n3BDdb+ruh2GZxL21z8zqmccj34Wr0rtMqrS6i0ZToo1cXhbWxyw+VwJkOvCJ\ntTsrILxCZiF2fWSm4BwcMhusIVNQV1KQVYV5lN4fsSwHcBE1xJX/4/20Jw7z\nAfc+\r\n=B72C\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"bbea043098c6926b1023ff093546d31ee025dd86","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.80","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.3","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^1.0.22","@balena/jellyfish-environment":"^4.0.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.2.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.116","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.25-renovate-balena-jellyfish-environment-4-x-bbea043098c6926b1023ff093546d31ee025dd86_1617232929952_0.7282640245786389","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.26-renovate-balena-jellyfish-logger-2-x-9b396d6eed27016dda13dcb70ff86e25cb9a9de5":{"name":"@balena/jellyfish-core","version":"2.12.26-renovate-balena-jellyfish-logger-2-x-9b396d6eed27016dda13dcb70ff86e25cb9a9de5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.26-renovate-balena-jellyfish-logger-2-x-9b396d6eed27016dda13dcb70ff86e25cb9a9de5","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d5e26f0477cc35fdab24e04c8684665a9e79ff9f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.26-renovate-balena-jellyfish-logger-2-x-9b396d6eed27016dda13dcb70ff86e25cb9a9de5.tgz","fileCount":99,"integrity":"sha512-GvKs7qUXUEDDLXMY2g4JoSG9Cq+MoEl8VibJIraEM6HkTaeUluS+tD6ac6SLWjL4m9ELYnOITAfkoSFVuXea4A==","signatures":[{"sig":"MEQCICEG5umQFnEGNwRMiuA7YhMiA7REiNqnAVQS8bObYyQbAiAMhlkQGfYoejNzzHyrEhGNlIRDGzYt74yWec524DpKmA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":668525,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgZQRMCRA9TVsSAnZWagAACVAP/jqCzZbuLsdv/b0bZMfO\nSZHF7X80auDkLM6+6nrL0skwe5EQmmELFmwFpeHM6qijofZXP+kFQJqpIAeZ\nY2vZY8dZopo2rB7IcXc7XP5bV44nIzVW8Vfm/fIe8MgWO6WY5I8s4VpyLyfc\n914i3z4ydPb0/imP+DBQqnFBrYBH6NpGWnWbUTnrvuIczwIEKNN8HzIY19Cn\nFSRKLV1KoOIUcwtzFxX2qJdij7rOiaT0rTYEH7eyM6byu2UW8Ye2JwJymw4U\nZMiTPLb9QD+7MvT8uzdgDDDXHXbHcKdbpDSgtQSc/BxPaIu6EDFfpDDCrLTb\nmRIp8L5t+8qziTeMHuKcxbhp6/gedQ/9ybjWmgR5vY97qZQg8AXMBvZ+zOwC\nzjS1TZNUIw4yaXrTTEFAIn1YDT9ObeuWGCVxlX+KFyFqg0KZOmr8gTX6YLoL\nLAeB3+MBOGee1uAu7RAQqqzSthTlxrEPaeHSCqwvMiYg8IXpeoqXtfXN8mcQ\nXEH3qAwe5aJnM9sWGKmzIQj1F9rrz77TaZjBi13eHqLxoi718vfaibx6S0nV\nypfyksrgVttHopOeWVy7kZF1cXcLxqXfRAT959XiLrvopdHlqgi2Czm6oMes\nHuwbd2hgsewsjZeImffBRlieCTCRsUdjyfadi5zgJUhcYWTZFDOz5x16Mbf3\n+Tk9\r\n=QPxA\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"9b396d6eed27016dda13dcb70ff86e25cb9a9de5","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.80","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.3","@balena/jellyfish-logger":"^2.1.16","@balena/jellyfish-metrics":"^1.0.22","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.2.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.116","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.26-renovate-balena-jellyfish-logger-2-x-9b396d6eed27016dda13dcb70ff86e25cb9a9de5_1617232971728_0.9831323524409243","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.26-renovate-balena-jellyfish-environment-4-x-9630b1cc6d765258c3b3056ba2cc2231188ff59a":{"name":"@balena/jellyfish-core","version":"2.12.26-renovate-balena-jellyfish-environment-4-x-9630b1cc6d765258c3b3056ba2cc2231188ff59a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.26-renovate-balena-jellyfish-environment-4-x-9630b1cc6d765258c3b3056ba2cc2231188ff59a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d687710686e67f71269c56d7c2b22a03f13f5fe9","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.26-renovate-balena-jellyfish-environment-4-x-9630b1cc6d765258c3b3056ba2cc2231188ff59a.tgz","fileCount":99,"integrity":"sha512-Qu6mi/b9r3gEs50XIWn1KNtI9oL7DqmK7IMN/JmBjlgRzSD9fCFeE/Npbrn+XUkJ2rnsGmG5Nf+FsOYMXeNCjg==","signatures":[{"sig":"MEQCIEjr6el1nJpLP3HQakeo4Y1Z67iem0qEWIhOTORNslCmAiAg8eX8FtcvQOXdY80Csf+iF7vOWthvAyF1STeOhxDqTg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":668540,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgZQUhCRA9TVsSAnZWagAAAScP/2akVf1IXe/i3fRruMuO\n2FdIZVpjtip9GEa0R6TBsjXGm4EJUzSZtNcguOzJXRiPgBP4Zw9HMHuP0ScU\n8aTl3HrzyAHB1tofcc9MEnFuT6VCsZNUxu/ZrQ3lYEJEJRa1B6ZHUUIGOfu4\nh0ZSKMtKpdHs++2joChcyWnG6L/JKerD5bunoFWlOHjmjRoEkQx8W6Z2X9mo\ne/25OiUTn7RTkcL1AICQqcg6aZ5doW9bwNqDupvfTHcvYmfVqSlxgafJfKnh\nJ7eywzlprmh3y5ubyIYiVCpyjzL2EsgDH8PvnRppVKFi/WfEi3/JowzddBo7\njkzVyuroIhbNlQfV26nkUtpLJs2IERYgGhX5n/v9fdfKM6HbaZkFD5VU1pJ0\n8qi8pBJMbR06ToDg42lLpZfi1VHr5q3r8WtE69l+JxSnuAZqwGu2b3MdvGPQ\ns0WZpptTtrdj8T3U/x0/lzgJ3+MJuB3Mh8bXuqySE0pZbgaCPF3Eudsvoqht\nsmmpMikmjgFJsvAMZJV3/WfzlaVQsh8ipJAgXQ9JdeIxBbqtnhf4j1NN6+3E\nJ7unlbwHkhsGdkYH4csx59W/TW7I90vO51DjXVoUhmHugV2QCtbL8I3Lyexz\n3x4qadkJZYqaZSu96+zQSYmjXszq1tUeXKQnwDvE/Dn+0PbOn5RX/AJFh7st\nf6o8\r\n=tQmb\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"9630b1cc6d765258c3b3056ba2cc2231188ff59a","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.80","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.3","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^1.0.22","@balena/jellyfish-environment":"^4.0.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.2.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.116","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.26-renovate-balena-jellyfish-environment-4-x-9630b1cc6d765258c3b3056ba2cc2231188ff59a_1617233184557_0.5134946643914584","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.26-renovate-non-major-5fc5d23a4b768bb8bf5565b2ab10147a2ea4dcb7":{"name":"@balena/jellyfish-core","version":"2.12.26-renovate-non-major-5fc5d23a4b768bb8bf5565b2ab10147a2ea4dcb7","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.26-renovate-non-major-5fc5d23a4b768bb8bf5565b2ab10147a2ea4dcb7","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"202456032706c934f1c218f890004b7b3bcf9624","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.26-renovate-non-major-5fc5d23a4b768bb8bf5565b2ab10147a2ea4dcb7.tgz","fileCount":99,"integrity":"sha512-DbU5deQd+v6SzGxa+vruHySjfFoEIWHmkSJsxgeA/eKGayvti1HrNEmbtS8p/94tY06XQqc2OTLEMa/F7YyasA==","signatures":[{"sig":"MEQCIASO78l+eUrGJre6BG+h6C11apJQthrNfbQgHz3X0aIFAiAZ0+A8JsiF+CPfFUV0zX2DKyaag/X11Q15wvkAZLwVig==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":668519,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgZSeiCRA9TVsSAnZWagAALsgP/Rbr2mV+4OFKD1LUD4yf\nOU7Ks3c+bdXsQlB2bSl4S6u2sSolk9HhVj33yy+SQRzRaBmUhSO3iJc6bBwk\nctRPfBmxOC+Zp+Z9VLcTWToQPdIulsmX0ft0poQp6pMcRdkY5L1JkPCmqJuv\nhQBbiurKkYqFlcUWORcq16x9Azl3DWx4ajMPQSXwShl9IRXskO+6XtxZ4k3d\nCWB37dE5UqBO+6dMQMwGeuIiHvUAXmH44mnUQHdnJvKts4hL+Vr7PPAzreiQ\nO/+pfL33OQc7K3aWe5nHzlEEgQLtvcj4nwF6LZ672FlpCuxgAATbXKZR39lP\nTeFNhpUfMzMOaPSHriBDtL2Gg+MVU7u5gBv2hkNKFiD07JO4LsGhZvLosEkL\nKCAPjMsSRsr/SA/FR1fVeXbdDeKP8NqaY3UTj24mZqBwHm200m/ENVeVBaLC\nu9TxqGl6Ck81PPByrXk1bV3TqRwZDvyiO078q4HaZKnzGVniOeovMMSiyjEI\niPqKeWudLFyI/zVWwPksqD/rkwL7//xdVY5Ww1LwX1CQbb+7WSdiJHcekdWI\ne7IuPUFt/l9qqiBmXmQTP2pYgI79BMzskN0G+7HD+hIjLZ+TOLfkukXNuYRH\nOyiLwyX+noj8Rt+PUzdePzQ5ATL5MYLmNlE8zSMj/NXt/k04Z4n4+mHFfjDg\nPSgA\r\n=FUbg\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"5fc5d23a4b768bb8bf5565b2ab10147a2ea4dcb7","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.80","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.3","@balena/jellyfish-logger":"^1.0.57","@balena/jellyfish-metrics":"^1.0.23","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.2.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.116","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.26-renovate-non-major-5fc5d23a4b768bb8bf5565b2ab10147a2ea4dcb7_1617242018034_0.6403759020539679","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.26":{"name":"@balena/jellyfish-core","version":"2.12.26","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.26","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"86e8af779e49a0c9c3b04962e1d7bcd19f97f30e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.26.tgz","fileCount":99,"integrity":"sha512-zuA4yAE85M9voBjpNDrYGUWABy5aH6e1aMtSwmWS/fekaojcV7sW0XAk0h4ZKOy1D/Je/8LU1lxCF+greKUJ8A==","signatures":[{"sig":"MEYCIQCzgw6PkOdRCOxvt3UB1jXOD0Mjy4FIJEnWv8z2pwWlVgIhAJs3XsuY6SwDqU+ciiJhovCk5OgTHkcaI1H40jM561pS","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":668447,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgZSfHCRA9TVsSAnZWagAAZy8QAIyUDbKMRUt7fwMpSnRd\nvWf69Vbh0THKgjl28AsmDFZIFb/V+GHEbW3ndpO1cbJwzQwtc+VhabFLzSGt\n7lZfJECm7VqUtjCxvUYq2pvBamUgwHOdIv4/fZl2bk88hF0yc7L8iLGGcIUm\nYMVL3lEmEkjTJ9noj4lleIyYNN9/RXDgi6htDAd4MdBosAI6QXFnj575aXEA\niBfs3GmgZakCdcMFCEjyyQSgaAZcIe676/2S6Qn8M2Qa+WwCxL/9YgmQHp5u\nL+dbox8rh+jjK2Bq3ZSJDKnvNn4WRlxKNOE6n6JrKf1Npcl3ItUr9NISUWqF\na9ALSf2BgNcT7DvXFm1BNDxS3iuq1WDpzontiXtiiA9TmFb16d7w1aFQdHIT\nSjK7pFC2ba8Eyk63h8pZcjgUSTXOUM5ghpvgQgU1zC09Wo6DNK3zMPctyzWQ\nOf4VvrkYF3tBgzalxNIE3n7GasmX+0q2k1fyQ5aznkaJYtTEAdm/aSJTWRik\nKnQTxU7Gdwke3c6Ev0tUYV0cY12hoCnG8tHId7bem2LbuONL6FnVDvDAa3YT\neyTMs9uGL7fBENu46p+IcQKJJJfRx6srQonlqOgXJErpW+uv4BVgIx2EImv5\n/b3dEdpCN0ZlnQ59Z/Zd9wzF8sE28LvMXsebYitp5r7pTuIM90Uscl9cxz5m\nsK5P\r\n=pjdo\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"cee822d4497de01147a34f871005654ebec68d7d","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.80","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.3","@balena/jellyfish-logger":"^2.1.16","@balena/jellyfish-metrics":"^1.0.22","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.2.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.116","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.26_1617242055387_0.24127426090850834","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.27-renovate-non-major-e5d0839102688009009cca0e28607105d1aa7e06":{"name":"@balena/jellyfish-core","version":"2.12.27-renovate-non-major-e5d0839102688009009cca0e28607105d1aa7e06","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.27-renovate-non-major-e5d0839102688009009cca0e28607105d1aa7e06","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1754ef7f6fef5261f6a25316e7611ff4bae43df4","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.27-renovate-non-major-e5d0839102688009009cca0e28607105d1aa7e06.tgz","fileCount":99,"integrity":"sha512-hYOCCMvWGW9UZpks/vhCfyZGNuJRPXRV+5btvSzn9ZkQCn7PB7k7lbgKJT1gy6F7+q4KB4/6I8nnBWHRxAEUlw==","signatures":[{"sig":"MEUCIQCzn8Zo8cmMfPz5vzi9T9kqGJguAdmcXZT+zD4mFVeLDgIgavBf+P198qHQx8rSoyBafXwkMogd4WIrCmdyGEWUzHo=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":668934,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgZSi8CRA9TVsSAnZWagAAZAwQAI2g2uWTx2mjccn4pCdC\nAu0m15mz274nkvTkvguIlm9RHCTzxFm8Ug0CnkR4pexM7ex988ug+W3QR9uN\nzLdmWQnYR7DHyPTdLToaxW+/degyac0pH7KgYjVGF7yA2kENMKmupWHcpUag\nIIMNSYXz/a55rQJhbikHCJmJO1dzHeNwwvhW7cVe0irDt2SDMvZ3v79abv0Y\nBlwnSf7lqp+YtwTQv/MtFk+yp8VYtvcfltiEyX7HCNCYnQHu3CA9a8WmX/O2\nOIPCihVgkBwBcFVPn7eLEkZTh9prkciUeEp5MBEDsSdtCT91MuWObPx45LUE\nkopCcpOC6bdYiTuXkHXTywkWdUyCA26SOUeaBDiKBjRRBE/k1vgMJVca8KLN\nTZcYQ32soPc2OoOiXJw4J4PcbDCoFp6knnDUeeLsp8IQQX+vn5ajvG1ZF4RL\nPqqprqRTyHZXh/+jvIE6kHvEisPB1RSQOBr7a4lxGjWrYTzV3i7K6sNAIJeZ\nE908FBo9iY1M7MJLzworEJ4cPmVGWiX+COgclhjAcP6IjKTs0c5u2faO6p4N\nqdN38wAeJia7uXJ+Xl0j4qUEYcNHOXAlmjsv7gdnQJaE3lC2Kmit4LAHmXhh\n/mi/nCyJaD4occLshTEpdp6FU/q7OxPrBpyioktyzqA45DjOuDccmXuqn3u3\n5Xdw\r\n=KL4m\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e5d0839102688009009cca0e28607105d1aa7e06","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.80","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.3","@balena/jellyfish-logger":"^2.1.16","@balena/jellyfish-metrics":"^1.0.23","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.2.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.116","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.27-renovate-non-major-e5d0839102688009009cca0e28607105d1aa7e06_1617242299812_0.36331938869977565","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.27-renovate-balena-jellyfish-environment-4-x-6e4efb6269bcec96d0efe09b4f0dd75d102f6279":{"name":"@balena/jellyfish-core","version":"2.12.27-renovate-balena-jellyfish-environment-4-x-6e4efb6269bcec96d0efe09b4f0dd75d102f6279","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.27-renovate-balena-jellyfish-environment-4-x-6e4efb6269bcec96d0efe09b4f0dd75d102f6279","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"7e163f90303af69ade899c8e3b0eee51c9143569","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.27-renovate-balena-jellyfish-environment-4-x-6e4efb6269bcec96d0efe09b4f0dd75d102f6279.tgz","fileCount":99,"integrity":"sha512-l8uBV8/NM5v3Y0r5B1MfT8UtKo1qwAWpuzdBp4P/gpyy1EWXl5HX7f43wHUyoVUnXuImYOki5dYX79SuUMaiuA==","signatures":[{"sig":"MEUCIEemRbbjr5MZFEe3gQaSzDS3Up00LwRcanrX+yVRgCEnAiEAlOLpa0AVA9/ESczfKfgkIANZ8EINiRVRA/nZZkGhlvI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":668955,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgZSkpCRA9TVsSAnZWagAAYkkP/A3Vgnu1XOszX2T+r4YC\nYQlW7zQh/FToaA653WtiMclPceHu/PeIkDCcqKyMU88IqrS1WT5P8Avm2Yof\nlp1QgAKRm8s/AM1d6LxPOt5lzmeKXKLJyCEmyO79a6hg2mf8AI+uhixZb8gs\ncvwlOh14EBBgFdx20uAdyERFIyNjhasZ/0UZ7LPTiuJJvJdozF9/TwvwdB31\n9Jb4QPgtuNGPe+4Ls1ZqT5DcImK9osYJr3NJd9Kbhxg0tL9uSkitOGij6HG1\nIPorEHVIJPH7COF+BUMa0Xz9+yUgVXUMV8agtNnoTGTniQPPMT9LCRI4+tbF\nbcz4hQsOGAAOzUIIIfYiZfsfZO89VZ/IwfngT6ITlq242rYruT0loyneF+Dx\ncj+t9PaWF90aWNdeqUTod47RkU2ic8XwWzpICR5bZYmzRJVRUBt0xqyfpfYI\nPDJfrpjIw5h7bmN+BNeAuHhOf/Lb47HhxPQie1ErQ8T5YsAV9g7wdg3ZuWb8\nlD1Vft/asIdM6kmTU0FglHTXHlx7Hmm0iIJH7T//1sOSo5FZe4CeKexnyHTR\ngugQmJvecnvgTaqooqvKJn18Fa6uV+Fz8A1gJldhgGucBtgn5KFLrRphsqVX\n/qPrLb6F0H1SGc6JvXn6TmntV0GsXrQOUgsETOYx1v/RJMHhPmObAQBwvJwb\nvhvf\r\n=qZRp\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6e4efb6269bcec96d0efe09b4f0dd75d102f6279","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.80","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.3","@balena/jellyfish-logger":"^2.1.16","@balena/jellyfish-metrics":"^1.0.22","@balena/jellyfish-environment":"^4.0.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.2.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.116","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.27-renovate-balena-jellyfish-environment-4-x-6e4efb6269bcec96d0efe09b4f0dd75d102f6279_1617242409171_0.2620280529659822","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.27":{"name":"@balena/jellyfish-core","version":"2.12.27","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.27","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"56515a0e199207236b1ec4ba1470bc9c118f6bd8","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.27.tgz","fileCount":99,"integrity":"sha512-5k5tKOKYtPoTIJrzhsTJ+qS/BIyxCAVJ6U5WhwnScrwiBCIMyaEPaHTOHAKUgy7E/U0bXrD9oMCBQ8QSJzCViA==","signatures":[{"sig":"MEQCICyw+UnVH/BsoyP1fZV2OUyZ9hz7HKfcP3Li54vuze61AiBc3sK9RA8lInIHrJeJB0lmpDcOiwuSfBFhrUSFG7sg5Q==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":668874,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgZSwJCRA9TVsSAnZWagAAx4kQAJLIKBjyJ2qTcvYEUPr+\nTlCcFVFttSeCh1xqn/rxBhmSJsretvqvESuQmaL25WtCCZXkYgcJ7EQuHhbc\njDGsxh1aWgVxxPifTBTiH+qWMxonITUH35t6MqAGunWDxiZHKWdM9+OuvqGP\nd7s+BC5qQa/EYogRwEXjSApR2PmQTknZFxm6ocOqSSHpXjCJJz7+pWeHufkT\nfgb92ixF45BNi+4WmHSAIsNNjKKAeyHZ6q59+b4BTIgkUwiWg+U2Fhh1vygF\nDofev3zLQg3uSQM6JTSTBiHCrzzc1EhzHrj/Lxu77vO3iovtRFXfYgOsTnfy\nye1fDMK//ouSaco7/3taGKeOGg0cNC2nnereoUqJUQc96ZnbuLU7e9ut4OSA\nDeh3KAsoab/Sfj1ZI6zM+5IKjaRYd46PpvJipehIwx2A2xl/gJ5bdCrg5qea\nvWV52Ox/cIc7YmeozHjRLiuh7/HTjvN8RI61wuKGgSKT/rPunW/5TKiJSsJ/\nOGJ5HNo6ZlOptsiLPQwotCV4Ur4c00sqKQRh1QhLa74TIW00nw0w/FKiBbq5\nPy+HGaEregewshOKOAcsKVUIs/FjEDRCBiRCbxYE8EhzFB4cgWriupy1zkNE\nf9cyNI2EOgi7IamA9hThmvBvN+rPjofdtJKZnOlVHYCQcf6Pt2YTBH0GN4M9\nzoYy\r\n=niJQ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"1f41f460badc4f95cdd0a9610c26c23808b3ba9e","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.80","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.3","@balena/jellyfish-logger":"^2.1.16","@balena/jellyfish-metrics":"^1.0.23","@balena/jellyfish-environment":"^3.0.1"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.2.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.116","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.27_1617243144681_0.8546631760648882","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.28-renovate-balena-jellyfish-environment-4-x-3970c9dbf0b823a40ba2e8401dd605c57d46a706":{"name":"@balena/jellyfish-core","version":"2.12.28-renovate-balena-jellyfish-environment-4-x-3970c9dbf0b823a40ba2e8401dd605c57d46a706","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.28-renovate-balena-jellyfish-environment-4-x-3970c9dbf0b823a40ba2e8401dd605c57d46a706","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"96d8f154c1b8c62a1c41f7bd18b036675abf4d76","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.28-renovate-balena-jellyfish-environment-4-x-3970c9dbf0b823a40ba2e8401dd605c57d46a706.tgz","fileCount":99,"integrity":"sha512-Vs9SCIAdv900lhFe/xoZRikcbEk4ltnS84xIluOq3+jJeQzBbFywBr8KZqDmwnIlm9SujOPZCEd5tY81NKtriA==","signatures":[{"sig":"MEUCIQDdZAcVfexq5cXYWx0IFXbJO/WLZy+UmtK1wbUTwVKpqAIgaoaKg0elxKLLAcdeX8IhF35h60oMCthIR81s26hYwRc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":669382,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgZSz/CRA9TVsSAnZWagAA4v0P/idxtSWzhkCuvXLREa23\ntQrR3Sx5roIcMUCZyrorDduQyvaE8CwsgGrShCZoroRyMyZIszg+lCD+ANGa\nW+OGDeqhIufNJ1/Z5UXmy4jnS8+ZZl+fC57fkzXeGxP3IZm1NoMbxXfIqnyM\nwodLYTAB5pBo8DpBMhri0KwCESdeHOxSsrE+dUd0Yu1ys8aiwmfgPg7WFxcP\nU2Sf+FxXQId7W3ZMKo28nfPl8TYgXajxD+11aITaLUQdfknr5/xoI+5mZRgQ\n9KCAUEg9xp7HrzFWHrrJVeRJVl09PUwlgdDCFrP+OmwEY/FnzBO/K9zwBxCw\nEJRdrfAmu1wjJ07AfT5r/nRhu6M02bCPX/FHKkTesty7WuUBzQtqeNpQrfz8\nRBeLp9YRFXLxUcxd7y1FxYhUvimQ4F0mydC08DQpSLTd3LXn2w8fTOP0+GVd\nPM2suu5l6UJrI4iqs7Nxih+IJipx8Ud1NYNjf3/J+nBlKBUMSvSVwQs214MU\n3TrxBKdhHOo8usxF+2Af200zszqXJDZ8OiE1meB0M2NSLHfyHR778YSPPHaK\nSatGPfOuOTl1+0dDWDiAdilPa++41nFlSQVm3Eh9E3U08NSO/jUiDEUbj5Zn\nXN3TaAgEF2yHfi5155fKRe980DoJDMZRtfg2x4Zy1CLI2AItlUyRjwEMVJWg\n4UE7\r\n=kZyn\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"3970c9dbf0b823a40ba2e8401dd605c57d46a706","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.80","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.3","@balena/jellyfish-logger":"^2.1.16","@balena/jellyfish-metrics":"^1.0.23","@balena/jellyfish-environment":"^4.0.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.2.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.116","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.28-renovate-balena-jellyfish-environment-4-x-3970c9dbf0b823a40ba2e8401dd605c57d46a706_1617243391420_0.14380389004819505","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.28-renovate-balena-jellyfish-environment-4-x-322c8c1c92c63dcbc2778cdd8e56783631bd2f22":{"name":"@balena/jellyfish-core","version":"2.12.28-renovate-balena-jellyfish-environment-4-x-322c8c1c92c63dcbc2778cdd8e56783631bd2f22","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.28-renovate-balena-jellyfish-environment-4-x-322c8c1c92c63dcbc2778cdd8e56783631bd2f22","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0395e0998542a130a20846ab00fe221e1d494015","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.28-renovate-balena-jellyfish-environment-4-x-322c8c1c92c63dcbc2778cdd8e56783631bd2f22.tgz","fileCount":99,"integrity":"sha512-gfZiYfR2X0YOd+BKWrkUtUcxz3cg5OqUVUJyk2OpKYfOcVGQGsJ0F7RNwXhADje8R3u5zNuc6IBi+96ykFnXug==","signatures":[{"sig":"MEUCIQCrCSbH9R7mGPr47299+LBvGj+834R04W5sJR2UZbGv/QIgPj8P5ZuUucsNpqngOYXj/p7ondfk0NDAJThkhRzmO6c=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":669871,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgZTy1CRA9TVsSAnZWagAAWtYP/3SkYJsPlXqyqApxo3p3\nDNlx5gMbplrGDPq2jGjWEFc5mE7oMPgHDQU0ezS9MktMPAwy5Y6uIIKiKKyN\nFTLiQPJtSNuMwEkLbM40B1qkEYVt5gsYkI4jePYxoQvWIgj8x80P8S8aZ6z5\nE0U3ujAsRUQpEOtm2M6MPZke3WHoWi1rpgpunRUY8tzzhtuJ7ZZ3iHBlJxKx\nPMsuKhxGug21bIP61P9l8e/YBQTYaHaPdhWmX6vI48c+KfN9PI+173icg/Tg\n+itQErw71T9oyLBppFZ025MHE8gE7EQSEF9Qg/yQuwCML6rkAP2u9QYG1FP0\nmSlVEEi4U7w36X+kWEQPJmdwM6yOX0ITZDXP0YO/jkv21e60e2KmiBSXzk0L\n6q5uSpDCEyVLb29TpaUtJta5427xxpglE7XFeFSntI5T8aYULFlrOID6ySYk\nW6wvzoX6xrzkb9gw87SWuZpM1RKITBKq7icIP+La3QGxLzKPTIeYLiwl7SUV\nJ6iFQoSLEypKB4Hzw/6Z7mMenAGxO+dM3cCkYllIyr/Z7KjTcvr2ka3q8OP/\nvhnXzUXporR3PL1TEo+K4bGF8I4GvcSFnzcN1UsRBPeC16FeH302oVK/ous9\n5pAHvVA48xfz04WFNmQVKNzAUDPq4o+YW5RU/m9CZQHBvRtAqBeWAAh1sfZL\nFwHs\r\n=9ZWS\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"322c8c1c92c63dcbc2778cdd8e56783631bd2f22","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.80","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.3","@balena/jellyfish-logger":"^2.1.16","@balena/jellyfish-metrics":"^1.0.23","@balena/jellyfish-environment":"^4.0.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.2.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.116","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.28-renovate-balena-jellyfish-environment-4-x-322c8c1c92c63dcbc2778cdd8e56783631bd2f22_1617247413476_0.5585134272642738","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.28":{"name":"@balena/jellyfish-core","version":"2.12.28","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.28","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"cc490d5ab7129ecd326eb75448b8017cb3b2728f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.28.tgz","fileCount":99,"integrity":"sha512-wLiSr2y92lt1Z1mtWJdhxL1YPBeErK4BSYiIjHluvddDQwpfxb39SHEEDrQRe9+cTDEBKLRRBTHs26TiyfWseg==","signatures":[{"sig":"MEUCIE5FYFo0W2vegwI2bioNOMjacC1IlXA5cDLCTtx32Sk1AiEAiERvK+FIozWT32ekIMFblhuZGAzW0zNyGvSzN3xN2BE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":669788,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgZUCLCRA9TVsSAnZWagAAN9sP/06KgqZqcCdBLnAkNh+q\nNxhQQu0I+79PpTZqKbdZW4WTOV0uX9Foah/0vHZE66xgHtJ4dD9Jbg6VVnwG\nEgpopf9DUQ75Exy4nIFR7WVJ1QxlN1zQy+JfZDW2LER9Y77ymIBYayXV3TnK\n06QGYWALzGuk/7gxUb1GwTophD8d0zNkMQAtfeonSisuHrFqt/4bm8akrP/Q\nncNZdBuDjlNKI93X9n1XqUZIwaLmA9BHuGg+IRzdSLo7nfmUo3ST0JrEvUSp\nGLUJSSZ+pWNxohSovtSHRH8TU90aBFvq6PowaU/ECGnl8EzANvdn1hFoPzP3\nsKSLGkpSMFXCQqcPXzVXu799PHio1g7NrRmxE7IMhEb8E9f1kaUBt9QRqMGB\nz7r3OgUF/GyM2pR83eZz6GaKN0OkWkLyngHDVj4UrEMjr70pWXKSYIcyvQIP\nE0YywkToqJhp2PuVtizsgqeITmZ9b5KGImST/UNfserzQMSsHThNZEDNMA+f\nyXVyfZxq5TJpOYn5gimW02RVvEbHa4YcPAc64sHzs02OqChWZYT1HtWFFCrI\nT599qEwdKLmh0LC2xKcE4FIFXvVW9uJs+R4OvECt4VllcHRJv4rV4MEVJLt1\nrePOZCbwY9Vw6O8XOzFHtEqvJ6eJrZLt3cByX/4sqFt8xJVDnPWXy9pK1pkV\n6+Zh\r\n=5IOY\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0cf1e89b48925f1b70300a3fbd7957536a28cb18","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.80","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.3","@balena/jellyfish-logger":"^2.1.16","@balena/jellyfish-metrics":"^1.0.23","@balena/jellyfish-environment":"^4.0.6"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.2.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.116","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.28_1617248394610_0.30011868548425724","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.29-renovate-non-major-b67548e790040e680b6643afe5f6a9dd5fbf83bc":{"name":"@balena/jellyfish-core","version":"2.12.29-renovate-non-major-b67548e790040e680b6643afe5f6a9dd5fbf83bc","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.29-renovate-non-major-b67548e790040e680b6643afe5f6a9dd5fbf83bc","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"202e48ae40360f32dba7c4cf5332f782af7f762b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.29-renovate-non-major-b67548e790040e680b6643afe5f6a9dd5fbf83bc.tgz","fileCount":99,"integrity":"sha512-FVPI9M3VnnPWfkPVMn4BJ0t+c5Q5kXwg9vVNPMpWldIfTraycKVquwQwWL11AnY0Lv0zZYdETEcMQhl9xkiGmg==","signatures":[{"sig":"MEUCIQD6khoVPRsncU2NeWSDxSAIg80BmfKYCjHJVMfRLhpfPAIgWqDoANEuTAmjjW6R8UAkZ/7iZq+X1+YwzV7U2jS1Uzo=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":670273,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgZc/8CRA9TVsSAnZWagAA01sP/R5Q5MMuh+F9uW9/4fqu\ndbq2Urs1x0EpS2DX2DqKU2/ivSPpDveUp51DzNxrVuCu6Wbj9MnBWWEwbo07\nV0IYZtfYLC+TIQfxDfHeZqDt0fcFWdDxQ+SfjwTQKtXINGstv51ucSmWRzgI\nmuc25lUbqTTO2yi/rv6IgXYkXQV4YR3Y+cjVTV9/HZyKP+Uw6Z6pzODG3BLA\nGKQUPfysyY3fZq790tegbU5V+BWTAfNdQLR004ryYnsA5EL+2IwV2XUrglyf\n5nXzth12pfInKldhXsqk6LlSDgdvIgFl1LIo/08+O0Iigw+vsydtNwzv9Q+6\nNpca8G5vlUDgN+F5itzYLPdPkdkkOonta6m+aoZ94Ev3xHVVNAOQ4CyRZPgg\nK+8/k/Hgse/tEiA0r5ESX5CyEVmglyhZ1L9K+0KcfJOJzXJOt9hDRXuMnpoe\nWQsrYG4wvSyzhyjHGzMWGWJH39z/brKYOJrE93jcwyXPoYdpriGayHKFMDCQ\n5N7UC5fBy2EsE9eByUcPpwyAIO2fnbZnTg/5W1q6MbVzVgSiSHE2RrKOPp0Z\nweBdIhXVcTeyFO6tU+GXqNMOidxS8FjjpMoEPcGQYTNxNclMlxxWV3uaSkHi\n+9Bm9MRsEwQkPhuDrQMBD3jV0IYAdfofxLQ2UqS+/KbFC5pmBmeYZ5Vrp0Ki\nAugb\r\n=KRe0\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b67548e790040e680b6643afe5f6a9dd5fbf83bc","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.80","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.3","@balena/jellyfish-logger":"^2.1.17","@balena/jellyfish-metrics":"^1.0.23","@balena/jellyfish-environment":"^4.0.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.2.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.116","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.29-renovate-non-major-b67548e790040e680b6643afe5f6a9dd5fbf83bc_1617285115957_0.6013919632058229","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.29":{"name":"@balena/jellyfish-core","version":"2.12.29","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.29","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2c5bbc1f9f11c54309f8e40802aaf525c8453d9e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.29.tgz","fileCount":99,"integrity":"sha512-GGZTbNvWZtkeGVsZyI0fYaTzK+spE0sYaG7ceSv7La+Fuuptrfr6cabOWb2WAluSo/dRzxYQQAdKHhgUUYQPPw==","signatures":[{"sig":"MEUCIQCZpZZ15OuFYSR/Xbz9Yaesze1jC+Ov8q4GVWiMNCLXDgIgECx6FycDgWP/q+VdOWeIC+n2u2CjasHfZIpdrDtZFVk=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":670213,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgZdE8CRA9TVsSAnZWagAATkMP/AyWPYpwIMqf6Pn55Tqf\nDopR1zu/h3JQi3aHpz8JCkMGn1XWnE+64mjqXrilzv3GP0UjWhfpdlkmgKmz\nmM16TsTERnayR2dG2fEYm4CTXkJdp36NILwY80WpUr+IF1LWl6QShAwlpV2k\n0qVn9m+aTdaqjgVk1aAts337twLdh4+EbruPaZJLj7u0BvzjTCJX1eLI9Ozg\nGKPNg8aYaUmF8wOce0Q3Lx/n2XCAexZ6HWkLMRgdH6hdCdirpNJmrK9twwhW\nT15nhuULKSmv1dgc+EcDHeKMDQtvdtgvdom8bLBgJTMeE/fK3MpIiMOsUkll\nck3jPoRaffAyWtXLwcMCc5KcF7KsA+o5iRUDcQsc2emsgJFv9r60DJohlb5S\n8IBL/Syp50BwPju5iB1VbwzTCdrznywxDDtuL68/K9cUUpCFUCEvl5IOE7LE\nl116g1NuVrU7Xiye3oH7ffO5c1MWaQ61uxrRnBmy587CKb30TWarH+nJ223u\nSQQFF/dOnEqFGW6Ma6cx63jbVzi6vjc61eVWLWP/8vrVSyRDw6CMjEOZQYaH\nkXdFrduRao7Ql2dlNx68w1ZKzUR23bJeajPZ8ojHMmRXzx8HipjG3diN1Zwg\n4CtjcjlrLYduEs5zpyUvQxQ7BNxW8MH4bSPam2gt+h6jjeX0lLtFajizSj4Z\ndAgO\r\n=wa5i\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4dfa1e05f45aea366c014c892dfcac072414226d","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.80","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.3","@balena/jellyfish-logger":"^2.1.17","@balena/jellyfish-metrics":"^1.0.23","@balena/jellyfish-environment":"^4.0.6"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.2.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.116","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.29_1617285436031_0.4646640397816024","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.30-renovate-non-major-62b0c17969afc5a21429504177f53a478afcd20d":{"name":"@balena/jellyfish-core","version":"2.12.30-renovate-non-major-62b0c17969afc5a21429504177f53a478afcd20d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.30-renovate-non-major-62b0c17969afc5a21429504177f53a478afcd20d","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ae8574d496f9880ff70a191b1dc72d57559f4057","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.30-renovate-non-major-62b0c17969afc5a21429504177f53a478afcd20d.tgz","fileCount":99,"integrity":"sha512-4yqi/hgUkO/FaGAsbcHkALM/jARxJgRw5oh1JSEQWaZ1mfAwY5izK91uK04y4F1iteDbxcMaGP+F0LaEvZGIjQ==","signatures":[{"sig":"MEUCIBgI2xfJEj0a96c9dsZkv4DcaWcXhXPMxA9HL3MwMy5qAiEAwdJjszQQdruAdcCWAsn0uYthLXXztdxeoVCKpdzqqG4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":670700,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgZeYdCRA9TVsSAnZWagAAOqMP/i78Gm70XLNRKgGpAjuB\nDzUZ0KH4cKILgNDSqyU1OndIoShFHSRAMQQTP6795rJ9beS0cAJtDLp4oyEz\noLnqnlzkf5W87Hep1PkvHRCAR82aVYYRzjMKB1LJ/MnEmgqLr7AUJpbGV8yK\nAq9m+qE/JCxVtjOiQyX9UG+6gjyCid0J/UyWDj33AAc/AsDhB0p8Wa/GPdPi\n8sld26dXZDueNFH+TB++CfESmuZ7iLw+XF5JBfBW49H2gQUHT8yAfPNT5xxx\nJQrwVJvodhmiLhzvASMhqZEHwSQgbd2RTvjqVlP5uxHhva7Mm1RczH4yBA0E\neamN8hQDqZDbcmIAyeYF4DdQyDio0in4F1cH6PNp2I2x/g7ZYnVw030rvItI\nNjiP3yEXklcUqAP9scl2FSyqLniMZug7emAvljlwBkdqMhHhWb5B25hhz7NJ\nvGILsP5p0luBkeBYnauSbfZvlBPgH866Uer0q0XQA+zhY0whHfdKXrybnugL\no0I3EQRvZIvWEOUIfmwSe4ASN67RokNzDspj+0wb+6hBXFXyaGC6ePQ82Zw7\nUdHJtf4q2/1nmouIJOiKtjaHhTY0zI7ftRbBr9mr6CvO2g7ScJpwd31qYdGl\nPZJ92bbHrjuJkKZoQ/n36WeHQacl/UjS2Qqs4AS4ZSCbiZMPSTj7yCi88qx1\ni5nR\r\n=WeFA\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"62b0c17969afc5a21429504177f53a478afcd20d","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.80","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.3","@balena/jellyfish-logger":"^2.1.17","@balena/jellyfish-metrics":"^1.0.24","@balena/jellyfish-environment":"^4.0.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.2.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.116","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.30-renovate-non-major-62b0c17969afc5a21429504177f53a478afcd20d_1617290781086_0.21435074207522353","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.30":{"name":"@balena/jellyfish-core","version":"2.12.30","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.30","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"174e8e361af9df191cdb8ea25aa9be8392e3857c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.30.tgz","fileCount":99,"integrity":"sha512-btrq8yS33ABM8V8wnFSYdGEZc7ljMkHd830b68BG6Zm6oIp1Df3Qx/W3KzMSaqVMR5GZxtmxx9h9dF/cjPcwVQ==","signatures":[{"sig":"MEQCIA7CW8Vo+IQtPWsc8lJdzPLVux6+R+R4d2nWGTRCE92mAiA/Cp5LVXM7a78/SJMVI3kHuIR7UZItMUKdeWvoVKHEXQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":670640,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgZedgCRA9TVsSAnZWagAA2JMP/j4eCh92Y7UGCxwtxG3Z\nAE5VAwwbKUkrCB3h83KSJxipCVMIc/Rd9vrCijdspPH1+tFexhwGAbYUVxmJ\nILpebDhUaD9+JWcvvUgBx6sUw+DI6IuWBlZcUAWFQwpSAVGNNYnf9VsFRsmz\nxtE5m+Sd8eCoQRSfrwzpwLe3zB0dsvNW21qON7RW6gFRHNAsyUXMH2CA6L5U\nLqgPk2mQlvzbBvCZRYGv6fd6pwF1UbQ1A0/kdFIbyIJwnXpBH8BHO0CEfV7H\nMPTZKrURQk1Fwhnotiv6Nd7Tz8fkNgT4iL5RLs7TU3AFSyMY81jm4qHWla5o\nRGvt8yUYXeduAhxHKKyje8s4NNRFDBWxgwHnrXkPpY8J7J+fJX01pkD4PATt\nZ2npM9WWg5hQN71+9rzTFg40JXgWUd8cMLnMV5l+JbVvZ2qcBIdxXMA987le\nrJ66buDioHdRPTJDjUXsPDZdSoioSCY2IUN7p+MqF9bE4LfmhUSMKdGgKyku\na5fzCc1z/FnZPa1pAF8lw1Fg9v24HpEWTCesFFRHz7/Oalrr/HeH3YQxu1gi\ndNeJSBs/4p+5lX5QalkQVgT8IB8gGeOEQEn1xi4l7YbEIspsCAmRdoMoqYad\nQNcttR/S8D3psjLbIIXrYmaF97dsif9BYcyJt7KFPvlr+iF4ekC19jFqple+\nquTx\r\n=ovaG\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6e2c205f996429891d86589cfbc721c86e3c4ea4","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.80","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.3","@balena/jellyfish-logger":"^2.1.17","@balena/jellyfish-metrics":"^1.0.24","@balena/jellyfish-environment":"^4.0.6"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.2.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.116","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.30_1617291104382_0.24212093426863968","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.31-renovate-non-major-a4d614b18c9f545b07f35a3fb66a868b78ab8068":{"name":"@balena/jellyfish-core","version":"2.12.31-renovate-non-major-a4d614b18c9f545b07f35a3fb66a868b78ab8068","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.31-renovate-non-major-a4d614b18c9f545b07f35a3fb66a868b78ab8068","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"cf89bc850aa05653bea67b052ab2f0b1271d104e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.31-renovate-non-major-a4d614b18c9f545b07f35a3fb66a868b78ab8068.tgz","fileCount":99,"integrity":"sha512-VnwYOZT8EPXmYgizzPCpFAzH1QwGEK81wms+jKy6hhKeMJVvRnGKLMHly+zZ6CwjCOAwR6AO3CPNkLkUGTq0vQ==","signatures":[{"sig":"MEQCIHF+DmolIo23Rz94L+uOsymu4AtRMzbAP1SNdCn4Ah1GAiAejSC5kIJ2bI/TMLDPSzTYJizrgpGM5FVslUjzvJPTKg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":671127,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgZloJCRA9TVsSAnZWagAAo/4P/jJHb84K2XkMOlvv4WR9\nV9QapLZ1vRXkqUaLYZwBAeAOGPUAAaQkLJ36lxD6r/bKH74C/MuYqN/HUXEL\nalmiE8NPPt1F+6ArlxKf2WZtkqsHcON7KEXiGp7YN1Cpp7qYoOMU2+lgg0Q+\nzp3gIPwOY6zrzNF9F28afnDa9ZzbjyDhZFFoHB50beFMsSqKb+hmOgnAyKB9\nVAIrBejkXmMfqmzux1ZixdcY3FgbvqV3iG33uLEzVZOQXkZg5IEaMt/r9mOP\nfwl6rsK5fjnEC9xT/cwyBgP5wGpGClR4UufUbLDSvvP+icVKYcjmUwAEM35Q\nfkrzxqQT7OIWTWiLDzysaBfdFY19yxhCiBRjEgDLy6QbirXO8Px7BBd4mhsV\n1dOvvmZDXGlhToUBiCCAFewdVgLRExmp0juGH5Tx4gK0pUPBPYwkDZ3gzotC\nsSl4IIaWtw0ntNxKXRgREtZ3x/nb+H09X7RslsE6Sf9TdAaNjmGyT/Gn8dBG\nCicIEDRVs7B2hb99RFyPJ5/joMQlw+/VFMnUEZcgemOSFfj6Qdcw8pdC/qD4\n/0cc475DAi9j+6Rwr0Ff5Fe7y/4T6IACfBdsM9XHVdMMzhanF7/g1b+BuhVW\nwkhMNb/VOExbBMtM8x1bg64OSEc+Vshwu9AzCNhfA2NeSZnmlh7pHjfJq25x\nOTws\r\n=aR7k\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a4d614b18c9f545b07f35a3fb66a868b78ab8068","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.80","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.3","@balena/jellyfish-logger":"^2.1.17","@balena/jellyfish-metrics":"^1.0.25","@balena/jellyfish-environment":"^4.0.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.2.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.116","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.31-renovate-non-major-a4d614b18c9f545b07f35a3fb66a868b78ab8068_1617320456863_0.5226459792226521","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.31-renovate-non-major-9a38f67a1ead75e3e4e582445037b6b03a6301de":{"name":"@balena/jellyfish-core","version":"2.12.31-renovate-non-major-9a38f67a1ead75e3e4e582445037b6b03a6301de","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.31-renovate-non-major-9a38f67a1ead75e3e4e582445037b6b03a6301de","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a2d7ffdbd41bfd66d3bac0c8e56597a1e9e810ff","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.31-renovate-non-major-9a38f67a1ead75e3e4e582445037b6b03a6301de.tgz","fileCount":99,"integrity":"sha512-o4a/Muk4dDbF3R6u/4/sEPUWtBg2A7dfROcFYLjcpKVl2k/PE0OWe1zCq8Ch34IW5tH8b8kQKtCDix+yuJJs4Q==","signatures":[{"sig":"MEYCIQCpMfPoM4lb9nxKU/AlQZvsH9GRe24ORVUhsYG/vHewGgIhAI3CCmjW+lnmt8MAr2rUVgLwnmt2luFwFoP3uoqK0muH","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":671127,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgZoLkCRA9TVsSAnZWagAAgKQQAIpNICgIBz9mClaWCFyB\n8H+LdcnvO98IDribfrDBrAFs6FGIkByYOtEKhAjS6H36t8WzWdn1u4jgkWXn\nxYe1ad8NClkyGcVWOl7KfLFt3b2joeHSidRtApT4q6DrVmbLfbePKmylfU+R\nZX/25L3QzXEjACohuj7H+OFz5IqKGIfJaK5tpfwpLhrdsIv8VTUJDnklLIg/\n3U5oYeQLagB6x2D05h+PrRzmMxCqiGCCbzBOBHfejO8OBBhnupjt4kib7KNB\nDxEfwxEHjCtFs4KyXxvCRAMUiuT1qNkNxUd1N1301uZS8bRHfRcINGjVxS0v\n/DMJz7YEIZd9X6Kh1xoP/w9ZfeNfDWZ6jrMcmMWTnD+t/q3nDlAEv9bxIfRa\nV6g02eNrdHF/ZRnPw/hFUCQ2Qb2mr1upPHI6A45vUKQec5z5p0KRlpBa+zh+\nbpjURQN0eYWtX9EkPDgPAXTzOLWZsLSEzUkSayq/8rIkKd/tz85Nkf7VO+D+\nYbDQVZw0zQfGlHC88Zdbkey4bSyv06rKRw4MIg6YEHf5XD9jjf5rP4D0XId0\n2tVp2gjOP/AA5qJTk0eQXotD61YNZZT3FleXnn/kML0/lgZKOC/1p4K/VI2i\nu8Rg4Z3WgRwV4eweFBY72X5HnhFSVJ1itTUPURf9tEbTrKHAnZdO0BHSW7+Q\nxyyg\r\n=j8b7\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"9a38f67a1ead75e3e4e582445037b6b03a6301de","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.80","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.3","@balena/jellyfish-logger":"^2.1.17","@balena/jellyfish-metrics":"^1.0.26","@balena/jellyfish-environment":"^4.0.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.2.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.116","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.31-renovate-non-major-9a38f67a1ead75e3e4e582445037b6b03a6301de_1617330915832_0.388100475916922","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.31":{"name":"@balena/jellyfish-core","version":"2.12.31","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.31","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"619389a2951196c4dd3da0e255def9a234d24e13","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.31.tgz","fileCount":99,"integrity":"sha512-XJkaRyqdrveKmRdXyLHeGmKif3m0sjvbyhPh3qJ6isQZUvmI7j0+XyqZt9nrLO7y4vL9lh1BpItMT4LiqxDnag==","signatures":[{"sig":"MEYCIQDbq27KUbNTBsgpNNrAG/efwTEma6RkTXH4XpGydQMF3QIhAJ64GjESOLsxzRr436lsW89esgNVHlxh07bW4hdwSUTI","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":671067,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgZoRICRA9TVsSAnZWagAA1GUP/RLLAfDsqzSOpd+073qS\ngEZpY/bhbhLxdNO7JEI9o8zwe3Paw60hKBwdsuUfNgT1oPrHqn+YIL4fxxf9\nTEcZZSf4USLRcL9LZhfVwRXsba0AJsebewkrYu/5uuUTDrmqZgW8ipODamyD\nI+5cQKf+N4/it5ZdRGKYo8OnGkVl0AgYpO2z7LuSWrgRUM/n7V4myuZ9eJzU\n8t1AOWplthi64e3YBWZFKdmbVHoEHPPD/FK+ZOknZMiDD49huAOo9gw1ndvP\n5IfysnU2HxHxivWEfhQM8zrv6WsYddwCaz6Fv95zTZlZxxUV2ha5IswaOupr\n6w8m0My/oNPMhPRB363gWluIvKU0Dx076cSqL3GQlu/ZNuWHfpBp1rlSer8p\n/oyacS4pyZA+XHRlKICoce3ztzmqicFpjqJciAbdp/21RU94AJMv0PDK5xAn\n4HK1vQytR/gMEXLqk42NcL8CYgrbSTheWI7aFKioSRKYO8xa9XIQI/UACpdO\nlXxW6n9H18XaUCzmtikRFPEwEJ/Sk8OeO+ngWE6sa9M285aMEdrB2WxQkCzA\nqZ57EDlqtTU6EkuId8frc9tqy2jopEEb8wdOmKzK139qekF1YIZfppVA+EBv\nqPy9wWChvLotqThl4kHhqIZN3CfVITj6WdbgF+LdMEHIY4Kau3v8yh1Rhyuo\n0tiI\r\n=o/We\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"acc582766979e2cf42d2081baac4ca20fd10441e","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.80","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.3","@balena/jellyfish-logger":"^2.1.17","@balena/jellyfish-metrics":"^1.0.26","@balena/jellyfish-environment":"^4.0.6"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.2.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.116","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.31_1617331271806_0.4555409292095647","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.32-renovate-non-major-35c8eacfdedaa6ea29405b19b3f27ed6fba37573":{"name":"@balena/jellyfish-core","version":"2.12.32-renovate-non-major-35c8eacfdedaa6ea29405b19b3f27ed6fba37573","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.32-renovate-non-major-35c8eacfdedaa6ea29405b19b3f27ed6fba37573","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"fdcb973df035cc445153ec430e96a0f3bc247043","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.32-renovate-non-major-35c8eacfdedaa6ea29405b19b3f27ed6fba37573.tgz","fileCount":99,"integrity":"sha512-WMfAW/vP13Ns69ooQUccKi051emBLC3C1OPzUUMTpFGlSa9wOXZLQeO5T5XwiRA0r56xyENXx476nBjuAokSsg==","signatures":[{"sig":"MEUCIQDZZUhY581UtqdVFZHdOqghYYqbHvXYrMO4YIk23OjeyQIgBwOlmraSSL1c4X0/c1Cg8x0x8r8UFAnrg1pbYTHqk6I=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":671478,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgZ06qCRA9TVsSAnZWagAAzk0P/2cAHsWGXwgdWBhlHEsB\ntG7SO/2PkzYlReit/2nCN7I6/0bsRXk9pn0WxwXufrhl8wn1Z+ZWcX7ANR7d\n+sgRPFyBpC9nWr90AHvQ6kCefVt/rf4oX8kemOCcW/OzGq2GhPF3/IgQvEVV\nCWwgs2oDJmh9TPIZAzOaPyv2LrpZE4D2Obsy1ezzT8d1z1ELXuso4Qj2q/KP\nr++HNwPTP690vhrarFYohscwPsuNaxtgY8iWmqcrwEcWnaOdvKBze+4qZzqu\nsNUb4Bgbpsrevx28lcFllZu/5Dkwc20CyesG12ACpqKSMK4bTHIJf6V/at2G\ntm+/S8FzfFnqogm7sURoMLv6Kh0bCJL5HbIWOChgwHsfH0fz827jMXVqMeRH\n2VAINVuEB4dlkxojIJeurWIVJDAbabY0VDDRmjNMLXfPEkMwtb/7kTZsjId3\n9yLaUIkQytQ8PTAw4/XSXJmbW+sBrJd/XuODvMlYjd+euFqcW+YFV3Cilq/q\nkYCHgogfW0NOdZmYiNiXnw2NQXQTKy9x5ZK66yFH647Wvq+5gVT1YCbxYpQ2\nLDOKY127UoKydKVVorqL8K+lAwiDDhICm+eBQLG71Fabhu0pja5+Jaaj/0B2\n1aPcwos0sOu/jEjdFxbH5ss5RmWliMc/JzyWI2QECRXpWps7GI9Kcw7jamzo\nsSLB\r\n=YzWr\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"35c8eacfdedaa6ea29405b19b3f27ed6fba37573","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.81","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.3","@balena/jellyfish-logger":"^2.1.17","@balena/jellyfish-metrics":"^1.0.26","@balena/jellyfish-environment":"^4.0.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.117","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.32-renovate-non-major-35c8eacfdedaa6ea29405b19b3f27ed6fba37573_1617383081607_0.7956513891684609","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.32":{"name":"@balena/jellyfish-core","version":"2.12.32","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.32","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e711bd3efce66e5d7d4ab9efa4e82d931dcc9c48","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.32.tgz","fileCount":99,"integrity":"sha512-/X84BWnmQ4I55ZIdzJKdvpMUEzuEMknIqHrFVTE29aYsEIt6I+5mQWPkn1DPd3ZpHPcyhiSnAAmWNHdP9vXS6w==","signatures":[{"sig":"MEUCICUMya8D/n9k0zmNVXPGE2G0ffT7ik2MypM3v8CyKiWOAiEAnoGrVrg+1yL3sozVUeyPKCgEqPshKMehKnFCBYMxnDc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":671418,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgZ0/iCRA9TVsSAnZWagAAdHQQAKPHFLF3chEJ0GlsKh2p\nVK1P+XUkDSYS7wMDONoq5v+peG4PKKV/2UpHdZI8Lnagmzshbjp/HyfsED7b\n3SJUAAIH/nfjsKP3IXBaF3t1P1v6Um8qdlw71WUmYOtnNMjEaPb+e9XgkIYd\nmN5X4dTWTVaQaHVKrtxmoy4rMzGPMRrtXSA7aJxvsW1xBtMTukar1Cvxd6AV\n+2qEJWUl5l3N5eo6GjfioiM1gOPjILltfwDejybyGY4u2ClVILxjxP/tHfWJ\nBdt+z1QRDjTQjM/QNR42v1getU99j8hwJMFX65eae6PKFyQXnf2xyg995FXc\nqo/m8pquKefKyw61Rw0CX/gcKrYSGqvTISHBAjiUctrsEsEWcK+cCNUkshFD\nJNyah8BJ5hS5DERza6ih2MBONNuqnNhnO1iDI9WopijrzVV0+/bfVIy+Y9y9\n64njb1V2SrKjsg1Uu3KyNSP47ky8oIp1VDqOo0VZ6Egy7kS779hkatglQZO0\nSKlRojsJYKDCR0lLizH+SS2z1UpeozuWKzNKgCUTS9HSqjrRBDCKqdMCpaVS\nxsTJ+9U0tVUn8kukaLFSQYtRaseP06RvXHO3xiugnP3eGwBvor/f7EpuIbee\nHvK+9pF6xZfzrZVsb7mxQv4+9DY3EYt7rCVJUmtCVIPTWKJ2+OzzBMcREX3q\nZoRb\r\n=ZICD\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"980d9e6f62f5787aaed81c04e9eb48cb0cf88535","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.81","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.3","@balena/jellyfish-logger":"^2.1.17","@balena/jellyfish-metrics":"^1.0.26","@balena/jellyfish-environment":"^4.0.6"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.117","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.32_1617383393641_0.578600201372401","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.33-renovate-non-major-28cd4d165706a1ace2374ce571462dd13f7bda3c":{"name":"@balena/jellyfish-core","version":"2.12.33-renovate-non-major-28cd4d165706a1ace2374ce571462dd13f7bda3c","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.33-renovate-non-major-28cd4d165706a1ace2374ce571462dd13f7bda3c","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2851f2ff606cfa3d8c67a7626fa2bc63b0cfa0cd","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.33-renovate-non-major-28cd4d165706a1ace2374ce571462dd13f7bda3c.tgz","fileCount":99,"integrity":"sha512-MKfpLvGx3VlvUUX6zD1wgZDQGSaX4G5CEa9gdtnWbhBUkr0r50HQjsuNnfmZuwaCYYMCjaEv2laTq45zup4ogw==","signatures":[{"sig":"MEYCIQCngPOqGaMV6b2RQl/trmNuwChgHzSvZ0pPtCRJgv2XdgIhALoAccJRihT1OBnRJPRIFoNtKo6rSmL2hkI+mhfwMNMx","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":671829,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgZ3OPCRA9TVsSAnZWagAAFk0P/j4PJ/iFx9cF6yS0RzBC\nESI49yX9yzLHINgAUPKnEyUgfjU5mmE06P8RHud6pPww5G7Hu+WEXnQWNWDD\nBoYmWZAoHPn+wAhYcJK1+le45A7i47aebD/KSx31AJu+SeMkNhgLRUvDvtmu\neu6hVoJgSqVJukdD20TsL4UjW2F4otcevNuCjBr+cycqBEBjLpdUV6fLvn/o\nS7SK3O8lBe3SoczoHbAAjGIllDMLZDSWf6WeUYyBKyTnWcppTwBNZ0b2F+rq\nm2Bu77HjjDVF+ZnMxO2Tq5y/FDXESQCBdFL/B9foqBJTvipbn6xgID6uaESK\nKnR1jyQzQkJ2rj3WsuKzWomQlk7q4Hz/yk4RdN1UGfw2urTOZ/yHWIKZYaAY\nGVmR+p3D2PJVcP0TrY/NA1fJNhBMKyNLoNzc+o7QIWo7NsR2x0lkKNRa6meP\nu/sX3GG6CYAIyMkrC9DVLJZh2IADUKZkWf2YxhpKF0zTanGKpdlKto905/ox\nTMy76bWHXZ5lhylFJdYwe+ebkiepBGHW3n08Aa6xlvozVFc9hQGF0Pp2kYto\nyhYt2eH2P7ZPXraVnf0YodiGunFCFXTyb+Djaj9N0ynnynm6owrC0CKH2B8l\n6PTXhhD8PjUdDSELn4gZLZjUQTwAWWCSwpH+AQ4yu/P/xR+q39wo4qdmI/Yq\nOmOZ\r\n=zYJY\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"28cd4d165706a1ace2374ce571462dd13f7bda3c","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.81","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.3","@balena/jellyfish-logger":"^2.1.18","@balena/jellyfish-metrics":"^1.0.28","@balena/jellyfish-environment":"^4.0.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.117","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.33-renovate-non-major-28cd4d165706a1ace2374ce571462dd13f7bda3c_1617392526894_0.3630931859155102","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.33":{"name":"@balena/jellyfish-core","version":"2.12.33","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.33","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d1d5460dc7b68d64a750fc469bcc1d6203bfe186","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.33.tgz","fileCount":99,"integrity":"sha512-c2F1o5F3Ev2xqg2nN6KUsKlgK7jTB30Qtsl7AJcWSIBAfrcl/ponoMnCaGJ0g7sp/kBkXq7TH60Ls70PjHoZgw==","signatures":[{"sig":"MEYCIQCyLhOfEdravH5u1ih2sJXSuO5G96mgB0fxZWjwFC8H+wIhANXoH0svcVAPvKeJ/Oq+MFOABwFVexG09y04+XpTPImz","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":671769,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgZ3TVCRA9TVsSAnZWagAASMoP/ieZAd9gmY69k1OEAkRS\nQsQWT4vQq6JnOgN9de9POSFyOnrcknK7t4h2mv8Z9qJCVM2uMHrAy8KE4tHZ\nj7IP5xL4g0AijBX50I1sn75ImUO3jZ2p13ZdcGWYieEOY3Wh757AgJwgkX9x\nQkCn+VKDLZkyNVTXSVY280BiPqFiLZ7JUaqDc6NutXcB2d0giMF6uKA7L0uH\nG7iY0ci7GVRFocqhL1nuxIlv9SDdCgzh5gaV5/a9VcasvvMnq/KYmQtFLdHp\neNXvpNs85n6iD1R1V5Dna9Qm8t0KNfrTDWJab2kkOkriGam5X6S7qiGBsHiF\nxw7TcFGfXZAJzmXzcaQE4J0z1P5s7x49IukXRP1JWfPI5SwQemhl3IRpdwDq\netZhaZx79L673OlreU2YTjGwuLE+kBF1XSEv3DyHel/lr9RXghXfyfXtL8az\nzrqL9g3biEy3lpQvbWlHG2jKkEOQCNpW/MfKzzSjlxhV6hgU2ejsU/lYK4Md\nlbBBDj0TvWm5/qTRttUolBLJfRdiswk5CJ7DqFtRhb8x9G+KjUr8+Nv8oUfD\npXiA7gz865LUXgcMeVPlRyI+2lS9TAglekNA6Qjtu02PyJhqVR+/P29BRpft\n8FlDujBo6Z/MgWyi8cm7wYyq/hEB10CiT7suvadGAd17lidYTzgO4nlZhtIB\nrwhz\r\n=1S+l\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2c0f04598a1d13d8a178583d077fc83f523ee877","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.81","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.3","@balena/jellyfish-logger":"^2.1.18","@balena/jellyfish-metrics":"^1.0.28","@balena/jellyfish-environment":"^4.0.6"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.0","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.117","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.33_1617392852400_0.6490554656579666","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.34-renovate-non-major-6092395ebd444366732b392c59c320ed2b1ada80":{"name":"@balena/jellyfish-core","version":"2.12.34-renovate-non-major-6092395ebd444366732b392c59c320ed2b1ada80","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.34-renovate-non-major-6092395ebd444366732b392c59c320ed2b1ada80","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0045d3495f80c3f6d5eb2a5b41c6afa0862f054a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.34-renovate-non-major-6092395ebd444366732b392c59c320ed2b1ada80.tgz","fileCount":99,"integrity":"sha512-Ax8MAPRJTgx+j71ljYHFi4xW1rvHkBHFHqsSXaHl3897mCC0gohTuJr19Q9gYEvuUKGnhsLqsTEVwpprozOb0A==","signatures":[{"sig":"MEYCIQDRgE99tTWgc7IHW/tsQzGvvj/GyuZEPTvk0XpHDuQfUAIhAINrpnT64H6XevC3uu1O5VeDBpt2MEo2vgZzc7RM90Rd","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":672180,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgZ54WCRA9TVsSAnZWagAAxXEP/0yDaNfIILDSM1wmwHvg\ntirmLgF67zrlbr42gbv6lbVKpaEVZLkAAqFDKzyVwmeYYmc8Zg+jCvNuuc86\nQNFDDNIUeN/34bjqRIXCvZHF/9mOCA0oo2rjUa+Yf9zQ4MBKGsxkFTmZkUe4\n8WrVmUvxzVh8Nr/tbbkK6lMScOjezGI0MV9ps6F20PglkTCKhBSSuejiA3bI\n9RyU6GWqe/Y3PwvhrPjnbzIyWFFG+rB80urnB8jBBYDWc+g1fDytHua5uwy7\nYCBxO1NmT4XTYFmyR52FjXkc0pO+84IM5m/ikTVpcy1Lusma88H4YM579RH+\n90q+Fbtme/pLCwCWEU7mPGWOYLqtLtbWGXUu7zafOTCJ/ZIzIGRfoht54+KD\np8Ntwodt17pzdAgYbHJQ1QbLVtgk5k2s+XQ4IepKSQYQg4sl94z5iQE/bnvT\nD35Yd/OydH5b2J33PZRB3qrI/J2+S1hMCiTuj/qji7BZofjCVzLUdso7pIp4\nXwm2G+g+g2FiWZEEvE28iDacKOvVLhVrI3AaYHz0788Su8DClIFAVRuRnvBk\nlbuIFM8ZxXYiIrmZOyASA1JvIcjDh3UoCQwAt/8mURsp5kPGFnIK3qqaWAnW\nnEYAo4ObdwpqTHbjZebsdt5QNNRDkEWE0ltNWWIjWzRGcrBok6LrmJ2AmJNf\n0FL2\r\n=NHP4\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6092395ebd444366732b392c59c320ed2b1ada80","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.82","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.4","@balena/jellyfish-logger":"^2.1.19","@balena/jellyfish-metrics":"^1.0.29","@balena/jellyfish-environment":"^4.0.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.118","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.34-renovate-non-major-6092395ebd444366732b392c59c320ed2b1ada80_1617403414294_0.026122995400929216","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.34-renovate-non-major-b3ffb216d56a14f6ccf1f1a86c56d1a6b68a93dc":{"name":"@balena/jellyfish-core","version":"2.12.34-renovate-non-major-b3ffb216d56a14f6ccf1f1a86c56d1a6b68a93dc","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.34-renovate-non-major-b3ffb216d56a14f6ccf1f1a86c56d1a6b68a93dc","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4a5e72b37caf20c66cdbe6a4f61344b1d39d9966","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.34-renovate-non-major-b3ffb216d56a14f6ccf1f1a86c56d1a6b68a93dc.tgz","fileCount":99,"integrity":"sha512-rGHMexoF7G4t6J862s3bgOU1LCSgWwzwyUL3vSrGiUWXR62qn9R3Ft568aGVC92U401Q3EJc5C5o3RxCsj0JOw==","signatures":[{"sig":"MEQCIArxFSwU1efopbguF34jVFTiRvWgtcAPs2KEesGKyNv5AiAnwBxQ6HN78XNmKy5HXW3zK/hWX3MIE/cnycJaHRJ/Ng==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":672180,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgZ58/CRA9TVsSAnZWagAAiVAP/3Fyfdpcf0mwOryrkKcT\n0xcjFtNx1gYWho9wZsPN/SpXCFIMCpTajB8Ak+PQT9DmA8p5AZW9jtYPwXwD\nKjt2JSkr3UqY92FM9zE+w2anfLvRor89VCRdlj12mcOuw9yNqWtdmYghYjvI\nnQF/Y9C4kzqSvfXooTegNdtbcPWnQqU45QX38DV5MpQcU9hgXWoo5MvWasML\nEIN/xjQ7yRT4FWSOm312ZqMTVnKWiCQwEevAEctwpEUMaAn7IBag4nv0iII0\nd1hs2X0PnjspBP2FKuDBiYl21pxrAsobNpLPYih8iAODgX+l7logKlLX4nj0\nZ08qy2WKVpBoyDj9rSzN0I9Mnml1MvKU6qxd2YlLg/Bx5Urv0gFksGCjcnOS\nfO/vlbgiMZa8Hcby3uKXL5R7YCK1swXxuXEtOBaPjjd3vqRNPq/bYU4Tv0hI\nnbLkUigMeMViTxgbODEEkVjJHa7YiDmzdXjeb7JuJsgv6UEnmuFvgDmYQs0L\nwxO/sqbQboJB5nm5wH5IyO5fclK5bRdAtpOUfIR8ahkU9xIpLo7PFODDCRd+\n6CTY9dBGI8fQSPlIOej+A+1eCc5FJsZuvtFVqhk1Cr6so1hoHH/nNzHkmECO\n+FM+/NJasWQmcQzsqxfQHC68kmDBDu9SkXa6rQy0OaamSQCn34+dCLE8UCMM\nPIW5\r\n=MZQw\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b3ffb216d56a14f6ccf1f1a86c56d1a6b68a93dc","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.82","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.4","@balena/jellyfish-logger":"^2.1.19","@balena/jellyfish-metrics":"^1.0.30","@balena/jellyfish-environment":"^4.0.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.118","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.34-renovate-non-major-b3ffb216d56a14f6ccf1f1a86c56d1a6b68a93dc_1617403710495_0.08140863593808678","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.34":{"name":"@balena/jellyfish-core","version":"2.12.34","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.34","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b26b6052970ab22f12f9039d7a6d70e2c6aa75cb","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.34.tgz","fileCount":99,"integrity":"sha512-r3uRVh1e5HA7DFs/k0orPZl8DtHMfTEGCRtbCC1BYEsN5tclxyJi6YFp8BO07YsJ39kcss4cimMNEPeYRFJcZA==","signatures":[{"sig":"MEYCIQDEFtpw+Nm76KnH7gL72mcq6jUTeVm2OS/1DHw9RSICUgIhAJQWbopEegQMe07cip3D/Yyl//xGdygDadKS10Ba6gGP","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":672120,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgZ6CACRA9TVsSAnZWagAAWqgP/1Bo7eUv3cmmSVPWqmj4\ncuHGmsOHU7wc5gFd0nexCGA1aU7YNn1VapN0bvdZfQensR4R+QKhIVWVvz+1\nvQDVUoRuqJpXsuKLPCDBV3N4wINxCJJ2vHjaZd9RJ88X9qgCBBMSDgmKT7m1\nOMUNDScnyuVDLfuoaY0X4unzB/qe0ZFnSG7NG667wJfzCscgzIl5OGLra/ps\nCCI0i7iqeAunzypGZPzhyDYOKTB93okjdfwvwDUuQ9sXYLrWNy3K2hJEzk6a\nE/SD/8Gfn/zVqJRnKEJ4KSrf+jw5eiO1VI0poedb3gJE7u6XLI7W55tEJDqU\n/ksoD3R9CxVRDEY6tME15JnF6h8Ll5ESj095KOVGlHT55oosIkzMpw1VmaGY\nfw/iFMAhI54EC7XZRHq8rQ5cxLDROXOlXLVuAqUmXAIws5fNu6kOvtJwAUbd\n/PreEpVrUQO6AULhrn8nk7N1AWkR001vuHZaT1MKiApQgok19ab2Lrx0qeHg\nSHYdOAbjF31PEEZRaNku8JBmv+/HZBfJOoYMlZ4UNjALGNTl/0uHaHedK8oy\ns/UAlOc2v90jqBmufB7O19C0gDO05UMRUdkh4XrkxDT5N7sN4ElwFvUP28KK\nPgn58GWj/RvDkg/mi4y+6vUJsSKNUVZoqc6DDrOYNlT5tLl6vWhIUR92tU2m\nzYXN\r\n=wkXK\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ff79b2594fde3d2cf50db9ffab8f933c2e403fb5","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.82","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.4","@balena/jellyfish-logger":"^2.1.19","@balena/jellyfish-metrics":"^1.0.30","@balena/jellyfish-environment":"^4.0.7"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.118","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.34_1617404031673_0.03267214822531983","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.35-renovate-non-major-14560e82334671f38213be59995a1ccef4bf6417":{"name":"@balena/jellyfish-core","version":"2.12.35-renovate-non-major-14560e82334671f38213be59995a1ccef4bf6417","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.35-renovate-non-major-14560e82334671f38213be59995a1ccef4bf6417","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5686801faa532fb782a0b111953db1d6374a7b01","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.35-renovate-non-major-14560e82334671f38213be59995a1ccef4bf6417.tgz","fileCount":99,"integrity":"sha512-Z2z3oYg34SANN2GH4IRXC1tsJHQ0g0/+TDfVaNXQfAnKj/x90yNYF2+jNyqCr8w+hxnBszU9PelWD3KWAtEgsg==","signatures":[{"sig":"MEYCIQCf/VlxCblHtTmr4uE/pGgNEkch9GLe4J3Rv9RkDUBgRwIhAIDc1/zX8kv23LwpBC2t9zxUeQAduVvUAkzr8D9ZGawi","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":672607,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgZ828CRA9TVsSAnZWagAANxEP/iu7L7BBYJklpgyYhBYA\nTpOhPE/5dKCtKWbylZpmcTmx6Wl78FB6PLJk5mzgy8fRiiB8+beGBit8OL62\nDCRo9zIuQGrWlJ/hE7x8AWi5HA+RJvLIgcdIdVD0QNSTQp09Bxlm2V6bTs7S\nPtFMD7xwT7slyj09U6UY/V9LW5be0TuPn3mbDUSiC2jNbzddCh9WUZ4Yay/f\nsdbW6/zb288PkcG0aEi+elW7o4oIVIKRheJ7MigUvVl0wKrSaVsXXcbyXesy\nrp/bJvdvSX7RLDNuIbCkJrE/peDIyB36ihV8ijYoL39gsyncurPAPddRhZO0\nBskZEiXDTw5HYVxBZPBdZ+SZsVQl2Dfg1tH8XQr+xtcmzKYfnNVHgJ+HjcAj\ntFGpSSjO8aw+cikg7yvTyWb7My8bB7oXx12TxKBfKBNvTSlvECaM+kAOGsQx\n/FTpXeBMRPwGpg2aInIUxya0a5JNkdP+hP0u1ATsNlm8LKg6PMzMc8703wdP\n6FHPwhbJ0YlFVfxQKxPlNBaheJds6sbH9GrpQdiP6Lz9BCFcAWuKrodfRLBf\nGF8VHb6u+N+6/r19I8FQaHQ5pxQp4Ka92qjy7stHo2cmCvqAPkeo3wGuCkGp\n7E0ZVMmU67rcA6iL3cmwRNVAMQrUn8IVA87ISQd5pKhXw7ldWfPM5K8JX58D\nn68T\r\n=i+hE\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"14560e82334671f38213be59995a1ccef4bf6417","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.82","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.4","@balena/jellyfish-logger":"^2.1.19","@balena/jellyfish-metrics":"^1.0.32","@balena/jellyfish-environment":"^4.0.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.118","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.35-renovate-non-major-14560e82334671f38213be59995a1ccef4bf6417_1617415612217_0.9631748362727375","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.35":{"name":"@balena/jellyfish-core","version":"2.12.35","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.35","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"cf5c0504ac966b2d84bd225bfaeed116f9a9d072","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.35.tgz","fileCount":99,"integrity":"sha512-E7YAFOSLkOK3eGO3Wh913IMWWh3xK4yXH5HyNkQG2I7ZyTEgGPF1UbTLecT+sUmwV7OdHi2r6Q4dSv8f/r0Lmw==","signatures":[{"sig":"MEUCIQDrFrA2jnKXoShw0EU8DvC9Hdw+0vjr9XdNJ13e9rXpFQIgHkwqj3fii3kzzzFeo5M0x/nyXDYkw9D91ioNkVuQFA0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":672547,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgZ8+qCRA9TVsSAnZWagAA//wP/0M7Kvkn9uUorf4PGQqb\nuaSCN9C0bcFtPd3IVQ/oUsYWlK45F9/SezDH6CLQWKobj6B9Pl0mPgqTIv3G\n2+MUJKc1CP3I2tPbK6d0tAU+a8DRYdmI6Wm79UszUo/dmpHyMnVjdb+mZ/ae\nJhnV7xBuxt5HYxF4Vj7dsO1Mc09crkJ2MZdFXn16lFVYyWMX8AtpO9KnTi8j\nsxGxDcG+NQcSs++S6EKt9vXG3xH9ox7Szk3lgHV5r4rW1Ltjy7uIQiskAwK/\nMbY9A8fpoa4rQaUqQkDwWPsnBBFnLKjauUqqduMlo0H81kjZt6jAFTza28q8\naTDzdZCysoY1BrvaU3RiI33LgZt16NCpZ4M4vv8KexMpQl4eZsbDrZSC/1d2\nWsVOuxxASONGyDjQ2g/4iGpZ17co7p4Wt+k7hg0XhuG6rwC9r3HGwpk5cLd/\nEVZbi+8u3kZaUPTJ3WYiLYThOEnnjr4i0uG0TZuLPmUg9DamBl5MjkjJVafy\nHjEYT8ZM/LSYuWi3prVN6zyjoQEAaJ3repYiCX1LZLKuWGV99ttjQ2kdIQeA\nk2GZonm5tNqVD+9ZFF8NbqvLarCs0Pk+QoHkXb28tu9pJnnLxunyC/UmTgA6\nybKtRG+K06KoDE3BdumFebdQXAitNgM4juhA0Yp6N+B6wNZvHyFJK3ByzF7A\nUHme\r\n=WwRz\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a8acf179dfd7f2d482f067f8666aea4582783710","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.82","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.4","@balena/jellyfish-logger":"^2.1.19","@balena/jellyfish-metrics":"^1.0.32","@balena/jellyfish-environment":"^4.0.7"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.118","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.35_1617416106366_0.2969683740999025","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.36-renovate-non-major-d9b814a9aab3201146173303e7f1b715f6ec67eb":{"name":"@balena/jellyfish-core","version":"2.12.36-renovate-non-major-d9b814a9aab3201146173303e7f1b715f6ec67eb","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.36-renovate-non-major-d9b814a9aab3201146173303e7f1b715f6ec67eb","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"890d7d446ef21383d3a82a5f0929c31da28ff2e5","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.36-renovate-non-major-d9b814a9aab3201146173303e7f1b715f6ec67eb.tgz","fileCount":99,"integrity":"sha512-WlpSDR9je7X+Pt9D3JH+U5/DNqy0Irk9aql53BQzYcBCYUs0Y+sh+LTHA6neW2nL2TJAYBkrS1Xk+ICDoAAP1A==","signatures":[{"sig":"MEUCIAborJ7uIBeR6B7oU0bBT2iwYUOYGXCOwjQ/6eNotY90AiEAs6FHtOlLDMTI6w4vxT6VH3MybHsn9ktN0C23VEJY6Uo=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":672958,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgaORtCRA9TVsSAnZWagAAH50P/R64S+kBfp7zpo42RYUm\nh56e+CXJeQQWI28wRIyL5bWTBGZaxG5d/I6exJWCgfB8r75Ki0rQeni+C6GI\nsVQbqPchXhZeOJeHIsBllUF/kP+oK9vVhChSQmaodm7GrLIjgSegjO1eas4A\no1d8g6MLCudmB2ZyITtxE1wSjy5oU2ybDB1ZtIRHQfUUSu7aJzgKppdK3FLq\nTJ/XrHDZ/zhX0ZnwfqYh7gwG3DkZSwxQSxpKosL4AyYq3XdQZZfafIwSiN1R\nM/N0l1S8sME7zjJ2Yqs135Aa+TcQ+apskIlDVgd07HFhChSkaLfaHlNaUAR3\nuabkyd5kBN9Z7uvdnwoHWCeKXETUcsNGPoBk1S1Gff831bapgJLeW5fRgW+a\nZngOS0kkKZ/4nePTn5A4hN/I8cRRhwUvl+d8F7LqT8P7+uufIiwc4WnPuUHJ\n4KMBrATVrQvfWieBorH9i0JJIw0xnqjTPfML06LmRhft30jwBJQ1U9FjV48k\nPEqYMIKO1zHHaCaeZEH6M4s8dABznTNaAt2B5Ob/Z3XPTBeskaUeNZQiIvPX\nNE/b33nXOygNXHrZ17iGCKxVjrOGir3dkI/sIi4GqoXwL2CKoG3pDZr7cH5N\nPTqkTa16Uad9CE1gjLOcXnUgoO8prOb8EWhG85BCXYiPJQFlNYqRxxZwDini\nsTdI\r\n=p/uj\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d9b814a9aab3201146173303e7f1b715f6ec67eb","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.83","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.5","@balena/jellyfish-logger":"^2.1.20","@balena/jellyfish-metrics":"^1.0.34","@balena/jellyfish-environment":"^4.1.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.118","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.36-renovate-non-major-d9b814a9aab3201146173303e7f1b715f6ec67eb_1617486957223_0.04869192103693698","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.36-renovate-non-major-cfa65b507d87d142a960f3e5e864649782f66750":{"name":"@balena/jellyfish-core","version":"2.12.36-renovate-non-major-cfa65b507d87d142a960f3e5e864649782f66750","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.36-renovate-non-major-cfa65b507d87d142a960f3e5e864649782f66750","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"89a11d93e62d07d59e06a9720e375977e7e44662","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.36-renovate-non-major-cfa65b507d87d142a960f3e5e864649782f66750.tgz","fileCount":99,"integrity":"sha512-PUpFurbLO/SWrvz4hNIgB44bsW5WhMYHDQGneJlpAVu/MqAvCAuDE4EWx6jss5TSsDI+ncCIA9izPFHYXteFog==","signatures":[{"sig":"MEYCIQDV9zvQFzaVblJ0q9DNSoHoAG+NrZtCzBsuv1HmRV+1wgIhAMvB92AvC/wOBT6UbMOvR0GbqZnf2bWLmXPrkKNvZREm","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":672958,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgaOWbCRA9TVsSAnZWagAA4RAP/2OVyCKXch2CPtnk4y9w\nBv5Tmo7q4BLEns9jegCopWAzQ5/YuKr+TJwqL66Qw8yikGOKf662wPLHL2+p\nLxDpXwhVpjastwXzGgDyeTFePXsS9ncVuuMLfvwRZFqdGGX/AOlK41eJSXGd\nN3zVrPVQHqZ3KE2bxyqK02qraTUav84PC8JzB30mizB9suUcDDGvOxa7BPho\n4HiYiCkZZRvyJmsnH+4u0Sy2mEpHFJhJGltq58VocFWS8/gmxuVFfQ1ELOlP\nGG9cLqaYtRssxN1MoXv57YV7GifFDnHsb//VykfCTjdtQVYJj2GQ0j67EBtX\nLNm3rJZGkAg1Hiss0N0Giw1bu8q/0/N+9JIZ/0nmaHsFOT6ehj266MImwZvP\nYj3tiOatr8d6lnjfv8sR32cVyWNN6KT9CDblP7oYmH51BAVEmVhTn4FB+o/N\nwV6mRUL54kfdjd/dLZYngkR56qsijDwiTfszuIQIrHF98TcaQ32u4OsRdVpG\nGzl0gV3fV+wbIcPDYQOE4ia66k5T2ZGTrjy6wyKVY78B9EfIUfIGrDySyxPG\n2UhWxukTjKVPwm68398Kw2pqiJEqvJnshmYUZ2H1u2IIETzDW8moKoBXN4dM\n+R5A1J4ugWzn5/mGuTL6XxCWEurus6k8GElr0IobEOqxnaiId6EvtXBmCmK2\ncRuy\r\n=DjHK\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"cfa65b507d87d142a960f3e5e864649782f66750","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.9.5","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.83","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.5","@balena/jellyfish-logger":"^2.1.21","@balena/jellyfish-metrics":"^1.0.34","@balena/jellyfish-environment":"^4.1.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.118","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.36-renovate-non-major-cfa65b507d87d142a960f3e5e864649782f66750_1617487258775_0.5497065923662321","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.36-renovate-non-major-41065e01a2842ee5cb3d4fe2581318167c957993":{"name":"@balena/jellyfish-core","version":"2.12.36-renovate-non-major-41065e01a2842ee5cb3d4fe2581318167c957993","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.36-renovate-non-major-41065e01a2842ee5cb3d4fe2581318167c957993","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b10b22c5eb698760436eeefaa448cc3b96d1b6b9","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.36-renovate-non-major-41065e01a2842ee5cb3d4fe2581318167c957993.tgz","fileCount":99,"integrity":"sha512-yjKTHG+5luFh/pricS20mGlwzZlSxu06Quab4l1quesNAAFBJhhHjT4vkMRNpf0vShnoRpzPP0eizcj+CylUYA==","signatures":[{"sig":"MEQCIGFPI1+cT93hV08D1dbBBMy3Ey+9P+64JWB5R3Fxt2NlAiBaks79Fy+BZirIwAHMIWicHFHmP92cRAyTrP575Jw01w==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":672959,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgaR0rCRA9TVsSAnZWagAA+q4P/1FJNCi0XCRI6ajXDmGc\nQf9zYZFTZGtDEG2whGr6rGDBXaCnQBPv/+YlHhRVvHO7b3pUx/JjX7MZDtW+\n8s9GONYdQfbvzaceOtuCjk0g5KIqP6gx5tpOmDJicKGfc8Cd84U6TCSqgXdp\nc+3G8hoLZDO7Z3EH4+umLEXw5eyHzSbwyZMr6o+4bCrA/7f0kWzmHRwRZ+gH\ndr1NVbW4kNoa6kgysBqor8xCSTxavpCd1EIwEHGx4pUkR7yHaXS3NUeMRHNZ\nIHcFnzaKx/svNL7lk+obGdPE1wC6FFaPyHU5xjSzo8IzID/xE8AaLKmfmJRf\nPJCRf5amClUFS5MKuTxR7vTm6aAh21wMpszJvcMvWOQdvKZpISW/sarfkbia\nH3PUkDBlJATNpsitOgyyJ2VkWDQqR3b+bbRsc261g6y3o4/65GFnedQsxBwc\nh22Zdssqnrmw5URCCiLmqMz/Jl5Pyp/BhUsaVZPhLH1UZQ+ldwiFz2CNyyj9\nqw0/VGhFLWmMZL0+alx2OHVhlNITJQXE1HJpSKGEqYXkpP/Ag+XzAT/ggg4Q\nTXu/hfivXTyij4ICV5mfm3Mju+rW/oKl///zi7DMTIvT3Pi/oNlscm6rmfOg\nuJN5D8vS/8xSPk6AEXE8XOGKdVY+qIH+bk+vmb7DDVNnZ9Bk+tw8AEOjVXem\nYW1r\r\n=SrXx\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"41065e01a2842ee5cb3d4fe2581318167c957993","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.83","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.5","@balena/jellyfish-logger":"^2.1.21","@balena/jellyfish-metrics":"^1.0.35","@balena/jellyfish-environment":"^4.1.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.118","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.36-renovate-non-major-41065e01a2842ee5cb3d4fe2581318167c957993_1617501482956_0.6399119907455801","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.36":{"name":"@balena/jellyfish-core","version":"2.12.36","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.36","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"eb2e9047cb1302e0e0eca36b530480e2d93fcf8c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.36.tgz","fileCount":99,"integrity":"sha512-+IadTpaK6Oi+X1tL6Mlyas6OjZsKljKqzfePmctTsBSzWY3Tgkg8navWif6mQGV1SLfzWOH1EpgVwZL80rIu+A==","signatures":[{"sig":"MEUCIF8203ZKOsMb3nC7PTlc4A8gv4RY/6CYd65tmeKjmPYSAiEApLxt8y0PbCnxEpbEnf3UU5NLbEzP0h+a1b2sU5YIEnE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":672899,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgaR6GCRA9TVsSAnZWagAAnDYQAI89uwUMKgqplEDsvWMF\n7i6wq0ZitoTUJwh3ViT9dU5qqiBOtkFc6CUMVDyXl8gB1k8Zk+LNOSHrVfzv\nMIV6tTJ1/7cYGVU5VkifIwLyDK3H4mefmbRYSMNWPacopV5rcsiYtIRcLNnn\nrB6FsfsMGcdNsB/7VOyloiw3/pmmoK2TkaGV9Ebr7Xf9WlQ2FGh96ZoCEqoa\n0IpjlYBpxXIpFKiZopWNqMiSsI5DTYnp0PL75WPvADe0/Tl+kyswpw/VVFa2\nF9dEvpMCC0oyQ/+CoiwyxM/kt5K/WWhy1loPnW9Nu9sWM3IxSBqg+L9SA5jd\nCjGSciWJ0NhsbFDnRS0WhuSKuhvSU8et4SxAWxCh8yLabFRD+mfAsGeH8JG7\n4d7uDLYQkJOyS916kYRyj3tRC37eDtEWbHYwaG9RJ2LMRd8QUH7ud7j9O2Od\nBALFmC7mP0vX3IvH1eJ7+B9r8eZyPK2xLojVIrC89f+rk1m8Pu79qhQ952qz\ntgTEMmkLeXXnPnPznMzegAJoVAonBCc3EE27ycRrKmDWhy/qh80pKSpdTcXT\nhhyHAgg4MK6F9tDNS08HsuY7c+5XjPR521xgoXLGUVuUvyZnDLrHUEO9N7+V\nMFdlBFZ7jO3cKK84+du6xwS5jLVmW8FTZh6FINCkXxPhoud+5rQ/JCirCibC\nQLyj\r\n=8YVT\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"da73310712e29531ed94c19bf789a015c0d96f96","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.83","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.5","@balena/jellyfish-logger":"^2.1.21","@balena/jellyfish-metrics":"^1.0.35","@balena/jellyfish-environment":"^4.1.1"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.118","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.36_1617501829946_0.8604419277350435","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.37-renovate-non-major-cdabab6fb996d9d338bad0431e7ac7aa3a5a9bbf":{"name":"@balena/jellyfish-core","version":"2.12.37-renovate-non-major-cdabab6fb996d9d338bad0431e7ac7aa3a5a9bbf","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.37-renovate-non-major-cdabab6fb996d9d338bad0431e7ac7aa3a5a9bbf","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8609d29a64d0c52f99d52189acef7bbef4ae5ded","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.37-renovate-non-major-cdabab6fb996d9d338bad0431e7ac7aa3a5a9bbf.tgz","fileCount":99,"integrity":"sha512-ZlZkpP1U5OYhot3BfW2fW+S3M1hFMcRz+bs9bJk8qrDzTs+tTIXHWHmEKtyHuIPjIbYv1cb6ixGbO72cNJsqrQ==","signatures":[{"sig":"MEYCIQCuZ3stcoW2tH4nJ6oE58IknC2cCqh7AoSReMfvR8oWiAIhANlj5ecft/NgUbjB/zzIcDN9TriMcM7cdSPwv7BG0YHH","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":673310,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgaTI5CRA9TVsSAnZWagAA/78P/1e2PNQbXDw6Vw01W26g\n33DPhZvbQpP5E4w72rkdnUTYQdCCH7MWneRJVrWWPbFfk0sh5Fugaf3xqywW\nHBPrSvfMSzMITZ3IFM39tllSNLsx8qcVFLZCbfFEqRDJ1vFaLOTkOZ/g6Wst\nBdLftl17GIklYcWSxWUppQwegAKIFH/NdhW40gcLzDXvcAY3VkX9vuilUJWS\nKOyqtLDCQrfJXtiVWBCBaeTAVw7+QAyn8rD2ozBxwaYv72y0ex+2SU2L6m+5\n84vxX3cwF53VGvml20a+1wC4q81JdkZiyNdm7RlLeHI+z62xaSH3bsvKgpma\n0WjazLjCL/PTA2y6aAihmGTnbuQxGvHfK9VibJQ8ucxB7OMLsEtCDr7B8cYU\ncryXZ19LD9CWTSjxTQVuy2gFW4HrZoczJTO6SrDSuGJR8RqpANS0NLEd3LUe\ngOA80VRR4/d/7uxyAv1a8E5YsaVvanbJKYESnBWE0iJkDuOeROWndBiyve4s\nmgeIyp8bfVKes/GRPDKdlA57xpp2s4SH/Y8VR3XcdTtJSZmPkyvatuaPEILA\nAsAsj2jsJ+0uCPw1lmCXMcEwiD5mGQ8ezdmXXlAOS9zNI5XEVAsjIg56z2d+\nmi5KFB5JDgaLBC6HGnaV2jct7qYFdYKF1qHLf/mWGom9Dikzsj9wM7X23W22\nONlT\r\n=D+Fw\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"cdabab6fb996d9d338bad0431e7ac7aa3a5a9bbf","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.83","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.5","@balena/jellyfish-logger":"^2.1.22","@balena/jellyfish-metrics":"^1.0.36","@balena/jellyfish-environment":"^4.1.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.118","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.37-renovate-non-major-cdabab6fb996d9d338bad0431e7ac7aa3a5a9bbf_1617506873410_0.7436214743690868","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.37-renovate-non-major-174147c285264e8f8e214d46621ec7559a412fde":{"name":"@balena/jellyfish-core","version":"2.12.37-renovate-non-major-174147c285264e8f8e214d46621ec7559a412fde","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.37-renovate-non-major-174147c285264e8f8e214d46621ec7559a412fde","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9d806029f718eb695418a89b12dc4520869f48a5","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.37-renovate-non-major-174147c285264e8f8e214d46621ec7559a412fde.tgz","fileCount":99,"integrity":"sha512-iZunFLXRnKTW+6H0x70zGuh4X4rAsJ3YrF1MfkLsuYuYDDa10uqFdnktLgTxsHWiPHV6r/QIaqJnQkHZfuGt+g==","signatures":[{"sig":"MEUCIFu6Icza+ZyB9nGxCx53kk50saYKzza+u95+i5NQtD9XAiEAn95DDRdN4hDw4WeSYY8wACqxrN3b2foyhWxw7qiI6EQ=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":673310,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgaXTNCRA9TVsSAnZWagAA/2sP/22Y2X2HD684kYv58FAo\nkYqZ0E9sIPC45oT2ChmXx6KlQ1IVmk6D3KsJouYjqFUL6RqnUxwzJ9YK7wmJ\ne+178em4bBPx6NXbNrNqPjPKuGUZKopgf3pLWMaGuqDDqyJ2yAl80mVdjqSx\nYOdALh19LzxGJOAkSYKnSb9+1Sr6/Jgk4EpstFX9dx+gvW18UlcKNjCXhUY8\nuO4ky8uC4OynofmFOQTlLaZ6X3yK9XFZtzimJBbSVHM2bGAiOApHeemnnXGt\nbTLiTkTjNPVmlPBQgUWZ/a+QR6KDFghAxvfmjDczAz8CTW2REE7GjOneXoK+\nVjKVvvBv/eoD/FiC1T9UjRQA7u8nzRyeUW3YzVOW40AOZAEpUpbOLg9cYyqH\n9uTAIn2KDREVDCN90Y9eZ0vOntZysRRJ2FnIUWIUnR59hNq1Ze9L7W2fIx99\n4e4Nb5xZ7JB4nJegyZj6ht7rfr/mgZVThTcjlvYcdiItBWKWmXGzeNKm84vA\nRVFQeCGF0U8vNynhAfuhwnp7/hagJ5tBGhrRW7VB1wnUuTNx2Ee6B42P4Hyn\nlWZ89Nq935jYQYOoXQaDOr8IDYzvcrsM7ZShkAx2ax+GAVwslinwoXShm88i\n9MfJKFMlSGpBONi2M6w9dFkBk2gRV2AGllLhUyf81Kzr8Rg/RXJP9JeF5plR\nVUv8\r\n=qdxH\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"174147c285264e8f8e214d46621ec7559a412fde","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.83","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.5","@balena/jellyfish-logger":"^2.1.22","@balena/jellyfish-metrics":"^1.0.37","@balena/jellyfish-environment":"^4.1.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.118","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.37-renovate-non-major-174147c285264e8f8e214d46621ec7559a412fde_1617523916923_0.6601513454666863","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.37":{"name":"@balena/jellyfish-core","version":"2.12.37","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.37","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b3abca4cf365dbeeceedc17a06d870c632ae153b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.37.tgz","fileCount":99,"integrity":"sha512-ZjlEbbxSCoP14UnCSuvgQsYgwT+o59CADFTdfDRSx8aC3GNtgMzP5RvouBmCrvS/4EJRNzuV7olswwCQOfT3xA==","signatures":[{"sig":"MEQCIG0FWXupDUW3EA0y36w/NDuZaMkeR+5LNo2IjHeCiYBgAiBXw16tytWkS4FBh+NPnUSE/qBqEe/66W2w0D180NW4dw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":673250,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgaXX+CRA9TVsSAnZWagAA5CwQAJRAmsF3mr1ki2M3s/KT\nIShnltKMEP8qvIdaG1sAPRobxk6AEsLP55mdT/EbXDNDAt20gV8Re4+5i5Tc\ng8M81j5y0/aCg3GGSLChcXMKG6r5W7vOP2jLpktQFNTOSWqopT6G9RlR8nfF\nAiCsjh5ZUiejNdq3dpfkhJnz03MOzfrq0vEtuYFgbq3lrYy0RT3D9aP5Ceq9\nF656RE4VfoLyjN5BsL27Q65JVY3BFGrRLp/dOGuQWGmMU90PMWGxSVttU9zz\niSL5xxHQmqPT/fpa8qvA7QE9s7LeKlKv52gdEVFyWm/mk6C8V7DMnxAkdp/w\nN/Zl5lKr5ZI0QFVXpEmgVKpt5BwMQ6011hzy199If0RdgnkaZL4DTW07mBQK\n7Q3gWll3vZ8cmAZczg1uNN3hzX8mvGaboxcsZFX7Xii0twenJsVcFciM94W1\n67vy7JH1wviDikmKihvAsXw31lfI0YebdOL1QVI64lXxaLIHLD/ZrdSFmnil\nimFVFp88xrbsi3DoTBXEwqQqTz+zmt+1FCJru0jPfugAwdljT7dgz3v/IjHS\nmO3L1vUtUCHsiN0zLcYC17m4PqZmnxTtjaBbTtk+mYYfuTXfSgd8MZdBp8Pc\nTajXXeXBPScmSOIuXScoMc5d6jOThY5znj7+To4pkSij5A9i2S/upfa+A8pr\nhphL\r\n=W7jA\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a98a98d91a347f44ac9ad222b4d81fcde7f43cfb","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.83","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.5","@balena/jellyfish-logger":"^2.1.22","@balena/jellyfish-metrics":"^1.0.37","@balena/jellyfish-environment":"^4.1.1"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.118","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.37_1617524222405_0.8038083475780313","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.38-renovate-non-major-3558eab138014058dd2ec4826c90e67e8598705b":{"name":"@balena/jellyfish-core","version":"2.12.38-renovate-non-major-3558eab138014058dd2ec4826c90e67e8598705b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.38-renovate-non-major-3558eab138014058dd2ec4826c90e67e8598705b","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f77a5b17dff227735bb8638a3bf68452a84b8aa1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.38-renovate-non-major-3558eab138014058dd2ec4826c90e67e8598705b.tgz","fileCount":99,"integrity":"sha512-8FMDKJpS0dFIzp9fw4Djf00rrOLJ/ZCP/dKNRRaO05J1NSwW6BuaQ8Fx+PKLfHH6oEuBudTLYubhgm7+cs02vQ==","signatures":[{"sig":"MEQCIERjKTLFCiqVuXt6LPton+XmgScZvIrvaVOcWH1SaG3nAiAshhPUipPMo2aq2HkqUj/WaCCzpRpDmsX+6xdYMy2nsA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":673737,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgaY4ACRA9TVsSAnZWagAAM78QAJbds16AVseZSyOpUZ8h\nin1/AT218GP+r/EKwzu7llblxWx1A/YdlgH6cBC/C8wAJg9lhcHCH7ZXMnok\nnZ/SRvpg8tdR6mPyffIoGrOBf2XJFdhwggZkC/Xnrjnfip4S2kdFaDQX/ulD\nfhvwIJ2j7DKiDXzICfT7lDaqkKI+HMG86KS9z7u5NhgF/pP/ivHUc8oqU7nD\ngH2Jg7mrZSyC1lqgtq6Z9L0nUmYTpYI7fCuAsWQFwWxL0asb3kzshQum2hlg\n5o/bZ1hU4MeG+y2dT3zcHuHci/ssVBMjx6mKj6/JVg8mGoAgpGWBxt8l+n2n\nJIKYiTIa/7JkS2/DH1SZUrQ07ZeeObMEF1CGuwUckXd3Oc+ExuWOXTmSkLW1\noqyNz2IieCmL3d4Fn1/7AYQ0WWD8I3aevMLBqlZFYqdLgKzCEHkdFWWcfMOv\nLSJ204Mlqd7an5Pxyf8rfc2g9crcghuNu/kAZjnZ9vQ1oTkIUJ7cDhKJ25cR\nzMekoDwRGIT+Vga39Z3v+/Vzx9ovGsbF4woBTYucKmvJuPcGWpA6INHzDmQL\nN1IwVerymp0j0C0j0QcsXUjOI7BAblMdIjtzDmX6FFlji/9fsgodeGMyKZxh\nXqJMPjIlr4elT3Pb7Te4Mw/hz2cx0K1GuAI6G7zOsInre5q+/mSWN1JJ3NY/\nl4kG\r\n=PaN7\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"3558eab138014058dd2ec4826c90e67e8598705b","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.83","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.5","@balena/jellyfish-logger":"^2.1.22","@balena/jellyfish-metrics":"^1.0.39","@balena/jellyfish-environment":"^4.1.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.118","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.38-renovate-non-major-3558eab138014058dd2ec4826c90e67e8598705b_1617530367915_0.8597696935019279","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.38":{"name":"@balena/jellyfish-core","version":"2.12.38","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.38","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3cd4f6212c592d4c1bccf14183ae9ca181d3a4a7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.38.tgz","fileCount":99,"integrity":"sha512-ZObKmh3CaBSZ531shDdBTg0EQyswi+mAGkN2jK/q7USMDtCiB23wmjp21gYzNHxPUZyguDqJ8DguaoekH/7iiA==","signatures":[{"sig":"MEQCID7WCZ/09tQuqp3D+EzCb9++bRPNo9QTjMlpiIDAu5ylAiAs+fvLltmbSmc7tQQwhogq42wQ/UeOQCBH18nURipaJw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":673677,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgaY/QCRA9TVsSAnZWagAA6cMP+wXiXtwgS8PFZpjNFnO1\nuD8ak2rNYShlPzKj+AVplKTqFAM8CQECzmvxZEBck4LITsm5MIFm47Ilqilh\np0lL3IHYcO7wP6dJ+1UP0490FaPEJJvq/FQK9j3qK/Az0tgtgtEZAUUuwOgf\nxK4nLVwAof+CG5Cci+PtjYrvm5m/+m/4wKeY4oMbKiaNiuMxAc2EJ4cEIKny\nyHiE1RTt/o9eF1MWlOux5Ya+xfZwSg0A/7pDeeCHyBsXLwMYgLmkwrDx/82R\n5VH9SrNyA61oZMn7dET1hCn/7h3NOjNRB9jpUxl/YFjKcK70aVsTzoNlYXSc\nCphkI0RPvCPzgLyA7hxGI9Yia3FYWT/s+JewWrta3jMFpZNmOiJqAikYm+vC\n8Hc3fbKtXUqavBtq9xrKzfRprPxxQqB6Bhu1xMDr8fcZvBDsxFecih2vJ0HX\nLvPjIGyd8dQNTRug1Pu8sKoP0qWxQYlYaUv+Pj4rcQlp2boDcQiDhVkR+23E\nhycrLGs+VFTVjoFcOoaNFabUyPOxkZPMo3rtbwqsqzjYhTtIzl6HH2nbBDNd\n0kekVVLz1yog5QGI/ZzeCHVKAzCIhjrW1Kqb6KDlOOVEUrkdWkYa1LM/I43g\nhZj/MNIVn+1ohO15Yhnn0zoZzDQlwHGH9ucuCrXaUHtkibiEInODpZDpEtIb\nx7pC\r\n=gkgI\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"1f2d1149be710e0af85306726e17be5ff31a0342","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.83","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.5","@balena/jellyfish-logger":"^2.1.22","@balena/jellyfish-metrics":"^1.0.39","@balena/jellyfish-environment":"^4.1.1"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.118","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.38_1617530832230_0.17887744592834776","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.39-renovate-non-major-bf33855cd4dea800ba8e70cd4b37e7f7c073ffc4":{"name":"@balena/jellyfish-core","version":"2.12.39-renovate-non-major-bf33855cd4dea800ba8e70cd4b37e7f7c073ffc4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.39-renovate-non-major-bf33855cd4dea800ba8e70cd4b37e7f7c073ffc4","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5e041719fa786335507bc6b9787706cf87cbdf88","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.39-renovate-non-major-bf33855cd4dea800ba8e70cd4b37e7f7c073ffc4.tgz","fileCount":99,"integrity":"sha512-pMu41N56kzclkCmc9YT6xE2lnYpC/0aEgK9wOkwizrl4xL1gv1x4gOyBLh5U/jgO0+wyXhHvnCzfBWm0qM4f9g==","signatures":[{"sig":"MEUCIQDhOPJQdy9aFkpuOmeh903+wzj8+0vgQADJh9BD1Ex2qwIgN9h6pCgEp7Gyq1BybVW4pZFdbF81P9rFVZWCK0YGk/c=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":674164,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgalWoCRA9TVsSAnZWagAAQ5AP+gKFuu0BPUhcD85c1HFQ\ns26gR53riUY8htVHMq2vheUgaecU7B1DogUD0CUaVIoy68+UHAQhIJTaEffH\nCDu4QgsGL8W20Vx319RLGzGupBeSuRS3yN49oFf31NLtx2O34lI1Sg9TUm85\nPKmFoBBB/zGU997aA1k/QhqAF75TV/udre2JUSPuqfnahtnhIfxDK4Iue2H1\nO2O+M9qJc3nXT7loMlxia+/IF9kkmZlj6KD+aDF10uIT2mNSq9M5NzK6noFS\nr1khRDCGDMs15f0eEpfwka2Kj2gJC6apWNgcwOhcAgpTds30ff1Emfpyij5S\nEpDm7epfVbRqAywoDoz5pYXSVVaZOiKPPXd7KSmgCCWcVkx+exsE0vyUBOvs\nelKs8A6C7WRHWWpMtGTEKXY8OLPY2fyatFBmMp7AGQ/IdWPofSmAPcFn4JBK\nbNbMk3jjFoNqY84+e2Ihxy50d+WRnUDRv2vj2iKXDfjuhTWU3z/U9/+HvAEd\nAVM4/XZvBETak93ud2dub2+zBx2mB281shb0jBHeZJWs1JpI943BwZG8+cwm\na35+O6vJpT87PCyF8elPtwhUrgUma15ZGP5/dHNh/HsFKz9KaCiZwgbO98/S\n/eu++jlcLP0uS+HXKyMSaeA1dv4BuSHy2lAM20igFxgh6EnrHmPujEKbZ4bc\nciXm\r\n=9Rdn\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"bf33855cd4dea800ba8e70cd4b37e7f7c073ffc4","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.83","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.5","@balena/jellyfish-logger":"^2.1.22","@balena/jellyfish-metrics":"^1.0.40","@balena/jellyfish-environment":"^4.1.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.118","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.39-renovate-non-major-bf33855cd4dea800ba8e70cd4b37e7f7c073ffc4_1617581480108_0.4677655186296228","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.39":{"name":"@balena/jellyfish-core","version":"2.12.39","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.39","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"15d74bc49be3fc85bb0f6e6e04ddf54423e756b2","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.39.tgz","fileCount":99,"integrity":"sha512-IzHpg6QP0BrKPYQVUOfdYTMBkdDHgmjU74nSIdD43dL6zCrUNE/zrk6C4YA9Awv+xausWofV1RoHqZlUh53M/g==","signatures":[{"sig":"MEYCIQDp1Vv/8p0qXb7k3QyoY9p1Vlj9OFv6gWWBm/5lP6OMVwIhAKcF2jeW40J5Dre2LaW0UKMo2f9Si/kMZUAPp+HDkdnc","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":674104,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgalbNCRA9TVsSAnZWagAA4tMP/3rbAHGbvprWltpPoXp5\n6a9/e020/UVkYqBIDvMrs93xO4ncqtS72klgaQKhckS2DGhtXfi6aKD7i7hg\nXsGwFUSnEEBIRGauDSJbr4Vg3EwChHF41FNzi5EgDFF1NDbxLFxZYfSHT32b\nup3fIwq6rWlukZXTIIk0BdO6XcM6dqSu5F+YPidvLOeGNjrNCyHS2JnFv/L2\n4XbU7faaC2aDTIJDIGoEuPr/hoK2dD583P9L6sDWuWEJeblMSuwgscxfhF9a\nk0LnvmM3XtaNIH0LJsMIARsSyUzgGe5GsTnkTzopHHQ2O9VzPbInR9awgfbc\nHoawJ0hrMNAksBhTD47IHI+15ucCgeYBONGT/B5S2se2IPrIjhniCUiImpzx\n2GeXbtkuIQZrZx1c8NYqgqY6I6+YAO/PwqYHXe0Tg0SlmM15sEtybATT794a\nBtkSB6aXpi+z5HrXsuuyF4pYs07R/Ttqt8nHKiC67jB3xHKcLWdEvmUeekV9\n42weQxahytnDbkGeOl7lHMXJVhEUr4mtOJAD7IATkwNaaE5rauKEWSQtBwOJ\nr040alDVE7I0N8HqCTE16exr2L5bAu5kcj1HTSOgXE8SmpEazUz+KJZTEIUZ\n+O63vZuedEKdkxi2DeQO10thzpYQeGVMkuGTWReKTfgLHQpbB5speCgq1MR+\nq0S/\r\n=MA6d\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"af277e8d2087145f45d34a29c47a397f488cc368","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.83","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.5","@balena/jellyfish-logger":"^2.1.22","@balena/jellyfish-metrics":"^1.0.40","@balena/jellyfish-environment":"^4.1.1"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.118","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.39_1617581772445_0.28043632461584767","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.40-renovate-non-major-5a4b9b58c06236b13b525166c9191991483b37f0":{"name":"@balena/jellyfish-core","version":"2.12.40-renovate-non-major-5a4b9b58c06236b13b525166c9191991483b37f0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.40-renovate-non-major-5a4b9b58c06236b13b525166c9191991483b37f0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a3b6fa288dfba6ef17f671e4150758874a05b989","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.40-renovate-non-major-5a4b9b58c06236b13b525166c9191991483b37f0.tgz","fileCount":99,"integrity":"sha512-R3WBfPM04tPPwIoLK1+TexRixJqwfSektRZRrehyAKmfEqRPvsm42TqnxTWSkbKdxJ/GKxtjRK5O7+6texA7TQ==","signatures":[{"sig":"MEYCIQDz1HDUjRlm0gbo0WX08bHIfj1aXRt+shw+PLQw65e2HwIhAJFqEbMfJLH9g8loa1dns67T8c263yXViDdPK0WoPryH","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":674591,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgbFwUCRA9TVsSAnZWagAARC0QAJTyLe56aJ1QIJLFKy99\n1i5A35DFZotsXi9pilO1ma1oLaQRlFOoTTliAc7E7NyyyirLruk75xIgKpv7\nWLfNPVaorqVmWlaVrqN79I/mBg0Fy5Uwb201Ya41y704dbBiA5Ot6rpOH8Ci\n7BXj9ssoPxDmrsbrosvsOF1ZYBN6Ud17WvwGcPyrMG86BjNJeHQ5hPy22IP3\n1xbopUwmqERR3X3f2iNI/p490BIgjxJ/3k50qoBMYb0HKoyUHl2odEstl52L\nDG77VQUVLXagaMts09FG53kmDWY4I8xeEiNO19LBIHu76G2USEpi8FXkuDba\nW82+iA6CVJLexcyqQQnQHHYTSPkdzFtz+7oXrtbhcU9cowBkXPnTalG4V8qa\nd6Ki9I6bwg+OwqFBaAK3kfc/s4LimPwi3pLa4EIuqurDJoeXxJBt/+jJJGzY\nWoR030S/o4kZSthv3K69m1U2Q+0CV9bTl8ysVXgALEl3/Jle4NjmCjFOqa5g\neW54ejgTECmPq7cXJqAiJ2k3R8pl3hfxt5Q9R5Cy2vOojeSJNOWzpCNDRMuj\nU56/XsW5a9RXmLss5YzMMlWY5NmUEdkDD/0dipOogrvN0ZNsUkWhLnH1VaGn\nJKXLfXQzrsuLlQwtsqKCCop7sO5vX3kfpNzQB+OwONWKEXAUivAFv7x8fvCk\nBayI\r\n=2Hjp\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"5a4b9b58c06236b13b525166c9191991483b37f0","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.83","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.5","@balena/jellyfish-logger":"^2.1.22","@balena/jellyfish-metrics":"^1.0.41","@balena/jellyfish-environment":"^4.1.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.118","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.40-renovate-non-major-5a4b9b58c06236b13b525166c9191991483b37f0_1617714195244_0.6592039147060909","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.40":{"name":"@balena/jellyfish-core","version":"2.12.40","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.40","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9b8b50cf761cf55fa0e8eb951a42852abc567c22","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.40.tgz","fileCount":99,"integrity":"sha512-BRMyAlDPbSJhpNsLEwjXuwSqx4heBBIlfPsW9wW0SB/BUQaKv6Xd/VOIIYsNWNR/DlUDkp575YaArzQ1elPG3w==","signatures":[{"sig":"MEYCIQCPMfc4P6PV9wcMk1FARcCjsg3vXfHbM/bQFhE5ZS0U+QIhAM+e2khAjkG7JRcN0PNBG0SWPqjdu8agr2AYw8fMYum5","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":674531,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgbF3eCRA9TVsSAnZWagAA584P/ApQiPzEujrtwzgJdgRV\novhLa9lx+cG3ztFhB0ffQeOJ4HI2aqlpjSrIIEbh59EhcUge33T7ub+I7oC6\nizhsNLARBEH72369/FTjGWbABH8VAtQ1/m0z9T5j97SVh1YQ6QniDblmZYrX\nVnMdORHde3AwRoyK1fDbRPCXYn062odZ6+YC2cixdXbIgu2W/76qPy3P8PeT\ny2RndU15/tTnVLYYIOxztfqh3ZCdAY0T1mBZmmSmJbwrwt1OscTLoVnKrGhy\nbM7+3Wq61ZG35SvI7hu31bZUR2E5tt3Xytjw5OKXrr3nWRqiYsc+RtH/szDj\njWuHqvg+a96siHC8ISnaAcyIbCwwGCL6bGAnhiHDr4eWmmy9ksRu3Srpma7i\nKlQwVF2kfs5x9udx3sNBq3LyQp2wPOQMFxAukiuN1vrieqSuyJEUaU0PuuA7\nIcwJ9KEugzO0leY+1MZ7XMIcnOQfvR7xXq/IpJdNgPc6pWpBm6rl6REXywho\nEPUdG29SFvDfLCmmcis8St1xTPClh6QDdQU7f4heAz++Zq5NLzlz+3yPtzLk\n/B+M4uA48WTyvXJuxJGX0fmy83JuNq0xQDx3gy/E5/pR+y/7OOXziqsHcC2b\nUr9g8NHQ+SqS2/g39ey0fBQ7nCWDBESKzUr93juUvEP1XIzR4fp1Fz9m5o08\nx86I\r\n=tl0m\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c15a73af0740db1f1e39e50a241b5a1eb8827f1d","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.83","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.5","@balena/jellyfish-logger":"^2.1.22","@balena/jellyfish-metrics":"^1.0.41","@balena/jellyfish-environment":"^4.1.1"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.118","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.40_1617714653734_0.005550901140773945","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.41-renovate-non-major-383a413227cb9bb6b23f672cc2f997e1352d31f4":{"name":"@balena/jellyfish-core","version":"2.12.41-renovate-non-major-383a413227cb9bb6b23f672cc2f997e1352d31f4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.41-renovate-non-major-383a413227cb9bb6b23f672cc2f997e1352d31f4","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5a9577929790f3f69bfac813e6f1e98a128fd783","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.41-renovate-non-major-383a413227cb9bb6b23f672cc2f997e1352d31f4.tgz","fileCount":99,"integrity":"sha512-Us8wTAQtxKUrsXBSPpupVRJd0Ky6SClspaMygdnH2Ra8/Xtm8tkoUTjZOzmGSdmRjwI+b6ZfTtgvYl2U+EWWbg==","signatures":[{"sig":"MEUCIHKW5Ys+HMIWTVDemm690YAUQ0sHOdJIqyrjlMqzUB3BAiEA0PdNyUuEA1ZpeVW5SxCMKbahGnZxf5PJoHRlqa/XyOM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":675018,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgbIdwCRA9TVsSAnZWagAAKBgP/RdS9CmDMoIyHE1h1CNO\naNewAkLstT/ZTDmpEeiR1NoRfEWpG1UNXUMMCz1qm85ZV4hjsg2BwC7zDayx\nqUscGLPr1qn4EFG7fmmCKMod6PLPormZ2tgV9Y6GKJG7GygyoaTxkA80yY0L\nVjZ/vVGZvNHpdpvL08C6ZROMoMmpSGMo/jZ+S0HI9Mz/rP+OIhDvERIRnV1W\nLKvRXUmvGixW3k3utMw5jhykeZKaNdDVivOX8O3WYRXf9OQr5xSzXpU6wU9N\nOpE/q09Qvop5uHb0umTrqmW26b/wfnArGYWDF7fzPj8VzQ6m88yUf0mvnMWs\nKsGSaWuij0jq3f2CkgWGH24bYjrsgI44HFJ1OWiI2RyThhZkGOqURhdysTGK\n+QRVZQL7j64tyMzH67JuBuskxZWYcKe9fG/n0ROcN/vnx+SA8da/4yJ0/jzY\nXXRlmacs0WctE0fV/sxFujO+YeIRmyM/Uw9q2AmdYLsN/Ji8wg2lOzypukdT\n1CZpAfbgz5+/aVY+GkG/9/afrJIowYyLM4b2mUxFzizrPShVRQoopIMYjtiT\n4m4fMfbnNnR50oFPPQpWOmzRSaxPzEvCTdT60VN6DDlUSRffbalibD4d2Q6G\nRqx2yXXsvcdEfwKIlwcTiBg2WzagolnY3qvWWZnDJA27z8MEm33jC0W35t82\n6yN9\r\n=qbyt\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"383a413227cb9bb6b23f672cc2f997e1352d31f4","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.83","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.5","@balena/jellyfish-logger":"^2.1.22","@balena/jellyfish-metrics":"^1.0.42","@balena/jellyfish-environment":"^4.1.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.118","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.41-renovate-non-major-383a413227cb9bb6b23f672cc2f997e1352d31f4_1617725295896_0.5721858878424966","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.41-renovate-non-major-3eb6739d9d0a812b0fe7f2d1aac6d1f1b72ec0d9":{"name":"@balena/jellyfish-core","version":"2.12.41-renovate-non-major-3eb6739d9d0a812b0fe7f2d1aac6d1f1b72ec0d9","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.41-renovate-non-major-3eb6739d9d0a812b0fe7f2d1aac6d1f1b72ec0d9","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"90cb76d3f898a4414062747065062c53efcc6f6e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.41-renovate-non-major-3eb6739d9d0a812b0fe7f2d1aac6d1f1b72ec0d9.tgz","fileCount":99,"integrity":"sha512-NzZLudHCFqhVTtLOjCsp7Sj3HDRLTQDcGYe+jPZqq+Xnnn7f4JBPXFifrqiDichRGD6Khp8VBq5sKfT92z1SSA==","signatures":[{"sig":"MEYCIQCut169XKaWWg7s0z9XzwXGXQqR6D9A6QQKUKZkyr4VRwIhAIzZf/ypZYfq+LuAR5Ww1SOi6w6EqjSiDHnpqADZEtzU","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":675018,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgbJ8ECRA9TVsSAnZWagAAvhoP/jzClRm2/G02hn2br/87\ncpQDcvKNdhBw/0Gh0naBk3F3Wl3pzdeMfER8o/C4lizRvrN0Cn39mHkGRoZx\nJU1lgr5aZFBaxOVcstfw2Xm4oS6Sj3MhhOLwPoZ4XjxzS4y2HjkmkmWqPo4F\nWgeM0a5ZvQ5RJy2r7jpSrXAuHjPcHA+dyOAoJuRkSlm2SGMIY/45DMQ7u+0v\nyq4frhFliIm0zOhk05JwKI8XJXWf/6emI1gY/Zb8JCUA5GJ7dyu6Y25Imyn5\n7MgxszFVebLQCP+DgRNIOpZaLpFh/x2bvuhEH/vBGcb7KdIhCqDzqbctnJhn\n3DOJAcIyCCzHu/PLrX+e/uwSgDPrJ0rNoCgPdky6lh9Sm33BjHP1rcyxnM2T\nyDoXaPBmxxvuZ8LwBzj28a0GSUcmVgOECbe/Rey/fXReDQQLTi/LBfcgSVEF\nLRTv6ITdBDBDMKcmWs9wxqane+ummFN+qnJ/AiJrWy2TAa5KL+xNTGAYCGiK\nLe7KeC5qVFqc/xu789zAKibFRXhckXivcU39ShcdLlNuTbD1l2djAElC4dFV\na9e6frxIDxVTJZYF3vBQ80oqXrg1kMZHbTNkJl5yLtRVyaGqJNnwHArpWQE7\nG3iT22FuJkLX8bhqzf04uZORIe8cruE/YVgQsOMzylNbOvUS8cE8kPifuz7x\nc+mf\r\n=9DMh\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"3eb6739d9d0a812b0fe7f2d1aac6d1f1b72ec0d9","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.83","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.5","@balena/jellyfish-logger":"^2.1.22","@balena/jellyfish-metrics":"^1.0.43","@balena/jellyfish-environment":"^4.1.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.118","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.41-renovate-non-major-3eb6739d9d0a812b0fe7f2d1aac6d1f1b72ec0d9_1617731332224_0.5749741927611307","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.41":{"name":"@balena/jellyfish-core","version":"2.12.41","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.41","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"08eb40eac22bf5be2cd78add461b68dd1252f198","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.41.tgz","fileCount":99,"integrity":"sha512-VRt8t0YnpeyHbt62alN0EOWlJ//WPugI4d7bYYnyGePmD2WtORFdk3URzOysOcE1yhcowKYcZM82SXLzjHfYbQ==","signatures":[{"sig":"MEYCIQD3kweQGRHfa2FPetBXVUua68Q+61KYIEkie6bIvX7DGwIhALbUcCywRqYj9mTmSrg/Et+XW9APvX0pYvle+mIIm9cA","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":674958,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgbKBzCRA9TVsSAnZWagAAe5QP/Rh1lgHuwI8qO1uCPLpH\njE0RNHwgiSxv7+/FVT2gblzNufYI59IFx1DcS0dTIpvkSuhA66xISWvdQZmy\nVxymlI/8WwjBuxu9xbq74wb72HIwrIdP6flpjjeP9LMJrildhFp9SDU0ZJ9v\nkqFCOjRox4AOjDxdKmJ/5I1Z1jWfiVPh7DKtqqABnLX5mubMeSN4lek04H4l\nTFMxhFYJl9pFhN0OEICA7IibuOdH4hvuaVOw+29tJqursUdxvuInZqmpBFj4\n5B4veCDln9CYCU+Ep1WCYdFKRVqtL5LOR9gy8717NdQIdkZn7rr5KehjtdRa\nZA60hNHNg8NTMFpqzAtK7aKa7QceUIF3JKjbcpGe3YARwePB1y3CuFlQU5Tx\nu7g5uX+Dd4QLykvuYv/t9bpGQUIzvV07lMY2olhmeGNmPm2LlqOTIzyeoEAo\n7azOY0RRSF+o0QKY/TvMNQ6hPMtXJTTJm+Hr3QHAPpgS2GWnJASPDdFqdGlK\nir1RvVUjkmqsv0jIYK/fvnH3HWWo11HMC5YjdQ8ob1m2B4Mi0g7c/w8rAa1K\n75+ZkU6lLztsKTABkrbKGsiy2K8GOpUV33aJTvCq2dYOoe0A6aheRvwpb8Ko\nYbyOqe7PRHMzFdfspUQwxFlQaIQikDuum5MKlHwhabkHOxg+HAhu/vL0CNWK\nZf5T\r\n=2XpG\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"82fc971d96f12e9614a56173b23826b172906e5d","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.83","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.5","@balena/jellyfish-logger":"^2.1.22","@balena/jellyfish-metrics":"^1.0.43","@balena/jellyfish-environment":"^4.1.1"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.118","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.41_1617731699063_0.3011090661913971","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.42-renovate-non-major-cef3af3f35ed4f1aced0d775cd538cb95731a192":{"name":"@balena/jellyfish-core","version":"2.12.42-renovate-non-major-cef3af3f35ed4f1aced0d775cd538cb95731a192","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.42-renovate-non-major-cef3af3f35ed4f1aced0d775cd538cb95731a192","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"df0a8a2862aeee4e6d7a1ad8b3dcbfbda1adf55d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.42-renovate-non-major-cef3af3f35ed4f1aced0d775cd538cb95731a192.tgz","fileCount":99,"integrity":"sha512-H4pCbl1FtF21C0lc24G0hKIc19iazmP4Ad4j2rvYQPK77Sz5Lf26JBsxAk/BrrLc68yX4nJcprVO8Ns3zNtVkw==","signatures":[{"sig":"MEYCIQD9FSAyJcQL/Au1r8trbCkksvS2KBPXNhovFNtsa64leQIhANZqkdozhZoQrYFl8T3owuttihjwncMK2Lxiq+xQ9/45","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":675445,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgbMp6CRA9TVsSAnZWagAAcHUP/R9Fmz/KgUzvvUVwxcRV\nLN6onO9Hcw85eNgqTbOGmR/ELFARBXm/3/X9Q8zIZQWtkDLMgKDhE7Nyuz5o\nEhNxdELRUepHExIXgKZKj3JSronZXzEQjuwvw7K5KM2guBhFF/3kqgl8ne5i\nmsPF6upvVpwmjAaIslnbMymbuFau53OnvCGhb93abpFip7VYtt50wrXQ1Y+X\nEYsOxmJAvD5JV6v+7uiVrk68RWZFBNeBlPxLzKO+zwlbdMd2Az1/g6o9kSlL\nsAmM4wh8LX/kWhox1TzbsqaUNaPihw6+y5WQ6TCqyrgreBQH5udTUxMpFH/3\n2gAe8JpXHq9/pQpugBfjg1Qydcs33V9v+mYYyAD4SIcyime+RKL0bMNnXCin\nqT9hATPfcnatUrWVHvGyNq7BSyBuXI8ICJOY5WOhUTTk87tadfnS+P600RBR\nvf8YcOkLWQRSva4AZ3kb05o1Umu5k3WrCpJ4gAqVE1a8/0UlSMJXv/UCj28o\nHhWEMi1zzM3ZnwYBHNiq99ZW+9GXqlwmGdbZGQZnJPzkvEUomMHzCo5HBkVH\njoGmL/kpMARl5wpbwxkbFegLxkbHOXqh9sDNqkuFWlYuNfcgSOZ3gppEXJYZ\nKu0nNX4YC5S6epj6zKsDrSA9I/kCGqQ2d7riM2DYlRopNU2M3GS2SwCLGWuL\n+5Ze\r\n=6oDN\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"cef3af3f35ed4f1aced0d775cd538cb95731a192","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.83","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.5","@balena/jellyfish-logger":"^2.1.22","@balena/jellyfish-metrics":"^1.0.44","@balena/jellyfish-environment":"^4.1.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.118","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.42-renovate-non-major-cef3af3f35ed4f1aced0d775cd538cb95731a192_1617742457735_0.3525766822384062","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.42":{"name":"@balena/jellyfish-core","version":"2.12.42","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.42","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e645b4a17b88305a1944a6723769487ad50d80f5","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.42.tgz","fileCount":99,"integrity":"sha512-62Ig77qcZQpO2Nzklu+y1QQ9VSkErulK+DXZe4jojks94te+iPsMDjQGBtKH0K1TB90+HPePnICC7FAv+koQUw==","signatures":[{"sig":"MEQCIH2i2DWodh3rQNh2GQH9srqgnTXdtI5UJk0A0eKOr5RfAiB0dk2956LGH+r992k2k0BippAmdSaCK3S0FnGQnha8AQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":675385,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgbMwdCRA9TVsSAnZWagAAA2YP/3hCR6RLj/i1Ww5YKq7m\nkScQlWpzvzOEckD7gs4eAtCfFULSrDIOvRnLRBrGsjW0b6ODm/JbVNPJ8tjo\n5sIc3AS3dp07CMwGeGL1ZkPiFwLXzgPWG003BnE+9XiFfwNWTYB7fHHuObFs\nxwwMavkuSgRJ7vd1kmiipwjgJYAFxXJqqtWgBQTqbcd3CRGY2H6wRaih2YnJ\n1wKFKbfiUs9u5jTyNF8SlRe4MYMS2VcMTYQB19sWmPubjBTc30gwXcCP6MUd\n4nlZgbmEv6DPqjujSOUNp4H0POgXAFKwnazDYS7NMhHGeZjf7Pui5zMh2gPB\nNf3k+5ukws7XJSJa8GkNvWwPAosDCNSRXq28eNsAhy4WF5pc9gDPanRO40rx\npsy/RNGiUngw1cRXSHIMMu7/Ihg78yXJFs4M8G/nZNQtr91mW1xZegOLjHmC\nlS2Uk2QR1AzhN9hg7NGxjCdPLfjwPGA7k4VV5csn+PI8dHyBjh58AR8H2Lj+\n2U5KapySTv77OV8UxdhcgOHD0W35wgwAWbuNM9I0pcQFjXrR0lzahxuLBVBT\nqZGEAen0quRRMjst10b7ObVrqEtryMvBh/A28aioBPoGaYkIL7x/RNayf/v5\nR71R2CugHBK/BWVjSpHAaJhk0tDV9qDG52QHG/K5SkZPj6fQs8vk8BUTuhdZ\nh+5r\r\n=p89B\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0bb249541a6c1372ebde66da6480d0f7164cee34","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.83","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.5","@balena/jellyfish-logger":"^2.1.22","@balena/jellyfish-metrics":"^1.0.44","@balena/jellyfish-environment":"^4.1.1"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.118","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.42_1617742877175_0.9686712466062202","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.43-renovate-non-major-82b1ea06c69254acb25270ba930103499652036d":{"name":"@balena/jellyfish-core","version":"2.12.43-renovate-non-major-82b1ea06c69254acb25270ba930103499652036d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.43-renovate-non-major-82b1ea06c69254acb25270ba930103499652036d","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"739ce5c18852a741205f35a6d0e30196ede0f87a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.43-renovate-non-major-82b1ea06c69254acb25270ba930103499652036d.tgz","fileCount":99,"integrity":"sha512-cypQsiqLWDuFOpmcSZ0c6TAL61kdMxBU5Az3ESe5MOw6+Bdk7MZTNVBwqi+mPUha3hTAF9pvxWcw+lIJL/2+rg==","signatures":[{"sig":"MEUCIE4RvxCBbOapfhnFjPa0zT+J3P12klAiBq/6K7yfHmmMAiEAiL+xpAnLGyq4K1K3tthEhe9/GYjZE1xHvw2ZXZb8NfU=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":675872,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgbN3vCRA9TVsSAnZWagAACiAP/1Y7T2ddCOZlxexD8ajK\niKdKqZGmZist8hU3OopW+2Vr2PoI3glFCTxPdu6OhdPNrfkb4k5wjEub5cRU\nZbLo6BFaXunmMyLrG2vbeOEokTkDz+kUFSqAwUISWw/RZZqeN0JEywGhBg4z\nt/mKlhiAhUm7Iz7omC+BzwDvbLawNp5WNb4c51xNrhEv04MlQ8AxdmGCYnXH\nP1aon7wCDDo9TQX97Rj6JnL9zw/1NwH4ZNtrzYV8Pp3+n6pu3KqHH1IftpUu\n2C86tTRRz3nrLkG55JvDNdVlLu4fKBVndppv234ij6zmI7xVq6lYjBCoiKWG\n4jxPo9sVL3EW1yBzA4p9UUfW+Q6m2n38PUyV9+nAXm8E3F5EjqpeWdN21gZS\n3pcoUttkyNXJg7/gwk4a22IaXLHuwjlh+Lby1b0r56txxtvujzlnD6HxUIqm\nhyw2v+S59eVIKMEomtlXjm5+tjWDySH8x/U5w6Pj9UTvm+jMAfVyQM0XbLqE\nCcGh7hE+Dh8LzX7wvxRFRSfgs8B2h48z76DwY0k+GaX+IJzjlGOafiTBXwWp\nQ8HSbtMbvbBql41CxBGN22z8n0xehRPAmX1GyCpVd7ogaHi6nZkIoK+Rw8BV\nE40od8cHnx+HhDxpEdhwSi5j/YpqV/CsZ8YMT+USi2+zDyPb6Y33R1VAfAtE\n501r\r\n=L9ei\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"82b1ea06c69254acb25270ba930103499652036d","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.83","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.5","@balena/jellyfish-logger":"^2.1.22","@balena/jellyfish-metrics":"^1.0.45","@balena/jellyfish-environment":"^4.1.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.118","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.43-renovate-non-major-82b1ea06c69254acb25270ba930103499652036d_1617747438872_0.9670653358031027","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.43":{"name":"@balena/jellyfish-core","version":"2.12.43","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.43","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6abac1171613d48c1c05bebcc94c800890a8d6f4","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.43.tgz","fileCount":99,"integrity":"sha512-VRlzcNi71RBDFLl/HJ/ZSpijqn4aoh1SXIBRoa5AcWQ92B8oW8CIt2ZlXXzbvgvZc0iuGWfzc2rTZe0VER0TwA==","signatures":[{"sig":"MEYCIQDaquqNR5++nUxofyr91DB0MGiWjHFKzKn+3yfQpyDx3gIhAOVeDfaXFXB+uiGHNEZWmxDX54d86ND6M+ZRyIz/jFUz","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":675812,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgbN8dCRA9TVsSAnZWagAA7YYP/2AAIbdZR86G4DLCFE+6\nKMRRYKbTLkY7c99mzgrd3RNvYe3ryXgLFUVw8vuWEWg2KiwGhAeqf+J0FQke\n1f65wq/iXLJuQRbrTKjmJOFm4iCqsraTV5oTz2W68TksKUy49KugZIJtIlSp\n6Kpt/eY9tU6XBy1ANlPJIHrGjKPxjyBO9vKFvifTi98WzkoFMdEjJPgfbST9\n5RyLItDDqt/W3thvl0vdnDr0c+ZkCywPjqEWynVftaGWMwaUWzXBzw/jkulI\nVseqj2yBRuyt7DN5ex5E8VA65fblD8lBCBSMAsCLGCKyVjmjRgawodhZi0/A\nz8R7+9GlBXXSwsOvhjJsMTi5rESdvvj3MAwS59gkCoq6MQr2IaUQRIGdXzpc\nwWleizNGOcDG7KhHYDYoMxmSvt9PGoKguT+3apmoljtr+bMT4bUimIGUZqAQ\nGOKa+50DtoONnZC+zU9l3ZsdoNcUm47TAxep924DFhfnc5uXqXsqj3n+z2WV\nRGaUeyVyfgK+i5IkADmr9Rmr9Ou4NHdBMnCQdQK+uP90uGYm4YvnMg842ccD\nxO7DZozboVz3P/G40M9WBk/RLl0wYRWItjMJuY2xr8tBoeYfxbKo5SS1kqLc\ndonNGMBBgDnYgAhHqZ87I5BidCOV3qR4zHskrBYTov1qAunncYcNUHrwyWbY\n4nwk\r\n=0q4e\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"1709a28876ae56c4754a1c0c490704aaebd5d99f","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.83","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.5","@balena/jellyfish-logger":"^2.1.22","@balena/jellyfish-metrics":"^1.0.45","@balena/jellyfish-environment":"^4.1.1"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.118","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.43_1617747741304_0.10264451246637885","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.44-renovate-non-major-670620cbb2e6c3af41c38bdf2bf0aa9550bc8928":{"name":"@balena/jellyfish-core","version":"2.12.44-renovate-non-major-670620cbb2e6c3af41c38bdf2bf0aa9550bc8928","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.44-renovate-non-major-670620cbb2e6c3af41c38bdf2bf0aa9550bc8928","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3d032f9ac0d68bbade3a398cf322840ed908f801","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.44-renovate-non-major-670620cbb2e6c3af41c38bdf2bf0aa9550bc8928.tgz","fileCount":99,"integrity":"sha512-aCPmT9ho141XlimStnJnYEE+/RYTMF8ZifwC/csyKbm8tM6zLABTytOIX0J/6VRcsEAGNKY0m14Rfoh7Q1MydA==","signatures":[{"sig":"MEYCIQDkTJBKT/o+69OqXUcTsYqxrodr/CnUcwyxoiFC6mrpYQIhANemvgxAGh3iCnhiuY9cTEb+0Nf85leb2ntsoBxLrBXu","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":676299,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgbPDVCRA9TVsSAnZWagAApBEP/2NJajYolV2/7MVN7mYu\n/Iz9n5JZhH2eXdEorpJKjxGMrqXSY3sR810Dy4g1iOERgdU2E/VX5W/aXvy5\nQrjedcXXwJj897pv2o+kv+fRrbofWh1GjrfrC7KH5Uyf0/9up0X0tYyHE9Ol\nipva+Hs9PMEfuXb0gp7FH+4F8YlgaNm1wEfJIGsC4+Cg90/RL5eTddyM9jaV\nO4YgaNBF7uPNNt+N+UsOV7VSjaEqq0tEPYSqVElfE1zimDTbnq98xEz1cdQK\nv1Blwn7pnhUcymP4qKNsLYM0r9W3Y7+JkV/PvzLglEvv//HAXtqdNNL3db8r\n+TFNIqesHtK7hkLFptHVQaMvqDXHdVe0TnykJoNuzeASvKchUiR6nKvb4S3Q\nMi4/jnxyevSkBM7RMEyBNycmsN/8/VbMiPCnAJdp86tKENr0q5I2UKDzIjeZ\nbZpiHc2OH/KHsN+j+sAHbIm2Bh/WMnJi17vtc/7piRTujaYSX7MkqvB+oT1o\nVhefhRUA44B7AxfCPK+Oq/oNQyMXNu1Afn0RrDCiy4ReN829IVADSgToKmXl\nUz0hjNWgACEvr9l4tnRdub8dmR2fjISIG67O8J8qbd5rCDj3BnHU7jtyyoMI\njxQhCeB3wfTBx+39szCnLVQ4JxmUJ7JbWnoA7VFxYQUGIYLuyftc0UyvOSPC\nmj/q\r\n=8iJw\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"670620cbb2e6c3af41c38bdf2bf0aa9550bc8928","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.83","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.5","@balena/jellyfish-logger":"^2.1.22","@balena/jellyfish-metrics":"^1.0.46","@balena/jellyfish-environment":"^4.1.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.118","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.44-renovate-non-major-670620cbb2e6c3af41c38bdf2bf0aa9550bc8928_1617752276756_0.6972294861162887","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.44":{"name":"@balena/jellyfish-core","version":"2.12.44","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.44","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9b6d768ceada22b82a4ee9123c7c762d613d4447","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.44.tgz","fileCount":99,"integrity":"sha512-482A/3dGxF7woaiJ9JFZiNXbOayRpcem/vlldvrGt7X1kh9dLkS5E/2XD64buaOOXHCqDBhuVNhPmKv8tlDNTA==","signatures":[{"sig":"MEUCIFl4Szl7iy49N69hGacinGo5kgkADqiAvMo9qimm+9O3AiEAvSq8dSw4hZtaLzKFwGSKfYcs5H3rvFKqEa3SMWFOnL4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":676239,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgbPIjCRA9TVsSAnZWagAA/eoP/1BkUQHWjXJH88VzZUMc\nhib5i6rlErEMaCPeB4VzzELQbhewcswYUtU8MYDocw1vVnv44UDvrkuACt4H\nN6Gg3Ci9hNdDpi3AAgiZpZHDBDxZGfXn+FpaDEro70Gfb4Fc+YMQNu8F3XU9\n8sUQ4hr4NdLzR5/8oA2N6csAnTLWVaTx1UFuSkmpPYf/h/t4jhw2Nr5+VDzo\nk6iQNg5ISORasTaV5ianZ5PFBSM+ASWvLh/+AwF2gLunLhbahst7LCeF1/2O\nfps6IA3Rp71rYvcf9l9RdFGYUEuO1XVT8LCngzJBPaZoQsXocoyTyZB/Ryo/\nL5tZc8MDRsZcBOb+aI1DkFRf1t7he/eHU24nN5RCvO5BGZYL6c7h1UYL33hN\nBN5DHohaTpUR+qP8P7sgBjzpt3QY/xhmR0cTG7p0CvOPZTbSaIsZas6BIwDF\nvvlhHcixLogTyCMmQljTFvLWGdI8Qn1InvKc1aSKzSkwA6X28rira8kVgC3y\nkpjO7THaZqMFPoTz6pCszxxcpLePbDtjmBx1THjf1DhclWWCFm5PBXjy/7lH\nz+HZrYxI0EZAjlrz099aU0kO4hyoY72EmtMZfGEKmGDkVMU6eorgDgD5VPQy\n/gyBZCUPoSHJ4U/1lMS0OGjO8YC6AvWmg2/MsMFoETNXxOi6ECas5D+sKuwY\nddZQ\r\n=dflT\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7bd32e97de59ae71b0157000bccbfb17d276dc3f","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.83","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.5","@balena/jellyfish-logger":"^2.1.22","@balena/jellyfish-metrics":"^1.0.46","@balena/jellyfish-environment":"^4.1.1"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.118","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.44_1617752610960_0.14670946413860753","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.45-renovate-non-major-10c4d7b19f58c72cb218231f7831f9ca5f5a6737":{"name":"@balena/jellyfish-core","version":"2.12.45-renovate-non-major-10c4d7b19f58c72cb218231f7831f9ca5f5a6737","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.45-renovate-non-major-10c4d7b19f58c72cb218231f7831f9ca5f5a6737","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3c3e0f7811f45e4fd009666eb6beeab7bffe607b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.45-renovate-non-major-10c4d7b19f58c72cb218231f7831f9ca5f5a6737.tgz","fileCount":99,"integrity":"sha512-K2tjyN5r+ShytpnE2STlz+DAdVgblkJ+MuaNULgnxzFgv4fq7cyqy+RNLgkyMXzO63PFxObRe0TLfU9CKXkbqA==","signatures":[{"sig":"MEYCIQDWbbEcLbqRqa/PVYtZq85cy1DRZRMTdFpJLE2+JD3OAwIhAIItb4ck2Zr2NNAHS8SoaRXcNn32XRefseuBrLCCz1gD","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":676726,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgbSGqCRA9TVsSAnZWagAA7j4P/RtwbQq6cRwrP1TTP//r\nHmqPIs+RETGLinoCKCJ2BAXnJKm5b01q5xLOi1c6smCjc+GcZjqJFXuK+QsT\nPMD4O4EIlXMj9QBF/oXFykqJqa0P0uVLeqDb7f7axwsfLXHMvn7gYk40jfw1\n8WHomyk5oqI0jFbFBFYPP4gk2kegkyiB9nKMM7NlMwO+R80xd/j1tx8B3gMV\nC6cP/SYO2I+WR9An1TQGgRbwNBFxNP+J4wDNBRoIwUBsbbefCMVTUJ8x2zmj\nisOk20jExbCxmL8vOpD20F6mC+rOfEbfi9NdogvFJIRdrImyP9sQ+D/IKgm9\nMefxX3CUgPll1TzQb6ChNQfIi8DM1FSfqCtzB6OGeLidymK3ez/RylFjb/05\nSRQCKSf5m5Bz7HhXwi/fGpcEqPSyM4BwsyLctZFw89CE6GErUUa7iGWoOeKO\nGgWj9F02BEN8TSMDB1aUFSRwlaOCFFOp5Vn0Me8zMvhe+TVpHnb1YWGYVRTF\nFbcjcA+M/l1dRKEuDsRdeI/uVbsHaGT0nDMzKjP1jvpd0WZC+a4hUIGxx+6l\nxtp0zmXkx4g27X4bpOwYp6eTZDj/wv95ArpSbwQEEHV5hZM9Tq0s1kFfoQ1n\nHw4sWAzDac/tfSvO8ArlsbFiPL8o3tbliDltxcxJa33h+Mtesms8HNevA5KK\nj4yF\r\n=9xmd\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"10c4d7b19f58c72cb218231f7831f9ca5f5a6737","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.0","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.83","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.5","@balena/jellyfish-logger":"^2.1.22","@balena/jellyfish-metrics":"^1.0.47","@balena/jellyfish-environment":"^4.1.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.118","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.45-renovate-non-major-10c4d7b19f58c72cb218231f7831f9ca5f5a6737_1617764778205_0.7699287467413671","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.45-renovate-non-major-279c9fa4f62718631039d653cada6fe27f980f98":{"name":"@balena/jellyfish-core","version":"2.12.45-renovate-non-major-279c9fa4f62718631039d653cada6fe27f980f98","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.45-renovate-non-major-279c9fa4f62718631039d653cada6fe27f980f98","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0db844e3b3fcd0105afec1096684f3c05b8aca43","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.45-renovate-non-major-279c9fa4f62718631039d653cada6fe27f980f98.tgz","fileCount":99,"integrity":"sha512-/bnebowwQ+QTT55SR+TmzE9WZQtFO0SYmDQT4+Et/nH5AUzIq2nLH9ni2M6t41SLUn8D0CydjUxlT2OyiCQBcQ==","signatures":[{"sig":"MEUCID9KXi5gvJ32j18hxNtzF5CkBEYR+p/z1Y/iv+W1FebLAiEAlYZ+Gha8PhX5aoyacRjWATACfsCcUpbrDWP4EAp9uEQ=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":676650,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgbbeFCRA9TVsSAnZWagAA+sEP/AuoOtndLrECU3nn03Am\nrQO2jZC94ikdpnjFnxxKSSZFVV7dcRazXZvVNJiU2e7mC3n0R7bqwKAsLfah\nsZIWwvbrm+dg9K8K9LDK4cleIvJzKT+bgz97FATGsXC8AwBXF0+2Kxy8xiX/\nB2PutgpF8CHVrN7JKWU+0gPADSAYZH8GAcnsqIcR1LnFDBspJM9wKI8zuuo6\nZlAoqigd74Ydzl78VtHzwSHXgTNq5bFmraGYZ28ZLw6ZQCc2XzZvcIXuaCal\nKgxgwv7ACVCm6qvEbLYqrr1eHEK5+qOx/+WHHZClkjsAvn4P/Ba8TJ7kB4ME\nwva/e+Vp5k9l999zpEtCEHb6m7aqy205fb68Ek1UE9CmDlbFDv3t8Ma7fYIh\n2JEgbWEheIxbxDt3YlcdW2JzooFC4lqdRqH1lW1aGxdpzJs74xepGblLPYE4\nOYNZdqqpMhdcuUGhoal7QJEy/RYy8Zg5+N/y/Hyd7jkoqbBRHfDV7BW4H+Uo\nDhw+kvhVmGnOz9jWlHuKlHAB67BuS4OmHesUzngifil+wGMJCdA0t/5C7SLq\n3NLfkjLb5dDQJxisG6Xw9tdKs4TFBP5pRV9WEz3JBi/ovz6aLVYF/0V+Mg9h\nIa3wC0MiuAi0V0MltATpv2H9z6qoyFGO9mdanwX2V2X9eNP06L5aJHT+aKj6\n+7SU\r\n=KMu7\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"279c9fa4f62718631039d653cada6fe27f980f98","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.83","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.5","@balena/jellyfish-logger":"^2.1.22","@balena/jellyfish-metrics":"^1.0.47","@balena/jellyfish-environment":"^4.1.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.118","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.45-renovate-non-major-279c9fa4f62718631039d653cada6fe27f980f98_1617803141167_0.2725472344848192","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.45":{"name":"@balena/jellyfish-core","version":"2.12.45","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.45","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d7768a4de5ffc2faa3cf9fdbd1e8178f5268dbb4","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.45.tgz","fileCount":99,"integrity":"sha512-WQxPeM0T7sVgtYB0JQfL6AopQCKFN50gSCt6kR5NuwWmCL6waE8FzCSPmqkn9U+zQkYmNIgouOZaqt2tOgtOOQ==","signatures":[{"sig":"MEUCIQCLumZUhAnORtU/yfphAccQXh7RVvlPIe4PgvicohL9sgIgOfk2hAY3DYJvi48WJvItTSEzeK2SMTrSJ5A9dLv4pQE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":676590,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgbbmXCRA9TVsSAnZWagAA2BsQAKSATkiTr0kNYAWoFRKZ\n3Z02+CEGphkQgvcoa7msjaqD2nm8UvZvlVrkPvZ1ke7sk911QNwenkegmeVT\ngl4gYDgFV65bwiUdNV9NSk44wXIJIgajP2HjaY8GNRanl/BiZgQgcf6eEYJ1\nukbRG5KLBcOsxCN/0smGIRDiickvY8fqNlsz3UA9A40dzorkscsn1MBFc+bL\nbsCGZ+SivlNJW+kKbG38OKM7tqxGVbGzGwAtPS6y+Tf9XPaPaMmGAFjUzdmF\nGuPE/ym7vtPjvE8LPoPEujJilEKoPHcdGcJE2TeH6ctK+l9Eq95ztaM0gq7L\nrPCDCq0k4J/24WenbM6uyjuVeBRPe3RRWhZX1KK4TJbJSxX2XhFCzAwmu/s+\nhU0DT77GXqHrYH5UXw8enWLzBaGYTanK5b8ghhjN9R5qRhHkqO2FIBNKksP+\nOxqEzkSq0YH7UtosP8KtSTLbVtOhbvIMCgb40BD5RkhzvqXnPz6wKiCx8+oh\nKm0A7eyIBdfoS+yED3RIr14IRjFczC7GACeH8vITO5z11iqGMRss0JG9K3/b\nhhH6q4KIbVJSStn30PzHQR4uwjwB8uatZv2ecXnEkoHahxdTxTLBfJLncbJX\nvNr3ErvLA7DJHOyKizHYkOIc4QlqqpCCGeW3OR9+e77PgpQKUD+ZRRWl6O/a\numoK\r\n=tCS8\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"9c6d90cf0c88533ed3dcbe0acbd0fbe7580d7a39","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.83","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.5","@balena/jellyfish-logger":"^2.1.22","@balena/jellyfish-metrics":"^1.0.47","@balena/jellyfish-environment":"^4.1.1"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.118","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.45_1617803671426_0.364620119436192","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.46-renovate-non-major-917ba8d3836b0b6a1fee33bd3420c52f7552f7ee":{"name":"@balena/jellyfish-core","version":"2.12.46-renovate-non-major-917ba8d3836b0b6a1fee33bd3420c52f7552f7ee","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.46-renovate-non-major-917ba8d3836b0b6a1fee33bd3420c52f7552f7ee","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"935f7e133d7918f22ca10f8c4012ad7328a37e0e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.46-renovate-non-major-917ba8d3836b0b6a1fee33bd3420c52f7552f7ee.tgz","fileCount":99,"integrity":"sha512-XIpClpK+0RSZPH+3OCtBKyd1vMBuBK15uky57cpxqnUI//4ERQPQfdh+ApFLj/V4pu6wqT5xGBUYV9HSOsyUeA==","signatures":[{"sig":"MEUCIAbYWhRH7JsiXR2LeWlyMWI9orj9xxXCnCYMyhQDzxeRAiEAgc/qgnwvDIME1EfW7MzfcuvGBTRa7FdccPTIV2DzfSI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":677001,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgbiDlCRA9TVsSAnZWagAACjYQAJ4mZK63prAgJlgHeZwo\nZiseeXgKraVfldWRc/sA0sjBxYlj/ueh4Myy7pxrUybxUsdpxQe5NCfjqqei\n52t5riXnoDvMzbUobeSikPdp9/YdYStEiF/k8HwHHcjhMMVBKyUHzwlZAqb3\nH42tARgbEg9uRzdHfBat5kZwC70izz0q/I1i5QOzc6pLpQCykrGy8wsRU/6N\nAaITPYmaAzpy0eAPrx3XssN+1xdiQDr4afJCmdkXL/oNWdM3FNVJ8RNBC78U\neA9idJy6LLmmKdinEzsPf0h1+f+Ew1bCRdhn8PvmgImAJhhgmRnD4nGGG2iU\ni57yf78NDeRaLJtjUx3pEBDQ5s/HvtRCHP0EK4Ub3len/CtC5f67V7HuGlI7\nqwZQm/fGWI2p8IyaYdDENlz2s+rwc7Ok6xtaVlsyaEhnHef/6Crb+g9hXkwq\n7aRG5+dzYVdDbeeAQJrgqm5o2jdCKL/eMYKQ1k2JMSUYap+AAMsmB08CDtu7\nW2E4TnFQHchwRWsfzvfR0PW4lNKJ9QuX6Ixm/pZ1/YLXfH4Wt3+MTOMR+0l4\n7nxdMW17SQ9DmRh4Cmz4ZshUiy4Xin0dpyusenhZdvQf5mdTI7AmxTAvxTla\nxeT1nJwvFhlbIrxfkszCZOUDlWjtXi34E2ilzS8bndyL5wuqaKQSnQ3ePu/o\niyjm\r\n=ZTTP\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"917ba8d3836b0b6a1fee33bd3420c52f7552f7ee","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.83","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.6","@balena/jellyfish-logger":"^2.1.23","@balena/jellyfish-metrics":"^1.0.48","@balena/jellyfish-environment":"^4.1.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.118","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.46-renovate-non-major-917ba8d3836b0b6a1fee33bd3420c52f7552f7ee_1617830117153_0.4973682130787691","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.46-renovate-docker-non-major-c18bd02e7f1bea44c8c86308bb9a5d1a76f398b9":{"name":"@balena/jellyfish-core","version":"2.12.46-renovate-docker-non-major-c18bd02e7f1bea44c8c86308bb9a5d1a76f398b9","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.46-renovate-docker-non-major-c18bd02e7f1bea44c8c86308bb9a5d1a76f398b9","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"813526a675b422e1dd9d123a33f5ea297417a26d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.46-renovate-docker-non-major-c18bd02e7f1bea44c8c86308bb9a5d1a76f398b9.tgz","fileCount":99,"integrity":"sha512-nOq0y+cKrXuiGuqoL1MGZKnU/o/zPhAm0fF6rGe/fe0h0AB46+z248QhWo41sdUomFixQ5Vqlv/kHP+PuYZ2eg==","signatures":[{"sig":"MEUCIHOQZVafVXZfZu9c/TYpZaRCMET6KQLrUkC6Meu1ocUlAiEAo+vXu480WgMzytJLYapOECx1Fd4xStcoHhEq/tIpDDc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":677080,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgb2TFCRA9TVsSAnZWagAA2t0P/3GS8zfazer5DFbk5mmp\nMoDQxLSxvF57XbNvpa8RQjTz0inBilYRXgIQPj5F2gMhNlIGNtcG4bh8LKMx\nKDbS0XecbXJWKN9lskiDJ4xYQOUTw03kD6+6HKV+KArcqbKgKATrOGnTFOwc\nR24Z16SNV6kk+AfN6ATFG4N8VpzevPAOOqtPuI8RS7eFYZ8iW747eZtXFpaf\nU3UVtCvpMyZFhguYo6rzOsT+odb8KJT3YVWXoPQGp+VKLP9atd7TnvJhxzYI\n9k0e+7InPSk5EVx9ntuFC+LqhDlAwQXfvyQOi8lQGpV/RbcptsibCwysqLU2\nmQ3wowU+c3ibpTzKKypqBfRqk7ezln5p2dSX344WOAqD6mCBMXJQ+XXh06g2\nvTxObUBqjTbyJc4ZHF5EVe+TGveMdJNGDdjv2eKbkzAnH5MnUOlJvjQYJ4tY\nUciAYNDx2ClPaDkWeX5Qrlgi4kAoTcHWCzS1QCJ1zc8CHSbfyPr8CZkFu9mQ\nM8s8kKFcwvYb6CmAylrCh6N+4Ov7TnEi3cvq1UqdPXdgGZ13L8DXDaOu4cfR\ndXAzDrpgyxw4j0GxAT7kJimakRuqYwQtqfyC4iU2+eGZsbpGLLXdtX0sfIua\nBZ1Vdn2knoj/cQQkT0766Pb1HyBRybUOmyH+jOUCrODuvG/sdPdGcUswFXqs\n53Mz\r\n=S77v\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c18bd02e7f1bea44c8c86308bb9a5d1a76f398b9","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.83","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.5","@balena/jellyfish-logger":"^2.1.22","@balena/jellyfish-metrics":"^1.0.47","@balena/jellyfish-environment":"^4.1.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.118","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.46-renovate-docker-non-major-c18bd02e7f1bea44c8c86308bb9a5d1a76f398b9_1617913029044_0.7707841947352301","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.46":{"name":"@balena/jellyfish-core","version":"2.12.46","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.46","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6345a5ea437b6cf09a5b9a85406120e1ac8a0a2b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.46.tgz","fileCount":99,"integrity":"sha512-REYIqBzEM0s63PK9J0iDU/KqMTV42wIg342z+7sgUrx7/qQBhFtIIXfLSOTUXgg9+IV66Mty6XF0IVZt5B6y/A==","signatures":[{"sig":"MEUCIQDOKxg75flMojh31UeslGD0f/z4ItVwT0mDtPu4NjiVRAIgcN2rnL9TtqFbCI0fzKkGu8YnHxG8NniP1A1uklLnNls=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":677013,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgb2ZECRA9TVsSAnZWagAAlzAQAJFkOsKeHqUCJt7j0+3s\nq6rNknBbBqfYGny9TuJZEswNmHajHMIERizuYnd657SY3I87DONqCmaQhFtK\n2N/suQaWhEwJVIHJjOFrU0Kw91EtS2OBW+lHuF8GzrZroqcVeZIk+SdA63P/\n/tfUoi06Nr58UFvFth1LGEiag5Gg/1x2yFLgdUZ0WIvG645tRlcyDhz4LyqD\nNjQNOMzeANZvQ8Insj76R8o4fSRPpLJtcyI/xvhsmkJo36P+5ieUGuftGt3G\nigTlDklapbwwJCSJZaa8UnOJKoRZ/CORNsjJiU6OoumvmhC+UZ+Y5IGirNoS\nqb01qiadzvSTB9uC2zaj3V/wN9RRDPENtXYb4HcXLlBdfdxxmZEW6ys2jw6/\nf8LXCX4yhISo202sqdqwOxhg5gg75/Wo7b/5ys0O3La7G+EoFxnf+pPHslcZ\nL3GZJMQ8664EgJQcyR40/rrGWV/HiK2jBZ3x8PNini2AjFUbsPA+sh0Dyl5o\niHYL8C9YvEHlZedjuxZGICuvHW+LEoFJIX45lpKYLhZju4apGWHZSq/nJGpu\n0U5CQDOT4kH8L/Uv8aBPYMT5m+5sEIHk/Mo2GR2Wh+VCvFklYbJRs62VnddC\nWgJly0l212w3Qumkfm/dduXCx775xqQ+q7ck61xNmc4tVYho+pnazo7oJRBo\n0PwX\r\n=mQ3H\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"1e075d11fe13ccd75984d0de80c4c45e0bff54df","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.83","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.5","@balena/jellyfish-logger":"^2.1.22","@balena/jellyfish-metrics":"^1.0.47","@balena/jellyfish-environment":"^4.1.1"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.118","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.46_1617913411526_0.706281370104255","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.47-renovate-non-major-d85f1d7d8e1ec5058712aedecd0cd941f3c84154":{"name":"@balena/jellyfish-core","version":"2.12.47-renovate-non-major-d85f1d7d8e1ec5058712aedecd0cd941f3c84154","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.47-renovate-non-major-d85f1d7d8e1ec5058712aedecd0cd941f3c84154","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"343ae7348135b715710bd2dc9be65b8892d65296","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.47-renovate-non-major-d85f1d7d8e1ec5058712aedecd0cd941f3c84154.tgz","fileCount":99,"integrity":"sha512-Lu8ejFRm6/D/w8gWf4TVRTyMIh9dtkHFIk1g+7wJ3ppFQwI6494paRIaOP+WbVDjqGmzH6ksW9MJIf1GIF+fCA==","signatures":[{"sig":"MEUCIDxSloRPtoVEVJDM8g1K8Pshc10rGplIpX+5FnoqZLOPAiEA5Yma9nVXSywOn3ZbZ7O+1+N8JoSiiay4tQQHi2b5qp4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":677424,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgb2b4CRA9TVsSAnZWagAARGIP/jYx7NWYq+/ybjXkn1aP\nPsESwT78rZTPJcOHZQFSBuFZRMZ+T8jj4Q9zwYKJgnw9dHAbgMO91bDyGD4S\nh7iJjEhUjAgeQezJzr7XdzH9Wlo++J1wpa+ZzJLxgIaA+liJ0/Kp0/lqmu14\nTDrZTJGQdBFx3FPc6M0OOH9aaqV7G83FxZOaUhhe2NCz0RlnQjfoTO/nzDp6\n3LD0xYAp2Vy+IBwf4ISlVEexrJc/6diDJTnXdkXtOrUnKCyiH9cPftCCqlqN\nMOshEiBxABhpgUyiN58v82Qgo0/EffczhyelGzLGfldCfyscAH6QZmAwsYOq\nOnUBSbF1ZCErSb6mml0cTojUr2rQcU/awHWa+rb3tUsnKZ2rVMF82QWIyWKu\n2frgwWEI6vLSxEPB3FaTILtaXww2o/WPwlA0jx1B5iB9mQhj1fY6KI1XGnRK\nLVB0LS8jHKWsARnpdD/PuN0GgVWnBD71VCsxFFxQTcdzDNOGjFFHenYCfK0I\nKdya7Lrib3MDT5EkvDp/hS16MciN4VF3/pFyuiO0OV+MvVbMol0rae6wErYy\nWo1//dnpw+YhNS39GBYk9A9FU6yQJXWUXmQLSAIu9O/wUdZYQ2zDifB15usW\nHv6VKhBkQR26YsOa+3rIa1j7dp0pv6DjylV4tfq5pOeZnNEiNEZzCKZcap2k\n0bCA\r\n=qHk3\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d85f1d7d8e1ec5058712aedecd0cd941f3c84154","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.6","@balena/jellyfish-logger":"^2.1.24","@balena/jellyfish-metrics":"^1.0.48","@balena/jellyfish-environment":"^4.1.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.118","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.47-renovate-non-major-d85f1d7d8e1ec5058712aedecd0cd941f3c84154_1617913591709_0.1984342106180137","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.47":{"name":"@balena/jellyfish-core","version":"2.12.47","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.47","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"29cad613e45c07ff19e827b833d0757c9cf79aba","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.47.tgz","fileCount":99,"integrity":"sha512-8I6L5s2bpcn1oqgFDvM2wL1Jyc1dZiegMRE6wSeuGGHObPZmMu4gui4GES4a3qCV8AQ/RB2dNIc55I0fi1vy4g==","signatures":[{"sig":"MEUCIQDukBTp15RCeDiXV8gApHUo1suipapN3QJRrnh1K9z2UAIgTGX2i26KlHBXOABLVsTzfNDWh9L6ZkX3h/NGbCv+yQ8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":677364,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgb2kQCRA9TVsSAnZWagAAl3UP/j/rLZxPdN00iUilD83w\nPvSX335AlQ+6ubBDqmvdK7kyk3YY/tOLhWWnzd5KB5TX2gspKjGach5vdbI5\nC1tScg4ZE4rMTxHrFFO/r1n9B4HVpU1ruXwns3VAYswjjSXzldRrE19qLH6f\ntYUaLm9myfOkpCOszcz9UG1agzU/Lqpj9LP5Z+guNEimb+sCLw+VNrfKetDY\nE1TYdJJ6sBLZkisiltsYh0U/wP+OfZSeuAuv7SJ715K2vaMTKqv5A0s9CJMt\nQR+t6ghOUJPE7YQ0ojfQdo2jzEe8JUDvRoLhfQzejOYzlBJX/TLYDNbZ3Pzl\nrPrQ/LK/qZMcomVCBlm4kPROKjNxUlNxtWNUV+9MHE4DlMN5k6sQz97dMzAi\nJrPLJV/G02POk3v5236zZ7VOVY852m5+lvJphCSqMcyTBCG5rusWSdmJ6jon\ngOmxz046izWeW54UtSctycRzs5PIbqqHjfJd722xPTudFylYC0danX2FCHTx\naLurvXa91dFv21cbKSrSDpWzf40w5oNp0+ZL98zUd+JarThQIXb0Io1dGIbp\n4jufwW0dbTqlVL3twdFheXM7pbd5Ylf0gvBrs9r2g+SI2BXyWUH/DYMkRZPs\nkCgBc0cFEuC0Dy8YoukB0aXTvvGYPjCVKho8hauCTehP6YmApznAkK8DlUVC\n7BKK\r\n=Vd/v\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8a55216cb96272c53586fd44901e3d12c53595e7","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.6","@balena/jellyfish-logger":"^2.1.24","@balena/jellyfish-metrics":"^1.0.48","@balena/jellyfish-environment":"^4.1.1"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.118","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.47_1617914127921_0.9584125539508057","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.48-renovate-docker-non-major-6b31e0ca38e4b73e9b6578ae1083c7082cfc1d69":{"name":"@balena/jellyfish-core","version":"2.12.48-renovate-docker-non-major-6b31e0ca38e4b73e9b6578ae1083c7082cfc1d69","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.48-renovate-docker-non-major-6b31e0ca38e4b73e9b6578ae1083c7082cfc1d69","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"57a3be4ec44e8ec586cac1636b488c06f3e7e998","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.48-renovate-docker-non-major-6b31e0ca38e4b73e9b6578ae1083c7082cfc1d69.tgz","fileCount":99,"integrity":"sha512-gk9f21pTBIFA6mXwJjXQm4A+ea8UdwCaYoUOKyOzU006bwQu7N+DY4zmJfrKDSNWB27dZJsVLMPsLWe0ppuHtA==","signatures":[{"sig":"MEYCIQDuCnANAxPe9tteJo7g5qJoCkLcn+okiJbHxoVSqlqQOAIhAKUA+92vg6zBOAQQIkF70eXy1rykAvbhZWcFz3hIqKly","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":677852,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgb2o2CRA9TVsSAnZWagAAFWwQAKEgOMX7TfqYbTAeu8N6\nCdSoZsiTgKLLWr8zCIIhQ4G1843WxHUnt0JjTuEfEfRtOWMkMEqebpQCtNBk\n68ZZ3+RIfv2It2+xHLeNkhHphlqJpa+1qAs83nZC5PqUKILdv7e61DSv7vSD\neCZRR2sFWh0qKZtaI74knCTdgDmeBlxkXOEs/mSxA0IzM33IZMle5KcsnA1w\nPliGgV0Of3j4kkJsGcKtXiYaXIOe9J1RfHDgKmiCTKeCx9Re2tGBUxm4fXRx\n+/GJWZWDM3Ihd8Ast8ODbyqybO9f4DyJl+qyuvuf/QKMoFfagkLykm0Jdbq1\nOeHa9WuMLotQYLWoLbbYW3BUeNyAxIQwN2BwilAW6268S3JHaI+15ya7Shf0\n0Y1h0ZwEDn6ub+gXG60LXUTuBAq0zby0eYLBmiJhKvZe4Q4T81pD4VKXMvUE\nVztegHVj0LtP+aT+5D3aH1T4l+reZiXRXJPtQfN7Tf1UVXMSg6BijY5qkw7L\noaGNKXIU8i9CEjlZbfMnxUqHhV9Q10N91Cee80Whr1QoNUG3jerFnPaGdtd5\nvZFQAMIFT0UvC/ynanjOAAvbc/9501L8F4enZH3d8G9zzci4q+AE+468MivD\nn9npS2pKHwS63vpZMbjX2akU2wNrRgBhG0KSXCGkiGaYMXq0ELPkNRVF4MzO\nV149\r\n=M+BR\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6b31e0ca38e4b73e9b6578ae1083c7082cfc1d69","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.6","@balena/jellyfish-logger":"^2.1.24","@balena/jellyfish-metrics":"^1.0.48","@balena/jellyfish-environment":"^4.1.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.118","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.48-renovate-docker-non-major-6b31e0ca38e4b73e9b6578ae1083c7082cfc1d69_1617914421710_0.4990124581317945","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.48":{"name":"@balena/jellyfish-core","version":"2.12.48","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.48","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1c934e7d7aa6e1974790b1e2f6ebf49c84be6396","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.48.tgz","fileCount":99,"integrity":"sha512-dKB83XwSFVdKDGd7iE63m2Q1AS1dF8FRNYALKmozYrBaMjYia0CnDZ/S6a0i7fTaAIHmBI57SJJggSBsVgl+UQ==","signatures":[{"sig":"MEQCIEntzHHhr/AUeZa7R55hAiVR3XL7lXoLIJggCgxo0jClAiBoS9Eq06Mj1IO11cu3oq0VWmHKW1/dzAPuy6Vx2KIuhQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":677785,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgb2vBCRA9TVsSAnZWagAApZ4P/165e4etlBDmdEJXj7MN\nk7jSAA+YDTdkz3UU47II5EtgjHKMm4rKFDm8oMqNa3+NrxuNAj9OgzyD+74s\n/73qOUx4unI8qihdB3VB5uAFqR/x79N3PZgVYa4zzWqmzNLYpnJ8P7EwxEj/\nq4tu8jFWkcN2gVnPSN/CbJIlaIs2KpFLck0TMCjK/EnYEp1t9jCDZcWW/HKE\np+3m30q+H0+8CtRI2BSeoVuIlMAk4LQt46JBhK08EyEZ/PyLrq0WI4S2o145\nNitIgBg1vLxNcNxptgfOfe6UI91SmKjWmgTd7G8YIlI24vlCJK3/BNRkht/Y\nv+cVgULXiwaxfynhEaQEIe4R0bbxw8eaONJwPAdZHPwPpM0p+jix9Oh6679J\nBigaHo7K90x4nIeswFVeRKyheCQtTDZCjRCCeb8FD40kpm4V+HBGrJggErnE\ny5R9hBZN3IqBUF5dDhYGOMgJnXvaPw1S+ia/AfbclUeo7CFv2fcbfGJsqi4D\nfHHx6SqsIVB8gFzv4BXpghtVaBoDNoquWokabeLfzynXhMHsKCIv2VISD/TO\nTNbr/ZUw6G+6Twf7iY0YyRGzUQxw47qbggQB20CCBHDvpBAsZrp/hEExuEth\n9Sc3QDzmeQqyAvFzGaxeCRZmXYFfYFh4uj97b66OvI46ieaA4qHDg0me0bue\nEOSd\r\n=ic0S\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"bd917ce0a5159bb86bd07571934176d2a59df61c","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.6","@balena/jellyfish-logger":"^2.1.24","@balena/jellyfish-metrics":"^1.0.48","@balena/jellyfish-environment":"^4.1.1"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.118","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.48_1617914817170_0.44911187454601165","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.49-renovate-eslint-plugin-promise-5-x-3e4dd9f1c1997ba5e64520211d20e28944657112":{"name":"@balena/jellyfish-core","version":"2.12.49-renovate-eslint-plugin-promise-5-x-3e4dd9f1c1997ba5e64520211d20e28944657112","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.49-renovate-eslint-plugin-promise-5-x-3e4dd9f1c1997ba5e64520211d20e28944657112","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"453595673b385bee93f668acef6d60c6b2035fe8","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.49-renovate-eslint-plugin-promise-5-x-3e4dd9f1c1997ba5e64520211d20e28944657112.tgz","fileCount":99,"integrity":"sha512-eLaFC6klh6h1Izl5+3OFhcr0woNcCzWTR50XxP2S7zb/UfD+fwfmEeGB6S1Af2XrBDVd+zCPwDUeBcArTpyV/Q==","signatures":[{"sig":"MEQCICs4vdnomvEmS7c8X1W22U2363LBdtVBBTUrVGSLFTyLAiARG4WdTUFO7g+ikyGjSFmZrMbb3UWKzKubFuJyx5nwMA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":678270,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgcMCBCRA9TVsSAnZWagAAvyUP/Ay2wOvx1tAmqf/oK4+V\npo8b7Bsagg0jcFw3ZBVMD861DexLWC6PfCW0/rL3BEBOn+iIaHUIBBZqDRNh\n3BAS5CVpDExoZvKwVA1aU9nKAuPNdJ7biQTfaBgA335p+ayFdd92fhQ8OQGZ\nUW8DpmE8ju0ch4ZyHgU5mA+mKMwuFJ2BrAv4H7xFQXwLC9YUseNmdccKVEvs\notct2Js6oGuOatDwYAiODw7qU2/sTZ5i9f/oGn3vBUHniAm6Cl83g+srCjjG\nl7ju2rtMh3StP9UbOmHXKUiyNabWE+JoqKSRv2uHgkq8+13NNvxCRCD7TvmU\nLNN652YiDPHDQMy+8rpeFss6vjh+jbKfpLQy2r7CMbP0fbyMVlR6LNCPYJfm\ncU5F0TUcEDctoUDChAGFc2UGuO3fSaRaFghYBLOpL6bduS9AXjCxeebwN7Oe\njshq7v51KXFLCe0GNkhFZKEj5U2rJEQymCjV0JYfcXFdq6detbI5vqj8/BSQ\niqGBvJubSk+gxxoUnSMOk5Sq18wWHtg8fb4uo8vzXJekoVoLBMFeO1HUkM76\n0dyjXvyA8cGI+brwDdOlzLTESe7HGMRRpvF4nIV3gh4Mr3w1+BkWyM/oP0vz\nsVbDVsVaAtjXgImAEANPG6gwRsZ0yswR4hN77xgTiy/HmJVY8iBxBvG4vxLY\nc9Ge\r\n=VQlX\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"3e4dd9f1c1997ba5e64520211d20e28944657112","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.6","@balena/jellyfish-logger":"^2.1.24","@balena/jellyfish-metrics":"^1.0.48","@balena/jellyfish-environment":"^4.1.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.0.0","@balena/ci-task-runner":"^0.2.118","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.49-renovate-eslint-plugin-promise-5-x-3e4dd9f1c1997ba5e64520211d20e28944657112_1618002048776_0.7810209762190179","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.49-renovate-non-major-55b6700d6b7c39af29fbf9bd566b5d1bfcb8f39d":{"name":"@balena/jellyfish-core","version":"2.12.49-renovate-non-major-55b6700d6b7c39af29fbf9bd566b5d1bfcb8f39d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.49-renovate-non-major-55b6700d6b7c39af29fbf9bd566b5d1bfcb8f39d","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c477425350e4403dad6cc3ed3030ca35ad6cec00","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.49-renovate-non-major-55b6700d6b7c39af29fbf9bd566b5d1bfcb8f39d.tgz","fileCount":99,"integrity":"sha512-UcZXtcfLsxsU1uL/6QpMBxeHqWIRqOYU0CSz+95Z1xgpUFOi3QBFs2BUl/okyQt6l6aQxl4pxaI9heilNL6xlw==","signatures":[{"sig":"MEUCIQCwscFrZxOkkjIdFHsnHDdFruwcxqdsc4MMzypI5jd1IAIgIjFerTpAtnxsqlK+6A/r1WzZNWYriNmleOdrDpn//3c=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":678196,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgcOM/CRA9TVsSAnZWagAAVgcP/0YUv/wRS0/WSzC9QJyd\nRz9mCa1jHeQw4M5ZOGeQdKkWd5Uo79SZG3SpDCsbs2wsxCWVaGkBrGXLAnvR\nAX7JV5kEAUztlg8JZXSUqLjx/tdvydfwdvQNPJ9YCgJ4y4hYaNfVgZXqTkzP\ndqNiBRZA08kFT7KW24KFksUGb4mkIGP3hPRIA9cyrBjVNAVBiZcJS3J3qlMb\nAZxtgYMxzWXE0Cjohy8bhSpeWSVbAp0rDZRc5GRHy/JSB6ev5ZhIPHjJOp3c\nnVxrqWcSXN0nObqHuUfyi+jiICWoiNYfshNyE6mmB1LU+S3Et+PT0dZJqA/B\nrYkXhxtu8IlgZgwOvya0XCrY+LNzIhrjGPeiMFQTFQvMZ+A00+oyVC5Y/bzN\n94hya0zL97tH5xRbpXdO7AG2r8pxuopuy3pdc41hu8GUnccjKfJP3gZXGLro\nQVerOF+5ongV4PeEeCkVC03pYOlRuKL96i+mxExOUxr2GDLGzGtJCWVVuRLv\nhd3OFurLAKBQXAn9axyTnDS2aDGlmCgzKg9ACnBUw5nh+R+xXiv1b4dxSGKJ\nPllM3Z8trlDWVFlMyI+z1FDgHfJ37yzb5aPsHOW/wj7ayf15zswSn33NlY3/\nUTex+qviBC+X1HQMs/ozd9Klroi1IUnkhtH9ivJayArK2M8e+BHM1z74KTYI\nR4Um\r\n=eng6\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"55b6700d6b7c39af29fbf9bd566b5d1bfcb8f39d","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.6","@balena/jellyfish-logger":"^2.1.24","@balena/jellyfish-metrics":"^1.0.48","@balena/jellyfish-environment":"^4.1.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.119","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.49-renovate-non-major-55b6700d6b7c39af29fbf9bd566b5d1bfcb8f39d_1618010943445_0.048497211087985104","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.49-renovate-eslint-plugin-promise-5-x-fab02614543c01c9407360365f1940af5c687e08":{"name":"@balena/jellyfish-core","version":"2.12.49-renovate-eslint-plugin-promise-5-x-fab02614543c01c9407360365f1940af5c687e08","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.49-renovate-eslint-plugin-promise-5-x-fab02614543c01c9407360365f1940af5c687e08","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c5b8de66d26eaece5bd1ff50be67e5d3c7f84739","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.49-renovate-eslint-plugin-promise-5-x-fab02614543c01c9407360365f1940af5c687e08.tgz","fileCount":99,"integrity":"sha512-WF1GkNQDJWOJOCm9uCPDSGEGfqe2+l/ZomytswoL0Sp0rmd2usfw3V9MPKMHlm8myGGSW0QR8sbIkV6Rghfagw==","signatures":[{"sig":"MEQCIBP5v9FeFZzs1NyQCC4QO0EMB5GSrDkK87BrDJRFOaiZAiACdyr5Z7Fe02ec13wJZZI8bkzWOs3+0hd9ehCooAirnQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":678270,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgcONmCRA9TVsSAnZWagAAxwIQAJ35Zu5UitiS3dOSdqXe\n04dRejwuGxBZogVTCtoai16D7abu4ETaJNS1GXW7riksYtSSjh5Sssf0GX0i\n/IqvoWpm86KzkVQtCjyMWZE8rtoP1U9BUjbU2iEUGH5m7h994foLcOr8ISYN\nhDs144YRR4mzr1Xnwkz9UlGjfOpV2quAVUwUhL0D3e60GSth66RhLYIDDxyp\np2mSbgZ3t69wZ6n8GI902OA8Kn7iAUgoHwRhrxAYWbql9qAMNqFH9HGRVskj\nhVbtglLum7GCXJT9fI4n0c3jBfUAzuaKgD0YNPJge2FBvxOi5EGmunpU/ebB\nSvopb8div3spm9IMNegAfD28cqzr0LKRuQHh13Ls2ufMLSKHuTaKxlHafbM+\nbNYfuC8f8aWa3zh4xf2aRykVVFGvLYYJMJtCOuuVMdG8BPNXpVo7uet9wBpU\nMe1QLDd8k6uZNF0Qju87GJbB6w0PlWPdd4bkoqGe66FkAf/mH2OKXu/yJ67g\nwDLVi7Ksa5D7VOeHGybA6mmHqI2FKC6mjoQFGvtx0lC6T9L33W6SzaNHT//8\nc8A8cpykssgYtJ4t9BGbsXk5t7Q3OFxcQTraIFU3SIS/UpQsrpwiDEijHEHz\nWZD9UdYHYUxhVwpL3rsm0mhnWEhGxkKs+pdGrFf8TFw9DLss2tiG+84NNkh6\n3KiJ\r\n=V98m\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"fab02614543c01c9407360365f1940af5c687e08","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.6","@balena/jellyfish-logger":"^2.1.24","@balena/jellyfish-metrics":"^1.0.48","@balena/jellyfish-environment":"^4.1.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.23.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.118","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.49-renovate-eslint-plugin-promise-5-x-fab02614543c01c9407360365f1940af5c687e08_1618010981501_0.9663048896808717","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.49":{"name":"@balena/jellyfish-core","version":"2.12.49","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.49","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8bb3469d11e07531ad75b02dab10ff6069bd26f1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.49.tgz","fileCount":99,"integrity":"sha512-miAaCwQfgWTAWSpXI8wmvexU6luV9f+8emdT510sgUq6kK2jMKX2mCpT8JvsU5kOnnxiLO9C2WJ8gE+5VE7zHQ==","signatures":[{"sig":"MEUCIQDsjea67thvnn+gepDIK1Rqqf2z1GnXWAtukV8Uz+KvqQIgZcOxf88jzC/vXWszDLbBAsX70wjtyu5B1wH6zrT/ijo=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":678136,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgcOSLCRA9TVsSAnZWagAAEzMP/iat5i5+atRW2oh00zGC\n+HLA9jFfrx41coOGeto0KyYI0idXGB69xztpOEJHpRFtv4zZ5eDb09Sv9fFg\nbFH2mb4HDX8vglR61ziV1qb/igIdqVdCycor+jmubEP/XsWyy+TgXlNik5r5\nv8JBZKstUWSOEl+InEsDIK40/WWYEi2DneCJYK7SrQyAIOWzsXegMylE4Nqj\nXeF24LfeQrwkyo+Fk2xWezCxlewNktAYNqIaEHQ3SbA44zVjlW+4tpxUvpHT\nKfcJUysLq2k6ihGK2xoAMt48DGhYZ3S5/zzRjo8tVMDsGz8T50P0uiGcTqXz\nLiQyBn3bw7iAbTFt0DK/GSUJAj3Jz+PP28vdtLigCWu2H2kB45EJ8hOKy/jf\nyWCC9LcPYGA+xEGBYAfgG0lEFCC+FQzc11p0vfeluIdN0GI/xkjINTDsrTAO\nxNfjnZjmbDalbQnGfTDh9uV5AJFsXPD1H7osbLoYGTfAx4UCS2wd/wp8+PAz\ntyegiWwCK/ZLUbwtcd0oz6CNTyCagzny9K+PTAjkHPlmFRRZEPS5XhXjYPXS\np3jHH9FxsYPYm6M2zY8eFEHxKDyI/2tSNPmXt9hG168TkfHteIm5QlNzKuKK\nGgd90j5PPaaRKqS0aAbNBBOOanMZmaMJuLw+YTXC9MUtlP+t0ZTO2P8TiAev\n2Tnn\r\n=wmge\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"15567987cd89dcb4ddb32218bdc41be275a48bad","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.6","@balena/jellyfish-logger":"^2.1.24","@balena/jellyfish-metrics":"^1.0.48","@balena/jellyfish-environment":"^4.1.1"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^4.3.1","@balena/ci-task-runner":"^0.2.119","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.49_1618011274995_0.657056536013964","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.50-renovate-eslint-plugin-promise-5-x-fef95bc389a96d2b3bfbdac6330abe85e6aa982d":{"name":"@balena/jellyfish-core","version":"2.12.50-renovate-eslint-plugin-promise-5-x-fef95bc389a96d2b3bfbdac6330abe85e6aa982d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.50-renovate-eslint-plugin-promise-5-x-fef95bc389a96d2b3bfbdac6330abe85e6aa982d","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"089c3e94c6ab2cb2f53d8517fd0fbd2e04c05b27","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.50-renovate-eslint-plugin-promise-5-x-fef95bc389a96d2b3bfbdac6330abe85e6aa982d.tgz","fileCount":99,"integrity":"sha512-lmBusKLLzE8M+BefVhEgypQDBNYw3DOTKZRR2NEIkODWa2RdckabL4+BPmYy8KzglmYjOjPoE4nLqJDOcgHKrA==","signatures":[{"sig":"MEYCIQCw3vZBWbCnQuFJEhhfII9A6oK97ge17T6oO6VyCEyHZQIhAPjfaTCx8rNG/yNI6I2Q+GiXqe5bN08ZACXO1U2vevGR","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":678621,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgcOWHCRA9TVsSAnZWagAAiz0QAKFI/KnrfNKRukWCE3DM\nvtgSYVCh/Kegr5WiOwcU1X9fFagvNEGgakeO1SXD+PmhALsI2YN32lZNnw/H\ngJtBzR+0xIA+wLIbEngzHyzkTCA+Eu6Ph83OnOoHoRz1aSFzgOt59E8prupP\niyrxeYfGAar3vbcZ9JSHixO/Ny2AeQUoTow4ip4JPBwZoQbOzNC1NzbtA2qG\ne27GmiZZfw4RcQX7MOQtQpkhWmM75VhVall4CoLDagFRaysz53UCJtlELq3Y\nhNY2ijaXp5j2ZxwVX7859dbKIFKYGRXeW+17Zrf3daP1taAvc7u0cEG7j6OG\n7d0FZ84ayUVJ5t9LM6S+eWggixPhMvhiXd6tOBlJQKWU3E+Ztpi1O258U670\naxRhFL3c0pa/DjGW4dYFdXKUXdV+aEP0T5Dr+cuMCKgbkLbNHGymz7nMDbsR\nuV2OmIpjdm8jj1lXOQMeOcYG5vbS0qTBy7KFseY8Jh8isXj9eVhjSlj+tIN7\n1no6EqVREx2/3F5HypN2UxmVvJL9zCbergpTbHiGF7oKNiW4U3ewSz3NfOSu\nxoBevYn8fpDWpNJCxKGeIlUgDc1kfoKdKdXwBHUt8kgi3FDjw+p7x6Y39Eo6\n1colTftaagpm/DUjKKhw6ZGlxmpWxmhxkDf0LhTCRtlkaVdg3t6U2nrWhxs9\nX83E\r\n=alBF\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"fef95bc389a96d2b3bfbdac6330abe85e6aa982d","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.6","@balena/jellyfish-logger":"^2.1.24","@balena/jellyfish-metrics":"^1.0.48","@balena/jellyfish-environment":"^4.1.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.119","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.50-renovate-eslint-plugin-promise-5-x-fef95bc389a96d2b3bfbdac6330abe85e6aa982d_1618011526748_0.5996737535693297","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.50":{"name":"@balena/jellyfish-core","version":"2.12.50","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.50","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"fd13cdd37fe794d4f5f61259d9639115af1d4ee7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.50.tgz","fileCount":99,"integrity":"sha512-VBt+vQYxHAFyRt+rVcaZuq9OUw1KSkQ5lnSdUZwMfeNEH/pTkYDo+e+Qvjt8yVPncp12FSRN8wKw2uSY76mWfg==","signatures":[{"sig":"MEUCIQDNoO1b98w8EQMH8oo0Abz9gWoAtlNUOlbQdVABqRuX+gIgGzx/bKx7oDVBuzsH3cjBkkpxfNmpd0TRyrYu4cl8PQg=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":678545,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgcOz2CRA9TVsSAnZWagAAqGgP/1pjxCN2YuSVvnxAH9ge\nGs3sK4XKmG+JvNSYaePcRcPL1mAWvYRTblmO6dQFe7329nzJpxM7LG//YH2+\nGH4GvgaBkII9/Y6NnyKGR/duwcuAkyKiRAfHA+tcUv4SH1ONMoZ6xKlRH2FN\nMZJWAK8P9Gj/uXNte9fSipRmSS7WeuwOwr+f67wyiYsLn8cc3259rRaTdk4v\nb3NZGmze5PBHZY3MEtGxjsMiBhr1Dp9dkvLhLdEKlASxsO+O+mG5GO2/q1YC\nKFe7Q3ksBHkbZo8wlpT+RaEmpRimXYzKxzaSwxbNJP7M+qelCv1T+l5O4Qje\nBECN7FMfRy2e6n1ITc14SdMIn/2RL9ukjp23whS4WddpikMYRgYfNnYkk38y\n9LwIW2QchWPYnooKVoLgl0028iiheA4GGGkfjtHpEN6LFvr3qS+MoLtMa6lD\nUejDW00OCyFMklJeth64yl42P8AvWdF0ew34+y5OOQ/e51iMnh9UWWIBnl8x\nq728YvsoYzyPqBolNeIpEd1nVXqVCX1blMzwPMoGVq6yF4gvu1EIkO4n6d4r\nlKSPsuYCfVINilpk/A4jEhruB2y7LyOV2Ta6zUCgHfVLV8QrfIwkno3R6FNk\nuNS5ua2iUNXgmHIGSieC4U9DnG7bGTTdDkHMVnnhTmxCOH/YPOYfFZJ8zghd\nm8+o\r\n=zWfj\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"390838db079f9b151a582d9ee1ce7418ed8259a7","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.6","@balena/jellyfish-logger":"^2.1.24","@balena/jellyfish-metrics":"^1.0.48","@balena/jellyfish-environment":"^4.1.1"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.119","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.50_1618013430133_0.881186927570671","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.51-renovate-non-major-3885e883451ce910a831ff90332dac6f132b3ea9":{"name":"@balena/jellyfish-core","version":"2.12.51-renovate-non-major-3885e883451ce910a831ff90332dac6f132b3ea9","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.51-renovate-non-major-3885e883451ce910a831ff90332dac6f132b3ea9","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a53e12def54378774210392a4d5ae0a673c54394","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.51-renovate-non-major-3885e883451ce910a831ff90332dac6f132b3ea9.tgz","fileCount":99,"integrity":"sha512-p1sa70WWOlYbep1dXY8RmLlU7jxMCxR5Q6Awgm04+8REAsF+kicEsCDwh5+3yuPfAtPQr4jLpqljDwZkK0/JhA==","signatures":[{"sig":"MEYCIQDEsyjLeYMby+EJqZspJbmhekKlEPkAUrUh4Qzmq6RDZgIhAL5VscXW0Qh5UfEVa3Y/j5Lhl+FcXSMggf05fKS6Cq8H","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":679028,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgcO6OCRA9TVsSAnZWagAA69QP/A99IY54QGpENBEMSs96\nP3vzneWEEirN9ANBhSEqXILpTA4G7iKEIFdi7j09v7/I4WKP/f5lITSvP5M1\nYIlSvPciUd/w93C9UDQNXKq0oopz9wJaX7KDM4UxfhDU97oq2sA/O1TQ6S4s\nmfREpVhRSsG4JsSTHOTnBFt7lFruvcRGHiJdBH+TLwXHvjkDB4JV06jWxbdi\nydl6//pFHFlvl1eu8XGBhK0hUhFwo76QJspVaYqhL2OaC6wr6AssgrC66vNO\nhva7Z5gkcvo5NeIf2a8qZjSgJMPAdmCBzEZfjkl6fnRKRwja2qos+8PH3YAN\nLBK4BxSkkJrbJ3Xhj3SELMzuIfepXl8o2RieX/gjSlilTT+mopyuRnwagdvp\nXD2zcr5w3pv44+tPiM6LKU+N3vKw3NRkgWHjCRbGk8Ucp+IbTllcdmvt5Qrp\n+TdNskYgyD6R0o0Z7C9dwcbl/jsrwHdblFZxXl0skK9aESE1kz8/f8m+Ax0b\nJD2Br+8KgxMpZeVia8UpoFLUjQ03pdPoEmAlTq7lb+p2t5qRSB8mhaVNH2Te\nrztNoOnZ9aeZ0y/kzWAbWzrYF4nKbR3pj0fBh1J5c66jLpsSdVBrQRsbNnJq\nJJwy8/Hfqhdg3ZLvwBV6L5grnIUHNcXjEwHyGRwXje+gMxuJHH8QcnrMz9HQ\nF4WH\r\n=bRFO\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"3885e883451ce910a831ff90332dac6f132b3ea9","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.6","@balena/jellyfish-logger":"^2.1.24","@balena/jellyfish-metrics":"^1.0.48","@balena/jellyfish-environment":"^4.1.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.120","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.51-renovate-non-major-3885e883451ce910a831ff90332dac6f132b3ea9_1618013838375_0.3667474292373749","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.51":{"name":"@balena/jellyfish-core","version":"2.12.51","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.51","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"da263b07835e136c48a498d48d40a3ba7b6040b7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.51.tgz","fileCount":99,"integrity":"sha512-ROarWt2eOIMZIoAe4M36hNgukLMkROvoo9AJhfzXZ9opsSnFNbEMQpvY+XYgCsGTEmzjwjPKtt8MKtKXjKWHIw==","signatures":[{"sig":"MEYCIQCBUgbqIdjNBCBqzv0P7ro1G4VGYLREffCv8ZFAG9lxoAIhAJwXGfQF9k0fvXXkm27zwaW9Mg/mlFA2UnnYWZU99/fi","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":678968,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgcO/aCRA9TVsSAnZWagAA+wkP/RJ1jdroziZgJDSUZ3VF\nJ+gKz2vHATN2svRvFwhnmrDfM854zKvUfSjsGz5mHzMBIa3rZjMcoD1gX51R\naKjQT5RjzvbuB2Pg2asn5shPf+wvmmv6L8VtgSup7MHxs/MbhThJHusWxEp3\ni68sYFvvo6OJ6mLxoGS18Z8dQyy3EP/+S4JTt33yF4z/wELV6aJoavFFYrHg\nwGOnLf3qGBHxumbiBLPX/u6X3ihOzpdQYUuKYXvTortUpKFRQNddpfTU83K9\npPOD7Vkje8aVfB55R1kC6cdBXal1hcCPnLB/EWDpeYgRIj+FrUfXTQuBQprm\nLRFTG94OfxA0sI4j+TSUtFOo+8xwBkeBVwt2i/MoGdlwEs778Ng4eJh+1dnx\n9nlpwXPYj8ZCFLIbobbGuzSma9H8kxI4bEGJ7O4fVc7qaQFfrIE9PW1hZSCZ\n9oOzIvYit2Gix1ofsfmaTCJW7D4GpfBqe0O69tTmVbtlas+PqV29ae0D6CYJ\nRKswETN43u/DOUJgimupiiD8DxRABQy0ldWHEiKTT5Afn5IhS1jm1wg4vCsd\n6xo0qh2GWw5q89tn50ezD0i9xiBW1dBzy0kBinysdT6SrSYHYWGDsEoXzJe3\ncUz3q/BKC50s7DZOgTdCuPDK/4V+oxhWNPA53KKGHRhhOOn6eKV+nJVebfIM\nRiPZ\r\n=VoAb\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a5182a189f593b9f6827d61b286f5da89a385c55","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.6","@balena/jellyfish-logger":"^2.1.24","@balena/jellyfish-metrics":"^1.0.48","@balena/jellyfish-environment":"^4.1.1"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.120","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.51_1618014169388_0.47269610202187007","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.52-renovate-non-major-7ae6c2132c5bcaadce410929d70076f7fba7e112":{"name":"@balena/jellyfish-core","version":"2.12.52-renovate-non-major-7ae6c2132c5bcaadce410929d70076f7fba7e112","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.52-renovate-non-major-7ae6c2132c5bcaadce410929d70076f7fba7e112","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a24ca422665992d6f51692e46637ffb5aa7c2f25","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.52-renovate-non-major-7ae6c2132c5bcaadce410929d70076f7fba7e112.tgz","fileCount":99,"integrity":"sha512-3ArgMQifL//o+47YXOntFRP+luJoNLBiPx76YaL/7Bzo241WqsKcWTGCTLdLOkU221aRu4y5Sg+/7I4gOsVfwA==","signatures":[{"sig":"MEQCIGqCTJ5ADTNkMsdT4+4xsxRzHscEK5iKRyTYia4YguMQAiB45llKFS3PV78+WSnH5pBEeD14fBrNoZSKNTs3ya5d4w==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":679379,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgcPGpCRA9TVsSAnZWagAA06QQAJSuzlHgAQg2ewcKDYfW\nvUkG6yAXIFNVC/BivgdafNoqXLyItRc6PjLMRL3ZFbuCYO4XlYoZlr0d3ubf\nVZrJEISqvZrx5BVCO76/u6GH0EdrdxgHnTK7DDgjs4tsX6dVpRYBrnNxu4/c\nK7djiRjEoSc1js3qhnlUPH9m6abqq5aJsjC/9WZQxFVWhypu/IO9dT9qDclZ\nj4tVSGbudWm6nCOnPtL/wXVAV+V9ZBcQLAFaaD7bWZLCuXpF31chqU5cKa+J\nGquSFVQItxXODk351kZO2vU0NXxTsat6DlPX6N/FaRfFgv8XoM57X6uwuZ5D\nxwqaTByPgbH5EjpnzuHE60N0vwcWK48LkPsPutoIRhrGQ66ay8kLJGwtlSku\nssT2h1FJRQAmZpoePe5D17XEP4MyDHYZE9YDLUExeeZ4rGfxfyLwfI4qGTMS\nP2rs1HiXOkmGDWjNLYapxWrICqUhQRsPi9AQ5NHMHX2wD481IOTmpXwzN07e\nabw9cWtTJkVsowcjACyO6GSfesdh/3raxPyLyWBMsw1FdYV3AoHW7hSfXj2G\noC2NBt2mP4vimm2FefFHtueXYta8aloNqJVI2FDAbvxevgf2vrgAzQDERZUh\nB5nsayqTzP72rTE9QUIUR7aDqbxER4gkT4aNyrxfmxbuff0ahEhpnvDD3UiJ\nyuas\r\n=i8s5\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7ae6c2132c5bcaadce410929d70076f7fba7e112","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.6","@balena/jellyfish-logger":"^2.1.24","@balena/jellyfish-metrics":"^1.0.49","@balena/jellyfish-environment":"^4.1.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.121","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.52-renovate-non-major-7ae6c2132c5bcaadce410929d70076f7fba7e112_1618014633436_0.45021350430199614","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.52-renovate-non-major-e80180f10f76ebf9a93e9e26513ae88f259ba172":{"name":"@balena/jellyfish-core","version":"2.12.52-renovate-non-major-e80180f10f76ebf9a93e9e26513ae88f259ba172","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.52-renovate-non-major-e80180f10f76ebf9a93e9e26513ae88f259ba172","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"311f2c949efb05157132d8616481a23e264f41ae","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.52-renovate-non-major-e80180f10f76ebf9a93e9e26513ae88f259ba172.tgz","fileCount":99,"integrity":"sha512-bcsltlqgRvqr/GycaOsT6vn0YbWBW/HIkBt6C9t2+IG2AMTrlcj2/ggVO0VtTTgEsWkIANcbV9/AHKswkxA1+g==","signatures":[{"sig":"MEQCIB8Hg/Qz07SrjmdNQLXL4KrFvBVuTU1zivaC1msWs7u3AiBC3N75fFcMoTXXLTvtmf0XCNclotIfWQOHhbyECscusw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":679379,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgcYXFCRA9TVsSAnZWagAAOUMP+gMvb8NK3OB8t6UxjPsY\nD5Hg50afPLkNQc2RpvlYJXwxjRw4S7wuQLcSV4zkzqPJzQHfP0wZ8T0TVJxJ\n3Vs+Z4al0c/kRfka/4ueQizM7a51YrfmPrzEmizhE1aE2afSt7NI9pfzgpo3\nJmeeEy2KBiHxHwTb6RpfYiwLBpgVdyTaS6Yv0rGsTL0q+hFN4A/HOepuielD\npePa0b2BwzD23O5dkMwOV46e9jp9FWYF+kCd4E/u3QbHgG5BtbU8L0h4MZGU\nHTWRmWtGVXipIeO85BwJbnnTG5gQL4aBhAgDvUO+B6MsonJBnEhRbr9fVNiX\nXFPsHjaMGu2yyJaOUE2Vmc/Kt/t9W2M4dusNd0hKzJW3V7GLAbkSIa0CKEUK\nndkL3sU25gqy6CiWONBWkHp21wR9cbp5QXaA+owfT4PLsQgiFVDiG3dJHfoh\nBzshwftIJEfq5WAlzS8E733ThP3+qfbtr1YhdE70R+pKNP0YnXorU5Pw70bN\nV6bTVkLyPgBrHpC4I8qHvboChUNXaI+wzJ9X6EqsEcGLzDsnXjDx/y3vLuNh\n3Ikw/N5F4GvEMNfZcAfXMg72OPprBPelY4yU9IFRI4/aGOaBsxx4Zrj3JrAB\n79fN9C6qbeDT3OU62bfiuQe6j1F3+HN8TmFxjL6cghJkjNjrIeJRnqFmK5jG\nCLYs\r\n=+Sjt\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e80180f10f76ebf9a93e9e26513ae88f259ba172","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.6","@balena/jellyfish-logger":"^2.1.24","@balena/jellyfish-metrics":"^1.0.50","@balena/jellyfish-environment":"^4.1.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.121","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.52-renovate-non-major-e80180f10f76ebf9a93e9e26513ae88f259ba172_1618052548618_0.3343696126195257","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.53-renovate-non-major-cfccb8b4c60163fbe73e318a10431036b3f4f95b":{"name":"@balena/jellyfish-core","version":"2.12.53-renovate-non-major-cfccb8b4c60163fbe73e318a10431036b3f4f95b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.53-renovate-non-major-cfccb8b4c60163fbe73e318a10431036b3f4f95b","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"7f8389d11c8b7515a5c5353e4c959a44d2104124","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.53-renovate-non-major-cfccb8b4c60163fbe73e318a10431036b3f4f95b.tgz","fileCount":99,"integrity":"sha512-f74u3goF5r3dihEDZDqu+KcIhML/dvZkIJXMNby9Erw6Zk+1UeQU+O6YchiVJs04uaaXAFRjhAqe7ju1iCED9A==","signatures":[{"sig":"MEUCIQCoc4Yi3byPME1c9+5peG2Xr7t2BQM6z9AWFNobQKkYvAIgdtRqcsc+VN+0LeAvTO03qL7H2BzGP46AQFHoDI7jyvQ=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":679806,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgcd/OCRA9TVsSAnZWagAAmIQP/0ezsBjvse+dEiSGkGmu\npg0CKNtUyhwhTIosM5BuSzw7h/mUvXjZOKkxt2HYSZ+52ZokP/Q9Jfk6n/Ik\ni3CDqPfXpxEkY7xMNnT3E6jdxVRt2mZFCHGSyHGoVLXGdjqK2TayblZYOEHm\nmFlaUNGBxLwpPpkqwo3ni0nHoRCqGsAKxY5w7ykXFupf94Bhd+EaqxhMNylY\n40SioBZ1JMp1TUhCmwZrFxU2Sn83WFuBUnqHp73Jwd0MJH2oBgix/D6zmnk7\nse61ZxZbHULl8ZD/A2lK3L8JoBeVj3E8OPHl6WtkjH8SpfEmX5XLZOzQsI1S\nl2R+YUq8dZ5tKnLm/5kqkJS1KXJi3SV6EQvcInUbld+05wDRquXmBgpfS/Z7\naMLHmPM045Yx7XOqAhrLfTxhALOjlzxLL2EVgSx3fHlMfOD/NQXD44xIWbiO\nqZCJR+8F2r4ZjSls4DM0Y/ffIJPbb3vWo6yoYO/gr7hQPRq+XO84DiOYnXFD\nWi8fsbTlfDZl1SsEQZw6rzp+iVwNeXSd4dRJHqYMAuMbwLvUUWoXeZMsPlH8\nvtYu9h8mLTWr07yPDz6g6ZKYBJHTP7DM6UhhtNqe2mkX9nTdp/VPd/Qjc53C\ntDO//Ea1Qp8Rss4+SY1oUs89iLm9TW6LG0NtNTGmPIWz7xWrFUwcle3AmrD+\nMRpH\r\n=xUe+\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"cfccb8b4c60163fbe73e318a10431036b3f4f95b","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.6","@balena/jellyfish-logger":"^2.1.24","@balena/jellyfish-metrics":"^1.0.51","@balena/jellyfish-environment":"^4.1.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.121","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.53-renovate-non-major-cfccb8b4c60163fbe73e318a10431036b3f4f95b_1618075598123_0.906326894975584","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.53":{"name":"@balena/jellyfish-core","version":"2.12.53","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.53","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"cbf0ae53ee01d5d96facd098b17b2b69cfd8c2fc","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.53.tgz","fileCount":99,"integrity":"sha512-TLReLkJuYI6tHVzoEen83QtCQD4OVleSZ9OkAS9TBNQjlpdU1+zCvRddPokPsAMARPk/Aa7uuWsAdCorzqYsRw==","signatures":[{"sig":"MEUCIQDJyb/bSAFPWmQ28qMha9TaUjSV44al97VnHUQsPIylfQIgUcAXFgFRm/02NwQee7HNv6gos+popjLX3AHQr8MzYhc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":679746,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgceDzCRA9TVsSAnZWagAATvAP/R2CHaRBClTtOryGf1fg\ndv4qWsT7Kn8HVRaBtOiZiHEP/ThXbeOwiM3mKbZIWbPqAPC65NXjo7nE94jC\nd9OZI/H32F/VqVkLc/nEqXtzfoCM+o2ZcM+rEimJQ6RVOwIMx2nmR3ZOaIh2\njmRWmH7t8jBlvpMaixlbTCxrJxrpzssu4SdIpyzvedvM6pVCtLf+jM4XJO7N\nK5/IWVdeqQNQc2ho4OkLysHpqv2D4m73jNziN8eoSVJpEerdWHBRSzfGrxzV\nyLB21r8guM+46Vw47o8Z3ypTOl9mkAPtscDFcKHPB+CtYaJr6VYapeUtlCle\nb1Toi8f5qexqYMVVojCXajabc4t2UjXeaDvs4j7aG0x9efPtNbC2mr4HfnE6\nap0ARmwbTxjr8lkIEpR5hoSFTxvYWi6vzK6y8gzGNaCWwdueqvyKqB44JGZW\nbba/ewixY24/TWS7wap9sHU6JMFTRXZLP0xfh+8+Oz5889QHu0rj4VssyUJT\nDSVxg3klLRO4u2jsrtlwiFv/1abRKkhI5lmIqk8/FugBFofHUL+pRmOQ2SXf\nK0bFKyRnRQ+rqgPolWT/uU4HrVD5CEBykSvTm85ROFGyA4qsQNpRvbVqEADZ\nLLWmHHOyHTMXUVlJffWcKveYkAPgv85em6FKldZjZDri+lVep5WHmkL+QTGX\nS4ZL\r\n=f2O9\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7a793f083dac3fa3a15ce4e0caeff043a589504e","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.6","@balena/jellyfish-logger":"^2.1.24","@balena/jellyfish-metrics":"^1.0.51","@balena/jellyfish-environment":"^4.1.1"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.121","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.53_1618075890974_0.880306896462703","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.54-renovate-non-major-c264e3059829a4be9eead4feea3ea9ed25baf078":{"name":"@balena/jellyfish-core","version":"2.12.54-renovate-non-major-c264e3059829a4be9eead4feea3ea9ed25baf078","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.54-renovate-non-major-c264e3059829a4be9eead4feea3ea9ed25baf078","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8e1f1c0de1fb98b244a7128172b919bc449a945c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.54-renovate-non-major-c264e3059829a4be9eead4feea3ea9ed25baf078.tgz","fileCount":99,"integrity":"sha512-8HAimLEka56xbn0sFeBoXSytR6BPx7L4//EvHmw+EZA0c0mP7bU4OEh7GEqtJs/lRDQPiINWmGhcs2f9jTpDDw==","signatures":[{"sig":"MEUCIEtdwPvC9Ozs24hgv8dk3hESGlOdnFXOCPRxH2GzIHVMAiEAm1vDyP6gNh3KadPLig/egRg7RuAjfN12ZVs073fewUk=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":680233,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgcfpbCRA9TVsSAnZWagAAtwYQAIw4V3mtmY1sP+VkpG66\nhSqOHJOzFeN1XHZkkF1RmHmCInVEIfpP4n7C2yDwSUZ1Ge4c0Qnbi4VeqS+f\ny0sJIYlKLBNnNRmFLsn2zk9R2DlTKQPlYxZE4oKNxhVOrNk2JJveLgpQD+gs\nGx3yLLYwyMzkdt5vFEinfOAYGCQ37hju7f6LZbeqsroVlxzZTbqesZTyXhfy\nNsokzkfAjnjOhmAI3s85Jnrpaf9C8dmOldYjAo1tJOGbMCH5sVczg4yYtk4L\nr+AtAObdD5hn6p9DqMY7UfGPoARlmL4+bSlGzeGGdkBh1lQspOM0LQVbMFal\ngGDktdROJIzC50YkjVxRlw7wI0mED9aGmbCs5yCVGHR/odc2Ssf+EZvi0Jyj\na+xHkeh6hFgnA9dVaFT4wj3zfvmHcx4txsm8owcPRbvbobe9Zm5pYxmkRtn7\nbuNpwQxEDE04fMh0EwgY3aaFOu7pq3bUOHYfyUU+8Moh1GL5fVgpoZ9ilFba\naAgmHfx44pHedzyIrP4QZmITEqJSup6oJpiFszpkDaASTvcvII1E6ZGFAUZj\nyWzIXHZ/89R2yND/Z6owX4kSuuE6pnm1hzOlQNMn68DdwMn+JYPRcB6YD5Gb\nVcMSXzmRmq15jCaGc4P2yw03MfEUlj0vHbL12vet883B35EJCCLZevMBrseG\ngrcd\r\n=jvxc\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c264e3059829a4be9eead4feea3ea9ed25baf078","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.6","@balena/jellyfish-logger":"^2.1.24","@balena/jellyfish-metrics":"^1.0.52","@balena/jellyfish-environment":"^4.1.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.121","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.54-renovate-non-major-c264e3059829a4be9eead4feea3ea9ed25baf078_1618082394749_0.03332755506695029","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.54-renovate-non-major-82ecd1b07fa832e7923c8b60b72f7cc17a3750bc":{"name":"@balena/jellyfish-core","version":"2.12.54-renovate-non-major-82ecd1b07fa832e7923c8b60b72f7cc17a3750bc","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.54-renovate-non-major-82ecd1b07fa832e7923c8b60b72f7cc17a3750bc","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"dd9aab85929d25749b125f11dd4fa8470a07073e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.54-renovate-non-major-82ecd1b07fa832e7923c8b60b72f7cc17a3750bc.tgz","fileCount":99,"integrity":"sha512-O+/b9PUI97X9+uKVpwYblyzpv9oZqL9mJvUx3FLdbNEUcye9jSe/+SuXzzyNzlVzZ42bj3Ew8OtDCv3XaAdyEA==","signatures":[{"sig":"MEUCIQD0WlV2eIRNSpATNoHvd6kcR5syrE6HcXuIAu3MoMjP1AIgQNxo6ttjXKmr8GD6Td2+h5jDMKVm6qj+gmpuwh6eONU=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":680233,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgchH+CRA9TVsSAnZWagAAgBQP/j+AJ/nZ5rbgwlfkBtDY\nrtJa1KTDPNC7tYlOy4B4GCyG+wNdbd5NZoXHOKuUm42MDZi0QBdEHe4lVaR/\nqJH8EJkkmWvNSbwjxnG+r5UjuJ2e7F0HjPATMOWnMVKnlJ2EgVzvcAdbx94H\nkFEBxnUdOzrModR7n9DDxObs/TK+Jd4HI7rbT8h6FlO0MyfTgqU5OMneLXPl\nC5Lty9yB/ADaKQb+l8dad3/ImcXAbm0uFsxnMTfSNo/ZHBKVdPhh6G56v/aX\nx8MUcFmlC8gzsR2cWhDGW5UBFK/Z2QFOQ2RNSezjFFx2I7Fm5TXpmX525Cqz\nYvGQj80SyiFtGqmM3LCuTrc+P45ObzHJTlhTSydy0GC8Oz3kdOGfzNqwLNCT\nvF8tYuvF3zaytssVA+/jiLPRZIwC5ZH+RMFmZOzg8u9yHuFRAOT+iXxID2w/\nZch84+aqPFoPNGYznbzbQVlDLF73gJ/M9X0IMDc70NTmuN1JA4aJQFXfduYH\nxa5X5Ei4MLLFgwaDaEWb0BJQmz6bFgylyaPSLSvG5cEifz+qHu6K53l1Z7KV\njkb6OXDJhEcsNrmxRlbS301hQxLhNE4vJOeqN68LwFqVBcs3U6FzeEVO1yOE\n9tuF7vqYjcnsVMMAocGsyvwkCKG2/52ZiOYS5ejoGyMHIZdAw6yJ/X+1vUOS\ndus2\r\n=VqGr\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"82ecd1b07fa832e7923c8b60b72f7cc17a3750bc","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.6","@balena/jellyfish-logger":"^2.1.24","@balena/jellyfish-metrics":"^1.0.53","@balena/jellyfish-environment":"^4.1.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.121","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.54-renovate-non-major-82ecd1b07fa832e7923c8b60b72f7cc17a3750bc_1618088446455_0.8365972487752031","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.54":{"name":"@balena/jellyfish-core","version":"2.12.54","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.54","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"613478ec58676b9d846a4249ab3830ed65b443c1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.54.tgz","fileCount":99,"integrity":"sha512-/aSme2T5KbP57uRjXQCOJ65HnxLgv2vFUpOr2f/CiN59ZGkxRNuFWwLci/QyQTPbswgAe84PDGJtc2JLo9LIIQ==","signatures":[{"sig":"MEYCIQD0i5z12K3NHk3WgnB5F9V5c0WWvHa0rEhV+ZG3Fea20wIhALgGCm7AsNmXmSoVqH0S7n0373tLtwOr0X0XDcmRTAij","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":680173,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgchOvCRA9TVsSAnZWagAA7L4P/jOQ27ADunzhXW3YtIpD\n7wKq6q9gAHr9tC2vD5qRTqdTqTcZlLE3sX4atD0usTC3eIQkbWUT1YGktyhj\nowmFXOKD76ac62uj2z8YSHbkQmGmKR3vkzQ5PVxR+GBLtz0uPfGUKE4Hkafr\nnzgt/Dc6FqNZ4AvOJYfGWukuK9NouPyxUIZA0UBUlMX2yVRnsAxC94tofIBN\nYThmEuRMr722WOKDP5NoNc6p6MQQsqxGMkGyAGXoZrUdpwJ3ZF0dYUT/09Ko\nQ7OYLqnah06nQwdVhoyD/0cXZgpOr2OsM9E7Gkee17h/95ww09UD2CjQdsTr\nF0lCu43JIbCLVYAA95zs4e9dRQLTzpmKchM7ImFmckFq4BHHX8SIQ9OBSIwD\nLplpA1NsfnGLap86CO+dSwXZlGkRj4WuyOzlvp0v6xatdPct7QYqIcaTz4QB\nVHqRyeyZpc1k1MWykQ6kFWBUJA6rTrjOr4tTZFhIHVZUVsW2AM7g8d/s1jFM\nfkra4bc/wIHxTUuFaUsi1BB1bFE1VhQMBxxkhwuEqGt5ewkaITmdkCzv1JPy\n9FCkOHytNOU/gLVjJcTwa+Oi5RqU9oACpU8nhv2od2txr2xY5LoZartsy4f2\nGsdk7qUtHmJ6KVtuS9XGQQqdMk66DfqJ+jMrQDaSmJPTJ6Xvs3kZC0sWzC0n\n+jwG\r\n=syLR\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d900854c924d6f07da2028c606cd579657094143","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.6","@balena/jellyfish-logger":"^2.1.24","@balena/jellyfish-metrics":"^1.0.53","@balena/jellyfish-environment":"^4.1.1"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.121","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.54_1618088879069_0.21169093101893988","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.55-renovate-non-major-cb56b6fec52b7f503591f517bdf70fce31f154fb":{"name":"@balena/jellyfish-core","version":"2.12.55-renovate-non-major-cb56b6fec52b7f503591f517bdf70fce31f154fb","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.55-renovate-non-major-cb56b6fec52b7f503591f517bdf70fce31f154fb","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6727660e258119f76a906a69b3b56db652fb9c7b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.55-renovate-non-major-cb56b6fec52b7f503591f517bdf70fce31f154fb.tgz","fileCount":99,"integrity":"sha512-pHG/ahs8u3yDmtxtLX3lRssobANXji4NAmLB+OvEKLuAu2cpScwJ+Xm3CGDRPkaK7nk5epJ3KXK10ZHE3MpUcQ==","signatures":[{"sig":"MEYCIQDvLr3WTUbgonDJB+C2Ib36eM/v2+sHF+25qLVE+G+snAIhAM58mqvukwdlsbf7G3+Kf7eUyYWFEAaA9HFlvv/osjwm","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":680660,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgcij3CRA9TVsSAnZWagAABbIP/07waI3TsSOLtQijZ9mE\n05Q6HPL+5nI9eDrf6D506R9bmDlREc3g4q+0QYGmEUTkD/+izWXgxwaeH+Fn\nMNJ7Po2BS7Vc5oLYFgX1zh8aGnDZ6mEkrPkGiitc0IBzlqgujYClKvKONLay\nzcNQLh59KrMgWT+vwrCuyV353aUshxwep4o5vcRlLCQkvbCq3W6upGBg+fWG\nKz9/K1iVGTyskN5Ah+DZmZ5bBxANQHm8D1GLAW/ZuSZivjtAk1aG7ToY873W\n7+gLJ1Y+olRCYiAzMHfr5+vscZtTvNyw8r/3WPOVBdavvfJaVgnikdxup3AK\nwNdSeEa60nJMmnt95SoQZmetkErq5SIF1NPGm29ZGDC27CegFzEk/I2zvtG3\nnZaGW4ZUiJuItDV/od8YMKq5rRo4z3MC91MyTwSg9ceA54qWhCF4UafHXzWl\n21Om0IbykKyHK5uzeO/rVcN3pRhAcxS9aTZMclthDk1216hN/Y4P3xyweTou\n0HbxvM+oq2529+k0MrahnT57mUKnZnT8MQFiljo8mnWq8c/vnMwpmXKQQomV\nSUT3RxDQt8zSqYBFdKS6GIGBhEcmRbuB7NH7QMMHPRSuXk0XLcZbkMZBMSjz\nff3HLVTJ4FiUaTScH3DYVsQJjSmTyRrtGaDIzdsVk/wXU+YMIIyRqVhGcLfx\nEgeX\r\n=VpuG\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"cb56b6fec52b7f503591f517bdf70fce31f154fb","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.6","@balena/jellyfish-logger":"^2.1.24","@balena/jellyfish-metrics":"^1.0.54","@balena/jellyfish-environment":"^4.1.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.121","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.55-renovate-non-major-cb56b6fec52b7f503591f517bdf70fce31f154fb_1618094327425_0.29179544603753405","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.55":{"name":"@balena/jellyfish-core","version":"2.12.55","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.55","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8180229ba65e51a2aa56231bc548e58bd5cce4bd","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.55.tgz","fileCount":99,"integrity":"sha512-Vam4H/VsWAyit/fqNA0LNSiZmoxaTUJQBMjZe4NtBVnmIHpkAYhcTxj2D4gYRJjQYNatKVUok3d4ZMGFixUjjg==","signatures":[{"sig":"MEYCIQCRy81VaQcnbEn6XwlzkxIhiax0clzlcr+ezaGkIj1MNQIhAL+s9Aa9BNCYd6uO8FBvQ5QWMqmJIg8iFPWhgXlwl/tb","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":680600,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgcirgCRA9TVsSAnZWagAAizcQAI7G4Ca2b4TxJvDbNsTK\ns5+0033aawCkKFlPvgoeBja4Z9UJxHO8J8d0Tl1CMyeNIAC0aV4HEIbdf45y\nsowLlYn4VViNynKPTjlF3z/2JzSrwQBuG5aKp3leb1ACNRJtjpISdVGkURHu\nnOeQe1DPiMtsRGDTT9wudKNaPrejZRtPP+RpqBShW1pKGZIoehDTcTiglh8H\noZ0cfSxdl8LmjPjU80gNNR+9M44Gk1nO4iL8DoOFakSGuoaOKO4tjObQJB97\noTs36wLkN2Bb+rhgFoIQvvGcBEwSpP649D4MrDN/4H5XX2+58NZIguCtQ4b2\nnlweOVSe1GsCUgCgaYkujf8vp119NCSQs+K0lYNs1Tw+Zums3TOuqLNsytcH\n3yrhY9Mi8WpFPtaVkdo7AXiShi6zHe1PH6Az1QTTOp63thRyDjz2WcxBZOij\nYhlwyA/pUoFMtAC9qMH2JQeImQOfvHL/Vh+r5fWZCdvoUiXr4Yvu8UcLs/2n\nnIexbM5Q6rOL0oTwrBfeEspwSKhH5uwAM02QSs0LF1zpmz0gzXYUA7niywWg\nd1H8ph8gm8gFFgHFEeB4iS88GER0heiyw++BbkjdZk3QQS90shEz4xEiggPE\nRP8HHo0dML+q2HVHo88lD3I9fDC0CJI+E4g9vsR161RGiK6n/QxRnMvFxh8g\nfOOm\r\n=lfsj\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e8cf2cb6d6eabdd0a9252bcf6c841beb1608b6f0","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.6","@balena/jellyfish-logger":"^2.1.24","@balena/jellyfish-metrics":"^1.0.54","@balena/jellyfish-environment":"^4.1.1"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.121","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.55_1618094816212_0.9983374255126951","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.56-renovate-non-major-fdf6edd63e678bb048228bad2a282870effcd7ac":{"name":"@balena/jellyfish-core","version":"2.12.56-renovate-non-major-fdf6edd63e678bb048228bad2a282870effcd7ac","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.56-renovate-non-major-fdf6edd63e678bb048228bad2a282870effcd7ac","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0949ca994be0d201351d1bca374246ebb22990d4","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.56-renovate-non-major-fdf6edd63e678bb048228bad2a282870effcd7ac.tgz","fileCount":99,"integrity":"sha512-NT6PXpXUhxcWquE64CJ8hE7gZNJRk1lZNEqGq6U7toGX8zM8R4+LkeduPa2xbUaDxnE/4YnPH5UhSuiJJJl2oA==","signatures":[{"sig":"MEYCIQDrXSkrjUbBXp6fuQofI9XtARdXDzHv88eMgdLlEO9kugIhALbPSSP55COw1VQPi5iWxFaA4tpZDW19frAwXj1U8i17","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":681087,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgclD1CRA9TVsSAnZWagAAiFIP/jbBVtulNhvf4nS/TiSs\n499uZdeKFQIhj1RkPupTctob5u1N9mqxiG0KEmakBpE3Po5E29wa5JcyzXc/\nS2//rzGc16C544eW0XqdcoSzt+QXy8gfQd9tPsZButoqjnYEJJCgCaRTKM0I\n4rsLFKnIQPEDaurbgxaWTFKs+nwPazTSeZXxZwbaLf7STrKzoPxBXQFu7MHx\nQdtEFY5kiWyyzgQdCv3tgnmG9f0595WMFG4+jlA+DdT5OS4BeKFjEvFSLrJK\neJxNNBXsCXMVpg++cQvshWk97KhW7Dzli6w9dma5Bw7t2Tx2OXCdedVSSWkJ\nFKdLdxilx8416Gw78t0kAhukAmxIJ+tAjeR9L1t+2v14FZlLB9Qqz6wOU+v8\n2IInLXzMmkLWlTiZ3L0XURB9AcSDzFN9C8XbXQZLfDdneM6mB0BhKZ/2VvSZ\nQVVXVT3UynOGHPY3Uar3aKDkEBw4Wzs9D1zWlFkBSLcSfRLSkvdxpdH92qKt\nCs/HZo4wX4SUGD5y4kBprR4Vxft9N4LflPMImPhyUwOMG7tf73QOnQK2agyS\niHihMdJnONjwdGhIztODzOyfvHYQoMqeG/BcQTQtRt79eNoX3ZwPdaoAdjrE\nWCspUwvgVohiVPYdvEnmBtUL7uMOvHQ8Za6ZUH/bMrjys1DMFF+sOr/YcuiT\n9gXg\r\n=dcBV\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"fdf6edd63e678bb048228bad2a282870effcd7ac","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.6","@balena/jellyfish-logger":"^2.1.24","@balena/jellyfish-metrics":"^1.0.55","@balena/jellyfish-environment":"^4.1.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.121","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.56-renovate-non-major-fdf6edd63e678bb048228bad2a282870effcd7ac_1618104564581_0.8701435156363997","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.56":{"name":"@balena/jellyfish-core","version":"2.12.56","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.56","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d9ad207c429912c41a629d056cae9bfbf4ee667f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.56.tgz","fileCount":99,"integrity":"sha512-HRsdM5q2lQQcH7jVyRRz2mgC60ZqgRa6P3UJ+KvDeU8lwzHE0OwgvVBjMpkic1D20wkzj8RpMkYud+XdscMDBw==","signatures":[{"sig":"MEYCIQDEi7WCwBwvHeI8w0DsmGmjM3pp5y9TO3tBawP7kRsoAQIhAPzM9pDd4IOnNhqz8zi6/4GiK/wHPUEalX/PGZzFRNFg","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":681027,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgclJlCRA9TVsSAnZWagAAA3EP/1AW0GmXkyiLSamMDCuq\nOzE588HHGU3antHY0K8mK+xenMOV6EGCWyeTCNIIKpj0x2lCaE5LyGe7/Uew\nFeppkLuMj6fdPEupNKesIy06rC8fSO0ti1LYeLP7AcnnKdtWC4mMAHjDtSXL\nH6KCx9tBPQ0Hv1xfyh/xhvT9KzRcspxO9Iw7dXJaBbB4iZ1/y8jijXPaRx9p\n6mGw4xfOZvmHB6mji1pBbm7UVDqbyEixoLGkuQL+fdH21FyCC7m0jJSdzN2j\nfNkTb3jUdm5mkR6t0CDWfnOk+YlczBWxFrAgfaA1dZpv9FoAwjhDZI3ZWnUS\nTZL/wqx06FfbJtgbMN9BoZJK1RbkwQIhMsBgqCisZ9XE5SDZ8yioqHLLjN0F\nx/c7dGxDsrN0fDC4LYRBQ6FDn/h+nyr2wcc3wg6WoZxlgq2uShO2jwKbjeTa\nfb02AXrazPvcr5FYB8U4sqbNOK+KrkmcakFuczZiuIkM9E3as4JK6jxcjCzP\nId7HQSAEct9wGm0rN14lVV9w6CZNcboTmv8wVyobPdzJvv9+xQeRekip1zsk\n8+MGqk2ObL3Ut7WO4E5kkQO/B/GRiFy2mskyzD/sRoQnUb2LhuYowb/S8wad\nOlsOm3mCeIGTYQjgDy4j3ZdWCaaTFNlQUmBj2HqJ/2KIH+ANMMd7R+R5aOtG\nmrBK\r\n=oQ3a\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2ef1f4bdcb5f8a20cf888b615dc2458aab952fc0","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.6","@balena/jellyfish-logger":"^2.1.24","@balena/jellyfish-metrics":"^1.0.55","@balena/jellyfish-environment":"^4.1.1"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.121","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.56_1618104933013_0.20064887249728502","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.57-renovate-non-major-8caed919404c0a9721aa1d1025ae6142ff6b6bf1":{"name":"@balena/jellyfish-core","version":"2.12.57-renovate-non-major-8caed919404c0a9721aa1d1025ae6142ff6b6bf1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.57-renovate-non-major-8caed919404c0a9721aa1d1025ae6142ff6b6bf1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"29c04451849f64221c59fe342041453d4b2559ac","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.57-renovate-non-major-8caed919404c0a9721aa1d1025ae6142ff6b6bf1.tgz","fileCount":99,"integrity":"sha512-jJM4uNJ9Gtrvxa392XylXJtv9xepkWHwpyH2jEJLmk1AHtbLWbuTVC4trVB9jBhnhIysvFSwPsbNf5xHqKC7Ow==","signatures":[{"sig":"MEYCIQDiI2qMEhmg4xLf6qW3TE7YbZ7aJOGNO8heu9RYQvQrgAIhAKEC4l0/8QndKuZ5KH+u38DtARZSoTFlBid90Gr4oQ3/","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":681514,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgcoyrCRA9TVsSAnZWagAA8agP+QCRbAABT2H+mpUQaELv\netuqt7QS34ZqUPP5nZdudSEErRLO618xUSKQHsIn4mGOLGv5p3dY2pEvBAIs\nPAwL3za2iwUjJt0KXBtHwPVakjFebi7yB4fnQIKKEWjApG5r8i7qstxcSfSX\naeaVNnBs1s7RspI67z5r2OUIjx6i1NCKtS9Y7zSFTzVf/SEdfdxK13xIgQQU\nVI1sxvKB1WtgTLSmP9vUTWPa75WuzDpLAE7d2acpMFv0UPWUP85DWpXFF8Kt\nxUD8Zkg+AfZbuwRyTVAfwL9vYrhvHc76fiYAxrUpgfTqDLd9g0Ohn2SzHt36\nsDg3zs2rLUH+m+p8y4q7GhYMiCOsMvtsBqBEXwsP/aKuLfW6th2q9WgeQIkt\nr5DobS+qLjix4IK1uh9WCXUwP4n/+mONWlttYLMEWZJ8BK4HiiNu1ViDNGwm\nNwRUvV+WWqgd1ItxcxgLLxnff/5i3eexaVM8FGGO5GQ6gknllpqU7AQLoibS\ne5LIBdIJ/1rmz6lgyYL7+CKK2j0zRSAl90OATRRbgX020TdIs7NSE0LbGNRx\ny8VJxShnISqw8JJJFl8iW49wraofNq8H+jSyrAv//Sbf340OtFqBJmZe/LxE\nI+luWFEO+50SO3ZYgrqNGhgLx9EApqWedIedLlXMfAXlxNZhAICiqU9czVLu\nHKYl\r\n=rojm\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8caed919404c0a9721aa1d1025ae6142ff6b6bf1","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.6","@balena/jellyfish-logger":"^2.1.24","@balena/jellyfish-metrics":"^1.0.57","@balena/jellyfish-environment":"^4.1.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.121","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.57-renovate-non-major-8caed919404c0a9721aa1d1025ae6142ff6b6bf1_1618119851009_0.7147327435007396","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.57":{"name":"@balena/jellyfish-core","version":"2.12.57","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.57","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ca4102efe66ea0143a4a56ed0e669f4b2486b5ce","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.57.tgz","fileCount":99,"integrity":"sha512-RNn+1f+h1yOHbqTeSXF/PTosh/32DUXmmwnYvqrvLcHjXvO6PwJ7AiYJEqZzbKX97t2c2mBqhM/SOTGC4f1r9Q==","signatures":[{"sig":"MEUCIDk6BZ+ZmReKdaZx49mSmi414cdA/wnWYC3H06UKcs5UAiEAlBdcjnoZT/3qZJMTxvTuaUwXt2b5BMZE973CXHfS2MA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":681454,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgco2SCRA9TVsSAnZWagAA84oP/RsO27EFcgDS1Ug0VjE4\nl09X4vR4ctb053pUqh4/WaxBaqyinWjaZ/XQrEEML923ETS8tLb4Vqebj5nE\nH5FHV85IvZ3fpAcbS0CBRbOp/b3CxTv4avnWdtZczPvsW52o8Gz4UHwbbKUQ\nXJUi/rau1L1Yzamr3CTv4HA8VBxvhk9H35HqnZhRnkKHwv4sfQvTwLNV98g7\nNMgIl0+R2qHpUyRDk4HXYYZ6bDJGJt47KNTqga4gjuM4XqFz7tMbqOSTQE+K\nArOQWpQymeBDTjX57cMK+OtPNj/aqJ4yqhqY18t2QpijvdOBmyfbby/qFcbE\nbzOPQDJzx4BwiQHn+SydRGzE2ndzEIryD1D54kSQU6UyEyrH2pK02Vg4HXco\nu7szGV+al7c8IK3lH+mBFXpjJc57kMo264KkN+ivBG44R+oMBvuHf47mEUgE\nOWE2O/V93NFF8RVqxn4lSqLQe3Q0zZHEQFsEH+TkETjRE8lhpFhVRS0r+ghK\nY3Y98mW4sfGZRKzmBqZ3RRVeWijhVpeDGozLF3rAk2F08Psv6DhuSLO9pJv/\nhJPg4j3pZ+ql82cligTfUlWLInMCMoW74fuj6JNZA1DQnJE9EuGApEWE7KsU\n2gqkYlvDmwrZu+zwP9m1xpgD6/oqbaAUnLo/57pCRP/Ucj1lfbtKd4wXfWod\nDd2a\r\n=pQK2\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"abca71a96ad5320c9264838c4303f9667d56ec94","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.6","@balena/jellyfish-logger":"^2.1.24","@balena/jellyfish-metrics":"^1.0.57","@balena/jellyfish-environment":"^4.1.1"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.121","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.57_1618120081873_0.5521421677059053","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.58-renovate-non-major-9a57377b70e27c5ddcb30bd9c4460eddc2dd2275":{"name":"@balena/jellyfish-core","version":"2.12.58-renovate-non-major-9a57377b70e27c5ddcb30bd9c4460eddc2dd2275","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.58-renovate-non-major-9a57377b70e27c5ddcb30bd9c4460eddc2dd2275","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4cdd6756fbb22f3cdf4d2f36b42cdea08cd2b2c4","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.58-renovate-non-major-9a57377b70e27c5ddcb30bd9c4460eddc2dd2275.tgz","fileCount":99,"integrity":"sha512-A7oOYwLu/XQH9ul84ZKVtRBFkwYCxyvkHwhz0Isu3QX2Gyr52/2k2kagmJvNtIFcuuX0djnqL1nSglzDESyISA==","signatures":[{"sig":"MEUCIQCdDIC9ucK+AqfaT6OIxEaq3MCGl/VM1B7+7ruaBZkmCQIgCeXr2DFLjHrTkbgQ27Kj5iRYcUg676LWFXFqzYQ87Dc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":681941,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgctpZCRA9TVsSAnZWagAAflMP/RL4pZVSOepkUjQcr6QN\nd+IwRy4dHEwkTGcNx3FRSeCEvY5VWXNnPFs++p02ltf9i7cWl53OPQ2xzAdR\nXSe4KIZIUmmgKh3HgDbMZh2ayDGKsuLUhsFi53RNURYqe53IF/Bv8DPcUgCB\nyrNaUgS26/6rAp1D4ZmdMeewOBYBER7u6Mn3nqICA1TllYJnqBAVTSeoMLox\n8kv2th29E2mKBsJuZgJbiyh5W7Cje9+CJN0/tvT9SKj3RwiYVC+apkpiDWsC\njQYTrmhW28dUR9eVOy8ESxx5bE4nK5zom7alISNRNxJ6F9kO7KJew5dAwoft\nw9v6Nau/B1dImFuMbz+5RXbM92RpXZed2+/wQJZIUe3dQho0ruGf39a+23LO\nfo2f4fkNbB0HwcBw9sT2BVgTP5PlFwxa553gPiSeKO0j/rQBFAnXSJGbN4V+\np95yJab2ijGK84G155Sm9M+Wj+lWq6exwvYxFIR24OwQPwhgq8N0xDlhbzPZ\nTIcYPgmXHbHVTQLK6gYpk53mjpepwMYfST1SvNi2kmHGzPPlg7sGPw6tgO4B\nMe4Jwcd8QjD7ceWqK1qvn9s0QlydDGEvc+VLhOcZJgnzSzYwdYPHTo22sXQq\nsIun0UF9cnm8cmiK9JeV0YoC1F4c/w0WbCWjDsRSzeX8uRFSeHiydEyx5O/L\nSk9v\r\n=auT5\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"9a57377b70e27c5ddcb30bd9c4460eddc2dd2275","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.6","@balena/jellyfish-logger":"^2.1.24","@balena/jellyfish-metrics":"^1.0.58","@balena/jellyfish-environment":"^4.1.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.121","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.58-renovate-non-major-9a57377b70e27c5ddcb30bd9c4460eddc2dd2275_1618139737066_0.10900781944438065","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.58":{"name":"@balena/jellyfish-core","version":"2.12.58","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.58","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"17ff223b61f46fc26748ae79eb2d50fc8479eaa1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.58.tgz","fileCount":99,"integrity":"sha512-DI7V9b2zhC2gYOxTvHHVIWmc3N9URKAhafOyU8PmY24d4Hzk5ihkPzL4V7JVT5E9w8qlBk8qm7AM7ZVSbDkdhA==","signatures":[{"sig":"MEYCIQDhtIj1pZAxfREh7ucFXmtPTvwbT1SPzmN2AhNYr18oiwIhAPVQdQ1kKSw21RVSCKCi+rXaeennAXFrhR0Rfbn9Qvsi","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":681881,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgctudCRA9TVsSAnZWagAAtgEP/1twqlXsUYNt52K2cKP0\nCw/JuUU5+HbZbCgFBGFoZYSXoDJ+PGxt2CvY8kYh7jWj6Lzv+H1M7fcbdl8z\nlHpXB4s7bk1NPcrE6itxonKH4L2v+cU8OXmSln4ujwfQGOgg5ithxwR010mG\nMwOIcybW8LavnbcL4uX0J86HKHk6owY4d/gedj0boIs72gA6eFB2Ik7Tb/yZ\n+06G/MTWyXuzKmj0yAV5cLy8hOM8KE+NKscJHJ/mPN91XtoLUolbc3+a6jw8\nJomQBAnYPzzKF0rK75ykkG34hFKad9QURJhPXZh5BqCNfyM/OiL896GnBGF8\nynlndhZ8uNvNZxBUrNqHW8/tUEe1vw/VnHtZ6+LJ8zrt2KVx1Yvg5tc2WPeQ\nwTBPuIUXtgkPetzWXFduabkO4++t9qNfmBxYvgNgyITFbJoVesGXkBODwfnI\n8pdMm4YkxTBOWBv9jdVlzbEnPqCLs+lW892drLBtgiqmcmnp3y+kk2KIojpz\n4ZTOu07/h5YyhmKx57CQmLMzvur1NieD02cGk1W3M4aESSbs3iR9QWL0hOan\nn5Eyml09eLsFbDI+TBtFeWpZy3ha8lMQIGrDNHU/i98qc/TtpAer5Kyi6C6q\nYl+iKKOR4P/FK52D9dzyPPUx1x9T8Axzwz0/Ic7B4ZoQSUdWr2ohveBb3AMW\nMrym\r\n=ArNp\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"9622c0224228f2cd46bf8d990b4d4d7031c95d02","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.6","@balena/jellyfish-logger":"^2.1.24","@balena/jellyfish-metrics":"^1.0.58","@balena/jellyfish-environment":"^4.1.1"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.121","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.58_1618140061054_0.6908940264821182","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.59-renovate-non-major-e01061c6ab933f2253ec858a7e2bcc4e212475fa":{"name":"@balena/jellyfish-core","version":"2.12.59-renovate-non-major-e01061c6ab933f2253ec858a7e2bcc4e212475fa","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.59-renovate-non-major-e01061c6ab933f2253ec858a7e2bcc4e212475fa","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"19ba3ae7c475ce7a84a012998a29f5987146565a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.59-renovate-non-major-e01061c6ab933f2253ec858a7e2bcc4e212475fa.tgz","fileCount":99,"integrity":"sha512-tuKKxlAPWTqMvKcxKZrN2sLD4pRh8bY8TD0P++TY5fYOHdsUvOFgqXwL1yDa3DOaX8et84IShlNsIUtNTiF9Yw==","signatures":[{"sig":"MEUCIQDcxmeuE9X5/SC/AhnfHphvqhwJvYvhwAwRqj0Wnm9M9wIgUmpnH9IPFjVjp9wEQYGHwMrOKgto1utKVjvSGfB83Cw=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":682368,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgcwrmCRA9TVsSAnZWagAAQ8cP/2jYiNQChsGgXgsISIsN\ncDS0A7NJDBsTwpeSqwEoDI49D/Kd6Gj0P8gTn8JszC9rwZ8ZsVJorUEUpdWb\nng7gWKZGo0dwDgDIlxR3kvXrUoEF1/VqvlwMcepi934g5fwGz6UBtjhOZvM6\n8W1RqQDRY16ne1sdv0R/TMyl8kJeEj2HnZ3/KgwGo/EiRVJleISMOvuwcRd4\nKBfGXTKfACMrJFo5qm9jUOO3yzClRAIlqGokqf+ZZdl/W4r531lq/JKxL1Ho\ndcveLX8blZ3Bd5HaJ0tAlwxnSKt/jTQoM19y9L+01qLtrbeNPI0lwSX4YG3N\nANfWsSUybIKzibqlc8k32ULZ6cQtHDcWWAQ0QAdOAVkb1ua27cBd/C9ucnEm\nddAxlGrVo9yAlfSVe65TnEAteIAdi/oZFEqaQBqgYoS+McTZuWJq5con25zo\na5FteoP4DLVc1cr61/AEyQF0+iXCtv6tKr2pWwUFgDIvQXQ/I5KoWD8+lVpZ\nqDWCMrBFeSb5qeu+8kFVcFbe08zSpahpGbgDMh707bmM9TxTNhu3wyFDRHJE\nudB6gAGZgwK/Rl4HDrJfD9VrwabXOl7Z5sRZHtM4vb+Adjpjj3CKhbOe8jCF\nBdSTHD0hRwXSTdL3xlguHdYbiq/whRZIYFaD+mIrWmuBa+383iV3VN0+t4Fb\nU6AH\r\n=UuD5\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e01061c6ab933f2253ec858a7e2bcc4e212475fa","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.6","@balena/jellyfish-logger":"^2.1.24","@balena/jellyfish-metrics":"^1.0.59","@balena/jellyfish-environment":"^4.1.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.121","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.59-renovate-non-major-e01061c6ab933f2253ec858a7e2bcc4e212475fa_1618152166394_0.38487538153213885","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.59":{"name":"@balena/jellyfish-core","version":"2.12.59","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.59","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b63faad8191e58270c18d9926607d4e407b376d5","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.59.tgz","fileCount":99,"integrity":"sha512-LVIKtbY5rD5vg3vtGtwu62QYVtA+OP9WEvQGFsx5M6cc1MFcv5+ISTYwZ0l6Hv67sVxzfJPQnDT5wmybZdb0Xw==","signatures":[{"sig":"MEUCID/J9ZhOGDWemIt8ZHQGJCUeIEqC/OfNu6/uERF0fbs8AiEA6waeG6pIv/3tKMonM/Wsr2eJqJBT3gKp3n4fMMW5I1A=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":682308,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgcwx9CRA9TVsSAnZWagAAI88P/Ax/HINX/wGT4o3MTWpI\nBFeq1Jl5k7LoA7x2CFW47+qhKzFbtD07AXhLt88b8SFe00AT/2Lwv9CVSO3F\n66FWNdsEZk9BLGAp4ogTEv8YeMMRKQh/xR2T9uhvA3fYJrs/KL8fazr9WEY3\nXlA+6dA57TQRCdINY3A1pnh+iSZ48tind7t4qukxu92f8j2tvgY0E1ohdqNb\nvvcHhzCXAv0hG7nUOaWa8JBk/9a3G1rYkGxi1BOqrMv0Xf6MhSHe/ETKSzpV\nzO/+vnh5krr4rBfqYZcfN6OlqZIcEEP58CiTQ3BZIRY1i1wy/WJdixXZFJag\nCDutzH6cDG88CmhSi7fgKlu3TXf9v1jrsZD2zjEEBK+AGpteZr8ZvRIuVo2f\n6CFzCX/jeG1oZbfcQwFaElxhJ3ry0fx3GrSoUQQCNhKwHShO7eItJkGJ/aUL\niPlcxsN/AoEnNgQgCHgbXuBxx5G6B17SM6QEnxYYGv8KVkMd7JSsZFPFvTbW\npShQPVJbYaNUyJibhVrcKjrrfEn3P8dhw96Ou/N0P3PS3/xiujYwyyniGyrv\nC3I5rSykOYXV9UN7JcAR7t1xqnoEz+CK2doChtET/kdYO4EGl94GIujQXwgS\ngFhYUAW15Cg9Z6l6+fQLSj15hsmhmK+0nMe8RewyPC3RagsNjtZ8vifOB8Fd\n/GMB\r\n=TNG8\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a95266337c5ff2203f155f79b1fb200f854f92b4","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.6","@balena/jellyfish-logger":"^2.1.24","@balena/jellyfish-metrics":"^1.0.59","@balena/jellyfish-environment":"^4.1.1"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.121","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.59_1618152573349_0.03426037450330921","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.60-renovate-non-major-db03dc89ad66b1119b9d62ec7bdab3e8074b1dfc":{"name":"@balena/jellyfish-core","version":"2.12.60-renovate-non-major-db03dc89ad66b1119b9d62ec7bdab3e8074b1dfc","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.60-renovate-non-major-db03dc89ad66b1119b9d62ec7bdab3e8074b1dfc","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9fa9a3bfa5cedbfe4397b0b00844a998de9e3bc5","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.60-renovate-non-major-db03dc89ad66b1119b9d62ec7bdab3e8074b1dfc.tgz","fileCount":99,"integrity":"sha512-F/IHZJjT8PTZqCga2IIcdyGRXHnNoUaIlen3qaCFZnE7KRkW5rQoAPCZJez3T5o/w38pifGkekc+QOVuSqQiYg==","signatures":[{"sig":"MEUCIQDN9zkZ+1Nw41fIe0ztFMB/M9ogNvmEFLSILSYyPVgAQgIgbih+P502MdFtgdlnq6JptnfgTRBLZKtlW9TgKKrErzY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":682795,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgcyFECRA9TVsSAnZWagAA42oP/296DhUbLaMzLjkGF49Z\nZy0HDBnWpDO7ByglVM/LGNBbH28UtU7mSGx/06TRxc/D5dGiffxq00HnYAcQ\n6YbHGl7BxYeozmaXj+1cXGl/OCHSC9mCStZox+2YARtpN5xWqsYAf0hGqRKK\nDAGsDqPKPUM5DUK7aO+bp5GOdMa6QNVmcyJ2lVIxRYF02q17xjky4BSZyQHe\nM8FecOAHg7If4fpH0lwqNzAkDTlHBFaGll/f0sh1VM8KangQ1udKHAd5QwVH\nh6MtIXqLjGIACUinMqFODEYwllq6CrgfvkLdOcyhbcag5TQ4UjrZR+r4Fc1O\ndzpGuMvsWsQBuzZgJ6w6ud4lo7aYpSay4ejiKNb6Lk3hjtwKKQS5Sr/YQldh\nqoCEtkzonGMJEmBcda85CsMESlWIo+QouLXDQKTw1383C+L2qt6foqSdw6x7\nhri5254DZcoWJR8ShaCzKG6YIaHTV+jpEN7XR1vOa9hIGRmFWe8lqsYQTnoa\nHMh9kARLq7LNVkSE20tfPgwCmqKcmyA75LUQdnYGWJ2LUXQPFP0HDO+dEYTs\nrO5/94LSJ1AgpRgPamBDI4MnJWLQLi+Z4/r+Nvx+G3f2E7YWv5NQxqmJgRlV\nooRR7SbhhnybazvsvJUhsSGJVXLdgOW/Yg/GaZrXBgUgOEf2UBdwACStbDZy\nPRa5\r\n=nqcD\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"db03dc89ad66b1119b9d62ec7bdab3e8074b1dfc","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.6","@balena/jellyfish-logger":"^2.1.24","@balena/jellyfish-metrics":"^1.0.60","@balena/jellyfish-environment":"^4.1.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.121","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.60-renovate-non-major-db03dc89ad66b1119b9d62ec7bdab3e8074b1dfc_1618157892377_0.041979917904368946","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.60":{"name":"@balena/jellyfish-core","version":"2.12.60","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.60","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d441934842318e31c715f32f9d0085ff3f8f611e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.60.tgz","fileCount":99,"integrity":"sha512-WaEhEYOKZxs5ZDcAja7vHTRA3y7inIuYfpkhJ3yIejAT7x7xRsIjgrGHXjMYcOQDFPDyNwKaUILH51G3jm97KQ==","signatures":[{"sig":"MEUCIQCDYYhj7mITqnhkszuEnQ80SFfkT0pbelO4EyqX/UGPiwIgIlBV7GtATmNG7NRiMEP0FDSjdGNqZjnk7BzehSbFfLA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":682735,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgcyJUCRA9TVsSAnZWagAAA24P/0qi+wqzhAJSsrEdVTMJ\nrAPnRpWWJJDHC+hHARd+KdV94f4J61TACHXnC761fBkOafJd3O9jAGln1rQ2\nEMMBkJg0ciXIeGWS6o8uFRmROcGIpYrg/nd8i9jiUsT/1mg3WxwNg1GHykXA\n3T2nzVyeArZ/h7p/tRjSSBj80gxi4Y8BKisLis6IL+ysmwIS3D74f0kwElwD\nwYAPuEqTg9CiHsolaY/EYH6eCsLNmI6vLfgXhier8LFnjbM+ASAg/f8/MNhM\nGPonz3/biG+Neuv4724v7syTK3lVQV86zcEGHnc1mv1zsot/O2LWco+MiMTp\nB+EgQkIpUgHaMbGbVzSpPuR4/T5vGJnRasxpF6ROF+ErSaSsF8t2U+4blx3/\nuv4rt8GDIZyTtmUQJlQ2rEBTCaBWmgiATC06sl+YITtvwSkrNuHhIz7Jdx/V\nvZEP6zSN4Ino2NdWEdBV4WdUs9Y3PSGeHN9+ebOl+q/Y251skTlRz48KmeVF\nVXZwFbzpKa/0gqe03giLO7bB/sjStLKjVoIKKFwhg6Bc/2dJEjkN95yuR39M\nLr8wqlIGfiaKqMHQ02oCxa/73ytIbFktFvfvh5gM2KpM//WpQLGd4pjFdV3L\n6fEuh1QWkMb7FdHCP7ZfrEx/wtYtok6sO7NfOH4Bv6d5esOQT+zJhbUV6V71\nigAM\r\n=hfxF\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"844a258f056036e45772f5b397c110c4ca647957","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.6","@balena/jellyfish-logger":"^2.1.24","@balena/jellyfish-metrics":"^1.0.60","@balena/jellyfish-environment":"^4.1.1"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.121","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.60_1618158163746_0.2149754915219515","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.61-renovate-non-major-84bf177b8f837fddf04ffe74e20541347e878757":{"name":"@balena/jellyfish-core","version":"2.12.61-renovate-non-major-84bf177b8f837fddf04ffe74e20541347e878757","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.61-renovate-non-major-84bf177b8f837fddf04ffe74e20541347e878757","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"deb86d403d8477ed308d6065225a8740b644003b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.61-renovate-non-major-84bf177b8f837fddf04ffe74e20541347e878757.tgz","fileCount":99,"integrity":"sha512-gAQDEYtaXEDm9NKAyU8N+qi9WAuFvV1evWKbNhoogyj+D1ZDsQmfyQ3cs3rK84XM69OIPJ2BKEvAW2u4J4D88Q==","signatures":[{"sig":"MEUCIFytO6C/C8i/DwCkH1rrkyLStgq6xhIDyjom6GIhpqSdAiEApvRAi/bI8aqemQ3b7Zieng3D+5PdIEOD4hlLltVHBmg=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":683222,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgcz10CRA9TVsSAnZWagAAEKQP/RMMQJ2WG4vVzfjZci9d\nWx54Sq1Rg1dVFHYNoI82MTODVdcMDPX16hKCKSqsknCivry3Arnv5qdGIUu3\nm6XNoBhOPRJPmkgqvweNPG4qA/qElOAKA2c9K+6/H8y9ZX8b7G9beJU7zuXy\nZX30oJdaK+/xjox0Fv/gdDoJBuYOc02wxTUDCsYd41AI9pbNbqg47v8Xer49\nUQVRsU68ldigMuplIIq4gvkVhOKNitM/zFAKVhGL/mZIgjckEX0eK2GO1byu\nVxFXzO/J9wKs0LJ14HfItd5SbOX3EyWdklToY4NtZ2HOf+FNFizd5oXfTmuz\nfwA1K17R1TTTuOq4/HyrhxXgDNRDB9paRd72cf/ok1Z4rFVZqkib+3z9S/F3\nqi32G44U0SBd4bglZaSNxHiQ5/J9Dn5dPi9OafCQZzNQweBt1DenvtPJM0+v\n2CWieMg9zZsfsojFuA/3d2ObYkB+qb6QttzB06XAK37y+KgTIX18dM1rY922\nV4iULfan7TsqkKFe7rZc+9kc67stRtliIwkKbATFuyyxyhr9Zdbz29FmDlNi\nRn38EnKwXiJWTe/+uP90YpqCXEVzZJ0X67/QvQFY5Fccrmi7InwSFvw8+4es\nZK7Ct4AylDzpKiomA6UYsDcD6RyMkpTt9XFXabIbckRLRLEaml6R2lslZ5hM\nxKmi\r\n=8DVD\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"84bf177b8f837fddf04ffe74e20541347e878757","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.6","@balena/jellyfish-logger":"^2.1.24","@balena/jellyfish-metrics":"^1.0.61","@balena/jellyfish-environment":"^4.1.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.121","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.61-renovate-non-major-84bf177b8f837fddf04ffe74e20541347e878757_1618165108099_0.20452730448359135","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.61-renovate-non-major-8b5d687d740f44a9efa78a5f299998c61c743bee":{"name":"@balena/jellyfish-core","version":"2.12.61-renovate-non-major-8b5d687d740f44a9efa78a5f299998c61c743bee","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.61-renovate-non-major-8b5d687d740f44a9efa78a5f299998c61c743bee","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"251b2c44ebe889e3261b7200a31cb4368bc4e04b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.61-renovate-non-major-8b5d687d740f44a9efa78a5f299998c61c743bee.tgz","fileCount":99,"integrity":"sha512-IuGeMJ1CPf02M041uSC8fY8GNQBnI9w/ug8hfcu1dRgECUQZR0vMc52spPmodxSJRwhK7g4+d2D2sfUPZTNVHQ==","signatures":[{"sig":"MEQCIDUAEw3vaCYkhHcxXKDfvbCN4qPMLSjCNncKyvEHBvGSAiBeTrSozUOwPysCNLc55ltd5zbjiLvbwW9mVY/wD/aNEg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":683222,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgc1jRCRA9TVsSAnZWagAAg9UQAIIWtQwXUL6fcTxqA0KJ\n9xzMKlk6tl6VaI6oox/xJcebqQ0KIIOWZH061Z9atA2eciMnc1OPy9jQYm2F\nmJzRvycAdxtK5spZbQqIrjfJ/ErRtXR7RJUG+vgbnHTnQgOlp02XR3ruI/Sa\nkfnVean9VEuHsmVDp4c8DEL5QBqGeT63RHxtObVz2sstHnTUDcvwL3mq6ngN\nRyxVjeaUbPXXQXVF+He9g7hV1EN9Z2MCVCJyktOyLNWrwYdJvZvj73bTK0cT\n/xXDUqqIoU0vtQAQfa6wVvZ8NKgzPyJ6DB0RJiUGz9hfnenh2Sx2amNif2vE\n+Tjhg/1XO5pp8C1a9J0ARhNR3rOkBb0lv0uHfOXDdk785dXtaH3ab26xpj2X\n6ASVpdNsOKf+vM/GBE9OQaD7U15jCVGNHq5MKJqufpt53w/xh2JfrLsTeOex\n7Qa6CfmLfeIWT3w2bdx+VaPVda13ZzbDYI9gFHGmxCgwwFqtoezK4wO5wZp3\n/8sUirZRksZYc4LTYEmnxqqOn5mrvuuhI3zjyvNLQ61+AqPrSGBgi+yDySxD\nwA+cB/tTyvGn8ndkL4AQ0dXOJypL/SFUFtfRGyBUiUgQK9raj+QG/Pn6rAxb\nrNmy1hGylOmtn52hvOrts2kkSSkn/6fGAzWeUHuoaN7YYj3eBvVMAptdpd/f\nt7hW\r\n=zBh/\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8b5d687d740f44a9efa78a5f299998c61c743bee","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.6","@balena/jellyfish-logger":"^2.1.24","@balena/jellyfish-metrics":"^1.0.62","@balena/jellyfish-environment":"^4.1.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.121","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.61-renovate-non-major-8b5d687d740f44a9efa78a5f299998c61c743bee_1618172113149_0.3805040078472912","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.61":{"name":"@balena/jellyfish-core","version":"2.12.61","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.61","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e52dc654aca59ee8b6ab27dea84fd17c7e637eda","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.61.tgz","fileCount":99,"integrity":"sha512-jjBLwBetVGHyxDRGHPCcKxcSruQWkaYEVgsz9PnINVtw2Ry/7YBfonChfd3VrM0SGPrNIW+bhUU+qim+ZTjBEw==","signatures":[{"sig":"MEQCIEIa3glHnBGmf9rMHfzjosp18nTyby20uMqTi2IShl81AiBoZ/MSVfKCJq3cdYBtg9P+O8k89HU+1+DVJX9+COqxDQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":683162,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgc1qsCRA9TVsSAnZWagAAuzsP+gOXm4xedWJLuvJX+Ge+\n7CzYx2g3spW4LawAIhw07FVg3fYjM78XbPSwgBnSzuycqcmQbb3TogprXufc\nTvzfmpnzncdXVr1v5MYWJsaTZhiLvqi00yojPW/VkkyFs2vWnMOUABq4PXGG\n2fZlQ4f7mb9yh1Z5nev2+sNIBZcsKNqIB4kPjnJBccQqaJlYHzfIYzukm8Yd\nwVfD3x55F/5uUc5Fl6orSX3Dh2GUK8rXsWNhr58Lde1Oa+X+wJLVbzNpAl27\n3+Y4UM04oFX7QUL/x4Z54UtKT7AcjrkR0bfCvf3U8vGO8zLoNX+8BDWZUi6p\nMp1QlgjalSJpPhj8QOi5g+qMBFfB/WiWC5I/jJNmBA8MWDI/EfGY/3PAw8oK\n9n1/NRHfPTZNyzOj8fihqh1vVGARlOJR2PzfeSe5gi9JH/oFBDOCP+MrPXjb\nxMxFtaRooRSG/VWf9x4+0my8SrJq3FuS2jvbDfyyHTDhPXKOP5hbtVvbP/Kn\nsSqn/1gN2h3jtKSDX3IDQvSKQktJST3zgSZ0rKa6WZ3JfTi2K7KgPR+T+pXu\nPtqz+7Iowd4W27CRxSk1Z53iZPb94OIFGO+WovtD4nfk5nwRrmg+fn9Y6WrP\nov5ZUhzckHxgmIiZ1v6K7hMPCNLLfUZ4Ij4ohTzoK83UZGwbjJMTbmw4tphg\nI1Db\r\n=Z5FN\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"44db247c32f6d4439ed0c74a4ac9d76efa5f9b50","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.6","@balena/jellyfish-logger":"^2.1.24","@balena/jellyfish-metrics":"^1.0.62","@balena/jellyfish-environment":"^4.1.1"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.121","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.61_1618172587806_0.43252247746732486","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.62-renovate-non-major-066c6cdd2597d0e8bb5aa568fae03c22c98b04cf":{"name":"@balena/jellyfish-core","version":"2.12.62-renovate-non-major-066c6cdd2597d0e8bb5aa568fae03c22c98b04cf","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.62-renovate-non-major-066c6cdd2597d0e8bb5aa568fae03c22c98b04cf","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6142682053bd1906b10083a931e602c5a9998cce","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.62-renovate-non-major-066c6cdd2597d0e8bb5aa568fae03c22c98b04cf.tgz","fileCount":99,"integrity":"sha512-Kbklgi3D5k2BhRSFsbpEvUhAP4nwqYB0vhe9YIXZ/eXr3XoSW63Piwa2kvpp0vRfhG7KKbqQnHRi5WKXu34X+A==","signatures":[{"sig":"MEQCIHoWjSrh1gelIYD0+Mk9auZPNEor2W1mW+GeafM8jPxrAiAjNSDjWYnUG6/Y28tzw5nXMXYut/6FETiD1HYaz8qs+A==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":683649,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgc281CRA9TVsSAnZWagAAKXYQAJKPXO9qaUT08r729XxU\n9ozc21HGtG3D1f8CnzdJXO8+YV0asu2DZAzKRdJvta/VpIAxdidnA0kdK0AY\nvF9ynw6RgacUH8uuMYWcssE6pV+m9h6UET8q6+C0zB0jzAu0kYG+DfeJFwmC\n4a8X1ksf2ZINmZ3TvoMUpDd+ZnvME/Tuj2DDEzNkz15qzIClqw1u+9/4GGXl\nGAxBumld/XXa63v20uf2VgVI7vSADpIKuXAQFHf5HcRWpvG1zBLIaTtN+FOt\nGfGp8erSKV9gZWwJuZg8E0xzCQi3IpbUDaK/1rq6Nfy/aVxOk+lE58PRjfg1\nrkmpbd9dtDU1GpTieMwAWKh/oKB/sQjEAg+5+AaY9ObtuprzDUKL8raI6qUi\n9O5ryXa1Au6eN+7sUCtaEkz7NhC6tIs1w007KzA1595+ahUQuYeFjwU5Y9fs\nkTxsMemYQ24M5zLwVtuckWc5Ehoc2myX7OpCo4qkYj5uii2niMnet2JvIX5C\nlZRqdw59vpynY4TLnuZwfNXZH+LL0vmWr9xEFrTgGhQhuizQcD9ExHe2sU9i\nosjqDBdEKwLwvi619UvA58wZWyXt6PvpVKwtwkL3DC23tSAWUw2NafRhGdQ/\n943jOo81sF7DLvA5q1a8AgRhuQBAk2xTtgoO0ev5z0blK7e+hDAAz3k3zMSZ\nV1gQ\r\n=ycX4\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"066c6cdd2597d0e8bb5aa568fae03c22c98b04cf","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.6","@balena/jellyfish-logger":"^2.1.24","@balena/jellyfish-metrics":"^1.0.63","@balena/jellyfish-environment":"^4.1.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.121","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.62-renovate-non-major-066c6cdd2597d0e8bb5aa568fae03c22c98b04cf_1618177845412_0.9855245427758201","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.62-renovate-non-major-49eaf288f2ebc57502954257cd2042a9724df54e":{"name":"@balena/jellyfish-core","version":"2.12.62-renovate-non-major-49eaf288f2ebc57502954257cd2042a9724df54e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.62-renovate-non-major-49eaf288f2ebc57502954257cd2042a9724df54e","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ca24af82a418959f8460e3a11b8e9888a3c8d1ad","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.62-renovate-non-major-49eaf288f2ebc57502954257cd2042a9724df54e.tgz","fileCount":99,"integrity":"sha512-Hpny4Hkdqt4yRsRQiego+LskPQJD09dy/fMNAqvPpn4Uwh58wqbF04Ch6sGyw/uKyuWq0a7nQUIPBKj026R5RA==","signatures":[{"sig":"MEUCIQCSbuqE2i7i39di44SObrn/EEwloy40qUcSbE244PlQ8wIgSDDSR3grqKIrjH8YlZQJWBAtHoXrXs+glTtm7zZoEko=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":683649,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgc5eRCRA9TVsSAnZWagAAV+AQAImmq+WmFaAogt6aGjkq\nql2RUQQ3blD9pjLfoyZHOg8zPbyRVP3BuwtDp1tgWtqs+Ve4dB9Cd2nvqmM0\n4YQXIchP37arzY5DcPLdL9n61tDCKg5MbAlBfb+KdCjgriQ8E9Y69W7PhOe7\nLB+RjJrfvzU02OPujO0hMrauNN1JLbyskzFq0yprx8J4Lj5A2pBdWs/qDO8d\nHaUyci1DmpDpOH7RiWdYQc0560F7p0qv6oWXeNEt8scDDCnqtX9DXOJzZnI3\nj9ueDz3j1u17jdRUuFQTrHsqvvA4fo8UEuytbiyynA1xGVUTdCSqscn1YNgJ\nuSyv2VW5GzdptUBXNDRkPblsekOpR6Yp5+0lHHtrWLxkvP5EZi6RGyv6uEoh\nkfAmZLe1+yKZj17uvMhUr0c8ksuf/ybBvl9oE4oYW+lnxoST2CLiNqzaX23G\nxAfJvaGhkl27FwaFPfUnymk8krbIKOJrGwho2USGQcV6dvLlnlYTz/3kXqeO\n700rc7RISLOkuddcDxV70AcvvS+EwwZW2lJPaZE5gmpnWRpZmvLmjMvbsB+n\nRBXuICxk/Zn3yCMzO5qW0vWAJTjwr5zSVCvMCMmwEFC/ohG4Wcjpx3HyM9a1\nFWRh3vjOth2uzUARlnv674HVWziJvUslcxsMQIDd+VVCqSslMgWk1ZiYX1eX\nDHMG\r\n=FWM9\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"49eaf288f2ebc57502954257cd2042a9724df54e","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.6","@balena/jellyfish-logger":"^2.1.24","@balena/jellyfish-metrics":"^1.0.64","@balena/jellyfish-environment":"^4.1.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.121","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.62-renovate-non-major-49eaf288f2ebc57502954257cd2042a9724df54e_1618188176925_0.29841671326058394","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.62":{"name":"@balena/jellyfish-core","version":"2.12.62","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.62","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3fea5b736d56b09be8e4cb4e298c781f170c527b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.62.tgz","fileCount":99,"integrity":"sha512-xjOqhKlpvwU2tKkLxqv43XI+mNfB5vhSeZy6sLt+PvXZUtIZ4ABabY4gOJTnzr+62tC7aYHskQBF8kP0MwtnCA==","signatures":[{"sig":"MEUCIQDGkljfbL/2x1b3s78PrBxp7G7Q0f8kXsSh9mFrFxiY5wIgGPMxZUNR85sSgc+Gm2rlz/+sxK969sX//wNx+6bEhSY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":683589,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgc6HlCRA9TVsSAnZWagAA18UP/jkyv5dDnThE2tg/711s\nhcd0a/rwotjExqtbe9gHXxg/nnfT2GVHF2tLTSlmkbiRUhoO7DGAXM3i7y5E\niIhJLC+HQlBAUV3153jy2O4j2eCUjP1AWwiM+PcZvN99rDGfUCCuiZkvKZFD\nEgomonxvyH4qlGYwz7EBzJcg3kLMWmBtizURetWVXjLkdHmnyuZQ6xp03D68\nSFb/bpXfafbEozsVrCma8nkpD9JkVAu8qKGIfnHc3Ez4svNC56cBU+Bjo1M3\nfQFnzim0zc88b3aGdSWNxSGOHYn+AoNp+BUb3xUvdp8iu1nhiYkjwXeyk396\nu3PQltCEY5xU5AkN/UQjyJdvBEnTaVqNKf/oHlxFFmuyoF3znTiH+CpACz+K\njG4RG5O//TEMA8H3SZQMSWFTyO4sPATNWMHZe4x720R0I1V+O0QZNaFcFVJk\nM3j1wXrDWBa2Ed+loQOdbAp6XzIRWqW16EfMq135/B8PhUPAz8ILxpY6vkDk\nOuE/gc4IFHmc2/8RPqrg85q7WMEWHQOqhwHUpgioBpZo8w/qLgXr7doEf4ke\nju7z+AELSXBOwNOEYK2E4oa6yfdmm8vaNb4Vau+PtjyRDJKUzfBMvdifrMRB\n/4TzpC7egrUSGcSuJ/q6XVU5RiMY+jzajbUIQSyGehWdfkMYIPfzxri8MaXo\nYwF+\r\n=WzNx\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"906f5a4e42665deedb5a3494104f70726a67a2bd","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.6","@balena/jellyfish-logger":"^2.1.24","@balena/jellyfish-metrics":"^1.0.64","@balena/jellyfish-environment":"^4.1.1"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.121","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.62_1618190821333_0.41567388818185735","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.63-renovate-non-major-0a3fb293b55eee0b0fe7e1699308474f1b105394":{"name":"@balena/jellyfish-core","version":"2.12.63-renovate-non-major-0a3fb293b55eee0b0fe7e1699308474f1b105394","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.63-renovate-non-major-0a3fb293b55eee0b0fe7e1699308474f1b105394","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2181d1552146a2e95b239c1de573fb7ed68f55da","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.63-renovate-non-major-0a3fb293b55eee0b0fe7e1699308474f1b105394.tgz","fileCount":99,"integrity":"sha512-2ARxRHpCachq2mYaO3fow0Vu/EtCMFzv6jZy/+qAnOWx9VNVsLnQX1+/Xb0CmC9zb8cbcj5deDL1qtFNLC5DWA==","signatures":[{"sig":"MEUCIQCROkeEhX4/oI9M+mgpgGHvkBaPE4kbZ+dyPkMOvUpUHgIgLtvqcJiNWizF4LM/DSBavwaO8PBl597YEGq+vJw7x+0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":684076,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgc8UDCRA9TVsSAnZWagAAQTUP/j66SYO0sPNo5+XWtsD3\nkVxsvkEGhWF9/jtzDCFY+EMO9W5sSPX4JWTK6raCWIsyeKGwzfTQj3Yx8ljL\nmrfVVWkKlKYDgbKhfF78T4lLlt99Sb8B1xjLcttgdrfBrXW53O+ZRlRcGMBB\nMUL8FbevQSul280MnavCz//dPEXpYvwhLWWISTJN2ahQXhMY5wJAnRJN+qe9\nc/pkuY6KXj94+Z0DZPwtX6tU+dknRw+k0NFzpLD5KB1h+DNfdZ/tH3S7KLYo\ntSe7q1MHNCydBiZxXPEtV+0miUJAtEjbrYyk1ZhBPJCA8y/mLn1lHo7ha9+P\nH0yExCe9Boq4ff5yQWNyz77jsAjVM9hWZtTcjr3St5oRZnYdRnmBc52nNMTR\n+W5rxi7bdQks+jOidid80WJqC4yB7xiyTS7Cti6FYESEgkyDeesdGoGPlrOn\n89GkaMW7pAB3fOix6qZAkMNGN0uuada4wMQWcHoZju3XCbgHqG4dmn4wuaPt\nuEsWQgY/ISVkV8AB3RuwEUONQAsK6QWf261uMUW3y6F6WDetOSffHC/Dos18\n7FeAN74uUBEQJMAzP0drHgJm02zPVc1Fe6nuOU+yARCkxe4/MZaapmKyIoYP\nn1pWeIzPO9B7Y4CgrEWff3dQgL0Vrg8RKFxjJZxXLoLLPwbFymbZdyL+ywDF\nyUAX\r\n=o8iX\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0a3fb293b55eee0b0fe7e1699308474f1b105394","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.6","@balena/jellyfish-logger":"^2.1.24","@balena/jellyfish-metrics":"^1.0.65","@balena/jellyfish-environment":"^4.1.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.121","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.63-renovate-non-major-0a3fb293b55eee0b0fe7e1699308474f1b105394_1618199811033_0.19473377843703354","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.63":{"name":"@balena/jellyfish-core","version":"2.12.63","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.63","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"17e885f1f3b1982dae2f6d3dd8008a7b776c22c0","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.63.tgz","fileCount":99,"integrity":"sha512-DLArMtcORtdKeiQpHhYoJW3TupM66PFinWvAbCV78rKRfgQ3ONjH1UuiL0g2t8FV1Ms7U+cKsAWON5VybGuiKQ==","signatures":[{"sig":"MEQCIAvRtHfsE2vLfDSCsdEUusJ4hFlm/Bxzxy1Z6M3guv6ZAiA1vDnApatYnTpppeAgaMYXp8Bksb8bv8R8nzTXHmRsBA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":684016,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgc8ZcCRA9TVsSAnZWagAAg1AP/iEmL3LN1kjCEuSow/Bu\nge9wnAR/vB2wS70473hJgN9/nrGqzXJVUBBsfduDJLjgMBzzts0oEHvvPpjc\nYsziPKwSxTz+XVTeMJnt5yec8KMtdRc9lqyYXNCrKQpTj6agEuqqXAhgsY31\nDLenL9Rx8eR0lUBqt2NkbYW2HL3/HemcWzAItekBshQJlI8gahRz9Ng0gmtm\n01Fd+3Si6N7xtBg3WbkyyYGvMNDH1HayqB3IC1lRmop0OhmVfmSJ352r8ZN7\nufiV+zpERIJGIkIj9V7QOSSVW3bzkh40UmAvKXMRh5qNxXGZ+t6Tn+QzTc/Y\nR0qXaMeID+7THKUZCcBCpTwRA0tqxyC6hVRbgE0qU2WD9cHooDeuFWargdzL\no3c0Ndv8SLw9JFom+akgVfVUB+Aih2bjsxKnOk+O6WKQ9DRSfpSVRQbtSkuY\nKqd+jxpSAxI5+Tg0v6ZkF2CGkV7ronCfGaJHnjj3I7UHc5+8PNfWkOrWn8ux\noE82raBqfNAQbNOBvaM9ocq1tIgv/PWiohLLRyksXPo9wStvJzZLTBw6fhDq\nIwp+CofTCv3XJE+WXEIivrP+M8zHs06nhAw4sy5heeCzjXRVSZd/Ifjh8M/e\nwAri+gV4hFBiGPl6E2Lr8Yj8Hq6KMbo+0JGj0k0fOKo1JTtWIkkJ8iMvZ9kh\nDADY\r\n=Dc83\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"24ed4ce219aa70d868be20f90a006cc0847dcbb3","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.6","@balena/jellyfish-logger":"^2.1.24","@balena/jellyfish-metrics":"^1.0.65","@balena/jellyfish-environment":"^4.1.1"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.121","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.63_1618200155827_0.9322689824357526","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.64-renovate-non-major-aca66fbe920f2737e6e65bc017b793555e32e2d3":{"name":"@balena/jellyfish-core","version":"2.12.64-renovate-non-major-aca66fbe920f2737e6e65bc017b793555e32e2d3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.64-renovate-non-major-aca66fbe920f2737e6e65bc017b793555e32e2d3","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d49538b66f65dfc1fdbfa10f0192f104b0044fe7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.64-renovate-non-major-aca66fbe920f2737e6e65bc017b793555e32e2d3.tgz","fileCount":99,"integrity":"sha512-IkShy+2NCwjxfexZlM0wcftLAbH1o6+TK37g1ZAOHOtRsHYSx2s4QIf/7PuT56nJlrfx8gi1xJcFIrshNG4Y0Q==","signatures":[{"sig":"MEYCIQCyciZ9caePMH/nBr5Pvc57LzFaOX7mjjL/0zyTUrlCXgIhAKkdt0K0oT8j4VIiHi2XwM23Xg2ChYbd5UgudWvbJMgy","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":684503,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgdCB8CRA9TVsSAnZWagAApsUP/2pSzHieB6a86bhWwjDN\n0u74BEcttbEslIVzAugkGoC7syqErjsvpna23BSQq+7NqYSFdUDQ5waKCfqj\na/9XY+x4XZ5rerLqPh9WKwZ/NMg+DBCa6yuHA2qS+jvOoGEkz4v3QhPIVcjk\nz12B8GAfAh4Lq/rscRH4WfZg3Ix2Lu0i/9jOlNI6iWCLo+TDXzLwJluENBTI\nPbWWlppci2Q8lTdi3BCMnlU/xOHMijCiLkKVFKU17xH5vQNRaf/7aSS/FIWZ\nC6HCUMIG99lvbFGEjHjcTczum46sb1O82iZqbbtU5FAazM+Cw9a99rDjrpu6\n4IF7VVUE7UWhd2Qp5W5AuHo74QwmrdZrVRVd+cUrr0ekWiUx4TK1gcI7pUkY\n093JuTEvDfs62FVb2t45FsVohfLgZL3evSNeh5gJZxFZx1IBYhT2R2XKtzrE\nIxl13KEmXTo/7zSoC/Fn/itjccUjWECiUePVV/obni9f5R5x6SmAyXS07chZ\nJ8en4cSTOItuXtme3oODtUs3vnKXxkrtky8YNAxHY5fl+/cxqpiBIXxPUs0y\nqiWV16T9fdC9J6Jz89OdR6FNvXUO6iGYgu0MzA9vS3+nOQs94rXWJ0K1eOkj\ntRjDGsdixaCUJd7t+5YN2ZuDOPqs7v7BwJ6RMPl0DS99ywuZZ8kU02yfuPAU\n0TYM\r\n=1rAW\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"aca66fbe920f2737e6e65bc017b793555e32e2d3","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.6","@balena/jellyfish-logger":"^2.1.24","@balena/jellyfish-metrics":"^1.0.67","@balena/jellyfish-environment":"^4.1.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.121","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.64-renovate-non-major-aca66fbe920f2737e6e65bc017b793555e32e2d3_1618223227551_0.7986358053063545","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.64":{"name":"@balena/jellyfish-core","version":"2.12.64","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.64","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"98bb4df8975c0b025f355ea1cfd14761977204ae","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.64.tgz","fileCount":99,"integrity":"sha512-fWWDqOfPg+zbn04EpX5WPSlahpCIv/0rOLKdqaOv8RkJZqdgPanIBUDyQRKhE5UJm6Ojd7gay6NxbAEoyJV1DA==","signatures":[{"sig":"MEUCIBQGtcW6C14H+qgTb4RL8Wc9hoUi1tNwoeREtO6rDhX0AiEArX/ESeX49iEf9BHEOJDQFzi5+r6Vh6D8eBWllgkN6Ck=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":684443,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgdCH/CRA9TVsSAnZWagAAkCYP/2+OIUwsEgmBZ6PjK761\nP5GFzmUgZ8YR3Vu3iKxMeOm2ngA617v4mAzziLjfTUr7+tWi366dqV6ZtEaJ\nsoca/FXN7cGLbLXyrvCpDdaDJNsGOEnue4WNjMLUdPWM8c0uDeAHO9cSY4Sk\nVMKcZRLbDDwp9JGusl/2NXQxipE+z8G7InZiBMkLcmCl6H323t3UVv9I1Jt0\na4KXRbYIb7ohUpkOoROXtCerZC95CycrKJu7t1hSHbdfX/5lHocndut6RISO\nmdm3qoZ06+mFmMkvQNF9FV1Pkvr8p2ZnG+RD6egYJa+NJwJegCJHSLw+1cq/\nvpUzcsEbVlX189i3pWimEDfqQZhdZbmw3U5BMKZoNk7QC3Nwp7R/vVsWdnLk\ny0yTKsjQiiBdNrDzQXtH+m7n6oGNECKxsnerg6IkdJ7PKLW9jOfpQSVLlbDW\nxOsXjmZHlGRJxW/Z4BWhJGgyWTraY9hnMv1+wSp0uIZ1nB4zhqCqRzF/EAIo\nWGoKVAv1oh3wnmc354KPml4UIMwnBDeaDhQa8iwFfnKJrOk+k5CgtEcLZTLG\nIWlLMGng1e7b1Gx4MimUwwRAZyvtjNhWwV65kjqs+0csnskA0w/95+gCeuE/\nchlNBqaueocAel2P/0KTe/WNGfLKCOiIgABRQnFRqKdKEHCIKZnFkuwub90F\nIO7o\r\n=Sj94\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4dcd43a62d6c8ae917945002da936b874aa76139","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.6","@balena/jellyfish-logger":"^2.1.24","@balena/jellyfish-metrics":"^1.0.67","@balena/jellyfish-environment":"^4.1.1"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.121","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.64_1618223614767_0.6137219805390841","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.65-renovate-non-major-bd843ead64d19a41a57a77f50b52a3e5be516e4b":{"name":"@balena/jellyfish-core","version":"2.12.65-renovate-non-major-bd843ead64d19a41a57a77f50b52a3e5be516e4b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.65-renovate-non-major-bd843ead64d19a41a57a77f50b52a3e5be516e4b","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2925dd9faccda55f676f016c1ede7ffb4b36a2b7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.65-renovate-non-major-bd843ead64d19a41a57a77f50b52a3e5be516e4b.tgz","fileCount":99,"integrity":"sha512-Sg3n0SXBkh6LVerdWG5VZsmDRYsPNKf1iR18lE15kiP5btdmm1fDABIeeRJX6ypWQ0ZWTEpKWUbRz+VJZ8Sixg==","signatures":[{"sig":"MEQCIQDzcr/Jtm7fNPPP6QhCSKTqKRK2ESfKN/Lhvp/J2SHKhAIfH7fw2o61nW02pwPGsfBEHIr+oZRBjuDlZAiqyOIUwA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":684930,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgdDcGCRA9TVsSAnZWagAAdkYP/1sawIxG6HDu7wFK0iHH\nYxZqNRqK937AOOvpzzY21zDlHoCkBk0CoojQlxLqMc6xX6oNhh/QLhqnvRcN\nnrn9hu7fQMsouQ8tLx6a6K7ulFnWgpeTY4DOkRJpRHddDbG0dc+MA88Ph9CJ\n3DGsbZrcCqBj2SlwNXtoSDx1VMAuAHUwAIPL4z/APjYZO3GiQWQx85idOxz9\nUeBvCONkbnorJUL7yLAVME1t0I9LdqWA9t4tjGYIIqmpEXgPYd/5uz+rGoH+\nz7Gy85WP3H6sK4uuKfeadx3ihKwWpKiSCB169ob3pNDymH4PYAzLuEafdDuR\nuPnHEnvLcmtLAsKi71oHL14sbRdDqk2Ka9SvKbBvM95CTxx02+whXWt4aw2z\nP8LA8NvmusLPstySlsVbdAwKR9m33DLHc02ve/dkRv7LfZ/DQ/fLZhj3Vwnu\n2D1gW3v/cdo36sjcTFkN2226m27AuC7x24p15q+0jlExqS1GGVLDLFrUyPFg\nhKwyIwLxC3gIl/CGnC+Trf1RplkoRj7B8n6AXV7YQLpd3Fy+HKtbJ2YbQXcy\nSt0ck/susaWHayxv+OzWvtabWQl5SvbMLWfbzvfbRwpEJTye6dDizougRkWf\nWKjAIaD6992DjJWstLjVGc1K+jXiqcAGqMx8u7QOrQFj/8Hy9M0bqs+EvWD9\nW87e\r\n=5yhQ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"bd843ead64d19a41a57a77f50b52a3e5be516e4b","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.6","@balena/jellyfish-logger":"^2.1.24","@balena/jellyfish-metrics":"^1.0.68","@balena/jellyfish-environment":"^4.1.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.121","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.65-renovate-non-major-bd843ead64d19a41a57a77f50b52a3e5be516e4b_1618228997722_0.6874950465440273","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.65":{"name":"@balena/jellyfish-core","version":"2.12.65","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.65","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9d8ee406a491b6ef21de415931fe5ebfcc263988","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.65.tgz","fileCount":99,"integrity":"sha512-KYkgoL7hJ0gC3fODXpXPB4GhgWlJRXSTWYphK5hQIvP9MCLemFrp6TZU5MkcDzaVajLuPb002hdDRj60/73m1w==","signatures":[{"sig":"MEYCIQCEhtJHONUgz+Ee/4QOSYTg8dS8PdU9IpeElO2gazmrBQIhAIDuHk7NMX6Snf0S1W2DbTWryRtGmImAQp/mgRuC7t7a","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":684870,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgdDh4CRA9TVsSAnZWagAAZnsQAIvEgZflKEMZ/7iiO5iU\nrx/OQTdyhUznjYXcYIPkIYh9e5hXD94NlWUSnnwXhA/bXq1xXOJJLSJDtjp4\nTm13/ES/BxzqsLZmKwyXy4lZEz8oU/SQRwBpawaX8yz5dh/Et+5VkdBO1bmk\n6LXVuQ52SwlOq75YnaomDc6qWkrMZdsNbqof9ctiGoSdeGUwL00VzO9ztQ4N\nVNKy2Zbmem9R3gIktCGxPxeWUEfqeD9Mxkvh8+bkyk7ZVfjP5HQhSOv9zchy\nuJOsOca99DwzSdhq1CTobLEwErCeNbctOvfZndcVwSSbOIEUPqedxKXPbiqv\n092dlB/nxqT0TkgAjfFPRjC/VClJjP5bKYjLLoq+BAn+x2zAKrmJWPctgkle\nzSfqffds41bkcLlJnvDjQh5jeY2TISBtNu45XN2TLSc2egmLNDGnx+GjerEp\nTtLCF2Y8onKWaiLG3fvRs7dA+9mtNsMtzQVAFLxwDnPYSGGhwCzTaBXuDTaJ\nQS+QiRgUYfDlqD4QtKiV+KJPboSxS9E7pS4Pd1kZSJMIMfW8kg2GLHGZmCIS\nsUnmkEo8BG8oFPHtzN2lTFHKoGpkBh5XLDAnS7WGfThUDUX1Z8JuAsndOu3L\n2H4Gr3x5Y7KzfNJphiIhDWnZBsdUSD6gRlOWnPsM8YgxCMkXbR9OLkV7JIiH\ngPmm\r\n=A3IF\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"9bcd718bab771d907f8e6360820db783fa3ffb9d","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.6","@balena/jellyfish-logger":"^2.1.24","@balena/jellyfish-metrics":"^1.0.68","@balena/jellyfish-environment":"^4.1.1"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.121","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.65_1618229367598_0.5332723847236931","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.66-renovate-non-major-dc306c93bedfc105cb09d162c95f2b60fd5138bb":{"name":"@balena/jellyfish-core","version":"2.12.66-renovate-non-major-dc306c93bedfc105cb09d162c95f2b60fd5138bb","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.66-renovate-non-major-dc306c93bedfc105cb09d162c95f2b60fd5138bb","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c59035e530332ec8001af7101b89913e550e8c62","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.66-renovate-non-major-dc306c93bedfc105cb09d162c95f2b60fd5138bb.tgz","fileCount":99,"integrity":"sha512-/xu4q8UaFEIdUSOcyxhslwXRMlceBq4+MGzqRfJ39dHx1HS1JWjIMl43f4WSDdO2UYGnl6uWcUEUtRuKvuXySA==","signatures":[{"sig":"MEUCIQDXWDTYzCPyxVn0dguYnKAT91BL7PqF7oAPC5JXDxsJBQIgIfwrvUoj7Zofiy64JJ3QKKd0aQu/ygipbGHvY9VuXp8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":685357,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgdEIZCRA9TVsSAnZWagAAG20QAIDJ+Xdb592L08dCsJV7\nChvCxE5z+BuGH2IfKkqnrAqH3M/w+CpVuxd0lfiLcu9X4dSB4AGrruOyTcSX\ndNEbRj8NQEs3wccnt3gKoFLbbE2wLCaEhB8uZUCvY5SD1jWMwbZkBLAREbDF\njr6ixYR+FeqH5AuErCdK76F4qcpc66NvfuBq6bAv5397q1P/r4XafgDReaDN\n9+mI5gjzKm6ayxnUJTW9rwjLN9hQzA0+ND0j4NIboJ87wXTV93u4twjgbLkl\nO/npZGNok0FoExxNViSajF4LslVOF3qa75stQ7xZ2UHRetNEn3nTQiV0IN3W\n5QARJ3WwM6HqEK+n4feSdduLoYUA1dsRTWpPurWUWNE0IG8AaVVr1FFOtYRu\npQ95+SS7Ert1+9ta+zGOiNEJCCosZQRRn2zRsNZTMRQ0cp2HlJiCzj4WqzLJ\nhaEGBP+WzYvXJP+VAGUcln3GCnGSWP0O7e1p0K+oPu5kIsBz5metxOvYawPw\n+vxtlxxoGHD7yVLfzVirCBYUc4JdmoeJpJ0y5ozJTcgsW9kd3P8Veohyru4B\nvq9hmKgITQrdLsigIGM2ct1LtbL2ExhcnvVTbQyGmfwoViLDfz5aUQlpCfY2\nQHEldfltU0Zg5jSQsPccNl0QX8NjC7Wkw11s/LvlTzNwdAn2QIKt/b0GA+Ff\nP549\r\n=UlCM\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"dc306c93bedfc105cb09d162c95f2b60fd5138bb","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.6","@balena/jellyfish-logger":"^2.1.24","@balena/jellyfish-metrics":"^1.0.69","@balena/jellyfish-environment":"^4.1.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.121","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.66-renovate-non-major-dc306c93bedfc105cb09d162c95f2b60fd5138bb_1618231833403_0.21432273810373226","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.66":{"name":"@balena/jellyfish-core","version":"2.12.66","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.66","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"37a7df01ea096510f0bfc6eb9767bb505f170e91","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.66.tgz","fileCount":99,"integrity":"sha512-UtDMHydHyWCEqlk/C3539R3/sVsNQaWC/BpTbI9LdUlCcsVcPL2xba1yJ6taxgoSdkew/QpporiS4RqlzY/I8g==","signatures":[{"sig":"MEYCIQCUZ9o4ilAYRK0Wh8jCtsOC1zYxHw0o048QeV6kgn3WnQIhAKgllcPkVPWzdnE5lkAtSPM7+lLu5a18fZo9mrPSHZYg","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":685297,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgdENOCRA9TVsSAnZWagAAuU8P/2/6+1dQ/YM5FACem6e+\nMRqrUXCouWc+/KXfVU2hylCPw3OP1CAsHVJsCsVV85VItsiwfSTHkbMAJtIR\nR1jalQXHluIa36D5+yS9xxEijNj9iGtww/nIykQ+WqkslwN1wYqOZNtHsUkF\nzYgVhekOjxrcUrU05gjz+OKeu63QqVXpfer1BEOSbsf12YpS/7B8ThiAzeBn\ntJ5SDWPSe+SC1+FQUq7FBqw8sAaoYRBuHbXy06fUG9nmjCkf2bDjFlPBV0vX\nSxw0GljCMtBLUFyEr+/O86481P5t2iT1FksirJPiQwJK3+5Uc4gweIwdqjog\n0BuRS/hoh6Wch0dqFjufGWgwe6+rdLXn0PFTJsHla0JOokyVBrPX1P5U1jIh\n55xVY2EFd9+tDrsqjUfYhw6VSUMDCwyoVet3LBRfLU7AKWaL98sNc9VPvn5w\nwRxSelQ6A4padYPyQtg1FUsfRTAQnX/WtyFZPWhm4pKFqf+EL+QOeUk+NM0t\nSd36gHdU91uY8ACGlet/hmiJZjuRFJsEvdFtWTTw9/wmaf3d2qJkhZOhaSM+\nqff9fDsHepyRIGiCqWHl7wHvCenYnOmYR3F+nBq0o9L+P+TCJyQXjjaq6Hsx\nVPTdk6CyUThuR9XD6rkNA/spVpIwdyi6fF4PprPhnMZHwLRO11sxoPdIuWIB\n041I\r\n=NSuJ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a546523caabc644b4bd698a0e16a813f7c584899","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.6","@balena/jellyfish-logger":"^2.1.24","@balena/jellyfish-metrics":"^1.0.69","@balena/jellyfish-environment":"^4.1.1"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.121","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.66_1618232141908_0.1388933637954537","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.67-renovate-non-major-09623cbfb33326ddf05625b262b8c1be8c660244":{"name":"@balena/jellyfish-core","version":"2.12.67-renovate-non-major-09623cbfb33326ddf05625b262b8c1be8c660244","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.67-renovate-non-major-09623cbfb33326ddf05625b262b8c1be8c660244","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"18898c5b2e8790e8266b28771bf3cd79a58967c2","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.67-renovate-non-major-09623cbfb33326ddf05625b262b8c1be8c660244.tgz","fileCount":99,"integrity":"sha512-lDFieQC4PsVckfiog7NQxI8S7i3uvaaYSUxrczvi2yCEdR3C8Pf3SiHLLr1P3Xb7jt9r3ZL449KoQLlvKrpPqw==","signatures":[{"sig":"MEUCIC/gohxsiAJdXfY2gdfKUEhbE99lWvMsRK0ReEJmVdKnAiEAzuJfiNK1ssDLgRpDO7PR3lk0nr8brI0GW+mNPviPiI8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":685784,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgdHwyCRA9TVsSAnZWagAAc4YQAKPRnW3rN96JrWXRlZh3\nb6Il3I6jS5FV0RaLNAnz7c2pOW1hTtb8f1uBXQYt4urhhCm5fOScZOzbLfCF\nDbgHt/NqRiUsTk4JDCQ5KryJ+TCzJ3UMA94/uZhPiGHNLDnx5tOpEPlGM+MO\nvvEpNwE9/Myf+CnVsCMKcS18O7a6/ox4j4V0esEVjwrww7z+nnP6zloyyNeY\nxbtUF9F0OVVwHTa1ExBhLBIAvuw57UNEDkAY/TEmigUI4Rk9mgkgZGg8VOCA\njeTw8CWitwxT2tpCFMZ3GbxjX0oMtsHkzZ+myeQPYPBEaYrMVHZIei6b6xKv\nP+c+g+j/sU+qBJn9t69zEN6ie+VRpGJNzDB7Fye3JxYq4tvjR9tzcZcCjBqB\nIBRge6wik0+NZCIk6FAtlPw9gIAJQOkP3tfrjAIzGOUsm7FnRKmmrZ1VBq6V\nWYdzOESTHIufIrJkLmuX+DtNc6OUFcszyvnKBI1BkwKZpbKvggx3jdBaLUyM\nXc9imlAvejNb2rIrsNpSKu9StbwYwunzGutKKOrX8upt/CTNkpnIsZY4Zhw+\nRUeu0yuK+dqQYqWfgBGlMfnOnuXX2oXRO9ThAoGTGjAoiCJPK5Jw17Hsb0ZI\nXgySOfYDw7H1QtEUzIKqdOVZsp4bx48BiCElv6NcOt6wnCzXRw86MJbLAhEB\nR0t0\r\n=D0I1\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"09623cbfb33326ddf05625b262b8c1be8c660244","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.6","@balena/jellyfish-logger":"^2.1.24","@balena/jellyfish-metrics":"^1.0.70","@balena/jellyfish-environment":"^4.1.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.121","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.67-renovate-non-major-09623cbfb33326ddf05625b262b8c1be8c660244_1618246706238_0.04613083184577027","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.67":{"name":"@balena/jellyfish-core","version":"2.12.67","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.67","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b4badb09b85cff5e9fc1c259d07bdad43c038ed0","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.67.tgz","fileCount":99,"integrity":"sha512-VfbDAfzFMASR6PNfHI+vm3OplB1RHdPAkBWqRr12WO6aezTOQyYfiyVC0KmAm5HkZlAysYkb/95v80bo76PnvA==","signatures":[{"sig":"MEQCIA3gpUJawklyZ/9mAo2gSaPKv99EK6ggNUhWK8Nb2OoHAiAPR3sOyd3vFw0Xaq1gJjxLd9LBn8z2ITzXwQlw6e9ABA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":685724,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgdH35CRA9TVsSAnZWagAADpIP/3eGGJtuY0p5Bp3UcIqS\n+tIo0fwDAjnKdp77Myz+yFarhIMmcR5t2NDnC+ncdFOSgCzkpBQTVCOzBUx+\nP3jV5wFR4ZpwJFnxYh0wY1gYJSgOvrupvxM5yIzKeXnoE9ptkMlOl/0s5TZm\nD0y5G5I3JyClG2DY6hKvNk0wT/ua3zDnNKoy5n96Wb+J3YeU/4TcfJSR4AWN\n1QZafC4nS6rmuPK4d9RPYUnym/kFd4fhSPn0bjwc4dGyKaVlp7dYjhdV/55R\nfTL/sHmzn7orztd5Q5Pdea35xBgoTRmUxhOkLAI2KZmvk7NbH6UUTQ4A7B22\nm2QKXZq4t0cFDjtIsm4Fe5dQgJLVf0SIIY18rZe4UNobTlUSxBtr6snMl3RB\n+z09LPxLhr9xP3dCtFoiaRS5qBeoZS9a/o+5rel0Q/3I7CNwtpRt/T4DX10a\niZ/qykcPYHgr5Sr0ciPLPbgQFeknWf2clX8avmvreBIa+aXh0KvuwDC+UsGH\nQbwy7E5ReC5tDNT3iDJ0l0BsWljfWCMdW5xl6IPKQ3FG/yTz26mUEazmkZzF\nnUIYYDesoFImZVGRosJVS7UJQeAXFhIOxc54LoTVQrEpKIm0Svi3j/7vLuOv\n8GeVq+UkL9odzGdYzqx5NEK1NFxJQw07WuiqRB3Aa9tcjVBQwHSf666JTKKn\nVGpr\r\n=8uXd\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"08e5159c3e908e55700a09bcc488729ecfd57919","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.6","@balena/jellyfish-logger":"^2.1.24","@balena/jellyfish-metrics":"^1.0.70","@balena/jellyfish-environment":"^4.1.1"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.121","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.67_1618247160787_0.3287462176360574","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.68-renovate-non-major-f840204521cfb22239151314d48429444876d867":{"name":"@balena/jellyfish-core","version":"2.12.68-renovate-non-major-f840204521cfb22239151314d48429444876d867","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.68-renovate-non-major-f840204521cfb22239151314d48429444876d867","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"695307aa553218713487f632677f218e9abfb2d9","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.68-renovate-non-major-f840204521cfb22239151314d48429444876d867.tgz","fileCount":99,"integrity":"sha512-aN/KcJZhlpqJ5uPFd/fhS3u3lC1fCJnZ6dtR5vU09dbwbMMlrYMnicpg1xFeOzsG3UX78zTfXm71pNGfvnINgQ==","signatures":[{"sig":"MEUCIHNPxHWn7krcjMiEeAPellWDdzg6WSt3qPARid8sfKHwAiEA89yHADrJ6IYI16Vy0QHtvRGKQrrSJosBvYOWOcEjRgU=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":686211,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgdJwcCRA9TVsSAnZWagAAhR4P/0mJYVOngFvoj6d6l5Cq\nUAWh6kgnBJpW8y2WxBr4sMNo5cP4dlwY6A8TNVg/aDUN7bOJvD0pCLvm//+b\n1pGMpdjlnaTRZ8c5d+rmXCd+xRu5Z2p/NoJVhh9FYXy8HaRDt5c+zXVcVil7\n14r+zUs03Yd7VxUa+0Da4VeWFarhySPOvtZfdUxNplO5tx62KcMheynQ8aJI\nwaXDmGPIy7ULLwRF3utvxGy0zSAKHhcfvM5pQM9bKUcaebbN4p6oP8SdWppB\nMYvBTmML2usVR+IOm+uYcEw2RNC7AmDGIoQz+4KGC0J8z4iT1zo9Xi5nUIja\n/U/gPm5z/UfIgZ/J3+258LlQ7H2bYwPXWCgxMa6OaHEngmOLOX/3gxBMw6a9\nrotNE2QQH5lNruF4/KhK8BTZooCjP0xvMkqs/bwOJZhKUr/F7vTdOyeZm6aZ\nBbkhHW8WZyKib2wt7JipgQqE7awbM4ZudA6yUJ0lJ0bT6EQTUHaR8fIDMl6K\nptlI5I/oORXXTFliwMRBsQVkvARj7Yehrr6SOiOYRa4hvog9UJH7Un5iG9Os\nM58paKaBerq90fv4U9Cc4nkKC/yWZjsh7uJ/oWLfBYVMtahCab59eHy2LBD6\nti5WJn405zfbuw3L9+OLBqDZmhIe3mZ+tHKU/2INCEAsthxNbdv06v5R40ky\nxrI3\r\n=4meq\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f840204521cfb22239151314d48429444876d867","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.6","@balena/jellyfish-logger":"^2.1.24","@balena/jellyfish-metrics":"^1.0.71","@balena/jellyfish-environment":"^4.1.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.121","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.68-renovate-non-major-f840204521cfb22239151314d48429444876d867_1618254875841_0.030300082997406275","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.68-renovate-non-major-693866599a1480481b1fc93ddb9a980ea188a122":{"name":"@balena/jellyfish-core","version":"2.12.68-renovate-non-major-693866599a1480481b1fc93ddb9a980ea188a122","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.68-renovate-non-major-693866599a1480481b1fc93ddb9a980ea188a122","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"32f4ef2ba549ee47e100200f4670c953c48daed4","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.68-renovate-non-major-693866599a1480481b1fc93ddb9a980ea188a122.tgz","fileCount":99,"integrity":"sha512-Q8Y0pQUpW3+uwk2p2i2oW+t214i09vQmTPid+5s3unW7CGB47/u9cAv8eewEXI2GSnCNwTMJycP+emPocRX4Iw==","signatures":[{"sig":"MEUCICNJ949iiFKMA/9319Mom9CKW5zkTgz7Ua4rdzQMxd1zAiEA3aS87OCGHwRdcZnioIFnwbWSGHgmYfA8GfPgD4+1PJE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":686211,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgdL4oCRA9TVsSAnZWagAAfk8P/A6Y2PtttcZ2/4qXy3WB\n7BlAjn/uUcidD7TrpXyaQDu3vubUE+J96VkZzdT+cYZMRd1gNOFmcbWSLMv5\nzHcJRj8ZpG1j3wdKl5SN3L7sDLSxCqscWy6C5sEDo11EXFidybaeF9BC9At2\nMCg/o7gapNcbwjAiIEYQoEO3+rf/YM1fAteYaubciHfFbaNDLUXyq7j9nykK\naKco5hBggF7F2FNalQRlwQ2JE2/J05Bxkb3LhBZZ0TOyCSHjcydq/1LnhdFE\nHv/OBbYajOp/G1d9QVz0HCJg521TbHGKhFv421VKlqHKjw2O/l4bjUxB4yrR\nPszSR3jXE/FUVx1AiJNmBvHxjvJ0afBxcIEQWKva7uzmoQbvVPvwZHVlxcKI\neeTNlmlUIFIftY8kOhpUmWdp2yBoS920/ms6XbSIsZJfMpOVcJa5zCKYZSXh\nRqWwGVa3T+6K91Yl2faaehJZOPvoDwqUQDEK7CtW3nJN/QmI+P7Y74PEd+xL\nnIjYxxgF2sTFlqKggRGpuAyKsXz/K9L99w2BK7w/ksGr7euqR9sK9NpCD9br\nONsEid4PNoWzJmLyq/SgbiNWrH2jAbD4SdxWE1BK9zQvR06cyp6+jpZBAjZe\ngYbrhMFxbIP5Yihq7yADftJYhYSr+JbkhI5Wkf2K4iXMgu83Rkfou90wFqbC\nac83\r\n=DZ15\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"693866599a1480481b1fc93ddb9a980ea188a122","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.6","@balena/jellyfish-logger":"^2.1.24","@balena/jellyfish-metrics":"^1.0.72","@balena/jellyfish-environment":"^4.1.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.121","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.68-renovate-non-major-693866599a1480481b1fc93ddb9a980ea188a122_1618263591522_0.5438343019044245","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.68-renovate-non-major-3f1eea833d661969de687137bae0e02b83085a61":{"name":"@balena/jellyfish-core","version":"2.12.68-renovate-non-major-3f1eea833d661969de687137bae0e02b83085a61","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.68-renovate-non-major-3f1eea833d661969de687137bae0e02b83085a61","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e89332191bd68312099de403d0a5bc3c9bd9726a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.68-renovate-non-major-3f1eea833d661969de687137bae0e02b83085a61.tgz","fileCount":99,"integrity":"sha512-TWOS1oV1UOO1Tm0I5n3EklKwuMUNhu84WEF2IK80BpG/KbZxuqdF4X1QY0fLl+9a4opuwD9nxmbYlJUQcZPHEA==","signatures":[{"sig":"MEUCIQD//HUdjz2peVukTNWrl+KF94YOAFJiTG/f/qxlIePArAIgA9y56HdvPa10h7pidaoZacG3v90CTw5q6waRYdDThSo=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":686211,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgdNbRCRA9TVsSAnZWagAAu8QP/jrpFLzhx+TyHimqwR8U\nlTm7TPcHMHRUPSt9HluUARdMYjfsfHtqoPn/sx0Atztz3yGFM/tpUlQI38kQ\nRa41jGg76WUb3oAtxVb1ysE+Bcw+R7LHa4Dc7kCBYLHJRR/j4jQZFma9UaMh\nEPjfnY6Gh4mapIE5t9VUqv/fq9fP88/4Xj1bLmT4QP1W+jR1K8LMk+4xKisk\njc+pRbzJcKdt2I1m2MwvupMtIobX2erMBme9LnEPij1uGaVpKsoIntxOsXFN\nie5fMtAACiNRICmawNiTTzor3zH0sDlHNkx+cwTtntWwmygr5L6iRAONQHL6\n8U4oPVzV0RkD/9006fMhjHJzApGAd1JAllbVvmvNnkpeRSND8y8v2XJdgFG/\ns6GRCobWl7OqNDo8bp1tMgWSQiIaTMX6Gr64IF1KSJzINyIWi7XxIccT9v3R\n+VGKB1TO+xFc0NowX65ITWGz3GevSQHQmxP2Y76ugQSJfsGCuyxlbP5iPIIa\nxpslPODttVsxA4uByNHa38gSy9ARq3dJzVKZ4mEf4WJe6/yoBohCqDNJtEoW\n4IwrVYkvJX61m/e5sg7U6gIv8Q+ZHfsdk0BEJu9JjgEMojFpS6Km3PDnHeWm\nqYZ8rs8ZM3CF0x0g+SSMiDOWVy7VCszjDnMDE0XwiB1iWuNBo5DJK7wToM/S\ng5w4\r\n=oaHH\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"3f1eea833d661969de687137bae0e02b83085a61","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.6","@balena/jellyfish-logger":"^2.1.24","@balena/jellyfish-metrics":"^1.0.73","@balena/jellyfish-environment":"^4.1.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.121","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.68-renovate-non-major-3f1eea833d661969de687137bae0e02b83085a61_1618269904453_0.5128295738803015","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.68":{"name":"@balena/jellyfish-core","version":"2.12.68","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.68","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"055f7e550f3d2e311b7c0da31f23b96f6be101ee","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.68.tgz","fileCount":99,"integrity":"sha512-/6CJmJK1kuke7Mh+ZwHlklQh3r3ZCYd+iepGC8eZkXfGE0QjN2ub8hI0O8XL8DN/jiO/ipUbtyZuxvrjvZqt6g==","signatures":[{"sig":"MEUCIQD8+PyDGXThuW2ucs2zwQ8gKA3Osf6qbzeDhTojgE/b2gIgJoG/dV8nfW6UvEEwte8vzKMlNHyKG0NFIO64RQ8G+ok=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":686151,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgdNgTCRA9TVsSAnZWagAAM74P/2P7EatabFPldsieylYh\nkT2auLWKW9a+NTEPqHuPWuhLelMjC4eKyexMYsmix9U2HnMiFteai9wXsH4B\na1jvWaa8Aw5Bwvikvv4N8xBLvXPGdueXjO2l3fEmAIuOKXC8HTL7u0hTQxAV\n8/OTRhQcI4fMLvUbqWY2Fdmm1ekNAyssnM3abs9yRWC/TLsRMTh/UvHZ4o4+\nYFzs8Ry+OiZwYwTPk3hiv0dQLtrPAkINSsc9YqKQxk6aR/toD2rFwp9MhipL\nY5FiLsbcPadnfLZA7LgzauPdtsDes2uG3a5/NjQWxsrKrSnyCRg7X/PDanSp\nAGlszSRCwg8K5+D16NeafPTffbwA9PQ2EkdcV+x1HZGdUBgm/AYB4bRkuDLd\nkCUU6o3QD3U5jLL+BQfyjpD20QOZA4kpJPtPJw/1HfVFCaAuRIxsChgCNGaW\nJezNsWDfiNcf6s06jOW/YrA03qsmtNhl3+kujaDKpkyYLwaQCfu8CLwHIRYh\ndvGiRhC54O6hWDjBifiAELzZRVTy6W20VsrGSrOAnHEDNynPx/kVgFOY8Z9X\nWGXQDDEMI5GjLrOHO31WocbtzYkFcft74U0lSv+wC2WleEOw2DcQ+lRV1ZJW\nWC9BgAOXPpjWN0GO3TbmovI5qrdvVpEpGhV5G8+Rtbc7LmqW1gvF+9dingJs\nbfSU\r\n=cZta\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c8d7e49271abaf5d7c52b2145f69729ce5527938","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.6","@balena/jellyfish-logger":"^2.1.24","@balena/jellyfish-metrics":"^1.0.73","@balena/jellyfish-environment":"^4.1.1"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.121","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.68_1618270226457_0.5923519042817067","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.69-renovate-non-major-8411b3001610d1f86396f59e63772c71f5a4315d":{"name":"@balena/jellyfish-core","version":"2.12.69-renovate-non-major-8411b3001610d1f86396f59e63772c71f5a4315d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.69-renovate-non-major-8411b3001610d1f86396f59e63772c71f5a4315d","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"47fc5dd4fad48bb7f3ce79e12daa75c457d5d477","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.69-renovate-non-major-8411b3001610d1f86396f59e63772c71f5a4315d.tgz","fileCount":99,"integrity":"sha512-l12uFrnEIih6+zUPJhKCYnACaC2IdI20jRj3LdZj1JqAJxKdgVMDcMGf11wPne83/kePLiNzdpOHaCBXaaFASg==","signatures":[{"sig":"MEQCIACQkb4moo+kLhOOBrog5KgTsvsycA0N4PAU0xZwFTcnAiBm3sILMQusAXkmX+ovWgYeVMWnm/w7KDlMtuR8YGrq/Q==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":686638,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgdV4cCRA9TVsSAnZWagAATN0P/0K2xc34BLDUuYBUXB/l\nRq3CuvxDVJItie99RclAshcBeLD48GVOXA7AKVpgRy+az07o0mlVqaSXGM25\nu35x+1Gioc7OHED3wzDCtz8q/7zoOLRlvzqy87zMoeP+uVSXaB/LlTXF+Bae\nsDJMGFWjCX9JMkYFCTTOkyEyR3dOGc0IT7HIzepqaBF0I+dQgMvUlV7PCAgS\nU4Z5ouX1pQfkqUZ2O1AfHa/bRC3+bgwKrYIyQEQBIk/uC4fquuYATKgZ/7GB\ntwwIoJP4uyeXi5onmi19bsZhWO9vcx/jTAoIV12uICI1blpA4qRjyT5GE8Yj\n3Qx/dEZvy5Zf7sJKV7YGLnBeAWMKU811MAGW6FMNKgzEFOA0HHlKNkIxPsqA\n8GQgRAYyhqeSuvkc4yyG7dIDMStmHGzTOBc7Cbr45qEKQbQQJsc8DCri9Oqh\n2512+Ve777CIdAZDdLqlyZbSdIgg5qcYm56/0bVIs+rSVWZHbaQ3NWiwKx3v\nLV1ePZb3dek03SV2iIDtH6n8Q9/KkGkxn6fzJC9VBnOKt6aivx5lkNa3J0y3\n0VPF0JZWv2lT1t1K+ilbiDjvPNnrw4D3GyqZGbUN6j6lEVTFO9Rqqouph+x0\njrJDYVIQi6h26QiCg6QbNO9JZ8LBtw1Fj9UqTfO9pqTmbZ7+qLvmGTIeTeN0\n5y9q\r\n=GOVd\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8411b3001610d1f86396f59e63772c71f5a4315d","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.6","@balena/jellyfish-logger":"^2.1.24","@balena/jellyfish-metrics":"^1.0.74","@balena/jellyfish-environment":"^4.1.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.121","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.69-renovate-non-major-8411b3001610d1f86396f59e63772c71f5a4315d_1618304539606_0.0631142781289078","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.69":{"name":"@balena/jellyfish-core","version":"2.12.69","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.69","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9bd5d9c01b6602418b656229c19253cbdca4ad8b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.69.tgz","fileCount":99,"integrity":"sha512-vLkuoenvnHcZB2N+5OJTbtABnOD8HzdBCS0UzgFVOudKLZ5rsJDlhBnXuMhQN/F3JWLXHdCtx3xlT6zjNJj8lg==","signatures":[{"sig":"MEYCIQCh+ejd7bcJaJWbq/VCAWamUUVzL608NAqDsLOxLISVxAIhAIc15mnil2xCpSKaUFh7ewj9P0IS9eMzJueTLw2cjQ3q","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":686578,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgdV/qCRA9TVsSAnZWagAAUsMQAJEpj80Cht7rSmLZjVcN\neSsJE727vqD6gAFuRTSFSkLf5kLeLrJaZcDnOAkhGIo8o67RCBs+SD+KTdHY\nkjX9sshwlLWf920CYziW8JhR/uAhuf/REO+coXnRV64TJLqPTW+m1YMs99rU\nvL7J3YJZmqnD1FHvpw33Jkv3ufSrEiM1GYbFBN9XXW+zGuQJ9t57oWSzA4Iv\nwE8ybLRcW0CoDuOBGux5gtdxlG/Eekkt/X30VX6F1YLtNClm1mCKqIKNni3o\nGot2jd7jaMSQXFgjvvXzlHBwZi1sknr4w6QF66eRrIMe1zzsNav8bwpqH2sk\nQslpqpUyE4DexFX9GTnm68Us8DXkz2SfZw3crVO8iXl2+wFttCo38wJ8TCMB\nA5RRspl4ytoyMEUey+lKqaPIK/ie1fXy3qAAj6b6d8wgUnOacbHqTSIvRByl\n0X1jrrml3z7T+48I6cvKTDdXjr1Osk/nK5n0Y2iLQcGlpwjEeuIQkhucOS17\nHOHYyYHUywairmNoDT55Qh3apX5KBQQHnbCnil61MwzeTV2SDE7JeCKjwlFX\nY/V6+TzzbWrMo/PNS4+kKufWbhxuehi2eSnPq9ZZxbrHHT9Z1iRjJzQklfjB\nO7vgc98aAprKy8hZqqe/dG1i/zRVnzfe1gwEhNqy2xbYD6CCHrQXhqeu8onb\nemh5\r\n=zDn8\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4b6e4a17670a021f2e9783f229ad64019db21352","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.6","@balena/jellyfish-logger":"^2.1.24","@balena/jellyfish-metrics":"^1.0.74","@balena/jellyfish-environment":"^4.1.1"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.121","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.69_1618305001777_0.3518068500133784","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.70-renovate-non-major-e5b134a275deac8cbd1a530bfa4ec1dfe718ba19":{"name":"@balena/jellyfish-core","version":"2.12.70-renovate-non-major-e5b134a275deac8cbd1a530bfa4ec1dfe718ba19","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.70-renovate-non-major-e5b134a275deac8cbd1a530bfa4ec1dfe718ba19","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c688312ecbe834d7ebe81c1123f2183b193cd306","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.70-renovate-non-major-e5b134a275deac8cbd1a530bfa4ec1dfe718ba19.tgz","fileCount":99,"integrity":"sha512-qEjmMH8Wv829ZtfLVQAbt5b79gh2UR7y5+tfMiRcT84xQO6aJNP2BSJlyFZHlZvZNYA07wxSmTrDK3IYvcnKKA==","signatures":[{"sig":"MEUCIQCeU1iHZunMBeLnT3g61zlUW2vnPG+lUbYOE1LtKavvyAIgHQx+IrZBf5Txxsg3ACn64rH9mEehlpvJPIZ2OXrg6n8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":687065,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgdXQECRA9TVsSAnZWagAAiykP/Ap0x9Ss+FRkNCG4wbSR\n09rki8DcrLAVJDz2j2rWhkIo0Y7BrNiwzZQlsz/VoI4sRN22GH1sst4JrAk0\n2b5cxyB2yWnVtfXy8LtyftS6b3kvQDUb9FZrlhjfB/9cLRv7cHHLzyr63BhM\nYSXypNdyQLCHNIeW97HRxDVpkyVi86HPb0UeCZQePdvpl9zbIWQpynitA17u\nJ4pTQ3Wt4qbAHSam9Q9I5Xdy61xmWAYTG7jeCPRo5X/kJmnFGwJ8DH9XGVEl\ntt8o5+Z//UVryzLFIY4KF+z6aphZCJKRzj+7gtAK90CLedVaVQYxqsqYvy9t\nX2QMlDKVGj29fZv8dAsmwXM2uUzyJ4s5uD0rI23vn/0uXV0uwH7LddtJrV/L\nDyTKNnQGZZ14d9FaCFGrwyemSZSCw0Q+vPZY1pch0hboUjyT249ccBQTy2AM\neAq0ZOSbUygPEnCqRARDZIg83/hdf1YxQ2Bjln8x3ysM2aafcRLvz7OcGbNv\nqoHMDKdEL5vq0QqbIxPBgOvEEbh3HmmfxmTHWpJDe4EQC33c7E4+R9sbBaRT\n+7v1BrqByHM19BbuPTP9d+YxIGIJl/2+5cWYVXqhPpqG1s3LkWB8ivvXJSBP\nklQpvyylTVg95EgWlBTZv816DXJhK/5mj2cp66KaWTNV5Oasg3jBMEXWvq82\nObeO\r\n=Bz5F\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e5b134a275deac8cbd1a530bfa4ec1dfe718ba19","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.6","@balena/jellyfish-logger":"^2.1.24","@balena/jellyfish-metrics":"^1.0.75","@balena/jellyfish-environment":"^4.1.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.121","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.70-renovate-non-major-e5b134a275deac8cbd1a530bfa4ec1dfe718ba19_1618310147533_0.7864942482411776","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.70":{"name":"@balena/jellyfish-core","version":"2.12.70","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.70","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"dc1d860692c45768afc0efb8fb2c580419f42e14","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.70.tgz","fileCount":99,"integrity":"sha512-22XT5qkCtInK/Q87BS3PEq9PMcx09LGA2x2U50pDKi5b2zJ4fp6NaojK8s19EV/eUlUTT2DLP1MZIuWorwyRiw==","signatures":[{"sig":"MEYCIQCeUBVezwcjcLeiO+7N6WirKzSMcNDHn60RY3g5/A3rDgIhAIk9WWyieSaKTjKtB1f5l60KnBmVUgS6CcFAzE7nmPzo","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":687005,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgdXXACRA9TVsSAnZWagAARgEP/jBoDm2ut/vVUBeP2J6G\nigKoE63kzlq4QC332v0awINy01jq7HBnqcYVLiTK6N1A8KS4xkPZpRuMA1sg\nLtYNWV6DPQ2mRzhlR05oQC144PFMSqkl6bROVb+6vZvbb4XPQQ+eLqFXdooX\nbN4PqYK2l+unqwxNb5740rR+l1MoU+If63sh25ojl5VJFbsiZNdoVMziZ0Se\nGhWTPF+JcsZTa/sfSjwunNT/D5nu0zTm/PJ5dLVjG1fJlwmIvNLKvQA900e8\n7tPxhRrNFC/TOkCgM1lMVYcPgo/ifBi65s6/al/PnnVUPRvnb2qT7i9G5Puq\ntflS0U0DtcW2GP9ywGEWkKRoGCrJKYUjMNZEBFzZR/wf1Yvo6AOe4UyfASl6\nJR95b43dII6clw05m5Dk4uhqbeHt2VXDrDEwry/7eRKHaoFpKUMUQYaGWLnI\nc6xL06QIAv5R/+L4Ha9QHeuSUH72ZItS5PLacECoBkonR15708LmlixmSAnr\nT6TnqU2osPHX8aAsUVPpYve/CXq/Y8RrL6DI3o/zX5HZmL13A/1s42l5bvNp\nQf4dMvEW9VdTmt8QjrIJsj+3wfhKKxoEUsbc8Nk/CGuzEvcVBULJeqy8ERMA\nkrybfcSBwjd4CiH0mdweh+WpbDbTKSeNYWycvQ13stfzABqtCykLuXnc9BW/\nDvsq\r\n=1tVK\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d08c07b7b375cfb555f81165f9f4ce1cc0c486dd","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.0","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.6","@balena/jellyfish-logger":"^2.1.24","@balena/jellyfish-metrics":"^1.0.75","@balena/jellyfish-environment":"^4.1.1"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.121","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.70_1618310592052_0.9781714847579079","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.71-renovate-non-major-27e2d99ee3fe682c2b053ef6e4a32e8b9616aad3":{"name":"@balena/jellyfish-core","version":"2.12.71-renovate-non-major-27e2d99ee3fe682c2b053ef6e4a32e8b9616aad3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.71-renovate-non-major-27e2d99ee3fe682c2b053ef6e4a32e8b9616aad3","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"057ae7bf5c51485c13b6d685161bb50970a56c12","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.71-renovate-non-major-27e2d99ee3fe682c2b053ef6e4a32e8b9616aad3.tgz","fileCount":99,"integrity":"sha512-qy2noIgC7Kw1AnuXGmZm1UGgXSBavaSh5C6qFLQvDYgI2ZsOQmuOBgDYIz3Ua9cwIAtsTGGo+GxZeOaQnrYPOg==","signatures":[{"sig":"MEUCIQD7OpfiebPJXKrlznS8MSG6EV9QX2Gj6uDzkgDU/zqaWAIgakuZgjoH9hOmpumOHuR2SZ4EibDYi4jb8qrzF8zVuqg=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":687450,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgddxjCRA9TVsSAnZWagAAB6IP/1daY4kNO0p2pjzKnx1r\n5EhhwEEnJSUzVYxd7ch3+kvf4XQlYUC/XUB14mW6/RUzBBYt7blCj/KlPyZo\npqVQQ7o35I3Ks/5acBqPr8QYTFkKKkK3jOMuZxSN+eGZBgSX4zUzFFbDyBiZ\nPcyHCCL6KWvZ9w48Cje+Lak/Wsv7vkRSxOZ/RSQVUvo/Ds7sApuDAoS1rY+9\nMzUUviJfqWpEnoBuwBJ47KsPPHhaPXjmURmS/TobM1EVXSxrgyqJWNiYWn+j\nsdrD0VcwxFNNBeQchFG3FvQ405pUadhCZTGhN1MLDWhlssDMnjtIHzzESOao\nP5YlHOZDCAcSht9R9gUCdQ4q6sfjRJwE3Z3XrNCVrKlFCp61rP155QmbmG86\nynE1ZJ0iku7/Nr78Ta70AHEemDuYwkz0msnai90mg/Ax3NQbrdsb/k4rDEjJ\nBwI+cuIZXDjaJLhGSHInFKN/ahQv9s31B4ugBlpRmO4ltsocbcgE2i8Xe3Vw\nFyCCuBEwE5c4bFoWi4IOwB1L5rgw4dHGRCo0WiDKo/bHP+/gX61+jVNPjfvY\nT6/J9maDPnIQPGxEe2z7c9c9UIX/77KZBPqXZwscDdZ95IwTVH7A7BkTmFzV\nIua+eV7PRUzdw9Uy+vtubKBkl7hYHC5wm3K6+Rmitam9LcUwoJvYSEIJll6F\n51tT\r\n=aSnf\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"27e2d99ee3fe682c2b053ef6e4a32e8b9616aad3","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.1","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.6","@balena/jellyfish-logger":"^2.1.24","@balena/jellyfish-metrics":"^1.0.75","@balena/jellyfish-environment":"^4.1.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.121","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.71-renovate-non-major-27e2d99ee3fe682c2b053ef6e4a32e8b9616aad3_1618336867267_0.47713326375786425","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.71":{"name":"@balena/jellyfish-core","version":"2.12.71","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.71","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"881ae003033ef51628918a1329b37cd94797cbfe","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.71.tgz","fileCount":99,"integrity":"sha512-svUkGM4bKk5qdhSG1AHwwiPCY6G3hB+kwMXFtlvZJOco4VoDcWnvmKv5SWVSHFpDb5u4JGhsyePy/tgyfwkUsQ==","signatures":[{"sig":"MEUCIQCgBdRXhrVux3u1xLceoSvscIKcGAB85uu0owmS/ZQV0QIgZC7IVEcjFWPw8X2/MoZEjiTV8gqlBH+uD+RmOm1J8RE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":687390,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgdd6JCRA9TVsSAnZWagAAHSgQAIcxA+rb8L5phQFfgxT2\nEExDJjnpz4/WJ1yggQf7Mcj3tYziDNteVV0Mx4DozNvTksdPc9wvwMu7TT6C\n2NNeKiBGrQuXQ8UQu+BZPoUT/rCUfnbpcm2UiUlyOK0/LX5dCBtWHrlZb6UT\ntSZmY4MQiZcaCrZhEeXDdJ2Qm1bLI+52yzNnBd40Gy3o7Fh6kBM5u6yPBRzB\nhFjmI2PYD1G1PAOWyWnG7+Jsl/O0/QIfk7G4QBGo5H1LAOIN1+Pxmrr9c6c2\nBupqrLSJSw65tJos/rhEb66nIu1JIY0SLjwQzagq+JU3yoyELT9rW+ldspqm\nLJH6oIzHD8OdDAIkezyJs2ag9j2F0tzVIV9kNiy6rFsBWhk8s3kfWcuASTXK\nIaQcO6gtO2l6/urhU5y6daq7lpBQGYDUI+umY6BrF0iT0E93JTf6D/ElBn2F\nGXH+r7IqEFDze0EtqGWobW7l42+YVkbTqnrzrkePyuddq85TQuZWNq3D0SFB\nq64dl/iHJ72o7E6+NFxcMbBqKRzPWITuXlwagBaPngM5ZfKkJLgdbzfy7mvm\n0ariGZ+iyCiGHY803KwRUfyvaxQHXzbSJGZhAEMf2jc36fcsApJ3HmOLVV/x\n1icw4QI3fpolwrg5AAIBXWNvGdkMEj9ca1Y+sG+TgrN82xm7HOlgKw65PBYM\nOtWV\r\n=0SEj\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a8ad9abf583b390ef0def22e524b45a824c76616","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.1","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.6","@balena/jellyfish-logger":"^2.1.24","@balena/jellyfish-metrics":"^1.0.75","@balena/jellyfish-environment":"^4.1.1"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.121","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.71_1618337416901_0.4150036682031826","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.72-renovate-non-major-f02d16c67982409d80e431c3ec817b44a1454bf0":{"name":"@balena/jellyfish-core","version":"2.12.72-renovate-non-major-f02d16c67982409d80e431c3ec817b44a1454bf0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.72-renovate-non-major-f02d16c67982409d80e431c3ec817b44a1454bf0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"21c37145f8924f98e81836ccec76315a16463242","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.72-renovate-non-major-f02d16c67982409d80e431c3ec817b44a1454bf0.tgz","fileCount":99,"integrity":"sha512-I/aDyLVTqQMbjh8RA9z7UsDX7ro1gYF71U7WKi2pBE6i9Yc51Bzj3o4gsY4ue0Pme9IgwDYT+aY+8kfwajbOWQ==","signatures":[{"sig":"MEQCIGBTKMzgAzIvaiZfpjaAAEyYxJk7neUOvVFg03JwOWm3AiBU3PVryDWRc81COfbv10CcpwZ8/lmuy0bmB9xeU2s/kw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":687801,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgdmmACRA9TVsSAnZWagAAqBoP/3Yd4E4yMmQCL0gl3yYN\n+RmyT7Wb3KXQ0JTN/ntoWLVtd8dfwJpRpZIExGGebZqxSzmHgHRqb5fhHNy2\ni6rGioxBnAywNoOPdiJdUe4tbCRa0vDo1YWrMcYbdZg0bY4wnj1TO084pOnK\nXSB7rUz2YXUxgt2oNWXp2ftmsMuPsmBl+L6TItmEKFLPVMJhqi7HqxFxi9qD\nRn4e9WIe2U0wkk6nrI1KG67Ya2AY1B1jxpDEws7+VdQHJIAW3/lBeMtOFRnS\nPu7IxuRUPxAllIzRe6XlJr6Duw6Z7YIsoaSgRtpu4aQR5C8G1eNeORFn1TSz\nUED8/Kl1yXHK1Zv4FaUfd9T7Yl1SXkpuAXqEGI4VO11j3gBn1qv1c1S5/XAc\nGxBirPIRQISHha81myVwWbwReEnv2GlRVEf7WLMhzs8fWQ0vLaeNp2Tanvzx\nwerniIDQGw0ZZr+gZKSrPr3DsHrGBf8wXvT89nTi0c8Bm1BibxRyIyvDPppF\nsTdMVIpdBrMpxaLNl5wGu7r/Dt+1Al7MsWHDrqDXOfTjYdGP/HMFhhDZzylr\nTwRe+JPgbSCXvA39jjY5Oh9Z9n7HCOIbFde+KdkSVbXB+2rHoNMjpvnXbfBk\nlC+Ki6LXbXuKB5AQx93V5E00nOXiMzn3iX7MTp6q0/swlCbjj5hVjFihjeLf\n+YHl\r\n=mmOC\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f02d16c67982409d80e431c3ec817b44a1454bf0","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.1","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.6","@balena/jellyfish-logger":"^2.1.25","@balena/jellyfish-metrics":"^1.0.77","@balena/jellyfish-environment":"^4.1.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.121","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.72-renovate-non-major-f02d16c67982409d80e431c3ec817b44a1454bf0_1618372991966_0.25204491016886643","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.72":{"name":"@balena/jellyfish-core","version":"2.12.72","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.72","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"445bb1759103ea2bdbe0d058ba85811f28c86200","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.72.tgz","fileCount":99,"integrity":"sha512-VtTZGGcYK4skDlZl6gOPt98y0oiXEifq5O6ZD3RmFzjwyuB+jUaQzp3usrCO4OgNMcEwR4JvLd+px8mUV/qqoA==","signatures":[{"sig":"MEUCIQCLMur6plxuH2S2mRfykIafqe4563zLyjErkuzPnPjuJQIgem50jo+iDcQAG8HxYf3j1mGZ692w8artTNrtUtINW1c=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":687741,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgdmrcCRA9TVsSAnZWagAAJAMP/Agkka8e3+jhMlQKMq3d\n6pqo1dCXmx0JlJ2JVb4IWhuvGfl4ms78IAcKVaE8YJ0CziB54vQwWL0r5fG2\nc69HTDS/tBWnk7WZzGqECZijwr9LBPygbnhTioRhG51Zsp2KRHY2NxBropX7\nYISbs//pb5Ag2yldCjXi7Q5Co6aTNg1DxJGUBBMlnBu+8wyuTGrtd21LFAOP\nhfF5xlDQsZ48DyZkLqQj7ekkPq6qSGSWW83Ooe+WKRrcW+D3R/gpvR3+1b/o\nl+1yqmgNYMbeulluBCl7AELeHAYnsZR8fpaESrSgYwSezIAlJAPlI6KdB4wd\n5p7DXR2mrQ810LxndJFw7xdezrbrtmCcTxGhyzn/gYa0cs+kiaIrk8RFftrh\nBk73MKF5jCBCfQKYGLGBlec+4aeS3ujHrqFDsN/n+L+ttbkxd0bNvhzG08aJ\n0IsgQt7izqvqTA9PyKUB8xsdq85e4trr8xQriGsJDIfQyJYosqCgGdopooi2\nQpM5ANHgrR7QYQ9TbLbulzxuoBJHUKrON/9hkwmCWksIDyfLH0okJLzSMIvN\nmRrctgTDDsGGuD5Tahrmon25vNWDkmGBSSLh/Bp6SNLvPALo4e8oxBrdVFdW\n39Avag7YrayE+3dY1/YyPCTrWvl+DzruD3vxnpFWXnF2cuIyqCJytRB/Jp7t\no34Z\r\n=ny82\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8228ea0d617eb9c00b8e802a0cd435e87e06cac3","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.1","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.6","@balena/jellyfish-logger":"^2.1.25","@balena/jellyfish-metrics":"^1.0.77","@balena/jellyfish-environment":"^4.1.3"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.121","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.72_1618373339627_0.6953655162507513","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.73-renovate-non-major-9a2c24fbfbaeda4d063519768688e116dc8fec31":{"name":"@balena/jellyfish-core","version":"2.12.73-renovate-non-major-9a2c24fbfbaeda4d063519768688e116dc8fec31","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.73-renovate-non-major-9a2c24fbfbaeda4d063519768688e116dc8fec31","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"794b03fed33a08fd4850bd6f7629a47126dd8c08","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.73-renovate-non-major-9a2c24fbfbaeda4d063519768688e116dc8fec31.tgz","fileCount":99,"integrity":"sha512-2wtfs7LzOJ603EXJunzqvkrwG5gqm3G9gYveCa7iEa8phkCzfrMNeAt4o3foy5RVoIUWCPI3THhcnt6OmWEeQg==","signatures":[{"sig":"MEUCIDCAVo690EyZUj8u+9SS1CpcxY+yA5ywW1z+p4KJnrfDAiEAsQVqXIwmgQPW2jqJbbC0CqJS4IzRozhUxQTTN0jhqQE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":688228,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgdqadCRA9TVsSAnZWagAA7yoP/1qD3KIWC30BDjcK+3km\nVrzf5YlSg1m2r+SvgVilUoOn4zipwatP/udG9Ll3jV3C0UeXPOG0x+zY08+l\nJ0C7gN7ZllfZ1yzRZL4gQV47SdK8rqjZqlJxT4KPSyl8krUhWtWzXbYp8+HX\nezKvdjCEUuU9LnRDZEYuEMZQgu5Lnu1uSxW5KqPuz80IvWCJHsTFElTxbQdn\nT9devzcNZXm+XWQ3SOaySDgpeJvXwrylezCZnXRTJs/D6PxuBrsLrIyZBcTH\n287CjGn4qVTD2ovpDG79Xh3WCiw0POvW2nVagXl/q1vu0+rs8ZTMatvwzWXk\nLuFf0WMtqsQ5PuXxU9qevGEKzIbEW7paNc/Cp8ik05+63GJgnlAyrwza+srH\nhhdwMLLvj4Js2TdFubEd9b4JcxRUPkPSI3PqnWQhmMxnr99ZLzEzQ+hZqOr4\nLhN25bcW2Ijte2DkEHOoiuyvg9nl2/hxM+QjXyYkpW0KCTNwFJN/7QygAclO\nP/L6CaX9Uiy3kAadxHR5rUfuGhJvj7ThFeL9c8Po3hIDk41OgO7c4cec+7Gm\nE5F9Uy8tKUKj+ep0SQC2I+yMPLScp/3qX5kNv2X1fOHLGKqInLxnQ8PSCYbU\nNbCcK8c07a2tXzvPw+2WHVSWazUZaIa6G3V2+0TiDyliw1AhbV2UZsmETdaM\ncZV6\r\n=NhzT\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"9a2c24fbfbaeda4d063519768688e116dc8fec31","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.1","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.6","@balena/jellyfish-logger":"^2.1.25","@balena/jellyfish-metrics":"^1.0.78","@balena/jellyfish-environment":"^4.1.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.121","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.73-renovate-non-major-9a2c24fbfbaeda4d063519768688e116dc8fec31_1618388637331_0.5386890557455206","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.73":{"name":"@balena/jellyfish-core","version":"2.12.73","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.73","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b615696c9312c3ca73be722e36ac2e9d4694c285","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.73.tgz","fileCount":99,"integrity":"sha512-VTN0webhN1D7nzSBAvicnTDJWrb6d66wsMtV+k3u0ORtJT5+x635nckXcBOBKgkk36tXW83Dwd7dSaF9BqS0Sw==","signatures":[{"sig":"MEUCIQDkhv2G51Bcc2J+AfVe0Eoh1jsoDcFggcpX3/+eXGrr4wIgLCQ6BnJENJ3BtmoVVo62socDtav0eloGtjP3bXhMPO8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":688168,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgdqe1CRA9TVsSAnZWagAAKEoP/RTOBCQhtdsQ2tB5Vywj\n1qFtfN3CRvA1e+V8aBA63umYzON8NgKISuCiLGZa+4rFrL1oVMXjD/VDY37k\nm7ZHkyxLOJMwRvQ0uC0LeYIGjYSOekAMScUS77M0+HmAquZfF8dKznHM8JY1\nNC1ixCki7rbDWcBavQ1Po4yen5JQmyxbjY0NROtM0t6SkF4klQRpG2SqXDWN\ncsTGavot9iN0qNIzj33D0BVfVcVDOhrFeir3bEV6JvO2BNdCcNEMQBYs/ZtU\ng9EL5xPu05v9SWamA4eR0pgsmrdgzv6v1rcHlzM9OPC38ZrU/DqyL7dm9p/N\nEF8X0E/LtPHqT5yhe65ZSbIJwkPclBPk2lijkM1FGNDKpEgY6wDXZsoR06Kp\nVDkA40KLD/8CFCEn8M397H0r13qQFMv+qOb1R4xgXobZeuy8qGef2EPoON/d\n0IjecFsi058IeNpT937d34xUJ8oiupUgYFnnaTOOwJvRXEisoBdnIJdmkk5P\nTA7ynuXszgak3zjiU9kdzTJoRfgF5wd3HrdBwt347lmXSG75iUvUp51OvHzc\n62QLcvasvmpbAyQbnIjMMaeaKcWCsqgKdimH/kSCZL8o607eQC0fP+ja77UB\niYTkk46c83mHkCZl4HAsBqfHJ/EBNwke+b2OgVglxeDk0UzzpCvvJQI/XUrJ\nThO9\r\n=kk0L\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"5c01bd8053fa23da815b9a2e9cc8b0f1ebd5719e","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.1","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.6","@balena/jellyfish-logger":"^2.1.25","@balena/jellyfish-metrics":"^1.0.78","@balena/jellyfish-environment":"^4.1.3"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.121","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.73_1618388917059_0.04619654793387751","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.74-renovate-non-major-9e05fc9601ff440f6b139b4f04437876539cc552":{"name":"@balena/jellyfish-core","version":"2.12.74-renovate-non-major-9e05fc9601ff440f6b139b4f04437876539cc552","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.74-renovate-non-major-9e05fc9601ff440f6b139b4f04437876539cc552","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3b491662ef074ffb1bad75f3c32f1a56bfa51f25","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.74-renovate-non-major-9e05fc9601ff440f6b139b4f04437876539cc552.tgz","fileCount":99,"integrity":"sha512-gIJuoIp10PKuzqoeTWb5a7oC4m4DbbgSql+Mv8CdmsAilGMA/B0puV/ckdmaHAWCSNm9xIvTbC8yeLWH4rDtBQ==","signatures":[{"sig":"MEQCIHc9FhYWycfAtplLO56pOm8sMo02ufhUlxmOZmalLQFMAiAKRYzc/ZD9O46f8wBoonGUVK/c6lyHYTBEy52XDqKOqQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":688655,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgdr7iCRA9TVsSAnZWagAAhNMP/jwPnL7LK/NJM1M1x8bm\nAFemVVyc6p96yjfO+eTn7z4fZw7Ie8q5W5s2W/tda6bQfTVESFurvKNLG9Dc\nX197MzMQVEO71yBB/bQFSpqKUXW6kM1k8hUuxDHSYjQzxfZnALNZrfgAqsPY\n1EdVN+0oJExOuSOPMMBpEcKpV2bw+OEWyFPG9wGJK9nrdMy17TE112vr/PqM\n8m0DDFhGOIz+DwX1C77oLvqJjyu0D0nJO8nwPL5CIeUDSfLh3JEbM/2ePT3G\nGxVakeeUnKjUmF1KGo+jI2Wo9V+flKaVsuvyKnNZtxDMNYsSXMkvC1Ztezk7\n8Nva3Wzp9TI8G2Q82UDJZ7CN6K6Kw2K5x+w+XWfg5IZoCAqXACnMdg9iQSlQ\nhJTQya6WAevHNzfQ/qHikD7QD7CdFOuCJ9qErAoM896ARPQDGXae3LA0dl1j\nEW8vAywPVpqeSo1DsBXW7BUBQ4NIvNOQi2q70F66ciRXLflrMEng/599XL/U\noW+eeGEnTywiXSzpTjMnt/oyXK9mxleUer2WUfjb7Rwb++GGBvsDy2y0n7KN\nf/IvPSfMDcx1knkX1UBr8jkLayoYBoyjifBPoEPTGutNiRlik/tvl/PECpAb\nv+9IUkzbIU7fBW0fZzql2RGui0qrVARr6ons4cLDnY/3u0eoLNn0Jofe8zrz\nEkQR\r\n=Qy40\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"9e05fc9601ff440f6b139b4f04437876539cc552","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.1","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.6","@balena/jellyfish-logger":"^2.1.25","@balena/jellyfish-metrics":"^1.0.79","@balena/jellyfish-environment":"^4.1.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.121","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.74-renovate-non-major-9e05fc9601ff440f6b139b4f04437876539cc552_1618394850001_0.9595744108390669","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.74":{"name":"@balena/jellyfish-core","version":"2.12.74","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.74","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"82949e6c8d0ad628b96ad3ceeb0d95720f4baf9d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.74.tgz","fileCount":99,"integrity":"sha512-XFOPVsGDkBc1PWmnlu6vmPfMDu+By7vZ4cJhwxsgpl1AvyQ4dblanJSKW0Rv8usLs9h+20jLAF5D4dLFhNViwg==","signatures":[{"sig":"MEQCIA4OjO51oujXVaf5KP9kzFt2+LIfiRJ8gTfQAKybRjbNAiA6HlESr7+SVS3bmZZ7xG4stZ3hZjw0NGSxshiK/ZNt0g==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":688595,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgdsCBCRA9TVsSAnZWagAAdxEP/2grbx0k0c1dDPZebcDe\nhUvlYXIHq2qzJiOWk4l+4ptpGNSE4CBmymf6wUZtnoPwEUcKuWbfZ/a0rpl4\nolks4N7QfDLvf/dSKsE/f3jBCdOSuuvvkZ3tP2CHRmdPn9Z1xrG/pgVazlMZ\nFUoQEQdmP6PbqLX0GMETO8mZRX/LzRVzV/VOQIJQEvG55veVxmtKe+x6p6N4\nHrQoPpffvup1lhtpEGKJmUO3d26t64ScoLTa49wa7wmcjNQQ9KQzwvsr18Wq\nLVWnaZSiGvtLuBf8i4L/kF+truzyOb8tFxhWHOHtlHC8/tOyTqRw7mTzKzFQ\nvtNnvTTisUd+eKByKQJ/i47w4zJBTGrAzi9ClFYkf5XQ8q6pOJtZ7iAUVdn5\nyHrWvbUQ1PEr2bfJKgb65hnoAN9fnBG0qmXo+JURhyJKJZn32CwatV4jqSS0\nYRuUxT0kxohyJEfr5gzmaVIDYfrZwqkJFftCdg1+mKZ3CUqula7k/MYTndGJ\ndTUswh1EetmlJm6fQupJgAQGTJR6Ecos73TyCr0vqgBPEhkz+nClTqt7xkeK\nkjOaRZ1E+A/VDl2OLkIDpxYOAxK7zxpueJRh1Tm9fYnH3CpMKusP7136NiAG\nIXud+vvsy2HUSLYTIihKVccgDO9+aUijkZBbg97F8INUbKJhqVTLthDoJxCt\nxU/5\r\n=K/qC\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"93237406b08248c981175a25fc69fa4e0d38ba7b","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.1","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.6","@balena/jellyfish-logger":"^2.1.25","@balena/jellyfish-metrics":"^1.0.79","@balena/jellyfish-environment":"^4.1.3"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.121","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.74_1618395264928_0.19483769587500444","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.75-fix-empty-jsonb-1d38df3386409861411ff6d03c1c84c06c75c285":{"name":"@balena/jellyfish-core","version":"2.12.75-fix-empty-jsonb-1d38df3386409861411ff6d03c1c84c06c75c285","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.75-fix-empty-jsonb-1d38df3386409861411ff6d03c1c84c06c75c285","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"13af28054e0a7537b1e6fb06322a8673af4a9568","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.75-fix-empty-jsonb-1d38df3386409861411ff6d03c1c84c06c75c285.tgz","fileCount":99,"integrity":"sha512-epDNDFSzU42tzRXh5f78FzYqDDMsH3EZpIrUF/cV8dgijVMh3EkJi7232vtTZoWfOspljDLG15KblQHdQJMRVQ==","signatures":[{"sig":"MEUCIB+r6ZJoS4GWpkw7fRWsL+z5DFkyy5xwi5WUZLaHNt+zAiEAu92T3f9nnsO2jca9zKYa+F3ftWDzXi25k1WY/Lr202c=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":690810,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgduu4CRA9TVsSAnZWagAA6d8QAJChSuBH1k+dwRgSvB5H\ntFJ0McK1kmPI5xozelwdX5FrFCX8aDII8Y0zjbK55Vfk8usH4btxsvFDcnh0\nWOaDWjy2AJl7PFGE+fLsKpGcih/fdZpG2PB5FbY2SIcxQeeorbf7EvTv14sE\nnwdv8Lu23KJSYcddOFK78nDgVZFVZUI7yMvwRU3AsOBeOoFIh25Kd3haQzvN\nFzJJHiH216bU0zDeyPJp6QcgeNGjNjfDgmzIxKQYSllrgBAQR5bb1ZKYZE1x\nzEwXm/ToPUbhaDMbDDSEr2q8360nUTw/a5D0JblrR3FD5qwYrhb1RIhA/Lci\n0RH2nNPCiDr0plzEgQm5sMmZiMPSexMwc5zs/f8SqfX+vYPHe61uWtEUZtd2\nsoYfGh1v0tKIdrh6qFJ6NCNEEJwWRbslb9SjElz78jOK60pNGg+nbjwYr01h\nD3tm8TqWWxNeKzvWMpLATYPnKCn+Zvo2HNU00EJ2mt1fpW9CoQQGpDgc6AQe\n13Q3LUjDclZS/R+Mfp8Gk1vVsFCsWDi5wcn+PwcjMZNxcHTf3CZOVx+oRs/t\nDSOEz2FDskT2izYvzsV+QHyi/Fpynrr6NRuHqM+jjT1BqjORMnFtSw54OFit\nNHn5M/QlsiW5LJvsSwZAhkPlRa3tHqL9OxHch3T1bD6SMZu2gw85BMgqSj2I\nuU35\r\n=1r8F\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"1d38df3386409861411ff6d03c1c84c06c75c285","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.1","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.6","@balena/jellyfish-logger":"^2.1.25","@balena/jellyfish-metrics":"^1.0.79","@balena/jellyfish-environment":"^4.1.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.121","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.75-fix-empty-jsonb-1d38df3386409861411ff6d03c1c84c06c75c285_1618406328266_0.8865492346014874","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.75-renovate-non-major-7fe80cba9009ae12f760c645d4a565385c45726d":{"name":"@balena/jellyfish-core","version":"2.12.75-renovate-non-major-7fe80cba9009ae12f760c645d4a565385c45726d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.75-renovate-non-major-7fe80cba9009ae12f760c645d4a565385c45726d","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"30bcc89cb3ac6d536c73b3992d7978e646dc60d5","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.75-renovate-non-major-7fe80cba9009ae12f760c645d4a565385c45726d.tgz","fileCount":99,"integrity":"sha512-f5D+cveurk50xxBbmsq1ath6i6sHiyN8FfhxditnmN6BINYKRqZSq5+vHWserhiWznvmCGqpdMl/mXwTR+heQw==","signatures":[{"sig":"MEUCIQDH8JItnBkifyPBseNkbzuzS6xiYkfkSM012gK8yaFTIQIgbZbgqqf88rNa7fdJeaEn7Pmv160DHKORL4j6jv8LLWY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":689082,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgdu8RCRA9TVsSAnZWagAA5qUP/iHmVeReFjnW7WhAaJR+\nzN/icIIxsYHpzhFfXt4R6wPVVlmeESXXqCux5ifxBlLuZxlsKLHaitVoapG/\n1YniNZ8sdrt4rybKqceAkvLT+4bQj16uAn55/KT8sbiNF5fYuCND0ejBvggI\nLAAxSVpNNg2MFBLNaM6e4UazzjFpp6C43w4+4ixooiAkcytxOm5ylwbLUeNO\nsgQhiUWn2tYVccCeylfITMfDYoAdzM9t4zyIM8zEicPO260nX5CZCvMuNWVR\nVYCbtM3Ob+zc7VYyXN/xTlWTand0SZIweHLERrcfrypKiagYtuzAN5sxziep\n8PRDlMvKdDwsVexprh4mLYT526vXtzc35v6elAv2pQOypFcvS0YoGN+eYwPG\nUSkxDG6YqYf8cnNxk3xSMb/8gSHJEJjyoUohGeqNpJRoALhALf2mawrXWguh\n15vD9NEiREQPAtBZI1/BIQt2CaNz8npOmMpbKY+CyA6tUmvvHOWtXaHhAuPW\nYKx4Zq53DSjLHTbUjimxdEzH+wc5Su0Eq6+6pum9O7bCeshOAPdKJJDUy4oc\nHUhHhRgXIuFCT43V8F0usIgDIX5xqKVaPRYePIzR52mJwU4oZnMjHhuXh27P\nVkjbaA0ajvf8h2nssKfOF4t/9x+YtAhshdw0x7UfYlqA2M7Z2v1iIGzaCkQI\nwUPt\r\n=aaXG\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7fe80cba9009ae12f760c645d4a565385c45726d","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.1","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.6","@balena/jellyfish-logger":"^2.1.25","@balena/jellyfish-metrics":"^1.0.81","@balena/jellyfish-environment":"^4.1.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.121","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.75-renovate-non-major-7fe80cba9009ae12f760c645d4a565385c45726d_1618407185072_0.19476658815338244","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.75":{"name":"@balena/jellyfish-core","version":"2.12.75","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.75","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ef8980bfb7d0a416dd42b947ad8d399da588488d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.75.tgz","fileCount":99,"integrity":"sha512-kj6rCchKe1qQvNRwLosp9W43j1qDCbTT1QEY1n3pn0cRRZc46W6F0PFRoWM+ERac5FNZshaRm8zLEVNSGyxQRg==","signatures":[{"sig":"MEYCIQDll2NJTyeNcBHCxHMJV9DiNds4t2ttCF2bSYzDasCOsgIhAIrv3lImATkg8Vr6bx0xRahzujvYrML/QPVFq/KFM6c1","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":689022,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgdvBqCRA9TVsSAnZWagAAJigP/jCkKF5/fitQ2imSr0FQ\nM8XyifHMra5wM8FvaXKu+SC7EZLk+RwrhQAhg1tEOoJkmPhvBpJTNJfWnfeG\nVgliNB3xhqIokMhbpz7hiFYlpUKjnwWg+AuiQNiqOB2pExpMle2dV0lWmrWo\nLgQLp6fgQ3txqwWek/Sna3fZhfEuzdf6r3EzYaeMKymM3ZJKQQwxNRp6S4Zu\nHU5fWbt763PWv8C1TS8vPqqkGYrj5mdfDJjw2O+7iqKgwNLe7gsjTkUmZsEH\njvyGLB0A7ut/ZGRZaXk70SviHbvGCt15xtAi9KutOumjfhJBp+9cy1EhWj88\nPdkvEAmvrjE2O3+cngVLGobR+qt0jwQJMzizXYhG16MzGrDMpY4YNoZI9i9s\nzRpX2OghxB0pwDvhK9lnW5+/ytOlv0AH7No+L8BJ+LKxm/POmdTNKoIffYeq\nzT08f+ztkpc953+T/e1+PGE9T6URKXiqxL9xn9rt+xsdttrC2oZSHwwft/BL\nQCHJVC6D3ilWSqksq78b5d/G1oVO3o5iZYyyZF2ga5kYM16uSJxZ+Joa4LmF\nHdWH0RQ+WAdYl05p9M32tv/xdDDfl5Eudnt5Fm8vzRZNJZ9Yd7Nej2Mm+hXX\n7Hd40L9OfwoMYexyc0JYwUk042tFAHOQjah0vDpBbeXdnV+eT21NcEXpnUic\nateK\r\n=AVkQ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0153d619afec3d1a31df942416d9671598d79b7c","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.1","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.6","@balena/jellyfish-logger":"^2.1.25","@balena/jellyfish-metrics":"^1.0.81","@balena/jellyfish-environment":"^4.1.3"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.121","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.75_1618407529720_0.15518863702596764","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.75-fix-empty-jsonb-e97031275cf50a60e52499625a9f82f3a0770745":{"name":"@balena/jellyfish-core","version":"2.12.75-fix-empty-jsonb-e97031275cf50a60e52499625a9f82f3a0770745","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.75-fix-empty-jsonb-e97031275cf50a60e52499625a9f82f3a0770745","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"fbe89cda6dbce53dcee3d6b9ea662f41ca9f9c03","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.75-fix-empty-jsonb-e97031275cf50a60e52499625a9f82f3a0770745.tgz","fileCount":99,"integrity":"sha512-1u/CoqAALtgCHmT96TELwKF5Iy52BXBgQ/S2LGQvM3j3bvRpue5aT7r7z+zWMQonBIGMYWAS4GGIUBVO/MvejA==","signatures":[{"sig":"MEUCIA9Ad6FjAyDMYJbDejlbIG69w6KDlNBPf4pwMWJc0z5jAiEAvKj5ilpmm8Ik0nzaj177pTn03hyQvsEogquTgoL93z4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":691148,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgdvDaCRA9TVsSAnZWagAAgpgP/icQtkOytxOxqzbRD/SC\nrsZvQaq0L20jkrvlAZT42VS5wjiw0IHXGKKeNcfHY17gEOqhWqQ6aHCaaNAN\naa8c0MHDuVNcp4ymh8KVH832s5gZ7TvRdl2kMPqRYacs32Cm1XKejmgqF1qU\nda8eLPzj46nWxivKoAh0B7fn+ZOh2O/eSKxaMpkcH7ZoddTh1fEvclVqNS63\nKJC3haTOYMlT/VixA0LQ42xxe3/nacfFDJ/j50fYvSwDT1No7RBYNN914KYa\nGOMposch/Nznj/QbGbYXz8GmZmYk0UKQkyNeGkpVXDRRlrzGVNQhcwHZZP6Z\n9HIsuMN/GeiMJ632I62q5gcFhJykoKFMJfEcUiXHOSgGGm4Kyf6pB1TM1U8x\nkqCqyQNF6u5K+NqGDLI2SdEsKeETg8lslvL2k87xHKdXor3C0jD3IJTivnbH\nl0eC4yqsP+wrhGl+mBZN6hVqM+y5j1tXLt0D8GDt3UM3dHCgV11cSG0/7SHH\nyjb6K8HEb9cf1LjCrmgdMnteDwNbWGvWZOSYcXkivW7t8YmSPYh7Mj9Ye9jY\n9L5ycz/KXg7KMbGq8QnYNYfN1TSaPNGd6vfG6+PaGAtlnlmXuAiGQXp8alSM\n7MLkr4ixGWuqR6KTtkOiPzXjdHSpXj4u+hXGguTj/11Ph5/IvpRVrRh12RwS\nQuzx\r\n=xboK\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e97031275cf50a60e52499625a9f82f3a0770745","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.1","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.6","@balena/jellyfish-logger":"^2.1.25","@balena/jellyfish-metrics":"^1.0.81","@balena/jellyfish-environment":"^4.1.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.121","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.75-fix-empty-jsonb-e97031275cf50a60e52499625a9f82f3a0770745_1618407642007_0.4988332204488075","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.76-fix-empty-jsonb-84ca1e987fd54f9b1065b6a3946be1ade41294cb":{"name":"@balena/jellyfish-core","version":"2.12.76-fix-empty-jsonb-84ca1e987fd54f9b1065b6a3946be1ade41294cb","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.76-fix-empty-jsonb-84ca1e987fd54f9b1065b6a3946be1ade41294cb","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d1474c2cd02f0aea923af6eff8c46861f5153722","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.76-fix-empty-jsonb-84ca1e987fd54f9b1065b6a3946be1ade41294cb.tgz","fileCount":99,"integrity":"sha512-CMKPV3U+cYb+b6SXvzPWyBLcCjOxu1xToMJh5u/OD+jhuR24SchnGE6o2gc6UW3hByotafMCJ7oqlhibEbJTRQ==","signatures":[{"sig":"MEYCIQC87FnmxpNz9Lop8Lpi5ij8mrxW478sJqnnCvYZr2VB/AIhAK5eBxlLe7rX5rWVR+FsI/Q5svOFHtC+3XnM2hNsQkR9","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":691237,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgdvKRCRA9TVsSAnZWagAA+i4P/Rubg1abOUvQIekZeRn+\nq3PSX+CS609cSREKK2rEJIhG0tToVr+8QBfYhE62HJONX1bR6IneCurgkcUz\nE6tqzba1sDj7v9zbcCmV/xtAfZO3I4gsh5j+9KX3TlHCCfWM2X/W8NQYJ7dn\neH/mw2+lg99mvofA6Z7CWZuVT2ZJDOUqSzEtXvMOqu9XBiQYFDuJ7aXqOKZu\nG0Iv9Nd641T2bq9k84Z7u2PvoZd0THXCTnWHvNv/V+ojJdGGMaqn13x7RUp7\nqnz/WUz4X3z9bmoUI0yBY5VgBO0yrRAiHdyco4lnmMa3coLpsuG0ys/tcCKa\ncKlZE3XE426Bw1zfRa1sIYcCX5nIRoYQQKz0HyHcQhLYq0scYhe1J2JosgiZ\nwAtBDz4WJHnJ/oqiv5/aUTgU2cd+ADlohx/5WTuDlXgPSI8ilbVsbActQLNU\nXLe6DH6oKs+n54MyVjHQjtasOBez3wf2DOeb8Afmq/IyCbPrxJZjZsbPZYXC\nPfK6HoCq07vAKmC0AK1Xo9wG8pO2+n5wOXxgo+X+xjFABf9f/ysl3xwnwdEt\n1rxmVqVSxbGXoH3YXFvl9GE23A7So57ntP5O1SVnB7TFdd0NdYFZDbxAkrpr\nB4Xr9Fxk7uiLPuhQ0YP/eBTsFeNffe85Yjvn+MfgOmrC7gGkTxwuCGp7JHgi\nbAyC\r\n=vai3\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"84ca1e987fd54f9b1065b6a3946be1ade41294cb","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.1","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.6","@balena/jellyfish-logger":"^2.1.25","@balena/jellyfish-metrics":"^1.0.81","@balena/jellyfish-environment":"^4.1.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.121","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.76-fix-empty-jsonb-84ca1e987fd54f9b1065b6a3946be1ade41294cb_1618408081252_0.6949485405562963","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.76":{"name":"@balena/jellyfish-core","version":"2.12.76","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.76","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"972f6f1fea5eb9ce8b3a08e884f3859d88d6150e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.76.tgz","fileCount":99,"integrity":"sha512-DStANiHtiT02HGRZOJaGALX1/mpNWdA6Cr6tlYp8gQp/9gyzNCYF9PZkNk9W7Zns5PJEPws9jrpP8UbrLY8nnA==","signatures":[{"sig":"MEUCIQDIBAErKFK+POSETexjtIzybk7DDZoX1Rg+1vca1ilJJwIgWNlY9PeEShWTdOGhHEp6JhjMXRL9stN8ABTXlM7wtPM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":691180,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgdvQCCRA9TVsSAnZWagAAG+gP/2zD/vunmoKodt/59ohZ\ncZhGUXG5dbGsdk9YyvZK3bicwrePDf5e+0jOjNWeUB9mBDXyLxN8WLeaHugl\n4f6a0GNNa4vx6GnM/cS6Vg+Uz8uvkUwz587vmAmcbrprc0fjVDw2TTtAAszk\nSEhNNZFO8rEKh4TQvLiXFSGi/IdNY7nz/kZvOnS8A1sBb7JX4CzpO2LANY3/\nKLW1f3lf2747ncOCqnQSOWB99spztZecgK8sP0nWY9t/6vx8zWjindZdqz09\n9ytD4XOVo5iaYIIG/N4Cr2VlnbHnY+JSYAx7tOTO/0Ec+UXbK5r6FLtdVXsg\nhbCH1K3vQIkv9fkQ7hrlpG6LXhruq/i3Y7wxPj0PGZdjY7fY9X1rWEzRfkxg\nU7ZHxkRdVZXKMKvbvap2na88toC60Tk80ZiityPBGy9K3e8mISOeuaOm/xZk\n+BGr/BCnoLz8t430eHY2toJuLEX51z1B1GZlzXCgOFwzJN2zQS7jPb6xo0CW\nGXu08YTyYQmngA/b94wNrRruEtLzGjfWKf0iBayNnBieewhND5PHlAl1jOiR\n7M5hW8xAHYc4OznKo5GekLpsubW0hCCAh3dEoGfFma3uDWwREv+D8Z9VFPLc\nl1+/ZZ4HkG3R6WTcRWduCUKeVU56Qnw10iRAi7nX+KtzgV49txhEAS5TvP8A\n0z26\r\n=Up5J\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"cd965493d6a70b4ee110db28182dc63a859a333f","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.1","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.6","@balena/jellyfish-logger":"^2.1.25","@balena/jellyfish-metrics":"^1.0.81","@balena/jellyfish-environment":"^4.1.3"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.121","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.76_1618408450150_0.8289640822315403","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.77-renovate-non-major-dd5e88736b812f5d2fa7228276bcb9fb960448f1":{"name":"@balena/jellyfish-core","version":"2.12.77-renovate-non-major-dd5e88736b812f5d2fa7228276bcb9fb960448f1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.77-renovate-non-major-dd5e88736b812f5d2fa7228276bcb9fb960448f1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ea5e6475ef501ef53f8d7df79e35854d90e33a24","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.77-renovate-non-major-dd5e88736b812f5d2fa7228276bcb9fb960448f1.tgz","fileCount":99,"integrity":"sha512-O2+dlNec4QJU1yhT141yTHWiGMdnUvVZSsEqS0rxkjvjKXnKuhkzRBX0DTkWRqo5+fk5ekoLbCaV+co8EXDk6A==","signatures":[{"sig":"MEQCIF25CyajLIZQ5FZomo9Il046phYPk1jEDhEDzJhv/43fAiAcgxiY17Hp1o5H4jLy3ewzNRYtMbURld5d6kNjGNYjew==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":691667,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgd08ZCRA9TVsSAnZWagAAUT8QAKKdaita8PmPu6DDmkLw\nDS2mhXctvZM1xKFpGH/NPBXmG/lBY/W9vAnLtbT1CZSR7LtSk8qPb6Yy6H8E\nMUFYdu4wGSpqUmT4bFvW7os9vwkpzq4/+5EOeLfBuagS0fEDl29uxNMDCYF2\n6Ru+AmD0W/GptaW7rzWEaOXQ/58TDq2dbcusAEwKYJjJdqTQiXtQAiVjpYNL\nMmLV9h8BsWKjMlt7mZv8HsKQNMs6yRDaB+fn4H7Hn6d2xfHA/ZbNBH+b/zb8\nhKE0H++/n64hVHw8i+L5xmIgyXkEdlYgwt2XXZ59WFhbLfyhpjCFqtS366S8\nmKxBlydK6nQctMR9pP3TgFuQ1zJnLzLe0YMfQW+RtTtQeQ7NFyCpxiCI6ALT\neTBe4o/YlYAIwF+p8cHhOBl10ZSmn3GdB/+hJknAjf9H/dFw7C4YO/Z41LWo\n946nY7aFtD7GnPLLryrqsD6YCGK1j2CHW02aKdssW6C4Np9R+9S28CL4KSvs\naoFklck1c2qZfLdj5SlLT+Ows/iz113itkMpYIf3elhWp2KTuQuFg3HIMfy5\nqiA28TaoAHV7BxU3tZ5QZnrlXIp2x0rAUEjUirHxQFFtZGkjaYMb33sWA8L6\nto3vmoQanzWDR+a7CfPOHRjgt0VdXN2zb9swZZL+1IwWeK8fxTHQDh3sRvuW\nGurG\r\n=qJrn\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"dd5e88736b812f5d2fa7228276bcb9fb960448f1","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.1","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.6","@balena/jellyfish-logger":"^2.1.25","@balena/jellyfish-metrics":"^1.0.84","@balena/jellyfish-environment":"^4.1.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.121","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.77-renovate-non-major-dd5e88736b812f5d2fa7228276bcb9fb960448f1_1618431769054_0.36535166553533194","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.77-renovate-non-major-2a453d2e70bdc8ac8d803912f67704fb9ad52109":{"name":"@balena/jellyfish-core","version":"2.12.77-renovate-non-major-2a453d2e70bdc8ac8d803912f67704fb9ad52109","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.77-renovate-non-major-2a453d2e70bdc8ac8d803912f67704fb9ad52109","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f962e71e9077341658fb0d1232eeea7fb18ef66c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.77-renovate-non-major-2a453d2e70bdc8ac8d803912f67704fb9ad52109.tgz","fileCount":99,"integrity":"sha512-zk6poFSqkuMoildlu1IJlcndBExFsRkqqR47nO/5Yg4R1FR/RAV/67McejtQch7qhLK2967zpyUBvLWuTyi25A==","signatures":[{"sig":"MEUCIQC1OzzMvzPwq3ALkFuFmfmosj/AooOBUUhzl0eldX2MfQIgSu+iQ4d3egmKPTi8NeJAXREEQmald1bK8OJ3bdt4Bc0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":691591,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgd3cXCRA9TVsSAnZWagAAkX0P/2pmQexPM3kixtdXg20v\nqcSlRp4NTwEIHlr+QOGI5tX3AJT+e5ukQYN1jXe/Qf8PRhvUfy+wC/OIoGXn\npf3eb8fUl+7isaEGNeP8IPWHmJIQz0U+HRSbh0pwCb8seSGGySteBHbypIaN\nWs2eIkz5wDdeWVHhSk3dbMnxY4dRh0e0cJE84Z2Ice5RuJC0h0u2anHt8Tr1\nyXS8GdL+6bG54YaP/3KV1EM/i6zL71OL/F1zQiG/I8SieiGWgd7H5MnVoHmh\nS8rWDSVLZ2KpJeAcUiEksaAwoMLxULNaeIkRVN1Dy1hTs5/XszSXgsiV8h8k\njCJTbY5xpKeK8+3pUPuOMtXa0MQxRYDI3/XKXFlR2G00Pzeq/m6PUX1WnN7c\nACjSZQqgIwMAfl5R4urfNTPx4u7MsScBpEUWriXd/nPgGwty1bJBaG+O4nuj\nYQQbzgiZ1oGbUDf0IW/q18E8OvcvewnwHmZl27QHM8g7IWmj3umpm33AL8Qk\nVuX7VL22oS1XI+xlHs4y2jX6ZFJfYQ8buVhSIvAerLjr5mTfq981LP8DWYIy\nlYG/fakjC7DmaLQGSVjFb3szmhldmAYjBeZu/3vghUUb+nTFRuJORBhnCMKG\nvq5bHUkx7Md5Z3gzo8LrwFOCyBETt0TzooqW90WQhtNzN8ZnvooWVYrwWsfj\nQbCZ\r\n=lE+f\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2a453d2e70bdc8ac8d803912f67704fb9ad52109","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.1","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.6","@balena/jellyfish-logger":"^2.1.25","@balena/jellyfish-metrics":"^1.0.85","@balena/jellyfish-environment":"^4.1.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.122","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.77-renovate-non-major-2a453d2e70bdc8ac8d803912f67704fb9ad52109_1618442007114_0.5557868240719672","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.77":{"name":"@balena/jellyfish-core","version":"2.12.77","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.77","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d3a208611ae088fc2c45bbfb6a831b8c277bd731","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.77.tgz","fileCount":99,"integrity":"sha512-XrTw9EHveqtHgc+8hssGTmdyKzxBOd9h2nsQV2haTnBXLyHWYvTvPUOuooZBSe4rWFh0iHxIth4Qcp/L9e33zQ==","signatures":[{"sig":"MEUCIQDB3nGWeBDWZxy+1rWAdm7xJONVLIqRvyvZ9cgkPwmYwgIgSRL3SToNEY3Lur3DvHNpPVkGHMWP/ivne4Z5OPx45BE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":691531,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgd3iWCRA9TVsSAnZWagAAcV8P/3K+p+0VURtjLaSMQCVp\nNaUrDwE/Nw4nNyZPfjQhn0pIChXEFAu0+uRyBmm2g6IDHr7M3wQ7OmH60/GU\nc0vY9f0BkvrO99stD5UmT95VoIPX3+/X1PBO+xCoAqQ8ZL9c1JjKuVPG5XMD\n+/2oIzs/MC0z2Bl0CpmyEiQbgkm8ZYwGsAPbinOoUm7OuchGmkfGahQiKG91\no2PGCgfD6xfaIgMbTMPr2RB+qf9O6w02pYZTZ5aamyh6QUYAmvt745mEBcgp\n3NfJkV/BgiKW0euUOKGfLGaTm1R7JlVZR5uvDYa8VVfswPVCRyU+Duqj5S68\nvbqREiodq2yx3JZFQWiVrOADsmsr9nafvkIaszPONZACR+oNkWPhy6FP6OYW\nza+wBADh7JrJlep0cBZklIP3EbH7pqAKnwjRF5RPf8BXMz7J+4EOEyNx1Wcm\nvKige3+FUZR1PangtWe4YZV9gg7yvkFJfHR8i7Wmyi2b0JtnNeJ7q3VA4uaE\nKasftHcJIGrm+mca64J9sFeIoCc7UMZwwPG1Wrg8FmR8trgnhUS8JTv9IvF4\nC1hdHlTfOoYwkHeJSMVBOV8B4ciSYfd5/YSyQGvumY/+lh+kqqrPIzolhQsq\nVpovMNPmtDwRXIv60OLqjSkcHPCLvYkVjedKVhIk8EafZLHHo9WTbf15LP4q\nm88c\r\n=2jFq\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f959e1505836249d97bbacca0bfb08bb65e23cde","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.1","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.6","@balena/jellyfish-logger":"^2.1.25","@balena/jellyfish-metrics":"^1.0.85","@balena/jellyfish-environment":"^4.1.3"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.122","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.77_1618442390434_0.14400494741375813","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.78-renovate-non-major-7577e8313abbf81b07fa383ceb0f619ab0684113":{"name":"@balena/jellyfish-core","version":"2.12.78-renovate-non-major-7577e8313abbf81b07fa383ceb0f619ab0684113","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.78-renovate-non-major-7577e8313abbf81b07fa383ceb0f619ab0684113","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b0bee69bab49789255f3c96b2967ffa5635f074e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.78-renovate-non-major-7577e8313abbf81b07fa383ceb0f619ab0684113.tgz","fileCount":99,"integrity":"sha512-DzNk69TchS6YkSNBJ5p6U/z2fAVW6nWOLdceQY7C+roiLegpyVBczlbSnvTeVwOtT0PRUwOUybLcgnwaVstgFg==","signatures":[{"sig":"MEQCIDcqL8vOIGkwMIRucnCv2Pncw0XwUU1zpcFnsdoph5J/AiBUUHNmYx8OLtvgaWI7b06Vej3Z9o5Ak1aOXlLJ9//k9Q==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":692018,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgd5oZCRA9TVsSAnZWagAACUwP/1PbPGvA6FXJysfDuUIV\nzr0uxJgQJz5aD9mrMSoYxREK48RBpe5PIVo7Tzwea57ULEcFPoICvGmjeGY5\n/okWqXuTIEv/sD1FCX/SQLniG+bYdJQPCD5n0JjIExCmswCjLD+IpHOrwXAp\n01jFkVAL2UPuOO9X11zv2AIk0oRnfJ/w8B1ba0WMYgGFKsOD6ljMs59mcQ+H\n41P0lVJDFlS3urI2c9OLqowjCDFb9TZJR/DjxxXTWFWAKNItdbr2osqAp+Wd\nuBZn2EescvwDJAxrQXvxMwVZMuBlwJqtUdtxGO1qGMCACejfLIYoL9PUtVZS\n65HVQRwbBukAmmkDasx4RK2MiNmZwBHZUFXPKF+AYMPK3jMIx5VCQaPjMLLh\nmSd277Xs3BHJnDuy4wZ+47F7SdjKapqBeLUwjLYEzvWM8nR8isQbi9fTC3Oq\n0Rg4xLTAtmVZhNowNFwa0FXq5eIJqzL5njDAkDvczGLdKDCxUYhiGrA9IQjZ\nxvbEwzzR91EgPRc/VWrqUcNyGwicTKroBcJkjgOtAzTC5SiinHviJkIflLgV\nWNpJ9VYVJh3mX2v9h7B3RBifhqKr2Ib9RTidnXvViHTjNo1kEWuvarWv1GH5\ndkk7lW9UXp+rOCVARSv3W9qhEsDA4H9TGqIv+Cugrl+bFd++uDgmUMeRN2ne\nJGuz\r\n=c6By\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7577e8313abbf81b07fa383ceb0f619ab0684113","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.1","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.6","@balena/jellyfish-logger":"^2.1.25","@balena/jellyfish-metrics":"^1.0.86","@balena/jellyfish-environment":"^4.1.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.122","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.78-renovate-non-major-7577e8313abbf81b07fa383ceb0f619ab0684113_1618450969249_0.43758876061949925","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.78":{"name":"@balena/jellyfish-core","version":"2.12.78","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.78","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"18e6bb408dd4eb82d17b81eb776e6c10c072c3ee","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.78.tgz","fileCount":99,"integrity":"sha512-0mkgsalbYKgx3mRQWUPziXoNeZa8QLCsZDoscO+t1tgs0QPSUWbieGk0IPEJL+JXvvu/QMde8pHIYQ4xhFnIYQ==","signatures":[{"sig":"MEUCIQC1VdFtoBhC4dMwedjbPDpwsU8vL6xodYXyD53FXLESsAIgfhTxfyzHR9dV9G4sqKFrRIxcjhqaBWRdICs6BFaRJyg=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":691958,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgd5ttCRA9TVsSAnZWagAA2f0P/0BGd6LnMqW5Co2oFi8q\n7r1ClD7E/3D8B1DEP3b9wLzIQacRLd5oMdyVIhNkK6Wur2Eq8ccQpBzRvBI3\no1W5rpEsGHy62juNRuGyYh1XUTZvx+ZSWZGyCjuliGrr4HH1lHMu5Ur504kb\npgluBDP+j1sBy5DIvtTJoayQhLKEILqmg66fnkTuAFtvaT4Fu/wyG7AMWsQc\nsZRI1ZMv190oUlHaYwDieU+01oqcgOp6MaImSppnfH/fhl0sfUOO/AxjDqB0\nES03zX63TSp3xM4ly4K7EFONaU8ViS3hYOOGiyKfZA6lu2QXvUYRDF3sbC/v\nrUcsSGrCENS9A5A9yYChv+hZSZQbGyySD6lfw5Y/TK+l0pH4kPPxYjLEINDM\n96HZ8gqzBMDIwk1f86W7CmIaOKAkxdUAYh/OLW4MvVGO+KQnnO6NN9LtW+20\nG2YMwE6QLuUeZcBn5WH4IQIWRD5t57IR7lJEAc8Xpx/RvHdLamzuWr5zSnhu\nMjhRUWC2A6IsUlsLoZggzFS0SeZEM/NM7L/F/0UpMj2H3mmzEPn4dFuVhKSf\nfsrpT2reVdhMi/tlozZmm8GZ6yJXMYP5///Z/tgMFR9JwKE8b/jA5wSQiGUp\nolEChD7nvXpA35IGZlZG0YBH+ncpwCxJMq+6bYMRuoNa9DMN1dcP30VWzyhA\nJcYy\r\n=yFqx\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"33c497347c745528122703fa49e029197cbfa5f0","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.1","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.6","@balena/jellyfish-logger":"^2.1.25","@balena/jellyfish-metrics":"^1.0.86","@balena/jellyfish-environment":"^4.1.3"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.122","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.78_1618451309003_0.8996736538558614","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.79-renovate-non-major-866407a217da9166769276e6c2d30d4afd65e648":{"name":"@balena/jellyfish-core","version":"2.12.79-renovate-non-major-866407a217da9166769276e6c2d30d4afd65e648","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.79-renovate-non-major-866407a217da9166769276e6c2d30d4afd65e648","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"25b67979eb3ac836e1f74348cb596b70f912d184","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.79-renovate-non-major-866407a217da9166769276e6c2d30d4afd65e648.tgz","fileCount":99,"integrity":"sha512-3aCjIeL2jTnh0mSuZCkGIdiq/uJ1rjai1GmbLarAXfZsR/yO7YpNE7xFE8ZRxZWQHlEAcoDXh2qGhyN02SjccA==","signatures":[{"sig":"MEQCIHZ03H4z9cEZFGvZ6CTky7YN+ehnWNMdxTVB39sARdxYAiAUg7KA/kfbh6G7POXa8XUyM/Sn7y2XzTqZOrIU0b+CIA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":692445,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgd7bhCRA9TVsSAnZWagAASmkP/i/ueeDIRpH6SfbhM5bY\nlk/RLZtFcqXWWjR+zd9OTzYepgoARdNESs7fD2bnaS1/J+wws06+hgsGbk3D\nSLRaO6J7DthFIJkf0yblppWIwAlcLsC725XhIJbpgUCj8AxjauZsvfrwDeFu\nMwmArjy19NhW2+B5uGG2Q5Tzx+AgSD2GXMdRNZoBC3X3XkeJwUSBtY8WghEd\np2em3SvKgD1gCnhE1o1yF6o9oP+MNYAiPAKT5t58E6HfsSSD+7pVgd4GVaf3\n/q866oVazz8nCohK1mFxjxMDnDP06zFOB/fLiQw7tzpcrN3qfjVM+o9aTvA9\nL84K6rxtfYssP5C7jqbGzKW3j1n1ek3tShDa8wbgxKjV9g+ZvvXT7Ayk+nJv\nh+0Ant6peWp0PhWp9+Lo2d9DfIjYNAvEwQC+b705VgPr51ooT5vpb3FYr/7+\n15s5h04Yflavuv68UVqNXyIDWQSum1D8vw87jnhx401GgRtn4T3tPfGQWlf/\n522gliOfn0COuFk2FmrQQYTUvOHNur3dkI0c89FTu/eGDN+/Q9BI6jFxad+a\npqtLnAclxRiPp0/ZzqXKxH1qzEKg4YM79b8MR7kUzxCyKqAI4rK6h87uEXVs\nA4x0wdFjwCrSKsWuoz9KflLOKYWPD3MLSMuBUj9LYgkYx5lNUOd310nriaWg\njXXr\r\n=JF7A\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"866407a217da9166769276e6c2d30d4afd65e648","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.1","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.6","@balena/jellyfish-logger":"^2.1.25","@balena/jellyfish-metrics":"^1.0.88","@balena/jellyfish-environment":"^4.1.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.122","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.79-renovate-non-major-866407a217da9166769276e6c2d30d4afd65e648_1618458337062_0.2302439430973684","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.79":{"name":"@balena/jellyfish-core","version":"2.12.79","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.79","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"05346817111a43f27d4cc8a0b51c451c90de45eb","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.79.tgz","fileCount":99,"integrity":"sha512-uDzsQcyZnOkKP4kUGb7v81YkoR+M/9PGU6s6D6MdoIyCfqAPNaZnigiDO0lU/zpTXsalom1hvOdodB8dqb+SjA==","signatures":[{"sig":"MEQCIGLZGPf9f928Nr3XiLSRGJMPlQAfGozgkv9cDNTkkHiYAiBrVpdWQZYed0bYqynvlaHTKbmR/Vzsm//2oswgqvhLyg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":692385,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgd7gICRA9TVsSAnZWagAAmQcP/j71rohP8vDx4g6hXX55\nunv+M/Z9HZWhx7L9taHMqIYRx8GLWA/uAqYLwKa2Pg4SKuDkWNgaIRpn79zV\nEzJsHT3AMTAszU5+SOcIF2JTQZZFqOSrzQvvila9/rOgMQjkM5i1O24HpdCo\nMivyvz2SBnigiidYa56ft4sUbd8mQpTTXxFZl8CDqjVj5FvQwVYtdIfaAU9R\nLeKRF4SaJBINFdVEtYqUi9L1riskLz2EFismJEtSdQ4DOL0c5TPDEhP79jBZ\n9NlF/AWYFSEUaqQWCTgNBP4vpAKvw8O6A0eW6mMyhxWQKFmC7GWjWvtdw+TG\nZ8h3IpoMw7x+bnUcakTbRkR8blsWUOwnl1HM3MAXVj0I8CgwaPTpznARc0Hq\nGY+Vi2fisTqMxybZJfBrXpKBQKG/BfRmmDIuYd8dG/ADga0/tzo1ahuqx5dz\nw30fSq93k+73NrS1liGgmvjOIeT/B/eJAITSwcwYa9J9O0fITjWdMTQdb6do\nE9UHJwUSMqzcsnmHt2Vs2EmedJGmPkdELaFjikfftPJ2y8M3I3vkD2cALEwN\nL2k4n/wp1yblUwHUQG0gkIY2Hoxq5kywNZatmzxbRpAY/wh1EfivPl4QAPdF\n5F7Q8D55cAizKsUe3fEohXKEFnYITQucSj5YNFsiD9Tf31/cWCvC3VNvhguX\nqNB+\r\n=MelY\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"afbbc1c8dd513e47118e1a3e3464a7f3b2ca6f8d","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.1","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.6","@balena/jellyfish-logger":"^2.1.25","@balena/jellyfish-metrics":"^1.0.88","@balena/jellyfish-environment":"^4.1.3"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.122","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.79_1618458632350_0.3745033531676598","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.80-renovate-non-major-1d717cd40517eb9decce4d8fe6eea758790bbbc3":{"name":"@balena/jellyfish-core","version":"2.12.80-renovate-non-major-1d717cd40517eb9decce4d8fe6eea758790bbbc3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.80-renovate-non-major-1d717cd40517eb9decce4d8fe6eea758790bbbc3","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c58df5feed5b8ab63d9009d50075580046e4b186","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.80-renovate-non-major-1d717cd40517eb9decce4d8fe6eea758790bbbc3.tgz","fileCount":99,"integrity":"sha512-1PaRfnyfGYap8730xxlbudlEY+4b/JKeM47DEvyyWOB8pNc6NMXIAWr/x2IrfRDKtZNeUzqBNAMIL3o/19NbfQ==","signatures":[{"sig":"MEQCIHgssSrwv1Lxa/7nG6oQvVwygOXhh9e8c+tQFeBAqsPwAiBoXSdwRxmrNggPl8CvPzcN79LIGKU01NMD5RlFoH19MA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":692872,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgeABqCRA9TVsSAnZWagAAKo0P/2Be/EP1VA1tnLRVzD7U\nDNiS8n/WKEnkXfLetxY4Kb+zEPfTE3wH4e8tbRAOc3KjItTEl8Ds9JDyv1ZK\nKOMG3B51fqup2T3j4PtPGcf35BnWgdwOs8ptyOUUOvJf/nYi68C3CKsduptN\n6jIs6XW22Qk+jSPsHNcGsK0ZkN1lWh1Zak05bwz+0f/0ueyXG/Tw5R6HoDbj\n4bDmBC5pMOgnmwgv3wRziW1ixYZFWgVLWRkpY7fxJjp/pOvC4HpectzuEgmx\nQy1aUGFMKAmvhWT/+Iwxa0tqeSpUqYR5uKqs42hkyvkVxK6zGpz48t+m18ei\ntIoA5b/ti0z/dKXBGCDRwL0nFY3kfKDFv5Y+8OVl4aqqaYH6NAegZ1WZB2IQ\n6XydjIOmwEg1g7YJFAOCFrl4S3bkSCiQLqvmDQsM+sTdSXGd92pGNsyF1qFk\nmce7R+E+7y908HyTMA4boYHTznCtCJ+EEKxiJQj5FHgv38RN08bL3PcuGevC\nrHCewkigM8uA5D9MYR8QVMCdD2QKy52GIoANnGUii0jnuDr7YKrXGx5pgjjq\n1RZqsk1UiADzCtzkOGag+tNNEWwwzdzXVSdz/m4yzwFrSwqsJnVf4U1HPu5C\nNJDi2J3b+tkXwdvheQPJbxgyIIXFYA22sOHo+RSQqGPHhNA+VXU78mjswdHn\nBY39\r\n=kKGE\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"1d717cd40517eb9decce4d8fe6eea758790bbbc3","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.1","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.6","@balena/jellyfish-logger":"^2.1.25","@balena/jellyfish-metrics":"^1.0.89","@balena/jellyfish-environment":"^4.1.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.122","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.80-renovate-non-major-1d717cd40517eb9decce4d8fe6eea758790bbbc3_1618477162032_0.6950476631711229","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.80":{"name":"@balena/jellyfish-core","version":"2.12.80","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.80","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f131e738fae9636b34b7fb8cdf574a20c106c67a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.80.tgz","fileCount":99,"integrity":"sha512-x5t6Z7MF+KBOpPQHveNVioW6YGk92kkK7uzclgbeSNW02frnQXoN0A/bDV/edjx8CaVciMRmNg2NBunKZnoGmg==","signatures":[{"sig":"MEQCIGoQezSxb6r+LwLRWt6OBrD3UpUVc1uguJx/utCLV/pMAiB5fSGByBQFPVZrcx7vaQO7Ykz5+xrqQzQYriHHw8GkgQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":692812,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgeAH1CRA9TVsSAnZWagAAF1cP/0C7RdFLarBjw6TDdreX\nfGJqIQ22VvoBzz1AlhErseGhZZoLaHDlZgJBXDUmQ365wMRXvw7B1lHaoNMV\n/WucSxR5mQ0UPK+lXVorp0KmMIv/dfKDQZxO5rukOelJPPfCX1sy+RqzTCTf\nlfOGONwB+tMgoYSYb2RkN36qfLtKTOD00kT9CeGEgf1mKgcqJLkBmdlhwCXC\nzakcW/YfBdJw8NmWze2ZVJJBD83hDuuSFWGq1EsFxOFTBGjYVyK9xi9dVfIe\naNcAh8fcj+gH6PyKmdwGQEij+Z8j5lATi18H9VAT/LQ74elPjnJV/OjrecdR\nJF9X56AhBcRPyoDdLDkcY2UUANkxhTgUmHanmxr6PGZu6suemgG+EykdLhrh\n7XNVqDyKAjm6j5WvCNUC5GaYbmC2xou+YZRvjaIthdnYqQF7Jy77AKTep5Fa\nQqJLj711aBO2t8iY+4mRcoswQrvaCYg6N6y33nb4hjlVjIZhMRze5z3zvXhX\nyv1dK7Yuw5wATqskUXYUMy4n7epMHbCQ+5ihy9FHamsX2FsENWOfxhFVblRE\n6zocZ3rRiTmeFLcfEYGHmIVvw+2D1xshEGPN5O6zRdx4F2uJivlvK5nHYkys\nwDqtt6cnrKcVoAmKh1KBeFcOQsn0ybJwpme+bHx8x6pWU3zWajPy0UjrZFtW\nMv4Z\r\n=A+l4\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"91069fd5cd4d9f8332a2e2e004a5c702e9e305df","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.1","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.6","@balena/jellyfish-logger":"^2.1.25","@balena/jellyfish-metrics":"^1.0.89","@balena/jellyfish-environment":"^4.1.3"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.122","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.80_1618477556999_0.6180401975699077","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.81-renovate-non-major-0199ca60b1cf4e1a6cd0b5946ef30103ee6cb6bc":{"name":"@balena/jellyfish-core","version":"2.12.81-renovate-non-major-0199ca60b1cf4e1a6cd0b5946ef30103ee6cb6bc","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.81-renovate-non-major-0199ca60b1cf4e1a6cd0b5946ef30103ee6cb6bc","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"000155a1ffaa8da82e269f7c04a661444cc7f405","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.81-renovate-non-major-0199ca60b1cf4e1a6cd0b5946ef30103ee6cb6bc.tgz","fileCount":99,"integrity":"sha512-bNq2UX6u5Gxfg/m8r1Z59NVpops5CuCucE2Wo++UiMAw9Lj6II/7NhOkRmuWqXsXsXCXlSLm8W/pW5BsiJgU2Q==","signatures":[{"sig":"MEYCIQDsAzhvgNtxpNrP8Y7LruTm/8KL6yTFH1Rlz/Pyl31MXgIhAMYCN2nsPuHuQVGV2msQiLPoudqYHbTPj+kH7QfuhCSa","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":693223,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgeCS+CRA9TVsSAnZWagAAQf4P/R1nszhKCH/zWcEVb6Oa\nRp8CKol49cAAHyXWhXiNuqJJRK1ujoJHNjRazxfCFAcDUMA2MsiW5kKfXPdE\nn1vJPKdDDNI0LB0tYIFgXsdJFX6I+6v1Y9IusqC+8ww+F339YwsZXx6MKvbH\nKOR+EHPbGH4fLw8uokqK2vIUssL8suUfKeyPbW+/uBEdk/38VObQt52CHD3P\nCnOfJlsizo3FUtr06cOLj1/325DlbInvyH1k/eHB0iPrvtHijwGFsPxhJ+cx\n+s3efC0gH1iDv1kDbIzDP+63/0A/93QgW7EvnyuUYIIgy8YRph88kRW0aaaE\nisPChRf5TEjFzAQjt9qsYnXI3FbV/34vg0/kAblq+oWA2uhrGMJ3pchGteoL\nBtg+9lSdOlHmjo/9xrHF4zZDVgWHYSkOUGuJ/0Tqbxu9LZf3cXXdifF0y+ZV\n++3BmJo44d/MrV92lEUBbVciJkIyr1Z8wAzK1KNKY/1bsRHRrlbB0BXPogHa\nQ22aTtKck9792eGCp6+zK8oufG+bTTdtvu04t1YsOGJfXqZnNc4vWyqpI+v4\nWF1FGTsoPf6lR5CUxxov+bOdYTusucEgoMOI6R0WJvbJxMGY3/fDiUh764E1\n6/EWdE09Qah7WfNHUPdpCpqhThx5xeYum04pyL3X5P7SLZQOjqkseumLimIt\nRy7j\r\n=/KEC\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0199ca60b1cf4e1a6cd0b5946ef30103ee6cb6bc","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.1","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.6","@balena/jellyfish-logger":"^2.1.26","@balena/jellyfish-metrics":"^1.0.90","@balena/jellyfish-environment":"^4.1.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.122","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.81-renovate-non-major-0199ca60b1cf4e1a6cd0b5946ef30103ee6cb6bc_1618486461557_0.03686201863583838","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.81":{"name":"@balena/jellyfish-core","version":"2.12.81","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.81","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4df6737ad0fab8df9b25b2bb1c34d7cc614284b8","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.81.tgz","fileCount":99,"integrity":"sha512-HiD+brlLkHHQeSd4CKGpDbf7RT+KJ8Tz5x24E4nMbuCZoIjby2ap70h5NM4XhFzKwN1ySUnAn6rO6OlApGt1SQ==","signatures":[{"sig":"MEYCIQCcMRwXIfSbSYzlfH4M+5/U7CHvi5tTf5aNqLCxceH+QQIhALqIlOPhkxC+NCAh1VBaufKQmhQ/xEjPBEoLZ2QWY2YU","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":693163,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgeCZ1CRA9TVsSAnZWagAA6PIP/iy4I5EVkVbfnPRR92RP\n8NsmiGbFhgWFEcSqSDIhtSSx9lvXdnzmH2uN5GhGk88ij7MMPKycdFf5Ry05\nZW/m6m5H7fIU4jWK2xcR6WuZD9Wvmb7t+/IowoYEe9yja1OyrD7usIwzuQls\nh4yViJR0S++tJU/TTaDpNl65BNXswPNpGlz6giZhWmOEtcyjROe1aAlvPQ4v\nrKvFR/bC2IlJwb32hFCoiRwI/5GSAaqL+Quuezx6w8gN0vUT0T2RPes2aMFW\n6+ZjaCG0ATBGi0BjyEhcZlo+Lv88YM/v+/3wj2kY57PJdCP5z6h4j5aGnrFn\nvd2R1L8XGm3jJtVV5Qu8emFV26U5u3NsYJ1zyojorYyD/c5WqT7cVfHIiidG\nxOVXDBNBLoxieBwWl5Safdqa5w8bfVYf25efUHu14HAC/YvTYgUJsjiYD6gH\n+10R8naSXeRGyW42+SgnvW+MmIxAIPZ+9Fb3Xt5TCL0eQBamlALMgwcoupIo\nrvWOXAVYBErX0Hvk/8KCyoNirqI2HCL/k9qvlmmK8MBxMrziDiS7nvqA6Xf7\nmhrbYvOI5lDAcuDbD3CMJIhuKMRnRNzCK++MkoYzKdW6Wp+CeLXp/7HoKxoL\nQBtoonT5luJ6DHotXBrsPImnKIsndTeOARiGnJOZY6guCcniSpt0A7zVkXpM\nM3DG\r\n=by2B\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2ad5c82b0dc490cfa51f0e17652f090d08e09514","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.1","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.6","@balena/jellyfish-logger":"^2.1.26","@balena/jellyfish-metrics":"^1.0.90","@balena/jellyfish-environment":"^4.1.3"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.122","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.81_1618486901087_0.6323166553831012","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.82-renovate-non-major-6a531ef2f5e0cf8c4e92bcd24cea9e6fbaa244fd":{"name":"@balena/jellyfish-core","version":"2.12.82-renovate-non-major-6a531ef2f5e0cf8c4e92bcd24cea9e6fbaa244fd","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.82-renovate-non-major-6a531ef2f5e0cf8c4e92bcd24cea9e6fbaa244fd","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8f2b08e66430c616da54c8769e5a954783c271db","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.82-renovate-non-major-6a531ef2f5e0cf8c4e92bcd24cea9e6fbaa244fd.tgz","fileCount":99,"integrity":"sha512-UvYFFLsyzfkoSnEnwrU3ChEz5zlHnhvG5ZokPVyLPDo0/tbQk/PSqPwOMPf7A4DZKbo5l3vCPmgpwqP/rqJJGA==","signatures":[{"sig":"MEQCIEHJQu7hXN/AuLXuDkRphWK07aWExmhh+fsp+gqFGVJCAiB42dfRv6lYgxc39FgRQZ+bQ66PHI/9l9kMOVXgTXROQg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":693650,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgeF2LCRA9TVsSAnZWagAATkcP/3Z6PeinsGSqtULVA7Y7\n8klUmoCREPXNB0EAgN6gPOBer8GeImPNtsK9lOJHn+Io5gkj2Nvx7zp13j8I\n++XHOqWWrtx4vabfx3tCKYy1VMUhNme4OSBEc3h2C5XXZEvqgKHbjkMfvKpb\nDWXXaq3fNygmlzP9aKiDHhT01g9efcP/wos5s7lZWNRsFAMPvYnbXEoVWh9A\nuAVVoOqmmkmEjDklUttrEupopLHCl6SfX9ca5Zle/E6PUmV9EPIjab6jLYcn\nCsnA1XMCc6uTIucoFlYfwkep8eCVF/Iw+CMWroMs/2rCPe2cgB06MmHR/HAZ\nsCWIk+pzBTZys/DZm0SNNHFe2vOxNo93sVrADIx5lGV0B4jaqRpM8e0uLJE9\nPw+5TiqVmR3u6++vdGz0jWj9HKelNYb3J5pyxWDjxudqeS98JZ0U3fK6r9f+\nOVC4STPGQqvoK4b0e47Usj2/QcGAGfX7NFgH+kFWdJWqMSTPaQbriY4K/y34\nJQAHi+ImWWbwlXHwu39Bbbe02H4X8i4RggYe0UlwB7JPSr7G9/fUmIQTNEaF\nX8GJkBIb5qqGzJuM+nlPMM5Wz+vhoNrGbBDuCfoWeKYwdbjAEZOVxRlaZeDW\n4oARiYE5dmZW6ikZNyc6m6bmBJp8Djft81wT/taqv1od/vmK46aKOTA1o44m\nMu9h\r\n=OSoG\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6a531ef2f5e0cf8c4e92bcd24cea9e6fbaa244fd","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.1","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.6","@balena/jellyfish-logger":"^2.1.26","@balena/jellyfish-metrics":"^1.0.91","@balena/jellyfish-environment":"^4.1.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.122","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.82-renovate-non-major-6a531ef2f5e0cf8c4e92bcd24cea9e6fbaa244fd_1618501003360_0.12239343444695816","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.82":{"name":"@balena/jellyfish-core","version":"2.12.82","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.82","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5b45b689cf5989e8f99fc1c49a5d8adc97dc090d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.82.tgz","fileCount":99,"integrity":"sha512-lo1UARz/4+L24zYwfe1vzCTVtsnXxBwol/2b1ZyaZQfMBjgLefV5bJ23MHsDSoSXwF0ZWPlbg4dc8+QX4Dnkrw==","signatures":[{"sig":"MEQCIE0KDljnWRJVd7AAm2GlZLUSyBEn4fr/7zN5hUb7T+cMAiAS7TW93frTmNd1Qx79gaviHu5KhsXT+EM8Lbh73I09OQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":693590,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgeGD0CRA9TVsSAnZWagAABegP/jv6pqWvMmiirQ1G+Jp+\nPHLZnF0/YcuSAQajRxg7edPyuDaCnEpiJa8QVLeLhZHFV9NdoRYo8dkEq5+D\nmh2FDulzgGSlo6eqdKS+j0fzVPc5DmvsDoFZ19F3z+h8fpsOc3BzAZ42JVvz\nFkObOlzibFrn0XX36KMKUXp/w9jg3J0H3lFR1OSZ6f+fPIfvT+ViTn5dsYkT\nOXdz5NPjAqWpjaFL8TFXTpj05NwTNLifPihc5dxbrlQycJMSg/cMtJD7D7p+\ngYQ2Z68/+ffLSHVgaHyA3K7xuMitRgqP1yj3ZWNQtqkCSedhbunab1D9DsqY\n3/XPAFpFNP7d2dc13CldV1ebl5PhoX9IT/UmdQjtfJ4wWMzn/6PyyfGAIX2I\nKt5lvBb215Jb0EAROL2esn/06FzmEZz7q0/HrX23bTtPVLhtPMlYslQKopTq\n0hCgQK5wAFgGImvQ7G/3UaKQmBPR6bwZPAAB3Lr+BBXXyfPoBDxQXuEz/uWf\n+cDiviypIt7CQuBVQkcYu1dyb99pA0vYc2K5TKl32khaTJYnqjVUFGtWYnsL\nFshgIjCCknqLtGxF4RPj1aOU76Y5sOyNq1oZqNo3zhCZ70pElT7KdwIRTgxs\nA1eb8D5GP5b1RokC7rHDSlQJ8DYQ+x9psvVUshRgNgIV4cS4DwYLBYIN4Vky\nAwzi\r\n=TZOJ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"de203c82c7e70133b311f8bfa2b57b33b46dbb9f","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.1","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.6","@balena/jellyfish-logger":"^2.1.26","@balena/jellyfish-metrics":"^1.0.91","@balena/jellyfish-environment":"^4.1.3"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.122","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.82_1618501876388_0.08885302843293275","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.83-renovate-non-major-42447d82d5da22a867ceeed9b7a2da66c1f92d94":{"name":"@balena/jellyfish-core","version":"2.12.83-renovate-non-major-42447d82d5da22a867ceeed9b7a2da66c1f92d94","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.83-renovate-non-major-42447d82d5da22a867ceeed9b7a2da66c1f92d94","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f3f45fbfd3cabe8133cdbc3eb72b7c5b085aa378","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.83-renovate-non-major-42447d82d5da22a867ceeed9b7a2da66c1f92d94.tgz","fileCount":99,"integrity":"sha512-T1JX+v3Xq2KwzZl16TFJdJKqF7FUKvzouDPD/6SUZXbnaOt/plHXVlgx73H+8G98zuDrv/vxiotKoQ9flujs+A==","signatures":[{"sig":"MEUCIQD3XC9JaZy04CsyeDdecJ1XHrlz50W5954nOahqAJ+nywIgcsva9yAbVMe8kETzrVBr1NRyGfSi602Px2uZRK5pPoc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":694073,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgeHmYCRA9TVsSAnZWagAAzmEP/2PUlGRvUZzJKZ0RHhvM\nwbRu0IweJHq/BmBDrW/FfZ9J/oCuwYu+UrZXHC/h2MF0t4wvkGZpvME8krB0\nNRQBV9QHBHNHrNj643wgSIF60TIs1SHT4z28s9Ibnf/ReNsDqmtbaxewwAar\nFj05wQsN6bLBG8gqwnFzo4A5TvfNWgksayO+H3SjTnrQ/ixECq2JV5/UMsSZ\ncrkdoPbFbxuYss6xvu2lT7MhpiejLkCzLq6/Lxla4Z4HiBokdqYYHPPNesh4\nc/CqmaX0vYj4KtsdJ4BPrcEhBeLzcmlIntMkFPjnarqFdYAgMKq1RHy+s+E2\nrk80utsvikHIRZnsIZLORuTbQ9F4+tzO384tpXubIhwHeh5m3x9V73UPMy1B\n1N2e5Ys5a7G8HoVEPpYCAoalS4eHGp61aNkfUXdD9us3V9g7VQccbQ7gW2Eq\nZ6aTTRd/K1+TbMDzCy6NOc3/nOJUI1wd/J113F9Eqndn5+6U3NYJc1ogrJkv\nY/tIrHFljaBJRMte22iWIKFBz+2Zn3sgVguEcXwpuS9hTqiTq+1OJIhbU39o\n0+EFVnwLCtkZoDqceHgwDgxvtBvl4575+XqK92tjMbSK1sYpSWCW8N1iet4i\nhOzohBp8aMs0+JhnyurHk09Dyw5KRGHrErO13P77pLg1FHTwv8vZYcRkXjMI\n0mXD\r\n=Lmjx\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"42447d82d5da22a867ceeed9b7a2da66c1f92d94","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.1","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.7","@balena/jellyfish-logger":"^2.1.26","@balena/jellyfish-metrics":"^1.0.91","@balena/jellyfish-environment":"^4.1.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.122","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.83-renovate-non-major-42447d82d5da22a867ceeed9b7a2da66c1f92d94_1618508183915_0.9230668508577589","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.83-renovate-non-major-929f749282caf5fb3b593a643a3d7ee73d64a4f3":{"name":"@balena/jellyfish-core","version":"2.12.83-renovate-non-major-929f749282caf5fb3b593a643a3d7ee73d64a4f3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.83-renovate-non-major-929f749282caf5fb3b593a643a3d7ee73d64a4f3","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"80f9fd467a286b7a3485e1cf9fca9543e0827b89","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.83-renovate-non-major-929f749282caf5fb3b593a643a3d7ee73d64a4f3.tgz","fileCount":99,"integrity":"sha512-oq602ThfyzwRfAJFcA4bMglUdKcbcrueWVWeLsqHn+ZFx/I0JTMS22I6eF2iF2jnRhyYNgrGcSWIFGWYKK+vjw==","signatures":[{"sig":"MEQCID+QKK45/ozoXyzwBmnduvAdRMXVvMZnoBgaMjvtSMAXAiBSB4WJl9ZGwSsLMlPXeVeFTmnkfpCqTXBg9caEvb/ZrQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":694001,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgeHrxCRA9TVsSAnZWagAA6tYQAJynVOxEQP7XifMEvfJ6\nS+I8GME53ZE7hLIrYMSXCTvM1i0ZBAVzl7juQ3rBF71pG0G6oeJe6EdiEf2Z\nqhuc8fv5SoAb7yKgbauG0ijE5O4PhlZKhOx6t7ib0DCcjW0XZX8s/zs5S0qk\nQgbIdgiEQava4aZslbbfIgA17SXBmxafZahI1up6NxhHV3G3CG3+0kXt9jw8\naTTPAUvjownHHjvzWqUmmudz7i2qf59Hx6BILCq2wsoI2Bhn5qK8oiynop3o\nf9QQdZ9B9MBQseDVjl0k4+MOLdQMXwtIkrJ72ezCNrPx9YqU+bYmsT29PbVG\njrO97Pd3fuJ+LnRkeNrBG4+S97EPR6t8vDsET/6FvsC1JM8HZjoM0d7XzlGr\nYOkNU+SFsp/of+4AoUBUtMnUjFLui6odTdOIFNNycEKSSF70N3b7N8weGCUr\nsKzmrZn7QYYyBtzWreP9rtmdtcM/1QnnvJF1KB2NUL244ZSb+szlpV7MgB6B\nHLD2nxFNUEQ5claFNycgPoQ1ItWkxa+LKpwh2a1LqfLbTW+uvRL5hbf32fq1\nR/XX0naObg/eSpuxrKCnjS5T4kmcx2B9iwuvviol/MZogG2do5TUpL0rQvdv\n9OQdWcdt2w6bC6i04XI8NNqfWR3Ij5scdl7jftbLyDBLwZFlF2kZg2afjBJE\n01rb\r\n=duJW\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"929f749282caf5fb3b593a643a3d7ee73d64a4f3","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.1","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.7","@balena/jellyfish-logger":"^2.1.26","@balena/jellyfish-metrics":"^1.0.92","@balena/jellyfish-environment":"^4.1.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.122","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.83-renovate-non-major-929f749282caf5fb3b593a643a3d7ee73d64a4f3_1618508528977_0.11143289803974121","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.83-renovate-non-major-fe1418128265eb239071ae9856d843535cb6770b":{"name":"@balena/jellyfish-core","version":"2.12.83-renovate-non-major-fe1418128265eb239071ae9856d843535cb6770b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.83-renovate-non-major-fe1418128265eb239071ae9856d843535cb6770b","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d7200c57af6d5469bc0778a47469350ee7bae3d4","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.83-renovate-non-major-fe1418128265eb239071ae9856d843535cb6770b.tgz","fileCount":99,"integrity":"sha512-60/QamVaKKJ7Nl3komHEe3mbbmksi7ekg7+3qJspVa3DSziHK9QK2eC3Wwum84LZPVTTwcYDj8GoUyg9UN9XkQ==","signatures":[{"sig":"MEYCIQC9gDfqYruXkV+GKHH8TBc3+ube6iqrGpNWWBB32vcceQIhAOWQJIUxjANCILUC1TiUanaYMLCPSjt6LYpYxWqDBsyH","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":694263,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgeKISCRA9TVsSAnZWagAAv+wQAKOttmG+dEnn1wrloxfa\n9ojNRQ1MjzMTDWoYkk1MHzMRIGnDK3HjrsRZl07B3ifsdPO00JvUnFi1bwfH\nLp7g9GULtX8YGTwzJNlyzcKbtdHeOaE6+Jwpw5yvT+JGTm27zi8bTQAafX2v\nsaiLJGaiO0WzFSs/zHbA4zY9uxk1Mrv+2K11GbxW3+++Oqg3oewXQfri9yNa\npr7rVh8FqDWM1zsVtRfgQTuu0zIOfdxT+JpPSMF6E0kxj2X0pjpE/sM7FMQ8\nJ9CrzT1oiK0EZElH+G4XmC9d5vU2tQsM1tjylMFs6uOqhOjaD9u2SPJiCsfI\nPUWpmEJ1izYHgey4fEyPs0i6igoqONRE1Do7vUv+JyDHXwDk8pGhxmURMGjg\ni/tjIP/H7vteshPA7Ja16K0Zd8N5kR48/q0l7t/sBQuo6dmtaUqvvZkdV9r6\n4suvuIYXpGrrmZXxKK21DO6BibrRd8zRc2koFXpC557uSnCbVAxxe8NDoWJV\nwoC+bdEE0pO5KqAJIlsFSgIM5GoUfIqThEiuDfJoFo6I79W0d7A68tS+PkMW\n2nn6EnKd+VuNQdlqxtNv/mYCC8V/UQXxPwh70T8dYLEo0URSF5z02gYq0W+e\nVJk+omt8PxMD8aYKnAhcmKoTZ8hiNu8ml7ophpwIRqhoqvjK1CcnTb5H/8xF\nJcG4\r\n=uVKl\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"fe1418128265eb239071ae9856d843535cb6770b","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.1","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.7","@balena/jellyfish-logger":"^2.1.26","@balena/jellyfish-metrics":"^1.0.94","@balena/jellyfish-environment":"^4.1.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.122","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.83-renovate-non-major-fe1418128265eb239071ae9856d843535cb6770b_1618518545730_0.7932339073294743","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.83":{"name":"@balena/jellyfish-core","version":"2.12.83","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.83","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9a3114013199d09178e96ef2c4e89f7b0acefdc7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.83.tgz","fileCount":99,"integrity":"sha512-bM+JzNUZx6nIPhDTysA4QkwMpFD06Ly5cOJbwN+rWSzaVNl4ZvRCitRq15PqT+7LhvtvwC/UYsIAX9EoUmdeXA==","signatures":[{"sig":"MEUCIQCEpd8jCRkfVbihm411NMVracMMGHOjI2PmvTsjiytkHgIgBJ/VQxZwB6fc5BRMG6H9KcWkU8mmmQ4342aY6lRaI70=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":694203,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgeKNyCRA9TVsSAnZWagAAhGEQAJcL4XJF2rJNiUFz6MMm\nzAEt6nDNwQSFM73Z3X3jcxD6r0Sdn93s4MvVhlKSyV0OziO5v8cRaKbRN2Sf\n1ll7vHVFSXCzPU/QXiV4FFfRK2Wl4Qa0RhsrT3kK5Yq51ITSK5rt/pY1TUSv\nLu20pJZ8kmb+soX49Pkm3jbQpBZFCum9vG7JmIi8vqAFkq5JiMrf8Xyk06fD\nfnwQ2b9brcJ1NOOhfSPe1gJrl7JnDVCDez4QCQYuj0YJu/X4oiMPsJw55yDt\na1hVGgcRLkJOV9BTjl77NOrec061HeZG4vTMmBeEmDLrHVKOntH82ICcnwZb\n9SVEr9xUa88GQUE69iajHl8riF1eXIl5I/ZshgzbHOUAdszG6hsyhIm4TyOL\nszNy039mBMRIjstrsGmErkenlcG5jO4i0ankt1oXR/MWu6ojO0dEXTNfszJf\nfyfvDjw158KBzFnOo/eSQ6McBr9046D+srwvfwd/fpQUEOr2As8NCJrMekhe\n+5gBwa4s/YppPgah+9qqD//pAWBNfX0+ovglAMw5AYYQET1JpWATz5PnxN/H\nQyC2BxFWoJhOL4VW38iYzfTyi84yw01KaFAqbRo/vef7Wws2HFc2LQ+CfCj/\ncGNHglL0llP+AHIvVd5b1yzCwm6pJokY3gQ/rVbgYn+jIz11h9T78ch0Yw5i\nnWHH\r\n=8RjX\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0f0615c3758966c607cdfdd8f71034a28537fea1","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.1","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.7","@balena/jellyfish-logger":"^2.1.26","@balena/jellyfish-metrics":"^1.0.94","@balena/jellyfish-environment":"^4.1.4"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.122","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.83_1618518897666_0.8963299950238455","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.84-renovate-non-major-970e1fa2878b9115f0bf2ef589c7e4b1bb797b78":{"name":"@balena/jellyfish-core","version":"2.12.84-renovate-non-major-970e1fa2878b9115f0bf2ef589c7e4b1bb797b78","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.84-renovate-non-major-970e1fa2878b9115f0bf2ef589c7e4b1bb797b78","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"7e8938c711ac8ca16e6c09f4188c16b5222aed1d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.84-renovate-non-major-970e1fa2878b9115f0bf2ef589c7e4b1bb797b78.tgz","fileCount":99,"integrity":"sha512-vhQ0zgNWN/FpTl76eCb4TgOci/ZEphFrhL/AlUR5LqTnwkwT1w+kkW3m4+J9yhtX68Ot8UAr0cLxlxWNBKJpgw==","signatures":[{"sig":"MEYCIQD8zr9ATBI+TcBLw3JZm1yurm6hMvoecgALrHA2moSnywIhAJKRtLVFUlEq0rYYb9mFpPtgILYsnXIhK+Jgw/UuT53i","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":694614,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgeOPhCRA9TVsSAnZWagAA5bsP/RQOLytFwClPxmbOqnJ/\nkI5AjGvahaeF2309HLXyLukrYTpxC+zCnLWwOykZ2PzVt9FXepzaWaiS0cRk\nsYNJwEPcdGpDrCuYbj4MYfaxqIesCChyE57jgYRKq8xrbPuu3EYSX8ZL3aQ9\nY+7773PmVpEmuoSWjIWeynUKIgu3QljxqZGxzWbsSh8dz50Ifjwn8LrjbaXs\nOJX9DMhJiSAY34ImNhJsEQD3qcqg3VbA4V3wIwFlfKUS46HGVofQHEiEkeSn\n9Xol6i6Wi+CWmPaUo6dVmJHYwWHWqm00jBk7GHlrdY1SpGkojs+jC8If0MNJ\n1T6OO5xzojVSIKroc19IlN5DEmCF+iNzFjPSEANtuoUeXeGG89ig2UzFklaa\nuZD1sOkbYCPRJ0E0LBWJfENUZ0YO4I/D2noU8KcNsLPTOO1oDCn3R5LetEE8\nEYlJnJxVuUtLybLGRMCAf9ZXffGUE9ly6uNrb8ZSyPlQHPrsEg9MVKjOqJcZ\nm1tjSt79+V03cVp28v94nJwVuSUyjVEepxE+G7DuErIhyfLvKUXpkiiA2LfF\ntaC4XYGEZTu/lVSwy+GNGzExS6tN6dVLzMG/YrC2uZKVOKG4tgUUi3LoLLEI\na65ZABmneIxB08OxZxL+b8pOT5j6x7Z8FN9ebB1F2nM+A9lsbgj2FoTarKeH\nTLG7\r\n=9LoL\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"970e1fa2878b9115f0bf2ef589c7e4b1bb797b78","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.1","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.7","@balena/jellyfish-logger":"^2.1.27","@balena/jellyfish-metrics":"^1.0.96","@balena/jellyfish-environment":"^4.1.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.122","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.84-renovate-non-major-970e1fa2878b9115f0bf2ef589c7e4b1bb797b78_1618535393052_0.3091074178523485","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.84-renovate-non-major-0c5610a3467c3534aac78368156483e94da109d1":{"name":"@balena/jellyfish-core","version":"2.12.84-renovate-non-major-0c5610a3467c3534aac78368156483e94da109d1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.84-renovate-non-major-0c5610a3467c3534aac78368156483e94da109d1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3765e2493ed48dbe232a4762e78a8719e6085020","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.84-renovate-non-major-0c5610a3467c3534aac78368156483e94da109d1.tgz","fileCount":99,"integrity":"sha512-iDk9IfSVTCcEajDsLKRKzJExnEj7rs9ghVK/+iyov7CiqWBv3iqO2UAew6gwKhmFs81TAfyN6w3ZEbhyqTVAZg==","signatures":[{"sig":"MEYCIQDRgHwyFjD1YDrJxIQBaPQbUjkKEWglrz3M6+XWcSBpuAIhAIdkdLjO18mwoA0AZYsQSfb99VCGWzsjkyyUfxD+cmAx","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":694614,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgeP21CRA9TVsSAnZWagAAK14P/jF86duMlgFnWrs2mTcJ\nLqDGmO8N4BBKcz8aaOAN7j6VODmzSYP83bleqWEE4UD2f/0SsK1w9XHvYF5S\nCvGQn7toenb0ZetJgUnQNzYU9POuPDbCb1wkCqN/mePofc5e9E/e/AuZLDPL\nt9VA5caJoC1I+s3skEmbmDSYdohYY0fwV5/PX3ialEopkqRiczzg1L5m9LZ5\ncCM55Gl8Uyw6E7kPdH2kAJ93fj9IirEjP4nG3dlwO9I0jMS6KEZctx/KpjRc\nCI8lsDDWBmbx8eizNza+lda+GjpGSVuP2sLL5DTnmgu0VMxPbjxD5R8IYT+C\ntovHNcM/GpMDyqNYVgtG0yLGNVTXVBnrJXew1kxEXE0GwiFouLH1+7XxnRev\ngNQOyXI2PT6oz4Hfpi/YI6ZK9WInHFCkYIEoXch7yBlhYOq65RQXdYAv5/rE\njaQPdv13pFpUHA5VnP1M3UyL1fQyxq2as3w7Q95I4OmyHNzuoQKOinMY/Cfh\nVBc9JBM/XVum3rp/T4GqzEvT8DS3j09N6wLfrCwIne16kn32ZVpPbUOBGNo9\nU2ZmhSl0mtRrKB7ZOByJwT5FkwRMHnEJiO0JrXiV23ZF7k/Wo+uNDb2jXOBg\njxUQ34iiWpiydEP2Rd+47nE40K0OFNgr1IU8OaCAiXK11Hm3wKRnvMZd8cvG\nEmjR\r\n=C2Yn\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0c5610a3467c3534aac78368156483e94da109d1","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.1","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.7","@balena/jellyfish-logger":"^2.1.27","@balena/jellyfish-metrics":"^1.0.97","@balena/jellyfish-environment":"^4.1.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.122","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.84-renovate-non-major-0c5610a3467c3534aac78368156483e94da109d1_1618542004977_0.09604622040705202","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.84-renovate-non-major-79d5fc0962c95dfda3536dafdf5e605ed2cfba3b":{"name":"@balena/jellyfish-core","version":"2.12.84-renovate-non-major-79d5fc0962c95dfda3536dafdf5e605ed2cfba3b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.84-renovate-non-major-79d5fc0962c95dfda3536dafdf5e605ed2cfba3b","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"867981fc75adf99fcdf79cf03d77f46c64b72b40","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.84-renovate-non-major-79d5fc0962c95dfda3536dafdf5e605ed2cfba3b.tgz","fileCount":99,"integrity":"sha512-O4BZf26hdUlz6/ckm3D5svtiHSwSXuTCSaylj98K6R6HD0IxtQ4K3OXNgzQ8SPosx20lvz3VoNYiBYUp1j4Tmg==","signatures":[{"sig":"MEUCIQCyiZDQA2ZjEcrk/hHf/he1v6kNpFi6VIo0KcsRosz/vwIgBI2jlLBo8WyjBl445Y/mdXxzjK4BY1cLpFGzYiI8atc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":694614,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgeQ39CRA9TVsSAnZWagAAB64P/RV4g2iYE3NRd9Cw6FGw\n/1uhQGY2JEahKtAPeoNq6AD0CukiYLebxXES4+9h+KhFBN7CenQ7Ryt8pXqZ\na2z2SR9c7S5f0sPFoyRU5tO8W1McGc1H6Ou+Tf4z6K4SLsekcGTQhxNvhoOV\nGfCAQ9JkDHbc/vsi43DyS5rrANjkwY/DgzTvDe7bpDEq6qGEEugK3oPzjLn2\nGLjOch2NYFB6nEssFmiiC/iz8M2LydoCJTuOt+osbX8Mw4ICzxtba/siFFGb\nBG+/P+bSk7zC7dc06GQHB/qHmLpw1HLJCy98ng/BRf2CvIOwBXTF7LUEa2Jd\nsDhrDDD2pAsPujLEJBvX5EnaqABakGlLMfZqqJ6/dhgdVdcfZKGoONOqX/c/\nc19kEXZSCnADfF2DK6byS8cWdg0yJS2kPhYMRBUB4ucH4FiGbPWT7ygUCJ76\nzeHhX5KDPcCTXZUI2sZQ5YD2M7o6TWpvqIha4Vau/q90IzZoo6c0Ow21z8ez\nWRaDzaIT9A6RjAJp1D6Eht/IzolVdwKl3N5XV5NZk31C/hhKG1jtlfJ5iZQ+\nuwDPkR5tyDp2/EBnQEBzA/q1AnPaY5olI2ESlEGY7YQzdEBVZv9SJ6O1+F6W\nv5hnnhK2MiLfDcbUUFtEe1yZx/WldzUIgPy60LvOiJvGjkM2k7FmNR+5VPiw\niMuf\r\n=FSTx\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"79d5fc0962c95dfda3536dafdf5e605ed2cfba3b","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.1","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.7","@balena/jellyfish-logger":"^2.1.27","@balena/jellyfish-metrics":"^1.0.98","@balena/jellyfish-environment":"^4.1.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.122","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.84-renovate-non-major-79d5fc0962c95dfda3536dafdf5e605ed2cfba3b_1618546173412_0.9904116902615021","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.84":{"name":"@balena/jellyfish-core","version":"2.12.84","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.84","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e6447cf924be9ba45d1887a4c9dd7492dffac906","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.84.tgz","fileCount":99,"integrity":"sha512-wE+XayAuaNxrl+TOl9fn5H6IOJwBdoQn133bGQz8WJrN9z73xkM6BSFjU5i/dUwzoWK/kIcIZYPMzbLrg8tK2g==","signatures":[{"sig":"MEUCIGigc22BD1xinLhYxqMTY6c/BLPKT3Pk+WvdY9cvvvwWAiEAsCLvoHXCAH0qFjaU0ivmPlM6p3zcGVtGdUH5C+8L8GQ=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":694554,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgeQ/WCRA9TVsSAnZWagAA6X8P+gNN10OOXqRABeMX6xv6\nv2YJPnOHbilT2ao8I4mNHcvciZVzkJbl5LyrvDy57OwIWT2j+3ITBAV5uZgR\nBA4UwoWMFqbCrDUknDl39RqJA/GEeX2hWbahXCNSjdjkz1jrB6fxgN+ra25j\nlcM+h0A7lWDSgYd7Rxl0nHZDyoaq/vNHIkfbysoeo7VkMwGwJxIeWNGOWt1J\nqtlAbo1kf6XTMB/tKXMB5B+gFK0QYAS+/Xqxeyn7IdAuEBo1NGfeTA3KiUm7\nEkGUIl3VoqSkszJoXTkdcez6y7yD72D4aj7lOP3nV+LIh+UW8igaE90NRIMS\n9VCOrfUPrIBikK/imCF93YSLfsJvmO3U6hFkKg5U2h+7c9hSFJaqEHtMcqFq\nBalVNtcnWe9QPBMv/NF8qPfqkaIseYKuo5c9g6b7ggUh1WA4zgM+bX4IHo5a\nF8wBJZien9mFTVf+yW60axSni/M7NyqkJyciWrgKifInmsWsga4H+edFTmox\n1Qc4FrguNEjkotaqQJR3kyF7NGr9VCqAeFhbkNed8qWELxQAarhZyk65dMKq\n3wo8263fpKlQ9xqXPUaxH+Rkg7SsqkTgFtO0tDFx0ZfpvkkpIongZNlhh08X\nYidNo1bQafMbg2CZwtHvSAChPkFQpy0nuBWMChpDG5R79qcHa0rjKK5sdEaU\nJKyn\r\n=EvJ6\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0842b6604dcdfd676e27a006df999c851a6b283e","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.1","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.7","@balena/jellyfish-logger":"^2.1.27","@balena/jellyfish-metrics":"^1.0.98","@balena/jellyfish-environment":"^4.1.4"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.122","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.84_1618546645585_0.629781165224244","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.85-renovate-non-major-9dfa6002f7368228de2371a06ead356c19865883":{"name":"@balena/jellyfish-core","version":"2.12.85-renovate-non-major-9dfa6002f7368228de2371a06ead356c19865883","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.85-renovate-non-major-9dfa6002f7368228de2371a06ead356c19865883","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"35e5b7fe3c2afb5c7f1bd79b66077199a014558b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.85-renovate-non-major-9dfa6002f7368228de2371a06ead356c19865883.tgz","fileCount":99,"integrity":"sha512-eO/AMIzPR+DS2eGd80MATXmHaTCT+q61xRCqDKzpV9lcogIMtUCwAqgTnhwyMtFFlWmsBmOuqLIzC49NQf0l3w==","signatures":[{"sig":"MEUCICSFFXfAkOLHsunWyQ2eE2lygFloz8JEGKJlcthoAFLfAiEA8+bg/xasW9AilSSoWoOisRL+dKiOov9/m/GQ+CH4lz8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":695041,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgeVDzCRA9TVsSAnZWagAAdGgP/jT0XnMtnFkZSek2Zb56\nbWowUCQFDmfYKm5FKsRHAH4vynGNply1oMeARjkQcVGnoXi+dEkbdQ31FFnD\nZ/dswNkis6kwuO423BhzBKC4OCKvpuepwVji1r5+8lde9kAGreQvHQcbCusv\n9TFHg/tqjnP9QuufXxID632uMS3fnm3bYnhnK+fMqjI8HdLJ1LOyFUrrNKsk\nH5X32Zq1j8F00YC2a/sG9xC7ZIC8UUiQ6Fwp9mXFhlm9lkHKpJuFozb+1F3h\n/hNZ/bJPj2MQqd/+zkCadcNhNkXimi8JLOPnXlnW8MtaXAA7WKS2BLS+YHr3\ng3mXcNRo5ED2zuM41KE6/NLruZOfR0XCATlWPq7K/7qfU7eI97YK+t3eTjeX\npL7zs228+Ox5zR6N8JQtBnQF2833lbuQcbPcX42WYjNqTje4BhWsdEWTgw1X\nEHloiv8qi8fPUctOfV9MXG6gw0PBxg1+yIkHOQDLK620Orwg+QjeWqtIT8sA\nh9VgBaJzyNXS8qBbDT0slE+fvOnuWWkQNXIMkdV++DfAhyl9OMGbOx5GWfjy\nP73Nf+b6SDciulrqnfN6Q0syH18ATYJNINry0vl7hS4s2waNaIzv988EP4ln\neY5ovmKja8yMTSd9yT+43wu345NRhnVDG443CHlymyUZ9q/Q3bH7CdrIZpt4\nNSct\r\n=uqz9\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"9dfa6002f7368228de2371a06ead356c19865883","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.1","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.7","@balena/jellyfish-logger":"^2.1.27","@balena/jellyfish-metrics":"^1.0.99","@balena/jellyfish-environment":"^4.1.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.122","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.85-renovate-non-major-9dfa6002f7368228de2371a06ead356c19865883_1618563314893_0.9561549507714482","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.85":{"name":"@balena/jellyfish-core","version":"2.12.85","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.85","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0bfac148578ba9866ca7a3ae89a235dbc38b268a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.85.tgz","fileCount":99,"integrity":"sha512-nx7MXUBv/Ff3SfAm2FoF1sjELx2wCiTZC2ByTVjKgtj40q3o4PeNKSUOF9kl4/tfz4rIba4AYTB7jwoe9zQjcQ==","signatures":[{"sig":"MEQCIQDn7+fgNJUDZKX4PXVtb/Ua+g8OCicf28/FjQyBlAni8gIfccTwd1mwaeEvG4zwyq89NjkbrwdAbha4OohRzQL0dA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":694981,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgeVI1CRA9TVsSAnZWagAAA18P/i4PvyciRh9H8IRonyms\nHE07XAHFlOj+EhkC4Zyh1k3IDxdd6O/OzUmChIob6S14hu4FEt8TMSu1ovwN\nUn/wTfUdCd3cZZgz0lxpPctAbVOcPdahWyXIvKU35Wc8bQJZQJEANu2MJHMI\nlZOEn0//AdKAuFLRet5NtnaPggYlHEQFKrhus7wiozNeXLXHNIWW+hk63wqs\nuFfqoQzpokTz4sbBQsfsiGpwDBI5v38DWmobcjeYgmqEESWWR27C23wHmaq2\nK7aGQ2yVTGMlaQoWr2O0zmXGD13CQyd0gG+ZN6BnsCRCi5WOZqVdneco5m4q\nuS3+ooH0bU7Ow4Wi+V1bVjHUSQMznlWXyAOy7CwAISTqOn+cs4StHWQXEwqa\n3Hs+l/wo1wKsCg3mOH4LdSwnLMjJLPKzwaDckcGVDhyhCuzdIiZj2Os9eJWm\nEYaLcM+HS9ZEKvYpOpibFE4GuG586n20A7i/kjoinq7dyNVfIa5kymr0n4gs\nqqXn8hQRK+WxlrFtT2bmHKzpcI8AA/Ihit45mTa/BMVfekNY+rHbGzetc1+c\n+ujg+fzLs9nUnzmSPZd5tjDQswTZARfQHlxQBZ2iepXSH1vXofaBriJCdThW\najlw//EY+sxNdz6de+nlX8Qeob6oIQDjrCVWfMA7HfyvK1ewBNeNn1UNpUcS\n8Wsn\r\n=ZMYk\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a0912a6847b3a312066957f5a6729fb033c90dfd","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.1","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.7","@balena/jellyfish-logger":"^2.1.27","@balena/jellyfish-metrics":"^1.0.99","@balena/jellyfish-environment":"^4.1.4"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.122","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.85_1618563636512_0.5660981913082166","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.86-renovate-non-major-3389eec065d4a599152d378815279f49eec250e5":{"name":"@balena/jellyfish-core","version":"2.12.86-renovate-non-major-3389eec065d4a599152d378815279f49eec250e5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.86-renovate-non-major-3389eec065d4a599152d378815279f49eec250e5","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"903400b22c4fe83d3364739a2c1eb1584d764540","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.86-renovate-non-major-3389eec065d4a599152d378815279f49eec250e5.tgz","fileCount":99,"integrity":"sha512-Dixo4y0TsirZ9UILOVV88xMPrRUUGddMd9mBwaio/b1QwNMsZT694xJm8By1XRwnOq5cDebZ2Mkk9q2mO50gkw==","signatures":[{"sig":"MEQCIA6AeuzT/LMGLBkSKxS4QJaHk0Jn+NHR+dSa2+CH1Wg/AiB3s4XBptWbV7dT7iMqqD9n2iPPbJEJDHS9LI3bIYs80w==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":695471,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgeXNbCRA9TVsSAnZWagAAntwP/3mhL+pg0zJB8ISHbmt9\nDD6iqq1ClCrSHfh4YRea/t8PcK1zFDP1L+bnPxgVus8G9qaoBoAsg18EwruP\nZe3vt5p2GSvq+zRvPRr1rVkfFxKlrYVwKRXhYUeiraqkZzXlIB1AbPH4kW9W\nzOlHjYXhTbFf1mr5iDUPak8o3tWEypekHS9GzOOGY0YHKF/LxLi52t3MkkQr\nPu8/IE3EhPj/kVcxnBbhQZKioUihNebjX4Imt6gajiRMLSRPOnCxwcUKuEeG\nDCLKAAOKj+4+O8Z2Qi0F5JQieQDwVYrKYwGW5EitWzhjB6Unc7jPcIltH8ew\nBKgVy11J+KOjIi1jThlDop2WLHDSLYzU7DQ15Cgq2W1bhxFCnH+p78k36y8T\nWzYgMJcCpJkqi+af+nhZDZ8Xp9qB0ZU9GRADpOc2dT/meNMgjZ5qKIkHkQ7j\nHkkjunpd3R960XTuvnkKHtEmyIEifuAY0rtFYeEDj65QBvWUOnV23BsYwpSY\nGAMpbN7R/cNnU4GxbCd6hP8Nhk2/b0ZccYvkSGB5zZl1iu33ERE/7GRFiDvI\nEVdprK/ZQ0p3DBDINiN6muXRNCu/VfOIUbvxSHwUAiXcGe7mkAZqrqI7Hkuq\n5FXchhPmWBciJeFdYYHz0jLbTJSpwIe/OAWkyVQqO94aEr8MQy4FkusYWdBz\nc1lQ\r\n=eQpB\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"3389eec065d4a599152d378815279f49eec250e5","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.1","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.7","@balena/jellyfish-logger":"^2.1.27","@balena/jellyfish-metrics":"^1.0.100","@balena/jellyfish-environment":"^4.1.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.122","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.86-renovate-non-major-3389eec065d4a599152d378815279f49eec250e5_1618572122550_0.2732083749433962","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.86":{"name":"@balena/jellyfish-core","version":"2.12.86","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.86","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"36cac6aae92c52f0fb3ba4ea385c911c05beb613","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.86.tgz","fileCount":99,"integrity":"sha512-DWpI+Zg9b9GMcd/ESN9v1CvL4jQ8tJ6eqNd9ZRFZ4JquLnSNvpP6Ehm5YUYDjmnS42si5OSc7Bclnw+C8iaHwg==","signatures":[{"sig":"MEQCIDx208NibeH0kD5gsWlrRpbvur2e+La8VUPKrMj/s4bYAiBPipSx2C4ppZxyITJOF3Zxpvs7tAWqMQpGkUPisUEHPA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":695411,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgeXUUCRA9TVsSAnZWagAAaLcP/RPMFkgd1Dvons8rKqmI\nacjslMCiVEIVcqPLk56SRAUH5aU9CQhGc2Glqqp/x7mc8X4lEVm38FKNwKQW\nr5aCUzOj19OWZnGopN8Hrl9h1O+DBnBIpeRrFLmD5T4N1B3JEzH/j0p3sR+p\ns5gBMGzsuPUFv8SFv8YI8ysAuh2XLdTyuzvto/+W9yDBMv9G9tRB6npm/Qwh\nX63D3KWHdrXLNMPawjCx+MCUe5/0yJElmJPdh9I4YCMFc3Hwn+2e7Xblb/8D\nep2+o/i+Pl5aDe/KR4QoFmuWJJVk2w4/VaL3BCXcJ9yA3A7cyusZIckne2Zl\nPBTABaySb/ukQIv3v9+VW/opM12HxVZjMcizsmCSmUjanquxIZXjmwK/Q4MK\n/8WNoE/O+tIIUSEYF7v7p8Po21dbaV9HP2G/XFg/TKYOZrgR2f0pbJd9dnRo\n2NTLT6tN4XmpU6g8p+UrvM6yv8jvbmhxiuQ6e2mRNSu1DkMObXL29X4ey05r\nTTpxp2MpaLWOlmhe02EZKipN7iUnnYAJWiGwo+79FGVX55wBFAvt2jSpnE8J\nWZO4qugc4jox/HzbhuQoaoO5JQmo7MG4qugrCLWF4m6J+EDBrqoKorC7Ob/X\ndKQSz09gTkU3uWdeyjeSUPFyx2QelSYsesa3S3+jsVH3h/OktVaxCqtxZjqR\nYkp/\r\n=1L6n\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"60269bfc958e4f31b695ead80a802a0d36fab1af","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.1","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.7","@balena/jellyfish-logger":"^2.1.27","@balena/jellyfish-metrics":"^1.0.100","@balena/jellyfish-environment":"^4.1.4"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.122","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.86_1618572564037_0.39359775105257255","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.87-renovate-non-major-4ceeb380db496f3ba544b41cc2c73f89be6d84e3":{"name":"@balena/jellyfish-core","version":"2.12.87-renovate-non-major-4ceeb380db496f3ba544b41cc2c73f89be6d84e3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.87-renovate-non-major-4ceeb380db496f3ba544b41cc2c73f89be6d84e3","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"79d5388ab2e815884d14af6fcf67a7f9bfea9c37","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.87-renovate-non-major-4ceeb380db496f3ba544b41cc2c73f89be6d84e3.tgz","fileCount":99,"integrity":"sha512-M1KiMsyjeJgTSs4i4StpXIfRwYP2kMxwrea/SZ7eC+OyLhx2MbuKNcQowszI7G4sxe3GLQ8wZk+9fbHfxc9n9A==","signatures":[{"sig":"MEUCICTnVcFHficQT+WsBNzyYpehi6l8A3/SBjuF5cecTVcGAiEAi89mql2/Hylbk4oVWoSvdA7CFT2Rt1Yo3qdyeUHvv+4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":695900,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgeZvdCRA9TVsSAnZWagAAZ6oP/A73k2oNoCv0LplDvAac\nedfh4M9hhzGb99/pVn07aj2Qt7la839a33M10uPiqsBKxxEC3ouTLSWSwmNN\n4Yg+6tg2xkiPZWk0beaCzn+SGv6ldHHOQBpD11alt1phM5sACvzDKcOStrOS\nqlmsnEt5jSYBwyD9TMQXQE6PtQBCM21hVTlC3r1NazT2CbnRh1hvpirzvkKQ\nO9UXPI218YHleCeuBIqV2YbJB7CjoQ3O4FEkn2gpXmQQVzXYbdM3pHcz7bgh\nvHE65mYuH/uNI2K3NDkEcF2tCySvIfWdP0VSlxUu+D2lVAm4PcD8ueDVS0vK\nA1UrjWjqvgEyMBQEwAqTAtGv/trQaMYFxeZrn3TofdxcP1EPU1Y3T21p6Gto\nuCxwpkAk4pgsXKOqO0JUwocy7ORdT66gE+No6f6tZBZj0Mm+J663XUehYw4N\nW144C7Ud8AiDxIFdnWIJXezLJjvudvyPGoq8Q9+Lu7tBgT6lNZUJYudOdFEp\nLm98tQisUijynPcW31x4AbpVujoh2vWLO4g2d1Uqh8zN/aBkzeJrc42HMLjA\nrJHAL/Ao1Ww69Tc/pzWergYESe7Kz/t+gl3Azt0OvYYhgA1JjmzzD9VCzXas\n480//iH8pTXPHPPreBADrAzERI9/oDRJyd+ARJC9qoKJ8eZ7aqUT2kAgV71x\nudnp\r\n=ZKAM\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4ceeb380db496f3ba544b41cc2c73f89be6d84e3","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.1","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.7","@balena/jellyfish-logger":"^2.1.27","@balena/jellyfish-metrics":"^1.0.101","@balena/jellyfish-environment":"^4.1.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.122","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.87-renovate-non-major-4ceeb380db496f3ba544b41cc2c73f89be6d84e3_1618582493241_0.34696414746181925","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.87":{"name":"@balena/jellyfish-core","version":"2.12.87","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.87","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"07fc491443c973e520893c28eda894332c04e246","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.87.tgz","fileCount":99,"integrity":"sha512-Ln1iynNB34sFWTm6Rv28xVN9Zg5BBlodEb8e0tF9+YbDad0DpW9UDVgn/DQCjHj9DpCybo2srp0xMXk/WA2Anw==","signatures":[{"sig":"MEYCIQC+F15dFT1et20hygOeNXwPgR7bT8rOm6zkfZ7kaNNrGAIhAJGWGFbmRZEkncrDENaR6f12rQqpYKTD4hE0tygCQ7oB","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":695840,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgeZ4VCRA9TVsSAnZWagAAhN0P+wXO3x8MaOTxyr8WYvYs\nobjJuPNbz1LWwZsN6zbKJvyJp/bgZlPi1gdR1WUp49FIFj81L3G1ZIi4n35k\nrAAMRgboiWNhC1iwjzm6U3B6fPFZw/8amacbo0hmNtH/KKSsdNElJyKfOxbt\nJS8C3UExXoX7r0tarpTBbA2XJuk/phn/Hnq0Lr5sE3YbqRTDzqwAJgIv4Xz6\n2vEC3hAtu+ptcaqRueRKArj0zF6k2CwVf1TQJTozvGtpO442MyI7mE+wQYM+\n1tU8/euhw/I58G+AUssn825UR6geMn3i/ACaba1tthflysuAFLbphwY/5nez\n7o4syX3C8jIfjTbwAp46iMjq+maoHgRCZFw4UfVUUDi6X4Hwf51CsBxDN1FC\n5zt/5D0TogU6/tq8F6qF0ZpY8z4qLVrczTMhIs/x1VbIxACdRP9qUxqVT2un\nWrWQCWG4aBeb9dg2yp3rOR6FM5xvt20IVsBtTfkxkgMzmQwLLF2FS4oULwG/\nDL/ZcTpC4FaSjdVrwmaBGj07JJ/TOJEPn4IMH9L5MkltZDvixDfwotFhM5An\nm61x1qCubX9sO+K5tTiOxrjXJl1WHrnUZuG3h2VlOCeWcrkfYJZ4/kIeEuwk\nAqMmgngR3w1jIz6QbBkyFgyRzvJTYf8B5YstPXftFetO4vz3hCc5PEeTAVw2\n1KsO\r\n=wZ3n\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"089336ba7ba41eabbbf0af9ae8e7baf1ff0772fb","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.1","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.7","@balena/jellyfish-logger":"^2.1.27","@balena/jellyfish-metrics":"^1.0.101","@balena/jellyfish-environment":"^4.1.4"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.122","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.87_1618583060778_0.0005267890858664526","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.88-renovate-non-major-56c989b6cbdb9fdd6e3631ecd8616198ab601761":{"name":"@balena/jellyfish-core","version":"2.12.88-renovate-non-major-56c989b6cbdb9fdd6e3631ecd8616198ab601761","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.88-renovate-non-major-56c989b6cbdb9fdd6e3631ecd8616198ab601761","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"067115c8eeaf36dffb6ca20682bbe69ac61f74f7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.88-renovate-non-major-56c989b6cbdb9fdd6e3631ecd8616198ab601761.tgz","fileCount":99,"integrity":"sha512-WHfgEghTb3C1plj/Lpk/q9ANydFjlUqesimaQTKDagl8Q97clnHBNsaKtKdNUbaCK0wLgzuZCzFidOl5ZItjsA==","signatures":[{"sig":"MEYCIQD8z6A0OWZT3pOheug2Ro3ZWLtlesyqBSgszM/BMp1jjAIhANn+Wqw99ACzsbkKTgimYcyS+qXNjMo9oW54kA3VqVcq","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":696329,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgecgsCRA9TVsSAnZWagAA1aUP/3XrQVO9cJX9uNpfczM9\nPh6ec3jYfaHjZcWkNdsOxrNuSXOFXU2MjjVE2ar5bWQppU3WXpvrdEY4DYTJ\nC2pmrtVNCzkogdDoM0xiTA29nbgxjn/vPc7YjtFaGjcOeyJFPUPDa02qHwrY\nleI2R5dbOP5g73eR92a7OSEKCBnfSfiUayUrVOsFRlEu4YEDJssccMTqwibO\nNNRGydbQeaGaS2eO3XZTj3baKnBmIuH9CD+uY+7iTSxdQb1u9YJ42ilKgRke\nOKP9ScO+4v1L/MPoea6me5oFm0ALp2eDb17Wiup2P0Q2wEHw4zw8fFrEKAhv\nZsryYagd8O/ZW+3vZmRGuQkFuNMUXMx2kqQw/OJ+P/VKnTMaWPwCIgyGX0/q\nH/DLEjlKxYeqhCTm3OoQk1immzrbllfV4hjVLKXoRGZpFfRmQ1pKc7s9Z/da\nYVMD5D7FAXPvHzEABZHwMtDD0DI8oE6Q4a3yVQF4xYeHcFbjdAYwTjD7jgzG\n1anLNg/gEHKCuE0H39UC8s13pqIiKqSecCMjZU8+NeehQRlokoOBxzwON21H\nK2G6x0jtHs7Q4Ms739746eAQKgyagYr/ccB384hhsAvZhKhDteBFz/vz9Mh6\nJlFQ8Wmmht0V+Cik5TVLSS67h31Lb5DJUXwAm0L1nH5u8ffmMXf5m1xov03S\nMaLD\r\n=Ra9A\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"56c989b6cbdb9fdd6e3631ecd8616198ab601761","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.1","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.7","@balena/jellyfish-logger":"^2.1.27","@balena/jellyfish-metrics":"^1.0.102","@balena/jellyfish-environment":"^4.1.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.122","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.88-renovate-non-major-56c989b6cbdb9fdd6e3631ecd8616198ab601761_1618593836216_0.2828645060873203","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.88":{"name":"@balena/jellyfish-core","version":"2.12.88","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.88","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"19a8577b28a34dc72cb8b8d441c0b7f1c00a71b8","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.88.tgz","fileCount":99,"integrity":"sha512-UeFZ7nALsm/BeTeM5I+6OzqhiGtXr5GHnw7vuYEwxnsUCapxhVz1rCAmiepGsKQmd3Kz21ZCDuoIOo45Fn2O6Q==","signatures":[{"sig":"MEQCIGZHY10L6OhW0eeB5x9rJkA1ELJ2Iy0wnxdnfm39dywfAiA7657HucAdhULdDJCeGA1csC3iAz4es/AoEO07FIwY9A==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":696269,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgeclpCRA9TVsSAnZWagAALtMQAIwJ8hM1vfItkFoFXRJZ\nW0/ZjS6u9Gm0PHBTwTFQRvDwMH7JTjqHEWFz9O+HHL3sjvWcSMNWVTMiz9Dq\nqzol7oYuBNGUDjsrNF3Pp+yMQ9BJpgQn3mtWIdAbGfNuhEQG6wXNlQrWidqc\nTIZn8yoCXPNHN/MAk24VX6hJXfUbiBv3eoe2lh8azdmV45Un5RvPQZKlWFYr\nGLVOIHuTTr445OrEx4jAmnEO3nepDUIPcwqzxd+UxN9pthVlvKNOLw7R3y/G\nRTTJBAUtWeB+9DvOZgfEDuMVBx/XDpGujJeSjdgwO1L+h3rgQSnDVDyU18aA\nfW1cLuZryIYU7Doao8M1NoTCKjmQ48PyuVVJyNbv+e+1UBRA3torvOR/cI1q\nJAfqXF5k6cjSK/MIscbcX4m+od3qaWRP27wp0d4viTzVv1H1fmosdEud3Fpr\nB55jaHw+OWjWwqrgBfqpOtZSfzRk+I1fbV2QD+mF4nXfAi/gkVdwg65Chr/t\nb0/zTJqL1Z6Q70SN+8s2cmR6JMQLyOOZm5VYqsnAHRD+PS4+CgCc4pZZdclS\nSqzUWT6K8pApf2DnGPag6U9kaAmq3x9v0YoAWR9Jifz/u/yRFryYIw4Dp5zV\nYM9SDCS/8ZNKvMvNJhSi1p61YMmNoOiOpz2lRTLCCg21ujrM7y+3kfvTl+tH\nW+37\r\n=I5kY\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"48e7faf1a6c18ca598b1e80aab449e6370403d33","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.1","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.7","@balena/jellyfish-logger":"^2.1.27","@balena/jellyfish-metrics":"^1.0.102","@balena/jellyfish-environment":"^4.1.4"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.122","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.88_1618594152900_0.21658585545363906","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.89-renovate-non-major-c8dcef8a6f26217f5c1db842cfbc6d80a59a38dc":{"name":"@balena/jellyfish-core","version":"2.12.89-renovate-non-major-c8dcef8a6f26217f5c1db842cfbc6d80a59a38dc","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.89-renovate-non-major-c8dcef8a6f26217f5c1db842cfbc6d80a59a38dc","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"7b82dd57c5d6f2dc7f4baf5f4fedcd82054d872c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.89-renovate-non-major-c8dcef8a6f26217f5c1db842cfbc6d80a59a38dc.tgz","fileCount":99,"integrity":"sha512-9NDq4NPOMQyFdQFPWGCsvmFQDP6VC2VRG13sTbVLsGoZ+j8Osx9nSTw/fipVjY73mfdGPGWBCtSxLG6fVN5czg==","signatures":[{"sig":"MEQCIDpDExOZmdguhUXhm/brp1u54vmRB43e5Ix7yo9m6+qEAiBsf/LI3jMfNuC/WuQyZr82ZOGK62dd8Jr3cKnOhmrvIg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":696758,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgefQaCRA9TVsSAnZWagAA+EUP/R8i1G57mB0COBBosy3M\nAr6XPqwNRtQ/81nNC8pfb2y+EY9WgaD0yjrZ7XtmQgQ+ivS1s7we9jW7/TOH\nw31FTP0oj+60MYo7TGvZzuI5utxngr8YbagnE0u105DYgKaHom5mb9Y9Hg+D\ndyWajfU545fzoM3J+bIJlGVDXjwZa7lhmQCeXBwkAdPEnlvzxfc38SiSxD1p\nXCSnlK+uFG9XvS5PFmnTkXfeVh4uSdJOUTKGIp9BrnCl7lENgOOsvBgJNpTy\nOhc/OiBXh4yXW1FovoRQ56o0bp0b/nWDLrYqBDVl+JXAA4fioa9qW6GvrXdW\ntecOk7/fGkMGIniP8in34/J94VUbCWmGFWfpTKp4+WlC8gqYkQsUVb5tZNh/\n7EwLhUAUVSoqyRTQy18xT2VAodNWoil3kc3aemuMopKS6V7fBtCzFAuC/hgP\n+TQPAla9aVzYcuUEGpoRD7X+V84TpC3isBJbG5wtf10B4xAa0qGylWC9OJbc\nlr7du1S72FGPWAYKLvyhruVrQKe0D/q4VkyWvXTAlbYhxrk9L3zCOIQ4q85F\nGaPRlqgqeewak7mzzroApl+lnrek/r8OBHrhUqdrINGhglXkd1sVRF3GCsv9\nydEOjR396wFrqpSD3FOf6gaVyN5IZkWo6G+c7JOElrGk4H/8DBCRVmAFdrse\n7IN1\r\n=HC42\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c8dcef8a6f26217f5c1db842cfbc6d80a59a38dc","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.1","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.7","@balena/jellyfish-logger":"^2.1.27","@balena/jellyfish-metrics":"^1.0.103","@balena/jellyfish-environment":"^4.1.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.122","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.89-renovate-non-major-c8dcef8a6f26217f5c1db842cfbc6d80a59a38dc_1618605081936_0.10091213280432543","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.89":{"name":"@balena/jellyfish-core","version":"2.12.89","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.89","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"83abe40f77ca2e206eb3836c4cec1ff9f4d7622c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.89.tgz","fileCount":99,"integrity":"sha512-i92dhPkFjmc6ivXx5EAUydzTkSXvXEPsKkO9I5lUv3oEsjmxSlcOihkQki4VqwQYICJ9E4YBvrFHvqq6hgwfsg==","signatures":[{"sig":"MEQCIHc2xyfeAZQ5+ulxwebT7JQAqLJ7SniI7howg5xshNVcAiAK2QjEtLbaNniS7mjIeLdtDY8mbC7e+NOTkYtULCWiIg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":696698,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgefV5CRA9TVsSAnZWagAAxTYP/3PScnUz1W909IdoHEyV\niKCX2bHGMy7ksDdCo7WUPXGTkvEu0XOKcU5N+rtqK3WwvGZ91aUosbx4WErC\nst21fuMTvuVcbyaZG3FHHxCp4zl0dUQSFZkc30MQFiCAB19SyryrtYd82OEl\nryoAdbXE81UV/9+ZHrRqK+j3oFgE2FaUfkQZHh/rfAg7IoUVNBVWWIvvRhsj\nRvhRLyuPwQY5bXi0eJEqM4xP3ZyTs0lCme8UR0WGUBwe0iiS5HRsl7yDNXM+\nfUSj4HOu2o8JGoYKxyMDOEiapMpFfOm+i2glMJUHKdXMApP5LQIxfCbDwcTn\ndtUAirauTUkpxmGCNNWUY4tM5YU4bRcI1IaiT2yPeUV4qCQu6K425Um6FgOz\nQZ7W+bM3eQgpBjUuziRpzSFf2Jc7SbCbumq3n3E7WJl2sKz6A3XnHM6jNQQ7\nracYMjIEZKGZZGzke9+h8OY7dND5VbW1U7zQSVbG2IGrnGYy6V+zwpTpJ77m\nGzeJixKYM0dAzPXbYsGBBEgF87Q/EB93VZoFO/yEeXLxQQkpde4j+sFUMvbO\n2mZXTIaNRA4xJ1iDQMjxCcZvcMGyQHbU7DD7e6QfY14ZzbxYaEA7CLTzqmSt\nmHC6c5DucIzR8Qb+2141yUcAIRiE1z6ZirmHrVxpqnUm5X8EXELcuEOO6jjZ\nar+j\r\n=Zu+w\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a9346a48064c1e05238d8f5e9afaa10ebb87c5d1","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.1","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.7","@balena/jellyfish-logger":"^2.1.27","@balena/jellyfish-metrics":"^1.0.103","@balena/jellyfish-environment":"^4.1.4"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.122","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.89_1618605432730_0.24650854156403534","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.90-renovate-non-major-c25ddb263ccd951f44fbda465177c3e51fe4bea6":{"name":"@balena/jellyfish-core","version":"2.12.90-renovate-non-major-c25ddb263ccd951f44fbda465177c3e51fe4bea6","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.90-renovate-non-major-c25ddb263ccd951f44fbda465177c3e51fe4bea6","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f99c59c3e63f6098067db0fc8e2b5cccd828e676","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.90-renovate-non-major-c25ddb263ccd951f44fbda465177c3e51fe4bea6.tgz","fileCount":99,"integrity":"sha512-W8vIt4UjnZYS37tzqPNQvoLC42ihfmXRUVXbuQ0miqdycF7zRj75zmUh/6ACyaB5z3FQNMOx5jblzytf+wuAPg==","signatures":[{"sig":"MEYCIQDlAxMhMhALCRGwuMjULZ8/MMEnDV6V1WAYGAXyMDrdkwIhAJn1X769bWGp2c8lc32XzwOJLyFbPfoiyhoLCuX0cQSi","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":697187,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgehyqCRA9TVsSAnZWagAA+doP/288faBF9K1b2W2XPptZ\nGPdCCGeN1VXkj9H0Nb5l/xg0KUnbhUhpY+OaByxLZ1x5M0sUlaEgNd5G33vu\ngnl470xgOEX2nyg6gWZvF2X7zgJpkxk0g9NkHXjPLByc451hW3v0aHt9uK2Q\nCD50K98NuM7K7/s7ft97IbuhLU5oY+zkb6WZsxb08cqa64JHTUFRuHRKRfCw\nbYdNmomOY7FR8gZ0XGQzd5RGq+zrvJzswjOALGE/qtd/4lQg0Zkk7aAe59t1\nWWxWMctWT3Gfc9VEaquw2q28eoblMnO6AZB34YXHGoqANOHxC4fL+kFLJ/9K\nVwx9SLgNhN8+4jrTqdV3f4MLtBcjzKmZ717HVRvFJGVsUBX0QiLY0pXcgZDG\n8U9Q4hrZeDJxLJk0WhTLNDnVKw7d15x38d5U2qBcq0yiRjY6SpudYwuC9ngm\nKOT8hdxkKEcVK6/oJyTiOmZACGll/fdZkYgSvNnvPvlz6ae5A2ZMco8Fbd4W\nyJHm+Oc7FZ3qo8a9I9YUfeuszI6/fHP1mJCQIHNN4dFlobcuEqBoSf0ti5qD\nf0MssCdRom1HFHiUDoRRhGWEsBFR2aJ56u1r89o/xAnqCeJv56P9Wy5lOltP\nc6/My3Pcf9hkOmTAqwyerghW9wqmaGySHVSpUhTo4aTiWAlGQz4JZiMgqxgt\nBtZu\r\n=orlh\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c25ddb263ccd951f44fbda465177c3e51fe4bea6","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.1","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.7","@balena/jellyfish-logger":"^2.1.27","@balena/jellyfish-metrics":"^1.0.104","@balena/jellyfish-environment":"^4.1.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.122","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.90-renovate-non-major-c25ddb263ccd951f44fbda465177c3e51fe4bea6_1618615465839_0.15078433944052483","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.90":{"name":"@balena/jellyfish-core","version":"2.12.90","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.90","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2b98c6aedae50e275af14de5175f961d969f8eaf","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.90.tgz","fileCount":99,"integrity":"sha512-jfd7d2K3SCPhqKpmJ3jFEUd23oNzBrI0nRXstg7N8YX/uFPbqFizo551BVjIGBgfxVFOooIiMM2/JCmFTadBRA==","signatures":[{"sig":"MEQCIEHQOKSmrzKvd5abmu2DIe9sDRIqSBGv5wwIOTTqTv4HAiBD0DXcTawFlvwrSJcymKfdvAQi/ayEbx37bDKCYeZTvg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":697127,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgeh3pCRA9TVsSAnZWagAAQskP/iQCQzavxiazfY+My3MW\nvM3fBqQXtlqgQU0S9KZJ+7gzNxhSj1ilOeZ0We+Mk8sF7uBaEJx+pGCba9Qt\nA87wqwby/d3mGtm7hwCOG5cWn5G3KqejY6g3T5ocdMPfbz4ZXTg3ZN4iMBdb\neaksRBFaB0PeFIFL5CnnA6I1QF07hvuqH9f27M51nQfnI7oa+qxhCd/wc+dr\nOBEvuj9UwQNYC43h53ZzMzNWWz5xUhfVGAyYOTSW/qE7l8PrOvxBKQuulvx0\nGtrHEYgwqQ0qEcTaQY0EKFtA+72DqtjkFsTpm6yEdTl5IdeNQJWFHyDCU8UN\ncl0WPzt/6Ja8uxasvZUCiq9VCU5PPUCKsaD6iMTds7Vf4DGvRK09gPEiAEQr\nAbc/wOfMNn+v2HUPyUC2C8oNSTLYms+DMM687I2y0IYcvZqvu0SFwxmOSrcK\nmHLXS35+3rSjxHYKR57tDjHACR1eTgbPvjsqsDDTLQstE51u3aG8QkovYWul\n4y0IatlVgaUbEToPWwJtwD8LoQWMvzMK9shRfPUO1kEfhbpqMZFnaDDWnStG\n5cWs7Qs3qf++5sGmoZu3TGegkREjA9KnmIslNlbePEp5kTSRitpnoho/V02e\nU9jXwYUJsX+Vw3r79/1XHT4HSu6q66kXDGxThoDi5OSAeLD60S+zNQxUa6Iz\no1GS\r\n=uvco\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"becdd69b9463410c691f1e6ce251e9b961b71970","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.1","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.7","@balena/jellyfish-logger":"^2.1.27","@balena/jellyfish-metrics":"^1.0.104","@balena/jellyfish-environment":"^4.1.4"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.122","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.90_1618615784628_0.7368320009559208","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.91-renovate-non-major-b0ff199a094e139396507e4af72664d2d541fe10":{"name":"@balena/jellyfish-core","version":"2.12.91-renovate-non-major-b0ff199a094e139396507e4af72664d2d541fe10","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.91-renovate-non-major-b0ff199a094e139396507e4af72664d2d541fe10","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"14a90c63c9203cfd3cbce923698ff3b6a500f851","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.91-renovate-non-major-b0ff199a094e139396507e4af72664d2d541fe10.tgz","fileCount":99,"integrity":"sha512-ZOYOSm9d/9REH2iEckjpunJnMiSbagH533oWNMoCVf0eSsLy7z83uK8CtJG6XhBprYSPm+zVSH0Awy9LUs+OHg==","signatures":[{"sig":"MEUCIQDvIW2TWwV2RFTxy8/lwFhH2wzi2aRy9w8ftAigj6gRegIgSro8DjHFoAaNS4FLXdMCMt8H0B/eBg0BzZmay9AhoAc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":697616,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgenNbCRA9TVsSAnZWagAALnQP+wTmjVXWq2rPmYm78FEX\ndwy8Uv5xZSPg3OOOXiWqBGno2HuQaZFzQ9eMri8VmTDA8sSqgM9Lb3+/C3RQ\nf2zzKQizTb3JcMPYH6grt5rkBjg8jurWo+SjKbDz1+X2tDy5ttJ/fTYCWjYr\nuuPe/Gn9Rmx/JFvTgcGfvQmrV2N3sTRfkGamimbEpPsCis8FCOE/FXxQmWfo\nsWVPvcT/G74ujI0eBhhradZqkkIpM9eYwuIdxxVUvVkDT+7sGiZfXXPGURPL\nqfO2agMWGDBRXzsdOmEVxY/fp6CqUMoK8y28feuXDdDl0AvfwEC/PMhCt5hr\nJi58Wj0ULic3pSb1Gwt9ZbMr9ehfvksI0+umkjldgXjcX8h5waSL9anyxJ8M\n4Wqy8XVJtdTk0fri92oOdASfM1iWGedTJJ69v6kWjXOpRI2I2V1zqiehARGL\niyY92Bwmc0J/nI1Ay09ATZ8+GNvejFIxjU2XbMRFO9TccIkEgm8j8U4DzSPj\nYNQyJlQjYSqEmz29PGp1JlCx+HWW9H0Qa/wAtRxb+JUBOu7pVjCIMFPNeG4k\n7a1yNRyZr8a+KM0WcnvWGyicvnDGpkFltxDXsw6PxJGZVVWDSnndEjCr8Kuc\nNweVaT+7PmyTOfM0O3W7kvJKkeKIv1nG9jeXXw74zkGzCxsLzoZ4Oks7YWLL\nyQ6f\r\n=ls8K\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b0ff199a094e139396507e4af72664d2d541fe10","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.1","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.7","@balena/jellyfish-logger":"^2.1.27","@balena/jellyfish-metrics":"^1.0.105","@balena/jellyfish-environment":"^4.1.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.122","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.91-renovate-non-major-b0ff199a094e139396507e4af72664d2d541fe10_1618637658623_0.6304961692796609","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.91-renovate-non-major-7e8610c966869eb869e4d5849c534971f7f10e6d":{"name":"@balena/jellyfish-core","version":"2.12.91-renovate-non-major-7e8610c966869eb869e4d5849c534971f7f10e6d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.91-renovate-non-major-7e8610c966869eb869e4d5849c534971f7f10e6d","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d57cb0c137fd7eed518720087f26b6c8e44dec73","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.91-renovate-non-major-7e8610c966869eb869e4d5849c534971f7f10e6d.tgz","fileCount":99,"integrity":"sha512-rQnIJTOqdmqZznEZVucxFjLb8XIrYJkiEUXB/6lvVxtU6wxKXbhpt9gBiAAMzV+ZOkfSfCUQJHBiNbhLX4hMGQ==","signatures":[{"sig":"MEUCICib5zUAO89m/kHkwtqREnTaxfIqGjI80e3rMEPz4gSkAiEA8JZzM9l14kkTpoCEJEKHXVtYQZvhKZQGHhiuM5vLUt8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":697616,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgestACRA9TVsSAnZWagAANZQP/j/8z73OEWL14dPxoulT\noqpX6bTyfXxD1BPk3Ov9qTd9/Tv/O1v17ay7igfqwhUSOTVoOLmLXEBu0ycu\nkmJ2aOfB0BbBwFzGVsli5qPVCRBYfO0nI+6yh6dmxfuzKSIcgDMbaIlkVO8G\nAO2xrNg0AY06GdD1isnObqkJMbnxI8FbLMnGMd7FLeuxI0og1NQXBKL9f1Nn\nN9Bb+auxTHRXjLJmtIaVZLOx3udqmB5tJBfQkXSZjKjqpILSZnY1A9LSFx7y\nEYWeUKtvaRDFmlBJ6tvA4QEG20GFpESDmYSebnGXBh6xvIrs5PSFewIVkdxd\nvTThUf5QUWZ8gREtjLYAbEr4keqo614HX3AIr2UlWBl2Bf3rVO5br9BBhiYD\nCp1DsOI7CixDUEw5maxb8Ymxwkr6X/FYU1mumt/09AQJCtyS41CZ6gBut1tE\nCYbIroY0D8MEJKzPftZE40wRREuXtbnS77qzbs5tcDqYo/yjKDPJYF7eAsa+\n6m5wfbf+45vhuNCDwHSKV2SNhErNeK6d6/vFvuG/eB5HyHY1Rt9dGTl7FfnA\nMc/c4sSs6mWntgZa3cN1D5tVtVL4fiVCpjK5vgEmTmfTCShIEWWS8l3CJSUT\nxScXYpnedfzEW9xLWRGgP1mz1BBYDJpfDahOMmnnp7Y1+eDLvI4IFiGJaRsP\njFm1\r\n=lPx8\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7e8610c966869eb869e4d5849c534971f7f10e6d","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.1","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.7","@balena/jellyfish-logger":"^2.1.27","@balena/jellyfish-metrics":"^1.0.108","@balena/jellyfish-environment":"^4.1.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.122","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.91-renovate-non-major-7e8610c966869eb869e4d5849c534971f7f10e6d_1618660159449_0.38445907177372507","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.91":{"name":"@balena/jellyfish-core","version":"2.12.91","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.91","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"7a15ab683081fe67ff4f621c1a7574806ef6078e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.91.tgz","fileCount":99,"integrity":"sha512-w964TQ4nJsRWOW9NtybpDOtcYeuiYsk/jHkfm0L82d5Fcy+w47my4GtOJfP1HYPdJfgWFbaPjLvvdZa3FmrJyA==","signatures":[{"sig":"MEQCIDhOxHwYfPf9mEwVfXQx6MNfBeUtr3X/UnOukMzYZe9nAiAne9rqduezHhAYBVWwtDXUjvA7RTPh4UWlacSrMbxAoA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":697556,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJges1QCRA9TVsSAnZWagAA8FAP/i6ToWWcK0/PKJBejrru\n0fcXSweQbNu6erjmkSpgw/admcZFw666meCaxSdU9Zh/fG5mwYP7TJOfb7Kj\nzaZCZlM2IMEbsJt6FGk3wrq/Um+ReVxbYneU9E4sUjvzRIeAcU5kVfZKHzz6\nhETUZ8iWdeEdGKz/LYr1ye+g2Je8Di2aw+uVcr/A0H+1wmM8TOdCZz3YZE6B\nkC1TIOTQS/QthxgV6q4uJQz14nh8uUbF2Ag5xDhbbxUBa+V0P+JcmhKzFbsy\noyO5QyGuO34QBlp13C9jo5ufCDOeXm5RtnLmISRoM7o1+bJeWz8KDZmZrBFW\nbq3aCP/9qDj9/kcVS+1qV27UpS/NV5frdNyJWbMPSpvgVTRmIMRUpUxKpNxa\nTaAHd+4Sh7iAVwu1KvmloMhSDKRBkPkmeY2Of6LSnAbX+ml721aweoi6w64A\n99rPKG2pNdh1OHAlAmQ60AkOonleU0+n+8zLID2rCwnSl8RNm3dniQygFVwR\nHN0+l4JkUn7vMI/eF/AGTCHpIWuC4QiHct5sk/K6PhAkCfrBKl+j4oTy/jp+\nhGEODjqwyFxjteFPJ3Bm8j5XM0dPu/ofTSYAfsjpqu24RYMDue5UJb9gtyLx\n3GtmCqmzHrf9KCBMOOLGvD5pIOGBDlgghq/NpN4C2wLaYGxKo+FdbyCst8tY\nlf8j\r\n=mCSV\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e9c04b77eaa6e69c7ea5a068ff328fc22a580c0d","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.1","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.7","@balena/jellyfish-logger":"^2.1.27","@balena/jellyfish-metrics":"^1.0.108","@balena/jellyfish-environment":"^4.1.4"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.122","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.91_1618660688213_0.8116936724966841","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.92-renovate-non-major-30268bb40b7e1038a6a9e29f3fe03c39b0e05461":{"name":"@balena/jellyfish-core","version":"2.12.92-renovate-non-major-30268bb40b7e1038a6a9e29f3fe03c39b0e05461","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.92-renovate-non-major-30268bb40b7e1038a6a9e29f3fe03c39b0e05461","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9e0956a6fa95cd7d5d491334879dec820f706084","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.92-renovate-non-major-30268bb40b7e1038a6a9e29f3fe03c39b0e05461.tgz","fileCount":99,"integrity":"sha512-plHhFTKOBnsSXj7gLWeNxMMzGdsx/qFkFK7r+NykgZEFB3kDAASicsjN4E8w0RohYzgoqfQX5xbAWHhCQy0HFQ==","signatures":[{"sig":"MEUCID7PGYGawFCLyVOsxTpZD+Wjq/SISOlFdZlaPeFcVPjIAiEA+eyENclHMhX4UJULobfqVO8mHhRBvAUXP5zROeH7K7A=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":698045,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgeuUTCRA9TVsSAnZWagAAYMUP/2+LjwZ6LV/TITKIiZQk\nid9o498c/BhdIUvjoDYJMKoG5fGp27fYZqDmpcdL2+ucXqcRs8MXRqGpZuyE\nLHtjuVZNSoZbgyguywkrDahO2Mmo2DEVmC3vQFNnQyFQmpJgrSxJvR4fZJld\nqMgf64PvMd0vxiaL4nwStFKYtINLL692yJ3wr92liH1bbF4FJ3QsoxGs73nh\nttBqnvll3B7prgR1JZgPhHWIyPllbklQtjErt02ljNb9Sfgg8dxzLa4NGrYH\n5xkSjplVcdLXPsvQw0RLvrbZSDfB6XfmyACNhBAFMLI0yeBlypKhXUiTgDNq\nFvpvrBvzOOQ3cM3g1VQtx9LMpojJLi4HwgzwQIwQpnGYox2GxzgskDEpLVQX\nOAz//jkHK7BqpPUNMj6YzvJFtbCttq1mvOd9dPyPOBeWVTAEaZYvJ5/A6JyV\nYp7QEHZQur33udVmLXd2XEqglnnnm9+/5kqDDh5fu43DdIkRvz0Xe9u4rzvg\nKOStMD8FibR4Oqy9qWWhvpd3Qi7q92AxJCoLL8Y0mX7T5/YkQS2VSBDvdOvm\nJr+q2jII8eTfoekn0/ER3lwj1kKGFDpCF5dw+SiK8q/HXlsutNOdq0Mci7aR\ns7gF1Xmw/mKkIM0AsS405Dpij6k5o1CSlo62U2MdekYFTqDJUfv+tqvpSybF\n8dSL\r\n=xajm\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"30268bb40b7e1038a6a9e29f3fe03c39b0e05461","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.1","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.7","@balena/jellyfish-logger":"^2.1.27","@balena/jellyfish-metrics":"^1.0.109","@balena/jellyfish-environment":"^4.1.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.122","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.92-renovate-non-major-30268bb40b7e1038a6a9e29f3fe03c39b0e05461_1618666770973_0.17151823461297422","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.92":{"name":"@balena/jellyfish-core","version":"2.12.92","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.92","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b8951ceba1d0b060b61c5cb16d962e84300d972d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.92.tgz","fileCount":99,"integrity":"sha512-5JWXzVeWXRGJGBQf4w8pIvG/OvEYZrNinmAk9c6vbiNSKQ/dIf3sLaN57PEkVJ35qyP3pCZ3hmXa8AIWLiXNxA==","signatures":[{"sig":"MEQCIDrz1+QVrCdx2YS6vpNEYek3PsT+HSxxKqNe1Rxma0UrAiARHrOmSKjTkVVgVQrCOR6DdWGnRnhrj3UIQW7Re1lq0Q==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":697985,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgeuZdCRA9TVsSAnZWagAAWC0P/At37dmClxr3YEAt616X\nREsH57jFx9aJ6XAau9KB1JIT9lCZo7zbyT4IuBR6H5uDDyoUjCR1FNYOUqiz\nuIjPqm8SV6Zb5xeobsgMjBoY0aEW7igN4smMNS96mjoTlgSsvbtHL6KAuDp5\nHswTvM5St38iWNIN6J+Rrcb4XJtMKQuFWTomRgeXLbpwVLLfp0wH0+iIQMcB\nxh/vM81V20meXbbZNR8SPm6mYPwYBGapwu90r1N2GTH51N8PbpSMXfQmhIFt\njJiWu7xIsFW8jfkr+KhJF7oN4TuhA5Qm7QvxrikPGBjCsCvD2BrGnkT0H73u\nR9r+gM7/sN71NQT4VUlqpvW7g8n52Kp1g82i+/gSW6Tgdormi2cD6TeYAPNG\nNptMl0fDQNOqWK1ELsXFO0A4wz3rjL4+JAzx/NLS2GB5g+xBfMQUZ82acxPR\nKY7+FbZwNRUJOK7Yqd1uBsq7SHLtCSnT8epEZMwWUQfIpTRYwIHDUyjBQTiC\n945mhygJR4Dk5eV06OE7YM1b/fPTyivbBNHJ2OG75mgRNPy8v9C42Bdi/PPG\nACqGEzOtf8yjV4wGxlod3xRea1NAWZc/Nwgfm9HqCFrycN+nPQtawMXbLUWD\n8orO5rrq9NXral9SQ1NuXp/lDh1f35GbKlxtLoatbejxbucAWmkWL8EjWGW9\nlTh8\r\n=+lHH\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e150f7683ece71530c921471acc384ddd1cf2830","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.1","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.7","@balena/jellyfish-logger":"^2.1.27","@balena/jellyfish-metrics":"^1.0.109","@balena/jellyfish-environment":"^4.1.4"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.122","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.92_1618667100691_0.9802390162981793","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.93-renovate-non-major-2566b98ea28e08e5c6eb616cc4111b5ce12dfd33":{"name":"@balena/jellyfish-core","version":"2.12.93-renovate-non-major-2566b98ea28e08e5c6eb616cc4111b5ce12dfd33","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.93-renovate-non-major-2566b98ea28e08e5c6eb616cc4111b5ce12dfd33","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"181a6a5c40301d11090aa16e2333d8c93d07c2ab","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.93-renovate-non-major-2566b98ea28e08e5c6eb616cc4111b5ce12dfd33.tgz","fileCount":99,"integrity":"sha512-KR8CfSl/h0ULQS57hJGRof/VxCBTSux6hxvz8lrMJ4xJjTXOrpCzUlNRkeE6DnLVSmC12GLw29mXhAqoyUxOvw==","signatures":[{"sig":"MEUCIQCFpTBVY2J/+ZF85EB1i8Zo6ojNQvF7buslGa90qiaXCwIgYMBrEklt4HUWDeWs/F5EvfxPlAL+B8dRDz8Ufo01Fok=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":698474,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgew0vCRA9TVsSAnZWagAAiJoQAJe9jZ4M5yS4WezMdu2p\nWCgx8TvrHn/OsLG5penKwB7hvumZErZqVTthT8O50KJP3hjn3smmDw8CZ+ty\nkdUqdhZvwMHBnlDQfMVGY/Gip29kf+CTWJYv640imdQNi7De/hGg4MnmCuAu\nza9+bf02y/6xGWcrkvOw0wRcZDyqPoaO6LRfd+vegEU26LQAiTFHXkZrKwdO\nRGfOyMecHNaM+cfir4bY2rz+TcDifaZu8vXgjkA0u1543LbhCqsagUSA+Zps\n/eiOhPxSLdqzCZABYKlmqTdKALkabYQKm9vS8BfjEKux/5XGzSykGHJWjBAk\ng5d/v8yr3uPOEwyvRZeBrHii8li28f3mGpav9qhASSmcDJwopgAosMCWg6d7\ns72wH0jtCo6Qq4487WpbAVV48ftLbxtS9NPsPY8Gy7uLa97zDkasCNuc4cyp\nsbNVOoj0vfSlKNgfm+2n7ZlCZDW5o6H2YPYioky0vL7PunGmWBxVBk0SfXSV\neh3ez5Aj4iHvrn5QOPzR5VN2rqrsKVdlYto5UuhypHdWJ7q65Wp9c3nYcl8l\nwpM55Wy2vWlwwyidaK3Os6xwvc7vXNuGKlLKr7pCa2rqLshgojRcTzXioZYv\nU1Lhi8piXC8cuEbgd7UbjGJ0I9UMPgjnAfXlVQxWOdqcUUDHz9eFxJTtIV5v\n77Bd\r\n=sDTm\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2566b98ea28e08e5c6eb616cc4111b5ce12dfd33","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.1","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.7","@balena/jellyfish-logger":"^2.1.27","@balena/jellyfish-metrics":"^1.0.110","@balena/jellyfish-environment":"^4.1.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.122","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.93-renovate-non-major-2566b98ea28e08e5c6eb616cc4111b5ce12dfd33_1618677038973_0.5775205986872338","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.93-renovate-non-major-d140cac7fbd8a25199e1fdbaa29982fa07d15c77":{"name":"@balena/jellyfish-core","version":"2.12.93-renovate-non-major-d140cac7fbd8a25199e1fdbaa29982fa07d15c77","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.93-renovate-non-major-d140cac7fbd8a25199e1fdbaa29982fa07d15c77","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ff8e39d4b47630135d5d8b10cf193bb03f1afae6","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.93-renovate-non-major-d140cac7fbd8a25199e1fdbaa29982fa07d15c77.tgz","fileCount":99,"integrity":"sha512-0PobHCqyp1xWJYIDxoEKK/WNyPB3DlIaB/nYLaUOzKmYkgtKeTDQudIkeEN4eKi7smzb5/Kt1NM78Lk/X/SxHQ==","signatures":[{"sig":"MEQCIGkj4hM1tbw6O7HaYlEbLIjh40Bs7GoGUqWbdcrjGm5dAiBewqLiYq0u0wM8MohMVYZeAIlgL7fmzyp/t6RxxCB9NQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":698474,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgeyUJCRA9TVsSAnZWagAA+Z0P+QArnSScAe+AbBidoJtK\nROmQwZCoHRNMD9S9wdvgAowxAsdjV5pBt/xzmBrqFuxsEzCSHraNaGoknM5u\ncCrRbjRVHS1eZ9AECn3+V3zVHQzrEZG05uM/wxgYuSvmp2QO2QtTQ9tn7yk5\nCTgCH28W5dOf0AfKiaHq5xgRm/ius+O+4emMD4wMumCjAB2IhFW+eBiRP3MS\n2WdwGJIoWtHpox+R7kdh+1Pk8r0fdfviKcPGutiN9zPhdH1O4BW/tL22p4si\nAc04BUWdDE27PggHd4xqcvKzl3ZlCEJBs2FrCV1A2a/N00K2HwClF+/bskZn\npldVkhcun4zU9PwSaNz++dCaWDCmSV5PnXocMgmFxhFeUWDb5yxiR6ovKCw0\n4YeYxxy5zODfbro5aBE7sElKvwYIa5E7Zaba/8t2YUimJzgDA3P/kZyU4FKZ\nKbSJF5KKDSZmVxMROHSMFyv3geUs6mfgi8hApAh6G4dmCpehjNC+FjRa0pTv\nDbp1Kv0i+nUHEvctNSBdzbGQ4cQWe1m1ELhU8ZX7X3WgEpnFwToxY3GNk5CY\nVu/+GDuW/OQ/0DtIzN6x3KW2GEfkaNBXNHB0be20og0SXuBm2wtlZNuFF9+i\nINcWIAdcFTHt/n9szqNr9eKShUXocv+BandKfPzq3RIJ2L72cVzm6an5ss4x\nXaWC\r\n=nB9f\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d140cac7fbd8a25199e1fdbaa29982fa07d15c77","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.1","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.7","@balena/jellyfish-logger":"^2.1.27","@balena/jellyfish-metrics":"^1.0.111","@balena/jellyfish-environment":"^4.1.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.122","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.93-renovate-non-major-d140cac7fbd8a25199e1fdbaa29982fa07d15c77_1618683144885_0.9166735770795136","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.93-renovate-non-major-0e2648d555d33915b8fdcf84eefdf39c0b4e8591":{"name":"@balena/jellyfish-core","version":"2.12.93-renovate-non-major-0e2648d555d33915b8fdcf84eefdf39c0b4e8591","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.93-renovate-non-major-0e2648d555d33915b8fdcf84eefdf39c0b4e8591","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a4f3c760e722e57a2616aba0697227d1ad0ebcf3","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.93-renovate-non-major-0e2648d555d33915b8fdcf84eefdf39c0b4e8591.tgz","fileCount":99,"integrity":"sha512-VVWrovxpXXLjgadm5zpCzYHVNC76w8jeg+EC1D+qqSptgF1q1okpKMS8prUxEeL0/EJB6wBZf1nGMOndJT3YuQ==","signatures":[{"sig":"MEUCIEuJuSmBxM0Fdnz108Yad5O2hi96Xwhz3RhQSLNimZ05AiEA7YN9+V79Fu7lZAQ1QaegC6DproSTphNqOUlacch9hgk=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":698474,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJge0LOCRA9TVsSAnZWagAA14wQAICXfetODSGyxoWcCWWn\nOM2OhaQApk/3esUkAPFZCfOW5S2ZSNaa+TVrM9Zbj9z6hhTTMUISgRypfBV3\neGCIpGjpbbe4pycgmJm/8besFx4ombSIVBDaoyT0qLmBUlobKibRnOJVpJF2\nRCT743qc6B0Xj6TLjbJaZRnRbbUGMxHcrDtKe8IGHa+NENvYVmronEgWhBnc\nttFsFKf156cby59evDKDaCA9rb4zx/f4WONIEhgGVvuz/QByoZBXkucx1yEO\np4LVlK37Qm+OYObXP/Vbn2ZIe+dOicN/n3mMZU308oE9lVQJv97bdpOKlVlB\nS2+nnbyK12paGHqlUmQXiWHV5ZbfF0dpwITdiN2V/BtezbskbI4SyuSdsWnw\nM/K7aUX7P+E8fD9LAzmBraBsR05pPovzP7SBpHXRhsb6R96xVlRB6As8Rxyv\nLuc6yWVk0jA38DAej+Qwuno3TrTsF1xl0RXLxRsC4zsHhw6MJ0SD8/wLLA27\ndwiBYfMg6CMfSaOCdPS6iM5alNBf8aGDyGm9t5RxmWU1QnR+KxbCE8lL8Za/\nLU3ed2W04LWkc1WML5PBGKaP9hk1b1dPz9ieiBhKnsv7RppH128WlIIY+La0\nW8bAwmmMkr4NVAyxXpg9Ohgy+t2hmCHz45cwZn5TDP99PuadyWmeCZY1dFdJ\nfAZy\r\n=FzsI\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0e2648d555d33915b8fdcf84eefdf39c0b4e8591","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.1","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.7","@balena/jellyfish-logger":"^2.1.27","@balena/jellyfish-metrics":"^1.0.112","@balena/jellyfish-environment":"^4.1.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.122","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.93-renovate-non-major-0e2648d555d33915b8fdcf84eefdf39c0b4e8591_1618690765192_0.8719103858057247","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.93":{"name":"@balena/jellyfish-core","version":"2.12.93","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.93","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"eb17edbfa4b483fbfe4f9eeca9d51a3a2eda99ae","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.93.tgz","fileCount":99,"integrity":"sha512-gh1MLC51hops9uzh1kKj0zTAd+6ofVUMWtQclZEapSk0uD6YJB2nfeDR0KHMUHrSqqw56Umja9hDSEeB9r5Ghw==","signatures":[{"sig":"MEQCIBSi0Feko1hX+VF4cm1/sEW8ZG4AQjbsFvvYWhCgJFg/AiAMZ4KGJnmq91c/P812aE+Cl87R6sPUHqenRRcRGEkndw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":698414,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJge0RJCRA9TVsSAnZWagAAhYEP/RX0DLM3kSoGkk/gYfa+\n8vHsqqU6mp7O2En1WHj6V0CL4TM2iFxshgxwcdcF960nQ9fSetY65FMMijCr\nYF8f3oiVaHIg6JI15Oz61ilyYtLY+dqkrPdNcbSAAnOuBLW8wA/fzDSJN4dK\nPPMSqxPFqrNthB7p6nMorI7WSV5VFKjZMSpD3+IPL0e3zHJaPCa/pdq0tI8y\nh1y9A3k9qIrc9Zph7pjuWPm1G/heHAgjc0rbRXCeJG0obt3rvKkdb+bZLk3A\nMavGyDyeTW2HFfOjNVRE/4juTxpT8PLiZ+OQSXITS9I/tT3NkMQK6+/S6QQh\ndfxPIsRg7wg0A/Wa8qlPfLJI6WAMteyXsbLoKEJW3uhTMjxfwAk3oyiKVUPH\nlto7sZR9tgV1tz/fKoPn1dV8r6x0ctUYTc3kaR2u3zLoSxR28gyboOK43dTX\nAMKRH3pgXghNYB8VgEwIckSHlURqy8gZyzF5xjqm+2jWhtCMX5aiYDXay1yt\ne0yIjKhdrZT9fPng5vNQfIQ0tob2pw8k35ahkiig/Wbuo8hdF0axiw8EaaVs\nwiikSvMTIQ15xeh9d+wOEtgkJtJbIERytbLMHtAmwtu22COnfq735LOpFZo/\n4EhHrxN6QT7PPhsJ9aa0qn7EU+gYfg+0q8H3UWeklN1TZroN/ITd1wclD1sl\n+7VD\r\n=QzNc\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d311d6b2edc039a7ccb0591e0b014235a8e83664","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.1","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.7","@balena/jellyfish-logger":"^2.1.27","@balena/jellyfish-metrics":"^1.0.112","@balena/jellyfish-environment":"^4.1.4"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.122","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.93_1618691145287_0.710115979956913","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.94-renovate-non-major-6665a1ed143e0fe5f13d45c3dcf7473152759e49":{"name":"@balena/jellyfish-core","version":"2.12.94-renovate-non-major-6665a1ed143e0fe5f13d45c3dcf7473152759e49","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.94-renovate-non-major-6665a1ed143e0fe5f13d45c3dcf7473152759e49","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"eefd0aac9f612ad64ee7c3799549e9b3bf881af8","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.94-renovate-non-major-6665a1ed143e0fe5f13d45c3dcf7473152759e49.tgz","fileCount":99,"integrity":"sha512-hrU4VjLqIbv81ImDr0W+Vfl2e1DWgjaEkoYZeSqVEKUaX+GwKGy4wJMtnmECXkbjrd89Q2E8qi+3DUT+T5L6iA==","signatures":[{"sig":"MEUCIFadi+XtsEMkZUfOTksKDlD0K141bAeEyhPjIIDyfxh/AiEAq2+GdSORAslnkawgW66ILEsl/eSm2m1clwh23Xlt81M=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":698903,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJge14yCRA9TVsSAnZWagAAIqoQAIY5zg6jmqRFnzBiFNjH\nHB8KadCCOxEknvULwi++17UXDY9H8zMthRkzdLbrf3vhcjvekA+3vEDpZU3X\nVSoMvplTKt1DIKlKQyL82MkTMUsFfqpc4CcSbUWeU5hhTJNYVX6X3z31pBvN\nNGKa/F1Ei0iNSclJmBUoAeCVQ2YZdcjFOhtJ0Xs5AgDf4vXyYRHlfdcpfDDg\nAPtJ1v6zW0UvvAO3ys5e9QlcPanzlABimtg4iq29Kwchq6rUR9cdNqlFYEei\nU98UKA7W0dzyCDMasdhVSjdterbp4xPh2g/u7Ug2hnXfa1O6m4YRzB5cKrD6\njKatl4vbledXiLZHL0JSH4UaaovWK4Djq964BKrlNe4Zo9oitvuGbVIZjMnq\nS/l1P2tlTueocpDAAVuHGdDW8KXuJxJhHhZc4vZv+lqWe2vWntyYshwfaMBZ\nM2nJV1tXiy2bzScEookkGy9v915AABYTcTWLoJfQh3RRDFb7p7htjuYrh8OO\ni+0bBET1twhHTNunoohkyZTAJSBFjPjhjQlHA/0IyCyyLsCvatYk5cbtMVkG\nxG6oQ3a955igQ5GS0tGlDHpjAnJbKWoMBEcWs83EXEptpvmWJLsYGGbGtLP3\nswiUkUSCBfApD1W5/3O7iNr62begyBOjIRow7yWQ2QgUu1wfKb/XlKeqadQC\nEnTe\r\n=rmOC\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6665a1ed143e0fe5f13d45c3dcf7473152759e49","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.1","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.7","@balena/jellyfish-logger":"^2.1.27","@balena/jellyfish-metrics":"^1.0.113","@balena/jellyfish-environment":"^4.1.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.122","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.94-renovate-non-major-6665a1ed143e0fe5f13d45c3dcf7473152759e49_1618697777869_0.2204612204943357","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.94":{"name":"@balena/jellyfish-core","version":"2.12.94","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.94","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a6359be6b0421c679d487c943ca4b7751bf18cae","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.94.tgz","fileCount":99,"integrity":"sha512-1hNdseAVFEARaLcS9WwLk5XW4wa8POySG9RFFau+KTNgjtOftXJ05xKRy4x8wuhsE8aUZQtqUTYfTOuIwvzK1Q==","signatures":[{"sig":"MEQCIGdAVwvQLIXQY0bVcrGvpIO1B+UdbPko9s20EuI7UWBmAiB18oO0dJJC4E+BpBFnvH06tWEbe7xFuwkv7YKcqbk7dA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":698843,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJge193CRA9TVsSAnZWagAAG44P/iad63pQP2kF2IT3Whxe\nb3/or9vH53WStKM3DhJRyNUfYWfHyNxVVAicD6GxnSBA/Wc24ZO6LDfARnSv\nHGNce0s5pee5yxpDV4TPf7mo7lXR+ECbIITyv9Ez7X8T9g+Rf2VM4OtCRfuE\nsaB4/Ku+z20qrvpaGAeV1y0mdaAEsKUurZ60nsj8aTl0BMZ6UoSjRgeHUKn2\nzZ8HDdTih5VhIFLUQa1l8EKTLLVyE2VtCPaGpOjFLLYt87Asj8TaytnGOMPI\npzTG5TLUdKmCSHORUdTpExAJKX4SvCms56Vts205WDOeFI6zwcvG7WRHA6pV\nJjNK0SKoh5/AYnu1ksjyDoSwbISBOPhmW54ELAEtGVyfBuzTg3iQVY7ISjK4\nMa4S5RQEjWK86O1Llahxw6Rbe4WiCk3oUGbLub9JQJpcy9B87TMdIz4cx7FN\nMNTXikumVYmSE35x7z3d0vRZgExI+ByfoAF2tqlV0YqyKbWTXE7p//SHZ8V1\ngaNOq05xQ4yuA1/l4vQXOZiX5SD/7aXODLuR6l6Cla1aH10C2LtrJAeOfgER\ntlxn81lKVRhzkVyrA6HsQ9OioowNdH+l11Zf02zyQVpulLzMlDU/I92x4GWc\nMZqUG389a5r4VsXXjjGEuaBxSa/0uaJarzaFbV+PGdnxWWGntCeuxsINTnTd\nCwPH\r\n=EYfg\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"29a2d48f8d392903937be27fb06db68110a722c6","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.1","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.7","@balena/jellyfish-logger":"^2.1.27","@balena/jellyfish-metrics":"^1.0.113","@balena/jellyfish-environment":"^4.1.4"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.122","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.94_1618698102950_0.6888547561604574","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.95-renovate-non-major-29ac1d3ee9d0dbfbaaec5e179476eaa3f24bfce4":{"name":"@balena/jellyfish-core","version":"2.12.95-renovate-non-major-29ac1d3ee9d0dbfbaaec5e179476eaa3f24bfce4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.95-renovate-non-major-29ac1d3ee9d0dbfbaaec5e179476eaa3f24bfce4","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ec842156c343d9cfe3ef614b4a2fa8f4fbdb02e0","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.95-renovate-non-major-29ac1d3ee9d0dbfbaaec5e179476eaa3f24bfce4.tgz","fileCount":99,"integrity":"sha512-ix2fY5KstbNdhVAInMJXIenyyP+CUlLL6eIIJaFsN1fFUoIsoGwxYjkg5CTgUacUDoKY0eoeejWAL31iT57EwQ==","signatures":[{"sig":"MEQCID0+XCABK/5Nc6zlOI7P/j3/58CQZNA1cG3qYLbQwFtIAiAPbBsODgj7A4JURoJ9wkfHn1y3GKcYp/3O9hym0UDKkg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":699332,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJge5FXCRA9TVsSAnZWagAA23UP/03T9iypudB/qtxcTvA7\naBapEh+O3p6xH4CXpV4iVmCmFa/RPM43HnT05t5j8hUVdee1l4jPblCUrX3E\nWHvNRifgpcUBdcz7acdC4dq1oi735IqcaMXl8N6xUn1j+UaXgvejJfCSoILr\nDjR9uE63TQ3qLyFIzz5kzGJ4MR1X41cpeflveD3drqsRVoHOgTyswQJKPYLe\n1B4e27k79PlGKC21wR1+jB75Eyae4ynTT8l2eNMOIQlhX9qDEIRcjuCXpUNn\n/rpTXAyDblJI65HpHJFxG4jtaM0ZSY+k6NK4JXsXYPcz5/M8P1ZZzxZi1Ee/\nR9bhaGO2tkCBy32tRI8c1JQgqYLwP5rILQNp/TSSJqHtSXhR+AmIBTlxZwyB\nz8WFcrmE3pR95FBCAhTab0D6ZU1TqgoL7xtvDGTEtYL460gOTl5hW822pjro\nZve9i35WAqkoEoEBj5rFMZP9qHBHXQVD0Ts1AU7+wGSsQnO8hBKupmCzmv38\n4x7nF85rLHFG+9YvwDcuV/TBr2ygAwbBcCdaaJtwMd0hhs8S+ndq62aSthoU\nih3YX8upCnjnBLDD/p6h/jABaPAuJWB0JCuKByMgD6Z3h/0J5l96SXF2PghU\nse37aqNTUJ3qTnzr2rd57SqD+FNlxXU9C32WPQjlAF9c6w9j8lsFT5nDr8Pn\nRF4y\r\n=Uuka\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"29ac1d3ee9d0dbfbaaec5e179476eaa3f24bfce4","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.1","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.7","@balena/jellyfish-logger":"^2.1.27","@balena/jellyfish-metrics":"^1.0.115","@balena/jellyfish-environment":"^4.1.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.122","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.95-renovate-non-major-29ac1d3ee9d0dbfbaaec5e179476eaa3f24bfce4_1618710871303_0.3998020946328502","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.95":{"name":"@balena/jellyfish-core","version":"2.12.95","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.95","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b572115eefc3d1628a0101fc015bfcc21bb0cb56","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.95.tgz","fileCount":99,"integrity":"sha512-tEHaSHb4Pa6k0lGwUmKWVFXN8AKAtHxM3En6HauD1oU9L35OPAZT19dn/xMCzoMZjFlE6HQLP3m+FdghWZhf8w==","signatures":[{"sig":"MEYCIQDYTEYoUO7oUhZbpLsPsS/hLllUmDkkFggwDphJH1foTQIhAMQAzLn/G0KO3xWXu4bYrAxNA6vgm1ekDSn9at1fEjv/","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":699272,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJge5LNCRA9TVsSAnZWagAAENMQAIOCbPTt44YyQG257/Uo\nGrqHbF+SG+pXKqcMAa4C+UDMVopfSrv4rJ4EkhFeGLuykSCv+7+5Wf/H/pb0\nunkhaVYgjfUPjIs0dkrWfqUg+iXSYUTzvDSpzj4wH5zaCKL/u4NHfXRPswbx\nV2Y1wOOXdtskOLgsoD4mki2hI9U3ufryl7QPO0qCZs4jpAEkiWk36tOeCh9W\nFHsO8UcSLCgw+1Rwh0X0p7sY8sVuSYyMpoSEdR+ZcmpveHnf+r8ptSs2xVge\nz4NoP5T4UGxGeyeAbn/nOG2ztq5DNKc7A70NDKDBygv3yY+NcQXwkYRVTfJc\nBYPcVUTkvKFIRS4aoWajxz9bNTZ7nclyDxRgcDz6RA+4+4y2W7/QWaBFnSv6\n8IOCc4J1Jpe7nJHdzBUvCu/n68dKtucSvSw0voi1QaJhtkb/rH4abGVF1YEj\nwRBV6s9BmLHT2Hbl8UNYIsT8dNnEPAE0yHvhHvfDiTKX4Egt6z/nolTgVZXN\nx8XGVeq0dsD+NfsAuE1DBkV7HfN20e7ASclXNhz+PdKFFcI58Td+GZFvLCNq\ntxQ2Y5M2nsjIkxr4q+5a0azF1PX5Ef6Jh0GYBCuaOV+GVPagf78Fex2PtNZt\nYkj2Xo8zNrGe8C2Dg7bQMsEoO/xQbvGqPdtl6/evKNVR7+Q3KAUxF0+DHP27\nMfxc\r\n=ZEYl\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"1510e3fcb40da1dac0ba1d8f5fc6958b010fc50d","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.1","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.7","@balena/jellyfish-logger":"^2.1.27","@balena/jellyfish-metrics":"^1.0.115","@balena/jellyfish-environment":"^4.1.4"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.122","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.95_1618711245149_0.44385888203319945","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.96-renovate-non-major-6fb878ddbce41cb88f742dd612d08945a5e3bf76":{"name":"@balena/jellyfish-core","version":"2.12.96-renovate-non-major-6fb878ddbce41cb88f742dd612d08945a5e3bf76","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.96-renovate-non-major-6fb878ddbce41cb88f742dd612d08945a5e3bf76","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0f87068228c8205a8c6cc999c545aba1c1152d87","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.96-renovate-non-major-6fb878ddbce41cb88f742dd612d08945a5e3bf76.tgz","fileCount":99,"integrity":"sha512-jq4TLTS17aI9kTK8h/mHRVnGmqO0AydYlgvHQR6/g9whqUhlACr/l88WBBQTBkz9f7X8ioXMLaDR6vOi58bMWg==","signatures":[{"sig":"MEYCIQDwpnnpG6qhW4RRujRtaj1v+2nSGCz/Knxcjl4eoK1sWAIhAMmnnFFO20wtUo75CKw8PaPOF2taPf2vwLVznkW4hPcw","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":699761,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgfEkWCRA9TVsSAnZWagAAj+IP/irZp/FFzqK9C9fG0CyM\naqR2IIuBuw7ueAZWc4b0nhLkPayFWFcxwxf+K5B4bOLIlG88Z+H2ssjvwqjo\nppkxhQXLcyLuec2lXK2RHi9txVx1f3kfGQKalUUq4ioxtP4TzxZnVOrrItwG\nbmzDJUQe/vAd+GwMoCBsbl5eNxKNfmvbBVkEGp7n+QOM2jvCqAcn9WqZpTip\nFwUi00I/IcOjKnyw7Gbn6x/C/TmbU11AU5yKBuCIdwIee72Se741Zxh6wg0X\nLiFUeiHPPoVxujMZvKwCRqkzrGmQThvDaOUEmoabkW3UQt/qxoHH2IX5I1tU\ndU1swakgT7cfeCTsAj5wjlt6Bcd5Wwvm2GfQYmrdRjCwtgNZoCgpfBHAPmEL\nsp4ecKyrHLADtM89wc1RIx3h5xNgZh7uSxx/rwfzvb6K4H1xf6sGJ5iSyLtj\nIVLDzUwUhcr2IMmwyD3WXFEFxJJF/gT/2K2hTkm0yiF9DMzNYZ5aanSLRse5\nCMxZPFBwgZRu92KCkAGJM0xIFmHCTKCZdSfEh1+CK7mxjgt+GfHiIAdzQX6B\ndirYSeCOwDflU3QTE171Y6AxJy/jRZHwvcEfDslU+AG6/z6DYj+hhIDR7GNG\nr7VjexRJKhIQamze0x4WqyxfNrKimSttzgPcWqOYTDHXs0jgOCVINJbSc+yn\nvEEt\r\n=cHFc\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6fb878ddbce41cb88f742dd612d08945a5e3bf76","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.1","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.7","@balena/jellyfish-logger":"^2.1.27","@balena/jellyfish-metrics":"^1.0.116","@balena/jellyfish-environment":"^4.1.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.122","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.96-renovate-non-major-6fb878ddbce41cb88f742dd612d08945a5e3bf76_1618757909943_0.20819599747188433","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.96-renovate-non-major-cee39111439337f7f8203fd8f532f50657b5ca06":{"name":"@balena/jellyfish-core","version":"2.12.96-renovate-non-major-cee39111439337f7f8203fd8f532f50657b5ca06","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.96-renovate-non-major-cee39111439337f7f8203fd8f532f50657b5ca06","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3452981810a908e592b8db3997f01f0ad63c7638","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.96-renovate-non-major-cee39111439337f7f8203fd8f532f50657b5ca06.tgz","fileCount":99,"integrity":"sha512-2+M2BifjZCVTtx3P5RRKrl/L/y3WwwGIuW+a41JAft0QCE1/3A4PLDrY6ioM1QgbRuaqEZvZaCQ54G2HjQ1AKA==","signatures":[{"sig":"MEUCIFxFZdP2Azda15aS/BafzXjJAq0pDC5n7JFaMeTS9Ai+AiEAwTUv45JkvH0mqT8J8Dfn60teVNp5XME/kHe35F4bjgg=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":699761,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgfGemCRA9TVsSAnZWagAAXJsP/1gda6Ut70tB1tzvMzSv\nGmn5x8wgi/UPhBqb5xlgVUR/MV9W/7n9Q7zORimTO6SuXejeJxi0TvXUcDtM\nbnwRIdyJlvotosuPvOMymtBwy/JZ7AXx4wMoinlNv+6E5kfTqVSvdxY0uhvp\n/PmwYSxbvxAHG9akwChKwh/BOUxrPScT5pxQfaNwuobbv9CFS6uNKG1ucdZm\n3Zj/0XGsTSX8YUSYACYegNUMLTrHx+2uQCg8d35qgw779zIwHazFN6KMKML3\nqWa13mS1oWPEis+u6KO9XWne8IcuQbkUmvtcoB6RHfJLbMHUDnFKfuiJ914V\n8qr5F5JtGV+rQrELVs1EeKkEe/9vt5bB+HhVER2vJ+zLC4PcwqQfpLXUga+Q\naZOmvO8zO9Zsb+zRth71SnWW/aX8d3oHU1yymwHakEB2w5kRiXHcQeMEpgmL\nud1Fayk0X3f8yzEgRt+OzjuKTWEKPvhBeSx4lJly5Ej84QMgj+euMiQ5z/dP\n6QwLZTw1gtcJK/JrRcWqPQX6SLsoUtt1GBogDdMDlAKabadwe6z94qfwcBa4\nkJRi5ZH0mxPVY5mupUzceTf/OhgNfcqiphS1YjcsHzJkobnNTCeugY8WmmVz\n7nAGIOggeQp8U8sI3jZAGI1iH5OYJhbR8R/V19nc3CDjnJe7Uc7fGZBqR8AW\nEAVA\r\n=ULdS\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"cee39111439337f7f8203fd8f532f50657b5ca06","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.1","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.7","@balena/jellyfish-logger":"^2.1.27","@balena/jellyfish-metrics":"^1.0.117","@balena/jellyfish-environment":"^4.1.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.122","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.96-renovate-non-major-cee39111439337f7f8203fd8f532f50657b5ca06_1618765733685_0.03973185214059716","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.96":{"name":"@balena/jellyfish-core","version":"2.12.96","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.96","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"dec82d236179c83b47dd21311b2ab9f37056aa6b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.96.tgz","fileCount":99,"integrity":"sha512-koCgiV+LQqy5nHLVu6BH8t9m1RbsTo4hihMdLIEZW0giD8ndwI+eMmhPLD0PdCdkkWQOv2M143RfvBMMtHG/sQ==","signatures":[{"sig":"MEYCIQD3vSRZJugVyWsAudpdMvYNXcBixfuP8y1whnDjJqs1vgIhANSj1izsN4ifego3H/4GDLX4Hxe6n/NhQ12iCMnk0XQz","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":699701,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgfGkKCRA9TVsSAnZWagAAeXkP/1oCBEi/9XrrmMLiP1AY\n7CC4Ou8RU0sHqwagR0Mas8f/B3dFtklnh2Ht5dP8xdkRq7PNZYW3uBfrD0FG\nHSndjjoCBO36NkknuDBcexBIgQ/4RvJ0dwRFvvX0Xt1JVxGeS+gij5eig5qA\n+1REOkoCEh5DROngK8bTE9d3ILG0xS79Ja8+uTtoXzmQBX2s+qYeud75uvqh\nlUVRJ8TPTybtzjnM669Sh+H9OZ0/InAkYbyjkir0UIw31jrnqErwIBJ7GvaF\ny887MuKOu5YOlFCElvrqpM+thSc3SuYk6jj7Rjs62GEwmjRZngctP7koB6mZ\nN4VgZvb/JK+zA6Tr5b9XcTnmLwoxN2iQHMEctxRw242pdvLRykQOMtyfebmM\n30nFJVmEL7jS9AgR3J7yZdmWnFk65oNk+YXQ2xFYh+gccZh/5UcGlMM9Zp/N\n4QntJMtbQ7adS8A+L0EArqtVnnRtdNYwXKaKuz4QRfPjQimhQ0D6tSzbP7Gi\naLt9bknwQ345fG+kgYuxYLVj8WkyymsWwmPPhnLKt2nv6jyjB/TufgbG5mOf\nv4H2Lpz4e6WG0VLhruS1zkSMd61Zxw5oK/bI+2/TOv75PhBTK/HDza5DidmG\n+1FJmyDwTAVRFyBDCofYh0W88Bi9/kgLKrMdDmKFoEoYA1y6l/5EDePWD/5A\n/gQ2\r\n=QKRt\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d92d94a5ed7da3051ffcd48ae4098672a45515c5","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.1","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.7","@balena/jellyfish-logger":"^2.1.27","@balena/jellyfish-metrics":"^1.0.117","@balena/jellyfish-environment":"^4.1.4"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.122","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.96_1618766089666_0.021195841759433787","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.97-renovate-non-major-10940955929ef9071d59de062907bf3fbb2fab0b":{"name":"@balena/jellyfish-core","version":"2.12.97-renovate-non-major-10940955929ef9071d59de062907bf3fbb2fab0b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.97-renovate-non-major-10940955929ef9071d59de062907bf3fbb2fab0b","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0fd607c99b632a4ce93260db12c16296b68704a7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.97-renovate-non-major-10940955929ef9071d59de062907bf3fbb2fab0b.tgz","fileCount":99,"integrity":"sha512-Trb1bN04ekqbzKSUHbxldmIMi68ascy1WfIXRdujiNLWhRNeUFlIYXRW8KaqWysrqCIN5D+oEEPZdoMsJSJbeg==","signatures":[{"sig":"MEUCICO6ohH4wdC6XVclFlKSnxLVnl5iJ4rWRMUzjRXmFQi/AiEA1CJ+GkBMGr+Tc60RLKrNueSaYBpTZ7Fjt5xU92RKlkE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":700190,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgfKDlCRA9TVsSAnZWagAAqCwQAJda8hEPPWQ9ttVrFyFi\n1K7yEt6nsWeLDYVrojqtKcoFeK1WNbHogpYF/hrtBbRomcq57TmkF1ABaMIX\n2i0CguW5haSUfoh0kGKHZbO18rJ4qIbtBenYIGBIMyMZhIKCyp74VPKS1oVp\nVSzWttP4GYGYxid0+V8R+1IbOI/f9DB/gtG9LYnp40Vxjw1l8cTMsJOMTrQm\nnNNQe0ekYbScNT3oWKyiX423v1wLVfJsD+1qf8roj1VtZUpftBHAE9d0uN5C\nT+zXFHxHPjmhgtYhF7qrQil5lylMYrTXvp6tEFpCQldUYDuIjZSXoStHwYo4\nPM+lIbVgOc/bVXneU9WHkIPjYujT+9gQ18cROYNTnzzTzSpyS8vagPewhhyH\np4R5f9rLH2TBJoBNgZKE6nWhlCIeDhpX9lKPHCs/9skiELwwsqcGwnnWTlnH\nB/+Q4WKbUv9rnYkfLh6wuigJKRByw0jFEPym8koGOQf19ZSfFua6Hqqzur7H\nTNWXatHfV/wVcIGZiD9eGiLVuJe6P0kN+LTbnyR9bs3wHbmfPdkf+EWsx82V\nDHjtiw/f2xRINzXMcWVS6sajljFXwxeaWqB2OyCMY1f2/AJMtDjnpkVae3IM\n34KmSE0UMiflt+jiyq0OiC5FyFx5mBnPCR1KPK/c6YM3DLgwB8g59CateACX\nSqTv\r\n=ISyV\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"10940955929ef9071d59de062907bf3fbb2fab0b","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.1","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.7","@balena/jellyfish-logger":"^2.1.27","@balena/jellyfish-metrics":"^1.0.118","@balena/jellyfish-environment":"^4.1.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.122","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.97-renovate-non-major-10940955929ef9071d59de062907bf3fbb2fab0b_1618780389349_0.2527089787309187","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.97":{"name":"@balena/jellyfish-core","version":"2.12.97","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.97","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f272b5602a7021f3b31a2549972280085a40abc0","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.97.tgz","fileCount":99,"integrity":"sha512-AlFcmXsFQfsswOfrtBPQ1HNCCy5Ek3O8kbXq/coRv9w1EIaI28m8D+WAuIDignkUESspI1tNtg0i2+L7vlvvxw==","signatures":[{"sig":"MEQCIGSXr6lV3zOUUnaKQkCUH2ccitfZ4ITp27dlTJsMHnYEAiByduL2rwUza/x6798k+lTJc6O332ERPTn/IzpAxAOb9A==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":700130,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgfKIPCRA9TVsSAnZWagAAC8IP/27tLsZl4KEMu3AHlQ9E\nyHnk9nlh0QEfIakc/wPGqCNLqJb+4GRovGFqoTUOdUKTARL57kvqBiiqFlkG\nocOj3nGLwsARKvjXqjwgz2Sad/NJMo/ULiLANMx2kGpz2FO5m0GsXso8nC/h\nW5SskWvCF70dbcddVg7pZWoMKyLErAcxE4boYb34ZD4aCJwmZNe0RILZX2mB\nKNSy8q8pdinHqme5TwEPB+siJd/oKB3dvzpBzbuFD8ulrlJ6OwY7j66Sey5X\nwtInnSIog76KM0f09YymNAABjjTk96INTywcxUtlrl1hUww/NG1zR7rp+ACR\nd5IISDaKax/swsVWwl1YzP6rIQSmubjKZ9a1nKpG+o5QJ/q70YA6l7hIIves\n9w9v+TvLsrLFe3RVEZTvtBx6aj1zFP3sv59ClKW0vkWhFXNBjylU9pYXiugz\ncPSkXguZxp/zcYOgMG4CjARA9WZQVXf7c6RwT3u5LX3OsnYW24YH1At7gXoT\n9sPFsuD8KaiNofckDX8TjjwL4bhanq4+gjUxgJyRhBtNuKqSN+A9DrIMPHxF\n2Rpv+s5d3vbltpmsYf+Q5e/lbcPHPj/a+nk8w/kk+gtH6aJ+1F4n5QVima0x\nqw9eeupYaprOtafPKfVz5OZeqR2/l4q4UhNKs2rpxnm8Gc1rW1XZ4cwCwwzI\n+Uej\r\n=bd7S\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4e3c22b0434179252eb0348b35cc9f2bed20937c","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.1","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.7","@balena/jellyfish-logger":"^2.1.27","@balena/jellyfish-metrics":"^1.0.118","@balena/jellyfish-environment":"^4.1.4"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.122","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.97_1618780686945_0.522930912151101","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.98-renovate-non-major-5de39f4340d8f8e77a59223a15ddd4b9b6d5f3fc":{"name":"@balena/jellyfish-core","version":"2.12.98-renovate-non-major-5de39f4340d8f8e77a59223a15ddd4b9b6d5f3fc","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.98-renovate-non-major-5de39f4340d8f8e77a59223a15ddd4b9b6d5f3fc","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"90fc8a4b5165043d0b1ab00eca198aac2c2eb2f5","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.98-renovate-non-major-5de39f4340d8f8e77a59223a15ddd4b9b6d5f3fc.tgz","fileCount":99,"integrity":"sha512-HLdsNzfJNryy04qaTinXR0Md3TYYAjDPw4e5W5fBULeX+36Dry+GKxGTbQVx4jUIXz4FfmenUBIeGxhuIZSS/g==","signatures":[{"sig":"MEQCIFnV6LXM0Q3jI+osr3l/TzuDp48+joCiSR0OhrPVuq31AiBHWrWiLHk7f4eZ+pQodZuushO/1mOiTv84PIKbRw8qfw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":700619,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgfR0uCRA9TVsSAnZWagAAuxIP/0mm6pCOkrzXouiB9o3q\nEUZ69PyPgU33wySQ5mZ+QI4ELlCaTDWA43rTcl2jcNC9h6VT5YAb+Dau2M4S\nSAw1HaQajaOOcfz6XgselgZR4HJ4z5kD6kA5lQaIhmbZ+SMh05p5vw86Nq/i\nivir5QQj2xg+BRm3egjX6C3nyGNtK3jaX+QE4f5w1UKF6RV+bYEHIXY2MneQ\nIPfhGGgjyUtVk/onmCnqqOTRPDVQk6Koj9Co/RZliJAuDT0ShHEFWFGC2aqM\nCoseIKy+HWroy88EAm8RzGH80FeEjZuViZqRZ+IFpnHgGpE0T93sUs4IWgwG\nMnAln+TqFnNnl/MIovmSuNzVeyrXigRBusVlCzau+06W/kL2YKDFeJXC0iIZ\nXZQHcylgB0RUrG+kuo+paJimqTfmbGjeCihAwoGlboHrZazJrpYbuSxOUobH\na5sJFRW7mhW9IJuvQoXK9IupXV4Cat4bl4spByhIhvJ90HGgyu7u7xSJpo+T\n9i7xsyji3TefOI2oCL7zty2S55j9wjNplh4EI4nsYS5NRsGSpYrnFvKfK63x\noAXQCQw2h9OKgQK/caKY/DpXLTvpDL/OAlIeBJ0GziYZLkj0yWi91BnISvyE\nrLIm5/jc3ywI22N5a2SO/1W2oblUv8wFc+KflGUzdh7JTWZKMIGYepv/8PWg\n5mw/\r\n=/uTs\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"5de39f4340d8f8e77a59223a15ddd4b9b6d5f3fc","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.1","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.7","@balena/jellyfish-logger":"^2.1.27","@balena/jellyfish-metrics":"^1.0.119","@balena/jellyfish-environment":"^4.1.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.122","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.98-renovate-non-major-5de39f4340d8f8e77a59223a15ddd4b9b6d5f3fc_1618812205785_0.5608680416494272","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.98":{"name":"@balena/jellyfish-core","version":"2.12.98","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.98","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e616cecfaea4e2a21d4b1bd1d74537de23e4978d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.98.tgz","fileCount":99,"integrity":"sha512-4VwNc/Ic3c9a6KI82p+nRhQpO7OxtK+CM/QjITv4L88CyNvEAPhLT9gGJagorWRSXTb4/E4rD+UiWCgfIqmhLA==","signatures":[{"sig":"MEUCIQDVpDWjgeJ4GvC0X04nAMqY97+zD0fqQbtpVtGZBa4b7AIgPf40Rjw0ewgx/ruhl6tnUKrWIKirSVIrhRg6O+rD1oU=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":700559,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgfR6iCRA9TVsSAnZWagAA7QEP/2MODl0kUn3LEeWFiOd5\nUe8pbnUokOjkjQqjDPkrOI+nbi7Y2f+othuSS81U/CDjbrbbLn3lDFW7WwHR\nN7eIxEHytlGZMZK3uBUbRc1IR3JLuvZH3tbvIUXFD+fhfo7pIVal449EdMdE\ndClNCJeJSytQIxYW4U7S4Xm45qTG4qLfwczETd179PV+gmMdl2KEmJmDWTxt\nKD+YgsLDNI2CiGMPykWodN/NYcjcS2vwvqqC8gaWWNY+3BwrF7mHXNyuBDst\nBnuEsGoX+sjNw9cQ6go2i2sgUXzkfOsOSRlBCA0Nz/LAHnAb2p+GsSZRjVHf\nhPpS5WoFjrWOSqrlrchyALDXDsnt6puKqkhd4Htp5Xmm9roh0pbn7ao6hf+Z\n95vdAu/oc5VGu5jQs7aZlFfFxsklCPPfa1VxW/pA9R1a+CbxrDyXbejqsd+W\nNYdzhI3Wn6sdxaT5dWiK21MQ3khn8x4B0gWPI0zExbqNJKiPBGr/N1Ad9W74\n10ZohcirFuhztUfZfjAEarBbU6voUlryqf9HgQ7sO8bzT9qVJrrMeodE0CaZ\nr8zQ1hpZvgOYwmXx66zgO4aeVm92sMW7W6SIis1kfqt1N+Eb4ahstSELOHwu\nV5fqrCIg+3yq/1Es8huFBJ5JO79EHUF/9V7XR16bwYpTykCn3Z7Dm2riOJEi\nsVi6\r\n=EraS\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b119859fffd32626adf35749205221365d2efc65","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.1","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.7","@balena/jellyfish-logger":"^2.1.27","@balena/jellyfish-metrics":"^1.0.119","@balena/jellyfish-environment":"^4.1.4"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.122","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.98_1618812577781_0.041153069594107805","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.99-renovate-non-major-4149e7c0b49242627b48d3206261fa66e1b9f52f":{"name":"@balena/jellyfish-core","version":"2.12.99-renovate-non-major-4149e7c0b49242627b48d3206261fa66e1b9f52f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.99-renovate-non-major-4149e7c0b49242627b48d3206261fa66e1b9f52f","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6dcfd2cac756973045281029fa16ed39490409d3","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.99-renovate-non-major-4149e7c0b49242627b48d3206261fa66e1b9f52f.tgz","fileCount":99,"integrity":"sha512-j+lT5dTgWs+Pxrvlj6FvpBp8cgvcZCeMNy1cbTBdw8P04Atfboxh+t4p7h2G1i35YM630XffMyWUp2DJWOKauA==","signatures":[{"sig":"MEUCICu4LSAU6NQyhPy6x1JQjhQjPoEYvP3E06skDUIn8nwrAiEAuRLbfDIVP3sl6PJKt05EngYMNWWqTgDiHMKuo26kWqw=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":701048,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgfiKKCRA9TVsSAnZWagAATeAP/j2jj7moHGXyG/SJuNak\nR7WlxM36beqgCNteQ4ekvAzcm7JCNlXnGNN35BT2Qf/55p9+42dFCIYsFPvO\nA+ii+wxhee4ExchHzE+YJqTrUlR26tNfZ5Sx2if33oKw0ZjlFq/IkXythnD3\noc8OMnzuuj3bUnRVtIT8Ocf4x4B9mfqUI0Bit6A4PyBUrVgQ/fHatNSyO6tM\nJtn0HxszZcIgwgjFk2AyPNTzPOszrjQfHwlrtur7qFUuScMLSDVAO+iE4RNd\nJYL87wHVElwwxhAb2u+ZKtcj+J485MQUqokxO4pOshkRCSe0p88goYk9EyV3\nRg++AaBz9Z/3EUq3RMOboDNqKOXRkaEUlCj2YTFPT+pg3/h4xIxtnoR2H2AT\njKno6NXh7utcAFCdtx6Vay5DTw6xXmjubpcM9VDK7Q2A1et1SUwkQZY4YnWb\nNdu8PBtKNwo6mDQ0yOcdmvrxU9WFJTsG15Absc3cq0xpQTCDU55ynpNVOeJN\nuM8QvLPIJ8GcxGBXZewlvId/CoMGUItMjTmLeftsTHIvdJY7/Luue/zqb3EH\nAwcSFs8KcDhOeFmv49icGqJFTI9iA77/MH8jyvtv10H+B6Wravcx4+1kEF+w\nv6aPXm2D6uVqmNjRlsiXAawmuFfkK6eS2ABlIJBYEqpGV2Jm0rdMALO+Ooh8\nyml3\r\n=2kJJ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4149e7c0b49242627b48d3206261fa66e1b9f52f","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.1","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.7","@balena/jellyfish-logger":"^2.1.27","@balena/jellyfish-metrics":"^1.0.120","@balena/jellyfish-environment":"^4.1.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.122","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.99-renovate-non-major-4149e7c0b49242627b48d3206261fa66e1b9f52f_1618879113537_0.7130731994083068","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.99-renovate-non-major-29f37700f77dcbbf106e82c1fd9e3c3a2b4def21":{"name":"@balena/jellyfish-core","version":"2.12.99-renovate-non-major-29f37700f77dcbbf106e82c1fd9e3c3a2b4def21","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.99-renovate-non-major-29f37700f77dcbbf106e82c1fd9e3c3a2b4def21","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"274246c5324e42c622a7fcd4ef452f1f26581c79","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.99-renovate-non-major-29f37700f77dcbbf106e82c1fd9e3c3a2b4def21.tgz","fileCount":99,"integrity":"sha512-rxaPWCHEtZLmD9RHEtxwTuW6ZFlYHudew46jqaot8eWbr47goOewWP4pDoU/ZsaKEYq5LzBSiUpIU4FoUb8I+g==","signatures":[{"sig":"MEQCIFcFReyjKlxBAqi50k5APcylWqJSHUQIcFjs82UC7gbCAiALHJuikS0tugl+MyUz26BNzam9UzL9g1I/BKiOoPX4iA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":701048,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgfkWgCRA9TVsSAnZWagAAynYP/R4Msx+3i26EVdt9qNSH\ngg8OncmGZqU4GD1pjenRjsF82Kkfyh2dAOFlgDxFhZjJMZAauPrJ0WGRvuao\nrkHsCadi3njTGxMetUruYBuzJpXGAl0mnQ5TuC4cG7SKj8D+tZIZvzYJkaxh\nOQXWIzkjMGiRVjB2qXW2XAj9BSCvySIvM4ncYNBRWsSvVN9C5X1PohyY8Qef\nXc8qoiW3rtq3IZk3uFszf2Dk9lTzptTmJNz0+IhU9tu/qoC4P9usmGli9Nw7\npEsj3nW9y3ejCtMxNbkec+plTmTJt9b1kNBbt1VOa/22F/NxEXhbQlCu+QwZ\nK7zMgOWAXuLtb4o/jM6Rd9ECJDF34lOeXq9Atd8Sc9zla0FHuaUvaVCP5rdc\njD+sXHKUUIOMcxdFeezsnrbIOHobv+BAEjUQ9PQ08iORDfY14cMH9RmQ1myB\nusO4+TO5Fr9801HeGAPHUhxkGGATSO/uvbNw6U6hVDI4SP3k1HQUKUcqLKRA\nyPWiyGJ32xvJ7Of0mRMOevHPa9WOXVkj+6B44A1Bav4CR8p6UN27CtiFYlqu\nle/rVDHk1z2GdGeUGNRtzc2OZMg3Tq5F6AfbeZqsEsk4fCu4TnxNt6698qXV\n0agJ0Fkx9+rZsXRLU1HhNSMzUccSWO8bpqdK9IDoPaLuth9U0hIu5jDr9OuV\n0Xkb\r\n=veS9\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"29f37700f77dcbbf106e82c1fd9e3c3a2b4def21","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.1","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.7","@balena/jellyfish-logger":"^2.1.27","@balena/jellyfish-metrics":"^1.0.121","@balena/jellyfish-environment":"^4.1.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.122","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.99-renovate-non-major-29f37700f77dcbbf106e82c1fd9e3c3a2b4def21_1618888095804_0.03077024262923911","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.99":{"name":"@balena/jellyfish-core","version":"2.12.99","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.99","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b1617a188a9218cc9294730329adb3b7b0898c2a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.99.tgz","fileCount":99,"integrity":"sha512-ijEUjWlBGsCY7PqbedJtbutEXl39HI7Gvswr2apViUWBeHesuTf72wqUkXSghDoBMRF/sl58ROkrNocT6BwI7g==","signatures":[{"sig":"MEUCIQDnz+dWX/6WBgogGpWlMGw69d63/rC6Ihr1QAwVJYuxVgIgApWcoEUCrkJhxLCrKz0ycMlXRcRwe6ZNrUlmO0Z10RY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":700988,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgfkfQCRA9TVsSAnZWagAAwowP/1DhT2D31Daz3ZmdKO5U\nXZc42FLDRKLvkx6wRlH7dX2PJOxpzjx0qH+GXuFfdSI5eHsWNla+yTclol/W\nDlBmMsjNC9Hmd848WzqMUqnMVoIVZpwpHb/2psy3qjv2dgFsTnDfxem7u/Kp\ngTgH+4c5TOdJLDxTZ5eTPqWQHjYXXaCITu9ocIb3KrPBAO93WlQuZZiE/EtU\nyg6U+06c3BzPXPBFGcWLy7dV0ufDjpOqTF0N+eyyASgZLl29xm9rJXEajPmd\nEhh+xR+3WNTwCfnfZv+sy+rZl+knCP6pWlOx3Rlo8x7R6HRsECOKZtsv8vNy\n32luvb+ItYYUwFOd86ly9KYhLuVNUDcNAuwQvEIY9S7+0VmFAkvreFfjpbMY\nF0s+heFXU0FrTKhJ1PYxKUVuhcZmiKVYN2MohuO+JfFqUJUVh+h991oUzWx+\nWrz0zP0WiV01rdloHBT02qywTfHGzzvA21Yw6SnvwtrUiQi8759qsW5Tyb5M\npIBobGA3UoZFZsQ9HVKesOxEwsrNRcjok2Tb0/heEZukbEPFEotI9hLUVDOR\nxK4trTSxmKfJ1nCurEC43ZT/qjx90Ut37ZojSq/use4HaCxAOBjF0M7jwUnE\nt5+kd92bnx+Sk4auDhNqRKrwi9PiHkydL6I6KWDNuVthjxSZ+uoE3syMXmwQ\n96qr\r\n=JPli\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"1ed04806d8c8ab5ed7602360aa7cb3c83fcc43bb","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.1","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.7","@balena/jellyfish-logger":"^2.1.27","@balena/jellyfish-metrics":"^1.0.121","@balena/jellyfish-environment":"^4.1.4"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.122","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.99_1618888655618_0.36007482260704116","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.100-renovate-non-major-75130e96f81145c41d77bfab422a827a960fef31":{"name":"@balena/jellyfish-core","version":"2.12.100-renovate-non-major-75130e96f81145c41d77bfab422a827a960fef31","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.100-renovate-non-major-75130e96f81145c41d77bfab422a827a960fef31","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3b1e1b51b6985049d46e32d477122b83f094d6ef","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.100-renovate-non-major-75130e96f81145c41d77bfab422a827a960fef31.tgz","fileCount":99,"integrity":"sha512-LNsrX5NQj3LqMNbf0gNAErXEOmq19piJQyZUiQlr8kHv/wxIrwEogOgi/Hopd5oilFXRfyRYor0ogQnlacmMcQ==","signatures":[{"sig":"MEQCIHC9m9S5Vd0FxwEsRL+qGRs+uMg+NFmWrD1WQhJ2QK2jAiBYfczu+8ESrumLajZHDkIutSn2yzW5xvZwRA6cOMxymA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":701480,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgfmLeCRA9TVsSAnZWagAAzdEQAJaRkvF585Q1bJqrynMT\nFa3HPpnIbT/zk4HAvJhrAPTm9DIqKHpR4Vj7GIaaEUwhAWQpofWqY1QFMzMQ\n/TundsvGU5DqPYAs2GJgvKYTZGXkPxlBYzAo4suP9vj5dtv4WWth6JBvne3L\nq5Dg/r1GSTmGwJ2c7XhcdzEbGQrY56zMlU4U1+3bOYzYbgluanX3j22ye2df\nteLDcp1aV+tocsIEchJT8pEIruRoqdI5DLtmPViIQEfyEjV0hjd/bqD6NEI9\n9SVGCtYaT2Jw+R0wMtpI0dSD6r1/pcoK4n1vyVFCg2h55c/263o2CAfXyyUZ\nrisfVoaItjfO8DZYHEcrax1hjJq7x4iaAA9dmA3Gh25dkVMoRsS2ZTeZN3kY\n/pN5eWZan3k9lYNpgYbQzyQZtikqRoEaIO/5GV/4iYJAXaxNzdXSh9OEsCgU\nuLoF9B8Cdohj+Pq3xbn5lrPlIDZlN52v6MBfX1Ot2N4gNCg3F6SP8PE1dsjY\n4aLZ5tgnKaGCuxN5b3NKt6jxekUXZP/uVEMKfQxZ+RchnhMVXCBjmIrxhqrT\noPU4FpNIwcFl2mBtKqa/XWjmIeoK5jT8hJ+5SERdcVR0iDOJaPYiwlyV5Stx\nqSOkepbMUeHnH8vMt8qytFNe52U2RCR30xAYGLoh5UeKjlt76M3KDXScu6Sw\nqTJr\r\n=iJrf\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"75130e96f81145c41d77bfab422a827a960fef31","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.1","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.7","@balena/jellyfish-logger":"^2.1.27","@balena/jellyfish-metrics":"^1.0.122","@balena/jellyfish-environment":"^4.1.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.122","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.100-renovate-non-major-75130e96f81145c41d77bfab422a827a960fef31_1618895582145_0.7759799074007667","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.100":{"name":"@balena/jellyfish-core","version":"2.12.100","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.100","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9ab8bce045335c4d86716bf699b881eb3b3b3629","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.100.tgz","fileCount":99,"integrity":"sha512-yXo8ajdRXo9xnKAO0jiDS19i6zFqQ3alEBZSTxzjSnOIQvMX8YwMdHDkmx6FicOsX7h/wDiTB0z/lKEGnD9KfA==","signatures":[{"sig":"MEYCIQDDHJcDN+e+TPt8HnE191qkYOj/8UCrFqClmwdDyb9M2gIhAILh/ORR7mtE5GZLy1rsaxAHkZzFqE9poANhJtSEakdB","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":701420,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgfmRWCRA9TVsSAnZWagAA/KUP/jLsUDQw5Ng8/6ScmRJ0\nqB2mZ4SFf9TGZMDOpgk2Das3KBf8f6BZftZB59uir/rhcTSbDb3xPMdUObM6\nfjsC6uiaEz8f0qJtaLjEF9bohn8ZOm9Lc9cjbZXF/MudVJokWEn+CMk6wSHm\ncHGJY292u7mOSkEU4HxZQL+NrXNjjTf703Mi43pBhgjRNGQBUiUSGic7ZJZL\nTGbpykjt07s3VpwBVoRXtTbrQeihR/m9H7I7y3dS4yIs6khJWOvS/duBMkhw\narHZw3Ficsnnex+EQfmsT67e/A5YTiWWfcp6AqpMypIm1BZQmPg1PtxnYayS\nCIuciNAuxJjj8z0Wid9Uknd6UV5s61nPdAV1C127mylS/enrL8CixkBi/bRn\nMsgxdzUMJE/CFSlyUgVVdI4Evt0jTYVI1EKPu25iKiZW+yQDu2pGAPY/bzAp\nVfRNXpZN94IpnwB5HBhX2nrs1pFgIuZ+TTYDtQqPufuB+uUsJKMOWaObEXkj\nci3L+9ETejo1aVPXFhJ1+OqkCZkY3vuNQbP2OsRTKVg6mAcdRdFNITX07FVY\nYcTykZGGDMHtp+JIjFgW3IeIRw2WgxZgz1EucYYhDc+9j2mE/woXqlXUz8aN\nu9XAhQkoeP0tPEaXyItiShU8mpUdI3+J6qapZ7WHCJ11j0vT7InqxtQ34xJ3\nAi9e\r\n=DRVj\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2a53409f4438d989accd3a4a08c5ae6a1a0338d7","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.1","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.7","@balena/jellyfish-logger":"^2.1.27","@balena/jellyfish-metrics":"^1.0.122","@balena/jellyfish-environment":"^4.1.4"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.122","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.100_1618895957930_0.9357491407221272","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.101-renovate-non-major-e0fd4a076a2c79ef1a7bd028485d8f2cb7d9e9d2":{"name":"@balena/jellyfish-core","version":"2.12.101-renovate-non-major-e0fd4a076a2c79ef1a7bd028485d8f2cb7d9e9d2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.101-renovate-non-major-e0fd4a076a2c79ef1a7bd028485d8f2cb7d9e9d2","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c017dadd76e765af4ea9cbfed76bca6e6fae17bd","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.101-renovate-non-major-e0fd4a076a2c79ef1a7bd028485d8f2cb7d9e9d2.tgz","fileCount":99,"integrity":"sha512-sX2zxyg9auHKqnwXC9HgCwTbxmpHaSdsZ0Pbfrw2buA9nnP7rbNTXpmhYdxpMT5tOEK+EgIRv1RqSmfL20XU0A==","signatures":[{"sig":"MEQCICMoqa1/sqs7nGKeWKNtdutVKevY+f4th8GQPA4pN4uBAiAlWX6rOyH8H08O4iI5zfLMzsIl293cTy46b0uhfj7W5w==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":701911,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgftmiCRA9TVsSAnZWagAAic4P/R9DVHp68Jj4CRIhPfnu\nKlAASSbgVTfP3UR+bwfbrwFroZ7w9DWPwAJCih1ASC6QyT5aUK5tY8Rd/Skn\n/20QUtcF6KhIbWx9ukiPzDDkz1d5JBii4u9MpO4X0VtiRcoVkCFlxiGVnxMO\nRGbp3fLkL4jtQXQvkJDp919NwjkUiPUbT0cpUBXtRQ6swasrZSBO7DOxVxBf\ndBhCF7hC3L4DabPvPNxEwJ1sXiDOtH8MXiBXa9sK2NaHtV164fZhuy9goCgA\nX6u+4bYwmrteo89wCzvi3dQ3V2bU1EBbNGIBZkwCeqUJDgkaDfeNMuFkt8n/\nfI4qtMD+ypdUccSnTQxXSehqpIFewJAr6sV8yQ0mKhyxn0kfp0ndiQQf3tR0\n0p/YA0pYj4KOCTGqG+Tejgrb8PU4w2eSM+9L7zJLkrg9DSoPiNEF9DFlbpRp\nWeDnIja+wweAZtCkqZPidFSIvIgGrym1FtLCO1eZciqdG0y8S8DvXi9SMeCM\n+VB4RWSogC6QUvyU+sUhSkKxh8fvqrlOTH25Zy5DrjWtKtJyhFDlpR3Lu0Mp\njBs24JU2R5jwEKf0AlhTASIrM3B26XE/8dtJHYKr5ljAHP1NeM30v2RcGOol\ncBKsTebOPb8FMvLQsXu+3Zl7rD3FnWrZywSknLWifmk2rnKfDsxA6MD4kv98\nNq6o\r\n=2YUr\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e0fd4a076a2c79ef1a7bd028485d8f2cb7d9e9d2","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.1","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.7","@balena/jellyfish-logger":"^2.1.27","@balena/jellyfish-metrics":"^1.0.123","@balena/jellyfish-environment":"^4.1.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.122","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.101-renovate-non-major-e0fd4a076a2c79ef1a7bd028485d8f2cb7d9e9d2_1618925985833_0.02498510631433315","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.12.101":{"name":"@balena/jellyfish-core","version":"2.12.101","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.12.101","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f3c23df485208067f55d495e5e77e7f0ed0dbaad","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.12.101.tgz","fileCount":99,"integrity":"sha512-umLgQnor0/JhoIp1Uh7dx0P/z497nsnAL4ojI130UQmcUibPb+1xQAGyxRdxCJj736QYXF+W1A+bYVJMNj4Sqw==","signatures":[{"sig":"MEUCIBVbILMUEcO8i4dW8OBfcqyqY3WdgMi+bUE+NluklXI0AiEAwCbW0tmIjRCdyULErZH5akwpFm7xUY6nzr59Rso/bFQ=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":701851,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgfty1CRA9TVsSAnZWagAAE0wP/RIt5II28PPfBOkQwXMv\nwpZDVX9GnZ/QKz985QBelJTqxS4yg3G22ogXrCntwiiJUh+CEsePSPSqQsAu\nHw3UFXDGluVxfV2EL++ukSJ+6HQtLpPkaThp1Lz8gK/zVV8GXcZLswvi0zGd\nh6JBXZ340I4z/5srvB7uY3JUbq76vJ//rv0fjvXTTHIZ7zGLJSByvS5OsY2O\nVkjztQnN6voftavL1/jN3gyPqeGRxVsR8J0LKJgQ+j85PEhsIY3zewanJESc\ngh1JLZ35GQkpbE8Ntvl9hBoUL5uZic+Af+qQ5yydYbkPfYNZsUS9qXofqZ/O\n04JtXMSw2qtoaid5WI9FS8CJcIHioluKgwL2XV6HwwpTRwnh1+VVBJPAl797\ngQC7oqzzuwm9Aq8mXCxWoosYjg5CXW1xoKTVzsEXzeo81WHGP9iOoLWvPxhx\nFfrJqAumS/an4ipa/4CLBb3OBPOcOGPd81ofugkGS0Xonfzfi4Z9HVJLs8EJ\nrCrcAihebhriALFNnuYJfEKNs/IBwVzQ2izJrp/22dZ4DkGSVbF/Rj3VPGxy\nWwQbw6C2e6t3629Yp0ffLngTT1W1wCWvG+Qyj+LF5cyJmaW1btH/fi7Ye8XS\nHe4LMw/j/mW1HU6l1+FSnU2rwMfNJKtcLZ6c77OuEW//ulkBzOTHbKvEbjU9\nzOK3\r\n=s5mP\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"9830f2933e74f950487c3559d8c182858003ef29","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.1","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.7","@balena/jellyfish-logger":"^2.1.27","@balena/jellyfish-metrics":"^1.0.123","@balena/jellyfish-environment":"^4.1.4"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.122","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.12.101_1618926772067_0.9140229729929836","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.0-top-level-exports-89a207c62627d1542653c7ac5c99c50dcfb89dce":{"name":"@balena/jellyfish-core","version":"2.13.0-top-level-exports-89a207c62627d1542653c7ac5c99c50dcfb89dce","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.0-top-level-exports-89a207c62627d1542653c7ac5c99c50dcfb89dce","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f19732c704f18ea1ff16831a8bd8b5f31ac4d41e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.0-top-level-exports-89a207c62627d1542653c7ac5c99c50dcfb89dce.tgz","fileCount":99,"integrity":"sha512-mY8lyar0ozAnwGGO/+1XjckN5HUSzhfiYqDkafa3yVXGR2GYXLoPHVqxWEFw3oiV6qYCI9YtYOJE/fpW2wnCGw==","signatures":[{"sig":"MEQCICMCNCHo/dNYjGva7IeAXJ1SqlIoB/sO+zUl4MolRyevAiAMVUCZw4spu4c2HcZDMLOvztkodHMe3MbMGH2pQBX12Q==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":703041,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgfuMBCRA9TVsSAnZWagAAhlMP/Rof/GgqOPLRRtTGpk7j\n1Xq/jTZwTT8CcLPg2RlPoCSdCCQyuvKYbS1LmaYqu58vRAeMHIWicw18TLL2\nV94dcg24WMqX6Gg5zWT2uXiXPBBnfqCPp0CoQn4dgkyRwmMCohvAPBIEE5BO\n5RJZQq18jSqeJVv3NToXDrPOCAeM8dg+3q3F365RG8F32ORJpnPbF9/U4yFq\neHYc7OdoMwCgvgukuF6DcJ6faVDnsNZvvq3vsZYRDH/ILwrsqF6VuKh3r7oT\nGiTjI6D25MScTpzojLhXbALINa6k0Ob8TGx1JmjQnRr5h7IKcaWbL1BFgQYw\nQdwfhI6gmM6sVJzll+7xdAcUFIizIgIeJWBeRcl8CfxK2LG/ClEmlastZxFm\nXxLj/kFxMEtQAAXBBmmSvcfGwTaE+bwXeNrMz+eG5l0jV4b9nlEGmYu3FgwU\nJ6O7ivaD60vEaLHBKF4yV1zY6VPEoaj/BTXBGaVtb+8hxFKd69w/79ZqfVXt\nqx1LISXjqPLPyKf0VcXJRaa96eoI4s5SfOyspltyKGh/x2GqddulOdhp75Hj\nzoj4kCj/Zzs6ZrynIov2VxpdfrALUsjqFQDaAcIIeBjvv2qhb+VRi6k5KnVq\nyGm3pwNOLlEp7pMHbCx6ntk8Cxsl+yfUKeacn8LWzcQPw2Q6hgulVRZdAUaH\nOzOW\r\n=gyMi\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"89a207c62627d1542653c7ac5c99c50dcfb89dce","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.1","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.7","@balena/jellyfish-logger":"^2.1.27","@balena/jellyfish-metrics":"^1.0.123","@balena/jellyfish-environment":"^4.1.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.122","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.0-top-level-exports-89a207c62627d1542653c7ac5c99c50dcfb89dce_1618928384556_0.36821432437041257","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.0-top-level-exports-bc83291befde3abee1ca22b289e23d4dc770991b":{"name":"@balena/jellyfish-core","version":"2.13.0-top-level-exports-bc83291befde3abee1ca22b289e23d4dc770991b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.0-top-level-exports-bc83291befde3abee1ca22b289e23d4dc770991b","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"89e5b060583e1c7e3d9c8d68d2d50a62864020ed","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.0-top-level-exports-bc83291befde3abee1ca22b289e23d4dc770991b.tgz","fileCount":99,"integrity":"sha512-ArlYyYemNgFTeokZI1aC1UC5i5TZXHy4939p4oOOfgMYXUQWKIctG35Bm23e+Ap+HL+WTEW7eX0GF7xEa4dVrQ==","signatures":[{"sig":"MEQCIFQ+jG8G617ybV+4vAmIzcPwEr0dUx1imDxIgm8fQB2+AiBm8bnZJZm64oKxKBSQgjtdYA9fqeyG/4oOq3MXzlsbFg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":703041,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgfupgCRA9TVsSAnZWagAAMMoP/0TMnCsfp3uJpdAAmzHY\nDRyhO93O18MGCyf/FArwvKXjaLGhg8OdgnTljkIsUU9LRRSHyoVCbw02Uh1Z\njnw5uGtIAvpcYZhW6ZrZeE6srn3nyVo1c1E7G3xjlVO0mtLgnEJ4ENk1lVD1\nd1SxCtWY/c5c7FJUodaK9DhDSGl1ujFetWebhANf0aBXWoL7xxxjf+ND/Eg5\noVaGVtLl9NZpjQKu6KQWik91JCMkkj4x6RDaDOXXip3Em6GXry8OH1/O+u+S\nqE4PtYVkdzkDtp0XOZ5j98fxfbFY+Pq8P5b17PMxlObfqTMWNIk7BvBVt32g\nB1M8qWafek+MmGFn7dVKxu1c7rsTO2A6lDqY8U7B21lHGK7mL63O/SOaKkSY\nustIgYGcZO9fH0XEC6DSAwAO+4vHOqfzvFHKRoE3xeu9Iok7qi5+4k7ipJMN\nHSmDTJxMbWOU1CiQNMUf+eEVClL4WRVGD5yqkKi+7yLovyFmbEkmLFa514ud\n1nftK/RVo3ijS+t+mmw1uGknnkB/utTIXo6Lnz4kxSUXKXdNVuMySGmMtcm5\nn0naYyqOMl6x10zjestdWWqKk2Bbfi3FgCfeZ/MTCY5DCPlcmPREsSWRMhY5\nk5GEn1B50Mi/j8MUFGCnOa5KiU5m9BZ46Xb1xjYBg4NtQWhQ08+Py0L/y8ZA\nlZDd\r\n=wuSZ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"bc83291befde3abee1ca22b289e23d4dc770991b","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.1","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.7","@balena/jellyfish-logger":"^2.1.27","@balena/jellyfish-metrics":"^1.0.123","@balena/jellyfish-environment":"^4.1.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.122","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.0-top-level-exports-bc83291befde3abee1ca22b289e23d4dc770991b_1618930271538_0.1570113932566779","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.0":{"name":"@balena/jellyfish-core","version":"2.13.0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a3b0ad83c5914b99048ac15495060203555d9f56","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.0.tgz","fileCount":99,"integrity":"sha512-qIE6R2brJuCiqAvft9pPDax14DR5g0G2yRUQywC30Bcmtvl9XVe5HDTTJDFEslMuzZsoqJ7xgvKdrC5uf3tDHw==","signatures":[{"sig":"MEQCIBUBNMAYHjWATLGLFQ3KXE1f3vW/1iPdEAMa4JDsApmVAiAHZcQCCRSH2spz2c+MctYtFSedzBMO3DnLryIK5d2P+g==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":702982,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgfuwMCRA9TVsSAnZWagAAuHMP/3MTEE9oSNUKYiY6XUzz\nU5t/q7VE3eujXPraMwOT7tx+yTcErNNkzS7XJgzPMkrSK5VQ8PKQBlWuXQp1\nkYeJVa0WbYUIqhkEijO3WMqk+t6Jf35B4TVAkL9Yisoa840SKxW2iFJbqBlZ\nokz3uLHB/XkAcpgJWG2hcVRfE0xBQ/1K2juoM+r5I0jXV64JBPFsiSuhqNhM\nnaY5Nf5BrA+Zn2pSDEp10sYOxQzQKfsYcKIZhNBXRLQX9U1+wQtU/rsKco00\nRqmZJLT7Pm14EinAaxW+H3uyLUjPbHz7RSrTOZW2HdE46619NUCi/IiGh10o\nV2TUG/ZYGebMA7tvTGP0xH1RcoBwdhm2SSqb8UIUz8hmOySTYmzYk5UYWzVa\nYmFSYklzryMdCj9zng0nFfTBbrBDtFWhzJh0U6Dskbwfdu6mLz413L+zXQM/\nmtGkTIDHEdMs0O7gYp0jYJDfxSyZw+3Yda7SM3mtMSo5zqyAfJEfVYtPT6U7\n2lLVRKU2CAAvBdMXq0xcNbj3Tkok0XHNkn8ZXTIjEA//h1T6GbB9IOvez2ne\nwd3ZMqE7JW1Y0bD+IumpVcMiTUsrAFaMy6aH17IpCXol54g2hAZuXT5mNoSo\nSeH7UB92g4XO7oKPrfAi0mQ5aJADXDff+4GlQVQn8tCUJFtJ9BdOwX4bnK8D\nPeEh\r\n=xI2s\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6831a25734eeff7b189429b365f7f96d8cbaf411","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.1","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.7","@balena/jellyfish-logger":"^2.1.27","@balena/jellyfish-metrics":"^1.0.123","@balena/jellyfish-environment":"^4.1.4"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.122","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.0_1618930699782_0.6139167957792602","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.1-renovate-non-major-e3a6e3c6cbd7b565c5d5f0a7c93f373ea41d03e0":{"name":"@balena/jellyfish-core","version":"2.13.1-renovate-non-major-e3a6e3c6cbd7b565c5d5f0a7c93f373ea41d03e0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.1-renovate-non-major-e3a6e3c6cbd7b565c5d5f0a7c93f373ea41d03e0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"7cb9ad19af89017f0d8aa9a494c19c0867bc2f93","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.1-renovate-non-major-e3a6e3c6cbd7b565c5d5f0a7c93f373ea41d03e0.tgz","fileCount":99,"integrity":"sha512-d5R5YRVwRxFWfuOgos+VaraknuhwGCDEuUABgKIX1mbNyFI1T9J5JbSLzy6bPbibQhMIw1yPO/WGayoTsrPSbg==","signatures":[{"sig":"MEYCIQDfTOrRMmc3WpC1XEb8UhDMI5gZnF2FyHn+i7HOnubyZgIhAIV6zDJutKrNdDklzJWD1t8QrnQ6PpWTL2OOc6Ai7Cov","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":703391,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgf0BaCRA9TVsSAnZWagAAk+4P/2Kf9XeqcERi1QEh1RJw\n1GSpknVF7fiLne0TevEuPt4Rc1nEpT5A225tFjANkMmJ4znUjal3pALgbfrf\n/hw+DjgtlWbv+Z/7+ryzeUtsvXofRKwK8riNPR4hRNUG6ahBgRcM1jlDMLGW\nuiGGGZ7VoUaFFak17iqWkv+Po9g0CgRxFhS5bbamCRy784JxCqi9hMUb5GY6\nZnVD7Dg/dvlCZtPmtfkbJm4kHIEYY0kCnFc9bqAgReunPmetwZq2ca97qFwd\n7elwbG6KaTVn6BiGKYff6KtKXWzk/DzbLxNucsO7mlffSyFmOHzPkFNkYlvx\nRrj6jNRGRi08DyeayYvAWTTPv46cQNi54NbPSL6HUk76zdAKZdMX6h2r5mWS\nAG15iFlsFtEr70Omj/OrNorJXK86/mHFYfTAOZPoGynK81DS1FdTcXx+ucqL\nDaHlOZHIrs0wf06hvHGIwDX3gWVNTY9FJHioCHr4hiJaVo2XheAABMwySJXO\nz2VRKp5Q1Go0IrFCWiL6ZJOJjM7Bx2suUuXhDTH8gUDjWiYfXVUgtRyCmJDR\nkDcV28g2jUdm/p2uN6gyi/0bu9B7z8+74QrHjUfuvTErRhsPgk1gIB6gNoiA\nxvdYmfZLazSP+J+Dq3xMcYSJvNsJJbuYVgvUtdCx+VRFAEhFL8U6oYn/iVfM\njlfF\r\n=61UT\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e3a6e3c6cbd7b565c5d5f0a7c93f373ea41d03e0","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.1","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.7","@balena/jellyfish-logger":"^2.1.28","@balena/jellyfish-metrics":"^1.0.124","@balena/jellyfish-environment":"^4.1.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.122","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.1-renovate-non-major-e3a6e3c6cbd7b565c5d5f0a7c93f373ea41d03e0_1618952282496_0.18041785937705512","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.1":{"name":"@balena/jellyfish-core","version":"2.13.1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a8e3c5be829c219190904a66b941f47ce599bb0a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.1.tgz","fileCount":99,"integrity":"sha512-1UW1f2m7ZZtEr+OAdFAfqtCM3nptc6qq/Qw7voFie+6vL3s2OvjAPBuouC574IXstPVxgXW5Fl2jLbdfg4g3YA==","signatures":[{"sig":"MEQCIEeikXrRifJ0dhNzy3eoBruOmzvVKo7NFRTq9Dmyea7eAiBndNphkHrgPjrVVKFZMIEhVLyPNbt9wLiicp0bvjza1g==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":703331,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgf0OxCRA9TVsSAnZWagAAc2UP+gJQ/V8PntDYG3/zktkf\n+3DF5HdrlD8QFBAI7hqT0HIWdPqbWDjwSvmaGuBx8M2sicKBNWUpDHpfD5yy\nnAzme6vshk2iySeiO/WZ8JzKEksjvR9Q9EJEqQ76TmZnvgDaCQjkEaAyajLr\noPJ8BWgGy4/o6J4R51TR/Ij8nJ7mBdiNC3/GE/0L5H5er8iLk2vKe9K/QXyT\ngmHxF+5d6UfLlwvzRRTjDPkHcmpmVR6nJthWUWN18xg5N1eZBTLTiz6mThyi\nOdxKOwxPgO65T9RuSyjB1TyyQCWnj8QwLQdplRUvhmomDc4DpLr9XMMS26r+\nm4smmmPp5qc+tJX3lJinyKwon4baeIyvz2PcRyWbjV2EN1l/3yqBDOfCi+Hh\nglKMIMlqFO2+FlhE21lKRAGvQpi+gMFtOk4OrdAubEh3uxFZZ4GY66RxFgSt\nxaWjTGZJgrp2+8Z1bkyoGcgzQU5d+xJVgdwEqlpliIy43KTGpNqnH5ePZiTF\nruwBsuC8v0WvGj/+FkXizSOSZinnfOhSa1MDxCcfPNCHhohUhCbmvgOEfjOz\n3QNpUMd/Hs2QxwobVjdCzq7Z7KPbuSYCjAikwownvZZNffr69b5gFWi+CrFL\nQfw0Ey1LoD/f8k0oBRoIi+gSVWrvkm6pYyuVKAZkHRAJ149iOb821XmtA8/z\noTs9\r\n=wz9r\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6d9afde750cbe11c14c15c9ef9997378fc26545c","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.1","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.7","@balena/jellyfish-logger":"^2.1.28","@balena/jellyfish-metrics":"^1.0.124","@balena/jellyfish-environment":"^4.1.4"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.122","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.1_1618953136542_0.6825613107710091","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.2-renovate-non-major-9674c9619a0b466c49da8f05cf47141ecd68b9b2":{"name":"@balena/jellyfish-core","version":"2.13.2-renovate-non-major-9674c9619a0b466c49da8f05cf47141ecd68b9b2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.2-renovate-non-major-9674c9619a0b466c49da8f05cf47141ecd68b9b2","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"dd1b643110ae0d0b17f5d78464fc5465beb7bdb8","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.2-renovate-non-major-9674c9619a0b466c49da8f05cf47141ecd68b9b2.tgz","fileCount":99,"integrity":"sha512-FjFZqydvNKKwYtjxtddMjUVLC3AXB5Ie+KKR3qy7XF95pKfAWACeGeE/D5TICAUKV3ZwkR93+F0dy/GNqbbpFA==","signatures":[{"sig":"MEYCIQD/sYx++Tj00Ik61XSVBITDPJwokMXcJtsISkvb/NRepwIhANGSPhrUThUEcReGTrOGHFR8IU2UysMeYKnekmZOTS3m","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":703774,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgf2e7CRA9TVsSAnZWagAANfkP/2/lGH7dXSrvR24jqftk\nW3pzkiw6nQRu8ifU46d1WL5BAH0+sp5leWOAPrT6by9rloVbcUOZllB5VCld\nzs3Kbw5CgVCHqujOLVAHq0f8cRzEbQpRgFPpR3dGghezl2Qmt9Wkr+RFTn2V\nMDvQtQoJlOOHFrrkraQCJRY3JaNmOVsAI+XJON1FwNn+yPoa+8CD0k3WI0wN\nLev5b5M5vf0DGXFl5o8HSh3couty1HkFA/W8gDkiyZiMsagXp+4gfFANEHv3\nEFMFEuJP7RCAqCXNMFeKLgIoAiG0NutLAesN61aBYDM8jDEFeyT/k5hJKSGK\nQA/xg3Q2VZ2h5lQmlFBIWyJuhxoMIY7rEMls7+G9E8rrevJp5qRAYDPEleVA\nC5oImh3lPB0y5bqabcekdD8LeZpuLdyM0Gm7Dwzw5nvBwU6DpV+YAUDf6gpw\nbPnwAmm/WVn2eRF63zHNcKXePlMCGo/FEfwG29jT8YdLJtC8oJBly/1ujLgn\ncrbU0emCGGk4a+qqK3ShocK6SZa1+nyNkvYeoL31fstH1pa19lpNhy9lamhR\nb/334kaJu35xMYCwS4v7IMIhj+u9M3aji/n0fUL2mva9vdIzE9AQPRzqu5mw\nBH0/8zvIzcij6l6SiylM4b6jHgxk8j7duRSz1/3jhfJSypnLKflrzAn80FJN\nY3Zj\r\n=pjW9\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"9674c9619a0b466c49da8f05cf47141ecd68b9b2","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.7","@balena/jellyfish-logger":"^2.1.28","@balena/jellyfish-metrics":"^1.0.124","@balena/jellyfish-environment":"^4.1.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.122","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.2-renovate-non-major-9674c9619a0b466c49da8f05cf47141ecd68b9b2_1618962363037_0.12483561977942537","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.2":{"name":"@balena/jellyfish-core","version":"2.13.2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.2","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0896a88eee7793ab148aa15d7adb99414f2bf11b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.2.tgz","fileCount":99,"integrity":"sha512-yqtXAvrwh0CURIt5K2zonv9inGiprwJAidNQt6J3ePxnJqDTiqsUs4IzyDFXmA8lb6NnPd7f2fhmIJLBB6y7SQ==","signatures":[{"sig":"MEUCIB7PajvltQE5VbdwWJ/dqNgOBwnlt3x8wUrTyOtqUBBmAiEA+UkQxS6T/tEda/ljev0jLIM9mGhIasfbtqSjhJ2n5FY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":703714,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgf2mRCRA9TVsSAnZWagAAoRQP/273PSoWWh9RFHE3D2Ap\n9dxyXOjCD4DFjzXEoHkyfzYZ7ZJcCdqTDPamsBoQ9aucPA3sfWCg5YNB4CGj\nK4jIfWcGBLsQHWzaKHdml4nonej/U23nmBv0vZ6wQfXX0qBW/yYPoWBIrk7U\n47DosNWs+oa3zcGqBJqgWs6WkhMK0jwVco3gD+1M3Z6UZ2QjlQRAZb0QfBbM\njtra+XQ+8bnzmVD8EoWjws6DFKPQGMX0TI+lLKigzy44hk9cFMDFJxocQIY4\nrxSrP7hMeZ+Ej9a34mxBxKSqb5a1lqWDsKR0/YGCpQPPBtf9HkFAyse6YLRD\nG671ibOturM/jxa7q6x3ya5bfQ12HejbldFuJ5PDaf4iJNqyMbQkmYih/EPA\n5oSIRgQWZzeXMIqebxu6dxb8Wg/bdR3Ag9fVQ9BhttyEWPKP8GpsN5YFrjFd\nlhslHoXb3em2fitn35kIhd5q6a874c66WO4erYZreIzPFi1m4IyYGJ/jka21\n4eGAYNsDeYzDBypgqxqarsb5y/F3p7h6rRz0edVvj+kXnSVnUUJEZJW0JdgN\neWoAJ6ssuRnbpaSPWLnRsq2QIvxHAQUq/qM7S2+0bk1zj8kFj+XI2a7gd4/3\n/WC8gk1kiUXX8b0hMLgCPCbEQCEtTv5HWvbfH9yElMprWjrz+zFel7iLvFKk\nLpmh\r\n=5iay\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0c174881f0ecac5fdf5db39b6a360366d88fe1d1","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.7","@balena/jellyfish-logger":"^2.1.28","@balena/jellyfish-metrics":"^1.0.124","@balena/jellyfish-environment":"^4.1.4"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.122","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.2_1618962832885_0.5343718550482315","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.3-renovate-non-major-c6f03ee979be66f69abbdeaaf909a6ad617f8ec5":{"name":"@balena/jellyfish-core","version":"2.13.3-renovate-non-major-c6f03ee979be66f69abbdeaaf909a6ad617f8ec5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.3-renovate-non-major-c6f03ee979be66f69abbdeaaf909a6ad617f8ec5","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"90ffcfc5735f4ad65c34865db20cd3dcca064de5","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.3-renovate-non-major-c6f03ee979be66f69abbdeaaf909a6ad617f8ec5.tgz","fileCount":99,"integrity":"sha512-j6ZiXqsBElPZJefhppgy5qjQpRMsz12XZmPd2gzReWMkKRemOB6ie27vIRGbYA67Jh0Qo+Ov/fA9NQ3w540Xyg==","signatures":[{"sig":"MEUCIFvoVUQTN7LP/fl2rpnJSv4fQPSjPyZVnCNSsT81/LBSAiEAh1srLg+x12TcP4BJSLzK7RTJdr6qwVZPTxqaWWimyXg=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":704201,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJggB9SCRA9TVsSAnZWagAAyC8P/126t8xMc+C3Lv1z7p2V\noc4aVCaNKJEu5/z6cQMotDecn9fm8ETvpryJKSVjr7QAaMn6O0gPIHr7ATMx\nvlwJ9O4LwAzsI5Vxgt4TMoFeNNKWS+AUHLjhCbXogPWY3NgzgssiAbQuGTLK\nZnkbqdLqb1mk6hZ1yuGH7uAW9+Azu7iWQpd6CASW8xBYb8u35ehO1SkeN3WL\nXp5FNvwvzVZqDIHtjLA5vsy8tK/uPSW1zEMuCZb59WH3zePjon/cvLe3hImX\nGRJp5yiKaolAtKklYq1okonTc+4NFrfneTY0Pj7VnoUTVUdruRooNsUwPiEQ\nbyaMr5fddx/qB1ZdkLqDkpuTc0WZP4gJBuNZPnISWNtKiUbxBMlfBU9i6ROw\nkWr9kkvXDLe0HHH3bAoaNyKQjbJEZn8x+pUmVCnbE6gyeSZ9fno1p0wdxeIr\nCbnMpofuLI+MBgq+a584MCbO3ZXX4dAO4yZ2/0CIOLSvv1+Xy1c2hJF9DTHS\nUOS9SMGnzXwvdqsspfePs69OP4tp74gctcRY5E9esMsIr/RkSqlEmvcBLklG\nm+dyXsgf0oGucEXWTofoJWDHGwMf3oWp4wpVtY/U7cUMzqI1DVuTJgCUv6cm\nPESHxn/kWMpI+nIXi93iyNJY5vX4nzZbG8JZmR/Q7GvEfZL9hEE1B8rptM2R\nmLa+\r\n=QWd4\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c6f03ee979be66f69abbdeaaf909a6ad617f8ec5","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.7","@balena/jellyfish-logger":"^2.1.28","@balena/jellyfish-metrics":"^1.0.125","@balena/jellyfish-environment":"^4.1.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.122","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.3-renovate-non-major-c6f03ee979be66f69abbdeaaf909a6ad617f8ec5_1619009362065_0.35905529728526453","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.3-renovate-non-major-87240af26b040b2b0e1e0000583acae35b468ee1":{"name":"@balena/jellyfish-core","version":"2.13.3-renovate-non-major-87240af26b040b2b0e1e0000583acae35b468ee1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.3-renovate-non-major-87240af26b040b2b0e1e0000583acae35b468ee1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b91e89e67098a7ccd5c80cbf92aeb2f560126963","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.3-renovate-non-major-87240af26b040b2b0e1e0000583acae35b468ee1.tgz","fileCount":99,"integrity":"sha512-a+OLt540kUQfZd+2UdeBNzikd4p/3TsvXaN4zZRhmCod3ATuAT9mu8poi9CgcAGpLzbgKLDQHx3b61Q/zSZtng==","signatures":[{"sig":"MEYCIQCsamsA4qIs7KagjxupJ3m1Bt5JIvm2HY+fDB7eYEcRIAIhALjCKtfGKRm6Py90bCwi7BdAzgU1p3XAcn1rsGbPdu5Q","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":704201,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJggE+PCRA9TVsSAnZWagAAftoP/2vj33nKkBKJC1PnXEpe\nW4pOy0N7cax7ucl2ccS5/0tAcBdQfrDCtQyNgQM9FMlWWRzDX6X/PHF0quTQ\ndUcl0/vyT3e0psbMXeADkXaGXVPHNkQSJArrEZ4HvC9WXWYFrYsX0TJkPxQW\nOvirVdqpo5BQCmqKmEYWSY9ZHsEHOGA8BxNcMsxcfSHq/gJ9GGtYefD+rYyQ\nLprFy7doM3NWQNcYmKCEJke1wjxYHa0LgL9JXkgFPJLMOd8vIW7y20RfijMS\nxHUUZ5NlquZzPHV/rzB2c1yhfe2X57TPWNSGupfL4F5vBXM3R/TLQ3N7D+Xu\nfPkQjRQkJw7TsAs2M7tl1+C8NPtl3VCBm0gU5o7ddPftEeUpww9Yuf4fMS1B\n5Mx2J65mU9gUlCv3ENdl81ALIHrNRVt5c7LT3IZ7le2gKU8vJxfzCiM1BZtl\nKFL/xU6xu1dXz/o/FDy5vegzmCDruxNlg6sLhE5t1P72y2hOPMsFYRiv6GCJ\nbvAj8Vt+fxCqsDXniRtcUoHdiIHS1mWNPIOq6mv/21Lmj2RxuwapmhQqX1iO\n3lgJjFh9ZSyQE07x6iF9jSRcLqBhtPkjsaBn/aLLzlPDbVFsRqCQqYES/6UG\napQNqczxZTBUjeH6OPsjCHjaZ37p4Sy9TfzPC6UaGiCLErJcoFAO257mhT/+\nPMlE\r\n=3iNw\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"87240af26b040b2b0e1e0000583acae35b468ee1","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.7","@balena/jellyfish-logger":"^2.1.28","@balena/jellyfish-metrics":"^1.0.126","@balena/jellyfish-environment":"^4.1.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.122","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.3-renovate-non-major-87240af26b040b2b0e1e0000583acae35b468ee1_1619021711318_0.18332886985627694","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.3-renovate-non-major-a7947509feb51268e45697c62bd2c6c73872a984":{"name":"@balena/jellyfish-core","version":"2.13.3-renovate-non-major-a7947509feb51268e45697c62bd2c6c73872a984","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.3-renovate-non-major-a7947509feb51268e45697c62bd2c6c73872a984","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"55d55a84749b1f6689976b80c4bbbd5b2b8711fb","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.3-renovate-non-major-a7947509feb51268e45697c62bd2c6c73872a984.tgz","fileCount":99,"integrity":"sha512-3MogyCGI9gpCXeVaHDr/6/f3CeJtdebmNeTNlX6vjr4DnU877bHgEXF046QtdndUTHZRTGSXj9HEBuF9FzujEQ==","signatures":[{"sig":"MEYCIQCHvESbftN2WV2SL5qJyYMbHe1HGGBd9Xrh2dse4sgGgAIhAIRrENGwl1bXfqRh1QcA6crP1TFWevR7xKEoov41i1no","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":704201,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJggHhSCRA9TVsSAnZWagAAqocP/3p3XW6ymoROl+XlCweF\nGnDuMFAHVj6kT39TVpt5EV38LTARHh2r3laswKDlQmxPodtweYdQ2oOkAoj4\nYAvlv8cO1vRi80AvH4BfNv63QHXK6ICiKf8AWulRm033JQuRiQecsRQMhaRE\nd39BpeWfuxCVrMqH7uK6Dt3O6rKkyug2zsmAJZY/CPUN9f5/bLPm/82Yv1gl\nh8Lo8HPYFqfHRmK7/oPFpeHUs004HTvlDnUikPyfWJLucF6Z9HyW8iBDC4lQ\nvRSo32rglj4T+WzZtCDGp4r7xu4bSjQUZ9tG4otHQQKX/37rpYh8waa3JdW2\nwL+AkiEYo0+1RNYPOpMJtRmHv7rhiHX8AXLemdogzlGAxjyMYE3vv1YkZ/2z\ncW7OPWQqzRlaw9osSxSU6GAgd/sxMpjVNfLnkjIy7m6iCNtPtYiaLPJ1OoEg\n5v6SGKfx/JKD0/91WPIjZ2fuAK8K0ViXN0eQCuaoELTzPxFK3Ydf0BtxIvAP\nZogAjCY0nGNDRH1hdz7Ego35CTewz4+C4XlMew+PaXrkAH3DKYfy24MEFUJ7\nEiA1FaU29krsU9bUIMqcxCEbixFCqsVxTzFhZjSxGnwciSLRYnkEW81jUgwZ\nmYy/ePyBbyU0J2G0/EtJhTLnx6/VIOahLVYGebkZXkx9Aq2OF0g67KW0SMSc\nXTzU\r\n=ks30\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a7947509feb51268e45697c62bd2c6c73872a984","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.7","@balena/jellyfish-logger":"^2.1.28","@balena/jellyfish-metrics":"^1.0.127","@balena/jellyfish-environment":"^4.1.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.122","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.3-renovate-non-major-a7947509feb51268e45697c62bd2c6c73872a984_1619032145878_0.911403369671544","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.3-renovate-non-major-03cb821b98635dbf64b1461014587674ff38f2db":{"name":"@balena/jellyfish-core","version":"2.13.3-renovate-non-major-03cb821b98635dbf64b1461014587674ff38f2db","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.3-renovate-non-major-03cb821b98635dbf64b1461014587674ff38f2db","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"060256b9c7a60443e299749ee2831379844cd7cb","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.3-renovate-non-major-03cb821b98635dbf64b1461014587674ff38f2db.tgz","fileCount":99,"integrity":"sha512-pqwX4S50q9H2kupXc2zKhsZPSlueb4TTsXbHI8GrCIjSr5N/JlOxoL3nXc2qXo+xUR8d6UPeWt+nHKMx1hhn5g==","signatures":[{"sig":"MEUCIQDlqUg9ogx4Z7QMtcTnu+ha6mAx+CpLTQuxzm/k7JqcLAIgaTsJ/p0qF2at0FFhDiC00ND96sE5d9DpcS9QP4z/LdI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":704201,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJggK+5CRA9TVsSAnZWagAAWEIP/Rkpt63BBVs9DK04vjfy\nE/TfEzCR+dFPlp51kD0/kMmZFmRWUeujBnZHNqW5cvo2VQJ16Xtke2bbQoJB\nbcqgxjU4+MnEhSA7pP9Xh/uC41zInGPMr6ebyM+UyOgPVY/gDkO5+lM2iEGC\nNgTqVSo/tEHY7ddHAdv2D8oWerFf9a2yvjzrE0151O5SbKCOz7yjBRuQG6OS\nCc8NgZMxEd6sj8PhdzpnMdzCjx0NBd9YMu7Niqhyu/Pq0j+72EpbeK01h4Ol\nXwQH431imK3r6gOztmMfbASsG472IEYs+WZdZAss/fVGvA+WZ6xRPPO+TYel\nHaAICnr5HOdlV2D+fTpSjom4kF7xWKT7fy+KAGfM6yzTx0tNGqNQDZh4aRD7\nXPWxekrdjf1Frw3ou/7WxR/vB8foKLMzbD0huvvtwDA+AVIpSC5EY+Puu2w4\njaiuqUWODu/22IPvQuFWrOoNHRaWVpoxIffwOdiUvxGATn7TMsXbdw888TAq\n8e9/VzIIsDfrq+RssGV6mjYK1x075+LPn6qQs/ypCMuxPWMM4PKtMFs3Bt0A\nvKN1l7PnaaLC4EtAFGkQCxooGGgvaoLJWvoQRGC/3UP2neSwybG2pxscGep6\n6lSxO94Gv4bqdSAuPnh4W2CkPuDrw7wzgiKDriaR1B/YuAAc4gI4lfo8WSbL\n3tet\r\n=hdh8\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"03cb821b98635dbf64b1461014587674ff38f2db","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.7","@balena/jellyfish-logger":"^2.1.28","@balena/jellyfish-metrics":"^1.0.128","@balena/jellyfish-environment":"^4.1.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.122","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.3-renovate-non-major-03cb821b98635dbf64b1461014587674ff38f2db_1619046328566_0.5496567816360303","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.3":{"name":"@balena/jellyfish-core","version":"2.13.3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.3","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"857d2d835632004a1590171611b07997db29a107","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.3.tgz","fileCount":99,"integrity":"sha512-AoJIeKK+/albrStMFhfPGvUzSloHKRclEWIlLqHdxWLk1mlTMjp3fQO0m9JSLQiJFYyskCrV9cBxMQZKntWMiQ==","signatures":[{"sig":"MEQCIEGvGOILav99RjT97c/UpSrx/ZZpTNHnW/1s/UfZKBC7AiBDH6Eys2Xbiww9j81sqn7LX8RZs+mCZv0EOX95MZ64yg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":704141,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJggLERCRA9TVsSAnZWagAAUvUP+QDZubdbOF+M9dxfnxLG\nFUAxYJzxvE0Uidmc3+Nz6RvBGAi+idwiWw7/73YCDVGBV2Mf3BYLDQvKhd3d\nvM+THdwY0Ln5gu7xjY5q6FcrEeY92Uc5PUnE559YQ29LBzTbrztHNNhRzJmC\nB+m09ag1Eg8REoFvnrTBq3sP2mrs0e6G0UAcNWE+pWk7tHYV97d3qNjpAP7r\nBQRTH+8yBby6bMaeyOrvHqZhUO5wEgam3gDf0MRxbWZ9egc6Ui/D+9sNMAwB\nelRpZBmnnltIapF81cPzzJgNZcMEftRUSkjcZemiovWivScYQQ9jmykLKz5e\nqfYTmsI+0TGozw8eygTLwGGvU2WAlhC8rU5OA1mXt0GCjv/esoFyyPjXFjko\nv+lN49TROgjB2FA7EWuIYrc32KBn7nbWpiz8m6fKdbKiBlR9URnB4llwsSUl\n/uUx6mZPCIvh6fHIaUAz3fjT3+dnutV8saGC7eKPWDymNcaYnoSFHu6Bbgi4\n7H6XxA1w3EhwZSuNESxwNA3Vu2VQG+FIiHzJ1tRD/Wsib0X+BiYL+nL+/0p/\nu1Q23FervzMeN78PksqSI+9hEAnoccpRcbDtMQLy1TAOOoWTeUlnDnJZlwVw\nVGGa5EQNA2Z0Og3QAKlUKwO8u6QY1xPv0Y6El462Tf96MZpgvgyDnW6lJlNL\nifCX\r\n=LQdS\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a1a3a96355e2989c3380b4aafcf7833ea9fec5b6","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.7","@balena/jellyfish-logger":"^2.1.28","@balena/jellyfish-metrics":"^1.0.128","@balena/jellyfish-environment":"^4.1.4"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.122","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.3_1619046672840_0.30871017104804244","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.4-renovate-non-major-366de783d6c9b21c9dcbe409de86728d32d69a37":{"name":"@balena/jellyfish-core","version":"2.13.4-renovate-non-major-366de783d6c9b21c9dcbe409de86728d32d69a37","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.4-renovate-non-major-366de783d6c9b21c9dcbe409de86728d32d69a37","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"bc37a055b8c37bca17f77cfcea7209262b94ff34","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.4-renovate-non-major-366de783d6c9b21c9dcbe409de86728d32d69a37.tgz","fileCount":99,"integrity":"sha512-9lgbRv0U3+7RBt+YNV9UoTl7UZHD0+PQs2QkbtNydWkMTWpSm3rOxo5S9nbBmbr+UVi22Mo5H7lHDiwizT2nWw==","signatures":[{"sig":"MEYCIQCUvvo2+bkKd39MO1+qvIAz4Gczyl3UnpWeHcKcj85aTwIhAKeBaNLlSh7kvKzcJwiY92cQv/gX02o6eEE0bZ6nPp4y","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":704550,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJggjOvCRA9TVsSAnZWagAAaN4P/RlxjKubPdr44do/cIT5\n1qipxFXSJbM8D/Np8SanVN1pQEqT71RRYyqMeADTnhs6ZGQUoMgVD5LG/vHK\nszggVErkJrHUC5dqCW/kqigzAhjRwsukg0fjxXTqvmwRRJQXwXaZywNCFW3Z\njO7u3n/vJbUImsbQvb2RH6OANjSgKEa1LzOvwfZclbOzKL8KZNknTxww+JGT\niyo4lxoEjjkKiGthuH0BOZMIEHS4YeMaok9bCrgM9moWWObAsJwrgtTGCovr\nwP656ZEwQl3fze1rZcLFWc7JqEIDa1N0XghmmlUVS3MpOfaFeFAqbCy49fyZ\nzEnOPkCRWC0jK+k8Hwk7Li/BlnWas3V+BLvNjQP99LPrrP/sDVXtHwBaDi8R\n8XJo9d4KoBsGHqCg1aX5z2IhUFcZkCQ4gzDSIWn1Sj5qU2wpmjw3azydpHKn\nw3kZZK+sW8LDo73sGpJHjyvWYu7NqTmwT1FQNnY0XDE0B5YdvH7v1UesFUXG\niu3kasHpt0U2onPYNQCSM2LgQsYSZkK2zU6l4WkfK4EjTWl4Mb1YUzgBHfKj\n4KBZv0UadDiOBn1fnv52iqKlo1nHhTRvdM1F77sPDYQe8fpJUhfpTsW/8KY1\n6bryEjDnn/eFxvaD9y2qZ4txPAXykwt0kU3IYN6NwtDz2AuS2DN8JL7pf6Gi\noj0o\r\n=l9sN\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"366de783d6c9b21c9dcbe409de86728d32d69a37","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.7","@balena/jellyfish-logger":"^2.1.28","@balena/jellyfish-metrics":"^1.0.129","@balena/jellyfish-environment":"^4.1.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.123","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.4-renovate-non-major-366de783d6c9b21c9dcbe409de86728d32d69a37_1619145647076_0.5863127246313509","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.4":{"name":"@balena/jellyfish-core","version":"2.13.4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.4","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"12c775325c1b58a7259e02887e50c76014c5beeb","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.4.tgz","fileCount":99,"integrity":"sha512-WpXVPfd3Upe+Kc/HpRAe/zykDX1wMONwnPssblhXF4bk5Asq2y7G5evjpS2WtCSG0G2l8SYvBTEKnzvRE/uhzw==","signatures":[{"sig":"MEQCID9lbBhJsXyeXXcWSun03XgUzYNk8LjFsrty/S7DuIgKAiAC6bqHEoVATlMb3F6ZPbuYLtqOy5pKQT61MsNl5rqoKA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":704490,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJggjUUCRA9TVsSAnZWagAA4BEP/AyUGTnd9GCZTL9hulxY\naA+Em5BGJy4yjzS7+ghYlt0X8glvcixP64P4ZfeKpiEFJOdy+Vj8mmHQ3psx\nC3g/Yn2Ebjc/SW7NU3Bqfb3AMR2PaYsG6vv2x7PvsRVFF2gR8bfMDJSEx48j\ntiWdQSX7vRPli+TE2OTswssqO/V48t43rToqdVGRyyHHzAiYknHQqXqO2pmG\nCltRUoe1oo/sWC6lAo4wR7MnTdq1/cN3brZqsoAXqBifctSZjKkemMrpMsw6\nHWN5hpiMSSERvo8Zy/8as93utubraaqNlOjTd7Us+JBmTqaNNLwhAvx3m9zD\n+XzbZ0xmjL0d05xh38MyU9CS/ZXCUt656jbMogv5GAVSYcMlGcv2qVqJdc1h\nP0x8dM6Wiinx9h9VtDqFdjqYQH3fPXhMnG84S0KJiXBlFtsbqpE8LNpfscGM\nc4MaGDA4Uzm+CmsLROjj0K8w8BXYB44gEtIqock9rCeMQQQ+1XPh08r48Ida\n+25BH/BrSMhPkmqW1dJd4zG6xAJ9ccUS90M4yPXbNLlZK8Fd/pLJ/qkdKBN7\nVftd2y3AFb3V7bdEEEC1mZxHKUNqul74qSGqc2h9QYKps/B/7KoQPTwv2038\nexmtzldMybNLGfSvnBazUV1e3xE/FY8T3N6SCS8GeobV5j4U9b6jVWpWBHMW\nWBjz\r\n=I558\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"bdee15d5fc0dacaa790c4d3e43860b36494f23f5","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.7","@balena/jellyfish-logger":"^2.1.28","@balena/jellyfish-metrics":"^1.0.129","@balena/jellyfish-environment":"^4.1.4"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.123","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.4_1619146004172_0.41036093957593134","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.5-renovate-non-major-6da21eeb43e65468495cb4cababac82c553c9c23":{"name":"@balena/jellyfish-core","version":"2.13.5-renovate-non-major-6da21eeb43e65468495cb4cababac82c553c9c23","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.5-renovate-non-major-6da21eeb43e65468495cb4cababac82c553c9c23","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"efd6893011fccf2c155938b63d36e82bdbb5172a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.5-renovate-non-major-6da21eeb43e65468495cb4cababac82c553c9c23.tgz","fileCount":99,"integrity":"sha512-jELZBFP6SFahRlF1LmbsYb9WXxiWah3caLaNOF3cAXwueZ0I+LoyWoKTqscIjRVtLhOCtl8MMzU8iQQ4ITes3g==","signatures":[{"sig":"MEUCIQCDbR/XA5ym7Z3AqpUzeqMPhnmMh7Q9VNJvd/5Bhkm4zAIgMtPHCAvmlrbPgPX14iLrexR7MCE3o8cHyGM0ix4tw4w=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":704977,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJggk4fCRA9TVsSAnZWagAANBEP/Ai6bVMR8ecNCO91jpCg\nCHjvd9QPrF9FXWSeWm6XJsRsyyHZA3ny7wjDGR4S6OdYZzhHa7cfdKL+4DqI\ndSxUns6rfjk5Fib8uQEM153TV61TljibDDeHiBiv295AkmZ9tXUDy1HRK1Xm\n7VpzF3yJimcTQqEn8EiLf+lsUKsE7Fw2vEDdKS7cCJYWiHQ7D9zg+6MNcR4O\noQEeuGnWYMP4n6440viApp6NXdN4ugwdX5KacDigHiup3Fo2JPdPuDHEm5yv\nZ+VTTw+nraPMQ2mnVN9ffqmRhxQd13+o3CCK+3JCoCpVO6Wuhs6NVnh6BGWG\nlpCe/f3896mQs90UEEQfvQCYoOo7AJeXA3eZicJbeAQlE3A2FAvHzGyk40PD\n07q1pAa+qf2i3aL7kVqvAWJXCvw3axr5ke6uPuGScrsH+or7y+pFOzPxz1U4\nuFWncMKnBQ8qqREYQrjFj9YkqvBNA6u+7mVF3XgTAP14UD2GKU2QeWpi4RgL\nkAftMd283ikOh/Tag0fDeze99KXep2DNVEgXRK+ExrJCcpR5WL0d/b8qYdtD\nAJyJnFYwUuMl/SzfM0OO+fZWStXutEv3d2JsY/hLJo+27aa4kWlJP0stzZ5T\nXFNCoSUuMoEHttdFNYp86upUkzYmQrYfJPipraB9MSMs0sFmxvsOOOyE7Mm8\nVXoj\r\n=Q+s8\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6da21eeb43e65468495cb4cababac82c553c9c23","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.7","@balena/jellyfish-logger":"^2.1.28","@balena/jellyfish-metrics":"^1.0.130","@balena/jellyfish-environment":"^4.1.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.123","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.5-renovate-non-major-6da21eeb43e65468495cb4cababac82c553c9c23_1619152415238_0.587086963696114","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.5":{"name":"@balena/jellyfish-core","version":"2.13.5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.5","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6655b330d6695dff86d6d3caf606e9d06ee40d72","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.5.tgz","fileCount":99,"integrity":"sha512-cWk7XR39g+ne00cA7XC63ict4JmAJAwcBEFE9dE0ZUvkG4FUz0xQ9vrGxwqkvOCGJUJgT+5dibgf5lj3ZBjLIQ==","signatures":[{"sig":"MEYCIQCZJHwN+m7avVmbDd8bcBewers+dLIjKghsGaDC/D68iwIhAOgQ5CHGY17lqfT37hoy8oNb59G4Cs7XwxsbOrN+AQyY","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":704917,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJggk+TCRA9TVsSAnZWagAAXAEP/2iei6HTWwanmHg7s2f7\nOIXWuFzpTQlnsDhacrHUiLfFVFkSWDo8MF7wzbTWAveBZpD7HCXvj1hjA/Sq\nFfCZRW72qdLzI2FqRzSSzw4M7pRvrrsftOFF7N6eAiKzWN+Vw5eOlRmZcPuT\ntJWgcBJgMENnRZkbRrNzjSSjWz8+K7vnXbjD54a50VoPUZlPdVP/7MeJEtOm\n87HsZYzncpjbjC3Rq42QNBFpwm1aoFv8IXx9sjS24DWx12dYrRJvAUPG/VRp\nyUVtbgLAtPsg0me5GbRagBOgjQYIkSxDCB7+44MHDPXdBg8nKsVUXkNzN8ag\nvxtIgonCH6KpXW1WheMMVeqqVhypGVjqBbXuTx1X5ziu3OqQhAQ/glr/ckNZ\nLbGUNZeTaa/bO65f+rx1oow3G5wESQJQYGyLsXnDzv9hCT4nHruvtuLuT7L8\nlW4izz4UIkh5kc9yONELXU7wn0jdpAAV1SEVrXBJNsXjqTT5XBbXqD6jK246\nLHm6aASghCH7K0IFeww13wG2TU6Wy3TmSpTWEnyDj8GB7Uv7Wfdd7zUh+rPf\nVLgtIqC7vgn4NehO2LpH0uHWnH5hROmWdP8SYAGUNShtQ9LDeQtHmvttYtOd\nIh3H8PIPXs8BZT+Z22g4L1HFoASfvZDDff2RHyv1oUo5TH+QQNB+QcPXSOsG\ne/4s\r\n=d5Iy\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"17c40578d3adad2b87c565422411a548aef56b5b","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.7","@balena/jellyfish-logger":"^2.1.28","@balena/jellyfish-metrics":"^1.0.130","@balena/jellyfish-environment":"^4.1.4"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.123","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.5_1619152786956_0.6216833589029873","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.6-renovate-non-major-52f21c903b2618b4948ba987b5b8f97d6e41e735":{"name":"@balena/jellyfish-core","version":"2.13.6-renovate-non-major-52f21c903b2618b4948ba987b5b8f97d6e41e735","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.6-renovate-non-major-52f21c903b2618b4948ba987b5b8f97d6e41e735","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f468ab427a8d381519f9db7f62642688db099123","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.6-renovate-non-major-52f21c903b2618b4948ba987b5b8f97d6e41e735.tgz","fileCount":99,"integrity":"sha512-mglTg8KYjEQk3hZZnezBtdWErjlFFA/u7l4bNoRbkJKz2FF1UaePeCGR2d3SxywhRRdwAFpy5NIsaIXZptDvHQ==","signatures":[{"sig":"MEQCIBUEuoEvrCtg7ETGZD7IPo4air2uFO7F4pCaAvAZ6hF5AiAjAXA3JHXToKdB7hPaOdff5Nik8g1WmoZUU2kC8AkCyw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":705326,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJggpdECRA9TVsSAnZWagAAdo4P/2rD4wm21RbfU15aYOxM\naA/ltXJTYjjbD8lRLakdJxNIBaillihaSLb3AHH9wicObSSlFCgYm/qIzBVs\nx7VkTSS2GeVKeZe49N/2yFv8/6r5RaTX24PzrOrWyKNjwuG4SgZ0JRjYZJNf\n7HYd0nf6cnT7NYfM+2zca7nDPGLg3soMyhBfzcCsRkIN2qqNTHZ3+T1HM3zu\nf3ER2cTFBjNDu8sB9Stm5URF/1oYD+zxdsskd9l+5T51ZeHwsji0z5uX0xBU\nqql1mS2Vu7KRMD4nrjOxzjq/w0l9CFjFaGSo3TfHJ9QN+yLKndOQuoLuE3Iy\nEj3/lh8ydIVnVaaGp4wL7du3jh5NlPP1PwakkK0/XeACsIdoqx+pEmCvizel\nd5bUWzVE85qG9uQamZvv3oGcb1CgTmIIezx5nzJPLHvfYe9+LHZOm3ifxDmm\n5vlVkfwPI4C26Xk7dBEWMJjuvprMcqNPWZXZEKScAsPU4+YrPOfWsEA7v9Bf\n/ioZ3kgluXwcoW2DARJnwytgYAbJyiffilkVK8mIgVdRkXg017sPFWxx2e65\na81cjhG0On7Ajdy+lz+6T2ijZ0B4JOUsf3fwAzp9350XPJvva6Qgad3XYagr\nSAuB20mr/aw+0WqrIIa5SMv6A1Wa+UglWmAoam1ORpnvNqUOsYCNLJIyLCPy\n/iAO\r\n=OQ3g\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"52f21c903b2618b4948ba987b5b8f97d6e41e735","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.8","@balena/jellyfish-logger":"^2.1.29","@balena/jellyfish-metrics":"^1.0.131","@balena/jellyfish-environment":"^4.1.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.123","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.6-renovate-non-major-52f21c903b2618b4948ba987b5b8f97d6e41e735_1619171139761_0.7103671673127336","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.6":{"name":"@balena/jellyfish-core","version":"2.13.6","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.6","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6182025b8fae010aa29699e4626f9cb0236d9f5e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.6.tgz","fileCount":99,"integrity":"sha512-YNKE3iE60qB7IjleXqaVij4z2tJ5DCzpvoBUjQJJMYgDtNX67G40UisDlfLumyrj+t9Kyjj/FtAgvzx2JZY0Aw==","signatures":[{"sig":"MEUCIB8gVfDozUn1RU0c2CwZiFoNIM59sEURoYcGcEc6pQRoAiEA3OSicFN4M66PT+vFHDhzggqt98Q2DmbM3tGJX7OUgBc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":705266,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJggpkGCRA9TVsSAnZWagAAu7IQAKQl/kolBrgUwP3uK9L2\nZQMDg5YbTk8ILqodjqi1iOd6qYC6pVQHZ/mAQSLM24ULHgv23FzNR4Lzsn6r\na01dim42Ey/9ndOduBCW4FizH3PKG4BQl2QojciSpMqQFbpMqRWDdYiH07Uw\nPk9TzuXPHb9XybRDehFl8OmM7WhzwwIC72Q4U/hHj/bdX+yuDse32RbrRVq/\nK/cwa2POg8Tz4Ra580FYDMLQ/FuES3243uyKdyyE9M650NW93104FK6Sz7bz\nrE8MGE5giCV90zDxKDbCPy5Er5F7lEY1zeBadA2AJmE2DMdXnWbBZvY1x4rB\n49JAi/KDkFsMt8cUhHYQ9OzRIl0LZau1TR1a5H1m74x+GYJaby+F6uHiL11C\nHV+DAd22rN/s7Ujjbzn1RsDVrUekUYuSXoaFqtn4JSXoStwJ6bsk2alKmT5T\nEQnZZN/5iSqWBmCWPTIw4si/rOfjD2Ns84RSt6+aQK/XqLhnsulRDWAiw1xE\nRK02bE4nYy2Zww9k7Wyumq2pheEgjz4cD/6pznQuw8+h5QZjNTAIbKurwWGK\nH3tkgUnZ9OjVW80PtHGW2qtMXQfCOQQnebaCXoNSQDIS/QhvcL19bAIl5EC5\nmR5oEppMub6ykD74UzX0QD3+XfmD/IkmT+TlYuIJP80pHLc2SXkWVJp9nBOr\nhiwg\r\n=eV99\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"134c065412ca7d66ee0b9d604187d3e1d33cde7c","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.8","@balena/jellyfish-logger":"^2.1.29","@balena/jellyfish-metrics":"^1.0.131","@balena/jellyfish-environment":"^4.1.5"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.123","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.6_1619171589831_0.9779253585551206","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.7-renovate-non-major-41e79b21ee992765bf2b42605e51c3e3a45799fd":{"name":"@balena/jellyfish-core","version":"2.13.7-renovate-non-major-41e79b21ee992765bf2b42605e51c3e3a45799fd","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.7-renovate-non-major-41e79b21ee992765bf2b42605e51c3e3a45799fd","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"706696214ef40577ab6fd9567596469968b0a3c8","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.7-renovate-non-major-41e79b21ee992765bf2b42605e51c3e3a45799fd.tgz","fileCount":99,"integrity":"sha512-XcPxVtb+Mj36VlV8KDAUwkRGfX4df1lQKkKnZTV84L3W4HqUp5wdRmN/U4pTRR8NT09XwRK9GkNZTmKzmyPtNA==","signatures":[{"sig":"MEYCIQDikGOv9Y3WaGErnWIL4xIypf8DucqWAri/A4jGp298VgIhANUSBvk+C23Kbv/ZF+hqpa2AEA5QKXjoZN7iqAgQGYTD","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":705675,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJggrF0CRA9TVsSAnZWagAAaMsP/2znD9zWOXQoCCwqEEl+\nnGgSoptMllMunmEIQUjk1sZCQZ7usgz4hXg3ss284NPccV+VcvY0zMuSImWi\n9PR/uY3mVk64cBJjMLU0iV3rf/imCX82c+O9ZEt8Op1LE9rkeqANjb2nQ6+1\nuyOLJyQxy74U3AenC4FozeZVGPVS+jbSmKOko+CniST2tE+iozwXjW8gs9y0\nOqlDDfDfp+O/ycEXWrknKXEqXW7lRKb+hD/73nN7eKu8Lkk2bz3wUvQMXIGv\nzbTCMvzb9Pk9SahFQp4dxWFP5lF+GPif3hobKnyxxgurlfWJEgbBdvnoBxMM\nJua0qy005N03NzFRz4xRSvW3zM1x2xfF6DANzgcB6uRCz9jvFAkEXf3Q4rfF\nhdsSXvNi829Qio7ieOYouNOP9MTBrG4rbHw6L6luPtCghO5OiDMURdlNNMAy\ndJ7dkDOtqjYffNgdEz790Fn44JuRSmO1V9WW+HwG2D/9qFQtfcwIQTx9eTjm\nBbIesSBSeYlg2NOVHOBw6V6iLW1jvQDKeANHjPutPIUuepkX8FuugKihUcmw\nJM+DiIfInlhKuciuvOaaIBX1HqroGJ6ucz25ExtRFI072Go0eOfIppSlWdAq\nNIGWsZdn4uAzGlXIGyNT89DYWTdgtytW+DPtGvSStkz17DOJHCgvfnHo5hoG\nfqX4\r\n=LfBo\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"41e79b21ee992765bf2b42605e51c3e3a45799fd","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.8","@balena/jellyfish-logger":"^2.1.30","@balena/jellyfish-metrics":"^1.0.132","@balena/jellyfish-environment":"^4.1.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.123","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.7-renovate-non-major-41e79b21ee992765bf2b42605e51c3e3a45799fd_1619177844235_0.7834041880050833","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.7":{"name":"@balena/jellyfish-core","version":"2.13.7","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.7","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0e1308dd5fd75bf3953d3eee49d62d67b9ccf3da","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.7.tgz","fileCount":99,"integrity":"sha512-WEfqkdrtHk1yq2oJ9iy6cYA/SXc662j/h+/3ereHkHaRTi7hLUrKkp2NU53pDYELoyv00ckB/dUTWDyhw4B53Q==","signatures":[{"sig":"MEYCIQC/reWysjZdidy2Hl11xE1y8+WY9C4qJQKVtfAvaOKRNwIhAJFbtbxBsElNK/dNYgPmO/HzqbpbfPMV/VLIaipwday1","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":705615,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJggrMQCRA9TVsSAnZWagAA8w4P/0nxZLEarMX6nA5yluEx\nJSL4MR8j9JhUqlG9ZEuJUEy9arp7lXT43Nbz2s0KEJ5VnIFITH1wK5ifbF+l\nJjQNh/0U3rMx13V1QmTvj0KRu4U8Rjj+cgR16dcCz4ptf0H7B+z+N8k0yPtf\nBlxII6TcbLbBZd0wAdcNIOGpX05kG+0KhDAjaREKRnwjGtt9sK20Em7Ofa+A\nyoMWS0BTq2P24p4g9lUDvsMsNGi8RrJbfF+0vHQKqJQt+xg+gNYJS0x/E3ci\nSzzyEWs+/AcT/SRDob2qLfhYNTOdSUlF8YaaEtdi/p7luurA8be/yA3srAnG\nuMyoZzfk58h4DD6mDtANW5KzDe14/xySAvEsrwOLpmNRznTBzWSCERIK7yRu\nE29zjCMy87h2PUcn4LRrNDg9PaoTMqbkvcXT+nLqTxvWldFkgk7a6EVJGAa4\nds30ibPMmoMcbm/hSZPwz+W5XaWrlIRXQcHY+1fdYSiD6t09YA5RccmwKnxY\nEz8Ts8H3J9zWtF5M60b+vwW3xTqbDbLvOPIao4twP82PALZwhmE2VR0DQwTz\nnsC3rF/eL4S/xDychaqM+drXnt2Sd4e2VHF5kVeAHCWuqKSwK0jcha2ofdsD\nAwpLxGcvArvIUbovl0TPw2jqh8gXK5zXDAA9al88mozRvpfvuZv1G5905Jwz\nooja\r\n=hy5a\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0484c8872a3632e70cb19925ac3c78794d0c85a4","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.8","@balena/jellyfish-logger":"^2.1.30","@balena/jellyfish-metrics":"^1.0.132","@balena/jellyfish-environment":"^4.1.5"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.123","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.7_1619178255626_0.55740167593","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.8-renovate-non-major-21e1036bc83d47b7dd9a5d588e07c462c497611e":{"name":"@balena/jellyfish-core","version":"2.13.8-renovate-non-major-21e1036bc83d47b7dd9a5d588e07c462c497611e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.8-renovate-non-major-21e1036bc83d47b7dd9a5d588e07c462c497611e","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a47b994bc055c458d5454272cd26be95e6c7e23d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.8-renovate-non-major-21e1036bc83d47b7dd9a5d588e07c462c497611e.tgz","fileCount":99,"integrity":"sha512-IWyjVTANKX75MGlXXNLmyLHA1AGLdIZWT6svx/XTfiejjQptxdEsQOkS3Qg0xlbxH9goPMeBu/C6GZxZVXdCvA==","signatures":[{"sig":"MEYCIQCRPVQcyugU+LQ/MocG7VdUqC1qPGswZznSKAHqgAgzVQIhAJZn5GgQ7KH4+uV3OVXytjIx/wYzUY4pvuRTuKhhP/hH","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":706024,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJggwk3CRA9TVsSAnZWagAAs88P/2X8G4JOO4Czcvm+lwXq\nUCUazWuFRFEqBJ3CF/6AB2/ampD9Iv2Hw94ue2NtZmqrFzNSPH3hhrp2M5yx\neLMRwZTIxxns1x410MyC2WbnHgnQ/iLlxb0BEL/0UL3KW43YyAHPZA6Hen51\nwY31WZhZYd6pHzzws/KJj1RGfzGin/jA4sLp4ORaUmrEMuAO+rkYcXVklHLD\n3+IP0b76Afaeo+F97HmlAou5YbToCbc/6ar8ML+obIuAJ9N8rSrFJ9SaaWex\nRqP7Te2j4d9EftcccqrOQacCd7krXVYstF9nLKbdqtfbwIu/j7vII1SXUTd0\nh4DlVTIkaZHTobVhrXRvarnGu5Sizn8t9OUsUsK1wrd+GVwy7Cx4T44MiiJT\na+XYFkPsRC/g6m7IzxrCVzg9OXm2watsyxJr3RxTSbZZBjnUefD2SJi5A3HC\noAcCtSqLSD1Blhn8UGucHkvB1LaSBCyDA4n28+kcpefqpr3Ug2Fa1CeWgGrB\nfEeGbKC1GLGllq0zNkRyGqS9yI2BaSFW3y/xiI6tiJBiwz/+dbFKjb0endEv\ntQ6GnFcbv8o/yg0t+DqTdC3qW/bEJAP7xCwK8bXlLLHPeLpG/6lh9sEP/rt8\nXgTcdEd9doAbMF5GoUqbERu2Q32lLDC647+IMfNeyYz0DGmqoSRH23es86C1\nrS7x\r\n=bYNM\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"21e1036bc83d47b7dd9a5d588e07c462c497611e","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.8","@balena/jellyfish-logger":"^2.1.31","@balena/jellyfish-metrics":"^1.0.133","@balena/jellyfish-environment":"^4.1.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.123","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.8-renovate-non-major-21e1036bc83d47b7dd9a5d588e07c462c497611e_1619200310777_0.12024538028934328","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.8-renovate-non-major-d0b642704c2366cb0edeab5ea2bc3ebcd573e7ba":{"name":"@balena/jellyfish-core","version":"2.13.8-renovate-non-major-d0b642704c2366cb0edeab5ea2bc3ebcd573e7ba","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.8-renovate-non-major-d0b642704c2366cb0edeab5ea2bc3ebcd573e7ba","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4ce592b6b9399dcd3c8b8e0e7380f901f0ad1f21","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.8-renovate-non-major-d0b642704c2366cb0edeab5ea2bc3ebcd573e7ba.tgz","fileCount":99,"integrity":"sha512-mXfufYeRirIny/5Z1guo2jwegl89aMaqWn8a/vMe1I4/WkKrI9Nn44UltXE19zTfOoLHE0reYC8dTidk85p2RQ==","signatures":[{"sig":"MEQCIEhAyzQsuDbV0QxMdTJmocO4kEigaGgI7g3Wi2JBmIvYAiAtbLHgmh1N3+Cf48Eb1Jf92UN2j+q0gFZw1oJiMSBFfQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":706364,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJggzEmCRA9TVsSAnZWagAAkEIP/2qtgL+vbYJ172wKuWuG\nUh/v0JyWYZwvAiHuctT5r4cmcooGdbqoqcAikCb+KfBj47TdoWkQbrCwkub4\naLXEArmF0WhdqH+cp2Fx/OY8RK+x6qK/AGktdgT+b+c071rOuMIKhB1idLXq\nx6/ri6Ybz62wf51LKPi9ODbfq4H36bZ2toZ82JBQxHp1gGcobAC6wso5bX0i\nAmBrcP4+3QQQ4kdjzGblvSdoaWJnpISYMuvRxxojAV0RT56GIzM+KsLkRt8o\nuNsenQPpU3zSyJ2xqMzrZ557HwIYVEPzR0PvOVDV7BNb3Zsuw4Mk75cLuy/f\nOju5Adhzh0zkptKdSd6MfE1qnb6pJe7o4fwewINsgmv87lDNlbQ9+45dP03e\nSF2OWe64XsvNMlhuUZg1AUwR755Y9/BztCwO9z5ZiXOmHoW7hVY7eGWsXXCn\nNKAuF/uMnA8KuUUd2DiUyw/O7dlcmig+ImQLYJGw/hJsfUELtPK8B44LwLVH\n26+o3/cuyOCDVW/XLDJvEKm5MlhBISXXrJGI4b+APcEDHAq8kj8L2YlLZMEb\nalaJGjHluoZrzVNNY4UrhA7yoe7xBUHcZO0msgLSo3u9mKvl8GT8/4oYEKiQ\nz0ToORk/85kG9Rz0vs0MeVTj9MWKVpHdNw+iMXgrRQJjc3U9l+ID1w6aLniV\noVom\r\n=D+RR\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d0b642704c2366cb0edeab5ea2bc3ebcd573e7ba","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.8","@balena/jellyfish-logger":"^2.1.31","@balena/jellyfish-metrics":"^1.0.134","@balena/jellyfish-environment":"^4.1.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.123","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.8-renovate-non-major-d0b642704c2366cb0edeab5ea2bc3ebcd573e7ba_1619210533831_0.07849321062924353","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.8":{"name":"@balena/jellyfish-core","version":"2.13.8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.8","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"836e40cde1bbe076da0272cf859f5fc2ac9aea05","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.8.tgz","fileCount":99,"integrity":"sha512-61Kef3wI64lHUo6UNglzaCfK/KDao/Z4FmsF6Ll0SviSibTPqBdHyjyu9Xo5rxIzhOaSIEo6PlzGtp4UGkYEQw==","signatures":[{"sig":"MEQCIBBF0byGShO4SP0thHMjeVkOJImA+jsmdgAF9TlxrwnPAiBKu7r24Gy9L/ouzNKtoctmfXWyPV/w6/S9wOfwOcQMsw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":706304,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJggzJpCRA9TVsSAnZWagAAJWYP/iVPBmolJVxOmPzwwHfz\nvc3vrsD2zThbalIm7QPHXAjncZ6EbrKXSd/6M8WlrApJJngs8JEc5mEe2pnf\n9H/KewgfDM/sJtFZkQXPLV86NuzYK1sYcFkalgGlsJx0o9hSEfNveH4qgs3w\nNpHGCJ5NAGb425FdZDL9xjICIdFu1Tyh+8Lgxkn8GDT3FuCOV1w4yDkNOTF4\nw+aZoOgS5coVJZzgPL2pSGuoeCKfh/RP12gVXaC6Iv/XmuP+lvMQJSTWr8N0\nnZ0Lc7ErTr7q1DXQI2qPg6ICvdCHWOqskHUmLO8XNhrH6Cmoq2PdlTygNRDn\nnbwLBPCpH8+EbF1F3qPIdDHpcrPkGEMv40PtC+DpMdEqEKfGVSRX8kAwqit2\nnifrmvSqo3QIzsgquC69K3DAwcngm62+qN2X5ng+o/DkKTsUDrpMk88ac6nB\nFwKrk4G1RrQdfmvxsXjErbHzZBaFW4ZH0MNeVHb3V7mrgJSCWpvPSYfHvqcO\n/4zGV31rX1Dx4uRqQtVBU9sjxXIYi1zz3cu9hVWvPpK+AV+mXfQpuC4Hg5AJ\n/S85GriP4ubHI5LH2RbionqMFnAInU4CD87pvYcOMhxd7x37qsedDeN2uIwH\niZiv2dDnQ5eBESo26BkfUTfOtsXAG/5cPEiQdTf16+86VPjq/Jw49p4O1qN9\nMmWr\r\n=Cm9V\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7cd5abbb276e9fd5a93a6d0336599683befb0b9d","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.8","@balena/jellyfish-logger":"^2.1.31","@balena/jellyfish-metrics":"^1.0.134","@balena/jellyfish-environment":"^4.1.5"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.24.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.123","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.8_1619210857098_0.4725082049328062","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.9-renovate-non-major-a5f2285727c56914c94c9f1dad691f5dd5f79267":{"name":"@balena/jellyfish-core","version":"2.13.9-renovate-non-major-a5f2285727c56914c94c9f1dad691f5dd5f79267","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.9-renovate-non-major-a5f2285727c56914c94c9f1dad691f5dd5f79267","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6f6755bc29548c095f5e7a441a1cd2234136a4d5","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.9-renovate-non-major-a5f2285727c56914c94c9f1dad691f5dd5f79267.tgz","fileCount":99,"integrity":"sha512-XLg9k97fS+0D5C1GWA/Fu/aAl+WMEQD3Oqta+XhmcYFg2Et7X13f5VbUszJnKwRUnGB9fWUrv1+LcjZWuLhKgA==","signatures":[{"sig":"MEYCIQC/G3pmOBrgeK/PgOi/XjDjQI+68G+FxplScyKnQX8AYAIhAN1tEF0P5olfeKegUpWMFRPOGLpUGcW9QfFV3c+a5xrb","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":706751,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgg2LACRA9TVsSAnZWagAAhrMQAIyLQAEY/BnK/D6ybdPv\nE+5lOBwCd0Xb9e//XqeUHs4zA838B2Q9zF4S6KFqPzBhz6b0AL0k1QyuE6U+\nasnZlfEbSpg1CB81ZTXxBKDtOuDvb7vgEpILpw9MrNF1nAwRs5f6/BECVugb\nB3mZaLeaJfqzaSHsEHuSigEATo+YqaO7oEXskSIokm/6yrMLO14wnvRepCfb\nh8e9xcQWOXJsODTtO2Ngz6qf0WWQFYyLa/z+LKpbl8hN6IZfXzXErFCLRuDr\n5Q0lfZNeV8OtKAlewXc4g6QcNxMGbVM8Ju043pDLahJp3k+c44881QtYN03l\nysg5+7kjOZ6FB0po5NcA4UVwHJq/ECPffrWwgLBdTqQoWwZVLXNlyBTTKXLo\n94XQSNVv/+EXTRga8+UiyDll5dEmIrhWgZ1PHlylOG5ges3NLX3FKamWKlhH\nzn0Sf+uZU/5iXN1VhVbX/8/Q+op9P+4+wSZbnYwnwKblQM3InKVHSq2ZtUXu\nuHgXhxc/3ZpLspoo8CSHW25DhK/egwgJYhgTtGwm5c0rQQBt3XK3LY64B6GF\nA+kvUKN1NlmfEG99nRbiBElH5ikM2n0wEZo6nxJluW6T0GcW8Q+lg8HxvYSS\n9uJO6QW/tMQkh6VgWZ1BtWtwrfop9LnJRdR3ihDfBhRDgcPEi1Utvve5Gk2F\nc1fq\r\n=TzCy\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a5f2285727c56914c94c9f1dad691f5dd5f79267","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.8","@balena/jellyfish-logger":"^2.1.31","@balena/jellyfish-metrics":"^1.0.134","@balena/jellyfish-environment":"^4.1.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.123","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.9-renovate-non-major-a5f2285727c56914c94c9f1dad691f5dd5f79267_1619223231882_0.045269171378176765","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.9":{"name":"@balena/jellyfish-core","version":"2.13.9","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.9","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"667c25d0fe3fee112b4e818aba63aa5646e9aa0d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.9.tgz","fileCount":99,"integrity":"sha512-BUZ2GsOIRquQKNjCzEqCXHr9oHRKxgBII71fk4sNVIADL0lr0vq7csBSLNGFZzmMuF0SyUi0qQX7GJUD5OJrFA==","signatures":[{"sig":"MEUCIQCso3Fg7579k4NytwXjRaOWrhSSt8joC9vxnfnOc7ibIgIgGaD+Mp/dbaKXuJlECwMXAkvAwp5u1U7qcqGrjflc/40=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":706691,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgg2TMCRA9TVsSAnZWagAApH0P/1QaizbnlpoEzQ8maaTU\niH296mjgvISo/ZT2RTP/0p+zmjkmH9EahLxHpa00xsLavOBHkzofFDgh3SP1\nr2+V9HIq9PZPrxlcA2RRSvwXZvJjO22KIp7NPdxeqC+pUnOr9QEHDG4s+OYT\njq3QhwalWpILj02A4x7v2DxDpt8gXvAY3c6/p+PG+flWKZRHEOtDJwTxn/C1\nQ4UclcgXsBBpK3jiEelSL/SKTsqX0pDcsC5m9C4lCEH9rIpKRP3fBScnMe7Y\nuHf+BMpizMO8zg4E4unYOpA/oRpz10jN+Pz7lunKj8yYlp7XGwdCroBXbo7Z\nMB8LvXGDlPpEgj1vaeH/fSU3fBvwScc+fr+bHIX/gwzVaPRhYQHzBzLmRAKs\nupedvR/G5kTH3HZHRzYy9Of5zqYr3ZW+eIEwck3E44U16zUh0s2Fuo/OXP6I\nLZmHuB9nVltPyLwWyasVilb7+lAYIveASraK8GG5l3q000ov7RVqBtFa1BVp\ndvnspe2j0z5c3cE3RcyRYF6dKM2cyMItsvnZJvBHRucfOXKUmOwow7yMD9Ar\nnKXDkhFZUVJOLUIBL8YmRWtEXzyPnMGH6n70AjJTyAQVa3/xLktQ3Phz4TvU\nE1HakmvNNHtF1tXyQxNcDbcSRZH0Ct2lXbtnY0aBz56fVQ7RAydLGUWHLAjQ\n0/p/\r\n=RzKo\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"cf8a428f3210519017eaa647edaa2a8805bc78f3","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.8","@balena/jellyfish-logger":"^2.1.31","@balena/jellyfish-metrics":"^1.0.134","@balena/jellyfish-environment":"^4.1.5"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.123","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.9_1619223756305_0.11038386888558516","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.10-renovate-non-major-04638c0fe56392b6d4bef62b6d2dac342ae2775d":{"name":"@balena/jellyfish-core","version":"2.13.10-renovate-non-major-04638c0fe56392b6d4bef62b6d2dac342ae2775d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.10-renovate-non-major-04638c0fe56392b6d4bef62b6d2dac342ae2775d","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e99e794e18b67321a090bca0e1d32132961484d8","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.10-renovate-non-major-04638c0fe56392b6d4bef62b6d2dac342ae2775d.tgz","fileCount":99,"integrity":"sha512-4brOVBQ30DDtoqiUg9z25PJQQXka3AMefgsZRGpEv95XJdkwesKh+jmPbYdeT4MgM+77IE+Xyne06fJhoXZa0Q==","signatures":[{"sig":"MEYCIQCUS1qbPnzJB9Hn00rrS6kWLUjXrPLr6WtcFmBkeEZqhgIhAKLc4vlLo78Y2OXHavhj3NUPZ/2dYF7VCBDbawsupbJl","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":707181,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgg5n3CRA9TVsSAnZWagAAmMUP/2VRxN/ng8YaQWRtT91S\nUUe8KLDFGuAytIK283kCAOrsQNkoA8UmwQ1aMR3knTBl6v8LZh+VeEzTF9oF\nEJlsUMws301kcJubOoJ5ju/oZ25Ua2d4eKQGcB1Vl5L+hhW2CRNYz/YQEPNu\nEVycHG9JsSJS+cNKTulhYgsI4dQBicRgO6/1UMpr9JAbLjMmz/JDc7uczqTD\nLjyTPMPHyEfe1n3TffZVXiqugzLe86FjdCp7ZwN98vzbHfZfj60tkUmwZeVE\ni6pmLYU7cpCXjgLxRnFlIWKquFxcE/UtGd8mLGK/xeIlnwFibSsO0hf9N2TA\nBNe/JN/XxNqviUBpUpL0zNWvdDWLhwo+yk9+N6r6dmF8A+8L4++7vf2bnxnb\n2Awz3MZJ6/VQt6HgaC18QBkEPykPJsnJpdxT3Qm5VVqyCuWXCvms6zf9vygl\nkchzTwGeKeRqsDmJ/8jxVLWdpc7bmINFSxxH9xQWsWkp6prQThMKHx5LzEE2\nnUcJX146ZxJkZkVarViyyEhfbTYv6+10okIQ/dTToBcLqZyA1jbh2KamA1+E\nsiogvDDduf4Zs3TwOuT2s3PyTPf+5An+TTiPjlG+6dO5ukKztrSWvW0NJXYu\nyXdeN7z45PJ27POBceKSiNTRHXdJs9G+QGL+UeXqOpuvnbmUv3G4uEpH0U97\n2L9s\r\n=vwoo\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"04638c0fe56392b6d4bef62b6d2dac342ae2775d","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.8","@balena/jellyfish-logger":"^2.1.31","@balena/jellyfish-metrics":"^1.0.135","@balena/jellyfish-environment":"^4.1.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.123","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.10-renovate-non-major-04638c0fe56392b6d4bef62b6d2dac342ae2775d_1619237367087_0.5635124382587386","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.10":{"name":"@balena/jellyfish-core","version":"2.13.10","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.10","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3a9735964e3d226c903826879e6ff9fb06ebb6d7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.10.tgz","fileCount":99,"integrity":"sha512-N6LKkQUZQJDHQyJ/H2aKsrXl/vJCxTR8H6kDOzG0/u38Cun+/FUYb2RDqmg8xGdVpFVGUl/y35URJK2VrZ9+FA==","signatures":[{"sig":"MEYCIQDW5RTnGb9+HhguIPD3QplANS5X4R5D7zZlJh8Jq/Cu0gIhANfh+3i+M84W5Qglc6EkvSJzFl9YsmfpoCz1g1EFaSGW","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":707121,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgg5zOCRA9TVsSAnZWagAALVgP/j6fXkIuFAytyv7ivpSc\n3ay/zSvclWaBomj+JwmXtZ6qQYeFZqiIp4VaaWliXH+iPdjyQ0gGuU//8AAg\n/E0bREQZvJUFhUGIYEGF0WBoocvqehbx8Q3skm8FEkJRqiUFniCX/6QBsGPh\nAG1P0zOjek7gwDZjh0xMo/5u4PU3TUd2PuMr9Nb3iALEFBiimPQmyd4okIgi\npKGEOl/lJOLaSqGeUgm9XAlTbStPyyXv56Qm9cBslcx1L6DxDe6gjBDy3LU+\n75IXJIaexRD47Wy0wTEcUk1iRZmUbEfYlzjgekEGWXCs5b8u+9gxZc13eeZe\n/prAYw1LgvqYh2TS1z0Nt/2yade6X8OLFQ0Vq9wJID6n4u7kuMDgZeosgtBX\n+1wsAkPutbXIdIO17R8YkOBQGuIm6Pbzz+RQ0e54ld5LuvfeOHi++BWgzm9k\nLZymjGReWUPiTgkW+3stTuE7DXeEz2dOmgoXbkDvkzGwZALFZcWU5KVxQDYH\nvud4ZNeV6S0/U/liWpu46A9C+iF67KuHV+eyzca3YULQGg5igFozcaQySyBE\nuzE+iY88O2ObmgjJiQ7rDsg6qKVGhMD4uiMm8j4oV2uvVaaufqoFPUbtI32r\nSY/I7UamSicxmuW+NnAch9SExs1QEAV8xdAkLL895M65JvHSxhvjpAvUxf+L\njuuL\r\n=N8H8\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7136f63422cb461739fcb69baa4f761e980bed52","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.8","@balena/jellyfish-logger":"^2.1.31","@balena/jellyfish-metrics":"^1.0.135","@balena/jellyfish-environment":"^4.1.5"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.123","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.10_1619238093795_0.11588853759206019","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.11-renovate-non-major-81f9a0d4ccfb18409f2e7b6d780915fbe11e1664":{"name":"@balena/jellyfish-core","version":"2.13.11-renovate-non-major-81f9a0d4ccfb18409f2e7b6d780915fbe11e1664","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.11-renovate-non-major-81f9a0d4ccfb18409f2e7b6d780915fbe11e1664","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"313963fc8ed080cd3ff47bc081e198db730a4e13","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.11-renovate-non-major-81f9a0d4ccfb18409f2e7b6d780915fbe11e1664.tgz","fileCount":99,"integrity":"sha512-xi4HczbZGZ41IwrqBeDc81N/9kNvjtM7/NO1KS6TYP97rXqAWdTFe2ITRWqWB0lJ3zFduT9C2jf+YkVCkixyPQ==","signatures":[{"sig":"MEYCIQCwJ0z6N2onj7BurxMqvD3nUGFCKo3yPMK5OSPH+67CXQIhAISxvdY+gPR09n27A/KwLvvo3qe7JCsBZIZVVikxBJNs","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":707532,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJghAekCRA9TVsSAnZWagAA7AsQAJXGDd//0OzeVgupNNeW\nQ2lmU3vcHv00wlaKAYhqX5ReRmhTOU2bN6xF6mjIe7qpg3y1FTojsBb4FdA7\n+awG4l99SggvETO2gGXHDBj/a69C9AO4fhdQcrfwaDvTIG+/QtnXlvopks3j\nQ+46lvGbddhaNX3rgbmLedohqKyX1DuaWvFucv6ol2Z/UD1R7f/SXpnzsrKM\n23JkZ1H8b2UZaUpaLgEwuHVNlt6ezUn9EnC4xfW7fFu8aPKbxNKd0cz8fANB\nKoS4xL0GDzKHuaKeVTtz7167mSlQnClw0iTNTytbuOEU9odArk83Pgs1rUpF\nF15nSmvLKiyGGg8rh8Ni/ssmDf1WSGYBB71AcElZ57eRRcQsD7W410/Q+fLS\nyEGW4uL6TYqRskPPMjW5d7pXGnLQjyjs5eV2s2veuSYSgN/T07G8GSyKUzCE\nSKd/jyD20QrRZXVutRhqEhWIM+MEx6QcAXVPC4a9zj/WjIPm16AUxA1oGXkr\niTI1TCVW2wmp9bx/UEVUzWIFUQYZ5mKjtAeu6rxZnr79eIwFRZTLF7CBM0ux\nVQPV8rVV93guTfEq/8+f4u67xYq4FP7bndJVeh5WpQhtIujHnNT7pf2MC0L1\n/VOV1meEJKzJiBWPVhnyf0ZwfMh43ClbMTJLsx+6wYk7PDE5fWzaNeDBUiHR\nDbFz\r\n=iE4t\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"81f9a0d4ccfb18409f2e7b6d780915fbe11e1664","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.8","@balena/jellyfish-logger":"^2.1.31","@balena/jellyfish-metrics":"^1.0.137","@balena/jellyfish-environment":"^4.1.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.123","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.11-renovate-non-major-81f9a0d4ccfb18409f2e7b6d780915fbe11e1664_1619265443697_0.3860828550317752","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.11-update-repo-yml-af2954a0cc31c5d487b071f8edb5aa8660ebf038":{"name":"@balena/jellyfish-core","version":"2.13.11-update-repo-yml-af2954a0cc31c5d487b071f8edb5aa8660ebf038","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.11-update-repo-yml-af2954a0cc31c5d487b071f8edb5aa8660ebf038","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"15af0cd0a2ba76f002efefe187eec473bd648b78","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.11-update-repo-yml-af2954a0cc31c5d487b071f8edb5aa8660ebf038.tgz","fileCount":99,"integrity":"sha512-/xhtADDfXZaqKt9Urd526+Cu63TXg//BtyufG/gYVuE0wSgKTSut7TkchcDAQ2smh9QZa10OWtYRK8NinRZ6dA==","signatures":[{"sig":"MEUCIFZyxkuC3Ebgn60duMDkLifpE4O82Rlv62CH8Td90obcAiEAogPj6cLr53WH7wzfhwsd1NpohkMkRQl7sutVdGkApsk=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":707574,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJghCjxCRA9TVsSAnZWagAAD+AP/3pfpIhwFnfvK2lDKFl5\nMzIh/ga8v6+Yb90nwGyW4j+wNg6ahQBSarg+Cf0x/dmGGY0aRpmY5Q4AUwQ3\nfbWcVVW1qJBD3kz3F7qR7YhDtDCTVY6p9oBve9sZpa/yveSeQkvN8mHz3Sc6\n01OYiLDnYKODJwDXCQfm1/7SmrBrP0+0Milem1UzS/j5TjMCTEgkKH7pzesm\nnwXNkWsvT9TCVYP31EAf9e1oQXCThKJ9C2sE9Vz1jUVBeCuXa6C1Gkppq197\nfDaKDea8Zfbuof1pceGui/ZwQauQtAU9HHCuayXXWFtzD8NvbrwnLVvHS0HI\nDxK/JJ/CEsuSYCzrQsIxgNigocdMg8jVZA7eNo7PUzrFBb0xAbswVX60DegD\nILshg49xXfQ4iw60dwtap1WiZBOkpOSnbKB+bixe1pFVmo0RmTMHl6pDUqKE\nz9EH+UxKCv2xi+zUUi1obhNJTTlFhivpNHIqyz8B/3/wp8h/xNwgHM3szyuu\nnvvUNAj5daV6PNRvTmg+i7Rv1e6LpanTDT2bswMX26pJKZPhmPgnBIidZI1a\nGVFe65AeLx8xFWErd+pDU6p1FadK9nK9NFRfjiDwkQjEAr3NPaFh5A1xWYhV\nTZ9LR6g+ow66++SMTR7vcd4HTj6ZCeO5UTalz9BpMeDY7UD/lWh8a/wdyJYs\nqahp\r\n=y+6m\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"af2954a0cc31c5d487b071f8edb5aa8660ebf038","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.8","@balena/jellyfish-logger":"^2.1.31","@balena/jellyfish-metrics":"^1.0.135","@balena/jellyfish-environment":"^4.1.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.123","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.11-update-repo-yml-af2954a0cc31c5d487b071f8edb5aa8660ebf038_1619273969375_0.4572431597184603","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.11":{"name":"@balena/jellyfish-core","version":"2.13.11","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.11","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a3d8658ebb69d03d4d17bf30303b27179e8a2f43","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.11.tgz","fileCount":99,"integrity":"sha512-tIxW7TjEeFDWv3dMV13wJmzPAxbjBKPUiPCXl4jJTgKdyluqp0KiI/YuwmDN/spe9dGC89UnS0LQtJ0el1NtCw==","signatures":[{"sig":"MEUCIQCnzaJz7Y4S0ZglxM/yIWP1aP2r6lu5FOctCHIAWUlSdQIgObHptVId18UHO8/qYCfNlgZ1upPnsItAPzaf2v8iLG4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":707517,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJghC41CRA9TVsSAnZWagAA6EEQAI8rV1vYSTYUCJMFK7WL\n4I5OdD2Udb6Q7DgGF0RWbJ8e9IsFD50YPqwZSEGxRx+7A1arITuFyTFD4pnR\n+Ecg/NWftggUUmUb48Nl6jMRHPP/QPRnyocw4wjGP6i4z2mTENGWkmduz37D\nzoIQPU32+jTnOdcg08bt0yiME1vstoaBb8i7wGaL01g1FQQPJPAX3EUdQdOD\nAez+7Leym+klueLYRfqmZeEmbjpMc4iuPjxoPpG3HJwq3tGyDwOmrRMjRl8r\nwkhT3fgvPU27C5dU5m6S9MPx2uvnSUbYSjWvEfZ0A9jkV+4pAEmTfeOd9any\n2D+sDBqeztBkSk8PcY1WdImHj7T3ENUt7Ug/baGylo4CZ3M28y8IZbyLiz2D\nySCPX3HkH68b0h7MD3JCu5d/yDTuieZWrvyHcHChCIVw25o4YAn08uP94ayH\npJGtipjlUR4AVoKHkFW0GkP7r1MXiwT4YVGGLxEaEERJfTGc/ovaPjCJcg8L\n+a63bksCSI7f1I2G1VTyjBF2cIyux9u9zU72x72kCCi6XM5PdgVaFB09oViU\ng+GarL1P0pW104i2SJMMiYHOvsG5GqJKnnuc5rNp0eg7zyZzCL1e932m651u\nPmG8zpiVTae3lOy36aZhrhoM+6gAxyQJK/ri3Z1O3hJOdCTkZCexGlXSfl3t\nxVps\r\n=c+i7\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"43a9944978328151129d884e8d002cd30e3f41bf","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.8","@balena/jellyfish-logger":"^2.1.31","@balena/jellyfish-metrics":"^1.0.135","@balena/jellyfish-environment":"^4.1.5"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.3.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.123","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.11_1619275316915_0.19034432231916298","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.12-renovate-non-major-c6c2887e392083f3b89c493db9bf6fbc0a2e2c25":{"name":"@balena/jellyfish-core","version":"2.13.12-renovate-non-major-c6c2887e392083f3b89c493db9bf6fbc0a2e2c25","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.12-renovate-non-major-c6c2887e392083f3b89c493db9bf6fbc0a2e2c25","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5ce5266aadee30ecf86ef6c9b2b32cf008da4141","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.12-renovate-non-major-c6c2887e392083f3b89c493db9bf6fbc0a2e2c25.tgz","fileCount":99,"integrity":"sha512-Y3FZJ0cySFDAqcmc+8ml6/m76y+czgQo+5yxcp5MQQrB2ubN6Jyxwp40l4P/Aq2S1dWVgUgtZl+2RXpIxMPl3Q==","signatures":[{"sig":"MEYCIQCZgK9GtGNW1QKMSnEulZpdpkKHYMDGt8Gl82NSliwnRAIhAOCMvKyfABPFCrEaxvIoSCZ6xV8/gce4MHK7VV7jrRyX","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":707928,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJghDCTCRA9TVsSAnZWagAAVFAP+wSIJhanVOegA/oO+C7p\n1hawkO6hb+y5Ddw3kQdQ26LBODvcKGmVE1QJBgkm8jQVbWKuFPAO7HNdtivw\ncQrOx5EnGfKglLsq3ZYMMPtlmnv3wIDRVOCXvkUmRgzrtmg69pAv0xadMA/g\njLr7HnBxZDLzE3DtVLltG6ih8tfchCdMIDPfY4Bi+MtudgUclOjnw1oLPaFZ\nO7UJwD0vuh8FguZoDYRHFBMLbjU4av6w4QTMIPkRryZs8ndUKhX/uL4hNwCV\nwm3gV3mEMU/quKnNlASu6XNVCiFY+Sh04QrJ75zXCsdXwNhlHStvev9IhZ19\ncKePiT6foCyCo8E1ZkbUUPyg+y1BtWeB5i4+BaVRgrozH1EuuZHf3zSO3yW5\nnnwSlCoSAhUlig0ZO1f4n+T/o/JVN9/KPKVrvkBmsf3P6WL6NEOXjIRyAx46\nR3qyWCm4aDK0D+froLi+xXfYsphWVosEB7GvOvoQH+mrJKGYmWKZz7LJ+TLz\nDKPM40azHODyo7fx/uxiRVSP6xe/lF5VDDY7tzOTkAaRalhBmzq8UCwZAdKu\nBv/VmeVMvNmu4WAhBzhmsofWEQfUWbHd+Zv5tnnFectGEYE9WasMkylr15oU\nuSN57dy/sDL8q1DnyIe2/hVVsinHD4btJcymt0Ee/5JoMzGHdu1FLNyRl9jx\nPpuV\r\n=BRED\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c6c2887e392083f3b89c493db9bf6fbc0a2e2c25","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.9","@balena/jellyfish-logger":"^2.1.33","@balena/jellyfish-metrics":"^1.0.137","@balena/jellyfish-environment":"^4.1.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.123","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.12-renovate-non-major-c6c2887e392083f3b89c493db9bf6fbc0a2e2c25_1619275922528_0.7434926356019231","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.12":{"name":"@balena/jellyfish-core","version":"2.13.12","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.12","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"325849df43194b8034a45eb210508af86d5ae648","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.12.tgz","fileCount":99,"integrity":"sha512-fRT1+WanmujsZETay2aeUW40ZJl0v6ansQd77M3J1mQ3m91PrCYn8xXg68Npt4wqOKw3nM3hkbWHIJ5AcI/HfQ==","signatures":[{"sig":"MEQCIG3Y1if1q02APoY5Eh4slL+Y+azx4SwvrXOWv6ePFO+KAiAKrag6sbFVEaf18Vxkag1Gk4+nbdSdL9Aa6IaPFSUHWQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":707868,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJghDJeCRA9TVsSAnZWagAALqgP/AlrvFkJ6HxkuQBE3clD\nsAd3FgeAwNqD191C4Pj1MkYMDdT3fXKsigPHoBDvLxIQNF0mQKo1rX3dG4cP\n1wt0PlfRKSH9gcjRwQ+oSJ7AhOJFrliP4xKFg3zBsaFs4gkVvg5hXmuaC9Um\n/1z8BrgeowY8k95WSzjI0UdfqdEwpppkDshpTVJlGpSV0DJuYvLAsNvPj0/f\ndT3KWElsTUgay+W7qFli8MWTTrpgKCvj6j8/xHD1QhIglt1jxR4HvFScNqc6\nw3R1GOOslf1Lwtu8DpL0eAUSw+6m8mvyo/5KCt4eNlCnYHfTaLzgY4RCWeAY\ndaxaap+LgkLSARDvl8ScjQ3DF7lBi+nArfPz+gmwbrEuTNjih038jhlLTQ5x\n0lnED85skURtN/DwvA3JqvK5rIcMEgxSnUr7ChRWwE/1UtIlqbqnV1V/A1lt\nUeqTFVaZD13xi4msI1Q0UOsty3R8GPhqGijGA+eZgexghXwcqNOD7aNEvBpg\nRPschU18xCOykfwOsunjav6HjbUxX+iSUAwqyRZoXg0sE0eSBKnKOgl4hTvW\naZJLAGJ7zqsz0Qwu0osCsYxupn3GKUWl6QOhcveaDlXKrrkLZyWNbRCA19e0\nSHU1HBjTRtE2X/Xms0eiMjV9xKAV9TJDRA2FG2rZR8j7gI7/0V0+5m0uCenZ\nUhBW\r\n=jn/z\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ec571c042d665a9b80292aa0875ca984086d718e","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.9","@balena/jellyfish-logger":"^2.1.33","@balena/jellyfish-metrics":"^1.0.137","@balena/jellyfish-environment":"^4.1.6"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.123","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.12_1619276382273_0.4398607136135997","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.13-renovate-non-major-eeff73f449f6020242330f5dd36f3fd0e4d381d8":{"name":"@balena/jellyfish-core","version":"2.13.13-renovate-non-major-eeff73f449f6020242330f5dd36f3fd0e4d381d8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.13-renovate-non-major-eeff73f449f6020242330f5dd36f3fd0e4d381d8","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"61d4c20a61dd3e19369be0e94c072f418d8fe107","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.13-renovate-non-major-eeff73f449f6020242330f5dd36f3fd0e4d381d8.tgz","fileCount":99,"integrity":"sha512-DXJG/EEzoP2Kkt0OCKf9ZlgrcICSuGUaTimsXgn/SAll2nJ7WH4Tx+W/o0R8uQn2tEjIwiDs+LjLXGwNgGwaww==","signatures":[{"sig":"MEQCIHMTg3ryt/G7UhwNUbjSnc/++9nErbYF6xGDvovhssAFAiAumMW3eTIHDY/Ra5bt/XEjjim94Poo+Wp9aAJYqlV7Mw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":708279,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJghGO0CRA9TVsSAnZWagAAi+MP/1i7JahCyxRr3muVaeCS\n5MRi3IA/yuO1LowxLakLCCUMCwjOnlDRmqY05+Mpo/pTkWhDadjRjMDEt9VJ\nDmEbJBaiNJvPF+StNyVYnIXVUPGp+c7N0nm2HrXscQTyslwetZRYkh5EeI+w\nPwzkG0jtWIAcfs64yN7EMcBKH2uiZotE4wbaBVFtoWGQTlgc0FAtsiOLbhPX\n+0QVO8G+GaD0pCPVzAhBPadp+L1+QrxCysWDL2VJBIkYemjZa0s3JCnU+jpS\ntKEbEJeJiHjl7sAklGKu8qkQDnYBnQ2mtf8qw6xsJRL5UmGuDwZ9pncqzfmP\nP5ZTVR1/zv7AjIMQ5E1fzRXDWl/8zp0WoKJARbwBk3T8Ds8o+3+1k/fHFm8G\nGyagZHrewXQtBuFfrS4eHKsPOB2UN6XGvcHH0fVTikHXU/7IJaY0WpSl46uG\nuXPTX2WjRO61/hHcSIvN9t69uAOQY/QZ5cloaj1SOmRfvI9KNmwaEsJMIDek\nRN6vZYR+M29Ygj7vf104TwV+0Z8ZElf+xG5TFyvDSKuGDGRs6q/fnMDEty14\nnF3kYk3uHyAtzECn1lzANYa9OUAZ5kNiJM/SJNnuh+mY2zfFpvY8wcSVNg87\nkHFmBw1fNVEHk7jaN8jsx8Q7e7TyIl2gLK6lOvDKUPMZ+6nDMljc6/yg5kl8\nkv+q\r\n=Ne06\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"eeff73f449f6020242330f5dd36f3fd0e4d381d8","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.9","@balena/jellyfish-logger":"^2.1.34","@balena/jellyfish-metrics":"^1.0.138","@balena/jellyfish-environment":"^4.1.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.123","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.13-renovate-non-major-eeff73f449f6020242330f5dd36f3fd0e4d381d8_1619289011386_0.9483092544949792","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.13":{"name":"@balena/jellyfish-core","version":"2.13.13","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.13","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"382ef22ad7e75abd87d361e411cb0b764773a9d3","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.13.tgz","fileCount":99,"integrity":"sha512-fVj9r+3cVwwhdoU2D1/NjrKdjFnH61dXRQlCrH3+DLPXO9gam3zRkNF0UwV6qKBFiaH4SskMAspIVDpyrY4h5Q==","signatures":[{"sig":"MEUCIDxl+Sq2UnarrF/qeVZmR7h36+uH4W/oPHoj+tKOYF8mAiEAyNye7OAUyXpAGCjHmHsn/WFBogUhW3rrdWl+iQut8XY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":708219,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJghGlcCRA9TVsSAnZWagAAv70QAJX3alK3vHbsHPBbCj71\nQpdFuy66EJin8AH/lcPvKfpPyPBlvdIjO0opsmg92XmGRQcrFHIcOMwRSwg+\nkxRcPoybioZySI7xMmfpX4crC8xUDP3/ZazGqqiwqiZZoud0PZ43j0YSQd8i\nkmtWgY9rAz3w4p1W19lPpCrtm6QRTOrTlDBWUrc5jxGa0/AHv6e1a/lXqto7\nwuAzWbEq7EWiEFDBDYBV7vfoT5RoOqbCdth7I0u7sCChupIP4BK80wOXrOO0\n3wMt/WrodSrOy34i9hCHsLM4JSBOm1OSbcr6xVb6ysYs3w4hs5r66e71z74C\nEuWuGRn+9ukdDvo3kjApqOhlXMbtVwQjweDpaN5HAMESiWQAicOwgORas0MR\n4d2HPdlXchVQR1Pm0KIpYP5zHHNhqakPPZH+hbWm25GeZmUDNYkKOm84cdnY\n8nZNnzUw01+nv3XUb0VgN92S82pPVqOFPqZ+e5/WuXLW/g1Jt5G/n4lCXLRb\nI3sCjJ0hPkFzNX7SzEzuI7GdRW2DRx6xro8XcmgN24gtVrWuW3S0YWktaF0l\ngFGgAMn7bMGw4T7RMMJxOwRuIJR/90fwkn70xvA5TBACawNUV2x4krKkgEC5\nr3sEzzbVQbpMOxxPnAFTxGmew2UCB1V+AlwDgK8ELY893O3NaBAa34kTZ5Gt\nDbQt\r\n=9m2t\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7dc37e9a7aeef88d2cbf8b140dcf7664044f64b8","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.9","@balena/jellyfish-logger":"^2.1.34","@balena/jellyfish-metrics":"^1.0.138","@balena/jellyfish-environment":"^4.1.6"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.123","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.13_1619290460415_0.2506075573948894","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.14-renovate-non-major-92197ec30dc6ea0b24118714fd611282ac3c9cb2":{"name":"@balena/jellyfish-core","version":"2.13.14-renovate-non-major-92197ec30dc6ea0b24118714fd611282ac3c9cb2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.14-renovate-non-major-92197ec30dc6ea0b24118714fd611282ac3c9cb2","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3dfb6973e94a07b6e054c20a5d48ae8c964d5f99","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.14-renovate-non-major-92197ec30dc6ea0b24118714fd611282ac3c9cb2.tgz","fileCount":99,"integrity":"sha512-pN3qgpcc0rRfLytRjphGBKT0o0WNxoje2z4LHiA7qU4gJyxa29Sw6+22aCyFyVkjMpp8dwRZdQ6uf86wDkFEkQ==","signatures":[{"sig":"MEUCIQCrhcdE1F8CL70P/EougzdpkTtpEVO2qIF0+oo+WBGhTQIgbfcuzs8xT0FqTrRb5bbS8mapz3//kycqzd4Fgl+Ee8I=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":708708,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJghKFzCRA9TVsSAnZWagAAFmcP/jVdRGdgAca1k+3UdzXz\nRPYt2NkOyUIcEPfm9Nr1htNFt7PN3HST1P/C31jZJBbYvbF4HTrDOMiDhUaA\n9exIT3/RRLDgh5NDEruSIL2d3YMZf4vUpuiIrrwtKvsCiZj2EEK7p5CHiwfc\n9UciFYpj5gc0hQjNWJCZZZfQl7nJ87bsj6k/3HTGfxEG+zp5yWULl6Bm34H+\nR9negEJJ2oVHznh3BTS2SymhDVHgBucNbG9k16T8gxNtDoWkFsW3nJl62InF\nhqhXRffG56YWbVRoJWB7Ejn3HL7w2SKrbdFo6HOPmMmpbkqDkzkFJfmXSSMD\nKtIcz+jj7NzB8Dm4f3GaHYHe2tl3r0pj09jW7Rsp8bd/5J54Nr715XqHC5k+\nuSoVw6CRbNZzxDCgNwSZNzszZ2SoIQCatou0Mc296KTTxULV06oMn51Vo0s3\nqEWjICb8H4lzmzbKDWimnTDpwIQIYG2Td68TP3o/ZRJE/ct+tR92gJDb608K\nvHqI401v2Ru9QS3EZosg/v+uHR7SvaZOEa1ma8Gb9wRC8CIR00kh9MlkQeIm\n/qc/0Ma2Gt25lEY1T33TyRo9on24T2CDHsU6VuVqLoLQV1HB2pkJ3/siZQUs\n2R097TDE9e5NJ9/p7TkEVZhUBhNH9OzsclXPLerIwyU2XsnvWGu5RyoubzbV\nuCEF\r\n=bdt8\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"92197ec30dc6ea0b24118714fd611282ac3c9cb2","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.9","@balena/jellyfish-logger":"^2.1.34","@balena/jellyfish-metrics":"^1.0.139","@balena/jellyfish-environment":"^4.1.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.123","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.14-renovate-non-major-92197ec30dc6ea0b24118714fd611282ac3c9cb2_1619304819231_0.3011383154913245","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.14":{"name":"@balena/jellyfish-core","version":"2.13.14","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.14","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"53cca2dbaea09f2c1b207152882bfa946c0fbc98","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.14.tgz","fileCount":99,"integrity":"sha512-ILHQJfpxzieNpx29q4Ifb0gAhCb+Siv1ZT6os4+a/6fCK6x9i4qe4i38P+m4XCsxYbqyS5rqYvGfSwi16CYDRw==","signatures":[{"sig":"MEYCIQCM56I8zFUz2KnYD896S9ac4QrcU6TTdsh/NpwzMZi+aAIhAO8beNdZCYEX4ScD+Ftu7u84G2oNkkM2RKSJvG6Api5c","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":708648,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJghKMxCRA9TVsSAnZWagAA+TQQAJP64YuiUQjnf2SoISeB\nlwyi1ArMpOtIWKbXe0PsbRcNJSwqZe/Vp6keowpVDncvdSThtJ+uvvkANXfi\naUmYCDGluNi2T4d1orsmAY9JqSgkGoKKbU8ZOiseXS9eMkDqm8K34MGQlZf/\n0UhYFbUkNxyiaVcFJXc5lBp9By4Fenc9awkF7aLiacJQ8rHufFP9m1asyiIH\n/nNb0qZmmsMdyXFqb0Mytg+RVzGQwok2qajncu8UT4X5q8IWgUJAy6+rw6UD\nge0AgG8G3nj6KWP+qCl5Rsqy1bl8wEEPXyG8Qc5bW6uskjx+QOOPsjDhYREP\nR4p3SJFlSQWBtSY1lC7KFuYx+Z2RFskLYnJUEsYgm8tRb22AaqDi3/xHf2vY\nPOtJVXLeV1z1u8P3GUfs/PsDOZX6PN8J3SF7WMH0Tv3fhSo2wOY9kJCAc26Z\nBHQsrtXV5Cb487hs09hQfzKUACD5YRmBc3esRKVM0sQXvzKFtDbYwg6Gtbjq\nL8fnuN1ECzKpDIWdwBqxArvgFqHorNTO8Si7vlUsSc09JJahxaD87g+DFQRh\nzwPvo0KdCV1cL7Y4JmMIIdEAxqKvrNWLuMQ0dzPuRtniFsRvY0uarSNy3EyN\ntFhQ8rqTKnQoX4Dgn/+lUIQZlZebCKnJ0rsMzo5FZgZHJG/WFKDde5iHl2Sg\n0pGF\r\n=RRLj\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6fcc74ae458b1b7b329563130169d1e61d35c631","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@balena/jellyfish-uuid":"^1.0.84","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.9","@balena/jellyfish-logger":"^2.1.34","@balena/jellyfish-metrics":"^1.0.139","@balena/jellyfish-environment":"^4.1.6"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","uuid":"^8.3.2","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.123","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.14_1619305264789_0.7788425073348875","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.15-replace-jellyfish-uuid-aac8d5b22ba8bad477ef1998df13b18ab4a4e1da":{"name":"@balena/jellyfish-core","version":"2.13.15-replace-jellyfish-uuid-aac8d5b22ba8bad477ef1998df13b18ab4a4e1da","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.15-replace-jellyfish-uuid-aac8d5b22ba8bad477ef1998df13b18ab4a4e1da","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"73039e8cde2692f63ca89a8d6328629ce8eb9266","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.15-replace-jellyfish-uuid-aac8d5b22ba8bad477ef1998df13b18ab4a4e1da.tgz","fileCount":99,"integrity":"sha512-WDyuHJUKqw5s8t6AH/5kQy1cUxycYzBppfgTkvGogtAmJZt2gf28fqqQwmg9HtPRYGhu9iEjx8hoJq5UNnzrLw==","signatures":[{"sig":"MEQCIHsGBq9lYxlO3uLgZI2ntjk9iOCGTU6iPvCfzmC5JAhPAiBwnDu5bzSuTtZG07122sarOJL+NOkcU1KyHtLKbD1D/A==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":709119,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJghNmGCRA9TVsSAnZWagAA4UYP/i/+vRQjFjQ0EN615Eq+\n0x0lef5PefMwu4HHEDf1pYroRSHNi8cgsxSwac3Ash33OXkiMxkE+vZIf1n/\niMt4GjIjULFAJ+s9TK9RATV8Y4++SRyEC75HGww/NgLnq1sE8rE9HaKc9oyO\nxQ2CmOpq8ja+XvvBV5JJT+DRVATdTbdNdeXPiIYQvyWCeO0pNtDvRYlobd/x\nRNp6XPHZlDT7X6RsTcK+xGh737jsoF5ZLTT1gDrV9r3Aetbwrxh77MD/Ta4G\ndZh5nZGc/uFJlO0O+9ip44P2XTVTJYQJDEI2rNRA3+aZG7y1NoAMubxJS4xT\nwGGmkLyDhOXRUgPYzSjXu45rbLa1QDi7ZfTDAp5vJ4Ay+VBc3zpc2PDKD1F3\nDVhStupOIy6GlYzIGc5QykHnqMadaNe8LOrI/uxGz6BbtdjKyUBpf1BNibR/\nZwyPWbvPAbCPEDDwXgToTL9Z5k5XIds6pLS8VhmnxNMYDgcqXC0nPKn+5jEo\nqyPi1hIksH7qS1UNZsa54xPK3apgQdRL5Wrht5sDPpDgJCmm6TIEVt2DVTsz\nLkSUdyRG415eVQhs6ed2jvKu7GVYyy+4K7GrxZGZs6F/WjXhjIavOVpoWrx6\nnv+Ec51JGRmpU3tm1gRVywTSu7QpmycyAt5xbnjiyB4tSCWtOcCHxMvAYeL/\n1cqT\r\n=mf8e\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"aac8d5b22ba8bad477ef1998df13b18ab4a4e1da","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.9","@balena/jellyfish-logger":"^2.1.34","@balena/jellyfish-metrics":"^1.0.139","@balena/jellyfish-environment":"^4.1.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.123","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.15-replace-jellyfish-uuid-aac8d5b22ba8bad477ef1998df13b18ab4a4e1da_1619319173753_0.4155060887084705","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.15":{"name":"@balena/jellyfish-core","version":"2.13.15","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.15","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"df37169db3134b7f2e48ad753199e56aebcb49b1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.15.tgz","fileCount":99,"integrity":"sha512-664gh26KpXuOj5TQQ5ZPM+gckhFV4xgx92PAKw4Q/B5CeTWmyUtS7B2TJcKfPdiPAEc2b8GbNRzM263XHZ0oTQ==","signatures":[{"sig":"MEQCIH4t/BqX+mDYfzLuClvLSFZMNpXmVAIYDl/Y0pi57VmzAiAZ6XAV9SaOCHn6MP9xPz3+NFfHM5CrY9CFIPhyVEJE+g==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":709055,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJghNv1CRA9TVsSAnZWagAANX0P/3jwEeJVBrjCyKALLKd3\nTo58AhTgDGa/LKfeMwH+Vx5iO9Z9MCv8Duw44jBSqJQMW4gWtSUuddxexr0R\nHisj2zhOso+r7eZjMEBYpdETyfmclFLY9zgKW/l7Fb5bc3mnyhZfMcfPSK7H\n0l6gH//Yc+r0DNvN1dqJZGZWV011oPlNx80TWLW7tn1xWNiMwBH595Go39kr\ne4N9wcMNh2GeQIHF80+L1YeKAMcjo94iodHyaMHPj6qW2gnx8IufFstUmhmB\nvI45hapEZDKvSIOB7oPPGNCNXjs9U9wzJhTT6EimBTwhr1nl+eUzz83BmHxR\n84MKMZcR8Us71i0BJlFBiaVxMtkqaJsJT4L4FzDbUQpxgmAKgOCM7hpI9uQ3\n64SaOFuLteYMe39jDc2txsGs1eqVw9xpypQ993RSa/kGWeFLhUo2HRxieZSm\nI/3MDaC7jN3g7q8cMpYeI3U213sniTjxXysZv8xCe5nGQm9by2es0KQOavi1\n91JNPohE2v1MQIpa86liKH0VtrcWyzDaI2auppx0gAZKezP4D9Ag00Gq6thf\ne1+eCop5PprZrLgPu4YbjgATCFeWLWo94NDKYqv725vhqsOqf5FUPJFGS+py\nAoapIpoekl43Jvz9zjUiu6BFOc/T52gaUjEnbWIl+tM9YH06vebN1ZOYTkEB\nQOr+\r\n=9CL/\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2fdf4393d9f969fac5db817db5b72035c669c1ea","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.9","@balena/jellyfish-logger":"^2.1.34","@balena/jellyfish-metrics":"^1.0.139","@balena/jellyfish-environment":"^4.1.6"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.123","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.15_1619319796818_0.8102541704263146","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.16-renovate-non-major-202130e28fc55a2a99c12b6aeccf3b7e8b1c9758":{"name":"@balena/jellyfish-core","version":"2.13.16-renovate-non-major-202130e28fc55a2a99c12b6aeccf3b7e8b1c9758","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.16-renovate-non-major-202130e28fc55a2a99c12b6aeccf3b7e8b1c9758","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f021c084ddd639b06b5c7077f70f7f9f35e9f953","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.16-renovate-non-major-202130e28fc55a2a99c12b6aeccf3b7e8b1c9758.tgz","fileCount":99,"integrity":"sha512-Lg1cQ66BELA6cEfbaGFb5vCA2AlDprdWUOYV8dano5nnMHMgJJZgKhmsxMylxh0thUA2kb8O7P2v7v7vVYrmWw==","signatures":[{"sig":"MEUCIQChqONv0GoSqyKt6OCAMTTQja+O6J5zgJ2ldw9vQ/aYiwIgFNqF6sUEnxNKtWieYs/DwZsWmXGRRZSBWTZJR4PzM6w=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":709466,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJghN0JCRA9TVsSAnZWagAAqW0P/3ux00ImHIrMcXS7OEVs\nAe8mSLfcUWYXGRdNPsn//BgLiq20rQxzNjSMbQLtGOVO4isDFQWDe2qANeR5\nK2+Eql7nLdigIAIk++G+O/QkiGpT/kJDCE6712cxVOCsbr0Nklc+jGjO1W9q\nWoN5WbNhSQOVFGdMbVmyb3rZb8uD4PzGGZ2Bs7AxkITkxMfcsc3J+N4agEDc\nWCHaTAMVxp08mpOBRPWfcZZcxi8l4iuMR0OvKmHOxa3duBbd361FC88bKt9w\n20DPJwtVdCS+u0LEcTiQCCOIYiU5PPaRD65wXakzzUWQoPg/ymsYXZfcfUWi\nZzZJOevszrdMzNFxZHokDeeOdGK6p8JnfQkP7grXLLVXkqDq9ZhkRvAAeHgq\npr7llKfJyqz9LbeS1V2HbinMNeAojdGOCMEf3wzwSR/vCUBb1R0ZsOuecF2y\naRcnsY2h1raPAfR4n23ZjW/tKYEh1zlRrkeOJ/oC8/brWr88QJYY+s4XL+oq\njgav8SXB0zZzxgWjj1Y19vJlffNanoAAGKE4YDjvtJXmWEHAPTkiovhKmHmn\nqSTD8ypgLiNeeber1PIHuUK+cFr2nV73gja/OTWP5V2w/4H5yzR0mxTAmnZl\nUTjbFNof5ixWO0aerTEGUm1QRJTJd1f5yWHJPAM9f1lSAUQF9lPGEcX3HYEK\nBwDx\r\n=IhoK\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"202130e28fc55a2a99c12b6aeccf3b7e8b1c9758","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.9","@balena/jellyfish-logger":"^2.1.34","@balena/jellyfish-metrics":"^1.0.140","@balena/jellyfish-environment":"^4.1.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.124","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.16-renovate-non-major-202130e28fc55a2a99c12b6aeccf3b7e8b1c9758_1619320073019_0.24973911333753973","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.16":{"name":"@balena/jellyfish-core","version":"2.13.16","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.16","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b16e139ace36d3237444b04e0714d8e85a975d3e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.16.tgz","fileCount":99,"integrity":"sha512-hDTcc92x9v48/Oavq+7dCCAV8Rpby87wxCgVja4Eo0mZoj7GSUh/itgzc/M+o//dZJYE3XwKoDZ3nav6DqJLVg==","signatures":[{"sig":"MEUCIQCx8UBNiXo8fcIfB48UZy/gMc0ZgLkhhDsS4P2j/JXOjAIgA2Z8ilpcCJdhKpquFyUvUD9hkYjDLiFxaoOj9RvLiAM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":709406,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJghN7vCRA9TVsSAnZWagAARScQAJasZEVZxjTpzyLHR+hx\n+GblCRDjl6VqUkALl443dnfoCbLUoykil5dSAZGV/6Pdl+ItfnczIyRL6H9N\nGFhLDEuTek1rk6RtNhzhl1SUG/pQz08qPqoALe87+GpKlcdJzhEerAXSXa9g\noFU1697247IZAtEyy/YdalFqbZstxCBtrBd4HblSOkSw6zSsOp8XSX110NoB\nAfuhxLQ3RkSDWPlU5+cLrcVFZu7+M1I4TpWeZGmFWdot6VfSZZvVxQtMp66e\ntyE3Zvs4nGSQ7E9FFhfvr2726XKNaFbcZtWdasXKH5AiO4gqb1/4ehyd7WKd\no+SRupl3ANklACs9MRSB7uxev/bo+Ylv0RVHenc6JeirFeK+hd3dRsKyQlcu\nEi2NMKt55cMhjaZH3rCfkTS8ajVzDZv3NdRQ8/iVAy8QVOxelezv3Jf/EjWt\nMTAnuyb/+jwsmAb9CGiaXycjTSlj93bboIQJeT+t19HfYz41AA0QsxKQqh1w\nBhI52ocI1n0UPkAWvLeeYq6FzIkpMGuTtiOAcIv6TQtvTZhwtewsTfL65Lub\n84e+dJXYFrhkkyaMppbJfDxoHcFa6N3goq+HbhceBSGu9A2HP7TEbLgLo1Me\ncvx190NBw37dgTbDJosyq/zNd2NvdT6j/oN+n8t79MP5dABr2pb1nmgfYI1W\nrX1n\r\n=gdIb\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"3432bd2e4babac384b519c4052d8e5b1eccef389","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.9","@balena/jellyfish-logger":"^2.1.34","@balena/jellyfish-metrics":"^1.0.140","@balena/jellyfish-environment":"^4.1.6"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.124","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.16_1619320558691_0.3765231763093029","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.17-renovate-non-major-3caf9f7f138e8b3c0be42bbe219c017321a3da58":{"name":"@balena/jellyfish-core","version":"2.13.17-renovate-non-major-3caf9f7f138e8b3c0be42bbe219c017321a3da58","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.17-renovate-non-major-3caf9f7f138e8b3c0be42bbe219c017321a3da58","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f1134f6c01b46f019c6400ea2a1b9420c125911b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.17-renovate-non-major-3caf9f7f138e8b3c0be42bbe219c017321a3da58.tgz","fileCount":99,"integrity":"sha512-BjT02TuG547nvFgrf1qIe8q2HPqrdpYdWARpsA2iN5lP6LXk0mCIKykQs7ARfPaLEFmZRmTZRvXwXQF7guhBng==","signatures":[{"sig":"MEUCIQDdMo03C0deo3vOZ8sqFzmGAE0OqsD+eXKAP1yQ6QNuyAIgLMRPK0IVlU+J7WFHka7NZfPZBo9LtYOi0vb6iKkJoFw=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":709895,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJghggTCRA9TVsSAnZWagAAkAEQAKRgtgC9evwwDLsUWf4W\ns8DV7xWYoKYj3mvgsaP7OdXyYnULLpe4FlqYRaDUVUFg3GyhRS0UNtZx+M9R\nApHsTtY7g7kGYBQ/2m8GcClkLUFeDzXl6JtNfzKIUdqF/FxGdkzLPjcePzdA\nAygn2hxIP1O15esNgks9fw06DDksBQJtw/UACO6AT7EPcsNC0JlHkm6M9lwR\nHa8V4scAxznSzesMBe+4u3fN0ZDBn0/xeYK7TvGdDdLqTDkHkVzlv3VacbUv\ne6ngashtgc+yI1DidWWnJREVQ87fEldtkYkkzMy58aM90qWK/idXAK/eUE75\nebRXYyGuEUVnWm9tAbXWUokwzSp6J/yeFcRizT9U6IG3WVtZAuO6W8iWZe3O\n4NbJl5swqNaHAVFr19yNykPCKxfCJB3+7UmmFsWx7kPD1FP9HnCt+OF/JU7h\nGfdBnCSuNx/31JqAyYaF4UoUQhTxkH98rMfwdH4BBUWvBJ+flJikjgG0Dedc\nZ2Weu6oIUP2VH+M+6KFa7wAIbonst3u8gysY4ZKqR0Sg+urAPk1Wl+x75EDj\nOrESufu/N9iYXuqMmJZqM+pranllyG0x+T4ItHB/Q6HNmdpX7MPzMcCdMjDL\ngU0nhJrq0/KY50WJAwy4tK4mmmRlsfnh5xUZAnDrLLhVADzIiXuiW0XLHS64\npW11\r\n=zooJ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"3caf9f7f138e8b3c0be42bbe219c017321a3da58","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.9","@balena/jellyfish-logger":"^2.1.34","@balena/jellyfish-metrics":"^1.0.141","@balena/jellyfish-environment":"^4.1.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.124","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.17-renovate-non-major-3caf9f7f138e8b3c0be42bbe219c017321a3da58_1619396626596_0.6763896590957654","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.17":{"name":"@balena/jellyfish-core","version":"2.13.17","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.17","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b2e0c428b511f0645e0cd90e905f9ac119b2aedf","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.17.tgz","fileCount":99,"integrity":"sha512-WpdFVosEwb5nD6FLuoVZ3GoLgB4CPEmMqGRHRqWLO+XbOqiLHvEZNHBR+gawMyFbHgGKc4PU+06cVAoHx64xLw==","signatures":[{"sig":"MEYCIQCwvV4ERKkjgiC75lSOaJtRdSdkEKx2SFU5fZOCY1PSQgIhALtFyCjPFInB0ZQ2IL2HCIkuuEBUCMAjMPweAchUGV42","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":709835,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJghgmtCRA9TVsSAnZWagAApPAQAI+mKCfSNw/Opnu4WSqA\nscsWtepgd/fTt058GM7h4XRV6AubbQ/7P4sKKcZsVfFzG0OWECb29rCOv3aN\nz9IZJmIKMHxlZBxYzXXHRQ1Ey7hC1jmjKQQfTJhEvTyS71RKSKmkTOJJo9ui\nopCcUF0T1GzHPwtLTfBrqGI+NjsfTovh44mZGOB+ywamKJCtK0TdeQnmwqXY\n/vPU49qaBxqdWtXKcHGwAJrFygxDcI4ekYljyCc3vinxc1NMGpnyCjS64z9T\nf5Kj0pavzHi0utKTxEDX4v3v1/GtdjEPo+ctst6t3WWZrSaUIGCH+p0BM5Vr\n5p6QAF8Ki70KgZe0SmSXd6CLfDhXZ1Wp0TuZENH2yl2t6nHxwB2npD5gxO0f\n0ZlrISnkJx1SYdhggxxaZql9t/thzAgHxgtewPT/kXp+0E9Jtfa76NkyHn3e\neTRQGhviK3vs8zCNcRyViRNBdHS64cBKAuLRhv/yB+YuTzUnhXupEAOsI+Tx\nsswbjP9E9MHRqgfOXF6ljW5rTTSgLYfo45KMLTNLEMBohybIkbqd6laJxJ3C\nv5JPwuP2tET+y6qEQ75j9reIid6w/x9rYTADi53s0vOfkKnPlQanSKo1PPBt\nf9pil6ILqjJhFgLA7v4XkQuSJtIChk6goYjBApdkiv/RWylD2eJQFRXlu7Vm\no4+g\r\n=cIyQ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"56bfe90afdaccf2bb7f4bb53130239e6719d7958","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.9","@balena/jellyfish-logger":"^2.1.34","@balena/jellyfish-metrics":"^1.0.141","@balena/jellyfish-environment":"^4.1.6"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.124","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.17_1619397036907_0.12345817545510362","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.18-renovate-non-major-3dd379ef5b056719957c70e503544c8a377f5535":{"name":"@balena/jellyfish-core","version":"2.13.18-renovate-non-major-3dd379ef5b056719957c70e503544c8a377f5535","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.18-renovate-non-major-3dd379ef5b056719957c70e503544c8a377f5535","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"57f12f840316e6b16b316d3699e08b0e82a26541","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.18-renovate-non-major-3dd379ef5b056719957c70e503544c8a377f5535.tgz","fileCount":99,"integrity":"sha512-wQqDQBEY20F7CcA7SYM6ymsWZGjdVaA1btw/nrVEg7RGFvOnY1YKEwhiQMdVTV3xmKdQYJRA6+nRvWt8x9YCww==","signatures":[{"sig":"MEMCH1OFCHvpw7WTq1/HGIrLd5WPWJMXioDGYSpONAY1dIwCIHsawYEo3vGmVJvofQJyfugX30FoPkSQWa7DtwIlK1zX","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":710324,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJghh5FCRA9TVsSAnZWagAA8lQQAI5kztnH8BhPV1UApwC1\nO2Otg8U3E/hqDZWUjHMmzWmq7C2/VgG61Z/QDjOv0ILRYip0M3851JpXoGwg\nbxvgmZhGa5GepOa/8YKUMTsB08u3fQXbcMj4nLAd6WCUN9IiJA5Snm0NK5y/\niCdn+HbvGBQWGLfGem1+VpHDHz/BecbZs+8JI4qHNUNSzay2ucrH+I8WRniD\nN59WEsJwsDKetZRr1puGBz0mo0pkWJeCGyMjmsCBXPi0mqUpSZS8hcTBRDaI\n0wUbeMNpwE3hgGTHlYHu16e9T1Y/VorOtMoYUVG/B6yAZsLI0KFbhMk9boea\n241m3+yIc0XNBnLeFe1uxL5rJ6bBIP6AvgjSMSoiiuWdTdPufX84q4FcxOGd\nZa0UxIzkjfsknqPVDfDdKpSg8wlrsjtpjS0/xhksRF4GaOeN7axtC7CQes5y\nT7w8LevO8tm2CtqMtn1u9czWr1xiMpvzuzsCWNI5md++6aMQoIDcbsOLE/kT\nIzY+zh+J7YJ/v7KIbkrTJ+moOKKdSbjHULxT7xfa+kiphmH+j+Po5n2pyPqs\nMqXBcAllZ9nfRk9cMdYjnXgekP8JJuyjcPwvvs6PO1GVLHpH41blBDecUYhg\nDquoXM5x3S0fVqmSlCzqFYywqEpNx69yF8RsQ+lIy/aC5qnUC3UyttKppjz8\nHKqi\r\n=yXUg\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"3dd379ef5b056719957c70e503544c8a377f5535","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.9","@balena/jellyfish-logger":"^2.1.34","@balena/jellyfish-metrics":"^1.0.142","@balena/jellyfish-environment":"^4.1.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.124","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.18-renovate-non-major-3dd379ef5b056719957c70e503544c8a377f5535_1619402308773_0.06164639935587868","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.18":{"name":"@balena/jellyfish-core","version":"2.13.18","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.18","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ff0d834a9b12091179e39b301a6ad35ee7d62ba6","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.18.tgz","fileCount":99,"integrity":"sha512-3G0d1MZ0Sm9cD7wOIGvrGkOZS4jt1zpZIlYEx8bsZc6CD3wootZgyIeEaynOAwmQ1nNrB6ePOSXGlQ1kxLFUzw==","signatures":[{"sig":"MEUCIQDh+9GpayEODLuDwc8BjgqXFtIqB9t3Q0Ijk028hUu0AAIgXl2ioxbtLJYgnzp6WtDMGqJPKdafyPA+prfdejtqXmw=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":710264,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJghh+oCRA9TVsSAnZWagAA0WUQAI7naaCqEwMzrmkvuF0X\n9ZdUMc5miA4TgcCj/Lf5+pplmfA196AHr9jxc1G0h6i5OMC5/OsjA1xsPi1/\n7LrzaInYEOBhogp6kHxRbnrAUv9MQWc3ZbUTzpGLf+mvYGsJE/D+4ZSrnOyD\nJVVMgrBzvIEyXlZx0IC4QCxRIXGiepegqL9dDTZLzFfOaLOnmp11RzwJ0OqX\n7/vw5gpmTyXh8XWDNVocC9SdKF+7xu47mUkv8xlmdiHAq1NMCEvNxWNxpWeo\nQ76cj6JdSsvIoQ6py3q4t71tMUKnffxA1rxziHctRsxL3iVgdpPleWuuCdZ2\nULpnuuVRaHY199hEh3D9cpT4hTvhbgsKY4wobGlc2j4jJYhWrF3v7Yk+U9i6\nAbTSV7mmlAXGEDZmMPQkQZc6NiK9zjU+hAsWzYnt+ojD0O53/SDqefj6zGjU\nWdmZdfKO7g8XtoS8JEDh7fBvdfmyPPvDE2mP/eVRAxC8VPlCjyLD5EHDUTQQ\nY0RxyQEsuZ+IYfg7GQEnexzxzsmB1ruInk2If0xgfgopVUPLV6Y7uQjYv/Li\naKREl/kbgEBGt9fiqupAOyhBSUpjOUUriwwwboGMz2MXpDXzTXwHiZ7amdYU\nf3Y/3/dbFYcPaqz13gCK9drQ8SlLFmribetBjOcoIVdGsItCvyUzdMCHsR/Z\nJuCp\r\n=puSG\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"869cf1fafa07538ff72bee9fc208d3a916406f31","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.9","@balena/jellyfish-logger":"^2.1.34","@balena/jellyfish-metrics":"^1.0.142","@balena/jellyfish-environment":"^4.1.6"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.124","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.18_1619402663986_0.5023639832908333","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.19-renovate-non-major-82cdac3db674725e2c86675b678f90187cbeafb7":{"name":"@balena/jellyfish-core","version":"2.13.19-renovate-non-major-82cdac3db674725e2c86675b678f90187cbeafb7","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.19-renovate-non-major-82cdac3db674725e2c86675b678f90187cbeafb7","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1ddb07b12632289d689932da511f896523ffe0d4","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.19-renovate-non-major-82cdac3db674725e2c86675b678f90187cbeafb7.tgz","fileCount":99,"integrity":"sha512-Nfv9JuE9kjrnVDVGJnNLhLCIyMnDK5z4JUj5wG2ftYUuLkgAGB36OE7326MWw4HBnBMBY54HeWx6KBEhemM1oA==","signatures":[{"sig":"MEYCIQCgm5fjmlstTANOarpSVTp5InOQWzROIvjg+I1MK/ysqAIhAPjwX+6LkUK63Zyx+oBdF66+/4HioLFtZPIFYTefvWD4","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":710753,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJghi9ICRA9TVsSAnZWagAAXXcP/3q1DfwgMKBWUeFxDdma\n2XvjG/viQVE32o6Ak1aWnHSpZamwuNEdj0n7U9k6cqcKGF6R0ShUHFDMNK5a\noJafhqOQQo18BfC2CiXvM0n6j6imy7bopZHJO+AXMT/g6/VfnV1ddPjvBcbP\n+If7wvVCwYdUR88orUgpwM1GNz/Xo21mGVWD6yVUwIdttrUCyO5XQGzMYRkz\nLqm2QaoOvgE+zXrbPAEGP3tYtzD0b2CinCZ0pvCirKfRMOMZN0+nnY4+Lis7\nQALac8yBGxgSnLk4bLVR7k+MYiBbO7itQsa0mASXaViTY2xR/Lk4/SZa5b0C\nwBlRamv0WASfxDGLfWRHZX2McxpIPJqvKxjiwGUZzt2fIJXARkp4u4U12k7q\nYaJ91mw02yi2IoTGIaOEsT/LO1NRIYNm0yKi0vrnpMAuuMIN8EZ0XDYQdmRQ\nNNXLuQRIOCIULdne0R7Zc2IyKgqSnZOgXaQg6LqZt1AhdFJhsBjSY3QJVA+u\nuoG4GSoeCJCUJhQ7VCMa/cTr5gjysehbXZS10S20vDboUCuJ/FKYfWrl+Zzi\nCU809ExHZLwMTtBk7meSMjB/Qyf3b42HSexMsSfry1oAmQ78APPPqUD3+xy+\n+Calkm9agilUH9pzF+MtV784TC3zIJIUpGjDVEjDgNmyCF2eDOYvmO3ai3f/\nTwiL\r\n=M3gF\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"82cdac3db674725e2c86675b678f90187cbeafb7","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.9","@balena/jellyfish-logger":"^2.1.34","@balena/jellyfish-metrics":"^1.0.143","@balena/jellyfish-environment":"^4.1.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.124","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.19-renovate-non-major-82cdac3db674725e2c86675b678f90187cbeafb7_1619406664350_0.8314342013194587","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.19":{"name":"@balena/jellyfish-core","version":"2.13.19","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.19","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"82a4bd41c99f20d2a50ba44f3acc28c2ec287d40","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.19.tgz","fileCount":99,"integrity":"sha512-iJXfILIWZOCKbjDVHBLOyddoGT3qXUMUb8YYPLbj8UJd7JZxeYsQZHRqr9IfvgHHembo4S5un3PWDdJ2qA8S4g==","signatures":[{"sig":"MEYCIQDeahQhXIH+ahlwnoMVemyQJFu+mV5dTCHnPtFE3DHLqAIhAOcWbPmezFsZszS5NR76OFrAzXOa/1RaCGPPUsmpN+91","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":710693,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJghjCZCRA9TVsSAnZWagAA/B4P/06gJIZWDmr7JkVmB6QJ\nZtLJ6/rauPqsCgbIwRFP5O5yuo9gs+E0iy4eUXvXreynasJ95YBN6BBIpyMt\nkEqHRojh9orW0lMScKYulYIvnNESck5I9pge5aIz3EynXurB6cVUwwNP4GUC\n6i3mBkpUuVwEp2Vt7NhSl69F6rq14kFfQZJY2bprnX9ajHGnzMf5gQcn3tws\nM4kX1S2VnCQbGyvoxzN+dK650E3czm3isWDIyrGCmGztGazs67DAmjI05eCf\nG82XzXRjC57sg1CY44yTc6Y+OyOfh5nkEjL03op7kRs958HUPQgZOMRjPreZ\n/Sc9nclgfozH93dpuxellbxzK975x9O30ca0MeW/1bGo3DdZv+amduEA/2yr\nmMRHv6aTvox87R0B/OgQYpcfwWYo4UYs1X//pQ4xBgYkg8as+UBBSejQEoZ+\nE51U9ReyjLhWPC8jU7Xc1wai3bFZ0kuUhbYmRz/dJEZG40lHv4le5zo0CIup\nkOXDOlGJy//2RNytaEun0FPT/rJfnv8jTMD+Azwx6Bhq0eX7m45q1/tqLacq\ndvEdrqgZkeIAfxfJlh8XsJrXg6ROuMxmX3Rf/uzJPuYJ+UCukYJGisZOFJz9\n+st9YX0h00FV6pZDHfGA1sA4xjXABEAt3RSiGgRaP7PtfnzCOqN0c3+HvJmm\nD6RJ\r\n=yu9Z\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"5178733c86e6f21c59f2d3ee4fdc8192efe4ea7d","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.9","@balena/jellyfish-logger":"^2.1.34","@balena/jellyfish-metrics":"^1.0.143","@balena/jellyfish-environment":"^4.1.6"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.124","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.19_1619407000549_0.86259496268781","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.20-renovate-non-major-287da357efe1ff433b3a32ff66cdd30cc7c3780a":{"name":"@balena/jellyfish-core","version":"2.13.20-renovate-non-major-287da357efe1ff433b3a32ff66cdd30cc7c3780a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.20-renovate-non-major-287da357efe1ff433b3a32ff66cdd30cc7c3780a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a2438ce946a5cf215f77eefdfae59ab40e58c601","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.20-renovate-non-major-287da357efe1ff433b3a32ff66cdd30cc7c3780a.tgz","fileCount":99,"integrity":"sha512-swKhBieiu26DKFtRL+JS0kE4sOKktjU+TH7ZC+Q78XPU1bKVfdmK5i3MAlb47ItS9WNGH2xr/KEE/pJU/43W9Q==","signatures":[{"sig":"MEUCIAk7+YAd6msAXdql0nuS5sBXgjUkdtBM1avZXYYiVZEAAiEA6goSaUtXirkJ7atjIM3K6xp+jG/JIxoql5uF2rFISwU=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":711104,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJghq8MCRA9TVsSAnZWagAAaK8QAI9k7aOOSTn7GzYdyucf\nTnLXe+OG5CK5Ld66R335PuHvZXGlNG/A2ZNDV+KTIKiTX1g/SL2HltZcdFj8\nlYWJl+Mzh52QHzzl3hE9zKU0KdANNKdZgDmx+qpwb39yExRh3i0jgx8PnF3v\nion6vv327EEt62yXwoHx74R05xUwQ/iWrhbIC0zm3VZ3+815G11bqPVu2upV\nAU4FKs/w3QYKafx7UnFVRasGSEEc1/ZMbuw6qpP39W9WkDsTZUrQsHWNjnsd\nGljnYOV+ar6eLR52bWMzpxqzPXR/zyFFkqSL/Hxqoodq4To7xhOhVMYiWpsJ\nXcaUp44irj8CYq792wLxfRHMKYI2Qoha/G+brpBZ3BVSlbuQ7B+TLUtJlfMZ\nfqtl4zIFRDrdOksY0DLVDirkX5YxYApZo7MMr4oHs8vLWTtfLIBrjS8O9uWn\ncTRtlHwNNQo9HPxoeAtUCTPN99nQ1P7RKG/HyVI+QvOn+Ptw7ZhCiMwX51m0\nCzuIOxo5vNVYhkoLuJ/e12ZCblu3lmNJKNrA3AHubWizYXKzd1TVUmfDa/Uk\nQxQrp2J46OQISzKgpAKhecgIOvRs/2YxpidX2BoiqvzWFUa1QCb9Ko6+Q7fQ\nAAa7mxlnYPB74XkNvL8azWkFQQoLOqP9dftKuJDNHnVCvdMOci2MzNj5bJ1C\ncZYV\r\n=wUkC\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"287da357efe1ff433b3a32ff66cdd30cc7c3780a","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.9","@balena/jellyfish-logger":"^2.1.35","@balena/jellyfish-metrics":"^1.0.144","@balena/jellyfish-environment":"^4.1.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.124","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.20-renovate-non-major-287da357efe1ff433b3a32ff66cdd30cc7c3780a_1619439371475_0.5221096035627149","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.20":{"name":"@balena/jellyfish-core","version":"2.13.20","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.20","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5e49bfbffebc0fec1c8f7479ec400bce344b4fe8","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.20.tgz","fileCount":99,"integrity":"sha512-YGm4GOxjdutiDOH8eQMroxkpbpB9DJZ3R4UTidE/PmreUmWIkvovc0pZojnUWgBjEBE3flaEFo8K+GoJRsh9lQ==","signatures":[{"sig":"MEQCIAymcF8T6QDXk0PFbQ+hpyCJvlc57svXKbyXYB0sme6KAiAeb+1mrcT8eEuq73shW/2W99cjk5Oesd505CfyVxtdJw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":711044,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJghrDTCRA9TVsSAnZWagAAq24P/3LaMur5w3OI69CauvDj\nclVT3ZFhtBONFQS+2IOGxwY6VwB0YQ1x5Ur1w9qbioExAYF5kE7o4nxA2lfI\nydcxmYBas+nA/CP1dkhe5XcFngzQe4mcTVs2+E3awLFpX4h6bnBdBXqy6vT/\nGJMXCL06dTGUwopWD8PLJDFET6FT6qeGpDTmJHTn9f63rUU07IiIMDXby1Ts\nNTNRkCCW/A9QxSYfH7WZjrAWqSoTG5eV6QyPhgnQXvxI1oXgnUymk4e6vAeW\n+oQMuIWDn+GaM1NqPPjVvgGze3rEoD0U6hKr1t3knv9zk21pHpGPSd3uSYbh\nRev2ZfZIWua8gHKot9sKPHqDHPdBHjai4xLI4l/70ZZrbhRFjoplfkhjI1QK\nGG14jpgwTL7BRjcr6jCRgKNP0KJdWtriNHeAyabolo+olPypyirsZl/OwXP0\nbcKAxE8VCpWv9PheExY/C7c7tCJ2GN8cBrXb/SFcl61gZpO1uFbgDTSUfocW\nTpi6dtFrcVeca1mEH4yhG5YGktzJq4lM73pVMpqNG+TdpA751NhyQIiq9EVe\nW+1Fju7X0qvNSv0UqQVTJE2eEF/tIvMi97cXg6mTEn6SxGTJwOoRMcDCOmKG\ng04lsq7Y7tIW8KlsnJfghr/M7aCVv35fQWDNUKX1GcrqUfOvNE+9eaQgKGuT\nftnr\r\n=lFnJ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"be3ffe5f36cb07cd43fbcc51521f03fe29f2c9fa","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.9","@balena/jellyfish-logger":"^2.1.35","@balena/jellyfish-metrics":"^1.0.144","@balena/jellyfish-environment":"^4.1.7"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.124","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.20_1619439826572_0.14304451070618773","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.21-renovate-non-major-e3302af9102606f5db7067c2b25b3d06a3daa24e":{"name":"@balena/jellyfish-core","version":"2.13.21-renovate-non-major-e3302af9102606f5db7067c2b25b3d06a3daa24e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.21-renovate-non-major-e3302af9102606f5db7067c2b25b3d06a3daa24e","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d7e3de2164a9952f138cfcbf96deee9645889542","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.21-renovate-non-major-e3302af9102606f5db7067c2b25b3d06a3daa24e.tgz","fileCount":99,"integrity":"sha512-UKvGoeQ3s0JmAFTMRS8X45Vwkh7aa1GehOm5XmgGr7zChZQ+T2lclhar50USGbaP0fom8gtmZZUyMewo4EfbXQ==","signatures":[{"sig":"MEUCIQDiD1c02HZawBEpK5QqKEdHpsvgE5t9Nqt/scdN/kk3hwIgQrxlf5p2GZAyZ37fQW9RX4X/jPwCmf75qeuJKISpT9c=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":711455,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJghxL8CRA9TVsSAnZWagAAAocP/01gkUSd+4L0rA4Z8K7+\nmAf8yAB1XIQqs8SOtbjM8nCvtsCF7HWJBLjnaqzpqYm3+3HYx2SOhwTymMh7\nGSHJyLvFF4RjyXKDc2nPqJ/2w5WzJmf7018XS3KzvcS0CfBAnxxQczjCM0ED\nzJAPSqp7kIM2jyNGwXYX/cQLUadUav4Lm5gh2UI8EoHBSHBkjdKMT9/Swu2a\nwu4CLOY/QDe0sIlzRrQkUMCTEoEvG1Ip+0xIgHMMdW4215ibbsi4B3Pf+WBq\nOx0vGlmxZpWC9/Ji7mSjuuP9iHBcn1ARq0EySd+spr557FJpWkepYiCogTH5\nd+6pRrnQe2jDbU/4VXc5StNPgqC4uCbw3adsznxWe5L39iqmicIiPSbmn5Qi\nI3hrNaYaXYK94YClpU2xMfzWwKA/PXFFOSU5YhSOQm5TBYriKqSBlO0bhSIu\n+bmmV+T5kgaqxRfIpx3wfYqha3tpbNwtBVaIO7Jc8f4l+uZisdX4vET4jBPT\nndO2qDoJWdxUyw/WIrhqNey+NYxUedMkQNTbTUJOhVkqGReoX4IzUYqI4Z4g\nd8w8eommalfuWvE/1ocgNNpE3Bk/8uYR2NubJSjJV+0sq2e5f1mjizsbCypU\n/ddqe8bNKaqfbYw2QlV1t80hrrJ0D2wPGE3QPCuIiBiXpjSkiLGcs9YFiYr1\niD1M\r\n=irA4\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e3302af9102606f5db7067c2b25b3d06a3daa24e","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.9","@balena/jellyfish-logger":"^2.1.35","@balena/jellyfish-metrics":"^1.0.145","@balena/jellyfish-environment":"^4.1.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.2","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.124","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.21-renovate-non-major-e3302af9102606f5db7067c2b25b3d06a3daa24e_1619464955605_0.022877156668282606","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.21":{"name":"@balena/jellyfish-core","version":"2.13.21","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.21","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"16cdbed23652d522c774b185fca4dff44867962f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.21.tgz","fileCount":99,"integrity":"sha512-GtlRstw+isrc5AbjUio7gsEyg4JIeIwjYEo99gs/OvE32HKx1xe/u4PDG7eYQ2Kir/UDuwyab8x8RVh3gK7qmA==","signatures":[{"sig":"MEUCIQChw8zxWI4rpY9Ow3QHywxhsQKkOyE1H5sNtByPoGU2fgIgURJgz/47c541fNj97ko1985dLUx5FaCItvXaZ5fqy+I=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":711395,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJghxRQCRA9TVsSAnZWagAAPXUQAI7CfYgHkL7/rk9DCZ5g\nvhT/CXBy60nnyaWrRTtssp1wpv8wmLLQVfEpoLE9lIMeIlEekq6DD4klHNUq\n8sZdAXZEaxi5F4z7xe7l7K6GPwHrIwCcqdzdceytCbndxuwJb4Dur5+j0hm5\n5xDxUY5MHt8DE5N45+tWvPPIN38HV1EZJfOIzI5VHG2SJX/p1AwO0CR776Fg\nC9bBSBr8zt1WoMT/ETKMuxN99pTlU0pMPDtRuVEIyDNwHkhmrGqVRHzcqFeC\nE2Z9ycWTAQ38K35nKNlPv/E9EL/RlEu0cJQCIz4hVy8WXdnrxiE7ngay+NXS\nVUhHwKeECPZ393r+9wgjrmxmCqIrThbzH+IqMUDtoFNFqit2LQimvQiRRnKy\ncWZNU95TTR66zLMSjlrg6OpD91mWAaiVmxfsnNuQM2oHsWz8PKN7/ouhCahN\nnv2O29qWAQRo44j4+BD+n7dfVgcT80cikbZvtH2e+agw2WIrNAzwcJWeWLtn\n33cdwOS0kNTu8eyofl744AHVNZeJbBSm7bfE/S0nGy3yj592+KrF3L2xcCla\nq7kk9QhAC5qVcU26TR70w0WkGc5jGHxE8xY9nMHkQf1m8Z0HCn3UL/WH4wr4\nHUf3Hp30ofzheodZf+dKmPS+p6kfkvcwx132jPK5oky01v79i/PwBkY1VUdy\nIjfK\r\n=HOkK\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"42f2647dda22be21908759ed57d1b15a5d0b320f","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.9","@balena/jellyfish-logger":"^2.1.35","@balena/jellyfish-metrics":"^1.0.145","@balena/jellyfish-environment":"^4.1.7"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.2","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.124","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.21_1619465295622_0.11624556313132595","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.22-renovate-non-major-6667bc600861793ae33ae53305290f246c79f7a2":{"name":"@balena/jellyfish-core","version":"2.13.22-renovate-non-major-6667bc600861793ae33ae53305290f246c79f7a2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.22-renovate-non-major-6667bc600861793ae33ae53305290f246c79f7a2","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"881eb9aa4291c93b6c0de6a6cd03129c4912ca30","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.22-renovate-non-major-6667bc600861793ae33ae53305290f246c79f7a2.tgz","fileCount":99,"integrity":"sha512-kdBBS1dK/BnDVmq8FB2lnLJ2EeYJZhcllTsO6azmF4BT3IJ4eu5h/tmo/BScO4YYza2y8PE258DxvJ/h7O8b1A==","signatures":[{"sig":"MEQCIEYeGK8GuB1h2WTHVwb2SdnL1MFZB6WZJJxhuAWwW5FMAiBI4eLDPxXCe2sUUB59WADyHeZql+q0DJpBtV222XwcbQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":711878,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJghxUiCRA9TVsSAnZWagAAafoP+gL3vl3rQXsnoGk3P7f+\nYAiihGIXcHgYbCxIgaGyLn6XJzOwEsktwHYTF4MvjbzrZrWeHLPqlwkr7vpD\nC/YX9me2UMape6ubCq7pzQ534TC5fUjFNqFyxNi2kt33R80AztjP8lhJzOfV\ns2uTaXY44zDK/onhBx5QPADnn/2f+Lc5VB/GGwmLfh/0W9Xhs9mMkNC/vSbM\n/4at2Vf5swvez3oCHZEXkdV6TnNSJO2EsDbjrxI9iUSxr25GbfqvDd6Mx/dg\n+kKTQ5WhdUA7k9vYD8ZYAlFjJKFc3qaQOHPgzZLjc688xT0tVRij7Cu1BeBu\nmR2fz30z5u4C526GadwsFW9pjcdz3G5CoaU4z+qX1g9ukh1vSwGDPnj0W+f2\nAb1uQS7U2DfX5VMz75HfzzEE9jHl+Uv1ktQugC3hmE3YT+Cl0/M371CtBP1M\n7XaOM5yw+/JIwwlEQ/Gdc/BpRKrJPCcQoMhIDFpS68jNSaXRsShj1Dv5bWV/\nP1EuDKTdfhsS78aJGjoDAa7/8+5O7TTWiOXPSQqryS5OTGyjphaeSeiYw69b\nUWLGUQL/VW6ejVn1Bsru4Qo6121ObkMUQlTRoAeXpBMPpWXA1K3b65M8jzwa\nBrePO7Yx92f6HxM9E0j7vyqvBMzyQcW7D+J+XwruGhBrcNk/rtKEWviY1T8C\nuYn+\r\n=7Qu8\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6667bc600861793ae33ae53305290f246c79f7a2","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.9","@balena/jellyfish-logger":"^2.1.35","@balena/jellyfish-metrics":"^1.0.145","@balena/jellyfish-environment":"^4.1.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.2","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.125","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.22-renovate-non-major-6667bc600861793ae33ae53305290f246c79f7a2_1619465506163_0.29634190865651067","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.22-renovate-non-major-ca5b18ee7a9c4d3d0c3dcfd18ce728390175cd08":{"name":"@balena/jellyfish-core","version":"2.13.22-renovate-non-major-ca5b18ee7a9c4d3d0c3dcfd18ce728390175cd08","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.22-renovate-non-major-ca5b18ee7a9c4d3d0c3dcfd18ce728390175cd08","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"62be1542bd03e6baa818716cc91433e96a91eb7e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.22-renovate-non-major-ca5b18ee7a9c4d3d0c3dcfd18ce728390175cd08.tgz","fileCount":99,"integrity":"sha512-Iy5c+IzwOvbP68KGAqzW6hLxnVykwXVwaciwGokNqq4kVDi0CWLlDAja/52OJteA9CDE5D7hm7bJ4NNM6+akeQ==","signatures":[{"sig":"MEUCIQD/plyvJRaSONWrdXS0hWM6WiZ32GNvE2aCgKi1HqXZWAIgYW1Z+3LHvOJQZ091YJdhyVA8u7hicyZL8+yMvmC0kzY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":711807,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgh1dnCRA9TVsSAnZWagAAKzEP/1dUB4YyfGX3iZG+kkTz\nCQ+c0i8rl9o6/D3/EHGX86a0HU1EGgdDHu98Z1/kLqxhX1JWt8OutdnYpPlW\ntyCiAqXFhJ6z01gO/Pm4Iobo3PByy8vlf29N8Xl8GrNRQKhpxlBPqfxFFIbV\n5ue+8Pgf3WWOryF7V4u4JILZvZ3108zvEWpdRhhB5KkrdREfvx3FBE9VwNdK\n3id56iFjVdK0R6zfv6nVi++1tQc05noRGbVg2sqQN30FzOUeN1NChLLEG96P\nTBbrPjbG6bk0gS3rGQl4jJfTvXQp2pDvB4R4kuyAIvlJ92QzLHPbVvSGxWNf\nkHPi4765PSIZJn9+Ni8Pl9PVaeM5y3WzAiVlvEO7UKWpJi4pBBUjX4W71U8v\nI9KvOP4lcaC74GUZ0qg3ETcm3tiXo31QjPSgkXMYNx4ScnypOeqwcaju2fxB\n+ggPURc44Ba3fCcDAt3MGWFQ0aEeL4lTiBoTDhsU1nAWBG6001QX6K1jnJLV\nZO5ljoJ4NEmnCCAyvQH6t4w7FPvxeZ78GyjSPCfme4rHj1NaGR9Z1Clzf6iS\ntA6f4NiE7zNcIaFr3Vy0i+1o0G6+GrN8E49oE35AMZEgETZitSCme/xBEFZs\nw5pXRlTbArdY9mr98Lu7yGuhSEUmTvAfOto95yK3ll17MkHPd0B/Cg+zL7Eg\np9F2\r\n=pbkU\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ca5b18ee7a9c4d3d0c3dcfd18ce728390175cd08","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.10","@balena/jellyfish-logger":"^2.1.35","@balena/jellyfish-metrics":"^1.0.146","@balena/jellyfish-environment":"^4.1.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.2","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.125","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.22-renovate-non-major-ca5b18ee7a9c4d3d0c3dcfd18ce728390175cd08_1619482470463_0.2791666156587167","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.22":{"name":"@balena/jellyfish-core","version":"2.13.22","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.22","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f9fbeaa2faa4871e9468ab8fb11a846a4d9b3998","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.22.tgz","fileCount":99,"integrity":"sha512-mCdqXJu80Q1lUVNtmRfHu9fsrV0WYHmgFe44BM9Rpe+MeD2IwpvhM5t5QyRn6OcQJqJl6jEUhZyMWPdVlZIQLg==","signatures":[{"sig":"MEUCIQDtsjw/h1BGI5bXgFx/75aoMeiieMK30reDpUrvDVmsVAIgZDHtUQGwAgv3jtRSaerwcpVnQ7gt5MrYOdP04aQMOIY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":711747,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgh1jSCRA9TVsSAnZWagAAG3kP/3+gXBy+OmNvwx2c87d2\nsMMAED9EsNFyhPF/YzFQw+8PWJSLnOlUzMO45UWq3LnGCjJ7J91CgOx0YiWs\n5meOIkoo8KSYeZB2zWqQ5+H8J5Lw5nK9vajTqRE66lZTcRRozLQ8lIq5uazC\nh4PXnoB7zY2iXCxCmvsk1Bx4NIz0uFru1hqpNk9spzwGFwDMjhExioVu/ljN\naaLctnFJUerGavzkp1L8weTw7bNqCMmKc4LsvlV6k5kqg9ToalQ4NKsdUBvQ\nfwyvtVpg2Jrb5nWRa/gcgx2A7NmknelFlrtrUcnTiS6jIfzLMTqLXy83aq0q\nnHiq3nSd6LwELDRQN8fW9NPFRwGHs1Y0VTf7hLGVB6fi78+e8OYcbKKrNOA2\nsKBfY4O/A/dUxfEh6SEstwKC+cHY2Ca2qZbWZi6Zas4oAxoMX2UDJmDevHB+\nah8hrnME68u/eN57YWIrXRUvMtHztWSCJVGaE/59zlmkUUmg/6X5TNE2YcLr\nvt/1KUCKdk/Rvjw+Dir/FWuyeMf870q+zirvFzkg4D7VaZoariCGECvFllSI\n9ZMI8ylwZWFhgMetrFemk8mfeGtX1s5eTGX3f4Ly9R+UksaWpFw4X2RIEsey\n7hOJiMGU+bl6bv+AjLuDXob3cpMnZSQ4DZdqlrHCHRvJF3vQZQ17hpCMROO4\nIyZF\r\n=oaW6\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a169ad8708162d91c6de1c0aca149957f6436f9e","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.10","@balena/jellyfish-logger":"^2.1.35","@balena/jellyfish-metrics":"^1.0.146","@balena/jellyfish-environment":"^4.1.7"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.2","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.125","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.22_1619482834137_0.7888419570760337","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.23-renovate-non-major-347252a5ddb25b9533d1f6e095b9ca2fe4e55f75":{"name":"@balena/jellyfish-core","version":"2.13.23-renovate-non-major-347252a5ddb25b9533d1f6e095b9ca2fe4e55f75","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.23-renovate-non-major-347252a5ddb25b9533d1f6e095b9ca2fe4e55f75","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5b8df286c8674241e3d1002e28990e4f6832480e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.23-renovate-non-major-347252a5ddb25b9533d1f6e095b9ca2fe4e55f75.tgz","fileCount":99,"integrity":"sha512-1Etmmh+VbneLwCxcWbA76pT+OXL9b6XSVaMmo+PYhVOtsbrWGvTfZQFnlYLwwnGsi2EklENKTx6+wBpOAcMVyg==","signatures":[{"sig":"MEUCIQC8awjivpj2RCbdh7trwiiQiFzOLCzHqPp7KXMj4NfqDgIgNF++r6bIc/qg2ObrcIVMGY3t05qG1V0xqxHro6130sU=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":712158,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgh2yKCRA9TVsSAnZWagAAODoP/RvtVwdtLhVYHyA26hWz\noiYaO5wFrv+5xrjufZoAvJ85PXFjZWUCGSAralOQTPrUZla2umQ1/q/YBXYj\n9f5OVWXi5YpwTDZHSxhTK5CMe+OreivvUom2jW9ybU3lS3NLpU42VWSoUc56\nTwn/wSkyA7egI+liqnqZJwHcjyUUhAI25nZRx+f9+J+IIzSZ19tGdPTkRRxt\nLntYhcRAZxdUAxFnlZLiXVwwZzdK5+bMjtI/KKOitFU1tmnQ+4KaETDsPVJE\nUU/9HunuDtx7RAStye423IL+53Sh/to0CqdfT7XJZH2YKohmnB31Awp/tFQV\nb1kNCK6d7Ibw7TjGwYULVLIqRY7aspqodunvx418lqLnTqhEUAxGAS+rxd8x\nPuiVj17G/C2c9Xfz1Av3Q1OH+sD5FBuwTthKMl8c6xwdM7V4LXIJ41sjCpA0\nHJ5VkOf8VJ8CcQeNxCO+DXtG31y4/yyok0byTzBAaQuSvJXKETcd1hfmmRgx\nJRpy7LZLO4NLEPgWdulocUS1rsuZxSW86ycX6SzMdcvQRZfhrPuTW/N6wmf0\nN/ybCruVR6h8lOnY17zbLVur+cLnCzL2O63UapjYIChEP2k8j2zUL59VpZnA\n+O8amM11LBRh2/8D1urGWvXPxSDaEIoy4ilbx7+32sGOaStyx0jLs1M14N7l\n2oSh\r\n=rkKj\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"347252a5ddb25b9533d1f6e095b9ca2fe4e55f75","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.10","@balena/jellyfish-logger":"^2.1.37","@balena/jellyfish-metrics":"^1.0.147","@balena/jellyfish-environment":"^4.1.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.2","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.125","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.23-renovate-non-major-347252a5ddb25b9533d1f6e095b9ca2fe4e55f75_1619487881825_0.3897033758895969","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.23":{"name":"@balena/jellyfish-core","version":"2.13.23","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.23","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d23e8fb84831d718b9613d2d1092a477cf9b6e4e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.23.tgz","fileCount":99,"integrity":"sha512-dHkROHxIp6WCufgruXRjyId8NwtUD5QcF8IBo3A24CG+4pYT7U67Xn9/GqXPhlWj4Njtft/KkMbMYgr3wR9+Tw==","signatures":[{"sig":"MEQCIGetEC9/8/Cp79jpyUmrMbYyotG9fDkGc9WeFjjwKJDmAiAmFXWyPmtgTOKBd1NmjfbZ/fdaIaOBhKbGXdmQGRHlIg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":712098,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgh22/CRA9TVsSAnZWagAAKH4P/1x9K2DrlxxohZNBRVt8\nNiNnaY4ynWRcOzu2rV89rhw4r3HFGtmTlx+e6Ut5hgH8KLCU7ept7/uPwrMY\njlo3dEioJCyEqkVbjvSCOjDSLkvYxAV/jnMxTsYVXiAVw2BDmL4d4mBM/07f\nb3T+YOOzpIuqpTx/eFvqi92kVWTjzL0ACcoaDfvOoYIZJEc2oAxIVdDCDHbq\nFtNCihRsg2UUgNNlylV+onDCUvjUtRb1KlgyDxg/AqTucQCpGiOipuNhfSwt\njuc/6k4D7+qr0qty+xhYpPE2MCr+sw8U3aQQwj7WDVDGLQcHcl17JH2k0DOY\nqggUsScoojh8bwXHojIIB24gs+zR/3YQWzVc1k1U50csHdMKB3hb+1SdGCxF\nN0EwRfqWf3oCWTDuowZ28pnQboBhjn5s0gI3kSn9PeuA+QRIt0TABUc0pt2m\nzlUTeL4PoSDWJjnmogr2/Lv6Meq+7SGGFOKx33j1+/LqWLtdsNTmF7ZdnMht\n27bvx1Jx5XXHrDkH86t9EetUEvNl6FM64vH8PLiQZCpeYeQr99JYCxETRZAS\nOLJs7qtDMATH8EAcKy783lZWm0Hc70OHZyO6h52iOarFmPo8g+Q0YWp5a8nI\n/nE6cvhpfc0kcL4flWtyqjEkhllAxyv4lmx3Dym0u3o2JqQ1v146HV8q/nsj\nZm8t\r\n=Ic6U\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"06615695bcd2066b86d79d4f6e751fa3bb8f0a83","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.10","@balena/jellyfish-logger":"^2.1.37","@balena/jellyfish-metrics":"^1.0.147","@balena/jellyfish-environment":"^4.1.7"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.2","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.125","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.23_1619488190983_0.4565206897258771","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.24-renovate-non-major-c156750c7fcdf7fcbbf354754ce2fa9dc129d275":{"name":"@balena/jellyfish-core","version":"2.13.24-renovate-non-major-c156750c7fcdf7fcbbf354754ce2fa9dc129d275","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.24-renovate-non-major-c156750c7fcdf7fcbbf354754ce2fa9dc129d275","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"bc1fbdc49b6f5bd0d7060d8a65796c37cbc9bf89","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.24-renovate-non-major-c156750c7fcdf7fcbbf354754ce2fa9dc129d275.tgz","fileCount":99,"integrity":"sha512-ytDIDIjLB4ZMiwIB9d1tZy52seKuAqJdYTg+H2pZ71oLrhkaJYatrGrRHyhPoCRtXqkMfNSeERHK0onXTKbdcA==","signatures":[{"sig":"MEQCIBcX/No7/XwLeZ2ZwyUTveOkU+6tgmr3h2L2G/yR/qxmAiBR5WS7GvH5e5o8L3F2IJmyjY7q/FSVPiknp3yFvZYHAA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":712587,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgh285CRA9TVsSAnZWagAAvNEP/22G9tdwGUaksg/yL/Qq\ntgcYtQcVuUUvPDkx30dnOmp46ObJ4th6FNnWiXVsrufdN7xZJhK8hegd08p4\ntgjeiYspXrYyWq8n1Tp/ie0s2iWJTBLe5S60bWn6D9LdG686FE8HZScVZcxI\n4Jg6KVIOFMHet8qc71xDE2AIRC6gmLCJynj3pPLM/xoCpIeHtc0ved/B/GKt\nSWzFLKXKdyV/ps24wCaNnQurqkN0ZeOgnfMfAkLQtUallWU70JMtzBFOvx6U\nnDafAx3AHl/vJCbCZszlGggckSElcu3R8+fLNoC5j2IeOvgm7OO72gGIy4sV\ntw58xnYYCB5cpvFwddOnsDBE3K4YBJ4A/u/cmRxS+ar5Z53ZrVHvIHkCbeL/\nOLYCw/BasiPPG5MSH8z+oE4byE9pDpk6NjpfxP6/RX4btvxKatYnNCd/EZKJ\n6DYUCk0QfFoGIk2Jdw0pfiWrtpjevVzfRu2zBbMeiT3ijnJsx+czJE+6eboP\nFELxsrWA4Hh6kLUKpqiA4GlmTznRZ+OyZ9AqdfH+kQSlAx/m5CA3KGL8ORNC\nLkvLmnQQffg14p0rMdxp/A3K1apg7xiQq5xESCmp0aVRMi/9ZQFeOJfKxS4K\nrVt9AOgu3JTKutNYI5kwvbsyKtum5hcKs8Kifg8/v1AbOIy8aRYj6A38EkJQ\nSCGA\r\n=t1LH\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c156750c7fcdf7fcbbf354754ce2fa9dc129d275","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.10","@balena/jellyfish-logger":"^2.1.37","@balena/jellyfish-metrics":"^1.0.148","@balena/jellyfish-environment":"^4.1.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.2","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.125","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.24-renovate-non-major-c156750c7fcdf7fcbbf354754ce2fa9dc129d275_1619488569135_0.14230419624367707","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.24":{"name":"@balena/jellyfish-core","version":"2.13.24","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.24","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a1227f479704e3721ca78b28b6015c96639ee4b3","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.24.tgz","fileCount":99,"integrity":"sha512-d6dw8RktfjaI+ZdIFwAFlFhjDiThwYPAxIGsW03UUS6WKgYkxsjqdtelMiKK1UjTVk6rk0bOjkAzgG7wO3Jegg==","signatures":[{"sig":"MEQCIFOMJyvPxZne94LaP0RJACm++6UMwCBof5WT4lwl2b2FAiB+tp6NW4/AoxSK7RULuYbshXhRzXN9VJEKpy42rmjpBg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":712527,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgh3CbCRA9TVsSAnZWagAAHXwP/iWaKwfqPS4V5eVRBvm5\nv25d3fF7B5Dmj5XyRJBtjXikSJk6ANRe5u+0wiqBH+z0ltyBNYvqpGlfpXVw\n/F0zFTQDhUuO8KQQ6zPj2t+c0SalEa2bNUL0zsqZmmjF/ojWjoYkdtFEQs1D\nspGM8mS0GeC1GPwHbxT6jxqs6u231eiOid5Gn3XQOJJDd8qvw/SDyq/3CQE3\nh8GYalrRt6MsZP3cQzoop9F34FmTzErZs6jYhf4BT92IzBFYuhuQAfXETwoA\n2NII/8HjUYSWm4HxPfE24vDdhP8bVkI3ancOnsWtnblhvRmI4K4HbriMFVuO\nsphu2mkan6I/+n0vPrK1/ngOI1bYpLjvHPVSqk9f2H3N774FTxw3yVaCzq8I\n1Yb6WBuTkjV/jAjUn6lFhxiiMQ5WrP46D/7kRnByd9F1vSuB26if8/xOwhnP\n+Ih9fYQ4VY7jLa11O+da9bQlsNF/7FeE7M4TY4DwQU5r3UWaj1sg7fCeMPej\npkDw9dj02fsI+S5pqsjCW80vwKYMlBGMNf8wE/8Bk/ZATa6CaJpPTp87uleg\nvg8vjGSTMIOUHmT4Qmhm4n/KBcig3JT21TwTivq229KgmciW9qAfq2dbczr/\nu3IX3MrsG5IQINVaxJOhh3zHRZ9izHeBYVDQ3f93E4bWUJ01WKOeOaOYYro/\nX41h\r\n=5Ho+\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0e6a7641c4179a37b187f2aef8dab7475b354520","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.10","@balena/jellyfish-logger":"^2.1.37","@balena/jellyfish-metrics":"^1.0.148","@balena/jellyfish-environment":"^4.1.7"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.2","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.125","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.24_1619488923057_0.5903471006445666","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.25-renovate-non-major-0cf8b8fb5e3a51127482a2950b83fdae1c7a74c5":{"name":"@balena/jellyfish-core","version":"2.13.25-renovate-non-major-0cf8b8fb5e3a51127482a2950b83fdae1c7a74c5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.25-renovate-non-major-0cf8b8fb5e3a51127482a2950b83fdae1c7a74c5","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"78849cfc7cb4ef17d610886f170081bdcb525d3e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.25-renovate-non-major-0cf8b8fb5e3a51127482a2950b83fdae1c7a74c5.tgz","fileCount":99,"integrity":"sha512-VhQcnkUHtfuJN5dtUIKJmzd6fp6n6hPxTywwpuW9Lavn1i/CqlkzcWNFkrlKusf8zdb/H02Fsa1T4TmBPnwj0g==","signatures":[{"sig":"MEQCIHTNjNcl/qVuLVvlRJbyuhnK5gIaLL/frmWFS9iS66sQAiBzf8osFFtYrOSkjk1jwzXRRXkiWrGMfb32AI3qF+g/NQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":713016,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgh4V0CRA9TVsSAnZWagAAkr4P/3hc7J6shB3AY4bhg8it\n2APTO0+IzpKP9YHOqj+PbC66MKF7sFnt4oGEzZvjaX+ohan/R37lIY6Yh5Ce\nL+rLgWHjT2G8lsbr+leFUjs5AptPqaNIxLipQyYI9TSBDqFCEyKG5cxn+fSH\nipPczVo+6zp7oqd8OckoKz1t0Dd5Vu9cVYNt0cRNRyCp7z0wD+ANP3tqqRdI\nGGBdMRABapiHqT9o2a9v8p91cviUn1QSzIpdTRfU5wEJhzHjdgEKLpaxgwaU\ntntzjikSuqS3tVJHiPGCyF94jC1Uiua6Ix+ytP3gyzWU8HeFJKDFjkA6Aqfh\nPMTaatlCLDyRNOL+1GQ2MFhj6IvG+jnx4Wi68Pm3eDW1SMijlNCCxkg7eX2C\not0OL57vk+ebKqfPZ3oIQjrRQS7C5iIYNi/S/u61yUPDdKPCeYqUe2PfXjJO\niCNyZh7UjwLPUUKnX+gNlD0nZm3K9T5ISF8fFlRnp3Sh1xM+9w4gVoNYI1t2\n2dffWEZfSNeP9yhYC1wQ/16F9dMolE1aq3iWcTjpnHipcpF3IvcoFrRxWsId\npSBBVKO1l5kkb1t1UKNrVxc7oHpo0sfrijTzedJFZkDJjKsCMckkYYLhx6/M\n21CnRman89ZC3kulLMahPEMOWU7KzdPBgh+1qvr7ZySQ1s6Plu0PwCvX1sNP\nMjt1\r\n=MFfE\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0cf8b8fb5e3a51127482a2950b83fdae1c7a74c5","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.10","@balena/jellyfish-logger":"^2.1.37","@balena/jellyfish-metrics":"^1.0.149","@balena/jellyfish-environment":"^4.1.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.2","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.125","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.25-renovate-non-major-0cf8b8fb5e3a51127482a2950b83fdae1c7a74c5_1619494259572_0.009040281161947439","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.25":{"name":"@balena/jellyfish-core","version":"2.13.25","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.25","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f7044ea014f939438e088863fd818e7726e94a35","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.25.tgz","fileCount":99,"integrity":"sha512-FLCeNMHORKJMMELNHuEHxcS/MAr8PTrU7N+eCcl5qgZKqf7uc4+XhIhkpma1TXpfddAP3jlF4WIdZOdWLxBeLw==","signatures":[{"sig":"MEQCIA74Qoshcz3DE3uD5MwyvuyTROZdUknqZUPJlwJ3iAgkAiBGxSfyceVTG+24HNbHVqfSX+Qt1ezrqFp9nJT+9xJEUg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":712956,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgh4bbCRA9TVsSAnZWagAAyVcP+wQf99iarMildwXwH/uE\nEKDtRQZfxnFTTZib8mEVRCcoqfhS8avWCXPw1rzx5o7Iq8n22f9rrQO5ffeT\n7SK3RyFF89cy19NdtHX78V24OPYBgvJN7Sj0GWzm6InyHWGe5TKyypaYtZwe\ndYOc4J5CLfEx4H7cIGFDyCrby9C+hsldBO+IcAAvosaWINvJO42OISoXbGvp\nmJ6/O+CyqWNQRrf8FQ5rAVwSxdsJT+KS94ZA7ou6J5L2rEK3FDu2bgXpbIu0\ngKfaKHdZMeNIV/kHcvxhMs4JljRWXsnhT7mJzNFP3atll0qbXgugS6uXSIPg\n7uj11cHYTGEpLdCkMQa0zE+clXaIbWsltXR74HGhWkUHN0YgyyXKEuhSmqCK\n7CexiTvCkU2PtW5oFgl50U1JYPKDWvCfCrJLhSet2mt78h9IcvdYeVMKdBaK\nqnp5zEP0WBJLAxoHigYSAXVzoTuy2JVbh+8r21LVgYHLj8gjxD693OUsVGjy\nobvghId+HO6WDuwdKj3TP/GilgeC6cdUfi62v6Lh84EpYx8+opggS0naSlXM\nj5H9v926/cwhliEbC+nmDvr5oxurxV7vkOs1YcjffI9fsf0gYX4yAD24/kKU\n7eijbg52Nph4A06t9IqI/w1YSCHcEL5FaQWPfCoNAhKKJXeB52TqQ+FZcbWn\nmdDM\r\n=WC9h\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c72bdb04f4eb8c8cd3d9d98e308a04aeb10b2eda","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.10","@balena/jellyfish-logger":"^2.1.37","@balena/jellyfish-metrics":"^1.0.149","@balena/jellyfish-environment":"^4.1.7"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.2","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.125","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.25_1619494618081_0.13541994712119032","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.26-renovate-docker-non-major-53d067c4c5e4a6b8f2de35de7a684e177ba970f9":{"name":"@balena/jellyfish-core","version":"2.13.26-renovate-docker-non-major-53d067c4c5e4a6b8f2de35de7a684e177ba970f9","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.26-renovate-docker-non-major-53d067c4c5e4a6b8f2de35de7a684e177ba970f9","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"57c7b9e63657e5c9d4a79a9632303f58ebf2a5c7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.26-renovate-docker-non-major-53d067c4c5e4a6b8f2de35de7a684e177ba970f9.tgz","fileCount":99,"integrity":"sha512-CtQC4sGfYsnYFqdv6cH5gJcl3M6hAYBw19Uv5d6r9gz1vwZ1TfQgEg1VI5J9HLQXfvltmb6MFy1T+zW7sq0ueA==","signatures":[{"sig":"MEUCIQDizcuKB4qvS4dOm+HRhekxWX8mSNILb/7O18s36BEo7QIgSCwGI+I6+QvI4k0WewBcfB9gUMOsnaPxD5UL94Rhxr4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":713444,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgh5qjCRA9TVsSAnZWagAASJAP/0sj9npkhtkQe34GHFCJ\nZZrpIXl1wThoDHn8EXhpWgoYLU9lchNchtQ8FcHWX05BTmCS0E4aK17U+KXk\nkkK/aROOyzHzMtgriP6PjOd6b32LeD3F41hU59STBNeg2DHYP7TMASniFrcB\nJ+rdF167DAOdgK88IFJnI0Gq/5kl/sWoJgBYezcUnPU9dLSwNpmOlmKOuCuS\n7LpF9N/O27fI5oQIQmlZtwvGI0bVOvASRa1IDUphyC6ybca6VMBO89mc6Hqf\nZMnNtIwYt19pO5B53z87WQs+lT2nRu0Mjkyho9+8GCI0M2HP8TBNZI9Lpyll\nJtM1e/RLSCGqV/nW+MJ6GcZPzOu5+2eXh0/Oflk5SzCdXqDEBR2YRVw/uTbK\nhDWz3sz69DDDFjobqyV8PsxOURBA/m3L3jEYuCVDQlBpn++50QCsThRMk50C\nI7Sxbmapl7ETe7ZJTHtfOdXExk/R6XwAkFHdkCu/JTHgXnHak9P6EUBHSYMx\nkl1Fg/oAPfWSMFSaldGi3LNp556ML8rPnQ31QGh2foGbLjc+BJjgqY6o739W\n3q/S7JYpGd+sILzJI8XNWYG8EdvlD/EOmIu58M849C3OEM/OAPqbViom2oqE\nefYjUHj2tuwOtUuko8gLb/YcsIvMMlYo8yyt+qqFfAWY7KjVpmYnDkwtX4fP\nNkzd\r\n=5Afu\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"53d067c4c5e4a6b8f2de35de7a684e177ba970f9","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.10","@balena/jellyfish-logger":"^2.1.37","@balena/jellyfish-metrics":"^1.0.149","@balena/jellyfish-environment":"^4.1.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.2","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.125","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.26-renovate-docker-non-major-53d067c4c5e4a6b8f2de35de7a684e177ba970f9_1619499682746_0.5942378731228886","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.26":{"name":"@balena/jellyfish-core","version":"2.13.26","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.26","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"fa9d29d6fa4141f1c2fd66691b7a590d73a2c46d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.26.tgz","fileCount":99,"integrity":"sha512-7etQaenn6wXJPsh+H0fJE0IQrsi0oRoXlFOR4OJZSpAxADOWvknDyalipFo4jZEFEHPbGRtfyWQzF9BKX+jzAQ==","signatures":[{"sig":"MEUCIQDo8+f8F5sRSrnd8wohu8cBdH6HJDOZkWVcZC88HkdDNwIgZ+plgHKGk/XhdaW5YlHmzqIMHPD/T9ugs3bAvwQctaw=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":713377,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgh5wbCRA9TVsSAnZWagAA20cP/1+XjlUUOFBxlljx3lmo\nBAI+4iZRydYOkPbi8wlWI9ztceee889rn0oE0WkVamtcodGGw9mZNALPUzmt\n42B7mtt4MdjmiXlkZCLpjulr+lYw6xp/WRqbBdJJZPh/uoRh8oCLWxC5jcYO\nAj2I/LCS+uOsaxcr3DE8Hpm+uqXs4jwLSakvh3cuCtDG7TdMJD2gF2p0gEQu\nCLINtBOhBFAEQ0ciDRcguy5/e6N+L+oOGaXjSy+mkmU2b9li58HPazr2Nagg\nnxU3Sq4KvMPKVpXO9VEmTVHd0OfdShLVSuk5JsV29cihZulU1jt2NC9h9ijL\nYz4fqcXOIkKmb1jKuVW0IlPINx0H/AOH0Yikee9yL5NfR5A4S8ecDYjrzD8B\n+2EprE9wTXqNMp1YLDQXhSnH/I//uXH6biiNFssaX+2akZUBtaurKhKhEiTS\nhYGCq8pUJukltraLYL0RZpaCJ7Hq+Tf1DBk2fPnjOtEdH7JzepsayYEESExq\n6rYzvbP2XNLX0pO+NuO9S4h4ksTry3M1JGMlPi7Mfqm0jNGmVm/C9bJtbZ8Y\nkCMoYhsse6Qvnl9GZWGJhIR1+uzTULOhLmsDunl50a9cb0n1gfDl3nMeIaPv\nOdJIg2fxkck1VhWWpq+OxskKsX3wO8SjfktI+lrxH7lak9pDyED6j3BNqeDN\nMR9w\r\n=DNLV\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6a31c49d66c8de390576bdd468c44f7b4cce1791","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.3","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.10","@balena/jellyfish-logger":"^2.1.37","@balena/jellyfish-metrics":"^1.0.149","@balena/jellyfish-environment":"^4.1.7"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.2","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.125","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.26_1619500058981_0.7648717571435912","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.27-renovate-non-major-57537d7360fb4aa6c67e720b19f07b6d21671f86":{"name":"@balena/jellyfish-core","version":"2.13.27-renovate-non-major-57537d7360fb4aa6c67e720b19f07b6d21671f86","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.27-renovate-non-major-57537d7360fb4aa6c67e720b19f07b6d21671f86","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"59f4552722df13a45b303394a01eb2c0d4fecd1e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.27-renovate-non-major-57537d7360fb4aa6c67e720b19f07b6d21671f86.tgz","fileCount":99,"integrity":"sha512-H4shCDB5SSGH4G2UYpbQreIfrrVCpeI1Mst/BE4HtAOqGR5c5Q1jCW01Ryue9Ou0fG8fqD8Nh1LuL0pO2bbZiA==","signatures":[{"sig":"MEUCIHEM8X7+uFxFKrLLREMByq4aZpQxMOevrL1mWcQBpArKAiEAsTCUJfjCc/zDY3HU5nzAvR3Gps2F+59Yp5cYiBWII0Q=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":713788,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgiDkWCRA9TVsSAnZWagAAcmAQAKNo0FlX6zLREQIWIJqN\n/gStQTdhc2xdRUkgr6d/UOs5gSSU8t7yIBcuGE8JPLJsmAEqO3MSU7P7cG46\nYhNMphzOwEuCp0878fODK37OinYH2FSYzwJEm+cWeaPUwfa4abEOvUDM9Zam\nMF+HMUgQvBYGJdynUJpsXcfh721tiZrAtAB0I+FBKCl7zGyweM9ytarZhdmS\nVNqkDqOSoKZRWrUy5gSe1J86jYPazQrizHCHUqZm3apsHHB5oWgPZbXUIYz+\nxSemmH3WUOgmx4w9EkG5I37hV/4aqBzPQWdc/EagSNDS5neiTl2ESm3xOrZ4\n1e3HRP4H+ZhEy0bMz00vjAeT3sVeFJGKe9bTyN1VTQx/BWbCpVhfJAIRrg9z\nmW0/1wQCu28YIfJfkQUM03I5KYfCsUZJ+0HhBqLleY9L9HpEC825Vtb7KidR\nEMrviVf3Ge7txbLRTk23Pk5BjGjcumEa7DTFgQfqSoaH8XMySN617grQQSBk\nPGcUbG289CXftYFNmLCgaV2J6prvwPPWzY7ccB1V/7QRDokqIl2k/ayypPwW\nsOHZgbA9YK+pnlhwe0zUeNyUBhVfhACc5oXnNOvKKRKMuA75iwIOTLpslo8e\nNdDpka4ASRAAx840f1KzLJPncWonGDk4bTKtFc6hcxsHBtnY5Ih3n+9u0SfT\nEk4v\r\n=/7a+\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"57537d7360fb4aa6c67e720b19f07b6d21671f86","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.10","@balena/jellyfish-logger":"^2.1.37","@balena/jellyfish-metrics":"^1.0.150","@balena/jellyfish-environment":"^4.1.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.2","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.125","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.27-renovate-non-major-57537d7360fb4aa6c67e720b19f07b6d21671f86_1619540245897_0.9433069018082658","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.27":{"name":"@balena/jellyfish-core","version":"2.13.27","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.27","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2592796765521fbd3756023d5a5b531f002b1c12","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.27.tgz","fileCount":99,"integrity":"sha512-2FEXyAGb8JFcREXLx22I56+A/2lUDgDAzjy0usy52613Vn7GHZBoApi7odxZ0p5qLsLuEoViTsNyxa6sQ+2WVw==","signatures":[{"sig":"MEUCIEDHt3jZTPXTAbk/WJzhDtXLhFUDuBhODAt4OHxAE3hxAiEA6fk16Jm61p8Y1fHuMy/hZELwzULpzRHaUfhvlbkOHP8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":713728,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgiDp+CRA9TVsSAnZWagAAYu0QAIAiQlJLY0AFWcfxHMso\nQtMNlslpS32GrhYzfHNvsbQ2MqC2cLfPDoUUSHCLgGzfwgPw/NRWejr6FPnk\nRS5T4QTNQ4hkKH9ja84X4kxMgKUzr701+jcAzI1Wmdkecocn+PZJMNjyORuF\nWdy0imuCNJyFamagTVZ/ghKB7zlEOH5y/5UBe6jaH2WsofQqmIjMt/Xlw/L5\n0E3+7tmHcyg0W5jkiemPmi8kzMqSQi8vn5Qsn6X10LLQa2FSY/G7MSHHXHd5\nl4ks8yaJyCxplj8qC12opfYw1P5G/6sT1WxNsXSPwn386C1S+u1KBdUtcl5S\ntQVljKnWXn2ALNkMYGl4lfoQUeQSznAB6ooxewPVPEI5wwZetYUA0aOy7Ef/\ntWc1PHbIZdyNCneaRBIlwH2sc/OZ/7y7E5E7BY4UnnaXhoolLyMHxSrqJ9ii\n0RwhKzgaEMgGbDBgfdO4UqRvdkQ0Mgm84HdI8mljeX7ET7Iytt0vY3Sf3Blo\nWckclCb7Ddowii8yIXqzibzoC/aF/WBouo4XDJHe+znePcWCWdhG1qyqgP8D\nLEvRx29HHmQYKgYaQJkQXwovQdeNG2nJDlIBd/m7OJZq1Urwv6nGQ1vDiPXW\noYpld9cTFooNmj8HViaQnx7eWhvJSBaDVryY7HnZSMt8mgd1iXr3aFMA1C7e\nkhkc\r\n=4l7C\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6fbae28647159ceb2e2534f762d65e32e6a8131b","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.10","@balena/jellyfish-logger":"^2.1.37","@balena/jellyfish-metrics":"^1.0.150","@balena/jellyfish-environment":"^4.1.7"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.2","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.125","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.27_1619540606353_0.46907208024357905","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.28-renovate-non-major-e4cb9b7b2735559d3043b50ef10319c302bc6f73":{"name":"@balena/jellyfish-core","version":"2.13.28-renovate-non-major-e4cb9b7b2735559d3043b50ef10319c302bc6f73","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.28-renovate-non-major-e4cb9b7b2735559d3043b50ef10319c302bc6f73","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0937edd02c743934dd61d3512d69cf068e0a8caa","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.28-renovate-non-major-e4cb9b7b2735559d3043b50ef10319c302bc6f73.tgz","fileCount":99,"integrity":"sha512-AiOqQxUpwgyos6SQEmq77dGr5NG4sD6KNNg9WjGiYa0KdCAmUsOkirqJlh+5wzC+MTSchGGykvKRl0/ygJxQdQ==","signatures":[{"sig":"MEUCIQCV1ZhIum4oAoE3FBeRkQWYg7oAm4/NInlm7bsmAJE+XgIge8GmDIVd9Dzn0jP+mKuz06naXipMu7gZ7IkM9xiZf84=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":714139,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgiL7cCRA9TVsSAnZWagAAdAIP/11rt2eiEhft/MIL4djf\nAUQtvI0Chg+E6bzMRunhGu4WcOQnokl0y6DDip7urrHTzp319AbXJJUq2m3J\nFLDt0eyb8ymsrNfTGYyxv4wew6dUp2QwyZB85qacFa/lNxAmAppG5UiiRmcT\neG/Pq84n+jg3AQBknPfdkk+bC3zg1pwOuY8R+MYF4TmOrsyAyYv+8mCmzPXB\nvAC3WvnnIV+RNHr5QlPhFK0SFIIq3FBiyq0djkZgcGUjH3OHFjg/og31KdOg\n2Xy+RbjBEiBDiy9norX4mDsB8+3aRV95uGTslS/jWuoA7I9/FMijoJYRTQ/A\nKtujXtGrymBGq3TEBfW8m5CVuihjIay3+XnvbWWzHFxUzWceae9caSGVMxtv\nBZKX+sn0bswOXXACLD/QDrvkjD/6JB1BLuFlYkQ/0/kNqVcAzE+E15qkxWGB\n4qbPtuSXCrSgrsAhFqrUFr8g0h4BKg2td6mZv6MVwCIBwQrHUpkec1zHRnsT\nEis/OdubbVkYN0OxnRDtworG+aBtG6btNCEmTfLau7XNYa4ZPMX13YZsTFIM\nyBpevQIjXqKTWqeqv6I5qgzeGTTDM8eF+F5dILSdcb+2S3ZNpiWZbGmnYZB/\nsN6Xbi5b2+t7q7HiXV/oouMsXFSyctBzU7CIMgC07i0I0EUs74HEUluGfaaa\neOWs\r\n=iwNM\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e4cb9b7b2735559d3043b50ef10319c302bc6f73","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.10","@balena/jellyfish-logger":"^2.1.37","@balena/jellyfish-metrics":"^1.0.150","@balena/jellyfish-environment":"^4.1.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.126","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.28-renovate-non-major-e4cb9b7b2735559d3043b50ef10319c302bc6f73_1619574491590_0.9766068538906778","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.28":{"name":"@balena/jellyfish-core","version":"2.13.28","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.28","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c17e028c716636ad07a6c31087f60304a8cd636d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.28.tgz","fileCount":99,"integrity":"sha512-wdu1pxq+Wt1gBPMZeDX4+NAn1hN17pxffSezf978kUcxWpBrGRO/jk9Zh3PlGSPygbo3qh4FsTwC+3JkqF8BoA==","signatures":[{"sig":"MEYCIQDx6EdWMMgHluvdehi6ZyDzmaqbmvo4/DUQsWfkjzSxowIhAKvoll947ko3MYcq+UJUGEl+3LW3BlAERpoKK/RByBZz","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":714079,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgiMAyCRA9TVsSAnZWagAAaIoP/3CMfGw/OHEG+Ljh8rG/\nPz4FWpxxBaj7KVyNSPkDwBNcG886wM9AOnfXeCvtuoPPAEHT4704rMuwAQUi\nz0XSDhpsDUejl8QmKK3Ziwz/Vd6aOg4RgCG4JCmeZztaDD51vPE9ANsFWwDO\nGm5NryyWCCRCI3/YDiaTnYmiypHfUPMBBO318BqK59HgwMRdzpYM+/kJ9WI8\nPxSW0SsWXQV3Kc8WpcHlw/LPxOe6nc5XclQJIAdzq0TIJ4uj8ImUHZXqjCef\n/vLaTUtHz+wD2aIrQYNy1MK8LawwsnQMyop3qqJuotCc1j6+NMfkk3zuOQEF\nG3jrA0Kpw8b5UtBuI+TMgYFSzDTKThLc6B3Pty75K3W8gk2CfNn5u2icWtX2\nmLesK1JLRmSsdYmxiIMdWz2qlbXA0yZZnSkMIxtrwFoLlWqYSgJp8vqzatDQ\noLwJbiSCSw1UbW5bgnToV+QlmLJk5rPta+rgfSprYBXzLUpHHIHL4EgVa8i0\nYGFGdDKVvf/hEym/jfwz/4SLMxJvGJXwoXV1YehSDKk3WdF3OE7Xnw7irtLK\n1dxhldrqLeRM3KRRbEI1pwJFr1guW/ikCFdE1lRMr7RsU3LeK2QUh/XC2YMa\nSsBHvGh74Ux5DYtPv/xtOeJpXe/lUReWLJJZOAfZvN7av7lmcvox22SsETcB\nGHPh\r\n=Qi8M\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f0fb8a0d459ce3ed3593748e6e45d27f470a30e1","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.10","@balena/jellyfish-logger":"^2.1.37","@balena/jellyfish-metrics":"^1.0.150","@balena/jellyfish-environment":"^4.1.7"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.126","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.28_1619574834235_0.4380253061441608","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.29-renovate-non-major-07b4e72071a4d55dbf3af9b964b590362d54d5ce":{"name":"@balena/jellyfish-core","version":"2.13.29-renovate-non-major-07b4e72071a4d55dbf3af9b964b590362d54d5ce","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.29-renovate-non-major-07b4e72071a4d55dbf3af9b964b590362d54d5ce","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4a3b07722b3231936bca070ab54eb92fac3bcea1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.29-renovate-non-major-07b4e72071a4d55dbf3af9b964b590362d54d5ce.tgz","fileCount":99,"integrity":"sha512-a1U1prOPTTpNxiYVGX7gXwIp4hIBvS9bLDF00+0UEc5wHzcBSERC9MEDQuARlgomvCYaO7GwqQP0i4eSw9jYOA==","signatures":[{"sig":"MEUCIHKIVWWnY4P/zbd06sdbMFFW5KGC1kB2vpHycABAc8IIAiEAoZXC3/FZC3CMz2Yt8ts9HGehzLPA65KRvATz2lrqbjk=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":714490,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgiV1vCRA9TVsSAnZWagAAaiMQAJAchhIoWSYGgO1KGqsx\nGRZOV83jK6MPZze96hQD8y7B1P+UmtMIs/EfqCSYDgBx2M2NIs9iF8WRBOkM\n2QuTV18FFwC8KpKWoWPdmdjbA5szYODyV8kMc8ee6yGQX+p2tYg/cKbvRk6/\nWq4d5HmkkeMWIjs6oUYpA1wVC82C40SfIYkBCdUbG2TKiok2PGTA8MSXn4bf\nr1T9BPcDnKFXFOXMI7FtQbP3TGqqNcDLm+Pkq+oxlMhiJWZsWglulF6OQAyk\nMHnsWTLVjym9WeqHbgVh/+ytzYedSSlj776CtFKJinKWTxYB3SdHoWACxI/g\nNd2cLAvl/v+srdHqT877o35qa0vdX44CiiIDcQuEcwYCLCVnd/s07GZAezbj\nn/MLjBVJyfCBoOm1jfMbPuOalkNw4b63cbrJmXpo5rN30OmwgCG0lkfJNPIi\nVHlwawHYBrMGg/2AKOOfgpx+CgJna2ecxO4UNobiVDHyww8yNHF/+ondNy9E\nZk2nsGi9x7EfNFviKR591USjhtejdwvvjDv+Z+Z3LtXcs1NaJqRfjDL95BfD\nVi0ucw4Gu4k91a8Qpkrp1DeglAxa57C8mqG0SK6d18GQ4pPv7QqykfBAZRne\nXdvawxxYsywUHIy8rZ9r3zXbYOWakD/wo6lBSgCgCWLLnGeG3klbTEzpnyNd\nwgDe\r\n=+Bf0\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"07b4e72071a4d55dbf3af9b964b590362d54d5ce","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.10","@balena/jellyfish-logger":"^2.1.38","@balena/jellyfish-metrics":"^1.0.151","@balena/jellyfish-environment":"^4.1.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.126","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.29-renovate-non-major-07b4e72071a4d55dbf3af9b964b590362d54d5ce_1619615087107_0.8388639166294363","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.29":{"name":"@balena/jellyfish-core","version":"2.13.29","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.29","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3c2921bc2fd1686a9d790e70da8c80c931720e73","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.29.tgz","fileCount":99,"integrity":"sha512-ZBXadVfNWY4IEWh23EF9SmIqv6wXYXXcPsy57Gf+T5ySO1Xsrog6D3vYctEhr4YQ0giUcFU7UPaFPL2LT3Z/wA==","signatures":[{"sig":"MEUCIQDIAdtPlgR2Vy0hxirGy9Rj8Wj2Hh9FXpFwZokmv5cgSQIgOq9Z1/2THV02KMZDpU/T0Fr+4SpUzNt0WpcG2kQAa0Y=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":714430,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgiV5oCRA9TVsSAnZWagAAs/cP/1H46/KyXHy0dBRWs9Ib\nvJRRVda7f6KER9c9FPNmHfP6K7IyRZh6jQyFMjAC1YCiuXC0PAG1rMWCWfet\nBQOYwxDTbpJAsmCWjnngNdmzH7TmCB4vy0ImcYn9G07C2aTMpZP+dXTkctgb\nNDigeFyVmcennvMoDB5obd6w49HHzb3M3ml7xcJ+ZRR3T4wXKD36QZ11dAKv\n6r0+GWsMg+pXUxTBoRKDLswl+cjinZ312gZXQ20BNaV5uyX2zdhhIJPvi1TR\nwXOtpzazOlHZPmCP9XGd3W/lAOSTbbQDcE0NJnS3G46dib2vJ9Q+h+Z9D6XA\nkMrFqVssBHyyFMcE9Z1YODbtZ+kiTsryU019/U5F5+2Go0lfM8pl/qiOdS7A\nLc830ZgcXdCrsp77s8hCCjZUhlCb2sXFUAA525yLUMNDe03eUx/RTbWkzPeT\nJuVIsmvnOrbDhQSHTNo6kcic735SXI9xQbo652slfQOANdWIW3Uoifs8p50t\ngYj94tGzgzgKM+Ws128/KzcQaK2bJ+Q2B6e+pwDBc8eByLWQ5Jby2cWea05y\nYYNpNnT5BFk62TXda/s8H+UIWbb8DETQNjyEdSpFmVauBQboRpWp7w6gC/F2\nTIHmq1Vx7mVzvUDoP+SsKwCChDwr5Py+lWstcTccc2kn7QPSpOu5tt7GLJBq\nE4RA\r\n=9bQe\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c0ab9f0a491c22f080e20908d7e80abf3409cc6f","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.10","@balena/jellyfish-logger":"^2.1.38","@balena/jellyfish-metrics":"^1.0.151","@balena/jellyfish-environment":"^4.1.7"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.126","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.29_1619615335580_0.8117567350121897","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.30-add-user-email-title-c6e946bdede30ca77e1ba411fdfdd6890dbbc337":{"name":"@balena/jellyfish-core","version":"2.13.30-add-user-email-title-c6e946bdede30ca77e1ba411fdfdd6890dbbc337","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.30-add-user-email-title-c6e946bdede30ca77e1ba411fdfdd6890dbbc337","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b00c82f0fca143e9dcba7889d6a2399edca35f98","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.30-add-user-email-title-c6e946bdede30ca77e1ba411fdfdd6890dbbc337.tgz","fileCount":99,"integrity":"sha512-yvfejBNVvXn2MJnJ2lqwMH6nDmjhtZ5W9RLlvhaXA9GueQKWTCryd3hl9gP3eR+VqU9fBSM3hcReWdOTNEPZkA==","signatures":[{"sig":"MEYCIQClfWTlYd9smm9cevURpEEkeaJyy52jX3okdfVnx7oICAIhAIpoILp26UkFH5ONBA27LDmlfFhmUWc/uSq5PFZkV38l","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":714998,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgikpMCRA9TVsSAnZWagAABGUP/iQgMFJXRYEbrw5iWBJo\nu5SA1cweDDMi6WtnYPGhARld/itTgSsfc+SuB1r7+yEUCB7ceVPn1XJ33K97\nxmmZ2eaS7gG9teiTIJcCvuubD1jQ+haxy9esdogPaUz61roZvX4aM1bsQlBz\nJx5foS7r0bm6WLK0OgyzT1kMHR27TBYIKJVMo8hWp5jNgsPRVav4WTtM4o+W\nX5CxEPJ4B/Q6eFNVOO9r73AhjlbZHMMaxkZcq6EanRRXzXVXZ6CUvJ0qM4Ey\n4emQ857E8ozvgSroDBu/U2+S7csrZ5jxM7znVDNf0t8NF2bYfC1RPaCJYGVe\nKdTefFybDl340BWhT2BscgO7AhhjK7QFW2qQ59VADifagW2Gu48gaRmX0tvY\nInSuUvbBq0YW0+7pjpvfbt2dgRbNQ0fIZ8MMVZpIwCnWqq6wC/rL89IjG4Fa\nXH4QELEZqQ/0ffGNMN82ZqFdQTU7Z48FV/Ow0U7+GPXsdmloPbmdL4Re/5sR\ndj1Hxw0+Z3PSRHSzM8L+ZaHREQhEVWwelif8ciqhwP2dgZB5lCkcIi+IDS9x\n7sXhpmvtafwI5PBOFyy1h3VEI1LF45+i1pNz0GBafOg0IFOPK52WkV5l7X89\n6bq/tmxWGiCuktRzi2qIC81y5vx4N/Ein3CQlmW8AwG8SXv5SuFIBAQ2ucXZ\n2Q1L\r\n=SsLW\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c6e946bdede30ca77e1ba411fdfdd6890dbbc337","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.10","@balena/jellyfish-logger":"^2.1.38","@balena/jellyfish-metrics":"^1.0.151","@balena/jellyfish-environment":"^4.1.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.126","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.30-add-user-email-title-c6e946bdede30ca77e1ba411fdfdd6890dbbc337_1619675724186_0.8844400628186377","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.30":{"name":"@balena/jellyfish-core","version":"2.13.30","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.30","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4a86b69c66c7600a50b5e758f06d173715969ebe","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.30.tgz","fileCount":99,"integrity":"sha512-WL7ROeSgCJ4yvKJdCZmKPtL9dHjC+Y/f1fUkvCZJKJLa+3+oQ+NvlI8Hwqo30+5YzAD2IdPQej8IhIVp3o0cow==","signatures":[{"sig":"MEUCIQCtIgCKQrHRqaQMhMJCBQN6EIOjpJUhE1PPBCERRdGU5gIgGk0RRUOCA8iF18t0BbS5+2uOW9IHQRNV9+P2+mGDv6Q=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":714936,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgilGBCRA9TVsSAnZWagAA1+gP/2ybzPNufiLKy44Vi62/\nrCmUsEmt24rC8CbGHqzsnUFBf705m30x6TXhu2lgrbEldExWQXqDe89k4DXb\nrRg5L8BmEJpVXWLhaoUmYRdERM2QFL4jhIi472F/Lc1V9OQRdnxmN7YhsIx1\nlzVuTlNhhAw4LBUevloL6zLTOObY2OM+JI2S1sPdCKb7bYMkRTfd9TiB5dDo\nE7dc8f4SBI0o/3hbOJ0d2Di1wFlWsc96xR8fTYBkMDDCwdn/3ETxCgM6Xe52\nkO2xUIe0shWClI0NIAIjNjSanRHEMdsYrrabZcjbnzBJXBduVVmWrKrGc8cO\nt1bw1TDjW+BfAjeJhSXxnJeK3dBvXYJ/YxyEcSU8e4Ga8G3RvW4MhdOAP2ie\no3zXlI9aDJJlcP8JzX9wT4tJfJG5uwDGw+i0rpKP8AjnYc5iv29+AsTHHMU0\nyd40gZIaCUU+myY+Iaw9aH5WCCV9cFARYIC0ivl7cMypwzAfW8v4NN5NQLFY\nA2kPsb6bP6sioOb7IAMLM9sPLrh+SF56jkq5EKdiEWtoMkYJEHhwFKWfshAE\nidHqBS2Q4nOuGhKoWB7tvqjd4Z0xCFPyP6yUhrXgCqmShwuxVXPD4nxXOFyg\nTtiwzl+aWs0d9YSHyOYtjBz3Sk6tHv8Ukw/X1GkKjMZZEDOYRfn41ZdaivkF\nLwEW\r\n=T4cb\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e0b9e9bfe0b624487fc0e38d30c424526618dbdf","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.10","@balena/jellyfish-logger":"^2.1.38","@balena/jellyfish-metrics":"^1.0.151","@balena/jellyfish-environment":"^4.1.7"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.126","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.30_1619677569138_0.3908855602911143","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.31-renovate-non-major-ee2265120043c4b3a9395f1ac02a7c76722b9de8":{"name":"@balena/jellyfish-core","version":"2.13.31-renovate-non-major-ee2265120043c4b3a9395f1ac02a7c76722b9de8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.31-renovate-non-major-ee2265120043c4b3a9395f1ac02a7c76722b9de8","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9e03bb1bf1d240547d8291645570f3106738c3d5","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.31-renovate-non-major-ee2265120043c4b3a9395f1ac02a7c76722b9de8.tgz","fileCount":99,"integrity":"sha512-2nwH4TIrlBr37ioXKjwws8/GHuDI21gJXu2jChI+4zl4MdPKEUF/Y3MuSZ35zdQJYSWTMvj5we1ETPPR8Qut7A==","signatures":[{"sig":"MEUCIDEo1QWFJHxQR5KcowVmSLJYMsXfM4qmHJBdBF86Gj/kAiEA/ZV0pOFIf8H/YNp8VZgP8ur98xHgpdN0chkCsBRHZhA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":715347,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgiorxCRA9TVsSAnZWagAAj2IP/3Jt7Lwu7n/m1NXmJumr\n5EMp7hYnf3wbTnP2A39pu2y4Ld9ZD38lrmeLdCNowuhDf2v98KR5d7HauzYZ\n39YlMn1xBDHCvCDaV9LWRra9x5YSkCxBsF48INIo+WdDM9OHI6th15COwqrj\nTnm+1nqpkYwyrPZ3tF3xbSr7ptP8BqQj2gC6d/Ry0Pac1pG4rQNlZ45SlJeL\nnocH+Ca+6TGgdCiQLdGUzS/eiWKwth5rt1hC57n3SPKatgdH+jUauhSI+NnT\nINPH/CNCaOtA9fmaQ/jbeEjw1nAJQ3QgKOFCd4uKsuR3n0MKt1aRduuSkaPg\n23c8waS3yzsTpqwnNFldLOA0EkfqP8KnkP6C7oOrFpgsog0WB29BDiHUVG9Z\n0DKndwrKtbXin0ckYZAWRGvIAHj7LsK3sAtyhqxmgj27+3Gldx3zgzGNqlxS\ny0q9g7GE+kjHTicWaFlOtttGSQL/HGz25M2+w7APQXOoPo1xAo6GEHOcNlgP\nx46QiqQOgFKYKmhsIukKS48KIA0vJj5V+AutNbUzFRZhlzimJ8p9qPlszljr\nrvpibR6yDf6eFUZx80fN0Q4NV+hvhSX+UEGwr465t93vHGX6KykwsufdqJXj\n6Ix+0MaBDJq2kHlu6vCx/AGze1HbvH+PVXZCwvt3R6+dKkpyX1Flgna7h2cz\nXn5z\r\n=nO25\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ee2265120043c4b3a9395f1ac02a7c76722b9de8","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.10","@balena/jellyfish-logger":"^2.1.39","@balena/jellyfish-metrics":"^1.0.153","@balena/jellyfish-environment":"^4.1.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.126","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.31-renovate-non-major-ee2265120043c4b3a9395f1ac02a7c76722b9de8_1619692272640_0.4485089114237377","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.31":{"name":"@balena/jellyfish-core","version":"2.13.31","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.31","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"19fd5ea863fc5ffbd58728ab9a58ec8ba7700ff3","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.31.tgz","fileCount":99,"integrity":"sha512-gFrzUJvBxwvAQ5Jha87FaAPek0MpE1kUaY3/0L/SnFFBMpQcB46/sinXYV0aBqfMCjLBy5Vwvw7UxBYuEMa/FQ==","signatures":[{"sig":"MEUCIC02u+HEvD3at4uX2Tozb1lKQEByxoIyB5HJsWSK7EQoAiEAu0ON1BaEjYHcDvITklc52wxyLlUzp8evMacAq6gRc04=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":715287,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgiowaCRA9TVsSAnZWagAAjr4P/iXuxPpI6Hv0VkEScn47\ny1JCQ8HI5vDqzhH85t+sCn2v/KDIyHzs6NO8oONZj0oDYiYu8DFmJGmVuN3z\n2/aAwGcze2dX51whbwkfl0deeI8q0vG1qKMa0JhWj1iIVGTZj4loPs0hWZH3\nv2xW9hxWGryh72nG9M8iffDicYF6Er14kabdIzyMVi13NcYeia2FVeQKNSRP\nyhqU3gUB+HOb+eNRx57xwWdJl0AOLJXPFY2bFrRQVVa/eep4xxdDEaB96Klv\nDrsrJ3Nezmg2nGj+jqUOZzSo14sAMR4c49TtzJ13BY35Q5zD+zs1qH1z0iou\n8b5I5yedMnSyrNbgisf0+ZZflLGWWaZGziNEqzghmHfYYyQhgGFRIq1O9AQO\n7nZnzchX7fFY04/EXIA/89VuXyBX/sufY1BjvJw3C7pEkhzzoGfeKVIOflni\nJL7CpNZTZwhEZwu1kEmKBv5Swe0O0ewD3iQKbuhw7obQyDlBldh2u0KX0bjK\n2k9T/gZQ6KWa290A6Fa6npGXFWswkoMVwwXrS7U+NIBmadx8g01dZFD1M7aQ\n8tthaQDupqgMJ+cd8tSyNXRAbGHlVCTXoAvTZ6+6CBfyZlPSl8y2ML3ieToR\nrkoqd/ugNUNQKSKbPXRqXi5palMTIAsCWIX5kXSCvpML/Q8Br7J0z/U1kQtW\nZZ+V\r\n=fYnk\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d90a7f3c9b7ec033bf2c8102a357b276fe893c35","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.10","@balena/jellyfish-logger":"^2.1.39","@balena/jellyfish-metrics":"^1.0.153","@balena/jellyfish-environment":"^4.1.7"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.126","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.31_1619692570069_0.9728108195713909","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.32-renovate-non-major-c5b8bcf2b21cbc99c8978c74220012fd5edeb6e7":{"name":"@balena/jellyfish-core","version":"2.13.32-renovate-non-major-c5b8bcf2b21cbc99c8978c74220012fd5edeb6e7","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.32-renovate-non-major-c5b8bcf2b21cbc99c8978c74220012fd5edeb6e7","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0d9d47a298f9acfd744d64a6b74f8ad8d50702df","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.32-renovate-non-major-c5b8bcf2b21cbc99c8978c74220012fd5edeb6e7.tgz","fileCount":99,"integrity":"sha512-UnY31aunE9IZlw/fVinoSBzFJvUeeKKZVmN4Uibzu6V/jXb0SR+9O62FGVimwrcv9FaPCe6fzDIjxdM5mKAcyQ==","signatures":[{"sig":"MEUCIHjw9XrpSE44q+mF7K47lMCmNktTBVss7cIx3pCb0LhGAiEAq0K9EIJge+352GNDKGYbCZTMp95dwwsGCOATIo+ptMM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":715776,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgiqPLCRA9TVsSAnZWagAA2yoP/3Mi1gq/+JQ3/sfUvqzF\nbOQYCuSs1mQ/vNVn0RgqlGjKkOJELdU6ezJZjS+oJMbGFB41M13Trf86gzy0\nvB7EmyepRrx9SuDLeTdm1c2R3GJtZvZWWnMVnv80ykOq2/diu7r6R1U+yt18\n9wEsT6KZyFbpq1kV9MHbYDdlBB7vwRWM12c0QVjhBQ+u4/CdSf9jiyTlMkI4\ntH9fm2lp6XVTr+uzt84gzwxVlUmQ4KdOZZ4kMI6v0UpecHVCwbsuuH+AhMfA\nRuRfYHW8fh6r9k/DjI9G3FjxHE0MUP5+eTRJhNveC/nXBg7pkOdSjTudiYaq\n7edcJkFbfgScllAXb2uIuf07Rb+GpX8EBQui92j3FLbxAoq11ZsW/lIqEki1\nUsp6xeRzlTzZDh3p34ofnPRLqR1y9GMfT+7PTQoJxTUU+fs/tarGXP4fVU5T\nG6xPWtavFJQWz+w0gGq7Ji1SH1X8VB9kf14Whky7jUFNw1nzBNvKVrRED8vQ\nMz7acFLEY8C2rJUj5Z8faUmQsjFlldFaXL10/VE3zt6dW/AxBi3AZkKZEqTC\n8SfK61GOq0g4ayIFcY7eGXyWH0QJMvgjuSY0QeUB1YhXGRx8XVogbwaMdBGj\nQLX6U6XXforuGgE0fC9Fq/D4dvJeyKXIKnkGtvaBqFikIeIdiqOiNEtrBOgE\nl8jC\r\n=e7qr\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c5b8bcf2b21cbc99c8978c74220012fd5edeb6e7","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.10","@balena/jellyfish-logger":"^2.1.39","@balena/jellyfish-metrics":"^1.0.154","@balena/jellyfish-environment":"^4.1.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.126","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.32-renovate-non-major-c5b8bcf2b21cbc99c8978c74220012fd5edeb6e7_1619698635345_0.2892825836517563","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.32":{"name":"@balena/jellyfish-core","version":"2.13.32","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.32","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"840cee2a89408fdb7e173c5fcf461b1f6355d1a9","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.32.tgz","fileCount":99,"integrity":"sha512-kGtD3pLgfLsKN1NpQuFHrjaXM/pqjJ1txTeSKF+ktPv0S7IBDYmZse+CJU8CewbEWtk40AFKgGWu90WjZzJ27w==","signatures":[{"sig":"MEUCIBDAz6v6ZhtK0XqAfvwQpg4SX5z43cwc9UW9ypixUANdAiEAgjmzX/7BaOT7S9Oz+IYhrc2xwCwyB2q/Z9tfqdNYs7k=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":715716,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgiqUbCRA9TVsSAnZWagAAW2QP/1xHjsfCtcQeypLHCy3q\nj6De/LW0JV6QP+igd1lm8ofOwsq31fEzTMFyBfHuNMGvm39NG2wNEs6GQcrV\nhHE+GYMd/5aFbhutwjXhOdoujN//1iA7Rfyas+OkdAE9BbPzgf+s0NWkh3q3\n1Gfx6SKiUsFFHMIMPh0Ls5tlwKaKZT1b5zxYE86Daip6cREX3p3Xk9jeLX1Z\n+RQ6MILKONTrXSB36e7tv6mIoOYc3be/dujSJS4gkr5sQk0oNomQ8J4nbzY6\nPoI3j03DkS/Ml7WSKkDCAtx1CqgtQYueu7FOjhHWQunXDKs86gbc5mFPe9pk\nlF87yRUXAbv0gDFOLb0ztZcMY7IrJDWPmWaJVsklzzGBrevJqeTxKuy9T6zW\nK1+IRLSiNmF9WkF1W72O5Xjnk8SjdqXoeVns78Hqtz24rZnu6c79QDtAfCnX\nlyvdeDBciikuCordKBe2lJLYClr77f6vACv6Wm8aoTwDxqscb9Og6SjtQ9T8\nP43O0aASURlwfAaYkDhujL4junRU99jyl0bl8Ho9NCVjVm0rnnac3mf6ly2L\nWyYUGx/KdgWO9YqKVH3iyqgq4WsRDwDiQHV1RYK8DXt83USZOJwa9lZ1FMuw\ndxp7azWyRmc251zmDxZdKpL7C/SO1Tu1ngwFZ+L9UOc1tYr95ae5O8YGjaAR\nmxLA\r\n=V6Uf\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b38e10c0113d49955705884b67a61701a3366a85","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.10","@balena/jellyfish-logger":"^2.1.39","@balena/jellyfish-metrics":"^1.0.154","@balena/jellyfish-environment":"^4.1.7"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.126","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.32_1619698971383_0.6009384822613317","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.33-renovate-non-major-5c276ff0709f400fd5a712bebec379f04104da5b":{"name":"@balena/jellyfish-core","version":"2.13.33-renovate-non-major-5c276ff0709f400fd5a712bebec379f04104da5b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.33-renovate-non-major-5c276ff0709f400fd5a712bebec379f04104da5b","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5481df5981d693122d43c936f7d73684528cf7a3","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.33-renovate-non-major-5c276ff0709f400fd5a712bebec379f04104da5b.tgz","fileCount":99,"integrity":"sha512-xaecuBD6Dy2nu5C+5ylLLUC+FBNc/xYyP3EhHYD5LFwM8bLnbEFjT0llLbkTPAAbh2U9XJNW4j1uIJlcN0jEDw==","signatures":[{"sig":"MEQCIAO4Gn7Hs6gnrTg+nSidQSt9NVttN4651UNJTy2WV4PGAiBrEUpU112cM8Jf2pd0tffpWVvGcuG4Lsi8CMWdlnnElg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":716205,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgitYOCRA9TVsSAnZWagAAul0P/3G6CAVlWZb9X/vmCOI2\nihx2YLlkhiUy0mhc7D+gVlMGbIXFumpHaVY3xbGtXQ7I9mMCF3gSKC4vksXs\n0ZJlvYaY28Dt1e/4XNAFz77Cn6HHZJAB7knrog0K24WMPNRJUvyuqnfi3g2G\nGjGwCoRTzDbMhGciCRNTCSy1QDtiENTwgrSptlvyBw2T9X67txXBL1k+tJvK\n5XHh4f9Ns2Rd0swPbqsmg7uI7uvFmTz+QPhsSYL0PW2xm9HHWl5v0INliYkA\nsSiO/pn+ACqYkwnDzrVAxJgmJ2P3pAYJQLpCi9/eyUuGym3YHcOjtW5Hnw/0\nBWURsrc79V0R/F4UFlAJLNMFrZqWuIPa/Mk+EL3W/3nky1n2BAJoyPDiKOeS\nfsCsAh0a/tQf5jP+TovE/1+LArw01cTrJzKGc2ucUBEUhjCJRXNyPHEbH+A2\nQVm2J8BMNGg1ShnklAm5Hi547X9XYZa6T03HY3DgL1Iomk70VzYESDrfu1oY\nc3n3IvFVeQyfw04qOnXlegnkDgBJ/wplKA/bwMdvZ9HCIWiF/1WdfrrZmcsK\nKjSLw6lcAT8TanBDAs9HJw/uLKdaQFa1F8WvyylSpeXt/AgB+MBId6sgbxdW\nnUvTnPOkojYbypyHg4T537Yh65UgO+hiZKE07qvrLBdGa6QwCDh98Z43PBoU\nDXck\r\n=uII5\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"5c276ff0709f400fd5a712bebec379f04104da5b","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.10","@balena/jellyfish-logger":"^2.1.39","@balena/jellyfish-metrics":"^1.0.155","@balena/jellyfish-environment":"^4.1.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.126","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.33-renovate-non-major-5c276ff0709f400fd5a712bebec379f04104da5b_1619711502164_0.873985845723374","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.33":{"name":"@balena/jellyfish-core","version":"2.13.33","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.33","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4f5d7b3cb8eabe9415f5bea2910506f4a32a59ae","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.33.tgz","fileCount":99,"integrity":"sha512-p6MEL0O8yz6DXpSgkMUmaEcD/bgyFY7+ZJDBP+i6lDEl4gZp0HfNy358UrNzdQzctE7x6VAz+TkgSIgSOJaaBQ==","signatures":[{"sig":"MEUCIHy2/31q2K5Ywz+A6RMDu5yeMwesYRkEv7epYkzyYXf3AiEA9Kgc8gFPkwWhbC5ZlxgrRM1ojpSs3IduVTNCvaoUplo=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":716145,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgitdnCRA9TVsSAnZWagAADbAP/iX9qrBDTQV0go+T1kvq\nzIZNNwvK3yOYfXgF68TK8O3a9FGriilMTpEhtH52OYiXbrMVjKj443WMyd6B\nix30gpRZEXfwuqZcDw+hXNqvnzWTL816DSacfmGFLywFbbscyAUBHyveFFcS\nKlRykPBEqzU3JE0P8D3zC/savVHNOPLIDoeRgkqSHh3JwLIE8FJOC0Kc9l1G\nuvC5MWd+0fH8pAYPbPgsvY97iuxdfL+Wl3tf4hHHUDH0G/3xUxcS7A+Ic9pw\nzkOac1uEUEUqUskEAEHpO2Sfzki5RPiyE0iI+zKM9QkuktD3gpKZvTc8Kq4k\n75IKKCRTxpFAsSBM8DpVCKKpIUU/TAOOzlK12bOpWT+mhQkaLAjqpjrSlXj+\nf6bYqDwx3pF3UNezwOwr82dF7r6JEcU9BMHhSstlZKgZrCwrqH1tY++YCvKs\nevOwAjtJIp0/Y9QNPiaLImRCBE3Qnn2TfmyrD6aoWl8cgz6j9MsQzxUgdLkw\nJAliEYvYAVuPLikEWuw1drWKGBWJvZIYjdy4CGXkCDOknwcFUPLGY1erbwJ+\nkSL1ybxakJIN8kDOSr+FvMJ9Vw0hMgX55nudvVHT8q/moYznO+qYQLyAyz8A\nKqoUMt92cVbJVmYgvTxLLaNHcO//NEsJVXbQROgAacFwXs/qPRE9XNwe3AIV\nG3bk\r\n=fsW5\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"aada0c1a6c19b022ab8cc518bea83c1a8f72af86","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.10","@balena/jellyfish-logger":"^2.1.39","@balena/jellyfish-metrics":"^1.0.155","@balena/jellyfish-environment":"^4.1.7"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.126","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.33_1619711847034_0.11428200679761247","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.34-renovate-non-major-2bbcb50f084e42fa507329b660d3efd91c4a08cb":{"name":"@balena/jellyfish-core","version":"2.13.34-renovate-non-major-2bbcb50f084e42fa507329b660d3efd91c4a08cb","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.34-renovate-non-major-2bbcb50f084e42fa507329b660d3efd91c4a08cb","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9f1da0478fe19207f31b67ebece26d709784a445","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.34-renovate-non-major-2bbcb50f084e42fa507329b660d3efd91c4a08cb.tgz","fileCount":99,"integrity":"sha512-GPRLz7tkC7AH/auuSFNeQ8ruMu0Qqn44lXokplsIaKQftbhCdR73yLOumsY7Whv8KozcV0k5VnYIDfY/Yfl3aQ==","signatures":[{"sig":"MEQCIDLhvy7AqHmN2iHzi2J0YT14Yc7U4lEzf1MxTDsF4eNKAiBKuAxyJSv0sqlLF9QtcmSQ807hi4IYDmm3Qde1DIfxtA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":716634,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgivKcCRA9TVsSAnZWagAArkkP/AgB8So8twnWJLor2Nf/\nJEmwJCopcvY891S7ACkdyUsrO8+ADWkFmY7OaR38Z8gWozwA6tf/Vv7g8Z+6\nY2QQd72l52KMDQKE2iFBR8IRRo7qwUvkt7MvqcF6oizhvby5PV8t9r3MQDdN\ncTp7f2gYpsZw27jOicpLWBL96an6z8Ms03JrdPTTzxN+i9XytqiA9U3fZ2BO\nmwl0Cl3lpEf4DCn4pd4jYZc0qYa2RF2+F3C1EvZFr0cFKWUXHz+t98NYTQMo\nwYYvPiVXmwnOM5DasXa0wjuZvErVEp0rNXFPCKEfLhppQ28CTPibNAuuQ7iE\nHZrFXc8Kt3ir41yYs5NqgoQnEh8Xa1VE17NJLi5JU8e65iAxiMHsUBbioK/Z\ntkhi+NNOsSOYnKuddkt3cpiksl/9E8M9xGUkiCZdw6PilLTyVXqgSc7k5ZvE\n+oa52aP2Q9zIxTBm+3f68HqO89kjVYP7CUxrrvS1YXseaBv5ZzNQKAarwMj7\nAy1kh3Ot+DbUGIkW4ymsN8455da35SxgXY1C3uSznQAw4eUu2PD8AUCpyRUe\nnsgeTchvAJfj20lksW0JdOo84ESn9aIeZ+dTpcTNkD57S2/UZtw5cbAYoIco\nNHAUU+30DwsWST7Rt3+9JZTInc5HU0OLpcumRNJ6yXsRZsOFPV2N8lN4FRtc\n4SNK\r\n=i9zI\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2bbcb50f084e42fa507329b660d3efd91c4a08cb","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.10","@balena/jellyfish-logger":"^2.1.39","@balena/jellyfish-metrics":"^1.0.156","@balena/jellyfish-environment":"^4.1.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.126","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.34-renovate-non-major-2bbcb50f084e42fa507329b660d3efd91c4a08cb_1619718811946_0.8876484795559443","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.34":{"name":"@balena/jellyfish-core","version":"2.13.34","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.34","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4d2bf84346851d2418fd5f967a5ffdc54bd5bf82","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.34.tgz","fileCount":99,"integrity":"sha512-j+VAE3mk8f/nLeBWAV+yxAFYLAMP05bue00r+8jDFSe625uYl3umFbi4LAIkCH7J2efS6X8UfizNVlDzJKku0w==","signatures":[{"sig":"MEYCIQDwoDSgP8n0eNrPnJDz8TkHiYX/ah69a9r9SF2dSLRsGQIhAIOQ+sZTLoaAyxE6GgynuxQHNqB/+Mg263TM1Udu6bwZ","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":716574,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgivPbCRA9TVsSAnZWagAA0AEP/1ZT3bJLdySKkzRB764b\n1M8KSGMWSWL0qsm+hG8WhrtzSWIaA1RZT/6TOw5/NBj9BV6tWfdgiW7JdMDF\nijleeyoiI6ar/7KTe0ThtRQ4tuD/IfDqZZ7O4g5yYbKIcy37hPKnFlxG0JIm\nJgdB9bOo7FaEZVz7Z6QzPGcHTfSaoY1EY0rFcYiN1UeeizbKvj0sFsi08bXk\n0VIHsZBp1+PSdYqu3hTfRI5B8+jemqoys9TGh/3fL8d+40g0Xh/P0WFD4ne6\nUhLe2ThRrXus2pUyI+4t2XAe9AKo4UT59S7jgy1vDBy4s/MKVxt/V8oCW1iD\n3oiIO5DHj2lhiSUHFDpq3IVju1LxYfQR6hP4HtLJJFQC6tfbYR1O3+SMCZ6y\nSKcVSe+/vwwrnF43hfZnfDNTgqnGccTnyjmGZNw+1bOe7RZk4c/FP1axnVH9\n64tF1nNKEwoDYlJup0QXjIhTfi++oX7ZDh8xvCr/VIiutfRwjgSI1cRYaHY8\n50wXs0+z4ek09uKAyMxjaWOF8PaVOJp3iOQjYH0hJGvLY8uqFImWMhz8cUWP\nECFX8pYmsGPfgZk7PeUAFz2Wtt0llEPme20GNRm9f0wd1wfkM6qfTvJmZzdo\nt3wNTKs/ZfMBog0eA14DxeBCIRxU+ZAIc4izDHtFNY8rbY0RNJQ7T6uBc02i\nJtO7\r\n=p1PH\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6c0820a071f0283cf02183a4b21a67d6b5602d62","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.10","@balena/jellyfish-logger":"^2.1.39","@balena/jellyfish-metrics":"^1.0.156","@balena/jellyfish-environment":"^4.1.7"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.126","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.34_1619719131208_0.9043675752921918","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.35-renovate-non-major-02846c53cabafa57f5fa004a0af35cd85625058c":{"name":"@balena/jellyfish-core","version":"2.13.35-renovate-non-major-02846c53cabafa57f5fa004a0af35cd85625058c","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.35-renovate-non-major-02846c53cabafa57f5fa004a0af35cd85625058c","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"859d456bf5bca058f98d4b66d1e3646bfb0a5e0f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.35-renovate-non-major-02846c53cabafa57f5fa004a0af35cd85625058c.tgz","fileCount":99,"integrity":"sha512-lHUH8WOjxGoYLpuxkUweYhzmGNQ0KfQCxGQtYytnwcFU4xS8Ur42wmxncu9IWqqXFbHGyoOLMIG1Xtj8wWBf3g==","signatures":[{"sig":"MEUCIQDFgrNE5+rbSqGhrt9q2frFQm8oVfJpVIqJHUTHEWsQ6gIgf5BSOSLXO5W+tfFkJvqWUtcMACUjrw84PsLS+367Av4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":717063,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgixxECRA9TVsSAnZWagAAf5wP/2ebG21svFkFV1YgX/Cs\nIvUfO+AyC8Fj7OSOjamWVxj2zCxb8K0mWuC1h6R3dsov6OZMvF1AebIxSOaE\n88r/8gZrR66BFlUnXbL++JqJChG8ZtT41iAVyF8DkcglNMdPu6Uarub5aRyt\nL83Vj1ks0B/+OrgBcVviwusi8s7X/BE4Wcag5Ekzj7Abtt1clvaOp2pJ4SxU\n8U+Odoop+DYu86lUj9T8de7HpG0lTBzhhrVITEDOPrWS/4YHFKim7XTgA6IJ\nVSbYtPO0H5O1nAHZJB/+V93y7KPIbdMpMyvYP3dZqWJ2ssNuYUNb1LvlZ1ka\n/OaLgedItp1cyxwQWJq83GcgV9busxjHjme4q0f3h8KFQzZhW9jUeJt0Owlo\nqKzAMm8yq6n6d+LbhpU2BN6BgJ0CcmQq+LiVa0qgFSfTwDwZnUCLPSl3wp/t\n5j9+XmXmtR5ZRuH9oydGTozNd3uTITyZwJM2VoeAElFuCOhvAsocDe12wyFP\nMUi+/oYYWuGwShqods8PrkXKmvPVJRUiUNQa2w7LYAJ55PtgHf5Rl3qyrZ3y\njcJ4LXO5l9Zc04E7shO9AZbf/dVWVLBAi7w7ih6xWcC0c2GRRYU6O2bOHDdw\nGWmYIq9QfufC2A9NkDP14jqbOkZI5P6nItyumeVethkHQa4Z6nG3IgPLzbgt\nexe4\r\n=OWm2\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"02846c53cabafa57f5fa004a0af35cd85625058c","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.10","@balena/jellyfish-logger":"^2.1.39","@balena/jellyfish-metrics":"^1.0.157","@balena/jellyfish-environment":"^4.1.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.126","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.35-renovate-non-major-02846c53cabafa57f5fa004a0af35cd85625058c_1619729476385_0.8765817059139636","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.35":{"name":"@balena/jellyfish-core","version":"2.13.35","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.35","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9896d7cf544213e237e6263a1f3e0384f647ab99","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.35.tgz","fileCount":99,"integrity":"sha512-9CkdNAe2PY45vrpwaqXviGNRgbQp0BP3ZNcEWsFvN6h1bXovGwFZPy3uMKAvTld1bWjU0Yoel9aXhCKN06WByg==","signatures":[{"sig":"MEYCIQDlfdLKdzih0Bk3k2SxoVvc8QCneyzDHHiGryWpNP1F0AIhAO3J5CNuC1A8DzG62Lxl7w0g38nYKFgPNI0FRtF/Aa7b","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":717003,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgix4FCRA9TVsSAnZWagAAj4AP/jOAqFZf9Ymx05150Ic8\nYogxfgML1lsVk9NVTYFBPom2I8k/Kbn+BhkMPnUw0uenA/8lrBVmw66mNn5z\np0yxdVI7AtTiZvYmTPmNrodGr3mQQYXmAqJyODScHRG4DP2hRnnhY3hQfJt8\n1NlvqtUYklJnPgUi0hC9gh2NwIAQtVV8du36VSQnKipRg0kyoqo4ZJXruMrK\nvml6gdD1SqfU3hFGBW2z02xNiIZYaAjqVIc41ypP/SxofLgQxX2kxP0Y3CMY\nkwwxxxPU/uTnM5dJr0ZlrYKHcNnX4ltkIAyWxhRGSYl5LpJ/QWiKyaYwDFEr\nyE/zzDgAnO3JkIxxEQlXGjgA3Fy7xPBjha3rp+rwB7rbxd7KTyX4omDvqP8Z\nOaI7oTvzoSKsGIXn1mbrNPP9DnLCtKNIb6DBsavCVI/AykMhD8olenguIIT9\ncz74srIJBY8HjMG+e6ducu53QUAPCftOsL6SjKEQQdaLoyvEpHx6Wao4Ul/G\nbKp5JXA2OuGMdUN7aVOxRnswr86ogMtMxZDM+CM12vQ0t+sXXfVdgLSBeleA\nbPE25611FQHQK86FGtx3Y9xCJKXDTB8k2KcqgrEe71qxVoVMRq4uMbz9ULVJ\nkmdHakJSYF78yOoMdGsYxFKEM+RSbaFOeBb+MkhoQuaREWt8610KBaYa1YK6\nQufb\r\n=fwj7\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"65d4261a2020968faba97a365828892ab2fa6472","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.10","@balena/jellyfish-logger":"^2.1.39","@balena/jellyfish-metrics":"^1.0.157","@balena/jellyfish-environment":"^4.1.7"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.126","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.35_1619729924474_0.25420284809465543","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.36-renovate-non-major-6b806a81b75b0730372ee5f17ef2559c4a8de885":{"name":"@balena/jellyfish-core","version":"2.13.36-renovate-non-major-6b806a81b75b0730372ee5f17ef2559c4a8de885","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.36-renovate-non-major-6b806a81b75b0730372ee5f17ef2559c4a8de885","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"140cb020bfdeaa1dfeccd3d54abef6d21641ceb1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.36-renovate-non-major-6b806a81b75b0730372ee5f17ef2559c4a8de885.tgz","fileCount":99,"integrity":"sha512-xnc5ByBhk1Zbum2jZ1gCRl2CPAxPyEGJJ+UDZQyb0oZYsu9DxWKrQN2cAUpVqsKj7E6aNUUb8VXc9wHWxb3p7w==","signatures":[{"sig":"MEYCIQD4PrP86CE9OS+L+Dd1ds7i/QGWKQTN2QEHZ0niQDAEYAIhAKejfi2MAgAWqntwTDHQ2FSqDWFxkO5mbemhDX3UM235","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":717492,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgizKeCRA9TVsSAnZWagAAqBEQAJjdF7LtBLIlGNGhrx7f\neZWUFPgOjGDoIDOrQeKqxV9sRdojcEfRZ8810ZPOs/KMkxf2vSd4ySpN/gVK\nZKRvm0OeqsSLmvFM7rfWBkio+00No7rpmnx6EDC1BhjaBxSSLa/LV1NlPBWQ\neNHBOs5PLoN/5IbTwPEnOfkQNk8C1j3rTK/meF8I2GcQd3C6+SoV4i+MLAZ/\nLlRJ73jXGwAYvIx1hCNLXvZR1tkPlS9DhFISUwyPJlhowQruv8P79fkZz++i\nCTrwlMKEmFX3ITDQ9xGdUasQro6ZZwKTpJeIfeZjp51nlXLDvw93DxuKz16F\nRn3LppqgAU2ubBLsKQt7otcqnjNGxLsVsAyMP6QHrlPqdI9Xdhdxow/Z04Er\nS6zBqBytuLGh/djj1ibif/K2bUhk6nJMTJ1PC0S883LqwBZ+CUzkXxewDGi7\nO2ctRt4lXEvNfTjkUBCP5YihRenMOjXc6mZ+UYMP0JFtScJwwbwKYCEsc668\n3Kc/sFm7A2TZvI2rhqxXQltdUZ2kuIYnn36ETURZJDPmxwiXh5Ip0v9JwSmV\nRx7XB17duP3pX+2NarkiYiV0rZ2m0w4fVODf6FFqSqQ60SuM+EYuWC4eyng4\nbXkiYrqCXFJqzUCHtC9rW5ViCO35MAdBTqe61tgIzCfp2w5YobtB+HE/UvIk\ne3M+\r\n=F6m6\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6b806a81b75b0730372ee5f17ef2559c4a8de885","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.10","@balena/jellyfish-logger":"^2.1.39","@balena/jellyfish-metrics":"^1.0.158","@balena/jellyfish-environment":"^4.1.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.126","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.36-renovate-non-major-6b806a81b75b0730372ee5f17ef2559c4a8de885_1619735197976_0.49665221942504334","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.36":{"name":"@balena/jellyfish-core","version":"2.13.36","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.36","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"fbfbaaa932fd16847e6009d98d339b738e256145","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.36.tgz","fileCount":99,"integrity":"sha512-C+3mz5CCn9RPDQFMjZeftUoAWWdYQwmLjFk5aADcXRKXasgTlvG4w9uYlBSfVguCMeLPCLgu7R20mBXvPRz+Pg==","signatures":[{"sig":"MEQCICye3DfjhIboZUkQkA/cshYIZOW/d992QiYnihtN1zLbAiBcUnPblkYLQAsWDz6Pxf3LJEqNOIsnjs7gAMslJb5Brw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":717432,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgizRECRA9TVsSAnZWagAAdhIP/ijyl69yqKUYXUNFKicJ\nm/TOCP5DF2s770dwpFBTUr/vYvNk1daxWWGUfBZLflsjZsF12ZUkHMOXPLdY\nSbIMY6JPcFiuNKmvdLye1yGd63pIlfLWTn2OgphvACrcD/mI2uoLjvSH7FDe\nOIeQbKBjvxW0uoD8USyPOTsWDZ4c82iTTNxjHq3q+pH+jrVBXZb32ppEUkye\nS8b0Zfeaq6VNi6VtKDe047vKX1R2OX1R54wP0dZ3Cqzcz5/ao+P0O70gGeWT\n8N26dIvGMrrGJCc4kAjFSbfEJhP6SBEHyCcKRk7Duy4ZdlxZ6qgjwoZ6TjlL\nk1BfMvJvQrrdZ/ZptuAQotG5kPcJ193uquDqmRwcCOxkWA7NlzcbIgV1M+/x\nYJIANsD8a+OBvYI9HiUxOup+1WroM4ViEhl14q/mN/ZWXv8V4HxM1h7lDZlL\nR6gEIvNTeUTBiN5VFHhQKjbshfNB09VsWbdM37TsSuEFIcJtcsJbL3EN/Xs8\nwRz4PEStWmsRch60y1N4lMKIMlEEbwizSyY6HcOkYsNZr0k/VKGHo3nBpLys\nYbvNTL3skadYgVNdihfPuaDrgSkNoyOpRMAkWhj9JANgMeN9Mn7wYFeyrqdF\nzyetlCS89TadZLnl2y+wMq0yB19S+GzJR+/OiI9pVTOwqB7H0+qtEGPbmKQz\nT/LU\r\n=zIfF\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"be434905e442a4bbaf780dea8731344974bf846c","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.10","@balena/jellyfish-logger":"^2.1.39","@balena/jellyfish-metrics":"^1.0.158","@balena/jellyfish-environment":"^4.1.7"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.3","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.126","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.36_1619735620417_0.6092725998529538","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.37-renovate-non-major-e5d346d22360b64be239c55f3a5632f04dd0f1aa":{"name":"@balena/jellyfish-core","version":"2.13.37-renovate-non-major-e5d346d22360b64be239c55f3a5632f04dd0f1aa","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.37-renovate-non-major-e5d346d22360b64be239c55f3a5632f04dd0f1aa","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"547355e9c7fca60f54d26388332c0ebb5d45736a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.37-renovate-non-major-e5d346d22360b64be239c55f3a5632f04dd0f1aa.tgz","fileCount":99,"integrity":"sha512-ClE9mcKqrqDtvxdA3uMAG0wuQoqr0vGcO69K7o4oN4QaeIc3h4RZ2hxnPHwHu1AKIE+5fKrg6hmp/4WGin5QUg==","signatures":[{"sig":"MEUCIQCd2Cu8Et0q8N6br7kMreBKh34sKJSr9YY8HVvKbDNkKAIgNKS62JivbqTW8v4LmOByVQ0zIKh9oahJ5Zuq3JoFoSQ=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":717843,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgi20tCRA9TVsSAnZWagAA85gP/juf3k2/6Dnva86XjNBo\nlO7KpJ6IPSTPenfFhfnCvZTF+pLFZ5YLJnPcv2CuYfOgbSW3po7ayVDxPbxk\nRZJXmgwIJdHjatPSlFp3Ob2mgm40oAsGXEOD3NbT5WvX8liewHxaB2rq4T/h\ny0nGvr9TIiPCcgeUU/0CsRZgElkAfkLIb4bSu86+MXM/l1MrN/w8ySPGqQIx\n9N6ls0xdhnBql3gTWtto/kl0wALXYhNehvzk7ByiFxshqobcAxVHfhgtBFo5\n36KdV22DnweWFuJ7w2s6VRF7r+h2gaG/pwHclX3z5L1ayWkVsM8n8x7qiMiN\nqrkXSG6g0fiDn9sXBxPyoM1K6cTIbi6JnEfJQT45HfG1OT32X7Dr3zuWRsZy\nN4lXNEBVeTnGFm63JYPqmzZ9TRVup1iqdWIDvjpa7r/ohG83AlmUiwhjn1ZM\nv0DELxd/3nSA2KyYKikNwydLLB+H/LlFVPGSZm/E4OtosqnPepbYsc2S6NYi\nEuy+3+eNF6//ijjdhsybAAzn0N5nPjxx3CeEIRizm7ufljt1HgJUHq66eeTU\n6P3tGRC6a8ob7EbqwAqw5VeiUyF+D2oJENpvo1g8dmMbWoDBhfB6MGaY4vUv\nEEuZSLrsw301a3H9DKXE0hBI+CaMhggulJbIw9a+05AL16ODJvcpGCXfGqtl\nQViV\r\n=+0Gk\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e5d346d22360b64be239c55f3a5632f04dd0f1aa","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.10","@balena/jellyfish-logger":"^2.1.39","@balena/jellyfish-metrics":"^1.0.158","@balena/jellyfish-environment":"^4.1.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.4","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.127","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.37-renovate-non-major-e5d346d22360b64be239c55f3a5632f04dd0f1aa_1619750188913_0.5151242514440137","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.37-renovate-non-major-0fe1f53ae541cdce2a1e6bb764a0b2826fc810c6":{"name":"@balena/jellyfish-core","version":"2.13.37-renovate-non-major-0fe1f53ae541cdce2a1e6bb764a0b2826fc810c6","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.37-renovate-non-major-0fe1f53ae541cdce2a1e6bb764a0b2826fc810c6","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"603457aae47856e1fab52703f3349992a3b535bc","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.37-renovate-non-major-0fe1f53ae541cdce2a1e6bb764a0b2826fc810c6.tgz","fileCount":99,"integrity":"sha512-MUe0SzR83lfPQDSzRfpBePV5zgJBwsJEwecVCM80v7nmatZ5BYO5jhskT21roNaZMta21C63tOTnQQyPafDC6w==","signatures":[{"sig":"MEQCIFoe/v8sZ1DHK+M5pexvNe6LdsK6FbqyjCpJRut6+zxlAiA0S/kvBsq4FZtVAzIpEcvt7v9T/8b8jjEw6RP+xAkzKQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":717843,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgi26zCRA9TVsSAnZWagAAOf0P/RVJeB6bJOVBDTuDFQaD\nh3NK5Ver4/20FJm7BWWbB8EuzWGKtTT9/KqOMAj3Nfe0lavFaPEFrpH/QNB2\niqheQuEldv0ziLseApEh4X07LLoOZA4vZwEkDmAP1wftimVl4Y+u9Lkrwods\naK1StE5F541s63f/XwCrplpsC8fsdjMsdMWJ8bKPptc2IwcNQOvBJn7zmewg\nyuqirnhrIdtLz5wSRg1yGXKRErTQJNUqEOYoidgR+ba1bgGcKRsOQFIbqa+G\nhox2W0L9lRZpXkjNbhMqYK7htCOtJgJX/fr2cAPOk1xC0gSlT75ZgMvB6Eqq\nEJ6q24oLcY/x+Z5HEq9yvFojxmCPUUdUvKjAnhS4ElITHONDwIaGQDaAOhxe\niPmSrRUp5OA0WZwqE9tsROAhoAEmxKVN7lLhZPgSeDPEJ5vFarXssIf1Rxv9\n3KyGk/20dZpUfnTz8xlogA9aNSmYxeleWIz0+5ZP2db/y/iPOG5k/QNdPrOp\nGXQN4cnJIkaT3+VJ9AcrQ1fmk7clOH2kXSaLApyoaFKvMeRraoYiItdTLXmP\nPUfrMJ5m4Ir39EE2JGzeI/q0Vfu3wsZVEFfKnSRr6540OstCUWSu7jVjM0vz\nK5c2s68Sm048LtoUohm6rUc2xlsNE5rc0G2oAy85OlBRiPMI3tyXZk30gdI5\nexcS\r\n=+TrZ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0fe1f53ae541cdce2a1e6bb764a0b2826fc810c6","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.10","@balena/jellyfish-logger":"^2.1.39","@balena/jellyfish-metrics":"^1.0.159","@balena/jellyfish-environment":"^4.1.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.4","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.127","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.37-renovate-non-major-0fe1f53ae541cdce2a1e6bb764a0b2826fc810c6_1619750578597_0.47010659748765704","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.37":{"name":"@balena/jellyfish-core","version":"2.13.37","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.37","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"eda348e88f0d5101076694753c6de86766deaa3f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.37.tgz","fileCount":99,"integrity":"sha512-g4JyoLn9w9jIfg2tk5JLsflYn0W3RRUNsIOnIWn5yDnc/XPKNlBDb8EvJzExgywwYDInaUIiEvpXwdvPFfC9rw==","signatures":[{"sig":"MEUCID/FHdY9XaCXaIsqZZIm1JiaKDIs7CLgooiYODMl6wrZAiEA5YMmdQeACB46iKELkClYMYb8xgSmUp5Bf0LfWAuAUnQ=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":717783,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgi2/6CRA9TVsSAnZWagAAhvEP/3Hp8gbypVnolRClxcXp\nvv3FJeyYVGIbKBLge4s0V5WfvuL9DE7kASKP2nrI4qgMwBX3feiozuSnuXt+\nmEEJsX3IEWrUORR6ZvDrt7zOHLcfhTgCKgE2dDBPzJR1f+AopB+VzuZ6V49W\ni/Iwt3wneVtsHPKNQ/bSzSHMAugch+acvR3gWYhKzwANIOPgUheOZZLr8Fpz\n51bXpd8kmu3M7KYxnfF3n/03jNjcJQO15WgSFgY/cGMfVyLEoWGJKDkz/cm6\nJB5RQd64nLBgMCJT1/78ddhP6tFDEfN6xqM6yrpk387oNCzeZpA/EnYH99wG\niI3zbijubca2fei51nhEbGogt9ShPsI2vfQPG4V60dHoscwBMPbGIh7uLvn/\nHAWJP6wp0NOptNmnLniqpkQJR5uIbGGhER+5imLcpF2UaDEm4NbskX28g/tx\nK4S4QIOlcSa3EE5OqDot6fLidNR6QCjg+D6apqihGf4U2SkkmjOmCbp+CeIl\nm/baZgR9mxMBQ42eppEbdfGnOWJT2OIbNSqcnzaA++L1pYboCO6qeMzwKV73\nac3oGT1j0tDpSDovi3QevLM0psExZIfn1c8xfM3Uo9kfE4wYD8SpNxaEfDZg\n+FgrlsB307W8MXLsGQR68/mXDeIa5Us1IKbWkVxjz6gLMEXdX3mHQoYX6ujS\nZNOb\r\n=uo+J\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a0dd4dd786a31627b8ecd7ea6cc1547d3be4af81","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.10","@balena/jellyfish-logger":"^2.1.39","@balena/jellyfish-metrics":"^1.0.159","@balena/jellyfish-environment":"^4.1.7"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.4","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.127","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.37_1619750905886_0.10612051855493254","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.37-renovate-eslint-plugin-jsdoc-33-x-4b38643ed595a6820c4c315c94e3f9bbd8e387f6":{"name":"@balena/jellyfish-core","version":"2.13.37-renovate-eslint-plugin-jsdoc-33-x-4b38643ed595a6820c4c315c94e3f9bbd8e387f6","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.37-renovate-eslint-plugin-jsdoc-33-x-4b38643ed595a6820c4c315c94e3f9bbd8e387f6","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2476739ac0526b89c9b94c79e5753358f73212b6","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.37-renovate-eslint-plugin-jsdoc-33-x-4b38643ed595a6820c4c315c94e3f9bbd8e387f6.tgz","fileCount":99,"integrity":"sha512-Usn8jQ7Xt9oeYXn1GASOmR/mi8TPVg7C6IYuwUP9YLpxBC5E3rtYSMbkMPKNWqNX2HIZYtN4G5wDot1pwzNn9Q==","signatures":[{"sig":"MEQCID7atpR2tkw5wJ87lw3SOPCkHm6R/x46TANfqlLwYk0lAiAULL+2KQZZQy4o1PcM3yE2qUZA2BJ4K+jwbTV2mhBbrQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718176,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgi3C1CRA9TVsSAnZWagAA1qMP/1wlfb5T2f5w+1JZthz0\nK6pB7h7EM4Ecyox+pgnLM2+7CbIlq1gpUalxi8ddy8RTzmVWzk8n9eni7/xj\nizSsnduV0KGrxtMoXa0kovnjewdAtJzxNWlZ5XrUx8PknSIlbA7va7hKydT1\nSUMxFZCgXR3Vkn8ttfQA4R+uWAUFjbxU5ClIErhBYzNEwnxVq6D91Qy/s53I\nHtSk0C0Bw4Dip/F332h06erOFWsDrD7wW55Z7bC6xFd5fNJWGe8ypynGyL9s\nlu9BtF7KBIE6cNg2HUEexm2z4yChDWZ+ekNviStChWWA4q57yMNFG2bUw1jS\nZ5JLgJ9nZo0u1G8M9CX/DuMIuhiJTMi/bnKaqGM2+4PjNQ0VOmrVDf2QQuIa\nXdAc8nYUOoMEmxWXbiV+8rGC/qTkKbRQagtNloredXHhGyNMRIYEbvOV8xKd\nFmkkBMUJxufW24gEpGPt2KSgOUL85/HX2H0lQssdsb8wpnuf/OlekQNLJbQf\nPGa2miR9mMjGEaLxLva4xdRzYsLCNPO5jJl52Yx97m97uGQbjegz4TmuKUOl\nO9OhVIySgF4C8e82rYBTF2WweNvcL2r92rp2L7hli0lG/KPTcSDXRi8yda37\n5UlSZTqfnJmFKE9RxNfSP1s03tVCXLMKCkmGszHDhxpUPBt4aLXg3nbEB2Be\nETez\r\n=3r3x\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4b38643ed595a6820c4c315c94e3f9bbd8e387f6","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.10","@balena/jellyfish-logger":"^2.1.39","@balena/jellyfish-metrics":"^1.0.159","@balena/jellyfish-environment":"^4.1.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.127","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.37-renovate-eslint-plugin-jsdoc-33-x-4b38643ed595a6820c4c315c94e3f9bbd8e387f6_1619751092663_0.3343248949736686","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.38-renovate-eslint-plugin-jsdoc-33-x-cb9c2f01e058cba0e072b87b3358fd48eb831847":{"name":"@balena/jellyfish-core","version":"2.13.38-renovate-eslint-plugin-jsdoc-33-x-cb9c2f01e058cba0e072b87b3358fd48eb831847","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.38-renovate-eslint-plugin-jsdoc-33-x-cb9c2f01e058cba0e072b87b3358fd48eb831847","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"72e4d38a493312d7d58544ad8c72d368600905c8","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.38-renovate-eslint-plugin-jsdoc-33-x-cb9c2f01e058cba0e072b87b3358fd48eb831847.tgz","fileCount":99,"integrity":"sha512-4iQnjzOYycb2Fr+1P7Y7R+Ob+8ZqT/5O6bKhAQf1utTZQ3n+7pOXR4T9LR1jHHFVVPbMxvD9QwvmNuyoLt3fnw==","signatures":[{"sig":"MEQCIBudwJQlqkOcqoMUTlT3Hc+dYTEei1Airs/xm1zUkquZAiBFhe9rtxkQGM+3aNfC8M/8lz9bV4uqZiQgvoQc089VBg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718265,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgi3F7CRA9TVsSAnZWagAA0r8P/1aFOXjXBhodTwjUhd1t\nQ9kbJD7K3pLLoCOiSOjVaDWdBmm+NIXczLgDttwQnJQX6XAoAMLZUoK9SoWv\nVF/c6rdCT4wFti1YRf0JajyEsFtUUlLmh1SJ/8C3qIW4nZg19VyfqIfJLavV\na3hHo5yooGFowEA+ysvOhqkSJcn376MjfJSZIms43bO8aewQV8g3NNDijHsE\n9QvX3Lsdp6+za0YQkgifjtu5w5OAF1Kh00WJ72DmH4502AUm9x1uny8lAZfj\nxnXts3zaXfLfDp6ukMnstdGcBRJ6u3onaeCo33JpmL0wQyjfQd9JG2XN0eNO\nF+4mYzOIEJf/CdH+A8gSDxwjBK5oMQk5UZlMmHCvG2LNT88Zg341s9tS08uV\nD39DBEEDMEYWShexCnfkq9J1LqJ99nahzhOcUBFZbhvAUNy6Up+AiqZPJyTO\nsdcMXdkfKCzGd4kv6grgBOwpqa6rrxODq4UIZo5hnN39CqelBqeka6G8pDuF\nliZwxGZ9GMUcnm7XRo/0x6hCOCe+YGQ5gAsWhlD6jvyzzzo3GIsHqRrahhYj\nWEgL/dF2bOsm+NeaSR89gZYvqiQZLsPFqQPKILFBdduYIlvyBfTsKfyIhccB\nG1LqQnK6PhAgTejUOiEALlHeJC/6P/J5vP55gpe3Rdwqv4ADr6YQDQUobRh9\no0hH\r\n=vl+C\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"cb9c2f01e058cba0e072b87b3358fd48eb831847","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.10","@balena/jellyfish-logger":"^2.1.39","@balena/jellyfish-metrics":"^1.0.159","@balena/jellyfish-environment":"^4.1.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.127","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.38-renovate-eslint-plugin-jsdoc-33-x-cb9c2f01e058cba0e072b87b3358fd48eb831847_1619751290924_0.8881410869445607","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.38-renovate-non-major-506b030eb86a02b9bc9eacf990b7116fbd6ffdbd":{"name":"@balena/jellyfish-core","version":"2.13.38-renovate-non-major-506b030eb86a02b9bc9eacf990b7116fbd6ffdbd","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.38-renovate-non-major-506b030eb86a02b9bc9eacf990b7116fbd6ffdbd","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"038d96824b4bec0b98a8263fd714f715b642789a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.38-renovate-non-major-506b030eb86a02b9bc9eacf990b7116fbd6ffdbd.tgz","fileCount":99,"integrity":"sha512-SgQrRW0+Nsz98c4I7KG6LgmF654zXAkb5H4j2fElFGH/cvV3OuWOdADpc86vAMhJ+D+bU6ljal8S16A7eC8Omg==","signatures":[{"sig":"MEUCIQCgrl3jdObi/re4S1S6u1ojj8VypgcfacpEgOfmJauJUAIgC+LmqcMfxVN1h83a8o5YTP19EQjdf4sEuPtCX9AJc4c=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718272,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgi4s5CRA9TVsSAnZWagAA48wP/j8x3pKqPK/i5S2EE0bZ\nj6DTc/ktgZlt8PKUrcXjxgaPHbX6llwZ6oMRG7rAxMXGIYgVIoi0Mui4Eq3Q\nJmoTb1nNAXPxeKM2MKWC7G5bnUUHZ/8JGEuWNdSnVnv6Vek0Xpw2S9tpINzd\nv2bOtoLNGn3mwgvJXVfZBmzuiSQXsRtofrXpmwR3Hr1T/0enOXHjpSfbkeZ9\nAfQc6JI4EvomQrLIZxC563taED1WTfax2j3pVYMlCScj8Qpf7/tpidypxA8E\ncQYxFoCIijmuM1XgxhghKm7CcYZP0qvc0DqLLOMdyxJoLwFvQK1Ax/C709iH\nhuq+n3osvwjDCjf+BCDR9aHm9yAYCe9ry/v2xPCekKMhJ/SDF6L81KqJ5Lcv\ntxiohWysUN7uiSfFp6mQl2iC7oiEtCfE4BDY6tTsZhWhUGIa4RnxGmmiJG3u\naoj6Xf9/8iPikqYvXEcXQ5MBsg3Ov8akoDfy5SFo+mtmMkg5KNppJc7/tfgw\nH+ZABwGiVRzQc7mh7FBlHwQQJlQYQpsCP18s9nEPXT4lcXYbT6hDVSoLaTsl\nwJgVLkWcyFHtLzi9ZutnDEU3CYE6A1FxJwOaOQOljZp/ObkLiKjoq1MaYmRV\nxcdhskGQa1JVVcURFFatCJt2cI6DBhSLKpwGQcXS1AOO5ioMOPl9TJPtKy/6\nRzR3\r\n=gAYd\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"506b030eb86a02b9bc9eacf990b7116fbd6ffdbd","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.10","@balena/jellyfish-logger":"^2.1.39","@balena/jellyfish-metrics":"^1.0.160","@balena/jellyfish-environment":"^4.1.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.4","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.127","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.38-renovate-non-major-506b030eb86a02b9bc9eacf990b7116fbd6ffdbd_1619757881239_0.8453378006983241","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.38":{"name":"@balena/jellyfish-core","version":"2.13.38","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.38","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9371ccd01a3d9f4c713fc0099de86f61b07baa41","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.38.tgz","fileCount":99,"integrity":"sha512-JYaSbcDdWzNd1sfFXyG37jMbIcpVNe1Z1BkvCj5RBW5JyxcsFNrjXo8N0yVNjoO3CnlKpxIVKmN3BoliNBo1mw==","signatures":[{"sig":"MEYCIQDUbOUeIJFHeRhqOYjfakRjmRSTiQBzFnxNpcx5uzmKtwIhAPbQJU/BEZOkHAjVTvwSrvnzCMVbaml8TBreX90qID+j","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718212,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgi4y5CRA9TVsSAnZWagAAv/YP/0KQnuntN57g0zo0TGeo\nWZiJAGt2q06/xbz2uas1AX+rFQVAworFHfecWr2UPq4Nzoxyf7j0HJKZAXy2\nO9wUjHa8PPQiiq38WiJZEGkMnOhI3vwONGJ76VSWd2DyYwUgfMtfK3YrCQHq\nuzMGj/rLxI7UHRGSxXLrl4ALA0oplWWGgdA4IG6NNuhtE8/s94lcvs7McXLt\nju0dAckbMyv2lD1+27M60bmF4sBCUKwt0Yjnqyo0zeoQJStfixJri/BtFmEU\nA2tR/F2b5+Iwqgu4729RoAcLFPZjq/WTAmVnRu39rG6GEbfND/4HC2kJOPHt\n8f6Mm7GItONuWW5q/jv8sgaQmVIgvbYIA2NvznYnr2WqCdWc5AzUxsIGITHh\nVes8aNmMuGv0ZzwTSuOo05FQYhgQHDNIT7WQNuPlC0FXCyEQjoqAoVWdzEKC\nFPvVomfeBX0ozygY5IJWoPxDbckhsrDp7xyQix9E466hY7IkUG3U44lEiSi6\nBYnbw2gFfw/iAik9NTzOiF5DkgZ68dberwVzCPBNJnRqIZ6OhDmpbAXvloaE\n1i/gsd7wcmoO2KTHUPSrMUHG3X+3Bpaswm10Yy0H6dd134elEz04I1nErbhj\nfxD4Y3vqkLnfcPqeTiIFrt7UjG8Xz9jhsAdlBr/6bVXSKVPNcxWu22FmNyCq\nqfuC\r\n=W4TT\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"851b88f71c10df02b904c6a6e178b421645f51c6","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.10","@balena/jellyfish-logger":"^2.1.39","@balena/jellyfish-metrics":"^1.0.160","@balena/jellyfish-environment":"^4.1.7"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.4","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.127","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.38_1619758265011_0.9149880057169937","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.39-renovate-eslint-plugin-jsdoc-33-x-1e3e5bfd65c5d60eedf710c09589c72967f785b2":{"name":"@balena/jellyfish-core","version":"2.13.39-renovate-eslint-plugin-jsdoc-33-x-1e3e5bfd65c5d60eedf710c09589c72967f785b2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.39-renovate-eslint-plugin-jsdoc-33-x-1e3e5bfd65c5d60eedf710c09589c72967f785b2","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4bc93293090f95bdb4e83a223109ec641c1f6a43","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.39-renovate-eslint-plugin-jsdoc-33-x-1e3e5bfd65c5d60eedf710c09589c72967f785b2.tgz","fileCount":99,"integrity":"sha512-wgYXrfM7SAlCvyxYL4IZWzvz+eDoqRAJ5GTuUztzIhke/QYEtT4K7+ba0cqT4BdIQUlpUbQMd7ty0uFnxoTcxg==","signatures":[{"sig":"MEUCIQDksax/jLnC2RH05kpLjrCGOUwhJj+H5kjRsNJJfoQxhQIgV2FKSBYC0lr3s6T72Mgbk2r0+ZHiy++bh/7a/8GVhsI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718694,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgi43TCRA9TVsSAnZWagAAYK4P/1xDrANJjAsN+HtG4hxx\n4YldKGqCoFcCrljwes6vjQlKB9FqV3/F/V5Xi0A90lcA/IHC2YbolJIF+H7k\nvNE7C3R5QP/KnxAec/072HPEwS9Mtfcl4m62I2/UJsDorH0CYwkgRvRRMCSH\nHGw8i+85uHr6e+xeNzyC58hIZK1zqC3LW7+KgkYmQqF3X+7g8UFsmoF9XObL\nYb4BPSNmRiLoDQ3mltOXtbRn653AYYQu+kjny0klyKuhiZQGBCkP9olNz5oB\nXWQANyoZDbh79Vd15s498TLlK+XsvHz+g5bZh8EKdZ3RmDj/lSLfcSy0H3st\nziVu7IaouAUsCc2Rgt8NKidMIHOY4hFBnxg1gAa7AIlH+/+4UCjbAc+tRf6z\njD7zx0Lmq2yp/eOLkVIHEJg90VyI78xtC8jKwySHj/jqvIZRQP2xvlRZGKF/\nUl9OnwdLCfN/8kXEkK0DriBKlpiK/AygWQDh1lkpUV+djphhyKuXCJYIuEc8\nKgZ/dUX1SZbDLbo3S0P3UzGWTR/oao9lrXOuLLu3aMxRRDF95vP66jfoIis5\nYwhxLT8WUP6jDLzSl13pdi+ou8AQoqVKLx9diTAcGTPCmsLaREHuwq5d7Rtu\nEDS1L9zeF1eq7oKLU7NmPpECONtD+uRNGKo4xTwwqqgPChCHj35+VfaWfvaY\nE3RL\r\n=y6YS\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"1e3e5bfd65c5d60eedf710c09589c72967f785b2","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.10","@balena/jellyfish-logger":"^2.1.39","@balena/jellyfish-metrics":"^1.0.160","@balena/jellyfish-environment":"^4.1.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.127","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.39-renovate-eslint-plugin-jsdoc-33-x-1e3e5bfd65c5d60eedf710c09589c72967f785b2_1619758547387_0.9347574140289716","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.39-cache-disconnect-99376b4f2d353ee0c988de92e2322405bf30064e":{"name":"@balena/jellyfish-core","version":"2.13.39-cache-disconnect-99376b4f2d353ee0c988de92e2322405bf30064e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.39-cache-disconnect-99376b4f2d353ee0c988de92e2322405bf30064e","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"375b0b6dddf8abee8bdb30bdfc8f6e49b934d5a3","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.39-cache-disconnect-99376b4f2d353ee0c988de92e2322405bf30064e.tgz","fileCount":99,"integrity":"sha512-u7JQh0uWk51p/iS0WBBVljTQXCf5rvU719eNQUW0j8bKtuN7+AM9ges6EseJUmpQUzNd1EpG35xMifsA6IGBjw==","signatures":[{"sig":"MEUCIQDf8k2Nl6iCJHsSyoOZ/MSV00DMB8OEr07PlXcd5ZC9ugIgEgJlD+SSHT43dA6T+OWz9wWMtcfjn4LbRlVxe6K2uEY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":719117,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgi8U/CRA9TVsSAnZWagAAKlMQAI7Bdc6kTxmCz+s25NGN\nQzl+odF2dIyFkDrkm9MQfdthsYy+fPaFCYsbcEjKWMVOFoEWl/qUrw7NKzse\nZQlfd9JSDvPAMnL7kmYdoBo65L7Xr/SSa1C0XWi8vCoBxrjILk5QdC83x/KQ\n0R2oN/SeUNHUo/HhgF5UJVzrYlVBSqR0yIdCb1FIHP+CzykCPeHrWwVDahDK\nCs1XKVYbCLLhxs0B6CU07f/pclMlZBdRA6itkHOJbk7uu6RHD7y+2lrr7a4K\nsutMNm26vzqz5FLtuI8QbJBUycBHKo7ZZXzoQMU1TWedWYGSSoFnHJonkFrC\nkMlrx11wAfY4B0AMMaWe3ZDbUM9P7wLb+wjBjf5oG3HOakC82BHnETy7ojwq\n8B3hNAfJi+HItKzEZBmZ4ieaYPGL8boXOa8ych5+o9IXZ2YoPzvjQZk+1mbe\nRHn9LFsi7HS3Z7Lnq08n1uOiZ9KKRqsDYX1fsoehKLIwK8NN1h4Shpq8ypqb\nNA8oqTZU4RhCSn8DFJqkrbVBJvI7u803jqIZ6KmlBrmpSVkbJ8EtBfI5nF/s\nYFVfnmiDIIvchVSprzaaraZjxBLZbZ+rElHjucWLtN6BfOXEra7x9Gz612gk\n7HDsAM6vBNAgiE+6Py+MCLg1o4GiJh151yy9tHIPHJFf/5LC5xTF/ffkKj7d\nBK8u\r\n=lz7n\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"99376b4f2d353ee0c988de92e2322405bf30064e","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.10","@balena/jellyfish-logger":"^2.1.39","@balena/jellyfish-metrics":"^1.0.160","@balena/jellyfish-environment":"^4.1.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.4","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.127","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.39-cache-disconnect-99376b4f2d353ee0c988de92e2322405bf30064e_1619772734495_0.06494365476881003","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.39-cache-disconnect-e5022a312514afedf8a6fbc2daee2a707c4fd8e8":{"name":"@balena/jellyfish-core","version":"2.13.39-cache-disconnect-e5022a312514afedf8a6fbc2daee2a707c4fd8e8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.39-cache-disconnect-e5022a312514afedf8a6fbc2daee2a707c4fd8e8","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"228a8f2cc484da418c02318ff4346dc011a460e9","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.39-cache-disconnect-e5022a312514afedf8a6fbc2daee2a707c4fd8e8.tgz","fileCount":99,"integrity":"sha512-SETeLeJ7NavHsenH8/7fmb+gDjD9cgh8FzAMltGGonLY9YyqN2Y80ElCG2D0v5QJa7BPcoY1CkZkPGS9ZyTYNQ==","signatures":[{"sig":"MEUCIEwxxYDHGlaupXWU+vZBYfJlJa+x9itI0iw8crvG4uKBAiEA38sVv2GB9W1qqlpgFzVqjZzAPc5uJ2Bvqbve7wbyYaE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":719138,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgi8eWCRA9TVsSAnZWagAAKZMP/0LXoq7UJFggIOBPSatx\nd6vE02Bp3jg1NiDd2gv3gjn0E/iIfyrS86wA/ptmeY9qweYPdL1dxJREF9ml\n0wyscUbXFa6DyE3UFaruJvCaGea4gKRHELT2y1L6M8OJ+dmurOKz/aNufbqa\nhEV2XToQbKTS9Bq6T0EwdROrFAEgwlNmxMrrFlykjBdHvGmb+eXw+iW7J+oT\nsuYn99ofEb1rJWUscq3MpsEwkSmQFIad7bvXNctTjzKmgRAbLRluwkcYV+1H\npul7YVk5eZ8DlmPhZzpQ5nQbzW2xrSYVdZu7SuZhLAd0HLrJcBRqMn+QcFtz\nDacjh7vd1+SuxXvuY2QqikyXWMOgfdY81qxqTSFCQ9C7EOJXczeu2gCDWOuC\nVcalgUxbtIJSaQr6R37Xjw4NVXU6NTD8IMh9C58xeLVfb9hVdxrRoiaWsPD+\npcJXcx02yCQXWx5w4z9Al1+tajXBH1I+nxY6ffeTIgxD6F4pYdtIdby/kjFn\n88J+mXNxg9a6t8YR554DAx7VRMvXKkLOXGrmshTO3muIqM8aiukk8tOfmCnG\nsv+3Nkms+QueZ9EUI/ZRwXAsLS+yDjX/xuLOle1fgrD2VOZtfEf4tpmHiq+Z\nkyR65LAYwQzqce6u0SMWGopnny/PRazph00nKWlQbSuOf3UGoJo+kyhlN/RX\n25Pw\r\n=0ELe\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e5022a312514afedf8a6fbc2daee2a707c4fd8e8","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.10","@balena/jellyfish-logger":"^2.1.39","@balena/jellyfish-metrics":"^1.0.160","@balena/jellyfish-environment":"^4.1.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.4","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.127","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.39-cache-disconnect-e5022a312514afedf8a6fbc2daee2a707c4fd8e8_1619773333828_0.8895612783125035","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.39":{"name":"@balena/jellyfish-core","version":"2.13.39","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.39","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"fd2d660cef07b1c15658bf3405586423852f7325","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.39.tgz","fileCount":99,"integrity":"sha512-O16XhFK32C4eqVEXsWx6EV91ixFCpZkVhhwca//9RmL53VleNsjdCab1eeJr/Wz962H+pMYEjYWVkNsevguzGA==","signatures":[{"sig":"MEUCIAtxc8FlixXnoNtDqXueVDCNfL/0PKZ/iqdduHpu/FI4AiEAnavC5SvQq0UEJYATizFgpcgT8LQYem2RAtNBaZJCDYQ=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":719080,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgi8pOCRA9TVsSAnZWagAATB0P/RTRQD5WcKwxIywn7otp\ntB8cIkOr5paApaPPDhI6G4j0lxuPzecrYeHx8oMnGoAuA/XFqWaKAG+JpZRt\nqxmR7RfDwB51CygMm2id2a+Ze6yCaucnJYi3xikNqntoeMUmmprpThkFWFAl\n+rIuTO6RSfd95nPKcySbSD5d7BFd/GvbDlWW6D690yMNB1xhxjF8/ffP5seb\ntfrAMqXuCMVQQvpXWRCyZ2leIhTM1apXiYddLUNsbRDBFxeRK5kFZy/nEGWq\ndOU0RvXzXWFvcg5Ge/Db9aSwAEch+kiNceshDN4+/IFk/v1BpSIX18cDeBtH\niharziRWKypvO8mYpRVdN6xc8gGTfm3JnZvkl9zbDvRg0PZOgoOruSefruUs\nDKLqW2hZIeA8yQspr1j60GQi6km/WPsVZmESEv/s2mrKcAQdprp/997xiMSA\nT7thxvNsBc27UFxnOj65FrCBeFP7ihQ1rscPaY4i99cADRVchjJQ4jB0xujx\njT9JHZwtlhBTUER392H6e8hJNrNm2QWx9q6yzl1AVKf2Zz5d7P3lIlbLIdf8\nqJy7rTxfEX7UyfmJuSE2qFUJqa0/12vDVSe4j8s52VAGlG6vAq2ttBZLGK6A\nFrHb4OJ78k5EVh+v6U4ptxzGl4H4Caarvbot0tLqoaKXln2Dc6D7mWclJ6CO\nmyRn\r\n=M6yL\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e6be2a0bd21627767ae30daeb6c80deffc506963","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.10","@balena/jellyfish-logger":"^2.1.39","@balena/jellyfish-metrics":"^1.0.160","@balena/jellyfish-environment":"^4.1.7"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.4","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.127","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.39_1619774030031_0.5783670546719064","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.40-renovate-non-major-39f2c0fae085b3495ba0e75571c2f2f882e12944":{"name":"@balena/jellyfish-core","version":"2.13.40-renovate-non-major-39f2c0fae085b3495ba0e75571c2f2f882e12944","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.40-renovate-non-major-39f2c0fae085b3495ba0e75571c2f2f882e12944","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a2ea40470207a1d10e826d61ea0dcab69ebd8c4c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.40-renovate-non-major-39f2c0fae085b3495ba0e75571c2f2f882e12944.tgz","fileCount":99,"integrity":"sha512-DcppaHlQ9qdZLWw0KJNdOtUGjfqLSA2SOeJ4mBqLbP6EFnuR3ecr6ez0EDEqwp56V1zcUmO1di1pA1Xmfaa+rA==","signatures":[{"sig":"MEUCIQD761OT4zUBJQVeg/h1KFWiddehcbOh+cXsYuOGdfH60AIgMVJw8Yv8UnlgZ+wPAHlQnAS9kJFU7FciLoM0px3ED20=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":719569,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgi8ymCRA9TVsSAnZWagAA9coQAIg2Iz6N7Rkix3k9nTqC\n9h0qJwjUhF+nVtQUUIHXGMCG9nVZ9oXdXsqgl8Z6kwFOcrUKu7gyinkTTC43\ncTE0YQ+hwvhp6Bb7zSgvaxeGMEEFxsggedsAWExSVHVczjKSGxAqNmsZkYRy\naViBr0rev3mE3V9KLCb8KswQyLERZjx+k4nXXponVCs/j9hdUdQyKyD6fGT0\nvjgmEFN07sZmrOO6dUYYxWIITuSKcB15MrH4Rbeqs2HD4UmaOGLr7ZG76RMP\nnBwVG4QBlSl7SVCzGk9yfFMIo30cUoODE6zs+CdENYK/8N23ZC3tYhEvp3Bz\n4BUnby9c5731+j524DPOWggsVcDRt16NsqiiytRyB0iiqplHdWoG1WMZA039\n67DXkgdAJQ8C+q9aXkfFw++eqL13oN9NuBFZZJd0cT7E+zOVh6gsh40NcxA+\nEzwpFldDxr9wfd2uBxavaK8gt3KVxcx/4dg3ULLP7j88IBISar5jtwDriXMh\nYHMp/ONgfQ5UOksAWI4KT4UFBniDi/2azwF8fq6pdRdmgWGYhkQEGShy/3Mw\nz9xlIBrBes6fjH51AkMavg5uPmwqy8vBi1A/Celbdpy7PhNX7MYIXb1PWTHB\n+YVgWaptUjwPD0ru4udXDqdHeIFsgtlCbOQFXiVMHemvYIFdM8iyG/3ZSMWI\n0AkX\r\n=ZtoQ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"39f2c0fae085b3495ba0e75571c2f2f882e12944","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.10","@balena/jellyfish-logger":"^2.1.39","@balena/jellyfish-metrics":"^1.0.161","@balena/jellyfish-environment":"^4.1.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.4","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.127","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.40-renovate-non-major-39f2c0fae085b3495ba0e75571c2f2f882e12944_1619774629798_0.19145856300824327","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.40-reduce-loggers-ad7cd22208f7017aceafaef8b8774504eceedf5f":{"name":"@balena/jellyfish-core","version":"2.13.40-reduce-loggers-ad7cd22208f7017aceafaef8b8774504eceedf5f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.40-reduce-loggers-ad7cd22208f7017aceafaef8b8774504eceedf5f","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"bed711b6b4b66937f85731bffbd97a44b09c9b86","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.40-reduce-loggers-ad7cd22208f7017aceafaef8b8774504eceedf5f.tgz","fileCount":99,"integrity":"sha512-MugCAAlEfE9PytYBy3yiVfY23H/qRtEvBboplPJ4bKJITq8X1e5WHSxuZfLHvuppEGcMDPIi7DKJV4W/e0cvjw==","signatures":[{"sig":"MEYCIQCJVQ3D+cmrAbUN0Ie6yh0Ukt2FAvUtknnYJ+NyCUQz7AIhAIICCixNfdmwh/bn3jolp4szjuoOQlosmltEZY1W022H","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":719829,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgi8zbCRA9TVsSAnZWagAA34MP/0Wq5ohAA6XztqQ6AXeN\nAirzzuNukeUxwYiPKDlcjt5Rwxlxo+ntLfnTMkb71t3VMbg4+xy5OyjoTSvu\n0UGYT4xxGWnf1PYDL/wkGSRwzCz5fOzhT1RPk3InL5s37ZxhDCvoOGjN31rh\nP+DbhsvFghBgWa7dqE6IsxeZpqMuRjCPeb0r5gG6N0pdj8BzFWCSFwU/VXNw\nd+javNC6egcuWU/YRidbFaOKypr5adOHL/qS9nwOh8BA7TEiaoGmYDNUx/Of\nvums9mTn6nkhJoemamTXa6xYAK9mXM5FjN6yICs0oc4zM0coI72mH4nlxDQ2\nuulali6X8dk07WynjKckgpdj84OHyfFwrJduiS8GbCZUkOPI+jh+rTx1wPZQ\nN0IbJBVZqjqtbpDj89y0g4mAEF5VQFgIQSljHRhn3ou+MUN/6uN9vemjmUeD\nKl3nkb05JYJAQsurDMVG16S0znQS6+oAWuA80x3ix9tucoodUgB/unKZw6Cf\nPqr4s96MFX7NtE+ptE73QNy8yq/w0RXb1jT13Dvh2EmKwvTdvgUV+K7S/H3y\n8J7qdAuRPaQbqjtBGqV+yFIduXuAzmDSvKgKkNfeSLWJt4foR0bB9Mxjy3OF\ndUek782glmL+94bVIfzSdyCa4Ads1TOQbfJtAfMqbjJI19GUy/oG5/l4WJax\nDIld\r\n=dTIp\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ad7cd22208f7017aceafaef8b8774504eceedf5f","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.10","@balena/jellyfish-logger":"^2.1.39","@balena/jellyfish-metrics":"^1.0.160","@balena/jellyfish-environment":"^4.1.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.4","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.127","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.40-reduce-loggers-ad7cd22208f7017aceafaef8b8774504eceedf5f_1619774682956_0.525617937209705","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.40":{"name":"@balena/jellyfish-core","version":"2.13.40","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.40","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4fde52b91dac9ae23a00a98041109b26d6d9c2c3","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.40.tgz","fileCount":99,"integrity":"sha512-kMeoy0wTAVGRMImQLShBG6JX8Bp/vhM/9OiDL6GieDqdeWt/GVAOW2yOxWJZTIvpwPQDkc9pg8tAgIp1RotP5A==","signatures":[{"sig":"MEUCIQDlP3FLLPnZShjzNmsrmFj7kugtjIX+9bwTqY+RNLHvmwIgMDtie6iNW79qeA2f2fPr7wpyY6pqtRY7yGPXr5543Rs=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":719509,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgi84RCRA9TVsSAnZWagAArv8P/2gg5cMeJx9mLcs2magS\neAb/ikDfqkLTyJ6tscxF9fFcjdjjjY1O+US0l2r5csXkSKbdAuRxRfLXmO5r\n18VpS62ZpPL3mptETb90/M44hEpbo90E/61yx2AwVv9g+Aa4/mpU169sp2Dk\nTHSeo59NaNYfls2PDndxB57aKwwZNN3isTb26XxRqeO8tcWI0UCsxxQPyEZx\n1s5agassfHm5bJzydj7UtzLqfZba6X9GzWkQQ2mhULstJRNNHMg/mNbvfo2g\n+sz+0zvqXIMDX/vFWkSKANbHV5gglizoB+F86pBLBt6zr95jY8CL9RZbICoR\n5CHumKHbZGthO2OKWH3oOKE/SZfmYW14JV3HrHrC+KzUT7OmWmunVAZfFR4y\nvq1Imv7MAqROmmncbcr7PDB5tW3dra7wi80pbAR+BiRzPuyDV8nvXbjUcHGH\n9SCiqbBVduiFJIRwIogzG3fiKP2dN9Q5OP2gyH2kqlE4T3yPzDKvavTRHB26\nKdU9DZyKtXBf0jm54gp5RCyfWxzt56Yz6ODAmizCEmfkmld62StCRIyXR2TL\nrQQDLRKTLfrq+TvDDM7mquvmkzZFuniG2KES8PSzsIsQtm/Ag8mrS8Mvxd2f\nk+ljIjOecQV6Z6v5Y1hnMC2fFQ2zd7JmqIkp1Bz1E7eQZ9m2S44wcm9cwjIg\n8C7O\r\n=RyzI\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"946542d6df7c009b3c6935ba4ee0db204d7c1a14","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.10","@balena/jellyfish-logger":"^2.1.39","@balena/jellyfish-metrics":"^1.0.161","@balena/jellyfish-environment":"^4.1.7"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.4","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.127","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.40_1619774993410_0.672000214766487","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.41-renovate-eslint-plugin-jsdoc-33-x-4bb7b6dcbde311619e7d40f9cdd00551555fa302":{"name":"@balena/jellyfish-core","version":"2.13.41-renovate-eslint-plugin-jsdoc-33-x-4bb7b6dcbde311619e7d40f9cdd00551555fa302","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.41-renovate-eslint-plugin-jsdoc-33-x-4bb7b6dcbde311619e7d40f9cdd00551555fa302","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c865a4c3eb63a483d62752d80bb2d3500614f963","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.41-renovate-eslint-plugin-jsdoc-33-x-4bb7b6dcbde311619e7d40f9cdd00551555fa302.tgz","fileCount":99,"integrity":"sha512-fhZpmPGpNjsHRmr7KrjubSPK2rMpEsGi2BDNSAcddH+3omcXFtu80TqUPLEI6twyYhIBlT8XzCtgdlgQ8xkMtA==","signatures":[{"sig":"MEQCIAuy/KKzXvicmwBM7YlG/telm+2ljPTA57oTLiu29s2OAiBa6MCudw2rCZlUA3UhDKfUv4nnbUv+JJGUwZYHIKfclA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":719991,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgi889CRA9TVsSAnZWagAAcjgQAJEE65vvNMubt7i1U3nD\n1DwJEAOxbJVMdkJ24T2sz4sGlIvtTVcaQRFePqpgpfMQaybqC9okDyb8BDWd\nPePccx/mwtQdt2hTZmNvtI4VZApzB38WJWbztwYTovqsf2LDonaUjMdZk/IT\nLlMDqqwNNNGTlb3sj1r+Qz73spdysUrdSxlAo7j1A34cM+7W72zBYtFIvLYh\nRSTdyMbTUvbRjNowleJ5VCzISj5BQXBp/g33LKnRJ0WtuW527IKnnVYxW3Bt\nqwdqYgZVVenb0rhZNqcxF9ZwBeXMpCnxrqIeTkl5PgHhw0dau9mleMygysyg\nTXc4Z8U304h/WDQPz+v5hXfMwrxs99S5lDf7js3Ad0xGIX0W1RLB6N7idjAP\nimtogdsHTPW/kUah3VrKX8wtuR9JSvi9IuyhNfTLwiDzuRMdb8iavshNZZyn\n/gFdrQNZpeOWjmFolJSOR9K7Ak0P24Dc5KYKd6NvDi1KdN/zGX9uKXq87UCO\nBI6yHzfiMUibD88HgaUFqF0fBhzwyZPHTbEw83CAtJLAoUpjfERLbM0Q39So\nd3JrUngwqYzuwgvrpMUGigLEClIvRxuKaOV3x4wJXvVci9XHWFAQ3cMdoRgJ\nb0Xl14ApxuWSG0JRzrCtzqLS0y218tyeO4o0X1thNeomGsOMz9lPwRm9SsCU\nZRrG\r\n=5Lkg\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4bb7b6dcbde311619e7d40f9cdd00551555fa302","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.10","@balena/jellyfish-logger":"^2.1.39","@balena/jellyfish-metrics":"^1.0.161","@balena/jellyfish-environment":"^4.1.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.127","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.41-renovate-eslint-plugin-jsdoc-33-x-4bb7b6dcbde311619e7d40f9cdd00551555fa302_1619775292800_0.16917522673473284","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.41-reduce-loggers-a9c119ae240f07a1d6183b2d1486bcc00c863502":{"name":"@balena/jellyfish-core","version":"2.13.41-reduce-loggers-a9c119ae240f07a1d6183b2d1486bcc00c863502","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.41-reduce-loggers-a9c119ae240f07a1d6183b2d1486bcc00c863502","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"612c3f9fe3882a75fcb7c48643b70a7d22f949b9","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.41-reduce-loggers-a9c119ae240f07a1d6183b2d1486bcc00c863502.tgz","fileCount":99,"integrity":"sha512-6tX1ueLmvJt/1TsGaMsfAO1wX2BhjeAZHDwenhfzvJLSh6Ct69A7MMC3kEM85C3TOaSGSCmabmgQbsljhGPGwQ==","signatures":[{"sig":"MEUCIQDB3Yd4L0ypbSi2Gh7BazB8W84JvIIIsefPUaiJ/c41GAIgfjvU8LH45HxsK738IE5W3RZT2fN1gMk05B9NsaBGrZo=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":720258,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgi8+ZCRA9TVsSAnZWagAA4C0P/0F8gNUR4ASBRIAlarTf\nTDSgS4O1yLJ1JMyg403r8qOX4Vt7zjNoP/ZaRUAAsWQ1Jsf4dd+bO0hOSdqV\n8FIJtEVzqyr+MsTUkqf5zqOvU7t7mOqR8vKd++FKvSh8oB1EYs9wJtQF6/wc\nebCgldZy5glW000qPpmZ3uj5lEGe7BfyQhbkbrEQDGwyn0h3+vTDAzwC6jdY\nrYfMvQqkQPoK6f0IopdUJ4SlnhsK+7sMnm1je4FjPwi0TYC2EiW6xJqW+4+G\nBIc63mfn3Su4LQNP3FIlalbWimR/uI7vJTaRrmy7qHk1mlRqrv2G+OjkTQms\nYJvq+30ID5+0rXUlkijC324b9rGArWYWs82p/TapXC7VEDQpgvyWxq8RGWBs\nUD6wC9FzxeDn7429OfN+nGv2heRTmUlZrvS8O8CrcElaaTYMq0mzJCMoIOW6\nQ2OKcpnhCqlW7MqKTtyxchx+BVW00ivJ6KPiHouBIeSXaS23zVdqv17lCaq6\np3/cQRm2laTunBIeQd6n7BXPCHjNHZ8Fljm76X0llwVv2ebNT5gtzrdZaBMO\n3pT6W/J94HYvS7hZ+HoPMB5R1Zi1A7mxXkRNt4kmEBr4+rBncLucGHYpEROg\nie36YnBiY7tl+yK35RJiW+u0mLYj7LuAj1CZ7B2CZ32RXzIvwMyPcbpzfffH\nhOnV\r\n=d1DV\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a9c119ae240f07a1d6183b2d1486bcc00c863502","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.10","@balena/jellyfish-logger":"^2.1.39","@balena/jellyfish-metrics":"^1.0.161","@balena/jellyfish-environment":"^4.1.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.4","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.127","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.41-reduce-loggers-a9c119ae240f07a1d6183b2d1486bcc00c863502_1619775385358_0.395489689310339","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.41":{"name":"@balena/jellyfish-core","version":"2.13.41","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.41","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"847566f50be9b79e731637e8a4ea74b25784452a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.41.tgz","fileCount":99,"integrity":"sha512-oKq1XujZeVOoaqLaNlPSMjQB7dmDgAMq1MSP1q+obiyQj1Vv7NlsRBXuHoosFNr4S+TiBHGqqf7PAhkXwVpivg==","signatures":[{"sig":"MEUCIHh7obwg0WqhPVzU5fW22/usCDgTa9rdyLMWhtmN86sLAiEAlJQ6gaEkUqr8PU0Y/sl5CF0Zry1zauBDPnMRC43fyK0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":720202,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgjBhWCRA9TVsSAnZWagAAcacQAIZLFHwEWIPTW6MpsSTm\nv7HzkJY0cytDzKwGORyi5h0d8eDeO6u+SA/UrQ1DUnzhoul+RCTElAFix/54\nEmxmE048Xf3u9RCkoH2Rgy2ZtpogsfVAdyLj9egLTvnze7NxzqcesJyItEIX\n2sZSG6KhS4+6fIlAZyUdXwxANPw8rlPP4NLzvyRfL2P9YO6aqxO6sZpX4SMY\nfyqHVAT3SxwAl5/g+xOD46GdXPsOt2dK21wKh7SJjkcmRo+PHGte7pbKOM6K\niUpWNAOS9kWRseQiwQTkIiUDIvU7ppnIxBkpJDp75Pya3kds87bQLIE8cXGJ\nwffuaHOyyxEP9M/mvFDLZrbH+Rj/IhECpAl22gSAiMYjuFDctgrOmi/RyZLu\nStK7O66pzsCwpYTStnI8oULUljBpE9wyhFecs/YB2jokinMUWsaJNCcPUbfB\ncISAqfCNxvI6z3Eb07UgPRd7zrH8TUC3WhmK0gaunpy11o7FreIkpF0Mp1Nx\n1lDyaXdK6tJCxH6+cgOAwPXwHe2znWazLW79KqP3KkdpiVdTSUHvtvbQ4GeG\nCWO/TIgWYOzCoW8Sn6iAwIuVYuBmxXlFFcCiBA3a0SjIVgHrOdI/93ItEgeB\njY2i4CEus3l7/QyyzqRu4iZrd4Y+cEPvJkNNgxgMTL2Tw5HIX2D+j6AwCFz/\nVNDp\r\n=VyGK\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8375dbfb91db026e1218d82bde706ac2827263b7","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.10","@balena/jellyfish-logger":"^2.1.39","@balena/jellyfish-metrics":"^1.0.161","@balena/jellyfish-environment":"^4.1.7"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.4","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.127","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.41_1619794006107_0.8439746685665797","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.42-renovate-eslint-plugin-jsdoc-33-x-1a446eabec41afa06d49b85d494e4acda66472eb":{"name":"@balena/jellyfish-core","version":"2.13.42-renovate-eslint-plugin-jsdoc-33-x-1a446eabec41afa06d49b85d494e4acda66472eb","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.42-renovate-eslint-plugin-jsdoc-33-x-1a446eabec41afa06d49b85d494e4acda66472eb","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"08b32aa5f371dcfdc887fc03dfd21f4f3f71697d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.42-renovate-eslint-plugin-jsdoc-33-x-1a446eabec41afa06d49b85d494e4acda66472eb.tgz","fileCount":99,"integrity":"sha512-RyQtIPiGDU2kZPgNyiQ7dT+VNHY7JiNcyLsdeTV0n+SCOnaaMIV4XeuP2lgWccwVvo6gQHaCn5ZA2m2GLjf1DA==","signatures":[{"sig":"MEYCIQDQm22TwM2ZHKDHD0Vm8Iz+0WvaVqUQwSkLFHkupdwZeQIhAK166BnRpcxmy5rhgsyeGj/DBeJhDk17xhc41bcdK4az","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":720684,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgjBnJCRA9TVsSAnZWagAA5lEP/1oHnEGnC9u9MbK8r5/s\nt2Hz0H6oxd2o96BPCt35mjp5Li2HC6UvMHnCHI6eTEayButZL8ldn6q7zjKE\nijD6J0NFgTDbZ8HP6dwyVc1MA6axpt4+tKPJszPCTSts1Kd29/t1MjdSTE7I\nA8SfJku7GCl4OmIhLYbcyJGhcReyJrX9zwU9H6/E3CC5cvL0CH15MvSm3F+K\nN48tS11vuQNOcxQHQ+MrbEHXhhYt3RQelhxN47OQA/ilQxyignpt42GJ6SGl\n3rTpQ/jo2O0TBuwCMV8v0in1ANsgV6zeQ50YXRDa6fytMLB/jHUSbeFRpeXh\nvGzLrJHeOUDY3GviFWrLsYIP0bePOG8RTEWuSKQ7EFbOplX2znnLUiO1MPkK\nZN9O1RmEchbOG+DpL6xLEbZd4teRP5QvVNK+I6SZHVX636XARoE6pLxCgYKN\ncRWYakyOMg4biQfAn5MKXQa7jOUqWmaSuHU32BWSNQETGLrAoMBKyLtZNnD7\nXe2BOjszqD0X381rmpZ7KHNMNxro7994KDHVwtMWvIXD/n5MrxqEZSVRLwsy\ne9efj+ltFz6epiau43T98dwRlcf2994BlQGufpCGtnSHc38Ci4lHM0eHU4gc\nGN3Y+C8U5Y9XWOjyIAs7P/INa4Px+CDITnKHWwu1aLxndPU2xiB4UKW9TlUn\npbAQ\r\n=ZYx5\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"1a446eabec41afa06d49b85d494e4acda66472eb","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.10","@balena/jellyfish-logger":"^2.1.39","@balena/jellyfish-metrics":"^1.0.161","@balena/jellyfish-environment":"^4.1.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.127","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.42-renovate-eslint-plugin-jsdoc-33-x-1a446eabec41afa06d49b85d494e4acda66472eb_1619794376601_0.20420758887324597","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.42-renovate-non-major-daab7d9ee5a43fdfebfccbf27bfbe6909c038137":{"name":"@balena/jellyfish-core","version":"2.13.42-renovate-non-major-daab7d9ee5a43fdfebfccbf27bfbe6909c038137","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.42-renovate-non-major-daab7d9ee5a43fdfebfccbf27bfbe6909c038137","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d91abb112fcf198ddb1c49b6e44ec53153fd5a51","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.42-renovate-non-major-daab7d9ee5a43fdfebfccbf27bfbe6909c038137.tgz","fileCount":99,"integrity":"sha512-uezKs3Ta7SVchJTEd+bMwtysc3a6muMtqlyrqKuZyq082iJ7zyhq/4UQMmLhtBCdRFAoIKRrXDBbXRNYXEX4nA==","signatures":[{"sig":"MEMCICNXuKcnUWHs5YvZC95luh8MHCCeIMlP6QyCbVwthEa3Ah8fDrIJ35K0V9xhsjp0n1VzmGRPBgYUjFAesO/sbJXI","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":720687,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgjDiUCRA9TVsSAnZWagAA+M4P/R62WU0WvXGWzvzUmy7H\nXJiCChBvR0TGlCjfKY7K8gV0qMWVRVfJNguwNop4FjC5dteSoKnncmOF5x4g\nRczVPlsgEt1S+2eqJvTe1lrs3rgGa835P47jWMYUAJsq03OT0khzlE6Q4XWi\nPty892X108EJp6QdS5jxugXQUI46iwZ1G2zoDddVX0ZKr7kWTNsg8ajhPace\n0qv+VhlGxDqWK5KEbi9Lif3uiZ+ulzxwdqHsbamgAbH0wJx/OqQv/yr0iADI\nP6FPwIpLigNSgR7/cJrqnEq3DUF9yF27PX6mrE0jC8qjeBOo90gGu9/WZ6Zo\nFUT/j+AsZCKEI/TWkQOldHP2hv+5oHn4twPtohmD2Z0hi51NQk06Re/EgSnp\ncIab2Oh5JQCF+V6eDrxSo+FKJd75pzulg1oecc/dTesLOULJzgjqofOLQmtI\nLooyOfqZwmEVBeDVwoDRTU4FSYLaga96D6LtewZApY8vZk3Qp69h8vlRvRzy\nUV0nQnwCJr0LqSPRueS4EzBjyzXuPmAcO5+v1JGFZsKzMuWxZpiiFF6+bBBM\nI/fo9kVlPO4jvFwMZZ3S7AaXnSwwkdLhX/vUdlYZEPqX1vY9MPsR23pzK2sE\n7/FmMO78ms/1umT6zbkR0bXmnEzUrBmcgpoOfeaUmDo6+Yn+qPcwj24GLcU+\ngEzp\r\n=PgBe\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"daab7d9ee5a43fdfebfccbf27bfbe6909c038137","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.10","@balena/jellyfish-logger":"^2.1.40","@balena/jellyfish-metrics":"^1.0.161","@balena/jellyfish-environment":"^4.1.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.4","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.127","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.42-renovate-non-major-daab7d9ee5a43fdfebfccbf27bfbe6909c038137_1619802259527_0.3339830529106642","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.42-renovate-non-major-46695cb1cc30330cc208f5b423339aa7ed539d20":{"name":"@balena/jellyfish-core","version":"2.13.42-renovate-non-major-46695cb1cc30330cc208f5b423339aa7ed539d20","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.42-renovate-non-major-46695cb1cc30330cc208f5b423339aa7ed539d20","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"441ba4ddf27c80f82c7635858a739c16095ee9dd","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.42-renovate-non-major-46695cb1cc30330cc208f5b423339aa7ed539d20.tgz","fileCount":99,"integrity":"sha512-inbTVq8vP7vmxgyTt8AG3xIAJk9eCgdTSfC3OfCOvimCk+nHPyD4K5e7LwAKihLq5DtA8m60X6qX7lePfNPyDw==","signatures":[{"sig":"MEUCIQC13Fn95rNkgNFTbQ1GyP9pIAvYVKCJJxD9qMJlcv0aMAIgVhZpHe/ZfQ+A74lj+ZeJKrelF31rzFFmgI+D4senr0g=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":720613,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgjDoQCRA9TVsSAnZWagAAe4kP/j03/Ddu7LrHyCyUp8zc\n47qE8/Z5He0Dj0cYoLNSbuq74rjem/IUaU9V5lMdY6TGs95N0j567NseSLuv\nx2qHQKDdlp/miwSRF6L0ABc2HVNOQIJbVu1QXTqLpy6yKSYbZ/MCqJtMs4CJ\n5g6YixOYbzWv4ZpTWl/64cyMQjOXqlbanQpplFhtsYTuMI/7oV/YmUQLUpgM\n9JnfHRvW/742hd2P6jlvZhATD5e7Xc0C0yLxPJrfr2BeRhL28C+FMTx4ZNmF\nrIrUz4N/iWSySYA2jW0fDexPZ5ntQ8+SOVB3cFHDuv+diZ/u5q+RicVjDHwL\npHpESzFWpBCSuZwL7zBTZltiVD4XUnXtoCgxM7dWpOUslKERDmy/rZ7Hx6vS\n96tycbxIoKT49PQjO8BtHaJj/TDZD2SoTmrlTUYu0c3WZDtYPwefffeZu0nk\ns1svfWghYkNrsJAfSV/ZZvmRKBcWGC/xLtsRQbHbAVNZorQWGvW42GwT9QdD\nDXDzM3qYMcjFgm/9qJ7FUTSF+TJ2MeAtjfei/tCNm9TP9UmEXdpbJnfeqbDy\nyjtBWn2UZCWigBIBfXQ0qcT1DdlhDVNk5+Fy5VLe0PImNO97HYlKgPiy5cud\ncBqkXbCHcGjbANNrIAf261z8q9qSxTLQkFVI30FATgddF+OJ3b1j22gqwFPl\neA3Y\r\n=ElwP\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"46695cb1cc30330cc208f5b423339aa7ed539d20","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.10","@balena/jellyfish-logger":"^2.1.40","@balena/jellyfish-metrics":"^1.0.162","@balena/jellyfish-environment":"^4.1.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.4","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.127","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.42-renovate-non-major-46695cb1cc30330cc208f5b423339aa7ed539d20_1619802639812_0.3543378934196155","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.42":{"name":"@balena/jellyfish-core","version":"2.13.42","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.42","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a347051a7a2a867f641c0366bcb93ae20daaac2f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.42.tgz","fileCount":99,"integrity":"sha512-kvbycbwJ4WVXVydENkyX9Ebx0jitRYeHeXBNUrgHnpWfWtlS9kTEAmSLK3pMS+tOKRkVHpctwG/R6tWGaPNgXg==","signatures":[{"sig":"MEYCIQC7Qr++cmGyf5tjaytWNGoGXEjkPF+FNYlpWqlunpYxRgIhAI/OI5KhaShH7gDwUjaiCu+1x47xtG+bWvQceuJQi/C+","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":720553,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgjDt8CRA9TVsSAnZWagAAEswP/jRUhmYcR67Ofa/b2SJj\n3bw6VnApYMvm4jPrzPPAtlwB/pMdwwpgEk7AmOGXOX7qQk4Wp9s6ki1vS26g\njvD/+Y1fCmmxM3YO1wdnlw0lTtW7vPqPTI79VDZQmB1gtIGM37Gm7eKYk/3b\n4bQ4k+4+fCqVZqeiIeJHg8prAk9UXL0IPG2nN4PyM43CCp1ctVhvQGD65QD6\nbckPsG9mF+336+t/MqS2LS5rSextcIVCveTho1F2iY3ehj2lCANY1JX84ibj\nGv4feRtWT1S2U1IRdcIIPV5XHmgd5INoYGw84/vRUwO9allUT/AxNwpCmspL\nT/k/GI27pfhDAyGC9LwWJVY4OnFvNDRhKk0OcBu/z9QwRNC6mXznhJ+a9E9A\nyd7Eu+3i0hhSB5iWryvEU1I+U4ZJmaT+gV0EkXjaXea0sysYgybVt4uD98er\nz4kSoTclNdvK+90gSNOP5Ow2srwb0wNrzBTuvQ1gT/7ouSbhJLmdM5v1PagM\n2Fl+S0T0fhZejHnAqpAf8YuOpxTI7F8zVBh4AmnHQ2ed39N7F7MJ+fDwm694\n5Uxl1u94pYJd2u62dP3uLjdPLydiA4bbuH3Kt+2ebwO7vWzOh2NW7ajT662u\nLSt7j6TT7BN/P4ZRAgXv9OX8jf2mwOmayfeypMiaDUZZ/MMgRQET5ydW+r8b\npIGz\r\n=fm9y\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"5821f0a3df5ce5aaae1d76ea7de20b6e47af47d7","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.10","@balena/jellyfish-logger":"^2.1.40","@balena/jellyfish-metrics":"^1.0.162","@balena/jellyfish-environment":"^4.1.7"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^32.3.4","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.127","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.42_1619803003651_0.7929257843219513","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.43-renovate-eslint-plugin-jsdoc-33-x-854e6aed2a18b9a6c86b65dbddd72738f98c8037":{"name":"@balena/jellyfish-core","version":"2.13.43-renovate-eslint-plugin-jsdoc-33-x-854e6aed2a18b9a6c86b65dbddd72738f98c8037","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.43-renovate-eslint-plugin-jsdoc-33-x-854e6aed2a18b9a6c86b65dbddd72738f98c8037","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"322fc1f1d76ab8fa9c0ef11baee78423ad9c9d7f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.43-renovate-eslint-plugin-jsdoc-33-x-854e6aed2a18b9a6c86b65dbddd72738f98c8037.tgz","fileCount":99,"integrity":"sha512-qBOC3ejdfUr9LA6hv4YqVhQSPvK7/L9E2ZArKm356B1/cdF4sN5gmYfMs47xRmCNexZU2e8tYhDQ9Z6XNBC/Cw==","signatures":[{"sig":"MEUCIG7/VaMKUFzKjF+uEFB4yO1pY7UqthiLGdVt0h4vOp48AiEAhljalIeLR2aNjrjIg1Z8ONtIy0uMXmJBEj3sfzhsTd0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":721035,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgjDylCRA9TVsSAnZWagAACXoP/iZEOK8ep94sW5G/7lbp\nqKkercmIuZwuNF1MptXipyusk8J5Aja4XfltbF5e7XhpS9U1Yw2GthhZOuu4\nRRI8Y6AWySyqtxG4QFNQ0YdQ5pUyqWwlp/NzeqPkQmtFe7Sx6nJ0aSigVQcP\nVDxDsN7zPHpur3dP6is7YwwGlvNXVQf4mQ0Q+rrPmqf95tdc/KuYk1g9k4tk\nESzwX1y5oaS93vnfBD1ryommENDN+01VxLHEgzhiWNN0lvE3Jys/DYu6bfPE\nUXaEwJvFtESgS0G4q/hIOXYZWEp4jPR9oirduOpMVuZDDl/BZijKZh0g1X00\nC4Vq3zqXNhShVAwhpeBGR5Mb/u/WJH00WXnGR1rXkJEb0syUopu7wSlqJgRe\ncafEfz/zltjfgwpPsHPjRKkGcpMJVM8XUASYkMJ+NKfXmPuNMEXZy4Zgp3tl\nYOCfktRtmFRSgvMPDiOMJ9Zbi3RFBnKpPjABSkQLaZADyBuVOx19QVBTLeCP\nW61O1N/DUgVuVTsc9WuMGxSL3xmVrel4ffbUM95Jk6nuPBYGyA0Cki0MON5k\nPg3r3Y3b2TWg2CGxaV3R7WrUw5JxHwTAqt2PhYMHWLjHgeF8wQnPFC1JVYcR\nqGOf9wE0XymcLCcr/LqT7fG6l1wdos7yyCABfMChFv9Dp5JTCsTthDeqIgCp\nXLkK\r\n=f57C\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"854e6aed2a18b9a6c86b65dbddd72738f98c8037","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.10","@balena/jellyfish-logger":"^2.1.40","@balena/jellyfish-metrics":"^1.0.162","@balena/jellyfish-environment":"^4.1.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.127","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.43-renovate-eslint-plugin-jsdoc-33-x-854e6aed2a18b9a6c86b65dbddd72738f98c8037_1619803300656_0.47560837773708275","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.43":{"name":"@balena/jellyfish-core","version":"2.13.43","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.43","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"84e3311707a3ed38d857e7a23aed1cbba6017f65","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.43.tgz","fileCount":99,"integrity":"sha512-TldT8C/ILWW72X8jOdYUWhYyuNOzaluAVqL/PeMY6IaYc+lheLzOBacg331IoO4RCjPPlSudUFnaEO3dJL+yfg==","signatures":[{"sig":"MEQCICavErfsnJeF48c/Hqz5uF+DloI0xLjgSjSAdz7JXcP5AiA5SD3qRdO3ApT/qMhzrpcnXxIp67SzJmcJOVCoWXptKw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":720960,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgjJNNCRA9TVsSAnZWagAAHjYP/3Rygvlk721ObS0H+Eo1\nYs/eHgLBXqCSl4lucpma6CFkb50EckVe2t01JYvtO6EuFH7MxGAOITTizmUT\ntt1IMrllriXfBsE1DJID6jnCZWsWrlaYj+teFoipDlSfqepVHyTFHuZDk9I1\nOxi3On76ZeeYOH7CUoPBEmrqW/QYUQAsfEjfHufmPvJ3LNSOWNH54u7/DrdY\nxIsgCyirWBIEJEDIjJXl4ZhiQa1MLijWVTjaZnaiyIraVLGfspOGJLJm7k8n\nwSjjNIVm4Ih9Igx5SsLTnM96oQ7LikJBEr7Yprw1PulB8XpYRP1xLFTMT/JN\n0Aq9KqNpaC0XghfBBMTjlAEUQlHx6atLeZcDJMrN/uCemxziJOhAkM5QQedP\ncozq/aRzOP8URBJ4N5Ff2VL6KiQwU1LS1zVmSvoPdqblcDEEl3UfeVSERGCk\nJ2kix4GOvaxBMFRxazOwulWB7w1rkTBRVU/Wlc0yrKmEe9pJwmusiY/KRweY\n3NEjKHeFcEIh+CclUOG+VGmhr92tp6oSahoTlx/9pEsQ7IAiojdk9TfpFbX3\nNhlg0GpJqV5Oslown0sAxaTTeJ9Hotv63n78GrWhBeXe4Z5zDkG7atW2dsfS\nPM94X8iTg/WKJ7Tymueac23A7FWlW25k/nu3TjrZ6qf49ok3ti2f7dU4BQZp\n9LJQ\r\n=n44v\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f912c70effb4705a0841dd7e805c519dc002e965","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.10","@balena/jellyfish-logger":"^2.1.40","@balena/jellyfish-metrics":"^1.0.162","@balena/jellyfish-environment":"^4.1.7"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.127","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.43_1619825484429_0.2863161939462333","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.44-renovate-non-major-6b2712dcfb02adce15da549beb83670e2e9f22aa":{"name":"@balena/jellyfish-core","version":"2.13.44-renovate-non-major-6b2712dcfb02adce15da549beb83670e2e9f22aa","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.44-renovate-non-major-6b2712dcfb02adce15da549beb83670e2e9f22aa","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"51c2a77b80b1aad79806c97af38ff7a92f4e66cd","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.44-renovate-non-major-6b2712dcfb02adce15da549beb83670e2e9f22aa.tgz","fileCount":99,"integrity":"sha512-SfMRIAyR6BTgl0rcuPDdWi+Q51WeBBE0pN1SgG97+ssNYkcEtrHRtOAWeQA2vVHiSWVmO3HiBGf8+TaYxXBccQ==","signatures":[{"sig":"MEYCIQDDN/9FLHx9vhISplVVnvlRRgHXKjrP3jm15MVtnDf6oAIhAKKlpsDjoPoD92NNzX5FIh5aFyvlJOkbleAYfSEy9kED","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":721371,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgjLKcCRA9TVsSAnZWagAADpUQAJnyhvt4W0eoN7STvHus\ng0Kx2Z2CFnlgMSOSIEPJHQRd85VeDVSNTZAl2Yjp9JdyzMLMiayXw2DIk/C5\nSrGVhvs/JNoEOSJ+tEFb9LZ5U48izW2Sj8CNT/dlbOEzoM/GVUQMy8kUjTUX\nZALHMVY4FIvOHEjSLtM+m9ouV+rraT+ySAt/nOsoIzHAQHAv6VFMJLS7TOVa\nyRr6tNgyPVlXsofhjjPWvyHKiJknvT7JarE8EWbjypq3IEtHUL5z546lqqyE\n9Q4i859rDlPyzZuSmZCXUP86jF01Lp785smeEad9yFb4UrWFQCyc5dYOtYts\n7Z2KqonMsZgfwTdkgVGJTnF5tns7d8CUrutrwbBaytQY7Z6xmLZRW+KVOSI5\niz11ftVVXqnX2JmiEnnsKR6bjhOCaytXcNw5ec0IofFTUV3aELLRWfWE3IRi\nKVGEUvS8VpKnW8Y9qs+nGSMN/qPJWYSY5/qwvnuiyDka1FA+x+CpB86tPkad\n7bO5hh3VZdty0iGLk7+xAJYKGkmcCDzWpN4wqTxkYPmwSKOljGelS9zegqp6\nTgwmTo1Y1IVW2VhVrZRTdj3Lruxe3iuhjDrhq2wme2Fdxe4aveoGCJhV44WS\nU9P/G7tMI13NGfxUpBtr0uJMeDvCC3A+8teykHqkKHDzJU3VJ9jw0Oobb649\n0a+h\r\n=7E/m\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6b2712dcfb02adce15da549beb83670e2e9f22aa","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.10","@balena/jellyfish-logger":"^2.1.40","@balena/jellyfish-metrics":"^1.0.163","@balena/jellyfish-environment":"^4.1.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.128","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.44-renovate-non-major-6b2712dcfb02adce15da549beb83670e2e9f22aa_1619833499898_0.8487176081129226","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.44":{"name":"@balena/jellyfish-core","version":"2.13.44","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.44","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d6042ca86d3209034fd35bf674b93a138bda6913","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.44.tgz","fileCount":99,"integrity":"sha512-mho8qtfI8Y4/3fGjUmESqjSPDuxl6D95lRPiKyJho0GIGdsml9vTtLFsHzMcReLsOD4T/QQlXHR6h3w3nI0NGA==","signatures":[{"sig":"MEUCIQCJ3LiPEd58u5JfPmGoyF5NGkXGUGisfbsDn/DBX1ffSwIgTlZLdOA19KY60gkoumY63eDrtOLHTE0qVGeLWdprEe8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":721311,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgjLQGCRA9TVsSAnZWagAA9dMP/RsB7BG/jSguZ3hTymWn\nqbh8f8bYUMKwOLeV1V7HBjheJfQgfSrm0WHgycYHHMXcREOeXBF36wqpMRIk\nNio9xW27ml830xRoQyN+hFY43XGlZ4K4yzdWaRYJk4wHNFB54l1EYXq1Xbeh\nmWZFDa5HutkgfM1yeWI6G2QFFXFrjMu6onl2svuOhNHjfrwXi4H3fy5VX0WG\nYfGg4qvr7BHJhCq4hTIRzwt2NTG4+Hy6NidFgoqBxc1JE2GUCllUC+cJiYzG\nVi4qipCKdZ1+w1GupjmTQlmj47wD2ID2UYOHL675k6YBbIfGiVgvIL8j97Sz\nE7u/q8O5dhGRfsQyOpZ5fxJPXOjt+fplO5oLjwBfjmsGvdW7SMCq/KEQXSi9\ntLfo98645ne7tHnj/dFF5SQpzXskDNS9OwTPCtbMIG4zm+CgC9Wed46FbP8t\nrM9d/rCYlP15h1XRbDhaFL5PFgq1Kj9vUqk+57WG6v5SEVNTsBhwtUu/v6yV\nbDMIDV6XQAZtV6sWo8I8awf2Nb2QAnzrKin2zvslQzcieLKKh4MT7TH2osLJ\npFaaoBrQ79X7ItvUdgV0on/vc3OEcnhhUvT8L1N0dKrsbrCemB1+aH2Dbvok\nDxN0YX4PP4WTDEAY/lF6ERwEGzN2wxMm8eyElg2Fzg37TUwB5EdGqlKSZkoQ\ntXwK\r\n=Oyrp\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d88fa280faa5d22992032c6f000f9257017af553","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.10","@balena/jellyfish-logger":"^2.1.40","@balena/jellyfish-metrics":"^1.0.163","@balena/jellyfish-environment":"^4.1.7"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.128","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.44_1619833862374_0.20756379003703462","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.45-renovate-non-major-596790d7fa25ec770948ad2b7c70a822637724d4":{"name":"@balena/jellyfish-core","version":"2.13.45-renovate-non-major-596790d7fa25ec770948ad2b7c70a822637724d4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.45-renovate-non-major-596790d7fa25ec770948ad2b7c70a822637724d4","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d87acee5bef240dffcb2eb873c2f17a40120619c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.45-renovate-non-major-596790d7fa25ec770948ad2b7c70a822637724d4.tgz","fileCount":99,"integrity":"sha512-Ze5cLucoWrkYrgMQibcuYFvM18xxmMxnrTE6rQHw1QOf6FYUZbo4FWB5NnBFjMm92VvaoWnJAfbCxHq3IYESkw==","signatures":[{"sig":"MEYCIQDotK6e/cA6ePuIptWkNdnVbiQ/EzXOmLH7ZiBwvfdrMAIhAPuv6unR3uJdUspABWrDx+GyXkerup+CzUfqen1yX5+l","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":721800,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgjNGTCRA9TVsSAnZWagAAObQQAJt+aHqFphDefL2JbjO0\nP5o2C9BMqbdzmEgLpdXTFflUxxQj0ns0uj5eL9LTjKcF7RoTHXarekaO/dNI\nNgCb5BxVbiumWtWLU+9Ooh2aOHnroY7PlugAf2smBfP8VChGXYyu6/h5/j+T\nmol5ICKH0rwcGiHtlXm2NwaQeTDgCqN9c/MsiHfsOBKJJZZ0g/iIVCYUMOlv\nEQLDgE18SjYYEBNBxy+m1a79rEsI6XDwiQrlYkn2Mgz4CxQ8qmuD7GrYeUcL\nc/fWBErdsDw/jYMBB8jYeD+snYbeKL0LzoJcl8Y2ztOsEurgbAs3V4N3HiCi\nkHpbBRZ+YJ2hgyDr9Tu3+DijTgEqG6ZmuMsPlZFDHLK31eDsMCRLI6P+FlYv\n13e4e/o4T0AZswrMjAXe7zqrz3QNGoTCzhcsfku4Jcz5scqqvPKRBYc9aJh9\ntTwcI+RwO+COunLAsmxYiZBOIRHEybXi81qp5TveDE2mZwO+yyOlhr0fh13/\nYpmDnrZpyTVH+3AQQ9dPASILAWeLU6bBSet4Mw4GN04nu7gFr+4myW7ND72E\nIdj4/e7axn3KRM1A5coLw+bXJlj2KqTXxC/9P/JsH01yfZSPTAYQc7sp3aG0\nsDLOFzaxda4Ds4RtXNf2+7NQux+hogUmapW6DM33/diQ4vxWDE2+CIOc9Vc+\nvawa\r\n=NiLR\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"596790d7fa25ec770948ad2b7c70a822637724d4","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.10","@balena/jellyfish-logger":"^2.1.40","@balena/jellyfish-metrics":"^1.0.164","@balena/jellyfish-environment":"^4.1.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.128","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.45-renovate-non-major-596790d7fa25ec770948ad2b7c70a822637724d4_1619841426897_0.2569289111267634","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.45":{"name":"@balena/jellyfish-core","version":"2.13.45","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.45","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4e7b89c59d01c722b3f1b57a11360db56db7e305","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.45.tgz","fileCount":99,"integrity":"sha512-q+Rjv2p6pJJOHy1QqMiDckbPag7GqetYQNmYsxJHWU04xETT3y+goYqc7A9ZkAlj18QPUfnwC1CYgRfYUlMAUA==","signatures":[{"sig":"MEUCIQDxFpTUmCurep8/nfAFrUu5nqzsVvhJBkQnj/V1e3oI8wIgAMTnJ1rEg6uMmzDuc3tn9mGsYbdwIRzyEM+uHrFM9IU=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":721740,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgjNLoCRA9TVsSAnZWagAAGL0P/jLzWRooXc9EFUxKKbEy\n1QdNJzw/afHFg4mKl6IhH16gMkv+BAl76Acyd5gqSEpFvblBNLLivaJiXJnV\nHMiNXUNd8z3o+FWhBzajCkFCVfdlb53qx9FJZ6Y0WthxKmumnx7xIMO4jwIF\nJoVJrU1n/UPnUqkOnrglRsEFPnQ6OGDABWhYJT3I+HmF2KDVy6s0ZEnl5Lzg\nj67O375u9Iu60j2eHfcCj9GDRgyPTsFwaQiIc6Y4DEi4BHnjOMR4l0SnhB7F\nIpLZpjhK9W8Dr0fdWzkAItUQRTm3uFQR0In8wMk9WcpM/feTsIP+8rUxplcR\na62WXVuUAh81UciTzvRTxr7g9y3kxasuAn2B7jh2VLjJ6NQ6OXXMBTV6P2IE\n8C8UCY6zfg4/ERb7VvByZmDMEtPYpO4skA/G/Pbu7PplJEV/HKSqtiAG1w0s\n/MpizpKXRUCUw3TkaG9ddAvpAzpUzE9AmOkeK3qmKLrLwQOQX/+0wNUJ8OcH\nQ3nqEhm0gCr/14cbGN/TOZWlHbna6ZKCQHmUACtTqVoLJFeoRIEJS9CjRSK+\nkfV95PXAD3kMI7W+JYJoF4wQV4Jzl0mG9RlX4NIx42vaSAHLNTAebfQCjJX3\nS9PEeF2nsXdnWw6wwBHebvqTVmjtLaNACPC36D8J1hlPr+o9wYFcCHBAZftd\nIcAv\r\n=pvwP\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4867ab6bea74d1bee1ee76e414368b1350640922","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.10","@balena/jellyfish-logger":"^2.1.40","@balena/jellyfish-metrics":"^1.0.164","@balena/jellyfish-environment":"^4.1.7"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.128","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.45_1619841768261_0.08964965142551051","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.46-renovate-non-major-ab14c1689cc92c0d26a28b1dab9d9fbf7f4fe2f4":{"name":"@balena/jellyfish-core","version":"2.13.46-renovate-non-major-ab14c1689cc92c0d26a28b1dab9d9fbf7f4fe2f4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.46-renovate-non-major-ab14c1689cc92c0d26a28b1dab9d9fbf7f4fe2f4","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"77b8dfbbcf85bc9ef08fc344344af9a2d759d7fd","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.46-renovate-non-major-ab14c1689cc92c0d26a28b1dab9d9fbf7f4fe2f4.tgz","fileCount":99,"integrity":"sha512-MiG1YtBYR1lVh8ze6S+6rA/bsFC/tetzqVQKY6ElzOJ3upGcWkj/fPMuTYsCz6C4Ozok+Kp6skaFrxuJiwNeFA==","signatures":[{"sig":"MEUCIQC9CGTEouT0EgD9PDc2wDpICDrlquWg2gEVjO25dSes7gIgSOV0iFv3HX+ePtFgVhvSici1xRiaCZ0M09UKfOBznTI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":722229,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgjSWlCRA9TVsSAnZWagAAPawP/1MeIms5GTL3J2mcnHDC\nj5O1aG8yn1A+l4NbyVdaGlmqhtkCoRYWliW5kE3vAJxK5t5aQHi0v4MVcEpY\n1gRmonrXR1abuo6Gjyt/SMqFKruiHsDC/YVyUXXPQvYPhFLy9jCqYERPd/Xr\naWprLmUA5psvvQoFtYueT+d9eMRdFi3QvNUwZpDFP7sDlo/tJ12Xctge8KHq\nfEsaYwhCv4BQfTkW7LDe8NP+yXxHjRu7zGJ8bPHzYIRiphAQuOTEVhNHuZ2r\nFnn6dOQWpLBpWV+dutjhoPkOwlWGDUac6n3J+Pur1ase5N7u5jRohPfKUxJ7\nzBHiYlpuzU8ot0SqTFt0OgpzpgqJAWpz89X1EbggObCnLwtjhrLoP085GeNy\n5S9i6RNbjzhxZaMzK6vSt4nhburO2yXZJiVz5CUK2xJihS5U3iblrnNoNZhA\nriJkzwQiXwfOnSAg/g5XZnz9UgV76J9CQL4LXTjmdwoEl83JuOUQIMIWp3I3\nxfrRjc/1Mes68zCiDXa3Zh0/yrHnbfCCmBsypxe77ravdWrzi8iwKcD2BE95\nR9QQYE0PrYz0vFPE8a5xbUIf65WEgf/GU4QzhBczRkI0eWWSIEWsZ0eaACAq\nj3ixDeq7f1lAS3zfncYr72FNAQlL3+A9AW2B0MmiJXWIMSyWq/UwlLSpZ9F+\n2jUm\r\n=jyw4\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ab14c1689cc92c0d26a28b1dab9d9fbf7f4fe2f4","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.10","@balena/jellyfish-logger":"^2.1.40","@balena/jellyfish-metrics":"^1.0.165","@balena/jellyfish-environment":"^4.1.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.128","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.46-renovate-non-major-ab14c1689cc92c0d26a28b1dab9d9fbf7f4fe2f4_1619862948952_0.41812395985795914","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.46":{"name":"@balena/jellyfish-core","version":"2.13.46","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.46","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0be8e8b3c9b7560602387c7280a3137986308d36","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.46.tgz","fileCount":99,"integrity":"sha512-QkUq2hNx9xPYYYgP9hGHlRaK09DhPwGjM4CMkZHuB0OJ+6LjUM2Ku7kJowbsh/zbsFeLhWa2cklSUmS18Tkpzg==","signatures":[{"sig":"MEUCICUQ3jgQ6dKRJo9k3PPYaS4Br42MeNEgv1hLJYF/MdpIAiEAyGiY0l8iAf3QXUh2PVgfWF4P6OSVSG9hVIL3UFIfIEA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":722169,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgjSbMCRA9TVsSAnZWagAANcYQAKR66nQxWFpD6AdgFQZc\nSIW3Y/upYW3Ph7Bqy+pTT7auavO9MW7vMgNXo72I5ZJDxFBpd4zkiTYeyAq4\nTRrGyRqvK47eMuAXcF64K710+TojAVO7P5Z6g2K5ekidoPCEQuiDEDHX/0ZJ\n+WkX69VqixvzTHbl19gd/hjwm20c2JAhwvs9xzhq8mnMBPIoIUvudj2ep+oa\nw/qJpy+M/CQv53mDz6s54iS8g89/Kg10gVgAaf+bS9mVTj8sHcVesAubJa1l\nbMCkw0RBRMIGWPrLKOQjo1G4PpVzvB7DtdKexlUNa/qdcrlOrzLY49AdHpeJ\n3c26fxWHJ/5MvK1G155btXX16MaU086AJNPW8Jd2rStcKjbHxmI30qNiPDTl\n9BkNKTQVSf+dGhJeCsjeKJNhFV8DonCoEZfWJMrW2xPX8bqBD6gLalHOmPC0\ndMYMZgZ/ugJAiB4NRcvZ6KHyP1PNmoFDv4OElhGMs6PXepXmn4HUEqiH851F\ne5ciNnQ3YQ94iMyMbYPiCX4X3HWfZu1e/2U+qlWsWYxsd0YmmyTIijHQ4yNS\nE3XKeZOl7qRKzRahWdpy2PqbpDGPdQsmqK3QBpduJFhtZvniVVNyAtrSIXTP\nMzWgND13bVDCXOITzUiYkQCVmOV2JL+8woTEv9QPm9lr80TbClRZ4qnGeB6z\nfF6l\r\n=l1xZ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7bea65834cbe881af7830663788fcc63ea1b609d","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.10","@balena/jellyfish-logger":"^2.1.40","@balena/jellyfish-metrics":"^1.0.165","@balena/jellyfish-environment":"^4.1.7"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.128","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.46_1619863243572_0.8549588061476094","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.47-renovate-non-major-90e1df44accd9fe937af7d7156b06600f8569146":{"name":"@balena/jellyfish-core","version":"2.13.47-renovate-non-major-90e1df44accd9fe937af7d7156b06600f8569146","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.47-renovate-non-major-90e1df44accd9fe937af7d7156b06600f8569146","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"cf34abaa5237ea909682c413dc19893e18ab78c8","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.47-renovate-non-major-90e1df44accd9fe937af7d7156b06600f8569146.tgz","fileCount":99,"integrity":"sha512-jVgUmT2cFqT1Day1dH5pVM070pfBAjzUlol11oCDOq/jaaBjepnxe6EbkX7LpVzkYv4l0lR/PHmYfG04RHkShQ==","signatures":[{"sig":"MEYCIQD+hC0D1yRByqzFq4+k4i/uqKP9tusq3qZ8PenDsS/wQgIhANt6EesCd4EBulcBJ4TcGVmFA3osot/Wp/PyGHdwH/NT","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":722658,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgjU5LCRA9TVsSAnZWagAAaioP/2/lGBqlyXwDKWe+yszX\nt7PbNsyB8pkP87c3Cb7bCqnLFZ56qMk7qX1W6ze0BsH6riH8tkAjO7TuBkOC\n17qJFo+nBT//dw6+qxBfM1xbkK55RaKC2dI9oDrB3trB/MUwMkpyXU5D8z9L\nm5lyp/9CTQlr/jyMrLBE+BJuoMq9yVwzJrWIoi9yvcGHeQqZxVLM3e4HZI5T\nI3DYApJNRNjgfmNf8PpfB85i2EY2owPs+LHuUyMvok0h1TPpznVHbz3GyL5r\n+Femq3xAFeefGvZosXsiRS39Dy3RgyiYjLleaoVR8l9G8ToRomuHfHzY9zBe\nJDOW+88cezjX0mASS0ZZuBegcwxKU1XhtWC2zrWyUB5AiTcTWi+GSmUkmlAm\nWkspr89f7ZE5r/0crLRFDRq9Bo46kkaY5BUYFyzzKlvsSt/G2zzNisiKanre\n3FGUZHOMHquGXNAE6PohCoMS47E6C77BX6vMcosqSb+njJQDq+E+kvj+PfuX\n98f4dPHw760YDt70a47QfZ95pTGuODo5IGSVOB09baGtISTF2+oAbLbO85y9\nfqMbpgMPXoJ4xrxKZczcUu+BbhKPevcj9d4K64t0xfuopIf2BKlYx4zJYCm4\nQEvGEUwRisXBphPKLJfN4pgvZfC1IYZgk/D7OaQ0CUtHp4wrldyCJz01wsJ7\nuJ1u\r\n=Pxrn\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"90e1df44accd9fe937af7d7156b06600f8569146","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.10","@balena/jellyfish-logger":"^2.1.40","@balena/jellyfish-metrics":"^1.0.166","@balena/jellyfish-environment":"^4.1.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.128","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.47-renovate-non-major-90e1df44accd9fe937af7d7156b06600f8569146_1619873355131_0.7560629601952644","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.47":{"name":"@balena/jellyfish-core","version":"2.13.47","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.47","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ddd97dfb346dd9898ff7c4cb925c0d37d0787e47","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.47.tgz","fileCount":99,"integrity":"sha512-SUyGRgSRpWxHh+/mrgqrSVLvmwEIFfrxVFFbsYfBIwhtPF33vO6jlNDCK2HdTS7b91yOVbhyL5d7/xfUdkWYJw==","signatures":[{"sig":"MEUCIQCp3xcjnRRuF0o7EVjfGvNJpmZPTfyy1w/XAO4k0Qs47QIgfjKYcfbV6+a5xzuiAS6c8ZNmXN5DZ8YWuCYyUAH3D/c=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":722598,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgjU+pCRA9TVsSAnZWagAAfKcP/2H46H1BCGhF/XSQmwdZ\nD46RUw97alcxy29SFXhSCONL4C88gN0nhOYCfILTQZPw6pAUaLHJnzynURTS\nl0PJA8RDBgunxmxUWLjRMHRNCWE+VISfysYD/ywOYdnW//syi454y9oggfEW\nwOrnu5Alxk4cB4Qmzptc4VrInU+xv3NIaB7Ek9bmz+IS3c55OjluSgXUdqey\n4glCYVdEhUgrDnyBM1/yOKpv9E+wXACQWgeAndBwQZReGJdCXjW6E8m9H66U\nhmRlG51xV0nVQGqT6UWm/IGc5UqHqMObP1Vw7AtVvAjU76QooBNpUN8XxqEI\nEvvTUgQ6MkKMeLemliF1TKZO+J/KW5Kw1eVuGWVEM5so9t4RQ4DwePoccmS4\nyK7J3n1skl0UY9nburu4EWFv4BvA5+g3NvbRm7A+eN0oGn50ou2G3BA7xvYC\nbxjAVZzV0oPxHPrmy0NRvAA7+9vzAXX8ZjXj0LwetjMZvTsTRNtl7jb8v5A2\noo6fp7M6XX8fDlGQVFNYpZ3N/dQayEdX52zhLma9NCn/hd3ADxPeFKTXxiyS\n9picqY4OieM0HaMxNuvrZ8qBtnXA20wT7wU1EN8u/DI5aOdgP/daWWdutuZB\nVKRaeuo66UBUb6H1PyIDoTnadtXyy13xUfJic/yXQb269vSF5tMDfgPe/KyN\ny5qf\r\n=C7LK\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"346ece2d137423cda0ecae70ee8698916035ba4e","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.10","@balena/jellyfish-logger":"^2.1.40","@balena/jellyfish-metrics":"^1.0.166","@balena/jellyfish-environment":"^4.1.7"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.128","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.47_1619873704512_0.6743629062275309","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.48-renovate-non-major-2ff90a25136c4be976ed37929ea048462ad1765d":{"name":"@balena/jellyfish-core","version":"2.13.48-renovate-non-major-2ff90a25136c4be976ed37929ea048462ad1765d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.48-renovate-non-major-2ff90a25136c4be976ed37929ea048462ad1765d","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8ce77e177d9b5f7f17c1b88dee74bc3d8c1fb86d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.48-renovate-non-major-2ff90a25136c4be976ed37929ea048462ad1765d.tgz","fileCount":99,"integrity":"sha512-S6Ot+CXzlPD54v+HcsMrjqPkyWO6L2o3RbCNaNFBNpOpceHj4WeDrj9gflx2HGXsgtxvZISyaipg0L8JUSAtLw==","signatures":[{"sig":"MEQCIHltCjvWQUquMGUMkHOD8YKOst3IvFecHUV4lrWU5K6JAiAMLI7AKcsK/g//Sg+RR1yqLb8v0SwPfVc6CtxcwafaqA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":723087,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgjX0ICRA9TVsSAnZWagAAOq0P/0R+L/JIUKAHCZumcp1c\nJirlyoY8BrXZk4tpKub4CaC923x6MQOgFo0OKsNZJhid5uVoGbDFQ/w2viJG\nnmn00JWf2FRMn1s6H6LbUTg46CwF11PTR85CJDaan8Jq7PzyEHaaS5MdcHtR\nMCfd2iaAk2wKa0Bt+F/eWI/NzigaS9znaIRQyhBfDy+7K2WWvM+J4vEjgQ+E\nXWLuZAMQ6JEpXdOm6vJvN4OhRCDJVqIiw74NZkNlfRs1wa19SFGJlyuNPviZ\nhetOZ7vG8FV7DSv/ClOlguV7bebi5uDhLzodBqunDciUZeLX3RSH//nGHP1s\nvIPjjCb/GjEbvJnwgiY4KU5vzVLrBdqFg21Al9DJ47VMlHmMWAbFSLmNQypX\n+RKgOgs1jrIyfIZJQ8dP0dTOp1v/EP59ZA7tdv4glVF0DOqNDZJX1p3KViD+\nyhtfEioAIvl2xv+eUJ4NfL7QyISJ5zUz/2MQJmzFf3YAwoxofOw6L8+gcsrT\noIjJTT9C3EPVwdmhIRUvqKkO8wnK/scztUdSa5zY6z9L7skeQJ2wbi2MyVdr\n4pPEM9HVx1hWbJgUhiCNHN2miFX32brfYDNHnboSjuTo/pGFR/SiF3Lns1DG\ngZSn0xuBEjF7b+2zttcGWrYv3y0VgvM4w/auOC9REY/vambDvuC6VVz4IGjb\nYNeL\r\n=CF11\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2ff90a25136c4be976ed37929ea048462ad1765d","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.10","@balena/jellyfish-logger":"^2.1.40","@balena/jellyfish-metrics":"^1.0.167","@balena/jellyfish-environment":"^4.1.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.128","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.48-renovate-non-major-2ff90a25136c4be976ed37929ea048462ad1765d_1619885320443_0.5964526060999487","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.48":{"name":"@balena/jellyfish-core","version":"2.13.48","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.48","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3e75f658726f304f1e5b251815b4aae1857f7f33","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.48.tgz","fileCount":99,"integrity":"sha512-YTwjuyP3bR26+9yy63J7d6PGSnuqITjRw0d8gBHqaRK2P1gRhfm6derLkTDhRFidKXWZaGuxp5vPNYBlguWdFw==","signatures":[{"sig":"MEUCIQDhsBtMdETnMFiq5LnOBbxzkkjmO7JQ9i8Heq02xmn69AIgRgROj0bCP7fujFasW5jJglIDV1rupJsDT7RYciWZiyc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":723027,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgjX5eCRA9TVsSAnZWagAATOwP/Ak9346KvGhnFpST0PpL\nN4qTeE3mZ69QEZ/vkblR52syx9nGhY3fWR5v1tJUU1UoU4MUVfyEPYiT0Sdf\nChQQ1njvHMO45q35TbXtNy3789PGdliaBsQdSxkew4O5+WalQw/x4O0wT5iH\nRpT+qkEVijf2byk1BjSvIRdU6b0AICpIJ1uEUkEJ0zdxqgVwp1XfOA9kOLMa\nh62T0+DRuryhCYib/sCP+VxDGSfUgkNN8/eJw5dxvQobCIxpbV4EmsvlSnq9\nGXegFj1JCT99J0gbbnwRIYZ74X2PIvPpOlCJGLBCyqI6IbOn3ixMP2IEEsFQ\nu17l85qP0XlpwgkFPNIqPClBJeNsSth1OqHCfxxXCWchunKYEIq0BV1sNmHN\nAFL4+sj/pqj5lnI1RFRdM9tb0+1eHb0SvKo7NB5JhJfmNKnflKGVS59+CMY+\nfkjLGkdHD5iCQS0JR2mU3rcZ4qH7pIi0+kLIFdUlFg1ln+BIq4ICA1jq++Vu\ncOCHNLyjFsBhXFGBCRgdqli3Uwoj+Gky17cHGzqPwY1/Js5o55gsITCt9awi\nm5Sv1FQ8vfOSxGxzo3I93Xab8WW8MOdMzXqMrYoWGufa/K9r8tV8BwFZaBxr\n5MMuYIsiAXEO6tqS0yhYO3J1PBBN2gdBHtnlic1qboUUgbmnAb61W5Ck0PeA\n8mhn\r\n=r3Hd\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2a6e9d95614576acefbcc524d84ab36d1a97297d","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.10","@balena/jellyfish-logger":"^2.1.40","@balena/jellyfish-metrics":"^1.0.167","@balena/jellyfish-environment":"^4.1.7"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.128","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.48_1619885662340_0.5498533284049594","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.49-renovate-non-major-15b3f8ca8e9a879bfaefd79e34b266fdd63393c4":{"name":"@balena/jellyfish-core","version":"2.13.49-renovate-non-major-15b3f8ca8e9a879bfaefd79e34b266fdd63393c4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.49-renovate-non-major-15b3f8ca8e9a879bfaefd79e34b266fdd63393c4","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f2448f5ee6ad0f6d4241fad3e0e359f636469c6f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.49-renovate-non-major-15b3f8ca8e9a879bfaefd79e34b266fdd63393c4.tgz","fileCount":99,"integrity":"sha512-yBLVy374uoA9/hauizdQBHhTq6mJSlD3Dz0L96JiYL7c7u4kmo0ZdCSDZPpKUBqxa/2X7gDOCq1M8nwD/2wycw==","signatures":[{"sig":"MEUCIQDj+lQgVQb88hpjdVRgR5Q/8EQO5fmERf60vUcdNAyIHQIgNIBf5d8wjeRe280zhopjYzfoULUG9fzTTMA4vXvwf6k=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":723516,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgjZKoCRA9TVsSAnZWagAA0e0P/irvsoeP4gqtlCXtIVI7\ngw86ri0kZPb37Z4OnzhoQycRAMGGaoSpO5kabWMjHkQi+okqDfX/cbG9vooT\nDQZXmPFDhYioPWEvqFG/Ve7f9oR5UbyMJWTpDw++O6n4PjcHIy0pY+CmQmtX\nxixHYNAgrfHpqpp7vPrTptGe5DGN1z9s0SaDaR7vdenOWssFUgmpPmPTklnv\nC0NcAs5sRoM5BX68wOAVkpiPWXOmkl+KuIPcV93zoPyGDioLZuhiGjP8kdxh\nqcTRF/yi700j35zaVRklPw2x7A2+kQS2Z6XNEztdvus+7XJ/B9cTenue4pEW\n1USAE+kkEY4dyDUJ98uDmBKeqA59W1cjAJ+hBIrxnNQSd1FwOv+OMDqF/mOq\nQMOd9utglPS8K7SGTYvi4qzRBbvjvg05k8kHdcn+dfxcZQe71XNyuBpzVoqp\nUIXBcRzQAbqc1KCBeNbKeC+F3SeQmla3BMxeoA6R9tSoM+rKoiYfrNSZ5PtM\nreHgYxsKczCl86t/J1sOKnDmKzzNui/hTLOZDaSYs36rShW339pIW4C5mZFE\nVR8BI4zf6/zXY00iEiG1ZmJ4vNh2cZSSojwZZBaS4jMeW1Jr6w4B5/xbiBlY\n5S3M4cDHaRWW/EEbK9rtGW9bVuFekU2X8pSIh5a1RnzBD3v5GkB6sbSn8tVJ\njIJF\r\n=F7sz\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"15b3f8ca8e9a879bfaefd79e34b266fdd63393c4","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.10","@balena/jellyfish-logger":"^2.1.40","@balena/jellyfish-metrics":"^1.0.168","@balena/jellyfish-environment":"^4.1.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.128","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.49-renovate-non-major-15b3f8ca8e9a879bfaefd79e34b266fdd63393c4_1619890856070_0.9872509164882621","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.49":{"name":"@balena/jellyfish-core","version":"2.13.49","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.49","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ba9c982b436a3d4ec315a67613a7a1aa8b6eafcf","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.49.tgz","fileCount":99,"integrity":"sha512-rLrB6WFnZ/qwNZNHap33HKFvchV4XW2KbqHcUVJ6Md6Py+vhUHB2YdTicnO8qLNYIGBia6MSNxe3fuz53NqU5Q==","signatures":[{"sig":"MEYCIQCrBZh8cT1TteLl80CHCzzrlq3iHoNi95yZdm3Z3ST37AIhAKTzVxv2wfkx7Pa37mNb0qj6iQNFaYZIAL8wKOIBRGi1","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":723456,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgjZP9CRA9TVsSAnZWagAAWZoQAJufiWAzGv2QwS30jxT5\nkAa3yO0MHFXKQrV/cujKSxTfmmx06QttsluCEb5UAMmGka8Cqj4dgetpExR7\nWUduX8vybfYCLtO+9wXz9n8tVexim+ImGccRtkOXLxtafpgPW7ha/5EielO7\ns2b77vefkndLjKcXQJGHlfaqvMdXFy8BsdcPSRJt9qkrTd2XOQufNUFF9RKi\nzJs1QISNbHmaSdR3Naf5WFmoy6d2OtiMozEXI7u01ieOMileDvmtZFPvCBN4\nnN/avArTDC2PEpqiyuhOkxVghabw/qm1ulFrRUj7FvcJW1bdke0JSfWgfX8D\noN2C3m3briOEOAoEFm23iLY4gS9lQ7ajuc244n65jIjJsNmnhopMfwTnbCI/\nkrVYhvg1Ae25U35rhELqif1Y6CDoOWBhRTo71FJ7PAgEToqp5wwZre5AAgsn\n9gk7naN2MniovUqSzOmqQeTcaY/xVTeJZRQRINqxjJRa7KnbUDwA2deBya/1\nqbw/Q4XQJBwg0fzhCKK4R/sMysU/04adNF4xeyZ8oZTmveiMPpc2JtngTcLj\nGRRGE9/3zSNT7iy12DuVaSHxQRlvDI2Rb2YaffhoRI+Huahjc7rKcARQaDUl\nTMajvorZgTwYi+/W/dZUKDp5E96aDE8BxppKDKHVRW4mmdW+/DLHYRowZPUO\n9Iz/\r\n=cwI2\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"381b440b78e5d4f9e019c2cb9c1bbb1897841092","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.10","@balena/jellyfish-logger":"^2.1.40","@balena/jellyfish-metrics":"^1.0.168","@balena/jellyfish-environment":"^4.1.7"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.128","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.49_1619891196897_0.8797548096658199","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.50-renovate-non-major-1f6a219ea4647190587ad39c1fda8622e2dcffac":{"name":"@balena/jellyfish-core","version":"2.13.50-renovate-non-major-1f6a219ea4647190587ad39c1fda8622e2dcffac","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.50-renovate-non-major-1f6a219ea4647190587ad39c1fda8622e2dcffac","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"51575a657be0d47b787d3ab9312e7907e98bf188","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.50-renovate-non-major-1f6a219ea4647190587ad39c1fda8622e2dcffac.tgz","fileCount":99,"integrity":"sha512-6Mp4eYwMNmoFD/fl8w8u3mkSdm6zlyxg6G+ijRI1aNabwHHtbWh9jKRk9jpw5S1TKV6f/K6vg7kQHA1DYMyQSg==","signatures":[{"sig":"MEUCIEt1zaT6gUN2rldoi6pTeWUKwNMfqn75zEn+qv/8IC8kAiEAvuogHNBbFN26QpHIFniOHjH3FMV0voaPnER34BdFJOs=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":723945,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgjggjCRA9TVsSAnZWagAA9IUP/Ay6g+CrptN6IY27eaDf\nJ6PGXFbJCWerAWyPIdwuQFaztH2xDZGuFRelOhReVs5Oj26vfnikekQBLFT5\nSPbSnALIGBrqQPh5XqfgkUmukFVETG7EkQrMWAhWiJ3nu7vPt0UtQDAivRBc\nyaHGaRN5RH5iLwldRql6LayJ7CIJWEtn7NDH5bGvgRt/pTutbHWWw+YKzgKp\ncSEv2kvNstF5GQ+r762JGVpq8jK9l0Lz7ZEw1rusTJoaHeydp5pxAxr0vULU\neq6delZBc078EYIVpWw9wfvR+IZ6ozw/MiE5CK83FkRynYEL2ByOf/L//OZb\n5sZsJmrby5cmgWFf5K2YHonqW34tAUoR2yKnwzO8O6ES8gSgTHIvDQjwi4Jz\nRRtSeE9VuSdTqsady/DPKkMPZLYgpk1TRk5CEXR1E4OYRsUaxM07IGbw1eNX\ntZj1Djz2BBSYtpzFWNfwBO2idH9ZJ9tgmvfGmL3uayzLUDwvf4C0XGfU8EJR\nOEVon0kx6uCt/amg+RferTPs2uWZsDLKghp+sYHO/seqPjyGrqKh9uPIssDx\nZZe9Sp5sTkSGfEWxkTZoyJolGDYPURya78fXD27S0mIipALv49WH2YusJJbT\nRBITcZKOBfZs1QMZMU1ghLWyES+2V5Nw0DyfM4a62lQe5OFdPsCh6JSoGH0R\nU7nl\r\n=tbje\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"1f6a219ea4647190587ad39c1fda8622e2dcffac","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.10","@balena/jellyfish-logger":"^2.1.40","@balena/jellyfish-metrics":"^1.0.169","@balena/jellyfish-environment":"^4.1.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.128","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.50-renovate-non-major-1f6a219ea4647190587ad39c1fda8622e2dcffac_1619920931156_0.7291296239424356","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.50":{"name":"@balena/jellyfish-core","version":"2.13.50","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.50","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ea5db5a0dbd7045cf39c27000f4005dd2c760cce","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.50.tgz","fileCount":99,"integrity":"sha512-TdAjsOQ2TqDKWz4M5M9H8zFD5/4iaY+46UxfXK2YFH2EIobvPqRXHArrON2nX0O//nmXq/E9Janpe377KQwspw==","signatures":[{"sig":"MEYCIQDq9S7By0HaiVqWv76BTkUwJpeZh68XMnhePcQSZZwuGwIhAJoIma3FscfM9vtc6JHBxunOIyfHBjd0gJRcKx6oW/pQ","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":723885,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgjgldCRA9TVsSAnZWagAA6AMP/3EqvNAd4EHLxM8u/5VT\nyXdSO63rl1e2MswdtfhasXmwHCxtm/7ojfoERFBR1xtidKnLoNGzMLvV51PB\n8KdIULXE4O54ioGbzaKZBSKi/thgWVNU8Xl3Mkn97KMq9vgL+OS5R5e7YJt6\nQOJWW90+ZHmnG8Go0LhPl2PM2LVLWYtISEQLV4H6M286Y0KmvxGqKCXE90cp\nIYv0NthHeRTqGmP30j1IClRAR4/5pd7gkfUp6qJqLtGoaY1ljwKZjcQxdBZv\nMqS/DnoB1KTbDUiGhimt2lYIAXaDEQq9XCEAtkaQDSxj5zpuD43deTkT6Mij\niTP9/ATRGa1Q3TX8QXpdiF9HyPrXeWJp2iDTg3W/Bzdk4Z4n/uYNFm6mEllb\n3jdmTeB1gpEr5r7fYbIG5AL7ZX1ulCkh/4WaBrVajd6f+E1ycZj2265EOScE\n+wnDTNtggxaNsYsnFaJ6QoFqOcVH/cP1n3VKeUjwUrsMCmp3EYghgGAoJy6r\n61Bc+xT2nnmxeOdyX+I/ETx+awmyCDijbF5+cMadWYbD9BRuUe2/KpOyi0j7\nTrp6M3rG2yxVJJ3T0RG1bT5KeyBEXZ7oTHrx7CF7T8MDPvguXgfZk6JMvJxx\nb6FAhB/T2MpU/eN3eGGF87Eyex64kde/WlMRfwx/MsG/YLnbv2Dj87SYnOVK\nEujh\r\n=U+bX\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"5eb779fd354f9889b844da4af68a6d2951395c31","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.10","@balena/jellyfish-logger":"^2.1.40","@balena/jellyfish-metrics":"^1.0.169","@balena/jellyfish-environment":"^4.1.7"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.128","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.50_1619921245335_0.9978301388289783","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.51-renovate-non-major-92b9cc6740a4c00787c0708d6e0c5dfd34178c99":{"name":"@balena/jellyfish-core","version":"2.13.51-renovate-non-major-92b9cc6740a4c00787c0708d6e0c5dfd34178c99","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.51-renovate-non-major-92b9cc6740a4c00787c0708d6e0c5dfd34178c99","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"dc41d4aa16c49d4eb92400c21fa7d3587af12b6e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.51-renovate-non-major-92b9cc6740a4c00787c0708d6e0c5dfd34178c99.tgz","fileCount":99,"integrity":"sha512-amX2FGDo+O0sUzUUU5IOwndhYrPUbUUoYOa/Rvifqank3HeohZ3MRLd4quUAiBBuoWZBWt4ToRaASTJGER4DRA==","signatures":[{"sig":"MEYCIQCKuGWlxDiqpi0fTjyo8lo8j+46f40rrvKbKh9jaLgvegIhAMzZaiiRqp4w8/izfUvgmKdsP+w/6YuNO6cIKdIbBJwa","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":724374,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgjizGCRA9TVsSAnZWagAAFHkP+wT9cKG4lR6SFesGg2ZS\n29vwuvJufJuJNuDl8l0OzoZaeHwiorStlJCcPg65r0E5VXdkNObnrAdKt7j5\n9khi4gcdFp/a62DAG7S2MHX+R25At/2mbtgWx23veiugkF3mKHjjLTEU95/s\nxzjXGyLTPZj3i5vqhojvchrZr/Fosfp7yXb5su34CXy++2KbCu8sKo6Yqkcq\n72YbtPLp94EzcrvTJQnb8IQBZ9xGMqZ2C/tRPpsROwXcjaBabFrxOGs1dXLd\nEK+HDfC1atXhJK7psegWQqISGZEDpbeojBebiXPtU2LIL1O+nvUGNOwxbMWA\nZs3R+s/Aa/BGDEid2L8oV5Wb7y+LOMwsor4c5UMJGXRA56O0JjBhkmdMZo8U\nB/jn31lMSxW6g4ij8uJkBP7rOmpwSkVuQ9WIeoEnRPVaj3tUD7+i06On2hCG\nmFrXcnAv1wKj/94MSd7G242of3TZfk6HAm+Tdve7f4cqx5pAoUauaFOLK35P\n14zkn1GVqd5JLjvJB9iVDJxyxxopLpv9o/djcQ1ozmMxxeARMxkUOBx+Xvgs\notOjBvV+OE2hD6SaUXNGlRasUtDrbSMx2NDiuXhxjH8wkbUbxMVXGIAi/DOL\nFC2qmk0NVjcaY0IZP3jav+p9IFlOQzu1GTZ295PCQ5TomDBqFhv6iKqEKSE3\niEhM\r\n=WD5k\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"92b9cc6740a4c00787c0708d6e0c5dfd34178c99","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.10","@balena/jellyfish-logger":"^2.1.40","@balena/jellyfish-metrics":"^1.0.170","@balena/jellyfish-environment":"^4.1.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.128","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.51-renovate-non-major-92b9cc6740a4c00787c0708d6e0c5dfd34178c99_1619930310036_0.07977159827945712","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.51":{"name":"@balena/jellyfish-core","version":"2.13.51","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.51","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6cb5754d70639ece893b3632f80206a1cb01c182","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.51.tgz","fileCount":99,"integrity":"sha512-i+QUo/tQFiwHLtjuDAl2n4UFgfl2QkdtduHxhQEm9DXLj2qfv5I90u+4pLVvnQTDt53aZQJ4YK2cHmM5uyMOTw==","signatures":[{"sig":"MEYCIQDOQv5LiYgQnL/MxpjntVw/NtrhU6vNkq4hmeMy5HI2MQIhAOSSPjGKYWvGbGd8pqqEi6jrehipgle7dB0lFg6Xv2HA","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":724314,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgji55CRA9TVsSAnZWagAAJoQQAJNeZsrmNmRgjR1ydhqZ\nWQm4MwjMKQWfpQK+qJMWkE+aI+FXAzbqOTovKgVpHR/iR6DB1sSQb8SAj3fT\n9WQeyCCne922aLzBKJ2AC6hjkXHjBG91GdvNfvXJuJJZ0d4on/yyvj2r+68d\nrSKtW96jSyPOKXaOg7T4gwIO4B7GIL7hNRbLtfaXibxq5OqDnmX8UjSuJujS\nomnjnCmm9uV6fleLRK/2eqaAcv1pWTGIqpso1Cre+G6y5ruzp/uvK0Z0Zz+P\nD/5YSyNthan7/3RiqJJq3/iXaL3cpytKVEF0ueWEcvJCUdmjFlhezg+ApfC5\nZSVXyiXSpoy7JYiOZtY7lTG7O11Gb5v3JlrAgGoG2LgD/oOkLvwfzN1wpL8w\nZ94gMS7D7A6Yuh7F/UetXMnJtFic/U4cuKAaaWD+n1LBCJX+4Fd9cMHBHrnf\n504OgBcUmCaLFizs7i+tatkXGjvBl1d5Pr0ZINf4idSjHK3UWHABUhBpTwRg\nDWer5wmiis6/XEWMAh7wuM9/oQ8cFDLznPs/oULb9KVP72ZfM0ZAyaOWx8sD\njh5MbSkZndShniWxbURE2MG/i/1hGoBx4u18A2UFjRydzk3zZ3CZrPbh6YEp\nfBc5oQuMiZkZ1D6/IEIkIJnk70f/oi1LVNAmX3478U/ocfapG3ZSPnjPLN9X\nfE/t\r\n=DiM9\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7e18cef5daa8e4ea06692e18f1e73079e90f44cd","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.10","@balena/jellyfish-logger":"^2.1.40","@balena/jellyfish-metrics":"^1.0.170","@balena/jellyfish-environment":"^4.1.7"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.128","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.51_1619930744907_0.4687113118772843","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.52-test-ci-task-runner-5cd0aa3e85497d555dd2230e4cbda41692cf99ca":{"name":"@balena/jellyfish-core","version":"2.13.52-test-ci-task-runner-5cd0aa3e85497d555dd2230e4cbda41692cf99ca","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.52-test-ci-task-runner-5cd0aa3e85497d555dd2230e4cbda41692cf99ca","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b26215120c77f7d027858bbb615a4b144481f075","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.52-test-ci-task-runner-5cd0aa3e85497d555dd2230e4cbda41692cf99ca.tgz","fileCount":99,"integrity":"sha512-1YyQ0mcQGlRnvzfhu7H1NXIqUk0+7Hl93oHwWyC44pTChRUf2+rh+9uRvw8a2PtvpeVGKfPJ1JKIVhBl+eBoeQ==","signatures":[{"sig":"MEYCIQC0nAPnP3yQw8T7bCI1oky5TUrLRg8MMvtEiFg0KHLdcQIhAJ5T9zbNhZNXnZu6FXPZoJMSTdYWjIfdoKwvGM5sO2MP","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":724882,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgjz9CCRA9TVsSAnZWagAAESkQAIPMWfsK/HLkriaPqfCL\nRJ82XM0KugtrIrGsFz+I+hSCK/JTkkmnwuMlXBhJZNBm0d9JOkh8P9bpddxp\nNYACqNDLzi+vvQjg/b6dBmOr6BaZNuiaO2pE5TX0r45FGxBp/peZSUEZcZfF\nO6/gZumuiaFWkUv+xZHkJXXonJnqiHj6l4z7d8rKRa6LBUzDsBo31KbwzTGO\nsCeiBYbtxg72IYKuVYzcdoEeOeMOBhYsHpTjRv1Sq4VP28IqCnfYdBnWFzvN\n54mVmueuOUu1vrt/crwKabSPy5DLpHwB8eTXqWx0MSXumdOfIG2/XVRxDJaS\nvkTo9FHw8AOx/vHPr4FxrMykA4EpdGeoEUwg+QKVWWfWdrtO6FxXEbf6jbNN\nPdhancae/CP4g6MzNQNMt+YDTlMmcK1VWYAs2npgTdM232wsdn1x50B+q6ua\nt7Z4tiZwvA8n3rUCT1eEK9F8b4sHx4ucDI9bXXrOF6ImahRfqy70sulrToua\njVEdcAsN+tpFz3zGM4nhAI/ZHJpeJ85M0HNM7zBzfPADDsU/JXl9mEiJWsQK\nduV/cn+32obPyly13SnABIKJb8FrhuLAEMDQ4WrrH0XHXI7ldKaZmOqskkaK\nXYu/1s14jSw5vtJvinnI7La3xk76nbWj9Zn0/30h/dITiX6X5G7oeZ2ybN0O\nL3Tp\r\n=IfND\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"5cd0aa3e85497d555dd2230e4cbda41692cf99ca","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.10","@balena/jellyfish-logger":"^2.1.40","@balena/jellyfish-metrics":"^1.0.170","@balena/jellyfish-environment":"^4.1.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"0.2.129-renovate-yargs-17-x-03bc14cf640d4b474d30d4f8083bf29568c275d7","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.52-test-ci-task-runner-5cd0aa3e85497d555dd2230e4cbda41692cf99ca_1620000577817_0.150694212118879","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.52-renovate-non-major-7d85b1babd43934a2a37bf2e60f717edf0bfaa71":{"name":"@balena/jellyfish-core","version":"2.13.52-renovate-non-major-7d85b1babd43934a2a37bf2e60f717edf0bfaa71","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.52-renovate-non-major-7d85b1babd43934a2a37bf2e60f717edf0bfaa71","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e50931dcef9766032d5f31a1bd276f93c1498bbb","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.52-renovate-non-major-7d85b1babd43934a2a37bf2e60f717edf0bfaa71.tgz","fileCount":99,"integrity":"sha512-6Vagv+wyItjJAPTeEInHia5Jw8BsYqxTkGhqgr68jyFeqzZKAKwT5sm3NrnnYyE8mD99WUaxhoNyOkB/Y6sJPw==","signatures":[{"sig":"MEUCIAqveB3CWwtBiP1h840q3iJNzZM6DZ7XubSkqj/MUKZHAiEA6w9D7WpXGt9FDQuUraiByV4aaukTtlA+hPVxlQZALMM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":724725,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgj01cCRA9TVsSAnZWagAArc8P/iRYbEE3e8pMpuGJyVKd\nNtfI9SQw2LdBaRdGpSwyGvojxSSGA0jaOicxJXXd7sQ1eyFSZorVirBxObep\nkwWbYyB+TdjyC4ppfFnX/IjOpC9sufEu5/aB2RsRmjKvDHC5Lw7PANvAXwJB\nwtqeCbGDJB+c97W33hZjVhGUn7LTwOlJKjdFrK8SK3wTGvpnZxfbw1/EpQJl\nNdxqiSetv4zP5FVOHLQqtm6dCSNIniG5J6lCL34R/xH2QvUbWo4VPsJq3oOu\n/1nOkyxUK5VD7Npk94kOf39gQqr6lysBqK4svnjRHi1DEJmqZ6QCwUaLQikE\nj2J7Pbjoxs/LS3QGRPx30wwm9KHP0dH/LCIYvXXiPFWMyWaSZPXNN2wmNNQw\nYp1XcxOhJ4+8cKjVRsy59V8UAY/8hOMEy8PesxqCBsTLajqVSQZBXgsPjCJC\nEbNvYYOOZafWAvlaT+0U5zkJPdFva4Cn6sfW0qBC2nfpasgWPEqMeHVkM1To\nWBVfDpElCgahoAluI+OuocZTHm/yX88tTmanMLHVFlrYrfe00kp4mhQQ1zgx\n1fthdi0NmsNzmA928qUQFMFnZ08kTQFZBWrfBbYiMWDFiw4eTVKPxz0IKbvt\nbTsqpDLge0x+Fymww0GpbMwScVQz5wCbG/12m7kkuEnKFFnauXpn0wsaYVud\nXSA8\r\n=eJL+\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7d85b1babd43934a2a37bf2e60f717edf0bfaa71","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.10","@balena/jellyfish-logger":"^2.1.40","@balena/jellyfish-metrics":"^1.0.171","@balena/jellyfish-environment":"^4.1.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.129","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.52-renovate-non-major-7d85b1babd43934a2a37bf2e60f717edf0bfaa71_1620004187753_0.7291483205191991","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.52":{"name":"@balena/jellyfish-core","version":"2.13.52","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.52","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8426c9a93afb874c5140010bab1a261e9f61c7e2","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.52.tgz","fileCount":99,"integrity":"sha512-5raJN3wMk+B/FbyNyNikfgwTm7ozipmikC4Z1GNit3nEk7qJsKRwfyhEoIw4iCA6RBe8zB5AyL/7R5hFSKWc3w==","signatures":[{"sig":"MEQCIHP4Az09e5XSFKngybiTl818UjXecRb21wVszg/epe/TAiAujpYpQoXog7s3Oovh2D33SUQiQMhN1688QDFGCdetOw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":724665,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgj06jCRA9TVsSAnZWagAAFWsQAIs4bmIahFrMDZApcS9a\nnnLhQwTlCcsVftc9ZiVAOJhqQOABkUhkEXSuKM9litjOO76aZm7esigvVHT8\nzIFUodwPuOxeKVG6EbQP2iBkx/MHwlTNB/omvsIZchNB6ED+tezYSs5ZPYjX\nWQrQljw9yAsw7KauO8kqtR9s1qTzAYuqJg1iKGJItqVN9P7zGwf8jz6wQ0MA\nhFlvUNZPT4BMExQ97yur6ehqLs4cAMslK+BDJMftfLVPG4fyquXDwZgFBV8y\nczGpnMFtJf9C01drDadp78Gu28gxALLOMdFUawPLocl3C4mVFmRAsrstiVWi\nxYugoIeQPtZI72UHtSF7LLJnsEO4c6tc9uskp2RXWUgvfE2ocwle1DZ0m1Qv\ni0wLvRXfjZWgAEGMvjNd2ObLpz7erbnP9H4LFqsIT1ROHrzjzrMIiUPxxV0w\n8W4ydLt8c2iWsi0qGcqiesi4nm6tgFvP0BxtSgmVXwMq/jhLRCOOLykHGpLa\nw/+jOUf9vTWhvHrsjdGwX59k8KSPa17btUGuXFQQ47GJ7/gfEHT1bXT11F+3\nAFtOjM63JRevKlbLxc2V1SnboLTUmrhPeYi1TW/9pcTcL8RbTvqr2+tWm/8d\nvMPfM/CaH0tC4grBQ+36aCdGhq1tOVkwvp5W6nCLmJeTx64M1JBpUt9q2ih/\nnBhs\r\n=MRXg\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6e9aeb47d9ee860517be886c230ced0e11211fe1","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.10","@balena/jellyfish-logger":"^2.1.40","@balena/jellyfish-metrics":"^1.0.171","@balena/jellyfish-environment":"^4.1.7"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.129","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.52_1620004515353_0.902119584973482","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.53-renovate-non-major-058ea2ffafdfa8fd3c87998fe5bb0ae190ae3a80":{"name":"@balena/jellyfish-core","version":"2.13.53-renovate-non-major-058ea2ffafdfa8fd3c87998fe5bb0ae190ae3a80","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.53-renovate-non-major-058ea2ffafdfa8fd3c87998fe5bb0ae190ae3a80","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"bcc119b27ba8aefe0992cade7d5b0ee6b1a003f4","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.53-renovate-non-major-058ea2ffafdfa8fd3c87998fe5bb0ae190ae3a80.tgz","fileCount":99,"integrity":"sha512-OQoWsPr7N3HcVvOnXmJsw2YLDgTDL74efX0DPREShI3s9nSWEi5ROmOrFkl3ehsZcKYF/KG4XaheyBnh9xmaCg==","signatures":[{"sig":"MEUCIQDgm+MFI3Fm9GB2tyqKCnnRnmexjOACr28A5ZJP4O/ovAIgLib1WHN3m2DNR2PfIcODcbKYrw1ItwpZvp/D/mN5gm8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":725154,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgj3CbCRA9TVsSAnZWagAAWN8P/3vnq2ePqGbQrGu3GWZx\njqY88ghzEVbvcIJ/h6CQRamR/OFDStKQ0HBI2pOguz3xTkLbSpCzSZ62oYDH\nw5vdwB0c4xnByOLrHfrKf4+FcBSPEv/vs5poMYjafi0WmPR4B/OGTQLFxA3O\nLEHve4ZYJmteu/TKxd2ok2sNDJ4/ikR3v8nUuCH1NptRcuD1Z9U3yWfpjezz\nvft5MQrrH2EPyUJHh3nnmU4YUaFs2Qov8TQVQSsHASLtBiIU+Fp7u7Rfqpv3\nUHGlIM8TTjpWL6rDM8a6gn5L7of7fGwtKvcs3PMDjYM2s9gaJVjTAUnvTsT8\n7ZvTWPS277/S/fDUt4aFtMqWz7sIBXNqqHKNdhuxRIJD0JKQekIojresLNPa\niBco3InHzDF7NIgPmD3oCGsy2QKOnJ9Tmr8WHrFdkC+1BhA7KC0ka5TRg55Q\nExRXab0uJo1crlkLbQfpp5f8NnrfoMwq03oHLD5srJVd0xcV+iSsLTPnhRe6\nheVCQW6kQaSZgx2afsJwpAUSHJH1D6mIV7UrcqBUffEFSW8rAubx+lBtB4BQ\nUGgaTWhuzujQdvnBUH1p8CMp4hgoozKSqdmW6LfIMWnX7HzkKRUfda3qTB6+\nBMKWNytVQEoej5I2fqNSpzpGl2C81+7xgIPNmGUnhQdTuUbQcesbdgkTYPUp\njjIq\r\n=tN0k\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"058ea2ffafdfa8fd3c87998fe5bb0ae190ae3a80","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.10","@balena/jellyfish-logger":"^2.1.40","@balena/jellyfish-metrics":"^1.0.172","@balena/jellyfish-environment":"^4.1.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.129","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.53-renovate-non-major-058ea2ffafdfa8fd3c87998fe5bb0ae190ae3a80_1620013210959_0.23058880712279595","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.53":{"name":"@balena/jellyfish-core","version":"2.13.53","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.53","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"de098cc640fdd6002f3a2a941e4bf0e4ef18be2f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.53.tgz","fileCount":99,"integrity":"sha512-dsEqExqHY5iQ23+tlgmmbO7rwu1iu3NEMfj2oruoBZqY2OTHPMB/hhV8huc02mQSJL+YyCCaJpyX2s/6M4hg9w==","signatures":[{"sig":"MEUCIEE50jii18mAzk3IEWL+7KSbE2EcHgQ4Ueapr0Rs6IoaAiEAj55+Xa+N/kfSLspSMn9H55G76eSzM+mNw505M1en0D8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":725094,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgj3GzCRA9TVsSAnZWagAAouoP+gPpI6DLKUR+bMA4l9d3\nNuRVlvXxC1F6bUBxGTtz4IGLaXdYNLwz/2S07a/JHBNx+xuQDG+uvHHTwedI\ngU1pSOwEYZms5V2S2xdtHd5N0VSuadoTQY8DK9tRLWhE1au7MtopdUQyq3Ta\nz9LXgMb6Y0cBaPiV5Z8QSiguZ/mQ0G0ypxqkfuMgddArtrv4uu0Y4RXEyDa4\nU9OplB3plEXoIg8fkw3Z/aggMKpM4d5WFM/YTy40sua6xPxVgDk63LXn9/kH\nFGVGqFyaUH1Yy8Uye5707MbzhpwGKv6owH8fw7koxzlxflTFV3+ee4hHWfwZ\n6PCszWWwVh4Tr92fHUDWTKBwrY9z6DRVdKKQf7dksRnoi5/KSNGUz7MfQagS\nyI5K9F2hVOYqCTiep2Ex4E3N55JPR9s9oHZhQvLhNWaNLBYOI9S4gVsXlw9y\nDxMCic8h1jmVTJ8ulECKYjn+3f+6hlAcMhZMRWinCdGpy6ra+LKxpRDu0VOt\n9X6zUWsNRyTdoQXxXkCfDT+8UINFM4AOvJgKLcbpTdrDWIMD4vUqL1wctG4k\nMP6tWUC0Y1qAs3cbUpazcEsJWYygIa1a8NcRpP5jHP2g2bKok9TYQjYfga4q\n5F0T1AO2yzyI48iT3hcohiFGDBde5yBUlvUuc0tlgqTxcMg5lnxxxn6ehhFx\nDDjc\r\n=gLe/\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"de628799291d45c53c00c11a365d18e16d76b1ca","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.10","@balena/jellyfish-logger":"^2.1.40","@balena/jellyfish-metrics":"^1.0.172","@balena/jellyfish-environment":"^4.1.7"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.129","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.53_1620013490841_0.8182283032262412","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.54-renovate-non-major-548155009daad52c5521ea4f5d4f9eb48f7d7dc0":{"name":"@balena/jellyfish-core","version":"2.13.54-renovate-non-major-548155009daad52c5521ea4f5d4f9eb48f7d7dc0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.54-renovate-non-major-548155009daad52c5521ea4f5d4f9eb48f7d7dc0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"decb78bb654a91a05a4a3c7001b71223f5bbcc03","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.54-renovate-non-major-548155009daad52c5521ea4f5d4f9eb48f7d7dc0.tgz","fileCount":99,"integrity":"sha512-WW7lr5T+hl/2oYM0wH4Lsk2NwAuyoj1VeSemSMDw4T9jOzTh7Yh13st37Xg6GSoWT+XHiWz1LwkVjHo/43xhuw==","signatures":[{"sig":"MEQCIHy0uS4kzVksPhgrfbKCPC8iy/jiYfbaiv63bXrxw6CrAiB7s7hRtpDUcfDahksf2XTThtZJPr9Ybh/N93tVyNWntA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":725505,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgj9aNCRA9TVsSAnZWagAAkPsP/RJOfOfrArUZppz4Cx/R\nJHMenV6liQsfMOBQ0daxQEky33dJrtB7Jj/rV1ZA/LgJWHMbRv2HseYYKSQy\nu+LDwJxZVmqK9p/VrCpgWdSWR1IZjjVicfc9isJk3g9Y7izBy0sSw2SSnnTj\nCHVaeIPWJuvyDAOQxjMarcXI0n6K2uXiNuDPMmPH2SllDqTbv0KWJHBmHh06\nwcaE1oMJNo+9K8EAWMWSQC+BjPaOyP8opTXy9v5r1YFD7E5KA3w77BJBFV87\nXOd0KdIxzmuaF/bnz+CEsg51/FsQLsfMDU6fZBX2nsy7CHwcT4fvHZN7QhG2\neF7G/yV4WTvt1HG1Zc/Lrpii7R1BJOcGw43o27zEBdHmN7LoVzpFUpHp0J+J\nVZvUOYRojD4TE/gyRcHiQx8q2FBztmTn3o45/qmDMVHsNQ6dWSepd7vCnypV\nCTuEmuxJRtQq/WXA1nnMvC/gCzSQz9MoFl5eJdZDunNzdNiPwuc3tLh8u2no\n7R24UWLHiHS4Ymy9QxwNxrI5MyKiJID7mZYoIGQnBSCY6gqohE4Z98UaF035\nKyyfePlbVUa8PRhf1KjoYXeNXHXNCwPSifA2Eu3AtOvbMjVe6vAFOxQpHpR7\nyN8JxXlQW8g62WQOCdI/H4JP8JQaW7RzYyJw2hd4CH25bVxVqQm3FcYX9Bb+\nwyyE\r\n=Bc2y\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"548155009daad52c5521ea4f5d4f9eb48f7d7dc0","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.11","@balena/jellyfish-logger":"^2.1.40","@balena/jellyfish-metrics":"^1.0.174","@balena/jellyfish-environment":"^4.1.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.129","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.54-renovate-non-major-548155009daad52c5521ea4f5d4f9eb48f7d7dc0_1620039309167_0.5693474787798309","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.54-renovate-non-major-17b749d2acf996bc4c994426c9afba7e000f2ee9":{"name":"@balena/jellyfish-core","version":"2.13.54-renovate-non-major-17b749d2acf996bc4c994426c9afba7e000f2ee9","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.54-renovate-non-major-17b749d2acf996bc4c994426c9afba7e000f2ee9","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d9f6e9004923c67f3d9b8a31e14391fa871c1b48","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.54-renovate-non-major-17b749d2acf996bc4c994426c9afba7e000f2ee9.tgz","fileCount":99,"integrity":"sha512-nUBb5efltyiJGgb3h6v78o2ZXUHul0aXuHoqRiePRlBnwa0S3DluhPsiKXvrsnj+5X/DythsDquiDmVfRVhAfg==","signatures":[{"sig":"MEQCIB0OAamNvyNQ3FW29ZkFwNGg0eG9ZAy5iV0Yh1T0GzN3AiBHUyQZw07XNtygFd6jlUT6L/av1CIIvZqvgOG9VbCN1g==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":725505,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgj+mHCRA9TVsSAnZWagAAKmoP/2wqX6/gqjphvt58qI8E\nxJurEt2X4Iple4DY0xHO/QLpy/SHMHXGVC9qL+gE29Jv7qsgZjKQbaLnm7QZ\nRkSwTxfxEY5gRGrTH2J3kdEXnzHvOw/TVI0S0/T70nOs0p6APL+05omUEy7+\nH4JilK8OViW0YFBU58KmpYYg0IIAU7Pm5DrhxYSMTZplfaz3OQgivLjxjHki\nVhNeNG6dvfV0fzfttGvn/fbWKvxy9t8EhEJ2hLJ5EdQapC3YWcWU76y/st1E\nT8N5iQtVYZbcxo4PRCGsLj/au54VIyN4rvq2ivty95PrN8KkafBNA6CedVrj\nHDPbCJhc1fiK6mK+tQWCllgb/ttyV/eGpQUtiZ6AB72iYp8tgSgW+9G5IvwC\nrxIxkFocQHtJV2kcr/OwEzLI4xEEdY1t7j8C12j/ZNMiMQP9AJC0IA0CEA63\nrCjYDEnyVOc9EVgYKOXJBIuRj34GqC66P5LXIYaWftJZrhYrjJf4ZBoHbdii\nTBpeGNm/2le9n+0s3Do0ZYGtS7ot2ZH1KYGDX1/6nXrysnrxfNSt3Q7puyso\n8VLr85484csGAChP5M6Wu6oh/kKprIY4o10YtuNoI7uxtsSuO3cK4mbi8Uac\nbBLAUtqpfteU5GTHLxR0MR76bSpEbF/8/EugTee58E4oTT+U/uVhtkL1hMbO\ngrs+\r\n=690l\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"17b749d2acf996bc4c994426c9afba7e000f2ee9","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.11","@balena/jellyfish-logger":"^2.1.41","@balena/jellyfish-metrics":"^1.0.175","@balena/jellyfish-environment":"^4.1.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.129","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.54-renovate-non-major-17b749d2acf996bc4c994426c9afba7e000f2ee9_1620044166778_0.7775601270202543","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.54":{"name":"@balena/jellyfish-core","version":"2.13.54","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.54","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0ec243e4f872afb597ef24034c4324a560ee11ce","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.54.tgz","fileCount":99,"integrity":"sha512-7Sq1wezYdvvN7kdgxDHUtmnCzZfAnaBpHnF5WMfOsWeH0q6Eb/Ge+smWR0O6bveMO/CLOR6LWEYTyXk+r9mGeg==","signatures":[{"sig":"MEUCIQCjZeILBPP2FkXA7nquBn9BrWI6YVFy+S0fmh9MfEf53AIgKM7B8QOIUp0mIYJRFPXPh5O9/FNCcH4AegpxOj0iVF8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":725445,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgj+sFCRA9TVsSAnZWagAAxRUP+QC0sjWk90HDQMSoynKU\n2dWyHf/2PTYAKNDlbyhjPSaHpUqpsaqnmw+uVnz83X/zh2HPHTYDkiimJDL8\n2MhkFX8uo44fwV7Q4Yq8XclFk1lBvXoj5b+KPf+HjOnrABRwGhD9p7JFdfRO\noOhXyPE4gq35QlJuAEWUgI17paxSpk5a6q0n4LsOhgb8Jp6NjfYqHlOfhrXO\npSj6X1lhJT21stWb1MlUmDLSplfMQ+G+JpyytEosqMPgKlcUSIZx1XEyQ8OF\nsxWdnK7O2tqX8JU/Ll3jfs1JQ1wX4Rr9/zwGhZaiTSyofMxt1KO/uMK/DRBN\ngYYljaTtHjYddjscEMyvY+tE2Pw+dQW4HIPcdQr3EoJaQUyCNDhEV4zoaZCx\nQJiyZ92BqoEvTOgZb58wbdiTkEp1XfDj2XpskVdhjrE4cglNvPBd3k8A5mU0\nEb2mqEm7/MJpMQP+wqk3SXfMQiYWGCNTFfCTMp7tB0g6dohyERIsTz3b/neJ\n+ktivVzefYM+6+hXUdlPgEw8LzqZlHHg8gh5TAsr+5wOdTdrj3cATg3Z3v6R\neZV4brl0FkCc/k2DuK5hQtrqbB/RJMYXhIwviY8XfUqPdBdQaJV38xKr3D0Z\n9MdZMCxZQp4OTqhVeedOxZbLeLxEBaPb3Kalt9j1GA4M2W2UZi5w5QeihHy7\nYrq9\r\n=Ru1W\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"43d5437c30e57f7e778685ec65f5261885bc42be","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.11","@balena/jellyfish-logger":"^2.1.41","@balena/jellyfish-metrics":"^1.0.175","@balena/jellyfish-environment":"^4.1.8"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.129","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.54_1620044549215_0.6592088395010036","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.55-renovate-docker-non-major-87722f1b1e4d7d5c0eafc652df3cfd2d2808e24c":{"name":"@balena/jellyfish-core","version":"2.13.55-renovate-docker-non-major-87722f1b1e4d7d5c0eafc652df3cfd2d2808e24c","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.55-renovate-docker-non-major-87722f1b1e4d7d5c0eafc652df3cfd2d2808e24c","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"84290142e3b01c5237a70b756f52036754e5d103","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.55-renovate-docker-non-major-87722f1b1e4d7d5c0eafc652df3cfd2d2808e24c.tgz","fileCount":99,"integrity":"sha512-R+rUvMEiE2n7IFrjlaXqzaU4YR0in0AGcJpAraP4VZzyMDh/mmXh0eQz+HkGXRSTxGZHGkug9/T0gPNC4NrNBw==","signatures":[{"sig":"MEYCIQDh3PYSUTER/VwDnyHs3ENKxbCIMXSMgkQkj1NypgI9jgIhAMNXuJFTxsHU6xN2kp4fjXV1NfMAgiRNBMo9tZjSrpbc","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":725933,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgj/q8CRA9TVsSAnZWagAA5kAQAJIjC6dpIpc+zQq6HuV5\nORFVF0SCH7Hx4d2Esc3J47RV4PescTXa+ENmEW9MkLbyvl/DBGxfbhTfDXOJ\no+vEmtNi/Khco3RpUAeH++JlXvJ+9k8tljJWUB7M1d+NArzlhrroGXF+7Hjq\nsp4d2ZLHjvlVY4pME0Dy3XokTY5ySVzdJxDYkD0zpV0WUHXrZ9X1LznUtQcK\nEQdyzOYO8JVGIvarkjCv6FctJ63XW7T9d35j8H4YfSZP6zWOnkm3vboCUMMp\no5GndHUrXgD9/RDCTvIbxZxg+rdPDNPYNf+zKqBgj0npZPVUJ2DH9MidBRON\njlEzTAenefIWTxJbKDBUgRTHuC3d058iRVv4yYUavgbpaROdnvjZxXyzDdI8\n53DPM5/U+Pe7ETunGS5fm+QpOqwrOs5PX2jIzKP6cUqxRfCua06eK/M8C3O3\n/y7cUHC7Uo98+ooqbXDXO6NRCS2xfbiUI/+ZD2nHGW+vuqx1NPKDje6HvK8W\nNT6C/oyAt2fTkPdFGD7fu/flLnmz4/wtFtIqRwxcOEZml2dlTMlefbwVdk17\ny4gIWlsaqj5HabcmvE/u8u56d6fLTdq6Mbp8E6J4LjfAcXPCa+FpRqv+LXm0\n9IR3oBFceEF5Pp/sacsPuCXFnOOOknayr9u8BMGHddf1UW9tXm0k13ghT2D+\nw9MX\r\n=ulgH\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"87722f1b1e4d7d5c0eafc652df3cfd2d2808e24c","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.11","@balena/jellyfish-logger":"^2.1.41","@balena/jellyfish-metrics":"^1.0.175","@balena/jellyfish-environment":"^4.1.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.129","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.55-renovate-docker-non-major-87722f1b1e4d7d5c0eafc652df3cfd2d2808e24c_1620048571581_0.5191194350993129","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.55":{"name":"@balena/jellyfish-core","version":"2.13.55","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.55","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4a80c7c497ad5041f6e84333fef8aa740c3de927","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.55.tgz","fileCount":99,"integrity":"sha512-/zKZkol1QZkxaGCKo7DaK19Uw9PTnU62ZrLDnwmSzIA/lR3suPCtXYViEuteR1Ab9euLXhO4Cv6Ol4o0zKJ+Xw==","signatures":[{"sig":"MEUCIQCKMcdBApLNaSF4wkn9eqECBEmgXxQNakqsmkMzoHiPsQIgUj2yq5GGgDItfDhWBah4zmjO01VPmWkyVwO2afWMzsw=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":725866,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgj/yJCRA9TVsSAnZWagAAw8kP/Ajqi6QrfV6vgUWjlIdQ\nwRlXbqBntWccjYeTuJoiu1QFuHQoOHEYwmSsxu9ExIR+/KUdZaJcDB0wQLWh\njpCXzAqKmK+Qf3NfE5azbJwX0c1s3BTAugpeZBuM9B6naumubqu4ZvVPkCU2\nqcjlu+KMEM8j+UmD+nguVQ1ujdhoSMiQ9uhDOmFtXpwVwiFJCHiGkIH/WizN\nXi4TcRrW9C+AQT2DdTH5+Q2PqFj/EFDoTU3fSiU9KMGTTAszfH981fs3JQW5\n0MPJCq4Bz4kVnHm9Zt6tAVcBBBf4ZihxVI2BYSnn2bPVzDdnzciIyohgMt+g\n857mEf1LIbgybosfBzf6DeW/8Tkqh0h5PzHjnE4U55m93lO6GGGA5TisUtMz\n9eE88GyBpDrx+3an7BjW3t4l8JDDILv2L4Jpwtzmf9c1xTumRTiwBcx2XMYV\n1RGFyRHND9KT99O6HG0HsOLDoWZyvhZVnwccH0vU+3RBAtUvBh3qBw70eHnA\noQOMmaMCwAMlQ0jPcSwVGU3dsQ9kZ2ufAcWvnMTaztg5RckwOktn+9oEZsU6\n+Hkb+zy7ca4e7pFk8r0xVKSCl63N2TiMR0BlJDGzUR74AObmrYTr6Z3UBXD1\nGDOKJWq0LT670p1cWDLLE2cIbrcCuEHOBMC3ROOhMLldBjVEEi8jDhqYPxdH\nsD0Z\r\n=ee45\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"72fed47bcd2400091cee80c3314e20d1fe528a86","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.11","@balena/jellyfish-logger":"^2.1.41","@balena/jellyfish-metrics":"^1.0.175","@balena/jellyfish-environment":"^4.1.8"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.129","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.55_1620049032966_0.4835879269129988","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.56-renovate-non-major-ba45b88f31fc72c78dfa135c2887d4c81b4633de":{"name":"@balena/jellyfish-core","version":"2.13.56-renovate-non-major-ba45b88f31fc72c78dfa135c2887d4c81b4633de","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.56-renovate-non-major-ba45b88f31fc72c78dfa135c2887d4c81b4633de","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e6e8a88d2530a88113d6b4af32d3c68c8377d792","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.56-renovate-non-major-ba45b88f31fc72c78dfa135c2887d4c81b4633de.tgz","fileCount":99,"integrity":"sha512-jhTy/U4eXO9MpQsgO/gN1wp7SosudGkkwASV9iX6rFFr/sJ1xGqQ16TUrmXl2EDuoQO+BEOW/KqOwegPlI6S+A==","signatures":[{"sig":"MEYCIQCfx33X2Egt3GXJLXEMG5rAyVu0mM5hYjugAb+wRpcktgIhAKRon4eeK0Co5YIGEucOjxfvG+vBnGkL+a6LWYo+qM5S","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":726355,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgj/2FCRA9TVsSAnZWagAAQsYQAJmlard1kkZOKmdrsAzq\nkrSMhUiJYsCpekq8KoJtcoSEtH5nqmvjXA54NR9vkim5EIlCWP+49xo65m/B\ncrJCYPf3Id9RWrWGU6umMS7G4VZi975u907ieMRb/Eh6TvB9WITIk2YX7Tzs\nu9i6OoMyLM1+DHjqcChvIkPoqtxXuI2vVFWddX3L5fe5TdbWG329ALfGt5vf\nPF9qusLOvA/nurayiDWa9uYxFGdWkGjNQ0NHWPpu41T6NhrXUOhAA39C1f11\nSRHc4Mmvu5Txbg9Htp3NKV0A0coksz7cxEjUeHxd8yGJ49dGcDdc1MFhaYrG\naFCTTkXQaqOWY+6SSS3n3lt0QfdxPApexeg5qYwhqrawcPqGpkS2n8IL1m1B\nXVWJBHkb7+7P+yjZm5xt6Q49d7e+cIK3WY4eAWDwOaI4rYbzUqjubj27B5H/\nG/J6Bj/De/ZkB9ckbk/MLsOIKmFLC2gZ9Z9ySWlXj/L0CC8B1z9B8b9JfzQe\nr3GHz42lfjRbZwgP6FiYUgUnIq+Q2RTl/WbLOarW2P2+4aCJ0TFP/hinVXLE\nNfjF137lZ8TgdG6HnlrMcFy11yuiB4VkgMi9p8LmAv42s9Qtd6oq/Uy470y6\n7q6O2ZZ9V1j+B79rgfI1yx1MM0NIQq00E2szkJ4Nlq2nkVLtM0PDHbpmXMzD\np95/\r\n=ryTE\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ba45b88f31fc72c78dfa135c2887d4c81b4633de","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.11","@balena/jellyfish-logger":"^2.1.41","@balena/jellyfish-metrics":"^1.0.176","@balena/jellyfish-environment":"^4.1.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.129","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.56-renovate-non-major-ba45b88f31fc72c78dfa135c2887d4c81b4633de_1620049284903_0.8816958858403314","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.56":{"name":"@balena/jellyfish-core","version":"2.13.56","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.56","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ae6b7101dac3912274883d8d24b760a69ed2f25a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.56.tgz","fileCount":99,"integrity":"sha512-9Cf20I408M69tjnYfVoruLUI/wbCZ9DdRyLWeQYkyHIpxGdzSNJPZpbggXMq34KTfBCjOF3zlNpxSZNBAxboEw==","signatures":[{"sig":"MEYCIQDEGeBU8mesNxp9SFkKGRD66lWXU0wEK8+5h9f7uSJxGAIhAMYJgLTbeMe5aWVb5XM0rniAgQJruKxRGEHzUrsZ/0ov","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":726295,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgj/91CRA9TVsSAnZWagAALFYP/2qGv65sREYN2G19Afad\n0ZZAM9vrBMVN8mQIyR4/QDzy2oWTsW6j4BP/TaGs22FvD2AMaPAiUikleYwt\nBBproUPuPQjm4E79xZii18eK2Vbs1TG6gkAK0ATg1ycAfKk3b4KhZFjQIrXT\ne21jP9I/uI1YxOXxs0KrcO+gGuYnlf8N276f20cp31tm9zoLe5fpM+M7/FS1\nRhIAdpKw3u6MsHWNmLM5REzMKL7Q8Rgx3O/ueCAXcbwzAcUOCRdfS/4y4t0h\nBJxKuosODJySem9j1NM2Le69Kr1y4sEwWgvkBvABbA3ZCHz9C165aORAL7c1\nOBBBBGS48JnRcYmaTPSydI4yKU5x6WwdSmrrAfwkoSSd6hU6HPZw7UMfuYUc\nI+Tn4HL7TwQeFR9ecyTWrYLI/6BmX07h8QpH4QALRB19bNfnZBXYE4xfvSJ8\nruOeGpj8Du/a1w4f84nFdkNgybtEc8e7XEoPbY/IRKSDW1TUOUxqZI+dr7f9\nRWLUG6VAc8LFBqDUmohZQYvZ2aStOQPPsxuDBfBFmwGb2lf2G0ngnSOc6vLq\nTFQOWD4VgMqUS7Bro3eriAKmG79/Nsmk1u8vKfQsG8gmLjYXHT99+b1SAZw2\nse4MzukIJtUCweIPhlhQevlSHtYM8IaChaRUE56NCwixYxfjf0hu2KKrQa6U\nO25X\r\n=zbdz\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"dc7fb8ff9f6d667e515828707e2900b58c102aaa","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.1","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.11","@balena/jellyfish-logger":"^2.1.41","@balena/jellyfish-metrics":"^1.0.176","@balena/jellyfish-environment":"^4.1.8"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.129","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.56_1620049781061_0.2686200288296592","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.57-renovate-non-major-beb628227eca71029e4bd4636230ec087273364e":{"name":"@balena/jellyfish-core","version":"2.13.57-renovate-non-major-beb628227eca71029e4bd4636230ec087273364e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.57-renovate-non-major-beb628227eca71029e4bd4636230ec087273364e","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5a27e0f1ba96ba0f751ce601508323c11745d8c2","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.57-renovate-non-major-beb628227eca71029e4bd4636230ec087273364e.tgz","fileCount":99,"integrity":"sha512-lLcpZO2fXkXgB35hnpsRh+29U7t3YFc2Ba55gmzqDNKy9NfTYGAErzEv4K2586RGL4jBivDgy4QF+I8uaXoUtw==","signatures":[{"sig":"MEUCIQC6IcuvIf2YRgbEpwArhHas59/r57WEFBwUK/ITzIBwwgIgXo8MTvR2wq6CPX1v9eR9cE470ezzT5Bo+bv4w+VR/C0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":726706,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgkCmXCRA9TVsSAnZWagAAFy8P/1+Sk+elKZb+RlFaN7fg\ng+Yflf4V1lCyUCfSVKLPy4/HGtliFWEi5FVoNZtZ7atps08oGC9j6cbJemKf\nCQP4KIh5ScX7oJXjFwOniTVKG5fkGCNCLa+3U09O5u1Yc0kmw5vIDoQwm5UZ\nOaoE6nEjmS6jRlZA3z1n0OYSMwasZlyj05aFCGXrED75zoWHyy8cCYHGIls4\n4L6vMnrJIwfPmzxUmDgaEUshUcvwlIW1oXhAA/LPVUUy+EEaY6+C21KYISLD\nYdIHUrEnPDRRmRoW5wzDW5fOoaCkxbgSWSLvEQHnPelaDvoMcid1XrWZyHb2\n/bfYbviAgA+M142DaKinJKYbp4AkNqxN+2JbI+T2spypVo2nVpbcZM6Lp8p5\nJTPDm8R9imK3cLubIoFGCObFTnWbd4C+Qel11/3TKioyAH6bdkZ2LZT/BvHx\nTJBm50u2i4gQa3xa6yq7WiquoRZyh6Cj9ZpcCV1OCOEqZxn65do/1xKb+NgL\nP7AjFDGA7WmdEBcT1nsluzL5JJWSqsaRpxCV/eggIE+hdtZYg6k0/WWlRzGa\nw0cC/oAjp+98ugmIg9qQW/3Woc0541rpjQAO4JVEMvEwWFkGZvrFLKDqciRn\nTBY1mb1FNMdZHLoAvlWxmkJ/rE/ghqobDNG4psa/C3MS+NDca6KMCNVG6bQx\nBmLG\r\n=EudD\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"beb628227eca71029e4bd4636230ec087273364e","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.11","@balena/jellyfish-logger":"^2.1.41","@balena/jellyfish-metrics":"^1.0.177","@balena/jellyfish-environment":"^4.1.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.129","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.57-renovate-non-major-beb628227eca71029e4bd4636230ec087273364e_1620060566461_0.9976218566259909","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.57":{"name":"@balena/jellyfish-core","version":"2.13.57","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.57","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"793dea324b211d342cac936686042f3eee0bf8f4","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.57.tgz","fileCount":99,"integrity":"sha512-cKdNS5+9YUv2KYrTZbx66pF5JXrEaRm/sQky71/cVj3x1j3m8ZM65ogTk/mp0R/17EhXl8lVjh9ZIGtBlDRm5A==","signatures":[{"sig":"MEQCIB+Zauyo2cO2Dstdevz5n43eaRtHr/Om+cHrxs/MF2PHAiBuFJbneFThDHvq5Vjdv0M7FxSk++Jq5nn3ZX379Yr0wQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":726646,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgkCtUCRA9TVsSAnZWagAA40wQAI+DRe+b6+5XMvKvoyru\n9eo3/RhByPQsDgrq5FMTvUU6nBzP7VLEaH93eUZMDJ6QflbkyUtQ4w8qnwt+\nNKOpV59RZy/c1LRRJ9g10k9q2Rw5VTvBREhA0Te7w5CePmvy5qhE2vuh99M6\nGrxtCF6JDtc6N3ojdCXWYkYAqQhQMcjZWpbwjvWllMWsia0LRzaJ2c6ZqQbw\npBzfw88l2fpD1A9Ky2kHQ+cqGh64OflpckFASJmiIfVdPctL2sZIxH0oh8Uv\nV4wfDtmRfsmVYjaUU/8KrnhO+RyUfXR2AzR+SbFOFeBB39zbsxJMWrI2arwS\ni00B93TbBOUHLHftigJFuXCSbrtQYoFzyNbmZcAekxmjF9sD1UdCHM2hOUXY\n/+JWvHXBv35eskZMytP6JJh1A8HEK69Ih1/M9AglWdU0hDNVHBQs62VqR7E7\nLmkfnVgjdchBCbGnKeNmhw9WTOm5cwRiA7Fw9nQd+Xk/GIV+xWl92dYbKE8H\nfebu73yElSTHamMf4FIzMo7CxRfEeqBvI2jqbsqF/K/0If2kkjwpVHSB20i/\nSrZ8UrN/WNhC0hiABm8E/gaveOGNmpg41T4I+Gjh7uxOccDpue+S4nezgTLK\nKJTqV4eiMsXFRYrdnek9DfkZ+aJP4L0hxs0Ivl1cHo7swpQxd29lYrQXymXX\n3oDM\r\n=zTnC\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"1066f8cd973a278564888a805dee61f415859615","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.11","@balena/jellyfish-logger":"^2.1.41","@balena/jellyfish-metrics":"^1.0.177","@balena/jellyfish-environment":"^4.1.8"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.129","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.57_1620061012019_0.5755740481075531","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.58-renovate-non-major-ba18571476c4c45b947a68a96f7667779f6f4a66":{"name":"@balena/jellyfish-core","version":"2.13.58-renovate-non-major-ba18571476c4c45b947a68a96f7667779f6f4a66","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.58-renovate-non-major-ba18571476c4c45b947a68a96f7667779f6f4a66","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ac12fa2e89ce338b57f5abcf7f072143e6532bf1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.58-renovate-non-major-ba18571476c4c45b947a68a96f7667779f6f4a66.tgz","fileCount":99,"integrity":"sha512-ChpWVvLKwoR+LQmPVbWdLdp61v8nYbJ9XsUPfQ4M4J6FLoeijFeF+E7ZG8sW86JuohkMVUGamoqFh4RvUQvH6A==","signatures":[{"sig":"MEUCIQDq2J0Ct5kFZr4yQFS2cG1Nj4aP2s3093PWo7r++jHsGwIgQSPuxHCB12HrxgOWQEwUP9SFbvcsrfbyKAy0fUxT1oI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":727135,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgkD1lCRA9TVsSAnZWagAAG6AP/3QWGFnOTcqqY+js7o0g\nJEo32pAd/2PxFidtWJ0DidSpRyU1rukdZJHKVfHGjQ3PvUS6xofzpyudYcJ0\nclzUFodDGPfEtYFk6YjbG1UdqHfAo4b493PyeyOVo1tmscZ2K0CdJWOPqa5Y\nZwGE5GK4SiQ5ErnaHCGXeYormcz4AosIHli4aGJxbUtp/7/KSVzbQX8DZMNq\n4ZOSt0O2BUrzNqjNa6Vhea5+0NMO0stq5rzcp6pmOJ9WrmsGK8mox/v1E3VX\nOe/DlAp6nPppVZABW9Bd8SDu5Eble2XCkH0D/GDLqjPYMIQKtr52usuk/x4z\nGNacGEcdtw2aMT6HPHxFg3Hxpc/Wujwwg21jFrH8iYJZQ+QiKAoccgyZWwQR\nDeQhP3GNUucdaLSkjXH0Br7P3cxf0cY5AqY8PqRa3yRGGNTdsWfS+gectMCc\nE2d6H0ogGw8mTzgUlVE4uJabd/+ULsEq2iRlfg7U/8nwnYLO/tD3tIHA8rW9\nPVHIlKbx2HEggICqqZVwbb85o3VURIPZjCrQ/AUtiS+MjLizSTSCyx2T8/5A\n8OBXJ9aOJvjdG9XVFkyb6KU0WpF8QwKlDWcM69TpO3WN7H+czUD+heSsYiBv\nyeMFL/PRaNzHXEu7VN2QZcOsgNv6tHuqgeR2q+9DKUT+TUJnNYrYXM6k7hOv\n8oQM\r\n=b09e\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ba18571476c4c45b947a68a96f7667779f6f4a66","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.11","@balena/jellyfish-logger":"^2.1.41","@balena/jellyfish-metrics":"^1.0.178","@balena/jellyfish-environment":"^4.1.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.129","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.58-renovate-non-major-ba18571476c4c45b947a68a96f7667779f6f4a66_1620065637326_0.10786385508667973","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.58":{"name":"@balena/jellyfish-core","version":"2.13.58","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.58","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"663ab946d0131d968320b6befc31676be00a4c23","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.58.tgz","fileCount":99,"integrity":"sha512-P85CkH/GT9PJKW8fDr4w3NAuOuAxDnsH+vOo1AIfSaG+eIDFcBFSUnlsozX2OPy0I3x17h8eQVXBELz7UPaxwg==","signatures":[{"sig":"MEYCIQDjGLfw5IvBt4k3f7GtifsGH+VPjY9+5+IqQDkxI2cdfgIhALoYG55tDXqdkUnZA2XcjL6MO5Ar010cJc/ytU2UHixf","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":727075,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgkD6GCRA9TVsSAnZWagAAu8wP/Rui2cozOugTkbsJPveG\n1Kbbfibo/YCL7MlWqyXeFBog0BXS/C41mjOcP38FocA9yB0DgBz3Ud0XGwVg\noxCLLV6UKF/Dyzo864wq3gEXab985LslpR+UNl7HygrnK+u59kMk95cSRvtm\nS5z/U5TzNr1ViU3+eflfb4Hqc3N6sGpwMCHn4CZd+dwBS7GT1HHgpGUcyJzu\nup+OoZx+EUsfPTpD0LQ1xSWGwQgb1xmYcEN9oQ0zfZgbQ/xyB5qmZY6T5kgg\n4RGO+NGQ1UyYiBKMvZ8vWa7fdpQ9QbKGxnJpL/UYAFzhUrqU+F5xbiGuRw60\n4TfBFkutPm0MSYkp7IPhKndRwu+zx2OJC311p4khSoSDuZKvrczn30EWJoko\n7ixkJlpSDPS9mQY3jSqG3fjWxgVQqzwXrZZp1y/MjRqrxy6scx+7EP/FuSOc\nmXMoLLB30XOkQraXSn/ecZreu5AK7Vfhnpd3tyXT/5/55Mbuge/1mxi+M3ql\n015fP4UczvatwBBVjUbhw6UxdmyxtkH1vb9lJbXpc6rx0+0PjKPW7Hy31MvJ\nhI6SLYzlU4k5wdlRID1NSi1o+/5vaE9Y0OtBwJIp//rk/oTvFzEYWri+3+Eg\nxzcUSOH1e5obJMlagagnZ8Cw3jkCw+W0VCDARjLisICXK+4QNh1zuvubX75/\nUXaD\r\n=kpwi\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"3b22d031f9d2b9a984043dc0e3d1d6c423ad6b67","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.11","@balena/jellyfish-logger":"^2.1.41","@balena/jellyfish-metrics":"^1.0.178","@balena/jellyfish-environment":"^4.1.8"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.129","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.58_1620065926399_0.5348038874639689","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.59-renovate-non-major-5199c36573bd70369b2e1d1d748d95c4d5982bfe":{"name":"@balena/jellyfish-core","version":"2.13.59-renovate-non-major-5199c36573bd70369b2e1d1d748d95c4d5982bfe","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.59-renovate-non-major-5199c36573bd70369b2e1d1d748d95c4d5982bfe","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"bfd1ab960cc87c8e6c3f8a9b8dd6d05a8bc07f8c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.59-renovate-non-major-5199c36573bd70369b2e1d1d748d95c4d5982bfe.tgz","fileCount":99,"integrity":"sha512-Sxo3ZiPptHt/nWkjC3Om/AXjhEr4TRvNIurN9Omenxn70/oxNJZYq2THisffSoAyjYoO91+z9NLTO0vbpihCYA==","signatures":[{"sig":"MEYCIQDSF1nr6Mw1m3tT8rXZE3d/ZYUibXRHWYHaaWAEG/2tvQIhAO2uOThoJA9yOLaOisyexYpMef/P8XCqTnL5Tz6+uWHx","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":727564,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgkG2TCRA9TVsSAnZWagAASQ4QAKHqIRQ5QHEal+8v5WDq\nLIppS14btGWO+0lU0/LgYAyO5PuDXyzfvh2TKtUFW8YEd/dQjWrrAktwVIUP\nWcp/w9/eVnVeBc+CGNe45u9fwhMvL820AvWBu5n76LaQ5XhmsGj6FQl24A7Y\ntbpIwBr1kvX5s3zPXyeTLk6YBdQyYNR22w8CBYeOeR5BBkEtuo41FjeGU2x2\n5y5q5m6y55gfrCsXpQ5/goU5xc7Idlcalp/66ojZNZTavxrKzEgzlrhv9lju\nA5hD5c1PqBU8UqNMIG8rXSgDNAJEXBJsTrCdpRoaXJHFaqjjnggj5oPID2wJ\nf202VocWImIWIo2XusIN1bwYHk7SfiPHPhnmVx6BeUzW4PQW/GOaJnJEivyn\nPZwhJ00FUlVp1ET0BzS5leMS+muFoPhTLoxEyTFc1qPHjUv2JG9cmHfLk6bN\ntueEw6O2CeaYtgYXEXMrewEjBsTxXcQ/QCH1bjguULkvG/6aOuunyGfkxeJO\n1u3RIFPOBOFzpI4BxLI+ClDbRmAI3ERS0acWam/VtYO/Yie9LLxfwqZWbUpt\nQgn0xUv3Z8zikSqga+82S2wMOuZe9fQEIDODd88QFnRpLlamIhD9ue0VATsn\ntRixtbNakMPW3TkZW3F6E1ykwVBuk/00EXzz6ql62wItG8C+WXQknD8rvl0k\nNS4f\r\n=osTd\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"5199c36573bd70369b2e1d1d748d95c4d5982bfe","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.11","@balena/jellyfish-logger":"^2.1.41","@balena/jellyfish-metrics":"^1.0.179","@balena/jellyfish-environment":"^4.1.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.129","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.59-renovate-non-major-5199c36573bd70369b2e1d1d748d95c4d5982bfe_1620077971321_0.8173217361295344","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.59":{"name":"@balena/jellyfish-core","version":"2.13.59","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.59","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"cb3e11e6e6bed64c0725ede46659657a40c6ee07","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.59.tgz","fileCount":99,"integrity":"sha512-ra8sVUGZnJL1wf9NmbB/WNsCb/Ni3l8uVEW7gm7xXbpfbN0SbrFiSzxy2dgcLmrEEWmChiSK3cvhywpAKK5X1w==","signatures":[{"sig":"MEUCIAqm0AfciSPgqTu4pvUmgKTbG3CX0cr5zOHaXOAyoioXAiEA+cqFJIshuCDQdDJLCwjBZJDmjco3sCO3pytZblHOxCA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":727504,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgkG9CCRA9TVsSAnZWagAA94oQAI3xCWuIHnEHPSQqiD04\nwIBQfqYB3/xuTTE6i/Rhw+KIfGPofu+L7+sOiYWTW0FK6zJAVrEWyiBiWjgd\nbm+kCG4vjtGgAo0i4df1SJRrtpTXIIG2s4xmZUzCtJAlt1QgjMLhRP0Etg0J\nB3KKHEuDYExTKyvT+M4UfaXzRlkv5vEPM62/MOnseXsOJ5qD8ZtZhuhW3Iih\nZvYZ6V8j4wYcNvJqbsq2J6ey5T4+uuqqHxnxLwsq6qWkceYj2bywichDuEGF\nonnuKviUAGg0fTpKUSBEwJ3qeMlGYsGfW7lhgV/deZ1Njr/DRrWy4l/U1jr9\n8Z3bp14i8+bema9VabqH3oopEYQAgji+MhCr7fE4Txop/qoMni+mQBfD4roI\nHRxTJRGBeQ8Np+H+BM0R7qQcVMvTKGYhVxuJwHBea+8abbQAnx7fbSmQKRCD\nlSEu5D7ZxuGgc/SmtEc7G9E6/C2AdoVDNUgvqi10yKitLaLH5i95axd/cOvy\nW3dOD//91izLM/ofl6LViViPND7ynU1OpNZnVtu6+1Dl1aAHZQOO+XncEt7l\nhgRgx+vqI0kzlHZRFpu1Vo0I2VfgpEsVPxqkEV/G0i2FFIb0UNV0yZW45Y1A\nfxw+S/ycXOW36GsnMXJUUrqWNW/bdVGozPY7ilnMrw/4XtR+HYJcdkduoCoe\n6CxW\r\n=JasY\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"5fcc45940f46110fc2022cf835f09859f0c71687","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.11","@balena/jellyfish-logger":"^2.1.41","@balena/jellyfish-metrics":"^1.0.179","@balena/jellyfish-environment":"^4.1.8"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.129","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.59_1620078401415_0.4104121059719763","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.60-renovate-non-major-401b9bf084b02483430cfa4415a8c55cbeaecd76":{"name":"@balena/jellyfish-core","version":"2.13.60-renovate-non-major-401b9bf084b02483430cfa4415a8c55cbeaecd76","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.60-renovate-non-major-401b9bf084b02483430cfa4415a8c55cbeaecd76","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c8612fe580ac86140421abcd3de961c5c2cfdf88","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.60-renovate-non-major-401b9bf084b02483430cfa4415a8c55cbeaecd76.tgz","fileCount":99,"integrity":"sha512-44HarVMyw0KxBFreYsI3gYUjYZiz7TWR3A8szMWuLkBFm0YYXKNkL1tzz+plCMFTUP2W5QaBhqFi58PpRaqJpA==","signatures":[{"sig":"MEYCIQD4TbWpFVHhvwxV5u5if6ION4Mqv2M2RdsmAE3Xmo5OrwIhAMjr5gLGKlHzMGUr+KYDjBNHlpizLH+OAtunfTjNU4JZ","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":727993,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgkIhmCRA9TVsSAnZWagAAF8AP/3iPqyNnuqfQLjoQW2Ft\n8Ygd1x1B4mbZ/0lFBwqK0um4PY8QlnvgmKbs2kYjs5o+nEGfNaholTURGkgn\nN9YqgvetCyArNLTlsko7PKJspwWHim3nkqfUXfkgFjzfjPOe2D88QVavMXb6\n1Kl75bqSk/KXr6TglAQAR9QQyw4hKEd1bCMjIXHWU7CCMYjH46r+zI2MPvxy\nELQAirU23PJngLSIGxe3DhWWrF/zjugmM/fACuYMmRzhh6DDQU8eKbnnrx0T\nZYBr6IQqHW0xBKV/kAc3J6DmhYMjS65tji8ihhQdFCLgu4PFS1m3IHesoKn/\nc6ccVjwXZXpgqssLadXe0FO2kkeit7Qvyl0C6HJ5En3Ok+23RsQ30WpeMNK7\nQ3ibQaRgl8IwONspKmKUvkHcXBZX/g3nFuF7JqRLVdSa+jM1p7Yx0LRj6J0v\nDpPOK3YwXVxelSxVFfWLQNtFK7rBQTLcMQwVbmBZqiw97BmGGUXYJZiQHRKr\nuvuSsT1+9XHBWplCAprCllOVpbxaCQFPJYBtPhHKi5xcWaSLzBgnv0kxZV4r\nj571tMmdZ8ZZJK3Ioab0bJZVJG0py0meEOiWkVMTufimP0hNRnCY1Kwl0ocP\nmIiNlmxWh3Iuh99tGz7ZXSvoJJ0lZH/dggjm/u7XZnl5pg5Ev44TPCHDATco\njeWn\r\n=4b3O\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"401b9bf084b02483430cfa4415a8c55cbeaecd76","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.11","@balena/jellyfish-logger":"^2.1.41","@balena/jellyfish-metrics":"^1.0.180","@balena/jellyfish-environment":"^4.1.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.129","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.60-renovate-non-major-401b9bf084b02483430cfa4415a8c55cbeaecd76_1620084837807_0.35501103649669785","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.60":{"name":"@balena/jellyfish-core","version":"2.13.60","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.60","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"43cfb0659fca61fbf90f3dbce1c875ec82183c84","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.60.tgz","fileCount":99,"integrity":"sha512-dJOh5ypa4KZUzjMTQ+vxDYSXYUC1D3TiYTvCl0rw1VNAdDIPE7JSAb0imj7Y1KqRq6ZmNbdidfewsM14KBtMHQ==","signatures":[{"sig":"MEYCIQCHk3xJvSdy0wDd7/SRJaGqfTEraT2K3wk9mrWbz+QwagIhAKPQK+Pr5q+zk1PH9jICT8h172T8EASSGRJpW0oNx4wk","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":727933,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgkIn+CRA9TVsSAnZWagAAbWkP/A4S1sT0pxHZosBjI+cm\nwQSa0uPFeYEUCFGbluFpzjFXQFWlC6GhqKIs2MEsBm1yyEg7AIKKXQL/HHTO\nyo2ddsaCIHsopv7sXXj0/BGHHYLGCSK2NmZXiQhRNYjTvnvuz5EtwgDJz0wB\nIf49M2gwHe1ZwXztv6jK2m6qMqNFK3rtbYCvlUP9mgiKJ56YUjE89/oxYHhH\nEQHxGIGGtWU2TAg3+YI+4nR/Cp1xgoXT2r/hvdew2YAExx0crtgUhTjUUcch\n6nRz25hzn2UTjz9458qCkVZ7ZPILJ4vBbS8xuBlQJdkjBZ4Y101i/zHFr5GG\n4cEQHYZOW4t909AQd4cc4vcoHHIGuyXsPXHHNZqcagPSaXfdCcfJrq2eRioA\n61BhkODWVlUGh2ZZH+izj67SU199SAcilD/M7D844r00YjQYXiRNldErE+cJ\nTpeHQGRGkWIv/5R6BKoKUkkALNC3Bra3TmCXnfcD+VrO+w5cm0lcF5HqmAMZ\nVHGrPXAPa6HrlOupvfKDjRCVsqVeKbbhce8I7yoLTSbWwPCxYAq5HcOqBmsA\nf8FFHUxm9C0W9ANJh20ATYdZSwgFSeXvtsDY3c18/5TK4PMQjCpL8Emxpekw\nXqXnI8VpAhv6dvbNbJuZyQwUMbvAlLL0hM6FhX1AM0OT53Sj366ssA9WxhiE\nFQS/\r\n=skPF\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"00bf17c9faab25a23e27705a8eef61e8ecfde3ed","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.11","@balena/jellyfish-logger":"^2.1.41","@balena/jellyfish-metrics":"^1.0.180","@balena/jellyfish-environment":"^4.1.8"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.129","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.60_1620085245938_0.06826441791770121","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.61-renovate-non-major-7e66fe0f8cf7f7133f4502c8e62dbf1fde286451":{"name":"@balena/jellyfish-core","version":"2.13.61-renovate-non-major-7e66fe0f8cf7f7133f4502c8e62dbf1fde286451","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.61-renovate-non-major-7e66fe0f8cf7f7133f4502c8e62dbf1fde286451","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9140d4ca129ae10e11bf5acc917051ffd4b7d25f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.61-renovate-non-major-7e66fe0f8cf7f7133f4502c8e62dbf1fde286451.tgz","fileCount":99,"integrity":"sha512-wC6+i8VSrThVZhyXoHu7YKonNX8O8e7BSOjqUoHhdMe3t/WjILiLKu1DYJxLHMEr0Hpzh4ANgPs1a/M7yTMhNQ==","signatures":[{"sig":"MEUCIQDIcW3QInh+ktASfQuC8uswAN/VqhcCXBEeJUp1T3XtNgIgcwtJYADlvngNEHPPQ5xvyZBTwBfjHy7SLpsLnhGHl5o=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":728422,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgkJu8CRA9TVsSAnZWagAAw0gP/0ipzRZe4618A3erCxcg\nRw8kaIpQxbCs0qEIk8x9B13QdTmkJhJ9dkk9G3Q4CEx4YJTr4fxUBhLW27Mj\npTNGTzQNkKydupNeJun5Wt+5ubI0iNpeuNUt5/3H7Ex/brldGsL9bvD5HnUK\n3ODTj4c0dHXyZqFaXe8p/XcY3rXvLdP7LppR+yXuoBYWIyU19uYCXIwndJxs\ndz/hE+LtOtZNt6ydp9fGEk+AxK/gqpN1XXpYq847686k/huSp4JAS1iSSUT9\nHCvrmZgJE3Rh6AV21ccJFDeu6EmrA4cVswXUFfD9wjmseMFu4LrCPusImkSa\n7rmzqqkJiUeh8nwjfapQhtvjKMfgDspZ9fSx/K3JBoVA3vUNnFiZKTlKTAoD\nq259/mAiyj7Vp3GMw31J/IxiLAs9Tb6gH0vS0cINrpEEa5BDP1N3LOIM71do\nTLVjXsBfQt8BinDt1+wm1zgWItS8YKRcRMkkLxuZGKy5zPQjFkud1EcH4QEg\nq+rbaprKm1bDMwpCa5I8YOoTcXEb60aD6EJ8Q0f7mxmevBsrzHaJjXL4Gs5S\nXW10HlXddVUY5LFvxCVIbuTc63iaWhYW4JmZ8jYTkJVcWwTRpno1Y60fNOGw\nXXiVi/Pjrth/1bZB6M3mnuSSJsGTphNVrhLcH+jCP5+xMZKpcH6uwjK9ui8N\nwD0D\r\n=C+dI\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7e66fe0f8cf7f7133f4502c8e62dbf1fde286451","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.11","@balena/jellyfish-logger":"^2.1.41","@balena/jellyfish-metrics":"^1.0.181","@balena/jellyfish-environment":"^4.1.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.129","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.61-renovate-non-major-7e66fe0f8cf7f7133f4502c8e62dbf1fde286451_1620089787888_0.6623137671895687","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.61":{"name":"@balena/jellyfish-core","version":"2.13.61","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.61","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d6e7b524811e622a57ab0563bf02b8340ce614a3","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.61.tgz","fileCount":99,"integrity":"sha512-HxeDR+acmqjt0NI8Vjbg0KWmQT9NRCA2Jyaqx3mvyNZpTRKq/pN1kwdazjco0fUdP1DIiRt8LC/mgdE6je4Wpw==","signatures":[{"sig":"MEUCIQDY2flI6ev6J7G+sO4MAxVebTA+9M0GatcL19b+tSOI7wIgVtfARbZULbSxVs6cHxXXgE+LhXRGhGQW395euAM2JLc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":728362,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgkJ00CRA9TVsSAnZWagAAqOwP/1lxJOy7IUM4Xc1NmvrF\nfJ5w4dAJL4eaGJbwuBWyuRxkZnjYrpVvRH1c1OJvW1raKqv0UeVOj94uGKkI\ni9L9qFNK9bYn4ehXkR1Mn3LoCdfFYvAaZVG/UCjTKGRc0xn0l6AZeUc9lL71\nd1BsQUR8wkAzK+R6zOwugVKOAwmxQDNgULziP3wP50eNerwmsO03wzBnba4H\nwPoNvxf8ejMR41/IdoIO0AiHC+RRRz+pMJSwC+BzbjW3grvqA7KogvlpaCbZ\nZliuoE3WT/twuBSp7lzcjnkjDQqzPL4rft1AzokxGKNP7Iv34aB3Y2BAM2FH\nBiNYvAaBiD0Uw91rrT8Tzstkt4bBFg+14IZmdyyhes/8UfPuSl/2lX6Dgjfh\nMtxoM181uplN9ENxKcuxFxmkcGAvZ7z00zHjCJ33ujlRjy3YLudPs7wXj7lU\nOvOnbSnc+tipsKAOYoZtnnunBSp721tSgfACwGrrooS0ooB21UH6+UrFwzeE\nJLW+K1ZzGAcAP7j/PzNrAXVNYZEo2WYfsQ/iNfrzG2u2ymaiF4ty6GMdGvdt\nVF1Z54H7XGtqlwOVRdFdFupcWVOT9XWddUEHaBh2giNezeSLVa6CwZLLkvym\nkm+Q+A9JSogTKNFwnScs4Lp9YZwHyUcbl+w5lGqZ5Yvzvou4/OhaRk05AkRX\nUTjg\r\n=V+CO\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4623e403c1ddd9aa9983acc99311669bf820818d","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.11","@balena/jellyfish-logger":"^2.1.41","@balena/jellyfish-metrics":"^1.0.181","@balena/jellyfish-environment":"^4.1.8"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.129","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.61_1620090164419_0.08262759156333543","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.62-renovate-non-major-194396ae6d1ec59403d4d25154fe6231c334c753":{"name":"@balena/jellyfish-core","version":"2.13.62-renovate-non-major-194396ae6d1ec59403d4d25154fe6231c334c753","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.62-renovate-non-major-194396ae6d1ec59403d4d25154fe6231c334c753","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"63fdc00c4b5e20945ef949f53c3425b298967766","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.62-renovate-non-major-194396ae6d1ec59403d4d25154fe6231c334c753.tgz","fileCount":99,"integrity":"sha512-NmFNmNE0dejweUfzY8S8V9VWXYBeW2LheXbZZYPtwj8URbSCBfO2uTBYqs8NcVQBsL/gcawE66ZHxfnNuv25Ng==","signatures":[{"sig":"MEQCICSJpqzV0JzCa5mflsBMtixTDLIA/GXNelpTQlLtGYjmAiBOKBC8mVAQceQP2rBPsPzsF4E+5MVT98XZtYmX1j323g==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":728845,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgkKgyCRA9TVsSAnZWagAAJSUP/3FepM/3tptAA0d/mASW\nspEPbfZDWnJdinbG7IrN9Xa2BMi2tEwWr9uGBWp84cHdLJE7DRyPWXzZGuDA\nHEkEExDKH6DgTGbO/CY+esaYiakoRe5Fm+kdaiikh5PbJeCOwX0SyqElBgge\nYMvXbo/D7wvfsLUMGRxM+rrWxtmtwuklvfHEoB9ZGIPS1NQTot3yhE0hCOUN\nDeiezATTs/voPU7U3mxdV+hQteyjuTbB7Y7BvpIXo6YedZIKMfN9fdb3dChF\nIKkFZH3Dw8LnGKskeR1TJq2cW2yo8HthczM3UZJHO/MvQJ55JN+WbEqZ/dpq\nHZjAXl0XzfAUbhMUzZO+54QQQbsROMDix9XHv/ZkyJZr1txeYMpTNOOeUJ8s\n6yjIpSHzLUH3SczNG1cOoUXKb0UD6pOPs3+2wEUHXWtptdcWzMt9+BhvRlPA\njgPW/N2eKUNTCiSEznMNIW604Etl0RVf3Iv2j3mVNq60VdNJH+85b04eHww8\nKk0gh3sKE6JTkpLat/0FWxyzd/TSOl+yBld7EPSOQgGrw4jHt+D6qD6bPKIj\nY1KqKP+ROIVEwc2UozMsaDJcToUzHBKaicmdR6ZF0247RlBxUT1q2f2zgx+x\n+Mt0xY9AkVWzoNm0EOdfWWgTFU6ojpWj713QBzYt95GZETYWpwfDjlgeSruh\nvJxi\r\n=F++a\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"194396ae6d1ec59403d4d25154fe6231c334c753","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.11","@balena/jellyfish-logger":"^2.1.41","@balena/jellyfish-metrics":"^1.0.181","@balena/jellyfish-environment":"^4.1.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.130","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.62-renovate-non-major-194396ae6d1ec59403d4d25154fe6231c334c753_1620092977398_0.518982272391955","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.62":{"name":"@balena/jellyfish-core","version":"2.13.62","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.62","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0480ec37a7779940505df7975e20ee6db3d05ab0","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.62.tgz","fileCount":99,"integrity":"sha512-Ul3EH4pUKhK7xkTMDWCWMYpSisMSkbJHEJiv8SgWR/CTXAL4R+szDP01vg5nea0iubQeiHmvTnAHbdPVnN2wsA==","signatures":[{"sig":"MEUCIEaCvAMSzODpjWCtZJf9q01KmX0BkoAiIibKs00q0FEBAiEAjfF5MqOxDqE1XxUnnbtyY7oEnKaP8nOJ0vD25zbsY1U=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":728785,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgkKmGCRA9TVsSAnZWagAAdekP/iDcnQRJ8PKXgjwsrjcL\nGGM/5NHb4PA2binIuapT80z9y523dLuyWS58Yijk9i+UbV9EOXdSx5wtge5A\nAEG1uxFLJ+KTmLH/6qBCSJoExJMSDj16nVdHPvbz6eQNacWua4+1USjguCEF\ncSeu2i9kjBwGuiBzGXsBgdzlsgHxRlgatIHSn76WwWgCMqur1ewYLns9jIcK\nFV7H/1et2Q5lyugXYLOzl3GUKSmzNC88/qEXWcwV/hMUo+8Sk+2qjnNTsQMr\nbujutESaLc2VryF630Vq/txL1vNni+GFezrzZS99pMJePmqymb3XGhGtk9ia\nvcNiwDD5Yqi10i91AcWTnV2OUpawCZtrDlFjN5EW+aP9TIT++IR1qQyRHzsr\ntLKlBWqv/qfiCidLWBvLCx77VhPRYzklCm2oOWiEU112Z/pJ6TZkUv0VvHvP\nCWDmNesdNYyxiqr+viHHT1iTv93X0mPyRM54gmlvVvsrz/q/PXFhtKNb6sBb\nZEMo9RXPj6rjgpSn7jy6ogjOQBfcuTT/tIMyl0R+NF+Ngu49MH9naajZ+0K6\nar29HcFX1yrU7cYEzWOPfICJu62zjLPZMDc1oJ40zFWDDWqtQEDcCdOjJydA\nXfgXMwgkpejl11qjwJObDqY7upIuiJ16t4u9Nw0R0cm8KtIcbJThjnIV5+Cp\nKo0F\r\n=a1MX\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"900ac379e56456b5c7c347374b25f6aced231a07","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.11","@balena/jellyfish-logger":"^2.1.41","@balena/jellyfish-metrics":"^1.0.181","@balena/jellyfish-environment":"^4.1.8"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.130","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.62_1620093318271_0.9809321175310914","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.63-renovate-non-major-f289d7f9354e88a03fbfe3836cff6519fea41451":{"name":"@balena/jellyfish-core","version":"2.13.63-renovate-non-major-f289d7f9354e88a03fbfe3836cff6519fea41451","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.63-renovate-non-major-f289d7f9354e88a03fbfe3836cff6519fea41451","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f8787060d48a2f3662e0a3f6b095571cb6231bf2","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.63-renovate-non-major-f289d7f9354e88a03fbfe3836cff6519fea41451.tgz","fileCount":99,"integrity":"sha512-pHAmrJHFyIrVsB43kMmqsplsnra+zSuT4J5oIQUKJdQHy+WnlKlXc1wdURAsNoujWJAx2E0rDmyL3w+jTWtr8Q==","signatures":[{"sig":"MEUCIHMUiLLfyZb1m9nlT0BhaNhKv8eLZQztX2OP+9BuxIe2AiEAjyNE/Eju9L04661I9fPnhKW/MxuJtVEDs09qSMlSdIM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":729274,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgkKtfCRA9TVsSAnZWagAAKvkP/josaNIE7smaCFGGNvIw\n5nA0BfPfIo12wRgpajjQ1MMJBVd2SgWtTuxsMVnsNsYhYqd67sezXE7sHpLO\n2xDjDmbXJOdYXL4nTjVQ+stoEOWir2AseCt7PTXNPSauSgXQ2S36refn+GBg\nsuhHVAimQL9w4qX3YRuY+WSE5hrdY4Q3dE/Z36iFqiY7lWoKordp9AcULPDF\nzRt+0/ELo2ZWqgML5K+nt/GKQfk0WG6PSJYQs610rBeVqUEw6lD+sn75s/cT\nDiJ8DrYfAsYVibI3HI07dXood/w3DUzJSwRkYLixO5bFXRyz6GQTzdgz7yaa\ntPQ4l2YmsmmYW6Xgr91KM1xGbIZEfXR0gLPW1v8gIofYGL870J+K52PAWzbY\nWbCo6QVEBliLoJO5D5bR/9vBl+kKjZpqhZr/rFLj9LamNZaEYS2cI65hK0TH\neu3Gn1+SO/RaUL6/MIAyAu4cWoodgoK/GBvAJxcINctlX1B054f1py6jGTor\nMcKQ5CKQGdHia5ld7CACWUZQcPen9uhSPBLEwZmN4QGnxAwsJyMX8NcM8ivb\nulqCUhMuvDrYpRT1PlEj2L6/WFTSe40CL1rlX1xLfTJpYaiRFLce47sMKIfP\nL0/MgZjD1eRbqYMxJ0jS9cX+0Ua5YzFUCcTdQc9XYZ8UkDhg1pWRjm+8BNPc\niunM\r\n=RGFE\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f289d7f9354e88a03fbfe3836cff6519fea41451","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.11","@balena/jellyfish-logger":"^2.1.41","@balena/jellyfish-metrics":"^1.0.182","@balena/jellyfish-environment":"^4.1.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.130","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.63-renovate-non-major-f289d7f9354e88a03fbfe3836cff6519fea41451_1620093791284_0.5043196607743208","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.63":{"name":"@balena/jellyfish-core","version":"2.13.63","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.63","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5da9ceb1872951fc13154211b4811b5d0b0bf238","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.63.tgz","fileCount":99,"integrity":"sha512-kTKGKhpcbeCe315wq48LaRsch2Ycst96xJ6zRV5ESnVSYpkubHAkS4X5jJORTf8R3pX6JUjvxPHUMafSGiq8cQ==","signatures":[{"sig":"MEUCIQC4Ov7qx78Q+AfZLIzW6dU5bgtbE43CcwWeFEZiot/4dwIgXeY59uuqAC1fjbNsLpCn6gm/FsL8ZVyEW6/CTzs9t3I=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":729214,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgkK0wCRA9TVsSAnZWagAAvXAP/jIOH1v1X2ovfB/hUtDN\nsVNti5VJ5PU2wIMTmW3vdSzzggPBz7PpwNel4LTkmXalZAMCB22kNPqnl1Pr\n/x//JgTh+tUxXntN53KhaearBIhpJP9gQOp/vQDAIBlwdafN+9iqWpruzuDN\nXjP6z9qy7ft7NLBNwW4PLJxYCnLnKk8/FMt7OvCk1K2NfjJYI5G1+F3aIkzU\n3MFckRgYjMIDRWt+brBKp7gz/54TnJboW9pR7Z0uy05ndyN0pBgoP7kz+6vU\nHDc7f5mBh3hoZx0iwwCMJ1ZS+3aXVvzZYBL5x9p+s1DC8SLZI4zLvpfo+ARi\n6HWqmGLtI5NUth5MQ2533NhJphD3dk9Wx6M7Oj+u+LZOIR6y2C2kwICITFU6\nK2W1x4k0rO3qtsttldteI8ioBkKDr4y96D/9buAG8RQ/3yNWWwWK2M7wNIh3\nm+dYUumHWKb39PjcIVFLtWl835MTnAndq5mj1NAvMiLYerYZE9kEOTgZyr4n\neB796xn59yi/ixWVvx5JYXU5nB6EweZxW5D1Md/m9ti9TJjEq4UlMukJIDm+\nH3XdcR97wETVjr1hH81p2Fyrp/ynwrfM9DhjThEAmXYV10jUn0ZX4RrHrKYs\njOsSMoP9c17ARnOqOvp/dYdm1TMX3AKM9fJ+8VbCCk0YT4CKJ7jJVRFRQtAI\nGYKm\r\n=ZA0a\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c6db1622157adb82995b0e4f52ea2e31e68a19c4","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.11","@balena/jellyfish-logger":"^2.1.41","@balena/jellyfish-metrics":"^1.0.182","@balena/jellyfish-environment":"^4.1.8"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.130","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.63_1620094255875_0.020834369633664584","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.64-renovate-non-major-80210e9a97f767abd62089c64851ad33d880fe54":{"name":"@balena/jellyfish-core","version":"2.13.64-renovate-non-major-80210e9a97f767abd62089c64851ad33d880fe54","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.64-renovate-non-major-80210e9a97f767abd62089c64851ad33d880fe54","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"32eda435d1882b841597268110e9bc71e82bc99d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.64-renovate-non-major-80210e9a97f767abd62089c64851ad33d880fe54.tgz","fileCount":99,"integrity":"sha512-9CwCf6tzliCrwNCsxOrbXfgSeMuY01RGJkdU4d0dFxMiz+i4EPrnfvYH430h9DgxwHR+zpLD6bHEl8E7rfyJkA==","signatures":[{"sig":"MEUCIH3yjj2nOJmF2ZnUnz6unc7Wo+MtyTaQasJRGQKp63m4AiEA0Icam1jdOFbUs+sYoaXe/rDa4q8jaPLttfHs5I2Z0Bg=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":729703,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgkPH6CRA9TVsSAnZWagAAETsP/RjXdU/himLJ+li307pW\n/88IAEWVFo27n3O4x3g4x8WfEMgPSOYxvJAW4D6z8yL87Mmp+qGC08sCOPOS\nZX5eoiHmS/y9ZpZhsuWBmz350k4COPh8POHqYIiuCOzyah3tN9qQuq+Ck3N8\nkTtJe8goAFFwmH09rrzoGvqa2qR/keyffJcSuxbF9NVXF629g48r8WglM9CZ\n0rJxPLHX9QXt2RdHhIG6g6sDMP2syQRwdiD6Y5hrt9NunjoUZ5KQkq5LVjg1\nslEVrXJBMBQssXm66K1plvykrUWbMeozTKztwseS7xZjCh/UpG+TYWn7VC+u\nge8VYRY5whketzlLU5PSshA9e4X1jpIB2kcEvFfI5vPRkHNUMFaTOID8LMnz\ndpyLsDer2rb33gZsAi4sst38i5KYwhF9YCB2SF1psqb7+vJNY+hhYtdMtlpY\n2clF7w8whTMpMZ2CYr5WQqTi7qCyLRSW/rOrJPEGDzkbOzs+Kt53HIZvdjye\nykmCCd9zC0QmPMa4t0gVmQQrCEYUj/BwyAp7OKWpX2CobY0PbFK2+PXmhcPZ\nnG/MX4oJUhM2OQmVgR0CQyI1EK2m0cwBvs7nuqNqKwa2R66yBro+shAj82sd\nr+qMlwTOxbhDG9BbWNp6n+8/8h9QmzV+UBw8qsgXb0ZB5xddm3DBo7or3AEl\n5fHr\r\n=vtOh\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"80210e9a97f767abd62089c64851ad33d880fe54","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.11","@balena/jellyfish-logger":"^2.1.41","@balena/jellyfish-metrics":"^1.0.183","@balena/jellyfish-environment":"^4.1.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.130","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.64-renovate-non-major-80210e9a97f767abd62089c64851ad33d880fe54_1620111865664_0.6814615006676652","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.64":{"name":"@balena/jellyfish-core","version":"2.13.64","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.64","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"454c620d7a2943c15a2644156709daca285797d6","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.64.tgz","fileCount":99,"integrity":"sha512-/Q1ESTYGmuaDtMtPQVLf/Wt40b80FXdMvWInxdNpspA4NeV1TgwTODcp9p5TxlRp1iBiZdhSGfs8F8/I3nt1OQ==","signatures":[{"sig":"MEYCIQCBtEz+GTvr0wsQEs10has5wx7P40xIn2awYfrMay+7RQIhAIq038utw7nSOch9JqTpMAxKACkWsbpQxGH/Wyg166QP","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":729643,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgkPNWCRA9TVsSAnZWagAAi88P/jZi3CKtKLSTlKQK+b0t\n6Cdq6/raTLgVIB5PPAEztGJqApYqX2C37ZVNePZrbRtZzyS91R1p/Ey5HoSb\n8DmCDE9vrqjVqHDlLpd1n3UtHclGku5mtf4DQ6F+RzMsW1awNh1J6CCep9/T\n725FN7k6eoFMHz4t+EeGqbxlmbJEr8FaLa1Xdf6oUMxE8UULNDqe34B8R7yV\nowo327tQXobXTV3t3ecKJMIV9plq+sej2sdctuXutoG+S9F4Zgy+o1HtCxJh\neG82AdxJY+/CjihGAZ4Xj1+VSB81yOczgpGA3TX9OcRcnt1IAxWdNKn98qWq\ndvrclBbz1k3zejWqzGQuVTmyPJp6yjdwcgOQzIDGz1d+fnSMASPGWNm2vjGb\nyJVxxOeF4DiQhXzDTwFu+IHOlILi0EjZ6BPlO9bitLlDwOyv1wKsX1IVH0+K\ndIk9htfQWGNcU2hKK2NXHK4QXyZJ4TcGxq08SoTd1VVlQVfrGwnpg4bScQOs\npUVOiL9yn7PJ83+4qZiA3c+8fQ+utCLZjNtNZxGd8YHsKvTnUo2PFf6pJ+xc\nuPSITrb2a3/KLgbnVfZ8ELBrbBA+6A5xsNO6VsdNDpiN9j/v5+uTqmUekk2G\nhhd/eJnL/A87ZzSJQpwmJt76xdRRMhnge6dDMhVK0VRozF4ZJSaaf9YsQKyF\nNNVZ\r\n=5oFc\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6374c797f5679a11fe35202d461d68ffc3f85a64","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.11","@balena/jellyfish-logger":"^2.1.41","@balena/jellyfish-metrics":"^1.0.183","@balena/jellyfish-environment":"^4.1.8"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.130","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.64_1620112213655_0.2649747403222744","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.65-renovate-non-major-5debac891515536bae8b0b700065a0e095f4c8ff":{"name":"@balena/jellyfish-core","version":"2.13.65-renovate-non-major-5debac891515536bae8b0b700065a0e095f4c8ff","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.65-renovate-non-major-5debac891515536bae8b0b700065a0e095f4c8ff","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a9aa696ceaf17f7bf230ff1cd7ce14e7110311aa","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.65-renovate-non-major-5debac891515536bae8b0b700065a0e095f4c8ff.tgz","fileCount":99,"integrity":"sha512-cYR5RBmTBiWYwlr6PKTIEA0PGXhZuuGMccGgCGSwfGqDGrGF4KL7l+NR9F1M7VksBcnUEJoamVP1zsIt+EvuMw==","signatures":[{"sig":"MEUCIAnBcee+5UGbfiM1UOxSiysEe86qMrTarAR7sB3ObBmtAiEAs+L4e7t1wkgv+W9Ksmjp8qxuc/wByv8uZoP2V6/nHrU=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":730132,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgkPxrCRA9TVsSAnZWagAARQMP/RzDlZwLH65DEeLiukMw\nMoJWMuCanvsIP9RrCGr9FOstmoNbcLFmE/KcB01TaNQtw7T+SyZQnvQ2qRFT\nf0/DdvDhf/UVaJMNWYeudc+91dTWnRvxjWuRJ9CrevXMnmOlU6TBagdj88By\nH1ipn0YBmfjSdP+Scu9zuKJU84bOmUC9lLf2Tu9awZdW6R5qBqncxpFax/dz\n0611Bzbv0ffW97DI9Caaj8A4g/MaIrXzgbuMFZG41ximexQ1HdrdPNaIJX0U\nFlMV32itWFQQLuVTCaMUj/YqnXQ64jxHysASbMvbVBa0QHVCCDsHLAZb8G77\nnxi4+0urhU/LMyWF93Vb3znaZWgoRbkT+CEmndT+tynmGy2lTRn1r8Lmh2Ke\nlLqNzlvPCBsCBy63CISCOrBsT89mF7JRPSzZi95sVwd/96NxJa0HqgWuZVfE\nRDVHJnZ45Qnk3v5ZEPxbn52ohOIJjnCT+QeOwd+AmW1JUh+3iy8ML6yA7POd\nhCCYWFg+6nWtl6O6NfPwYFY1SglPsVssMAKlXdOiBC8r7CuQknZMEyn4kXR0\nNpbEyHWNFXE+DFZ2UjL09UPokgrVzw3NdUFc7+Gg2LgQajq+2yO0jxPY93Y7\nYP3hi0/pqeDwK4CkvRko4xKC2St5Haqrr3pQAORbVp4CsF/7Xw+f0tPDbazw\n44OT\r\n=Zp+0\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"5debac891515536bae8b0b700065a0e095f4c8ff","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.11","@balena/jellyfish-logger":"^2.1.41","@balena/jellyfish-metrics":"^1.0.184","@balena/jellyfish-environment":"^4.1.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.130","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.65-renovate-non-major-5debac891515536bae8b0b700065a0e095f4c8ff_1620114539257_0.016844897912860146","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.65":{"name":"@balena/jellyfish-core","version":"2.13.65","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.65","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0b563a9a475a794b51174b05c1e0d22ff1a34ef5","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.65.tgz","fileCount":99,"integrity":"sha512-A4COmMMhkZbwPSY+Ch+oqkO5rVKLoHSx84MS/JAQdMT+45l+guyYw4gfUVe0h0Ms6dzcOtwonRw/JmZtWE+P1Q==","signatures":[{"sig":"MEQCIB+htvbVByjb7gAUuRWj1VSw5BFZCrEftqznJcjtGP1QAiB2pQD85OawG/i8j0rzheyFinRcaQZRQPOOD3CPn5oz6A==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":730072,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgkP2UCRA9TVsSAnZWagAA6h4P/RvFPBIgu9EZEdVVcpBb\nbbG3T4UyoyvkfPSekHHrVUCEeC+pi7OqycfYIX9Fyki3rHM4UphuzU7TLMNW\nQUyPXH2ST2SZizAVwncm0HTD7u6UmU9fJGMwQbPcRwHiP5bysZWBvi5E9feH\n3QsH/cjkyX9GWZdkKn59ecv9hnS4epHw4GhT9jVAnFORWj61uJ7redmU0uGu\nxaODeCR+VNvwmAHY84bEAFsCNREG9IIK0uRDod3cIV8N6QLF07xatsYFnzHE\nA3S67z4riRaj5LnMrl7DUcVoZ1ervuawd853N0Y4Zn0e6lmcXNoudqh6NOZl\ns9iuhhCkoIaW30NwKL0XWhQQzNBdbqPYl0MxX2heZVbwS3EmeCYD5WJz+xAx\nBkNULeOEQhvmHcz24Yeho4ztpPzEhaNNTAxKkF8a0W3woQl9fRLPXtUGQI2N\nv3ARIyLSedPfp+Wk3b1TZESnrZr+N9HZtiBMnlW0liunGYbQvYZ7pLMz4Qda\nCwrXhDbjELPV7LPHMkXtRDM2z++tcnoFOZwZjTXzvtqAyF08qYX70DWZ3VyK\nkisZmpiz66GfMdw7eyzyN8IGweru/vUVTNR8BvWujOIsLaM/gW6wzNJSMAAn\nd4ZsA+92aAr/Z0sEHAgTZoltuHcbfoAzrTxR7EXr4B6CD4+B6WIIpZG1nHoA\n+m5T\r\n=VvSs\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"3bbd883d41070734528a7a95d5a263face3021ab","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.11","@balena/jellyfish-logger":"^2.1.41","@balena/jellyfish-metrics":"^1.0.184","@balena/jellyfish-environment":"^4.1.8"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.130","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.65_1620114835694_0.799673842177713","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.66-renovate-non-major-0b3a6f75f0e59f1687f3c23e48e7817f4b0b475a":{"name":"@balena/jellyfish-core","version":"2.13.66-renovate-non-major-0b3a6f75f0e59f1687f3c23e48e7817f4b0b475a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.66-renovate-non-major-0b3a6f75f0e59f1687f3c23e48e7817f4b0b475a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"edb33d1d847b60dc289bb6e08e78c029280b4334","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.66-renovate-non-major-0b3a6f75f0e59f1687f3c23e48e7817f4b0b475a.tgz","fileCount":99,"integrity":"sha512-vV58uV2zhzjgoaBRGUkV6JbTNZuufkV6syoAZleQpbyxOAGzTLZyffKeytNaoaqQuXWBW8MRyEt5yjoEc2Ttgg==","signatures":[{"sig":"MEUCIQCYMJkscn9q7aA0+F91/vzATSRzkSicJN3eGPU1lzwodAIgeJ18Q92lWodcyd4LY+pgfdU9BqIQ69qgxiw555jUrbA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":730561,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgkRkJCRA9TVsSAnZWagAApV4P/1QaF4Vn1K2XmOljYlZb\nd7e8zemVD3QOGxCLX0HCA3QobeSMw5Gzhnixdb7MhExK7tXQ+KG1icNaSCfZ\nGWPcUBE0sxCks2UAlAbC7twVLWGE4qGWhCQg39ODnKWazTMNbwqbe+1odrPM\nnen4HQmhfEUFm/KVNWDK78APZ45ZUK3+kLPsbJhLfQQXYuBwvfKN440bp2L0\nkZzDP0bG2Zgb0uoJsuNs5p/uTMJV8fDdh7+ndzddiu4F16rNGPl6Kqkcb3Ml\njhh33TMsnjRXU+gFXu66ddq/ck7LP3zJB2G8Td732Fls00kd0pRye91alulM\n29ye3vo0fXMhcGPTcobllp8c2nIU5+s+SyozB2SeTpMKBK3KfMEbl6wXDPjr\nK6VaKUpTm2Wu+tMw3yZxO1bkPpHMIa1ov0X2NU/LrZJECrtzTDBrMA3bWVnN\nvW+dnh8Hfer6QfjzL50N47WB6iL2tpB16EqTrTZ/+ua8jYcsdL02TZkpMiVs\ns9MIY7KT69KnjluPFXJL4PuBcUEFnNnqmaw8wWf59SywERmJ1HQQ79KGHcxq\neFf4h0TEKZkroET6+3tHMcE/kgdWU4RztEMfMkv6+8j9T5BTYh/RLX1FqR5y\nZtMwqtdLXNyu81DLO1eE3htLd9bO7kovH2KT3zYZAyMwaJSv3+w/BBktq9d1\nwIsH\r\n=hxw4\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0b3a6f75f0e59f1687f3c23e48e7817f4b0b475a","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.11","@balena/jellyfish-logger":"^2.1.41","@balena/jellyfish-metrics":"^1.0.185","@balena/jellyfish-environment":"^4.1.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.130","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.66-renovate-non-major-0b3a6f75f0e59f1687f3c23e48e7817f4b0b475a_1620121865334_0.9343867068945808","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.66":{"name":"@balena/jellyfish-core","version":"2.13.66","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.66","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6c9ba52004ab777c56a150df9025d7c573079493","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.66.tgz","fileCount":99,"integrity":"sha512-L522oQtuoM1WIsNbJTHh0iS5uZslRAhMeGOI9f9jplpAC9ktOqKwnggLNGHKmoMOOuPnYeTs48gnXaM0tObc5g==","signatures":[{"sig":"MEUCIGzTbLqZYAo2MD+56PC3GIgqPfsC4Hsv0nbI9Qi9TXMTAiEAjyx67TBTF2qCmvvo3CSvTXN2uD62S1TwSjhbdKMQA3g=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":730501,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgkRpSCRA9TVsSAnZWagAA9b0QAIdYFWMVJ38AaeeOfLXf\n7+UlD4KVzUanKFU9kyzqJu8HG6uaujRXUb6drRwMWe9Ev7KZS+Qs46Yf3T/9\nik+Pe8UPv+49f3IOTrGEvCqQy6Ef80eYY7V9Df1SemKJnTVNuRydYjLgEmqQ\ngN53/kYmU6RZRMQgMvHpU4Vq1o6TUETXOOoi6rE3lJI9D/CfPHk+zHg2Xsnx\nscQ/bYTwPoRI1MK/MNYOJCYlIQqlmexv4V1iFFoat5CeCXWIKaLLI91ya6yX\n4054SutNdcmx6NtCMhYcuYVm/hLIsa7tdcs5Fi9l7Eui/ay9y1wF7e8nWEVn\ngRghmOFl8bd/Z0AeEcrUTT6XGLXlDYpYJVvMgm8Nru35omvhKZMEPmuRtc6J\nGZF56WyuUU5mWdpEtrnD4X2UvtAJ1vn5di83OtJ7gYIFBZSg2krtGn9qWE/W\n1rOqHWAwmx6xxcU2xb2jNEGbYxDeGUJ/EA3lRrsWBc677NbCSB6YqHup+VkU\ntpJUHEkzr7oWUNieyXBu7c/EUxnyF6bmYCyPI5AsYgdqaIHKPRfzMuWXxW21\nV9ETLJYBF4j8zGSlSgO4nVYds+doTZkhLFYh6PRvhDcjzk6jNfjiyYqxF3nt\n13gv24R/S9BMp0V0GVhhj33b3FSg/RQmVngXSTAjf+ll4G8XueoSeuidkPXx\nFwl1\r\n=gMdY\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0576982f116f7425558cc3fa577fd09b71a6205d","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.11","@balena/jellyfish-logger":"^2.1.41","@balena/jellyfish-metrics":"^1.0.185","@balena/jellyfish-environment":"^4.1.8"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.130","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.66_1620122194401_0.005493999984479192","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.67-renovate-non-major-0c3e59e98e606c7300c7505cc9147ad7a7d003a6":{"name":"@balena/jellyfish-core","version":"2.13.67-renovate-non-major-0c3e59e98e606c7300c7505cc9147ad7a7d003a6","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.67-renovate-non-major-0c3e59e98e606c7300c7505cc9147ad7a7d003a6","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3c8e109f1210d002abe75424e3708bdbaf793ad5","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.67-renovate-non-major-0c3e59e98e606c7300c7505cc9147ad7a7d003a6.tgz","fileCount":99,"integrity":"sha512-n3SDj1sRX6tba29PLyRBE7Of0RaV3CzB6LgS4zTkQUXbqPvtIBVD2CgQtJ2ZGRkUusFOKzJf4WS5Q7d+7qmSTQ==","signatures":[{"sig":"MEUCIG1+FS2T8k1v4ukGCeaDFu2450d4GX0+MgM/1N6giXceAiEAivNjYoQa7NvPDwpKMvO0gHalz2WcR6xzCrfs4OqwbgE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":730990,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgkYcUCRA9TVsSAnZWagAAJyoP/A+0Ytc9DgcyK5Sb3rRb\n/h86FLl/0+pKoxpS1+y5bt2wtvwsY9JoChpbabiDxzD0LODKnIuAUG/ucVft\nv4gzokXMrmxGTcoOq/3hQfbN7Tg1AriuG7ONFvgrV8fo1Hb1RB32a76ndLvz\npFhlSTZSDlHxAPReku4ELsfN9gjJoRvTe57jfgzCTIafZPWMFdQxWtbpM6B/\nMDY/4ptwpLZPfNJKLwCP+sfu5J1/u++ZgJoRmCLdGwrHixbW1hq9nu0Ce4Kl\nu7psS4SDAXy+lp7XzEDvHnLDXm7fYo4dKiUE/W5y13HfGUxP5bydj6MKfUg6\nLQCv6yRhJza8OQszw3FESLmauNxU5iyHtqBWOtVxNIOhQPAvqpKbTcN6qIkZ\nD5OF2A7nG7ejS4DZS+Jt95ILxH3pnn1lU4xbcu/ONgFb1PY9PbcMCfPviQle\nwUOBi/M2i5Wqy/1HD/7utpHZPg9b5Zt4xoMhem2fpFoyi4jDWWD7LRY3txQl\nO2SX6rwtsjgaduIGhpuu7XrXdUAkhCNjvvo3MZRWCLCT99f7ekCrACFldIIT\nWvS+ZRvtICaTyg3UaD0wZ4rNlBd7YARf2AVtpOgbKR0zWxB15dEvgzVqWTAW\n4PaDMSBGk2VfdJzff9GoIsLvEgGwphwWoZzCa+l7yqR0zWh3AhNRr5/9zOtX\nrJIx\r\n=Z4/V\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0c3e59e98e606c7300c7505cc9147ad7a7d003a6","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.11","@balena/jellyfish-logger":"^2.1.41","@balena/jellyfish-metrics":"^1.0.186","@balena/jellyfish-environment":"^4.1.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.130","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.67-renovate-non-major-0c3e59e98e606c7300c7505cc9147ad7a7d003a6_1620150035708_0.47118808972454707","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.67":{"name":"@balena/jellyfish-core","version":"2.13.67","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.67","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5b2923ec5e82d4a04f99ed4fe7a439923f1e3f53","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.67.tgz","fileCount":99,"integrity":"sha512-S6fQ645fOKmmhoEMCKKCJgeuce4QuVmjyjMlmua5RvJMsvQnnc15zPuzZ680Y/Y11bC4EU8g+YryeoPTRz8dDg==","signatures":[{"sig":"MEYCIQCTH6iRNqMw15LgZW7pj9LbmWL32WAEdI86WM7bTFidJwIhAOUSnQVfiNj0qts5JztALuGRnMATf4mW/ym8r/SdEwTb","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":730930,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgkYgTCRA9TVsSAnZWagAAVP8P/3qdvV23jkDARLhkDmdP\np2EnELNJrufUrRv9QOoIgme8xLvVJow/a5EYKmZ3v8FuQZZj4eqChghHUdwj\nb0ek9NPFfcPvZdlBr25wkjUy+FSlM3kQfse1/mtBuMOfFwv79v6TSmCqIXyS\n0zwmNTEk6BISA8fh5/Kc3UbNsgnhbd574SmpvDr7ENUaj2kSgFgPLzEroGMD\ntMyYaqFW+iP+Agkjg5G1gvcySxqcZ4MWfUMZv8q7u0LrNv/pSkezLgA2POvm\nHvktzo9pZIcNZLd72Tc4/zdxBEf9V1x9ZZMFPGlJkqHB81x4Gh6nRzDcVygG\nUb07kyNv32ht8el2tvc8o+QpIpoxDs+EGq/3FhRnfbYIbxAX1/SFlrKHqAwa\nB5lmB+3bxxXNjqYO+cxV3TKbYmhxppunPa4XxwuVJN5abVfVGFChzP9EJ8tF\nROVv5GNmgMVMjlfNirC5j1oHAxMg2Fe5C5uZ6ql4gobkXYVHFt1C3KzssiUB\nLi1+MUo21/vvDDIc98yzclnD3Lo9BWvDEzNBicQsFrQ2DVv8SzhyE4dFctZ5\n+jxnMj1t8OwFiAt1yTlcn5l6Yr23rPN2r4s2f6rV7FkAtS9CYDJ6tqYF99AK\npSDG0So6Vx8VdYyy2h8mI6EqrV8+R0HLHHVrMTTYfV2fXltJFW4t5VdVEeSC\nbtK7\r\n=xQTD\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d7616576e7cb856ae2e527450288e4a21dcca119","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.11","@balena/jellyfish-logger":"^2.1.41","@balena/jellyfish-metrics":"^1.0.186","@balena/jellyfish-environment":"^4.1.8"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.130","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.67_1620150291182_0.5772616075950923","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.68-renovate-non-major-cda0eb71a324a15cfe6ea2cfed5d992dbb9918bf":{"name":"@balena/jellyfish-core","version":"2.13.68-renovate-non-major-cda0eb71a324a15cfe6ea2cfed5d992dbb9918bf","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.68-renovate-non-major-cda0eb71a324a15cfe6ea2cfed5d992dbb9918bf","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"dccdabeefea810dbafd69c7932d26edffbcd3b93","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.68-renovate-non-major-cda0eb71a324a15cfe6ea2cfed5d992dbb9918bf.tgz","fileCount":99,"integrity":"sha512-lxL1H0HdmGpTVCmlL84OVG0SfVERbMIJM98K/VzJhRjRq3GRkCaCe/uYcboDyVJigKgkFHKS/ss61Ngp8r3z3Q==","signatures":[{"sig":"MEQCIAjS10KWkm0ew1cTQdploSdRw5nXh97tv05P2fRVsl1aAiB0K6FaVyAvyFJ6wBV9dZ0Nme7FCfeerWxSudNJK6wt9w==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":731419,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgkcOsCRA9TVsSAnZWagAAQwoP/jdP83eGrq90yBAitWxw\ndS3h9Hxsq7XSTcpg0OpHOKKcyrfzNhmbDo88rQ+e4oHDrK5rFAP0lbHZ084E\nK/vnAzEdFKRAIjJx5K0ERVs2VYcG5ghzmShmmDvXgnXTjfchriRzFYU7DpZt\ncPWvr6H8dDSvFeErrBaBc3dtF7rSchBVdcgYZXw+5cKlcvPFKGC059br3Rq6\nKZeXNbg4Sx6EL99xFPgFS/1TZEberieBgA7Yz9At7TjSnw3wtQfRLJhNINDz\n5pWZl4gPmljlXcmLifzEbrG+/fcc7eqzCGKat8fJNTufT48bh2s6unSuhs9L\nO+/4QccU5ZrZX8Z7xHnnS51rg/oiVALc8aCGzG7zEZFwcYpiX6o5+QLlwXzu\nPXaZ+Lqzjw+SDKg9AhXzMKXAJgNKIWr3luXzpB0SIejXAufmrSeOMdwKq3mO\n2YG+6nEB3RGuUgIC7vnUak3M7movsXg6TPfIPUwJ2T6hnKE3uJnYkNSGYlDM\nEO2Wu3vC00O7Jow/ydftshBZzBYFOBa1MJHFUv5qKnXMJAFvQf7oH8K2pClb\n6FQGU8bD88mM+1+WULLmP/1BMhl7XOccOXRkSiRtHbmvhAMMMdHN2nr/lDLY\nGRJScaPErfWmUZj7q88S4AxjThsADN7JNOg3JiX5qqjNjSbaPYSxts8s4lBY\ny7/U\r\n=Cap4\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"cda0eb71a324a15cfe6ea2cfed5d992dbb9918bf","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.11","@balena/jellyfish-logger":"^2.1.41","@balena/jellyfish-metrics":"^1.0.188","@balena/jellyfish-environment":"^4.1.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.130","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.68-renovate-non-major-cda0eb71a324a15cfe6ea2cfed5d992dbb9918bf_1620165547767_0.8312257258859417","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.68":{"name":"@balena/jellyfish-core","version":"2.13.68","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.68","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c90e9101216ad2d0a8bcb5c11d0d29a4656abfb2","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.68.tgz","fileCount":99,"integrity":"sha512-35Q7tOogRLazU7vIdqA4XAHsk/cxz30r877cvfVE0kE6A6+tLBnOpmQftbUEx9+Ao4LCOROM9T5UauHHSWDlBw==","signatures":[{"sig":"MEUCIQCMmyaWmn0S6+nHil/2CwSBAc//ypACn6PEqG7itTK1bQIgP/yfK67Forzt09g3Uay/CxlE9lx8pUWoxOew680K9zs=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":731359,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgkcS5CRA9TVsSAnZWagAAowoP/3ntW0nMRWicH8/ETgGr\nr5SRa+Sxfwq6A+Q6aOD4dyZErma+2aFWO0O4Dq5aQkZ3vWY3mM+Fd1YCyXMa\nFgf+DIQJY7BmQaKlalA/Z4LJnmYymewbLiPHcgkHeIWZaIGkeCZIWpXlOtOV\n8ffpPTRbK8usu/tGI9vrQf6okalhCmzq+7frm0OdIQrZVFTe0Ti/q+A/0i7E\n3jTXpO//kCnjordvewg4M3LkRqrQ8Ww8A/AH6xzFaWdGCqL9ypz/kUL8vYn3\nMTgOkabKWvkH3KmveSnaNn3tmTQWPypAJkJ9/ElrO1DuihPD6vVzALPXTakt\nXOpG2KIAHcqxfP/uMXnr+o0WkVUZIiJ70IbYngVyT8vSMj9kIr7sjp1BfdJB\nsuCKHg8EjyhUK+dhlCkc0JbjrXQslKuxElDn2Mciz4kJ61fXaoe69Yl3NMdO\ndOCz6A0IGsrYH4yKgASm8mGHQ0HTIxdoieJc+Fz+5s6Ia1dOoIAPPvAh2R8X\nqdkKsVOnVAlWsA2dgQo9ZNac7/eCmaeIEiv3Co8ALAIMrM8+pnCCNj54AydG\n91Rz86xa2ejx5N72Dz6kNKUM9B3FEeSLdEn5q9HGBSxR7urnQK74wmxV6FCL\nnIsk3lCOxnu+rgQHDZIZEApvFto+5HMztVxLj8oToWCPHRYDGO9WGPbTsR7c\nXVlC\r\n=mLOJ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d1c18cb03f5936655a6a6f6f7002551b0cd884f1","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.11","@balena/jellyfish-logger":"^2.1.41","@balena/jellyfish-metrics":"^1.0.188","@balena/jellyfish-environment":"^4.1.8"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.130","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.68_1620165816834_0.6758090347530727","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.69-renovate-non-major-1d28f4cfef665caf78d627aba0366937a43f4a9d":{"name":"@balena/jellyfish-core","version":"2.13.69-renovate-non-major-1d28f4cfef665caf78d627aba0366937a43f4a9d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.69-renovate-non-major-1d28f4cfef665caf78d627aba0366937a43f4a9d","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5b8b2cfc185b4a63601fbc32684009e4304d3a32","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.69-renovate-non-major-1d28f4cfef665caf78d627aba0366937a43f4a9d.tgz","fileCount":99,"integrity":"sha512-/Ffa/8c66oky34cJJ9uf7zHAKsfe80+1344GwAFUa73BH2O/jR/SgQVfpQpzDSJUGeP54nJoTb+aTGt9zpCAWw==","signatures":[{"sig":"MEUCIQCEKn31CI6s+ZK9AaxXHI6LpUCjJSU+vahgtX+FRN0G4QIgdfi/bL/AIfjl3REN03uhfwS8zPCaJE6PnztXgdJWIu8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":731848,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgkdqhCRA9TVsSAnZWagAABloP/1dgxVc2H7C4/kPnYioe\nhPnhHmUFtv1OmFKOX5KQ6AftWCTj1Z72znkBsQ+4n/9Fsd/S3m04JdTsT6f4\nj9k/JRHTDK8dRi3PfXvGsz60Is0C/b3L9xiwKhm6PlJ0gaCQqFDajuf1sapR\nNoFrL+DA0Ib9q9h7HbJogEyMCGdQCmxuqVesH/w4bQrXi4gtX66IeUw4dXj1\nTcZpJb/HuTzUMJXQrEJuQ7/GOcLkdmXeT0DdT/flKDypDRQ6nkuegv4O7hDN\nS+dT72NBBS9vRXHi0liZ0zXKFBAm7cq3cdTI6b8SHzoXY1Pl38moYJx359Sz\nSiE5tcgt5hhLD8RzqQ8Y+y6YiRjt/fuG6wv+nvhEWYIhJ/yTQgUUB7sJnIMY\nRHpO4RD2e8BQE68NwAbncXAyeMdBrDu41kNbF0hr0gvPzSoyBvEEsxnBTTt2\nAkqSuwi/nLIHpIv2zV0po/c+TwkiWRdrbcH5WEBQbnqj9bKSMhcHyqAuFcUf\neYl9FO/ZVMCLPhP3L7DoqQhi1IcZ87EUWfN5HlxAi5U4g3teJIUAR6dy0qMS\nf642uIIoznDpnvIjh0SR3AIuS3GB64HPPKzx4D+9g+h+kUvCryl8SBHEU8lp\n9/G0HrCLpFY1Vz3lJZoFCRWAgA2FBLWcpMiq40o0KdEs0e50Sc/jp3jG1gn2\n0aLS\r\n=1UKB\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"1d28f4cfef665caf78d627aba0366937a43f4a9d","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.11","@balena/jellyfish-logger":"^2.1.41","@balena/jellyfish-metrics":"^1.0.189","@balena/jellyfish-environment":"^4.1.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.130","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.69-renovate-non-major-1d28f4cfef665caf78d627aba0366937a43f4a9d_1620171424674_0.4359651704207641","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.69":{"name":"@balena/jellyfish-core","version":"2.13.69","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.69","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"999e75768a88af663ef57f167584ab6cad5da246","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.69.tgz","fileCount":99,"integrity":"sha512-S/Qc97BPA+fBDxYlKENT7K2OxL7i7lj4yyyhspBDyxkf5n+EQHvSfWE82QDuVvFielSAENddiM4U7SjU0y+OKA==","signatures":[{"sig":"MEUCIQCYTovtOcSRuAxowb5kZDaBcSYvvYfASCiK1nXFuQD8aAIgbhzoEihQIQHKXIdNWoiEcbFeFOqp2O83aCkTw2r4R0s=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":731788,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgkdwwCRA9TVsSAnZWagAATPUP/imFKSQ31dSXaUei3ZzU\nNiVUO6pZfCsk0AWwGROK9ne44TrvUXkUodpWkZHlPbyWqxVhr7yw+077mall\n7/cmmdlL3tPh81pOVMHsc/ihnuI++c+YaoIKGabveubzwjqjinP7AJM9FHPG\nchRwkLh6dQYIHs6NEVl+SWueX0xYPu0YwLl1YaFKZw0cRrwGyi9N9X9c0Mx0\nDOerLEn9RrIZw7aw42SLCQh6lOzXJ/34CfR4C0GJmFB/Ivnd0q41C8Guf50L\nuxgyBzAXMNW3nMmV6FkYE+Pbt74UTsh49n+CTSxpL3O16TRRr7bTZhbD5/NR\n6mef66vfZ2hbKU5lDiA739IKNAvcLEHXNfTnqFa9Yjp2RMT5gPtPJFXUddIE\ncdpoyaZgzpVPTtPdPSpSw3dWWK1spKx65iOozZlJU86c0s1Ex0gQtJhSny6H\nozwJ4++J/ROU0W36ecSJLGCCgWS3ExAOYx4yIXKIFVf/xK6oDGmz8PRaLk3i\nfL8s0DBtPm46TToOhzeU7OE2LZYLLZlTnPcNY72jBDaneSlHhObKqlgPV0zZ\n/ohRO/7S+LpGfDFaF9jYmPI237ydEuKf+REYwx+dw79yDwyplv6RFKdyfxd8\nhPnzeeOVH3TfItr5fE2JxDb5TVxeVdMFEem8JdnAQp3XCjKHOo+YUYj9e0tB\nfce/\r\n=5O4C\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4d50e0d8ed1e07cc5d3c3bae1afd3a0d753f2214","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.11","@balena/jellyfish-logger":"^2.1.41","@balena/jellyfish-metrics":"^1.0.189","@balena/jellyfish-environment":"^4.1.8"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.130","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.69_1620171823613_0.7781361718643971","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.70-renovate-non-major-a77c8dfcc290897ee83804bc69d66b0efce56023":{"name":"@balena/jellyfish-core","version":"2.13.70-renovate-non-major-a77c8dfcc290897ee83804bc69d66b0efce56023","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.70-renovate-non-major-a77c8dfcc290897ee83804bc69d66b0efce56023","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"72fb822b302852f4e0755d607da032989256d458","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.70-renovate-non-major-a77c8dfcc290897ee83804bc69d66b0efce56023.tgz","fileCount":99,"integrity":"sha512-eGR0TSHmYIA66V4SbzE/h7vsTopgtdBf02Y9JMkblf0rCBa8+l5pFRbjrBIMY+jVSU56LoOBvZWkFmej7pjo3w==","signatures":[{"sig":"MEUCIQCTUU2jU3kB/HQDbKqxvQ3YM4tJA3XmxftEXwRMQB2rfwIgArioHTlqZL7pZ8UhFYvf3o7CZ3degW8H2X5V413SnZ0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":732277,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgkfFQCRA9TVsSAnZWagAABoUP/RYZsXPaXnXhfwKACwIQ\nnS5Frwr4ajFrsOTBxkmQ4eMajHIIDfoh/dcHQFJKTEinK5Z8u3zVnmmFmgfA\naDGbwou2LB0eY+elajzRVkcD9sBvCD0n2B42WBtdg9nJPWsdeWVltSr+0Nko\ngZEaStLMwH29yxhQsycGjmEpr/DfDy1RagpJo9FXEQgursXonxCDrDYCd1a4\ndn/7UZ5z2l/Xo5zvVYdyS/XEU8PPPSbBa+e/zumXyolYoLz/njN29KAPTA1C\n1i949FhNhopmSkERlYJVHLJ90GNMSeiGwjQvrQHjJSFJHSscROvadiLUYgt6\nOx0x0ZgKvlcdC7b3XwW19epwtwkOhaEzXSKvolbf18w2JSlnjCGdYoS7HQUz\nHAUP3aK6Qg9Sh6B4gZ9ze0d+ri8H1gWCMud5CKLKlTLPqYwN3hP4/vnzbYVc\nFABBV0ccE8QD9DuWmi4/H8ZdJ5Q788HyBeDpB3NzGC5U4WwahDIaHEixuWSx\ngJ+poMrRquRGs+7OpzMniXWqDLPWMRE4ztfv8kv0h+LRwPb8vWsw23znft9E\nUoq6o61wpApeCDhQolW99UGVSFATUWgcCzSY1w5FG976h84IhbVAJrwahROG\ngPuLS0v31iIyTcPq+INchSbjl0CGezhOGECHPb57EtkshlNI/yjvKrpc7GQI\n6K9X\r\n=//Pt\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a77c8dfcc290897ee83804bc69d66b0efce56023","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.11","@balena/jellyfish-logger":"^2.1.41","@balena/jellyfish-metrics":"^1.0.190","@balena/jellyfish-environment":"^4.1.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.130","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.70-renovate-non-major-a77c8dfcc290897ee83804bc69d66b0efce56023_1620177232238_0.7088954099309608","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.70":{"name":"@balena/jellyfish-core","version":"2.13.70","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.70","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"688225ce0a9c1ab7f8407ecb78f6d4d11e0a46c6","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.70.tgz","fileCount":99,"integrity":"sha512-18ntg9GDv1N3mkDllplFRtXPqa+tHtOeNWwTX2VuCNu3GnUYHrSEqjn+FeHPgNV9ZQjr2jCurio9NCynpBpeMA==","signatures":[{"sig":"MEUCIQD+yS282tVGqYtjlR/hKwxS7j2VeINH5O7x1D7pdT+6bQIgOrl16EkazkjePRQhsnOtANCl4iWa5wznf0dqC8aA+lA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":732217,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgkfMZCRA9TVsSAnZWagAAA1EP/iwVPtVrdO8pkyDoVbhU\nKLVZpxCAGVbwAphlN9+kpQBjq38GwQO1iTllhb3esU9dxj2AyhqUty58AAHz\n41m/BBgikOHZl3sLeN+HPN+eeOtlYusodLhPWtCWpW76qDeLX6xZbb9NZ318\nS7ksx0ihZbl++9EWSg1oG/rUC9env8R6Hfr39Pz0DoWMRHWVD9uujAaIfgJM\n9BQL8xGdidoTQkfcREBowtmHCFre79EgZ/hTZOvBBLxpq/NVOEhXDSa72XEm\nntD9MHPX4U1hgVOvubDZmCZsvdSWtNvHSEvyHS+teARQ1RI9iVYn9KN338nB\nvLIC8XDTYd4Zk9vvp39FXZmigkCyz/OIYgHQU0Un1lL/04Q2FJw6jrVSM0Sp\n2qH5V/sPH8LFRs+vi8L95kNksoZgPrZjfthOp4SWOZwvw0y3qy/jy41xFSo5\nA/DZP+KiLN8i5oJFTWiSYSh/Kz6Jljp0/o425PuZIeRxCrvjnHe9uBzOuneg\nTxwP7unjHtcKhyPHb02Rl6vMHKVhD0xi97ei+k8HBOvfxYFht2EoXTgzUYet\nNeL8Wmuo71QQvN36RTMMeDwxw9kIa2s9uI5Gj69RUeCF7ggeMmTkiiwi2pr2\nSCpZ6EwoCI57IqCnP+3EvxOh2a1FU1TkAIsChL+H8suCPHO1qUjVJYsZD+SX\nIZG/\r\n=kQCe\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"1ff51713605a02ec87f81fcee0b4b23970aa87b6","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.11","@balena/jellyfish-logger":"^2.1.41","@balena/jellyfish-metrics":"^1.0.190","@balena/jellyfish-environment":"^4.1.8"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.130","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.70_1620177689390_0.17095724701328963","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.71-renovate-non-major-205ae8cd63373e596e32599219e84263748beb14":{"name":"@balena/jellyfish-core","version":"2.13.71-renovate-non-major-205ae8cd63373e596e32599219e84263748beb14","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.71-renovate-non-major-205ae8cd63373e596e32599219e84263748beb14","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"377852380d5225632f800d817b4367264aa01c3e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.71-renovate-non-major-205ae8cd63373e596e32599219e84263748beb14.tgz","fileCount":99,"integrity":"sha512-aGhTqym3Pq1qknTjHPJwOzuZ/tKl+Zv6KQD4oAg+orZkwwKRj/oMJo5K4Nk9RTtDTJdgu+yxugIf5L5EhqZwvA==","signatures":[{"sig":"MEUCIEIBWpjfPHaJF2nhLCv2/W9Xf+HtOTpve0ULeop11kZYAiEA8bGj1prKkScjxxp5qqf25XAGVke4NSQEg8fgMVXkucE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":732628,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgknTTCRA9TVsSAnZWagAAlzMP/1YL2kOWCERkJK3ybbx+\nf9OGYbbaoL1Tvtuuw5Lr/btXPEnE8Z2cAjLm/fxdROIb3h05Kgy4mUu1puVv\nYVODrSL0yz2vLOqRwut4tZT+XsHI2hTcLj95ZwNnQ0YTPfSseKgGboRQc05n\nhXCEtlk3WJj2NHuAfmV43GzHW4l+E6KBO6nyMZTJ6Wer8UNDJhGxvpo6Bi2Y\n0oH3f1WZiKt4ttw/AZeeLWzRrEG56OtjqXF8CncWyyF1uYGho58SEZCD2wmd\n2aQ/OyewRPPbndUz699K1N2tJMhYKecOV5ibMwZcd/R46W5J4qb8xzaLbflN\nSzsX+IzY8dJTRx9ay472Yyk0gvTdao6fO5Un66B5j0lju6+k/wtOti+K0lWx\nLEotI5fBGl8x5KOEDd0JaBcZV70VIJJAh95b05bowy5cy7qDqOSzDkEJIOuw\nJsaWz0ZvUzqvw9KOBGpKP3BOUp7GQQL6LwcE0ldEklWpBbZ02QxbGBrMAJLz\nWY6tsJ4ElZyKC1c51UmI2qgbvzgJhG+mFtjOJWzhTyKIk1VZ8NvLwusrYkn3\nw9j7+NQ4RZ5oBsOGKGV+GfxIuFgur1YBYvEH2PP6vMsFDvo7qdhUjmm92stE\nvINKBPaGmkHAJ4AjWhSnMdbCyFwJOsZISERxNnB2XxhPP0ia8cbMfil4iRmM\nl2H9\r\n=3oUa\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"205ae8cd63373e596e32599219e84263748beb14","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.11","@balena/jellyfish-logger":"^2.1.41","@balena/jellyfish-metrics":"^1.0.191","@balena/jellyfish-environment":"^4.1.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.1.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.130","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.71-renovate-non-major-205ae8cd63373e596e32599219e84263748beb14_1620210899291_0.41759645021722425","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.71-renovate-docker-non-major-8872e32d9af6337c5d0b1eb9eaf76c0343229fcd":{"name":"@balena/jellyfish-core","version":"2.13.71-renovate-docker-non-major-8872e32d9af6337c5d0b1eb9eaf76c0343229fcd","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.71-renovate-docker-non-major-8872e32d9af6337c5d0b1eb9eaf76c0343229fcd","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"64a23ff2c320dbcf5dc35a1dce09bb0d20a36d95","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.71-renovate-docker-non-major-8872e32d9af6337c5d0b1eb9eaf76c0343229fcd.tgz","fileCount":99,"integrity":"sha512-jUrjtjn/4nUloGoGKP6SiAHgFZ8CE+d0u4b3LhmX9v2LZe8rylEteD579coTK491wYgQQY6kNh1O3J8gSRv4wA==","signatures":[{"sig":"MEUCIDWHwjW7tl9gWWsMJkSg4zKUmAciSNghbqrR1EcuzbZfAiEAjIYjpxCsEnGZSwaoS9FCkx997yZthSWv+JiEEMzjn10=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":732705,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgknUWCRA9TVsSAnZWagAAzBYP/0Ujh1+fWneLYPSn/FrS\nqNdQBOf22YGf1+6Eoiv0y1jBu4jVkt6fpOBg5TLWubK6OVOCfPn4judvltja\nNUae5TB9X7RziIxDeKI+QrkNdLgZ4+rqTUrMx7T1gAknpbp6CrgjHaN1DhGl\nDUnf2MY9EVRTthwItOsWbcTLmh9IAERpXKG5ZEdlTgFpmFNx5KlyNRlZRArz\niE+lJTw0WVn3jEVOsoYhlBB5w5+WLxPWw3ofsPk6+pH+BhrjAcqqPwHh32Bb\nLHuWeOtY8qxMUKOBuRhzJ9b0cdjgXlA7knLtLJnkHcsp+uWbIom+3dP5C/NC\n/zIHERpLuMdIuHzBzKn/n1bi0erlV9OzDcbD/UGIhXXESAhFln/GUppl3Heh\nfkSHKHr8W56H8I21HErnvH8xwKgjXwZcrwDmReoMfHz0PQ31cAmkh9SJcyf4\nTp4jOagKTMM8pqphXeJEacWx3fv4ydHH1EgmXHr52fygzI/uDzp485MpKqEw\nU3xeDpRIuV9YuzJaneclvaRyFuC6znNluDo4FgULkQAvOZIGaqOosg/aDb2f\nEhq3Dc3dDsBaxrPIo1rjsOGylRvhkyg63YroQuyiCAGdjmA6vA38KDbz9iZu\nTAlPmwEWBV/tCSDhOcowau6oU8jWdr4vpQbCRKgofKkjIxP6dZ15RBtO1/YT\nEqKX\r\n=kloJ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8872e32d9af6337c5d0b1eb9eaf76c0343229fcd","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.11","@balena/jellyfish-logger":"^2.1.41","@balena/jellyfish-metrics":"^1.0.190","@balena/jellyfish-environment":"^4.1.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.130","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.71-renovate-docker-non-major-8872e32d9af6337c5d0b1eb9eaf76c0343229fcd_1620210965911_0.8975800718962974","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.71":{"name":"@balena/jellyfish-core","version":"2.13.71","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.71","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f97b5813fc756a70adbf066c4a163e838990e0c0","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.71.tgz","fileCount":99,"integrity":"sha512-63qQa5d9badj+YhxiiKV27T4X4WYHdE6NJPlh/HqZfcsqCFq4/fZL8rlQcpJ3EZDn3QlzF/AbNBUzRdWKhtjtQ==","signatures":[{"sig":"MEYCIQDbkz6qa6Xjd/wMiATBAQABQP2Ul708uD1GAWlKE0HU4wIhANUPLnXWzjKmMMSOFhwT9pfD9ubafzP4ygGMuU6NSAP6","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":732638,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgknY0CRA9TVsSAnZWagAA+l4P/3yjxAGr1v6ibqW+KK3A\nG1cNa11C485+axNTisYxF9cvfbx6AQChLEy9xOfrFU/1AEQpDGWv6BzXKThO\ntExb6llHgv481PPi+krZ3FdyFZDufLV8ghCXeJrxizDCpMf8AC6pP0vjCbT6\nXJYJdUfubR/Uw1ZjYbTbwt3nyqhW7lIrg5mOthZ4x/VJcDWbwJvKkixTjeaM\nTPm8m+a3QWtzHRDoE8hRPmkHS3sc+bY0hSI+spy0wQsTc+IUsBbeBQVgYRq6\nj1/s/Z20l3xi8uHpgf6C+wGEY0UaSiYnMMolFb/s7sWRfgXr9LGefk3bkWqA\nOrxcRU8PTuwWBQemhkJcCGd0iCVI0sPoKHDWzFNl7OtIJlOKT55nzW/zNtDD\nD/zha+OJgrrZbKly5hQO/B2YW613Y+YNcFhSNBTZ6KJG+ssBg2gRB3ChtCXA\nha0y6ELPSgYVG8PaVrR1gwJLJgzTTydBDolCiY6fvqEkKbtTIrtbrnnHgzX1\nuIxb0yJL/Ruz2hRL8tj1dliIHW9gxrU8YXekO5ogWTLeCpNC4odxfCUmRak8\nZ4Aw4/1sAcJlPR2Un0yrKbxNYAume1C0+ILDwPmcyTHN0kh0nzg59LwOHtKD\nXgrz+ySjEReB63XJdZ0OP2hpQHnOuz2yRQajnsnRcK3EVGFNVZuQWOfY7uis\nuuzq\r\n=bMes\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d8a9987503b987bd8222d5ecc266ca2e5225ef04","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.11","@balena/jellyfish-logger":"^2.1.41","@balena/jellyfish-metrics":"^1.0.190","@balena/jellyfish-environment":"^4.1.8"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.130","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.71_1620211252330_0.44186163032459747","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.72-renovate-non-major-ccc1279198280d9aba510f040b7f8ccf9e5892c4":{"name":"@balena/jellyfish-core","version":"2.13.72-renovate-non-major-ccc1279198280d9aba510f040b7f8ccf9e5892c4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.72-renovate-non-major-ccc1279198280d9aba510f040b7f8ccf9e5892c4","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1b70e2fb0d206789cadf92fedd0084620ed620c0","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.72-renovate-non-major-ccc1279198280d9aba510f040b7f8ccf9e5892c4.tgz","fileCount":99,"integrity":"sha512-GDHEHkiTtLwutRnIY9SpSIKiVZMwBNnjgZdK7Bm2FudXcC07nfy+sXEgRJynq8bnH74wiT3Q81Z3oKxHrnkgCA==","signatures":[{"sig":"MEQCIFK1iNCyc+Gg2myMSuLMfoLr03lLWNZEUO2z+kXe/OcjAiABia5Ip5WmHLHtmg0kZzE1sgiJ/Hd3F/DMVv+AHnE3Mw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":733049,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgknduCRA9TVsSAnZWagAAIX4QAJYJdC3tSgGXrVOUtDvT\n6PMIjNeQqeYuoj0agrfoirsnC2IqeBNmrKUoCwotdRJmE6txcVnD5DGvDbVB\ns6HZX8K4+qJeUhxRD+rma5pM73S+HGqU6/qHDvjRL671tcKdAm8AeaQ87JZh\n+qEcPn3LXel8gMEOTKBgLnaXYs5LkmwrtohvKMlInPaV339bbt7p+YEV3S7+\n6qdMkebHHZX4BbMGt+9dlzDX5oHk7NGZTGa3o5V0Fyj3rdf+IVPhcWKJrPLY\n9WQqrzat17/2HQisDxpemB3+kdXk1ci/uvpxxjzhbeQAhlkX6LgknDOagQ9c\nI8Bsco+kJcSh6bKD7mn0LWnZXyzX6a4I5Hz88hhiJxsxRghBQt5Ifsha/9LY\nzXdMWG2cPvQhrOs0Mxb0bTsfc6dAUrK4UcL0PNACBXi3pvm+7q27aE2QFba+\nCqqrYbtJtKNDLuObnqc8cOgmBZvygSJeQqCdIegC9MeH4UM/j70Q5GaDmY6j\nN5DXjW5XV6QGxkJE9E7/7hEaun9dAY4UkKOlrULoYLzwTE7wN1GPXTgBzMaI\nD8o0A73LmYHTGW427h9uO3dc4t8oik+C3TMCJrlc6+K+BF8uMqMir4bKTe91\nksjfGs7lMSDUWrdURjBIf9pAC6KzvA9ocQSCEBW3kd18YX7JUkhO+DnHcBhN\nhmQb\r\n=sfsZ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ccc1279198280d9aba510f040b7f8ccf9e5892c4","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.11","@balena/jellyfish-logger":"^2.1.41","@balena/jellyfish-metrics":"^1.0.191","@balena/jellyfish-environment":"^4.1.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.1.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.130","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.72-renovate-non-major-ccc1279198280d9aba510f040b7f8ccf9e5892c4_1620211565671_0.5640099143989119","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.72":{"name":"@balena/jellyfish-core","version":"2.13.72","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.72","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4906d84ab383b4bf71ab25690f689fd84498a566","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.72.tgz","fileCount":99,"integrity":"sha512-94sQ4ScVq7sf0r0xGnrLomhTBBDZLvTYMjwzfI8+hmjn5GmpQZJxXWoe2iDYRfje9IKlRGIujPGHCIjHIR+BFA==","signatures":[{"sig":"MEUCIQD4VawskV3epj0oZuaYrtIMfKI3WMV/QkA9Dr0qdjedWwIgUf8pIEbYaT2wYKVwNEtnhyX3nZ45rrO0CphbaoBZCMs=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":732989,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgknlzCRA9TVsSAnZWagAAEaYP/RjkAMogQatfZfeirAne\n47wBTtJbvgFtsw/ZuZnTATF0x2dNPs4oo7aZb4q4eIKuNiV6xXXGiLMngEX0\nx9870adDo/elyZSBWReE19rd1YcSjLzS8Gs0wRf1zFYVbB3knEJlv1HSwioz\ny9GcCJlP+csqgierE6+OFC4nH+qBJAPyg1dU3QOocgdp5dtsHcdgdY5/FQam\nWI6ZXIvnPbV64PXwt70sgPz4egaLOzoIO9lWk2unkQOXXK/MrayCLxamfHI6\nrhZzrBH/estwC15DxWizmpZa5ckOY8jT0uVdOe3eG++CEYOQzz7CxEOn8A3h\nnCrvAIbRH6swY31Q2RWDOwgKsF2gK+T8DGENN8LeG8hP2Y4yNhFnrpytQDpl\nvzNOu4Ba0OajfPQeeA7RuSkUefQmwvWULpnWo1BnBiIyONZdzYnKz4lSiMkm\n4M8KDLXLd62qVNIIQfoj+WaoMiLn1ZUCSlV00HOoVpYtVlLMjVr8SnS0weOn\nNKcZHyppj0fGny5AJ112wnbP4wgYPk4XN9kSnIbHHMKFWkBbZ8RotvzDjcRk\nbOvfW44CsLg0SmQA9R5YWBNCMwCNYjaeUkQyQmbkvg+mRCbPN2/26ch65uZY\nq4Gu2PDSqyUGOXidxMsGhysjsXA7P2P8gXMENNOjtbVu3//c6SNDpUaCm5fM\nDCqd\r\n=Rfww\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"631a7dff51598cd623096f90dc4dbd6c6c69746b","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.11","@balena/jellyfish-logger":"^2.1.41","@balena/jellyfish-metrics":"^1.0.191","@balena/jellyfish-environment":"^4.1.8"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.1.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.130","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.72_1620212082766_0.7328835324283627","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.73-renovate-non-major-d5840a03c6069ed79ee3939b82dd3e9b46ff748f":{"name":"@balena/jellyfish-core","version":"2.13.73-renovate-non-major-d5840a03c6069ed79ee3939b82dd3e9b46ff748f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.73-renovate-non-major-d5840a03c6069ed79ee3939b82dd3e9b46ff748f","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ff85a8eca48ac6792f7ec7258ccb25b2119fa622","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.73-renovate-non-major-d5840a03c6069ed79ee3939b82dd3e9b46ff748f.tgz","fileCount":99,"integrity":"sha512-+doYN/wXiF8/EBVKepVntV/nSnwLEp/3YT1Ga1rPwupZ/1M/qKy3dVTRmc/SP4O4rttXGnEfbUX5jlTLRADEmg==","signatures":[{"sig":"MEUCIFYTkKn7HCfJeBkQa2XoGpqLUp3rlTZUKiGzZecXc0KqAiEAg3EOWwyW9pSHVdZxFAuJWWNl8h6wTn91icbl6SLthuM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":733472,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgknqdCRA9TVsSAnZWagAAHxIP/2pqVk5mZws3C0E7HazV\n9qraWIsJHX7i8RXzXyhqEW9z2FF9KTFoIIrwSZ5bjA0d55lrV9wkOJABGU5P\noBsPdxGZBE+p8wXJExrIZBv5Np/b358tVXBS/bw4sD3i2J+jCdbBSyH+d8fF\nfxKAb78mydCe0MDm3bWfgiixHmBfC4kCqHY2Ga0vgicf73gnbA+pbwqiT5AK\ng33rv2bnJeAxMcJ+QyF6v3/p9oFsE48Y8xYO91+vppJ9+H5a3OnkLikABPZZ\nnQcVDwyPyLWeFo9GsJYe76tEMxC4p9NU6dIiNuvrs1LcccnbQkIR5ZnyZAFO\nRRIUXWcgCkDaJveCKddAWUKREQwwK2QHcPyDGxuqQw5ZignGDiNx+ciKEMdb\njP8hJ0bJ7jZBnE8qdL/jovydlMaxFs7mX4jrMIn/zRYe9mESM5T6qr0YPJ6o\n9aMqsingYDXTBRTMv37vxYXHdOR2EBvYT0cdEEH3TQDHTfpCQQmtVjZtCiZM\nFWObLadyx3Pq0K4MSkmmX2OYik3Unk7jo5mc6WlDjdH3H6CdbG3wGSoMQGLS\nQiZB8HX0N19o8IDesGEk8LtPSszWcR63dKLHKzYfVrF6JuigQT5l8SyljbL0\ndXcIALC2DHIsS8A6/SR3c2KpsW4u6gwAriN16gpbig9wFmmwRGQZQRApWPBJ\n71Vc\r\n=QOCY\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d5840a03c6069ed79ee3939b82dd3e9b46ff748f","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.11","@balena/jellyfish-logger":"^2.1.41","@balena/jellyfish-metrics":"^1.0.191","@balena/jellyfish-environment":"^4.1.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.1.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.131","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.73-renovate-non-major-d5840a03c6069ed79ee3939b82dd3e9b46ff748f_1620212381345_0.28820265627889063","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.73":{"name":"@balena/jellyfish-core","version":"2.13.73","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.73","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"50c70c500d5d3e8ae3ddc77f4465f6285ddea121","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.73.tgz","fileCount":99,"integrity":"sha512-Qj9ZKkK5G4oUYSHFLh56n4P7GWCz9lRsziHQ1uvIAqezcNH6Qqs71TNTLhjE9ZuCZ/DIpX/FF0aQseS3OloPTw==","signatures":[{"sig":"MEUCIExBL1PNRjraJSpVCChxTYdKjYNRkLfkAUC27S8zz/dtAiEA8XlkNVAFV8pFhWQskQWKT/eYrDLtnb5FimduvnrUClU=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":733412,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgknxxCRA9TVsSAnZWagAAP9QP/RgvfCvcKZDHdM2vwW6n\nUVxWT03A2ojEzvDUVWNB05iKdWQMKFch8TNi6lGrkQnp8+BzbQ6LZNDr9jIQ\nXCcvLgtSfPIVpQhYPfbgNgfN5N/o4k79alR3mldltsfeR6VRTvYTh0UJS/oz\n3QYTK2gbXvmeELWMM18kQyo0ExDTQ73VeczpTSoM+Ku1YTia80fYkCM7AT3A\nqhO5QnzsIvqVogIBsgOm2nSnyTmh+49wU7jUXg0kLdodrWY/wqX5YR31Y3LP\ni+IqTGJcmqHRpmancHlzJjqbgHwdRUOsEue94CsRVWFdqQPsxoMOieaREd48\nneVXNZGSLaOd5tOxBPhYzNj/vlGDQHF7pkWyrjKMwkem9kx6+Xg6B73KLOMQ\nCPWd5ANIL45ufpk0tHczXSBrVQMI6ev/TYmLfniHHCzPzaEVtw0PIRLHCK2/\nhJsoch6in+XlydEfOiKz0kwFgVmpDcbipTyxVEeIDDZF5VjEjklFvE8hb1Co\nAvcqS+kH1hPh9eIF+0RvnCgoiD1y//4Uf5mzAbO8ke3dv/8+Z+gXwomrBHVK\n2+KDC/j+40sdnG+Xjq7K8u+yRGpIcykOCH8UuyHH106XDdaSOaRrGCoyu39g\nWD+sIxLwdjJJ7GAoRy/QSvIY9qVkuATTIiTYPco047qb3Ghue/HdrqLucjDB\neDxp\r\n=ocrb\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"74628a233a263f1365fb17d03a9384d37e89b094","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.11","@balena/jellyfish-logger":"^2.1.41","@balena/jellyfish-metrics":"^1.0.191","@balena/jellyfish-environment":"^4.1.8"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.1.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.131","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.73_1620212848670_0.26037773912582773","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.74-renovate-non-major-f6947b5892c1581dc7c93ef53b4cec34ecf63989":{"name":"@balena/jellyfish-core","version":"2.13.74-renovate-non-major-f6947b5892c1581dc7c93ef53b4cec34ecf63989","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.74-renovate-non-major-f6947b5892c1581dc7c93ef53b4cec34ecf63989","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1de7632a0ce20bf2746b659496de6e8aa3f1ba6b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.74-renovate-non-major-f6947b5892c1581dc7c93ef53b4cec34ecf63989.tgz","fileCount":99,"integrity":"sha512-/IewTZ2fzSS/yFm/BNl9+YqRA7yLhT93hCZtEfs9kGFeagoI9I/wroQL9uL4+D3e23jDBlMQYS/qqPCnW3A0Nw==","signatures":[{"sig":"MEUCIQDueZEQy9Tf8TzVxuYZsQGwQC/wxgajNUe8Qzm/qy+8UwIgSfB4GngzPlvJaIYu5NLlOQr8OC4IsFD3E5L3RfimAtE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":733823,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgksrYCRA9TVsSAnZWagAAAzgQAKNQ6iiz3VHgsh1ZUGDq\nmaZGKvlh8cEEa2pSex2iD3S91Mj0PtrjiuOS8Ng8PxUaLLjftuv0LjH2sq/J\n6XxMBg65PLa6id0aUDf8Ou8mpXvPpodWBkW98xVdRxTLgenAjryVV1kadOS3\nnCNQb0D/KtDFMYOxeQnT8EdYCS1h0NUoerO/phkYFZCUXhtnCTnZYR6Nstr+\nNs/jn4uaAw2a/ZaP/iKkof0iQepchn3SyYP63ELCRkmJeUMGGs5qPNl+L9wv\nhF34aFjJbEdrZ9b0cL6CAn6+5pKRL9e6+rMQEePxfoO2lpLHg2v+u37RvZhe\n0j3YVhq8qU7kaEvQ7JGnCeRpZAh8sTAz1DUOlS/zkPiUWd+vaQikMGl/181o\nbUsgbVvch8VejsWcaU/qnfYYqcmxJV9Nw5RFD+zOV5sFnxsHZPT6pk13783v\nDqn6UPqEk0lhrDbVbTqHi/N08pjuZG/Bb14NhsHTotYu0w46tVt/d7LqmQI5\nQeojW4sCA+lmFDPZN50ZjqyBM3QyY4k+umi0u5+eTYnkXo+R/u/jvc7f4Rqo\nGUnmG/8WqhbNcd3GnZjLvgX4ztbIRvqtKY2zKsAYPLokMuaq3vYkwn7BHkVw\nKLNHLwhYpaoO/7otGSqFDJLSTpTX3GSiEDb+siyfVFPucCzPJNq7s02Wwh3F\ntvFc\r\n=FwOS\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f6947b5892c1581dc7c93ef53b4cec34ecf63989","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.12","@balena/jellyfish-logger":"^2.1.43","@balena/jellyfish-metrics":"^1.0.192","@balena/jellyfish-environment":"^4.1.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.1.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.131","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.74-renovate-non-major-f6947b5892c1581dc7c93ef53b4cec34ecf63989_1620232919874_0.6660269028937071","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.74-renovate-non-major-5c27ba07336c90c7889a8b09f130a7adc465a5b0":{"name":"@balena/jellyfish-core","version":"2.13.74-renovate-non-major-5c27ba07336c90c7889a8b09f130a7adc465a5b0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.74-renovate-non-major-5c27ba07336c90c7889a8b09f130a7adc465a5b0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6a88b4902493baa923f5583e277811d739bcf94c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.74-renovate-non-major-5c27ba07336c90c7889a8b09f130a7adc465a5b0.tgz","fileCount":99,"integrity":"sha512-NBvfqju04ZeKI66TWT7eU1LGqfBuRs+GhtGRKlVkCPaB1z6hzWv+VZiQwMeQVykzfvqCuVIQOF3frOl53MV74A==","signatures":[{"sig":"MEYCIQDJsd9eJ7NIvXDX9k8llSLX3A+G6xWZOBZbjvuPzxa43gIhAJBs+NV+hQ4UyJl2E1SYIg30AhBb/92gfEfEtu8qjglj","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":733823,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgktxZCRA9TVsSAnZWagAARcIQAJTnexYQn9Yd6OB2X6zZ\nDYM4i3cJpvDnAen4n/gqOsFXOnjoIJs6cpJm+LWqy4ULFNws5edhu2d1aXfD\n1I83bqFO8FCIreKA8fJsQY21Fg6gODDeiBWnZ2EgRzlDOfdKTuoCQGBN7qlH\nnzYPsXydSqoCZo16EkvFaMcozvPGd8OhfqOzD6+cnw44iO0inHoOPVxgIhvQ\nte9hJJ/VcYGpaXrOhWCwhlyQDjm87P6DQ/7xczTZBaYctRls4ExJvgL+YqKF\nBjrvLwhhIx8ZnR7Q4nm2LY5rHv3pyA8cApDoKxOdW3FGwNSUDqxOiFavhycU\nXKbfVOO3fyK55Ss0Lm+JQL5FcCeztWDnEtJ9FFkSdMbsCxugSxKT5PzZn1js\nsrK3cW4A3fv88ZMZiPMsal7gFnmVKOcg6BRO/0UqNCjPXXwZ6ZFOws5dd4b7\nQPR3RGNonWItjSvosGAzcmk51eMDTgEcWcEywKe2laa64W8JxgYSo3uISqN5\n4xxmmwKD/HP2fWHYhcxRx5ApWS8a11p80fANAW64493mR32PaUz9ZtcEf2af\nwODt1g4gvW/mWmvG4FLD65Bug9/s1UCttEFoLWv+wszNCJWfK+zwUetC/9FY\nNiu5P+ndgom9g0AtLZLq++HgDDdkKS2o+mXk48+sAJWnKoePbSvK/dQyW7qc\naO7i\r\n=MzUE\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"5c27ba07336c90c7889a8b09f130a7adc465a5b0","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.12","@balena/jellyfish-logger":"^2.1.43","@balena/jellyfish-metrics":"^1.0.193","@balena/jellyfish-environment":"^4.1.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.1.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.131","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.74-renovate-non-major-5c27ba07336c90c7889a8b09f130a7adc465a5b0_1620237400928_0.4970421604107427","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.74":{"name":"@balena/jellyfish-core","version":"2.13.74","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.74","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4cbf5b213d3486841df541f3738b72f2adda8ef1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.74.tgz","fileCount":99,"integrity":"sha512-/3hCFgYFT4Xct0yCbBhhuVD99VxR/OYsr0Jq7UI94BM0QSoMzVoavb3UUIntqA+3hqTZXMjU2Wp4UbUbv1jQMg==","signatures":[{"sig":"MEUCIQDHflr4HkBOyPb50GDD46eR727GfuS1SKxJv0RxPiYcvAIgQDVLVqvwa76Urd6/jd7FgqqciJ/woEKv91g+4DADxVI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":733763,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgkt2dCRA9TVsSAnZWagAAiAYP/2H2vXmqP/T6uBcM76Hb\nkQIfOWQ2qGABSWeWa6Wqg5xh0YK2Ln0npCEOVCydAXXoH2g6kfmJatxQuUJR\n1SOn2p48WhjW9bX2VT0z+vFinHsVXb+98GAZgGGsraHtWEz59bGowo0VQI+X\nM769ED07B4Tih6H2XQEzNC1XYea8zgrtEXaEBhF2rUon1CHIidNASQYZ9Xpp\nx10FnMYr4QyDBgWb1x8G3iPHRYSuYZzHFwnNSXLtrCSg58Rqg57zIoLqi27G\nbHc7tjPvWow8C/Y8PWit7pXvBplvCSQru609mE6D7AFmR8Ykf9NRTHcvBvEo\nfBdTMBZZbO1futupYUQu+HjQm5UPx0zr1kcPzzx0IU6aQQGvDE4CwtanZ7yC\nngL4qkBM0if5fJ9k4ab4sUJxVT3Mlat6ekusI2+e3e5Heo7tuyn8Ll35U7jg\nxtfmTbutvIWcyKThrMu+3KSsAeI07wx9jAbGAQP7DvlD2Bzsx1V5R4GOehq1\nylRXZQs8puXaTXLcg5nNR8MoGDvGdKxyyohDdmF+GcGnVonEjYCxqrEbMN8o\ne+uZyMmxHdnAZL1aD0ugLCO4sDbNZ2kOgKqiR6WIa+aPiA2inW2TdRQ7UOnl\nRMHNZND4RSIx2isBBXYvHa/tBHWNq7CQ0GALmSaIrDVMyePeSWlmFbY10wEz\nPmzI\r\n=wfak\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6beffc112345b7364a74ef89904903da5e0f1499","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.12","@balena/jellyfish-logger":"^2.1.43","@balena/jellyfish-metrics":"^1.0.193","@balena/jellyfish-environment":"^4.1.9"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^1.6.2","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.1.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.131","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.74_1620237724992_0.005088774416537767","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.75-renovate-catch-uncommitted-2-x-aaee9401caee0c88475bd53e3f41275d3cbc246b":{"name":"@balena/jellyfish-core","version":"2.13.75-renovate-catch-uncommitted-2-x-aaee9401caee0c88475bd53e3f41275d3cbc246b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.75-renovate-catch-uncommitted-2-x-aaee9401caee0c88475bd53e3f41275d3cbc246b","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1b13ce61fccd719ec364bc6edf76dbbf7b95eda3","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.75-renovate-catch-uncommitted-2-x-aaee9401caee0c88475bd53e3f41275d3cbc246b.tgz","fileCount":99,"integrity":"sha512-Y4qWjXdTKJTz48niB6CacV/Jm38GG6ctdE6gnkPsPXlW/+JxbRExcVMtYOF9lxij236eVQ8fcpAGEtekWj0ttA==","signatures":[{"sig":"MEQCIHJxnol8pjyHJLgM5+yRy4CQ4rkYQBXX2EMQi7darOTDAiBwD5QBrIdz8D4VOs+0/4Dq9hliQGAutlsXwC5Ao/IvKQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":734236,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgkw/SCRA9TVsSAnZWagAAld0P/2M06lsCQNkYFj7gbqCg\ni+lBnd8CJnz1QCemTf+ua3yfVZwKvrQFtypG1MOj28+i1/D+TcjPX5AtJGsV\nK2KtbMJ6tEytF75YhF9Q/9IBKpntnF8CE6pAmBeKMHm1OFiosOUUWwFcJ3Mj\nBq4o9Dy+85e7F24aSIfZYBIDh/Dm8/B9dJYmQKQsgrlirVzKOJnxYQaaUOLR\nDVQEk5OrEgEmoIl+g5kS+uUVLAYwfrCN0cew4oJssvSjea2Pf7bj7xtS3WAy\n18X3Fh7ol6dZJEgC6eBJkXMwd8VeDpRGrxWWKEPFPrlZPo6ei8hQKjhv5UsN\nFbyd2jWQJ415531qxr6oLJL7ztKuR2ow9sMIZkd0/UiD4bV3evmGrQ5vDhvB\nJwKWuMvhodcb3FerxabxdhrdVNV6bhXC6ia4v/FKKNE5+Xz4C6riB3LHtXdT\nmr9zfmfQDYEj+zrpO9Qk43qsv1iBPRz5KwrP7WVZhcSqsMAfw3wbmhD4E8Km\nVi5gZC/RAShetx6PB11s6J/2Y3rJrVsSYU9Kewm2jPskvcccNXOseEJLkCFG\nC/C/T0lxnpayjj/MnN2Fjt1KS+McCuYOCY3eSHNWCbXphydJ+tEKin7/8b4g\nnle1l5VhY/NFvIHYAwm33gn14g64geByYwI0CNiJk1thCktgbRsUcH4uv7NN\nfjNW\r\n=V4jh\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"aaee9401caee0c88475bd53e3f41275d3cbc246b","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.12","@balena/jellyfish-logger":"^2.1.43","@balena/jellyfish-metrics":"^1.0.193","@balena/jellyfish-environment":"^4.1.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.1.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.131","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.75-renovate-catch-uncommitted-2-x-aaee9401caee0c88475bd53e3f41275d3cbc246b_1620250577515_0.29953949508582944","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.75":{"name":"@balena/jellyfish-core","version":"2.13.75","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.75","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"391e1598f8dd4a51b83329c2a33124b008992e02","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.75.tgz","fileCount":99,"integrity":"sha512-Z5sg2gX4UMGSzeDd5zucsz6UFVsnQVIgKQnkOyt9TwvR8LyU8WZz2hxhg78VhIznbOAh7m/PK9cp40AxlcoVGw==","signatures":[{"sig":"MEUCIFB0MPgjcyK9IdTg1zX3sUugjjLKaMT9FhpTPp6rGmPYAiEAvHdKDY5GjY+vLoNuScDt/3hKGJmD+zXJ7ZN1+4jtzpg=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":734164,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgkyXYCRA9TVsSAnZWagAAYPIP/iHM/2O1hTy2GBOt0xJg\noCpSPRLaCPqxMY5o69qU5t6KrrsoEL3qaCUuCG/AMfo06IQblSsthl3qqgaI\nwuunJb9sGhrFKr3415pVUaCCZiloCs1vaScULGW14KKWGGcFFINdaoxYTg5q\nyfeRBmyFoVCRdZyzUoymsEFyxCi7ISd2drAR7ABelqOdiWV43ZYFnPGLY9/L\nhn+diqbR0OB24i4z6za6Eu5808uUPpPHBHFdLmZ/BykwoR1s3zr2HHvEnvJX\nUDNhSkxmJ4PIOEnLsLzJd9C6HaSdXgwnDwMkT/XqvuK1Pb6jNtnBDvWin2YF\nFTb0ygJkNhcdXviL/HlRTqkaQ5sxR5xfDWevYiPUKk319Eir/IVBUEpxZtsf\ngEpY7T2Ct9q1wDAXqhAhcp+NBsgBQICet/7HZTt1E3IfAwh+nRotNC0dsyGv\nNz2sqOVSaY8UFVjYft+TVKCoA41XVUIq29+sWYvNCa5J7hxHRRcqXtmNgOqz\nasxXEKp440D/OUit8URO6Qx6O24/wr7aUhGSxPDmgLaL3WJ3Dt/sumFGqxaK\nXh4GFYmMfItxDoaQ3GXNRq3H5JCMmJPMkp0+GpI1H4E2cNo1PRIr2JJOA3Ib\ndUtscjIcO4l8anxnLuUpJBNR+0Vd7KorRTOVGI+SVQqSCxKrjwx16fn5AI3S\nCeqM\r\n=jwV9\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d77e906d5ca987cc2ee8e130c7c7497d5e6c6e74","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.12","@balena/jellyfish-logger":"^2.1.43","@balena/jellyfish-metrics":"^1.0.193","@balena/jellyfish-environment":"^4.1.9"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.1.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.131","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.75_1620256215495_0.7412746110488899","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.76-renovate-non-major-219e028c760248706e337ff90413655f51b775a9":{"name":"@balena/jellyfish-core","version":"2.13.76-renovate-non-major-219e028c760248706e337ff90413655f51b775a9","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.76-renovate-non-major-219e028c760248706e337ff90413655f51b775a9","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"62e6466cde7437ecc3cd86415bc88e6c36dfb3df","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.76-renovate-non-major-219e028c760248706e337ff90413655f51b775a9.tgz","fileCount":99,"integrity":"sha512-WS5QL2wUoqqaWTQEqOXKRUw4FHdrl50qF4cOw0X1xDTi2xKII1ZVpxxSl20PRtDDLlAHr1c378zF5YMEZ0+xbQ==","signatures":[{"sig":"MEYCIQC4kpJX0PDOjH20Z1rqWHe9UfvAf1yt7i6Ss41WMM5kMwIhAKXzUEKzevlwRKbWfkYBFXO7byJTWRqfWqscZbr82Xts","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":734576,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgkzZMCRA9TVsSAnZWagAAqAsP/30Ey14r6Xa3ybe97kWU\nHMSGQu7EWlRRf5bRqBbzt4TIqhviaAKKoP2NBWyC01atm+gS/t3Caaq8LLEZ\nB78LUPtJ4oMNJJ+e4pv6SdtgFRiCo0uM1Tx1jGc0lyuv4GTj4uOhEoYpIgTl\nxDgcqMW/2zGNarubHvzIfcPBCy3gllCGE0gk1hPk/QLQ8k2anh+fjaQK4XxX\ng+tRx+3BCZhXo+xLEJboUlntqcVzzFCCTi/KcV28NQjJaMDr+w3ATAhAtC6q\n3MXsK7U+wFA819V72tG0MJi/x+bY5r8afqfuUpTHAzYIgqtS0LwYkLbgUqvG\nMTbR2+7V1XIMGYRSW8Z6XMxViVmU2H8uLnurWkQr95PzFzac1a7oxsUN/YBD\ncDVGV1E+vrb4eSN7Rxn6UI3U/N5EjDHTCrlQJ+pqo1umezxCpmVvCUAPN2fz\ntYZHmP/UFLFWBWiwBBgGe9ccn8hgh8KhNKB+SckYOrDrRsa59y7JXRPFs/pc\n4DfhZUTDMYwae7hGs+2yGUVyI/+hLybZwm7LPQwalVY9HPIzucNErSPPckNp\nWzEKy00b1PdWGQGqw2nH/hgYxvqLmEo0FEVp2byDL0zYSxzJ5V8MCwn5ubgD\njHqFBkaIQiZ8Uk9Glmiuzi7jP/k5jlblHrLVkrPo10TZGoNC/a2NOzyENQCY\nt6Tm\r\n=bN0C\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"219e028c760248706e337ff90413655f51b775a9","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.13","@balena/jellyfish-logger":"^2.1.45","@balena/jellyfish-metrics":"^1.0.196","@balena/jellyfish-environment":"^4.1.10"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.1.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.132","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.76-renovate-non-major-219e028c760248706e337ff90413655f51b775a9_1620260427863_0.6326277984674868","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.76":{"name":"@balena/jellyfish-core","version":"2.13.76","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.76","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"33717aad4a0f9ab2cc3af75cf9db4dc7754cbfe4","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.76.tgz","fileCount":99,"integrity":"sha512-3UtXjy5f1vkI4OWlKi/nXcPZiXg9H1cfgDNYO+n1LiqF417vgg/HOkDLFwdXkkl7yZyo4mVEWJyzhW65C2zzjw==","signatures":[{"sig":"MEUCIQDD0Xr1VWV4EhmkbYfRVP/dccFZ9VRyGBKfjHsF/uyMzgIgBp2WBk0GZpYeYrlh7AsySiSiBwXGjDi3KQTh/A8Hc5Q=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":734516,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgkzeRCRA9TVsSAnZWagAAM4MP/jnKqbrvkZrAeVOn2kDj\ndb94PmZ+pkWZJ9QOCwCO2vehFPgAo14+F7CDwXnM84r0sX+QSW+rwClEKd7E\noXnIF23mXqdioVoabKsNRPXUqW7sF/6uxo9ELJef8EfDmz718Zg47eGbn1Sv\nrs1BAe6LjbFFsWeOutpcqeTc+1LzkgAtTSo6KuUpCw+kYdiThfF0RT/MSWoz\nTd816aL41KBSfyujqLhjNoUZYVj8uzBbRDvIvoZ2UnhXYAOKgLoUZlSAvqCt\nKjt5/dSOUlHb2w8m6RL0bjeuowLNxml6ZyQpfeAGriwtG9OCRQCpDTT2YtfU\ncOGiBMhbCWz6s/aGqSKLHMUhF0bLz/dNbj+YBulUpRlBqFOnTzQhunPF67z8\nLRtpBO4BbWhFlqj4wVNeXvuehzVoabpvCcn/nK0yLwvRucGtjCAo/cR/YIO+\n01xV0jjGRu8vD+CALRLHVM8KOoVtn705ASi7Iu7vZ6XI2u2Ffjjfe8NkgCCm\nQoWnXCFNbA821zHNKWqzSeMui+UhH3QTsmN5+v9KcoHvYfvJYGGJUDc6y1Mo\n03VRS/2hFapk2Bk5dEaBQrDRnDo3Gq5aQ/bQIq1tqspTLeVPcoXd0JEBNuvz\n4moINE/CO6gf719VzA6Z8H0vbNErq2gwH8D3ZdrvajhslNv9GMbRpixwU0rm\nkRma\r\n=2QFT\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4ddbb6ce0ea8ea6c1b98c74f10a00dba35404baa","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.13","@balena/jellyfish-logger":"^2.1.45","@balena/jellyfish-metrics":"^1.0.196","@balena/jellyfish-environment":"^4.1.10"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.1.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.132","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.76_1620260752391_0.38505378181247796","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.77-remove-dead-queryv2-code-98a5aac8f283b5b650b44f52d5814bc83a5d14e7":{"name":"@balena/jellyfish-core","version":"2.13.77-remove-dead-queryv2-code-98a5aac8f283b5b650b44f52d5814bc83a5d14e7","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.77-remove-dead-queryv2-code-98a5aac8f283b5b650b44f52d5814bc83a5d14e7","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9cfede68a18d8dfb76d8200eed2e2ab549385d73","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.77-remove-dead-queryv2-code-98a5aac8f283b5b650b44f52d5814bc83a5d14e7.tgz","fileCount":99,"integrity":"sha512-u3Z3DFQfkSwIZz3n43pEWRzwVG0U5UUYIGm0d3g01OTzKnvqmo2hXZRYsGtZxz/xC8tt/Oqui8uc8IQKNdEIZQ==","signatures":[{"sig":"MEUCIQDKgUOJ7vC8z/fQMB+d2PrID2cMRs/S634lJ/MJ1Qp4dwIgKiZIb9oTeDacrxaBoAWzbDZsANrOD6gk9fMRNvIikjs=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":734443,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgk9u/CRA9TVsSAnZWagAAxo0P/jFmOE1v8+/jGB6yk4ht\n5/cMdI54DepgEY2bUe90zWaBaDps4qBOGjHlURyY7RZ2EHOes3nWmxi7crZo\ndAgzAEBy1rGSuHBq5yofHuBE2xtP40aZQzmsWplSi3l0LtNVDn+IhkG8Guvn\nTBA+PSdNy2k09vtoaAomOScMg3PNnRTlvEHXcnJ0JMYyMDGzHszNofD7YLuY\n36LGKM/rWX6pZhxA/GWgBvrCLLWXRpzBB9j9fDdFHjhkhsQZhlN0SVATBfS6\n0D4+rZUKBiyG+juQP2NU7eFhwV6onoqvfX4efUdCXbc2ySE26sDw58a3Id1S\n80S30wjGIJ/tTRVJPq+FkFl7dCc6dMigc8USquQQRc7aWxAIT6N/8KCcRuIJ\n+fYtDeXToMfgHa1Gk1cfd6aAyQeV1ThFBBOeIRvPqvna4xPSL+fYc4h4TGKq\nue6yC+p/3r9DFPB6QTnnF1urG2J136we0HpkkNte3X0dopl9SsstWT8E//0N\nYmlCmI1lDFii96L9RJno2H7zOulSXDbzVckVrm02LuwWAIJoJguALMlxiZlO\nM97w+q9OpRun3uxO+rSGVCfz7GQG4wU7/7K9BMAoh7Dncsv+IsDolj4f+Y6W\nc8nDbB3vDuhr95sr+SI5ZocGdC5ol3zN8dn3jEFywbSdMWHTiNEuOkg6dzcF\nulTf\r\n=g1Qi\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"98a5aac8f283b5b650b44f52d5814bc83a5d14e7","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.13","@balena/jellyfish-logger":"^2.1.45","@balena/jellyfish-metrics":"^1.0.196","@balena/jellyfish-environment":"^4.1.10"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.1.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.132","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.77-remove-dead-queryv2-code-98a5aac8f283b5b650b44f52d5814bc83a5d14e7_1620302782805_0.8178159941131888","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.77":{"name":"@balena/jellyfish-core","version":"2.13.77","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.77","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f22c7e942f17b17fee290da7673aa3d5ef200bf2","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.77.tgz","fileCount":99,"integrity":"sha512-9vCPUCsyGnPlmhOYzZ/UQL+mcEUv5XKRFhz0lUFzNhdp3GAG0S3SlVPn5sdZXrCwyUQyxL2wQ0BiDmuVaQZgFg==","signatures":[{"sig":"MEYCIQC75aoznqfE4BQ1hKov7Ds0hWjH5LyC7YDV07k81J4mzgIhANSnIwHiQj77egUXxQdLRT44NP5C7iueyDWEHQkUCmtk","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":734377,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgk9+VCRA9TVsSAnZWagAAF3YP/Ri6b1Zed7xB4K4SFESZ\nMARK65JzpbZKoWV1c0IIsq6Deg+ccEel71CsChLv4glpHUjMXMBzO8G8O9U4\n1rgow2wvvnqZHIqN3Z5WEoChPCXk20U4eTQgfNDqouxU5uodBu0boZ2qHzzW\n4TJHKkRMnvHPpNgym/GisY582Nvg3RJZNlbjCZWqF89Urdqs2CAFb51Lb7/h\n204/0tllLNadeBIjSk0O6ux1o1F7zJhLcemQvHMeG2Q0efdmJwwC+Sjfw+Ra\ngT3O1UZZvu24GHW1CSsIGdl5org8kWXX6aulbKrvzAwsapU/j9iVgk2MEGqz\nRQFIyFrOj38jAcUMzax/yLVOfCBfO876gc50AI8rc29avys7l+9jfGoT60tU\nrhN8RFg/Hcgy4Oz2I76Guu2DZxdqne7RtduWluo0yNCS6+JD3dgWYrO8rnez\nTKLmJJm8hG1TjEKsLJc0+l9vTLFaqlInuh7RLXt0QCAxZ24cqryjLIXAKYVZ\nT3FFxWbF+f6TGrvMAc3f7JmrnUB04xvOHTO/z064YN6HZJw9IrwFuQbOWUfq\nifaxwHRzDQLnjvjMxFAGu/m77EBPqC5t9q0zHwQwl4mE8ZTQhIqM69O3hrXs\njwOYr36AXiW9g3fy1w3vCjJ6Cf8wnW3D6VujHLkq0Lx8g4TlckdkZIVjDGqL\nqKaZ\r\n=bSzh\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c59ce5da9565ee19ae99f037591b0c87f6da779e","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.13","@balena/jellyfish-logger":"^2.1.45","@balena/jellyfish-metrics":"^1.0.196","@balena/jellyfish-environment":"^4.1.10"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.1.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.132","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.77_1620303765063_0.29320410124923524","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.78-fix-scope-mask-7160a6e01471c833e84790c953068dc722e0a850":{"name":"@balena/jellyfish-core","version":"2.13.78-fix-scope-mask-7160a6e01471c833e84790c953068dc722e0a850","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.78-fix-scope-mask-7160a6e01471c833e84790c953068dc722e0a850","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"86bd29ca3e964bb8f5bd5656461e3805008b0798","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.78-fix-scope-mask-7160a6e01471c833e84790c953068dc722e0a850.tgz","fileCount":99,"integrity":"sha512-JyDK6eweth4MJKeAaSOwIsP2GS7mpDI7Svqe4Ee9KlG/TkKjIPfxgiBuE6hHSVnWMy9wg3T3Lu6ajLqidajETw==","signatures":[{"sig":"MEQCIF2JZMdtJQOoLKFp09QyF5rZ+c6DnCRECPanT3bqi0uhAiBE0TIQk0QJfuoZpeyPcTeRXlZyOzlVm2bUxmnULnDzmw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":734918,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJglI56CRA9TVsSAnZWagAAMokP/3Tia4wi5WWv6x1SOOJW\nlsKPUMLrtOSw1rID3iqURLAts9Zr3GtP3CEdxrwT1qEMg3/u2CYXRot4qTO2\nuf64/vs4DyKr8iH1hLZmd9ZmcmCorW4kLD54PkoU5Ul+HC2x48wsmEawwHyg\nVT5T0rdsFKongGk+PabgIuKDxczWUHX9IzvMcdFzAxZJR2WNkA4DFw/5H818\nhbTH2YXj+ofiRkwoiccju9hAX9IE+w+C611oOwZxI6vYe/GAhX3IzRrXbUvW\nhGVQEwTeSq0FwK1PmYpVKh08iPzWZZ1YdwDWjDYwgefQHvo6DU0oBD7oRl+e\n5GLHEnsX0TFjC0Qr9R4/+7+RW2wnP/Hu8jRrKIyF1LFoQn9whQx80JkJ9anu\nVWH+wxsa6Pu/VbYj05NsBY9kBuf63LdmUIliMv/Voy3Ol6CHEx4jgLYIS2vB\nZ2kJrPT//YrtMQKzmp/gZO9ytYXLHZvlIzqcZJ/bIn6+M/ULlxoxJmIaN/V6\nyyaXQwZpsu3auKdk1ZCFL9VPxfYwkE+ScGYaAOT2eo74LJEYiJPleIk9f3jw\nQ18e3Fcbm3UQWHCM8JbGLnkGPgfqWwHNT0L3EovFJIhi7n02dFJXzQP0JdMA\nnwcs3mZ6Rd0wcevPCFtpQxn4l/3SbnQVETBQwKvk+5QzA1puigqYDiRHKYhh\n9XBo\r\n=asC1\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7160a6e01471c833e84790c953068dc722e0a850","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.13","@balena/jellyfish-logger":"^2.1.45","@balena/jellyfish-metrics":"^1.0.196","@balena/jellyfish-environment":"^4.1.10"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.1.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.132","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.78-fix-scope-mask-7160a6e01471c833e84790c953068dc722e0a850_1620348538085_0.9476286599990684","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.78":{"name":"@balena/jellyfish-core","version":"2.13.78","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.78","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"fad8714546cf7c2c842ab20c8d88201e17c2d5d7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.78.tgz","fileCount":99,"integrity":"sha512-ExwPYVZBtk3iJP2xWPns0NTnnGYeRAu43awGbbRAOU3PwY7DSo85fHhCxzo4hp+92tlteMpfUYSGqTdoFMAmqQ==","signatures":[{"sig":"MEQCIG/3dVdtmlmBpPdnBPgcsJnpRPeKaWGHe8z/zOSf9Uq/AiAGLGAt274fXebocPHEBA+eGPcLXcfifqkIf66RQ8bXKw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":734862,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJglI+1CRA9TVsSAnZWagAAX1cQAJTW7MfGK09bS/2xyqZ/\nabuubLWKP969L4reANSiZCif+tphfC/LfAlY++QL2xnLboI7dwg3eWF4OMSC\niXrQB3+7YY/N1TGcVlwY0QiEagb+LX4qJ/RM842265lUp8XRsqXzRKjI6m7K\nRB5uMgQaRodV6NFclqKjD+dy/cGaXCBT60o5wMq0rmae1C0Cwd2zrJ8OD6c/\nqn0HoJx+4ustOO5LQQJ8kmjzFzTUxGjD/qI85Y/GRZqnjmiKUPdC16Mz2gDA\nhQtEXR7Xtfx51Z6zKTNeXw+gfiHxePJVFvklP886nHFWX6PaNI978DYouSb0\nDPq/HcGFn/uTEe5uGcAYK1fqAX4KNCtdz+Sc0bFoBEU/6OYAMmYiUV4pujNC\nPjX04n0HIFR0UvY3pNoHF9WOxwJ+jz2qSi4Z9vjD26D3+u6z/kuLfnWfTFlq\nC0QDGExBzrHQ5j4nVR719owo6JACJmY+U1TcnEydX5PPVf7tRe14YaZul1ST\nNm2WnWrDXLsbE1HOm3+Eht7PJJbtmwoJMJ7JdO0Wci4b2kqqbBulQWPmBliX\nzC5YXaz6LKV6GdlrLbaTILWa8arI50Z5ISc7WxjRXU9QL2GchJxM2CfpoA93\nk0QkIdqeb7KW6uhbOFmtsc2zCFzXG37vD/np1mtc12WLglv/8Bj683Peq2EB\naYSO\r\n=skUb\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"eb52a6dc9040b4a468db1c7677c7f6ddb668004d","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.13","@balena/jellyfish-logger":"^2.1.45","@balena/jellyfish-metrics":"^1.0.196","@balena/jellyfish-environment":"^4.1.10"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.1.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.132","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.78_1620348853172_0.7086455381509384","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.79-h32-log-version-on-conflict-ffa179fb0773fdf4dbcdce744021d1f3d01c788a":{"name":"@balena/jellyfish-core","version":"2.13.79-h32-log-version-on-conflict-ffa179fb0773fdf4dbcdce744021d1f3d01c788a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.79-h32-log-version-on-conflict-ffa179fb0773fdf4dbcdce744021d1f3d01c788a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"87e8c12835b7e6393c37254b6f637d29793bd339","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.79-h32-log-version-on-conflict-ffa179fb0773fdf4dbcdce744021d1f3d01c788a.tgz","fileCount":99,"integrity":"sha512-hyMIr+RduETNUtYDC2tvJVx8pDGgxt8fc07aAjUkiK6LtT+9Og5eXmzAuGD8Vqh95Oxq90VQy+lpC/x8fjcWUg==","signatures":[{"sig":"MEQCID4w6hf22nYZBHoJ+uYKBjsh6LNLzhwylblAJqjZqcO1AiB4UbkRiB+dwt1uNrTyjtAEyhoNr6JfqJRDROP/Jy+v3A==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":735334,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJglOQJCRA9TVsSAnZWagAAUnIP/0QR2fVs0wOXGn9VbEVM\nkUBfgtCvMWjSW52eVQ4VMAooVp881dO3qNUPJVvfWhBtPWGIqplWQv0mpi7n\nhv1n3TWk8cBFXkaQzTXQUMaUdwUTJOq6zrEi5In/ecSXW9F0x606waVnNU7I\nniHqHtkzH6IdMkfXOEY989NOJc5zaa6UQuz/pjDoNkxD/va3BSNGYgwoTpie\n1jGUowRcpErr5vDhaFExwLHbqZpksMDYCrJ+JMC1400XEStcnD11DimoiXf6\nVDAW0l56twNaJLlUwSRMxqmyW/HNtWjELzmYPSsLrNpq9tvpul06l7v+UMDL\nVTK5JF6R885fNzFSsWN/O4H0nTcRC/hf43IQUY3avhnGFh4DCY6AkPiDapbB\nU3S/Caca9vaBXbhy9jdiqlUhh18uECiKp2glwn0giruao0Bdf+a3YLno3O9Z\nw/hiznSo7+Pz3m6NDs4An7IBUPJLvr6/wqr3T2TZ/cc8IAGNbsHZMJkUV+lc\nOjlS7QBMCTP4n3Xj8zz6ott6rNxCdETR6Dej9mCfTi+Y/xGpLniIQUAAUUkj\ntqaaGoJgAsLAlXrhcYJBsCH8qEEZsoFUtQkO9XiDMBMiG9r+70kb0+3KE4hg\nVnCOY76/DjSRn/uL0mi028iGUqszzu899tKzusKH/d/dFurGY477a9YkmH7M\nxYtb\r\n=E6+l\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ffa179fb0773fdf4dbcdce744021d1f3d01c788a","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.13","@balena/jellyfish-logger":"^2.1.45","@balena/jellyfish-metrics":"^1.0.196","@balena/jellyfish-environment":"^4.1.10"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.1.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.132","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.79-h32-log-version-on-conflict-ffa179fb0773fdf4dbcdce744021d1f3d01c788a_1620370441107_0.7061661024469348","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.79":{"name":"@balena/jellyfish-core","version":"2.13.79","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.79","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a2c63bfde58c58992a8e0ed6ebce33616074f3aa","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.79.tgz","fileCount":99,"integrity":"sha512-DlKAOJZMy7/MjiqNk0S6VJ7qGKg4jebxMhrgtEnwuilpa7LOXar59yuT+fmRlqgkH1iJovATa7kJlsLfCa4gMA==","signatures":[{"sig":"MEQCIAIgAldC3qj/SVz2O9oaIHNIwCyqAp0bPWftowqXRpukAiAPHNxkWHB8yV3A2l+H7QuQ3txMRc2IgLRWbwdhO3W7dg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":735265,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJglOVeCRA9TVsSAnZWagAAgtAP/312VzW3WzoSm+pFPQ+2\nYrjMsXNGbLwxo7pZxe8VIDda7+K/0FW2nojlgtPuHQrAfMmcKIq3ciEJ50HR\nka1Rv/SI+at8KnLvY5BkfFGOdNlnNEZ4DtnOMoobklWLvTc9Evh5Kq8sVjAj\n0rxXsYrOHOare4NYgqHWJzgqjYntSsZroWyy05bhjr4Rzt65NStNB/cpIZII\nnD7f0a8+EcVedNu/juY2aGi6EQXcLDD/3HRa+EuntPdGnYh+ZS1SnCCKyNig\nd0eKiyLDfwW66ECNTdH9Ze4y83VmJNq7lmb3KtPHWwFjzKI+RGfkEIkfXvtN\nhYjZiMrnRvzJRoHCEgeizmEhvDYWw4v5e4Uki0R6aop61WAV+aF1TxEPUXao\n/kNyYnCt5kf0BnXhwbNJ9W1/87yzCp2b7CsMR7MVhKRdw31UOBAvJeiATSR4\n/sSp943SoGgtfuNon0y8EMPv2j3xHdhLKujlz6PbNTR4xNRHbJ8RH0pfuSHF\nbtL9PqO1NuEQuq7S5r6pIMOsu1tTCZn2AMoGo3EfAMJ+xa5ytMBDfCyxFFRl\nVrinRCy6nyn9r6QDz88Efb7LQWFJELOQTnuc9eyNvz78ISJrf7oRA1B1U9cr\nsxRqgtvSW8fbPPpDcKpPsiOkXzrRgb3SjiDWJevZW/n0p1+a6L2kPhQIzJ7B\neQkp\r\n=DDhY\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"cec79d6e3b0ce5746fb097cd1a9cb531bc54c71a","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.13","@balena/jellyfish-logger":"^2.1.45","@balena/jellyfish-metrics":"^1.0.196","@balena/jellyfish-environment":"^4.1.10"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.1.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.132","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.79_1620370781855_0.21066791618709546","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.80-renovate-non-major-618c6326302a9a4a9520d8418ec81513bd0ad983":{"name":"@balena/jellyfish-core","version":"2.13.80-renovate-non-major-618c6326302a9a4a9520d8418ec81513bd0ad983","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.80-renovate-non-major-618c6326302a9a4a9520d8418ec81513bd0ad983","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6853ff8fe6ed228bda51836d73b4f5c8ae78875c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.80-renovate-non-major-618c6326302a9a4a9520d8418ec81513bd0ad983.tgz","fileCount":99,"integrity":"sha512-VJ9YcohesMHd2YE12mTmi36vRTryMXOUOz0Y3lA88NofDlU9bOTLOTbeGP1eGhD7ZrcG21KyEvmBL1oghg4/2g==","signatures":[{"sig":"MEQCIF98ibgStMd6sn1Z4YMKluJJAT6ajENUtDR5CncJWmVUAiAuh3CYLAkuMRt28FQmzMzHbMoKb7th2ohGkEk21zF3Iw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":735676,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJglVOwCRA9TVsSAnZWagAAoGUP/2hT6FRt0X0gqdJh8pAv\noGQB/6DB/PsOsXLzVoum1InL8Jol0D8UKdyixEkIJcaXZUjTcwaGh3CJq2tW\n2m1/2VdzhbMGXJnohmfMlc0fxgQxZgSRaUiKd+9vKVZ6zoDMPiw1QG4ZuRrv\njlNrhssbEO9UhJMdh6tniXy1rATUBzvF+XZOa9cqk9cdtPPNWJRKH0opNWku\nppIBsj1pc/ofWxnJh9wm57odOPBG7MPWBYBpHMjr+eSznCVFKYXZ8yaFTD1w\nNpvpeXxQv0K0nzzObo0sHjqrp6I2vk1Y6fwqGWRUhVL5C+nxiDG3IHFn4oAM\nHmrt+MerOGk77jukdzqQzEebscirW06QfMANdTN3yx2lLd7JD/vdUfEnYfiQ\nyMnHpJtBSZv0ssskzFURCyqrCpAGmuEQOv5c+ub+h234d6v+lnZEJMkkiVHw\n4u0+koYDza4My9ymMF7r9vhZoUSKTaMbzqPC/gv2Pn3vbMWERLHM2+ZhYEfl\nEnQbEgZvGBbaj+KoYcmMuhBs64JQkbJgKmVpuncDlY8awFuDZdD0k4ZipSMU\nbsZT/X/BMHhLh/1DADY2RcLhqOv+RMEK0Fpg9hTSuYxtrDBAUQM31xVFK2fq\nkqLqFb/GRFft11mx7MTxAWjaJMSSq8qk8sgylNGWXyfI8X5IVr6FEHgcXoVB\n00jG\r\n=dupM\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"618c6326302a9a4a9520d8418ec81513bd0ad983","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.13","@balena/jellyfish-logger":"^2.1.47","@balena/jellyfish-metrics":"^1.0.197","@balena/jellyfish-environment":"^4.1.10"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^10.5.4","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.1.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.132","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.80-renovate-non-major-618c6326302a9a4a9520d8418ec81513bd0ad983_1620399023728_0.4147655592070296","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.80-renovate-lint-staged-11-x-0bdb8d9b16a6e68d04d24e477354c4126ef41fa9":{"name":"@balena/jellyfish-core","version":"2.13.80-renovate-lint-staged-11-x-0bdb8d9b16a6e68d04d24e477354c4126ef41fa9","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.80-renovate-lint-staged-11-x-0bdb8d9b16a6e68d04d24e477354c4126ef41fa9","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"eecb9f53a9be61d174ae31df7c51fcf24cbc2ea9","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.80-renovate-lint-staged-11-x-0bdb8d9b16a6e68d04d24e477354c4126ef41fa9.tgz","fileCount":99,"integrity":"sha512-UnQMSoZDi0AfEqejpPqc6RHmuuLibKX99bVMCbCZnSV7g7xqw+Yhj4siBKacfxFRPzfHQQ0gQ7y7ZReI5xXOuA==","signatures":[{"sig":"MEUCIQCTMAj0CbBkagFipi6AyytfMbayKbXAWYcWcZkePBUoCgIgcI/kgsPHTOwbnw37iqKO0atxAODTSpV2Y+8kKZK7Vyk=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":735723,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJglYLXCRA9TVsSAnZWagAApqEP/jtzXBcBd8MXBbY4x3hi\n2MGr948OOraREcfwtNArhnxREFPbnR7Mg/k+dzvNNdCwKGIL7L0Vbz//YBdq\nwZJ6wedznGJMKV9DojvFYLtTMUJYnwO7Jnt802WlzRrfql5ZBHsOGpdeB50W\n+scBeF3juf3aewNCsG/KJrp85hzMTGxgNmf/a5GBUwJdBgfAOZlCTwmuVqFk\nc1OuF4/xmTd8mM5G6NbU18wyJ+2IoQNOGz4gu0CHoX2tM7SX8U28K687ZSpR\nJ6gLvHdAvTrHDRFSStcMMECw3RuCXKizccS282yuykqwizquCd0Q7Es8X27o\nU8JLV15xSv8F9Xr6Au/DsvIi7W9CMxUmQUsjGa91cRY9xh5hhlgliZqzv+fP\nYLjmhF8xVN1swwXcwjhK7v4SNRa/PoGDWSiaWnipxpyVh+dGtmfncl4ZceQz\nQtoH77Nx9WzkE2En6/g1FxIEW/J/LeTWHdbcs0HRSfzm7DIQv94+5A+FPMQD\nsTQQT6irPneid3DQlU43V2jRC/OA5pYmZub63xk95k01JNXjC1TXOV2wpVLJ\njWExbnSXcoj4ekin5tznlAaWf417ANlHzll1zp/IIhDxHDyoVAKLe/Ud4rR0\nxWdVxx+pPIYy6Cv4CCpcPheWc8yfJSbGvWN1c2QX3beRpfNFxsLVS6w551YT\nh3FS\r\n=Z4/i\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0bdb8d9b16a6e68d04d24e477354c4126ef41fa9","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.13","@balena/jellyfish-logger":"^2.1.45","@balena/jellyfish-metrics":"^1.0.196","@balena/jellyfish-environment":"^4.1.10"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.1.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.132","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.80-renovate-lint-staged-11-x-0bdb8d9b16a6e68d04d24e477354c4126ef41fa9_1620411095168_0.9894153985615912","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.80":{"name":"@balena/jellyfish-core","version":"2.13.80","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.80","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e3b30b2811d18c683f8d5635c73634f8a3c8eb0b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.80.tgz","fileCount":99,"integrity":"sha512-j8a1T8PaNh+kleX5QOtM4nuYLjlCmxXIqb/5grRQITUjbSsdqs8wAAJESPXLtBsHpHOU4XdMljWIzfEkHJhJcg==","signatures":[{"sig":"MEQCIBRAS8A0DKdmIeDX7cO8MRz4puEuq+6dR+C0vqKlIoVtAiBPs3O3k1ZMifdQZ9J04dR4RNHMh1vzU5lNJJYP5DSgsg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":735656,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgldcZCRA9TVsSAnZWagAAWxIP/1W5nvY5VD4aWeChTjE3\nRwYCUhMuZcv2nzK9Y8+mClNPlVZsu2KMpVRrH5eojzj8q4NMabpb3f84aD/2\n6LGI94H+74BNLA/3c5RrkPkvBy3Hhix/L0VdT7egeAqDDlPqeNGWyZiKWBTb\nDIXxHTSWnFFkT4Z6j4WfKRICmCcP0u7aJtwornCF7uINcHWNoqMjb0vbkgE6\nrdwRRtTlovYX0DDuO32GrK8fNDTgFed7pvcZ/zdJLKQqMDnFybJZo7qzSU9L\n5Iu4wx17XsaKoBFiA9d7IU5f3FcHnV/5APeNl8qyVOru7JpA6/v9uEn3qPwn\nsAXtBYc1Dlm8YK15CTLoe2fI4my6RLexN/VqPbNc9TBUW66RteEVrskGE+1y\nVy9fhy7atJLz/biWkArDkuqmrl9AXrAQ3LUeuV0rrHwlLodma0nhUl//FJ5c\nyqo9mRK08G+VR/o7zCNY82BfEAYTOldq2Z5bnKT+NrdWYz24+aQlUYneJjjv\nMYi2EWfy8iFCdjVBZfYgAihCIOD+TdFL8uejzvYo5J4ssDAMfsKl1PqCCO4x\nPthOAbWrPc5Q1BxJzdJvHTu4oDghMmJ/zFCfpd7Njc17h9aXBVsloiKAqiQl\n9t9y9ViELoCmZ1mfIW0QTNcT5p/QayGnRm4X3uHEP+eRp9eHBVMqzO4WvTM2\nOZWf\r\n=3Qpp\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6e2887c768f6f77b25dba0c265839d55d4ae657c","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.13","@balena/jellyfish-logger":"^2.1.45","@balena/jellyfish-metrics":"^1.0.196","@balena/jellyfish-environment":"^4.1.10"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.1.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.132","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.80_1620432664939_0.0005301884561048631","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.81-renovate-non-major-5f36a6ff214ffba89a88de7241023b61d12f3451":{"name":"@balena/jellyfish-core","version":"2.13.81-renovate-non-major-5f36a6ff214ffba89a88de7241023b61d12f3451","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.81-renovate-non-major-5f36a6ff214ffba89a88de7241023b61d12f3451","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"68fd4d1f13892bb07fb3e8297ec4e06d8fd422bc","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.81-renovate-non-major-5f36a6ff214ffba89a88de7241023b61d12f3451.tgz","fileCount":99,"integrity":"sha512-Lc+sexvg2sRWk6ky4ijWq8flw0FVUGgm8L+iCcLAgdpJU7iXaKa0b9zkrmNUMsIK9G27p83PqdI0CGujXbBX1w==","signatures":[{"sig":"MEYCIQCN+YfL5exiT2kKNmJ7KQpZGaV9BcAnKcfNsvZDhIsWhgIhAOF01XzUnCYJUp7XmwJhBRPlbzdppdayfh0VCxdT6av5","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":736067,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgldllCRA9TVsSAnZWagAAp2YP/1tHj8fBgYcrMvHLvmdF\n0pCxjRvkq/vFySLEYTIVpEx7atJ3QvwS5SQUM4kt0lp5YXxpMOsay20hnC92\nEVrAYiMgmNcRVK/o7//PxG6GsezVt9HYGwVj9gTQv9CUfln2NMV7Ei5AOtNs\nK4GHoTyllTz42LBxGvCmFaF+4Emv3epJ9VvSXd3fuT2KL3BBJ3HC/E0fWb0W\nGAVFlCkD4c21TR+Ci+kJmYyOtyPwOIJVIrbAdCdXz+aHMKxDL+6Rb3w0oP2x\nQv2MLwSeGe5nycyBR0NEum1bk8GwNhzou4hpBXdemj1Mi5KvfsoGj1t43X1s\nVUZ0JgfbI7GsJqRTVeY67UcP1L/xKP3DtBh1IlRMh3mUxg3Hg80ccXjqpSDD\nTJXvAO6CUOrLAOtkubV2vdh5TwmV8jWW/iiPNxgI1OHnJPmnAl2XgvPmf8TZ\nINtEQ4z+DzDFf2FGdkPrdl+DYuVVTf1XmC2z0D4OzbBs7Ec4zz1bUaxKnTTf\nzqbtA+xsLhGmPST39e9zVbMQiZfl1Fq4WbVXN63YgCbBmtYo3Y+J6eZWyAIp\nVayS2kjpcKrZA6mWnJp/tMZDJZUUy8NutK5W/NsARNUeUKzqz5dIqrsB1ZQb\nliOxy1VgNekSFuK3rMcLE9c9v19MuIgl2/2w7zgou2fMJSrXW8aLo9WjpiDo\n2D2A\r\n=rZcB\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"5f36a6ff214ffba89a88de7241023b61d12f3451","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.14","@balena/jellyfish-logger":"^2.1.48","@balena/jellyfish-metrics":"^1.0.198","@balena/jellyfish-environment":"^4.1.11"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.1.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.132","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.81-renovate-non-major-5f36a6ff214ffba89a88de7241023b61d12f3451_1620433252814_0.039981014942564785","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.81-renovate-non-major-105c8a4d2bf3cd710ba114a272bba86284120b11":{"name":"@balena/jellyfish-core","version":"2.13.81-renovate-non-major-105c8a4d2bf3cd710ba114a272bba86284120b11","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.81-renovate-non-major-105c8a4d2bf3cd710ba114a272bba86284120b11","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f38cc17fdf67844ef6d1e33d547734d5770b90f1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.81-renovate-non-major-105c8a4d2bf3cd710ba114a272bba86284120b11.tgz","fileCount":99,"integrity":"sha512-A732HqkvY0NMeXmf3SXtmOlM/YsT5nfw6IrawbkTCbkTw53OxNJkPrVUoamLYfvYLgF9ec/GGTCYpGsPzq3Yxw==","signatures":[{"sig":"MEUCIB58XQQ//kGkikwbkUcZDO3/3S5cQoh+4eb4zVByZEOOAiEA7hHAXm47Ocpt6QRBllTpJbCpa68vFE0W0qfSeOxDVl0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":736067,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgld0aCRA9TVsSAnZWagAA/OgP/R3uUsyEQ5Ig46TCJt1V\nMYR512zV4T3hCHQeInlABzlG/5FV6oPqagxP8dv6spLrGwojAQP3L8EPQAov\nhdkx31eFTxrsAg3PdD//ShAq6pbwhx/7fFXP63fnEBhaZyiErDL+jxZu3RJn\n/Cgea7ShMv+RqDTZZLAFHnBrQmLNEB8cVOJMps2AaBLWPASuToUIjeJCEOWH\n3AC4LILOJmX6hQmJMYC8yv2TxEhp4eG9g3zyoD6cSZ1jNkuZGiZsGt7ltrG4\n7XINi+KACS6oB5Yed++UrBmP+KAyn0qgP0Slx84oEOPxKpErKV0ie1HG/JFs\nF3RHFfAQfUKNfwDWTQu8U2GCW5333heej31KsctTi7ZpMAcwMTpDxx1j/XpK\nFJgihV+OqT7I8lC8K0pj2Xfm8iVlYA9S9i2C1RugDaBXgHaiJ0cy16TN4DZp\nhASSCizeH1PrLjadv9H7OHHexfrTW/GO/SVUftSSXgSWVlVAIoJZOV+mIMOK\nFTx7x+jdTtGsKkPksGDLLMuUSN22XLJLhqm56PpId2RnOeIywpZ5/448JRpq\nc6A7Si68mLdaWQcBsQdHSa0JTH0T0Ckvkzqvs0MWHdSF9Ny4+dnpZWvf8vnj\nNFbavSRMYirZxGaTsiwW9mjUYQqpQOjZmmqucRECLnUlmZ5skThqtyuvRbRR\nNB/j\r\n=yebU\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"105c8a4d2bf3cd710ba114a272bba86284120b11","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.14","@balena/jellyfish-logger":"^2.1.48","@balena/jellyfish-metrics":"^1.0.198","@balena/jellyfish-environment":"^4.1.11"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.1.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.133","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.81-renovate-non-major-105c8a4d2bf3cd710ba114a272bba86284120b11_1620434201527_0.16253847746957728","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.81":{"name":"@balena/jellyfish-core","version":"2.13.81","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.81","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a99aa5d8839225b75c9dcc3d61c69d2597dfd089","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.81.tgz","fileCount":99,"integrity":"sha512-QnqKVIDpG5IvkmyVmOxCSMcV7BH4C7VrZvgZaqfgtMY8il05PMjF+aJRDQ5f0ToZ8KMMLfi8dpXdHyReEhUapw==","signatures":[{"sig":"MEUCIGi/4vHDhIND8UPL6MLGNDi8xwJ5RftAei+jbGUMdsZvAiEAoTkF+smzIO4D+e59FL6d0Y0Te7CMiOXorT6bvi3s2CQ=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":736007,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgleFcCRA9TVsSAnZWagAAkvcP/j78bFF+Eieo2RIrgpc5\ndYVrpmNEDzlncsSiHx9hPcaV7mMhN/mZi/zxq8djE/EAuFCT9cFsJQ+lXini\nY1fLytu7K/2Kzss0goDwjOHaa0afXANMuLm+uhLM8ucqNXGeY7wD3L5sfSb9\ngb5L560r1vTM/sfll7QcBpFpZXWUFUaUeiJlukYSJl3fqZ8vK3rR0EwYcvQj\nMmioJWlOmSEcEGsCE8uEmgEj8uKto0X0KskOtnSjeAUyVpyqEvqOOk7ahy6r\nnifNzYgPWZg16DMJWQaDUENYLM7xK86fgpAqKUer5hGz074sEaP+AllTcaTI\nYv8iYyVGpnDCUktIvoKXvbPAF5lWX7gUJJhvhgvv+4gVVWlLc2FOvDlN7kV8\nRhzllUI+keRksG+YqII3icSQaOrbvjjmI5zjQqN8plegps6ReLH+UPDuPj29\nM9+OBgy3hsT6E/B54yV9prk68uoQ0TLxIoR0FhaXSGp4SyW0mkOx2J/aKFzc\n5UOkX6ZaUcJ9KyS6ubRdbqY0rAbAvWgzORfF9Jd816OYxHRQbdMRgn9o2p6l\nMXKQF2OqEU8uDCCjtz7NICTGlENCR2EcxJk/npSwZBHy3UYDVyld9Y1Cfejp\n529J63G1Nkza9nUpnPHRfVjiM8gH5FAnIdVMlPPX2kKzpDkQp7Xc4SZN2HxU\nwdF/\r\n=CQ67\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"21035fabccf86d1bd17aa5a41fc1255b608cb29f","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.14","@balena/jellyfish-logger":"^2.1.48","@balena/jellyfish-metrics":"^1.0.198","@balena/jellyfish-environment":"^4.1.11"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.1.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.133","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.81_1620435291674_0.4938539244814957","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.82-renovate-non-major-84d59f2c0e34b0ab0bf67257cc8983987eff6c32":{"name":"@balena/jellyfish-core","version":"2.13.82-renovate-non-major-84d59f2c0e34b0ab0bf67257cc8983987eff6c32","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.82-renovate-non-major-84d59f2c0e34b0ab0bf67257cc8983987eff6c32","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"56c19e1bc037175ea855bb7f31ce1d3e5a019428","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.82-renovate-non-major-84d59f2c0e34b0ab0bf67257cc8983987eff6c32.tgz","fileCount":99,"integrity":"sha512-ho9l4fTyg0WYsAiXoxvh/u5SZMpSae6NomW2Z559q4tidw4wixdPlWFOJA0IlLe0+m7ltnqcsae7f3aKXEyykg==","signatures":[{"sig":"MEQCIAnmKyLirZLQbjKsbhPqVosnjZh/mE1X3ZQeg+sQsmL4AiAYJKZxRi/Ij3ZTaqMGcJja0w5KxlZR2zH7+R5BhcGa4Q==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":736492,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgleKTCRA9TVsSAnZWagAAVTsP/1KHqyrWap9bnygED6G1\npi0M6sE4nwwEjj8EA13gxPXd56fYfaBSXjC/Za9NOFQBUsGZNUaaMmY0ZTq0\n7Mg8TJ4rFxEKHqjwAhOycNSRVXnY/Bja5Yk5rC/XN0Yd4uqxbCijlcV5geyu\nVjTjUzPoxyXWRGzrduYoLlOpcyf0285I76WzGgDnEyj2J7R7A3LzDkPsfrbO\nnj8MCynA9ot4Lm9YpUV9IKHdNrWxujV0MM7JPRp6B0ykiXdUXHfAweEpCSjn\n5lDqiHaMyvOgD2pK47vw5aBcbi8DqgnJMBWhNkmPT5eHHIjTzgQsOWpOm0bc\noRIO2Yc2LdpLyLRvBr2dQ0v9LADwY5EfQXvww2y7N9i2JfdGgixSiuFRgrTI\nGa97AyS7NDPcxVP/e2ClteLJHrResJU9I6ZF1a477hCdu73T12BWzvyhvdJo\nS74cinxOd/+twWflRerZghwsg0dcfT6IM2+ALZ84ICR6kDQgA6EawciLHAOp\nmprzeqFgskh+eIVZHi+ufIq7tyxvY+cPhM6W5+XU0FBxGF7RkPq2I+pzpwQQ\neR2410hI008sx2DINecrRjfh7hxZWdPR/6hFeNOJj4SFYGSmLqn1gAoitpqG\nGqipCl6ELf1q8uD8ztI+MhUU8rZcqANKkU76CST25ZHjii7zo53aMszEPBbh\nEENO\r\n=WZTn\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"84d59f2c0e34b0ab0bf67257cc8983987eff6c32","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.14","@balena/jellyfish-logger":"^2.1.49","@balena/jellyfish-metrics":"^1.0.198","@balena/jellyfish-environment":"^4.1.11"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.1.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.133","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.82-renovate-non-major-84d59f2c0e34b0ab0bf67257cc8983987eff6c32_1620435602815_0.9169044334383993","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.82":{"name":"@balena/jellyfish-core","version":"2.13.82","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.82","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5bc6ba7ceebcb73a8f4ed658b6708214e0e6cc13","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.82.tgz","fileCount":99,"integrity":"sha512-A3UjqAPJMBbBLs+0jgcX48CttN4y3Yu7h7hIw3e4nNZZopPPisXjWZMsYsdxscchTvPWnZQnGpSIlrdNWok2Rg==","signatures":[{"sig":"MEYCIQDlHMVM8o9TDKsFe9XVAAjP62YERHg5rmjvvzJSJaB+7QIhAPJvExsQ/Szp7IMflUYSiPPNL5VbUuKn3c0CrTGy76hA","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":736432,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgleVnCRA9TVsSAnZWagAAIY0QAJxA9H3pW6kyJPH/mJs6\nulumDXrJQokfLKWjN+2YrB9BuDvUwdz6YgyYFdMYowQHhsu/Ez7c6KSy6yrD\n9sasYVf24yjszcE9hdCKQRxP0+B7dWYJaOLpZ5Ue+t0WkgldZ/YiOfZXJ9o5\nupezytdvfuxeVDQmsZplcCX8DLsiMz6aMeNW6C1u+HphXg0JA63cUvTGTXtD\ngyxtDr2dnGtSsDVgiuhxvUKk64UGjKzk9xOkmn1gBxXWsy5GoFZcIZEY0aTr\nW4xa/rlGQZiWjiUa9uuyOz7nFBlqP5iGLFknGrnBOev+8d6RZUumuOkAJ2x1\nVDPEdPbuVoIVRc2/zdXq9oTN/6Jo/1QJdh/VkCjkZy7uecfvOkZQnEcUJ1fz\nRVcSwqhCSIpLiTBGPi1wqXdYphzjZBbQxsEz1EuY/sRCIcP8bgzTFgF9NsrG\nAXk6cGS4KZWd1UXB59xtos+exlNKp6Ju0jd5nCvP5UtUELG7nZXgIVaLqEBu\nSh8sG6hZKqn/lujeozTwiDAcEHQwDrEMpb/LTLyTjn18DgOQAagHCdSRfM0g\n9YvHdYwiSaSskTQYj9kZFPcKaV/w+MDrZOHwOZqd70DMsslbU4WyahQhm3Nn\nyfTogdAV+hyOHYvF6RE3PQzDk2CMvcj85p+5+2Pn73aab91tQnOjOO0zcf8z\nz4ZO\r\n=IKJC\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d76ac0a9219d36c4c0bfb0f26d0f9ae40506486a","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.14","@balena/jellyfish-logger":"^2.1.49","@balena/jellyfish-metrics":"^1.0.198","@balena/jellyfish-environment":"^4.1.11"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.1.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.133","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.82_1620436327265_0.38579395112113746","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.83-renovate-non-major-8976eea745a4bc98d8dd953eea6cc8ff1913c3da":{"name":"@balena/jellyfish-core","version":"2.13.83-renovate-non-major-8976eea745a4bc98d8dd953eea6cc8ff1913c3da","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.83-renovate-non-major-8976eea745a4bc98d8dd953eea6cc8ff1913c3da","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"688fb0a52a649b3c5d3c5698c0d9f6db6703bdd3","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.83-renovate-non-major-8976eea745a4bc98d8dd953eea6cc8ff1913c3da.tgz","fileCount":99,"integrity":"sha512-oDIrKQlOYxkCcGVYJ/VBDM8BuegZVvAs+THQnUmH8jSlcD3PX2MlHRbpXAYeLT/zwDv8HOXw6/P5HVkWbYpbDw==","signatures":[{"sig":"MEUCIQCO46sski4WsqJC3YD4XscYNkQkzpVOuwdR4sLYl6rkegIge22138mTBw0H3tskyUM1bWMdjS+VZMq83N9czRPaa28=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":736921,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgleZcCRA9TVsSAnZWagAA1qQQAKTgF1VJM9pN6+BYHNsg\n9YYWGC5vAmeH9XQWkD8pBQKrldvTlzcJqF2SHgyQ0tI3bqCq7yryGdxfg+k6\njKHIRmd9dLZQqxTWZ6r9tsMbveC6b1MQs8uuT7V8i0Ov0CozbaEKk0InWxd9\nJ5R81D3QtdU70LBPXIzRfLQzCy7IbNdblvZAxNF3rZSFMSPEVpYr/fDppJ8I\nIJ+B29OAFJjfMK2/8dM3D93zPMjsNcghyyYEOPCIMnxh9HiRzYJKXbbbNm3b\nQ2QN9UwT3qEmExnzOv1GyPCOA6tszABupMAxBG3D9F7Gpdws5gTh63Ku1jt4\nRFmjDLpJBKx2G8Apnwz/VCYZF4nrnI2fqKy8wKkKZdH5FTP7oxLr628S7lkR\nHzb/k3TbNeLldrVE6w5EBpJk3XGVZLVxPftc7UzLBYuSFJffh4wvikb1z65N\ndG4xWyNgv7U0f1MLxMwS+/mMOQ5wxegXSTo5rQu5rukqPjB0VvW5bbBTsbMs\n266EoXUZm6NJ3LyAZItc+NBbfN2/99VV1vSLXHr7Yh/2tIU5rsV56S5N2VTP\nFlz2GXkXy0M/XrRn6x7jf0maMLPKIUdjycHiFDVbpKCrMuhbwsoJqxEueufJ\nz5ZqHXpYY/kl9u6fLNlkMfOCZ2GsYucVxWFGA0tTYZM9XESgFKmujJcdSs0t\n0IP7\r\n=H2x2\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8976eea745a4bc98d8dd953eea6cc8ff1913c3da","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.14","@balena/jellyfish-logger":"^2.1.49","@balena/jellyfish-metrics":"^1.0.199","@balena/jellyfish-environment":"^4.1.11"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.1.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.133","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.83-renovate-non-major-8976eea745a4bc98d8dd953eea6cc8ff1913c3da_1620436572326_0.6274827057169288","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.83":{"name":"@balena/jellyfish-core","version":"2.13.83","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.83","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1e2baa3f154ace93c294945e62aea80ed83e0c0a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.83.tgz","fileCount":99,"integrity":"sha512-I2MMBPYdk5/K5MZBZnY3SnKvk5S/3eTeqrSn3qVipTgrqgj33TdgfDHL5JTM+8+vOJ9R/B2RuaIiwc0NOUg0cQ==","signatures":[{"sig":"MEYCIQDH789Kr1J519ShdiNk9eaGNSFFGLPmiGPT92wI+wS5LgIhAOrXlYaPSMvcIuaQzpmDYB3fS5cvQZCCZC9Gr0Ns5mxJ","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":736861,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJglejUCRA9TVsSAnZWagAA3RIQAII5NlDF7cMq4WRlJ+H6\nZhDHiWqsW8KAa8XmTXMiBhyIBfm/HKTriKVjmPQgWc//M4kCglltpqgjn6+F\n6KPfpBVZdkvK+P3+qVGS1Wk89PZ1yXM0RWdjxygLONxbdcjBPH76yj9/a5No\nTqogupsOJ/Z4j9wpAqF6blaLgKhevABRiqBWNAskPwQ+e1G0vxgjno9Qii6C\nhshu/8fUS7udtK+FVzLO4rqVQb0V55hO/sf3syE4Jjf7NttYmo6JLrN1CAXM\n1QqGx0/NA8QHWRtE8UrwEEK+EUMaZzEA5dQdVe069zJD6HXOmfpSCBujy0xt\nY8cRFCkhUbxzMBGXSvJCANaRqDUPN+5BmgUwtnnnROa1kcOz457ARJQXfQNq\nFZwVqkFbEj0uRAx9QF45BGKBEDMrJDf+GiljNdFUkb2SQWZWwbrn5a+mMllv\nIVpInJ7xUT6wvLZiMln8HBkynQ7FtmPydOLSXZ3IOG1nTKTlJQRtEBnpvild\nVoVgM0h62vUOBlZ9Ecn49mdNdHxpbKWsQAt+BgOrPT1yYAP28yfjxgw8ZDtD\nJ9wz8y3rEXawF3o1L9GAESp0Dg3BxBFEV8kIJyURgd0GILEuAwk0qpwwiGkT\nb/vrvGPMUf51TOvoCNeGsOXOCmiU/td9chZ4xcYfYbPiVoBzY7jYfS5gD9Wp\nl2lG\r\n=5TDl\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"07d818f1f41e4e0835c6cc458cc286b0ac4e6aeb","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.14","@balena/jellyfish-logger":"^2.1.49","@balena/jellyfish-metrics":"^1.0.199","@balena/jellyfish-environment":"^4.1.11"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.25.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.1.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.133","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.83_1620437203706_0.6784511629753487","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.84-renovate-non-major-50e3c4129177a37e767a266387ffd93785f763cb":{"name":"@balena/jellyfish-core","version":"2.13.84-renovate-non-major-50e3c4129177a37e767a266387ffd93785f763cb","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.84-renovate-non-major-50e3c4129177a37e767a266387ffd93785f763cb","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b9fa9e008c8075edeeac03044df75c170e1780b6","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.84-renovate-non-major-50e3c4129177a37e767a266387ffd93785f763cb.tgz","fileCount":99,"integrity":"sha512-xJfvlmX3KNXaNw40cFmHOetb/dh2ziR9jYhH6Ihtq2EwuFCAVVFwnTig24zovLle53S3FndFtuwOShZwU6hgZw==","signatures":[{"sig":"MEYCIQCdVookr6y8wOdzj1prLc30unwdRWGb9Vc2bLgvrDVh8AIhAJys2KXq4lK67bmPffMNAQnagQjVL0HTs6jF4wt01M4C","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":737272,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJglgVwCRA9TVsSAnZWagAAWYwP+wYPPifU0MVbC67s82Wd\nqNNeQ4a3PbWpddqYTk3e4R8YlC9BpZtllKoemEd29j5MzEPdIBEpzAq0bDRp\nusULrVjIfCURieQCoeGG2pkFML8NANulyr9qcAxAwuTdUGbKL6S8kELvrW0i\nN+VlHg/0DOcZIfRzhjF5t6w3pflqh4zCVzZJE0mK/9RV8PAO5b+D5urfDdSi\ncrNc9cD8AJaH07JOsZKnrR1RD+7PDgXoRu7sdZcfKXoIK5JFd29Age9FYdX8\nkqOiB2fvV5rCL5uBwxX7/aZAAp2iZ1v/BRgtInJQwWs06Sn4WyxyCa+Ir1GR\nI912kq80somlGLVqHdavwLNhh5oD1504C6jwZZNIleNa187jwifu2YJAebrt\nq8Uo0fhT8UU2uacUExgSdxrmT8uhu0vCxB35RQUyc6YQ4zO16SH/phhiTqFM\nTWZGcyIo9opSo5iSuePFkIyKGWXo3rYhqixqoMrtbc0BA7ieH0j4iBwP5n4s\nc+HJZevujJevBe1Z9yDT0y3V2xLCC15qH/HSaBLlB9Nzl3N7LUWUy+sT9DgT\n2yCxI/4A2CzlFysW1LUYx1F0t3D88HaKuNHYw/II7mesSv+G5xIYn7E+YGqV\nGijMwngPdrS1cbkvtPHenpqovcMA1JZfgtHi+7CWaX6vyEqTp4JGso3wuzt2\n4qTQ\r\n=/+0L\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"50e3c4129177a37e767a266387ffd93785f763cb","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.15","@balena/jellyfish-logger":"^2.1.49","@balena/jellyfish-metrics":"^1.0.199","@balena/jellyfish-environment":"^4.1.11"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.1.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.133","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.84-renovate-non-major-50e3c4129177a37e767a266387ffd93785f763cb_1620444528030_0.005914111749746098","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.84-renovate-non-major-c99cced5277e55fbb7f7f8b63f83503821a48f44":{"name":"@balena/jellyfish-core","version":"2.13.84-renovate-non-major-c99cced5277e55fbb7f7f8b63f83503821a48f44","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.84-renovate-non-major-c99cced5277e55fbb7f7f8b63f83503821a48f44","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"cb139013c0da176f8a623e6ad18e68263a02aeb4","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.84-renovate-non-major-c99cced5277e55fbb7f7f8b63f83503821a48f44.tgz","fileCount":99,"integrity":"sha512-RTCvNPHYhC0kyQzJmiiwhcCbONbSysymCMFZRneJV+mHVU8oEkP8F0Lcs9WL1R75Wmp8o3/0kQQ9UPibV3g7VQ==","signatures":[{"sig":"MEYCIQC5ozwaBpOtiNeFwY0sh5VlxixVhqRF1lor3cFvjtMp6wIhALTUTvdFlTj4djn8v/+ihBQsFkGm2A9hDEZlCVvAfGjb","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":737272,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgliC+CRA9TVsSAnZWagAA1i8P+wSx1PrQrJaYzJNKYno8\ncuF0tH5OuB0B0wgCQrDUGGpmXs0IVTPuzbU7LAKTc/ngCzoUG6Hoqm1unMZv\nvgGgvncvBRWsYbmpitDLQrdXktR4hLVOaJrVj0wydsNQqdI9lJWEYAgdFZEC\n3vduOp9PF8JnL+JuIf2heuCZniz4WOCZSYF5t87ZSs7uz9aD3p8YwCklP6hl\ncOvuljXMJa1LziKD0EqwcgfCYV0XFy4SF8Jppo5k2Nd79HlrQY6jYvZSZhH2\n7jQysWYoDDOkyn1o3B+gP1GItPGv2BbVB7o6XWhNqvcVzqA5L1sb/26odi/m\npG8XEoY2Hh9FJ3BpEJVBDJwKhvX3Do9o3UWgotS3rwN7V8ebgtpQYpOiSvQT\nyCeOwTEIaNjBaiHVyfDqIfa+xANEB0dtG6xLUhMBYfCnjwJB/3srt2cvyuA3\nUhVNm4Jd0PlNwuUfgE290oP9APFWCCi5xTmKRzkrf5GANJU3tdmIsEqN153g\nJt6JSCXhsArlee2MFRW1WBPVVfFS9z0UO4nzYiH+s7lN8ltck6O0fTFmUILs\nTALmQysYV77RGssMo3C/qsDOxCrKvN6IOlR/qg2lhxt7o5oXqYgaVZyfNXPb\nnU5hNYVMj8gITLWSis6XYXxocVDnX7lM7VRVuScZitlM0rjUD/0ag/UeGcgJ\nZzKz\r\n=n0B8\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c99cced5277e55fbb7f7f8b63f83503821a48f44","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.15","@balena/jellyfish-logger":"^2.1.50","@balena/jellyfish-metrics":"^1.0.200","@balena/jellyfish-environment":"^4.1.11"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.1.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.134","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.84-renovate-non-major-c99cced5277e55fbb7f7f8b63f83503821a48f44_1620451518487_0.6253597492347291","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.84":{"name":"@balena/jellyfish-core","version":"2.13.84","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.84","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"bb93b15b60415cf849962df6864546911fc2a44d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.84.tgz","fileCount":99,"integrity":"sha512-UkdwxqsOqiwFFTaa+sqvblfP9aTDQ2yppSESgvW/pcOal8xHypZkjMIrXhTPBcQRYFzKTHC1SnY8ZbavNvvw+A==","signatures":[{"sig":"MEQCIB3czISbyj9SnxTSp9vKTzEdn7UQWfR9FP/q32xoTHyGAiAlEitb1Qz8GT8ECkMnOBHCZoCa48FSyoz5HdDWpyZg6w==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":737212,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgliI+CRA9TVsSAnZWagAAeFcP/2riIEfzuiUuqq4X3EpN\nMp1qop1pnkoO5I/50GcjhGul3ijZl6pbmMpxLIOk6HOVFDVIOmY2VXjXD+ZU\nxdo2zSeXHN7MFfywcxWAuzbP0DfHZeFmn5kKeAt02rs3YWXjbHKxY7fFP3VH\nGSFxmt64ifdSv/NEpX0javqGAZWVNHRlP8Edx0Wn4GTS3Be99vTal5ss/qtG\n3PWo6lQgrc9OmwTB1451BkvrY6udVaU3/1FUSn8rKxTWAeNbrslET3YWQjS2\nuHanTYtRw6m9LB1MK0dyysaCpPTMUPDDCQU37yEcLXQq7QCMN8jSZbEi0DOX\nExECNncq1OTzjxnc+I2FQmJS5GqNvSuWp9wCMBqtfbFxBDRFB+oiSeyRPLbe\nm3HQEXYp37wlwCVyrbeTbhY31ME/VXswE6r+mf489jnd/KeWN0Gw66CvjL4D\naGQbjayU+tDmPxnqiaD3Xa9ayiBF2+R7esHcC4WmFx+L/D6fQauuHUTDUTTc\ngiUIQVka/58IOFX0jZUxMxhlSy0KExq8xqmU9v237z9k2wqjy32p9nqIRMQK\nfxWVAyAxhzZBA2LAAogL6MSqy4H9hiT4zGIG3fWcZITxhYANM3S+Rz1uvZjv\nGq6TeAJ+H6vY/3MMt8qnFbuefLbT0xRfhrEVr5tQ1phph19Q7qqwsnVUEGg1\nS27R\r\n=X6B6\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"16f199caa2dbf24d6f9b2ac412fb6b17221e34fd","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.15","@balena/jellyfish-logger":"^2.1.50","@balena/jellyfish-metrics":"^1.0.200","@balena/jellyfish-environment":"^4.1.11"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.1.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.134","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.84_1620451901839_0.8789472123386457","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.85-renovate-non-major-42bed61d9bfab1e50677c2cf65c29cfab41d0952":{"name":"@balena/jellyfish-core","version":"2.13.85-renovate-non-major-42bed61d9bfab1e50677c2cf65c29cfab41d0952","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.85-renovate-non-major-42bed61d9bfab1e50677c2cf65c29cfab41d0952","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e2137a10a8cd901cb9c09f615fe3effbe211332f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.85-renovate-non-major-42bed61d9bfab1e50677c2cf65c29cfab41d0952.tgz","fileCount":99,"integrity":"sha512-qYwVJc5iMwM7IGxAvqXJBiP2Ct/JJq4FZm5OQ0lDv3aVR7wOJI1KJ06wGgzfRvicm3cXIxFzP10bRNYTwE8wHg==","signatures":[{"sig":"MEUCIQD/9zeDdMKwblIVbFBWD2QU3Pkl99Sqrh3/alLRPlWcDgIgbCtgfwjs8qSHUUtL6mSjD1Otm5K6rLARdhNeIp0cASc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":737623,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJglkkQCRA9TVsSAnZWagAAqgMP/imEeM2Nssn/mCki22GE\nKn8l01Mg5D/2sMMe/eZvx/90yVyF+EaDwdOJgZWo7+fbcKL7OWlzWMvhHcC2\n5MyYbp9kf9H05EpY0ocVtzUY7OozatkJydzEDAzGsGrgvXX5i5VK4EzF6a/Y\nOb9jL4JJv8FkRCuAhrRyr7xhpJlg0K5kd/3UBqB/yPYcOAJJy/GY8YpQ87ft\n8lTGnqNQtqCovvaawpy0R7o2vfiJ8ytfHbZ4GsBfVTqxNb6ev4Zenkhj5M2s\nZmfaqyjFESbniwr2mgWIxEiggKFIrXGCTBqn8pDHrBE2eMxJ5tZEtn4alLFj\n3xc4l3lr7aN4aaObRuwkAYpxK7laKOPHTAWxq+NDa2wAflL4w+niN4iFu7dd\n0o5dXY7hlcIYJHDKuudRiLObTqbip/GDRKmp5C2r2FoUTzHb758OGYjJbjTY\npXnrM8LPtfbGxLrlwQ2pUUQ+o+tufFG0Tfyw9xnCwgOO3xUi6y+sJnxt0g6o\njdRku5rHjb6EPLBf1yumTh8Ig+Ppz4dFiMRlwPsTYLvbcMeu6rHr7Ws28OxY\nuSnI2KJc+h93UD5mZEtPvcys1wf8TqxVCju2NMmSS+H7ZcZOczirxmDM/5aB\ncDWA19rwLFhwJAsOFuzD6fte8V5qp4Q12rUBVtVfUUUKPkkEaPbPygOx7M19\n7W9V\r\n=tko4\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"42bed61d9bfab1e50677c2cf65c29cfab41d0952","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.15","@balena/jellyfish-logger":"^2.1.53","@balena/jellyfish-metrics":"^1.0.201","@balena/jellyfish-environment":"^4.1.12"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.1.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.134","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.85-renovate-non-major-42bed61d9bfab1e50677c2cf65c29cfab41d0952_1620461840187_0.9016502168517009","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.85":{"name":"@balena/jellyfish-core","version":"2.13.85","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.85","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"161beaa200fc0f71ef9f097c5025d9134893a615","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.85.tgz","fileCount":99,"integrity":"sha512-/PyTsChHe7t2ZQMvZgC73+QVDQP7zHh3sCcG8OQLK4oaO/WAJcxX7TNoYyxw53hsVpuR9MzZwvqynm4Yrvdbuw==","signatures":[{"sig":"MEUCIQDWWXA8kHyjZ/dWaJUu3uM/eGg24mxoj71A8yJFQboZpgIgdkaHe5gQcN5D57CCfS+5hmQZHL070YVonRk2d4o6VRE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":737563,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJglkouCRA9TVsSAnZWagAA4H4QAInycG1Y+O76yVhR3B6s\n8RUCt0zKSsD2cOPOwq75w3iKe/nHlvO8l/Kfrx6DJBne1Nr0ptQD7H5Ffnrw\nbbt/QNREn7kRv4EtytOo4N5ig/YvNmoVBrUq9eFY8LaWnU6ZBN5qVWHch8eu\nPsMS/rl/W2UiXQEv9397PSR6f6Asjtp8GxGLt+XktLqKZLGIS93Yf20BJqEK\nXuGz8oZU13xokn0o4kX9HmEFd/sSMAsj2oee160hFPquLsutKPAqMOVLQc3o\nUfwVKNVTSshrBj02Wo5blcLRcrDUyoyXKSSv9dgtxULbeECDlLZ4cHumKtep\n1Ic1Sjwsb4zBEKUxnHH3vIAVlCIBSNZowRXHVx59CNruFv3xJSwRtI4SjMqt\nD0IDV4UO95zF0HspLj84sDh6OLZhOQ9h4Rp2NK0tRDdo3hmXf4JBZUJXn3CZ\n4liRZ/PDgRvaQxKCWhmQu7dP0oKVTe7R0Z4FpUr+NAex8STnKtU6EwHOjVj/\n1NRGHQVd2wgqCY2gMhweZlMZSy5FkI23b9LzZwcEJf01KPOY9WjrhfiZ16ng\nHrL7PGfv+vZjZ0TfsgDvSpGZ4Sb0w35DTdOU7pW5iNP++6xXSyk6xBz2A2Gt\nR1O0UusYqd053JPTvN4iIn4j6pWua1c3ChqoNFdzUX1+q2pJq9ewQJSPVbl1\n+joP\r\n=rlxy\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f5c7dfe8d2f4a94e87a3c29178cd44c031279487","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.15","@balena/jellyfish-logger":"^2.1.53","@balena/jellyfish-metrics":"^1.0.201","@balena/jellyfish-environment":"^4.1.12"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.1.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.134","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.85_1620462126158_0.8280567918453088","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.86-renovate-non-major-0c57d02fd3b450e035f463fc827ac308f070230a":{"name":"@balena/jellyfish-core","version":"2.13.86-renovate-non-major-0c57d02fd3b450e035f463fc827ac308f070230a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.86-renovate-non-major-0c57d02fd3b450e035f463fc827ac308f070230a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"32be6f175dec55c3ae3a6321121447a85ab9876f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.86-renovate-non-major-0c57d02fd3b450e035f463fc827ac308f070230a.tgz","fileCount":99,"integrity":"sha512-0K+qFcTgjzkcnZim36jvhJKs1lfDASo+AGU7UxUYN20TN0mKtHp64nWnR18O9AnWYot3fIPlZDbWBAXXawPXlg==","signatures":[{"sig":"MEUCIE9L5Ljfum8GsFtWg/5GZjWQVEtlu8zgnONT2+YAOoo9AiEA5lvqfg06R4zOsP0nS9uBRaL0loV3b6edRsaqbvi6STk=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":738052,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgllnNCRA9TVsSAnZWagAATmQP/RMznRG4OOlhe3uKEEcs\nsEQUq9A3IPnhsIhkU8sI3jiiBeA/NSFoy/GgBlcuXQacTikplZaJVWP0PGHt\n5L0kWswpKUv0P/tSDy4MGdOTG6Jw70bBE4V8f7cRLnM2sE2tgcYwRBFDMgTv\nchqtx8QzihEmHMwh7wUO5uI/QgY7G9UoNN7uwQ0sufh+2eaCL3iekUh8ghHf\nB7Cl6MjftkHD5wXrP5Az2XbmvzWWp61ZVizHENceJ/ZIaS5MkCX/RsqllsWv\nadMIdwkk558GgmCxV93b1BPpf1YCY/LoEvPMVd6dOgO4cX+NAxJ1K7J1dYyD\ndlrI/2jhcpTUhGOOy4ALldEX6hGIJqnXhgYh7AhJ1WONoA58BRqyEkhnJtX7\nGA7y5xB0h8J2GfHMuomyjanU+AnM2/ruiCwUhLyhgIQTKJWNnlZuzS4n6QYO\nr91eRqucHg3jSZ8wm3oL8Lmqz6E054k4AgDVTN94VJPU6YaKpQlO1V9AoSGP\nRWr5lLHwxmNtw6h0voYeYzZ96VQD9sdCRAd4VeG1e56fpAgv+l24y2SJTafr\nl5Us9rxzX1gaMAHClmPd3+LFeaRaFtjD6IZBMJo2WPQY/k41qHgu/2PtktHU\nmrVaEH+HLk73bKPNELk1wjtCJTSJmeW05G5ifx5qEDfj1YHSjCglkZtYCP6S\nCzsh\r\n=Rjli\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0c57d02fd3b450e035f463fc827ac308f070230a","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.15","@balena/jellyfish-logger":"^2.1.53","@balena/jellyfish-metrics":"^1.0.202","@balena/jellyfish-environment":"^4.1.12"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.1.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.134","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.86-renovate-non-major-0c57d02fd3b450e035f463fc827ac308f070230a_1620466124459_0.29649272521014325","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.86":{"name":"@balena/jellyfish-core","version":"2.13.86","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.86","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"06d589388720cf3d87f7c47d9369de9e2f752804","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.86.tgz","fileCount":99,"integrity":"sha512-Ucd6dEb9b32KKl/JunYMRkswU4mbRz5I9+dbQxsjskOo03WEnKhG6/EDwUvQxK1IVRZeyPdDe0ZCP2dIUNfdLQ==","signatures":[{"sig":"MEUCIHbFy6zq0fQbVYtV53Vop3SBuRPojdQY0c5/A6eDkOUmAiEAwYlmMXd27BtB0JYjjcvQifrGT6abNMxij3bWX1hL8B8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":737992,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgllt2CRA9TVsSAnZWagAA1CAP/2rDlNExRhcWMiErVeca\nz6EjyD4xfSubgQIjX54uFQajrKoqbAxOrhDDUz+lHaBZtL25zoMRhBdYVoyH\nGdwWK/dhx3gGXoAgBHz4Zs0Pkqh+zsGsARJ+ifVsnLAMSHNvI59xchd9nsvs\nbwQPkFQoYc+xoOlhNgXJtfRF6IlsNGTonlwpsvvc+6G8W/zV8V9uP1WHAoPc\ncoDcrvlU651xkJGRquaSwwfsjqsX6gnt4v/L2mAi3e3OIpEqMYfsytykfWiY\n6YsAehqJqg+JuzceXgfhnc0qD34ccOuomkT+gKa3DJ5VWWXoWEHumxrmRT7f\nS88XlMReVfOXcG/g+YoxrQXNXSzuQIsd5pYY12AFoGjgtqkZ7qR6Z221ZlZf\nZCtVVme27WX2wv5vNTBfQxeMqOICbZIB/vIfir17gFi7w3OwMxUnTUKIpj4i\nN8vYLaBMo2cS2q/k8qBv7dm4NLi8vRRgumGnQXZZnLysVGQsNi2ArcMQMMGj\nexoKWlB7iu+tAFFayuw7EFQwypcYcvM3NwIJo6sxMgnKLX5YoCpb2GY11Zhj\nqkic6tlSdYx9SbYNKYJWcVlWPWuf3UdwrteSwP/xvJeLUiz/C+bfFbwAF/CJ\nlAzglGNTXWTkltfY2CpzkyOhypHi0/fMPvS5miRNTuBcAICOvA6iJ5LUkR/m\nnPeY\r\n=XaDf\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"62dbe7ff6057a6c4ff3bc41e60fc4d24d69cc03f","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.15","@balena/jellyfish-logger":"^2.1.53","@balena/jellyfish-metrics":"^1.0.202","@balena/jellyfish-environment":"^4.1.12"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.1.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.134","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.86_1620466550020_0.9664539632671261","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.87-renovate-non-major-0c6b28b84d0bc4dfe6692cdfb1e0199d24381a37":{"name":"@balena/jellyfish-core","version":"2.13.87-renovate-non-major-0c6b28b84d0bc4dfe6692cdfb1e0199d24381a37","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.87-renovate-non-major-0c6b28b84d0bc4dfe6692cdfb1e0199d24381a37","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"59715e3d1b504a6667828f42b174eddf8ffab45f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.87-renovate-non-major-0c6b28b84d0bc4dfe6692cdfb1e0199d24381a37.tgz","fileCount":99,"integrity":"sha512-WgO3yXSgnWKHecTkvzXzOeM8prkcNTbf9C5b1a5tzWsL0mdiKGdUg40Fl5h1x/apY1NCNhL/PtTeLhqMqijFkQ==","signatures":[{"sig":"MEQCIBHCXIGuJ9/thkds5nxT+W3qCRNUKc6cRivpzkMBDuXEAiBjUFwSrteM13Ol+XY7srQ3TeRmmKclEVDDHQ+lHmLbXQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":738475,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgly5QCRA9TVsSAnZWagAAl20P/3jibP1nJ6IsMFYWqn3a\nIQztbnL21JaIGGHUf8RUFLOYgnHSJjotj2nMh2cEfqTNifNB8YOdbvSMvXz+\nAcTa+Jvd79KpMrzSAMJC16ynYYFIOiJcqbmw3iNCaET2MYV6Iuh+nK0RZnGK\nZNuTR9ZwoJKLlOdvLnQts4KfqxDVtuv3AvPtId8/ez+dAWGltsHKOB1ldRv2\nvMKJLqxRLJRjp/98+irR33CeTTWvjcuwtgZaJ7dH4VeY74saCCr+Mdor1bJ3\nEgyhNjWeQB79/jJM3kbdPU7CStfLVp5on9llpfcj1mvcB9ElLT246E7U4B/j\nuWRqTVcaVenEKbU678RYwAue23k90j1VSOfGbRKEvnJ8lnRB8j8qVvxlPfol\nsilDHsv4ptlL5E0e1BgC/xZvyUWvSFopBuG/Jc00ikzFkovH0ImlOggA535m\neGYG9YaYsujq7JqRBN3nmYQMYYTQHURDQxIdkI3E8bN5M+pFR8vN8CuCl4+1\nHUdk1BYUAiUxqbLA6vXTE5+A//ZHgr9qLNInzm0CWyMfPQiujowJYPbAXTnb\nBx8L6iUpKXNvSIM+1i2ULUPAMGAYTinbetdOnXHOOpmY/AIhOZgX7xTf+GlA\nGMoreH/M85eFVVNyVWIW+38h8j0jRR6tHBtuwepXkqBxNIOa4AOESmlspjdK\nYA7H\r\n=WK1+\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0c6b28b84d0bc4dfe6692cdfb1e0199d24381a37","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.15","@balena/jellyfish-logger":"^2.1.53","@balena/jellyfish-metrics":"^1.0.202","@balena/jellyfish-environment":"^4.1.12"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.1.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.135","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.87-renovate-non-major-0c6b28b84d0bc4dfe6692cdfb1e0199d24381a37_1620520528136_0.8776618260212847","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.87":{"name":"@balena/jellyfish-core","version":"2.13.87","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.87","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0e661ec8507fc2161da4e171f970926a2065ba3e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.87.tgz","fileCount":99,"integrity":"sha512-ICMib97w8otG/wC/R7SBweGMJ4oSHkt/Btcv+d6bYTtPEHCMWSFaWR1BqouMObde6y9WWZgaTt6wNEvp9mdK9w==","signatures":[{"sig":"MEYCIQD7bU3560bioBUpXSnkqjsGuUD6l8twklPQOFOAy4wOsQIhANiL5szkOH8Xt8bbA1nkTkMzkBaG0Os2UB5L0i9rPR+q","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":738415,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgly+lCRA9TVsSAnZWagAAhAMQAIAgPUKC4x+53bO8l5PY\nsSmekhx4hGdhRgP5dd4KSMn1Za4V5uZBb+2JaP8kZt3gwHmnnIHmyKkC/LBW\nXV0a/jWRhHx8Vi5h+Jc8+Qbwv5Fau1EVNozN5knJFLPdjH+37bgChdP640qs\nA07f794pe1itjO7yLTyH5HlA8eeNNfi5PnhFcXEV3sGkTM6fnMlgzDvYa04G\nhZAd+ceh4jesmJpfpAMPpGiwbbcf0P1aTqek5jy2L5Uf5s6ps04OL8MgcFmZ\nuIQPKqa9jZ7sax3RbQGxzLxTc14XFN7xIWAM92O+c7hXwMz5SVH3ZwypLJhB\naxWBXxj52wJccwXK5tS/UdN0CtpL0YjsW+oQt2gDfhHNQnwztQYI/E62RBdA\ny1VupLocXpsCDtw0ktF1Bi/JNQ31WLos1hTS3RhItpPRJ92UgqMRdyDCGVEI\nqiYqUtXytyuysGvtFkyj7yGHk9sqZMZpxVBZNA9HQUsHIiSRKQfYr+Sh1a09\nhGnoFY4EERmt8ipnEGAoELP+9ecXg2+Jk776hSpBAgEBiZ4Y5aMrINfdfJZ6\njhwoTa05c7uScLj7qBv5PeLMIVEgmEr/qnxvePrQNjrKf7tRVBkShzGiEIWL\n/pDhImPAcl8MLa1ixAWOm0ChjiTV09rATyXoDt5F0p0b92da+C3Mwrtpog67\nDFas\r\n=RojO\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2060b85cc80edab8c4d4338a5a1d90a46a106f90","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.1","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.15","@balena/jellyfish-logger":"^2.1.53","@balena/jellyfish-metrics":"^1.0.202","@balena/jellyfish-environment":"^4.1.12"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.1.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.135","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.87_1620520869052_0.08925411358386137","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.88-renovate-non-major-70f436cfb9e7aaefac1ea87efdece4a2b46421ca":{"name":"@balena/jellyfish-core","version":"2.13.88-renovate-non-major-70f436cfb9e7aaefac1ea87efdece4a2b46421ca","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.88-renovate-non-major-70f436cfb9e7aaefac1ea87efdece4a2b46421ca","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"dfb448f39138deef80d68fc4e1904a43e200e8a7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.88-renovate-non-major-70f436cfb9e7aaefac1ea87efdece4a2b46421ca.tgz","fileCount":99,"integrity":"sha512-ceL51041QeZvfjxM8xugZI9wQ+MxhO2W6X9jcYq3bzwiBd8Uhv6w0cXe/Ks8GZDBx9XCg+WlIwK5MZtuxzSMng==","signatures":[{"sig":"MEQCIFJgMJQGYsnXZ2nq5rHOlMYWEh5grVxeEEQMzA7WqPeOAiArbzyrYIRKx4AfCc/gG1WnGgcyHX4KfL0iTA7YNx2r7A==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":738872,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgl5zJCRA9TVsSAnZWagAAV5wP/0OArK5043FTxA3RIhmF\n+Tw2vUABODQlTO09AZxlfDw4+qTSBMt9A63SUwVujVxulTvkf2p8ZCzDTyWT\nvc/TIUxPHMltyZMsPiEKskGNCR8HDeR6GtykeJBo5x4J+uttPpn4G3yYn02o\njp5sX1VrpjV3DuNrTFH16TeNZH2wE71jRVopUWBg2jx+Qe8yV2VqSIN5ytAV\ntHh+UyIpGPtXqKYhSpy0EmegzuwTb5UiCf8O65mQla07lzmouhY5PgUy//U9\n39O0PwBrbIZuJR3S7PKhDFkcXUbqq/LSwfdmvkKhZ2sHxPu901r8T7UpN3c+\nQOwM+gS5qVwZChIef1Prb0U0k9iPt31gSaDkXPoWrsIlbAka+vJXEpr24mYz\nyzLLCHFfNNOoZws3cIM7vm59oZTWWaiXMaNvyDUL5AcQIb6ObdQluASoUGRn\nMYOiH4AXzAzcmkjfc/s+aJxJwUHFGvHgzPdfylBUzjM2Y39BX9J6RfG8rR8j\nI4/kPbLTb7bEto8kFgnZWr3T3O8y5QnEf3gWRzBssl7JaYBsJKaXX/hv9OeF\nE+IrwupuoSIrc6iwUNd4VwqXA0QtQjjYtgi9hktHM9nffHyl+uxoTVPJts9N\nJRKrJQ2Di5dxs3E5PFDbQiwFgrzQ1nQ4FGPN5IlbcLw/VD5Ww16DnUHY0+53\n/4j/\r\n=YACT\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"70f436cfb9e7aaefac1ea87efdece4a2b46421ca","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.2","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.15","@balena/jellyfish-logger":"^2.1.53","@balena/jellyfish-metrics":"^1.0.202","@balena/jellyfish-environment":"^4.1.12"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.1.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.135","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.88-renovate-non-major-70f436cfb9e7aaefac1ea87efdece4a2b46421ca_1620548808876_0.3032868418015049","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.88":{"name":"@balena/jellyfish-core","version":"2.13.88","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.88","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"92f0a38f3f2fcf2e3308d5e5d213258eac70b3f4","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.88.tgz","fileCount":99,"integrity":"sha512-LD2Md7JsTo15TNXjw2IMdF76w6/pVavOzMog7+5KqUmmwkk/N5eTfcYBfMiiVJ0mjbW83ZCgffU5d7wFDSQj3A==","signatures":[{"sig":"MEUCIFYTuwEEraAH77p03IEeEHD2rR1YuXj+nKLc3j9Ti0S+AiEA+J4PVoRrtibPfWE2x7gcCIqjzQSZGdqJsIQvwi4pxKY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":738812,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgl53/CRA9TVsSAnZWagAAHjwP/j4wJkmo7ZuMSU/2OpMK\nFKOy9Hb3cMMIHsgTbQcJafLlzZsJWnBDJDRocRiTZF61aMMcQbbBTRnfCyHq\n1XFBgwIXT4eGtn0XNZhvsOzqO21sEe7Am8fJnCJqSZ5rHkUrf035KYFF1nSN\nVYXkdLrrsWdbbd0XKu6s+XEyIj2AITkWgeBvvTBitTdla9O1pq4WSxtAn8jR\n2ERW+YWYunPh+7Wm8oZ3zJ3YsSy1viIxEx1UbcX4vqbUt6LAFWtUPMbNj5PM\nLnB4CMFK07yQXADfKOsOAfeq7Na6PbFonfnGATkkcr9WTcfF+JOwucH6l49t\nemCHWpaZXY697V9vxEgGoRmodpW6Hcoxj4RcSTiSuLUQHNG4fZyMuBqTALla\nOJ7cIm0I+qwCpUd5ZzQIevmX0Kb479TJq/RLb8bG+ZAKWkN68s9xPo2F6rwe\n7PrOPR/kGgtFLsMYk4XWhmoXw0tPxIdhy2YWPIJAredsvV5fcoVVgeYuExxD\nEk+cu/bdd/YK6VxiVZ/RhGUkVkcQLVqtWgMvxvysIu27ZQiQtgjJl/PuXd/O\nW/ZSzoTJbM72bW6QWj7UaLUFxuvE2Wvivn4FWWkRjVTHnzBYF7PJQg7aYvZA\n/gVddEEJ9V/W7ajHmSs5hCvnQeO23zfjka8SMf0OOGtrADFAsEbGGnHyut1H\npey7\r\n=Yie7\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"79c09fe0940d8bc753dee0b917699b93087d33be","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.2","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.15","@balena/jellyfish-logger":"^2.1.53","@balena/jellyfish-metrics":"^1.0.202","@balena/jellyfish-environment":"^4.1.12"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.1.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.135","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.88_1620549118828_0.829869059596561","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.89-renovate-non-major-bff644f29b809c960077c9656a588754d3ed9f3f":{"name":"@balena/jellyfish-core","version":"2.13.89-renovate-non-major-bff644f29b809c960077c9656a588754d3ed9f3f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.89-renovate-non-major-bff644f29b809c960077c9656a588754d3ed9f3f","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"561e29f62d0b6cb8b5180f925bf0b18faaaeeb22","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.89-renovate-non-major-bff644f29b809c960077c9656a588754d3ed9f3f.tgz","fileCount":99,"integrity":"sha512-tM/d4L/9jtEKke1ibJmxrE2qRJTs/a1GTTP2VAynlTY/oQ1MGfu6KcncSmAc4TLklGADuj/UPNoGOwX9K07M7A==","signatures":[{"sig":"MEQCIEj63oMbu3s4WTeZqOUERR9CPKJrU92eJDVt7xiBXK4iAiA4F330NQMpyKyLuTo/G6FtnjlI42g7O27JbowJ94cprA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":739223,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgmAtCCRA9TVsSAnZWagAAUhkP/iIYR3RzFZ/sv9fxHFZb\nK4zFZAN7KT0Ce+NXzTKLJOv5ThFResuhpXjJKYFprErQSg2mhzMCLaBG3O3u\nHOaofgMabupL2ul+x8qtefcJMBLCC8/oL0VIYWaAygK9z8MffRpJvrefgTFb\nAwZSwyHld7XXiTmzeyIFxR9DqHVP+IwZheJiqjMECpBmeI7EBlpfTSVd63Hb\nl04dz9Boe8dTsv1jiWS+rvA/qYFczvyO9CgQYTANEjWNXEVewRQxJtKBVqrf\n0CZRl8EIsIKYg+TiKh/0jPmDVllM4Zg3E44jCgX1H6n5YM+H9+zzb2oYcByX\nyL+8R6bBs+pgRQHjV5utQkDLJDBK1Y5ulJcjpyWvR45Tzn490bJp0VoWjlUL\nWNal+0DmGqTQPwUggjmEVj8AnIfePdUSPO11c9HmPwrnw1sZ9ff9fwOK4LBk\n5jlP4aLiJlWqTb3Y6tNadHea/GHfK/yNpWShGBhejk3FkLPqhpTWjBThWn/7\nMqOTu9Zj2SrulU262negd2zqAw7O8wuDt9g2ml76toAM5yLB0WmWDQO8FGFD\n6OtEeRMjTDeczYs/YN1H99j6+5GbT+azsL/diJaWwE78kAMF+rJCumha1ZFd\nz1t/YPvz2hgEyUH2GlUZi2VwUVWwSOGAUazrQmXbPeSC7dVstFa/UttEdAu6\nkMQS\r\n=MgM2\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"bff644f29b809c960077c9656a588754d3ed9f3f","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.2","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.15","@balena/jellyfish-logger":"^2.1.53","@balena/jellyfish-metrics":"^1.0.202","@balena/jellyfish-environment":"^4.1.12"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.1.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.136","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.89-renovate-non-major-bff644f29b809c960077c9656a588754d3ed9f3f_1620577090076_0.7008254885625196","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.89":{"name":"@balena/jellyfish-core","version":"2.13.89","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.89","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"dd4961385657dc10ff6a6591d5e13c8eef2605c5","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.89.tgz","fileCount":99,"integrity":"sha512-BDIMNTCPYsETqFuj9ieShWrpKnPlRmrYhYVhFIyNlDFBrpXmS4LLSZbwiTXUxkOKnu2oq9o2uhwp0du9WObh/Q==","signatures":[{"sig":"MEUCIQDTGX5ijY+vzudXDx0neoOhI2S+zZstGwaBRiIMwQYTFwIgEU+7s6W2aFOs9C+6kykUr5EffFOb+bZFcWYTiZQ9tYo=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":739163,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgmAxgCRA9TVsSAnZWagAAph0P/1NIc2mEo4MJASUbGl4b\nB4H7slEuQD/auAuyOScsWCNtfT877j51/e6WI+HpRw8p2Z/rm1BW4vug1A56\nvjEZSU4p+Nk5qvXrcHP4ZUc+WZd/K909ewBlgoyNiK3qmNsTW/lUddgcF1OJ\nSdGqEJbk08aMkj4OXCVCIQuITe7M3vM8fqXRxsy37xaswbASRg0EMsSs9iIZ\nTZbir/vrS/2XXrH3Iyd+zI5NRnZY7uLaHfXV6fFHMrov0xg4AyEHP7N0Hg/J\nlNtpExjs46GkocUWppteVtemF5JwuSGDqdfSbPvP2w9qmF7zPQih33OXt3VP\nQENMu5gZLLgtJi7pRiIf4WDCne0dwdXm84iby/8GicC7Q3uJkI2O8ZiMiU9Y\nfswLBa3uwTZg0eHmnzgdn5QYoWT5XNHyo7s0DI0v59zR2G4xykaUMshP9ocI\n4x/PTbCHEIecmmrGO5AgFTJjHSVe0oQepyfi4F8o69HHns9oYB8wP/4ufwtF\ni2qEdGs0AfQxDMscaOfIH91MoHwALnPmGwyKi6vZMO+Yzuk83pyxHkL4Aaj6\n8PldrMQy6ea+lgJKS9UZyjifeXesmFJzMoOrgPol/gsbZd+J9QWE2IoQzz7/\nzcn5EChOh9rSBfRfSgVbnxmVMl3ctAWNy2fx8oKd+BuZtRAdyYZ6gAjY2jDw\nDEyn\r\n=WJMa\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"5bc6e8dcc6ca02c9c7356430550a61cc291272ad","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.2","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.15","@balena/jellyfish-logger":"^2.1.53","@balena/jellyfish-metrics":"^1.0.202","@balena/jellyfish-environment":"^4.1.12"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.1.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.136","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.89_1620577375583_0.31163337291536797","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.90-renovate-non-major-0fe2d1bf85156c214deb586bbcf62184b7aa6ab9":{"name":"@balena/jellyfish-core","version":"2.13.90-renovate-non-major-0fe2d1bf85156c214deb586bbcf62184b7aa6ab9","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.90-renovate-non-major-0fe2d1bf85156c214deb586bbcf62184b7aa6ab9","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"144189e58923ebadaf3ce3838a4a883d686fd61d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.90-renovate-non-major-0fe2d1bf85156c214deb586bbcf62184b7aa6ab9.tgz","fileCount":99,"integrity":"sha512-ldaLXa6bzH9Cw6eCVHn6zMf/y3yzRTBi54ib5yoDzuMzIR7LDyyWsmXN07+xEsbc7w4GWEq9jyiZf76/msYgfQ==","signatures":[{"sig":"MEQCICJZcj1Nov4zMJ+RpZbdqKg4opeLhgUtU7bsxv3DhgA5AiA9WowDNy8BI2T+s/RU2/JhqV0KgD45hzMtizOPq2A5ug==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":739652,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgmOmbCRA9TVsSAnZWagAAD8gQAJO1zmsT9NHDQM3CmqUL\ndEkrl34qV3KoEBerb4KxrnzlPtSQEmjv8mi8eGqwJDt4s+zECMhBR93cpsKZ\nVsmmwlpabDENMF5QNLUSy0lkJ33vVUXdCaOg7qn+A2qppOJO+z7dzHJ7izRr\n+nlt78pPFpQhhwTD67cP5X/C3a2xzzzdvDOPPBJHICmVsRFuBfSJCT37L0np\n1rRFWe65+hRD4jvIRTytyC7a4mH82HvTFlaD6MWJr3qHLMVUUK/B2U0dFt9x\nOzG7QTb2hZz+NoCIIFVBQ5o654dSf/I06bNLf7laWDgz9goNVPCh6w2Zb94E\nyXWs3iPtAd6PyF0bTBuRR5X5edRkz5iYHfO0S591/rW+9nF21k6byZaGrmOW\nZ9YOYCOYDZtTzFGEbMYuqqLjIMgvS9TLLJAO752EeEaRURQuDPuEPSQJKmfM\nykE4dATsaoLMACSJMjmcH1g/QmmgU0+9jh12/eWi6m4/ziyw77GPwQQXgidB\nx25sSPrp5WVpAri90OufmB1qe+2leYII2vPXvcFgdnhEbMuDUhg+BNazWiJV\n69dAQ3S0F7JPC38keALUj3KUgtdeRCnJdh9AkkTSnNB0/8H7OHaM3ZRV7zR7\niuEHxDmz30YjQ/yTfZJbsIwOVsZXKhKe8AwrPJRSgpSMXpt1hVmP+iqjoIw9\nAhIF\r\n=NWO1\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0fe2d1bf85156c214deb586bbcf62184b7aa6ab9","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.2","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.15","@balena/jellyfish-logger":"^2.1.53","@balena/jellyfish-metrics":"^1.0.203","@balena/jellyfish-environment":"^4.1.12"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.1.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.136","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.90-renovate-non-major-0fe2d1bf85156c214deb586bbcf62184b7aa6ab9_1620634010847_0.08072251312078582","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.90":{"name":"@balena/jellyfish-core","version":"2.13.90","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.90","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"fdaa2f96fe15e8af273bcde885ff72be96c88b74","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.90.tgz","fileCount":99,"integrity":"sha512-L7+st9lfqWhQrUn/i5NuVAKLvBz+VaPQCoA5CE0O8TD1FRX+G4iOEJJ1/GHPkgmNH5w3sb0w9kWEqcgGi7AlsQ==","signatures":[{"sig":"MEUCIQCSgtVcrYCHHMKtKlV5VP0T8fuxgeee7YVp2LhvOAa6PAIgR9JYHR68UJjTj1VvHRgw2YGEoT1v6i3R84txRjXr6Q4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":739592,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgmOq3CRA9TVsSAnZWagAAbUkP/3c63GczDxB7CICMgERx\n+HYqHzZkPpKb1TwyI1JOSM0pnh3FXvjGMujzqDlxvnwCxctDqpzK2WsYE9KT\nhNFpF1egvnUfx9DILZ5AUYcODhhWgnxwjmcQGzVYcWReYgWTegYO+IVMJh0M\nnFOAoLYl/Nj5nidSjDyhOIEWFyfXU3MLj7JyGRJEf+J/+5QGjZ/NlPNCLdD3\n86hOYXFTXlSr8/EzFsHGkQZgI+3Pzas8DTdvC74U8JXDnea5RX1M3Hj+dE/C\nEuZbwdxSM+2+2sQsDyNVVx5Ufm5XR9lv+dSi3MUcEFd7EA/k4jrbxC2O+EY/\nusV7iaIGm2zq9IHU2dZ13GdTtZlIVgbXzXduLz4Knuz6s7Ulyick/aGDXB8b\nntZh8cmJYuMGvyX9GQCmZQ1vbZZFplLql6tddQTh1fc54cm+c16fRDqyHcmZ\nlyJhdpAmiNlSIn6hBelxkVU5rizWjsqEDM2mI+B3MZUa7UR1MUSwJ7TcMtRy\nZIAur9eukp2JrFiAmp9QLH8aS2Lx+1ChM4dseF8xko5GalwH5kEDqMocugKr\nJMmkv9CnvZIUWqqz2vIhNNSHj3+o1WgKtFnK0CGaj7dBlZXMA2VfMb0P0ZAd\n+AYW2EZrxMypv1n/6Po6hfiUc+hxL7mizXBpsMYGVkPo0uupl8UEVs2PGe2Q\nsX9p\r\n=r593\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"31418816a8530aa26c3ca981256e743fa2c7bcd1","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.2","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.15","@balena/jellyfish-logger":"^2.1.53","@balena/jellyfish-metrics":"^1.0.203","@balena/jellyfish-environment":"^4.1.12"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.1.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.136","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.90_1620634294549_0.11265405387551564","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.91-renovate-non-major-aba9bd04ac58e7a50ecc96a412474b5dda656ec3":{"name":"@balena/jellyfish-core","version":"2.13.91-renovate-non-major-aba9bd04ac58e7a50ecc96a412474b5dda656ec3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.91-renovate-non-major-aba9bd04ac58e7a50ecc96a412474b5dda656ec3","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b5aca566a55f8ef7891e172592cd8bedfee06644","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.91-renovate-non-major-aba9bd04ac58e7a50ecc96a412474b5dda656ec3.tgz","fileCount":99,"integrity":"sha512-u2tc1o/z5qZ8RC0iliNnFKw6IsWdgEK745qRh22G9s2oEXq+9KH+8rCNRsBs+6CxNyKy6x3a9gqP6lXtmaSR1A==","signatures":[{"sig":"MEUCIQCNTHBdCJElkzQ8qIr30frtVKjIZkwDdarIZalACUXfYAIgQn5n6lnPHWZaGPAtaoEhP9xIQLnSSXgbDVYtt8Fo/ww=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":740077,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgmRVMCRA9TVsSAnZWagAAndAP/jDNwyHsPff+TZVxMPRI\n86FZrsrs5yfDxhvohNC9nz/OHeYul5RFjtnKcBo9gB2ZW20PilyEWAktnuiT\ncM6XILK3ccNTJ2gh0AobmggdTjNkjNkydGxDQ4w95VuBKvbzj2ZWPeG576ed\nPlCJxKLdx+XB5/tSAzAJlD3lk6YkPBHOjgOnqb6WkWPbwC8CoxkdybjKXHUa\ntSujy71Zt87qG6HXW+sN3bisMj9WMFpU6btKjPybmde5F+u5C8VI6z9N9lGH\nj2FR/6Bl4H/1GzIImUf22cHB7XFlpuNXD7oAsMEMc0rHKZLJlRxXQ/RYI9hv\nP56eR53CamZ8YinVtHbWWYRlvYxHhE38VMcXu58QJEeFXIn0BLXqwXoALlZk\nGvwPjIbxGTYthdHl/c4mFJGHESmiKKdKF926h5KO6rj6eGr+qRjGciUzs48i\nretDlQ85GfFIAaL4msjFpbaz7ieG7Xmy4w+SF33fH4ciJPquFtNhm4D5lcXn\n8n3l84oW6VfwFAPxLQmDGToR52hJhO7T7YmVIS9IwkKdPUMeHtVXV4RdX4DK\niPd2Sf56xD+PQr3JToE6jD3cuG8YjR1FJYlJ3ZeWIg4k+yuQPfmr7kkXfx6V\n/z5iUvI0avw52QRxaOooFWY+w5g8g7t70FPPofEP5hU6NM+wlwer+IDt9CPw\nrI2r\r\n=Eff1\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"aba9bd04ac58e7a50ecc96a412474b5dda656ec3","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.2","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.15","@balena/jellyfish-logger":"^2.1.54","@balena/jellyfish-metrics":"^1.0.203","@balena/jellyfish-environment":"^4.1.12"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.1.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.136","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.91-renovate-non-major-aba9bd04ac58e7a50ecc96a412474b5dda656ec3_1620645196187_0.004819928312482791","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.91":{"name":"@balena/jellyfish-core","version":"2.13.91","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.91","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2cc5cc0d554a44a741d110d2fc5d95acd9cf837d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.91.tgz","fileCount":99,"integrity":"sha512-M84XoMCIC3mGpyQrnPUMPHFE1RRWfd66W8Zrt0OWwCompXtp4fc9SEzgAlDJZ/pNwjU3rS0CSxSMuO+pcaQdsg==","signatures":[{"sig":"MEUCIQC5R8+GeUrcNY7iSP21PgDWJ+oFCa5rmYl5LVEukPOYCwIgUnkRkXZS6aTSzVnsWLEWfIlBOYHaOGlRiCOzHwn23r0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":740017,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgmRZaCRA9TVsSAnZWagAANfAP+QDSazrGWmRjrFPyKdUt\n0dhRCtJDasEm1cVPqrACvWk+ZHDO9THEW8bf2uplZ4jUwK8jQ3qSOoFGIsol\nkG7cgD/iye+N7Tz85EiZVuk8yvAnOl1agmA3X/OejVXOTM0dDoZuGYD1E4cN\n8F/UqhPj16l9vz5QrrVymHHUaiqROjY6GiOacyc2reauX+Tb8uOqlZ/WVbIo\nxaZA2T84ecG3YhGsMvzyw9ZSVLrM501LBBGPnviY/YDvJcVtlkqyDp5/IHEE\nJZmg9ogmZvJKyk4WDj8mFPstidDqyRfzczVq4l9SM4qQgczwW2oeK2IRqwHA\nG0Y3+spLjGgcjhyti6cwGM+fgx6yRszlel1y0XXZlsIT1F/semN/bps3jCfN\nS9Nj28CKHL4mrJVpGc24a/lyA2KaDtBbPEeb1/xWIowAYhpXSBzR2LCEH+yr\nlqsf75ohN12cKy1KMvLSK5zJ1sjtjaC8cUOEYA35rR1JpDocl3aYz4/wH6bn\ngck6ZF745ptPeWlXsGcmprb99KdQqWV6AyCOmg1KJXAgZMNb9ni4Aosn3wVf\nDGSvwSAV+RX6V7R7QibQ0CdN35aAiCXF5VCqNKqlB0DdkBuT31ZWpzso9KlP\nUbxbvvcoQiLof+cIjmIbqA8CKYbod+BksxK5Fur7DugiAbuYyCDL2RkJc3kk\ndc1X\r\n=oMRi\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7370c302a7d36ff84c1fef3d1fe650f17c8d79b9","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.2","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.15","@balena/jellyfish-logger":"^2.1.54","@balena/jellyfish-metrics":"^1.0.203","@balena/jellyfish-environment":"^4.1.12"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.1.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.136","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.91_1620645465582_0.5957029480040315","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.92-renovate-non-major-992b08cfe553cb822a3031a453fabcc908e85cc6":{"name":"@balena/jellyfish-core","version":"2.13.92-renovate-non-major-992b08cfe553cb822a3031a453fabcc908e85cc6","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.92-renovate-non-major-992b08cfe553cb822a3031a453fabcc908e85cc6","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ffcb2f1de8181087dadfefa053883e825b8cf41c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.92-renovate-non-major-992b08cfe553cb822a3031a453fabcc908e85cc6.tgz","fileCount":99,"integrity":"sha512-bD/QG9SmXU5OSW+eRZhiFTjYxm3VcmhiQHNMKb41I6KfgYby8exUgGzKmJq5N5WPl/CNvgYpcK6IsNBM7jEkgw==","signatures":[{"sig":"MEUCIQCIhgSw+p/fY8L6La6bpeSoT5g48n+qA2PIrnU5cYKL1wIgA2YLxsG+/DEwmWs1l2GEw3RAby5QI3UeRYT22B+UKR4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":740506,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgmTMdCRA9TVsSAnZWagAAJ9UP/1sFv2+PaaCjYDiM+ptw\nzIcd+/0noJYDSAruj/uDzhTbtyC6qwltQ9I9p9EPblS9mP2bzJRNI/ixsLbG\nOiQwzLCPkwSSmqTW3n+vF05IwI6FV96aIE59DUgkZ55ALcRlTtg6QItglJk2\n2btgEsliCWRF97/cGw901TNw7FjFebcYNGgv39g2UawLXG+jM18vm4pqPWWZ\ndpq26MBoEqJ72fzg1j1uE3/g0NyggOuUqmh/ppxwTLr5ld81WgYhdSSeflLk\nzLJTIJiI43vcxmslftNp7lAB+Jy6QUOpelGY0+NfxvW+XrtJIQK59jIAXtpi\nKwA+qR+mFbYKh16/imKtw4X8g7eGe5pqVXLMiNsJZ+h0LSWDf0ONm9Ui9Tr/\n3EDu7HxJzH9nyOj2IbASwnEQ5G3JK44loPh+wX9Hv4fJy8BXhDXivZnaT5RI\nGF3EjXtqDIMPLKFhFNOE2Kmh2XnXiNb2RlohHNX66PnLae5kA7KPY/qVN96p\nqUQKWSyrvwNlB1+/Avy2M5pCtzusMo/skIftmTJFQP2w/LgvwO8XdCWZeBTF\nR1tWd++qUPSf5g4hHlXoVzxudbRDH6oDaZM3w+w/h1RtbsyRNR8A3lnujdQZ\nJqYYFvw1ZPgZXXZ9/fFehztPzjM3xvuaIKK2pd9JvCd557WX34HYc6a9R79z\n9DtH\r\n=KTjn\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"992b08cfe553cb822a3031a453fabcc908e85cc6","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.2","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.15","@balena/jellyfish-logger":"^2.1.54","@balena/jellyfish-metrics":"^1.0.204","@balena/jellyfish-environment":"^4.1.12"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.1.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.136","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.92-renovate-non-major-992b08cfe553cb822a3031a453fabcc908e85cc6_1620652828999_0.9097641923154538","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.92-renovate-non-major-a18df3a88ae4b4d9987c5946d5e481d7f98fed4e":{"name":"@balena/jellyfish-core","version":"2.13.92-renovate-non-major-a18df3a88ae4b4d9987c5946d5e481d7f98fed4e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.92-renovate-non-major-a18df3a88ae4b4d9987c5946d5e481d7f98fed4e","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"306f23fde2575227fafafbdd99fca9a9da5ca455","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.92-renovate-non-major-a18df3a88ae4b4d9987c5946d5e481d7f98fed4e.tgz","fileCount":99,"integrity":"sha512-XQWY/KKBEuXp8kpKO2DOSk24H/JzPHrsWhmnK6AO0UG+LqbEZGGEEmeqjXkXCGW2XtokadXHD6qWeb/O5CaWLQ==","signatures":[{"sig":"MEQCICW6BdJ5GpVp6cWwIKdn3rAaPuJUWqe904PUSkwOmcoAAiAZw8U2JCf+vn/rHczYqyt20nEmyk7A6J+xQsImKWYR1w==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":740428,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgmTRLCRA9TVsSAnZWagAA46MP/2xsjJZGDw+oMXVOEKfj\nQetClzpt1KyaJu1umdopCGas8rQMqWdM+VjnlSspDhevYW/eEO2mCfk3pnHg\nBIQ3X/QGswXM71l81sj2IBD9BLH6RMjoOdULovzldBoM9a4aBiAXPW45lk+g\nePhdkJ1nOjjZCsX4jf7l6zx8hlOUMYXUeZhpUU2RToZr9ReL+G3mauqyCjX9\nASsvQDLHlKGs+g9e7KtOHmPnmM7EUx/o33fIkwEhgZMFP1u9UejTuQt10Xnj\norMmYzKKgfUbC0MTzjuv6Uq2jEvxsxwYFjPqgMXct5lqAXOReTX/5eKKORgn\nkl5eGn/U8E7zQG5oaMKEGal9lf68sTthDXlwXF2EuSdlMqSf2bWQ9XQQHunu\nBohxWhPpZcSOxUaO19MzDWJsmfTsQPlFvOr2LPkDx2mKPkEj1mAlptaLV3Vz\nqyG2TCVPJMldytkx8aQohLFE9Cxn3EncoeAFLAKjVMIsHUOfiHSqtGM9JpMo\nxqmiMPSXglqQjjSjyuPcc/vxw5WGkCGTCZVmj70Blbpc4hluwxoFV4DAMXHr\nK3JoZ6tzVIS2PbPXsMgP0u5fWL+6HZcbJ9TanujEV+21J2StVkFZfmxXomZt\n2HLl+x1zO1i6kLQ0be21uL+TE6WrIRYAD+Jb9EICESLXTyFXos8HzX/UIRnR\n9f26\r\n=1EWE\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a18df3a88ae4b4d9987c5946d5e481d7f98fed4e","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.2","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.15","@balena/jellyfish-logger":"^2.1.54","@balena/jellyfish-metrics":"^1.0.204","@balena/jellyfish-environment":"^4.1.12"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.136","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.92-renovate-non-major-a18df3a88ae4b4d9987c5946d5e481d7f98fed4e_1620653130409_0.9834610672330633","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.92":{"name":"@balena/jellyfish-core","version":"2.13.92","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.92","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2312423beea0979b317863937b7fcb46f52c3a24","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.92.tgz","fileCount":99,"integrity":"sha512-m4H577NLyLCGkHFSCmy0Y6CHHCq01ODsBYJdNSX1Zkqu1nO9HtILj+8YiNUq+l1Bp/Cj/Q4cAW93dETMHo8CdQ==","signatures":[{"sig":"MEUCIQDytYPyXMo4X1V4BHTrwusq/L870xvPR5a/vqhNImnlpgIgO6OFu9APD6/ngzMIAe2Aiu6bbP1t8NDcAwq8MDZUHzI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":740368,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgmTW6CRA9TVsSAnZWagAA5UoQAIQRKK3twazkS0n32oF1\nmOdCZjUboWT+AWM9NiD7OupYPPmt+HceSPfFoETDXuniShREjmyG+Re7K4hT\n54gbW9pzvfaOBw7fznetu0IrVvTODHF/eokAuFlThMy3VTLy9SEyGfvFGkpI\nAdMH4PypMk8JzC4WneUIw9w54YeSIEa5CpLPbc5rbA4zeuAKdvP17cx1D2l5\nG7pd6oly84BOMPPiieLiKCTjFKj0DfIcdoT0Xvs0l6E8U6kWzPvHLKuF7aU0\nu5KCe9ayiMtyIBulmV8pFjoGtzv4vTzYa4AndpgW7xjk9V3qRd5yejJlRKNC\nuvZSvNN5pcN7HY61bO+WJijFqkPS8RRDUOpwVkNo6LiBOlwIMnROO7HHX1YX\nC+0cRvsh53MSJxdWc5dGrq3HdkgkwbDXtdKRwx4jQyzGkrV2xTAEA3c1RGq5\nzNirgwOUF+BA+0DYpw3kycqF/02XCMDdUVRpd3fyM7Pp5N5Mqhdh3ntD5xKu\nn8a+7IhhwTaoz7Pe3VgEGfUnd4J4zjjQL81c5oGf2T1/j/ksnA8b2yVSpwMg\nFtO01/fbg8Kv4M5n8TAqLv3GSVH/a+x+oiol5v3Stc2u6Kt5w5AvnKzClVMU\nHMnxnE547NONeUJ+hMFlCt8c5MzC7xJajiGIsHHjZLvPEHsfHJDq6YDbIY48\nmuw7\r\n=LdA8\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"12636dbeb6d23aad6ba3931cc97df1e5aef048e9","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.2","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.15","@balena/jellyfish-logger":"^2.1.54","@balena/jellyfish-metrics":"^1.0.204","@balena/jellyfish-environment":"^4.1.12"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.136","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.92_1620653498358_0.913308386906295","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.93-renovate-non-major-c4c62c790b767b5f0548c526c72871e7d52a18b4":{"name":"@balena/jellyfish-core","version":"2.13.93-renovate-non-major-c4c62c790b767b5f0548c526c72871e7d52a18b4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.93-renovate-non-major-c4c62c790b767b5f0548c526c72871e7d52a18b4","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"fe427fdfda8050b1dc8c24adda3b0b21566bca02","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.93-renovate-non-major-c4c62c790b767b5f0548c526c72871e7d52a18b4.tgz","fileCount":99,"integrity":"sha512-luCZT6g86B1+0T9mZE63DdiPxqc/Pln9/kymP6Xg4OEV9wRVaEAZgBq7TRt+xAXOX4b2LO9tlWeLV3vDAbo68g==","signatures":[{"sig":"MEQCIDpybSWPAqmCjEkqea2IrklrsxQ3oSasVQeBJmY8cPWxAiA5rV85v1sfwTLV7HXfZbPPTSZJBrsz67fs9aZWuK6dFQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":740851,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgmUbDCRA9TVsSAnZWagAAhmcQAJ+c8GVNc8rnqcbqEnB5\nCAFIAthq0fzqhGvz5CwIG1f2CVfBk1kd54lYkf1gQy94AfaEw4gNG0+EPImK\n/WMohEnGudUKgXJ9YlQ0dI6/ys0Po13I+EyeYtHy0T7ILGPc5mFulHmRMjFt\nNwtlzjVZRO0LQU03dCf1u8UvpFUpgGaA8HIbiUZeT/zMlQfXgyXbfDKs+WZv\nFpOCIcvbd3Rz4jQ00QPtPMYM7VgWnzluPykVCXyXSRZu8T5lN/qUkyojwLyf\nXnfoC1Cy1M7eZPMTRf3/w5raRrnPiuLEjL6BXYoZT/rrthW/HYuXfCf4lCFs\n6ef7jFsHTNl9tTvyyJYkjUsnr4coxblET+DRwk9RpEXIbulaocertXguKQL0\nmVD4fm5FKOx5DfchYKyD3mzOAxnWilKI4D9NT8FcLf+tM/tzenOEKqLIJ3B3\nAE91kteWT3niGFRxTj/a24f6jVeO3iEhWXl9m2q+ccDflj1J19m9ZDgaBtQm\nryruTeCeKHgm7juYdEAO5e7VvFWakZYHn3WZ7vu/YF+ZEQeiVedFxTPkMiZh\n+4vphlpi3Y3IrUlS8GVj2YjRcwhZyJekgt3csEBzhjp4rgq0jwrL3fPt1RnE\nXZnxfRZXpoVDHjdHYBQxvwkrFwA1D+tR70GESqbUjZn0NnmeARDnX87BJDmK\nRMjy\r\n=zn7e\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c4c62c790b767b5f0548c526c72871e7d52a18b4","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.2","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.15","@balena/jellyfish-logger":"^2.1.54","@balena/jellyfish-metrics":"^1.0.204","@balena/jellyfish-environment":"^4.1.12"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.137","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.93-renovate-non-major-c4c62c790b767b5f0548c526c72871e7d52a18b4_1620657858694_0.3572114506532884","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.93":{"name":"@balena/jellyfish-core","version":"2.13.93","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.93","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8d12866073324aa4cbf03bc4c62390123b916ae8","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.93.tgz","fileCount":99,"integrity":"sha512-wvzXbODlds5CGhtA4vSJEvNTLPi1aOTpyrcQN3yStbNkyeSk6+P5+63bT/0QtcJIIWfodXM4A0VTeeM7+Sor0g==","signatures":[{"sig":"MEYCIQDLBANeU801PPNmximFKqFOyU9L1V8ZapR628fZT6/ciwIhANAaYfJm5bAAMhD2vpXhhjAjU6wbM0HO2D61RK70/FiN","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":740791,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgmUfkCRA9TVsSAnZWagAAJTcP/1JwI7vzZQMdhonpVPlr\nVp2NQeqJoH5RP+UhPGKSErLYkJjt2qv/dVfsleN7kJ1z9cqxAwn5JadVB6kP\n1IjSRTXpm1OXSBj1H3TBu7YRbMan2O3ByOMoodOaG2N/CqDMzH6/uuOPyIDI\n9r9udlg6eO4fNglNC0X4xo9Sdw7x7Se0zbn78ulbOAGT4xO4ywBc1yslBByr\n+gPxIbqI7zhj8KhLBQWfFzJBwXjf7ETfL8NuG1Yg01voAAJCjAGfY6xHyNvO\nGz5lYQfN2Tp1U3/JMyTs4C6YFoaS5t9g3umuxd7ZCSgPNBxQA1PR9rkik8fZ\n0wbTNvZ0Cu0lNcn7j27lOW2VdvqJe0W0mqQ2XewjduWpqHRTpxN7TsZXPagK\nsZyR6qDm66JWjRYMq4Wrt90DVhT0B2PyzCFD6r0o3m4NWYEeZYLToBb6MwzK\nv+HhjmKkQfGbntCgPM9voRj39uLJbRqINZNRPNJ6xq3Qvvn6KlapGcpDN3d+\n2p7nTlDb667n6XOF9vA68nf5bV9iu7aLgVXeHXyD9FNvwhBFxAkR3JzN4DhY\ntZf/aNx5ZK12Uc9biofJPnm1fpBxRphqugBz/u1Uqb0zkbJtO3SEdhr7zzqz\nL0xiyAEEcsU647k3Cx6DOwOjOxHTC/Z1czwzO2vklzS0RUCfNXknC4hExcGd\nXhqL\r\n=5bkz\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8004abd51089079ab3fa8682b7f5376f53c4de1a","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.2","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.15","@balena/jellyfish-logger":"^2.1.54","@balena/jellyfish-metrics":"^1.0.204","@balena/jellyfish-environment":"^4.1.12"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.137","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.93_1620658148316_0.07829928573151812","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.94-renovate-non-major-4253bf34dc03c17fe46a6245afe8541154f23fb5":{"name":"@balena/jellyfish-core","version":"2.13.94-renovate-non-major-4253bf34dc03c17fe46a6245afe8541154f23fb5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.94-renovate-non-major-4253bf34dc03c17fe46a6245afe8541154f23fb5","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6038a974ce28ba1307b1becfd1f46a456c65a2b1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.94-renovate-non-major-4253bf34dc03c17fe46a6245afe8541154f23fb5.tgz","fileCount":99,"integrity":"sha512-UY5ur/G64iiqkjXGlgcElsH2rKRMvQp03EMviERT71Zq20Jtj5IPyznxN6iU9w1QDVF6mblnCrAEQY8wJc/jWA==","signatures":[{"sig":"MEQCIFdZMfqNkRqd+fW3XKICGBILyOk6ZxYl4Gz4yxTXQQlwAiAxPLXruMa3f2C2pltZDhw3C6u3j0LR9+qssxdR1CSfjA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":741202,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgmVYdCRA9TVsSAnZWagAAImcP/jMKa06i62gvGDR4BfyJ\n6Q+3duX8Vp9jA9dOxmWT+Hd6XFUhPB6zpgDCtw4l8E02k/jqJQe3Q1j+HR1J\nIpupEe3DMIBek1fWjAmQLZrEvJNNQQqZJAr/Gc1W8geg493tp8+TXqqKdcYP\nwyc6qQ4Ym7HxHFaxd9kuaV6nNfAGb2LMx/a6nxHIvh1+IMuJqZBVjCGToaLy\nPMfbev4iIPkde7SNNNQ4H8hr+FRgq5f/JVe3rM7CRBe94tjHii4VFfak1b6G\nOENBY5yRY22SSPg/oVE7J692v2Ap5KIbQVsGNLL+78w9QCzc7M7l1NCJq+2H\nXV1qv6aSxdEuM/7XLj5qIPR+6rYwCDWuBJOHf5giXhdQSmJtLjdX3ScR2PPz\n4+z/QETrzdf89QBDEW63T2mx6kv9WWq8mgn3Sv5s6lvG9fY9YVHyMGYwqKbs\nYBhMBhm04xXtl3h7829Mje/vd5Clw5rKIniIMmNQQP8DMWDHfpEZoi7NuQyH\n0Vxs5bj4n6uqIPzr43I8OXozs89zmHqTmE1NHU5uFYh1lEgcRHzPExkBE7tD\nPXXgv7QtHYBxdISQNTakj5Rq04dY0hGtzdbhIM+9P8k+miqt00nmTJ/vSvQ5\nhCCA3zj4XrhNlcUezbN8cMAnR0B8bzjMaU19x5r81XDQ9/eu6o6JmxOrJ87B\ng/94\r\n=U0Dd\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4253bf34dc03c17fe46a6245afe8541154f23fb5","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.2","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.15","@balena/jellyfish-logger":"^2.1.54","@balena/jellyfish-metrics":"^1.0.204","@balena/jellyfish-environment":"^4.1.12"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.138","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.94-renovate-non-major-4253bf34dc03c17fe46a6245afe8541154f23fb5_1620661788883_0.05993211946223309","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.94":{"name":"@balena/jellyfish-core","version":"2.13.94","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.94","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"06c97005c975dd6e32c1c19d3fe0c7c4a5ca9ca1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.94.tgz","fileCount":99,"integrity":"sha512-qmL1k2k8FQIpTlzaurHzfCJMyaWja9oLTsBBSnmAgrO8Ua6AFKHJi4OIWikGp4nmqTaB7XWSp6XUwOyTmPFElA==","signatures":[{"sig":"MEUCIQDgzlLPq7LCEhzVyWGC7Xph7YM2IgkgYuqZZeaKyyQXqwIgDgL+pGYi/6AvrnUIduvE72fluyLWDBcVlKPUwNQjw/c=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":741142,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgmVsxCRA9TVsSAnZWagAArAUP/0bj6IA/huI6gwVn2VAz\ni3wpUYB9qJPOx30KT4lawZdTJL+uv1oyHyLsNdJCD9atZSnuazWuKQsbYc5G\nsPSkckChgHZqiNpOAWuzrbmLt3APDBYBOiTN4JG/jnZwxieXgVA6JJ40l+tR\npfS7HvxnubR2/qPBM9fmZSx70Nyx06Kuu1fpdWe1giNS6DNwHe+0wUU2eYcp\nxd8HUDn2NODwB8Ylp06walwkn0IAzIDkTQQJzJq5jAQkq07rBSjBYZ877I5Y\ny8QcrLrCc1OZfcLnMc0QujY5tuEFAmSoOcX3CnoXSrv5g2z5TiznNK1VdPvH\nGVXYwX3LSakgKK2kwtY0uZY14/wSf/LIFAw9+RrYx4cU9KxkhZBkymLxw2wH\nt3cA6rSQ2P99NP/6SL9Iw/9us+UtuY8c2Uc+7whWpmYUYdr8Vkcyqk0g10Er\nFh/11lvK1IRkijIJWrvJFdX3aVUtjQCPloFn8UfRJJFNcM4oZy9H8LyUwnH9\nGk+zmQRw1bepbJfBbQC58j5Nay6sHHVbZtZkQwCX/SOVwjE/LiXEMcqTS8o0\n11IOkUR8IZVdU4PZJOZTubto3mQIZ9dHgqKXvC/hMcsMo0gMs6EFkKVusei7\nnZIqpRqbeptAxHaNjr4LRDoVZxNExFnpKHiubPz1MgglSJRKCttw6HQ2Anue\nP9r4\r\n=nJQE\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a810b59400fb98d3775fa7cc697bf935445de5c5","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.2","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.15","@balena/jellyfish-logger":"^2.1.54","@balena/jellyfish-metrics":"^1.0.204","@balena/jellyfish-environment":"^4.1.12"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.138","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.94_1620663089131_0.9798387960982027","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.95-test-multiple-prerelease-7944ca2c838844e1d6d6fea155a339d8d124ef9f":{"name":"@balena/jellyfish-core","version":"2.13.95-test-multiple-prerelease-7944ca2c838844e1d6d6fea155a339d8d124ef9f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.95-test-multiple-prerelease-7944ca2c838844e1d6d6fea155a339d8d124ef9f","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"793d95e8765d2eba9e369f4978cf9d88b31bcf31","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.95-test-multiple-prerelease-7944ca2c838844e1d6d6fea155a339d8d124ef9f.tgz","fileCount":99,"integrity":"sha512-joXHm0w9pGCzcDy9foewHYywpE80y7rVDWdMRgzcPArvLFYvnpvKmy9zUGUc4tQMyrE3jSxXTqwBPN2idFQGzA==","signatures":[{"sig":"MEUCIQC6Q6apNytuKdPQ8/LWy4dLsOWEyGzayC3KfU6NUah6agIgCI7b1MlIz+rwB9lnTWWz2Ko2A8UkSKC6uDHhDpAWPgk=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":742692,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgmdKyCRA9TVsSAnZWagAA+cIQAJOGR6to//lytlOxZXHu\nKxnwLW0+dA3feZ84lAzCmdpZyH4vdNaKNRWN9mEm8L47cS5QeuPk1Hrdplb0\nO0M8gCy7VrDjMlmaYruALdST5D1le7JcAar2USQlBezk+BPils8rhNgLca9V\nM/ZCEH050RGLG3BjcnpRPm+vDok4/QgYUXMhmWKShoDKu1kNsiKNTMX1dQr+\njxBjuUaY3oRl/HOA+cNitqQoqVYKZWcCy4iafB/xTmAWkeUtqsVyrI7ia1mL\n9F8o6u2e1qchVXLGWIFNg/n1g/Qoj40jkQaaMuiZ8isx/0Ug7K5zrMBH98AZ\nNDfZO1DZkCY0Ecp/1IWO5WHsK7xRGSrXi67KEfshSRw8ORTD1QK/7oda65DB\nMrnliIZu5Q8gCfmbgxcx5vjWB/EmqOsPCPFr+cvSYwYt5rC53ZuE18uYOvzk\nF0zl5/el+bhRSnsm5gv8c9qDTyX7WbDaSPIfqvuzukio2yaI7pxBOCRbvkzU\n/2jmTvQqbf3rktHZIUop3injB+n0nvaXtdRS2qbKoQxMK+9V0wZCtiCj92wH\nFsjSuv8IMC+zyvMIi9V8/ZTfuacmHrSyuKByGK3i15fkGWJQdfTFbNFV9hOf\n1QVWev/UecZJdrVHVObhou1MJbV5Mu/sWXop85z73j09Gn6tvcKi9TQvg835\n+whk\r\n=KOt8\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7944ca2c838844e1d6d6fea155a339d8d124ef9f","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.2","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.15","@balena/jellyfish-logger":"^2.1.54","@balena/jellyfish-metrics":"^1.0.204","@balena/jellyfish-environment":"^4.1.12"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.138","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.95-test-multiple-prerelease-7944ca2c838844e1d6d6fea155a339d8d124ef9f_1620693681790_0.4186863591186629","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.95-renovate-eslint-plugin-jsdoc-34-x-15557152097c156d657a55a7a02fc47331ae08e1":{"name":"@balena/jellyfish-core","version":"2.13.95-renovate-eslint-plugin-jsdoc-34-x-15557152097c156d657a55a7a02fc47331ae08e1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.95-renovate-eslint-plugin-jsdoc-34-x-15557152097c156d657a55a7a02fc47331ae08e1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"252f770a98df43648f4c877891686e709d2dd344","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.95-renovate-eslint-plugin-jsdoc-34-x-15557152097c156d657a55a7a02fc47331ae08e1.tgz","fileCount":99,"integrity":"sha512-E6USrH15oy6eVHN4Dw1oJKgFk7ZWSyxMuTV1B2KIPa1ZXR0DOOZkAqsDpEWTF7ZyyTm2RF/jSUPJ+AdLPnbzUw==","signatures":[{"sig":"MEUCIFUbhfz1AwHyAq8C8hyKGfhRgDB07NC8foFfMFtrKnORAiEA9WtHUrWtSuhUwHW5+pzkwq198APUKafPy1mJLsf76FY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":741624,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgmetkCRA9TVsSAnZWagAAS4MP/2Qu3DmzKUNquJW71HNm\nMaeIla0tMKxnLt6gx7VPsWVMiNmXeXG9ZiEBidJhWfVPBuosFWJ6fO5F8klo\nuOG0ZIRDBEp/Q1eLtwi/0YyZ6BZ1AUSMEoG6gVJulvxGXMXxbnIX58d7v2d9\nV8nk01BHhEQ5xOlfgmXs3svKD0UwVhsxiYaNV2IFyeQ+eMZNEmBH/T0kH3LD\n4MitygfgyDHKlQdFMqPz6UH+MILnboMdJ3H9pndVB5Ct3fhd//taF57m1UfN\nEMRyj+z2xio6IqZRt1gArcsaTzIHk5Nzkh8nAIc2IWRzhS6bBLOvAmLMUvW7\nE28D1NZBj5vrzpyiUPhTSIE4J62phcKRwJXRQ6A4uC3lzKTQiTrk2UOabJqy\nMRIf2EXTvVXged1JGYE/yu6Wm3aQJTV86F27LlnP5FjOJUOSKVBr921qZqKk\nWxrqIP7/oWkUdOIyQyJD0ctJBVxBxpwItuK712TMVwXSY9jYMWhA+qwzs0r2\n1Y15p7QWmYjCIoD/d4+9BDRFHmLxCcxEPRDHbsK5JVGiaRvcQqXwuCGWAIVw\n+pHowpM8+9sAw/B2fh2IZIg84ooHmkin7QNJ2IdN9KVP/onfqxbV6iTPPJBa\nsFVD8JuN3iwrcWYN8iwkfkJB/OEDYF7WHjXZOvmAC+WewqJj6RZtUosv3j36\n1dB7\r\n=CXev\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"15557152097c156d657a55a7a02fc47331ae08e1","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.2","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.15","@balena/jellyfish-logger":"^2.1.54","@balena/jellyfish-metrics":"^1.0.204","@balena/jellyfish-environment":"^4.1.12"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.0","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.138","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.95-renovate-eslint-plugin-jsdoc-34-x-15557152097c156d657a55a7a02fc47331ae08e1_1620700003529_0.17223131873043496","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.95-renovate-non-major-3599c46571b33c93f16ab960aff2fa2a190faf9d":{"name":"@balena/jellyfish-core","version":"2.13.95-renovate-non-major-3599c46571b33c93f16ab960aff2fa2a190faf9d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.95-renovate-non-major-3599c46571b33c93f16ab960aff2fa2a190faf9d","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d0a96ee6be36d1719ffeecf037965955060952c3","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.95-renovate-non-major-3599c46571b33c93f16ab960aff2fa2a190faf9d.tgz","fileCount":99,"integrity":"sha512-/vBTH4S4YxLS/jmXxo4Vr2+EeFJV/sRTPdq6UcHB2IxSvkLckqWpugkHrFtufv0eWKT4dLppNjDepeIwKtNCMg==","signatures":[{"sig":"MEQCICkgzfkQGqeHT/1mFYQJqMvyPQRs2pu5bwcB62OcdUGxAiACG2LwMiQTggxL9vBC7/L0rfkwMOp5l6SoaCFAJL3kjw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":741553,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgmjviCRA9TVsSAnZWagAAwOAP/RqTMsws+kdqmE/ujbnb\nRsFpDK5XF7V5ilXg/Ndx1IK+h5mxsV1NCm0i2EEZmk4kzadFrjRv8s4Ox794\nyuwkOqSXVwACwqagEH7AR840wx0jcv9SxrtvaCeCQQq9+I5472wUAyvPEYrw\nXKa3CGpi3PjOG69GLQ1jRVD0Tq1ZjVcEjGpKEuyE0x0rP6a0Gf+AQwRTX704\nSoFhwLZPWSnto7xrlGHM3UW/8llmkA+JymOsz9NgZtSISzBzMSmEvfxfPucd\nqSbjTMUt+7eUFd5vg7zZ2QoVCdhLlI1xnvotd/FVsTeRuQ7Zv0d0ZVzYLVUA\nkZt1Pyc1WkGGB4LzWC5PJyon6I2cjijbh2dn1h4MCc5wpTEzqPjGRXAXcdwL\nsOQyqzL3cyujN//o1vJHtYD6TyHCGs/0tCU07maDjJ9sXOGkwz2G3xJ4TdGP\nhyjso2+mS0gKFnWq+epj9/Bj8ELI7l6/PjB+3loQtb/RLDNlew/CeIT0+RUG\nay5v1Cqd0/N4dH0KTTLPTOOhnmyGMZBD0SmNQA6UDCAFF3h/yNhLZ5UfquYO\nX9Q7VnxF7MS/Q4L3wTo05Wqk1cv+ebYTOh/dSn6SSIi5adiUQDyM0xuDj6P/\n8JON56dY/y5fhLyfdhIkXJ9NXl9ausO34KYJRViBeyQsz0eWcSStZk9i1Jwn\nTxud\r\n=cMvl\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"3599c46571b33c93f16ab960aff2fa2a190faf9d","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.2","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.15","@balena/jellyfish-logger":"^2.1.55","@balena/jellyfish-metrics":"^1.0.204","@balena/jellyfish-environment":"^4.1.12"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.139","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.95-renovate-non-major-3599c46571b33c93f16ab960aff2fa2a190faf9d_1620720609991_0.9626774330459869","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.95-renovate-non-major-9f7ccebbb2212b5940cc1ed8aedce74f3f1f5c65":{"name":"@balena/jellyfish-core","version":"2.13.95-renovate-non-major-9f7ccebbb2212b5940cc1ed8aedce74f3f1f5c65","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.95-renovate-non-major-9f7ccebbb2212b5940cc1ed8aedce74f3f1f5c65","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"81be731abd60841593efaef001c9d477ab3d176c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.95-renovate-non-major-9f7ccebbb2212b5940cc1ed8aedce74f3f1f5c65.tgz","fileCount":99,"integrity":"sha512-fwjgiYpySZVFUJbcTKii6qD/S8RI5PXYhXs3X+xhZdAqmL7Sc19p6lSwcCpaTh9PX+7j9vDJiYlCsDZeHAjBJA==","signatures":[{"sig":"MEUCICkbPPJY6sZxm1sqrHDFsHGZiYCt/U+SxfZZ8U+XuMqUAiEAwXNVV9fh28DnzqG8WqY1f8TDyn53cEo3m1DUKDYM75c=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":741553,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgmj0rCRA9TVsSAnZWagAA9CEP/jUkJGBTYfb7kbfxjhN1\ni9OlEmoAozWReakXTrinFW81PRpGG6/YFLiU3zHR21lGSWWSCU3zWF8eia5t\ncyhMrp/+qh6quchY5+WB0PuDxURbjUs8FAtNr50mLItTceuJ/G28W0zj32pa\nixxWUllZAAfAquncqTxTSbMiZFb453jzyj60CfQKr9CD1+5R2mz8I5845Yp1\nwCMajWSW0m57I6VJ8PmDSHn0RDE34rvizhxgnpEKN7xaD93s9Ddvoss5hjCt\ncEj8uVMQn2d6S8fLj83+tcHCqROTOHySsKvpdax4jzkAkEL1/xa+JQfM+FjT\nH7cEZkiokr3USJjEFQjbVdiAW2ZJ9mcPkGubMv0CWvsr9nplNNCLM6R+RRMg\nGSfrHTERxHv5ELIu5TKctENmhaOsUQ9ter00veAvrH9NYSDndD3DD47gPvGf\nwA0SrqhswzDSYqRXgwAlM4FZnFagK9aQMlWWls3cQv0frr1YcKYIA/vNYt6a\nBu6EPDG9yMpJuD0FzuFiCZowddUSjoeOoXA2bcyQ6sWa4oolvkGVYQ29IGR3\nSfwGuY5lhhb6ekX8EzwiTmTwQA3DegiDJLiS2bTU5YcfxKfMUcjQsObijndK\n30oaCXVb+KIy6/dGPxTfFowOEF9BWs4d647/a1nTJg5EPhd3nlh5UsSnXh0a\nW51r\r\n=vClM\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"9f7ccebbb2212b5940cc1ed8aedce74f3f1f5c65","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.2","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.15","@balena/jellyfish-logger":"^2.1.55","@balena/jellyfish-metrics":"^1.0.204","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.139","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.95-renovate-non-major-9f7ccebbb2212b5940cc1ed8aedce74f3f1f5c65_1620720939191_0.017441056045648606","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.95-renovate-non-major-dadc554e9e52747bfacb95d9f5377b1a278d1fed":{"name":"@balena/jellyfish-core","version":"2.13.95-renovate-non-major-dadc554e9e52747bfacb95d9f5377b1a278d1fed","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.95-renovate-non-major-dadc554e9e52747bfacb95d9f5377b1a278d1fed","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"7d5dab58c9339ac07c436ceb9a1216be5b31a5c4","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.95-renovate-non-major-dadc554e9e52747bfacb95d9f5377b1a278d1fed.tgz","fileCount":99,"integrity":"sha512-b+8S8oXHBeAFMMKc0cAinh6K3EGsUs0fN2cvdUtK0l51hteiBpeyeHi5owMcW73SDyM1Gh2N3j8VVcswcl4MEQ==","signatures":[{"sig":"MEUCIQDlNFqoLCd+gYpkJmuIj20MwTrGNf0IPXdPjCFIDW4UaQIgYRmCpVT/7BXyrtB8mFSJzckZv3WSDFgGro4N6g8V0Ug=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":741553,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgmj4zCRA9TVsSAnZWagAAcQUP/RlbCXQ0Q1+OhbqD09DO\nNeG5WZwddZi5q7zZWV4rhtAippFcyVyaTTU9Te+rWw/46EbwE+wWMSiIpO8B\n2qzUa1BDkBB2mDPvqpm+VtM/X7qhud4CoYqR5R+LYv91fgvgnWZNe1U+xIhY\nQzSby8kIN64D7JSzAcRQrddYS9pTlft7v/uqggj3fj7NuQGhwaKLtUQJf4Gw\n5oAlIFvElMOP1YDG455CYwhIN8Hmk87FM0AqgPAmCjSxFXNhnGp1QWgCG72s\n3dAOXAACCDX143q0+ojjN8zP3b6qX6urDKh4AXz96466VtRccwaFMVEmecBB\n9CJnSrGWIFdxSn0WN79492jOQnvaKReCVmkTSkdqpJX5d0Qq33QUnvSdJl+/\ng74EY8dnFxHm2M4CefdSx9q0LsMxBvuAHuynOS8H0W4iurk9XAXpcNjOzxMV\nx+3d0TC/ZZtaW22tk97la2x2EQ0upWBLBKiusI5o3tW2vUg37z9h4Ooayqr+\nbU2XcOGVrqAoruFWl2bbbXotmoI17vDtr8nbYSsRvZSD1ss7BvEBdDG77s1o\nm3VGrFzNz0I753qSE3fenKU9NgHeQIra3Oo4/PnTxDi9Ex54vuOQ5hiXqJey\nZkOYiW/tK1zRwbrXwDPj2frupSKn/bxF1LzGSX8R+ODvIZDbCNx9DtubVa7M\naice\r\n=MEwi\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"dadc554e9e52747bfacb95d9f5377b1a278d1fed","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.2","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.16","@balena/jellyfish-logger":"^2.1.55","@balena/jellyfish-metrics":"^1.0.204","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.139","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.95-renovate-non-major-dadc554e9e52747bfacb95d9f5377b1a278d1fed_1620721203406_0.582178758514293","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.95":{"name":"@balena/jellyfish-core","version":"2.13.95","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.95","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"473e1d5dbfe6494704e697c1717ec9dd4e27ea9d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.95.tgz","fileCount":99,"integrity":"sha512-aLO/xtSaWMsrCt0RsD8rQ+Mf7YxoKWcLYd4B4w0EXoKA3dykh8yIAvcUMOVDDO2Hd8fhzVLm12R/T2Tnp8spKA==","signatures":[{"sig":"MEYCIQCcYoflVgG1+GEXlbDL7vAMDtEvS2T+Vk03LvYnpTqvAwIhAO1TjocF2KcPKAHYJ0eJrcQz3om7w/ovqhuoXPhAWgPz","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":741493,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgmj95CRA9TVsSAnZWagAApLoP/jsqn7EkNDDysQcaa3sJ\nXcs1SB36dxPGghEprgLY50mZZcCAVLEdEf5ZVxImUzQVHgHiAX92cXj6UouL\nvCrF5dBLmZcfrOUz/AXznJ/oFTPEm9x8rX8kludnrcNc1orjDij2/a1Pr75f\nXjHXUmgJOUk9J0pB139VkAWvHzJoBtv9frimx+ChHPCelq7Rl3/9J1AehGLP\nL3VhemYJY5wiW1RPP3154Z22KSxWtCHhJ6nshEN9kwgYFq4BJ3MwkmLRlstu\n6yl0U97FSi/9QNtnN2PjUoNll37376iazQ/bAD19Ve6kXgsshrvgdY59Lclt\nP4PYs5fnDvbV7wX9jEkikIl1AyuDCCE7apv7+rYGpn8AIDA2xw8WCaGFY8mQ\nbqiBds16SQzW+CNwziCmvtZ4zIJxX1Qq/TIEclQeTOaWB2xnBwTJhcWzNcFz\neT9tZSitguA70vQJSUqZQD7SsAY69kK9sfXkS42vVEoMZjTJJTIufUuXLIs7\n78wStq1mi/L9qL9RZU6AuFJUbGvPwPOLjeKGvlWdCWwbz2JKDPsYkhey2QP7\npgJ/xsGQofM4GrZs8FOmoHes5dLceVcHhIQlwSf8zlLVYA3ViSE94wj5gaHI\ntqgqwJyKp1Ej1NZVb09BAzfDr7OaqrkOP0uEy5OaG1v4NM1nCCeIZ8x2L2q+\njVCq\r\n=EnG7\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"5d9c63ec97538184785149300fc5e42291bb811b","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.2","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.16","@balena/jellyfish-logger":"^2.1.55","@balena/jellyfish-metrics":"^1.0.204","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^33.3.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.139","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.95_1620721529372_0.8933569339574559","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.96-renovate-eslint-plugin-jsdoc-34-x-f82d528215e0549834c8db50c19697b11d20b9e9":{"name":"@balena/jellyfish-core","version":"2.13.96-renovate-eslint-plugin-jsdoc-34-x-f82d528215e0549834c8db50c19697b11d20b9e9","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.96-renovate-eslint-plugin-jsdoc-34-x-f82d528215e0549834c8db50c19697b11d20b9e9","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"54a27c22bfa0984fa49792abf6bda869a91b2964","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.96-renovate-eslint-plugin-jsdoc-34-x-f82d528215e0549834c8db50c19697b11d20b9e9.tgz","fileCount":99,"integrity":"sha512-Vo/C75Fmgawv9LzPKGpbRWbvmdqdogT9+yUzv8/pL91Om2boqbjRZRQ0/6QSBjQES2ArmYfS8atVNY88UPv0Gw==","signatures":[{"sig":"MEUCIQCQBMMOt6gWL527vP/9djrtN//uIP1K81luT1QXJAq5DAIgRCPq44rQm0e4FVt4bJSWAtw9uNI8WHm/UsltjDB5BXE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":741975,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgmkBjCRA9TVsSAnZWagAAOuEP/3ma57DohhbHGvwnZbj8\nLf7lTSHzK19Na9ZHYG/EZs2f/8kdd9T+i8knN3cnaqmZ2aOS4UOdAsVx4vRF\n9Rutagys0S319AeqevP7HDbyUvepvF8DcsfnzDfy43cBU2hhPLpNY8/sm7Gf\nMHW7buARbh9K+0KlFHTI0mU8wm028k254sRowXjvZyAfMI8FCpm+Pas6DWgn\nKNNvXWqfLedkCvosEJgfVeo2d5O2O0jufkv8rjsi3815TuEIMj+lNF9hSN4h\neLKskbTQFMxzEJsspTq1rPqlfFGA9w99l2DsK4zSddcyaiZCz5mAZp1K0rW3\nyZDc+Jl1tfNudfWFlhQPSr0d+9/YU2P99MLjq2ltDPkgo/v6ZuObmKZyTsvo\noraV4t6WWiQ4+NPuFy8u+G0vTxKOcd4aYYSWQPXDp+KSf1hnz0olR2QvpWtv\nbI/J0KfOHsBTSf8JeT3QkRq0fdDgMCuh4+dpqlVYfPRgWYvBL3KO4iaeECa5\nUMkVQq/bdNH2Vn93qsIVVoFf5R8kQDuyAVr0wQLYbMegLdxqKdgKAXdxUxSC\nyw70YFd8IlkEaa+98eaWu2flf1n1Rxky+rXPBkNZxeTBq2eDIBK8vk87IwlS\notGomAZTAnsu2T2GvTl4B28n4gHvKn/gsaKiqITOgPWPjcfBfhOtE1tV+6oS\nyxZ+\r\n=gTJQ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f82d528215e0549834c8db50c19697b11d20b9e9","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.2","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.16","@balena/jellyfish-logger":"^2.1.55","@balena/jellyfish-metrics":"^1.0.204","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.0.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.139","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.96-renovate-eslint-plugin-jsdoc-34-x-f82d528215e0549834c8db50c19697b11d20b9e9_1620721763399_0.31724014998925787","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.96-renovate-eslint-plugin-jsdoc-34-x-be9c116d65a9a2612d494fd8e6322019b7ffcb74":{"name":"@balena/jellyfish-core","version":"2.13.96-renovate-eslint-plugin-jsdoc-34-x-be9c116d65a9a2612d494fd8e6322019b7ffcb74","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.96-renovate-eslint-plugin-jsdoc-34-x-be9c116d65a9a2612d494fd8e6322019b7ffcb74","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"714805a61f7c37eed8f82934970d671182cefdaa","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.96-renovate-eslint-plugin-jsdoc-34-x-be9c116d65a9a2612d494fd8e6322019b7ffcb74.tgz","fileCount":99,"integrity":"sha512-wcSrl6a+QOzHebRjyaavlZB7wJlstLlq4AUV0+DcxZ3iCb8xw901lBZFYNZn/N6axSSpk3HjFVqFv5HespNviw==","signatures":[{"sig":"MEQCIAGcVGnzURLB7ClwF+lI0s8b2E0wOVtSicoWGMTRHghOAiAbDkgAL6hS1um1/j5DV6FtS4Oi/8JSJwIAod6LNGnQRA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":741975,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgmo+GCRA9TVsSAnZWagAAgGcQAI1C6s9MNxTgOjWCw+lZ\nQ21x54HPXweH8myP9dZk0T6w3F8YxOC3KVD4eqf65F6dzPwCkDhJBWEnlCIE\nDWGTquO438X10zet22eJR/F8iVg7hzKhJs8L+bqQjrqkLWrJfppbMBXXirhl\nC3bYhvbV3/nqtk3UdS3KfzwTuvsi0GV06vIuZnwb9n/y2j8qNY8eCl9cA564\nR81Q4OYiGtEuEpVUHJAoamwOowHPFYyWcuPeT7g9NwKFb/LgIS/L26fHI/cs\nttgwYXX9Z8EiZr3sTucSZOuf0XQ3ucwa8/afojrArWh3TYMArPELghbxmtEA\ncovWEaTX6iPVtk8JAsn0eTZmiNow0Hk/xkRAI7Lr5CMFC6ipcTxLN+kA0hxM\nFB7dVaKelQ02qQdtjxFacgkO/OQOIwYAuVHR6VB6RHwnO+OLrifnse62hegs\nL6RKYwI+vZWPb6gTtawlF+JZtQukLX/dcQju0fGpMSPbZkXQ5LbIQMPm9xjp\nC8OxN4eg2mBaZIjNdie2mey+N1G1jSJBPgoPho+nMIhYdMFXQF68XXeKEjxo\nmwFnf8FK+NIJU2h/e2XHV1hpPVfwIHubsKWrVa0Bi7W7CdzFUuFcMIipDtiz\ndOqZpuszCjJjGWpTw99WRb4nMjUc0NpaIWrpzNtKMPbdN1DxjyTBHJv6wow7\nHTv5\r\n=br14\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"be9c116d65a9a2612d494fd8e6322019b7ffcb74","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.2","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.16","@balena/jellyfish-logger":"^2.1.55","@balena/jellyfish-metrics":"^1.0.204","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.0.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.139","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.96-renovate-eslint-plugin-jsdoc-34-x-be9c116d65a9a2612d494fd8e6322019b7ffcb74_1620742022159_0.6865691880615319","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.96":{"name":"@balena/jellyfish-core","version":"2.13.96","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.96","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"fb480f75b3fab2924c47598ffe0a24fd6a4991c7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.96.tgz","fileCount":99,"integrity":"sha512-dwYuwfw8trv7bmqKYsCuYySzC33X1xp+babVRX4b9hFv2n1w8liF8DtPq0GvLsq4x+kIhkovqQxwpxIsIkvMxw==","signatures":[{"sig":"MEQCIFnQNJbwfo71Y0Suk+jesVnR5oZdr4T9Njrkyvf4NWkxAiBzax0S2KSP8Tz/UUmIavNm4t2utrGpDOu6OD/0BM4wtw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":741900,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgmyDhCRA9TVsSAnZWagAAwVkP/0vOIvOlfqYPKXGqSoiO\n5ll2Qh+A2ynvW2mXHFvZwaSQ1JweIe3ymqzzsMIXxGlmBWG8klb45O5BZSo/\n52WDQaqUQa6ziQvh9WfGtEnPHW4SbfDKH0D38ARbLIuYVmFsJcagBoksV4q4\nBQq8lgnFxORv2g990q4gww2QD1PbE7ZWRKYsaikFyFyxvCNKAx8oEA5FRK7b\n1SdjlEhY1jxV/Vv/gNMBb3pCPBUkC/ieMNWDmhI0A29y1KXTafFp0AF0Fhps\nzjRfNhi6tMUYPsUICMC9QZoJ9K69G73UbJLRwRB4JF9Jh94PzYCejZ63u5VN\nAPCfaLP5B1Nrm9SZdVzV9gPriyhwBOPNbKWRQyGC89yuAwPMbnMpnP9+0Q+3\nm8ct6V51FYSvp0zkzX+M7p7mlpBNKNZ5qu8e7YJTy03zr+x36I0LF14ZiBQu\nS1WYlvwLhEjYTXHoMp3phc4eOLYeu/dt64xj1bQM3uCCcp8f6rtz67tX25Ig\niTrkyx6e5eUeJNmeSrmKGmRPJiLpxsb6jvkw31+2v3fBumkQiNSUp37aTWUr\n1IZR3wrtFFhKRxnleXKzXOz8MrOMfb7Pr/dFxIw8MhtyjEQnxX0CsS9b4cMn\ngimtmMGvjswRPZwmB7Q/5sNj80kDYLxcM1ZFrLu11V/6Mz6wzpXRo6leK0xK\nTqXP\r\n=Jv01\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"48cdb8fa092894060c93fbb4c3edeee0563b1375","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.2","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.16","@balena/jellyfish-logger":"^2.1.55","@balena/jellyfish-metrics":"^1.0.204","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.0.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.139","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.96_1620779233109_0.28456489202503565","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.97-renovate-non-major-75fe6bf5cc8309721f1eb1874565a2034ed22430":{"name":"@balena/jellyfish-core","version":"2.13.97-renovate-non-major-75fe6bf5cc8309721f1eb1874565a2034ed22430","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.97-renovate-non-major-75fe6bf5cc8309721f1eb1874565a2034ed22430","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b4d65717747e3cce2a54fcf4c5ec0a92c5c12637","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.97-renovate-non-major-75fe6bf5cc8309721f1eb1874565a2034ed22430.tgz","fileCount":99,"integrity":"sha512-pP0OXem+0ThPqok8u3kfTRIhoxEkSxTm3ddsOwQyityCo8pOb+2BhojmjyFQINN6HNmnLJK/BQ0PaerPskmp/Q==","signatures":[{"sig":"MEYCIQD+KY4r2DiqoFIicmfM5zrDEbTz674FsD8nxGRNh3SRtgIhAP+V7Tz3HMywDMgO/vLCJ+1A8GDcanK9qredWzQiHkZ9","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":742311,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgmyHoCRA9TVsSAnZWagAAOLQQAJn5bka8Xc8JHbDGbikt\nO22FMhqhGBDr+knlc/muSqjRu/u+fIpE9iS7YFB7x/iv45UcMbtsXoUKxFiD\nsQVHVy1UXzNyYpPQYSXVZ2UxImnBrjV3csWzTGuAla6jzRM/4KhIAVWs4miK\nr4CoUYuT4bJsKPaf/WsTXf63HCPJkR+cMX848kwo4cbG1L2R/Vuhm2xHq6K5\nRcy0t6aV7x9l6k+L7dlLvAQchXbYUQPelGPF+Kyk+FuCYdk3mPAsuaNwgKK/\nbtUq5Ds9GLfzFxR/u2CzKztdK55VmzFJXDF1unYvtRUYaL6gswpnS3nr/Fxm\nHoJ2Lk4qcKaxMyeVBxl2xPDIyzAQNLf0yX7e2X4t4V7AMUczC4J1RbrOH4Zk\n75+Cyd7dEWZ6jPGUfYSzVSJVpl7k6n970yQr7PxJKXBiD8nUXmPWPiEqwCJc\nVKDaT25/UyV9sSlwmbckzeeg5mUpcHpJf0DLN0aQg330DQBDBXSb1VTaunUX\nKMcEufA6sIBVakk6fCZrpnU7txcBSHuwdRKarMgSb9AhrkW792yppxdRDcnG\nI9N7NQz1T/NEcgZSz9psFaLIgUfAieXQwJGB2Ae7jHtW25y6ATgcD62Lg9Fr\nIMgZ0r4nVU39CRICIwfrGNwZB86engF/IGCjFwlOxhd1wQ7M5OJM1csoOGQa\nxZ34\r\n=LcAy\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"75fe6bf5cc8309721f1eb1874565a2034ed22430","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.16","@balena/jellyfish-logger":"^2.1.56","@balena/jellyfish-metrics":"^1.0.204","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.0.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.139","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.97-renovate-non-major-75fe6bf5cc8309721f1eb1874565a2034ed22430_1620779496165_0.6463514736760199","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.97-renovate-non-major-51e3baa398cf2d5eaf25e3c1c8f87c69f56d6994":{"name":"@balena/jellyfish-core","version":"2.13.97-renovate-non-major-51e3baa398cf2d5eaf25e3c1c8f87c69f56d6994","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.97-renovate-non-major-51e3baa398cf2d5eaf25e3c1c8f87c69f56d6994","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a75069ae74bd44e78eb8947863338d3cd41aebc8","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.97-renovate-non-major-51e3baa398cf2d5eaf25e3c1c8f87c69f56d6994.tgz","fileCount":99,"integrity":"sha512-5qqTSZfw+j14/KPwUVk05k5iZKc9rgCMyt4ve7odHgEbCd+kFL7yggoeGHfi7zVOQnLHeOf+0IsPFYF2/WhgDA==","signatures":[{"sig":"MEYCIQDTs0fabJprnGGTHlYKiEhxxO3SFdrz8xuyhzciWWlzFwIhAItHM6NlIrxXHWAfwurj4/eU/KWDQjMkgdaReMiNP7mK","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":742311,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgmyJvCRA9TVsSAnZWagAACAcP/ihgKLoU8TEt0TSENWqi\nvbVINnehw2XAVC5Mb4G50/7gQEPXhxjj9F52btOGgKGjHRsefAYrhNWzWh6j\nxjlnvkVbhFBfIiQ8BHyL9ruGXqNcXtjJrM5+b0GT0qarjrJaY5dV8j27hGr6\nGceKExkdRYo9tAf0JA4t2CK1c1awM/02xnntlIAP4iPrDtX+pUHaolLRxtp8\nzz+DxPuh7S7HI+l+J0TAN8k8c2lelxvFOde8fW093RlBru0FsA1VulXHR2Wf\n1O1Al9kiVmH9Rh42omncmPyNQAf/HHUqs9Ky72G0UAhNKesgdoe+tnZPk2WT\nNoMn8GEOzu2E3SwjCRykaAuLA1fh0B76iFzkHg63W/6jRYcyDNijvPVqC3vI\naUCCNbeZPGQL5jDRUL8lV2xNausWMb4UVHjEmf1sOM4BCFDwjL9Gsfi1YZBG\nRWj52f+MR74wwkcbf7cv+IThmWslpCpIu4EFRsZOBT23lyRj7TgpzP+SHVso\nWLQ2JMfLtNE3rN2dvi4WEmSL0yosUXaE+xsq1AxL019KrOg17+wQM6gEhj7j\nh9bM9L2pO8+r13N3eYaZQaO0RRSgCVEy6GqKnJ+qNIbR+SGNaUFptlGGMI8e\n+d7ycgO9ZAg63qvkxxYuKuXboonM5wrIqsQpiB8H5ut5o1mX/3ihRQm4JS/K\nhHI5\r\n=D6GV\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"51e3baa398cf2d5eaf25e3c1c8f87c69f56d6994","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.16","@balena/jellyfish-logger":"^2.1.56","@balena/jellyfish-metrics":"^1.0.205","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.0.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.140","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.97-renovate-non-major-51e3baa398cf2d5eaf25e3c1c8f87c69f56d6994_1620779631217_0.5609158426463898","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.97":{"name":"@balena/jellyfish-core","version":"2.13.97","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.97","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"7b1a97f1e591e06562fe6376fc8fdd3f2b181922","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.97.tgz","fileCount":99,"integrity":"sha512-9zIhi+YAIcJZbLzVIUFkhslen7VWaMAgb0pkYj5af3sCM6RB/b33XuKB2vU3SoGqrbjEXW/BFOxAoawZIFEzdQ==","signatures":[{"sig":"MEYCIQDPV1P1QH4wvrt7p4D+wfz2T2J/9GTRS8044yG5VjyNXgIhALaoybUV3pbs4HZ3U9NNdoLkEZbx20jI9epbHah7JmcX","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":742251,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgmySbCRA9TVsSAnZWagAAOFIP/R7ewGl9KXwZI5LseKfu\nYNJXIygZ6Fn4Jdm/H/Jg1ql73pa/GyQMX1Pc3ehO5Qsct1L+DUUdwbI4vOIT\nf5hOD/97jMFeiEitmFpMTA/HY6GqbXw80z4nTUmC1nyKTyJRMJN6xtCbK0Hk\nXOniJieArNkXPd/tU4LXGK9ZddVWz/YCS9xnCvRj7U1uM5NCwWhfdzoCRGGd\n2s4gBwhsWKy+PX5glggfB1ABXGLcWZF+DZZDnhpOKSdk2sBnBqTYrI6gkXNe\nP//d9IZ9Kt7SqcaE7zD07NEH2Ai092V8pjruYUVaYWchiUTW0n0WIhrQyAcd\nf+gJjrz6Fk+xEvCsbt3n1SoTwAOhdiVJ6y9z6iuK3dJYSBokl2TbAYwgniqa\naAucrSIk6OgijZtP+FX/EvoS8WlEAhXzfMQRNN/ItqDgmJ51ppgPxdsDXW1v\nJhmnaHKXiDIOwc5w7BHyeYBz2VcCrIUZ9O/ZL/4PrA92+TJ8LuNk0hX/w/Vh\nYA12O+/ZIwli8+fTabvJLTF0u+AAULfOwJ0zzb+7DuoKhlPPNwp0vgPoNwqj\nOVNkhHc//ZJ+tIxiJWJqPNB9ZM639fPTcRcRFDo8bFkkN0KH0wtKFjsTR5bl\ni8jLuPYkdL9fOt3uFqT5BjZK6yppenNgMGY434zeYzMc8qjY/x4gP9Lh/D/4\ngbDX\r\n=3pHv\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b1c74673e26eece9df5122723ff74b5142db8d52","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.16","@balena/jellyfish-logger":"^2.1.56","@balena/jellyfish-metrics":"^1.0.205","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.0.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.140","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.97_1620780186662_0.3686004976992716","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.98-renovate-non-major-220e2204ae7b9c9859c07a103e9c9d815352ea5a":{"name":"@balena/jellyfish-core","version":"2.13.98-renovate-non-major-220e2204ae7b9c9859c07a103e9c9d815352ea5a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.98-renovate-non-major-220e2204ae7b9c9859c07a103e9c9d815352ea5a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"def07ff84e383009881657141e484e1c3cf9060c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.98-renovate-non-major-220e2204ae7b9c9859c07a103e9c9d815352ea5a.tgz","fileCount":99,"integrity":"sha512-Opc5YO/CY1WrpkciQQMc49ronmcLoyyqKdNeLMGqSgw+wFUZ4w2Wg/XV6mtoSk3QPBTaOqa+OTf7EREvPR8shg==","signatures":[{"sig":"MEQCICA3x6wxWV8bLYsq2Wqpo1sBvNlft/xZ7HiOBFrZfdZ3AiAzc60Ufry3sgRFq7TGsr6lJTWrvq9YTUJrrbnXOmWfPw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":742726,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgmzkGCRA9TVsSAnZWagAA+GoQAJYoIGanxFhgwaJE9w/j\nWA86CvHYBU14L2YD9pLpC9rmwV8EvkqKGEn/QBhJJYf753wDl0r3kTw2lcZV\n815HleV3ICaVg3Sb876AbLqPb3rkfJhv0zddo7zDeuE1M3+gV39VhTRhv0Pz\nJwXI6Nkf681l9HfJ+WqR/ted+i+eTeiuusAp/839j9rdtu1G2Pf+Pg0uY7rd\nfaHa9YUIC70x+rA+/hXI2QhiAURD7pQxnOOvQGUUZQatEfaTjs0GF3KXyGrj\nCznoVTe9qGv546HZPne1JYH/0LlP36kFNOsI497YGdlRlCsnrffs8wYBIwXN\njDbehwPEqmohb2JnZrgDp91EMXBnskzrH/JOtIUHcj4YNkGAQuQbX/OOBbVt\nSMcccRnzzOvPpLA2OTEs0Cs0JcKXEapw9tcpDSq37ncWnF1pBI7P8FZ6hiaz\ncgFpLrRN69JfOv4vOboxlx5Lab8z2q3zeS2WCMQRitMmRR+aTfeJiu5ad9sY\nxJ9LVW4lSE+QNtFfPRLxG4U4vzCCjYllSqok3mx6kOaVyE4eCG+AR8HAAjhA\nnai8xDJGMWsshS4nO/tRYeqxiiPJF3aUEl1UyOM5S8bNcBKIv47VAnkG/uo6\nLnI3qpu0/JbnTw7XFeTatCj4YeatGWzD2nT9Hwj5U1o4/uzWy1NhcH7xlrUk\nNPe0\r\n=iffJ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"220e2204ae7b9c9859c07a103e9c9d815352ea5a","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.16","@balena/jellyfish-logger":"^2.1.56","@balena/jellyfish-metrics":"^1.0.205","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.0.2","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.140","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.98-renovate-non-major-220e2204ae7b9c9859c07a103e9c9d815352ea5a_1620785414304_0.07088148183978693","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.98":{"name":"@balena/jellyfish-core","version":"2.13.98","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.98","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"cdf0c275288af90c148c135d7300efdab7c822ad","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.98.tgz","fileCount":99,"integrity":"sha512-MoAkOF4QLb02yvfApLDPBXq3gVtC+3PmCOAUsh0RQdjCWKQ/dpvz8zBYQ88l28NVH4U6bCfRd1qSGUbV4n0b9Q==","signatures":[{"sig":"MEUCIQD7jCw+jRoVyA2Gi+cV5/UH1CEXy7yAlWMhd+0b/y3gXQIgZ7vwvoh6PTW7hjOcOHjuul0a+xNLzl7lDhdjB3dH91A=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":742666,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgmztGCRA9TVsSAnZWagAAKiMP/1WCHHz1a7Hc0fh0k5zm\n3OO5HNdTS6VUIP+btD1YeMOPAtoC3Zs3KvXZdDZwRH73IPQ/IpCI+zuvYDgT\n/qTs8V1k1JwjVnanU1pBEObOQoscih8TwYj6LCVsHNKzUCZsti3f4ZW4ItW3\ne2xgWepRvSzTuiSh6xiY5+Muql7Xs5FSdTTWsxxyDgreZ9F6PEZ491C5zcmu\nNZdVU5mF5zgES5YZLl3mqGaySudloOTwjQO4mdh8FIXURDlRFShjrdxlDKT7\nRLIxZa+Kj/uWGtVBdG/zfCXZgeV03ZOseFI8NvH6Ic0V+/Ervo2guUVW27zh\nATDQ7Vkja4khnQuC2v7L08asjpjkWLqjrXd52TeeIiW9ukku9moPEBQklX1G\nmA9pQtx4D/+/9dzbwaucRIRM3bYfWzOrOVz8tRa21fAY7BLWQ4/RJvlO7mR+\nD3GwGgpF4vkvsnCcC+B0amWoi84/EjHmsHWkOTIIioPZyWdy6OKCdiQEPtBj\naMLlswIBHj930YvUz2QsjHhe3vFlvYcD+w/H4xwpdsb/O3IEVCbv+qEOnxa8\n+h1MKeem6DDinsylqo9gQDzBAbTMtzuF2S1GAopoacmkOKKHLDeNTuV2Ogfc\nKAzwe1HhaSNJLDmqkXKWPMDrk+Fp8ul/eCX5v4pNI+kzqhVZbtb/AdgORwXs\nuZIN\r\n=qEqw\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"cf906e58fc89a987e56c3b74431aaad72460cabf","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.16","@balena/jellyfish-logger":"^2.1.56","@balena/jellyfish-metrics":"^1.0.205","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.0.2","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.140","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.98_1620785990315_0.6928949515693648","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.99-renovate-non-major-135c1d581445d4617672954c2f4826047eb11f3b":{"name":"@balena/jellyfish-core","version":"2.13.99-renovate-non-major-135c1d581445d4617672954c2f4826047eb11f3b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.99-renovate-non-major-135c1d581445d4617672954c2f4826047eb11f3b","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"35e4f5dd1ad0074ab560d8b7c01cee0b2046755a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.99-renovate-non-major-135c1d581445d4617672954c2f4826047eb11f3b.tgz","fileCount":99,"integrity":"sha512-9IbugKWkP8ZHQkTqNV03pUSTVX/JNtmDzxKG8jGt4H78pHrk3yLgIMdWfV50nkvbiIK173M6T3rlPfoRtPJksg==","signatures":[{"sig":"MEUCIQDjRLvXCxatXQ2SdCJidNy3hzSaioISkfjzzgIeWXbHqQIgV+f6gvgtVkN9gMvxiRvfNAsSVZlWCiHkYHP756XRTmU=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":743149,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgmzxGCRA9TVsSAnZWagAAhGsQAJsnhtkan0ygqV7eZm3a\n/NM8pwvs1SkIbxeAVXSW3o1VSYASvOpBWVh4IeoxBySRY7UD0Ogv9W76B91a\nnj+cZpSO7rOzTlPjetusX0QZkdre9pCEPx0dEYR2Lgp3AOpt5Tj9g62IrXmf\nD5/aQ1+kZ3tgjmYWuD2kk7J6iUxI8kQfmpSrepxYjUQBGloeu1g/ixQwWwAR\npLDJhwf5AiJBAo84XHSckPSA1BcVGjRt5Y4ji2UKlIda9uuYCw2dKem8zSuC\ndT5CvcBaoV3E2u60/+rTV8nz9T1nBoLcjjUX8hseRNglObFvaHWNi2Nn3uYG\nrSNSZ+Mb5HYh7f52L1+7xhCH0owR4dgbYW9h4pqaVjvL5eTiQX5slTFN6YgC\nAhCaRRlGQeGxlKsWKWXJnz9A/jqYHUGIKgKl9x5SeMOh0IWWR4KEM1/mROUy\nSvxSqKqv1nFJa9WeQc/RCCNuqUV1FjZIqOzLeiuPdNAtDF8n4xpu3Fk4czjW\n2TP571jL00D36abJVIR6KHKNSTzwCHLD5Z1WNimB2TBSD4wL04oQDnwfpO7B\nC4saRDpe1/hndJbw3LU/eE7Y31w30uqZwoug1bwZCfpKsuv9H2kfzYlxPjZV\nqH/J0ZlmP633Lj55kZzQCfnrHAreFm01GBZgYx/NplIkypdO6Myp8VFcZVqI\nNB9U\r\n=StCH\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"135c1d581445d4617672954c2f4826047eb11f3b","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.16","@balena/jellyfish-logger":"^2.1.56","@balena/jellyfish-metrics":"^1.0.205","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.0.2","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.141","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.99-renovate-non-major-135c1d581445d4617672954c2f4826047eb11f3b_1620786246252_0.42231405876964656","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.99":{"name":"@balena/jellyfish-core","version":"2.13.99","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.99","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6ac58913e01a98239b4bbd197331959cf1df3887","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.99.tgz","fileCount":99,"integrity":"sha512-qAWv//AWL6D5Ik5kzd+m7eocRStRGEhNA+grbH2u7OGzgdKp99rE4NLz6ziR686EQKSbQhUwYmTiVuxZmXBFEw==","signatures":[{"sig":"MEUCIQDCiyhS1X6H1/qEWj6QtaqXMeLbmZcJNXaSZ3pXY695jgIga7k+yIkyPoqq4o7ArTEQtq9+7JYS5Zj9NGZX/CpdZGw=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":743089,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgmz5RCRA9TVsSAnZWagAAWTMP/1FSCfaC3JXCSzrueODP\nemfLNWcza2CS2b2Ri4ZozPLphh/n2WeJM+qW7Yr834IgKduu9VfOVh3GnjkY\nxpcQzH+CAbYryTAz3GhHPtcrqfyU0ZMKclnx9Bx3WyPhKbOjSDXZdyE5/sHf\nzKzBwgAlMUmEBtTOmVAC55vpCVEWIHKnne8fNCjSpNqXxM2dkSLowWZrNhUP\nhFPogU+HSk+QbeWixTqrOSgKe1b9Pa/J/FBlqsaI6LCQl0yR7vRxaP3ZihM0\n0H2OEjvlShlihko/TZyqQQN99JzZuPMGpWmpfb2HmxpJyJiujDn2AYpO5Jav\nh5ChNd5b1ruv8XGArOr/XGHPiaOHBOAsbHEaZEJHxskArSJXP90RZJLHrE2S\n/ilMYcKq5aGtcQFgw7z+LH+GpWlMPu5xdOIGJVaPv3NKoUHc9w3SC3psE9sn\nkEbfTbSjQ11XGN4f9zhUlq6JYneu9Tf50rL+6cAWMrsHw6I42Yl752G1d3mk\nBcdH+l6qwBlkehOExybdzxj1Ni4g1t1Id+U8BpOlOe/pbVtxM+MOCnMlhH0m\n9pTGH1dx9NOAfMVFwdmgAj1DZeugbsJHfCq9+RbbEPmXaC1WYJvwN2WN7go1\ntiJkGiMV5VEPK89MCCnoVTWqSIdpjLKRPNMp38Ok9wVv6kih9gz4ZuDn2Z5Y\n25EW\r\n=v2Eh\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f3df5319d7a02055e9063ea59c05d76afa0bca28","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.16","@balena/jellyfish-logger":"^2.1.56","@balena/jellyfish-metrics":"^1.0.205","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.0.2","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.141","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.99_1620786768422_0.7276523808668991","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.100-renovate-non-major-456a923d82845d3428b575b7d5badf490c083326":{"name":"@balena/jellyfish-core","version":"2.13.100-renovate-non-major-456a923d82845d3428b575b7d5badf490c083326","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.100-renovate-non-major-456a923d82845d3428b575b7d5badf490c083326","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"7f201685cda975188d41321166b2040b50aede43","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.100-renovate-non-major-456a923d82845d3428b575b7d5badf490c083326.tgz","fileCount":99,"integrity":"sha512-6hvnbXl1rypMKP9n0s9tMDZC082HinKGoRXUe+tIZiAnuLLrlE2XHQHWCblOpzalpg4r3ZVOYbnpaKFfE4vEkw==","signatures":[{"sig":"MEUCIAqm/u9RFD/8nlHaDIvuQ9A/B8+fhe6ibf4sC1J8PpOTAiEAmh3J7mey6p1rXaLPZn7cRKgOM/skDhDZSn2cH5bx/jE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":743577,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgm4cTCRA9TVsSAnZWagAAotMP/RkgwsVLl6kjhSCM6Oj7\nWJlDyWPciXQUYU2n+KAANDt2Kic+UIa8MaVUZ8PDgYzxEHQNtFUxn61oNFKe\n8h9vaZ6YLFrBOUKtoen0aRmnqifOseqppUDg8eDMO2FhReZCUTQ1Qo/D+s1k\nC/MVpm7FK9KyLNGrC3l5MPgWx1bpAPwuTs8qmUNV1namjuJk80NOcp/HmrGN\nfG9wbc2gjtqGVb0U/FRIJXJ0b231e6XvhHUI1EYsMDYtRu8XLE2XHBLvfVY4\na2D1zCOkayUG1rMMfNmflYnbi5Xz8rmPMY/F0ecGofltzStV0OxDgpYB5q8V\nq4LJW+Fvroy3vZXk6BOtlK4Bqk1oySSHJ5+J/IGLDP6J6bBG3jifbP+OxflD\nJpI6a2ziC/+EUyznXSbvqLqqygr6JD0UcSthHE6KUO8HM25eQLFQfUtJtX3Z\nDN6t1lGlildD9sa0GAxrf0g3igLKU5wwz54HPZkpqebz68pmj0L+bhYNTWNR\nv/J1zcNuMIUZcYucru8qZ4vv2mkXYhrMufPZvU+CmywfBFo9CfPyrZF4RNMC\nQgmJ6840dg9/7KsdMPrbF0938dryU5uCzGCmn6bToZkIjH/XxqsQvXcMTg9U\n19Rnv3hJKxCGLV4fSRnAmlHzyTVsbz9FlcMlHIFvUyKkTrEITubBBDPkRTTs\nBzNZ\r\n=tICX\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"456a923d82845d3428b575b7d5badf490c083326","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.16","@balena/jellyfish-logger":"^2.1.57","@balena/jellyfish-metrics":"^1.0.205","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.0.2","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.141","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.100-renovate-non-major-456a923d82845d3428b575b7d5badf490c083326_1620805395385_0.7965514342144384","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.100-renovate-non-major-13e26c30f2b708d4f47568f142ffd362199fadc0":{"name":"@balena/jellyfish-core","version":"2.13.100-renovate-non-major-13e26c30f2b708d4f47568f142ffd362199fadc0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.100-renovate-non-major-13e26c30f2b708d4f47568f142ffd362199fadc0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0dfffff841e391e61f869a6687555cbd0ea4da23","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.100-renovate-non-major-13e26c30f2b708d4f47568f142ffd362199fadc0.tgz","fileCount":99,"integrity":"sha512-wzeAs3WyyYrwg/bISqlnUijaK23qwP6An+mxPFi9pukLIAUO5CEku6Ex+PfuVRA6ndXVHQk1iFmhc287UHRAOA==","signatures":[{"sig":"MEUCIQDS1+oIvQubFnS1wfpgjwA8zZEQwP6WbtRTxCUrwwKm2gIgduQwKsWyQTC6jXrVH9JVWHEsT2Q3IWvoPdqIbPRJNmk=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":743503,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgm5lACRA9TVsSAnZWagAAJ44P/iyces7NejNbLt46eU0c\nvUYxh0eOOe9i0vINHyJdP9gvZx+jQ4zKjy0lu5hPNBdY+dH518S3c2vz0lIN\nDLlf4O157xu5EmqMkABWlwYSeuKnMmCPJjygjKjaB9aCj5E5+a2eWVKMk59c\nQJmtIwZ+/klK/z71WHD/6+uLqpf5J/FmA1hYJBhFSv5cpy70ggz89WjtQhe0\nkXg4M3c+s4kV6B7m92+9wExl449rxsMfG2lIYog8WdKHYbuaG2Gly/hvSa/5\nfVMVAuh7bc8V7PgZHFoV8P2L8bjYHVMFW7UuBpSdOCgB+3/Jl1GAxU2Ev+Ti\nS8HTrzPZmBfWYalm0Yxl48U/5zh93gK/R99999KQ4mvThEGNssNTdBS9ZBCQ\nfvKFIRCL6jvctWXcB7qIyDBQrAc8JBa2uvOVMsiUUKDDzn26Rk1vMTsVn7YV\n3cAHhrByk6v5tdR8YSuiDAaj00oiQYIXk2uAThJJFkwBiMcn9SH46IfNNIMi\nSLOoyPSCN9FPTcvEoD9xRYHsghdfZDzV5l5FAtuOMSgBvKzyL4oqOkpAi/la\n/zc10uzVFNt6LwK2wUBpTSnxYtPSNAV9gr9TeLJ+H3t9rYyzZUgrBNs2L9e8\nR6WNuH5nUGh/CK54YyNpm6xN/hiwkSBZsbIN+LoBzdsJqzHvVqOLqTW8D66O\nyD/u\r\n=FSzO\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"13e26c30f2b708d4f47568f142ffd362199fadc0","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.17","@balena/jellyfish-logger":"^2.1.57","@balena/jellyfish-metrics":"^1.0.206","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.0.2","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.141","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.100-renovate-non-major-13e26c30f2b708d4f47568f142ffd362199fadc0_1620810048531_0.29060944712918246","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.100":{"name":"@balena/jellyfish-core","version":"2.13.100","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.100","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5e94b0f10cffb9d7d17e0d93c32fa53c21045aea","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.100.tgz","fileCount":99,"integrity":"sha512-ZQd1ni2mBoj8E8HZbQ1BRuDCWML37D65/TkdQdVJmyFhk2JOpfUpDJA9SpHV+1Y5Iz7eTyRhmn+cKsn7qYD4hw==","signatures":[{"sig":"MEUCIQDe0gAFKFCU9IgRjkRnlAqv1r5v1tZqQ0X/mK7Kk8pUiAIgdVP4mmAAGKo1XgUgEXLgkDB8pTg09ddzd5VKxofsL9k=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":743443,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgm5p+CRA9TVsSAnZWagAAOTkP/0FXQbjHWDMvkphrHzsY\nhwrI52j94rvY1xiDH/0JTPrgmr+NpBbT6e+9rUMKOOnVektK4F+WBSm1OhiM\ne9DCm/3mA33llYPrblt0KV89SIbJ3aJaxJLqWnytx7lJ5eTFt2y72mBHTfZY\n2N7NgBiTggqOVGtzUJstugFyaex4j1KuoGzmKHX+RO4iMglDwoYYzzdxh1yo\nAEAi1u17/P9ZbghSr2zB2ilx9Dzd1GEQnVaFIOZTzerL0fxpb9MNgcmbOyzV\nK+Ug+4Fg/4GEgOo6W6MeU+6ZVGe3bfHrr351W13gklX5E1qdmVh2b/HYbpXL\n7/45/BrrjBL0YZZ1tY4gxO4OOPiSTI6KPWJZDS/ztjTr2j16cuFSiukDDNKc\nQdEab2YDfEOtG7Bedn8fls4o4XGPN9Zr3RdM9ivTGU/NuMz89o28j2cEI8tL\nwTClpkAlhLHjDVx6GXz3QQGitZMqK2cM0+0N4QhbgRIROT/whSeAxQMamKYR\nDOn6SGZDKroqvPeU5HQ7gvryPap2st+McPCN04QmPikSVKGcIpkJdgZERSLZ\ntT4xZnVjtjzFmPNBi2lmBaYNv8EhgU6zOVLxjxWMEyesMdDdWAHkI8vj2W62\n96f2qLrKxcYkHcdS4AWXNBfwlyiwITRRsXhuKVMEHqfqhPoLhPu/HkArwXnL\nIpho\r\n=30dq\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7c79e6f56620c48f65dc51667631cefb6f363f42","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.17","@balena/jellyfish-logger":"^2.1.57","@balena/jellyfish-metrics":"^1.0.206","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.0.2","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.141","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.100_1620810365539_0.4165417981371706","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.101-renovate-non-major-fbf266a39e62a646aba519931c92f4cd6c6282b1":{"name":"@balena/jellyfish-core","version":"2.13.101-renovate-non-major-fbf266a39e62a646aba519931c92f4cd6c6282b1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.101-renovate-non-major-fbf266a39e62a646aba519931c92f4cd6c6282b1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4f1555051fbb5197d0a028079649c18599c93a89","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.101-renovate-non-major-fbf266a39e62a646aba519931c92f4cd6c6282b1.tgz","fileCount":99,"integrity":"sha512-x3YyiLg4rwaaUPbPxi0eoVWam7cuqTnziSvz4HAN3mDrgRL+v9REJekeOpGn395PIoJVqwm6QPhBNFG9kKTdMg==","signatures":[{"sig":"MEYCIQCQnwJ8hWtvHYDxSo3IMhOpyI1jKBTcYWM9Xv+S+N1AjAIhALxFP2T9nxj9OmlvI1IE5R+ORSYXqYTJS2JIuWftmbFJ","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":743930,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgm6MJCRA9TVsSAnZWagAAy8gQAId4OLHdIyoasaclfUJI\n0FZ/UtG+f4CUBvNywubowHtvnm2MCSsF/4K1qtP0chb/x5AnG818wlCkiRwa\nW+SRyMu13WqH1qQo214T0CFm+EJqeYUDSk0nBJxqayuJ9MJNUSeERsdjwtId\nOG1c83Iu/VF8Sh9saRbQSvop21kX0wa7Y6gemagY6Q2NbKicjVLf5z2QlR8Z\nJPM5XMoTSxfgAGD6ynCU4xg8WFLoUnPp4ppj9/m/JbwXCfUC/z2qq2eBiO6M\nX3SIYSQnYDfmVafbT5rR9McgZ8f8FyNCp9oVgMTtwu8JbOAQtOxKXnCwaFcs\n8tv4IJM/jTT+kfM82rK7txn558TnTLMxRmLANyCFlnfMtRViyAXlHSetQfKj\nAIFGBdP43SKQrG7nkesGyHWVJ+GXidbMWygm4m5j71fcdw39DXYDbTa7uXsS\n3ADK/bK4Qrau3Uo3Y0xtkkL5IcF6RTUmrlITKhOGaUIPpnUlhIJs8+rVlQVv\naM0N6Jn8DXqsxVktP0pp/f555S5A9DvUNUdUxE4/ZkF4masAroWROvj6brWI\n8lKOjUp7Nab8qhBGSn51vO9zRvjPKTtk4rQTHYtMT4NVER9Yjob0tQIsFilD\n0Xz6jc0FM3r0bap/BSCAo1G042CnbZQN5plF+wPxBKiOwmoEunAd8VI/rOfp\nc9be\r\n=730F\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"fbf266a39e62a646aba519931c92f4cd6c6282b1","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.17","@balena/jellyfish-logger":"^2.1.58","@balena/jellyfish-metrics":"^1.0.206","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.0.2","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.141","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.101-renovate-non-major-fbf266a39e62a646aba519931c92f4cd6c6282b1_1620812553245_0.08124783758377685","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.101":{"name":"@balena/jellyfish-core","version":"2.13.101","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.101","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"46088e2c116d414e115a4057c423209403a8f0eb","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.101.tgz","fileCount":99,"integrity":"sha512-KSsRsFclkevn/F+vVhQvJ9z8vdYZJUZVrnl4wLvY33DJalAfujMJlwAmdHUXX055IMENPVv6eHVXsfXT8qvAUA==","signatures":[{"sig":"MEUCIQDI+fjo02pM43XLaz+dlXsFHyESIqR8bwYlDZEmAy8urAIgPxbXRsi9U+EeNRwagXLWMDceBzliGqFqIwD4+1OGGoA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":743870,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgm6RzCRA9TVsSAnZWagAA4JAP/19b7jOq6HP4OzVRkofa\nVqbSYWdy8POjnFFOkyq++8nNG0D9HIm1emBOy+MFXb7B8GskuBKQjw/XbVTW\ngPxyrYMLAht0UV5ZTIzPT+1evW291mcooa2Ey4Leb2TKAWpk09txTDG9bmF6\n32v34IlPZoxyqTQWzWt9q39HFtdB6wVPvk0n+pmrewnPSHbHICmz0PQbwKZz\nWLdjXLJTyyUIEHcb2tT2u5q4rxVjf8DyijLxcZkdPxLsXXyJVCZmhoQ45GYT\ngasC2nb/C2XPQZGIis6lb4Q1zq1J002gdSO++a01d+clGJqgckc9+AEkb+ug\nrrQwb9lCZtjWIOUgwTxw+Na5BTMrsxgHLQF9U3e1u45VL8mXf5lUXhXOyzxG\nROc2Ej8cnpBLb+focB6HwNMSBc1wkMySt+S3Zy4HzZt+KjoiA/YU/+0xH7KF\nbP7SwHB9m2P83A+Zy3ZV/unzw5aFbDNUyXS9nSBuSXlYcaw2B/oTRCJWZe5T\nyJrDvLh+RGgGN6JDi3+EHh42yI9vRhAYZvBCsIcXZ8jTOrx/tECTPsFL7I+4\n7pWNF8U64tj8PwJDS8AYyidoTrcQlM99k4yQ1bt+S9d0MVGPD10KVvaFn9Ti\nRutLu8AXlmArh1+6HNwMMfPJf02CODduzMoPzzdSKb1RuIFdiP+l0Qdgbca1\nf3BQ\r\n=7H+r\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f9d5a40c72a7845c318302908fdfc621a69a3d57","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.17","@balena/jellyfish-logger":"^2.1.58","@balena/jellyfish-metrics":"^1.0.206","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.0.2","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.141","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.101_1620812914985_0.31029853140051666","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.102-renovate-non-major-e775f86ac59b9ce1b4a560d26cb7e11499d89503":{"name":"@balena/jellyfish-core","version":"2.13.102-renovate-non-major-e775f86ac59b9ce1b4a560d26cb7e11499d89503","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.102-renovate-non-major-e775f86ac59b9ce1b4a560d26cb7e11499d89503","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6df3d40b8185240237c66b061f2079dceb9b8bb6","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.102-renovate-non-major-e775f86ac59b9ce1b4a560d26cb7e11499d89503.tgz","fileCount":99,"integrity":"sha512-hiEmVQCLDBRhwM4AtcL9bN1gFH1onFw1rge2t6sdj3v7QdAYLlCeernxJ3BIOmZS/ZW/D1zS6oXxDl43Db8rZg==","signatures":[{"sig":"MEYCIQCgbIRuNLbXNvXeNaWsnkQ20NbvbtEoR62tobNxqbxswQIhAJmIVc55O9brRp4Kb7zs/TUrkUp79P540yChA2KoR2oH","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":744361,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgm7csCRA9TVsSAnZWagAAgwQP/RGieVogWEoJhDYvTAX5\nph4zvxN0XOG8uKzCcLmleiKDMm65umVkSdWduxC1eGGDrKegTY+cuTMgIU3G\nis16UVs9zA3t2Hs7trQEIlSbLEYI0UbWGcnlj2ya4KucWvbFB4ISp9xmI9l+\nLBt2k7478tEUL2Y7XI7mv/G11RCjhrx/SA/OZKuOEtredbIu/308a9w+1Qac\nipCQca/3rQwVp0eVWUVOKEz+hYlBVqLyre/1eVUNek2Djj3nv+vD3GU+npqE\nRSkI9b4sm68vbWA7t4+nU5cGZk7UtMcwH2nd1kbZVwmWtLf/lnAxAnKfwJLT\nwC30Od6vsG8FCX2zFdl4G5OHlaL1JRby7Df//C+Jfv82N1D40oRTCQak+eJr\nlGbfXQ/jOoikpHreXKmLlFdBGPMNUa+/B/pul71yxUpBf6AzH7Ue/s5hiiBr\nxNsxUe5qwybM6Z3V/g5JcXAHyJDehJrfflSZ+id4yD4cSpbC+awGYe1OQIj9\nukJ00z/vayv8SeaHXlKnYgkYfOGnnAzf200eHpPyTw/oXTck46PxH5INP7ih\nCho4eD7542oZ7Wyh71oFcgBp2zeGQEzmBguAyaEQCFVfRVM9n2r+w2UHyTjK\nMe++YLoJEeGkH2JJaZAsZm64ovWO7pk6hlvLAd/G+qdgb67E9lA71XTMlj2F\n/QY+\r\n=XS6s\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e775f86ac59b9ce1b4a560d26cb7e11499d89503","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.17","@balena/jellyfish-logger":"^2.1.58","@balena/jellyfish-metrics":"^1.0.207","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.0.2","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.141","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.102-renovate-non-major-e775f86ac59b9ce1b4a560d26cb7e11499d89503_1620817708100_0.3768621286470413","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.102":{"name":"@balena/jellyfish-core","version":"2.13.102","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.102","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"25efb044842e6e4cbb3e140b765e3f37263f7608","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.102.tgz","fileCount":99,"integrity":"sha512-b52Mn3ht6WViplOctdBOznDFGCvVVKJZ9Xs+mthsdjVMqFQz9hO5pxEF/BrgafOhu9GeSmyxtwPnZ/cUna4u4A==","signatures":[{"sig":"MEYCIQDTAcgHMzoJeLXVFo3+xJm1K5I/zAGOhvERk2hgi3AztAIhAMwUJfc6PVYtB0Y04ZkXQw75yjeqcbKT5Si9Hc2frCj9","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":744301,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgm7h+CRA9TVsSAnZWagAAQj0P/2UCzU8ZM9EBW+uBzLhD\n8jL3gjJEMrFvRSOhH194zOlBLTvhZKNN4auY4mqZSvkh8Nl3EDIsa03pLjk3\nlfxkRc4hJqQWWfdSVvPDRwrFNJTyG3frE6Ftjzmz6DeifmYquEausBvJ4pMK\ndFH94ePg74SbU8vbxWOEDIv7ZxF1UpsnVTaeL6OjaAMJ8Zfjpt0iNrGnxGZV\nEgYJGbxu4isE71vU34gmcZRYe7/ixGvM3e4cBe/ESaZfZLKsBT5wvBnr+P1j\nGE0s+hBcMNmKS4dHemlO7n7rtdNe5cJjzrtQt5jWhdp0SIKP53oujtJTDCBa\n3ugtlhvno6i80/nTcncoJgXbX4lcqqsmesMYpXpGRfk45V/tOYuKb1Xwr8z9\nhsASCpRSub4GzD7P/xb+2ggjVO181zhIkHu6bEAiTpyUOg+kYwwjvsc4WvE0\ngOlgQuXJWSWiMfvMTM4EynaOoDOINA6LYaks+0L62UdGhaAIW/+ThIy7oKGg\nXDRQTjdVZ51UiCOay8AOfa9BL0tTLF34Jc/zvIeyeCCnL5htH9+tXCTY+eiN\nFBXQo5Q5wmoHsQXsbLV8SjpUAk03mQtGpatZpfr9IO9Q7/MVhdqPlYi3OT61\nyrJFXg9+InRf9sx3EBOMbd9+4jmJGBLkisVyvLAwUwlw6quS0EGHzaK4NNmP\ncL1Q\r\n=xyWZ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"faf1a13eef9b34e8e3f224d5d572c65ccaa558db","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.17","@balena/jellyfish-logger":"^2.1.58","@balena/jellyfish-metrics":"^1.0.207","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.0.2","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.141","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.102_1620818045722_0.9395964829905574","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.103-renovate-non-major-26bb61dc5ff6eb245f012eb52815ab49b4b28950":{"name":"@balena/jellyfish-core","version":"2.13.103-renovate-non-major-26bb61dc5ff6eb245f012eb52815ab49b4b28950","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.103-renovate-non-major-26bb61dc5ff6eb245f012eb52815ab49b4b28950","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"843218cbee5b32bb45314a871476e5ab76ed5072","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.103-renovate-non-major-26bb61dc5ff6eb245f012eb52815ab49b4b28950.tgz","fileCount":99,"integrity":"sha512-qAUA8kLDfy9sAYmXzBNnNuGZirJ4Z6EFHFclvNNmMB/wKn+4sHoaGm0Ad/IaEmRPUKeG2ibcv6asqhRFtRb37g==","signatures":[{"sig":"MEUCIQDtH+J3E70IX6AbQ/vR1xlH56Q6Zu9v5EnoeUVtyZDxpAIgbJm5yV0xyO3aQMWDAK1wjRJPKFPgz+jWg2hU5fLZ/wI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":744792,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgnEdNCRA9TVsSAnZWagAATBYQAKIvrWOYXV6PnSXGJSTm\nuHZXZsnA4wOVJdzuf/Q8+IqKqlI9C9qSluVFS6ZfeArBPcCDgG915ypwd1TV\nerhF7pstxwAVzkpQXSrT8iN+Ddge61zk9oTCF00tkhmN/Ex0GKzVD1a4/HLK\n7VFm5UapiMQ2pIG41B/mn6/ln//Kl2UHQot8173z5gScF4fRGwKaEPuLRaUw\nqQUXayuviyuAPprIeXsPdFwatXO6DqZkZDcSYhQ3ieVwDg+9ONdGxrO1U0Rq\neF1C9UzQDrPhBuBxqQu4mml5M03rTkelYDaMZtm9Cn1G4idPFlb3g4uyf4EX\n9fTmK33kxQWZjXViteP7D/fYjNnAGK+24IZ2HKBxUvkzvKwxNghdhSwRMGQj\npmp/fhSBqd6AaAcbxSo26mWuaSms/J3qs/6op9IjFYXvJ6e8+vd22uZkUNiI\nWE75pZv1jRKR+sWWRBMJUnWRbKzoOsRfvYV4rynNbR/tLpdasi4HFpphrCA8\nAjDl7BFiBt3f0S9CaSlPTWhyRN0dKuwdnXM7rkhyPVFZQPFkI6W4Kp2Ka403\nMBmsjKrGOX30kM8TPwinEZKJ0mOioDLnphAz0pWS4RFAiZfnUj7iXJ4751b2\ncWWzuHW6rCsko5pK1glsWtjaAWSGUJdV2JdbbDmSq0mjqYy4ne6hqlEOeSq9\ngSNb\r\n=sLz4\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"26bb61dc5ff6eb245f012eb52815ab49b4b28950","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.17","@balena/jellyfish-logger":"^2.1.58","@balena/jellyfish-metrics":"^1.0.208","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.0.2","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.141","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.103-renovate-non-major-26bb61dc5ff6eb245f012eb52815ab49b4b28950_1620854604555_0.8347320359980723","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.103":{"name":"@balena/jellyfish-core","version":"2.13.103","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.103","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"09c55c56ce9bd09ee901fdc464a5bdea9b6c22bd","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.103.tgz","fileCount":99,"integrity":"sha512-XSMOsl/H2zHfOXttG17UDwkiR2n9V7aMOr9uBtTd/3G0fBQoMoG17TiPlMzUaoGqNtp3wIyCo5u9WWdD5d8Zxw==","signatures":[{"sig":"MEUCIQDG6qiQy1QhoJIAbrr08k8g4l2f48CGfItlA9yu4L7d4QIgZXHfxwekf13TtvdWo5kToi8TOqi5wr9NwicWXnmqUqw=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":744732,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgnEiuCRA9TVsSAnZWagAAXxIP/1+EnNHzuW/wKhrOGEUd\nRvBsK4reb4hiG+G2IQqYOapKpsXgagso090MoTYRgIecrc5a5M8oM8nqCmMM\n/hIh9f8LnAVHebN9hkWNQlwl+oFbdu5wCG9A8/Tq/8mRTFz80Hcc5EhUSfLd\n+JbPiufymarbxtIbNOEMrBfUr+16+cbrWhVK1IXeFl1vOhAqDGcTRJVvArcy\n3KK72FJQvJPHtkkshfBBrs1GomX8MAfbXJ5dNL5XnaDk4Zwv9j3P7ORsjZ0i\nLcQW2XwpXKstQ0+LBBOzOWXTvpn55F7Q1ny2XodRnoIAhR6MQMYXx+v8mDUr\nrKTJEF/l7rwuflishBzD8hZW7VoE6ol3xmWbnS34lOEv94AUEZ3Ax3JPzPXR\n0M5DIR8m0FRkzOE9fBeXLfTOi2JL60Myh5Y0V3wqgJBGH+6EwoctKJeAuxHU\nEmBo/yQwwiuplW2UTs0Z9YaXOJ7qG80DJ+OxsA0mMdTW9enqujxUYTVLLdVD\nh2tNC2lJHXCCH85iliVA8Cn1LOCAyN+C2FRve4n6J9zK49KiKdC0MLLOrTES\n8lMY31BFV7VYVcEMTsi70D0Sy0vCHQOVd8RDCQKZOvme3LM3KZJ6berzPzDv\nsTFTFyf/5EbciTsVPKO33U/iEAsAF3r8Z84A/r5oY7N3IqETKkBKbHpmof9B\nRWhX\r\n=BzXK\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c3912552a32a7b332366135374e981a6fd789c01","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.17","@balena/jellyfish-logger":"^2.1.58","@balena/jellyfish-metrics":"^1.0.208","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.0.2","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.141","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.103_1620854957517_0.7165251428645156","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.104-renovate-non-major-f037ea6ab95a889c6ff71fff8fdb8f13bf0e28dd":{"name":"@balena/jellyfish-core","version":"2.13.104-renovate-non-major-f037ea6ab95a889c6ff71fff8fdb8f13bf0e28dd","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.104-renovate-non-major-f037ea6ab95a889c6ff71fff8fdb8f13bf0e28dd","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e438b918f56fbd2a1233e8b18c1f20e9b761f170","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.104-renovate-non-major-f037ea6ab95a889c6ff71fff8fdb8f13bf0e28dd.tgz","fileCount":99,"integrity":"sha512-N/nBDHffbNbJxRJ6Hz7pNlI1hq9vrX0isgvENcT3yhHqKV0UT1Uyw8U0mknIYuG7pwxhavlafEAXm4Fi7Qmt6g==","signatures":[{"sig":"MEUCIQChULbcHpix067/JdzZm5wfpZxYt9HoF1lblDzlLUoXHwIgOiHQmU5vzNuEpv2iGRBfqhfeJNEzkBr3vdpWoHQ059k=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":745219,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgnSEuCRA9TVsSAnZWagAAvawP/igYUCQWcBpVd1yINhCK\nsWPPRWmRKV637kckwN7q3TqgnloXERqfCBEx7wxfoI/ihQeD4Cp7njphjHly\noYok0u2DT0T8PRIhWdCOOthK17oxzJVvoZyVzdC2dpgBMzUJ8h4cUp1aFy6E\nf2OgKFooUAQahzZCZFVACtO3xNupLcMqOi/KYe8hgvqZeBR8ONtVSWkhKehs\nmRnyNHy/+sOGAEJcwiqHzQaF29XkDNqsDC7T3ZoG+DSx7ELVcN1mzKA/p8yP\n8HUD7pm5AvtI48QmD7zyoX6q47lGnu9U1h+j8Z50zeGvx8M7DcHzeLGcSA/1\nEiNNcj4Rd6m5jxmk4QE3sZyqJ8jvjTz9ysNOXYiK6Nd/rG0rLCWUZR701hdK\nFlc7/vroWioJzM1oldRwPjRqq7WwUoh2J2xFFT2bVdSZcj/i5Fc1CidzIWKS\n7EeBM/abPDMQARU00knnrEKEY8LRidEVXsKYsprdsiq87DwL70nBhH9qd/6y\nfHzmwWpnCqzBUQ11BFmXVgLelacTN1gfX5OCDmecuohZEvJtayE2H51oeyn5\nAdA44+2H/mUup4ksgMqiOkbzNGkbWBOu3WOQKQ7McBbfQkzUsldfgNN1C4Td\nOdshKIR/Abu/u5GJ1aCa0Bhqd2GaSv37prKbli9r9M8WXPkj5mFFsmvZNldt\nAghF\r\n=G6oG\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f037ea6ab95a889c6ff71fff8fdb8f13bf0e28dd","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.17","@balena/jellyfish-logger":"^2.1.59","@balena/jellyfish-metrics":"^1.0.208","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.0.2","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.141","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.104-renovate-non-major-f037ea6ab95a889c6ff71fff8fdb8f13bf0e28dd_1620910381620_0.967990300130259","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.104":{"name":"@balena/jellyfish-core","version":"2.13.104","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.104","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4701c3fb2db49796842be3577d8c8ad601b348a1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.104.tgz","fileCount":99,"integrity":"sha512-SqCSFVb02eh+SfPjd10OsLCrDpZQMC+wHThAAuFPz/kXh5vbLlUvoXWWnzVo+uh7+REtygI09z/BvYpruog45A==","signatures":[{"sig":"MEUCIQCvxn8aCZVHgeYgeYfZSpXI/CMO04vsxo4y+EeMxMbnEAIgH9XwZs1c2SBELXpuPYNogGAJVnErftvknViaQ62pNsg=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":745159,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgnSKDCRA9TVsSAnZWagAANDQP/06IsKo+lzmmlpTf3B3t\nBVjkZMGst4hOqWDVU8TZ5ZFLIomAYJwl1yPlM1kS6s6Q/Q+ZIsMOVzcMLMKS\naViK88vbmqM8NG5m+Cu0aHBaqW88JJaG317bu/0x+RaOndq2fbHk1pmbaETi\nPG+YWjJLqN/OpPzIgp0ssblCiPpWEBP4FaNhn+MFS18YXXr4HZsjsejq2PH3\ncfvV0kz6yF3hNFioLDWBg6Yy3L5G6ntLiVZZvXvMmtdk9S4uQZnfwmvDV4nT\n9aiJ0J4UrPPnMYbiNic+PZWiFvEgbw7993XO3qQmur8+bUy03yegOsNWvcZW\ndv403SOPH27uX6kiBinVdWtIlQKKfheUqRoIdvcBC3HnjpJ31koHRQDCO7Pj\nhxwRYYpGJKOFfokkZkx30sIV92pmPkp7bMCiSwUvs8JmpE74xdb7gm4n4Xzv\nHtn5bWp41kvObdEhJc0kOuAL+jZobdcJzUS8V/qZiTegSNMn2HsYYk/C3VxI\n5a52sdrB2EnjJYo6EdyICvKfUDC5DhwmH/p87WBBNWafyLestxv8e+g868Rj\nu5837y+Aa1ghAIyGohOPpVhcTDQOlAoyAwCoOYiRAN0I+cFeUZ6llWyfFJ74\n+gulq3kjmB3QQfVgzcCHCYAVzLfj25Eij7SkNSDew7sGPuMj+vuBT+flCFsk\nNBQM\r\n=KjkE\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7880c8ca6188f50625f13fcc37975951a231d257","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.17","@balena/jellyfish-logger":"^2.1.59","@balena/jellyfish-metrics":"^1.0.208","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.0.2","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.141","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.104_1620910722832_0.5957098835633525","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.105-renovate-non-major-2092d9cc37d6e187a514491cc90ca20c3f72a0af":{"name":"@balena/jellyfish-core","version":"2.13.105-renovate-non-major-2092d9cc37d6e187a514491cc90ca20c3f72a0af","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.105-renovate-non-major-2092d9cc37d6e187a514491cc90ca20c3f72a0af","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"90967cee11cdaa1c64c6d136fcb9e4bdf854ea71","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.105-renovate-non-major-2092d9cc37d6e187a514491cc90ca20c3f72a0af.tgz","fileCount":99,"integrity":"sha512-mZzHWlT0Vuu8cnJSDNW1WYCicM6sAq4OMsoEeKcLm5fUCsCv9j8gciuNYHh3o3gArAgOq/CCNpFtOeqFo9+iFQ==","signatures":[{"sig":"MEUCIQDnOj98QYG7Zw8fzqwv6EN7nKMlrnq13aYzES20yX0tLgIgep3u6UaAAjnAerAckqy2I9UFcdDU7ia1wQfepapIDYQ=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":745650,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgnSr9CRA9TVsSAnZWagAAsfwP/jtnoV3OCU/7LR0TBdFG\nmgZuLDXLYlnqmSdVaDjeScQuz4leLcf7GXWGrhg+1+wSt8yokU3s/dT4uVKE\nQOh6BqxEmId5e+aAYEJo/Q3TsrI8JeY25udgau947ltsISGAC5dyB0ZZqN21\n8mMKGWucazbdYbriZFCS7eEDZR++W4xzPFark2G0ENLQVmUZMxemDVLhx/a7\ngugT/GnGYZNJ/U4rOxe11sSMdWtu8KcZepr+l5kdz+J/FZ1AzbI6nsPtHyEj\ncZRH9D9mgGlr9aV4hUOgO6X008oQ6/NaDHMSrVjrgUhH8CLXwbOS37MsVT5z\njHeXMIwnjRM5KqdyQToDSJPI1ZFJa0yhN5aXnAL1uwCyizyHAry6o1F3qWBD\nDFz4EbiYAdiSi1Uuo46IYH9HUsxzzT0sIOrQf1hDs744AIiMvNC2DlT2y/lM\nQCRChlr15vkeK1kkhasQ6K1A729UwRQI479u8Fv2YF68A2lA6wZ3bXwZcoLy\ndxKMtCI/u1GjeoGj/+N3kq1ZgkH0/dtL3U8xJeE2VlVdTjNWCwqvT8XLK3qO\n4L8KJJUJ1GyqYauGFPbkAML8uGTDMfez/+x3/LgPKNraluNT094AHrKEmNmJ\n2lzbW88cqll8tsMgjqsayzanu6X60djZutSTTkmN7YYbeCGsOKohcYUq/4jC\nmE6e\r\n=tnV4\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2092d9cc37d6e187a514491cc90ca20c3f72a0af","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.17","@balena/jellyfish-logger":"^2.1.59","@balena/jellyfish-metrics":"^1.0.209","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.0.2","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.141","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.105-renovate-non-major-2092d9cc37d6e187a514491cc90ca20c3f72a0af_1620912893062_0.0904688923599859","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.105":{"name":"@balena/jellyfish-core","version":"2.13.105","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.105","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f93cb47c26c2ccc32aa3a6e0023cfbb1cc6cc36d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.105.tgz","fileCount":99,"integrity":"sha512-7ZB1CT1uLno5hFk8DkDmzGBhhu0BeTAyv8pYMl1orsgvY3HpnDOj+ITRWEKTggm1OMlI3UYglik7wyXQUzDEeA==","signatures":[{"sig":"MEUCIFXrTbpf1kyduRDxU87drvW/oGmok0aeDDYaJGNUa3tkAiEAjS1qv6aaMdxTwhxoD4imHgDrRoyS/77OF18Swc0CV84=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":745590,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgnSwNCRA9TVsSAnZWagAAYRYP/00o5pkpRwbSaRix2h7i\nxLRZkzSPHJdAkBjgEc8+l2g4girtc++4hox3k919cQIjHtcfjEGl6ep+xWpR\nkNRbC/c0sKj7Cvqe0LGMH5s3dtkajFCu6P623juIjEAhZZHpBd+XOMIAXtoN\n1f3s8A2a3t4nILnpj/v5JkZVxbFVPmYco91cZOCO11y9d0GhL09ZhB57w2jB\nz05mzrCAtZy7/m+ewgZIm34AoKc7FwPkY6cvbL+cU5hbTQnhvKy+wQLgAVNq\nz8GRU/4zaWpul8lBLA7ogmrGfNUcVkXDl+LCkBIdkp6hM6a78+wf6qldec1c\n3sgm07jAzePI3CpeR7+4f9Bh7/5eSnveTPN9UR/SjkOdTvfIc0QFitXILKNw\n/6ZhfzD6ogqWX8ITS9V+jWFcESqoqZzz146qUw/zAPDAGR7a4sgd2Z+cJi0T\nLmmsfSRGSfwqDeQ/qRCtLueGPt+N3u2MfjPWU3StZdIukPvKLrtV5mM0EhHg\nrA+yvwHBC58/KDFy2EGLokHWa8ysVwZMa7oVwC1SjBtOG5iFqDGGn0/8NVn0\niGj2cN2aUGNjtfKj+4YeJXots9s+4H0RDEGo/c7LhG8+KCVKCF6IAl1BmU1c\nEb0wFh15UXfEmx+jInIpJk2fjC/cu9goxLr9bJ/7V9hseNET39ynyjBSJ8kz\nqGsr\r\n=XYSe\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2918720fb7a8ded0fd68218cd12e74981f100c8c","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.17","@balena/jellyfish-logger":"^2.1.59","@balena/jellyfish-metrics":"^1.0.209","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.0.2","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.141","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.105_1620913164799_0.9467012051954828","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.106-renovate-non-major-afcf40355709c36caef289b0cbbe6ef04f7ce36b":{"name":"@balena/jellyfish-core","version":"2.13.106-renovate-non-major-afcf40355709c36caef289b0cbbe6ef04f7ce36b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.106-renovate-non-major-afcf40355709c36caef289b0cbbe6ef04f7ce36b","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"885b16d18fdcbc6b161ba975e6898edabb0a49e7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.106-renovate-non-major-afcf40355709c36caef289b0cbbe6ef04f7ce36b.tgz","fileCount":99,"integrity":"sha512-w5mg1asJchoAoXsiif8i2Z3ui1yEkcrQboe+KExSKh0ntZu+l5dMPkIy8cnRGt8CDsONauKVGZmDt4gigoy81g==","signatures":[{"sig":"MEQCIBvi6hCp9ufJbRQuRP2mK6RKHUjjtmkLcJvAhYNDNbXJAiBZM0wetDGQeLK/AYQwfNP0HvjUQJ6iPGCnJTUHQwkPfA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":746067,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgnbTGCRA9TVsSAnZWagAAUpsP/3FHr0GRzTbDqs9RQm4p\n71XSkHw5zaoRvWnQuPKf3gBk9vTl0iX1QacYZZsRmd/fbqARUrfn7R6iRmb2\ndbQhwva/IXYcJJ0D/zVtOqA2KC9huwo7sVNgM26Wr0iyqxxXy38DQ7ul9fwa\nf3pPZTenaj/G+MeAc70yZjNuBwdmCNGA4DzNocciI8URypZjyRjLRv7S+flh\n/USer55be198/ynBCT5kNtP0RJe6TnpPcK5Bwy/8Zuq6vt2+kbYapIuQKWLW\ntv5mhkQfzovpfI54zGbYAZpjXI/pAlgu+m/1qizSuDftJ2MzpO32xHRJDyXn\niwGD0e0L7ujxTkQjP0loWjE/ags4upCTnOQC4n43MpYZbO1fYB6rDMkIT63o\nQlc1WhtjmtSmODcH79iFAZZo+Nv7Jz8paZbkkL7gl4RoQ53bbca6o+2OPBY9\nuQfvxOEuFGjL69gKaFget+qmb22zPHcDKXyzdgYJqgXnHHt5wGv7XUkfvFMI\nWzkS3ktSPpiFiRT2sjsba3/dn6fFPhM6q6z287YwFEBlF+k0+TVhFvv8K0oC\n2IlgH+nR5QmypDFYGbxUj5Dl3b8g2OOTJ+E3oFxWb6gxgrmp3x6ZLIbB0oIe\nbjQtAWsPC7I627LZDax9ILRX31lnJ1va7+wCmuAAwqi/6iEpxhBxxNJOfn2D\nzEMD\r\n=HZ9h\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"afcf40355709c36caef289b0cbbe6ef04f7ce36b","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.17","@balena/jellyfish-logger":"^2.1.59","@balena/jellyfish-metrics":"^1.0.209","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.1.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.141","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.106-renovate-non-major-afcf40355709c36caef289b0cbbe6ef04f7ce36b_1620948166365_0.014640126642440077","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.106":{"name":"@balena/jellyfish-core","version":"2.13.106","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.106","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"cf1cf93d461b7cf3ee5724115371f8bf7c6bce58","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.106.tgz","fileCount":99,"integrity":"sha512-UBs5g5V/JtOhvw6K3jhVK7b4ZkCnYLmnEqAmkvtV+JHRuQ8pQnw01henBG+YvHEl4BUMkNKNSpbHzfFnhkaS3g==","signatures":[{"sig":"MEUCIAwg3pmg6k8HCudMrH6HyfjEowpoH3YfyInI5Y85KiTKAiEAtVYZuyxJlMAjF+sBg+bgtLb4Y31zTjMK+nIi3jwjy0Y=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":746007,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgnbZzCRA9TVsSAnZWagAAQ/AP/3CZSY7qKZVn2Q1qykqe\nZNUKx+YbaR6n2c6GMze2Zk1Yj2OBqPqRvsJ6jnRxHGdTGOmX54z7MGHGQfMI\nNirMBk9LZZQHYyMEsS1wmqlHzD7JOTEfr7nf274BHUHDJn/NlKA894dgAFJQ\n1dcC8OEDwi0jAfowrcc3e4hdGFVy1z0QXEQ2GrxeUcZ5VwE4VCqsT3QqTpH5\nngUSm5QI8qKCTuDlUVWfLIOPlXzc8j141rVOgemdQkprFVhyFeBHVDCjhdxt\nYc4NcNh7kxt9Id9DWdRFhpufU9Ve8FA+2uWS+vTS9F0CSSQgU/deGLzhpsj1\ndqyFujjlqa9iYr2Ht9kfn/vWRKNN8+51j07LXJOC2qV3Q0F47wpjrzAD2qJG\nBPqnrmfZ+Td5H9SA2MHWFlp3ho2c8a4m7E9OqyPVxHUbbxzZUn+uaVxrBeS1\nqndHIHsI1U6yY3fG2uwAnsJvbe4X1zUwAMrYL83vrMXjl9HLxGVxBPKYkZzT\neJr0iYxn0Ztl1BFOjC3M7DXXHPQ3viYqhzbVC47E5RL2+X+wAj0hLauFoaHx\nQpli4eSolPpXgrmy2GFNy/z6r3fbjBGeoC3DGGGDncwJzgUCoiP6Kgpxy1NP\n37YMYGP6KNeImwB/b5jVxGXklB4exuSPsgLYqyhTTZqB0NBMcBRLV/mMCQ2p\nMOHg\r\n=/Dxw\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b086cb60563489694594d625b6aedf4e2136eb13","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.17","@balena/jellyfish-logger":"^2.1.59","@balena/jellyfish-metrics":"^1.0.209","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.1.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.141","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.106_1620948594582_0.4995973782952219","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.107-renovate-non-major-c705d4924ba2959dd26331ebb65cdfe2e703bc7b":{"name":"@balena/jellyfish-core","version":"2.13.107-renovate-non-major-c705d4924ba2959dd26331ebb65cdfe2e703bc7b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.107-renovate-non-major-c705d4924ba2959dd26331ebb65cdfe2e703bc7b","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c1a890bbf65a34246658c2d6845fd69b3d5e67ac","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.107-renovate-non-major-c705d4924ba2959dd26331ebb65cdfe2e703bc7b.tgz","fileCount":99,"integrity":"sha512-IeeDuBAyv7C2iuI9LjPkF8vb/hzhlOTmpfZJldn0qTfsj0FRWE6HRZ9npS9XYa7LQR4/dytCIT9ly2NDFyYQVA==","signatures":[{"sig":"MEYCIQDlYvD1Jrt69fuNhgH2EpCoFWqIyTtrlYoMlU0Q6pRMZgIhAKwEjwEFtSMR9Fk31/SZbQynBvSZG1qeRLpWpyxhoGjT","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":746492,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgnbfVCRA9TVsSAnZWagAA7YgP/32pDkaVlE6/WWxLHUTh\naFLz7JfEPlDAhHt8IJWLIdx+/X6zOrI1PN9fbEd9R0S9Y7s0ZC1JGRfqEWCa\npW3xR63GSN8FF72uqpfT/LX7hj7d3wbg1WJEV7ZMOvsH6ag5T3sBJBCKcHWX\n18pw0PTUmA4zxUqIkX+0b2hlT2vP/dunzvsOKsn2r98zpvsSqfAnd4QHE+Hs\n0lpL0nDNKRDTMDVZlnr7om4tOHmdZb9EQ5uZjq+8MYWrQsuH1alI8molG8MB\n9Y8XcImsPcMqntAiA7f8L7/SBHdbDdk4LLxXdmuLHAHvEg0OU1ENvHHz6z4r\ng+pBcMo/lig/UZi1+I2IDILsYxg6HhZgdTbxUxK7obvcR2uTvOylhGT9aoE4\n0JUiRuBJ+ttaVUdURWaGFaC9EivILcmpytVu8HzKsb9GevbplvwReVv8zLfj\nEK/N6PiBHHVZwq60I0g4Me0glGw+p8Zse0LmUXsJsLldFZskEPcN0o8epJIZ\nYzO8/tRgM42alrFsHAmxhEr2Oh/UAebOtfAkML873dXH2v9/d8ISF05UvwSD\n2BBvbZW66nvid/6txwo0L1IXPyx9r8JzDV3GJ49KhRnMAZD1uAWaPFCMSlWf\nOJAav+dEXzAs3/H5aojqMkYDGZWNZul6W3LnSWHgjOF85rLueq/wrKOukIP0\neM6b\r\n=XFne\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c705d4924ba2959dd26331ebb65cdfe2e703bc7b","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.17","@balena/jellyfish-logger":"^2.1.59","@balena/jellyfish-metrics":"^1.0.209","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.1.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.142","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.107-renovate-non-major-c705d4924ba2959dd26331ebb65cdfe2e703bc7b_1620948948683_0.35061836311364214","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.107":{"name":"@balena/jellyfish-core","version":"2.13.107","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.107","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6bd9da2241295c2eaf851e1466e9fa6879fb39ca","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.107.tgz","fileCount":99,"integrity":"sha512-m4Fjj8s42C7UYhp56vFmkOdkDADgWEy/iwhQf5/8Cn9CF4EuO+uvTUPYksN1mjBL3taAWF8fY0ywjYl6mYbwdQ==","signatures":[{"sig":"MEYCIQCugftTfmpf60mIKibVYeTUVqbcPiz8Phd2+jm1tFdPHwIhANjRHc2CUsXSKBnP7TIrj9FEj3hvJT8jV+bOTO2RFJ2+","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":746432,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgnbk4CRA9TVsSAnZWagAALMYP/RnhqFF7W7fL7E9tTkua\n45UnsifEHqWR42B9RL31KkzD0TavjDG6/65Ykv39+z7JIs6DFztAljry4VHt\nETTJ/sEOu7Vpvio9LwNKghGu1aQlo3IZ1uvJKqlprjwFjjqBo4n+3BLvYNKK\nHhDSd6lj1ogrPX70zQ8uXd3uhvvAchxfJDBzgegEHIiqwiRHBAtjyAG17jCr\nP0952hHXFyyi6hFwC3cmW2sBMMx3gu40QhOk5wm+UCLcfsSq1KCxBZQjAP6P\na5P2HgqoBtUuh4AJpRCjme4XGObO1SOnfmJs40WkB/fQggtEGFN784Xt2khO\nLta9NPSvu8TfOGB1hQZSt2UYMgF9zQlF2hDB5Po30nx+mjvrxMJP1fe8XVHd\nPoQ2TKn6wVVQTYkIAfOZERAChW5R6jHZKemgwWrbKsqvuZXP3msAUO/03tNh\ntHxcR9mW+Ke0fWfiCJILTcWd5JB00X362hfbWOGATpCEPyBtTsX/pOEVGO8h\niNvQYTzqOH1cwqtdi3yawjoQ+8ArfLHwpK5OFunpSWzK9HoJS3M2Pk655zyX\n53sUozWdNSnlZwEX5FtEl0RbD3eykrBbS7aQ7MEdaa2/39Z79uu6W7V7hfk0\n8kq83jf3bGS6v39vpNSvl51A4ZmkdBnI189RNvDB1xddlzF1OL8ugY7NpnnT\nI+SF\r\n=9Skf\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"795573c7b4ff65e0e9d2d1cda5e267ccc29f82ba","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.17","@balena/jellyfish-logger":"^2.1.59","@balena/jellyfish-metrics":"^1.0.209","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.1.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.142","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.107_1620949303634_0.20898606135001963","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.108-renovate-non-major-314d577f737e52fba09b730fa57f7fc9db5494e8":{"name":"@balena/jellyfish-core","version":"2.13.108-renovate-non-major-314d577f737e52fba09b730fa57f7fc9db5494e8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.108-renovate-non-major-314d577f737e52fba09b730fa57f7fc9db5494e8","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"37810ed325e0580e3af2af2c894a5ee77a836016","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.108-renovate-non-major-314d577f737e52fba09b730fa57f7fc9db5494e8.tgz","fileCount":99,"integrity":"sha512-kAiTkohysGcAteJoMaeXDMB+6xKM/1r0/8+N18NqOHJG3A5VOU24YsCnKUep8MTMburJIYsqddaVWkHkwbYmBw==","signatures":[{"sig":"MEQCIEvDxsBu2uZc1NhY+fykCPRc2T5EjTD0eIAm8fctR7dCAiBm2EEEaxkbIadwcgzrg6jEKOJUCBq0zLgIcuJ6gm0W/g==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":746845,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgner4CRA9TVsSAnZWagAAMPsP+wRADj3LVeXyv71kTGED\nQHMDekFEzEE8jW6u9U0mrkNXoEbiNkLtoAulm+PRrSk+yiJpFxaAKou0uBfH\nCVu67V2SlJwSVVTvvGx58O06Bqw4DSYO/Mnvm9WH7kq5txbiSq5qnLcNwMzC\n016fsxpMjjdUQPKKZjBiIxJgAYgYIn0Rbqu2MQSZgKkIRSgqYJn0W7hmt0GV\nc1kuWcTLOld4t63hZFgGs/DCPYkK0w6nG04ORsJoHrL6HNU7c5+7fIAqnyZv\nStsLw1M/e+24rWmpcM1CExd9T4zkntaBL6FhwWKeerBGBCP2fytFNb40imb/\nq/RDGkBh50eeTbn5J4hK28tIVuv5NFo6aG8a/adKWYigk5CLlTB5LA1YFgVN\nrcEpVexqx9HlrFz/rR3ZHiAvOScfFyoTp5jlLii5UsVTMqSt0vKN+UpeasA7\nm1m7/UuCPiryGBaSfBWoEa4ez2ZwjbChZLNNM1cw63lu5QoLobG/U0pr3pjM\n2W9LQg3S7zC0OYbzLSdGv+jaa5ZCb4TYF1YtpLKJa5MhkRQFpbWn7LzYQCrr\n2BLGSpHTMJMggzB+QLP/93LFt1YNJIONZEid9tLSzSJnqUtpeEyzEyF+HwzK\nvHFya3Xa8kbM3n5mqliVGEzZ8/4vGU0o8ZMQsvz0n8/3AdAZbJXti3eCEAYT\ndQcJ\r\n=N5np\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"314d577f737e52fba09b730fa57f7fc9db5494e8","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.17","@balena/jellyfish-logger":"^2.1.59","@balena/jellyfish-metrics":"^1.0.209","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.143","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.108-renovate-non-major-314d577f737e52fba09b730fa57f7fc9db5494e8_1620962040264_0.08480826736038471","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.108":{"name":"@balena/jellyfish-core","version":"2.13.108","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.108","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4e417f383cd16c042f4b2c88b5d8749a31a5da41","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.108.tgz","fileCount":99,"integrity":"sha512-jKwY6M5OtSJ8bSht83EBa4cfJVAnyI71LzMhMadc+z6zxuA/y6iAquEWhE4WQupc+mAxUdp9xrGiDvh2x8d5NA==","signatures":[{"sig":"MEYCIQCI8UApFxiNwucx326H4r6wvNXfOheTfDmIpLohD09SSQIhAL1iY8iOLn80YaW0gTm7K1yxrP7KiFMLO4YGG1egj3TE","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":746785,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgne2ACRA9TVsSAnZWagAASRUQAJEC1DBRhqROxd4Tk/Tz\nAifDOimJhytg3qZJXjuLQxf+F7ifum+O9P9+qetDBnjI8siDCPQwETQT9/NS\nzZVUCBFGeUAYeAPFT9kRXmrRzMdrhYhEql4HWLe90ShNzAHlxP4BSYdCxYCC\nz5rjpXoTnY4EeXJdGqtPY1lgOZWLxSUpq3Y9kwVldHQeN/AoPeWMbp3yHfJq\n5g3k5U6X1S9RG+KcexYT+3XsolE4pyZjn0UTakR5uFUay5tgiN3XTKISntkm\nlsvrxUU3fjr/IB7TJo0dpW6ErdzgrT2T4xwTw0nG9APMa09boq6NNzv7m8hq\nJKQl9zY85slv06NVUg4KOnbAxHVhgr03R645CFwX+qNZB15Hq8GxB6k+5tkn\n3Q8GlyO9ndG6TvtOlQNiOKMxpAAczW+owF/22VvcCGa7ggRkXQyxIgp+70w5\nkw5rdlpTy3J/87rctMQyf/DdzyXQVrqTnmhLhM+moyfz2e1KCcwiIFSkJdDE\nHhVS2qvdHtxXN+xarhnJDndaMM0B70u54q1WnaJuI03VFbYsyk1bg5o16qma\nrMfuZp1S9g89DX0OjxMbbPcn++nhunad71+0GqEr9fn2sMWHlXnDLpN3sNoh\nu4P7SivAlakB42LVa3Dr/iNe8h/pIRsd9dbo4z+fFp4EXQ8rba24mUamayhf\nrqBZ\r\n=QuT/\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"aea53726505f3679a573690dec8cc7b050969f8b","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.17","@balena/jellyfish-logger":"^2.1.59","@balena/jellyfish-metrics":"^1.0.209","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.2.0","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.143","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.108_1620962687506_0.8519999543360239","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.109-renovate-non-major-45c4e4e10ba587286adc2663ab19aca54c1a6417":{"name":"@balena/jellyfish-core","version":"2.13.109-renovate-non-major-45c4e4e10ba587286adc2663ab19aca54c1a6417","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.109-renovate-non-major-45c4e4e10ba587286adc2663ab19aca54c1a6417","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c241027c4bfb6ad02dde6cd7ed471fe7e42480db","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.109-renovate-non-major-45c4e4e10ba587286adc2663ab19aca54c1a6417.tgz","fileCount":99,"integrity":"sha512-pQGbScHz/lZWpUeGtT/eW1c1jOq3aPoCO3zF0Vi07yvZ7nyF6/kb4+egDI9inoPSpDRoDFRyEF2r3Y3zrFaHCA==","signatures":[{"sig":"MEQCIDNfmWpVPrAnkIB5OnIQuBdhP5Ru4iDy626gR/S6xNW8AiBEwOhYfWWQCn8+0MHGXv3lrmQnZ1NzsvlgWq3/eEjr+w==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":747262,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgngUCCRA9TVsSAnZWagAADzkP/jQ2ssEwkPLikgm5LSJX\nO3PdetrBW3WOr1wxS/UXTsf7B1olWc//wYszIbkagC8e7irk72Pe4gJ2iXtV\nSm/vcb7gZjVeBJ0dbZEmDoRFnIzwWxd6EULKoj5Od5D+8sZhJrCYOYPPdgEn\njLWRevKO7xlWf7384zBpEZ21fl3ms+uEc301nYIjSswOOn2J6w/xnv/28q7V\nOaVVjOFUJ2iJGORjkleTSCR68goD0gEsOYRJVHy8Dpvdy4uludm7WW21FM7a\ncmETMl3id9m0RdqCe6QCkHjBxokpzRUf8rgJVpoBkYaZBlzxsPtZC2ws//Z+\nuc2RsYLJZbj4bV9xhaGFVfWhl213rkSa7nS8mxeOwHe6BCjbdKQvvWHRZdu+\nKq0Y4U0+YON7uS70yL7xtb1P3WWKxZ4xkfRdcJRGbKqiYef2PEgi4Bqjmurs\n6gSj2SKd4n7Dugj5MriOLOtyFVk3drPmxJSmpi1V5qXMI/c6BjnDsjO/WyY/\n+LgEiw+6el7JABYwcnOutNeVtocBisSFjVQzCq3tk4JHaXFBZiE+1i3jLVvq\n5VivF4meqtJejLhdDoNSgriWkWFcMKAkE9lw8pFLWPXClpzB1HfMA7MqHxxq\ncUEEq698DpW6npKoKLtU3jfaL6OP9bYwtVJqMO1bHDP4siBjswwJ+S04AbJ6\nEtbY\r\n=r0y3\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"45c4e4e10ba587286adc2663ab19aca54c1a6417","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.17","@balena/jellyfish-logger":"^2.1.59","@balena/jellyfish-metrics":"^1.0.209","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.2.1","eslint-plugin-import":"^2.22.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.143","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.109-renovate-non-major-45c4e4e10ba587286adc2663ab19aca54c1a6417_1620968705405_0.541556344977485","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.109-renovate-non-major-df92842fd555fba4cf2440dbe79c8a1c7740dda3":{"name":"@balena/jellyfish-core","version":"2.13.109-renovate-non-major-df92842fd555fba4cf2440dbe79c8a1c7740dda3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.109-renovate-non-major-df92842fd555fba4cf2440dbe79c8a1c7740dda3","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a527bb7c06453f07c44cfef4d202c938469e38c1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.109-renovate-non-major-df92842fd555fba4cf2440dbe79c8a1c7740dda3.tgz","fileCount":99,"integrity":"sha512-RTHv/qUBUiijRIFgqD3/BzsSEl0hw1jv5ETaoFxorM0+5in0igbqF40EjjVNxg2pTcDhF6MvEwNGHxfvw2dL2w==","signatures":[{"sig":"MEUCIQDc+HsN0QNr5al7Ia8nQohev/yDySQdLmj8ZMTqxpa07QIgT/ypxUUG1SyshWTcU5Cf3rSu+r0dEdjNrNItztUYXi0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":747198,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgnjFxCRA9TVsSAnZWagAASPIQAI4/OsN2SwAOd0v/Pg+F\nCEnBxFd24ZHE4Eal64o2DNfLyJBaSrOJXKW4aP1aydXq3HEpQN0vZOVmjQ9j\nPgUXpO/2wELZVnOJaBdaSHl8eC9MXC4Fhxs9WErN7RSJAk+UwZrj9SVBcjzd\ngf8WsD3lw4Xxblv36fFbrI6P8oa7MUy7Xc5CzBpxrzid15LuUUITeUqgVs1x\nvN+MRHIJtobDjKVs7RFIOqwltYiCHuG58FKBULG+RE/1bk+SmO2ia9Rmulkn\nJBnkV08dPIHuyoxpWuk0NefvKWdZcDSZVBS4vut+rnNvyCKBkM5GczAA6pOk\nw7ebwfoe2ao7uNnvLS48p+8cIsbcfvZxwViMxyuFWOLhiCs4zD4wC/It/FIC\nwKCW7UqirWw1MSUh/CFxC+oBbolEIJBg1Phrj+glEF+9kpb38zW6ujHfBiNU\nz+IfsOj55LAv8Yne5Qj4UMGnVMbvy0fwhcmQEFbiRT9y0CbtBMNW1MxU6Io4\neMNJSjOcKn+m6Kw+0Mb870hTlkH+pu0wpz8CD1/HrOR7ePgKR21uxpngeyoJ\nTbe78wyzBSJlrPNU/638BTLtj/iSp2n0wwmp/V2b4Eyn8G4SQQLh1yZ78sKo\nXgEzaC0enCBUysjLVffShVR+qtKxOtYPDoxD78wK/O75I/nM+wJSlwYiqj5u\nXYTc\r\n=0ZxR\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"df92842fd555fba4cf2440dbe79c8a1c7740dda3","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.17","@balena/jellyfish-logger":"^2.1.59","@balena/jellyfish-metrics":"^1.0.209","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.2.2","eslint-plugin-import":"^2.23.0","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.144","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.109-renovate-non-major-df92842fd555fba4cf2440dbe79c8a1c7740dda3_1620980080624_0.676879289848165","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.109":{"name":"@balena/jellyfish-core","version":"2.13.109","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.109","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4c4237bcdaba5ef66420e6f22f7c8145ed657200","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.109.tgz","fileCount":99,"integrity":"sha512-bWW7xDNOUWVCkwhZux6rz7jcjeSCPqV8nhabbNB6s39/UDntkn6SFPTpNlogETpHjtUgyzVcWZConE222s/f5w==","signatures":[{"sig":"MEQCIBUd+W6EBDVQjq+NEMJDxVU6Lapu/aj5yCYtgQkASGdyAiAmdr+L1o1AiSeN7mH+dmsF4xcTl8JseRBqbnlsOPtA6g==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":747138,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgnjKlCRA9TVsSAnZWagAABPkP/RbG+vDZO/XTBEU0I8A6\nQIt7UdlGdNeLuJ6bTvYlhBsUFjGp4PRhgoUGkgbiOMsPd8WIaJ/DPTLen+Lh\nSP4ZByMkI8tYc8wQajml86MIjWKweytHK/Ghx5cgtb5LwYIXbRPe2JSl6OHg\nePAZZVIG71gHFDOlQFxezmybEgNVoX+lHPsNiJ3maHi63etDz0Q3OHL3yENw\nE1DMSiULZskBWFY5kSeIRkpsu5Oyg6NuT/JCXlYyClgrXb/3WGlLuM7xxUcr\nzNeUpoadBUT6x+UMK532n1IzcsnuDKygYFDBSO9OYtVw1DAhHB11ubkAQ8qi\nj07RMd+TYQKgCXmmHBzCecc/U72ZsT70vgWTWf5KQUPw32wW0LWXExvSZIv0\nKvY7sZa5Ky/NY+5tM+P6MOP3CuQlmBOKTnBaPJYJMfAHzVa27kbbdieB/Zba\n1r2nc+raYh7d98dvyLlYA+zfA/fS+qJrfQdTnEnei579qP3wqXDoO2E9Pyw2\nhn/TDUsQ6GY/oPQ+4jrQMTBFIcddUW+54Ob5isJ3+sbrLaOJgkE/chjeNB31\nuTV5oR5CnoThtXK6xp7M67tAt9sOj5y0Xcjs3JE9yYXWNI+RFMHmpWMklMSU\n7nBOQg/Y/9iYr4IgSmPWTw7LF4PZ0FI6ZPatIcQztSdu4gKOgMbuQqK1xlGM\n4oI4\r\n=7mv1\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ba0603b14d5bab7bd99cca4003e4de82170ebbea","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.17","@balena/jellyfish-logger":"^2.1.59","@balena/jellyfish-metrics":"^1.0.209","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.2.2","eslint-plugin-import":"^2.23.0","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.144","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.109_1620980388519_0.7428489202577935","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.110-renovate-non-major-f91db8253145412b1bc749ba824c6e216467c317":{"name":"@balena/jellyfish-core","version":"2.13.110-renovate-non-major-f91db8253145412b1bc749ba824c6e216467c317","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.110-renovate-non-major-f91db8253145412b1bc749ba824c6e216467c317","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d798162ed084e1af9b2c3f8a4e6a44c1fcffedbf","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.110-renovate-non-major-f91db8253145412b1bc749ba824c6e216467c317.tgz","fileCount":99,"integrity":"sha512-Y2EzucRnE20J4b2GcwxOM0g5XGyohwe4GBenooel+JncD+LoCLOOhPV14LquA5z+3qtLacn07+P+QWDr1oUuGA==","signatures":[{"sig":"MEQCIEdax/9xdHw0QUtifdKHIBvYlmGom2U0G4mE4YRdQ9NfAiBlRFQKGQ7iJ+huBA7zMAIQvi6mlKAKqdkX2tVVztufvw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":747623,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgnqGYCRA9TVsSAnZWagAAywkP/0+6Dc8yGGuOUXx0ZkPS\nVkOTqDwIIhN5O3QSgz2BUD/ooQApPYN7tQgFOoVC9lsivHq6Pu+rI24g5yTq\n6zaF+uJKeIPXDo8c2lEa1paHiNMAZle+J7A60oahe3JWcu8Oa0mdU7tSIPbh\n9+zQ7+GE511pwzj1Rk1i/0OTSKCz4YbyIamkBT5StRTyUmnjjeJWXFTWQhum\nZ6fi+t/kdmD2BiQkusJ7xgiUoVvP55T0P2yPUN3MCJVNb5ENSSnjjqS2+Pqq\nDVOm0lctKjNFC47poJiWynsIDvVqFGBdw1zdrV3QumVekSdrDUYHY/sK+SgO\nAzNPZITg1R0/D8LhqfYIaionfxfsnxXEnQ1QpARqA4/KtLPaKxQWR86UGX0s\nL7EYRnlDhqQq7VyDT9jtW4mdC96KnBgoV5kjNign16XdtGbwOnmkBlGEySiJ\nMNrjPGlqeJ4BKQJRbd2odVhTJbcfgoc75zKCO4DNUBIaJ5GR/mCiEQ3JWA6H\nVx4PFUwtrAJMnlxmJxDm4+twDifHRoFX5H/qCvYPzkqatj8LYKq6Rjb2UruL\n33TqsRRZ6qJcdu//liwQJ4dwoyFyc8+kAQ1nBrCcWlxS7MsKDAX/ftk4z+8y\nfBGP4ldbAjj079qJ0LrKOmCJBmzs3LxFfbZ1Bj6dmrLvNvqmNZ0lzaSSyYK3\ncnvc\r\n=XUYh\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f91db8253145412b1bc749ba824c6e216467c317","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.17","@balena/jellyfish-logger":"^2.1.59","@balena/jellyfish-metrics":"^1.0.209","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.2.2","eslint-plugin-import":"^2.23.0","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.144","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.110-renovate-non-major-f91db8253145412b1bc749ba824c6e216467c317_1621008792154_0.6498583656182857","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.110":{"name":"@balena/jellyfish-core","version":"2.13.110","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.110","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9fe7f55d26148c8cf8db3912fa21fcacb5a45a08","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.110.tgz","fileCount":99,"integrity":"sha512-3NUKtP98IeSZJop2uFqKxgZc2eRSNjjuXnKaESIHit3/g8W6A+j1pCKp91xKp6a6n1O2hn9kShQ7nFw+VpvWQQ==","signatures":[{"sig":"MEUCIClpl+o+cpT4mNl1nlHSHrsnb0YR4QRUPpsM25Mayfc+AiEA1J1EWYVc3NStgU9q5MO/A9vMP44gRQMkw/aIobGkHXg=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":747563,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgnqKzCRA9TVsSAnZWagAAOSwP/imrI+uGfk0/GeYeNDBz\niLe0vPWk7fMv1a2xSwkOVblpkaned2U8z/HzvWQ98ll+r6ARPcXEgcBaiTBg\n7ICRkF85eFoaUH3BTo25Zh1KeyV9VA9W7Wc2+qtQdsPVMXWV0ntzBpy5M43M\nhTsHZDQesp1ItGYCNVImUkNV3w922gpPGA0DaDXMGI6LhD5SqDrjbMNBrpIo\ndGk6weV4NiZ3OiCrThwzGt3Q7qyZnHtQaEX4yXD4rQoJKqN7JICYKykDC+/k\nT8zUxvIljAT/jWk1SjILINrGAUTcRP58u6tnHwQxRDZbSE5ML1GDMqOXqDoJ\nkUJ2BXS1VxvDxHvlkCbCurvfWLYZLVttRUQ8XeQYDac5toOwaneJR6RWIisz\n/p2uVTmAFuzssbheramG6kbbGIdDxnPVlfE2yUI4dt3zWYXsV42BewotIISJ\ndFYJmL89zmY0boeR0/n/3aiyW4N+HgXpKEHwY71/L+u7Cp2ASiPHmnYR9XQX\ndxWwZbMUpfKCdzI4A82EmaQPR8A3ans0micFCSGzbelvnSOtGiHQXoDnQLCc\n9I2t9j9DfuEGDH1zVDJH/7mWk68JMp72mbRsvQmwC+p2vkah1LeiPAwj7j8E\nBSK2GprimMEYfrS8huBV4dFcCxOcm4Ip+qRIT0FEoQ+ndwbJA249Srs03DCX\nNpDJ\r\n=lxcy\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"05f5bd293d42b3cb0c30a0d99f6c5de5878c2a1b","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.17","@balena/jellyfish-logger":"^2.1.59","@balena/jellyfish-metrics":"^1.0.209","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.2.2","eslint-plugin-import":"^2.23.0","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.144","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.110_1621009074508_0.25121585201762664","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.111-renovate-non-major-68eb467abf7b598b92a233f23d7bf326f0a89154":{"name":"@balena/jellyfish-core","version":"2.13.111-renovate-non-major-68eb467abf7b598b92a233f23d7bf326f0a89154","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.111-renovate-non-major-68eb467abf7b598b92a233f23d7bf326f0a89154","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e9d2ed356f898d4c6af375d8d551779e3183ff84","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.111-renovate-non-major-68eb467abf7b598b92a233f23d7bf326f0a89154.tgz","fileCount":99,"integrity":"sha512-5yrWTvpI8zCtCzLBrua3kvuKeFsxglbYE9gvWrERIj1Mjbl8RDEjTlD5vZj+brS40JKnKbs6kuuPJ/BG9mRDqg==","signatures":[{"sig":"MEYCIQDPqMvaCAYvce1X1ZqwBqlMQgS0KEyBvRDBTDryvWuKTwIhAOKZwCkDiLW12OUKZtgtqrY9tO0+7Sx92s9rSvh3M8L3","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":748046,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgnrpmCRA9TVsSAnZWagAAvcMP/0HYgXf7xnrKnAUN0rZS\ndM+mDyshXveodIqkD3SNwOuTjN0EOYFVipiaWfJEz8HxpmpkV80iG0BqYdSo\niQSt8QQuK/QGbrbj6IgCN4Xg+uU+wSVuoNUzBGyqx8tOwMakSaJ4mE9ZVrzo\no+4ChNLf6yDzJH4IS2Do+X3VDMIVy9tVffy7QQyMuu+2tfkPK5zIZdJtI9va\naEJx2rLQxAksFllPS8QbPuRwzG9q2A1295qmFcowhAy6oNKJg1dJQAkllH5f\nD2FJaZmY1FPlOy2DwYEULUhmDiG+Le4Ww0K6pAnzM3F/MxBEd6FDs1FhY+oB\nAJblIvd4TivJlZOU9Qn7oYMV9vpes3H41Nwgida+B2FptAebnj0kAcL9fdvf\nw/uN4kY/vLYQ834LFhL6HkXczDQ+LmO+VOUDGDXAmMDCPUswrSS5b8+icud3\n1QqKD+5Tc7r3HQoXLN8alRlf1bvjAEcHqkp5VMwPcx+EvjtHGLs4vDFAHYvv\nT9XuVClzetdr1fyWzk4ae+NI3AoFtLQHfrPUew+GCNOddpVxJlANG85KSfxs\nfJAbfmYBWEbl9fvscqCH7+Dk+RAx3Tpl23QnDShy1FzJAzZjkWIAkLvvo3vi\nZNiYNO5CjHdBJyp0LWtpAseIUz4zHQQSV5yaNLoLYwkJUyuHOPd5jqhtQJ8Q\nkyns\r\n=cow8\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"68eb467abf7b598b92a233f23d7bf326f0a89154","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.17","@balena/jellyfish-logger":"^2.1.59","@balena/jellyfish-metrics":"^1.0.209","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.2.2","eslint-plugin-import":"^2.23.0","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.144","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.111-renovate-non-major-68eb467abf7b598b92a233f23d7bf326f0a89154_1621015142449_0.09641869654009771","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.111":{"name":"@balena/jellyfish-core","version":"2.13.111","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.111","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"92353f886e897ed526b0da92dc4a558239f5796a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.111.tgz","fileCount":99,"integrity":"sha512-db4YK7R01apNZVKYVwS3o2mpUmVnlYTga1GOalGKDlbzyzc9s8vNXTyhp3KQDeoqgMzyJN0qyN7fWaTMlMNh2g==","signatures":[{"sig":"MEUCIQDB6e93+Oge7uk2cwDO81YLK9jenZ/l+X8XcsA5Ukz9dwIgXh/LPB6rwq9bEqfUVQW0UXlZWlmnKES7oxgN0huWkVU=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":747986,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgnruHCRA9TVsSAnZWagAAevkP/iT77Pqrhnju5YQ4dC1u\nahgdNgfuttZUetPa/d3s6XODu5xyPhOluUYuCkPDhUm3z7SPs/cWIhnkBFSm\nueJ8nqQkkxbtlqZCSLrUfEI8tm7dqwoR/L5dZNoLRDLnPlo7xUBhov+4e27R\nMrr4+rXTf6gc+fP1pIGCCzm7c7BlTRYi37OexPSQiedvDdbzQQURkZ1PCNmN\nl/tvyfzebL4IUMqYX+tu1V25gfoTtF7r4NOojM87uL8sOvPTGy4RRCeRjcgJ\nHHtHxcd4UZOqT/C+XMhlqhCEvxIfhV7VD4rTN1u9wc5eg3Twuuy/vXDnlGWq\nQF+40CdkFdPr8WGv7VlaAAsNC7gYChWIkGaAT5gbrYoHbUKQ2BFfGJXu2QzW\nzAfAGjV0W/g51DYvPuEULDhBi7FBd61ZjmA0MJHGLQPvA5ALbsE3zYlxrePQ\nuvLx/6TOEQvLShL5ZwR+WznArNsrwUSh5Ff0af9OmyYuG14X8KGeQ6rslLYH\n+vwIQqNL8Mhf0n66VWV4TmncwR59+KzmCtzYtk0xh1ZfaIOPJxsezCmnE44I\nNXrqbP5sAYmsNkcU+0uPxCG0XN9SvivNCvuq8ntHViC3hYdmIYTKQdr7vcx8\nZiL6+/Ne79Q+h2Edl3gATh7bDg0wFj1BWIbZCto0RbP/wklofIEqzi7OqJ7o\njVEU\r\n=ekxH\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"3c2cf8381045e6cfec4c20744662a3b76b42d683","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.17","@balena/jellyfish-logger":"^2.1.59","@balena/jellyfish-metrics":"^1.0.209","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.2.2","eslint-plugin-import":"^2.23.0","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.144","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.111_1621015431134_0.49475177066544784","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.14.0-typescript-conversion-2379ad2763b23630c66c87dd198a687e083e895a":{"name":"@balena/jellyfish-core","version":"2.14.0-typescript-conversion-2379ad2763b23630c66c87dd198a687e083e895a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.14.0-typescript-conversion-2379ad2763b23630c66c87dd198a687e083e895a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"03dd9edb22c8963328953f31790d3f10ae97f807","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.14.0-typescript-conversion-2379ad2763b23630c66c87dd198a687e083e895a.tgz","fileCount":190,"integrity":"sha512-P1BmqzsS/d6hwsSQ2UohVUx2sfEMluktR/siLWY+ZGOU7B2Zo5LErUw//U+0UCtINNengpym3QsPb28LhB01qQ==","signatures":[{"sig":"MEUCIQD5O7kNSoDJ6BubeezZFWj2bDVAfCaAljKfI+01uvd8+wIgJJ47PzaZZCjETLdGd6tVwgsEjAhYCF9HaaFTCEJfXhE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":715478,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgnuR9CRA9TVsSAnZWagAAbmkP/jXYG8LC5h6aeP02suH/\ngbbrU0mQqdiMMcDVt5F0qoJb/etI3y+BXydEv5UjvcR97rFEmvAGF43UxT4R\neQsLSeKoCCaUnC1TLmA42a2ZL5tn9+/EssiWkrLuAjS/hlTJy/S2b9VlvB+x\nD6u2afQ3UujGvZKhStiOKfOCXpGyy6/gfHdudKJsEU9jWWo1yHcIhtdEiVm3\nAWgiUlktUxJykkg+fT4AA2sL25I4TKKJifc5EgFTia2NDSnq25Gi8C68KheV\n3UOLdVCMvMuB9lCM5OdKqdIwbpZVJJ9eEVpPH762WIZBU619/7CnqlGm32Do\n4i80sL10fxiUB88a0HLmDe1bM4cSbFr687SwS7WrP8TISFPhfLFmp7Z0pK5I\nlsw1JcNrQ7QXKmT9Z1zLqk/4Ho9WmR6AW4beDaqiFFKfh4B+4xdDTdxdtwi+\nORmbR0OUEcMk7TSIdVRAjcUHMhFszdfKIjYTE6phqQbuB+l4MkSfJOiR9PvY\nl+mwar8HP6jZ599K4C+oSNbGc8YTlL8f1k5qWgUwJ5Y461LWjj1xeqWr50hV\nAJ+3qPPMJX2mXrlpTCTKcqj5ZOwd653wJDdpVsO7XQsLcu7T6h2o8fcv41BP\noCvNp9Z5siB/dPacM1IB9XzmTZEmjIIuz7Sa5cJQMQ6OVOZe49DZ98ReywNt\nQdKY\r\n=iBQJ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.yml up --build\n```\n\nThe tests can then be run from your host with:\n\n```\nLOGLEVEL=warn POSTGRES_USER=docker POSTGRES_PASSWORD=docker make test-integration\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"2379ad2763b23630c66c87dd198a687e083e895a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint --typescript lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"catch-uncommitted --skip-node-versionbot-changes && npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --typescript --fix lib","integration":"jest integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.34","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.17","@balena/jellyfish-logger":"^2.1.59","@balena/jellyfish-metrics":"^1.0.209","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.5","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.2.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^5.4.2","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.144","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.99"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.14.0-typescript-conversion-2379ad2763b23630c66c87dd198a687e083e895a_1621025916998_0.9063696826041565","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.14.0-typescript-conversion-2e0d5020c3342792b2fe3f24e735bba8926454e4":{"name":"@balena/jellyfish-core","version":"2.14.0-typescript-conversion-2e0d5020c3342792b2fe3f24e735bba8926454e4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.14.0-typescript-conversion-2e0d5020c3342792b2fe3f24e735bba8926454e4","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"faa85ef19859cf48368350f8c693d94f56b385ee","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.14.0-typescript-conversion-2e0d5020c3342792b2fe3f24e735bba8926454e4.tgz","fileCount":190,"integrity":"sha512-pRQBRKHkxHERH1RgCXGWmJcyGDj8/QsKtti9+Hdo/CkR1+MV+ZGrWcmdMMAH3O3MToSLTaRLiZELSplG/3y2vg==","signatures":[{"sig":"MEUCIFozhtFq56zA8khOvfvk+rOyHY7ZpxwCqvyIBeP++H4yAiEA9G80f8UmtVt7ag8WDKIbpXBWESDuBLln8OSH3psEnAA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":715500,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgnuw4CRA9TVsSAnZWagAAlgEP/1/HbUfObxbnAfc5ndlp\n3Otw/1pQH6nLXDL1LmsmNcxcHYZLpI8dMVYtvuZTn8iEIXPB5nbcIqGq+Zgt\n0HsmwBPNFK/w9Eiv2wbOivt5o9vknq4VDeP2OQR6uBCmOFgvOxE3A0Tz/2ga\nVPOIwrN6chLYP8bvGYHyR+cK0duZtkar9sPXY/8NwKM16RdF5nu50vXWPUem\nLMV6X1ns+ZPo2lohZregoPhf+XHsztq594KEUmwi8jvp2o5DPsRqq9xTd1PL\nL4CwH2jZ6uDIUh1tjirPWviewID1GXvWTfVG0xxyGEQeCIGc2iG6Olq0uURE\nmEbHzFO2eyp8psOtmyuL8QBCk39w6o+RF6x/fDNSQ3PHR4LAau8c3aBIztCM\nEckRIru6wvcLATnIzSWixqpu824jSbrqlrnkRBZeOXC23F7zIa9bT2jNE9ah\n3yWkA+ukMOoQpH+zednzwrjj5aLjsC3IGrylZHCV+3mWFwny7g8rYc7PhVW+\n5fOllWZQbmUOWLLrDLN62ZkOFjqBqCFAEXEJBAZFtx1BhXRnnHkxgVK5zjH+\nexrFx3QYEpzL4VnkJuza5mjb+trUcxGYO4dTN4cUwhmJCBr1acTkx/pX5/cz\n/T6Rx13Czskye3ORg5dKyPsoBLFmA0BknUHdBAgoI8cF8ewvskRiINljckcK\nIZ6s\r\n=QwOl\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.yml up --build\n```\n\nThe tests can then be run from your host with:\n\n```\nLOGLEVEL=warn POSTGRES_USER=docker POSTGRES_PASSWORD=docker make test-integration\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"2e0d5020c3342792b2fe3f24e735bba8926454e4","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint --typescript lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"catch-uncommitted --skip-node-versionbot-changes && npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --typescript --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.34","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.17","@balena/jellyfish-logger":"^2.1.59","@balena/jellyfish-metrics":"^1.0.209","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.5","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.2.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^5.4.2","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.144","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.99"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.14.0-typescript-conversion-2e0d5020c3342792b2fe3f24e735bba8926454e4_1621027895520_0.15275799961038472","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.112-renovate-non-major-3182a5cf2afc6144384200e67c18f29ca840af20":{"name":"@balena/jellyfish-core","version":"2.13.112-renovate-non-major-3182a5cf2afc6144384200e67c18f29ca840af20","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.112-renovate-non-major-3182a5cf2afc6144384200e67c18f29ca840af20","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0a6fe4e3ec92df5c9188837e21288f7664a0361e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.112-renovate-non-major-3182a5cf2afc6144384200e67c18f29ca840af20.tgz","fileCount":99,"integrity":"sha512-u7h2SDA4igk48HYRG3xP+i30KgN1/533y1EZD0SX4vwPTt91BDEpNfi48Zha7smDs//1g/kOZGTLF/+Ssppeeg==","signatures":[{"sig":"MEQCIG6ib9Rzvn7u0jaTG+7nVTpLFtjudI//Rz9Y5kVGtjyeAiAkCkXRtPgPEYCm64metKYDAWDdZYkicN7mNV+xfZxrbA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":748399,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgn3oeCRA9TVsSAnZWagAA4vQP/0DL0GlI6ZbLivuqJms3\n9rAsxeV4na6zRAfIFBmzJhQP2Ow1DcegicIdxZcZp6EBnjTFA8WKn5GlVUZg\nCehzcLOKK+bRAOk18WuWMAzsiWpquUcTjdOaVZNZbWmLFc/9DeEvzZnOyR2m\nOnI4ytJNJL5okvw6uZrHFElTQHvYpkxDic7bwcfS8bK4R72+mJHsMhcOIVhZ\nZ1PIHgEpkzpAuHndaTsQKn3HIMASJGNLIvJb30Co2LLv7+qqmROR7uCBdFJR\n+cwwBj87jo02xzkGGBdQlWrurwdd7CdznEOG7rjk8+YD5ffOCFpnUpE/++PM\np89Y00RauBjvI8u50F7kctE2Rxu7+KR2zlfkM3q6+L+cuRY1b/B4AdlVdwr3\nfGomX7Ofpps6vTaYdEbiJgdlN2JCfsMrJcA6G+UHH0GQRiX4cOOhYX0CEjqx\nKQ0Uk7AMI7DTEnfw5TDqTvMxScY8cDSlpKIjfIB/zu7q3tWoiR4HXn8n07sc\nclYXN4Vrsms+O0x0ePDbNQYX24JqLi9ClSV66kZK1qSVPlZyI2Npxrae2vL8\nasYBicVF79PXhcFk0NvEi5YUqo7Xhc53WAMkFzoTXf2WwV19lbDzLc4ikNRC\n/F0VH1YAtDOckDgvO79EiNPOw1zcEVsdlDi8JYJGfUaVA91qKd7jVzu5RV7V\nD9LV\r\n=s92l\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"3182a5cf2afc6144384200e67c18f29ca840af20","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.17","@balena/jellyfish-logger":"^2.1.59","@balena/jellyfish-metrics":"^1.0.209","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.2.2","eslint-plugin-import":"^2.23.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.145","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.112-renovate-non-major-3182a5cf2afc6144384200e67c18f29ca840af20_1621064222037_0.3232723412559406","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.112":{"name":"@balena/jellyfish-core","version":"2.13.112","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.112","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"95ff9a04f2c8e7f9b5ebe9c990d0a1827348777e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.112.tgz","fileCount":99,"integrity":"sha512-fpCV6+GLg1W9HTV7ygq9Gg99yBQLAyqV8/5ZDyukGDY3oCtvytaRszEm+MyAz2SdPIHmue2pXvq3zuZhL3jHkw==","signatures":[{"sig":"MEUCIQCpY8VElprrrK9TJF8V90KOSa7qmwyMJKFDNOcQ0aee/wIgQrfurLj+Et+r0AOxaktpewQwX2qQEL+ZORZSpRWeaSs=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":748339,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgn3tpCRA9TVsSAnZWagAA7hIQAIbrUuS3XoDdjq7eEMoA\nKME9ar111nDHFJlRXyZtjrc9sFqPB1bIuOPIYWuDZ8x817zW6716eYdvFApe\nBQBg7+/LXHbVWJ1MTz9CLPyh8QRlLYutb+FWb4VwLeiv+Sbl/9TqOeP6JKFe\njEOvKk82QCN2HxLg3sfN7pdSUju97eLMySp1AB6AZ86q+PAjaQ797e9C+IrC\nQvHH14QXF/GZKSLeIa8xcPbiTI+fu9PJ1/mSJSwCXvd6gAkpLrh8qfofT2z+\nPP0K9m96ZK/Z/uEj5IB5TPU38kb45ejhSHp8SM1hzvskyoiSZYXOh6Z1hmd7\n0a9RH/0a/pzlcTfecusBVq931OYh2aRrxhAuo/Kgcr70jGKX+aimRIvYbpZr\nkJywFET46EcXvt6h3i2LdWfhLfTArPG/z/GG9qFNzphPwvN1VB8+zTepQE3s\nN1XQNvaSTPfReAjofpWICQfU22ikzhFkeaW3TD1uoqlKVAzUgkOKFZfbs6Gn\noBxUJNhjG1eABsow+JqjStHIiXAJbqG7yivef5+aN4IPcAGMDyU7RoAHcFX/\nO+80RyjUKtR66l28vJhbsTOssl5c+9GpG1rR6f0Z3RDJleaDF6oXt2knzOZ2\n9bQF7XPCIj//plBROASJlrwMRnUyA5E16bKBbVrhgp4/PAxP7A2Gv+wEA9gd\nNgxE\r\n=jNu9\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4cb8bb6acf3009435b825943529c9b8c3fa1f5d1","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.17","@balena/jellyfish-logger":"^2.1.59","@balena/jellyfish-metrics":"^1.0.209","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.2.2","eslint-plugin-import":"^2.23.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.145","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.112_1621064553189_0.19061714792292928","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.113-renovate-non-major-cc7713d96f96f2d6df2e42b69bfffe8317546c20":{"name":"@balena/jellyfish-core","version":"2.13.113-renovate-non-major-cc7713d96f96f2d6df2e42b69bfffe8317546c20","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.113-renovate-non-major-cc7713d96f96f2d6df2e42b69bfffe8317546c20","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5de7630fdbf667ba00671513409f561f95d9edcf","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.113-renovate-non-major-cc7713d96f96f2d6df2e42b69bfffe8317546c20.tgz","fileCount":99,"integrity":"sha512-zch2yD4Fkc9XZHyNbairYXu7kMk48GLhF75Zk7Nun0wySVoOb6OUqqzIuaoLbBr003DGx0ThmPsjATHqp3NX+Q==","signatures":[{"sig":"MEQCIGYyRuFNiz62VfZcYJi0CKUr9V+DLCVw94O6V2vaBgeKAiBXzghHPS8CfHCkmmH3uB3roexJmCPOzJF8ZNPB+/srdA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":748752,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgn/t6CRA9TVsSAnZWagAARYwP/0KQ8+CsTpU+UOnL1Qkp\nZMUhhy8uy5Zjs9eBHEHNGcDW13wruQ+bQOzFjlL5gZ4uOoKAF++f/Zvkp4Q6\n32/rrxRXce7LtjG+ZKIFKHF7ipsZkzxJuuDt7jPRKfZIx09x78rc/bE0nxsb\n8JJTZ1js747AbcuZ7pt4nzURImN8UshiYLWH7T/H825W1YHDmmVcAknK/Oi4\nNg54TOs6j4VDOtagMU9swDvSita5KSomUU5537O6norZL/WVwx8nJo58WsZO\nZhKiv3OIafj+qrOLQbD78TMcwPYDEdIWXnFo9DlXqmZ8jUwRIYxtDVsdK+fO\nBmc5exPaYKlc4YZW5DdMy65zJPrnEImEh5go5Ox+V0BmktbvjKNkhVbgS3q2\ny+B+rDsrhEwQqotRQgmFR7U1rIbWiSSQeZuGcimQl7PvQDvpPCsKDr+QSyDF\nPSaLG6Dokhbe/RWmLWN+TK9CWxhf/t1LocVeE0fakcxElzjhkOMxPP23pSZh\nJgWZVCDkwKm/GZLLDKDbvzxKT2pKcW5GZln5Ifcc3Z8BfozJFotgYNKfPmOA\n/vkDQxu4PwOro13EDwlOauYblRF7Azf0dRkv4E8OF6+2jovLjkmj9fOAP4UM\n1teHIG6LRo7RZIf3KSOlW58/VgWZ5lle5sGrnHWfP9CStvck8XzlvezX6vCa\nI2gx\r\n=+WOU\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"cc7713d96f96f2d6df2e42b69bfffe8317546c20","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.17","@balena/jellyfish-logger":"^2.1.59","@balena/jellyfish-metrics":"^1.0.209","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.4.0","eslint-plugin-import":"^2.23.1","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.146","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.113-renovate-non-major-cc7713d96f96f2d6df2e42b69bfffe8317546c20_1621097337942_0.07855271169242983","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.113-renovate-non-major-917c4a0d403c2fce4d0f0a5fd0f0e4cbb82ae529":{"name":"@balena/jellyfish-core","version":"2.13.113-renovate-non-major-917c4a0d403c2fce4d0f0a5fd0f0e4cbb82ae529","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.113-renovate-non-major-917c4a0d403c2fce4d0f0a5fd0f0e4cbb82ae529","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"25ccea1c9052af6f2c58da6b5f8bd112f9403d86","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.113-renovate-non-major-917c4a0d403c2fce4d0f0a5fd0f0e4cbb82ae529.tgz","fileCount":99,"integrity":"sha512-3RTsw0C35fH9Tze5ZRNIOi71MdZA7KkPkdXL6N0Pxd2/XUx4dANSAdY3GOa/FXCiNFQsoyc8sBF8EbdSqhn7xQ==","signatures":[{"sig":"MEYCIQDDOG33MnkmwqueTdiI3v0CVtI5QOZgpB37dsOlIqdJ5wIhAKNaNEP5Y86zryjBGrfh0jvwdnFcW3jLaQhRCpRqLanZ","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":748752,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgn/zJCRA9TVsSAnZWagAAlNcP/iAJvzSzzQPNi9csJBEr\nRHkF6h5x2hxjddrL/gfygNV0d8E45CfFWgqMau3pplf9mfrExFH0lsVfobFT\ntZtqath+V7aVVXmM83rxjlEQA4yF5qNuYykXqme0kZF5QtrUW2QpnDpo6Aar\nnZXdXiBFJdvYHJV07L35BWAI8NavlHFc2F0vpwjY1lDjOkF0ohItOlBaz+I9\ngdzJQ/72ZaAM+0ODDv4vWUkIClYQyBc+DJaIpKqkOwmchIoxna65l2WXxjF0\niUsjBF3eiuV1QFeBhsa6VviFyxstVo26/A6hnQbEvmLQ4MHAf/h+R0QnJU9X\nSFFnmRAccC9GuX79oi1w51GOdsdlLt+n3E/q8UUcm3MxXjlnOqAnoDWLD66f\nHxxIphqIwwc+G6yBsl42XHTkUvZyDSpr0mBbkE/AJPGfJaZ1wqL17rIuc/dD\nlUtDdalFjj7un4jlHkCdpxwTrniUudh8RtwqmQpsL5QjWpTxLmpY7K0zt3Si\nKLi1KJiuJ59+1IBgjsrreGKQ/bdsG+DNwhknC021XAInM3iRKkf2EmVbWNW7\nRjjAwGa7pPxT6YIL51oO/SD694MbQQmsXIytTNWZa+GOFWg++vpzybf+srRM\nz1M5LT2IrvOfIs8HiZx3PhVd/BJ0rZlHjS4etH2HZ7NGE5uZj4hyotgWqHSe\nrng8\r\n=g4bc\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"917c4a0d403c2fce4d0f0a5fd0f0e4cbb82ae529","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.17","@balena/jellyfish-logger":"^2.1.59","@balena/jellyfish-metrics":"^1.0.209","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.4.0","eslint-plugin-import":"^2.23.2","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.146","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.113-renovate-non-major-917c4a0d403c2fce4d0f0a5fd0f0e4cbb82ae529_1621097672903_0.7342515637357188","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.113":{"name":"@balena/jellyfish-core","version":"2.13.113","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.113","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5a901eb6c3c15d56c9b075b63c589855a12a661d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.113.tgz","fileCount":99,"integrity":"sha512-DLcKhlvgwlGR52GAifgw1yAktQGbBZWW+qCwP2LlogwsxGAoKuGkNzU0mOPdQXIgK1vLKudVBkJqtTUzr8uhzg==","signatures":[{"sig":"MEQCICMUD/Afa6wUtvSNmsaInYviOJbhoiw4kk2n5txlb3EGAiAZ7NFoUaSaW0hWWo7Vd//BKlfO35HALG5BP8I8j2cwLg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":748692,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgn/3YCRA9TVsSAnZWagAA14wP/iB2EgQhsn+TAoraPX0l\nJnmqWMszl7PbFgk+XnXyKl0s63xgPs7wZtBu8R5WZg//4rU0ky28TX1Z0XJ+\nq/jXdkiEZ0e6YgfEw3ApyH2yj4id2QI/V0fAOLMwdcN6n7qOFQShp4/cZCKl\n4rJXQPzyKEEIngSOqizGr3xGtTBH5z84D/5agU0TG5SKvBOjhen0lLqWZh9y\nZ5HTf7oHsKgtTuRpr0cpZekyiy/vJQR6NUPNDcBvx+fTs4BlJLTvV+Z1ADfl\nPsHdh2sYfzR1vt9+z+4iCH8So+Cf1rog+4cmwzMrv4LuPZYGult/S+aMVi8Y\n8Q/99qh5iVSGe46d1SDBmyyN4kNjrJPJdP9LjeAiPar+/7bjombzydI671hR\n6hulFdlNJNoci3LU8xG57w7ChxL4B0lW+Z8K+mrM9MRjpYW5U5lFQZjMIRqn\nN7TbffPIE8ZgJ/pknh6vqb31Hy0p0h6fxX128AWleA3lj6qNPG5cLqjqNj/k\nVx1AJDK3ZspKoOqKxKEy6TYp4Ogif7JtOe1d9BMhG7fiqKPvNqphmKe5pfzb\nVwD6WvkTIOFNFesFPumKjXpjOKLRpelVoffAxvMwhxvgPOYrwlx+RTBNd3+/\nzfth8m0tiw/TPc2y4LArb1RCZh9SR6QW1x/4fjjvlauDWfn4CCUyUOF79HoM\nktG7\r\n=zjrH\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"fba14efd2d361744601d348a47d51c370ea08b5c","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.17","@balena/jellyfish-logger":"^2.1.59","@balena/jellyfish-metrics":"^1.0.209","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.4.0","eslint-plugin-import":"^2.23.2","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.146","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.113_1621097944390_0.013066462662082223","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.114-renovate-non-major-ec5fac6682736e2e036e68e55113387295e7be76":{"name":"@balena/jellyfish-core","version":"2.13.114-renovate-non-major-ec5fac6682736e2e036e68e55113387295e7be76","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.114-renovate-non-major-ec5fac6682736e2e036e68e55113387295e7be76","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"338fe90ce4e99dc096664a75392b8e8819061947","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.114-renovate-non-major-ec5fac6682736e2e036e68e55113387295e7be76.tgz","fileCount":99,"integrity":"sha512-1WhJW5z3Qyjw1MQQXb5BD5f0Pe6vMM6MQ2XXKJLFq+D1QDTgD0f9zlsfBs+5dJ8rHhxO/9vxyDufXxNB0PA9xQ==","signatures":[{"sig":"MEUCICvdOd4oTfSjmwJUIm9wJMx0qEg6TvHEK4EdVGVqbY4yAiEAwNe8i1J6SVOnlwhB+3HHQDJbs5BfXsz/AzO8FTR9yck=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":749105,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgoA3RCRA9TVsSAnZWagAA/V8P/RdMIAQVozgOnove7FNi\nBYVpGC7lfkMlzB4MICxPoeQrykHzu4c/BI8yb7NdOnJKT2uUXMrmpS7eXJi4\n8nZJXq0qkBg6p+sILg9015ZlExNe7beawsEdDwe3sLQwHSFHYysWLkztHadM\nPJMfE6oQ0gx12JYZ64tVcNXk9lcNbzEwc3F/j9RCjTJS8na1FpSYkuoqSxHo\nox4yaRwbFa4hHfSaGCWB5NjJxgrZ8RN+xQs27qQl1eYKfM2IEixe6QJgTI5q\nBl3keJFsyLAZHPIXT/XizE/JN4rzZzKbs28CtfVDtTXt27DreZg3QAXA0JfN\n3r9r4mkHqYfnzaH4aQWwy9I21J5WkjvQ6IvT1wS2yIWUfWyR7oqX81Cb90Ej\n0Z3IDYfh0yw0zuZ2QMfKgfFnWE4hJuOei+6Bm8CIs9opiaHM6JiPJ05wKTAZ\nXQrL1OLzggVPrP7wZbQr7vuBQ6dMTg3CTJ67F0Ulk5ACQET+csEHgAoHUeop\nrvuP9xIPL/pZ0z6zKqunhkXh51CG9ruc0vdCN/2NNGC4XfbtnjsX6NhOifOU\n/XIeyH5mh2w6TlhscGFDV0nJrZOYb143qCUSRznPY73Xzpc5itj0buunte+B\nWZ9xLsKu6fv2FMJ/cACMw+VQpKXF3N2Av7WCl3pAu3/ESD9r2BrYsz09Fg2C\nW8uq\r\n=meyu\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ec5fac6682736e2e036e68e55113387295e7be76","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.17","@balena/jellyfish-logger":"^2.1.59","@balena/jellyfish-metrics":"^1.0.209","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.6.0","eslint-plugin-import":"^2.23.2","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.147","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.114-renovate-non-major-ec5fac6682736e2e036e68e55113387295e7be76_1621102033086_0.14588915731187968","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.114":{"name":"@balena/jellyfish-core","version":"2.13.114","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.114","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"cdb0e7cfb6b310e0e54d5dfb9c3322a3eca5e0bb","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.114.tgz","fileCount":99,"integrity":"sha512-aN0MWkl6eTgcSYmhGYLCchXT6Cwwspp1D8y6nrHwk+OhN+CpT1aoHvZ4/iEoZfTRiejhXdqWVCnzWN0/7DvApw==","signatures":[{"sig":"MEQCIGjWyP08t/kjsJun5nequ5edMQ/7TlCC75EKX725/sV1AiB9Y4Gw7oi3J3mXOQ5+1JI3MbVX2acuf/3NubVU2Ykq8A==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":749045,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgoA/jCRA9TVsSAnZWagAALeIP/RF4mzXIiFvcSAGBZtEi\nLOXym748ENjLsus8nwnk/5M+yGgpNZtzxgNI2forV4pY8DaCck0LfpBqVCoR\nMklwED2Bfqky4GRHN8K8pcjUrzL1bMqE7IUvqUrgwdn6/ufKaL0UOzOJvatj\niDlrcaQNdXqll75aDAAmV0mQR4XK7cyFzbWnIUnlzgnnvaU5XzwdF5yRYufl\n7zbRCGaPOS6lT7ZsrMpefokXCgU8jThzmd7OVBpAnpYWXa9mhPPnWMAeVqZi\n35VTXt0m7FkaoYgQFh9gH5BGQ3IF+KheU31R7qhrDPuNq96NcY1dF1OJhppZ\neNSK1StdqolSTS9+K8dBi73djqYyKdhABAG/MsVXWNMCkuK3fkX5MtGoWlLd\nqy3Cgc9MtdQD9bIerq1IMXdf7hUToT5d+5QUhn6bxitsEfqirWX4Zbyz7PBZ\nJiuw/E0A1OVD6SRl7Pnyr2VvU9j5WCW/P8ac5z8jVqdD77Iz+IZTLL4cA0nC\nZMLKf6J+SGAJPIN9Fsyr1EVtz/3hEOA+SItiUjfd9eNW5AnwAl2rcjXSYqJj\nedDUwiuJ9Nk5IfdESC+L1+vkQJKMpzGLqudbCHaTTlIPxU0o5Z2P/tGc7laV\n3WiZpAvMBPTulLwBlKSH8/p1ZigF1a/xgDDy1G5+TBTs1vU2AhtGQccW34dl\nx8pV\r\n=7R+n\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0a912e029a11f3b56ff33617ed7a6f22f8f29841","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.17","@balena/jellyfish-logger":"^2.1.59","@balena/jellyfish-metrics":"^1.0.209","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.6.0","eslint-plugin-import":"^2.23.2","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.147","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.114_1621102562817_0.7086661645064136","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.115-renovate-non-major-8c1ae376864faada40887af84fee932947b500b4":{"name":"@balena/jellyfish-core","version":"2.13.115-renovate-non-major-8c1ae376864faada40887af84fee932947b500b4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.115-renovate-non-major-8c1ae376864faada40887af84fee932947b500b4","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"bbd8f654319271b39d1b907517fe9c33bc7d776b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.115-renovate-non-major-8c1ae376864faada40887af84fee932947b500b4.tgz","fileCount":99,"integrity":"sha512-kpe3xdZ9yvncwaS/nLyYKxhzQUq/P9+J4xOjrvI/sbfZzNntLVJsykfqylFik+qUg4TmkR8v3OT53KVgtzt7AQ==","signatures":[{"sig":"MEUCIQDkC4dfYL7yTD6hjRc790kk3A7ty3D5+oNryHRo7QRXwwIgAhjO4ovqsKe2EN1l4WV/0eOOuYlyI6c0CkMJXDiRC30=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":749530,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgoE1/CRA9TVsSAnZWagAABI4P/A3NcUAEop6cTn7/2XNf\n7buQobjuYyYuchi9r9kE9PCHOGWJhwpw/zZBrHLJQtt4LLmkJaHrO01V1bAM\nL0UMpy7HPnQmZnyV5jW77gDn+QfTMSYQxJdFduA9beHfs18eUwMX993qsVab\nRRDbqqdqxfiYK0bJKEslqbTXEdwTrtCQjWEfZkUBpcue+LxqXBxCkGOK1HXF\nFdPffu/BKLK2Px78RMd4Qr/A+urKeoMmuC1+b40wW07zMXJBrFNnXnB2V59x\nHBe4J48ofy/R7Mj4JsLzucHyPomNrKH1wjxPu3NAaEQXYdlNCaYBkDoGYR5O\nCIMLpr/vQDfVMMXfsVWraZTo7DVnQNu21CikV0E2PYtwRCzX48bJ3q2/m3y4\n3jiPHpNR1xNW21BSHhr8BHMWbrMUlB5VQzdHZB45mSpbk0aCOCioFQmVMGPW\nn9g2TSJ7eaVEMkXfIE9hy9IaWpKXsj8VHazJtAx+C/ud4Ga4QNkH1SgLNOk0\nllwaJDiqI2Ys4QC0Ej42yaVHskUlHMoP5RPfFgnfQAKsefX5TSAHmw9aylST\nAxaKeF1+rrbVenpPQGtnI/G+B7p6waDVNLN76H3Bx7zdLvxNhhU5+q169fpr\n7XOct8GhlxK1wgY0Yada3YTy1I69/DNXglu9+ZyxgT0QyPU2BH0DpTsR+EZ7\nFBLO\r\n=YaIB\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8c1ae376864faada40887af84fee932947b500b4","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.17","@balena/jellyfish-logger":"^2.1.59","@balena/jellyfish-metrics":"^1.0.209","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.6.0","eslint-plugin-import":"^2.23.2","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.148","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.115-renovate-non-major-8c1ae376864faada40887af84fee932947b500b4_1621118335054_0.29252962491996026","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.115":{"name":"@balena/jellyfish-core","version":"2.13.115","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.115","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"80b1c67d063ade43c8a2c0b2a9b2503034a8b8a7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.115.tgz","fileCount":99,"integrity":"sha512-RFEBFgxlYq+BPZikMCMf2KsxG8F1hSt4Hl012LlJlczvqLCqwJl4TrZm5rL+9UES/HH8CTOiIPC81xLn6t/V3Q==","signatures":[{"sig":"MEQCIEHzmkcdpWq1krOnTBKa1luy45CyH5JOmYAxJtOMQgFqAiAK8Gdcw8DHakECy6h8DcIjaThRk5euhFLX4gxMsdiyGQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":749470,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgoE7UCRA9TVsSAnZWagAA4NEP/RmPBFs0VQEk0Zg7Lylu\nfN6o68NX41/5pEnXWyfYtoILIriK1BWwLCI7sIPF6Ngwa+xCkyd2niAS4J1n\nU6Yp3tx1amgRROYK+8WHbBrT9AqsGXAHRxzb3veIcEL8VoOBU4qr8zDaFEht\nYAWvpJpkfZQbftQxfYTmoa4Elyo81KNBrW1PUDQeyg7MysIFj1RYhsmn69YV\njumyd0uWtX9ysZsNRfDX/UHIzAg6GwSHnsDZHDtpS/7KGb6LxGT9CUAsbBIT\nQZu2f3l767PD5LyI1zBFICxEj+6qDzzbqeKdNsSyRlu9+7+KCAYCvdNI1pUF\nPtNxyzcnpDFF2doY4RoZ8LUZCY7RufYzLidGA64ZkuFaUxiZsXLc2WhTtPNc\n3PUVtTuI+y5LgJvCepNgDd/Wo/c+JLJKI4Yr6BQG/nY+zSRG8QCLVDsR+Jim\n6oYLZIrb8xSRBVO/V3r+MpVWZyWzIHwaQFqzslnbuHHXXcJEL+JKa8lG+cis\nWjkfExn0koCl4xAYq7RTWBmWMQ9Z6FiIXW02qqtlFy6xN+AHQNakBVJmtJXY\na5jSk+JwUmhc/YlOcD3RhSEhSZAiU4gHGbcxlKDC+Hh68PWxP3RoHBWdSgAu\n7gj+Nd8e+oNdFtX4iL8PMJYHbc4pgxtm7507rZwAjliPuso5+7fPZDITjfHv\nMOyo\r\n=aQdy\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b2502ad7405f95b800e7afc686ce307cd4facba4","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.17","@balena/jellyfish-logger":"^2.1.59","@balena/jellyfish-metrics":"^1.0.209","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.6.0","eslint-plugin-import":"^2.23.2","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.148","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.115_1621118675670_0.13623574024629193","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.116-renovate-non-major-90e1f999e2dbb1e925e470ad85b745403d1e6422":{"name":"@balena/jellyfish-core","version":"2.13.116-renovate-non-major-90e1f999e2dbb1e925e470ad85b745403d1e6422","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.116-renovate-non-major-90e1f999e2dbb1e925e470ad85b745403d1e6422","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"421721026a0f0db432ce20ea2ac97e081138fa75","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.116-renovate-non-major-90e1f999e2dbb1e925e470ad85b745403d1e6422.tgz","fileCount":99,"integrity":"sha512-NqzUPRLsMJ/2k6G0W+GGpznZA6kEFZ9rP6Jog+yM0wQnUNxp0JEn5+ghalos+l5VS9LSlyALSWCRHMjHoNSmWw==","signatures":[{"sig":"MEUCIQCEEtuELvsQd6vERZ6FLY61ATC7KdYidp43dgTzaYP/dQIgYcmm8KXwNORhuEuCJgvfoKrNUb8JBSlavHIqR/wxM5s=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":749947,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgoGIDCRA9TVsSAnZWagAA/CAP/ieHMoQWLptdVpPqPRx/\nImQMsFJKnZe/wr/QbyFKfxhpce4u5ba0X5TJsKaDnbvTfVuxKFrmtuaSYuiJ\nQr/mjYEOVfzvD7/YvlQpPabODqMliov5TX2gQRrVfZg5J4pJOmuYAPveQpyn\nUHozb89dwxxckfq9N3LFzxSTt2eVJTQrqoG4HZg1fIITVR8mdQnDDP1Nyb08\n4yOC5lvizYFh2jtwyvucNxlQkS2qf1AzjBzCG+HihGxj0gptqYRgRqfl4dnu\nVd0OKg5BXeYqsYwvDsnFbGQ/8RWE4ryas0nqKIyv2eaYMpiuH/MXgluCydN1\n59lrrJCs27mvDA4C2J6fpmBRynY6DwlU94zxkYbwxappuhrfnpw2jw9qgc+r\nhbFISWZjRIrS6pEEqUh2EAY9HahxaPDfUSdpFSAziPDdsesgJ4kaXCc8vOEg\nh60RQ5RAfFk51b0QjflfioxeuuqjQia03Kx9QJED02u1fNophm6jowL5bdzY\nXSt0astBk/elVUMntsWl2BAMaQwrxKtD3CpsUXRQUXFmnokLLvt0Lgg5gPm+\nHUNhhZLdejxAoOihMqiH4YUmvhGIqUSHq9mv0qhVspHgXpJBWZMSS1PZb9uL\nJcHxcWf49hDEYxwqd4scqVQfGhcCyTg1VJT0EQCo11ju9Zn6PGrZ1ZLC5Cv2\nrn4n\r\n=H6AG\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"90e1f999e2dbb1e925e470ad85b745403d1e6422","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.17","@balena/jellyfish-logger":"^2.1.59","@balena/jellyfish-metrics":"^1.0.209","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.6.1","eslint-plugin-import":"^2.23.2","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.148","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.116-renovate-non-major-90e1f999e2dbb1e925e470ad85b745403d1e6422_1621123587113_0.19371412359618767","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.116":{"name":"@balena/jellyfish-core","version":"2.13.116","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.116","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"adfe99241eb06c9fcc8f5d1c91a3750a90ab5493","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.116.tgz","fileCount":99,"integrity":"sha512-Oy6veu64n1oMBYQc0WSQA7KY3mavf1v2cAXFbND+Ih6yllp+4IkR93mylK/FDjfgPy2WhxtEk5P+YBe28Rh4bQ==","signatures":[{"sig":"MEYCIQCXRxZ1OvDyShd2gwcQHATyuUxb5MWMswP9I0Nx5QJdNgIhAPanHyvzGbvSY+nW4H3/X9jl6O+hLSYXwDf7ShMQRJph","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":749887,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgoGTiCRA9TVsSAnZWagAA8YsP/RZg+5bMY3CTTB8dlPjz\nHFaNvphFlXvbQmCzskOHDj6abuPMUK2XBIivi9By0MuTQZzBgL4mc3Pa/hKQ\nZ98iAWmrWTijXmZSadJWMN61xLHeLPqsYaBmk9/EPouCVIemr+my6tCKA3Wm\nERu5UpoaHh8RGdN4TOyC2YU8zbZmfFsy37swsurrmtsCFGJ2xrIG9hT9UcMW\nCP55C9T7JGEjFv1mdd4YMOzz5WYb6UODjARLiG0cAOzRuJHGOiSdngoo4/g4\ndfAY5Jt2Uj3/sU3egclt/2kNFIJGEirx1Tqz75ev4KI0t6eNQP9rc5h1fnad\nBLSH9cJTVxG7th02OTHb2GcdKmsJJ0U22WaFjaQqiE7TwAwRYzhV0NjpGVgN\ngmLlNauS2KEtmWfVti6kA1K/6xZOB3C0qNw4OGcbPvRAhFI3BkSPkmR9owAL\nFoeQwoj6a8JeshrHeSgpRwcBWBozm3YMdHz6RSepr2UT2Fsfe8i90LuGaGeI\nx38iw9s6CvfpR5sxpqdO2i9UR12beUeqWpBjhXV/2/9f8dsrQVV/g8CDC3ZA\njYlnF91JZNJws6/Sa1+ppzxAcCxN8D58cjDXGHQ3x/263UNTzKdUxod+MzD6\neBU6sZddl2oQa5DvSqy4Ay6No8AHCVGd9J/qazEor2ZGNvzwjj8F3dnT/coX\n4fYV\r\n=Itbi\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0cc0f51023ac4f06ab17c3b61a2485640f8129f5","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.17","@balena/jellyfish-logger":"^2.1.59","@balena/jellyfish-metrics":"^1.0.209","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.6.1","eslint-plugin-import":"^2.23.2","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.148","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.116_1621124322189_0.6250781859296692","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.117-renovate-non-major-52996d14b258594e296af7147848e08ddcd94a77":{"name":"@balena/jellyfish-core","version":"2.13.117-renovate-non-major-52996d14b258594e296af7147848e08ddcd94a77","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.117-renovate-non-major-52996d14b258594e296af7147848e08ddcd94a77","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"22713b0a58a5892e51fe751553bbee380710f035","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.117-renovate-non-major-52996d14b258594e296af7147848e08ddcd94a77.tgz","fileCount":99,"integrity":"sha512-UulODjbq7s/UmVQlqpzywqPis7Y5CnvjRhPpnWQpeD2M1Ifmvl0Ny4qW8nHM1tfHHSbNPFWLWazIl22ilURUMQ==","signatures":[{"sig":"MEYCIQCGLQFZ6UsnvjCKkhMvzroHXitSSPhHPB845dTthsXWPwIhANPhKi6gfgyuaU7cG65OL/avi8xaEr8npobaz6XS0Y0W","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":750372,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgoHgsCRA9TVsSAnZWagAACOYP/jOjoN2fV8BMpztzH2/u\nPDCGOeDL3HuJmP8hoU0ycXa7o445yX6nReNG8GkuQKFivWg/f5gbXUOBKglc\nx6YoMy5+J3fL+ynx9W/jESdkshLHBxtW3LtQZ6OE/ZDeNAeVGOH1fdqcSwfa\nO6U/qMUHoYBr2tGVrPR3H3lnpmHMjUfIh3Q636Mfz/1Jk7nFAH3FoWi0g8Fz\n75ErgQndxH8NNOaAhEHRzfJc5g2STb3+QOnX98mv3or0OOWHVwjN4So90SZ5\n2YsA+tp84HHyQeHHHH+1fArPkxO+UQ6kmYL7nLP22zZBJiQZnZF2F5hGRYYL\nzipEwEwdrn6lq1fLBO9Q3qbYGm/PxU64CHW9cBAPqzmoK6DBub6jbEG0/A8J\nLRdAiCwCpU4llfjRzM9gsqop0QFRPjlRBSAOJrEtzRC32XAhEqJ1vLHRu6+O\nZpo483n10vOuxPA0Gqk5lodF3JvcNVRBuxC7dqLv2quHvLJ7GtyV+tkhnmre\nx4ucDSOt+hR8D4j0StHpLTpymbomcmX69xk4TpOgFBpisD+WW0nFOLO3Vbmk\nSIGOzXpmw134/QlFH1k3a2pLSLp1xjKso+VF6yTaKYPXR0IxsRMwsEM/CRCz\nsaQ7L527Pj5Dm4RGJeD/jfj88vUfVsz1OqJ6GRxB6hZesRV8oKjmT4ob7Il2\nEVxq\r\n=kMsE\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"52996d14b258594e296af7147848e08ddcd94a77","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.17","@balena/jellyfish-logger":"^2.1.59","@balena/jellyfish-metrics":"^1.0.209","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.6.1","eslint-plugin-import":"^2.23.2","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.149","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.117-renovate-non-major-52996d14b258594e296af7147848e08ddcd94a77_1621129259949_0.6894397158512715","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.117":{"name":"@balena/jellyfish-core","version":"2.13.117","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.117","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ae2d95a0ed163ec010212c66092645b58a331fe7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.117.tgz","fileCount":99,"integrity":"sha512-fPt2osRRUZHZGxl0ov1f7r5gQqPUBI6pGB7ZhfC9gWyc7YnrOq82VPtjvjBrWV/tpOy1/UYg+dbqaq8WjjBxYA==","signatures":[{"sig":"MEUCIQC01iuzomOhjSost5ensX5sP/gbDPBqckT6UHgnoIESHQIgb1Vw1EFsYqMJaSrgYziggoTBYUVC1Cn2AXO4oSRsEuo=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":750312,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgoHlNCRA9TVsSAnZWagAAXr0QAIc6cYsdx0v+LRclSFQH\ncje26oXKeFlF2KCuXIw9wGG4+bVjqx/taZJdFtqB4rHrJES4FvpsIcvjki/h\nBYZvNjQaJkrbNDLsVH0zt22jRIqeH/mLpAtQFQ0UVECJ9aQpKZoMs3L8fTTZ\nQ/4WgSFqT4kN6VZiRPEdWEDPeelaZ+E0OZukX1ACq7V0y1xity/NtvuDeBLs\nPfjECUMt93Z9XXrIBXPFr+pEdR0w3/uN1J3xwakJ6LwEYBkJHCYN23Eiv3lg\nYYNye5CPkmF9kbnbu8Qvv2RQFwulGwrZQ1Teg0vzU0KOwwpcTyyEz0kR1uw3\nTqPaoiYBKI4iqLxajVNIedhT0XrajRUCaRNg2Gedz0+FtKq5zx0XIoqGqrcW\nyNkUjKbA4qqt7e5brXz3p5FIhC7f/ETFfnMCYzNmRGFkrPT8koCvCHSNzdQ/\nlfcrufIDNcYZvzav7mcCVecCV2nbVEhUGpPg9Jo0eML6sfhleJJz7ztc9T4o\njqPZy6ZAqEPqM2nylL302pd6GCEC+qjSy6u0+1Cv9A1bE2b09JH+ozZt3pzY\nXUzg+5SkqLAa36llNRyAPPlkUjsY++hhrlxEL8Xi2vVl0g1et6xHMTCLzgYI\nxreQrGPoO/XbUKIzgVQ5l56U9tjA6xkH+N5wloxf/srkqJ21eBm/6lkfGYlH\n9g/t\r\n=0vcA\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ba695923fe43e09b2c25ee18500530f4984295c3","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.17","@balena/jellyfish-logger":"^2.1.59","@balena/jellyfish-metrics":"^1.0.209","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.6.1","eslint-plugin-import":"^2.23.2","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.149","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.117_1621129548881_0.1550608542711469","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.118-renovate-non-major-f03d73b635ebc6c895f719d6d2c5c0dc60114f31":{"name":"@balena/jellyfish-core","version":"2.13.118-renovate-non-major-f03d73b635ebc6c895f719d6d2c5c0dc60114f31","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.118-renovate-non-major-f03d73b635ebc6c895f719d6d2c5c0dc60114f31","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e7f0ed4be1d6a95cb6305f1a5fa3090fc81d7d41","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.118-renovate-non-major-f03d73b635ebc6c895f719d6d2c5c0dc60114f31.tgz","fileCount":99,"integrity":"sha512-kgtxYrhoNCiQ4IvEI5hVK0eUWnKs692WjG7jd2B6eBpy3WJY8X2qpGNbd78zE85ZtCg3FG2W9AY9Go8vVzPMdg==","signatures":[{"sig":"MEQCIGpENBk4/EZzTafo4Q2A1RrlvCjeP21Q9tWN5R/IVmEVAiBQKNe9tflgd7dX5RqJrC4F/f4I3cnuqhp/1gfnO/GYHw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":750789,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgoUblCRA9TVsSAnZWagAA8e8QAKMIrihQLJm/xXcDYtVM\nzWrmAWtM04QfJoR34beZC58NLnJ4m3W05qIKmlm2l9nVHvBjmw6sUvTgcAqu\nhuG2SRyp6bMOuMguOnULG9pS9IsxfMu+m1C1qB8ybkq9gqpsZhoQvUlpeA44\n+/yncM0e7hxKiIL6YtZ9IHxrbo+TUU6D1qaAI0ncGmTb90oZ3AGMz+fLcAm4\ns2q8hGrBp4hyvaUbJGXVzl0GvrT37KsdQcCGVCQn6A6DW+idAqPaQpGXHlIs\nJtK/oc6pIx7VPpmYkDWw9jubYE4nN9oB076DLskalpkxCvs7RTcp8mWvCz7C\nffp93FI9vTC1XBWKCVbQfRGv4JVLKk87FD7CLqEvvTT7EahGKbgCPyIB9dcG\nQ18vVuuf02PejY6hXjhOTTeCW+JWMxLocyFpv+Pfmp4S/mATpF0QHFjn612l\nuEvvn6Sucb+59Gwwgrbe8BNmGC3sNmpk60gD7qM0gebiImJcoLH3QdLg2Vy/\n2H4GIF2U+Kl3MBDp6M792RCoXc0Cs3iKyLjI+kAuRV1TzCSESXihXGMkCLau\nKzvfWItFZeikrggdvtp3VEVurKwYZ91gfQ7GrBDknzYDGsiWm0StPkGeQ2A5\nq5wsvLu2TwPNrcXuReoY9MlWPo95zHCDAxoW7vCgWxyyKFP981ex5wgMG+6s\n5t2M\r\n=Hp3K\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f03d73b635ebc6c895f719d6d2c5c0dc60114f31","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.17","@balena/jellyfish-logger":"^2.1.59","@balena/jellyfish-metrics":"^1.0.209","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.6.2","eslint-plugin-import":"^2.23.2","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.149","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.118-renovate-non-major-f03d73b635ebc6c895f719d6d2c5c0dc60114f31_1621182180940_0.0035191330548838273","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.118":{"name":"@balena/jellyfish-core","version":"2.13.118","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.118","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"da5f2eb502844875ea564974f73be84b8eb06393","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.118.tgz","fileCount":99,"integrity":"sha512-efYI+m18Bc7cjrgIwel4++HWPx4KH34iX1Fc9o3259MVE1IoyO3/M/CA1XNZOE+mE9Mi91bxZ5SbdHad+Bw93Q==","signatures":[{"sig":"MEUCIQDe7kMJg7rvxevkPm+UkRvlvQAHbrBchwMhivPz3crxrQIgRXRg4dICisjSSVSkJNEGmdoBtny54gtDbxKUSfEs3Ks=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":750729,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgoUgVCRA9TVsSAnZWagAAR6kQAI+zasKOce9XzG6vi3Qv\ncoVMjshGFIQnMgASzT7hSMMLObCq+2m2kaQSF92TD1C7gLX9F6H0cighhDuU\ntnO1ZXL+axzc+5x1RQBdMOfWKdLLi6uI+7gQplo2XlTrOS5gBnecwsiG97r8\nzV+Y8KHWILtjXKfTVqbPIW4blyW4OSdasPBO/l84CDVilEcKhhAgevy86mVl\nfB5Xh9IsMOo/8WqCBzF9mhU+CI9o3R1GNKjMivJEONcJ27DgudCLbVnT3ShX\n3Rsak0HR2bCqXooMPRsBqHJyYRfM+Vy5CqB/ZTvJrwhwSrn24cVwXUdCU1ZS\nyi6gcroU5jlBRnZvp/2cBX2W1+wfjxw8RWOVmcbsrptfkHttsHSX/KeMCmM+\nSIRWi7xL3FeX5Ba4w1D7I/0Wbpaec/JEK1Gmdfu3k8/D5VUBXKBEQJVkmR3L\n580OM8rRMZvlo5Mc6sSg3B+tOI9bOADdKRkmCcI6tMULo/qLRcN8dv+AyyrT\nkIx0hPP982c1l7CfBXeMxCEJ2/77JMoO6yHSZIvLjobHZhnLgnR7rtcjo/3j\nwnZUDmgcTorfP1moiRiVsDaza/Amnl/7/SSziRBr4L2+hbaJv4g+PjUBP0KJ\n9VsGCJi+uw96hFHqT4ad7CK0o5W2aTNdg6CoxnY9w1qXZVjj4oB6Y6/GU2mb\nESm2\r\n=ue5a\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"708d51ceddd7d5e52cd52e7b28a89fb18a73dfe5","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.17","@balena/jellyfish-logger":"^2.1.59","@balena/jellyfish-metrics":"^1.0.209","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.6.2","eslint-plugin-import":"^2.23.2","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.149","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.118_1621182484905_0.7678544691924998","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.119-renovate-non-major-e169ade06943a690e5cd3d2f4559af0ca92759a3":{"name":"@balena/jellyfish-core","version":"2.13.119-renovate-non-major-e169ade06943a690e5cd3d2f4559af0ca92759a3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.119-renovate-non-major-e169ade06943a690e5cd3d2f4559af0ca92759a3","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"501b85883235c2e7748af16fe87af961a6a0e096","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.119-renovate-non-major-e169ade06943a690e5cd3d2f4559af0ca92759a3.tgz","fileCount":99,"integrity":"sha512-BYq/EWr6K9DeykgmZShlSW6oMgAmolBapP+Z3b3zMWli/9RguGbLEIPiNM423wLu3NZuoZg1oCJZQkRQgHy+UA==","signatures":[{"sig":"MEUCIQDqiknY8HTAoLVvz1rkURDonUGr1MZUIjsW6uV98jOfIAIgFDwWjsJ5C5gHR/55F1RnXjc1mDoDSGwtzU6fijuxyjU=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":751214,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgoVn7CRA9TVsSAnZWagAADrUP+gIeaV+31m2VjbQEDx2Z\nBgYMXdv30MufLyV7cJtyCEighqCxA6vMkTG9BxHP+OaPnuuIgJDvgKro+yzp\nw/VZq6kzl0/F//LPpgLY9tYZioTom0p7gpW8SEkwqipHCw/7w3JsmY8EI5h5\nHDv1XiszqFgYJA2JV5oSvmO8dEye6xiI6gRVD4nQjO030TW7yzIXShzoSmxA\nDnK3+cjqVCZDC+G7dj9a0i/IERorqGcSItTy1qB28h+TH6CEMF5WMLaYOkjS\nogNZQ4SCt4ZnANbobjaafKSGEIQymVXIn1ZXPj97OlhIYNnhyQOfZNnW/oQW\nWLwsegyBi7TpH6yPAC9X8pzwmjPhqGMKGIC0ZNmQFJwmyXalBva8jzabiW5L\nmtv+/lZXKI15pbnQ2JDhr1RUM1GHdVLre1YBY+gIp4MDgi4qKogKilEWZXsI\nG0PuqUW6+mMC2T7MnNiHE6r4fOZ6dQn6qv/Uqx0Wfx3EVPASj0GWoHdBvwMP\npQCRU/gAPIQi2/IZ3OWKyduZwbuWGAX6d0z/8UrLEKb+tuE3TAZRstzeLQiK\nEEnaTwS90JEmtZYaY0FWeURDMDHUHa492CvZbWJ5miTSHBkUtCz5Rx6dvWeR\n5rtj4/768j0B8gM1e8LFyPgxnEhKAKO4kBr9/UQ76W9o7oOEXNBWpDEKpHx/\nJR4M\r\n=ehL9\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e169ade06943a690e5cd3d2f4559af0ca92759a3","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.17","@balena/jellyfish-logger":"^2.1.59","@balena/jellyfish-metrics":"^1.0.209","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.6.2","eslint-plugin-import":"^2.23.2","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.150","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.119-renovate-non-major-e169ade06943a690e5cd3d2f4559af0ca92759a3_1621187066957_0.23442975181290748","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.119":{"name":"@balena/jellyfish-core","version":"2.13.119","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.119","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"bbfcea6f909e7da52871be8eaac63aedb031bdb2","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.119.tgz","fileCount":99,"integrity":"sha512-xc3gqDUeIO4rnoVIm0t3sySoPBVQpQFfQlVCMV5F57Zyb3BTWdUwdMEv1qe86dwEzLt3H/FEjD6NWV+maFoTHw==","signatures":[{"sig":"MEYCIQCHl+pMcNsw9oCPDTAnBjiP1mD0hxId5h0uAevlOwYbZAIhAIoqCYr7ZBN4wM2DYlnqv9SlEYOtMviDzkwEZt5IB6Qk","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":751154,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgoVsICRA9TVsSAnZWagAAcdoP/RdXm8ZLfnHc4pnedr+P\nPiqXptRDg9ieagc4tvQNVWBvb4HH4swDMGUALuX7cc6Y7P2gprWErQFz7MRS\n57hoEdsKTjgpVo1FOEBUyVbbbR2nxK7Vyn1lBh7Ow0KpwSsqAu/e4D8InEdW\nkji33HVF2b5mld1rlf0qPfAoW7+9Ofv7frhwWSU5m4+I2TlKg3x2lSFmTkqf\nX2hDqU88PBBAEJHp8Y5anKFseJVNdt5eiE1W8UzVxI6/rqppIDbpkWgmZd8y\nKVY2dJLNj8Uc0TzVtHVzMIm6uAWwEDpYmWSDCEi8mBexxq3XsWoc/MVzEIvx\n+fwgbU2rPht2M/EcvyVNTdJGDpmxijsmW83xZDvFjEwNPao6Sur0nJWTT2bG\nBMMS6e51KmthJg54d4M/1zn9QiKXVRIjPWFgJsdHkl2QkAmaFzOL1x7tkTYV\njMDI19To1pxFEdfqzA4u5h+oKfDbox3bvSssbRj7N27zJq8uHHaRz/bz/QGd\no3Id58zp87vf52+wkgIxhh6S6mBkHLqrvHA9mhag794Axe5tc8EhtAA15FdM\n808o/PBODedwcLC9UrEY3N1xMcHuoUMUwzpGviVOuGI/w3e5qiryzfASOPQM\nn61jNEPCvlIZ1SrX4Iy0giiBYetz+djvlgo3VuzAa9JfD/aQFI6mKjNTfqXO\nXPgG\r\n=LN5a\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"679b7bb72eef27de8ab3bd2e1bebdf10c3b817a8","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.17","@balena/jellyfish-logger":"^2.1.59","@balena/jellyfish-metrics":"^1.0.209","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.6.2","eslint-plugin-import":"^2.23.2","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.150","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.119_1621187335970_0.4284603163445575","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.120-renovate-non-major-c7825cd9232227a111e655c75e412925bbba4971":{"name":"@balena/jellyfish-core","version":"2.13.120-renovate-non-major-c7825cd9232227a111e655c75e412925bbba4971","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.120-renovate-non-major-c7825cd9232227a111e655c75e412925bbba4971","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"04ab9d2b546c7c26c217e56b166e38d21810ac98","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.120-renovate-non-major-c7825cd9232227a111e655c75e412925bbba4971.tgz","fileCount":99,"integrity":"sha512-qvR/vCPb9wXxwmhyxLCIZ5Bvyy8nQFmj0svQGZrRlxrGOhy1eat2DfsH3i+uj8RiW7+x2Y54BZoO5HRW+mKtgw==","signatures":[{"sig":"MEYCIQD1A7yY0OZJE70oBdqrpKG1z+DGu1V73tZOE/Vt1jMPhwIhAIXHmEpVuuIQt05YSkyfR7dbP/U2NWNnk8x1/Cs6nHFf","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":751567,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgoXD3CRA9TVsSAnZWagAAdVwP/0Qk17pweIIE5wfXYQoD\nnjRM1dAP/ENHT7PZLXb7Ajo7zWcDLQ6TCi10RmYAY8+C31u3hzopY0Px/ysd\ndzQPSQZax0lZE0y5W5jAXpnY8FmbtrjHP7SIbzpxoTNXiQbLVOcppnPtE2Ec\nk/PdWL03YbbkgduW31SMPbsH3XVirrykmCamvjVUmGua5ZVf5dpQ57G8nPUL\nG5UNHGLZemWcoSbOseyA0jTNige4c3Cx2U5DSgN04fZ+jxcrM4XfP9wP6OoI\nugatd89JfPpag+Z23Ipn87+MsXBl3FQwDoyAfhchsXSD71TWRCOZqHYGTDEw\neM78JEdc24P1P7mCuax2ZlH72dbWG8IgfhykfzPDlksRBdGQ4EKvHK4fRvSC\nnvyNc1OeE3491qi+Fh4VMNIL3o3miVURUGHjwdxVcq1Oa/ejv9Ob+ma/TyWy\nCRZ8QgFfMK5feEr1jm0+hdHj/yTo6uluRGqLGo4C706H2rV3WqvmQh9xLkDg\n7DJbb2FPFiNUWwWjrjRhNURamAzhrer4AyqPJf3ljHD+PsYH46lYv5WkmpHR\nS7wCIZUXrazWbNwotb++5RVX9Hisw17ENBm4OCaYmHtreuVogrZ+riM7Bygi\n8UGqD2CU3S0lTfiPE6w0xByeOCbi19XP32jwsZc+IG6LoOpfVN5R9mo/x6jk\nGPcC\r\n=krJe\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c7825cd9232227a111e655c75e412925bbba4971","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.17","@balena/jellyfish-logger":"^2.1.59","@balena/jellyfish-metrics":"^1.0.209","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.6.3","eslint-plugin-import":"^2.23.2","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.151","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.120-renovate-non-major-c7825cd9232227a111e655c75e412925bbba4971_1621192951298_0.49973875441696447","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.120":{"name":"@balena/jellyfish-core","version":"2.13.120","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.120","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ca9a5f79bdb7215eb95bb39e06ca61bbdce28dd4","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.120.tgz","fileCount":99,"integrity":"sha512-o114htinodXlXM7ux3clbPxiiBMwUy6WsPyYMlXzuSMhZ7zFbwJWNWni6Tw4dyS1CoMwfQkqyjoWA8gRn2w6nw==","signatures":[{"sig":"MEYCIQDPA+8C362Xy/h5/ojD5a02svJHHsI36xpBmByF8TqvDQIhAKAvyjd/3QpZNg9Q6FwSxKXldI03EYfWfrp5oNLxBW4r","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":751507,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgoXHFCRA9TVsSAnZWagAAFDIP/R2GBRClinNN1TsKAXmR\nFT4v++qg+TxryY9LWF8Cq9/dStqdVksEVGnsGhoXvkQgQcTCcNKEXS1rbb2H\nNJu7TzsvtlT5jhfXZY8qv0xb/U8jov8aeX6xvrUXDj5EIv3l6NH5Hz7FqLG5\nmkC1qa1iK61tvvlLdDJp8kA4e1YD7mI73gyZHCFKtzB/1DZXvphHNfJ9Lvgj\nfpWbueKbTKypZ6JVhtoHlHA+STSkNk6br9PQ9evX/rPCtZ7UzqCQK0LNbdng\novreddIo5zJ7dPvBA2QEUxXiCClKMPPkcJT92SgZziSyWZbXWWvmF/kJr72x\nqSqDSkzn8JWDtWGlqfmNt75WaBmiQCd+Zm6RPsqzq0uiA346HD6/u6pdESus\n0MyF+Bf0gydRJI0wNyUgTE8U1EXQxW10ouZ8VfAZv1wkHdjGnqkP3g70CF3Z\ny9izFlyR6Rbs3YcWonJNq2E/FkA8g5rlsbVIteIoPlSXt9DLvUcrX5YKCX7k\nTSmt0bY5je9/xv0uSgPtO38+iy1FzqiLanVnn50RttEUzKjEXu/1WQbrttiP\nRv1coe2244+sZeeMuEbCssBqXSBAyWCgXKU9Vh+wtPeEQGoeb2MigjcPXOm0\nfLOzPnuR/CzHjw9KdtL+n4eAenqhbdgMgTfRxcDDTrqknTnm5XZc9HwA/Mn9\n75Tc\r\n=kRJw\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a29ee922941692063f227c6457fe3ac423cc2930","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.17","@balena/jellyfish-logger":"^2.1.59","@balena/jellyfish-metrics":"^1.0.209","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.6.3","eslint-plugin-import":"^2.23.2","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.151","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.120_1621193156883_0.6159171333444993","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.121-renovate-non-major-0e72d50d91ff4713a8715df625806d8ea6aeb353":{"name":"@balena/jellyfish-core","version":"2.13.121-renovate-non-major-0e72d50d91ff4713a8715df625806d8ea6aeb353","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.121-renovate-non-major-0e72d50d91ff4713a8715df625806d8ea6aeb353","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"cac67ec72b2e9b1911be5f743f3285496ab10cb1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.121-renovate-non-major-0e72d50d91ff4713a8715df625806d8ea6aeb353.tgz","fileCount":99,"integrity":"sha512-WbppUYQgI+iSE6Pl/p/qVxJKu3XMAX/lHQ/5ROt9c8692kpaRkLBqduSrbKsU9j7oqMyMM3DSsAkLtNnub/fXA==","signatures":[{"sig":"MEUCIQCH0hGyVjANaUb9TFYPEBq4AjAJacGT78VeFe0ZhlMh/QIgJQDu/+RNfWa9xNds7CeeAidB6ON93/i8/Kddfo9nn0Q=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":751994,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgoj15CRA9TVsSAnZWagAAYcQP/0yI4hlZANKNwq7R54H7\n+ULzbKHym4BYKreHd6oaXoXQLDNo5R6C0KGU7R2X5Eo9eB89xMtMmq8BZtmj\np39oLPao9tV2MTNECdsyxPN9wI0jDzTSOm0wTGaV9sBo0seuHO4F0wm90k5A\nX7VON1ZG4x+Lfu8EFAhax+biUJHh0UbYCAqFi4wO50gjMeKLgWsxUJJKmF1U\nBq1UlZNPvg+xuVCAUhPiChG4X3+JkVPCTTnIqPh6CTalX2hVYEG0w6nqslvA\nhmKc0EgAQaW+it5+l+d2Ekz1kSvhkDVx4PWTmP28Y9j7sUV1JnipkNLRGFnK\nUGOYtbIy4u06Qs5sIs8DuGF7SE2OrS9u/43heRQGsrns8a4Agtm6gMOXOVdj\nxHdduYStgBKOcEAb2liF3W2IlXFizP4yMiSQhEq7SOlkKlKvf6xCtlkBMFyx\nD0I4a3kEuhV549vicUp7HIpl6uO5F/Ra1PGDu0ekWbR+3VSH/tw3yAqJzLnC\nzjNNy5iLeWcsQstpZ/C5zvXWt3TfbLN1XV5+4KxT8CGEeQwON3Gcm/wsPslM\n05q5e8bPSTNxmv5Z8+WnQC6B+HmyVgc84kQez0T9RM5E2mpETKqQ39zKWGsd\ntefvsaxFKc2Y+nKgA6KnWdFN/s9HTJpxSJFpL+tNuRGtdNTH/9xoAT/rQNXO\nz5P6\r\n=SZIU\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0e72d50d91ff4713a8715df625806d8ea6aeb353","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.17","@balena/jellyfish-logger":"^2.1.60","@balena/jellyfish-metrics":"^1.0.209","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.6.3","eslint-plugin-import":"^2.23.2","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.151","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.121-renovate-non-major-0e72d50d91ff4713a8715df625806d8ea6aeb353_1621245304681_0.8826109422693085","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.121":{"name":"@balena/jellyfish-core","version":"2.13.121","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.121","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"73d2a676b8654f24dc6bea82f9028b410e4482ea","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.121.tgz","fileCount":99,"integrity":"sha512-onPaYvNDm+GTLjNkwK/0xiasnLcWIfteiQLnZ9C+Ho5m+MUspeBKjoWh787fiw+3xXmkQD68igJ8ogS+lqfr8Q==","signatures":[{"sig":"MEUCIQCH6ugq8eqaM58NGhPO5pbLPnCTm+f8QsI7KHzRkhm/yQIgCrwnNqVe5qEVcS/Dvygahxy62HS1zd0vASlCyg76zwE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":751934,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgoj6FCRA9TVsSAnZWagAAk8MP/1UVjhG3t4+vsy4QwUeq\nBsJ/okxDK3QUmW7XzAwlpK+ccrs0SPRM12ihnjnD/tSxP5f2fvmBAAhhe0Jg\neELw579cUp5/7V40FfRzGoFvJ6nH25h07p2EUk0UDDM2oabwZ7mXn4jc+oXB\ncQ33Dw+9j8yOyvIFQcedfHDrmClpwX3AUJL6O5icIGX0ABgjCWnolWHp++mz\nM2cVUN4D7gvc/15YHJFelDN+kOW3Ci2/Sc43gO05KAphbnTFnJ3fKPQJqbDT\nisUdvwVZxERieGKIxhz4CPlSWO/GYeQlyzTjvqxRvTSewOmpxwZbObiwvDH2\n0mv+POm9R2NqvYxY+c6RbUmC250NCUYh24tLnanFySXbaSxEXRw+HmMLCXCA\nDouvyhq8lf4CeNRJ0oLC0ZvHSnRkcHq21Z+LuqqytKJTV+wl8yMSpZn/80Y9\nCUPRHc5ZJPXMD7PCthxsp3ZwJ8QMg0x8zui5ohknnnb/mcTTAZ8djcRpyD/I\n9WJWqu/cIoPILr3JVAX1rO1RFp1rIImNZ5RJt8zI1A9kLW0wWFsKUJkS7d/F\nhr9dLTXXeAaxfJT2KHJTqNa62lJ8FL4PHE+/8+qoXmrWQpMKM9tMywe6kTsH\nTovNA/Rz3gFp5bCW+r3t+dPj9YNp4/hhaDYr4BoIBvEx6rFYRb2YHwUNkbjs\nez6l\r\n=BwVK\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6b532853b463b7e9c1c3a075a8f27907ffeb7be1","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.17","@balena/jellyfish-logger":"^2.1.60","@balena/jellyfish-metrics":"^1.0.209","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.6.3","eslint-plugin-import":"^2.23.2","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.151","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.121_1621245572784_0.8947716653169229","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.0-add-loop-field-0ffb755f89656879abb466e5067ddb117e421f70":{"name":"@balena/jellyfish-core","version":"3.0.0-add-loop-field-0ffb755f89656879abb466e5067ddb117e421f70","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.0-add-loop-field-0ffb755f89656879abb466e5067ddb117e421f70","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"026de4fa9379e189aa3cd8db5c2f34c308e2c281","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.0-add-loop-field-0ffb755f89656879abb466e5067ddb117e421f70.tgz","fileCount":99,"integrity":"sha512-A+44D8c/9sltcfGe+Pqr7SzrNShsSPBeTyeHWnL/40nevKaECZ257Y2YgYDhtf1/dycu6p0X6hFc11efrx6bnA==","signatures":[{"sig":"MEUCIErCBgwAEQzua8cHUp8smWAi85Bhf6T2b62vR/moQXSrAiEAiJoAVTV5VUUhv6Np/mXHhKJWe204tFNjYiF6CuYASc8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":755450,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgokdtCRA9TVsSAnZWagAAHygP/05jc5kelTvcLxng/2Wv\npGKYIL6IJa31Ub5uD9LRyDolXbUb3qfbg2tloPVGnfe+5Qwl4jSQ+c8tYSpY\nO1pW2pPemnLRM0S6s4XFECgrA6d/JuU4KMkfwAtnWcqjXg+qYkQbVNi/r09L\nr5BkL3G/QwQB5n/hXwLm+N04IL2loe6ueTN/MDB20vF3cIEhcwSsH+j3Iu5h\nSyAifO2fTi+TFqltsuDJVXU/86zrKeWw85WHX8moLYOoNNuSxAygWP88Ezyr\nMMEAPrSphbfK8VBk5kiHvwhw6GHsl6DWC6Fz5pkKmsKVkAjXnVV+nBLKFP+I\nVIsygl/tayUe8h9JNW4B9ac8ctuuiHKqwSPjSeeDhLA9nQ0F5g5aWhZqml7N\nTKSMMq2jtl+9jdoKNZc/j5q1tflgBwPdAIrdLlBsg3MhdYgwlyGRDiYAw2Gp\nk9teUstFOy7rdR4i5CPaolKoG6655naifTl9bZ9qijaXbWu31kGcBnK3eh+Q\nddg1kkxssBDWEKuejHkDcfmLDgSj9qkH+GRDJBMZtOGyZq/SogYAvuZ7l2DT\nJ0GXroR+Z6n2gTvbUvRhP5OOwv3mkagYMzOTYxqoE5XiDeJhwPb60zNZ0Mmn\nRkuKSQRu56LvkrBhCqZI4iaR9wUOJfdHJMWVq919pBBHahwHmT8sy44D5DEj\n2Co9\r\n=C2Hj\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0ffb755f89656879abb466e5067ddb117e421f70","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.17","@balena/jellyfish-logger":"^2.1.60","@balena/jellyfish-metrics":"^1.0.209","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.6.3","eslint-plugin-import":"^2.23.2","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.151","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.0-add-loop-field-0ffb755f89656879abb466e5067ddb117e421f70_1621247852632_0.24308015408205863","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.14.0-typescript-conversion-2c051419f405ae2f8af770b66519a01cb3bdf902":{"name":"@balena/jellyfish-core","version":"2.14.0-typescript-conversion-2c051419f405ae2f8af770b66519a01cb3bdf902","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.14.0-typescript-conversion-2c051419f405ae2f8af770b66519a01cb3bdf902","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9648cc1bbbbc42b7323df8eac8f2b8003a1f2772","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.14.0-typescript-conversion-2c051419f405ae2f8af770b66519a01cb3bdf902.tgz","fileCount":190,"integrity":"sha512-KEwLjutYaoYKlytKdmK3PNeUcmulwdWQjI/eftO2Q7iJ3N+xCb3aa6JJCzxYuyVr+3ocrgxeEUY4QtlOh6P4XQ==","signatures":[{"sig":"MEQCIBFUt0oZtpU6l6x2dobyudRF1o/hfdWNgV5K60Xkc6AZAiBDJ1BO+kt7t3nOS2InQjSYal8E+A+q51rNQ7JhrraAHg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":716389,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgooeRCRA9TVsSAnZWagAAFdoP/iEOl0W3L+a8gzV5oa/s\nBJmuLJKVUh1lGeo6iVOQanXtbQzSpYbsTkEIbNF6lcyYrWbqPwZ7ENlNYU3V\nbuf6C1IRazyHv0H+pOEUQVAiuzEUHE0rrdsUGSFr0jsTEf40JpOC17u0lBPJ\nB+kMfvHya5+Wn2TjObT4t7JWZUyoj/VXnp7N3fWmqm8V/gLJDsK0+iVFmLUJ\nV/a6/gvvi4zNQYBmk1rpVH4oP0utGJzzfMWL5ZAIkTU/PMa7LADMn2GBbug+\nI9GZfoWcL08DzFiw/aewL2JEBk+FfekUiDv9AqOTrgrfTKSPc+Biw8jykID9\nSaZOvlqL9W1X1yVxDdDq/ZdlKMngpqguvZgGMDQ/XMWMr7Z9wUlcp5Popb46\nr7QFyOeZFEF4OM1dHDdLzTjt8nOrDJBjIp3Q0nQAM/cYArDLOKDL0DmL0q1J\nFXxWAn5LcY1moP+4rr4vm4tErcueRdoopKvYq9/paso21qlx2l/l9+yYOYc2\nWpRp+RYGYodYoknar1KGdJaaFqSXe1MupjsaYEz3osHTMDPmhcUKnzwogNiD\nNewdYKiOxAg5Bu6JU5fCc8AQWd6bHRpNGlpVEwOW1Tt2j5UY1G4mO+AWjmEI\n9dDkNkKyoMt9cRvGlynbju8BhL/yA60sYq9ltIjbvH4ez5XgnhhDThPQcRHG\n8mve\r\n=cfpy\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.yml up --build\n```\n\nThe tests can then be run from your host with:\n\n```\nLOGLEVEL=warn POSTGRES_USER=docker POSTGRES_PASSWORD=docker make test-integration\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"2c051419f405ae2f8af770b66519a01cb3bdf902","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint --typescript lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"catch-uncommitted --skip-node-versionbot-changes && npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --typescript --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.34","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.17","@balena/jellyfish-logger":"^2.1.60","@balena/jellyfish-metrics":"^1.0.209","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.5","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.2.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^5.4.2","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.151","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.99"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.14.0-typescript-conversion-2c051419f405ae2f8af770b66519a01cb3bdf902_1621264273416_0.5807266998637086","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.14.0-typescript-conversion-6bf04a712788e879d35764410e33fc214ae2d7b9":{"name":"@balena/jellyfish-core","version":"2.14.0-typescript-conversion-6bf04a712788e879d35764410e33fc214ae2d7b9","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.14.0-typescript-conversion-6bf04a712788e879d35764410e33fc214ae2d7b9","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"68e1f644cecd0f67b2d131b55146d19e43f1875b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.14.0-typescript-conversion-6bf04a712788e879d35764410e33fc214ae2d7b9.tgz","fileCount":190,"integrity":"sha512-EukDTxaIv5UJvqqoPPNsWee487gs9PrTsytJzsG85hxvyr7iE8jT4M9ejQP73hQ7/nOk2x8YXVxibrKkEatBFQ==","signatures":[{"sig":"MEUCIQCUxE3MTDaHOalpKmr5zWV+taUcaEz8cd6ysh5DEiyVNQIgf6Kb04tKTTAC4YXdTUxWKmkvi1KqIwF17dt+9soDGrs=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":716389,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgoonYCRA9TVsSAnZWagAAxCgP/iXhOeQzhEKFjEimMbB+\nyo14PLLgy1dBUqhKOuw/MdIAeQmidU/l3wAuNUHWcPriGxqGg+6z/ESSYWCB\nqAk1Sr5zbZAy858/DgD8FPAfbjq1hRIVM+xmJELhwiofNfhfCwk12W9m38Pd\n62YTPWHXfRRdZmPanNI2ESDFZwHU77zbQiMou8tSYYu1FY4HfvzN6CUv5i/f\nmoewD0gmNnetOAkFaw3h8YkVrQl34GN3QF31fbHPKufqvTX698FXR7zhFkG1\nB83NapKAuJcByCY2mC3yimJW4hi76zlA5rihqLxITYSU94BwIqUxIbkdlZUC\nbTiA1Kf0Ymn5FAeEEvLH4CxyC/82bPRGfFl4nFGTjlLULjG/Q7BL5lJFz0GN\nZ42U4rFXpzJCjp8TIP4do/NClalvqQsWRYvLJ4sHdEObrQmN2SUL8pZvLiac\nAVqub7HwErGgD5LO0g5vorjWKycN3RMnw3BB2svD85IPzJwOQCUerC9jq+OK\nXDDmZR5Zo55GGKWV2jbJSfqOWv2KXgNc1VYGju2U4dlXRt4sdQK3q2EtUWO0\njhd/UWmjS/0lX0WKDMMRbTfTNvHOSKregPXCdcDGwqhNk1rvQXC1g4QglvHx\nrSRiKM8enJf85IuCqsBpTeeBMUXIaLOcZP9SCAg0ZbhTYGg4HhRbU70Rkhx9\nzwaq\r\n=TKBx\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.yml up --build\n```\n\nThe tests can then be run from your host with:\n\n```\nLOGLEVEL=warn POSTGRES_USER=docker POSTGRES_PASSWORD=docker make test-integration\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"6bf04a712788e879d35764410e33fc214ae2d7b9","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint --typescript lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"catch-uncommitted --skip-node-versionbot-changes && npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --typescript --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.34","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.17","@balena/jellyfish-logger":"^2.1.60","@balena/jellyfish-metrics":"^1.0.209","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.5","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.2.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^5.4.2","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.151","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.99"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.14.0-typescript-conversion-6bf04a712788e879d35764410e33fc214ae2d7b9_1621264855566_0.2884283694830083","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.14.0-typescript-conversion-05a47a84d6d5962679175a9ff77ed72e8fbb7662":{"name":"@balena/jellyfish-core","version":"2.14.0-typescript-conversion-05a47a84d6d5962679175a9ff77ed72e8fbb7662","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.14.0-typescript-conversion-05a47a84d6d5962679175a9ff77ed72e8fbb7662","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6d545cd78cd25dacef5e9fb8be5ceb300735902d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.14.0-typescript-conversion-05a47a84d6d5962679175a9ff77ed72e8fbb7662.tgz","fileCount":190,"integrity":"sha512-hEh8oDJbyextlpp4hRRTN6oQ0aqst0l7qit+MLdVbmhWKuE1bXk5/BcZs8f5F1kL4OC/p74JMTL82lMrkJdgxw==","signatures":[{"sig":"MEYCIQCBdyQfPWexqjF0fyJiIhtt0+hOcVrWr9b4hzKWoG1e5QIhANFV5mFDmD51e9n7hM991qk0PNQqGP4l4cfRtkGVaEwL","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":716389,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgoo4dCRA9TVsSAnZWagAA+vIP/0WOifCIkzGtY00M5m95\naykAqOFAe7+tgDSA01ZnHnYyWS9tID0vWH3cSHAe2VFg0qbf+lNg8sCNroma\nZiOYofnTC4XnJqQpU3s3uyb44IrTu7TETy7QtABeJyDy3/BC3RsbAr2lWk3H\nAM7Cq8e8doVYc3yHSgApy1D/21lB1xUpWmoA167JAJ6TByejQeKrc1UYBDzP\n20ydsliWwvtpwjYzUe4cFAhDMjaIQNcJcQxylrFqMefqK+PZeGPtsP/NGMW1\nSxBgTn3PSiIzx+ya//owkbE6KHxJhGotkmGSObAZREd3tfDTC3Ns3VMSKwH3\npGp/TXGYLjBxLXWXZVMMHLshvQGyLENVfV+yt7VObq7hpcv7dA5ZNeiVZJSJ\n8pjuIsn3QqugCFOhsU2P3Z2e2KBAhx166eDzlgv3+lnpMPCEmlPqSB1SPYaV\n3JODHeNgvUZz6B5au+OChMPH9PJgzC6s6uHU6hn8wSuPbFxaXmCH2Vfnc4/g\nfEGxmHIN+boNg1cF4+r6hfYvEaS3og9eY3nsRteeHisWVVlqRVW3n7O7l+mb\nfaeEomRsS0q+bFjhCIUcVvfDa37SmFUvDXrJ3DgKLX6Z8982N5IhD21UoRRc\noHQEmbvgLmWydioS708XuJQmLwyA8QsGOzzeiiBYFSCbNyIad4TXVbpjryi7\ngaZz\r\n=7ryI\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.yml up --build\n```\n\nThe tests can then be run from your host with:\n\n```\nLOGLEVEL=warn POSTGRES_USER=docker POSTGRES_PASSWORD=docker make test-integration\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"05a47a84d6d5962679175a9ff77ed72e8fbb7662","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint --typescript lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"catch-uncommitted --skip-node-versionbot-changes && npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --typescript --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.34","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.17","@balena/jellyfish-logger":"^2.1.60","@balena/jellyfish-metrics":"^1.0.209","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.5","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.2.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^5.4.2","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.151","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.99"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.14.0-typescript-conversion-05a47a84d6d5962679175a9ff77ed72e8fbb7662_1621265949002_0.9960052479001242","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.14.0-typescript-conversion-7dbf8379191cfc394bd564b603a375586124d6a4":{"name":"@balena/jellyfish-core","version":"2.14.0-typescript-conversion-7dbf8379191cfc394bd564b603a375586124d6a4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.14.0-typescript-conversion-7dbf8379191cfc394bd564b603a375586124d6a4","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f9fcd9861472be94cf5c2ee0be3bd6f3c46ad8ae","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.14.0-typescript-conversion-7dbf8379191cfc394bd564b603a375586124d6a4.tgz","fileCount":190,"integrity":"sha512-BcZ4EuQZbg/x8c1KMDaJD78nkujL+y5fTKRpAjP0uFlUYWvY507V7BZ1ruFkCA4Isei5s3Rl/nD0iwjibwzmfg==","signatures":[{"sig":"MEQCIHooA2LGplKoaIOYjOBLQmf2iMpPleURVq//Al1cXMZ/AiAlPZiDgjwBeO+wABe1pZBEphvVR1cqgA8+DAEW2WyP/A==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":716389,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgopIvCRA9TVsSAnZWagAAnsQP/A3EtcDhry8WvNVsMa+A\n6nPK+2MMe8HAhPsa8ee01uaXHbiPdIS0wU4ued3/HfLNvBnImKSXOhRq0Jbd\nlZSqfEmLZPtZiH5oMMfsWPC05u+giP9c2itcV9Mb7h45BkwuZo6Z5363n2tj\nJoqB5o3xQSXPbTl8Zg5gH7UhIQ1zahAIPSVBotU13hjIgyVIvtoj13OcKbN8\nbVzl6eOEl2BOBy6afKgBd4BOJy6P/K1x6DTiIK56YnS0BLF/bCD6Eszz8VIZ\n4rAMOSCelGWdn0Lrxuj3ox+PWY4aRuZcDrOhmm3nD0hLwirR9c7nbITanqwO\nJjIqiYfb2DUaady+J1T4STMBAzfWtb7IzUpPavYJjNu8Qc7/3MQx//+LIm5k\nwtLEYxcUvp8AABYEhnV+oyR9oez+W1VNUdHNteDwvHrELwZJJrgMajIAzaCI\nkRHprJTRT9G4lVrAH2addplvlx4zOBj9M8hpXt93zhuFosPp95BbUMT4z2ur\ne8Xy3Y4Zn5UH6q4wVcs0LJUAnndzvlzSkKUm1O7Zs24Pf1HRk6QYBwZWYAYM\nVquN34uzg5shdt2ACC6fIgyUZJToCX8GaiRVYSScaUgaBejbLsyhdtrBGX7R\nKs0O2KhC2za+3Znv7YZIlLuLx8kzX/MPnMELOPuBhKy06JYHVFgSCjaf2Ezk\niECA\r\n=oiEA\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.yml up --build\n```\n\nThe tests can then be run from your host with:\n\n```\nLOGLEVEL=warn POSTGRES_USER=docker POSTGRES_PASSWORD=docker make test-integration\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"7dbf8379191cfc394bd564b603a375586124d6a4","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint --typescript lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"catch-uncommitted --skip-node-versionbot-changes && npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --typescript --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.34","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.17","@balena/jellyfish-logger":"^2.1.60","@balena/jellyfish-metrics":"^1.0.209","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.5","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.2.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^5.4.2","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.151","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.99"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.14.0-typescript-conversion-7dbf8379191cfc394bd564b603a375586124d6a4_1621266990797_0.34138403663792083","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.14.0-typescript-conversion-85dfacc7e1415844b1a3480ec71fb9a2d4c30bdb":{"name":"@balena/jellyfish-core","version":"2.14.0-typescript-conversion-85dfacc7e1415844b1a3480ec71fb9a2d4c30bdb","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.14.0-typescript-conversion-85dfacc7e1415844b1a3480ec71fb9a2d4c30bdb","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"04d6076bf228c6762bd3fc82c746a06bf879eeb1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.14.0-typescript-conversion-85dfacc7e1415844b1a3480ec71fb9a2d4c30bdb.tgz","fileCount":190,"integrity":"sha512-B+9UO5H0aHLaA5hHCWcIrEwrschCtPof+uB44vOKleDryF7iO7fHC+1GyQOvXEeWyZErU2m7XXisgH97z5mYJw==","signatures":[{"sig":"MEYCIQDH1nzfAtzT4mWUC/emXFG/8Po5rJYL0CP6ctkinRkbowIhAMCmaLxch8eYPWZHDXDezcW3GLKKVPVpAHBK37Xn8pHd","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":716389,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgopLWCRA9TVsSAnZWagAA++IP/iSUfxjoIxgeKh4At2fo\nMSwrpnk09yB6g1xbpzQs6aIywPEuMOPV7onG7I0tPj7FPZZbtgVtglbngThb\n1DpAwTiG2elwzcIbsJti/OWsEjL9GLS278HMnKugKie94DAyrT5aHXxQhl7R\nmEygnW1YHojbKXnL6VP4LdoGT5xq9D4jDGUSdIfJ8Rx34JwO+y2o8heFxBC7\nHdFc5PeR4AcpJwxcQMqu6fuQxgQQJ46HyhT5eW6MKlmOFArBUfUizxGI6v7j\nPNYyUqYLpso1RAWge1beFriahCUVGgbwDYk7lequcRCsQcNqDl0oTbxCVFuu\n8HM8LsrC/tWVvcsGXM9QaiSqqDfJ4Rqwol+9y3Cfw1i15Mrmy4Rr3rdE/s60\nKWNl9ee1aJQWQtwj4D5Oy0zNZbYexQYY593ukIXGLUHfI5mlUtfKoSHilCGb\nWJ0ODCcXvWHSAEBy1Xwju/NBpsj/+RKZv1JZythN0hEjQ7ytI2Pwal3n6Vff\n/bGxB+I8auHCpBVQyAuC2P/jvMWsTf0BzIvto0u7M3N+Umb5i7GuuX9qeXsV\ndbpXRoiHL360WcD1uZI8Bn0NJBfJ67T4SXiJA5DmKBcFb5TbPLTj9jGAaT5H\nMoDJVt7m4vJghUFT7N6OmMu4wW3qTs5JXQp6rWxfiZvDby1XhbuF20Gsui8i\ncSvU\r\n=RyBu\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.yml up --build\n```\n\nThe tests can then be run from your host with:\n\n```\nLOGLEVEL=warn POSTGRES_USER=docker POSTGRES_PASSWORD=docker make test-integration\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"85dfacc7e1415844b1a3480ec71fb9a2d4c30bdb","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint --typescript lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"catch-uncommitted --skip-node-versionbot-changes && npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --typescript --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.34","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.17","@balena/jellyfish-logger":"^2.1.60","@balena/jellyfish-metrics":"^1.0.209","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.5","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.2.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^5.4.2","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.151","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.99"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.14.0-typescript-conversion-85dfacc7e1415844b1a3480ec71fb9a2d4c30bdb_1621267157958_0.5862547667058184","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.122-renovate-non-major-e36d4c5dedf4ff8bc0b030bf2a89c6ba46a1b952":{"name":"@balena/jellyfish-core","version":"2.13.122-renovate-non-major-e36d4c5dedf4ff8bc0b030bf2a89c6ba46a1b952","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.122-renovate-non-major-e36d4c5dedf4ff8bc0b030bf2a89c6ba46a1b952","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"345fff799fcbf09a7fb3c000189288921e569bbd","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.122-renovate-non-major-e36d4c5dedf4ff8bc0b030bf2a89c6ba46a1b952.tgz","fileCount":99,"integrity":"sha512-NCbqqKoVzSM42H34TLaRkVRFFpKqW7sqGyPiNC0OyOVkfCCCh89yPMJdC9UrgrJfkNkGiUbKB4wrKxT0VVXQpw==","signatures":[{"sig":"MEUCIFbzi91D3dZBr+wsn1d4B/tM52o1+XLkU83EzhJKp9YPAiEA0lrUc2PP5aWgP3X3/COamB2711wU08o4rBJ2CSANyc8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752347,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgora1CRA9TVsSAnZWagAAia0P/A2sGaXQ3AhirhnPAOXK\n4T/ZI9fBS8CtxrrKBCSDASyHFOc7I8E4rpWRyPbkYlT9QolVzHS5HJKHN6tH\nek8vRp2jXAKmepB0txEarCi9K9euLxff5znwiqIOb9+qPPf7ZglhnG4BIUV5\nzPMDqKRmuHjxwMF++nmOR3xrwtlEJrzMVHB8V2lP1Jez37nMrJnOGQulbQ7G\nbK6da0+LMNMhH4pyIgeUZKTc8MwOU6p/N/gTILUGXg6r9y3/vAs9oHjqvPfv\nDsz+KzinFdF76GW7nMR0G3MZqh0+ReaOeCzx6BuaWzmiQKD2M6wlsVOMVaTQ\nVSin+L8AyZp2ZDRt5zO0pG35017fK7SE95boSGZHIOJUOQt/bu2KRcprMBfm\nQIPesUXZSLshCF6monRkxojx5GU9a6R/NS0NPiIlsqagZLA92qOCDZJE+Bpt\nao7wXDv+L+xADabPkqRmhjCVhOWyrFCMcxr/c0srmazZsEfHAD5aBKA8bqHw\nfkXRduJ4O3zBFwnjrJRUljFcTZl1dJ5yocwinvAHpdyvoGO2714VK9jSa4wZ\nDa1PsmCF0Y0l65LtKD2jjmKaGUBGIn9NsF58Slzh8zVe7fIp7Wk/rdMrLEg4\nW4xLKTno+aKWJC08GS3iUaI74ynkjHdgHMuMohS0oVQ8hHt9M/Jto9USx43F\nvqCG\r\n=Jo3p\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e36d4c5dedf4ff8bc0b030bf2a89c6ba46a1b952","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.17","@balena/jellyfish-logger":"^2.1.60","@balena/jellyfish-metrics":"^1.0.209","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.7.0","eslint-plugin-import":"^2.23.2","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.152","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.122-renovate-non-major-e36d4c5dedf4ff8bc0b030bf2a89c6ba46a1b952_1621276341156_0.0816106574218114","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.122":{"name":"@balena/jellyfish-core","version":"2.13.122","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.122","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"cdd66dafb33c6f9a3b0807ccf3e30ea2834c7edd","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.122.tgz","fileCount":99,"integrity":"sha512-5EFErsJIDpyWN/AOgVkX6POL8sO4ZYN72MMEfw+XF+s0k1HmMORtojZaFhq24fZ0Qcquc8t3POqAmYvim3VDPw==","signatures":[{"sig":"MEYCIQDZBnDuAv1QpAmvSyFXMwER/oPuaeQoqsUWu8gmR6jTUgIhAKobQB/NyqrZLFHP49GVfrpdBqhJXWL0l+t7QPgP6x9q","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752287,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgoriYCRA9TVsSAnZWagAAPbIQAJReA2ajd19LZ+Kr+S/2\n7GMorROUxEs+7MIi8uxxET8qJ2mIMccoO/E1gnBEdZWXG5cMelbMFTuKBNVr\nZkMeTiNw+ycLHJAXjXbchrWNlUOiQpTO55rjMlYzz4wVGnKAKG5bgBCImE1g\nUdmpj0kexIRWAwdquvpxneanrj85VNO5zwTCezYv6hPL7iB2zGWPXg0rYAG6\nlBScci30nLtCdqFnEx0zCA7jfliLWM7puzo47tbyRztoyc64hKrAs94BR2Yw\n19sIio3Ixb4rZnnB44+JkfYRsR+jVUq5fYMxnAb9HthclMatvdJxVDEl4Yyo\nX6lL125xmifEM/BkJ+JOKZ2AHQ3XFa6KhauvR1HWsroaRpemdfjmyo+Qo0r+\ntGx5ej45lQ/Z47HOITUlhA0K8zb6BoSzRZqpwI8py5hrujR2Q7vGaQ1Ir+/D\nfBmDyN6ySsW/g15PEaSsrUNr6sffWWIiuJHnY2bC7UWM8fiE+zCDdNkPnTZ6\nyaAWeWTv0A8YJucm3xjTsXBv2OHOU3Qib10pwQdgQ0uAABG3GL8BqqujlsFc\nnQgf01fcvAWZc3jtHp8niFPQTgKNMbh8FWq6EwZkCoiIedKuTlMSt2y/Dd1w\nj94o3ohmGliKkpKqhT571LEkKN1paRv1hIU4bc3OZ7PpeNd2t5B8q2k+BSfI\nsCwR\r\n=vVjM\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"29ae5e86b8f480ced773b88948f12756971cbb6e","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.17","@balena/jellyfish-logger":"^2.1.60","@balena/jellyfish-metrics":"^1.0.209","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.7.0","eslint-plugin-import":"^2.23.2","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.152","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.122_1621276823580_0.42030213134204697","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.123-renovate-non-major-8daf31b7993984f003544172d92a5a8dfe339f85":{"name":"@balena/jellyfish-core","version":"2.13.123-renovate-non-major-8daf31b7993984f003544172d92a5a8dfe339f85","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.123-renovate-non-major-8daf31b7993984f003544172d92a5a8dfe339f85","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d7f25600c1b5a8481445fbf29f11a73cca1866e2","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.123-renovate-non-major-8daf31b7993984f003544172d92a5a8dfe339f85.tgz","fileCount":99,"integrity":"sha512-NH7b2JSPOsf1gAGH1pMYJaz4ylTtsA3FZtYweoVpGzElZC7c/qM0eFuTQr/IYFeWIt9s6Yh3LwjZROhhXWsLjQ==","signatures":[{"sig":"MEUCIEkmkLK3dD8sCEqMAEl5omwd2pD3ECVl59crj7e4/F1kAiEA+1VMf5f9WePEBzrJHv/v8xKknO6T/mHms3LVf3Di0hc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752778,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgowl3CRA9TVsSAnZWagAAYNgP/0oyRPlhByjvDmyuwL7n\nzY6EbyP376JmPpfgV0h0DqyGqW0IO2+4eti8aufepHmQEoQ5hjlPU927avKz\ntT7/MejfiODlzwm3x7itFsPkZBQf1IIuJ6eLMyaZ2+mjGj5pDN1xrYkain/+\nq8BVMWpwaLxQGcdRj4CtqZSDJrc4CuCT4bQFPMZFEru4dFCTjoEC/xT1Me6p\ny7/KMOHR+iB8Dl+KDSiFMX+UbDsFIm4sdN4xV7Hwqb014qY9EOH/on3fbyax\nzL0BZ67X0HrQIKLXxqAPhvSgxlucKWyl2DzLhEtRq4FJjKAEIG8eCrQxbaYn\n4JvXq7PVreLqL2V1TDO+UBt5SsGc7uvCi8auIv85sdtnhiZTBr0sk8gPDcf6\nlNiVnnRyKhBpAlypNtn1mHqGNLdM+Sq0SeD5r1+sWXxyiUnhNh4zjVLGzZ6K\n0wq+RNGrP5KyxUlQ37T8aF/WpMy7kRYmllK1zRENfQI5zycLcSwiJ/yQ28f8\nz79WjzxsfEtOk8faFK9xew5VdyqrxPMH7zWfJ+pRfx4KJSpJD+BYE86VU2+q\nnRNaKhUAggfz2h7WJkHqG4vUSTzJM/xKMMM6iz9ALTtzq6bvmyWhGdS0AlTI\nXvJEy/cnYccXiBtDDw6jnX9324le68yqtBXt8657Z+2I27ze+1cfhGUfq5Vg\nCTUG\r\n=/cRV\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8daf31b7993984f003544172d92a5a8dfe339f85","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.17","@balena/jellyfish-logger":"^2.1.60","@balena/jellyfish-metrics":"^1.0.210","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.7.0","eslint-plugin-import":"^2.23.2","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.152","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.123-renovate-non-major-8daf31b7993984f003544172d92a5a8dfe339f85_1621297526767_0.6411018089760214","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.123":{"name":"@balena/jellyfish-core","version":"2.13.123","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.123","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"bce4c8fb0b6f0f4ee245ad9ec6599aed0a67c0d3","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.123.tgz","fileCount":99,"integrity":"sha512-Lk0HvIyGrKG+Bk3M+ds/WxLZnNDSMXUULY3ZKk8Raqf74ovFe24lfo0BjAJ4qKKHiIok2RA32bseayExvGibYg==","signatures":[{"sig":"MEQCIEP8VsU0S/RmT8Rd68VieVDBzsVll/qSdjRAcOkysxp6AiAPqU8FTi0F9ipuosXuPPIHQ8cNDiOHDA070lpXPssXAA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752718,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgowqyCRA9TVsSAnZWagAAN8oP/2zA2IqZFNIJF2jGJ26g\nBeJBHA2XqX8aok6OxcHstPtbeentEOOfT80Qz4h0r5YeWB8GbGCIDXRWtCAn\nX6asJSD78qtrYBSq5W0RQDDRDpjuAOwevZ5ajL1U9JFboRjPwG51P9hfeIh8\n3lz/ojgIbZ89x2J6y+ho95bfaSfqwmg69C5q+8GX1tULhTg4qJIF69koe/Op\nuz4m7PF3ubD3wvF0D6RUHLlzursKATrIL7Ng2dEkSj5wUcogOCoIBvg4C9BZ\nqguk89DGhX79UrqsfVC4wUFTDwKhf/CYTox2kmhSPx9sDlofC6a5I2YYYiuX\nK5PcU2dkCUsmzGBvX2RRA+OYanPQJ0sGj7lBtMuvEYxVhUmJ3BkzkHxEYCmn\nRVz6Ykik7zLfzJigy4/THLoAh6TsfFOyGjlF2bt5i7RnN+uI90gMH69wAS/Q\nIRCM817ARQ2wpuNl++28XohP5cxA7PyW98i8l2vjvZ5fTuq4SuU/GLgM5aU9\ni1GMTWghsgCaXtjhfsr5dnCx7WNHs+qLBRxgNu43yvBVOXTIlq0y4Wf6IFDU\nxIcIt87Hav3BdXEe348Y+wCUBlYfc/c59sL20z8RKIjb3JSrIstQjYmIOrlt\nSb9D1GrvSCWYpcmjy8rXVoP4wOpE4q2Ycc9v+BuIiWaoMyks6y278kjG+n+m\n+9CZ\r\n=glme\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d62d1243e5f4e846129360b90df71b21e1b4390b","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.17","@balena/jellyfish-logger":"^2.1.60","@balena/jellyfish-metrics":"^1.0.210","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.7.0","eslint-plugin-import":"^2.23.2","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.152","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.123_1621297841983_0.5225305582766753","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.0-add-loop-field-334e746763605307543a5142e8697a7d814030a8":{"name":"@balena/jellyfish-core","version":"3.0.0-add-loop-field-334e746763605307543a5142e8697a7d814030a8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.0-add-loop-field-334e746763605307543a5142e8697a7d814030a8","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"583a7b9c27c3615fc48badd4e227d86f47abb83c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.0-add-loop-field-334e746763605307543a5142e8697a7d814030a8.tgz","fileCount":99,"integrity":"sha512-C9Ufm9fAKPZU+dyUKiht8YCbhS3Sngc+lO6pQdjqLnodZvZPJhacquiVZ3QihVp4ZY01XUu4HhqVfbHF6sngyg==","signatures":[{"sig":"MEYCIQC0SZykGobSfKwNLqBCUVye0AAKdbV5sh82AspRau3NBwIhAPPW50uAtNn9Kk5dakscVH7pPqC5pkwS2D6E97AB8Da2","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":755692,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgo0XWCRA9TVsSAnZWagAAmX8P/1alXfbQMwHZZyP8tyN5\nICjA5XMbQzJTyAVNGOzLE5LXkE/RRYorhQzqIi9/6qVVUvFn8OA2ksfHEChT\nKS594T/rgFyZV3kfVtYydwus73SjvEQGOqOJ9SzdNVvaVLk5UVUMRE6sIqyr\nVDnNndeAWCvEkGNAmBt8JzDrMASNTuRB+zXZKpZ1vP3wqh0M3x8RaNxY8Id2\nfkyne7jBij+VoiKUYgpDqmxc9OM2L4A0xMdFfnyVn2lLJ3/OoRECsydw6M+m\nbR2Iwt+Le1mrRnzYIOwvKjQDw+Of7R9uQ10jPxntfnAzkhQPqpDKCcHA4Inw\nwReSngCbhGH2HRLztn01exW/htHLDwIi4zf7uYvWnYK8YiUDqlM+N0qENBTl\n0nedPEpr2N6cPw8/mesGSTzFecYI1SegYq4mfdXNX+gfPiTeRaIOQda2fSb5\n64Bwb8LEOlLkuKkvooZH6XlIxKBKXNWRAx1Gb3WbmeGBQBWx4aQxVTLdBpnE\n1k4SAgL/cj+xwcuCAXd60x+KuALl0H7IsTvpUAyopKOtDnI3oBvN/Ie6RCvA\nEwtfs1kOM9ZAtDDJAZSmS+oS/vZRzVy4r02efzoQjV3Y2cv5hfIbWc5Sraot\nlYTvbtJyHaRZCYAgigqQrGSEbG4mDRH5ujhc7CktV70QVMgt87iX1AOhLzqV\nIMbl\r\n=MEVZ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"334e746763605307543a5142e8697a7d814030a8","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.17","@balena/jellyfish-logger":"^2.1.60","@balena/jellyfish-metrics":"^1.0.209","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.6.3","eslint-plugin-import":"^2.23.2","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.151","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.0-add-loop-field-334e746763605307543a5142e8697a7d814030a8_1621312981712_0.8671263739031556","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.0-add-loop-field-f5d2d00f8916db6324172044495538c7d0c7115b":{"name":"@balena/jellyfish-core","version":"3.0.0-add-loop-field-f5d2d00f8916db6324172044495538c7d0c7115b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.0-add-loop-field-f5d2d00f8916db6324172044495538c7d0c7115b","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"80df6e46481ad54ec93951f5e2983f7d0a946869","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.0-add-loop-field-f5d2d00f8916db6324172044495538c7d0c7115b.tgz","fileCount":99,"integrity":"sha512-X6/gUoVr2v9Oknzr8oRdcn60v66RuYrA2wp8xFdDizlowA2xkHSyhpEVqsqGObfrLRgXcWlZ9/+mZUsmE2bLvw==","signatures":[{"sig":"MEUCIHH9XpXsfLzRt14uyo4XTD8mWZCOAtBWAJxRYuYewODvAiEAnlMkSKTKcQHp5yxbANkEgncNiGYkmgMAgeCnlM3j+Gc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":756302,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgo4hjCRA9TVsSAnZWagAAwsQP/2VMWWbypr79HHLr3Tmo\n3Rq82sUHMqIUmeL18Sftwp/8s/0gUrKwhjFNHZZpqMSJxESxCuNfh0yUXFio\neH5++nC0JMO1WTd3Sq1j8NVPCwliv8O/KLKuxJ6RIDauMlyYm/ek0xdEIISE\nxJOD8qbx6k5SUdGLagc1vmWk0RTNFHB3PQOr6BJvqmbvch/2CpY6k6hxTUUz\nTo8/AiQREhoEnIEub4uP7KTg94DYPzoZ4oiUwsczjFLma8LlgTu1ZMl/4Wx/\n89/0YMLx0jeJcJzjr0kY32kO1PIihQDqlBCEBKzvH9zJujJGkoh3P8QLH/pn\n17LnkICiW3+UxNn9odko3c02vZIX5oXnE/7DBMG/h9MemaQG7vZ/g175CfGZ\n0BI8lqlNK0tO+SMvS0IhJnG5omqZhEgu12EzCscb3qUHmTGJXA3LH/Z2PX6z\ncw/ji5UUJQ2oZ/avXQ+HJsDj39wLw45ZUW6DLspQitNVW1+d2W6h8cBs8jqP\nCcGLZZL1sgJ1xf7zzHBbFPaUoqgIdVMurnDmmJ488q5/f44iS+TVDdTt8w+k\ndzh22KlZMp/DzrOIcv1Pjt2HiujMtC3rgAWJ9hPIeeCXSvg6kbbGFtmqE6TX\n+s1DGWXMyG/EOCy5VAKw49On2UJ3/M62sAGcTiHHSiIqMwcuMbsZnqtR8/w5\nGT2l\r\n=XUjg\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f5d2d00f8916db6324172044495538c7d0c7115b","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.17","@balena/jellyfish-logger":"^2.1.60","@balena/jellyfish-metrics":"^1.0.209","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.6.3","eslint-plugin-import":"^2.23.2","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.151","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.0-add-loop-field-f5d2d00f8916db6324172044495538c7d0c7115b_1621330018547_0.5406589420293002","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.124-renovate-non-major-1b239062ec45e3650b06473ec1835c735b9a79f2":{"name":"@balena/jellyfish-core","version":"2.13.124-renovate-non-major-1b239062ec45e3650b06473ec1835c735b9a79f2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.124-renovate-non-major-1b239062ec45e3650b06473ec1835c735b9a79f2","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9e492228ed9152ce3c87ef4a465a287acee79d5b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.124-renovate-non-major-1b239062ec45e3650b06473ec1835c735b9a79f2.tgz","fileCount":99,"integrity":"sha512-mBwZOeu8es5ccXgh8NgscFgQPfQ6YjUddsL63NzB748g2LbUfLPhYZRLflzW23vnSqWbfGXdfQKIxtu8qkTinw==","signatures":[{"sig":"MEUCICvfKoyQWXlDb4Qamv6wWQyl+99LYDx++R6zO1DoK7boAiEA8BG9rTOW+u4RU5yF4retqKAmyIc3YMT7HAvKoVfdhMc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":753209,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgpAWJCRA9TVsSAnZWagAA3u8P/A2oVwT07rYKwBo7nHb3\nyjyrryJ9XE10wYh+ojr2C9kSaxCYDTB3AjL4YdoQf/Jr4jQN0l8PNYiiZMLW\njW5QY4RiKszYBeMIVt8rGKz/wzvfhbf3RDD+MIaH6F8JxfKf4ijYhRHJi2Fz\nHDxsE6v2tV6C11zXUtR1AfU490ZQR1XaQOA66ABPbe8ITopR+DkXtNFfBr3L\n3vp9N1hV6b9silZpefpwzk7gig0QheD7M62wbuE0aYXC7w1uloYoabGyqbec\nHzsScuBAZvJw8yp/YJIV6B8IYdiGfMtljsvgHXpTQaIObsHJ/ifXt7WTOdfD\nOGq1QFpTnIzg2rqvokvLjk6QeoMYdAulWl6zA71n5h+5AUyYTkKEZBdsxW2q\n644v9jE73beyJfEc0C5TqbhUtRDt+3TMB1vq70XQpNLeG6TT1nAH1k2iJw5d\nfN3w0Qnc9QN5Iz1XG8HS4HVj5/HAeIIqfopKVFBLKCwmSyv0/1IeuUTGtyuS\nkJJCTfwDREmeVkYx8Iyqj4HGaerSCvNthXaO9ZkFZCIypYAI42xZnG5Dkkgw\no+i39JskWJXumRIbYS+QUNk7BtulIKkjwlIF7MWfemm5ThYG+522hWxm/iIV\n723dkzBFTQBHL8nzCjr6G03KxSLdhfSnziHyvbSCwAuRVjRiAlc9D1wvdkPc\n3ZyR\r\n=k8R9\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"1b239062ec45e3650b06473ec1835c735b9a79f2","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.17","@balena/jellyfish-logger":"^2.1.60","@balena/jellyfish-metrics":"^1.0.211","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.7.0","eslint-plugin-import":"^2.23.2","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.152","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.124-renovate-non-major-1b239062ec45e3650b06473ec1835c735b9a79f2_1621362056524_0.14823206738343275","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.124":{"name":"@balena/jellyfish-core","version":"2.13.124","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.124","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a12984042fe32b605b7087ea227e1d049a44e3bb","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.124.tgz","fileCount":99,"integrity":"sha512-LkzoX8jMKzuJxo+KXRS7p1wIWF4BAyiIGk1JJ5ro13r+5bHhQ/OKdqwOFF41UHAjzvcIh2I0Him7dDfPaUUUmw==","signatures":[{"sig":"MEQCIGX9jkL2BHkUz4hNczctH+ymZrBFXNPY5awBpKxeW4OgAiAriMxSH1lNAn77WOCZ8jgH0sS8igeHbQ9l5pWO+sgj+A==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":753149,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgpAeDCRA9TVsSAnZWagAA8DQP/1U6bP6myEkVIIH/OwC+\ndyZPQdrt5PvvtYt8wLD4OPORJ0+Byr4NeTqhvXnGN5zoZbm+UkYNY3GeL9RQ\nzxULiNaqMWW/DHZaJ9rJucjM8mQqpxwhFiEePGAB6zg2h4s/eGj93w+SV7ku\npgm7GxHonM7PNbI89UXoX7IDWkvvRH5+oB1aZaSouIc9SY+A8tkqeRJyYQRe\n5eNFLaCmCzcYZvvDK/uTrGERLsNY/4u64V0iZgKy03atsXZeviWNjBnYw4Sw\nGMg1C2Ud0d1sys/Y9BzJPsP+A3ISDElWwKxU3w/r1ISfsak+oVOeAKBPyd35\ng6wN5r268Sp5miseTLTxaOgdKKEcMUXs2+kzRVQP/8SwQD6SHRnggFxVCQl1\ngVBRP2t1TutevD13Dg+xOUnRhtevZmGD8MWH+HWGf9qjLzRs1Ki+FHE19ett\nDo5NUsMko7VkZ6i69F222KbXTzsmnkyCxirnN5Co4B9SdT40jZgHo9oYULQm\nkVP5mhAVc3tT2dSINXNHKh1r8UsAHBImcJx3t7s8DHGJ7trQIuqQsYDovBN/\n7WzMYUBWYu7lX9HSuTbOnNGZI/9IyAaE9KShfU+rXJFUXQbVi7uswCEjyaBZ\n3iOl++rZIfNLb1nkcmL6FYxw9JjPXAXHqhYwNM8JedTdkzsMy+cuMLu7xs4u\newya\r\n=jzgs\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f38c9a8adce967dbd4b857f67d208973d606598d","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.17","@balena/jellyfish-logger":"^2.1.60","@balena/jellyfish-metrics":"^1.0.211","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.7.0","eslint-plugin-import":"^2.23.2","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.152","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.124_1621362562952_0.31261722373488565","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.125-renovate-internal-non-major-3f7834e3e142d813f08c5d747eb08f36afac1f90":{"name":"@balena/jellyfish-core","version":"2.13.125-renovate-internal-non-major-3f7834e3e142d813f08c5d747eb08f36afac1f90","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.125-renovate-internal-non-major-3f7834e3e142d813f08c5d747eb08f36afac1f90","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1f2497819d96306181f0eaa55201b4e437b9ce83","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.125-renovate-internal-non-major-3f7834e3e142d813f08c5d747eb08f36afac1f90.tgz","fileCount":99,"integrity":"sha512-wwden0/wRIV3YRdjOjqDU1jhPaD+enVECNDdnrK59AABDdUI3hOuH/yTqwjjWRDvWZb39fSnGAZU1cQo1vkksA==","signatures":[{"sig":"MEUCIALIFVjQECRIEgCuZum2afjc+kK7JkUWd/SCi739R2r0AiEAuPPtWx+otlgVLtf0YXoHG8t64nS5UHYhz0xWdJIgQlg=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":753589,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgp2vHCRA9TVsSAnZWagAAnfgP/RWbNsZ5JL8zAwzAECEq\npATwqweS7IR9TsbvzmHke2L1lAG8mnPEjOiFvHxsX+Au1tZZUs+zvtHaen2w\nfI+BcLsmNsJibUSLnvAKb/XLF6FxMgacTOwZHk/d0N3OhwXSaesifqPq5AVC\nBsjpMZ9SZTj1iNcNQBV3Me41moynZNzWcsOv7g+cjXr/NyVEEv75lHs+VfAq\n8S/dxbXNvud1hnpQJU/ja3Ya7U2RwAvlgJ/YLzX5ZxkD+jeAInGqpSTln2Z9\nfLORScsLh7DwtoN0ccNarA2NeWdHVHVF7SQ96FZYhUROStIHrEjiuccoRgNG\nxY+Ti/ccb2I+QLtai3kV30gnN/JEON6Rh164Bci0M1PESDCR0iC+2GNPA1Wa\nAPfVGxIS8XHrQ0ZSut/fNxsPmCCGcNbyDjbcY/FGrw9HehD8YAP9TO8lQgcp\n1eG8LR/Eze2bTShFV0hmg+cfxLuARfVSYyipbM5aX4qCURnF/XY72nhGiysS\n8y8kXIpeVykbwtdUuZ1/yhAGKltxJxxDyKf5YkKoYgZSYbpuJT0JEyT9XHjm\nET0aS2aQoUXFFWihlcg2hJj90ae+UO6x3UpjHCMSrBgDiPlvSebW/Le8rZ/W\nFM10mhw+EVeHcw4Mg7lXYyb3PvVwbjVwfaGD4w8vVpLKkBd2S4kK8DRnCTaK\nS6nM\r\n=B4Mo\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"3f7834e3e142d813f08c5d747eb08f36afac1f90","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.19","@balena/jellyfish-logger":"^2.1.64","@balena/jellyfish-metrics":"^1.0.212","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.7.0","eslint-plugin-import":"^2.23.2","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.152","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.125-renovate-internal-non-major-3f7834e3e142d813f08c5d747eb08f36afac1f90_1621584839014_0.4848955126976877","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.125":{"name":"@balena/jellyfish-core","version":"2.13.125","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.125","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0e396303c48386c0d9b44d225add9a81f3a2a244","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.125.tgz","fileCount":99,"integrity":"sha512-c+tXTH4YyxP0jpFgriJgCI2iAlLXa/fgvHO81ruMAGZN96oXSQPD3yNgD2HQaE+4rzs29LLRhbG29MiRr09Y2w==","signatures":[{"sig":"MEUCIQDQ3SGpV4/u+m8u65TfJ412OjBxXwrgRl3I3W2VMDPKngIgaYAmeR22/ZOTCyA+FHF/OrvMapi/ZG4UtQ48AsMFiMo=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":753520,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgp2z9CRA9TVsSAnZWagAAfQcQAKDczE/O+J50VUcRFgi+\nGnt/eKBK7xRLGactHtDIy9TgLdwxsGlPbzgxALp3/HIi+Oq3JqQ3F+cV/Zv5\n1bAQMf7XEAVOudL7JmS+Zu8+svW8WHJG25wT2kx3A0apc15tO2djT6Jvgk7o\nmWB2DDbp9zkyRORGNlVbuB2fHn7QnKdSoWeTaCuTQ2SoAfiIMwKwdBjO9nh1\nkaPa3o5Vy597lYXjb9yddsduZOfMmW+W/3c7xp/l/VG3aQkSRAnRO5iTqDTI\ngReTVABdA2GSlV6ug44CqscvufIdWcL68QXdtX6spOyb63E2B8LAhXRqhx7c\njMxN3HLaxmyB2TeiHInUZ8xyeVb69enhsTE73EfPQNh/9EBma2MVKwIRcnZD\nCvxKHJr+x7H3jaVGbLtjk1R/EupPF4UPTlHubFE0peYOH/XIYN0+p/z4bUyo\naOSEqrJv4vJuQLNXhfhV7L0aaRWmSAo0UUDb2+3hUxpxLWSrcEQeKeyepDw0\nTGoe+CZAaEOqYoELIUt5rsKE9ZXT9OHoDm58hy1Xd106nbfexUqNuXyFcbQf\npPkEVdYL+TxCcO1nGopgRMhkSJMW6cD/IuXFImqivjxU0OfxU2z5mybS0Vo7\nlhJw48T4RWQhquPUGTs2bhRyg5A7QOwbf2wj+Mup49t6Iw5Njz3svajws6+u\n7k4A\r\n=qfN4\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c9cd8512fa4ad92c93d940626daaaf18b517e852","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.19","@balena/jellyfish-logger":"^2.1.64","@balena/jellyfish-metrics":"^1.0.212","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.7.0","eslint-plugin-import":"^2.23.2","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.152","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.125_1621585148787_0.20648347763426478","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.126-renovate-internal-non-major-db2d5d078e8b2ee56aab7c86d7f514e313260778":{"name":"@balena/jellyfish-core","version":"2.13.126-renovate-internal-non-major-db2d5d078e8b2ee56aab7c86d7f514e313260778","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.126-renovate-internal-non-major-db2d5d078e8b2ee56aab7c86d7f514e313260778","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5cb2e3c80a3130705f4496fb6535addeeb59de60","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.126-renovate-internal-non-major-db2d5d078e8b2ee56aab7c86d7f514e313260778.tgz","fileCount":99,"integrity":"sha512-ZpLXxcgW8pdM+xR4H5rWdDsLj7NvaoqtEKMgrf33KbugMQKGdECb0zXa1ebhc1iNuil58a+cmqyZv/kMvMzW5A==","signatures":[{"sig":"MEYCIQDXEeCA/jmltvAGnkZBngHmNTlDTHctYuGLAhBt53Y0ZAIhANPxYhIcXfPRPvyE+qcJkRZ1E5G7u4RQcU4UH95eFurF","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":753960,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgp4tyCRA9TVsSAnZWagAA1OQQAKGPPWUIzzWEXR67crnN\nB2ygBasMp/CW02kCf1+Z8owOqjXbDmaxKMjuRD6XVsMHFJ0cbw/Zh39nQMGF\n5zSJC0gacZzuC2NLt+ZapLAVBCb4xff0Pvc+tUGoQ73wam8v7BLSDJmokzUx\ntiyeGefRCYCKVESdJ9Fhz2t4pS9xKIiqo8Il/5JldBRbZQaAVyFLpENR5V23\noOBiYogH3+x+spsSoDbCzDi7PSFRazQeCzuxq1mQsi8W9EvjwN1hYjL92Nw5\n/Bfzx3k0XbdJ9sMoyAMn6st4p8sds+uplwRFGy7IGs3uBiSF6XCDXBEvSdW2\nf1rUaDJf7THTCZouXU44VMaIAioc0n4lmskOvJSNPGPZ92GoOrYDDiPheG7T\nuVn8vgcxcqJ8+uremEUYe2RwfSXd9rBaBOCSkmUfLje6B1FmRxLDpYZM2+z+\nZ0C9UkctoUI1qco8YDwC68XB0x4sHMxVAWMS51AI6tlx+oG8JEnKNmtz+8cO\n8lDJJgsztmyvkhzj7bunbUePDLhHGrDaCXptwlwvXM8WN0jJqhAgGeW0zD98\nA6UOVxekSgrO8cMULzXb+qxVkh/acA/QqwdIpFFLnwOrYNPm4ZSs3DmQdR36\nEaQLr+DYRsorRoA7TgdBcBNYRJ3LfbqTq4gqy0l4vIUbZjtWZGTHjPpFB/2U\niJl3\r\n=acWx\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"db2d5d078e8b2ee56aab7c86d7f514e313260778","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.20","@balena/jellyfish-logger":"^2.1.65","@balena/jellyfish-metrics":"^1.0.213","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.7.0","eslint-plugin-import":"^2.23.2","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.152","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.126-renovate-internal-non-major-db2d5d078e8b2ee56aab7c86d7f514e313260778_1621592945910_0.4200327745897996","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.126-renovate-internal-non-major-a417a006ef4c0b913910934416ae16bcbd1e4a04":{"name":"@balena/jellyfish-core","version":"2.13.126-renovate-internal-non-major-a417a006ef4c0b913910934416ae16bcbd1e4a04","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.126-renovate-internal-non-major-a417a006ef4c0b913910934416ae16bcbd1e4a04","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a24401dcc72571e98765e84672d2b8a87b961730","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.126-renovate-internal-non-major-a417a006ef4c0b913910934416ae16bcbd1e4a04.tgz","fileCount":99,"integrity":"sha512-+nQI/SM84mQdTrQyn8GFy1JyvacnzSdlUIxYzMzP2BOZPelN+qWJqX3gHOhMR0fIGpqQi7Szh/+oVb2dM+i/aA==","signatures":[{"sig":"MEYCIQCJ5TYI/N8UqE6mradxGx9uhj0ynQsvr3ExxCzZ+VtJOwIhAO5ej//ONpgq1CZZT/qJjwEo426T4Nm7aefvgxFF3wbP","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":753960,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgp6dyCRA9TVsSAnZWagAAwN8P/A/zXR6qAQYkgD9Ve4BB\nyPTIgDbsL5aBrCrTns0z4b+el/iPB+mW1hDq+wdLcYxVSZBWeLoRLPG3lTQ7\nF1Djskvf90iAnOr/JDUH+/yVQmsXLJxBMBiBdiZ+S5zNSZlOheNNCYK6tJNS\nurY2eMqUzjawWTsg9neRe76gwwTHJxCF+cpuWo7FWNiUFROqf1ystWJjs6c5\nxctYe4qG4QVp55qER2FZyEMBP0jrf6XZ1hrpRZOrKMHDivqIcFR09qCrIrZL\n0eM1pDOCqYExRAhyBsPts6VlOVH8hoZXecPQJ7npDlfZGKB37USg4ozIhzgY\nFKXcoaZRmA0JBcqSeRb7cV0EWfjlbHr/zr2tUYXzLR3OJTyjAkyVB4ShBcf8\nNb+nVuJOsfUoPNOUfxsjtqAofBWHufZGWiOmYFg23DeI8EMFIl+Y4Batyp8v\nSmax69waRrLtt3HUL4O68hbXe6FDo5IOrmobdhl72sbBMuwWELlmKBzvw2Vm\nHqkvpj0Vx8ZSMYfL9RfEQeNW7lblox1Tng2YpBDrr7jJwf7ZJiEXGX672YoW\nmM2KJbpe6FzaCeqbY3yGRAQBzj30uJjGz+czbhcEVuDo7zJaihWJP37RBcYF\nAgW9Jp7oG7QJ5mnJrd9uer2VxbgDP2ul/jrBuXqK1KbA+RWG+E5KV/j9MbdC\nta9K\r\n=4Jkd\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a417a006ef4c0b913910934416ae16bcbd1e4a04","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.21","@balena/jellyfish-logger":"^2.1.66","@balena/jellyfish-metrics":"^1.0.215","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.7.0","eslint-plugin-import":"^2.23.2","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.152","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.126-renovate-internal-non-major-a417a006ef4c0b913910934416ae16bcbd1e4a04_1621600113788_0.5648191765034876","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.126":{"name":"@balena/jellyfish-core","version":"2.13.126","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.126","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f4fb0c45d8a75cf045805b68d4b92ed3dc548878","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.126.tgz","fileCount":99,"integrity":"sha512-w+XGHd7KoAsMJ/rlEh592MK0pQIYmV7dAaEBnbdRGw3zaGjrPIuMIzhhM0v0sVHq0gnZ9ylYclTAZz2pj0zNtA==","signatures":[{"sig":"MEUCIQC3XRTHGa+HqeQlV0r23dgp8LeFZQ3J3oD7CZ/4uhRJsQIgeT/jz2/Un9ozoTxFIAv3ShTYlnpg/O+xVkezl6GFG2w=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":753891,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgp6j2CRA9TVsSAnZWagAAiFkP/Aunts7/UsUtvDq8bJYo\nZWK2u8L8el2msqkrjrmsCOVDDmXQHluKBLlpUJeQRhh+8SSF73pM4wBvYU2y\neltbuHo3WeUSdkqlT7q/SiP/R1Zm5bSFlQLK0XDjcqCHjToEq1CK5q6dFsB8\nJZ/KG6iFPZB2uouCLANDgPEx/iJN2PsXYxxKkGnVUg6poimGoKNVg4eb3hoG\n/xrnpRGqwhPUmNhoM7Kr+cQHiCdvUM7c8A4rRYMlHgEdz4o3N9UeE3cOhk4O\ndroN6+V6rUiP8Y9F5CFC3Vg8H5zkLxCmw+Rq31oZr+eNQF3etzfooOwM/5Z8\nmUuMgQOFT8+Q7qCT3LSpRSgkcDCOthZPuiSRO+5+esxn7vUGSynGYY3wrFEo\nzH23QwVWeB7zvFzsX/y3y0Dx/NHmULVtKyTlZVApsgMCXuGLdD1lUEsV/obe\nVs0eElzskE7cAXFHa1sKlIOIuk/hx+2a5FlVyI+CZCkCqJ4o0VeCvU0fizyH\nVGQhlbfuta6QUkqIhe1NnJ5rqkY5ftAJq+cJOW3UZ+uUJ98RAxvblMxdmQsJ\nm1Cp9fLKTO4bTiht74nU62EvyX6xtNpNmBnie99c2plz1Epzni72EWBE+ru8\nPVgw/wBpRosnsgGIaatEeHdNXskH1gri+1i9Yp9yMWFAJoz8PaT7m5fm/EWd\nq1++\r\n=QZxq\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"599681334b0c9273c0dd90b31cee6aad279ac7fa","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.21","@balena/jellyfish-logger":"^2.1.66","@balena/jellyfish-metrics":"^1.0.215","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.7.0","eslint-plugin-import":"^2.23.2","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.152","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.126_1621600502227_0.904579712060013","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.127-renovate-external-non-major-ca170d67daa948e245960d5e74471c8cc7e05121":{"name":"@balena/jellyfish-core","version":"2.13.127-renovate-external-non-major-ca170d67daa948e245960d5e74471c8cc7e05121","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.127-renovate-external-non-major-ca170d67daa948e245960d5e74471c8cc7e05121","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d9bfc36a55855b77fc7dbc8e24671cdbc994a247","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.127-renovate-external-non-major-ca170d67daa948e245960d5e74471c8cc7e05121.tgz","fileCount":99,"integrity":"sha512-HKv6UrFJOjVDLBZV+cIed4PGA6fCOZ6NW99F/sbuWcVRu6JbXcuz8Fyoyk1iYP9b3rBghPIL4yoksG0NpytTZA==","signatures":[{"sig":"MEUCIQCCjwoznZRaYFMupKzCcj86Jmvu5pMiJSGMxJ5HYAWOdQIgQSXDHOOric7Z7cG3kWLHJJQ5YFqc8WFYUQVPjQyM0uk=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":754331,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgqEzmCRA9TVsSAnZWagAAeqgQAITksilmiJEW9hrI6ykk\nI4VYXlmxGH/JSIYF+LuYXElJ1ZUmHTVhX8VP9s02PcWSHgZx7o5ZXaaUNA4R\nQy5/zJW94fQDIKZy7v619P1d8tuJ5YKzzKn4TtupMfEhrUKhFfnI/ddp/iHR\na0n5u1JsNA7PNWT86Kiav01vxl8pYukibd6NW04t/9I09sXYMTqjE/2rgqI3\nqxuPcqURRkR3sN1NohobaxoZfghHLGkQvshdJvNl/i95r/Rlic9Pk3yDkWGA\nrhmT+zD74vdPobVJMSp7sTCJw3YPo0fUq/oM0IxXs3fOlz9LX8FkdYs4CKzP\nQwTmD+J0ShTVGfpQTmM0Cfhg8aks04icq6EXmlMDfPBt9OcqbLNe78d1+zNF\no4lSXCkzSXC89JQ9WJY8RLKgwHlFWz6tvh5OuDjyOccpkWaqd6gvvu81tRRA\nt/sUcKjb/d1UjHG6LZV4nof0/ApHVk8Qp5dj+nSlfC7WbwxNKW1vv16Y36yx\nrLkQ/e+QPxexOrju/icTZ7k+12FFTWMFsUSgeQglSML21bFnG9rfIf5vUQMZ\na/dC6Ld0cnYGdrVO2+xYZjZfvq6WVTcZXVGCR8CtNJr5IcUjrvcDMVg72LHd\nBHEfhubQeCO2Jteio32ZSA2Fkco8+VdCYKZZRK/LKkOGG3RBx3/97yIdEY21\n9qR8\r\n=mlfb\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ca170d67daa948e245960d5e74471c8cc7e05121","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.21","@balena/jellyfish-logger":"^2.1.66","@balena/jellyfish-metrics":"^1.0.215","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.8.2","eslint-plugin-import":"^2.23.3","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.152","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.127-renovate-external-non-major-ca170d67daa948e245960d5e74471c8cc7e05121_1621642470063_0.26136222324084213","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.127-renovate-major-external-major-d9c2a19e19b5d74e7b8dc99aa588e00619d616db":{"name":"@balena/jellyfish-core","version":"2.13.127-renovate-major-external-major-d9c2a19e19b5d74e7b8dc99aa588e00619d616db","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.127-renovate-major-external-major-d9c2a19e19b5d74e7b8dc99aa588e00619d616db","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f9c156b8da10d22cfa1e9dd5500f20f5c0354e27","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.127-renovate-major-external-major-d9c2a19e19b5d74e7b8dc99aa588e00619d616db.tgz","fileCount":99,"integrity":"sha512-puuKikNl0qxV+L/9U9Q346JOl+4uROT5s4+p4NIvIMH970BrPfRVSjq6Zt802K76IejTiGsA1AQPCPO9ZOE8Og==","signatures":[{"sig":"MEQCIC9J5FNilGsf5KWcT2wxzCczmGR6Y9WkRR4RlfYz7IvqAiAuIS1Ss966/X7AoqSCcnwQ+YFngOZ4pzD5BG111nPzQQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":754375,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgqE1BCRA9TVsSAnZWagAAfB8P/AmwrGUiXpYbqwoZV/2Q\nC4++V/GenleFIMrCjGLiSR2t4himVQMgGIfA1bnjrdMLZR3gPxkqv6FSouTu\nthUPKzCOL70cdvAxd31N7iB9OH9emu3G9ZWs0FCwEI5DJKvyVR0px6P7ZNnZ\n1uT3t7sfnYDfp6hp84XKz6jBCl8yJVB3CMrWQnl1D/AVq9MyoV6lF+rDZKgG\n9LV2RsW3lVr3luWicfec05k6Pu6N/W5b4w+0HgOkxA19Q0YepSqkwxOEt1U2\nECYemzVql+nFYWqBdfOX8Qk80QU5IoFpuCYahBVjP8bfGY56UDqGJpwXpknY\nZ2KRS7dcvrTSLXBpShb1Gn2d0Fp4HRZaseCXS7RONpoIyiRBifLgTSdmHkHD\nXxNiPsfT62sOcr1IukoW98GU0uqg9atIHw3PgP9CiOmD9h0fXXBzdgHv0LNL\nalNyIiiAwaa7/0JPFpV2jZNMXUb/4PfDK2B9GUQIVDv81/EGmLzd0imxfkYI\nSUJTYW8VPEb83DPAaMR/pz4/1d60ND+qsFudxQoaHQmUxhoyj4gqo1OsS6Wh\nZtQ+pQRDXrXer274R7XFvGZxoiiEbkp+FiZrVIuGhwZjwSiSQC9wA/808wtd\nVfbB5rtw5QQ5Fu4Ch8+hcsz3WaBZ2+yWKba2IJ3NXgivtmh+9MPuzTadhrvz\nSvOZ\r\n=7K2e\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d9c2a19e19b5d74e7b8dc99aa588e00619d616db","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.21","@balena/jellyfish-logger":"^2.1.66","@balena/jellyfish-metrics":"^1.0.215","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.7.0","eslint-plugin-import":"^2.23.2","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.152","@json-schema-org/tests":"^2.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.127-renovate-major-external-major-d9c2a19e19b5d74e7b8dc99aa588e00619d616db_1621642560787_0.889831212770648","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.127":{"name":"@balena/jellyfish-core","version":"2.13.127","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.127","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"590e0ff89622439a6e342b1d61505f071e0599a0","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.127.tgz","fileCount":99,"integrity":"sha512-rndas6cT0joE/kJQO5XY9mAb3R1Tdv5bP6s2LGCrD6jL68u0JPKzZE/Ni9HYVsSptmfWj9NtscOkwepeoNVzAg==","signatures":[{"sig":"MEYCIQCfWCGQysKNfdPaq7hzzdM8n3OMgYvz86PnhOdUNCoJEwIhALctul+M15il+UvgH9W2LrQ5O7RLLvOh8Dtl++wbkpBS","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":754262,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgqE5LCRA9TVsSAnZWagAAb6EP/3Rr0aP7j+Yk4g7JBS11\nw8kHzb6CE+PZgBHdIqhwZdjEdxTM3Y4Erk6Vu+pa/YDkQ+5Td23YqWl88obh\nBNxLo2WIjOprTb9ZToQ5pdJ+2SfV+Q5hZuQOvE+wzq2JNZRz0ok1cK0bg6rc\nekpitTcDVq56kxDIpLrBC0yoXQpPJYg0NYFpWRXQsiUXyxtAI0RoXoMvakW9\nHzncpdtxRQb1XRrsGJidf0JBshObN59sN4nedjiA/2X7J/sl8IqtmfCt6M/y\nIiWBVwsGlJx0IgaJdN0BflIIAh4BMfSrYmoZM2UfuFESmt/o6rZBy7VCMB0b\nxWLCWSaZX7hmYURv64lsO0B5deqz46PKQs8z6/+sdcFJ+LPsj0y4i3d87V14\nshNgLzRekTPE4Cx1V+skssKV+w4ixedaPRjNOcdaIVO2xIM5R1TqPr4XJ9lF\nrz85rzp3O3wMhkWsHrthHFXKtQDN3k5MEUo4Ozp+2i/cTdrb17ZPUxjrsruw\nhREwBIp0O4Dby2Q9hs/Pj2PqCMrqP1Ts4yHR8Z0Bz4eSPxdkJQIn/DIA9BnR\nhEPAQaUkLIB2oTBE7+8Rjd6jbjqQPYYW8DI8YvQCaEKJs9Hl1qtZG2Z9oXU9\njEVqSRwfNyetNKHsM8Ap5JoK+aYdZIuSfqTFHq3sqz/7xzkh29a0ISKtQwJJ\neinx\r\n=0dEJ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"05ca6766f7c2606f62ddf689dc3caba16fb7207e","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.21","@balena/jellyfish-logger":"^2.1.66","@balena/jellyfish-metrics":"^1.0.215","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.8.2","eslint-plugin-import":"^2.23.3","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.152","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.127_1621642827104_0.803965023242442","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.128-renovate-major-external-major-491a38db789e53fab365a229c3e8205c67970526":{"name":"@balena/jellyfish-core","version":"2.13.128-renovate-major-external-major-491a38db789e53fab365a229c3e8205c67970526","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.128-renovate-major-external-major-491a38db789e53fab365a229c3e8205c67970526","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0df5afd33cf5d7ea89c6318057f8730305e4b6ce","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.128-renovate-major-external-major-491a38db789e53fab365a229c3e8205c67970526.tgz","fileCount":99,"integrity":"sha512-mKXp16WOhc+rEERA7aMWmXinyIoWb9rVfl1lf2guLygqFAiYcPdkagA84JVEfHG6k36BK1QUII8Czt9pkohp0A==","signatures":[{"sig":"MEQCIFTL/U/zwi7zEWPwtQ49RPvB9pZx9WnIOpOF3iyPGkl1AiAbBOfi6w31rLsFFiU7lPHJb4TB4nYEMzfsjdIej6lb3w==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":754746,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgqE8xCRA9TVsSAnZWagAA9pkP/jhsqQdJXlCt/y68U2x6\nOW8ZOlVbwD24EaG6MX96Abm1RU6vMVdHFQoBj50B5Lh3j1Xg63riMDnYHsxq\nNSwXq4f836jCHkHpKmUTVbQnds/bLuLUG9HWZf67FPff3+AChexhWYjX7db1\nPEAL7avalS80jgj0ZNivvAIRwlHTDnvPYkOh2jTydVC8HwdosGR2Yc3idtWE\nUhHPe/x/k1QD7264okppT0WQBDMQhZVCRkWppnFac+eZ62AR8rjEwxUiK/tL\nn+9+Fk/GZrWM4xBPedxFLsbyxIYZOWWZ60JluCJaGLehPkaNXQ7t+5fDdzcO\ns4Q6sdDSOavdi92ERB7EU9YF5/Bpq1d14UtE/85UcRPBYS7ooohP3rcZywZI\nvFGr586xECbelYMzUGj4gNTN5+gCQj+MHD1iydRVqu0K1y9TSZU4thbZtZb9\ncufdedzVPl2bp7+MfeTBm+iOBz3rAyNJGPDaT5WOGjESNP1P/5RLqfXMIrc3\nooiogGhjWztIZmMKDQlEsvR35zD9C1kXL7WfW59Se3neTIznMjeuhFOT2Wqx\nbiTAVmhxR/I4ixGDxfHLT9OyyIgv8HT/FBhohYn7JcEJ4GNmNuGtCwl/TKLt\nBagvjGsgVTansrxSzSHcYu3UqibEZdxpMlEP2EqZ1o/9kJE1DHfH2qglyT9L\n7RSG\r\n=Fv34\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"491a38db789e53fab365a229c3e8205c67970526","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.21","@balena/jellyfish-logger":"^2.1.66","@balena/jellyfish-metrics":"^1.0.215","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.8.2","eslint-plugin-import":"^2.23.3","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.152","@json-schema-org/tests":"^2.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.128-renovate-major-external-major-491a38db789e53fab365a229c3e8205c67970526_1621643057013_0.16724408514882882","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.128-renovate-internal-non-major-678d7daf30369b9af4c30748cd204008a114bbda":{"name":"@balena/jellyfish-core","version":"2.13.128-renovate-internal-non-major-678d7daf30369b9af4c30748cd204008a114bbda","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.128-renovate-internal-non-major-678d7daf30369b9af4c30748cd204008a114bbda","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"20a560bbe3789f5e506dd2ec52ef7eb5fe8b3a9b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.128-renovate-internal-non-major-678d7daf30369b9af4c30748cd204008a114bbda.tgz","fileCount":99,"integrity":"sha512-0xR05NWDcWgjgVPPuFCZVpLTA+ShfjvRxeVyuvksme2USoxfshMGOx9TfV3t1oZqkIF9bZvgNFfU2XprGWS8zQ==","signatures":[{"sig":"MEUCIQDCDx01EEMkBXLQlRYnifITTwttlRa+PoCJDmethOyekgIgS31X6aRh5bdNMP0u0XBSyGnE89J7uL2pJ7d96Bq8BnM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":754702,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgqG22CRA9TVsSAnZWagAAS9sQAJcK5Rhv6FnJqfyVM48q\ncjW9QA/MZ432o/SlZtFV3QsSq8jpFFZYrLIEBbD1oadgNV28JEAhP/VWHpy+\nlgF33H3VYHcSz25UOVD6AlzUwTS2m+vBK1qayfWu6ZiAGFwhVO9C/N5mc9fh\nDGqYoMNosYIUEgNBi6QuJb1h5trcLnNy0PIbEyDDpzEEeMyCepbg3HsjcM+t\naS+Fapm+Q3GVWkSgw2vfbqR86P9/RZsk+RJ3UmgdUOh+YARMP7kTw/mD0tgc\nFLke9jMYGX7GaWqwXcNIGCW33TkWK0dg1tb9dHZMVkRpFFiu4HJEtVbnnaal\n7C+YaB4X8No7suDxV1qe9YRrpQWd8AlV871Gq82vwdAheVVu07OaJXBEOM4Z\nqduyJG6g8o6v/P4MuJERUCv3NhUaFh7GmqBZJFV/tvmHsVZfwsbVzN1De6zl\n8xkh0D1Vkm0gUgQa8XFeo/6ITcJjqioHI03IP1OAPGg7dob9yKjExp5xsYz4\nksuVYPo377qOq27qsqRjW03ZoNLEuQlGn1Ppgxnovc0m64xV7jTizYfKaU/c\nfIta208+aakfUSdShSDjEBYo91KhpJouR/GNBPn7pj4mqaw7eiO5q+mSIo1j\nnTrUn3+6lDok6rnrw4XRI8+zgZPDu8WF6Y8zv9W9drZRxY6MSJX4iSvocxVw\nCP00\r\n=wlcH\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"678d7daf30369b9af4c30748cd204008a114bbda","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.22","@balena/jellyfish-logger":"^2.1.68","@balena/jellyfish-metrics":"^1.0.215","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.26.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.8.2","eslint-plugin-import":"^2.23.3","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.154","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.128-renovate-internal-non-major-678d7daf30369b9af4c30748cd204008a114bbda_1621650869584_0.08515581489922797","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.128-renovate-external-non-major-fffc7e1a5d2ef816a84a97875ff410c734873d76":{"name":"@balena/jellyfish-core","version":"2.13.128-renovate-external-non-major-fffc7e1a5d2ef816a84a97875ff410c734873d76","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.128-renovate-external-non-major-fffc7e1a5d2ef816a84a97875ff410c734873d76","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"196b6a65d5ca85a95b22c754fb1689454bb506ba","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.128-renovate-external-non-major-fffc7e1a5d2ef816a84a97875ff410c734873d76.tgz","fileCount":99,"integrity":"sha512-oHcM9xFBf/vmQhFe4X1+Kxj8s6kHu33dOad7ANgWo+59H3CVLvNeB6G2kA0kBdvf+iwRTxyE1uVvtwzHqK0Eig==","signatures":[{"sig":"MEUCIH7P6qBRjCgtrytfHbcdAtpvoQELCdw+7RRi7LIuvZaEAiEAxJnqZf/uQ8FodpgDqGD04RZA/E91MlE6X00sEJz5gtg=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":754722,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgqG4CCRA9TVsSAnZWagAA4X0P/ikIeF3nSaIgvSTdBi8L\n5aaJJB1pz/Bt1APVIOv7o0hrN7iWTvZQRfMdHAkSFN/c1AXbMBAKQQxucJbl\nzHJlII24nkYCCKW/hooBBfjtUwh6MpZGNn7c1jGBC2D7qO/vvMKzH8G+FNWa\nDG3bO5HWPL6M3WRx+BZo0Ut3hRxrdJxZCC3T9DmkmQ0pGiVDOfvhb5kazslP\nlURqRI6Z5gEhzql8MojmM5qCoZr8CKYBN9Mf8oFqVtk0jXlgASpaTfS5F8v3\nvWSlOPeisBgrorjrWV0AtW5oc15+7K0S3ejzVac+GppnY9UrH3vspb4yL+0v\nzed/X6R03R0bVgIPxfONFMLT794guvLl8Kj0pCkSOqwr/B7RZ7iEHxNjbDsS\nP3EWa3rJyQVEUqNGbPqsyvSEoOK13HADA38m3PQkdqJIkuBtS2bNFXjS7KaW\nFwXNGnEN+fYsyfVCLOu5CvS0NYdIixs4UsjRjX62wgolzyKgMS7j+KV+s4Uc\nlcaKy0VyG03n7j0YmxvzOwDbQ+8d8FBsD1ZlhC/JUWIoYT9zOmxOhXGgIQfZ\nxmNWTCLKTjw0cvjAOoEteqTvcuaCjEZmZ6ZpIXSaouk/CvsWvUB6J1xO8bOZ\ns2Xz6XqyEQ3LppgcSzXWP5seF2tlTs8Dweh52P+Ylz6MZza7OH2CzSv0AWw5\nPCd0\r\n=Mv71\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"fffc7e1a5d2ef816a84a97875ff410c734873d76","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.21","@balena/jellyfish-logger":"^2.1.66","@balena/jellyfish-metrics":"^1.0.215","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.27.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.8.2","eslint-plugin-import":"^2.23.3","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.152","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.128-renovate-external-non-major-fffc7e1a5d2ef816a84a97875ff410c734873d76_1621650946355_0.6406843299540304","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.128":{"name":"@balena/jellyfish-core","version":"2.13.128","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.128","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"fd134d63ce409cd5af3d26f665cc6c75b3a4b906","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.128.tgz","fileCount":99,"integrity":"sha512-kCHgIZ9h+dCvkWRYtPglA5kkFhfrpPCote/0JQSYEn0D5/Y1KkjOvauBDUlT7YBhI63WWdb3shJQc7a4Fzccwg==","signatures":[{"sig":"MEYCIQDqYpEBsXv9pWAbLShYYl+AjoJe+Ed9bccyMSahcbCv/QIhANK30v0w6y3/NhkTyqTE/bU4M1AkX003uaRPW/xuxkkI","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":754653,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgqG+KCRA9TVsSAnZWagAA7E0P/0zx3nobKKL3bMoi6J7u\ncQTWQOPUlP+KlcVFgObewJiVpxYrceRRZ1x43uHsLvak0k1RR1MzvAvvdCqI\nST09BCQSrfbzOHgG4P+1pcY8FLmou+Av32g4t6x4X5vLsxH21oH6MjFFFhpi\n2Tz0cZ8o/Gaw63dVatNXZ0PaNf8GNWFL+u8x0ejiJCJ3uM4vzuMSi3DWYiYO\nes/17NUj5ae63zhlnSeXn0gVU93hz6Wmr0lVPMtkUPqtWAIpXm2QrQzeCQpk\nGfbqru6YtuJl2yl5b4/Zl0rZGhLj/Ps4zvk2FOJdp7Y+JnAoPG2Qcis59e9V\n+DmkMytu9g7ewKaImZGmyKyPvNoI2gzCGnDovp+wMam4XT7xV6BRCb5gakv3\nVS19W7YqlesJz48lXzis2+lPrLALfqWEZODkeeHM0quir5Hs9/1zXqkeH65E\nbW8ZAByovtC9aNL/HHn1yGFD0nUEKXDFYQ4wjWYu/uWiv2eLj8xULbLQ+55y\n/yjL9D2OJ/0eFtVfwCfY+ankdphShBLuxahucTEFyvwGMb4/l4uK4imnjPTT\n6RE7176ZsdAUmPeH3r8cgrz+kkTXf0KfnTSoClYYTzd0dTS62NhJpvXz7+Vm\nCtFVGhTJhLj9thssQZCAztaaDPt2Kn9scdSzEYMDp7bu0mwpWAwufTnncJK3\nMY2j\r\n=mJl8\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7c51017d6e3415423b21cd06076074698e40dcbf","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.21","@balena/jellyfish-logger":"^2.1.66","@balena/jellyfish-metrics":"^1.0.215","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.27.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.8.2","eslint-plugin-import":"^2.23.3","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.152","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.128_1621651337472_0.429559773275197","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.129-renovate-internal-non-major-5b474adb46a196c5629c53b8f69cea1c519966fc":{"name":"@balena/jellyfish-core","version":"2.13.129-renovate-internal-non-major-5b474adb46a196c5629c53b8f69cea1c519966fc","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.129-renovate-internal-non-major-5b474adb46a196c5629c53b8f69cea1c519966fc","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5dc6a887df8281873965f2a6d792ef1989f86449","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.129-renovate-internal-non-major-5b474adb46a196c5629c53b8f69cea1c519966fc.tgz","fileCount":99,"integrity":"sha512-SygEe6Y0O4KWUj90g6AUw0BW9SWINAZORT+JDBpQNIiIApkPmFrMu9mIy0r61E6/r1MPfuZ6GFhKfnrhusTCsg==","signatures":[{"sig":"MEUCIFBl+w/jZAeCrXKzbrf82grIf8R25oNb8/s7yqSDsQBNAiEAspJepG4LAYERTUucXfZQLkEFYjBH93OKLyKTetBORIc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":755093,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgqHC1CRA9TVsSAnZWagAAdJIP/2C3C77jMv0+UyFH09Fy\nmPez4l299gZS8nTvqIGI40i1j/v7KeK0xDmKH73xmiVZx3eWWzYsSNVAX7lO\nMK9uhKoa+4F3XWrEZwlVgIQ/4W97hPKsavaxAXHXzi8mz2K/9yfjCLGjdrii\nJaGaZSarY9CWrb1ezeBfQpafCx0Xf5nOvSeyDN+LRijLsi3qxBpJy+8egpXs\nNyilfDXqOw4TJgs9buXUpipvmxZArK/0uG0F4WM4qiO4CIl1GQd9qPf6dwi2\nKdRzEPf9wydn9J0OL09UpkTjiNSlgfFLOabIhYdHX9vGGDivUAIiHofr0qfh\nILyQGFiOHmimE1dnutGSPzrQHZkV6vTT3UQeG2puQG0vlhhoxhPjKxSQuhEV\nR5BUapcyOYZfdjxOjT9sW9e9VjQS5y4l3/TfJhflgcLDa3kgH54mp2eU1r+z\nw11vTbtBkO7ySdXfYx4IL2+C88fNDLj/tHrgvpJRuxP3cgWeTAniOb9U5xk4\nIYukNayu507RZ6gsF5udWKS7XbPleEd3tZvg23i081nlSEWSJXFygnPMGy74\njL7mgtuCeHT51crFIxLDEbgOrqouBWueOseAH8tsM/nl72TSKp2+jK/U6bxK\nG/Yq0r+oz2VxDMC19+ZsGJ13jJIPJ8O5mTjBe2doyJMIgjV1ZP/o7H/IHsR8\no/ki\r\n=qHYm\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"5b474adb46a196c5629c53b8f69cea1c519966fc","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.22","@balena/jellyfish-logger":"^2.1.68","@balena/jellyfish-metrics":"^1.0.215","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.27.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.8.2","eslint-plugin-import":"^2.23.3","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.154","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.129-renovate-internal-non-major-5b474adb46a196c5629c53b8f69cea1c519966fc_1621651636865_0.016447998546865295","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.129-renovate-major-external-major-1c75f89720ee983ad0fe700581a07c35da3a33e2":{"name":"@balena/jellyfish-core","version":"2.13.129-renovate-major-external-major-1c75f89720ee983ad0fe700581a07c35da3a33e2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.129-renovate-major-external-major-1c75f89720ee983ad0fe700581a07c35da3a33e2","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"723dd27fb4d3baf811fbc1eeb6eb7ef25d9daa8e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.129-renovate-major-external-major-1c75f89720ee983ad0fe700581a07c35da3a33e2.tgz","fileCount":99,"integrity":"sha512-8s3d3grTNi8gDCe0QY0nCyFEFCf+yf2KOilkU2SWUc15IJFx+ajhkCp1BQFFemEwmM4bCbblbDV+G/M949LoRg==","signatures":[{"sig":"MEQCIDSU1h/s0FBiZJQNzPhE7GXM2yfmUUDGWmotuSjzTQ7pAiBTe/rwvEFiJT9Qa4G7plFDPI1Knn38gSQyJqLZzvmFNA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":755137,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgqHFgCRA9TVsSAnZWagAA6rsQAJra+10JnYN4MPWyC+YZ\nt4oTkqKoMNTFtycOEb28O0DQqYlpVOl8mrTodsSP80lfueW3PW0Aug4UBGXS\nhDBlTt68ugIGpEzyUckVf2+4mcmUmCLw00sMPcrl170y8Fjmom5CsLBUhHAq\npkLiT7aHEQCR6Dc+vs3iRdbHCMqMZ7+8hagyPojjCXX6n6c+JAHGa429B2hv\n361sQ0QE06F12xrUeU17iKfK30ZUjdUeQLBUbCuIjxgJPZPdu4EjZGyFJAbo\n5INB+SW+6B3bm8VRTUsPgeD8bE3vXuRAGMJJ8dIbktooVZuGGa+SeunUuIn6\nxXQQRADBileTg9W7/b/UkYyJwBPprOK1DDzuVFqxwN8NKM+OGOJhzQPD25CI\nwZyj5leeO2zpqq1b3ZPKiQnGagWbzOlaTR9FlVir159o/qIDKV36H5ljX+yN\nUVZBuvFhYoU+raoEshlAZ2QnponhP4cfjBkenVSl0/jtQJQzTUbJR0wuPBEt\nZG2xheePNi5+I0uEvpwsGmI9rqlfRedFnM4Y5wp5iCOZEOuZHA+L3geR+tFm\njxxxp+Ily0DZp4xkk7jc6EjmiIAXf97+eL0C6ZarD+Nj5rT5WNlmFSY9hiHX\nomoTgdqisZFAEm4y9yZlQs6zisHIB9kexBdIbZZtZuFWjj/1B9UQQd0uq5iH\nYKln\r\n=xhWY\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"1c75f89720ee983ad0fe700581a07c35da3a33e2","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.21","@balena/jellyfish-logger":"^2.1.66","@balena/jellyfish-metrics":"^1.0.215","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.27.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.8.2","eslint-plugin-import":"^2.23.3","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.152","@json-schema-org/tests":"^2.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.129-renovate-major-external-major-1c75f89720ee983ad0fe700581a07c35da3a33e2_1621651808057_0.0649632320338247","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.129":{"name":"@balena/jellyfish-core","version":"2.13.129","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.129","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"866441f4a0e14bd226ce9158db1492669a23ade8","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.129.tgz","fileCount":99,"integrity":"sha512-YqYmE7ywjglAl/sEO/ifKdVztQRWD5STM8mWEPHqnRsT3wq8TqL7sTnjIGksKg1mElYYdb2vIRVoGXpp+A6POA==","signatures":[{"sig":"MEYCIQDvFtuGNI77Racg9inNhRlopCq7BYCRZXlsIkBbcoGeIAIhAJFMZIZOAFGf0bnq6AekqhlUCgedgig5+fm94klRZk4T","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":755024,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgqHKBCRA9TVsSAnZWagAActYP/04/ftxMgswbYc+kWvCd\ndWBFxycBzJ4TG93kNi1yG6Ch06w+jUe8K+rBfFWh/R//50L6FIRCswNRasob\n11VO22oWE2fB+CL8qagxAE7xiZNlOlTs1j/o7i9l+q+DTiGrfPi7n+uzBFp4\nkpapByzeUpQ5Awp57jmzqDHp3uEtDuic/k0Zvo4sSw26fy2K3xoBvCJ/Ff4/\nPXf0Dz+IrS4yNTdvLsWG/Nj9F6814K/p2/jaKFaTMtTeUE0QgaVXBsh4aZc8\n/hAmo4eKfzevTNatW6tj5pS3jhPCB5zPZp9HS00Lb2GIRQJ1pMmQYcyHO1/l\nn8ishxVtIVv1tqTvUj4I+n9LpP1VWBRwpRv8HJOouUBMM2R5dCeTAcxo/z/9\nofbZsBRFMRVSFYNPFIAiLnqqE9c6lcE4AARrYPJZzq9j1gYWCKfpaXk9aGGS\nh+Q+9EC+A1eirBkDXK2MWbUvkpnsFtYRfF5NhhQ8c/8vPj+w/p9TshOMfh+u\nSca2KX7zTlSRSmlmHdXIEgI2cFD+lcyKWsJ5qPpVzUaffQDFKH/3sUEaDSLD\natCZr3pml0EPxdF2om7wb/yAzzQZjFLpGLOjGm792DWd1CFFVxaFAr48XD/s\nANNa/FAH3euHhpRmoS2CuE68ivTDhxIrLnb1dgjlO1NXFNIrn0TFRCpqSWS6\nQNxG\r\n=VfH4\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c7b340d22634bb900ebe0c60cdcb6dfd980a3510","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.22","@balena/jellyfish-logger":"^2.1.68","@balena/jellyfish-metrics":"^1.0.215","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.27.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.8.2","eslint-plugin-import":"^2.23.3","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.154","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.129_1621652097553_0.48161434594923014","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.130-renovate-major-external-major-08f70d737e1c9a27da3321c8b0225ee06fda831b":{"name":"@balena/jellyfish-core","version":"2.13.130-renovate-major-external-major-08f70d737e1c9a27da3321c8b0225ee06fda831b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.130-renovate-major-external-major-08f70d737e1c9a27da3321c8b0225ee06fda831b","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"77c9515dffccf900aaefe11e9e14d55146a3e440","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.130-renovate-major-external-major-08f70d737e1c9a27da3321c8b0225ee06fda831b.tgz","fileCount":99,"integrity":"sha512-lSJ1R9ViO9vkjfWWajDTBwmR+UtV1GMAfVtim/kWhfifUZAIxbsr+f/cLBCS8TsPpWhmWbtGc3aN4t9lBh/MgQ==","signatures":[{"sig":"MEYCIQC6SA141SnN1fvuJFjrgkQWdjAI56j/2vpZxRJh+THZogIhAMmgIywAOHfHD8n/vTKhS0sQ12hvNLeU2mloPYZsoaMJ","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":755508,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgqHN4CRA9TVsSAnZWagAAB1sP/0xOWvB+/9qqEMjN2JOM\nSjZTM0faBj2SyLe4RX6hbLKMJk+9hHdfPJuNEcPl8Vse1VvNvf0Ede29EBk8\n1N2HzGzWVXePsMlBTTnxu3XsDl3r5nCCGINL5ZQ3+bLOGXFzrb4zdzpAJf/G\nMnycthXvlg8LomwccC8b3olV1GWEuPbaj8F+CsAUVlhkmjpPHQ+xF/4y7rGX\nz12jI5jlatuxpGrZuSONs0WzlMHSiyD5Nl6lNS+KjZTs3uzweixWPMTpaLW4\nk15doec65IPtWG9uYxpgRAclTRfBOh8hzD83sAlgUR6OBJHNvvBxAlt5tAEJ\nrphS5KgizxvY3sfmCAk/Qtzei7T0Zcis7WcPIaTKjrxtJH+wXtujkAcwjdKu\nlH15EY3oKyB8RkDonWGO88qzu6NuxqE+C0hrZGTcR+PY5Pj+k+O6OtVUspcE\nLLQRFe7wGFHVD5ogL4oYFa8kgFzgkhXCoSOVkvpzwZSfnMi8h1F89+rCEKAn\nWPDZhOJ/p0tTnD3lukaEU0K5DM9Q/XbUV6upaw/X2Xn+pkBvMqORecJoYG7+\naRpZP6gfI+zhvKVi0TP2Uz8XUCZRdPfFmx01vD+bNY3BF7wuyBjTUCcFTsFl\n8M5T5QuUalaIF3OSdJkbOD0EIadv+IK494AwltzvYWsDp2j8pw0aBdww2Kmm\nzLGH\r\n=18lf\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"08f70d737e1c9a27da3321c8b0225ee06fda831b","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.22","@balena/jellyfish-logger":"^2.1.68","@balena/jellyfish-metrics":"^1.0.215","@balena/jellyfish-environment":"^4.1.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.27.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.8.2","eslint-plugin-import":"^2.23.3","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.154","@json-schema-org/tests":"^2.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.130-renovate-major-external-major-08f70d737e1c9a27da3321c8b0225ee06fda831b_1621652343957_0.8548563305688932","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.130-renovate-internal-non-major-6a1717889387ae343581bc542dbc273d893769ed":{"name":"@balena/jellyfish-core","version":"2.13.130-renovate-internal-non-major-6a1717889387ae343581bc542dbc273d893769ed","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.130-renovate-internal-non-major-6a1717889387ae343581bc542dbc273d893769ed","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a929177886103a28a92930849a91797f47d993bd","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.130-renovate-internal-non-major-6a1717889387ae343581bc542dbc273d893769ed.tgz","fileCount":99,"integrity":"sha512-RIGo7o/1T7z2qns6ULZUTK09qbE+03GPIEycyq8jzl6pbTESsF4WffoUYiGWCcpbOvyfhg/o14wgzn1HWTEF5g==","signatures":[{"sig":"MEQCIGGZ10qeAfIoOfYwodHynDEvvxiK8H5qWlxqyAnxXEDCAiBVGnANbBB2nN98FUvft8zxdSYLGVp/UAeEvIbcgAsyxg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":755464,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgqbcJCRA9TVsSAnZWagAAfXIP/1qoOwL6zV7xppsa/8Dj\nOsl2G6k6F3L2Z3O/feUl9X7TSz3Jj82ncy7q8nEd1Xgd5V0gmVoB34M+ivqa\njQcjELe4fj1UzGxUpcdSQsqAzOxc7UO7rA2jiFtoA66jLOVK33SWDpmaZ9Ib\n/5IHucXvKY0nUkWAy0CPeI86INpuBqwWH6Q5DLmJd9Hzo4ENNRRSsPN0E28G\nU5HjnoLfjmvPf2i+bk4nM8CGC1z5rCc3PGVQGPegyAVgfzfOSxRaMCINmEoM\noi61qWHLml9BLPqfdXn4vXb9xZI/bD2Z1F1YllxAT4K6qv291X0SnIOVq7XS\nNERU/MaMhAzElsD3Py7COKb80/32ybx59djBXM7ZRccqQyBt3jNr5cztClx1\nRQ7JwezExUDEGc1nlr/YpxMyM4xU95auvqsFseau8lSPCTBKTIQT5AP+NIh2\nOL7zO5vGiAeZ+S09wIIONRuMAfRdHQIb1GRe5osQI1yuevGmDCDUtJdlmNXj\nVu98olhXKfbBjh7CcVg8nTPcGzIrFuMKc5BAdKC0mb6vCDPXKorNIi27bcaB\nYXPq7GZqcGuTHKBrVVXau1SAs2Pq3b2clFlZEE2CMZc808RLUm8FUCsbBeCu\nw/Fk7Hj1eWskZs/9ZRlMTZM/jdXRL/vrAe73D9xDdhu4F2qy0FssNjFQeKa8\ndlOe\r\n=CeYZ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6a1717889387ae343581bc542dbc273d893769ed","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.22","@balena/jellyfish-logger":"^2.1.69","@balena/jellyfish-metrics":"^1.0.215","@balena/jellyfish-environment":"^4.1.14"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.27.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.8.2","eslint-plugin-import":"^2.23.3","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.154","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.130-renovate-internal-non-major-6a1717889387ae343581bc542dbc273d893769ed_1621735177507_0.31173556823385695","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.130-renovate-internal-non-major-f27a9145f6d72c50b15bb0329a506da46f03c935":{"name":"@balena/jellyfish-core","version":"2.13.130-renovate-internal-non-major-f27a9145f6d72c50b15bb0329a506da46f03c935","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.130-renovate-internal-non-major-f27a9145f6d72c50b15bb0329a506da46f03c935","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f49d01deba2e05712844aadcaf09ebe000448c7d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.130-renovate-internal-non-major-f27a9145f6d72c50b15bb0329a506da46f03c935.tgz","fileCount":99,"integrity":"sha512-fmSfIlN2E+cXxbD21U9XSPQUPS1sdDVkZzeNWd7JKbsUKKS4stjOmGzYpA/hFNpLiXezdSpHatZl9KLXgqho8w==","signatures":[{"sig":"MEUCIDEXqL5uCNtwpc2c43B7Mf3Qo79Z/k0hbMDFgVV4JYGIAiEA6MWBCT48QosPQlJ1wWQoIjrsgsRS6yjW8VU6SG1ciys=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":755464,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgqbhjCRA9TVsSAnZWagAAcA8P/2ljvrgA5crBdeG/tZDS\nC5NQbtrKo/QhfWui3xKDilxqRDZXYoDmPFq58QyM6EhJsO8b0SaSojbdmQ10\n5RdKj173ci9rl+oljxzkm8T2mJzaXs/k5XV86l9sKSYX+OtA0kq6+XAukWY4\nbO5ff61uQo8Kuf3DWGGe2iplZ5XEkx6Wp7T/Ynm8f+K6rTXt5/Pr48nV3Sha\ngM/H5TjbAgirQ46hNa3aD48XppwrC4KLQzNvLdHG9sJw93S3b9BO/t44t/nv\nVNocuo9RRlhsUO5e5CMd/tml4i4YmWYD6J+tZs6j+8VuQzQ01QAOKmwCH/So\nMU1YtG3l2dEj9U9oWQY5GoGB7U6vO8RB+TXZnRwd9SQXXW0Nm1+6VrJPPWdh\nKxe7UNgO5Sv6lYqdrKfhzT4Ji7z+4Rgwb1n5kD2Ssx6rKlkNv4i1OI756vT2\nGffu2c7JusCQVd/JQgpTSGmForgtHrzLnVDICMMDlH/V0Rs1dikzjvmN1P1S\nKw7hHB+IzZ3NMQmzkJo15JF8f3IAWKoXY3yJ5C9rJ7S2FP7CI3KZQz+jZzpQ\nMoB4U2F7+vzZeNjIPSKqmCLs4PZeMZn4nNOBqVX/RZPN5zJeCY7H1qefVO4E\npETM4S+/iOb/ucMtMFTlFk3r6wsNSKav+47/sfP3jy9F9UH67Xh5xO+BoUzf\ngLH6\r\n=HzYQ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f27a9145f6d72c50b15bb0329a506da46f03c935","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.22","@balena/jellyfish-logger":"^2.1.69","@balena/jellyfish-metrics":"^1.0.216","@balena/jellyfish-environment":"^4.1.14"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.27.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.8.2","eslint-plugin-import":"^2.23.3","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.154","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.130-renovate-internal-non-major-f27a9145f6d72c50b15bb0329a506da46f03c935_1621735523489_0.450827718517419","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.130":{"name":"@balena/jellyfish-core","version":"2.13.130","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.130","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2da749585646d71e49fd23c746acac120f2a6303","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.130.tgz","fileCount":99,"integrity":"sha512-MIbjrgtkQa03IRlUOB5RMCQSdFrAZzFadDR4C552wKtL2jiyRAy3Zj8uqGOb6YPc1B5Gtjv1mPDhTdsTnF9voQ==","signatures":[{"sig":"MEQCIGymCT42hcjxl19G73jh/UPkHnifwWcJPPb/t0AX6UXFAiAdm7EFHd2nPhJVMmXoGDpMCAzD86qBwGf3FUDtriXXbg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":755395,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgqbmECRA9TVsSAnZWagAAe48P/3NSsWFh89W2dQ8qo6XH\n5+/y2dDGOZAflNFeioJmhVDcqEb52qMFUDtkJxoqTxWD8gOH89HSCJ3xlEcF\nr57jwFiM183Mn2Rs1r8/iwBHpaHykrkxWcIndixQHcF8ojMb9eHpBGNtqitx\nPZ5ucR4Uh6z3B4EfE/w2+K7KPasvuGFc1J9GmJCLeWJI7qH6kJMzlOkztgx1\nyNeMvOdhqLIBwOiy+6MgLjsNoYyVrZ1lrRrSkOOf28OH+nLKmrSx7n1q/JNN\nGBbjD0dyeQjHV5nBUobHzshfjyzWGOtX7O2A42fhMA0gw42w1115AfrHoLD8\nYcoYH8oHcMEjUoZooJ/bLynv9Mut/h+sukuubzgQzE5uiFoJLLE6lAsr71Kc\njVFP8EDaeF5f00R4qLseqmzd12BNWUpqxutNaweiqajzSocgB7hStA7tfyS6\nL6XV/lWIiqEX1zHfTT42OO5eZNMugqqPyGg+py0+2n6Y+1mBP/mHwD4E9AfP\nyQA7FyO8bkhfgj2LTcswMBDEYeQlGeHLsaTb5n01nn6Fl6+FWcv5lqpMfx+N\neIo/JiIYT0yymiy1rikaqysTuXmr211LgarTbmbk8h4CWa+YpI6Ab+xa5H2V\nhyn9Xrf9k8BSeQIzQ0wBO1wNkIhuuIN2YVIP5dQDFxURVmTiJyDGqxfdO1gd\nFs8X\r\n=YXdC\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"01f4accf51bc9fc4ccfbca01ed8f42d998d7e9bc","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.22","@balena/jellyfish-logger":"^2.1.69","@balena/jellyfish-metrics":"^1.0.216","@balena/jellyfish-environment":"^4.1.14"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.27.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.8.2","eslint-plugin-import":"^2.23.3","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.154","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.130_1621735811515_0.07433283885508124","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.131-renovate-major-external-major-40722245c87f5a4df2635914f0dc500c6f63cc97":{"name":"@balena/jellyfish-core","version":"2.13.131-renovate-major-external-major-40722245c87f5a4df2635914f0dc500c6f63cc97","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.131-renovate-major-external-major-40722245c87f5a4df2635914f0dc500c6f63cc97","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"97131725fe72e9b045024c017a5529295ec1a731","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.131-renovate-major-external-major-40722245c87f5a4df2635914f0dc500c6f63cc97.tgz","fileCount":99,"integrity":"sha512-MZasdRT16vxPI7E18S+Z+zKhCLwlqjRC+Q8fH76cRcfcZcyxumzaXFSAAhmisHuOLwEHDiTAGDaIuvoltMbcGA==","signatures":[{"sig":"MEYCIQC1FZaxxKenZEehk9kspC0NLjXC4RiOajPq5LbeEOEM/QIhAPOniJ9N9mjnp2AhBbzDHmYRMv940AzgRAtt77Y3sYqA","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":755879,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgqbr3CRA9TVsSAnZWagAAO0IP/2T6it/1oIDmXrhTRdv2\nqtYcJgxdo4slhYOAx4Y14uE3BFhgOIds6BHeuEIyEFAawnEdO3UXQUFaHC3Q\nz55xtye8QB+IWHB9/4s1JneVjREs0JcMMv+18WMNLopVX57ZtMuEdA1DokiT\nwHnVDKZ9bc1zTMw7Z/DxSgsrxriaqWQiZWxfjE5g0ZJQpRnWKEDVlrs/95lK\nSrLKPYf7cYToavGgzfLUTWX6tixDv6pPv1tsy79f8yN7MEzFmENyCP3Aj/fY\nAcrJRvkMtWjx0xFwHgkSqbUCT7Fvko2xxwbdgEgAK4fG+ujZTa0aGZujd2pL\nTVU0Rz5nH+rq5Exg/1jMaiXm1XXlP7ZL427c5AZuo92PAR0HqbTKuo2spGrQ\nRW9tgZMDuguNvU/ewh5eq+SxIOjLz2SksHOlnDi3Bx2+L1WH2Ee5vXCsztsQ\nSOi0zT+HsZ5MiHphSIim1BKTf7LcMTiWBjINrjHPKpz6PrkpOw+4WKHnR4vz\nDnIsv/2uPzCtaRev/G67aVdMtI8MzzC3VZLZEgFkwcVQxleFoa+zkB/hdXOs\n4rPkEpC29Usc6AjSJ7O9/oj7sowqarTLuWl40m4BtYoOI19ruiXZyJhmZvmJ\nghyy/bEO9dAvhcrC5NYwNRTFNRnnkdH0GcHuPc5PuCMbLYTDmuEedYLbz3u1\nxWlh\r\n=k51Y\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"40722245c87f5a4df2635914f0dc500c6f63cc97","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.22","@balena/jellyfish-logger":"^2.1.69","@balena/jellyfish-metrics":"^1.0.216","@balena/jellyfish-environment":"^4.1.14"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.27.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.8.2","eslint-plugin-import":"^2.23.3","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.154","@json-schema-org/tests":"^2.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.131-renovate-major-external-major-40722245c87f5a4df2635914f0dc500c6f63cc97_1621736182911_0.569209807817298","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.131-renovate-internal-non-major-7c38ebf10c3521cc42967faff3b718af0a97806b":{"name":"@balena/jellyfish-core","version":"2.13.131-renovate-internal-non-major-7c38ebf10c3521cc42967faff3b718af0a97806b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.131-renovate-internal-non-major-7c38ebf10c3521cc42967faff3b718af0a97806b","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"81ec91df5156b5d64ac230461bc8ce663b05b793","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.131-renovate-internal-non-major-7c38ebf10c3521cc42967faff3b718af0a97806b.tgz","fileCount":99,"integrity":"sha512-AoXICq6MIeij0w/73ivs173dun+DJm25E1PlU16/WeNPBjX+CKApfssS7wV/YRip5qC+2Tmn+/yMnkUZIed4HQ==","signatures":[{"sig":"MEQCIEJg2+MhZiXNhVQtZFZ+ZqNb14PXjwydfqM8vsMUEd9XAiB40QS6qoJHC3FKEo79A4mO8ZUFlUqpOiN8j6bX+nvuxg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":755895,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgqbtHCRA9TVsSAnZWagAA694P/iuo8xg5iN/w+9w9/eAo\nEm0/8mtYhjStoqFUQjTuryvHo3FmrIuO9bErLN0W1SBnng9Hg7mOJEryG2X3\ngQCCn1WsJls0VXBXDMRY3UfknEph9APVWf/GuR4wbIsJuLs6Xu3bcezeyjIX\nPH840mFbKp+NrSuVve79B2Ov4N3+Jqxv9KHi54lC+oZ8AULkQISfkQnPr82T\n8cX5j5ZNGOWT+lbsGLBcxmMRhwt6Yez1AD1XILaBfzIn6hymKfyFoLFy0sSm\nglf2eknwi6Wh1KmtdbZVH/MnBsMBIj6rUo7mb1WoIUGCu41ihiytTW38/skc\nPv+3dkNTYVBcl3xpx9H0Z/KW/aYPqV424gP2hMZcwgr75YGd2FYO2DNYP83Q\naDFrANxYE9xfuaLROHHVC4TnTnQzqKYX2WF5BFBOqpmL8ex72Dne1x8XKhVw\nwI9glM/8FlHTUqs0q5kK2sKk2sbNNClEyqhF68g7YtxljiIs/Wxoil10rru3\nwtcaHxuCrm1ES5n48FDp+HyuWA7Zud26jvk4vtsu2+TVxWllBhi+0lFnEwAY\nqP2CTInuwMzdmKFZSt/cstCXwQIWeeQTp1NxKGBi8NAeydHqyPtHlFULOjBW\nrvRYvlhg+BumrXYEJbbhDIcWwkq37qU5NPrIBivqpnhJyr/3BJsIxyapvxms\nHV8A\r\n=laQb\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7c38ebf10c3521cc42967faff3b718af0a97806b","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.22","@balena/jellyfish-logger":"^2.1.69","@balena/jellyfish-metrics":"^1.0.217","@balena/jellyfish-environment":"^4.1.14"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.27.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.8.2","eslint-plugin-import":"^2.23.3","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.154","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.131-renovate-internal-non-major-7c38ebf10c3521cc42967faff3b718af0a97806b_1621736262731_0.6028724084665189","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.131":{"name":"@balena/jellyfish-core","version":"2.13.131","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.131","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"7df336ca7126e3723936d92d2bda57d61e18bdd9","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.131.tgz","fileCount":99,"integrity":"sha512-mWJXpu4cZzln0BcpRHpZeeNrm0EzV2Fhs3Kb543YKdQZNVWCEGxq+i10/II1uHFi1tO8GRJAqjwR0LOC3iONbw==","signatures":[{"sig":"MEYCIQDSh9flzLG4j+lACxW82zCSRzGxC703/AgU9j07QcNC0QIhAMoajfRZ5907Ub+Fc+gUGqyZOHP9DQAJQdnTqfmTk2SV","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":755826,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgqbznCRA9TVsSAnZWagAAmLoP/iVM3EPE7ztilIKqAlJn\nYxZTHjaH7vIB5aSvNW0pEFl6veXQfJem4L5oSpdroCOQ1ZhjIWBQ9sOOHipN\n/KuqBI3Mz3V0osR95ZJuc7ANGJGJS/F/XdJVjGs4Nqa/FCUCQ8WX9eA8n9Tc\nHdfmCJNytPPEyol1w9KGXm+dZzFAuDwyTfdcy4NgQ/ayT2ugFqJiSBHYB4za\n5EZmS2fmolZyK7q0FwGIKGM1VrFBVbsBWYF6bXQkz7Bi6XHK07ZzlE4GgHPa\nOoQZYiQ6QHkdh846PzT1BBgLmh/wezEP0KTwhXn0CeXkTGokTgN2ieuK+nca\nvFHUEkw+hkDjqzhaMUI3zBRKS7sw5Fm3wDO0vevh1JBz5aI7u9p6gjhq4IUK\n14krBCXJzuYywlXEVXziFxj933l13eABNyOoHjfm+zqxXl1AYtf+H+HCwLYn\nRK2+/gW5ws97x18b7dKllb3fibCgAUQqtE2kdmgiKg0gM2mSM1za+nrDBk5y\nkhCm2XnuVTcqR+Wvfqkz/YFkHdOHQUEd44/A4FS7UHKzusHGYUjkw/zpRzb2\n25tz+WKFok/HywOw1S3hvlCUWFGawTI8/aNCl9/46mPMbdI68yBRHjq9Eg3l\n2Y2KDZOhgyGRP2isfhLGZ7/3cBQ2W95VKN3PVqDrUmx6M4vMIKycRQCZ+QtU\nKm/s\r\n=+DRW\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"93f0c4a8077f96eacafd5c1400abf8380cc9f31a","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.22","@balena/jellyfish-logger":"^2.1.69","@balena/jellyfish-metrics":"^1.0.217","@balena/jellyfish-environment":"^4.1.14"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.27.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.8.2","eslint-plugin-import":"^2.23.3","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.154","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.131_1621736679050_0.4053448440618843","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.132-renovate-major-external-major-07e08cd372b45c97d34e2fb9763daddaadc49203":{"name":"@balena/jellyfish-core","version":"2.13.132-renovate-major-external-major-07e08cd372b45c97d34e2fb9763daddaadc49203","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.132-renovate-major-external-major-07e08cd372b45c97d34e2fb9763daddaadc49203","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"85a07248b2c07d8e5a215501feb9ddc93d48c366","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.132-renovate-major-external-major-07e08cd372b45c97d34e2fb9763daddaadc49203.tgz","fileCount":99,"integrity":"sha512-4Ckol+eG+S5EdzgSjRYLimxtHO5Hh5vYuyW/zmLMi6CCoEkTtdNnZ7AEqhGf4ICNQhRt7BXC4HJxFfC7phpUHg==","signatures":[{"sig":"MEQCIBV7UwNQgGKI04kgTHbRSwcaPyUyCs6H84Kg1BjRy8gTAiAUbkQq+MszkV2brPPMw4Fo1yvdAgw2Iyyj7lh8PLfSWw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":756310,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgqb5HCRA9TVsSAnZWagAAQRIP/ixjaj2hpBw0er5Dfs2G\nebSViRGSYAo+oaF2ba9JWNDcWo31Yx2gBAdYVKFWNXaWbuFmbMZyQAPUTV+d\n9BIgUvoa4vQzp1oNcwOc189kDs5Gtx63spZcOaH3rcrXYZz8IvDjAG7gK4/N\ntnR7rbA2MwV/Z8pZymU8YcSyjpdPEbt+KAcvnBo9d7gN/u4VHDJJxOdj/PE0\n8VkEE/y2Lr7p0Ap8LDAKJSGSm1He4aPtIpA/OgfOu5V1CiwKDchahYxUtJS2\nPMqMfC8hdlV22/RjrUmeAmOlr7+HahVtjHqHg65RDcrcyydIHrEmEwzxmOd5\nr5Ytau1F8yJ4BVIHAbiAEnemRgburvOZCsOT2McRiXHXNTgBWw+jaj4yCJ8G\nfUn+stpQZTUL6AvWnPLDCj6efIlNDAIPZlYMhv7sEzQCTuWSTWi9rQQcMF+0\nTkABs6mnreA4UFHcQtCpV7lia5CAEbcm0K9CIFYy/CmzbNe6HTmVyS+HlWyT\n+5t5QiV3bThy0TnTY80znk5hycJGh44X2GX9c14qSDrOyrOSrQZkqR/eLmEE\nESf+Am19lppcqc4XJEE2XVl/qkQlDHuVxWL8u2+ey29IWHE5QJERgUw/12g4\nr9pNr0CVoZCCaHDc/JSkg+UWCGOnHDHuGjTLec3lADKExOMHp3pOBAck0sc1\nOWWi\r\n=hF94\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"07e08cd372b45c97d34e2fb9763daddaadc49203","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.22","@balena/jellyfish-logger":"^2.1.69","@balena/jellyfish-metrics":"^1.0.217","@balena/jellyfish-environment":"^4.1.14"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.27.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.8.2","eslint-plugin-import":"^2.23.3","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.154","@json-schema-org/tests":"^2.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.132-renovate-major-external-major-07e08cd372b45c97d34e2fb9763daddaadc49203_1621737031011_0.6543339615205557","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.132-renovate-internal-patch-d6baf22094611f0f56e6f0de262cb7d4210737c5":{"name":"@balena/jellyfish-core","version":"2.13.132-renovate-internal-patch-d6baf22094611f0f56e6f0de262cb7d4210737c5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.132-renovate-internal-patch-d6baf22094611f0f56e6f0de262cb7d4210737c5","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b0650a555b90d3139fbc7aec1955f9629ffb3da2","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.132-renovate-internal-patch-d6baf22094611f0f56e6f0de262cb7d4210737c5.tgz","fileCount":99,"integrity":"sha512-K3GJ11xYXPjyceAccRzeu2mLSLTdSMqoPYk6IahoR/rq4nDhztQZe4mifcnTVxe5NPLNCS0/sMVqZ0sE0BWSKQ==","signatures":[{"sig":"MEUCIGzwFU+uubf/IYT15qES2B9/kkFyAE28bxmWbGWpwcmuAiEA8nghtylqbMKWTCBLsfcohASWblObZ9wgHfxliUiY0V0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":756328,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgqeE0CRA9TVsSAnZWagAAgj4P/2wNEl8sKzUUDJ8Ju0wp\n1Iy3coVwoiAur+gExQ/aw1OuRliMjSqYdLjxiR5IplvR46LviWSXQsJrJbQ6\nNEDnqSOVghNZAWRpX0EjYv/qIr2GfIACodyORRlng7D85VzmS0JO+we1eC2H\nyp3K/qw73/DU/gOnfdafQzl5BINM+zFw2r5fPdj1PsHtg4g+4OqSfoJiR6WN\nQ7kfEv4G/MJ7BUYTH4N1fJhzKtbOMT0DsYm2zSEpt8bYTLk3iQN8hazLdhdH\nNY0YyILq0GGAx9LJzBGkH7gQsJfQTTFjNBRuQxcEfatIFEHhZ/iztfiTlPhO\n1iCxoYlcxJNJ2KaJp8c+qHci+Y10Y83K3AuNxFzkr0wjHdfgIVE7sozwTlYe\nBTtqUzKHDIB3/hLOoSycsTKmNTu/1Ph8hiPCWT/hzWiOYNF44OVzo5ZV/+7P\nR20W0yWpmiZ4FIvB/KWDGdCowWJ+qjHO3JEqNfuB1fehvJmXDjJFuZm8228J\nZ9mZslM+qgqdWYSDGfX8U1Qz5O4qWci7kpTUACSXcyupYvGosp0MnBqtTFNu\nhsJnrhc3wDbYHFjbFFYm/42srP4iQ4k0Q1gmEaRKLDMBYP5QggJQORZdPtqG\nimxeycS5s9aP2mZ2OXVAzTffDXuLl93EYMctmlRBQ4t+5TiSZUIErpveDlVe\ndHTo\r\n=VtU5\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d6baf22094611f0f56e6f0de262cb7d4210737c5","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.22","@balena/jellyfish-logger":"^2.1.69","@balena/jellyfish-metrics":"^1.0.217","@balena/jellyfish-environment":"^4.1.15"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.27.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.8.2","eslint-plugin-import":"^2.23.3","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.154","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.132-renovate-internal-patch-d6baf22094611f0f56e6f0de262cb7d4210737c5_1621745971733_0.5055792126168004","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.132":{"name":"@balena/jellyfish-core","version":"2.13.132","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.132","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"7004d75cfb02f2aadd642a29586b92cd489faf88","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.132.tgz","fileCount":99,"integrity":"sha512-SxXgbLJIYX97w2jNHTY6vwU7s6tE17gl2o9nOG6NMve0lu1PPXtGTUcH77xyoHwmWYgq168+GbDYhTq8euGbbg==","signatures":[{"sig":"MEUCICtmZWgWxyVHU7Rcu3+S/HUs6ZUMShuFO7HPXJuQpKb1AiEAh8hq0Qo8v8uGHeoLoLj26Sj0n595buc/vWciMaLp63M=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":756263,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgqeKzCRA9TVsSAnZWagAAdRcP/0JG9HnRSnZKNxAAfElK\nk9qngmWrwJ+yZsKaHv51URvrl+EiudiMKUS7OvZ6l/qkG0e6ZImcrans5gDr\nIjuTHTljLA/vu2+uV+P2j48g7qchuy54rGnziJ82LI3VABmVMbhyX3Vy/FWc\nIELq5kDcchQL2eQhJXirXThznNfDYJZWCLlQPS1IdnV1/ZmavuA3cnEQI6ce\nRyiPew8tDWvCG9UpIsnyepQzGKPvziy4hiR7n6QeFR84lHfKTvkcABkKPW4I\nFh0Ei+wiS9RqfnIyDp2DD5FM1BFLLS40R/kgpDTzRY3d2xjcCOqGbsyW2vh4\nhuYT7fpDI38PFTS1rOcQGka3BtTZQwe0bGWbVndIn5Y2wbx/PZlQHGLGeIBA\nBzsCgw+ugFyYrZCU/CJElfcbYPGI0AQtN3v2bnnW3YKDvBSrFQ98NCLO6Q4+\nCeSTtnEqOZQt8ZQyqxGQndM+Ii/0ElyOetOhQR+qp42fx+v04xr0ACrTkyeU\nflaGj6wLxtrkwBzz5bAxoEScZNjdONe1Z/PsvY9vYPX0dqG2Lmut0qGbEek/\nsFdqdFSns5N4skLAAGaG8vI3UX8O1QQtJsn7Q2KAM7ZU+bSiHef3NwLyOSDB\nCcO5SL7FuGCBclU9xUg2ilj4geUgeBVRQd7VhEd+ety0d56RL38Ijv+l0Eog\nZacM\r\n=4W02\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2f4c14eac8f55ea65a30851a2bfd970d3cf64fa1","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.22","@balena/jellyfish-logger":"^2.1.69","@balena/jellyfish-metrics":"^1.0.217","@balena/jellyfish-environment":"^4.1.15"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.27.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.8.2","eslint-plugin-import":"^2.23.3","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.154","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.132_1621746354788_0.39225802116167796","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.133-renovate-major-external-major-ba36b8a590fb8c24a3afa209174ca8c19e28bd0f":{"name":"@balena/jellyfish-core","version":"2.13.133-renovate-major-external-major-ba36b8a590fb8c24a3afa209174ca8c19e28bd0f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.133-renovate-major-external-major-ba36b8a590fb8c24a3afa209174ca8c19e28bd0f","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"89dc7fe3f19b203670532f1e0677e4d2e2ecc70e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.133-renovate-major-external-major-ba36b8a590fb8c24a3afa209174ca8c19e28bd0f.tgz","fileCount":99,"integrity":"sha512-SduIGRb8Sd/rry0w9nSzw+9G0QeVi7lpHsyiEjNUxFQF2j4RunQkMEBxsWpTHGCzg9vcgUkvgc9VptohZT7OUw==","signatures":[{"sig":"MEYCIQDwiZUZcfh1XYHnMtzFAMj4OPud8SKI2Y+dgPN37DM0/AIhALPRPHHfyBGFHE33E5XFOPq56pBCufBcMOZT3djepZaE","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":756747,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgqeOVCRA9TVsSAnZWagAAO4cP/3sw5ebNNa134LDRxqjQ\nUhReOBzXKx+S+D1cBRHa49sqyrZldJCO/6CbvbMtBj68fnFjunbJQqVbhHC4\nmLjuSRKskRZbJ1CfHbCoz8iawQUxzCsgJAAgZu86kbthh8ufF3chkLu3CAHe\neKMbWh1nwWWedlxlMzWGKE0T/gDA2qogfHHG2zSh4JsfAEWn4Dejih9+XgQz\nN50DEHFGsI0q7RZU0IaDI6jWPtsogpwoY/Wom81GY7vvT9ZkrrPuFJErBdGn\nVSDoad6qK3MsOOLdnZfFEROOrAqsBMOvaJZ+VEMWDE2oHvzuN2nI1nD0miVd\n54lZulXPIRq3cDMVZMbbhLE/XBUxICHJG2O+8OV2HPogmtGxzLxX2Cn9sH75\n7wws1B3iRYTC0ITrUy0M3cQ9dkHRnhL6ikappECdcx8HCvs9tfEOuipYb9l6\nJ0rVQK4imU+RjegugDEko2EGH1gH+2R3NyPGionCDuyFOAkSYnsKQ0IxJAMD\n1DBYhdyt6muiq1mzbb/A/n4rRDSzrvOZD05je1VqYVVDbCWqrcgXfNrRnTu8\nyY5MMvqbsxyWRrzBmrgJsW/lmJIBqxvqHNZa38DuuQ18zv95vxUofQgzICtQ\n3384aQawKxOcfpyJ2WYxtUx3S4oEhapfxcLpMwBLNf3joGx5g1cO2H3RVQOv\nnmMV\r\n=SkjH\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ba36b8a590fb8c24a3afa209174ca8c19e28bd0f","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.22","@balena/jellyfish-logger":"^2.1.69","@balena/jellyfish-metrics":"^1.0.217","@balena/jellyfish-environment":"^4.1.15"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.27.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.8.2","eslint-plugin-import":"^2.23.3","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.154","@json-schema-org/tests":"^2.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.133-renovate-major-external-major-ba36b8a590fb8c24a3afa209174ca8c19e28bd0f_1621746580927_0.23513747635957238","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.133-renovate-internal-patch-df3b277b039dc43de3dbd4c32dd7f00cb42c78ba":{"name":"@balena/jellyfish-core","version":"2.13.133-renovate-internal-patch-df3b277b039dc43de3dbd4c32dd7f00cb42c78ba","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.133-renovate-internal-patch-df3b277b039dc43de3dbd4c32dd7f00cb42c78ba","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5f70f7344cc4ec982b30dd10077f9c13056eae45","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.133-renovate-internal-patch-df3b277b039dc43de3dbd4c32dd7f00cb42c78ba.tgz","fileCount":99,"integrity":"sha512-/VoMtqbL088zaqFPgmBPiUmmcRcTAy4j5Wqp7w8QR5xXkuoMFaUwsP2BqEjYlWPhOEq7e8qAgIo8khUXe8kwng==","signatures":[{"sig":"MEQCIE7IofQyz4ZyqOWrPn/qeJvTC85R/VDEL2iObi9sv4GJAiAyl7YaZY+/hek41nnfedMWbPe5L/YTpzanbiLTDsqbmw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":756691,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgqii5CRA9TVsSAnZWagAATRYP/3RSBWnalX3QyKWSTtLM\ntPAWvFqVY/Fl4wesDfQZnUZw/CbL/Xvww0DR5pspd21tXKBB8Pprc6e6WT7d\nGsevaHc66SQk9L5agdWUv7q9JkdLkOXf9tMoeq8+KBGZkaJpXVsycS0JbvyM\nvpCVngZw291KxwUJHIcNPcnnXTT14UKsw/fVNJMaEMwadSRfDMcE1awrn3wU\nGPaKnH9ZhPRA95vIh1dc2RHRccao8NR+oOQeLHIPkYbBvSO92FHISLGPVHW4\n5IKvBHUXjCyDeM6zUENQjc1aO8b/ZZjxhWGj8ULvDHd0+7+Gh5AQAPw74ZEP\ninQPI20GNRSyMxeHDf4k7aK21hjI883leZkrHvQZFAQGf0Tn0GfWuTNQmsZN\nLg52Whjr8GQvDg70ByLU9oQ+z+CskyexYMLF/JoO+83z5PsjB7E5l5Evczxm\nnpgVkh+ZlqjPkG83evLFMa+n0/1ayIebkMdjNj/NLPAN6OFFF8Fjnr42nquv\n3JILjtbnaHmnY+Xy5ZoNjZmSbw6va7RA6H8e7CdIEwTVSda73LAfA+IiH6d0\nzltMBJoIefVdUa64gjG8Ga0gzCRCfWke3eOZ0nAI+ofi8erjN5I/IZplE3X0\nqQY61WC/4udFAplpt4tbqv68BLbYnet7Nn/xrgz72MOtxzRNNlXWCbVpbEEo\nLnkc\r\n=vxPy\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"df3b277b039dc43de3dbd4c32dd7f00cb42c78ba","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.23","@balena/jellyfish-logger":"^2.1.70","@balena/jellyfish-metrics":"^1.0.219","@balena/jellyfish-environment":"^4.1.15"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.27.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.8.2","eslint-plugin-import":"^2.23.3","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.154","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.133-renovate-internal-patch-df3b277b039dc43de3dbd4c32dd7f00cb42c78ba_1621764280562_0.8605768168086068","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.133":{"name":"@balena/jellyfish-core","version":"2.13.133","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.133","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"93e633a66e1fd5d3ab5e4f443caa5437f549a44e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.133.tgz","fileCount":99,"integrity":"sha512-nkcB8uEMIME7JuqH1Szy2n2itdxxFpFLs6Rhv/d14MafAEwCKMtvlcqLnSVaVd5eWtKa53lqo2MkKCn0YhLE6A==","signatures":[{"sig":"MEUCIQD9gNQJtVamcIQ9ecd7HQbACvP3ZYz6SnUKNng2/sCOEwIgYggk6/sPoBFrLdmgB4fnE6ucRvQnda0bpS8LQzFiihI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":756626,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgqioICRA9TVsSAnZWagAAcGYP/15Zp79EPxRhjxN+4g6m\nsT+1LOm6qWMgXdcXATr4d4VUvOE6c+CoLz6zpl1nMFmha+r+5M5t7XvfXnQa\n0/bBR3hS15kZWUZoXYtQKMC/IWcyGz+rEDrvWS3HG4QhGCzUYxU+WbngOo2r\nuW0FA3bgI7bYkWpLeaIcp+jKK0NidEdyztGilg5/vPmpb6GQWon6k6p+n7ZA\nit0yWtd6+iXF9jJ+k5UU7+Ywk6LofWMyAdTMb3SAdI7XzFIMaSWLSF2yJLdH\n/Md46Bvo2NFpWEfrUEzhdjZZ94zZdOV1TACjYFuksygs2/2M+T3xrx/EU7/Q\nPHiG+1snjOfjQJD+/CJZH8624jXE7pFSxf9c9tJXhyTL9cB+HarEv9URDFKr\nTIiYE4W1trPcGjoH3R44QdON5vvaiKRXdDC+eCasd7fFjLDnhRQKB+zO9HpF\nDXDScYpN7aQsayz3hLtsPPyPrqVhGjaCpcbsXNvceHKxw4O7PUBMX+fqtU86\nYCblcX+b1I/N52+c0vUE+ToLQiXAUm/WoIGWUVdm9GmQUZFBZBS9PzVeTRzy\naD7kUQj4qqExKq79e82G1RgdXL7q1XNZUnWVdgDsRi7Enpsz6hJyyhFpJs0s\nOgVTR2FHNAwcjStfXVRIC0t8giyWiQDOOKSfejZkRmirD9q/vbI1dkgiylkE\npn+d\r\n=5Lzf\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7c858d44debc02f2ed4b459b2d9317eec5693636","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.23","@balena/jellyfish-logger":"^2.1.70","@balena/jellyfish-metrics":"^1.0.219","@balena/jellyfish-environment":"^4.1.15"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.27.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.8.2","eslint-plugin-import":"^2.23.3","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.154","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.133_1621764615895_0.49341094217857107","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.134-renovate-major-external-major-09b0efca3a03f0f31baa93991630f391f16ef08a":{"name":"@balena/jellyfish-core","version":"2.13.134-renovate-major-external-major-09b0efca3a03f0f31baa93991630f391f16ef08a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.134-renovate-major-external-major-09b0efca3a03f0f31baa93991630f391f16ef08a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"544f520f2fa1b3b8891a00b36fbfa88699dcb5df","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.134-renovate-major-external-major-09b0efca3a03f0f31baa93991630f391f16ef08a.tgz","fileCount":99,"integrity":"sha512-mgvq9yZZb1IzpPJeY7ewwdJx8BEH8gAyr8R6YrZyvDV3qHFJDGFJcrnDjQs2pyWaCUxL5TIHR0U48nHjFmngaw==","signatures":[{"sig":"MEUCIB/9p51aRzkp13p3X7j0ACGyzeUluOe1KsjHMTzN6fAmAiEAqw3WX2FC+IzG/Av/tiJjaFv2X7qMIANyLSvhUzzl88k=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":757110,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgqiujCRA9TVsSAnZWagAA41MP/0jH5EHpWEfq9bFJQwj1\nYPymfxn2qmaicPlV5UwaMqbLXy36meYmH/8ehGgV1ScgPWKztoXGYE0wP3d4\nOLEavAgkTs+rqAPbfSoYBOXXcU2z/uB5mKjjGyIM/36sfUV03FeyJUCPnJRc\nx3BrmlQadM62yJe4vNeHUW1l9nHTQ+d1NwtMYE0Gs5jDuhJDDsPh44G3Pz4X\nsMFdLa4vtOGiZrjUhLPt5eXMFA4YcAZe5fosar+MdekLXmq6HMMgBWLZ8LS5\nZ+GBFECyT69+ONH2xveujTRnj7+glsrF6wRM7IMIsR9+sJhJslZciYWtp7gH\n+GmDDvn6ZSAub4wmEXQeMDUnGSSBpA7aY5gE44cyfPR4mUlfqzdXV35AH8dI\nPRtrdJbHdOTLA/n4ez7kiBQKOtQf76LPer/vYftccP1yiFJQLhCmiEpifH1z\n8f8OxviGUawtFzy8qzOxxX5VoPEc5tzBvKxER7xP7ibgXz16mgte73LL9lUd\n2TFvPVFuPWo2dYje/YiLLvHwITOGnUKBFWpDWCiE5FZdxQ4YhdZS4gzkzT7I\n+fy/flQoe7LcbRBzMX/em36J4Ktkd8pDpwm33gpJJ/csGioyIKlxlks15EBH\nDkcjq+Cf3PMNhyHumiqqCq0i2Aqj3X0j+WLKfRDNA/4qM3xSzA/Gvv/gjQ+Q\nagF3\r\n=TDPL\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"09b0efca3a03f0f31baa93991630f391f16ef08a","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.23","@balena/jellyfish-logger":"^2.1.70","@balena/jellyfish-metrics":"^1.0.219","@balena/jellyfish-environment":"^4.1.15"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.27.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.8.2","eslint-plugin-import":"^2.23.3","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.154","@json-schema-org/tests":"^2.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.134-renovate-major-external-major-09b0efca3a03f0f31baa93991630f391f16ef08a_1621765027259_0.44078862913132166","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.134-renovate-internal-patch-13d896952759434984e3ff53f95f72899b4fb080":{"name":"@balena/jellyfish-core","version":"2.13.134-renovate-internal-patch-13d896952759434984e3ff53f95f72899b4fb080","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.134-renovate-internal-patch-13d896952759434984e3ff53f95f72899b4fb080","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"80474e4ec6447dea5f1049f7889b7100a27d4472","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.134-renovate-internal-patch-13d896952759434984e3ff53f95f72899b4fb080.tgz","fileCount":99,"integrity":"sha512-/cXM9J2KkQdez5OO5Xdsopntc+JgCXGe0uSzGOvUkhPAvH/H2JQMQgGZWqgr5Qa5i9B+qd/GlOyW7KbHFXl7uA==","signatures":[{"sig":"MEUCIQD9R/LjIAc2Zx02/+FmBRRRSInxLbDYC8oUWh7Bw8b1zQIgfOwOHlDHJy8nJlsvK9G9oJhlh88OxTqTx804dXmg4ys=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":757054,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgqw3tCRA9TVsSAnZWagAAzDQP/R4OggWZwmwV5z25HOUc\npbW0YZ9WtZ2H3uSylrZ+faJ0dh3gqY1yK7gQkc9Vu3+HbN12y54+llLbNwXK\nB0PFRVhReTkSBBf0M2eH4T1EO790cjhHb7Q/rTSr88O4FEcamj6MPJOigT4W\nK3cFs7tBWbkuA0KtI1Kdt7C3T7IkepbyJgt/NLfR8PcFOhf2JXFvrjluPwqF\nuvON8wfiY543cYYeJcZVWJinIGxHCNJjQIPJ4h48C+UZA5JEwheQk4LzsIWg\nxzqa+BrLVLNLzkVpnQF+Ik5jZpQGoNCrqBV8XZCgpdY2L3nO/RdIsi62btMQ\n20/0JIAWDPvqG7V8lcf5X6fcx4gYAihPPrMhadRgchU0uw3wrCYWfy21bUoz\nTmgyv+AAjLMOuPRU4F0YE+ESCxCLSWm0UF6ZbXO3uClNNvmnr0VVyQl5NTBm\ncyVukFc3yqW7ynOHePuJa1szh6cCnTJQqXk9vxfR7oZIgEvgJhTR2Hp1qGnV\nkH0hKmyC/coITSAVzP7RN79CDO2ywUnkolozmZGrpwEYAuRuYVrldmFXl9G4\nhsrBKITGtY4cuaB3PKReSCYiSr4widpJEKBerK/Q2Hu0AsPYP0/LHkuSmqmi\ndalguSB5PLRNu3xQY8FrmtI36DA/ahLZp5bhTAdq2Nt5eSRih+nfRjLuc/0b\nNK6/\r\n=3RKy\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"13d896952759434984e3ff53f95f72899b4fb080","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.23","@balena/jellyfish-logger":"^2.1.71","@balena/jellyfish-metrics":"^1.0.220","@balena/jellyfish-environment":"^4.1.15"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.27.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.8.2","eslint-plugin-import":"^2.23.3","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.154","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.134-renovate-internal-patch-13d896952759434984e3ff53f95f72899b4fb080_1621822957016_0.8074946697056151","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.134":{"name":"@balena/jellyfish-core","version":"2.13.134","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.134","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"580d55f35ae788da60078b9053cacc26601c5b87","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.134.tgz","fileCount":99,"integrity":"sha512-K0m2VwVF7fe9BNuDGeQlH5YCIMjTrfeawZDoGN8EKfXIGZuj6UgGUWw7E3Y0wOng0QB6lMsM2Yohn6JtNURXRg==","signatures":[{"sig":"MEQCIAcgvMfwFD+vcAqRLiF+Sws65a1dPGLNdul6wTRh/73wAiAJe/SR4Rgmwwb9dnLFT+WvdJsOrJYHjxURN3DNCIOAsQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":756989,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgqw/nCRA9TVsSAnZWagAAmPYP/3FIm0kfZjPvmieBTgFU\nvAoIhS8o8Rk+N/wmN+b5QsnnVgRM54dJMJ7svYvnREfJXF7dJ2IA1f8FZH+w\nlirnsZZxwFs5bqkJS09Vx2kKrHa9RYcJ23koDoBcGpLiWXXVMeRcRrYZCo8C\nAksN4Rs+JMySNOJl6XPpB30vhBPb1wvnCFlJ1spjDbwO1RaeArwJ+xL5tdQ3\nNfHnAyyZ3ouUhJQAUbeOCLjOp8VR68NDSsCivwy+WW6VqjqpMkhWpEq6Mj4W\nI9eeprG5qLPwxK0ljHKnGNTeQcgHh09xhEp0ToGkaRzOCAkkY0/ugNuRII08\nmPAuHjdgYPncXQsUVQSYk9P9ppEeNLwEBCK0d396yObozuAeBuwDRhbPXzgk\nbMTOylwcQGtQuosnB0BBnF3ly8v2WtB/PDQ/UnjgCbXRl/7HB7JqxxB1iATc\nXPfWHDJCHzog1Qu80taWN5g2pVWSvS7U80d3afnleki3OO9f+UGQLfpbbU9c\nPUEpHgiLS7TQveR67PKq3B8tlaOtieesOnWqW883VJx+6KL+il+ix5PBaha3\nPJQxHgXbrxKvhXm/AvAMdOfFXKsP+qO1veZz8ujL+529fnzcSQ8ndJOcmyh4\nw3M5Tj7dK1T7lvQYHnLs8NNSllKJg0NPOkLjTlSmf46d8zCnN4+aEap7s6Qf\nDxxy\r\n=XAq/\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"5b88368b9d3f40f3b66e15b954d5a927384635c9","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.23","@balena/jellyfish-logger":"^2.1.71","@balena/jellyfish-metrics":"^1.0.220","@balena/jellyfish-environment":"^4.1.15"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.27.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.8.2","eslint-plugin-import":"^2.23.3","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.154","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.134_1621823463305_0.9283617478118726","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.135-renovate-major-external-major-58a549b9ceafea8c181506fea2c64132f4cced23":{"name":"@balena/jellyfish-core","version":"2.13.135-renovate-major-external-major-58a549b9ceafea8c181506fea2c64132f4cced23","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.135-renovate-major-external-major-58a549b9ceafea8c181506fea2c64132f4cced23","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f0eff061b7a2d12affb51f72cecb8709fa5eb9dd","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.135-renovate-major-external-major-58a549b9ceafea8c181506fea2c64132f4cced23.tgz","fileCount":99,"integrity":"sha512-n9GeIhW+HIW7IcsVa9t9N7CDtSmDOa/5Z+/KRnKsMuYfFXbz/TUgBaCsrHkPZGDzWw45KcByWstUa3yyVXL3cA==","signatures":[{"sig":"MEUCIQD1r+mEtHdMgabyTctJabpAKaOLtHnBXygCiX5BuZBRIQIgGBlUKIYn0x079UjMfUP8JCgnnVYwVB7FQt/PKZY50Jw=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":757473,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgqxDcCRA9TVsSAnZWagAAmewP/jyAPZaLdTBNDiCkue5I\njoRAROM2Fj4HoulNbrpeJFRSLFFrfq7/ZTp10jdsM7dI87va9QjVp0XCgGyc\n+/xHP9HHoe1BuJ4CLe4v2miABFnPhUduAweV4Np9ul+rBBKeNpciiNZvBdZe\nrZ4ki2EDrxZTG6AP0S/i3ZV3ecfFIqO8O9cxdalFnsOPNW3e0bAsrV7ql2vp\nknVh1DdIhGb54DBtmQX693GjiKhIAsITDFuw+ZCsxXCRJOSJyOshlm/n10nz\nSWu3vs/bXPzSenNmngRmbEMom4plp88x1gfln/OzmfjMNtH/Uq8A57FpWNmj\n9GRE7dqDf5bXk2e/CsnzBHf4nTSyqiXdOsiMXN5i3K80OPj3hoBXdsWHEko8\nuT7GX1NpLIohvnbbKWbYoRsvDa8vT5ISS3nOmh1nHsj+lfhMtw2GY3avEmN/\nsi5+eS+INQLtYcvUprbXJdAxxSRYX1HFQq64aLTTZ2X03C4X59ihtF1HcUS6\nz2v4UUveAY2JbKWfeE3w29MO7Z9bCa3FpwLwkNVY9wpEJgBMaKa6eIlzptjt\n0y3wD5wDin0k+FOd2KvNlTRDWij8Of8pxXZou+qF89aC0POXrpQN/4wEAjKL\n6H9OXWVxWTrKgVmJnesLq+BFR75xpEDSwI6jxzVGRsx108tnpAXKrQ/LSMDm\nsXKV\r\n=SEls\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"58a549b9ceafea8c181506fea2c64132f4cced23","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.23","@balena/jellyfish-logger":"^2.1.71","@balena/jellyfish-metrics":"^1.0.220","@balena/jellyfish-environment":"^4.1.15"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.27.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.8.2","eslint-plugin-import":"^2.23.3","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.154","@json-schema-org/tests":"^2.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.135-renovate-major-external-major-58a549b9ceafea8c181506fea2c64132f4cced23_1621823707528_0.7093005911284649","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.14.0-add-link-indexes-d3c5abcb22aa231bdee56a1183b97e287af59b61":{"name":"@balena/jellyfish-core","version":"2.14.0-add-link-indexes-d3c5abcb22aa231bdee56a1183b97e287af59b61","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.14.0-add-link-indexes-d3c5abcb22aa231bdee56a1183b97e287af59b61","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"7d4db1376f8b8c2d5e26c23165cc178249a7665d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.14.0-add-link-indexes-d3c5abcb22aa231bdee56a1183b97e287af59b61.tgz","fileCount":99,"integrity":"sha512-FFuMVe1+Dn3Er/fTHtEPtXk1EarjZdyd1lfltfdDlteuw/7e7UpPMiUJFohMt9YW9Xpi1Zy5Tana6ETOyzP+iw==","signatures":[{"sig":"MEYCIQDC6oC3qEgobWmYEo5AEyTDDAMtPsUM/Jy1UfMGTF8UlQIhAJBeMoAm5gBVxbJ/6+UzIia/scFAFWmeVh5vV6fgbC0G","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":757962,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgqy9zCRA9TVsSAnZWagAAnvYP/3xLYMr6RQ78QipmB6k1\nvINb1y8iu01edfB8LR7Djaxm+odopsifVyNIFQCKBzJhCau+IN7a4xIXlrOL\nCnCvvC5SKw2jPAjoepYkAUR+mDAM8XzNY3puRXOqXnoqp36nKIwi978AY4mJ\nLs0+tgRBxYfGqg6rIl9P5+Iu8s0zLk2YJXRWQsj+PlUbpXq6WKeNJ7p18qLv\nQuQOPfUJ8LxlHZ3vRRnuJuSt+Qc+h9h0ILWZkd35lbnj7PUKZOejT58iwrkm\n+rvATTVj4qiQ2Mo/B1cNxG0aqak8Zc4bTo0zbiVnutjmwxTDK7L14yq3Fi+R\n/ALY+Jnci2jX3R7O26WOLdy1nA5yVZjJ4Cm8TmXIXJRMj9zEO7Ah4IcQuKZB\ntQ0E8Mpb3OvKCu6cpnqyx2MuYdq6xovfaBPZ0IUJ5JAhrBTxubqpmOkZLSNC\nfIeHsWJZIMKgt65gPNZTps4Unh8uaaW/wmUGjOwDf/mqyoqViZmmUHTZj11c\n8RWpFPJYJh8m05SPvemwbCdqF+jjOHO+LHNq4mmsZtoT0S5HescGThoBUlhM\ndEaozl7ROLiN1et7a/KMfNn70Lcj0dgMmyI9wn0mJXfjVcXOyG/3C0wHueMa\n9SRBfAzSGPS4gZxZV/qpifJq0B6cEqKcJL29641qGe98nQH0Ctk4V4oyBNvO\noRZ6\r\n=5zgn\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d3c5abcb22aa231bdee56a1183b97e287af59b61","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.23","@balena/jellyfish-logger":"^2.1.71","@balena/jellyfish-metrics":"^1.0.220","@balena/jellyfish-environment":"^4.1.15"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.27.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.8.2","eslint-plugin-import":"^2.23.3","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.154","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.14.0-add-link-indexes-d3c5abcb22aa231bdee56a1183b97e287af59b61_1621831539091_0.39833282257620395","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.135-renovate-internal-patch-8b990005e1704e274b3bde216ac631a3a0e12968":{"name":"@balena/jellyfish-core","version":"2.13.135-renovate-internal-patch-8b990005e1704e274b3bde216ac631a3a0e12968","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.135-renovate-internal-patch-8b990005e1704e274b3bde216ac631a3a0e12968","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"af098f5326e14139db6ad1e934e6291548ecf65b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.135-renovate-internal-patch-8b990005e1704e274b3bde216ac631a3a0e12968.tgz","fileCount":99,"integrity":"sha512-3DDjCfH8oG6lo/zWCadQT3Xfcc22vtH2/WMBWnqwS+hygxkx58535OR3Di2Bxs/h/qwpaPCXWiYuMNxvBjbBGg==","signatures":[{"sig":"MEUCIQDe9/0KvUM+YlwzVbjI/6ixqFE6zPHhvBrov5zVXd/OTwIgEFR2s/NJW4wxK+/Q9heqT3dqM+Ooh50x47kYt9HpzII=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":757417,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgq10mCRA9TVsSAnZWagAAtWEQAJMIxilW8LnechghWuGI\ndZqdMqcvxZSuiIRxrFqR7H0tVM42FqhM1TGQH1OMJ2DWlTC9TmNk7yk6DXze\nMLBF+kIF7KUQZWLr5E4qdMjfbfJraZeYBwG4sHT523F0JcT4KFWYcxg+ynyw\nWZ9nMSJtPiI/J+RXOwdYXaP4IkJiyinrlb2WO41ofySnNZ37hAgNrg/akf4c\nG2Q65EGJMR5arclhyzNT8L89kgz/1wAdrzaUlmKHc1c2NArZ1cxpXd0CWQiL\nd2mpRtB6b/Suepc0KefkOYCLhn5H3GEPAjKyCMeaOk7S0QctHFzo8PEhqlMa\n187h5sjwUd2lHCTQHHo0YERfGbsOj8Zc/Jnk4n2NFIxayeh0/SepozkYvG8L\nqkJApY7LUyimsEWqLGcIic6ycHsBGc1i0XR8vtxHW/vV1P+JvBh3apwnFfxx\nFascORssX5uqXH+5sfFPLF1bJoY4SAnqv1sqPR2+xt93gqQCVkE9P7jiltMZ\ntK7e5iBz/zXqsd/5UMaDPn+Xan27RGtkABLm6NJPVd7uZgOW4g9SArY71GbE\n13c3dHr1wsZjxt9Gk1tpyeoknUT4pGoBBQ4TdljxOHh3IhOONGYivVkpvgMN\nMKifBtvazCjRUCVPdCSchmHOpb7eqH2WmTAvRjtfEY0DPzjeCQ8S+ZhporeZ\nSi7E\r\n=tXHt\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8b990005e1704e274b3bde216ac631a3a0e12968","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.25","@balena/jellyfish-logger":"^2.1.73","@balena/jellyfish-metrics":"^1.0.223","@balena/jellyfish-environment":"^4.1.17"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.27.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.8.2","eslint-plugin-import":"^2.23.3","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.154","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.135-renovate-internal-patch-8b990005e1704e274b3bde216ac631a3a0e12968_1621843237531_0.6281179224573548","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.135":{"name":"@balena/jellyfish-core","version":"2.13.135","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.135","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"67f3961b41ef69da9ef97c0d85706237b2588795","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.135.tgz","fileCount":99,"integrity":"sha512-EMjFec8ANJ+UIxSq7PHBdpSEmRq+r5pYgUeOVYiJ4wAHWlEuIK8IRAQ42GYo7s0b2GtsSsxz9M2c399NcOGkGQ==","signatures":[{"sig":"MEUCIQC9sL5E59WoaqxGye4H6FrdM5KhnV1ZCC080QeVr7JekAIgaJNbPHHWM0nYgh7CISTWJhH4mcRwz9EvrsvJpQiER2I=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":757352,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgq15iCRA9TVsSAnZWagAAv6QP/0MzShr5uePfzXMPjeHT\n5BQZ9LoLONR0ZqaMEi1S4+dnYZA9uP47ROVv05yJPl/aGf2u3Px/FbzunM0j\n5+TSDUOCPhtCdNGYqwGkBgNBMwegpAkwAvBjAIvRyTjQsy8TNsgfRlG+9at9\n90yK1D4jfBKOVFq3p3dFt1/7ubAXw6GkCf3gezkLu+KDcsUo26L8bNcGQ2YZ\npIP2cdTvdvnbq9NiO/GQDqECPFFk+HX+dv5ogjvyatctFcDEqMHoLRB/v5h6\nkN5QwcAMxJ4lZg/8lBMLe/xtgngaukrQ1s0FcJ+g/YafG09OliyLeex+OYab\niuejAC1+xOD/uYit6bTjoY4eCoihCwA/AClT8RT9y3GS193ZEscb5zvsYoEn\nZiUvBX9Ncg06AM3/NuQZB26ITKL5qVpr4p6Z2l3mI4sjXlGt3CTXWaemGWZ6\nbfq3lHJI+wu4LeqvXWq8JylN3pdOwgqLR1yAg+dvbux70/ljp1GLpq6rDOAE\n/b84t+k0i4Tz/NjR/U9pGvVMvB5J6rZ8otz28MKCE4kNwR9zpue/Ev+rI9c8\n7Bgf8dWQSEGEt6IhSvumRxeIT9VO+R+NCzO0+pkS+JmQmCTinJ0jZh1PMGoO\nGeX0SBYKkZepfzOIQZva1hIM80FxwoLf+2iP4FUBe2Bah9WjvdbkXz64ry8O\nfKWy\r\n=Z7FU\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"536e4673674ae1eab409714c496b9f340402f254","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.25","@balena/jellyfish-logger":"^2.1.73","@balena/jellyfish-metrics":"^1.0.223","@balena/jellyfish-environment":"^4.1.17"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.27.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.8.2","eslint-plugin-import":"^2.23.3","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.154","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.135_1621843554345_0.9208623367030377","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.136-renovate-major-external-major-6afcb841e072abd0f5a986b9caa6b936a857db38":{"name":"@balena/jellyfish-core","version":"2.13.136-renovate-major-external-major-6afcb841e072abd0f5a986b9caa6b936a857db38","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.136-renovate-major-external-major-6afcb841e072abd0f5a986b9caa6b936a857db38","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5ded408c374f3da2f7c93e7378417b232b21f80e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.136-renovate-major-external-major-6afcb841e072abd0f5a986b9caa6b936a857db38.tgz","fileCount":99,"integrity":"sha512-7taIClnIuVL4NO6b1auD+IQuaReyhOX52jikWPEUVNxCQwNWpApqt8R0dN0/Ze7QDliP0kGBJBztU2gx55tlHA==","signatures":[{"sig":"MEQCIER8QyZs9HGb7yJHWJ+rENJ5oYafrDuP1a0Chu/dzoaIAiBSdaKvWi3SVtmiDpN93j/IhdicjMhDZxfCse9W/sRXCg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":757836,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgq19VCRA9TVsSAnZWagAA5agP/2h5gKAaLCCkzgAbCh5Q\n6X9slRx9+J1r5sOsPKLK/Rs5ra9oRVKOgRoYMaly5oNg96NrWcN8xfpfMov6\nPobZH+/GjYF90LhzduHHNgBjL2ojo96iZobtT5ASu4pKsOe73cE3J6mDHAtm\nRHnRyi0bEdS+cHUBo2T/KFGwcnVYXWjdDUYIatKqsM7i8LdPcC4wQj/ISyCW\ntAjS581pZvqOEBL/USFYsiYNl/uqrYWLIdwY+VpGwqmyd24oIaxHW6uDyoKG\nL+rxjG/Vlqt8ZNcTQLCJAa2MdQSEpRy2bD3xEdz50CEZuTGjRhD4w9Xzy4Yy\nVT1mh7e+xxDQPYlwjBRJOutRCL6DBlnSwAfyySJfCVGJgtQVsBArWjv3xNB/\nn47Eh+905ynoWoT0zAQN3BzBZMMCxcRZGVse1UH1FtRt/J1yKe3Oo+ebbYF+\ny+4zuJnvp9cJAUHDIglTFY7ZB7NGhSaOh1vXh/mBNKz9WLGQ2sTdllV7pUVk\nA1tuGtiQW9jhLBrQhKeBH0rkZ7r3rpxQrNCBlsAWVDzpbvo5Xwp8okYycBvo\nHlWukxI/hvP2UAzOtEko2fF3NShk3kB02R7vFjDnp4jWL2TbBPGdQKUTbcxj\nFKMNDuGZ9DxxFPj7mrF3fvVjTUMghG1PgDlFmNwjyHo3tZ/c2zzT5feyttJp\n59hs\r\n=qzI2\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6afcb841e072abd0f5a986b9caa6b936a857db38","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.25","@balena/jellyfish-logger":"^2.1.73","@balena/jellyfish-metrics":"^1.0.223","@balena/jellyfish-environment":"^4.1.17"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.27.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.8.2","eslint-plugin-import":"^2.23.3","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.154","@json-schema-org/tests":"^2.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.136-renovate-major-external-major-6afcb841e072abd0f5a986b9caa6b936a857db38_1621843797014_0.28744937557886785","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.136-renovate-major-external-major-a850d34c1ec194bfe9ebd225dee48f2ef7f79760":{"name":"@balena/jellyfish-core","version":"2.13.136-renovate-major-external-major-a850d34c1ec194bfe9ebd225dee48f2ef7f79760","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.136-renovate-major-external-major-a850d34c1ec194bfe9ebd225dee48f2ef7f79760","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a2c0f108abbc2e6168ee65519c14150252f3ff06","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.136-renovate-major-external-major-a850d34c1ec194bfe9ebd225dee48f2ef7f79760.tgz","fileCount":99,"integrity":"sha512-I6OhUnww485B08djvzCg/zBH2+zeItM3eUy39fwQv/4NQXFk0wensA4AfwZci+AYzh1Mpx3TexcXx4YUEMwcFA==","signatures":[{"sig":"MEQCIGlr53+qf1FKCV1xLmuh9Sgdn2ZKz9JUgs2Q8iQD5zEtAiA3a+b0iKBsLL1ZnbOlkZ1Yl+ttEo3o47iW24hlOPxLXA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":757786,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgq3zbCRA9TVsSAnZWagAAYi0P/3gq0JLBuPIStr4SBEcy\nzt/xPoAaMWJLA+j2Ofec+r6A/dc7OFNfGjAhuaWRIGUMPmCKJW6Rr9+T+6JY\nwloGPNWHYWQGq8DrE767NCGi9d6Rkjv2O03ArpGvcjaR+UXnZkmwG4BRs1/4\ngFontmP1K/2zhPBYsH77MFXEMCbccNoD/N0plPz3euekoVPvpv76iSFgF291\nmDFuULZ+yd9ha5MhaAa7/OX1VWyca7vJDsvYAM9L3Wu9oTbZoEZ9piaUXZpI\n3xp/wuKpM4UQYJe7+hDa2Uew7Sr4EaIPqaJmCEkDDisHxV/QmsYUXm+C6orH\nKKD67eh/M4cbxCwj3QtmwFLkDR4O9QSg5t0FA6y6CPecl5UqioxVUztKm0qU\nchtjbpMA0BZKWQf8r2LzLE5q9Bdg35yQFEjilNM4TyqIJP9Pi0kontbdepca\n/uAZ6CD3RDTsoCdiJ3ywDsKEbcXbehVG56in0bh4ol4JIa+L2jQ08XOOyOxK\nSmTABJLpiq5GvZo5ToT9p0nBigWV5OGn+o+aUDIO16jm4b/bPwPicbC90ZjW\nEgxgqLH+A16sC1+KYnGjmDsPnYn20Gayb5gGt6DsGuGD0XZJ9eHPiANIbzj4\nvHy4XbhHqnaDKBbNFuzW7vlrAFaUk1FDC9axG4G3kLQWDnp6t85DdnQdLZfp\nLvwy\r\n=oqFv\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a850d34c1ec194bfe9ebd225dee48f2ef7f79760","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.25","@balena/jellyfish-logger":"^2.1.73","@balena/jellyfish-metrics":"^1.0.223","@balena/jellyfish-environment":"^4.1.17"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.27.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^35.0.0","eslint-plugin-import":"^2.23.3","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.154","@json-schema-org/tests":"^2.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.136-renovate-major-external-major-a850d34c1ec194bfe9ebd225dee48f2ef7f79760_1621851354540_0.3967266098293558","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.14.0-typescript-conversion-7568de4d664e954101f5c8052b72d5fba6ab443a":{"name":"@balena/jellyfish-core","version":"2.14.0-typescript-conversion-7568de4d664e954101f5c8052b72d5fba6ab443a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.14.0-typescript-conversion-7568de4d664e954101f5c8052b72d5fba6ab443a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c7b58d600748bb9d5929b16cca579ff43b46d0db","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.14.0-typescript-conversion-7568de4d664e954101f5c8052b72d5fba6ab443a.tgz","fileCount":190,"integrity":"sha512-VIQidjBTNfYqOTAhawzPKdjvByxb26Im7QOVf2wZSkERlflpZlDU0GCQB6AM3vuEB1gZJHUwB56KU8QhvQbctg==","signatures":[{"sig":"MEQCIG8ly1g449FmJcY3D2Ripwwa9YHsTjeRf/yLGqjWpL7zAiBZK5kJNTOJrRPRdYTM4goGZsCxYhn/KNzsUgkbzSIT5g==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":717273,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgq4PYCRA9TVsSAnZWagAAIlQP/jGI95yZ8dlrDrnJXU6S\nWP9ICDIHwZt1qz5ZKehrMH8ZQB1DPYSwapcffmZJiQC8tPZhGr43IX0JWJ/o\n3xrRmPzkvdYBYBl1rOFyTeXdg0syxdHc15wMagHksiC9kMkK7r0H3ftPoGhY\n6mJiETRSUuVqU2FX6lazcStnuQl+rN3NWA32ESQXTbjF3Y34mm+giiO7LmnP\nLHtXEH96jHh6PeOE+3PI2za1wTX7N1wFDBmeMFc7CkCSQ+5dnuZ4KcXtVCga\ndfZBQrq3tJXYSidVgN0MRBZbrtvSZpK2H0jrQdGMNnF4Kw9Aoni9bapflQA+\nmJ9X4j1vVGammz5sQOiTxQetc83SPx8nKM8jodPl7IgG89g5RdXn4+G0pL9J\nbBmjIevhBy9SrO/jZBjgHFTmDZdUUlsPDBxWsg0nt4o/s9wUPDIMaQT7jeFF\nVH7yJpJDT7TGILEh7EQttOPdLZo/u+85VOFqxk97/NFZeU2eJvaxotoqc2Nu\n0R+vyxkiHeb76ohx1GULiDqWmBKXdp1hCiKG+tUDndsEI0kfMBnd1R2RujB+\nLabU8efFO2brlpqdLWNM5+1qCp0DvI6h/MGfOeRXABeSuhKAy0zI10/XAM/I\naXGjsZg18Sz3USLEbSy/exhJW5sxVYxVmiA/qUh4BDNrCr6R6RNao503Jp1e\nF/Ab\r\n=y5J4\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.yml up --build\n```\n\nThe tests can then be run from your host with:\n\n```\nLOGLEVEL=warn POSTGRES_USER=docker POSTGRES_PASSWORD=docker make test-integration\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"7568de4d664e954101f5c8052b72d5fba6ab443a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint --typescript lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"catch-uncommitted --skip-node-versionbot-changes && npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --typescript --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.34","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.25","@balena/jellyfish-logger":"^2.1.73","@balena/jellyfish-metrics":"^1.0.223","@balena/jellyfish-environment":"^4.1.17"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.5","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.2.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^5.4.2","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.154","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.99"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.14.0-typescript-conversion-7568de4d664e954101f5c8052b72d5fba6ab443a_1621853144278_0.5910657141270714","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.136-renovate-internal-patch-6206d4bebd14fa64bba890288d13b67179a00bb8":{"name":"@balena/jellyfish-core","version":"2.13.136-renovate-internal-patch-6206d4bebd14fa64bba890288d13b67179a00bb8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.136-renovate-internal-patch-6206d4bebd14fa64bba890288d13b67179a00bb8","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"97a511b7f3497fa35e28f942907cc2130e25868e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.136-renovate-internal-patch-6206d4bebd14fa64bba890288d13b67179a00bb8.tgz","fileCount":99,"integrity":"sha512-3tsrqNjg9M9Tp4ClsKuT7HoAPZHSyc55O7c0exPlevT4uaoa5KIL6rWsAnQQZXLFxFXu3tEW8Sz7bPin3eXYlw==","signatures":[{"sig":"MEYCIQDUVDA+l9VJkN4jRsyDprqUs8E7TEZSTYJturvh0dwKnAIhAPsc+BgaTkgdX18YXeSM/hY6lLA5TfCEN3VKeZqkZ4z+","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":757780,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgrFNkCRA9TVsSAnZWagAAy8sP/Ak0tF0KKZkbI0nQc2BC\nf+Y+x3hj5SPrvLW25AhFsQCls0sSL065yaO+XOdKjddQr5q10c+MWdl+1jK7\nr05GX1ycR4ihpxa6akPmcN0+VHYecS7vRoGD918c56ILXZNz7SnlX0adaO7D\nqurhq7zIHDke1cXgHvsBVREHLkZO0UdILP6Skv+52QD7IxViZXiQxWAN2Kyo\nYI5GnIvRvLppjgQYKDSmwSjt6i0QHaXdbcd1m0fKFuDCYean6zrsVTN/qcjN\n1tqtCqyY1We1infTA/Riz1XzuHSbkGShcn7YHJgijZq4F8Zx/MNKa+GSlfB3\nyo51QjC7rzhaIacHFPW3inL+Chot7K9wus88uspUdWKOWeeFT+wMXfXCxrlx\nfI/kLebNcsQWhbgaf29duOQp0jY4OPmHvF1SPI2nTw2syIUd0wj6HnFmgno7\n2wBaOLIgDwdG4+sN1vWDpMXGW5U2ZSbdfeAXoEo82PJ5y/hhiqjIrnTbYG+U\nq5Z51V/vvOkR5laTPj2K558xMBB9HyXd2dE5mLFiAHTt0T6IeHeOZ4lLtQov\n6sIuF6UUlLkL47BqOxijJgJeCRT1wmmGl96VERehPXTtGx78S8HdH+iRNfKB\njQUBwtmDWxUyZbmbATD0LJOPQClfql4d9GQGu7coivo4BLnwTG+vN8koGXdA\nvuR9\r\n=x93g\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6206d4bebd14fa64bba890288d13b67179a00bb8","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.27","@balena/jellyfish-logger":"^2.1.76","@balena/jellyfish-metrics":"^1.0.227","@balena/jellyfish-environment":"^4.1.19"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.27.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.8.2","eslint-plugin-import":"^2.23.3","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.154","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.136-renovate-internal-patch-6206d4bebd14fa64bba890288d13b67179a00bb8_1621906275225_0.4652557466286078","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.136":{"name":"@balena/jellyfish-core","version":"2.13.136","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.136","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"96dc5716f46c2dbe395cf08330afe3d5ed15f7d9","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.136.tgz","fileCount":99,"integrity":"sha512-uvgI99nAlcF8j/6cWAayYT9kFkVEj81SarxNWjTiBOPwfsXPmU3vs8ZjfDMkoiSaodyGl5eYNckf0qesa+fvow==","signatures":[{"sig":"MEYCIQCmvLoesDM6R0xc0NAmxU69BtWK3WGSFR3scn8OB3TQ1AIhAMMPfYFW97o8NZO5YOtM9g4XZ5KebOSwQqrw/ll2rRqf","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":757715,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgrFR8CRA9TVsSAnZWagAA52YP/jhIHuj+L+IWXa51Y8FQ\n/kuvHTaSZpYRf7UGWmX2DgdbfaFjQ/HBjU8BEj7StydwfwOgZVj01tf+Gbi1\nVucKWZNszlemf4xLd1UMMaZJ/oqSOdf/w+zDV7C/Avo6dyp/X5qeV5hjn6vp\njDI8qa+kr9sPt2KXSjGd70chYfQP+47urgJUVbg5r4G6+WVmA1p2PxkxJA6t\nT61y1V86ajtbUW4REBEvvLAOa0fK67d/UZn0pwnBbXmfBbws8ginIlEHs6nu\n5EUlxsoDtb7NlpaQJg04dGxLSqkGsrnmSsVtqJoeKqY86gx+An0qSdbIO451\n8/7bb3yrNTh7JAkcQ7bq7pK9UR21JtgMYyElbs70oeiUiVSjZEFYVIWs+0oj\nBHG3Ff5nj7psmJFKhL0/9Mk2SEf8VTu3SHSoCqCKQChMkUBJfhrhbbQbLVMM\nvoTFe++jTfbZ0BwdzZcbbzrIigBQY1XdcDdjy4iet9OCj97jHpOaDMfr8lkh\nOgvF4Leus8nlHjfnm61i+N4QkTEH+jyxE+6ywt7sm57Hm+IicSfvRsR0BpbE\nW5qfDunNhmIiHSoXr5lXHDP3aK5ACgAf5S3IayQwA4z2NJcvJyZRY1fwYpgU\nTSySJ+80+6MzFjhnm3kFVRQM069u1/CCpsePcSq/ryx8LvAGoTWoZk4Y+4rC\nJUFH\r\n=JVUw\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8649e4bdfbb9643a6c9b2345487399a8bcd7275c","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.27","@balena/jellyfish-logger":"^2.1.76","@balena/jellyfish-metrics":"^1.0.227","@balena/jellyfish-environment":"^4.1.19"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.27.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.8.2","eslint-plugin-import":"^2.23.3","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.154","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.136_1621906555213_0.2308972218411509","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.137-renovate-major-external-major-53727b83318658121fd9f556d13bd329ce24f195":{"name":"@balena/jellyfish-core","version":"2.13.137-renovate-major-external-major-53727b83318658121fd9f556d13bd329ce24f195","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.137-renovate-major-external-major-53727b83318658121fd9f556d13bd329ce24f195","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"20e5ad4cc4c15ad21ec4b255292ae159b0cf3fc1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.137-renovate-major-external-major-53727b83318658121fd9f556d13bd329ce24f195.tgz","fileCount":99,"integrity":"sha512-3KsrCMfithcclSn+sBy6eOwofjGZUZONpGNosyxqycQUGE2NrmPDlCIk4/To9bpGgbdG+1JmN3mUXauSxGjKfA==","signatures":[{"sig":"MEYCIQC6v+Y/osxmD8McCMmIORdlvjgHDQS212Qs0gSCf2yahwIhALLkI3DY7AN2zpRWtkHZ+PIUFRbBOEY39G6I5L85An7a","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":758149,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgrFX7CRA9TVsSAnZWagAAPzIP/iMyGnBHbzOUqXiOVl7J\ngdE2pUjSiUL84PcwLn4GtqiGWizR585V3B9Wcy1DXmO3JvTJmgbqCV+ORKPu\nsUCZq9+LzKaTZ//rXJJgzIo8x4FO1tMShqS3BDVEcjb57krlHuTtSbinGBMP\nSBe1GYScc6+imM+xP0RQZsBfTUmxAxeZ8Hx4E7ZA+rhq8zg23vKemKP9JQlg\nj5FiAs/1M+cLhLYjfm7k81mDsdzJsBPfRFWfbx7NMSIGWpVOwf+t9HidpXCz\nDmmwTPpni2hB2q1TUDCCjywo5XAZdQXyc9S9ZIb9wJPW0MM1nAJofbA4pe5i\nAGNfuqiWPYXe22bH9wsvVWuoRYCBeLN7mSiS8N+0SDNa+2vZRZEbCaScPPIo\n7uDZ5Wr9JZZEmJaF6ZQGX+kgaH8oL+QQYEHhgPMVW4RnRwTvM1zFbISreBZo\nXf8agtYkp9SX7Dm0hYLJK2RsM76Js0ziQvZ4OsVkEqz3XPyvgDXoOD1mjzti\n+HEqfAenGGfB9Hj7zvQ1bSVQhlhMLJhaYKueEoIBV9r4ygVSoc8TH3EVVj1M\nod2dpsEaBIW33PVn0w1bZQHkuDzTSQ1Flfk4NzXhQfbkc6jP9WtnD4RiykAm\nJ2vXli/DpsbZzcbwF2Ituf09f844WoZkXY9rd0ZaprSbvfdI5GbCiT6jATzD\nrNQz\r\n=wCh1\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"53727b83318658121fd9f556d13bd329ce24f195","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.27","@balena/jellyfish-logger":"^2.1.76","@balena/jellyfish-metrics":"^1.0.227","@balena/jellyfish-environment":"^4.1.19"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.27.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^35.0.0","eslint-plugin-import":"^2.23.3","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.154","@json-schema-org/tests":"^2.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.137-renovate-major-external-major-53727b83318658121fd9f556d13bd329ce24f195_1621906938569_0.8983513350596359","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.137-renovate-internal-patch-a54e99f226a7fe6cef8e7c13350fbd8e15c9afa9":{"name":"@balena/jellyfish-core","version":"2.13.137-renovate-internal-patch-a54e99f226a7fe6cef8e7c13350fbd8e15c9afa9","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.137-renovate-internal-patch-a54e99f226a7fe6cef8e7c13350fbd8e15c9afa9","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5b05c87826b39bac3214b8b47c5cab51a899f9d8","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.137-renovate-internal-patch-a54e99f226a7fe6cef8e7c13350fbd8e15c9afa9.tgz","fileCount":99,"integrity":"sha512-d+KfdGhzxZl0U3oXUVdDtwl6KctEkgsmPpQKBVxaOuuPbGO5+P8gwU+SwL7KFKPW+e9V185YVCKBjdQImPAVWA==","signatures":[{"sig":"MEYCIQCgRSn6nJJr/NS1mUNSY+N0M7rlo99ZBKTZ4qIZkyJNegIhANkTxUEQHnSUX4TpJgDMilnKdQgzEWAxkZB0KxjP0Xg0","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":758143,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgrHpJCRA9TVsSAnZWagAAcaoP/25u9+qpJkTVIbcsz+rU\nlbYEAArPnAlRsSQVVl56hCZ1vL+PQ3fEzDv7bqalbUVUA2grrcY18CpcBX6F\nKYiBjfD/DQqrwUhTP5uzCEN5hsR5h3oTXEPrruUPtMiCRQt9av6hqXRL5PF8\ntvzOxqP3TqmlyN+OY0w4c7QhdxPesGpwaPRzukfMsEi/TQv0TAVFX+V7z1eO\nKCUmNTvDisNjzgX7gqMJ5sx1sn3Ab2qGh4UtIAR8oFU+gnzrTl08FbVYjrkt\nLbpLiCui9CeXfBSYXjj4fMjJJwEIwfHAx8+/40J2tnZ7aYGIL61E/a4rsTmU\nltvQ8lwCjP91s3iesUMMElRFy9wYzEjrPbL7wZ0j8rsqT2mvEsM162IIwFQ9\n8jW0p8rPS050ClyfHJq33DQGWuNy/lvlbZJ6kw2AONH+NbeW/eISr+tGHTsz\nmuqaWfcIKrt6chn7ycS/O9HR0EGPI0kZ9v0u3M1WGUEdQyobtpSZQGEGCpIk\nEAZz5rwi5vQ9BBPsrhIMqT0It3ZmWD1HKXFZ1y5giMspmBRMRKoAl6PCshIA\n2hm+oOgeKEkA8T3BOPNDowqrD0/LQkZ9pslBVyGmOjKewLYPGzPd0nYnQhbR\n3vGCtqwM4uqBMy/5UCe2jffU24FHsJIqpAOeht005BB48Iudr8WzNtvv/z0A\nbPbT\r\n=NavW\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a54e99f226a7fe6cef8e7c13350fbd8e15c9afa9","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.27","@balena/jellyfish-logger":"^2.1.78","@balena/jellyfish-metrics":"^1.0.227","@balena/jellyfish-environment":"^4.1.20"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.27.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.8.2","eslint-plugin-import":"^2.23.3","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.154","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.137-renovate-internal-patch-a54e99f226a7fe6cef8e7c13350fbd8e15c9afa9_1621916232989_0.44843356570016346","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.137":{"name":"@balena/jellyfish-core","version":"2.13.137","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.137","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"74f4e5905c61307c3fa7c986ebce7b6be2ae88a9","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.137.tgz","fileCount":99,"integrity":"sha512-KM0nD9Fec2CMy9ZI41YJxdzKCcrFBjC2rjqS+z/BLLgbGyOdlwDL66wod9gFkDClnOt+jefw+dlbvgA2Bfrmig==","signatures":[{"sig":"MEUCIHhu7fUPSonodzFetOC1ThCHt4GSkU5kjBunqUf1LD25AiEAxKDmy3MmkVEXefQ/UgS+WBTXfC3n41cAQD4aL0jZxdQ=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":758078,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgrHuwCRA9TVsSAnZWagAAC2wP/340P0FL/vfi+UHpqm0I\napWQfGt9UYP/xZAsc6luw+/vooqvLxvzra/NbplA+xv5KmU2+Y3sI7THpKWd\nnhQuxxZFK1h4loU8xCGFA12YZw8s3O7ko80AIYO59CwDbJOdaftiwaDstv0m\n+reLocpS26n5mIk7sPCALg1OetiesXoVo5ndwTwvoa7npgJHT3k8+ernPiPk\nlb23xWsXu9wyncRjqVaInu37rHaDhVLPYpm0wH2ORDN5VR4tiAhA43Rt4CUI\nVA4DuVDhoI6eghZXn5CgVWSxVfS+TIs2AXBKZZ9xUsuOsqpTU5EI8tTAEED7\nUA4upp+uPIuBkSHZJuSRbD+t6Tu6JdijUL/CTV4AfQXYc13IqSD1+BvYw0Lz\n8QU4jLnoH4aoq2kl4h07bALeNFf60WpzgxVUPgLazQFGtceeKTdho8wDUeyT\nVaRKOJ90CriOM8opw4wVjbM3vFbo/BQBLhOzKqTdaeyOIakPMagqPidd8j3K\n2yEQhya4PIE22mhKURQ7QyOagUxuNwROQNtdYbwX8gS/zi22T4CDjjhEbTnn\nKz52xcjAYhLoA1c2U1zlT46mTrnNgdn65PNlG0W63c7ukO/LhbRNroyT9XfJ\nCdeHAOPgFmodnDYmUnEdrRjXxZbD9rNAUaN4UWEbcjC8eCmNg8cXT2Eeh3tB\nEl3D\r\n=5R+G\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4d5c41febc9bf1843453f2a4e8dafcd167460cbf","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.27","@balena/jellyfish-logger":"^2.1.78","@balena/jellyfish-metrics":"^1.0.227","@balena/jellyfish-environment":"^4.1.20"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.27.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.8.2","eslint-plugin-import":"^2.23.3","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.154","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.137_1621916592399_0.3107198644303626","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.138-renovate-major-external-major-22cf2fe7b49b78b56c142c0602cd2a9a7cba34b7":{"name":"@balena/jellyfish-core","version":"2.13.138-renovate-major-external-major-22cf2fe7b49b78b56c142c0602cd2a9a7cba34b7","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.138-renovate-major-external-major-22cf2fe7b49b78b56c142c0602cd2a9a7cba34b7","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b1ca8c08dd6e2a9ffcc084a8e189ceba0209b41c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.138-renovate-major-external-major-22cf2fe7b49b78b56c142c0602cd2a9a7cba34b7.tgz","fileCount":99,"integrity":"sha512-QPVlTP0xyBk61btrRjq2gr6rhTTZOtOKxM/SW1vbfn/ut/hOE+9qPehkvvxCZJwKbwFj7ExpZGGSJEljEVq++g==","signatures":[{"sig":"MEQCIDGjOuZ06s/mKexAqf9lybgkIa/52mXq+2Xd4x4YayWzAiANMrnI1KRqHY7uc6NkJa8bjXwQJVg2WztrYPVQ6KHolg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":758512,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgrHygCRA9TVsSAnZWagAAwyQP/2Ce2OMwWliHYQ4PnulC\n2MSWgF/RR/+QVEu2MwTVnLawq71ahlpNscABVe7VNH38VjUFnF+m/FkDWOa6\naHuqfEdmmH6TXoWx6Vff2mFb8avPaxr+VTzsq0LEAmzwsgjvoHoo8gHB64Aq\njScWCYNPeAji4PYMA/hnibE+TbaiZ3QzQK4lkl0Myj60vaLdLWLv7u5llhNj\ns99oeYc6VNKtNjMrBGYzsW/JEc3tWvedC6kbiSG1i1AXmWRIB3XK0YpXLF9M\nmobLDVzWdjnx0x3gay9tw3b5LKycLBT1wsOnuGljd3Qvwvq/HL50UgKb2EVl\nCw+1kEMGzc3zYR5gUGt/p+PurMuVUYxNhxNvjP9QEEo6+hOsBJXZlpWZSLUX\njO3pRgmqj+uZ4L5Gj84TCSCPwXP5wI9Dprhps1S3+miabeewxB/n8AOtL9IR\nzEgLztrZAnxOQC46NRJFuSYGQz9W65HwrfujZ7dHWatNa8uR0wa4anjO2QvK\nl2kbINicFR4N8iWET7TGdBXZpZyv3jQjIucBojr/Es0YQwpzXYyPizmaLj3K\n3jRey+xtYsCmTk4KMaBmaFecV3H+evW419naXqyQ83RVeJCOwQMe0ylXv6IP\nV0MPrNjAh4zupo4gLHgoCI5YSZTG0+2Bse4z8HS2pdYPAkuSN4/hcPlutbw6\nB5SU\r\n=HsnO\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"22cf2fe7b49b78b56c142c0602cd2a9a7cba34b7","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.27","@balena/jellyfish-logger":"^2.1.78","@balena/jellyfish-metrics":"^1.0.227","@balena/jellyfish-environment":"^4.1.20"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.27.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^35.0.0","eslint-plugin-import":"^2.23.3","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.154","@json-schema-org/tests":"^2.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.138-renovate-major-external-major-22cf2fe7b49b78b56c142c0602cd2a9a7cba34b7_1621916831803_0.7686881350178134","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.138-renovate-internal-patch-db5ace0e19e3646e80bb2328e5032be307e87ff7":{"name":"@balena/jellyfish-core","version":"2.13.138-renovate-internal-patch-db5ace0e19e3646e80bb2328e5032be307e87ff7","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.138-renovate-internal-patch-db5ace0e19e3646e80bb2328e5032be307e87ff7","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"df15bb4c68a5f9a367aeea8ca00c8be6605fe4af","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.138-renovate-internal-patch-db5ace0e19e3646e80bb2328e5032be307e87ff7.tgz","fileCount":99,"integrity":"sha512-DgTKSOzekVi/XsPVxP6CAWCH4lfoLwam3TlJ3bn3QTqdvIM5UCJkLiD4z2Eq2BHYoIVyAj4Kt0HoyI6z2qsFsg==","signatures":[{"sig":"MEYCIQCqNBAqnxiRRkmB9d4aFL0s/d3XDtKACio/YgewTB7xRgIhALfYCb5U6K+8D/C+U/xc5ZolWbTVBQp1cm0dqWkluwM9","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":758574,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgrKkcCRA9TVsSAnZWagAAbCwP/Rn1d1YNG7eaxQpbaE7I\nOUQiDt78IyYurNxkyYrP/ikptXysOau2oL1fo/P/WuaHd4Zd5prsp9etpoeG\nP+gorZF3UL5MjvKe/xMwaXa5PyC75MQC9Bi4ir65ogP4prc990ism3ISZe11\nbNS/r+14SN1qXiscRmF1kYuCZEApPwH0jlUoeDJxTUclG6kL5vhZb5vkDhcg\nkKp7VnrK1RPjp2AbceoULUxGphaH02wI2eDvDGlW5yDgCEps9FB+QJNGPPKl\nzF4RyHRFN4T1Ypjd5koIs/Cs7kIGsLX0fz94KGUI/SfFMcErfSrax8MI8Pzb\npCZYi2PQkJUufRfWS0gwlv5B0+49x7I7Erqr4xHiAvpEsdyCXsfM+eo3Lt6k\npg6vlWZ/yL7+mifystNqD6sO8QV/PCpbsrgJdf7FKISai0wj8Xl260KD2Our\nS/epmvSH6zf/3AvZ9p1RNHIYPMvSwmnMWK9OV9Kr6weF8AzteT9IWUtSbhKj\nf4+8dtC+NUUVHBBUJcgTooKZodLaKPrXoh6IbxI8Ae+luiK3sNLn6KLn1Mu5\n/U+dzz7LDEGTvAet2kXuCt+3GGuOHk9dqHP/VuXvlCcCGbh4xrRkVom5pqts\nTu03Dsm7KFjNuCzsG3/MzJeThk6uOMpA7bif946ZPwJA28HF0nc6b42ImyYb\nTBWh\r\n=UTPE\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"db5ace0e19e3646e80bb2328e5032be307e87ff7","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.27","@balena/jellyfish-logger":"^2.1.78","@balena/jellyfish-metrics":"^1.0.228","@balena/jellyfish-environment":"^4.1.20"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.27.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.8.2","eslint-plugin-import":"^2.23.3","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.154","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.138-renovate-internal-patch-db5ace0e19e3646e80bb2328e5032be307e87ff7_1621928219658_0.9113812693674859","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.138":{"name":"@balena/jellyfish-core","version":"2.13.138","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.138","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d3b6bf32b78b1a3813aa682c29f1950353e3d40e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.138.tgz","fileCount":99,"integrity":"sha512-a2fjjavmMzFWpmTJJrpMfELTmZa7zJNwiI5u8kO4/EYa1CN6GqJwtNsWe1YCI8Fe+JFSFDV63XQ8dGG9nwCiRg==","signatures":[{"sig":"MEYCIQDv4obsIGVCGjL4BgT/an/6055jj78EkzrBzVR1ju2CagIhAPPf6mWVa9PLENRX2Q5rZu7DPnSn7mYypFSfuQ6B2w8/","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":758509,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgrKqRCRA9TVsSAnZWagAAW2kP/AltzGjD8bnSmhYRSQpt\nVu/mEigPm5s4OfTjS1B2wGxfbKJjBE8lbn3pqCQ3/3JGZgQzmBnZURlQSAwR\ncYNiWB6fzPcPUU5RqA/vTQebWIPzKmzc6EM9NG7iLx1w0njTpvcs28KndooO\nd2KuRBh1yByDIelOk1cH0elZCQ1qLQzbWJ97ZyScj1AIdsY1gKGfJNn9ItIc\ndvqtk4ZIKrwoJmRQbNsDGf1/CwPfOF2Ov0Zsa9bDBUOyg88wB3ztOuhOb4J1\nGiE6dKUMzM5ggXTNLL649B9NrjxNUZhwElb32rk1ZZt7HFs0RiEsg5oTM1m3\nV3jnNjOV90F2F+sDZdJXKG0lOmIAWQ6IMJetx5DFJVQPXED3bg8q9KVg24Zl\nd1f5o4Rd//LZ5uIRnhv/OFGWgA01J3XRsmQRH9n2uUsklr3+mgkWRYNzl86N\np+BJG0HZcaeyG7h1S8onT7d7KyGPIf7ud7FrylMN3edCxEdX3wNj8EgnvyOJ\nQFiha6FW7CFTXv+ugUDTX5P3hndrchIhPx1tP9dnYKC43Y6DQbAnchuUykZy\nih2LkuLR0pMOog96Ksiy+WurzG6qcaQ6HH05LRKfmgo/JYj6uFD/rRoo/YvO\n4VV8HXZAvGU0CBmLoDil2Hh8svBQ/NWCLHn9+u1J/ZpPo6gxxunKiqlTqTQJ\nydWP\r\n=NYZH\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2d9a409ce7c25afc55870a7445598730ad12dd0b","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.27","@balena/jellyfish-logger":"^2.1.78","@balena/jellyfish-metrics":"^1.0.228","@balena/jellyfish-environment":"^4.1.20"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.27.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^34.8.2","eslint-plugin-import":"^2.23.3","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.154","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.138_1621928592965_0.5423814370063538","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.139-renovate-major-external-major-da41a3e495c53f901821e6f8d8d37aab04196610":{"name":"@balena/jellyfish-core","version":"2.13.139-renovate-major-external-major-da41a3e495c53f901821e6f8d8d37aab04196610","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.139-renovate-major-external-major-da41a3e495c53f901821e6f8d8d37aab04196610","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"bac5ad1b582f651138063cbd1dbfd0dc93eeb247","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.139-renovate-major-external-major-da41a3e495c53f901821e6f8d8d37aab04196610.tgz","fileCount":99,"integrity":"sha512-IgBhloM1iOHgLe7RDORkeU92GCxGgZ4ZDIC35U08AIwXW0BcZCy6+DmPSA5gkFquedC1vYCkejn43bPSW/F+mg==","signatures":[{"sig":"MEYCIQDpjCvvP5ZSw/fIW6YzijRK3WWNkaNSfUvqUOF8LUcdFAIhAOhu2cVInER9C2+j8wLzcreGMGgjljvWLRRhMZN2gB26","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":758943,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgrKuDCRA9TVsSAnZWagAAbucP/2Z2LBNLa5Wy9zcdZkLA\nbG5bImGdjF0cv7FI9uVDB6ljlhsfDz12q+IAXst4J0295PUSWnxX0xmVk6xj\nCz/m4HJ33CSBB9/jjnjYKAQRdxdCc9ZJ9uc06PXSvuPsrPtzdEThhmcVwrvm\nQLG4oPyV5aKyCv2sowpCf4k/9TupxtRM9iStbu6gFnMQAmqz1zDyOxl56mAD\nry3APvEyh5DvSk8DG8uoIXfn2CbQtcjBIp0ESuauydUEZW36BtWZJyBO2G/f\nzRbFZ1UVhsz6etiICQfL1vElzyQIljiQbgYKoV/g6DGTyl+abO8RglLee4tx\nQ22be5zAO8my8QVJulfF14r4c3pI84GGAhtrCxYsiuzGkGOLdJ27/5CVD6Aw\naFyqduS0DMbUJlWGgrR6Pc5W5Ts0lHDK/yrmb85Gcjtw8wbxI2bV7WPYq6m0\n0bDEdHUL2r9SuZv5d4vLQfVipegxckawlSHybJRbCrcKEPFdTC9Nq/eg1YMn\nhuSQn9HQwVmPJIz27ATLwUeJIq3r3/d9LVMfMCes5dBUEvBoQPaSFb4GT7iv\nG1eJ2hHxuDgm0we1Zqp8Rf4BnuX0Gk3Im9xdwECeBtjO0VbTKzuJuOAdnMLR\nIrTnUCbWvr3EZPXi5H7+oBdPmbx4ks2QfwU7cOlJFJ0ILqDT+D/VTObw8NaF\ninE0\r\n=yBW7\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"da41a3e495c53f901821e6f8d8d37aab04196610","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.27","@balena/jellyfish-logger":"^2.1.78","@balena/jellyfish-metrics":"^1.0.228","@balena/jellyfish-environment":"^4.1.20"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.27.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^35.0.0","eslint-plugin-import":"^2.23.3","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.154","@json-schema-org/tests":"^2.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.139-renovate-major-external-major-da41a3e495c53f901821e6f8d8d37aab04196610_1621928834260_0.19878746042794138","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.14.0-typescript-conversion-db3b86f4818ce3e19869c21b666d294c8483de51":{"name":"@balena/jellyfish-core","version":"2.14.0-typescript-conversion-db3b86f4818ce3e19869c21b666d294c8483de51","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.14.0-typescript-conversion-db3b86f4818ce3e19869c21b666d294c8483de51","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"11b653558b5886387cf1f8812ca38c96437b8f63","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.14.0-typescript-conversion-db3b86f4818ce3e19869c21b666d294c8483de51.tgz","fileCount":190,"integrity":"sha512-r15Ow02+dBxgQUiuRQb7KGcU7NqkVOK/f162VdiPPRBduG90YzgoPSt7Es+zTWcHBbJGJRkCwlJV2ZNU16GHlQ==","signatures":[{"sig":"MEUCIE3NW9vlGoRlwWMxrW6sRiExwpvJQ1vAwRRC8V0N61HnAiEA49G2G0yQAWJ30c7v4xhbO/wPuW9Aru5kR2uIlKi3AQo=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":717611,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgrLR1CRA9TVsSAnZWagAA2DsP/0SVYKtfps+lahSB8kRu\n6SsPCjDXP3fsVOmk1pEzc45BfRpU+BY/ZGqVOxRechEEw7x1/AsovEBo2yFG\nUbE2TtO8DY+or8WlDXDraWKBV6anzyHYIM2KtHWlFLHUqGna3uDGohlWRtSy\ndACpFuXUpGxmrAERhg6Mndm+lwawaC4Z95ywT7LAohh3CwK3O7oyZn4bmlE6\nunQNYVFUWu4qwJf0FUYS0czGL/xsENwQ8ckkwiDDffEWeisxXpLetNW2gw2f\n1+WMwdsjYKyUMfL3owmI38oNyYAwbg8tVv9AN09KKgV5rSY1Twm7DHkwzE28\nm/ngEcXhiWdz0+HQ82HiHEm4sYEdvMOVo5gnQbnBput9F2inruvR9DgvOgmP\nc9wRtU8Kd3AtTglNM5jz9qIaNsubslZRzyj89fQ4+ayL/axl9z0ZNbuYdDUW\nwb1Em8USY6xOri/0J4AWqHVrulXC3y4Ur0kktOuv6sMHuR0aD08AUG3S138Q\nMAwS7wetUwadUu6og7SqkVHaGEHJ8aNuBcZpJHaNZjLw5uiIEkdVw/dsu+cj\nYrMMp+YdCuVg8/R28Cp7HjWhDCRxZwkKLTfLuPsdAdX4K0jYzhE0BE9iqhAf\nAGzBpVFskdt+op/xBtUMoJpIioTHUGt+t+ol6kWPVXaJv5I/aPteiPClEIb2\n8dNp\r\n=4ivX\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.yml up --build\n```\n\nThe tests can then be run from your host with:\n\n```\nLOGLEVEL=warn POSTGRES_USER=docker POSTGRES_PASSWORD=docker make test-integration\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"db3b86f4818ce3e19869c21b666d294c8483de51","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint --typescript lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"catch-uncommitted --skip-node-versionbot-changes && npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --typescript --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.34","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.27","@balena/jellyfish-logger":"^2.1.78","@balena/jellyfish-metrics":"^1.0.228","@balena/jellyfish-environment":"^4.1.20"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.5","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.2.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^5.4.2","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.154","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.99"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.14.0-typescript-conversion-db3b86f4818ce3e19869c21b666d294c8483de51_1621931124899_0.40364014409914173","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.14.0-typescript-conversion-ede35f4b3c167ae88f3bf254a3756098c131679a":{"name":"@balena/jellyfish-core","version":"2.14.0-typescript-conversion-ede35f4b3c167ae88f3bf254a3756098c131679a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.14.0-typescript-conversion-ede35f4b3c167ae88f3bf254a3756098c131679a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ad8422d811a53c9551e84a46f6683e89429430cf","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.14.0-typescript-conversion-ede35f4b3c167ae88f3bf254a3756098c131679a.tgz","fileCount":190,"integrity":"sha512-Susa+yY4qaUNoLWAfmol8E266Ce6xjtYUfO5BNZ33XOf9UmoionqrwXr2tCnMBXv55n0i4hqzG9JIx9j3Fgy0g==","signatures":[{"sig":"MEUCIFDgxNC6k7/OtPHLA5et1hUS0Ndqt9/RnXW/kYnYHizjAiEA8JxmD7XV+vd2gsC10lRVghuzyefO/d8rD+7J5JbdVhI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":717611,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgrL3PCRA9TVsSAnZWagAA9q4P/3WcjTfY1f6FtCwFdivZ\nCl3Q19WibM4wSN8wZKXq32FNiEvG+z0sWRidxQObbHkeftKR6yoYPb1nQ37h\nSrYf8eBsk/pHz4Vb84RyRKDSFJXss9sa0kFdcQr+4HzpwcszB2nBkmxiXpnF\nFEwGwcnLlbTEkammcMmBORtQOFKSmHZl5vI6r8q0jFMAZCnbDQtEFsAKIfdZ\nVpcWTX4NiLHy0enanvbFP2U8ze5A6Qt8wj+DU/pXyzQ1qcMLJplo0BWCLvN4\nA2hnxf+bltW8MYgDMiysZl0zztKyRJ8m7X+47NygmYrzJWOTdznMO1CWhj6N\njj9gWyVhGsYrOB168Xrwyv50F31JYPNjZd/ONmJslo767rEkl1fD1pA8Zuq0\nDXu/UDBPuQVewgGLARI7Bwqmz3voNATaH99ziXLgMuVzMz1Z/xR6khvhwNtc\nUlwWYfqGY2pysjUN99ppYhM/wllcVKbW6k8yHcZsABbAXXGj5z13LhpWpaPJ\nh/H/9j3pWLS6zbG+m0bwkxn7+4iW/CHF2MiW+r40c2+fI8M6xnCYqZDu1Ac+\nEXUe3iL4+QeAgYQ3xPk5ms8gMW1vhmNq7tDEhx0+HiuK07PMKwOCVhyCLad8\nwyeDMtW2MKu2O9JsxnHloZqCLenQPk5wiCyNc6jJKWDv6JyNSVop5SOtn4IN\nDI6v\r\n=g1Al\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.yml up --build\n```\n\nThe tests can then be run from your host with:\n\n```\nLOGLEVEL=warn POSTGRES_USER=docker POSTGRES_PASSWORD=docker make test-integration\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"ede35f4b3c167ae88f3bf254a3756098c131679a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint --typescript lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"catch-uncommitted --skip-node-versionbot-changes && npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --typescript --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.34","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.27","@balena/jellyfish-logger":"^2.1.78","@balena/jellyfish-metrics":"^1.0.228","@balena/jellyfish-environment":"^4.1.20"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.5","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.2.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^5.4.2","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.154","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.99"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.14.0-typescript-conversion-ede35f4b3c167ae88f3bf254a3756098c131679a_1621933519050_0.5691003455232166","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.139-bump-eslint-plugin-jsdoc-60ae652f43313ea08d09f5df5c1aef211d916a32":{"name":"@balena/jellyfish-core","version":"2.13.139-bump-eslint-plugin-jsdoc-60ae652f43313ea08d09f5df5c1aef211d916a32","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.139-bump-eslint-plugin-jsdoc-60ae652f43313ea08d09f5df5c1aef211d916a32","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1a5f69d28bebcd62912ba1ebab877a8b8d12ccb4","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.139-bump-eslint-plugin-jsdoc-60ae652f43313ea08d09f5df5c1aef211d916a32.tgz","fileCount":99,"integrity":"sha512-ZiuhoamfVdYrlCAyxI+JMRlGdejdCO5t/w13G68ujt2yiJCMinxyPm3H5pZGVQhZwSwJ5gI1wH4WgbKM7Wx79A==","signatures":[{"sig":"MEYCIQCB6ZA6AiEygdYXFFUE8X9O6V698y0tPQuQFf+55C3imQIhAIelizllN4w+Pbpw0FvLKnJVIqMTnJ/XB4SdxUHza4sK","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":759054,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgrad+CRA9TVsSAnZWagAAS+MP/0CUPilbF6bNA2tuo0OZ\nkUxXxMF51F0s/GWJioTQ8SPPBGj1aWHi+y4N1LrMkqnNBBqgNnGZP2nRSrB+\nelhim+xXdXarLx7auJKBboKq77mhDT2htihUr4xVPptvev//m2UQKkkiH5tw\nlmWj6k2aKYLEJhRbJloOOe+mg6aUKTU7pjRf6QR2EFJ8oPSW0vHRk5jdi4UZ\nykq9ofcFwq8airKfZGjUOu70CgmIdsLpOMqRmrdMYr17rJJVBRByq3HgnUEd\nOLqFa/+d0wXvAwaOmZ0fLAilMjEDof6lrU/Ahtu0p0QahLSnbdrOwYG2as3W\nABcxOdFGYxI9p0nY68b1zcjNUPvWG4+P2ZnkzlWRv8c32hnPH1Rq3Wqhcz2v\nmyc3h2Cvjsnro9kA6sddetGY04MFVG1JMKSP/shTNNZS2UPAytsiGl1J+ovF\nZTdNMGfzCr3qOeBsT0hVTU0huUMqO02hk8Qt/2fZvpTgtHEyBbMwWixAvq+m\nMi6XFZjxEZtLr693AIxFxsAEFO8UhEz5cgUE4iAAGcDMwQ15lQfoOnmDd/Y2\nopghSTnEiUD4Trd4ljiWo7ah4bUPLCFB2+7x8+9uNlrCz5X74DZDrU/RDFBE\nFHTjfc4eBcC+ZG9PdabaulI5Ad6RMTM72D43TCdNQy321zMu52q5/wRghj/x\nk3Qy\r\n=e+wy\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"60ae652f43313ea08d09f5df5c1aef211d916a32","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.27","@balena/jellyfish-logger":"^2.1.78","@balena/jellyfish-metrics":"^1.0.228","@balena/jellyfish-environment":"^4.1.20"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.27.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^35.0.0","eslint-plugin-import":"^2.23.3","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.154","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.139-bump-eslint-plugin-jsdoc-60ae652f43313ea08d09f5df5c1aef211d916a32_1621993340690_0.1299109370143816","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.139":{"name":"@balena/jellyfish-core","version":"2.13.139","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.139","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"95e5e20cec417dbd2f3c360684acb6eebf33edfc","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.139.tgz","fileCount":99,"integrity":"sha512-6iqWsXivuDFF70fntX5wAfqu+SuhP8i5NInuHAJHzHY6oL5cIJb2WNJM/AfuvZAzCMgevsXCcM8cVRvZOi+6Kg==","signatures":[{"sig":"MEUCIQDE2CCDJ09ueLnNC2xrDneJ0TmE22B3OpWKFArfxdnhkgIgM/R8gEj6vGRAk0DVUPxpgqLDchSUbUflN0v/TEMb2jM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":758988,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgrajwCRA9TVsSAnZWagAACg0QAKGFLhUDUx5BRYtRfMZN\nWuGndP89Z6VmAx4dtglUQ2UEdEZ48hRFFQf3jiM/LlG2ZqGY5hpGl4ESjz7c\n7AZE6tSybf+93tFBMl1Pw7zCo1tWDeFg5R5hul4YbcWAShL++cVcjPLA0fM/\nEVYmGygSCsDDjy0bXJIUq/Uyka4lL9kAEjfMwlfozMYYP751Q7mbAFOQOn4m\nmz9N9Ho56p0lOAcQqa+ITirbpPr3gGcyXtgj/DoPrxOmgOcKJQm8xsblxA8E\n61dwqgZW4YMXUHbiqvmXnsEvG9OXH+xszFB8951X5Y97zCOe9Kj91XtybO85\nktXbH4Ldmh1UQknJo7w/PmTxJ2hLm61svn4ZvNJxvc2aasB5XS+FKd3eI9Pt\ngptn9Uc6lfGozGtdfBKZkB71Db9tCiPu3hJzWKlelaDMxL1wqf9xjgdynygi\nXAU7VBPtsKh2IIaQUUGuAsbSYVHVD8L5TPJ1LWNwX1Zfphh9iiQoVWkF2pqt\nnKqxfPKud9/ABejQqoA8Z0CVTB1URsS9S+hZJ631iWYhg/UOCBljWXA4wa3n\nQZvBYcizhgeb76A36UQkiBwhPTnI6DKLYSowzW8KbuouRZyQVCPkkSEUI/Sz\nHlAya0fx+vx8U9M2cA+IiY6WyouJX9ygf/GT5tWU6/QOQlVbqvp4TUzNxQvJ\nCDCs\r\n=1Q7S\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f902e901580b7ba5eae971b854aafa69b3023898","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.27","@balena/jellyfish-logger":"^2.1.78","@balena/jellyfish-metrics":"^1.0.228","@balena/jellyfish-environment":"^4.1.20"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.27.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^35.0.0","eslint-plugin-import":"^2.23.3","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.154","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.139_1621993711734_0.6850969221751335","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.140-renovate-internal-patch-04cfbc18c6d1089029f040ef23e919a412ec0827":{"name":"@balena/jellyfish-core","version":"2.13.140-renovate-internal-patch-04cfbc18c6d1089029f040ef23e919a412ec0827","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.140-renovate-internal-patch-04cfbc18c6d1089029f040ef23e919a412ec0827","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2c5583a84886f1b8a74c5c8af4cd27bc4445a3ec","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.140-renovate-internal-patch-04cfbc18c6d1089029f040ef23e919a412ec0827.tgz","fileCount":99,"integrity":"sha512-CbTstcF78ofm8mA0sLySDGrUdRgvefj8MK+WDiHlxUvEhsnPgvkP0Sg9yo6auHOekPQ6CpF3yI4MxaoR0bHB3A==","signatures":[{"sig":"MEQCIDwmUdOd4FnON8gLxrfad96rmuIL2eGPEaLN/PPmIFA1AiAZh4UJg6XDwCe2WYhs4IcukAlaSMNj5oSSWI0/4SoFiw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":759480,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgran5CRA9TVsSAnZWagAACnEP+wZh3dQsrW6t7cKQEFlu\noS5ZH1b74CsduSX5bTn/+lG3TlHKPdn2/ab9m1e3BmMlkmtlvgCc7Lobp+82\nydIaKVkxA4AUxT/Uh3HPTsNOuhDvRUIDenk9XSpLWLFGotgzqOhkc7IFV/SM\nFVfmreQuYc/KWIO3/WhtmJt60DnajQ5eIFbQDQlZFpPrbv1nJN86eNDASH1K\nA25qBArr4vuYI/qsJPtSdnCi+/a/WTfvw+pZ+S6rSK7CzNEGSLI6NRoCYtSL\neGTWNYVPOYlLb/MLfbu5vtVxHwP0omhrtinhv5VDuA05YzPKcLchcJ/yzG/D\nUrCP3uxzK/ITCMiZMO+ce3yKXJDO6EpXPUv9bl5DgihpfusRMbB0hceHQ6Xp\n7V7YMlf8BAxnjbUiyi8RVNkHvFAjuUj6+Pnz7ogB1JugUB3iYunIsHrlCOdZ\npt10tZDoQALYbRKwAmNCgsa1/X9OLfHPU3+lwijSoBw62E8yrpgAtP9jO0/3\n1Xikz6Yqp4EwX7iFGuEFSf8frKbE0Ym1WwAV9fHl7Epq0bym6L43+F4oPU7B\nD3H/pvl7ba1i+8buKiwQUnoJvEbfhr+sStH5+hj4XeUeVDC0WnXdtidiV49X\n3jWY9NpJ9/V+CXwNjMeZbxwp7v0rx6pGi5QPQOjV0eTi0x2EATuAEYq3y4FL\n+HhO\r\n=5Oku\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"04cfbc18c6d1089029f040ef23e919a412ec0827","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.27","@balena/jellyfish-logger":"^2.1.79","@balena/jellyfish-metrics":"^1.0.228","@balena/jellyfish-environment":"^4.1.20"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.27.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^35.0.0","eslint-plugin-import":"^2.23.3","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.154","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.140-renovate-internal-patch-04cfbc18c6d1089029f040ef23e919a412ec0827_1621993976843_0.6798596350901218","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.140-renovate-major-external-major-2857686ceef248075de97850a0e52db2fbe3fb3a":{"name":"@balena/jellyfish-core","version":"2.13.140-renovate-major-external-major-2857686ceef248075de97850a0e52db2fbe3fb3a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.140-renovate-major-external-major-2857686ceef248075de97850a0e52db2fbe3fb3a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"271dfe64e95acb38f7413b803a190ed5dc03a4e7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.140-renovate-major-external-major-2857686ceef248075de97850a0e52db2fbe3fb3a.tgz","fileCount":99,"integrity":"sha512-cpwgIGqLozCNdUBmKxwq3XVQS4QDSeFGsgFQjSr76pfQHPySq9dGOCdC3VCiwLC0pKoMZG9/GpdMf8d9qCD0KA==","signatures":[{"sig":"MEUCIQDrnU1KsO0xI+Yjej2O2Lo/F7/4iPWmuJ6EBkLaSTdqhQIgWU8n33ZKOwmGz7z5m4fmSRc0CQclr5/Rw8AH2re6rp4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":759472,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgraqwCRA9TVsSAnZWagAAoXMP/jaa+F6gu/YuCd2ggxMM\nck2lw0lK+2POcTkwZFjobOezvj8QelU9T3I7OaIDkNrQVoi2JfJmPtgJqqRo\nWzCJRodS63FDb9FherHbTVcbKsGG/tZjqkx+3JaiICa87ztxy4RuQvezVVb3\nCAK8asqV/2pPQmWUt5TpqSRfUwRhQ3R0Thwa5j0ooEfW0IChtGW1YQki0IlA\nQuuicnh0uyjCX6Y/uCFMaQvM73UEH8JZryS0BVMLkEku7xA7GZ5EUtRQ6++W\n+NCVpp0FPzmjdSbo884koFy9pBIQUK+IGzoWC/v+S7wuMYNsu3nY+Df89jq5\ni+gEp7I/BCHhHSlJl4+qnry8g1ZW5ark07zXB4UCvndReOVT33+ni0Ztxasm\n9a0MEqsSu8KmPMpenHYTd14TtVbHfeGFcOlA4MGmQEWp1Tg3bPzxh3X9lorp\ng9aOvZUCn0GOQIPfsqrXvlSc1r5wfnemx82p2SOcdBDUXGX2rA3t/UraYPaC\nzUSaE4pYAn7P1hV51twI4WLp7ENPtoW8TFIGZmzeGqBFlyHEoimPGefqSHdv\ns9APJJ1IdGV8MYN+5u5gwnILaSheMDciSiCT3xOzZAKO1ks1usY4M2yCb1RB\njZ+njt9Xbk81Cdejv3oK4MPK4k+en5bx60aVtm0B4VrRBA4Vsk0M3giMHA7E\nqa9U\r\n=NG5r\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2857686ceef248075de97850a0e52db2fbe3fb3a","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.27","@balena/jellyfish-logger":"^2.1.78","@balena/jellyfish-metrics":"^1.0.228","@balena/jellyfish-environment":"^4.1.20"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.27.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^35.0.0","eslint-plugin-import":"^2.23.3","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.154","@json-schema-org/tests":"^2.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.140-renovate-major-external-major-2857686ceef248075de97850a0e52db2fbe3fb3a_1621994160167_0.4639307232092216","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.140":{"name":"@balena/jellyfish-core","version":"2.13.140","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.140","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5a1069ad4b6572c9482899beffab0a0c40dac1bd","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.140.tgz","fileCount":99,"integrity":"sha512-G/wJb0sSYPlq3RXE8r2gN+vOR8Fx+QFW1vBiTBhsp89ha0pco2K78wtIq9Qv1AUrDmkIBp9bh4NbUgrRS9bp1g==","signatures":[{"sig":"MEUCIQD+wrXnp3mcREhmMnR1gRdV7gkhzKYdWLVSdaV6ldBcAwIgGiC7ASRJZeGYZLUp0IjTDrJQyQsSoMOBpgt6qMfUqM0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":759415,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgravLCRA9TVsSAnZWagAAmOcP/2L/cX/BzcPhCrrYBcaA\naDSi/kwte+gjl9hmtE4caKGR2mhPX/+9SN0dUkKH5zlmOKB7K/yTIVzU4ZZM\noITVb+xtoZ9s4EyBTKug/RxZx6a/1rwUO24A1YWyveKNNKWAXz6Ulq6qYtqx\nl4H6d9TdjmGTLRsM7rmE54Z6rM1RgmumsBGpbP0OzPpSAHWmSn+sbyU6/K4T\n0nDPLbi+lEnRHm7DqXKTeVnsS6s9R7nG6clzUtLjHat0WdKVWTGDOycBBweg\nfoqrqBFmQc5zdP9NQo1h0JBbBC8K4l1weGLpcUZYydqjSHf6piQ5W0iK20UI\nFX34GjBYhAtb/rdHQKA/RO+8dE3bkRdWLlL4T6CB9np4IHwj/FCN0pCtHkio\n63IE4o9d37dWWNMvAt9l/8FUmKXgTp00GgCzLEcNu/DWpBp6Pst/COaYFmFL\n1jGPQv+bOvs9055AZ1muRQkiF1tP96sGNGXFI2e6O8LOkG2fYm6FuG8kXe6x\niRmspM4OkDS9hr2hNVjrD3V8b3gilpMG67nmGqtmeUV4nmYOyoYDQ7LBxhIa\ncCbTMr8kr8fAfJk6HM4BljOtdqsCRu/6Iy5WnQKeSrQr5ZFHO8kqPhQr/G3C\nM7YmW/hjJ8jNt64qwDY7J8PBs9hMSOj9ykvEmnqT7PiLkzgi6LenVjoctLjM\nM0EX\r\n=uqo3\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"672419c00e3ecbef5d49584ecb286861c3fc886b","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.27","@balena/jellyfish-logger":"^2.1.79","@balena/jellyfish-metrics":"^1.0.228","@balena/jellyfish-environment":"^4.1.20"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.27.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^35.0.0","eslint-plugin-import":"^2.23.3","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.154","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.140_1621994442603_0.40977249771048374","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.141-renovate-major-external-major-a1ae72fcc75a67a4ef059cebc29f6fd33c0d750f":{"name":"@balena/jellyfish-core","version":"2.13.141-renovate-major-external-major-a1ae72fcc75a67a4ef059cebc29f6fd33c0d750f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.141-renovate-major-external-major-a1ae72fcc75a67a4ef059cebc29f6fd33c0d750f","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3904f538f82291372481c3aabd0051fc3797c59d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.141-renovate-major-external-major-a1ae72fcc75a67a4ef059cebc29f6fd33c0d750f.tgz","fileCount":99,"integrity":"sha512-FZlAtjhSNXShKnj0vT58ZvGg0YbBrY69koXHxEKtxWbNfq41TwFf2bfMximGdYjwGng2uhIRYJPnxpUqG6ljvQ==","signatures":[{"sig":"MEQCIFUeLrva5rBWKnkh32EyILyxMHhHxxfQlWD2Jr9hVFZ8AiANtvbN7YMnT0qgO0tlwvZIb9iYPNK9fZOTK8C6l6fsKA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":759899,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgrazVCRA9TVsSAnZWagAARtAP+wSqcUvtxF2CxAQie+Qw\nS0tqKIApmACzkCsPGoFTqEeAJVH1y6nGJD4w3qDY4CnP5fpnH06KC07mnO/i\n83IhK1NEIlJCRmVkv8fZRqFvF5LujDHY9NF2KIdqIhT9KyljtQOJZ5rkKlOX\n8osGVm2Tma6j5HnY3H/X5POTPeeyUzPWia0MM2TseVWwQkrdS1BFXh67P6lB\nWm0uHWByAi/2IMs4+oK5FPOErywAN804JdypbewiXdpIFbR3MYYDen3Yf/e5\nYoTrzCOzWcb6xZl+0gwPX3ZGNhAJr6atK52dKcO+lc76xmZfBX83AU4XVOez\nPl75Ay5CcWVluCMo5PmbCoFX2LV0szB96RH+NJdmNRDbLOcGFOe344tiYnTn\n60t2fiitpAiRVmn+P1OCOaVWldE0QUrcxF/zysPHbzraDi3v61WczGR+kPkl\nIeVB7eRnozo3r2+wF9HLq9Ud0wru2mAiok782XGwEMRLlUle4JHlrnbr3bF8\nYASlEYZJduifd7xtgzIkb1xmJ1iW0aTRwxRhT97MZLfcGsBVX3c07xVEuxQV\nOjgBPVENZSQyf7h/xg82o8sPCZsNrmCab9s3P2cmWx3Li9hg8mPqRv+2uvZR\nJSM5ptqyHKIxKYJPK8QISP6y6k71i+UFiR7C3rDLfYEjJS11uxWujMap9MiX\nu3z9\r\n=9+d8\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a1ae72fcc75a67a4ef059cebc29f6fd33c0d750f","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.27","@balena/jellyfish-logger":"^2.1.79","@balena/jellyfish-metrics":"^1.0.228","@balena/jellyfish-environment":"^4.1.20"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.27.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^35.0.0","eslint-plugin-import":"^2.23.3","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.154","@json-schema-org/tests":"^2.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.141-renovate-major-external-major-a1ae72fcc75a67a4ef059cebc29f6fd33c0d750f_1621994709356_0.653331133521428","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.141-renovate-internal-patch-d122e94d31641e7c43f907e7d7eb638fd20b3c1d":{"name":"@balena/jellyfish-core","version":"2.13.141-renovate-internal-patch-d122e94d31641e7c43f907e7d7eb638fd20b3c1d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.141-renovate-internal-patch-d122e94d31641e7c43f907e7d7eb638fd20b3c1d","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a235e10c41cf3d2536ca0332ddad11c1d27c26ce","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.141-renovate-internal-patch-d122e94d31641e7c43f907e7d7eb638fd20b3c1d.tgz","fileCount":99,"integrity":"sha512-TihKAfTZY9pXwRbF8WGxjTdNSpzQCouXAI90kiWWRgKGyYDm/uz0YUG6kak1b+25c6GRudAzitebE+qXk6vgGg==","signatures":[{"sig":"MEUCIAXytVj9AwjWdSbjD6lbf9tkESpid9Y8fyCUNg7q9nFJAiEAnWqoPoiJ2l4FE8gUN59l3nbYtLx4lpXmKYWETjAWJwA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":759911,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgrbsjCRA9TVsSAnZWagAA/3EQAIYopt2xOjySz9Ssy1x7\npRlu+ZicrIo684f7TJ9KE/FS1IzW3VyvNeuB5sKdS69TRu0rraT2o5PrpJgl\n/8PaRZJXYhBArr6xGKG//l8ORGx3Bjb1UoW5wRuWZBPB5TUcsK/UJlLYAwlC\nG3T2jTnqRGJ7lyFm3LbS8Tz98t4EWbV1cQf2kn8DSqI7prD1kgZg3yHX41C1\n+pCEFQiQOY7tKcBBLPAOK7sbM3alyiwwNMFmusIYe0D0uCjkfzNzwkuyEtEW\nio8znPZfGUVOzuWlydBLsIrew7KHwNxxnHWgLtvWD1ULsMU2IcLeSGVIfQnk\ncD+HgdYV9tZffj+hHP5uVB91lUnsdjFweeeaaiidVz3+1cKbZrGJBQW0iO+7\nWifB/kgtPSgILl13ChhvtACTu+qsif1Bg4hRCU/6gvXQqCc1oCsoUflMks0l\nkFXmNRJsVzcwqSRRb9Mh5l/LLDPr72E2gWyHZ14SEj5dp5fC5LDhNsuM10Fj\nS/XD6NI8VuGMo/LxPNG0a2dtpjxw+oXKYv4wyWwxVg6P7H4QySDTWSejMI5K\nEkn8eN3uS5GQDwQt3VTwnPJt76wEzR3q2xL1k02+JGaJjQ5n2yJz72sJiaA1\nE7J6Xd928adxBxGNfBl4LtaIWlKSYgys1kHHKXl3V5h95FdUQvlh1TcrtXP5\n51ea\r\n=+CLJ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d122e94d31641e7c43f907e7d7eb638fd20b3c1d","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.27","@balena/jellyfish-logger":"^2.1.79","@balena/jellyfish-metrics":"^1.0.229","@balena/jellyfish-environment":"^4.1.20"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.27.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^35.0.0","eslint-plugin-import":"^2.23.3","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.154","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.141-renovate-internal-patch-d122e94d31641e7c43f907e7d7eb638fd20b3c1d_1621998370611_0.35985065968517715","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.141-renovate-major-external-major-28723f8fae65e70c0216b715ece05a955588e081":{"name":"@balena/jellyfish-core","version":"2.13.141-renovate-major-external-major-28723f8fae65e70c0216b715ece05a955588e081","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.141-renovate-major-external-major-28723f8fae65e70c0216b715ece05a955588e081","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5fcecd6373d395c093eb9f10742fd40e3c472f5d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.141-renovate-major-external-major-28723f8fae65e70c0216b715ece05a955588e081.tgz","fileCount":99,"integrity":"sha512-vkUz9rzRUF1RdOuRWYuDpBot5qoMG6idjmU8946ND4f6F5VsEZv8OeTdBImzCbXabaNTFdBUusevXKr3Tos8sg==","signatures":[{"sig":"MEUCIQDkH1PyFT9PdZo3wxXSfrhDxa2FXm5oB+LOZWgMgCG+aAIgaPIRC89wAGRQTJJxiuQ+DznGFsCFubR17tLRe5soZQ0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":760239,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgrb3CCRA9TVsSAnZWagAAhYcP/0IojBCYBRVllftKVVqI\nNU9U4KaMcXptb9BKU4zoVmtppZ+9smTsi1oxfX0X67kMObP61WN89udCGa1e\nF+elt5Adl1qevQki86tZygNzcmiocLGSLOLuIb4aUgeZzbcrFG9qquj/uXB6\nA9ZcUGtm4k2aPNblfx8/6GpJ8R/oQUks+kD35jEaEnEhBPBl43HcAzbHdkPG\nylJU6C6yZtTpnViUuXLrh/gkb4IML/8RbI6uqs3nzcBcIgDxzsJl1AZkCjkM\n/LYS+YlJxNeD0X8dmhByup1kcTalsARByUNLPcyPBD8mj5dJtcIPfojIWbwh\nnTtqdQeXvfC3noDoQ5XtDYNp9qZ56V6QaGc9U+pMA1wjfv17o2C0a0TRTWtH\nbg7Qp5AEi8L++NwAOpDd23VMoPjHiDoNu2ODDEfeOmrDkbtnVC4u00kKfhOF\nWgp5OAW7SAz1DSaRei67zR+9BDCwmQqTFDh1w3YVeiMI05rHD6t7PYfwEdzT\n3C98px07tf6zJog7OANT21WLCispDWxW1jMhGNmc75g1vRK2V2mejZaZdBKC\nhNoifYL3tA2uTYA4EKCd93lkZlv8CfQILFPp6PUKELw4qWIu5mS/QNPNXxcy\n/F2l8+XWOXXHiRWatww5pihOrCS8XvEvbo17Ca4umqsdaoLpP1SPlp0MwwYf\nJcH8\r\n=Xu2g\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"28723f8fae65e70c0216b715ece05a955588e081","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.27","@balena/jellyfish-logger":"^2.1.79","@balena/jellyfish-metrics":"^1.0.229","@balena/jellyfish-environment":"^4.1.20"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.27.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^35.0.0","eslint-plugin-import":"^2.23.3","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.154","@json-schema-org/tests":"^2.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.141-renovate-major-external-major-28723f8fae65e70c0216b715ece05a955588e081_1621999041665_0.8848343898570439","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.141-repo-sync-jellyfish-config-default-8453876f5745c64582f4d128c39cd41c320758ea":{"name":"@balena/jellyfish-core","version":"2.13.141-repo-sync-jellyfish-config-default-8453876f5745c64582f4d128c39cd41c320758ea","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.141-repo-sync-jellyfish-config-default-8453876f5745c64582f4d128c39cd41c320758ea","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"63877a374710c13badb47562900ea2af62c96edf","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.141-repo-sync-jellyfish-config-default-8453876f5745c64582f4d128c39cd41c320758ea.tgz","fileCount":99,"integrity":"sha512-wAmMaAS9x/ztKnyEcUGH9IN/XYDuQ+JjVegZbHDBK20A4zXP2Px4h62+3ZGEiTBnL9Stvna0orRSRA3K9rUBmA==","signatures":[{"sig":"MEQCIHQ79nSGi2hbdwN5aqAxIOswKT+K9k20rFDwTIbYnqqZAiAVQYd6KHY8imA6VghLmM1/ELOvtBvqcFCsGyxpIwKTBw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":760346,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgrvPoCRA9TVsSAnZWagAAUycP/1JWG1RdrJXwqWViFdvz\nz9vkxvh79sJAIXbvCpv4KKw971Uf6r2Hvnk0/8MA26PbCLMqnmuMKo8crpYG\nJavu9uAvjfkO5lFIdki0e7YgiihwhEEvPvX1DdeX0MBl3O8SML1zg6Rf6rbL\nBXUq2mS9urUtVrne8t6ZvD2PNLFKITyeisw2PIbGwpFMwYFDIMDGsCRiQYSK\nBOBQjtEQhIFeeQUM+h7y4A5Mha15A67BxmO99oOW7FEAXsPq6QFaQLSwaW0F\nPolv2xJcVeDB7sOhqrRDUpayJiQgq4g4tmw3B1v2H03fV1oxOTKJ1+8/MALQ\nNpUQiQau9mvx1qw5zUdHnfvwZQQNDGmo3h7ykRFsLlPBMGbcgiuZc9+DRS4k\nGupAA35GzfNiIoXKe7ROPM+GDPF5sgU9bBWyyDLiN3RIR1QEG3WHJHE3S7oB\n5K80ZsK/Rp28zsgbvOcc5Z0qmOI51iRp58eoHpsGUeV6ED2Pj0CxtRoTpY0W\nH/OLuH66etda262eXlggtv7lJfM/wggooMtJ3QeBfQp6of/NVyBoE5CKoXll\noSnJY7C0VPM1u5ZknnsxqLLZuRqhppNxWwu2DKNHK6/laeppFTdtH9rMdkB0\ncEZI7rKJh7N9wuxxNuYns391KrUyeMClpDNqp5Db69m1byvRNlIAW7+a506w\nv/fw\r\n=unEF\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8453876f5745c64582f4d128c39cd41c320758ea","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.27","@balena/jellyfish-logger":"^2.1.79","@balena/jellyfish-metrics":"^1.0.229","@balena/jellyfish-environment":"^4.1.20"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.27.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^35.0.0","eslint-plugin-import":"^2.23.3","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.154","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.141-repo-sync-jellyfish-config-default-8453876f5745c64582f4d128c39cd41c320758ea_1622078439745_0.9856554670741289","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.141-repo-sync-jellyfish-config-default-6d532490eacb30d3b2e8e342bd948bac16387dc0":{"name":"@balena/jellyfish-core","version":"2.13.141-repo-sync-jellyfish-config-default-6d532490eacb30d3b2e8e342bd948bac16387dc0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.141-repo-sync-jellyfish-config-default-6d532490eacb30d3b2e8e342bd948bac16387dc0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9b028977d59c5b2ed63450d8fb499e76fa8668ba","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.141-repo-sync-jellyfish-config-default-6d532490eacb30d3b2e8e342bd948bac16387dc0.tgz","fileCount":99,"integrity":"sha512-UUm2t5BWBoTRwWVl9pBLpwpiXtaTrHIRvWO9iO11rgwNkUI6RizuDntsMHdJDVGgwlf0LA39Ai64NAH4iWJssQ==","signatures":[{"sig":"MEUCIAQMo5soTGhPzoAmTepTYVaUX0LA82jOr8pAmyfmXkm4AiEAlVHIRRj+h7m+Hlo1shC4tGIdMe/tBonXd0YzCIF1VW8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":760346,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgrxDzCRA9TVsSAnZWagAA9qwQAKDzEUBtR9a/eUu83mVK\na/sUgAuC/Yvw2xrkGytTHRfIePYlRussJeBFpUJG53ZX0wb9d5vsGwypyiOs\nnAlllm2hiWdl1uIJz9E3IRAsNwIjcGB3ouqwm0N/e4PBnXnUdGyqBVraFFeI\nXDJ4igCarIYJAeGrQdGaSehqM/c9u2eJEJ1OxMe4HeNlzjbn8MsmFOFMxGbZ\ntm0mfd5qWjzl27w8kUZfVFjQTsElacZJvelNZ/hnxm7iQTk4KmatEt39mqQ1\nBfCEq7LfRSlD7jhX9TiEoMRSWADurwAjhIVI+D1frYtYds7Nri8KqQiQc6ss\nhkUHl3Ig6FKw7TWpZl4F1a7HZtrOL9JbWPXnYAOngJnkAHRfjeLeDEMJnrXd\nymVS8nqooAvSQOegzhvHD0vNcGJDGgNsDA7y1auNu4uyDeTq2xM4dOrlzhmk\nFUIIpqVsKslzNdr8KsfeU0SRWhCCy2AHgt2LLB+Ucb0oJv/DbOvrhZ4Q+Noi\njZ1WzSFsE3RcvmH1ElHgB1FhXReKG1y2Sm02TbrWR+Pu+BewY9GoI1h4XZA1\n6n0cqMf96Z/MoJa7NcPIAJq8w7UYDv3X+yujDj2cJJaKzdE3ncWtBo1br1+1\n/JVatrFdljy7RO05t2JKH7VBE29SzxsQsfnWX9yzfLjDWg5PpI8BMbwUxizh\ni81r\r\n=qfKc\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6d532490eacb30d3b2e8e342bd948bac16387dc0","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.27","@balena/jellyfish-logger":"^2.1.79","@balena/jellyfish-metrics":"^1.0.229","@balena/jellyfish-environment":"^4.1.20"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.27.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^35.0.0","eslint-plugin-import":"^2.23.3","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.154","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.141-repo-sync-jellyfish-config-default-6d532490eacb30d3b2e8e342bd948bac16387dc0_1622085874739_0.3270178002038724","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.141-renovate-internal-patch-db163e8e51d4043f7fe50a47276e4c2cc6e3f256":{"name":"@balena/jellyfish-core","version":"2.13.141-renovate-internal-patch-db163e8e51d4043f7fe50a47276e4c2cc6e3f256","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.141-renovate-internal-patch-db163e8e51d4043f7fe50a47276e4c2cc6e3f256","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f7b5e8e5f19c91ba3f2750ee158ecb69ba02a01f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.141-renovate-internal-patch-db163e8e51d4043f7fe50a47276e4c2cc6e3f256.tgz","fileCount":99,"integrity":"sha512-FCDWMkrvNWH602hV6bRVi5Kk7RZT9a5YdJ9P0eYy6Pvvj80O1M/eMd3IxYt2GQnATBaaVHwMMJSm27FiKaHz7A==","signatures":[{"sig":"MEUCIQD/xkrdg7PtWqV2HR7l0Axl8lFM7HaDpSvSNlesGiOxoQIgeNgMMRZNfFTGS5W2eJze8UiBV3Yoa1MM7IS9XJ4VtPI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":760183,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgry2kCRA9TVsSAnZWagAAFH4P/AsK3B+8AsCNsxTG9MYT\nfM5Ha9KbS26XxzVNqzS+abJ1HjkkBv0lkBk2tvwtzZU2wYoG0OPnlcQN8kEM\nMzNUkHAV2kgTCgTypWEE6kn1BhR+mtbgefwGepCSPNVrcu7Q9XehXVQuS5Zm\nvaRgEUb/NGEL0bM6HH1rQ6W4CM5n5osW6hpPfRG52Hclyrh1M3zw7pK+94dD\n6F5oJMvkORH0O0t31tqNbEMAxy6t9nE6kN/Yfx0ZNjLl0QIQJ/qNXkSS6yLP\nqMmUykBPdmnBwxvOh0ChBmNWYPVqqC/BeSUMz55vY0TOyc0wg4PQnxwiyxlK\nBvXOCPtC64hBmpEXg4Cd2xP7BlvIHO66pVfuLRyOQdfufsAVo027SZe8zJLb\n9bxwOSvjkPih5TvNhdrazc1vdC2nmhk2+zvDeGSWtjSrGyFBxbPq2HDQFFvF\nq2DkGx3j6YWx/XoDyk2zW81GljkJos0Njr6aVpqq/w3eVVfd3Aq/stbYtp9v\nYl5tg3bFmcAjncaOWIQU2/dUNJWwiwokM2dH+k8NNfGqpFro2cih1cMmqZpi\nwm1spcFmKJ+QHwZmmnCXntggFH+DcqfzQ6ydYa56q3MNqz3qV+aUaa4Hn2Bz\n6+CV0XxT+1leegCPCzXa/HV1i3rL4n9KqIPGH+CMyUqa7J8ux5OeBmmJQuKJ\n5ZgC\r\n=PkvG\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"db163e8e51d4043f7fe50a47276e4c2cc6e3f256","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.30","@balena/jellyfish-logger":"^2.1.81","@balena/jellyfish-metrics":"^1.0.231","@balena/jellyfish-environment":"^4.1.22"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.27.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^35.0.0","eslint-plugin-import":"^2.23.3","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.154","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.141-renovate-internal-patch-db163e8e51d4043f7fe50a47276e4c2cc6e3f256_1622093219549_0.1781645658509896","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.141":{"name":"@balena/jellyfish-core","version":"2.13.141","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.141","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8c701534a907d763bb38380ce09b0e622b7bdf2b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.141.tgz","fileCount":99,"integrity":"sha512-6LALusj3GjVA0w8b9jmD2tbZ2q58x+7fV06EZMQ2tShM7etXRv25fLE9yyZnmC1mxaRs2zsOF73ptSLxBzfRiw==","signatures":[{"sig":"MEQCIG+0Ol796FpgtQF7sKBNYki54SfTAnw9ObCnuPyOgmKGAiBRBV2a/kuAX9yjuHbyfyUmBwPK1N4y1tIcXGXXdPlzKQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":760118,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgry9YCRA9TVsSAnZWagAAOtIP/R65NM3BG3kFknYkuSHa\nOE4K+G51MirQn9ymIiOBcWvrUIMmt4nFHVu97xqZN7kauBhK9in8MwWUI4hy\nlbHxonaXnNyVY/dXflFmAnbQNu8qG8RXin0waNXGWkGdGVkC2EIEM08A1lMu\nwKa+cg6RL5Q+SkVPQJO5ApKpdaXkMCLP3F/TKNtZpZpKhmVEhgvOKpjLa52G\nAQoGP0etuaAuiwEBXMIaoHHWjIyhlmS4FHLaanrHYGSt3OkrBMhADpDxZu5G\ndrgUF/BmIejBysZoiT5LTz/ChZcqdrczEybrLvypOUWbd1R90zxdRfMXdQMh\nS5AHGJTAKvcjaTftVFNqkyShBgDINWOandkNlZC/p1eCZSSMZtwnczVQkHm8\n9AshlggA/DI1VRS6mwU1tKWO+zxQiBp5mutmcFJ5YaQrWmCy5YiuaGal7Fu4\nK0xHXXx+oZSGwZoEC5YFZP8dWhSLdUXTr1dC77ZAjc/qQwDk4o+hKuBORq7I\nUBflGOC/iRomFuLY+Ad+YfnvPLZMG6H/3lJsx9kCFEQ/IutjVGZqztbtAlWv\nkfZLWK++NvpPxgkZpfpC6fwGdej9GHD2vWst/sKYmZmzt0P7SVqg9N1ZZrOX\n52BMePrdkIebrVRgYAnbpLtpXPkvM95sOdzQHTjwaMgv+j7PwB6/4l0+k+Iq\ndEX9\r\n=TZXo\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"42880ecbe68165a727da84c438564d5124101fee","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.30","@balena/jellyfish-logger":"^2.1.81","@balena/jellyfish-metrics":"^1.0.231","@balena/jellyfish-environment":"^4.1.22"},"_hasShrinkwrap":false,"devDependencies":{"ava":"^3.15.0","eslint":"^7.27.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^35.0.0","eslint-plugin-import":"^2.23.3","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.154","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.141_1622093655712_0.9847610912580895","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.142-renovate-major-external-major-81b9c0d67fa7e410d38f805058b6896990c89257":{"name":"@balena/jellyfish-core","version":"2.13.142-renovate-major-external-major-81b9c0d67fa7e410d38f805058b6896990c89257","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.142-renovate-major-external-major-81b9c0d67fa7e410d38f805058b6896990c89257","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d8f8c4c5256145708195a80c293b716f0831823a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.142-renovate-major-external-major-81b9c0d67fa7e410d38f805058b6896990c89257.tgz","fileCount":99,"integrity":"sha512-7crvk+K9XQJKuBgaMD9ZpDUkZiuatvfTKO26fqiCZEq6y2prLWGD0F0SwrzSU0NzOEH5Prvr2bX1lRWd1N4j5A==","signatures":[{"sig":"MEYCIQDgzzRlMt0EQ0sTLPefv0Q5bSJAfmY9v5Ob+Gr5xEUjMQIhAJjKY8l5MqEVqSROY5wOOPdcsUPBCzSv494foYjGq5yp","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":760602,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgrzEfCRA9TVsSAnZWagAAQF8P/0aM4z0r9bGUlqxPnuqk\nTpA6V1tFcowTlxgD6DuOrbEGVXDKutPWQZgb4j00Sdl2REjBCtJdedMPI4z+\ng+i29jvIVPF9SLdvqYItOEHTY2Hb2D4scizC6+X+hDpYPHVOWqE1KZtAANyK\nE9afYMOVsoQTmoUHI++hgxKABLvXDZ49onjNPwmCB3ltk1mOEr7RxMDf+pBD\na1yRkx64rZIwo9QZk89CKCb5Y4jU/3fbPR9+A1c164DWWhh1/kVofYaJYdfY\n9YJIKzSl7x0COPom67Hfe0pYuwYAOygueJFGFG3yoT5rGveToyvNEkwxxD4b\nvanaZwWWHTuLxzjIm59x92qUS2wqBD23H1+YdCZwrxo+GHdX3KmO8V3YlfFm\nMVdSzrWxSHJsae/0zAckZtf8pk2i0kutcTmvWM5QH439IX+UmQt4++UyyV1e\nxp61oFMDoD9vDqaGBq2favdW61xljM18xoBdvvRfYVBXjvKMcnKYgpROt363\nHoTQXwBAO1xTlt5CdXdN/oLeExR9r9T/9mNubmyz05gou/ielJhxEaCW1yCr\nqb90LZA8DwFNvg8jnfB7rKco38PrINECLpTEmFXEeS6Rjzf2j+4qtz97aK8m\nGslqTXd62LeSKLstWdDYeTq7/YKLGuPva5e1ukkeYq6Suw/B7pYslb+IL+qc\nOhDO\r\n=P0/W\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"81b9c0d67fa7e410d38f805058b6896990c89257","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.30","@balena/jellyfish-logger":"^2.1.81","@balena/jellyfish-metrics":"^1.0.231","@balena/jellyfish-environment":"^4.1.22"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.27.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^35.0.0","eslint-plugin-import":"^2.23.3","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.154","@json-schema-org/tests":"^2.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.142-renovate-major-external-major-81b9c0d67fa7e410d38f805058b6896990c89257_1622094111030_0.440613217160984","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"2.13.142-repo-sync-jellyfish-config-default-399f33945b156a1d469e1a16dc2f993a697b82af":{"name":"@balena/jellyfish-core","version":"2.13.142-repo-sync-jellyfish-config-default-399f33945b156a1d469e1a16dc2f993a697b82af","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@2.13.142-repo-sync-jellyfish-config-default-399f33945b156a1d469e1a16dc2f993a697b82af","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2aa88f17993d9b5fb81dcafc33e7b7ee0cb39355","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-2.13.142-repo-sync-jellyfish-config-default-399f33945b156a1d469e1a16dc2f993a697b82af.tgz","fileCount":99,"integrity":"sha512-a+NLA3HSxRxg/ZhTnPV6Up4d0H7A0E0QGYjPfwCjO9ODW9bBad+ve9EeSpKydbI5S9LULvuD2ZIJTV/WcQgDmg==","signatures":[{"sig":"MEYCIQDnvM8XKK158JbFlESFuVpO8vdY+y968nARcMYIA3I2bAIhAPoL+Mo6DacvEv4IFFwaj20hj8GEWW4BLfVv1YX99IEw","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":760709,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgr0GCCRA9TVsSAnZWagAAOkIQAIJePy5NvCWtU81SRkdn\nmk2/wsdYcxNpuznBwBfS+17uACVqkc+DtX51q5U2xTH3DsCxM9HfKcsi4/J9\nwQSU1Fm0rpAbcMDGW+43PwXdxr79kRkW6MVIvIDcSRMmlAtfWdXswNBnO4gS\nlIP0hdapRECqYAUJUsaDEXbmFCC/H9aakhcI8jRM1hB+k43jx9PtnFktEuqn\nSTAjhpwRp3/VXoZ4Y3UYykNSqVXM/jY64veT9VoFZlenBfdiZIcft4vx21xc\nI5yQG9OdUinHzgpOPKmDGZ9HgaTty8+xMY683qW+FwUa0Zq5a4QBVjYIjbyv\nhtMy5s/AvL6lNRwQkeyLewEGMiljP7dm1BHapCrI8olRn53fgHstdkVVQx4i\nYmlYRTfVTBLs7oVH+vEbumvC7JHguuQEajiFdV7knOjsWRK830fM6A2ZfqrS\nP7ikW9nwYmyaZ2G4GvvYK8J0LmtxKwFpHTjSKG/RWwEVq9HhwJlU0OPtqV4b\n+MQ1JmWhU4x7RFvq6D8Hlt6kogDjqjwZ9AsMucUaqj6anrK43Esnivayqg7f\nvsuibK58aB6cToLbtvB/ap9Ftb8A8GLLuzbD+NoT8Mry7n3kdvWvSwBi/9mm\nxTe3LsYq6kbcwaBSx1l7P/M36AuLcLcFcSeug6n1HkWuphBAxrsa3heRpbEC\nCTZm\r\n=UqSY\r\n-----END PGP SIGNATURE-----\r\n"},"main":"lib/index.js","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n\timplementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n\twith the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin  And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core')\n\nconst cache = new core.MemoryCache(environment.redis)\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options\n})\n```\n\n# Documentation\n\nERROR, Cannot find module.","deplint":{"files":["lib/**/*.{js}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"399f33945b156a1d469e1a16dc2f993a697b82af","scripts":{"doc":"jsdoc2md --template doc/README.hbs 'lib/**/*.js' > README.md","test":"npm run doc && catch-uncommitted --skip-node-versionbot-changes --exclude=VERSION && make lint && make test-unit"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.js":["eslint --cache --fix","npm run doc","git add README.md"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","typed-errors":"^1.1.0","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.30","@balena/jellyfish-logger":"^2.1.81","@balena/jellyfish-metrics":"^1.0.231","@balena/jellyfish-environment":"^4.1.22"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ava":"^3.15.0","eslint":"^7.27.0","deplint":"^1.1.3","depcheck":"^1.4.1","lint-staged":"^11.0.0","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","jsdoc-to-markdown":"^7.0.1","@balena/jellycheck":"^0.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-jsdoc":"^35.0.0","eslint-plugin-import":"^2.23.3","eslint-plugin-lodash":"^7.2.0","eslint-plugin-promise":"^5.1.0","@balena/ci-task-runner":"^0.2.154","@json-schema-org/tests":"^1.0.0","eslint-config-standard":"^16.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_2.13.142-repo-sync-jellyfish-config-default-399f33945b156a1d469e1a16dc2f993a697b82af_1622098306098_0.8197024951696457","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.0-typescript-conversion-9fe8747542b2543ea2eeb13fcd95ec0e9eefd772":{"name":"@balena/jellyfish-core","version":"3.0.0-typescript-conversion-9fe8747542b2543ea2eeb13fcd95ec0e9eefd772","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.0-typescript-conversion-9fe8747542b2543ea2eeb13fcd95ec0e9eefd772","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b1688784742c7c00ca5a567f037634bf42746803","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.0-typescript-conversion-9fe8747542b2543ea2eeb13fcd95ec0e9eefd772.tgz","fileCount":190,"integrity":"sha512-911Y179HPQOMoloNP5toDd2IfsVbJjIUG+nmnqEoX9fsdD/O5GASixqaJ1WIrqaKmQ+qaoPokyXbR9OA7O+wHQ==","signatures":[{"sig":"MEQCIBxV6BIh3tMypoO2SK5TGnxOrEN2VVvi0C8ZIBI2rfoZAiBNoWxxWmXXV8ADT2b5VKpaCnimB2+uaIFsek8vDurwfA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":717946,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgr0b7CRA9TVsSAnZWagAAdVAP/RrB/y/sY+0fKz9U+Fg9\n/fRXIMfcdIH5efkY6oCmcIfChek665csq6bhOZiU2RDCujXQkgMvQsge2V9l\nhpZs0rNRUawozNFfW8HGhnfd+X5oiJluW+1DgyHsCovZICMufv6WRFdxjXn6\n2clMkeo4+SnXzg4WfuRH8VoLhhBDhhv/3IJkq6F+GahY3wpCprXufqhnJLCl\n28znmLSXP5jxZ37vt7hcQeDDj987RxdMT3sPLsoESjU24UlK343kOEjptdFd\nKDe5IKswyM+pK9oq2t0VnYJ0Z6V1Erj1wXBdyAz2vpjpCF7+1NUnt9zdTegW\nsaowYANWH283flq3aX0Po1Mk+COyCH+CbvdD6QrF6D17V8ym8ZU9VV7kZsES\nQb7iu7/TyVgJF4L+wY9Ansd0SkOjTo4+gMvrFre9zEwDeSoc7tejBIVViwhy\n4ChVL7PhSlwDInwNjaEOzsqces0ufDmj1PNJwxuWRuZDUbUfm2WrbnG8B8eh\nwWuQXZDQN0d85dn4qcPbK9LACCjEoRtUQe365CKKz57UIWtOnUsV/V/kBmdh\n+KnRzEK+kZYxZZHS5DbpdVrl1XeayYYycHd13/jRNS1R/YEXbyY19lKAVns3\nKBZWqBrO8tbUbvm18MmxXnztI7sgVyms03ARAdO7jOvxPVs59HeS7WoJxg8w\n0/bp\r\n=p4/x\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.yml up --build\n```\n\nThe tests can then be run from your host with:\n\n```\nLOGLEVEL=warn POSTGRES_USER=docker POSTGRES_PASSWORD=docker make test-integration\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"9fe8747542b2543ea2eeb13fcd95ec0e9eefd772","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint --typescript lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"catch-uncommitted --skip-node-versionbot-changes && npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --typescript --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.34","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.30","@balena/jellyfish-logger":"^2.1.81","@balena/jellyfish-metrics":"^1.0.231","@balena/jellyfish-environment":"^4.1.22"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.5","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.2.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^5.4.2","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.154","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.99"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.0-typescript-conversion-9fe8747542b2543ea2eeb13fcd95ec0e9eefd772_1622099707268_0.17871850852642668","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.0":{"name":"@balena/jellyfish-core","version":"3.0.0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"62a2df0c7d9458ddb13507f0cdaebe939e303904","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.0.tgz","fileCount":190,"integrity":"sha512-Ah71pgt9KBZRXAwQMxZEdrnyXxqxgV4RaKxoWLivcMaG2b1DCp4B/gJSzUC9l2VOSYtbTtI+aNz5YMWDopf64A==","signatures":[{"sig":"MEQCIBtlWtnW37arIMFxZBjtU1EyrqQGIC/18bt30h4YelPLAiAhGzreKln6R85PjjYHfHzNaZPnOrqCGz613AxhMTmEng==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":717883,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgr0+DCRA9TVsSAnZWagAAHAAP/1liSWBgdUdboib2d44f\n/Qd+wRlTA3uGuKO7nKLp5v3vvAJwojCdTFqp9S4Qr7DDeEyKUTTwoomw5c53\nM5KOaOnTFJvKrzgXcgYhLhxM57MMVH1NFdMsyvDexu1tGylaMZlAZcJCPWTk\nyMPMv8P3OaU+1Xesh8LC2zXh9agRZJdCGnqs/EBSjxtwZhs2qZj6zFG/Z/JK\nsP2cpcim/Vg8rLe2T07tuOGz/QfA0KnL1A3VHOGWXOw76oFFzqE3MWICzDoV\nJyIx0AhRb6WH6zizhJMxPDML2gv2Sip1ixckBL5aadg//mSOEOT3tPMOJSjh\n/ZrO6oim6jExkX6gRo8pTW5swglIGft0XgZfrfUMvnb4OJaVlduCF9D4cRsF\nOCaziDVh+mp+p3xR0BUIk/dA2N1+F29csyrxIQf6BAdMuusk0ebvgu6k7TUu\nsGtFRH4gsh3z4nZrQDGvcajWpT1WzopJleBht8sK5yJgnUhL4AF64O67jFNK\nBPWQdPVf91BYVeG8jNmbfy75Zao9YkaSFv2cvoWgSbKPPJZMYqIYf+IcysJK\ntCgSpQ1K1cbOgaWtOCSWNE2g9Y1qsu1xEQsNhujXVQbZ3L7Qdy1SbWx5Y0Q7\nWyWo29IDKK4s9vhRv/RR8YHyfn5tBr7YFpFUDDawieSuakzfIGWakBrHGh8e\nnMR3\r\n=Cy4y\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"d60040e2e12eda179d026b6851ff12f8e055132a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint --typescript lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"catch-uncommitted --skip-node-versionbot-changes && npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --typescript --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.34","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.30","@balena/jellyfish-logger":"^2.1.81","@balena/jellyfish-metrics":"^1.0.231","@balena/jellyfish-environment":"^4.1.22"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.5","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.2.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^5.4.2","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.154","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.99"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.0_1622101890981_0.151297987259027","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.0-renovate-internal-patch-3154fa2af52d73a792d232018c48e97579461e20":{"name":"@balena/jellyfish-core","version":"3.0.0-renovate-internal-patch-3154fa2af52d73a792d232018c48e97579461e20","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.0-renovate-internal-patch-3154fa2af52d73a792d232018c48e97579461e20","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ea6b16d3995f102bc72e181448c9d446538f42a9","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.0-renovate-internal-patch-3154fa2af52d73a792d232018c48e97579461e20.tgz","fileCount":190,"integrity":"sha512-1BO/RL83AD7BAQMaJhCXFIyPoBQTYQv53ArQ0gWy56PMbknGc7mshMDYfQoqnqllYxFINlxYXMQLoqilkycZXA==","signatures":[{"sig":"MEUCICrbw1IQ6kl0eznF8iBodxZ907c5XfB1R6m3KfE5u4BiAiEAvROSU35xcI/Hzhm4htHofgM8RKCgexjDGpOK3Gy56Lw=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718027,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgr1CNCRA9TVsSAnZWagAAuooP/1NL2cXQeHq0rYaUIAkW\nPXjpTKFA6cpFl/mZgRSeVu2kU387q+fJnUiHk4ZoyJkKdz1y5PAzpoBURrCY\nkc3aZYlWRdnO8iOMz+ugkgPPONudbv5KhAXBtIFr5+kw8K7CCoL/SG2H1z23\n7d1cdX+8tou3rWbSDgnvudw4HIR6xtIkfzykBY1cgq9xIrxoI5rwKAHB17iS\nnTgP/Ea/vmsQHV2ynAQfdk4XCbkRYWZtD+d4wuzXjc8yO0sZyM0xN+9BiVt5\nggHHep9Gms4y0T6KwyEiIew1Y4OHjyIIMZpCJ4hAcflYgE48IiQ/A6U4D6No\ngjd7ja7goazuvwpHEpU3X2llYXWJHvU/d5k24oGT7spLsBUJhFH4wRAUH9mM\nu1mNV2g0M66aT8fjDyP5BXyhW5xcZ0nFRK6lAExuFCKv2G5UbLS97JUVGdE4\n8JZOcZNoktI+5OqZsAVQ9RtkEJFiqBGWMTnO/gQgtcIFSqEscflq6FMN0v7o\ny6xenfKPzHtoO2aRXdTZVeKNXQIY07KXxuCedUkL15rQrCeuV48yAaAFbem0\nJ4o4LnsBy+qrVoOFJHMfXBcSsI5JjHSXJFZQh6E4prFYfZGgkEjoDi6svdHZ\nXSG5shYXi7QwoMX6fRSBlM30f1w02yiinYj/NNutm9llcgja4RSNZgXOiBfU\nx5an\r\n=NA0T\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.yml up --build\n```\n\nThe tests can then be run from your host with:\n\n```\nLOGLEVEL=warn POSTGRES_USER=docker POSTGRES_PASSWORD=docker make test-integration\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"3154fa2af52d73a792d232018c48e97579461e20","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint --typescript lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"catch-uncommitted --skip-node-versionbot-changes && npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --typescript --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.34","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.30","@balena/jellyfish-logger":"^2.1.81","@balena/jellyfish-metrics":"^1.0.231","@balena/jellyfish-environment":"^4.1.22"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.5","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.2.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^5.4.2","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.154","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.127"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.0-renovate-internal-patch-3154fa2af52d73a792d232018c48e97579461e20_1622102156731_0.9412893961936635","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.0-renovate-balena-lint-6-x-cbd196635993af7e583172a5c1fe4984e63de2c2":{"name":"@balena/jellyfish-core","version":"3.0.0-renovate-balena-lint-6-x-cbd196635993af7e583172a5c1fe4984e63de2c2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.0-renovate-balena-lint-6-x-cbd196635993af7e583172a5c1fe4984e63de2c2","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"33f6db6bcbc50e2ba8a02b69d886cf0a35bd6354","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.0-renovate-balena-lint-6-x-cbd196635993af7e583172a5c1fe4984e63de2c2.tgz","fileCount":190,"integrity":"sha512-UmPRG/LtNmCoPLaCpcivby2NPoC4gUIBBkbg3hJrzLG4cfMDUnquRHV4UJNJLVHpFHIguc/0EHcVvQpLad1a5A==","signatures":[{"sig":"MEYCIQC+ncI/TIgLdbNO2PowmmfKfxHArIESzPs23WNboCwcGgIhAPsfukKO0SROzCXjC9AYYpllimeZ0CtIaj7h6ZLqTrfB","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718010,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgr1CnCRA9TVsSAnZWagAAhF0QAIy+BDl8NblZpcj4mSmy\ndVcaMK3/QJWLN2dLdMqde7I5Z3ppRM1d7rljs61QUASRqWBMK2IcNX5hk0B3\nHEjOmpkn1Hq7jC931Lr0lJeNm47NimSRq+NhIa4TsGZCcNbJSG+pAdKSdaDq\n7GfbTgY/4+NZ/8nVQv0x+LI1Xu+TvPDyek0ojfQMXBHwNfcOIT3w/f3VHLG6\nN62rY71vD/mcNdpGp3gK6K0Ir4sDspXWhJb3k82FIBu9NHVV1vvomccFrmql\nmGyHfXebll7G3u1X6SrafdXaDz+skKw1gXM5k5ERp+nrWqRABRJaD+D/yjzq\nZjyE+6OB2lJkU6m9Q3EpCJni8NDHy9m0CxL1T5/AWxOWE4ssFvsltxBq0w02\nr4PV9apQ7lYoAOf+OQB7RfGTiZtIiuSbQk/TTrn1p+zRvWiqUuM64tVhzZDu\n+6t/gGyu5SxpXkXOMC3zN9jMQrLwVUljNcdgH7ZPBWhKwoJDQlgQ1e5uOsOK\nAvIUR5uZMFC5Cp1y8k5pWQMq8ae2+AxUel3g3xcrigYer4htb3dhIp8pumWT\nyS/D6hKdZe0aQYBokvLJmVZCVmvtrn5rX24l90uN20hO73zncGAxh5OBtZIr\nUfP5gzOwGu8GTbBk2kH0MQ4KFuJOHoBnpvpUa1zwLkioD/A33MiQUYM668O6\nTp0N\r\n=cAIC\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.yml up --build\n```\n\nThe tests can then be run from your host with:\n\n```\nLOGLEVEL=warn POSTGRES_USER=docker POSTGRES_PASSWORD=docker make test-integration\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"cbd196635993af7e583172a5c1fe4984e63de2c2","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint --typescript lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"catch-uncommitted --skip-node-versionbot-changes && npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --typescript --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.34","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.30","@balena/jellyfish-logger":"^2.1.81","@balena/jellyfish-metrics":"^1.0.231","@balena/jellyfish-environment":"^4.1.22"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.5","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.2.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.154","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.99"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.0-renovate-balena-lint-6-x-cbd196635993af7e583172a5c1fe4984e63de2c2_1622102183086_0.9916579509761001","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.1-renovate-internal-patch-62ca8398518b7030d225f1d84c565bc688bcf32b":{"name":"@balena/jellyfish-core","version":"3.0.1-renovate-internal-patch-62ca8398518b7030d225f1d84c565bc688bcf32b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.1-renovate-internal-patch-62ca8398518b7030d225f1d84c565bc688bcf32b","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6b78bf3308ae17a349df59c7fa8cb63b41432419","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.1-renovate-internal-patch-62ca8398518b7030d225f1d84c565bc688bcf32b.tgz","fileCount":190,"integrity":"sha512-RPYVCrOhEt3BhhLwwjr/8xzFhErBdraqWxLPmb7pYCF0KOh4faRAKndd0z+LJHvAYHovzvJVHsYcqv9Z4yHBRw==","signatures":[{"sig":"MEQCIBx7A0wE+7M6WJo593+40Gl7BFa7WG6GXAKH9rqvHes+AiAejWijrht0/EIVO65W/jIK0FQcUYfoNcRcWbd7P0TQjg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718051,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgr1FvCRA9TVsSAnZWagAA72AP+wQxt4plUcyB+fGN1NJk\nmR1QQO01O+3LzZng7Zz9n5OEB0tZ2vqYEoyD2LhJUac0JClmlRdcK6ky/jep\nHAt9cw5y5Xmxqa1V6lLddr4+5AT7OouhQfktqhM3+Uiqce6xTyiOqVmJXKxW\necq0sH8P9QTECQAEziZwzz7e8N2hko8bkb0ilpJaCMcs7V3KdKZY7VG3qqPN\n2kSDht27Vfhzy3yXDXXLZTO8y+DoyvpnNIw5ph9oDZjGPyCI96PGDdombt8+\nLO9wg+ckZF/OatwTBhS2iCgZ+BnMgTZaydfjShqZ2tmTUGLuWnlbV8fs+Alv\nDL2y6/c3IjI7nt/GL1F9q1LErbdPUN1LPoDYeAdA+M8Kn5Uu9pK4ul+pJ4lp\nw4HA8edgVKTYYfpeuooSEH5ClIY5ZhbrmGQQOBy9EPAzJBuX5hm6B7kHL+ga\n/2UtF3fdiwSM/fWUGwvclB6G66UflMSiCXlmAdwjN/NUQh3nSC9t9j1vxvun\nE+SNDUJpLTnOcKzDBhPC5JmhvrmebGtc+Rijlhqgl1OIDqzb8vXjN8o+Rp/4\nLr0zm5yGugQcUunv0HM6i67/E53mvuwKR1964XMtOeBNTnleSy8JiOGPDtoC\nTfSt+H1G9ItIUOpLZTxmiTkLKenU4uyD7G7JjxV0BtelhjCoZnsTts1atd/c\n4Pbv\r\n=FBxw\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.yml up --build\n```\n\nThe tests can then be run from your host with:\n\n```\nLOGLEVEL=warn POSTGRES_USER=docker POSTGRES_PASSWORD=docker make test-integration\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"62ca8398518b7030d225f1d84c565bc688bcf32b","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint --typescript lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"catch-uncommitted --skip-node-versionbot-changes && npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --typescript --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.34","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.30","@balena/jellyfish-logger":"^2.1.81","@balena/jellyfish-metrics":"^1.0.231","@balena/jellyfish-environment":"^4.1.22"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.5","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.2.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^5.4.2","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.154","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.127"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.1-renovate-internal-patch-62ca8398518b7030d225f1d84c565bc688bcf32b_1622102382946_0.4098281366655556","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.1-renovate-major-external-major-8b0659bcbd08ba1a306ae7a9f67791d39af719dc":{"name":"@balena/jellyfish-core","version":"3.0.1-renovate-major-external-major-8b0659bcbd08ba1a306ae7a9f67791d39af719dc","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.1-renovate-major-external-major-8b0659bcbd08ba1a306ae7a9f67791d39af719dc","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5fcefe20715b60900eb3720b85f3a8c11eaab93f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.1-renovate-major-external-major-8b0659bcbd08ba1a306ae7a9f67791d39af719dc.tgz","fileCount":190,"integrity":"sha512-IExS26/SCphr/y0njRPKE+s3J3D82WYf8kV30NWuAEozbZPeKuCHvQ/2ouXSlD8eQKm18qancCtjS1yAnwpCNg==","signatures":[{"sig":"MEUCIQDycHHSuuuylCCp1U14IzhkZOCA5Q2QHo6LhB3qC6JtHgIgWJbmUyfzcEScWggW1Y17DqfjfG1OYtZLHT0LAKPYWoE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718024,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgr1HsCRA9TVsSAnZWagAA1iAP/3jgH1Nx8zO6kAMm/N9W\nJBrP6mlrft7VSzMpw9lTWnFhItjzAl19LaEjk47TPouIy9PapdbNk+OjY7Jv\nKXmn25IB7LIdDkZsIa+M4PNphd5TThpSVX0XHe3TFIBrhOJgg2OZHb5zxa+v\nT8PMLzyN0wFyoyf6fgavTrUVIoEa3sanwH4TWCECk6bISX3DJym1W8uIh1zs\nItnWouN0wXCh0Ye4Xvdes/bOYzD8BxbHP1DYGsPfGJWPJK0F3+20F305jDaX\ndL4Uar6GFPFGyOWhT6a2QFtqRvuu5gXmn4ugDEHfW6Njy8HKy11Ayouvj5wX\nsMqHoEbCLkUqNqYHcXvD/HR5SKDw+domMXv6EPIEkv1IimSkaGm/k4J93MwI\nPUoNAv5MPst0wGNskkTQkCiLchur+IGrUlkd3b0C6o/nZRgtoWc/qmkqwI0u\nAdE98JurkggJO2PvwXIC6DGNb97WUsaQ4JV44DFXwn/uVEnFqQpwnksQolgj\nG1MTH5/qTV+pTw38OkEJJEAf2SQMKx5yUjMZdTu2awpTG/mjL08/6FirhsTH\nEdEr1FByXVPmjucVl+AzMzDwOd8BrlRMws2iv7qjxLvlL1yfhZUONI1lpWrO\n0Mst6zY4TFdPQZ7c/d3zRWf0m0vfuuU+PW2BMvQn9e9ecJAjoUSeJKu6zvK6\nsk+/\r\n=nGxC\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.yml up --build\n```\n\nThe tests can then be run from your host with:\n\n```\nLOGLEVEL=warn POSTGRES_USER=docker POSTGRES_PASSWORD=docker make test-integration\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"8b0659bcbd08ba1a306ae7a9f67791d39af719dc","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint --typescript lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"catch-uncommitted --skip-node-versionbot-changes && npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --typescript --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.34","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.30","@balena/jellyfish-logger":"^2.1.81","@balena/jellyfish-metrics":"^1.0.231","@balena/jellyfish-environment":"^4.1.22"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.1","deplint":"^1.1.3","ts-jest":"^27.0.1","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.2.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^5.4.2","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.154","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.5.99"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.1-renovate-major-external-major-8b0659bcbd08ba1a306ae7a9f67791d39af719dc_1622102508351_0.9311489300838409","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.1-repo-sync-jellyfish-config-default-e9bfb15b21a1ae3885df843bf3cf0f4b05963093":{"name":"@balena/jellyfish-core","version":"3.0.1-repo-sync-jellyfish-config-default-e9bfb15b21a1ae3885df843bf3cf0f4b05963093","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.1-repo-sync-jellyfish-config-default-e9bfb15b21a1ae3885df843bf3cf0f4b05963093","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2a09322f7b85c8c53b3daee38b09bacc9f2ca329","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.1-repo-sync-jellyfish-config-default-e9bfb15b21a1ae3885df843bf3cf0f4b05963093.tgz","fileCount":190,"integrity":"sha512-NzxTR52mje7pZue1nkeZmBj2jH/oveHwL6C6KCFKDWYVvnWNyS1GfiR8HoNilcx1eJG8aMnDXZmnkmX6hPG/yg==","signatures":[{"sig":"MEQCICNaIjzNZnMbGuBNVkDFkp0Fc0rTUb9E6So3YNVd2LSIAiBAjoPOrtI67ZOt/kWfOdxuy/sNfdr5VEMZ/iRnzD0fnA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718054,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgr1xgCRA9TVsSAnZWagAAjZEP/2i3d+VjBcaSSooD0t7T\nofHIZgrCvM/vC8ZUG9Td6yp9vjFjhmJODryOliI0wZfTKNyBUTuKLhpUnlhq\nkSQbyWoPN+HXSEAKebknbMrSGoBPRyBtxtG3wGLR5wDeSMZSCVFe9ctIFCrE\nzZkcJpMMw7gBT4q6u7Or9zhQZ+99ZqaJrVvCy6T+XtM11kaFD4UnN3dwYF3g\nqCnAaPf/evKf8OmwtU1CU05FJC/7jTU21w5HY5LaRzzTHL1NssH/kgcTR9lK\nittYUBopvkqXq8OkFkzwH4LSyCWE1tTTnnt6WAn8QRy5YauZ73/hQ+iRWFI3\nc6xBcJ8H2YjcAKbN/OiwJKfWLaRDivs2CGIh/rZgqCwPF+wdTWuQt59v1yrQ\n/aeYDreqhR9l+n4Qk5mGtYz1bjWufrEf8UA14Tds/tvawNfSogeFCnAIDyvX\nmxLUgN3z7pJxzpdQfv8ecZgrQh2kjs4CLBYIHsqAJ6uTSEUCkDmMFYUF/Ncw\nDoxKwGkNrUSv8oxflrIyyp0Q2F+hq2/UtNECUwgfMBKtJmWo5AkCkqhLRMyw\n/8wk2VWXuxspcmequ7oI6aEPgl2Fqlm+Dq1ThEhEl+Ru7d5nuN2hTf5fSSgM\njtr3Mp2R2uynCejNgPcj3sNi/IUDsXC40iuTY8NKwN49BXi6hpBXz8ThXPsz\nZWDx\r\n=czD1\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.yml up --build\n```\n\nThe tests can then be run from your host with:\n\n```\nLOGLEVEL=warn POSTGRES_USER=docker POSTGRES_PASSWORD=docker make test-integration\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"e9bfb15b21a1ae3885df843bf3cf0f4b05963093","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint --typescript lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"catch-uncommitted --skip-node-versionbot-changes && npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --typescript --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.34","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.30","@balena/jellyfish-logger":"^2.1.81","@balena/jellyfish-metrics":"^1.0.231","@balena/jellyfish-environment":"^4.1.22"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.5","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.2.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^5.4.2","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.154","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.99"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.1-repo-sync-jellyfish-config-default-e9bfb15b21a1ae3885df843bf3cf0f4b05963093_1622105183836_0.38307243148761994","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.1-repo-sync-jellyfish-config-default-fbbc6de294cc9124bbc0dfe705a40727c3d832d8":{"name":"@balena/jellyfish-core","version":"3.0.1-repo-sync-jellyfish-config-default-fbbc6de294cc9124bbc0dfe705a40727c3d832d8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.1-repo-sync-jellyfish-config-default-fbbc6de294cc9124bbc0dfe705a40727c3d832d8","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4f4a82de08677ed169a5a5083491c9b74f02a36d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.1-repo-sync-jellyfish-config-default-fbbc6de294cc9124bbc0dfe705a40727c3d832d8.tgz","fileCount":190,"integrity":"sha512-h4MMD6QHa2YjfFLjnm7ecrJ6Ap72+1WsIueXuy6jVzMvAJ86JieJKVWWQAN68jX6AaIhEfQzUTfC0UxtYXibhA==","signatures":[{"sig":"MEYCIQC8cEwKBFUCnyjXSOg1ozxEq1PersLzJfnZRWC9f1NBRAIhAOsG583LLft/7meewFpOF22fj9J9v7mVaCHXLtLDFzG2","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718054,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgr2JXCRA9TVsSAnZWagAAHrwP+gMWlpwiIXVdBLTzA6OM\nR64zctEQ78ZZrbHCOnQljPUDuo3E2OBipNtqZelg7hLAhfMnql1Mn9QqPDAd\nXIKy/+okv6HLW3w9lx7zwVRSrduYiuMRT2OtebqmNEP0KvCPKSlAT5E6m66T\n5SCfF9swuo2xayF4Sgu9wwATUOBRXDp+IPNciXiHgY0Xd3cc/Q/GZQtHqm26\n2DZ5oL1nx8t6lRm4XOrRpmyJrHk9k0ozXY+sTrSexzQ/nSxdq97f1vdc6HX+\nGsDQ4vm43gdn6qvFsJsiqASrOP5QUzkkyU9P6VWnYyI9jJ61md850DWG+R1q\nc9KOu6h9uQELgz47/q3sYvUDfARZsHGo6yUfd6Lxi/BkdTxhcvg/FZx/E1oB\nbHePe2dzYuD2ZpP/09NmJlSnRAWttcl37nk/Md7wmGrH5BdjS/Vea1W0LyMK\nt5NP+Xn8h3adbAcXIpgPRuHblSsrtN1Lj1gE1lxLupikZSXvFd69wDOF5HTw\nDp2CMJcSgH8qmL/S8g7okxjxpaqb2IxcSNB4/tctDSqf8L5rT6XEaLL6lHuJ\nGX7XQfAoOTppC52crT+Vi2tLuiGpmMgZoUBnnWuo7DVwi7gCldnIzYoY3pWY\nQDUp9z8dMVHSMuZWKPQ7Kr2sk4nual8e+Bl4ZWxAMt55HM8i3eJRsxUsUydm\n3Pj3\r\n=x1os\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.yml up --build\n```\n\nThe tests can then be run from your host with:\n\n```\nLOGLEVEL=warn POSTGRES_USER=docker POSTGRES_PASSWORD=docker make test-integration\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"fbbc6de294cc9124bbc0dfe705a40727c3d832d8","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint --typescript lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"catch-uncommitted --skip-node-versionbot-changes && npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --typescript --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.34","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.30","@balena/jellyfish-logger":"^2.1.81","@balena/jellyfish-metrics":"^1.0.231","@balena/jellyfish-environment":"^4.1.22"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.5","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.2.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^5.4.2","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.154","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.99"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.1-repo-sync-jellyfish-config-default-fbbc6de294cc9124bbc0dfe705a40727c3d832d8_1622106711318_0.844178379165939","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.1-renovate-internal-patch-e71f354eaf0fba9bcb549bc1f0cbddc62a18ef68":{"name":"@balena/jellyfish-core","version":"3.0.1-renovate-internal-patch-e71f354eaf0fba9bcb549bc1f0cbddc62a18ef68","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.1-renovate-internal-patch-e71f354eaf0fba9bcb549bc1f0cbddc62a18ef68","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"29366334bf44f1e45da620a14bddc14dd69799af","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.1-renovate-internal-patch-e71f354eaf0fba9bcb549bc1f0cbddc62a18ef68.tgz","fileCount":190,"integrity":"sha512-RlPoBySwVpbzDC2z7H5yJkM0lIDbcNai94X5LiPXG70fvXFRl2QB3bDm7BCvEQfkat3zUn46RHKM8sh0mcmhUw==","signatures":[{"sig":"MEUCIAu3KnsqYPPWnAkeyfm5MW7FJ7/DZHZNmstsaqNEODOuAiEAsBp+DJe6aH7TnVB4l7OmmDi69s7WHzXZ8b5YPKn/O20=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718019,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgr2obCRA9TVsSAnZWagAAew8P+QFNsf5Ofw3wpPJkqaEn\nYt2Xkjf3y6xIp1T/PgB0cbR0F7ZSra5itSLxmmnO2SC43BY0dDAgbu83BloT\n4GFbJ/aHcKdu284pI9EwddamNws3QDChwkCiiTHKD03MYZ1NBQioSc3A7qrW\nNVWu9malzajLyoQbb3vdlsrY590amgr8hCue4yW9qyeieoLd3cltPiqC6BqP\ng8gBiphWI+JdqDPWRcyGvkkv/8thSENwHd5snFq/cKU4LEPCiZcpBgWMFbJV\nMyH9T4K6O2hHlOG/9ukrtIdh4MQb1WQT9gQBGWe3vJyGourxptfxh1r6Lers\nFNt4KUsNZ/m53ApG8RNG2kEBG4pRqsfgGuZ2r3uLBsPceKQYMl3ORTSFm+Xf\nr3MQRGuZzcakU1zGlRBoG2te0ZLJXZDohpSv9h6icXCJm/98dK4x4FiwNJwC\n6ruFUi7GT26jt4RdrcuJEM+zMEpQMAzDvYFslS+L7EX79591nWUrWlgcl/RB\nnQ8Y0e1e12yZ6Agd3AN2O/0+t/5Ve90lZYVOVGtC3LXpMNvz7JwM3fSSQwDe\nwcPJC2iF4ofjW2wE7HTKpqp02kQUxjYOAlmr6oCIRC4PvYGocR65Z3KTtRFQ\nbRCSnde4eo+nJME8Mjfx6HZCt6YnyadZ42u4DCYb5EA/ypdxzvqkqtBFHv1v\nqtD3\r\n=GQkQ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.yml up --build\n```\n\nThe tests can then be run from your host with:\n\n```\nLOGLEVEL=warn POSTGRES_USER=docker POSTGRES_PASSWORD=docker make test-integration\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"e71f354eaf0fba9bcb549bc1f0cbddc62a18ef68","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint --typescript lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"catch-uncommitted --skip-node-versionbot-changes && npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --typescript --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.34","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.32","@balena/jellyfish-logger":"^2.1.84","@balena/jellyfish-metrics":"^1.0.233","@balena/jellyfish-environment":"^4.1.23"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.5","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.2.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^5.4.2","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.154","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.128"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.1-renovate-internal-patch-e71f354eaf0fba9bcb549bc1f0cbddc62a18ef68_1622108699363_0.8466544943274248","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.1-renovate-internal-patch-5fb49f0cfb15452b65652b16b2549655647c2d67":{"name":"@balena/jellyfish-core","version":"3.0.1-renovate-internal-patch-5fb49f0cfb15452b65652b16b2549655647c2d67","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.1-renovate-internal-patch-5fb49f0cfb15452b65652b16b2549655647c2d67","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f0b865a4b09070ffb9e666fc23a749580929ea2d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.1-renovate-internal-patch-5fb49f0cfb15452b65652b16b2549655647c2d67.tgz","fileCount":190,"integrity":"sha512-d/syuUYWvGheIvW+o5aGfTDZ0E+4sRk5mLnrWFoiHza/SiQK6rykkbl25LRHzPXVRKQZdOt80vtB97258hCKLA==","signatures":[{"sig":"MEYCIQDXgphqvKcbzPydzyT7q0LH2wWQUy2CtYXYIjkBhqxEFgIhAPltTKXBr2T3Ul18gRVATE+WysBwQIQkayFzein9tXfB","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718019,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgr4IMCRA9TVsSAnZWagAAqeIP/jU0yZX23sEcynfxqlH1\nzdER+ZMBn9l7vqjz8jZt2EtqN72o7h/wN34C58z6zBYzfAcPxiRa8sqFbISh\n37csprUfBhBnUClvelBJ7ZykVEzLa5BCmUwGlFwlULQyxe80mrP9yxCgvugn\nwDVl3s4eKYErRE6u6OBF9jegKvjtXQo5sdW3n9vdIrKJUsl2zo9lHBY21WZp\nIEM/ftboVjodBD3Cx55HcRZa/3hqZJLakw5o4J3hC9o/yAfenc7XJReKRAZy\nFADG64N9a0i1vIVWrL+IvDnJow0hTZGqUrMsvN9Rt74i5TndY/fEaKDZAJcx\nCjTXEEQkyiLOKIPqRwpsRrYVBhwCOMWFPzN2PdbbvHh8WP3qFe76UbZJ7v2X\nOBXNXcvlqXPWO6oNJ/Pufy+vhlXGS38EkX4SkgW+bQaSlNvKZ64urH/tw2OR\nkz9K7ZUxcDe/GTWbEfoIukvqzkzMlObvfaEVNFdENEiiQNnoQ8uZUc72lGVH\nWCU1QO5fq7k96jyFKqCmFTabXQKaaJvDBGoZqBqWyzPpUlnhA54jLqBu2VFO\nqm0HsLEJV+kNQAYOddq3Ln5O5fuVsZehy+O11ZMgST0vKIx1YstWZWhEloPG\ns9IBT0iucmx+MS1uZHOJ0zLnZfLPXAHJScKeZOYg/M80KWPi4/bSu0T+4FY2\nuR+y\r\n=bgG8\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.yml up --build\n```\n\nThe tests can then be run from your host with:\n\n```\nLOGLEVEL=warn POSTGRES_USER=docker POSTGRES_PASSWORD=docker make test-integration\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"5fb49f0cfb15452b65652b16b2549655647c2d67","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint --typescript lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"catch-uncommitted --skip-node-versionbot-changes && npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --typescript --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.34","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.33","@balena/jellyfish-logger":"^2.1.84","@balena/jellyfish-metrics":"^1.0.235","@balena/jellyfish-environment":"^4.1.24"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.5","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.2.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^5.4.2","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.154","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.129"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.1-renovate-internal-patch-5fb49f0cfb15452b65652b16b2549655647c2d67_1622114828013_0.7499710834387456","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.1-renovate-internal-patch-53504721cac5fbcc9999080fcb4024f4f028bc30":{"name":"@balena/jellyfish-core","version":"3.0.1-renovate-internal-patch-53504721cac5fbcc9999080fcb4024f4f028bc30","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.1-renovate-internal-patch-53504721cac5fbcc9999080fcb4024f4f028bc30","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"de64ddb4835aa1707c99a41dfbab9ab96d5bf201","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.1-renovate-internal-patch-53504721cac5fbcc9999080fcb4024f4f028bc30.tgz","fileCount":190,"integrity":"sha512-2dnm1uoq2Qpt/lski6civoSRcC8tRPr28wRg+9eJkryw5X6KZhRbi9SgD2oHqRj6I3eYAbRRjA/Sf2+3HZQZlQ==","signatures":[{"sig":"MEUCIAjBQ8lvXmepRy5WEy/5OIUpOfaALuzVzUK1qlTggZRIAiEAvxe1FtXVXAwewooRIkfjqfe+UAjE+q1bNmm0BRNkWg8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718019,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgr4NmCRA9TVsSAnZWagAAlgsP+wXKwAUejqR0okdIvw/D\nSKxUoVV0mXh+U26YNGDZkKiSKeslHKkHDoPEoCDNXXKnT/5YrCTuFl8nL2vW\nNDVTqxqijevQSXGAk4rD/utv/WEWnFWzn+KwWvs0dl8vI3nc9NEfRyAbaAIR\nSrEGYEUVMrPufwtLK4dXF8UrPqCtcFEGQUoIUf0lIvFGiI81mNxQNekL4BK6\ns1eklm8UUW/GiomiOPDiqpGH5wjam+zWFRgeRuLkgljIqwTXvzcj92LSM20E\nSFkOL3BSolfKIlgxgqHvUBebPgTNi9Fk7hYCizHw5wZ+rO4F3DjImkCabhdy\ngJyn5XOd8fFlrhca367ojcsZZONTi6t/e8ptW9JL+m84F3oBDWmYaByFKdy5\n0UCf9hwYJQR0x+0OJUXbnKVGKziV0k8fATFVJRb7fvKJQs/mIN6qkBEzIv7q\nmbOMaJqmO3/E8IsQEHIKFawycK1/YCvjDeEhBAGSdqoH/pfrAH8/iPtO+VHZ\nDS8W6Rvve9CeM0xniqLeS7fV6Rh84ZA4o/213MLM8HecLxp2ahFfHkpLWwj7\ny2YW2zFwYcfqCVm79Ks+MpU4DIbfxF3rzKDq7zJYsLcYb960oQm1gDeuVVxd\nD7j+9WHcuHuDsprWbvotIwbLT8ThniO5IE7LtlGTm9Kcji/SUGqeWJQUjiHJ\n/Rvv\r\n=p+KI\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.yml up --build\n```\n\nThe tests can then be run from your host with:\n\n```\nLOGLEVEL=warn POSTGRES_USER=docker POSTGRES_PASSWORD=docker make test-integration\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"53504721cac5fbcc9999080fcb4024f4f028bc30","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint --typescript lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"catch-uncommitted --skip-node-versionbot-changes && npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --typescript --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.34","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.33","@balena/jellyfish-logger":"^2.1.84","@balena/jellyfish-metrics":"^1.0.236","@balena/jellyfish-environment":"^4.1.24"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.5","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.2.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^5.4.2","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.154","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.129"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.1-renovate-internal-patch-53504721cac5fbcc9999080fcb4024f4f028bc30_1622115174058_0.10223636426178584","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.1-renovate-internal-patch-8dfddc6f7020caa41aa83a0138599ffe0f466ec1":{"name":"@balena/jellyfish-core","version":"3.0.1-renovate-internal-patch-8dfddc6f7020caa41aa83a0138599ffe0f466ec1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.1-renovate-internal-patch-8dfddc6f7020caa41aa83a0138599ffe0f466ec1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"385fdc127a11f464514e971e30a37b23945b1503","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.1-renovate-internal-patch-8dfddc6f7020caa41aa83a0138599ffe0f466ec1.tgz","fileCount":190,"integrity":"sha512-YbcC3qG97SrRagIjp2FpcAdsuXpkBwzJMIQ52G26opBu7uitBj500KUpVteZ8H/j/hnUMnE7zX8s2mtYn/QA7Q==","signatures":[{"sig":"MEUCIHz531gIPxS1E+iQEa+S+NpVwTlYYNmWy5frYAERmhckAiEAm92qXccXBxsmluApc1590Qi4GV+yHy63c/z0B822Cnc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718019,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgr4ToCRA9TVsSAnZWagAAHLYP+gPIcXKxSYyfK+UlIRkU\nCHOeqtWS+tF/Z30jAxcFLNQ4TfmPf3RQ/nJ8L+RqAfNWxWh+6P7DiKgqPx1W\n77yIkOWUTZnHKhRR9GG3PaRwGbPiTXRFRC8+mHfnvVFsXWtaaCMeNABiw2n4\ng97j6UV+zdeO55A5Ofh35kUSQCkJ3LJ+szuofk8FWLGrVAy/AD+4J5XkPsPB\ndWep3eHxeiSWhi085pVwznix9q5vFcq0I+9dm/2oGGhd5SCNQkeoqrioprVH\n9STL990353TkOIU4SLLPWr7hLxEANRFcrw3b7Ou1BL084im1kgr9ALW1INqs\nAvBas2Nw+8rVAYwEs8OIGd0l1iR3+/Nr44SLc05n2qVNnzsCj2LgsSt3YBIF\nZ1bJg7OGUIxu0zJDNTGu8H3v7p9AxS+OgoqsdKUoxo0L3GwaI8qAXegFbsus\nz4XI8Z9NLDh/xDGStTthRlf4zySuKkes8Dl/L5fk75FyKJJhIo5c6y5VvgiE\ngr593Tguk+OKbmS+ql6DIdzDGXaNMSfAs1T8s623zTblZkq1ywsFmGaYnuIP\nRRRwgOWRUPpVpZ8CLghmZT7KivTI2VrnBkRbfBMROBX1FXjXqmf+uoc3PiQm\nkFU6Rs+tEBicq7oJ/LkZRuaFRfwKakCcvhGYXtVZzbIJJhM0/AeHKlXPOGGX\nTmik\r\n=G19D\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.yml up --build\n```\n\nThe tests can then be run from your host with:\n\n```\nLOGLEVEL=warn POSTGRES_USER=docker POSTGRES_PASSWORD=docker make test-integration\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"8dfddc6f7020caa41aa83a0138599ffe0f466ec1","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint --typescript lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"catch-uncommitted --skip-node-versionbot-changes && npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --typescript --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.34","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.33","@balena/jellyfish-logger":"^2.1.85","@balena/jellyfish-metrics":"^1.0.236","@balena/jellyfish-environment":"^4.1.24"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.5","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.2.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^5.4.2","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.154","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.129"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.1-renovate-internal-patch-8dfddc6f7020caa41aa83a0138599ffe0f466ec1_1622115560065_0.9214671033094908","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.1-renovate-internal-patch-01362fcc4446d2cc0bd3f579c28247a64aee2b7a":{"name":"@balena/jellyfish-core","version":"3.0.1-renovate-internal-patch-01362fcc4446d2cc0bd3f579c28247a64aee2b7a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.1-renovate-internal-patch-01362fcc4446d2cc0bd3f579c28247a64aee2b7a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0fdf810d8824b5c9f2b9b746fc5c459bf3c2d071","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.1-renovate-internal-patch-01362fcc4446d2cc0bd3f579c28247a64aee2b7a.tgz","fileCount":190,"integrity":"sha512-DddiM3T0SQhW8QGNwf8Cst10nqhgeOMKyOkDXApeObf1OD0oMgVJ9lSjbeufD0sswNZA3H9x5ZVxoMEjGiCSyA==","signatures":[{"sig":"MEUCIQCKiMRWu+43NXpjDypFBQrfiCVCDOfp7lQz+dn1LAxhRQIgBc1eVSwYJGmBfZXHuSJoAqB4z88khHvlI7t//HEUIBk=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718019,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgr6B/CRA9TVsSAnZWagAAJrIP/RKZwCHSfO7/Vc+Wkotb\n/am4VuRUIpqRlqUZkt6hGcyIjDwJrpAb8oOiR28ZPGfPZquw/j8Fc0g1moHV\nUZtddV/Yln68DdJENM4sD51RV0lkEt5llDvMXkp6q8a+hPdY+O7oRz3XINil\nEvUGgTAKeFiC7dQjI1sIYbDbwmd7gODbOLVqv6dXkq+ZB9sVkP+O/r6FvB/F\ntuA2lVOMzEOF2UEMDts0dfKOTblLhBmvQTDN/GexGy13QjiaaSortGeoGOd3\nd3xzIrAFwKQowwQvGk/2Q504VuXxyZX9a9Gf1EJh3Q6gtlpMTD7zppC0C1S2\ncflYYjHobJIMTjg5AYGhWZfUcH+hwGvB4ZzbjTOzMjUTRHLDi1YxJb3sEDQN\nCVPg5p0Gm5HUeAIcEUI4m8e+ZeBRtZMHUMsnRVjvuxo4dPVhCuBc9T2BT4tQ\n+e8NdhR1zOe3QNMGLklgsvPgXSOXa+0bWAvXlZ4Y41xXnwV8O+cyy3vIXmx0\n6bKlpblKbuODVaNVsYwbWH2tgC0Kqw1gb91JtwOEpClu8vPbFWUJV8xAoM+r\nNl/rkZhAEWyUW3PvcrQ559yP//tPnP+nM3OL8FO98QQr4XhBjeCE50JuNtlT\n8CTouOVUWP95O5RZ5kh/3snj6XZgrwILKVILbfw6GiwQfqmvjk3zTk160HLa\nuzDs\r\n=FAy+\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.yml up --build\n```\n\nThe tests can then be run from your host with:\n\n```\nLOGLEVEL=warn POSTGRES_USER=docker POSTGRES_PASSWORD=docker make test-integration\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"01362fcc4446d2cc0bd3f579c28247a64aee2b7a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint --typescript lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"catch-uncommitted --skip-node-versionbot-changes && npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --typescript --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.34","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.33","@balena/jellyfish-logger":"^2.1.85","@balena/jellyfish-metrics":"^1.0.237","@balena/jellyfish-environment":"^4.1.24"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.5","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.2.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^5.4.2","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.154","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.129"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.1-renovate-internal-patch-01362fcc4446d2cc0bd3f579c28247a64aee2b7a_1622122622729_0.41232372404252193","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.1-renovate-balena-lint-6-x-6ae5ca9c43174b9727fb46cda3fd682ddf2d7ccd":{"name":"@balena/jellyfish-core","version":"3.0.1-renovate-balena-lint-6-x-6ae5ca9c43174b9727fb46cda3fd682ddf2d7ccd","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.1-renovate-balena-lint-6-x-6ae5ca9c43174b9727fb46cda3fd682ddf2d7ccd","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"52c2626d8fe1e52902192d2135ef4ae8e44bfd2f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.1-renovate-balena-lint-6-x-6ae5ca9c43174b9727fb46cda3fd682ddf2d7ccd.tgz","fileCount":190,"integrity":"sha512-qjn2pxvVCqQUVWuwT/EHU1wX6MJ2H74FEHuGWbFQxVwaEiZCxTHLB9OHNlg8P4eWsGQ+XLqPBpcMLwvHyPC8yA==","signatures":[{"sig":"MEQCIDZ2hmeFAhV2A/ieD487sQ28PaxFztFIKYeqJnCxYxTHAiAanpa/mO44SIxe9Nq0jTjpsww1a8p19P7qairacA/n6w==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718034,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgsDevCRA9TVsSAnZWagAAY6IP/Ri8BbrBCnSUw506uz2C\njgPgujkd4DpnVEZGtqV4yiK0GE9AjOseBgYfwDRiatuRWyt+olRvY8KtWONE\nbbwbE+yLUiUou2RVch3E0wCoyu9Igs4gt37fhc6hxD7m2D9n9zBVOfTktMmY\nXevFXMsHLr/aGFENDbneeqh0aJjxCW7t0f5PL/uv4gKYhI1poH/zctUznnBd\ngnrsxAwkkYYaRt75XPqrNltbqNiexFqW6MIe1hLOUzcNhVbc+3gFxH38aq0B\nBXJJyiNiN9ILvlwohI6p4RPTYN9RenzqIYCs9tWJFxmQspexbIh+21tFHrxs\nLbf4TEmPreu8+M7JR8RfUrP49inxmBUA7DWCl+dtzM/+oouC3nGZaW4Q8Aog\nAfIBHNp4s+faxyRdYds+nhNLczH0jYUVN8o/f4Cks0F+BMOF2k8iTbelkaEb\n68cZAI3h+BwAAo7a171Q8Zs3EbS0a8AQe3U8jVcIXnSVg7tW6lt6rsAo96W/\nJ+iDGivgG3UU8A+y9tOfOQJ/5i6ngPWFw4wiH7OwKqE1jRop2UQXsSzG/sBe\nBOe0uG0BKQA5laQjsqJyruZdShay228cq7Xn/juFJef8XsnIDHaWjtbQX5Yw\na7bgj6UJNMdRqkbIf3ffuLsty0vZe/bfQdSIueTVn6D+sVu+nYlziUYUyATI\njSoR\r\n=RAu2\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.yml up --build\n```\n\nThe tests can then be run from your host with:\n\n```\nLOGLEVEL=warn POSTGRES_USER=docker POSTGRES_PASSWORD=docker make test-integration\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"6ae5ca9c43174b9727fb46cda3fd682ddf2d7ccd","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint --typescript lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"catch-uncommitted --skip-node-versionbot-changes && npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --typescript --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.34","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.30","@balena/jellyfish-logger":"^2.1.81","@balena/jellyfish-metrics":"^1.0.231","@balena/jellyfish-environment":"^4.1.22"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.5","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.2.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.154","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.99"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.1-renovate-balena-lint-6-x-6ae5ca9c43174b9727fb46cda3fd682ddf2d7ccd_1622161327264_0.015481007273197012","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.1-renovate-balena-lint-6-x-1ac2e7daf480750f211edf1c613203ac404b253a":{"name":"@balena/jellyfish-core","version":"3.0.1-renovate-balena-lint-6-x-1ac2e7daf480750f211edf1c613203ac404b253a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.1-renovate-balena-lint-6-x-1ac2e7daf480750f211edf1c613203ac404b253a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"24c9fd07af541090e9db2948903c5fca33092a98","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.1-renovate-balena-lint-6-x-1ac2e7daf480750f211edf1c613203ac404b253a.tgz","fileCount":190,"integrity":"sha512-bqyyy4w0Cv2D3cSfLBAxqOEPCb1pBgDtD8BThTxi3h6UK3d6jGaBuH2WKQBfutVBQcEn3oYrf2dvT6JwMUanJA==","signatures":[{"sig":"MEQCIG4zhsCUevdtFEUlNojXcqYcmwG7Iwg42nIhe/HOlgfmAiBkqJMAU/9sp1VshUqwtCu3zl5dOHh7fbQBfBYWv44ugg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718056,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgsEBKCRA9TVsSAnZWagAA24EP/1tDXGih20lYCsAFNq+q\nCz9PZNKMqbBsFReuayUw+GVHirQ3BB2DKhUJ5uOnq93G0BcF4dK60yJtXTcX\n2+HSxtGW9M9a9HhNiVUrFaEraULyPVjYOBtoBINjfC5T2n/LPSLre96Ll9vk\nqi8f6xGaQv5V7sPCMtBqMwtfLaMQ4a0HgcbEwuZAK70Ad2w4VbOxrrppcrXW\nM75TzGWNaZx6lp6KVpFvBTkzwFcbL/XzFDBYRSVLnHZl32qT9KKwFgZz5dL1\n+z4sUxLo5UiD5PW1vrew8TqRc/NbFo4jRV/XkzanMG0GfdXVQko2cwSL40c3\ndgBsdd/ebAgWOqxVBRzG/fZC23wUsHBv7+vZsmvNOJ+uNjGzW+ZOOnj39AYI\nsKo3Z2DWoyU7/r+Y8kkV+TL8WAVBLOrfu60hf/nsETpigTol78XFgpFKsohV\nm1dcpFBLHt5vmhRPIrH1rLo38zlNQfjL/PSEB20zsmIYuzXVMO/lMjcTZRj/\nKbblUEHOgyFcth+ekDSx0yyw6Wwv1V+9Gk9H4QAqTuvEBoSdtUPNYWHCbvWs\n/y9igwER0clePOIs6kToAEB9tO6IVjqSLZbAj8pdkBH1LPPJuQmxsVx5aksk\naZutec9zSRABbT1YlAX75gDc65RPMP4XZOhsfOrPIyBd2A+mDGXXdSf9LpTl\nzuXz\r\n=UzCf\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.yml up --build\n```\n\nThe tests can then be run from your host with:\n\n```\nLOGLEVEL=warn POSTGRES_USER=docker POSTGRES_PASSWORD=docker make test-integration\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"1ac2e7daf480750f211edf1c613203ac404b253a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"catch-uncommitted --skip-node-versionbot-changes && npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.34","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.30","@balena/jellyfish-logger":"^2.1.81","@balena/jellyfish-metrics":"^1.0.231","@balena/jellyfish-environment":"^4.1.22"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.5","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.2.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.154","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.99"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.1-renovate-balena-lint-6-x-1ac2e7daf480750f211edf1c613203ac404b253a_1622163530286_0.9081527331391095","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.1-h32-setup-in-transaction-9cfc811158738f2c1005273361a0271baf14ee0b":{"name":"@balena/jellyfish-core","version":"3.0.1-h32-setup-in-transaction-9cfc811158738f2c1005273361a0271baf14ee0b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.1-h32-setup-in-transaction-9cfc811158738f2c1005273361a0271baf14ee0b","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d93048843d55f8fa4c0759c166ea490d3f014dfb","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.1-h32-setup-in-transaction-9cfc811158738f2c1005273361a0271baf14ee0b.tgz","fileCount":190,"integrity":"sha512-FB3J+VvoQKh7JLUCMNhpKG8TNHxz1oq/Rfkr/DguVlDqPDLGXrq2w+cdA98x3CIrhISYxTDfMJsJo18ytKx7oA==","signatures":[{"sig":"MEUCIQCtYCku908dXCf0CBf3fPYvxnigAHdrC29auPy2L47PXwIgIyMn3R8+aiBc4ezIwJhLsijennpbRtK6TREuhstzNk4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718310,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgsPykCRA9TVsSAnZWagAAvuQP/2/JQERhYvVDKsjY2Sgi\ny7AbowGcch6+TXxZNFaCtMtNwdvyOSwN8e/HWiz7F747LA9l1052oFJnwc9B\n2bBN35V1lUNugJMv6Qpw5+tknKzHkPcuSdiFx9zgaXWE2bAGJz0pBRE0K/cS\nO9JsObxbOJSGiprnajHYDMLVSbcmdV9Kap5ldlkivyWMW0OSF/Gv+asuwdbn\naFHp3uxG/KBHNyThHeg1HccRija5fkeuvQvrAzG0xx2eyxdZd8QKv8QmxKDU\n4pf9KXpz4FYLYo9/e40QnRNZI5Vv+ibWkcyRJkKvZClIW/fuoBi74SPCXN6w\n1ZKLbv6G6R50EqvAshFYUQ9qCajYiaKJTIrbjuJ+rKvh4zOifCWX9WnnE9I7\necFO9bKEuLmV8bC4o8CNqiByPXMdjz2kNYgfzm//YZnF7ezXgFKDzCA9qmrN\ngSjWugtd5gYFDLTK3cu0W1EvXh7KUooCAeWhnq/5VsR4FRKNW5p85l/SqfEd\nv9mh2T0SadFGfGXTnkEEu+p5pgBkBvUNlkPc8kGR/4oIj8Q8FQpaV9vZh6S1\n+wC2y69Yg82Jey6msKUEIzmqSbCJsrKBmeB3arcRvGc57jKxpN3mVD4Yii0k\nRyC1agoRkfhKwF+5tkYSNIQR4fH3rMcEsg49df4Af7hqt18S6LucRNbV3yoi\nry+H\r\n=//XH\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.yml up --build\n```\n\nThe tests can then be run from your host with:\n\n```\nLOGLEVEL=warn POSTGRES_USER=docker POSTGRES_PASSWORD=docker make test-integration\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"9cfc811158738f2c1005273361a0271baf14ee0b","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint --typescript lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"catch-uncommitted --skip-node-versionbot-changes && npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --typescript --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.34","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.30","@balena/jellyfish-logger":"^2.1.81","@balena/jellyfish-metrics":"^1.0.231","@balena/jellyfish-environment":"^4.1.22"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.5","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.2.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^5.4.2","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.154","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.99"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.1-h32-setup-in-transaction-9cfc811158738f2c1005273361a0271baf14ee0b_1622211747682_0.9619649164927737","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.1-h32-setup-in-transaction-6fd70e1f0fbd8da1c15853ff5efb294a4161101d":{"name":"@balena/jellyfish-core","version":"3.0.1-h32-setup-in-transaction-6fd70e1f0fbd8da1c15853ff5efb294a4161101d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.1-h32-setup-in-transaction-6fd70e1f0fbd8da1c15853ff5efb294a4161101d","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"683e9f3f63b367302927df4dc39f01aa4ba71013","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.1-h32-setup-in-transaction-6fd70e1f0fbd8da1c15853ff5efb294a4161101d.tgz","fileCount":190,"integrity":"sha512-OAyua6/Bm63vBVfwuDlLq5uOp5x9gLWI9dXwJcjR9fuLM9GhkEkCDp+XQsVfrxDd6uiugLPmmNav99KQrR6iwA==","signatures":[{"sig":"MEQCIEBE0JgYwGjMii7KBack4uFdHw0qItFL8T/8K4x0NZWhAiBgYSvqghlZC1eLa3r/5GLULKVAZiOa34uDioVCWgLMTA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718310,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgsQCTCRA9TVsSAnZWagAAD6IP/2It80mLwULJrpFI8XTc\nd0h9sMrYzQ/KlUsvi5ds8ycjGIuXQgD8E/cMwbHAuDZqlJqcTCbpLKBTsiPP\n5oYwGuW5WX8ySOOrK6aDLOrwTVO7YyQNYEz4Mf4Pju2ty1wrGOvqrjLTu0e2\nUO9UiApebkSqaK5bR6YpabGDQRZXvByECZPdh3890IIN4WnAS6C7G3aMazUx\nj06eNko1zk6vw4nFZhMppFOcKt684qUp9/eND6ZoRQM1V2OrMm9350fX7Sw2\nqkQq73W8stY/8X7T8IEMI9T6SekKGWGzSGY5/HHk6AYULjAUGuEbpzNiwTxW\nZFkXb2vVFGwteg/UshHGmWmwpYitUUJ2R2UlYt4f+O1dl0meJh3pYGdGaLJG\nkYfj8YVavMMTJL5M0vbuJS7PkAknI/QdX6OQ1xwnYzuj5FnFUdwx9DSu2003\n5YU4Uv0stHmHjfZLGdtM22iD/bevK6IbV4ApE9qx086wLFCfmACYZh+/oFLa\nUnYqNWj/itQIGO6yZHubu76a90fUm23Jt+rWNxcxF7AISEtrWSP2YUynY22b\nAgNPycCjMDlaBVN0vVOJXmotTBBWVaNMuslcG8WuTdQY7/h4gRx4m41sanwO\nGnrZoDG/0d6CVbTERXP5SXNO1CMMt358HoEN4wAvAmK2jLQ6eXjutfQOB49y\nCMui\r\n=MMQ2\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.yml up --build\n```\n\nThe tests can then be run from your host with:\n\n```\nLOGLEVEL=warn POSTGRES_USER=docker POSTGRES_PASSWORD=docker make test-integration\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"6fd70e1f0fbd8da1c15853ff5efb294a4161101d","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint --typescript lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"catch-uncommitted --skip-node-versionbot-changes && npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --typescript --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.34","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.30","@balena/jellyfish-logger":"^2.1.81","@balena/jellyfish-metrics":"^1.0.231","@balena/jellyfish-environment":"^4.1.22"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.5","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.2.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^5.4.2","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.154","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.99"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.1-h32-setup-in-transaction-6fd70e1f0fbd8da1c15853ff5efb294a4161101d_1622212754921_0.5665307841782214","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.1-renovate-external-non-major-63b2cd74132965460c2cf7ed38b5e969e2b4e868":{"name":"@balena/jellyfish-core","version":"3.0.1-renovate-external-non-major-63b2cd74132965460c2cf7ed38b5e969e2b4e868","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.1-renovate-external-non-major-63b2cd74132965460c2cf7ed38b5e969e2b4e868","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"43897c4fcd302978ce07203b2e7d6eca82df3788","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.1-renovate-external-non-major-63b2cd74132965460c2cf7ed38b5e969e2b4e868.tgz","fileCount":190,"integrity":"sha512-191lrHCFRbNCewnFmyBrJfPwmQzYdFnyIrFB7NhxxAKNwgVRtOEtaHm386Lvt3HBb/iS8LqnbNMrv0hGSrFqag==","signatures":[{"sig":"MEYCIQDml+/YBo5ssiubkv6zXRUpoySnMmakk1lhBSPJIVHHjAIhAOeQf9ZDKyjMwVON6v6HKhhkrfPMDyHyRveE+4oZf/vy","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718026,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgsY00CRA9TVsSAnZWagAAETMP/0SdyVcyy3PSf84u6Et5\ngNg3gEE/8YEt7/is5MYS7Ro50ntNqB7DLs58pnjDqtGXYaQWqV9mPD6nBgfR\nb2BToCQCnE/vyWzNTtWXQMw/Zu5lHlMzF1vulRDN1wj3SDNRq4TPBBbilfgn\nDAakM9Rk7bOAnLL3BBbsSbus8TwGYffVqeV5fzmrclh8PTruJ0cZuToCJ93v\nDhzpBapk+gvO65f3YWZmfug+p4z/X19SliG6WOtNYyo35DxK4QxcLENx0Wje\napYUOc7wQkLOPng13BRPq9PUVxdlfOq/Ts7qF800y3MMLmacrYfSYCfNI0ro\n1zAjfQzx+U8zcRiBSL1vQAdGl+0zak53LA0B0O3P2EpzGtzf4bX3cdc+hFaB\nJszRkbYFt14eAx9cYD7/wIybpA2mshKhsXMh/5kzqvBrkzOH6UiSmBhA4sp0\n+n2KwyXGEzXg1jGJIz1TRsUOE5YrePvviBaO6CPYcrnhJPAhHqJglPamVeQ5\nalZCiFrQLJPB7/tBxZo92y48VHB8YeXrp2wFrpFW/Hb4LUD8QJOH+culWlsl\nNKXMtGTisup1a0KUSKcaF3eLKCSbwj3XGeyiOrbTX+QIfzb0M7HeHt+BDsqS\nNFABlakH79CU6AhWcDVfaHGNATghcCY/1/JZdSTfIxnQ7XrEFA84ePuLLECP\np5ZY\r\n=aqDA\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.yml up --build\n```\n\nThe tests can then be run from your host with:\n\n```\nLOGLEVEL=warn POSTGRES_USER=docker POSTGRES_PASSWORD=docker make test-integration\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"63b2cd74132965460c2cf7ed38b5e969e2b4e868","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint --typescript lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"catch-uncommitted --skip-node-versionbot-changes && npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --typescript --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.30","@balena/jellyfish-logger":"^2.1.81","@balena/jellyfish-metrics":"^1.0.231","@balena/jellyfish-environment":"^4.1.22"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^5.4.2","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.154","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.99"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.1-renovate-external-non-major-63b2cd74132965460c2cf7ed38b5e969e2b4e868_1622248755618_0.7420770807056942","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.1":{"name":"@balena/jellyfish-core","version":"3.0.1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"199dfbb9b9fe7217bfd558cbc26b148ba5f6072c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.1.tgz","fileCount":190,"integrity":"sha512-R+K4EoI3Bwveg3FQmwmOgigafDRJAWbS0aHKqTa0ALA7butYTzXOFbYJp7KcODHa6ywxpiEY3AGn7cJ/gOj5Aw==","signatures":[{"sig":"MEYCIQC1wykuI3nK3gltlqO3UaHH5N4dd0r/ELyz1SKQmbKnkQIhAOYL5oEiRm4VJn/SAjuHdMU0+HUfe/5aTaMrHZ37AszV","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":717957,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgsY7gCRA9TVsSAnZWagAAPegP/1DV+KI+9838sdhNg4Ro\n5CufAsgOj2LlKYCSKkcb53nurKE//BHMXy3oHyxTqd0Xu0ICrsTwGtJlZe5x\neXNraoRLAoD4B0H0vBVAQ1Q73V9Hi43RgYKP33RRXVzaZlgNU2cGuNSycRCD\nQOknMueJqmO7mlbNzcFjMrAH27ikpVwtpHCr2jFND5AnWwSZ3qHKz+7CWo4q\nAyorNkqQx8Bici6TrdPOwYnGZpd6lroyaMyYT/wMGU4fDxGUXrXJ3Y2tWmow\nd8dCxE7rrqciOq6t+KFn/MGyuc3KrIH8U6AbAzE8Qtdu8LQ29p9l1Tq3hzYX\nY49eJ3o5FhAJW149d0KbDSxK6QydAbcw9FI5W1WGx/65DwQ5RafXGAxP2iK3\nBmTUp+WuF18FtDPYkxPr3O1d7GfW2fDJ8L53NcIqjgkeXd6hCtZ8j3FU1SD1\no78bS/BtS2kmxVdAfnOJH7mGYgvXV99PT2c0Vn4AiaBy16ugF9eIReKU9Xp3\nK5uwbAQRRk2LByPZhj7h/aJTvnbNZGpR3o0qjfAw3gTPxtJz25P1+LnkEeUr\nCxaeYESLl6rDufbrN4YMGdQgVbBOB0k8DNk5hX4I5a5yHV6NADDbdlKmz68a\n/FgOwaxFcuN+gb8pLgXHf4nEIJeIqSEjZp7xGSpz6lMEcIb99iD2OVQD7KOw\n60vt\r\n=BpFz\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"85acfb7bc2e4ca1a412a81a78299d5b04c8fd59f","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint --typescript lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"catch-uncommitted --skip-node-versionbot-changes && npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --typescript --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.30","@balena/jellyfish-logger":"^2.1.81","@balena/jellyfish-metrics":"^1.0.231","@balena/jellyfish-environment":"^4.1.22"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^5.4.2","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.154","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.99"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.1_1622249183131_0.9194107097530129","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.2-renovate-major-external-major-6d62cc13348c3af94bff98cd0f725553232c570a":{"name":"@balena/jellyfish-core","version":"3.0.2-renovate-major-external-major-6d62cc13348c3af94bff98cd0f725553232c570a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.2-renovate-major-external-major-6d62cc13348c3af94bff98cd0f725553232c570a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9d90fde36d4314821e29152289560b83e35a4b74","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.2-renovate-major-external-major-6d62cc13348c3af94bff98cd0f725553232c570a.tgz","fileCount":190,"integrity":"sha512-/YxYAnaBCRXEmEAK/x2RbzmelyLHkW3lD+uHDxgT4ER0fzUxpgYCf6yAoWbPSFDb1hmvoYUUSosk3jO7n4LDTA==","signatures":[{"sig":"MEUCIQCt/2qwpVZfKgWO1gTaK+RVWzmoDcXS0GH2y9cdTqKrYQIgNkP/+ympSa+Q33adMFhSpn/MZelkpeFR0CxWN7PfwDc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718098,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgsZCQCRA9TVsSAnZWagAAiL8P+wYLi9sKvFEidwkDxiiS\nNUWKUP0/0wfEVPeNqrKK5iijzflquiwajRHlRFDmjKJSbq4gTrL4Q9kZ4G9+\nAHTLbhj0rm2CRO/cHYs/uP3wG4nsaxljNqmNz3ZgxjoK+7vHVuQTbEUasbgV\nPnLpIxFDKLMShmr/+mIjNNugZZJAvnn+EMa0yiH0n0+Vg9ugi6HyKjA6QdeA\nTiETqaPaztsoVOPH1rqE9uzp/FkHHSgpFkMgCrSayz3Laex8hXBfT1nr7UDL\nYYQG82cKpue5oZtVwUhpQDAatJ5/zoBLBk/Tu5cTqLKwagC2UdkE7aZRG1dF\n/V69FYuNrk1z5n1moVV5wb0aBClwZX6HWP8joo2JPEKgLXamzhcJYSNKyTEE\n1NVAJL47/q54Ylywmcdw4ICiIvVt3tzwEIajur87iW5qP4V9vlYTWg2lUDOz\nmhItaZIytwZYTpnV9SDzL2vahd4gx/8Ff98h96OyPAv66pmp+PdE/Mq4yKab\nqt9Dc2QzPMElcx6yCBFchH/CrJDM4mOLjpg88KTLjuSb3Kj7JSSNs4HIhQge\nmgdHv/JGjttBZBkEHL8zmA1k42kzEfWPNIGiCkKd5h9SPOAaCzCSdybJ9XpP\n4M5jv2WWa60PSeQ0GfXg7gIDYb20bskScFxM4Cpz1IeXs98/oiSQKrMLTqck\ny45D\r\n=3rgy\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.yml up --build\n```\n\nThe tests can then be run from your host with:\n\n```\nLOGLEVEL=warn POSTGRES_USER=docker POSTGRES_PASSWORD=docker make test-integration\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"6d62cc13348c3af94bff98cd0f725553232c570a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint --typescript lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"catch-uncommitted --skip-node-versionbot-changes && npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --typescript --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.30","@balena/jellyfish-logger":"^2.1.81","@balena/jellyfish-metrics":"^1.0.231","@balena/jellyfish-environment":"^4.1.22"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.1","deplint":"^1.1.3","ts-jest":"^27.0.1","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^5.4.2","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.154","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.5.99"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.2-renovate-major-external-major-6d62cc13348c3af94bff98cd0f725553232c570a_1622249615521_0.4589542685709611","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.2-renovate-internal-patch-e03e392f2f42d36fc89369899669d1857408324d":{"name":"@balena/jellyfish-core","version":"3.0.2-renovate-internal-patch-e03e392f2f42d36fc89369899669d1857408324d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.2-renovate-internal-patch-e03e392f2f42d36fc89369899669d1857408324d","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"08f9621d13deeaf4b8b5ad4c72905f7b551f393a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.2-renovate-internal-patch-e03e392f2f42d36fc89369899669d1857408324d.tgz","fileCount":190,"integrity":"sha512-xWMXeNsBvtVpHEjTXeMr9C5Cvidepr1cODKBAVJ50QS29VbHE0NfIM+Yx+mr53fjxXZ2KZFfHw7KQkrYwV2wWw==","signatures":[{"sig":"MEUCIQCy4saJ6DHRShoxkFW54fXQKvon1EImPoRqOiCaReAhRAIgFhjRPYr19uirxTNF7UxGoQ6TwXT4wPZlbX9IXUE9ZNA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718093,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgsaDxCRA9TVsSAnZWagAATEQP/161pB/rlAUtla16XN11\n90SK7+zwewqHwJ/LDSNPeAg6l6FI5GLHJzg2jqfcj4dmC3jS/Uym3wvOKt1G\nNBU37Z697GVEcpwWT7DEQNXAhPqgSAJ4C//lS2wNzKRyA9POrrVkyBP5uEQa\nnuefLyMfPtbj732fAClKLSnlH5l87GrmRqau+DwdHqrBB0a80cEjDqL4YFzH\nb+IDzGe1ynRZlkMGbNOW568MlVoS7qayCHHzlhg/9H6wHz1rc3lTY+RtsjoJ\n2mis9SC4b2e4T63+K4Ls+ndhC82aAIFrkDrb57NWEHNkTAOaaP+Fp/gqfZWi\nbpbCh50UQiF6p+9w3poAu/JeND8zlXX+p7LwUkJ/qQnRmUCutuIYDEWEa2hL\nDd8HBg8ml+c41TpLtoj7z5mXd0cDwlQ4u8bf2tK9h9YKR7T6c2Uzh/rINiVY\nYGrGIAIddSbhTUC8VXhKFeid4f8V0jjfKF+/Snz6dQ6YORtA+P4e+H9Tp2HF\nnPkDeRtWJDRwoWuRdlzvjwDGGjQfKDvmzqG8fNzhUQhLAHnuh5Gj7RbD96oO\npWkB9ujSynTHY/kbVVKOXznxl00j1XZSbLlFJon9qCy1Hku6I0rlWNcKl4MZ\n8HT8f1dDlXyJBe/43DVwyjuDeyiyiQ4WxlJ2PD/3lTYmr8VRhRUCLv7oNV/R\nVrC+\r\n=JGdD\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.yml up --build\n```\n\nThe tests can then be run from your host with:\n\n```\nLOGLEVEL=warn POSTGRES_USER=docker POSTGRES_PASSWORD=docker make test-integration\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"e03e392f2f42d36fc89369899669d1857408324d","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint --typescript lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"catch-uncommitted --skip-node-versionbot-changes && npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --typescript --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.34","@balena/jellyfish-logger":"^2.1.86","@balena/jellyfish-metrics":"^1.0.238","@balena/jellyfish-environment":"^4.1.25"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^5.4.2","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.154","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.130"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.2-renovate-internal-patch-e03e392f2f42d36fc89369899669d1857408324d_1622253809302_0.20574220304974222","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.2-renovate-internal-patch-f310f08940ae95adaaa4f2dd1647be5f9f0daebe":{"name":"@balena/jellyfish-core","version":"3.0.2-renovate-internal-patch-f310f08940ae95adaaa4f2dd1647be5f9f0daebe","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.2-renovate-internal-patch-f310f08940ae95adaaa4f2dd1647be5f9f0daebe","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2020bd5a68f93137c90969716e5ea49ee0ce0c17","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.2-renovate-internal-patch-f310f08940ae95adaaa4f2dd1647be5f9f0daebe.tgz","fileCount":190,"integrity":"sha512-xaV2GOoGRGDl91mZdWlaahD6r2xktyWr3NePS2LgxS+yzODdtgelM7iBW0feHbOs1Vxsd5Sx4VZxU5lj3slSMA==","signatures":[{"sig":"MEQCICVvy6qaXZyKJBfoGez4Vr//0wFW76o+turVZX0vW4naAiANZZ/zlEcqgO7OSBrvPqyEypV/y17YKwwTAysfD1kosA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718093,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgsbEkCRA9TVsSAnZWagAA554P/1pUsPfjxcyUS15/+ejQ\nveIctZ7kgZYfFAco/AJZmDVQ0vi0Sh88os1T/ly2kzoGuHevQVDEUVX+DIcz\nKK8UUkk8AKc0NmQWporh5mwwg9GrQt55AzDHRZxNnnrKNhW6Rqmxtdi2vH+Y\n4dkot2d0C9JdsuOj6CBE3DOPZoAPnSbpIW6Ww30CAzC8CJ4IU1YkhvKQQ/1z\n2ndP1rW9kYgk0oReLDObZTwjHzR0bXOZXjn8mo/PSGNmFqsCDKux4KjgaBnj\ncBjm1rRroAg3o9OJe/Wasm04L6fAmliA7pyp+LkdpUFr00jElHtilwtJa8dO\nV2gZ0wydmQsSPAUQQmJVFeAUD2Wqj3w+EgkMTFsZtCSbgjI6JKbe7g6YdVLq\nBtdq61WNq1hUlaeu26TCeqAGWdSnpAkE/sxGjFbI+H2cflZVPsrH3D1be032\nfCnOyHIoZ8LxeAmv0sMC4xETbZhPIzCHKckbX63Qw4qUjCNj4gKFis1S9XRR\n3WG0EuHTcTv8lyO04tfZZOUzVVl8riUhNmUWEdnLswAu44TMnx7c6fBNQZ9y\nskgzw0j01OoEyEior3YcEfwoTw/x743WlkEm6D8cL2/IAQSnBgfBktL6nObS\noNzp6Ri6QrV9sZPc4YnMphLokYV1wq9Z8qb2JgJIJ68Rca6FoaT4ij36/FqM\nzH5z\r\n=w2I4\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.yml up --build\n```\n\nThe tests can then be run from your host with:\n\n```\nLOGLEVEL=warn POSTGRES_USER=docker POSTGRES_PASSWORD=docker make test-integration\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"f310f08940ae95adaaa4f2dd1647be5f9f0daebe","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint --typescript lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"catch-uncommitted --skip-node-versionbot-changes && npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --typescript --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.35","@balena/jellyfish-logger":"^2.1.86","@balena/jellyfish-metrics":"^1.0.238","@balena/jellyfish-environment":"^4.1.25"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^5.4.2","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.154","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.130"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.2-renovate-internal-patch-f310f08940ae95adaaa4f2dd1647be5f9f0daebe_1622257955931_0.34624467016389726","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.2-renovate-internal-patch-b5ae202d5b8684d0162e71888fd0d93627296295":{"name":"@balena/jellyfish-core","version":"3.0.2-renovate-internal-patch-b5ae202d5b8684d0162e71888fd0d93627296295","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.2-renovate-internal-patch-b5ae202d5b8684d0162e71888fd0d93627296295","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"cd903af31c23ae1f2d1edf07088993ed33d6cbbf","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.2-renovate-internal-patch-b5ae202d5b8684d0162e71888fd0d93627296295.tgz","fileCount":190,"integrity":"sha512-jG70PfweTe5fWTmYeKsdPi56vd5l0AnOG/jmJ+DS8HR0wPb4KqYHNuQ0QL2PO45V0ESHd58jN5C5NH4vHccOsg==","signatures":[{"sig":"MEYCIQDaAMY0mxLGoET/anpPIxa8bUeg/HItFbWJfR9Zs4qcfgIhAK1jCRlyLUD7cXU9EL0tjXxBeN5RdIAxErStCTkUlcRS","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718093,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgscaICRA9TVsSAnZWagAAjlAP+QFcEuDSc1zMg0+hIYPc\nDPZh8L29jmhdHIvKIrWBiakCraK4K+4K2aHY2nRpU6voFWvz/OlsbnuoKbZq\ns4LPZl3uvp10/9xHDUpAeaPaWsfa0OoyulFjQn6B1Ye5zAxt9PlSQ2QyFpRf\nUWMRx/LWVp0Yh2VVYY6rXkum9IOFS4GeCQVndNM9J1an0ip+0fAmt3SfP06E\nIK0ZCDLXpNf1yyN5KqjXYhv3xHZ4L9rXimS7MsrJak82dSSjsfoe/wulLO2A\nNERf9f2g6KanZN+lb9+bouRbSeeiiz6yTEg079cS8aw+fCtmnXP5aUs42Ny7\nssF42v6IamtH249A9WYeDkT118SUFVBeEUtaAoRSomxF9L9MmwJOwo96Wg13\nPoubDqv4UpYziT0DYRf7mBiAybYS+t+WTL8Nx65ZHg7h6Qe75S4lfoFHROMH\nlIxdJMeY8BN3UEdanCzTIbHt5eOiNq9jBwnRBebSie/PoLQEenyNg0vF7SJV\nj8c3bWM3AaakddWywb6dZToxJ7mZv4a8QFFRQLSSdriKlLxVn861oHxRN77k\nDfpqDBA4Ja4Y4pML8yY7skRw9B6v/vVWz8sb8+FT9V1+FtUTX1mp4uhEE4bD\npSGoIUJGZSKe6nT2KkGLHHU3ucio66vp68jYkdShGLCqZI1vfFqxceuRPz2r\ntFB3\r\n=hRjD\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.yml up --build\n```\n\nThe tests can then be run from your host with:\n\n```\nLOGLEVEL=warn POSTGRES_USER=docker POSTGRES_PASSWORD=docker make test-integration\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"b5ae202d5b8684d0162e71888fd0d93627296295","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint --typescript lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"catch-uncommitted --skip-node-versionbot-changes && npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --typescript --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.35","@balena/jellyfish-logger":"^2.1.87","@balena/jellyfish-metrics":"^1.0.238","@balena/jellyfish-environment":"^4.1.25"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^5.4.2","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.154","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.130"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.2-renovate-internal-patch-b5ae202d5b8684d0162e71888fd0d93627296295_1622263431759_0.6914721590261819","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.2-renovate-internal-patch-5161973b007046a280c79f8dd28baadd60242050":{"name":"@balena/jellyfish-core","version":"3.0.2-renovate-internal-patch-5161973b007046a280c79f8dd28baadd60242050","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.2-renovate-internal-patch-5161973b007046a280c79f8dd28baadd60242050","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"39f1f38531410397b9c9b72a5ee7af689edac9f2","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.2-renovate-internal-patch-5161973b007046a280c79f8dd28baadd60242050.tgz","fileCount":190,"integrity":"sha512-96F0VtOIdNoCzzxEhVyR0NfGrJ6aOTyJ0jXuFJaO3nSOmpDZZdDErsw88ApoY1ht2lM8a4Fb5ZxZXzCjT0giKA==","signatures":[{"sig":"MEUCIQD0dbqBla3M/odtiT4iDRhWhkdOSm8NcqO6O6hy7PfKLgIgQBS1/J5/YFjoitV6Qu7qgRR9ngidickPsfjFIDtSiNE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718093,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgse/9CRA9TVsSAnZWagAA78UP/3RvrC9jk824aLpaOmhR\nyqIK1gTTZBJvFliAF4ZsgssjnwJAyhIwlz9kX7g1cOGMD6ZySmJC3sdrGznM\nQiiDvSakAx/K3neyD2vr1px5q4VpgnIQU2VPMPpxTUxpLhkgG1ZgtvJVoSyI\nkrLvbjFS1IW6Y1As4QLaC8yyDShHTtHVzGBsgs0F+SQwpCscdd2N/XFVw/T7\npkTK7lbNrJ1LiAygtbYG1dtstNrNQ91KyemvPsLFK7YQCYg5r6KJAGxglVl9\nJmhhHsnVN7C0wIUgzBdwjhn72h4bnWYw+57SVWF8/5jtlfpjM/rhDgfY9X+k\nNot3lti+xWdS3bMBOJqEVHhjgeeuWibzfuDse5vsTjftVVfHNH0AJYqMJwAN\nUhmtIEeEfxlv5VZ3VvptYO0QN/v5HUNpciZ8G5E2RxzK4ZCha4pl9ewKp4Or\nlhcbR8wzzZMKv87zTRKJmir7Uvm3Vce7WjwhIAN5NcRpxMuGIQGVfrmBPAzW\npwLZEk84JqFmkll1kPxTL0GeOkpOSsxeLsLG5tNhfz/xnVxWe+j75EDyeSWC\nsM6N3C6vfFaNVuz+56BYsKj3tYPUPRGO5EWMa0P8t/WlK6yf53v/IXZEo+PI\ntcfwDlzxXBx9m9tJksh1E5p7PChNH//p/QtCGWT2CTWMqqG/0z0/PEz49Mth\nyR0F\r\n=rsuZ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.yml up --build\n```\n\nThe tests can then be run from your host with:\n\n```\nLOGLEVEL=warn POSTGRES_USER=docker POSTGRES_PASSWORD=docker make test-integration\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"5161973b007046a280c79f8dd28baadd60242050","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint --typescript lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"catch-uncommitted --skip-node-versionbot-changes && npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --typescript --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.35","@balena/jellyfish-logger":"^2.1.88","@balena/jellyfish-metrics":"^1.0.238","@balena/jellyfish-environment":"^4.1.27"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^5.4.2","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.156","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.132"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.2-renovate-internal-patch-5161973b007046a280c79f8dd28baadd60242050_1622274044963_0.3022472602320563","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.2":{"name":"@balena/jellyfish-core","version":"3.0.2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.2","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"bcab1d8e67abfa40c3041dd21bceb39652d3191b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.2.tgz","fileCount":190,"integrity":"sha512-Wu+bgKXB+Y+YyBff9nBbtevUVNnLPzHtSjKl7bobNLtvdK4mEM3GWcTjuE/CwizanfUZZ82qcFDnBsC2JS0uCA==","signatures":[{"sig":"MEQCIEaJZGGG1QINdWY45ie3FhzryurUEyuaziuq0WKSsI31AiB/JHFIAdp74FPVqdsywFVJ739udzqCqRAvNda8Pfsjfw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718028,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgsfFDCRA9TVsSAnZWagAAH54P/iGwiHMiS9ThfIQk0k2T\nFif0nU4SqIsNc72YNVRbxs2FMZW+MtfdD6ORPEEBeJQZyzPC+TYQMCgs24ST\nPQ6Jvg4Ee3grvlws73H2zEIRS8362pHq4kUTJJOe/niKcU8d9pKxeuLEhLq/\nuGcNLfbbLeYsk5hUSjMb1CiaEenDyLYi7oZbA5YwNH8fBG1PItZpQ2DVdXWA\nNdpQNH/S9pysQJsX1VEeqIN1BQdV9iXT6u8wwXjl6y3EzL5CLLaTj/6ZHuaq\n4CrJjf6jwusg28u/1rMOOe0zkF3OANX/dgMp68KgOlswF2WrZhlZe7hAwnH0\nTTpEYh6tILhOzDGjfSY/juo+ZCZFX45edL3FPhRKpZWz5tsmmG8DbPdXKjLq\nExIaqZLz6MepNbtiYwBnqAcS19J+dKhtEgvdmZFVKTkVsss/C/2YdGJzuFUD\n3Skw1QMf8qDodxaYGGqGxA7AzeinGUY2dnf8zoKjq2k+5QXA+TsEVfmluJDm\nxxuA/Hqj2SnKhpjhqzYsfkbndm81pmdP5O2xSRHLVMJDaqInvy8KMwOBU8ua\nHvB+OpE7tojxOGq45wIF11KFgiqAkq2e8O/vmXynezMCQ/SuohuYeIBgMx3q\nKqfvxeRfX3s4lrhtmQQlUzMVcvWfCxBXr5mas/rboGkhvwRCXNjpOjb5JKZW\nxhQq\r\n=1gVf\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"75912292a15e0add99292848f5e3c60ee85a8cc3","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint --typescript lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"catch-uncommitted --skip-node-versionbot-changes && npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --typescript --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.35","@balena/jellyfish-logger":"^2.1.88","@balena/jellyfish-metrics":"^1.0.238","@balena/jellyfish-environment":"^4.1.27"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^5.4.2","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.156","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.132"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.2_1622274370472_0.23117402403341836","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.3-renovate-major-external-major-6eff559a2cdd26e577d4663eeca04c549975a46f":{"name":"@balena/jellyfish-core","version":"3.0.3-renovate-major-external-major-6eff559a2cdd26e577d4663eeca04c549975a46f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.3-renovate-major-external-major-6eff559a2cdd26e577d4663eeca04c549975a46f","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b0f19c59bde874c64282b7c9c328a651a071d0e3","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.3-renovate-major-external-major-6eff559a2cdd26e577d4663eeca04c549975a46f.tgz","fileCount":190,"integrity":"sha512-d5evRO6Ig5a1lziuvmxh2KrBfEJ7qcs5EjXLO3S3LhuUMNsn2D3zO9+3jn82KkJicAC4EhqlXcswVni6WFMffA==","signatures":[{"sig":"MEYCIQD5yTX3sRoG9aE9XAa3xx2HaZ32quUbC3HAW7ujySAzkQIhAIdu1qtZetVpYbAVSlzP0qkxE0LD2fVwZEO4qK7xlTB+","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718169,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgsfJWCRA9TVsSAnZWagAAengP/jUClKhGPtnjgtAtbndR\nBd5zvKowZzCtzcV9kMM5qIhG/PXLOksSOM1R8j+HnCDPCu+aWbwzu5iY/cTM\nYQuI6BRrMQTyOOq0CkeDljD+4CQAPY/8v3w6DOpVpZ10c0KYDz9hapsxmkDD\ntmxXKxSnxTx+7pc8LJVzIsos/Fu2R+yyBRE2i1rnp9g5VRx8ml3WgNe1xZP6\nIbrZx+EXWKAZWHHjcLQtJoTMEoWpAgeaIeoyB7gAchsSoemRyL1jjbx1StUw\nDktQWPK3DzKAsKhQqc4Wb92b2tMa+7OvBbTQrJa5e9hUr6ucjv8POCte09E7\nmDoccf55QubsW9Q1OsaX8+dYMdTjzhynxMbGRDYRrLNF5N/OfLSx+u+98k0s\nBAzYDIIGBPCEpT8dSTLCWN+qnvRfEQ0MPhKYjB5G2vkSXzWnZxJrLHl04v8q\nyL4iu4uGyvOtd7QumUgIrjNGzLx5oqNUSXlFeeHBvqbsVKnUlQtKAufd1y5v\nFVcpR+G+UIrG1tte+ooxDESY1N2GSFaLvg6JnBtLoTLAMnS5Rw03Li/iQNU7\nLTzrnRRjPUsq/TLSarb9WC9ndJ+SvXbuOHx01nVpt73cpuFOs+LJnHoMv1tm\nDtYgFymnJECmu4SmAD8Wkt6GzLuU+rWwV5zlnUTzgKYcBFSeeB1XM6ORX+i8\nuo4o\r\n=14RB\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.yml up --build\n```\n\nThe tests can then be run from your host with:\n\n```\nLOGLEVEL=warn POSTGRES_USER=docker POSTGRES_PASSWORD=docker make test-integration\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"6eff559a2cdd26e577d4663eeca04c549975a46f","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint --typescript lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"catch-uncommitted --skip-node-versionbot-changes && npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --typescript --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.35","@balena/jellyfish-logger":"^2.1.88","@balena/jellyfish-metrics":"^1.0.238","@balena/jellyfish-environment":"^4.1.27"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.1","deplint":"^1.1.3","ts-jest":"^27.0.1","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^5.4.2","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.156","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.5.132"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.3-renovate-major-external-major-6eff559a2cdd26e577d4663eeca04c549975a46f_1622274646416_0.9851819928276007","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.3-renovate-internal-patch-156a43bc8c77e4cdf0c94d2c4daca3a4f9bbbeae":{"name":"@balena/jellyfish-core","version":"3.0.3-renovate-internal-patch-156a43bc8c77e4cdf0c94d2c4daca3a4f9bbbeae","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.3-renovate-internal-patch-156a43bc8c77e4cdf0c94d2c4daca3a4f9bbbeae","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5742c98b0521824f93b4a83a27eff94739824a9b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.3-renovate-internal-patch-156a43bc8c77e4cdf0c94d2c4daca3a4f9bbbeae.tgz","fileCount":190,"integrity":"sha512-Dm6IUiCaFR5bEuCiwMkeDSW+0zugdBYNLAU24G5dagwT/u3a1YGRX6STglzlSUlSn3ol5VWw63psZ1fiSnKqFA==","signatures":[{"sig":"MEQCIDbxpCGsb6dft0VagmiyrCSZGp1BBnf8qldJ5exc+eD1AiBWTA0WmtK8pgWH82PBkHCUrUJzjBfk5OKitKX4YuZ0Ig==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718197,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgsfQqCRA9TVsSAnZWagAAmggP/0r5QsyvO0zqeOTX3nbl\nSi65S2gjdrCIpWu2VCjmMRHKzblk4mC3K5tcABBgDAx6oyawlXlRZ828o+QL\nySMtfs5i8+QfRLbo6H2M6OfzTDjnbOa7PvNupDFKNi7v9gzrSpl+HlCoiPxK\nfO3rrbVlsXnOdPJ7IdASpEzwl8/VcWvwyRRKhLxgcADIDnzaN+sJ4q4d4gZF\nBn9i9Fv/vEOSdWMQ385KjNszk2jm5OFe5Np5DaJ/xSQvzAR4W7h2YwS/xtxg\nV8tBI5EKbnlNhn7Cbnv9UkEwTFknmZrlIldiyIUGWzwiqUaiiZmfuXnOyuuG\nqPWvX8hF/xF8zklpsMT7qIXrk2Lb+fqEywqFuNf/3qJcxrlOEnS1QI8L7UEM\na/SxyhLgBrvKPSom7Q5QgUCffQZTMKm/12WqqNwfHDpEtWRk7GE5eAODa7D+\nnhXn4cfVqeIBIoSyPGlFNp59IEWAvcO7ms9TR1Z2NdQW9q6sP1VS0QFTZJXL\nkTXX3Lu1ASd3hI/lADWrU4ck73Q0+c3t1EO89mkgwPvdY9lSyc/b8NVeUmp4\nDk0AaJIq7cPLY92EntsYvYdYUSHxtRrNgS/jvMAwkOCNj0KHTn6s+ncaFZuz\nCO1Q175SOHiRrZogeCUyR9UM1JTY9YC/uXw+bNY1whvsG2t4defUE5kiTmwf\nrGTi\r\n=JIiQ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.yml up --build\n```\n\nThe tests can then be run from your host with:\n\n```\nLOGLEVEL=warn POSTGRES_USER=docker POSTGRES_PASSWORD=docker make test-integration\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"156a43bc8c77e4cdf0c94d2c4daca3a4f9bbbeae","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint --typescript lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"catch-uncommitted --skip-node-versionbot-changes && npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --typescript --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.35","@balena/jellyfish-logger":"^2.1.88","@balena/jellyfish-metrics":"^1.0.239","@balena/jellyfish-environment":"^4.1.27"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^5.4.2","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.156","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.132"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.3-renovate-internal-patch-156a43bc8c77e4cdf0c94d2c4daca3a4f9bbbeae_1622275114003_0.33893723835865774","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.3":{"name":"@balena/jellyfish-core","version":"3.0.3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.3","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6d15a3829d849a3dd64da5aed5839748254f11f6","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.3.tgz","fileCount":190,"integrity":"sha512-MECaN+k2aUuYdtbgwev+0CbTIy1MWvaY80ecs+xbnvNZBaQgN2WnSR5/Qp16WdYHxQtTFJjWE0lN0bYve5pRNA==","signatures":[{"sig":"MEUCIQCy8O/emJyE6qVO3wn4Jas6UYhxEpPj4FdBuZ5YC1A/8AIgAOM9/bRLQ7BVJkU2LjHAuobjfyYUPOw6pEO8AVFafkM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718132,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgsfWrCRA9TVsSAnZWagAAKS4P/i0EXBLW4xjb+e38/r2+\n28Dkn+m0I3egYqL86AHv9pFTFXmf9INJhib1oVwnLLzE08RU7OFXeNZ5hfpS\nkq0C7O70vnD7Jv7c8Ty0VagQj6c75ZC14tJNaKtcSHsxyxZnbny4KaALvIMx\nU01PxUZYEit8iAucS/9VYpPEmX51FhaZsmjgEsmEpp5qilTJlRU0OjbERntq\nkj4eBmQMeaP0XZDn+AxAKiRlXzUWs1Cplvr6ymobJhTCvY8lQAxOMl0adop6\najiINOAcEdiVcoLizOy2qe8AzaTNYJhShFQBf0F/FmYiYE/P1LtW25n0EJXl\nXMB+ezXRkD7zZzUHfvbQq6p7zy+UAAjcIFn1M196oYUoJXcklH+9qUv4V/5x\nXMZGkamoGQs8NOJA4EVtcYeZjIVMjU2ZkV9h1b+RxmQ5YQWirx56WeuKwew+\nNeUr22GsfXjmxQWy+Grtpj5puXbc8YpDSKqiF534cx/mPM2QfeYGsD7xs5a4\nhVT4L3e0daxzCQOPB0c+Ggr9a9Iiy8kw9fbFomm+V2iCqjxMh494pLqIye6g\nr9mFt37JGC2A7UMqzqXJHsJv4S0J/aSfn9cnXqQD2qw8FKYagLzAZASM4YWl\n5GaXtsKytdw3U0nn3/RmgGa2knDeKbV7T4MDQoVhrGHNlKqSqjISjdDU1GVX\naWcf\r\n=uF7S\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"b3eb2a9553ecd633ba3261e06809dfe28692da29","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint --typescript lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"catch-uncommitted --skip-node-versionbot-changes && npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --typescript --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.35","@balena/jellyfish-logger":"^2.1.88","@balena/jellyfish-metrics":"^1.0.239","@balena/jellyfish-environment":"^4.1.27"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^5.4.2","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.156","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.132"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.3_1622275499165_0.0983386143417786","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.4-renovate-major-external-major-49c1b50b8001c6167144f2a6dc949e4beaeb5a66":{"name":"@balena/jellyfish-core","version":"3.0.4-renovate-major-external-major-49c1b50b8001c6167144f2a6dc949e4beaeb5a66","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.4-renovate-major-external-major-49c1b50b8001c6167144f2a6dc949e4beaeb5a66","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0a8121c2c7763934b6afaf6b2792b94160ac7ea9","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.4-renovate-major-external-major-49c1b50b8001c6167144f2a6dc949e4beaeb5a66.tgz","fileCount":190,"integrity":"sha512-wO8TtSxGSt1iFBwk2JMTHL19Cx0i1o6RDkw+sD2YkjfPEQORvn4hXaVXrFrjD93AJ7JyRPn7JYbWFfB/EQWRYw==","signatures":[{"sig":"MEUCIF5jCPnQtBYmF1r4PBv5DeqbDhpJ3v3HJ3UlUm8HjRIUAiEA+Yv7GPe+aX5i2n0STU5oqOiSOawpgY790568RoP+NQo=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718273,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgsfbmCRA9TVsSAnZWagAAsF4P/0UgAD2irRw9+52aN/7q\n2nrh2TmzgnyQejFQMt2BNtJRz74BGKzlb5n727Zs6vzMEUceACrr8TW4W+Lv\nkufeRRBtRba5Nack3qqjlhRLJQBi2AGHXRFX6sMm86NZAjqWlLNKQ8C/bJwj\nj8dRJRrAoAkHz7yT6OCaiioGixGdpPMlXkdRz0aF22EjoLKAIsNgdGC09yeL\nOQjJSaJn62wX5R9qOnVjqaDJ4VMWOgtAugaD1H70nMcoj2FYR6IDLh+ybQUb\nKw5PTTdbTvgnkwFC8nRQ4+pJtaL+xy0rSCSKxVzERn4dOEBHfpmuH3J4PuUI\nE3+A/wwPjHsPByP0BdtEJI411lpZ+i3GcNoA8YeXn7hRP+Oypj6NPOqO+Yei\nvw0a1upUpdHZ8eGRTSpZlfQy16+3OSCkgH3DOOJPKyKSr4K++t4r1bJk49Bb\nhI+sEGuJ6MGQfo7E4mLspC+cIWUbmkUrhMbRmyf7S5Y1pgN3Mw0vgG20KYAU\nK/o/L8OHtSqDClXaQI/uTEN/pRWIYTaMpeP/+Fq8dTdLFIIc0l+SwlyYGLYj\nqkjqPT5I93RnRNqSUM+Z9CUzOsT0dl+/jToo/YZ12zV1vjbysSEKuOrwz35w\nj9EWN8XRTDasJHsEkxjHfGEx/iHg3apNjxFPIT02AfhkH2g0nxZMOvaGfIQ9\nIul6\r\n=bfIa\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.yml up --build\n```\n\nThe tests can then be run from your host with:\n\n```\nLOGLEVEL=warn POSTGRES_USER=docker POSTGRES_PASSWORD=docker make test-integration\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"49c1b50b8001c6167144f2a6dc949e4beaeb5a66","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint --typescript lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"catch-uncommitted --skip-node-versionbot-changes && npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --typescript --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.35","@balena/jellyfish-logger":"^2.1.88","@balena/jellyfish-metrics":"^1.0.239","@balena/jellyfish-environment":"^4.1.27"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.1","deplint":"^1.1.3","ts-jest":"^27.0.1","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^5.4.2","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.156","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.5.132"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.4-renovate-major-external-major-49c1b50b8001c6167144f2a6dc949e4beaeb5a66_1622275813659_0.7511160261138909","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.4-renovate-internal-patch-ae76a04aabcd5f1ba62b31278c0008410d6815af":{"name":"@balena/jellyfish-core","version":"3.0.4-renovate-internal-patch-ae76a04aabcd5f1ba62b31278c0008410d6815af","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.4-renovate-internal-patch-ae76a04aabcd5f1ba62b31278c0008410d6815af","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"df7caa6f22da38484191e48850ac504f80b49639","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.4-renovate-internal-patch-ae76a04aabcd5f1ba62b31278c0008410d6815af.tgz","fileCount":190,"integrity":"sha512-kSEZp99OBthv3J3XwB6YxavrJm/lZhJtzjyHUfTWc8yP1WPV2GtWFmLJ7YVPEDhv/So4/UUmVgYXleobN6g+PQ==","signatures":[{"sig":"MEUCIQDrz2oJAc7HOg4I8uknLcXz2/aSwqQdpijmqyLer4P8YQIgQ+NMIB+8iPMdxEJ+orJF6rPbHTMvY0SNakR9RgXpOT4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718267,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgshEjCRA9TVsSAnZWagAAxA4P/3CTt2Xe7NCLmH5Q3LHb\nKttK90y+llN9m1vTeq78MfKBfzkQGcm4Cyl3HVgi5m/4oV2llSIaN1peGGNS\n7SaFc45ThdrxfViJNzgb6cr0DTyulAlWHf+2VwVeKviBgIS6LtHMqgcHDM/9\nr8cDOrY3nScvyiiRMxd+6I2/1WQgtsdL+VrqBlgV7xxphUUhAgtioP9Yl2m2\nAtTwOpu0pznZlKc0xVxKDGYtx8fj91Yiz5Aj44DEFpiMBpMVEi2dVBvhZQwc\nDNK5SZfHT09WH1BGR70zdg9fyRqHcdOMq3dutedSHLsz4p8kKh9LuUwbdwUR\nyeyl4lt/hx1Ac4V5hfti185Iirxa5+u31fh9jy1GEiP3pkG2C2gwC0maC6Yy\n8dt3OQBSoGYLBCrzNJ/cLvJguVqZfJOaPH6bojoT+pnmH3seJcxlNzlGRXml\nFZmfD9E7Sh/tMLLhezNn/ADU30aifpQDGLfVAGhXjC3EyTXfsGat/aywsFaU\nEXGfPAgPdBD2ZFEHziU3+8HPSQNDPqmA7/K1B3FCF1oC805wIN2pqs8rEXfH\nFhrseIoCTeSldvcaGYIHNaHVp2G7vrki4txjuSSUcT62ZNKeLw8+iArFAXCp\n8F6+/2+mjLhGm0AdEeUfQhLCTI4ZagYQnkL1q6pT/6eCL1u7xmuHMbvHj8TF\n2D/Q\r\n=SUnx\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.yml up --build\n```\n\nThe tests can then be run from your host with:\n\n```\nLOGLEVEL=warn POSTGRES_USER=docker POSTGRES_PASSWORD=docker make test-integration\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"ae76a04aabcd5f1ba62b31278c0008410d6815af","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint --typescript lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"catch-uncommitted --skip-node-versionbot-changes && npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --typescript --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.35","@balena/jellyfish-logger":"^2.1.90","@balena/jellyfish-metrics":"^1.0.241","@balena/jellyfish-environment":"^4.1.27"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^5.4.2","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.156","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.132"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.4-renovate-internal-patch-ae76a04aabcd5f1ba62b31278c0008410d6815af_1622282530341_0.21529241713760539","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.4-renovate-internal-patch-1740d29b676c6edc76d4e8ae8ce7cf006bca889e":{"name":"@balena/jellyfish-core","version":"3.0.4-renovate-internal-patch-1740d29b676c6edc76d4e8ae8ce7cf006bca889e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.4-renovate-internal-patch-1740d29b676c6edc76d4e8ae8ce7cf006bca889e","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d274c455f62795b5a85e0145e3399ce7afd7602d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.4-renovate-internal-patch-1740d29b676c6edc76d4e8ae8ce7cf006bca889e.tgz","fileCount":190,"integrity":"sha512-9bPqTPXSRyJt12emLj92J3sk99swGJ02wLS8CcSrHikG2k8XVSksaS+etJFFQ0tZ0pFDuJ61xf/kZ9xuYeRcVw==","signatures":[{"sig":"MEUCIQC0zH/RfIIkKEVhagAJNvB5ogMNvoJkVg6L5UpugjVMXQIgWkVQftXFE9jQCKPqJYrmcbUTqpG/sjRU2AaTPXig34M=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718267,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgskfzCRA9TVsSAnZWagAAEVcP/iSM6RzfiUtXZW519Jz7\nzWRtwGODsjQNiy27sOo8cztWLqd4tmp0YDw8Xw9nA6MluAayfvMJ1jrgdDGS\nBpJqh9B4nvljvT7+yuUjrIGjXaO8uBQASlDmH7PuGQIuWhv9LgNk28ZgSKmY\no+anHT7sx5ODbyhZAfKynFQOgjSCjEIidT3DjF6P55C1ydirbk3mLc2wHs2R\nd4ewz/1LPBrquCRDj0Qxv4BxOY8Ub6HtX0e3Uj+zzY/H60ugQlHZhhZcDpgH\nhi+hHsaYbxCuR9ua3qDlKRqkZ/Tj2lwBmXWIQ/8t9qwpuDkyNJtO39qjPHLT\n+o0B7QL5nUrZ1Frxtdj3dT/sx1Hgnb5EVuJydFSzcIMekEI3ECwx/Jlo1uwn\nXuOWl22v8o4cWC7ncdXh8MbUIr6liDZ+8WSWFbBtdGZYJxkYeMJUtQuaa7f4\nPMQadPMDw+sRYd/2NuQoO3WLIxDtBU5J5/+WQC+UUa91tpXJddSYhMqlqE4k\nOEOm9dW4revuul7WGD9Vy1cgEYL+OYCFlv5cZMQtoT37kUzeQ1y0qc8VMjMk\nBW+URswj3J5/6zWbEJ+JjYV35MDDLoZSHrApIbwkzBSun37tvsUy6bffan8g\n4tpTKuH909dHWg6l7uV2pXrn2MXDBbvzziowWMCPY+lddtlQpYE9xCGKojCY\nQ6rG\r\n=g5MO\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.yml up --build\n```\n\nThe tests can then be run from your host with:\n\n```\nLOGLEVEL=warn POSTGRES_USER=docker POSTGRES_PASSWORD=docker make test-integration\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"1740d29b676c6edc76d4e8ae8ce7cf006bca889e","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint --typescript lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"catch-uncommitted --skip-node-versionbot-changes && npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --typescript --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.36","@balena/jellyfish-logger":"^2.1.90","@balena/jellyfish-metrics":"^1.0.243","@balena/jellyfish-environment":"^4.1.28"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^5.4.2","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.156","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.132"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.4-renovate-internal-patch-1740d29b676c6edc76d4e8ae8ce7cf006bca889e_1622296562807_0.13706787236507156","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.4-renovate-major-external-major-1be9a439e8d83f91eb017339bb5282b677f90152":{"name":"@balena/jellyfish-core","version":"3.0.4-renovate-major-external-major-1be9a439e8d83f91eb017339bb5282b677f90152","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.4-renovate-major-external-major-1be9a439e8d83f91eb017339bb5282b677f90152","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6c4669f14d8b9b64a400ed39b168ba5fbc0de977","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.4-renovate-major-external-major-1be9a439e8d83f91eb017339bb5282b677f90152.tgz","fileCount":190,"integrity":"sha512-gDrWGML0vprnO9U9uKywqqLTFQGwHdJ07VxKIOsoCuUpuXdsod5cBifjyyC44BoayZqMwCcl/QCT1U4yRdXmcg==","signatures":[{"sig":"MEYCIQCXEgbGtx0w0BNgVVcnLBKWd1vKS2jfAlJD1PvMiWFeKwIhAOtXzAugkfOXqpSBaUfnJTc/owKXHFGe6VuU+e4fUYgf","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718273,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgskgjCRA9TVsSAnZWagAAJzAP/R7XOmJYuNTs+1VTLGl8\no7jXRN6F62yUtAv6GjQ/6nAcCe7MaflFkES77SKfDczCyWhvGaWWZ88QXlCj\nvQmtaM8mG6L+2hlztpwY/bKeewIVXYJxwL2RlakUll9AVGsis1/Wz0diHqao\n2LRrDjJbArbSV0ty29iuSl/Vv0eA49r+UYH+OUNtCayHKs9BKpKPDehvivpI\nHgBjIkoMBth/ArvRXf5YVABTvDy7k0vHnTlrGmJMH10ZSzKmvtkS8Bk7PShw\nAJQN2YXWImmskfTMiKz9jSG4bAzRF2F6tj+o9Sx6HXM32rfo35Fxmqbh3Nqk\nH4s1TX39AkEjZyN49LO32EWykB8KXG8qLbRkTMDIo6BVosDoPOFUfyQCJ6gh\nn8szG3Kd6GLWYGjjrgQxsXByw6iCeRRQWNSXy7Tijli0zCCQO0qR3x5jaPTE\nhdbaBVYGtffUqHI39WsrwekwBGNx5KIlc1oam27040aeuU93KAEqyupWbtIM\nppvlQyz5uZ6iIR/wE3sFKRmG3LI8lpChkMB0jSkYuLtOBAZPkiVArvhTn5Tj\n9nvP2Y0PgzPDxunHt9JJBYRNPKat4X2sIziXisAa7JKOwfxs/nZpTI9tej7H\nn0nlExYyc/51HKHd4w9W8fkolJd8TXZInRZCxURVgRxpYN3sy/ff5iRuYjIR\nULXt\r\n=ihQr\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.yml up --build\n```\n\nThe tests can then be run from your host with:\n\n```\nLOGLEVEL=warn POSTGRES_USER=docker POSTGRES_PASSWORD=docker make test-integration\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"1be9a439e8d83f91eb017339bb5282b677f90152","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint --typescript lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"catch-uncommitted --skip-node-versionbot-changes && npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --typescript --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.35","@balena/jellyfish-logger":"^2.1.88","@balena/jellyfish-metrics":"^1.0.239","@balena/jellyfish-environment":"^4.1.27"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.2","deplint":"^1.1.3","ts-jest":"^27.0.1","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^5.4.2","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.156","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.5.132"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.4-renovate-major-external-major-1be9a439e8d83f91eb017339bb5282b677f90152_1622296610531_0.9679822729019685","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.4-renovate-internal-patch-5be2f148a82223c88c2ace52e2b6913b4f71f3a7":{"name":"@balena/jellyfish-core","version":"3.0.4-renovate-internal-patch-5be2f148a82223c88c2ace52e2b6913b4f71f3a7","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.4-renovate-internal-patch-5be2f148a82223c88c2ace52e2b6913b4f71f3a7","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"bdb3ff5b0b9dcec6a153e38bd5265c3e989234c6","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.4-renovate-internal-patch-5be2f148a82223c88c2ace52e2b6913b4f71f3a7.tgz","fileCount":190,"integrity":"sha512-IkJlt12m7yOoA9lLJwpYL2neT+jIvVKqnNiaKcn7mB60RQ2TEMIZqVl3gbxz6KHmPt0JBI51Ld22m/IB6ox3rg==","signatures":[{"sig":"MEUCIF6picnB1VYhOymKfH3bgjYKT7ukeMjh9V5v9POvMMPtAiEA5aVrcJ+gtpou3N3KZMAv+DZyY0PbkAh5JiG/JqFEBoY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718267,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgslJrCRA9TVsSAnZWagAA4NoP/0yrEcyot4gvlrcvMVxW\nY6zcrEnRfrZ2GVz8t5xbHHQq3k50E8kbW+8b+8w+mxuRcQlIUlo9MQHRl8rg\nB00vlpFIrdbRne0579tHhc0T1UhhbO2bEVOrb6QYrj4GgQh75L76hETFiUc2\nD9ExzP3e+pRi6kSlczgmYV//KIpCFLD6duen4ws6fa5a8d+MHWbmx1vBugOv\nEcVGYrGP0DXIjaapOR3L065tZZMbF8iX1UczYzbH2Q3DXLJP2/KIGp+QwTlO\nxnvLLZlsV7xe+LFtnlac46jjjrJf3dCVYHgWpDewkTi+HnRUrzEM5OKwfLlq\n8kQ6xjpRV36lhI0p/ChDC+fDhl/XWIMkT766lA8rGujmdqbpbn0iyDDg1pYo\nqhTqjUYphiV8lFt5J8HbS2JFlLscNhzsh3gQJWnnONvKBO0rXLR68ZaVf0Vl\nCWEfYou4Lyh6IMWhmBWpGL6+JJGdl7av7+L1VtXuI1BzfkgJ0zpClhocC0tx\ncD03Jwc30fH9C7JTY76hXaNkJcZC8LG/6LYjWbLvi0wyAG0uswL8MwVSXsw8\nGvaa4dPxk7zwEfHVUiGX8dF4aRR+7B+6PKXFgixI+j1iRGGCPEZ7fVtf4qyL\nyZZ3R/v+6tA7Z261XWkIv35Xl/1YvKVm7A6Hfx4OmmGFAS06rRVJ0duGdAmI\nygaO\r\n=Z/zR\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.yml up --build\n```\n\nThe tests can then be run from your host with:\n\n```\nLOGLEVEL=warn POSTGRES_USER=docker POSTGRES_PASSWORD=docker make test-integration\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"5be2f148a82223c88c2ace52e2b6913b4f71f3a7","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint --typescript lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"catch-uncommitted --skip-node-versionbot-changes && npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --typescript --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.36","@balena/jellyfish-logger":"^2.1.92","@balena/jellyfish-metrics":"^1.0.244","@balena/jellyfish-environment":"^4.1.28"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^5.4.2","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.156","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.132"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.4-renovate-internal-patch-5be2f148a82223c88c2ace52e2b6913b4f71f3a7_1622299242532_0.029829488921121472","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.4":{"name":"@balena/jellyfish-core","version":"3.0.4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.4","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"817279c73276b5e41e74cf3281a0418587a5d8d2","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.4.tgz","fileCount":190,"integrity":"sha512-gMV1+rXN0w7QQcgrzuzQSxu+xbdvQ0IXWJATyA7i43ZvC+W1tLZlmfq9xcVw2vGCsoBBkQGGEZ4PVuk5nmyEfw==","signatures":[{"sig":"MEQCIFE2q10BD7knZJkgNOrBsp2l/w6FRrt0O4SFRAOiyAW2AiAnjwwoceW1TEj/uDqSxLigvZ3Z8nYezAnS9Ns5/8L7OQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718202,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgslOsCRA9TVsSAnZWagAACa8P/07lHqeB9TqQBF8ISBsY\ngChQBwK8UlyFykgff3iWVNwOTwhQEt49y8VXIwZXdbSnHa7qQwUwMBUUcS2E\noShENSbkp66cJNW/pje/rX8EVCMoitp7YVelNOESalI8sCYmd0ynnk4lOoK5\nFxyfZLb/Sbs5yldQ26+eJ7c8LFm5ZqIfRJbJ+g8U2FkgY8QstOoTlP0Z4C0j\nI0q6qdkyIWOeLBq4OAnhZuvHNSr5lMXmhX1AfnbrlcOehDrlIzQCQoTNnpJa\nuXlhTQq4qMe3ZEVVI5BHKYklI2z+1zG29Ik/g4qngyiTllfemHFsbB9OIuoV\n0cD95CtbI1oygwOsqaqw9WwEQidtWnIB/oMBkeLkaAiqS4+M6LRhhmr2FYAN\niH53QTSzIlFAx+vhE2YCCr4cWcl1laYxI0TxdTIowfNgGw0wDnq6HX/RWzwd\n/rjXzgvBaqNTC7+3BCkDhab54rNHIKfEa5C2GKUo8qEYj/v6FdXpF8etWt59\nJzCzBQYNRMIIHfSVp9Knmt/KqyAoVkgUVzov2b3pHPQ1DQ4vrvVWF6p6+oGx\nW4VI0FZtL/j4onQVWtDUTlDspqzFQr5rsQU4OxdoDeUU15kCFGfE3NzAppt5\n8Tck9yotHKoYc2Yf1glv9/5SIWRNhVfp8eRl2yWP0+sxTIJb924bcJ5JGNfM\nNM9u\r\n=YeCu\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"c05046fb3cf0706c1664b11c1be6432d8f83992f","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint --typescript lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"catch-uncommitted --skip-node-versionbot-changes && npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --typescript --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.36","@balena/jellyfish-logger":"^2.1.92","@balena/jellyfish-metrics":"^1.0.244","@balena/jellyfish-environment":"^4.1.28"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^5.4.2","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.156","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.132"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.4_1622299564282_0.11866730966535521","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.5-renovate-major-external-major-52437616f683e1b8b78740a5d28193d8ce634998":{"name":"@balena/jellyfish-core","version":"3.0.5-renovate-major-external-major-52437616f683e1b8b78740a5d28193d8ce634998","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.5-renovate-major-external-major-52437616f683e1b8b78740a5d28193d8ce634998","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ba273751ccda74e69a8004be9bb66a6dcf736a14","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.5-renovate-major-external-major-52437616f683e1b8b78740a5d28193d8ce634998.tgz","fileCount":190,"integrity":"sha512-62Yo5ckBrzC5+zMAVbFDg4MQ7cEW+CYtHVr0ug7Py5Fo8LhDChaxNUgIcUArL1nyrwbEE8xcIuuHu8qDDimD1w==","signatures":[{"sig":"MEUCIF9m++3aJ3y0dAGoxB1pe7VRs3PKRV5k94jridx6y7JaAiEAkV53jCPX7M1p9EHvo9U3fJ4ivY1S9jDBveUDE1m+zOo=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718343,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgslUyCRA9TVsSAnZWagAARaMP/iBlexEtxelIRqjIbbY8\nVHlqKoem08Bu29V/oVfVGHeH0PBeotA8y+vEeLk5tP8qSz9XfIje4x6HUPOo\nOJquGavzFeYXcaYxqzAzW3TLBi7Qo0kxUFvT34Lvbx/1ItNrm7sO7Tn1eSOj\nVSNAd1eEBE4x+rAdEEUcfPEa0IuHtDVOh5ABIZkEv/Ubda+HN3lKxcSIFoQW\nBpbmJNdjDTzLymxZNCVRB698yAK+2QIpQ+FeKf+qbX01YGZgrtRn+YAyWVnp\npNZ4Ptp65zIZD5gVgsLAms4vh+IZe2C8XDkfv13metqQv54LT3FL8EfqEZSj\nif7owd7M1woMGgsPgMSNjaAc2tWf5sP7RCodxSWgxbs+3+pDsr3d9iYNpIPD\nUIk4v9eC4ed9bX+3e4KeQAeU1ZqO0bFHcGUw+xTkRKPIMtDuofi0j+Llttju\no/WP8JTOEd/7DwQmzulDURKvOAjIi6FcQhd2Okr7k/++h9/oggCB6UEnFg4f\n4mb77s0qVNcXXDBJpd/6Nn4/jHzNsfNuUXJhF79cRju5nz+tgRO+/0x7ZFMJ\nOOuFsCRfawknmgfZrp+EckISElHbpN/xRXroU7dNXjdkZ9zGIGX/A3HwjaeI\nBXJklIDt9tz+Mi/lppglYbKp9X+XX3sSlDPFuNuvMMufbjGAWCR6RH+X00Xt\njUVd\r\n=dNV3\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.yml up --build\n```\n\nThe tests can then be run from your host with:\n\n```\nLOGLEVEL=warn POSTGRES_USER=docker POSTGRES_PASSWORD=docker make test-integration\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"52437616f683e1b8b78740a5d28193d8ce634998","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint --typescript lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"catch-uncommitted --skip-node-versionbot-changes && npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --typescript --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.36","@balena/jellyfish-logger":"^2.1.92","@balena/jellyfish-metrics":"^1.0.244","@balena/jellyfish-environment":"^4.1.28"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.2","deplint":"^1.1.3","ts-jest":"^27.0.1","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^5.4.2","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.156","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.5.132"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.5-renovate-major-external-major-52437616f683e1b8b78740a5d28193d8ce634998_1622299954023_0.7565936122576304","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.5-renovate-major-external-major-da372a749c227cebd5cfb69de1537f2cdef86762":{"name":"@balena/jellyfish-core","version":"3.0.5-renovate-major-external-major-da372a749c227cebd5cfb69de1537f2cdef86762","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.5-renovate-major-external-major-da372a749c227cebd5cfb69de1537f2cdef86762","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0bace06ea246dbd8c450354f2ee403c5f5544947","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.5-renovate-major-external-major-da372a749c227cebd5cfb69de1537f2cdef86762.tgz","fileCount":190,"integrity":"sha512-mRZXo3Cr6HIrhaJcMs29nNeCmcv4PFqHg4YR2A0biUe8//2ytIRBy6Jx1dpKZr/OT9yrlA73oR50B4gAHer6gw==","signatures":[{"sig":"MEUCIQDyKf+gqa9xbNqp702tnjmz5DdobX3cQqhOisQxa5E1BgIgbmco3Y90mfWroWw4Fnq/cC+1lP626qW0/Th1OCdoozw=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718343,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgsoiOCRA9TVsSAnZWagAAgFEP/jzRR7OM4ojIeGnEjo0W\nVD+M1V1lulK6cmznGaAwT7rZXe/kBpRu6GHhIBuMHuSJGkTIdvkcqPyxalBT\nNnvN7JUqkJLRYYhZsK9Uv1Ss05hqi0MOkx0/f27BndGsydnq87f4cil2MaUU\noAOvPly7/pK0ORFwUk7uIwDlTVueulmZdqiZlO9ORi4r+TrgiQNYBBVwPZEY\nCW9yDimGOPjF7chgY2/Q7U9VIgDG0rZWKpRFPRgDQt3gEyNAcFb022TJV0rl\n7+9IUL81GyvQmsP0F3ruCybfTs/x570avVeV529qrQO1kgFQrnyPbBVuj+6+\n3qPLxhBAcWd6f1dwCx2PIuM+Q2qCDF7O875gqu2kxWnDVYPxNaY6LYjGrnWB\ntS3XhvataJ51QUAj318RRTiBIJ7lgb3J5Uy21SCjIjtQH594UwDAkkVu/gMN\nCGa6Z/V/4/ELsK5LZQBY2RdumlN59bKQpUZ5qgfWb7YH6Q/ZQGmXMV2iYODl\nNzStOlFbzYRZIbFWQnNIPpmA/GVGos3rMRmcMklwK1jJMcnhV7MDW5b9ANAD\n+LMpQO3TSBoJgWycqbq24ddd/gUnuHdABZeLNdGPTQeTMF9r7ZLflMvwkAOY\nCdBoyggScaJAB9gQ0XfzGhvdtM7toN3Rbj5DsGZ9zrQp+E+uHi31LOlD0WnI\ndPyb\r\n=NdxH\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.yml up --build\n```\n\nThe tests can then be run from your host with:\n\n```\nLOGLEVEL=warn POSTGRES_USER=docker POSTGRES_PASSWORD=docker make test-integration\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"da372a749c227cebd5cfb69de1537f2cdef86762","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint --typescript lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"catch-uncommitted --skip-node-versionbot-changes && npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --typescript --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.36","@balena/jellyfish-logger":"^2.1.92","@balena/jellyfish-metrics":"^1.0.244","@balena/jellyfish-environment":"^4.1.28"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.3","deplint":"^1.1.3","ts-jest":"^27.0.1","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^5.4.2","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.156","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.5.132"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.5-renovate-major-external-major-da372a749c227cebd5cfb69de1537f2cdef86762_1622313101553_0.3451043316701097","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.5-renovate-internal-patch-d6f45eabba58a1610c9407e456db241009a134ba":{"name":"@balena/jellyfish-core","version":"3.0.5-renovate-internal-patch-d6f45eabba58a1610c9407e456db241009a134ba","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.5-renovate-internal-patch-d6f45eabba58a1610c9407e456db241009a134ba","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e1c9db5712e0b46edf4b205898c3ae49459801b2","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.5-renovate-internal-patch-d6f45eabba58a1610c9407e456db241009a134ba.tgz","fileCount":190,"integrity":"sha512-kYgeQBhd3NmtVa66PbrJCc4JcSnP+NPoJUeRf4FxGRp6xIUXagpiqxqv3wk9Op1nfkHcsGD8grBCUJ/+nyr/8A==","signatures":[{"sig":"MEUCIQDlZ5oe6E2obuxm1MtTiRmJXJFF6PfHubifGgPCoGEFTgIgQAc21yzzJEQnqvRtOvFKK/AGu2xhDC0NfOy1LIZLO0s=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718337,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgsp/GCRA9TVsSAnZWagAArh8P/3+CpSirtMCNnLpZFTzC\nW5W6hTeVynBiVb3LFzNoxtD1ZrReFqusFBPg38b6JnC+2uqcJjxmHAFm2Ojm\nNOspgT/PQEa2Ti6g6tRjAL3pPgLj6ikTy/wOdcLo3FjoNvz3eDX9Ar4DHZ+9\nR0uOlnHu+WPsTZfR+lTIu5E1XAxbypEhNmIHHC1bU3eioX1lX8IKJ1ZMe+9A\nAcu4C0RcNDtN2lMbXTlDN1LtqPybctEmyY7Yn33bDeaRoiOss6Qz6VjBQ5o6\nVm8AQi7C4myb2AKnTCSgsC8BxjqCrBbc0XzPFrZ3DMjXl1zLt4KpxEgtJMOY\nVUnNsowh6lfLH2OEYHQZ/mUPYiP9+s0rzF5F11ChVeSd62KlaKwGWGUGRul2\nnHs1EXMCwqcx5W1Zm464pvSgHC3SlN76NNAdkws0HoIWiKufMni1tAZpHFDc\nJnSRvGGx6YhXo9HdQl9BGHjLyboBSXd1xTPG659J2OPlvPVAmfOMDeFwPVIa\nAIpRHl0vQ4zOLW4L2V4Hy8JO+m7PZ+qqr7jRvI2ykHBksom2kCGydl5cnofW\ndFTM5kqMQdsS6a5636yQrdYPSjo4vEvRc0J/k3q4xBZZp4MqutyvCGt0I3XS\nYlN/aEIpyEHSWj574bczpTTDcoRFs4TCMBYc1bkLt6vOFcVbi1IkYvaZBTVi\nTxLw\r\n=G3R4\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.yml up --build\n```\n\nThe tests can then be run from your host with:\n\n```\nLOGLEVEL=warn POSTGRES_USER=docker POSTGRES_PASSWORD=docker make test-integration\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"d6f45eabba58a1610c9407e456db241009a134ba","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint --typescript lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"catch-uncommitted --skip-node-versionbot-changes && npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --typescript --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.37","@balena/jellyfish-logger":"^2.1.93","@balena/jellyfish-metrics":"^1.0.245","@balena/jellyfish-environment":"^4.1.29"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^5.4.2","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.156","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.134"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.5-renovate-internal-patch-d6f45eabba58a1610c9407e456db241009a134ba_1622319045591_0.048800441138736605","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.5":{"name":"@balena/jellyfish-core","version":"3.0.5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.5","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"812987da4f0d70e1e77525bb126baa97ecf54769","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.5.tgz","fileCount":190,"integrity":"sha512-Ls6ngx+KSc/7BcI4GOoRe9mS3M+Ax3Vn6F9gPmDSxIkUqmct3kcmHLoh4GTVNy5YuxPgi3DE1NREfWUxz2WkGw==","signatures":[{"sig":"MEQCIBiMoLyNKU7c1zA2IPXnBtZO1+69sTS6nu/piS8Ayg9rAiAkX4r/2NsSeZouzKsEfKeEZ2YgheyfPtbGl9MN2/Pnag==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718272,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgsqE7CRA9TVsSAnZWagAAr4kP/3/3dqnh0CmJ5p3VFgpP\nxyuanOOSRGwYPGhAny0m2+7mXW30aFlKV2LilrJMej/vT/nN+G80BABHjWpk\n52gkrblV2VyP0V3+86BlYbhu6+4Y842H/37XpJPtvA22SRj90eYwGT7bdx6t\n3CiGxhcz+P4xZlQ4fdFbvk/DC0TEnMIyh3uS3ftlP1ZW6ntFGTrIFx5pFtES\ne/QIJhREQIQgP45yJOvUOsRgw+s0pYRi3fPi1mHEO+X9W/EB6UF35ZLJi4qL\n/tMBFVluockGb9qG0bCks6bDD7l0YVO25Orbd0QHnQbHtuan5Xah3PJYlcG8\nwF7AH6ZENRQa+lFN+O6t4zOn6FF0zUzNMdiDbrWlR1Xnd9VEMRhMoRi7AGTI\nUh8FssQ77MysVLIIuJfJ42H18kerHL8rt9PzRgd8xpalzqwDsa2Bg+r6KDMn\nsO2qXK/d5K4hJt6EdyRUNC6O0IWQWNyQVWg5CB8DbISbZdVFtmqwrHq2m8I1\nlhPBH4AqG/ThWJffNdEd8SuYijIuCKHBu2H7voxCWXASiyu/IYLiAwxXKMtv\nzb7iEgoqmTX2wF8/hSI5+0vZAGGdku9XKQI/uY+dgJM8jVvvWXmhf7kJlMAO\nf43/ZfMP3TdxIxcrODXEn//uRQJvDFLkFtzanu1MXHdW6sj/YKeh3Jgq2LaA\nKG5v\r\n=YByK\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"e78f9f49839f5356ee2d2d54bfe99dd5da2bed07","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint --typescript lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"catch-uncommitted --skip-node-versionbot-changes && npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --typescript --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.37","@balena/jellyfish-logger":"^2.1.93","@balena/jellyfish-metrics":"^1.0.245","@balena/jellyfish-environment":"^4.1.29"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^5.4.2","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.156","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.134"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.5_1622319418640_0.8924564769455487","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.6-renovate-major-external-major-b6f579bc3cf4131f4601678bff56b329a55c7460":{"name":"@balena/jellyfish-core","version":"3.0.6-renovate-major-external-major-b6f579bc3cf4131f4601678bff56b329a55c7460","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.6-renovate-major-external-major-b6f579bc3cf4131f4601678bff56b329a55c7460","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ef0bbab78a06f1a2a8890597f31a2835b7890fba","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.6-renovate-major-external-major-b6f579bc3cf4131f4601678bff56b329a55c7460.tgz","fileCount":190,"integrity":"sha512-CvoNcnPbdTTl2wPxK5xi1sp2vAUtGaVfIoFiIdSBV6rHawzxVXm74rTpKlcR3rs4AwhtmieB6S7DHgGlo3YhnA==","signatures":[{"sig":"MEUCIA3LG3aPopKP0JV8PrIMDIi3NRSpPoUZz66J2qKkol1yAiEAy62FxPYv+t24pwgPuDtIYtMAHSK6YeZkmWODDaHoeEo=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718413,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgsqKaCRA9TVsSAnZWagAANyoP/0uAFQQq4ieVZcWcmTkk\nbAB92koK0vs8frbq87V2JM3+ewLFESiHuMU1NaDWLwAW70tUmtxPl0f0rglX\nyG1M5mv/7Dz9tpf/+abWOGlN/S7CEAihNJPd3TGUzHrLInuwz7YxgUHF8Dvm\nboirGzybnlpq/VVHWzN54EIWhYlYpGPvHcahZNFWDOynsHb7yLyLVdGoyR8H\neO9cTfoXtHihsfL9uYvBhgfGqTudgCwtJr7/kzvMMs+Ajp0ekjpHGV1bDDoU\nmcQqbmbf3YDOcGB8HyeoMoio3rAzNofDYXzYyYpRms0diyEx4qxfJ/D1RstB\nezaJHiBLLBJRi+Rs6axmKefqe1KWYZeD13L1CCfWMh2eEbeBQ1aaVhJAoNeE\nJPJGIbyO02qI73wBOj0eSbwZxG7gWgF06bsyLrRicgZMkVNxTjKBKjT74WIU\nLShxLqFJrDLIeq9xK8YPEq7QpIcGbKvPrfGzYINfLhJyIDw18HvfUmdipiI3\n6QqdgJGfFJoYct499iq7yBRMF5i7+bqQZ7fAxvg54x7Hd1oD5NvUU2eFt7IS\nIe900Hzmv1Uh6TzQiKOUCreD2UXRmc+LJP7jwAwe9nR5VSR7PWnmUv+1up3A\nDG1C+l3p2TcgQs8TuaLne5CFJfQ3BhIPq9mLULZOKlnYMNTJkeaI5Hf7o/we\nbTYg\r\n=eBe3\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.yml up --build\n```\n\nThe tests can then be run from your host with:\n\n```\nLOGLEVEL=warn POSTGRES_USER=docker POSTGRES_PASSWORD=docker make test-integration\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"b6f579bc3cf4131f4601678bff56b329a55c7460","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint --typescript lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"catch-uncommitted --skip-node-versionbot-changes && npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --typescript --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.37","@balena/jellyfish-logger":"^2.1.93","@balena/jellyfish-metrics":"^1.0.245","@balena/jellyfish-environment":"^4.1.29"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.3","deplint":"^1.1.3","ts-jest":"^27.0.1","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^5.4.2","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.156","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.5.134"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.6-renovate-major-external-major-b6f579bc3cf4131f4601678bff56b329a55c7460_1622319766682_0.3445098301603662","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.6-renovate-internal-patch-e93685f11c71b544045ba730c675d2572d0bbbf8":{"name":"@balena/jellyfish-core","version":"3.0.6-renovate-internal-patch-e93685f11c71b544045ba730c675d2572d0bbbf8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.6-renovate-internal-patch-e93685f11c71b544045ba730c675d2572d0bbbf8","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0011d88feaecb37b87c84dc003e8689a5febf191","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.6-renovate-internal-patch-e93685f11c71b544045ba730c675d2572d0bbbf8.tgz","fileCount":190,"integrity":"sha512-SdkpJU13anBL/y2VvmR9mcWYDZ0WYfyzY+d/c96o0PhuNq+rIOD+AZn3yYeHu6H6RJ+lJHXd5jDHLBMKYtM5cw==","signatures":[{"sig":"MEUCIAmSV+3b2YgY6+HFgWRtl68b40n83IqjxP79vSvWt68bAiEAvA1jQGaSVs/mygZIA4W4cAx6ps+vUtve61aYWM1SnG0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718439,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgsqM0CRA9TVsSAnZWagAAfmQP/2ti5dcy3FL8x4gT59R7\nFfd4cZ4h29yAqNWD8jM0RQia2MbRDn8Ln5KnCs9RHZUupNLr2RAcyb/ba8wM\nr9EuKngZshIdg5O2RZ4fabH+MBVRRyisPrvXI1wqt7PV5EDvx75PsgE0977r\n00DHObAd7ZQCQ+kbdYB9Wb7w4vqbjoeDZU/LSbpI7hAIdZL5kR51YzhflCyE\nz8Iu/UZAUwlF1bBBxI0/Fm1bOR1yylu8esjwQI5dGKgcFWQWVi+subCTpiTr\nEnNu9q25hDrCzIUhpBY2TAktJw34GnZn3rEGI+jInuVAISWAG8/Coof0Eb+W\nskPTiFbuTln10rB1LHbNDRdBLittdetYpE848wYJeR52sr2iZWQW+cm14JUA\nv8s86yB6pZA9h74UY19H7FxDQ55wCblTcTKjeXhdPZk0UAU+4oshc1zCgczl\n9eoTlGm9E2/4PCF/Vtt2TO9SvF9+DoJEhSsqYNVbIRvbexJAa9thl9xDcb62\nr8jxt65smKATyxQKTGGK2jnmNStRq9yRq3Ekfzw4X3NGMUZOsZKj/hlzGLms\nT1JYdj3Www/oqimxw3RBgD6fuxjYyagchShY/w3Kp6O/ow5BDp+OHhK9rnkP\nHlYJaIFdi+qyz5SQSmIGAESfoiy7ahUdkl+BgxCE4z3AHUM3RbwWBx7R6YDe\nTJOM\r\n=4Oxd\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.yml up --build\n```\n\nThe tests can then be run from your host with:\n\n```\nLOGLEVEL=warn POSTGRES_USER=docker POSTGRES_PASSWORD=docker make test-integration\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"e93685f11c71b544045ba730c675d2572d0bbbf8","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint --typescript lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"catch-uncommitted --skip-node-versionbot-changes && npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --typescript --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.37","@balena/jellyfish-logger":"^2.1.94","@balena/jellyfish-metrics":"^1.0.245","@balena/jellyfish-environment":"^4.1.29"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^5.4.2","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.156","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.134"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.6-renovate-internal-patch-e93685f11c71b544045ba730c675d2572d0bbbf8_1622319924180_0.12964362998918277","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.6":{"name":"@balena/jellyfish-core","version":"3.0.6","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.6","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"77a03f0a7ae2c880882a58ce83ad9f8fbbac5ec9","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.6.tgz","fileCount":190,"integrity":"sha512-eqhM6O1Qc71L9BVpaYoJm04/OAONG8TjNButIOLr5edQZDh2Lc1KqBN5fHyLLp4NWckkW8WnLxhLrUqrzWg5SA==","signatures":[{"sig":"MEYCIQCSGXhEpOxm3ow7iNZAjSoxfwAL1BMjiJce9hERLCB9GAIhAJDXQQ0ZSSZQ+KrCWchVXpvQD0ix95ZEQBagMibMCD+Z","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718374,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgsqRcCRA9TVsSAnZWagAAc4UP/0zNCIv9K5yzTpaZRKHH\nQM27iXXRuxIQJUJpOcZ7CwXFMjK68SA6m5SftGECBMj1h/mgMkqkag6AmZeM\nGFGBnv6A/RLA3wwoLji1ZJkrGArUoSZih8u56BAU5xdMCXjoEoq+R92PDSEI\n7AY/NBEDvlKbpt11xH8dSEJ7qROr6LswCOfHMQ39CWEB+/pSGF0ZhQlJuImh\nYkTqve7Z3AuVx5df3N89eIglg6TGmSFYgirJJve72JAVs6m4CIbw0Q29AZTL\nqbCUGJujAP9R8CW8jI6Rb0db5DnJ9376AaZoaf9H9oZbd90o6uD7v95aApHR\nuOBuPHjn3Xe5DvsBf+OEggxQtBzB2+yBPV7Yk2xKQZCvnvyr3uzjDBCQ2Dj/\n7bUq+ZpMA1bzT+QYIuDy86mgIHhQ2BZbf+HbvTV9HUtP1Bt1S1DBMFH9vkf6\nWqt0YLTqMl1XDOIIHAMHbfcFkQjWIbsW8IJwTSMwCYWThzkV3FjXPxmOUHuN\nWMKTTXsdhEcLnRmskM19tsFbxa4FWle158srREOIYPapfxj5aEfVBwKUDxu+\nmE9nZs7DukDJk9hYW/hrBW2lJZL8+H+EkrlgPPU7chVDvoEWnXIB7xWkGbtI\n6tOw0Oy4a6fumQhXGJedhU+KJVUrl+vU7GhvobAjtFAPot6CDm4pzU+/ZYHj\nhwrS\r\n=pEQG\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"2483b6c1b1bc2a6c89986a4ae1cb072ea219ec46","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint --typescript lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"catch-uncommitted --skip-node-versionbot-changes && npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --typescript --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.37","@balena/jellyfish-logger":"^2.1.94","@balena/jellyfish-metrics":"^1.0.245","@balena/jellyfish-environment":"^4.1.29"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^5.4.2","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.156","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.134"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.6_1622320219995_0.431238203393705","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.7-renovate-internal-patch-e113326a187605f8cc1302bcde42a53f7b910b3f":{"name":"@balena/jellyfish-core","version":"3.0.7-renovate-internal-patch-e113326a187605f8cc1302bcde42a53f7b910b3f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.7-renovate-internal-patch-e113326a187605f8cc1302bcde42a53f7b910b3f","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e37f8c8a68762c45aa9e8dafa9bc11e2fa45fd46","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.7-renovate-internal-patch-e113326a187605f8cc1302bcde42a53f7b910b3f.tgz","fileCount":190,"integrity":"sha512-pq6BAyG5Lf5szFzUWso7ZgEIs1nGA3NGshmgHjSKU4EEU8RbMj2xPobmKF4adyyq/DGMGqGw1v9jL/QQ2By+qw==","signatures":[{"sig":"MEYCIQDNzDqUgJlBFTVlTsQjmuWcQJt+4LZEDITV/G+eT/wYwgIhAO2lfLxnwxRvOlFWu9WBfw1hyJJIfS8sP23sN7vaFkhj","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718509,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgssAECRA9TVsSAnZWagAAA3IP/i0VmKIgTMW0WQ+0wNv+\nCdm8Mq/A5VQPvgKeOLGblPPvGzppPsRo6oUOFTLoUU3ZObUleVMzDSLiY9V/\nIj/W8agTdEb6cYHKCFSUl9Qmh7xDdmB+U9IuWtYK5WaaX4Xpp/Vt1zdaNRTV\nSXOfftmJI5GUiAM5jlb0fy5UeqOTDB6jXArQaFHEY9Z3F2krGF96qTALDIEp\n/5oBmpG9XtcBeGA+UizhaFvkGLGzFl6nIJ3q7HR6kEAS7eHVckPXfZKcEfPr\nLOmb9wIuYKNQhBgbhZXJ6CAe57LI1t6rNQFyU81cpJ67MjYd8e52Xhmy00JY\n/+h6Iz+s2NVmxf5kfq2PhdME6eQ5XDDl6TlPybgicHoQv9XGWLobyXZyFxVS\njB87RRRoQgFEKK9LNHv6xKJ7p1mgOCtaJ5BBYKszfH/uKBxNPovQy0sEeStr\nJ/fUZUx5Pv4EcBQ6u1fahIbyLGm1Ib2KSJ0Pg9yKmGxN+aEJxfWtawhgDazd\n48L6AacD1ji3MoDZpT1CPTdGfqVPnR/NRsh2O/3Qv1tELOsAovm1++5oSuHr\nOrGz+d80PyMKvEASmwvIoVwetL4uzGAM4yGUthl16TG47yqZZP9Rfwh7Xm4b\nSDHLeZgZu0S8se9QB6HE+hY1DGK601QE0TF1lXrNYlt9hDJKBBMtK/+ZP9TC\nDApM\r\n=G6f3\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.yml up --build\n```\n\nThe tests can then be run from your host with:\n\n```\nLOGLEVEL=warn POSTGRES_USER=docker POSTGRES_PASSWORD=docker make test-integration\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"e113326a187605f8cc1302bcde42a53f7b910b3f","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint --typescript lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"catch-uncommitted --skip-node-versionbot-changes && npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --typescript --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.37","@balena/jellyfish-logger":"^2.1.94","@balena/jellyfish-metrics":"^1.0.246","@balena/jellyfish-environment":"^4.1.29"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^5.4.2","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.157","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.134"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.7-renovate-internal-patch-e113326a187605f8cc1302bcde42a53f7b910b3f_1622327299506_0.007628315616050596","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.7":{"name":"@balena/jellyfish-core","version":"3.0.7","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.7","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"cfdd51210720a7cf6e04ba454398e1cb9af35e09","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.7.tgz","fileCount":190,"integrity":"sha512-JTzfs2wcGvbhz3DVBcvOAoBeyERhKrqEuQ+g1aNWhSx7S/2zOkry6APs9XZd6O7NOL0/fRljlKqGS+GttcbVYA==","signatures":[{"sig":"MEUCIAKSq1VvPnWS7hffqqR9HDDUIH9/lZfMT0uTWnPvnTk8AiEAjROzUpnnEInK8LZssaIxcN0NCNE+bYRlygiuYb5TEY8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718444,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgssEYCRA9TVsSAnZWagAAn/QP/iCGkAkC00berapwS/yk\nuAGgZ+oUm4/MbHn2/6hjRbiKqIKpNzVQ131YlDcPkz4Zho3jIV2iHmAxamgm\nRDvRCbE+cqT6uEXMdVINaNTThppflQzwpFU37D6hvBqv3NpoM6NwYBNcV0lC\nnBcdQGFURLLHgZwFhxHo29A5bJ1OCkASTgWDcH6kpfwBM2ypWe/8SUQYR4l0\nqerrPmlmKPji8cfkQi46+l7+Sd0Ja4ILSXJ2MxWfZSi1MiqbBy3vuumqC3gg\nwau7zRa22Cnl5HsoT1wV7nhyv3zzBcqnEPeM5qA9Ab5wZOL+pjZ71gFApCoq\nEllkEK5ZheFN1dH0YcGTckFdgNBUJx6P0brhCgLygitG99sXePopQ0LZrmj9\niU4pejemqK/ZFtOJdGBqCXlMPzBIZS83fyfNlJCL9L9doMEBaNun2kzcc8qy\n2zIYYGie2oAJqHal8FKLWgVlz4/Fqtq3WbvE6oCGAl25pQQ6xAkgOZz0Alj4\nDjZa4PRvYs0WtESYBBNpkNpGwlR0Pb4JJng/j3ebAnCWbegtk4r7ey1RtAXh\nhE9Qp15kJKt2Os+mYO4XC5nozCGmDN5MuWcOXUwhlUXNHQoek/nIzhxDiwVO\n6hzorQ6CZCAjbwsaVf8Z59RTYJvIPFPSt4NPT803Tr7avaxdq0GrN7Vc1ITe\nULgO\r\n=wicT\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"917879cad289f4149f6e6939e27045fdb142be33","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint --typescript lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"catch-uncommitted --skip-node-versionbot-changes && npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --typescript --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.37","@balena/jellyfish-logger":"^2.1.94","@balena/jellyfish-metrics":"^1.0.246","@balena/jellyfish-environment":"^4.1.29"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^5.4.2","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.157","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.134"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.7_1622327576105_0.8758456999206796","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.8-renovate-major-external-major-6738339e14d40176ab9c437d18e1d626195e92bb":{"name":"@balena/jellyfish-core","version":"3.0.8-renovate-major-external-major-6738339e14d40176ab9c437d18e1d626195e92bb","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.8-renovate-major-external-major-6738339e14d40176ab9c437d18e1d626195e92bb","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8bace937e97d561dbf59137c0d541d5567ba4dde","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.8-renovate-major-external-major-6738339e14d40176ab9c437d18e1d626195e92bb.tgz","fileCount":190,"integrity":"sha512-5X2s+H6F3hWrrVjR2G4oxrzUu62/3cpbKCk+VswfhcIeM5lESbuF2dgt0f2Bw5ypvK7Xcwvk1UIeOIPwtdbZ8g==","signatures":[{"sig":"MEUCIAHpYBFMFY+ryZ7T5wmGDjgNf0JYqgrRQCuvm11EbYMFAiEA5sFokLZUTbAQxvgHo/IJ3nHb8cuBX53v1TdouHmkyLE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718585,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgssI2CRA9TVsSAnZWagAAEg8QAI7I+u1rqoUFzcjQmKuG\nxSBgIP6eEcti+VwhMNCdG2uAPI0180/hKUMtoth3HWNARChh3L3mHx5KskHp\nev2xvbPMHjK6ARbyKVTuKjGGBAcPfGxtmcs78vaQx85q/8Q3i7KgCZgJN4iq\nAmPolPB2ghLV0+pFRm3zntNbb5YCvL6pG32Vb/nqDzPtyUmT6gZzbuij2Waa\neAp9GJtlZAuLaHihc6Tnt5jqaoYFeVhcZRz9Pn9Elk+GwYOuYPk16AKY30PD\nFelNeajakPy9pr+J3aqdMpRRX3FWRH0RIeGFjt8rWaTqeURTinvgVCWWVR8S\nbwC0Pf6xiJOjPmwl9veGbEv9uUpXtHgGp4F3NIXfKUytqVaZvkci/qiIR9ZJ\nCqAnttpx8Y2jZrvBK8r3wr38noHlqD3+YwjESqz4edF5bkjXkIYCHZDFofOs\nWmzamZGdxGsIjPl9m5AoOJTX4XuH38xle/lcXy8OJwnjI8oMATf5J4SpsoZP\nMy9thTGl1xvBEBStF1No2d/2eH7q7hmocRq5T5bnQbj4SdeN5ln3phK9+/zs\nW9V9pwpgXV7YSIX7oRxnybme/iCBGLB3BnbWZwvQs1tGHETCr4WexzkWBhfF\nkGC9K0AZVbgzoEYJybQ3GiQka6xXKN+R3NyUWc88V+LlLrjMAk/iUTfBtmKJ\nhs/t\r\n=jCAk\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.yml up --build\n```\n\nThe tests can then be run from your host with:\n\n```\nLOGLEVEL=warn POSTGRES_USER=docker POSTGRES_PASSWORD=docker make test-integration\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"6738339e14d40176ab9c437d18e1d626195e92bb","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint --typescript lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"catch-uncommitted --skip-node-versionbot-changes && npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --typescript --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.37","@balena/jellyfish-logger":"^2.1.94","@balena/jellyfish-metrics":"^1.0.246","@balena/jellyfish-environment":"^4.1.29"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.3","deplint":"^1.1.3","ts-jest":"^27.0.1","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^5.4.2","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.157","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.5.134"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.8-renovate-major-external-major-6738339e14d40176ab9c437d18e1d626195e92bb_1622327862302_0.39313980751031075","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.8-renovate-balena-lint-6-x-dcd6c23c467c208370f83cd89ee37a6806c35fbb":{"name":"@balena/jellyfish-core","version":"3.0.8-renovate-balena-lint-6-x-dcd6c23c467c208370f83cd89ee37a6806c35fbb","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.8-renovate-balena-lint-6-x-dcd6c23c467c208370f83cd89ee37a6806c35fbb","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"11f225102ebbcee9fe42114e14ebdbe14083a965","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.8-renovate-balena-lint-6-x-dcd6c23c467c208370f83cd89ee37a6806c35fbb.tgz","fileCount":190,"integrity":"sha512-Gt+IHyUhLu2eZXxAaixJWLWG3yvsmUHr7lvxXXnQifdlHZOcNeQH8nDVH7rjCmnQfUschf4IujoQdJdjOyMktw==","signatures":[{"sig":"MEUCIQDoD5+7mZBi6yvAlItni1nr+JzZYEKtBdBDG4fbA5AGdAIgeqx0UrdLUXaUBO+OL04KnL2TlcB2UV9IBV+gXmgiXcc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718617,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgsuVaCRA9TVsSAnZWagAAVMoQAILV9YZZ2wzXk3/74TET\nGG4PDkyLrxxwuTJyEmSTnycaK74EtlTftXrYbwjztCA7EbeNY5vo0ZRTacXq\n4S4HMPHGATJvkEQwqJuVbrcxRobsjnvwFutAtz9MuzvNpnDA/9yrereNMm4T\nGirS5riKEW8THz5e0io7GVvfKwGbuheNdacwe2RAF5Njw19VC9KGUKtCZEBo\nHgVaZTPBVfvrCXVD5dM0weIdjXAugwiwLX0q3/vpW1fwNoqxWBmgXjP0+1qG\nBtUQMW/UBoeH7J1Ernu7KwYjUMiFPwuH7U/09L5T9olRt85W0stBnmUbPlrT\nSwJtn6wamUnE+DPzwsWa0JUUBQx4fxBz8KAmbepAaybagDG5VZEZm0hTaK/g\nJW4K1fBup7IdJ+237vJNd6+4nwZfpAPPqgRSravX2QhYUY/nWTb2Pnr/82SW\n85yF98qBpqmDs2vASDDIoHtHqQRBx8MXJeIQxVUSx8uOLJsTg9q5E0sVGghP\nEA29wDaepYi8uoK31GcwxUiE7+FwZuourwHm8iRkCH2kWGrZV33JlZsMcncX\nOgXxs0IEhF7Vb6fNCEFrKRJxjZUY0TmMUhh8PUprE8hIMd4PRBAeps8aeSog\n0ahSfbAC/2zuqph+vAg+j+m5exqIYs6j9+EwJvd0tTbpkkn5jtdRyPmwVPZ/\nGIuJ\r\n=B8NZ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.yml up --build\n```\n\nThe tests can then be run from your host with:\n\n```\nLOGLEVEL=warn POSTGRES_USER=docker POSTGRES_PASSWORD=docker make test-integration\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"dcd6c23c467c208370f83cd89ee37a6806c35fbb","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"catch-uncommitted --skip-node-versionbot-changes && npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.37","@balena/jellyfish-logger":"^2.1.94","@balena/jellyfish-metrics":"^1.0.246","@balena/jellyfish-environment":"^4.1.29"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.157","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.134"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.8-renovate-balena-lint-6-x-dcd6c23c467c208370f83cd89ee37a6806c35fbb_1622336857568_0.7731071387070583","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.8":{"name":"@balena/jellyfish-core","version":"3.0.8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.8","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"92896de6fe9d44f181036320177fa690cb112a45","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.8.tgz","fileCount":190,"integrity":"sha512-6lT8yZcybFlQyQZ/JBrukZ1VSinx3LiZPjq9+U/DofyVsYODj+30ezRw8ORAqt3G+XwSfqSPoOaBsX/cVHjf4Q==","signatures":[{"sig":"MEQCICSHKX1RsavpGKGxMPKmkilg/fcBi8MtXXcOkEeItpR3AiAS0VAmYIeqf2Hcb0tYsRah0dGg978H2EuENKNvTSoBDQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718551,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgsu8rCRA9TVsSAnZWagAAv9AQAJm3CtyYLyKpd5Il7r5f\nVNavRIBHsdk6N5tWe/wdktghCLJzaqKnKvTCyZgI1SivJ0ITJAp47IEPax1f\nINsSSlOWQoahoO/Af1cajd9ROA0VrHIakMuFB4FN0Wj9KrS0zmaY3IbiFtMb\ncA9269Dxart7z6HqQdoo7pVJm9qxG5o6LK7iHPdn6zI1Y2A8VtNHDfaWGxmm\n8NKeAj47dLp/yVurB5GbMUTTSf9uyQf/VhbowWDAqkDA1vrmzQw3G76jLHV7\n8a/Gr9Xzv2D9GfYnaKq/bGNAS/YB3epSiaQNo62s1SR+8FMKccNAhP4XmC9m\nnWNzF/rrKX824KqOyp88mX0U1jhharObpmKaGuCWfg311qyhe2u0RvW0g8L9\n2rrwC05+reRoN9hA4xiRXi6Ill669NwECoI8LLxbhxbvx/oCn6RhxbHNDX4A\nATF3B9zDWvJ97ZyOLGaxudxuaiXiH9vIc6WOlONKmGKrCuurPBVzZKZ8jUe2\ndbm9CGO5zGnxCXZAB65OMYOMh0TgCjxIa6nS0wwMZCKWzuTjqR+PU5h0enks\nxha39zgSAfkJhURrk+q0FCsQmCIKeozJV3ldGCzJU5OP/IiK7297mJIEyW4R\niocSJCGmUnb1YGf+qQFAZyPDErgjGVD/1IJA+Ejf/wYHLVBkoD59t+GO083r\n0MVc\r\n=lZ4W\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"f3c110ce416cd160fd6713fa7e76d22e27cd4145","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"catch-uncommitted --skip-node-versionbot-changes && npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.37","@balena/jellyfish-logger":"^2.1.94","@balena/jellyfish-metrics":"^1.0.246","@balena/jellyfish-environment":"^4.1.29"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.157","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.134"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.8_1622339370863_0.34315731407662753","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.9-renovate-major-external-major-b090b8f8dc4a7f0a19dba3d68c30b91b290f2891":{"name":"@balena/jellyfish-core","version":"3.0.9-renovate-major-external-major-b090b8f8dc4a7f0a19dba3d68c30b91b290f2891","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.9-renovate-major-external-major-b090b8f8dc4a7f0a19dba3d68c30b91b290f2891","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"41e3f683882876934a36a1a375549e0aa76d3da8","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.9-renovate-major-external-major-b090b8f8dc4a7f0a19dba3d68c30b91b290f2891.tgz","fileCount":190,"integrity":"sha512-S9M878GzjpkjlLJ2cpPcq1+KRDWWSiMEc1onFPw4XGaAMvNKCNWGfzKEWbudoPJ/mMTywKQkZSfSeNA4KGueJw==","signatures":[{"sig":"MEUCIGAwohwxamGslvFg53NIJ9Bhy8xk2/rjRlIJIqsyBO2dAiEAuVmGYsxL5ipkow0fF8yKo8CuBMgDOtycEgM2Vu8veno=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718692,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgsvBTCRA9TVsSAnZWagAA+fgP+gPYkzZSxQyE4XDOis90\nhXlfwXR4NDxosvfxTNMzpzQNHui7J/zQj83MuOEJOgde7iW1BwYjYr3m9PDB\nus8kSRPUxnRmCI6MiJ1HpdVepWD43xNmWI4mJ8IZhvL0PNMPsU6IRgdmtGMz\nbmR1fgmrXOSOk+bRCKRY5WjCzZbzQSWAZV+KqWyCBRQus5mCX3bkQ+rGB9rs\nyuzWaQwGaQBklPLtJSuFI0v4rUo344IlvGtD1xcYtqtGGSF4TLkK6Uhy4yT4\npqryEZbLVkztGhdjN/QM0E6YYTvb14n5J5r1jJZVFxaujKHdyfl8YBDeG+PK\nTver1NvAWn603GPBgcgNsAvp7qpG8XN/bq7xI8nroDQ2o8SfPj2kHlZHj9uU\nvMwM6EszVDZ8fcd+Hk4HoueKYgl46kKjMIoX//L96dB23eESeFTSUqc45Ah2\nLMhZAOVC5yn5tgfYQtdXvpecDK46FMtDl5jCfx5O+v6Ai4djTezMKsCR1d8Z\nGkUCsxbgsM5ZElKJeTx1L36QooxNGJpT3x4/FutE4f2McviqzgdlAHZ7TCDT\nFjAHIK4Zsy8z6HOeMkAvcWt5eOWjGTK/Rh4ZPN+ObrFfclS4QqjdaPuWp9FU\nnngb1Ab9RDpMTBNHtjOG+TpbQdIC6RAaj8Fgz5veLqs722SEQ3eOy1FMFlHt\nUTPh\r\n=xlFU\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.yml up --build\n```\n\nThe tests can then be run from your host with:\n\n```\nLOGLEVEL=warn POSTGRES_USER=docker POSTGRES_PASSWORD=docker make test-integration\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"b090b8f8dc4a7f0a19dba3d68c30b91b290f2891","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"catch-uncommitted --skip-node-versionbot-changes && npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.37","@balena/jellyfish-logger":"^2.1.94","@balena/jellyfish-metrics":"^1.0.246","@balena/jellyfish-environment":"^4.1.29"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.3","deplint":"^1.1.3","ts-jest":"^27.0.1","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.157","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.5.134"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.9-renovate-major-external-major-b090b8f8dc4a7f0a19dba3d68c30b91b290f2891_1622339666962_0.9667646706178801","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.9-renovate-internal-patch-f0e4b1e30f6eaa7a8de308fd464396d35c855fa6":{"name":"@balena/jellyfish-core","version":"3.0.9-renovate-internal-patch-f0e4b1e30f6eaa7a8de308fd464396d35c855fa6","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.9-renovate-internal-patch-f0e4b1e30f6eaa7a8de308fd464396d35c855fa6","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f4e1b61c607a0fa26b204f935b49e0cfc6d09781","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.9-renovate-internal-patch-f0e4b1e30f6eaa7a8de308fd464396d35c855fa6.tgz","fileCount":190,"integrity":"sha512-5B+sKrTd0dU4fAnkdwOXVM2LpAfdRf6tc7YRDzUpAt7QebEE/ZzykLj05mfcGNw/Eb6Ymc2EoQpRRcmqsZqWJA==","signatures":[{"sig":"MEUCIGNwyh6jHAfLHPQRvWKfDTstSRzgKOupNJtDBeUVOAd1AiEA8eAmxk5JG2IcMWQgzzIJFhK4D6GYvxuXI1VxEPzJ638=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718717,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgs6+pCRA9TVsSAnZWagAAIIIP/Rvp4I+RYrRjBc/i5tNT\n21WWpqFYs33KMwZ91DXkB772EcJGFKn8wcbHRedpdosqPuXFVzMZb+FUjdoQ\nkGQZHa9ufmfdJZK5ACAr0eOH8DeLc1cjPZYf2YiSkDnb229vl2TsUsQ19aW+\n9vYBW9+VXPfLYdc4Jrv+/UlADK20t9Sh0VYQkubtxdh3nUQUdnk9QcDJmAR7\nUi6WYc0nNcdMwfbMiXatPyB26Q9Lx7y805Ot2JMVtHqzgtq0ptl4GJxSsCxq\nNYnrz7HKgMZCSfNwrMHAEasMNVcU/KYBCuwtR79HMWvyfZSwIyyYKzP/u6lj\nnnXhgngfd1SmoKTdrLUaUniygjjulSeK+6Bvm29HzTv3/HROS3jUajhTGyib\nLK/1aUZ+VxbDwuJsT9uvcQgl2p/BmyOXMnPbe7GJm0ltNr3w05i1yn+/yvpo\njxWn9cHK4jPlOs7NfPjPtL+EHw7JBpZkp8t1W1T4iJ+gyYUSUB38qSxGNOST\npc02xCUpDEDT5PgMaPshINhzv+2ZR4yIYERkG/Z8ZblocE5y5XBhcF8+kMSp\nl/H3kMa3KbxYmhJADGUN99FYIR60LV1Zf/KL2/r+ZSNjbcSkxX9Y3c7bWDXu\niyZqq3vI48dh9+U811/HqkMMcJa6Y4oyxAaLfiDwdswEfEqBHmHw4LkWfIyp\ncfvQ\r\n=whYk\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.yml up --build\n```\n\nThe tests can then be run from your host with:\n\n```\nLOGLEVEL=warn POSTGRES_USER=docker POSTGRES_PASSWORD=docker make test-integration\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"f0e4b1e30f6eaa7a8de308fd464396d35c855fa6","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"catch-uncommitted --skip-node-versionbot-changes && npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.37","@balena/jellyfish-logger":"^2.1.94","@balena/jellyfish-metrics":"^1.0.246","@balena/jellyfish-environment":"^4.1.29"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.158","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.134"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.9-renovate-internal-patch-f0e4b1e30f6eaa7a8de308fd464396d35c855fa6_1622388648572_0.15357025623514464","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.9":{"name":"@balena/jellyfish-core","version":"3.0.9","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.9","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e6ae4720e88b7a33e5f20962de3f988c36f37ee8","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.9.tgz","fileCount":190,"integrity":"sha512-MQVDJGYCNHo/E5ECMApeDsxqWTVAA84rvAvHKPgQiMDvzbzLWthZUzpycHpjt0Rr3s6OJpezxALAWy7uoK7uuw==","signatures":[{"sig":"MEYCIQC36FQQ4ItyCwhDrWpYNjm4EHvSdrZ/w0lwjhLUURk8vQIhAJGCIRqRUwq7Y+MmriuNjJGR7HesaLJiNEdOIlhXSWaK","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718652,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgtCfTCRA9TVsSAnZWagAA69gP/1hVe2vKPEpryMR5e51G\nHsMuXIz122MOed9eZU+GWNVsBVWUIGuZXxMpzmuA3PAY/Er2JEKgwNf3OpwS\nsP5Ute2NeT7letwWiJcJM3ge7qu1Xm7FXlS1BhLUSkIMk8iyv4AYqDOS6qdQ\nDnPfLBUmZsASpzmwwiD4QZ9uM31lWdt61Nzw6bEOgHUN6D2JyAaFs/J++p1M\n7/HV3TtQRlmqYhGZSCXE5mB1UwDJx/mFZguwmn+bSaCEHf4E47shXhGBy7d9\n1zCcwKYFSeMv5hDyYkFPTKWzSX4aZNFX4ipXCboOuz6bpcbDnOl8h86TOw8W\ngRcXTYd3jJqKvqgrjuCy5b7iuyelfbP1S+Eqan+/D1BUPAz788txspwOqJKD\n8rp1NuyO8Gx7TbHpE3SrAz7byeI318mBKnXX3mv4/zdmrso18eSngnx60AoI\nNqJXCkWmt2XDXIYI74zggmo26OIoz7PQcprGzyf2D2EiDwTk+QcdG4NfjL+g\nh1Qf672qXg2d/x3iy1FiiyexX3AAsN03U4rEzQqchHTKYsg5oBulW4CntJBH\nTkFZvOK2BjxRGvGss3EoY0Bt2xTeO2v0Agn2AggrvnNFiLU6e0qjQhQ5oc4B\nRb4kMeq9JV8IiS/iHGwR+ddRwHWWV6oUEksdNSPjE6LU1SA1ZzeK9T9cmttj\n6RRR\r\n=W+TH\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"e04e5ee7d0a04ae2b6522e759e02d6e80c0c0cfc","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"catch-uncommitted --skip-node-versionbot-changes && npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.37","@balena/jellyfish-logger":"^2.1.94","@balena/jellyfish-metrics":"^1.0.246","@balena/jellyfish-environment":"^4.1.29"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.158","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.134"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.9_1622419411222_0.6878147125761802","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.10-renovate-major-external-major-72a14586525845c9383614216aa7bd924ced9500":{"name":"@balena/jellyfish-core","version":"3.0.10-renovate-major-external-major-72a14586525845c9383614216aa7bd924ced9500","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.10-renovate-major-external-major-72a14586525845c9383614216aa7bd924ced9500","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"763d7e47b959ca17c6bb286eaef44949987f9998","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.10-renovate-major-external-major-72a14586525845c9383614216aa7bd924ced9500.tgz","fileCount":190,"integrity":"sha512-+6lefUlNu3fKT49Vm9Z2aNAHDSIT6koqIFi+RLYYiXsV/pCdSgAaqzqYzcVOGQE4S5LIt6I6epq1LRFw5ufKzw==","signatures":[{"sig":"MEUCIEP6A9o3Sfugrh5lY26+QD6C24IhJtVvz5L9JcZi0nbuAiEA/jf2YIBIrzo1afqu3LWxDgprRZZ5rKkZw5KQYD4Of2w=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718795,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgtCjnCRA9TVsSAnZWagAAGA8P/1ybPYr5yd74mTNM7O/8\ncKOxfxmrDOz1FA+ZDSKNcEfWdSB/DLakQpMmxbPMzunAJBrx7emqF1m/tYxr\nTfpU5PYFNdygerX2y14lG9rsb5dV0R9hJK4SE/TDG3O/PzA8hjT5N1+Ecb21\nycAiPwjTRtjfmMMW9g1Sfgq/1nDKOxfALdjIqZw/v8AW7FhQqSwV81jFy1Ro\nfIJKbSzQlT5PzEU098Ii7vzjXHkIJbGShI9VTDM1GjZlSqLGycRiSqW0cmmf\nSO+Z+cNbF9cRvVyE4SqSJ/3q99eg5gW4odoBN9ZNtB52rutOwsNYBk/EPWzb\n78U4hDuGLZXIUFJhu3EQSiRImyv2Hzcq3+2SgmFSw1rPEH1lERV/jOXHr9qO\nM26SyjmVwSNnudjfEvwatpv4ULCNkp06YvabamM7oFzBYQrD3ihrICfYxoln\nsh/moWxtRHm+IArPPNNAE8psGaKWaadKOWxbjiaaxWQ+KteoDIqeg2GEoeyh\nr1qbUxXotBcGczHYBLYHMbraZ4qHd0MK8OPNwU9/5sAmq3KsqASVJOIYSzKk\nmQ8qLBuK5iXSO/PuDFpD8NLs1gYHniUCD1wfjYKV+vHIPHQD2mwaKMPpjBS7\nxs5SVLHjwoywBAXob6VP3+9TYl9hQ53LiOO+ofTI4VRLGjwV8vetwvIRYUYx\nftdu\r\n=pV4C\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.yml up --build\n```\n\nThe tests can then be run from your host with:\n\n```\nLOGLEVEL=warn POSTGRES_USER=docker POSTGRES_PASSWORD=docker make test-integration\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"72a14586525845c9383614216aa7bd924ced9500","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"catch-uncommitted --skip-node-versionbot-changes && npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.37","@balena/jellyfish-logger":"^2.1.94","@balena/jellyfish-metrics":"^1.0.246","@balena/jellyfish-environment":"^4.1.29"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.3","deplint":"^1.1.3","ts-jest":"^27.0.1","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.158","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.5.134"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.10-renovate-major-external-major-72a14586525845c9383614216aa7bd924ced9500_1622419687409_0.2538349688589665","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.10-renovate-internal-patch-09fb0dcbf77343f25031e24015cbe3ee3ebdfab7":{"name":"@balena/jellyfish-core","version":"3.0.10-renovate-internal-patch-09fb0dcbf77343f25031e24015cbe3ee3ebdfab7","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.10-renovate-internal-patch-09fb0dcbf77343f25031e24015cbe3ee3ebdfab7","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"fbbed68312973c6d6a7043f436f99b19b0b56291","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.10-renovate-internal-patch-09fb0dcbf77343f25031e24015cbe3ee3ebdfab7.tgz","fileCount":190,"integrity":"sha512-acIDNfFIuSGVOEb/kWeFPe2OfZ4YkCS8EAMVhe+QQL5GQSnbMVYJlhzdfrWoLukYts5Qd6hMvt+Oc0OCS4r0rg==","signatures":[{"sig":"MEYCIQCcGXXUc3VXu40RhZJoZMcGPvExyLFJngAkKU0jZcIq+wIhANptEVYP0pjpIAmP4gQHcL+Grc/ZLBRX9a6B9Ib5zsA7","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718789,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgtGRMCRA9TVsSAnZWagAAViMP/jswub1WgV3iD00L851/\nOWxlGDLPTioBQrQEDjmKiZn5YrPO4PHTEc0m35fXnHAm2RyaZc09wzwa0oru\nhQhU5x7atFa2XNXGZpyI2/mJN6oOsiV2lL53eiSiZVU5SSoSDD4M/MMFeNQ4\nDaZz6QbPodpC2f+eKLLMjlXjRMErZ5rZxu3259/hp4gkT7qI3WYc7egVgyKB\nuvXmWqpqjRT+O6BSoWhbHns+z39tSl21bpNgVoswPsZgRAT8RU1X4fQdyQ1a\npmZGEOdJ0DaW1xV+hic79OSlaHB/m18LZQ7RuRYKLESHg9SrpE3YpeF5riIs\neHxMZK4Sn/w2iG38Seet+DJPHGCtQ2WZdkf4o225hItyMwO3Dn6zOwJp8wWe\ncpSigKzAHD4N4gF5WQAZ2hc9+5fHgn1BfP3r/h4emcCU8j2D25sRWQG1Ld/V\n4x/WHtNhk+dzn8fPG/sN80dfbSMynnxbdPLi3dQNutmralN4+e6xJtBS5mgZ\nm8jGINaLaX8uJQSyTgBi+9e/nrlAVuGWxSizGZFHO3Lc2yBRQXMMgirZ/TOC\nOyOLXfd+6q0C2dddMSt+3aNAwkw/39EfLlwkVNo+N7p3NSGGt45tdGmgRIIz\nVdB1QzPLZAmz2I8mHk+5AeQ7YjKq9k1QzfjqxFlafMv78Tpx1lpRrOmNIAUQ\nf62b\r\n=nKaq\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.yml up --build\n```\n\nThe tests can then be run from your host with:\n\n```\nLOGLEVEL=warn POSTGRES_USER=docker POSTGRES_PASSWORD=docker make test-integration\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"09fb0dcbf77343f25031e24015cbe3ee3ebdfab7","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"catch-uncommitted --skip-node-versionbot-changes && npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.38","@balena/jellyfish-logger":"^2.1.94","@balena/jellyfish-metrics":"^1.0.247","@balena/jellyfish-environment":"^4.1.30"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.158","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.136"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.10-renovate-internal-patch-09fb0dcbf77343f25031e24015cbe3ee3ebdfab7_1622434891477_0.5447164943624181","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.10":{"name":"@balena/jellyfish-core","version":"3.0.10","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.10","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"23c28b803799745a7cc85076b2f34166305b3f19","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.10.tgz","fileCount":190,"integrity":"sha512-+SluOs8bM+G8PlWvsoWtd0t996WJisulZTawDCiA//ZabipxjnGIi/yVaBC9hD6PyZAmuKCBovlqVKgtcl8hYw==","signatures":[{"sig":"MEUCIG48VQskN4CrwFmI3QRKV56JT3m1TXVkBK817bUaHcFUAiEA3nRDaI+eR7PEAOW0S6MvPu24HVux9HaoWFXpHlQfc9w=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718724,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgtGV0CRA9TVsSAnZWagAA5RwQAIvm9RshzT2Lq39QvPBM\nGygzLCKTGHW6ko0KxTg3Eslk8DUx7a4tIsTp+hLokTpAjVlMXrjbYCt8IRRK\nsvcfaKpVURaEfpM5xbW1iVw78IX7VksHcEWcc5QOKnIaPov7622HSzMRUyQ6\nj6Oi9qs3MsRPJJQcRLyWdrNgh5sc10kBDKdlzHxzpJRZt8oaS1b2QHOJwjQT\nAFR2rGClpHU2rkUkO9iHhnyGh2VKp8StV93TE+uayJ8uwau/fpBqum5r84Dg\nS0nZsTE3M4lEqrs3BtCzZHUuuKdgstBEVaZHrs/Pr2nMFTMvdPHOLv45ti3d\nXWVCiziifZovzemyn6cfjNoif36mNXHDLroK2tjxth6U4fZjeVjO2NnxVqfo\n6xQPLUB/LG3yp2xodBvAV5eSZu0QH9OhLs1jJ0kiaaTqCljRKZrzIVu4iaP2\nFC0Vx57205JuYPPml1v6JVLcjn7niJDVMRb7Szzb4dISzl1x9uXXKlME1cly\nE67kNEM4dBe5RZtvSaIasGqUzRyap4k2//0yoxpxZSCqVmJW7Asn1v8+6RO1\njmEJ3H4Rh+iMXTyThsgyVdtaEynDSgL33435lexDIseY3UYrFUCmp3OymaBT\nmlfQWermFTTuRZNR/M5OX8aPCAu3SHZb+nY+WXT5TeL5guyYEsNoIhArsskX\niEby\r\n=VOKn\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"56c8495c10b6912475cd20f6c906b934f3e167b6","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"catch-uncommitted --skip-node-versionbot-changes && npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.38","@balena/jellyfish-logger":"^2.1.94","@balena/jellyfish-metrics":"^1.0.247","@balena/jellyfish-environment":"^4.1.30"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.158","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.136"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.10_1622435187710_0.6978011666527311","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.11-renovate-major-external-major-56b5d8ff623d41050a44149d4482a3cc9b7898c3":{"name":"@balena/jellyfish-core","version":"3.0.11-renovate-major-external-major-56b5d8ff623d41050a44149d4482a3cc9b7898c3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.11-renovate-major-external-major-56b5d8ff623d41050a44149d4482a3cc9b7898c3","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8d2c0f0541bff9bde16b79673ac1d59a2ddd3db5","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.11-renovate-major-external-major-56b5d8ff623d41050a44149d4482a3cc9b7898c3.tgz","fileCount":190,"integrity":"sha512-je6rjbqJa4nbUY74OoAgGpH8QgxvSLB+ZoiIbwoIiXr5/b5spRj6MYSIn+ejL5FxWEPUUvvUZmhVDY5xcHnUmQ==","signatures":[{"sig":"MEUCIAjECQDHQ8CEgVpiHDnhN8jP40A9yzAmHssz2eOcsTmUAiEAueSdPNVMvK+DmpiDJBtQErAAfxyocav7X1h9ZY+AO2U=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718866,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgtGZ/CRA9TVsSAnZWagAAlmwP/2vUG/Y/UuwYzzHlfi3A\nTHbChspBtuqWSQWctBdOP2FMr6HVoYePXOieVktnKOHMDGa5w3ltFBkpbon7\n4vdD06U6EGRsNlLtGedGGXodv4CV+fIFEjdUilp7UFSqUkgEiJU9wEpT1J3c\nAyxDy5iyHGETCSZq3nUBPqzV1918IwQseZtnqYGJEp58c1l1LW1XK/G9qfqa\nI6fss4CfweyD9Z9XQWMMP6l1wuPyZMKIpRa4EtbSQzCLjmgcMFlxGaxJImYB\n0B5LMaf5SF6Uwgs4BokKLPgnf+w0TACTrDF2aAZ+JHrjx8UQWH5yLsFYVWup\nRQrcYSAC+hRCcrecO0QXfeN2OV29jcPPMj5sdJ9iEdg3ZEN0D8Hs8rHIsyss\nB9o0vUnB02e+sJGIFbWf5liIMre8paKIRC5FWiCMoO5SZ17CCnyMvH4ItoIA\n3uu0So0A1JrQnLWDlzZeG9xN0dEEIds3VF4y5xfKzSW0y+maFWetUgf7w8Et\nrzcJh8T8L8kLzxhl53Ec9iCJWVqSMcKXXqWwjzJLYJoDacMdDpPtB9exFmfV\nRzgp87bg6ssdtf2y7YexPNECv0ztWliUjIJKQaOFERQVVTNNEDeEQXb/A4K4\nJvIc5PN7wIHRwlfMAkbBeQEsTDJ9jZCuwfl+UeMtZ8x7B3p32B3q98mIWE5E\nRMRM\r\n=pPhI\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.yml up --build\n```\n\nThe tests can then be run from your host with:\n\n```\nLOGLEVEL=warn POSTGRES_USER=docker POSTGRES_PASSWORD=docker make test-integration\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"56b5d8ff623d41050a44149d4482a3cc9b7898c3","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"catch-uncommitted --skip-node-versionbot-changes && npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.38","@balena/jellyfish-logger":"^2.1.94","@balena/jellyfish-metrics":"^1.0.247","@balena/jellyfish-environment":"^4.1.30"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.3","deplint":"^1.1.3","ts-jest":"^27.0.1","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.158","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.5.136"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.11-renovate-major-external-major-56b5d8ff623d41050a44149d4482a3cc9b7898c3_1622435454681_0.3685390411595788","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.11-repo-sync-jellyfish-config-default-903c0b7bc3bf1bc78e04838e48d7dd9273254fd5":{"name":"@balena/jellyfish-core","version":"3.0.11-repo-sync-jellyfish-config-default-903c0b7bc3bf1bc78e04838e48d7dd9273254fd5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.11-repo-sync-jellyfish-config-default-903c0b7bc3bf1bc78e04838e48d7dd9273254fd5","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"7c30cbdd68e377819b976caf884534802a291903","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.11-repo-sync-jellyfish-config-default-903c0b7bc3bf1bc78e04838e48d7dd9273254fd5.tgz","fileCount":190,"integrity":"sha512-ha+k6Gps+obUy4E/zgWhfJJaDWX6aN/a1vB9hsbHgQyALnzdCxMaDaqEyyS9vrFb6cMYotDjbKe1lNg+ul8jbg==","signatures":[{"sig":"MEUCICYaqGW+ioXhph2UD4P/SmxjGAwEtrU38ly06qdQTgqLAiEAvcKG84q0ml4BH2MF7con3uA+wNzSB2cA9lU8KwdGbko=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718896,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgtJPhCRA9TVsSAnZWagAAGvkP/it2AWR31qiW+IIy2KW5\nL3soW2mRmYEBHnzgqqKYCFJ8LpRSquW2zRDuD6w6RtCnok+o37sboT0lGzY5\n4DjCU+k6o4OXVIxKqRPTmpvARcN5sEu4VlMy0iQcffVHKNVUAkdcOduZbWqt\nKDbKVunj/w9LuVUydP7jbR9Psilz4MuEHUW1f8OJBqVIdmTt91Njq5FtJufn\nnxK/pNaTqYpdgL9yRB63vRKKMBFkduTtcBZgIx5ytArQmU9p+sGqNYTNI4V8\n+Zb6f8NcxfFXEeVIg+IiOHCxPP4bCRn0lUgWx2zPWZ1BtwiIqrMIE30L6E0a\nA8fAg5vIKhUHVGWJWKLRyZdkbxzSmhOYx6iEHJvebsUr3SAJegem3lsONkGz\nLNOs+U6UxHyyVv2BgYunwOGAqjkxUrG//TCYb+PLvNLvFSJSSCYZFPwcKw14\nbx1u4hpxwk/fhGMca4hZs2v/sWy4A6CW5IjhuyMp5CgP4iqIWaKpOIZqeTD3\nNg5dICWcFcgot6aUZogUj0IcE1HfF1Nmpyvyot5xSMFHVTy+8opMVtujjutK\nJvjax24ozOEBw3EJD3NRV/TJAAAUpsqctYzkHcgxm8do+5Eb0MWDlbS361M9\nQXkPdwiuYDW0c8mJr7O7dHSwTzLmVeq+NnbuIKztcMyw2nHrk4T7Px/tv7kx\n0H0e\r\n=eLTb\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.yml up --build\n```\n\nThe tests can then be run from your host with:\n\n```\nLOGLEVEL=warn POSTGRES_USER=docker POSTGRES_PASSWORD=docker make test-integration\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"903c0b7bc3bf1bc78e04838e48d7dd9273254fd5","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"catch-uncommitted --skip-node-versionbot-changes && npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.38","@balena/jellyfish-logger":"^2.1.94","@balena/jellyfish-metrics":"^1.0.247","@balena/jellyfish-environment":"^4.1.30"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.158","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.136"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.11-repo-sync-jellyfish-config-default-903c0b7bc3bf1bc78e04838e48d7dd9273254fd5_1622447072971_0.8479887892694749","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.11-renovate-major-external-major-7f635812eab0539cf0bc0fc550865329fce5f9d9":{"name":"@balena/jellyfish-core","version":"3.0.11-renovate-major-external-major-7f635812eab0539cf0bc0fc550865329fce5f9d9","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.11-renovate-major-external-major-7f635812eab0539cf0bc0fc550865329fce5f9d9","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"aed7921ace9a3348870699a4c6fdfe0d44594f09","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.11-renovate-major-external-major-7f635812eab0539cf0bc0fc550865329fce5f9d9.tgz","fileCount":190,"integrity":"sha512-N+hlMtn/i4x6Dl7UWubgkCE1+CS7Ys/iaLZ7grRg3R9uI5ap6ySXbi77dZQ2SArq7JbmjUNpsjedu6hz6fOnIQ==","signatures":[{"sig":"MEUCIQCObpodbyYxPcyw01oYnmpkDVnzXik8wv10Qcvem0PB+AIgfrUs5XlhKv8VlK3LNd96LV2HvnkOBQclv+6g0a82VVs=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718866,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgtJkBCRA9TVsSAnZWagAAiWcP/1j5u+aFHUhMPGVlXtpF\nMO/nG+9L2LFpXUEYFr3loLxh+5AeL5nm4EVtahRZYJFcxDjZ8SziFscKvB5g\nb6EzjI/MVcbN88T/P83LKaiphqhvYAsZK5uaKgNOL8zywbh8iYlbFibvM/gp\niGfPctCG0DKsAk/obWZTGXpu+8LHbruixnp0EBqejWBHGZBiwn4lllFirg/p\nrt4QsolRpwIu9DGI/bnJqm0s/ZgODjPfY6B6Sn25bE3fGwKFY3VcUfsZ1Etp\npGgoVFF1R1jsnmS6giezuymsgoAoO+G8a9BatZdLTDvAS/7VZrHuEBhzW8E4\ngJQfNdIe38sEaKxXMNSfmUiBcqraVECRq1jMEhre514Md8xa6j30aj9E9qDZ\nktIh1ajym+5OiCpapmPoxIhmtGbmf33x7hzddG+AbceoXri+uQaThTiHMFSa\nV0mY6XDuJ9ViBheabd5IUEOQ2pXgaenUClfjxk8ymNDQf1xb8HK/sQuerX+J\nSktTELo7tktyA+3uYMC9/MJuzJDu91riQnARF+9srzYi6RaMbRg8EUzNyo0Y\nadPcA58/Sx1EnzEkyAAhXWgk0Oc01itA2cDe8HtqX9NF1zJTg0oV4RNss7s1\nDYElS/LMNDrHiEg1v2F3j5pJWBhLHN9hi/w/k19PLfmXpVbZo+9QmIbt+wlG\nzPHR\r\n=cLpk\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.yml up --build\n```\n\nThe tests can then be run from your host with:\n\n```\nLOGLEVEL=warn POSTGRES_USER=docker POSTGRES_PASSWORD=docker make test-integration\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"7f635812eab0539cf0bc0fc550865329fce5f9d9","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"catch-uncommitted --skip-node-versionbot-changes && npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.38","@balena/jellyfish-logger":"^2.1.94","@balena/jellyfish-metrics":"^1.0.247","@balena/jellyfish-environment":"^4.1.30"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.3","deplint":"^1.1.3","ts-jest":"^27.0.2","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.158","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.5.136"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.11-renovate-major-external-major-7f635812eab0539cf0bc0fc550865329fce5f9d9_1622448385015_0.927654217670187","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.11-renovate-internal-patch-4e1cb6123a74add0f321ec677d648c041e17b401":{"name":"@balena/jellyfish-core","version":"3.0.11-renovate-internal-patch-4e1cb6123a74add0f321ec677d648c041e17b401","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.11-renovate-internal-patch-4e1cb6123a74add0f321ec677d648c041e17b401","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a11ac1b66e81d4affbe8377cf21b641ed9fabe2b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.11-renovate-internal-patch-4e1cb6123a74add0f321ec677d648c041e17b401.tgz","fileCount":190,"integrity":"sha512-EWbymRhYwHyoZGQF9/3nNOQI8XwBWr1hnmK/eBH9V33qhUiW4JUbKdlDsSEBnhP6t8ktTxHqXOIXfhoQehLyrw==","signatures":[{"sig":"MEUCIDE2kkkV72L10+voAFZ2XU523IqrPA+VrCO6qSaN4d5yAiEA50vVtQpS6DAZqC8/mTLx9dFq14Y2L2CPfqjcPbM5oyA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718860,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgtK12CRA9TVsSAnZWagAAPO4P/3HxAWYynqDAXJeZFUte\nbiWyj1MizlNMIPyCu803kZRH5zGFQrPfiNAiO3dS7hXy2+F3sePCkmgKGPz/\nJ84uLaxWPP6GNyzm5TQY3XvvpwGfdDn98/ZtKqxn305mjw3At0UNeidlkYzc\nGWy1/77TW4LDAxyVy6AQB+iD5NarB7WtyaDiXw2uXEdq3kBp9JVCC3xIg0zw\nVWq9PmWwupY4zxzfYJtSHto4CKbvpfAbw4X1eTkwQdGRkElP5g2HjrH6WE7k\nXDq4HkPdeQj667TVdRPTKCdbZp2T99vLSlKLGdJ0+YtTdX4hc+khT5522qjm\nfhTo9zp9vHeUFCamkPF9KPYXoFyJ9G6eJk4WHZ9wZFX6xnLHvydHZTtWoJEI\nZQmBCOI/R6ILq+WPxXHDoccOBy4aEMtITKGdxWHqwYNLcfAvr9NtcaHHBCtv\nX1mvAlf3ZUWT9mzb7C+3MaMO64OKpSo/KOouO42YIyo+mFdC6zFMBL0fRflD\nUwwaryE0fOjelkQf8Mh8QvI5a9lRlz7pLJqFDjQG2WcgbE7fXyEcw7yVc2o9\nU8hBEHEQ+6e4pPx5TAMGJBdVayG0sPtwmESaNL/OfZW0IsqGY7mCWfgJEG47\nw4yP2pp0roEo5bFCR5LVhj1OXW5oClorBLQLq0+3GGvwdDTAfi1GhIQxgf61\nS0b7\r\n=YI3d\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.yml up --build\n```\n\nThe tests can then be run from your host with:\n\n```\nLOGLEVEL=warn POSTGRES_USER=docker POSTGRES_PASSWORD=docker make test-integration\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"4e1cb6123a74add0f321ec677d648c041e17b401","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"catch-uncommitted --skip-node-versionbot-changes && npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.38","@balena/jellyfish-logger":"^2.1.95","@balena/jellyfish-metrics":"^1.0.248","@balena/jellyfish-environment":"^4.1.31"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.158","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.137"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.11-renovate-internal-patch-4e1cb6123a74add0f321ec677d648c041e17b401_1622453621886_0.3300036537459454","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.11-renovate-balena-jellyfish-logger-3-x-01169794b1e2dbf4e683f614b1c1f013628922f8":{"name":"@balena/jellyfish-core","version":"3.0.11-renovate-balena-jellyfish-logger-3-x-01169794b1e2dbf4e683f614b1c1f013628922f8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.11-renovate-balena-jellyfish-logger-3-x-01169794b1e2dbf4e683f614b1c1f013628922f8","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"871940d93a56c837a029e452cb3021cc0b4ad4c5","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.11-renovate-balena-jellyfish-logger-3-x-01169794b1e2dbf4e683f614b1c1f013628922f8.tgz","fileCount":190,"integrity":"sha512-QERsHaZGtkGh+dRMbkWxCOLLPqKkUlhgr0Mio8n9u3iCboJThqXDZ7x49U8/3Nj06y3tlJ1YPJxjXiDryA3QFw==","signatures":[{"sig":"MEUCIQDpDIkQ3tCrjYZGauCFHKT+amWLOV2t2dsau5I04kKeAgIgTRE/ygsWGfU9eNY+rpiBO5KwR+OOA79KG3Hy6vyAkgQ=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718899,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgtK5+CRA9TVsSAnZWagAA9i4P/0TrOrpKaaQrFRp34WSx\nPtivd72EMIiPCQez5QHJkhJBQao5f+B1FtFXr1A6phBiPdSKtdtURVJ9P9sZ\nteQRx+U3TglwOkEP0aCe/DkX+7E81e6rpZrh91OZbRGkNz1vHvjvLGzgUSwr\nOOJtl3R/D1Svm0J66iObVlfbESdbhogrTQFrMh3J45JcP/gi5QAOWeO6o8Zv\nl0dyi4k5Of1nv1W+hISDT6HCjIbgC5FRj2MR6zlaUuZQwG/DthJHPLn0bXas\nnPIGPxOWIgail1+jKk+MRPOzj6jSgm3JTY5U2Uxw7UENy1md/Zvr+1IHqfH5\nq5zl4XaTCYlDkgylvJci2xjnPndB4fENXvHCk8YvFAmF09hpVd1/Avj/CJ9X\nQ/hxisgU0jVRdBdrxC3wNcipXm7d6ltaDr4QztPBt4g9NGM7kh5qRxVJQEyF\nFxDdk92vcS0XTcw0yBJGnERN9d0M158D8a09mO0bR7InTdmar9qETPfaykp5\nJynbigOdALZQzsrLwMibUTtgOfzr/H7BN6u0Ie4G6ewibX18ZJsfWPOo9QKU\n+N7sWUZMdWB/jN6Gnq+Z1/0r/mgRkn+SPVEaWeRlJioJTkiN7x6vJ2wAt36g\nkPyR0nNgw6ONS6yql9+mnJ1+P6zh7CPsrXE+jWeW3tZxucY0G73cCOrZqO/H\n+LtD\r\n=UrZV\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.yml up --build\n```\n\nThe tests can then be run from your host with:\n\n```\nLOGLEVEL=warn POSTGRES_USER=docker POSTGRES_PASSWORD=docker make test-integration\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"01169794b1e2dbf4e683f614b1c1f013628922f8","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"catch-uncommitted --skip-node-versionbot-changes && npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.38","@balena/jellyfish-logger":"^3.0.0","@balena/jellyfish-metrics":"^1.0.247","@balena/jellyfish-environment":"^4.1.30"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.158","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.136"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.11-renovate-balena-jellyfish-logger-3-x-01169794b1e2dbf4e683f614b1c1f013628922f8_1622453886442_0.46026874248113137","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.11-renovate-internal-patch-6fa60af3663ca1e036aa68266efd222733843bc0":{"name":"@balena/jellyfish-core","version":"3.0.11-renovate-internal-patch-6fa60af3663ca1e036aa68266efd222733843bc0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.11-renovate-internal-patch-6fa60af3663ca1e036aa68266efd222733843bc0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"bf2533bcf967967c3682946fb1d3635d8e1de0e0","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.11-renovate-internal-patch-6fa60af3663ca1e036aa68266efd222733843bc0.tgz","fileCount":190,"integrity":"sha512-Our2pMn/9UpH+lvh7yMAukh7pGmjPlwKOP65hHXuP0avWtGWbCWjt25gnOXQQ98N712yAC9FBYvtOAG9DJkDLw==","signatures":[{"sig":"MEYCIQCQU0BpNWJLD0zY4/qgx+b2t+wCcvqpxQHDBud+Z0461AIhAJ9uM4pLtmPxd6aXfUkOiOXmTqk9eNqe7nCccICtSH62","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718860,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgtMfDCRA9TVsSAnZWagAA3wcP/3WJIPfb0pU0AxzEC3Vn\nnJlypXF6fhPy0EWrAiMZz3NXoDwDWFXhUSXiyHjvVr4IqGWUotx3zJHMdQR/\nA4CDj3J5Z8B+BqkU4PWjgdfyqd0l/m7s39agPSoNpm62lqSyg0odOwN5ah5C\n3uoK0I7cuUGG0cjCWgIOQcpwJGGJAxmyox8cT4EA7WAahoFnNaCoaHwLDVEG\nfi7ieOCfIiU0/ZGaQw8bZFBud6rtZh+mIEERC+CctsYCHLBT0lLXmtYnXcw2\nA0h5qM4cElgth7+qsjckZ9kjSOhd4eGHcppZPo8ptqsffYunsBWncuKOpjH7\nHgO7bi4pE89eA5E/J1QC4ULGN0uurpPtjN9ZeV4KOSjeRfKLUL+rwY3+JP8F\nVoRo4q6QJ0YEUjvds20oSE+M0ZV0jQM6EGmkXdbYVSA4pU0UEtY39s1Zhaip\nsAr+ZBvP8aLVzKMd8gnYAUeFrF+T6LPPWknQfJh7eviVkAVbNcjBNw79BiBI\n7G6MoRu1e2OSI8OqeebICCdsktTU+JQVrY/vgzbKFKwUxlrwSOHyBSKGxRBm\nSkbBuV7brXbVysmdmkKQ0bnC7n7sFJYMH4o82P9MCSFdz41sAyM6Otik7MK/\nb1d9aYQDdzLMLJEQ7VocM2OT6NmpvaQzm/ABkaBKFftS8MMwFhP84B0xcy5U\ncQH2\r\n=2zjH\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.yml up --build\n```\n\nThe tests can then be run from your host with:\n\n```\nLOGLEVEL=warn POSTGRES_USER=docker POSTGRES_PASSWORD=docker make test-integration\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"6fa60af3663ca1e036aa68266efd222733843bc0","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"catch-uncommitted --skip-node-versionbot-changes && npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.38","@balena/jellyfish-logger":"^2.1.95","@balena/jellyfish-metrics":"^1.0.249","@balena/jellyfish-environment":"^4.1.31"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.158","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.137"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.11-renovate-internal-patch-6fa60af3663ca1e036aa68266efd222733843bc0_1622460354455_0.29824409668364504","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.11":{"name":"@balena/jellyfish-core","version":"3.0.11","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.11","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9fed27cb53537f9ccfba402d6e6f143dad40acae","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.11.tgz","fileCount":190,"integrity":"sha512-TZ2dfcvDprrL+KH3hOUc7WIkprBkhnawbhKaPWx6TjL5hhqSdtwMbQ2mLYk0DeV+L7ab9+ZSQzxntM4gZIMPzg==","signatures":[{"sig":"MEUCIQDVd0X76xdZFxS24wleqJ+2D6zBy8sSaze+3ru3XI1w6wIgb9ycekIxpYKRgssWQwsgL7FTmocbE9VgK+VRMjGEFEM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718795,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgtMlHCRA9TVsSAnZWagAAyzwP/R2scSG+Da2/pjLtF2DV\nhi9iFkHclcA6oKJEX7q5fVAtzBSPnwnJfHhzoyy0/BgBpu0MDa7H7xEbFeen\n01VmChA2DairCYYcIrUlvAe9gRR8CJ2x+dgOgpX4luxbvejU1L1+kDLfc9pq\nrDWwuWmsoAZ1mL75g2DYbN0wQUfqDHgTCDTbulFAq+da5jJfOjDIFAO1N8jD\nu5sOE/3elpuKYXS3p7rADZYXH4VPum9SafFiyi+oiznmAmFIJ7zHwFt8UvkY\nUbBSktITzkh+JTuu3hisyNd2IFZJdq0iU45DNZD9hNxl9ABDq6dYeGAS6NVn\nAgfVTZR1efKdluckP/O1+6VhOVSRRDg2mbMBFBPhree4p0Wf7CTKXCYYNdbD\nndDa/6Hr+vi0CzoBX5LHdzJdA8QohhQoO07dYiH/hsbbhuLWMxK0aH5UA2n8\n/xTslWs65mrxzMCZqW68XSt9UqHdCHECvojVfMFqxesejZORQO6OlmoYlORS\nZLbyoohCg1vaX0f0mJfS8Z2T6Hwl1AP4C5z3F2LFkTOpc57THYjVUU3bisyg\nAk3jmHSw3pQ0mTYL+9raflZnUM6/WDiRENwpjhOmcVJJgv+ostLArIfz4Hws\nhAk2WQe/qRhIlRED7qju2PiRJTfHLOJeDNVd34Nhf/JKScBd0+6eV5LZaciH\ntN6H\r\n=rK9Z\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"c802c3af9e5714c9c084203d1d26d780661b84ae","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"catch-uncommitted --skip-node-versionbot-changes && npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.38","@balena/jellyfish-logger":"^2.1.95","@balena/jellyfish-metrics":"^1.0.249","@balena/jellyfish-environment":"^4.1.31"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.158","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.137"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.11_1622460742717_0.7918729201722741","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.12-renovate-balena-jellyfish-logger-3-x-8d05dd4e07b9818dd6c1c7e6fa3f81def088d6af":{"name":"@balena/jellyfish-core","version":"3.0.12-renovate-balena-jellyfish-logger-3-x-8d05dd4e07b9818dd6c1c7e6fa3f81def088d6af","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.12-renovate-balena-jellyfish-logger-3-x-8d05dd4e07b9818dd6c1c7e6fa3f81def088d6af","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"91fc58899f675b20bff96d6fe1927a899fb9fb96","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.12-renovate-balena-jellyfish-logger-3-x-8d05dd4e07b9818dd6c1c7e6fa3f81def088d6af.tgz","fileCount":190,"integrity":"sha512-hceIHSK+jWLW9kFGhZmkXf/Oay9ZClk3qnt8EQexm9bLH4aJ0plFLF6B2IX1Sg/6ltKuiye/QVrPCFwZoa3hZg==","signatures":[{"sig":"MEUCIEYJeLgS2ld/Va/zstqBjyoUte1ZBUa8TmEWYxstNLVZAiEAxEroofu4rpuxqCxpCy6ibwrZMs32lFSoMdZ6Nmabi0E=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718970,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgtMpNCRA9TVsSAnZWagAAlsQP/jv+vBEmCmIcgbnyLN96\nTt6xRceXlkVpcySa1nP2vKsa+AWIqZOUStP+qZllZLIlfL+wm5Z3kC3IlpF6\nVCWDikM8sb8b4899H6YB8mJ4WgdEKNfrlgm40dq4Oy5gcxfPpLKjTaoiqnU0\nPSNwRVsJEUUGWGQXqP7bOFn9CeQh2hOADbwcZlOtjjTlNHFG2euEppEuVcjw\no80gK9DBhS4cS8l8bm60/ENMI5o2vzEjN94u9FZW1FA8UHhZUPCI4/Kqn44+\nKqj7NxDxp1iCR0yDGTMOG6j72dF8Pbyvg5lptz+/6Dve6iUuQqvMUQ/ItEpo\nlCfXb4KVX+NZaZGJQ5F+ws57Cej39fVBu5+ctbnuLCErkoIEpOepYzpHCYzH\noCStI9dVwaDiJzQSRUFpb4Wrnx3c6wXlAdi+cOHvWXiFUo3E0Qrv/x6N8Vet\nE7aT6vjCtI3caNPvrmycvNCT1lFl6ibWGlr7PBcdz2xfq3noiMmr7myswlag\n3Gfwb6u44rUBy8iLMbYh6jRCCOXFOh4nY8/TzwFb1DWMSqhyBbfBSoLceRgH\nvsk5ZbqczpphCReHFZTrr8xr9UYzFoKFkqAOMdzFzp2hIrOZIeGx5tRI5ooJ\nPf0XL7xkdXuBQns9lKHcZ1iS70EYZrZJsS5j9JXA6qbmeba/NYjQmyDiqG1p\nWtfo\r\n=CbLt\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.yml up --build\n```\n\nThe tests can then be run from your host with:\n\n```\nLOGLEVEL=warn POSTGRES_USER=docker POSTGRES_PASSWORD=docker make test-integration\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"8d05dd4e07b9818dd6c1c7e6fa3f81def088d6af","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"catch-uncommitted --skip-node-versionbot-changes && npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.38","@balena/jellyfish-logger":"^3.0.0","@balena/jellyfish-metrics":"^1.0.249","@balena/jellyfish-environment":"^4.1.31"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.158","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.137"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.12-renovate-balena-jellyfish-logger-3-x-8d05dd4e07b9818dd6c1c7e6fa3f81def088d6af_1622461004900_0.8423530844915021","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.12-renovate-major-external-major-76f62f3ff377ac7ee1b3c34ab662031590d9d4b5":{"name":"@balena/jellyfish-core","version":"3.0.12-renovate-major-external-major-76f62f3ff377ac7ee1b3c34ab662031590d9d4b5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.12-renovate-major-external-major-76f62f3ff377ac7ee1b3c34ab662031590d9d4b5","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0d83c36c2f8daf65cb10e594ed92b0afffd3f592","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.12-renovate-major-external-major-76f62f3ff377ac7ee1b3c34ab662031590d9d4b5.tgz","fileCount":190,"integrity":"sha512-Ba4BfXZUUE9Scg3SXJUZNXHbItjQyNuozVcbNFSmMSgKSGPqwLQaF6NID0kw8LRHp6vbi2O4pG5dRLkdXlm7tw==","signatures":[{"sig":"MEYCIQDsQkOHxxlFq2aZaIuKIlWysmNX4Dw9V/D1+h4tOvt/3QIhANMZqD81xn0z1eY9cm+PazmW/mtKO8Xy5tfMWI8IcFSZ","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718937,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgtMp0CRA9TVsSAnZWagAASVUP/1iWHpMRCJ7/7mhdlYJn\nllXN0O6/G4AcVVSdZ/p6FD5pK6J6PkwdIZYI6Gqn7jkzh8rkHYKi8vQy8EVp\nJamuCRPM/ZO9HvRNa2MdkZZ3piO94MwsVdMGAJpj9Lg9WAMOKgwwPsrB6LkV\n/phMTm5QU9hNyTmVh1D/YJW483v/VmdkKrxsTLgk1oyGqaJmIW/v9qMbQind\nhAAcTk3esFHgDBeyXXxV6LLkRrkhKnzPhnl0NFeJ82rD7Q5VbxLQuMd1h0y1\nl7fgXFBXN9uq/l01HMQrWtleLeJuFP9UaTdqhM5RwJrkD9/GUpvFrSJKP7R2\nz4ET+UvDhV9HBypBnx1lvaL8ZYlSzxJ66yLPbjXmwhUHQNfpG3uAgn+53qYx\nupTlmi1dVkkfJbaTMV+xtuMx+G87XhfBS4AYaPEe9gDxVICfZ271JbNd/neL\n7+W8FOASeJTv9/Vx4YbRAK8xA5B8MPvFWLlRxe6f9nj7Uwyd+YoXM+h4IFLy\nz8k0mltF5F31E09/Wni6aEZ68N6Yni2WEHc5HHPtHbvOjvut0Oc2yDECy2DB\nHm8y4U7mrkWKGSFw2i2cG2j5CJt0/xuO1WuK1hZ7RZ2LdJoDnsd9axLLzQo6\n0MID3DQarnlnD/Ounxgj42ThH0o+LR6z1RdOIEiBr4dyyKWBwKKyyLtAqhQ8\nGPgt\r\n=bUPb\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.yml up --build\n```\n\nThe tests can then be run from your host with:\n\n```\nLOGLEVEL=warn POSTGRES_USER=docker POSTGRES_PASSWORD=docker make test-integration\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"76f62f3ff377ac7ee1b3c34ab662031590d9d4b5","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"catch-uncommitted --skip-node-versionbot-changes && npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.38","@balena/jellyfish-logger":"^2.1.95","@balena/jellyfish-metrics":"^1.0.249","@balena/jellyfish-environment":"^4.1.31"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.3","deplint":"^1.1.3","ts-jest":"^27.0.2","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.158","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.5.137"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.12-renovate-major-external-major-76f62f3ff377ac7ee1b3c34ab662031590d9d4b5_1622461043379_0.8104753844380777","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.12-renovate-internal-patch-4a9eae5034fe5ba46864976b8159725eb1934c7f":{"name":"@balena/jellyfish-core","version":"3.0.12-renovate-internal-patch-4a9eae5034fe5ba46864976b8159725eb1934c7f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.12-renovate-internal-patch-4a9eae5034fe5ba46864976b8159725eb1934c7f","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"383b579c1e28a0658bd3d13c6cb54ae4db49ac59","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.12-renovate-internal-patch-4a9eae5034fe5ba46864976b8159725eb1934c7f.tgz","fileCount":190,"integrity":"sha512-NoL689a49+twvgWVIhIKw1rtSuTfLdbN4tZlXuSnoRhCCmTPx8WTLHKgZqeYK23QN7fwuePeCIzLOINQ0AWnHA==","signatures":[{"sig":"MEUCIQCmP0ZgtJIB8qbtL1/XZBzpfLab7K1QGdUVAMjan5BsMwIgWcOWr9zJqWta/ICCOdVzpjKVebb/09ctweqfXqEuQiI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718965,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgtVUCCRA9TVsSAnZWagAAjHEP/27ZpABr66Od4hh0e3kN\nG+Vq9b7r7CVkKpDd1azZ6nNkFuGZiK7f46vyBz/ZvHlld8CNxZ9AS0fYIWjR\nsGzXUxeiK8eFTJpuj0ctJeoXCzvW9EYQemsQ7UzhsFYsapt6dRCEEIS7hBFK\n0HDplXW4YseQvZesYlyLXSw0ODE1r8Bcyb/DEUrOfu8VvOIO9pIDrZWI5HAJ\nTubfTjzy/mYviTZeCn6kpCLIV811vWyhgkUqrfC9lhRofAVTbCYiCR3RZwh3\nJBbPH2inSKKQKHdKMTvHnHpwhBVs/pW2TYa0sp3eUVpbjHOt60Lf+QrutSvu\n24NrRSl4ClNTfIK/uoEh8OsijtxG7Vg13exbax+dPBwBOfQIjnkd7lSO/0Vp\nYItfd0vnmN2w7EDDSefuugV8JdP34SjocGCOpgMwjKusgNlhQK8Zxsjs3a+Y\nT3A9FQ6LPVpibG0HtjsnERG3ri2hYnkhJX4OV9kHC+whmi3+xVQQxDNxiMD2\nkNHevJvLuW9MvZeCBMltitXJ/ySDbUoRKlNngID/b1DvYM9kAhRnOILE4faV\nNHbG3+7rqOQwuMZZCC9Ke0R0DesGcBossRHSESv2tVSGwB6LYddVBhA4Z95w\nkym05bMuxroJgFBAOpxSzAZvbAAnY/kCnA/CTJlt/pZmANs2uSRZPGQuRDUY\nHbf0\r\n=PJ/K\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.yml up --build\n```\n\nThe tests can then be run from your host with:\n\n```\nLOGLEVEL=warn POSTGRES_USER=docker POSTGRES_PASSWORD=docker make test-integration\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"4a9eae5034fe5ba46864976b8159725eb1934c7f","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"catch-uncommitted --skip-node-versionbot-changes && npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.38","@balena/jellyfish-logger":"^2.1.95","@balena/jellyfish-metrics":"^1.0.250","@balena/jellyfish-environment":"^4.1.31"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.158","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.137"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.12-renovate-internal-patch-4a9eae5034fe5ba46864976b8159725eb1934c7f_1622496514293_0.45013593595101464","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.12-h32-setup-in-transaction-6aeb4788ab619c3dfcb9be5717eb8af67145c45e":{"name":"@balena/jellyfish-core","version":"3.0.12-h32-setup-in-transaction-6aeb4788ab619c3dfcb9be5717eb8af67145c45e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.12-h32-setup-in-transaction-6aeb4788ab619c3dfcb9be5717eb8af67145c45e","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5d93f2f8c156d5aa545d5eece4f98f6e0c63ae9c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.12-h32-setup-in-transaction-6aeb4788ab619c3dfcb9be5717eb8af67145c45e.tgz","fileCount":190,"integrity":"sha512-HhNpjJXoFntx2GMP/hnecX44d+bRU45dA47QSEYP8fEsx8CRgQbnJyIzN89VrnjciYNDvT1AuHVY32ccYqbrLg==","signatures":[{"sig":"MEUCIAVtiiiYhwHa/Wt55uDelGGtZp6Szd6AKfmRT2OLMLkGAiEA8lPBvpIZckzCJJKsFuHZhw5m771E6TJuFjO0aL+dxYY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718279,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgtkGSCRA9TVsSAnZWagAA2QMP/RD2f6rn+k2Z+VwoGZpe\n7lBs7yuWs4sJfr9vXGi19Aqxy5Tt0SZgNLLBLV3AxPzsM2Ar1ar4qDLfIKKz\n3EJZLwD629LDbYJrodenXqd3NsQbK47YvCL+7/agKrtXOV6x7Gj3WQ9u+7VE\nbrRrouIzjBnotBpnWvFXxe9E8HkRLhgSamyTbKvGg2ZSVwYdLgnl4eg5EiaB\ntSwxrfkPcKt0h1akbPTY8yeRmhGsL5Eb/kFR7yJQWKSZ0n9Ytluu4sjmPdFG\n9PsBTWpKV0ADxvUOInu0gbyOLzzTdrgSUt2OBNE0PMQu4DCku5DfdyrYZ3B6\nmqwxS7h8KJISn9VsDvZm6vq/kTjKNSnM72UeyPwEQyeaIONz5zyQjuAhEn8Q\no48GSLYYme0tByOuiw/pnFa0CWtEa2B98ppVrO/QuSYpmD8dHhdLamOWbuY9\nN4Ayish5xmC5f48mDTkya+bkZvcMH3giZH5RqmOvhxH0SloUDJ4wUl0BaXWJ\ngTjh6O9P7i0m0d9m2Awj22qtd/E8KVdSwMWqKfkLPiqjGKqubBmEbOmrHLYl\nl4ANeD4tgFmlufaXkShqJY2BRAfHo8CeFzq9jJ37z4MG4JkdSqjZSQOK+/++\n2niX3VA34WKlP9m3DCqchms2GyHz4c6AaSD5VI8KoL3/KZJH0GtPggp/qMDm\nU8L7\r\n=Pw++\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.yml up --build\n```\n\nThe tests can then be run from your host with:\n\n```\nLOGLEVEL=warn POSTGRES_USER=docker POSTGRES_PASSWORD=docker make test-integration\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"6aeb4788ab619c3dfcb9be5717eb8af67145c45e","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"catch-uncommitted --skip-node-versionbot-changes && npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.38","@balena/jellyfish-logger":"^2.1.95","@balena/jellyfish-metrics":"^1.0.249","@balena/jellyfish-environment":"^4.1.31"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.158","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.137"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.12-h32-setup-in-transaction-6aeb4788ab619c3dfcb9be5717eb8af67145c45e_1622557073781_0.36328681270342433","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.12-h32-setup-in-transaction-5a7a58ef30df3c1eb44a3c9cb2b135eba889e9ee":{"name":"@balena/jellyfish-core","version":"3.0.12-h32-setup-in-transaction-5a7a58ef30df3c1eb44a3c9cb2b135eba889e9ee","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.12-h32-setup-in-transaction-5a7a58ef30df3c1eb44a3c9cb2b135eba889e9ee","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e50b2ebb460108148d677d3aa8e4ac3c5e4a9f8b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.12-h32-setup-in-transaction-5a7a58ef30df3c1eb44a3c9cb2b135eba889e9ee.tgz","fileCount":190,"integrity":"sha512-8Lv1eTioyM1+4oB/BDL/Mv8pfDueTLlZJRO6nubItuy1HRyhC5JWUeCytdT9VKzFYfhM4XCa+9/l4k5TrDUlrg==","signatures":[{"sig":"MEYCIQCC0Tsk/2VbcvE6sIdzkMcxlLZEiarjOHB91R7gKa21KAIhAOMiSGJafEZ2m+nzmF9PMKn4ZjcJLwIHfKQ2ymSlTESp","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":719338,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgtkXFCRA9TVsSAnZWagAAY0UP/33sxTFMdqh8mWasr02x\n7GXKsIRPZ6+yuWloWnunyhJcRVirrEejedqVk08aVB18MwPxdQ+mbm3yJsaa\nPGe/slB6aqJMaRgBk0hKKqttVjkWdK8Lhmb7QPCtFnuuybA5dKEIGXiLDjP+\nb2fK2lA84cL5/L3V3MfT2WkHq8qzczYyrAVAdhRBEqkZV20RU8J6Z9aCPvV5\nzYpJXa7l7wBWLj+VQGoj5oJx3JWo60zdZ+4azsuhDiHhB+uliHtvWgJOHHKx\nld1rjd3Vjz8P7D1J2hW1JW1Bei3S0MVN29r8nHJv8AmoFHYSbETRvjggXAiB\nnYColSOGUJgRW/fbdHNwtx/btyproDfoajkru+LIMJgPMw1WMhxLVKltW69A\n7pDDrqM8dk+8HMIfHK2DjefObR7RhF9IopU8mSDhSz1iVdKWCCc0X5t7ja6F\ncfU0G+62qrTryuECiQuo4xAUo/zftpi67t4yiqViPHtpXm69jEcheUKnHQsu\nyz5Q3yfe540qvF0n7WPHW1GOqZ0YE6jC8YmS2b6jazipVZBDfwb7iLv1/ln5\n4ZXNdQeCgzdHqqxeEX9sY2Fz4uG3Uf5O7UZ2GbkBeQpZc7a+pDqoypNSVzAl\nasIfUP0/MTsmbGgdSukOxUnaZdDynTNOx8oLXcrGhw+RbeoEXJEkGVkMZ2N1\ntISB\r\n=bulX\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.yml up --build\n```\n\nThe tests can then be run from your host with:\n\n```\nLOGLEVEL=warn POSTGRES_USER=docker POSTGRES_PASSWORD=docker make test-integration\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"5a7a58ef30df3c1eb44a3c9cb2b135eba889e9ee","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"catch-uncommitted --skip-node-versionbot-changes && npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.38","@balena/jellyfish-logger":"^2.1.95","@balena/jellyfish-metrics":"^1.0.249","@balena/jellyfish-environment":"^4.1.31"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.158","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.137"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.12-h32-setup-in-transaction-5a7a58ef30df3c1eb44a3c9cb2b135eba889e9ee_1622558148846_0.9003852067269702","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.12":{"name":"@balena/jellyfish-core","version":"3.0.12","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.12","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f4e273b2ff4974d8478c9c7bc26b508261431a1e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.12.tgz","fileCount":190,"integrity":"sha512-UcKbMti9jVFj7dkwOT9vI2GkolxtHSkEtKIuA3UBny2efRNibyGb27pCL7VguAjFTzUT3TjC3vXdl6Wzu1Xeog==","signatures":[{"sig":"MEQCIAXuGonxzx9lJnuJmmJYqsHuiPgWKWQsDX2EQIK9jA+fAiB2QgFf+5TZ2qLI/9C4vq2pHIqv1/nM4yh3NPoJWZp+dA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":719272,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgtknICRA9TVsSAnZWagAABiMP+gOjcfOdto0BYNR4nQpz\nSIfKSeEBrd50tVLDzIxQtFYsqsRSIuIZeMpP7VMXXNL9y8qp2LXZl5rjnLZk\nrPvM5BLju6xoFftHEuONPn2/QAyIb2Qic2MtUFFD7RVzj+UPVqIxEjHV9qwt\nCyasFcTy/5lRckrobPi1jyz9TMAMXS0B3L74zf4IwGn+qNcpF6rlARVElJ85\nruRgdVJenVlX6y0TcIyF8XSYiMPHL64tlRWtDlfpqV2b3MOfjjgAPqE6liLV\nUh4bSoJ2aGFK73SrlNLUpcGKrwA+/qkamp+/6odXmuYRbPhwyKcoOyAmorgL\nehBJA/Zvk9+pcmoAy6GehdYQpFAHLJFQxcIQFHcJgn7/wOKbTEiMUkITkQ4R\n0gWC6nn+6FJ9zbx5Q74VsE77qnO8k1iZdMCmpcJIGI6VqtdJRpNq+Tn7OLly\njKOzyls1QsurU20Lz2elUdKFqwV6iA/V+AjQpIS71LojVUaC+TyoSVTW/ZW9\n2X5hka3T5fa2dygogWGIdsJlfieCk/TCmDIU9IqgeRrfvKCwfrQWog3hDn8u\npH27RHu09BV/0ayZaB2tp2ggWnIbN43xh2OsjGOldwdC2wGd8RCqcAWZP2vc\n58XvPFtFsDqEZksG1pj7nae7cO+uypy6MumcKFQytxEBrnDL7us8giESpEce\neRzf\r\n=EIPD\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"0b9176f16777a190ad95c6da72768475782a5ec0","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"catch-uncommitted --skip-node-versionbot-changes && npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.38","@balena/jellyfish-logger":"^2.1.95","@balena/jellyfish-metrics":"^1.0.249","@balena/jellyfish-environment":"^4.1.31"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.158","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.137"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.12_1622559176183_0.45617094935560054","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.1.0-add-support-for-query-mask-28b12b8285a58bbd4e695a95929e874db703ce62":{"name":"@balena/jellyfish-core","version":"3.1.0-add-support-for-query-mask-28b12b8285a58bbd4e695a95929e874db703ce62","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.1.0-add-support-for-query-mask-28b12b8285a58bbd4e695a95929e874db703ce62","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"68d93c73a110b8acf66caba7f0ff325d7f54c314","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.1.0-add-support-for-query-mask-28b12b8285a58bbd4e695a95929e874db703ce62.tgz","fileCount":190,"integrity":"sha512-xYZLUiIUkxzXMmfLjTmO0mT861Aw647KScL6VHq3GXXbcjyRgJFaOsv7hvyX4M6o4QL/7anGWfQdBUWM0Q/QNQ==","signatures":[{"sig":"MEUCICsWeFvZ8IqkC9COGnWQjp4M9XmcXXvMfjm9nRdynjPqAiEAiLndP2pTAIO2MUMFZwNvBs/F03XzLLdcBftgz4V412g=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":719942,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgtxQoCRA9TVsSAnZWagAAAuUP+wSd8FeIYhNhR2eRgp6F\ndkgthZLjPToFrvFumalm0oMghFzMxWAoZiAkG64Wd+NERNlpZbDTGXD02+kn\nuG1uzy29FwY4gqJdqE8WsZwd6lktZnc3R5h7Ojy4u0VJumUgg0LklzE6JGZp\ns63bfMEoh28KEjabpAKZ1fnWQpli06gLpwgrPK5lPoE6Ojt7axI/AmLkBkAs\ntZ8C6qZz7aQMeX9pJWtPOgEwfkAkY4hIoNEjcCx6On891fo6kNuR6vGC4ptj\nj3d7WYiGdMwU6d5fmYIsKDuDVSDueJ9TjQ7vxG+5KGJ31ZJGH6qTL+G9Wn12\n0nKUPCl1b/5pe6BcZ5NKfNlfHxz9A3nsd/ka1aH77g1VBB2EbvUrw336tXPq\nYup50M93hBQCqEKfBV5CIpn+/CToFr9XdDw5EtUZi+CqIAnYhZeiaygvcLaO\ntgsW/YletzMoDJ8xLt/7Fe60LROjQtMFr4DxpFCVIlCDZDvHepTTqsdTn0uI\n6CJK6S2aJ6o4AK4qKMaKsXsRNcCNY3XeaB84DHPYAlSBzj6lGn3dnqIY34JM\n6mZtNinO59LFnAp1uAeX+SxOHE4aRxk03rHfMjQ00rXv9qYVAB6FXX3S2zMs\nZ7p89oz2Nl3VAge/7H5UzaZfF2J/cLXHdbq0bx4eGDUF2/3E7ahJdhYlC6IU\nnVAi\r\n=bbF8\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.yml up --build\n```\n\nThe tests can then be run from your host with:\n\n```\nLOGLEVEL=warn POSTGRES_USER=docker POSTGRES_PASSWORD=docker make test-integration\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"28b12b8285a58bbd4e695a95929e874db703ce62","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"catch-uncommitted --skip-node-versionbot-changes && npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.38","@balena/jellyfish-logger":"^2.1.95","@balena/jellyfish-metrics":"^1.0.249","@balena/jellyfish-environment":"^4.1.31"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.158","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.137"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.1.0-add-support-for-query-mask-28b12b8285a58bbd4e695a95929e874db703ce62_1622610983965_0.0044182012192970355","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.13-renovate-internal-patch-7bb87a3b3f965f91ae318d941829b8a7e9354b92":{"name":"@balena/jellyfish-core","version":"3.0.13-renovate-internal-patch-7bb87a3b3f965f91ae318d941829b8a7e9354b92","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.13-renovate-internal-patch-7bb87a3b3f965f91ae318d941829b8a7e9354b92","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5c1561f2b413ca0b7bb82bb66546642b235d5750","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.13-renovate-internal-patch-7bb87a3b3f965f91ae318d941829b8a7e9354b92.tgz","fileCount":190,"integrity":"sha512-dm9slc/b2yfPm31KxmgFGbYuTziMKPPMyZJ8isVzhqJMaIC27fRBnIisXYGX0WzMGMth1Pn+fd375pXACjGc/Q==","signatures":[{"sig":"MEUCIQDQyFMGmb1H2to3rOuOE9PNAhjRZvq4GHoeFwNnFBQfJAIgEAPt8EunbYRxR/7LuiEe2kAnk7EVin2XsKuTosbyT74=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":719408,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgtxejCRA9TVsSAnZWagAAbxQP/jPxePlyqI0eakafCMje\nIhzkYUIzf6CI1VuRi+Rsl2Fdtuxsa+/umTLXBjv5tw5TN9suqW5o4mJCk36j\n2cRLyaZ8MaAyTCEII5MYEWg9F5LZBYY1LFxV6/0QcCd0NcVRaRINUkuFazjT\nyx8vwK12zbGa5ZcPNRIuOySop1nYqq/HLQuIajmnst5c7rRjr/hgbN2Q3Sgp\naWQOo5OrjLrG6/hZtQjR/7FXMolzD8HYUlBjO5kvxqMWr3PlbR/59Rhla7ES\n9IM/ZoOjaF9004K9cWDz52+AH8jDMyW+/SZpVrI7zoMXcwOQdGzccmHjGCOS\nVbSB3vecGDLT9KGnzQliCRNsI8ugdcVKz7BOcTAPuS17x6pgBiZ2HfaJiUTb\nZ1Ne0l3dKU3J2EjbR0tsVf4AUOgogjIFclTMvpm9HEVg4C302HW8L+pUxxV9\nd+crIRl11i0Y4jtBs0HO+aZ5Qk+DP6baVPPA93thZdif+A9OFqwl8rqddv3c\nJRiZF+DVX1x4B3K0qzvSJcmYY3G27j4sduorwGWszcUNAMhYMmhhT9bMjO3X\nf80mc3du5lVr/ZkTkhgU6em61u3STlk6B1urE/1ch5t5SA01I4KlnpAyo+S1\nl+LyAIxaY7ZFuWRkqbVIxWvkspPAVvZ8cT0+IkRQ1yhAzLvtwpv4D+9R/S5m\njZxh\r\n=fwNH\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.yml up --build\n```\n\nThe tests can then be run from your host with:\n\n```\nLOGLEVEL=warn POSTGRES_USER=docker POSTGRES_PASSWORD=docker make test-integration\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"7bb87a3b3f965f91ae318d941829b8a7e9354b92","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"catch-uncommitted --skip-node-versionbot-changes && npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.39","@balena/jellyfish-logger":"^2.1.95","@balena/jellyfish-metrics":"^1.0.250","@balena/jellyfish-environment":"^4.1.31"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.158","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.137"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.13-renovate-internal-patch-7bb87a3b3f965f91ae318d941829b8a7e9354b92_1622611875134_0.5396558253005252","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.13-h32-test-node14-b79a66b71857deffdbc504b507f524b8c5dd574a":{"name":"@balena/jellyfish-core","version":"3.0.13-h32-test-node14-b79a66b71857deffdbc504b507f524b8c5dd574a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.13-h32-test-node14-b79a66b71857deffdbc504b507f524b8c5dd574a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"37ec955f79719ae489f656bae80bc6a096ec972d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.13-h32-test-node14-b79a66b71857deffdbc504b507f524b8c5dd574a.tgz","fileCount":190,"integrity":"sha512-+JB9ZipjrUevO1OyT77MbHX7DFbE63Fgb/FI8spxjmXFKGc76fXPMf707uHQM9UA5YqeMBN2pBK8qO847jKtLw==","signatures":[{"sig":"MEUCIFdjS/dbCsRKKQqn0MYR73PU/5toZ+vDeZRskP+yOlh7AiEAyifb13V+XrvEoBgDBLyKEvgjsgvQpwAUWdnB8kiICPY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":719412,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgtzR/CRA9TVsSAnZWagAA0YgQAIqac3LZbTshnPLOOjqq\nh8HrcIedOWmwRL1KMZzBqqnmNh+LpcoKdwPdKEYbIuDrjPL5mLON0XTBpGwu\nms1uCvHfsCrVzrZlMwUnOP0N9EpD94p3uX0FGtZo0OvWNH77oZ99b0Fwm4uV\npCE5r1z6l0oKXBC99Hmxvgrnxmt9Gs0BOYMq6J5/eobF8oZVBd5ZHb+eiMHP\nTz05YGfrPrEuy3JFzaEBKv//XIbf3uypnRgRw0SmTO2PFQIsmh0iNaHz54Af\nqyM126tIqedASQswetn0Kfu4ewvwenNzO64TfsYRfZXky5cydjHTSRWopm+P\nLxt8y4PrAZn3WDmDOiDhMczR399Ss86mYnOnD022WVJYurK12e5D7N8KlDqF\nM6VfESv0mCNygZItCXXGVDS2jy5VEI2K0NMaTikrGItgDuAZJZMj7kK5O1/A\nfWa1YJjqqh+mHO41FO5snMI4Gu9uICcumz5o2pi4z1rrDfUwIXRD1ajxIk/D\n0aLSL/pfaaOO6PAZnD4Ckc9Xxogv81TLr6nWFsqhiazPftBA2i/ldLzNqbes\nLcAg1Zp1Tq1DUt9wRskb4eMfXYA/yDV7//fVMZ1xqhR3SMV7MAmH22SQ2vu5\n2buEFATHf4/qr9WU/A+HW8GlzgyxMN1n5yMeYRqmAUjJZ0ubIvDFRph1Zmvr\nOX20\r\n=ZWjX\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.yml up --build\n```\n\nThe tests can then be run from your host with:\n\n```\nLOGLEVEL=warn POSTGRES_USER=docker POSTGRES_PASSWORD=docker make test-integration\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"b79a66b71857deffdbc504b507f524b8c5dd574a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"catch-uncommitted --skip-node-versionbot-changes && npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.38","@balena/jellyfish-logger":"^2.1.95","@balena/jellyfish-metrics":"^1.0.249","@balena/jellyfish-environment":"^4.1.31"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.158","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.137"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.13-h32-test-node14-b79a66b71857deffdbc504b507f524b8c5dd574a_1622619262837_0.5827514079218741","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.13":{"name":"@balena/jellyfish-core","version":"3.0.13","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.13","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a9fc5764b3bdc5334f8c6c9b6ee8cd3585d058d1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.13.tgz","fileCount":190,"integrity":"sha512-nMy0BW5KtfVOQLMwu4ZvZp4curWKODXImCrVqW1UFeuIuuGmDE5ny6z/Ml5oJsZ1lgl1Wis8LajGWjMGDUyMuQ==","signatures":[{"sig":"MEUCIQDzqI/4XDvk/vTBf8g/ULbA7cmaw3QJszd+pz7oObwifAIgMcZ7+f4e855uEoUNMg6FWAYltEHjkxsAcgawdZuVCFY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":719355,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgtzWqCRA9TVsSAnZWagAANCsP/ihhgxkxSodOA+fVbklC\nlX3qLe1953YwpxYWm7RyqPmVWswfLo2QUjZ4M6sU5NkdCpb/2c0P+K4G99Vs\njkvNJMqx4oPn4+1x85Iyc+34SYJewZ3hR/OO2kn6x1npa8oVN4/yuclOp3bd\nW0fUGUU8ZGmP2qjKiIEEE0r6NY3lwjQpD81dsOqodT1tBS7bg975zxHDY2Kc\nqYt+lbjiOSb+9F5eYYk8S5gkEGtA7DwzhMZSnZEHPTfk6iKSqCbbah/OVwE4\nToC1FVxlCnkaW7Xm3SGL6EGvdDqUxn6pvCj0o5ZANhK0RtzOcQa64OTEQh6a\n4lxIF+ott4RHaId3A+EmQd/3V8+m+f4dkNIi6MtjkVbXlFCTmC2II7VXT33o\naVIREYACeOgbKjaKKfEZH/Eocs82gsV7VLaN/a3XF8h6/UMvHiEYwhdbnZ+U\nEebuwBrrkYDUDjSqK6bd2Q82Kh6pzLIMAzl3bkXdJwlaHmtyvd0M1b39hvTv\nloWpGIES/DsHgQAVRXZWSPmi+pQF3epPxMNiwe3H9Ay618tDk21ojmjXpC6P\ncLuJWiunMDzgE1vkeLGXEhF732Blsh+Dlm2sT3Y8Eg5FZiN9IVIfLziKSfol\n3D/K1BlM5InAAiq3Me9G4fNazAKZcwKBqd9Wqg3iF0O6c6O3C9AdTb4Yzv35\nfHgH\r\n=RU8H\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"002d6933d962318c43e283cff215481e4f4ca068","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"catch-uncommitted --skip-node-versionbot-changes && npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.38","@balena/jellyfish-logger":"^2.1.95","@balena/jellyfish-metrics":"^1.0.249","@balena/jellyfish-environment":"^4.1.31"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.158","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.137"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.13_1622619562236_0.9971563391995331","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.14-h32-simplify-transaction-d3c1947ad6b73a811275de2622027918e3517916":{"name":"@balena/jellyfish-core","version":"3.0.14-h32-simplify-transaction-d3c1947ad6b73a811275de2622027918e3517916","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.14-h32-simplify-transaction-d3c1947ad6b73a811275de2622027918e3517916","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e84ab1cb3ffe140fc4dfb59ae559ac753a0c75fe","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.14-h32-simplify-transaction-d3c1947ad6b73a811275de2622027918e3517916.tgz","fileCount":190,"integrity":"sha512-FjmjEuMdhJM2z0tZm2dYUkQ2u3+Ue4B8e+XoKwS9pIqkZ/RWTzQyqCRFsbD3VQhWG/uv+muT793DzYfhZopD1A==","signatures":[{"sig":"MEQCIDbv+3PqEXC0x4ZqLW6S0m4+7U5yygm/IPOLKSNZL89qAiAPDjxlKb2U2BNmR8XIowCaTIf2wuEjYq8Weu4nJ5YIIg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718050,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgtzmWCRA9TVsSAnZWagAAKAIQAILn+o72Q/2ppRBMvE4H\nLeXYKTr4n5xy5LLRx1hYYo4ciIBoQyDSpmptYwlQdlxN9ZOxeNP5rmff3Wdn\nWE1wMII7NnR0pvjSHkcLpvifVkc3lV8XQ45kP+dhH5eIsx3WqOMlGoAwemTq\nMNmE6ct3UwUS4qoT4KsubOM95Ht94p95f7lVbCDob9GJ73gd0fEpPrO3JOZN\nMPV1vr8NHQNFc9HB29bX5HQA3AWtJm09I58YQzhDm8Nc95AtZtV97DmasCUq\nV1jmHbvlrNcVklECk+muMOw6p4vbGblfElKUNkOrueGICTCdepjHcnMyygmB\n0gESgL+4ZvOisK9zzYul8jTsoz/XhkFMjpZFGdQpgNfUKnix5e624VUrPufu\n9ey2SJNE87w6NhF5zH4dwRVqz5xCcTFzYgLOXoSK3fJkyT5J3Q1m5A55FrII\nzy3nPGkfB/0VblZIrGUXOUdzjl4lFLKsR6hry3N1J9vTfG1l8jWyyra8qkO8\nDqrpawXiQvLS54d9QGKn8UPOl4G3GUMQMfcjkslMofgDheHx2h39FdSKMiCT\nW11OzTf8j55f3Bim+U7tkJWtNtIYodwrJPrP5bs1Z2/3KmwTedz75mxQQUFV\nqujOChqtlCEyypVHvGtQQEzsXZKMtsilRRx6MNrS/4Brv0wFBdovk1mRZs8f\n8xJF\r\n=CZrx\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.yml up --build\n```\n\nThe tests can then be run from your host with:\n\n```\nLOGLEVEL=warn POSTGRES_USER=docker POSTGRES_PASSWORD=docker make test-integration\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"d3c1947ad6b73a811275de2622027918e3517916","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"catch-uncommitted --skip-node-versionbot-changes && npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.38","@balena/jellyfish-logger":"^2.1.95","@balena/jellyfish-metrics":"^1.0.249","@balena/jellyfish-environment":"^4.1.31"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.158","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.137"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.14-h32-simplify-transaction-d3c1947ad6b73a811275de2622027918e3517916_1622620565863_0.35733377615979145","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.14-h32-simplify-transaction-bd9b6d65d305b3157044184ebda58e31af1ba697":{"name":"@balena/jellyfish-core","version":"3.0.14-h32-simplify-transaction-bd9b6d65d305b3157044184ebda58e31af1ba697","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.14-h32-simplify-transaction-bd9b6d65d305b3157044184ebda58e31af1ba697","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"608ed4b0406ca4740496a6b08d384c08c512f1a9","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.14-h32-simplify-transaction-bd9b6d65d305b3157044184ebda58e31af1ba697.tgz","fileCount":190,"integrity":"sha512-agLuXXXVodlG3DJPtkn0pC/2KJGl+fIqMAwkFMJYdL/v2AjDudCgr/fM16KYaVrWxdOBg0zpAEKAm78W3V1rbQ==","signatures":[{"sig":"MEUCIQC4EQswj+bJNzGKh+0SnGKf/YvVcwC84CdN/lL/iBTT/QIgNM7zF1sKwJKa8W4mXXkX/befnYsBss5rHJ8M32ZVicQ=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718050,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgtz7FCRA9TVsSAnZWagAAZIMP/AofRtaOYkKX8E4ZwfKN\nAzL8fQniGLtKm9Ijt2qK9/18MhU6siNcaZ1KqFTgWnTrNKBudz2rg2iBqTpU\ng/e2mD+gUgW+wv/Msy4I+qGjRyDQmSHJev5MhIrh2ydxEb17B+XsZRY77O9K\nKc3w/Uwiol+Nl+XtEysgSqcIy89xiIjdGmtIjQZQt4hSmccEsxwFgK+xM2Oa\n7h41+3Auu+En07YAvp5rgmwG0usOQ7yKZsZn4+gxhh+9xj6DWOfbhahrKnln\nFROW3CJUiGdgeQ4kJ5OZd9l0L+HclW4BP2RpXMuBj1x5c5f1gbPkibaw5Iwt\n5WETErDAPFZZ039cL5D0Z5dgIO2u1o0P0OGIim6pGR+UCzgL+yijeM1vxiPe\nr4nhAVb9NLE/OE79RAPPY1N2f6tZr24wlxn+1vijUtILv1KLE5iBoF4pA3gY\n1ocT/w9qdxi3GSFxzSmDXnCTocByU+1hhqGGgXjHJD84u2TP/QitV+iILCIL\nmGi8bmUzuRozAwPvqow5xQhmC7EN1ZZmjttUvcnNz51qtBAyRi+fIEkc/xUq\nKi3L3bxCUcr5Fn8sa6DvRDM8v9F1W+CHUkecDwIKCeeBhObmpprdk2BbsnPY\nC+pKb6Q92x9FhHNonMet//EgKrfsuJw4tM4G0KNP+tcl2RqEc/lzpkyGt3Fc\nZzu3\r\n=9Ecb\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.yml up --build\n```\n\nThe tests can then be run from your host with:\n\n```\nLOGLEVEL=warn POSTGRES_USER=docker POSTGRES_PASSWORD=docker make test-integration\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"bd9b6d65d305b3157044184ebda58e31af1ba697","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"catch-uncommitted --skip-node-versionbot-changes && npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.38","@balena/jellyfish-logger":"^2.1.95","@balena/jellyfish-metrics":"^1.0.249","@balena/jellyfish-environment":"^4.1.31"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.158","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.137"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.14-h32-simplify-transaction-bd9b6d65d305b3157044184ebda58e31af1ba697_1622621892976_0.11596365265449715","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.14-h32-simplify-transaction-00a7d260024c25209139b148e0c399bac237dbe3":{"name":"@balena/jellyfish-core","version":"3.0.14-h32-simplify-transaction-00a7d260024c25209139b148e0c399bac237dbe3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.14-h32-simplify-transaction-00a7d260024c25209139b148e0c399bac237dbe3","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b7888e9ee2f854362a307787763cf7f1e8a8ed7a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.14-h32-simplify-transaction-00a7d260024c25209139b148e0c399bac237dbe3.tgz","fileCount":190,"integrity":"sha512-r+ZH/3f8vy2V9crz+ljt7HwULdk3PvNnptq8KEFjsidMx/9jpSv/sU4XWhxWCpkZA+7YPvwsQJ0/uAc0UXxMBw==","signatures":[{"sig":"MEYCIQDy8I6WQaXVQJ62Qq0stiJK9SmRm3mpjfcnj1PRYUtGawIhAMmzS5dwPRAM35uBPPDv0g6VQbYl7ohRiGfmaASryEnh","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718042,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgt1O0CRA9TVsSAnZWagAAXisP/ixeH9LeljUxCl3SEAni\nVQ3UdINB05tqVpUvJ3Va13RRXHPVuk7ttRKuzeAer8ZmhGLjeV0DvD/c4LWU\n2ya1bVniCO8sQZM5evxcnPSeXGi3hr3yjJGzg3trRXHUfr9I+RKnt1QU3RIm\n3OIlyXB5K/N3ldBEph8oRMUKaeyLeCI3OTsNLKDkpGm5NK8D+idyiXhDXKto\naV0wmY1SZgKXC0zVZGA0tmPsYMz4nCZ4qYp4+xQnhaK5HGcEXtX8V3gG3X9Y\nNmFCPb5j0g7ljDqRGjtUAJD9n1FVMbsGEtuN/w5ZkII7TLek19bqsV+bzXXJ\nVPde2VH7LzDa6CakbHA9KHMdNT2o4JRgYsY8sJn3tGSDoElmOmJmgcfh47XS\n96Yv+EQGuukSWoW9O+z4pea3J+IqNZxMjfn1uxk+LLuUsGhlAOhT0gXe8v3B\n+r5n0Lpnfld/H5zYn+lofA7riN8fOkif7EddbCyGuT19tCeQb+mGfiEqJkcc\nDIyGOjN0dr9AnIMV05nM9Jn9IbQu64ZabzpaabdYWiW7toMVCDTnv5DGKCQ3\nzp1/sfgOW//ZzBO7q04dDXJEl/kDGWKuOHSkcyfDDCenCKRD/pYFVptSk3hx\n5whzd0cfZ18E9YBr/WBSok+uPt9F1v2ynrEx1YEW9+GDWwcuva70VDCQy0XC\n6s3m\r\n=tOqa\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"00a7d260024c25209139b148e0c399bac237dbe3","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"catch-uncommitted --skip-node-versionbot-changes && npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.38","@balena/jellyfish-logger":"^2.1.95","@balena/jellyfish-metrics":"^1.0.249","@balena/jellyfish-environment":"^4.1.31"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.158","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.137"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.14-h32-simplify-transaction-00a7d260024c25209139b148e0c399bac237dbe3_1622627251341_0.582719240648212","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.14-h32-simplify-transaction-1476eb418c5afbb7af78f0ec2e09eb9701c62535":{"name":"@balena/jellyfish-core","version":"3.0.14-h32-simplify-transaction-1476eb418c5afbb7af78f0ec2e09eb9701c62535","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.14-h32-simplify-transaction-1476eb418c5afbb7af78f0ec2e09eb9701c62535","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b582b2d2d4ba12690239c69db187c04316680e64","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.14-h32-simplify-transaction-1476eb418c5afbb7af78f0ec2e09eb9701c62535.tgz","fileCount":190,"integrity":"sha512-METYc8UVwWXdFaZxikx9g9weFP9ukSiZ1srJ5jPJyDe5kYRN1g509+nVS/4z+d8PKcK7n7T2l5G0MUBy/QHKIA==","signatures":[{"sig":"MEUCIQC0+8m6GU8/gikrsCFbiQZUGRX5i2Fxyof+8xohovhMVAIgeNHsRacM3/ss5dhAvam1LSM8g+KAH8pAGM+fYeihDU8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718090,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgt1lUCRA9TVsSAnZWagAAG/AP/2yG8+szpfUTSaTz7fgX\n3wSvIb/SkCravNYpY/+iwPcbplrPJ7EFbA7tBfM/EfZtQlvlBxffenZW23SL\nUCH1OmAuLTd9orEkGOcfB4ixPDItVInGN9fhBmOb4kw5OQfWR3IBn6i5efdf\ncZ/1Cb5kX2JXeY3Rc+6Jemxl1wYo2ECBP8yj4vilTYe6DkjRzX1DJu2VNovL\nCanHfACGAodFeq09zfOdR9be0X9s8IlHVf6L/PaDQm0p7UOVWVLve28X7eg6\nHKWN/9F86dIL9/ko/udLbKmAOb2HKhAVeLRpek4GZTY7t1eq1g2VvTZ/RtJ9\n6iSGOT0F1pFztg+P5rxujQLn9prWX2fiHteYAybSGjYO2WLuP4XAcNjudETL\nTKoeTsYMEEiQnLF63SUXLzMThALdpThsr2k5juXb/airF6kxmv9oQ+b4eCg6\nWbR5EsHzUbQkQEQa46s1XDFdGoloTwhu3MvT6ydvkyI/LuV/VbG9gOlBK/iD\nuZECyorLXRAe8A29WZrUYogOTarce4gfjSkJShADDCbJfo79c+QpFV5m0HBZ\nBmY8fH5WbhNrH7ZIVI42XOiKU9TB3PT7YZwFfAoLdsN7wCrz0NqyHwiCTPCi\n0f9nZUHD5QTP/DIx0TYqSnUjA53PurFz4T8CdXVxNp5hgWEAuf3AkKYXkmxG\npgXJ\r\n=UYvc\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"1476eb418c5afbb7af78f0ec2e09eb9701c62535","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"catch-uncommitted --skip-node-versionbot-changes && npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.38","@balena/jellyfish-logger":"^2.1.95","@balena/jellyfish-metrics":"^1.0.249","@balena/jellyfish-environment":"^4.1.31"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.158","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.137"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.14-h32-simplify-transaction-1476eb418c5afbb7af78f0ec2e09eb9701c62535_1622628692271_0.020266553060445114","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.14":{"name":"@balena/jellyfish-core","version":"3.0.14","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.14","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f8743b06b603b4388e871346593a9a2b702d44e0","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.14.tgz","fileCount":190,"integrity":"sha512-xRsHBZemiDifwSRWwL4qe8+nAeyeIU4nnk5wMmBu18UwUZb6RCPV164SAkZdYR54C0HVO4fJPTJMFTOR+rDysA==","signatures":[{"sig":"MEQCIDYuorjSOPlfm3Cd/ykou2eMKDX2zXKeFCIshlqsvwe7AiB7uunMcOTynDbSj8ABdYtLc+eMpCWqrWIrmkgLscCWFQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718024,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgt1pyCRA9TVsSAnZWagAAsDEP/A9+mDyrBfae0anr+GBg\nKHbRrBgusP5uKxcC94XMszBuMUN0VobyCLPlIdCk9fF8dpcRRynjURh3jFxR\nMmAPqjWH32SXq+HXMf0mcjb6ZOkuxPgqUExy6RDGoOpaNV5lB52Ox70FKTka\nMxoZfpbM+yQRFxZu+Bw9ucITg/VGV67A/l/5FKYljMR4A+wGl/rFiXnOF4g0\nG5SLzOyKKn+JzFklY1Orm1MJ2IiuNrvFalIKUnyrfzM4sIRrYSBXnXfVoF5a\n960kk8sIW81H5w/d9ST4hLfuWhW8QMMN/VExugQOlnaKY3QaS/hsCn8Y7Bb/\nfXbbkIRAQb9iEsLfHAHB+YY159J5JbSoOGMaQ9auwvAiIpvnzuOLmWjIq5D6\nl+HxfgiSIuq1Lx96cqKKLo9rGn7rJHahAL+dg9NKyWFumCFO7Gcw88aAO3H6\nhZX50Hrg5ngg42HYtarOG3jZeG052rbmWYdwlcAq4LCpdzFRdDle5XPLO+bP\nvjomIyiuC/oE6yp3tCqg4sZg31E2TfPDVr50DIzAmb5gW+z38yOr0gj0lOUg\nYqjPWIyXeaWi3XhJFSYh8FMitr93kmBEy7y6ZgO2UxHRwaJFF8Rm127NB2ze\nRJnkzU5gB8A+gSfxtLhGPb5W6GnX5sR9xY2334q+D8rNIOGiPHYbiAdyxY5b\njbqZ\r\n=Wqoj\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"44914c5905c9371b8e78d7f20cd48bb384af4485","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"catch-uncommitted --skip-node-versionbot-changes && npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.38","@balena/jellyfish-logger":"^2.1.95","@balena/jellyfish-metrics":"^1.0.249","@balena/jellyfish-environment":"^4.1.31"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.158","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.137"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.14_1622628977640_0.12608908474582936","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.15-h32-fix-storedproc-names-74d80e7597bc307bfb08a81147968c87c94b8e46":{"name":"@balena/jellyfish-core","version":"3.0.15-h32-fix-storedproc-names-74d80e7597bc307bfb08a81147968c87c94b8e46","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.15-h32-fix-storedproc-names-74d80e7597bc307bfb08a81147968c87c94b8e46","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e5d88ce7bbf1e4fb4bca5d73afc16d873c2ace35","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.15-h32-fix-storedproc-names-74d80e7597bc307bfb08a81147968c87c94b8e46.tgz","fileCount":190,"integrity":"sha512-y0FydvJjvGHKJ0pdXbqBok/saJnakULhn4oM3WSeV97pFshRdZW45rnsVljSx6m8x8Qi1ohilwdhg0jHP/Rw1w==","signatures":[{"sig":"MEYCIQCQM/k4LVBtQyfRY3fm8qOjL7XTyL8ATEyqEJis/3qWfAIhAPTx4WXe3RWXL1Xf5PZ+6BL0YrByFBVmvimXeA2nOk8+","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718358,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgt2R7CRA9TVsSAnZWagAABL8P/jvEoJi3RDKOswW80Fgn\n9h89ykBjTOT8ZAzMh0jZD55zgvl9ASximIF+qXRc3fhIcdnXWWIZqW4J/5eP\nF0EQ7RDOw6fn/J85RYsPpBzLrHvH2WU2hGN3DpfFFwg6ldeq9B6LSl1OfWpd\nuurZQU3jrlylPgpKDx65CuFjEtLHcZX0vidJGCGNHpvEbsXKM4poy1Pfs/vc\nX4pDfo2swxNllRkPrAdmyvLo5IryZlBYJzI3nkxJ2FNPL1achhksouHFqUb1\nRza0qqwNaG/Jt5fSniVkJnQAr9Kv5Kpfybqx35pFitIOjKdzF4vmURe/JRJD\n3vsR7bYwyhcoY2jfpbPJoC1bOKWEcQnK25uuUBrregiSXzcm1DZ9osQgSNqX\nKbmfy3hIHVIfQ81tMIGfckrCnbO2wnUiFUBpKfF/pGp0O4XHudE4zr4pRYGm\ncs0G9koqqUZfVrIb6z6EtB82NkPmGFo+jeBUah9iHkj6K1QiME8WxA68qFtJ\n0HbXjT0U9EMr+InB/sq4JurntiwuMA9mCOXTTrfgCH546jti5vz0MAqZr2mC\nTZKh5zrnjkA0JSWIBzpYsv5xg6nrUfyMx+br5I9Zs60DPz3E0BhQ47YaPQwW\nIjIPUmLiu9m4hXAGOtkaEdoxnCOdHYJerv0m/3YX4/OmlWfqXxD3nFp4KaXt\nQQ/2\r\n=5knj\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"74d80e7597bc307bfb08a81147968c87c94b8e46","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"catch-uncommitted --skip-node-versionbot-changes && npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.38","@balena/jellyfish-logger":"^2.1.95","@balena/jellyfish-metrics":"^1.0.249","@balena/jellyfish-environment":"^4.1.31"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.158","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.137"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.15-h32-fix-storedproc-names-74d80e7597bc307bfb08a81147968c87c94b8e46_1622631547125_0.2920713049664856","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.15":{"name":"@balena/jellyfish-core","version":"3.0.15","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.15","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b61676792a72f9e5bb436aad1d96e5132e336f7c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.15.tgz","fileCount":190,"integrity":"sha512-TjroaJifbCG1j/ClO2Q4vq3KIonaZY+XjUu6UcMCNxC+8o6vvQFKxomFOs0jiBAItm15gqAUDrG+ZcdPA0/4ZQ==","signatures":[{"sig":"MEQCIDRMwAt7krzbv0CAc/DpBNQ7T9MiZi9mo86Ln3RyLMm6AiAmiyit7kG+EzNPErWBIfXuchw4FW6YW+kFX/NEdPwpVQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718292,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgt2WdCRA9TVsSAnZWagAAv5QP/jcWvGyIKods4f+6qHuy\n81gXhq/z+s7Z+aZOB+lQNVy9Nj1KTLe9bpqwyWwu7xTPewRsMi1VnV6xjNxK\nWENvykk9raPHKoYg4XvCDQOs8XO6qRrLxgTEPNeebzDJPItqL707Ba25QeHa\nM74tL025KUdxhBPmgiQQnUvlhxCP+xglWhmKzvYc/F+nZoD1X4UJYJMUWZT5\nvHnzKYqHrx0sXEG5CTuDxcHpi5h3lK0XzXpDpd2tCkroCCMVEoo2vr9c1ecY\nMqNNPARG5g4hf1Wg28Wh7v6rxamnfVMXrKgUg43j+YGQReYlalZcRYvz47cd\nj18gopDNurwoDa7Ibm367/a7wnGB+2FBJNGC+qQuOAeEB6vDkMJ0DdCCwv5Y\nPFp3GJ0cdx+qWQkF1lPqsXLyl6WGL7cGbxOXttP0ENqC2QE4/wZfa0JX/avx\niJXBvMNTp7vIme6nutFferqjyi4xnLcsEfQCTBGMQ6BxzdXLXQijLOVpw5ct\n7Xq9AsR8TGrF2JNlptYuiD8Gt1dGVc/M17EdDbj74zGrHkpc2AD5f5y817i/\nATLHtC5B69XjYjbg0FBvHbEujK4X0R2WlH6A1Z9NCC/+pYK0yJBgM4RQjm/9\nQ4ZZJg7HANd6vslUYdLUgtFb+IPVBWClk0OATqDbDkQ9qmgHEExmOjOVsOhX\nvu8W\r\n=4Xkx\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"6d7dfc14eab5094f5f8e32639906d5f4a99a2f92","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"catch-uncommitted --skip-node-versionbot-changes && npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.38","@balena/jellyfish-logger":"^2.1.95","@balena/jellyfish-metrics":"^1.0.249","@balena/jellyfish-environment":"^4.1.31"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","catch-uncommitted":"^2.0.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.158","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.137"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.15_1622631837390_0.43255486412948385","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.16-remove-catch-uncomitted-9cee188aee92592a659d0a5964788904a7cd7d3f":{"name":"@balena/jellyfish-core","version":"3.0.16-remove-catch-uncomitted-9cee188aee92592a659d0a5964788904a7cd7d3f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.16-remove-catch-uncomitted-9cee188aee92592a659d0a5964788904a7cd7d3f","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"fbc1b81a6344d1c42b8b462ee2f7d0512a61d626","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.16-remove-catch-uncomitted-9cee188aee92592a659d0a5964788904a7cd7d3f.tgz","fileCount":190,"integrity":"sha512-rArFR9X0Omyqv85pzlX7BMsa+30i8YE0sLct08AX3PaRW1Q9931QGi9TgxBmfm12Qcf1I5013apsd5Db29xC+A==","signatures":[{"sig":"MEUCIQDXCnN0GYFUsIOoVs+ZvlS1Hg4xxxVXPjDWmxCedaJs4gIgbTrf25uxj2D8rs2qCxyDZSpBJqeAwZX5indD5n9hF4c=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718349,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJguBHuCRA9TVsSAnZWagAAZosP/iJ3aJlZJHtJBakTYn2Z\nGANKY2Drdb+lVIR2xvZNPJEWlJl/FwQ4y5+ha4kxxcHzEfImgh2fnkmE+DO/\nORfjyww79cDYbrdjklBhsQQoSSyhe1wleiNxFRqXJ7T7koBQaPoQtyxSJYI/\nRSYvg4yKDS2FOXR3dDpYS1HW4lHFplTTl8Y12zUzchosfiZnM92511UHV+ML\n+mTOK5SMaNAomFjTN4CciB0UK4HzijTu+Q4Bgavr1XIcpb5roXYCVrCB/On5\nHuUAQix0v4ABHwSHk3ezy03LsCl/jzSgjneDCEe/yC54EtC3Nx32l/Hxx10K\ni1uS2d4nlTpTR36WjZUJOP5MZXpsS5Ax0UmAC6A9m1T3+nOb8y9uJ/+kg+AM\ngFZrmZedAhKhdNMBX12zoaXDTzAvU/1ifkAMyDd8VBOfHmJ6q2rw7kFE7pQR\n3JdpSWIM1Y1c1fQwr4J7g8SUaZ/XQk6n0kYPp3b/xV86NRzCZ7J+g3OQcxu0\nFbNZ2do6y2b4Wv934v/fbQBxfAAyBuJ0at4cJk9P2aLXVshaa52/5gAhGb6l\nw7fQG3p365RBTyxmQoz7yW+/G5aIYrWyquDoMkI2iYaYDEo2lLy4LmPiRxjp\nr5upo5V6CHkobbh1r9+As56pN62Cy4y0PBg6KOMuwev3WZ8TKlA9dusfc/mR\ngIwR\r\n=PRhI\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"9cee188aee92592a659d0a5964788904a7cd7d3f","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.38","@balena/jellyfish-logger":"^2.1.95","@balena/jellyfish-metrics":"^1.0.249","@balena/jellyfish-environment":"^4.1.31"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.158","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.137"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.16-remove-catch-uncomitted-9cee188aee92592a659d0a5964788904a7cd7d3f_1622675949890_0.8513196061853405","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.16":{"name":"@balena/jellyfish-core","version":"3.0.16","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.16","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c758576803132f60974d725491b43fb8f363fda0","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.16.tgz","fileCount":190,"integrity":"sha512-sxHgXkz6Sijoo2mnxxDFm2mbkjYhbI6I51jGAkShB+nMJvUy/JzRjn8kmy+oqi8T6NMIE+YQXyIgJYZTMEaxgQ==","signatures":[{"sig":"MEYCIQCkDlriZ5Kea7rtJEXRdDX6dRaUvf64MMY/p0YRCTVCvAIhAMI1mvafazQwqSn5iw4rl2TF9FwTG2pfu3zr/d7InBhV","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718284,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJguBMXCRA9TVsSAnZWagAAxD4P/3ZmdKmpuIfoVET5mUvi\navteTFU7I4MCKtLYu0o/S7k9rrUpElqqSwfIL0M532/f6A1Xml2GRng5Dx4/\ncF88E/GqzCJ2lR6sg5xh0U+qgtLtX7/L4n/3NIV37H4vmq4tL9eDtaT2giW8\nWEusx3hnmwmlmntd9UOHJ67Dh6OR3187s/bvEiCNQhgzHzyC70WmvESxFJzW\nciNPxWanF+ag8Vrq42pqRweHKb079tsTHcAiZHOd7UEvPRQpyrcllPiQ/I5C\nfQtz5/64rhre9czz0D30XgIjtkbZojcWOr1fSWT/TIPGldOfseQhCm0U3OeS\n/X0l4+BIKKOnF68smrL722ghvSjZv5D/8mk2MzLeg38Rdd1/tCWES/iPgUSg\nPa+UVcca2D+vr+6wgg8+fftq9Kup102IaEcj89km74MOehM6RIa3QikWcUuq\nDEas+TJP5HMXVUkdT5BgCLjXChcdQzyh6Mcgi0lV9WibpoVXzGhdSpAawXS/\n7AXsK7UME58k824yHek2I9eeMF3yI3un3zaAuelPEs45sm7jAP2IJkkjcGk7\nF5nQbWpLAo0LLfp0qdPyL4ZMIltdd+WZ3l8rnpO38ACUctz5QTo0FKVFijTY\nh6iELGYzGxekPfoGxrQfiZDEfquf20wRQqt9WPQAsqLH+MAuy9Y1KyJ/8KoI\nDGq5\r\n=tU8n\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"7308954397268e172054ea3cbf0119af26d09bdd","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.38","@balena/jellyfish-logger":"^2.1.95","@balena/jellyfish-metrics":"^1.0.249","@balena/jellyfish-environment":"^4.1.31"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.158","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.137"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.16_1622676247691_0.38978147855295076","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.0-add-loop-field-ts-5d73002cfc71714b91693c7d0d1419e708097a9a":{"name":"@balena/jellyfish-core","version":"4.0.0-add-loop-field-ts-5d73002cfc71714b91693c7d0d1419e708097a9a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.0-add-loop-field-ts-5d73002cfc71714b91693c7d0d1419e708097a9a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1e102f6213e2ef2ab4e1fd036a66958feaee62aa","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.0-add-loop-field-ts-5d73002cfc71714b91693c7d0d1419e708097a9a.tgz","fileCount":190,"integrity":"sha512-HX4AuPKek8Te7hw5vjUHoAV4Do6Cx0ho0iy9F0DDn0IKZGLPjWlqlFkN/BvtDF9fuvXnV00xyqECK1oYTQ/cDg==","signatures":[{"sig":"MEUCIQDtdMjX7vUB23epUN5JBzeDbQw2/Fml2rwSFy7GKe7tjwIgfC4xr9QiJzXHDHAffVLT4TUYEbnF6HLVwpcJ4R99iiQ=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":721563,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJguEpoCRA9TVsSAnZWagAACggP/2hSBCC2z7lv3T3dUN3X\nfJ+tTyOAvQ/f7XET/OXmeqmYeuyO36kM6ee17PAqehBfnAtr3UIZK4Gw+rVN\nBCfgm0wUrAKo4SPsREM5QsZ5b04t9i2MXiVJdZrAvQbB48qkWdHgTejShhxE\nWMZJWyvTNiLCi2WmeJ1Xp4/77Uk4oCvdUzAK+1kIyz3x3yyePjJ/c8G9fSxa\numj7xufHgl0hC2EN+myiDt7LNqXsXZqj/OuedzfX6DKQ0bF+5zE0NAIM4kdl\nxlsDuCJsA+z+GBrVjfS4+R5ozlUahKGYdWcoaMAGaMDd1DAL4YKZ4eDIAxFM\nXDs27z4PiNnCCR/GCRERVukEgIUgt+8O9DC3mTJI5gAIGWy/zDh/B22WPR/C\nGa+ysYTKjYpAc24iInZJGE/p1ZV949lXMypTWxX7CH2Rvyk360djNPHHdZ6Z\ndSxzz24w34vLdvaA41bBaFwVu7UvSADQaZZOIS6S62EiM+uehOjtVIQDju0q\nhTmpJkWCKOBKbUnu/p92U+xbvIIfdoWRjcpJXGsivHQJA/xZaE0goCoktg6Z\nPnTvqCWGnhWzz+7d7eD1+rpUoSatYXUnAK4Pm/nXruBfOn59SMkZhVouMKU0\ng8aTCpY8gPCdV4uGBso8BVYBvUKSAmcT1zn6kA8ujEAYV4BOrfr+V5efjZaS\nvKtG\r\n=qnWe\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"5d73002cfc71714b91693c7d0d1419e708097a9a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.38","@balena/jellyfish-logger":"^2.1.95","@balena/jellyfish-metrics":"^1.0.249","@balena/jellyfish-environment":"^4.1.31"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.158","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.0"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.0-add-loop-field-ts-5d73002cfc71714b91693c7d0d1419e708097a9a_1622690408004_0.21006661268737248","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.1.0-add-support-for-query-mask-c319525cf67b1545fe44f80522b92c786dae01bd":{"name":"@balena/jellyfish-core","version":"3.1.0-add-support-for-query-mask-c319525cf67b1545fe44f80522b92c786dae01bd","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.1.0-add-support-for-query-mask-c319525cf67b1545fe44f80522b92c786dae01bd","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"564b36fbe473f6d5ff80145bc1c1009439f2ee22","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.1.0-add-support-for-query-mask-c319525cf67b1545fe44f80522b92c786dae01bd.tgz","fileCount":190,"integrity":"sha512-JsE1RceQmQ7e7qtkCTpSo9P/+fodtunstgACqNgVjDt/DIM9Gy13KGyBYtJXHz3gZCo2NIXTBtb5VmDFmjM1pQ==","signatures":[{"sig":"MEUCIQCRGUDRkoq9Hj+r5GgUDTuvH8DcoCT9RwwHKhy4imZLFAIgde8FGavpBznEvADd6YTGhxbNcxyzbs5nd+lRuqMmyfA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718954,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJguEuKCRA9TVsSAnZWagAAmkYQAJKYZA+aGn/LYc6fSD9J\n+YLevdUXFecX+4p+IQ/gtyIFoY/dkUdlZ6mBYoKjXH2kpYbRI6JdM/pR4FIY\ndfVD8gsKYR7twHQzy/etnlhewHSdsUsYAIK+bKtTWsRmI9YnSqxckFhi29ob\nqzt2+XHb+ALG0B9iVk7c/GgEgIAXxfyvTRja1/l2tKmVoXh4uMmLZansaQ8I\n/ArAdFFXpF+G/PooB4eBCyeD9qlFs1vKvlpto+ox8LjSHX1yAI8cL8oJ39xF\nldZaZBKjfv9c1mlU1iMiCWY+Zqkflr+HVx17r7ZFmKxaxpbuSXMDVk9PAFJ7\nwQ4SLFMNS7aMeRy+gzaT0R1Mzyg73eMYHvKHzi/wN8YAIxnPdfh0r9fMdk2k\nKxs3hGdMD92y8gQWRH4cCg851S/6CCMtZC7bCFEU0Yh4VQxwO/bLZ6ioc17i\neMs5CczmsgrW5OvKgFySRoS+uTpYQQkb65MnAnauyv/9Zh7bNdDrlWish0I+\ntN1EdiM8uHDVtY4FB30Clc8iRCCYyOsuUXxX7XsqPLKbe1yfn1LR6U8/4hr0\nSri5H+b7K9wzagps4ZYLiiK+dFkxMpiaI59rab99cZNhNYeOgX6NTup6M2VN\nCaWnkat7VHxUEm3ZOji9dMCBYqOQBDSW8DFD6u/ojrRQkmPM/A1pljRV4jS0\nExud\r\n=aM13\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"c319525cf67b1545fe44f80522b92c786dae01bd","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.38","@balena/jellyfish-logger":"^2.1.95","@balena/jellyfish-metrics":"^1.0.249","@balena/jellyfish-environment":"^4.1.31"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.158","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.137"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.1.0-add-support-for-query-mask-c319525cf67b1545fe44f80522b92c786dae01bd_1622690698633_0.3691901360688632","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.0-add-loop-field-ts-be5dab956805ddc9178d51967e7bac801efe4570":{"name":"@balena/jellyfish-core","version":"4.0.0-add-loop-field-ts-be5dab956805ddc9178d51967e7bac801efe4570","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.0-add-loop-field-ts-be5dab956805ddc9178d51967e7bac801efe4570","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a746596631e72dbf6e25a5e4732fc113850b72b0","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.0-add-loop-field-ts-be5dab956805ddc9178d51967e7bac801efe4570.tgz","fileCount":190,"integrity":"sha512-FliQiuAcxx3F4uPU8jAd0Wk8uXcR14kxV8evr55+evjnN8aNvOXpwO4eercxuxyVCVj9lsSj4WZXvRkPhDaY3Q==","signatures":[{"sig":"MEUCIDiooNL7hlYUXpNWgassphf7fZjKwRY/bnMH1JlMiLAJAiEAidMg3EdyrIX2qivmP0iPv4Q3Ko7ll/w5NC52xGT94zE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":721678,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJguGzhCRA9TVsSAnZWagAA/JsP/2JRiN/Hq7UqLZGaPwcy\n9OYJ+3O7O5DT5l1KdnmknplCJBRXpSEHl2NaW87Y55Q96TrdyCL4EszdzVBh\nDTU81+MFRocWKP31et1a6eJ0Adc4tpODHZo1l7zN2IPxEMNeMcv6uYpcIyGJ\nL4BHcmUvXGt9lAbBaRKfyQy9EyMWvZkQ+mjywz2RmD07AjSZjxL2Vb8H/0u8\nFY3l2F7pQ+WnGtxOjHAE7ijii6DUCnXxWiyBKmV2HAidGiF0rqprbCY8dJac\n2I0Nx9LBz7JQaeIBnCeIRVC+NccBFeslnNHmWJ5r9VSUBI2g0VpOplNXeuya\n8ifSYvKU+U04uUbN+gaNm6yh1jBGbxnXctEkLZyBiqrwUQCOV2pakx30S4h6\nwS/UizlrERoA4/aI0p3sVA8h5GlEMQ8BI7M1zsGRKZqEiCuxL5kYUgzGZY+H\nJrTdpyeBwGPeB3QqO4EkweZcodhbg1Cvb7orVIQ5m+SkK8JAbquXkIud0FM0\nOGesiemW9wBXe8zrz9SfkZvMqrFYQcBJm9ZZqxwVB8X3CBzlsOpTblsOsLBe\nZFL2XbflhVReysdm93x5jyLgnH/x5TfvZLHGVDYeGQbYcKyykrj0bPOV4nYf\nzbm5rMEXcBYl+Mrs6imkJ/h33p7IU4fcva5aAd1BKeJxScNpD9muYHIofDRQ\nAvNU\r\n=ShC+\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"be5dab956805ddc9178d51967e7bac801efe4570","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.38","@balena/jellyfish-logger":"^2.1.95","@balena/jellyfish-metrics":"^1.0.249","@balena/jellyfish-environment":"^4.1.31"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.158","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.0"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.0-add-loop-field-ts-be5dab956805ddc9178d51967e7bac801efe4570_1622699233448_0.09739622113543311","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.0-add-loop-field-ts-d2a6e0eaed1c3ba5dc48657713d744f6d42dd067":{"name":"@balena/jellyfish-core","version":"4.0.0-add-loop-field-ts-d2a6e0eaed1c3ba5dc48657713d744f6d42dd067","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.0-add-loop-field-ts-d2a6e0eaed1c3ba5dc48657713d744f6d42dd067","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b15d7a658e6e50f371596a086bb7b2351e8a456e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.0-add-loop-field-ts-d2a6e0eaed1c3ba5dc48657713d744f6d42dd067.tgz","fileCount":190,"integrity":"sha512-a5KSYfqzHobxKsySpwml+rFQHbluaBQpgvqNHxQ/tdcG9MhQX+RVpH0rudoXiZR8ThC7Iob1Mq989QKceZu3BA==","signatures":[{"sig":"MEYCIQCFtmsBLyIH2krTAz+nFLbXRfsBVNGHALcW/bp1r+CUtgIhAJzQkar6wY8hfw5b8x//JD9qo00hs7nbZn+RqdADwyG+","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":721678,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJguIBFCRA9TVsSAnZWagAAwogP/1vYTP8GMrhkAJOy6ZXA\nBJO6JrgyN01BJi6kucN4B/V57uaGsANsVpyem4lv6vnKD0TlRH5XJJlStalT\nNzFiGpQ1tU+6O6B2yhQ2TBCORr/4Py4dEPT7LRb5I719pxqMJwXSLswpoBX3\nUTwtleFdPZFSf5S7Kj85AEjZdzpzCdEOYLswqWCUSj8ypCaYE7OYfakU0UnP\nYYNkkieyJJzuF8AzcBlYrgrnAiLZmEe8ua92rfr7WWj1lQwt96jm08a4PTPR\ni3yuBqHrEX83qtasdMG388OPu9sJNScA8zANzXh1O331OWify0WsH0K8gA/X\nlhVbdPA9YzrczDTJV2HhlMoHymLxh5ckW7OQod48bU68ShzyKWp5irtIFYBY\nuSRSvtdR+WhAArqcKBo4d9fQafd5B06xQTM649kvE+ijQ696sPp4iYOOsFF7\n4JEQLpsQ/VxGqvTwjjJ2fFpX0lYpyVua+phxq/7X1ZPYFL5njYPRvJHkpCaQ\nrr88YaVHZB3+KyKGo/VuK74qcQcOfGhSO6fOMTuX9YlipOFD4lzwKFoQQZZG\nPWQCD9EIZMbVDgJUr4BEh/qCoST1HOnwAoLcMopty1n07WKEO3OpPbRR7TCK\njAhu03oHvrkF/UHXflJJg9rekJSkkLUa7FCQZsqlzhIbklaRk8efZWDvwldT\nYg8T\r\n=d6n8\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"d2a6e0eaed1c3ba5dc48657713d744f6d42dd067","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.38","@balena/jellyfish-logger":"^2.1.95","@balena/jellyfish-metrics":"^1.0.249","@balena/jellyfish-environment":"^4.1.31"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.158","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.0"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.0-add-loop-field-ts-d2a6e0eaed1c3ba5dc48657713d744f6d42dd067_1622704197245_0.7661611639902217","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.0-add-loop-field-ts-dcfa269f5238c40c226a1fa9c25ec37a4eaadf26":{"name":"@balena/jellyfish-core","version":"4.0.0-add-loop-field-ts-dcfa269f5238c40c226a1fa9c25ec37a4eaadf26","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.0-add-loop-field-ts-dcfa269f5238c40c226a1fa9c25ec37a4eaadf26","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e13cab5e27d1d3bf4ca61ef3ba0727dec53b35f9","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.0-add-loop-field-ts-dcfa269f5238c40c226a1fa9c25ec37a4eaadf26.tgz","fileCount":190,"integrity":"sha512-rSfcRaNcTxAhk+KN9a19mH14N4kQadT8K0fBFr31R/AON5Rj4Z0yhpnB9mdmnERhUzHbeLZ5OmPnRS1wjs6Qjw==","signatures":[{"sig":"MEUCIQCHL8yDy8H40dSvvf9idvRArAJqg26R5MczQUsRtjQMcQIgQmybDEP3LTq4r0J9MNyr/NOlrmKHQl2F1GRN5ocQtdE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":721726,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJguIP/CRA9TVsSAnZWagAAmSwP+gJvfP5i1VgNWnLDF+s7\nVk0xsUcAAMdJL+QvbiHTVwb1pW90irg3H3LHVQN4WvXJT80ARRPXi184V5Xo\nyOBcm+wAB9FL2BwlfMfFNvAhDTZvrYxGLgzUaj8i4h+uh/osbaTJ3jDlXgnS\nPPfkxckz2b0yq5yztur0Lx3P7cF6kDq9nveo23pF9alMGM1YA26kzgLuG2x6\nr4axGyzisA6nO68HUb1dmR7sy6bpYi8zHzWRjSmvSuggdxL1coSlVSQp/iT7\n/dJActOEUvvsoyfllo6Bus8TY7RDZtHjGFHAtCM7xDOzDnrOM4fvwk+L2seh\nsA4QIry/IMAViZC+NeyUeabcdN4uJkccBQqUzdAUh+DfvyjNNRdi6BUiIVgJ\nzqcSSie59ZO7P4QR4agqcMTBIb17lhxMxs7zrg8QN7He3QyrjuBQi00Pu+k1\nlO8bfEdPZivmbyE6YIuTUE1ByYC/OWxqn0lpHt+5JuN5ZYo1ufrBnwthMsI4\n1vb27+BOPIeZ1N/fRYURNylOL8x6PAGKMWv9SoDzuUlt1j90mm/+k0qYM2Zh\n9WAWXYw2xLmkabr5PIaBO3MGAIpr/x8/7AIQFaGjpg2XwNKvLzBm43o8SYEI\nuLJi9iV5jg1jlXRISr2ak5NghFKh9b2K1Oi+VJdGZ7MN3z1d/hP4vLAjwWHH\n5n6s\r\n=ISvN\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"dcfa269f5238c40c226a1fa9c25ec37a4eaadf26","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.38","@balena/jellyfish-logger":"^2.1.95","@balena/jellyfish-metrics":"^1.0.249","@balena/jellyfish-environment":"^4.1.31"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.158","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.0"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.0-add-loop-field-ts-dcfa269f5238c40c226a1fa9c25ec37a4eaadf26_1622705151158_0.2455116849446195","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.17-avoid-underlying-connection-b81f67cabbfcbe4f085ce7d9ccfe7cd46c21937f":{"name":"@balena/jellyfish-core","version":"3.0.17-avoid-underlying-connection-b81f67cabbfcbe4f085ce7d9ccfe7cd46c21937f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.17-avoid-underlying-connection-b81f67cabbfcbe4f085ce7d9ccfe7cd46c21937f","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"eee5a8c75fcbba74a05e61c25ac7ce4608ed6650","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.17-avoid-underlying-connection-b81f67cabbfcbe4f085ce7d9ccfe7cd46c21937f.tgz","fileCount":190,"integrity":"sha512-LGQmQQG/dkx0oMhCC9V89J3/3kJBkA9Qol4zHh2aCI1ZM+xLs8NG7sVbY17fsuU49W6TrOsddeB2XKXQ6Di1Gw==","signatures":[{"sig":"MEQCICbhimCr8EValNecOnAe0JrTqrm1af6z8rJun/QnL8idAiABnSKVKnkF00gYZWRWTcfysMpZgz1o6Dz3gSibAnNxfw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":719147,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJguNi8CRA9TVsSAnZWagAAJQMQAJ7TV0+W89zkTzPQFwai\noLuB+sbp6XpG8p+mYSQNgr85jl6DVu2g/ow/enW83KVqsYIdGoKtT4N8MFg8\nk8UiNZH1DcfJeT47NCXsLGIAF3pk1kRfTxod+06yf9P3jNiSOP6Rf3q0I33c\nwtC3ZqgYjotUbOcHWqM551uxUF4Boe4lMcnPhAtSqCWMxcAm9pfug+hYzLdm\nsLKwlBC+HgbZ5YKgX54vxDsMVp+ADial/Ob5LV8hVfsmM0wfLSD53P97LgaC\nKjBvQUrwmT40mLM8sNYjIW7kAjLqRAoqFVWyAV/yYR0jvkvSL/2ygmvPI3bb\nbys0nGRIW7OTTv2yQq77qdA8kwsNOA/hrDhPfoQ4nYCZ/0lCWJRKXTSavL3R\nnozhedcI27pMOf+/IxfJh+n1R0jO1BKDSfo/CqsUJnJyZndEWhmiPvrErESZ\nacrQzw2LZyDEAk1VFtJpwViyv6IaUFtDJl/AiYRldabMSjmN1TIwj6GzJMn4\nepUTfCOISuyYpTYC9GWgE37Y4KUcCRnbzaGYffc3AoP0EUdaW1dU5hCg76lV\nrCHOG+FvHVRB60GQ5pt2i3NCWBpI8GsMg5vdZKr+bneaFzj+6f3qMPHrEqYZ\nrZpfeJjjIWre7N5F/7qehaBN3MrJR96M8tB+FUbjp5WJ2c0V9cBp2P9Ei7Aw\nkk08\r\n=NbaS\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"b81f67cabbfcbe4f085ce7d9ccfe7cd46c21937f","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.38","@balena/jellyfish-logger":"^2.1.95","@balena/jellyfish-metrics":"^1.0.249","@balena/jellyfish-environment":"^4.1.31"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.158","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.137"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.17-avoid-underlying-connection-b81f67cabbfcbe4f085ce7d9ccfe7cd46c21937f_1622726844558_0.4050544058023453","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.17-avoid-underlying-connection-9d8623ebe0074ded2abd66a2d17d2f962169b580":{"name":"@balena/jellyfish-core","version":"3.0.17-avoid-underlying-connection-9d8623ebe0074ded2abd66a2d17d2f962169b580","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.17-avoid-underlying-connection-9d8623ebe0074ded2abd66a2d17d2f962169b580","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9f38c1d7942e10c192b4b6d801e763c15548cd7e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.17-avoid-underlying-connection-9d8623ebe0074ded2abd66a2d17d2f962169b580.tgz","fileCount":190,"integrity":"sha512-1UMgdepGKZ1gnBLtHNG2Rkib65zlvZ9f4ShQ1xZ/O60xDgtOcYTqlD8B5XKNW2VWW3XJn+0clUOE2wGmii7pIQ==","signatures":[{"sig":"MEQCIC/kr12pzI4ivdHd43gLF2XMl0485boeOJuM+giUX91BAiAbF6fx5AMmw2C1uHWLqZa1tbHIdRiRu+BFn/VZWamBRQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718852,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJguNqWCRA9TVsSAnZWagAAA08P/R7oinfiYoWlzQkgcUOR\nc5knFOzudpT37pD9M8l707Pk8T2PgYMydu4XgUHXWojzrVrPvedvOBbBuz7P\nkjxIEjayeknXsozhQdpdh+9Z2dxbga0rS5pb2shLsy3PUPsIHG/5mO4aPw/c\nvei0YPpbuVcV9CT6Lh2tV2CkOK8WGCHwH9BYx9Vs4pidGmlGzRr+PpCU2URt\nFTpnrOPXNCjSR3WhJhfJnovulDG4Qs0+FuXn5AjuQABLnB5IW6ockNvUfk1L\nGeIqRwZ70/MpZAo5s+84vb9BdmGA1r9oU9PBbPPUWPwRLm4MPXNfre4GacV6\n1bubxQlvJDU9ZVI+6i3hIBKF/+T4q6wA/W6Zk2wzRVPhjHSlRsmDAYS/zFkb\nnAzEnv99u+1dEI3yOheSXc8LKa8tv4EyMSWV7cGZUDydh+2sdbIiNdHXW++5\nV8W3i87nF22PU8I0OLGj4GZmon8rWKNRgm0WwBenr7Isn4uVumqXcXQ4+SQw\n3KMwFw+F/Hqv1nM+Zt/X8q3qGVJiqDNh5D6TnlXqP6l+Rup4taKOPgDerkpW\nCoVynbXSjDAyMEgWbNfIlJpHiXKPC6HFIaW0tBka8tN2v9ZhSLPOh4ZTWMM6\nb/BjJWAS1WpytNxYFxNM4lWPG5WHAWzIbvhQA78kIShcRalwqsMZ4RkXme3z\nCKSO\r\n=palP\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"9d8623ebe0074ded2abd66a2d17d2f962169b580","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.38","@balena/jellyfish-logger":"^2.1.95","@balena/jellyfish-metrics":"^1.0.249","@balena/jellyfish-environment":"^4.1.31"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.158","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.137"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.17-avoid-underlying-connection-9d8623ebe0074ded2abd66a2d17d2f962169b580_1622727318678_0.4166925336816536","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.17-avoid-underlying-connection-04c7809752e25b3d4fb877d373e09282bee92468":{"name":"@balena/jellyfish-core","version":"3.0.17-avoid-underlying-connection-04c7809752e25b3d4fb877d373e09282bee92468","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.17-avoid-underlying-connection-04c7809752e25b3d4fb877d373e09282bee92468","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"62310eb4ebc3c6180a00f4f60be5a00d8aa1ca18","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.17-avoid-underlying-connection-04c7809752e25b3d4fb877d373e09282bee92468.tgz","fileCount":190,"integrity":"sha512-ycgyIRciOysb/ILJbzy/M7XOO/QtLJZi5kd6CuJ59sVfV6WgKHxFAegFwArR4eyoSfU6MErIlygQwPk+z639/A==","signatures":[{"sig":"MEQCICYt2uOht6rfDvuAAva/EEZgFbBwL2Xu9X4hSH8nyVAhAiA44ix8Utg5EbyURhmEPJ4U5Gylqs6Y8ZSRC0JKNkFCjQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718852,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJguPPlCRA9TVsSAnZWagAAA0UQAJDw7j6DeMtlglUsl7nD\n3bbHAOjdrhnjHBsfdv3ubieVfhNDk0weeT62xTyVNr0ocFnfW5XpZAjm1AhE\n8+FkZmNShc3/JEB4JghPkJIoJXegf9imSn5dLEcXGgYkr6EnVj7cZl1qENgI\nhx5KZQn9/hMSAQ/1LeiYf+B2XBlCFYaMLa7CIMnIlMDI6kqLmkM3w+K1AMRv\nDNO2qBNYZwLKDGJNyfKIfPybmD/75MGpN3XXXS6D3YQ3PlCNgudIfErjTsB0\n2PZ0LOGstjq9uDacD+4OLixxe+MMin6UFKvbqhWsT6I9MTeQVtU34uZBJGYB\nyUcGctMHwg22BGoxX9HEETYj4NZxsI7gcbuJCOugdu8S36xGWw9eJFLFlzn7\n6HTX0WTqoHYUFrgwBzzuEeNB7NhJBZZFpkVcvXy5Ynk5AEvBFhfn4GdwoA21\n+dvikBeRBbHYhxWSfj15vm8wfKnUz8+eR42RBvYanHSyxI15qfePNEzla3nC\nBMaMLOp92k4GDXV0ef9QzvfOh+RAff54CcV6+LN88evOgFVm7xzPKNg+RC+L\nfDQJnT5ZwSCXXymMdHvyyIrlQEqNz7vJ4zVsSUYw4pd7HiI+4Md2FNj4WDDA\nCGj5uHt/+Vza3Q7tfL+IwAzTVLjIc3EBPXBnPC9+VZk4+YXZfnp1jqa1Hp8t\n3fzu\r\n=bnDM\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"04c7809752e25b3d4fb877d373e09282bee92468","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.38","@balena/jellyfish-logger":"^2.1.95","@balena/jellyfish-metrics":"^1.0.249","@balena/jellyfish-environment":"^4.1.31"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.158","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.137"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.17-avoid-underlying-connection-04c7809752e25b3d4fb877d373e09282bee92468_1622733797535_0.10213770053451099","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.17-avoid-underlying-connection-f7b787414548452e1b0c05afbfcdc9926d881d2f":{"name":"@balena/jellyfish-core","version":"3.0.17-avoid-underlying-connection-f7b787414548452e1b0c05afbfcdc9926d881d2f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.17-avoid-underlying-connection-f7b787414548452e1b0c05afbfcdc9926d881d2f","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"828232c45d96a203cba19e63a97261582a96cfec","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.17-avoid-underlying-connection-f7b787414548452e1b0c05afbfcdc9926d881d2f.tgz","fileCount":190,"integrity":"sha512-TWPmtkCfi/a7wwp8qqjhg4rz6WRKdR9qAUiz+jsbjun5PcjG6/20uA9hbXDkK/3XnV2ZoTloGFJ5XaoiwOoXwA==","signatures":[{"sig":"MEYCIQDIy7S+eopkUT6AS+voPjnnRvSjNUeNRPfrijM2L489YwIhAP/NZOdonvfxhvZ57aqgSDz+m2BMENGmFffle1z4qmOa","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718852,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJguPfbCRA9TVsSAnZWagAAV5YP+gJjlENw2KJDR8PldvJS\ninJaGyfpBG8qyVYDm33Z43OIYvRrGQ3UHhADSkAw744MjpSBCbmMY7UE5Iuv\niCcqDZ3k9ZDSTewAbHLbr1QqVuvaM1Gx8FkmdH69QDn85DFjWVljVmSGJzhi\nb1bE8HkfkK6CZegweAsX5Af96HQKDFZE+LdZ5i+N8Iq36Q+RdeBHLpThXyba\nNS306X/4CImXtXLNw1gfX45S3uYLxyxIoABPelBv69O+lD8zByQcLfO6kpVU\nVi9uhu+q+89nmNyeX/NS3/vO+4qVETkQCzLe90nMrJQ59dwAvSIj9Fcs+hHd\nsdr+DCuOSlrTL4mJMXebTsmGTvPMBMxYKVkgyfTAk74TnrlizY9L4WrBalD0\n5eYl6f+wYbfpIZ5LJm4gxNDFRrn5dr+4NWj/35vhjtE8o4O47SJm/NQUlJUs\nzQZv4eEPgXxwwLXJcc5yhLXPXL7OOVX7iQXFppM4vClfUVWgd0gXXGCh34Yz\ntg8mNXvbCD7rmpiHB+mI5905mXqpEtQc+PIrr/AxAgdShsFYG4DgvvNSHGsQ\nbFV8uMrLF6iMljskf1S1ntamfqURAr9ThCRcHXedl0/739XeoAJHIGan2shN\nGyQ9O/eZA/RZlWjv1iOyfvWRzPzlV4Gqb/IBJM5TiYpZWVLZF1uwq3h4tG4Z\nK31A\r\n=hit6\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"f7b787414548452e1b0c05afbfcdc9926d881d2f","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.38","@balena/jellyfish-logger":"^2.1.95","@balena/jellyfish-metrics":"^1.0.249","@balena/jellyfish-environment":"^4.1.31"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.158","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.137"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.17-avoid-underlying-connection-f7b787414548452e1b0c05afbfcdc9926d881d2f_1622734811124_0.4247235091666628","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.17-optimize-commit-lint-ff2ff7ea118414e782f7e85afc49aa04fc04796b":{"name":"@balena/jellyfish-core","version":"3.0.17-optimize-commit-lint-ff2ff7ea118414e782f7e85afc49aa04fc04796b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.17-optimize-commit-lint-ff2ff7ea118414e782f7e85afc49aa04fc04796b","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c424d8dc12d7368b282a763df00f8a1f674003a3","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.17-optimize-commit-lint-ff2ff7ea118414e782f7e85afc49aa04fc04796b.tgz","fileCount":190,"integrity":"sha512-D1BNjjyKdBY9xm73CwYOuA0RV23TPKPg54ugqFO/BErxkl9mBaI0PvLqSjQ/G9ODCsTnOt+WKOU9i/l/Rb688A==","signatures":[{"sig":"MEYCIQDLQIv0tLMyVsFfQOq1zR0PlZaIRNTgo++QnqfYmoT+FAIhAPpyCWbjAx7TPWGwo4katq0/HYcbxZiiWrQfzpxW47sJ","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718403,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJguPiACRA9TVsSAnZWagAAdQwP/jklBSN7OLkBZNGMTTzf\nD29nI36KWoaE2mLLJLrGObHKoziKZbEIjbynay7rc2UrxOelhiTy9UoDqplZ\n73iT4lW+op50NxzoPaZmu/gupLngWSrBILKiyM8srM32vR+DIlRnjg4xQfLj\nheddthbBQXnfR0Slplua1xbUyY6tPDtajV0f/VHBuP8aO1yA4knwwqScteS4\nSdBWjwHhfmoULABPxx0DZsUyYyLrzBk/rkrTQQU3LBv5sI0CIzyz0kqXCAiE\nqC9B0b4t4GUp3X0h64vpx4tffHpFTzJ3rLkNjKJoAS4vaXElzzwIFSPE7D0G\nfruTMnL4FNIveAx73OJMF8CgiApStrj1qHo3SaE05yernURCFPnEoz9/aeA5\nX0BWgmZzBj4/7vyd7p0M/BUCPEuoPKk9UanQf1zP8kLlvUNBLmmAE1jDB6Ld\nZDcYtsFgGvSHK9O2+MPwm2LyZMCeYoezIrctIJI71vC2/jrWciIp6bC81nQX\nzwV9abZnKllho+5XRTDZGU9VztilLsvF6Z2aR2Pf0eMSW5GVCCyiJPgnU7tJ\nzz66mkakt8KabmVdz/cJ0LVwSntUC9IFefiiSLb4N8Kp7n9ePs6o0yApGqMp\nu09YIvrfQuxPq0BDX7HpK41hu/Lt/TNI7Glrrnb3RnW+fKhrccn6z3RlAwFv\n70Ym\r\n=KUi8\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"ff2ff7ea118414e782f7e85afc49aa04fc04796b","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.38","@balena/jellyfish-logger":"^2.1.95","@balena/jellyfish-metrics":"^1.0.249","@balena/jellyfish-environment":"^4.1.31"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.158","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.137"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.17-optimize-commit-lint-ff2ff7ea118414e782f7e85afc49aa04fc04796b_1622734975939_0.10297876731566213","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.17-h32-ensure-use-of-tx-conn-bd7a8e7735e34e3051b5cc9433b89491b946b16f":{"name":"@balena/jellyfish-core","version":"3.0.17-h32-ensure-use-of-tx-conn-bd7a8e7735e34e3051b5cc9433b89491b946b16f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.17-h32-ensure-use-of-tx-conn-bd7a8e7735e34e3051b5cc9433b89491b946b16f","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b2c283ea9d0ea0a9172a7c624bc511704c9ab9de","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.17-h32-ensure-use-of-tx-conn-bd7a8e7735e34e3051b5cc9433b89491b946b16f.tgz","fileCount":190,"integrity":"sha512-qtmFZgWnLvYNIOU2wknY0iYVWsz1ybBLZAO4aiJ757C9z9oIXNvCfqPe1ZnOOAB1/TuLaalKFqaiOmryA1OsEg==","signatures":[{"sig":"MEYCIQDV/8p98lk2E4jGJZRR9LF5uxVyWyYMJSmkknTFFIjkKQIhAIj+Gp2WqQ81mzU5LNJhkKWyLFXimNe8wLZMTwgxloQw","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718084,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJguQy3CRA9TVsSAnZWagAASq8P/04cJWkvOC6FdsKoGK5j\nJE2gaOee0waA12n43k0GhyYSuvsx+BZDcUrnbHw6q8EkmVIoYYnZgSQ6t+bW\nga9tmQ2FtSrCZK+oXR0lJoCsOqhaYcC93NhviPvchAmxlubQEgfDAfhr8bjw\nVnACVtAT9U2wCueDjMUi3m4QvUg7lThlwq69sO+s6MygaCBBj29dshzHlL6R\n9T4paL2oqy/W4cuV0d/vw/+uMy0QdNvsU3ptNDhg3Vjd8NPUIlcOiSO7cbLu\nez8ksLrfBaooA44P8X3Yl7NvAV30RtImDdr0elQGfVcX/p4gU+WQKufFTv8b\n7+Pyx84urHXVber6SR22mWbYWGNgpVP4UDwo9k60qgnVMV3E0GppDg2+S31J\nrlIl+Hw4REnPIbF2OB+b60OlXZh84BjTsTI1lnWYDbkcxGmeUVl39RciOruW\neqFV7bVk+AsEpYk7fDB7zf8t55Yq65LbkI7lHVOgVQY9T2+Ns7S4hIUECuQt\nsAgi6XS/R73xhacKBjlv9Vih3QwLflF0CUHFdNc1bdfBLzQEEmnz1WFvlmUR\n2o1OWsWsKH709uS9o8JzThFzPugQdcKRZ66mQcNjlGjuSDKsCkj+nwiy4Wm3\nG1aIw/Txn61Coci9ZRIiEJQ/tBHPmmwb8VbGG02kPAJGf1irZUBRjYItRX8B\npTQx\r\n=yCCv\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"bd7a8e7735e34e3051b5cc9433b89491b946b16f","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["npm run lint-fix","git add ."]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.38","@balena/jellyfish-logger":"^2.1.95","@balena/jellyfish-metrics":"^1.0.249","@balena/jellyfish-environment":"^4.1.31"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.158","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.137"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.17-h32-ensure-use-of-tx-conn-bd7a8e7735e34e3051b5cc9433b89491b946b16f_1622740151392_0.1642533980747909","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.17":{"name":"@balena/jellyfish-core","version":"3.0.17","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.17","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e476f26ecb4af57c32e98f96b3a7900df0d4dcae","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.17.tgz","fileCount":190,"integrity":"sha512-TwATfMg5/Rx6c0EhyPD1Sc5fOwDvvjgkggOxhmohjMIE0dW1G2nSYwVIv76v5KfYhnTdRHTqi01IMg65ionctw==","signatures":[{"sig":"MEUCIQCofVkUikqreFTDQD9sJDsrO6GPUPRhhiqCesvWufUzaAIgbrQQMFPqYBQVwf3+yePTMpvhEBymqEOMMx36SVFsyPc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718341,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJguVd9CRA9TVsSAnZWagAAFTAP/24qDYJkrVfwQ6xv3YAj\nSkckYg/wFVSfJF/WKkXlFZJBPthsSqmPtFz0Jqa+N4xlQNG84fC1pjtBXrap\ncLyVeFmi3tx/j29ND0NVI/b973FnhrbWDJyETI9CvbnWawupjbtEcgC95MNI\nr3tTUcc5Pu57eKJFMZi7cvwoQEWKX/Brax0yKZuT1OesNQIMdjUXRPTsl3Ep\nBsEYL3Vgx1bJfAHgIV+c9MhvYgxBe33kAHPT15DWrjIm1GMyX66JYITnVtCV\nijvWaJUrl1BEwBhkei4xp/1SLk7aLzP6VWpXNz5YEjPIcSju8PMwQuIF7btN\nPKHWUbmlmwkIaKaxbcq98+sdOl9+8cIK+vCLL8TLFHW0N2cWj2qdhygwgFaa\nCKfMiArBmqIsz+1djDxJ9K/eA9ZukkitfAD8BWy4gPkdySzmeOv+O6jSmao9\nvDblve+SxeTGRw2eIC7U9JOk+6mkw7cWRCymkdrJGDOKBriWlakUwJQOoUcL\nDmXomWr7qcWWrts+VeGmyEMlHH+1CXTllh85KXA8mNjdTq7gdXwAjd2uNslv\nGGu3yh7dGrwwDgvB5s8rryeZYb/ohrXG+1icTwHxkqYAOvjoUAoFAjogpMuk\nI69gZsapjAogBuZ6ejnmnJ0Xk4Oj9QB54CI5LTdMur8dfr2WCrmnvPx6G0HT\ncS0p\r\n=qGJ9\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"7024a25a5f9cbb1dcd3bdd1246120e64e18e79e8","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.38","@balena/jellyfish-logger":"^2.1.95","@balena/jellyfish-metrics":"^1.0.249","@balena/jellyfish-environment":"^4.1.31"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.158","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.137"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.17_1622759293215_0.8302899873142899","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.18-renovate-balena-jellyfish-logger-3-x-44cc567c02aff994cc2e61ad83295ecd1c70c81b":{"name":"@balena/jellyfish-core","version":"3.0.18-renovate-balena-jellyfish-logger-3-x-44cc567c02aff994cc2e61ad83295ecd1c70c81b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.18-renovate-balena-jellyfish-logger-3-x-44cc567c02aff994cc2e61ad83295ecd1c70c81b","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5d4faa43068ef7c8698af83f5f7e99668903f1a3","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.18-renovate-balena-jellyfish-logger-3-x-44cc567c02aff994cc2e61ad83295ecd1c70c81b.tgz","fileCount":190,"integrity":"sha512-CsBl0jgviU4lxGqAO6kSgYgmadBjhLCW6+adxNy7gzocbVO2dPN4b/0ooR8aKy3o7lfcAeR2N8qvQDzhmVfPYw==","signatures":[{"sig":"MEUCIQCECas0dPeVoXj3A4IUvhIeSzs95F/8+JuY7SA/jk0oTQIgTvXkygw5xhEY1lHU6No2ueqkD12JHjTFzJHHzcv9WCs=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718516,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJguXW2CRA9TVsSAnZWagAAyiAP/0dOV5pt6Gj3/nD2RnlZ\n06X9fH2VWPZQ1hLhqR2MPnAkbu+brvsipixuuYxL2rrLROYPb1xYJKk2id7p\nhrxmyBv4YEjbgtCWrA4hfbi09IN+CKa6TOhHGgujDaM0F5XmdpQsTxhfEcwc\ntbfS2B4DxorXCmM30X3hi4ktMfC0MOD8bxs2gLGlt0MxljH0gUxFwKrNVNcX\neCMkuB5iNWy/sPGmmgKITg0Ozq5SIJ4Zd49TjdIj7COYvIBF+5TvYsRecKss\nGoWaDSinpp3a+Krw2MzzBmNXKIVZD9g/dXmNVucgmveLngOIaZ7qUFBJ9n3s\nvssU56mwtvVLX0tM1h4xYtgaDPKw5n8kvVmmCmaDX3ViQZx/4gnwzMWTO7h6\nvkFq28oPMxq7+LPWGTVmMG+u6Hg6KsYpCUA4LgQRkY9scT40Q6PSXwtt4udY\ndl/MC8ZuYJgbNiT3HpIUnX9+1wS+ZyEO6mFi+hwyhpZhe/bxxjL2vkbCzhR/\nwuNJtc3NHfjwE6LohkLrSP2rF8awghFFk6kSJOg8FWLqIUuTLiXiZiz7vh6U\nPSIacw2LLsmOgHr036wEi3bXQuRXEY3AckozlJY/6sxsiIuJ+Cns5PRbGTWj\nDCdBeur6T9gh2I0FfM+w0+0rsRqOdEShHGJUxJt3Q8nlupu6vtUTgohocukF\nMrTp\r\n=MKGZ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"44cc567c02aff994cc2e61ad83295ecd1c70c81b","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.38","@balena/jellyfish-logger":"^3.0.1","@balena/jellyfish-metrics":"^1.0.249","@balena/jellyfish-environment":"^4.1.31"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.158","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.137"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.18-renovate-balena-jellyfish-logger-3-x-44cc567c02aff994cc2e61ad83295ecd1c70c81b_1622767030353_0.23345998018006786","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.18":{"name":"@balena/jellyfish-core","version":"3.0.18","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.18","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"19ac7632430054c152afe7a6ee0a054a03a226ba","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.18.tgz","fileCount":190,"integrity":"sha512-FNyG/ElA3evseekUDdhfBg3q9DuFfOWPNt0yuyKHyFTRsvTFUSBYuQhIRFjw4U42XsuVtj740Iqwj7Shw1F7KQ==","signatures":[{"sig":"MEUCIBYQByz03dtH7wbGO5wMIi95EpaVh4p9BztE1WwNNullAiEA7T7JtLc3bm07jLLghAXhGRFnd9O1Fe0kDAPGclwdgqs=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718438,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJguXsQCRA9TVsSAnZWagAA+8cP/2T1e9Jp2G7GFANS7K7Y\nixa4KD2QYitORdR7BsUoI2cj9sFAIHrHDabkekKif2oOp7ylbJ3JosDq3vG6\nDMGdAtWd9lGSc78ACUA0Z94hjWWNki2FSuFnPJMkT/XJqHxD0T5bHdE9H8+S\nsHSe2jZGQ64DbA2+bfVanulpWdpn60hIYAGn5HS6g6seSXkxAFmTkL9H8aqX\ntKV9kh0n1Gr9bEA+eK5Q87HSTFHmEZYcUreBlAi/c6mjigreAKeVca2ILOVf\n0/k3Wh5aLezJ/sir0A+KGtNCKScScwsFHG2FXnBY3fG4fLmcl34SV2vWVnFJ\neyTF8qbcG8IlFzbnqPQ2Ff6hOBw1yamUPTHS52IQb3x4cdlwIKCSAO/pY1r2\nmWcdfUxBBTPa17obCql6WEwrnUrQRF0zUM0Ecdo6woIAluEEzIkwt5WBIvEh\nFZb9wAFMXCNJJ3w8yhHoTzuZUyGgouS8kR3P1k7tKfTue/bHWy2Ty6IOY6HN\nkj7TJYON1HS7J++eRbt9CXs5eJrar2rj6GJs3Ma5m6Nmg5K/0khR0q+wE07q\naUMoR7xcEMZic+VKYYSIoWZAGoQ+e/4ALh65HZpEl9N3n+1mq8RM95puHqd5\nLtii/8xHkhyBLxOT88rbOfROkwo6tlJnRq1yjnH5wRaBU6V8Jd8Dq5Ly4Lls\nehoi\r\n=K13H\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"cffc00f7412c7128a32fc0e75ff4f044a984952b","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.38","@balena/jellyfish-logger":"^3.0.1","@balena/jellyfish-metrics":"^1.0.249","@balena/jellyfish-environment":"^4.1.31"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.158","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.137"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.18_1622768400678_0.14325513254317968","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.19-renovate-internal-patch-440dea294d6128008d0942a1f70b289085537253":{"name":"@balena/jellyfish-core","version":"3.0.19-renovate-internal-patch-440dea294d6128008d0942a1f70b289085537253","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.19-renovate-internal-patch-440dea294d6128008d0942a1f70b289085537253","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d87007b6f7e0c688e2ab07dc96e53c979dc98a5a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.19-renovate-internal-patch-440dea294d6128008d0942a1f70b289085537253.tgz","fileCount":190,"integrity":"sha512-g9o5SXICxbtBso2pximZqVr09Iu33QFWrQnvCLiVHNDBY1N9Z2om5FtRaJy1dO4seAH2bZFSRVo0xq33ieyCfQ==","signatures":[{"sig":"MEUCIEHWnWq5yK89KxyhOQ4XVAGvA7RynzhhtG0Vtpn98880AiEAzp07GpLhGSKHJzYg+KdmX2y92jfQjO/wL2A5+OB6/cc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718574,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJguX5QCRA9TVsSAnZWagAAJPUP/2bmwsIrDNRXOpmQyCoU\nbFHFr6bI2nixPt/2wqs5ijCt06FkFrpSTSUBpVfCJ54fWPs+MhjjpHLVHTBg\nO5m6HbA4NQMVH5Whcq3L4NXDRxXyGiaO7NnkbfZPBxPgv8ltOlxexLfc8Wi2\nTQrr1Nc7JsNVNElLLmwIo6J6G4YFDBlUJK8jHqKBzgfviuGTXAnhsDl6nPc3\n/4fsZVqnu7Zk6hyavMN72+9t+ZL4KmMXkcGt/ssuw+1CTsqazJuU6dLJwHeF\nS5Al3Ke/RurhZrcAzd1K1GD5BL6h0xbLLP+1Y/kvo0AaZYiXMASpnYpuJXR2\n4JDCqxo6RvJPFQdVL2vg2uAe54E657zo6VZgPUlIa7RpOAulMDLBLH5iPMgl\nB4to8IrPHGV400e3FEKHhZGCsbARoAVJgEwDaJvkyo/EKb/R0YAAqzog9Vce\njlsSKvzXgeGVz+pPFkLvK2Fpi9BEIF/AaOPeCVTvaKsA3UUtw7SOQdmKbYE2\nzhAc83VOMgFZmpwhjGd3UKj44DgqixfRypmEb086VDonsWAvC7TRXTXw3/Yt\nbgrmcEpOFSOXYhEa0sRA4dR2oRle8Z2HzEfOM4bHsAYMmplhBYQ4Wl7zTcLo\nzhA62MevS8m5wDOUf7eCNuUJd469QLoGZwrKJNShCztWg9mYz9ixCkaajraA\nVJ19\r\n=Mhl4\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"440dea294d6128008d0942a1f70b289085537253","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.39","@balena/jellyfish-logger":"^3.0.1","@balena/jellyfish-metrics":"^1.0.250","@balena/jellyfish-environment":"^4.1.31"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.158","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.137"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.19-renovate-internal-patch-440dea294d6128008d0942a1f70b289085537253_1622769231914_0.5932385650085052","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.19":{"name":"@balena/jellyfish-core","version":"3.0.19","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.19","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"564bb92da67b4837be72008bc005f3be67984d3f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.19.tgz","fileCount":190,"integrity":"sha512-Q4yELAQ6V8hd1ZK1/iVM5U9V3JCN+P2Y2djokRyqRp6ME94ghqDJWu9rVMCbjs7m/QPwMRsM8J0hAtwD7ENpeA==","signatures":[{"sig":"MEUCIHaSBEH25BEz67/IID4QO19MN7uIEgKWcBacUveX1oBCAiEAkJ1ApLJV+CWshPWsXYE6zuHU03Wj7XuFgXLbWQ/lKfo=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718509,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJguX9dCRA9TVsSAnZWagAA5twP/2OgUV8DpbVHP804fA3s\nBq2SUjC3aLBSYHcU34eSVt3MhEN/MD+qldWrESPhu09mW/aBvHl7uKHi/RtL\nsBQLWR/IAec8Owk9jH25l9QBbrrRHEpTxdl+6ValB3LjYsRfmNjmsnct7eFf\nrA3viHkFkfzW+KVCNfBmPBoY7zxn+M9QurSLi6AcCNNt2m5eZ0CoRFKBpO2H\nLUn6+lqt5UnrfEJzvZkHjFvAAG63wJvS04cSlP7nMm7qXO8s5FqUMdI2Qt7A\ndyvs53tes5MlJgSDz8dP5IWRzri+kJKjSHpz6ftm7GtxHgzUjSqMJX8cnzwa\nzTB1sUH28W8M4n3G1iOYDwhycu8N3l+yOc/kS/oG5vJliYMQwLD/BSDVCIi2\n5Z6nu9wV1wcBRH7Ki53OVQE0DHsHCo2D11BUpUXQb+90m3x0rSrFXkwRJ5mx\nCO0ToCFkLAtLCJ2oBWjbxYlDfy0IFT8ZjPkRMzdCxE9MtETwCPBtdAbaz0m+\n/1XLilgEO6BiHtNO2pVYXItY78vv1UkOrm4Z3SSVuBQjLSwQ0xL04mUWkk7m\nPqhzbLXzuywo0Od8HnWs7FTI6IHunJ9FT6sqJsf7pEoOZ7+a+OGS/GqiVrli\n4AztkSX4gLL9f9bYDtMcV+Lbe5I9Fv9d/nzpyHhi5M2JMxs89Yra1KHIcX+W\ng95g\r\n=qPEw\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"bc9055c009b6e34c489c1a54bbe385350a3e19d5","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.39","@balena/jellyfish-logger":"^3.0.1","@balena/jellyfish-metrics":"^1.0.250","@balena/jellyfish-environment":"^4.1.31"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.158","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.137"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.19_1622769501552_0.39491292455935434","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.20-bump-jest-deps-137a30019c5b29059e3bd6a704ce6be714d533b0":{"name":"@balena/jellyfish-core","version":"3.0.20-bump-jest-deps-137a30019c5b29059e3bd6a704ce6be714d533b0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.20-bump-jest-deps-137a30019c5b29059e3bd6a704ce6be714d533b0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e5e9c501f09670b90fc6659f04939a8091a8e7cc","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.20-bump-jest-deps-137a30019c5b29059e3bd6a704ce6be714d533b0.tgz","fileCount":190,"integrity":"sha512-fRn4sVwWZ/x+qRokXPtAAe6fdwvLUHGgSRNHq6yEeE7ahHO5E2bcVGiYVZam4c9oDSk4imDmk+Ye6cA0MRHY/Q==","signatures":[{"sig":"MEQCICuvHQMHBwxA3OGylF4mgFqrpmTpCy3xZtmP8+YujhEwAiAnW9uLg4EmXG2wLXHYILEUHvh/tx38go2SAZHKsYa4PQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718622,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJguYSNCRA9TVsSAnZWagAAHcIP/AtQ2Yn9xVo23hqgva+O\nXbdWLcEoMEpQSTL8J11PFXfyl+rtWmuD0ni3h6AskytyxHL2Fwj5vWRcwGxG\n/c/LfHMy9wPA75Am2MDJqOGw5A3ngcT7i3fse9XEDOX7l7F9BQahrXOqjW0o\nk86N20mISmyJFoW7tMpVI09qlyuFxTjO5D3A+MbtB2B0uPneEYsEcqtvBylD\nzDDfHctj+74PT83ZWOLzUz8cj8N4Yuglo+qhy92ekJwqOI0xaCr2igv/zLhF\n2wckjumiaf8Ki0QcsA+ITsEOWH1tYIH7S+2DtU2hREvVG9+RgoemT2Ic2yRR\n2sN+RAsxeoltbC9o77E4l/m8+UDcWv8swONJsCoAAEMbB/VpgXpRCd9ybVdg\nZtsjlSDzAM8xfa8Z+UvWlQj754FwNU9fbOPxnd8XZJCLRACyA0el0ioDlKvO\nKCRyTtmtq5SZJp3cyFzRu7w9Qu7F2mLi+b/NzjPmBmqpN7nBuT/fe805RbeD\n1UWq5GkQVmUz7eSBT42p1s321GXNedIkd+wGFYo19sn/F1MWeG+OYKE2n5oz\nMqHhNwoMQ2eigsXTvIUSHdp2Zvmhv993JkhC3IwmMEJRFRlnAc0MmBhofWSf\nrs39f7AVf4IIeyJKCAt9ufQ2SpTBPM1z7/SsM7eCeXyKbQwswTidAqijINKp\nAGDK\r\n=mBDq\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"137a30019c5b29059e3bd6a704ce6be714d533b0","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.39","@balena/jellyfish-logger":"^3.0.1","@balena/jellyfish-metrics":"^1.0.250","@balena/jellyfish-environment":"^4.1.31"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.3","deplint":"^1.1.3","ts-jest":"^27.0.2","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.158","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.137"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.20-bump-jest-deps-137a30019c5b29059e3bd6a704ce6be714d533b0_1622770829491_0.09825822221076375","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.20-h32-ensure-use-of-tx-conn-b26e02a3f16852308bc35440d78c992a8e790b5e":{"name":"@balena/jellyfish-core","version":"3.0.20-h32-ensure-use-of-tx-conn-b26e02a3f16852308bc35440d78c992a8e790b5e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.20-h32-ensure-use-of-tx-conn-b26e02a3f16852308bc35440d78c992a8e790b5e","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"fb19fb0e657a7905a2fabf6c5ffdc392de3ecfce","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.20-h32-ensure-use-of-tx-conn-b26e02a3f16852308bc35440d78c992a8e790b5e.tgz","fileCount":190,"integrity":"sha512-gpGX0pc+Dz3t9rnj33odDJavghTpIORaxXC9I/TIrAmOyV6itOprat8CA+89wZN/TTUEQZoKNMfq4qDLU9OWWw==","signatures":[{"sig":"MEYCIQCiwFktAxafHKaJ+XbjO5EUruJeD5+WrIJTeClk1qvD8QIhANB9l25Gkh/K9M2uXb6QVZ3d+4Ded/cj9iPRLvpX2e4n","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718309,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgueO+CRA9TVsSAnZWagAAcdwP/27w2Ab20CLX6uxiB0wl\nXDLHfE3WSfIWWQH16CK7VRAK3hXZmAse5+Edyp0zUI/NH5enaJjNxbUhG4DP\nyVycGkywL2jkXGWLD31yt8kGJ4RA4YOE2eNHLeDrG+AlhXf8GPS2BZ1mt1Eq\nwiHD/77Z8Br/F3ViiRPxFJCQ17cMdtJHY4GCZyqDvrnH3kt5aR7wdnbOB6r9\nSyaVbt/jzYCwf/JavwYE54XnHGDF3vmOqiv5yMRuEBx5gMIZJAQ9VA2YG5qq\nTjTiGjxWm5Eaq+ffN5vn1cLSfFB9umYbDYdG0t3gsoJ297dU6gD8tYuYVkW/\nV7QzfO2zBrSWVnSPHQrZoKVYqQTfnFe4WPZxdPGcbzbrK9Z+95P+0+PjrEy+\nFoaHvqHIRHEzmIGhdQY/1HsNvlR+UMGgpjxt8QuVIqiSmLM1z9MZTAOQUxuj\n73B+fWjyWfX5ksbqVahq6FNmQZ3KgEx/ZQCFtD/egx0uCewTosLk9VcqVuyi\nVoS0S2tt2At2BpogZJL1sRJ5bXahMFqOgPw6G+zzbWPlvW0R7RB96FcsrI0H\nRl2RktfAm61LRnC5OwrmukdfNDoSxWmPhVMR6mtdTjknajQIpTweOXH3/OeJ\nALSsL+h/2Gykji8zISAxkw9zdByfHyD1B7tYpaft4qzjLU+lxWkyem3DhR1h\nTKag\r\n=SRxK\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"b26e02a3f16852308bc35440d78c992a8e790b5e","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.39","@balena/jellyfish-logger":"^3.0.1","@balena/jellyfish-metrics":"^1.0.250","@balena/jellyfish-environment":"^4.1.31"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.158","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.137"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.20-h32-ensure-use-of-tx-conn-b26e02a3f16852308bc35440d78c992a8e790b5e_1622795198410_0.9830290156148103","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.20-h32-ensure-use-of-tx-conn-ba0dc768c09edda96f23aba3305627775be03cea":{"name":"@balena/jellyfish-core","version":"3.0.20-h32-ensure-use-of-tx-conn-ba0dc768c09edda96f23aba3305627775be03cea","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.20-h32-ensure-use-of-tx-conn-ba0dc768c09edda96f23aba3305627775be03cea","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"72ea9c67a28931861c544ae3f1cfaff22793ddc1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.20-h32-ensure-use-of-tx-conn-ba0dc768c09edda96f23aba3305627775be03cea.tgz","fileCount":190,"integrity":"sha512-+Kc+k19EYu7pghedw3rnlDv9X085Ya80DA+kXjwhgbIfMWq+1wdSJX9o2ReOeM2f1xzRuL1SKfTZiaywswYKdA==","signatures":[{"sig":"MEQCICq++AUC8ZGuFzlKty3zWIkLggbqB7HO56wcyPDtPLMAAiAUF+WoMMtkBoPVfrO5FqX0L6WuZLxVNInWGK+gErfAoA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":717943,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJguetgCRA9TVsSAnZWagAAjDgP/1ypjPFLTCrRsGYAD9T1\n1HZ3/gU++jyS4C9h3Y0nwmzispNLw63qvgQl7dmSi9Ac/m/lwZd/pFl+voX8\nM+tpc3H7ao7/j3C1zRXK7paJJcVfopiYIruMppubrb9nXhOHS+cqkHfkVw9U\nYG1TzvMILpw3X34IWbmvmHvDd7dbqdjJwgu1SYd5H2rGFp5hAvvdj4O/f6VD\nupLymgrm1wK0JYIv4nGz3mv4fPLjwqGRMyJgf5YsMqPEGdW0CHyZrBPibZrw\n5BT7M1H7xpsmvCI+YcaC8aBNR+ptPDZhoIwEzPQyKPPmyWXGz6YYK1xWQ30M\nzDxc0M7XLudExLNYwdkoi8lBWqnHYKoO7Dh+vzZy+FfSMGOcf5j2YY4OI0Zu\niJFeANu4d7jZveCIa9W9VJ2m75cBCQuE61LeFThy9aHC3od5J5aJStgDoenA\nxmq+yDnKXi0jL59jq5KYWdaIXbyDzuI81DZMbs46IsUId9ojrIUH1iZlMh+O\nUnPeuCwSDQqLMOlm++1qdsZZCRsmD/NUUSsck0+/shnN+pekEY0gbtsrwexG\nPRIPbdMrizV06X6ule0hmWuQbaqDvE6dIWZpFW0rXrZmPohF34nL2EktNf0Z\nKm14+J0dIkDy6hL+t3D+y9XxKox7+UpTZmAGHIjzzcXaPs5A5GcOkYmHvWMD\nBOFk\r\n=Sj1x\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"ba0dc768c09edda96f23aba3305627775be03cea","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.39","@balena/jellyfish-logger":"^3.0.1","@balena/jellyfish-metrics":"^1.0.250","@balena/jellyfish-environment":"^4.1.31"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.158","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.137"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.20-h32-ensure-use-of-tx-conn-ba0dc768c09edda96f23aba3305627775be03cea_1622797152035_0.2707476241797546","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.20-avoid-underlying-connection-a7b7cb427c6c7ad048da8337b103e89abcf89aa8":{"name":"@balena/jellyfish-core","version":"3.0.20-avoid-underlying-connection-a7b7cb427c6c7ad048da8337b103e89abcf89aa8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.20-avoid-underlying-connection-a7b7cb427c6c7ad048da8337b103e89abcf89aa8","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5700340e407cae86db7abe84e9b4170b5ff4c76c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.20-avoid-underlying-connection-a7b7cb427c6c7ad048da8337b103e89abcf89aa8.tgz","fileCount":190,"integrity":"sha512-iMY1882HwpyEOr/CgEDes+1DPOFq5OxgDltZQiy4vKOxZI8OtBtFoBsi+rgeqb+P5fbKOTbU15tIE8DI6ChDbw==","signatures":[{"sig":"MEQCIFgxBUgkk1B1q0GbZmyrmyOskqVcDFIcQQpVh1L39S/1AiAStt6uhcQdPTA9De5jDEaxCOdxlgyQhsmMTDiFp4PALg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":719077,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgufEACRA9TVsSAnZWagAAvtUP/iIbXo5e4URmOin4diuq\nxXts6Bmdjn8hd7L4hpF5rhfZ+scDhJTj5HSzvAFK/V4VhfrMDIsf/xBZuJRe\n+KFG78vWp2zQJXRO2vncgLmU5zNQDUQqGixYXROivxfv8J4mzHQlcb8AqG86\nNQM362PumiutzAtEr6O6ZrQ8Oz4/CLfP9JAwhBd3a0ObpkIHktbw1CPm8+QW\no9f02l7ypyJQRxxQa3F8AbE6T5aDtjrmD4veqpbeFEfr5W6X0fhl++N7OQAD\n6DAG0Oil+4aJ1tUVA7v2srJJTzty93bsXtU0xafhx8xjUfsdgMK04yJBeSte\nk6xdQUf4LTbLoJDefCt5Wn274KIWSWKaqsOjt7xR97Q6kY7P7QcjBOlaEMv9\ne9Wb6CoKuEGxqVR2vm5teGUX3yTSjB0/aJyDqOsmU9zwOH2p+xSUnzuzmnjB\nsgjbMcE0DOiDyp3PaDOr3jVuBjPLZWVOTyinRX48/oIX045UrIjOsB8ALGou\n8mV4THfnhB+lN2Q7XMMUPaqFgH41JBvTVqdMSNA5jO+odeykhpH3RejsKJiA\nMJ+JTiBeRLBCc8ISdcehk2TogOHnH66CFKjTZa618cNaoWbhrIIlvaGnrhOg\n6nEPs6hnva+omaORbY5GIBuL1y8Y82RxMv0IQxq8eTBysj0mYTQCQvEau7Xi\nYk/l\r\n=pLSJ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"a7b7cb427c6c7ad048da8337b103e89abcf89aa8","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.39","@balena/jellyfish-logger":"^3.0.1","@balena/jellyfish-metrics":"^1.0.250","@balena/jellyfish-environment":"^4.1.31"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.158","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.137"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.20-avoid-underlying-connection-a7b7cb427c6c7ad048da8337b103e89abcf89aa8_1622798592646_0.5155886041495423","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.20-avoid-underlying-connection-bf25f3ef37dfab3d3a7068c6fca7d375c7d11597":{"name":"@balena/jellyfish-core","version":"3.0.20-avoid-underlying-connection-bf25f3ef37dfab3d3a7068c6fca7d375c7d11597","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.20-avoid-underlying-connection-bf25f3ef37dfab3d3a7068c6fca7d375c7d11597","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9fe5372913ed71c462ac52fc9f999f54cf6d3644","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.20-avoid-underlying-connection-bf25f3ef37dfab3d3a7068c6fca7d375c7d11597.tgz","fileCount":190,"integrity":"sha512-1L8tzPZFN7MBuGs+2FmorIFgECZXniytgosJ6/jZhkHYMvbw/AgUGUI334HGjLKgIIZFfu+2HlVkvwDmFsm3Rg==","signatures":[{"sig":"MEUCIAmsSqlXJMKN81FQuEIEHWAjA2+9a6PspoeCjBgCtr0BAiEAsTA+owyBqMNSp3eUsqihLjVBVK2InhZchY0glf3lRL8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":719077,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgufUvCRA9TVsSAnZWagAAHPsQAJKTlkXAk9DWniFXBIOS\ny93s/tqontuvXwZnDuYQSxiIuZClmRTOimUIpCpcLsWju0kb0avRIKVWx7+w\nw2IYesscRY7TZQn2yiTdb/P/B0qA81UqSk582D8Y/uhdo6HjXlwdON3Y8wof\nQ5hmK/f59Ch0u/G8dAQ9GT+h9mk5QK68opZVB1n+2YpTe6ABpoCSeDOUTrcs\nYxWstp83TZUJOpE5vXf4kq/7ks1QLSly26hTaijOB5Ww/zEdV8VwOtO0+icJ\nDN/8le/mVrCVd0DfXQ6JZ/4byaCnS6e/cMxwCUM3ilsQ4e1SVHPFqeohxwmV\nBpRjQlFNiLggscRZhhOQ40TcC2kfCCl13Q49cfg/n5/fDy3PIpmFrgEbohcD\npZ74cVGXAIgkxRzcZ4F1gBELNH3MK8QNN3aIdJOW5ESjcGdJkUJZj6jQLi+a\nJsYNWO4MMF8ToG09iQA+nlmACtDPA25tfRTEHT98iJBlf8dJXRnQt/LO6pzl\nt/7ndmG89h6ZHDrN7lYk7ino1EcfDb6YeJE+TPb4bBjCksLldPjysgjAmmB2\n3lKEup3sJPnYRitYfx4NAc4MzDOINY0oJwgkr01ofCF+4Sa/nzfsPJodgMpb\nB5ILXpVuBDjIT+VxAS+DBPmqXDGd1TuB8UMs/24nHAPt5k0qDqTMbzy0Fhf8\nq6Zu\r\n=Eqy6\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"bf25f3ef37dfab3d3a7068c6fca7d375c7d11597","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.39","@balena/jellyfish-logger":"^3.0.1","@balena/jellyfish-metrics":"^1.0.250","@balena/jellyfish-environment":"^4.1.31"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.158","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.137"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.20-avoid-underlying-connection-bf25f3ef37dfab3d3a7068c6fca7d375c7d11597_1622799663344_0.6699866763287083","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.20":{"name":"@balena/jellyfish-core","version":"3.0.20","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.20","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"232639cf73185f956bdddde2f3b27e9122593df1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.20.tgz","fileCount":190,"integrity":"sha512-EEboAVQWVTAP+B+1MYEVGcF8b6r2yPWcYLP0nU5j2rh6jAV83QfPCVw5foeRWt5bhZrwl4glQom8iHUMZokz/g==","signatures":[{"sig":"MEQCIB+MzuFTNk/ErCqSnc6pKrUgfdX4IV2POiH9R1bTt7BRAiA8rokK84QgcgwbqOxVYgcBJ6QBFKDOonDmaOwOwySQfQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":719008,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgufZRCRA9TVsSAnZWagAAbfYP/RFDkOqZJ9uVMvPtQNoE\nncp1MICov/LOoUaIOXBzhvT/Y4OJoJJgb36m+IJSGwnnaML8ymp9WtxW5Wa4\nzj2z7QqChRUCAHxWjbrWViJ+x/tQO4ifKCl0qzfUj/+2CduZLwokcoa1ZXfi\nourBCzPNQB5ETDTyk/A1//EGsMGFhscXZ/u9oE9i4+BGrkiyISJy0vzzoRUy\nyi1E8uSH1DmGcaBDy8IBCx4aaglubaARmRMC2aXr+D2P/Lmuf1bTjnQ8ROue\nejcRCbR6apMlOWtuiTDwhagxuwa9k5NcyGRC/JA24NH83SzHxwY0BaZTDrhp\nl6bbeA1i3NqWoVII+D03yWyHuSrjZGuoZ1CBbBpynJpPgOXIVqoobIPsCs1n\nmnfbDZdCxdxpkVbQvDNyB8FS+G/2Ntv4j6OzSRClWORT5x70y1xqjvu26/TI\nuYgRiCk1EHXAFD0ZSAf/eSXW6xLv0K2BsoXDDPlm/RwZ+6gfEHGPiNmu/BAg\ntCgoQW4nnCfbRt12zsT+hYtD/6IGPH3yZqYERhd8Cxz88A6FRpAhikul5wDa\np6DP5Ri3AnwP4WE0Sk7P+DaYSzhDkwEvZ8VVgkJxlFkGdGSnabaVvLv0tZRS\nmht8gkaCpqirqHxeKmLuooeuRsrLWVyWHuPbJ6b0O71CDthU1/WsKRhOMPnL\nPFll\r\n=JP56\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"3ed0f6e792d8d2476e672570ccfb5485683ed38e","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.39","@balena/jellyfish-logger":"^3.0.1","@balena/jellyfish-metrics":"^1.0.250","@balena/jellyfish-environment":"^4.1.31"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.158","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.137"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.20_1622799953366_0.7159439678109047","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.21-h32-ensure-use-of-tx-conn-cd1b4d3188b16d874890ed09065ecdad1bcc6ce5":{"name":"@balena/jellyfish-core","version":"3.0.21-h32-ensure-use-of-tx-conn-cd1b4d3188b16d874890ed09065ecdad1bcc6ce5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.21-h32-ensure-use-of-tx-conn-cd1b4d3188b16d874890ed09065ecdad1bcc6ce5","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"071de83da0eeb1f4eee4575cfe7732ef65b55964","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.21-h32-ensure-use-of-tx-conn-cd1b4d3188b16d874890ed09065ecdad1bcc6ce5.tgz","fileCount":190,"integrity":"sha512-y1VGgPqQ/b4NK7ok+12a1PGUH+dx2SkPRAsqJUudhaUX1+4kBhD1bffKszNWfvIg0eP9ZnE3ggUg5SsRYwD54g==","signatures":[{"sig":"MEQCIE841oEHv6WqGHg+YIFZZHdz+ZLpCJ7M6cyqpVhD/HsaAiAdZKkE2O5ucB8vwllh0/uFescVjzbyrjzET4D1RaKn0A==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718364,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgugfBCRA9TVsSAnZWagAA6EwP/1jPpFxx8b0e8jm0p9ks\nDHIKXVcIakVveBAZKHEMwNOx6sVtTc5ZiWbMbV3rwJA1yQdwaP481S2u8diA\nb1cJjwqprk8t3jj+CT/LzuW+Hc4uT29yFhkEF6EkhicvbUzZk9Vf3ik+Q9Rh\nBJGCrIFCJcIo+cQ7qSmbwxYfNk59o3R2s86dUlck9NcTetJK7Ume/cI7OW8R\n3SFK412wlFv6jHGWB22vSu26TN9Inei8eQ0K2Torx/ibgFgrDQSpKlwwFnd/\n2jegivhELPNAcaG6ZLsjaN8IJINQ9b8Na+Lm08SYRA9s29kgy2EXNRhxuHrZ\n/HVISJ7jVUHJChnU36BDNIkxtkgi2aq38M42K36VphJTMOAbsUyvlxXr/+01\nPDiyvA2uwU+Or+7bEbrk5ao4q+Zgwvj1lrztiihbEoZo/54jdogcsbrkeK52\nNY6toi7soZOs14kBcvWZwcZUlA5CduZIhJRGLlu/31p5rWg/LGSvho/W5bM1\nZ9uIVe2zyOD2kpMWVdvjHGH33S8i7UVTx5HY4J5Mmui4gnN0j7eHwifg123F\nWMoV2I0vTT+c/QDYiRxTgGNQ6G5mBQnrtYt6IhJFaI6kTectMtUQ7kEMewAz\nZInGikpwHRFTrQf2YjGtU3rN8bb3lj1HUAOsDsjeZM+YvZ7HbvpGdR52SQsC\n655s\r\n=/PWf\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"cd1b4d3188b16d874890ed09065ecdad1bcc6ce5","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.39","@balena/jellyfish-logger":"^3.0.1","@balena/jellyfish-metrics":"^1.0.250","@balena/jellyfish-environment":"^4.1.31"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.158","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.137"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.21-h32-ensure-use-of-tx-conn-cd1b4d3188b16d874890ed09065ecdad1bcc6ce5_1622804417640_0.06091111717697384","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.21":{"name":"@balena/jellyfish-core","version":"3.0.21","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.21","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6fc7f053e3a2c92d3de20db7fb9c2c085b6c659a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.21.tgz","fileCount":190,"integrity":"sha512-+HZuLxpdq/+98V1VGzasnq4yauQ60zc/oukXNs6yNY+GMK+E+ifB4CHmzld8xaZEa1CeABE0SV2IMmrdEyNl+w==","signatures":[{"sig":"MEYCIQD7zRNquRgLAc0YmZg4Nf5FV2+uLxEh+JVximX7bwWzPgIhANHNlmx+tZ3Il5dAJcKIntZBxx1nNEwmJAQAQdXe3aS7","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718297,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJguhd4CRA9TVsSAnZWagAAYScP/1k3z/YPKftfqyXON7cx\nxYOSsIarAqfw4/ybBsLKzsuC67bv4iK1krCkzKAju+UhTzG4yBSlxm2ktnps\nT6Yzp25RiaiT/STLUDVcecD/Z0L3moZF+LW+8jRWUVDO/3C3A/yBMvFBI64v\ntm05K/vBgdUAEaVnxkqcyRiO3HZz+iFKTfKRlsiggyFdLW1/83MSNmjhO2Mx\nGHR0v11vPARF0NbDrzINnNFLnnCZUu8jDwWgBmTi8yz5zeZyJ7AItk0dmwXC\nZXoU2Gsx3pCi7iiglms9wjHB/8NGA0/NM4rzlW+TO/bYIL0ulepgpS4eWmX2\npbN08oZGTUQMCbFQ15PDYKmE8oYHMUUNfW5VJiLbXnA1UNk4QzwFsoi/YAsX\n/AA4CBGYkqnD5Msut55hl5hW4aMPNFJuf3S7k2tbtIK0vQVyP7LlvTvfWvvD\nVCV10w0EMaH0DvgPGKLqKV6xj/MRjfDSbGURFCmlvgpEHb80Ix4JIfwf9yB1\nbOfPZl1O8gjgEKVjLX+J6IGueq3LeT8lEDMmxLOzkCmix7G3YobxMwiavi+g\nQlyuBSlFg3syYh2YzN3Kh2NRPcTInhq5W0K8bidxwCvduV4OtT1mM7u9gZNM\nwCpUpsSTXTa8GESMqCtYKCUH9qTeAFnP7KiKFs/SRMmvn5gKizgYFFdHxrCd\np91C\r\n=D1hs\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"35f1b3dea55c6b844c602a53d1723ccdea1782b5","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.39","@balena/jellyfish-logger":"^3.0.1","@balena/jellyfish-metrics":"^1.0.250","@balena/jellyfish-environment":"^4.1.31"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.158","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.137"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.21_1622808440529_0.09139429414572664","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.22-renovate-external-non-major-7343bac31a033935fa6f5b579b09e32d0bdddaa6":{"name":"@balena/jellyfish-core","version":"3.0.22-renovate-external-non-major-7343bac31a033935fa6f5b579b09e32d0bdddaa6","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.22-renovate-external-non-major-7343bac31a033935fa6f5b579b09e32d0bdddaa6","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e828b811565d4df92e3ee151999eab41f74d0a1e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.22-renovate-external-non-major-7343bac31a033935fa6f5b579b09e32d0bdddaa6.tgz","fileCount":190,"integrity":"sha512-bcLxduwaf5jzt+S8YGJxmx08hbIVQp0LWJa8nduNHGHblubzP+vTZTdpuc5jluNAOzUHP1/aAAb4kJ1ttZLxbw==","signatures":[{"sig":"MEUCIQC7C654MeGRXQUs7vWwTVko5ra7dFlryHPLEWZEzfz0OgIgThRQ70ITF0DqNOGPCEOuqohZJaxfUgyV18CZYzlLHJA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718457,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJguyicCRA9TVsSAnZWagAA3/8P/2ctnBHa6APO8hlgWBPs\n1Q9YEgGh+8JqaJpXs/zjMNic4wtsZZMOWQ7j3hSXI7DnrbOH+nRPHNBm3GLR\nTkAyVkoAfQ6dScAGDEIRdNK9weCM8lyrYn4Gd6s8fteC3mWGxJ9XUF2HBXLL\nBgBuHDjuqshAtn9KdA8ak/FXI/TwekQiFRpS2hRZ0sVd4YrepWhOjZj7G1UY\naKa+tooNYmRvcU6Nt/dUpQcQNRQWoYhUQKSsx81VYVDg3I/uO0NMDK+3Q2iD\njVGSiFhRWba6bJbZ1nWaRlZX8/nDUyr1egXS0z0LE6GnV/y+24sKlfHVwkCm\nV8hFlZCJIELIzf5Kg5QzIfwwFY5uYGC0beF2VJxLaIZF1C6sVle23ndvb6+N\nI+vwqPuhPkzbptOacXRnTi6XPYRMj0DPV4qPi/Fgfdf+xFQlmCWmIArhogaw\nleefLQCUEgfCpbK5CQV3RE0r6gHB0DSzMwqH+KH0ttQzfh+LpSgMrEETi/s2\nCyIio5r7WwhTyhASey6Vgm9yC0M34DP+MkUAgd4IDKbxSHPjBknFk6BDTG5e\nYTkv2jCzk/NhY4y4PSc1FCjQwQULZnMqWJa5LG8Bsdu22wpXZr5MqpID3dBD\naqTn9iS5Zyuy/0ljhFbMi6nI/xyVSVZtDtQKpHkDdLM4i0zZznt6E1m97pMq\nr3AD\r\n=YJN4\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"7343bac31a033935fa6f5b579b09e32d0bdddaa6","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.29","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.39","@balena/jellyfish-logger":"^3.0.1","@balena/jellyfish-metrics":"^1.0.250","@balena/jellyfish-environment":"^4.1.31"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.158","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.137"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.22-renovate-external-non-major-7343bac31a033935fa6f5b579b09e32d0bdddaa6_1622878364269_0.609780856495153","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.22-renovate-internal-patch-40caf879743b4b8da16d5241a82d62bb1f888184":{"name":"@balena/jellyfish-core","version":"3.0.22-renovate-internal-patch-40caf879743b4b8da16d5241a82d62bb1f888184","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.22-renovate-internal-patch-40caf879743b4b8da16d5241a82d62bb1f888184","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ab655e5ec6dd483bc04b02328344745e60787b14","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.22-renovate-internal-patch-40caf879743b4b8da16d5241a82d62bb1f888184.tgz","fileCount":190,"integrity":"sha512-+EkSAIIR79EF+Sb8E4SQtNrsV5TV9K9JyeCzZpVWnfVXs2Fjt483RtKUXTEdtORGfE1u55IlO/n7zvqmvEW+9A==","signatures":[{"sig":"MEUCIQD8PpUX9g+rOFaUMT4YyJvgmqjwUfj/j7NJe2hfwgN1ZwIgKTP32rou85hckmu58fEDVZXCMuAHGnh4cosIZi0xZy4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718433,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJguyi9CRA9TVsSAnZWagAAIcsP/Ao4wnfjn7RfqSb3pUum\nixVOqvxFo0AEJca60qB7gJHL3/btziVOY1ZPFuiWlS0IpKGgjkX+kIlQK3cP\nyNPBq2+v6wMAKaMJ64s5/xgIxsWhHQ8RX1TgeyHsFyG8jrC4SQLq3NTAeKQn\nvuzOLrtnvhJZF1NyDJ62wBMYfJFdcTyKQb93nS8lxuJu0WCtGFj6KDLUfRfL\n75j0lmzZWM2tlbxNAMm7mxhUbXjZP7yib0P6CvYPmu28i3XPAbzdOEGKL+j0\nHyoWpKgcjqCDXD73LbPlZJ99yi6xAOEr7fEZ4CLlsGD/9QQ6+7sHJxogSjeI\ncOdnCAGq1RW8/v3QD98PbUrjIgdPphmfN516qWo3tSt59rZpZP50pje1sPNO\nY7CYjUQTpHoMwo6HCN1TuEN/h7thiVTrVURn5DgfD+QOnnNZ/14WksBxslwd\nuxYqSGBJzDEVTZgWSBVBD9aHt+vVKdL2E9VTC7MouTnsB21FvWbWxtMsqR4C\nvKe1qsA5d0Jl46UvMWKCgprdbsnk03dFhji5mP3aL6dSYDmaD9spIQ7+QLJc\nmbVXzVdEP1dGOilxRvhX4pTcf14WvDptWvk+Yk+MYpTVMkx2M7zDI9w+SPiD\nG51BqcZfDNPDcmDnfnNRa8DUacxjZY42+JHAto0fqrHnXsELBoT++ojl50uw\nPuqH\r\n=gzUe\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"40caf879743b4b8da16d5241a82d62bb1f888184","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.28","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.41","@balena/jellyfish-logger":"^3.0.3","@balena/jellyfish-metrics":"^1.0.252","@balena/jellyfish-environment":"^4.1.33"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.158","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.137"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.22-renovate-internal-patch-40caf879743b4b8da16d5241a82d62bb1f888184_1622878397564_0.3021316554330864","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.22":{"name":"@balena/jellyfish-core","version":"3.0.22","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.22","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"cc8a8143781208133447f553f74c46421b84034c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.22.tgz","fileCount":190,"integrity":"sha512-4dSdivVk7NrUlXFoLBH/dtryRLx+Ka2V5tkJOM5u8LNcvxZe2oI1q2eEI09jy3s/oUf1RZn2f0eimsIqVSZ8Kw==","signatures":[{"sig":"MEUCIDr1zNAvUlu7z2mNpZpx3Tol5r/F11AmKfSdjumzeqYrAiEA6e5yaTaD8Hvg22HxnbsG1Edl4J1U/uxR6XnNEPSjJss=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718388,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJguyolCRA9TVsSAnZWagAAhoUQAIegngWcNBUUnhInKCOi\nMlTV+ps/wPIGwkM8BAFT2E6lAaOy6uDOY/39SMYkYLL2KliSDwhQ3/uUV1e8\nWeNc+1OJMAyjYbtkJiH6f7h5zwh8JYWWLm1tOpNqZqBN0TPwrJxT6C5I/0Zx\nZoXKNvphUCewZxqchfuU/ekUdiHVuDls17hLdW8sQNoH0l2sjh4qwkkE5jXs\nzd06SovUUO3Qn0L9qAMRsR5AJujL1iokwg5XkC6ilwCPWWgzG1Io0Me7oBOb\nGShAesUE/A4h3utPS8ky3Rwm2ef9H0DXlrncOfU/BHF2sg2sKBd+oaK1hj6O\nMhfA9D241a8kwIIn4pkSKDNkuPMZf0LOA/QxJLEtVTUd/kLColUuBcq95rxi\nvSkQVY1hYzJLqPWO0fTH11rnFNCEQeqYWiAuMScydALW4+33e/dzawQEJvWL\nUeXzonZPe0/IbSEKADkMEhjUBwdHghbga4cpgJXcBkpY/coEJRxswem+s0g/\nFyUjLU9ghbJXb5KeWyKnLCjfUjGYKWqSiSByr0AErg6js6CwwKnp1tiB2oDJ\nKwePOaxskyj7cRVG+FCxvX5ZVEiuR2Y6S7cJoCrZfWQSCn1FZMQuTqeymPYp\nbdXP6x5OHACcf5dJ5o5jXRDAKoYbzo6YTZvi0UrvQYHr4j3AlnnEcbeZAYF3\nG2Ab\r\n=EV5/\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"73aeb178e88073fbc8fffc63af8c61e2b270c28a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.29","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.39","@balena/jellyfish-logger":"^3.0.1","@balena/jellyfish-metrics":"^1.0.250","@balena/jellyfish-environment":"^4.1.31"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.158","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.137"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.22_1622878757392_0.6477051504124234","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.23-renovate-balena-jellyfish-types-0-x-e963d69059061179abe599ac82c4982aef2aba1a":{"name":"@balena/jellyfish-core","version":"3.0.23-renovate-balena-jellyfish-types-0-x-e963d69059061179abe599ac82c4982aef2aba1a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.23-renovate-balena-jellyfish-types-0-x-e963d69059061179abe599ac82c4982aef2aba1a","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"bb11ffa8314d5256b28e6822ba8b04f79597bf48","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.23-renovate-balena-jellyfish-types-0-x-e963d69059061179abe599ac82c4982aef2aba1a.tgz","fileCount":190,"integrity":"sha512-L/WCpT9PHbz2VyhYa7D1tciIinM1QKFYTMxAX5ASbIsqAjB1wCXGg1hIxzaou56Dbfkvkx4/PFnIDo3RLEsLdQ==","signatures":[{"sig":"MEYCIQCnFOwoERhetD7L2bOY1u5im/zlY2h2NqdPQpbJwYzFWgIhAIMWkEl1fqVyb4m+jBvBn5bxP6jezcFvJjvE4xvtsAyD","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718564,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJguyufCRA9TVsSAnZWagAAT4oQAJ6l8Vjw33+YPC6eJKtS\nXEz3EoDXuaB1PkM0w0wPm0vhHPuXAuX3uRlRaeljvR4z/cLUlMCVdHT1M4jM\nlIwZ5WKBpOGlSnWp2CwFBi/9jZbW85wIOBjxBGTn9Pro19ngOOVMBypoIej1\n2iBW/TB6AGAynughe6zNzwcDNjsav3E0/3GkuLINjQUSi8BFxfh1h1//1LVf\nwoTygUG7tO3ppmPtRh7ufwvXAqiDC8WeMjVc/4vJgPCiH3U3Ugm8ievWBEt9\n0Qke4CPbUH4ELaxqYfMUT0ZJ2JEl8z2juIKw2hjfoq12OQGOIigR6HZxqpny\noDZ5JOOkFZBvV1q9aw2EhrWTW6ua2KTHoFKeBNpNe/uOzK57Wn/Xe8VlfxcZ\nn2ywhagmT/V33P6/FvHk4m1TtWExOX71S0JxhFi41EqpuSKPUL6c2Sef80xM\nr5url1+C9ljPlhwQLQA8BzwYZiToKinv3l2XgPntg7McgCWDmw0M8UIVWGxI\ni7V66XzAd0f/Ama1lhZy6xmmZXZSJDIr1CPXkFTbBQ9u+7NBPITHhvFH7kfQ\nIS9znRkos/Rhs5QbiHe0PYBM0VNOM8NnAqPyUa2MTMv4oFJGrwWYW0xax2aL\nFfkzILPurWKr146KjpjytHtoIq6wHcYlh18kSgDxU3x0HAZOIbEseFHcoKRJ\n0HML\r\n=M49J\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"e963d69059061179abe599ac82c4982aef2aba1a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.29","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.39","@balena/jellyfish-logger":"^3.0.1","@balena/jellyfish-metrics":"^1.0.250","@balena/jellyfish-environment":"^4.1.31"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.158","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.23-renovate-balena-jellyfish-types-0-x-e963d69059061179abe599ac82c4982aef2aba1a_1622879134802_0.36418680681427995","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.23-renovate-internal-patch-b1250aef076d198f654ac7cb36ff11208376d5e8":{"name":"@balena/jellyfish-core","version":"3.0.23-renovate-internal-patch-b1250aef076d198f654ac7cb36ff11208376d5e8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.23-renovate-internal-patch-b1250aef076d198f654ac7cb36ff11208376d5e8","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"cdb18b57c1e41896fc12e7d44dc0e6670cd203ac","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.23-renovate-internal-patch-b1250aef076d198f654ac7cb36ff11208376d5e8.tgz","fileCount":190,"integrity":"sha512-j+LZFBDLlHyR0OCNP3GAHMWbx6nDG2a4aKeYPZ+7sVWZEOVu5GqnRRUCijZcoTOQZm8lxpSGSTAVA6B/0WVFYQ==","signatures":[{"sig":"MEQCIF9gUuHjG0ZHpOoC2COQpBCc22Gsf8qCbehW4T/1x8jhAiBG4b8GThI6/SGUzcTrgWOihgtfsHugPIe8GRk+Ilyc7Q==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718524,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJguywpCRA9TVsSAnZWagAAfxIP/3SJURnbtqDHGvfhSpD1\nGO++WYaKN9c0DJ5QCqlUkBfBjQK5nOTBD/WdY9IOewime2r/7HI/ww8A8Nhh\nf24sm8eUaOWIHtMI7FT0hhFkZhIxq5mmQiM+Ojnx4EVFB7HUt1V13VZRGPn+\nds9TZLkMfLesQr7xf/YU9f8csQmcvJiiGOSBuVMACr7JKM4z+qeNZtmzf/JV\n37nmGlHUc4Z230u64/8R37ABjzT25JxApWT/A9LmNt4c9iUwADu4KLUTwyuW\neH9badgH62MgTpZuG3CSctyADddNq/u/LePztoP28PgILuHRmCXIemaGvzYS\nGnLUDG9cgtQPDw8Hdl26rUMA/v/JNhKXy1lJpRUlKCgfsBGVvbyE1n66k6l/\nc4RgucxrLAg185zCgVaiBySJomBsARt2HW5dfsYfnSwIQEFXMD+vhclywWZo\nPOYgglqQq7qTFS60t2UFhlxQdQNWeTULn1pFJlTHSTh8h7DhTOna5nOnO4WY\nGFBY2BfbuADdc450EJjJNU5/4QeKeYVIkRuwvFoxGbyP1HzPHZlVLYoMZf2p\nHlt8klz/wrUn09Nry4A9kB8R6tRbC+gU6nC1pxzXG/x5niJ/NsWlvO4jZsiO\n0pZs8/nzQymTVwtIeiG34Cefl8C3OT95BCu5EItxZqOOAJHe5vMaugNsuvLb\n5Grf\r\n=npE3\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"b1250aef076d198f654ac7cb36ff11208376d5e8","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.29","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.41","@balena/jellyfish-logger":"^3.0.3","@balena/jellyfish-metrics":"^1.0.252","@balena/jellyfish-environment":"^4.1.33"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.158","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.137"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.23-renovate-internal-patch-b1250aef076d198f654ac7cb36ff11208376d5e8_1622879272268_0.8389811907516898","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.23-renovate-major-external-major-c094627dd61ea4dcea820c4d3e2ad84aa910fe64":{"name":"@balena/jellyfish-core","version":"3.0.23-renovate-major-external-major-c094627dd61ea4dcea820c4d3e2ad84aa910fe64","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.23-renovate-major-external-major-c094627dd61ea4dcea820c4d3e2ad84aa910fe64","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8a33f23fbf54d3038ae3da0a1fd5d3dc929c8ce4","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.23-renovate-major-external-major-c094627dd61ea4dcea820c4d3e2ad84aa910fe64.tgz","fileCount":190,"integrity":"sha512-kS7oBb1P1WQkfWl1e0WkcPPvKNE8A83N8Gc3OBWgzXoFxjK3AmNT35A4PTCmgWNk9fX5UFj06O3cZ9mBhQnpzA==","signatures":[{"sig":"MEYCIQCc2MORrs2PicH5v6Y8X906rGv2idmrezhzta/S0ycMQQIhAK/AdpGoJZ8oQd0wJP6l1Fehp3dQp1og8iLVKJPaw8LC","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718530,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJguyxJCRA9TVsSAnZWagAABcMP/jHEkwE9IMIfGWs+sdSO\njjDu/wpY2NDzs29ZVMYFBs3t0MdM3hsP2zJTeG/9Tzr8wqybvid4T+ayoSLk\nnpbSXr49ettjcNccs1oG12W/Uuk/BlSR5BZFc4dQk4w6G7eFJ+1hSRRnOxVR\n44LTWCvFhQ5vhnIl3sP5HqV+AT4TCIX0WPHpmKS0N83OTVt1PRi5C2KyM0xh\nnhdJ44nbQ5Ak0bCdqGzonVAqQ4KMF5CIeVtVBCnyxJrNVoqkLOa47KooAQNS\nlOzM1rtvz6jKjmFSLIUysbmze8atR9lSsc2XSlNmti+2L/bQ56FOCJCeY0C5\nmBjoU1xLZP5nXRe8i2BzfJxamq0lCn6WIFofMViTb61F2UJ3GT8CUdjcTjh+\ndcLSc5EQcL/w5jWa7cDQFaULVDDJ7DmJ0QzeMdYHanfSKV9CFYoDYjWNzXtQ\nRXrmpiof3hWbumFW+o6uROlOwfyCEwI1hb5YmId8WRx2JgOx/z7Z7zhcPD8K\nTntl7CwMJd0PJWrL1xHYvsYXJBTpTQii1m9oOf40HqgTx8j8wkgBNC+kxV9P\nmIHQrfu0OSp/2lI5/AECJpjTdlIgvbtGHLegkV8OO5mtDTDF4mCFuH6QDv5g\nb7/Xa6toFLmn/hcm++nn76RaG2WNu2uUQFT2zMiPHOOG/AeKkWF44MmbjWFS\nnn1L\r\n=NZay\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"c094627dd61ea4dcea820c4d3e2ad84aa910fe64","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.29","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.39","@balena/jellyfish-logger":"^3.0.1","@balena/jellyfish-metrics":"^1.0.250","@balena/jellyfish-environment":"^4.1.31"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.4","deplint":"^1.1.3","ts-jest":"^27.0.2","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.158","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.5.137"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.23-renovate-major-external-major-c094627dd61ea4dcea820c4d3e2ad84aa910fe64_1622879305228_0.7174323597645045","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.23":{"name":"@balena/jellyfish-core","version":"3.0.23","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.23","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3ba28d26249194d9de9d4f925b9ee366a1af89ac","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.23.tgz","fileCount":190,"integrity":"sha512-Z2sRWEgO88TXcHbzQ8hlFKDmsYCBNPU1mhOWA8idJYe9WIXotyzwSIEjclDfSoeoBYeddrjIHMi0RN+pk93xTw==","signatures":[{"sig":"MEYCIQCT1a9tueygy/4ET4p8KLvdDzuOjI9n+XA0dtA+68us7wIhAOobX4LMx8Ug6zaByVzvWrK7mL5Fgm2gqSJQgxibY2HK","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718459,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJguy2mCRA9TVsSAnZWagAA2d8QAINvhNHkM916I68QZble\n6npORbuOy+4E6e1YfFZLt35qixCACKG0XtkiowsC8HA7Fj3oIX5bFKTbU1m+\naHB4pK4s15x3zOa5VnyTr9qTK8zCmAlmgYeuRQjQQu8nJgq0ThcB846wsjGU\nIJ5LYgkRzKlY08hs69Npj6v38gEJYv6YHbB+V6n0lOf2G0jiw4GXP5uCWyG1\nqsAJIXc+m8+wz2Jfd+thZ2HN14dYH0lSqeZshqbyqU7g9ENUA5stoTRrNy1v\n0gx5vb9x/nx7wd+yxrI2tCtUFGTLFs7aZeXVsCXRnAKVRZXsEDsKOaAKFEnW\nZB+ZOK5FBGmdrddIM4CwBHA5LyOONJMSK2G6lOuAdtsC9NCWqWksrASrbrnK\nhtDWOCIKcscZ5QRrBLSofQhEJUyZpITcASJNbA/5hPddAfSQDvEPSwpvWDkk\nETrXzy63vj01McLpBtvw3077xMIa/mNiZmNAwZNNM2AMBvWzQeuJp+Adi08Z\ndBvDoz0q52zfS4YIywdhmAaFrV6+3JbTEh+tjHzzHYkD5fbfnVFaU/7Y3e65\nVImNmQO8om3JZw2tNK2NCJVm/1ySVS6ezMNMT3kqj3DzBpWc32WA6RcmqKWf\nmfuipaYmwps1nZXUreXWyctT2bd9sDcw4LrIAbQCG2Y1+H/se2Z3Q4UROEMd\ncpNU\r\n=7pXc\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"cd64dd0ff4343ec056249af61239d06821572008","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.29","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.41","@balena/jellyfish-logger":"^3.0.3","@balena/jellyfish-metrics":"^1.0.252","@balena/jellyfish-environment":"^4.1.33"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.158","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.137"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.23_1622879653907_0.7386413720686871","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.24-renovate-balena-jellyfish-types-0-x-a4e68cd887ebb37aae93dd70845435e9f03bbed2":{"name":"@balena/jellyfish-core","version":"3.0.24-renovate-balena-jellyfish-types-0-x-a4e68cd887ebb37aae93dd70845435e9f03bbed2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.24-renovate-balena-jellyfish-types-0-x-a4e68cd887ebb37aae93dd70845435e9f03bbed2","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a485eb3a79606781c29a7b12bff4df806a645732","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.24-renovate-balena-jellyfish-types-0-x-a4e68cd887ebb37aae93dd70845435e9f03bbed2.tgz","fileCount":190,"integrity":"sha512-SF5h23SZNslw1hyzdVrIBmuUf7vzMdQvCJz2p9oZH8CSpVb0IZ/JTEJdrFTqqhCryJOrFaeZJ/uQ+8UN+0isLA==","signatures":[{"sig":"MEUCIGOkNQWSm4OKv7eQvz951IXfG4B3vF+YJCpiN2as0ficAiEA8mAV/+iNgyr2dR54Tc7BqZfgFem2jgntPjbJ2hdg50s=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718635,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJguy6bCRA9TVsSAnZWagAAQz8P/RtekDSRDh5+lNRkNAUy\nedBrgRS2cSnQv2PaHi190QSKfay8p1kowAZpOoPGeDbvtz7pJrE3tyuUe0az\nBEeXHyGo+IghkMRGs6D3sPybkDYUeWHVo0+3kVQpdutwOO8FnXDzd8/NLGeV\nSNpFU+BgQVD6IFllD6151MsEbhJGmXMW66Oqy2dR9MplqXSj3VVh67X14KKL\nlnShGqjbIdWuRFi2MCjIaEt1aY0ZQQXdkpOP/W0JHwrK49s/0AivOrtk+ayt\nMn/im+8e8HE5tYSTbERLYHEK+3isbqG1UP8b6pw4sYaI9s32JElGzP/yUOO+\n4zy4VK6joMxc9ebEM565eYYbpVXa4DkRt0C2GVQM8JPQoucJ2slUaOT6oNH4\noKw4H2si8qt+SFfhPPmHvHHZ2tRXbr76FjeMdGw9JgNN+VKYFOOcsGy6wxrs\nkLkyU1V8Xtrq3/ja8IGW3Q5fr3Ffq5NMOuued3UmfvmvPgknxC2Fw4ggE4Ys\naL/9LgaCVmMOAFKL3nh2F/z2evowTfcr3n2LbYfouokGRg38M+h6qx0BKzv1\nDbB86birRHPooUFnzfX7WHb7+ZLoVJ3JNj4sjKnTPhbq1MhnZA5NPqOEajUw\nJMQJkflBtIpmQrifSoeh14R9g759Abdcw6H1pEKFOZO8uWss1nSKX10FBZZF\nqtJF\r\n=suOr\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"a4e68cd887ebb37aae93dd70845435e9f03bbed2","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.29","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.41","@balena/jellyfish-logger":"^3.0.3","@balena/jellyfish-metrics":"^1.0.252","@balena/jellyfish-environment":"^4.1.33"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.158","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.24-renovate-balena-jellyfish-types-0-x-a4e68cd887ebb37aae93dd70845435e9f03bbed2_1622879899270_0.8735460224606311","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.24-renovate-major-external-major-5373fb43a729971e2c8a5ad58969c59b405b3876":{"name":"@balena/jellyfish-core","version":"3.0.24-renovate-major-external-major-5373fb43a729971e2c8a5ad58969c59b405b3876","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.24-renovate-major-external-major-5373fb43a729971e2c8a5ad58969c59b405b3876","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e1d42d69862bc112cca8ef201a58b40c2c992c60","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.24-renovate-major-external-major-5373fb43a729971e2c8a5ad58969c59b405b3876.tgz","fileCount":190,"integrity":"sha512-d3fz8J0UEjdB/LEh9mYFad0CNdAmx7am/LhdbhYLD2J+dzgGHI45v1LKQB+9DhiAcgSabpAHw49mZ57LR+XVlg==","signatures":[{"sig":"MEQCIDuiLk/2HDvrRMnB8PeuPvHpr/JRj+SdNO5f1/FX24rFAiAFrdlNAtfuPsc3dgFhH0wLJOZ+mf4sF95YFgQUN8Aj6A==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718601,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJguy/BCRA9TVsSAnZWagAAC9kQAJ3wXCjIuxJal/Mpb08b\nujzvYL124ODlF71MGtlx1T0sW99Rg3akgj+G6Q054n8GdqO6J52TI1AOcCRp\nvsMKcwg+0ZSzgVmFr60rrB2Rc+m5XXP5878lSFRJhCIpnn++0+I31phQ/Lhs\n/Yqh3dxd4UJTMYrWF+nTlrCzhQay7sXSv5xUGnBnVsDnsg6fvqOpB0vzJx7G\n7e3jjprPMxqPp3YOIgKr6zHX6oDaSTwJieMERAm8v51I0s2IYQtjPCHMX+Nn\nA/lw6/F8pdPCOEF/3cerLqvSMQpg2l4wGHVHxaEIifoFe9qHPz7UIdK3Wo6a\nn7w31lMMCY35g2BhBlapqUicmsXNDKIo+/qcyxETSRmBZ5MXE6qgHovMtYtT\nNQsbKXAdMZk8MtJgmo1wXlbmkKFbl1Zt0z8z4u3n15m2jQjSYdNB5Mu7SlMR\nXyS2pc962NYtdPjC98KIv8KXcxIhAEmJDpikZu1X+D4IoV1dW87wH1IL7flr\nV1lMyXa+Gdtt0ZyIo4ZcCd9YnBFccmpaBmd1hfOXTK5RCOEH17uP+Kb6KNF0\n/UAJpp/xHpsnhihvv4H37/oQhsgEgjjZut1V8sZEOIkqTJpRA8NRVAOH5BD4\nt7SJOqD46Vu8yJMUZtlYWi+8DVEQcnUYspv/+df1gjzQaVTm4DqrlbxxJFXk\nU7hl\r\n=sDk0\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"5373fb43a729971e2c8a5ad58969c59b405b3876","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.29","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.41","@balena/jellyfish-logger":"^3.0.3","@balena/jellyfish-metrics":"^1.0.252","@balena/jellyfish-environment":"^4.1.33"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.4","deplint":"^1.1.3","ts-jest":"^27.0.2","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.158","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.5.137"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.24-renovate-major-external-major-5373fb43a729971e2c8a5ad58969c59b405b3876_1622880193335_0.07457986921199855","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.24-renovate-internal-patch-6bc39ef7473aab9e9b0f04633b4e61689692e68c":{"name":"@balena/jellyfish-core","version":"3.0.24-renovate-internal-patch-6bc39ef7473aab9e9b0f04633b4e61689692e68c","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.24-renovate-internal-patch-6bc39ef7473aab9e9b0f04633b4e61689692e68c","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"661a43ff14bce769662fb49075aa34b04d9d40a3","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.24-renovate-internal-patch-6bc39ef7473aab9e9b0f04633b4e61689692e68c.tgz","fileCount":190,"integrity":"sha512-q+AwcsjdPDLsoiQs9MrxMInLZ70bCJUhXWUUafEO9/+D2R7QRc5Mewlg8KH2sq9B2Rd6feJ2DOgXJjE3lDwgPg==","signatures":[{"sig":"MEUCIQDc5C62lGk7izME+jaP6fmJgr3wgy+hfUEL/pA6uc+7lAIgXshdE36Q578nUp+Hl7KBjwMX5GtklANBjFc2KmpEuRw=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718632,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJguy/MCRA9TVsSAnZWagAAJRYP/iZOPRmTEWOYr7RT0BbF\nq24p9eTiyNjH5lyKdUEzxiYppQw8FOO2p0F2q7mTS1nIuVmDyoVgI9IIncaa\nllo3+B7tzwFAVRwbgdNMyWekqT4AfJwr4+On5+2w16OQNrrSzH6XMhWxzUeD\nLTxIHVaFfZPtnSPOP/1A3GXijhFp2zQTZouPaIa0nZkDdHtfiSyhOkGZiVtE\nlKcWGG+HjEtjQl/P268WAe3DHGTXYrRzRxnjlMAyIFbl/2j0m5toUXnBkOoG\n4+SbX34hCVcSc1jNFzBohkWIe3kxmtp7U1VBprPgpZpldpZVmzGRQumnbbdN\nsouryrpv7vqTkCIEoA/BngvgosVwkxodt02vIuykn+7MK9A2kTFlUd6FN/MD\nLad7m4S7NsT4QjLilmni0uNedW49dxABMNUzKY+LCQe8ABfCuUMIeJKP1TLp\ngcqlkmV/VClX4DKLbxrgEEiqICasrL6DrqprecpoqcAfQ8X1I7cSC5olthiK\n85d0kOFOqFODIpawfyUcbcKLn6RhAm4/6Z4ym3odTKCxPIFqJYJvNcoKCgVc\njzPjIzrmUcMR9a8csY2slTrjUGIHq1RcBOD0fGCEQ5Wkc0maKuCvC2UAonxN\nQT8+7eiD81pMhfQKerIbOXG0m0+n451PdEgrXLkm21sKfs4QTUMHtc7bya6n\n/5ZE\r\n=EbhL\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"6bc39ef7473aab9e9b0f04633b4e61689692e68c","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.29","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.41","@balena/jellyfish-logger":"^3.0.3","@balena/jellyfish-metrics":"^1.0.252","@balena/jellyfish-environment":"^4.1.34"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.158","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.137"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.24-renovate-internal-patch-6bc39ef7473aab9e9b0f04633b4e61689692e68c_1622880204382_0.3966155020199673","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.24-renovate-internal-patch-8713ab9234ab2e6f5a4da0c70db703c9872f7ae3":{"name":"@balena/jellyfish-core","version":"3.0.24-renovate-internal-patch-8713ab9234ab2e6f5a4da0c70db703c9872f7ae3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.24-renovate-internal-patch-8713ab9234ab2e6f5a4da0c70db703c9872f7ae3","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4214aeb9121298d7e6e0eaf2f9fd117d5dc48b6b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.24-renovate-internal-patch-8713ab9234ab2e6f5a4da0c70db703c9872f7ae3.tgz","fileCount":190,"integrity":"sha512-ajLbijCsDs2tXs0cwk4Pw+oVAx40+oE2+YOET328/ghB0IJZPDWpzMfU4yamltcuolutESeSQWLSzg/tFHSLHQ==","signatures":[{"sig":"MEQCIBHvdyCLZkmo+6OGf7cS2fJnyskEU5GJPh3sVz/S1ffFAiAEWyUXVgTGKppQWMaRKJbV0e80BlxZ6CQ39DZpKPkYDw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718595,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgu0I2CRA9TVsSAnZWagAA+/MQAJN08rliuV6BCGDE66FQ\nrXpk9eppIANQtk0h/kmzCni7xSHfw+OVvGz48yYBrzTppVrYZhHYjkLkE19/\ntPstGzWOUcjbcBzhUcrqcKTj1djUUjI5gO9fau4oyxr6WdnoikzCIvYCxIpB\n+MS2sBApC9GxwDBQhQCZlHC+oz/g0A2xbdDr4SfcHIM6aslifqhAPeSHd7We\nlm0meRpOK2ygnqVxHeL7M4FlIyOdKGPpGud11+LwDkXESqaJhDKnmr+ZIttd\nROStllcl2TJHj/l29LlWvWjqUs2pQSDEDrUTtJXNHd4yNbZ/pZu3nSklf1sS\ngTbv/7aMq2c6X0XpKYRaqEUCG3+i2TL1kzZFw3XDsJm/uY/dhqXO9aiFXMWd\nUEVwxTjPgh/KtK697cZV1shPvD2+KPh11uwsrjm6HH+dAoUkzU30/yIaIB1V\n+TWXkU8MupX5+s3USLd4UsPBONLFvjxPlevCBgj59SYi+OGtu9A3joAZfbBY\nTrn/tp8FN3Eb4RceCTziGjxp0wBz4Fm5hTnpPw80B+/VcPcyJ8kmrMRW/Rmj\nEEnQ5U4AXxaqy2qVivkwSlgWACFCmG5bXUKe4m6rYUchAnWau9w3HhQ8q5eW\nIiyktzhz0F8Lp1/JhWJIbAbZmp3xTm02XsCoaG66lj+1mG7EkrxcMIOHzaR9\n5dC6\r\n=3sGd\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"8713ab9234ab2e6f5a4da0c70db703c9872f7ae3","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.29","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.42","@balena/jellyfish-logger":"^3.0.5","@balena/jellyfish-metrics":"^1.0.254","@balena/jellyfish-environment":"^4.1.34"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.159","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.137"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.24-renovate-internal-patch-8713ab9234ab2e6f5a4da0c70db703c9872f7ae3_1622884917907_0.4566200096558901","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.24":{"name":"@balena/jellyfish-core","version":"3.0.24","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.24","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"52bda65342dc9553b9d98b058cddcafe059e7752","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.24.tgz","fileCount":190,"integrity":"sha512-7WEWasEE2ZXjOoYCswgVP+Abds/QQjZR+IReXDUT15XTU54VCjYmJFTo2fE7K0B6RNNO2da0YAksRcVpv6dhuA==","signatures":[{"sig":"MEYCIQDkBj5ks7xqoBuDqte8izmrywRJSTAGAZlZ/uhnZVIpUgIhAOHNtSSEQl/uvkRhOies4Q6MtHJlDsmf2S/NrXsIw3m2","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718530,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgu0ODCRA9TVsSAnZWagAAr7YQAJLfy9zclopuFLwKYyLQ\n2bad9cteFV91oWdQ/lAK0rjukYFqqzJs4fgV0ZdOqiWxRCveqFQKN9N+Jun9\nvWv5b9wLzzDGuBqGV0FBnygHG0iyNEY/ew6fJC9SmxAlxOGAgT8damOhDtn5\ndAMcQUMAEiQMy6HOYI1iOFxZqWrja5gATTXUu/vSGgyQ9VrSzry8+NYpeahF\nvDeTFK7V9deIynwGbbG3yrC/w28X41mELNLAYRMWJvX+t3fkWobox1I3m7Uj\np5MgC0DygxfPm+JzJM9gt8NAKfBylTnIbWHVBkp3Hztg8fiXgc1lVZqxdCom\nGKNg0vsDLFJ8YNci+XvTWH6wLHC4VcmHpcKhfyAgWa60S7J8SlUjYvAEp+DT\nbZeJL1QXmsjSAJdw5MxsSUvzU6ZSa+jvJA8SS306VJ0ORFc8hPqSKrG/ufr0\n89NzI4TH/eSeXwxzIamxuawYRzVUTrfeJ7hCVy1uMpCq8ioHYMfTgQIQH7IS\nc1Yh4Nt0M4TZE0FtZNyKNgot+9/vryo2Vt4yLph72JaMUNERtlDRDumO7FKj\n3SZfvokRN1W8DKjrfM3p/fTVF4ZrEtXciuwIOJnbyM+IWWT9HNGsLb1Qfy3p\nL6+gAVzAS9B18oDkRIEPD819QzI82ZFTCyCsR6PeUXEc9OZWMvmUCKmFzrGt\ne4by\r\n=5662\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"a991e56774b61605389a241ee04d4b31663242e8","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.29","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.42","@balena/jellyfish-logger":"^3.0.5","@balena/jellyfish-metrics":"^1.0.254","@balena/jellyfish-environment":"^4.1.34"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.159","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.137"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.24_1622885251274_0.10421192793030443","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.25-renovate-balena-jellyfish-types-0-x-b6c1cee5360f801eb13966911f944d1e8564739c":{"name":"@balena/jellyfish-core","version":"3.0.25-renovate-balena-jellyfish-types-0-x-b6c1cee5360f801eb13966911f944d1e8564739c","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.25-renovate-balena-jellyfish-types-0-x-b6c1cee5360f801eb13966911f944d1e8564739c","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"75e493341d23556a2ae30517951db97f2e4dfd4d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.25-renovate-balena-jellyfish-types-0-x-b6c1cee5360f801eb13966911f944d1e8564739c.tgz","fileCount":190,"integrity":"sha512-vpJkAIAue5SiVXuBpka2ZzgftbAvqdxnnIgPJpwXlCNC4OdKkeYZQPL9JBpCXXUgZlW+m19GaMDsdpzt28dJuQ==","signatures":[{"sig":"MEYCIQCWS5Xl9jDrsPUQf9vzs0dYKmncUtWestD5MA6leH0CsgIhAKb1l1pResXBZO1hkt8z9BF6WUiORNsILcPsiB6jfSXD","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718706,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgu0ScCRA9TVsSAnZWagAATP8QAJ2FMYnk0qbphlLR71Zy\n+z+vT1Mk84p2fPeICVmDxeqIX+jCf2MkjmVkokt6M9w+NAthkFwBZ3+Z/bfj\nOUpK08YAfV2Dri671E7vJFT7aiSqoNilHnIAfaFT8moptx8FAxmRG3aJJbzM\nx7LDw8ZkxengiUkIzHVLO150PQJyW8DamwkffqwUsCj0J9OYgPQDfXnBwa43\nelF0z4Vpj+lKRsmTIYn+Sn7RPGol1IePsu6drjLvXGTFvdSJyKigRW/IsWRx\nZvOqD/jrABJ/SHT5Zl/hKSiSbikY8XQjFaIzii7tWLn9BOXbk8EKfMkUVpkf\n5sipdHJN4x5BY/tdqnrj6vkbtJ4kkktv0TiWsLVETmXTvmw/nuPEyYe7XDSt\nFBjX5+4yQ8kiiWKHaOVOnbkIwkPgOCq6UzRtpcnR68tuxYhwRrUFgr1B2Bfn\nXIMcuy53rzUGiJooOLIHC9yUoXSwdHsucwGfo2n2w3I9nTWvR+DKMJlFWe+z\nGh5zrEf3ip+e/ZA3xBBeKZBigPfzeTl+OSYWvwVbHn4+SJ3ce3SN2RrcJa+c\nRF7TKILE8j3dXUjashCPX/CX979NDldAjTqyXQ19aJ3mtv7N2YxAsnz1ZiGD\n3hFIhvna1rqC7kOXy7l2WlCmyOvtxJUrik7qbm4U6khCIahEyodhdSnQbHaV\nJAcN\r\n=bbFh\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"b6c1cee5360f801eb13966911f944d1e8564739c","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.29","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.42","@balena/jellyfish-logger":"^3.0.5","@balena/jellyfish-metrics":"^1.0.254","@balena/jellyfish-environment":"^4.1.34"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.159","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.25-renovate-balena-jellyfish-types-0-x-b6c1cee5360f801eb13966911f944d1e8564739c_1622885532134_0.993607455278988","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.25-renovate-major-external-major-e15c031e02a822a83780d71f862817efcda5172d":{"name":"@balena/jellyfish-core","version":"3.0.25-renovate-major-external-major-e15c031e02a822a83780d71f862817efcda5172d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.25-renovate-major-external-major-e15c031e02a822a83780d71f862817efcda5172d","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b9287b16f3ca1b34d1ae23dbdb1fc38c29e43ffd","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.25-renovate-major-external-major-e15c031e02a822a83780d71f862817efcda5172d.tgz","fileCount":190,"integrity":"sha512-pTfjTbKnFVBDjFh6Fw86TjKJheNC2cWkbOE2Akx4ip8YMQU80DiU99+TsnqcNJ5WQI3hjhkMn+YdQG1KE7+NtQ==","signatures":[{"sig":"MEYCIQCRbYpkEdm6x3aAToiYWADT3TcWmtHyxs3eHi8pxZYw4wIhAL0UV5XqLc9mZanarHmd4b1mKzstqrWVdaUADPfZRbOf","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718672,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgu0T2CRA9TVsSAnZWagAAHPoQAIZFlZ9l4vP6MKSSis5V\nTz2DcW7l8Hp+HCfeAGXnGfCr6sa5pEr55tiBxqNPJGP65dwgchBr38MOtbi9\nq9e1ZBnTZQ+ASyt8SaVtVHcAOtVMn+b61FcwT53tPzi29b3quH+Nw4nX0j2J\nx1ZnQGYUcsClhq8miDiJGFVzwzM3f++e4oYZeUX6BltN6IQ4dPH2bag+YL2Q\nK+cDlNHcC5THivsXyCZi/x8CNGpCmYaIzBFxvD2TXEFd0ppKr8qwlAEyZDkq\ndK73b4en2pete8tHLzWAfDa+nqjt+wKQ8PXMM3qjhr8E7Msy7KoKObz8s7n5\nQSyukGJyAqeA0rblDOzo1V7pw8bu7KpNc+gUTYkvxOIuLTBuT1DxcpM7273F\ncuqCtO39VZw71xnj9Ztpp75FnK8vcTFbUvwiEaOAU5DOrC47zWkKKHOKHIcg\nNnI6GAis8sJ1aR2SaJ4OuHD7nXnLz5HJ7wFIK5JusBVpP5tuiuFSeBdOj2p0\nEwx6PCF3a5MLpt3KBaXXybbj0Y1BUV/Jxyr9gX+Ze46KkcM/1dDJHorZehew\npnDfoMJgYT1TIbWjVrhXkSVpZUfDPrQB5TH9Xvm3vUg5JFZzU8JZyxI3shLb\nt8RYy9sosnBTT0JCvIA68KFcCXCddgTAMZl+EEejnktEUwbLmoHERDrmJH7o\nt4Ge\r\n=GTur\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"e15c031e02a822a83780d71f862817efcda5172d","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.29","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.42","@balena/jellyfish-logger":"^3.0.5","@balena/jellyfish-metrics":"^1.0.254","@balena/jellyfish-environment":"^4.1.34"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.4","deplint":"^1.1.3","ts-jest":"^27.0.2","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.159","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.5.137"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.25-renovate-major-external-major-e15c031e02a822a83780d71f862817efcda5172d_1622885621901_0.32401614902377895","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.25-renovate-internal-patch-39743676e754e33d78434f7c817e52f70a87f67f":{"name":"@balena/jellyfish-core","version":"3.0.25-renovate-internal-patch-39743676e754e33d78434f7c817e52f70a87f67f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.25-renovate-internal-patch-39743676e754e33d78434f7c817e52f70a87f67f","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6d83e486e30b9e2c799fc6915465be44a8ebd1f9","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.25-renovate-internal-patch-39743676e754e33d78434f7c817e52f70a87f67f.tgz","fileCount":190,"integrity":"sha512-d55WbZ/vu5DWK+IALq3NqwMR3x8rctEqfFgqgWd8j6fEWVMKRDIKg/okvXy2likWM4/x4Od2UwMXGbDcOboCDA==","signatures":[{"sig":"MEUCIQDh6Cc4nLp6lDuef2onDLYpbTo6gOjjPyifC+8vCah3HAIgWnEqLRN+7XWQ0eqQOamSB4ZveHsVhFThjDLj+eoXu2A=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718700,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgu1nMCRA9TVsSAnZWagAAu34P/2zeqHRuIxA3KfQfPPyV\nxv9n8irDYh5Y0qeUs2liWYBlHYDZRbXM6Iq5BuYS+gyt5Qpow4g/6JyVwMEg\nDCnn7/EC4xP7zB37Po4FJKVQRVfSdp1EXWoQYL16n26d4VZpiHZUhWE61gPW\nE964LOr/GNKY9xhyXwW1tKC0YOqqIBssbl3aYpj8YGGoM6VsGnv7vCQ72a7h\nob4LDR9YTp/hYNp56T+YWLnJa6Dq/ySJviJVDl0dhiTdYzVHRljii+fPKHKS\nEsqGSr8fd1+NVGHprpARFUXN2STMiIEU/XLG5TQQwUu5psVJ/tMfGLW220CI\njaHitfzCC722OLgE2eFB+EXTXLcq4STvsiP/1QM7Gs9Cup/k24m3Vw1WA78B\nDmVsQ3VtSPo3vaWzcd69+bd6yzxmIog5HO0zrwRIEEu6mljqormw1hY5RESP\nAKB56zTMbZ3WC9/s2u0Q6ga2E/N0xhgQFRj4yzv7G1J0SQ2sWvfHe1TtbEb8\nZrW255E3VzeeuxAMbGuG+snOsgw3PDd1NGVQSep2NsAJnPx3m4awZK7BQoE0\nvW2OxkvMg8eHb+5hhZxeaL+LRucfPaAQtni2Le3C5rqP8O5IYnQIpyxgrRqY\nezzy1N5PCGwMw3+7VpjkCEveCrMXPln3HldMxuGLj/o0W4tOf+ZlUhVAkoTH\nwBeR\r\n=xFzY\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"39743676e754e33d78434f7c817e52f70a87f67f","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.29","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.42","@balena/jellyfish-logger":"^3.0.5","@balena/jellyfish-metrics":"^1.0.255","@balena/jellyfish-environment":"^4.1.34"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.159","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.137"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.25-renovate-internal-patch-39743676e754e33d78434f7c817e52f70a87f67f_1622890956315_0.6363342658064373","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.25":{"name":"@balena/jellyfish-core","version":"3.0.25","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.25","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a784ddc903f033fe8bcd41e8a37b38545d3fc9c0","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.25.tgz","fileCount":190,"integrity":"sha512-8WenOUwXEO0Z++RGlRVGitH8FLjW+BaPMAKn+TRgiiYlr1edaxhEEGscSG1YWYEz8nGBh3PZfHktlcEGNTHc4Q==","signatures":[{"sig":"MEYCIQDm+1TeEH29DjJxyVkNLuvjWtxX4z0D/8GuFRh3RoN75wIhAJ4gQWpngOpdQZESC+6y5z7OVddgY8p2YrOn0cXxvQuI","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718635,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgu1sXCRA9TVsSAnZWagAAneoQAIretZS/bJPHnHx0sJtu\nBWfxHze5eL1zeVdlFfxST7QrmSB1jeslzfymm9XteIAkGTbxsSg43ZtjAqCd\nalRe+of53JayItmkizCBhGttiuYM2MYSoCOEA9YSgaTHxhEXIuLxTUwl+ljL\nRLLZ/MWEihzImoUiMtl/gtsdMTY0w6SeJUNnwbMM2iKJY5GOanTnkLLtBoIf\nOyh1TU6zyBAyANt8hbZ+yDYjYf4OOcRz9jaik61u+nw5RbzDZXQROFNhXQWJ\nx/BRiLrqRMdWJPpQ1IiKdFpQm2LyKmXUjqlYMmln/XPH4aGId+Klbe7f8X4H\nI5gZjqIFYIK/cL/ba6gZdjElfUMLgLhbfSVcu9ieW0W3zQfA+VYEwoyJ9NpG\nzy1LOJ11zeqdbQeN7dCl9ZKZtkq/CKt9BwDOE+g+QaAlUF3tjAxWwK3gixDe\nw5OZIEznaelBfq+vPRU/rCjoOS2H/Gh/J6OGAT42mzeB5BjE06KMlWDjf6sm\nnZqiyKSQZ3YANMSOF2qCM+T/wsE2AsFXkzda3G9Bz4vjE3PwcsejGBJl6Yfm\nRoeHRUeemnCotQM+qhpbFHhYZcDbEZZY+eY5kb93gsuk3lRwDFF5TyUCSr39\n3CqAzjismDtiBEyInZnSZ8jMZS2QB2SIB/cVHcsKPgQmJnPzB5udeY3bwfqh\nvBo0\r\n=nINt\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"a396d99d689308db904a186890a9cee432c59bca","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.29","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.42","@balena/jellyfish-logger":"^3.0.5","@balena/jellyfish-metrics":"^1.0.255","@balena/jellyfish-environment":"^4.1.34"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.159","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.137"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.25_1622891286889_0.34983205006503426","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.26-renovate-balena-jellyfish-types-0-x-d40f535627e27fa843d7d7027512bc161143d2a0":{"name":"@balena/jellyfish-core","version":"3.0.26-renovate-balena-jellyfish-types-0-x-d40f535627e27fa843d7d7027512bc161143d2a0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.26-renovate-balena-jellyfish-types-0-x-d40f535627e27fa843d7d7027512bc161143d2a0","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5903243f1a947fead6a699581b98434ac9696615","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.26-renovate-balena-jellyfish-types-0-x-d40f535627e27fa843d7d7027512bc161143d2a0.tgz","fileCount":190,"integrity":"sha512-YSp52qQ/VtWsLTZs68IRzxpCZwQi3F5tq1VDY6uH5CzwzsCX/JOvmYcGAhYLvDgXBKiaX8hI5sUxeMjwl6ES9w==","signatures":[{"sig":"MEYCIQClV0MCQv1Y14JYJNlVX5g5R5f8qYSqE65kM4PiMMrCCAIhAO8W4qD03R7Zcn9BlV4w1Ed6vnEVRkB4Lr29HcvTAM0M","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718811,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgu1wtCRA9TVsSAnZWagAAcSEP/0Chk4Bw0HUTZMhM6pEi\nxxeMLHxn6kqvf4FWmI2FUwoQmN2lDCfdktYemeOkK6Wyk1uLXWydD8zKLsY5\nvhtj7/gmu9azHUXhROhUPqnULzFJorjbpFr5IhEN2S4+KjGIhMNms7mOC/MW\nqc33nnGU5Jrjl6E2i+8t+arStybnD9kb9Qav9vmEhYCT+boQHaCqklNlM258\nbmMwm5qEatl/NlLT4O4A2XFVbZmSi6twgT7dAvfIQOsUTAFRXQrL3u019M7J\nd7tMZMjXG4r0rT7eMHEGwJPBzxcF/3u5JJbTJ4yZFTuJCQygS6cTLNaD8b7M\n2qMNEpZ/yEiFkbBvzCT5DHm8vxuJkuRMLtKK32Q2zOaGnp49vwQLxyDS0eqP\nyslEdcYFhWIgHQ4ui4bjsYcCSRx+xmCotuvZvtZbXND5xpZHFK3MdcTi9g3R\nOlPgD3JAb+fERtr6GevKfC+EFSqxTsVzULZ0e4F+akX/DVwMWA0zybZwUwgm\nivXudNf0xV+cIEOaRV69+yXJmktmySD2uFoYYPZq7nlWblXGPWw+J0+JBofE\nfAH5jgixZOHl1P4WNkDjAYGT53NBWvP/ShM6gcGhnEzw8xaWngeDVD3FqglH\nlVe2i8bSS5StxLhV+tVeEsIfqEci+2/4W158dWm9EYCF4L3/160uMmC5Xb4Z\n6DBD\r\n=FKca\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"d40f535627e27fa843d7d7027512bc161143d2a0","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.29","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.42","@balena/jellyfish-logger":"^3.0.5","@balena/jellyfish-metrics":"^1.0.255","@balena/jellyfish-environment":"^4.1.34"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.159","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.26-renovate-balena-jellyfish-types-0-x-d40f535627e27fa843d7d7027512bc161143d2a0_1622891565640_0.9847316175213503","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.26-renovate-internal-patch-4f88ea4ef252885f9f5e159caa653a6ccb17732d":{"name":"@balena/jellyfish-core","version":"3.0.26-renovate-internal-patch-4f88ea4ef252885f9f5e159caa653a6ccb17732d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.26-renovate-internal-patch-4f88ea4ef252885f9f5e159caa653a6ccb17732d","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"06b9ecf28516f5da05be2463b9ff442922ca9730","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.26-renovate-internal-patch-4f88ea4ef252885f9f5e159caa653a6ccb17732d.tgz","fileCount":190,"integrity":"sha512-l1v8NJsW6ObW81TOiVwsMK9wVyxtlKte01mz25wzGJQqjyn/5jHugSd8ocZFW/YL05nzZQ6As5Yh+RwY5xyimw==","signatures":[{"sig":"MEYCIQDC4mTSFW6g87VxKYAox+Tg3zR0kgj3lfSKaMWI7rMSmwIhANfUVFmVPKleGFExe4Ya5YLje9MAcEob9XvSyyQL9Fp6","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718805,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgu66TCRA9TVsSAnZWagAAi1cQAKEtX6ZRagxh29ZIk4WD\nLL3BqkOZH658wr/tJLPZ2IJ7yMnrz067dKn1JA13S4HdlPHStjtYZ4rWGCoD\nTv7KC3wSU92K2SMgBAuWHBhKbMq+Nb7zMjgvBV8h91i7TdkhhLEhpoFvLqDW\nqrpxq0VcStxfFBJLqZvPS26HkwgsSIE+MwszxGvLz8BxOMmaxEmGHfSgXH9D\nIbn7VCQaX+SHrgRdtT3ExeKWvgq1/Tk7w6VhHVtdHN7+Ch8j/7dU3M4c8EQ1\nCpNLBQyB/Zm9aisO9jTBmownNmJNJYoUQ8dDJ/WuTBBp55KcwUMoo3Y+NBx8\nj9r/Gr3mSNw2pmdFrxkg7Fhw3U4MZck1OQJtpSLdQg2wJbgHDbrpW3QxWJx4\nt2xqFd0P3PktceGUoi0KN/0dpTyNoH84Go+RoT73xwzR0rtlMe/oc6qED2hR\nLw0Ufs6k41C2RVGI+l8iZZTq9UU/l/wzrdA6ZVgUnCscvpLjwsjIpzlruSCv\nuk1iyub6WzestPUUnbIs4suHmmhFR4zc6y1GtIgSuGRZYYwZGl478PbOH5cr\nqEuYA7ISkzfowCtnZcBL8ZzyoXdUTHg96cf2ffiN2IBjtxxYTT1VsdRTUjqw\nuLRO129i/FAoCsMg0X9mQXoa7h/3qdwru4ZY6HouwmXsOEnfXQL86/pEInwq\nDMgo\r\n=QVVf\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"4f88ea4ef252885f9f5e159caa653a6ccb17732d","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.29","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.42","@balena/jellyfish-logger":"^3.0.5","@balena/jellyfish-metrics":"^1.0.256","@balena/jellyfish-environment":"^4.1.34"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.159","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.137"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.26-renovate-internal-patch-4f88ea4ef252885f9f5e159caa653a6ccb17732d_1622912659070_0.8850634692757462","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.26":{"name":"@balena/jellyfish-core","version":"3.0.26","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.26","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"628f20bb8ef76018ff8c80773e5c5d77ef9ab25b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.26.tgz","fileCount":190,"integrity":"sha512-5C676d+UqK0zsvZIJvDqPjQGpBsRoQ8f/RWr3036/poICy60LLhpH4Xz/saRrgPYgKSJTsZ6d/adfJAJR/lCgg==","signatures":[{"sig":"MEYCIQDofype+JJ6yOHR6/TajpkOPO5TBp02lEOQ4Ah4FAc7SQIhAMc9Qm2/UYCj4buiEaRHHmlVZP527Q7zhgXDoV3PkEym","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718740,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgu6/lCRA9TVsSAnZWagAAeqgP/jamM0OXeVglWrQNIyps\nspo+5PeomVISTAEW3re9rGAJV5/wNRLCi0wh+e1STmWZsBYEIrwgqZ6zcpTS\nXGiVLYXCSAml5aCOiVO7W8hMjzlCw+gMf0PjJp+9JI1Gxr+4iCQjkNpz7g4g\n/HAF5+Wpi+XU3fzmp20T/zKjsO9PjFG/B0T/BRUtPlyJIM0WZDeaEecqvcZz\npbVlOoVXPxZZxH90obKj1m2iYnok7u20WrmgjNZ1s4YSTqzL0kMI9xjQ9lMn\nz4QgoWsEqtMFTKpWP9uAOM5dixWS5NJjzRmm5yuG3MXsxh0zPdNfPjPHG37e\nmUplbHtGEeQT+T01Zo5Oq8aqJurKFAa+pu+Kn4x+mG57jjB47QL1I9uMcogX\nR3F4/AHxUeHnCALx2N35TgDOctKhtAe1ooxtfx7EuZ/HgOMyWxOS3jAHmSgt\ndKyfYdNRTi72Q1mGTde0yKUx9DsPK8fmeBmgfiu25uWNs1JOfcsJVbwU0oM+\n6gVIQOdfKUyDTEeHN6r0E0gmWl46jxsms1e90k0DxW7iJKDw2FxvlvlGegmw\nMZ7Qg2Hoz4sJtCz24kv/8cfaoGwiYPzi1/q3h8SMWxg1DZphP2ZV1vqw2qg/\nvabD7o2slpCkRDP8O6PI3CoLd5ItVOSeXpqicKNHSj/OmUha0AjyJnWLVmAZ\nyVIi\r\n=DmkR\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"605e6ebe656630d3f1988c04d4835b93ac1cfffd","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.29","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.42","@balena/jellyfish-logger":"^3.0.5","@balena/jellyfish-metrics":"^1.0.256","@balena/jellyfish-environment":"^4.1.34"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.159","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.5.137"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.26_1622912997034_0.3962153261012382","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.27-renovate-balena-jellyfish-types-0-x-4de789b6bfda01621393ccd76ebf5a1e9ebb70b3":{"name":"@balena/jellyfish-core","version":"3.0.27-renovate-balena-jellyfish-types-0-x-4de789b6bfda01621393ccd76ebf5a1e9ebb70b3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.27-renovate-balena-jellyfish-types-0-x-4de789b6bfda01621393ccd76ebf5a1e9ebb70b3","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3acf7e939b82fa27ed3d96b7b73d640093e8cc11","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.27-renovate-balena-jellyfish-types-0-x-4de789b6bfda01621393ccd76ebf5a1e9ebb70b3.tgz","fileCount":190,"integrity":"sha512-Lp+xFTzkuaIp6fPX+GE2HaO3NbGPgznvNvrlg/UdINIvYo1vocuUudJXxBYLECp0c2RtRzIs8FPXb7fqYRgqEg==","signatures":[{"sig":"MEYCIQC+C6Nx9+8UIuld8dU+Fy7AESism2Yh/YDUcLkSz6AULwIhAON54raJiTGNtctWFbHPvDzccBA95g4DpC9kkWKG+DoO","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718916,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgu7DzCRA9TVsSAnZWagAAMNUP/i4pHoNYz26c+ijE4ogv\nQ6PACLY7HjSD1CUo3eVXaQTQ83kOwPeY2OSVwAH7VGjSfvnXm/lmNJtlEadK\nqQ31Z5JyLjjU9YyDIOYFtNSe09lvvPdVb1MPrRqot42P8+1WWC/N0sXmOzg5\nbiNHBRyCALpK4f9aA1S4H68VyuTWZKaNtOWFLTyobXNoPaqbVTF5z9v0UA7B\nEGFsvd1bITUwuNb7nkrRxrl6sobEPAzSziNlV01a4Z6voh7PDzxMi9VAp88w\nZnzRyBWTdNYPyJGBF2J/RnljFhU4X/0jus4AVL6caisuoZGTgmazGeNo1Jq0\n4K/S2x1r63LEcZDXiIFKeGkNEE2olOItBMxXMVGpLLatMkXKoDRaQICbKIXG\naP50a3ZyWAXuMaav+ATwk0sLY5frO54tabqZdOMqh5xPzKQpq2UP6YbzvdHs\nwdpo/U9opVmIYhOVcwBd6RotO6VM4bkUxup9+fCVUWqFE2H4VNpcj0Qgx8HY\nuu+FebemDR4lEm2VRVAtaQh+Xo6cOIlrIPCmHKaAbxaxgAo3Y0ZIfCx2q5lv\nvNH0LhkPT09gDm7dpSKxrhiKHuqi9Qhj5LqTjIM2Zc2n3FhRiGe928kl0Wh5\nMUSX6XZv6KyQqMgwhRrNJN2z4Bshu5W7dlS4+tyvCTLKcS+F0D9+OITI/TOl\nl8up\r\n=UvbZ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"4de789b6bfda01621393ccd76ebf5a1e9ebb70b3","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.29","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.42","@balena/jellyfish-logger":"^3.0.5","@balena/jellyfish-metrics":"^1.0.256","@balena/jellyfish-environment":"^4.1.34"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.159","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.27-renovate-balena-jellyfish-types-0-x-4de789b6bfda01621393ccd76ebf5a1e9ebb70b3_1622913266985_0.0018684006837241807","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.27-renovate-major-external-major-973af80f6b5437cde3e58fb0afba6dd0b6f275dd":{"name":"@balena/jellyfish-core","version":"3.0.27-renovate-major-external-major-973af80f6b5437cde3e58fb0afba6dd0b6f275dd","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.27-renovate-major-external-major-973af80f6b5437cde3e58fb0afba6dd0b6f275dd","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"38cea0f28459f409cdea2f885b542df82609d992","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.27-renovate-major-external-major-973af80f6b5437cde3e58fb0afba6dd0b6f275dd.tgz","fileCount":190,"integrity":"sha512-+T0o8OQWBMnoa6KX3lE5/xDP5YEXmtFKtXpUrW+EdTYqU3wVUzvXgiAzGVrWmBWDQTQFciTg0Pait0V6AP+azQ==","signatures":[{"sig":"MEQCIAD+regy879MzIV+EIbERD+STUn9Cwngfjv8jo9Q5sUIAiAeQn/3fKz14HjsHVQWMLBPObdM4cPpBq+eyh5d20vm9A==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718882,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgu7FICRA9TVsSAnZWagAAXCYP/R8AILiiAH4a3rlx7mwd\nS88dCYaIomCtuSsHSF12d1CBCy6aMWFbspQt1aTXDVQhbTKQWyBJmtmHE93h\nA7QsKzyBYCBaPAd/NRj3GB/0AjipEOy051Z15ceGtOr36pCknehwWiaJ3+/d\nQaEz1x80XEGP3jzH8NajXks6yd+kl4Ln/clmCfhTj90Kk1FmzWwDtnXerZ0C\nccX3DJcAsj5VYlOM+TTwvjaPaHfMkqqJAUfpo1YyUF1Obi3sM5eO6Gd0LJxk\nMNJ7mH697VTNlPtT8n+E+ZbzJ6i18WEWGVLFDdemcqlrGvXmxiRTmJ99Wp/b\nHRk821VPRWUobD4xnbyvlr4ESjpiA9e/FHaVZjBrC9MIc0NyapGp7H+OqUC4\nTSJD/GUBvanwguEM0kdV+tquMVyJ5OJrvkyqyUDbyZDVT7RQOaqD68ZXOAVY\nrT9P8eiBdRaQSQGA9agcZSt0eLWwmIXCpZYQVYF5fir1zJTH2mslQzthYh1i\n8yMx1m23il1zwg40cY53+gaNYFqfPRvwciMfq35wyl/J/YbvzrooNr3bX/An\ngbcBOBZGtN1f+RYLgvLTpr9ZZ7SgbKu77loHmWfMtAvUhSY/NANm4q++rPqs\ncPhaDyH0ucHUygP4RRFEIDAq8tdh8zGNqGmqwtJHwfoahdJ5X4uiUpiFyNH+\nO0vn\r\n=XBJ3\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"973af80f6b5437cde3e58fb0afba6dd0b6f275dd","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.29","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.42","@balena/jellyfish-logger":"^3.0.5","@balena/jellyfish-metrics":"^1.0.256","@balena/jellyfish-environment":"^4.1.34"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.4","deplint":"^1.1.3","ts-jest":"^27.0.2","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.159","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.5.137"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.27-renovate-major-external-major-973af80f6b5437cde3e58fb0afba6dd0b6f275dd_1622913352243_0.13228410990905348","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.27":{"name":"@balena/jellyfish-core","version":"3.0.27","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.27","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8293fafe90ceb1b09f1dbdc474ca079ac40e311f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.27.tgz","fileCount":190,"integrity":"sha512-PSbl498m9VcwSh1IiI0dEFP6bfTLfrI6moUNjb1nCF7KJPGgPZg0kzgrv1XELh1y0PL+ld1PFhzdpYrgAIoGYA==","signatures":[{"sig":"MEUCIQDOaZI8L8uodQNC7IqxbY6uG4m9S9oqAGswxpBEABxwOgIgTXaYz4PJoacxGJ9ygS965T3MaERaj3evgrAYr3AIcMA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718839,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgvAsYCRA9TVsSAnZWagAAc9MP+wWtnQyNdiTLr07dqswt\nb/b5Jck+1PnpRBlcdlwltJxCx3bMQWdCkQGif+payynjAoO6RpvlrbE0W++f\n6dG43On8+h65WiN8pSAYeK+t0DKxICyifd0lBu+/dJIP2JRN8DQblxmwDmVI\nH2ZntY54FPr4lGVG4mmgtuyesRCsDzd8S1mpJCGzTnSXobz1pnPTBn0H40LR\ntcWN+Nfiox9lcGlXY7nHQO8AjQSnQNqCXSAaoC0Ka50ksVd0Ho6Xi4+E/GuD\ndO1M2P9EZUCzbhQ/9lmqhjkEl/6pQaLFH7DAVISiPT8kUoOAU8bZQ8JKHNB+\nX0fCNRbx0SsIlLuV5JfNrOfOwikmLHxvYqlZDZaX0LlTDxsv9iGBwHUJpxKo\nwKATBV40h0OpnlL/gbHEWareVjDF7wlw/ijoRvjYGQ+tnx+lUESLF/EJg5c1\n9nA1kqPa7TXOt6iZkbSCMQ7VT7KGxzGnbC7MojzwToBcsEHXgPpZW8pJAqk2\nqeuRCAy9W38lAfUuT5+WPaEHisvcwK/SoaB9+SuBlwP+LOoM60vKjOvej9Jz\nPbtzV66J/DaG5o9WShPAKhnEEtZDWyiypxa7CObO3snDJmwe2GsogstfKGwp\nEGvOhHLmzZUWEcftrjbOD++iE6zQKNqqcageiAhRmUZE/tTKYByCNfR1CYwO\nKbDF\r\n=12rW\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"e13db72bb160efc79aa12bc30a30cd744a784244","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.29","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.42","@balena/jellyfish-logger":"^3.0.5","@balena/jellyfish-metrics":"^1.0.256","@balena/jellyfish-environment":"^4.1.34"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.159","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.27_1622936344059_0.7603970682882071","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.28-renovate-major-external-major-4e97ab1471bd80d1e09aa99bb2e8b1aa38e7c845":{"name":"@balena/jellyfish-core","version":"3.0.28-renovate-major-external-major-4e97ab1471bd80d1e09aa99bb2e8b1aa38e7c845","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.28-renovate-major-external-major-4e97ab1471bd80d1e09aa99bb2e8b1aa38e7c845","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"61dce3047c4ab06442328cc5fc0ccba99d2499bc","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.28-renovate-major-external-major-4e97ab1471bd80d1e09aa99bb2e8b1aa38e7c845.tgz","fileCount":190,"integrity":"sha512-MQwG1R40y/yFFeLrhjRcF+Fk95+PK81PBYSA1XtMFtyBHXZOWocWFzY8QGHIcZhgirlKXQ/yBVJC8iwtKaKsOQ==","signatures":[{"sig":"MEUCIQDLW+ZxeLy43l+h3TCOiO4womMPbKXZUZg7vvNSyB4jBwIgdzt3A87I/pnlcD+IIsoTsg1X2b53pmhBqucfKKWjjm8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718981,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgvAwoCRA9TVsSAnZWagAAAQgP/i8Z9HIKKybaRim0AeKj\nlkMfzvkU2QbPyLj43i1AZB1Lq8j5vkrlzah242lZAAAmlH34JZj+e6g52Y1i\nxd2cnxh6I3Q3A6paM+hnHSZMqSiRgLP75FQ9C0hG5KQYx/c4JIhg2EiU/jJM\n7FtrfI4VmX9xa6b3Rc9L9Ro+jL3VUNQ/eqNxvsu8H3aNfRm3UIN2KghimcSk\nqhZZCXVrcvcXVJJp7QUDtNZdO9hvJ7a9F3hyGkpxONtKbjTNJowDX+b1YM1N\nM3x2iLNx0rChWZdKw6diLIwBeMkpD8v8bEFyXn9AI+YWeMjo/mc/+AdsYQXZ\nnLbyTnn2ev5rhBVmDUtRJYNjGu72Zf+JEfHHJu8zDe1QSF+tgRnfKk7GrQuJ\np7nLuPmahixo3k8kKmo/FKhxGfeBREPUbtS8av6dxM/JnkikNRIyvqwiGnFG\njLJGBOLkVYVsFdIaReRnhBqTeHUbOhRl847QZw8QYkKzmCBn4rMDQ1QMRIoc\nW5PKqtuBzCj0/20wf9hqGOwG9NcowWoGyc8f+Mba3IjYEI00vj46TTNc4hQH\nd4HxtcJa4/TnuJ/T+M5/c+qJPdi8df4xGYIUFIzggPs/fG39ipqXgm6TGBuY\n+9IV3Xzd2+25Ezfjx7JNwgY8ND5eQbp4XeaF2NY8ZOr363u3umlD1X5iIJZM\nqEB4\r\n=0u6j\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"4e97ab1471bd80d1e09aa99bb2e8b1aa38e7c845","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.29","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.42","@balena/jellyfish-logger":"^3.0.5","@balena/jellyfish-metrics":"^1.0.256","@balena/jellyfish-environment":"^4.1.34"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.4","deplint":"^1.1.3","ts-jest":"^27.0.2","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.159","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.6.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.28-renovate-major-external-major-4e97ab1471bd80d1e09aa99bb2e8b1aa38e7c845_1622936616726_0.4453076370742426","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.28-renovate-internal-patch-731e6d58d5984331fda249cc5f290d9aaee452cd":{"name":"@balena/jellyfish-core","version":"3.0.28-renovate-internal-patch-731e6d58d5984331fda249cc5f290d9aaee452cd","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.28-renovate-internal-patch-731e6d58d5984331fda249cc5f290d9aaee452cd","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2a125a2d6844d56f51f696ae7a85072f346c0328","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.28-renovate-internal-patch-731e6d58d5984331fda249cc5f290d9aaee452cd.tgz","fileCount":190,"integrity":"sha512-ZwRQ/MEQnPj4vpEBTKk2f2i8DNgU1ntFcWU3ZxgXLu3Q6ZMHNZWC8yRxY50W9+DuW0LJBJKsKuAURUTPOZuBrw==","signatures":[{"sig":"MEYCIQCJWuMTgqicuUZpShvFcGlt4uQFGVvQW6HuwRFwWIKYfQIhAPzL3W16KYQeWNZPmxK+nXFfD2Pm1sYSvTY9A64vllox","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":719006,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgvLNFCRA9TVsSAnZWagAAIF0P/09DQvkwORzQ1Pez7yqC\nWnmckC38c+B2MNmNQYCpiCrrhhzZP0A4AFZCjmfH6NBud2J/M+R1aXtjsfu4\nNnZstXolDQdcIoMna09AEXcmpLFL2qp1vJ4V4KDbJGzmbR4sAWkZ2g24eENI\nUzw1zzqmcdoIAaTiR3N8SNS7fMRPSo0j/hZR7CYeBkbOTXCNdpcG4pieN61b\ncJP4g3yoPSqfswb1to6U50y/cAC82XHPX6NVTma1QgOOtfnaVEyf0LJsuPEy\n20TAyxfNKAuShtE9ljVYNk69Zs2Q9KuxvIlW3Nz5HROgjWaLR1TiMkqny/yK\nBr82TlvDBHrLXd33AjVDleVqzRYViHLTBzmtKrAttSriVCeezuzpJ/trF/Os\n1DszOoHsgg2DJwRf09DJxYq1su3tTIxO74wb6jwi3G2lVsWW5osEvxuVy3br\nJG0Q5RnzWzWeST/1FXlRKEaFdNH5rHwaILLxWeciRscOx90LDniSOepDpk2z\nIVtTWlqRKU/qAPxacDywk6OI8ZSLLgm5u9u6TtB3A0AAFue1IOSB4LcFeekJ\n0My+6YwNcws00p8ZvFH0efo1hbUmSIW/BAF2TxNju9mrKUEAjBZKK2eEoxha\n6KOvPCBV/0kxwYhgfZ0W+bjLQJ6j/GU1pRlEy6szFowR0uIWzE+KTV1EnwcF\nh6fI\r\n=TCda\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"731e6d58d5984331fda249cc5f290d9aaee452cd","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.29","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.42","@balena/jellyfish-logger":"^3.0.5","@balena/jellyfish-metrics":"^1.0.256","@balena/jellyfish-environment":"^4.1.34"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.160","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.28-renovate-internal-patch-731e6d58d5984331fda249cc5f290d9aaee452cd_1622979397097_0.6748637753104854","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.28":{"name":"@balena/jellyfish-core","version":"3.0.28","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.28","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8c97b715ca2c1db38983df77a6c535018850e424","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.28.tgz","fileCount":190,"integrity":"sha512-DApIppkh6RUJTIsWBLkDXg66PW7HO2bTzMmxiu746rqDLehaJX2BWGkW+FC0fg2HF0MriAp/4aS+hKrkcCaCkA==","signatures":[{"sig":"MEUCIQC0FJTLIXxSgC0yKnrrnY3MLbH9mhA2C7ZeGCXKoWo8JAIgUxm4mAYNC69b/WgIl/70e5q3bPvWSdLgGQNkW+CDlp0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718941,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgvLR8CRA9TVsSAnZWagAA1QIP/Ag82WF0GK82UuvXyzQy\nrZ4HdmLXPIuG0LIW5pQsXXPN7z/cpnADsA9rgdX8FjIeHEE0QAD7XknE6lRZ\nOsTtm4U0Xm71sr1p3bLdC7DE7U73Wiln+BnMX0EjqqsDje9rxT5f65h7SBAP\nmPn8lScBJKjo2piMvnD3GG4743Fc7FK6Gkws9x1zdM+X9g10shMb9nK3V9dV\naTSkew7z3fgL+84CgtuxFG588lAp/cMDepBOFcRWukRpBTQ8nkeZZHB/CAk/\nZmjLM6f4AufDCa9iBhEvSY0Cjyc246363GWHELU5UcYVPH180NJS59kuYEDj\n/z+9f/1ZmBG0dCpDNdCRsM0UrJcY5d9XcTnkYj6ZxXY2alepYIY0f1hcHlAT\nJB5Fw5nGGNEXduLyl8NW+vEHRYMWobMy/QQDKvJrRmwC+dI4grwgvb0Ky0Zt\nBxD4PtYJMijxTcNcoFfl3gHvnkdkvTDS4qJU1o6fKH++qVaYwYPhfFVcBJFN\nVuX7vATfvA29czdwPxz24bI9ffFvyj1TLfK151tZRFo2Jj5p/af5d3j/0+db\ndNZz50osdOE/J9OT9QH6Gr8FfXT+s2+9gZ5bQycqWMlpanIq1ODtDSd6mfAU\nTJf/TtR4OAFVGGO5vQQWtzSDEovgAJjFcKn1RBigJhdND8zlgUNU7GsLC6QW\nUQOy\r\n=gCyN\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"53f0ed3bef7d5d7acd2a26cb557b33b4aaf5b614","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.29","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.42","@balena/jellyfish-logger":"^3.0.5","@balena/jellyfish-metrics":"^1.0.256","@balena/jellyfish-environment":"^4.1.34"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.160","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.28_1622979708189_0.7474642878328437","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.29-renovate-major-external-major-c5eaebe5fc9c058f6239e69087bcdcfe8a6bb8f5":{"name":"@balena/jellyfish-core","version":"3.0.29-renovate-major-external-major-c5eaebe5fc9c058f6239e69087bcdcfe8a6bb8f5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.29-renovate-major-external-major-c5eaebe5fc9c058f6239e69087bcdcfe8a6bb8f5","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e8148d2808ab64c161473f4415d74ec96cdccea4","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.29-renovate-major-external-major-c5eaebe5fc9c058f6239e69087bcdcfe8a6bb8f5.tgz","fileCount":190,"integrity":"sha512-ZH/bRXWak/0agLJJ5nZQY1A8y6hyZUpRTi1gbox47lb3ynIU4JxCTARTG9miwwcIwu2jRgS9O1LApssPV6A3uA==","signatures":[{"sig":"MEQCIAndMzFA2fcOdQvpfH1VboEasYLG418CU+278piPlzrAAiAkCR2g27vly9ZvhKd40nwY0mBjVF6qb2SxWMLVhTf1ZA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":719083,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgvLV3CRA9TVsSAnZWagAA9TwP/Rd4Xqd4bgRIdRlXVpFD\nN1cu72ack9OIobeFlgxtsT6XVor3PZL8rrGHTFXosphbVgq8M9upxQ9GGS1s\nkqJ22QPo5mqdbZUs//iUMvAVC7Q93WvvOTl3V0JU1S5+yf3x5K/ANluu7xh8\n//gPdOM+O1sd4HrXyPzs1zYrfILr/xtuSf/Cd6kFbel5Bv5VQ6w+tXj1McC8\nf8AqnUz/YPGu6PJ+lwx6B8m3aM7Yt+ttgqV67tHEgsILJOlpSE/mZbTATU73\nw0D98va6cueu0iEUPET6MEN2pevD+DR5kXx+T6FrtEraa3CWZtnpmrP+rKsv\nE4wtY/Vbx6rvW+Q3kxH+2xFw0qbC/Sp7QW86k3jaiGTLG3l/QS58knLDwFAf\nvKaSjUpvOwviPixnjQDW/eMRQmKnVoEWeDpCC56HpT3dXDOxe9sM1AVg5ma0\nHLGvTqR93zrq1c9EtQ96Wqi7hyVl2EkV6XfQb2CFc5jiW6JU6Vv6qbUo9ini\nUag/GDmVnaDF5H3IQKsK/XezeydfhGwlsBxDVwEVZjnl5XK5BjlcYDpZuXem\n+XVQP5fEgj2pTlskPXCo/dpFGUd4yg47jrbPblSFieEP0c3qG/nW6zLDUuf9\nwu7Yfo0oq7E7D20l+uJLn9kWJZ+6HO9ybG13+XNHP4d92G5Vg6fV3EhkpNSc\nDQlG\r\n=UJOU\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"c5eaebe5fc9c058f6239e69087bcdcfe8a6bb8f5","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.29","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.42","@balena/jellyfish-logger":"^3.0.5","@balena/jellyfish-metrics":"^1.0.256","@balena/jellyfish-environment":"^4.1.34"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.4","deplint":"^1.1.3","ts-jest":"^27.0.2","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.160","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.6.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.29-renovate-major-external-major-c5eaebe5fc9c058f6239e69087bcdcfe8a6bb8f5_1622979959161_0.32663156595285625","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.29-add-version-tests-05eb29da65e6fe8161851ef927f2ea57a0276c96":{"name":"@balena/jellyfish-core","version":"3.0.29-add-version-tests-05eb29da65e6fe8161851ef927f2ea57a0276c96","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.29-add-version-tests-05eb29da65e6fe8161851ef927f2ea57a0276c96","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8d1ecbec7c5636c5cda11c46ae8ade29bd9483be","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.29-add-version-tests-05eb29da65e6fe8161851ef927f2ea57a0276c96.tgz","fileCount":190,"integrity":"sha512-IvbGPHB35mcUI/eyrhJ6lfv+1kz9zH4KXJlyysXIRXkXnFiJ8vmGOpGdOxpTU6hdj7BCNkj41UaWU6opaBnLFQ==","signatures":[{"sig":"MEUCIDA4zVmGpm741o77ojQ+A7R/U1Ej+GnuC+xDY/8i/zDFAiEAieWM3UKfRQd04TX3jxfDGPRNM75/ZTgPYa/5vVNROGM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":719078,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgvOeCCRA9TVsSAnZWagAAklAP/iz+MLqWuSvNqoPvqIRC\nnMauuDiiP+9YljlsYTengbK03j3jwSo06ow6ixZPgj46h0Xgk1WmqZaEGZ5z\nqPyrr6FMYK/j5eTWph6fagtds1gHKwy71jEQrQYb9wOG+ZfHHab/bw8nwr0c\nrv45B9ELaXhRGM08LUTWMZL1b7klfl+hwtio1aIQS2DUMLe48L3WrDB1O08J\nrrYHa4/tyiCwU5GXqCQESg11pwjVgBm9xHet77JbtVaJ5cpLXlcJFfqGgNYT\nLU11F6zN0IYXMAQK16/N//dItOgIJqY1nXhs2RVEFemC/c8BcmMRgjV36Rfk\neziRb8F8kPdU0FMH/wkyGWweuUTxGit6yTk8i4rNfZTp+J9voPt62QC+pp3W\n8YkTCYa0QH+sdVEVxzNYMFl/YocldFQX+wLv4FtcblIzQEyTFZlDecKNRlE4\n7rqOVEjHgXYqpnQRZjWMp8/2j1UaJe48j77l+TUqFCu11QD6rCT5et0HDRmg\nBQZu6D8FMN2qjZUVoxmWDHd2TsLmUa2u0xlfp/uKjQbMD+uPl//E4wYFCYVn\n39FnazuRUSCgneV0TBwCUa/JOMKE0wHbVZGzAcz8ttB1n9XH3Mbwd1JRY9X0\nCwxW7tOoI54/o7hARX5ysGudsK2v6RXFD+tFZ7uDI3AkXvlYEcO71EI6At+Q\nDZ0Z\r\n=RRT4\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"05eb29da65e6fe8161851ef927f2ea57a0276c96","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.29","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.42","@balena/jellyfish-logger":"^3.0.5","@balena/jellyfish-metrics":"^1.0.256","@balena/jellyfish-environment":"^4.1.34"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.160","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.29-add-version-tests-05eb29da65e6fe8161851ef927f2ea57a0276c96_1622992770533_0.20578744868034438","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.29-renovate-internal-patch-3a111985d106afd23ff3ca2715e9c3410e2765e9":{"name":"@balena/jellyfish-core","version":"3.0.29-renovate-internal-patch-3a111985d106afd23ff3ca2715e9c3410e2765e9","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.29-renovate-internal-patch-3a111985d106afd23ff3ca2715e9c3410e2765e9","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"39b63c3ac8b7c677266d1539bd05984a8b98a247","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.29-renovate-internal-patch-3a111985d106afd23ff3ca2715e9c3410e2765e9.tgz","fileCount":190,"integrity":"sha512-d5BSgrZFRT2nFACPGBey/HJ0pLGeWdJxAZo3Y8NEMaX7MMjroIlfJApFL9XtA2pGH5rJWPxzW1+xbJwSdKt7nQ==","signatures":[{"sig":"MEYCIQCS0YpbAD0+sQ5u6jb3CJJvtH/2F/pVC/bXAaLlQJHYiQIhAIoOXxrpfOWjNYMOQuOniOxXqKU3Kk2nASuLb2KyG/aA","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":719077,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgvQeoCRA9TVsSAnZWagAAEV0P/38Tc8Z5j7Xc/PrqD+3d\njeuP+jT41DPA6y9k+udjVLV8V2EDNssh3NkJuDO+PfqzOS2WAto+k0qWSZ9R\n/wVREdwTeosqVwf3TWDPdsModFCyKKJHNHLmgHcbhd/z+DG62tvC5tAtCmKV\nPW0h200FnguDn/LbcrsIr/BVEaEOv7Dg08n7dwHgsGEfKPl8qMY4uc/1p2U2\niQafdt58ROzoJpBdqp+gbKMhZWbusst18nVq4INcvfKe5gtcGhJAhmyY56l2\nE3nk9hjSDe8mLI/y7r55O5UykDe5sQg5CIp1yCfZqBg4yfQwMqG8ZcaX8JMn\n7fJ3cTLlu91Q4NTZC2l7naFsqi/dIhVRWJ0U4kXdvrOv5oO5B8LuVppB3Ryf\nptUrPgvEKHDmpwbm4V2EZSWmo9kgl4QFFtQgw4334NhL9CODbhQ/LiSAYG1b\ns3ZhMhnwXct3QcWDRu8lE/OfCoXRhjmJuGYARTU4yely9GBnOzRik6ueRAne\nXqzuKF2IrDy/6+zK5C3GR9p8PKWgK5XJrkKV6f8neCnuNeiarWtpwRIsOpyb\nH6+bRd7vxT7+zaWeAbi3EezYTQu5zniPxtn+8AuKR6Qz8a/CC5XxSZs05flP\ny4wrWD5AeZQ8YafTEPD0+0X4Q89fNWgCif04NTmTsNTdhxZSxRm2t4A+5fTn\nc5Qg\r\n=wKMz\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"3a111985d106afd23ff3ca2715e9c3410e2765e9","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.29","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.42","@balena/jellyfish-logger":"^3.0.6","@balena/jellyfish-metrics":"^1.0.256","@balena/jellyfish-environment":"^4.1.35"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.160","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.29-renovate-internal-patch-3a111985d106afd23ff3ca2715e9c3410e2765e9_1623001000637_0.23665546937804272","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.29":{"name":"@balena/jellyfish-core","version":"3.0.29","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.29","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"be7bee0c9b23924335d226bb9f837d68f33d1835","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.29.tgz","fileCount":190,"integrity":"sha512-vwe2nCXgNXxmLryNY/LP84dgoUjsnaSr8SO2ebuz2f4JjDMPVABapOKY/PxEwoEa+08h/GSbsV1ibPCJc4PeoA==","signatures":[{"sig":"MEQCIE9xV6lcjzXWfoBFN91/7XPxJayWzAfT0FFlvnMPCRcaAiAzdh/rkDC9Yq6N6SLgbBVyi/RHV60TOpai86V7s1QnxA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":719012,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgvQlbCRA9TVsSAnZWagAAXaEQAIAD5ptjxeVBbEx8Ofzz\nPGNcGWKmC6zvYJgd7Tn7bYABIUxGb8b45uKe4XQUaWFAp7UlnJ85saDQ8dlZ\njCoituUaKiLuwsa1Vbh1KQd2Me61M1iRaXQM7MC+dwAwFjOS5FEz3McHSFzv\n4q9k30icMP3GirKj+S/JV0zI5OGh1a8ttnwHqOU2XelbVlToKBhlZBV2gDyI\nnGpo+pvoVKxJ5uwW8MgTtASaPkWEgHMs++OKnGb0tWX8u+Qf3IpZt+SBdu70\nrB+gLrhmm45JJgUPWUKjdc75QYBAXkBgksamNtf5o+WXgqIw8cHheRIO89Zt\nEbYwLd1icDrqKhzSOUsa+jfNWucrzC9tzb8m6uxBhbYmhSjMx2O6je59eS2Y\nJYWZXhBV5hMjHkJPEjx9milMdX7RC6K+V13XziUKM2LBp2F1VCbFh315jwjx\nibOaL16xV7Rr0ifyHXG1PI4VB6vzsQeHFiZwmN5Gn4u7jVRt+FDpLCyf4GrY\nIhH5/lWOtceoWloRfmIouxzBAc3oD5Mv7BlOO4X7NXEz1NPuawR712s0CTt7\nZgvMNM0c36Zrz/sKrbr3BpgQpb8rtISxzp8Qv/PXWT04P/ZFVMYhYc6b1Afa\nN7yYgmvXbmLqVe8R/qfSiLKogTpDVN2/I1k0avnv6xnGDT60CnBXYE/b+3S6\nGkMv\r\n=QqBF\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"89dc0fb450ca8d31b98eb5232e8b1c28c39607ff","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.29","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.42","@balena/jellyfish-logger":"^3.0.6","@balena/jellyfish-metrics":"^1.0.256","@balena/jellyfish-environment":"^4.1.35"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.160","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.29_1623001435045_0.8229522126238531","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.30-renovate-major-external-major-df906c3f6ba63ce2963e0b8737a07591a0a8cb4f":{"name":"@balena/jellyfish-core","version":"3.0.30-renovate-major-external-major-df906c3f6ba63ce2963e0b8737a07591a0a8cb4f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.30-renovate-major-external-major-df906c3f6ba63ce2963e0b8737a07591a0a8cb4f","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3c4fa8a8297b298d128caef8e5bc25725d8822e4","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.30-renovate-major-external-major-df906c3f6ba63ce2963e0b8737a07591a0a8cb4f.tgz","fileCount":190,"integrity":"sha512-TJqwAIIJOUWvxNvgDOKIhSq9prnahmkZRTW2EYfMpmykK9izXYT6fGxoFjucuEHzaFG+QIofjObKi1gfaMe4Og==","signatures":[{"sig":"MEYCIQDGnX4VweFmbHNfXdkmJYsdwC2p6MtqBi7fBZgRxRioVgIhAMh/+c4Ie2a05V5H2XT9Pex5p5cj0DfoM7RvwEah3Nkm","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":719154,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgvQp/CRA9TVsSAnZWagAAMWwP/0kwBFx6fdsM88Eo8QO5\nS/01+NIuCyKlUs3dU2AXtMYny/havfT/rA/H3vXdZmAqBm6wHG1xs+yK8KYJ\npEhkbJsIKp3NF+Oa141Jp1ug62dMDgY9nKSeFVdGBrmfqB5lqq51Ilu2ptMW\nBbKn+kPbLvRCWDuqE4i4yMKypouYjOd0UO3iG4vsGN4wYeqjkt2umvXkU3CI\njyHS1N63fDUWMmHi8VvXn73zZq9yniAi+JOXP46BCWqWntn2q1OO21lhjOF2\nk3gS6k+6zG1dZ6vJD3dTHi4c1VidjsKTUj5nReUgRIfaAZ1T9KffmhScB5O1\nxJSXNCorptBFuhnJ9gCX3wXJ2TwgoDlNttpXerIDHf3nZk6AsBYxH1U6tT2R\nh+mgbmLicWJlvKnn+XpBed53MkuKr1nXVRgg/f0MFbcVRSsPKhHxcpxHWCxe\nedwmEN7XyHeMSjyQ8JWeLkWhEptsesim6bHexwpFWPPbp9JdQUuNlboxZa70\nUz9CZ4W1D8XOzvhjV7JNYQJKowv/y4sJ58UjWTZvmIgqY4GHuQcmvhnIWXI7\nK5ebCynwADGUVZ6TycnMspcPok1qg3jK7k1+iWqw7HJhF3kSNQkpKlj/XzDb\nhz/a3ZF5x83dnDNdvSTbCf+LF/P4TFPpr2xcodCuConb0R/UPj8mh5Haon+X\nHBQG\r\n=jPeo\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"df906c3f6ba63ce2963e0b8737a07591a0a8cb4f","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.29","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.42","@balena/jellyfish-logger":"^3.0.6","@balena/jellyfish-metrics":"^1.0.256","@balena/jellyfish-environment":"^4.1.35"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.4","deplint":"^1.1.3","ts-jest":"^27.0.3","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.160","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.6.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.30-renovate-major-external-major-df906c3f6ba63ce2963e0b8737a07591a0a8cb4f_1623001727049_0.8269373980907602","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.30-renovate-internal-patch-138e15f7c0dac3f033c30c77d167e48afb5a535c":{"name":"@balena/jellyfish-core","version":"3.0.30-renovate-internal-patch-138e15f7c0dac3f033c30c77d167e48afb5a535c","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.30-renovate-internal-patch-138e15f7c0dac3f033c30c77d167e48afb5a535c","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"253b5764b64d4197b531f8034c170e31257ee995","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.30-renovate-internal-patch-138e15f7c0dac3f033c30c77d167e48afb5a535c.tgz","fileCount":190,"integrity":"sha512-kprG0rcP5B08FLukx9WOU0118Yrlo1yLgffbG3bl53KCGD5Ef0voMTsfk9REOe/yGwpBoAnrdN2hp4aZ1d7q6A==","signatures":[{"sig":"MEYCIQDBv1yKwCyt3qglhdMU20PwekQMZgHdYtw1KuIMNEBbYQIhAOUr4jD1JD+1ZRoDhCYgcT4F74YJM4+rr2plyFZh4V3F","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":719180,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgvQsJCRA9TVsSAnZWagAAqjgP/id58IYXhm+R4ZLG4yK8\nKnMRh6upE5DZobvEKvpkx2Bt+lQf8zNYZfWGHRW8wY+yRJFG6P8HZZn7Swta\nEnc+VMd+bPFdTlN4IjfVolcyXSfYeprlIgjGQZ4tHqTIOAeB6ALNMU2m7maK\ncvCwBrBAnS7NquX0qJ5BBfpxf9KLJIyoqzm/gxgHFLvF0V0paUNoA1t2jzHx\nMuP++hQUnZ0j7N6vRDFgO87OjC1V5CJLinHvpxMUYqdsJwgrj92d3wqx2qoA\nFbvfvIRWbS3wCy7UmlEMrbZnRaIZxPU/WsJIwIV+MRpRcvphvdGaweHCg/j2\n3/2KX6tpDMjShi9SUbnHFILjUNJsuJ+z/McTFjHNMt4qJPnA2pVAQAU+zqW7\nVRqNHiKsc+2pSfv2fqhkepLvikkFQkTOklP09CS4nwJ23QtpXOE1UB6erY9d\n8sfNKxvmAHEOcUzTTCZSJadn1l49MW7an8t8gLEJL4VotUjjgocx2iqWfOn1\nGmaWFs1vhzDWVkf3t2wuO71UtYgLtejk22DFQarhM5NU0SIrCs2w0Qt0bwcL\nstyjEKb7KZNlrzUUKJWZGOTJnCXtyzlBtq1GWN7i1Xe0k83g9ZDflg0w0h+V\n90VP673px4IO/NnsUKOQP08AbL4Vsg5JBSxNZgY+DfqE9wy2Da9O88cm4O6w\n6zQb\r\n=seTc\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"138e15f7c0dac3f033c30c77d167e48afb5a535c","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.29","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.43","@balena/jellyfish-logger":"^3.0.6","@balena/jellyfish-metrics":"^1.0.256","@balena/jellyfish-environment":"^4.1.35"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.160","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.30-renovate-internal-patch-138e15f7c0dac3f033c30c77d167e48afb5a535c_1623001865771_0.7891226583293576","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.30":{"name":"@balena/jellyfish-core","version":"3.0.30","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.30","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"fc5bfe2a45fbebdca5ce3cbcbcd03f36dc8cb785","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.30.tgz","fileCount":190,"integrity":"sha512-nv6JyjS47TQtQ9Vn36NJfZq10vr+otDP2UORsftvq1HEtjOA6of+0r7JjUWlT4/DAPyE7u3u9jRlEaye0VAizw==","signatures":[{"sig":"MEYCIQD4WyOj2YpDktrdHUOvtZqICL5Xm1Fm7n9TDPkdTDAvbAIhANsHndAJag8qjjR2jPdscthj5j6bqOyuy8HSnyZaD59l","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":719115,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgvQwKCRA9TVsSAnZWagAArVgQAIcYsINaZBrXHdjcA1mB\nwlEPTul5XMlvXxvvdKEtIQDPAgjEIn9tIlrvbTjVxYphx6UAi7ZNGogHWQBm\nBYJzhk8NNC53YBl6uqWUys51RwaDAK+nGEjpxaCOEiyQ4+gT/9idVhd5CJ3C\np3X56s8hsYgSGraqIzn8q1uK25A32pRbpF0dXI7W0FMaV6+lRT5ma4zmPXvf\neXAzczPt3X8bA79tohDUcZngPneUbMD1cU/6D01TE/3MqLpSMIzxVXZQjJDh\nYPSYpjgpfvGfpDhxiMFi4pjfBq5Fiy0+d89P8M42P0JobqpC6yAdvpk8D00G\n+x3oR/Zgr/nFK1fPkwIQ1THR0NR7gFNhni1m4/Z9LbrqaV9pIZiXP4mBR1z5\nOMOzP5qV2gBiW/jR/4WYOOW5N/efMTtQqzeRUIKNzvhPqmjQCtscmoRmDlj/\nMtP73e+AFN4Dje0g34Q4ItJvS669HnPT/g2un059Yze6mdGSRpnEX/TZRrwf\nOlqfajEvXX9GFdP+HKbEEimjDcoQJdzj0JElVmwUnE/EZ8iA9GQcr43GbIi5\nFB5YuQ4AyAK9rwqphH2Ud4CjPupJtzdNLTQ94EUK4cZ9iDxyEsWfN7uXqDp6\nzp/HE4+Y+FndXvPtlZG/VUSW7ESCphWnuABI07nNpnF8rex22rWUd6N6xKPm\nadMx\r\n=xjXu\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"5d1a69c903d295c25eeb0a26d92326465e3b329f","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.29","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.43","@balena/jellyfish-logger":"^3.0.6","@balena/jellyfish-metrics":"^1.0.256","@balena/jellyfish-environment":"^4.1.35"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.160","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.30_1623002122370_0.48834562218505595","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.31-renovate-major-external-major-4b2580dea25def2bd1db2c49d77afd4c6062832f":{"name":"@balena/jellyfish-core","version":"3.0.31-renovate-major-external-major-4b2580dea25def2bd1db2c49d77afd4c6062832f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.31-renovate-major-external-major-4b2580dea25def2bd1db2c49d77afd4c6062832f","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f47c15793ed2cbb731f07488988aec21b6beafa2","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.31-renovate-major-external-major-4b2580dea25def2bd1db2c49d77afd4c6062832f.tgz","fileCount":190,"integrity":"sha512-TXkFF2iI4A1I5BnMd1fcnSZvaMTMdlteah2kc/7sz1mFNpq3PUic/7uxlBW/faaPvO4yXU1Cv3aCyybDMkIDog==","signatures":[{"sig":"MEYCIQCXY1N1paCnI5DKqKgS3U908TTLLfepD9Pv6UgppGdk8wIhAM164E0SP8IwyxUMhblWmM+nsGVsx9+vzhU/7wf5DC0U","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":719257,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgvQ1GCRA9TVsSAnZWagAAJAcP/ighAXLR3EEWHU7HWjQ1\ntslvUoRq6IM18rmXh4IszV9E3QwdS1zzJgOkjuzgNIZy/Bnv93RD8BVaIPbl\nRBQ+UxuwXTPA30YtiSrn8ezVpCVx2iSjKgrRJaWP5ZzmbMBq2Z2QUJvVy6fN\nv7jnZJvuW/ECFbw2pUCdO3Z5F3JWmqKsIQnyp/vyoMEXbhY+KWfh2CbOy1GN\nkJCbx1HOXf2IywbCIR4ALz4cebh10diljMDkSu1XgRxuTf1Q2j8RceCCc8Y0\nkgpdsk6MVm+pFLjRRtnALWBvXLxx3ZnQ+DEiSdhKzXsqVS5eLJWBl8IixdOt\nycxg2LrGfHf7SZHl7FQRoBXXOWapaUxf8hWNyZfS+LWFDEFQJit8W8W3m6fn\n6HjqFvQGJ/93mhBfpEGhsbU7SQ0S0eK632WZr3Hcms11ogz8Udp7dcGaYZpr\nsVp6vKqHxLePEVCZ/1H7Dn+6T7wqL9JdbNDsLpOgsaz8zaeNoelukSDaw5Ci\nfQoRr1Rd4lLF4Fd1aGi+oIrBdy7qytwPrMQmuheGM303+4TPr24Nbv4koWKp\nYtvG0ea2+U5bhytB4cZOvetKeqpdNOt2zp/pmK27rcsSVnTbpd7HYtN+UJcc\nEQSwyI09QkBYfPy4JySb6MhF4hhGawoN37nh7g22jdCCnvQ0aYQhk0B2gBfz\noe8P\r\n=ZfVp\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"4b2580dea25def2bd1db2c49d77afd4c6062832f","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.29","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.43","@balena/jellyfish-logger":"^3.0.6","@balena/jellyfish-metrics":"^1.0.256","@balena/jellyfish-environment":"^4.1.35"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.4","deplint":"^1.1.3","ts-jest":"^27.0.3","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.160","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.6.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.31-renovate-major-external-major-4b2580dea25def2bd1db2c49d77afd4c6062832f_1623002438692_0.8691953431686976","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.31-renovate-internal-patch-e7eff6db2a65748449d72cb84ce60e58aeaaa7cd":{"name":"@balena/jellyfish-core","version":"3.0.31-renovate-internal-patch-e7eff6db2a65748449d72cb84ce60e58aeaaa7cd","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.31-renovate-internal-patch-e7eff6db2a65748449d72cb84ce60e58aeaaa7cd","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"635f9f35b04e5dc06fe4a7d56b2185e1a934e396","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.31-renovate-internal-patch-e7eff6db2a65748449d72cb84ce60e58aeaaa7cd.tgz","fileCount":190,"integrity":"sha512-Wx582w1CsQEpgk8ShuUmwWxySfEQip//+xkjgnzgjrJ26cmR4Ds03xU+eYiX4MCgW0wJAIhFj8EQcX8GKLpOEw==","signatures":[{"sig":"MEUCIBtNCeIJWAi1mA8oKgayIrG0T//RYWRBvbJDMvnhG55AAiEA4pTzGe/Mrt5spjk5V+lNEpQiB8cNS+q0tK4aizVPysQ=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":719285,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgvRJvCRA9TVsSAnZWagAAUCcQAJDZ1UTNJ9P329S6mxNp\n2KE9qxu3UBZblkkELqvS5tHN4V8JVMGoHRvzJuGD1AKQWbY96U+k+XcHJyq9\n6Cb8xa3OLjHH2eOv01sC9Qu6vjlHWGL1H+M/nR7DO//C1I5X+3snVkndrL3b\nUi4QxkXsOnNWL1DAhW6VNbchrU3u13xDO7P9YakgkInuMwIlQ8JF3kG9FEf9\n14Mn+Ci/5EDiHrB3Xds3/iIFfH87u1s7SfPciEyaV7c6mAgq/Nu2k2cbPM6t\napisxK6QigTTN/l7KAxw3Cz8mi8kAwT0StyROz+R8ZEN0FGH8kRjBJwXJa3r\nHtiY1B44tfwA7xPDwGa+JEi6ttLZSYzN4TTz5VxgAbvQ51hT8qlVFn2d2u5z\nfD3PcxAT+ev/8JEvoV09H2PUk1pEuS/iPFo84zPfjjeLNJARp4oQzVjlmAIr\njjjU1TNzFz5QGD1holKqW/IMCTtNU3Mop+sOeRQ5SKtF63xl9KGOyRGteUF4\n7a8Xtzl4+sjHRwVXfTNDQdS7F+NvfjhPwJf4bqQA3f5SwRrw3z/NkJXvEFxM\ntrQ3Z/r33/kiqe/Wolzhg0JslzJWk1XwCfp5aQFCc/2N4SU07iGVJLt9M6/B\nwvFvSoDs4VnEh/u+uwY1R6+ytd87fi7SLruTFzKw/8mvqUmKW1TXKghaKAVY\nH0gh\r\n=YJP7\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"e7eff6db2a65748449d72cb84ce60e58aeaaa7cd","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.29","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.43","@balena/jellyfish-logger":"^3.0.6","@balena/jellyfish-metrics":"^1.0.257","@balena/jellyfish-environment":"^4.1.35"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.160","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.31-renovate-internal-patch-e7eff6db2a65748449d72cb84ce60e58aeaaa7cd_1623003759382_0.7530315042674274","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.31-renovate-internal-patch-a2ec4246c9476dc27c1526f2afe248722359edd3":{"name":"@balena/jellyfish-core","version":"3.0.31-renovate-internal-patch-a2ec4246c9476dc27c1526f2afe248722359edd3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.31-renovate-internal-patch-a2ec4246c9476dc27c1526f2afe248722359edd3","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a0e6744b4f8b71e9152407532edfd13b6eda6bda","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.31-renovate-internal-patch-a2ec4246c9476dc27c1526f2afe248722359edd3.tgz","fileCount":190,"integrity":"sha512-jNRhWGRhj59MgrgwZ7WWC9MGw1BCryvN0gdIT3gTTDnv+pTyOnhsikLXVZwGJEPGK/Olum04bXS5hIatN/YwzQ==","signatures":[{"sig":"MEQCIBr/RMrYw8bMvZ9/tFhSMXYtF4d70vFTFvbJAIoYrILtAiBVEafMxJCm9sk4+JLSOEzEaEXWmfykCcL0Er1NU+TzAA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":719251,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgvRV5CRA9TVsSAnZWagAA7qIP/REjSDdQDPeNdhurbOI4\nLAVY3Ik7jQmJtnQQ/00Ydvg/KYSLztnNCiIPAbp0g+hqDl80I7WJ/XUWJq45\n79mhuO+W6knyCDkUdis1fqYTvDWPjFuToAEJV/J8GeYJUND05XjtWaEwPD0d\nYAWMR2pxPStt+x4puVWOXEnDwznSG1bMA55JTN4X7mw3zmRbAzommGJxQyMm\ni7O28kjCAozuSJ7x6RmpJPrnzZhCysr8Z7dD9trP0sWLvdBG9lgpenfcA+qn\nVkelnlquysbb4beN/dPBG7Yc+8srota1TplRX2ECO+37HwR/22emYmlAie73\ngKV5tTOkMdONTfvRWtLIEcKCs13aPk6aBV9Bj0LvBb0SwXlAZo0PwAgXM6cK\neJ/TqMM8ZnEoz8Rrtr+A7Be8/LX/OxwAO90diQZDtBTTLW8D7lNQ3MG1Wn18\niZEhBiONRfyISdmFZs6u2ge50fIBr5D2fkGiTRmlkHl1X+kFhke+nMT2sm0o\nV3PCW4BC6F+nECk05vu1U+bRQ9RUwjz1ALCLeI5zNzgE9Bk5eFNn/Ftt3+Bv\ncyvS5lzRwDNdjwH9w1azlqX9IjrQpJ5KqsyFB91yT6TAtq/ru4SZM45d6C1t\nd0wj0+3KIR6F2kU2HPralOtDKYI7/HtzFcQPLvwa+E5QSZbx/wm7oUzAy1xK\n6dKB\r\n=7a5A\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"a2ec4246c9476dc27c1526f2afe248722359edd3","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.29","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.43","@balena/jellyfish-logger":"^3.0.7","@balena/jellyfish-metrics":"^1.0.258","@balena/jellyfish-environment":"^4.1.35"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.160","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.31-renovate-internal-patch-a2ec4246c9476dc27c1526f2afe248722359edd3_1623004537254_0.9105681274752768","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.31":{"name":"@balena/jellyfish-core","version":"3.0.31","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.31","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"7b42d7a4f5c5419a4db8816c603eb89dd7431bf6","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.31.tgz","fileCount":190,"integrity":"sha512-8KakgZzthf++S4l2GeR1XFTRzpJjYyZUaWa43QkG0Bz6rIBT0/vk8DkFClb6ujWdkifSOxUGPBHeNPP+NaD7Vw==","signatures":[{"sig":"MEYCIQD1vyzlA243rUXrzd/UxPcyvzgRVvK2ie1QCah0UqmX3wIhAIOlOquKd33vipiIdjSYyfpEr3laPn98TIcN15cXCS55","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":719186,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgvRbcCRA9TVsSAnZWagAAQzAP/RIlmhs4yyACDrOjjr3e\nNPg/g/bRG0v7BvNZKaTrZMisZKA7HICI8Q4KbecxV5rZai40FAXCbqigUxuY\nQ9ZCC2VUJd2EYf6MQBBD2Bw2yC2snFD2fjGTHZX0rn7U3wLzjupdbNs8hxR9\nT9ykQSn/+R5Gi3TDwrSvX7OtMGrEV1wUMeshsbCwDP/rITNXMZw8qXMi12d9\nATNU5To7AWkNH+a0AlC0Qo4scoROGOaE23bKqJT1NuDT/Vc/dje280LDbFM5\nmXhrv49+aM6zqD4LkWZ6ZlJpdkyxW8MlfeNoaFq95cdXDhACvEYGmjyom3AS\nnSwczXl2XGOAwX0Dpu02txAfAtkmoEuP5TVj4mzysWekK+zZYCwUmDS8uvIY\nbUx69VqEs40aB2+2vxEmlWpOV6UTAMeWbxkHDn5/tr+DXjdS1GReJJLC0FQ5\nyMcwphPz2q9pnFPHBwZSDh5fgFlJ1GBt61dnJk8656O9U0F09osDfny/mCCB\n8mF6Cc0VSbNIcbq/2J5nJledIjT7mxqr+1BQNiTwBLF2cI1ERcZBLdmS8C19\nHbv1+ltmv8nlG8eAwU7/Pv18HuNoZUJS/VxMLtX3h2VMr226hbVf//cKZcI8\nqKE/FBaAexAwcPEiAF2C6rs3rfLizr/Khi1vA6EyDgG6wge0Ljo72fuMmT5k\nct0G\r\n=H2u/\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"fcee16629e6db5b8ba2be500404a95e26097e7a7","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.29","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.43","@balena/jellyfish-logger":"^3.0.7","@balena/jellyfish-metrics":"^1.0.258","@balena/jellyfish-environment":"^4.1.35"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.160","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.31_1623004892501_0.36598693488536904","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.32-renovate-major-external-major-d689e49010d9a915502396a519fbed923cf44209":{"name":"@balena/jellyfish-core","version":"3.0.32-renovate-major-external-major-d689e49010d9a915502396a519fbed923cf44209","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.32-renovate-major-external-major-d689e49010d9a915502396a519fbed923cf44209","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"012ba88e6270272e8fcfae36cb769b514c9ebeee","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.32-renovate-major-external-major-d689e49010d9a915502396a519fbed923cf44209.tgz","fileCount":190,"integrity":"sha512-0JwXoj+zd9lxl1vkIDemRCLd3yM07OHJR9nbVUJx0KcqO2jT/OHAsKtOW6neygw3r76B2GOwMeHJTIBjK/OESA==","signatures":[{"sig":"MEUCICGGkznI0oYO6qz4j7NBegpEXuzZ7QKqm9+5A/N30HVgAiEAzFw+yQwrc0bl9ukZOyoW5aeQNy+ISam61xEKhXtgcN8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":719328,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgvRe+CRA9TVsSAnZWagAAiSIP/3Kb6a8fHytDvQV8Sf/x\nGOyd4NborHhmFfeliGJ74Gx+yiNgCrzzrmpKyPY41DgXeSixpXgH3H/VMrqN\nFQAJQb/jPJFZ3A+t9rqVRzueU0em7WVsYD+05hgWkBP4/aGPuPUI6Rc3dqvS\n2wT2Dz1MSWpV8m5b/LzamLnBuioVf2C/ZT5WuVmDhdsb8TwEkRmN9aS1EKQS\nnbl22JWi8O9AAyzgWMtEgHgXe9kJWOHJiCNrumk6FOYzjMSctXIo5tA4FXaR\nPbhuC/XguHi+XUSUdRytDbeBflhUMnpIdcJ7IiVFFwNBD/gDrIOqnmvdUlfX\n0j94ZBz77WbiExzWt8ASneUoR9Sul8fQYq1S81Z/xXTB7IGCHO6U2mUK132I\n/vYuf6g+Q+u2mFEq90oMoAgcerAyBUPKx1HKnuWUT3fk2NIqgDABSlTD3jEg\ne0N1zJknIkozcZHhfeil/Hv+/PIewdG261O23PC9GmE4jnrBp/TCapmnDbzM\ncHV0aSI23V69dHrF5rppLNPaiS4/Ec2hyAYI3HuUB2gSbC3Lvr6yioQ2WaIj\nF8AjFy9uNgOp/zAVnJCz7t3wov8Ues5+wtMLquJ6XLT24bqFCbT8opYQyzMb\n0/tJFAtgt0H2wHG9Y1cs2xUPuAjm0M4rNVKFCxmQOZH1IOdL/7uhwxTu9UN8\nVL2t\r\n=vId4\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"d689e49010d9a915502396a519fbed923cf44209","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.29","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.43","@balena/jellyfish-logger":"^3.0.7","@balena/jellyfish-metrics":"^1.0.258","@balena/jellyfish-environment":"^4.1.35"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.4","deplint":"^1.1.3","ts-jest":"^27.0.3","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.160","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.6.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.32-renovate-major-external-major-d689e49010d9a915502396a519fbed923cf44209_1623005118133_0.3619438564682522","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.32-renovate-internal-patch-59bd4ed91cba1703c10023ca8444a0e4a54f1392":{"name":"@balena/jellyfish-core","version":"3.0.32-renovate-internal-patch-59bd4ed91cba1703c10023ca8444a0e4a54f1392","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.32-renovate-internal-patch-59bd4ed91cba1703c10023ca8444a0e4a54f1392","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8ddc7afcba9c225d02e45dde57c8a859dcf57306","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.32-renovate-internal-patch-59bd4ed91cba1703c10023ca8444a0e4a54f1392.tgz","fileCount":190,"integrity":"sha512-AENsTRmlWHsW3k3e1VRGfW/UUNTwxnO/SBWlaa6+9z5hEWu+jmuVctxi7arG/Q02YdqzFJGEnvPIrDpMbl4ovg==","signatures":[{"sig":"MEYCIQD8tk7uBPzNgHjnyj8Oor+nHZy+sxoO4I2hTD9ixwN8gAIhAItdSxU+lDytR3j1DApQZRZTKIPWrxi3WgjwVZvgkv44","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":719356,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgvXHYCRA9TVsSAnZWagAAJfoP/RrOOR/dOC2D9JkX4NAX\nrbsfPZItbir5sL971V81e78PsS8MdegvojFuLMjEY6xEa9gIjlb2WZbj8WTu\nb1RiSUn+kX/LE6151wBhAAsaFNpm59cZ/x2EMKX/xWDDj7UCXJoDhonbcfg3\nKt8Pdx3S0CyD9ZV/ci+up67RZDZ+sjCkl/dj350xZa72hTLz2xE0kMoOLOqZ\ntIskDAB+3QuWsM7AS42TtHakjKDsxoDtav6fYZhaIq5EYo/TmbhtiNvw9kU7\nuq25M1dmhYW5FzGFXTgjQAToSqOKNb8aNgWNYrnFq0Q13Ew+cMKxbPUnyjpf\nU63Y/2KW11crhLNbDDu72+h+sEpDskVTtErc9fDrKIL+ebt/KFFUPj24gB/i\nCMWxDFwwSa5tFoLOc0Bk5KmysstZ7TFSi1cdQabsBXY7SevQT35SJuVa9p5h\nd4j5BJKSzqFB1qHbdCyYX4fisk32dIMCu02jD913hjppKTptQNvV4rH/gYI+\naFFdYFXYHoV2vA68YkwnrKgw8my0Vat4YCWzXDynUUjHRRffeoRhxkV1akbU\nNEq1sKzRKB1T+FvPhMl8RnI5Reh9WG3LabtQVX5HNpA1+Wy1r2S3lU3fRzwE\nxHo1cSWKyF9CkVsmP813Q7Bzx/CYTzX1LcIOa8rCcqr2d8n/6wXxoUfLU5QW\nTyxz\r\n=JXpC\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"59bd4ed91cba1703c10023ca8444a0e4a54f1392","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.29","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.43","@balena/jellyfish-logger":"^3.0.7","@balena/jellyfish-metrics":"^1.0.259","@balena/jellyfish-environment":"^4.1.35"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.160","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.32-renovate-internal-patch-59bd4ed91cba1703c10023ca8444a0e4a54f1392_1623028184066_0.3511315219923159","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.29-add-version-tests-66bb05fdaa469c75c2993db4247c5f73afefa6a8":{"name":"@balena/jellyfish-core","version":"3.0.29-add-version-tests-66bb05fdaa469c75c2993db4247c5f73afefa6a8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.29-add-version-tests-66bb05fdaa469c75c2993db4247c5f73afefa6a8","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4d0668ce32144b23b3361f876c3aebc3ad4b8271","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.29-add-version-tests-66bb05fdaa469c75c2993db4247c5f73afefa6a8.tgz","fileCount":190,"integrity":"sha512-+9xBNAuIw2OZmp+dXnZ4tW4QH5kSdNE+e/slpCPWkRFWAR0tK05xJxVPXh1nYISgvxAR70D2Id+xgbA0XAUROg==","signatures":[{"sig":"MEUCIEBHcOlySLZAGmYEJUVq11M1DDo2jG3fkmxFtoXHlAbfAiEA4WPKeb8eCcxVFbZKqg8NRTBF6U3jOnYJtG9vkMba5X0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":719065,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgvXKxCRA9TVsSAnZWagAAdvcP/j8QRdUp+mSAEUdvWtiC\nFl/Ykmp0IkZgJAk4epUGqVhTNZBittx+rlCQGqd6C1ramGZpPvwEUf1pDgx0\nABWSy6ylg/2C+6vGi76fn7vjwjh/72mDLsp6XFBOb6TBRaZx1D+oOizfHZvo\nWa3CZ1B+nj61F3YrcJR+WTZMagg7m2Zng1D75V1hfyz1rdsseuE5tO49brBr\nkSm0appI+4Rz7oinZ7QdQkDqihNentHCJ/XYevLb8EROm2w52ZO0chFq0Wtp\nbPoftVMeLbipYcL+/byS7GFFbpmSicJtlA7wWSkJksJcsmD3YlEk4Ggi0KW/\nMPhsYP2V0MTc/6CpPvz1x4M6vxmLeeI2u9mSRpRIDEg4QzovHBWwtMMk4vg3\nJT7MJbUr3E6HjNPr069McQ1Bvcqkk4mzmiKKfbXBSqOCRQyMKdwausIAVdVz\n48MY127Cv2wHNmnojXI/aZpSh3L1/EddkYdYtyaBNEVhyd5tRdLUYCGG9Ilu\nQilw1gtt5g9Lp9T6V+bEH1cPQQRvhEWPexe+0rqiW6N6X2ZHz5AsMO3G9GJM\nIdbfQkHDXYzOQO/dIZflJq+hiiytC9+MQn1g/beObHDvBiJBExGZwZBQN1oD\n4pFJWGRp2A6WLiLCb7bjDkEIk+oqgr1b0zKDE22x/v5vHQ5Srr3faP8o1UVN\nqI7n\r\n=KnbR\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"66bb05fdaa469c75c2993db4247c5f73afefa6a8","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.29","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.42","@balena/jellyfish-logger":"^3.0.5","@balena/jellyfish-metrics":"^1.0.256","@balena/jellyfish-environment":"^4.1.34"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.160","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.29-add-version-tests-66bb05fdaa469c75c2993db4247c5f73afefa6a8_1623028400928_0.8828337099568533","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.32":{"name":"@balena/jellyfish-core","version":"3.0.32","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.32","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8e918cdf37c2539fbe3eb306720f90840c5ca0ec","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.32.tgz","fileCount":190,"integrity":"sha512-N8bHgFvKM1j/S1JULrtohShYgFatPE2LAyhLFlGwJh4tVZPv82XB1WQ6WE7jHBIfg4KvL82fHrjZgYRL+YReXg==","signatures":[{"sig":"MEUCIQDXoeHJktugxd9fVOlrVMXF0uPc416qfULFKaVh871hoQIgf3xmii0QbjsteUVsc4AWByxrxF4EgtIQB9zkN11UL4A=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":719291,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgvXL1CRA9TVsSAnZWagAArP8QAJJwJqWFtw96U9taBe7m\n+5kkisligl/YtJArJM5gPfTxNQ+SxhLVg92feaxZ9LOOpWOBQAFTJn8fChS2\nWD1R13zbQ55uoVSVRfmd0euXjPNQeADnsDY3dLEPfc3L2dgKXZr8UgE2t3Zs\nV3Lgjwq+i39WhEb2X6d+10TUZEcmuHEZ3c77CexlGdy15MrDbgVD8NBE4+nt\nrSGWcSvLFmhvQjymxdE1H/meb2uIGSgORRkXc/BYNXQyraTiMHISUo47i1Mu\nbDmi8hgFmMzIOtu4NMYXaC9jOY5EyVsdDg7VWgINpSbu9Kvb/rAVpd5cjtHV\nBODt2C+PsMtOo+VTTBQN7VtnlnLWrpG8zT1MvprGYBW7u4mvjcq6wbdF7Tcx\nPJVamUX8oEEnii2af91W9JwTO7N7sH97ujXDFoYPAaj6U5+YidqYPkZlRzTC\nIrwp3ZhhqSCaK6CmSPfItbyVqR00N0erRX8GOhzecvHUZRrsdc/jUhSiR8iL\nchHpF7ogx6e93+W4VeVMf/fOL7sJPOCmXaWEhDdoR5icf+hPST8Rrfsb42xv\noh5ueyafHJfc5N6gk261Mu8cLRXTkdWxK1jyxpXXUiu5FkXH/iN5+S9fDmSs\n7aUji7ZQvB1eyNCuSAYwrRm8TEX0Tmg7rOi+q/5xo+GTa4PCtNQegtboCA9U\n3UYS\r\n=h0Gn\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"2eff079f136f31e57f684f2905d6686d2000aa0d","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.29","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.43","@balena/jellyfish-logger":"^3.0.7","@balena/jellyfish-metrics":"^1.0.259","@balena/jellyfish-environment":"^4.1.35"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.160","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.32_1623028469223_0.8794965367432572","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.33-renovate-major-external-major-0921e1bdc1e8f356a22059a3e703a408ac08588f":{"name":"@balena/jellyfish-core","version":"3.0.33-renovate-major-external-major-0921e1bdc1e8f356a22059a3e703a408ac08588f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.33-renovate-major-external-major-0921e1bdc1e8f356a22059a3e703a408ac08588f","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9ae8b7d43b15ec72f566da7c0fff07edec31b13a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.33-renovate-major-external-major-0921e1bdc1e8f356a22059a3e703a408ac08588f.tgz","fileCount":190,"integrity":"sha512-iBYYKPbeTaCVthVYGJh54SLBTLqAj0EBN343GDWIYgAZdcHg3VyFIvl/L3lCbKc1Aov07vmP/zxvVTVg9eIMZQ==","signatures":[{"sig":"MEUCIQCcUE7ySrrzkXHS0fXkprxpGB48JYUES/jtmTGbG7/OfgIgCDMmqxrXnR6VVVjapQRpUl4mzOg2i0vx5U93XXDcKRc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":719433,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgvXReCRA9TVsSAnZWagAAhJ4QAJ5U1ixQK29d7PmZOsLt\nexpI6tKwSWhUcmRM1WWzCHXe7j8wlaM/28l9NVB/ZkZbhwwRh8fKe8rjQm55\nGqU4Ebsfz7BlQzkFnFO+3t+ZTYohv+j19RK8ZpJb6w2ZY47+wprOT4h6+Z6l\nRqgyCDZRP+m+Wd3QcUQjWqIKtW1eN81yFGKp9mbkAHPqliE4PAmTHXjRcz9H\nH9J24E+OVu6yxDi51ezWfe1L4ss1ykZexbHH4YWiOxPY5JgLrLedDxECDOGO\nBNG83/l38BSMgrsy2/ajyyaCafalzq0TITaiziwMZ5g8SoTqXbpy9GnxZJhi\n4YrwmQKqOoReXklQaTQzWaAFbpwypzJoK3no3rOPPEAtrVM9NiIZcKMa0oRt\nJ3ixb4sjs4awZPeYtkOmfsHqYCY7XlkgJ6wl1WubLu5wls2fJttgl0w6enrb\nJcLLat8B3RRau+NRxMieW+tleQy2Qakp02BUrQHg96zSJ+1AkgTdNuocj+QG\nw9Z0eCIeYt9g41FREbte2yeL3V0t3dNaz4LQPzFuVF0tIsXmz6q6v/WihRn6\nXl5bt9BI3fO3Wstt+bmb20FazQvbUvTZaRG/cg76dcYHqv3iWDbEOPOvYh3S\nHonJPF9HBaLJGlxa9+yKwLLmmz39bua6Jf/tMD2TRYIMfQ+whpXRoeep9Dzc\nONgI\r\n=qHQP\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"0921e1bdc1e8f356a22059a3e703a408ac08588f","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.29","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.43","@balena/jellyfish-logger":"^3.0.7","@balena/jellyfish-metrics":"^1.0.259","@balena/jellyfish-environment":"^4.1.35"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.4","deplint":"^1.1.3","ts-jest":"^27.0.3","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.160","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.6.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.33-renovate-major-external-major-0921e1bdc1e8f356a22059a3e703a408ac08588f_1623028829843_0.11050516517026043","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.33-add-version-tests-2e41531055c370ae82d9925f1779a931e0c88acf":{"name":"@balena/jellyfish-core","version":"3.0.33-add-version-tests-2e41531055c370ae82d9925f1779a931e0c88acf","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.33-add-version-tests-2e41531055c370ae82d9925f1779a931e0c88acf","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"910a91c4a8c52add16e9c4779ba595f5810c1632","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.33-add-version-tests-2e41531055c370ae82d9925f1779a931e0c88acf.tgz","fileCount":190,"integrity":"sha512-ZiU9RD0zR4pX4JRCTrDzyXgbkalqLlxfS7GimAxpyVIpTtySEv+bb19LZPaj+qccEc7GfAeoPmGyB+gJ0mnWrg==","signatures":[{"sig":"MEUCIHeGnw9EQ/etSyGZW34aQjYAGrq7I8PfEj+QKYbZNveuAiEAsYXiMDJnYhR2NX46nXPZ3tKfeSpxt/T0f8DW+cit3/s=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":719415,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgvYPHCRA9TVsSAnZWagAAikkP/0h5DWLXsEoOJwTUs40o\nf24Dc1pDOK+8o4rvtmrxEwJvTtgC5YPCrOJjjgyY5XLUrGfkS1+4YNJEHyIM\ncUesyJ4zvXN8w/Un0a1HFkPdijsDDCqhj/U+HdfD1dRaq9QP3IxDj1viCWza\nesd74fYoDbs/B4TshnqqYGZ9Efe/Gkuxc8Tge7aNR/rFMO42zVht0PLMSkHr\n/llhCm05cIGWYG/B9oE1gyhYu+cgfPXCSxNjAzdT1gL+GazOntwRkIvtsC9r\ngzFSyJxlo7ZMKhgKgJgDM6qxfLhXJjK1F+33P37cWcBXZfVBxN9wdBvPw3B/\nb4Kj7FUDRDUdgymHA7HCYHLHLpwSZfzQWK3RLp//zsCI+65vNMF418WFP36s\nmLRtqGNxwJwg2NnFLdCVGhiatSMYE+dMmpxw5K2V7hsu18zeib+073uP8CAO\n+xeqoZOulUIcJ90aut6q/Pp2Y7UphV0djI944+SvIaeo6ak5dVdbkkcpKZzm\nmOATuimky8zN4b7ERUwPZ5mv++lvMXmkg/JrT8DpD+0j2wEL2QcfAa/npgdD\nK2Lrp1YSyE5WJDS6qO8xJ1h9f9v+sS+PAb2Dl+jWKhC2VSDeBiQllPkZ7pG6\nXOw8ErLE29lcFykKwo8WUfDQTjirrDG7Y/1mUp9dYIoJ/msPv/+QISKrd7D7\nPF02\r\n=JJ6B\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"2e41531055c370ae82d9925f1779a931e0c88acf","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.29","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.43","@balena/jellyfish-logger":"^3.0.7","@balena/jellyfish-metrics":"^1.0.259","@balena/jellyfish-environment":"^4.1.35"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.160","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.33-add-version-tests-2e41531055c370ae82d9925f1779a931e0c88acf_1623032775329_0.15817690594643574","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.33-renovate-internal-patch-513e65d48a8026a3f086d543535e519c40061786":{"name":"@balena/jellyfish-core","version":"3.0.33-renovate-internal-patch-513e65d48a8026a3f086d543535e519c40061786","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.33-renovate-internal-patch-513e65d48a8026a3f086d543535e519c40061786","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f105bbb5cb3212652f8baa6bd997cd156613e747","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.33-renovate-internal-patch-513e65d48a8026a3f086d543535e519c40061786.tgz","fileCount":190,"integrity":"sha512-Uzg1stmSvbfDdzPpVS6Njklu1ZTciKhQgsUA/c0g5T3QePu6UUVtkpIYEyO+Z/fL2n1KKMJLjXGIBm4GCNR23A==","signatures":[{"sig":"MEUCIQC2u7r4mATUg3jk37j5rkc/U9eZItyFRXMlWW3D8hAGoQIgGyTk9Hjc5kYVBFCixjvf8nBdDXqCqmAdnge59sBYahs=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":719427,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgvY6pCRA9TVsSAnZWagAAtbkP/3gNyiV+XxtVjS2MrQS0\n53mBHViQ2dNBAWQJtcvX5xho0NAozX+20RK23tvfzIqumFhwQOmhQpBrNrry\nLgMfQWrd3w6vi6m0v+tTezcnlfAIPGfYZXgMgiur9N5pJ5FAv/ZmEftBU0H1\n+nxYgw3uO/mfV8CWzaheY+jRW5uNHPJJ5ZHzOdNfS0HN6wwY28qSPq1lq8NN\nKkv3b28BOqoLrpscoD99njHsu+7OC9lh85xP2/DsdHScKOMTdZ7KsXA1UVSY\nzf6wLwyXXxI+sFSyRmd1yxn5mwBkuc/maiHfI9Ng3hzM7yAlBcFnewSN2qEL\nZgUaKrSB+r+jHiFH2hx88+uzZHHwlb1rnjludbR4d9hkhYKGwBTDFJJnyHJi\nR7MizC4GanfR7ymSJ/ppgVyLJi8g69+WdXcUo6TtH+Oxw5zIkecawmQT6o4x\neYDCp55GxAK4E/Q2mVm5y5gwwMR8agoZdlnChfNZMX/5TDs17r7jkRTt13IE\nsk9vSIwOx642rnIKE5S2q/rzmj+KPOSppJjMemE/e4vEf7sQxmCyq+WbnpB0\n1r5lIdDSQWyXmCYRL9hpOAvaz2kXfc0g7ytdM1AJbGdCZD69ZaL4JhmDaFxD\nsOYZ/o/encZAYpBIXKccHjH1P7PVR9/7tUG6BUwmwiUaOgVhJEpDJ9XOTrhB\ncZyL\r\n=5Not\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"513e65d48a8026a3f086d543535e519c40061786","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.29","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.43","@balena/jellyfish-logger":"^3.0.7","@balena/jellyfish-metrics":"^1.0.260","@balena/jellyfish-environment":"^4.1.35"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.160","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.33-renovate-internal-patch-513e65d48a8026a3f086d543535e519c40061786_1623035561752_0.3503219492103038","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.33":{"name":"@balena/jellyfish-core","version":"3.0.33","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.33","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"be5e18af7332c2e4a0452ba9ce2c13cd78e17bc3","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.33.tgz","fileCount":190,"integrity":"sha512-tI6MEFUzcMTdgQ1fy4PNval44/QhAB6DEwb2aSZKpmpwdH2cFLN1nplUtqTY243umrtrv3IgvXI8xsXLGRmdMw==","signatures":[{"sig":"MEUCIQDoFhKEf35F348oPgEh11CW+zZTKXEXdVz+MIeK5atWOgIgUOjjyFuM4TJoTGc/I5AUY6GGlVJsbHve/6PkBevqBy0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":719362,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgvZAwCRA9TVsSAnZWagAA4hQP/1QdvVBtWMFCxzLJbT6y\nWbr0geC3IrvhiJ70Qy+fs1RYe3ukUvdpBXzMwcnH2kkWaHz80rJpYafAVYJt\nM8gu7/0i9sgEGKsvJ2wlZOL/NWyf50e0Q94Dhpbv2eP5gke+MxXvLSBMiaZj\nTKjI0DsIt1bNkTf13Dkffe/MGav9HL9lVsUeGMnDzYjg2iux92ZATLy7TLzc\noQFCuNtQdDtT2AC8VMDPWizxxbh65UjdCfHYyAY6YsdNQTMH1GKyX2JTogXK\nZhLjZlq9f/Az0Q0MUfwuDOtAxGeqtQtf71G6rCzsjE0q3Rfhx3Wn0xH1xsM3\nrUGICVSbJphX/+MmFqI6eS1PJDSIBAT1lRQycL0jgdk4VrAxK5IrebJMsqEU\nzsRUxyMR3KPQg2rHDvB9w6f5jmsMwLNA0YvXK4kXepq4zgazrJDwRiSHdw07\nsOAiFmx3HkopEGf9uTEErg9qv0IH6lpCN1SkkDU87/5KiM38hixRbJFbhcdM\n2F6g8NjOU4M6wnhugjzwQTKJpxpF0UApmdcsu7SNe0QvoQngaY5WLI+vMLoB\nu5tNKX5xuTBjkWu9QQEEK2pl6Z2VeLTADMnEBFE8NppvOkeLgpcg/SXqS7Rb\n3ijV5cMrMeGiSHfXFX0yaz+61xSw2OvnU3PaRITVP+Wh9AazwUhj/+dVWkjZ\nCzsJ\r\n=O0uK\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"97ef0afd835282629b22673f51acada32760bbac","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.29","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.43","@balena/jellyfish-logger":"^3.0.7","@balena/jellyfish-metrics":"^1.0.260","@balena/jellyfish-environment":"^4.1.35"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.160","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.33_1623035951759_0.5286136407959228","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.34-renovate-major-external-major-8fa1007c4414e53a44689ff6c941bb82fb8fe188":{"name":"@balena/jellyfish-core","version":"3.0.34-renovate-major-external-major-8fa1007c4414e53a44689ff6c941bb82fb8fe188","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.34-renovate-major-external-major-8fa1007c4414e53a44689ff6c941bb82fb8fe188","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"7a4e2729d1cf6ee3ce407e883e2d1fb24266bb61","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.34-renovate-major-external-major-8fa1007c4414e53a44689ff6c941bb82fb8fe188.tgz","fileCount":190,"integrity":"sha512-40JMrIGfyqZqIPL4T0x8lLHJe7j0k44hOGtv+afT5iekjhp0MyKvJD97JJ6Ud+Fe3oH0lDHiDOIiyn83f+0icA==","signatures":[{"sig":"MEYCIQCDcwsnrxlfOjl3bG1EKOV/Plecy6Pjr6uLF/C6F5GU/QIhAO0FluPMsBOzMaAoSxbQThPW2dpNRAjP/dbZornG//Yc","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":719504,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgvZE5CRA9TVsSAnZWagAAwqcP/1Xp24J9CjDe49hS6KgU\nB55qmTjd8UARm3rTClFo54SMPGvO9fw4YPXUyuunayqJvL2BqRC8H3NPBlzm\ncBc03H/yXBJ/EvRumMujKHImUtgfLHtA8KI7D7rbopFPPbIx11C/lPCNssxz\n/cqj4Zy9EPYMWcr3qsZZ1kjYXy2u6ghsdxUVALYUNe6QiwcZWbXby0tEx7S/\nmx+alDBp+28c3iAbBGyCmic8wORfjUbNiO/hhuIAz04LJ01VRVKoQ6PbOiuh\nvm2ISp+udk/yZITKJslYZqRd3N4CutHKbwtN0mqVEaPlr4LMLX07RkP+P/fV\nIFyEu43FsbQYqTP6rY8w+QxUxRbgx5Bw57WgrC5fcwZFI93dVadwVEAdD/Si\n2WFNW7LvrvAEtLOXw70CePTEqsKlLPS2RKtLT7L9bYbbLX9Toe0bXOM8ZLv/\nMn9prM1WDx4ynn60u8R13ug+BxFbQnKA4hwdblXOGWQdL0UxSgeHzIGRvjvd\nKiZw7Q05xZmrKeYKE2DHQcK4YWkg7cflWvNM3Rq/UfsT2ZPpN9po9zH7/6sz\n17pJFJYi3CmG68Qvbc0ykEW/GqmZCTvIwMNOQsiE8hAH++1pr5j772bvcCmX\n9nBNkzgHpS7RGeVqEZGbYIOiy0UnI+WH/pIEA86Kv3onQX/7QktnP1Qo4PKT\n0Buz\r\n=zkvU\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"8fa1007c4414e53a44689ff6c941bb82fb8fe188","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.29","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.43","@balena/jellyfish-logger":"^3.0.7","@balena/jellyfish-metrics":"^1.0.260","@balena/jellyfish-environment":"^4.1.35"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.4","deplint":"^1.1.3","ts-jest":"^27.0.3","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.160","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.6.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.34-renovate-major-external-major-8fa1007c4414e53a44689ff6c941bb82fb8fe188_1623036217213_0.43498292832395635","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.34-add-version-tests-e84f813251e3639535b67476af01ee36f94f0e89":{"name":"@balena/jellyfish-core","version":"3.0.34-add-version-tests-e84f813251e3639535b67476af01ee36f94f0e89","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.34-add-version-tests-e84f813251e3639535b67476af01ee36f94f0e89","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8fd9daa80cb783d49bf37b9b7d9b56b5b18607ec","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.34-add-version-tests-e84f813251e3639535b67476af01ee36f94f0e89.tgz","fileCount":190,"integrity":"sha512-qSqXVsl1/IBRycdgMJ7A0VnXNUqhzqYOdqeElg7MapAr702z6ooj04XOyx7oabLDSK2C3fwjHEjDMk/c/0zo/g==","signatures":[{"sig":"MEQCICdFOkMVlUunHXTukMxjcQ+riT5jTGyxhs/7AghduCMtAiBzrTrdY5zhSkCWYl9tbGuXSf8hBy9rD82wG3pImxbAmQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":719486,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgvZMRCRA9TVsSAnZWagAABZUP/2ecK8clWhDz9dV4/7o3\nfvdrXfR4pm/qhAnXyneGtmARgvnPYM915HqZPocO7YHw6KEht8wXl6NMl6Wf\n4+ZM9Q5xQt6UnDGH4a6BX+9vAR1nuI6wJS4bVTgAGRAOi7W7aHK0fZlV4EhJ\nDeU4EIB5qFuzpkjF5UJeBiec6AQlboZB3KgBf2ROwSuV7DGxiWqQNxABcHZI\n9XQI3V9d3z12eBpcqsHICv1EiqWzLAQ2pLxR4b3cgfXjp42sxznblLq1tWSC\nEvkoTsz2AMrZ4h+AYiUzgS2lRT0lrOvOUSo/w16qqawaLRAf0wXxB8r655go\nf0s9XBJs8k+HfjWbcElOOPhlKlWcSzeWkvafXiBB58DzrSjxgHybd5+E0dex\nZXEt9OWGcis3pOj3JrMpgDMUO7CFI1/MNS7CiXQHnEtIcYjyKZQRVhBg9xo8\n8EehY2RE70unUPwpv5mNWKXsREcmvq6Ifn+zTjzktLypU58GdKivWsDJaoaI\nhIIcYIhb4PKyOCQPMJXgeUEC2er3yJc9sWGnAGIMvZaetqPDlCSsHmZ3k0Dy\nDSOJ4gr72SgCwTmqO0W2FTldAXjPV+GbpOz7YKhrkIXDOHh7xYr8f/n2jvEt\n2wb4ctr7wRpFv/+TX3cdr1niyMl33oMVC+LTPD519QNH1em5/3DI1yNtP0Bc\ngydx\r\n=hiVJ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"e84f813251e3639535b67476af01ee36f94f0e89","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.29","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.43","@balena/jellyfish-logger":"^3.0.7","@balena/jellyfish-metrics":"^1.0.260","@balena/jellyfish-environment":"^4.1.35"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.160","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.34-add-version-tests-e84f813251e3639535b67476af01ee36f94f0e89_1623036689636_0.26381894714625753","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.34":{"name":"@balena/jellyfish-core","version":"3.0.34","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.34","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5c21f511f3c422db047e8b21e8f7c6014259b9af","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.34.tgz","fileCount":190,"integrity":"sha512-pzOWbTBUf1jjZSm9BLGQ1q4Wy7MgBLRfVPUAwm3i3PvWNPKZZY2fY6ycaoHa6ogbhFW/vk1Zctqyj23u3dZrhQ==","signatures":[{"sig":"MEUCIBXA2etKaNgNHcEHSx/hTbj90He7pKiX6Ti55J7KuhF2AiEA24X8qLU1iREp+3KGKey6KU5xly4UJvI4bSlQMmrZmz4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":719427,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgveFRCRA9TVsSAnZWagAA59cQAJkKOeZgvB8x8nVYEYsk\nbIQJH1z8LV18yKgScnVdaEVt9KdwqgkeqZ83fu3qnahloJxnUhVS7n5O0uA8\nRbxDhyg8s2FfldMtRrIYb/WvptmH5M8i+y9szHNJfhbVXtkWBrpivlZDco2O\nEgqQs30oY/uLQpZMD4ATd4MIsf9XKZvOyCUz22H8PWEZWOHaqE7dfIOW2mAO\nAHGsqqFpLb7MHsHznILxqRvgMJIG4KDHKNgf6QBVLNyZAIQUDymv9ckwelvL\nE4eftSKr7OCHy+lreK/WzL1Vx9WAiOvDMV/mFlw+ZH/5wMukPPhsVamMWTiv\ncstsG/3a/NqGH4dtTrkGvgmyMvKT23JY7yiDSSXmFL0Mxq3hkFbfzRlBG4L/\nd8u27Sj5zbtiN7ehBNKp2JU9h1xhH0cdxIaN2gC+449+cBl6WYsm/nlYv6T2\nByJNCndt0Vb9Ah8CtSpl2qJbf9Eu6mj/qsn4GvFUhH2l9MbkT1+PX4c3PzIM\nrrEXnRcEjMK3hqWTLO8vIl5tsQw9SLxav+xct3vC7wNg5kDRx4DvClkfpqUB\n6oi825DWadcM5icAdR9yPmJZ6Ls7k3xtnwGup9JTdH3oajOgZIpJcFdgXSd4\nmOUPvwO0o8kr43qQi+B9ObN9TGBTTjJJ4tsA5cMc/ytEcUVL2CjSgqAjxc8L\nZF78\r\n=QOoh\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"f7e8d54d371cd11366843f139d8f9038b89ce3c6","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.29","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.43","@balena/jellyfish-logger":"^3.0.7","@balena/jellyfish-metrics":"^1.0.260","@balena/jellyfish-environment":"^4.1.35"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.160","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.34_1623056721079_0.8939858126158031","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.35-renovate-major-external-major-34895ebd5af7f16a641f432105fdeebcc0a40ae8":{"name":"@balena/jellyfish-core","version":"3.0.35-renovate-major-external-major-34895ebd5af7f16a641f432105fdeebcc0a40ae8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.35-renovate-major-external-major-34895ebd5af7f16a641f432105fdeebcc0a40ae8","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"70d7053a4e54bd8a951dd9bd36e95750f049f91a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.35-renovate-major-external-major-34895ebd5af7f16a641f432105fdeebcc0a40ae8.tgz","fileCount":190,"integrity":"sha512-ZFwE0Jpd3dI1Re/KvKMSYeA70X3+cSTWVxwzK0OhpmH8hmAJ38g/vaiu5FUYjjHed6F2K7/ovz0YgnVnckFEgA==","signatures":[{"sig":"MEYCIQDXmMIlGEPmD1kz1yR+2QhoEAWzM8PPaTBrlxgcketaigIhAMyMIafRQ5ORC71h6h0RCd7ZvzlZaqsbnkJo9vYE75Af","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":719569,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgveLVCRA9TVsSAnZWagAAoIQP/3WSGRnoYQs5fpuRav7f\nCwMJfIKs9mLEwBYAXMBcDnOGHBrf0YwOMGBCqyHvTEiyTmzNyvZoUUMQKWSV\nxpNFv5VTBtVIEZb+E3gyEaXufTtyHBxx3Dg2Oen+WIHj09plsxQ0R0ewFbPZ\nkhz7DMGhMm8ges79DmIUJ1J6LPUAqdCtY3M17o+rLUBpx4nZ85tj8u7WI9wZ\nd5bUfl3t+6DXWHtar77PAYdI95zNNzqceF4TPh30IJ2TMd3raEhyjh5OTalP\nlB4WI7DWfUIdIy+JtJhoqQHdREPr9vWYUPxNrtrSVVgbSng0BXZzanQ9nYou\n1EC6HNfXJowd4TqOp1slwIjVwlJXqEcDcyeL3ZWun6b1RwwDM/rHGBk813zx\nm4R1PrnHSPCI8ciiChR1vYKX14OULAK4AvpiGhXPOrPOldDAA4KonoF4cS8R\nvvA9KaFOar0goYbJSHkonr9uiPOvWmOE6u0WcwlXme/C4MmNP7n7+aejPXyd\n7Z+QVuMWIUlJZceHp3OayhXR9SH9Jh7rs6sJbu+0xdjdOGYQFwQzm0yR5BAk\ntcf4RdHAL5s9l6jnSN55RSStjzmDp2FXg4txqq6EdZ3RjvdnUvXkqS7QZ0J7\nBlK5xLKCcx/7eQ6muEWgnV74SluHiYyUq2bbBpfScPBaMzwgXLRFna+ZfhkX\nPpN5\r\n=UhgH\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"34895ebd5af7f16a641f432105fdeebcc0a40ae8","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","@types/redis":"^2.8.29","uuid-v4-regex":"^1.0.2","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","fast-json-patch":"^2.2.1","@types/pg-format":"^1.0.1","debounce-promise":"^3.1.2","@types/redis-mock":"^0.17.0","json-schema-deref-sync":"^0.14.0","@types/debounce-promise":"^3.1.3","@balena/jellyfish-assert":"^1.1.43","@balena/jellyfish-logger":"^3.0.7","@balena/jellyfish-metrics":"^1.0.260","@balena/jellyfish-environment":"^4.1.35"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.4","deplint":"^1.1.3","ts-jest":"^27.0.3","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","simple-git-hooks":"^2.4.1","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.160","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.6.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.35-renovate-major-external-major-34895ebd5af7f16a641f432105fdeebcc0a40ae8_1623057108910_0.7834181307430379","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.35-fix-type-deps-52a4bbee34ca1c785fbcf19d1e246527a8f8a7fe":{"name":"@balena/jellyfish-core","version":"3.0.35-fix-type-deps-52a4bbee34ca1c785fbcf19d1e246527a8f8a7fe","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.35-fix-type-deps-52a4bbee34ca1c785fbcf19d1e246527a8f8a7fe","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"020c42ae0bf829b9e3a31e572476e886364b0262","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.35-fix-type-deps-52a4bbee34ca1c785fbcf19d1e246527a8f8a7fe.tgz","fileCount":190,"integrity":"sha512-73QxlJfatJaysI6Se+/3yoAse8RX5RiRVzW+nz/Y9o/F4UjUB+t6tI9YH3FKg3zc6vgzwrztsG11RXSbTiRfOg==","signatures":[{"sig":"MEUCIHmp2xKRyPYSl9T3th8AMHmuMVH+LpnVdwQ57HVCrUCrAiEA/IN4nClLS9/PF7EPP3cBM43Ua24bCVbpPntm43z6TVo=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":719558,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgvr+uCRA9TVsSAnZWagAAmZ0QAJJuwHzAten+GuFgJXJK\narTppLH485/WVe/rdEFyyL/U/DTOmYyfp6YJ3jjIbm6EnPFRRZ63ej49iQfc\nxnvjsZIcM+f2hc6UU7bMjJ4Yi1TUgwAp1/UamNvDekQtM/O9IDzG9tDkk5Ia\nvIy33Kv7eoDrL97L4lzOjeF53tU8VxrsZnmknUPYJKf7x5j96jo3YOYA4RXE\ncjbQQtokZhZNSSwDkBOCIQjexy/6KyGcIMa3DNtV1eSZ3+gWbZzmYS0s1s4y\nZFpnFNEb9qmUStkQ7y27au5AVVlUoyWgNzoPqjodZnVELchpSO5Xtxd2zmko\nEjr6c+Kc78Jh+2p1vaWRW1sWCOi2avT5ns8kRLEWndOHntGHVwaq36o+gbIB\nNgjwn7h3b+sbR05oKk7ud79zLHAK6XJ1/t21cLhQi0gj/nXJhvXk5k+0WusP\nTscqOTXgWiaWqR8H1AEGTGy+INSHptthFzsvhpkZcuaUUrv3g6teyejQoSfs\nH9NEFdxRkwCEAe7GCG/hXzN5nY6pWfqZCoTRIwT7pOYSrcF4tnV02L7n2aMX\nCV+J64iRXeFofyCOBQ49d9rqq6v+dVqyITolOQ3hcjNll/UDjhelpf4HUrJs\n153dBiMfHg1EYWjynI53TLW1pUkjD7VNeamzp3oofhYzCa2V341ncv9PhCHA\nctNC\r\n=rZmQ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"52a4bbee34ca1c785fbcf19d1e246527a8f8a7fe","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.43","@balena/jellyfish-logger":"^3.0.7","@balena/jellyfish-metrics":"^1.0.260","@balena/jellyfish-environment":"^4.1.35"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.160","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.3","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.35-fix-type-deps-52a4bbee34ca1c785fbcf19d1e246527a8f8a7fe_1623113645867_0.022699278214552088","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.35":{"name":"@balena/jellyfish-core","version":"3.0.35","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.35","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"114d501faa613fd16c77e178d2ba7a3fb4c6e927","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.35.tgz","fileCount":190,"integrity":"sha512-+btZbzYwr6/K7640F3tv0fWxS6F7OueMxTVpLNEanbFmFEyQu7VTfwfRjvd93rHeC4t1w+XlzQO8Ya9p/EjCeg==","signatures":[{"sig":"MEYCIQCoV3Imcv0C4lpDme4kX1BdUifB3vXvms08QgToYtV2GgIhAMIE0PQ9BSk3objse0O1Gn+H3X52pYt0G/xVULpVhm84","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":719503,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgvsLBCRA9TVsSAnZWagAA4dgP/A4k6pLcXGFnlyTiwQ2L\nJZz0x9z40cmi6Cd3+Fn/rf+i/5eC7W7GDo2lxi6UOkEUz+gt9KyV44nCNXlW\n3tS5dhC90KoQg+LpaO5LO7g5GU51A+Cu4wSClq0k+NP2LzwMZdIvdIbTvF1q\n1fprdPWsktdnbbFCZxWnNvmi/IGwL9tCaEZWWLfQOEHgSQp18I7ulH2nvbuf\nTqZ6sE2Urq2CvRsn2yGhEJHmFJds9SBRtR2jd+6RyIWACdjKzhGmm9LSUbwt\n+FRGScp8tJW9ZtcBjV1TqnBSzUTPnBr+OcdszwybMvkgTGm7lMK0Ghk6dyy0\nSn2kRDrv210lXy7Mfoo7EuO45iqje8SkVzz+lV2phRwa3ZA5huzLPeXGliXy\nsNItLRdFK6yBHvPTkqDztzKCUr05oNz2pnEIF7bLhRBfRnJJE6a9bppDQ6S5\nVmMqM2u5W/i4uuY+QT4fMCTHhUgKz28u0n4GZVB4Y6dX0VyGovKXCAOdX+gt\njRVsdVWKrG0VMcJpksEw+T0GaXGD5F2JWC+bHHlGsMtKwtIR8Lr0n+hqVx/A\nkuvxCP4IPSggo9ymS58zgLeCGXawP91JRN1oKBEqBYbjhkMzjr12r+oxiq8n\nzW6jWSm+IL6vMqXScdU0v9qID+AWMXtd3P9D342HrkFOxBjvw2dzSYUWTTk6\nEBOK\r\n=vDwm\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"ada6f69cf62c4dd42e2e56ca516df02405f830b3","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.43","@balena/jellyfish-logger":"^3.0.7","@balena/jellyfish-metrics":"^1.0.260","@balena/jellyfish-environment":"^4.1.35"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.160","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.3","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.35_1623114433691_0.4837807816166597","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.0.36-renovate-major-external-major-a166913d81791efc7b24a8546c5ca7ecc4394cab":{"name":"@balena/jellyfish-core","version":"3.0.36-renovate-major-external-major-a166913d81791efc7b24a8546c5ca7ecc4394cab","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.0.36-renovate-major-external-major-a166913d81791efc7b24a8546c5ca7ecc4394cab","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6c8f4a865b87a4a20e74ae5403de9775a867c0df","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.0.36-renovate-major-external-major-a166913d81791efc7b24a8546c5ca7ecc4394cab.tgz","fileCount":190,"integrity":"sha512-lGF6XVVYu+QO+GtEUVr7K89RUXlKV/icTQosFoaxQKlLR2sz3X2HcOtHERpXKtEr9rt/SZg18fGIL5k0CtW4VA==","signatures":[{"sig":"MEQCIDCR0hZ/Hz9KoBXKVHGdPbn4jdIZCuaoj/bBcgZ05TX8AiAP7l9W6I3s8Fd5/QHxcxmI5e6F5LdROQ9Dwl6FSMXiUg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":719645,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgvsO2CRA9TVsSAnZWagAAMZcP/1jdJpMkz2z4b9tsz2je\nW8B5+J9bR3yv65y+GtpMlabV9I6ehpl91rLeT5XtMSwU7JIL6gPXkz6mVOwJ\nEz7n4cMffSrdGlsCyAUnVj81xPXYZy/WvJfoNihVmNiPqA+Ig2MwhMw+Mtep\n/Qi7Yxo4busr2ZyE9RAq3XgvW89yNo0yvlh1AnISgGtPCY26L8r+6vNqwR/w\nzo8x4vI0IUrkdTCQlIDwR9hQme+lkpDQqJvmX9k8BSj3L2oX//mjo2R6sCsr\njw6PTd3BNf0BYbxsojFjx8eia8NDOB1h+/88ha1NszfJrsoXhm+0C/yYwFia\nU0wIjld5CRGZjTkD+R4B05RVZRF6vfgv2SvW3LYVu3USIJodgdfZgTkJ3x4y\nXEznkJstAa6nqZ/KJ3JBdRIhAVMzvfgp0bN0aDemL7KtwtfWqFQHhwSYEw2J\nIwvNL9AXHexSrEobM84ygit+0pHYQPbp91Dkp+nWAf/QAxAXefMhLRmA/vqQ\nsOstVzd+X5ZlQFkcqWathCeXAQVgWg3TCveeiPjmEP+ZJmFQxVVdLumMWWEE\nGHjlp5NzIt+MecNdEJIdX2UlyNWYVkQGbHIsww79e0YVjIOEUHgwkr5k88q1\njYzxyhbJasuyKLBdvwAt2/YY8rfOlxJvDDxEA+yVBJ2VEqB6KlHrcySE5bFZ\n8oT2\r\n=EvMS\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"a166913d81791efc7b24a8546c5ca7ecc4394cab","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.43","@balena/jellyfish-logger":"^3.0.7","@balena/jellyfish-metrics":"^1.0.260","@balena/jellyfish-environment":"^4.1.35"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.4","deplint":"^1.1.3","ts-jest":"^27.0.3","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.160","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.6.3","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.0.36-renovate-major-external-major-a166913d81791efc7b24a8546c5ca7ecc4394cab_1623114678101_0.6735697348700365","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.1.0-add-support-for-query-mask-a2b11a083444e50043e290cb8c4e4fe8a7e4ed62":{"name":"@balena/jellyfish-core","version":"3.1.0-add-support-for-query-mask-a2b11a083444e50043e290cb8c4e4fe8a7e4ed62","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.1.0-add-support-for-query-mask-a2b11a083444e50043e290cb8c4e4fe8a7e4ed62","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"004e876e024583c2fb12c16947faf0a9a9cd966c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.1.0-add-support-for-query-mask-a2b11a083444e50043e290cb8c4e4fe8a7e4ed62.tgz","fileCount":190,"integrity":"sha512-KYtNBepbkERVMC2oj9ZSN+fLGBPOIRlCHmPV4nUKXeIt4ewYkcBcsBwq16XePeY9dvIy6gOVRO1pQc6RksBb0A==","signatures":[{"sig":"MEUCIQDihVuCsoVJgifFtQ8glTCWPHTvMT1moJTf8e89Pv0gaAIgV3hzHplOi73yv12YC3RzSNRZVhwkRplymSCseASbbDM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":720172,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgvtJWCRA9TVsSAnZWagAAEP4P/0Do3U3wy6bOVztyBpF5\nsr8QGGJ7BrPJ+QEgWIDQhhkLhF2/KRV+jGuYjBn6F9HOs7OIGNB3E2raFYan\nEqj9jE5GJmQuDtFhwhRBkcK891G6Tj6L6VVZs0/Kcpc8j5plfHmiWUBgxGGL\nuoZvoxtg21ZY3kLQxYQ++zh2vqKAAa5ijsCBGg+Agmj+l99d93DBUdcbbSb3\nUW1uNAQR8ZHGPvMIbXCGVW+XHx7hcp5h4/4jYUP4Y94satb0xsTvgpIx5B3U\nPmKGDFdh8NFp/U1zBszOFb9sjNZvE980DwG2VQLeOIAPfEZETMO1s76XcZVW\nfWspTjE/51+ELZ/E2iYSi7Su7hF8TADy2xqrKgzKjj0wJKLa9tbCDxmZS64/\nl07dI/NyH98BaapqvyZJIMuPmjvuO6kmpRnXWpiKXG8x7l8JwC6HXz8yfF7Q\n+pb7QkiQj5koOK0QIqmXkt8EEjcPGgoqqjywKDTbdAycceqScL77NQqb8LV3\nJ8pXvX2JUmJ9Gfkreg4Ek4xSdtOdeY3wKOAtnJTcrdGgz141br1hbpCsHn75\nzZxA6RkPHmtvsxgyDPrHQcrotaY/O1cTk0EARuc2V4q4/j8ITX2doJH/u7HB\nu+KYmX6NnJAolcxGnW9VrDwo4jG838ksQfEvRclmkiJB8VoD/aoeLTJzbBaQ\n8sx+\r\n=80tH\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"a2b11a083444e50043e290cb8c4e4fe8a7e4ed62","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.43","@balena/jellyfish-logger":"^3.0.7","@balena/jellyfish-metrics":"^1.0.260","@balena/jellyfish-environment":"^4.1.35"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.160","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.3","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.1.0-add-support-for-query-mask-a2b11a083444e50043e290cb8c4e4fe8a7e4ed62_1623118421992_0.611390624991413","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.1.0":{"name":"@balena/jellyfish-core","version":"3.1.0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.1.0","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c2e3fda2938146f7300a32d40c5454fb0e7fcb9d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.1.0.tgz","fileCount":190,"integrity":"sha512-qMPuxVxzfPvP3nyEigmVtBFTG3OtfoTVr0cDPyR6Nb8lLVSto8iM7oyRPyDkkEYJvY0Nh087eWy+LAKkE5aUPw==","signatures":[{"sig":"MEUCIQCHGl1NKcvEcsY+FCzXxWi1UOcWkLhEd+OPKrxzUL//qAIgQf7K0mTdzbYyYQu4YVCHO2ca0xqw/kucu15nyPP8TN8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":720104,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgvtzzCRA9TVsSAnZWagAAB1wP/3lYEzOhK82LkB0UQuy4\nHPpLoSb8LNYDHp4YU67rFFyPvR8QDonDmXz0Ml9aKdRW8ISlFxlJC/IYwILy\n0H09+qw+Fau8jx0Pi4I8aT/9FjI6mullYJEHzvjoLt4XHGdz0kn60cbGfBsj\nj2vkr9VlkUNmFBoiI6y9rzIOMxAOmbpklLLkQ2kANir+yQ7wjLkV1WrG44Oq\n3CWvgCAQDEr0Zdhhq5S5nHmDu49Wis6o8g9havwU5SxQHvNmC3sypPOaZiQ+\nh7EHzCnhqdZZGHoj5r1nB0IFBRaoTmyRS3LyhbsSNowebl6mTMgxibdLfS8N\nEaEVAAYu3o8ozLRJenRo73Sk/r1vUAnSxlc5NarJKSlCdhLT7sxKeAJ8Tmhc\nOg9w8UpPmvlYYP1JNpcLN5yvmg9rNM8kVUGrWs9K2hdTuCNnC8i7VEtVinm9\nFtCUiCyjh9RwnV4Y40KS03KIv+lI5FpWpfttdLaGEcL54/UOIxhyexvRdcpF\nPTuIFcspNhWXDA6BuVaDrw/7jDbS9XNAKsHjoo9vI5KpNjEc9C80tP/n771J\nAmgATCS+SzBosJHrO22GUE1KHL53hCERu3WAyFTX+HLdatDvKIgfVJr+miHj\njH9v+vhYZPCGoDheSuwsvX5kkwnCQlXZLFG3UrJWptdDNI6T4RSfUuS8rEOL\ne1AB\r\n=BEv1\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"806a5254e6b5a460c8b2498963592c93c46e4c27","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.43","@balena/jellyfish-logger":"^3.0.7","@balena/jellyfish-metrics":"^1.0.260","@balena/jellyfish-environment":"^4.1.35"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.160","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.3","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.1.0_1623121139567_0.855728415060014","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.1.1-renovate-internal-patch-c15f1432b4e8d12a13e2a20745f75b2c6780957d":{"name":"@balena/jellyfish-core","version":"3.1.1-renovate-internal-patch-c15f1432b4e8d12a13e2a20745f75b2c6780957d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.1.1-renovate-internal-patch-c15f1432b4e8d12a13e2a20745f75b2c6780957d","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9d0b2127d3678e62382dabc20d9d08c6e30edd73","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.1.1-renovate-internal-patch-c15f1432b4e8d12a13e2a20745f75b2c6780957d.tgz","fileCount":190,"integrity":"sha512-+U02/kv3VEjWy3QqDbe7A0QDrOcTsza/8eY6hSl4TQIR5GAbehsGmL2KzOt0GpXyxeeTD2s7lopigNtKqLlG2A==","signatures":[{"sig":"MEUCIAPBlQj4ovUFmihoNI1oxFCu/Mun8h6+OdnkDUK6C8l5AiEAi1PM+VhBwFiWeKdKhUeb4YouCiuuolFzXrSsAhEpFXk=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":720273,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgvt57CRA9TVsSAnZWagAA58UP+wbnp6xOsHLRd22Jj05x\nah+xOt/4fSxmJlrgfqSo/F9UNZrIhDKnNiJq6lhhp4AAx1Cb+BZJEI5IVPLN\nF0PmyUYgua0vgJo9/m0HsopVVxcTEn2vYg2Do2mfOybOdCucFFGHRbNqoX4G\n1k582O6UUNp5/AQVAhzhVw/xzLAB95G32ePvoa2PrmlaBllmb+9Ts/5N8svJ\nyMHGZoP/VFsMFmuqoEbvVcq+YfDjRvYuxZgzK5eLHisZ01bDuE2HdWHkDHD0\nOueOB14eNApZ0zexS/tlXlHRS31wv53ZqA3A/lvJ0mXH2Q0X/8XxaD8EXcVx\nSt3V8VNrcU62sr4+QL9SifYRBt7KYt/CMmD6lb2Rl8WiJy/Zl3XcxSE+vK8s\n0WTl/dU02qB/AA5iVqvssM6tVVRjmVXU5AertG5fFv9t2fskxgQ3mJ6alG0R\n19uOO+5yaEBcF/L5IV+K9DsT3nxlU4RXfr9g4SVJo2CM+AnjwaBOXgKSgD1E\nR8V6qxepWGZgly6ybdWM1t8RJtEf73E+mWOwJhTr38O90SbUYVnEObe85l9X\nM+j9ZgG9bGf1idlY5dhRLztelbDO+AcbP0fU+8QUUmvnUXrryWF8TVYeuC8Y\n3wXOCSIwiWd+QHGpnKqy0RHHlsufmGcH+I9QTBpgOn9lQ4hdK2TuhZQtOlqO\nWQrx\r\n=gdAy\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"c15f1432b4e8d12a13e2a20745f75b2c6780957d","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.43","@balena/jellyfish-logger":"^3.0.7","@balena/jellyfish-metrics":"^1.0.261","@balena/jellyfish-environment":"^4.1.35"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.160","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.3","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.1.1-renovate-internal-patch-c15f1432b4e8d12a13e2a20745f75b2c6780957d_1623121531770_0.5698555013231241","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.1.1-renovate-balena-jellyfish-environment-4-x-7f10c16b50b595540ba527e5309ee7fbab352330":{"name":"@balena/jellyfish-core","version":"3.1.1-renovate-balena-jellyfish-environment-4-x-7f10c16b50b595540ba527e5309ee7fbab352330","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.1.1-renovate-balena-jellyfish-environment-4-x-7f10c16b50b595540ba527e5309ee7fbab352330","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"7d5eb91069e96a25c07143c09c94eec568b9206a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.1.1-renovate-balena-jellyfish-environment-4-x-7f10c16b50b595540ba527e5309ee7fbab352330.tgz","fileCount":190,"integrity":"sha512-km5p1yuRIok1piYBeZ633JPaF0Lkz07F9WMgrEsnAkemIC/GX4Vh9QBLo+S3880oDr0ftOCn3r1g3XJt+q9yxA==","signatures":[{"sig":"MEYCIQChcXwdxpwXLWvKl5b3JbkAuBCvZYsPcvIvcrw94d1EzgIhAIqP6PrqlBwr2m/llhunON0vuBEJsar3plHw0t06oCui","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":720292,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgvt7ICRA9TVsSAnZWagAAmREP/RgoHMkcy38eZ6JGFGNx\nkiPD9ZBi5pelKyrvAwu2+2fLIS1m44rccIW+XpfOPIhCpy7j6H1cfGzObXPs\nszHswSWkGguYdBFfICmXK43dv+hw/OPNEhx1nCOkHFND6Gr9EPzG6eCV/rQj\nd8f98pxo5UEQU+aPrtvLBqSQPvz2huc+yKxkb8gZP+gdYb9Jm1c2iPW7M8pB\nccPz7PuywkOCyzWbGnnmkQcQ/rSUhhrhNq2iAtWMQL6IlbuVqaly1fYwCTH7\nBFcPHdMAgFHMZEjpsMoXsByarLodIkPYmBe7puIgm+jl3bNwBVLtnVzjI7so\nX00gXpEGgNUG2tAUUQFKUcsWz/jCeL82Q+gUv1qdrCUOyoFOEVHy23QCqpyM\nFs4GFBeY12BGyw1SwjEDYyi4BZ5/udNxqyi38OLOS2vwagPJXzCzRnXve+BW\nSi6gQgpVMv3sZ3p57IGgt4JgNF2qVf81rcLG2osH1GGyoBFdin3N70qGmyCl\n26dmphKKs7W1O6/GulMLAs1Qw+PvuKVVQcmxDAienbf7qIQVI7rFFeIWgLnx\n2GLa6/UDoen38EMUa/WA5Nhx3B6vBne5T/5q2U8uvc+ejNVvfW1YG6+Ci+6J\njZAD8UoAhLn20EiAhCn+j5hyRIZ/L0uWMrj5Gj9aEtlwpI+wEy2s5Ep4d5Od\nQP5Z\r\n=x9cR\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"7f10c16b50b595540ba527e5309ee7fbab352330","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.43","@balena/jellyfish-logger":"^3.0.7","@balena/jellyfish-metrics":"^1.0.260","@balena/jellyfish-environment":"^4.2.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.160","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.3","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.1.1-renovate-balena-jellyfish-environment-4-x-7f10c16b50b595540ba527e5309ee7fbab352330_1623121608432_0.020501173505879544","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.1.1-renovate-major-external-major-6ba2fe486fec6ddd28abb829fd4603499857fed1":{"name":"@balena/jellyfish-core","version":"3.1.1-renovate-major-external-major-6ba2fe486fec6ddd28abb829fd4603499857fed1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.1.1-renovate-major-external-major-6ba2fe486fec6ddd28abb829fd4603499857fed1","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"774493e4e950ca26e0e43da616d3c34df7344dbf","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.1.1-renovate-major-external-major-6ba2fe486fec6ddd28abb829fd4603499857fed1.tgz","fileCount":190,"integrity":"sha512-CqnvOZ6+g9gCsROSjv66SSknNlL7ujJxfdB4wOog3lHPsiOnHkT+erEb9ASf0NA4ELG+Y3Eh821G7cxEwUkneA==","signatures":[{"sig":"MEYCIQDxg/KjvhWkonjD7j3khymtP6nPv+mM88YlxZhYKivQvQIhAKkckp2a+l1q/aDWcLTH/h3mnJ9LLx13mdo/I0gaoyUA","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":720245,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgvt8WCRA9TVsSAnZWagAAEewP/RDhWSoSDKuYAVSrcEwa\nlBSIPGmDFsaCDHNx8TYaSPR+pxR90ySopQcCQdt1VVltYSAxpHhHYnKsQ1SV\nUlWhv3cEX9jLKFR0HVFO39gBzZr6Ll4tKBrKfLHQWM2Pf15hnWYpLXTpQoHK\nlLYCL1ZjVMui2js8pzWhwqw1sUD7YIizkteCG2cCvyowfjlmul8DRf8WHdMp\nuhVlUnUtt1DcDx+EGgadBkQ3LHt4WbNXNzqyhkzkqqNt2A4jyHNgHZuOdEeO\nTAmqpKYKGd4Knf4QJoMQTsxikfD+cYGoCtknbzHKupM0CBO6rK9zlzMhtgWU\n6IXdwz/BHh840G3QS9UNXUx5NvaZFuuvNKI8c9cHxgGs8egdg3zPSs2DgFGS\nN/VJEynfLhnU7G32HY1X/diQguxlG5jl0ayp5S4g8gL0W/Ouw2ml1UCVQmTw\nop9dT7gzn4pTwyyVCSw7Im0PcidwmFHev67/O4b80Ec/RipQsVcKBkKci18W\nYyR6xtcvKY4r7KXVyCI+jFCghp7jFtJITR2yQHSUtpJeFYdHWi9gEpMpfclA\ngExP/vHaUpO8Xs0d+zdSEU0cl248uCgn7zQ0hvNm7FGPAb3QkIZ1Y+d7lX0R\np94VyYyhqtpI5w8AGSnY2RcazqXIrYpCcmwHAz6ThBxHQF0Fm0ydJ6+Ub9OB\nDena\r\n=2shS\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"6ba2fe486fec6ddd28abb829fd4603499857fed1","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.43","@balena/jellyfish-logger":"^3.0.7","@balena/jellyfish-metrics":"^1.0.260","@balena/jellyfish-environment":"^4.1.35"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.4","deplint":"^1.1.3","ts-jest":"^27.0.3","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.160","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.6.3","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.1.1-renovate-major-external-major-6ba2fe486fec6ddd28abb829fd4603499857fed1_1623121686259_0.3528026212438673","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.1.1-renovate-internal-patch-3a4db6793e2bd9fc0243ff30dedecd9a30732607":{"name":"@balena/jellyfish-core","version":"3.1.1-renovate-internal-patch-3a4db6793e2bd9fc0243ff30dedecd9a30732607","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.1.1-renovate-internal-patch-3a4db6793e2bd9fc0243ff30dedecd9a30732607","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"cbdd9acb5359fceb049053d28548d4f5137b9eca","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.1.1-renovate-internal-patch-3a4db6793e2bd9fc0243ff30dedecd9a30732607.tgz","fileCount":190,"integrity":"sha512-CleA+IeOlhQ+Lv8o5KxulbrKFs2q4iWZQ5pqRF2PDxbqWNMb/TiXYI1ndScfcCACCqqF2pXAn8GH4JqIdGE+6Q==","signatures":[{"sig":"MEUCIQCgWFDX4zPLblOK/g/LdepGfBPMqdGq4LUJi/Dk9wuglQIgbEV4jaJCWOV7QdLfqaCQMFkN0FC36hsAHBCLvwThpF0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":720239,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgvuBACRA9TVsSAnZWagAAt08P/08/OLzQxUbRsSZmV2Hj\nw0bwhCb1IipO39dCL/5u27h5HxLlho+iHz+7S8FSWeqrrmssztbE0IyzwXQc\nZyvMgbQJE93PZ7BqFzwb6Ysz7nRrbt7iK5EH1rAjrBuakSjlKKTHoDxftlSY\nauVoj4DX3pex+HQW0JVaPXQU1cBcj4FmOA6+HgWM2RmbesO7S5nqq3n6DbLD\n4ws5eHSHLWvSRAzdkfvPZ3wxdaAAvH/G8ziXLA6szyZPkepor4ufXBT6bAlz\nNECBu2s4C9wlusOVviyP0t4xWmRcnl6qscTwnBJVtmwjAzkmO92vIX37PI4l\ngTsyzT+SMq6Z7ovhCFV5cW3PUjIpJGU1LZvtad6OKsgrPwstecquVKSJisHC\nCs9F5aauSgbvGXsCBDnp+RuDl0OHGRyipaE4dq3+Ef0DX9v1Y136GovkfISE\nHf0Y/YQjr6/r+TP7qIbNCKQJioUXvtg+OwVZbNJaq0L2cvjYvY0597WF+KyF\nSgtQsKlofDbZybbEBC7cVuSpQy7ecBfiYb8xVThixm3ge66kGZ9/2xyPEPyH\n5DJHZixdQBeJGctGbDWhs6rxwffnLqs96rqmy52xNN7Jia0862pJ8elCASXo\n/sX3NB4J4ayKtb+x6yeDx9D3OER8MyenvS1dVjlgM+pL4bkWmdwZ9pnYOO5J\nIJAp\r\n=xTZo\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"3a4db6793e2bd9fc0243ff30dedecd9a30732607","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.44","@balena/jellyfish-logger":"^3.0.7","@balena/jellyfish-metrics":"^1.0.261","@balena/jellyfish-environment":"^4.1.35"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.160","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.3","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.1.1-renovate-internal-patch-3a4db6793e2bd9fc0243ff30dedecd9a30732607_1623121984665_0.9003585817544908","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.0-add-loop-field-ts-f961e784304b1637dd83cb426f6b709bd12b1a54":{"name":"@balena/jellyfish-core","version":"4.0.0-add-loop-field-ts-f961e784304b1637dd83cb426f6b709bd12b1a54","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.0-add-loop-field-ts-f961e784304b1637dd83cb426f6b709bd12b1a54","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"68f261d196588dec45924a416ea919d6e1280846","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.0-add-loop-field-ts-f961e784304b1637dd83cb426f6b709bd12b1a54.tgz","fileCount":190,"integrity":"sha512-/g6zNhDfg+MraHMrwo94Xx9qoitYzDgtfa8HfWKTfPNRf13UCIVjvNBcXPMt6bZZhw83esvdYpU97qE3JbIwkg==","signatures":[{"sig":"MEUCIQCvcPuMlotcrHli3IVvAqC7bQd0DbE+qvKzAM/HpFlAYgIgVnbZ9AP2Fl6leywmpIyhjL8W6WHHHxpdNSIw4Ij5ptk=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":723549,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgvuC0CRA9TVsSAnZWagAAp78P/3mIzSvMBd0tM4ZclLpo\nNXcS22e0ldcUQxATJI+CP2oRquErfy8G0apwuLvjumfqPN24g3LW2ScYzQmz\nSqMpPCjRASwizwO9phhXabrQeey6E3CxPb8RB2RcoMMc7qoqikNdLOWTQ6G4\n6AAcIhZNzZ6RDoIh6F/MT2o9132QKhrL/9C6/GBMUtLwKEtP9i+dnYKba37o\nl6aZz0YoQgGUbGlCUm/DVgi1P476aSXjwDfgpniKLi1Fmp3mrOhcL4PgJS6+\npKOxgM8iucHuX62Wn5oH16x1KR0B5oCK4oFaNgOIDZd1qKl+pIgcnqWYfv39\n/ZMECWKBdvsksAgxW+VbdNqupMMXTG2XmX29evuZUInIj5i5bRFDeTW1cm8U\nca6Vtit1BTEo3KW5Fghb91jZtPBvVmIWCI3x1o7gqcDQNhkPE74rNjQ3pKoc\nUwc9tRKxWN/MSoXYh3thU7HSzN5a0y6o1JxFeGD/3DafKPqPgmm5Bw0TQUFr\nEfrZu6YjzsXm/D3RlmreEfOspJva2Sm7iwPEwtzWKMsFaHrwd4T+NZR0w/Ta\npWicFI5sXHNEasHrWrI6bwYNJeVj5LcxE0Ok0S8r5MsZdpj01fCUjkNrxOPZ\ns80rSzXQrGSdNsQU34elXuFXLJyHxcR3PB4EFvrIrFTWSW61bwLZmJFZ/8Ss\n7V4m\r\n=XhNr\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"f961e784304b1637dd83cb426f6b709bd12b1a54","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.43","@balena/jellyfish-logger":"^3.0.7","@balena/jellyfish-metrics":"^1.0.260","@balena/jellyfish-environment":"^4.1.35"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.160","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.3","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.0-add-loop-field-ts-f961e784304b1637dd83cb426f6b709bd12b1a54_1623122100565_0.9208094542007941","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.1.1":{"name":"@balena/jellyfish-core","version":"3.1.1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.1.1","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d524e1b7384d5eda17893557107184353e35d6af","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.1.1.tgz","fileCount":190,"integrity":"sha512-wIXl1F/BUIUgqofl+0aR++ZHMjLEXcYbdO7AjncdHSJcm4wm6IKHTp4rCRGRROtCL6cAN/67RDt0wvq9seipug==","signatures":[{"sig":"MEYCIQCc19VJgn+dbaa4v/0/UM4HHvdaguG5uD+xHx+gxWNnmwIhAMH2LHnmqcDNFR8ZXSmB3outNzsEpCp1AjLNDnk+Wfs1","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":720174,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgvuF6CRA9TVsSAnZWagAA7uQP+gK5UEiHm7CO4pTHwbad\nIYADXGuToKvz7MkIMRxI+xgo5hskcDVP8JZKDtjaUE3J6j7qX3TpWCehTbYc\nhw60/myNPSE/VKlmwUrnaa51Jb0Yd4Ign/txYHh82NnR13ll3h7P3BRNdrv0\ntKmOuCzGPCI2bs6q5lyyoAA0BR/FxFKLCFV0Qas/9fUDzC0SVkxydcMnHO4m\nRvI3ZqFOrPKS1Dsn7F6T0Jx6Mf7cffwkNOXdNqLstB/rnegRbiG3s3Nzs+12\nOvUXzrnUjsWqNi+PJSHlfos1isocUO1CIUCV2UVQ//X4o9j/pwjLzA8dPEY3\nkV0INYVwTPyAguHbnqVXKD//d+YS3gO3v0eHgQLEHcJ5EkWsZjb1nAyT6HRb\n8nrlTq5SDPsWyvKw/S/VCNeOdp2dK065gdGxsAXHK+Rb7brE/lWOzvG9blVM\nXeOMckfRZZFBxYBNbGD6ZfDCsSlIyMpyo6iTVDIM54fHTUgFRDWGKWpuxIyq\nbI1Xf9mcZJSTqMRlztZoEK5nVj463PcI6cMhla7/GikLzFGbefaMDlKBQN2d\nnIJJqmB7pCbYybB+GEHfZ7ZqeZfsPcOqMOiqlKQ/yFbhF6VytLGpPywQ9Af9\nFZyrIeoPSVtyco8fn1bRBHkIsLvQNaaZ1h0lFGrHAXiT6K64aQE4r7UHIais\nn0EC\r\n=rkqW\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"f357c7064b0a190c24d8f1f432207d3fd7fe0c32","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.44","@balena/jellyfish-logger":"^3.0.7","@balena/jellyfish-metrics":"^1.0.261","@balena/jellyfish-environment":"^4.1.35"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.160","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.3","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.1.1_1623122298652_0.5708268132170689","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.1.2-renovate-balena-jellyfish-environment-4-x-90391ef1d142c1dd71cea168fb5f2ca3c8644d4d":{"name":"@balena/jellyfish-core","version":"3.1.2-renovate-balena-jellyfish-environment-4-x-90391ef1d142c1dd71cea168fb5f2ca3c8644d4d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.1.2-renovate-balena-jellyfish-environment-4-x-90391ef1d142c1dd71cea168fb5f2ca3c8644d4d","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5ccdf7199fc650c96de2b948b7bffd9dc140c7bc","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.1.2-renovate-balena-jellyfish-environment-4-x-90391ef1d142c1dd71cea168fb5f2ca3c8644d4d.tgz","fileCount":190,"integrity":"sha512-sC1rNQIUpHJXVMMFszChNAX+Ns6Fc/kI4AcjS1WG9t2Jz/cLGml10lFTeHPncrgVCul/KOwIQ72t0/1MTxwaFw==","signatures":[{"sig":"MEUCIQDgt17Y9yqCJYPMUowWmVCwCuePF2hS9XgVcCUz+P0+ZgIgPbJN5CNlWqqdRtOxJebHUCnXQv3JYwREU8anFcJQpDY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":720362,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgvuKMCRA9TVsSAnZWagAAdnEP/R3tPE+dcCD8OI7dnIIw\n16n7Pj+y+D49Gfa1SEe8HtrBooOeA+wvVrsQtGqyngu7rwD1Uwvyze0HAhRW\n2ojhkgkebUR23DNd+g/XXiKBeIavQd42kl+g7KHr+vKpJsq3HJI4hPCj+wSg\nlmvU4n+TRZW1Viqo1Yjj1Jw8D6wPzUQVcTXl01lqHT9cWf6Rr2PTgOQfPv4i\njkODUlprUmiIR5sUqunS6MyefpkKwyiMCjUVrRS0aDTlRhaP/P/1HzQMguAV\nN5jKPEq9Lmkq13dhp8JWW69IULhQ/l+nQ6UPXV56qweUZXgngC7O6p71HtYL\nsu07+nimrYKf3pvc4Ebl3dTSbge/VN8iProeuMDrkwjWUfjD1p3UwUsuBWhO\naBQ70Hm7PvL3C7Mt1YMRmhSAOc54ronvYnaoC61koWT+4mGXx5k6WlXZxcVw\niS+2s7ab3W2qry6Gjr9/JRDd7D2eBtKNqPvVeSwlyDvxRUF/2pvI5mfSRx/n\nS5pup2oXkfx19NlnU/T7D70d3CynfxCPGuP1x3BCpk6IEGpLo9tmXEfnEGrF\n78kno/4bBpp8Pqyu8FLzc9wryjKjxtMTpk7EP5J6VmKxseh8E4OHNFUZLjyy\nWJtlDcu/7Oq4Ti3AMEbI5DVMTx5vA8AkkiAhbFafT13WNRT+vcAmUy4IzaLb\napwq\r\n=WlRm\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"90391ef1d142c1dd71cea168fb5f2ca3c8644d4d","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.44","@balena/jellyfish-logger":"^3.0.7","@balena/jellyfish-metrics":"^1.0.261","@balena/jellyfish-environment":"^4.2.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.160","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.3","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.1.2-renovate-balena-jellyfish-environment-4-x-90391ef1d142c1dd71cea168fb5f2ca3c8644d4d_1623122572301_0.5755790330718316","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.1.2-renovate-internal-patch-07064257fc114f8942651041dff4be99e89e2f5f":{"name":"@balena/jellyfish-core","version":"3.1.2-renovate-internal-patch-07064257fc114f8942651041dff4be99e89e2f5f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.1.2-renovate-internal-patch-07064257fc114f8942651041dff4be99e89e2f5f","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"7c9fa54e775f966081a92e149eebefe29467224d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.1.2-renovate-internal-patch-07064257fc114f8942651041dff4be99e89e2f5f.tgz","fileCount":190,"integrity":"sha512-Y7dczblkiTKwb6wdccUDhIbi3+H2i6Ey7+xQxzZCG4B88ILkO8/TeKbVZLYE2Guq2wCBypE80y7f6y7tbC9QwQ==","signatures":[{"sig":"MEYCIQC5gpCm5Z5bfWm7ql5wQmaSORH2wv+4JSfPPg7pJnw4RAIhAKmukG5jkf4sJWST5PtAQe9qZ5lQrCvf9c1FSvfqmpi6","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":720340,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgvuPZCRA9TVsSAnZWagAA34YP/i/gpTJuuuqGOtWYsFCk\nTODJ2/uiZT6ZRUBGYYHvatri80z/LRdOWjBgh7Pk0mXzohZPN2CqumYCU7Sy\nA+j8XZXwH3EjpBZlLhsWIcuQ9pCJBHaJTH74Yqbe9OGx9kg/4uBgi/BgEjlZ\nD8t/xJs6lRpFDDCEmNxZkR06UEezjlsSCnpx90FfsZZm4zC+1YU3RcpaGJPn\nJmDXv3KPkvFnEQ3BpTeyQCkAqBOstwmVsYxku3WUTZPEZCPzr/s0hKZO7Iw9\njuZErt4PAN+grgram7/MQPHKq7QBpzuWgXDQEvPyAsU0S8MnKTO4VsN7blia\nVQx759DJSXvqUiRaGkyodpFoREfugQjZLvQw/hwVUIR6ZvBhuTvcAtn6kJ48\n5ZBZ1gVIv2amFhYDFliNpNIaiubqu/CXkK36PdVsVjpQEH/U7qNUrqjUhYSm\nq3GXbHnoW/V7yY65hwjTf6IyWvAOXrJlyEKfhVO3cqOE41KMyXJe2LjBSJ7M\nPPcGIMmSRCX05LvD1LfgiY+o9LsGiyfwQeJpU3vvgzs8441IRU3e6XmekCjy\nLXDTvuohv09DlZrWEo1tDurwZcfHVkUaS55Ncr7le9V6WqIbjkTPF8Ge4It/\nNyCF9xSxcpMM02maeeh/JR0UHYMqj6Qj0tCDTYcWtCp/WstqUF4F8aLcbwtF\njpRg\r\n=vxsp\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"07064257fc114f8942651041dff4be99e89e2f5f","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.44","@balena/jellyfish-logger":"^3.0.8","@balena/jellyfish-metrics":"^1.0.261","@balena/jellyfish-environment":"^4.1.35"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.160","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.3","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.1.2-renovate-internal-patch-07064257fc114f8942651041dff4be99e89e2f5f_1623122905158_0.60380719725932","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.1.2-renovate-internal-patch-e579a995ed0f2189a0c75680ccbda8006be5917a":{"name":"@balena/jellyfish-core","version":"3.1.2-renovate-internal-patch-e579a995ed0f2189a0c75680ccbda8006be5917a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.1.2-renovate-internal-patch-e579a995ed0f2189a0c75680ccbda8006be5917a","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"802681c0502e0440c641673f63f9795138e1b217","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.1.2-renovate-internal-patch-e579a995ed0f2189a0c75680ccbda8006be5917a.tgz","fileCount":190,"integrity":"sha512-GDbF+9o1oRD5GnlG+2LWwjT2vXMDHCUxxGT+PMZLFeEWUYx/VPZXXWf19d3uslYBogjuwmIEvlGbxGfs+SP48A==","signatures":[{"sig":"MEUCIBBUhxQ1wPIVpnEo6tCJVuPpDJIQhFfB13sRINbJkrkRAiEA7SuAVPwe5bx1ggklBRMirJ9PgAsZ+EnOl/Gah+j8hnQ=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":720309,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgvvdhCRA9TVsSAnZWagAAKBgP/2zNzRDuMYzhdBwj6x6b\np2P1UN9votYjAq2MqHrmT2EDZuHLdV5F8jJ1kGBjai+mWe6MillnQtjTLZ8e\nc3Zy8OhmSeKEnt/SyGbJFhcdsCGKsMCqUOz26vJRqTimjqyxBL5eu1tkZpuS\nnS0rbjSGg9M/yLR7dZm2bDi0zWNJohiDcL5Uncd/epoVs57c/eKOun/9zrqN\nXSNiRCkbGMybQaD8HbxuRtRgilfAxfDYBZSfZnSqmSFjKVpao2OX51hR3ktu\n4n/28qYURMMEcbZ8WKLEPtikZQ3MnvitN5RakVGlKUozlFVmaZpT02nvm7Fa\nyn1r4OxlksJJWK5Bg31BIivRohsPmnSG3PLvOGqpGLz0tF6NY1wdXX9RZEzs\nMugUDoALN/2gAKwhgdxktmF9N1nWjxje5GVv4MofcCgHziWvOP8FwJCNOD70\nVqPL1W9X+sTrl/xH/YZ2hhdNGyTAgMtCKnBD8nHBYuIQ9C4Xe2HIWKZPzXTN\nH4sALqTV22xzH7ERleawCwOB2DVa9yuSJncPE8AxNe+flsSi1OJ4JAEjUqyd\nIIgajEMyVbMvoYKvAOmpkm1jMPfHFdQjNSg1RNm2q1Zq0bK/TMKq6fuKheOt\n9wMD0YsCwLzJOHX7z3CAPDKJRm6UZtYlDFYAnRxtyrh8v5RzEE08ZsU6tWXx\nOK6e\r\n=KLLA\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"e579a995ed0f2189a0c75680ccbda8006be5917a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.45","@balena/jellyfish-logger":"^3.0.8","@balena/jellyfish-metrics":"^1.0.262","@balena/jellyfish-environment":"^4.1.35"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.160","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.5","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.1.2-renovate-internal-patch-e579a995ed0f2189a0c75680ccbda8006be5917a_1623127905250_0.8467772229785175","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.1.2-renovate-internal-patch-734536481fb41d0f41c8e50bb092b646f0904019":{"name":"@balena/jellyfish-core","version":"3.1.2-renovate-internal-patch-734536481fb41d0f41c8e50bb092b646f0904019","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.1.2-renovate-internal-patch-734536481fb41d0f41c8e50bb092b646f0904019","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0f2b514d1d454469b2f60b3633e74184e92af876","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.1.2-renovate-internal-patch-734536481fb41d0f41c8e50bb092b646f0904019.tgz","fileCount":190,"integrity":"sha512-JiffZ87TKTNyY+nuyfU6PN5VwocgtgY2qNIroTW8Mw+G218Ej+RkewCuF/hf/VJ+HvbQyuoQmXeAY0ITvW/68Q==","signatures":[{"sig":"MEUCIHAiZe5sIgISi47S0pwmk7c+FmYwMCG6eMAkZKGEGivqAiEA7DqooW+8S8k2SZHLJongBYTGnYIjsK760ljZ1I78pbY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":720309,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgvvioCRA9TVsSAnZWagAAvjsP/RhUbsk8WUvc5zS1Lse3\nwFL8IaJ+HvtfNSSyCuJxXa2fkIQIcyIiW227fHnLcd4oRHe+ncbq+tQY1JkE\nUIVGxsc4h2IxqIblCfyQbxGSM7sY8Ifvs2m+iY+AUs1TYjYqaIG3Hw/QXr4a\nkRCtzv85zXVlNEckCl+ZFwL5TkCcsJ7b87vEGvTWXrbIlXg4FLvWnTIt8u20\nBoctr4POgSEmZ67NMami8yuVHVaE/IyOQrauv+1czjvmI2JuuX9VgAAsdq1d\nb/Jj1VP2fdAkJ7rM9gF122AfR5aZr7R9RldilZpOFtTaalu8hhJGPhPtI1cv\nOvmpChJaBJQE6bWrVc13pbReGjMeL51IgmeWmVTnYk6Y8irPoPlNVU7CwRcU\nYFIIWRbPfHHBPlP/jngc7qdwSFqtAP1CLd/tkLjt67tpKeoYTzP/GzBNb3jt\n1eeS3NijgSNHe+65u3QIPTWyAITP8nbfri0yeoOvK29qULBCTzp71/I1kgLv\nh1v4t0quRk6sMDUpCTj9/LKh6BA9+ilERKt6+g1lpgBAIkjk8Uf475osCx7q\nhWYqoRLk0l5VD/DgmPqPyqFjM0jK6qhw8DQZ1Rxqhcmh3gVwNAzEYDTgsqg5\npmLLvuybtuZT4PEVvmVyCfDcZELPEBepI81wB69KC76Ikn1+skhw+E//3+OG\nbb/E\r\n=CqqQ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"734536481fb41d0f41c8e50bb092b646f0904019","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.45","@balena/jellyfish-logger":"^3.0.8","@balena/jellyfish-metrics":"^1.0.262","@balena/jellyfish-environment":"^4.1.35"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.160","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.6","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.1.2-renovate-internal-patch-734536481fb41d0f41c8e50bb092b646f0904019_1623128232366_0.9163025849738731","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.1.2-renovate-balena-jellyfish-environment-4-x-026dd2fa6c479d575858641651ec72b86b9d6903":{"name":"@balena/jellyfish-core","version":"3.1.2-renovate-balena-jellyfish-environment-4-x-026dd2fa6c479d575858641651ec72b86b9d6903","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.1.2-renovate-balena-jellyfish-environment-4-x-026dd2fa6c479d575858641651ec72b86b9d6903","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"52350e05c42c6bea88452f2c4aa92a7d02fa668c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.1.2-renovate-balena-jellyfish-environment-4-x-026dd2fa6c479d575858641651ec72b86b9d6903.tgz","fileCount":190,"integrity":"sha512-KtB3PiYnEZSXVgLWgK8xg6AFdfkgAdz45GI6Tz+Be04sfQ0Z8a9lZIlgq62ltT/NEFgvSgE9j1M1V/B38O5Z4w==","signatures":[{"sig":"MEUCID9wIiIXubliAeBe6eIdZnu6zesI8NXVHY0atDhp1HOEAiEAxBEhMWXcpXn8pYObpLWk/u9vYD2sL1n86EjiVnOQrHw=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":720362,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgvvi8CRA9TVsSAnZWagAAYGkP/j0OF4RMqJs7TBte5+S8\nVFtLL3RfE2VMu40p6iWAWJ1m9Fq/XfmFI7NnJp8ZMSV3/bY1Fhu3R2k4rVZp\n7QgAwJS32pPN2zIdXjnjvbq0K7zVzzOcpEDjo8Oxw69BxCa+wtZ7b+uQBZad\nicRxLKMf9+3cDbCAsg8QWDYksJx2+iGyJOvuCIITgAu/vVLNCB21Q1af6htB\njb8sg6C0NHGsDbhZM7Oublw6IFnq+nE8URhRsnUzTTqM0lFOxwdxeAnSAkaa\nr96Wcx0zjdZ5WxvWjTXCcChfeTQeC9u3qUqOdnqtDRp5ZqXOl0VSQubfMMqC\n49813hyLLyJWcktpPygbH8gVFTDNY+Lxs2y1EiOhoLuc2tV5oY9be4rFs7ca\nAL2dZRjVhyv1SPdJzVHKgtlfSC3/hEwrHUOsseAlv+tw0TuautVmbuakY/io\nZ9V7LGMsYde4IzEmYrF1mEMP73bC8vfTHPdKmytm8wKYRVJYnv3r/oM6QKNg\n3cQQkpy4ljvB3YCs+KrPJ3LRkmm5G/ob9YJxhF+YNvKJujZICRl7HVumsgyp\nnycB/cPKUAMeECKXhuWsVv9oqonFk926pxr0TV65MLtqRQohjtWPF7TtqcHc\nxJfwbEnG/Wzxy9Y9wO28PSVa1S0bSR81LIUimVFGZIsumlQ6AhKo0XSEAtB/\nHwi6\r\n=j6Be\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"026dd2fa6c479d575858641651ec72b86b9d6903","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.44","@balena/jellyfish-logger":"^3.0.7","@balena/jellyfish-metrics":"^1.0.261","@balena/jellyfish-environment":"^4.2.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.160","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.3","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.1.2-renovate-balena-jellyfish-environment-4-x-026dd2fa6c479d575858641651ec72b86b9d6903_1623128252137_0.017367176132046414","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.1.2":{"name":"@balena/jellyfish-core","version":"3.1.2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.1.2","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d70823c7e45d823fc36c1b5f8364f38626eb4273","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.1.2.tgz","fileCount":190,"integrity":"sha512-9iXyFijfsDRTdjfDT0N2Stz5lw92LycVoQ3Lh6OTPJtIwCr4fJ/Az4z26dxtn4HhB52SjhmLGegX/V/kjhfAdw==","signatures":[{"sig":"MEYCIQDwk99YW0Q4YvMLY7/8ppdy2UxUxQ2wwAnDY3Sr6xogzAIhAJwvTk5gTtzTz6lS/5GXqdkAJfNFmqH3GrtITNeUc4HY","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":720244,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgvvoICRA9TVsSAnZWagAA3kcP/A4BEusyibBnzCWCgmqL\nO6gHV6CJnob/LHYqWLU81oIIWHlGPnCxurqbf9Hj3a9PZDoN0Rmg46rKZW+j\n7ONwu9Tz08rTbvUHp1py53vukP6lKUiXLUIc6BY1E3hGCafzVmJscM3iOTDy\nbDAolyuIL76/Tu89kqxX13+onYhbsdmNgtstfRIs3iVaep7+rkuvQ78jWWlo\n8zs7/Fx+jg1jnW3orwofOK9pNPF3QR5tTCguXJNBiNOu4q1+CdOcC8mqLLQl\nYeZIRQ7eQe/zAYkGpnz7zrJBpPX5UJ7pbNvEMJBzcwm6YHLwzxnmk1VLjfHH\nNJuKfjSXqvLQ2x9li45XG1k69NHUTJEcY/pOBlrKwzufzSogWI2KavQ7FzTr\nq0CutuGmpSyVa/S/fs47YbtOtyyHt4RtyGlX8hBYFUAw+HP7BjOeJ5p5A8yn\n5AVXSdOjsUKZmgYjk5jpr93K1Pwe3QG5GVdlgtH8InsLmqD6+x+9U0cspFLl\nw1t5Ej/Wxkaul7JKmwvgLvo4Cvo/OrHJYyFnGGSbDcpvKIBzGZr8yp5SNwiX\niIge8NKO7F4hD7Gm9XJE5wtclDiUGo3gnL80hJR9p6x8SBxISJBa8NqUc9p3\nnXRyVtYPXvAPbTjX+RxLc61WAvvFr3l9DZDo48gwOe5AypiLEM7NKk1fF01J\n7C/9\r\n=Gak2\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"bb161761f94a3f46b6fc102327d04824907752e4","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.45","@balena/jellyfish-logger":"^3.0.8","@balena/jellyfish-metrics":"^1.0.262","@balena/jellyfish-environment":"^4.1.35"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.160","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.6","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.1.2_1623128584320_0.7820114283819204","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.1.3-renovate-balena-jellyfish-environment-4-x-dfb60e952274136fc5fe8365b057f60b6ef5c1ef":{"name":"@balena/jellyfish-core","version":"3.1.3-renovate-balena-jellyfish-environment-4-x-dfb60e952274136fc5fe8365b057f60b6ef5c1ef","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.1.3-renovate-balena-jellyfish-environment-4-x-dfb60e952274136fc5fe8365b057f60b6ef5c1ef","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f3709da9490b0c4950c0acc2f3bc8c2b75f53482","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.1.3-renovate-balena-jellyfish-environment-4-x-dfb60e952274136fc5fe8365b057f60b6ef5c1ef.tgz","fileCount":190,"integrity":"sha512-11q3l4+zQk6WP/86lNCzE2XLPno7Wd4LAxc9jvD8pyg0Qwu54APzUEHg9E//T0M7Po3Vrlbga5RkEhtCvw6yUA==","signatures":[{"sig":"MEUCIEGRfC2cO/1K/SqX+02UfwL5AGO2WiDuuO4wIUe76wYWAiEArbOu9b3DFJZr0Z6eRJM4Hpv1yPJfN5jQMJzYLBFp0ZQ=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":720432,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgvvtRCRA9TVsSAnZWagAAlB8P/RVYZwdDeluFZXy3o81I\nvUF4DKhE1PQtFHIFmaUFqvEmNYBAYD8jyvkIPifh3aRH3a0+470dkILgl7QN\nO5pnzUU3CPObC7fjE2P9ZfrQvdgLnsCkcF4fjsNeLtSf6J3VW2Y5sissk/dp\njmjFeAB1rKTAKh5Zy1mgVX+hGHrqDx2uNmyNM/wxCAXgaKBlaVFyxsn5w+Ew\n69mN9RAG9ICCeXTN3w74/2b9EfmH4/IkAHIy34nWJccSGVA+deeJP5KGBkk+\nGvRBExhUXgQCfMP72ATYGTs2gImeHbmCVHOli5hdzQBut6Jg3RgxO2pyqxp3\nWqp5zR2ucXBVoHVFh1DsjEhDGkT1Yhfyxna+Z3Y6CDIhG6FXCNvqo0BeUhd+\nbXqW/RRiBghWMhzrdB3Km6PNp51h4Ie3sdMgtThERX7Q46esvNaaxsPlkC+I\nl8iuHCXOMCl3TLKhHPXVVSPeQPXJyNciPwt1GgNHBE0lqYSYJx7s4o6bMYou\ny3zdkZ7vKqVekYuaCoo27QhtQ31qKgrbNak1vXIl0MGcism571P9Los06kQp\ntwCCY9R5VFVGkw7CBYqheVty2fpvAzMF9Kw8wUhdbUqJ7Avi3UjG3jgAG01H\nL14VIRblcjVyHHTIR4KE6fRDRL4boITfoUMbTKvgLu7PllBxkOaYEC0LTLDM\npyrL\r\n=Pl0G\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"dfb60e952274136fc5fe8365b057f60b6ef5c1ef","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.45","@balena/jellyfish-logger":"^3.0.8","@balena/jellyfish-metrics":"^1.0.262","@balena/jellyfish-environment":"^4.2.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.160","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.6","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.1.3-renovate-balena-jellyfish-environment-4-x-dfb60e952274136fc5fe8365b057f60b6ef5c1ef_1623128912910_0.7816387505747981","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.1.3-renovate-major-external-major-10bf066c7c56e2239168dc9e2b59840d08d41cfd":{"name":"@balena/jellyfish-core","version":"3.1.3-renovate-major-external-major-10bf066c7c56e2239168dc9e2b59840d08d41cfd","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.1.3-renovate-major-external-major-10bf066c7c56e2239168dc9e2b59840d08d41cfd","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"69a454773be76ab255e3423a7c7b6da3ce16a304","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.1.3-renovate-major-external-major-10bf066c7c56e2239168dc9e2b59840d08d41cfd.tgz","fileCount":190,"integrity":"sha512-AIpTlFG+Js6pq05cDICix8PT1+LUuhSSaQPl278Mteqv/d/7gp88reLBpP4xmnSUp2HNqJas7oeN1qnLw0C94A==","signatures":[{"sig":"MEUCIQDYSyjLch/cR0c/Np7U/zXhANrt4R8Qr9DPUfqb7+3DWgIgWKthuYDMhsPT3VnVvtG5deWweuIp+YGWZJo/aOyfz48=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":720385,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgvvtvCRA9TVsSAnZWagAA2AUP/1WRzt79kcbA8tnnHS2E\nCMLjmI+akXPX8qJuUQUTea367FwuMXy1GRBBrzle6ZtB5+u3pYtE/18A6nwl\nM5s8XNf5+yalgVOjYGRGvA7BeP4R2qTyut/e9+VDSyaM28egxOM1Nn1G/dVj\nZpudrtpZzfgoGa0uMXEn70KuL9z1lc7uIwaHKu/7G6cdyjLTy844W1g4qqVJ\nt7vljlL2ENSiqVEGt7XzgxmL5FtrCSG0CNB3ePbxbRwXD1KTTFeQcXa4NaSi\no+nolKjStNf8wN1kwfioLkml5sISxR9SRarpROXWAZnqTC1PxiLT8C7K22Jj\nX8GX8JoYIohtVv5LTqK1FhWUmEurz/2cBp+cI319zSUszZPGqyjb/zCnaJqS\nvZCLG4IRijdI9SnjbjyXLlEjqA5idAmRk/FOlFoFOr07qyk35uU4cdsUFxgQ\n34tDhST+06JB3ugfl3EKmGHNMJDSq75gK/jEEVDskRnR93FVTzji0k+n83ux\n9ktyyAUrLj8MP+mGAvpqFOQsml3nrgjvi3i/4CmXok8Dwz/T31nbRIdbw0s9\nVe/MnXG9+Y2z+MGeMgSvcCqXfQDHmFSICqvI/w6b7j+Gh+3LNvDtJjYPuCgy\nZ037R8d4tNHVYYX4ubLNo5OhJW4+Owb/JEW5YOkkXGZICOgZQW0fpitw8+bR\nnPBh\r\n=Hybo\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"10bf066c7c56e2239168dc9e2b59840d08d41cfd","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.45","@balena/jellyfish-logger":"^3.0.8","@balena/jellyfish-metrics":"^1.0.262","@balena/jellyfish-environment":"^4.1.35"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.4","deplint":"^1.1.3","ts-jest":"^27.0.3","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.160","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.6.6","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.1.3-renovate-major-external-major-10bf066c7c56e2239168dc9e2b59840d08d41cfd_1623128943679_0.05074838019590966","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.1.3-renovate-internal-patch-8583639a48577ffaa005c5ed8a7b4284c88976c7":{"name":"@balena/jellyfish-core","version":"3.1.3-renovate-internal-patch-8583639a48577ffaa005c5ed8a7b4284c88976c7","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.1.3-renovate-internal-patch-8583639a48577ffaa005c5ed8a7b4284c88976c7","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"311cf5f29a58142ea00a4bc25ec5c1308fb94d22","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.1.3-renovate-internal-patch-8583639a48577ffaa005c5ed8a7b4284c88976c7.tgz","fileCount":190,"integrity":"sha512-dvS6mbPdWdq7ZbtepFwizDwFtYgAwtFUAAq8m3DeGb1mgv6+dVgk4GvY6T0BJO6lE2WvFO4tK4hp2JngJtE7+A==","signatures":[{"sig":"MEQCIHzpWGdt6hG7g9kksi2lEYXAkbrWzTfYl+i1OJDnp944AiA95wsspd+tbDXeiIA+WOP6AqStayYHdTjbo6N42yhOsQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":720380,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgvyLsCRA9TVsSAnZWagAA/x8P/RvjmLIJxsUV1Rj+Xv5o\nLg6Ca2Z5ie8RZ5e+YA5e3sbGCPZVjKeMJ3sBzhylRY9XW6aEynMTAzdiAsSG\nPJImWaOoIktuoticXBUqqrWT2FmonAjl+W3mC3W/3H3dujBtinYEX6hwZ75R\n3+sv2PZn4numOOUM9g7U71dB2MrO5VNCGe9tW6/FUpxR3XFSnysIzOfGNw6t\nT5hGCScoXpwOeenNF7DL9iZVsl5M+Zpp8Y6tT5qb8Ytya1BcYDbEUxEdD/Bb\nuhN9MkCvNd17lqWQQF0k4/7kmgwsuG24iJ03dO9O2KxDplzJ3gYYHWeP/Duh\n9iNHR0QnfCVkruEsYUsnVQnX4oJNRh4qfKSsEW59gPchyfINnbQpR9xCSN0P\nOVILBMgEtCnvH+4mwsW7Oy/n559KdfdryW+LglsaO2pg45yvIu4hUpwaD838\n48Z9JEgWeNdL13quVua9jBruBZ80O4NmDoPTFfCpJtMC3I+Dl3BHR9ux1sDv\nWXDmH0obZ+3RyyNUr8RUPoNr08M5iXSbrq6xWPp41CxowB6WAdQD0ZZAQAFC\nElg8Qen1MHjil+3EC09SHypdS/DbgHI/O6Nc65QHWXMTJuS7taJhUIdE7uY0\nIKsls5wrt9LjNHRC+XIbUL9/VAx9jKH8ANYe0VZNOFfBD1Or65I2psnQ9BCy\nN/ep\r\n=Y3nd\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"8583639a48577ffaa005c5ed8a7b4284c88976c7","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.45","@balena/jellyfish-logger":"^3.0.11","@balena/jellyfish-metrics":"^1.0.263","@balena/jellyfish-environment":"^4.1.35"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.160","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.6","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.1.3-renovate-internal-patch-8583639a48577ffaa005c5ed8a7b4284c88976c7_1623139052669_0.8218025625180387","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.1.3":{"name":"@balena/jellyfish-core","version":"3.1.3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.1.3","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"16d198623fab53d7e96cc112c9049e389ca13208","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.1.3.tgz","fileCount":190,"integrity":"sha512-PQZzCVle3eBbAvtLGQiemkO2Jm8MKIxj9VIBX9WXk1QGYHHOc9SZ0NbcxPKLLwIoAGpJhVrPwFmUeb5MfotWSA==","signatures":[{"sig":"MEUCIQCyMWkDcH/l8vFIkHiMbysTFMOtTFVjA0q9oShWc/yG+QIgXR4+z9SAS9Y2lObEEIdPEWsHNlp1AKtgtCEmXwFmpWI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":720349,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgvzVVCRA9TVsSAnZWagAAxegQAJoCvy7z1wxutPzp/SIi\ncnoxOsvVae8Y2pT5mGLTdw2AjLFojRYE4fbyf0X503m5vA1H3m0BVbh0/uFE\nO57M+QHHQ5sPZ40MMkTl3a8orgVXulWbUjjy/hmzI8PcNHGP5MchFRWakVSX\neuinIqv4Z6h7kygnDt+4+dP5vWGY5HFUE635uTT7SQhGP4VVaYQZ/1Q+xWd+\nKG4Mneu2qHmCGZ/bxrtDhAAqA7ZI6/PUudWa4U6qSTRBB+tbyDghlvzyaMNL\nkosHNoKm7AqMqCBTckCiO5OO584VBAdYrPIwqR4OKZJwyrLRhDA2H7wFyM14\n3L1EVJwbeBrEOQTraAv6IbJaUtC6tZOj+uQW4l/P9FlD3BbFT8eRcbsj6N5W\nZ6f1dGD7MwruEMuUfuq/C4W10AMzaL8oHRC5X9Htz761k+2YbBSE/Ql6Kdrf\nT0xrLb92l1o+PeaM0CDQYONCJCOafG3zLW7gsFpUfYaZvBOMgZ66bhCPav1+\nU+rnTZL4z+X7RH/pTZ/hY9ID/dbZWZXYAHDrK32+h7L7SKo/EdEZanriA21s\nTUsDyEonwdX1OHikZlVHQi8ad0TXhHOPBL1ATOULClfb/GH25tt5y+8/HinD\n2UAEdfH0RPX68J4kvtI+3+1iUZCkoE/ueTfOl7bAg5Jt9v46NWOTo2X5MXn+\n1tZa\r\n=vZ7P\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"a9654b7d716be105eb3399293598735e5c1d6a01","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.45","@balena/jellyfish-logger":"^3.0.8","@balena/jellyfish-metrics":"^1.0.262","@balena/jellyfish-environment":"^4.2.2"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.160","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.6","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.1.3_1623143765620_0.7661077028295238","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.1.4-renovate-internal-patch-dad7226aeb041746ffc23b66ee85683d083ac540":{"name":"@balena/jellyfish-core","version":"3.1.4-renovate-internal-patch-dad7226aeb041746ffc23b66ee85683d083ac540","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.1.4-renovate-internal-patch-dad7226aeb041746ffc23b66ee85683d083ac540","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9ede178013785e4684ba643d5ff78ccb150a5d50","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.1.4-renovate-internal-patch-dad7226aeb041746ffc23b66ee85683d083ac540.tgz","fileCount":190,"integrity":"sha512-aXWjZ/W2y1BD9Yx9hw954mXBapB9hdYo8DeWyKDqBepKsGzB2u7SUlipzFdjYPxbrBvPX6LY2SqlQBlLU/t+OA==","signatures":[{"sig":"MEQCIH4YmOwZoEsZCSyzw2svkz8i0ku9UfzchrkRvogFHHivAiB7PCUZdfmzsCoedjuXSsKd37Gg+twupeh0havga1glwg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":720485,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgvzZ+CRA9TVsSAnZWagAAxlcQAJI8vSdL6mz7FkcIxl3V\nCNrZB1xV+sy8LeIiXLuv/1FMgc5fy1MrZSv7Us9DcBx55WiAkIjlIykEuqB2\nksFskkIOMyOamFu4laMJ7rv27YBN4KpJEw3WHutOSLVBJ9xLVeNR9o0GiolR\nbg3Gxknz+ZN2bT9V0Is27dDXcTx/JhajSUnnDUPbbc58/cRanv9E999w3/VL\nGtrLwAxrMKXraVaxq3+1/qHOmCClV3o2sT1cPkcZlbwhct+zdgF9cdW8iugY\nvR4fSAJPvDOjR/E0ETVsuOAV6qkzurs21MzlY3QtKm9l6OXk1kFEFEYRpTQC\nLM5wJwfpG7/DnAu9/nzpmpCgCATq8otzz5fJxc8on6/z8n2BhwchtjgduQ8Y\nepOzESKUz5YhdCMPDMzBOTSfJ1uN06y+Sle0fpDKaXEwP8TJw+EC+TthjkJT\nkbFPKH15zRAXbq7rIUDiPIEAkeu8Z1tHlru/eSZZURy46tJlLKJtE3xPIXHL\n4DHwzID/xnsO4IuSHb53eeolz+/ELL9xJWb47Ni+87oZkI9jsNbvm9s0C2Jp\nkuGAHvsC+Vv9QKVv1OPDtO/13b0BdcpDy8L5oF2nToe7F6mu7XDidnpZ2S3Z\nHryf9jH6HDuB66Rr+bxpIbwyjPBnOTfj5qpTZzf/QfxmaFBBVGCUfkSVnKLm\nipb/\r\n=Su9X\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"dad7226aeb041746ffc23b66ee85683d083ac540","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.45","@balena/jellyfish-logger":"^3.0.11","@balena/jellyfish-metrics":"^1.0.263","@balena/jellyfish-environment":"^4.2.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.160","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.6","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.1.4-renovate-internal-patch-dad7226aeb041746ffc23b66ee85683d083ac540_1623144061894_0.8290766958782918","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.1.4-renovate-major-external-major-1fc57b5f8cef8882bbdf0db11ae663d8f7b805b4":{"name":"@balena/jellyfish-core","version":"3.1.4-renovate-major-external-major-1fc57b5f8cef8882bbdf0db11ae663d8f7b805b4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.1.4-renovate-major-external-major-1fc57b5f8cef8882bbdf0db11ae663d8f7b805b4","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6eba2b4dfe14b5db6ff6c557c47ba3971d49f1eb","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.1.4-renovate-major-external-major-1fc57b5f8cef8882bbdf0db11ae663d8f7b805b4.tgz","fileCount":190,"integrity":"sha512-6oZHAuhzvxqy8eNhPYRyWFG2iZFt1HOort+Xlzg7/Z1/qT1+DMVV3t4VNYqF1EMTw0aybcG6LuWYhgahpq4zLA==","signatures":[{"sig":"MEQCID7Uhtg2bqXgD0zh8Hu+o24x/wrRuyMIFrCvhgO4Fw12AiBCHIfdB/B/eSXfVB50bPlsG/8IgYHXa5zDg94NgM/vFw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":720490,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgvzbjCRA9TVsSAnZWagAAUtYP/2BUeMcZsqR8zTSS9D2B\nxDqbi+r45VrjtyySeJoC1I6L8hxMwXgqxRvRp1mXrOvacVdL66PeXrTO95+G\nKxn4BMAkOVsolOAPXDUJ1oMDfhH5wbG7Tif3fzOF/UVD8fjQs02xoDyjwL9R\n1RZlJBX7CD4dLHHbA/quiu/MC3qEmixiFJikCay3ltMuZlho0yIWHClu5DCn\nDCd0hIT5OqIXK3k4FVqFg/ZVbJ00y41CMG1a1uPo2A/mYhB7A9ahwHnXRFhy\nvUjN+tj1qO5fOMu21d+5Gy/vozUCBPa008eamwAqudFqcDjedDo9gfMov7uv\nciyv6kgG/UWSZSiDQLX4rCPQFz+F+NvEtepJQzcrf16Y71HRlIvbfGYVJeqq\nlAfqm/w/WVAVzaGH7AF68mAARegjSm0apBVE023Rvp/N/+6LG3lAhAZCBKA7\nAPjNcKxWrWKb5Vg75iw0PzS1WKvcQlv6cvkkwbcyLpFc8qwcvKGwFr2hxA70\npv+U0e08E/b1YIA7HiitJtBmzIr67yOnL2iKxqs6kjXo4A77HFYo8bcbsY2T\nUrh/BhvOqlzMEkYlhmzGwjNbnUINCjAsMOgSUv+m5vqfK200nSRrekCslykp\ntzD5yWso4sHTLZGTB02s6bOzfKC8TVZX7/r5LwaWkOKqe8YGw9nid8SdcfrN\nLjK7\r\n=+zmH\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"1fc57b5f8cef8882bbdf0db11ae663d8f7b805b4","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.45","@balena/jellyfish-logger":"^3.0.8","@balena/jellyfish-metrics":"^1.0.262","@balena/jellyfish-environment":"^4.2.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.4","deplint":"^1.1.3","ts-jest":"^27.0.3","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.160","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.6.6","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.1.4-renovate-major-external-major-1fc57b5f8cef8882bbdf0db11ae663d8f7b805b4_1623144163496_0.24172556460972916","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.1.4":{"name":"@balena/jellyfish-core","version":"3.1.4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.1.4","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8def03883477447050f63da34cc31237b9bc83eb","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.1.4.tgz","fileCount":190,"integrity":"sha512-dCGdiH+zxeO0dzdAg5PLCldJbyqM2QPy1b0FZtvBUuO5nSfFtYkAs7Tu7uy5DCSwvHVjE2NpUayVK5kMYvlIYA==","signatures":[{"sig":"MEYCIQCf5UIocmyImdX74uwAA44N3bQMblJ61ouGySkRXyJc7AIhAPEo412THDaRXBmdIIUgPUWeJMtvc5VSBaoTGcE2ZiI1","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":720420,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgvzjRCRA9TVsSAnZWagAAuuMP/iVzRYHdfLGwIVzaqCnJ\ne2+Cs3iEybSzb2AcJpAsEwRf+tC6s9P9Eu7ZjTuoisKpQa37ncLx+kacrgD9\nOn3hWRDJINgwdX4RlsiCPAjObJuffXx2BB1Qe/eWAai/itRd0aKTQ/xWyPlT\n0Yee/KeZJr0WeR+XsSps+LdTsclJjdqtmxiMDMMMHLEgt3ho0MI5rx565m8n\nDBMiTXILQx9+ZH6X/IG0yScdAU3syfC22QgWr078p3S/WGpD2L7ZyrMEjUn8\nJBdrb6baJ9JnNWBekVpCTViuDLv4IJYBGqGNV38kU1gySCz0uNY1y34RJVg4\nmS1Ethx9qCcyzRhn7zviAtiKBJjyEH8hdytiwcBvQIGhf0QmDPgYe6vSv5AU\nGTkVyyjzXbm7ZgW7HnwoEARSNFIV6idejKs8b3h2RMeJGpW2p9OJKzTMr5K6\nYDNZ3Jdet+bvSC/YAmCEOa0PavdDbNaKU0Np4pb1IvTfalV/LeLb1nVpIgEj\nlAvyCtkDE2hPs++qKbGNxQMYJ26ad/jFbasb6EQkcQyrqCLsFSfopOfusYR/\nllXgbr12iEqiMHNhcTCCSK6ctSUFmFVVvb2Mh6Lj7+PsDUoZfrq/mrgBdGzc\nnzyi6wlkJ6Tz762Dbz4JAYV6nYxlQ+vU8Kr9fxs+T11eDg+sqgZRJShyz6Jk\nidsb\r\n=ivdx\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"df5846696ca2ab046e62eb32230600509182209a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.45","@balena/jellyfish-logger":"^3.0.11","@balena/jellyfish-metrics":"^1.0.263","@balena/jellyfish-environment":"^4.2.2"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.160","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.6","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.1.4_1623144657063_0.20919003339106657","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.1.5-renovate-major-external-major-d4a5b464df3dd6d4c38945e64d8abc5c248c9ed7":{"name":"@balena/jellyfish-core","version":"3.1.5-renovate-major-external-major-d4a5b464df3dd6d4c38945e64d8abc5c248c9ed7","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.1.5-renovate-major-external-major-d4a5b464df3dd6d4c38945e64d8abc5c248c9ed7","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5b25a7ac576214a1e8815d86059e21afa6736db8","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.1.5-renovate-major-external-major-d4a5b464df3dd6d4c38945e64d8abc5c248c9ed7.tgz","fileCount":190,"integrity":"sha512-PbJNSazEzu0+RMR+hJMFmAFw1QycL6Mh9MSajjTGKJYr7XdWuXu1r+THoP8kZDGnYzV3vOxsJRI2yMJvRJChFQ==","signatures":[{"sig":"MEYCIQC4xx4EsHTOPqSSQ8XuF+jjcYShGiJ6WkOwufnKVYseLAIhAPWnur16z5whLp5hLS7pNlMHjUmVt3ocII3mQqqP4v0n","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":720561,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgvznGCRA9TVsSAnZWagAAM3gP/Aja4Tv0RPi3G4bvQ9Cl\nvxz2fsFjzkXhrEyMWjvFZdOPuwuX5/mSFN9j3oQJWL5UKbCXzmlK8ZcsTbnx\nxYabDGHhnQhPj0j9D2nzzYnuIlgOUks45B6tOWUTG4KohH7/sbamaFL0K37U\nW4DwPBWwvEPnKGItTI5xwzMUAOA8HercLlqFOJlBdjcSdo0jJj9w8Q2wvnr0\nihbfsV3SAgquvmAuSZxC0d9Z/qdTpnTPpKaBtDGC3NGusbEEZL/F85pgcAlQ\n+oifMF4tQCQQGoXn7jhNUC+W8yefI/AAWcgqCVF41bq3+MlfSzyusdlYB27Y\nRCmwv4yifgsHyf2ozY5dqtRMSWuo0M0DQ8DK1eit4trxy6h84dEhUmSpgleS\nD6RJk9cGo/Ndo6SaG2Pcy8xmf0LJGDxkbU8Iv3F4/mCqRPMyn9+gD+H7prbX\npXMOpzwlupUtCJPYyKDmw7K3+OH/zsdUT6uOTP56hD8Qel1CdxKkc7eGyxxl\nQOD7LXQH1grkxibr9bbtW7VAyPg1aiawwUOywVOamDAW+W9ZOwnxrQADEL+C\noXNQwR7gSJ3NMioV7Rfn6PRgk6qYyC64L19YWovk2gW1VT4FprMjPrcPxRbI\nlFrGOwl9Dob1lrsW81v2AICOufhIe3mCS+Kx+oenQI8AJ7WGuhYa04hWizmx\nl/4P\r\n=24SG\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"d4a5b464df3dd6d4c38945e64d8abc5c248c9ed7","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.45","@balena/jellyfish-logger":"^3.0.11","@balena/jellyfish-metrics":"^1.0.263","@balena/jellyfish-environment":"^4.2.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.4","deplint":"^1.1.3","ts-jest":"^27.0.3","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.160","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.6.6","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.1.5-renovate-major-external-major-d4a5b464df3dd6d4c38945e64d8abc5c248c9ed7_1623144902620_0.3297096192169269","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.0-add-loop-field-ts-aa785a82497616fd2b2aad809a5371af0cc64dbe":{"name":"@balena/jellyfish-core","version":"4.0.0-add-loop-field-ts-aa785a82497616fd2b2aad809a5371af0cc64dbe","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.0-add-loop-field-ts-aa785a82497616fd2b2aad809a5371af0cc64dbe","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5e568bd436bd28b6cb3e3e1f09a4879fc374120b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.0-add-loop-field-ts-aa785a82497616fd2b2aad809a5371af0cc64dbe.tgz","fileCount":190,"integrity":"sha512-3CkBzcg1no01wJMhNeKEiFaihsTdp5vMKi5vQj3ffkS/bORMZzYBW+88uDG6Mwatl3+BISqIYWLt49fLDDLltg==","signatures":[{"sig":"MEYCIQCbxQxrgeZ8uH6iokGiATA9oHeyDIwTl0NDxw7qQoYcZAIhANBbFgYZY4e/MT61SM3jshqcm1i/R2XcrXExdlALtf0V","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":723865,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgvz/LCRA9TVsSAnZWagAA1g0P/RbYjD2f9s6U4PgdPZzx\n/P3HEvlq+7oFyoH/ZK8qi0IkSACX/09DWtjcf83G5DZOPpWbc+GmJsCbu080\nPHxdnJw2GpHCzTCFboEb0WdnIbcnbIsrF81yg+reCpzwri63iVhRP7N1BGz+\n8xxGoGvgGLEWGL1B9IKSiawK3V0uZOMc/fbuRjRMDDrVvOKnMoBJ014SVHd3\n9tAeVXw6f5rRHXkMPLY9sOfzPq3LDNn9F0toRBcZ0znWPM+II99GUG8U+h8h\nNGRj27lZSI0biFXcXC9lyXvF2TbzxUiIYwqcX8LWXES1KnvW7Xa+G2/Ol6pH\nU73uo/A5hcfwU+H9qoOBqUHuuSlt9BDCn78jSgP8Dq99v2kLTR5lRS5u/CfX\nnro6VBquxADbKRExvHniR7nRc7bqUNbNWUfV8WqPf0PQ027tvkgf9jGSYF2x\nl1eZKIdhVM/xBeV57aC1dTtRF/bwiqop4u7MBYGdqyiZdDMV0nqAuEnF8reP\nVcAeFMZwyNBZCRDIha7L8Lz+YznZ7mutTGmoGosfv/ECwH2cjG3jdHuA8sHl\n2iH+OvcsxTz9xaWyy5aiF9ZjAuiinsonGS6TFcY9zuVomil5SUotbNxGnuGQ\naKh6GXZpWE3QH+wION6Tgwlt84w6CkILOrQT940kqhz3zWPAsoiPcH08X7sg\nFaI7\r\n=hK3Q\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"aa785a82497616fd2b2aad809a5371af0cc64dbe","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.45","@balena/jellyfish-logger":"^3.0.11","@balena/jellyfish-metrics":"^1.0.263","@balena/jellyfish-environment":"^4.2.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.160","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.6","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.0-add-loop-field-ts-aa785a82497616fd2b2aad809a5371af0cc64dbe_1623146443322_0.9577932419623101","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.1.5-renovate-internal-patch-40519c9ac0822113a96f58cf0019b037a9979f2b":{"name":"@balena/jellyfish-core","version":"3.1.5-renovate-internal-patch-40519c9ac0822113a96f58cf0019b037a9979f2b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.1.5-renovate-internal-patch-40519c9ac0822113a96f58cf0019b037a9979f2b","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"fb876f338eafe2beefed64b8d194f2d457eba8b5","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.1.5-renovate-internal-patch-40519c9ac0822113a96f58cf0019b037a9979f2b.tgz","fileCount":190,"integrity":"sha512-xAGCESUv5SQtdGhnka+l5J/FOgOUBWpiQlL/XmWq8IEf2+6eoOrbPxnUnSevKW0rVzet2oVOMtVYplW7p/z5wQ==","signatures":[{"sig":"MEYCIQCMD38OA/CvfvxQxrDgdU1l8E77ztc+yh5XETLdTcRp7QIhANRCdOOItyvfylwHpLfSlRug4hQ2KZTua5FWf0eY3kMU","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":720589,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgwDVcCRA9TVsSAnZWagAAqJcP/2gqTpDJiS2QyNg9RvhL\nzmI/2FCRahoOGj6HiOG3bl0nDZajgR7T9SA1r9JdVnk+WR1qO1VINh64F71a\n29o2IOMKFtTwsK1DED2KRDAJ5g/TLaX27Bp06pmtQib2H/JoH7cEQujxr5or\nLrqea7X3WxjecKOg0fCfCFMVU5E8gbfVpUnY0rImeNp3v4EcCEB5JjnKiDkz\nuOrFuTiCki7vpEuGvZs8PT8iO9D96ObwMFbJorV6UKGnX4hE5EYEWJiHG/yO\n8Gvml3lUSxc9FXubPBauZi5DXAyT7Z9sDeJ35UX/29eXI0UjpoW3ieCwqhIg\nkUhufhXjhQ50aSNYeACWOZvJ6EGG3iOyo/6sJivFv6Ba9DBB4Eg05R4YPq35\nMT2fuGmgYWkfUwqfRAwVnnur5c1t3lV640EpOYvd85S0bBiX9Uuse+ZlRpL9\nyA6vdUz/OFaDy+b3XcC1nR/Y8hHwf8C7ToxF8KEoEBv0Q66mOo30PTek4oya\nfp8r8WlPrhnIF+Bad9XR7G+p1XuEGFuZePK5GqHemIxyF5pOC9JUaBMULB6H\n+gxUK5v5vacPcOw1/oLjpGzNWO08w48UCyN1gq5kSOU5wIOUzWdaZT+9Rqbe\nLyPQy42vbza0zlL6ReyLXIij3MHrZ6LNzEHa0dRMTTAaT7McvS8OfUWhJp6Q\niuAD\r\n=LXKQ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"40519c9ac0822113a96f58cf0019b037a9979f2b","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.45","@balena/jellyfish-logger":"^3.0.11","@balena/jellyfish-metrics":"^1.0.264","@balena/jellyfish-environment":"^4.2.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.160","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.6","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.1.5-renovate-internal-patch-40519c9ac0822113a96f58cf0019b037a9979f2b_1623209307921_0.23613443990220495","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.1.5":{"name":"@balena/jellyfish-core","version":"3.1.5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.1.5","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ac4893802fb989eb2c470100eeb6930bfbcb941f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.1.5.tgz","fileCount":190,"integrity":"sha512-CstT7xXO16XmH2wyRm7YtGR8+Xe79eVrz1xUj6GBTd+i5OtClaIRXi/1zt+/grPlGhRlUL6cliHcVmKgG36tZg==","signatures":[{"sig":"MEUCIQD1pBii0JirJj9fDjWE6ubT+Owx+ttnmBPJrwxDOw72IAIgWuQnbhWE9wQ33s97t33dZVVHZavXR/NOAICodtb/Qrc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":720524,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgwDbQCRA9TVsSAnZWagAA5VIP/3NWQtyJosAiror3QFtW\nVXRkfplC2bO47KZHe3FI1+JQf1MX65VoORZxbwi7RAJu/WbGn1gZ4XoXi/cs\nwUMG2wyYZdld65ZiKDG5gtEGQEBywzyftpsT49k1ToPzUHHet1p7IWzn4V0d\naXpCQ2HA6XSu0vIwMbt6UMvYrEdiwVGn/WdN6wcmnm97ONjQzHEvgj03qDga\nt93aPaFEB32hqK90q//cAwIMRmMuFKUrThO+LVpAsAR1AZe2O36tXpx3eQzI\nauoMyKoOZb7CSmgs1jQVy+/nVHPxTs1SdzP1ggo+qbXb+uPbNz44/OSdnMnd\n7DOS3uIFAcT9tJfIAlrcuVV/afD8AK6I1dt8bkiwygBUvY8TyX24D2imgazT\nL1YmbO3XYcPoOz4P9LagbL6uOUnogyP++CcJ+a3/9xa7BtPM5yFGv3VzulJ3\npJAXimFEkhcauCTVg7NejZ6QUkN0z8tH1maarki79Q+tI3PLendb2r7zDoQu\nTCrwhA8UXEg5/ziZ4EpEBzetR6MjX8BGJK6olCzUd4FEJGkJE4RgF7tmcgME\nDFVwHtZM8tRp0C9eDDzoFmS2wYbtGcAWxrFK+O0Uhs152TKUZl899ZlYux9F\nv8T2O5OrVE5s95pMHKAJwuWOcTGrZszU9mUFGi4kA16QvvqKAIViLa4ASBqu\no9Gm\r\n=ByDt\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"ad72eee942b1f68b5928bc37b323a4c18bb66662","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.45","@balena/jellyfish-logger":"^3.0.11","@balena/jellyfish-metrics":"^1.0.264","@balena/jellyfish-environment":"^4.2.2"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.160","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.6","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.1.5_1623209680072_0.20393195709430878","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.1.6-renovate-major-external-major-5e75147d71de080db6ce5e40dd882cbccce7cc07":{"name":"@balena/jellyfish-core","version":"3.1.6-renovate-major-external-major-5e75147d71de080db6ce5e40dd882cbccce7cc07","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.1.6-renovate-major-external-major-5e75147d71de080db6ce5e40dd882cbccce7cc07","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"13eee56d213e6b511e4adb9975fa9e15678272d5","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.1.6-renovate-major-external-major-5e75147d71de080db6ce5e40dd882cbccce7cc07.tgz","fileCount":190,"integrity":"sha512-KdEnD3mW96Esobe8LIBdHAmVWdQxUHnQ0Pph/w3xLSBahSPqhPDeqSDi9KYGSCe9nak1JzVGaSQaBWyweDh6Tw==","signatures":[{"sig":"MEUCIFE8lqKvv8d4GO3G9fInjTGVfjjLNy/WoO2EH9Jeg7LtAiEAyTbMr+mQ7O+U6WYzGEgqhrb0Iomz0XWz0SAkeOB+HqQ=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":720665,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgwDfRCRA9TVsSAnZWagAARh4P/iZPFyde+iTn0VYRJF6z\npLAHwrz/WbuXEiLAlodofXBffkhIKhxRv0xvf1LoXkH5FSbNWHc8gR7/GnCy\ng2JyCHAHqPZxhAzzqMKa1m7K2xZ+qWG/+LgXhdy7OZ8nfzBO7g5AwNOzGGEy\nTzyX7wFpMvUzMbFIsTzo48Xhp4FoI0gYniLfNRVNKn+sYC7qoVPuntHbwr0H\nch2hS3YHtX4lwpWCZ2Uie9QAbCcuU0IUxYTLqQR+2DLr0wlQXSSQxiqqIGTY\nX/a12vmCpkUsvn8L5bBt6EF4jV38PkDSouSFAejsZnL6BBuooPrs7fg2WaG0\nt4zFljsMulbM9in1Wgls7PrDquBQDcBd3zsOPxFhoGcONOE9QlVaz5AawjEz\ntSFsAZTLB327W/u2WGBBDBVmZE/1FkDtRuGnEWWEqlak3cKwUADwzgyF3C3s\n8QDN27U5AdE6PtJJNZ7483eB9lA99DCUU+OKuY3lNxPtHrPwbjxrZG1ZSWFQ\nSb7ZU3cw5ql3B3x9ha3Ca3H2nnJKmPLCYQdxBIGDJtjzRlsTvq056hUxvpQi\nug4htjQcnRbuu8I5+47s+ic7t3PiygDH5GS7d0Ba5V058BiTbl7p93IGihah\nNkferg1SMdXdBhCFRNZw6makUftnHSCMq/rT9g76J7Nkl6NWhTaZBJ82BV/n\nZxDG\r\n=bf3Z\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"5e75147d71de080db6ce5e40dd882cbccce7cc07","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.45","@balena/jellyfish-logger":"^3.0.11","@balena/jellyfish-metrics":"^1.0.264","@balena/jellyfish-environment":"^4.2.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.4","deplint":"^1.1.3","ts-jest":"^27.0.3","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.160","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.6.6","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.1.6-renovate-major-external-major-5e75147d71de080db6ce5e40dd882cbccce7cc07_1623209937451_0.0031297207307441344","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.0-add-loop-field-ts-99b7965b07e7e5566b9f24aa9295da8928841c5d":{"name":"@balena/jellyfish-core","version":"4.0.0-add-loop-field-ts-99b7965b07e7e5566b9f24aa9295da8928841c5d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.0-add-loop-field-ts-99b7965b07e7e5566b9f24aa9295da8928841c5d","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"be20381c6470cf4d69fd38a1b492026b7b6b17b5","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.0-add-loop-field-ts-99b7965b07e7e5566b9f24aa9295da8928841c5d.tgz","fileCount":190,"integrity":"sha512-2S86t6h/v1lGZpXjDiHUxLyz+dE1nx4tNjw9kFljdIdqSrAOLgqdSMxjeuBdI5G+FDtaK6T7GXT9nxyqW8VoAQ==","signatures":[{"sig":"MEUCID9sRMbWjU+vZ8AJW3w+pYT1FmPQMh1xT6AuDc7tNtEvAiEA62xKMm4q1tzXoePEdwGNWSwKCycvP2hzJ3vYMR8AhzU=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":723969,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgwXEzCRA9TVsSAnZWagAAJU8P/j6YZ1RwUyiYSPJEVcT3\nvZes47roVX7VpHydnCcsje3eUUiPFNzWDkucPqJlFP8LmD9OiplLH/PC84og\nwApbvl3ERHS+lruh6zlWKQa1xpm6ww+LGpEclwvSJngT0ncF94O6fH1idFVN\n26j8m2UKe76QmUiMCJLWQBlCUhugGE4Y9hqFWrOxiHw9aOIqdKIbJDKQjaFl\nm+VHAoA1SAUByn8pg/V2U1mayunH8y6fcfZxaZGECGhau/QfX08MXc7dFwR6\n2pA1kAOviCx8Q9dhUOgQEzVfT5g5IoEUQeZ4d6Vl6hP/QrSmJmjtJUyF7OxV\n8LgAo84HfNwnliJzzF2KzJFkjfspXC1vlfXbUuFm2KTa3kUkUGVhya456gHV\nQiyjmwi6wth1Gz5AeNBiaza46Y7imqgnNBGIVKu5wO939z8SIFz6PnPXZClJ\nefAbyP2Nz5DY3ZvRaxY5BV/uL3GpNL2mIm+49Fid0qoq5HqntFJ9waFsrw32\nvjSdJBUarSwFSpBtT07wvRuLLElhdjzEb2VAgWb99w69bOCTwVkRnnOznsqr\nOLnABY3cazJORK/HryWoObjWKlbx+Coxqvb8H70lQeHpIdNe22NtaAWKYzT0\ngdP7VVcg10KcfBv5D2E+j+Du/dtypS8Bxjht3jxF8a5z4fydw068ZgapaoRf\nio3C\r\n=QsOf\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"99b7965b07e7e5566b9f24aa9295da8928841c5d","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.45","@balena/jellyfish-logger":"^3.0.11","@balena/jellyfish-metrics":"^1.0.264","@balena/jellyfish-environment":"^4.2.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.160","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.6","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.0-add-loop-field-ts-99b7965b07e7e5566b9f24aa9295da8928841c5d_1623290163076_0.04031485742903129","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.0-add-loop-field-ts-c957d59d0363840e9116016f2f561bcfe888b9e3":{"name":"@balena/jellyfish-core","version":"4.0.0-add-loop-field-ts-c957d59d0363840e9116016f2f561bcfe888b9e3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.0-add-loop-field-ts-c957d59d0363840e9116016f2f561bcfe888b9e3","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5f45945e8102d85918c65a5a7a4a627f7d882106","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.0-add-loop-field-ts-c957d59d0363840e9116016f2f561bcfe888b9e3.tgz","fileCount":190,"integrity":"sha512-trq7JWEQTSEiIyHg+1NsvbM6k0wkEAvX/tJ6ZMAZIn7Vrx952FDbIryq7esgU+XeaCiru3sGKILIyVuK41s34A==","signatures":[{"sig":"MEUCIDi2IF7L45EZtcXOm1fUzOY+wL9X3CF4ni6fy1A0QKL7AiEA0m+Q8NRUsROwRl4a6N9F42BzT6JGXhFL/aqmeCuZry0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":723831,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgwr8HCRA9TVsSAnZWagAAN9YP/Ajfky4JjKucsSPSCHxD\n/h7HZPNfyT+kwkA6HqWQ7Y3GZ61z15BfDKdSuMoU2r73J+HeQEn9Ef/FNhqh\nOgg1UEtliw2y/Dez8X6V6q8v94gh/98h2wpcyD0KSCLx9fvWb3597uvIW7on\nirQNXN26ukfqqWzkYSqbZ4yMisThjCKKEBTPmrxEt7WdvTUaS6VVR1WwfdLD\nZ8wZnGr1SgT3zpJFrrfqBlQ+Zs8QemIdiI6MwR7jTvbFHKlrNZmwHENi+TPJ\nvw4kqQXEZI7vJ/bACCPXqs6zGcOt4O/RHmtie81INoa/igqde++ZvOFn6nlr\n0ESyydf5eRUA5bcX9V5HfzuWFwqZkw/1XbfAziqkte5cRligt+2ACDe6kxur\n/IKA9oGJD3Qo9UJmvP4LYabV0XkMJwSY02bqSooBme15A/oVLzwBAuCoEhBE\njjXGY1TnUkuhblbZ+SYnnPvVjmxENXZVP8BM2X5Prza+re4bxnr9OvhYPINg\nC8RWudWrvwH60tZHMISkpCpmIKXjwO0YYwzJJ12bohtNIenl9a96Y2HE3A3D\nx4lZG+lvk/kvKPBuvl3E4OQWeFZ+zwYqNoqd1HXToOib+TMfBqRWyaf6brnm\niNgl71ErljelG4Tb2UXG5EHgdLUNu26UHcbQNFmSpzSVLqaPk76qYIG6AoAg\nbYLl\r\n=Iuq+\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"c957d59d0363840e9116016f2f561bcfe888b9e3","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.45","@balena/jellyfish-logger":"^3.0.11","@balena/jellyfish-metrics":"^1.0.264","@balena/jellyfish-environment":"^4.2.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.160","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.6","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.0-add-loop-field-ts-c957d59d0363840e9116016f2f561bcfe888b9e3_1623375623091_0.38385677115723893","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.1.6-update-docker-files-dbe1298a14a7dda06521a40f502c47ee688ffd81":{"name":"@balena/jellyfish-core","version":"3.1.6-update-docker-files-dbe1298a14a7dda06521a40f502c47ee688ffd81","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.1.6-update-docker-files-dbe1298a14a7dda06521a40f502c47ee688ffd81","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"cb80c3aa2c9a203fb3edd394764cfaa74686e076","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.1.6-update-docker-files-dbe1298a14a7dda06521a40f502c47ee688ffd81.tgz","fileCount":190,"integrity":"sha512-V1esrw6nzKsj9gXZ2x+XFbAkgr9GKkW5M36sZkJldCQOkI602bFIXdcY3SVyG2Pow7eVajtMOBxg+YChgNfzPQ==","signatures":[{"sig":"MEYCIQCocxFNGTiVcjEIjQbIr7eMO6XzLk3WqmLTxLcZC+DTPgIhAPoWndSAw09Z9SVy/mXa9sUa+cQ4XEi5Daa9xFomxZU9","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":720675,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgwsEKCRA9TVsSAnZWagAAwR0QAIaNAd7LeZgLuA+czGZo\n6WkolnMtgK0wfSjTfij+g1mbabWnODNz/fOz+o0WKM8Lrun2To6rR16FiB4f\nbfLwLd7k3264uyPDoVORYaoluU5kvuBiipNr00RpWvbbYoVTBGRDTdTp/HBD\nqJIasujvyGxG6O0cje4YumKoy3MbsxD96q18jjm0lYxowoszVYFfn/Dj+G5z\nxQpTUUzkyJ1J7G7ss6EshhG/rroqJ1P5/3tuTocDSE7CSec+a1sInqYmS9gr\nkHZcqPGMgstA2dHOz1WfUoPJdjqzkJX3UXtwi0niOa8XfMY87cJhBq0BVQCq\nlTjUjj9bshaR0rZDhdO9asaEss1sUfdkkTBYfMWoHEDfvYJuN83xUkJo6Rtt\nMEFgSTh0I9m4Ja0vbinnrNc3HcUZnY+pON/D9r9FRKMZqejj5g59vzt331Tf\nFfzMoksEAhDSfQW+jhKuXoJPfkNmF8s6ZQrOLui+j95ggb/uWHv0r4FzIj8K\nVfVM8oTCNP9gEUeAOuW4Qy885vQqEbYMByN1SdkrN0C25zK2ixcjM4mrqlCO\nVgjWlfk+Xbgdr8DS0WJy9yFMz1lx63bVLZNNh6xUf7bob7BBDK4otZf4YrS6\nOPz9ESHNeT4pxdGW6i+81bGp8yeDZMb/7fwHk0WyfNK1MDv65ObBgKIywJPG\nCHvi\r\n=NywQ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"dbe1298a14a7dda06521a40f502c47ee688ffd81","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.45","@balena/jellyfish-logger":"^3.0.11","@balena/jellyfish-metrics":"^1.0.264","@balena/jellyfish-environment":"^4.2.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.160","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.6","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.1.6-update-docker-files-dbe1298a14a7dda06521a40f502c47ee688ffd81_1623376138494_0.37789613475596706","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.1.6":{"name":"@balena/jellyfish-core","version":"3.1.6","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.1.6","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"62ce11a9e88ac10059364d4f6d2f36b705a5b8e9","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.1.6.tgz","fileCount":190,"integrity":"sha512-1r7NqSlLV89lbfd1/QT+5Gbe4uSileWGxIfSAVnP14hdPIeD29Hzuaf6mp7KBSjMUaiE5WJcHogzU4IkNEhMRA==","signatures":[{"sig":"MEUCIQCFhf/kQLj5na+v0DU7kB8olMlwHqcEtYlWOhHmFNlBBAIgByOFVW2AqOC2ZSgXmdPOP+z4+YbCo2yAyAARQEy7YfQ=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":720614,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgwsUXCRA9TVsSAnZWagAADKoP/2WF1bMFiDLDU8R8Hc4s\nEw0Dkd+/494SW4F6VLbNiE3nTrd6gKP7NEU2BFudEtNajmMzjJz69JioNdwd\n1TLLB8VxeFYdlsZJ6vY5sV7+t1XBJDRw6nWINqOtm1E5AvxWRb582MozDCt2\nntyUHyGveIJDt6xoUDQ2lWPpxUJ/DSV+6G8LPPovkt/GvMJmERZFLOBkvNEX\nAfcwefezHTQdsn+1ClEy/fqv/E+L3mORJaX/etXTXq5JvgAf6lOIynJNl2hY\nj2FjuHINb8AhmRNGxQF6us+catCnjwexMKI23SzwSOlTg0hhjgeXI/toKn+x\nSmfjIybcKf1mfovMg95qaGkQDBTcRiapfUMgUKBMW6G+7soTcPl5JI/zDWHm\nz1X4BEMqEnCz3PT9+AJJlqfaB7MmdVtq/EiUACuIHiGZb4rPeRL7G+mEMvkM\nRPOW/7g+/pmXTlUAXUniAN9ZIsvpF3ORSyFAA/2nWu775sAbIKhlC+NI/t1D\n9kOBGK4pYz8uGdFFZrlj3NgBlzEG2pJokVrAuxKxohEs0OzC8DsAFqe8vF8D\nre01T7LcwnUdaAG5uAkZ/8ntQ8gPlmXpgj9y8Us0JgfcB4YmjFMgan88SOGR\ng4TKdN48x/XDrXplDn53G9+BPDsla9ck1vDBE2971oFfmKfeM/8CqcrM91PD\nsO1Z\r\n=22/O\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"a4a6223b41c174337864933ceaafd103b92d55fb","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.45","@balena/jellyfish-logger":"^3.0.11","@balena/jellyfish-metrics":"^1.0.264","@balena/jellyfish-environment":"^4.2.2"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.160","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.6","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.1.6_1623377174995_0.8926743036274032","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.1.7-renovate-major-external-major-1de233416c53d43f8d69113ddfbd2236c568b8f6":{"name":"@balena/jellyfish-core","version":"3.1.7-renovate-major-external-major-1de233416c53d43f8d69113ddfbd2236c568b8f6","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.1.7-renovate-major-external-major-1de233416c53d43f8d69113ddfbd2236c568b8f6","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"26ed47327ab0ad947d4474fdb4ccd265a88de180","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.1.7-renovate-major-external-major-1de233416c53d43f8d69113ddfbd2236c568b8f6.tgz","fileCount":190,"integrity":"sha512-OFVM2hmxNrEu5N7v+2c+NLTTq3OSmd6eMoAF40ziopei+ViId76U7Qviv+odtD4w4F2EoO2D7ziXBfbsfHH0XQ==","signatures":[{"sig":"MEQCIBbb0D4edR68shE4+TIXtjkmn/kU8kyI9fNXyiKH3MrSAiAs9akB2jHhh/oNfFFGYP555SJZFhgVNs0NEBEONJP+YA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":720755,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgwsZOCRA9TVsSAnZWagAAZgYQAI7NsS6Vc2syy1zFqURb\n+2pY3EgCN8Lm3iOkHgu15adDAJD4tJVMY4nQY0c0etC0+OxqSp4xJnLgYEgj\nGK+CZOzdGjAtuOSLfE7Ya5/8xcs3GnxO5db5STHGXGiFIrF4uqXvQ1G8WSIi\n2WYfkPn3XUliWOTnpwYncxBKwkontI0WcnOIsJgu9qMr40vTxoxklHEVD0gg\nkSM27yyaClR+szT4MfRwuIMdaYC2BqfusuVx2p4WYLyHS4PUNkI54/W64jSW\nlFfxHkzfAI8Xqf9/HGwbretzL+7NDP4QVh6J0jrc8aDwJYDuPe/vyJ49Z/gD\n4JIv7dVmwLeiCqgj6eVj9lor/7OUja7PlnOvGvA45SfXegO6iSSDGdNQWGQu\nE1J5Gkbk4oGedxIlO0CAKpB8XzL9nPBal6psHZFK3Uy4cCKCV0PS/e3Ag0on\ne388nT7exxuTh4TlaMgof7jmpdR4GNLQDPrRQBUzwLrCkwGpeQeiAzJPcUQ6\nW6fzWSHbMe525eRoQ7Hsu6tSsmjHHM3ttcbdNY8UdbTR87PWLGZP5Tc3ps6n\nfnnd6EqOr4karIAxFDsRZcVUQMKmum/wwn5XXGdjmrn84gq6neRSTKDOYZda\nFA2kzbMtM7L4MaAOhLIgkvf1nW2aJLJAFPmo+Batf/vHWRxHQHBjJVIFJmkS\nbC6C\r\n=My88\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"1de233416c53d43f8d69113ddfbd2236c568b8f6","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.45","@balena/jellyfish-logger":"^3.0.11","@balena/jellyfish-metrics":"^1.0.264","@balena/jellyfish-environment":"^4.2.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.4","deplint":"^1.1.3","ts-jest":"^27.0.3","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.160","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.6.6","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.1.7-renovate-major-external-major-1de233416c53d43f8d69113ddfbd2236c568b8f6_1623377486083_0.7606296590231834","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.1.7-renovate-internal-patch-e6d12768c1a13b89526ae819e1d6ec83bd0a1f0d":{"name":"@balena/jellyfish-core","version":"3.1.7-renovate-internal-patch-e6d12768c1a13b89526ae819e1d6ec83bd0a1f0d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.1.7-renovate-internal-patch-e6d12768c1a13b89526ae819e1d6ec83bd0a1f0d","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5ff08cdd27e16463208a9159bf8e340c6d2c060d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.1.7-renovate-internal-patch-e6d12768c1a13b89526ae819e1d6ec83bd0a1f0d.tgz","fileCount":190,"integrity":"sha512-rENOa6ZnGKJZTm8QWl7tt8ocHaXU7yhgJzlfQmDzrgK1rxb1SgdVpupbVtE+T6dke2murjL8v7fSjNStPAEN8Q==","signatures":[{"sig":"MEYCIQCjIjKZ7Zs0z5nsyhMmnbH20IfW3u6oXqlfJOwxPv9OYgIhAP9EO5ukLpqpaxaJzmnlbUwBFsmWY4h06a+1IJKpS2nW","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":720749,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgwwRWCRA9TVsSAnZWagAAXH4P/2MbIpuM5BcxGVYQ9oiT\nBReVZn+1pKICHVb/KlyWoNZUnen0M1BLFFqdnFEX9v3ODjyU24wwmdtgadCR\ny+rmiewAnd5b5QciRyN//h0O3txCKBcnjdtrpC56JNUxcPGNI1duQaYTLidO\nZzn31HxxbsSFjq7H97r+lc9E3BfN7MNbPrQkoNdvOtloLi8R+dGDsqFaIq+n\n3zrDPD0v55vIQudYljlTCISRzdYSz11tE6wjlEoRiY9nLtHpX+Gk23ek0sXN\nzfRuOiw6F19AYl6znbqDcY1MBG6/YdgawiAFOxkNg+8chX/QnLHUosG3d83q\njFjtkQHSadZFr1gwg9is4QBbcWFPJsgfemFzFUCHBNEJhj++LBelI/qq0P84\n5VeElcbqA23N8KjHs4FwleSk0NJgHz87r4PqkC8O4DDVROj6YWqDnP6ZChsP\neY9U69b55mZDq4dfe5pfmiKnRSijOtRcD7880QxCLBjSMJ7e1tjfwsVv/NUG\n0gpxyFuyxcfjcbaVHp9VqFALsHh7gOLkAT79tWQ1+tjHwbYf/QsY//Aqch94\n5qEmqvJu0iLpcGFwBKA/86qKewfL8Vb/zrkWg3A3ihWrDgYnaEjLyYNCZgEg\nXMW5KCePvvP+SxCTYSN4JshDVyo7rEuL+OhMG6QVWc4dJ2IQzejRIxWYPGM+\nyuSz\r\n=2IY0\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"e6d12768c1a13b89526ae819e1d6ec83bd0a1f0d","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.46","@balena/jellyfish-logger":"^3.0.12","@balena/jellyfish-metrics":"^1.0.266","@balena/jellyfish-environment":"^4.2.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.160","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.6","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.1.7-renovate-internal-patch-e6d12768c1a13b89526ae819e1d6ec83bd0a1f0d_1623393365844_0.16323435782652518","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.1.7":{"name":"@balena/jellyfish-core","version":"3.1.7","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.1.7","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ced7ba704e791a573ed9f8ccd1b45436419d1e47","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.1.7.tgz","fileCount":190,"integrity":"sha512-ySJHITkuQJfzmy7lAqguiViHL9qjqQZ8iHu1YTGw2zwerhuI3WNy5IbpGKPUffeQzhB3B1oIYvYiyYbgcjWC1A==","signatures":[{"sig":"MEYCIQDgqKu7ScOPUCb3vI0o3QiTrLzyHJfUOfaNbEdoDdvA5AIhAKNUkBx1xBH3F1PxWakAlKX/WQPRL0VerozdvHHTRJTT","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":720684,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgwwV0CRA9TVsSAnZWagAABwgP/2cLkIJLcL+XtfbfxEjR\nHfoxZP1SoPDglk8UmInHTfopWiSwyXlwd+PU1l++xXaBjLq4qjjQc94nzVIr\nAf44g7OsZ/xAL1mzGm16PlW1e8GPk0hXmYvtYiAnoQ7/mr7j4t217fGYDFDR\nTWk0znSNIe1/KnsYN0zJtGJ98ZBxYlvuRbI3vY4SehO8u//92uAcMYgdy8T0\nZl3crZVtVYv9/uhlpbu5EWGYAwb3Mnm9PGzIX1GRTtH+oBkpDutIkgULMzpL\nEUUiilSjD23D7+HXBbAl2bgPmUTfB9KYS0I6ebtLmaZVeVJKbm9wo64nLygk\nPIr1PdfsR4gDnloReluO0UNnhdxuJgbM9EVs/yTZS9jkaFWnlJbQecuneuWh\nOxv6RS2sP8rXxr4nwLkidUCYEgOw6ohPbU1/nAm/b8F5ybF8HA4WsaX0Vmkh\ngOi79Qs+Y3xWTSTv9W5BcjtNUD0Otipj/0803q674iseL8BUg8PDgXbGSxA9\ngBtnSS2L+qXCLs+rdHK4OLJ5wyt2MvtdCl5WvSD3sT21kGe063buR4Kiqov9\nn8wk26BZGxQGSAXaVtdnddhGF4Aipt7gEPthGBH9S3NemzMeIUV8ElSOWDNM\n3O+5bCHZn3bxn8Fr54w8vByBSe2p6q58fQQDKsKMY3UbeYpUgY24lgfzS7wP\n8eUg\r\n=sCGe\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"9767286091f883387414ffdd4ff00ce06fb851ba","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.46","@balena/jellyfish-logger":"^3.0.12","@balena/jellyfish-metrics":"^1.0.266","@balena/jellyfish-environment":"^4.2.3"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.160","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.6","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.1.7_1623393652271_0.8281894152451625","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.1.8-renovate-major-external-major-3204299bb5b8bb748ce598aab3a5c572a655d3a1":{"name":"@balena/jellyfish-core","version":"3.1.8-renovate-major-external-major-3204299bb5b8bb748ce598aab3a5c572a655d3a1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.1.8-renovate-major-external-major-3204299bb5b8bb748ce598aab3a5c572a655d3a1","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4e407bbf914d7182dca8de199a12096333677e64","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.1.8-renovate-major-external-major-3204299bb5b8bb748ce598aab3a5c572a655d3a1.tgz","fileCount":190,"integrity":"sha512-IC2wpyhpKrG6zg8vcCzqUCceSGkvTxxdoHS5rJiPnXxp4Bv5G8I7RA9NcBPevXaI6wxuqlESDBsEtJabs7rbVg==","signatures":[{"sig":"MEUCIQDii9E2E7tK/EcutOjSX+dAOl+/0pX8N+1/JrY1L3A3XQIgX7EiAIf76PYqJK0o87MLj4ftSgQIy8MJfGnOqHXZX2w=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":720825,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgwwaGCRA9TVsSAnZWagAAz6AP+QBBrGBFu7y4hpd6bRtG\niFVRLLOBxfAm8VtNBs2rZai+XmVvawUVH5TZv6jT45rf78eKc4abcSlImxkO\nO2Ap8d4p8nKoNueGLRCaeUNQLpdybhAbt0nDbeWJ3sLUk9KvuRq+mAhyv6Nu\nUVJ90ljF6Kp2i9X5nlYMr47BCp69VtSRBeQ6vzV0di0kZgtQpbQfN8xEWTSE\n7kgaFEOxYaRSpWQatGVQJeh4b6of8oObeoVBY35uT6U7tK65nBGfIpNTQhjk\nD43q1j6hnsheKL9FuDk80t8LzWkPDtCUKX51R/Cye1Fd8Y9mD0XIdyT3Zpv/\n1zkPRKycYIFKCneFVoRlcbZoUR+Xgl4r6snbnvKPV/uL58nxOEo5PODo04xI\nIL4SFKa+BfygI3QTYjO+fGHT18imcV7z1rXAkNIZdASnAMELA+0UWjUrKuzr\nWXE2JmPe/GMW+d0Q4/dHftSXoMuzwyXC29neLLiw47KA/RJzYFeQH3Puw7oD\nOYBsm38uSnFAWpf+XBqt/VHtgcMTpKeJ2aPSW6PqHvIJAr04mviAhL27P4By\n/kXVW6WACYB7y7mEoYI57DEUZmB0XBYKvvx8eGqJog4pcVRuRelHnvVLv5xK\n37j7fi3q+KyVplyMZNxOxPvcR+RaGKcWs3QslKyepmdKu3yPpnyXe5InUB3V\nBsjz\r\n=i6fQ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"3204299bb5b8bb748ce598aab3a5c572a655d3a1","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.46","@balena/jellyfish-logger":"^3.0.12","@balena/jellyfish-metrics":"^1.0.266","@balena/jellyfish-environment":"^4.2.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.4","deplint":"^1.1.3","ts-jest":"^27.0.3","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.160","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.6.6","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.1.8-renovate-major-external-major-3204299bb5b8bb748ce598aab3a5c572a655d3a1_1623393925827_0.7168126243951074","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.1.8-renovate-internal-patch-d6392ba30b76aaf5a87077bf7ade44009f8160d8":{"name":"@balena/jellyfish-core","version":"3.1.8-renovate-internal-patch-d6392ba30b76aaf5a87077bf7ade44009f8160d8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.1.8-renovate-internal-patch-d6392ba30b76aaf5a87077bf7ade44009f8160d8","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8445f1cef0f04908f2f485eb036f05cc6a0aea1e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.1.8-renovate-internal-patch-d6392ba30b76aaf5a87077bf7ade44009f8160d8.tgz","fileCount":190,"integrity":"sha512-viabWfNW3+g8vnHDJzyYOwEAdG0GYjJS9zNC+hjc1CT36bGTsEanPwgXSoawpgOLlYznXlTzozmqZPWEjc9/Yw==","signatures":[{"sig":"MEUCIF/gs4mhhipHfmXiaVyTLqomRk9q38mG0roysSKBd9MwAiEA9IQXgDOvgAGsOM64wpLJWnHV56TcTKn/gr1nluh3FMQ=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":720819,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgwzBWCRA9TVsSAnZWagAAi/QQAI6tn9HGzox3G0po56/7\noyXn1IEOrPjC4hnORfdqnHeUyrPa1yJvjJA8labjP4KfPWmXEMyrSsGuMRal\n7CjtUMKkhhYSYAlkwn78PFmIHvUxAjLgwis5TWpGZIgkO3mXRk+eGRVqMWJu\n6GlHxC1HK9wilIGQU2eufR2C6XA2oL39PEIQxalZwwhhMxihr6bzOs1pxIGQ\nWTvCfreaUnMhx0GZReBcnHEUNbQGjq1g2DHqfpoR4Y+leB5QDJTBQ829h1Jl\nem7vmeskx9JUbZlhiiPl65jcJkiJlmeLNr57ZYOH0ms8AuoIz9PVspMtfVZG\nkshdHtrBI6ufJSIqKvOunvxLzdA2xN17W8s/HeE7P5gIjtlFfIvf0dNYnuaF\npnnHhUD7in9UbdHdvQJlNTeZDu73SDvUBU6jEP3cpOfzDUWjLaHvOFM8U5KI\n7uco+2wX6dF/U3sUulohjR3BHz/QwRpZ00QRJT4kMNjZT5V7zSW9d4p3HS9I\nyym4nXY9CYKM0BxVK8W9rxw/wN/rvFQ19Wkt1gEzR/tALEA/hwc3sKWoNKu4\n5OVRV0rJ5YTm3Way4ni6BxUnTyuDanRS0ggxD36Dps5puV69ZOhB5raNS2V1\ndUpwFule5Ix58tTgI2f+tmzbt+QMuxwgRuPAwlUlIhsIGGGCUq5oRKGhct3N\n9DTu\r\n=fmo1\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"d6392ba30b76aaf5a87077bf7ade44009f8160d8","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.46","@balena/jellyfish-logger":"^3.0.13","@balena/jellyfish-metrics":"^1.0.267","@balena/jellyfish-environment":"^4.2.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.160","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.7","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.1.8-renovate-internal-patch-d6392ba30b76aaf5a87077bf7ade44009f8160d8_1623404630426_0.3412878663763039","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.1.8":{"name":"@balena/jellyfish-core","version":"3.1.8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.1.8","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5b653d6233a25181448c3a081e9257333207e7a8","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.1.8.tgz","fileCount":190,"integrity":"sha512-gWlUlfdnTnsMfQcJDooJ/TeOUJW7vlO/HsSRvq+MFF0+1zku+hl4sybCgugEYPqpxtFc7go256M/soBZhpvXKw==","signatures":[{"sig":"MEQCICJLiLtZql5WCFuL8QBK18IerBjRZzFWUZ5vOU/+MtDFAiBqfFHWx8i1zYOKcFXdup4Lp7mp5ltK4xvezx2mYvUrqw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":720754,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgwzFgCRA9TVsSAnZWagAAjggP/2IBtLKWIozyf5vwwjj2\nj1pScFs1g3vM+2G7978CP7IbkXRrj9gie4iQsb2TWtAvX0Bb4aokqbSAanSJ\nGGvUyY9D04CPEKoc0RKAVFNuMumpLrab0sAABhD+DsMeGL86JpBUEcoLEl8A\nFPpXL2CsU9Zn7kNxcoK/Gh5fvE/jRnJ/XhwJzKBkImPwp0yYKTH43za3E5c3\nvJEeCDBUbEqhitjdQel4rFCftxzbx6lwLoPqTk2+c0KDVdt1ynBryrQeGViL\nROV7I1QR6iqDgXUkmfLpvoXz2JypGpT8FdNcJ7fIQKsdvhVHzjXZhMc/Vj+6\n3TzRK5EpzOW2IkeAawCf56TK9vOTFEfuJTxqQCjmZv90+Nb+IDjhkD717whq\nJK59mS/d3hoz3WRmLtOqVtwIPzxaQWas9bRemurB44DQLyLo0gKHSCgPOqgv\n+JLxjsOPHWvh053KkyIR92dtdROEjH6eY4Hfo845Q1vpWwjCGmNoY51nsOb7\nxCP1z2V0xJcl/sl6VcjVzueK1W2DkUDoedlmT0d0lFaenCV2eyBepn+Jb+Lb\nXDuviSO+eUuXXiHzsd/NnrM3wG6H94rDTfZIV08yMguWFYFhjIH+ejSDzZ9X\n3dKU+FiZeo7FWCkWP1WcNks7Q7EfjiJtCIsgBmln3/xi/lxjONU1T1kHRaMo\n7GPl\r\n=PoMY\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"2ae39555a5deee2bac5797ba831971acd475e5b2","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.46","@balena/jellyfish-logger":"^3.0.13","@balena/jellyfish-metrics":"^1.0.267","@balena/jellyfish-environment":"^4.2.3"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.160","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.7","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.1.8_1623404896090_0.6629514429858707","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.1.9-renovate-major-external-major-1885ddd7351926cd2051641acdfdccbc2b58c2bc":{"name":"@balena/jellyfish-core","version":"3.1.9-renovate-major-external-major-1885ddd7351926cd2051641acdfdccbc2b58c2bc","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.1.9-renovate-major-external-major-1885ddd7351926cd2051641acdfdccbc2b58c2bc","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"179bcce2412ea95c6a4fb1a7d3b2f6af8806c376","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.1.9-renovate-major-external-major-1885ddd7351926cd2051641acdfdccbc2b58c2bc.tgz","fileCount":190,"integrity":"sha512-/oQ/SLiHYem6psLRrBl9ofJBu0aIRJZD/Kfk37p4cOG/TWecJ6CpjJ4d2BaXU8V93p2POxJY50Lr6I0Z6ai8fg==","signatures":[{"sig":"MEYCIQD5a9gHLGKlphXBjCh4n5DGcjTVuH+2ReVYHk6bXwkUQwIhAMMIJEx7ow7JSvwAQO3TN0alFiOfigbbzgPhzbXwcwLD","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":720895,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgwzJwCRA9TVsSAnZWagAA3bsP/RSZHkY7UUX++y2DHwu4\n23bbxxLnhJNoKqdfs9nPgxA7HGyofDDOGMkQ6hxZODCNjvdFQgxLSv7d/Afn\ndittLstBtaGNDqLqjIUFo/Osi7r3JYehCT1lvd1mSpYXPaLvtdNt5zWFVPjr\n0ySBCOdghdqTRWiXDIes9Piv6cm9jnSf7J7wCvlgoVGK1+ZNns+WLA9h+FDu\nTd3Ef7k1uDcoqOQcKSqMrERQ98aI2VmmtyIkWs35vMURvH5IMSS1ygpQFTzP\nDnl+jd/20IGtAbzQLPPqszsRtU02omF13EWgJWs0AZ8/moV4jbX0Cf0offIT\nPX5HncOJpZ5gbsDnnibTWBD4Fu6w1Vl5DF4iwqRS1XxzikEBbsrwH4ypfQ1B\nleltljT5GHVSezrQu71mBbaRDat6Qw0ixcGT7HVEao4ETfpHf8vAekZnJX3S\nLEuvaveweIMR6bk93sZRSXKQYUHIbSH1EjGS5gsCCGKr3hxDPjQblyRtVCOh\ne2EKeJuNR+IdMzkWpzEen2G9wx5vz0hv8DdC6zieJFkRHaD1HZiksT62yk26\nvjqLtR/f2MiuAXSbj6o71IWK0vl80Jcju0ey8mUpILR4ApUVZ7iVwwiiRA0J\n+TsXBiHf7oMd41asyMgddX6CXWro7fdArAQ+rDcEWcBPdtC3fDgA38odQ2yW\nLb2G\r\n=2v3u\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"1885ddd7351926cd2051641acdfdccbc2b58c2bc","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.46","@balena/jellyfish-logger":"^3.0.13","@balena/jellyfish-metrics":"^1.0.267","@balena/jellyfish-environment":"^4.2.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.4","deplint":"^1.1.3","ts-jest":"^27.0.3","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.160","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.6.7","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.1.9-renovate-major-external-major-1885ddd7351926cd2051641acdfdccbc2b58c2bc_1623405168277_0.6342726655387458","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.1.9-renovate-internal-patch-70ee23bc60779c25d01417e138befde9348958db":{"name":"@balena/jellyfish-core","version":"3.1.9-renovate-internal-patch-70ee23bc60779c25d01417e138befde9348958db","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.1.9-renovate-internal-patch-70ee23bc60779c25d01417e138befde9348958db","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"462d89439d492c134e09ab9f1f97116e80e9a1ee","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.1.9-renovate-internal-patch-70ee23bc60779c25d01417e138befde9348958db.tgz","fileCount":190,"integrity":"sha512-QfwRWqMwvuLMUz1Rw+3/i3QoETuDMX9BVD2zHcZ7uHJAUzAjLzFM4MYdL6VPn7mVjgrZCDkQ08ikQ+jczq+dnw==","signatures":[{"sig":"MEYCIQD/8CgMC/KyFB34ah0DIJGE0I25z4Vmg5/EikJj/4fWmQIhAL1D6a/vk56KxCwuRedDK7zO501eq2CeTN5HBybaRt4b","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":720921,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgxAqACRA9TVsSAnZWagAA0xgQAItLauxY8fbcHKpy03AY\nZIujaTrPLs3aH80GixCWhKYXKYrRguc4sMEuNuzEUDo9jqRljFlIMm4jMIfN\naa8GeKPakPZIw26DJsJYOZBl/P0wX0UWdITF4rhvV6jLvY5N3FQb/ZgrRNEL\ngzkqOux4jCQ/ItCb5qHp+Y2r0CYwc/UvYO8rHhuTGNs1VUsfh0v+wgsYuMMb\n3tTaDK6pdQZYF+LdFv39fabYfO8sAcAv9qx4QVv1NPtNRQXItnb+uV80Q7b6\n7Q+y6NRhA3iiRM26Q3U9FPI7VKquF2S4KUBXTN2kEp+hdP5FIJRhQnCQc6VX\nsOUPH11nBk0w9ZAa9l9v2XuXjsTssTXjgMN3Bnsg59BbRgRzemKdz09RsV91\ng++y5trbAjbJ76oVEBgfzWS3y1zq/+hI4tbldXZTTMCOKZ/jwdIjUSdUQ3Mg\nXMgQIpCgoT1yDpJ0sRJERxRvs68hlnCLDPoI1CeJ2Pf23K1bliVBtXQ5F8wa\nefnEbaEj4UYViBVzt84rbIR2whwmmfbN2SXteR8dyKhBshzMgIr3v5l2jZdT\ne9hqBlSKPiAiIpislvufaUGkvvvX1EDZoLVVrJsWdTgLIkyP9fuV4mKKzdfM\nQtjRaxR33YxXRd6Dj1YhV7hbEiCTkutBnrA/sRxpQDmTMMsgqaPrzYLqgVbZ\n1tOM\r\n=3X9M\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"70ee23bc60779c25d01417e138befde9348958db","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.46","@balena/jellyfish-logger":"^3.0.14","@balena/jellyfish-metrics":"^1.0.267","@balena/jellyfish-environment":"^4.2.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.160","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.7","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.1.9-renovate-internal-patch-70ee23bc60779c25d01417e138befde9348958db_1623460480206_0.27468822265559156","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.1.9-renovate-internal-patch-a03d218787f7d423bf90e5036ceb7e5a0b2f7937":{"name":"@balena/jellyfish-core","version":"3.1.9-renovate-internal-patch-a03d218787f7d423bf90e5036ceb7e5a0b2f7937","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.1.9-renovate-internal-patch-a03d218787f7d423bf90e5036ceb7e5a0b2f7937","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d18f451e786dd246a444aed8ad4db3623aea3b77","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.1.9-renovate-internal-patch-a03d218787f7d423bf90e5036ceb7e5a0b2f7937.tgz","fileCount":190,"integrity":"sha512-wpyElIZwceIqqJBNy0aLRNx+ybXdOu5kEPZlPe75dMLrbUisaKHl0M7UwJr7fAZpQxOhxR/h6UQqWcF/6sNxrw==","signatures":[{"sig":"MEYCIQDss4NcuZ0nwPtODQqpOQu4BtTUy3a5jFcN80UHhClAmwIhAMsGdeqbub9tRCztSlhLK8sSdwLX67hugNDF7exXxP55","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":720889,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgxCPwCRA9TVsSAnZWagAAJp4P/1sCVTz1wxELZaUyA3VX\nWC0XpoFAPi2RIwLacv/RkfCWGe5xbhQrVOTV8NM6BgATgRed41Qe30duDPu1\nVqryFJOU7cpD8sPRmPm+SYJqb5VwBZ43GYUO4uLi+MJWfHetS6g2IUuVNISP\nn5/63nyzUnJy+9FOmhhHhw5FvY3J7P2zSLfvOilpjikGoUGrYlyFLijgAsfU\nz8Fa2zrk3Dtp6xILTaEGjzA28Ew4DvMp1ENmmy6hICEbU9IlhojRhEujGCXr\nxdZ8CCJvPcUtCiZoCHtLnP0z0vMLo4BZwqiQ5aWdz8WSRaDexqUqhFUI1e2D\nml59II8niZwcawY9Mp2Py6xddsC7VXEf+hcNxS/++F3loCY/kg1LvG/uxGvY\nVfGWYlEs1sfXuqfX1Gx9abmoHPNCs6l0cICs0N4lyYlGRAlCau+L9zirYwHS\n79leByqzfg2cooh+aPy53X3PgZoks40U1fs5c5s/iXr19eREYD6oCneb78Er\nJZZWMdnkq6JrH/EUVVXMg0dWtEjNOFwm9sxEh4mqo6QxMiOf2l3T4MyT8ZUv\nWuoFtbY6EQbFkLWKHW0B2g7S0OgFgak8ULwv2mSnVOtI1o/YEPF+r7Gy6xb4\nywXyv9qnG2V2GGM7kXKk/veWt21HMAOrXsblGUUj7UfnIwdor7snwahJXpic\n0GaZ\r\n=G3dp\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"a03d218787f7d423bf90e5036ceb7e5a0b2f7937","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.46","@balena/jellyfish-logger":"^3.0.14","@balena/jellyfish-metrics":"^1.0.269","@balena/jellyfish-environment":"^4.2.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.160","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.7","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.1.9-renovate-internal-patch-a03d218787f7d423bf90e5036ceb7e5a0b2f7937_1623466992364_0.9256830686200956","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.1.9-renovate-internal-patch-9e99a68c11846a48457861ef1de974a3549b4d3a":{"name":"@balena/jellyfish-core","version":"3.1.9-renovate-internal-patch-9e99a68c11846a48457861ef1de974a3549b4d3a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.1.9-renovate-internal-patch-9e99a68c11846a48457861ef1de974a3549b4d3a","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9e58c64c99fc25b1ee33d6ff8bb31bcf14f78072","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.1.9-renovate-internal-patch-9e99a68c11846a48457861ef1de974a3549b4d3a.tgz","fileCount":190,"integrity":"sha512-wSGoLlWh20SYLv4WFR8Ol1N5OJVDxx1WvGjooXsAoWNR/n10EipVxbgBKOleIh/P05tJEHTPF/qcercMbhaLQw==","signatures":[{"sig":"MEUCIE+ut0Zs89EUy38c4YdzINnLSGGY8LxaxYSFky/5v9smAiEA+xz6ZNp4pzYHS9vknB/NkL3prEbLnw6IBBNSBsHOe70=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":720889,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgxCU8CRA9TVsSAnZWagAAnLIQAJhaxIgk/eQ0P321Q90s\n/lfpqs5IVVynLsfdXCY1+4ZH4rXb83J9mqLdAMe9/yHvz4kSQUulmdlPl5x8\na2fLknQQGZhkCIEcYtcj5STfP3T6smODJSkm9HIY5edF80m19G57tQzZkXPJ\nStHtw2JGD6GhZ/V06QbHLr+lReHS+LbFt1t/mCvth9Tuhlqph/5FZmBfpKQz\n7U1H8ipmWEr9N+PaHZx7OrgXzwUqNSsph2lOz4cTj45bs0t/ypi4IZfr9ikb\ngBl/v9UnKIlsWAI/+XG/zUr90QT2lZFqCD8pdLnQHLsXo9nmKcJlew5WkCJy\nGLqUUznzfO2KAiwBZAiossl9FN5lu8h+MgCUgkq/pufhHuFqtppv9VP9V0HT\nf1r+O4nWFLVeUcsNqplR63+/rLk3HmzGEMgX9Hs+JUrjlhjePbZzyOWzxPwy\nEpqmqGXRZ6ywyWflX+ciJ+647NxO1Xvn2/8/81mXlv6m9/a54lzi/ncLVt8a\nNPaw+QPuZLSvVBqRHdfst/gvF09TRG2fOvUZuKimFwSf8uqzJudUbVUpSJi+\neVywgaruOLvFeFTJp/2Mo4+a+6jNgYqIJZp2pmsowXbbNFSZ0sXh6vcDxg1S\nrK/nKv5hl+xP+tEDHxWEAqgk4YubhTMbuxcX36avMlwWhM8I8XehGBuawo+O\nweRI\r\n=khP4\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"9e99a68c11846a48457861ef1de974a3549b4d3a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.46","@balena/jellyfish-logger":"^3.0.14","@balena/jellyfish-metrics":"^1.0.269","@balena/jellyfish-environment":"^4.2.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.160","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.8","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.1.9-renovate-internal-patch-9e99a68c11846a48457861ef1de974a3549b4d3a_1623467324097_0.39059064601111704","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.1.9":{"name":"@balena/jellyfish-core","version":"3.1.9","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.1.9","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b2ccc9e35df3e9762e9497439863087d555e25b7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.1.9.tgz","fileCount":190,"integrity":"sha512-wPHliSoS0YIzmG1TmK6uQiJti6nc+EmXaAXHVoFC7zYDCk0Pa99DdBaUhUy4M7cuNO3ojr/X5q00gzjr2f4yGQ==","signatures":[{"sig":"MEQCIAKItly8CgQilnUks0uhwhsi/T0dSl8lVLiTr1dORrhQAiAVhnbGMlBHW8NBagsV5KDE11Lcp9j2wIxETqvnGiHJeg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":720824,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgxCaRCRA9TVsSAnZWagAAAFIQAJ/Tr4x+ZkDNNCCN94V5\nr18eVlk8xAbZIQrrLJL3MXCipgOWSECPI4AsCjXWjXqC+eH4brQjtrAqerJT\n/Q1cKFKIi/QYrFFuHr4URyxrs9qi1RDYY2tTGGB307KT9gd9pywesMvBKosq\nLo6FheCTnU/upIg+HYEQG4uwkjgqGrBax7hRiZvKm8Db8H0faZLQCAKvk/6S\nISzIJcOOw92bjFG8rZ3P+XVjpU+dFJ51iLABZs+7vz7A9YC3gxAleZLFppt+\nB3CxHfz4alxV2N+I45OcxUW3Fxhn3LuTsrZtmZjV+fSspdMYh7QYlllO478L\n39tTwBoNVXUEc28Dt2jg1rilGrPMGZ2z9lVVNeDLFHWxTfXMdRfnIcItMmcg\n3wo+VktudMtKC4XEZMMZ0RzuwimnyI2K1+WEyNB8in1lLXif3YM3X70/6Lbc\nWRuFKh/Dw4IxVTllV0ceyTryot+/8ZjHATqUgx3l+obV/mChbn4292rA6uIJ\nzb/Lb1O18lV8TA81ZEmbWsjUVJi/kfN0Pt6mxUZ4gaUvxQHgpfR3zycAcviN\nOPC2I7zfpNtStiNe8UShSjok013p+TiBRWyVdtBh+NUUSHvDyRSsE9wlb7ao\n69I+Onpd1JRWquq3qRM+Xbta3U4Rt1qLfkx/ZUvO47vC4dv92MiuPXn5WXSo\n9/cv\r\n=cpte\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"9d023e9767fd16435adfc175f33e5dea99b9ef7d","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.46","@balena/jellyfish-logger":"^3.0.14","@balena/jellyfish-metrics":"^1.0.269","@balena/jellyfish-environment":"^4.2.3"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.160","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.8","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.1.9_1623467665545_0.17462960727730392","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.1.10-renovate-major-external-major-da75953c0b0c651288a6d9be25f6c75703bd76d2":{"name":"@balena/jellyfish-core","version":"3.1.10-renovate-major-external-major-da75953c0b0c651288a6d9be25f6c75703bd76d2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.1.10-renovate-major-external-major-da75953c0b0c651288a6d9be25f6c75703bd76d2","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"81b44675f408beeec59ab8e9b78bff58ab073b73","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.1.10-renovate-major-external-major-da75953c0b0c651288a6d9be25f6c75703bd76d2.tgz","fileCount":190,"integrity":"sha512-ohnIn+i7nq7iPmRqSHOe6aI6dkT7jIeDlGYjqoB+FxwpjYemA4bVabBr+ZUNDeqe9YpAv8B1ftD9NgOrtDbodA==","signatures":[{"sig":"MEUCIEKGJksv/QAcC9TacQ49/F33ZZhUSVE41EscQJhd07hmAiEA5U+6jouMa0SoQfIVutBElKVUzYS18iSf0C03p2bHysc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":720967,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgxCe3CRA9TVsSAnZWagAA3kAP/R7etCmGOZWTq+0uwhTT\ngCsMaystPlFB/P+HszxwC7XXNJ3lRFf9VmufhCz84yThTUYJepBA3qgmOXz1\ndMMCSNI3b5DW9I3VLf+60mEWxO7RZB1Mh9+SQzoGyXtks5TaVjU7dnbGxsZP\nX7rLAGib9CZDLz+e3/oOMoDn0cf1uWc34aUtaYa8HSH+41EVVLF0crAi63cF\nZz46VnD9su3x+wvyIc4nEVHdNIv5TTRleKGBamulz+8S4ryLD764gvsZNrql\niToF8EHJLw/rNq7ZZoMC3U1+5IO11ja2H5WG2w9Xd9MnGgwdc6k+qsy+6xy2\n9C3W0Po4NY2Z1ee1mCYRl3KkuIhRoDpnlWZDORIayFuYwRaIcwfCQemK50Bv\noh3MAL8ERoAVSHiG5KDSyc5gUBTKXPlk11pnlItKKgpFN0rYTiXKVws01bYt\nTRs4DG19LJeeCZzme8e214kovYDBqbUm1ggNRfSHqob2R4TLSASDHRT4cTwt\nzAb51fjdvsSKF0FOwCjP8ng16m7IfoBmHUNXonnkxm1Kpl38BOGS3TRLrEAQ\nZrz//w0FyFcqK1b4WAmTnOcPVUfuBYiHyNS6a8/5DhNN0EROO0lTbjLRzVCH\nHcLHilsKvkTXYnif+5Rgx3STfRzUcw9P5X3Kj+U+E7UDg14I7fpzqwyoSsVt\n/NAR\r\n=6FXl\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"da75953c0b0c651288a6d9be25f6c75703bd76d2","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.46","@balena/jellyfish-logger":"^3.0.14","@balena/jellyfish-metrics":"^1.0.269","@balena/jellyfish-environment":"^4.2.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.4","deplint":"^1.1.3","ts-jest":"^27.0.3","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.160","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.6.8","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.1.10-renovate-major-external-major-da75953c0b0c651288a6d9be25f6c75703bd76d2_1623467959735_0.7174526397425576","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.1.10-renovate-internal-patch-b6da586d57653d188491e166e26c0c9603521a80":{"name":"@balena/jellyfish-core","version":"3.1.10-renovate-internal-patch-b6da586d57653d188491e166e26c0c9603521a80","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.1.10-renovate-internal-patch-b6da586d57653d188491e166e26c0c9603521a80","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9eabc7fc25bb2e2965181eef97ebd7439e25912c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.1.10-renovate-internal-patch-b6da586d57653d188491e166e26c0c9603521a80.tgz","fileCount":190,"integrity":"sha512-VQdXFW2tg9j30Dqm9dYipKZYK3F4KTdy82/ZtTdVlgUXaNl5EhZgosmFsP/HZIqPvjz+ggLdHWP7wnWamfD/Yg==","signatures":[{"sig":"MEQCIH5UPgyxGeU4hPsLGFE661zQ6OMq5PHOynHboyXvVDCvAiBlmYRpPidXAUm4ZmtIkBhc2ETimBMBicYBWuhZTQpmVg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":720961,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgxIanCRA9TVsSAnZWagAAue4P/jOmLw5uKkB3hgn1M/vB\nfT9uCRhgTq83CoiAxlNyslqZMB9Cw/DBq1AVI/X4gJbd7i/jzezcyqwX+eYf\nQBS7aafoUigTt9sE2VWjZgJ7b7q2aZ3HUqkxVHC+ijjUiU6GT4XOpcYyhFDQ\nluSeZK5O3TIT9ezBJndXic1OBXiCBYnagGnVx4pOq3hasLXvNQoZfZLfadA1\n5lZDd3zihmzSdq7kBTD4lk7xUl0w259bBwLONRouQoVu7kWJa5m/KFN+JdOP\ny6pEV4p7EbmbCO79jyGpYRN8S3X7ZLIajI/I9WrUanUtJKE6QEiW+r2jryuP\noto00Amqay1Hnuot/dOuhCrfAoj+AIxidkUjGASsJP4mwjSrAPgDDyKZBFS/\nfozMouCkxKF97uf90EKcb176PoWVZNY3VSsxdie44Vo0qZ76wMfGEgqamT66\njWGsIwPKlobrce/aZIIuuVmkWfHcaEkblzHL3iXjwxMY1Wtzf7TInPSxUk9M\nsCH+Mu3Ngtj74p7YuFGaLBeQgX3vkv/wAqiR7Q/907dD7Nqod3UCngL90c6O\n4bAVqzv4YTVGo89IYbPPiDaAnssjq77/Xu8rspbpiF5lYvOPfxcHEXpE6SLF\nx/tczRM0WsmgAH9akdZHSP4n0dgUyO3HXe5Sla/pQ5OBWv4xF/VS8PzI4BGD\nGrVo\r\n=LJIA\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"b6da586d57653d188491e166e26c0c9603521a80","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.46","@balena/jellyfish-logger":"^3.0.14","@balena/jellyfish-metrics":"^1.0.270","@balena/jellyfish-environment":"^4.2.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.161","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.8","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.1.10-renovate-internal-patch-b6da586d57653d188491e166e26c0c9603521a80_1623492263516_0.4409068913844181","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.1.10":{"name":"@balena/jellyfish-core","version":"3.1.10","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.1.10","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"04a241494bf0ae65d2a423f3a2dde08ea36f6254","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.1.10.tgz","fileCount":190,"integrity":"sha512-YCVz3hZQPqS4VIaslzVCNYs3NzPDszuEHYJaVM0g1tPvuY3q7ODNewPdK923hTW3J/CbDiTQDVF+7vnHeUQPVw==","signatures":[{"sig":"MEUCID/TTAgB8kIw9oYUoqu+7gluA4iJSYGa7or0ttv9dlCyAiEA1ef1NoWbihzwt9QfdF7IznsQDyvBVwKcEUpOujkbUCc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":720896,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgxIfUCRA9TVsSAnZWagAAXtgP/jBwydboJlGdqNdNtslg\neCGIPZAxWhG2mVsWWg6U3z8WySUne4GX57C82jZ48EUq7RNxaA8yiaO2CsxL\nwT/4jgLtWaEE9clkwUWmQemaAdoSvHCk00KWrFHFxsKm9iyKYqCGAlLSmAf+\noKU6yESExYleLYxzsbTDWJ6p7yS8P3vGJrXawVrAKXnlK3jGHxopjwfWHBgD\nwbw6c0uBqI1m6Dhix4Q6C8x1y8sLTAurS6zcVyTwdviHyMqRDXsvSotTA7US\nkuWMLPJoIQG06oEziilmFvRX6mwyyDchbCY5+RIXULplUQc+CVoRyy5s1zZF\nvUEDyz/kORrQIEBJRb2GTQrzX4f87HxuldzPNtD1g/B3twaCjOtCnIgmUWmG\ntpNe1l3g8dmd83tUc8P5LK7iw69BJyV3SmoztAX3xgDixREXzLe3dBCuwRDg\neh8HNa3BtK75X/YHVUNdaJr4Qdb6lyNy5HAgdUvkTJXjnoF/MzpcBI5IKQHH\nR/1DTaCnGwvLeb4NJYmqDsTNzU6P3kmdUzPzwt0zJ1MkBWg8zvPkPBrT/c3a\nvO3bRafWzohHv9pNYcTW3HPvhqOt0nZ9zurt8KmWVy6pAdQ9KqZQ/o2BMW0i\nCcfzDAk2w7+nhwEQKiPdpOqFsFn0FFdhlbkD8lBXL4ThghdlxMsuwbY0wyUS\n0Ohd\r\n=wVQi\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"d3255e1bddd4bea5074c567491fbecc0c82b19c5","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.46","@balena/jellyfish-logger":"^3.0.14","@balena/jellyfish-metrics":"^1.0.270","@balena/jellyfish-environment":"^4.2.3"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.161","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.8","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.1.10_1623492564608_0.5378849953150715","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.1.11-renovate-major-external-major-2a3f493f28339420aa9b31bd6ed62b4849ebd5f0":{"name":"@balena/jellyfish-core","version":"3.1.11-renovate-major-external-major-2a3f493f28339420aa9b31bd6ed62b4849ebd5f0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.1.11-renovate-major-external-major-2a3f493f28339420aa9b31bd6ed62b4849ebd5f0","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"882b245eec20e1de1fed74665bae00cc4f4d4a75","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.1.11-renovate-major-external-major-2a3f493f28339420aa9b31bd6ed62b4849ebd5f0.tgz","fileCount":190,"integrity":"sha512-8vSaVXbWdxqlbuJ5rlAyULplBUhjcWs5bCZKWfhmFOam0m02raUVNR2jwlb0IjgBmm6nKzHycnZG/G87CQkpAg==","signatures":[{"sig":"MEYCIQC6Bh1kITJGtTmVpuhmPLtjM967Yu19N/d6QbJXKKznYwIhAIsiJtjlT7srzoKoOtuRAiGJsrdarb0qOEKRxqr/fTJU","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":721038,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgxIjZCRA9TVsSAnZWagAAFTwP/2XUuDj/yW+Q6l0sUSYj\n7AJVuBP9Xx6nlm5QEV0v4UPwPaXq1yVfLabiIj38338Katdh/y4aMUqSxBBg\nKKGk2Vg3n046CpeKb5DMYZsq87z6nRvSusNFwPy1QR7U44vSAflpAk/BUDIn\n9JmKrAjA4TEgxFGOyX4TXp8AD6GJb1F1WKLdp/amrzbmLxPpQlWXYXRqPdky\naMvEkHeOWmRULyaeARqVoYDPQfKwyhfB7CVBdZIFBmtc2T/k88VStkSHDZO7\nFc1jrYeC/Q7nz41Nr1vxZ4Bmv8/o9SgmSsIJAmmjMldtwsFkI3BQVa6lQ53d\nm2B4cJvInqvAOGwVzgdACuQ+iq9CaZgq6jPKq087ZSij04cB053Ijv54kyNR\nHs1wZgIlwbCAgh8IIotlapZ/tGJeKnLHyteqDh8UW9R/1Vv3D2xvwbrHlmcB\nJHz4yvJGlviVBIkyZoihQX+0e14oCFagUy4vtmGZTpjcyRNlGAaRJE3VGa4M\nvB9kuJw97AmC01iwnAB5J6lcHcflY4OCnbrjJfyy2gkSLmPiM1kfCfDVaJOW\nAYw5G2H5VTJ+z/ZdFyPN6d9l5vIMV39pei9RrzZVlN/fOt1UFIIwLgPTiwRh\nMZrK6huJjFcV0ryxBmKLOxoiZg+4F0r/ArNW2XxiqSH3zf62p1jM3XzK4X7q\nAcnW\r\n=myMF\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"2a3f493f28339420aa9b31bd6ed62b4849ebd5f0","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.46","@balena/jellyfish-logger":"^3.0.14","@balena/jellyfish-metrics":"^1.0.270","@balena/jellyfish-environment":"^4.2.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.4","deplint":"^1.1.3","ts-jest":"^27.0.3","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.161","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.6.8","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.1.11-renovate-major-external-major-2a3f493f28339420aa9b31bd6ed62b4849ebd5f0_1623492825344_0.8298613209763974","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.1.11-renovate-internal-patch-93bb531f3c8cbe588467a79543162abbbcb8bbf8":{"name":"@balena/jellyfish-core","version":"3.1.11-renovate-internal-patch-93bb531f3c8cbe588467a79543162abbbcb8bbf8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.1.11-renovate-internal-patch-93bb531f3c8cbe588467a79543162abbbcb8bbf8","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"bf2640e9a83b6a85bf0e874088e54f7a801e3536","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.1.11-renovate-internal-patch-93bb531f3c8cbe588467a79543162abbbcb8bbf8.tgz","fileCount":190,"integrity":"sha512-W1MYeSXzlDiyrgTjkPWwXD26lLcNr4MucY+7pXmHXdPIgKnYbpXHZM2xrGOtWRstWHlSVX5+dRGzq4aKl6mW0w==","signatures":[{"sig":"MEYCIQDKbPqtEX6C8/o3K/u4b0lg8scPuVvcLiGJNHKuniYx4QIhAMEDkNGqTIO47Cj38/e9TP8Y7LklOUPYvAlUwpowkwIK","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":721063,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgxOiFCRA9TVsSAnZWagAArRcP/iH/caUwPCTOqH1egm4Z\nCsDzkFtdF6aZuXMW2MKakaDBKu5dYvMZO23s9XEzzTph1nPPu6axbJsycR/Q\n07daOqMTlwHW3sHhhWRRP4PbMnnt1n6C5jpUxNbi3ou5GyyZmm/hjaFN84FC\nllUiSJYSFJmVnJ7dKiaolRB154cY2HhJB+JZjDFHhEYYvUp+tL8btgCQmPgh\nP35GQypV6zrCHdDxAsptsHBUICOxG/WQLGNif+thInrXWzyqEay7qi4n2nPN\n+zNWzHUsRrhw9kF79hxPmeAsI7Euin0CkhURI4lyKt+VsY3Ms9py3+VVDwvE\nNpEDZC4B+Ia1loVTcHP5MSfnKFJ58dPI3hxnzDU/anPnk6/yav8UOYMzD/l6\nkFUms0vah7qIn8wdoRAM/kCK17h+vaTEQA4JbVcNfl5iEVDLrhs7HXs//s4A\n8itnaKrVlalH74RkQJkPP/KLeWt7U2ufbjM4M1hrrqaQqimfyH3A53srN2B1\n+hmAcUKejhMEwPDgu5VrWy/angW/XZWn3kRJTLkdfiuKJt6qdq7Orahyl3W4\nBBDNtTFpIo45T57AJNDzG8Pl9CmEEpzpqMJf40fTlXVjpc0Bcd3wjGBcgjuz\nFn6ciDjPffBCi2uGmyE18DfHDX0h8uf7wlZVyu9M30vz+Hlm+YH4b8BR5YOs\n44mc\r\n=nRFQ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"93bb531f3c8cbe588467a79543162abbbcb8bbf8","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.46","@balena/jellyfish-logger":"^3.0.14","@balena/jellyfish-metrics":"^1.0.270","@balena/jellyfish-environment":"^4.2.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.162","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.8","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.1.11-renovate-internal-patch-93bb531f3c8cbe588467a79543162abbbcb8bbf8_1623517317324_0.14080841296675994","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.1.11":{"name":"@balena/jellyfish-core","version":"3.1.11","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.1.11","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"78cb82bdde9231d0ea43b080daae27e2c03d172d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.1.11.tgz","fileCount":190,"integrity":"sha512-jbxCUBKTJhP0oNVucgzEwMg6mMM9GGbC1imrZA9/KekwiS6z3hFWrvrhqiD4uBSkf1YtTXHeMtbdDhMmqbK2jw==","signatures":[{"sig":"MEUCIFOWIPPCVYRY4lokoOb35fqztUW4wRaJe6qUhqelJJfAAiEAt5rjeQRGpgp7vkYGQFACJkSpiTtCIFwWhsqQpcqNjpA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":720998,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgxOmyCRA9TVsSAnZWagAA4loQAJ7vTBE06X0IGmulutmK\nTzmswzj/LShOe2jN1V4OSAE5itnvlBF3e+j85qW49Mamm8s6vRb6EghKX789\n9hzbqbKNYyzKt9LsI81KudQdZZrjNY8UOa1UleEkDXvOP4bfMSPDwVxuzTqE\ngGRw6PJrBau9soSXezWHxRZKp+FFccyIeQl6CADB1uCs3LnG98ghygegTeLB\n0pasDNVyXbVUN/G2WuSsmvF+5mMWA309O1PtSoN3+F/Imx7zGArChDM5Gble\nly8wsfVePeQj7ISIKAXacOcDoxW81inbxflMBT059vNuHSARAWrA5dz/uspm\n98QsX9Y6aQBNGM71eIe7bbqAghb48H6GL5y6ngNo2ciV+TW/3+Y2DJUwUNQM\nefU4RM+gBncl1Mb+L28aHt/jpDdBQCkgVAG6yyDs2q2+vKoy8VjGkTJTJz7W\n4bKbbj9cPY9XYuuRXCa1D/qv8oERb7B3MADxqp6bboSiKu25O2ADKfFXZMiO\nJGL3T79lI8kAxjPvdtJEAeAe+srMmEiVtGKAIPKuhISaSur27Z+8EJTZcU2B\nKSfWj0Nsko2HB3AemAruLvq8bBwxDoYZOhb53KIcXQ/8+WTJkhY23BJOMa7A\n2qZyHIt7JKhT/3nDFlYsDU8dLcB1ZwAduCQCr5Kz8Hg50r4hCuO08AcV+6aO\nvnGO\r\n=JhSz\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"c10ea138196b229e92e0bde2728f15bae8912f8b","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.46","@balena/jellyfish-logger":"^3.0.14","@balena/jellyfish-metrics":"^1.0.270","@balena/jellyfish-environment":"^4.2.3"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.162","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.8","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.1.11_1623517617995_0.7247290707814886","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.1.12-renovate-major-external-major-9739884d35970597debd6e248fbe674acbd5f36d":{"name":"@balena/jellyfish-core","version":"3.1.12-renovate-major-external-major-9739884d35970597debd6e248fbe674acbd5f36d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.1.12-renovate-major-external-major-9739884d35970597debd6e248fbe674acbd5f36d","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f19f3334d833862a5897663d5b1e214d60377d28","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.1.12-renovate-major-external-major-9739884d35970597debd6e248fbe674acbd5f36d.tgz","fileCount":190,"integrity":"sha512-ypJ5mOoOCD+H6t+vBSdATsw+aanUUR5e8FuYQg/+EPjPHgc+jekpg2veRTkcgT7wDkO5FVQVW73xbqQxkO7lVg==","signatures":[{"sig":"MEUCIHDNimU1nA/Vp4gby+wIUxPCzjk8gYll/8SD77JLl+o1AiEAkMi8JVSCZxayvXpVfbr82DvsNsKMjYLoIgXizAgiBZ8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":721140,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgxOtECRA9TVsSAnZWagAABkQP/A0G/IPVsEjxmdn5riWL\nuL9Jx9rRwbF82htdYxHxKSvHCUE0XvconQwoILMUaqU0JTF0uRtGVox7c5Hz\ng5oWDhry341M2Ap4Fq7/yWcnd5IF2oqLotgwrydA1c0bvj7mVyeRuEG8pqEV\nVM7lTdJE2M3Y9hHCo7VGyzdRyuEAWP0Fn8L0/eUIj97g02GBJ4nuh8Hai/RX\nTzGzIa6udDbGuPvI9MgPjqoGeQmNeuYHvaXQfDhlcNDADj6UULzYNYNzBA0o\nnaMYitxRaO+zmMMXJT7jryEf83hVA9AK7Xi0MkK+3f1VElzGfqJzFONa5tqW\nSg9dkqXrUXsMfNe0fnpWCcHmZmaQBBFouAf5oVv0lL5YgTFKUCwc2y7Eawua\n+WdQ+1L0b89zcnlBji7d+AM/Jkp1WqcuI1sg0hzR2mdse/wpLoodcUsx+k0A\nRx+If1jUTvS+vT8mMiuOoWcs+GNXZuSTwBZX+SSBYy5nexLF5n0c62yGhDfQ\nEvED1v8gVrdLS6d3jy9rR2n5OyenC9Q8S9zUzzqvLrRDbBuH+/aAZ0xusvDt\nmIjbdcE6vNMR58y/fu7NkQh/x58vLFBGAQAS0PJMHxt9QjFMhiwtELIjHEDB\n7JYL3qRS1tnyAt0C1H0NqrXkZD8HR9E0FdQuybyqM+KBIEl4KWd4Cx9moKuU\nGm9h\r\n=x7LV\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"9739884d35970597debd6e248fbe674acbd5f36d","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.46","@balena/jellyfish-logger":"^3.0.14","@balena/jellyfish-metrics":"^1.0.270","@balena/jellyfish-environment":"^4.2.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.4","deplint":"^1.1.3","ts-jest":"^27.0.3","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.162","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.6.8","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.1.12-renovate-major-external-major-9739884d35970597debd6e248fbe674acbd5f36d_1623518020618_0.4512751874419465","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.1.12-renovate-internal-patch-55991aa41b0bc9137ff0cb2beba8a7b89f482531":{"name":"@balena/jellyfish-core","version":"3.1.12-renovate-internal-patch-55991aa41b0bc9137ff0cb2beba8a7b89f482531","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.1.12-renovate-internal-patch-55991aa41b0bc9137ff0cb2beba8a7b89f482531","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0e06265011bbb7e8c15ea9da0f2de58472939d54","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.1.12-renovate-internal-patch-55991aa41b0bc9137ff0cb2beba8a7b89f482531.tgz","fileCount":190,"integrity":"sha512-H/ILFbMzkeFFZioHaRPILFccSznWv4tNAzUigEjwnLo3PJqX2PriHf2uQwWT+K7pFlRelyLDzOnkiD2XIckmVA==","signatures":[{"sig":"MEYCIQC+5eOLdo1g4fT+aO3IGvS9bfexOtOAOM4q6gF0+V8gcgIhAPvmGiReTREsz+2mPJ71+8Oh35icbVE/gZKq/6dY//lm","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":721165,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgxX1cCRA9TVsSAnZWagAARM4P/3fv4SnoC9tXJqthJb8/\nwYeCze6GoKeY8Br9XcSHJz5UmzXZNvZmauq836FYwwhjXxU6I+JY1jctXq/g\nmR0ZM2ygka9fdCsbCuAi8XMcY0yf9Xu4Nljt7pkBInvMQwooaBPOBxL+uma3\nMZNyNAQJVoM/tW/EJusdZDuWqM2tRF5F3DDIFA+3gaKqRpe2FLlOe5GaK4bT\nz7vlLdYMwle1P2z+uQwfcKM3P6IFV/iLIFw1I1IAYX1Q2lhcfGVjejYr7NTc\nga0tjsO+QC/oSw2fYaAyfZXlLpv9FYj1OY40j/pXZX06VOp+4EIFfep6DTxn\ndd9CcGf/Lja2iAU1mMiK1RViT5S2esSzbLSSoBN0ejv2fFTgRf6HjGhJm8LB\n+WrMlCW3L+V1sPPNHp+6w5BZDrLONCX+cvaD9BDaqUlll6rwSZnwiikYtgN/\neRig+Gb/jAJ0RQ6kRav1ekRSWvz5I5+WixTf4QSjY14kD8L2RrgSRaSw6Ge6\nfpuHVdGz+AXQiuPPrT6O1swZIoHcIJ4a0cbqnMeQqPZ8VUp5FksMOxYzds0U\niXy8gAwA/y7bKhfkhjJgu6XQtGEi5JRiC1fS8HKG6KKnsdycamW0pVN6opmN\nP17o8pM9r2KGkNBNJcuMkcB6pjTXKjOav6dzUn1G8fgXNGPtqbwZzDZetb9S\nsqVL\r\n=rbw0\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"55991aa41b0bc9137ff0cb2beba8a7b89f482531","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.46","@balena/jellyfish-logger":"^3.0.14","@balena/jellyfish-metrics":"^1.0.270","@balena/jellyfish-environment":"^4.2.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.163","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.8","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.1.12-renovate-internal-patch-55991aa41b0bc9137ff0cb2beba8a7b89f482531_1623555419960_0.09580308095973189","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.1.12":{"name":"@balena/jellyfish-core","version":"3.1.12","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.1.12","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"398e856b5c6acca949a94bb6aa07c46ef77e4550","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.1.12.tgz","fileCount":190,"integrity":"sha512-o//eh+1Bd2ZImTcKlCJv01Kl6Iue2iGTE2Zm4fuQQ0JIOG6ObCdNeK3vSWhYHH57GvoZJmDGjPYwg62gHm2mWA==","signatures":[{"sig":"MEYCIQDee5KqGWsALdtQfVc05sdfnxZOAtPYsftJQzIjeJFDHwIhAMmgUClbqoCLGu5NmE6WkjpJrDZYjv19HIIsKwCE56Rb","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":721100,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgxX6ICRA9TVsSAnZWagAAQ1oP/3K+rhKGRM9Sa4WLUXhB\nrMhUn1w61HTZC93dDjo818miA9/bm6AV45ryDULMonivKYAFbKIimrQDzgEC\nflFhMKO41g2ry8Vkn68IzbIuMta/6hx2SrM6txQX/YVO+oAI207d+h2vXfTV\nKycUBY50U5mVLE6T72BUSHAwHoYlpBDZbg06JLSShxVxAijRDa+n8YLZ7N2/\nglqaF4P56AnnJ+EqfWmZX9EgEETcfhnsRaEartUJbKVzyRbEWB9jc6jV1qts\nipTCa4YTbbNnhi+2DVGqTWpKFAHcpB4CRcNyWerQjQ/0UnnCvFEpd4lW0Y2G\nEqEfMkQm0joR5sgjsbBj6tbQpv/WeonU7Pdwznj3bMytKThGHcCU/L8+n6mk\nWPBRreu9zOmefDksYpHTslvRgzYIn6ZmjPbP75XWmOOskPp6ZcMEKEM34elB\naKmFcj+taOhpUqyZ37AA/4/61Ux0GraR2/c3kl4/mR8x7U6PXxHYzuKt1cF0\nxanK6yzrxmROVFXYOmarJvbF9HDkB9s7jIUiidsE1vR9nYAu/iBSvQTiLq7t\nblIk3KMXJNCqIOSbxLu/uKNxtILPaaCzhL0w+NKOZ2J8UZhCh9BNvCTY8WfK\nJSetuLrG5l7OBWpKlXlw8w/kBbMrrDdoLIS6Q5wWrwgncPbxF8f5CsSmvlFw\nzA1W\r\n=3ibD\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"790b52340991e12f61ef077b5ab5204f0be04daf","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.46","@balena/jellyfish-logger":"^3.0.14","@balena/jellyfish-metrics":"^1.0.270","@balena/jellyfish-environment":"^4.2.3"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.163","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.8","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.1.12_1623555720506_0.9793321726002935","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.1.13-renovate-major-external-major-fd6b04e94c1b23ec32ace8082b11564b1cbbb02d":{"name":"@balena/jellyfish-core","version":"3.1.13-renovate-major-external-major-fd6b04e94c1b23ec32ace8082b11564b1cbbb02d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.1.13-renovate-major-external-major-fd6b04e94c1b23ec32ace8082b11564b1cbbb02d","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"28e9448963c7b8c8921d50acdf28f0de2ce32428","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.1.13-renovate-major-external-major-fd6b04e94c1b23ec32ace8082b11564b1cbbb02d.tgz","fileCount":190,"integrity":"sha512-fKmjKw4/YXjCUObO5LIIDaXHOdAnp2w0uB/SWQL1NbSkPD7mhSxpvjoxwA6BePv+isCP6avelT/PW3sIENDHKg==","signatures":[{"sig":"MEQCIFB5sMUCpz6RXkbuJ7lJTCu5Mw+iR8T3gFvhdOykK9W6AiB7bhnqsRA0trWCcBFHls9B0JzXK7Ztv4jZssp2MV/VMQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":721242,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgxX+UCRA9TVsSAnZWagAAMhsP/iocgVlLZpAA/QZ8LZas\nUiKDiWicMYfgyMlYjATOulSJyN4/laKW8BNELU3elYZOQ0v+mwKh1BQ6rmSs\n7daVEDLgtFaereWmasXIy6WYJfFuHMjh+KjUFe6v/Y5QqgCANVL7Xt2rIUeU\n/2RqPNnRoTxbQCXJU5vqcQXmK8u9J/T3EwMz0MPzeprBdZUv7XR+ElU2TGdN\nKJjELSdhLgK8dqNq/HYCMsrwjzRN0VuHdagKSiolaUoJZOQW0Q/Yp8ee6+qA\nT601LNVn26bAoghFaIUKiXS3O423bXYxDfNxQgWFGCkGi9tfGYZTyP1ZxEsm\nPrnSEEVERqNgngjo+ZUNZ75CgfwitNYEJcb3T6vww45qy9ncDeme7SSlN9tH\nqL26s04nxGW9TbB62jJizQ14NFUWEFn9c4Ly9mpEo1OVI0s29FcG5vLRrdsV\njMNVuSmy/appr0mWwTCB2Llug8B2v7HH/esF1g/H0K7vTfaFn+HkJaGDgGaK\nXDTNM0enHPmoCRkjQaImD5/dDeNL6tDGh1wUiSDUmRg03C2hdRcXf8gAvQ6W\nYUpIOArRX0gBILcGPbaafMocp5FHxrnJzsckxzjk6xxgWi8a+7lwFWOzb08C\n/gOtZAiIfhTw8JXSe32MVVLvkIbpxXleaki7KnLNEYHC3GLOuO+K0ynV8S1j\nJbkC\r\n=thdo\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"fd6b04e94c1b23ec32ace8082b11564b1cbbb02d","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.46","@balena/jellyfish-logger":"^3.0.14","@balena/jellyfish-metrics":"^1.0.270","@balena/jellyfish-environment":"^4.2.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.4","deplint":"^1.1.3","ts-jest":"^27.0.3","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.163","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.6.8","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.1.13-renovate-major-external-major-fd6b04e94c1b23ec32ace8082b11564b1cbbb02d_1623555988094_0.09587059466513348","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.0-add-loop-field-ts-557df42a1e1583fe0d25713179048ccff8c764e0":{"name":"@balena/jellyfish-core","version":"4.0.0-add-loop-field-ts-557df42a1e1583fe0d25713179048ccff8c764e0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.0-add-loop-field-ts-557df42a1e1583fe0d25713179048ccff8c764e0","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"467dc93a5f5519f69859312e9c25e694a74ee08f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.0-add-loop-field-ts-557df42a1e1583fe0d25713179048ccff8c764e0.tgz","fileCount":190,"integrity":"sha512-tAyMdot/ecdnKobkkJqB/Bi+jXzRVzV7V0B7xSk7jAfmx0rZ4BbDofy4VVeskMEkmZe+YFasjP9ZUxLxDBBlwQ==","signatures":[{"sig":"MEUCIQCGGfDxo2xQRvGY4yCR+r5UNEM3HunQxKViiSXPT7wasAIgWWKIXQsG7yk5QvAj5GKWaTCvoNcYmiY4pNGQOHv3d0U=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":724406,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgxwRXCRA9TVsSAnZWagAANhMP/RM/s3hMbP19WNG7Q7C2\nTB1NujbV4quCtIRqVoxwWZlvh07FPqhDKugidvBAnSxSELG5BFl/OLZk+rAX\nUom7qGL31cThVgFu3ttg7ugewSl/vRGYeFzgVn1WhTFJMwgfPcNy5gzIL/yi\npMKGjYB3zPEfbk2eo+RQOgYpfd+XMe7XhIQ7L3ELjIpW+whOBdFw1n1MYl8I\nlDXstm2fCsQ4FVdsCqCC1f8lUa2fzPmpEmddPc1TcYc2tDOaP1/16HRQZBm7\n/pCuBmFHLo3hZJwnNZmR9v8jpRQ07EUrVNtUbxYEdH3dqWxwE47obHHJ6eZq\nYK6aie8Awzt1Ybb1xnBCswM/uJMNfiaQENynDlWJ+wamVMED4MjmhtmDTBBs\n87pbDPvPdotXI1krOcLvE7bcU3F4LvSqClA5Z8GPvZgfp3ObSpxyES+egD7R\n1DrccKFn0ycJDKm81txo6Vlm999tVs892pKZVtsnCDFfWG1L86XuSuRSQ5Y+\nJyJWGx2auv1Myt0l7A9p9rzFj6Dsc41Y8jLO+09xPaORSNchMJkO2TO5Gv4s\nci47FgajlarrC2qJwCxXKTf18iYJcBfzVhEvCwTZ+GwbCcCxTtptMbEn43Rr\nzT1MvhMK9AWek5iwGh4t6k2H1/C1+Y+NuxR5DeN4v6qHVcSuPxpFaKmzC+1J\nphI+\r\n=uuR9\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"557df42a1e1583fe0d25713179048ccff8c764e0","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.46","@balena/jellyfish-logger":"^3.0.14","@balena/jellyfish-metrics":"^1.0.270","@balena/jellyfish-environment":"^4.2.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.163","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.8","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.0-add-loop-field-ts-557df42a1e1583fe0d25713179048ccff8c764e0_1623655510889_0.6896457773377582","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.2.0-versioned-slug-07b219e248ddf7e060afb69a523bff1ae98da542":{"name":"@balena/jellyfish-core","version":"3.2.0-versioned-slug-07b219e248ddf7e060afb69a523bff1ae98da542","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.2.0-versioned-slug-07b219e248ddf7e060afb69a523bff1ae98da542","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"025e265f3cc11bc4f7faa48070e45ff319e6cc46","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.2.0-versioned-slug-07b219e248ddf7e060afb69a523bff1ae98da542.tgz","fileCount":190,"integrity":"sha512-R78/YnSMDlzSLaAoHb/sxPok7zmhuHRt1u/DtL9e/vzMbO+Vu64OEGq/H5CFBENtOuxvkYUl4pQZ3ETXq6MDVA==","signatures":[{"sig":"MEQCIBQnJ18UZtBxkv1vXLX5yCir7J8vGz1w4uSX3BftWz0LAiBOROAgj/U0BQR1/Shem6bq9G5XRXEIEPQ8JtbKeu26sg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":724495,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgxwt+CRA9TVsSAnZWagAA9jAP/jXq/NrGCMAYydXinm8V\n7NEif605y873wVrjrz9zpgDUq/widwqYVCXdlkUfOUJkwq2ktvUWK1M8YCmb\ntkGi+LfEFutc1O7wSkpaIJQIUwMhj0DvwEQKpouDs9TC5G7HjLvaY8hcktDS\n3bSWLIDuiRBvOzQ3IXD7Rr2pOxRh8OIB9UVxkOZ125VzNXCzG624l4CzbrTC\nN3+eujWSBgZG3YynLkZ0PKbJx98DwWbv06qYNYG+tYjIJB8nfctWDVi1/kWA\nDBHFkI2VDgWplFbpn9+exynHlQ1vdU+L7GN5nllFjVlNOlO9psRnZGvubaSA\nmXTDf/VUnGeQ7kkJ0D1NIy5gDF49eceNtG/QEeVf/uuy1c7KR4kKSTLkRzPt\nP94JzZINg0/b9d7mx2qMnNOpTntywhq75Y7UKhzLqTV9cGML3CN4We10i6Jl\n9z1MQaDh/aoaxW44cK3oIG8szTWSYVSa57JxVv5wrnkcNXOBM4g7Xq7IHiop\n4QX1VaEtV5HsIJVD/QLiu9LrbOyTJslVWLIckpNeH3mIfdowoyJiPGlqaiaU\noPDPDXg82biLX/ll3IpYwkUCCtqNdl31tWGhu15XHBAnkxPxeTwXmJtmAWeq\ngQY2hj3B4j5Vi2kV8xDL6BY4ZOvWxb5qpEXTOj9JlXBxWjUmI1HOXrNppZ74\njPFn\r\n=lu5V\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"07b219e248ddf7e060afb69a523bff1ae98da542","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.46","@balena/jellyfish-logger":"^3.0.14","@balena/jellyfish-metrics":"^1.0.270","@balena/jellyfish-environment":"^4.2.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.163","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.8","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.2.0-versioned-slug-07b219e248ddf7e060afb69a523bff1ae98da542_1623657342104_0.12129047017187267","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.2.0-versioned-slug-f72844447a3c67c6a1e0f2753fa1121fad5fc194":{"name":"@balena/jellyfish-core","version":"3.2.0-versioned-slug-f72844447a3c67c6a1e0f2753fa1121fad5fc194","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.2.0-versioned-slug-f72844447a3c67c6a1e0f2753fa1121fad5fc194","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a47f715b2f54ec95cd94c0148843fb763d70b569","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.2.0-versioned-slug-f72844447a3c67c6a1e0f2753fa1121fad5fc194.tgz","fileCount":190,"integrity":"sha512-HEBmyR/evFwmbFMTFXywUAdk8RAWPiryt2l+Fqzri2/dS+7QWrKUXsVX8KkRXkNpcixjyl1c8MB2cm1flKIT4A==","signatures":[{"sig":"MEYCIQDheLjOg4jButbMNApT4pUulB1rd9ce9dqOvWGcvc/1/wIhAJMngGTd5R7LKRig+RhQjED8gz/j4quZBUGb8KulERm9","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":724535,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgxx4tCRA9TVsSAnZWagAANXIP/1/S1BKTy96E6o13G7yQ\nWRL8XEquJgaOz4ba1fTSLr9GUiylKDtT0xUi9+kBkKq3WnGefvf6Sh9phvoC\nCCcQC1GzDjpMZvG8BLbFmtMZLHdgnLNBSpHZ0N8SGw2HwKRRMvG2B70Oq0mW\n+4x3yCLr8Za2Qs8NNj7gXHknVl1l6tnskedQw/RKP+ADx9/0MP9bxjZc/I5p\nimk8nuYfAjoeLGYl9ycug+IWUUgef7iB6FNU8bZYA8l0vZ+gUR6asCPAPaHu\nyyK3Nfo3+lQwLBAQhsMlwYOPsAZEoafQS/n/f03Qz7RjRo6eUJdtB+OA9aX1\n/+ZoUf8GnMEdz6gJBZUiyagFZEfbkLOX17rNlujv8HhSneYiLB0qKNvIFLiP\nukjq1eT7wWo0EZ8UyBHKaNjzavLjZO6Lx9N7EHEjzltkN/S5BOI4M3R5/69V\nBEXUMWBQ6LZIzZuyB1ZKJA8/ObkqfLbIU70m9S/TGcBvRH8kOWF1I+fZXW5Y\nnOIT0RfPd2OnangW1gyOq75yKpsPx/SrREL0RujfiTdpyB2CqlOJor5UFzHm\nEIL5sYcGgAipp1TQfdlf9FxGTdCGONG+qEyMgklsSwdoSbTpAQu+oHhnedjb\nTkAFuSuYmB4PP0I9dXNnjNG+/v7dPRnWe2NSDSptqV8zhfrNiL12KmcIlEXV\ndSCC\r\n=Tise\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"f72844447a3c67c6a1e0f2753fa1121fad5fc194","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.46","@balena/jellyfish-logger":"^3.0.14","@balena/jellyfish-metrics":"^1.0.270","@balena/jellyfish-environment":"^4.2.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.163","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.8","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.2.0-versioned-slug-f72844447a3c67c6a1e0f2753fa1121fad5fc194_1623662125153_0.5315106729952654","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.2.0-versioned-slug-efbdf1b094684a4cb9f028d86ed4892f955bbfe0":{"name":"@balena/jellyfish-core","version":"3.2.0-versioned-slug-efbdf1b094684a4cb9f028d86ed4892f955bbfe0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.2.0-versioned-slug-efbdf1b094684a4cb9f028d86ed4892f955bbfe0","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a2e3ef7f0432517d9b9f336796c4a8233930eddd","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.2.0-versioned-slug-efbdf1b094684a4cb9f028d86ed4892f955bbfe0.tgz","fileCount":190,"integrity":"sha512-a7GrFDOg5B+FgCvw0EESuHsSta0u8iUMAG5Yyt5duxSohyvIPNWFeitc7LL7yuscCJHlrQJL/esNf4ShFbPf/g==","signatures":[{"sig":"MEUCIQDqgWh/qINesNJlsnYSfgQWSFBTLHAC/1rmAAh//M5WdQIgAkot+J6LF4tlV/IRGtd33ixTjDKL1KnL46s71zwkf+w=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":724802,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgxyygCRA9TVsSAnZWagAAX8QP/0Y1IEqRbTFH4xT41THi\nij6O4/s9fDUfxBh/mTVfIQV1ozNRYmI/YlXfiMScYfya80MfcVi13Oy63e3h\nceIaFm98nAUSAfuGp0fQAE7VEaI8bxVlSEs3IG44DAiEW/iif/1KFMT65BYT\ng9dTnT/irXdiZjw86tw6XGqi5nU6PtVYAG0vsfN1LMkbKZnk+Bp7W25Q4tvS\nUpJ9F3bZ0eOjixqnJdo5w2OVZveh4GDbA/g4QScwNQZ1ALbbE7D9fyaCt6iB\nwjnuvgaEPROK0yBGjPEpTfq/poabxf8vKYPzDqLIHCUF62iqiCIk8YPCQxvm\nzzIUJfH3HOxlhwY/aRX8nutdHCoc4zxGyasuQYi1DuaJImZH7yrrrBMlLD7H\n6i2pM7mb+gNSb5bJu2gnnAmS5K1Bcbb7UrIGHWNzy7sEoNAUFSyKxvRzbYmS\nJdoeSFuesTNzLMr4tsEGR1T6ntCFSHqOlGM2xeyLLc7alPi8dKwRrR/zjvns\nHM1No7KY0vbFlHocE0QnYg7J/a5PB/+604pu6u0+hvuF81tnwsp2C/JUEmvg\nAZ2YErKOIE7xosxWphoGMfFjgvsJAap8rtN/yycYykHBl7n3gPSLtGqHHC5q\nhmNn6ARAbzc2IZJ9hNPY5ZYbIGLHjmR2MufECoQytisTODSbS5hAUYm4ybcf\ndllZ\r\n=HOXz\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"efbdf1b094684a4cb9f028d86ed4892f955bbfe0","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.46","@balena/jellyfish-logger":"^3.0.14","@balena/jellyfish-metrics":"^1.0.270","@balena/jellyfish-environment":"^4.2.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.163","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"0.7.0-versioned-slug-e7100dec55b4eb97c2d44ed3de2a6cbcffb17a4f","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.2.0-versioned-slug-efbdf1b094684a4cb9f028d86ed4892f955bbfe0_1623665824654_0.5826840030031901","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.2.0-versioned-slug-296db031adb90f7da5b3f2820764fad6aa9d845c":{"name":"@balena/jellyfish-core","version":"3.2.0-versioned-slug-296db031adb90f7da5b3f2820764fad6aa9d845c","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.2.0-versioned-slug-296db031adb90f7da5b3f2820764fad6aa9d845c","maintainers":[{"name":"nucleardreamer","email":"nucleardreamer@gmail.com"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f98ad77c264d36a0ef52e7b70cfa1f20d3ad31cb","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.2.0-versioned-slug-296db031adb90f7da5b3f2820764fad6aa9d845c.tgz","fileCount":190,"integrity":"sha512-oi3XTHSi9T1mXnGj5oaN5gI5D8W/EuGJdasbKDLRERLHi6EiOVTxFP0Mdfi9oGvOB8Dk5FHMerfXPOh1L8E5TA==","signatures":[{"sig":"MEQCIDjpI8bbyH8SLcipDBO3yq9AegbnuSaenYsBVCNhzrgpAiA6k0uS0G4p+gjBxRmMvnklLTx9VkIhsFDJFWcpyKTrzg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":725339,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgxzXOCRA9TVsSAnZWagAAA3gP/0+hyJF69GWmLMrcY4+g\n3DSjBx7uKt7xK3qTAMOHcCCmLhLTeQxQgGGzMWt2v+WWBUXgvBFvBJN/0X/k\nLi3GNH27YjLccf+IvHYXYbZxskwDIVt1HlKtJdNKqs+gRxxgwtukaTVwfe9b\napZe/W0Xtid3m3uz16LgVKYLRMH1Rp+797IyeH/qNuxthnlG3zp++P2gnIh6\nydVpSXMBvQqkpOMcm3LqiqlGyIuT6VyNiSjNIAKDWbuQpYaT5drQ7pEKH+rp\nhfxCzoagBNyxM9rdRb6TrbMc3r4Zee8bKY0Ti7nA6arVs4WWs+DvwlKHMkWI\njsKBdpJuV6rjsxkQQI973D7IRLlb9I/i/Ffv0UKicIl6SK0sLz3gqZHj6Ctc\npvBZDcmcS/HDAwR3EkgmhUrEj0Ln9M2kV8sa8ehtyfJ/ZlmbeNQwNoHJs15J\nK4SrF/5kMV1VhD8jdefQFGSjwJ77dkTJjkmQHJml40Zfm6KEiY3UnyE0IA71\nzUkMeCT3wSXdZ7WR+HZ/DAtZart1kp3wIX9mdW3eqP6x8M0Vjocz8aUUWWc4\nN3uG0TB4M+u5Zdtwm4oYfQD6yrD/nyC0tRpoxYuM7Id6eDimoenmftvDUxMN\ntf/VqDItzA7PpVMwiGEA2bk6NV1cWyMm3+X4ZKpWbYDQc3U00yVjlBncc5VL\nld/8\r\n=2hvH\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"296db031adb90f7da5b3f2820764fad6aa9d845c","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.46","@balena/jellyfish-logger":"^3.0.14","@balena/jellyfish-metrics":"^1.0.270","@balena/jellyfish-environment":"^4.2.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.163","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"0.7.0-versioned-slug-e7100dec55b4eb97c2d44ed3de2a6cbcffb17a4f","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.2.0-versioned-slug-296db031adb90f7da5b3f2820764fad6aa9d845c_1623668174605_0.9535682934883376","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.2.0-versioned-slug-1853e77437a405b711221e662737eaeaa4be6850":{"name":"@balena/jellyfish-core","version":"3.2.0-versioned-slug-1853e77437a405b711221e662737eaeaa4be6850","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.2.0-versioned-slug-1853e77437a405b711221e662737eaeaa4be6850","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ac616d8a0c15cfb70d96a1e48f066977dd694bae","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.2.0-versioned-slug-1853e77437a405b711221e662737eaeaa4be6850.tgz","fileCount":190,"integrity":"sha512-e8TtvSpOKG/gtoO4q0PxiQgkO7RdtqVfBHe8Nv3K70bg3wHW9uVJTkYff7vjn8CcVq/afaEP9piV8VXVuHD1Ow==","signatures":[{"sig":"MEYCIQDd61nhmLRZgzIyOwWvscdB3cuqpX7FFN6eTXxn2sibbwIhAJK3U9qlKQ/Y+QoL5pa7/BWhO4BbM4ErL6vocT+kpfAQ","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":724241,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgx/lUCRA9TVsSAnZWagAAHKoP/1n6WzgiHCljqI2EkiZl\nPz9nmxjtF1RHSTJ77MlF6jI4B3KNmDsjvU9TcbO74QtF1X6cw3u4HKAg0CcS\nzX32/L5Mi20i4PVjSP34fJ17bP8/fVBOPUCaiRDT5bIaPawiwX0ViGA1Oflz\n7Tj2GZQwFBevvsb3zVWKDb/gayF1jNqwl8C6x0nFewsakSlSP1arPj8ub2Zg\nR0wUnlts9h4v+KGonL+wkyaGqT3uheHc5IJRaZL39hsJbadrO+L3PrkVQnW/\nGcFLaPztrPY9j0+7tsK6rkGldzhbySPrmt70CnFFa+zo+8pI9mPm/O65kWQE\nGpPBaSn7CeCYcmD0s1UhB+P6NGvUeIS1H23lUMcFP3xn5NLGvS9ynp+lF/1Z\nbnTHoFycSgnPeQ3EHlBTGBcALCJW1pOW5D9aVZtrTv8PteGk31UoVweqHebz\n0Nr9zjj5qEmsDBZTnw76cABTYopikW1h5SC52Q/Bb9mrPl1wEGibl1iDuAds\nVIQwV7ru2FYvSTuSsD8W3k26IIKHIpitTNJvdgcrbuU0rz2TmtXgyuSEjd8F\nVPWPLJpHgej/9Mb+qv/HK/kIE5caRc4jIDRRuJy6R0++aVZWGolJeKAk/Fwe\n4AqJkIzYXdU56SNWS21AMAZ1zp1QI6jO3FfjeXlG5C1kBf4d1xzcanFoNO1j\niS3c\r\n=Bsa8\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"1853e77437a405b711221e662737eaeaa4be6850","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.46","@balena/jellyfish-logger":"^3.0.14","@balena/jellyfish-metrics":"^1.0.270","@balena/jellyfish-environment":"^4.2.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.163","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.8","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.2.0-versioned-slug-1853e77437a405b711221e662737eaeaa4be6850_1623718228009_0.7486949152278277","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.1.13-remove-relationships-b6eff9bd625751e78877edb9abd8639a71cda410":{"name":"@balena/jellyfish-core","version":"3.1.13-remove-relationships-b6eff9bd625751e78877edb9abd8639a71cda410","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.1.13-remove-relationships-b6eff9bd625751e78877edb9abd8639a71cda410","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"fe88664947d437e85ce984354f7e876b32063e6c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.1.13-remove-relationships-b6eff9bd625751e78877edb9abd8639a71cda410.tgz","fileCount":190,"integrity":"sha512-gLitRryo2Kc2ZW+FS63Bg5Tj1za/uFD0oXtp8aEG77TA1nFp01j/v/Lh3ulmBTvoxQ08LY1im44SYR2a+aq+iw==","signatures":[{"sig":"MEUCIQDm7eA/A/bm0u1DMUyLuRhflbb4FShsAEe+9KU36j8argIgawdOKQvDrXUvhIZ4A5/f+gA+moPwpgcfIQ91YF5XMkc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":717538,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgx/w5CRA9TVsSAnZWagAAZcUQAI2V9YMTAS2dUgSxvSWK\nwlR4e4qehjb1bzmOoUrQZL6cOZaYzAMghcX23wg6n9m6ZhVNgSHCF2Q2s7tg\nUZkB8tF22+EaQyzilZ5tkUcV0FLi7RT1bzJkblCSJIDeh/mtccsQneIG+uOL\nSKN3dVfMq72aiPw2yHQtndiKIPkenriuHtK9xy/zc7BmCZutL9MmZ9yVL/X4\nA5myAPZwqAhxPHpQ6dAmJwopmJGx7kYo01Fjkijm2UdnTfL2E4Pl/aUViY1J\nkFfgvKFSfJSNtvnsj1CP1JpIq65vOHV4lUHsHK+SLFTxKFP55pKAPhDcgwEg\nvUXdHsr5OS56bdcvdxs3w8ujVVd99biiGCALocpX5O/hzlhu6sw0DmrQMEn5\nncAQq3xDD9hoqBSme6V1AET3QFzcpIrTCC10zeEPkTHXIfEFG4Lf1j8VSiXh\nZ20pSFPxByRbT+j99vakJXe6c3T5XcQyY+qkhqkRhy/lDeK4ijQS1rs816Zr\nQY2KBjM9zMQv3rYLKcWFI9aZD1pX/XbGRgjimLdRhE4R8mvA3SjBXAnLqzB6\nUoy0IYI1nM2QSpQBEE61sXHuk7EIVRIRx3o03st54YPlE4t3wn7FKgzpBKUu\nlzMwIPoDGygEiD+9FZpnZO0gt+m7P2B6Tqce/SdOCPmK/7Osxo6+Iv53m4sP\n3/N5\r\n=ZaDv\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"b6eff9bd625751e78877edb9abd8639a71cda410","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.46","@balena/jellyfish-logger":"^3.0.14","@balena/jellyfish-metrics":"^1.0.270","@balena/jellyfish-environment":"^4.2.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.163","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.8","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.1.13-remove-relationships-b6eff9bd625751e78877edb9abd8639a71cda410_1623718969504_0.0637390266465836","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.1.13":{"name":"@balena/jellyfish-core","version":"3.1.13","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.1.13","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"dcc1575141ebff77311e743d7d4c41f850696ca8","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.1.13.tgz","fileCount":190,"integrity":"sha512-9htN3cMpeDAYNWu6MkNXEhPrzY0T5Vd79umn55VI/n2Gyk8vWdQXV25aFfiTFwSzxXv5e2y+72PEU1sKvDYVvA==","signatures":[{"sig":"MEUCIQCiktZfyR9fbE3gGwHcD4IOCVG3nF/nrHjm3229UQeBIQIgMW0AEFkR3uvIP8urZmECUpzWOugu1uLx/AW8jT+jd+w=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":717476,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgx/1uCRA9TVsSAnZWagAALeMQAJ+Y9/jghw7fNpuJoSck\nCfOFAgZGIpYEUiP+jDgu+Wk2d8Q30UObK2GmRVUXLOq2I87WnJ8uHvNYQVqR\nU9l+olpY1W0Evqj+geyE03TLNJaHfGXbd6rjdoj0RaD9ldbZemFRpqxGGuiN\nwt/VCgnb/T5XWV/ipdYRTRm/rZAX8eX8XORcBpYEQI0DEqpy5uP3lzaP6m1w\n12G4lFotxdhvTq5M7wD5fxh9H0siIjDqGoGGVRHvaBgtS+zN7QT6XMEXpKwr\nPrPbuSVvJVmv9Vk3tBzNyKE1b5ldTyPFG+NEqOACKknJdfEsS/8ekOtt/ZFr\ncll44r7hBR6I6PAPyL8VUZZMfe/39LVZTt8wW9L36IDZrPbUqLBHEQKZjk+5\n81sUInYF9FaLIYfk5pFjc+rh7uxkPV1DzvYLrF8ynsymKlOPiabPnXWwLk5k\nb7i372CHoydRbePIBFNaYXWkOB0raH6yrlpa4JhE6aFnREUyqkYYqYo2bNM2\nft13Eb35Vv+kytl4GmQfWrthYi1IwlnG/+NqAmbtJWtQQhgCp4F4AWQJKv7S\nOwdoKcXPvAkJNXHfaBy7P42+hioz5851KsUFWGy/wNcQjifttL25jmbYe6+f\n8Tv0YO25x78WTY88SYX+YyPzIA3vCy30jEuDVKuDFDW7NnQdqCPcUPwMKREV\nyGGk\r\n=UD96\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"10e911f2e474981bb4ea6f269d09c4d47de6b744","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.46","@balena/jellyfish-logger":"^3.0.14","@balena/jellyfish-metrics":"^1.0.270","@balena/jellyfish-environment":"^4.2.3"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.163","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.8","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.1.13_1623719278499_0.31603212500373323","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.1.13-renovate-internal-patch-589d1667d951d76e04d69b0f30d8ae9975fc0a7a":{"name":"@balena/jellyfish-core","version":"3.1.13-renovate-internal-patch-589d1667d951d76e04d69b0f30d8ae9975fc0a7a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.1.13-renovate-internal-patch-589d1667d951d76e04d69b0f30d8ae9975fc0a7a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4f4136261456083d41a0d20b17a1f4c6ccbd3ffb","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.1.13-renovate-internal-patch-589d1667d951d76e04d69b0f30d8ae9975fc0a7a.tgz","fileCount":190,"integrity":"sha512-Gp2wbB2v4+HhHI8HFWVxK+LFmpz78gSkZY/GuSk25CXIi6OSDcCjOuyQhbxLLJbRC7rJ8cLAJTbXJWjZc9xApg==","signatures":[{"sig":"MEQCIFf50M4TEjJHuLxZ4ZDKWUIgnsAmcAe/6NoXxC2z5T5lAiBN8mowAeWU6pt+CT9CceYifudH4Ilv5/vz5HvXQVVcvA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":721236,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgx/4kCRA9TVsSAnZWagAA6Y0QAJBEEAH6eb1y9T31Dheg\nxhSBawF8zU6cZaOnTm6WeOskbVAWon2bhGdHnSibYe035hIlsGMvj5gmOuTK\n69RTGmXAWWtwInXEMWa2b/7ZYrzRN8maphJWIb2blYvCIpN1qqEZSL7JEAuH\nHAThXlA0zbiY3CX2IjFY9/HxYJag7jQ83Jd/GcJoOKh7yZb++mSGM3m6yt3W\nKX3GaE4mKbZ0l6SnGsKi0Fnvpl8FtMuEcrwt64bI90X6kbdHgD+pRWZIhU6j\nrBiY2RyESxshbwyw5HRd1/Z+YLNRwVCOdJAT4Ds/1QCaiTA7PuOUaMjiR1hg\nmaiCqWaD90Zqahssfa3wercYC/fbVsKCzGl66x7h2mZyA4+sRXceRkMy9hT1\ni+5FWzbzRHNhMpty2KF6mkQvZfrSuC/tbX6mgsZic6P8WmmQ5Sz5wAHWTFYL\nssgRADdFKQ2NbBqKBdnehxZwgISI4me3H5M/zhMdD47HYyJOq1p7KE335zgh\nxab5fythWjmaudEW+F5OGg97ChSMpXlbkPbNx1Vsub7ym7Q6FvZUL+gBiMH5\nUOy3qM5sGfHPJb4ytvKjFrQTn+OMHNYvSkQ4qjZsMI6G9whv1bpn1OxNhmrN\nfmW526Wgs8Bui5r3fUsKMWtLQbbaBSZwZJijKfYDCwNUCSPjNazyAWZc5D3V\ndJoc\r\n=z8cj\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"589d1667d951d76e04d69b0f30d8ae9975fc0a7a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.47","@balena/jellyfish-logger":"^3.0.16","@balena/jellyfish-metrics":"^1.0.271","@balena/jellyfish-environment":"^4.2.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.163","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.8","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.1.13-renovate-internal-patch-589d1667d951d76e04d69b0f30d8ae9975fc0a7a_1623719459961_0.13669869671088586","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.1.14-renovate-internal-patch-53ff7eac56b5481353ad397e15c5fdbb0a5c754d":{"name":"@balena/jellyfish-core","version":"3.1.14-renovate-internal-patch-53ff7eac56b5481353ad397e15c5fdbb0a5c754d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.1.14-renovate-internal-patch-53ff7eac56b5481353ad397e15c5fdbb0a5c754d","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"196833312215c369082758f67e4258fb7b9d6ade","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.1.14-renovate-internal-patch-53ff7eac56b5481353ad397e15c5fdbb0a5c754d.tgz","fileCount":190,"integrity":"sha512-F4YkMkmnJnaUeO+7T7B+Js9ukmvb/gEsmqP1KRR7RhMeAcfs+kYFJjdMB70ero22QxEEwsOM7IH8t8DsO3XrGw==","signatures":[{"sig":"MEYCIQDxDDDyuE8gLPyzWwH/WWEIigiYb4FUFtpPksHOxAm4+QIhAKM569D/L0CJLAns43GtCgSAwW72+JKpIvrmgJ3FNSCZ","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":717612,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgx/9ECRA9TVsSAnZWagAADFgP/irBkhZxdXwq+mjrMabE\nhsYEQ+TNokz3TpRbPHaCix1rMOrIQsOEWjzfEV+gZOY0skVBouL7h0qyyKDN\nHUDQj/GHfLGmaREkr1lX0d6uxdCfJkETry51fIZKosu48ujLaX4Bqqh4469g\nPhFioZsud/P44zh0Gwoc5OG0JKCt9leYew2eTYOK/8DkQ9aPrF2dEQmaCZ/7\nOpkG3oseEVe8JbQNrK+9TuliZPbdqR4saWbhyZS09c1JMcZkjevbXP7B1VJl\n+zx+FeFC2pf994X45pH6aiEEYCPXnCvSt8/x7kHtu6HC4IkDu4SkEEL9Ysu6\nW7cBVxfcew0ujRQ924z0eGvuREU89+NRzNMYsHqH8Vmb4B7QaGw2NinyGAh1\ngs2NhI9Xuzb1fGpYEGEbN6NV/Y2ovn5VmfH0PKOgCJT95jUUOXKSwy7TznW/\nQxbu7sTJq4mq25aKVk+k3xv6vYja7bxeNmqg3BEww4AKBci5YrlV6Bc+AMp0\nBrlLhjEnMQwqx9dXGcLiYp/U52cq+eVaJUzS0n7sRcca9P0NWcwrdZQ9jKHO\nM6y+PGy5LRK8y+P7y06qTyM1QvO1LXJmH6dlKO6SYAqcIkJS8mlrdDxFNJ0K\n6hcHjOzGjoLbB2xKxU+dOk7fvEcJpzILMXnkZB9pp4uqKOezbSWthB/dFDNE\nJJk3\r\n=Kh2b\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"53ff7eac56b5481353ad397e15c5fdbb0a5c754d","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.47","@balena/jellyfish-logger":"^3.0.16","@balena/jellyfish-metrics":"^1.0.272","@balena/jellyfish-environment":"^4.2.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.163","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.8","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.1.14-renovate-internal-patch-53ff7eac56b5481353ad397e15c5fdbb0a5c754d_1623719748071_0.062228509753412986","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.1.14-renovate-major-external-major-773911e7dfeb49c973e3398da254216b174cc621":{"name":"@balena/jellyfish-core","version":"3.1.14-renovate-major-external-major-773911e7dfeb49c973e3398da254216b174cc621","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.1.14-renovate-major-external-major-773911e7dfeb49c973e3398da254216b174cc621","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0c939edca2f247da5cf28c8aae8f33d1ae12120e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.1.14-renovate-major-external-major-773911e7dfeb49c973e3398da254216b174cc621.tgz","fileCount":190,"integrity":"sha512-cnmLux9NlIqlHbz6LrLp4Y1f8eFxgdox7mNuONTpJCqpK3HEe66IhLFmxSNHrstA/97qNtMhOPm3uOhn+AGFDw==","signatures":[{"sig":"MEUCIQDAyH7ps8jVaaMBcgWs17bWCaX8hjfslJR85JUfkuu0XgIgbN72aM20C7UfigqpJUInFcsixqAZjLy+ZI66uHJsasA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":717618,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgx/+CCRA9TVsSAnZWagAAIVQQAKKbpZyxxPOINrLeQxg3\n7QDw44fTH6sY46a69/CJ3Tt/Mc1vYt7UUZ/eRknCAqwrpunOVcggQNvFrtIh\nyu8Lid7Tw2+2JByxMH9w9jT4fPC6vzSkHrbRLFGvNhNprNiOOJCyE8hV/BVs\nlP9O13UFMs/sSJ6ecw3E4Z6CKzKyuhqGl3UWAPR0+vReI2qjE8oWtZaiSEfq\nkkgrMPYJlz5MH63jBNr63Re9s6VPgIAp4w9Hj5hpagoIk8Xy6jDBk8/GsNtN\nOxDgtCiUsecLK3vlqineAyWu2jQ9/qo69zwB5qSX9oLxoOoRVGj0OrJoNQJd\n1FG6ZzX/ZHHqQsQ2yLf0QMMMSEt2xdi/tfI4Lr6u/5/DUVx2uFCVIV6YhSJ5\nYG0pI7R+M36Bf0xYwB/rqCcNRxXg3m3ovJOA6o5q8ursbzeV2DREwgQHVSBI\ndtV2E6oPxM1EX8z3PfoMOsf3GqC2crlJx+JLqe0N2nXeYNkK4IS6Uwx2SNVk\nKddGimwK1rcsA3XOq/kmXx+HT8gwK5tDOl5f5omc3CNGQ7xFl/IqWCs9zbKy\njY8T8mJxh5ihlmgJ8M4CpB62YMI7VCIQZgLhhj2E3rvTVlibloLCapULjCIK\nfCX/i51dIvntdDKZMvSgri/LBThrZJSkpGV122BkUE5ESNz241T+RemA+/t5\nLJiN\r\n=Cg1J\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"773911e7dfeb49c973e3398da254216b174cc621","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.46","@balena/jellyfish-logger":"^3.0.14","@balena/jellyfish-metrics":"^1.0.270","@balena/jellyfish-environment":"^4.2.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.4","deplint":"^1.1.3","ts-jest":"^27.0.3","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.163","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.6.8","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.1.14-renovate-major-external-major-773911e7dfeb49c973e3398da254216b174cc621_1623719810163_0.46698364607128884","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.1.14":{"name":"@balena/jellyfish-core","version":"3.1.14","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.1.14","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0d752a0428e53861e5b033640988e3309d679233","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.1.14.tgz","fileCount":190,"integrity":"sha512-vYxIWWD3JE3PsDqkodXpCw7wjET+unuV2846GNwEByLhFFD9PHQodhOsUMFwwatKtdmP4TpNYCwseYnIScDiPw==","signatures":[{"sig":"MEYCIQCTudHuf43lEO6etVN8QRy+hLl7j2ZjF/iUt0JXULTOgwIhAPOCFAmbXak6cCKj7+oVgpDrJzgleTYxrNqh1z+kePI0","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":717547,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgyAB8CRA9TVsSAnZWagAALLwP/1YsgmePJrM2UQItSvUu\nmpSFLbDcawUWglUC+AkxJGGob5doYBAUnkpUf5aBhbqoWnXTOH4xw7whTh90\n9DlwqJa/ItQ0BrwierQRqtnOicnrk7Ta01sU3cSworNyub6KzcURrHRWWkcI\n7bW+eLvAFzeIa7VBQ+MeT9MBAAR/80euYnc3mKye4fb3etAHv8390kNWI3Sl\n9UEGGXPIvSX7qOyfk99+MpbcUsyy4xkvAK2IsVTSDlSM8ACf/JfjBemSKmLe\nn+Yc9IVEXjKUR5zwogLK6ZUHf5xQ8rPRjwayfJWpDktqYJ/fLFKmxfilZueE\nobf4axkkL0WzXHJ7rKHy7Kb2Yx4PZtRH/0NICclozMePEXtn3WxND+MllbBE\nf+zY0e905Le/IphSzhXLhss588JFWxe06v1Enic6jvYJ3t6BTXi8x+jhXt/V\n/D1kD+sXC/laCM3+8br1eiABINARZ7iyjPyhQSJyAXBYuPw/n6hXnJn+jeCr\n/OUrQJ2xmtRHNhaDKNWC6UlZZUUxztLrP0HHHxI1uYaiD1xRchlqdD/gefC7\nGMLjQ+b/ZoDl2c10svJxZ4lGaXVPGo+uuSPzT4CUFVxNsp265+KWmUoRFxl5\n8UvwEDFk34OgrAaEuwEcHfl+548xe0giOFh9sAlK031wixW5i45ItwAG4ERj\n1P0I\r\n=bXqt\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"37851316644687f3f06ac7d77a5d9548650af592","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.47","@balena/jellyfish-logger":"^3.0.16","@balena/jellyfish-metrics":"^1.0.272","@balena/jellyfish-environment":"^4.2.4"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.163","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.8","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.1.14_1623720060643_0.03252837988614865","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.1.15-renovate-major-external-major-20713bb4936a837f0aeef1c50da6538fa426483a":{"name":"@balena/jellyfish-core","version":"3.1.15-renovate-major-external-major-20713bb4936a837f0aeef1c50da6538fa426483a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.1.15-renovate-major-external-major-20713bb4936a837f0aeef1c50da6538fa426483a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"24066c35d9fb6b1043ced065f9da3df3cd7a1017","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.1.15-renovate-major-external-major-20713bb4936a837f0aeef1c50da6538fa426483a.tgz","fileCount":190,"integrity":"sha512-z1vSmm/jVHotaVZcA9vD0Y25Lc1Pm9DfJ7id8pnEOXN3G7gwSld/G9z0kzeh8Nng4RB3QanrI1086hGUN0DDQg==","signatures":[{"sig":"MEQCIAD7kLDgwhabpitc+PhLJvZbJJST9e4FnEcqdqiNx1lTAiBJFP5RcTV0lv00PIPNa9BlAiKdvg2P7uaCbRMB1kvIqg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":717689,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgyAGmCRA9TVsSAnZWagAA9bAQAIDdy0Es2bvnnuqJMXfm\nzsrKhoGENnHQNBwz8yBoukRH2rEoOSlJCSqd49WxJZ76CpUZUUZ+MgoEZoKN\nYcaWG3jStfzwxopqnlvzm+IN2B9VEmoTuO1wEtFUvfhet2Wwp00LxW013QWd\n0oVFU1REHILxuR7IelpuaGyUvPMDqEeRP9GfjpKI0Z3EDD41XyzzMrhtno7a\njp6YTsc5/GhqVrD+h3TsIB2/KO2RZMwOrZOz0wNLo+e8XxQHpUyVTdQV0jn1\nDpodA4wUT3gfS7wNREtZtVBfn6eRsHg2PWgdim2qgju3cbwZWJ6m9b1BJyqm\nYnE87d8Uw6NlD2SdMb8o9S5a9w9CP9GwqsrkdY0iT9QCOBKI9OEMRgTCH6bF\nXOF05ELfiB2l47z9mMBZs5NtYSrBlLik2neeaAhZG6SCSk2FonANO+hRP/xi\nIUoIwUvIvshVXSE9djf5mNuq3zSNnO/ivZ5RUXX/fPWYqskMBnXACdyqmVYC\n1xU4ZnWkhV4I5HGyfe2gWiPuOxZcaMra3lfg5/7xu30s42eGaiQwqHkKVSrA\n6iTNJqa//uc3Qod2XKFnUbAfgWG1eSt2hG7+4w4LirKlLDDKCIMTv3eW20Ki\nd4u6iq5xOAshG+SuebW8G5n4u7Yl2Tdky5f+jl1ZjDaNETCW43ZT/2NQcYND\nWA0u\r\n=4I4e\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"20713bb4936a837f0aeef1c50da6538fa426483a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.47","@balena/jellyfish-logger":"^3.0.16","@balena/jellyfish-metrics":"^1.0.272","@balena/jellyfish-environment":"^4.2.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.4","deplint":"^1.1.3","ts-jest":"^27.0.3","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.163","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.6.8","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.1.15-renovate-major-external-major-20713bb4936a837f0aeef1c50da6538fa426483a_1623720358378_0.5880487704745856","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.1.15-renovate-internal-patch-dda806fd9a7dbfd35841f89f35c225acc9f13ae8":{"name":"@balena/jellyfish-core","version":"3.1.15-renovate-internal-patch-dda806fd9a7dbfd35841f89f35c225acc9f13ae8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.1.15-renovate-internal-patch-dda806fd9a7dbfd35841f89f35c225acc9f13ae8","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"dacdbb761124e8d30f9879cbec0e5114726b90e3","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.1.15-renovate-internal-patch-dda806fd9a7dbfd35841f89f35c225acc9f13ae8.tgz","fileCount":190,"integrity":"sha512-YwRBX1LAXxxqOVV3pYerAh0CU2h+5QXsHiyICdvsh0pIdu8xcJr26POUWjAM3ihazA1XRpS+4g5hZl9+Nhn59g==","signatures":[{"sig":"MEQCIEGZessWwQjCZOOL8V6E1ZVo1SpALrtKJCExZ0PCu4mKAiA0No6SzQntnWpwADckKt6tNms7tpK3NGca4oYck4wF/A==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":717683,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgyA6QCRA9TVsSAnZWagAALD8P/iZ0J7vPckZOuZlBl40c\nxV2Y/YA3CgHHIAkTepwkNK8wF/K7gGfsONekNjFA6xI7RDixNid6NqeKNZvs\ngt+4w0Jd7UKc6s0XPZ6YLFW5Rfm0xZBWmRVQS7lLhVAOLCzBqCvut2s7mkmG\nD6am9XoDV1ZR3ADE0xGbsAV5pq7H+ZkRUxR7FCp2YZgi4VkxMzUM/exWsRyH\nEl9cs88DaK2NWdl7dOHeUs2kZBN0kxctGn6FbcWy6uJIFn7cLGOQlchMWN8m\ncCvj7g7revizfQuuKH16/Ui9wVUhIou6eOYihOxw4c1ML+ykb4/KvhpZgR11\nMvs7uEll7elQ25rvJOFq6WK1MQYOqbIor7ZrGEkbG+epvK+Z/vW1hYyBYdKH\nt8a5Ej/ihOKW6CgqssOedvM1j3H/6R/CMdlkjqfIqScVcMNouNjoan14cjka\ne5x3/w8Rc6y8u3F0M5IIWWpG7PV9wZtrDUNyq0n5NQqxDPSWFxvuHzDBV2s0\nF4RV33WipZ+XF5yyRqm4o5NSzkMHLbFRlNEFYLxjUivEW/L45WYrWNGl4qG7\n0g6aPdtfaIi/PMrkHhCFvbFIuWTLuPnrO4A+RFxcG6QrIMc2A5iRXDhp6XoK\nIhtH6UGZw8zYUH4m9sCHiRpV2DfoOxJD6PMyqGUS4D2Rqnag7q3JRO0qYJjf\nh+UN\r\n=p1LJ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"dda806fd9a7dbfd35841f89f35c225acc9f13ae8","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.48","@balena/jellyfish-logger":"^3.0.16","@balena/jellyfish-metrics":"^1.0.272","@balena/jellyfish-environment":"^4.2.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.163","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.9","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.1.15-renovate-internal-patch-dda806fd9a7dbfd35841f89f35c225acc9f13ae8_1623723664583_0.9642739720636619","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.1.15":{"name":"@balena/jellyfish-core","version":"3.1.15","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.1.15","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b296073c977509207c1d2bc716be8ec02fa79a68","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.1.15.tgz","fileCount":190,"integrity":"sha512-fKJPtt5Z1UzGsTFyPkqvqFKHVaRGKQvGSo7c/UFjyk/10NvZjZT4yFqJEDCuR6InSRIDgHegcYHintgCHeIt1w==","signatures":[{"sig":"MEUCIQCVzOXz8n32to6bUfSCv7ayFmJIkefJGwCa0V5h7lIqrgIgCffez+N6Yu+/yRYCr/ewrdUjV9PeK/MMbjN9IY9Y+Ik=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":717618,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgyBACCRA9TVsSAnZWagAAQr4P/Ah5zVxg5KdEtlL5YIhE\nKSO18i/f0ydhSMJkPslpUF7lbrasOXeMGs9z/ItNONbx75LIuP42NzgQSKbG\nq+y5lRT3nBaLz9lPVH9aSq100dLfdcimOMs935x9d55MtipZ7KnfnFwzzbG7\n92S9RdA5cIJXkeSlj+pxTkw2JejVxvn0KCa13TI8J3Gh1SXnS6+TBCU+3gHe\nQNRpxyuJ+9zJewO4lWJh/PRqWO9ZNS6ann/nmqKz2lJQi82AaOdqKnmsdaka\nYWsxqr6fhzPlZoQpV0h70NgXWZiCXLZrCU4mQ5w0xrx1/vRq9UYFQ16yW/RU\n4YsLgpmAh2Rx2qDHQFOENOEg1nXQP2j22IyTloil/mqf/GfIMXvdtGIX0BPY\n1kGz3h99sIj0gX6WUvWfDh6rgSaiIasA9PdFf2FYWRTUJckCaUrX1LgfC/89\no/egORJXTijTsK7cGZqDO3K8UMManb1PbDjrXM5E5of++NALTboq3qecYFd/\nmNwxRJwzaCZXRl1x31UgV6Bto+DgCW1yoWdaxjo7kGqQN81RKKBIsBXzF1L1\nTEd8CoSgwWByWWIr4u4i5Jw+jaZ8VP2mkeTkMEIl5jWiTrRmJZCC/5uZgaoC\nTM6jwN4D12hz5IhTg/Mnj1gyZxCe43eU3uZ/DHYDPCzKYceBeUHB00qmQcS1\nW/9S\r\n=K2tz\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"07ec36f9199966317a3288be3aa478e8ffec2922","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.48","@balena/jellyfish-logger":"^3.0.16","@balena/jellyfish-metrics":"^1.0.272","@balena/jellyfish-environment":"^4.2.5"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.163","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.9","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.1.15_1623724034264_0.6468871735554726","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.1.16-renovate-major-external-major-bbf5c0c6f4cdcea619d9a3a2f3c227b2cfbd2c38":{"name":"@balena/jellyfish-core","version":"3.1.16-renovate-major-external-major-bbf5c0c6f4cdcea619d9a3a2f3c227b2cfbd2c38","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.1.16-renovate-major-external-major-bbf5c0c6f4cdcea619d9a3a2f3c227b2cfbd2c38","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"bd42ff8b6e53ec2eec10ced32d03747b577bb508","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.1.16-renovate-major-external-major-bbf5c0c6f4cdcea619d9a3a2f3c227b2cfbd2c38.tgz","fileCount":190,"integrity":"sha512-tbpbTq4ILgIyOdAWAlfvEKpoY1xeE6/Cstmz89q3GRer1crPlsI4Vv5ObYG9J7MplCC3z2JgI+9bLq6NQqqRdg==","signatures":[{"sig":"MEUCIQCOJ9mE1A+w/1eMLsVaviDyJ3YcxFZaBxssADApkMfExwIgb650tyopbyFBfTVWPJJt/rc0tB9WdqywpKvWj7uBhM8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":717760,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgyBDpCRA9TVsSAnZWagAAGdMP/jYpBNQoRZYCxYy03nZh\nykMmPO9LVM3iZ6vG7LLKs8yBvtSgJv993EzLAPtDtnXcJR/HJYseDRZAanF6\nUtiNnrrZSCqNCLQ96p4zzw62n8akT0IIeAonHhswloCdDM5/sNiu1E8rSOVb\nHuUxwgB0cSfuYFqWAxVPxSd9zPo3EVMkMTFR+IHwISxoCNYWCr8E1NcD7rhX\nOs+ZBZTVW/FuJXTUw1TIMJg0QfvFvaYcXCOufUq13PWRAXejLR6YtQ4w7EO0\nnrg32RcGTjNkHTkexbiRXeO5HGjU1XVY0Old6H+C3u7Zr9X0m02S38Va73Ag\nb1/i6Yi+7gd4hh5lrybDfEPUGlD+i0QR27o06iqOQ6QeOmwtDBik+IIUMglN\nOoOHP0kSLW4FryAD8BDk/OALTPmMsXVdhaipgf8A//5kiOfIvsLrAVIBmnw+\nWnwxZbCdub8N4fneg1Sfc7+74WNJfkMYdsqXrh0+I7ZM41/eJvMOfqyNzkVi\nu5xIdclL7LVMLvU+1E9BDJGWC20oSOkayn7HBn/WA9U72FQLEE9z7JeiAXJ+\n/fp0txMMDzq0nvFQTUEL7ksMpo9peHe2PyHfJMKS6lZHrMc92hM6ZyOn2x+1\ncJS76L7IMQoW9l16Y2vzapPe+Wq+DesDxMw3FpPCRBHr5NZXsfq7W/Io3dfq\nBd51\r\n=GvF4\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"bbf5c0c6f4cdcea619d9a3a2f3c227b2cfbd2c38","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.48","@balena/jellyfish-logger":"^3.0.16","@balena/jellyfish-metrics":"^1.0.272","@balena/jellyfish-environment":"^4.2.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.4","deplint":"^1.1.3","ts-jest":"^27.0.3","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.163","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.6.9","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.1.16-renovate-major-external-major-bbf5c0c6f4cdcea619d9a3a2f3c227b2cfbd2c38_1623724265399_0.9084387470807356","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.1.16-renovate-internal-patch-60abd8fd660b6ad6409113ded557daa53ae8f6d1":{"name":"@balena/jellyfish-core","version":"3.1.16-renovate-internal-patch-60abd8fd660b6ad6409113ded557daa53ae8f6d1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.1.16-renovate-internal-patch-60abd8fd660b6ad6409113ded557daa53ae8f6d1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"aa34ec6a3a1581fda9468faf6a8867a68a6f8019","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.1.16-renovate-internal-patch-60abd8fd660b6ad6409113ded557daa53ae8f6d1.tgz","fileCount":190,"integrity":"sha512-KIzzKEJq8fvR27P+anGgIrzvxyy1oEZPJ84qVuq/f+ufx7eiNdQTuQSv038kBwYYyvPRX3I1nlsV4A5LO7e+0Q==","signatures":[{"sig":"MEYCIQD+tdpLZ4wgNqJpZPOwA2LsALh9BZITjbobBkWZVWIJZAIhAJY+xLesG4wIF9j45LcgGkJU4tnt83KT7Ci+ru5TD4+d","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":717754,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgyBMkCRA9TVsSAnZWagAAr/EQAKFCy2KdKGyH+zKFb45Q\nLnX38/8x2XBC32AnZCRodC1gRA/4azZSCLRP/vrNJXGe0lJLT0UB2hFiQKjW\n5QjLtuTtCelOzWmgRcPzEIpD1ueLGqrRQGnoTqxMnqHwUuHbRdrsdTqc6Y1y\nqboGVrxhXlJHnpE77jjzWTiHxOi+LQPlMd+9JKGbrFg5b0RmsgLH/TodFzFS\n75lBjOKCCr5kCA0n1K1pJn5NR5i7bCo5WLmGMbjXjsYVsGwJnPvMQSzVvhWo\nKVXUwz//M2Vyt/dUF9gtxbMSOPvUVnhTJ8w3GD9m532lYOw6qdur2aqdLQ7O\nGDwWyl0X5RcArUJ99RG3bHZ+2PiuwTpag2WfT50f9VC0qGpWUGQMMS/Nz5jw\np+wnz1gIiSOmBSxTyVlVRzMqpAqgohcykva0uuf+nmVyjGSsduBJlJEqgJxe\nYjFpx69JVK0K9hxuBXy2ykgFrTaSYsv4ZPkX87ghpmvJL1S/OIlp0X8QevBV\naeMin8E7MbKBYZlSwrV2AsV7w1U1+cx4FYPfg5831+bIAQDkA4wmxo7ec6rZ\nFHFsT1/2GD/xmLnJXuI2k5Ns93pi07OswkVGucmvCaS9TI3S2Gp71YEBiwum\n2pYh0ttXT9CFJVN8U0c6ETPtj1tYfqxKU17Gc979O0zsKnfA8h2CX06K7UUc\nsjUt\r\n=Ef8D\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"60abd8fd660b6ad6409113ded557daa53ae8f6d1","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.48","@balena/jellyfish-logger":"^3.0.16","@balena/jellyfish-metrics":"^1.0.273","@balena/jellyfish-environment":"^4.2.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.164","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.9","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.1.16-renovate-internal-patch-60abd8fd660b6ad6409113ded557daa53ae8f6d1_1623724836588_0.07365044053416336","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.1.16":{"name":"@balena/jellyfish-core","version":"3.1.16","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.1.16","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9e053fdf6c0b1475409e277610307c88883c86c6","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.1.16.tgz","fileCount":190,"integrity":"sha512-OLrRnjf0AVOPn4n4mn0+C0+7kpnaOaoJAx90bur09xzv3k3zSpjlOi2iSwez2aLoTMDTjuAtRNnj8iKuvQX5yA==","signatures":[{"sig":"MEUCIQCeldt0W+SuLiwmBLe0iqeHM74nNM/TCMJd+bkIairTSAIgO8inFbFlw9/P0/cctjHfD4THlh9+bdEYblxSsJrdxrI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":717689,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgyBSnCRA9TVsSAnZWagAA8v8P/Am4hOPNnPfsipC0af3h\nDd5I+clLSE6O8fkRJaqUFdcHRIkPw0nxhbvQGw2/5/VqVeC04nXl+zcj7Hs1\nBGCa+Q7OkKAQ8tja0FeOCBhASNS96GJ6GQFcCAjPrFPfNoCwoOZq9ZwfLpeJ\n+5dHWGn4Kqy7gR3FRvKgZTqAVmIQBY/c1kNffOqo4DvttGmTXn+19VYWtufx\nzzkuGDCcxruV/ZqAZaPOQq+/a3wdO9chr1nWE0v/rt2wjUSPcpzFv2t3EYle\nr/uA9eofuDgG0jf2I3Yi5qqfcpIbFu151FckZFbAQbL7tIDfb9Bg4bu1dcU5\nh723RbFfQpyrPAD2X7EmNuQ4LlCxyIvcfk72ZEZVacCFQBVwKxbhIXQL7CLa\nTW5NmytY0PrSmnuTXHfIj5gmp9v5wDplCY2VOZ97I8siWHJqol9i2v3m9XEh\nMJwwLU1hFfVI7XjdttAYOcH5sxKZAuKAAOd+cmcvRPzp0rAhlOVuWUkUQe8v\nrGc+hhkwH9vOaXhQbmwjhpq0PB4PP8BotI4JUL/rejwqH3Gmew6BAgSV+iAG\n+QqLBeCkizLvGwkCmRvUzi1+yx1P0Uj5poz5d0vTwKl33yGNZPVYGr6w7CDg\n1KG+GuI8xMQWyHBM4f8uH8nLAWYi7LAiWxcUNdRb7vbDHryxqO6VLSzEqSRG\noIjp\r\n=Ck0K\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"d78e3443fca14a1411c3b0e832af5c4b5ee0ba20","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.48","@balena/jellyfish-logger":"^3.0.16","@balena/jellyfish-metrics":"^1.0.273","@balena/jellyfish-environment":"^4.2.5"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.164","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.9","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.1.16_1623725223660_0.8279035998106725","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.1.17-renovate-major-external-major-0b88d179e5f194cf8dc8876b7afd446aefa5bad2":{"name":"@balena/jellyfish-core","version":"3.1.17-renovate-major-external-major-0b88d179e5f194cf8dc8876b7afd446aefa5bad2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.1.17-renovate-major-external-major-0b88d179e5f194cf8dc8876b7afd446aefa5bad2","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"7f79a311b1c34f4920ecde74e24c1d999f704fdd","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.1.17-renovate-major-external-major-0b88d179e5f194cf8dc8876b7afd446aefa5bad2.tgz","fileCount":190,"integrity":"sha512-aNLlPKSVTKcog7UxlldnIdeWItQGKCK8u7USXY8TSlFGAxPrgqABld7F2SW2yFvnKbyaHEhQvD8X2LDse3A2fQ==","signatures":[{"sig":"MEUCIDfAgKc0iifcTcj9S5UGq3gC2j+O4LEtcsdtqFCbpvnTAiEA6Ttv2pLZ23bZAokmQBVCtCJC+EYgB2PGSKD5cw+x8rU=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":717831,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgyBXnCRA9TVsSAnZWagAAQxYQAICditGTOnAkemsmGVDp\nR/G9S/xUQxxAS3GHFZ9fTLMztS6hwnvlSrC+R0AvE+L7jSr12Rz1dzvmjK1L\nh5xJuLYbPg1ivEMDfpZbKSrzhToEwCG0MyuYyhZrR1kYXRxhxcZe5ag0D79a\n66vTOsVYoUmssLL9XCN3HaZFR12RZaJMmPjcP8DaYVB+0yz5v1JeqSqPpMqu\noCUt14xO/+wO0RK1h1l9XtvHCVddUlPLIwmW+EhnGZXlSDj2WNvX2zDEegQM\nz+Wz65urUSP1+QNGKyRrOGflzXPk7eDu6e8f0pYGjR27pSf1c8IOgEoJFLc7\nBykNC6oZYe3HPDzHykN1VDX2/QV9co2h3XHoceTRqbX8J+Skx1qgM69wC5Od\nDFbAxvlH1aQvbPj8HgoCA0Vl86qaq63EjcCjSUsjNPDa8o9qXr6SBwBrtGVP\nPOcCKWTX9xp2q43Id58u6cRJ86P1lgv9fIKtpMqlgC684C7lDxqnN8MquKQD\na2tg87i8Ac7siPEHzTNu6JGjz2LsOfaRfSyajVgxxYOhE17fW8QTCkijOdL+\nIZlnsW7gnRFSUTrNMRn4EWED5wdx1chOFLZYFkTKyhx6g/BmrrkxVkUFKgys\nU0Nvtqnp977wPkUKyf5MfJIUCdhb23hwI4svbDdDbIEngizTmVszPBdbzoGt\noR7V\r\n=ORn5\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"0b88d179e5f194cf8dc8876b7afd446aefa5bad2","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.48","@balena/jellyfish-logger":"^3.0.16","@balena/jellyfish-metrics":"^1.0.273","@balena/jellyfish-environment":"^4.2.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.4","deplint":"^1.1.3","ts-jest":"^27.0.3","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.164","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.6.9","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.1.17-renovate-major-external-major-0b88d179e5f194cf8dc8876b7afd446aefa5bad2_1623725543358_0.22432344535698934","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.1.17-renovate-internal-patch-08d87668e3772c16ab32f058fab490feaa8e5d1c":{"name":"@balena/jellyfish-core","version":"3.1.17-renovate-internal-patch-08d87668e3772c16ab32f058fab490feaa8e5d1c","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.1.17-renovate-internal-patch-08d87668e3772c16ab32f058fab490feaa8e5d1c","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6c5a899a729c4db1ad3219e2e1897bec48854e3c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.1.17-renovate-internal-patch-08d87668e3772c16ab32f058fab490feaa8e5d1c.tgz","fileCount":190,"integrity":"sha512-vsyKKOIMYueh9kU1I3OLziT52LtD2jAjLLNfFN8RNWxjB6yrhvgpJJ38s5bxKYKjyDcvMk5Bna/81zZ0MwZ68g==","signatures":[{"sig":"MEQCICORxLjo95fG1abSEef/MiswlLPZPzr2rzCPnDJHrei3AiBMGBpTP/OimKdwkP6ZN8TA2Us3+4JxZMVVxr6wJNyxwQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":717857,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgyELHCRA9TVsSAnZWagAAEpAP/RHqQaniyCiSzgJHh7IV\nfDOOBvyOMBcvUIye0+LjFG3Ifp+AazFEpinES6Xj+1gSz0Xj87qsbRfErNN1\nML/zKhV7Nb5yn3bPB3X+YGyYhN+M8oP3cIXN/2LueTx+htyepKM/p1JTdHbk\nH1z519AVovwaHlLAjOlka3DddEhk9PSIVmEx6QBbAGuAWFtluCjk7fC5KNny\nH75N+mumTXoOO37WbSwu+BfneHSBWyPNA/iSvUF+tiXk8fnR+BjeAlfu0kQ6\nXSAU0XdC4/u5n2B+ZI2hU1ItINpu5WhCAxwdyhmVNDNuRoTpfobVnm1fve7H\nExgr3vqDDsfARx1QWEjkOiA4RXMQRw61XYEeGT9Y23v2jFxRw8uka+k3x/qZ\nBfGlFzYnJCRixZNw7pHurLdbqnkaYrH9isYoXLVRmSkbL1MftgEmUo4UZQVb\nbHqgmnaKOAZZPh5LFXO2yH67wbo4urUIyW1KyoZqcqK3nKr+3d1xvzWIhs7z\nL8wgErAb0jCVeR9rtSIdGIaHiUV9BeOCHK6mjZojBKGw0ohqlMgDb51QALZM\nWwcO3xjfCUNQOb7QuwQeoNT82H/VhX8WUJ45wp+ekcQlCtjn5jnoe/73/AkC\nOCr6KsuCzPf0NNqesZNqNhc/grj78xFB9JEwpMr3k4/jLZifqZ1HvV8H+vgy\n1s1S\r\n=/1g7\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"08d87668e3772c16ab32f058fab490feaa8e5d1c","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.48","@balena/jellyfish-logger":"^3.0.17","@balena/jellyfish-metrics":"^1.0.273","@balena/jellyfish-environment":"^4.2.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.164","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.9","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.1.17-renovate-internal-patch-08d87668e3772c16ab32f058fab490feaa8e5d1c_1623737030719_0.635430642168755","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.1.17-renovate-internal-patch-5428d4a4eef1a0c1834ef695f253ae92acd98af1":{"name":"@balena/jellyfish-core","version":"3.1.17-renovate-internal-patch-5428d4a4eef1a0c1834ef695f253ae92acd98af1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.1.17-renovate-internal-patch-5428d4a4eef1a0c1834ef695f253ae92acd98af1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"75099d84b3e8cfe8134ccaaf3859d06893d3de0a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.1.17-renovate-internal-patch-5428d4a4eef1a0c1834ef695f253ae92acd98af1.tgz","fileCount":190,"integrity":"sha512-yUhiR2I+Lvr/Cb6KXPdkB+gDNGxBosZV1qbq+qE5rCH3fDkNhj+Gp2XIxuWM9OFwFMRrAtMoJktcwW6ko9TLnA==","signatures":[{"sig":"MEUCIQD+AokU7Ob29eqo9j+1DEq3JMn5NSrd/J1gyuWTauToygIgIpa37N61qkLbOo2Q5VoN9bNo5MC0QLJdjt3zH2xDT2E=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":717825,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgyEQOCRA9TVsSAnZWagAAWr8QAKQzuTzb6tZPLu5/I3lY\nT2l/CM4D8YARKrWFhwB50bJP5af/DZU/ZpaoTAhta+Zd/CLVmNTzFRjF9kjO\nrfyGkGuzRW3VklR3UBQ/vMvf0PnufWPSE+CmTbNYV4yBxFETao8HWX91zE3N\nrvv/7Bpl08w/l/HP8yjD0JtUMNWoG2dhjOuy+uDeTYGKKtw2eb9UQxSFgZ7t\nxyl/qQU2xFLOVJc7qjCfwMFB0fCQqHytKbqrKvs5rnK7nwl4nMo523uxBs8j\npx9lBxi8UbRQ1uWKOPyPsIil4e2Zy+bhr7o0G5HBpQguYm2GVgKi2IiF0xyK\nSO1Djquut8l0RnfkUcBRg1JKQigkO8iyoVFXjV524VOucMpKhqJMqSWLGWEC\nD4Td1Jv+tNaKrioKrwt3o9XAddovQrHOuxB02zKs3gOqPrP+jlrPV3P7DYEj\n3YKSBDPsX+By+15xBmKFEmHpycrRWv0IevyRHvnV1uwGaaMyPTkpA7eeopUy\n85fytXFtjAUHZDVJhBJHuSwc173a2E5fsgCphMek9b4nwhOGT1ID0oU2jXR/\n3aOApgXY+J7G1XyaD+wCC83IwSUpBOzqwQiuEU747v8LO5Mqjb8wy0aWKXnD\nvV0HG3SzgPBEDS89iObzRGt5vVV4oSidgbTfDaa3T1E+y6NQNHKTxSv/E/MF\nIs/2\r\n=pOQT\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"5428d4a4eef1a0c1834ef695f253ae92acd98af1","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.48","@balena/jellyfish-logger":"^3.0.17","@balena/jellyfish-metrics":"^1.0.274","@balena/jellyfish-environment":"^4.2.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.164","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.9","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.1.17-renovate-internal-patch-5428d4a4eef1a0c1834ef695f253ae92acd98af1_1623737357936_0.8935356876120615","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.1.17":{"name":"@balena/jellyfish-core","version":"3.1.17","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.1.17","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b98b45bf5584cb748e2760f96c5e17af3b4bbddc","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.1.17.tgz","fileCount":190,"integrity":"sha512-Cm3XxBqJYOHQKDvk8yyPiRY0OG1yCleua7tnJtvFs2nemsa+XT5JoL9r+vAbYFtbu0FNyoX7x8ajMnpCSfPNbw==","signatures":[{"sig":"MEUCIGxjq3MUBCHfArqRZIEbPBCPgEVlGQw9oeQAhVviSrNtAiEAsHkyheN9ZXQq4+yoGYt1P8MTtN+iP8g1tSR4vdYmQQ8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":717760,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgyEUzCRA9TVsSAnZWagAAyQEP/jibYY8WXox9dDnqgQNc\nwKJoadB+Y1eam4d9uSeaI+hP9+ieC/13ExaOW+jk24XccflWzD628O0PJ04N\nGei/hdAuJmPGCcIScjr7wnjuKnK5sT5ws3LtxURPYyS4yYpogo2wW5Ft8wF5\n4LwuLBTc0l/BLNkrJtu2CLFue4U2KYeH1c8SV2KlOUpS4SFe7bEKNf/X5fmQ\nrwLk+0FTNdXGTQ4g/8e3XtBNO4Zl3UZpjBN+WEcBkGnPa+Kz2L+3KZKQBGCx\nDOJGoWeY1zxf56OsLFSWgpaxXIjTFVFjwVnuLUsY4lS4kXo0fapvDx1vZDkc\njqYRScoXLfJ5TdcbqLFmTJJ1/+YaN7AXilhpwqYfu3XMetb99Z9kG74MD3kP\ncsFLE8Px5wGKSaXtryijl/bfslaUi+gqNXTxzOKylrV1G4IX1I4hEe8TWyKY\ntwkdoWkRpMhcyCXmAmXTniV90uYrGp3IRBH6j8+lLnUOKwI3rNd7rnTG+Q00\nEC3xnvJxrLeOmooAuc9c7EwrUnwMtdA700XWCRD7ySjl3AhHogo+m4yJTwYF\nvVmFosPeSIv4DBhQMmYJIEDlC9hQatXIfgsfn8tsi1Hy4OeAx2myXI9I+xoD\nDp9eKI933pUU5tQMpaN9rKVOhvIWd0LsTuv4QQiZJpmPLroVoLb8EXQaz8ei\n8jdv\r\n=fLF1\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"6aa72a964f720bff76e96cf2f82689d6a3c8fdb6","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.48","@balena/jellyfish-logger":"^3.0.17","@balena/jellyfish-metrics":"^1.0.274","@balena/jellyfish-environment":"^4.2.5"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.164","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.9","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.1.17_1623737650932_0.8625821191299614","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.1.18-renovate-major-external-major-25b172a7ad58ea597f31415f08ee899b5df089ac":{"name":"@balena/jellyfish-core","version":"3.1.18-renovate-major-external-major-25b172a7ad58ea597f31415f08ee899b5df089ac","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.1.18-renovate-major-external-major-25b172a7ad58ea597f31415f08ee899b5df089ac","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"54e6e3bc731641dab5f2a4311dae7077dc098837","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.1.18-renovate-major-external-major-25b172a7ad58ea597f31415f08ee899b5df089ac.tgz","fileCount":190,"integrity":"sha512-B6DUPRo1//LNJIS5pszScl9CN6deCii+HwtfNEBjmu5X/1cSSDA3Ca/nWjk+cUNzp1Uxm19i47mD7FeRoBj3Mw==","signatures":[{"sig":"MEYCIQC0tQen2o2jTC4b9BSvIvhhqInRIQnwdinwysgaINGV5QIhAM1LOjS/RfwacrIdfnkeap2YT/V7D8ikScRM3DU0lJtn","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":717902,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgyEY/CRA9TVsSAnZWagAAxqsP/2KJ+qTXmJJb9baTBwZz\nCoRnklywgeKoG6DQ+cKRSCjjn0Fh48rkuy7xebhWMA3J81Dc3cJsG6HcApG0\n45Q96ojUZNoqtA+46SCQIUpsjxR0HsRKtzuy9cjTPdegbiYQAjPcGJG5j8if\ntiuCmPYLlf0UrE8zuMxtd5tMkGI7Wb0i9Hf5ER/GEjNGHdWXsfdKSS3un6mA\nQKJL0lGKPbMsVgI/OfX+1C+DAQY8XDywhFpTiJvEd1nNkAvrY9cnR0Ai0uqC\n2D56bwrW2AG4R/8VGzqrWXse2JGnpXPjjtTMaF0nc0XKJoYF85blTRXJ6Fth\nAzBqV2WeVGyfGorGeS9Va1/1a2pMumOltVpImx81zGQsKmmcY96eyroo/e8L\nRHI0ZzkUAOI6I1vBwoNHPchJRt5WPSrWCv5LgSH+mSoi1HA+fuek9vYkDdGb\nCeZbd+umgmPexhlM5yMvg6sAFuvIpHTEz0Ss3ytHnYdQhlRiuhd/oItJ6Rbo\ngnjdS8QYgFXSk6VgaWMAXryw9BScBFQM6k54WePzGFIVPpMf4ZEn0Lhh8YCh\n0MpOJHnt/pCj57YLwtgGya1TQjAo3F52aUUOOApc3X4AWFSrSe8GdJ6InrkI\nhUlO8sw2dWwqWki73mevIVgNvaAIvst8fJ2H8locPlyem1hnhW9O4atV19uO\nm983\r\n=58WA\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"25b172a7ad58ea597f31415f08ee899b5df089ac","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.48","@balena/jellyfish-logger":"^3.0.17","@balena/jellyfish-metrics":"^1.0.274","@balena/jellyfish-environment":"^4.2.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.4","deplint":"^1.1.3","ts-jest":"^27.0.3","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.164","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.6.9","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.1.18-renovate-major-external-major-25b172a7ad58ea597f31415f08ee899b5df089ac_1623737919649_0.41710259104706315","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.1.18-fix-indexdef-generation-7b308651c53f1f029c45d8fe9599099373805b00":{"name":"@balena/jellyfish-core","version":"3.1.18-fix-indexdef-generation-7b308651c53f1f029c45d8fe9599099373805b00","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.1.18-fix-indexdef-generation-7b308651c53f1f029c45d8fe9599099373805b00","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a850ccd7d9a5c3bd7444d5c39234285311fbcc66","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.1.18-fix-indexdef-generation-7b308651c53f1f029c45d8fe9599099373805b00.tgz","fileCount":190,"integrity":"sha512-IxzeKmZjkA5NcLjqWa63FwCrNC9L9bNRzKvwOG8sMjqMf6Iw4RFCVcnNWOj8PW+LYu0Z7KDUptufiI5ZYsIKRA==","signatures":[{"sig":"MEQCIGXRN3Ml7R1pIYOvGC4zgSKF5yCkuchl6IiFts+LZSobAiBMJaCUaKYXf2JMNyXN32rSZjwq5X93Em+/MHC8uEFcbg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":720231,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgyHYFCRA9TVsSAnZWagAAoYwP/RxnGC13UvjSaEuiMidX\nYuuycxfAecgGbJmCnKFn4gE+gGuTLGNzWM3fkLwrHcfJFL1KVVAr0VIOU9qx\n7JQ+tag+zHK9l+gVo6wS4KmuGTvC1ZN0ztmFS3lBH7yMntndyhp229EeZf7K\nbuHNqQZD5GUnorfTpXvOvqR1yjOvIyd8ApWu0m1vnqpopX52WLO5V92+8/8q\nyqRjVYJh8lHPPPDbsQ9HHaH39J2hfYQTUlETHL91ODvDszEBNKRncQ0z9wLe\nXqyh0A0nj2AmAOs7jBFEd+KHnYIehRSX0BIeOOCuOw6JgBdi0Gt879o6GKjS\n7+C8mS96Tr1ehNEV4QcGdonsTgtpBU/onFvsqSXq2mNr4E1NmzSKnIC54isg\nc+XrIVGfXLn5LUug+n8VqRDq473x/jDrrnIIqhYRKeqNIUQy3soeUwIJ6Q+Q\n4DHtc0ceaJ/XTZlgJvXeV/Itd2Q6FxwSaZIs4umeLJu2xbihXa2SGYNmz9cS\nIVU0fft6xRPWP8E4yQ2szJmtAsrtLhCjj/YxAUmrBhI/OqMK0QqA7qsXUZGG\niTvDqXyGHggl7RLxYrSKd5zRpOHJVM43d3cpSFAJXJeEmGzM/9VPXos8PUkc\npgecD+tOu86A39ewBe30E7KaDx0JeXYq9TxxM0HgDcYNUTMVWTXTEg+YcOeI\n332+\r\n=3nA4\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"7b308651c53f1f029c45d8fe9599099373805b00","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.48","@balena/jellyfish-logger":"^3.0.17","@balena/jellyfish-metrics":"^1.0.274","@balena/jellyfish-environment":"^4.2.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.164","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.9","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.1.18-fix-indexdef-generation-7b308651c53f1f029c45d8fe9599099373805b00_1623750148828_0.5968578652437753","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.2.0-check-link-targets-ae1db0714258c95eba9a8b8ffe7d971c7830a394":{"name":"@balena/jellyfish-core","version":"3.2.0-check-link-targets-ae1db0714258c95eba9a8b8ffe7d971c7830a394","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.2.0-check-link-targets-ae1db0714258c95eba9a8b8ffe7d971c7830a394","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e63eb506e7fed50ccc996f3956fa2a3514f6873c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.2.0-check-link-targets-ae1db0714258c95eba9a8b8ffe7d971c7830a394.tgz","fileCount":190,"integrity":"sha512-OjtKa7atLj5dJvJR8FwHNwpSLUXwYT4a6lZezB3x08ufu1V4rw+eCcWX5tPbpI/tWt5FuRc5/XRlr10nrlpmHg==","signatures":[{"sig":"MEYCIQCp0CmRMpJSc9kdOF7Zrn+AA7thXXWGGsJWR+mmSjXRtAIhAI+bjRkINNhFmQKAtHECfRuVBUz8OCF5s9PHbtc1lhJH","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":719255,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgy+KrCRA9TVsSAnZWagAAP9gP/3FcMUI27/6yUoKHH0iG\n3zzKNkKUdwwCdo+skNcxI6VVfPooZi8L0wP8wvMYThc3OeHb957sK/+p3ajZ\n+tunPiLR3HIt1kcQ3voUPf8nmEjl0+dPDDGR4rW0bq/SXla7T25/kZCegGVi\nO0PnwwKznIF/0g2SS62U0w69U38F2xy2HyFQo1dxKRqN725Bc/2wp3Ffy4P2\nmwqbwlD1CJtipCg4g/EbOyCy4Apukqa9nTrq1qAgYqKHWewxUowT3aEuBPN/\nSZ3xi012LdU0q4Viiv+OSKFZ5eLFuXV5HlwJuzst/au1ahPb4R80zHhnQhQn\nSVmOCiqOk8X+Gimy+f5ILIAIWLokUe7115b6frI9qhlzdFljJoKY/SOg8+JZ\nLjPsJoX2MGbYWa8I83UaXVtXcks5Xtur+uGcoEUT+7Y6hpvMDy6lAG489OdU\nMzlJx0WGORchrkoWoDG9fBa7cjhPpsqTTQYeqlwUgpC69N7uSMn1NkjCaqqX\n0RydaqjZyXHD78KdYr6m2/q66nDDqYP4eJoDvzSXrAmKFGz4XjHba6V2dTcs\nFb9/tRFl6CUYr4r/RVlJXc90LrEfO6bcxq12UPA84PTKF8ieF9Ij508gsFPC\ncgN91t4SJFpZiY7TgsK2IrLRQahjTGRVDqavbEaplGzRzug/kT8RJRj9hy0W\nj6U3\r\n=u+aG\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"ae1db0714258c95eba9a8b8ffe7d971c7830a394","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.48","@balena/jellyfish-logger":"^3.0.17","@balena/jellyfish-metrics":"^1.0.274","@balena/jellyfish-environment":"^4.2.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.164","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.9","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.2.0-check-link-targets-ae1db0714258c95eba9a8b8ffe7d971c7830a394_1623974570553_0.24156185540211061","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.2.0-check-link-targets-cd16cacf4f5d83ac791bea38e1ef8fea05a9c1d1":{"name":"@balena/jellyfish-core","version":"3.2.0-check-link-targets-cd16cacf4f5d83ac791bea38e1ef8fea05a9c1d1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.2.0-check-link-targets-cd16cacf4f5d83ac791bea38e1ef8fea05a9c1d1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"31c4485b69054eba37b433dced113c644e447961","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.2.0-check-link-targets-cd16cacf4f5d83ac791bea38e1ef8fea05a9c1d1.tgz","fileCount":190,"integrity":"sha512-iIUsfaJ2aoFG82E3S8itVbj/wFI/lCIRxgRCAWz+LrcD3uT4sdtDXtubHLUixifVjZrRZlgd0+OovyahVKcUNg==","signatures":[{"sig":"MEUCIDg0jTL2aR1y+cuQ5lwNekmG0dsiASgONX9FY6+LDnHnAiEAjbsPj0djJ4P6y6mM+yqPbDU695jsl8/jZyPa+sijAJc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":719255,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgy+nKCRA9TVsSAnZWagAA33QP/jY971G4AhujDcbN1Tuy\nXxTRZWUM4+EoArMWXz7kzerkACwCU0E7vfdMzBiirUIuwYT8eXXh78Ydmkat\nnrpfwxYkt7R4j2aDiflStbXYrBRYPJbqFenp8kD/NUo8+S30l+P7jttdFKah\nVhyDvK9VY4R2TnpIpHEzYt6HXITNEKV7PMoFybpFK5t119OFKD9Y8zrEfB2k\nSdPOgSmwQQIVcb3c2KwGCTVnHLgnbj0ehd7l5XGe4BZtFIIolgVb+5L7da5P\nFxJRBpuAxqHikEFANkk30TGxJD4uRr4dQagfCZ/9WfeabtO7hNrY48kI4DIF\nFEsJi2UGxUKVmCjbuXPEwlp7vlka0M+tLmvaJOKCchWmGgs45Dl4vBPsDMgD\nPrdm5T1N/9nm0Ft40wfGa4tIVdfMRMt48s4JHKO5nLxPiStRAXbns2ZwUSJR\n8VE5LLnNTVf7gLws8BUDgWKW6o3M54REd0FKBY1WMnWgOtQQeDzyFhpnszRF\n1rU9dRzllmjqp9ZVtfpurRGiXtiQcXT5WazlmMsx6CKLs6eB89m+b7DWhZ2a\nEf/T5hTeHY4xrBIanbB4D7N5w39crcC6RmtLLpw5Wo0DDTLlXrDl0d6JsX66\nxbVi55lcYpHkVFtDz5Cs+Lr+4Q5z0Kg/IXGVzzyaZlJhqkTu3IbMcjjnKll+\nciPV\r\n=apcI\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"cd16cacf4f5d83ac791bea38e1ef8fea05a9c1d1","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.48","@balena/jellyfish-logger":"^3.0.17","@balena/jellyfish-metrics":"^1.0.274","@balena/jellyfish-environment":"^4.2.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.164","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.9","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.2.0-check-link-targets-cd16cacf4f5d83ac791bea38e1ef8fea05a9c1d1_1623976393944_0.17541989443066552","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.2.0-check-link-targets-bf499820409cf51fedf6f1e88f25bf959f3daeaa":{"name":"@balena/jellyfish-core","version":"3.2.0-check-link-targets-bf499820409cf51fedf6f1e88f25bf959f3daeaa","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.2.0-check-link-targets-bf499820409cf51fedf6f1e88f25bf959f3daeaa","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"abacb459a2ee67bc9c7bf2dd82aa56c5e4b2f6e5","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.2.0-check-link-targets-bf499820409cf51fedf6f1e88f25bf959f3daeaa.tgz","fileCount":190,"integrity":"sha512-LNlnDYPZBixCpYAYFnQ6IjZIUcUE9uvcXpYxWs6kVm3GVBRXKuDEOoFPd9RE5Hkl+RN/vAGOX9pm2A7qcg7UjA==","signatures":[{"sig":"MEQCID1/RuL0Lu7YkXvYj8dhWeJM+hTvETmONlWBHNW7zxxHAiB9iM0on7eQncOdRZ0PbO6RRuXaCXnZPhVSUJiV8K80mg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":719255,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgy/BWCRA9TVsSAnZWagAAcsYP/31uFuXGvKiwj8GYMwd3\nfMV2ypTFuj8bDLq5xz+ipkzAmlIvJTN2y07P39ZpF4/mb1nby4u1uMrEQ5LO\nZlplH6SH7XgqcEbB+k5FyMsd9VnYjnzCxPKDQZ+2UylU8cokRgthXNLk9KkU\n4yEykIdGtxtx5HmtrFaYRt+plLgVDA3ZB06nNxX7h4OGYZSPGprHVrG9bK8+\nVVp9p/9dPUuz++HOMium8UlbORzZRKTfvx7MoMQpYbk2sGw97vZ4wt1EBGrM\nXkJzWCdwDmEl5MK9owrX+qBMCCsaOsq1esz+dl24OQqRsc4DgKRrLZPg0aWM\nFUgcezvux6vHGuJ6isA5MYU9pZNns6JNF1opq+m5lT0bJIHF+cz/Vtqoafvi\nTr4WTxwvGVOr9/aLkzXV9n+RyyWLMW34a8UyTa73xEPo1hY5BX9mTVlNDal4\nHzyfNQAgPIIgMal3fiM9voS2YMZpgps2VJ+gJTSOQIKYHShLLA+pHBG65rda\nvaNykX5xtWe25douPpeaNtNAa2QXp6ZyOL/COMfr9zybBjp+zBHSuIO1vMdS\nXyJW/mSsDo7Y1k0VKZT8h6bV5qtgUD4ERDjF0uj3zBn/nm9R4afcQX+Dcvxz\nNizz1KXTI6bDr386cg9HWSi/ZxypplmmDhUZWlylO6zgjcehfjeiTuBHGC29\nCwl4\r\n=vQJN\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"bf499820409cf51fedf6f1e88f25bf959f3daeaa","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.48","@balena/jellyfish-logger":"^3.0.17","@balena/jellyfish-metrics":"^1.0.274","@balena/jellyfish-environment":"^4.2.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.164","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.9","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.2.0-check-link-targets-bf499820409cf51fedf6f1e88f25bf959f3daeaa_1623978069768_0.19815167725612826","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.1.18-fix-link-target-version-bfc7fd3dd01cc18d06bff32ca8ee4a5a1b2dbaef":{"name":"@balena/jellyfish-core","version":"3.1.18-fix-link-target-version-bfc7fd3dd01cc18d06bff32ca8ee4a5a1b2dbaef","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.1.18-fix-link-target-version-bfc7fd3dd01cc18d06bff32ca8ee4a5a1b2dbaef","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9981ae151cd64b12c973cf65e0c8808b1db74cb8","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.1.18-fix-link-target-version-bfc7fd3dd01cc18d06bff32ca8ee4a5a1b2dbaef.tgz","fileCount":190,"integrity":"sha512-FvFfg4n3/UM8gHd91IluiZY+dLW9N16SYhKaSU2QZhNEtuYaB4nSnbw/F96uKCpGuBuCHgVuE52KWMQUMDGzLw==","signatures":[{"sig":"MEUCIDwudJfCdnf0sK45Ep5TgtHvaTr4J7jQ1Fss7t0w3mQ2AiEAqJTZJHv32nxg+TiQXkPTQwNyaMac0pkuTPmVBOfhS5o=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":717887,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgy/bkCRA9TVsSAnZWagAAQZkP/iKynkDmwxnCpMDzoBN5\nqY6yGQrMmoGIc7im+ixzKKZFXQfpN7dtLE7NPqfVb53m5mc9Ua5v549/hC7z\nyy+nd8np7rDbwq6LrbX6J+j0B7AABjvawN2rmqgkDYpbHxmMSHGc4gx/GBq2\nLfDemTYjGyrP0GY9HHXsYkBz22W9U4gSR69jDOur/aMlki9NKV1BPfdklsFg\nxorQz9qZEjDqlJnmdXnnaWIgDky22N3JPYsHEeS4Tr4w0s3jfGxXX+m3322o\nL3Y9C88OZVqyvXM41oOuvf8fpsXieGQSW8ziadygMXlIh3GitxviViJc1JAJ\npbAzbRdEN/46YmpS8RnOInNWJuA0G5kRntmZm2syhrVD0RKVK2so14ijvWZQ\nGATrOYqo8FO8XS0DKgVp/WT9RTaIZwY06tD2pCE+mZKDRt3/K6OrP2HHlZqz\nYp+aheLpqohgYaaQvXD/z0v6nx7YYwTA5wFOclWc0s+5wEzBT2q4y8zVfYtm\niYPpGdXLCTpzw2T7roXmPTvXacdl/lexG5AlyZgh6F/qpFaKeVHypsas4pWd\nuk86yfexgEcrzXfHQ/d4/iQGyceYZHArJSaWiZ1XGhHVdqoCl5NYlU1LU/ap\nxY02iySibsDKwg13mwstvGTQ13Wiro4WoBZlQuklMjdC2B99Qa5xkuXwwtfr\nZ07+\r\n=AC/W\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"bfc7fd3dd01cc18d06bff32ca8ee4a5a1b2dbaef","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.48","@balena/jellyfish-logger":"^3.0.17","@balena/jellyfish-metrics":"^1.0.274","@balena/jellyfish-environment":"^4.2.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.164","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.9","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.1.18-fix-link-target-version-bfc7fd3dd01cc18d06bff32ca8ee4a5a1b2dbaef_1623979748281_0.31774610482202625","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.1.18":{"name":"@balena/jellyfish-core","version":"3.1.18","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.1.18","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"31cfe5890b3fac8d02433c66cf75e7530fb8a461","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.1.18.tgz","fileCount":190,"integrity":"sha512-bAgv+Y+ggQGoPoKp43jw19CNHVux0CwWUKpsLekJjwgm57pbTxSywJfrfsl1q6xk+iCKu/Le28KtWN+etjZq0A==","signatures":[{"sig":"MEQCIBazdIbbo++Ay+Vd3Lxr3dkaNgFa3S2anf1mGGLcJIlTAiBD4+HmIwNS/voQvaOw7e6t2ugK4LIr3UmxdIpffymQlg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":717822,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgy/gNCRA9TVsSAnZWagAAOoMP/3+9ZC6dWJvdSzZh4wNo\nIMVq7QZOzZfuXM9Bgqcs1XcZUy8KoGOee4yNY25yooCmHFSJKqQvH0XhJXVH\n1bJiedaV2WJ0we6x8yp2raSIY9mUK0VvSbp2rbt8FnrxnZj5rfdtcH1IqUXk\nXZtvoeFMoecd0MyiM3rr9MYoTWMQqzD5XlJ7uYpAb6YnLruK5VObrVHPeali\nMPI/2daEE9+5Q/pQPGP+S1nX97Faly76cRXnHeNACHyiW+hRwGn71KeQOaiT\nAnVR72J/3zdPrcPTCZ9SuHIyHa6qSgGJyVJ92ZmTT4+xsouFxCZxOua3DdZS\n1OpV5ly57icXqABgSItcvP253mWAbz6IRgKciCBK1eDJJPYPOw1i4x0Sz6zO\nJFBRYRSFPKSfsmhLKaZRJGYB3GHIgqwz6K4QCVzrb++M8XitlTc3VQr1eM/l\n3xD4Qjz7v/sTudOzontg9I9TOl2Fp1W2fYhMRLuCrxPjGrTKFCDtz720bO5b\nGaPsq+zSna3wZTeF9YEo69MwmWF+bOThhWXxI0bF9Ph6hG+8CUXfc5GKvxCE\nIH+rjoTqOuIsFofajB+bfyPvk+BdoeUy5tyd6785MxLikguUmvaW6BRk/Kjp\nK7xteG+/VVN5UYTuN9xbtjl3I37ToNY+lesUyn9/P24RYHAhi0PpM/yXGplE\nM3Gn\r\n=GEZm\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"893564bcc8af77612e7e5f5ab8787e72c01d328d","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.48","@balena/jellyfish-logger":"^3.0.17","@balena/jellyfish-metrics":"^1.0.274","@balena/jellyfish-environment":"^4.2.5"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.164","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.9","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.1.18_1623980045286_0.3284269859188029","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.1.19-renovate-internal-patch-0df62ccc271f892980f999783f001d942714d155":{"name":"@balena/jellyfish-core","version":"3.1.19-renovate-internal-patch-0df62ccc271f892980f999783f001d942714d155","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.1.19-renovate-internal-patch-0df62ccc271f892980f999783f001d942714d155","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"79f127bc810a4c0caf8c78c19c9d531f24c8e9f5","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.1.19-renovate-internal-patch-0df62ccc271f892980f999783f001d942714d155.tgz","fileCount":190,"integrity":"sha512-VQSg5NMU3anZH5DdSgcWbpxP+3i3rn7eqYdhelAJoWRqRPBpiixT0gg5x5lDTL+bRBL80ECY8iTI2b+HDpAVOg==","signatures":[{"sig":"MEUCIQCs7LkrlPAV2SZd/FHfzv2p45m2p6kpiLEyD/MHRTfL6wIgCYLZzLV8KBS2TFd3Rd500nihldPgxcr36IecDxjhEg8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":717989,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgy/m3CRA9TVsSAnZWagAAW8kP/jD79zd4VVo/DjyHcqYR\ngbg5l/RGB2KFU3C4n34quWYVQ8mz9evY6gO0OFzveDIhHvOxapgFq5wM2GQn\nANEQrl8urRL7cfgg0NGmfQeA1Ezix1NFkEKAHni8Wfnxi6m7k5OHe662fzPE\nfjHo8usCvzWyYFONev/XbUJP/j+k0EGt6+XM8/VOwxraPv+Qn9nuQh5MrpWi\nOD4i+dZp7Dgd8+923Yip8gQApN4I8jvXdzH/JIdzJrAZmyPx/cK24i3vSzgA\n+I3WrCq4z2wSD/GREfdT9hYZmXeFS2feNHCGVBCvwjkx4lrjIlUZ5zkky8/W\nBrGd2pfXl0sL31cpcC8Jr/MvturzGY7XEf3r5id2X4JQwNpbFreEolTZ7xHv\nwjW6zeNWMilwA5ar/9Gk3y3Hb584O05xnzwcgj6uIe54s5tkQwQdLaSxiNvl\n6hPyb8JcRmKl2BfAVe23xjoEl07pEOLJaE/UeBQipwnwqApS9uPDG/aMmRYk\nrSWT2UmzkMdwXnRg+hL5dolqvOaGsqhfeiLxd3BWQEEB9MqHG1uqULK/Ad50\nprEL3gMaLlIhmkwvwpIyofoINvXcpsK1m+E+pbI9gPprwTUIUnM3lc+i8I3e\nfBjCnyvnJAaXmn3It5kLaQkjOwaEh8aokBKylfDH03CFLjC1yc0qfgd3+hfG\nrdzQ\r\n=TwuL\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"0df62ccc271f892980f999783f001d942714d155","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.48","@balena/jellyfish-logger":"^3.0.17","@balena/jellyfish-metrics":"^1.0.274","@balena/jellyfish-environment":"^4.2.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.165","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.9","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.1.19-renovate-internal-patch-0df62ccc271f892980f999783f001d942714d155_1623980470692_0.10607211384228821","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.1.19-renovate-major-external-major-2232b8fa6da2f9a6f565c7f9c7126e632e2e9896":{"name":"@balena/jellyfish-core","version":"3.1.19-renovate-major-external-major-2232b8fa6da2f9a6f565c7f9c7126e632e2e9896","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.1.19-renovate-major-external-major-2232b8fa6da2f9a6f565c7f9c7126e632e2e9896","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"80cdf9c447a079c499143dd644efb1423fc152eb","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.1.19-renovate-major-external-major-2232b8fa6da2f9a6f565c7f9c7126e632e2e9896.tgz","fileCount":190,"integrity":"sha512-uXukTQqCsG2K0Lw0mjLfthyw7NUMR4V01pFvVpnyPZqyzAEwU6TklL1Ubb6TZ1K16hhA1FA0CBDEY7XymE/IXQ==","signatures":[{"sig":"MEUCIQCsNy0eYhtApvDn3M2EVo5dZ00Q3uHD7tqqmZwUmrwyVwIgOCWdiN12iOpy1dSGyyJe3vGvYmxx3L8Z2OtrVvelMf4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":717964,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgy/ndCRA9TVsSAnZWagAA+WgP/0RDGSDGYsOvmb9yz2qG\nh8vMz8uMKbhVHgl/CAk5sZx/SRUCL92dAsetk5ewz+dKNngcifMz9Ykph/aO\nXwxwu6edm5wE+qFgDHBjoaRT6f9XVnG+JmndMB2E5aPenm0wd5nXtOxm8kZU\n6GnjgzMVgRKguG3rsVpfqDuQX3v4N9bz48B+74TrbUglda2ldOd4aYARbvcA\no9NYhOLnjKCXmW5eFCcEpmEnvxTCAjy77BlhEjl+t/vkg48VZT0KAycU+Xxh\n+MTuPwxNumUEhyrOeeOdnfnbU4CIA209MiXRSxc23SVQKP9sXbgO3iD1xxvk\niyPVAK6qCN+NEXts7/vgcwSlrHgZSHdjh7vGSNqvPBUwidpD4wzMJo++leb6\nmWSwfdiNco8dLEZKIu8b3xCYVt34b9z8hXds3kxZmkHBgaKr35rJqCXsLAXL\nfnc2+l5wD3lCFrAZZ2Wk4SZtdcsdHlyuwG6HSwmo0ER9WQas63Hg+aG3Gm/n\nIJ+zHeVCqstWA+p/lnmp7wBt8tp/en+UF/JgYyueBXZBTY/95RhB5Dt0c1pO\no0wFb34dv0krzEQzjX6nsR99KOaytAhfyFNNWLFwXxQDTui4fUlqwoTC6duW\nzXON3vfkonZWFxHCvp15KC1C57OzZh7nyyQC2d4OzsSIBqnLNXCebTBuE98J\nH9UR\r\n=qfWk\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"2232b8fa6da2f9a6f565c7f9c7126e632e2e9896","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.48","@balena/jellyfish-logger":"^3.0.17","@balena/jellyfish-metrics":"^1.0.274","@balena/jellyfish-environment":"^4.2.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.4","deplint":"^1.1.3","ts-jest":"^27.0.3","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.164","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.6.9","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.1.19-renovate-major-external-major-2232b8fa6da2f9a6f565c7f9c7126e632e2e9896_1623980508572_0.7306154939886986","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.1.19":{"name":"@balena/jellyfish-core","version":"3.1.19","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.1.19","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b04f4c354b59f51d6d772e314bc13d631ad78cac","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.1.19.tgz","fileCount":190,"integrity":"sha512-nGlrjjgbfzKsB8aQxkRtRrEYFgyrLvZoohP0+tZesH2aown3XksGshUIn9oJJLjuy0omdFEQ8mlOqBTBfFLCBw==","signatures":[{"sig":"MEUCIQCCyI8H4EfQNGxn/MIZX19YNgmPLyTeZ63TlTRvdPae1QIgYT0KFzrfHNexk7V1yywlY+r3dTNTTQdxiu/e/PzO344=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":717924,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgy/roCRA9TVsSAnZWagAAZycQAJG0cYFLvqs5rmmYEHUl\n5bi7dAXCbDiZ4VFooIJknk5W3X2F3VOqS7zhK12wQFzirPrEKVNQjoLA5obo\nP3/t8uIftnFxsDz4QPMOVIalZ3iAnh9WEGfb6Yd78Gd1gANsTBelixJYaTo6\ngflaAEENNyKklEWmw5x8JUa9yf9D2eIdJn5OViQQc6yAtwcfUhnsPx7sbnAG\n10Lfqlnq/lFB8oCuMIf0HBdCDwOkg2Yz7KRwRzBQwuvwSRSYup8KcqavRnTo\nEttetOK6dYJK31KhIRP/xFr0kBZ4klE9qC4nc+mp7Ngo/JqIV87Q2p7prUmK\nicipB39md4h3AlaMa2z7kThSmAC9U/MZS0FkWFtktX1U7EpV+SNsoQfpGNed\nUO3I/yF3YuB/N05FbcMphNG19Q79SyTvCvSVHL5pXRQo6EP9abJS8eAA+Wf6\nYk4mNvFkCVKCNCNTi0sF5jz4pLz7tIX1qFBLX5nu8m44Lka3A6kdom1HNmq4\nwYJK9CNv8ffT7HOLzT7l+PoPRPX1f4Pl8f77qsRo5p/5rrUzxzaZNTInSdhA\npZcPDmrdd1duI/SWWafeFf9H6NwYSk6VuKp9iq/P+o5ymawOuCgHqFCZAsOG\nWuBzIiAwXNKASoSA1iAnZkseqk6FvmlNcFHjPwFWz5n5nNBsrEuhw1JNKh1o\ny3a2\r\n=uCH7\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"477abe07376dff85f506ecb1999d929f1b31f654","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.48","@balena/jellyfish-logger":"^3.0.17","@balena/jellyfish-metrics":"^1.0.274","@balena/jellyfish-environment":"^4.2.5"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.165","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.9","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.1.19_1623980776071_0.22234607905963943","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.1.20-renovate-major-external-major-8bbaea222a0ec721793657ec014072d5369fa204":{"name":"@balena/jellyfish-core","version":"3.1.20-renovate-major-external-major-8bbaea222a0ec721793657ec014072d5369fa204","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.1.20-renovate-major-external-major-8bbaea222a0ec721793657ec014072d5369fa204","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b8c46546fcb907e00ed6cc993021238b322ce9e3","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.1.20-renovate-major-external-major-8bbaea222a0ec721793657ec014072d5369fa204.tgz","fileCount":190,"integrity":"sha512-nWAdUq+ld6U9n+e4gnAJaMBZsIh5FfFZ+ytCzDvdVbLq9HduA3QYiaWkKXoyfcrCbJCcqLsGAXEh4lc9lHA2Hg==","signatures":[{"sig":"MEYCIQCbR+5sheehtGDT8KDZ7NTiwXnz4WiSqaMTwZExT+s2PgIhAPQt1UiUflJgDVFsQ+zacdvzLDXVMelX9eBPpLQOGeBY","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":718066,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgy/wICRA9TVsSAnZWagAAC2QP/AmBLZdPIZSMrVVzqwUp\nNVoVUuJhWR0/MlFh1TQBX75f2WUasHgZu/ILBvpQRqY/cj68B9/ruR2NH8gy\nfEumVgs5exENpO/hooB62RyVNQ7ezsXx9+IFgSWU9gacnRFSRafQQy4LjL9/\nadVXmuQCSSgP3ijjXjwgl08XPWX+q66sxm6ikAIyntetrNOkVqVCJjMgSdu5\nhlxjYHE51dfFAgCHHp3yb1g52BaiJ1XYCePwSw4el42pAwh4kr4uB04G0eyk\nU25kSeQ8dEV3xFWWH6F0GPgLxCd2+2CT8LELrpg+FxzjpnGMEK+6kqPqIQAm\nwyvrwdR/KvrzFbaz/K9aypLkWoUcYDudiCaFjwW8BgHT34NBIJg2LIcQ5c2b\nrVj3JOTPv0bvwqGw5L8nXSKU6IL7aZ8p8gR4URY1lda6FFw/xYxxhavabwom\nR+Lo8L8nEHzwspiUgLZUgqwMx3jARZyo4QhH7/JfnOg66zCeGFXwXbZTKAoU\nZnOKl/N0Dx3y8hHss7RpZwwaPHUq6IGnhtNzKL0VpPuVSYbSxMTEBVjP2Pvk\n6ApPLJV9FTm6uebZV58h5UerKeUySwTLipyJCqFelf4imSRemf7DOGHGoBoT\njd/X/AvSdYQx9vGfgkUx/Yewuy3JDacBRCXLsv67OtTsy9OxCUHiFbx0Usgc\nYOGN\r\n=OLP1\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"8bbaea222a0ec721793657ec014072d5369fa204","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.48","@balena/jellyfish-logger":"^3.0.17","@balena/jellyfish-metrics":"^1.0.274","@balena/jellyfish-environment":"^4.2.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.4","deplint":"^1.1.3","ts-jest":"^27.0.3","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.165","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.6.9","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.1.20-renovate-major-external-major-8bbaea222a0ec721793657ec014072d5369fa204_1623981064081_0.8940863804027257","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.2.0-check-link-targets-3f5752e2498c5f4566ba1b1f860ba24f5f5be05d":{"name":"@balena/jellyfish-core","version":"3.2.0-check-link-targets-3f5752e2498c5f4566ba1b1f860ba24f5f5be05d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.2.0-check-link-targets-3f5752e2498c5f4566ba1b1f860ba24f5f5be05d","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"dd4e5ce6a7200a8e6c50858c5324f182fa5296ff","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.2.0-check-link-targets-3f5752e2498c5f4566ba1b1f860ba24f5f5be05d.tgz","fileCount":190,"integrity":"sha512-Z6EoOAWheTAsyw3PCkh0YrYgUPnearRZYm8HMyq77aSqh0aOeugEfISaa6D3oduuBjO5rypJq4q9vw69mHQVvA==","signatures":[{"sig":"MEYCIQCJrR+IEbI0uK8EGnzvRICOGpsN9z/QGT19nDN60eqv7AIhALTG1OCbR2JjIw+M680il6yP6BALAAL5BS1XV5jbda1L","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":719515,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgzMXwCRA9TVsSAnZWagAA4DMQAJecr5GT2sU2POASMLmr\nqfAqoXeweOwZeabOCTVsrbz9nIH7CNxHdRnZ5MTTHlq6UQLf/fCszlyvEETy\nwI2KS8XCnnn00hbMRaXLN0bpnVcySLi9EixRk8njGovQra+ajimCWvSswMev\nnKSiFSEqS9K+fLmkHYFxBdjLlDo0y+hxUbUUdl4z8DSccQmi0S1xoHSeS0eE\nJmfTQAtyZhDyeA1ly0e0RLJBZe308wlGooYmbQs8JfcJu8NZd60HVVfCu+/i\n2w375lAWNd740Es1ZErToJregmSsPYXwgFEmqX3aq6SwwkjEffqqwbDlzKSK\nG/HHhw8xMMYniQsuerkvrHCFV+gqXmKcD+Ob+SnPMgbaKMlVhFHHXMhyB2/5\nwpAaBA9jReZo7gYGuj3whysbwyxhMT5DO2noRC458efs9SiuM4ZnL5cj1H5Y\n07KREGOlsE3900k8EU6e4WJN9z8SCRaDqGCSxA/wH5cE1sjehEol/JJbbI01\nPgLL+/Dl6jWBwv79x8gg78T4GTO6OgSRugMV0SHlHc4A4wUnt/swiQZTVxc3\n0NGupV0zQCJzc2AqgNUAuzQk8kSg/indCj1H/KOWtGBhyy0QA/UssTi7RXt9\napDxa5bLe4xv/nquz15V9R/f1gd+9cXPMOcYlJrVDy83dtFCLq1BhwratC0K\n3Yik\r\n=8u3k\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"3f5752e2498c5f4566ba1b1f860ba24f5f5be05d","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.48","@balena/jellyfish-logger":"^3.0.17","@balena/jellyfish-metrics":"^1.0.274","@balena/jellyfish-environment":"^4.2.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.165","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.9","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.2.0-check-link-targets-3f5752e2498c5f4566ba1b1f860ba24f5f5be05d_1624032751383_0.3268647069414168","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.2.0":{"name":"@balena/jellyfish-core","version":"3.2.0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.2.0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3278b64b3bf9a69ea711dd50b56fc8d47d0baa46","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.2.0.tgz","fileCount":190,"integrity":"sha512-mkeXhjMTUS6lckVJPQDdVfJLMXbXGiIkTZAJMjkNPObB0KpbExJmfSnna0lNcxrJZXAu5cnRSt5S7CA212Neew==","signatures":[{"sig":"MEUCIG9yrEOWOFoZsLDrUms8eCZv3tdJfcq1ejTL3pRgsD2QAiEA07tvkvNVq5Tt74/h8HwKJvKhLSV85nPyO8VPY1hHaKs=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":719455,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgzMdMCRA9TVsSAnZWagAAnR4P/RYQuHfAB+MKl0xaBfb0\nmuaD47nOcWGWRf2AqqDBBqLyrKNB4wyv8ikoQqJQSe2MzcmxlNl5LBc5i9RB\nFLlAusZEr7KaGS9fHvYDt30mfFIVW//RJ6NJ248u7zh+LHExiRLMiIwugzar\nqDOQ3/eZLbZgL9DBlUNvgJFU8K9HqYhMzFeMWQRafGQYrcMk8+viDYrAZZnU\nhVMSUkn7PU6Za0wp9kJvYzxDj/6DoE1ewuCjl3ybjdaZoVhF5JDXZ+FIQ/3W\n5P28DC4bexq8CRfkvKgQKsIKl6lwQt+oX544mpJMjLTdp6Lb/UmjJyW0aAfq\nWZaav/o6TfL6x+E6QkGfO5g5mQTsV2Auhq6HDILaqkh9pLgRV2zKsfhNUyEU\nvXQAuYyxbGThSJCrdqBO8pciGnFZyZQEC8QqSXD6bsvVabMJk2wtk91Kqxv+\n8BKeRfbFW77YmA/W6fYHWHUlFwhXpUD/qq9UcWihukEBnQWLXiHgkxEt400G\n5lOd8SU8b1sEwa2mI40yp/YISulMzS6Ni4GZPaEfo28/GeBMFNi5f3++R0Iy\n+gzDN6+BJRlRCHRk3MKDEd23okqfjw73V4rhctiVKmkprlWyDvVRNVZxuYuS\ngCpnI6zJBYzbHIsYUEAcnoZc+Dal3JAQQM/h3fU/jKrv8WivSrsB63TZhL3w\nB9tq\r\n=uMwS\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"c42fa27ec783db8ef9cb7c08b5457940bcd9b30a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.48","@balena/jellyfish-logger":"^3.0.17","@balena/jellyfish-metrics":"^1.0.274","@balena/jellyfish-environment":"^4.2.5"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.165","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.9","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.2.0_1624033100041_0.1530935928974233","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.2.1-renovate-major-external-major-1134256b881ca43ae00109c921b71d4b4e0a4893":{"name":"@balena/jellyfish-core","version":"3.2.1-renovate-major-external-major-1134256b881ca43ae00109c921b71d4b4e0a4893","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.2.1-renovate-major-external-major-1134256b881ca43ae00109c921b71d4b4e0a4893","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"271e1d49d585c1b535ef408d3a525f765dd3e7e5","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.2.1-renovate-major-external-major-1134256b881ca43ae00109c921b71d4b4e0a4893.tgz","fileCount":190,"integrity":"sha512-QLui1Pr0DZoZ2q1oAN+QWA6eNOy5b3RYTytfx+ftY0KlbxiMUWQ+7VK1nmx6s86lnZfe1kqOZWAZNmPFZDjinQ==","signatures":[{"sig":"MEUCIAVYDlzn+YB2IIt3a3S9Nnt8+Ixnz9r9hnutncW8BgiIAiEAqHnHHNDTM+OIcmu9cRGBTHwtt+oITm4IGnktAFrW2BY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":719596,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgzMjvCRA9TVsSAnZWagAAP4oQAJI8IlG5F5fLzIhbsphO\nj7tMufD3/7qr35rUPKUDu6P9eXKA0fggk3hfsqy3sHX6bBcFyI+5PWRfYS6C\n6eSW5E/w2wCQH6+/iQw86J9SQLfQwZHDIanXf/3dCzEFMHs1+9yX9tGcb2Vs\n7vRYBnnMOXcW/V0BtX+gPtN/nmeH4k9HQ4SNKuCY5dnxa+Dc7RCrlvUyFAfX\nDr2G2CpUcsyivM2oi99fRLHMWksWNo1me17Hl1STJcBrvbURdwRDDR/BeHm1\ngwCzDZwtCxAI4FgZ/uRuNW+NCIfPqYuCUsUVqwesc8dyEeuMc5wwg56oOEV/\nlBKtbR4RleLLPEulouFGZoUcXggV0n1zxIc8bG5tEcdBDy6yIhIidrLQnVtr\ng7dei78STDHZW0PGVb3TpdfPrywdGCN9BBAzqA79jhZXeFJxNB6/jO1KeojV\n7CDNef9nzQ8E0YxiX4j0w/7ehFjatNUkMJkxGLDUNw92SO4AFyz3j+/zfp/l\n/Emvuniz5EtbMKzTEL8r4471YiEnANLIf8aRmzRy8v1I2/QjqMD4tYo8b17q\n9u+u46ZA4ku9Ku8JXXNU9GdWoKLbHeuA21qZ8llAaT92OJCb5txFBzJnodHC\nPT2xEPEQTVeXi/F0RGNczegJDHwBJLUOp8p7aBpf2/f5uijyzhzEXWilYc69\nEk7S\r\n=l9kl\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"1134256b881ca43ae00109c921b71d4b4e0a4893","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.48","@balena/jellyfish-logger":"^3.0.17","@balena/jellyfish-metrics":"^1.0.274","@balena/jellyfish-environment":"^4.2.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.4","deplint":"^1.1.3","ts-jest":"^27.0.3","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.165","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.6.9","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.2.1-renovate-major-external-major-1134256b881ca43ae00109c921b71d4b4e0a4893_1624033518719_0.649264903323145","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.2.1-renovate-internal-patch-32deada756d9ba04b159d026e6953f99d55e3f95":{"name":"@balena/jellyfish-core","version":"3.2.1-renovate-internal-patch-32deada756d9ba04b159d026e6953f99d55e3f95","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.2.1-renovate-internal-patch-32deada756d9ba04b159d026e6953f99d55e3f95","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"16c6b7185a61b6559f528cb5657441da928b7d3c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.2.1-renovate-internal-patch-32deada756d9ba04b159d026e6953f99d55e3f95.tgz","fileCount":190,"integrity":"sha512-NxUPW1uo3tIXCBHW4RsuKCXYUMQQu5rO1bKN+p+lz6hnoyNNNjo165rBtpyyvvlj3o27UHQESds6wzhUWqKOEw==","signatures":[{"sig":"MEYCIQC1YJhjTnrziw+RttcDR8xmQkU4Q8RdJ8MadL963oWxUwIhAJYMZgn3TLz5Ed9v36FPBI1JpPGKMjG1rjRIYVCDe158","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":719591,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgzT8/CRA9TVsSAnZWagAAZZYQAJ9OiUqCxoDXuZdSpvxp\nq109oy8JYqWra9xyKdAecLey58JQggSr4YM15jios585Ee5Ixsqui0F5IX+n\numkl+EuUEhEQ232ncp//iQk+fGz2ZYQmxdT2T1JstGCX6Vak33XH1vwXKLdU\nRWMvmz10FVcQL9Pr1YXdg2d5dH+k3pIQwRdxSG4qZc+idJ4NZfq4LE3w5jQB\nEeQbD/tLNw6Roc3Ij2LW0eZc3swS5i1bb1jGL3l0Fjb8LmWApKHWnYgBXwpt\nd6Mz0gh7kGeePlnNIFzIfT7AwNlJm+J1oWF5qdlumV6fUmCt54ShEXpUZMP+\nRNUPWnjxIIlbYZYXkSe8Rr3hmcrZJ9Rma56uPikD5sXHlxbdlaHBNWHpO+Pb\nXqcrzFZPC8xkJx3j9LvCBHfVeH7LmsLD9Kg/DCRUMI1g/ZdcjDrrBN5LWrt0\n2gfu3KuikSiNMOqlHJGnW4lhiX93EMK5SVz7k7bNM0PYEW9bSdxWIs423tsL\n+qwg2nIVd9qhEcL64RIEksTzar+5nErob9H4svFl1xskfFsHZr2D+zmfKDGG\n2tsREUnc7sGSdMYx4FAoiYgSLFIjASeSd+gvovrReL9hy1Tg6jXxH/9gc68X\nfGAES3hp8kwtLlnweR2pyNFM/gris+Jk6thdbjgKWbuvdsI5aqKK+SAPGvw1\n1L2m\r\n=Se8R\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"32deada756d9ba04b159d026e6953f99d55e3f95","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.48","@balena/jellyfish-logger":"^3.0.17","@balena/jellyfish-metrics":"^1.0.274","@balena/jellyfish-environment":"^4.2.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.166","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.10","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.2.1-renovate-internal-patch-32deada756d9ba04b159d026e6953f99d55e3f95_1624063806518_0.9493892906328993","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.2.1-renovate-external-non-major-5d54b0415c493b1432cf994b2ecd17f2b7c1662f":{"name":"@balena/jellyfish-core","version":"3.2.1-renovate-external-non-major-5d54b0415c493b1432cf994b2ecd17f2b7c1662f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.2.1-renovate-external-non-major-5d54b0415c493b1432cf994b2ecd17f2b7c1662f","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2be93458577a78c38698954daa2453a9c4e0fac7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.2.1-renovate-external-non-major-5d54b0415c493b1432cf994b2ecd17f2b7c1662f.tgz","fileCount":190,"integrity":"sha512-DdKaGSm0BsB8iSNfUrm1wkhmLgECSwpDbUug+//sieZh8euY4MXt2aFWKlywnmmsA+Epsqpf1ZDvP3kdQj6ZcQ==","signatures":[{"sig":"MEYCIQD2VbiQxZ2FiB99dOaBZ74/5rxKGCoJsje0oll6VywxRAIhAKfyUuob4nNy3DQWx5465nM8chsXfegw2+LaXJnGjWqa","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":719597,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgzT97CRA9TVsSAnZWagAA73kP/281yZ9ba9Vg5CnAbY2+\n4oTOYy7KvDMpJYFnQUhm9PSvjw/4NAibPb4hCMdhP+lzS22kkQ4eVjuOfQh3\nK+8crrnqERArh6GyTwuU+KVAAJhGVRR/gSj16yaa1eWUAVFb8q2ZyRFYfJyw\nu23xv4T/rO/JkWRM6wGO6QChLDy6D/Qj8CDdFddh80nqbQ2UjM2u868//ZuV\niPwjGOEGwogfgyH0wnJznNbe9UFvHsqq6GugIFqT4wM7O0ByGK1Jlmop29QQ\nh+ymiGfN9Nui+PytektdtmiB2c8cbcgNjR/NntA26IpJnVNPCckOLcPjrpLZ\n32qglwJUwqPxCsa4OKudD0X+IArNKgBC0BPxHAwLzLs0Rr4Xd71s+/zucLRo\naZuz/4xM/CezLIfKxD++x00fH1KeuEymLIukCUiB7vqFHiOGB0mdDHPpkFO0\nXcaYeLhyqa+Y6tB/rSUX59nRiOaJYTa8mmjlD3Rdp5rXBQg7H2sigYl+d/O1\nnOUWHjuRKD3BcG3yJ60uA/0j4BjWWTvdMYhKY5DQ/z7K4FHuNY1T7mGUg5Vk\nqNvGTYuQVVtTFLL3XirAn3tAWPM9ov7G7xy6vkulQt+0xlRHfHgAe5mU6/Xw\nclkiw0F3JE4oMvN8sg4qYyNOMqVBvpDQWHnsREMdePBd+AKE0mPk6fnt6ZBg\nu/My\r\n=3bkD\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"5d54b0415c493b1432cf994b2ecd17f2b7c1662f","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.48","@balena/jellyfish-logger":"^3.0.17","@balena/jellyfish-metrics":"^1.0.274","@balena/jellyfish-environment":"^4.2.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.0","depcheck":"^1.4.1","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.165","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.9","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.2.1-renovate-external-non-major-5d54b0415c493b1432cf994b2ecd17f2b7c1662f_1624063866938_0.6818875092332655","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.2.1":{"name":"@balena/jellyfish-core","version":"3.2.1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.2.1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6d07eb0f549d97c84392d3bdeae4450c24ba9d97","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.2.1.tgz","fileCount":190,"integrity":"sha512-2M4RP3b4C3vYjsOG4dbD7yvIMeCoZ4x47n8RNnuBEkKog8MKjMvak7WblItGneq2z++dKWpD4H2maQCyr3tn5Q==","signatures":[{"sig":"MEUCIGWUDHEq1nyGI2P/NwqlGVuiK9gmJVVk6e4ceqf242UiAiEAqWgsNTVAp4m4IXhIpW/YPCoZvm63Eomx2vyNQmGZ860=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":719526,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgzUCKCRA9TVsSAnZWagAAhWwP/3vN/zdRriRAWp7Is3p5\nVeEYyap+SyKYGBWS+LtmU2Dh/2JdOJtc/8ewGnJhG2RJl93ACjHflEarnu45\nKf7Qw0V8VcDpbSKrD9479kWQs59ZedMs4qZBMA8lJuk6Wf/WbzQA1Wf5k1Ov\nc9LsrDY4y/8m+K0/Mv7OoUwCib9WF9VjxXXLEWBcYPbEPCv6IwsuSKnIou4E\nwkv/lG+IJ9DygGEgAGUq68bapLeyoPrn6w5yTMp2HRY1Lpec6+PdkOuzOUmC\njVX3xeD/1XvIlLKTqRNf3zBFQQtD+OK40LY4eVoB5sizj0rPjnnr+IhDK79M\n102ZQmFjQXYvZyoiPLcjwDjsNEsgsMNfjub+s3Zla3DoKGWa4UI+fhAbSuM2\nTLXAZBAEKqeVGE6GlrWJBasrhMVzVrnv/VG+xAvvy+LXO9CWhUCxC2FVuR5I\ncjp1p8u8reAtNuUuiXbBUWJtq0aCGf8MMLnFMEcjj/KdialZN4rprGHC4f/k\nEptQWqzb044ZeW5E/aVRsXy/OAITGJKpsS1pD6Op6NeLQdOGiKnb1OOKVLNM\nSNHQzCJey/3T33MQX89fbYAPpN6LBhZTm86XiNSbLF55jOkX7SeNUzZm2V9m\nIbXCcAQmA7fQiis8/Ac4noLoYvhNpwAvEBzM+ph3ZZoU1q9aczAxXzuCzo6o\nFNVV\r\n=nCh+\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"f2968251b35b2f073e46b6919d46ad26a7d99815","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.48","@balena/jellyfish-logger":"^3.0.17","@balena/jellyfish-metrics":"^1.0.274","@balena/jellyfish-environment":"^4.2.5"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.166","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.10","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.2.1_1624064137994_0.6812044977487548","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.2.2-renovate-external-non-major-31c173eedbd7fde77316bf9318404a142bd792b0":{"name":"@balena/jellyfish-core","version":"3.2.2-renovate-external-non-major-31c173eedbd7fde77316bf9318404a142bd792b0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.2.2-renovate-external-non-major-31c173eedbd7fde77316bf9318404a142bd792b0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e698ddc0e29c52f9ac59fb08fd6ff7cc3967ed14","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.2.2-renovate-external-non-major-31c173eedbd7fde77316bf9318404a142bd792b0.tgz","fileCount":190,"integrity":"sha512-iKUduc2kdcYknc4ns4mua0xkAy+AzNnTamqFJn+kB626y/DrN9GZ1HCQ4TXWhb8TVItz8iUf8aFuwJRL8eYVAQ==","signatures":[{"sig":"MEUCIEqI7VdW1wu248lMYosr5EUCifVyVFN2a3RzxsHyCbmRAiEAv55z0lXGfkAnrgrlOjnAEo9XXcFsybWggCJbwQBDt/M=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":719668,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgzUGtCRA9TVsSAnZWagAA+QMQAJyBKFlZFR/iI0YTaQML\nn6F8jsDF5bzFI9nW3/vFODRI+TARq7MsyaegS50ubWRuqbWT/vD1pszMqroN\nKlrd3zXWZlqf9MePBS22m9FpqbPITboBQLtVvOF2HDUbj+TxyPL/5r1CQTcB\nM1VUiP8uiBhqqEVHhnvnKcJG2mtg3LhL2G6K5ykAtpgCbb3hr3P99IL7XQpL\nlILkI2IRrklLE+OALhOPCtPp4ypf6BY+pR7Zb61xRFafVbsjXy4rwoObeJs0\nIamEQKR3r46c999rDgj0j2+LTaV2SwA7pndH8jRmY1WMF6swz/3THRKG69nU\nhZhYztccnebx9nEKQRN+3Pp4nRQ2bqxHa0WCtrzkvhmkI3OT9AK+N2nSeKep\nyYxcF+EcBaCYEWAmXe4qXaFVFko5B/h1BpK53yrdZbbUfFXZVrg+FdpgC4gq\nm0x18pR6OY8fFfurhKkJdWFBfA2Kr9X4m1ZBeFZ93ZvpFATKyP+DWljyk359\nhLyczrZrKUZSDKqb1HZnndvmBxmgtB8JCoWGwxYGQGm1TwJb7sbYgPtuXew4\nkRVfm6RcvFiAz28i/d+0Td85gmBCDihQhTJyBFQPHLuyc1J+cJvIgjMO4zeO\nX5upz5O3iypqGP2uqzvWUUGIS6ijcBnVtTeao6uCXVqV8268tFvdZyvD+x5h\nuYdV\r\n=VSvF\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"31c173eedbd7fde77316bf9318404a142bd792b0","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.48","@balena/jellyfish-logger":"^3.0.17","@balena/jellyfish-metrics":"^1.0.274","@balena/jellyfish-environment":"^4.2.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.0","depcheck":"^1.4.1","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.166","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.10","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.2.2-renovate-external-non-major-31c173eedbd7fde77316bf9318404a142bd792b0_1624064428549_0.4833549156197856","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.2.2-renovate-major-external-major-a9ea2af83da82635e384ec90539970b858f8ecfc":{"name":"@balena/jellyfish-core","version":"3.2.2-renovate-major-external-major-a9ea2af83da82635e384ec90539970b858f8ecfc","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.2.2-renovate-major-external-major-a9ea2af83da82635e384ec90539970b858f8ecfc","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"19ab17f55f2fef048bb1071fac637e14e94ba990","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.2.2-renovate-major-external-major-a9ea2af83da82635e384ec90539970b858f8ecfc.tgz","fileCount":190,"integrity":"sha512-2pAdBzwzTMKIRPiRvShs2bJ0tlqOtaKc+iuiZczs/V+tTWrsLrHqNK++CfT2QWW4I6bWa7/Y0TM4VfqP4ncHjA==","signatures":[{"sig":"MEQCIExqovuzTrotMj6ZryTNDKUx7aCVVMf55ZBak4MnwhobAiAmvwsahQxQUyIvex7tcqXDHZi50ZqhK49QDWBdJ3qNfw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":719667,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgzUJBCRA9TVsSAnZWagAAfp0P/1UcVSNsnijbuSkknMRL\noSqDtlXhiLRj/E5xg1GANVaGeWz3vFnTKkMYwBOjbAmyVpVCDzG9wVBAAy7C\nqWtQCURJZGEzNCg61ukW9IMnHjXiiFaO2cRK8gC4mWhF/4xQx3HD0zTZ1061\nQ8Eb6RO7diN4Km64ibXpfMJcRnUjfQN7NXXCU5ydcJmHg5VQEpadgi5INNYZ\nWUBc/mpwgW3qVbrCB19M8RAVZg2DhqCZoQ7YoFywPMBs+Xcuj8xH/i0ql0wh\nG/IdLbkLxjG34bMA+BESzjW6zgypQHkE3OkIQVeHkz5EzsWdSVJ82Qcqtla/\nNN2Hj+uiUA5/pVnGZGvo8omQXffdTQHlQRMRxnbkMEq8ORZ0G8JSgic9nQEG\ni69hDtGl18zeLtD6ZFVrTF0MMhZEJRkCR9gz8vB5KQ6nMVuP/u1onnxAEN6E\ndfpKMLPdsOpI+wPOcXza7o+Ux9L2rp5WtpAf9F9eDURvOkmLWbkpMYKnQb1a\nox5LCfuDrXffr8B+ryg1B3CQl6PBdVRYeEfCbZmTCkPN+lSVAl70/mT5P6pM\nwiYfbNwCe+l866ZTrhuArFWGWnIcFDI7OgUW75UvbTqmaZXB13EgfHePXS7a\nX1Y8aRs99hptkrcZaanFO1qmxMzTasPz8ieMymflAwihC75dpUc9REfw1scE\nbcKO\r\n=X6Qz\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"a9ea2af83da82635e384ec90539970b858f8ecfc","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.48","@balena/jellyfish-logger":"^3.0.17","@balena/jellyfish-metrics":"^1.0.274","@balena/jellyfish-environment":"^4.2.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.4","deplint":"^1.1.3","ts-jest":"^27.0.3","typedoc":"^0.20.36","depcheck":"^1.4.1","typescript":"^4.3.2","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.166","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.6.10","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.2.2-renovate-major-external-major-a9ea2af83da82635e384ec90539970b858f8ecfc_1624064576952_0.30483094653804277","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.2.2":{"name":"@balena/jellyfish-core","version":"3.2.2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.2.2","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"87a45e382d594f3706b5152187932525caf2c0ee","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.2.2.tgz","fileCount":190,"integrity":"sha512-E8LietRKJUsgcdHEp/Nnf/SNfjz2pd5TjN+JPnhF3AyjMQmw9hWPxnWfuh5YUs/UqiwTjXNG0ZvZfiA1fINAtA==","signatures":[{"sig":"MEUCIQCO5pO87W+kNaGySq/8dA/Hnw1hc4Q8+LH0YS95HVMeAgIgTQhbdQuC66MJUIjJS36ERXH5KGQQt4GqR22XzxyHLT8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":719599,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgzUQiCRA9TVsSAnZWagAAMPsP/RW0XkOnvjHmq2pDAV7s\nfB2HHKR+Dv5KGzBbLQavBzvCVHhKn+lDmBcwFM10S0euRtwo3tAbOcISDKMP\nTJQbYASxAHbADQPNO0BtEOy7yIGyA2x5b2/Oyt+IrVvkSNK1RImAk1R1VWVE\nf0pC1CKGkaCcTOVu65akiju61dZ48RxN95xp5hMZn/OtuUMhQgj3Evd47+4n\np6MkHD6FRHwyubY/mLVbnKhc+yR5AvCOg9WdfXNm79zCw2IQEr09EUMuNoUT\nfBPSkfh+q+Y9Ms00mK3L/QfG4Pcjdl+o6Zj8bViWKDadDM0saNNJ0Dcx89jH\n3z+EWPX8xVRPp4dJ/hvajvWfmTMDpWPADq4tz6wsRRa1RUlTgdm8agdvHbnG\nTRD26I3r7z/Gs9IGnXybj0437rP7/lI1ngLNTDQ61iepCtFmBIN83MJ8cB/l\nSd3C3x5S7d1EEkXLNAJcJRpziERdMTnDP3BZ3hv8ViBXdbXUrd+qIyu0nwju\nMNx+3AoxBjYC5QpaoGzU5az2R9qPxHSlKx7jV23iR+judNYJgTh7Q7S/aw2P\nQw5ndYeoI5Z0ZMY2UNMYXdjy6pA3182Nf5DXdo8pSb4qVzA52LTSomqVmS3k\n7rX4ebVeVXwo7RKFFS5ScIOlVKZXXbAkcmpkuAheP8JDvVdI34dQakKwty4w\nqZKl\r\n=oWLg\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"afafb63aef3a7df3773090b028431268a1365522","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.48","@balena/jellyfish-logger":"^3.0.17","@balena/jellyfish-metrics":"^1.0.274","@balena/jellyfish-environment":"^4.2.5"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.0","depcheck":"^1.4.1","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.166","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.10","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.2.2_1624065057951_0.8166102680855418","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.2.3-renovate-major-external-major-d2838d48762fd5a007fd424f5249e8c2a67f106c":{"name":"@balena/jellyfish-core","version":"3.2.3-renovate-major-external-major-d2838d48762fd5a007fd424f5249e8c2a67f106c","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.2.3-renovate-major-external-major-d2838d48762fd5a007fd424f5249e8c2a67f106c","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f9148842bd42d35466b05b356d8dfaf60ab4fd3e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.2.3-renovate-major-external-major-d2838d48762fd5a007fd424f5249e8c2a67f106c.tgz","fileCount":190,"integrity":"sha512-DVE6gfWjGvFM7QQHlRXwaOwM/DnJfjokK8+1uikQaHDkvAe7zLi8fUmvHOFTmdLdUgQaoARojZUl1lcL6cG7fg==","signatures":[{"sig":"MEUCIQD/iZVvF7cY7NPAXF9goPcbdlHR7rO3xMqIKldNbQi60gIgEkD8HCodL81fZDHDAtyScq3uFq7S/auGsz1LG1yLSFI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":719740,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgzUU0CRA9TVsSAnZWagAAj4UQAJCJf4k4LNRZ26IZUc5g\nlZ4aHchKAtJvu2IuAQ1I6oqDnbyi82kvL0q4Ult8VRJ80jf4IsvMiHJdlxDy\nhLF+uIQAvV8UOCDjevIwWJPYHzr52Mg7vu7XTO1ewyea8W4QeqigXavOyRKL\n+R1WjJsjo/GpVxkELIe2obohn5eqKoqxUSs+HCZT/Iv09SZVPMJ4pL5zvhIc\nji7fHrsAzi7U0KWKGUskx4fXkzXXNCiUPuWWvfFGysNHRAv1XisyeukZEwQG\njzLwYNfLQi9WJ/p4cIyhCIrwQE004yGzY1dG/LpjQAplToiOA/2trwlKYpo0\n9mTDaVsRAB1ZOtbSq8s5TrRhcXCWj36FXOg0Uwvxd3T4BuQwHBdAQBnxzmxG\nVAjB9HEmLEYIqX6j7ypmqDQJ5Aph/zViQoSUPs06i7UJD1gAd05p0oLlWp31\n0uP75NE463xGKbRKuXqsPQGHu8eFCjpSFwGBwzuac6DvekejQAw1NXzzs0mo\n7UjlBnFdMaGn2+GW3p4iIHSOXPHMWTc8jznRKuMB6rfP9+y9R7xCcI1HLjjP\nPgSmgW+nUA01htCzPUqIYp/NHSfBcH2Duh9WU44pS2SxA8BX1qaEm5yLd4+x\nOLmb1yTwOBGCWRz04b5ibBeN0L4HYMtmPEZSsbjw0l7KmsnEf0mjpsI2YBex\nUvFK\r\n=lFWc\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"d2838d48762fd5a007fd424f5249e8c2a67f106c","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.48","@balena/jellyfish-logger":"^3.0.17","@balena/jellyfish-metrics":"^1.0.274","@balena/jellyfish-environment":"^4.2.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.4","deplint":"^1.1.3","ts-jest":"^27.0.3","typedoc":"^0.21.0","depcheck":"^1.4.1","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.166","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.6.10","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.2.3-renovate-major-external-major-d2838d48762fd5a007fd424f5249e8c2a67f106c_1624065331949_0.3915740073044045","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.2.3-renovate-internal-patch-1db23ae9a9607dfe9306e1332857cf4e68718701":{"name":"@balena/jellyfish-core","version":"3.2.3-renovate-internal-patch-1db23ae9a9607dfe9306e1332857cf4e68718701","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.2.3-renovate-internal-patch-1db23ae9a9607dfe9306e1332857cf4e68718701","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b2eb55c2124b98ab8da50b2c39e0b761ced8e76a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.2.3-renovate-internal-patch-1db23ae9a9607dfe9306e1332857cf4e68718701.tgz","fileCount":190,"integrity":"sha512-OjbYEBYWFMwgic6AbDqbRFwdaXDvwy54XErQTt+Lv78n4K1f+6XUjn8MeEaSAdT2fqbx/W74XBweHPSucUx9BQ==","signatures":[{"sig":"MEUCIQDL7SVTcaMN3AaiOfVMqhcrAgFyJ63ySzzIbZnWUKJFKQIgeyvCcxUReqVVGNKuln031RkpXzCVS+QvoBHDaVDt/9Q=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":719768,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgzVvYCRA9TVsSAnZWagAAqBwP/A18BC16W1TTwLEirqVY\n+PtsWTGxHeFyyxXJFt2B0RnG79bB4ik+l83llD0UIfhYD2tkq0hWVYAPnbdi\nk5c4e89eHgi2RxXFCvpJ2HBrpsjpRwIwyLY81xgD1yRCNgAnzWO8huA9aEGG\nm23gY0PYC1tRjrDBRKltFo+0RpsYqAKOVd7Rro5uApJ9oCXVnrBqpt8nQgVd\nnTahl/9Izz93f1M3RpHs2jBycIwFGtKRY/MXVVWAkhBl2sTJlHLJ19uUsxQ0\ne14fFsV3I2rZLcrj7Vk8AK+yf4ppb31ekn0g1VDqxLU/4ft9vTet8FEA9sni\nzdblTNp2Z+d0oGztcomupFtsrWk9dm9CTg4LsaRIEoZErnyv+uq81rm/qXlB\nFIR8i4bx2e8WDp5ZeeLjSKvxfPZRTaM1TK3kaIcA3d2NYjsP7ued63tlIxvv\n24nRwyJjZH98Eu8MMm5UsgxEhM5KvFiFoRsmsPCe/KXjwEF7NwLir+O97DyR\nv0HXsfGTzw4PF9HFRkNAr2hS8GL+IN1HeQYkNPy89K8RX+s/VAR0rqOF9SRK\njRA/P6KgFQ9LP33SkcjAK2lW4KROlXZlb1A6WNRMYnHkOfsOmcY4C+JD4HG/\naJ4P3KkoraNU7q8VJaUSiqIEwoXq+blrPt7Jg9edZselnNFi8EAT1pByx+Fr\nAUzW\r\n=zAVE\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"1db23ae9a9607dfe9306e1332857cf4e68718701","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.48","@balena/jellyfish-logger":"^3.0.17","@balena/jellyfish-metrics":"^1.0.275","@balena/jellyfish-environment":"^4.2.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.0","depcheck":"^1.4.1","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.166","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.10","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.2.3-renovate-internal-patch-1db23ae9a9607dfe9306e1332857cf4e68718701_1624071127467_0.4605344362000603","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.2.3":{"name":"@balena/jellyfish-core","version":"3.2.3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.2.3","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"da3dc547051def4377b804c0b8ab25d0dec926e0","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.2.3.tgz","fileCount":190,"integrity":"sha512-6Pu0E/FBJQym/qwMgF3e1s+2K0IEWOW2oTTfLoH3JZelQHiOq54E9t5O9VIWyuM2LvhJ4yuR4aBd4pxtCv//OA==","signatures":[{"sig":"MEQCIE4Idj+wkveMfFMqqcK3aC1jv6pz/Gn548nwypzqaY2XAiB63iiROWHM4FT6czJq/XLno5jNRDrQLbaVSccejmVEFQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":719703,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgzV0XCRA9TVsSAnZWagAAMJEP+wZO8n+VrbCbCGaTO7Ae\npbu1DFP1MAXtIx1H1Kd8u/6mN3ac2eZCewd1SweO1TbktB1x3TbfYJ/i0hUL\nqSA8YX9YG45gjpPa5ajvXU/1rE2rbApIamYHTj+KUsAwi67g1PjH64MOsDdY\n2T8x/TNUpNHgU1GnwieRF11+31jEg4v7xha2IGQzum1ZU91IK4A9PC1z4w80\nnSofy1IwCslKwURWsB9vm5G9hE7P88ZAPMOSHa8pR3b3+a4KV4IZWvxMxlXk\niSA24a4sDMvzmJmoCd6+5JXNdoGU/hWS9eG5gDVS3vzkbHjkCISla+Vtd1nZ\nuob+FM0VHFV4LLeuSRmJCe+ykZOwhZ3Qup99NbUU6FJjmr+TTtxMQbTNe3zG\nVf0bKEV64eXTLc+u1mwh8hGgGBQCNtTrkC0vb6RiwlqRPXYdjICGdJBMHv7t\nq0LhnjSEZFc196KWKZgEOfj9nSUwtdTOZXC4qFGAUihcXHkwM4uMQL2QmEYF\nLODzJEKcIBhLBx1kopadNqpi4GUXt4NI0A0o/v98lcNxu6yOsqlNf5TQkxpp\nFHb5UhbVc3DauO/A/hj/qabUEE5vTrGEsl2e1G5V5a7iUkLbPU3fDXyT8Tx5\n4WyjaFvTRPv/rYePHJGyHeCjpkGFfyy8ggXAxqUnp+PjznbqKktpGGAbreF7\n9mdV\r\n=dmds\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"3fdfd674a271f1bd1f9b6ab32118da10aede0220","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.48","@balena/jellyfish-logger":"^3.0.17","@balena/jellyfish-metrics":"^1.0.275","@balena/jellyfish-environment":"^4.2.5"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.0","depcheck":"^1.4.1","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.166","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.10","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.2.3_1624071447362_0.700521766911427","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.2.4-renovate-major-external-major-7d09d7535413462809890eb73e829963133d7f81":{"name":"@balena/jellyfish-core","version":"3.2.4-renovate-major-external-major-7d09d7535413462809890eb73e829963133d7f81","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.2.4-renovate-major-external-major-7d09d7535413462809890eb73e829963133d7f81","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8f170edde865891ad9593cc4b4fc1809887ee2cf","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.2.4-renovate-major-external-major-7d09d7535413462809890eb73e829963133d7f81.tgz","fileCount":190,"integrity":"sha512-vMeG5tlvZNZs9cFnNY+MEV620n00BAmvN5qey3kmq3sJIhBE2N8aFkBJG9uBiCIDFtc2g/CiynjoA9SmHVTf2A==","signatures":[{"sig":"MEYCIQDb1CRnfhg4JKMJQw8tBl24Dv3w7LCE7p6IhhOfmwbY7wIhAPzvH4IOl7AxkoZKrsEXYroxqSO8RDP3bk+BeAhj9vt2","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":719844,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgzV5KCRA9TVsSAnZWagAA1CwP/39FoYHXQLUKfpxinn01\n9aBmlRj9woR0toWJ2yvArCAKUUSBsTx6r3Zuxd540d03pq9pVF/c2CFu/yfP\noiX6eMS96uXxGbHa18P3As3f19Xid3h/bTM4qi07OWrP59L49v0d+YaVf5kN\nJGqINMk4gHkWa7NYsVHSBtqG80HI8ysxnyyR+8RVkHyDaY1mvfi8uM+HzgNC\nNm6kXvRa/zDkWmjPUFgqT08VDyCgouWXHpM+iS+fJadNIzwZcaULTy6K54L4\nGcJzpwWfgV7Q/cNvl+c8nGKgRDZU+sFuuF+tNBY1c1Zqjs/ceHsn5mU9yX2r\noUS6NwdjrFqkCF4MLJhAKbjdAlZ3V1WLWeag6/0x2LoW65AXudvMR99CQiiV\nkdcdbyErTdf9fU3n5Nc2PvqWjn17QArnxjrlxHT0XwQfI2l0hlfYd9uOje3y\nHnSLjOtbvTdqscXhs2qzh46jAxIH8ZG2jmBxiPV9h/o1uthl8y02kcrSWWfx\nQiG34RrAir8D846tqkUliejs8z1lVXCqq56Hxuuuv1VwX+TbwaiqLWPpfxbz\nIfLbpvFsgNJYzOKEwyQ4axELS9MD+b0Od0YzKbffjgctCEIryDayr6iJbOFy\n4nuDoRe1lH+KqvhP/1W4KUla+qg8GNHbqAccG+QC03yTfPu+fyG5IdIwUps0\nZZtL\r\n=6/Zk\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"7d09d7535413462809890eb73e829963133d7f81","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.48","@balena/jellyfish-logger":"^3.0.17","@balena/jellyfish-metrics":"^1.0.275","@balena/jellyfish-environment":"^4.2.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.4","deplint":"^1.1.3","ts-jest":"^27.0.3","typedoc":"^0.21.0","depcheck":"^1.4.1","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.166","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.6.10","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.2.4-renovate-major-external-major-7d09d7535413462809890eb73e829963133d7f81_1624071754398_0.984971656073389","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.2.4-renovate-internal-patch-d6c63137a8a35eb6186d0319fe8f4203fdb91870":{"name":"@balena/jellyfish-core","version":"3.2.4-renovate-internal-patch-d6c63137a8a35eb6186d0319fe8f4203fdb91870","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.2.4-renovate-internal-patch-d6c63137a8a35eb6186d0319fe8f4203fdb91870","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d0a89945cbc8ec4125c3e028a78ac318a3709b8a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.2.4-renovate-internal-patch-d6c63137a8a35eb6186d0319fe8f4203fdb91870.tgz","fileCount":190,"integrity":"sha512-AYCXvminhofAf1nrOoikTxkGgotZ5kxAA9fN2p1PxnumelXljURae13BDWeba9d1FS8ZINKGVYarzfMee/1b9g==","signatures":[{"sig":"MEUCIHDsTqWlTeI5umnfCNVtq5fAdscwB8XvolFYpUnna4gyAiEAkkxH30QeQE3vMdAABwXrAwWKQzT3Xi6mwR9s9Jy1TXw=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":719869,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgzZfFCRA9TVsSAnZWagAAqccP/0oky/x2mbf0ER3XCWx+\n1L1JWEwthF2MqKeyXVjkdpKc8+CHU+v6hGRDH+gdtsQcVSxfu8acnLm6/z5w\nnlp0jJg1CHjplj4uiur0m604linDXFhptsEluc0bHhqMPDSvKihoim87Z+i9\nTJOwc/EBn3q+0wcGecyGIphRPOHrS/DfrP6AjVISY0rNUp+X4IL5UBE3LLHI\nZ2Bph/5uVxuUhAfel2bFv0Q/1SDvFzXJHHHUAm2IyzKs10x2XLLOUORyB//b\nODxO1Yk0k8m8Zu1njK2r3MZrxm0Mu8/W1S9WQGbx2NZLFlwSQgtYGBhIbT7y\nRfmi9fsEY9fA4ypd8tC7tW7OW7Pq7qMtUHR0iC2yNoqViVeH4ik4f5a63EMd\nDVY6e0dDH/v97mUecJKYO448Ni6Abi4Kh4fpB8R/oTUgBl8/6ItAUosH0CSs\n4ITjT5dNAivuoOYvTLF67fe03Q61LnjjwoYujjIW7le9NMixS3LJ2x29NxUO\nS0DjQ95Qxy8niZOUX+ilFGx4yg281T1VquWtWMNS55T60Fl0UaLPSaIRqnfq\nsueOqqIM5zHBP0MndBsU13USeuoCPM28CybKtPzOg+OhR6wKnIyAO9KHhlcf\nlv//Ah4nCk0ptQB9m0LxOQixY+1YX+YH9rOabiZJnjg/5mVCMXul6BLVkswW\nXPP7\r\n=fEjn\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"d6c63137a8a35eb6186d0319fe8f4203fdb91870","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.48","@balena/jellyfish-logger":"^3.0.17","@balena/jellyfish-metrics":"^1.0.275","@balena/jellyfish-environment":"^4.2.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.0","depcheck":"^1.4.1","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.167","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.10","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.2.4-renovate-internal-patch-d6c63137a8a35eb6186d0319fe8f4203fdb91870_1624086468988_0.8801960082841327","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.2.4":{"name":"@balena/jellyfish-core","version":"3.2.4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.2.4","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"42b8fee22d45bb094c6308bc771ff13762e8bafb","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.2.4.tgz","fileCount":190,"integrity":"sha512-qCxS55rVMbGh2AwZq2503x4C50tnHKbKjoY2A0QbifiRDyWcb1mnn4G3HMbfsa5yTqa9lMBV5DsYaLDFB6wvVg==","signatures":[{"sig":"MEUCIBLIlVmUzji+QZicLbUAkme7zB0m6A8kZgA2sOHEO8D6AiEAp84/NmzD50FPg6mHKjTmbrluqAr7Pzid5c+8yV1mcaw=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":719804,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgzZiqCRA9TVsSAnZWagAAEV4P/124/U/h5JM0QL5Fgg2Y\n7Lq5eIc5bPhK3o/svfP8iaO/2APabaqETcS3p/SmBoFmTtkE5lsSNxzdRqJA\nrmad8YgyLuTI/gpMLZxkzORtuo0p6aTAs5EeB7EoeRiMgaQ1fdj5a7BMHH2P\nzQqj2sVT1c8MMvuMvUTAwkD1/98u7ptZghaHqYDzGdHjp5kKlFPg2f/tWUtD\n0kLeJrWkReOog+/ykTxq9G5YIwjJuyBZgluLkWXAEGXIrLXNtxXKpqKE/Qh+\nUZzM8gszYWhzsV+qwzbNwMSHWYJst/a/IPd7Gr7wwckV3YwMNsWfAQk6TipV\n/aR6ZK8BAi0GJT9ETh1mbaMnXRPdnm8eSe8YlKCfkzgS/AH1Bks6LUwgoGb3\nbmSb3i/imGYC00+b1Y5gwG3ZJGZHuCZptQzqNFUVGe8NBgL7gY54r2xg/zO3\nRMo0+bYaJKdDYmIqMu8GLVEfWNfb/GIM5F81gZNlKjfM2Csf2euBkpXASCJ8\nixuQ/NihjW5bLBp2UZGEfdM9sVLxc4jtsIuhTKUoOStr15+7aJYAbVsaQcAN\n5jkuUJiD64qrxObLhTSpoTE3jdDi0U9z7S2HD24+oT3DTUpn2vvZ1Wo74ZAB\n014oRfUAng4KhZ4vOlxbTcOw68PhkjJZnHHeU+YV7/kp5jYphusIgYuVGPob\nPR3i\r\n=zfQ9\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"edb004027f25b2c14ee39d9082cf8a9b41c940d2","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.48","@balena/jellyfish-logger":"^3.0.17","@balena/jellyfish-metrics":"^1.0.275","@balena/jellyfish-environment":"^4.2.5"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.0","depcheck":"^1.4.1","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.167","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.10","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.2.4_1624086697682_0.7610329740641018","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.2.5-renovate-major-external-major-bb8e996a61981097114aecd8cae5b766c21f67f8":{"name":"@balena/jellyfish-core","version":"3.2.5-renovate-major-external-major-bb8e996a61981097114aecd8cae5b766c21f67f8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.2.5-renovate-major-external-major-bb8e996a61981097114aecd8cae5b766c21f67f8","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"515d7c1528e073781af570228f4ec34affb24de4","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.2.5-renovate-major-external-major-bb8e996a61981097114aecd8cae5b766c21f67f8.tgz","fileCount":190,"integrity":"sha512-utFMC4M1HnQSNFFM/q09R3tFtJLfXb36NiypZyQtZrhpUFoDpG+TMysYnX4ykIvTBbi9nNwdmkOg7fC1aW5UkA==","signatures":[{"sig":"MEYCIQCsO2dDnwsTcWd47BhqE+rwP+YvvWsrWRUWnhSwChUZeAIhAOxLsSCUKoknJTwAqzfrikXDg+C4oOmpuuI2m4/LcHa0","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":719945,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgzZm+CRA9TVsSAnZWagAAXcsQAI6gJnmGuhkZlJM8JAs/\nv1AHUVtkFvZpMAMEqgPWqiW7HPBlJbbTv1sHw9xadqCenCEO+LshfA51w3Az\nehL/qO6dH6PPKt/Vvzk3WX0cqj+nJC189zmLDILvYmAqX3uLVWlCyLvUKoiz\nZOUWq7StjZ8EGKhI8+voie/5klE2pbBN8KJE7m/7/he68qTAxwDAINCY1wt6\navsfMdIwOeAKJ5u7AsXj0rSo9jFricOWNrTziwb0G4X0YJW0qm+kaW78DmLx\n0EaKLD4u96X+EsZQN2pejaC9Pxv31vElcoqtEKHqmO6dYwi6e6vU2MLOmYz+\nJa2524aYYzvTXUdftDy05d/aGbsMC+XXQW/vZTLIcKKqUZhiA4zuVmATBGqw\nCSlgazuWTV32DuEX8hsuisLhr3vwzLGoV6pNXcpUJnJqM/OoSV7bcNXEKUen\nXoW9NZ+Ip3jluwJCSPu7EcjxXh+kyEafxvQfrG/mezg5gjyabP6E6gbBXtGu\nlCRnX5wgClu5UdpNCG8nlTOT50wwRxF2QZYrIdC7jjqdbg8I1FHaUQWIPRNi\nhCbDDQY60wtpcm5i9mFL58su55j3X4cOZjCdvGUtvfkbmrbxB31pJGKCBiB6\nXrDKm34wXnrGcuAxd6v2CvNPeZy89vk8lnQicquEgpnlfnT/WTEYx5wgvh7g\nYOOI\r\n=z10i\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"bb8e996a61981097114aecd8cae5b766c21f67f8","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.48","@balena/jellyfish-logger":"^3.0.17","@balena/jellyfish-metrics":"^1.0.275","@balena/jellyfish-environment":"^4.2.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.4","deplint":"^1.1.3","ts-jest":"^27.0.3","typedoc":"^0.21.0","depcheck":"^1.4.1","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.167","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.6.10","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.2.5-renovate-major-external-major-bb8e996a61981097114aecd8cae5b766c21f67f8_1624086974403_0.1759079458902406","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.2.5-renovate-internal-patch-d3591a769b6ba14119cb71661de79d99ec17cf64":{"name":"@balena/jellyfish-core","version":"3.2.5-renovate-internal-patch-d3591a769b6ba14119cb71661de79d99ec17cf64","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.2.5-renovate-internal-patch-d3591a769b6ba14119cb71661de79d99ec17cf64","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0bb687041bf707de121ce41ae1f58d50326bfe2c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.2.5-renovate-internal-patch-d3591a769b6ba14119cb71661de79d99ec17cf64.tgz","fileCount":190,"integrity":"sha512-CxguSor7brt2VUA4GuEU2BK7odSq9qbFeFcNi71mEBXrq0MvxPxNc6vd1Q4uMVjwbIAaSwibNTVKR6L8Z6/qmw==","signatures":[{"sig":"MEUCIHCDsJdOcDM5wFFSP6iqFGtSxOoKJ8GgyLUQpI9tUXslAiEAsFuY2SwA/I9c/g9qOxr421UsebiSkYotsJTNzcQN3kU=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":719970,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgzZt2CRA9TVsSAnZWagAAiM8P/iT9o5l1yrevNY6+m4Tw\nRuBTES5g5sZrwwueupklz0tksy2JSwCu7IM8ueHSsbBbM9FFo53CHghBkaOT\n60rmeVxn5H6Drf7lvMlnbyEZTlMSYdzVwf+FnZ1KL2uohKAv9IxiXCtBaMFh\nHlgv19XINAPUqdU2nX3P39JID4tAoVVWDLitk/ysg8O8snqXF+KrCsPVXwTn\nqoGtTFo+x4MX8m8ZpL8ZGGf0WjJG/tCKUYyxLT+pFZdxan7fCdEkxSzSqbis\neKiwD8d+1nB/OjM8XWBZWkMPwUmbiP8GBYKTXd30KrgeUdULVdXXXn/uoeUh\nvKcOMQg8dlUA82aOm68jKtI9SiFNY73biAqGyfwuUEJ7gK2xc5pG+KFT1NXj\nQ01zJQkUiK3eDwsrxHyYzhFF2jIcpR3DOoOuRMPJeYErq4IOnlr3aUjtfDZd\nOL3D6A8mVVakEIsbhXI9mayRHai/HcCKEyeeQYpEJRxhimtLRxVnmwg9GvaG\nb9sQ460K74anUnuOKCJaoM3DmyhXmNUvDl53k1BGAaPppQAw7aLA3W0Ez4PN\nl4uFO93hqPODFszb43A2ALSp+IEz3HVS9ts/wBGlrMoX4JSihvW22Z9wAwzu\nXtULAr/uCGYyjgzh0FXDHC3nR8MEi37vQQ6R9WcOHDESZSEpZtUpR1G6TpVi\ncx/y\r\n=prqZ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"d3591a769b6ba14119cb71661de79d99ec17cf64","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.48","@balena/jellyfish-logger":"^3.0.17","@balena/jellyfish-metrics":"^1.0.275","@balena/jellyfish-environment":"^4.2.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.0","depcheck":"^1.4.1","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.168","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.10","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.2.5-renovate-internal-patch-d3591a769b6ba14119cb71661de79d99ec17cf64_1624087414371_0.6661305898884426","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.2.5":{"name":"@balena/jellyfish-core","version":"3.2.5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.2.5","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"770373526defb23ded9f6a08105b6ed798589c10","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.2.5.tgz","fileCount":190,"integrity":"sha512-XZ51wHGSBI3TyPWpNOSQ9KSIbT/h7cRTw3ZVM6DKWtYi5O1mCqaSP25R0eRJ9pup/jC2GLUNlM4kP6gcPcrKzw==","signatures":[{"sig":"MEYCIQD1cueii/xjU+VwFHy8SXf1OcTDaPV03GbSwe1meKCOCAIhANhUFHT2abI6afgVSC5IAfmzSz3Ksu4nlHEVz7Y3gHX0","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":719905,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgzZylCRA9TVsSAnZWagAAf3IP/327XqpEkcd/2BYApdpX\nND3uoAiodEAK1wyc0pF6wMtLtUffTiy72c3Lv6O33NmVAlhL4Rht/phkzYhf\ngIIkP+TSNAJ/sIYoRmihFoADyAwlioct44kO/2mwzmz1fEjgOkI+ksv0JKro\n/gv4Vbkdor6Rk8KhILDv7r5K1TOPJGcBuD145wPUINSb09ojBlRyuTOm6O86\nRJzJWiqvWn8ymbU6L22jtSA+Y3N9Pv+WCdch7kuac1NuQLgysggeb++YrzTp\nVrvHUBgiSFDj8AfgJdZV/c9b+s1v/ik/JnK8lI9Rcf6lE/I9nh76SchryijS\nNr3RWfwZsGPLZoA2rTW5Ip/dMyV9VSpd90PNtpLwdzdB7K8FXnO2x5H07x2/\nE7rbVYfn3O812frQGrpQqibs5h4XtPKY71sgDpHRLOkYBW0YRJeh4jr9Rhqz\na/2AddARJe2Cr2WpaO1MOG3svEcWvzScGmfbo1Ev90l3D+mgAtpXRnzVFw+1\nRLAqkDCb0KbDcD6YNt8hCk4zXj7o3tB15JLjBVieDZzlbDeCGZ2n9vRSrrd0\nyAh+RtOjJkfWK99L7IagxkWZs5UGu4Dnn+jqG56yv/Vd5CWoLNjw8On6mknX\nCOgpfy6lS2QDSGdIbCqHNqFVsgqpQ2ff55GzCkJqBhlYVlI7V/MzRke9Mlog\n1h0/\r\n=ezsA\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"a78c335102fd11803945ca1ea8bc9e2a16e54a96","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.48","@balena/jellyfish-logger":"^3.0.17","@balena/jellyfish-metrics":"^1.0.275","@balena/jellyfish-environment":"^4.2.5"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.0","depcheck":"^1.4.1","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.168","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.10","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.2.5_1624087716890_0.8010371114557966","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.2.6-renovate-major-external-major-4ae4f076bfff14f9c957a84bf1fd80b153d6c86f":{"name":"@balena/jellyfish-core","version":"3.2.6-renovate-major-external-major-4ae4f076bfff14f9c957a84bf1fd80b153d6c86f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.2.6-renovate-major-external-major-4ae4f076bfff14f9c957a84bf1fd80b153d6c86f","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0abc324baf6c4cbd292099a79181e888a8c75e53","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.2.6-renovate-major-external-major-4ae4f076bfff14f9c957a84bf1fd80b153d6c86f.tgz","fileCount":190,"integrity":"sha512-7ahDZjDy7EZAxkpMPV3db0gHHyjq8dheiZmAo3+4pL4fIV7qoIR1MCnKR3ujM1hLgzZFXmn+mJir85cjawcwAg==","signatures":[{"sig":"MEUCIFxGb0CYn9Xkm/kJeXYJaxmwwmawPA939TIKfENnb+S7AiEA9bYOjoe+gW75PI03n/H24cNLrV3nvDNUcV7U/vJr0WQ=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":720046,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgzZ20CRA9TVsSAnZWagAAL1AP/jBxsuaakhLb4wq+mYp7\n7zun1xJJPo2NtHNn0GnODO3TyCqnTY+HkwlOE2XiR7VaXHmKxDbOO5L8ywCj\nK1erPiwdnlyvQHc5Z4lxGSQC2F8I29IGhmO/Mzd+mzLkIknAhMegvq4mecOo\n993NNGiBTP7CdKeGv2dPE0MLMKOzEvTwlsHqbM353u6Nq8aT4jywpbhKh3NN\n4LWbv9lndSGtCFSCHzONxF0DGmo0aA2OujiUOLsKMyCugy+JuoPRI542IAAs\nt2chYq856V/euxjv2kTdUeManKWnCytZaFu1antvfdE2YOtzCgE+VhtAt36I\nOIb0/FQ5s5bhih0fcumYnZyPAYNhoguIOTXAnRXE18LON1M41p7nrRRzjHhr\nP4wew1WxBgCsa9TAmxmSiGFhXaJ6HxzYLB4OeRnd9XH3nbu3Ojqi/tDU/nM4\nHE+54YQe2saqfiG+Nwc3rYlMhb1k3JLrHzWs4FPQkHyqCy98OTXax7EDzB78\nxxDoHq/oRNN15PkI6ybDLR4BUf3vLY2C/Vf0s90JQm4Q7xsuSq5gzK2oCVkF\n2RmMAOMAXCDoBpNORcDgmSgzGCFbGUYdhWgZaN8pFKwCCAJ3Rc1EljsLI5K6\n7o9fTJurYhtmsoQD12xaZI8MDq7HkkjrxuMCjHIKzgVQaIZf9tDxHEeL9C+/\nmh1b\r\n=ehPk\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"4ae4f076bfff14f9c957a84bf1fd80b153d6c86f","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.48","@balena/jellyfish-logger":"^3.0.17","@balena/jellyfish-metrics":"^1.0.275","@balena/jellyfish-environment":"^4.2.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.4","deplint":"^1.1.3","ts-jest":"^27.0.3","typedoc":"^0.21.0","depcheck":"^1.4.1","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.168","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.6.10","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.2.6-renovate-major-external-major-4ae4f076bfff14f9c957a84bf1fd80b153d6c86f_1624087988320_0.6562356854341835","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.2.6-renovate-internal-patch-a041df8b11e8af97fe9a6ce7d74ca623ca750bb8":{"name":"@balena/jellyfish-core","version":"3.2.6-renovate-internal-patch-a041df8b11e8af97fe9a6ce7d74ca623ca750bb8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.2.6-renovate-internal-patch-a041df8b11e8af97fe9a6ce7d74ca623ca750bb8","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d3be3643e6e9c2df9fd7cfc7970e5503b3602edc","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.2.6-renovate-internal-patch-a041df8b11e8af97fe9a6ce7d74ca623ca750bb8.tgz","fileCount":190,"integrity":"sha512-BMELzOJU4DkMgTr1HD5LIJzwvhczddzzwixBg9XukoxRqbyx1oaWdMP9wdT8cn6li9cmZz6Whh+Gwkg5bG7tKw==","signatures":[{"sig":"MEYCIQCyxV/FZiAbu5oxQe+eCIg4KcxrNgXYQf6nkuVSsmqOkQIhAK8bkRgOTzRmtV5JUU6apw9n70t6OlKuM3trymQmpl5M","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":720040,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgzcc7CRA9TVsSAnZWagAAmTcP/A3lwgM5Y+oekFtIqOwV\nQ6nNy6q2NRo8zLGM8PjXiI5px1G8Eq0Ugiv2IDsxNHB7b0BOuQ7oB6u752JW\nOwJOD6mluMHIDEqdiH2Gk3SAZoUC77nP/ZXWgkXkgW4/tPM/46zUKY2geZSO\n5zf11wVYiDKVg9kAIk2QtnX4mNQgGGvrlstcBX0u8K/rYkyo6WPNE81SDJ0J\nruehvACwXZ3JYRyfK4w+wKPrF/dTUa54EBTvIlXLihKOl4szZxgww3HhWXYq\n7isuj6+IbpjR2VNk/OGLJ+5xE2loByh4Q3lExeoLqbw+YtJwuLPQPPituR8A\ndQvqV+BSSqS/Min94Pm51EXF3wp3LL/U11aH2QGSZmKtLyPURjD73zE7vFAv\nj2WYDYZdj28rIgh+8B72CSSVC2OrqibdT0Dlrv8m7lJh3EJYRolrby01aNF6\neopC/mWkHO0/qT+0lMHbM50Oc+IduNPA2ggHE6siuS+C7zoUW3a6JuZogyza\nZEdOZRR+lp/KRHa2Uxe8Ylf2ofNYywBQ4F1zuW/yo3gkYwLIJluNadBY+GpG\nUTVBbD87rMIHRxJM+r9ZQQmF/iiY8aM4ReGJqiqa40JVOQ+Fp7VbPuUGQzeG\nUs9TmUSfZIQxS5bqdCJQtX2Yp+VeaqbXkdkSEdwqfXneuHsHMSMJ9mWYctiy\n2I0d\r\n=Z9rO\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"a041df8b11e8af97fe9a6ce7d74ca623ca750bb8","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.49","@balena/jellyfish-logger":"^3.0.18","@balena/jellyfish-metrics":"^1.0.275","@balena/jellyfish-environment":"^4.2.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.0","depcheck":"^1.4.1","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.168","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.10","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.2.6-renovate-internal-patch-a041df8b11e8af97fe9a6ce7d74ca623ca750bb8_1624098619269_0.6236155264548957","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.2.6-renovate-internal-patch-7978b3fdb774af9785e9df3b600df5351ad03296":{"name":"@balena/jellyfish-core","version":"3.2.6-renovate-internal-patch-7978b3fdb774af9785e9df3b600df5351ad03296","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.2.6-renovate-internal-patch-7978b3fdb774af9785e9df3b600df5351ad03296","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5b33bc42740588a3b3cdc51646483f1ad1e0d983","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.2.6-renovate-internal-patch-7978b3fdb774af9785e9df3b600df5351ad03296.tgz","fileCount":190,"integrity":"sha512-f+iuYRpq+zHf5icnxIL5/bT1BBruxNkRSe3ZOEKna4q1KFGnih9liirRBc34U/7gD7MooZWbo18k701hrFCvdA==","signatures":[{"sig":"MEUCIQD2fflF2ifm70+5a3TBZNz5U1lc0yOZunhH4cT+1F4OJQIgdJXI4D16Xaa9Md6diY4VpV7I6gesUj/CdjqVxupOApM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":720040,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgzchhCRA9TVsSAnZWagAAIbgP/jL7SHfsPA5CdeIdFPVT\nxAbjmaZ8h1NjyoGNwyf1Eh0sm55dk7DSuE9UbuWchF62PRJvS0abVtj7uBjd\nudVMnzeNlZ8bwEZ+DcqW3jDT8SFIiXR+i6LL8XvfytlJ4zs27/yQ5hf7eTGI\nv2YJV3Y0rkvPrpjzRzV2QibHL3RSGRleHzL8Ye0nZuhgeTIWXF+UvckIqxAH\n0dq5DOcIeh2eZVJSnmHzkWeq4CMwaB5Puq0h5mvpCBF0sT2yIX+xqWvw5gtz\nKl/A9/hM4zA5/5RUYYFAnfAY+qYOIwywmvBlw2EB/Hl6vhGK8wDCSt82whyx\ntOWTPPtbw2/n03i8ssbre10QrwBXGA2qYoIr/WVqaOETLt/WjzwV5g2XPBps\nlzPoX+1lFvhiWq7VmjlsDv82F0BloE4aa2Rl3LDWsPMSKiA8JCd+hOzImPCO\n/bkhqoNLaeRwWVxg55vHcVl7jbMx9rX5DieRsXAQxWoIp24heDSvPNwG7abu\nIJ8BOuKtlNxv2gI/8FAl/dHwQtLV7tAHcGau0ODkZd59wXv3H1LtFRZnGPO6\n3CeGL5EIACirXQ6GA8junEO87YwzpUTefEqBK1UGglZhxTHloC4Q2KYjmLUv\nYY7UnsRjJmp73Hc6FmJnO0W83FetyOI/Tm7T7hryBu+386b+UVVbMnD93wcG\nISNA\r\n=4wZY\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"7978b3fdb774af9785e9df3b600df5351ad03296","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.49","@balena/jellyfish-logger":"^3.0.18","@balena/jellyfish-metrics":"^1.0.276","@balena/jellyfish-environment":"^4.2.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.0","depcheck":"^1.4.1","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.168","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.10","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.2.6-renovate-internal-patch-7978b3fdb774af9785e9df3b600df5351ad03296_1624098912837_0.08213822994240916","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.2.6-renovate-internal-patch-fce47545d9451c3bf10b0e51656e651b19e29f7d":{"name":"@balena/jellyfish-core","version":"3.2.6-renovate-internal-patch-fce47545d9451c3bf10b0e51656e651b19e29f7d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.2.6-renovate-internal-patch-fce47545d9451c3bf10b0e51656e651b19e29f7d","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6e048d06c32466d9a98a5b6a18e6a1281afb4b44","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.2.6-renovate-internal-patch-fce47545d9451c3bf10b0e51656e651b19e29f7d.tgz","fileCount":190,"integrity":"sha512-YhpzxJOrbIR+xSKPHgngY9dqEDEdio+npaFYJg2JTAykP84DHwvqae1asMf/qh4KjQ0onT5dCjt1MMKM5jKwyQ==","signatures":[{"sig":"MEUCIA/Hdvrg9ZJA6LYjoeH6Q+OizTwZbYZXHXXgN+ZVuf7JAiEAo4V1Lxz9/fjh0llmySDqCGJk1jZZg2pQddX8xNXjajI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":720040,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgzclzCRA9TVsSAnZWagAAJL4P/2DEd2Mxc4vJsQ7Bf1V5\n844Eh/u/Eo3lvYIPB/K7lJxxiudMWiznAowuz6JMvFSbeyZd+qrMECVrejOF\n+8DZPpwvru1vz8bLmqwBQxXKFTFAtSo+l/YRmb6RBEkWzVJvBFB8gFI8Um47\nS2OOND9Yi64orc5923hdROcWssAtrQVnGIFX/OklU/M3bYHGIc604OdYdccG\nDQ3Whc+dI1OmwzqkX/cQ3MmJAz7I3Pni3XkjjlefwvDfSZuXK3VQGHKDNmHt\nnjWCd9ygZlKRojo+whGTMTdshc6tAZJNQQCq803X8OsHQIWKES4qXIfJF/uL\nLkfdnk2ef2n7OkBib+uhbcBYH66bnABY60mNcEi01ppDfoAboyz7vwE3bcNi\n8dbOZnUUlhqIUvFLUT3HRlGZZcKTvvV3aIh+WQyj2xO+neFpBJsBZ88+gsYo\nFyn/9925uOd75tr9Fe3ocf2fle9xiqK1UbVB643C89e1vKuQlsrJfmZimNen\nNIxt3wQTq2WvvQ/nzuao1hUAAeDofa+7NA09bt+5vBrU9/jlM6mMKLIuuZnu\n+kk6f2di0W0QRWHjuC9vgRE/oztYgZOLTyLpvTDLL9Y6keoqvWSJ5ajcB8gm\nYhCGeR31nGN2Gbpscy/Vujs6tbePJk273DUb/aCurjUOdSZjrl6l3v/8tLMj\nC4ri\r\n=J+YG\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"fce47545d9451c3bf10b0e51656e651b19e29f7d","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.49","@balena/jellyfish-logger":"^3.0.19","@balena/jellyfish-metrics":"^1.0.276","@balena/jellyfish-environment":"^4.2.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.0","depcheck":"^1.4.1","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.168","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.10","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.2.6-renovate-internal-patch-fce47545d9451c3bf10b0e51656e651b19e29f7d_1624099186993_0.7648205917107864","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.2.6":{"name":"@balena/jellyfish-core","version":"3.2.6","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.2.6","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"892163ea216145814a1baa77dc949ca1ecc34469","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.2.6.tgz","fileCount":190,"integrity":"sha512-a+BtjNxthut8Ahh+KwQ/8cZRa6V2fy6z7ficNNyuQkqNKrCzEeh2Kadxyr/dzxmEg5PP16e/SGO7SfYycbY7Xw==","signatures":[{"sig":"MEYCIQDS5e4sUXyW3EcypQ14iWmeKQLzMcv/m5ySkogFGoJTYQIhAJlvKGJD7o/o/MMgg0/+Uj0ect5YYabaz5bxEj8tqLFf","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":719975,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgzcqzCRA9TVsSAnZWagAALnkP/jxq1SMitQFlGNxmBwfE\nng8cT6cAP6ZrM1arlQrPmupV1SXydJTRCXb1VRyP8gpIcErPjxxtXLF7kpS0\nSyaCI/og1G7hfqiNVaoXHDnXfo5UWj6stbQK6srj+9p5JJG1d5U1MLeeYJSV\ni+SQhdeGelS54puICFoqJBsrEos2XK9tMep1xdaaxCSq/ZsA8B4R9Xvsqs5v\nA0l8+cBbejpY18GBx1YQFfR7Hx8ipKld/lmhMRhrNAyfdLs7a2zxm4i34Cq5\ntJbIHFfibWj26kEI9KjciK1cTrDwMWaurF8wBVWa0hshB74AYTqzKY2gPmjO\nGkF6oFcCDTYvK59Pa9RoPjH4wP0Esz2xKJhlcNMtzXx9E48qehm/8y5NvAIP\nWBFlHlt86IQGSKJvH58vl6BjoQFmFtJlvRGVkhc06ODmUfPK/eWpaGvZ3nNg\nrvbdFv93NsUXstCNGuPS4Jgd3owHOz2YefCgmkh2TGACvprKnKSzd+i8WbZ2\nDGHSxlR8LeWdMGxMmWtfEWnpt1hU0umuZpT86zvtYeIjph6tdrJsRiLRsbFG\nHEfXMow0rXgXoYdTo5grJxxE2q8fPuoR7ZtYIbV/A5b/ebtXMZVZ6SAoHMLw\n/YbccLCkOCLIxH3M2snDQHHSuhYXpTRt+4Zx2/0Ui8zeC1D1VK9VvH/WkI0J\nYuM7\r\n=+yr5\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"e98f7ad46c87ec893af13645c824eb288d162941","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.49","@balena/jellyfish-logger":"^3.0.19","@balena/jellyfish-metrics":"^1.0.276","@balena/jellyfish-environment":"^4.2.6"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.0","depcheck":"^1.4.1","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.168","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.10","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.2.6_1624099506727_0.36604746533763977","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.2.7-renovate-major-external-major-c1e790223adfec9e32ff4d8eb1c8d5a90dc25bb2":{"name":"@balena/jellyfish-core","version":"3.2.7-renovate-major-external-major-c1e790223adfec9e32ff4d8eb1c8d5a90dc25bb2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.2.7-renovate-major-external-major-c1e790223adfec9e32ff4d8eb1c8d5a90dc25bb2","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a228f33872a6885678f5b499578433426d054ccc","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.2.7-renovate-major-external-major-c1e790223adfec9e32ff4d8eb1c8d5a90dc25bb2.tgz","fileCount":190,"integrity":"sha512-6x2kkUWg45Aq64k8DC3g0EKkvySU4UrsMkj5HKQUkNcZA3KJjOvnhqmM1WlAZBN2n418ZC3IL893z7VntM+zYQ==","signatures":[{"sig":"MEUCIF8Mb9MIhmH4t8CwpePcIk7LPlqGBZLEuOVgP+aN9Is1AiEAr2Xc+Q/TFJINHjhM+SSeXc8ZApG9a9pQEX9iXl4//u0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":720116,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgzcxQCRA9TVsSAnZWagAAYUoP/R5O5ktO8pBTlZdEjCn+\n/7jr+ENWo0BgZ7JtjUiGemfEALXwsS1w8CB5SOccIVSEgwIL8cNXzwoNfaDM\nJ3Hd/hMZdBUGv9oqH6Cz9/LCTdV+zSzFWa5Se/waamzssBzrVWyWUjzvRsfU\nAo11CduwSQi3YXw2bSVC5PQ2jC2682UaJVn82SQGItr5Mmv9yP9VdE2bi9C6\na6QesfoXYMMIXEr+FAtEVJIJOcvZ/X/m40uE6q6/uCXl7CrxM8Y11LHiqI4+\nEr2dr/QsHpNOWaU99DvIBAWl3DDVbVkNofofnpqBJQ2zbbiqkxkl4JRQ/IOq\nGxMuAHIRj4QNcUZnRlWdmVeCNRvuK3CuY7QGRgpmOwZX4wZXk0HVQXZcCLj6\ndnK8XF+Y826nziYDD694z8Jbo6TGYem//UtztEd791uQtUY4hyaYjRylksP2\n8w8dEmiLwJ2tGalTc6rePzzPF4LajNdfla80UhMlYD3KwHbTexXHW/JkGXLL\nf+hbF2ZmcEaGUqh6Q42X8tGBJmyOLq1krmrpje8yRP30YE1Vl6lKeKXf49b6\nDG1UTaG63tEPAkoJCfH/RyYL8rkh4QVgF67s/5aRFAXJHRMZhRhQ7tQnD4a/\nQRNegpnHmxJS9lR+ltzjOe05bIWtuT9SL0O8wMcfLFxuqZbzeb8I5gmK7TD/\nnjgT\r\n=Sltf\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"c1e790223adfec9e32ff4d8eb1c8d5a90dc25bb2","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.49","@balena/jellyfish-logger":"^3.0.19","@balena/jellyfish-metrics":"^1.0.276","@balena/jellyfish-environment":"^4.2.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.4","deplint":"^1.1.3","ts-jest":"^27.0.3","typedoc":"^0.21.0","depcheck":"^1.4.1","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.168","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.6.10","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.2.7-renovate-major-external-major-c1e790223adfec9e32ff4d8eb1c8d5a90dc25bb2_1624099919954_0.3933904603409004","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.2.7-renovate-internal-patch-333295ad4e6fea9411bbd4d830ad32db6e5c486a":{"name":"@balena/jellyfish-core","version":"3.2.7-renovate-internal-patch-333295ad4e6fea9411bbd4d830ad32db6e5c486a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.2.7-renovate-internal-patch-333295ad4e6fea9411bbd4d830ad32db6e5c486a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"bd11a078c04a51fd76fade5fd340c8a385f76fbd","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.2.7-renovate-internal-patch-333295ad4e6fea9411bbd4d830ad32db6e5c486a.tgz","fileCount":190,"integrity":"sha512-4ycJxFatwQgxAi83sfN9cAhOze67+lO4rXCzjX6lbIsscveSmCkapqRlRg0q06w9O4VDkvTgMIQu9tiiAAGPmA==","signatures":[{"sig":"MEUCIGj/3ZvuHvDfwfxO7aMvyLX3Hw/y/sNBDTtoCGXziqdZAiEAnNZbvmxHn9lJ+dmUBhW63VPFy5g0651tWIW4QVhFivo=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":720144,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgzfMYCRA9TVsSAnZWagAANZ8P/2Lcxso4lXOkcCMunwLq\nQC0Unu/LVTt8O+tsEE6xhABIEfn3fYDtwzNTl1uGyzBbPG40yK54XM209cNy\nSbN11n7PTYugyCOxucobp6dIlb7FnLSAGYsLIFWPN279tN1GphMmNQnHCpfu\nm03CRCjxdfPJoY5CiO4PaKy/oduuUyTlnKYGf8CRobcOB4kqVgWCim3m2FXK\nE4KTjjXa5DPsQIEhguCoXn9ezlW8Dx5LaWXUGEMHIMIPCH9uiZNjjapYr1EB\nrzw6o+wDeS9yZHke5+xTPH1cdyuT4b5hfSfhlX4p1lhlyf0SwA5uHEI42m0V\nad6xYrCy6LwS2IzIUfdc00GiaL7aYBJPpjwuFr/DCIcIQIcaqEoDaitxskG9\nBwer0FWBkQaJjsZXVVqGJD0XBL+E4hcnfYjYWi84sCD6MQdtL8gui9s2Prr9\nOSe1ibG2EzqttLwPDb2ctTMg3dQZHl8YzcfHj9rG5pO1u7bXdhVL6GGi+o6Y\nW9iPkfnEDpsfNLDK+/HHoyvRRipGFpbwUhKqlodOk4RZMv5pYhGCcImeijnp\ngC0jWxNvja1zmHstGCOLM/rH8kCKP9zOQSBLHtKyCTgk1jvrLhg7VmiqcJ1d\nlwgjG7fVZXkSBL3OjP6etLu7HQs26Syf6zcBWfJijaCT5litRa62N8l1SOXR\n3ZwK\r\n=eyqg\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"333295ad4e6fea9411bbd4d830ad32db6e5c486a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.49","@balena/jellyfish-logger":"^3.0.19","@balena/jellyfish-metrics":"^1.0.278","@balena/jellyfish-environment":"^4.2.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.0","depcheck":"^1.4.1","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.168","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.10","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.2.7-renovate-internal-patch-333295ad4e6fea9411bbd4d830ad32db6e5c486a_1624109847979_0.5067864952651566","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.2.7":{"name":"@balena/jellyfish-core","version":"3.2.7","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.2.7","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0933125d7b805b9b80d36fd66119c9dc1fb977c2","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.2.7.tgz","fileCount":190,"integrity":"sha512-CpXzNuh0dxtq5ZivAxO6EiqaAOzHywTK8l45tEKo4VKVHrqYlqtLdLaSdq6T95x7zBQNoALBJELdxb2BQb3X2g==","signatures":[{"sig":"MEQCIAXW6b5mGVF9E57NGc0LxXKAGaXaKYZTRDztDU0aVtG5AiAg10a5zswwDY5xOJ7rm4AOwZZwhMjCZ9Hkx603I6y5Aw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":720079,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgzfRbCRA9TVsSAnZWagAA4ywP+QAOMoB5iS5Fm6egYVTo\n3nZVnjbwk1o9jcFZumRtbNbBLu1f8GetbrxK/DTL6/1u3/f0CIAE7gZM9Cb9\nlBDLjC1xr8ELgrjXZVMmNh6APC2MaRCYekVJA30YbhPRvNNxLLx78E8J3+p9\npWcnH2ntu7bhY9skaRafwhtGkd5iQcWwDT46AFadfvnElW2OLp1ZLzvWSexi\nvWNiquT3zD+De+L2JrHB8UA3ojHhUzdkU2nkB1yiYqCLjWKbIN4O0uZZJVzX\nUZci99ma2fBhgdiOiwWAoXKIGMAKAg6a3jeJDqhfn6a0vrG2t4t9qqeOgf9d\nhXaCGTLX0g+i3lND5h84smcyoj3mKcDJjB2hcFn/guw2b18FohLPrJvftjPm\n/DwiskgX/QUOH8QenhaWfTdeHlwVUbLfPJww4bhQlEb8ga0Q5z4L8OzC/Nsh\nQIBr4kzulraBD2oZaDHw2QSGdthBWCwGPQ9iM30LDcz5dknIt3aTkgl/MKlR\nB7n/Ehk9eBk/IV8gwMDAzouGYAhEvvNZyx4XwobuCH66sTdx7HJfXa6Ij4i5\nN5Mpgu+vsjsn7/Jbz5LOicaSyRwUzu8q+DlagX+kaxwhvuw7YtjsnkWP2dn+\nTy70D92VXFufeciyyOKd3AH+xbjw5OPXP5Uc1W7gpLqoIV04RQlPWA3xE3lW\nbhTY\r\n=yb4G\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"e28c516b5e4ed0d957ed4646f4d58937e084cd78","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.49","@balena/jellyfish-logger":"^3.0.19","@balena/jellyfish-metrics":"^1.0.278","@balena/jellyfish-environment":"^4.2.6"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.0","depcheck":"^1.4.1","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.168","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.10","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.2.7_1624110171242_0.7217287625008177","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.2.8-renovate-major-external-major-58c46ee493e66990f690caeb5b754ba3e2f5d04b":{"name":"@balena/jellyfish-core","version":"3.2.8-renovate-major-external-major-58c46ee493e66990f690caeb5b754ba3e2f5d04b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.2.8-renovate-major-external-major-58c46ee493e66990f690caeb5b754ba3e2f5d04b","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"baa37edb62bfd264cb6040cf76a6575a903af4f3","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.2.8-renovate-major-external-major-58c46ee493e66990f690caeb5b754ba3e2f5d04b.tgz","fileCount":190,"integrity":"sha512-W2XuhBxq3qbWemj+LJlefillIWS/+KGyrUd9UuBaF2GyLrXzx033+8rP9J7INtbKwUBMuwW6JuuAdGXeA0Lmyg==","signatures":[{"sig":"MEUCIEmsGBQO027cE3eOlzjNIm8X6FkEaIYKnYOfTOe/3N+aAiEA5W+yw9FD5Junjql7pYal035etJ4j0Fu/PhJH5lBotk0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":720220,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgzfVtCRA9TVsSAnZWagAAzo8P/23D7aB8xkeXcmBLZBLL\n2qa8Nr9O6gk+aAPSfMlih6gge5tIZkOZaeLlyBst7yfFdsZT88MkJFh1JPjJ\nSiLCjcYcV0yzNq48VJbIFBg8zlWK4F1h7lwUAP0CRRlroh2fAwyl1yRNcNr7\nnqqkTXfOu0TMjYlQbCbdR41DXZFmHd8ROlnLITZFPIxn6abZuCa3cykD9b+E\nQKctBBnV2ZtGax7DEpauPyGu9kV1Kb6HXSorzM0nqlzk0sv6nIs8VUFKYS/x\nYARTZsUQmY/CUMe0HxD75q/UYjx293Q0Hd50S5ahSqRBtwABcN6ShP0Fo9t1\n3kZILCMZjCmMKycX3aS4kaXf1JqFLRAk2KNYsBG1bcDHtRu5HnAj31oWl2C5\nY/z0H7fpo43lKhx/B4Ehxg6hMLIe3Nsyz1GrUs9aIdCwBma0EnkJdw8ffOL9\nsXzd5O3Oq2ihsMrb0345cG/eJcJyjtjmQ7cVmX9oPXQUHA4uQ4DfpQdVYTMV\nYwM9LzO17LtCbH2r0gAAfjahnT/PVeTDVXKn1g2ScjxWj5N1by8HrJxP9wuw\nfyrpsre2kUYF9sXO7bjJ+3SxUwmlCoh2ju9796QBc0l8epCV3g+rpVcVjeMe\nCAeEAxO5Zj6qUxVOr0+05dbJX63AvZ7jfX/uO4cjD5CkIx9HhbI+cfJvJWh9\npRBq\r\n=FYB4\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"58c46ee493e66990f690caeb5b754ba3e2f5d04b","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.49","@balena/jellyfish-logger":"^3.0.19","@balena/jellyfish-metrics":"^1.0.278","@balena/jellyfish-environment":"^4.2.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.4","deplint":"^1.1.3","ts-jest":"^27.0.3","typedoc":"^0.21.0","depcheck":"^1.4.1","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.168","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.6.10","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.2.8-renovate-major-external-major-58c46ee493e66990f690caeb5b754ba3e2f5d04b_1624110445090_0.5104098449719849","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.2.8-renovate-internal-patch-d7936546e09ee95ffd6850681d43e3267f58c619":{"name":"@balena/jellyfish-core","version":"3.2.8-renovate-internal-patch-d7936546e09ee95ffd6850681d43e3267f58c619","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.2.8-renovate-internal-patch-d7936546e09ee95ffd6850681d43e3267f58c619","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b44aeb1867687093a4fe1acc4cea7f6a4af19600","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.2.8-renovate-internal-patch-d7936546e09ee95ffd6850681d43e3267f58c619.tgz","fileCount":190,"integrity":"sha512-e2Po10z2njbpDIDEm6os4yyfDOQ+2FaK2gznBxR+jHtns8yyZccIJTZLcVQXTGgCdeHRY7/cgXUlP/7c0/GWvg==","signatures":[{"sig":"MEYCIQCaqhCCEKurrsiwpnipYkQ3fNGvreI/YsqVr46Fu2dQdAIhAJMr2LoPRfgMBbAmz5oJjFQvKmjvQDNeXrve//71KTQa","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":720214,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgz2ZgCRA9TVsSAnZWagAA+oQP/2WMhjK0rS8JpWqbfkCB\nFVorVmb/z473yhEvgA45o2IvWQJgvIujBNdswvAtTusSL4lZ991VPD4JHbPp\nJ1nY0d1L7Kn+R39ebJSgY4ruiaTGw1p1zwdSMXLm+6D8oVoOEpqOyQAvodbl\nz/dbEs9wosW0PHDHpGf8GZpQbixqE3iUJxB/liROgA5z99dy5qrscW+3MXFp\nYQQfFyh8hXNLTim11CZfM/mZrjUpkigwjbcbix+pqcLKco6cYoWfT9DzKzyW\nLb1oJysKJ5U2Ah1S693SNlgleXF3/Lr66RILGa0Q5BxITznS8CRCrnWDFQg7\nJTx9utazIgb3cfzEkRDHYlMF52H3SVzeKC2buoe4Saxd4VEY7TYkFHd0eG2c\n4pFewp8IXpoFp86iBLMqZByHbGQY+p0swGN7uo3JNnnDsy7KXnNeio+I1244\nFaPUyAuovy9NPPwn12QrzEwMwichi6MASls68vfF03S8mZHdiRHTuLSiFk6/\n6ZhcjlBr89aHqSvobDw4xfeSv0O7wEZ0wLzGmNxAkSMA0JifGVztwOMlrIpM\nbQ61QmveWe65Ra5HG3pcJNlNSA9YaHDMOou7cI7PqPM/A8EUHBfGFYr/0aPU\nYjfeJglJNVO/baYPwIeLQyXMy+/C4p1+fVi3aqGilMyRa6i0uRIxUaut9R9v\nutT2\r\n=okYo\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"d7936546e09ee95ffd6850681d43e3267f58c619","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.49","@balena/jellyfish-logger":"^3.0.20","@balena/jellyfish-metrics":"^1.0.279","@balena/jellyfish-environment":"^4.2.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.0","depcheck":"^1.4.1","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.168","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.10","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.2.8-renovate-internal-patch-d7936546e09ee95ffd6850681d43e3267f58c619_1624204896043_0.4716427860085983","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.2.8":{"name":"@balena/jellyfish-core","version":"3.2.8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.2.8","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"493cc9fa5748d9a68b458bb901c8fc3f3c0d44a4","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.2.8.tgz","fileCount":190,"integrity":"sha512-gBYKb6fHQ4xrIpDvt1k7ugP4drFxb0HByO155MSwEOmdkIax7b6JUPaIZVRSPahtpsNXbEeLu53JiQm+2TSxFA==","signatures":[{"sig":"MEQCIEbwzGGylZyHtZPyElMt5NOiVgzBaIB9FWJCujRkePfXAiB+tFH+NuVpRzAZan2/JDIETlj3NaFRNjPog89LtwkXbA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":720149,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgz2fbCRA9TVsSAnZWagAAU1QQAJDqIWGpEQgRDThH9fOE\n7lKSwPGHG1FFxbDPWl41YcBgiG/t0WTFWhcKOeOyqIFlTnX+AlzKgli0KMEC\nLuLEKwOkcv29QngSYvojXGMesMNg6gsTFdZHbQmSMR9y60mUToWN4ee7P9vm\n6I43QTtO4VxUjaa/c35WqvxzHr2Sjru0gxTiA/ZXFcmlvi+sicAwUJFGHxP/\nKS/dyDmoDxwxFy6Ix5ekVvwOHysqkZ1VNvdBjnoqs059MuyFEsQ42Faz6+y/\nyg7rvqR/RtwdERPM+VwY3BFmmzhCR468i77AY2GrosEN35neS51HFa2K07/Z\nEpgmvCevUY9FuKxY3VvbmjSTqBMZHX+vC3GvLl0AVYJ1eaVLkE5luQx1+2Nc\n+tk0Ep8GS7AE3VZd0z/riyYgdyGpLIbVv3c8xcT1O+J8mhPogJPWjHHCRIUg\nye+WB7kDrNnbGYKNEwX+e78Il3mBDGJV3h/G3DOCLLl5jJvtzWs060gFraU9\nS1OVUqmtKkluDjjxdr4Zwdz9cpVhi11lWgtQ0zMtSO41jbwHICJe01J3umy2\nJQs/uEZ3hFVyNRMSE7jkGXfbqAxSYe73/Tg/EhlC4CXGST4YifUW8VQjsRN6\ncbDJxAZGs2xwYPVbJK2l1EonDwvjMikSlnkOeFPk+VwJmv0ZPwmUTKRXZpHh\nh7hg\r\n=f2zs\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"d0829a2fda7aabfee93d75df8a09466af7a68a28","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.49","@balena/jellyfish-logger":"^3.0.20","@balena/jellyfish-metrics":"^1.0.279","@balena/jellyfish-environment":"^4.2.6"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.0","depcheck":"^1.4.1","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.168","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.10","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.2.8_1624205274683_0.6478615838284603","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.2.9-renovate-major-external-major-8e5fc578db156b5409497534c9f9d48f80448bd4":{"name":"@balena/jellyfish-core","version":"3.2.9-renovate-major-external-major-8e5fc578db156b5409497534c9f9d48f80448bd4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.2.9-renovate-major-external-major-8e5fc578db156b5409497534c9f9d48f80448bd4","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3b5b94e76af8a8a8eec7d496caf82643e71eb1cd","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.2.9-renovate-major-external-major-8e5fc578db156b5409497534c9f9d48f80448bd4.tgz","fileCount":190,"integrity":"sha512-WLmEISnrxaXVsDjHHPckKvjE5TYCe44F+VMoT3A6UzrXvbcc90dbDkPtQIJJmkSnz6+Pu9dmgiNXNPh7JN4nfg==","signatures":[{"sig":"MEUCIH9K3SUnq63i9fsjKBrXAKAJ5OmLER1ByNaVtTueTEQFAiEA9xD84kDTaVwfcFx7BITHoqBnJkn+ov2WNuvMs+AqgYM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":720290,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgz2jHCRA9TVsSAnZWagAAOuoQAKA7Xc9jkztcuIUP9zDq\nk0apeOCGxeeIf52jMaAmLilUiajiLCom9egn1hfwQZB/CdyaT1tXz+hbGirQ\nFIVUk1/HhuCEJsatMgt5G2dhi54l90ZQFA059FIa16Ppx863Bp4UZnxxHg9N\ndz1gRZM2LQ9YDJH9xqoL1syIVX3e5gKHY3VzTT3BsbFbIZ0rZK/yyOEZYvIW\nT2GsvKSd6x5rT6zAHsPmg002Ru/9TQqj6pkW/pNKhT284MM0H/StjM1XoOD/\nEIfWtOebUAzCnLDQQY8f1aFkU+szyb/QiYGQOxSN79eYUjGZtN76++1jgk9l\nJ69R1cpjcGHqJFDtywqrbMb43jmxDf+4dil1eH9/+6iwwbKse24HKx+25tU8\nHxwkkQv83kVNuaR2gcvk75xwVQ/FDqcjLTYdzZAVTr3kFjHmPndF6bZGwLgr\nl9mwEzBgUplzs4Pa0TIf65/SWPijpQO1fTKlsd89zOQA2C7FWFAijQ1jnCjF\nxoDjEqSxwjYOjfKkBBAd8YccJW5EZRcS02BKQRCflDqtEggZjD1OFaefDdAI\nk1lDPoPRSxWsrpsvOlQBOXOXhooeNCdNepKMicmgvej1Db1SQanwg1FGWs4U\nyrakpFLwB0A20FohMcH4mB8UhFG3tKv8ddUOuZiGq7bP8441Zoly+41/z2Up\nNxkQ\r\n=ZFFq\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"8e5fc578db156b5409497534c9f9d48f80448bd4","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.49","@balena/jellyfish-logger":"^3.0.20","@balena/jellyfish-metrics":"^1.0.279","@balena/jellyfish-environment":"^4.2.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.4","deplint":"^1.1.3","ts-jest":"^27.0.3","typedoc":"^0.21.0","depcheck":"^1.4.1","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.168","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.6.10","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.2.9-renovate-major-external-major-8e5fc578db156b5409497534c9f9d48f80448bd4_1624205510915_0.4915986688044587","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.0-add-loop-field-ts-47d940022a5408f9ce9614f0f72206f7d64e806a":{"name":"@balena/jellyfish-core","version":"4.0.0-add-loop-field-ts-47d940022a5408f9ce9614f0f72206f7d64e806a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.0-add-loop-field-ts-47d940022a5408f9ce9614f0f72206f7d64e806a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"875c5a7d150ea9626291efe0f4e69917be5ef62a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.0-add-loop-field-ts-47d940022a5408f9ce9614f0f72206f7d64e806a.tgz","fileCount":190,"integrity":"sha512-h4jfL1PM+Af9Nw334OX4bfWH21tTy21L6tFv9EaxgEr/HmrWYDvqB8ONnE3qnoaOR54WbR2hF1ShsENWIapgbw==","signatures":[{"sig":"MEUCIQDlWtRhUsuNDHx8UChiQcI+ItbK5wimUVKIiLZpfjNFnwIgeiKBHE429hXBnRpLVOVjT35+W7UqEH7f6lox17iP0Fc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":724468,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg0V//CRA9TVsSAnZWagAA3QMP/Ag9SZfsBUA12QBREDiS\nZB8eWsGh6utYpb8xrfVHgD0BxVeS5D29kRgP4WPWYdHNhPRDCcVBmElwcCUl\nfsOFnQAVEOC+kc1YUAdatSn0vcPRnyIvqJbMU9E0NUjI0kWcBezWo8t2UwV2\nADMS+B/w2oK4BdaoK8DbBBvbaXTi+0mKhvmpi0KCwUky2G5TfznIHu7wuxjc\nBm3721yxe7xQoaUg2p06MDoFHJhpogmvO7F/fp1mzwtZs8Jfo3Coa48VmAKI\n9KdKnMtXbLQ6IwauUQqZgyjlRna5fKuDtdviE4b9o069ZjNEz40UrRzcrgzW\nafZAbcxVWUDsxwSl7nOm95ahk9ONhwDWBX3B6g2bA9KvCM7FEJ6anJcSZxyZ\noflJsg8ctBc6ZT2MJS5TSHr2/Ce51x+F8oPKFyvSgpKpLCjVyM3AhPWEE58D\nqjxzf0PMZnz4K3xehsNGcYxGM7xxDlR3hRI9+tYiWm+vwYRHQmL9d2iMkZ4r\nyjlU6iRhpxcm3I4SvJgclYzeG7CmaFyWkcJCcCTkGmX5T1kp6o1UP0GXnPWh\n05QumJoqtbIJgPhKc9a5yDa5tvqQIOWKmn0zyDy5Mu6r/2gi6kVbuVQ8kulw\n5fMi/NFxOvKPHclTYc0FPTGc6WTwDF75xQ27D4fy0lquzdhxiw4zf4VXSL6r\nNBCn\r\n=UdUh\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"47d940022a5408f9ce9614f0f72206f7d64e806a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.49","@balena/jellyfish-logger":"^3.0.20","@balena/jellyfish-metrics":"^1.0.279","@balena/jellyfish-environment":"^4.2.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.0","depcheck":"^1.4.1","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.168","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.10","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.0-add-loop-field-ts-47d940022a5408f9ce9614f0f72206f7d64e806a_1624334334598_0.07663570216424809","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.2.9-renovate-balena-ci-task-runner-0-x-9cfb9dc73c0fcddaaad8a690b6a5aa5a23a9c69a":{"name":"@balena/jellyfish-core","version":"3.2.9-renovate-balena-ci-task-runner-0-x-9cfb9dc73c0fcddaaad8a690b6a5aa5a23a9c69a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.2.9-renovate-balena-ci-task-runner-0-x-9cfb9dc73c0fcddaaad8a690b6a5aa5a23a9c69a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"7f4e03ffd9822c1e2a60b98a10977b9c6ccc51c9","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.2.9-renovate-balena-ci-task-runner-0-x-9cfb9dc73c0fcddaaad8a690b6a5aa5a23a9c69a.tgz","fileCount":190,"integrity":"sha512-qt4Xr4j4SD+1wZDKEJt+tq+SegaUShWrWdOJTEwop/9h0PnPVwZ593IDwF/iMcrkARWxjP2Czq09/dcOgiR+cw==","signatures":[{"sig":"MEYCIQDZ9clNMphTUwDlaYaOWjiPBU4l9+DV5SIRfkFq2jdBWgIhAIhYcZDlrRoQ44KZVtuH9+oBVwyOd+bUsj3Lsk2pHgkO","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":720322,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg0YnICRA9TVsSAnZWagAA1TkP+gMwF0P/NaeYtkeZn9Wh\nAQytpI2UArKZ4Nj4g6MhcBF9fH+FbYUoj+eh2PbLBBZbucwmA8dndBG2Ty9D\n7MRvp1r5hu7ZJCu3czxdPIihy7ntBpa5BAJziRR/hsQCSlfbT7aCJrUyls0d\nll/PDSzgOA/N2YyKteSLpiBcYHnScIkkTHLFHRM0zhEtpAduQWqEgRlD/Vhs\n051rKeWXtcpdTMGRg2uNtoFdZiCVEYffLu3u4OlI7rXSl8+xTEQczz+bwvxH\nRIa1v5joMjOjlzIPImn/VAkVVo1bZSKeYV6t9BC0xitldTrWP9GpB2EbZDHx\nlskMEn87GVdfQcWCVO2o4SdsDOyZcj3+9+jqaXSUA1+bApqYj4VYz579dD19\n6D4UoNRbRfEfqzMm8nLmkMxkco/gG/R5Ack/znR2Tw2Rc9g8w8q8Q/BUHvj/\nfhxCneMaO0ChKCB6gTbEafiN+V4Yt5BbVLvHxjQGIo+yI3YRTuvXuyZhlw2l\nKlBkwwm/fNKve4mCUIgTPF66bLkca3XXyhHzWM3sty1Pe2/t0sBfLkqW8+54\nRv4dXgsfkQZjkc8GQvF9NmKbGLEfR1CIRktndVN+jE4rh50UW/OD+UGx84p5\nSaVJAQkxq0jmW4URsQUctHAbqQw2uxs0joOpPeQfIJS4T/4Ewi6AzMxImZbZ\nbpjp\r\n=BbXQ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"9cfb9dc73c0fcddaaad8a690b6a5aa5a23a9c69a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.49","@balena/jellyfish-logger":"^3.0.20","@balena/jellyfish-metrics":"^1.0.279","@balena/jellyfish-environment":"^4.2.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.0","depcheck":"^1.4.1","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.10","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.2.9-renovate-balena-ci-task-runner-0-x-9cfb9dc73c0fcddaaad8a690b6a5aa5a23a9c69a_1624345031532_0.9656352772972756","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.2.9-renovate-internal-patch-df980219692af1b53e235c1fad0f526db3d1a76f":{"name":"@balena/jellyfish-core","version":"3.2.9-renovate-internal-patch-df980219692af1b53e235c1fad0f526db3d1a76f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.2.9-renovate-internal-patch-df980219692af1b53e235c1fad0f526db3d1a76f","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4498f758239f097164aa1b8cc15fb009365be5ef","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.2.9-renovate-internal-patch-df980219692af1b53e235c1fad0f526db3d1a76f.tgz","fileCount":190,"integrity":"sha512-mCQl3eqZV+U/FQrWYFQ9Ror/NFxcIyUMqj9PhFu0J4lyG8qoW3zbqwxCJvhz6fTjERXVNWvvrDoUERdJ9HhQtQ==","signatures":[{"sig":"MEUCIG0BnUdnZCs7Yav+C+w8FdQO4FI3J9nI32HzRLUUFFBmAiEA9vaZ2q47WuKpBZ+fBAPmBawiyK5jRBj3XldzZ2XiNJs=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":720284,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg0ZayCRA9TVsSAnZWagAAnw0QAKKWXrJp5NAjI0bhCczZ\nf0biKmee67GLgMDCtRaMifTlp811f2ilJkKll8kljkPkE/8xCDZJpnsk+p7m\nyxoDE6QHJNZiDeDAYRQkNSCBDYOMKnsDiDUqCjC5xG7A5rmTjV/wHvlp7D1S\no0wHvEe0FUeTgcK1XI3J3XfQCLLNWb/rPNv6wDNnGEOHdNavuGMyO1BhBRdL\nxx/mvt3S9LUP6n001AhmhkbsAlG6Bvl1PKTzLUBFl02ARwErVkCo9z8KwZHp\n8lBX0rpQy98Z5mt3KRUjh9g8KB67nA1xLrPcqD59X3rKBlbMBdq/ulUz+/eP\nqDfaduEmupu6/WTSJTtGlUm8PYY2aSdeaTHDQpCalesCOGCl25//iKTMu9+B\n/zsyjxE/Sg/saoQKBrK4KuUZ7cuFeYtoE758xNq7VF1akqZxDBeCB+JZW4Yt\n1AxX87qUVUbakhK9SxWnheel08bN9ozPiHC5zoIKgfvR3wNBl0NzaVg533yf\n0KrTU/MC4r/PhDDa3I1RnJZwu5y1CPz38ExgAAsVPeFnXSLLeG63gXekwL4X\nowturOq7lKXAZ9Y6JSDId9Ask5OkW3QMEuyXB+axgPwl6rpE6icvRHvSNZq3\niB3vTlOtIXbC+JOav+MQzws3ISO7llSvEkiaxYh9oxQb9aIjQ8jk/rTEFXw5\nSZkP\r\n=uMDJ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"df980219692af1b53e235c1fad0f526db3d1a76f","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.50","@balena/jellyfish-logger":"^3.0.22","@balena/jellyfish-metrics":"^1.0.279","@balena/jellyfish-environment":"^4.2.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.0","depcheck":"^1.4.1","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.168","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.10","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.2.9-renovate-internal-patch-df980219692af1b53e235c1fad0f526db3d1a76f_1624348337644_0.5630451199941333","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.2.9-renovate-internal-patch-3c66c3df98c28c2622418a4875fd58aa045f6777":{"name":"@balena/jellyfish-core","version":"3.2.9-renovate-internal-patch-3c66c3df98c28c2622418a4875fd58aa045f6777","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.2.9-renovate-internal-patch-3c66c3df98c28c2622418a4875fd58aa045f6777","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d9bf73edf4a808fb7bd7366a76040eb1e56578ef","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.2.9-renovate-internal-patch-3c66c3df98c28c2622418a4875fd58aa045f6777.tgz","fileCount":190,"integrity":"sha512-ZqqCrgiib0oR2zNR8WrskdMJ5Oifeej3fPnRGVq5U2xIPiR+ug5uM3MNYyAw2uP4crAmvZp5nUvfc1JpGg1qKg==","signatures":[{"sig":"MEYCIQDUvpMdeXxpTJ9zqHd9NKUv73BbiAcvmJlbHikpi7Z4jgIhAOzkZu4/lu36UHM1GyyTCLmjte9QKs6Vw+9cRLBE6Bga","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":720284,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg0ZeKCRA9TVsSAnZWagAAtWcQAJERpf84cAwrRjZ9qHF+\n8NRQCyES8ABrNujphx1VYnp9exYbfc7vfObsLgcRoJYecV1C+hYWyC788bb4\nUptVkuUpYx9+kTGSH1zRv0RxK9T/SmWlPBo15c6dKO4wKBYZDrX+9dzjKHqt\nh1Ns/7a1nRagWqGkS7QrJ9WVOI3Buzd2/n0wuh1IYQn5afTtzoSSNRqp8ukt\nDxnF04uSx4esJE0uzmkNCuJE6L7kxWj68CRf1JFWepINTkTi54Gy2Ycnv4nE\nnrOxjqzE/exS582w51ovc9m7ukB5p00vHHcXWP25BCyFSICoSmIZqBoLc6Zy\nmPjU4oUPpFRza72EB5TBrBPvNJJmBD6knDSnhieTwoH6NDzgO3/1kUloF8x3\nUPyld3UaV/l0900RDy6h+FJS4oqnEfErbO4ei9690MPiJcDPi5J6cwwr0g9v\n9N1dO5cr5QFBJpOIky7fTE2Jt7z8RYSMb3ydN2lV9Sqo6aNFgd4HgsWOmjQG\n2rlfclPLNOzEKy1uB94p9ObEkjS4aLT0wKLBWEbzvjsCdggy0/fy+AWjlwbt\njnO+KaJCK130oKcesGKtEnkj32J0sWLNfeEvU/80qRECfB5Km8yZDDQr0cXK\nnFCYDWWeVmBgXb+8k8YQqEE/vCQhOGnBHtr9rbV9rjXzCY8woz0L7J2klYY7\njCCs\r\n=BoTF\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"3c66c3df98c28c2622418a4875fd58aa045f6777","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.50","@balena/jellyfish-logger":"^3.0.22","@balena/jellyfish-metrics":"^1.0.279","@balena/jellyfish-environment":"^4.2.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.0","depcheck":"^1.4.1","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.168","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.11","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.2.9-renovate-internal-patch-3c66c3df98c28c2622418a4875fd58aa045f6777_1624348553748_0.22816815883146568","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.2.9-renovate-internal-patch-6e3d3e7afc9c60a4949585b7c1b0a69abf1080dd":{"name":"@balena/jellyfish-core","version":"3.2.9-renovate-internal-patch-6e3d3e7afc9c60a4949585b7c1b0a69abf1080dd","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.2.9-renovate-internal-patch-6e3d3e7afc9c60a4949585b7c1b0a69abf1080dd","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f426e9171d515b820254fd36068532137563431b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.2.9-renovate-internal-patch-6e3d3e7afc9c60a4949585b7c1b0a69abf1080dd.tgz","fileCount":190,"integrity":"sha512-HHlskX+Ynjq7cRr/pjxeA6KazXQ5MPPWdmHOzCphK0SUIfRyCdf57OA/M5MbIXOjnakDSzQB7h5lF6d8Ei817A==","signatures":[{"sig":"MEQCIG6KSZXuIf6TzSaMyGsYQA/fw9AFmAOVLcICsJrL8uM5AiAEC2k61l6Km3/V8cM/R40ZxtKtZFL2Y6vWck2GD22yzg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":720284,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg0ZkLCRA9TVsSAnZWagAAUQ8QAKFwa+91diX03gnMWbyo\nCyRdYSEtcqMpCZrrEu9VwGp0oX/P9YhXyj2V2I1enqzXd8Y402tzXlTulZWv\nKIOJERgKR6wQVUdd616fSdHIIWsOslhQ6GgRlMVvwQi1lbo6Hgi/kQloq6+J\n5C28jPn+/YKraUgZBADouXi+cv594C6ipnxJxxBcFnUBMFMqln9sANgLhV3J\ndpXeImT5J47YQiGDeBk1mi8f4rE0kKmTEahBqppU2EyU47dmQJTQwd+v6OGm\nWg+CKTAsymWpi0B8rsqQo7T6psS6q/U0XlZDFZsYrwvQa1lWD3Z+FipLYPhQ\nAzLP9U5b7wSqKoaXvGYp7PJTTXmuS4VWCjfoHHCsaodCiz9Wp9CtIR3VEgf+\nIwdNP8A+AGuvCMElY9O1lGInsLteQLxup3lHmoRIVhPOTrkz8SkKXBmLS2CZ\n7uCTQNC9AUV9eRM1p19dgYb6LvN3GVYmhUZxcPXDHEaBZIaRH7ojywC2I7NI\ndNFxiDthDqe9XyyEdNpuLcBQAxcfun5kMOUNseRQUl/tap6JAXud3XP85+0w\nsTVNuwQOT2Pabeeg3DisHpGFCtBxBXcGOFk4DcRIoFT/wQtYvMysh1nQ+7W8\nysYQfIkyydCzuZ4GehlcSDJ1nbJcJleyu+m5pvUl14rcPq0WSlJhuviJ5bJW\ndulI\r\n=DTtf\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"6e3d3e7afc9c60a4949585b7c1b0a69abf1080dd","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.50","@balena/jellyfish-logger":"^3.0.22","@balena/jellyfish-metrics":"^1.0.280","@balena/jellyfish-environment":"^4.2.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.0","depcheck":"^1.4.1","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.168","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.11","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.2.9-renovate-internal-patch-6e3d3e7afc9c60a4949585b7c1b0a69abf1080dd_1624348938517_0.9159442672458762","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.2.9":{"name":"@balena/jellyfish-core","version":"3.2.9","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.2.9","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3546e8da156e8fbfc28b64c5c4bc43c50323a76b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.2.9.tgz","fileCount":190,"integrity":"sha512-cmaHMQFMEsJuxRyMX6eEF6FZqOJ6xlXutipQGnFOzHxPY3E/H3Mqjy9j1SpkxaQ+3i0SNLuSZxG9G9vHPfOf7Q==","signatures":[{"sig":"MEYCIQCTufUhd5yUs8jW0SiUh6gMvLeOW9p/xqCSKd9V0J5rJQIhAJSyZIhaVC/84CVe4sOEc4j/AbVd5JrYboz95lWycyER","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":720219,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg0Zo5CRA9TVsSAnZWagAAM34P/i5VxhWZkDhmPKRuOv+8\nQpOjaVllvVj1JhgZLQkKw2v5+xZjS2JU5QNGDRWien1dV+8NZysXEzSCv1FL\n5v706tPhDPPbKWGV48g5Fs3sXJW2pd9QJ2OpDXANg9J3bzCirFpl4H6LqC5V\nNMwjz+NQ3MIpZOUmCCyRWS/TmAOfSzBBkC5AyFh/vFWQf7j4j4bvzubdWO3Y\nGsx3AC6+UGk+QLRZyKcLBqt2VwEva786AOrATZZz+oO6Kp4QiGc4O1dMzUOA\nxx6TKd/cSeuA7a1HXGcW5cA7XKYT9xzNZnNIPxnkyFzdBDLEXi4Tty4RRl5R\nY3Houe3Oo0w7OxGq7rusMVX1XKDcIDetTt0mEqJ0IC+wL7Jz5XCZpFwTqyDe\nWlFC57JOvYonhpjUg2oA9+KUIaU9Q56zKkbQFrhMKuYQwXKV8l6RSEXGylY6\nCSiRWnF1/zjpWmjXkmuS1ESLf37ZIQqGQvzGv1wCfRUt1dgsca0bDwBXCC93\nnqcKAk/eEFqaV8xeDA5TtRql4cBc2VgjVJjLfSlx3VZbJJKUMWNHDLmpRnFe\ntZdXIUJl48vvkw5Su2DYKmH/Nng8c5N5Yit1HH4WtVZsfjoi99+L2KJMssJ0\nHpy7HKKgmsD07C8Y7aUTDLHyYJTfNnq2mWGEtU63IH9bDjfM1GLfFnxLFJGI\n9sFt\r\n=mQF0\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"9df908e23d468388c9929377cd341c19d9c38b8e","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.50","@balena/jellyfish-logger":"^3.0.22","@balena/jellyfish-metrics":"^1.0.280","@balena/jellyfish-environment":"^4.2.6"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.0","depcheck":"^1.4.1","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.168","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.11","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.2.9_1624349241024_0.06304028202081224","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.2.10-renovate-balena-ci-task-runner-0-x-b49d1b255aba0ed8898f1f7060770091ab8bfa79":{"name":"@balena/jellyfish-core","version":"3.2.10-renovate-balena-ci-task-runner-0-x-b49d1b255aba0ed8898f1f7060770091ab8bfa79","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.2.10-renovate-balena-ci-task-runner-0-x-b49d1b255aba0ed8898f1f7060770091ab8bfa79","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"757e1b0294dd16fe82f35e9695b3b0b2e0325eae","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.2.10-renovate-balena-ci-task-runner-0-x-b49d1b255aba0ed8898f1f7060770091ab8bfa79.tgz","fileCount":190,"integrity":"sha512-yDLJGWnOv+KidO73fE5XOjx1XSN2f+vCSVBZwi/7kXYYy0X2tYG9RZWpmWkGcP4YuqGsFJovLc5HV+njF0neMA==","signatures":[{"sig":"MEYCIQDPloMcqgnOLZiC9kQlSoAp4w5V4ipKfA3WDJPhWDu0hQIhAJ8BDYZbUTZsWTJhvYPBo75/icqlNjwxydbX28WK9k51","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":720394,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg0ZuPCRA9TVsSAnZWagAAv6oP/0rTQ2TdIh1M930vxTwH\nWLIa0U1VL89dZme9tdbmZWg0R3pa6Cm0LE3rEgq2bwby9LsRzWqJxwkKvAhd\nyO9hJHpG+LaZyTm2XIn5HHeZlQ2L/5Q4QsbO3tMxMD4MspYG2WpABTVfKzji\nVY63/afoXsepgQxu/n9ttPT8OBtYKc13cq11d1hIZ8RKDMvVzF1M3vg9Vwd+\nmCJoG2ZoniqOq5FI8jWMD7sB+OXSsr9Nshqk4Z/NV2BV6NrEdSUmXmcUc/EW\n8dQQfKzVJBCVs2omwHHZW7D8CaDeEat7Pkg2QlcdA9zdM1U3liq3VKQkJ8Z0\nBGJ1+aYL9WatuI/uTcqydlkxeGPtyNkUJlSIWF7CqJX/qlhdDFV0YTaAHaVL\n+holDunWqC7cgsfUZaOfTYYH/DH8bic3211u8jb4KyYzeOYtWnsqJh00DPr4\noGs9CB7YAjNQe2WpLnU4fDz2/Zy5zWMRDBmeVWm+5OT0ZFQ2scQ/cRs7KIlo\n9QMGjxByrev1Kijr9Deie0cSfPeLXN7lGva2P871R+Jtw5N9PbELgDXVOykY\nmZHtFkon4gJzlky8VYY1TxhheM7Df8HO1qyDregQNFkbqx3iNK4o7d7udgiw\nzF/W934YxNAxqb9slrq2OkIiD3RtSfGnBDGAYgNRWHSdg8+kLfgqy0ygUp3r\nVWKY\r\n=9jvg\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"b49d1b255aba0ed8898f1f7060770091ab8bfa79","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.50","@balena/jellyfish-logger":"^3.0.22","@balena/jellyfish-metrics":"^1.0.280","@balena/jellyfish-environment":"^4.2.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.0","depcheck":"^1.4.1","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.11","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.2.10-renovate-balena-ci-task-runner-0-x-b49d1b255aba0ed8898f1f7060770091ab8bfa79_1624349582641_0.8801249947932595","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.2.10-renovate-major-external-major-79c5ef7bb50d4ee39b6f80faf25a9a29926eba8f":{"name":"@balena/jellyfish-core","version":"3.2.10-renovate-major-external-major-79c5ef7bb50d4ee39b6f80faf25a9a29926eba8f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.2.10-renovate-major-external-major-79c5ef7bb50d4ee39b6f80faf25a9a29926eba8f","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"554cc376a71bf035d2cb8c61c1b4422063ef579d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.2.10-renovate-major-external-major-79c5ef7bb50d4ee39b6f80faf25a9a29926eba8f.tgz","fileCount":190,"integrity":"sha512-rjBAQBFiGSA3hl+sVfqTOKWAjaXpevSPx5GPRXMBxarPL1WS0qimI7upAWJCkWot41u0/RvySKUui7y19+d55g==","signatures":[{"sig":"MEUCIQDm5yIJH5tasNhyLv3UeWXmXjSt/qKy4c/iUswXoazvkwIgLXcUZN6qlll8Mgfdwg0wVy/Ahn2S3zoEgni/EjkQPeY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":720362,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg0ZvACRA9TVsSAnZWagAAwgMP/0ZAzOt44DPdx9utrKf1\n/8pHoUuz9TEOzryS9cvU1781j+fEAc5mqWeurN6FhnDYXVVPNnDNatCka4lQ\n5ZMKtjnWpainkW+Hp6KJMyrxFZTbRG5UoYaeBgJS7bPXQBLg5Zl7aYZTY8C5\nYddVRaou+OnF3kwYii0W8sDY0aGhloEGbXZo7XXFVjAMi54mTfqp/mh4Tc54\n0rkRSQVsx232o996LSen3Gy/qESJKsrG5jd4Rq8znPAOotsD8u70zpVDokLn\njQKc4weuYpmBb7/W15mYcmiy3hoEW0k9eWOrxLuQe5Z5JUb7mZ6J11p+6liF\nOQexCMc3darBZ7WTvtuEFLGX8jl7m7MYDqTnWVwExIqtOSxzIGJVirMjMBXK\nG00FZr4Z2C0XHz6XY2uDmQx7L9qEIasbAoKIY70fURLNjS2XfwKK5ckRg9wi\nwBS4/N0+3z+Iz7Ij2tSJJLFVM/zsF+8OiWxUkuIxLHmHtSShR6max56R12d5\nFxiPgfFOVj8s9UyAND0VLJMGv0PpXWgvKeJmpyWHy8W2YgeAqH5e6FQgjCWb\nMvd0QUswoUlh2XDuZmToNlw9WvquEiZjKMC764eEORiJdav9Xfdu+XAFydre\ndmjZ4XQuDrx/N45U3+jmTMf6kz/exuozPz1mhEB9rnem2CK1kofPIPFmx4lE\nTHsQ\r\n=gB+R\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"79c5ef7bb50d4ee39b6f80faf25a9a29926eba8f","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.50","@balena/jellyfish-logger":"^3.0.22","@balena/jellyfish-metrics":"^1.0.280","@balena/jellyfish-environment":"^4.2.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.4","deplint":"^1.1.3","ts-jest":"^27.0.3","typedoc":"^0.21.0","depcheck":"^1.4.1","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.168","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.6.11","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.2.10-renovate-major-external-major-79c5ef7bb50d4ee39b6f80faf25a9a29926eba8f_1624349632377_0.8699300347341357","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.2.10-renovate-internal-patch-4b564f52a1d4d72df71a7af06f4fe6a959549405":{"name":"@balena/jellyfish-core","version":"3.2.10-renovate-internal-patch-4b564f52a1d4d72df71a7af06f4fe6a959549405","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.2.10-renovate-internal-patch-4b564f52a1d4d72df71a7af06f4fe6a959549405","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d4297044f67c440acd2de36e7693ba07122127dc","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.2.10-renovate-internal-patch-4b564f52a1d4d72df71a7af06f4fe6a959549405.tgz","fileCount":190,"integrity":"sha512-dZD+HuimVk4W0Uz3y1SEMPAj3YpbK1buB82JvL6+elMrp+UMmvm33BB2RGjVRiV2ZtpDSGnJu7ZjGCLwSmu2Dw==","signatures":[{"sig":"MEYCIQC90AdyfD1NKyYWxUiujT0cyGa8h0ysrNh4jL85V6BJ8wIhAJFF0NQVhsQotxA1auPt5pstcluYTptO4xJ9NO+4IyMV","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":720390,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg0ZvnCRA9TVsSAnZWagAAHOkP/2Efwvk6OEOKxYvfQfn3\nmzorahgZmwFscs9/Y0QD6HrjZaYiuYVs7209TWe3VBdAb/ttylvPvbYacx+p\nGMxw3LrwLhoSqrBiTzqDz9+7JL+qa8mgQ4sykp+faVc/jZar+DpH+ieDR3o7\nhuADpj930dbGt6QiEJXwzKAj/GH/V0hFmWeqvYBZ6dMvKZaRn37QRLw6TFTn\nwl0NTKMA5YdtA3rzaV/WFP8dPXBjzWlIzFZvCa4A8aMBu0cjVMBWKxzSGyXy\nreN11JvDOd/5fRB7V8GBcWNf1nU3di5XG7FgZ7pp5EAFobh59vgMv2jZJiFN\nD3/ApOEgF2u3K7+GqkzBIR0rEwh58UU3wz3qi2YW8rjTUoRy/He9DOzRYUVp\ni91Wu7VsnVxb4bIvIPfpo3/vBW5glI8i+oC+0e5sFW6WQP+be1P8otUyJUCV\n0t3fseSuGEJ/KTBB7nq6pFYS6gSdFenKtKiBbkbNYgipKHeP+hQ5i4SfiQWL\nhBIjy5Y9yjiNmM6n/z6UOt7rZTsYCnjJdl5pQmb+E0BeE0hs0qiX8TZOJcAv\nNmee+erI9W8cujWZSJyUBOSg1mvotvrUBs523JYa49qyk/Lj/oKMD7cbVXPZ\nXwRJmOrlzXV6pnUzEX/XKXd8ZH5vfcuWy+ERebr5wPmrZ+knnOdy0kwYnLP6\ndODs\r\n=oQ8m\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"4b564f52a1d4d72df71a7af06f4fe6a959549405","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.50","@balena/jellyfish-logger":"^3.0.22","@balena/jellyfish-metrics":"^1.0.281","@balena/jellyfish-environment":"^4.2.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.0","depcheck":"^1.4.1","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.168","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.11","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.2.10-renovate-internal-patch-4b564f52a1d4d72df71a7af06f4fe6a959549405_1624349671379_0.07708706985985181","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.2.10":{"name":"@balena/jellyfish-core","version":"3.2.10","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.2.10","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3caa6ddd770f16ba796afb8215c1528de97e02c0","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.2.10.tgz","fileCount":190,"integrity":"sha512-lvIfJsU1rp+rPlHrMtIgPRZh5dy5hnpCh5Qx20xLGoyGSf570IvgRZaQXvuoS5W7iB8S4wN5v9+Mw0k3QrFaJA==","signatures":[{"sig":"MEUCIQD8J+i5g7CGqpxF2l/GEAM3IUvVs8sUfRo/Tq7UdK9yPwIgbtEZ8phUxuznVrSFuWIGOC9LWZ75+htMDot4uzgGnto=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":720325,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg0Z1LCRA9TVsSAnZWagAAzHgQAIsDoi16qDwrO5/LAL7v\nxASYRFbLA9jItXTNR1L2wtij463jDL91WjfjirYFxEIb+QlzI7qNcuknomit\n9c4i/WXtZ2+PTJQHCpAJme5e5w2IDEcOLWCYGo630EtTl/B8YAxFeVDRjWhI\n+pKnTuJUG4RqlQugQXR1iZ2c6QBgdOcWRWQZL3/WJjz5PShSVQyudYitRvYz\ne6GaHEXrGU7vnPHrp9soLJIXDod7IpB/K6wqpyQ6SunA8KKOYAy93fs8zNd4\n6KHcvajfr/CkeSkuM3rilj+ervdVwgD6HVKx+VmhN0n0QcS1mYwV9w0Z605C\nUC5m7NFIGcVLI9fcs7zb7GZbDH0DWGnLRvv+mUoRuzJfToan5LDR467oNZgh\n2anzEdky8aZBFDvjSy6TwziveGTunMcM1DH7zAGmyhp+Ewn9n7G7DMHnqrZA\nVo3oXxIU5xirEEgLzIol/g4bUd0i2pXkSySu/VHK07onamoJXodORuB0TdXJ\n8XYxy7ss/gcXJBh85yCqnzIGDjvYIPDsNxWHGShrROBBLAaXinsw2giFqcjd\nGEStk5jQ/YczEjeAyBD0p9zQtlTsOjLxJwg30SuqhVLfOBAYwA47ja8opR2p\nYjBXiL+IHnVL4Vn+mZoCC8sj6VGBgnDHNETuyMnDEnY4X6a5QGaBwLkZaM7P\nLMol\r\n=f7ZO\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"447f65bacae89454b88e342390f38e4bd44aab37","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.50","@balena/jellyfish-logger":"^3.0.22","@balena/jellyfish-metrics":"^1.0.281","@balena/jellyfish-environment":"^4.2.6"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.0","depcheck":"^1.4.1","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.168","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.11","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.2.10_1624350026362_0.7693357127334843","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.2.11-renovate-major-external-major-91f9b22a08f553acc291b4ffe613a348b61fd147":{"name":"@balena/jellyfish-core","version":"3.2.11-renovate-major-external-major-91f9b22a08f553acc291b4ffe613a348b61fd147","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.2.11-renovate-major-external-major-91f9b22a08f553acc291b4ffe613a348b61fd147","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"582c9473f13127f511f65f8ae807fb5264271313","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.2.11-renovate-major-external-major-91f9b22a08f553acc291b4ffe613a348b61fd147.tgz","fileCount":190,"integrity":"sha512-DV7qKBrDB5ETf4bI1gka+JYY+t5VVIMHWNUEPJN5TVzhltVcolrcrHAI8u5Z1wNBCJ3wC5pu5RHPFVC//68lww==","signatures":[{"sig":"MEUCIBp8m3mCHR2vrmul6PEuQnb13t/wxLl0ZXjrJj/Ob7JdAiEA23OKTLtpWSFF12nclZaclp2uqL+c2GcI4dgJ2ubzzmg=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":720467,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg0Z6cCRA9TVsSAnZWagAARPkQAJRNhQbz+OfYz2rZnf4K\nA96AEGUlqV+QY8/N3rRj8UgBSMrfEzeGMcooAAn3Ed7QVsKTQUO8jw20/zIV\nyJ532QSrFq/W39aE4lJgPI5tNm+O2bR+TC8ZT/yw7FRSiTm9Ei8VeLd4ExVx\nGh/TyqjRbGPW/hj84y5nnRhekoGOkU5Y/AbVm2zCGY+zJ2qiq2LCvov8iRG0\nfqjFyNqimjqFtNRqpr5CgJEm1BdKSArI+wkZYobuhHMTqTzPPLONkplW96KB\nexeuq15yb8F2/Yvi/tc/glfsIhj4lcIsfiCBrQdesBmLN9GXbC2+sE01FgtJ\nYolKziSOFKyIxtZV6GC32M/4ddYYRSEVXHXRFdI6GwImiutVUnqTL883xYqm\nSnViqzK21BZzMRsukvArZRLXSxUShWL8/TK/7eFnM/lPuxFshjFCCNAxaz0H\n3vi1aTzmEBnNco44FCx3mjM4bjnRDghzPTClOPdoPAxxurTsZnQ5S/rvJxei\nJ8xBUoeucEY5mCYZsMENm74jYiRluThj3mVaEIHHvYy5Seys/SQAYfinNZ7A\nbMrzAHLggnpXSFXmeUPVa+3ReDWiYIBYuIKejqGeSEeGxgl3SnNVkz0H6lxY\nXoBoshZ5GhTYBmJIL2n06VDiT0z7GmJ7mwzpFoNTNn0AZae39RdGw0VP7FOk\n7Zts\r\n=rzGu\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"91f9b22a08f553acc291b4ffe613a348b61fd147","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.50","@balena/jellyfish-logger":"^3.0.22","@balena/jellyfish-metrics":"^1.0.281","@balena/jellyfish-environment":"^4.2.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.4","deplint":"^1.1.3","ts-jest":"^27.0.3","typedoc":"^0.21.0","depcheck":"^1.4.1","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.168","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.6.11","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.2.11-renovate-major-external-major-91f9b22a08f553acc291b4ffe613a348b61fd147_1624350364133_0.9963789419754969","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.2.11-renovate-balena-ci-task-runner-0-x-e063f563bdca87ca61ca832691110d54bc319e4c":{"name":"@balena/jellyfish-core","version":"3.2.11-renovate-balena-ci-task-runner-0-x-e063f563bdca87ca61ca832691110d54bc319e4c","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.2.11-renovate-balena-ci-task-runner-0-x-e063f563bdca87ca61ca832691110d54bc319e4c","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"786b47c69a86a3079ba32322fb3de1485bf3ae2c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.2.11-renovate-balena-ci-task-runner-0-x-e063f563bdca87ca61ca832691110d54bc319e4c.tgz","fileCount":190,"integrity":"sha512-XfM0Zqbi7bAixcn8ZqwZ799w9JJg/pRcI0XuhgPOc2BW2GvaiITa6U6mZZheCZ2pGpu06yszVa7bWt/OrlEtGQ==","signatures":[{"sig":"MEUCIFeptwsExFWJr/qvsdbGIIlR2osNqJjwMzVX6a1YXib5AiEAze2zM538+N1PyZjXfU0qcvpr/1US0fF/r2r8YO2Pf8c=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":720499,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg0Z/XCRA9TVsSAnZWagAAcHIP/jisk0MkU6UXGeFaFISU\nC7pyMf722y6HrcscOpQGhxZkt0vgj1Dlo8CryAja0TZKDzBL70079Mufbku1\nQ4EnepKHr9K/uSEMC67S8fc9McEmTtt2T08ZTH7ZKBZtb2jXqZE00SvK/6S6\ngOKIbV67piM/5YVR3qsfJibYk6XE9HouIpLEbCsatBggHrDOR7vyMdratM9F\n0G5Rn7sJdCYtcY80hu9kQ+1OsY1MsdMq4hXeMmy4XwHTOV4xa/zYWdqcT35n\nzU2iGh8QcKGz5R5RqZ3lRIe7vSsTIAdLiENN8PiL5SuwuDWZY1d35ecGGXpz\ndGDYHXpJLh2bAJ5e3qow1MyW5XooJMKEaAoMHhYD+hVRDdgBgU177KPJXHDy\n8vta0sEGnWB8bKj2xf0iQsVVMCC3oNSD+Q/QrlDSWW+W3Mphef6o+U5NioLd\nN3woOu2CAPV+kdrfQgQUyrxoVJqj4olruCL6IMvcCkcWVOd1sSXRJPRChaLe\nyN9bsMG8sLvK0r7qM3HjeLxYGvmobNSnM7OGPy7+QlkP4ZqyQU4KS4mdkR5v\nw/Yaykh9LwSj4NEuUchmy/RZOHtL59yn6qCkEFQfJRg1w/MXMxUv1HangBW9\nXEu60Nk6pF6FLTAua7Z7BkiIPUT+RnTJqXdJwOv7X3ebnREWnbvjfqNJEiJu\ntq1D\r\n=JXPM\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"e063f563bdca87ca61ca832691110d54bc319e4c","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.50","@balena/jellyfish-logger":"^3.0.22","@balena/jellyfish-metrics":"^1.0.281","@balena/jellyfish-environment":"^4.2.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.0","depcheck":"^1.4.1","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.11","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.2.11-renovate-balena-ci-task-runner-0-x-e063f563bdca87ca61ca832691110d54bc319e4c_1624350678673_0.3338833228555671","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.0-add-loop-field-ts-2b06124c58bbe94078f43bf5dc2bc8636c17bc9a":{"name":"@balena/jellyfish-core","version":"4.0.0-add-loop-field-ts-2b06124c58bbe94078f43bf5dc2bc8636c17bc9a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.0-add-loop-field-ts-2b06124c58bbe94078f43bf5dc2bc8636c17bc9a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"bc9c262db32418dc738afb950d9dcb147c9e1947","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.0-add-loop-field-ts-2b06124c58bbe94078f43bf5dc2bc8636c17bc9a.tgz","fileCount":193,"integrity":"sha512-8xX5sRJxJnflUnzETSfmg/2rnVoD0KcaF/oNptVj9FuiOZde/DWHJ/T4bmno+5YDae938CmxAV/ALbDDePG65Q==","signatures":[{"sig":"MEQCIC+pUjZuYS9/0sPRLx0AS/DU4fX4rTo1qrTeQ0u7mUFWAiA17Sj4yvzSCGFYW0GUwqX9iqn9QydVv/8mLKwvCPSoag==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":725592,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg0bilCRA9TVsSAnZWagAA0AkP/jc3dpMHvAMTJJ42Ddit\nsGNLHXW/jNmRpNBFLo3mKKf8DF32KVoxg9r4cwWAciKkv0cBRZoGGs0Sxjuh\nenB7AmvZ1UwA8nXaphvTKsO2y99wI0/5KhFh1V0PVjaXRPJWsQ6KW04nInYk\nqHEpZHCh51S8EXA7Qne0mnyyb2YAkifd4uHc1shH4LLXiqGpnvIsyVx2jQgb\n40z2v49Iskd0L8kJHE8fT0s2kR6ckCX4nyqg2m5aziWALfnY50Y+dHaNxhgH\nOUMN/hb+/XTi37koYVsLZfMaIFTTgpcI2S1DIqeyGoDJK/mqXmmLZMdT3umy\nx91W8ER5lIB53PaEFJsMMNmnxWTV+kfPum4PiLQq+Q5bHdVzfgdxiJdBBzj5\nL5VrpNW3+GYUiDgLQXffQJxTNXCeKHHoKFMRJvxL3p4T/as8Oe5lcVDRcTsx\n55+sHlvAxO6MwoHFAWedOzFTX8TkCobDdjq0s6h6Mhn5telvVXwMYxA/QK+6\nSHTk/V0bfX9oUCl5UTne0d+2XxRiLtE5IfhqWI8RSHbAHmMGzCxfjT2oT5vn\nAEkuZcDyzVAxuW1Sk2hVYLriUn40PyV9fx8gfTZsQaDWNM4DGpWv/E3ggPuY\nzVhyq8OsAQbK3TKG1MBHqsuvrbY86CNiyQqkVnGaKfY2coyWs1wYG1+ApqPM\nYRnu\r\n=tgHh\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"2b06124c58bbe94078f43bf5dc2bc8636c17bc9a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.50","@balena/jellyfish-logger":"^3.0.22","@balena/jellyfish-metrics":"^1.0.281","@balena/jellyfish-environment":"^4.2.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.0","depcheck":"^1.4.1","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.168","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.11","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.0-add-loop-field-ts-2b06124c58bbe94078f43bf5dc2bc8636c17bc9a_1624357028741_0.5240464424454143","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.0-add-loop-field-ts-9045dea5ba47a375828bdbd4f48a8116029cd7f6":{"name":"@balena/jellyfish-core","version":"4.0.0-add-loop-field-ts-9045dea5ba47a375828bdbd4f48a8116029cd7f6","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.0-add-loop-field-ts-9045dea5ba47a375828bdbd4f48a8116029cd7f6","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"83d789475c2aa474b881f11322e72491f71eed4b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.0-add-loop-field-ts-9045dea5ba47a375828bdbd4f48a8116029cd7f6.tgz","fileCount":193,"integrity":"sha512-jpJdGoCsfxt4xSfEpvKH4Adm/R/SZoSPUOmnjI4vggWf6JWQbfjpRGtvxGQk8ox+eG/ngUTllavXfZtCXfUpRA==","signatures":[{"sig":"MEYCIQDCFlSs36bQUXjoGznLZ3SxGDdb9GfQd69ii91GRwzaqgIhAKwHq6RGN4U4K4hCt3aSdsmcMhTVH1vHez4ophcnUpkI","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":725600,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg0b3VCRA9TVsSAnZWagAApk8P/iHe5FkATdWYpF835xMp\nWRRvYXPDIrgDMv57Qg6X6Q8vYajVo/fNV2ZQoRaeYJuOTAdicGkB+mE6fNAp\n0NrcfNzR10RoKtNQN9X6yCyUb8ZyREwgUipN083wQHQBaNle27e3HL04wYq4\nYO5gMIR06/T2J50+IAstsZQgYtplWn167ws57hSLn39ccTxI6q4L5umcMPy0\nVbNSPYKbqMq5eYGKVaUiBrQSLnnkmYqx/JCG8LG0M3SwD8M2imoLPxni8BWR\nk7bA6BrtC3pwwSL9Rix8zRn/Ey2SQLPuF4cU1kKABQNBgwuhQKX4U1z1rXWP\nzvrwhf6c5ncMFVQZ7072vo6AuNBhMF1Ej3svv66/UawpJEwW5avrXJa9asEz\nYW++21WwjZ99b3iD06T910o7RuZSf4taeotFZGD9majNgIaWRYvXGJR0jzo3\nsyyBcd9xIx5yrseLL29PlqspvBf8f6fjdu/3OrlrTjIaFVIH14nScVmIz7ub\nJ3d19ePWPLoOLQRAgr0485SVO/iU5wq8vkmDaQA78pbB0+NfxJ8IQgg7fn8P\njrPJhRh5DJJE01yizjHoa+fd/yRvnnGtA4Ril7teRSDJlWSj3IzvjyOTftBq\nghgTg2nvP2ry0sBXWjYZEe0c4UZsB0PdqPuU0xlkuNlemlFsbCuINLg3PskL\nkQpq\r\n=Xa6N\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"9045dea5ba47a375828bdbd4f48a8116029cd7f6","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.50","@balena/jellyfish-logger":"^3.0.22","@balena/jellyfish-metrics":"^1.0.281","@balena/jellyfish-environment":"^4.2.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.0","depcheck":"^1.4.1","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.168","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.6.11","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.0-add-loop-field-ts-9045dea5ba47a375828bdbd4f48a8116029cd7f6_1624358357235_0.04113406787450735","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.2.11-renovate-balena-jellyfish-types-0-x-7fcc894cd436d58a452f7c763ac69be32917ad95":{"name":"@balena/jellyfish-core","version":"3.2.11-renovate-balena-jellyfish-types-0-x-7fcc894cd436d58a452f7c763ac69be32917ad95","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.2.11-renovate-balena-jellyfish-types-0-x-7fcc894cd436d58a452f7c763ac69be32917ad95","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"27b29af6e144948fa41a011aed2d6fd8f8fd1d25","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.2.11-renovate-balena-jellyfish-types-0-x-7fcc894cd436d58a452f7c763ac69be32917ad95.tgz","fileCount":190,"integrity":"sha512-jNW/uXFQTH37tQGNOfXrcfbMKGuk/xKBDjnCcojJZspLepUFgV6Srb++XaRY3bhedy1Gb0xCF/kb3zBy0rvKKg==","signatures":[{"sig":"MEYCIQD1A/40UkCZvGTpmAQ73nXvU4Y6dqeN3s5hLRPS/4KnXwIhAJm7K2lQf010ZBuXKAuYMrOmUF7SvVE9gSw6D3TNqdqf","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":720502,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg0cLwCRA9TVsSAnZWagAAnfgP/30kMw6Pm1t18GQkWA1I\n7b6fY04my2Q6ijhu6Esib+RYlCU516Q5k3fNf5+7M7Jz1k0V80WlxuswcgC4\nw8+8wOBa9tcvGLWMvEOWrYa8/PuwNzcmh+caow6WHismLmxrFN9rRllC279h\n29w/+4P4dhp4ZMS0t4y9a87rW56sA4qRj0TcuWWpECn+RQPLJqp3SNuKtsJn\nJfybx4EcY5YXtpKaVNGYSsX2ZxnO71vZWB0ANkBAUOq+ljGwh/TPrgqlH98R\nxJesuB1A6YszF6/Urf1SEEq2a501YBCxobPQD5Tw2iS04Lmudnrmkwo58FJm\ncPMmxOA6QtHa7swXaZ38mgbqNzGqBwvvvmDStZswrEp7kDO67XzfYF9jCKGf\ndjhM9K8MLePaCtAX4wN6vQ0HmT1RgjTImYeGxETdzIi6H/9MckHElzweEBlJ\np74BJsnt1zXqqiEKfEuWLMPdjtE9lTjE5kGGaX1KIpueUkZL+BU+tQB9YP5l\n75HE4yi3lO4uMRBGuTHJ40HzWeWqKETWzfZdgAv/4ctjnSQxJh6kKidXTGQw\nlTfZklyL5vSVBJpWCsv5b/DdPMjNsSVWB82c5ByQ0YCWzzlUGDMa8XeNxmbw\nqqKwpGtdRR9Aic6MmOdLpZj+CtC2iel5/oPf28DqaPALwdnX2fz7ShxnTI1V\ny8AI\r\n=9drB\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"7fcc894cd436d58a452f7c763ac69be32917ad95","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.50","@balena/jellyfish-logger":"^3.0.22","@balena/jellyfish-metrics":"^1.0.281","@balena/jellyfish-environment":"^4.2.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.0","depcheck":"^1.4.1","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.168","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.7.0","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.2.11-renovate-balena-jellyfish-types-0-x-7fcc894cd436d58a452f7c763ac69be32917ad95_1624359664112_0.46647148193063037","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.2.11-renovate-major-external-major-ef92202a4c71ba589ff18014b96c37495662b115":{"name":"@balena/jellyfish-core","version":"3.2.11-renovate-major-external-major-ef92202a4c71ba589ff18014b96c37495662b115","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.2.11-renovate-major-external-major-ef92202a4c71ba589ff18014b96c37495662b115","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"587312a95b26bced8b4ac9cda0e3df7cdae2404b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.2.11-renovate-major-external-major-ef92202a4c71ba589ff18014b96c37495662b115.tgz","fileCount":190,"integrity":"sha512-LoFmxs2m5fl6IfO+MeGZdnoJZ0nzJcY+yZESJ5KrXBwdzChXxz1UOdjw5zdx9yYyhLUUNjRWXmElkUbjy/PrLA==","signatures":[{"sig":"MEQCIF8sfWUtfKlpGBj6Jwq7AQB48Y0tg+MoWHfrd+SDuz54AiAeaSYKJBafj6BZl+3UBW3a6RSVsLURD3wVLVBP8eaIQA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":720467,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg0eHnCRA9TVsSAnZWagAATzMP/jkIaYJi/KQ+/zkzFO9H\nxh7tMvQ29FEUmD3pLNlZ5PkaT891j5u3dUOeMnaLygPBe2KlyfzL4/0Y8OYX\nAVuyEPPkgyKIquJEjrfE3i7rGkWifvDzs/TMgOjkHgpwVdAK0AVPWPbAQh9J\nOte9a+I1SyehdfM/GbhfSzp74e5BGeNYAK9XWRFWNDXh5u8USkWlJFH/AKzX\npwvweHzRf+sUs4bq4qvq1TGYnAALaoawWu90NxBd4Do1s3G3ZQZ9y+Mnd014\nq5q93YYAHgy0suwC0KIr8Y8No5R5gr/C9tPc4EiAVy6Wf56IVBMIUb4OGo3m\nVSULYphdKP25CJG84rOidJKR3xOCqeCgDScnTIB4jwjxXPyVMzu6GdQk0rga\ngFiPFdko0Vr797X8VDqg2sRTFuC03HT8wxsTKgg1qqBATkgjPx5LLv7RYHUP\no0+UFBVcRvrKbsZYTMWsWgKhZPvWSFVk7ZDCwIrqzoDX8YDeJSgksiPAcw5W\nQu9TwyNtQw4AmZSbTUMIEmP+XpPMLIp0o4Ogjcfn3+NRsgdSsaXk1dxbtRp+\nofExBA2GjE0Wsfi+7SjcTkZpEtZYJzn9W6tdFm/IZpYlItV+x/1IgSWII2Gp\nJWG+SDZnbD2XowTAxev78l3KDHszBARoLRufpb+EmSc4xos9c7/by5g2uWB8\nZk9d\r\n=n3v9\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"ef92202a4c71ba589ff18014b96c37495662b115","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.50","@balena/jellyfish-logger":"^3.0.22","@balena/jellyfish-metrics":"^1.0.281","@balena/jellyfish-environment":"^4.2.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.5","deplint":"^1.1.3","ts-jest":"^27.0.3","typedoc":"^0.21.0","depcheck":"^1.4.1","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.168","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.6.11","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.2.11-renovate-major-external-major-ef92202a4c71ba589ff18014b96c37495662b115_1624367589877_0.19420032414400423","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.2.11":{"name":"@balena/jellyfish-core","version":"3.2.11","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.2.11","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3a1aaebece10390bee9d58db5f2c0642ac8d21d8","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.2.11.tgz","fileCount":190,"integrity":"sha512-aiT1Ym/BY0E1zoEfcTxFw+TqhZi+mS6h2FjRnsXhTsz/uSkVa+5PxLvQvClePB0A3O8xziZ5j4OeJsBDvF//jg==","signatures":[{"sig":"MEUCIBhlfIYJcgmlpgBKaJEyiJUDkBIGl9LDvQY3c02aP2MfAiEA1Bo2fXS77ST6jYJ1WkTSAIsWA7ybWw1PL2c1dC3t51c=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":720425,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg0msiCRA9TVsSAnZWagAAUnkP/2YpIJIGEn4Jce4oq6uz\nGZVqBWR423mAqLlxXYVNggdi4kpD+oQP7lsclgF7Ef+AyxpARVRwe5cVvBZN\nmTksUElLZdnLmXz0bshsBHPqY0bcQjSg+J7DyXO2ml5Y8FETKm7ShGc08Gr8\nboesHSAD9JLLWY8/ypC5CSDzenkoa0/o3a8uJQsEqmDdMfWzLmpwENhDpByM\nBJrGQ63YC/sNsms+VZ3Z1HSH5t8qzUPxHn/j/rx1+pUu7v/wzfYYNbvb3et7\nFNeCJUAF7dXiU6RVB2teE0EfqH+2F7cP320YxhPwAsASPErHAWb1xGbQid7e\n2Tkf1BsirgN/M7YnrS69spW4Qde6Mzf3YyNCVCKSzAvduyJH1FAfZMsrr0+b\nq5aQ03ZKnuRZUx0ZV1fUMj1BKSLmDssSUjv+a27FNs2ylqQjZbav9kyGmzys\n48TfxF8zcolKmCzzM3QJLhhH/qYlIE1DbUDeWtyjKhlNFTn/ETwxLb17+fad\nldgWE+VdrnVgII1t/nQrklycEhKnye2DmIjFruJoOff1EW1Ms+uD/rYFCtL3\nbKr06Mya5EumjcDd/ngVKGWSqjZiB9047IlCkfJNMcNrFh17U3P81AqG3FnX\ngYq+ncVnrQSVHiWhFh38qRnLXZQCUbvAuW11v4OoDfsPZgyHvPCU2uhJPkbw\npCre\r\n=wwC1\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"ed295925da066ad807ebd5f25f6974392af6531c","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.50","@balena/jellyfish-logger":"^3.0.22","@balena/jellyfish-metrics":"^1.0.281","@balena/jellyfish-environment":"^4.2.6"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.0","depcheck":"^1.4.1","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.168","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.7.0","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.2.11_1624402722158_0.6162130473044583","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.2.12-renovate-balena-ci-task-runner-0-x-e75586ee77d22e62810560011a59dc18b492aa4c":{"name":"@balena/jellyfish-core","version":"3.2.12-renovate-balena-ci-task-runner-0-x-e75586ee77d22e62810560011a59dc18b492aa4c","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.2.12-renovate-balena-ci-task-runner-0-x-e75586ee77d22e62810560011a59dc18b492aa4c","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"64cc8312a61eb29492b57685153342b01ea9bbc1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.2.12-renovate-balena-ci-task-runner-0-x-e75586ee77d22e62810560011a59dc18b492aa4c.tgz","fileCount":190,"integrity":"sha512-bNHUDcn4U8Dpnx3MlDBLPRvF5q2MOhk0iuJ+8Sc5d6qCHVzB3LIfEkuEehKFzbtCdJUphn02idTvdzOhsBjlOA==","signatures":[{"sig":"MEYCIQD5tKIQZLIxVovs5qUZj5ocel4xUgx07dMuPFvOthLefwIhAI6saS8QDm7m1NupFrm8hdRQMAQVmVNGnAwkmI8ASwQ/","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":720599,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg0mvuCRA9TVsSAnZWagAAnfYP/AopXRUCrP6LOgXJeg+0\nONnjcCaZqort4xFmzMefvf1gyz4Z+xev3oY6Z1hPWQcxc5IgCANUtuvgmG19\nkekI1DekVMhj49vtpakZ7ppOcrjs1/9/l7Z9RVztBY2FG3omlCjjsbzD92Ej\nXbs9pFthpC9OJA07BOgSEGvX4Z/qJax1v8fNWFK1vg4brdVI7srI+v8CtDu7\nfucROHNcaVo1zRsg+RPeNxk0XxpdZ2e3jeA3Glp2qg2tKwkRnDDtAOyNBc/X\nIR//M2ejYF12eLssl/FfYhcRn6upzQtOuqSUm4Uq93VLMnUyhDs7NWTNODN1\nuO3FqRZNuLddIHlqIlT21noZ+oqkVVThlByJp0eqb3qOVvhjB11uKnQPRfSr\neuYCkutXYRVWxcns8CURKiP5mwifbTTmlg5ndSlG1lbeuMfikbdP6M0UQPSn\nV5HBkqFTW7ibphFFeOUn57sw8b3UP9V+QMr4jarDjpJtnF4TtSM/JADCilxo\n5pGkXraQdnwJFaqaBCh0OS6jPt1qzurmS6+1C67x2D2OPJ/FbSHgxrGFbk0L\nFcEDDQmtnCWwaHjY8olUo43VR93i5IeMozfj+YpGJe67RY7oRqU72Qt3qNMc\ny1LJQpDaXk8zSh3m0IVIBH3JC1z7yC8oGwKBeCGdcL3Kqb2rMhKaNX5g+RPd\nHWGf\r\n=FkpN\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"e75586ee77d22e62810560011a59dc18b492aa4c","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.50","@balena/jellyfish-logger":"^3.0.22","@balena/jellyfish-metrics":"^1.0.281","@balena/jellyfish-environment":"^4.2.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.0","depcheck":"^1.4.1","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.7.0","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.2.12-renovate-balena-ci-task-runner-0-x-e75586ee77d22e62810560011a59dc18b492aa4c_1624402925606_0.3385602701678432","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.2.12-renovate-major-external-major-56ab01fa0aea2517d4995cfaba8c23f7003ab767":{"name":"@balena/jellyfish-core","version":"3.2.12-renovate-major-external-major-56ab01fa0aea2517d4995cfaba8c23f7003ab767","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.2.12-renovate-major-external-major-56ab01fa0aea2517d4995cfaba8c23f7003ab767","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"28543f962018e821bc5cb04959a824286f007cd0","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.2.12-renovate-major-external-major-56ab01fa0aea2517d4995cfaba8c23f7003ab767.tgz","fileCount":190,"integrity":"sha512-xxYilzT7nEpiwlFdZQWJAmy8d0LOOfO374iliBB2mn2yhOEbWhda4wWxdCgb8p7Tz380awRpPFhL64SSI/6lbA==","signatures":[{"sig":"MEQCICXGm1vXGmE5uTowZ8pwugwswFfzm5SG5moybtqTbt6jAiBvEf4j2QyTitvpRnEy5lqtT2N8Wvxt9fAr3oJOveItsA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":720567,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg0mxfCRA9TVsSAnZWagAAMegP/1hsaBt7OvosP1KBOvQy\nPNwLAsMRHYzDjuOKOSlubIIawFwm546hQJ5gdyV9H7CIKTeY9W1NwFuc636/\neePmkQYqwBWvEli2OSUdjor6AZB/oNsLlgco92KG277pBp944annSARtNbDI\nOGCw/6/2S4qLUIWIjWCMQ/FUIhdlwA98PXB0BOcW/I30mcERAIpDQJGYhcao\nH7WV/+YueLyE1hOo+pQS+bhsOlQl9kcVMhRHZ298/37KVP07uYcnJFiY00EW\nrMJ+dZiqtROwFqfn/VJBxn6ed/OCCA5ASsnoT/vUewXdc8hNV5rp6KByA4W2\nZHuWguFbrS3rOKlBx76XmL0oZG3BqQhQ+QPB3mJdm+xeXJw3JF8+XlkhM0eI\n2GD3hzCjDADPOygMFCDELUxpljaxe0YK1v5iucT0UnY/Rw1/8AvDMi4Y49mo\ngKx84AQm/PS9ho/05lSe2pvRGVs3ONjuJUvkHaOEgb4rAEjkEoBAG/otIDrt\nTpWD1orPyk7NbXxC6KtNhS5yUY2/qRCofAfk7icWzRVv4RgpJw2PYuuvm7l/\ntVI/0E5NU0uGMPWQQhAxKfIM7g0xMTUgEmf+6e83YkVlMOqaDQmJRNVjhkku\nj8gy7ITHM1CiuqNRM/WuRMrO6kZDcrUtI3KLb/FyK17XbBKvn3ejKneiiZIT\nYLk6\r\n=eYP2\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"56ab01fa0aea2517d4995cfaba8c23f7003ab767","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.50","@balena/jellyfish-logger":"^3.0.22","@balena/jellyfish-metrics":"^1.0.281","@balena/jellyfish-environment":"^4.2.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.5","deplint":"^1.1.3","ts-jest":"^27.0.3","typedoc":"^0.21.0","depcheck":"^1.4.1","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.2.168","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.7.0","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.2.12-renovate-major-external-major-56ab01fa0aea2517d4995cfaba8c23f7003ab767_1624403039100_0.7100871498798489","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.2.12":{"name":"@balena/jellyfish-core","version":"3.2.12","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.2.12","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a1eba5d85afd38ffcb1908108e68ace95a15a942","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.2.12.tgz","fileCount":190,"integrity":"sha512-giaLZkfIC4OuVMlpNkYSUvRtO7BHrWMhdq5i0uP5msEUqHRjRxMzOYtMEW5rG3KkihU6c3yZ0DYicAGqBTR+sQ==","signatures":[{"sig":"MEYCIQC/Wa/XmYsEIShQVW5fuctzjLnUPcZbK6TwViqaws7ERAIhAK45ClN9UpzOs4Uk3iMy8XsRmFzPAxirxDiUONiN7Ako","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":720523,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg0m4ECRA9TVsSAnZWagAAxVEP/1b0Hjh8W9cGv57+WM95\niY7/TIM4r6fBzgRZksBruqb1nwk4UgDYozWXmTPTxomz3M1wIBgFROtPm54L\n17G7CBZk2lvgevjyPKrq/Ta+LEJqvKs3nTke0cKTWoQhi2JesIiHlTgD9vrY\nP1c3l5QLf85sOUruq2WI+PEuIEKyNUiILDBJKL8/OzWV8g/WMx+2tYtMJZAV\nah2W7n9o/AO0UwYljCEbQ0Q8JXHybwf6prDhKQ+1Lo8dqlU0qsyrhsmEm861\nQ7ILAYroPTV9aMnE9lRYMn1PLXZHysWv1ftk3bWlJlAuU5QzUeE7HDJ/Uq5c\n6YZ5Cm1oeGKyX9TkORoxBFsZeBPr30E8WqZr6OeglW8/3zQAuQxBP+dmYoto\nPrDacUt45KKHtKblqJe7nLEEZUehjYS/ipzpRdKtKY+dEGmhkzyiBsC8yQL1\nbNO0n8eDV53ZnbL4F/zeXBLmBbN1VQnydTkhgdUrRzLEPMdc+e3HyepyyH8B\nNS1SEkudNEJpwE3gQEpb9CMEOiY7wEapnsMILtJr+PauNfKtevBQRfft/pvr\n5mBLVm++fo01iON06ILNzgsSlYTHcn9WbjdXotqMe7yUIJoRa4ZOYw+sxgMo\n5bAdR68ODqfL1V0Pw/QwqgQdGbNj2+GwH7KO+1e/dBCudZuHIMhWRLUsEzpB\n+IKy\r\n=7uUP\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"93e915602ad375dcb5213c557f3e95d8ac84cc55","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.50","@balena/jellyfish-logger":"^3.0.22","@balena/jellyfish-metrics":"^1.0.281","@balena/jellyfish-environment":"^4.2.6"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.0","depcheck":"^1.4.1","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.7.0","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.2.12_1624403460357_0.42991890532797306","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.2.13-renovate-major-external-major-d175994dd1998147bc6a2c78127c3e1f4440f55c":{"name":"@balena/jellyfish-core","version":"3.2.13-renovate-major-external-major-d175994dd1998147bc6a2c78127c3e1f4440f55c","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.2.13-renovate-major-external-major-d175994dd1998147bc6a2c78127c3e1f4440f55c","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0209fb36ac8182d3a28a384b80bc5a3d1e3473be","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.2.13-renovate-major-external-major-d175994dd1998147bc6a2c78127c3e1f4440f55c.tgz","fileCount":190,"integrity":"sha512-yfWXoqgCWzzRCBXoMC0Ixn1538eXqTxSlr0cRhquUB41AoyTuhweeBMpDbRIHWtwB8I4SaKu49TlJHN/fqRrdg==","signatures":[{"sig":"MEQCIHUzkOIqGxol35w3TLxwkKoTOFR1m7ghPj8esOnAiu70AiBhBFzw1JmqYr/orWJQsq3KO9sbHKRVeQRGPeQmctycng==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":720665,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg0m81CRA9TVsSAnZWagAAdLgP/1+wgGZMFxWoGtLEp0ro\nIxdgfbd7WtKcF4KFHw6oR2OcCCB1Kk75ASDSp/QDBi79tr7Y0dZUqJFNESzG\nNDq+2cAW7rX3CXdNC9Y9O+d+5qfeogwdlyIsIJiIflVU1WJNOultLNABZbl8\nHukfbgzhuLIBmOu8gfgoCz+oO2A9YKzHWUsMfctH4UhuE7xtHWITrIiuZd7i\nMf1ODT9si67PQkZmxmR9GQFDEZeQ+KITiy5+OjNey4xmyOdBHC9j9pt9MTOb\nPTnvA+7jfKF26R0FRnTmtHhRVW4U5E5snzRlyBVUsu/3IUFbk9fktDzT6N9o\nZd6P9lQZjuNqsaFtrDdpYVHR8GbNh7Zaeu9M6H7VpE7QBDWpboHRa0O4LRNZ\n62tzqPLIZVU/zondnK4sRtK59zRq7FR8rEiEv6a58dZteeG7prBJhFYlcxG6\nu7e9+D4TqdmjrxWzKd+svtw0qGSVhhXUnRBw5eZJ/DN03wp53yzJsFaXzRHc\npdNGZJR10ZYGWwGbghOdCYxvmMMmYv2sM8L6iAwmE4cZxQx5ANg0mc7SDcqg\nXlh4MYSa0n504yKxNj5v47Plz2zMmipshL4WsXwJ96fVrLYi6+CneaPuwpZK\n+oOdpUe40T8gj+kty5MKk6V7EVYa6DWPNF1ydDfRiUntddH1t8lBl33e/vWD\nHurz\r\n=c2b5\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"d175994dd1998147bc6a2c78127c3e1f4440f55c","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.50","@balena/jellyfish-logger":"^3.0.22","@balena/jellyfish-metrics":"^1.0.281","@balena/jellyfish-environment":"^4.2.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.5","deplint":"^1.1.3","ts-jest":"^27.0.3","typedoc":"^0.21.0","depcheck":"^1.4.1","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.1","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.7.0","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.2.13-renovate-major-external-major-d175994dd1998147bc6a2c78127c3e1f4440f55c_1624403765086_0.03545805540974456","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.2.13-renovate-internal-patch-ae811eeb388610903d430a48c1161b91e0404bd0":{"name":"@balena/jellyfish-core","version":"3.2.13-renovate-internal-patch-ae811eeb388610903d430a48c1161b91e0404bd0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.2.13-renovate-internal-patch-ae811eeb388610903d430a48c1161b91e0404bd0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9bbc1b009701473d4f706bb2adf8b3a46316a026","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.2.13-renovate-internal-patch-ae811eeb388610903d430a48c1161b91e0404bd0.tgz","fileCount":190,"integrity":"sha512-SpgHiRJvb5qlhpYtnK++QO5fozXPNKIoYgJyk43otzTB+5E0YQRlKydtCLdzCBA2jrDbhYgxRBZlhy5EpJsXsw==","signatures":[{"sig":"MEYCIQDkO0iy9ExEoEGTLpFc50usmD9hHJwquOIV47pgFWU4SgIhAIB0BOho5TS49YOr96MFnZ6CFeJaIsOB6m5m8F0uCkPb","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":720659,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg0oj4CRA9TVsSAnZWagAAEVAP/0ThGTowzgbmarjQk7K4\nePlWzKrmetdE7jOncN/W5lTPBv1DJnJsIfCj/C3o0E0KY+fKXLPgSVSZrBkh\nxHuKMMW2EdfsYht4cQ0+f93H//7ATtwCseUpUi/ha3h3jnFhVHmPqSryMVQg\nuCiyqjjnjXtP7bH1GZ4XNEPI3PHEWBcmBoU/Yw9HolsQNBSJmAMyQt0+1Ahr\ne41rY6257NeZdksw76tTnxrHEAH9SXmyMVzCvYwa59PNmKCUzdJUtBllb+OW\nwaNnc2vTCV9EK88dJcPN0UQ8LZLypivhWab5zq81OeJhNqqBaGqPpZH2+2R0\nPFZ9J/7WQbwe/1b9/YBwbTnnStsMVr40Kzshyy5PJopLnp1NTLTSbdMtrl/8\ngTKFijX9HmpRYu3KDHTRBOJBzly55pXm0V9RBSMHFq9tMHGBDsU56GZk+0Ks\nYGvmsheSyke29bTgCbP8DJ1X5LGK/oPrACsAEPZ20HXf5WQMJ+ZAVPx+eNcs\nceCUaHmNSJLyGWjplKhuRHRT4b2JO830b0JwXHv+QWGLahvtgYWQu11kWNQd\nlA7Xnr5IaZY5LF1EAazPyCNTQec+7zebv/Mqu5BNUAE+Fb44WwzCgpWaMaSm\nKwh1qFnWYGoWiEr7jw3OUQNoFSEPo5NCDO5ddvBVa1W2Tfzb6FFPs8R3IKBg\nKrD9\r\n=VuhW\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"ae811eeb388610903d430a48c1161b91e0404bd0","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.50","@balena/jellyfish-logger":"^3.0.22","@balena/jellyfish-metrics":"^1.0.283","@balena/jellyfish-environment":"^4.2.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.0","depcheck":"^1.4.1","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.7.0","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.2.13-renovate-internal-patch-ae811eeb388610903d430a48c1161b91e0404bd0_1624410360165_0.09808534073038278","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.2.13":{"name":"@balena/jellyfish-core","version":"3.2.13","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.2.13","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9a54de1273adc7dd83fc1070e9b74a9050e40aa5","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.2.13.tgz","fileCount":190,"integrity":"sha512-R2ulrUPU6etLeLb8/jydQqNu73NAoXu69njOA3wfM3WviuguTYdVxZ/NMNvboreIkQi0+/y3zDD+/po8pU7gQg==","signatures":[{"sig":"MEUCIQDyJz2BRSbDt5nB6jMw48Tk+Ny6rMLtK26OyqSxm9fhDgIgCfyZ4G4SrGF9+7cy4KlekuWZTNXu/MYCQk6fVoxv2pQ=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":720594,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg0opcCRA9TVsSAnZWagAAjYsP+wU5cvLkI67RNtyw4BSx\njj/d5BSl6UuWqUxsslCsTy0FYv04sLlvlmFxChcepOcKYyGAS9+2QdB4wJCf\n2SrgwmXG4zDg6m3HE75M0RnHbFo/P+2GXXAU8TMjcE0LPHL4VBoMuKHx6Jo5\nit18Ouoju5JhcGrIjP5h2kNDimNvJrD0yfgqTUZ17MT1aLsfILb3H1GtNLrU\n/QTubkIE/aOUUUrXVeb1gSoLetsZIrvD44nm//s4H00zH9GeZt6DsY8uocL+\nU+TVjTLgQCF6jYKKotj5xc9Hbh4+F6WlN7H789Yz8Mn8F2DEl1Awh5nv7knf\n5i61m1J0pSxHl6k7txXjZNako2g7NCMwUVDvPvswdC2R69tJcYWCai9SlfMt\ntKC5e252NHoa57jKKhnwi1VPTnixvo6HQAQdZ5/HS8raPNTdCj4io6CYzO5y\nWfPjtkOVvmAQ8i+x5kM7/dGdv2OaxFamfZegYHvHAdDOmvLBzv3ht2f5LJ4X\n9JahD2YPXaIhpkcyqfjKZsk3WwFUX+XAtr5FV7raf/3iD6iXr3jMdKxSXDlg\neef0V2Kk/sQYDcIkz2SePHyj4aND7/wLcLun1UdvMmDz4piDxMa/sbWEYqU7\nKG+6YcjF3Z6MiVWZiK0CvykQDUzNsfo0oA7zzV82V3A5sL17pFjfTFN/7owQ\nCpoQ\r\n=3EWV\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"4c3b3ed5818f4fa2c8d9272a014ac44118017bef","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.50","@balena/jellyfish-logger":"^3.0.22","@balena/jellyfish-metrics":"^1.0.283","@balena/jellyfish-environment":"^4.2.7"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.0","depcheck":"^1.4.1","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.7.0","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.2.13_1624410716475_0.12215973213166853","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.2.14-renovate-major-external-major-09e4c1ff3a32de58d7afa1181ae2b581c079ffce":{"name":"@balena/jellyfish-core","version":"3.2.14-renovate-major-external-major-09e4c1ff3a32de58d7afa1181ae2b581c079ffce","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.2.14-renovate-major-external-major-09e4c1ff3a32de58d7afa1181ae2b581c079ffce","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"14cbf4aff0ff85147896dffa489143a8fe978cd9","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.2.14-renovate-major-external-major-09e4c1ff3a32de58d7afa1181ae2b581c079ffce.tgz","fileCount":190,"integrity":"sha512-RJ3D/7G34wnFE8J20i3IbFo3ct3qp8gGTphqZEQ95D4LzKHc9Ab7Efd9Q6qEdx8+mD8iu6psBHI0071ZumR12g==","signatures":[{"sig":"MEUCIGXtKkv3hJr8uGP5feUNhEbdg2qBw0CkmWLOLrv2h22hAiEA6cZKzjZq729JfEIIcrszZQ21pUEW4zas9xRi/VWaRK8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":720736,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg0otBCRA9TVsSAnZWagAAcOgP/2z9ShLS9Jwd2LDYcgdt\ncXlwOmPF8Wobi+QTUTYJbOb7hlOfdSEH9QbNAEGPA3CCxmTTfo/kahoyVp87\nCWIGIhLWLv97uvg9zwnYqOVno0ABYByp/hZdmhPZpivjzc1qiFZkEwdsd06z\nQFUqXQ6RgWBh0J13nFLfGaDOdyqeDyqwFloqlUXKmO46FBQUjKPg9A+uyTuY\nTxpD8OzwPwShg2PInFqek7lSO/TMRTIwBgRbcH082e/XnM24QqIBGtyyFG+E\ne34IXYQGGvuVjXn+jmbHcyFTgZIKMlWZeZiYiY6v7X2PQHuW1SU8oXIamT7g\nsJT/cpfrK7BJumhV0YSPbwWuV1hrDlE4FjXt5mwkmSTSjJrms2Zg198KR8Fq\nj1ODX5w8dGiZZaYTo4r8IOqTebrQFj4p8VP0mhzeJTweVAsiNxAcxuxcblEY\nqfmy/VroCz5JP8uvjhwyyHyrqhkKA0gl5pbklQYyXo87D2AmHm32KTKMqUc+\nVjvn2/ZQrsCHvrhU7OWixd4Ny0r+pALdNSG1chLCKls5GDLFvmXt/e1BK3fO\nYHh1rrGRhRCenOMpikGClyqDksxKl4dFL11Mv96+ILZyENAW4r8E5zScfOjW\nafZ9lr+5AM4LU3wCsLwPcf+VJhHzL6Pd0qY9/EeoJGO0WLSzMvcCo8y1paTo\nW+1W\r\n=w4Rx\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"09e4c1ff3a32de58d7afa1181ae2b581c079ffce","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.50","@balena/jellyfish-logger":"^3.0.22","@balena/jellyfish-metrics":"^1.0.283","@balena/jellyfish-environment":"^4.2.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.5","deplint":"^1.1.3","ts-jest":"^27.0.3","typedoc":"^0.21.0","depcheck":"^1.4.1","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.1","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.7.0","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.2.14-renovate-major-external-major-09e4c1ff3a32de58d7afa1181ae2b581c079ffce_1624410945093_0.20383106332582623","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.0-add-loop-field-ts-1dd5a52d8f4db664b3af206ddd5106813040d94b":{"name":"@balena/jellyfish-core","version":"4.0.0-add-loop-field-ts-1dd5a52d8f4db664b3af206ddd5106813040d94b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.0-add-loop-field-ts-1dd5a52d8f4db664b3af206ddd5106813040d94b","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"cf57c6746c84b4ac8bb8275c7ed0d71b5b4affcd","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.0-add-loop-field-ts-1dd5a52d8f4db664b3af206ddd5106813040d94b.tgz","fileCount":193,"integrity":"sha512-6QYejpKk4ARA05FkzE4cprn47dwigEe48QAEWLlU6Y/qpbR2j6icvC7GomxCnwZMPxVhIifYakROFzdA7Qz+Cg==","signatures":[{"sig":"MEUCIQDdgtdMgCPf2Sc16Pl8lteRQOM6aKIzPz5C1Qi9jxgrzgIgQDqQOCgUM+p9FND/MbGbEqVB/21qVH+Uw/fJ9YLzvm4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":725518,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg0pRgCRA9TVsSAnZWagAAX7AP/0PmikE5LBUVsq+ewwFX\n+a0w0I+Kf6cV1lDhlZRpsHQVbCSuf7Av2j/DMGQgdMT0fKNFAU80S4j9qhXn\nmKQWOqo1OSdnw6xnzGjnz4BfRbcdDIaPDkk8mK9//chxCGE8OFtWgYeixZS5\nISPffPjRZAdJCSnrl1UjnGRq863lxhPxcffiswRdhaVPc7iqHVCLw1S19OoI\nqRIMi2TYBdGHd3QpnIYc7wHUiTTXiyabj6iPA9i1X1otihjGNnrfM2YsXfhd\n31agvD10slzTWBn8Li14/WCz+Ef0PVmn0kidlGx2LTSneIc/uFr+uLTHY2bm\n6fikpS7IjT40szszaUh8Y+zHoJLMiuHerLrMduJ49/bNOGX58DatvjFdWZr+\nj0kZMxOixeQN19YHCIWZm/rR2pz5oe4LIdizpYgZ7/jXd+HVL7EtY1QTMfXn\n2IzUIUBcW3JcdF6JGzwl4kSyBm7Y/AteTHTx300yjA3E054Q8emVvUBg7vAd\nF33fntYCoVY+UGaFde/Er+YAhyILCpbPyz6s/Cz16DimzZPcGvGj2tqLE90S\n2iiv5OfgU2fWMeOAgJbi0/z61LviM/yoQe9o2fGvJcDTowcOdXVYaDR68x6O\nJW28lNO97b/hZNLzQfceFnJ02Y/EwMVceyNJJytRfkUJq3uludSISR1mwfvZ\nNJGg\r\n=N47g\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"1dd5a52d8f4db664b3af206ddd5106813040d94b","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.50","@balena/jellyfish-logger":"^3.0.22","@balena/jellyfish-metrics":"^1.0.283","@balena/jellyfish-environment":"^4.2.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.0","depcheck":"^1.4.1","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.7.0","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.0-add-loop-field-ts-1dd5a52d8f4db664b3af206ddd5106813040d94b_1624413279895_0.03394035303676901","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.2.14-renovate-internal-patch-77e66462b32f10e49e36fadec29eb1833c8307cb":{"name":"@balena/jellyfish-core","version":"3.2.14-renovate-internal-patch-77e66462b32f10e49e36fadec29eb1833c8307cb","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.2.14-renovate-internal-patch-77e66462b32f10e49e36fadec29eb1833c8307cb","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"989e72f3a761634f072632dab2d8fa75d32630aa","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.2.14-renovate-internal-patch-77e66462b32f10e49e36fadec29eb1833c8307cb.tgz","fileCount":190,"integrity":"sha512-WgpFerRxY2Xb68afZW5A68ekZQZENexTwC7byn7s6LawM4F85uDqlHEhFHbtfAYJ42xfE60mqTNOc4Q7Kzidnw==","signatures":[{"sig":"MEUCIQCU3OPzUADS8wvaLNZy+9DW7TVTz6OEl5jX1V4dqMh7FAIgdZzHMK+zn2K3XXwWfZlwELXVLh+oOGCP1wLxR5M2pR8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":720762,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg0tmECRA9TVsSAnZWagAADIsP/iDjkhV08TgbZv4rt+3r\nasjegWcTcunx3RzcErrE6kGI2F3wLT+vVUA88ss6lUgMUVS+Dx7KA8mIz0fT\nAI7VNXMmAmgde1cKkBJ6EgKjJy8PVHsZjtqwUDEMdpPkAPhxtHN4t3lJh59N\nr7CR9mGPTduaJOKgLqMAx+S4PZ2kXNOPZo4L1YX9RvAF1rLhKXIe9jm25QMb\nB6g3K32HwoqZuqIYAWqQ4qO1IEmO+OIF9whp86+rNqzcjhPJPbhKdJgwqbTM\nEJIdC8TBxc0gRwQuxWQOvvCFahR3G12guLJ7dCbB2iPqEiZGDi/uBoNv/W9f\nfaPyyxqCpSV+ERoMqMWsThaq0ORL80kxMJ1ljcCz72fh2Bo9Ko42toPnTbHT\nPE0DwPJ3TZ6mYXm50+7gvtZbo2jldibKw9WXpjQ1p1enVeYVY7fs6/i3ZBV0\nmlZnSho9TSmc3Y/C0xSnFdT05lE2eVdHpqfTEvjo5APFdwozxAQBxuOQ6UN7\nZ7138n6b+7pvb3CzQpLe2IlsGlN4mVDLJhtPdLai9eVwT9Iq84gDyek+T70W\noMvRJh5pVy66ZQd+xksZOmpmCNu/myxMzrGngXjrf+Ja/aNKRTeffiQh+ace\nzGb532bHB/aX2v5FIWS5TRXUXqhnn6L0ihtyoV6CYEurY+H1IkrQT0tq8ids\nRDxV\r\n=OHX1\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"77e66462b32f10e49e36fadec29eb1833c8307cb","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.50","@balena/jellyfish-logger":"^3.0.23","@balena/jellyfish-metrics":"^1.0.283","@balena/jellyfish-environment":"^4.2.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.0","depcheck":"^1.4.1","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.7.0","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.2.14-renovate-internal-patch-77e66462b32f10e49e36fadec29eb1833c8307cb_1624430979565_0.3123325873681524","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.2.14-renovate-internal-patch-21ec1b88107a6a47d79b1a35318ea2c0dfa4fb60":{"name":"@balena/jellyfish-core","version":"3.2.14-renovate-internal-patch-21ec1b88107a6a47d79b1a35318ea2c0dfa4fb60","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.2.14-renovate-internal-patch-21ec1b88107a6a47d79b1a35318ea2c0dfa4fb60","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"68d245231cde6d5a3ad135a80198c4a087f68e7f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.2.14-renovate-internal-patch-21ec1b88107a6a47d79b1a35318ea2c0dfa4fb60.tgz","fileCount":190,"integrity":"sha512-pQuGz4ReDWyNwG28oCibm9Dr+B90/xaVakGsVK12HdZlWbG+hp8mePtiAnimsXQep1TAnDKvvzGZ2wuT6Orzmw==","signatures":[{"sig":"MEUCIGxDuYYz8EKM/OTSKBFFmYJt5GBO0XRe0EpytJC6LEu1AiEAlkCFoVtVijAEA0Bm0VB1PELUwy1yBTg/v9u5CCl9BM8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":720730,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg0ugnCRA9TVsSAnZWagAAXt8P/RAnqMJuN7bNER06FGI+\nCtW5MqwBURIkXO4DndMbYwyrjm9HhLB9VYiZLuuf1VTCUUKNeclRw/dDHBPj\nxzyj22+bT0XPslEx0BkFFQWVWV42UxV2Ah5K+EkXhqVERCERKwzH/MhkNgSB\nFkN622hkgEG8K9ncsNRhXzuW9pNul1zIF3LKU6hY/ObM27VnoV68Y7SelCbW\n4Jy8U7j0V0Qnif6eGqedJIEPEg2UoBH+rfKPBBzZqGFIzjA6pQVR32BEr4Hk\n/CabpqveGqZpE7l0gc1SiLA0YJrNxZ91KezpFnmZ3QZ2zSVC3Ub2NJ+drxj8\n3Nmcnqa3w2SNPpkuwMEwQdh8qrA0kqey0nOT0cjLqrww2XRo3Y5ZLPm+JbYv\nPhAqRPJSujDazmlwIjHwujLXVBt9cUQFBUfgIMA/Y7Tr1b3/yPmJLLWzPakJ\nuHx9p/2Cyzr68vIgal0FWwZiivtt6n7kCMIBMKGYYRefZ/WSG41KqSB5SEuZ\nm2h1oPlPjub2Sxzh43p9o3MLBlH9BURys6HTKQyOezs/kIQ+NiDGWEJA+wlR\nCP0bQyWFylPIO1K2Plo872vxZ0AT3cglrguwHInfVhbB3NbzjQ7AVsi9H183\nRAO8uc21N6kkMM3DLokPMteAnmDCRw/VlIUZZ4EB6GBukwisk+nvmzqNtnAT\nTBn1\r\n=HssZ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"21ec1b88107a6a47d79b1a35318ea2c0dfa4fb60","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.50","@balena/jellyfish-logger":"^3.0.23","@balena/jellyfish-metrics":"^1.0.284","@balena/jellyfish-environment":"^4.2.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.0","depcheck":"^1.4.1","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.7.0","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.2.14-renovate-internal-patch-21ec1b88107a6a47d79b1a35318ea2c0dfa4fb60_1624434726369_0.6708367949329206","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.2.14":{"name":"@balena/jellyfish-core","version":"3.2.14","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.2.14","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ad03cc984524982af1b0fc2c9e61bc99d115d259","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.2.14.tgz","fileCount":190,"integrity":"sha512-vcSANCZl895QZLIzZpASAgWhnEE4lbieqEmBXKwO6XIzYQEqLj8W2U631E6FOZ8bWLQSIdftgIJ/qThXdjJEsw==","signatures":[{"sig":"MEQCIEQ9K/4yvRpGamkFENhBHBxRiflICf9NTbKnI6jYaRSaAiA04a6y+RuhvAAJDRTG9K6zNvpPKpBa2ln+uEc2+o/vng==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":720665,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg0ulrCRA9TVsSAnZWagAA4zkP/iybPSqGxOCb5AQlpan6\nWPO1Az1d6qkdYwxSOXt90toLECzF+71iu2Mj4aVJ8/yxZRPkb0YCZvtV3Md8\ngcgiTodgaFWRP+PXom/XUy21koAvBafi1p1GuozIQm2sHHBkg4IB542ZxAI9\n9MaV20vv1j4ybXy1JK5CPDOUZCo2Sh7NRG5xPJZ0LprIek35A4fUvl/OxTpR\no1QldKQnERslP6RaitNTsycHiypDsdO9E7xwbZP985Njlnq0tl9gLqplKHmi\nXrf2QJR2az9goLRDWAI5BwY9VasMDgG2KbDDiCnkuwZK5CPBLFWfelp+raVh\nlGbxJae9Asv9SSWvY8dqaUoa5Ebc3aymLjXVcdMJN3K7q0RkGQ4Enpm+o/Wj\nb48Pc5rglt6jXb94ANh2RGkhPlfVerLwAydRIGYJSixLmxOVhJJ1WpavgHFz\ntdsWRyAzCtpTvaVF6PEeyy8K6YFY5sBWnyxn9WtJSVLzeI2LYWh/4m87cm0l\nK4P/YjpIh/jb9XyqYK4Dp9z660nvKKujspOcqHDGPbFq9BeQO7MMjBPu+D3l\nJLf1vdijOfTG3XpyVQXMqwBs7aaSRU5By2eqt7732FtCMXRlJbMV06yaEvnw\nUHWhL2YqptpIrSP9T3lBacmJKzNIQLDjTreS1yj+0G4jZP9pDb2bXa8R5UOB\nlwtg\r\n=B18w\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"6d8aa072d98099725e791dd045df159874623e85","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.50","@balena/jellyfish-logger":"^3.0.23","@balena/jellyfish-metrics":"^1.0.284","@balena/jellyfish-environment":"^4.2.7"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.0","depcheck":"^1.4.1","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.7.0","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.2.14_1624435051207_0.5912369625638454","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.2.15-renovate-major-external-major-4fde2c2c7c782eb392d851ed995f883fc27477e5":{"name":"@balena/jellyfish-core","version":"3.2.15-renovate-major-external-major-4fde2c2c7c782eb392d851ed995f883fc27477e5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.2.15-renovate-major-external-major-4fde2c2c7c782eb392d851ed995f883fc27477e5","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c37e8d5d2b34e0f8d1f6869cbdacab54316c33c2","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.2.15-renovate-major-external-major-4fde2c2c7c782eb392d851ed995f883fc27477e5.tgz","fileCount":190,"integrity":"sha512-ePTA5+lQuWqspN1+reE6WQappYXw8LMDYGUwYs3PSioOfH8Ez98V8PCrcynCRTPJ6T1B6ef5Z9t+dmjRLhywhA==","signatures":[{"sig":"MEUCIBpIus+fbfa7SO45Uv94yscGbE8/0B8XVPJmvSJ8Kv6dAiEAnx30oO3ALFMcL4OIDWWdjXZL7tdaKTjMA4pcdGoyL24=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":720807,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg0uqfCRA9TVsSAnZWagAAdSQP/jilVpyM915h1yk2KI9Y\n5lswZ/u4Aq4QVNTHTUjPQEvnEcuUyzYFr9eNjHROkxYezIcmtMEzo1wW+WH9\naQWbjTumMAohNaZHtkpMEpoXOhC7bvBIQ2ngEcnom4dvI2peKz51qW7E33bW\n65hckFUkQB/f0VDfGLLa5KFGlOv0asNlawaMVepe884bB+2VCNKhZMQBc1U7\n8b0HjM6lp1tAVT6tdqjdtJQWwNgUujWCjHCtYmyeQpjX7/YL0s99XyPfQwvq\nGcOejwmNu7pqGPs22ESCkHju1DYwEiQ8kco3s2I09O7kiKR1QaggrTjizt8c\nEMQLVauQhRKJpZvCaciMGLRrFCxcKu43lDXejUbFiWpiZl4mx+x8cHulMZCn\nL5I2vY5u2h7a1+huvohWz3tv7/0bjC7b/YRUp4vwDAr9H7AVt5CO6QX8p/mk\n8dIlDpvoyh8d47xkVQGGircXI0PBEO1On0c5zEQnpEqLG3xk3tuhSLr2Svuh\nZXGA6W0JihDXyH7hRB563sq5lQJEChQJd8BWvl5C5eQdW2eZyq6HGdnMZzTl\n/XBn49TNaPGi45gF7QChiVazx1jzhX9QkCGBvSWB26uIbLX6NYn7Grs4htkr\n9SW58EnU/WItSE1kJ96y5xlGGZyACataZAUIktSBg+tuqLhj7lqVQvzwsZfL\nCrJO\r\n=gJj6\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"4fde2c2c7c782eb392d851ed995f883fc27477e5","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.50","@balena/jellyfish-logger":"^3.0.23","@balena/jellyfish-metrics":"^1.0.284","@balena/jellyfish-environment":"^4.2.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.5","deplint":"^1.1.3","ts-jest":"^27.0.3","typedoc":"^0.21.0","depcheck":"^1.4.1","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.1","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.7.0","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.2.15-renovate-major-external-major-4fde2c2c7c782eb392d851ed995f883fc27477e5_1624435359308_0.793536671301307","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.0-add-loop-field-ts-2d6259558086fb4fd446076b5590981f6e0a07c9":{"name":"@balena/jellyfish-core","version":"4.0.0-add-loop-field-ts-2d6259558086fb4fd446076b5590981f6e0a07c9","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.0-add-loop-field-ts-2d6259558086fb4fd446076b5590981f6e0a07c9","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"fa82417f83feed4fdf49c1ec19199dfdc405c119","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.0-add-loop-field-ts-2d6259558086fb4fd446076b5590981f6e0a07c9.tgz","fileCount":193,"integrity":"sha512-1hKIw5dib18Cv1IDbFRYigL+15/xWxYqj31wMOioF1/WCMjYHsK7LUdMgOcYxMyyhMOavex9mscpOCNAMT8Scg==","signatures":[{"sig":"MEQCIFQmuiZA1Qw8q7zSZ0FL7LAtFhQj7Fcfffi9rgFUE03hAiAWB+V06PWHWd1O6IxS8E6xkt95Zj53xnY66IhOrza6Uw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":725853,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg0yJ1CRA9TVsSAnZWagAA894P/1TV6yrUoyLCTQ5M7Lrp\nrzq2zDAjsHphhUQM4PbntWKq0ZHVKrlU1SujfCjkGYJUnuQUo8N87uuFTimt\ntNBxMqksCpCgzKs1A9pp3UljGIsjEIRnfhCyJpNWIp6+HWQXXvN3Y+1jGREB\nlf1T8IQvRJiuBb+AJewbixrNhEHmnlzcwgPm6lwM+2XgJ6g5UIDphbpwwSZq\n6lMZ1QvD6rLxGlWWE2WzHy2TA3ntpcmkvgHjAIQhNBcHJFHOQd//f+lA9fZZ\ndhfXPAv4lGPj4hxQCO/97ecIMt/HsAuIQ3MFrtn48kt/QaZlOK1TWvlNh43D\nwOeCWzu2+O7mlp2d72/r1MFufxeFbAbl6ZiVvEGKRc8ORrxUW4g1G/pP1aU/\nR2l4cNkbViSPwlAa7I/h03bI0v5abV/nnPQYzKqD3slCZLw/+CooT6Ozhcik\nSiPIH5C/I/H4m+6gN2zB/ONNuAdKwBAYCNj7B6p5uq4CvQZNSFPGTxU1M/CW\nicA3NXNRzT8Up6xcpqrydBX2y+MvzUznEKgomdagnm3CSDM66Uz4u4NxDkFD\nfUsIG2IdCQFemaB7ruRePH9cfkyMDhk3Gp4eLQpZj8TcSIJtAMxyIUfcH2IY\nwxsasSbzWD/n0wYPbNyWA0XWmtl/NFuOoLS/hRavr3BDTZh42MmG89+6Bbpl\naT9z\r\n=DSsS\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"2d6259558086fb4fd446076b5590981f6e0a07c9","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.50","@balena/jellyfish-logger":"^3.0.22","@balena/jellyfish-metrics":"^1.0.283","@balena/jellyfish-environment":"^4.2.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.0","depcheck":"^1.4.1","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.7.0","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.0-add-loop-field-ts-2d6259558086fb4fd446076b5590981f6e0a07c9_1624449653283_0.6466074142703657","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.2.15-renovate-internal-patch-a7bf8c024dbd48ec832f3ea0729f79fcad205f11":{"name":"@balena/jellyfish-core","version":"3.2.15-renovate-internal-patch-a7bf8c024dbd48ec832f3ea0729f79fcad205f11","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.2.15-renovate-internal-patch-a7bf8c024dbd48ec832f3ea0729f79fcad205f11","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3977ada5fc328f8f2d0cac9ad561146299101694","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.2.15-renovate-internal-patch-a7bf8c024dbd48ec832f3ea0729f79fcad205f11.tgz","fileCount":190,"integrity":"sha512-d0PX5QC9gA1tSbBHA1nDfW7LRDPbWw40ZbIx03le701zCeZzRJ6zF5xYIa8E9jBZzx9zVwmj7ckIqQPCe3gx3A==","signatures":[{"sig":"MEUCIQDVCTgFEflpwJtJr8Ut9SHxXf2bHZNOWr1NbgNZfcFmqgIgVP+wLrR5KVaf7AWsM03U19Ag1/eau+q5eeO5AoIIOAA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":720801,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg08s/CRA9TVsSAnZWagAAlwgQAIHYbJCDbyBqdg/6O3H6\nV37/ymAeeLZ9Ey6N1K640vyPkdL2nx0FjLpaEBrzdDBBEt9FPi4czmx6X36Y\n9fqMnj6UPbesPZVecj9NN+9KXsc7mgRltTozN7FZxLfcFt1l2zmNKxxceIlv\nVSwacn2vJm3pLkfm4rJ7nleqmNOQG/HS/zhsBsSbafxZ3lxWRuXpygwQRQxS\nuejns4Q9Qmkhoe5cYXXsAUCaocPNW/Due9PX57ux5UUGTBD0D2aBHp/hSVVz\no4eAUvKa0/Wg92ESlslFY7J/j4Oe1z3DBnUdVV2nsAFnSOtvDLlse3zyya79\nqBbax+pxtWMjRpHNWBL6v6YEWHqOMLMFAL39kkLR+1aNFvTCV1YSVBsCV8zJ\n1lo+3vJTysOxIe/B+B2Qh7t3DJd1AsQ7CQdJa3tqDj9B/Iv+NwPzRDQP2P6v\nwNW+JgekIJiCE/DXmKFJiTsJdXWUQSkw38Vj3aP5ABdwZFZk3VNf5E6cmDTF\n62J3VfPV4ec1JvgSrnc6nQhv3glT7UaziKetkTUma0F6ds0dTlgccXrcsWcM\n0i2Mce7jsV5OcWQZFu+C9OY2XxlQN+xrHrQCNq11L2M7+NanLFn1uG5AvewT\nUYYCvSfVBU3OIz5xHcMRT+pdjBnEPhX+mEcDpOdYVqlLIKHNNqh2Hdo/PI7B\n/Tqu\r\n=B9rR\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"a7bf8c024dbd48ec832f3ea0729f79fcad205f11","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.51","@balena/jellyfish-logger":"^3.0.24","@balena/jellyfish-metrics":"^1.0.286","@balena/jellyfish-environment":"^4.2.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.0","depcheck":"^1.4.1","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.7.0","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.2.15-renovate-internal-patch-a7bf8c024dbd48ec832f3ea0729f79fcad205f11_1624492862641_0.12409929920596396","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.2.15":{"name":"@balena/jellyfish-core","version":"3.2.15","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.2.15","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"428324ec8bb116552f0051e7f11a93825d6325c1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.2.15.tgz","fileCount":190,"integrity":"sha512-3zn65G/cCja2Uhoq6XUwKI0bq7EtvXFe5KUL9ukgaD8LCB7wF8RpoOhlpwpcRWwRFTwn3xcgsvPa3YicA9mLsA==","signatures":[{"sig":"MEUCIE58YwVHIz1QFvAcsxuMxZeTcFYG4NRdRYD0sYJmqchNAiEA6g0Zc/Iv3G0wdgfkVwkzrHbqetkR5OduPGZIG4B7qt4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":720736,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg080MCRA9TVsSAnZWagAAkloP/1E3r0Wfm5xd7b0RTn++\nmkgqFneUltUXMJ6bbZlVtblTCFJnqH7I10uVWOl9I6lYoKe1TKNPPISex+CR\n5tYRNHtDBBagMuIl8gYxFA4r/fnvODINj0+n2+nj+UKmczw+PDO32stLISsV\ntiIk1ma77z5KNoZBR+9qu39pA6RxtTUVOkB4Bm7eJOgzdYX7e5NNmni0FaMV\nwVXxdAyiKdw7JGjM4i7BJbENS3BIKezm6eGK9vdQgnF4JO++wyQf0wzDn//F\nf3jxYL71Iq351itY1P6X0xwIiF0sFYgUwdNpAo9lymv7kVPE8NJk6SX6vj4o\naWn6cgkWCysXOZyRB7HFpw15x5+pl2XjtvodnPIJ967raM8ge0MVK9AKxlaI\nrkk7KtjJ0FsIC6ud+2EuXyjpFZgna4T54GpPW2gd9gLl+Es+cZaUrdrMv2gQ\n/gJvEnd9HhyrxV5+1ShySA4x3azhFxJ2AcjUhWtyZAwI3xge+NetOEc/8B05\nuGrRHuZ5SdxOKorc1epTOYS8+isb00jPy11YadAwbh2v/ErYN4k0Z6xe6ie5\nFeq16nrmFrwHSIsWRJwfCsaV3lfM/ZbEaRMAdKJKg9vvScBvJ9y5deNgb+Qi\nUBo1OKE61yFNZJz/+AQtudFKWSrnKsctKUVhvfM+xuB73bQxCS/2yXW6R+/H\nHhnP\r\n=Y7xb\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"e02fbe8e71712a78880b88f72f67821f37da8f6a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.51","@balena/jellyfish-logger":"^3.0.24","@balena/jellyfish-metrics":"^1.0.286","@balena/jellyfish-environment":"^4.2.8"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.0","depcheck":"^1.4.1","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.7.0","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.2.15_1624493323856_0.9540209341154855","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"3.2.16-renovate-major-external-major-45093d10ba2231402f3e2bf1a79e8454e81d2c13":{"name":"@balena/jellyfish-core","version":"3.2.16-renovate-major-external-major-45093d10ba2231402f3e2bf1a79e8454e81d2c13","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@3.2.16-renovate-major-external-major-45093d10ba2231402f3e2bf1a79e8454e81d2c13","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"bf372a59237dd32ca12398c839cc963c035b8812","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-3.2.16-renovate-major-external-major-45093d10ba2231402f3e2bf1a79e8454e81d2c13.tgz","fileCount":190,"integrity":"sha512-RVExPs6DQe24Vul+AinIOX2tvGrbiXVN1pxO+58vBDZIq1XJ+8MsiyjuMdhssFveUzhq6V/vUDZ4/IlzzOuBow==","signatures":[{"sig":"MEYCIQDXzZWJWQLdm8lvrni0aovwwTixlM2BNthAzs6AjiFrsQIhAL+2vDBNxgNg0S50ICt2kwfGsJcuLMpqV5fBYL44zDey","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":720878,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg08+mCRA9TVsSAnZWagAAZfAP/jXJsz/JWq1QyvafBjY1\n+CSRrNoLHwjUgTQnuX8rV5o5tG22uoy2kVXT2iAa31xXoIzm5TkC4GTE+aNl\nOl4jgezZeyl6U7MTmH+HWzx4R3pCfyeIny7LozicqWaRiyY3sML7hVQ/Lr2D\n75u6MvmfOT38xBJwJPkWLPENLlEybGfV3G2Qmlm/hucEnYKUcTndF2jyk6FM\nXJ/64zCpxmFFS/eVR1bkXmFpFIJJ3O8zRUkFQhrHOY+aTTo4UN99pxpbX6dm\n9V6N1fsj8egTSoCy7umXc80q8UIifKLUJVZFq7Fduh7s3R/IddiJuT2X8PjP\nt0C5WuU0WCYQ/Rk+OFVzS132Q/1bydKgh6v44/ILTUxbwy4dZlLODoiTpkV3\nXiT9yYR4CxeKAvpZbNXmS+tXGA+ivUbOJuewjBWXAHem11nhOoPZ4yT2yxqM\ndmvfBmzxYXuj8hzYzZe+HFUr8nw3QCjLp4d9yWt0qZ5wmfTdwiNLj60I/+nT\n5ULn3OwD4OHe3iB/w/lD69+nnunQubpGM9f4wOJhpYjTN4afOs3w2FSPUhSl\ns3KCZIzGs07uVUWvKS5MDY9r7X6g8UUe07NMIh9xNuG1hX6UuHBuHdHW6k8F\n8sf7eb11waa5E7qnaHLd6p3Qj7JaW/ucK7XlS8TaTppP1V4FXRqaswaAQ6gI\nT+Rt\r\n=dX8H\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"45093d10ba2231402f3e2bf1a79e8454e81d2c13","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.51","@balena/jellyfish-logger":"^3.0.24","@balena/jellyfish-metrics":"^1.0.286","@balena/jellyfish-environment":"^4.2.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.5","deplint":"^1.1.3","ts-jest":"^27.0.3","typedoc":"^0.21.0","depcheck":"^1.4.1","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.1","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.7.0","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_3.2.16-renovate-major-external-major-45093d10ba2231402f3e2bf1a79e8454e81d2c13_1624493989608_0.0061268353992642854","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.0-add-loop-field-ts-db67490e3a984ec221a108e50b74bfa625e39841":{"name":"@balena/jellyfish-core","version":"4.0.0-add-loop-field-ts-db67490e3a984ec221a108e50b74bfa625e39841","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.0-add-loop-field-ts-db67490e3a984ec221a108e50b74bfa625e39841","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b39272f682945a9284f1f33ba4ad5d4ec62f176b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.0-add-loop-field-ts-db67490e3a984ec221a108e50b74bfa625e39841.tgz","fileCount":193,"integrity":"sha512-if6NnmnVqUi70hQsBIVSZ0TxqUvc25rmeENVvJ710D4/NVxMkoyoIf2Nq1UnRm9iZ6E2U+ies2s4i+t7iwEKVw==","signatures":[{"sig":"MEYCIQCuXqYWaHspGZzNR+ZfXolj7+qGAY0CU7yO0ak3J8ZqagIhAPAqKafoyWK4RzZK1ekwxiJRWibFxa3wDleTDovh9QD8","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":725995,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg09QOCRA9TVsSAnZWagAApa8P/jD0c0DalZZh9yAZIgH+\ne5H3PQrnf/EKZUuwF2BGPelKPxKxqNc77C5nRVGs7lfcN5wnj0cPZAGuf+tx\nCSHwqObvAjwY38hMrtPRMtNedIDkEEeyF9gHSW8FOh+qFtDomL4neZajfFcU\nvql/q7vVOQBJ4xsnafiW6QYHHu764TkNUAyRu5p+FRM9jemrVBz8ErpQLUyn\nAirqg1fK+uGdU7jLOrjaBfXpq6QL7fWHSZuH+m/Wp1di165lDH0g3/Pmb6XE\n9xT4qiwixTDPSLDI98vJpQqyrj9WrF+ti5xsLXvkpx14kfeR+F5T5VOlYE2x\nQADVNaZmnEVr04Q+icdqUNwR0iXPg50mwP2JvPok6+AUt4Vahv+ynl990GDY\neMciKiFphoOau8g6xDDvbAj7qFi0mj02j2UofMYBhNan+9Qnp54S4Zyt8ZkG\nNjk+tuuIMsvxLVMJBO0s6NRg0hALJnxIWK3gOEds1TY3QbMxiuTuGNohBVFK\n+HIZZesAusDTm6DSBme2dAWZKYELHVg9SH5gQGR2ewL5wTg0SICOYv0Xk9JE\nalVRd2MQdUHi/PMGx6ZmLyRMv6jGaoffaBHATXNaJ76xEWI3HnSgy9U76TOJ\ndyjbW34mwIcH6b1zbZKQ7cZBLGsuClR6CcgAEocOzEteiSeTvN21lxPLWOzJ\n+grs\r\n=XNsi\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"db67490e3a984ec221a108e50b74bfa625e39841","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.51","@balena/jellyfish-logger":"^3.0.24","@balena/jellyfish-metrics":"^1.0.286","@balena/jellyfish-environment":"^4.2.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.0","depcheck":"^1.4.1","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.7.0","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.0-add-loop-field-ts-db67490e3a984ec221a108e50b74bfa625e39841_1624495117840_0.6005640170014221","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.0":{"name":"@balena/jellyfish-core","version":"4.0.0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4769a9bcbadc54eec5f917f19808974422dac3d9","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.0.tgz","fileCount":193,"integrity":"sha512-fMITjX+JecI+dO/75uNCBnaGccFIEVar6qNg+RCM4bTQbdj3z0S2gN646Xtyiym7fkYIyC2jek8xTwncDaHZnw==","signatures":[{"sig":"MEUCIFpuIclJp+hAxr6MDjkJ/kWwYsm7ruv5zBr/vNQnbOrZAiEAmsmQf6h5NYw2lzRnLzYpUbxZWAfERtM8TXDXlRvbetc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":725936,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg0/fRCRA9TVsSAnZWagAA4F4P/06ovUmm7hb9h8CtPFo9\nUqeiX8wtLn9YcAvioH/PxkNTdq6I5E/NB0CX38gIYFhb0GDXVCBxeZq18Fib\nSkyVOHjR5GR872XIFGAiLYpfCitO47Hl7u6hTVlmagkKO6QmrHf5zYUtZtve\n2BxUC9XlyVS9HRa7hAS6zJ/ynqnJdgPa2dIcksSpKf1+W1jEWOlFiVTPA/A8\nGbQdKiFLPG0gtoqFJk1eZnt81tQCj8tXz3EdZ3toVzZmzSemHZpc+EVDje9x\nk4iksoGqKxSCaWLrEYU6VBy6oa9CR7DVzDAhLa/f93U1Lr26dymAR3Wm966m\nOpAy6H7fUFNtuUNiscrD0WIimWEir2xZ+cbIcPYPND8apU6yNm27TVvZ2Cac\n/A87a7XKVyKSaASkANgsAN4TLH5/AnDXVkW3G4fj75LJKg1DOdpWR32rqfDA\nd6jQU/KumP+CZ/jHBPz15fuLBVLKpB5rai8y17ICSvEK4qzZgqnQgYlrXNeg\nR7/tjUgiLg9iN3bS7jdKcLcWOnFYSM4D2ilIIBooal393R3khQioiNeMdfjj\n6Q5LByFQ8FQVZ9ocSaTWxTA4BPk5Y2GQtCEy2DqCebOWHjbv3BtUK7AuVpK3\nzt+USzF2iuY91KHOnLGJO4k5C90TlFZMkkuUwHKnklqqmfYrh6Fgk03a23Ja\naG14\r\n=xfK3\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"b09166d5cd4c5b2dd4a6048d463d5e6626ef60dd","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.51","@balena/jellyfish-logger":"^3.0.24","@balena/jellyfish-metrics":"^1.0.286","@balena/jellyfish-environment":"^4.2.8"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.0","depcheck":"^1.4.1","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.7.0","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.0_1624504272983_0.8929947801560649","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.1-loop-type-check-441f31761704581dec2fbb857a71654ce18e9472":{"name":"@balena/jellyfish-core","version":"4.0.1-loop-type-check-441f31761704581dec2fbb857a71654ce18e9472","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.1-loop-type-check-441f31761704581dec2fbb857a71654ce18e9472","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"cae610d272e0a2b6495bd274771fdc44298ff30b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.1-loop-type-check-441f31761704581dec2fbb857a71654ce18e9472.tgz","fileCount":193,"integrity":"sha512-7lA8oAxDOGdl3EgHbnrAxhiBlAzYh7AK7PbTOPMcNNROTMKvjH/maL9my7Apcmc4blG7nWxi4vnooS2/oNiAMA==","signatures":[{"sig":"MEQCIAbW7RLcS5ZiXgaGfbEQ+kE4cxFOO0uyUZaBRbRhSumdAiA9MJxH9UmGxnym6Rf3I5FcD/rAPAuIKo6mJQQoI/kJvA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":726193,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg1DRCCRA9TVsSAnZWagAA/M0P/RmYZLwUUohvPUja/GdS\nvYJonhAlB7kSsKR2W0Fl/wDOqhLQp+0LRu+Lhocg9cZndXJ8uo9X5BGjXdhs\nWb6wolTXbXdRirDq1IHRn1nmZtLciTb3S7LsYsCNspIb82jg4jUIykm5ZmCV\nVIfOu7NYsEYWnDCT2gSgKC4guyud7JQNzExKhJtVhG3QqAZh/wQjKnFYtXqD\nUMKbOAQue0KbvEA74+ZhKU8o2WSSN1a61OgKjBMMVtUb3x0aNmpHJ7Q3NX60\n7fY8SrrPSnca6V/7JOFqiq6KwxmB1xvPiexUd7FmEGKk/3dBjAFTlOGvj/wT\naXvmER4HzgiWtCCC2rDUNKq1mY/n1/4Y9qa2NxRx/7RJnPBdgd7T0hmWtobq\nKy9pGuPE1/7We0Ak+/LFAIB4UI35MLo1sJkenp3DUutwy7S6uskucdHtxEzf\nVzRHRG84T8EH9KKMI0SsOUJJjo3T754pdJoJGG1DdMV1XpLGGf4AC8Oz2ceA\n9WuvUvHSPLwYAqHktsl7PvuNcCZ5t44JvuyKOP4VumAcYtV8lvKVOuR+/mO0\nT1C+Ss3/qU2p47SpKoTo2B1U8Ilfty9t36L20/eHy/+NYPp+BJ3VjWh7eWtY\nvCequwqLG8wmZSXD8hUr9uwhMDcZEawdVmJzISz4eIWOoKxzewW/axtgP+HW\n/i9j\r\n=LIKU\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"441f31761704581dec2fbb857a71654ce18e9472","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.51","@balena/jellyfish-logger":"^3.0.24","@balena/jellyfish-metrics":"^1.0.286","@balena/jellyfish-environment":"^4.2.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.0","depcheck":"^1.4.1","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.7.0","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.1-loop-type-check-441f31761704581dec2fbb857a71654ce18e9472_1624519746292_0.7392857798806494","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.1":{"name":"@balena/jellyfish-core","version":"4.0.1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4af35fcc71fe502438a772abb1ee4a6b926e743e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.1.tgz","fileCount":193,"integrity":"sha512-SOt487WTyLAkHl82KoZmbBpT6zxZCZDk2wukjpNJTdZgIyB+BCJiAcCC/0WBCyQSQikBpX2hDze9H2JXAanjAA==","signatures":[{"sig":"MEUCIQC4U6vhvy0V0GXCaJATSzcfr3o5LahGXp6FAOcapF0uwQIgdnBWFbERSFWwFT5dr15EMk6LEqTdxKOuwyTfQiUsv1Y=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":726136,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg1LBLCRA9TVsSAnZWagAA9OwP/2CARP3YA830MfVHE57H\nBrAUyrgQxx56w/wRYnVS2wbz7BRFqosoUvtn+jEtDTi2FOEz8xM0KJxfbjqK\nEH5qPqFw0kIURPg8kaeq3qSyWKuk/BYwbHUok0CY/+wwXeFdKjZki9yaNHM4\nK0GiM6UjX5prRTz996ge9HEy2xcEongcID5DGXKjjXgy/mzJfmY/yrPnSKLm\neIEO8IIL66FhsK7G+JV1oTC2Csvb99R3ydidkO+ASu3SqZtvSBGDhNa7Iitp\nR7AVAMtcdfSRDZtG1r5PE/feRJ5VpAFvEW1pZuFLi+KdPWuxKG2FyxMaGVx1\n4kXluEAZN6TuMapti8ZKwDbdnrH/l4W2jfLag+oztydWofgUuE7m4MyHsDQ/\n5q3p6e/ulOPxt1ysebbk1kkWvsPFuZ+N7ox2dllFYOCU2fbrRsavnDInBl96\nGtNB1bul4wo7pAM6JBSFaIFvLAUaIeUTTg/C9JmU19Klsmaibxz8piATrkg0\n631pe9YDEaS+jOR28eUu3jFCTTLE8uaUELRZlj0gRiQiegUhnXnCxWWkyNDS\nMnVcFz4V47oGRcI84QwOXqrgW5ZIrinQJ5wa0UNivJk/jy/UjHqK85kK94Hv\nOLIsgioMj3/TEP26/9sVgF5w6EBFUbdGZYStcdAnXSih1l5o8Sl2acCXNfa9\ni3SZ\r\n=hiwG\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"ee92b59a5c207997b60b758895ea14c1d5681cb2","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.51","@balena/jellyfish-logger":"^3.0.24","@balena/jellyfish-metrics":"^1.0.286","@balena/jellyfish-environment":"^4.2.8"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.0","depcheck":"^1.4.1","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.7.0","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.1_1624551499151_0.8448999649888742","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.2-renovate-balena-jellyfish-environment-4-x-bd322b84adfcb5fbfbef24257fba01b388666821":{"name":"@balena/jellyfish-core","version":"4.0.2-renovate-balena-jellyfish-environment-4-x-bd322b84adfcb5fbfbef24257fba01b388666821","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.2-renovate-balena-jellyfish-environment-4-x-bd322b84adfcb5fbfbef24257fba01b388666821","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"04462715a12179a780685ff2b7438ccb2c4a8e59","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.2-renovate-balena-jellyfish-environment-4-x-bd322b84adfcb5fbfbef24257fba01b388666821.tgz","fileCount":193,"integrity":"sha512-somUPAGYhE4bupV2bukFf2aLA4DOvtKvij1d0DHIhi1q3l7IrdNomgFFEbqfFSwzDDN2KlpB0W2yjdjZjDnkmA==","signatures":[{"sig":"MEUCIQCsEXkZX1gItxtHf+DpvHJfkc+fx+pCyji0mJ2lHKnxDQIgP08c9GJAVANymXSS70zvCbh3cMAQFj5dtUBFAagysnk=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":726325,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg1LITCRA9TVsSAnZWagAAyE4P/jEMwMuTWLRwv11c5JoP\ncn9dmHKHYe6gk/E7UgCeD0GocBkGB56eCWCrQmXdlbX1+yWYprP0REtT2l1j\n8+8XCi+bPFYYFQuDrQ9SOvT/ysxhR4cn6gQ3Ewg6KTobLnw/azklxIW4RxZ5\nO2qZT+QpTWY81JGwfNy2dvZHAFnCm8UxZ2Moz3ACxbzqiKH3tFYtkVwNWz6P\nZgSl+dxXPpjXTl+7/BWaWO1uqjFKBRF/uhYDbF/fKAV/4s1VmMhiVzxmDvTb\nhJguVSQSQrRnrkngzTAZxm6LyC1PU961e+GWNJ8xFsNGi1gpuv/yJ6+ni8LL\nNYXUrnSLQ7IKCqKCiuaTdMnWeAHzzAruc74mqlJfWQDsl6edKrLfvAywn4jK\n5sddoRUYLu1oX3ZADXhRVtPOL91EWkiIzWpTg58z3xl6xbccfmlTqnAnMZ6K\nEnyPzGjoZMMb0+gT+sVTGryS1h8KFx1k8AU3Mg0lZkyNdukoPSVAmK7/EYSA\nMD2d6KNmmG2uq+sEqDaDwlFXr2gOzoTOCOl4L8mxpH1DNA1LLkTvOMHHKvJ2\nn6gh2fpXb6q6wnyzYkm/NlkD7IzRhcKve9+9M+nOPSKte+FApDXG0+C3GdA7\nsgHQ9pjBDsyQs/zJPynT1fiKHCdqrqVGcSvQ0uaBiRk8BNbue6D8x+vO6aUX\nn10V\r\n=RpYg\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"bd322b84adfcb5fbfbef24257fba01b388666821","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.51","@balena/jellyfish-logger":"^3.0.24","@balena/jellyfish-metrics":"^1.0.286","@balena/jellyfish-environment":"^4.3.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.0","depcheck":"^1.4.1","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.7.0","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.2-renovate-balena-jellyfish-environment-4-x-bd322b84adfcb5fbfbef24257fba01b388666821_1624551954856_0.2840244311971263","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.2-renovate-internal-patch-26f77e380f05ca35294f680b70d740f8b1317f43":{"name":"@balena/jellyfish-core","version":"4.0.2-renovate-internal-patch-26f77e380f05ca35294f680b70d740f8b1317f43","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.2-renovate-internal-patch-26f77e380f05ca35294f680b70d740f8b1317f43","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e23227869e53da80a5f278b453eae96542563a05","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.2-renovate-internal-patch-26f77e380f05ca35294f680b70d740f8b1317f43.tgz","fileCount":193,"integrity":"sha512-zgrqaQoJ/8JLA5iQLpXX2N9Vd01DNFl5zvIrIWcH4ItsYFWVxEZzhfmMbpk7STajPrauWl15Ywey0vIqNmEtnA==","signatures":[{"sig":"MEYCIQCc1ZGo/g/hSv9YZEpNU5fiqa+GLPAwZp1/7UGJlDc1pQIhANilOmprCnNEFJndYxm3j/RAJG4bc0Bf4Ycs4B+KZ2dI","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":726303,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg1LI+CRA9TVsSAnZWagAAr+gP/2iJPcD3HtXu9wSKXo3k\n68NFPm44WXpCu8waSEXO2RIO5KqMKXY+btyQQyfAigr4caONRXaFDyTE+JEX\nvEQwznZ6xvm09N6uh4cYaLWfGUsZtwgzQpPNrdxfmMAk0W0j9WWrFvB4iWum\n7cgZklMsQQxe0NCIWVO6phOBPfkmFtMrk6Aygvegn1N3mxHVTKa/T5w9+j1P\nA7iSxJX/dNhW8awl49DvtvcYJ8NCXm+dgRZgVk/VqKUwKEWnx8RkKFEQRAf7\nsNellia9EfwwDElGp0hyvHfGLsO+8nCUltNL+m29NKDCH83CjMnX/iiqjniE\nvk4soyKCTk6luQKQa9ki6KbGjKCfGg6MoKJshW6yUEtIMVsRWVR+pj1y89Z5\nYo5SySOimDSrBxaSJY+494VdCsJaQwkiJI1+Y0AvBSwBlwsY70gHcGJZyb/O\nelzZWde/8G9ON4kKRKqww9/QJTlvPezhrc1QD1xfT/toZmfqv/V1p11bwE1W\nbR0zmqHZSNN6qmmFDYzG9Ug2o0UxxJsXmKYW0LeXRaYmbTnrWaaqzU0H60Ws\nyuACo37iycbNRjipTnDhoUbmiFwQHSUXeAtcQbGB1wYCNnAX8AokYAk1moyC\nBZ4ttqgDO0mWzBiobHa9qtmzI7D7fD+TVWWu1KgxSBYyAqalkMd9Cw+wmzYJ\n3fb4\r\n=r9KF\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"26f77e380f05ca35294f680b70d740f8b1317f43","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.51","@balena/jellyfish-logger":"^3.0.26","@balena/jellyfish-metrics":"^1.0.286","@balena/jellyfish-environment":"^4.2.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.0","depcheck":"^1.4.1","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.7.0","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.2-renovate-internal-patch-26f77e380f05ca35294f680b70d740f8b1317f43_1624551998234_0.10947696247663963","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.2-renovate-major-external-major-5d08636ed5712972bc1a42d97e425f214bf21d0a":{"name":"@balena/jellyfish-core","version":"4.0.2-renovate-major-external-major-5d08636ed5712972bc1a42d97e425f214bf21d0a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.2-renovate-major-external-major-5d08636ed5712972bc1a42d97e425f214bf21d0a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"bb70af9df0d0d80221247d4266596a3cba626511","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.2-renovate-major-external-major-5d08636ed5712972bc1a42d97e425f214bf21d0a.tgz","fileCount":193,"integrity":"sha512-KHgiVriN6CGCbtgowTU6MIhkKzDTQwKPYD6LkDHpw7m68+G2atGGMAOfFKxY2KpTBfU7k5LDNTxCIvvZMWF0Gg==","signatures":[{"sig":"MEQCIFUto2S8Y0A/ZkpA/zhBr8JPIRIyX2q2KXw67R2swUXvAiBCaEBlqhAkxMsmNl7DKXt7gvCxV05D+b14xkgt32LLYQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":726277,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg1LKqCRA9TVsSAnZWagAAOGEQAJ++xrUCfUTrN2iytM5N\nvW9OUrc+YzSXT09+t7nsqGkB0jyI+sK55x3yE/P031lvGJ32LERpKSYD5Kvn\nQwfBJ/lGghveb/J2fYN6fn9gfhxT15GVMfwwNmfsBMsmoHC3pHiFmvodPeiG\nsZYI3wn5UlCcE5fvPP8U+WPLr5TS/ZXCIHUqgD+2GWRH9zewGuvkgc+PQvfE\n7nPWkeDuJEW71HI47sVvZpBji93cby6akP5fF7Aja0w6VwobMiEhHNHxRazy\nPLtMCFPn9mfB3pQXd8gfuEhIJbpSc0n8jVaNSXLoahoxzxO75Ub5zlY3VMdY\nxVdLeNxn/vTIVmg+foI3JbmRpZBZF7qmi5vtXPRUm0+JJQ8DM1NQakGNCJ3W\nEbe68gzWQPcWEzGoNEHMTXqj6WxpAZ6oCQNXqpjtogv2o23na2bVMalREeO4\nYMpiu8zqTcYyKlxdBgeGf32obaOhwGsmd224FnMHrCydwBM0/dnn7sRRnfTi\n9vJsiIXYAguBplW+BWTGk36VmszpCZCVtMow0J9A9NK3AV2Jphb49OPFxvwb\np4bO450rtTXGkinh8BC6qeVNMywiU/kYkd7KvVoJeoUy5wqUhLT6Wys0rV2F\nXDog7nHhLOZ+JnmegFgMuc+ttwq5RUG6FxEuqM4m6qxevte+ajNzt9FnH8lC\nkSsu\r\n=IpFz\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"5d08636ed5712972bc1a42d97e425f214bf21d0a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.51","@balena/jellyfish-logger":"^3.0.24","@balena/jellyfish-metrics":"^1.0.286","@balena/jellyfish-environment":"^4.2.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.5","deplint":"^1.1.3","ts-jest":"^27.0.3","typedoc":"^0.21.0","depcheck":"^1.4.1","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.1","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.7.0","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.2-renovate-major-external-major-5d08636ed5712972bc1a42d97e425f214bf21d0a_1624552106267_0.7150115809358446","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.2":{"name":"@balena/jellyfish-core","version":"4.0.2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.2","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"05aa5e5284dcd9e6f9ad961f36631b73ffceab7c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.2.tgz","fileCount":193,"integrity":"sha512-3LNdfmRnoAfDeE9Z1R/eGu0up7AUTV/jFpBzaBvo0GyxwTklcjc8E/ohNoaYeciQuEE+zESb1mma2RlWSnpXtw==","signatures":[{"sig":"MEUCIQCDXuuX+LrInoYG8eS7TdcTgj71bYAK9ppO5Q1TiMJhhAIgI9esQv/h5pkh2YSp1q5wo7RVrxvyF5xKyIsCDpWVxTk=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":726238,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg1LNqCRA9TVsSAnZWagAAG7QP/36mKy80Ru2uhVIs4KuW\nOV2Jv9iQs2tdTLIj/mA7jsa96ErTls34g4lfYo4vuKEFo72hl+7CFf85ehnE\nQw60NFkPHbptm8Xj/j5A1FtHiqvPb9syY6hwhigYGwdBAdlXXhmtAYSCSsZd\n7J3J81dickHE3/Mii/be1pZtXNwvMCEK8FEZgu6/YH5GoVUTfrWIbaVMaW8E\n+1OTUyfhK0l5bm6ujnYqmWKl0fXiJPb6CNnE/txtzymrp6XKkvelHebG5GZ7\n+5q28khFB1FvEFmgdybtzmMlcHJvBPk+gT2RLGVGaF3JsP0MgQMPMeOk4BZp\nOnHsQs/R4P/28nmeA+MhKciGHbDGPia7JgUa9mMa2/rh5WM03gNbpzeoZ4s9\nl9mmLmwdoC4TwVVBgpEfh7dlftMMT8oSFfl6LxCwS30PXn+4DQH3DM8MsBok\n7GOVnX3hZYPJZCohFbr8VzSvSSjjrd+I85uqI9uV+CYGvBj2zkygKwrcUidW\nIwlKsxE7HDa4E0APU681TYELkxPqnznjPz/wfAIyJ0kt+wBw/zy6r3oOFvUG\nHxUDkYqlq5GE3j5R5jcoPMNLAMD0L9yFmRQN2wGd+UM1cWEJ0WnlanTOcuQo\nSatUTD5lyaps5ytEB9HRuoXW63J3DW9RCxX+LKwZ+P7JkWQqU2s8LYYHiqeK\nKnKX\r\n=ajgb\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"73b2de608b7623d53f32ad5e1b0a180188a756f4","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.51","@balena/jellyfish-logger":"^3.0.26","@balena/jellyfish-metrics":"^1.0.286","@balena/jellyfish-environment":"^4.2.8"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.0","depcheck":"^1.4.1","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.7.0","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.2_1624552297967_0.48422392787191715","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.3-renovate-balena-jellyfish-environment-4-x-1bf792c8d29f092af63d5c7519676f2b898b4042":{"name":"@balena/jellyfish-core","version":"4.0.3-renovate-balena-jellyfish-environment-4-x-1bf792c8d29f092af63d5c7519676f2b898b4042","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.3-renovate-balena-jellyfish-environment-4-x-1bf792c8d29f092af63d5c7519676f2b898b4042","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8d23f577eaf5af55b6b4cde15584b66266623ef8","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.3-renovate-balena-jellyfish-environment-4-x-1bf792c8d29f092af63d5c7519676f2b898b4042.tgz","fileCount":193,"integrity":"sha512-EFGFGK+IDgMrx5bUgJSikkOQ5sShNFgF9weagbBTR6+NR7M8vI8NX/rLcxexIHtVcMTTYSdtIxJlIWD0nw47kw==","signatures":[{"sig":"MEUCIQCtuRWhIwNVx9V2BbKJ1bX0iFhp1ISUQdwsomWUaP4AsQIgEbAivtyA7Au3remnrl/QqqChC4uAmLS9Mg5mkWp3Dns=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":726427,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg1LRPCRA9TVsSAnZWagAAmYAP/jE789ZGPIUjInY3Wzei\nRCoEVNNw8zsYD9YYSCJOaZRJj8KPW0SIE+0v198P3sLIqPBX0APxJFttgORY\n7TQdfUop7axN9tCjU/qBjuHJyy0gpbrVdwko8Ptua6XwGROAy0ILA5xxrrX2\nsF9BP20gpRDzTTylGp9mg2y+985cTQojbxSk2zTXr+N3gW1o1FIAkowHl7yQ\nImflMN+xLiY2QNKmHmSDCOyYKapcCZcui1reNohLK+RYt1jaBTRcHxI0kIAQ\nUadFEv4eh8bNBHUiCerjQOfLLyRtoLd2bQmFz8N9H6DqQTnuEPz7DueLNVva\nvH+WNdABZ3CrPWKYBGfYASotH/VT1mvH2TQpwKEKNwOHNHYJJmESv5uWOu6Q\nplRNF0hgDaxL3xxDILwSclEiyHQHb2uW58VH3IPd7pI/ULG79nSMoIiK1i9l\nu9TL1ESnKGhzhr1W2xh4qpdGLri5fni8VtgVpxvLCP7sOQ2yq3iOO5Fevi4r\nT5M4wtoU/jfvjh1X8aQIUwcISnB/ImqWkPh2uwclAA9M/hvNBzU0XsS7yggG\ngQA1ebMXpUfHWaDEMgLwvzyjypAvMX2B9pudRPjHcrRaTbWyj4o15xZu4zHh\nH0CfyKKKF+FQyDVDqYKzOAjvT00Ty7pLxTK0o9KwqBR8h3dzjqZPni/XvE50\nyEDn\r\n=0tKP\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"1bf792c8d29f092af63d5c7519676f2b898b4042","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.51","@balena/jellyfish-logger":"^3.0.26","@balena/jellyfish-metrics":"^1.0.286","@balena/jellyfish-environment":"^4.3.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.0","depcheck":"^1.4.1","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.7.0","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.3-renovate-balena-jellyfish-environment-4-x-1bf792c8d29f092af63d5c7519676f2b898b4042_1624552527325_0.690882110796555","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.3-renovate-major-external-major-e76452f50ba2d6fd1727da1442929333081a6b0e":{"name":"@balena/jellyfish-core","version":"4.0.3-renovate-major-external-major-e76452f50ba2d6fd1727da1442929333081a6b0e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.3-renovate-major-external-major-e76452f50ba2d6fd1727da1442929333081a6b0e","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"acc04d257fbda46c62789b9ea69880359ebe0359","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.3-renovate-major-external-major-e76452f50ba2d6fd1727da1442929333081a6b0e.tgz","fileCount":193,"integrity":"sha512-5lbOdwpm/h/oLgUJwPOFpO1NUaOCUsMCKWtH5sfe3ax8eRowiMVr3dnzy+vzuynUFPO+h4hMnA4ylgNFCDGI7A==","signatures":[{"sig":"MEYCIQDKZSkuW4TnEsdd/YhZgxhJmF+1s2Alw+pw+X8GwLMNnwIhALrlWNixYH3NPiBp7M8vmai+uA0ozg2WP2NuglDOKz/1","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":726379,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg1LSjCRA9TVsSAnZWagAATVkP/2zlo9Bs0ijVuIdQMtc5\nonFjT08uwAHvZ0uWnzQ7CXouBNs3iz1WmqCjmIisW3C3tsX1j1/xOaIBFJOt\nACr0jYdA/qiMbeR9N7+A1a2O3KVE7QBrSG+hulWI2DhGPKHce7ed0FCpWWsd\nETDzC19LrZ3V7jdLK7YaqvGAfPAVcjD9t197QwjVe+aoqBxtdsZ7B/Jurfbd\nZE+Dl2XMoC5QT0uWAtSkhjoG/v58YFEtCbSn0LC1rLuwoBJUOSr7sMl/nrMI\nXWrTB+a/UfS2QOSRZYF9NnbrK//6R0eIkAEE/nYdLHGwQ9V93zBMMxXrRjsG\njfmnD1NbAcSgTyew0a+A852SWOHbqsCi3mov5MhzEIlZpbnPtu8kbd9gtj7W\nHvomIiU5fdgS1MDEleCR/ojEb+/gvb4QAYPWU2kYtfvtSGe33v/wchNIe6uQ\nzLj5qm4Ybty32UEmCOyCoTVKz8h2WMhQnNz/PGWX4ksS1aHApPLzwg4j+qEV\nZt3IbBezljaXrzEHzZRtvSGABiAb7fFZPpDWaIOBXCpPOriJ68G6j6BMBJj2\n225sAd1SzjcGotntrwGZj4pqA52kHn5PxRKQljRhWwxPlfJC953QLGDfZ7mm\nIPnQpbUORmnbanmrJiAUTUqWEfdBHFO6g2IUG5XnPNsrXHc2ibf+xDH6F5IP\nmDXW\r\n=/yT2\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"e76452f50ba2d6fd1727da1442929333081a6b0e","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.51","@balena/jellyfish-logger":"^3.0.26","@balena/jellyfish-metrics":"^1.0.286","@balena/jellyfish-environment":"^4.2.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.5","deplint":"^1.1.3","ts-jest":"^27.0.3","typedoc":"^0.21.0","depcheck":"^1.4.1","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.1","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.7.0","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.3-renovate-major-external-major-e76452f50ba2d6fd1727da1442929333081a6b0e_1624552611194_0.12143872987770576","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.3-build-include-backend-types-f1bf0920369994b81301a95d158664175a0d279a":{"name":"@balena/jellyfish-core","version":"4.0.3-build-include-backend-types-f1bf0920369994b81301a95d158664175a0d279a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.3-build-include-backend-types-f1bf0920369994b81301a95d158664175a0d279a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a46e81c340225704f2b473c80514a9e7411b7be8","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.3-build-include-backend-types-f1bf0920369994b81301a95d158664175a0d279a.tgz","fileCount":196,"integrity":"sha512-/LeJq8P3jSJszEjraG+g6iSiikGsbg3iEhkh4r2PUBTmm1NecxPPHr0oqt79IAaFLV1EgO6nJwftqOu68mvARw==","signatures":[{"sig":"MEUCIQCiSJ4w+/efAT6Hq8SjlCBiUDo96pf/eMwNk/EGMwB3+AIgG4I4Z9KGqQ+SdLPwBVwMjgZXcSQd5JvR5xC7h/FeSB4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":728144,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg1LU+CRA9TVsSAnZWagAApHkP/0IqdquGj6L1pcvX0R5v\nQukygAfDQE7psaBkhjwJmgBX1llPAvfl0dmuEhvU2HgfdiSSwloTTmUBqCJ2\njgyqHhg7Ke/aiAr/TZMYY5WcWK7irdDDndz5sqHe2wLGWYp5cNP5meqjV5Mr\n94pV1EvGOsfXzsHn+gA6Y0/eII+7iUKjqLdy0VWfKjRG5rdC7aBoKI0skQx8\nqbMbM4UpVvTf7az/fHZcwPPPWBsZSKh0MM0bGsGM0x2b1KEUMzP8YaxjE67V\ncrHB6pqpnleO3K+P06J68S2QMMGFUaOkpcqMhm510L5I1/JcuZT4fHFO/hyb\njv33rHYXqljHPvME28p2HQK4O4D9j6cl+pfYqDX2heStOocLF4l8Qm7/G384\nUVNWpTiaBo+zlMFFG1KfPa3pXvLoCr4qOsEDPGiL3A7ja5h3rxva6OjbGrMn\nDGdkHgkf3xJZnFZQjy55CwD+YR2vQ6M2v1mw8yYbRT8DU+bzeYFYOseRuAe9\nh54plTx86Dtd4lWFbZQSHxOavmpe4x/w6OWWyAE4BM2vdr0mSVmzNbFpH3yx\nuNf8RQoFL/D1lJh6BUINRfNhzQC6j/iA31Wrs+SCFUky8WviHJ9WP68LIhsd\nrRLfAqUcka6rbv8VXzV6lW6JpPYTYuz40pTn7MH/7ElqaM5VWePcbESzwVzm\nzLOC\r\n=dZ2k\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"f1bf0920369994b81301a95d158664175a0d279a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.51","@balena/jellyfish-logger":"^3.0.26","@balena/jellyfish-metrics":"^1.0.286","@balena/jellyfish-environment":"^4.2.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.0","depcheck":"^1.4.1","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.7.0","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.3-build-include-backend-types-f1bf0920369994b81301a95d158664175a0d279a_1624552765786_0.6717637341687774","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.3":{"name":"@balena/jellyfish-core","version":"4.0.3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.3","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d7c66ad02c5d2df3f5424eb3a3e3e968af4b9da2","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.3.tgz","fileCount":196,"integrity":"sha512-qxzpJwzvrYNqHvYiQUxB5WwlJluW6P20Ty1oSZObfyN2Zs0uPDZAE6m2pJdbHnrQelmUSmM/DXRG4nHA7MMPmA==","signatures":[{"sig":"MEYCIQC55ZhmpHqgZELQCk0qA4+G/jLlwu6z69VBt++S1NrpJgIhALZZYsjL0oS/5aqAS7I9vHGdVIy04Hsex3pumy8epdTd","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":728075,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg1M7ICRA9TVsSAnZWagAASAYP/3NEaqefebrnh5w4Cpmn\nWxdu3tSqSMAPKExWYcSDzFvO5L3C45kEYrpLEOhzioaJZbTzzZxALVFu5FmZ\nocnwkyMT6HzIDpQA/lUutCQ9EWQ2a60fdJkp73HQxeudNLCNNsWOQXmBI0zj\nRTDVL42Pt8sEBvxyq6DPgsg+J3DTyPIW6bIsji3AFwSqpiTTgSZYjpFB/8fH\nzmwDczBRipMGYJu3Zj+/kM5pXGJIro8Zm/LGyjEdqcmfN3dbgs0GEtu9I537\nTsqvhMUGqhL0bPKR0Z0K++V3Q5FkGOg2NgsPFamUgqWCo19c6P68XnZlt9yG\nnfBedA5ew7tQ3PAsph8w6czyYi5ST9kePHapwn6q0DhdC64j99WKqHP+03zI\nYxSKPW6iTrlbM5DJDBukd9vFvS48I1z/t7VAVpXb5JCGwowHSNecZMv8DqX3\n87N+H6XvZb2Gyh+gdAZJO4irdvwW198rR9Y14z/HcV7SnA3+hDoC//7wDcsA\nMxERc1A7LjbGBKbx38tZAyONjywCeNNfnY6sQtC7mUGS/uQcOQ4vtkuf9kbI\nwgFWHRy4B5jEPl8dnffxIGy0UJ8ShKA+AJ4c5iCcLX3EwSBxEeeGwGyJdj6S\nyaAdbWKfhAvDlFaU+uWlH5SvsStCYfOGzXBRhwoawdHe48YPkPJCBGExnY55\nCDKy\r\n=TsWo\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"8c7e6a3187bbb6ac802196b195d63a29bfb36234","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.51","@balena/jellyfish-logger":"^3.0.26","@balena/jellyfish-metrics":"^1.0.286","@balena/jellyfish-environment":"^4.2.8"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.0","depcheck":"^1.4.1","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.7.0","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.3_1624559304339_0.5491920458370974","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.4-renovate-balena-jellyfish-environment-4-x-716a13d07e9ab7b6e2ff4f1adcdfea0a8d92d60a":{"name":"@balena/jellyfish-core","version":"4.0.4-renovate-balena-jellyfish-environment-4-x-716a13d07e9ab7b6e2ff4f1adcdfea0a8d92d60a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.4-renovate-balena-jellyfish-environment-4-x-716a13d07e9ab7b6e2ff4f1adcdfea0a8d92d60a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f3873324fee426acef62543ab5fd45b8eca31d85","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.4-renovate-balena-jellyfish-environment-4-x-716a13d07e9ab7b6e2ff4f1adcdfea0a8d92d60a.tgz","fileCount":196,"integrity":"sha512-6j/Iyb0GjoB/zwwj/rCDYI0puacnNENtKd/ZLuZcw7iiawNmoicnQPOPMkltLCm7pzWHEolPLiSiS2OaS6mnlg==","signatures":[{"sig":"MEQCIH8z/kwKNeahP1SfleIlYIfRLVndDsYhmTrnnZ+jPUGWAiBdmA8Ml2M4uEyQ9C5ET9RD+tils46fNbD4wjYPVi1prw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":728264,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg1NCBCRA9TVsSAnZWagAADlYP/RG/j6/gd5rytQ+AmB0I\nQU9LOPFwlD2UyM6mXMkZQ/NYJ/IQ3Hne5ENDrCmTcaHYi8LXdoG/GHvndihc\nIKkbUCeXznyxpMvQofe0AfANqWeieqFgAkNFiKgq4e1+kUtv/AAl0x8NVUGa\nzqrWE1SmSgS8VFTGUI7tcdB/AL7tfFw13X4NAmmTrVSlZv0DyJDvfii7TfG4\nVncq/gVx96sE1Mb4f4HSyIX3G8g+pjI88hWQoliAfaDA0yJuq+oT3HMIpBIv\niPw6Y8rSdinPfR0zd55IFN8J41YYlLtuqKVJ7AYAzQNkf/g4/U9hzHOXryEK\nZuECX9LK/CYpsQ+xiTHGahw5plFH+PFZqrTXN7kOaviU2Z3BlpEWZ5BsP5Qb\nBmQt96MbuQTMP/UOoGTJIpUBlUiH14WeiWO7m3eK+RsxBNuhcsjKpVE93aNE\nVTjguTuKwM/ni/54B5l2crMzUyQYZIvJZMIb9r6vam4Y6P9Vm3xau8lPIeWG\ndg/wCtfCHFmHUpWgyJrcWJqXvujEA1QmMLD0Wb4t/SYBd8idlN4FE1KyC8FO\nOG4fD0v3961iqI/KJrElpzYwcXIo8WMlDt2yeYB/dHVOPhF4uMulJjjrb68r\nvcZyVZFVD6/0l5T1hqCFR3u+xiVnC3jn6QwJ+fpfxVEubigtda2wsUuqT1qU\nRDIE\r\n=7UNf\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"716a13d07e9ab7b6e2ff4f1adcdfea0a8d92d60a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.51","@balena/jellyfish-logger":"^3.0.26","@balena/jellyfish-metrics":"^1.0.286","@balena/jellyfish-environment":"^4.3.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.0","depcheck":"^1.4.1","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.7.0","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.4-renovate-balena-jellyfish-environment-4-x-716a13d07e9ab7b6e2ff4f1adcdfea0a8d92d60a_1624559745342_0.8106154050069494","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.4-renovate-major-external-major-003f6abe5f6939858c146057c0c1ae4fdfe45ddf":{"name":"@balena/jellyfish-core","version":"4.0.4-renovate-major-external-major-003f6abe5f6939858c146057c0c1ae4fdfe45ddf","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.4-renovate-major-external-major-003f6abe5f6939858c146057c0c1ae4fdfe45ddf","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0c1ba54980ba3ddcf48a6d61ab3cb56334c74aa4","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.4-renovate-major-external-major-003f6abe5f6939858c146057c0c1ae4fdfe45ddf.tgz","fileCount":196,"integrity":"sha512-Vn3WmXKuyqzN2R1vWBrO1/ZG5LGZ8B0JI5kqoDEuDwqIySf37OmQPfcpRr5jsVbxnKnaZwAbWVjVUzFA4QEK9A==","signatures":[{"sig":"MEYCIQDRUUk8nfjX20dj3bVgl0hMsxUlH1e7CFncn29Hs9+OtgIhAK9ZG9D3s5cwNYm5Pfv9Oiya5C56bTDvqGuq5JU7rk1U","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":728216,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg1NFBCRA9TVsSAnZWagAAbycQAI8+wq+Ju1ANzPw1wfsa\n5bDKV/6u/PpHtFJ7qBqeKj01qCwvJ215AXq9dDFPPlF7k3bvCT7GOLgY1f3G\nKE8WyfUIFwTQTtlIhb6xIGdeGRKcUAsRqqOJfIZIPVRVMC6SFqJgmaZuscMt\nTaEIubeHQ4TgU2PSk9UheS5wdWo6vMHr0aDB+VVTd5LA6QxXBY3HxNwg8rxZ\n9T0wf6xbz7kIO17OAsI7+HImHFwkppu4k8GepAvCd8q9b3r8NXi56z76WGh7\nBoyZS2XApJzLDAsAmphQDh6gZh5hK9mz0qg+13I6DecUQ7J8JwLqvhswCv5W\nwb3T1sTWVHZqHgMhNh6s5YPIpeH+OQ5N/Cz0JAvV3y/ge/3H/Q/+NYVw6C5O\nnIKq7hwkdPf/a2kAc0tn5qftTBV3InUfd+aotVJDks7mVqxJM7n9DhjugA4l\nclbWJnhxkK1dSzuT5RrfZagg6YUOFm4qcd7OVMvBQfG1TZ6kxePamAdepkBE\nrObOLq6WhYT2DhiOUhuJz82+zBIxIL9HL+/RtHKcPCQGFIant10n+ha9/8e/\njiu5xJudtZnhJXkLX6WOTisXnXktUyMNapC9eAwh1JJhwFvQQq3FXwk+ywAx\nSSHbGv3JgUhLe1Y35eBx6GID44j52MhQBdsUWAZtW2/M/3mzNtJAmf+5L+He\n5mVx\r\n=+v82\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"003f6abe5f6939858c146057c0c1ae4fdfe45ddf","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.51","@balena/jellyfish-logger":"^3.0.26","@balena/jellyfish-metrics":"^1.0.286","@balena/jellyfish-environment":"^4.2.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.5","deplint":"^1.1.3","ts-jest":"^27.0.3","typedoc":"^0.21.0","depcheck":"^1.4.1","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.1","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.7.0","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.4-renovate-major-external-major-003f6abe5f6939858c146057c0c1ae4fdfe45ddf_1624559936995_0.5255444983679753","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.4":{"name":"@balena/jellyfish-core","version":"4.0.4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.4","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2849d0464fcd637462803e22ea888c32a716d51b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.4.tgz","fileCount":196,"integrity":"sha512-GYEJC7EJCeC3zNUmC3FL9H8OSXD0gKcsYJdVdH6aDGZDnQ+oNXO4rn5sGdj7ywFogmRkUZwWp6vIZT4A6BHL6g==","signatures":[{"sig":"MEQCIFVuphgDGRAkB+DMnOygj2IyMh1mgBOp+hC0Dop+EnT+AiBNIu+JQXN7/GeY7vSRyfodV3imXc953fdnq1BqtqrTbQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":728181,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg1Q6GCRA9TVsSAnZWagAADxcQAIRschN4MVjSnvRJg2el\nsMxXO4TR0jaQKHX5E3Y++ydWIgGQowdqA4gxAFUHqSZ7YNSTA1XwOiIXhLZ8\nNdGBqLsARognPwjs/fORHbI/LP7pLoM8JtJvWyU8A/7Fr6PwOsvgykukIU4N\nLvDeNYi7mz/I5iLpr0qLNDd9iItJGpxMAqEL/B13sFkGKfPi+K3hoG3Ky2Ic\n73MoYy9IznIla0yk0Un1OapBDaj2+4PAOcA/10USH+/LNmsHJM/Mtd/wP4xE\nytaGJR1TGLP2po0xA/pb42818S7iVaH3ekop1s4FT9qFPI46Ew6dVqizhO/U\nbitD+6bbVYYip9t7ewY0f3j6WT/KMq/5KkeSXaBt1plNEx6vuwYSwZxiK1vc\n2Xoyk1VbzaBD1LU52V/LYx0QkHJvAcuAhCtyt6GPOW8E7fAu1W09qi4Xal4S\nh4mBTEGvPYLWo2KJ8Cdz2QjA184V7Ww0FI9S1vyCe0RIdZo0aV4qiX6qzGxC\n8hOdk3S71rGJtI8tnyIv8/9RSIzke8nPgY28tQXhWFAQQKQD+Hs94LsKymNA\nWynWqFbPFX4z/1VvtCO0IJqEAMtIJozwCkRKuG0Pqn3vbo1xhqPJgUcGYO2D\n2pwh/Gz/2qh3vR11Y4cDjh1v8Txg/NpqN8A9zEZIh0wuvFmE4Iu9GkUcjUv9\nFlMc\r\n=A89V\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"27771dead43e9368479ab7f1f972a786dc250539","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.51","@balena/jellyfish-logger":"^3.0.26","@balena/jellyfish-metrics":"^1.0.286","@balena/jellyfish-environment":"^4.3.0"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.0","depcheck":"^1.4.1","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.7.0","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.4_1624575621526_0.6395726567043145","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.5-renovate-major-external-major-4ee1c293b8ae67bc8a7426e2b34e9512fe28bd15":{"name":"@balena/jellyfish-core","version":"4.0.5-renovate-major-external-major-4ee1c293b8ae67bc8a7426e2b34e9512fe28bd15","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.5-renovate-major-external-major-4ee1c293b8ae67bc8a7426e2b34e9512fe28bd15","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"13862b72135446f53825a61ee6c57454bfb66e31","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.5-renovate-major-external-major-4ee1c293b8ae67bc8a7426e2b34e9512fe28bd15.tgz","fileCount":196,"integrity":"sha512-hrJOAm6d2Z/om6xe6ek/kN518WGkBY7D7hdvNKPyD/Nz49n7gtq24LMFIU1oze71SdeoVoWIZndOQ2XZDECi6g==","signatures":[{"sig":"MEUCIDBiwXEszHqLahigiYCfO/rRe0btUY8FHL6vbkbBSDYpAiEA5y9UXtWnh5zxY7VCmdlkF2oSibnLMUz5wOcQWaTZHrg=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":728322,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg1Q/jCRA9TVsSAnZWagAAZfQP/20ozUUFEI3u5sTspka3\nh8j4zo92ePA9wEOCE4iyskzDotlYAZbEEKaZnABH/5FjFyt3olwH+5s5/d3O\nrlN83h1a9z5zRVvG5C0gItlaP6VW80Q4y9E3AShi23RWWbmLda3L1IS3toDF\nrMWfjh73JHXsiqBUDOoOlau2RMbcBIzNj8qxkBgcAEp0ITuNi+hVpy1YbKgD\nFufuxLQ8ViInDAiNDouyuq+vKsli9NHyKMib2CFXrgHZIa6cdZBmxsECt56t\nhGgFhs9f5jIkuvvwLnvyzShypLsLViWMFxCy/URc0rqWtP9gBR2GXNRdI+ye\n8Lpbxu0sr40FffeQ4sA7heIdU7gOL6iphVFZBFAS15Hqke1fpJKdeSLFCuz2\ngeaJ53n8nyjkTeElvc3zBhH3SVrwjhezMLj35YTW1RoHarcVwNB+hGfODOBc\nujPwl2REB7gnGD4jwscmUW44H+FWprDlaexP04GFdgUZJ2dt9574Z2p/ltUR\nCysjwvIfN1IYOhF0QBCxk3KYkvYHJ0zMNbUSUmF9LzNgdj3qvMGJn7xgSnp5\nkUi/IAUpZFNej3Pq97sba+HRCun2eWlAP8LbYKaVyzOjVhvja1/afXh+F075\nVLEJvc0rYvONrG0YEL2pKgfmlf23mB6j8C3ZYTzYRAVYoNerFlhWssgFaSZi\nD6i+\r\n=/AeN\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"4ee1c293b8ae67bc8a7426e2b34e9512fe28bd15","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.51","@balena/jellyfish-logger":"^3.0.26","@balena/jellyfish-metrics":"^1.0.286","@balena/jellyfish-environment":"^4.3.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.5","deplint":"^1.1.3","ts-jest":"^27.0.3","typedoc":"^0.21.0","depcheck":"^1.4.1","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.1","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.7.0","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.5-renovate-major-external-major-4ee1c293b8ae67bc8a7426e2b34e9512fe28bd15_1624575970976_0.13508501480849344","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.5-renovate-internal-patch-db25471665ce1467a5ffa62f10ff85844ccdad35":{"name":"@balena/jellyfish-core","version":"4.0.5-renovate-internal-patch-db25471665ce1467a5ffa62f10ff85844ccdad35","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.5-renovate-internal-patch-db25471665ce1467a5ffa62f10ff85844ccdad35","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"84524672dff9074fde77e6967ba33920f2ca0e17","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.5-renovate-internal-patch-db25471665ce1467a5ffa62f10ff85844ccdad35.tgz","fileCount":196,"integrity":"sha512-0I4ZWqqJbo573aEMGYkoPa9wsJ7PH5K9Pqe4Wtmq5S1MAOPHaIc11hfSw+X9Zn6LF0yviPBAKX4jkqK6nVcFdg==","signatures":[{"sig":"MEYCIQDQooEBNA+P/Dn8MRTynLvVEgnkfeqUWeANP5TN5kd4iAIhANOH8TkxLLA96ZQTBsSUS4EMpfr2gCChHlHg8//ud+q/","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":728352,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg1TTKCRA9TVsSAnZWagAAXfEP/iBAS/e43/5tgp4Ts0+a\nOvSJqw20tMBc41kV2uXKzmcfdHPeEc9epddg5rXJ7j5cg+aVIAt7Kp/cywbu\npPkqLl4YTLtHOEjhO4BNdnu22N/P/E06a2oa0k6RlKOkBCjGHHjiTXsHVoKE\nhJE2tDAwDMEDmLo2Wu9UgtDQlzP1NG+0/XvR2N8lHnLcAN0Vbe2UHbhozf0B\nM9DYyXu4OSUk3rIOk7o9x3Wckl4Mo2LsC7KYhMaP2dkyG79uTXwkDZCk+D8X\nYqwi77jqHMzsgKpW4yg2SRbXz3wQqzLDg2Y4q+OobZ8fSWFPNW4fe6Iz2N7T\nkPunQVksLWmVNbhjDwH7BpR+kDCJ0WQrPX9MkclJPZ2Sv7niZbK/PIJWq+KC\nvbWhwcoWVO96243Mb8CbkTqrQWrkv9IV1FoxlvSH1z5VYNdVGBAPG7rznY37\nLJSn9zz1NpJyVDRpD3+iqL+AwtQeZKiFmtgPcbeOIsBI50qG2IxomqStaLMa\nYeu/72qPjHgKO56eRYmuajnTwGXA/7ZDC2oHbCvPy87kL3wJt1zX132TOFgi\n6+a0N/s0m5vtDyUTYucvPnTmpoxSOiCEfbg5+aRKO9fpQQ8pHv1TqCfrgwc3\naVmRlGbZk8zsTrLNtC9EE2W7du3cV9tTRD44TvxiE5Un+tWbVrmQjMfq29Gy\nj26H\r\n=qyfe\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"db25471665ce1467a5ffa62f10ff85844ccdad35","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.51","@balena/jellyfish-logger":"^3.0.26","@balena/jellyfish-metrics":"^1.0.286","@balena/jellyfish-environment":"^4.3.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.0","depcheck":"^1.4.1","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.7.0","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.5-renovate-internal-patch-db25471665ce1467a5ffa62f10ff85844ccdad35_1624585417717_0.10977701254474703","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.5":{"name":"@balena/jellyfish-core","version":"4.0.5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.5","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d30b3100bdd094dac5f6924659380e6c12592737","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.5.tgz","fileCount":196,"integrity":"sha512-e5D3i6eu6D3pgcM7UxLYviniyVJxb8k616SS5pkv6ArSYKOPcvieguMj64/sqOXBwaOjCHXiQI0Iqu0S5BvnNQ==","signatures":[{"sig":"MEQCIEyPBKVMA++CeZ6h66Bp50acMLpbNqD8CemRhvmMR4vvAiAfVAc40/okH7MRsrfdk8NR8FIH26QQneGofmbdjnLyxQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":728287,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg1TXgCRA9TVsSAnZWagAA8mcQAJi3wWzIf2Po5frV2q65\niWLctdTSP0whGwMFt8r4O+msTzbYyupcIodFJLC7zphFln444MX5ebp3Jy6+\nm/9GDfS+zDHY/2tIA/Q98VTLbcLCyeByCpg9RnwSEDIUUgy5d9yI/EyZAU01\n3xbiZWfZht8uQ/zQOblaFmiEGSjZ75nOvMM4KXZN861cIqaFBfm192oB51ZO\n6f1CXqYEE9eixpqhAI5nln8Il/cFZb799OrnjaqBuOr9gsgTL3SlhqZOChVx\nls7El4LBTpCR2Gik3QSmhF6503AhlfGXxvvCSXp53l3QutYu49JLLKKfbKKV\ncPjDJfG/vG6TzTdyfBtO4EwkUkFbB7bElCaYg0jj7sdsmHeNKqYHrLVpOM9k\nauQ7H54oe+ygeEECH3tGHjeI8vCb8LwR0oXYJhbFNvM5Cr4Oujfb2Owmm1fe\nhfwcRVdEAzvu7xaxr0uNCRP0WdK7XQbZlh1w7XxjyFNgpvZs55fNVw20Dojc\nxDVFHdQGvyhHUYrnTobPVcRrv3sHPkRt/RWemvCFK2KsZbHhPCg1iXp/FhR7\n/sSG24mVa3w61ObzDm1dktLrPyHlxB4DHTKlktnqWRteggVlEf/MU66Z7Ips\nHMUqXPjqQ29GAPSAypRRgr23oZ+nBVMg9TGB+AmOn6ApiYc5Y8VEikMtRSOW\nsI25\r\n=QfvI\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"fdbf971921f712e0f148404ddc614a1e9e723b1a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.51","@balena/jellyfish-logger":"^3.0.26","@balena/jellyfish-metrics":"^1.0.286","@balena/jellyfish-environment":"^4.3.1"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.0","depcheck":"^1.4.1","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.7.0","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.5_1624585696001_0.014688766911011841","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.6-renovate-major-external-major-b608af4aa6b8d48fa2f859909c7998fda0e8f607":{"name":"@balena/jellyfish-core","version":"4.0.6-renovate-major-external-major-b608af4aa6b8d48fa2f859909c7998fda0e8f607","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.6-renovate-major-external-major-b608af4aa6b8d48fa2f859909c7998fda0e8f607","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1a3f9af63ec60813f62198fdb374eb0a0ec47210","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.6-renovate-major-external-major-b608af4aa6b8d48fa2f859909c7998fda0e8f607.tgz","fileCount":196,"integrity":"sha512-oBN8hWFBz3OIhVLTHjyWFyThcn4WW0W/qPsWt3vgbPpY9nvY6uyZmKNHjMi+crEIaUQRFTajMyRXZ+70qDcRpA==","signatures":[{"sig":"MEYCIQCPNVnTTqHC7FmBAhUCxKEkCws81MW0ypQNWBsuSApFUgIhAOdoRW7cvFZFtHw0QrdDwffiHQFfsIPOkyCwj8yHvV6U","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":728428,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg1TdLCRA9TVsSAnZWagAAbJEQAIGD1LzUMYGFLVhVn4Sl\nquiJu6x1DjuOlWOvGsvFS+MGnDbmm3epC+1htwsqcimOJj07nxgKaMumh27h\nLvx++tzzWq4AcZZBtAdz4pXjArQUt3V+ccPuaylD1o9va8OMDn9tqGd4x8yL\nXRj0/Ly2q0S+EoegLwX4W0GhpSdQ4O7yurUkRDjSK9ACs5E0c8VVK1y0MN2w\n1veXIr46Yx/1UeJ+RIQsyBA1vVTcuuLpqIVIt8N/MNpDjnzswLe0D2Om4ftn\n1R25xLKJfqfB72LrYJ3BCl4jtIQD/vtZQvxVZ7yvxQ5v67PeGujb5TuLFJpK\nqdCssMutcxYDMHijMlzq3Wu4obIePRPyXOWLKtVOnGY5vewUP77uNDgM0Nvl\ne2nOqOx0SlvukCq63sQPNRB0+yEP+am1hcPKN6nOkQkbQOXKF0bBm3OUrNxk\nAb+tNTrNq4rU2eURaH5+Ca2N+sfZ/+yUSstOduvaUrPwpPTJHda+It3rqxNs\njAOc565M311thyZ+pq3XqwUTtDtBlXDi/4NzmDIK60qWqXDjU673cHc2jvR0\nDt4Pp0gpnq2V6Y2ZLQHH+z7zV/7eGeK2KdABj2JjB28cpKBMCIS1TO3CiOBV\nXlacN3iuYdTg9JKPPmnu9tSMNXN9kHvZdAeNBeDp3TaCMRirbetE27ixUgmp\nJS+p\r\n=PPt1\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"b608af4aa6b8d48fa2f859909c7998fda0e8f607","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.51","@balena/jellyfish-logger":"^3.0.26","@balena/jellyfish-metrics":"^1.0.286","@balena/jellyfish-environment":"^4.3.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.5","deplint":"^1.1.3","ts-jest":"^27.0.3","typedoc":"^0.21.0","depcheck":"^1.4.1","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.1","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.7.0","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.6-renovate-major-external-major-b608af4aa6b8d48fa2f859909c7998fda0e8f607_1624586059439_0.8353040840284358","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.6-renovate-internal-patch-79756a154a4b859e0b9f181b19dab4ac82f030f8":{"name":"@balena/jellyfish-core","version":"4.0.6-renovate-internal-patch-79756a154a4b859e0b9f181b19dab4ac82f030f8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.6-renovate-internal-patch-79756a154a4b859e0b9f181b19dab4ac82f030f8","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ce2c5065c7401a7a6eb5eb7d7ec33bf2ca99b4c0","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.6-renovate-internal-patch-79756a154a4b859e0b9f181b19dab4ac82f030f8.tgz","fileCount":196,"integrity":"sha512-fTioV3rW9p+1X3dFpSxlntVvgaw072ZbXfvoGu2/FMFAbNIyDwkiPc21IIQqC02Xs1uM3GGvhYOl8kOUwkywZw==","signatures":[{"sig":"MEQCIAiAOG3/JFaieizuJ5puBJ49fIXNZk44mvOz1NHyxIZsAiBHxgNch9Krs+ORZfL9tTXJVF2xk8qgoZtkEXja2N74lg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":728454,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg1UGfCRA9TVsSAnZWagAAheIP/3evEpIQ7pS2ModQfXZq\nD6s+jmZyy3xifTRdNjfIADFBmPC2UGUuTDz1OizD/OENnf4EOqI0s43he1hd\n6EbvyM0vMTxaroAaQkRMkwqLnvk6dGrCtxacmmdFeUlXpSd72QGuosSLI4Fk\n1g4B2JOEA1xoNW/6vQ+iUSENhGo1/YR6Fjb86b7WdIZVNkwEdItMZgkSqqbR\nloMrrVRzwptX5LDBP7EpXXQgL+URgX83jxtzcR4BSITP9LT+cTfKAbbZ88K4\n5xz1V6uDzZ/aRwhJxNS8hdrW7j8z77bQFMmKIf0UMApO5ljWm+gC1C6wDqtI\ndvUIPYexfKYDRR8vgXJE5w8HGWqhsSFHW3sJLTV2I4UO25Y+oYoGnAtwtEuG\n2wlp0rBndSEZ7B4IC8vB3PpS8Hj/6KU2lQXmiTMlP6SweYUUmubhVqnWUYAV\nZnkrSRwGwlbRN+XSqn2cbP0Ce4CfR6b+UJmT7nrRnFDWEcSacmLe8S6LWl2B\ny8DyezAt/FRDRAs2uMEfI3ynrXdYzAAwf3cMBDJItYD2kt5maKjHUZnPh6Vr\ngfG85zNaMq2oU+akfeH43Zve047rGDdDMtJ7MMjwXuUVBlf5UWIUppEULvl7\nfR5uJJCCWLCTgSGz+f+8sLfdq+xAtx07ntJ33FHrENfJ9HIYeslj4PHCqr5U\ngYaV\r\n=4OIT\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"79756a154a4b859e0b9f181b19dab4ac82f030f8","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.51","@balena/jellyfish-logger":"^3.0.27","@balena/jellyfish-metrics":"^1.0.286","@balena/jellyfish-environment":"^4.3.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.0","depcheck":"^1.4.1","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.7.0","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.6-renovate-internal-patch-79756a154a4b859e0b9f181b19dab4ac82f030f8_1624588703235_0.3620649277486927","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.6":{"name":"@balena/jellyfish-core","version":"4.0.6","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.6","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3f8ff33b6fa70348eb410dd8ab178bc803c881f4","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.6.tgz","fileCount":196,"integrity":"sha512-rbAOJA8awXK88RImjrg7tdBJejGKnkyr91+GFnfjfJet3+8GmbuHPNsGyiAq4G5cCmeo8UhK9sffbCzeKQWMuA==","signatures":[{"sig":"MEUCIQDrmdI1WnwN9Ho9sy4a1IpPyaH+58AFfFOdogRecX3NLQIgA8f5sSq33jlz6CI5qs2nVWjECqvvfBAfQO/AoYB15cI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":728389,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg1UNBCRA9TVsSAnZWagAAh7AP/jpZzKnmxej4ukIcixzm\nySAJkVfGWwuXe1oILTeqwUHI8o/UzcDhGhwDxn6y6EDncaOt3/DyW3Cuz/N3\nF68JiCt/11OVcx9zB1gJ5ixT9QV0tD8WbFSf7H+sRIohR2BuBuu2zaxTPDoP\n9LO6i36NdHc4dcW3oVNp7A+tbhG+YPx0UjUqobV/gyqPSEBwneBi2Sa2NHFs\nNZqTZhXksnAyiBmW+mfUr5GpBDtjEoMxGE3LkL6WoW16vXTYK9V8yLZ/KDUl\nnkMmFDkdJzr10ygyagCkFLdxiGIiIphHpcf+sYVdGJ0OCOBd0ljwHj42WB5L\nkBtwJzRIl3j+SP/vFwLMQwGklI9TBjGa7hbTtx3mtqQCBcp1HeaBO/8fe+QF\nedArEt7XwRA4A0Ha56NXBjOXlbzaYrxkIffgdHonYYDw9ZMTvv0TK7116z6y\nv3rC5Go+YzXwtkJTDH0TxfdL4MQ63ZFKmgwPHcQQF46fwMaziJvLd4A2Yxvv\nnSteqYSyqt7VFt2/38+KVOE1O978leQK3IODdyp4X1b4oDsWHurMDqZkEJcj\ney1K+bwCcc67Q1wn4kGltkgHHPmt7TgENy4uNLvkGa3YMwYzwIhS7gPfmHZH\nGikcaQIv4Az1L3zKR5BNKjUQbpicwsO8S9HjF89DoB4sSaHzqDeeIZfOx2O0\ntd48\r\n=tzfP\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"bd8268e3e627c5adaff8a97a305f94009cfff27a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.51","@balena/jellyfish-logger":"^3.0.27","@balena/jellyfish-metrics":"^1.0.286","@balena/jellyfish-environment":"^4.3.1"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.0","depcheck":"^1.4.1","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.7.0","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.6_1624589120587_0.6259789878935669","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.7-renovate-major-external-major-a6c86ac1a6fc1f65bbe68bcdbf7d98d2f8ccae78":{"name":"@balena/jellyfish-core","version":"4.0.7-renovate-major-external-major-a6c86ac1a6fc1f65bbe68bcdbf7d98d2f8ccae78","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.7-renovate-major-external-major-a6c86ac1a6fc1f65bbe68bcdbf7d98d2f8ccae78","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"fe686315b09fa4b04db1926b3224b7a365b6494e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.7-renovate-major-external-major-a6c86ac1a6fc1f65bbe68bcdbf7d98d2f8ccae78.tgz","fileCount":196,"integrity":"sha512-ewZNxR6Ne+PS28in3JloqkBRSL95XWGrXWeCwL+1qSiADSgsfA+Nzn6o+dEhRgSVki1Uxczyj24WaMlLHCKUlw==","signatures":[{"sig":"MEUCIFmqpVtKrZnkmdNWztcBJxHugz8GEpc6y/2fCxsjn00mAiEA1piR/zj6wft92Y2tcXxcvsur/ofCUQMaBRNlR1OjPDM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":728530,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg1URCCRA9TVsSAnZWagAAzSEQAILtczBvfMOn26CX4y1X\nC8dKCTGmLXF3veXAMotuDyfNEa2q9oErCpvjO+5hFQkvq3qTOg8Q13XmCDco\nQkWtsUHCMtxxzKV5pXvaGuxq3UH3Twt8lRkIBb8qjCRzSMyu9JIeWBnd1LjQ\nYfeEtDUfWxonE64XBvJpS+lqVEvphUIBwYbKVIzm37LsIayhj5Keu73Q1MRH\nM2fXaJcECSJY7n7nU5LJaoKyWnklHoI5GZ+GOvi0dnp7fhqVxX6u2CVT6FJR\nL+1X28DpV0zT276lUsJza2iil58EYnDfOniPwSuFl27XpVmGa4LrM2K87lzG\nX0F0y11L1uSINaeWwypO/kJhI7JFIGmxUeMwasZqLz44fA2MS3eemcGEwR24\nqrMN1CJNkdqdfpS1R1jGZvuCcWWJEnFhGFjST1puQJXLtbgtijeZ7S6UTz+c\nnoI5ZfuHrb6zkZFQFVE9DcDLXGq7ZuzOwacCxcTJCb3Pd9AA38oy2Bw22hK8\nMdksAHli8ibXEsZ3thBzgosbz2ooeu8AVdp2IaQawSR8nyftTtW15dQ1BEuC\n7nw35D0xNIpWg/ClzUNF1GoJRMIO9HlEJRH2IbFMYPMTsjnq9RMYJFYLfuC3\n2eJSdifjy5d4CQAA9EX/pn4Uam3k+7GuuxuZzSVS583nXGQfJmBPtI+Px/wp\n+l/8\r\n=1eM/\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"a6c86ac1a6fc1f65bbe68bcdbf7d98d2f8ccae78","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.51","@balena/jellyfish-logger":"^3.0.27","@balena/jellyfish-metrics":"^1.0.286","@balena/jellyfish-environment":"^4.3.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.5","deplint":"^1.1.3","ts-jest":"^27.0.3","typedoc":"^0.21.0","depcheck":"^1.4.1","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.1","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.7.0","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.7-renovate-major-external-major-a6c86ac1a6fc1f65bbe68bcdbf7d98d2f8ccae78_1624589377967_0.2744313766986517","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.7-enhanced-type-generics-7dae7cc67bcbd478c2b9c0bf33731416651eff4a":{"name":"@balena/jellyfish-core","version":"4.0.7-enhanced-type-generics-7dae7cc67bcbd478c2b9c0bf33731416651eff4a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.7-enhanced-type-generics-7dae7cc67bcbd478c2b9c0bf33731416651eff4a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"530fecf213af6109127ba29b2b66265266f0c1ef","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.7-enhanced-type-generics-7dae7cc67bcbd478c2b9c0bf33731416651eff4a.tgz","fileCount":196,"integrity":"sha512-PJRA70ERCdFsTWL1CgpKXgLlWk0xO580cxQ9LV8aMb2SHn4/Mu8JfGE9Jq3qSx4twz4JqHJkKvBaZyj1eLX4+g==","signatures":[{"sig":"MEYCIQDc+KPVNbXSofbb6OrBH8CFv8qON+NwyHNkRfXvonO+iQIhAPL19oKJetjuqjNGSKeeDBg7QLdgPtZoBvGSA8gwQxMN","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":727836,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg1Zm3CRA9TVsSAnZWagAADyIP/0VECHjvMCSl76EtJb3w\n3+VVieMbUr2OfjumFsMaMzn1EmR2vz92GqjlOQthfDBoKE9EgdEXLrD/7K1s\nGrMCRTzFNMuktl7/pLRwLQU/L443bKb8P+N/ckkCN61ECjtb26nN5ydXZ0Hn\narPNnlc+S/cRhpgvju+DHYmHsKVrenhqTDZVb7Ddtcc5imitvWGq4bv+ddZN\nuzTAS1X3qLjEisyvUk3Z9Ee89eFEF7ccoOWULCFKH1RL4EmAr6SDgpOTXlTV\nyyQ3zk6SVVBYoo76Xv/n7/R55ACo3wBFHeWznp8YQWW0mo6N/weAkQ6kCs3M\ntcMuySDC8L/dI0jyKBB3QzMB+mxvFvoyjAKNocCki18sLf2dKCjvL9r3Ct7d\nyand12Bcm/GM/qqxDdQxTGyFHrGn8ohPSQPNbaK4k6Sib0ysvasSoy27sMB9\n8uVD5NTAFgjutjivLnNjBQ1RtGeF1IznfsUU0v4eDWdD+EcMNLjVqZ2ah0/m\nt0+XMS50OlUH/5/JS+olCzbcvEJKIdmo/c6AbEBjYbWjp3aN7MEb1um+mqOj\nL9FIXPRDnf89rf+HJN06cY7Edt3HorDo8f6DtaFZ189E2Euox9AxlIttAbHf\nSDH/UQRSMI1cuJ09bMYniisiZH8ZXzE/Wu2+fOZCYLFvRJNbNnJRxVUn97Hq\nxnXA\r\n=aL2J\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"7dae7cc67bcbd478c2b9c0bf33731416651eff4a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.51","@balena/jellyfish-logger":"^3.0.27","@balena/jellyfish-metrics":"^1.0.286","@balena/jellyfish-environment":"^4.3.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.0","depcheck":"^1.4.1","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.7.0","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.7-enhanced-type-generics-7dae7cc67bcbd478c2b9c0bf33731416651eff4a_1624611254652_0.5954868804725231","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.7-enhanced-type-generics-bdc4abfc8579cf1edf14bb8d83beae5f3c731806":{"name":"@balena/jellyfish-core","version":"4.0.7-enhanced-type-generics-bdc4abfc8579cf1edf14bb8d83beae5f3c731806","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.7-enhanced-type-generics-bdc4abfc8579cf1edf14bb8d83beae5f3c731806","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5466861bdc6a29bfca0800c748de9309985e81bb","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.7-enhanced-type-generics-bdc4abfc8579cf1edf14bb8d83beae5f3c731806.tgz","fileCount":196,"integrity":"sha512-crQ+YLoxv16ammXO457rM0xvk7GTupUCsXm6DelK+YJHtpuKeckZutPoPBQQhkqqbsl/yu6UcsMQs6x6hczqSQ==","signatures":[{"sig":"MEUCIQDEWVp5PntMlEaP6cSfbK7vCASNrjtU2C1UmfARH+ow9wIgV3b73ZYLNdfot2Y/1mRultP/qNEDaJLtjsllT5tSaXg=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":727836,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg1Z1+CRA9TVsSAnZWagAAoTwP/iYTTwAkgrbZZYOb3oH6\nRsGxrpKbOSbmwdSzZY7KnzUtVTFvw/mVM/uf0/2bPTXvBoEvu38y+rK4V6K7\nEmXUg7wKmvQx/ZP7ariQtKv9uvOqr5ZqoKMBcHdZdjBHczZYKX1Ah4gThbU/\nMw0v2DSt1Q1x7hOlGqAaGvdV+1/r125bW59AcyM8Ny9Y80GLHpVmYxnNLm/T\nZI4G2pfYEOBLLHOe1SLqUQeCmRfJFJttgjcA1iSJUZEBiPZbFbqOUFIKUvTp\nM9hjowVWeFHcOD6/J/ZlwiURQ8rpNWqmGiTE3wdrvgzyzf6LEFnKSolojDpf\nzRhqqp6UfqhdzVFwsoh2X3NOU2q//iKxqmzjGl+ww0iSK15uX8vusklfGnMV\nJbrISWc1hCan0kPAfI2YWqj4koOfPcSw66i0GpxLz3Pp5aJcs7GK/CL904SB\nlAVXGZZLeNeXJKRZz8108+DRh7RB1FVUmS/PdKe1uovRh2eLMGCjzvFknu4C\nmYrDCFk5QeOhz2Eg3S45hTIcJoTCZV7lrucv+XutzrFR1Q6nKTaBpuJhIvhh\ndMmoYtJ4VdeIB++yUpeokaJwyak4jPc+sq2RjN+XfR91ixh1ZjD9YTZxRyJV\n9IOAp5wgHYhXv+zfDacDb/CmRNjEZr7Lko6A5KwNpGgvK5Uh201LQoHsQrPn\n2Wf6\r\n=VGb5\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"bdc4abfc8579cf1edf14bb8d83beae5f3c731806","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.51","@balena/jellyfish-logger":"^3.0.27","@balena/jellyfish-metrics":"^1.0.286","@balena/jellyfish-environment":"^4.3.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.0","depcheck":"^1.4.1","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.7.0","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.7-enhanced-type-generics-bdc4abfc8579cf1edf14bb8d83beae5f3c731806_1624612221832_0.003331212958742169","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.7":{"name":"@balena/jellyfish-core","version":"4.0.7","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.7","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"54229bd18cd02e31c1fd694e690987dbec7d1cfe","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.7.tgz","fileCount":196,"integrity":"sha512-G6Rqh6y5F42ra355GA0LNTtpK64EwYCEJmqtbCFgYmYUMc0R21Fdx71ozuY1C8YnXOPUSD8mZKF6aHifMfPreA==","signatures":[{"sig":"MEYCIQDLiepAxMsTstqNPDTqeIWHolERU8un2zVPV9QZzEYhagIhAJOG6R3vFG9AyouVKrygFOnrayRm6lD+/9bL8DRpYIqi","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":727772,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg1Z/JCRA9TVsSAnZWagAAkb8P/igvalPMyI7G9KxUg/aG\nM6mQi/Atq3ZxTJmC65I+0qM/eehhJmKDFh5+GGPFi1rpLAKB/rVWhQE9ovKL\nci3ZAykIOASpfUBq1k3/nm1JIzFi/ER1tnVHdzBZJFFLAtW528dby3Rx1qjf\nylIfdI8bOSyqCazCXoEePoJE1IwpPZrJQWBGKD8WM/xh56KA3RAMolnc7mKx\nYYeuWoNCGioIY5oCddrnDWJqavalH7e7mek0jSbiL/GqC1Dc7rNm77Rkq6X8\nZ3mQUJQclwwpO7woLkN6j5jOSTT3cL3RYYlMGLsmAFVK1tpw3A+J8b7O9fTr\nELYntkhc0UGXOLPDhAWO21hwwKOOP6r9PfYyqOj/zaR5RNWzVs2SPxVw3wVI\nCXwzC5MvOaLBlkuoKOYrrICF12uRl5v/+q+UnK7CLpznjtHPxo8J1Ynsy6ap\n3qPx/2JO6lYquqi8jB2pYKbZ9SmSNalp1Qkz0LATb4dn6vOfAvCfrJQjgjVP\nH1IbMh0fs+4WXRwtwitLEgD2G+AZCrDVpF0y9a7//KtFRihPtemWZhskKvLh\niYj8kgKZxx3g0mrJ5tS0Am4XMZ5Suv5sOwOhI2mualAY85gRDUP9VqTXxccI\nA/aOIX3E0bmG+WD1+PSeikEH4Drt0JwE6Lq7T0d1AM2nDsIQlrPWyQUfdmYT\nkuwU\r\n=AdN8\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"82654eac8af222b13fbf8582371460bbbbcf1d6c","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.51","@balena/jellyfish-logger":"^3.0.27","@balena/jellyfish-metrics":"^1.0.286","@balena/jellyfish-environment":"^4.3.1"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.0","depcheck":"^1.4.1","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.7.0","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.7_1624612808922_0.7904014316889343","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.8-renovate-major-external-major-a3665e6417a4a002c7387bab779e073c28e82396":{"name":"@balena/jellyfish-core","version":"4.0.8-renovate-major-external-major-a3665e6417a4a002c7387bab779e073c28e82396","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.8-renovate-major-external-major-a3665e6417a4a002c7387bab779e073c28e82396","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f809b9f29dca080b864d982557f83f609e55b4a4","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.8-renovate-major-external-major-a3665e6417a4a002c7387bab779e073c28e82396.tgz","fileCount":196,"integrity":"sha512-zxf7+wmAAKiXY4eq2ze9OgvMeXamX47ppXDykzhAOo9ImgeXNBBcJLerup4TcOOh467ibDPBk51fqti5QqZ5KA==","signatures":[{"sig":"MEUCIQCoDoDtBhYJPJ3KYR2q+/Zv7PgJauAuKtnovqzc33kIdgIgMuRaOfTkIn3y9uoBHm7KOmS+sXkxMDCPKMdwJSdXeMw=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":727913,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg1aF/CRA9TVsSAnZWagAA8EoQAJjzljorUAUDlwiR8UNU\n9Wh/+KivlLw5dZWIBTGmUtOiUFnauNm3oDMlWlapWo1rcmfc46O51OkqgL6Z\nDTSUSXqIGYWXUsvfVE8jZzZLTsfsawPOALr06qtRVgXYPKKpXCcioirElf19\nMlnSP0NlSmbC8mdnne8Ore+10aT7FJ69XVzxWp4CAR0rPJrSu0fK+UkXKAGi\nR4X6gBAzuVvE44thhMsPdbUCxngPcN+Nbwq3pHQxd1eUaxQ9tvy8GVVqPlvi\nvgL2Yy/z9VE23EFetwMfw2yerH2yomDAb7cg/9bG52nLBqAuKJJ0ZVPYh44O\nVSTpFTZecR3AyOscMP57zh3xkhlliAcERFIkoKvykYfRlwIwlgkd9i5igaki\nuXQw6lAhIKE0hNyzCcKE/NsXSm3Gua7klkmHD40Byw6l2RigFQTI8rrXEpe5\nAZ8gwweGYCynp7WMotnNkuMnOne8HN8ts/EtNOykqU5Ecjhl4LGCqIUC0bpN\npu7a5/fdhBv2ruei0nSQTWty+RoKv93LeZ35BLKAl/zWvjC3TVSVZtdSrpz2\n5J7RP7I5Az9t55tpJuasF6S5k3pwSjyqQjhY90FoKKpLZYPND1c66aPmd7Ib\nvTxCB1+cSGg5gAk0IuhJoAt8QdSBcCAQzYjZPSdNyOfbWpJbLSmxA5feBDlt\n3vF7\r\n=uhlQ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"a3665e6417a4a002c7387bab779e073c28e82396","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.51","@balena/jellyfish-logger":"^3.0.27","@balena/jellyfish-metrics":"^1.0.286","@balena/jellyfish-environment":"^4.3.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.5","deplint":"^1.1.3","ts-jest":"^27.0.3","typedoc":"^0.21.0","depcheck":"^1.4.1","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.1","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.7.0","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.8-renovate-major-external-major-a3665e6417a4a002c7387bab779e073c28e82396_1624613246943_0.3998405640601199","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.8-renovate-internal-patch-885f0f579567d1552ffa6dddfaf95aeaca09cb5f":{"name":"@balena/jellyfish-core","version":"4.0.8-renovate-internal-patch-885f0f579567d1552ffa6dddfaf95aeaca09cb5f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.8-renovate-internal-patch-885f0f579567d1552ffa6dddfaf95aeaca09cb5f","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"548a6f9fd7d1f36e3891b33fb0caba46a50c9c79","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.8-renovate-internal-patch-885f0f579567d1552ffa6dddfaf95aeaca09cb5f.tgz","fileCount":196,"integrity":"sha512-nLwWxOaa7BZuibdyuBkar2mXEgH4O78sPkH26fJJlJs/sExO5WEI0Wlar1CGs+rrFbt+AswWGrqpLk86WwN6NA==","signatures":[{"sig":"MEYCIQDcuarvvAn1ZPNeRyBdk6iA7VnxTSPnGllCWytswQTglgIhAPr2w8VoTiJX5aG+A6couWTBZdzEEJSQRQkzpNbn8nir","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":727943,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg1l4mCRA9TVsSAnZWagAA0ysP/iZswQjF2ZqCexUsHzeS\nlvYWmZ1+5Wn/OC923Cm01c1X0xl+skDmGU2QoQG/OwawMrFpjEoeUlWVbaor\nwp5sDwvbphiJV7uqI3o5esTJucmMLmMVqY7HxUUyrx7PEGnJ4qVFFgJxO99z\nPsAJj89Y+SGklDmHHVsdPbFroHnHD6jUqyGwE4GxL+iDoxSSJ1TprcjRKIvf\n2zAu8XS7I8ICog9ecUdFlFqfmpZAzrBwtWhRUbaPrGqvaRSzcsO1VD0oo+Ox\niJsZq+qw1JP6EjE7P+UrXZaxw0UsiPpq4r+/Lwy3X9xHyri9lvTIrwJu3JZu\naRe1W0GHQzaYiw+eNnlTOu8dD850fyDkCXsEMQ89JAuTJUAC3fTG/nXEKPtc\n/W8qdOLkdk/KvKAnXR/v/0gypqS6h6U+cnxsUBdQH0gvk213SPKT6JxSW2zD\nzkUTHDGX6S/XJXngrYWYWFIFxDcae2/VvYhx651vSqsRBiSd7TadvJpBzl4s\nzTxFvwq1IftYaDOFHYj/H5W/rhPq1jXMn3hx6ath3k36nepnXH1Rm5Z0nbPN\nBlv6mnmy/LjamXsCwaCkyRHOvaCxSPmJnU+rVbanLSZszNOc2puapBOgR0An\nSIQuq6REIpyHpK5nYaecqYw6+SGrYIGhNMc86EaACNVTgQu+mX3rM3sMD6Zf\nseIP\r\n=IxjY\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"885f0f579567d1552ffa6dddfaf95aeaca09cb5f","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.51","@balena/jellyfish-logger":"^3.0.27","@balena/jellyfish-metrics":"^1.0.286","@balena/jellyfish-environment":"^4.3.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.0","depcheck":"^1.4.1","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.7.0","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.8-renovate-internal-patch-885f0f579567d1552ffa6dddfaf95aeaca09cb5f_1624661542165_0.22447690488760896","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.8-renovate-internal-patch-611e0ff8804819a941fa6b9268b69560450b01d5":{"name":"@balena/jellyfish-core","version":"4.0.8-renovate-internal-patch-611e0ff8804819a941fa6b9268b69560450b01d5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.8-renovate-internal-patch-611e0ff8804819a941fa6b9268b69560450b01d5","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4e1a07bc4e100d1bbadf1c3afeddde24339ba254","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.8-renovate-internal-patch-611e0ff8804819a941fa6b9268b69560450b01d5.tgz","fileCount":196,"integrity":"sha512-jqoCdUpFDOvpQ5AzAJA2uj6lfP+DomVFdL5bmCZZoxXhti5/IF8f8An6HMkznIgAnFSeLN4OQSVESf1HRU/6+w==","signatures":[{"sig":"MEYCIQC8b1PP8zcNcKSgNlXCIkO3rapiQzYOGARPqCBvhXa0CgIhAOFQsdcovRuvaqpfDy+1Au1G+7Zau4MFtFwmprTeEcWM","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":727907,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg1l+aCRA9TVsSAnZWagAAt1cP/AzYKI2Eh2VdP2FB4a8c\n4SjfydSvV1cTDj4/JuG7oSOttjftFktr2WyDkGV1DMrO/LZVn5wiMAHhJ+cx\nWySTmhd2k5L3NJxFtg9yn7HKci9cI+qj13P0muFx6dWyUMLQVufmSRRPnMGW\n4K0eGjRSPDFy+ym+a+OXvS09qSNsnuY7dShjyKvPbKCgN+ASsyWx9/NX5eZg\nQSC1fnDzgb9Pxip3SAhz64j+oRih5rxuRjDQHFyO8lfnVZv8beFIvqWvwUN3\n80Ujk6Xv/3nFySCbJNBvLKHJt58fDU41cVoUCgN62K6Q67Vjw6vxP1forJjR\n6ErnCUcfhjcsyBxcmBlMJCj+XKk3+wVVo5/eJkmCjqaL9Y5Moj/YVKRvywR/\nIaOWCQH6+meTnv40Yr+t6jwPr0Ge4Nw+CKRz2Tx4p/X1K1/coV4g8nQxtmPs\nrbQeKaemOWd+d5BoqQF3EgSBVy1MZEvqK7FZN7+k0ZrCOdzyRVVnVViLeajD\nYxPR466KlT+cYOuzZ/XNuIWdcmtoZxlVV/xOYQrHp/cY1yRKyIv3Bc39bpgx\n+m9AlEuuDsYsq33QW0wu0hFjeMCkVlF9lLTcYVfetAQfk+oPseqWxlVwY7wk\nI5IY6TfA3Jm0X4Z23NolOUSLrWrkevXc+LrKb/bpjb3EU1KDQ8mI1nIKa3ch\nzIrK\r\n=b3EB\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"611e0ff8804819a941fa6b9268b69560450b01d5","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.51","@balena/jellyfish-logger":"^3.0.28","@balena/jellyfish-metrics":"^1.0.286","@balena/jellyfish-environment":"^4.3.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.0","depcheck":"^1.4.1","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.7.0","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.8-renovate-internal-patch-611e0ff8804819a941fa6b9268b69560450b01d5_1624661913742_0.8195751950408614","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.8":{"name":"@balena/jellyfish-core","version":"4.0.8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.8","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"652fe0627b8378fed33db4f8fb1625f05b888da0","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.8.tgz","fileCount":196,"integrity":"sha512-9oY4y9MlD39J8MHh8e75qGnfAYHIovgEFqK84KzIJPoaGhyAFnRGhAkdGK26yPMVkN6HyM4YAqhY22jzstPvyA==","signatures":[{"sig":"MEUCIFbPMZetIfggc6CKg6oKvLGojOVdy4iWTFD0HEwXI+8KAiEAtw5Gx5XnGU/T52BebJ9olqwHWxNzJOyAE9yWiQvdlD0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":727842,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg1mDZCRA9TVsSAnZWagAAsp8P/17asorJq0pahIOe6Z8Q\ne4itC4MRqco/aNdz32wTImzvfR7AKt0zZ5mVLVCfNfWwsqLSu+nPbjxuEnCq\nnId7KQlV4ncIFNyiVSYayXD3MgjEmtBN7p3JcSJUOYr2ERByHukxqAYywk80\n5c/tzZns5FfSvxMIlZebW2Ts9XkyCm8PAapG588i8CPyW7mO4VI3lbp8j1JP\nhZ9I+oMSD0uC1bT23lD0eaYb3IXG1rM4drttgb7EDfGe0PoKXYTqiYYHi+pn\nwuWY5qVcQoJw7RQEte8q1tpi9TejCyTdITCv1S37lqiQxfCVBsCbiYnDKqZf\nIOdiVsYl5y/oh+b6SeFh61xUpgyUsekciKixEVwlT3cQwgRNY0gLkUgNenKe\ndjsVIx3CDsZYdK1eyd1/CkWZMeiT9XGLW91hANaSOGVKMRs/uddQag5/mKfl\n0GUfV4K+G4fqXoA2tKP8QHGgvnDFSoqxKXpGACAGVV7OMHSdRr2ni1dUvqpd\ndOH4db5OREeEU7/zJ/8a8tj0ly7jtqlpXHA988/4S9TnbnN+TIt6aFR6jHx2\n98Fi+fy8zB4NxCiUU58qodkPzIR3M66q5LivXWqRCOHbNwUMoMw0wfy6GljV\nbNmn4Q+qXsac79FpIx053riSEtivCpadauVqfUR4NBzPDUa4yzgVtYG1he6L\npEvW\r\n=Ctxg\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"3f8baea18d3a76367450a35eb3ed3070cfb917fc","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.51","@balena/jellyfish-logger":"^3.0.28","@balena/jellyfish-metrics":"^1.0.286","@balena/jellyfish-environment":"^4.3.2"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.0","depcheck":"^1.4.1","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.7.0","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.8_1624662233287_0.029575268593228232","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.9-renovate-major-external-major-9fd8f0350a14e1869584c359110c0e4bf096dbb6":{"name":"@balena/jellyfish-core","version":"4.0.9-renovate-major-external-major-9fd8f0350a14e1869584c359110c0e4bf096dbb6","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.9-renovate-major-external-major-9fd8f0350a14e1869584c359110c0e4bf096dbb6","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d1a48e1b2503a0038ab0f4651029b073c981dd2d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.9-renovate-major-external-major-9fd8f0350a14e1869584c359110c0e4bf096dbb6.tgz","fileCount":196,"integrity":"sha512-5x66GG4YRIrygeh8TV1BYAwy7pkgQKyxh05EZZBtQwWUraeWtRlr3757GMQtuPp/d0FBAv23tVOGHNqkh0TucQ==","signatures":[{"sig":"MEYCIQDqCegf+mSJaUhWIWcgnQJ9iySj/wz9dLH+wIODhDzMgQIhAN6i3g5fQ0HKkbmIWlM9wfaQYbqjyeNaWJh3XG5dKjDr","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":727983,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg1mHoCRA9TVsSAnZWagAA3hwP/1hCG8OuL6S3Q3/ZRm3M\nzWqFN9uYSnCXmPy3aZmSP8pMIDnSlWSlv4zHuUQJcAyfL80PaTYpjGsfjbxP\ngFCceIEEFhwAui31TLW9e5I59F+DKril1nVsE0SCNiqYcEIe3ZwZaecLwUM7\nk57ytdgZptLkiDzqVhbG8C4zPr2wqNhEQ3Lc7DZcuyVBmijytsr8KjkXjDzc\nZ/WXoE+yKzIhSzagkBRyIhALzwmzdcZbB/IDyknbXKfvTXMvSRyAkFUAkpeo\nooKvOanezXnoIlyF7BQcJ6w+8egEun3pV8mnQeXLB6aQBJNVxp+uWdl4UzOi\nw5J5jr3Zjaz1/09eG/HX8qwHeNPmsjUCr5+w8fBcUkhKJKRhgOO1lf+4Yd32\nPsnfFABApn0kuczZZJNtdl5ZSBUyEZ5Ncj0N7eJ8I66yKvNZY9IzmvJHzNTA\nD9mgdfka2FiKTrVvX3SS+xQcy/ikmeXdtXEXvf6NRL5MsM2ZqjsOpRDkG/CP\nBqdwaqbg/SwMEqndDsmTICsvlGi3nd0d9fen05FtMPbl/pgLi20qaTMrIjDG\nq1kU2ecaUH/TiGnArL0tjpvKFAuJ1QLFQb5PrdAY8RtrPV2pmGiv5yS11ZiR\nv0DC+sEqPcrJSush1PFKVAsiwoqEP2X7n5u4VOmSTSBm8s81lMdG3i4GyI4F\n3kNz\r\n=ElL5\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"9fd8f0350a14e1869584c359110c0e4bf096dbb6","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.51","@balena/jellyfish-logger":"^3.0.28","@balena/jellyfish-metrics":"^1.0.286","@balena/jellyfish-environment":"^4.3.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.5","deplint":"^1.1.3","ts-jest":"^27.0.3","typedoc":"^0.21.0","depcheck":"^1.4.1","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.1","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.7.0","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.9-renovate-major-external-major-9fd8f0350a14e1869584c359110c0e4bf096dbb6_1624662504361_0.9097937055019296","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.9-renovate-external-non-major-208f0de58e77dd3289cb1bf44c694b8d60a26198":{"name":"@balena/jellyfish-core","version":"4.0.9-renovate-external-non-major-208f0de58e77dd3289cb1bf44c694b8d60a26198","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.9-renovate-external-non-major-208f0de58e77dd3289cb1bf44c694b8d60a26198","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4c3f5b5dbc025bef3ac10f849d0dc0d5fc63cbd8","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.9-renovate-external-non-major-208f0de58e77dd3289cb1bf44c694b8d60a26198.tgz","fileCount":196,"integrity":"sha512-OXRCFrTfxt0SJGvgPaFvmzpDnFs2AmpuKPavMs/6gm/3uTSFyUMi0BceH+QWh2CV1pnMgW8Zvvl3PUHhiJMDmw==","signatures":[{"sig":"MEUCIBLai2Hj+4q4RvIUSeCUt3fIlZYxoGWigmF61kiIRUAtAiEAgdWPb5qYnUpVMdzao2KNr0O3jpmjjCKxCB9pnQY0h3I=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":727985,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg1nuOCRA9TVsSAnZWagAAijgP/jNYnFePY8AhQgQvu4jT\n61NzWMzC6HzXJHVBAC9P5GvJ84PeNEEW2uBaR+Rw38vJMS3GxiY3xpnnxYXQ\nNPibksyBkI8xSo/+k2aFIxpewcYlWCLfILMKzwgZg+y/qW0Aoi/miqLlVSnO\nkAzzRVBgjZLCtcPRgAWtNHp/gyKe/npVZA/GldTMgDFlNy50q8BMjHZOHljj\n/FGcQTQqgl9NckziwMT6OEObJGQMgJE7jaY6RtxgTW9a69rmACvwmFCT88kD\naTQXr3siPReW5RLVpBY/24qgC0Oo3/OQPqVhyBjzRQJbozQmlcwlmv+hneU5\nsth9svvp7RikxmucCYemgdrG34LCfD5I2rHq/iqBFCO3Wm6XhBmNmQH36wga\n9Z6v6Rs0SwS12k2k6WJevu49eTBMLqe1cIIuUGE9S2WXT+JL4FFD+b+Q7fWw\nnCwUKSaWqe/9nlQy4XS5q/6epu7wOo6St77TFRDC5yRVQSmCRw4+YNxPcM+j\nX2X+8rCkrYub3i2inUv9Kq7hnn4Q1M8jnQssf1kyjfdvcrk2DrW+mV5U3k5C\n/DbYMZW4bmKjPlicMTi4jOrwvrQ6il3mPnAWB7ehmWtVglBWsrdUterrFKFf\n+t++UAR+gatIFAg0aqPr3pJi2KNADfC+kHo5AWeJInPOG/Opqzy8E8rViwMK\n591D\r\n=SJVL\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"208f0de58e77dd3289cb1bf44c694b8d60a26198","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.51","@balena/jellyfish-logger":"^3.0.28","@balena/jellyfish-metrics":"^1.0.286","@balena/jellyfish-environment":"^4.3.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.1","depcheck":"^1.4.2","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.7.0","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.9-renovate-external-non-major-208f0de58e77dd3289cb1bf44c694b8d60a26198_1624669070188_0.22846408676054097","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.9-renovate-internal-patch-02ce9cc2b60d1566a40b555af123693ab891cbe6":{"name":"@balena/jellyfish-core","version":"4.0.9-renovate-internal-patch-02ce9cc2b60d1566a40b555af123693ab891cbe6","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.9-renovate-internal-patch-02ce9cc2b60d1566a40b555af123693ab891cbe6","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a49ba4f1399375a17acc9467b7d3148dd8c91096","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.9-renovate-internal-patch-02ce9cc2b60d1566a40b555af123693ab891cbe6.tgz","fileCount":196,"integrity":"sha512-VDqXbWJW+E8hl0mQ4N2TDBgwcN35nKS1gFUtnUym7m5h3MTcVcnxqZygGabIgSOVT+XCuYOHfgsIg5JtWxALSA==","signatures":[{"sig":"MEUCIQCxOoR54ghEKeWEa+Cn0IzuLiI55EDIdCXjAOsNuP9fjAIgAPnCbKmlOdEMirlAIAuMu/XlrMaUB6CbZVHjvJi/5PI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":727977,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg1nu4CRA9TVsSAnZWagAAvDEP/0Z2rNWhyH+XDygHPA0K\nM/MSzh6ugdxkSZAMFePBlYwl3+Npqvg+YtN0v5GfoLOq448uBqxaDO61mK18\nHqiEKh/m2w/BNJWMhMDjOw6AXABrig4XpZ9yrTOcFZyA1uzXBKXqGX9MQUOI\n4QnYql+sXL4jCFYOlmMXjnaeUyZn9VlzyiAnJV5AWhtVneowCvpMJfI28cC7\n45pvukjiA4ufkZ+EvdhRDUYOEdvcMwiyYFfYROz5XR711a8FXrhK5QqHXlTk\nbyWLS5BYtTUQNQkC/5TbgsNMVtGi1GjCsvPbjrJUvMy913qbOJFRLo98hxTj\n9FX0dStg/CLO3gcx76Xd22K/PcYVbCLt2c3sGE4zjYYv2J5c8lc+Nh2hu+J8\nB+if8c/qfPBtZs+jCoPTPKamP2BlRjb+kkxlExTwZtEDrb0lapmwopWDlYON\n6MmR03hsusMaMPgq9BrnHEbzAPH3Oq5JcDqq7g773wId/IcXbJnDXfdiHok9\nnNN3K5k1gixlymLXvaZAiM3ZEJq8KcoMOem1ke8SROgNxvZwPBboiaiOWOY4\nOb5W4gf/OPlNHAi0TsLuZ4C42KL17sv+onqk+1EOmxNbuKDKMiWN8XCa+duP\n+KbG54MOEzuwGtMGkSevIOVTvNLbwh0tN6FBAi70r8NKXu5+6UNJ7hkyOIjG\nxIAr\r\n=frb5\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"02ce9cc2b60d1566a40b555af123693ab891cbe6","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.51","@balena/jellyfish-logger":"^3.0.29","@balena/jellyfish-metrics":"^1.0.286","@balena/jellyfish-environment":"^4.3.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.0","depcheck":"^1.4.1","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.29","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.2","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.7.0","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.9-renovate-internal-patch-02ce9cc2b60d1566a40b555af123693ab891cbe6_1624669112291_0.07541065603746078","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.9":{"name":"@balena/jellyfish-core","version":"4.0.9","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.9","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0ed2b8c179b48605de191928c15a25c7d21189ed","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.9.tgz","fileCount":196,"integrity":"sha512-WYJ7IBQl5URxQpKLrgjcuhlCQJyymRSPrGb6AZ0PVQOfRApA62KLexCh0mCWijFxKxU4tmVTpzquSCBPI4PC+Q==","signatures":[{"sig":"MEUCIQD+w+HtK8l/v8XjNMVzjDE2b4LRPhUCYb+YhpLyC6BU2wIgCJPay9ouK4U8ZQLFhG1YESPKlzQOUABkiUQ/x/ca8TA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":727916,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg1nzMCRA9TVsSAnZWagAAGkoP+QAv6ww0Vqi9LWs6VpD1\nEsSUYZ9ZxbJjSXWnocACrfdEwS8nSDA1tKRJ7jMJD56R2Kqm9W84MjgAGElH\nqYpE76Fs/VGW36zHtcyFZB5FS6+VSgn+eMJhnuJrIXuQPo3oA39g3FqUa9cH\nf+mD3cSuR8mNDPNc9FvWopysuRdPKDhjINilMZ84TPvjmhH3TU8BPwpnnr+5\nMz056SvT8P+iQcy05l3xp1+NyLTYAC9mngPH3HTh3MqvwWfxKJi/WIIs373u\n3WNeqjJw5+6iRRQazcFnhN1m//S/98DhT3fiwai7bBTLguY1brnBIhq/KNpZ\n730z2Mi6JFGqVActnHuBIXvB/9wihdeyUvnMbmnUC+39IzhDp2fWrjJ684Gr\nq5QzENtduIvT8WmbIP4Zj78GSzn0uKw9sGL8G30Ve7nbzUwht4inp/vl5Btz\nzV/BsK6kNKlxCshK6Qd3VyUCUm9O5nd3nicFRvl8AacEPBVMe6YJhCRUMQHL\nkaHhRDIhv+tpj5ZMK3+4WMqmZb65ZzGUvFLlvDKNtqKtOqKkSlcmDN1IBnbs\neu1TclZ66YwAf2Ph8PZGC+NjXngV8mq3yHa9iAgcJdqfX8232Fno12W1zWCR\nYv8prljqqBosICzGT7y1MvjULpxZdGfi08HvmOXr1/LLFnNuQy+2dk1FCsKg\n1gvs\r\n=CQNl\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"4f0b4d66bdb89d2ee7017a01ac906fd3a795ba4f","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.51","@balena/jellyfish-logger":"^3.0.28","@balena/jellyfish-metrics":"^1.0.286","@balena/jellyfish-environment":"^4.3.2"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.1","depcheck":"^1.4.2","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.7.0","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.9_1624669387938_0.7696886734785982","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.10-renovate-internal-patch-19c67c73f5731281232dcf4e7f8ec01c6125a098":{"name":"@balena/jellyfish-core","version":"4.0.10-renovate-internal-patch-19c67c73f5731281232dcf4e7f8ec01c6125a098","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.10-renovate-internal-patch-19c67c73f5731281232dcf4e7f8ec01c6125a098","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"00968450edca01e50b3bd8b777bb156757d41458","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.10-renovate-internal-patch-19c67c73f5731281232dcf4e7f8ec01c6125a098.tgz","fileCount":196,"integrity":"sha512-0n8N/HBCzNSoyGpRwbp7KGLynH5TNT5WyrMkAWaxC8z/UJ5THzLIcpA0QIVrA6tei/9BQiJlIlQvMQ8bnrpUJg==","signatures":[{"sig":"MEQCIGNRWesD8J0Rhj7AWWDl9AIi20NZUcaDO0QhupHnEDt+AiBcno8EWz5xyEZFa2hsSJMM348RGQgpK2XZZdNz2PgcpA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":728053,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg1n4CCRA9TVsSAnZWagAAkIMP+we/L1fxYlxzDt4rHxdZ\nYcwno4yuGnNnTGtOJZHmv6gNa/6vGape1blBAdrt3A6hSou8YVciRX5n+s6T\ndKqykaGh5eUxBi7svJCSEiRU41jmjp/dFe4D7gx9mxolS0YH45YIrxRI4By/\nI8cOdRVLk7AW0nzOD2VVxF0wbnKZ+bY43Fhe7UNmMI0mMC8oVbzxVNTHVlKL\nYGugpn1TR3wWjr9DnJ2pSqhL2xh5C5ayg1peWHYJnKx6ke5TnHfvt+2WVbOZ\nSPkxiGn9HUne7cqg/6C3xCSEV2i49ER7wcmgLmPGr38WYDhsychhpQcvkf30\nvAcB+BTbYmM56/3GXsVql0aAMjMHtmmxwB5+FRGGwmBqXEZSMNfkZeuuZZGG\nvv7m08fB9WvjFEa397ZShUchAAwZz6F+PWqf1pVXH8WTNa2M/AwRFPF2yrtQ\nUIBRXGP9AtRhdfu04eWufgvai3TXAcdTBeL9raLSjUNOP2egdynM1Qf8vWDw\n+pk5aMWp3IpSRrH76k43IVXQubkoMvHj7tzw0+qUQVIJkPT+oQjAnSw7jm8c\noGyTexWkYTJlq1FZiLOuEowQ41lHoLtMbrqbyLD4V+viHQTm4PAzx4uctAv5\nbXrRuQuceVmbMKbUphi292ypYDrm08dZyQb/FkOJarz0E/3OfGpBLvGkl2mp\nQi5l\r\n=Krhm\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"19c67c73f5731281232dcf4e7f8ec01c6125a098","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.51","@balena/jellyfish-logger":"^3.0.29","@balena/jellyfish-metrics":"^1.0.286","@balena/jellyfish-environment":"^4.3.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.1","depcheck":"^1.4.2","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.2","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.7.0","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.10-renovate-internal-patch-19c67c73f5731281232dcf4e7f8ec01c6125a098_1624669697698_0.23288979828369838","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.10-renovate-major-external-major-e9ce07bd1509fab16a87287ac4d6ab6735a7f9c0":{"name":"@balena/jellyfish-core","version":"4.0.10-renovate-major-external-major-e9ce07bd1509fab16a87287ac4d6ab6735a7f9c0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.10-renovate-major-external-major-e9ce07bd1509fab16a87287ac4d6ab6735a7f9c0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"eb1143e8fe2144ffcf99cba2752776af56c60d15","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.10-renovate-major-external-major-e9ce07bd1509fab16a87287ac4d6ab6735a7f9c0.tgz","fileCount":196,"integrity":"sha512-pxbVcHW2VOuDJR6f0yykD3+F4ufJ5CYWOtLA4f5sv3HcTUdM7QOs+nYU0G5fwbEwhyqBSDw4cBNt8pMkpCcF3w==","signatures":[{"sig":"MEUCIFkJYHqE0XqmcNqCU8oU8KYvP4MNXqO6i0PlbSt8ggOOAiEAxsyg9Cqe1JZszpMT8PjPu3jyjS/pl0+byTQe8LyBc1w=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":728059,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg1n5hCRA9TVsSAnZWagAAydkP/0C2gOGDXK27M2YrEfg9\nyeKg0AtAq9EbxJw6vd4kiwpNOInCaAvWN69AHIQM7a1MBwZOiwZA17LylBxJ\nlm1QbEgPn20jhtuUo/U0SGh810DAT5Xs7lC1OXy/BzN5MJy7FeoiP4vL/E0v\nNfs1ar+fkv2AzeiSMWz28Gva/WNqwqG5heeSMdYvNyluKYrXXbNJNcc6hQdP\nDUusPyGTB0r+UmhXBMU1PXXflo54DZ6MFnt+ZNIxGQjVIVROXC9kWZMDoC5J\nb1+VsE1dCRJ+YlvEFI7DXEjHTz8uoszQ1T4TapkE11OZqFej7yqAf4DED+cp\n6sX8CclSxXLTnFDXSpir5YuJC3wfqqNIJXjcL3YSvR/b8vTnrlZRZo1F6v/P\no+0YIaMeO9n+MMkrNJpytBxfjtUR+GtQiODoRXzXbppCrPU84PGV3rWG85yh\n+K2hkqht2Og12uNjtKBabbzLmB4vcbQvVt9xqQBk+NzToWkLr7n6RRWUbcR7\n+X0bCNdD6Dqxdc0LaHbM8TzP6RhRSspEqKkKscuvuKmxpp8TQcOb7K+g349j\nyGW2bV+whuYoAGtkBmyMv4il3+Qm0LGxJUnal26GLH8lQGhAkXLGDeK0nceA\nZ2B8F4yRAFR7qkNeQX65lRbFLKOvfDlnqXe/TYy8ULTn3MSHZ1g1CHoh2SOa\nhVJZ\r\n=xyPO\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"e9ce07bd1509fab16a87287ac4d6ab6735a7f9c0","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.51","@balena/jellyfish-logger":"^3.0.28","@balena/jellyfish-metrics":"^1.0.286","@balena/jellyfish-environment":"^4.3.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.5","deplint":"^1.1.3","ts-jest":"^27.0.3","typedoc":"^0.21.1","depcheck":"^1.4.2","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.1","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.7.0","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.10-renovate-major-external-major-e9ce07bd1509fab16a87287ac4d6ab6735a7f9c0_1624669792480_0.48266094277968485","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.10":{"name":"@balena/jellyfish-core","version":"4.0.10","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.10","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"eb12fd86d54f700c8f9d4240053d2b822773b2a6","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.10.tgz","fileCount":196,"integrity":"sha512-IscEsBsYx3dJT0GcRZR+vlsPHyOT642ZBt4dVtfDtUrF9IbRCThV+u7d58qs0+/i6pmHNET2TdaAdwE7VvZgkw==","signatures":[{"sig":"MEYCIQDIV2PO2i5gCEs/RUdkeFDXcmXK4A0JuWiqki1DwxQHZgIhAMMJtf1aYOCQzuGBrF7rK2IqQj0AiPuqHbD105OpJ47v","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":727988,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg1n/7CRA9TVsSAnZWagAAe5EQAJFVz3YC+qGFQsP6eLy8\nNAyvBxdU/rPEfsnYKR/QcGYDwxu355s5zQjmyEhl+LuFpRbBNjIJPYJMFBGU\n3oW6jARMoD1qKm6pVgpAakWO/BZyrVFMow9bBgmJkW4u6Q4ESFnzSreGCsyj\nZSnmJnpwT77u8n0P7kNGsM72EInfMf0438Kg2BWf8X4jzZurBiAytYE72/FR\na1YQsd8HV7I23Ao2xlo+sxDuU5uyH8PgUJ40ERRxezV8sPGWsPMGN/o4b3v7\na10+ROtXaYvfZ+6OvE6zA6h+AT9f1Wn6bJcEeSRxVZidzyze6TECF3B3Om4f\nBRjIsYB2yxPYhz7aK4O5lowpxQEJ+EmiXxRRoCgsaTAAzIymcbNCoPH7EB/b\nMy5LIlOj3YkPT4q4InGma+VCvvLiPAlXNZZCQKZqEvkVgAqkLETJFGPmfcG4\nREwXWxnidYh39Sfm/dYP/Q+5qeZE05EBcq42oT79naEdC5pA7IJxVTegHeR3\nkfXUPNwhGFSPTIAEmqkmUvFcMhFEhLxz8lLrMtXexS17xY7aO5bF9Y662xAk\nG1zmYgZ+xkOUzzWr1ZujQMRTRaQJP+vF32qEK+kNnZyTkcCeoyfZeTJx2hEp\nXTAodyNLG47Yc2nOqNefZQSiHF+HanN9xjmKi3ossctSNvXiYfEiwOV8gwP6\nz8Fp\r\n=V3n9\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"217fe9f0f2b9639e58d928b8faf9a32da6ad2376","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.51","@balena/jellyfish-logger":"^3.0.29","@balena/jellyfish-metrics":"^1.0.286","@balena/jellyfish-environment":"^4.3.2"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.1","depcheck":"^1.4.2","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.2","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.7.0","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.10_1624670202495_0.980922621526271","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.11-renovate-major-external-major-92d28f042cfef5b8956f0e267ec0745d5c69af46":{"name":"@balena/jellyfish-core","version":"4.0.11-renovate-major-external-major-92d28f042cfef5b8956f0e267ec0745d5c69af46","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.11-renovate-major-external-major-92d28f042cfef5b8956f0e267ec0745d5c69af46","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f8e64c4c1a1badc9f8723b1ad76c0928fcdffd6b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.11-renovate-major-external-major-92d28f042cfef5b8956f0e267ec0745d5c69af46.tgz","fileCount":196,"integrity":"sha512-uZjgddCyTJQt9wLx2hvgIojtPaqBxy7sANMUe6qo+h5iPqYF3PzJ7MG0IeLKDJzPnyDVHPWT79QSgOH3ZEjC/Q==","signatures":[{"sig":"MEQCIFpT6WFxRmRqmmWlSaCEO+0GjG12YfGEUVt44XGAqeY2AiA00Clmpy7xSlHlCF5nHOZZ15utL8kd3zvbPZsNj2HrCg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":728130,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg1oEMCRA9TVsSAnZWagAA/BsQAJ7DZs12K+/YQZjnFVtW\nTJQozV5YgESSOs+IakY1VJ8onxmq9/XoY5nHl9BbvkgTGOlOGgFRd9ewJFMC\nLg/IFP8flPY/20ZHj7M7SHaz1moiVCh9CL+IsP2CNkVOQwSx+roKWF/mCJZY\nT9arjNL2Nnw+OcMTEMFFb+BsG/2361YiqJLdWo7iL9X1W8Ax0iQPXc6eZQSn\nV0itA1hieOjghWvez2mxx09akPb/IX/5BEkwU0VwaHp/vW+JlW+dQ2J7K1tO\n8dXLLt00XXyww6Qmo5mjij5DNpCHW21eXrxupgnKuNKbyc+Xj8sUY/gZ0lNb\ny6mxRE/Wawb5Cp3/OLhNAIIkdVUtsUHQjtIcvOJ5SrjhH2pStXt+H9A+Rt4R\nIlhuZtxWKhbLp95lr76JiyYC4LFqxqROF5x94Gck0SbLhj5vLyOCT8Gzu61I\noi1fqkLxHPvVxoCZIoag1RxlfFtwn4PzwKz08f8HbYEIr7OY4liuv2cKA2sy\nJ3O+wrhucaz3jYn/p8hxqIFSY9NX7YhjouC/3YI+3JuFjOXbWNBwPLm9X7VF\ncV+d9sHj2m6NLfORWQcKDF64xw+4EucmCLDPUSTozhnOrGsvmfdcAR69is8o\nH0CcI/P3x6tR/VICP1OXXZnB7lWdUKdI6fTWFxIMf5FR9cX1ccB7G46XlwK2\nuTit\r\n=XycW\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"92d28f042cfef5b8956f0e267ec0745d5c69af46","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.5","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"12.18.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.51","@balena/jellyfish-logger":"^3.0.29","@balena/jellyfish-metrics":"^1.0.286","@balena/jellyfish-environment":"^4.3.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.5","deplint":"^1.1.3","ts-jest":"^27.0.3","typedoc":"^0.21.1","depcheck":"^1.4.2","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.2","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.7.0","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.11-renovate-major-external-major-92d28f042cfef5b8956f0e267ec0745d5c69af46_1624670475857_0.7937464254718924","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.11-renovate-external-non-major-85672f0bd00447e398ff7953ed9c06b2746d4562":{"name":"@balena/jellyfish-core","version":"4.0.11-renovate-external-non-major-85672f0bd00447e398ff7953ed9c06b2746d4562","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.11-renovate-external-non-major-85672f0bd00447e398ff7953ed9c06b2746d4562","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"7be9c45b85760cbdbf4a46746416272eec15beb2","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.11-renovate-external-non-major-85672f0bd00447e398ff7953ed9c06b2746d4562.tgz","fileCount":196,"integrity":"sha512-lrIn3XgcFDcg0J5CBaTKzfRykzPzZipZ+tLfwtUQ7lVpGtAqopqezggDxHvCelhE2olO5ACByyZzGnqm0AsuwA==","signatures":[{"sig":"MEUCIQCgMytiE/hu4iwYykgSTaeG+J4kdtraSdCBMIujXDFhAgIgG8Xm+OaKuCxkk7049B0jBXP/dsz076jzRycYpZVeYCs=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":728143,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg1+stCRA9TVsSAnZWagAAxuoP/jtsHsqTQChRAC1CjuHi\njYZgy695s9goG+rOBSz6TPo15pz8Km3PaVvtuuxUqXXVuBaDZr78g5Xo7hdu\nq80hTKX22gIgDlcJQWRZREgBuRvDa5ZEnGdyLeUedlijCuIkh7efgvk/+88X\nESaqZeltanOALxkOMWjHKsbasRHQAiq7DD2yb+/EOqIDiDdaaI+m/1LMBk0I\noVtlPJK/qiOs5hPv0P9owawZd02LoMuhpP3VNBLlMfvMQlhiAfux7Gj2MweE\nErTB0meDUyAjFz8T2AbNuD+TxirRRtS43zEnEFH/DHyQFNK88WyjTb3Qf8ZL\np/RuuZ7LgOM9c8l+rz7Yk2XEsbwdyQiWVgfoeb12tyv0GSWfyIXUs0zt7oRK\nuFqN/jsEVpuUq7IhK+2B6Jc7fUwqyaxocXcN9camhWK3g+KZ6SRCAEz0dIEI\nCT729p8H8CdrNxkA3E0Oq4+GKvrUcl7GLvqNh+9EyX7wgv9NIoLpNG4zwPsE\nxmiz7LsaWtzGGOENRLvfObDmRPPRhL6+BeC4NNDeRZrcMj9i1E4nHXOFppF8\nQ8M1oYeSXHSJqNtJuTxst0XM/vTHPep3JsxjQWnqys9qvF0dw1nSFyrz5n62\nNuA6Bsgv7hjSA76eDwQCsEWhd8ueWrNIg//QwRHe7z0w1DjvY9Y3ArqvTwHY\nHOGi\r\n=kA6M\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"85672f0bd00447e398ff7953ed9c06b2746d4562","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.2.0","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.51","@balena/jellyfish-logger":"^3.0.29","@balena/jellyfish-metrics":"^1.0.286","@balena/jellyfish-environment":"^4.3.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.2","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.7.0","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.11-renovate-external-non-major-85672f0bd00447e398ff7953ed9c06b2746d4562_1624763181424_0.7196567026410745","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.12-renovate-major-external-major-dcb2c922cdea3ccdac97626ac853621a88c80dc8":{"name":"@balena/jellyfish-core","version":"4.0.12-renovate-major-external-major-dcb2c922cdea3ccdac97626ac853621a88c80dc8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.12-renovate-major-external-major-dcb2c922cdea3ccdac97626ac853621a88c80dc8","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"90a85e3adff565a479e9786af9c996519934452f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.12-renovate-major-external-major-dcb2c922cdea3ccdac97626ac853621a88c80dc8.tgz","fileCount":196,"integrity":"sha512-FOVtrr+Z9KWDIU9HbHBUjDE0gEMgI9q9KwHIRg793zIiCb3HN2pImi+sRB3ktQgFDVNcAfdmPaWSJNhoGKYSFg==","signatures":[{"sig":"MEQCICUjnbyxWoPAdEGdo+SDbzaDFj4yQQISgVyLGHd39BulAiB6vpqfdyp/8tn8VdSSeqD1RpclOtgojquNw+o36y8C7g==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":728216,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg1+4hCRA9TVsSAnZWagAA0SgP/2Edq2VFpSrjRN6cHW1b\nff6HCjMioLZ4Pu6wO/4d3Gq6Z+xmIRFyTwLyJQXTJiX4678AbS/gyMd5ey2f\nWpuVDcAX3TB1IWWVH/ndoxTgg5DIz0CoRDkvs6hCcRptDs524pN/Kj87PR2S\nX92UmTUxxzcqIJC6YAFfoMn+8tGeu+rXrvhQlxoxO8T6qRShbXamNnk2tYRB\n6/U7z3urfTGgE9vNv9Gov4qga3rAef83P5nVyrpv1gmLQFUPR3N2w3o0jA3o\nJa+TFiUqhhRqtiG14mSezhc6H3asOHr26SO7dhYs8MZTaj9ystktzPuZxc68\nwadXQIw0cypi/DzSYADSbEhzQNv+0l4KYdCnxxshT8TCAhpWdJy4y8IOrvzL\nUX/26MN92CZHhM9ar2U2bl+XYEgDRRyKkrLRc+w1PO+J9z8AFO9zzSC+A+f+\n4F3JPqjkIUyRXqupEuGc2Ai1g2B86wlVw9RCBLNeePbu0E5pwJWix7QYW3cI\nzrxtgCmhBkjLULlcN/xVym336tXJ8ENKNkd+6qEjEIj5C8nfjzetAVWpDrrD\nbh0ssHF46012+b4XIVqAIua5VNyh9SpA/5LYQkla3dlwNMDwQfPbImjRbhNA\nAxyl/IP4ROTFEOQKJzh7h+uHgR7F1JbBFmB2PCGZFh/QUAx7BQhIPrvyzEFQ\nGjXI\r\n=BPQa\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"dcb2c922cdea3ccdac97626ac853621a88c80dc8","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.2.0","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.51","@balena/jellyfish-logger":"^3.0.29","@balena/jellyfish-metrics":"^1.0.286","@balena/jellyfish-environment":"^4.3.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.5","deplint":"^1.1.3","ts-jest":"^27.0.3","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.4.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.2","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.7.0","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.12-renovate-major-external-major-dcb2c922cdea3ccdac97626ac853621a88c80dc8_1624763937428_0.23402460056200947","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.12-renovate-external-non-major-f2a544f72c0bc8130714f02fe3b52e3b3a5ec036":{"name":"@balena/jellyfish-core","version":"4.0.12-renovate-external-non-major-f2a544f72c0bc8130714f02fe3b52e3b3a5ec036","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.12-renovate-external-non-major-f2a544f72c0bc8130714f02fe3b52e3b3a5ec036","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"eda548ae8873493ab0ebe12db189207626ef1d96","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.12-renovate-external-non-major-f2a544f72c0bc8130714f02fe3b52e3b3a5ec036.tgz","fileCount":196,"integrity":"sha512-U4D3F5ic8VAaX8LiuUdHjxzOevlgfmQxCly5LLC+sLmH1QvWPre2qLRqtBuJhsfplJ8qIQ5G9lvwVnAuBRWwyw==","signatures":[{"sig":"MEUCIAd1Ijf2/P3wdoPBvqjVSOJE3kadT5IRgcd3I26QW90wAiEAhTBEhgaHm47fNQgTqjxA4ou0CDpz2KArfYUjwVLIY+A=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":728237,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg2N/LCRA9TVsSAnZWagAAzNcQAKP3TzPvfXmxedBaysUj\nGviaCMSPJ9VKWjKNZOymQZXTbgjWP+Su//iXmUY/rMitN9JHBCxqZl5ZcNMi\nYQSqa9XZ0INxbxrMxLijX4aTZBeZ/wwO3Uvzn4Zlw/qIkmSYf0QSG759qjiQ\n5U3n68upvI9DoC7HvuObTZPjK3Oa+lrAWMR3AEom4sz8LgC8C+GGRimoAV10\nT975kt68RY4uEpRkDOPR2x4O1bz1wFVgsLA2etFN4lc8mmcZ+x0PfPHaombk\nQ/Ii4BXC428xIkVUTHTU+/uH+aUE7W1pXx9qpkHCaznRaUBz4C3aCvKAYEL3\nEjwgAzZsKoXk191dHu53k4zKIBJAR3TVDiJlu3Eaa3SRpy4CS832Quq8P1Wr\nKePOldWMUjfkA8+uyXgD0Sc3j3HMlzVtw2JiNucJKK+m1vtziHQO/L4kA0sg\nKoKKpVuArmJhCvaUsqAW4rQLUS50nMibhSufG8T3Ann0kX8SGDOM4EPj01J6\nHI/4bbbcIJ3OzEg8JLhTN6DdVW1bltA9VbCZ/kEZ499XdumWjjxhxwbnbeqB\nLRdLZ+8P4dtdLDCJ2SJSDkhxTcdcGixKOUQKRvTqqAV+5a2XofCFZMhOZqai\nuoDxECgnpmTDv5zwcNpYxvyHXEFD4JIWqMlCj9fL0uojH1n9UfVGzoCPY66p\nIVRN\r\n=sKcJ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"f2a544f72c0bc8130714f02fe3b52e3b3a5ec036","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.2.0","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.51","@balena/jellyfish-logger":"^3.0.29","@balena/jellyfish-metrics":"^1.0.286","@balena/jellyfish-environment":"^4.3.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.2","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.7.0","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.12-renovate-external-non-major-f2a544f72c0bc8130714f02fe3b52e3b3a5ec036_1624825802542_0.8892506899252002","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.13-renovate-major-external-major-f4c30634aef8d90fb31c65eb98aa13da67fe43b1":{"name":"@balena/jellyfish-core","version":"4.0.13-renovate-major-external-major-f4c30634aef8d90fb31c65eb98aa13da67fe43b1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.13-renovate-major-external-major-f4c30634aef8d90fb31c65eb98aa13da67fe43b1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"787c683caee6334158e16f30970e4a892b3e19e3","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.13-renovate-major-external-major-f4c30634aef8d90fb31c65eb98aa13da67fe43b1.tgz","fileCount":196,"integrity":"sha512-o+7fGwOLXDzFD4gs0K7UNJlgDGwOLe9incVpO+QjKmjlJu/3f7JCgoEJy2y39dqIlz0eoF+UQfWFM/3yb7hBHA==","signatures":[{"sig":"MEYCIQCMS2r0s2g8k13JZxvSQAtapauzK5Y7p8Jwpfi3DEabWwIhAMWHolx6v8gmb3keYmh8u1SkKm9N1UxsBLdaiwKDpjxQ","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":728310,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg2OHxCRA9TVsSAnZWagAANl0QAJpIZ2ief21BxbhhOfa6\nnojrWVAJcdDeOlZRZFAtdRVAezECELpqcvMuNkFmNWGr38KlYeeuRz3E2YIb\nM28n5jhIiD6YAU0X8deOa0lt0vABM1bA2omuuWhRGVsAApo6nM4igQ24tOef\nUoFdN4IH11QuPnQCSWLwx18KH8f44BlWkJA3ag+JKnM9NonLUhS26NkzJ2Rx\nY8RrdFZ7lTgyThyyluHlBSInnCq5p/dFyZrzM7ARCAz/ijFEkQXxu0VRmaf8\nMgXMYEQJZmYkkhLXWFeEOIfgz2kevrJEc6kF+btVEmES2Vd//ldqkI5fVk8t\nPY4G0+fZAqQWFBqG989UFqqxARTqqYfGgGPqvgXzaMck0fWEz6vJRPUpZq8A\nT7znHHDerdgpChuo+kparK4L2GG4aDuzlxeq9WPszqmUYtgq1EbRecOPJzvk\nG8ajtQ3E5IzrvQmB5nmNXsBpN7K71UInDL/3r43Z2TzQuAZeAqHNwwlWOXmy\no/gilfB7pGqQARkeII9XkdVKLpjePKhN5llFrVyVpCebMU1LeJUm933hXeo0\noUHz9BzfshcP2YCO/xkNj2l197BZeqWWwTajR9S1/EXu3oDqOSnqo0bKWruy\nM3CuWiI7cs8z1GAYzQGJqFKSh2oaaI+dsaXmNfQvfpGHT80YdvwYO/HOZIWF\nBA+Z\r\n=ylCD\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"f4c30634aef8d90fb31c65eb98aa13da67fe43b1","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.2.0","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.51","@balena/jellyfish-logger":"^3.0.29","@balena/jellyfish-metrics":"^1.0.286","@balena/jellyfish-environment":"^4.3.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.5","deplint":"^1.1.3","ts-jest":"^27.0.3","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.2","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.7.0","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.13-renovate-major-external-major-f4c30634aef8d90fb31c65eb98aa13da67fe43b1_1624826352506_0.43278217774660743","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.13-renovate-internal-patch-1d0ec0f41ccf1bd7306c843ab759d8d7553eb5ec":{"name":"@balena/jellyfish-core","version":"4.0.13-renovate-internal-patch-1d0ec0f41ccf1bd7306c843ab759d8d7553eb5ec","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.13-renovate-internal-patch-1d0ec0f41ccf1bd7306c843ab759d8d7553eb5ec","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"320f9ff98ce061ec65ffd258fedc6849a5f62d2e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.13-renovate-internal-patch-1d0ec0f41ccf1bd7306c843ab759d8d7553eb5ec.tgz","fileCount":196,"integrity":"sha512-chbCQOYVxhSVe64TfbhJ+XQoj5xmbLYXdHjdHA47/KFcknIUiHqmlZLt2DmONl0aHLqTZKq6XybS9vj+wY7+1Q==","signatures":[{"sig":"MEUCIAaA+RKtsSrcuVHzfhA3lIVyWJeVDsoM7jpaeDN8I+1qAiEA9cJCUG+z9SkxWyjwiK475LQ69wNHI0ruuaU6+hH/ajU=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":728333,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg2PAhCRA9TVsSAnZWagAA3lIP/3CmDhFxM8ZlFC8IakEA\njmNUOMG9m3g0M6ZCzePcFU2mh5x8a576ngvxwKU+TSfo1zgiRAqMygFcdTPS\nT22J7fXRS0vtBdl/jpCLdpsRx9Ux0iVkF+d/iIu/D/KxrQ6MtBnH6xeMchmM\nBs1hyWLrOIj1haj3YuTVwPqZESVZUKsPyy9TzOiWaulF/GUxbYfEnK0kEAz4\nOdlfLdfk1ilNI/evWlwCg7onVOD3ym09Cfnj3OIR+lXjDOyssin3IQvcv/zV\nBnLaHXE0E33ZNq9c94+4tM6G4VCc8XtEnu0JfqyvRnyAlKyRj66sZ5a1F3qT\nPL2yMQbBPj3WZG9RmxT904YM42/06ySrUvk7rfQJqmK5iglWs1Fw2/YYd2C2\nQM8cyHPObk2xbmVgkjGUhSBAB1rgGUqu8vFMrpLT6sIwHjGJW8LtIDEZUC9t\nRiWWDt+4qdxI816ORneFw2IT+yRx52R35VZ/BZi8FhZOJ7syPII2oCczVycT\nh2U4O3Grd+9UR4c1EKVpcZNW0HfQcgmwBBDGNhZqCnv6AMIEGcUGN8exv2ID\ncMmPTgh/bxVjol7bt0uIW+ACxDInFs6omG6u17uxNALfU4hCQMMqtvtfUHyM\nER03rpSs5CmDcleOgMr6iqZgueVvvfVfVnUXXA+BC52QUN6LNWQQNvAEBgcT\ngRe1\r\n=yX3V\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"1d0ec0f41ccf1bd7306c843ab759d8d7553eb5ec","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.2.0","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.51","@balena/jellyfish-logger":"^3.0.29","@balena/jellyfish-metrics":"^1.0.286","@balena/jellyfish-environment":"^4.3.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.3","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.7.0","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.13-renovate-internal-patch-1d0ec0f41ccf1bd7306c843ab759d8d7553eb5ec_1624829984634_0.01705496464798517","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.14-renovate-major-external-major-8e068964a5b4d6ce2a19761a06f1422362f1352d":{"name":"@balena/jellyfish-core","version":"4.0.14-renovate-major-external-major-8e068964a5b4d6ce2a19761a06f1422362f1352d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.14-renovate-major-external-major-8e068964a5b4d6ce2a19761a06f1422362f1352d","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1d0fb8ddfbb52548fba885fd920a48a02210802b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.14-renovate-major-external-major-8e068964a5b4d6ce2a19761a06f1422362f1352d.tgz","fileCount":196,"integrity":"sha512-IlnIpzBYk7SRl0OBdJRPojvEBVPfVXZ/+EG/+1TvOh/CkXtmJL8L65mtV3uttkrW1MXCpBjlJPaECL5WTBoZMA==","signatures":[{"sig":"MEQCIBCbSI++XNBBUcdMbBYDZrvdHTeJGi7JotMxmi9B+GL+AiAyA2MKA7gIgjJGoStGBeEkXcFwpx+RFnGxvbXHjKi32g==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":728410,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg2PJvCRA9TVsSAnZWagAAhdIP/1yLkM+YU6u3BaIffSnM\nHxFzUFfoshc9iOzzq6jb5jHtFJFago+TjQuK62ov+WYergp+hW33jms0Dvqa\nneljwZ9U9sktfIfAABEaUu69ERo760Ot7HZ45PccJPOG+LNDIO5k05nMYk/J\nvWZonCGfjkDLdk3FdsZiz5Hvm3ldLG8AM0h1Sm4fpYHzoMdeasBSC5/ezMIZ\n50N3Rnm8Q/EJFVbaYrn7gJhMjO6uCRXVTKnkGuHhQfkKqw3HQw8aseg0ZTXJ\niDVWtlrgcTD9qpUniUqBAQXVhzW7A3azhkMI9eJZvifXrgaUAYi55qTYxsqU\nBHgLVxH5t8q8bljfSDiS99PHvbswaPfdzwH5tYp6lHDZEhtK1QKo4lYPfuG9\ncSOZeCU0yuAxACNzKElTp2T/uknL9zRrInpoBbdkn3sDGi17YFSLhgF5aZS1\nsB+KdH5g5Z+D6546md6NLmCUeJ4Z3oMafYDAq9cK0+7b1DkXnxaNjYBBzZKW\nrqbAk1dWn8O/UPKhNV62Uy9q3pkMKZ0NhcDzxkR9Ku8C5Y+zOnoYaM6cWe59\nxEs1NUVLuCQomgPmsZNlb+B9b+kA4Abu7DnP+dQi7kABBZUIVxlESXHZvUrm\nUFO4B3nhn6QB2Vr+kWXfuMR6pHsQMFg6OSng7kwpkjfA6rawuo8C/uo/Ysed\nNtxo\r\n=YYHm\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"8e068964a5b4d6ce2a19761a06f1422362f1352d","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.2.0","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.51","@balena/jellyfish-logger":"^3.0.29","@balena/jellyfish-metrics":"^1.0.286","@balena/jellyfish-environment":"^4.3.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.5","deplint":"^1.1.3","ts-jest":"^27.0.3","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.3","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.7.0","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.14-renovate-major-external-major-8e068964a5b4d6ce2a19761a06f1422362f1352d_1624830575110_0.33503422215038836","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.14-renovate-major-external-major-329d3da161a515541780fd2d382a5db33b502da3":{"name":"@balena/jellyfish-core","version":"4.0.14-renovate-major-external-major-329d3da161a515541780fd2d382a5db33b502da3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.14-renovate-major-external-major-329d3da161a515541780fd2d382a5db33b502da3","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5bbaaf85789eaf21376807706031ed322b27a518","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.14-renovate-major-external-major-329d3da161a515541780fd2d382a5db33b502da3.tgz","fileCount":196,"integrity":"sha512-iljaFE6DswYxGD+veK8wPmHdjDslEyvw4BsNcxdVfwjmWEclxzIrCgsyciBgeyA/C6iV1o3+uy2ORaX8vtx5Bw==","signatures":[{"sig":"MEUCIQDJHOXzaByuJAxhBLaCvYQfcMKixHhdAkvlK0ym+hP9ugIgD+P1tH2/USSaE1Ep5H1RvxqyZ8d3i4ncPn8Z4YLHMCk=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":728410,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg2hTkCRA9TVsSAnZWagAA2IUP/2v0a8cj1+PMYMsPBfC4\nDFH6/LtTysNuSxHa7VBbwh+DvfIZNjzFZDh2+KUh1jSEKp70Nkb0+DVyqJKl\n3tztFsFlf/V2tUt5ER0ytE2RXfGZT3Oq8fiydp4IBZEVRVvv6On4kkpPQyXf\nBMKMtgGxbbHw/BsmhdtiAC5Txkv3h81I9NJdZXtN4W6k+dgOZg+3EhoEd9eO\n8d/stJto8eR4noSNrE/dqes5WOSX16/5MLIoFtz/6gXnK1DVoMsPppI9M7QQ\n0T+9FxMtrcLsN20nj+TPt6yeYjh7Xe4h7hsNSBqv1J8ML2FFcu903+MuidOU\nwLgDCplrtR0fEyJjkthhJGSqFpfZpnQKZfKn1sX0LQ4VlBeev2gD1Lr7wkm7\nlG+M586qfsIUe4O3g4uhtpVVg3/owH4ZIG2zpvYvPKvWU6d0oNKjmJI5LGqK\njqet+Onfm4st9EDzDXceyLv8hwmo/0jM0bu52RZBAMVuPH9NTjpdgqmtEeaa\n4my05hFSK9k0ajX2tOC1yFG0a4ElGy7no7SFr8VkkrFVfJlb3fIwjdOWog4/\nmiGJCB/V++368c9h/qFXAIdCXB6T4tjjIXvURuF/8E79PcSnYcFCB4nwjIc3\nJ4hcQsKql/we1jyL09+EjftkV6b1hriPBeWBLVA70s7QPhGA5xu+2ZB/0xGq\nwYWC\r\n=PdKu\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"329d3da161a515541780fd2d382a5db33b502da3","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.2.0","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.51","@balena/jellyfish-logger":"^3.0.29","@balena/jellyfish-metrics":"^1.0.286","@balena/jellyfish-environment":"^4.3.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.6","deplint":"^1.1.3","ts-jest":"^27.0.3","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.3","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.7.0","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.14-renovate-major-external-major-329d3da161a515541780fd2d382a5db33b502da3_1624904931457_0.9956833099599045","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.14-remove-package-lock-4d629d328a67f41e72bd1c2ec65752b5d9df2cb9":{"name":"@balena/jellyfish-core","version":"4.0.14-remove-package-lock-4d629d328a67f41e72bd1c2ec65752b5d9df2cb9","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.14-remove-package-lock-4d629d328a67f41e72bd1c2ec65752b5d9df2cb9","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6f061a25a3da53a758098ae4bf5832cd9a03c619","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.14-remove-package-lock-4d629d328a67f41e72bd1c2ec65752b5d9df2cb9.tgz","fileCount":196,"integrity":"sha512-mueH5kRUCBKzHn3aZs2Ui768T1IXVlT+H3KZz1kjeJ6q46qQTUlG+waLL8S+QdhiwhYViGWo0vjV/hjR4QXMnA==","signatures":[{"sig":"MEYCIQDHX0+qGm/Q9RB3BezRoz3sU1kUbhfcwSNXgRAKI4zb7wIhAIr6odPerN1XfLaGI6TpKpwj4y5rq0PqCd3I9VqObwhj","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":728445,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg2tSICRA9TVsSAnZWagAASbUP/13yOSz2C0oyE1Mdm4dv\nYW1Wd0lFJp3gVLNxEgDSHH073exNtZHlme79hlg9to4peWCXgcqzfWdylrhl\noxPYdaee7HrMtSgg7Hf7SAR857t31ln/66gC6P6PrT/jTPVYHUzNABfb4bSd\n1etCFrNtR+5arlBBK1UTzjlqMo++jvbd3fBylClr2XgSVcC6pCPMiNoxTjSu\njj0DfL90pTeWCM/h6zsdloVOYboW8msSKMKYbQWC/KhxCW8Q0Z5zDW5Zp6aO\nsVKN9JSZWKtZLvmTabWTJ+bmCKaWGxhKqQusivCXy1ZoYd0I+qrZtJMwOpJX\nNc9E5zj7VDKvppvIQSSsOkGHMrCE7MKA57W1EKHw29R5DwnnbUcwqLCgAUR6\nh1zYt5kN0BeVyOwjfGHAAYUHo9fCqs15c6NT621z04TL1QcM3rqa0use3yBH\nYMPt+WTir1E0593Y2dKPTbXBIcoRi5GPargDb8Kvj4cZYruYDJglbmn4hNBO\nddGg4Vo1OjDKcIi3lN47ffh+0wx34gpQutVRTIfKVUiPnoIdicRmHzTVl9pT\n7xe1FFuxOoiTfrcfO8A5L+QnQE3GSJtiL5tmYF3iXyUz0zpEfl92SJ4ohiCE\n9qGEKG2e7VMHS2I3NxGTn3RHDN7g+kDVTh06OMrZH4VbWXrbJpfdWIXfaOh9\nErtT\r\n=FYw9\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4d629d328a67f41e72bd1c2ec65752b5d9df2cb9","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.51","@balena/jellyfish-logger":"^3.0.29","@balena/jellyfish-metrics":"^1.0.286","@balena/jellyfish-environment":"^4.3.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.3","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.7.0","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.14-remove-package-lock-4d629d328a67f41e72bd1c2ec65752b5d9df2cb9_1624953992049_0.21610414112900322","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.14-repo-sync-jellyfish-config-default-4089216a79fbae926a54e8246ed965738f17c387":{"name":"@balena/jellyfish-core","version":"4.0.14-repo-sync-jellyfish-config-default-4089216a79fbae926a54e8246ed965738f17c387","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.14-repo-sync-jellyfish-config-default-4089216a79fbae926a54e8246ed965738f17c387","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"de78542c0292f766da7161262647ba973808fe77","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.14-repo-sync-jellyfish-config-default-4089216a79fbae926a54e8246ed965738f17c387.tgz","fileCount":196,"integrity":"sha512-0SPaGsVQTc8GNYdg3zo3Hgv3kNuImz8rJFL/Inj2fXwaIFhIAtIkNxoAmir0+iai8k/7w3UKmAW5+Kfb1O6qjg==","signatures":[{"sig":"MEUCIQD0SovPwbXvAEWmWBFgfxCDE3a70OLVvpK4L4Heu+64CwIgM53Q5Hy41JDkfXTccIVEc83KAo9OVRuiSVLdYidWRI4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":728440,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg2tygCRA9TVsSAnZWagAA3JMP/iPdfyJWcTJobKx6exoA\njZyPZ7sB1Pd0Xn5nDCsM0D12d4mELFdMs+bUq6PLM21QPszCZZHH5BPypjQM\nezvqPYcaqC8U3Q5uy4tzlGhGOmHCi6RzzF3yeEW5QiQGFTtFzLwl5EQADc6N\nE+MInFnig4PntB7conndYygr+n23dI1wigzj3wN+zW1RwfjrPiOZ56Fo/g2a\nO/sDGvQBcMKSnqHO+19H6dCznDqFqXI9pR6dVIigS7GurvVU3yVTjcQawXtR\nGquna0EDtVLlYgHdoMwxYGhxHV6/CFoXlzJqUg9MTHunGIDMQA/ABHTSHMlU\nv3jahIsjTLoqa5ft2gyzPS82i6bjTELLqb7C9YwCtQQtaAA+XPMUn4dxHoGO\ntjgqUP6fcHK+3a2IdhSFmWquYMsPiORN/sCOnT2D58Pve9spC53vLqathbgW\nyY4RkuYBvnOEaF0a1A4bxe4y1lQOtov/BnW4heu1DgNiy4GrPk+JppSmN99k\nChmbri5V7IlBFhkhaNIeZPCwy2mfGkMTXSSZrN4zQ0/1Bs1HKoUmCuRUFhpY\noLBIybDE8v0r8S+XBTInAZQVTjCBGn7vKBdxB6SdwFK6lp1+cNSCabMGHhh7\nlvjgSrB23OQLPiAKPu+RkJutKUr8QHGnoYImwScDHkTP6SdkXxREbIxAqUb9\nhsNl\r\n=W5zH\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"4089216a79fbae926a54e8246ed965738f17c387","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.51","@balena/jellyfish-logger":"^3.0.29","@balena/jellyfish-metrics":"^1.0.286","@balena/jellyfish-environment":"^4.3.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.3","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.7.0","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.14-repo-sync-jellyfish-config-default-4089216a79fbae926a54e8246ed965738f17c387_1624956064380_0.9724771532749146","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.14-remove-package-lock-3377ced918b196a7a811b599b8915287120e98a8":{"name":"@balena/jellyfish-core","version":"4.0.14-remove-package-lock-3377ced918b196a7a811b599b8915287120e98a8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.14-remove-package-lock-3377ced918b196a7a811b599b8915287120e98a8","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"96c4719a79a620b2d3a1c0eb1f1a7490939a60a2","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.14-remove-package-lock-3377ced918b196a7a811b599b8915287120e98a8.tgz","fileCount":196,"integrity":"sha512-py4srulo3HySv5ZnQmW0VDbyuE2LX1n+nYwOWmqpf+7mBQzrdup+rTbfRXk0IgEGiSk8WgE4zR1gdAn9owwnSw==","signatures":[{"sig":"MEUCIQDTZLQwxFxdS5Ifm1clh4ITDfut9gD4HxwuZ85S5l9B+wIgBMjIUnB2Txj8hrhpST0+lkdtK2PB4xQpPk9IFyXQS6Y=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":728503,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg2tzLCRA9TVsSAnZWagAAoQcQAKAD0QbFT0lh5NVRbAAJ\nnK2dnM7Nqb7yUVNOYdRc2wfLLoZUiDKmmczinht6d58y3cRK6TU7m0gsCnOC\nuW2PBBuOm4XZgA5PirQQ14Y4u4J0EJMz4zcvyVcGg6kmYBFwYpPhwpspSYet\nNN9vA82yhCnXN+IMDsoUMLTeQlAKc8MWXmyjJrGSlHcqAGPeW9sI4yfjOx4S\nHwX6bdcvrvuiWKwd+gxwIJnNz0a1rkiAldJHCAUvM6H4xwALKNKlAr5bqyib\n8WwMthKML8Ugz9cLW5hXOwZtHOAATb+muv9HntthYAUQNriZff+jMAb+9nn7\nH7kxBGVZ5TIS8Afctq1R8Cchqb9FimTsomKHzlJ7Z2gZwBAyKJU1QYZp6kL3\n8T7VjohY19SdJYPPJ9/P6QWBglExY1uOUurdZQayfbi0D+R6WtfmIwBrYchf\nCcrmfRii1cye26L6UbA6Z5zucRVH3PEhAi3FQfMe1kHXrmjFJBBR/G6irxg5\nFy4XQBWoEnhrBfFhsq6TCnH9KpOxIAc9pP+37LFIdU/4RSaDvytmow1pFs8a\nImbtkqKF4lhBnqF8uZm/L654Q+vYMiwtnKlFIDn4a6epY/mvlokSY6qyQw4h\n4szFdWNlFdGzlsiuLozUN/Hzsmta+t8k501SVvPLpyMG/J1lHjMl21ZCcqo9\nSnXT\r\n=4x7T\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"3377ced918b196a7a811b599b8915287120e98a8","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.51","@balena/jellyfish-logger":"^3.0.29","@balena/jellyfish-metrics":"^1.0.286","@balena/jellyfish-environment":"^4.3.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.3","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.7.0","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.14-remove-package-lock-3377ced918b196a7a811b599b8915287120e98a8_1624956106533_0.1924251030969255","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.15-renovate-major-external-major-d42452db99ffbaa64aad17037b5e99bb050732b4":{"name":"@balena/jellyfish-core","version":"4.0.15-renovate-major-external-major-d42452db99ffbaa64aad17037b5e99bb050732b4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.15-renovate-major-external-major-d42452db99ffbaa64aad17037b5e99bb050732b4","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b9034c68fead5c12e360600d55d3f8009f24653c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.15-renovate-major-external-major-d42452db99ffbaa64aad17037b5e99bb050732b4.tgz","fileCount":196,"integrity":"sha512-DGkxfGjfclSKrzjV/EcjT/YmvzLjRGT7LrTUS+qIGPANbq+nu8f9Nt7cKCZNBhc5OshlG60rLrLmt7/BC3FTMw==","signatures":[{"sig":"MEUCIFWpW9wpbCLqW218fx0C8T17uAadeHqh2Rz+jsOXUweKAiEAic89RAJ8uYZ6jjTQ/dxSzUmUsTuLu1eCOQy5azSZFgo=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":728506,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg2t90CRA9TVsSAnZWagAAApIP/R8Us+7bikp3Ynl0NzzQ\nM1Zc/zn67EkK3sA7ZT7jTZrzAOd84T5c4BMbkwpMHlDq1hAJVZJbZCSpGROH\ndksOlY1nIUV4Rx7V95f6To+MU62zILpCWocx60+IxptFTv7QSz1Rxq2ofmLK\nMoxqoAT8u4KX2+JwQ0KEG4vv+/g0WU/FMylWAUVKS4wnuo/VrxhbEtoJVTFC\noXs47BwWiF7UvCAMCA8wUDYR8PKG+/FXx9JitkDtFRCcERbQOGTFNR03H83m\nvT4aXqe7yt7BQPuVi1O3JRRqzRloR5TmlYguZ30jkWuA5/dEd0j8GT8bGb8a\nUZzh8voCZMRxTKlTkMRJhtQe5kn1kK5WxH5v24tQ0R7waHXksCSh3GW0H0Cm\nKzzdFclQ/lcDxd+xIXdoDB7gGzh74FXYjYSUES1r6SP7kdYoM2FNxqD5UyrH\n9lZZBBDbDktqtYfvJcUp2XyBdg7khjqldIngegJp17WYKQ5kM1v5un8hG9cJ\nUgVI1CM+GTQYxm+uC/L4ctOiVvwwKXSYOzp3+mjQ1I2MCzIwWeESmqNPPGMZ\n9H6/N92PSGeH94hf+l5e1B+PuegzJEega1sVRCY9PN463zkIqpUTHtG4I+ce\nwfCjOU3BqM93uBWomQHOUljUAMvvFKDfyOtN/Pw9NRaWEta/jc7VIIjVuCnY\nQ/Yp\r\n=p9t6\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=12.5.0"},"gitHead":"d42452db99ffbaa64aad17037b5e99bb050732b4","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.51","@balena/jellyfish-logger":"^3.0.29","@balena/jellyfish-metrics":"^1.0.286","@balena/jellyfish-environment":"^4.3.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.6","deplint":"^1.1.3","ts-jest":"^27.0.3","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.3","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.7.0","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.15-renovate-major-external-major-d42452db99ffbaa64aad17037b5e99bb050732b4_1624956788059_0.23281532280210238","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.15-remove-package-lock-eb67e76fec7fb764d8ffe3407607823f767bd827":{"name":"@balena/jellyfish-core","version":"4.0.15-remove-package-lock-eb67e76fec7fb764d8ffe3407607823f767bd827","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.15-remove-package-lock-eb67e76fec7fb764d8ffe3407607823f767bd827","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1b43f8074a2b2dc57d75c754c98e15628bba7c5d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.15-remove-package-lock-eb67e76fec7fb764d8ffe3407607823f767bd827.tgz","fileCount":196,"integrity":"sha512-vZgQjIlGgtGMqpTgcRkDQBhOr+sDN7uV4rq9mHhvuk/nLo/SvQ1wdwgGdHH6aHLRHhzGhLww9ES49L3Ww0oJgg==","signatures":[{"sig":"MEQCIB3RLvmiV6hSK8SjMJqSex8Jx+kMfs1WWVbAB5ZM/m7+AiA6ULpgWJMNERpFwz34qS2LAMlAoiOVBeet10x/3wDiUg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":728599,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg2t+GCRA9TVsSAnZWagAA5sMP/iaGUK+sZoJpbK1JTQqk\n1R41f47zTqVbbNRdAyZWLvu7vlYFZsPgQ0mxhNntFLqK1dRfGAWbfTUm0RKL\n52lNGIKDRsslc9wf+D360wi8/EpT/lT3R/SDQJxVFc9ARHME2ThXZNgPZwIt\nJVU16D15SR8Wfd4ugbojmrc7tg/r1lYkhpWy7GQRx5DXv53ILqZfrhBHnivf\nqGOVH1YJNrtoDK99gpLXqTUMj3jqj9CGsxMaamIrT+ULFhmfYnD0svwJ6227\nlu3fJty4qbsaex4kcsTnmkBxRKtiyEOzpD2YJoOwYX21m+JLik1JEodHvlG0\nXNGlZ1w7JDDijaRtkYNQK2LMT/d2PlqMpngl4qPzn98dlUm/f5y++fDef1Zu\nnU0XQphff1rG8hHxbam3Q6N4Q/v3sgnFwrRMZGfsxnF00rjDO+VvZ2qJAcpz\nS5NnrrigRH+jES7DAtVG1HlItmwcvDRKRdlq/xHcbykM5RTwHE7PjQ6qSm5p\n40zpC26Mhk0ZkEOZUkrXeSjFCF9HdyuraoOIDVYCEBH8Um8CbR6eSHBHBRpt\nIbr0DZF0OlJDcIPX45Wg3auTexLGOicW315ae5ebKh7ENo85RqrCukWLOcmZ\nkTft7IBsl/FAwUT169tZ+gvds/11jO3/fEYptDbaOj48h13IEztDbV6bhirL\nUfFv\r\n=zAhN\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"eb67e76fec7fb764d8ffe3407607823f767bd827","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.51","@balena/jellyfish-logger":"^3.0.29","@balena/jellyfish-metrics":"^1.0.286","@balena/jellyfish-environment":"^4.3.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.3","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.7.0","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.15-remove-package-lock-eb67e76fec7fb764d8ffe3407607823f767bd827_1624956806150_0.2038705788604176","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.16-renovate-major-external-major-a3af8fec3e0445cb314f62dc269ee453c7985a7d":{"name":"@balena/jellyfish-core","version":"4.0.16-renovate-major-external-major-a3af8fec3e0445cb314f62dc269ee453c7985a7d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.16-renovate-major-external-major-a3af8fec3e0445cb314f62dc269ee453c7985a7d","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8b318208423c9f0ab0672238f5caa0bbf861c45c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.16-renovate-major-external-major-a3af8fec3e0445cb314f62dc269ee453c7985a7d.tgz","fileCount":196,"integrity":"sha512-NIZoF25I9FKfQ7HaZZWRuNwVyEgYkIXi3L7Zpjgp5ZnYVuiFRLLAdna3F/kXh0wfYvjeMr6jZl4yQjaXlL+s6Q==","signatures":[{"sig":"MEQCIEhmXsZPeTYl3Wedu3NfbO6Npe1qoOQ51BNqtw+XW5jOAiAMgwP+izwYdyMmHK46jjZeqoGFEXt0ei8B/3jbxfCXTw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":728680,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg2uQQCRA9TVsSAnZWagAAtZkP/1fOyr5KbrDL7RnX5KJG\noblWVgOdSPm9szt53LPOpSGdzXJJs3s4sWyPs2oDf9Re/bKhhWjeWgCelGmC\nUYl0hjREo+IOIUVLuZMIBIsEAFT1f2ynqpcr8AKSbwN9/KHIQmXpxpZXBpkx\ncM780TxXT0EMl64vuqopQ9mYW09vF27iFp8LrM5sfo4M3mau8E8/7fkkxj9A\npfpsJ1+PtcIaqZw92wzU+CSKS6+lsVFoAYJx/ifeKH+Ix3Srwc6jfjTzHBd8\nMkl20l013XPqeYU99nvgGMb2X66TMA7gHI8johBkOzwcZmYFmPvwhZoO/XL1\n7SYUz3R+ckdzMKNQ6TrZomM1aM+srlZELx7+tIIwsF5XCDXg3x5nio2E2H/6\n1eaZu7F8w2tGvY5N+LYvFN8yVoVY0nvLYRBUntEiCEqGcrht8YiJUlJb/w/y\nBRst3N/Psm59visgQ0G3xWICzJU8kxrZZ65YjOmZHFpwvfb3kPCu2WXrcr47\n9jO7aU8lNd8SnYKNhVDnSWKFzHnDdpYqYrqfYDcZS7fQoZgkYrWvARBiLTcT\nYn5DS542Mv7XpeKyr2T/7WTkqtEzoU5ImbljXKSWRwE41vuwxWXRya6TxB5d\nsJAU6S0WxYkPOf1AWl4Opl0kkVuGi7ULMnlV0xF+gnXD4UuQrV2YhxOPLI/s\nSPws\r\n=lAWj\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a3af8fec3e0445cb314f62dc269ee453c7985a7d","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.51","@balena/jellyfish-logger":"^3.0.29","@balena/jellyfish-metrics":"^1.0.286","@balena/jellyfish-environment":"^4.3.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.6","deplint":"^1.1.3","ts-jest":"^27.0.3","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.3","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.7.0","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.16-renovate-major-external-major-a3af8fec3e0445cb314f62dc269ee453c7985a7d_1624957967427_0.4761029272572628","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.16-renovate-internal-patch-600b8e7a428564b13f118e256136d773ccdb323f":{"name":"@balena/jellyfish-core","version":"4.0.16-renovate-internal-patch-600b8e7a428564b13f118e256136d773ccdb323f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.16-renovate-internal-patch-600b8e7a428564b13f118e256136d773ccdb323f","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1b87349050e0645a20492f8e19f778ec759c42d8","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.16-renovate-internal-patch-600b8e7a428564b13f118e256136d773ccdb323f.tgz","fileCount":196,"integrity":"sha512-v2o3h+/BPkl6X8J6BcZNfBORB1OmcyhvXYIXDNHLtp8nS43oOVudexEDl6U1IjIthtCuRC2T2aN4l927tE4xAA==","signatures":[{"sig":"MEUCIQCPu8CGta4jSrTOERVRQlHqLfDmVbRkNYVg2112NbYxTwIgDco3r5AHWO15H0qnf4SMp9L2+4EELtTsLGkzhPbmu/A=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":728674,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg2vbXCRA9TVsSAnZWagAAdgAQAJN83iA6kl6Km3RVvbP1\nYBLJGFWYzSWz37g6DTZhSKNl/oETeya0Fn5SaCqUorJqrJQ9iREl5EoCgN4k\neb8K3yrkq55yDPGvbuOv9kE2fFdz2qCB2pYnqXkj7WdzeBsvkTJJegenR0os\nLBHtwEAcdkd1/0HbV6uXZX/whVDgTnmOBwTffPF15izT7U68coARkDlb6jFO\nr0MxXbpDpThon1zNs0tiHDKt2f7ydPDnNwouJ35Q0DOnNJnIlEQgrOROahFB\nSyIxp4VpDLDWv6hXnxHUPjQKZyDGtAYeB+zL2P2cOQn+RN6SSMYDHAKAn6xy\nsTxyDqD7z7NR6Zi1KUAxmC9+lgadneAPysAq4qIYFvSA/iDtzZ/5ht0nvxq6\nsIAI/0LDpUnF3L+pAP34fgKgbL39u9jRYjZR6v42qT4AJNum5ouiyJ1YRMZP\nSHOi75HaVCHQkjoS8qF7Kza9rtQlQj+qi1PC/7jnXgt9wwxi8KoV0NljnstW\n7pBa5YKy0tqYGVWRkjOAa5+rTaIhxe76vGw+UNUUCRmPoeAQBomjHM+YtYKa\nfpE5TEqeSyfnWj8Gb1B2ehY5xsYwo9fH8YdPCYLbtRfq40YEaKALxBWA1zTa\nB7LIOqLtopHHKN6r4/d7oBqiYRv3bofu/2+HSFSDTQHUAPT43RhT55XPnp+/\nTbKg\r\n=iVM/\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"600b8e7a428564b13f118e256136d773ccdb323f","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.54","@balena/jellyfish-logger":"^3.0.29","@balena/jellyfish-metrics":"^1.0.290","@balena/jellyfish-environment":"^4.3.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.3","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.7.0","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.16-renovate-internal-patch-600b8e7a428564b13f118e256136d773ccdb323f_1624962775107_0.37283163710433076","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.16-renovate-internal-patch-f31eaca5463e5d2517d94c5a963b8371e873819a":{"name":"@balena/jellyfish-core","version":"4.0.16-renovate-internal-patch-f31eaca5463e5d2517d94c5a963b8371e873819a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.16-renovate-internal-patch-f31eaca5463e5d2517d94c5a963b8371e873819a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"13925e171a786644a5021f326ce97345f59dcc1a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.16-renovate-internal-patch-f31eaca5463e5d2517d94c5a963b8371e873819a.tgz","fileCount":196,"integrity":"sha512-zU5URoWchpjyWCQVG/TICd0ijF4QKEncwmWy9HExbTm3Xl8+ppkuy529GcTo9WG1ushvCbtqBzaSsyPF4hzkTw==","signatures":[{"sig":"MEUCIQClmNAEvDcNK096O2RA2fZzaVAhP5jHwo+4V0seZj5EYAIgYLasYlaNDfMyjh6gDmMF2HBqi3i5GLeAeRbXEnyy/rA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":728674,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg2wk0CRA9TVsSAnZWagAA4lEP/2PLxE5kMePpJ4ERnM7m\nskoF2S0ZschpzdJptlVkvBtRz+k2eG0zdeQ2ifCYPtJTLUldoqkoywfwBpK9\nPgakusa/sYldKZuf6UEaUD9lp/OA0pviRMEoigQ9VdPpSQPCdkX4kcSuyZDh\nagq7Or4rS/XIV/a0GCI7ETQqvwfv1+/6Eihc6d865I4jSBtFHWHd6khDzaqy\njPI/qoIbK2ApszTElGbwUNj8oDCHZ4GGZTX35LNTcqZYcvvDYJcum1dLRUPv\nn0VDAjrVUAkpCzCYOkNo3GhfRpxmc1hK85vsAAt7q5mvv7+S620J7XAzyT9+\nqKdWsYFfr64y5xSDLeKLvCIGbin1NcQzIhM72YpkINyBp4o/r3U+7XGjZ/fI\nUgDMgpEFvsO6+ZT8DmnedRuuU/SEYdz+FAxeDJ6DHXtQsH+Kh3Ud7Ib30avv\nrZsnsYs6Tz1ir8qsOh9TLct3VR8Vgn/qI6lClcsFDlTEm+vdIIZV6VXU/m/x\noo3zcxkqSJbhf6H48lV4UQVOObD5C4ooaCpqvPGaxrav2HpQiUQGwLd4stu6\nPEVoJzfTzpN/9T36EFO469uhRTDwBfxd3s4h5ixKHpA2QCim3mTFp0nq2XJi\n+5Grp52oeftlZxOp8vbkW//Of6zo0h6+LC2TCKgl75co1K0lBB3ywEPDR4MA\nKFt6\r\n=+MaW\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f31eaca5463e5d2517d94c5a963b8371e873819a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.54","@balena/jellyfish-logger":"^3.0.31","@balena/jellyfish-metrics":"^1.0.291","@balena/jellyfish-environment":"^4.3.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.3","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.7.2","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.16-renovate-internal-patch-f31eaca5463e5d2517d94c5a963b8371e873819a_1624967476099_0.9089918070842027","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.16-renovate-internal-patch-b32e15756ca0315a4eede57c7600b67eabef7dbd":{"name":"@balena/jellyfish-core","version":"4.0.16-renovate-internal-patch-b32e15756ca0315a4eede57c7600b67eabef7dbd","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.16-renovate-internal-patch-b32e15756ca0315a4eede57c7600b67eabef7dbd","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"089e1ca30075814ab52dd553691e342a4824464c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.16-renovate-internal-patch-b32e15756ca0315a4eede57c7600b67eabef7dbd.tgz","fileCount":196,"integrity":"sha512-HCOWG6IeMYFAjjL8h8wCEmlk30jt3J6IC60Fy6Olfqj7fV/OfvjbwsU5tQXOIrs6WUURmMdveUGujnqdyJ+CrQ==","signatures":[{"sig":"MEQCIAFF8U7N50NIh6u5ClWP5urtZH/TPrIpNXsb3cOGYEw0AiB3afjx9T9N9KDrGWF4tuJNBQ50XQXXTvamCAM+5sZCXg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":728674,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg2xKzCRA9TVsSAnZWagAAM3QP/ibvEANMyUD0gDm8D5bc\ngybbfmOfSVZ5mOmJFiD7fFyLIXFkCtwmDH00+m6YDiMKehSiACTilb2rcQfI\nU6dropeExhSMn96tBFMjGoaximXDffvxzDy/CrH9MaWMfPNuPrVufmtb2EJ9\noOzJJ1QNgNZd34OIaTW8hkAVqrsNY2IazRVdYtQM27pyue7K2G9yu5OadCbi\nY+QXDtfRe6kxThROqrpNuu2RhR7zmzoTttWQ+Cbn1WxtxwDN4LbpfG4/+p5h\nraYa8IiE/VgyLR5NjT08fFXzWrhhKCpv/eyb6FhTpx64mUrbHdB+Es+aCt/h\nkHMg/6e0BQbwqSMjPW7D4MJM3g+VIalPP5s2SKK+poTGvZwqYbjltXxAr9Cz\nNylmAcccwf8fs2BK8L3xrUOAOUgbwaEp+Zr8AC+oB4hmrqDxFY3c9PXn5Of+\nh53kawuyNts0mRCJF+lDVYKbgPQi2no0h5EmCcdyYKs9m5+Tlr6lMJwCas3G\nslRueT4sd4LBNBE/KPBOU37702Q/keFnv/f+7ixnzAZpMkCHcGCT7jpxDAbC\nVMm65g1XTEJnT4K2R7Pp1h8CKV12gaZjfFNPsKdGUigsBQI5ntJJZWbG8YTQ\n4Phx4bJdnUdS4XNQsRcEG0IrHKmGLvaw4x7gkwIx4gUOfaWKrtVa4lTan/cQ\nDf49\r\n=fJJR\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b32e15756ca0315a4eede57c7600b67eabef7dbd","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.54","@balena/jellyfish-logger":"^3.0.31","@balena/jellyfish-metrics":"^1.0.292","@balena/jellyfish-environment":"^4.3.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.3","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.7.3","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.16-renovate-internal-patch-b32e15756ca0315a4eede57c7600b67eabef7dbd_1624969907284_0.06605293447396421","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.16":{"name":"@balena/jellyfish-core","version":"4.0.16","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.16","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a7651448f34d01bda80bc9e8cbdf5cae93651d51","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.16.tgz","fileCount":196,"integrity":"sha512-HlOn0KF8eWIgjYbQ7TrmJwltNlY0rP1jZ3BGZVq5cdhFdusNMw24mNA+FgO/UCivqEM9ilbx4AQL10xj8qhv3Q==","signatures":[{"sig":"MEQCIBIQBbZOAvpl8VuWqxIsE54yvWF8dBpU32xFXWJz2488AiBSmMaTrR/nlbCnaTM76vuqkyjKID5IrHKSAtjcWu2C9w==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":728609,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg2xQZCRA9TVsSAnZWagAA1UgQAJhwr7ahLAhQOGKnrcZF\nZWQhgGbzg+5uIec3cjy0mhrq1nYKBZTdg9fOktvEuipiZazKAjHYdSHV+vhU\nydTbSv8vP8voGTaZF+AcR2ue2WHRO150OqpCHmZaNebpgyBZxlee8f7oPepv\nSc+TpxSKFt/vGK6UjRSyo3OVFA8aGsKYZkRBBtyulnesI0T4YdL7ZKF+r0gq\nAxIsyLbjJKffjaMf4SIZcAOxwTfLa0AG/VuFcv1cczQLTmp4g73O9xjgHzbk\n5LSFp/WlCpS0LtZPkCGfGP9mPGVEEWiy+zsD3pyBz+35gJwz4W94GjmfhBd9\nZ6KHyY6OYAwdNkMhsJzLqRKPrjFrIhhFPDCMtSXVmXZwDNKmkCE+dFUDOl1s\nUUkPs/wxHMihHAhHuQWYd5EZSrrwxXXYCtEXcje2m4EIpdRrq8S6bp+KxPlm\n4UMLjBUQo4nxl/+3Vj7n4ldGt5SdIp+9XupkdceRjZf9ty+0xfXl0D8s/E5c\nds2la0Ot1OMDu5j5t+MlolirXKsT6xMLqrh+OprQKl5j7an6a2HhTgQR/gFF\nIlEVGYgvxBGr3PWCm0PQxSbk6onDGOwwvNFx+JBZv6ltInv0TcrRMOYMD/dJ\n0mf6kXwOR5xzjpUnAiwEC7vod4yU32J0pRjhrDtv8u61xHCx2pjNS2aKkxCk\nMuQM\r\n=Z7zu\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e3d947c0572d1473f5f99d043004cbc8951d167a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.54","@balena/jellyfish-logger":"^3.0.31","@balena/jellyfish-metrics":"^1.0.292","@balena/jellyfish-environment":"^4.3.4"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.3","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.7.3","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.16_1624970265021_0.8816984756206803","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.17-renovate-major-external-major-a6de17f4272b5b929d756d6ed28d18d1bc45ec14":{"name":"@balena/jellyfish-core","version":"4.0.17-renovate-major-external-major-a6de17f4272b5b929d756d6ed28d18d1bc45ec14","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.17-renovate-major-external-major-a6de17f4272b5b929d756d6ed28d18d1bc45ec14","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"70d774f0b7612f064e96473a172d4af5e7efe9da","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.17-renovate-major-external-major-a6de17f4272b5b929d756d6ed28d18d1bc45ec14.tgz","fileCount":196,"integrity":"sha512-yBCqpgkczQRPJB2x2j32yLULYbtdmZQS+d6Bd0ou9TGQkrtb1YKgjkEs1G4iaIAagOOQN8EOjrTTKetEO21Gog==","signatures":[{"sig":"MEUCIFkoi3iwZiePqS+9TrAflHE3Yd/DxFK01sHLu3Y0EAj+AiEAsCh+BpLSI0SQe4Uk5hwcqlEVkjogS7SQoxlclCfKZv4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":728751,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg2xTzCRA9TVsSAnZWagAARNcP+wZJNE0dI9vcu+yLH1pR\nG0AmKsF2ROSUfwYwzXCsZ2i1x9KBzNBhgB6VH0rk/Ob9Eficxk9Cr1SsS338\nPKPw2rrUU+TZfbYEp3jo8Oxq37EDWvo0x60qSVAPnn5YjQdtV4N4e5umJi9V\nugCIhQj7wAhmXpYDKnfizSVQtMyrNTr+pcsHRBiwZSAuRs7u2Ti4q/JsQwYe\nmXWauOhUsiRlozonO0rKWsDYET+G6Y7BmuXzVtfpc9AKzkQMgWkzey1MFEYR\n1j26dJCJIaNEp4ry6PSbJguIWrt2ZPkBiJ8sS2c1/cKzGXXKGGWPg6enIeXJ\ngBrjqM7GyunH27bno5Km/d8gZUJNhknxpkER2jHKg4DlRLlQcPlx9078/I4w\no4LiAPwkmsbVmxQa3PjEzwcTmFU6nYPTRqvpg0/Qv234DsJkuvi2TRwFM/tP\ndBGA/K6CuE7FbvyIappskVw8CUE8hIRWQhBoNZcsbj2gJs5/Hl70HYKw3U46\nbcu9oVdRW5Nc7rYfKe3juzJHnzm0cpj7pVQDVKPW6awOTNnY497eBgsXb3+w\nGWhVTvp5wHq0Irreu4xsEla4h/Mj1bAt7cuW38PgCysBgSqNrG98ZaUrLQ7E\nhT1p45Pb4y92f/MTcA2GxpI0PDbeSNZHBPzARuUHONqP001CQd4/i27QOZUy\ng/CT\r\n=++ab\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a6de17f4272b5b929d756d6ed28d18d1bc45ec14","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.54","@balena/jellyfish-logger":"^3.0.31","@balena/jellyfish-metrics":"^1.0.292","@balena/jellyfish-environment":"^4.3.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.6","deplint":"^1.1.3","ts-jest":"^27.0.3","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.3","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.7.3","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.17-renovate-major-external-major-a6de17f4272b5b929d756d6ed28d18d1bc45ec14_1624970482836_0.22228276408405034","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.17-renovate-internal-patch-29252fdc6ab6316db9449e1b4b96e7e6d8523ade":{"name":"@balena/jellyfish-core","version":"4.0.17-renovate-internal-patch-29252fdc6ab6316db9449e1b4b96e7e6d8523ade","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.17-renovate-internal-patch-29252fdc6ab6316db9449e1b4b96e7e6d8523ade","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e9f7496dd339118fe3c33639f8883cd750b5f110","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.17-renovate-internal-patch-29252fdc6ab6316db9449e1b4b96e7e6d8523ade.tgz","fileCount":196,"integrity":"sha512-bduDWoLY8anz1q1dWgMhT7URsiMyzWV/6/Gt4wJO0XzhE+kR6wYWhTJsPfMMJ4Io/bNs+IvLg/H7myMamfAsXw==","signatures":[{"sig":"MEYCIQCwwCa8x5pyxR3sopqgXJsalfylhgFDF6H2tZDiHJj1uQIhAM6dRi6ckAi6dG5nK7Pq7aYzC+Jb5DvigH8N+n1vfO8x","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":728781,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg27ZcCRA9TVsSAnZWagAAwF8QAJ17JhJB/2K0R2h/hpUG\nY5B1/uADEGcTkxS+X+HETQO3l97czZZKOFVpfkyoRF/CTppcnSbluc+tqckl\nv8d3//LC03khghbTVV9Oswct/RJPddKUyEM6Y4UXZTUCgnImKLCqpZUie1Td\nICUpi7H7wb10OuE65h/XBQM0iZB6JRwekdOnMcH7qBbvd+75ekrUWmK9T9nK\n6W9g/eQGd5lto1yrhl49+N1DC43Z48PfdFhmct3DiXUH62xWEUQf89oCxEc0\nqxF20SAC6k7KSep9zwMOL0HB0mz2nVmO8nf2CZ73FmBGF8DPMX+lV49lNVVz\nSxSUgT3kMdcs6R2oHo3xZ1fDFU7kxKfxQTlfDleMxEA976YH9AvkbprPqoUS\n/lfdjcJ+p40sd0DxF6xUK9tYJkxJ4nfANeRXIeoRzYu6Fsrp55D15wYCRLR6\nXWPLRHkKgE9MelB7NPDRWqc3UdyNRhDAVQy1BNO71kz5LXcStEk4tJ2oYrLq\n0weUmEKS/7Yo26SPs3lo8zuM9UnmKrgfPkJxonZpT14mRfR/0I6wE7vIAfCK\nsLAQSZkFjn2HGLsLzeMw4WMh8m4xCHCb30sdPRsJkiKBLZt2E535Y5NkvMiG\neKpRJ1gG7xE173Xbe2aea6Q+bNE1qnmammozfxbZS76sBIuFB9Fhxze0Vak4\nDs7Z\r\n=51gu\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"29252fdc6ab6316db9449e1b4b96e7e6d8523ade","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.54","@balena/jellyfish-logger":"^3.0.31","@balena/jellyfish-metrics":"^1.0.292","@balena/jellyfish-environment":"^4.3.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.3","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.7.3","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.17-renovate-internal-patch-29252fdc6ab6316db9449e1b4b96e7e6d8523ade_1625011803513_0.15594468018294982","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.17":{"name":"@balena/jellyfish-core","version":"4.0.17","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.17","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d17b3c4af57c731b2b6a7c196108d461fcf32c18","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.17.tgz","fileCount":196,"integrity":"sha512-ig5RVXe92gxdhOSQS5j/30WmGSESCcmFn/qV5RuQ2rI4V5fsJbRSx0fzLG8+mjf9P4JD3ftgbekGFlJekNeuQg==","signatures":[{"sig":"MEYCIQC3ZE7pvlO46QRXdzGMTTeVe4b9QrqJjxVfDRLUxTbWkwIhAN2W2kN+ZrWLBZDIkxZff6sYdIlDWtEpTlXuv8cN5rXP","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":728716,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg27ekCRA9TVsSAnZWagAA5akQAIJkWtZ8H4CV3L2OrkpK\neNxuaZVedXsxy4yEQhtfKbecQezN7T/jwrUcHngAQn+CxHqNj3qOwtIjeuBh\nxvWBJj/70UfoayWi4xvtZYsO2Ppk7h7ZUI5PB5Zp84guzsCm2GdsAPwepzVx\nj1YBuhn1nakRp51wdoSJJE5PBCkdnNvtlRlNfA5L0FupV5Jd2pVsJEA+6jIE\neXdqpk+dlf5JJSmPMrgzwK60xUHB/eN1p/H2H/lTDsMlekuZHqf6yw2HGQOx\nwuf9yTlScYimK+d6Fftv1Juq7k01EXsl9bEUUi0gdKCAMMNCUu+9UZmvIkWH\n1+TT3kJeYsgU9IFokA/J0yUMjy6HemAUSAV0ZVJWm8/2H9wLGTVN7pIWZxvG\nYmqgjXWsRjwk1apRpEzE5G42+WnPI6yFI/uaZHrM8VPJbr9aXBp3HLP+ok4u\nLBouBb39SRGggeMNNbVns2q4jEexZe9DfekbC8DLwSN5p36nz1Fdf7QsKleB\nnTuj5kowmSVOQZIOsXLN2oK2JoVJ/d6r86GHFV8jAcDQGU3LjoZh8kJyzZXI\nysNp0K0IVL/B1+acO02A5QDy62cvGJfh3KHel86wmqNcmLl7WwMuhUoWkt0C\nMiUagtgyxJ7S6njkpOj/VA1HdAuzvO9fNxZJxaT4w53wHBVrfwR9YqdJxR/l\nCuMY\r\n=iiQ+\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"cedd86a05ba70d9881a919410da8ecb4c264f7cc","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.54","@balena/jellyfish-logger":"^3.0.31","@balena/jellyfish-metrics":"^1.0.292","@balena/jellyfish-environment":"^4.3.5"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.3","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.7.3","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.17_1625012131328_0.8512290622062897","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.18-renovate-major-external-major-013e8a5998ef7f741d531b5223f8bf34eb81c24d":{"name":"@balena/jellyfish-core","version":"4.0.18-renovate-major-external-major-013e8a5998ef7f741d531b5223f8bf34eb81c24d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.18-renovate-major-external-major-013e8a5998ef7f741d531b5223f8bf34eb81c24d","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4eece7c682ffd6e2f5b7bd3f7bf0a1f36e0041c3","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.18-renovate-major-external-major-013e8a5998ef7f741d531b5223f8bf34eb81c24d.tgz","fileCount":196,"integrity":"sha512-/bXpKcEt9wnTczwF8sRae7WaoCz9TjhyGLCXsVdmT0QlKQ5DjiTsbCBks2VHk7qUPYp14OKkW+8ht5i3bHeLfg==","signatures":[{"sig":"MEQCIEClgwWoqFXJs79HLw8ug6y6Yz05MPOh0LgU66xbsi/4AiAlUrmrVsw3Ucw2mC7Yd7k4zPdgaJkDvQLI+QagNpCJ5w==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":728858,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg27h8CRA9TVsSAnZWagAAt5EP/1Vg1FXRBo0RIqsUIYZW\nYpQvh8BvPVZv7rWWTyFLiw4zOW5OzAgvx/h7HQEwFs4k/gYCXoGeiJiO8o9A\nv2W7OHkXyMD7cWX2rgW8Jl1rI5z3UldDEJQ5x01BA0RHT65uEeFg/F3lr5GD\nEdadc45b/REbRAadVtLs6eFob4+KJpMT3ibw4h0ab/yuTmYqlb91UvYwO94Y\nwiZiOdFwOiYUg0Y5OKS26wbnPuhReyLwb8jeFjsjX8g63Vdz/WLjw41ghcNl\nZZusiFCd1WnugkLWYymWM7ktmGFjXg9XlAdYVt3gPzOlpIQpiSfNnKxaJGmi\n/1GEeHk7k/YAQHag8rZoMoMc8KSLOgJCG/oly3Bq5pxHdRW058oC6tPaaxe7\naeONAinfdJ+PUsFKEwU402jSDM6i2OmzjK+Z01llPoU4tL3fGrUdZ+ricjx2\nH99tL6xdjSQYIdCvlsB5Awgi/SfnM9l9d3IOMq+BATZ/WIsplAQzmDZG1Jou\n9znoJxqYGaNFbx4wqHlnBzXHsEoKj43NczoHSLOiHrzYLR6liy36hq+AcIOY\nnhNI+0K1Au5RlnZsnCfKF9UbhLuSEYJU4BMWsUxgIuO+Qxb5BStildYm4SCR\nJzTmiBetmFVeA7X3K4js8z9PjGW4v4Mg6CZGJsY+yVfy4hqOQ+R/vvukRUwy\nv+Cb\r\n=wipP\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"013e8a5998ef7f741d531b5223f8bf34eb81c24d","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.54","@balena/jellyfish-logger":"^3.0.31","@balena/jellyfish-metrics":"^1.0.292","@balena/jellyfish-environment":"^4.3.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.6","deplint":"^1.1.3","ts-jest":"^27.0.3","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.3","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.7.3","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.18-renovate-major-external-major-013e8a5998ef7f741d531b5223f8bf34eb81c24d_1625012347889_0.7187529354840942","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.18-renovate-internal-patch-2631d15f9e37b717976c8b795b9b8d8fa623769a":{"name":"@balena/jellyfish-core","version":"4.0.18-renovate-internal-patch-2631d15f9e37b717976c8b795b9b8d8fa623769a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.18-renovate-internal-patch-2631d15f9e37b717976c8b795b9b8d8fa623769a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a6dc9942c2c187ed3f3cda031b899380db8de3db","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.18-renovate-internal-patch-2631d15f9e37b717976c8b795b9b8d8fa623769a.tgz","fileCount":196,"integrity":"sha512-/4Tgf4bkMJjd/vj8WbsSewBfnfilFO0y7r7Q3M+i/ecbdF7iuf5LfmzOPmDzPKyrLH2Bi4t0K0Xdj2GWAZXigQ==","signatures":[{"sig":"MEUCIGP8Alh2DVWGdX+8AgI6EsWfovG8bkww5jwme+tDa3TPAiEAlx4iLzzz7jx0DhD4ALHMuzShDxdH0U93OpXtLsjc0Fo=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":728852,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg29HVCRA9TVsSAnZWagAAjykP/1VPuOiAOmf64JM3zjUP\nM95iQx66lJ1PhPC9MC0lFHZRJeSu3sS/6xLM5fNAz17C0pPieG7p59fSlShk\ne51i+2P1Zor7mwdjhzSRtUChB2z6THHRs/ClR2i1YldbsASuYJ5vya/ipMVz\nrRJKcAaNBOdsAfZT/gP1614oyP2StRxMpAPBKTloJN6RMCnqcO4ap8Smnk5T\nAzoXGOk4M5XT15d4b7+Jb949BxAwDKI6Q3ChvgSqG6dfAyPV0hzhD4NIe/Tq\nZKNYe0hCBbbNn0Ih13J8oOVm6JAmPGebKZFP+LsZyzP9FFFqOjLjrCB2k1wh\n1fIxOSxeRnjI9+Y4BnGW4Vhbdd9X58faydsHp8xj03mAwiWhDTGR2cPvQZta\ny0D6ew5jYTdaqHnqTIzMQgG4j5sHzfEezyxVfLEEkacUf7gddl9ZTkg5evEQ\nnJ8vJC52pmfv77JPBt7wkjpy0GrhCjMOmc5mMrlcGec+ymEsDRbtOrnK6x4q\nXs9euZALrKlnz94R/wZaC2fuN3rqEA1NCzT7X5wRz/hGMyetGbcigvf17Obg\nJMoJxxRsKFosh15xU5IVu0yAgzeLb1JwJoq1hqgGsGpfMRq/c+EUR19JYpx8\nZfrS0Aj4J0ZCSPUfZYYbOi8wF1+7A09bGMWtQrZogAzKGjJcTWA9ZZZmA/Ce\nL0Yf\r\n=6ml+\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2631d15f9e37b717976c8b795b9b8d8fa623769a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.55","@balena/jellyfish-logger":"^3.0.33","@balena/jellyfish-metrics":"^1.0.293","@balena/jellyfish-environment":"^4.3.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.3","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.7.3","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.18-renovate-internal-patch-2631d15f9e37b717976c8b795b9b8d8fa623769a_1625018836721_0.07064876613782478","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.18":{"name":"@balena/jellyfish-core","version":"4.0.18","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.18","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2dce301c4cefaf65588d922c08ba3fadd210d438","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.18.tgz","fileCount":196,"integrity":"sha512-QrME9x7WC7Es05feDLm3M6rrSznCDow1b0a+KqN96cbYgLZm79Xzn+S2zyMGcBtqH9vLzwyQr3V/Fn7uTP//iw==","signatures":[{"sig":"MEUCIQDB3QKhUcZd0xHMGdTC6ZxSuvybGckhxh4w2I83V3hutwIgYmGE5K93yP6W8DhfiID+qF0HaebPyTNGp0vJxkajyuc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":728787,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg29MeCRA9TVsSAnZWagAARAwP/2sI8KHqqW4eMVNZ5ETk\nlEAjXmkkX6DNMQvnK0tcIQHZdaoFJhdFOUn8hPxVykHq+n2sQiYDXo+rrlof\nl65MLZnVukiVlgLfJLuBZgSKg7vftX5x59qx/cy2f3BsVDxAoQjGERhPhxUo\nUwDujfebqXdnwvF0JboKbWQ7VrPp/lhjPiRPrRreNkcCmzjLAkpugeskTkzm\nC/4lVyf2PuxNhNjeiXvzMPIAkndM148busus0fit3IQRKXCi/6sHReF4KmcO\n2aS9m6kXmzS1RjQJG+ivPxlnzOCQST7IEdRESSFPZbRIjF1Cl3MOZW1Rflh5\nJVfa03GiaWAUoWmkNWgkhHy2xD6RZMerVnPC6T2f7IZH0lUOWykcghfu+UI6\nfd4vEN+UVPW48WzLcvGjHeh46UHETwMJQx9LmTUQra0zgZLSsjbCsXyzJM0s\nSQYvQW/I4b1BPshmj2tv0RZ8jkclnyH6r3QN95H83oVrcbPFvjMOf762ZiLm\nbgPsvJqVPAq2jG84sEP7CfM6iHkQZ4X/0F3boVMzfSKCHOCf0iPdIoWefLTR\nyX9xlF8J4YCcLUTps8YkASbS/LTEo2SIjajv6B9Z7BTHzbrQriGXmKS32X3v\naGACSRA3RXnjBJ/PQNfJ56LroN2f4+AOicSSdhYRVZAHKHYJW/b2EElDeyPn\nQRpk\r\n=Idzr\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"da1c819d79eafb472a212cef157d2d530ae02102","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.55","@balena/jellyfish-logger":"^3.0.33","@balena/jellyfish-metrics":"^1.0.293","@balena/jellyfish-environment":"^4.3.5"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.3","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.7.3","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.18_1625019165493_0.9150165180919201","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.19-renovate-major-external-major-d88673df7a7a99349eeb1997c56f4c381dd20496":{"name":"@balena/jellyfish-core","version":"4.0.19-renovate-major-external-major-d88673df7a7a99349eeb1997c56f4c381dd20496","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.19-renovate-major-external-major-d88673df7a7a99349eeb1997c56f4c381dd20496","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c4d3da618dd527fdf69ba43329015f92c39b1480","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.19-renovate-major-external-major-d88673df7a7a99349eeb1997c56f4c381dd20496.tgz","fileCount":196,"integrity":"sha512-Vpxak0x0S1lSiH7z0APNssta49PwTeY3Cud4qe7YDr387Sv8YCuOeLtZ9ZjFISgLm1+5/BBmxI+M6bIm0/5s7Q==","signatures":[{"sig":"MEYCIQCmOdwL3Oo3JgZc+cxj4LpypgETjfAkyW4u8YvdBvhC3QIhAMcIBP9lOuevLr2gwfCEA6Keg5tzCFa5qFf0NFZnVQnv","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":728929,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg29QUCRA9TVsSAnZWagAAX60QAIKEwYwvLP7FTKgJQc5r\neOGEthR9uTUaN009VqUzpASfZQ0u54GvPgwgA4Px+Bx5OVicjBkiRjrEiYgY\n3yyhgtYjTjPA5tafeu6sHJZ59pS5EqVl4HryeANKzJNpvgbrkjAgNCYIG0Ba\nL6Stl+WOtJBApZvx9nrKrtCaIFqC1FHG+pdIqQlfnyxTMiXay7u7xUNKrQv0\nag87v292o26dkOJ66m2A0hqcvsjKe6pY5Ro8x65W+Ia/pLneribsmL/a60wz\niRQ4SENmskqJ0i5q67BUdLc9jaJm+F8zPf5CKzLnnMfKfLvBWOjjlMEGSJ7v\n+QJS06vI1f74kjNHaQ0XlAn75RPy+oE4xfVLjOw5WA8eSxVXAz3unRiYrfWl\n56lo0B9kJdLioLo9wlAgf3m/CqYzvQRsWnCy32gc+vMzg64/Vzqw/lC9EECa\nFhc8X3VGuzdnJAnXcuisfqBrka7YkpzrOFPZYHIRFPWHRgBEsXEDNdFq0H9+\nOkM9AZ5WEeW6Da2Xql4LZx/aKMkl1RfyYCCaTYuV36tgFS9Oj/8hMpcLbmiF\n2+0j/nLkt4PpLp36P1V2J3H69tY6LUQjStofF/SSxr30BJOvkeli4lGmpnfF\nJTZwt2kVdL7pmwRJb8JW1EDBRSBbg2OMKIoYnIsr/0paCHrcz46HU7UKcntT\ndPK9\r\n=1jSj\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d88673df7a7a99349eeb1997c56f4c381dd20496","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.55","@balena/jellyfish-logger":"^3.0.33","@balena/jellyfish-metrics":"^1.0.293","@balena/jellyfish-environment":"^4.3.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.6","deplint":"^1.1.3","ts-jest":"^27.0.3","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.3","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.7.3","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.19-renovate-major-external-major-d88673df7a7a99349eeb1997c56f4c381dd20496_1625019411705_0.8637366359663132","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.19-repo-sync-jellyfish-config-default-b5f7d39816208e5442793c9a57acca909c572fe5":{"name":"@balena/jellyfish-core","version":"4.0.19-repo-sync-jellyfish-config-default-b5f7d39816208e5442793c9a57acca909c572fe5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.19-repo-sync-jellyfish-config-default-b5f7d39816208e5442793c9a57acca909c572fe5","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"92b65d2fa46e4c8e014bef68a6035492a784c6e3","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.19-repo-sync-jellyfish-config-default-b5f7d39816208e5442793c9a57acca909c572fe5.tgz","fileCount":196,"integrity":"sha512-evFFcWLgD6ak2mxefLymMWwXimE2K33jpmuIfS0zti3r0mcZ+WfRPeymrQ6YDOtMqqfoi8X9o+N3htH9+TOu7Q==","signatures":[{"sig":"MEQCIBCM8SSI2dKdgM2G9hDmKWcgsRHc3EBzjKZ8Q7K7xqyDAiAQEXJRmjRLHtO6L/fPMywdLfT4uNENeN4pXHzRTqwWng==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":728959,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg29S/CRA9TVsSAnZWagAAXHUP/0P3qP/ZuIEAD24T8Wgp\ntiyJQXnJjT4oW/GvBUCrh4X3yOE4ncXU6KCwbhyBOL8udC+eW3WYHkA4UP2J\nrxTebg0VLzniZI08nSlW512kKkmkcS9ajJE8nZA2rkvJtz6KiP0IQDngTwas\nT2Oos1arANrfpsDFmUggVUb/tdncVR04YohLAg9A8eUCuCZHqamDYCRNa4hj\nfkik5lM9UBenEsEeuw77uuBB0BfVXnrAAq2qAHFXt88BffKnJfAfrtER8+lh\nDA/efacIpi9mJIbcGwzIv+wGsoeVd7jdEm+pc1114tFBLMYOyJoCC9DyrpRN\nGLw3km9at2QjbgGzIBQuFeqhPTHh/SBjQuB94u+4BAzwLVXnLEilpReEyZhY\nSpQmV6oOwwLA0nqma3C/IIBbzelMh0vmFQWbDTZ9N9WUyucQwFaVkltlNuoU\nRD5RA550fUQGjzqNMnRbpF/Olrg+jBcZ1wb3cakPfvXdk0R7/R+Szq8zViVP\nL6fCZQiq2nDmbtnWbnWrjKNftYym58+AkY7tAQEx/TfL7hW6HtNyeoFIDbmO\n7aDlLOxPEgXMhwCdvSvYDT46xVHgHQ4qEYQI0P/b2cA79X5z9ZJvDpd6WHmi\nIiLWYva6h3hjMdopREdJCr8Q7Cw14QwPjOJ0jTHX1W/DM0I7q/hv9976IJsb\nFdlO\r\n=DE+T\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b5f7d39816208e5442793c9a57acca909c572fe5","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.55","@balena/jellyfish-logger":"^3.0.33","@balena/jellyfish-metrics":"^1.0.293","@balena/jellyfish-environment":"^4.3.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.3","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.7.3","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.19-repo-sync-jellyfish-config-default-b5f7d39816208e5442793c9a57acca909c572fe5_1625019583007_0.9764575377719227","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.19":{"name":"@balena/jellyfish-core","version":"4.0.19","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.19","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"187e1c572b15375043f45505f2eda7c165e986f3","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.19.tgz","fileCount":196,"integrity":"sha512-IrEx4oYjjFFKxODZLHHpJ1UQHzsWYbpBMiiFQSKccNwDloTXnQPPQPwKoGvQkOML/DHEzdVl/Crxc7xif14bqg==","signatures":[{"sig":"MEYCIQDtq3AkvAxaAodCmD4GF7as5jiVaAQCreyKuLKiMlih7wIhALkteswZ06n94uycGj6PUkcaU+nULCe+jTe+cF1BGpWX","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":728883,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg29XyCRA9TVsSAnZWagAAqp4P/2DdTVHw6o+8m08F7L+O\nSVZvcP6pEfbum/utDujEvGSBEeboCMuDyfgj2FEXuVra1XNUD5OgPPz4STpu\nU065mF282r7t9FO7DGmel8WmBixZdEtOxM6XA4wcIHlxyJDd2ZbPoBDnJm42\n+M0vEVpjScvjRG8gy55lIHk2QykG88+wiPbW72tX7QktWMelVxAjXLf4hYAh\neOWiNSf3Pr51e5a9EMcnc6TayAwIAKl9BGUDwwq2z4DUE09wfhA+9eg5Z2/M\ngQ5QWmSwg5uBr+AjOW93Yguoo7L/2M0Y4PVZPHkUIAN/FIP3XP8tBjAWRJPr\nEa2fb6LaUR4oHqV3jDpd4UxLaZ60Kgi6RgSVinkybW2S0l1FWflpgHvTcmNw\n6oFp2ij9jBg+n3GF3uD5boCOsB5Dbs0X88RuWY3sWE9Pi7iGgwhD4nc+5nQA\nsmMwDKfRefIJ0Q3AfZHA3fvpEY7t6CsNH8lys/+u8CFZIdE5VoOSXMyEpi/D\n9rs3KxET1x5VDlzpnsc4wzqZRasTuG1ICFt63gfFnssmbYARlHRuCsj8SRN1\nL1hdJh+IQSMw/3ehqLdnllfaN0+Yk9foWZfbk7uBXKWq2jc2+3aTGgkNMedB\n6BuCvDkU3OyTex2ND1ZvHHKI9X1uD4qRhAEXILz3nBXx3U00rcAlACdO0Oi0\nt+3p\r\n=U7nO\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e5cc16080e2e67501703731f5dd8c543b3e77082","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.55","@balena/jellyfish-logger":"^3.0.33","@balena/jellyfish-metrics":"^1.0.293","@balena/jellyfish-environment":"^4.3.5"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.3","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.7.3","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.19_1625019889685_0.8246556615541636","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.20-renovate-major-external-major-0ed803bc1dffe1e01442a6414dee2713d2565917":{"name":"@balena/jellyfish-core","version":"4.0.20-renovate-major-external-major-0ed803bc1dffe1e01442a6414dee2713d2565917","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.20-renovate-major-external-major-0ed803bc1dffe1e01442a6414dee2713d2565917","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b3e07952d681ce41506aa08943cd27347fba58d1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.20-renovate-major-external-major-0ed803bc1dffe1e01442a6414dee2713d2565917.tgz","fileCount":196,"integrity":"sha512-TTCpn7cbefS5xy2NNXQPQZ9XQDSHDpyR8yhJlSiBwf5bRIQK3ptYZwv9OinwhqmDsc1Eo3SKsC9NIUK59XrVIw==","signatures":[{"sig":"MEUCIAaWerfYm6HBYK8eOL06u8/y6P35LyQfcNigsGqh5tmBAiEAoZfsZ0O4MusQOcWpOIqGO8Q6PT5HbmBXbkclhY7weBM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":729025,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg29dVCRA9TVsSAnZWagAABPQP/ier1WDkhJlbThIetZcd\nnD0gKwsQgCj8M3Rmo+4zyFqy4wSgZjiZp2hjNUj6gZe1TLwtegjOZffJEMC4\nbWjJ0VSOcEPk7ORcqg4FlwW6xrUOs8vBMvJsWPnLFu2cWez4gZLdBmrzdS+Y\n0HziGF56WNwF10OYFWRuTxqJe4c5yko8gfQc3W0QK/+qQ7gnOx9Y2hBvhPgw\n3qDrlxNeNgELfetmWki6AVTFXM+PkgCP1+4Nlz3biK+ltLp3SXfPq39moZLC\nqTDjgcqR/BbeXsHZErQxZUlB4R3hWyMrwsOYCUbHO3U/mInL9kb7hSbpH6OR\nULkyUZ+Q2lL41USCWSN43jm//FpGPE7ZaLYBQHWZqwz4NzM5sLD+dshkNVbF\nlemyoGSNYMuEx97fNhccOK9gnG3FcfEIJGiBhs0jdvjVQet51qYxU+HGtugj\nW0l++JPPEaoeQwA1cKo+xUl97x0vtF+XIoyPz9JRbvjKbXBpNzrkVf2XE07T\nIXJaehvWqPF9Af53PxvRyqgBGWlg8HiV8ELbu52RdEX3WX74onfUCSNn+OGs\ng4IyqpAp5bx2LFsJIO4piN0/WVhSOLJwLqvn3i/ZflPnpDUMV6TEOWieahBW\nYk5QehGDl74RYPHjw5ASOrpSx9UM1rgkupRSUm+18mzhllmsqSfhoXv4iz8a\nHoXy\r\n=9DyY\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0ed803bc1dffe1e01442a6414dee2713d2565917","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.55","@balena/jellyfish-logger":"^3.0.33","@balena/jellyfish-metrics":"^1.0.293","@balena/jellyfish-environment":"^4.3.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.6","deplint":"^1.1.3","ts-jest":"^27.0.3","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.3","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.7.3","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.20-renovate-major-external-major-0ed803bc1dffe1e01442a6414dee2713d2565917_1625020244686_0.12666150302224488","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.20-renovate-internal-patch-3c0c0934ac58628fe221c5e6b1fce31d63c7558f":{"name":"@balena/jellyfish-core","version":"4.0.20-renovate-internal-patch-3c0c0934ac58628fe221c5e6b1fce31d63c7558f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.20-renovate-internal-patch-3c0c0934ac58628fe221c5e6b1fce31d63c7558f","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e1993f2e165426a20ce60dd928a9ee28db5bf5d9","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.20-renovate-internal-patch-3c0c0934ac58628fe221c5e6b1fce31d63c7558f.tgz","fileCount":196,"integrity":"sha512-lopTt12pen598Q1Almjhl3jW8pTmsgYOxlWpS+fDi0jUaXBGOF452w/LM9UieiqmFttVyVHypQ+7tMAoB8XhIA==","signatures":[{"sig":"MEUCIFsrim/Yx9q1SB19MeHnjfpd5qEZOT+Dgo8Tk+0bLkJjAiEA5x2fd++cdD5v2WSoX2OSydkPAvXhmQalswixM124aJ4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":729019,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg2+RACRA9TVsSAnZWagAAOlkP/RQDry8UOCS2fpXz+LHw\n4I3Jr2sjtR3zCP2UMTPQyPbMw6UzJsyZLsMMVcPI0Bgt6kkg5NaKFl5y405a\nLhnNj2g7A/LSewivX2IeD98030d/+rd9vUFAddMK9VZ0ZxLAMCkSKwHUHv92\nyxbOSVW2Ovgbr3apKvLMhEt7eK1zjNUwOh3pzsUANPmc2fBm/lWrYTvHTt9v\npKeSSkmK4r+z2b7gFlObvJCnQJPCNHXeu+mOWCXdxpMJ3BZJLsKpaGtIrJ9b\nWvLwFAmV0fyaawqbKGxl75BoneggKMlqLEGViVNpn/WVvHLGMKpXNv8lHmPr\nLDXs3Vs6Yc54G5nK0nVtocJ3lYcu3kJTF1twiTNbEfH+PwrzkIHqysGoRShK\n/fAQNhezMaHCAVs6mDFGpg+Z94EJQwqAeScWrPtvfg/xxZhkNmIDF4dh3j1j\nExisi3rDm2QlVRw+AYq6plHGrOl9ob82km9bn0eKIvK5PEmT8QrtUoi1ECuC\n8fcfbyg0p4vfT6QVeWX7ifYD6Xb9+flYa0kEpK4uR3q8RR7xUhKy/1l5ARvB\nvbKSXeCRTusOl2duqdldAJEcgw7b8IU51a2WEIoMsBTSZHTQeNl3P2ry2Pjl\nhhdhJ4Q0Tzr86K9dBkdw3u2KAQBVGqcZsgQe4UMFNzrBIRyWzt55QfZUennm\nI+/M\r\n=evzT\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"3c0c0934ac58628fe221c5e6b1fce31d63c7558f","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.55","@balena/jellyfish-logger":"^3.0.34","@balena/jellyfish-metrics":"^1.0.294","@balena/jellyfish-environment":"^4.3.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.3","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.7.3","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.20-renovate-internal-patch-3c0c0934ac58628fe221c5e6b1fce31d63c7558f_1625023551445_0.8808354804174976","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.20":{"name":"@balena/jellyfish-core","version":"4.0.20","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.20","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5d70d40cf140ef608178c9572ebbd0fc0fa83780","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.20.tgz","fileCount":196,"integrity":"sha512-8L7NK7LrxfjIpGV0on5vmYJ/1gjNeSHLLHzEBU6vg3X+587y019uQ6sSO0PBkyScCMONIgaDmqVgH9Vf5wTZfA==","signatures":[{"sig":"MEUCIQClxEfSq2cTZ2IeSRGBlYnk/HnC+wS291I8ES4BfxTq8AIgI1miHLnKEwvJ536YROWWBmUW0IT/3/jsiDj5wUV00a8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":728954,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg3AoLCRA9TVsSAnZWagAATSMQAKCaiIpoS6BIAN9ILP8P\nFXntbFxkjFfUr/fR3XobwPBHgoA0jWsJBUzrzIrUeblgWbSB7HeG5NKVLLYD\nQl9FBgthc+2hZ5dI7B/dyUey1f8pZYMEd840C4F0nyy32tHoQlQcRjr9epmc\n9K6UTJ+LdPNsLR5Dz17xTMJ0+eeI7thsBxcvzqSi6MDipby5YCQlBlQlITF+\nhPbyI2BdBPzgDCFGRQhSsB6bQVynJWMumkdWLExV3EPfJ7PIvUUcCHdTHsCF\nIZkkHYMKnbj3AYBNlveV38jgq8DsP807HbOHkhVjJ/+RX3C6XinQPDIqkD92\ncUDaWiavx/d1gkMwY9IzLIK4x+ElCDY26rlB9LHLBJ0F6E9EkWOxVm9PZwir\nmtsArvezgd0SMTg4nb5m0uclj3W/vn/Ic9cKwIu7SfhZDg+qdQBQKzGJKAhg\nBIE+xxEY3mMDHNdt4dspYioPq8BWmr/9HQ5+xsBjZJhY/lOroRGwcYcIIZxB\nlTrgVmtY8f89Wv8YN9faRgSxANkvoFJEn6sPoZQMptxNvTTvtRLLEoFDwJ0/\n3IYEZxcrenplNevHz+a/fWjY6DQK/uQ1ZLn+XRYBxoxsXOTSvNsqgTikuOoN\nEBYSm9b0JUcfZcq15ctVNB9btOdpgVFD2rxU1Kicgu1KA/G4nPjHgZ4ntK9m\nbCNO\r\n=5pfp\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"671a3184abd7dd25da852734858412aa52118c90","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.55","@balena/jellyfish-logger":"^3.0.34","@balena/jellyfish-metrics":"^1.0.294","@balena/jellyfish-environment":"^4.3.5"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.3","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.7.3","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.20_1625033227207_0.04232301064839694","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.21-renovate-major-external-major-f116730b0effe47837690f7442a962a13ef15628":{"name":"@balena/jellyfish-core","version":"4.0.21-renovate-major-external-major-f116730b0effe47837690f7442a962a13ef15628","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.21-renovate-major-external-major-f116730b0effe47837690f7442a962a13ef15628","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2b55f3612894db0c676d7ed92c305f6b640783dc","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.21-renovate-major-external-major-f116730b0effe47837690f7442a962a13ef15628.tgz","fileCount":196,"integrity":"sha512-5W0nt8HVMKbWbfvuMDqATqdIqvybu4tbLAk+Ek8QXwrNspUTAFNojnnlyIf3F78E8Cx0+wIXKngxf5bVDo5pVQ==","signatures":[{"sig":"MEYCIQCsaLfXj6Vd24DT+JawJ0EOBJZBgOHE7LmaCvJwm+wPHAIhAIHNXUUI9IIoEeiYMfHOCyvP3gYyOmor3U6wcttluiv/","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":729096,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg3ArKCRA9TVsSAnZWagAAfkQP/2YXXNDq0G7aYYnlX2FR\n949/bSpRojcwCNstiHa5SrPawt0WSLj/TMAT54WFM0pTaSIX/u8UWhEXIVcw\n7PIuQD987dgeomm02zennPDtsSK3qNMUlVXQ26IhRGygnB8Lw3YEilyjOafJ\nkeiyPHykw6f3jSQVvYch2yNJqQ7o7uwzH3P5KcLzsBfrZGWHmPYmgc3Us7/D\n+bDpRSSAFyE/itLHhjeoSWBKPc85xmqJvHS5O9H99Z7Mhymz0suKUeGFAjyo\nauysm2EwxQNQJ/xZlXOkWjUK61x3YyskPLmTJmX8wvnmPQWPXhG3F6kPuiJO\ntcyPCVCQKs3cLhsYZfYXR1NG47kugmmN4z6vTR/6eJKn1JqM4BHvxrkAt3QE\nKjvljbq1iUYWJxA9P0vUPdsyA1KUwPSL2G6fQh6ZemqaWhO28ENAXkbjnld4\nKmqyJRQcqzdkKXknXuG7rgNeeZ11JoZVGLG9iZzCg+rb9WmSCvanvOnAlR1l\nc/CD+1QnDNwcEQLeuF+Ghc6o0HOb+rXdmwLE0teHKAeKoF45La0Y+lst++hl\ngAI1BEYiYVzb2UU2etVOfJy2iL11qZa4BX8twPhXqnfR7wLVoKEJ3rsKvEjE\nx/6AahSp1sA35aLwr7qepDbh9og5qYnu49UgcY4qsjcNlg+zG05bvX1oh6mv\nX10E\r\n=GSVq\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f116730b0effe47837690f7442a962a13ef15628","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.55","@balena/jellyfish-logger":"^3.0.34","@balena/jellyfish-metrics":"^1.0.294","@balena/jellyfish-environment":"^4.3.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.6","deplint":"^1.1.3","ts-jest":"^27.0.3","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.3","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.7.3","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.21-renovate-major-external-major-f116730b0effe47837690f7442a962a13ef15628_1625033418011_0.22614141329657111","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.1.0-loop-widget-d3c219911e4da8f8a1a2db170f25162bce096fa9":{"name":"@balena/jellyfish-core","version":"4.1.0-loop-widget-d3c219911e4da8f8a1a2db170f25162bce096fa9","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.1.0-loop-widget-d3c219911e4da8f8a1a2db170f25162bce096fa9","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1db081d6355fa02cdc35c81e1f1bb7a1f26b5c5c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.1.0-loop-widget-d3c219911e4da8f8a1a2db170f25162bce096fa9.tgz","fileCount":196,"integrity":"sha512-HlzDO/Trq8Ugqcv+Y/3iL94iWrFhzbDYZ4Jt5NGG0onEugkjGRHgUWJpP70/+JsQNcamBUk5ANVkmGebF+/M5A==","signatures":[{"sig":"MEYCIQC27LaFxV8rAU1UuEJPyo31NPlCa087Vk2fUw039IlWigIhAN0pwbK2AbGUtAtr0c6EOGbqsWWIlORmE8wptIkB0KEa","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":729326,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg3A0uCRA9TVsSAnZWagAAxTUP/3sc8ZbFaqA/Qsm9g4UK\n2XsMaTCfXq2VeJWqBEe7h7iaC/wTeNp+f8LA1WSpT8r0UIic/OnbYpbN8NYT\ntTJDVlZGrVVBmOsRakp19vpIv51hmtmEN/NsGnVlSYC/sncFJUhZHA0ycNDQ\nNJULo3unPbXzk6kqZtTDzv42Aw/SwzYqovGE2N/aCOECtmjqEu2Xf5nwMY4U\n+gcui7uhfv/Rs3DtURhhk+HJl6eDP22UUDbOQvReDMGOr/izB65zwuQtkjkF\nV6N3nyBPYiBGkbpBXX2R9zlfo708Y+RsXlv3kAd17WeDLm+u7oJNzd6ck+tm\n+sjFPw9KQK3pPY7MNq1ecwZCxYpsuTom1swHuguIPHq42H6WbyqOTzYwNvgr\nUpwNiTzCK4UVkdo91abkIuL9Z54jZIgRqqHyPgt5daqoK/0b0jhScJZ82QMW\nCY79TUJ0xZIxgeMALjS4JW7gnQ881BELfQ5c3wIoLLajnopuFNK3iFg1XmPx\nqtiLsKWpcneENw5wffHBmge7NTYEJqs1sXmBIjTu4ZVQ/3iv12I55phouZ5+\n62MLU190yv7HYk2p5a+xtlgX2kX2L2wvxGs1ogG3Hz/e46gWAoIuqSb2j3kx\nN0gO0HNsn6tCJ4XuPEWt+VT7SEpN/lsSqbmXivgsltWr/zMf0gdfSTkKMkqE\nC+KZ\r\n=zAJ9\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d3c219911e4da8f8a1a2db170f25162bce096fa9","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.55","@balena/jellyfish-logger":"^3.0.34","@balena/jellyfish-metrics":"^1.0.294","@balena/jellyfish-environment":"^4.3.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.3","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.7.3","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.1.0-loop-widget-d3c219911e4da8f8a1a2db170f25162bce096fa9_1625034029785_0.1993292877258792","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.1.0-loop-widget-cd8f5085ee1cef4714d8aaaefb8e312f5b1e0502":{"name":"@balena/jellyfish-core","version":"4.1.0-loop-widget-cd8f5085ee1cef4714d8aaaefb8e312f5b1e0502","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.1.0-loop-widget-cd8f5085ee1cef4714d8aaaefb8e312f5b1e0502","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"98d371db6c56430c74795b4d3079916581a7f071","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.1.0-loop-widget-cd8f5085ee1cef4714d8aaaefb8e312f5b1e0502.tgz","fileCount":196,"integrity":"sha512-nz4aeB+Xz2t4inJBHqvgmaG6ANhhuEjd5WDSdvdHeN10dk9W2LW1nAeA5YoqSgSIkZ4pAhZh5O3tbcyRPO7s5w==","signatures":[{"sig":"MEUCIAmdCIr3qf6QIZY1cYNXExHwLy2z/TG3HzVU8CoFxWD3AiEAtXKX8YD/nPc4/DBOEmEobLyPt6z610/rAmRJn1vMXZU=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":729261,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg3DrQCRA9TVsSAnZWagAAfjoP/i2vRMuhpE567wOulcgK\naXmllnQ6/vYuFJA9B+n5lG0f7eqaiODLIRzuDOucqIN026aOXbzqQ2YHrkmO\nVoSyYvwTpRUaOmB+YyRxPyLEYuSkXZE7G0KRx32zIwqDUw8P0abYYpwCMkIh\nMZugd1w1KEYiO3RqgRkgZtOyL4jlbWlCxLP96qa9pBKBi87CyQDJZc/MuEe0\nWM4hLZDNMLYsPahOijSsDwoY1OkdHFCMkQgunH9/dzyj/93CSQCjLbE+5l+7\nsUwGgmT1aNRSdE/RIKU0Pc10tfLIPJ4gWuR0auCHKLNce4Remj4my6bNDlws\nvnZzyib0yqKyXTp/EDQZfXXoEPdWkAtr7n/vPDzI+dQ/AKD3YEYaHgeyL5Pw\nMMfavN2b1+h1dqQFQUWs/H8c23Fg3pnlMECzVBae4WvhE/pWcO6MCXigHUbh\nX7aHcg5Ni51LzVXxu0B962R6aWWZmEVHX5UyWMH6JaUB1qSEiBR4FqZFLgNX\nWehrLqUnuFxwSoWFtbLilClmI3NPEpdqKLZQHnNvedR91NmBX0ki5KvaTJ/C\n7j7GjyTdlP97EA7RcdJYfRTSOeluVo6fArpqfaohLKnQYFW/vVbfLh5+AKbV\nPsVLspM37pnS7DqJpe7VeuNgdkmJFx73Y05JavyQi8W1ImmpBj7gJmwcpmFe\nq933\r\n=QfgJ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"cd8f5085ee1cef4714d8aaaefb8e312f5b1e0502","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.55","@balena/jellyfish-logger":"^3.0.33","@balena/jellyfish-metrics":"^1.0.293","@balena/jellyfish-environment":"^4.3.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.3","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.7.3","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.1.0-loop-widget-cd8f5085ee1cef4714d8aaaefb8e312f5b1e0502_1625045711595_0.9918790317282327","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.21-remove-loop-migration-code-3c028d44707b3df4fa9b7dc27671f758be43b0bd":{"name":"@balena/jellyfish-core","version":"4.0.21-remove-loop-migration-code-3c028d44707b3df4fa9b7dc27671f758be43b0bd","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.21-remove-loop-migration-code-3c028d44707b3df4fa9b7dc27671f758be43b0bd","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"7bc066c87fafea4b97d75e9cd379463de1550041","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.21-remove-loop-migration-code-3c028d44707b3df4fa9b7dc27671f758be43b0bd.tgz","fileCount":196,"integrity":"sha512-u7PTNEcqwCucDZ/os/taN0CdbmUzyReXg9X1+9f6dsEO9VtaE7/D2epcqmXzQuq+elaPoVeM1aM4CUkjgU6TdA==","signatures":[{"sig":"MEQCIB2KXzqZUp4NVue06CIJU1EdAInAU2s6zLQgSZqOceLVAiBq0U3CbVJ4EGD09kBCaSSF8QWr9+HG59RR5d2WjI1yGA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":728863,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg3GtXCRA9TVsSAnZWagAAb/cP/3wO3v5tQwbgCmwAS2Nv\nzhzDjmoK68a3QbvztvzKAhrwzAsJf92jKksYBojjbnkKTGRkZV2VUjjkEk/X\nZOyJZKgn5g61Xs0VGLfC0vk16t3a8g6SgXoy7yGoske1U4LsyohBLUjrrWxF\n4zVR/0p7CYlJ4vOcz3c/nkq7bkrPsh91PnU961U93zKd7Fy45U1VrPrutS/s\ntX27ggt+cpyG26HcMb4e8d0xWnxQ37wMVXvpsSTsjkI31P262xVPCes0GilQ\nQD7PjTiuf0Q1UFWtLCxx1FP4L3yWdDpgzXq0NE44zy0PR9Tn2QmbaQxdIBzf\nN59LicsXcovjWkYS7fbFtfKDyi/H8Amvh+XIWtjd7HqWUxXwbqwGnl8fORH/\n/q0ce4GZIJSmY6CLQhddh8aisSUeXbJXp/hjQuSTaqJWIvNiVZdYvdAYa8DJ\nmnsrfX3wA76Kgy0WdWOzsBro7Tyse9t65cMXGEcbb2IIX1macWqgUlXWS8bT\naXRWHqLvoMmJubDKGaC+izMbhZ57UcJ5SUOOYCFkaq89zehz+6RJFW8qLH34\nQPUgaaAiEp8CDMJnaCXqWmldBSWR88RCMutmKMTAYCIczEuO5pmHj0SWxVsj\n7janIPCBu8mgCFzAdsOcNGLy9S9+2gBam++RtVS5G++6xnAidzBclBqTh5Z/\nOnYV\r\n=CNB1\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"3c028d44707b3df4fa9b7dc27671f758be43b0bd","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.55","@balena/jellyfish-logger":"^3.0.34","@balena/jellyfish-metrics":"^1.0.294","@balena/jellyfish-environment":"^4.3.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.3","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.7.3","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.21-remove-loop-migration-code-3c028d44707b3df4fa9b7dc27671f758be43b0bd_1625058134629_0.27516935611219906","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.21":{"name":"@balena/jellyfish-core","version":"4.0.21","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.21","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"271b3b34628bd743c61841125ed6b36eaa1061c3","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.21.tgz","fileCount":196,"integrity":"sha512-pf4PufWBVwcJHLcSJnZn844ESmGujQfwIAKcM5Qi9eo8OxX62wPJ9/XnOUGHzji0LVUN68svvC+M7FIAsVjsow==","signatures":[{"sig":"MEYCIQCJmleYxeupbSUXQOOMTbghbzgYT5ne+N+9Qsflj/yEhQIhAO/NtIjM8dG794UlxLTnnI4c7mgYMMDBEZpGaEd6xlHz","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":728795,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg3IP2CRA9TVsSAnZWagAAWr8P/3LVozakCj1OQqaTSoCL\nrM2tYq+3MiNHbxJ+Y6QlwN3jbx2hbHhUBEjLSizLz4hT9nsgyxFWB52XD4yr\nVeFYtMD3p4GaSyoo3cRHsxl3b4Guo2gZVBZOCUlzs7bRBvAFJQoYRPxGyRsV\nRdFGfrmOKNgTCgbN4N6FKlgDHv6MLxlmWlT3b1jlytZ0gEIdyqLXWP6WtOHx\nC6xg/3fToRYhnLBWW1CegaYgI2BNS5fR+sg+diyHDr2X28o5UeMdWlV5cDUh\n1jzDz0d8L81OFh/24AJLo+yCmZvONIzzWZXJl5a9jKzsUNphe9Wi3+RPG+ut\nsBYJHFwrbjYyYodw3RUCZhcng789HY+13rEx7qef5OA2ueAr5M/grL7SDsmK\nEla+Hxvr6TU1QrEYnx3TCuhfMzIc2HkFPCDe5NfR9U7OTYCxrdA6o1Z4EYjt\n4zI7O0TUgq/lfHGp2TMAD2vyy+QUBHey2J5hqgmSkDu/lXhmkPisjBbmhGPZ\nkTrESxD6NtQOtqw7WmLJdhh4wibhhuOHbxJRggDV6Ihpu4hlz+fCRO1wqE6k\njpPtk6Sw1CyLgTc5394L5aZJ47tDcgPB5tMba9ABnqDc2EMTTroE0Yn0tTfQ\nCynn9dMAbWFq5eBf4mPoH9ZRUjjNgrDbJcHcqhYf+pQ4K8VIVTL8AytWwFWo\nwSx3\r\n=EDJT\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"63810871e0b13228ed1b5fcb5d1c392f381f8cf1","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.55","@balena/jellyfish-logger":"^3.0.34","@balena/jellyfish-metrics":"^1.0.294","@balena/jellyfish-environment":"^4.3.5"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.3","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.7.3","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.21_1625064437626_0.743003152676281","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.22-renovate-major-external-major-8bfd05c3a0fd3ff1feb4ed894a861ddddf4bf9d1":{"name":"@balena/jellyfish-core","version":"4.0.22-renovate-major-external-major-8bfd05c3a0fd3ff1feb4ed894a861ddddf4bf9d1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.22-renovate-major-external-major-8bfd05c3a0fd3ff1feb4ed894a861ddddf4bf9d1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2048f34c123e5fd0b51e6475d9cce9dc2fb21b3d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.22-renovate-major-external-major-8bfd05c3a0fd3ff1feb4ed894a861ddddf4bf9d1.tgz","fileCount":196,"integrity":"sha512-y6r9MWWdYvXOS8+pBxwRJ8jSx0aqGNu8j+brAFJXhFrWlr/1NdHoqLSAi459JwPMNyZGJe/zJDFwlrtAO6lBWw==","signatures":[{"sig":"MEYCIQCAyt6x0HaOq6AbQEQCrD99flZIAihi7Z0jxhCiFvcbZQIhAKdnlT1fFtMalAdAXmJUtj66VW9dzD+5ersfnHo0dbZN","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":728937,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg3IWUCRA9TVsSAnZWagAAq7QQAIo4aP3bl/Ar5wbRPGGY\nocoV2GioX/doMV/+CH4hxwvQ5P2OGrDCTt2iiJYPyJxtPiMRE8i3ix25pNeN\n03z6Ql54VsMmDUPNsPpEHzp1nJDG09W5m/9lPIO6Sbf5rZ+Dy80v9YnDcwIa\nRsRpQiUaVHE6ZNcpX7hyCYRROtNJ3yZ2I7rvIuuk3rZEQqh9f2N+5ZoXwMws\n1TuxX1m3OczR7r8JIq0iKJZOwNIJyaYIzxDf4NEtyCzjyPVyl4KAcn9UgrOY\noFXyua1I5G7Wqn4wzTKIAGa/zuI8FMEMHNTYKv1OVnz1IwXXoe/BFUCHufJ8\nuII4YKkUTInmwFVV2TJuoPhMDw2L9zTpJ1+Onz2hbnlK/byHiA+7CCdzxfNy\nyn1YZGkh1pLJnXvbiVR8AabWHuxLx2cO5hFpwk/zyQWVsn4BCGsiSyyo8Czz\nBQfHFr4zfxYs3XftRf9SW24Yd/vrLit2jhLFfDiyeyXP7q1t5X6ksmw38sKA\npupwAkBNnlo8pncryBUP1UqiY33JdQ2qi3rQoRkL0KFa+oQ7zS+zXh1Nk5cj\n+gMS9ZS7Fw6PyvjuPn6cAIrzCOxGRlXoYS0bG/+dBV6fTo1q0Is9Wp4z/OLR\nEC2UdYgIxXu9vdh3JGg9tMh4r1ma8emR0G9jjLsGli6o3RSh7D1KYmlMcA9l\n0ZKM\r\n=h0jN\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8bfd05c3a0fd3ff1feb4ed894a861ddddf4bf9d1","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.55","@balena/jellyfish-logger":"^3.0.34","@balena/jellyfish-metrics":"^1.0.294","@balena/jellyfish-environment":"^4.3.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.6","deplint":"^1.1.3","ts-jest":"^27.0.3","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.3","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.7.3","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.22-renovate-major-external-major-8bfd05c3a0fd3ff1feb4ed894a861ddddf4bf9d1_1625064851689_0.9287732168301372","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.0.0-fix-create-export-c6d0162568c83328f13fa442243288c6b01ff296":{"name":"@balena/jellyfish-core","version":"5.0.0-fix-create-export-c6d0162568c83328f13fa442243288c6b01ff296","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.0.0-fix-create-export-c6d0162568c83328f13fa442243288c6b01ff296","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"24e2cb345b606864a945c3531305001e140d080a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.0.0-fix-create-export-c6d0162568c83328f13fa442243288c6b01ff296.tgz","fileCount":196,"integrity":"sha512-jFIMXuX3WZKRZsf2Vh8XJzfris9F1L1F9jUSmD8AeYFUGMepicSDlGiYcQw6KSSUlVKAjqGNp4KpSfZJ2ShDOQ==","signatures":[{"sig":"MEYCIQCsiU7eA+0Tp3EpaAskMxXIbM2XtpSY5H65GtA5sP2dXAIhAPoQjLvK5Mx6PpJnPTHdoDO8cLah/mWAwPlyInDKlpOi","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":729160,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg3mfOCRA9TVsSAnZWagAAqAgP/0gWwktEOMQE6RtjHy+e\nbd10Qq6csG2xPBtLoC1FT1cCR7GHUQ8sc0U3bWkcN0VNCBcwPDSV2vbw3kHV\nY0cX6WPJm/8obs6WzNPA77DSpG1NOZInlfyy1f1aqF8ha8/sFXtXnAWnCJ2q\n20z3finDIwqZj+5sJ+ajU0QPXuXHzNWg3klZAoyxdLg0/SMErHW8LjhmULdE\n0OGUgRUjvdduMUwjmH8sURsfe23/aVQ97gCX8xSqn+18EnNQ2GbyiixYlrsc\nFBN8gciE6cRVoa+NBL2Obtj7TsLD6GRz3mDH8Xvi+Pkao5lI5Y4tCLXY9rvH\ntDSO7GOJv0wLYhwdB1BoStn6aiQ/+IrbGvLfl8+jkLF/MFnDwI26PQZMFCyp\nN3jQbmaK6K4FfdaN2ZwhPhKHOG/qeyYAaXNa7U/VCh9rthEV5LIXiFN84CYv\nkPVm7SwQm+spcLZaoWl+cKBu1THHUArY+lAm88U99EZBBsGUXpnzwr2+eNzj\nYMMdKy6RDv8S4xtspdoXy/oNV3fH3up0DgsU4v6QJYTGcksXnkFDkVFXKWXR\n5oKIcC3khT48TIXJsbsg6FStGMIzRCXHnY73lg8gROcP6gktAZH2h2ucl1XL\nA5ChugVpermZXOLKxmYWH8WQW0YkIY0n/mrxNpU7U3T6Qfie0JaQPpOd7HSH\njCR2\r\n=+iO0\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c6d0162568c83328f13fa442243288c6b01ff296","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.55","@balena/jellyfish-logger":"^3.0.34","@balena/jellyfish-metrics":"^1.0.294","@balena/jellyfish-environment":"^4.3.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@balena/ci-task-runner":"^0.3.3","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.7.3","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.0.0-fix-create-export-c6d0162568c83328f13fa442243288c6b01ff296_1625188301412_0.22083058630905184","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.22-use-go-task-4e808a5b4271ef4ebaa8a3ee971d573f9b320f01":{"name":"@balena/jellyfish-core","version":"4.0.22-use-go-task-4e808a5b4271ef4ebaa8a3ee971d573f9b320f01","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.22-use-go-task-4e808a5b4271ef4ebaa8a3ee971d573f9b320f01","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2c3b830c6099dde62e09a5e937f568703e63c54e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.22-use-go-task-4e808a5b4271ef4ebaa8a3ee971d573f9b320f01.tgz","fileCount":196,"integrity":"sha512-3jhoGxyhIdjssfHX33I1Z1WqBOlDExP76V1/iNThW1gbE8Na418mtFaSdjXVzprKiaCj7ftvUaovYTsUehTBJA==","signatures":[{"sig":"MEQCIF7IXUHqDV1m78WmJjs1ZYHJ6mduJh2WSlqt8E/gkSpTAiBGdcQQSMhuavNFgWwZaEMQXZZ0oZKgZQlIAfuGtj7HVA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":728886,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg3pD+CRA9TVsSAnZWagAAj44QAJJ6EKyMRgvJzyOV9kd+\nntqv83X5wkXnsLO22+fcmNNglwyVWy22i4nZTMGu25mTFEzzOQaZ1k9Q5Af5\nr12yPysF9fXvQCVZTMcsPL7Tu5Hw+DV/OqZQ1u/nIeblKtA+AnXvvsGlNMNl\n6+Qv8PBsGmxgkulWdjVfWUR74qwD//VEAYqjAwJd6/+WilCQXzWUp1v70zcf\nCcdu0ZlNB0VJ5PxBv5bh9Ql1ubEurL1o9jbPmOHsqXECU3ghFJdFQ14Nmzly\nRq8ihwyqrx0m0ElKZvAPOVPiVl/Du2E7iAs8hcdJFJGtw17J0XUkTviJTggv\ncEE5aqz1Ie7CF/9BJGjNlsyqOwptEQJBQo76vv6RIKOFrZzRvLSLXEk+9fzY\ny6VtWdbKzWlJYZi+IWLGXbegUAB+nINYXrnz7nH9nOAiXJgWRCHJgCRw7Pqd\nClQpftCGHx24jEzp7zEEzeI9LGBYkC9U5C96IujAX8oEKAay2dAZg6ettNI6\nhGOJKbJIl/kigcC9JOoINUT0g+G81j9oTzRe2d3jorD3VwLrz6xHSEN+7kY4\n/qO6w4ghDqb8c2XH8Oz+7g5tmZD651USShbrVKrp5GfoPDedMqvUbFNH6xkT\nzak7VTYRotBxm81L3kNPsZbjN21NH392Rdssj9Ch2qcyuc2on+Aw35+nhDai\n0xOI\r\n=OCGV\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4e808a5b4271ef4ebaa8a3ee971d573f9b320f01","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.55","@balena/jellyfish-logger":"^3.0.34","@balena/jellyfish-metrics":"^1.0.294","@balena/jellyfish-environment":"^4.3.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.7.3","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.22-use-go-task-4e808a5b4271ef4ebaa8a3ee971d573f9b320f01_1625198845520_0.30666456010832466","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.22":{"name":"@balena/jellyfish-core","version":"4.0.22","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.22","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"31727609cf37be8dbec222424e20006d8b90dfae","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.22.tgz","fileCount":196,"integrity":"sha512-s1qmg0z+FgJEeEqZwDrhnKrbuzByyJDszCZWbHhhM0yj2N7hpIGJkwSODO2M9X7uU0/+dGFEV17mDrq25GZrdA==","signatures":[{"sig":"MEUCIQC+qM78dSkixZCh60oOUy+Wojju8hcjRv1pEeme+L4cDQIgHq5i8fGnjHpL37z636hVFxQfEXdOTzwE5onZW14B+Og=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":728833,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg3pN4CRA9TVsSAnZWagAAqJIP/1BAPRhMawuamRpJjrur\n5miCaOJVdncjRiCzyfcqX19k7JPMP7kwnfFvLDonvuXe+nWX8kv532tltdDj\nnHAVXJX6LAnzbq3yKVGyFA4n1UKgMGZpORvQGFz6SN8dCa7GUKyKuxLP+uyu\neGJ07MEbAyDnLXRykH6w5XbUf+X8RUc5xOci1/dKBtMn5/NlIirbJ0ywLToE\nPpsYS2i0hnAq4uNynExQ05yFhFmwqLimq5FjxR1ijRDTQpu/debxb/adjqZj\n6SZeaECaVceSuXdaKHl7NhkFotd/q50RPtImYhzfT+2+SQaD7+saHlgSWyb5\nAgwCot4DQFnNpgVs8w7Vu7iqe/I6OUWiZh7Le1N0+CHF7Mcw5y1tdgfQFsLU\ny8ka+oAoVG0vW03p6SPDQripOe9gdEaytWRmMaEe49WKZ7HrKpPs7El7xI30\ngx1NTGICDqzzDuxvhsXyPlQv65oYLgH5QpqLPS3ApH1sQR1G2Ra+cfQaegs/\nJ7Ay6AtAO9Q2t56wylE3OGXb+zqWo501nB1HP5A6NjnsCud2vkzsKxbuc6MH\nJwx/+3oPCF0JFV8wPe8YiAHn6Xq492peJsNz/5TQKzscbpjrY1RzFUXX4X4s\nl9q+0LW7Dw1W8TxBHbv+v/1uc4JYRPno5SZrSfpLs0f9UND8R+7bjUIqiSPJ\nC8sM\r\n=LE8Q\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"bd357963da7d22e9e5d85e1b0c22c64c6969a08a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.55","@balena/jellyfish-logger":"^3.0.34","@balena/jellyfish-metrics":"^1.0.294","@balena/jellyfish-environment":"^4.3.5"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.7.3","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.22_1625199479711_0.8324614058790107","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.23-renovate-major-external-major-fb656039c267dfb7185c647361e9f5cdb7142d83":{"name":"@balena/jellyfish-core","version":"4.0.23-renovate-major-external-major-fb656039c267dfb7185c647361e9f5cdb7142d83","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.23-renovate-major-external-major-fb656039c267dfb7185c647361e9f5cdb7142d83","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e3f787d9ba02a64ab0b1d39f7ee35d1d31785d76","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.23-renovate-major-external-major-fb656039c267dfb7185c647361e9f5cdb7142d83.tgz","fileCount":196,"integrity":"sha512-GuMJKfDZ9xBPuEFQW59ECO5THfnupgwNR576ZN7tNLu1AJOwdaD8UqG5qyK5WSTcwhcLgi9pqlK3BgmKr8LolA==","signatures":[{"sig":"MEUCIQCNlJWmVYWyKe8+7GoGbvYb70ezI7sgHswXISdyyOM/AwIgDecIVooiPftNUVe/9UoBeMzK9YBEU6dljESPGavHGNs=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":728975,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg3pRQCRA9TVsSAnZWagAA5isQAIXGauvEJdoCenQ0r5ke\ngQ3WY2gTSVKApaNy0rbIEfSv99L+tzFsADespoUlL32MkT4ben/IPsLEabXQ\noo0JNuSHoz/Bn/3TH9Z4WfoAc0s/OuWuRpXWykLDScxMTB/yHQxqTFpHdogo\nZlfo6ymGk7aT3jQ0GVYqsVc88+X/i/4qCa5yJ1llg2n9aFGKf3tD4c+uV9hx\n9Nvlq0yd8lxuEkoXsBGWszuupKYMK2bMuyS+zEy7L8DsfY2XbM7vFMHEekZz\n4+KeiQis4PUsDyUY2JD+FSBRsjNAKNy6pNkMt6Nnf41hDDij7s94FtQGJz/E\nXGtof/wSmkWYKljdbB27zoruO+epZOJlB5oSPrDBJQQd6DdeEhwkSPpzc5gi\nf6DdiYOcgfONyb6VYtBQyUbSeWIFEq16xTlJ/JM4gOWZLTECHfNKd6IyFque\nArT5D6W/hz4c+gAgcdwDElZNa5L7tFIGMcoMsNvPzIgISesuFLpVA9CsF/ba\n4I4jNQg0gYpIfHg0deq6HJWF40SFMzXg4msmJjjfSMbpWCj/BdnZ334sQDqr\nc+zIiZ+tYs8NuiX+38FnCOanhppqOCX4j59QK+tmGF5vZIFGXx1fccF5CRWk\nyrBThzXTcEcQVBS3yjoMta2xnJqVGz2vbE+DeaA/YRiUttu0qQzk5m8K+nU3\nFuLF\r\n=yi/U\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"fb656039c267dfb7185c647361e9f5cdb7142d83","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.55","@balena/jellyfish-logger":"^3.0.34","@balena/jellyfish-metrics":"^1.0.294","@balena/jellyfish-environment":"^4.3.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.6","deplint":"^1.1.3","ts-jest":"^27.0.3","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.7.3","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.23-renovate-major-external-major-fb656039c267dfb7185c647361e9f5cdb7142d83_1625199696109_0.593758101110512","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.0.0-fix-create-export-17310b16687f45bdd12d84ba75a677fb1b1bb1c4":{"name":"@balena/jellyfish-core","version":"5.0.0-fix-create-export-17310b16687f45bdd12d84ba75a677fb1b1bb1c4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.0.0-fix-create-export-17310b16687f45bdd12d84ba75a677fb1b1bb1c4","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ec73a2d43c09b3625bac63c50f4f4fb59a343022","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.0.0-fix-create-export-17310b16687f45bdd12d84ba75a677fb1b1bb1c4.tgz","fileCount":196,"integrity":"sha512-CoXbnV/1bvbWX8RGabso8QEbf9LJ8xk/BWyxNBgfqwKrElUSnIcd9ThsFCqQXSkG0Pwro3yGbnq6CQYDVKYQyA==","signatures":[{"sig":"MEYCIQDPE9kALzc79oMIchY78W5kTWTAs0P/vphlfpnyKKiiEQIhALRuSIWZhMehDqVx6gXpY/ieI+0UP/yBn0n0tnxWW7Bb","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":729233,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg3q6FCRA9TVsSAnZWagAAMEYP/jS9+2xE/ImF6+DAfgul\n6bXEXMM1iWRMXQUzQ3U/MXN/jvME11qSU82OjlR8zzVxUJYMpontLMJsfNAM\ng4+ddnsdPnCSh8kbwZiyMn4rNs0WKlDgVJmYrLgMlvqYdd1VrDb5rfoaFlyM\new9BeRBpsReA9eXQ/DqXi8/d5iF47qWAZTt9bSFaObI3bCfN0PRj87xlcR3m\ni3elOZMyR4hQ974tCJYElZJ42agmBczlQ4GAicN9fqMHMdbK4txw97CcDaog\nNIjNo+G4udBBhF0OU5cXvnRpadpi52jm9zTxKAQ8nVvdL+eBIAQ5utk/W09i\niI6Ams83DXSZBCfhj9GqL5yCzVTVk1OP/pMBDxepaTEbaelMXlUjRsL/W7LM\nUcfIimx5uk2m7DHjEMwwr4poQZOx4QCk4UVk5rqrJDJTVmGRcJNuKGQ9hbQE\nUprnv8RcDgqm7q8jT9REqeSmQ0ng5lGTDjNvJE959LAsFYc95+nm335Pw2v2\nMLdrRTARw63joHRAYgARpNPsvg9h1eGTl4fkF3Ok4Bl9Y6h8lXovPn/TXL+Z\nZh3mZdHiG/S/TO0rZdaJOfUSgfyswmfRFDqmoKTAJU9Oq3jyfUVqcfQjlMDj\nGhD0eh7w6w8019VPrhFJOgKMSa3FlqiTXdC2HYRR2eNE6Vy2lonXqDsxga2J\nQ1ko\r\n=Hzcs\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, createJellyfishCore } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await createJellyfishCore(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"17310b16687f45bdd12d84ba75a677fb1b1bb1c4","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.55","@balena/jellyfish-logger":"^3.0.34","@balena/jellyfish-metrics":"^1.0.294","@balena/jellyfish-environment":"^4.3.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.7.3","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.0.0-fix-create-export-17310b16687f45bdd12d84ba75a677fb1b1bb1c4_1625206405355_0.5963251182235874","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.23-renovate-internal-patch-41f9e16b438c3b3224afa0bd4d5dca5c315bf003":{"name":"@balena/jellyfish-core","version":"4.0.23-renovate-internal-patch-41f9e16b438c3b3224afa0bd4d5dca5c315bf003","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.23-renovate-internal-patch-41f9e16b438c3b3224afa0bd4d5dca5c315bf003","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ca08890e2b84563e49a9a521c6d0a1800945c3e0","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.23-renovate-internal-patch-41f9e16b438c3b3224afa0bd4d5dca5c315bf003.tgz","fileCount":196,"integrity":"sha512-c0GAu0ELGMgGEGhODrHuYLin3weofloMAW3wMRu/JCG0aPY91zwKz5pCdAy07zMwpzEX5dz5kMUmsfrLjDMgUg==","signatures":[{"sig":"MEUCID+Nj8IirMX81VbmQzGszTH57COZ/47D6Et36ltLkj0ZAiEA1ROVp5WNeWOj8Od7+75WjLh4+UdKBgGDDBmCWXeUSIg=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":728969,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg3rk7CRA9TVsSAnZWagAAk2wP/iPiqapLxumgyCdzbS5s\n/jPEMKnOz6EKdAxwlPNOYbWOmlhkPbqdDMQTHW7fX02oNzBIb6huDm0SOvOH\neV+qgARTA8zOkhdl4MPBbDO+ilmQkVVJnBtjtGV++gETa0mvZz4/09AEnOoV\nhQdiTpRHhtWiwhWdSB04WvXDAoh7VJYWKaLbqLM7/PqDTxE9dD3vBvrGT7N5\ns5NLjvrO8BB04mjMxW+zQREC5pKHt/lfD5oLcPSZ0vZcRBoPMWW1Tbl8CoO8\nQnDNruLVtvme8FbDY61augluyvFY9viYThYqpwQ/2AM/kmVhIrGMSOT1+FrP\njyzPWQEmGjq0BlkXDf55B3lPmNL+atVo13rJqhgKcLXt2vJkyEa3CcEOa6hn\nMVz9qN0hwfJVHOHYJwF+XdOBSyhzqGuDBKP4MeTlxREv7V7zbTlDldjPvxjE\nFNsoeakoSAicRjwgOzFoU0TWzHo7KUsmlWa6FXVFpoIhm5kTxSTy6ZbMTeq5\ntoDxqQc1CVO7BErrwtJEJdFoPJ88q+3rXo63W+JLDeS8Dg2P3B6NAaMjjgIu\ncqx/eNHiQKVyG2hM7ehaLJw9peEP2DfwrLz/FXluGtyTxD93t5olkQbNAMHI\nUir13PuLk9yoohK6ALnJPEVfcnwLq4AHCVeLlvrAL2zcZAJOg3Hk+dGwtHhe\nl5Jm\r\n=0eU8\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"41f9e16b438c3b3224afa0bd4d5dca5c315bf003","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.56","@balena/jellyfish-logger":"^3.0.35","@balena/jellyfish-metrics":"^1.0.294","@balena/jellyfish-environment":"^4.3.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.7.3","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.23-renovate-internal-patch-41f9e16b438c3b3224afa0bd4d5dca5c315bf003_1625209147059_0.2685160981765127","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.23-renovate-balena-jellyfish-types-0-x-8c3500568b6b1bbaa1c05f1c9ff0f07109745a3b":{"name":"@balena/jellyfish-core","version":"4.0.23-renovate-balena-jellyfish-types-0-x-8c3500568b6b1bbaa1c05f1c9ff0f07109745a3b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.23-renovate-balena-jellyfish-types-0-x-8c3500568b6b1bbaa1c05f1c9ff0f07109745a3b","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4ceef50acdc937e5ad424cbb1dd7c8f90fe04004","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.23-renovate-balena-jellyfish-types-0-x-8c3500568b6b1bbaa1c05f1c9ff0f07109745a3b.tgz","fileCount":196,"integrity":"sha512-iZ8yMSIODyBjZBTnDLOZ/bFEr9D52J3/dvMFv+TZANDDf+HzCc/TExEKnYUPAOx8N4UzoqXnwqBkjL2h5h0Vcg==","signatures":[{"sig":"MEUCIA/fVtV0dQUKcZb0Iji4J3NKYvnwH9hBJ0GNHsdAU6sQAiEAgQ2Vw21coF0a2w15YiY/KZYScLtfFqA5LMepkh+ru9w=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":729011,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg3rmBCRA9TVsSAnZWagAATrwP/2Ps5Hva38h8VXOQ/tyf\n7y9ULqTOU4dRzJV9eyBSl1J5tMEVHy/vAp4fvOmi5FezQF0feVsmveBesdpD\nPjfEp9y1QjZF9TQNSl4yT1dYi9EpzKWOyFWQQg1sif99S1tdNlB0pIn4pknZ\nriQYoaM1buQXSfim8ALw/tznY/nn4EidTvHfEefnwHdaSTBXptmMj8pG/i/i\nPJPEqVE1WX14np3Uq1i9pQT9rWxGghZQkugFOVEZcfT9RuvQggMUxPUzgiLc\nGhVwdFu3q2sC3HP4CMFEkeNcbxYyuRLmvgvtwhFuf3vhkOoMRcV/79ZDtpTe\nefi4CBrdAIydX6+9bBeA2XL2fG0o93T31yYLg+RQME/cqp/KWPQQgiSQT4vK\njDXxDOO8EgMUfxoJLuiNWfwaRcRcVDon11/ZD2KUOoEZVFTmEfPdk3yuSMxk\n7cHD71eHifoBrswvJhJSq2pGxfzoR5iR/35wAtwN5huRgVf1thwyB9zLUDfm\nbiYbLGc14SxNBUkfZ0Lb0tS9uIMtHWF4c+QLkVXpVkC5IOALtDBaGF2mLmJk\nMcnSPDAJ1zD2VTSeO0irhOYNBtABVbYboKrLIHGQc+FYRzGGid6BAZJzAfHb\njk6pXcElFbW2cJb/afeXOIbtwAupcM+Vg5B8ASAhrFvNOSXbKOkqbnWAP/OA\n5ykz\r\n=5thW\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8c3500568b6b1bbaa1c05f1c9ff0f07109745a3b","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.55","@balena/jellyfish-logger":"^3.0.34","@balena/jellyfish-metrics":"^1.0.294","@balena/jellyfish-environment":"^4.3.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.0","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.23-renovate-balena-jellyfish-types-0-x-8c3500568b6b1bbaa1c05f1c9ff0f07109745a3b_1625209217292_0.2693628055656281","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.23-renovate-internal-patch-50c55a9d3c5ae918b1925b57b49d2ccf42f6302d":{"name":"@balena/jellyfish-core","version":"4.0.23-renovate-internal-patch-50c55a9d3c5ae918b1925b57b49d2ccf42f6302d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.23-renovate-internal-patch-50c55a9d3c5ae918b1925b57b49d2ccf42f6302d","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f6ab7684b54d43dd415101432c66df3466a92e12","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.23-renovate-internal-patch-50c55a9d3c5ae918b1925b57b49d2ccf42f6302d.tgz","fileCount":196,"integrity":"sha512-2Ps4V/+obdjlflJs6D25Ky1t1AnrYkhhBCZzidGGYGl4PzJjymcQFcFOW+stAa8PtPXORg782ei6r3pulM00QQ==","signatures":[{"sig":"MEQCIFH037NQp9X/5AlfrRLsjYiTqoUfKFE1lu8o+ZVzSnvKAiB0o5rIlJ5k46PVNAwRmPRWZQ4JPf+rJKRtFoNOVIHtqw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":728969,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg3sVCCRA9TVsSAnZWagAAeagP/Rjya/srlO7nzNjnc4XJ\nyMiojimHYQi3BdvUl+GztdxS+ZvihDY97wHWuB4UV6h86tp/PRprGdSfXhBU\np+Wdl8wsTqCMB168PFI8ozwlKJ+shfmeXVssLteL1W7BizSDhZFTFGYIZsY6\ns1JTHelQ3ThwRc1NOtM8lb1RlksLN6H0aK7Y1QLtsVPyC+SDwKH3gq4/KN8n\nJFyfe4m/pj/twtQww6SBS6ynCAvGmc2PAn/zPxJhhYVFCe4/NMNKb3srfC79\nrQ3C2ZjPkAR0mi13xef4zPMh7muWfP9cg9gftHBiM0YKVN06ueHqyIxWC3Oq\nmUxa6N2su39mRWzS/yAD+6jMx1MZ+HQ8WsRKEwcU5LfxUY7onjuFnxPeQrrH\nI73jBjy7WeM6TOyS7adbRkkNbZKFKKUs2UlG44f9FXqyY+hkcCshal5bfrpx\nY7zXQFe8g7YNQJ53rtD4/tJgm56T7Le46JwEaX1sbKMJ2g0M5nrPe9t6jSTp\nJ88B3IwmqVw1HXMJKsmChNeRBez2HYTcExgmQHxSEIEjzfm/5tLRhOzQuSmF\nlT+bs8tUlVqnSsZvYSMuWs4sCr0F/f9MCvzvlPPjrHX+fTdQMKRy9MAwcoQ4\n3c2zgNJI7rNTAiuWP+i7ot/Fa5zlSilDyz8477MBW55Ii3uBtRPvhZA6YKEt\n/Zjm\r\n=3k78\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"50c55a9d3c5ae918b1925b57b49d2ccf42f6302d","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.56","@balena/jellyfish-logger":"^3.0.35","@balena/jellyfish-metrics":"^1.0.296","@balena/jellyfish-environment":"^4.3.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.7.3","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.23-renovate-internal-patch-50c55a9d3c5ae918b1925b57b49d2ccf42f6302d_1625212225564_0.5985000098307789","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.23":{"name":"@balena/jellyfish-core","version":"4.0.23","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.23","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ea60d13187b5ee4af6b15c2098ebf5949290b009","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.23.tgz","fileCount":196,"integrity":"sha512-92pKCUsy2sYjOVmTLFhZC66jcf3ea8hHuDLf56PxWmRUwRdiyidpCMMrvq4k6RXFJPYdypucXNi7ez5Xhc78kw==","signatures":[{"sig":"MEYCIQC783e+ibKwHpaD4MLR647OxyORnSYs97saANFcK0KgTgIhAOGUqjmzzYcG57HNhOaYGmqrFWpRkrSixz2fKP5/shvU","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":728904,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg3seCCRA9TVsSAnZWagAA6a0P/3CV+oKu7SSa2LJx/pq4\nLYaRCuLJdSAI7bQmhQaHVSEK00FvriYSLfxw7J1RHNCDjs9LN8V/nb78RvgP\n7wwL3DfMP7TkQUA0XTqFCddXdtpyz2JqocQTC7gm2JbEjBrYbsF9wWJUblWy\niQj8B2HFk+o4cFJRc+k/hJvxd26PZLV+TOGjvYHaZcr5aKbbhSW0fAO3DoLj\nehKfh0J/B9fwe5PSxptL+a+oAQ+3W4a1u6GYmZqml3eo64z7KybYFj2Gs/Mo\nKtpMHoWnGJvFlCREvEtjN93Tj8cZPIrHdrKvlfA5sdlHhI9lNqL88339M+7t\ntRgd5ojh4pMjLg1L7GUAdiuhF3/0JEF3cN46lHQ47ZwYb/mOB9vZ+Js9BHz9\nOTRAjcCrul2USYP0ann+DvHUFWzifbSCfvNI9I+qapwScDzZ76kx05vS2M8F\niWk+dekcxmdy6VaoKDUaWz6NNRee5nTBTNzHMC8m44HGQuG5sC7Won2eM55V\ngCJkYtlP0gSkqkGxJaOokEBVmMTYNPN7TMo8qJ7/i6d80yHewCFvorUIUJim\n6MQmkR4qghqAKWolzl1Y6WVdp9VKgqGFbnvLcTrXgv3JyziD6VIrd9JvvojF\ndnNMHAcApbv64iVfhOWaEkH3TXlZhOqH8U+lqiaYKKsLw1CLP0U9lBDS3ksB\nIQ/V\r\n=J+Eq\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b331c6bfced842642cf00f56bd937441c8ae32fa","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.56","@balena/jellyfish-logger":"^3.0.35","@balena/jellyfish-metrics":"^1.0.296","@balena/jellyfish-environment":"^4.3.5"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.7.3","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.23_1625212801421_0.20153817568010757","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.24-renovate-balena-jellyfish-types-0-x-5a1c178bd211a3b9d8fb38e9b92dea5bafb3d66a":{"name":"@balena/jellyfish-core","version":"4.0.24-renovate-balena-jellyfish-types-0-x-5a1c178bd211a3b9d8fb38e9b92dea5bafb3d66a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.24-renovate-balena-jellyfish-types-0-x-5a1c178bd211a3b9d8fb38e9b92dea5bafb3d66a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ff8588c24f0f2c120879f903eb55712d67b6258b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.24-renovate-balena-jellyfish-types-0-x-5a1c178bd211a3b9d8fb38e9b92dea5bafb3d66a.tgz","fileCount":196,"integrity":"sha512-39XL4fOxdrko4ImfbbsozTloZLq2I2MIqMasGG4V8nC8KD3g1Qvqrn392nCVRWIfOFUhSauJdMZRG4+3unRsuQ==","signatures":[{"sig":"MEYCIQDnev55my+sW8Kz3Pq1r8ntvwJHPE4N2YD3gO4xB1YTXwIhAJqaoIOc67lS1zPGNnu9wW9j+1e9VmyKg9UNkvYvBbdl","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":729082,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg3shjCRA9TVsSAnZWagAAYbcQAKTgr1KiNbRfoGVwJvFb\ntPDU6QfE33cjlkQ1/CfDMuBBiu5N+zXMwWAFfnakDmVVi2vMzJtoo7PHuyn+\nXand0I+5J3vjxSMLOQ2G0rNijng78nDG8Qxuuqukeb9IW8AHCeRq1Ac8F1kO\ndBWNglT5KdCaXnfCKK5T8w/3gzloglMQgw8DTemCZLY21NFhTkVMLkaLcuW/\n6yl2cvvDrgoiC7ADlKfyQ0wskzk5/EyAz9WnZhKnMWkEyyDMkg1P+i4skKd8\nwB4tt0/oHS55CAL8XaZS9Or9+nhjM+ndD8Ph110q9N4xEsu0VjQpVl/DtRiy\nwTV40S1Zp1vVQgt42L4a3VurRoJaryofvICypzE2Qk3+s+Kl+k0KZ2FXUqww\nBecz56YoKKRL5YcNKys7DnsOT7ozSTQkEDg62W86l+S2/mW9rJOLs7Mdk+n4\ndCDSQSk0YMxFZW4zqSDoOvilBAmx4Blt/rIHAlDhMzRFhcC4goiMJLfNrLwG\nEZdQBuuSg+KkZj7og/XL9SmzmtlAFotgkGZ6MJpDuThXxCb1PFlWpurhC22S\nz1lfPLZDRQV6SjkBG10eGCCV632HIg591XZQjhRA+Y43YknyF1tSVS36Qgo3\nx23lvMPeVSEIki6i5oEcXnpIGfr4Vc/mKrwlAAe3tdPd0QcnAYZ2+wbRCeo8\nWexP\r\n=bRuR\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"5a1c178bd211a3b9d8fb38e9b92dea5bafb3d66a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.56","@balena/jellyfish-logger":"^3.0.35","@balena/jellyfish-metrics":"^1.0.296","@balena/jellyfish-environment":"^4.3.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.1","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.24-renovate-balena-jellyfish-types-0-x-5a1c178bd211a3b9d8fb38e9b92dea5bafb3d66a_1625213026537_0.5727464003556708","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.24-renovate-internal-patch-be60a0e0fe1b541092eb25292f8333c28fe82cf5":{"name":"@balena/jellyfish-core","version":"4.0.24-renovate-internal-patch-be60a0e0fe1b541092eb25292f8333c28fe82cf5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.24-renovate-internal-patch-be60a0e0fe1b541092eb25292f8333c28fe82cf5","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"587ee6ab0035958479f907845c76dbbd67ab5000","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.24-renovate-internal-patch-be60a0e0fe1b541092eb25292f8333c28fe82cf5.tgz","fileCount":196,"integrity":"sha512-Qly6Izc6cAffwHqkeo4jd6kkK+DidaqszdKiQI4Qa2sfBeHRhWBRJFlYlAeqlfzbXw9UZ15QZSJuf3aJKqGFrw==","signatures":[{"sig":"MEYCIQCdapWrYcoNb2uRx6B/xchrOB27HpMUuDisOFwBr6mdbwIhAI0sDKZ4DW8rViDc8foEN9dEOA5rNjNakfUWCmk1d5JH","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":729040,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg3tiBCRA9TVsSAnZWagAA+WMQAIcGXowAY0cjhm+kx4Re\nJX7eX5Mn3YCwyr+juXLcBKoRBBLYkFBKCmopmomvbwvrvAoxIMdY2rrf1t6P\nF3t1PGJUIJFAxu7yySjkGNH3A4yzGao8eiR06qGQ06ll8PMQ1NOFA4DJV+Tp\n9MwEjCCbpORbKeUdGWJ+vPoJWWgG1VAjPo5gvawL6xp3ugoAILs3ywsYxMs5\nrlkKuFabl3PZAchCdlSN6JPEFdQjbwE/a9G78zXv//xNPJ2BH1kAvRbfxQDi\ngX5j/ox/V8d5fKXYrVPL9SdR0wzmc7v+v939Ft7gBqEnXpENNTSLbiA6VLwl\nXad+FQ7WQuW/xuimAVfvNQc0TWHBV17gnvJJLaRW/80DeNu/jREspTM2Mya/\nltHAOL5P6Z31f0TYVC5mUdT8IHrQA9tBYebyJP54S19oIXalRcfb+Ins5uzS\ntL3rdxE4odxxLDSlk/hZxGElfdVR/Caa2YM63/OOCuF98LVzCoa2nN18fxS4\ncKblzeCESC2L/CdTTQjivqYl4/ozsQg+D0LUr19aEKfu5eB57tokKl9euwxN\nXfP3UUddVEIMW1Hq0eTphb4vHQ16rz8l0tSrRt+RFMdg2obkRKMkp0peu3qi\n/vux4/8Nonr6OMpSx+k4HDE18zitQd0U7uzXKi4DKp+KGiLx+iYN0LiAJF8v\nssto\r\n=5v7O\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"be60a0e0fe1b541092eb25292f8333c28fe82cf5","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.56","@balena/jellyfish-logger":"^3.0.36","@balena/jellyfish-metrics":"^1.0.298","@balena/jellyfish-environment":"^4.3.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.7.3","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.24-renovate-internal-patch-be60a0e0fe1b541092eb25292f8333c28fe82cf5_1625217153390_0.3627872392176337","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.24":{"name":"@balena/jellyfish-core","version":"4.0.24","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.24","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3b94af43ee080ff788fbedb6568731a74c1aebc3","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.24.tgz","fileCount":196,"integrity":"sha512-WgxWmtc/HMpfZp57gc73KhaDZVEpE4Qg2JxBB0f2VDd/x/xoGc608txdGISW+7zX0/N1VHCgcYF6TuaDe/COsw==","signatures":[{"sig":"MEYCIQCwKHf5Hm9tyyGrpWIvJ3LqMeoiXpq+ac0AAgWqY2cOnQIhAJKn5JjER0Bfs1OEajbJePvE/pYGepF1l/P4kA6CdY2n","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":728975,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg3tn1CRA9TVsSAnZWagAArl0P/iXZ7prcn6iGsFQTqk5m\njimRWS+DewwSvnSH8fiRN41NTYqtfvGNYfpHNU6p/VoJmlZ+v0tgfU9IR/62\nnXgXw6gCvTHuJ8+112TI7L6ZBXkFeZteebToNk7K1PICZ6aLfcMLrU3pKLhD\nsE5pInACBJrRa5mQn4RQGqeDe8mAO5NvVQKSTq0iXH3jlun3YexS0OPvaRtv\na1mAbRpt0wB0eedMLj8oAJ/iKqnR+DXUwm2gMukOHd9ntLoGRZM/I5ipJbQy\nCdm6SFIIGa5anBf94h3byPBl0FYwK3mLR/nyyciyjpqb5PSOlgYat9cHT3DQ\nJK+K8sDa2mWSm7aUENGKoF3YEu4bSHJobS6oma3LKUsZRspLFTxd68zzw9Ek\nPqOqqowWkBudknW3WuIQFo1foY5DtNP49W2xgwlff71Hhtc8AgyjaMdHK++K\n6YdLC5NYRpmYHayq0pyr8+ni/w6Exxh1hqyVvu6Ukx33Fgt6vtrV/1yvzFZn\ntAl2O7g17PKZMYvvn3urVX1gdbDFYPMieqvEeRqvww71NeW877cUdxsGxTzj\nU7gC9kDePImpAmFjrBri5CrVomJmMcd4+/kHALkUOrP+saR8T14TBYw1lqnY\nlvle7BM6N1gX6gHXyHZ2bhy+GewVbt3bG6YB5E+uCQEy1bwMAhi08eVMd5rb\nPmk6\r\n=epla\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6d5af384bb5c63c8a83c509fec29be99e079ac04","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.56","@balena/jellyfish-logger":"^3.0.36","@balena/jellyfish-metrics":"^1.0.298","@balena/jellyfish-environment":"^4.3.5"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.7.3","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.24_1625217525272_0.4402591915745384","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"4.0.25-renovate-major-external-major-fb4e9dee413847eb609f390f2db2c95d74aa6db9":{"name":"@balena/jellyfish-core","version":"4.0.25-renovate-major-external-major-fb4e9dee413847eb609f390f2db2c95d74aa6db9","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@4.0.25-renovate-major-external-major-fb4e9dee413847eb609f390f2db2c95d74aa6db9","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c13ccd1d7c6a249b2662f559b87c891010008753","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-4.0.25-renovate-major-external-major-fb4e9dee413847eb609f390f2db2c95d74aa6db9.tgz","fileCount":196,"integrity":"sha512-lS1OahyFn+OgDTZWBJ1D4NHaRdFHc42OBx+uCQsSxSM4a1YxclGrpXCNg9jD2fRF3X3uJzXr2CB6wgGSjse/hA==","signatures":[{"sig":"MEUCIBSEJckDbEgJp58FAphrw42/aVEuAe8lJOVh74W56SBuAiEA2nnDABndDA+lKXKFDQe2IcVXclEIs/jhhLMIf25pRek=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":729117,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg3ttbCRA9TVsSAnZWagAAJPMP/RlDBW5WYnw4OJjJpzNE\nkjJdvIPKpwDzki/XkpQ5hwg/DlktWnndEKDYA6iK/UytmKgUFs+aDybHJoWE\nQToaQ5SUF3NbEWazV3vPVYqVH+2e13T5yJoTbJWU6BygjWyrKWHlXIoOVJf2\nxGcLl9qKA9RO6nNC/UDYGHlDgJz5chotb0NlwhwNYCtXr/U77df6hQsJPy2S\nPO2fuVDQY2B1WZm7TTtY4VlCNswZGfP8B9k8RMzpyJgr+jAGfAXsv7UsMR8J\n9JGVARdD4SApvYodfW3a5iADETNPJIuhb4Kpi+wJsylU7rCy6AtB6lqkM2D0\nzjC/+20NyfRyMixNkje5cUVwfRINRob7dtimWgC8lUg8YQoxL2qCNz78JJwq\nZE5UH6FYt0B3L/Tr/JxGN3HyM9LkyZXmnHfojTMedMOtz1iPyKWkL40YxEsC\nX+LkB25cgamhUFYtIO5gJi486+Iyba4KK8UPpmqDwhA7sKLaYk1W0XjHhVsT\nuI2h5s6O7oNw4A+eRD4yY7QwR8/ady+gvJ9Kdh3pj6vi2PrhsxqCh+C/dg3x\nHsXzYsgCfLPYH1JWx9ZgyRMIr0x3nPfHtrnRHgtQDhOwXUXU+3cibzEY/7Kk\njHBDLzlvwLlxo7sIWNgiPKIxlG/na9/gX3ZClYWsjd2Euj94jP8UYxNDzs+4\nn8eZ\r\n=7jaO\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nconst core = require('@balena/jellyfish-core');\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await core.create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"fb4e9dee413847eb609f390f2db2c95d74aa6db9","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.56","@balena/jellyfish-logger":"^3.0.36","@balena/jellyfish-metrics":"^1.0.298","@balena/jellyfish-environment":"^4.3.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.6","deplint":"^1.1.3","ts-jest":"^27.0.3","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.7.3","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_4.0.25-renovate-major-external-major-fb4e9dee413847eb609f390f2db2c95d74aa6db9_1625217882795_0.2052911869702425","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.0.0-fix-create-export-3201a24f17ba8391330f9c8d41f4b2e48fa107fd":{"name":"@balena/jellyfish-core","version":"5.0.0-fix-create-export-3201a24f17ba8391330f9c8d41f4b2e48fa107fd","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.0.0-fix-create-export-3201a24f17ba8391330f9c8d41f4b2e48fa107fd","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b170b73e5b5116fc5f4e42aab3bcfd15d9223ec2","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.0.0-fix-create-export-3201a24f17ba8391330f9c8d41f4b2e48fa107fd.tgz","fileCount":196,"integrity":"sha512-TWG18TXjvUq/yeDLgQXUoZqR6Iqi6/+VZTsH6M69lcqo4ZaKzqYtLLz2jsJd2blcKjlTedoI6zrOapWWm2M1VA==","signatures":[{"sig":"MEUCIQCC+pXEfAvuIPf7ZwyU1vOeViYk4URZuk0tXd6wIOjcQAIgIw2BYXMyThpRhO85w2jMX5c9oaV5FnaALMBYR/TzmSc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":729181,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg3txXCRA9TVsSAnZWagAAXuIP/1zE7Hzc8aZhfj4JSPHZ\nq3O9padAkYs1lKzK1kXLFhbxjdb833JSZ0pLpSzK12OXRamXjMf238dEQiSU\nvq2qeItZ20dmkPHPFtQPaeBg5AoZaktV074GqmbVbs7DivlIJHxtWu/VjjcS\nDDjpPr23xCJu7uOnGHYyoMpLtJc7bJKovUXXqEfP4k2qlyqqBSdIRIMNykgF\nX/qSkLCfvh59b6xS1fzxh9t44zL2ISy5qi8B9LzfdMc99wbEgIXypX1KiUN+\nrf88bDCHSAcbY8Jjytj/KZmccXQ2Wkz3OnXEA09pIeE6M3DfMVzJ14YLLmCs\n/HcDkclh5sNqSOSk6puzK7fGRxkt6lsG6kbSE3UxqV5cGNXNQ+rOC0hYyOAz\nud7ywXIroYJR60nngIwOsKbYxZUONiYQFszYGifcIzF7oWt72qKJas/OjPpH\n55b2TXC8CLdQI5pe76gL+o29oY8b2Jc6DwVYYT4hsYfe0IthgQfagT4bs0mV\nYut/MkuC816EVBXvVF94ZzYZuZC1LT+0B2LjWI/xcN35C0IAJNFpbakYEnSr\nbrKkgPKkHGnrXtCGhUHzWMZyhlu4AceFP8oVna3vHE8KUptRF1OHtbrjY081\nq1LzKlIUUMx14FE3CyTFSoyygWo1hWcfAqywYhnGhMK3m+YeUjjtuy+FX1HG\nunCl\r\n=koeZ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"3201a24f17ba8391330f9c8d41f4b2e48fa107fd","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.56","@balena/jellyfish-logger":"^3.0.36","@balena/jellyfish-metrics":"^1.0.298","@balena/jellyfish-environment":"^4.3.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.7.3","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.0.0-fix-create-export-3201a24f17ba8391330f9c8d41f4b2e48fa107fd_1625218134667_0.16507285337721433","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.0.0":{"name":"@balena/jellyfish-core","version":"5.0.0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.0.0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"47531d0028f1108540c34a71f64b0018b811b084","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.0.0.tgz","fileCount":196,"integrity":"sha512-DCZ0TsnE6PtfnwitvdCFymkwWqbVqRGnkrmG0Eup4xreuMGaGJwtIhvTAeDjZM8dPVPQDrzxIlyHqYgGMyxn5Q==","signatures":[{"sig":"MEYCIQCax8NNyN5XOzo3nkuYe1Vi8szd8/NDOK3qCnbr/3hMXgIhANIPOOpnk/bBmfF5qd1XSalfO4gH32pq8d4G4RyeNgfI","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":729122,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg3yp1CRA9TVsSAnZWagAAKVIQAKQXw8mBJrU/eGK4+404\nX6Ju5yfUtWQM+qGlOLfTj7j3RHk/ct2Yz1lhwr2ajuRwbjGxbdYbyjcGjuqI\niFvMZsWW/plmfb+Qx1mmosiJvnuu5dycUW68JAjv+DNZ4lXsVYTvG999oB24\n2ayeDaH2/kipcXmtXtLHfBf3sxUb6ZUHYPmdl2cqlAwo1Mjpv+j0znV/vyVh\nSH7GkfEj2lgFcTgiYzYjStB2nixvF78LyidmaMEYOYep/uGE2VMFSQLO3841\nAX+Em1PT5Vk4s9FBBM0WXkzqO6QjINxyRJSCmrrXpmRbvDftxlHkvkFz4MZI\nPrIjQ9GYEb/ZoyN28AnTYJPlA28H8/UsGH25q6nf+mH7D39z/6Sxn06UcI6A\nHujCWB3uJ4lb5GL3kuPQPbhcayWFhw0GQ2LCmqiWE5sRAe4S9Y6flY76EyWA\nGbcM1BwAHD10xO6rK84i1WV9/LJ+1ULThBoHUT7hP0lwmiWV1IPR7BFxQlZ0\neulQ8JjH+mD5bdNhZXnxHo5kbfaregFS7X31iq+JomneQ5mWS++NNXhQENcQ\n9ofTYGI3g/pXyLxmunOkwgTLAVhTBaI7GLxSMUilRKFVK4zQXqUmVkk5IHYg\nzrJQIqquYphEq5QEEa0/fp7AVoYfkLg2c+dRTQe9j1nFUSESCUKJ/vxqjw0y\nRdEA\r\n=o4sE\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"bc8b5f7e42f810831a2cc474a3898ec74574b852","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.56","@balena/jellyfish-logger":"^3.0.36","@balena/jellyfish-metrics":"^1.0.298","@balena/jellyfish-environment":"^4.3.5"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.7.3","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.0.0_1625238133055_0.2974559058855444","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.0.1-renovate-balena-jellyfish-types-0-x-cf2397e08910e674ba1c8ad54d9d8054aaaa3613":{"name":"@balena/jellyfish-core","version":"5.0.1-renovate-balena-jellyfish-types-0-x-cf2397e08910e674ba1c8ad54d9d8054aaaa3613","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.0.1-renovate-balena-jellyfish-types-0-x-cf2397e08910e674ba1c8ad54d9d8054aaaa3613","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9cd6cc5724c2d23a0ac11f16d13277732f5094ed","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.0.1-renovate-balena-jellyfish-types-0-x-cf2397e08910e674ba1c8ad54d9d8054aaaa3613.tgz","fileCount":196,"integrity":"sha512-ePJvaLRtgy3aqohowBUxxYa7IwLhDeX7ujH3fGKyfG1JCGXTpMgUkfqROoTCqe3kqV2rZENoszOKCZhASPK1qw==","signatures":[{"sig":"MEUCIQCmX9ee/JIbYfmMuED2Z8qEDgWDi8nGW7SBDz+++HPSVgIgQYHepOxEUh3/dWzI8Xp6wPufdw36lReu4Q/W71Jg4HI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":729299,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg35OsCRA9TVsSAnZWagAAdpgP/RGH6gvlWO/Z542IbYgm\nIBVbM9IsMzyFC5ND1kDvhh/3hKY7uaH3hZ4lU8ve8guX5XM9RIJ6/Nap49Jl\nW3k53o5fwJlxLZbc25d/n0O4WZ+5C83hqfHlJWxbfveUBrR8mI39QAKt6mmL\n6130xZ/M8G4lbYnnYVH1rtwV8Bc7asRcWlF+2H2igxPxnb2AwFqRx+IfulX2\nm1qjDg41l8qvRj1JY8HZjqDIsCiZLuCZEXCqaY/bLLVuCntf1fkfVtR+5Dql\nU90zYy3f0Q/qAdETG2azP0gan9guXI5/7knGzhv8aY3eqT368Wof05dZvLg1\n593UT30jvniVKCpnWQdCV5pZK7KVQGhkVbzK/socCKm131a92Tj88VcUQcej\nXjJdqCjZRDX97DQuZWvRy/icZV8AQ7nRdpI4Db/jOLxh5nkMVVVL5WOzLp9A\n1Wephnq1DoqCDMudn2b5CRZh8OW0Th7vjvlQIPhxNsup5oKuw18n2Pe52MX+\nj/4wRiWvPCwRBD/0q0F2mBD7zAguCI1gOWKh9Rr+zHi/MoGwyhBbJ1coKFYW\nsaltCLUVILHfWE6KGu3lDH7N0mQgaOb28fHvdc7Zt1Bl3fokh9icnoMuOSOH\nHy2BmT88dLu1kdOyp+2WtdachZiaZYmAo5GT7mNoAp18dEuIPSWTjvJy8UUc\nXZO0\r\n=oot9\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"cf2397e08910e674ba1c8ad54d9d8054aaaa3613","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.56","@balena/jellyfish-logger":"^3.0.36","@balena/jellyfish-metrics":"^1.0.298","@balena/jellyfish-environment":"^4.3.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.1","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.0.1-renovate-balena-jellyfish-types-0-x-cf2397e08910e674ba1c8ad54d9d8054aaaa3613_1625265068011_0.568337180576511","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.0.1":{"name":"@balena/jellyfish-core","version":"5.0.1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.0.1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"fb862c20c670847c810ab4a403151af2c23b2493","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.0.1.tgz","fileCount":196,"integrity":"sha512-VtYhf0SoFIxiCFwKV/Qp+OqOqFVWCusYTnEKOKC9gVlfHF4Ke7wdH4p6BE5iBbKk3Z6Jl77FOQcAemtCPCtk1w==","signatures":[{"sig":"MEYCIQDoHcue3aM7RC3VS8i2NaCQO7ueIbyjlMuBICPMaiLxjQIhAMaea1OWmnJUBZGT8/FAkTYkgYA+2FGMiuKCrBrpx6lu","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":729222,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg35ZFCRA9TVsSAnZWagAAC2YQAJdMoJsTB/vBGjhhEZa/\nXohEFyx6q4zc1JqP15mXpvbSzhtvkKC0VQobeYwDq1xt5Lxxhm4rfW8M+RsH\n/sW5R+vXZuTVGvJwBj+4A+vLy04fRUwrGWGIO/OW5ol9pyJBZFILje9/TTwI\n0Oz1dkUQ0pGNSLVtx0fza9zbcL5IsoQ03MLOzhBGkDR1xlDAapLzUck+TLyT\n/8PMrcDpnyxjP9Uj2WiO039V/pUqAoYyS7jQgJpOa3KfMpe+ArpqPgbIAyNc\nOke/F2z/HES2NUFnWlyc6UlnTbOYsKe+tK4QGpKutYd5jUSAIfu819BwH6Dn\n0ZPllDnqsRf++Bzw0m3ABORJM4I/kgHmvDtzaEot6/88VdVmmEyT9Utd+zjH\n9s5RQLeWojfhbr4UA1El5jpQYXJkgMShfx0Chj6/6ylbEFWI+f1/A8V/LIGi\nMUpI/Ogf82Fu59KDveH5QGy9AFzzqPI6veYjajGB9nNf6TDl9mMUFl0jn1ZA\nrjZ6apDawhk+/Mlyfjbbu2ZMVpWsVb7xkCZGOPhHsWlKiN7zgX4r3Ob45Ulu\nJzPSd05Z01RlNWy3eEbNLD2qzlaJ1VO5e8iBumd63uvk+vysth1pappTLtt1\nwo85pGhuZuPhPPud9aHlJYE6L5MjJ9959BeDOaIDaIDCWjV+vH51LOVtHWkJ\npy4a\r\n=h/nf\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4d8c97ebf14e0884160ece9d18b5e6a2ba12795b","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.56","@balena/jellyfish-logger":"^3.0.36","@balena/jellyfish-metrics":"^1.0.298","@balena/jellyfish-environment":"^4.3.5"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.1","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.0.1_1625265733350_0.7050818755767683","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.0.2-renovate-major-external-major-bff2e26bcb9045e12ff903951ad2a3d61972729f":{"name":"@balena/jellyfish-core","version":"5.0.2-renovate-major-external-major-bff2e26bcb9045e12ff903951ad2a3d61972729f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.0.2-renovate-major-external-major-bff2e26bcb9045e12ff903951ad2a3d61972729f","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d6ea45dacace3ce6b0e5800ee8f1bc8b6ca64ae9","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.0.2-renovate-major-external-major-bff2e26bcb9045e12ff903951ad2a3d61972729f.tgz","fileCount":196,"integrity":"sha512-fXOjVdUknhGUiIuWA/Ndga3GEA3aOw1wlKqEoOBXMeXyvD4/5RhgzRWk4OJKTeurYMFoMfmFZVXpKYeyEK6amw==","signatures":[{"sig":"MEUCIEWpNpFbiMfNAzxO8XXe8WAQV6ypvkqCLRAcTky4PwzKAiEA7sNZrZiZcaaQmM5iq4sImG0lbDmnCwLfWTkchvxFPxg=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":729363,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg35dHCRA9TVsSAnZWagAAUP8QAJUNE01+G0BfrRp0hWAn\n03no14x+0o673lm+q25lN5NnF7FtGMcTVRwqWGL7yoBezsyRkvfF2vCSvi8g\nZ5Lz/MkKUIzIognqVDJM+zI0bVpAWMsO8Jg4Nd9azlkuz1+VBNjko4I4Nuze\n/QkKv9N1qi6ZzRqK9+mhLiPXpP7XFSmlsv/m1r0ty/yiONF6lAq/V/93Wbqc\nFDou+Al8jG3TeRhqHn+LH9PqpF/cIGSaqu5OnNOBbnYkq9kuP2nPEUVlS8mS\nR+mlNNpAEBYnhQNhXWfLT6xHLWjQFLcFmLyD4jIYJScWcxcsnx9R1bsYugUU\ndIEg/Th//ZnkPRKpc08HGYfKCerLU3Z6AzbFPOzRKa1pv4RbJs+c+7M+jsoo\n1Bjz/jsyAyCFQfAzV5crLRKM7mU6idUe81CtzEhXkcCkuVleEW6aUzvwsHvC\n3h70hKAEK3sUwy6xSGXSdk46Ou7vSCfBNMjk/SrvF014wsShf75kvF1M0QwK\n8VrHS2TWXjlxfrqTzPksdTPtgyH8DtTjJvtM0AWlYPlDabyvRKHsmaM7S8hh\nppng2VPRv/TCJKTpdaJ4JyiIH/KdSD+21XHPCKaAUI5lBxLsX3hvGL2EZyV1\ne94LDUz76kZH0JH7E4p08X8MKqs31v+ergpU4Z+XfTDIAZtxcTNyEGlYAyBm\nVtEv\r\n=f1qB\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"bff2e26bcb9045e12ff903951ad2a3d61972729f","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.56","@balena/jellyfish-logger":"^3.0.36","@balena/jellyfish-metrics":"^1.0.298","@balena/jellyfish-environment":"^4.3.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.6","deplint":"^1.1.3","ts-jest":"^27.0.3","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.8.1","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.0.2-renovate-major-external-major-bff2e26bcb9045e12ff903951ad2a3d61972729f_1625265991293_0.1581241816440715","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.0.2-renovate-internal-patch-6c2d16bb450d4d49aee58f2a15f725d526ee468d":{"name":"@balena/jellyfish-core","version":"5.0.2-renovate-internal-patch-6c2d16bb450d4d49aee58f2a15f725d526ee468d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.0.2-renovate-internal-patch-6c2d16bb450d4d49aee58f2a15f725d526ee468d","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1a08884473465c1a47eaf3f36ff0dbd88866c6be","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.0.2-renovate-internal-patch-6c2d16bb450d4d49aee58f2a15f725d526ee468d.tgz","fileCount":196,"integrity":"sha512-6vAYl3hZ6ZLr5gvlcysxUhS+RjZYOPxKipk/RShLVEaGOwzDNBV6L5nRCfbpOlPLwJCOEVJ6nq+yQcqiU0hzsQ==","signatures":[{"sig":"MEUCIAHCqdbXSXQF8SZw+6upvkPJzAwmm7Vv9pqLPhAVs66VAiEAxgSdj+27ydtlxMIXIAa+DkQ/8FvTjQWaimj+Nxb6qbE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":729393,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg35htCRA9TVsSAnZWagAAe4MP/R0/UnAMFF2BKO8Futba\nwJW4uVNvqGgcyfx64sTFeMdejA8s5EsWZYG3EwZW4TGz6jkfz9sr79uGFEf0\nbe1PDx973up+Ugk4JR9V17tr+KPp5I5DxgLJF8TIS8K/6HoWDeNhQQkpZvPe\noZ7btR7UNEwKimj3yaZ/VAe1He2Zq+oFK5kuN+BSGZhFpN8Hx1ti3NwtEN7S\nWNVXH6jpD7TGHn3r62R8Ud9iXCJDfcAxJ7YXRLkyjKgf9Xh/uH70/SH2V9qD\nzUc5NVaRihUTUQu4P53Phe9YX2Ek56hTCEjRDt/nG6x7SsSVRYNQi0JfUB5x\nx0Kg/rknaBdLl80hYMyGJgOZ+1z/DMpmxnj7/A+3xadUKVZraE8eWzoQ4HGs\nG4rJ+h2vhjduvC5LL0IWI+EZRZhFulfsEEuoDUR/g8qnHJCIZsdPPiyjXK5m\nDJZMH/2nIxaxkDNRratVohwT3jdwDVsTN9hM0KEJEWUN4jtgmbXO0UR4iLed\ny2XZuqtcVASIOCIScG2h+jjntRWb3HkEelo/aILIwiEXdX1UXhzyZwxCWdpt\n4YsSW958mcwcrCAg64K0kdBnRfHVXzuBRsGDg/IdkmPHJ78lWk9+JCOWTUP+\nf+zJZ7f/S4Ja0YT9NSaQzfijEp9l6EVl9uLNgmDU0CcoD2SoTPEvDQFwNBCc\nRkqt\r\n=JmV1\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6c2d16bb450d4d49aee58f2a15f725d526ee468d","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.56","@balena/jellyfish-logger":"^3.0.36","@balena/jellyfish-metrics":"^1.0.298","@balena/jellyfish-environment":"^4.3.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.1","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.0.2-renovate-internal-patch-6c2d16bb450d4d49aee58f2a15f725d526ee468d_1625266285513_0.9569554977055743","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.0.2":{"name":"@balena/jellyfish-core","version":"5.0.2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.0.2","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8f1bd315ea652994dbc5e8cd935cc1954607b18f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.0.2.tgz","fileCount":196,"integrity":"sha512-YqEZgD4WvS/9I6FkuN7Q9CVa6+aZQ95D+fFadcLLkiyaFhDk/OdBM0ox+RUEMKv8m9gaO3/7VEV2iBqDE1MQVQ==","signatures":[{"sig":"MEQCIHSkbSRNqHoUp6NLiATcez/CsoGBToSLjQ3k9oPiJNolAiBI4CNB3PTqbwmxCeDchS21hZNQvzg221KPeY5XQBSsMw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":729328,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg35nvCRA9TVsSAnZWagAAkssP/jXmlq2lHe9lXXdD4I/m\nbSmMrr65cwQBSFyiaZiStuR0XVpwC9nJ7apkZAG28bJuZlpSPwg7GEqFg/KL\n0O1qhiG5OmFEtOfkpG6aX//qyMjhJelkWdaFS4bzw5ytnqYAo8RPo+CbPq3e\nSbsk+zgkVFhwD7rnVHiHWsa2Hdidm0ZRzjkIH4HqVDQlpwTr0cCeT7W2lLR5\nkWm0ci6oa4b9i0+a9FxKpyuRKIbZgkR6ZQzpt5WLU4aSLyOh2CyNzFzKZdDh\nofUCBh1F/pdqEmriZvLlzwdzVIFizxbtY4pximir4OSlelfdnde4CrkyrWnO\nC+Q4TcTykD4uDzenYlqs1fw4ojdH6LvvATM7Yg4eR+t8XZA3vpQEK5weM9nb\n9Fy5OeBwxdXssyHF6CKJZuK+h8vZH65zv0v7vYf4kazRnfiinIhnAgyB+QIF\nLVNFu3Z32g2Nu5N1/mMMO3s3deuoI8Tmz9itNT40rKRzyM0J3U63lA2uDRtt\nQCFTCGmqUJsf6FQWXiofFIvurqByr8X2OvuTfMYf9YaSmt1uibN7Fxp90t3X\nqJn1NV8r3/HpL+zUNnTHvqjUMQFsdym1qPVbVdRYjgM7avAbaN0PWxufn9Mi\nm7Qiy0zx/pVVQEyzWg+E38y4UFlIKovFJ/Qfrw8NDQVdPX4dcm9vWN1M+ogq\naf+Q\r\n=zTmg\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"3f44d7ebd6fb3a88ec0aa428a5ac8cf37287d3ee","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.56","@balena/jellyfish-logger":"^3.0.36","@balena/jellyfish-metrics":"^1.0.298","@balena/jellyfish-environment":"^4.3.6"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.1","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.0.2_1625266670573_0.048228055856596175","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.0.3-renovate-major-external-major-34dc199ffafae88e698583c1d7e82f6bc67b1c92":{"name":"@balena/jellyfish-core","version":"5.0.3-renovate-major-external-major-34dc199ffafae88e698583c1d7e82f6bc67b1c92","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.0.3-renovate-major-external-major-34dc199ffafae88e698583c1d7e82f6bc67b1c92","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9f3f62c5651349f573c2d223a338ebdf013bca1e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.0.3-renovate-major-external-major-34dc199ffafae88e698583c1d7e82f6bc67b1c92.tgz","fileCount":196,"integrity":"sha512-jsxIzkAqtnN7uj2IlxrkXWlWhhQy5VHppX4SxCBdDjc5o//AA70dLnG+iAuC4XR+2ml79gTmbaxbceAsKpphBw==","signatures":[{"sig":"MEUCIQDQD9TFG/CdH6hqWvNdwzpStO7kgT7WQGr2hDn8R5ZzOQIgYB/mu9A3E7ZEXjJd/+bRP76f+co8pLwH++qzaB2AnQI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":729469,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg35rSCRA9TVsSAnZWagAAXBsQAJehQtiGDvRH4dDlE92E\nxcUWBnP22GOusx1mChYJ4fb2rOSBUSLQGUUze58aW5NKqB4wkxouKhCLRt3O\nENz2MZFCVWiMqhZdTZeQpP58LQjIbqH0wNF9zfwgJmnyAp/3u2OYkM5+0FDi\nRR4Qrv7Ol7FmfbQUpYOVgO7zeo46VA98iUxCEFsypnC8e4PzxiLq6yoQIPae\nnufYKBlWkuPPY9e/arHYeyJ0ScdrQR69mHhGPcPBVewo+qNu4IKSGXmS6y2s\nqcuLCxnW6PRRIaUG3BkaUsjlFa2iPDaD1P+kCKIXkkE/CNzf9wEsazxZ5+dF\nk8EkgTH9grLrRJ521v2eALk8M8LqGoNHjZZMFOv+BfdmPVaAn2E/QcQFJL4o\nE/Srzt3bDfRbzxt8kGGeNFFI/bPIlKDPwF2ygPXLdc0hnjbXiiXBjmuFbzm6\nFAPbQ13hEhfFD8GyXu0/++Abr1E32ojFjNyU8GWIFWar80nhg2ldBlRKS7eQ\n0EpqS0mlObKRS10geNeCImKooq+O8B2OokCkH6G9TqdtOsX1V5+eWITaDpw1\nN7bUlWR5mot0rad5rK8ifIF/4OkU9VaL5kVVFNW2wN2hXp5zO7wb+em/YLkL\nVrZQGs1jtplJ/nWs2BU78YjpamQIH12wa8DdR9HEIOHwKHveJh5l5r59RCGS\nD4UQ\r\n=ZWBR\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"34dc199ffafae88e698583c1d7e82f6bc67b1c92","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.56","@balena/jellyfish-logger":"^3.0.36","@balena/jellyfish-metrics":"^1.0.298","@balena/jellyfish-environment":"^4.3.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.6","deplint":"^1.1.3","ts-jest":"^27.0.3","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.8.1","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.0.3-renovate-major-external-major-34dc199ffafae88e698583c1d7e82f6bc67b1c92_1625266897443_0.7058266018961719","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.0.3-renovate-external-non-major-9ddf40c069d949b8805844c7337cadda9136f814":{"name":"@balena/jellyfish-core","version":"5.0.3-renovate-external-non-major-9ddf40c069d949b8805844c7337cadda9136f814","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.0.3-renovate-external-non-major-9ddf40c069d949b8805844c7337cadda9136f814","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ae8fb61549d5db60d75848327a540af3003ab571","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.0.3-renovate-external-non-major-9ddf40c069d949b8805844c7337cadda9136f814.tgz","fileCount":196,"integrity":"sha512-6UeoWhpLRR9x9XI1ADz3rCidKqbceWNWwgYjlBYF9rpHfiO0yOegLoClqMmPa17X9qP0RJK49UXhASKCW51H8A==","signatures":[{"sig":"MEQCIBsdkc3uZoDm9j09IbMVsr6QQPuDpW/dHLZd32bYlKD1AiBHd/RfS1c0JDYnk0c0OH6XZ7BH81JRkQoINqmdaQx0wA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":729471,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg36zhCRA9TVsSAnZWagAAAxkP/jAfUc2GeI1sMgxciV6+\nLa4Urryq5kU0JRPA4Fptr5KUYI7QMQrXpqu4azUhvMHDKbuT9YyM7Hw/toS7\nnasP898CmnUNm9k3yyMrC7DVEs+tw460UEi07YJ7iikrwYVgM+qtWfeYLTpJ\nBaxRMWVOm7NMShT+hvPMzWXIsdAt1zVcS7H8L84D6okYIX3WNgM32ZS7fnvi\nmufuoRRBKbbqdHmcAjNRxu5eQHkz2oAoH9kGyOQLHdxIJrZi6q9cR0oqYB8/\nN8AdF3R9SVUWn0AoHmI0+sqawyrMzGbfq0fifDllC9idRcSBJMskJCZYzqR0\nlrkgLreB2Eg/wCFXOxJA3NhY7Ii65gZtVi/sQnGjCxFIYT9UACvT0/sBvpri\nQjW6agBaXO4jarkSJiAGaBI8n9ouYhkXtp8D1n1r2COj8qQWFlDohXhuae+h\nAlLV2zcs+Ks9jA2Ltm5JskS4QsboEHgmhky/4qFVi3boEVE6jQLdpGa8rX6u\nuIa84OkPrZZXl8LLa92h8RIG3D6Mdi1riq5Kb66WB8ssXuYiXNDg7Lk11isv\nX5F6DwRA4Y2ND9RyASZvdW5Fy8uik6/UhQWO0B0TLUXNDWBA1LlxTbmiRpEm\nJQ4RnU+eqO+1pbK02AMJ+2vSMoyWSPsTRdDqiPgzTjBkd65kyDIk7nGpwUwO\n4r46\r\n=0RTl\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"9ddf40c069d949b8805844c7337cadda9136f814","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.56","@balena/jellyfish-logger":"^3.0.36","@balena/jellyfish-metrics":"^1.0.298","@balena/jellyfish-environment":"^4.3.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.23","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.1","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.0.3-renovate-external-non-major-9ddf40c069d949b8805844c7337cadda9136f814_1625271520917_0.8107390130822796","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.0.3-renovate-internal-patch-083beee7d106c0c5a9882ff07baa64b125484ac9":{"name":"@balena/jellyfish-core","version":"5.0.3-renovate-internal-patch-083beee7d106c0c5a9882ff07baa64b125484ac9","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.0.3-renovate-internal-patch-083beee7d106c0c5a9882ff07baa64b125484ac9","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"dc19f38234e2a0dde0d64dc7270a38a241172ae1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.0.3-renovate-internal-patch-083beee7d106c0c5a9882ff07baa64b125484ac9.tgz","fileCount":196,"integrity":"sha512-8LBlQP7DaoiYVlYVVHAM5h+fNeZXGBnzHQ8f/pKHYzPyTw/XZk8E/FWAsO+dCaXoOv5Q+G8amabRFUinWYcY3Q==","signatures":[{"sig":"MEYCIQDsHIlHBAmgCaN/0lppqp13AIbu9w81iM1retNP6Si/WAIhAO/Rb+tLY+zq8FQfuAwlnpIK7NZzGBEmTSN8icijrQOy","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":729463,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg38A+CRA9TVsSAnZWagAAiLgP/0+QFnaawlPj8XT4w0hB\nPnM6h1GcYveSzoUCIgYh0OHUEpfFy/CMjYdaT5/PseAI80JLCtJEZNuCBi2H\nEyT44/Alwjt4N6wTLHmDLwEP1CWbQuLSIJZeLSU+fy1VJ67h4b5OwemNMRXa\nhv0Vgk6xDXEHnABrWCFqU3oywqHQ5JyuxoA8FAhCr8BXhmJv0YjS0iQCyvs+\n06PX/sHc/+UEE2dmfd8oRVgB/iHMru67jQAgD9X8y3aO7c/PxScyL4XPotgx\nTXfwuY4RKnRMqvoSnAHoamaHs05e/5Lwb9iwIyZ5E1s+ZVcTX6NEpmyvCNiR\nCCWu0pK1LNZV3Su9W++uXXK4Td8tTa38OQo+PFVxtBOze+EiYdxiRina3EWZ\nA92uJ5WpoZI7+YgDUJS0Ou+3qgrOY0nyN8gyTRQGLxmNTvlaGW0Zg3XRm7pn\nyb/Gu2tApDejVByjc4S6oEZOK+HtU2gUCyS/OMuLrjO/YhYqAzQ6D4MJL23i\nlP3UcbZVHXtxX/aTf5uE4Pkq4038oV4yLzh7YE0GpyQD95K+anRZLOcI2tWm\nugaicNEDnPSE5CO+vqlZObXxi3WRZJEjen4ZWS0VN922vKNGvVoSKGl6aYhI\nkmUm7nei5EmkeeXX9vNRmx/rbEdCEaoykYbDOve6hJvzAbrUFbsZkMT4HcGk\n8kix\r\n=J8Y2\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"083beee7d106c0c5a9882ff07baa64b125484ac9","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.58","@balena/jellyfish-logger":"^3.0.37","@balena/jellyfish-metrics":"^1.0.302","@balena/jellyfish-environment":"^4.3.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.3","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.0.3-renovate-internal-patch-083beee7d106c0c5a9882ff07baa64b125484ac9_1625276478445_0.7178661934937829","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.0.3-renovate-internal-patch-ade6e77ba9cdca66c7ca3177808a0b627a4b5e22":{"name":"@balena/jellyfish-core","version":"5.0.3-renovate-internal-patch-ade6e77ba9cdca66c7ca3177808a0b627a4b5e22","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.0.3-renovate-internal-patch-ade6e77ba9cdca66c7ca3177808a0b627a4b5e22","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"7a03b3ea9382b81f6c814a78fb0ff78a85dce078","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.0.3-renovate-internal-patch-ade6e77ba9cdca66c7ca3177808a0b627a4b5e22.tgz","fileCount":196,"integrity":"sha512-nmVNhWXGwSPoCVJXznnJxhJXlVftXIp1Jem0mrtcCwHOfwYrbBtTowGo/JxDyG3dIqwgI08XRKpCBxbsPZVHRA==","signatures":[{"sig":"MEUCIQDhjal5rBKO1tQA/Sxi4aa0MrTIo7Ug0T5KoathfpF4pwIgPHeJCGnvDwGf4m7iuq3z+f7ks4wcXECnmfxfmR0vh1M=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":729463,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg38EsCRA9TVsSAnZWagAA52MP/R6uiqBOi8yM7CJ50OrQ\n5uu4McYK2XvVgBU0ZrSGO0StLaG3fLFHU5dMzKqlVzG6lrfRkxBtuOq9314a\nmY8HJZ3qX/OUdUeiSFvlqA7jNdgK7UeOzhPfCeoBLXCp6fkn9C/Xr2KCkqKH\nfqQXgQwnHrsmpgw5YQqIqDXEIgfigdIE0TlL2b2h2qLN/LpE1NGYKcrLbAT0\njh8X8VQXSugEWdMUsJx5vetFjUmJ229MOPmgRXTMNh6j/wtU0Z1D96NjyOTK\newToETjNIIKRnBIpqAjbEwIXHS6IfNh/mRey0+aKP2TeoMCUcCQchYrPvqWn\nUBwjlxDBXMkid2oUZG5Lbf/RQKbGQTdsD6+VLlxV0Z0lQ4Hscmloxn7SSdHw\n4rSwpLPodcqqWKO+JUSLWPMyBCXM4tbZzz9P2u4MyI4AWuEx0qqzp46Lanbv\nfG2kKn7iZK27tZ4UQyItx1G42naZH15RYonc0obY5L15j345DxRKMLEZgqBT\nICK/hfcNDnNYLnMrfltegxhDoAquvIRo3ZkIKNdefRUbeouWsX8810Qp5vsd\nvqAQmWKF7yaA+VyPBMBZl7bsb/l5gvxYDtFSM91lfiUcqhdujP2ZPq8uuW0b\nHwMNY8pVmKaOGbtLJIqhnF4PmlEBr0ikLUxWTTUkXo4Kh2QK1I538lIseAWX\nUvgi\r\n=4fYH\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ade6e77ba9cdca66c7ca3177808a0b627a4b5e22","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.58","@balena/jellyfish-logger":"^3.0.37","@balena/jellyfish-metrics":"^1.0.302","@balena/jellyfish-environment":"^4.3.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.3","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.0.3-renovate-internal-patch-ade6e77ba9cdca66c7ca3177808a0b627a4b5e22_1625276715585_0.9975412397825478","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.0.3":{"name":"@balena/jellyfish-core","version":"5.0.3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.0.3","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4ba97cb552de0edbc552ca213db78b6b98515b61","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.0.3.tgz","fileCount":196,"integrity":"sha512-xIw0aYytwnA2V84OF/ILvcYu5pv0XKDwNatS4OAad01/8n/aWgOQlCQXREN1OBw2OiziSRHe8o4wfaaYn7bGXg==","signatures":[{"sig":"MEUCIAeSU1A63LXRttaSAdNbHBiOvaedW3oV+u1qlAWk3nQjAiEAuCqZ03MTTWiuZ1q1XXGxZdwfd5XepFn9YtlkjDqwjvU=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":729398,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg38JTCRA9TVsSAnZWagAACf8P/1AS/oTRjARJgjbqC8Ac\nkMmHz1APyvQERXkcJ6ZLvsNsZPLpzEqof+MJ4lahyIcDElN32vWZNcq/vIKY\nM2/H+Ru/xLGzGfwD7xSlqvQ7qra0yK6fTvGCRxWX5XCabTjTK5oTI7zEddOU\nJpYYZ+6yQRaNTD19xId/iPHWCMNWEb0QPSwGQZspkYeAfwlWAOmje4iG9AZt\n0Fh9pzAbOlnb6aFxoU/Aa5wLoCWcbGBUaPajST1vFBKe+5RARqWCcpP7BZkJ\ncWgs1AWakbwSL1AGGdR153zWWmYLCyxLU8m4d3mD5Qa1pF88FjZW5N2y68wd\n1D9ewTj4DRyTVF6M6NoEYAkp98w67POuy+b/AVytesaAOyyROdotoOnb7Y1x\n09Ug4cTEnid8GUwRyujE2l4pEav8fgGRvQoXpjrXpmlMQHWyR34xX1qKX+zu\nA4cQZTFreM8+i05gIbvQ6HLCLX+XZ19V2TU9FGicfVcFZ6Z3oTE35qwzPv/l\nWitGkxWXNqlEL5E00vhgrECzzjWR5NipWUTWsAnZy8ISe51C0FHzXTXmtXOV\nz+l5yTJpWLvBfILI8aa2NaLe089KPYw29vvXfYhh7mP3fw8O4kp8z3qtAB6s\n53zKg4K6TDXzbIN2pTfixPFh6gJS7/xTX+88+cCe1lBlPsTmniJf3GzeBwlH\ns6YS\r\n=8kkw\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b7248ee64429c520fc44c39fc3f87fd2fb37aacf","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.58","@balena/jellyfish-logger":"^3.0.37","@balena/jellyfish-metrics":"^1.0.302","@balena/jellyfish-environment":"^4.3.9"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.3","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.0.3_1625277010646_0.9163749957686715","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.0.4-renovate-external-non-major-5dcf727d376f7d79b7e0794f0094f8055a3ba48c":{"name":"@balena/jellyfish-core","version":"5.0.4-renovate-external-non-major-5dcf727d376f7d79b7e0794f0094f8055a3ba48c","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.0.4-renovate-external-non-major-5dcf727d376f7d79b7e0794f0094f8055a3ba48c","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"48a8020dd4589b8205e099c4581641a9649a6e42","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.0.4-renovate-external-non-major-5dcf727d376f7d79b7e0794f0094f8055a3ba48c.tgz","fileCount":196,"integrity":"sha512-HgXYowVwzUKjESDTlV7+qlaZElUkXW/7oX68ej1DofeVuf/HvwqQVdPGXFHFQkrAAKl5LP6HTcjfcuQgJf7WtA==","signatures":[{"sig":"MEQCIHUtwBpJQI5R8vyC6J722zVMrzgomtdpmxL+3Yhj4uzNAiBq2KEIckOLcmA6/DUuLI4w4ClFZZ2Vt6Ui88XbkJJX3Q==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":729541,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg38NUCRA9TVsSAnZWagAAMU8P/1rIiTtajMHF0CbCkVI+\nuwiWVpJLCFB8/74wGVy7T73waU8Qxo0aEc3nMjs19MdxiMASbu4mWNBwK4Ra\nezKuf4K57kaxQb+haqysLm8I72P62prEGJ8lPQKeINsg7/C9AXLEKa6tKYUx\nQNX2fz+podShHHOx5aKsKi7/RK321iWXgcqRIq5MBQ26eQgJQhBnAHQz2SZ4\nt6F4uNPaRovguluBOTW48JvgFNyOYg8HWaNLwBqcblCYEekSlkAaWnNtFOj2\nx+DS43efsMc/MzJranvHS7mTXgFf7gT/bcgZumvGemXCzgrdIh5HC7AQlkn1\ngO41oDfCQ2a/mBf32NBzGYds3SsaAVKI3reZGU+3GtwCvL0lziwqV7g8AcGX\nnBpVstxgnCz516wnv2trbmpLIy5Ni1gyYLPZjhe2nQbKhMAfSD9A2N9pcivb\nJ1CPs+YQkl5vNwi4uSobLeOI7DViO3qbekPBpj3KG3vxXxwzt4tUY4hG5a5e\nfhIdKCgprEh9EL82PxiYwwbA5QCems/U0/iEsE6wEbUsWol82sw++qwBGJQ3\nDQdRd7MUejIugoBdUYFs96vG6rDIa+d88KhGfamn3CVZRr+kPce1xkR9tHou\ngrlV3Hg0tmohCl2QuDXT8zg0SUuR1teA3QCYvefZ6q+gzP76b1GRXbbcTmIN\ncWsJ\r\n=L7xf\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"5dcf727d376f7d79b7e0794f0094f8055a3ba48c","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.58","@balena/jellyfish-logger":"^3.0.37","@balena/jellyfish-metrics":"^1.0.302","@balena/jellyfish-environment":"^4.3.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.23","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.3","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.0.4-renovate-external-non-major-5dcf727d376f7d79b7e0794f0094f8055a3ba48c_1625277268459_0.16344577042531294","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.0.4-renovate-internal-patch-bcb3af4dd07c8dcce87fe4d4ba74e149eca88ed4":{"name":"@balena/jellyfish-core","version":"5.0.4-renovate-internal-patch-bcb3af4dd07c8dcce87fe4d4ba74e149eca88ed4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.0.4-renovate-internal-patch-bcb3af4dd07c8dcce87fe4d4ba74e149eca88ed4","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1c061b734ac36527015d70d123e21df037700610","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.0.4-renovate-internal-patch-bcb3af4dd07c8dcce87fe4d4ba74e149eca88ed4.tgz","fileCount":196,"integrity":"sha512-IA5OgA+1SPcCKf8cDoMbj5LfWDC7CD2kUxsrWiylhTvQDLU/b6ZE1X+lgry/xgiSUuYM/KnJp30LbqfwTF3CLw==","signatures":[{"sig":"MEQCIEXIzxCZgsRsyJfzBL5rn5fxOp+JuaUXa7V6MN7d4aB7AiAEtwZE3yMi5McH55eCfbQHkCVS5t11BLG1vVTTNHtZ5g==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":729533,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg38P5CRA9TVsSAnZWagAAgCAP/iQ4I3L4DZiNYuweUdhE\nUE/g1zs8zkxlwfv5k6c7PeKHm3f0VGplrmHATn35j/20Vd0OalrYhLLRqvUU\nxB4RAX4qNjioHzdoseC/PChtA7TxCA7+/5DCmc+9I9o0HQdxSMK6KAgkdGYP\nAuT/YX7Z5yKMDsJruT+wo2StKizZksaoVn3Mx+bJj0Q9Md6cbSqYD8BbS3do\nP2fFuyCghjwmswg2kKDsoOcwGWybmTQN3O5otjGD3KOp9aLffwzQfHm7oEXW\noU4UAuHoP1VHMexFS/oaWJOaOp72MEWMWtfjV7jeK7FtHf+5kxh6+8BKC/jm\nEcexsKgmcEdMk6HS3QhuNto0NEwS1aobqyRqOsVDDHAs7MWUaRp77Ua1Krw0\nPX+2TIfS2zjmLhtMg9B2BOLRhNDM6e5nrKpP4ttt4I6rRhMHTt9WNrC6F1vy\n0dlwVAOWKUwJVyBnkNmkqCVZdwOOKBvtuFkiPvdv3ls4sTyk82Kou4ZiVXNL\nP3wNhSdkXv+faBXWDhjwGLQ2fcvEgMoPG/95r5tvY9mM81zD5s40pEzcGnn2\n8iCTv+jbxMqomF/UTkUHn8V9p42mTFGisXhQCJ5ETdnHQ9Rm2o6O0YKZ/nut\ni50WiVrwpbg8VlwDwVeflQ/IJJBWuJGZYYqaZCS/RIMPNLWxy/fQkpQJz6F9\n8jlZ\r\n=2UUn\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"bcb3af4dd07c8dcce87fe4d4ba74e149eca88ed4","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.59","@balena/jellyfish-logger":"^3.0.38","@balena/jellyfish-metrics":"^1.0.302","@balena/jellyfish-environment":"^4.3.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.3","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.0.4-renovate-internal-patch-bcb3af4dd07c8dcce87fe4d4ba74e149eca88ed4_1625277432952_0.01278905966872923","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.0.4-renovate-internal-patch-eec4c5363705b6684f67c9ba2717d9050f99a5fe":{"name":"@balena/jellyfish-core","version":"5.0.4-renovate-internal-patch-eec4c5363705b6684f67c9ba2717d9050f99a5fe","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.0.4-renovate-internal-patch-eec4c5363705b6684f67c9ba2717d9050f99a5fe","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f25ce35fb8077c75d665e6c141ffa0483fdc576e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.0.4-renovate-internal-patch-eec4c5363705b6684f67c9ba2717d9050f99a5fe.tgz","fileCount":196,"integrity":"sha512-+vYtC+zaVL5cuwNL9RIBFg5s2Nd8+2FTcgPphdVgykkHY+k+zpmiwRnjs6JI0e+zpXslMPWsY4uLqE/p9MH4Lg==","signatures":[{"sig":"MEUCIQDfmPv0HdQ+1uf/JVrXpG7HmkodBJo0SSNl6piF+FHr3gIgay9Acc3P+UGuXzOeln3UgY1ctsuv4VVjwl6w1ZjFulw=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":729533,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg38UVCRA9TVsSAnZWagAAVNcP/jLtERTqQmJNyqit3MwH\noZzOMy8OIn+to/aT17aldAjMU2RGUIGwmVhmtVOYaJlGFdFfz0+fkStUFxfY\nOtvCUYGZYMbeHKMajfYL9ecKmxaBZ+7i9hWWZmN++aVkqg6oeRSJJnjIgFqT\n1TFxyIF5RlwbIjUyp8vh4TWOe8eT3ijJAG5CyexgMDpZrvKkIAfLV6mC8B3m\nn9TMnjwPg7A9cUchK50NJ19dJVkxVaeffMvRav1lex60Cd6HnIsFDR22otxD\ny6FJQAesZcMXuhjRYSuBfTuLWne7giZs4+nAXg7pjJIZPpIvx/C9+5RIJAp9\npMXsyXpxj+PpKfgJx9H62dYw38zoJmIO5ZCYlsDO1onLfQMoC9O6OJ39DXBD\nsDEoh0Dl84PEzLSf8wLkp7wst/JAECClrtIUjnGcbZiE28gkn/wBhs/UJN7f\n11byAqTjPjXHT0f7mTkaPIq9Z17+h3tuQDtqlmLDq43j4HyslZbny1SLMUNy\nJKlmx9c1Mksxa6SJbeJcqQZk4ITHULT2Qeyd+kPyB8Zrb/186Te6GoDDymve\n4ZX808VP1Mwz06crZ06UT2nTpeigANXPgJg9V49hATgNK/niVwETp5FrHDoU\nACY1jLzr9JbM8kDHwLM1KK3OQ+EC6cpooTvsc9VRSrd1YGkLcsfnPJfPuszQ\n/138\r\n=nxd+\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"eec4c5363705b6684f67c9ba2717d9050f99a5fe","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.59","@balena/jellyfish-logger":"^3.0.39","@balena/jellyfish-metrics":"^1.0.302","@balena/jellyfish-environment":"^4.3.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.4","@types/jest":"^26.0.23","@types/uuid":"^8.3.0","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.3","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.0.4-renovate-internal-patch-eec4c5363705b6684f67c9ba2717d9050f99a5fe_1625277716944_0.7668359309117911","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.0.4":{"name":"@balena/jellyfish-core","version":"5.0.4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.0.4","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8971b2faf7c8b9cd5bcd767573d699a9dcced871","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.0.4.tgz","fileCount":196,"integrity":"sha512-9VzAscLXMoJb5LsPAfQmcTEPkud1qbcSzgMNx04DcmLqd9Kf6kItbYRFBoilPdutsts//10bLqHp3YoNCnXKwA==","signatures":[{"sig":"MEUCIQD1bVkCuJAoHpIKdDm6F1bFNuFhDN9R31zqwd5a/BOG0gIgEwNfUcKItGlyCmUS3fKgZERYTQE88Y0BcVgEIw3UZeI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":729472,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg38U1CRA9TVsSAnZWagAAiDcQAIh6A1Tadgt0D7KlDVNe\npa9MSem1aompClWj0viWxLrK3sj4o0mC/cXEHqf8dGVzU6BJ8KH+AkJOVPMF\nOH27EVV4sVZVzDLqZs4KN4Z7DhuIeAHu0FtcTVuJ/1NBYkp3ON1Rq6Hev7cV\nLk/KF0bE6EZSdZoh4g16n1lzKuNjC/9BAz53KqZrEpK78ggBBVOhLdrfgjhc\nAf/qM3ZGSrzjM1E1R746y+SPt56jtgHO0EwjAelslAaDNKO1PkznKxxdbn9M\nbfCpOgwQ4mVmDvp9JpHgxTpxOwalGMEE9Goy4RQU2WBfPZ4etYWaym65wYJj\n2P6HYSd/iJoWjqwLOWDYBU1h4Zquiwqbgv6MvF7KvVYrvtmqun65sB15Ubsx\nte1wBmBFmacQL5m1exXG6+bCtjC2L01f8fr0A6FZd8jAy4eDSGtmmlRM892f\nqx7vnC1Uja5Z36lO2d8hOC4w0u59fxoUDgGyrNK6A4wcRLXT/En1wVGzAWv+\ne2CqpNNE/JC/n/4202/Nq+kyN8RLwGtn9Z4lrq3E/EHz/8ectjXNEAnYMZu5\nfW7JAm6/grAx6I9LACzBFkO2kBKUEswED8pxjh0bj6X1xZWJ9dLSRC3IbMpu\n4+PFSvxs5IEVemg6z1Vnb+OU+9vGOoWunejU76gnrky3E81Dkw2+9DJ+4W7A\nE89u\r\n=caIM\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e0d5d44aec03cb6476eed7083f9eaf15647c0ea1","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.58","@balena/jellyfish-logger":"^3.0.37","@balena/jellyfish-metrics":"^1.0.302","@balena/jellyfish-environment":"^4.3.9"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.23","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.3","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.0.4_1625277748965_0.34696027868858015","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.0.5-renovate-major-external-major-5f66873831fe2dfc8d0d62dc615e791f39d76141":{"name":"@balena/jellyfish-core","version":"5.0.5-renovate-major-external-major-5f66873831fe2dfc8d0d62dc615e791f39d76141","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.0.5-renovate-major-external-major-5f66873831fe2dfc8d0d62dc615e791f39d76141","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"69a3d5ec693fe9b6016692b663e00927d6f152c5","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.0.5-renovate-major-external-major-5f66873831fe2dfc8d0d62dc615e791f39d76141.tgz","fileCount":196,"integrity":"sha512-zQkFs2UJ26c1vAn5kxA2uUbC6cd6etV2Jc5rzHiAPR5uVG7wFbh77ZrdfGlPlUzDX16rRFPxsWqqGblGHmUx5Q==","signatures":[{"sig":"MEYCIQCqZNzsvFITNakQt0JFUvNY3p+D4xpknFaJYrtnb8UniQIhAOwRhANjOmotZyyewPW+POE/1OezrpRiAvCegh4zONxc","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":729613,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg38bWCRA9TVsSAnZWagAAkfUQAIqRPbVbANkSO5GDfdX/\nPuPBDhwOaPfY4K3pPVVYONMpERIm+TmaXSjYRvglFLFNqIypAPklYB+98B35\nY9zoo3UDC/frKakoPW18Bas43lNX4as5XqE1PgBBGk1opCH4GpeHCRXxF49c\nOc1CnJL4uwXArE4W21GVD4DIH745qTr7LdDRXlCesj4iEA/kmB7rt9mGGdMj\nI8rZMN5WyzBRVCKWukRuh24DNX1zuGr5nEu46xnU8ri+3sbN/jfDoImnL0yu\nZtUrkLk4KEJF1bBXpz6PQKHS4DHM568precQ81UIXq+LpdRRJeuC8Wr56UDZ\njqScRo9EFkKz6bIFTytJWyNDFRH7DX79W0UaAAXP7unw89PGpKQHyo9Uf713\nYBMVjb/GGckwlqSH8jg67+yD3YcARby6ZyvRvmnswxt+JPR9MjIIVc0A/QHB\nKKsMhopH4kH76N0oWCKFYtzeZZc2cahlRXQofpQ3kMLMZlimyFYfFTr/T8MS\nE2njMkKDTMC+c5kljJgxeQbARCXFEQ6AOyYMuObgdrw918wcgfH+S+dgrX2j\n/MDmPPP+V4Xicc6GOiUF3Me66981z9UsZSB7Yq1/8jsOSKnWrrd2ElDing67\nrCPLjnSLGUNn5278M6fpNieYWUsFzPh/r/IdOm1u93JI669QSzbJqFnftZD+\nS96S\r\n=Rreu\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"5f66873831fe2dfc8d0d62dc615e791f39d76141","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.58","@balena/jellyfish-logger":"^3.0.37","@balena/jellyfish-metrics":"^1.0.302","@balena/jellyfish-environment":"^4.3.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.6","deplint":"^1.1.3","ts-jest":"^27.0.3","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.23","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.8.3","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.0.5-renovate-major-external-major-5f66873831fe2dfc8d0d62dc615e791f39d76141_1625278165824_0.953241892210996","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.0.5-renovate-internal-patch-1c371a0a6c1631a2c8b3166d7c3fccc306f5c14d":{"name":"@balena/jellyfish-core","version":"5.0.5-renovate-internal-patch-1c371a0a6c1631a2c8b3166d7c3fccc306f5c14d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.0.5-renovate-internal-patch-1c371a0a6c1631a2c8b3166d7c3fccc306f5c14d","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9a4a7146d06e1d9cc83bcc7c4cacbe80e558e1da","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.0.5-renovate-internal-patch-1c371a0a6c1631a2c8b3166d7c3fccc306f5c14d.tgz","fileCount":196,"integrity":"sha512-+lrFrY7GxW7qEKomT5OqGhuL1VHBeUjwQvfvGY6dUaWsqIWb/ce3h30ZIMQZXdRB8TRem00qunuVfxrIjVxxKg==","signatures":[{"sig":"MEYCIQDnoUWzb3oFAvm/VLAIH9LNFsHEDj1Y6oVX4PJa0803KwIhAPr1j29ryP5CuISsda2RMcdpad9YCVuMlny0t7avKskO","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":729607,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg38gFCRA9TVsSAnZWagAAwLYP/jNFVcso7Hx5SFas3qOt\nNC5oV8ow8trDjmAz1MEGys4b3Fpgmdwu6AbrDtaYUSEUNyPv7DA6+pt/ON06\nNZR29re0Txv0VItUFp6wNIEnXppDMaopzmrEHIL1V+Qutllf/rmXECwHiV9I\n2EcjU5Q85RzFQIwWOgdYMaiUUztanj3QLhYC5O/sNip8T5hUI413ZgbvwB9u\nLjKoaIRW8RLYkpei24EKFMQAYchA3t/OdO93T/4hfX/5NRDJE15cCR8xafyz\nnfeel34ukGzbYppxZ25fUGga/a6FeD7C5TfhlTpMAkCsUxSOYXXCRuDUE5N/\nxIBo5rfYnMkt+3/GzcTgBELxPk4n40mAs5/GmVIKMF8j3bfXXDePjn/mHTVd\nA22PrjVB8WT93r/xi83YZWbf4c6RWiTVVuqXCjoCt6JcoO7W127bD4ftCBXn\nwVMENPRwtzuvrslgjIJy1xGbGgoBU0WjMUsLp91MQG/44ZsEURx6jkeWkDGV\nldL1XzHJFrLteEhzvayUsEN0f9nw7+Q9Ol3H3RW9B7zuHSfi0rmHRr+qOyAX\nrEGA/W4BAn353gVVIEjZDsLWshQ1oyML+nsxcaxcwH7h/aArcjtKqlles9PG\nkzI5cLeRWCaF4Td2QCimep4izSvAyrwQEFlLzawfNASIwoejOiCkyFtKTtsp\nDDNA\r\n=dL81\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"1c371a0a6c1631a2c8b3166d7c3fccc306f5c14d","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.59","@balena/jellyfish-logger":"^3.0.40","@balena/jellyfish-metrics":"^1.0.302","@balena/jellyfish-environment":"^4.3.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.23","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.4","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.0.5-renovate-internal-patch-1c371a0a6c1631a2c8b3166d7c3fccc306f5c14d_1625278468420_0.5298172327317059","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.0.5-renovate-internal-patch-23a654794c58bf889b5f76b8263ccc9c864d5bdb":{"name":"@balena/jellyfish-core","version":"5.0.5-renovate-internal-patch-23a654794c58bf889b5f76b8263ccc9c864d5bdb","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.0.5-renovate-internal-patch-23a654794c58bf889b5f76b8263ccc9c864d5bdb","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4ca2363a1ca857508f557e0e222ff2fd3276828a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.0.5-renovate-internal-patch-23a654794c58bf889b5f76b8263ccc9c864d5bdb.tgz","fileCount":196,"integrity":"sha512-CSIY0PqT94UCgwaAT+20w+/M5Y5AwkYGBtLi97dnyh8gGYb6tvHWyIPJf80Lm+GWBxvI/9ovuf4K353n6yJykA==","signatures":[{"sig":"MEQCIDA9BdLE5VNQw+CEbgnDar13dvTWwBuRwGQJMdJYnJADAiAYBOh6L0wQQoet3dXxC+3CWaCRXD+XyRPUbYvB6uHPwg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":729607,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg38kNCRA9TVsSAnZWagAAmEoP/ii7Dsr9NwRRIHwdWnif\n0GM/Q0ylaDalqBtDArGldmdEGSE6aiswLTftiWj5nuc64KhqS1pLulit5YB7\nje7VuTNkuSM7AGBdwRcbPRka/6z2QgSve4mJjsF1eOeA4RcY+ZqchHbV3OlK\nKhMEIz5ab0MmS3EHZq81HXfPalrAR9S5S6zQE0eKNKAxRXq+8JZe1HxykG1I\n8/ABUnUi79vEwKA7Zp7QR8lu7o3ZaPqO+nc2xE5DoMPaMl6laqa1VNSx/Bmg\nLIFFAfKJypCCC6KVqJwPeTCYwt3EXJ2SSqi7FWgJiNpEdcQt3KPQPC3pmCBg\nynANoOWunN4/k1cz5PIsOtYH2FfPSqVEn5zPIrcDFXaOQWU7MoOrD78Q7kCs\ntqK6PNiO9Pm3MVAymsqqxjpCc39BLFI2FYeJh9CHytsUBBfGxku98UQW72jZ\nWvtMZcfPcj6ofy6CNmhvXcWlw8YdVWnvrQTH3PGcCMMNrhzPCcFxDfja//yG\nCixXajnWxkV/dvxdCFaFSbbTwIvr2QB3LwDw/KQw+WFik4gA2H9hbm8GEZHa\nxrU6Ysx2En2n3yROMIqq9To869Wh//8pjYvYhN0hjOOTDAtmr/MV1lA8O2SA\n56zjXRflFDoOiMikPlLMfMtVotv0rBEs3FClFSmTuB9/vtj+++KZO6DlpnYh\nb/EU\r\n=6I8R\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"23a654794c58bf889b5f76b8263ccc9c864d5bdb","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.59","@balena/jellyfish-logger":"^3.0.40","@balena/jellyfish-metrics":"^1.0.303","@balena/jellyfish-environment":"^4.3.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.23","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.4","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.0.5-renovate-internal-patch-23a654794c58bf889b5f76b8263ccc9c864d5bdb_1625278732535_0.38109188973699504","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.0.5":{"name":"@balena/jellyfish-core","version":"5.0.5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.0.5","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"90a53538d7dea3a2e0489520b676303b594f18c9","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.0.5.tgz","fileCount":196,"integrity":"sha512-rlFzRVdRJrFEvJhPYeZ8phGrLsb/vEeIdGy4DjmdWQgM6/8AZ/0MNWNKJC/upks4G0tEe9Ji+NRCYlhCLcR41A==","signatures":[{"sig":"MEYCIQCVa7k9Dw6vDMbX3TGGtNzYKg/kQUNWdPFkAiOPVwt3SgIhALuYg9UGzWVC0HTORuRO9mxVKtH+C3QrQ/tG59AMgf8O","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":729542,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg38q+CRA9TVsSAnZWagAA5OsQAI/sgVIyc3HZ66bzaZ0F\nbq3CXnRMv6DBXOC5WtmeunRU2UwxGyQ8kT4d18ZmcvmRWhp5YHDw2+vrIJzV\ngPZ5Jw7lpmq8dX8JbnQI0zFZyOJkWnXoVxexPoIS5tC0+7GX2f+A6Ui7HGdV\n+B1yyigVyg1wKSbcaUa8DgJr32ouXyxRBNiZYWOg06YxZbqxAQIWrLXRk289\ncXa2Uck0wFCStwfQ7mwldymBE1Pz91HrhCoSi2PwsNdbhOw8bID2vj/DjChL\nux9FV2NgY69GGdWXmcmtbmJvu0oYWzaiCy4qbjBcpJVTxh9uZ30Jqj8ffZo5\nuIAVhMrjAXwQ6ADNdVG0HXV5RAFND5Q7Crut51QYtzTS3UcInar9V4wPH4bf\nVQeYeJbkSqINUIsYOCYetbV2N+u/9E7Oc9Xc8MxWd/j6t3k0Uaq1A2jGas3O\n7nFKgXTfWDk4y3DT4K0+qnTN4M0KZAA8vjGENjXztgqiYnzmC0/ZmNbDwBQ/\n91T/KKnZbL8e9kksIZGlJgkNIv19bN40JwHi3+QV+E2U50sanB2VfMzY8M2R\ngeo0ZDmOe/rEyzQJgIY1KU8LEdEoOqHMLBFGbjD7X/94esCWzVv1YGb7X6Xy\nre9vZQyFXmg4l0k01R6ij4iMdhAEuRqwJ+blXgX36lShMihPZXBPpLNF5w9E\nsfLt\r\n=yoRK\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7b2ed62df458d8d6c2278957773260c32749c25d","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.59","@balena/jellyfish-logger":"^3.0.40","@balena/jellyfish-metrics":"^1.0.303","@balena/jellyfish-environment":"^4.3.9"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.23","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.4","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.0.5_1625279166118_0.24535380413700558","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.0.6-renovate-internal-patch-656521503b16894bcd8c02946610c787fece7a52":{"name":"@balena/jellyfish-core","version":"5.0.6-renovate-internal-patch-656521503b16894bcd8c02946610c787fece7a52","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.0.6-renovate-internal-patch-656521503b16894bcd8c02946610c787fece7a52","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"39422420ad382a6588189761de1f33b43bb8c7e4","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.0.6-renovate-internal-patch-656521503b16894bcd8c02946610c787fece7a52.tgz","fileCount":196,"integrity":"sha512-dVupdtysNIJGwh8LE9alJ07f2XKbXHuqQA9clPPdBAM28zsyL1U43BREJgkhrATWPTFtISIDCQ6NCtAMhsQ/1w==","signatures":[{"sig":"MEUCIQCRID+X6Gye3Dww9IDwC5d+xVy7DH9MZ2F1Ry7JCarQTAIgGTIrBmHr85bUsMW4b6jUMMqUjV6akIeVX382QPdhmn4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":729711,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg38uMCRA9TVsSAnZWagAAuxEP/1lIcPdpXHKIw7hmqXhz\n/JIZg6AklGrBdcrEsP6DHNrdaAKsaxFRAPmwJMfLMa5P55HXKt5hqxG/UVU5\nVWRgHiq3zCFOYSnLc45gA8FZOxH9qgg+0qgHI/GSuaIhquTFgRE+vcgSSUwZ\n762ywh+ZFz4FodrTEICc1LHJQeSAWSb56LV6F2KdwAtOZZ70bjxEJBT1/un8\n9VEtmQky1mvGFAV1D0z+xbaOK6MvnEAlQ30EG1VwxYFu45TiRZ8urOiyBKqn\n/56hKYOVD4hvn69Y909bJAmVrhT0se6gJGjP4uyeSMQVUaS73mD9+YnSCgT1\n64SRdCDDLTvBhT/u1o3MgTCNuWEN7Bpco+7rJ6b16UfI0cBEuL66qvCgiPvH\nayqFPRaiaa53PCP5K1kfN828Fa99wc75rmahuV94fzxp9Ahz7hM5TOvXYG1P\nYV5j7B1oXCw2K3TOx6dLdHDc470xDeaAyxMyv2A/imCXrA/MLs8faV9YKSfW\nX3GeAJEv+sfqon+gcRMLJHRME9peTuth1uvuGiC3tPTRYXcklAcTf4U6nR3N\njaRriz45gjLcjsZt1YWIhP7f7yprg38rJgJlbaOWNS9qq0uKXupC0mh1y7H7\nF407bnr3lYmBht4XQluj19xp0yu+9pyCTkPI8QhZvw9IyLdbVEswkfIEHMKk\nEnIV\r\n=KP0m\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"656521503b16894bcd8c02946610c787fece7a52","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.59","@balena/jellyfish-logger":"^3.0.40","@balena/jellyfish-metrics":"^1.0.304","@balena/jellyfish-environment":"^4.3.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.23","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.4","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.0.6-renovate-internal-patch-656521503b16894bcd8c02946610c787fece7a52_1625279372302_0.7744746736688939","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.0.6-renovate-internal-patch-0a095ee8548474c06398e89bb41912b7548ad744":{"name":"@balena/jellyfish-core","version":"5.0.6-renovate-internal-patch-0a095ee8548474c06398e89bb41912b7548ad744","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.0.6-renovate-internal-patch-0a095ee8548474c06398e89bb41912b7548ad744","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"badd9a9abf064f68f1f882d228d4ce09691d818f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.0.6-renovate-internal-patch-0a095ee8548474c06398e89bb41912b7548ad744.tgz","fileCount":196,"integrity":"sha512-zymIJfEgIXP6SZUhQC8vOG5NU+LAFMbqxQKsQdbmJ+afQNU6DZPlO3s4fYa2Rzq5CkAIF7YVNaZyU1SBYeyQ6w==","signatures":[{"sig":"MEUCICCFAV4rU4EOR8y/qDGoNU5+RJLhJKclnpf4y53Odf0YAiEAh7niW2g7yIrgAHs7DLXvBjXBrBKcpS65u3ZgSfbus1Y=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":729711,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg380xCRA9TVsSAnZWagAAYw8P/A2WrCuTalcYiHcYr0Z1\nX+zhJsnYw+7A3asqVVe43tNrsUXTOE8/rsYocftaGgQ+viyih7LP98szh5ut\nVkT3cecUYtC40EMheFAowTHN/iOLHa/v7ZPL6MzaJgVvEapHMDc58pyUu3z6\n4Xh1RE/ckV9mtGFcTEoTt2HkJxpQv9jiXbBSYcAazTec1Vs0TmuGXZjiR2j5\n33Es9YVKWKct57/AYk7gf/aWUL9UfkKgT00XEHIxw6ujnFcD28zFiQm+V50l\nQRnWWi8YlMQ0vL7W3m02e0yJfc4zrwsdonEMYMXaHtPpY06yOTMMXuByM0v3\nYbcX3L3a1wn+AhND6bnw+xT1KMdl1Ei4fbihDIY5tT+cqUC9EHqFSMqcK8l/\n8z/fS6vDZMvuTIPra73NqjdI+8xcg30UAJdTooXgSemYXAyOOFkxFoVqr6Gy\n+KU/hsqb7MIRiYrYSXYqDHBELNMiXTb95BP6PTOHjig3Yn45hoLR3jqdKWqJ\nFMi5QDbKRp0eWNMC1ZePqv9vVgbq8rciyRjqPW+PBRf0lGx24fryMxmT0BZe\nZcEvKZCObj/Yy0cSnhxq0UpX5VUQ5BNPR19yhO16JO/0BXf1ISqEaRyg7ELt\n1o4a/BI+BqeUGhqiSScMH1jgCwGG+BChVRrzGOpuSzU9QgYoBu5WCsAdhOq1\nuHcj\r\n=8AQ7\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0a095ee8548474c06398e89bb41912b7548ad744","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.59","@balena/jellyfish-logger":"^3.0.40","@balena/jellyfish-metrics":"^1.0.305","@balena/jellyfish-environment":"^4.3.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.23","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.4","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.0.6-renovate-internal-patch-0a095ee8548474c06398e89bb41912b7548ad744_1625279793310_0.9950908966117666","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.0.6":{"name":"@balena/jellyfish-core","version":"5.0.6","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.0.6","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6911cdedfc6a85dc1e613c2e170298bb1f6075be","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.0.6.tgz","fileCount":196,"integrity":"sha512-By1sJOpq4drJdUdaJIucGhhPGiFhmiddiuUHeYOyuO6qWbO3OYE2FM5H8+LBuTX8VVwj6hrsP9BEjlqpU3bJNg==","signatures":[{"sig":"MEYCIQCeJVkJTye0DAFo/S5MbB/4x3DOHj1kUdLTcAv6lq7McAIhALkPB+tJExEyhMg/UmUYJ44+Um9bZEDIwMJzJYSA6Ycv","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":729646,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg386HCRA9TVsSAnZWagAAyaoP/j6EwmEtshREatfczOit\nzlQZVVt8uRpOmX3j7EDYtL/Hjcd481Cs/rGRcnEpceVAVw9D4xkUkdNTw9FP\n6Ah9ACfitIs8tJY5m24d3LViJmuSkilX/GaoSl9v4WUe19+8kFg7GF+FfvgX\ngQBgwsERqlHZstuyqYP5YK+Hz3BOOm54xIJEMTvVZxZvlFlHpDhp66szyefk\nLJbThbyvVB0PHjG45wxJqacX765IdnQuWZxbCKhlRsgawNrmH/PpC04+edZK\n8odmP9gByDGw9/HUivosWnGLmL2VXOql/yo0+OONS95tWJbumwkjAwB/0Bbj\nyc7zCir2fuHOvtpgildkhamGaObWrNjSLUriIycyD+KPXtqoxS/+cpJ1YoH2\nxgvHSFPa0Ft6IxuPG3teIrMDsSJNU42Bw9IIgtQnfRkQuuc0WXYqob9IAQzF\nrCvINnmdtqo8Qp0+f7mrpPHrc/AFTph3VNHZEtn8POAL6uKUQy9c+32xqmMR\nDSXk0/kj2VIwxdXKGNZ8GwIJGPox9g1cqDFWDbIRDdK7NzHNsA1a76QcRBN7\nIODCicvuUdfJgiBpTn4lnDxxcRhsz0wiFbTwLS+evqYwb0VEPhFgYl00c84k\n2wq0Um0esLny7awsg2dIGxhWmPNts0DnBHMclRhoNR69lsxVZz+YexxucSBg\nEykX\r\n=xQOm\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6d672655c0b757e50b89641b0f7b8e7369f53831","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.59","@balena/jellyfish-logger":"^3.0.40","@balena/jellyfish-metrics":"^1.0.305","@balena/jellyfish-environment":"^4.3.9"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.23","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.4","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.0.6_1625280135091_0.11021219460411591","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.0.7-renovate-major-external-major-86bbf249dabe5900c0e9c6469f3c71675a57a41e":{"name":"@balena/jellyfish-core","version":"5.0.7-renovate-major-external-major-86bbf249dabe5900c0e9c6469f3c71675a57a41e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.0.7-renovate-major-external-major-86bbf249dabe5900c0e9c6469f3c71675a57a41e","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"17c0109b0dedb717e1aa87977c6ea001e2072684","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.0.7-renovate-major-external-major-86bbf249dabe5900c0e9c6469f3c71675a57a41e.tgz","fileCount":196,"integrity":"sha512-JOxLUbPtL0KH40VQbPzUn99A1ihpNWBIJEJC4HHBZKKcHj1BDamDgpk2keDWkRxQnRRoLX9E2weOk+D13IA7Gw==","signatures":[{"sig":"MEQCIF1Vq+t6VgbkdePyTgiVcd1Yy1uKSGQbN08+x18uC6HfAiAfioEoFCuSRE5mFGWgu391YkchPPxVBTS+lKZ27cmOrg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":729787,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg38+WCRA9TVsSAnZWagAAkwgQAJgdoy//msBd+Sz87oxY\niTNsMdF08cMdhryAVqFtp3slPAaNqpKOJHNVeMQAxopDkJyiN6Cbr0MXDjhM\nQEtwDbgxcPQaLePkbYMsYfSNKr4JHmUqGsdauLDEtMYYapUrYeDULJovDog+\nguF8ux939oxlrHrOQFVQoWYhLqUV0oqD9EvxLrsqJeSa6WrqJw3Ls0rhdF0q\nRD/g18bECUrPohGLGoJOYxTW+nPffg6iSoyNvP2Q+RJne4DntyS8F6EqNwfP\n200+vzyLwFwDQL6u5ybgPgvcB16ChUr9/MckpYPHCLoLPglPg9/MdOglEcfI\nYHKsftvXO0qDMYMUptn2Izz3eFXnlRceDJlomnJodSKUPKPyIPRZ6oYP/Znf\nIi2dvN/3oTjiKxcsTesVSXXPe3BOeOn8QmOjRAB07XjT4VBB0g6uThw4ko84\n2M/oqoY3aw2A7ecHlTLU/hjV0fyuc25syTNQQgBIS33ckaa11RyHTm0hSUMo\nvFOL8b3qw+E3LOih+cXsDG9gYkBa3sAPVHQNVZvXKBczPPKDLTD6ywqH6Z0D\noMih41jKgWwBSaFho78TXPOTrEjGwjDjpXRdTts2jfPQzMTbY/cRqI1Xpqwk\nrXsv6or9oDKmo8Z7D+BnCPR8205FMNYXgPGbO6IzEnJEPC7LKBEDWL3S3yvg\nr8FA\r\n=/mZ4\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"86bbf249dabe5900c0e9c6469f3c71675a57a41e","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.59","@balena/jellyfish-logger":"^3.0.40","@balena/jellyfish-metrics":"^1.0.305","@balena/jellyfish-environment":"^4.3.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.6","deplint":"^1.1.3","ts-jest":"^27.0.3","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.23","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.8.4","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.0.7-renovate-major-external-major-86bbf249dabe5900c0e9c6469f3c71675a57a41e_1625280405251_0.2394183073223768","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.0.7-repo-sync-jellyfish-config-default-67e8e074a3ff143e9492f139bb9e7cad3d757107":{"name":"@balena/jellyfish-core","version":"5.0.7-repo-sync-jellyfish-config-default-67e8e074a3ff143e9492f139bb9e7cad3d757107","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.0.7-repo-sync-jellyfish-config-default-67e8e074a3ff143e9492f139bb9e7cad3d757107","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2de17326b33c08da5e1f6b518997328fc583b79e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.0.7-repo-sync-jellyfish-config-default-67e8e074a3ff143e9492f139bb9e7cad3d757107.tgz","fileCount":196,"integrity":"sha512-9wbtgyGlfizzP+x62DhnMz7u1rryHpvo7om/OyoHN1e2mVLFsuE5y1jFPllZ3azweerQTdGNdV3Al1hmBO4jhw==","signatures":[{"sig":"MEQCIDCxU0OVu8lhjju0pRvu4ANYQ+N9ITb2h27IoyrfJgVIAiAJRFwQRdMwVpP+K1Gw9Nj9v6HRx5VR3sOqp4EecOvbDQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":729817,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg4HmNCRA9TVsSAnZWagAAHfoQAJq2ifxQTKtvBKgooflE\nYFg32+mGfHpVcUDisYwyjk4EBQZVlyMJlH+b2OTtR1C8XbgcG29XH+ALKzQ1\nhD1vtnF5idxRk3ccmZZJwjz5vUvv7jt6rBS2vGqdZnUeKjG6MlTUW9fEiFmL\ny69agjOJTH+iPT+zvFfpkk0bHB/6POrEcg4oxRANKjZsoGmLz5QVxGz8AFEc\n/NuZqoLfHyTSRCPG+kMsW6iKCoEMZgM4rd++MBbGEb1+BIIvbdK9Yh0fizkQ\nzLXJTwmEVYwAUlNCBJAAkhE4DJxepHj/1cvXtnk7P3pgA6/nSOj1sOnkjyWK\nR22awFnnGl96X6pXoXqjmOSe/pibp7fHf9CYRRhJmVI7J36wLKGKOxIJ94zs\noTVPOLN8XrBta9HNEbZhXg0nAHxMVkMChkZP0aPh/UdsHGZ0n6t5Hlshbv8N\nRnt3HFKY5ihxGTh+M3iF0bbria9kEHI7dv3H/9Ik7xx6+Fqm+0CThXvU+lkB\nCq6yeeLcS7/SrI/WjXsP9o6qWDEUC1kUTtvCzb3J5oSS+7G+qHRm9Jv1kIq8\nhyFEbJ1lYJ5gYv92txV3TN475VbcLiX9d/6CbuySNqF3+trKuVG4jm906WKP\n+V1KvWmD/d6Qk4LM4c6QzXSpxCGQLpzjIui4thz+grjv059nZDup3i6oAZOZ\nUwnH\r\n=xJDi\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"67e8e074a3ff143e9492f139bb9e7cad3d757107","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.59","@balena/jellyfish-logger":"^3.0.40","@balena/jellyfish-metrics":"^1.0.305","@balena/jellyfish-environment":"^4.3.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.23","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.4","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.0.7-repo-sync-jellyfish-config-default-67e8e074a3ff143e9492f139bb9e7cad3d757107_1625323916517_0.0009416033432974036","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.0.7":{"name":"@balena/jellyfish-core","version":"5.0.7","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.0.7","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"bd9a1bca9de2ba97188afa5ee0af6a6c4fa99367","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.0.7.tgz","fileCount":196,"integrity":"sha512-/+eMGGqAXt4enQHHkz5EjClkz35gAcGPqm4XU3kmlTyqAOja05hEMhckpkEVXvA9N0mU7ZBmo5TI+xHaDxq5QA==","signatures":[{"sig":"MEYCIQCeslyrMJGgBUiTozczVTwvjPGyOQDatOf2nUy5xwW6TwIhAPqEemqi4f2s4nT6G/NIgCsIjShpHDT7Jdf7hZk8v9IN","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":729741,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg4HrsCRA9TVsSAnZWagAATcwP/00z2s6YIvwcBvw/0Jht\n37ZQC8DvOu+G+z3VVVzshawKzWaiPVIro3PUCxA95bbhwH2a5VhRkjxhRzta\nHs0u4/+uXdQpO0BVa5wX+aT1pijhfZqg1NPwPYmEsaBuS5nARXd1l9zYGIEU\nFB7v1hpsrd922LNnbiV2tqrW4r5X6/Ir93wSlJwGC4nLJvXaEhovK/xDd4ho\nhwT6sdHn+JxLh43kDOWoRI3wAf5qBOSj/WS3CaPgB8/Rou9blRUKahqArgBT\n9d042foyJ7yzCMrqW6pj2VvvpLXre4r+a1x/5DpeMVBDzxTZriSZhhUYS9nt\ncSeKAlkr1DhbzIORl+BeZ8XT0gMeyzl6k6lrCkl35OPZdVamSymaN76ikTPK\n9prVrliquzEAPUaQsz2o6QIvDqB9uLkz+Rn19Z/09HGdv4cv+4RK9foH5526\n2RcuhBl6eKNfuCdHPqM+EoxGgv3+0/hZgm+Ipo5NoVgaaPQUV8zCGxfoSd/5\njj4Va85q91jDSByVb13W7DvUUnUAulOLhL2qCyGKI/sCVqywliSKKmae5yxW\n/259cOPyzsP2lEvdHlMF7z6Hwf+oQcGClq8ZdXcB01R8/4oNYVRfirAU/5tF\n234nidfyofHOi/W0grM3ZRQrC8aR0Eve5HFIvj680TaqjqsrgksccvlD16Gi\nhrCm\r\n=7KGl\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c06e09d821cbaa47392fdd084fe2d4de274e94a3","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.59","@balena/jellyfish-logger":"^3.0.40","@balena/jellyfish-metrics":"^1.0.305","@balena/jellyfish-environment":"^4.3.9"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.23","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.4","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.0.7_1625324268305_0.5265839700679711","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.0.8-renovate-major-external-major-f36562462a6a3438b8d434ab62f76988f22b146a":{"name":"@balena/jellyfish-core","version":"5.0.8-renovate-major-external-major-f36562462a6a3438b8d434ab62f76988f22b146a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.0.8-renovate-major-external-major-f36562462a6a3438b8d434ab62f76988f22b146a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"fafd9804f592eeb5a92bfdc6aa890c2658205969","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.0.8-renovate-major-external-major-f36562462a6a3438b8d434ab62f76988f22b146a.tgz","fileCount":196,"integrity":"sha512-YHyfKDqAAzwaVQGZo6ehn+k3ihaFND4KK2LYffG72BfjpohVBEdoLI+GyFE2U9/5oOIpoVlVt3Ny+wTK5Dv55w==","signatures":[{"sig":"MEUCICHdMWizEOwQQMR59Ws6piOZmib8GQl8AgY114a4Tm/9AiEAvvhh5wQHbmQ8IWUazNdloDkFYnrh1dTkjurgvbuaXmY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":729882,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg4HwvCRA9TVsSAnZWagAA+0gQAJfUfFus1cpW9pPCPU26\nRGUzriYS4AJt2HAkAqGgUFrHkm1OXGupI+Jd8VnD3SysfFhW9qQQ5cqyu9sw\nTHg6xGhKQBPYaQb3s279FKHgs41kVE0fDI1vf4AvBN59gKQ0i65YjM5YIruU\nTQp9vqy9QtPmrfE/VLlmIaBjYrdbsUU/XeBmI18ReVpBVFpnc1AI9MxZuoxV\ntDaL+3NQdDStPQyHOS+gdF4lvOYCZrVPaQ9L/zKDAiA1T5gLNe1S4E4u4BCh\nWhULZuFpwWcC1fOVTjN5+H+G5yRtTXmnZDlTdug83J4l1nGfVI5Qrx+gkyOf\npjrab+4T5dDFYO6RaPeahvPcIrYOF6BNeE/BN38m+h6VMFD1M0I4R1hyjCsH\nj2TFbLmVXfaQ508Yy+hJzdGu8/S0aLb8jC44+tkBAwEd0JLGy3g9h8jsZfRP\nZxuAaOQ68KYeXXxL1xrU1GMuaS+R3Tk7mjvUKpFLyRsoYKN3ahgRgXmZt05O\nHh5WHu9SP5bfw4c/i8Nw00zecBghe3SJbwWp58sDQIm2Qv7eidJYuKqpMeW2\nKimurcOw3Sg8UVqiWSXEt+AEx89zAopQtgLb1jFgIhsST7P3HetISBPtHT80\nr6RZ7ZAH90V0WaKbLprO4vxTup0bd5v7Scrv9/ylMwfh2iLjrZPyRkxJjRxS\n5dzN\r\n=l/5K\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f36562462a6a3438b8d434ab62f76988f22b146a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.59","@balena/jellyfish-logger":"^3.0.40","@balena/jellyfish-metrics":"^1.0.305","@balena/jellyfish-environment":"^4.3.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.6","deplint":"^1.1.3","ts-jest":"^27.0.3","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.23","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.8.4","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.0.8-renovate-major-external-major-f36562462a6a3438b8d434ab62f76988f22b146a_1625324590351_0.8698304210625956","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.0.8-renovate-internal-patch-6a7777ffe66a4b05463610d40aaded7859b1bfe9":{"name":"@balena/jellyfish-core","version":"5.0.8-renovate-internal-patch-6a7777ffe66a4b05463610d40aaded7859b1bfe9","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.0.8-renovate-internal-patch-6a7777ffe66a4b05463610d40aaded7859b1bfe9","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"07b08e62cbd177bfd9f275fc2fd700caec82a9b1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.0.8-renovate-internal-patch-6a7777ffe66a4b05463610d40aaded7859b1bfe9.tgz","fileCount":196,"integrity":"sha512-mQZZmV69ZJZsrPV/DBo62qfyLYfjjxnpmgQw+3bb5H0+ktoDM6zI9fB56ueBqyi/WhB4+x3n38PSwaBA1Y/nfA==","signatures":[{"sig":"MEQCIA8eDOE9g8XFe5YeLD1wXh5GxSU8EFNzq4QBXV7y5lQqAiBtQPRlNIN0Bq3wHJpR1zjFF6e3443oqARHbekDcxy6qQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":729877,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg4nX8CRA9TVsSAnZWagAAnUIP+QFvNlOfN2fWPTUh70gD\nw119xguowhKCOk0Dql2jcBC0Zw+WIaM8cxi9JX1A4n+4G6JUoubohEs6Lw4M\nHRfAZQ5VBYA/DnaMfZnm43L9hPNj1P3BxAzJjBTRzQLgcD+PibkRMCC5Kc/i\nijVVg3aSL5x+PS4iIaOiaHQJ85BvLi7qx7wg1SuItDJmPWRau2JmXIYUe3xB\nSC4I9UHUEByCeZF4fMalQyS8AS55RYC381xoAnOHLB+sEi+mxAJKGBNAervi\n9oFgglPxW6Azc4Flx6HbeOVIlkGptivmUL65mAwOmg0R4IQJNoaWzVAKxQka\nSWJvClj5wQFnyYsgOZlUudKV1fgoL+0slMueurfgZQVXtBapwCAW/YmJuGv3\nITjj+TdbFE04PzR5TKDT5uM6wwMpZM/ntcJR8n8/CTYFV/r+wbmwSG5413vI\n8RYGO9nhNkw+KXns3rL2ClUUdJKPE2QcQwxrVFAyerLcAyO/yThjvZdkJZb+\nLuZQ/oBleU1BZyJGPJWl8IEPEvxXUYnCW0i756wK/YLBmLeudRM6nId3oqtW\nF5aJUKxDk+OabugzFwv41Rkr5JkRwSPWVH5Mf5E95f9zjSQ2SFvHm8ZZHQKJ\n4xJDRJUnrRiaw2CsF16pl7oXEHZGVLUbBQE4WXEwTEbazs0fjqFqlRX1upHT\nHWRd\r\n=lRbX\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6a7777ffe66a4b05463610d40aaded7859b1bfe9","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.60","@balena/jellyfish-logger":"^3.0.42","@balena/jellyfish-metrics":"^1.0.305","@balena/jellyfish-environment":"^4.3.10"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.23","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.5","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.0.8-renovate-internal-patch-6a7777ffe66a4b05463610d40aaded7859b1bfe9_1625454075443_0.7464770841715411","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.0.8":{"name":"@balena/jellyfish-core","version":"5.0.8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.0.8","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d7cf1348c5aaf520a56a6d46bd9ddc6c1054fe26","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.0.8.tgz","fileCount":196,"integrity":"sha512-mtNBvBlnqhh2GdsKe5PsFpSxwYDiJW5Xqnu0d05AhJYpObifNdYvaqKUKhaZbK4QgLWCtvijdHAtAJvOkN4KvQ==","signatures":[{"sig":"MEUCIQD8RW3Imvwpiy844c9NazW2pzSYxtPV/eT2d6JijJnqsgIgdYbENSd+P1QbTxI0Rbq7+vu4+vAWIPArKeGm42Tv6CE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":729812,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg4ndcCRA9TVsSAnZWagAA0bMP/AvmnpNl4IHUoY0kZ7zo\nkBsCyR1XeS5MuXd5SA62zZqH+T5Id2fAQOxcNn+xKMPdubV7iVymyPYCfaLm\n9GGjeJiHP58xBtcYgnZG+OyHaXZQJb+t+OEQWjOpH+bCHobH6LTgj/kydUuD\nRUJvPNQzp5bAAZlpTG2bHvGBtVQCxm0V+M4UKn/LWQM4nfGvl3Q85ZIRZP1n\nbxElar5cd93WUWHi2ffCR/nuVp/+RrNzUcu9p0XAeAkv6RCenTbNja9bUJ3o\n2w8kKHyOYidw7DVE2JL1Fh7We83bXLTNBNcZ5qw/ElVKAc2rCrfXv4Xyqh3B\nGzgrtNg3DrYpLm2qe6fUQ0tk3fSlmxoVOeHcq1aBiQj3Mp6U2TAGCO7qsQTs\nq38d/GoU7TQ5wNMBEJozul23ESwdTrgB8LO6pjVl9fSDeONt3r85FiVT3/pa\n4ux9m5WqsbEjk9bOWbRGRNvoOG3DUk6rVUmn32puQRd2+FGwQ/hNkQMFfGTy\nIe4wweNpkJK/C88ZQrQes3DALSftfG3rvWtqBAiPFHtzCxMsI6klNnXifAV9\nyN6jFtSMb+q3hZDsFtclwD2J2fGiMwaZfeRsIZMj4ZWfu3+piphNbTcUG+dt\nNgu1TovE6iE13wt5ICa383hUXBc97HbrNp5m0skHy5V0zVROCF3+dAbPKNJR\n6CJE\r\n=NVDN\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b1587054d2bc723a8fe38e293c60681ad9955224","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.60","@balena/jellyfish-logger":"^3.0.42","@balena/jellyfish-metrics":"^1.0.305","@balena/jellyfish-environment":"^4.3.10"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.23","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.5","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.0.8_1625454427672_0.04072554381429927","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.0.9-renovate-major-external-major-52489c052acce6f69dc4dd9469e38ef64e643dd6":{"name":"@balena/jellyfish-core","version":"5.0.9-renovate-major-external-major-52489c052acce6f69dc4dd9469e38ef64e643dd6","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.0.9-renovate-major-external-major-52489c052acce6f69dc4dd9469e38ef64e643dd6","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c6b4f0b1d8016f8aae23ab0421d336c66d71638e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.0.9-renovate-major-external-major-52489c052acce6f69dc4dd9469e38ef64e643dd6.tgz","fileCount":196,"integrity":"sha512-jZBBLGfUzisVCDEWaZMKq+6daDOhQsDmLToGEAharuX3LscUJh41r5qhQVHgbtqGzNX8KEOtM+Z3ivbDEfRdPA==","signatures":[{"sig":"MEUCIQCIr5z2FZh8EG1Lrr9YOZm3TJvaQRY6UlcB8KShj/KTTgIgOZi8W08sCzVhQ02A3OCQNhr/RRk64trYaOPgrpyNFLA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":729953,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg4njlCRA9TVsSAnZWagAAMV0P/1tonvZsSNzA6V+Q2fX7\nX4tsusW6om310evTYcObEiY0wTVcTsDr+akTixSyoTulEprg7PnUWq7GvB8/\nXUEUG1v9eegoMSHRhjpDZCGR85Gdk/BLg5pJJ6qaJqxSC/KAoI7cGKU2n5Ir\nA5pySLfAr2NaP69bCzxdJGLIjeX6IIa22AZR05xAVIkMkg3tisFgwiYiomED\nEM2y3YQ0coK7Vnqeq4uh74x29oI7BUNHZxu70AvFLYgGi20akSw0Rs7P4ayC\nXbFzOgvdA4JK0i7Ack5FeSm4QNHG+pVbRZ96vAWnBFiXpjmGB11HP+tqk7fB\nyAY9lg6N4Z27bf0gwdrxojDNqCn+H79qGoyaifskddR2bOc/OcJySt0t0rzh\nDOzXsdXGpJ99XqMmPpVVYVF+AbU+fk+br+QsgW7PL9Caf1PP0+NllaRW7F8Q\nVPfGG+Xuu6riiZect4ozqn3Aww3NQGEvO8Dt+qMMknPo+FwdglNdj32JdwCm\nvdAQHBZ1K871ekaSZU27fTfqjOBx7tI54T7q5cn7g53Hq6/TD0o67l/oqUvG\nKQ/RQ2c8MdkE4X5KIsv1G/RJfEsCbXoByCWH7nc3LXiV265WDBlY2T5wUUcQ\nWruijjYwHz5GH60DZTWtKwYni9Ri2TJzQMB02625pz/rADttauELuyOpAYeR\nEda/\r\n=MH+M\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"52489c052acce6f69dc4dd9469e38ef64e643dd6","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.60","@balena/jellyfish-logger":"^3.0.42","@balena/jellyfish-metrics":"^1.0.305","@balena/jellyfish-environment":"^4.3.10"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.6","deplint":"^1.1.3","ts-jest":"^27.0.3","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.23","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.8.5","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.0.9-renovate-major-external-major-52489c052acce6f69dc4dd9469e38ef64e643dd6_1625454820833_0.5154450888953281","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.0.9-renovate-internal-patch-9574a50ab94e87002f8f96cb84b36b9454226178":{"name":"@balena/jellyfish-core","version":"5.0.9-renovate-internal-patch-9574a50ab94e87002f8f96cb84b36b9454226178","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.0.9-renovate-internal-patch-9574a50ab94e87002f8f96cb84b36b9454226178","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"273becb260ae1ad384e756c4a06e58a02964a2c5","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.0.9-renovate-internal-patch-9574a50ab94e87002f8f96cb84b36b9454226178.tgz","fileCount":196,"integrity":"sha512-jrLWQJhHRvya0n71am/VeH2KsM8agHue/uiC7eBmwcf7VkcPAneqqn6hTIKODQnAOQgQWpp/W9EvGLq4ETiO5g==","signatures":[{"sig":"MEUCIFGScJn/ZVS5T/4C/lbjULRpRqAliZYmLEpc9EyvUCr1AiEAzvTUSvd4Gi4ELGs4GhJ9SqQZs61vWBiCmBHrGM+m+7I=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":729981,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg4pbdCRA9TVsSAnZWagAAnAwP/0dY74x/V6TtFp+/NmXd\nBqsftdU2bORXWealnEK08sl/l7wwIngEzIMaAk37qCSXc+neyldTCiHwK5qk\nhrCiIwR969rmPVr8TisSI+tCu4VwpyKhwpubiSQVCpNqK/0CWdnuYs7uJT0X\nmU+1qFNMaOllnpCTkrLTEtbuYCvH3LWmJSjEnWAvu5+nIjLjzcrQr9qLy6zK\nfCeClsxNlIkdLDtpF/dHR3ZtJW76f3EideZ1z9xGLs089BSN1Spp2JZ65zNH\nhNFOmHGIpRqZbARVHDQQtTa8gOtQH1RL1v+ohv/00epudji8RFxton+pHVb1\nFXSWwQFusTap7q6yA4vHu/ggF/Fg/36ivkzpnCYVENShXqJZIxLOgtsKIONG\nmVtT1+w3KvsRQ4VOOy5P0YGpG2VKldw5J3Nz7v6uh6I/EVTAEruQfW+K59yB\nqa0BMhCKJdbWFkkG/09pxbpQzsG/hqoOi6x/bWgnDT6gGemcVJCLnBYUdeUw\nEHj8603JZFDcRNpPFNt/303ZDG0U1pjA5iCD3K4bMaQ0MEsf/M45mFbgcUmM\n0SjvuN68toXkjY/XkXBklXC+a7TwLPUBwJQ6HYpLCjXZY75nU3+Fl6qUKaOM\nkNvkBbnizjNOll41QRz42gz3Tp+GZPRP4qQzad5b/L0u09dwKSOpXJnwAZo5\n+rlN\r\n=8eHw\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"9574a50ab94e87002f8f96cb84b36b9454226178","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.60","@balena/jellyfish-logger":"^3.0.42","@balena/jellyfish-metrics":"^1.0.306","@balena/jellyfish-environment":"^4.3.10"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.23","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.5","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.0.9-renovate-internal-patch-9574a50ab94e87002f8f96cb84b36b9454226178_1625462492949_0.7735396447112621","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.0.9":{"name":"@balena/jellyfish-core","version":"5.0.9","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.0.9","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0495894aac459c3cc9df04b9f2c86e2701870bc1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.0.9.tgz","fileCount":196,"integrity":"sha512-50pDMcKdkW6NjTi4//T+sdW54j6BrW2Sfp/P3DfU6XIcHzXo8JWricSK6lq8sZFk9hVqMpBwJsR9yq+TDoxZcQ==","signatures":[{"sig":"MEUCIFnkv5cj/VS4Szk26kFHx8YhCFTR0x4k7vg6EPh+dYQgAiEAthRpEeD/GtmGaJOAuXko27VUVjQeW5eW+vBu+4MEU9I=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":729916,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg4pghCRA9TVsSAnZWagAATzcP/38QoJvBK/p4VmuNz9td\nxYdNqbbf9bZUpgnj0ekusHPi/cDQskM33zVzb6in0TA/zvV9nVHefOya/5Oz\nb3SJGt8CWT6EauiQf3dZq9Me0etnRyHjCXjuhbW6FDE35wrXsKHFY0pYr4Z5\nc9zOQ/6GDDHivh6R2LK82JTs1OwhFaPeD12QJLZl6bZBXF7+ipSF1gFxZNj2\nB6QY+6f0Gl/u4vmUOCT9A1SczaR6aVjfVEmLiY7ClChbXCGX7TPoesnGnlrk\n2+A/s6VZl/T9wVoBhD63xcBZbXo1LCV66afCb7dn3irRljDDZpD01u0wQr0u\nTk0j2/inZO/mcN6wmz5d36C0EjU7IaXIDXBqSMNjR6dYHdO/agkZaS5AY72y\nFHAsE3GIKJbImwRDElhbE8Rix63JxaGz3jaETfUmFKqN6CYIKqOzwtvr07Fr\neXioKhX9pi8iN9cY+E55eqkq+QTzMIr6QIY977fSvSZ/4caLkWE8+7kpcHox\nAK/WjFaPJs7pmCg3F1KjC3XCxkFq6jTQFN1tGZa2d7b47GWJEdV63e4+MEb9\nWlW3bd2Mo5helANm19HJca2tN/MQQRw9X3F+hbcdhTKdPLhVcoiZFc/k2o5H\nMouMOzwGM+OwRXxJMN7cB7qRpxxj2zawCF5ycG+TyZIxQBYbVb/2ZVH80qPF\ntiZs\r\n=mPCB\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"86810c7e974602e0ec84c82953259165497a8302","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.60","@balena/jellyfish-logger":"^3.0.42","@balena/jellyfish-metrics":"^1.0.306","@balena/jellyfish-environment":"^4.3.10"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.23","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.5","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.0.9_1625462817428_0.8690753703998997","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.0.10-renovate-major-external-major-e6797bb0296febe49a04229f8de4724027c64b40":{"name":"@balena/jellyfish-core","version":"5.0.10-renovate-major-external-major-e6797bb0296febe49a04229f8de4724027c64b40","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.0.10-renovate-major-external-major-e6797bb0296febe49a04229f8de4724027c64b40","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ce60f72af24744160371071027c47ee4f3a55948","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.0.10-renovate-major-external-major-e6797bb0296febe49a04229f8de4724027c64b40.tgz","fileCount":196,"integrity":"sha512-oRfLbHNsvnPLMIG1nH4DY4P2Wqg8HWjfLWgtndoBfDa1bO5Zq7NxE9auVTI9xlRALiGYysJlZQZ9eo/qvjIUVQ==","signatures":[{"sig":"MEQCIBPNw64kIQFXE2cOqbik1LCfcIKzqlXVl8F6JGSt/UN9AiA6DcsxR1Nz84JodbGDfo45ZVauzvrcV7pArfUuV5xxnQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":730059,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg4pknCRA9TVsSAnZWagAALM8P/j2USH/P4BLFiKfibOJQ\nQIzCIEz+rtc7EHkuy2s+OtMWGEOHaNp0XkW7k3l8cu72V97at2BQtHl/YOKz\ngBmqnOMXjMUOZsDm5RwhKc2QuWTsAPw2WeFZCT4SPuwmLoHhVmB3vy9yf/kl\nGuAYYMeke4UoYsv+fbIJ8OsqorSGSCIhCX0QMV2Zw4VWtgNsKdaGdH8FKQx8\nFhKl+smobaUA3Ep5p+8nmOQae+Nq+Qd8Bs+RzwAg+rizYFA4zqnR0fcotNR1\nS+sFeKdP0RqhqJGFS82ahVlEqprJ7SW29Ks1kZZe2IZ9ONXiu1UHL+jWNoSK\ndVkdTHjlNQ/l1eZs/tZ6UrJ9d62IY5CPvruvh3hDWanCkufKdn2niM75pcr6\nzWR5ugeb09vB4baFtJks1hR9P1oUQA0lZjq3+Gq5I0ioFc95lFU9UcmM1BKn\nMILYQpjRTvygGiYQ87kzOe5dVe2K5iAYOw6gLVOGREpVE7Fe9PEDN1y7u93L\nsbdUvU2fSkwXDWVkEAa7/gIpoHkAHeMaOYJpznTqrQjNVoXs7p6rBYRb1AuM\ndZc65VCuWdfKXAaTn0ABsBQFQc+KEIXIxxH6V3VuuLZRG1S2gtQt7gZTp5Er\n2rY0F7yvO9J0lQDDG1oMwY6gKuGN/FIn52C99meOrhAnV30LVQtCMNkRL4qt\nC87j\r\n=Cwek\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e6797bb0296febe49a04229f8de4724027c64b40","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.60","@balena/jellyfish-logger":"^3.0.42","@balena/jellyfish-metrics":"^1.0.306","@balena/jellyfish-environment":"^4.3.10"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.6","deplint":"^1.1.3","ts-jest":"^27.0.3","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.23","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.8.5","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.0.10-renovate-major-external-major-e6797bb0296febe49a04229f8de4724027c64b40_1625463079483_0.5551669947423326","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.0-failing-query-8e587c49860523ee99f5cd1d0381fa1cc0950b74":{"name":"@balena/jellyfish-core","version":"5.1.0-failing-query-8e587c49860523ee99f5cd1d0381fa1cc0950b74","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.0-failing-query-8e587c49860523ee99f5cd1d0381fa1cc0950b74","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"7bc1a76dfc55bd4483a72eb77e77837b0e1d6b3d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.0-failing-query-8e587c49860523ee99f5cd1d0381fa1cc0950b74.tgz","fileCount":196,"integrity":"sha512-Fb6Z1Gxg/jldo8FCrmvJdpIpkG3m+fPsQnk9kGGHOk+d9R1VZ8H6oS2XK/mO9NdcSL6g8iJIDC2+cGTsrqI4SQ==","signatures":[{"sig":"MEUCIDJlywIRDswh2bmuo1+WVIKmtMQhS7is1tOrUtJt2sLrAiEAsnlmc91v9sDzqxbQsrwDDvxLc4zbg9kvlGfYTvzlTzQ=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":730031,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg4tdXCRA9TVsSAnZWagAA7z4P/0LuKSNVaQv5wz4CICS1\n5xXQwV606FPKSgLn0dxC7Tv1vq9PyXUCtVJ9Xi3n5yp5JyDbW0EYXVl1ylDT\nJvyb3WbFEB3jCM25A6INDsIw5llCAa4C/B7tqMjnN3wGD+4XfDCGpqsy2D/j\n+t6iGi4as1tvaSM1UWLjLmDZy/5BJRYrtaz3wKL8doPcH5R3J9S+2XB1tp2a\nIzAQrb7SaYH0wCVGg1dZ6Bvsu4GUr0DGPIKm6NIhgHNlRePzCWudE01oLSR0\ngFo/Y0YRXPhvswyOj96uuVW5Y0HI5MI5eDIH9jKOUIaZi3DQhWpm9fQGE4Al\ndPOGuVHlTHmeEMQUfdmoWtxIxoouoQv9XbWj8pe4adZRPx1Z3S6VSuUyIFXI\nB/enxRFHXjU87sjlvFTB1szm8Zq8PCJ7mnkpS5rttCIfqzQZUMKgsk/aNBh0\n6WHaUE3R+eEIFyJaPKho7pDsPip0JgsAZtWLURYwjCTmnoPxng8xKKSIyTQK\nvaOEou59QUodLbXycerpXqiYz8JnRAvhCN0P75vqHLdSINQ2jfMnE0QtzidK\nbkuccOzyCl5UDlsXNYBh7GDdKQY+GalVZtVVRmiJz7pRIU2nERLe8N4EHzCe\nINULvtVTKTbXRwE0lTYiPWa6InT/RKEtYg2y1Yqh7tyX6rZ1RAMdgeLPX73x\nA5Xv\r\n=S9OF\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8e587c49860523ee99f5cd1d0381fa1cc0950b74","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.60","@balena/jellyfish-logger":"^3.0.42","@balena/jellyfish-metrics":"^1.0.306","@balena/jellyfish-environment":"^4.3.10"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.23","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.5","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.0-failing-query-8e587c49860523ee99f5cd1d0381fa1cc0950b74_1625478999396_0.784159900449596","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.0.10-renovate-internal-patch-59fde4e49757d007625311a5fd0db8930d9a067c":{"name":"@balena/jellyfish-core","version":"5.0.10-renovate-internal-patch-59fde4e49757d007625311a5fd0db8930d9a067c","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.0.10-renovate-internal-patch-59fde4e49757d007625311a5fd0db8930d9a067c","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"13434dd62268bfa6a1cc338fe7d15dfe3ed633c6","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.0.10-renovate-internal-patch-59fde4e49757d007625311a5fd0db8930d9a067c.tgz","fileCount":196,"integrity":"sha512-V2IopX2lZ5VX52V4rDkPUnIVWEAfxa/skp3NQXiQmOPRVUeBroA6p6fMolUEEXiTh3TbPeA04Y7Kl3OuoiQa6w==","signatures":[{"sig":"MEUCIHht6VYyh7hG/zu0omV/ke94+tFxqpROE7xnMl2ol73vAiEAxRGUVDA9CTs3ituoXx8J33wcnc3tv1rgsCm/yw1w2Ws=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":730156,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg4ywaCRA9TVsSAnZWagAA2noP/2nVQeY1IR2RquhVIdWA\nOaBfMBA9riIbaBhzKY+ILoHo47hI2Yb+boskq5JOUU5K2ooUB5ujiqqD2GxT\n6UNEd1ZoJB+D0jMByOjJrizOE5tMsAUev+qwpTNu1xncIq0NO2DhbRaCZNsK\nth2lcbgmQA/6DyClUVIhEVC7+4p3In2Y79EBkGS/r6OV9hMSktdS0NkhNEdW\n/0r1i1hi7Yi5EBDfghWeeUBh/CBL7WhD09efN7CK+Ehb8QP/yvP2K3UOPgS5\n4zaT9W6n+KD2bW9+J+7u0AOVrzkOTzBNTt0apIdFT+RxZEoGUeFE1jtYwfSG\nZhX/HoOC36jJRizzydZTzlOqJ9YE6LziDCAd1U4qhGBLnQgO9JGZtf1mpLFk\nqGdpEVUI3bZfr4TcFdDKfdtlp0RYsnx0nxpWVt+iMPkylMXz98Tc1uc8Hl5s\n/8S9C93tcYAAZHFUjg0ToP6zW8OE9zWqy3SelnjArXf461KA3crpUXBDymjm\nNwJkMLJ/e3SVCeqEHktkXmEPEB9MIB4HRW2QBnM8t5wXslpGemzm+92wIKwE\n4XSlaHwamnPORqrhxindGrNAs1kXVlFCIufLuaArJUbQ66CUURl7lR6+jK+m\ncCdlVtd4OlSFNsCb5tgD8zflfXC0+ZY3Wj+W+2YYxD6KkT2nkLoR3lXT0hEv\nMzCw\r\n=Gcu9\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"59fde4e49757d007625311a5fd0db8930d9a067c","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-07-05T15:51:44.494Z"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.60","@balena/jellyfish-logger":"^3.0.42","@balena/jellyfish-metrics":"^1.0.307","@balena/jellyfish-environment":"^4.3.10"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.23","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.5","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.0.10-renovate-internal-patch-59fde4e49757d007625311a5fd0db8930d9a067c_1625500697697_0.44856268848550407","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.0.10":{"name":"@balena/jellyfish-core","version":"5.0.10","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.0.10","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2a8d4c80de3ba1b0a33bfdbb8a3287efbd23a144","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.0.10.tgz","fileCount":196,"integrity":"sha512-zqRbNbxl/jHrjdvzbOmgQpHY9F04O3x552e1TD9hvd09+vKJcaLnreUJSesBNG2tbZT8JLI8Ro+UxMU+i71Qig==","signatures":[{"sig":"MEUCIHReSdzkHNqers8tkhFg/iL53vqZUYRyef6DSWd/TBFpAiEAhW7S/5xwJelDJo0y/0lED0CwAAIK2hjdzU4KpZJOUSY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":730091,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg4y0XCRA9TVsSAnZWagAARGcP/3Fy3e1LLfytq3HQ2i8q\nTULllWI2NE/9isM+0HYMNKJQbfje6W9NLVNG/OEf/OSWUhDnl/GjbVoUTNXl\nsK8T6bYIsUDaASIOes6/nvRj2TGkHYE6KaSC2i57CCgBNqNtad9LnJCQYKJR\nitBBIfxMM3VXSNzgpeyFpkKFLzhzQwcSNpZvq2Vb91uwgQd+C54ihRRzzCJw\np6Fw3210dHVtfbEStpJKx2Gk/p7fGYcUHlq5R6kwJ2vLzm5n+CYaiNGuzB6n\nfnYhDLlH7yA8EDekwxmNfkE0XII3HuVAiP+2OB2MNogok5XjENvE1sbn/ajv\ntpZq17Y23dRPVE2Dk/+6PeInqr/IJmGCjriluK/g94ie6yUbF8fipeykaxAY\n7U//gnTkfPJYvj3u6kLRpBDRZtBF/F6E+R9ZnlyCu7K9G3AxdU3RqfduyaHZ\nwQwMY8cAXH0n6lcyPSdaDjV0d/yw7/E7pYO9jp/CQyomfkYFTo0tc4QyC8bB\nTLVl8ImcNzmOjwYnLJYNDkZvuqWcF4slr5YPtbUpzw5fdMSackNQlWQyO57b\nHMX1+edL/STsZM+YCT7KNsrsXBs24Z1LBIel5GX9+dNWwlAAKC0omvvgqEcj\n6oiNT77TBT2jPjikGxEjL2w47pdfirnRUns8Ds3kgGStJUZySZcQQmo4iVdL\nHQIp\r\n=ajO+\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"760f6f3f393925388e6162db15aeff80b832cc79","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-07-05T16:00:24.595Z"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.60","@balena/jellyfish-logger":"^3.0.42","@balena/jellyfish-metrics":"^1.0.307","@balena/jellyfish-environment":"^4.3.10"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.23","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.5","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.0.10_1625500950507_0.7478692345504914","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.0.11-renovate-major-external-major-e1e9bc6f600d9a2324ea2e27976084635ae14be7":{"name":"@balena/jellyfish-core","version":"5.0.11-renovate-major-external-major-e1e9bc6f600d9a2324ea2e27976084635ae14be7","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.0.11-renovate-major-external-major-e1e9bc6f600d9a2324ea2e27976084635ae14be7","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c12cb72b48ddb0ff298ae70c7368efbed729c3d6","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.0.11-renovate-major-external-major-e1e9bc6f600d9a2324ea2e27976084635ae14be7.tgz","fileCount":196,"integrity":"sha512-RIv6o1E8QhrE12i7U1R3UP52m+b4KXpqUZhf4u30LYIg1qIyjNqaNZZrOzbCLSeuMvzbXPATUDSCyAyLd007rA==","signatures":[{"sig":"MEQCIFvA6DuR8sqNqLZpF6nr1Znf46I5Dy7F0X214txQwwEvAiAyFq2YwW15RaJiD5Uw2BdzIemSZZZsk362i66bZKj/ww==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":730233,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg4y8UCRA9TVsSAnZWagAA2m8QAKNSycvGIEl2PHCAEovE\nin8lDPbUl0+fyWDBfZN4/5SKY+rzFMYP0grgBlYrskKQSYYK80c+RudfHUbr\nPbZn2VAR/kFbYGSZFzkm6tknLS12m92qihKGXqxhuC/D0/F+Mjw9yvSmfFcJ\nSIUZwSvPrexdU5rt7EOh++L8oy4/swE4ZNWYnqFn0Ykh/Hethvl6k+nOs8HD\nmS+XGyW1JgKCycltqjy6YiUNrbReZBsfDeTZWAgNcosQH+gfA696TnVjFnGy\n0GcwxvwUoISrsZ3OWGWC89qQJwgMBgKL8toiO4ezJAqyK67qxWTTxpdxkg8e\nZjIeB5BBFDqDn+U9YreMo1sUUOVrZim1IsmbVzZwEhkKV0qzNCzL3t6U2ZGy\n432lehK6jR6bDwqgnG0NpCfmBOQrMO2Eo0mTDuS/58bo++Hw60/5GfXUQpDH\nChWf2RQzOqAaM3dD+ZEHM8f+z+8e6kYLEN/LPRWRtAgY4v1WC1ulJQg4pIz/\nBT3YP/Idd1zLVE2Je3cVLbpPxUjtUQG9paOabG4UjHP59mYuBrmT0x51US0B\nGaKDaA5Ftdfc99jvLId4KXQxRxqCP8n9cRceFvSCj91W+kaFIJP1fOMrMgEz\nMLGtPmfu/EhZzV6fCoaxRksshe+n7BYbZ7j6PUt0Q6zy6F0bdun9M4I9kVIW\nrehh\r\n=A1FF\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e1e9bc6f600d9a2324ea2e27976084635ae14be7","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-07-05T16:07:43.029Z"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.60","@balena/jellyfish-logger":"^3.0.42","@balena/jellyfish-metrics":"^1.0.307","@balena/jellyfish-environment":"^4.3.10"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.6","deplint":"^1.1.3","ts-jest":"^27.0.3","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.23","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.8.5","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.0.11-renovate-major-external-major-e1e9bc6f600d9a2324ea2e27976084635ae14be7_1625501460415_0.9163959589293427","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.0.11-renovate-internal-patch-1e82e70ffcc50ffd95adbbbe05a82ae1a18b782f":{"name":"@balena/jellyfish-core","version":"5.0.11-renovate-internal-patch-1e82e70ffcc50ffd95adbbbe05a82ae1a18b782f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.0.11-renovate-internal-patch-1e82e70ffcc50ffd95adbbbe05a82ae1a18b782f","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3ab339a20d59093c2d22b3e2a4b0665f5b050797","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.0.11-renovate-internal-patch-1e82e70ffcc50ffd95adbbbe05a82ae1a18b782f.tgz","fileCount":196,"integrity":"sha512-YWusvsF5WDvs07pwgOI2n925FGZlzd47X7RBBkLnYpNhlqTuAwD2K0LX66QE3us72AubocMi9j6Z9bcuyN6mQQ==","signatures":[{"sig":"MEUCIF2XC4rt4lHvz+6yQWk8ftQAI1GmaxDTVVCcdcVwuUedAiEA4+Q2rV41ApmlFhCVDTeNUElCK0yKsC2S2VKT/+3bzd8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":730261,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg40LECRA9TVsSAnZWagAA2yYP/1GyrRnuiSBV8HyRPxjT\nK5fKbyLkfjM5zQGw2ob0zKG/MVKWhbX/xjIeJPYpByo8GLgOc4qtQidCvNgT\nkbl0glc8DSRvxX1iJ+W7hfkI/cCd0Vuj9BzbuPMWGM4vlBxAQvw/p5ne8zkJ\nbU9CMo0R9dJPQgxWNAqr0qKuJepCoWBjjyhjs9ytXXmWC+bOZK2F/+PXeNNZ\n5CM1xCmwiWN8gHIoXcKqaQWFsUL84QmExRp3UQTX6uFKLXFhdrGjsQ09WpOg\nYDRSEN4MftoNZgSFxWYGzf7hayDs0NLn+KYmoLKyFIU8KXpJFYU5xbIEe4tv\nLk2Le8XBlR7cpLT4XC5pXQmT+RbFadj6xdS4ikBX6DkOY+fy2EV7bTfa//fX\ngzX5yof68VWcV8ny85MVczqZN9SIqjklhu8TphBkqryXmvMQU8pEBXONOsTv\nBAX275GL08CS1s9Pcpi4zKlpcPhWHv859+ScA6g6w731h56Q9u6X8z+FLqIZ\nT6GRRx3g5YC6uy4j5HgYDCV6GmmzrcBwxwknzGymXMnPppITXCh0nGSt+o84\no2kFOJSU8aNaZaAo/hAGeHCZJ9z84uRa2dZaPWzj8DJTd0jzf5fAHZeoTnPQ\nTHUeSas3nNRZ4P5HRqTzrGWSAbR0ZrwhqAtxaj2bFUW3NQO9c2Fh3VkFIoC0\nZweP\r\n=WXTY\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"1e82e70ffcc50ffd95adbbbe05a82ae1a18b782f","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-07-05T17:32:32.211Z"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.60","@balena/jellyfish-logger":"^3.0.42","@balena/jellyfish-metrics":"^1.0.308","@balena/jellyfish-environment":"^4.3.10"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.23","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.5","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.0.11-renovate-internal-patch-1e82e70ffcc50ffd95adbbbe05a82ae1a18b782f_1625506500116_0.5710602860365208","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.0.11":{"name":"@balena/jellyfish-core","version":"5.0.11","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.0.11","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ee0f1398ae8b6d86fdb5eb194d6b4e680b753f46","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.0.11.tgz","fileCount":196,"integrity":"sha512-ldEufakWXboFsrWtqRN0lSlvGHQH57mPRmYixruLPWPW4JWujXUYc0wXf4Vu0TD/TIy5cJithLnOVAolpz8K5w==","signatures":[{"sig":"MEYCIQDMi5FBcMAMfrv6m/L/AySGk/XNSMTylDO1M7zsnFoj6gIhAMbXZe88V7zF9U4LW0TiBYi+Na0XkGTqUYWCflvcvibH","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":730196,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg40SvCRA9TVsSAnZWagAAl4wP/1f32qO0Pa5sBULUBDx7\nqhl9VMrBUVKbfkGKsp98U06+sgyk+82hc/b8JkW+IzK/OYh37nCAjA5NK+61\nq/oRHeDiirOEr3puDy7BH6eQKVm+WO19YVpzssD9/cz9DBiDUT91LtSBZkUL\nXiQxcz61iSVUvudz0EKWrwkEXoui/r55pCXE/ZfSBEGXD/5/uVSd4PuCCMkF\nazGUUB25hpAsZdF6qi2wkD8DChiwWfuj4gvlQyOnP9i/wSUPr1g9opM7VRID\n7gIku2tYmfzXvttfvDqYQpFairY4kUeWLjMtwbXiWr5jGWZWf5nXNQX9SaFe\nPaSMZL8kKnNGnGrE+Kbj7XjqYwd37/cxpyD78AOOGxfxK0WCxaqyKX2iyweU\nyNdTUG3pDVCKfhbf7xOXfsH/em84YJ1J4M+tTJ+deAr01LE/TY/jSKFaJNcp\nq+9TJYzC3q+OXIWbSACiTa36e3SJLIXnZEPedW9+pGVZbKQvWAM695jyKRKP\nxbwJj9oyfocIR5TeYhPz5fh6EqHbWKK1g23m2k0CDycstTg1hfx6/YjtVKss\nSph7Oc34D6XioSyI0j2b0k0z+0wDv4rVcxfNDEuW/3MQ0Qr5VFLA17039yoS\nlCvj+DJbcum7jGDWbrGiCH3rgTbWKH7QUeclE4ZJPJkZ7RRnei8xmPKC9UVt\nOSae\r\n=oAyx\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f2cf3d2bf0b918ede93e672fab40891d49b329d7","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-07-05T17:41:21.369Z"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.60","@balena/jellyfish-logger":"^3.0.42","@balena/jellyfish-metrics":"^1.0.308","@balena/jellyfish-environment":"^4.3.10"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.23","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.5","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.0.11_1625506990653_0.5979551547456046","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.0.12-renovate-major-external-major-1e55e6854e6074e479d6e4ba25fdaaa33a1b2178":{"name":"@balena/jellyfish-core","version":"5.0.12-renovate-major-external-major-1e55e6854e6074e479d6e4ba25fdaaa33a1b2178","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.0.12-renovate-major-external-major-1e55e6854e6074e479d6e4ba25fdaaa33a1b2178","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"facc9a1b6ed189c553de8444e1a40e6a6f2d0bb8","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.0.12-renovate-major-external-major-1e55e6854e6074e479d6e4ba25fdaaa33a1b2178.tgz","fileCount":196,"integrity":"sha512-xeoz0d83tCOTa0bsjSyYtj2YN6YGVMW3xqPJ6Hm6Kug/POI381D6p68tck0ovYeXGC4X2Vx/81Xl3eU36nKboQ==","signatures":[{"sig":"MEQCIDbILeaxFmB0OB4/CIg2G/EkTvl1j7T2xD7bYUdOivtuAiB/cLvosI/MlOPg6Nt/gyMHU6uQ3+Cf/9lVutki3EbSIA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":730338,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg40VMCRA9TVsSAnZWagAALXwQAKPisXXK1ntARo6gtP3V\nwCmsgf0pYl6Ix/jFvwmfD+N/aprqsmJilGvXkCej2qB7m+oegXSMVFAfDnQB\nQBQ4hgUi2Kmit2vQQGGWdAjsbPVxnQ/lTupzD06OcwpN83eiew/h30wP6tRp\nEcJZTvCXQTjn8MIeLd/fPC7FV7VnkM+8ygWhilC6bwWW5eaeg40Xsyf7lzKR\ndQLCbp7CJAiL3XgtW1IB0l09MtalHwtIWUOTMt+XRIgeG6cL7sYqN+BTliLn\nfIWT4zE/QR2frZpkuYmivYmcYdGwPOGCrXiCvjCwy7WT3cGOzN7ixG4Eh3Tl\nyFU3fJVUd7PXOLrrGSLndDaiEOoOtGlFCdgHEgMgnkBfrbEp8RnRsGUWDUGs\nWtTw8WFm4dkhZgl5uys+px0zAGnwm4Icavoz7Ik5+8No58U35DwtWdHGdD2g\nY/tfTONWTjgyu4d4RmhLxo/E1H7W5kZAJaMzWK2SLc48oD6vBIQpWtchPFLJ\nmCXEfsUQ4KLJaZobbJcVlBo3aSaUdtkCu9FlA75XiESGPI5D1oHySdiR2ofu\nexRR1kFa0l/NWwOxjYgO9OKRWV+k2MYtv8trYh086M+uu0xNryh9ceG6Bur0\n+v/EOCsL6pbzOOM6YiN3O3YaBiGKNeddAHr1L0Ez3+BvYAGQngyxtPCY/DRr\nwrpo\r\n=6cb9\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"1e55e6854e6074e479d6e4ba25fdaaa33a1b2178","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-07-05T17:42:41.482Z"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.60","@balena/jellyfish-logger":"^3.0.42","@balena/jellyfish-metrics":"^1.0.308","@balena/jellyfish-environment":"^4.3.10"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.6","deplint":"^1.1.3","ts-jest":"^27.0.3","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.23","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.8.5","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.0.12-renovate-major-external-major-1e55e6854e6074e479d6e4ba25fdaaa33a1b2178_1625507147929_0.6681587736515324","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.0.12-renovate-internal-patch-9d66a642ab5cfdf1cec889c0b55c480f76b7c7d8":{"name":"@balena/jellyfish-core","version":"5.0.12-renovate-internal-patch-9d66a642ab5cfdf1cec889c0b55c480f76b7c7d8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.0.12-renovate-internal-patch-9d66a642ab5cfdf1cec889c0b55c480f76b7c7d8","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"568f487c727f9e0652eab5d7fa2ae1f872bed25b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.0.12-renovate-internal-patch-9d66a642ab5cfdf1cec889c0b55c480f76b7c7d8.tgz","fileCount":196,"integrity":"sha512-Id28/LpLFJbuCsRenhJ8HONo6sozVV6xu4VUJk4n7rYfmyAbJEAAVeb6Dmvk/0jGWEmiuu7qmMBGcnx+I6rG8A==","signatures":[{"sig":"MEYCIQDhkCnGSkK+fJ8PT6Kp4COmY+uKfQ3YpCsVQnmIl4m1PQIhALsSoRxrhPB/J92XSQYS4eOjk2rQl2uejDdBYQ7brxes","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":730332,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg5P1FCRA9TVsSAnZWagAAnOEQAIy0CQbm66Qczk5iBzWe\n3AkzxwITcMGTWSSPclXcMQwh1mvkkb+bCySqLDB1A0dKTgDnSC2+tMIjRwra\nAgClvPXR9VtUUfZ11ZYrC2dMG+mFYJq9bXJZ+RwdyZTY8kpf9Wx4TfY4YfwZ\nyVaf9nTQQaGcKnDHBo7P0YXON+NetXAXe0ticDyGJf9I6PBunYQhGAr2a0nj\nINYBXxEMi6J6fPRK49XlA2DwhQSf/JYW/aHQbt+O703LFvDyC4XbT+6jDVfY\nHovMqatccB1NarOmmAfyvnXa+IKnVrylTweW17eBgQdNcL9A+2PwGsw9S4Vz\njEIfLa39FUwiCK7DQbaZJF/Pl4kuy4as1/glRNkmpH4GiRRyC2OAaqTxEMf8\npTfaSrWqUfhnVIPGpipYTNqgGBSemXsbepuyAHynRFrCnsmz1MYVLczrILTZ\nUSwYl7eFavIxB/59gF9Xd2HcBP8I0DLm3KDUVnaBIvjUXARKH7fqQlMOf17M\nfPpKaiq7AvQkWQGqzW2l1Um7BOV1oNSUUBgCZnA6tsnu/YnRREmuwJpNzPMj\n4Wu1SlpDFF1Tsg2MIJ6uWS/afRPRKst1Dh5HcBMGx18ncmZClpQwiuO3Vr9B\n5+OmM8dMFIasa2etB2MBusu9mGcs+aCC+RnbRHFfrp2wyAZWgZj27uEN/F6m\nHMsM\r\n=88eh\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"9d66a642ab5cfdf1cec889c0b55c480f76b7c7d8","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-07-07T00:59:14.677Z"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.60","@balena/jellyfish-logger":"^3.0.43","@balena/jellyfish-metrics":"^1.0.309","@balena/jellyfish-environment":"^4.3.11"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.23","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.5","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.0.12-renovate-internal-patch-9d66a642ab5cfdf1cec889c0b55c480f76b7c7d8_1625619780502_0.7427680465763098","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.0-loop-widget-0fa8e42354d9c13be137b2eceb2bb9d149f95dbd":{"name":"@balena/jellyfish-core","version":"5.1.0-loop-widget-0fa8e42354d9c13be137b2eceb2bb9d149f95dbd","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.0-loop-widget-0fa8e42354d9c13be137b2eceb2bb9d149f95dbd","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"cfa106c791f488cbb845fa0767e3687a797344b4","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.0-loop-widget-0fa8e42354d9c13be137b2eceb2bb9d149f95dbd.tgz","fileCount":196,"integrity":"sha512-VkLvKzOJfmPrq7Vbgd9ZhcbTesfXBgKKaDlwwo1pf30QtYTnY+Y17FL/0OTT6JI0yf1GSJAqKqqPkt5OWiLvUA==","signatures":[{"sig":"MEYCIQCTiUSnhC6LYZRqofAV1d8PSe5DpOZERHwIKjGqnlpAHAIhAOvwa1B1+U8Xdg5t4gV/KQNtc+obgHFFyLfBm39Jikzh","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":730574,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg5P63CRA9TVsSAnZWagAARKQP/3Lhktc364SRp7d4Ppgw\njEOtqP4/WdfVbd8zXpqMXhDCPUbV3h8yw2FVSDGG+G/HexzUtYpEI2BsA4ny\nziUREaeb+FSVfLroaxvk51nzC71fMCF/an4+79qmDLxZXg2ocbxWmWk39Ls+\nvFwQEUWUeAa/NQ6tUvOsuzOEPij3euRWBh9oYHDIqNeknzHYRnyiFNmaBKFk\nLX4xt2u6KcABlf3PugpbVVDsposFPifUdrml56xaK9+jjDSuHPS3xZ1dRXUO\nPRd0INVPT5vlrygusKe8pUM9IOgWa5ApJGs9bgSYZQFtKU7Jmmy3ug+Rvubs\ncjBgC2zppqmFa5lBLcHZWC+by7gDyKaQwBIl4JfKR1G3omWfXAtmqPJvgeHo\nbhBkGHzSu87vv5W3VsOYhtMeRFTs1FI/J7n81WFh8d89GIk8pPCtdmnW9D4L\nBbmMpQO8+BWTw5ekgAhozgaiimPF32ZSGYJWqaf/ZhgHzggkd2LQ+FE1Gpxb\nkoUBWsss8E7c1DQFNE9H5b2xtVhAyFAROptipNccnPynJsWh3BKYXXfvW5Rq\nDz5GFUOPyw52fCuVbfVuh9H1rY5rYQLUEyzNVW2MHk7uIVOquMOp2gBFZVQ2\n8aOSlgS3ObI0Tc1kB9BHpJz+6hcl5uOKmQO0tfQ2CTe8AUXuelbim07ISpmT\nWIP2\r\n=+e3G\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0fa8e42354d9c13be137b2eceb2bb9d149f95dbd","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-07-07T01:05:43.196Z"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.60","@balena/jellyfish-logger":"^3.0.42","@balena/jellyfish-metrics":"^1.0.308","@balena/jellyfish-environment":"^4.3.10"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.23","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.5","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.0-loop-widget-0fa8e42354d9c13be137b2eceb2bb9d149f95dbd_1625620150778_0.30801721241244695","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.0.12":{"name":"@balena/jellyfish-core","version":"5.0.12","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.0.12","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"462cc9f670f643171a252e6e40417a64fe12949c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.0.12.tgz","fileCount":196,"integrity":"sha512-ZzleFx/LnB0hvubxqjC6hSxycNSgzu33Poi8UVSZxepfEJ5sVsvBAwzMB9Zj3gmghvuTHWwutjeskgammCLnpw==","signatures":[{"sig":"MEUCIQCye9OQ+HkHoAeb+gQv4JdVMHfF2ZHM8K8IG2gHDjEJsgIgN+9sOyAVlRWaJiNuwsqTLCuZc/gUwR/K7CT/LrmKtDw=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":730267,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg5P7JCRA9TVsSAnZWagAA3AEP/jzoLnzB+UduArrHa3Os\nx34zJH1/mnbvn5to7j8ELqlvlBHXmfLbzGCXLppmEXAH/jlo0Ej7pX109Nvk\nIIkDPc+5Pyq0o2pcfqFcZ/cjaUAiY6atO/HtHlDLnuftE76lPwFLoxUMhWb0\nYkMwkDQMWBsajlSgROrhd7fxwDybM/cO914OJUNXSGsHAEMqMo3HhRQjYBe7\nhwpjKJtbwfkwh3Ua1w7yoB2+PU73DC38zzkQJf+IjeAvdgZkOUGp/MaNqUN5\nlZfJ08IgAL5V1IBqbxjLM9oKa1TWzh+KCZULgTuiG5yaOo5/pwMWpKyghVRX\n12I1tsSn4fZ8EHN2Hc5f5tqoZ1m5gqF0pAUZSQTfogGq5A6j/vTPFZyUfLkz\nYNjY/McxFpAFkYHDvQtd1FoNVWQ28Jv1qPhNgbhh6ce8KhXIcPOcSBdDbQww\n7KEuU0hjlr0rrEXrxdscJ3006Gc4m5pug8VnuiYcHLcMK5AW6oz7A4OMOBby\nb8wfCb0SVhLo8OzpaW9Ad8kiWnfAQFBCZQcK5YqVlmJ6S7naZNf1lY29c4hD\nlxF+OMyyBy2XJPQ7IhV6l+FwBT9NeDGvbZN/7UeC2T1xYHHINYvjC3lvF4+0\nqG4jZJXqkZ3fj+cu/SY+rR6B1BniArR5UvpC6lWIBg1PVOzKIWcut7BmbwOU\nQTJg\r\n=VdCK\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"fdeb3629c4771bde4febed64916fa076a38c53cc","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-07-07T01:07:14.357Z"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.60","@balena/jellyfish-logger":"^3.0.43","@balena/jellyfish-metrics":"^1.0.309","@balena/jellyfish-environment":"^4.3.11"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.23","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.5","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.0.12_1625620168948_0.38992396362900417","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.0.13-renovate-major-external-major-037590442258bb988fdc514b27de4c70c4d21a42":{"name":"@balena/jellyfish-core","version":"5.0.13-renovate-major-external-major-037590442258bb988fdc514b27de4c70c4d21a42","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.0.13-renovate-major-external-major-037590442258bb988fdc514b27de4c70c4d21a42","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c0cde8e637282e19e7a9c2ac2c0f1ef553e9f645","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.0.13-renovate-major-external-major-037590442258bb988fdc514b27de4c70c4d21a42.tgz","fileCount":196,"integrity":"sha512-czsJmzxEH47mFQ27v1TsQaxFNgGQm2WljxJHkdCXXzta81o0HhgSnz6kLAo8uJMsZ71quh40D+dYo4qA2aUJzw==","signatures":[{"sig":"MEYCIQD/MibKwzVoroDxCU2TeGaY57hhdGZN1x/BNU3e815RjAIhAJHlZU77c/gl7A1AWooncVmWrsv/dWmx6Bf11E8vifYX","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":730409,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg5P/UCRA9TVsSAnZWagAAi5AP/1I75u3LpGgTCiuqmyWG\nZbrNTwuy1nGtiNPF7U3Zxcx5BGmk+ikSlwjT+zd2e/X7NH+yZmDp3d4zYi2T\n01Z6C4fVqqIxPimu2hM3NUnrXsSM+8pI4q0katQ/3pjDIxL/e1p43nyWn/uA\n6fq97Xhn1stb3blwqnh2eWkjEMWfUqtVqt+PH/GiBSpwcK3IJaLU4max1JgH\nrQKbqdnAgtxRWWL6CyMqFyGXlJQyjO0tHTizyzokN93RPm28Su/uW9w8C+fZ\no/inmUPnrCZgZ/c38o65ft4uXcP2mTYrzYxHzGsJ3StdxlM56ZjHteyF5U1Q\nYA9tswCrWSR9I2IUF9M1IKVGuUrl5umxRIG7AQYjPl5nuMsmB3BEbA3nwEGj\n1F29nfbMb5+lHCOumePp4wwD9bXxpE23/MlDy3IZ1SImHThjj9M407EcAFT7\nnoQt4yxcG4rPkMubCm+38Hp81IcnBERo4CabV2zQC/dwsWXYoahhRZFSdVQt\nusae+N9J8U4X909eDrGWIhmVTKwGWDDMW9nPT48zjGT5HqjvD5q9l7/FRK4f\nQmrtyBqx0+s8oxPwUZPW0pT2gBdgbLF6JUmdNVziqRrJfKjVqhplgFZgr3aD\nnHpc0USYii2C/4WkP8O7ktrC98apP21wGp1rwMKvM0TXMDsGlJ8+585RZk8K\nxqMc\r\n=WDjj\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"037590442258bb988fdc514b27de4c70c4d21a42","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-07-07T01:10:12.358Z"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.60","@balena/jellyfish-logger":"^3.0.43","@balena/jellyfish-metrics":"^1.0.309","@balena/jellyfish-environment":"^4.3.11"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.6","deplint":"^1.1.3","ts-jest":"^27.0.3","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.23","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.8.5","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.0.13-renovate-major-external-major-037590442258bb988fdc514b27de4c70c4d21a42_1625620436226_0.7828155756854791","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.0-loop-widget-6ec113521b9dafcf5284f7f4717ada79533ec6bd":{"name":"@balena/jellyfish-core","version":"5.1.0-loop-widget-6ec113521b9dafcf5284f7f4717ada79533ec6bd","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.0-loop-widget-6ec113521b9dafcf5284f7f4717ada79533ec6bd","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d2f97d792e044390f0f4e9cf2d900f2fc36f03f5","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.0-loop-widget-6ec113521b9dafcf5284f7f4717ada79533ec6bd.tgz","fileCount":196,"integrity":"sha512-lRIEL6Y1CeeEgxC6MgskqhDoJ8L64wHz/GUhJc6tHmJ9Sc3yM7RPdtKlhatu650DlqQ+hbnjuFYcsFAIwpPEEg==","signatures":[{"sig":"MEQCICcAz7jmtkRxwZuczfxIbwG14UNuHYmCqQUYn9LzZWXQAiAVNqiKvClQbYeYfXuIPBWUxSr6RsKJZ0A59ALigRIZmg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":730645,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg5QG5CRA9TVsSAnZWagAAUs4QAJsaeyLWKhagFceGY/GB\n2o08Z7davJL9tGHMEozV5kHkiGxMwYBK1sMPaLKysLicHM+2H46ScTr1chTF\n9GSmXFg/1o6uT+nCQaesQOYafLCjKcPqzfrMAQhm+SV5tvdYuqzS4I1X6aU2\n0LW1DDRjZV3WrECoeseFHlw8FJGvJyyMIELtXSS+VAMobv+rqjWd35252jmW\nNEdfe8kKv7pVJ03qBcuX/roq0MJGP2gdGxjIcYnRCg0Dh0XKkEFC/2/nvx27\nPE6GZq4mpOTYOReCojgac7GJvucXC0d6z9MRdDvbzMUm1S/EupRqSP83Pvt/\niiap6+nlSdbHtlU7umwV75HxQAYge8X/AIezjTDa406X5ZZ0QSQcWgL86omo\nwx6jEtrvh6BhzeLxu5ApOLa9RjvjzVn0iFxvWlUjw2pxEM1jed8ing+51p+5\nQddePNoxcEgb+cZ/9ECdPSeI7s9TOI+7i47vw0egn39sAE6KlN0HKpdkiped\nnhuZc5+XSqJp+AEu+e1hTn/MlzgFIqugE0+xn3Byjux+lI67deoeGvCMW36t\n9dcukhsnf46bFCJhZ2Ad4C3XzGRO63cQ1oNCBc8+39Nf1GyEL4mTpGLN4BQO\nLK+aA2TjtXUDMQIw575QcSVpyiUYI+YnekKBXtk9IePHhzlbobfmjQr+klcb\nK8EH\r\n=Vb+C\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6ec113521b9dafcf5284f7f4717ada79533ec6bd","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-07-07T01:15:53.142Z"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.60","@balena/jellyfish-logger":"^3.0.43","@balena/jellyfish-metrics":"^1.0.309","@balena/jellyfish-environment":"^4.3.11"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.23","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.5","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.0-loop-widget-6ec113521b9dafcf5284f7f4717ada79533ec6bd_1625620921151_0.8844451624812852","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.0":{"name":"@balena/jellyfish-core","version":"5.1.0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b7ecbf51e8cb3e636edf5abd4293f42a69e76526","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.0.tgz","fileCount":196,"integrity":"sha512-F8Lxsq718wNDra383v/guK3UxUs82xt2aTOOLCesXRKECa6WS0e0O5H23ERJW5uwZmUHBxiEeHRMEwXROlpayA==","signatures":[{"sig":"MEQCIEFC5khUJwlkhdVMY3BMT+x7RRa+XqN/lR56orSLNl8aAiAETEcUVSakv7lDQ9q5Od1f039Pki9+zfV+O92RgIrYJw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":730592,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg5QMNCRA9TVsSAnZWagAAhtsP/Avu/CD2ZJg2VBTKD4zA\nubyQwGoAXw9rMvJuSbFVMhy1iR6CRWjBkA9uA8l8tLFFSXGAmNpCxYa+XzYY\nWyGVvOayv5wQcb8XSJvW4yC2m9fa7e9hNsYVG80G+k75WCgrQFz2UDtKZCQc\nRRnUKOzrWZgtEKWrTxiatRkKXYdiWVnAr4VSQnAR2KZrZ9NHQyaHLw4JVG8u\nEg9MMDLu6HgTcm+p6bO0TT75QPiZZm9bt3UQEZppGTZOoYOicIE10IeY8zgF\nUSULA3d0JQipRhUFcKwEH6/shaap0yvhVREwHZoNitKh+XksfogL2gHYsFTK\n+jfP7K80bbt1MthzohlJlQCd4zwNn+W6Hybq0iHJ9LsmR+5KWN2tfETnFrqT\nJwusHuf5tJ8H2LkYqDa0SkPBuBYk7W6+RbUZvEroSgMIHKsBYlBDtcE5+gsL\nsil0JqfhiDck+mWjsKQyhROJRRs/mRrha0I9FzE4suIDYfaLIZNmOOmjv3Ds\nR8MfxbmtZVgEDrNSZG64wAxlkzElq3rxvK6vPAhxf773U1qAf2AFqPZ+dfSN\nwtGPjLUdAjIRy66ktWJN2VdcgFZZ+NJgbfQhvjK+2HGbzufHwdq0lpvQQy7G\nHrt3ucUWRN4Fo80nA+kqIP5iZGl7u6qe625GCxM0SW7KfR2bcwvyuBZ4bVFa\nyJdO\r\n=leKK\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ed8be14541ebd3948a6f366f894d16f45bb66423","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-07-07T01:23:57.868Z"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.60","@balena/jellyfish-logger":"^3.0.43","@balena/jellyfish-metrics":"^1.0.309","@balena/jellyfish-environment":"^4.3.11"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.23","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.5","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.0_1625621261095_0.40451924298069963","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.1-renovate-major-external-major-2bdf293c5fdc812ea01780408b565cb19dce211d":{"name":"@balena/jellyfish-core","version":"5.1.1-renovate-major-external-major-2bdf293c5fdc812ea01780408b565cb19dce211d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.1-renovate-major-external-major-2bdf293c5fdc812ea01780408b565cb19dce211d","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"315890532a89679a1bd402b0a2c90eb161542d6a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.1-renovate-major-external-major-2bdf293c5fdc812ea01780408b565cb19dce211d.tgz","fileCount":196,"integrity":"sha512-gajSDhDFfqSd188CkKPruO2IDxCIQ7gTvtnywcCfXg3lj53VqJjw9q0KcxCwMLkFb0H0pQie2EuSMH3O6E2dUA==","signatures":[{"sig":"MEUCIQCRzP/rWXrxNJt/vQ/PZW5eHBdU3gU2ImwEGzhJCfMJRwIgM+IGj+LhKCoGQ4xNYRCCizb5mc+S9xF/dNAsr6yOdYc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":730733,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg5QScCRA9TVsSAnZWagAAHjgP/jCaQn3bIyXs11LXAc4R\n9ubW8RrrEToYkS2Q6we7OWneYgqrg3m++Opng0N7CZL1CU6eaV5Jy+XcyFvY\n+MKLkSQdsSJHfBXpQ4pwMk0NIL+lKNP/8NgtDuSUuW554esDpK63j+v57lbN\nwKmke8H8e10za5xzsWON4on7pv4QLRDPOqXfErptes4hd+PYopQVK+4zqOLe\neF2MiN9CkxaVvOfRFk5eqM9vMnMIeYB8ayaRUzzGYErS/PommQ20qwxqY5ao\nu//mr1J8z8jtk0PD7ZPi08zQVkDlZTFnZlyzIl5jijFhgj1oGUIlSX0ZWzW8\na/9rsgCNSeooNSAtkIkiLpsaVRB/FKgGinMo5U94LS27NzNig+q5pKIcGeIj\nIkDQYEYrMEJLTNw6WhQSvehraSWP+x9OzisQt6F0Af6w8vXZ5KaH+v2qCfMB\nH/lcDNnTBC2BtzcZgdGadnBHjR3zB0DHiBzRQPMBBaQR5k5lsPh6t00/XRNB\nSEnWdY2VKj56Ly+KVH6yITzLE9tBJvLvq/udce0l5k1VhHozXMHSkv+/VR6R\nRhlzLXkEr5DuKRb5iUOKVoJOqb7LComHBHTVoYYtj84OA4y36w7qz9G0blqf\nvsWuDvNs0rVr2kdkwRpxOXooPDgTliB51G/pahOUIyRlTPPhPMssvlrPvX3Q\nbymc\r\n=aVU1\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2bdf293c5fdc812ea01780408b565cb19dce211d","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-07-07T01:29:08.745Z"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.60","@balena/jellyfish-logger":"^3.0.43","@balena/jellyfish-metrics":"^1.0.309","@balena/jellyfish-environment":"^4.3.11"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.6","deplint":"^1.1.3","ts-jest":"^27.0.3","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.23","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.8.5","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.1-renovate-major-external-major-2bdf293c5fdc812ea01780408b565cb19dce211d_1625621659480_0.023599182008894326","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.2.0-h32-safe-migrations-694c2a05c645f3d5631a5b6333ebfa34031836bb":{"name":"@balena/jellyfish-core","version":"5.2.0-h32-safe-migrations-694c2a05c645f3d5631a5b6333ebfa34031836bb","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.2.0-h32-safe-migrations-694c2a05c645f3d5631a5b6333ebfa34031836bb","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0b80a443f86527defd328ac97307f9597ca50b08","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.2.0-h32-safe-migrations-694c2a05c645f3d5631a5b6333ebfa34031836bb.tgz","fileCount":4,"integrity":"sha512-+2fQ3eki9EfG2i9RfYQddh63HPgLIabPeWldXOti6psmC4fWfn4x7T9PwQuUo55zxv4gkisrYJD9VxmREi0TMQ==","signatures":[{"sig":"MEUCIBrsuAvOtB47KpwkCwPpdTtFOnAxODZFIzL9ht75keo+AiEAmwQiLoHkei/xX1r5H/h3QXm3H68Ir12VaR+Lr/Q8Byc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":88010,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg5dzHCRA9TVsSAnZWagAAJIEP/1pQOnhxG6v7rruj7FkE\n8gm8fKrMq1h6MdcMT1vpXU2s3G4OD2hZIq4B5xjuQNPDE7PtM5wMSYcbr5PN\nKjFsYQdl26pn/acaNuRteZ7F0soxlaFAKhFkDdn6NfpxquBOv93YL+4EI1aU\nnvw/kIxq7J3o6gORyL4aNnTDMtU2LIoE4Z89TIa/R+3YBacZzG+XgoC4sukK\nwWkaEr7eKaZgZnD1kIpSdVXv61IqwsoSb6bM7HTM98Q38BnYUfkK4kUs1Ufq\nuyihVj8liJcxuaUujv7jSR+/wvVr256GRF15/LCndXtrgaApr5AK1W1yFsqw\nBrSU8rek3adQAX2L2Pg/CWEJbhVHNDUuss0XV7IrWL/IoPQNtJdPU0gq+kle\nhXZYjRPI4SnrV8RakyJ8GO25JvxDArDkfsVUNl+afNM6rpT3z58xeXj5t0j+\nAnlZyiYhhZkVLltwhAdlqeCpgnpxkVKxA58zAsptbLzf3vt6T7o10aoEI85/\nmLFNd4TYRUQEMoVIMe3YPxh9TCdqBEHoBAi7M/C4Ym68LU1v7pqpznQhueCA\n9+6IKYEb3ptTG4TMSSxO1xiQ+x2LYyGdzrL+kLGM8xxM+u72gAYrillRUxX/\ncU3ldcRTjksjc5LxQnzg/UHQTacU2cMiP6wqW17m1zAHSp3gvYbWutRfsPZw\nsjB4\r\n=0MF+\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"694c2a05c645f3d5631a5b6333ebfa34031836bb","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-07-07T16:49:51.310Z"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.60","@balena/jellyfish-logger":"^3.0.43","@balena/jellyfish-metrics":"^1.0.309","@balena/jellyfish-environment":"^4.3.11"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.23","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/semver":"^7.3.7","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.5","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.2.0-h32-safe-migrations-694c2a05c645f3d5631a5b6333ebfa34031836bb_1625676999063_0.739660104996603","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.1-renovate-internal-patch-76d347d04510c3e9856065bbfdb6486bd1b1249f":{"name":"@balena/jellyfish-core","version":"5.1.1-renovate-internal-patch-76d347d04510c3e9856065bbfdb6486bd1b1249f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.1-renovate-internal-patch-76d347d04510c3e9856065bbfdb6486bd1b1249f","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a2c4fb28f0a9d397122a64c617501e5ab53470af","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.1-renovate-internal-patch-76d347d04510c3e9856065bbfdb6486bd1b1249f.tgz","fileCount":196,"integrity":"sha512-6N9/sSPF6P2lPcjt7cROUUQx/voqB4MISfc9CyU5m4xyHg0S6t1tmaLlmv+ibQOz0UD8Cs05+6BO1XswSbMldA==","signatures":[{"sig":"MEYCIQCcTFfAnb9RB7nIPpXNZ+556jS9E0e8rQ0nmPpLJ6diSwIhAOMqaGaWz601MMezQyna/FbR3chb9KSyRzxF2/C+5YtK","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":730761,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg5jmiCRA9TVsSAnZWagAAA0oP/Aj7RHnXKQKlIhOr7UXm\n9Iq8DFh4Gl9CpH9/JmaO8ClnK1YPtmbfhDY3CuP0spa3Psk4WKkBcghgRNNU\n9plD1NO0Qm9tNEYxuYi8mwggU13fprmy/0NBJk44RQHL+W867M1YSaTLW7hD\nAA/s+Snbd1d7BX0AhkQkcM3KmOyw6dFRZprjLUXKfxQ9cBC3p/JApeN1l3AC\nu+Uvp/YB5msJZpSZ4xg13BNux8H54swrdWK5HFUJm/sMT4ROimQEMtWQATzY\nnAwG+DXfgMWDMm51y8V0ZaL/Ewb6ibQcN6drwuV5WC0jfWSo55pIDtaDtJH5\nC3y4sqsu10K7r8ZzDAbs5VlC5CrO9quBJYjZj8StiumWaFrVggBI4+YdK0LK\nsEMfYXS4/VwuwhjOU7JJ/ueoX2egx7reMievKpPLit2BZCwlovN6gruVgywj\nld9WAqpAHAEngwRSvJ4FAAMlzGc8+YQV2qWAgkLKEfoLe6E3JRDutlSASnFE\ntQptjbBnfDQtomnt2wmhoIlRr7B9dCDB8JJpSuRLN1fdJtFj6qeVo+b6FjiC\nK9pbmFMM68YnGjbJyJxQoIYpNbIVt3Is9Le/PvZpxIRiidjcrD9r3vYq/d/s\ntXClpC5+NOUlIzBnkcRINyYJ62LLQN4dClU0CTKActFsKaZhyCoccGPjnkiz\naC4r\r\n=6mhN\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"76d347d04510c3e9856065bbfdb6486bd1b1249f","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-07-07T23:29:41.984Z"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.60","@balena/jellyfish-logger":"^3.0.43","@balena/jellyfish-metrics":"^1.0.310","@balena/jellyfish-environment":"^4.3.11"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.23","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.5","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.1-renovate-internal-patch-76d347d04510c3e9856065bbfdb6486bd1b1249f_1625700769902_0.8828167266336377","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.1":{"name":"@balena/jellyfish-core","version":"5.1.1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"98c9542f8d872b1074ba9ba9b046066a33f5f639","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.1.tgz","fileCount":196,"integrity":"sha512-5Qk7OoELjSYoiSx/pfRLV6JbOmZ1gu1qSJ1akc2A0XbQOp8AlSZqhQq7mlyv2TQg7j6t8iEWN4Tdr395/nJiqQ==","signatures":[{"sig":"MEUCIHyUn2zUiUOEu23qkSkAzDJTSe0KCpE7vkEp8FD+2skbAiEAoZCVMWoO/Bx/GkvAHSJdkA72jtifFC/TZ5QX4b6rSJA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":730696,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg5jsUCRA9TVsSAnZWagAAa04P/iRDGH/+YZqNORXR/KXR\nqLlSBoTKPcqIgAaG/ms2iRX64H5dJjHRTddIssoPAcnMZu2bPEMBAPM4qWro\nBQZms4B+q3FqO75NG79J0H3qkRZX+OFEVzLDqvJAPvgBL/z/1PndPNvNre5t\n9kMnXQtPmgNaR/zOGeC8qu1aVAWm8dXqdRrFaSwisbikGmuTdxk9mHqeNR/9\nN9bONpZ1dI2+0Am1c/jSgxg/38Y8/NiMV6f7OzGNHVHE1r+OmVrB7ljNyQQd\nViistpm31lwpLXbeBS8DX6oKuARQJ5cXoADMtDctFaFWR+HFs9SbhfYu+6Zd\nshccLSjXu98owUpqiq/qedsSG//d8G5J6fganyohH+wrdRuULBi/mxeqTbpW\n4tRZ9f+LOie2SA/XUVdGYrLtDP2Q+bCRZu2JCelvd7Ww2CBiZ2rh0djFasXC\nG2Q9+h44o9INFj6MiS1ERlPo0N2M9/7dlfRxBMM1Ajw5kTONdYp4jHzJPKVH\nSViw3rTWrQPCP0Ehy+kPg6kgjxD5JHXI3BWhL2fUuvsCPBL1OimAUtk/KVq7\nt0PBQqEn5m9b9YTcY5MFTEiQJ+/LkXivWsRnhu1ncGiNnQ7kxl9sSR1dWCWZ\n0d7ntpoL/RTx68oGRp/sdeKV229q/G1j6H/jqrX7VEbyAFQDnD6jY/yqE5fO\nCLc6\r\n=PgbE\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"239ee8867c4389ae90615641aae1e98f02e46218","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-07-07T23:37:12.010Z"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.60","@balena/jellyfish-logger":"^3.0.43","@balena/jellyfish-metrics":"^1.0.310","@balena/jellyfish-environment":"^4.3.11"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.23","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.5","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.1_1625701140128_0.4416438189642775","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.2-renovate-major-external-major-b1f972433c3e75f6beb7729b2110a93d890e4367":{"name":"@balena/jellyfish-core","version":"5.1.2-renovate-major-external-major-b1f972433c3e75f6beb7729b2110a93d890e4367","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.2-renovate-major-external-major-b1f972433c3e75f6beb7729b2110a93d890e4367","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"df38c123166bef19d3f01a1e626eb689c0cda3a9","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.2-renovate-major-external-major-b1f972433c3e75f6beb7729b2110a93d890e4367.tgz","fileCount":196,"integrity":"sha512-necRPVOdfLdGIbymNo5a8N8iwXc8XxIwpTd/gbyjhmcy08sI1bkLbEUq7HvqqkJ44/69K4ftnOk5xTpoR5/TBw==","signatures":[{"sig":"MEUCIQDlF4Xr0cpOynCYtY3gynMKEhFmLm0dCgZUAxSMtf9JfgIgGASzH3/4sjJ5sRQjfscjRC/GBBnCwgrp4EN1YtNDPYI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":730837,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg5jvrCRA9TVsSAnZWagAAAKAP/0irEzWogD6KEhu25zBL\nR67DgEdkniKgSNPf3l7g5Pp5SXBzPaZxyxW0BPN+Y2UC7w++FPeFR9HCYZ7w\nOw2nV2FjSGR+l9bLl/N+akTiQgVEJMEt4RWZPJBE7tPTT6pbd3v5kag+0ZSc\nQhzPdsv7FwJJJK1WydiQUb2dsYpyiraABmSPvJIf70GIDwebKxzWa0fmrvIo\nAGEGyjm5V813A3zz8Ew9yxSkYR+MuubGxTMq3OoQEQFKeM8e4KVDtCtrMYn0\noYw9Mz0crs6Ucie55i00f4lHcKSKbyoS2gdwvfC5VCuhBaCkYRr0njoVxBdI\ncAXZO2LmYFMW3RnCJYmY7h9HUJvERvYpPViZVBNG/ERR5wmxsq7cXV2Saox2\ny843EYBxPLbmmXEuLIl/waQzNKpPfS25n0w3puju+LT/l9aunM6BoDjk27Os\nS37F4rkXlyfXjOfamMFi/3pj23H/cOjjZM4YYdmnAjNhiBZh18facyQi6+ga\nPCtj7B7oEw5z/s3wE0axa1TvzDeEhwvWPFlNOljxzRM8bCixVhp+Acr3aaPS\ngrzDvaydGNOo5hoHrDklFrypn8I/eXjBkOF5Bf4ndAFRzu7wVnFJmjybwd5u\nWM+ALIM2wwtGTPvdgTFXXIdZv0ER4e2BRoqWcAvrB+DdSDsoXIJowjoEHOf/\nni5Z\r\n=y1ac\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b1f972433c3e75f6beb7729b2110a93d890e4367","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-07-07T23:39:53.493Z"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.60","@balena/jellyfish-logger":"^3.0.43","@balena/jellyfish-metrics":"^1.0.310","@balena/jellyfish-environment":"^4.3.11"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.6","deplint":"^1.1.3","ts-jest":"^27.0.3","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.23","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.8.5","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.2-renovate-major-external-major-b1f972433c3e75f6beb7729b2110a93d890e4367_1625701354544_0.5041397909761796","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.2.0-h32-safe-migrations-0cc23bbf5baf1cae4b817198a7cbf85ba08e0af8":{"name":"@balena/jellyfish-core","version":"5.2.0-h32-safe-migrations-0cc23bbf5baf1cae4b817198a7cbf85ba08e0af8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.2.0-h32-safe-migrations-0cc23bbf5baf1cae4b817198a7cbf85ba08e0af8","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0525257760de2dfb329a544a3211e49ec1792a69","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.2.0-h32-safe-migrations-0cc23bbf5baf1cae4b817198a7cbf85ba08e0af8.tgz","fileCount":4,"integrity":"sha512-b/ytJ84t9Wg8og95ZsnMBl2Lfge2zTuNExGwu6rIRzTAZV6fgfD43SgIYm9GyG+bKCCDab2/XqOLPEgjbhJaVQ==","signatures":[{"sig":"MEUCIQCXt56jVL50f1YzeuaDih6UbNQ/+JPslhlRhNXYInoQ+wIgYDJgQ6gD85Ewe92VGlxAKeoXQdrMbnIt1WozLtieImI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":88114,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg5lxtCRA9TVsSAnZWagAAUj0P/jHfgWuaco+7iO3E7dhq\ngzI8TdDpG0REh40pDwytVUQNXTe6+/cYqA5Biu4wMG1pzL5uFTGAnqalyFh7\nS5392bV9m0tkCQ9usv5ZvE1F5HsydAHEEFJcyw0SJwitUbfltbnUypdVC0o7\nYMF/gsosdOCNfEX4PnO8girz39nmSTp84ziejah+lJKgbwyQh86iGy7Au6xM\niyn3XEaC8aMdy5ByDKHFSAFedBtsWHwb8x2HxwngsnLq7eQEitOLnLzI8eCx\n1F8EEsBrQq01gEHkbPja9dzUXraiGtmhgEeNyTon68eZIc6T4v89kMJC8YpR\nmUKVB6UxEO4oSmhipjbFD5CTulnKrT0mWFSqzqtf2OAllWVCZwMwtyMI6baD\nY+0F/Sx6KzCfxddQ6OfOG2hEIbgbmjFQweoPNfrXABsSDOonvSYo1bbaC/7P\nd3QzAdOtVSCTXhDME5cmpjmvZOw5gv9PuS5yuj2j+TU6GMptbQEUNp1wQ53U\nH1LcTI42s7pQZOThdyppZUDJ0H1PbJeR5cH7ySDjNEtvF/aaOV7XrHJ4m9Ib\nrfdGsJyIGgSjgEySEvAGQ6NcQQ2nzKCv3ggUz4cJCcTg2zqprTwCpQaFyhPG\nt7nCGtsstk32J/lUF6GSyQxNAzqiXENHU9tKPwT47xn37N87KUbN0DUzsInU\nQdfA\r\n=MS8x\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0cc23bbf5baf1cae4b817198a7cbf85ba08e0af8","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-07-08T01:56:51.571Z"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.60","@balena/jellyfish-logger":"^3.0.43","@balena/jellyfish-metrics":"^1.0.310","@balena/jellyfish-environment":"^4.3.11"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.23","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/semver":"^7.3.7","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.5","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.2.0-h32-safe-migrations-0cc23bbf5baf1cae4b817198a7cbf85ba08e0af8_1625709676634_0.7595687995591167","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.2.0-h32-safe-migrations-96ec945323e39670438469a654e6a0734133b224":{"name":"@balena/jellyfish-core","version":"5.2.0-h32-safe-migrations-96ec945323e39670438469a654e6a0734133b224","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.2.0-h32-safe-migrations-96ec945323e39670438469a654e6a0734133b224","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ff970a7361eed38fb0d1cae045616e24388c1207","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.2.0-h32-safe-migrations-96ec945323e39670438469a654e6a0734133b224.tgz","fileCount":4,"integrity":"sha512-CXGHFM821GoZ/JPtJCB2HzupmEmmElmkF3zMStQ4bLwyh6dAMPTx5ez/9G06L3mxrP7cyfQDswe+uTnSpTVp+g==","signatures":[{"sig":"MEUCIQCZG3RcXPnVpo4uWi1X5xkM6ZLWJdlfksLeyEBl2IrUEQIgC9jaQZea0JAAJQRPBsV5CLp8dKrf5h1qMY7kptCK55A=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":88114,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg5qkYCRA9TVsSAnZWagAAjUIP/AtRFBaNwdR/7Z4DbqUe\nZTRSweIkKNY6r47pHj9I0BgEkbSfwEGSwO8Z4cxpnj3iGh9pay+7MmygeRlF\nB3eF/81I9FaAUEZ/9sItSgxH/Og7WFctrltFiUEB/nHR9EufQnaT95zdpBlo\nHgumAXA0razOc23PiabSpw/+hKSCUQcHY2PrVmnCBsX5FKDBChsGF948BkZn\nRZKNqFVTLD9TU1XYXlnostZ4+iAFOcAOG1CtAp9lvrMkAIWjExpSBO/DvBGN\neLz0oq7w+syY0Ju6ClYjVu1TjpU3xAorlqOCZOasZQ4TXJv0AGOcrVKQhcMl\nT9MuSV2uW3sVfuDstA9y7QuCjnWJQY3RBr0DQHOmoBsSfEQaebHSGPLwGjA1\nv11jDXuLSJCHo60L8fmaGMAXMrBl8DpCXwRcUI9KhiMazjFNV8H1ZOlw52Uq\n79ADbK7dzA64+A7SuG+1bJ0wRK0IaQZewpFsRLu1Pw8x68cvzy5lypjSxzml\n1WRod0YzMF5wg8/hJhUO2jept4X5BUzH7CVsU4gJ5W/fkciFypAeUPBsKyuw\n5kZtu7HuiCc5DyBgVMywlZRgNA1vHKqCWHe0sCRGXDSTiAaCiTT3M04t8ng/\n7FFFZpRdiJ+5WSzjqAj+2QKujgehOlUSKHxX1zkk74K8eJvftrBUIx8VGlo2\nB2zw\r\n=6XpK\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"96ec945323e39670438469a654e6a0734133b224","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-07-08T07:24:21.518Z"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.60","@balena/jellyfish-logger":"^3.0.43","@balena/jellyfish-metrics":"^1.0.310","@balena/jellyfish-environment":"^4.3.11"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.23","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/semver":"^7.3.7","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.5","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.2.0-h32-safe-migrations-96ec945323e39670438469a654e6a0734133b224_1625729303868_0.48921590443491025","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.2.0-h32-safe-migrations-0bb9f55f66eabccc582b63dd600b2dc80f5999bb":{"name":"@balena/jellyfish-core","version":"5.2.0-h32-safe-migrations-0bb9f55f66eabccc582b63dd600b2dc80f5999bb","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.2.0-h32-safe-migrations-0bb9f55f66eabccc582b63dd600b2dc80f5999bb","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"540f7e2fffef25b1aedd031479924f10a24d1386","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.2.0-h32-safe-migrations-0bb9f55f66eabccc582b63dd600b2dc80f5999bb.tgz","fileCount":4,"integrity":"sha512-Hc62LjckxcxTOaSqIUbIwFQs5tkZQg8B8dkuFW4VgqsCMTFMBN095siFGJIZhmPXEB03otEVdjaLI23Hq85tvw==","signatures":[{"sig":"MEYCIQD4KxMGkqme607ITHPMVyEnHjOU6KVzdPx3H8Z/6PZatAIhAKnZfz7r+EGvUpb/dkmGTnvte4zuAhAKVfDSa4mYIRSW","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":88114,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg5rIPCRA9TVsSAnZWagAAQgIP/RhSWxByaxOpWa0ZDKHK\nXOm4AYtNCuBWDmAUnjN6JDbyP15KquD5PDsCmDI6nmea/TE/E2C/7ryPTpEN\nb8bemRb/4uI5coU4idf5MlUxte42A4jzafrkwjkAQngQolhTbt8uTOhYfp8Z\n9wlrKAapCrejXWtj9TxEuTuW40tXLknvviuddlwhNXGNnQXEZV/918ypFO3Z\nUQqT3oa7JUDhedVcGH9V5oslT/LKNPJzE71gJjK9dmadJuAuru/hQ0I4VANX\n7ox5HPILs/r4IJxnviRzwHQjZkwr3F02luKquAei+dBLlwda0UU6NOt4lk+o\nl2Z8gUOBnV3d+yCz2rsqgLf7Vbd41ZHL3Jk60P1DIj/ejNaHXmKZmstUioR8\nKuv3LdScM3cUc9NsXrRwJdhskGlUPJ2vUGzsE3iXYvJASHtiSaP+q/Ex3oRH\n8f9t2tASHo7i2VdlXmxYkb0wIy5We8aq1ex4RknM0XIsBM5QyOYSP7hiJAhS\nhREftae6OQqc4TXb504cEuWq5E8C7gx/E30T8ckQnouZXfTFdbOItCWKPlu6\nf+9pspvbNfUWbdpDRY7Skdk3Y6s6Mkb/rqDy5fayfC4FPpKFQViA2/WSgNkI\nQa++2189cKrNgHbE4LEXacymOXPCVQvT6jhbx6z9rRlNAhx6vSGMOZgGNZCI\ncCK/\r\n=454D\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0bb9f55f66eabccc582b63dd600b2dc80f5999bb","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-07-08T08:03:21.795Z"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.60","@balena/jellyfish-logger":"^3.0.43","@balena/jellyfish-metrics":"^1.0.310","@balena/jellyfish-environment":"^4.3.11"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.23","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/semver":"^7.3.7","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.5","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.2.0-h32-safe-migrations-0bb9f55f66eabccc582b63dd600b2dc80f5999bb_1625731598848_0.31834377632630484","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.2.0-h32-safe-migrations-b3bb83d0a83d3c77cc896d4fe873c122dcdd71bf":{"name":"@balena/jellyfish-core","version":"5.2.0-h32-safe-migrations-b3bb83d0a83d3c77cc896d4fe873c122dcdd71bf","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.2.0-h32-safe-migrations-b3bb83d0a83d3c77cc896d4fe873c122dcdd71bf","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"87036b47d1397d261012f754ed3966c244e3d8cd","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.2.0-h32-safe-migrations-b3bb83d0a83d3c77cc896d4fe873c122dcdd71bf.tgz","fileCount":4,"integrity":"sha512-m1pRiejVkvSnDXs7+70/C8iklMQjaoayY6PDpFf96bgLLh6o7it0IfbcC8aeIWP6Z4KfcrueILALQkNSTYJdxg==","signatures":[{"sig":"MEQCID1QfEvIArCojRaVsSTIsTcb2LCUF3t5cfcafWZ64pKQAiAJGA2P4bXqvclTfZL+YqDQETsLREMuWe6xlG+GNUqfoQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":88356,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg5smTCRA9TVsSAnZWagAAxA8QAIpl5s5W23HbyI14sC3t\nJEvaG2y7rGx+v0mk134ICOWIxFsWRQrRneVYVhAwlR6LJQdvVOUqIIHUQVVw\n4nB/eaw1WUEqHTJjeppp8DdY5zBflo/hjGHcIzMlsipyCVS2tD/ImO4ccW3c\n4Xa3S1NPmIjpbCqowcN4hV9ILNSbEeUdlKnJ/+DEOja4kQdAfzBaDb4r7BTU\n45PdYW1R1NckvJKE6+KSQwiJEvS4KRAKRoYCIqaXugCjhmdVoqiDwKGmNHu9\ncWRca2Pfn/hX7ot1G9P0MnwisNs4uaakA18HCoLEQR5UoD0GFe2M+Cg7krS/\nP2mynC2QQSRBrs/NoBVAACadtrKKH2DQz2rl4wFlRwdSZ7TBWyprtUlaxv4V\ngdaUQJd3D/LJDRQbrx+pASFozeLizAqfAyjYwKCzm1EFm0KcLySqHjRJrFIK\nK6gXEsDMvMlv6q/QElL2d3HEpMYduNj147A26IWopYGYiBR4b747aSzZdSlE\n7cHbloLSX/zHuiGm76WG5nH35rQaUbTR0Sp7VJZJ7OU5rugoyjIn4ubc5ESW\nWzZ4FEHxIgBTPM0sSF5Enr1e9mZOuCCXIkIV/5TYb9wIDukcqOdnlEX2pDcY\nIMewquaI2NluAFGLI3pKWaQPCTy0YN6Yj+CTGK2ykfZm5xbKP2PkNYJN2MFl\nMZaK\r\n=H1nN\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b3bb83d0a83d3c77cc896d4fe873c122dcdd71bf","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test","compose-test":"docker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-07-08T09:41:21.536Z"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.60","@balena/jellyfish-logger":"^3.0.43","@balena/jellyfish-metrics":"^1.0.310","@balena/jellyfish-environment":"^4.3.11"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.23","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/semver":"^7.3.7","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.5","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.2.0-h32-safe-migrations-b3bb83d0a83d3c77cc896d4fe873c122dcdd71bf_1625737618832_0.3576326101726326","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.2-no-inactive-sessions-a0012a11fdd3286908378ea9549ead1101caeed1":{"name":"@balena/jellyfish-core","version":"5.1.2-no-inactive-sessions-a0012a11fdd3286908378ea9549ead1101caeed1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.2-no-inactive-sessions-a0012a11fdd3286908378ea9549ead1101caeed1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5436a364247f9c34a9752403ce782f8561f4a4c9","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.2-no-inactive-sessions-a0012a11fdd3286908378ea9549ead1101caeed1.tgz","fileCount":196,"integrity":"sha512-HpmvJ5gf5TA9WUhyDhUHFiJy/y478SWUeW0TA9AIjwDVpEeVHwN/i/CqN5i4nQoRHcvTJmLvUed5mIK0H3gamQ==","signatures":[{"sig":"MEUCIB4Sey8EEb07WOly/L13Z8XwYtzfAmP5XPRrTPj6Y+SBAiEAzwNcR83UfUlwtP56u6nMFOQuKPAzN+SQ6oTXiPqBuUE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":731117,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg5t6QCRA9TVsSAnZWagAAI5EP+wRizKa9eUkJCEe7aH1A\ntNIHGWhTaHBsPUxrOkxoPdakA8yKzWUcPFcv9OC7BMg3rAmeE/5H00NXT8rY\ni3xQXlagMNn3thYbssuPXWmpMdpTu6bQ2xc6rZChDkIZiMGEsCc9BGVgXVEU\nP/kQC20wr9hJkA0o9p8/IZ8236VyK73q/bBOiug1Ari59/knZ+uloQkSKvWw\nzf+RaxChEyFA7s2wAvZaVnLuUQ/yEUnQI/EDIemaHPDD+a/QgsEqS5LiU6Ij\n73QuSzi8zB5j2ixaYQAiLghZtzm+vIsM6Oxcm8I4GcI/YwdY1/3yINc6utzi\nXsthlDUCO4KJj6k+eyUSZjTqy0pkFGNRiHjJ0MW8UwsRgmOfl0Z2JzNf2AQ4\nlTdGaytoVyObIOP5OUQ/ubAV7SrYrX5Uw1nkOP8f9jFKRmi/iyrhzu0RF7/B\nqQqFtyDmR8YSdKUsbMEPAlVU73Ctx6Owg2cS1AScd9aa94t1DKeWy6Vy4WFW\nslGZp5UJ3QJy1pNGhWze/1pSqf09MEC2Soqy1Le1VD/kU84WBCdtyfb9lg+H\nnPjiqdl//gYtVQE5xIDoT11C1nxaWrXzCfM5Mc9bqIoG0K/54YDl4qURUcn8\nM90TpUJ3+YFxsH6OnZoUR4LI4bHbzvbcIh5l7hjq1VurLTIvEziZMbOZKe17\nQPw/\r\n=1Cg9\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a0012a11fdd3286908378ea9549ead1101caeed1","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-07-08T11:13:21.813Z"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.60","@balena/jellyfish-logger":"^3.0.43","@balena/jellyfish-metrics":"^1.0.310","@balena/jellyfish-environment":"^4.3.11"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.23","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.5","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.2-no-inactive-sessions-a0012a11fdd3286908378ea9549ead1101caeed1_1625742991661_0.8981933761951337","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.2":{"name":"@balena/jellyfish-core","version":"5.1.2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.2","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2146a5820bf950fab45e77ec95bac957ef817f66","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.2.tgz","fileCount":196,"integrity":"sha512-0I2g/e57PXB3PwI1CsSOMpHQ5yusPS35H864jug9+Jd7WZqMpOWrByiDrjYY2t5x9CcGBchDpeYbm2kx2ubJEw==","signatures":[{"sig":"MEQCIDgS0rHQNWe/xc2VutNFuadPJjO7C4DHdbk6STbqX4ZkAiBJfGFPVYThaggqBC/nYDM/n8vhYRkdwhFPPAqCX8dLKQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":731055,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg5ux+CRA9TVsSAnZWagAAJo4P/2x4v4WzjnA4S005kKfY\nMDayd1hglUBBpHmcYixCaa4j1GmCNa/0KRMc7eQZEffw4DSY1YjwYC7hHgAD\nT8IMwH0TWARs+iW1Zc0H0lN+vT6ywNBMVM9WBDCJYryJbIYaPA3VJ/HLG3Md\ngrWywI9HNmB+4hYtd2nH9aR9ixzjuGyJigkxRNnqGWkx0iLEwWViydZM04qO\nuuTvI6e0qhaGElia8wsgOIBrsq8Qql66Zy9fiumaQA31NyKsOalV70Kn1DYX\nz1pf6c3H71BdU7BCnU1662wy7MhJzgIaeH/m3A0w9/sgXxpjYtvFcEYKSwn8\nXduy9vU2AYbuwiBO2xkReCd/I2nd7DS5lQmYUZfQYMiuOjCbGuSy6bKVG00e\nsHRn53eL99NbBO3gj1UmdH3owiNZI/HklQLrJWuEaaadLf34hjPlTxBYtzsC\niy7KKzE+/jF5yX7UhAzy+BvmLoGzRCHj625fOKEoa9mTqb3IiiZ4PEvJ0XkT\nHKcunTjj4IM3CouxiYG7+b+E4TnSwGYBPsiSTPUOK/L4OSXmf4xhQGxU07yy\nvG+yS9S+//A/NpRsLkPNnqZ3mPDAI0Vb00kKGQeW2QqONq0W75ne81hchPLr\n5FOQfOMEe7Ff3DizT2xn2JgvwXie2HshWtX/3pl2Fy8ggzyBP2x0pGXNv7WX\nKfJO\r\n=GQwp\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a547f71db9d232c07edae141db7f88a8be72469f","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-07-08T12:13:20.372Z"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.60","@balena/jellyfish-logger":"^3.0.43","@balena/jellyfish-metrics":"^1.0.310","@balena/jellyfish-environment":"^4.3.11"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.23","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.5","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.2_1625746557848_0.5909357727905744","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.3-renovate-major-external-major-e7185ccb1e7575cd80f65f3eb6e3b7efcb5c1c46":{"name":"@balena/jellyfish-core","version":"5.1.3-renovate-major-external-major-e7185ccb1e7575cd80f65f3eb6e3b7efcb5c1c46","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.3-renovate-major-external-major-e7185ccb1e7575cd80f65f3eb6e3b7efcb5c1c46","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"cdebd36efea92cbada5a3ff875ea654bc228aae3","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.3-renovate-major-external-major-e7185ccb1e7575cd80f65f3eb6e3b7efcb5c1c46.tgz","fileCount":196,"integrity":"sha512-xtGxcD/4hJCwNA/gHCGMhp10OWbL9ugybrAoyK9m/CSYCYKPEwOF+7INEKljUFwQWU9wVlQdtxLjPxJ+RMJC3Q==","signatures":[{"sig":"MEYCIQDGU2MsloJnv6jqQ08mJ64T4ARRwB83/FrL5r6yA2A+iwIhALBnnfbvGMVycwSYuTPWCTzYfBgs8OyGRKiHXDJLCq9S","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":731196,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg5u5CCRA9TVsSAnZWagAAsMQP/32QEWi7dVJJsD/JgIys\n16yKOTarNn2qp3IYxBcaTFurp55V1jEF5DnPz2+TT5I/liJRhv0Yktjycddw\nwNcOapYEJ2Pkm9TA7fyICWfPJNNGS444zgNE6N+jL6xs2kBkkr4ehRN7X2H9\nGD37/tg8U95K7V2irANqRSv5hAPbUDXgtwPvJ9O8c/ZVg7nyZU/p1L8wFub4\n05yzAY1vOzAwEzqwfSn+ChuHINk0g+JTFq4smzJH2tH7TpzbI4i8fLKq+bYj\nz25PACbVDhDslVTB2+cfAEaM6T3Naesv63YcLHIEH9RqLdH2i/mKbO36Rd4g\nqojtblOk+5g1lyjFu5XcWk74Hk9RJUrQlQ/ZEtAq32EHDnm+XjWIMmtWbGtY\nKAdLU7x6Yk0Ngwvmckz09wjpLc+ma8ekb2tlfxg9gf5OTVvS7dG+t1+Qt6VA\nc1+pGrcowE/E7ffZGYcahECChY006z915Lll3TRpFTv+hn+tYLjmRGnvdWbD\naWqHpNHl8ixAzEqWPEjN5hnYOav15spmBzRuKJeztKM1GgMBeitZZeV/EhsJ\nov6lwdl7iVFdWWnHyrbW773f8IGO8xCSYBaExdENxRmFSqOlQH6Vbc2Pn5Wa\nPvbARR6ZCmeQ4Tah07julEdS5+eC2Qif+RbOOt7I+4iQn5fJmQ/49CDV7/X7\nX5Ee\r\n=TdsE\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e7185ccb1e7575cd80f65f3eb6e3b7efcb5c1c46","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-07-08T12:20:44.317Z"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.60","@balena/jellyfish-logger":"^3.0.43","@balena/jellyfish-metrics":"^1.0.310","@balena/jellyfish-environment":"^4.3.11"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.6","deplint":"^1.1.3","ts-jest":"^27.0.3","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.23","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.8.5","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.3-renovate-major-external-major-e7185ccb1e7575cd80f65f3eb6e3b7efcb5c1c46_1625747010017_0.5068913288665851","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.3-remove-only-test-959046e280a15dafe5f99f50e85467cb5d92188b":{"name":"@balena/jellyfish-core","version":"5.1.3-remove-only-test-959046e280a15dafe5f99f50e85467cb5d92188b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.3-remove-only-test-959046e280a15dafe5f99f50e85467cb5d92188b","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a1cc847a6df88a617b4d933add5410666e00dce0","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.3-remove-only-test-959046e280a15dafe5f99f50e85467cb5d92188b.tgz","fileCount":196,"integrity":"sha512-L9d2g6FBdjuJRSci0E9k/TF1ZIuLSmkvldpLrM6CuxdNcdVNcXNEZVmqufh0+H/jbU0Jt6Rb7FmVkmrtOlNegQ==","signatures":[{"sig":"MEQCIFrW8j57rGO6Pko8YTiuEHKCmcMaKtY1Ia7DWXRVMor6AiAdpqJBBAT409xb348ev8OSm5u12UgwUF2FsciE2Iwpbw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":731196,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg5w+QCRA9TVsSAnZWagAATRoP/1ovu9Y5p2Z0ooHM3bUr\nHW4p/hPCQFAAHMBaaqOMXydY+5LxSdiA5uMOtbmNF3LtM9oK6dsYX3E9L0f9\njJKCoCYMXQ1RZAZXr0VCzIiLmrxFpwY909dgIePUWC06h4oQ+rj6nMU5tIjv\nYfhixnswLX8zCSA1r3s4OpWz2F2oy4uwcqr0Ybp3MudrP1rXmufu/G/Rwscu\nLXq8V14rG4fmJeX0sBaUytvuN3EAXIkuLNDyVJ2fF58Vo+5paGysEw51nlRl\nIsjh3T1/4RJZd1Ml65s7DLe7E30xOn9Dcr+mVTTqFbO8KdvpH5b4nNmpw3aZ\nOklxCrZqH9wGbWML40WZT2cK1Shf/XyvFMsE/gLuL4O6DVhRZyj1lr9RECjv\nogQe3UbZnuwSQFy0T5Xb2ynYO2OCqwVt/CTlKSj7QXXbFMNdNnqdsZq5sV25\nAVMxiaTaZpHyQB+f80pgYKLxoQz6KaprtUDBUWPV1ayHb8dyyJdqzXlyMQd9\nboPF7rgcrflAjK8CJH258VeNgvUu1y8gy9u6aMypZSzOXMym899R1s1h1uEg\nYBMJMJP/uf7lVKwCwPGl8ydZJXM3FBvwl8/+tPFrDVH+usKS3J1FfgeIx7tM\ndNJmsWKR/8yKwQLnf+xje6/T8vj0IwHe/uV6tA/zpQH+jKIYAtvFLHoztGID\nz88O\r\n=B/RQ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"959046e280a15dafe5f99f50e85467cb5d92188b","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-07-08T14:42:31.019Z"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.60","@balena/jellyfish-logger":"^3.0.43","@balena/jellyfish-metrics":"^1.0.310","@balena/jellyfish-environment":"^4.3.11"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.23","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.5","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.3-remove-only-test-959046e280a15dafe5f99f50e85467cb5d92188b_1625755536142_0.8873573591713064","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.3":{"name":"@balena/jellyfish-core","version":"5.1.3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.3","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f6e59ffd23f40aebab234c7c012828e4b5969c0a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.3.tgz","fileCount":196,"integrity":"sha512-1Fhf5h02pmEdU9Udh45AbHk/CE1ClrQjoVnqkqLRpX5BqhXBiPoKtU+Qkg8Xl/etclP+UABgHBnSIcky4AfOow==","signatures":[{"sig":"MEUCIQC1kresEKUOWkZ4fQxAXmZ2seOKQ7stZO5Px9+rbIFZBwIgC6oRsa2N43MhdxZhO1Dy2HAsEjJoTdzpnVn3HSWllbA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":731138,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg5xEsCRA9TVsSAnZWagAAZ/0P/ifsIfOIUmfk9vRSvEzO\nu/8Xy7yWjhq1jtD9/xjeYW4UbdGEaQNYxXmU8wq38QkNulyjsbG3LMX4kefs\nvYlHvKcM9ar19WqpEXVMbvacS/2HX8pyygrAKgTkgyUnTqXnB462Pvo/iugG\nWTnUcEWxkp5rlVvxOcCnZYHZ5GDs7IHLCZCd5wv7gijVg5/BNBjSnTAPTJuP\niIpI/v7HEp7UpCwRNbkpgwRRoEW29JQKBbRKophUSOPfPhYtTMMBUINFVneY\neVztMeMt9Vsu3NOzFzAMsx35QvzB3tzLyk2zgU4pAaRBfa1wAZ/txU4ImTGF\nFwzEzusKAH8ihZTNwPW8/Wnp+lELeEJAx+9rRlLqZkjxgSV0G9M35svxNy5p\nGZ5ILv+BXgeP3RFSLlJbHwd7aE1MOySMYDZS5Ru9ZDsfGxywAhCX7+qmaRjl\nauqxKTKhwtry70zQwzHYZf+ij37u6BdoNIOOfrpiNVoASBjRzkXs/GReeKgi\nF+8tMWqUnOsdlqT5IshQKAhnfgrSpcEt4jS53yfLxPs0av+QYKPPqTJdIGRL\nSJ0vG+4Tgz3HYxEqagRdk4DzvaI8uAxL0bDICVMsinE045hbn5dTur1zY6vR\nwr6HAzGhiZpZq6d33O1kPiLjvfIzmBK5qpdydDO3jQpURLQg8oRI+8TJIczD\nuWdZ\r\n=KbUG\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4c5d329fa4c50ba31a8d4d336a127a2fa5794936","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-07-08T14:50:41.458Z"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.60","@balena/jellyfish-logger":"^3.0.43","@balena/jellyfish-metrics":"^1.0.310","@balena/jellyfish-environment":"^4.3.11"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.23","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.5","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.3_1625755947954_0.6843147258294464","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.4-renovate-major-external-major-186b7434bc914188bd34e610afcc4f77c83d8e3a":{"name":"@balena/jellyfish-core","version":"5.1.4-renovate-major-external-major-186b7434bc914188bd34e610afcc4f77c83d8e3a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.4-renovate-major-external-major-186b7434bc914188bd34e610afcc4f77c83d8e3a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"52adde5c047c378eca3f712880d16a9c58dc7b14","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.4-renovate-major-external-major-186b7434bc914188bd34e610afcc4f77c83d8e3a.tgz","fileCount":196,"integrity":"sha512-lDa5Ynrk9VDtamItbiZCarEAOjz/VVbgxuxX1fDx6VclWXf8PtqncM8bLrYKo6SbpZxcLZDRYcVGvzyUu0CqgA==","signatures":[{"sig":"MEUCICJVNyXpDfS0zKePNfPXkwc32o6ejUlsv8FlbfyGYpyqAiEApcpwD4wxbCjnCX2J2BDorUTtRvLSopPpuF5QPe+VfHk=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":731279,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg5xJmCRA9TVsSAnZWagAAfuQQAJGUnvMTrT2ahEeeCUnu\nZwh0TaaKGpEVJJYdW+QHCUrIjie3bgc+hQW32Zv+v0/PkYoTBURMRRvn2KgG\nuZNnVYlxtSXH/zWpfVCMqvSRdDwHu/RN57ju0Pe1TNbi7bTsM0yQ1JMjhYGl\nXy1hQSw6/Hct77TcQmHCe36QKkZJUa1jkgoOXB7U3twR+7f6cQuJ+dTskbRt\n/qLsUiSP6cSzdk5N8WhRpRhNeuPZuyfetdWjYAtq9Bg5CAmPRJCi6zytU7/N\nwy1ECCThdlojlcSQ9TYlF74OZDxxdki4PPCNlixzGN+uMBAnQ2MJPGkKIDZW\nN5a3oOE41HRcnKjLU9s93qauln58oKtc3tucHK293P4jL413TgPYBPf+QwXI\n4aJoMocl0Vq0wcSeVbkmV8ISr2W5M6xxBac2Xyk9wytadU3AS+Qpayhx78c3\nda+wnXjrhYjHPrFXPoMcC/lRmTstqu0OfpahXbPA49OrkPtaBwCSWVEVvwKH\nR8apRdLMufYRjkLLkBslDJOfpxtrf8jZNrsHXwSgDHDlXYnL6nUCkJTAO4Cs\nFGlkgdyBN3T7FqWgdogHqZrTWfAIiALeSTa6guNPRZ5o1FCdTtEeIAvec8S5\ndRPzcydr8LLcbUz97rbGoAQR1quNnFHLZJLPqGRuTYsY+cksFNMHd+bwYj80\nFfdZ\r\n=JSE9\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"186b7434bc914188bd34e610afcc4f77c83d8e3a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-07-08T14:54:07.934Z"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.60","@balena/jellyfish-logger":"^3.0.43","@balena/jellyfish-metrics":"^1.0.310","@balena/jellyfish-environment":"^4.3.11"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.6","deplint":"^1.1.3","ts-jest":"^27.0.3","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.23","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.8.5","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.4-renovate-major-external-major-186b7434bc914188bd34e610afcc4f77c83d8e3a_1625756261921_0.5964352964334616","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.2.0-h32-safe-migrations-573d52b6eec9967ac27f382061acc1790d99a9fd":{"name":"@balena/jellyfish-core","version":"5.2.0-h32-safe-migrations-573d52b6eec9967ac27f382061acc1790d99a9fd","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.2.0-h32-safe-migrations-573d52b6eec9967ac27f382061acc1790d99a9fd","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5b680c129f6426ec8666cc450b31f6827499468e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.2.0-h32-safe-migrations-573d52b6eec9967ac27f382061acc1790d99a9fd.tgz","fileCount":4,"integrity":"sha512-IQtb1U6oRA85cLdgvQA3U84PDjK2wEwLy2iKGlZKEWfMA91D2GojBTP4fg0WgNdogZhh6oPvBzsOV0XyndHAQQ==","signatures":[{"sig":"MEYCIQCXWU7PZWdU/D8Z9Uzm87ZNREKxH0EJmq2DEvRyIHiIDgIhALHn0E30pF+HQlejczlyRyXxTfdoujCcESINjt5gWIXQ","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":88521,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg5yrsCRA9TVsSAnZWagAAeNQP/1CoajSKxwIFn+kS+/Lv\n8oIPSqeIPQ/2lf46gws6om2ARMe6OKCG7R+HgP/xdl6E9bJybZig4cn+6RUb\nGUL27KvqDMFeiCsPgWl7fsiesHjHpEtYQaIcCu5PJ9ess+5TfQ/us7/9rv8K\nz0IubNb+ulTPVJT4l9j8dYK3J6+Z/2QmUPcwwB1NSRlyFLyVwFY8dXc2MlzO\n9WmAZ8UBiuE+4G/EmIqNYxVXD/nPYzBLHYnCCFSRX5WZSHdoR0MztTgHYFHT\nK7Korb8mridvVTUT8cQUSmnOPNeaOpQ/OxnhIdq/B88RtEpTrHiNfpBkt+Zd\n1/LSmDuhewJPZRlJ/C2j4ztnOnSELP49esh1QL8tp263tMxiR5zeHRHUyfF6\nC45oxWZOEu67TrkNhZAfWw8DZq6Ht9+F0mrzA+3PuuX9EGCg+qJ+2HVpeSI8\nO+TyfNmo6l4aWPuAlvpjzJbetKyaDupVHp0YOvzR5kXrP5otOBAeKnEcoK16\n10b//Z+sGDaBXn2GjeQdX1EUZlXuT31NoT73cis6LRz1I2nNtn7mTsJsYqO/\nPf37pT1yTM9nmiZl8z9d3wN5iVqVBLwF6izV6nIGTRjEz9qtzWMXIfit64u1\no8AwcOBCGCpDNn2vPsJ38LS2zkaSRiQlzDVNWqq1JOyk67Xy7De1MoQPt7+s\nzCr4\r\n=5yCs\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"573d52b6eec9967ac27f382061acc1790d99a9fd","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test","compose-test":"docker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-07-08T16:38:31.991Z"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.60","@balena/jellyfish-logger":"^3.0.43","@balena/jellyfish-metrics":"^1.0.310","@balena/jellyfish-environment":"^4.3.11"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.23","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/semver":"^7.3.7","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.5","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.2.0-h32-safe-migrations-573d52b6eec9967ac27f382061acc1790d99a9fd_1625762540419_0.16327035471028584","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.4-renovate-internal-patch-f1d7dc177a8ab4d229a2974c6b858d5233af18c0":{"name":"@balena/jellyfish-core","version":"5.1.4-renovate-internal-patch-f1d7dc177a8ab4d229a2974c6b858d5233af18c0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.4-renovate-internal-patch-f1d7dc177a8ab4d229a2974c6b858d5233af18c0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"eb58e61d9bbc532a59c9374ad440822f94acf881","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.4-renovate-internal-patch-f1d7dc177a8ab4d229a2974c6b858d5233af18c0.tgz","fileCount":196,"integrity":"sha512-ssqlDzhK2fChuk6qiyRKKtV/NPU89SzR1gIaxetkpkpSFKH6SQQVaBdLB6TtHpfYz3JmZtfn90rNFX/vj8H2lg==","signatures":[{"sig":"MEQCIGzpl3DPHWTuC/xpTTYzboSerJ3etf8U7xf3I8GgyNd+AiB2/O8Xwj35I+tmhGYAcZ6/ztdQtymFywUpJbod5gSgFA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":731273,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg57UICRA9TVsSAnZWagAAPe4P/iHX76W2Vr8edkCdOHLn\nyjePRziHaZJ95Pzj1HNWSob2ySlAs/EuyLpPS7qGKxYic84euFp7M89ZZAPI\ndNkD5sXMso5+9V4HOJhtLy0St/PpFrNnB9uBjJ7wnZRVyL0So0Ib2qin4pTA\nzzO9s25OuSX+vT5coZsMeo7R2T+CzvNXD6SEKSETgfC1IKZrfKCLRyebQWkY\nj3xBY28c6cq98YIOmW56r5LKfTjW9gmoNRuwEV/a4sPo/9X4G8F6yd8p+FCz\ntCpq5PJ7yK221mVD9WRDPUKa3uubjCG9i2fPwl4R9qpj19gWN1Gi+7xavd0p\nLtFzBdAd+UDn7bAYv2nj+x0e2CfB9op9ER+sy9zAcAmaiuwu/3ybq/D7813L\nzLkBdrBwzJYxOfFU5un3qmqgM+8/JvDDn29EkION0fyKg+UR1jd21xsLVauA\nCd3PamonOl8SyB2DHDvEKQkxmbxZ0C7TBONZlZ4LlTdZBDFp3mzLgYv4tLpc\nVKg/c9gtc6HRGE5gj+rxABiLhnvSJqUhvx8rUdszPbv5LxFL/OBqRC+1HgS3\nx0gQW1RUzB+qmf79K8cO0b6fkoT2iPWDA3DIwc8hgYOY8X+mZ+1ni1Avt5Hu\n0G1ibJ+W3xzfqWo496ttpoSgyTNtJldvF9JHjloAfK/6lnsLu00e5TWdnBbh\nFIyw\r\n=Jjrf\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f1d7dc177a8ab4d229a2974c6b858d5233af18c0","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-07-09T02:28:32.319Z"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.60","@balena/jellyfish-logger":"^3.0.44","@balena/jellyfish-metrics":"^1.0.311","@balena/jellyfish-environment":"^4.3.12"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.23","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.6","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.4-renovate-internal-patch-f1d7dc177a8ab4d229a2974c6b858d5233af18c0_1625797896076_0.5174856625724502","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.4":{"name":"@balena/jellyfish-core","version":"5.1.4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.4","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"026e8165483068556651f1cb71b776d1db972be5","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.4.tgz","fileCount":196,"integrity":"sha512-c+flO8apaTtjl19HhrRR0IDeZnnzHez27VSfBmNheNwmajlQGmXuGMj59Wj2y8eSFKMJdPIxjumL5lqR85y0WQ==","signatures":[{"sig":"MEUCIQDOgE6as8qV5KXHHxu6cmD6wdnwZxKOppTPVpm4Flm8pwIgSbDFzoePQTrvc148FpIekAAyqPpMlb6FiRIz1++MmXk=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":731208,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg57a9CRA9TVsSAnZWagAAWPgQAIHNch2tyxoVHpNnMhDd\nRgWxfdvNzvMgxnZKd4KdX0VBfleY5wEVJKY8A7Vj5lr4mwUztICjL83IvCby\nzptKwP4Ggs8f+0Ph/hZQ52oePKoKq+QYd+MrA++caqHxGS5sNvzaMcxO4lno\nf+v0spWNgL9WHz9afa/UJugQauYAWvpeU/B1MnO2/qbSku2HSoebYDPP8XA9\n2d0DQYXBnHTpgBZUmQ7QDekBDtJinHACAmQPIiOxcTo0UqyIbHZYzQsKThSd\neiEtb1/i6V06lSlt1Vrxz/Xm9F26kKRCz9+lGgE55tWyA/JdbgMZf/iSZOsW\nzy4+45X4PXJN7T0dGHCZTFghn19wZs3HKPr9jYlqiFtBxhTyKqWP4BsG/Km5\nBQWqw92GVKJbkJx3JT45Py+4vDF8lOjtIV8b05R4VujpwEuSfnpfbSCuSCYV\nrVqIvGPFCP8jR/Ypmt6x6Wb7mXwYwFQ1na0oPn2oAXTvuabArqMj31lH+2lg\nMrBeu5ubGaQ/zgK/HNKYycy8jru5sLFBhpVASoH//olNr96Bxbajz4QrcT+1\nsg9aOAAD4/cQbWV+xUbh1boyO4ps0M01pFkpUZuVqHCUYSpze3hBR1Bl6Z+T\npgBV53dtzlouavWM6Vi/jqAHk90ySjKggMZkadtvNwa+z+Tr0+zfY8qt9v8A\nWFlk\r\n=sHgl\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"3f6acd60fdb9d9135d5569dc94474bf8bb8121d6","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-07-09T02:37:02.442Z"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.60","@balena/jellyfish-logger":"^3.0.44","@balena/jellyfish-metrics":"^1.0.311","@balena/jellyfish-environment":"^4.3.12"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.23","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.6","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.4_1625798333095_0.849694563350226","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.5-renovate-internal-patch-e2f237fc8a997f9a89c6a23682240fc071baa136":{"name":"@balena/jellyfish-core","version":"5.1.5-renovate-internal-patch-e2f237fc8a997f9a89c6a23682240fc071baa136","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.5-renovate-internal-patch-e2f237fc8a997f9a89c6a23682240fc071baa136","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6ec316c62a90f6fe1efb6c16b8272a30876aa296","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.5-renovate-internal-patch-e2f237fc8a997f9a89c6a23682240fc071baa136.tgz","fileCount":196,"integrity":"sha512-hckHy9btKzkATfNjx9HyhtquEyNqgaTBoA2sOe+b+cBSzUGKHnU4mm6nM4AiAMXsO+aD1hAwCszWHuLXgPRxHg==","signatures":[{"sig":"MEQCIFuL03YViUkzOJyeRZMHPuFSR2yiVdTPQs7Uh/eZFk7rAiAKHxUOc0X+sUOG59SIifigqWDL9EzQaSRDPZpBxulCLA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":731375,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg6OwnCRA9TVsSAnZWagAAmCgP/3TAXKv/IYZKDD6PkJO1\nkrJ3klihqB858ZVKQpSXxsyxhuE2pOFUvChFG5epMZSgopnVu7hz8a8uJ+SL\nxgoV66I6dHomd7wQhxPXNSER7IwakWS7JbbP4ORN+oO38u+W1yddhekYGY3k\ncY76K6B4Y7DhsfJLgMml0B+Y2wsXcj3S5gC11fgBZUcKbSRBOIpK6vs3t0TR\n4LyWyRjirq4m7dxJAGRMdfDkhaWyJQnx+LjZt4ZUAkDs2fjvw9vnIo5Mcef3\njgrdMKdIIZmnHFzQdCjL3TYTZHL48EFs1+aSoNTrwcsEuayRYvESN8EYJ3Uy\nEz6AFlIeQc/mv27MZ5A40pGHHopk4CcV1Gb8xC2vzYD99PetWIyPVVZgyLUf\nRBz/7emYyEsDzwfasqecd3PPf1GqjdfMMWn/OXvyA/hVacu+nlyY0G1c2rzN\nyO9gUZx/laIBoRAoaOuoQjI5TsGh9hhp2lasmuMz7xA1kw7zKSBKmwqhzIbm\nlyKKWy0Cl6g+Vzh/uy6gbQrDUApggrddYMmPhIj2OEMH715Aos72eBP7jcn0\niDx8X0FWfSgSAf34hTM6oCmOH11pY1v/HgNbc0oILB1l3d2IcYkkx7LC1STx\n5kEkqaq+wEWNNOCbBGYIJ+o6Om6oNFs1Btvq+jkO+RjE4/9CjSuz7xC6fG5Y\nftX0\r\n=p4Y9\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e2f237fc8a997f9a89c6a23682240fc071baa136","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-07-10T00:34:33.196Z"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.61","@balena/jellyfish-logger":"^3.0.44","@balena/jellyfish-metrics":"^1.0.311","@balena/jellyfish-environment":"^4.3.12"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.23","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.6","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.5-renovate-internal-patch-e2f237fc8a997f9a89c6a23682240fc071baa136_1625877542443_0.025655142731120018","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.5-renovate-external-non-major-ab4dc888d5e699257b02c17772ab6a63c37aca22":{"name":"@balena/jellyfish-core","version":"5.1.5-renovate-external-non-major-ab4dc888d5e699257b02c17772ab6a63c37aca22","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.5-renovate-external-non-major-ab4dc888d5e699257b02c17772ab6a63c37aca22","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"69f1fb7f68d5e8995d6c459e5d3c4416d1ffaa2d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.5-renovate-external-non-major-ab4dc888d5e699257b02c17772ab6a63c37aca22.tgz","fileCount":196,"integrity":"sha512-9phNh9b2xhstTrHH85As/kNmdLKWnJ5AQWZobIlNzWudk9JN2sM/oJtfAbVovKcOXVs9xVG7SGpIsOa3codTnw==","signatures":[{"sig":"MEYCIQC3FpSSXh+O5UFFk3L90+CkyW39ASxrvKHRAq1fGVcelAIhAJsYZeQftF6YoAqzJKxihpnF0m6l2JGzb5pOWY1X2XWj","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":731351,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg6Ow8CRA9TVsSAnZWagAAMioP/A9oiDOSLFeFn547Pgtr\nYtU4NGdPCh6olNi5f2XZb+ZEdk7K19Qit0mhbrJkxwtmBgF92kC/K2mhZNR+\nyC1ksE1rtOdrHfbHG3LEhPglai6saYOLVzjK7uYxRHNnjTia2hly0QwomnL8\nTvsQBgQ00LixPQd6BKBnsUe1VrzvhBxfotIZPVRvVR4WNggGV1WxybY5wqpZ\nTRX4DfQMnHoce3IwuyoCLSNNBxn5pWyeTmpN8oOBqOXbVhUGaaFj7eiH4irn\nf4iCXuiK/gWtPC0rNhAv4c7+j57GzFojLw548kbOtuHbIaoM32abFwiF1VMX\nrmPpkwljspVEbxZLxnbovJnGlOzj5JFTrRF3mneVw371nPXUhWW4w8H92L/o\nyOKADTnlPSHq7BA+7F6jf4WWFDTCT7w6t5coq6XI9vobfUJve9Wg6NN0T4Y0\nQRSKJ+EF9HxpQ5koxSH27Hbff2SgpaRO3wZDgTciuPMpe07rHcI0LP73oA2A\nfwoTTu5z/KXq2+DmcfhQLFp0jyoP/+VKOrvME64O/6HJ4ZZLFMse+Nb1URiI\nHwJIV8AH4MccZwWMEW9DopYtxcnFDn5Tfmr99iWmZ1sGtFHmTCCAyM18oK1J\nxB78YU5PBQCiCxKKOO+f8KxnCPZ36oGdoiuWhZ1M8We/O7b5QGUQ0lbPBuej\nSC4g\r\n=QvyX\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ab4dc888d5e699257b02c17772ab6a63c37aca22","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-07-10T00:36:12.324Z"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.60","@balena/jellyfish-logger":"^3.0.44","@balena/jellyfish-metrics":"^1.0.311","@balena/jellyfish-environment":"^4.3.12"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.6","@types/debounce-promise":"^3.1.4"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.5-renovate-external-non-major-ab4dc888d5e699257b02c17772ab6a63c37aca22_1625877564443_0.9431970989757232","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.5-renovate-internal-patch-c70d55f6ffa2734b12902043bfa84ee66386de93":{"name":"@balena/jellyfish-core","version":"5.1.5-renovate-internal-patch-c70d55f6ffa2734b12902043bfa84ee66386de93","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.5-renovate-internal-patch-c70d55f6ffa2734b12902043bfa84ee66386de93","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"031bcd0481371565424efd9ddc93749f398f3d98","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.5-renovate-internal-patch-c70d55f6ffa2734b12902043bfa84ee66386de93.tgz","fileCount":196,"integrity":"sha512-l5RYM5wKmdw1qq1F6NBEZDEKNH9DUBLcnrxl24BCfiBcWEjncOoO1/2/ni2WgpNUBReodVPcqBM4bE7LShkJ9g==","signatures":[{"sig":"MEUCIQDVgVulmbAKGQbRHTJBIsAO9O6QqPHFkGM0+KC3V/THyQIgKj7OpYb3v0ZgYvl3gLGTnrSEMjsCZNcoQYxyEDTGsV4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":731375,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg6O1uCRA9TVsSAnZWagAAtVgP+QAPvG3YvP2rRR8qsrd4\nxgEWnLGvflFMkqMVfLppiXuKiKRsmykFkrJ2/N3J5kr+CxQRQFqPZ1chTnv8\n9gOmdV7Qmq0quY7i2MMYm/bYTY3QOH55qwOOhjsfQ8Q1RpRf39TqcPil+HnS\nPo5PNR+D1BQueE0d68/Ydde3AdfFdpSz1v/Hw3VH5J9x9CEDY50xp5ETALxI\n2XV9z/nA3cDBSH33HyK8RbRkZaPceyqHv9AYN/mbHF/MAJjTrhf6KI+y7o9f\nvg87Z8dkkDccGubERqmRy1ZkSg1xfjP8JiAD+EPQa85ynVz0ADwQD9ap5cx4\nRgor3xaWbQWDR8/2EiTsrWHLymiReSonGKlPQ3O1nv862lFL51jG5ivzbAV+\n0vkO/KSN37orKeb+F9RGMpwhGYjfmPuq6GIt0RccPNLB4WgCMv7gNONEheA8\nMzYEwfgDT12vPEO/026kP4OvsyvZ+DTh2PxmNOgetY3b+m63hjvioj6J02R0\n+5H1rSH4XED2fbYeTGME7REL7Dmra14Vh5g8ylhSCU8i7HZIKwbD74ppuCgG\nKTx7lYPUjMfjQTCJl301RSE1r+brKISwUdbvX1DjNfwOy5LUAeuVsEWXmbQ1\nDBOeRXBM71kBwpC7Bqxv/l7wG/kL3ilnqg5L2b028sl/fHjNTdYhTcFW3J47\numn2\r\n=r3di\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c70d55f6ffa2734b12902043bfa84ee66386de93","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-07-10T00:41:43.955Z"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.62","@balena/jellyfish-logger":"^3.0.44","@balena/jellyfish-metrics":"^1.0.311","@balena/jellyfish-environment":"^4.3.12"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.23","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.30","@types/bluebird":"^3.5.35","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.1","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.6","@types/debounce-promise":"^3.1.3"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.5-renovate-internal-patch-c70d55f6ffa2734b12902043bfa84ee66386de93_1625877869646_0.3963546873857915","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.5":{"name":"@balena/jellyfish-core","version":"5.1.5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.5","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0f76389b0842e274404d443f708a7e7549391579","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.5.tgz","fileCount":196,"integrity":"sha512-euhxirmxLaAD1sqH5ynKH3CJeGhpGzQcplLlIBNuZZ00Exh0zxlqdanFfTwfs75J/qIncCdQyPPt3esvfNSP4Q==","signatures":[{"sig":"MEUCIQCnUGFjn4RI6myPgwhZJTs+9S+xRYswUEQcS9yofraaIQIgGEs1n395AjiEOqIbgyiGZfsnXmyYjH1Vew2sO7iizr4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":731282,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg6O3JCRA9TVsSAnZWagAAV78QAJyVqqLUWtVpPvfK+AEy\nnLSFvnNlEsYpccJ7C8UwU/pxtwUkHzr05eFX4M8OR5A/NaSlAQMEeoYagqO+\n7v0zdLTXPLEGnubBo0u3rfWfu66uJ3Fj1wirhs9MjY3LPZrZLT37+h2NCSAO\nTn7LchEtWmosGjadfQdFBsbzMgND2Hkk6VPPRZ3fhqVhmb2JGFZPwxHg9g2f\nugA+xHz37FsIu/66tj6oFvSdxE9VFzLY+9O0qy92aPf9MXX7bBQwD8m/GWla\ntBtCfqGUceVXOS45Gc8AyLJFgaMfbEUCP5npdHgzYFhwPTpccvFxCYV30Ghz\nNWwqmPaVcVkZff5W3huP1NHmF7bT3BrGwwr2NiH5NAPN5I+Gy7oOLboNBwq6\nnvakOmmj5G8RUxoEkE53kX0O8EJ6DgtjOPGinIgIbegfSXRs/ExTLBfi9bcl\nrejPndCTSpVfZRbYxUhMZP9PnpfBvULSabeTmYz/AnyJ4PFZJTHmIvrMRUH6\nPAVAoImOCqySvblQHLTQnr4fZPSob4yafV+9uubRoRrUvRGwXjXCICbO8b35\n8Mxu+rPAh4xkVjCzra/VY5L7vpQjpotvx2vOlTSaHeosloM5HdNdnkH2rT3l\nAui2MqVtdAPwSEteylZJhDGvJxHni3sFZA1vzSt++LK7aqiQPID/XOi5nWyV\n+TLL\r\n=5UaD\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6e19eed68381004764b6260253c98bb2a4355a57","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-07-10T00:43:54.834Z"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.60","@balena/jellyfish-logger":"^3.0.44","@balena/jellyfish-metrics":"^1.0.311","@balena/jellyfish-environment":"^4.3.12"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.6","@types/debounce-promise":"^3.1.4"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.5_1625877961181_0.13637618905877158","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.6-renovate-internal-patch-72f67b9cc9a8fa6fcd3f5c2b4331ea2be5cade53":{"name":"@balena/jellyfish-core","version":"5.1.6-renovate-internal-patch-72f67b9cc9a8fa6fcd3f5c2b4331ea2be5cade53","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.6-renovate-internal-patch-72f67b9cc9a8fa6fcd3f5c2b4331ea2be5cade53","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"96dfaaeb631a33cce5306ffc62cab1c8fe15c013","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.6-renovate-internal-patch-72f67b9cc9a8fa6fcd3f5c2b4331ea2be5cade53.tgz","fileCount":196,"integrity":"sha512-fnLkPl7Ct3Wvryl4foCskjnHIig+71E0yFyOhHq8lwlnelOxbL7GXU5/6ehcSaiaJNziRJrlyVV79LOt8GVYZQ==","signatures":[{"sig":"MEUCIQDBm6UJe6OvSsqcbtUvQAMmA94IN2zsyc+UEh06sm0AagIgeBh5s5G7jNXZBdpjIvyMW1M6F2HAETRIQ8RwOkzwav0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":731449,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg6O7SCRA9TVsSAnZWagAAxo0QAIrziQEBINoNtgT8eXYH\nmxswjGffCucaEyhnmg6X2vbigvgvRGEtcI93aUG7vp30OJZAGHBKH6GgaD4w\nY7+cr/p4XVoQsFCcSGKvUFFQf9I3FuR5xD7jDr2hdLPEh8MFL2dzqcgELmGf\nsil2gK0G3pwTnlly+gresN7g4ZAafmmIjwULepXVVTOPZJM1/mmXT6npM4Ih\nIWFOBvGyR5pIrGq6icA9Xln0tKfEM8QeVeb9fbx0Tsaj4vcm5TTjfxAacIBc\ni2UqHLnZe+uO3f7wOF7zIuLEhffaTdm2PHt0uX0Nh0ohXjgB5rRxXpKIAu53\nd+0OO1i9SMLITBKarFOBPPIfpUN1iP2yzQxl+xwnYqg++WGfPA1JLc4Faw+D\n3tAYX204AbHEzDnB21y3K5RJSyKijeqzyNI4+B6RlVQtA27UUVDhFPHDwuO7\nF2CfsPR4CzOr4zdFS3BlFOOFK83ix/9Xe/AJnPIy3rj9q/mxi/nOtq4WDYVt\n9vVLQ/HkadG8QLaB8iTAu6eauIvMlxR+N0fIwxL90CPhNFTY0uGQk1SFXsyV\nzHthLTdCrofIZkx0t6Hfa5v5AfMqbECiv/oDBVvMvyL0nrvdZbOYARuKrJlc\n7u1ZLgyfN8A8wN2/j86unkcWkhon5Ye5IZcgjl5oLwDL2KkUFECFRexAuybU\nX2pQ\r\n=EniW\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"72f67b9cc9a8fa6fcd3f5c2b4331ea2be5cade53","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-07-10T00:46:42.042Z"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.62","@balena/jellyfish-logger":"^3.0.44","@balena/jellyfish-metrics":"^1.0.311","@balena/jellyfish-environment":"^4.3.12"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.6","@types/debounce-promise":"^3.1.4"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.6-renovate-internal-patch-72f67b9cc9a8fa6fcd3f5c2b4331ea2be5cade53_1625878225775_0.1743415438246536","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.6-renovate-internal-patch-428afd2e63016830858ffcdbab83f1854ba506d0":{"name":"@balena/jellyfish-core","version":"5.1.6-renovate-internal-patch-428afd2e63016830858ffcdbab83f1854ba506d0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.6-renovate-internal-patch-428afd2e63016830858ffcdbab83f1854ba506d0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b3f963f18c958c5450b3d1ada709b9a819304cc6","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.6-renovate-internal-patch-428afd2e63016830858ffcdbab83f1854ba506d0.tgz","fileCount":196,"integrity":"sha512-+48bSNz3BvKi6zq3h6EROt6MwmBuesl3gXuI2UurW86PmrLmx7TEHySqoc/tWNF2s4pbJpdoqN9S5wvRjoEbnQ==","signatures":[{"sig":"MEYCIQDNKtXOUUQbVU3UzBQnuUry95miWab+UicUTu/TZH/fNAIhAN+n24PYm4cVSG5be7OhNFKl9VFh5rG7x2dOcC3xskys","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":731417,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg7N+uCRA9TVsSAnZWagAApHMP/0bMLYhB0C/wcmgHs0JQ\neECDoNSpDAajxz9unZo2dbQqNE4LXsPr/mKnid8Sr6SsULYgD8I6e3DRieqF\noIt2hOpL7+BhHrpuba8oxKAmXssE2aEabZBiMei49r4wdB4V0T+Cb1a0PvcV\nQhDTTi5OIyQMgpjnBa7/B3hk9JUuCGrgbXayNff2a/E2/hSqYRuAd8Omx4eD\nH4LRa8x93vZiMNzSPYrSPHGljbBB9sVv7UJMGFFOJSxkYOxaZVbSDaa2fwFG\nYEkuAxjXgpT+fT28Pf9rTDr0BcCYUcCfTqURExeboZzlxTye4PhZOm2pmxyc\n4WXLYMhQFkgP8p8X2KQkLVGZwPJVgVvv+gCemEI2okQ/QmeI/XVl+rUQgQqa\nIE4mqOu4yFiBN2PmeOw9YzLBQ16uRQRtBlGEg8ouPdl8tZrqBkU6QqZOBQLA\n4KkFVOfeqJvprZoW0UNnBTCeLxV6guFk76UccNJu+hxv+lxgAg9pyzdnYYLe\nj4clPIr3PYTG+CAjmXV9r6G3BWBXNiMtEv7Au4B4/yOYy+9/s1iTAGWrA4qf\nnoo4ZmtIKOniHebnqLPHVL68LDUVVwjQ4ng2UfSNlp+EnUFa/wNb23PjbXQE\niJym44KllU5HKlYfCVV0dUCyngUVih7oAkKAMXFQ+Vr9tpZPNKQbcoKTxxMY\nZW3H\r\n=nPDg\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"428afd2e63016830858ffcdbab83f1854ba506d0","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-07-13T00:31:41.656Z"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.62","@balena/jellyfish-logger":"^3.0.45","@balena/jellyfish-metrics":"^1.0.311","@balena/jellyfish-environment":"^4.3.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.7","@types/debounce-promise":"^3.1.4"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.6-renovate-internal-patch-428afd2e63016830858ffcdbab83f1854ba506d0_1626136493862_0.4834300625960606","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.6":{"name":"@balena/jellyfish-core","version":"5.1.6","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.6","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"94b03a41bd507f38a2673108f6dbf41c0ec48e65","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.6.tgz","fileCount":196,"integrity":"sha512-yTzc9SkWP/fQB5rbxIZ/HIOpAnzCd6z86XizJIEtD+69Xn+DSKrvV5Iu481K02zB7WPHpPogfcvnn7diCNSo7Q==","signatures":[{"sig":"MEQCIERe/F33elMeisrbXnHz8a+RDujtvnit+Wutg84+lwU8AiB0Te5lfrWXv5B5LLxE+HhZUTVtaBcdui5/jmAHL2iyMg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":731352,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg7OHdCRA9TVsSAnZWagAAcWcP/3ghp99ngu+ZwK9wnMDm\nm17LyReUDhLXbWX+F7TE/ma+Y0/pL3D7TXkr6g8YHPDKRAO/mrad2NoFAh5H\nr+5zt8bNToWly6F59Y39G2ohx5+XP+HwUNBu8u2BVLoT8sAKTUDobUK3mUMd\nwzaZlvz7zwoULgYdTjx0MTZpnsaUsMDKYoyhuOJzMQZtMnitctEeqGVlJaHL\nnovFBbGqG7pq6oA8ospj7ycvyHA+gYLpc1/13sfLakoCryO55O5P+8WYT9vN\n0YV85zY2WGpgPU8TzOICk7wTWiD9dRoH7LUlO6WlmoHQmqfHEa8iXfyCjsCe\n+w2S95qt0Zu9hQIy8WZgvwO7vwOmYB89bhU2Wekh6HfyoX35nQwEtnWcV9DU\nVOInAuGW0/2axFiMCqX4rKX3F8qMNB+c4owwX4PNhUCDu0SGK/LcAadhi+hJ\nuDWH8cE8jGt0arzm+8roq9Te9I0rpz2C9cBzgHi01pOvG+nr49OuxHUg3eIf\nI3ufZ1R2t07pkXo9FWCWqSasrIaByF5byeWZloX6NUoLCMCbCDMuif8x7qQJ\n9ETdcpe4IODFCI4oagv7fvrdu+CP7vsmObnuFdsmDyEEl9Z5+NTzQ/dlK0R8\nFu5lEIk1CwNoJ7OvC2a6+efsJ4vm/cbsStwhNidpQ+F3cejcY8yf5jJO0rD8\nk861\r\n=ix0R\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"065774204afe932ec565c60ec038f65e030d1ec6","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-07-13T00:39:54.978Z"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.62","@balena/jellyfish-logger":"^3.0.45","@balena/jellyfish-metrics":"^1.0.311","@balena/jellyfish-environment":"^4.3.13"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.7","@types/debounce-promise":"^3.1.4"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.6_1626137052511_0.5167190061080269","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.2.0-h32-safe-migrations-a66c6b27098fc38136bb1c4bc128fb0a3a1184f0":{"name":"@balena/jellyfish-core","version":"5.2.0-h32-safe-migrations-a66c6b27098fc38136bb1c4bc128fb0a3a1184f0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.2.0-h32-safe-migrations-a66c6b27098fc38136bb1c4bc128fb0a3a1184f0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e8ccc2541743a5428304d3a95bf811b4a16da441","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.2.0-h32-safe-migrations-a66c6b27098fc38136bb1c4bc128fb0a3a1184f0.tgz","fileCount":4,"integrity":"sha512-mHaO50OYX+6JlBZT4Prq0EX6yv+ILWJ7K2X/ZNlEGFzsQ/eeuE0uOX2hXMAr9n4tTsRULyoWSz9aFoxbqfU7ww==","signatures":[{"sig":"MEUCIQClV9dqf6AlMmo3nRqz34HrF+csDruCeVrBGKpcSGiOzgIgFYJXaYxVvhCQpRT20EvltzsD9ZnqwfBkfyfqoGntHBk=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":88735,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg7XyECRA9TVsSAnZWagAAn/EQAJAvZnFKtuZOsysqb28r\n3/7Fjome9IeZno+38aqc1GqwfUhVGcpns4EYBQfNwIuDzbHmT8DvLfLiHKkZ\nQRouZ6Nax2aE8B2R6RI1MqKHXDyhbeMddtsrnub6nsf9HVgR2633bM8z1oYw\nEwUG68iJ5P/Nw1c5bJOyIRrtBU7k4UO77dNnA7/AzbQKXGTHNMo1alEgZFqA\nHGZKnpb7lJdNezLsd6txWqRkIJWloMjgf0Q2P7A7BsswwqvqB73iXToYvQQk\nqO4nbT9no9clFNZWMQ/eQ/MSP8L8jXmY8HlO2UCe0q4wm5H5z1VQEOeXzHAo\nGgi9lIYbxIMGTLLHShYfvFP25Z4DMd0hjdEQoz68BLyJtc/lEGxAwBDodTZZ\nrQ/mCMpbp5FDIKLDo1T1g15g9zKc0crczYugDs+Ri9qSKlD7iKh8prADFRdq\nZGkFCm3o6Zdok6iaOpvsROLT0e2x34moZlVJ+N9epBSaTUep5A5mejFpu0tb\nrWriV0kpyR8tksA0i26/aDmASp4OZuMlESM4PUiJggSGQPXn1O0iE4I4Dn+0\nCP1Blia0sDWJunuEJD20gOjHjU9Fs5HvZbU7YOEPt9zMb8U5LdIcG2uy9WTP\nQ70ZK0OeNS7udD7YkXQ+cMJNIV1798so3APEQNHBz4fJUVb27FmMBBzt1bZ2\n8Iq4\r\n=ZsfD\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a66c6b27098fc38136bb1c4bc128fb0a3a1184f0","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test","compose-test":"docker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-07-13T11:39:39.354Z"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.62","@balena/jellyfish-logger":"^3.0.45","@balena/jellyfish-metrics":"^1.0.311","@balena/jellyfish-environment":"^4.3.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/semver":"^7.3.7","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.7","@types/debounce-promise":"^3.1.4"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.2.0-h32-safe-migrations-a66c6b27098fc38136bb1c4bc128fb0a3a1184f0_1626176643655_0.7941794727992664","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.7-use-jellyfish-config-682aca56421d869284cf93c0b8fa793a14ef2309":{"name":"@balena/jellyfish-core","version":"5.1.7-use-jellyfish-config-682aca56421d869284cf93c0b8fa793a14ef2309","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.7-use-jellyfish-config-682aca56421d869284cf93c0b8fa793a14ef2309","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"90fa450e5791af960cc0b4d88e39af0d83de28ce","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.7-use-jellyfish-config-682aca56421d869284cf93c0b8fa793a14ef2309.tgz","fileCount":196,"integrity":"sha512-qj3bDJIqUXG6zlMjH7c7gmPyGcSZPrfC6nN2gdVWrCw4ZT/SPpHG90dy64mVydQXj5ATHsCSF8twyUt2naUrdA==","signatures":[{"sig":"MEQCIDPSIRxdsiLDryt9g6UP0ZWIMkqC2ARfMVdhvNx2qH9AAiBJxB2ZpQI1h3nkw5VwUgMno6JdexL1aZNiJXnJIpY+fQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":731534,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg7pRhCRA9TVsSAnZWagAAUI0P/0Uq4oB4ElKVCdPz5dPk\nXUuDtmey6PXMT/khi97IagAdBQb7g7RoZEtOiRFRDzRL2Vuhm6Y94MSZttlT\n/AH9foADjutuEaX+UKSVX4ZvfgvrmInTjLofZZyIjB0VwhSCR4fmId8v+532\nBjHrCiaO0o/no4+X2TkCv7u+pJ8NS2aIgWRy0Oj0iyqQmtQMcpeKaNuQqK7j\ntM/ErrXWYRbnC9yKIkIyeITKanCv19TssPURO5NtiBoMixqsHQ3eNE6gQYyJ\nTjJomUl5hQGA3UJ58rh62mXhDf8ij9EKEoc4mEcwspRAKN8Q5KwffZnJCg+Q\nkjEfE6ccBBna8NUoxnKxMeyGe/sveV6L5fx/HyewYonraDZpmgrJIGQndpPy\nsvOtVTT2K8AQsRNRzQXa0Nfp374NBY/baiX/0lW/GEKuisgrcMSjKmhHamJ8\n+oUckfKT9Pycb5XGAKeRW4JQiZ88b87OYJoW2FWr5Re6s4z22f5yW0dQxoTs\nZF7q28WBVXknyY+gkmim1Oca9vUctQOZLx6WVg5i3qKYT/EwT/Jrq0qTzlRY\nqUH6dEcxzRY/a44rhtQSyJEY7NTcheFvcWqCzThpwbHdrZInggQwnwF0klbe\nxdR9VD/K9XxgBiFAorl15GNvKIVSPE569esvY4bG9L8K6DXt2+LUnyBIyIGx\nAqz6\r\n=Hk3o\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"682aca56421d869284cf93c0b8fa793a14ef2309","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-07-14T07:34:05.663Z"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.62","@balena/jellyfish-logger":"^3.0.45","@balena/jellyfish-metrics":"^1.0.311","@balena/jellyfish-environment":"^4.3.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.7","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.7-use-jellyfish-config-682aca56421d869284cf93c0b8fa793a14ef2309_1626248288753_0.09105792109513611","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.7":{"name":"@balena/jellyfish-core","version":"5.1.7","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.7","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"761fda113e7ec8d1495438e60045240dc9756189","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.7.tgz","fileCount":196,"integrity":"sha512-totJYc4yFRnujxTlI32lnqEp+9GzRdQ33o3UdoRpU2Y2i81wx0WgXrLZPEHc8KgtToO+GQGPrzrSS+7AqSXGEQ==","signatures":[{"sig":"MEUCIHRLZUOX6VNCOswUYWMspAPeRKilh3FCRWA3fQpkxSqWAiEAj0isvfB5M08hx/q3PpQNgoaYgNdZBlnmsWtUsKa42lQ=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":731472,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg7pYQCRA9TVsSAnZWagAApVwP/Aph7edMimBUQXGkv0Ph\nXehT1/DvcAuk864Fy0gDEMobidz4q264VunQOuLFMkVTLLIMl0xKsPVfQ3pE\n9N57ZNcnkhohY7RVa+btruTaZ21BN3Wgj/EDh4h5nT9UVEimCv0HfM3yxZ8m\nfrNMeyKJ+afB71YJvycdw9ZV+Vb0mdPX1CwpAhpU6E8uEKHA3Jt3yorULEPI\nBqGJEQ7SeUHg+aEHUiqAdH+SKWfNBhs9e/HjkmFWb/j+hC+9PSWNxTEA7KX4\ncnsDNVqpAgVtUfgvrV9wVVVmpZOKv8m3N6HE4N3FV9sn6YXz/+f74YLkXtcZ\nvdVam9kPxIURaYCEixERpQFns8h899tmGbNhLzv8vSWEQZgHhdycbT4EP8mz\nKMYaY35yq2lXwKVpZFUHtxKmUNLafM/qIX4dINgckuXDMGokWqpVaaqae4IW\nmthMdpyi3zDSx771L/37aHpGpI15HAHsYsHeD3N00yKGeo4GT5nkqvm1IZwO\nujRBigl0DsBONGQkELU705fECMykkABj2ozDM+ysMqunv+yzk4xAJgZyuYqn\nOy9k2ZSL8Wt9vO4qf1scmXKBjsqKk2Y120JETvQ1GTIGSfX+Q1mZ3bTLM1WZ\nQZGRCfsQEDiv1OmHyRboN8Na3VNwhcrRf28mOCS6kcfkKopoMEZ6YjrDA2np\no8jT\r\n=lPY1\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"5d07559e45b6088738b758148a53fcdd18047d20","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-07-14T07:42:59.238Z"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.62","@balena/jellyfish-logger":"^3.0.45","@balena/jellyfish-metrics":"^1.0.311","@balena/jellyfish-environment":"^4.3.13"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.7","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.7_1626248719675_0.34974105530298627","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.8-ui-schema-tweak-d80a557dbf32281bfda8849c5e1be7f83bd99e2a":{"name":"@balena/jellyfish-core","version":"5.1.8-ui-schema-tweak-d80a557dbf32281bfda8849c5e1be7f83bd99e2a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.8-ui-schema-tweak-d80a557dbf32281bfda8849c5e1be7f83bd99e2a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"412d94013f5df216495e3f7717d22c66c999b9e7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.8-ui-schema-tweak-d80a557dbf32281bfda8849c5e1be7f83bd99e2a.tgz","fileCount":196,"integrity":"sha512-Ng34AgpB+sC68KQoM8J56nizdg8ab9XGukcrs/RlRYVbjAV3el4rUTewkvLVwI8FH0d8z2zBD1I14IUAPj2ipg==","signatures":[{"sig":"MEUCIGk5K7VRbJ44Tn23/wucYDa14K9YnJ03WqLbKYLCL+i9AiEApHhBr9iJaqaBFcgIBdPMsY6ulLB/PuxCTarI0rRzVeQ=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":731791,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg8T4VCRA9TVsSAnZWagAA7p8P/2pGFCli66U5ZwvfXUKF\nox4c42lJn3CqvBsFubtBZIXgQ88E8Oqkb8Bpk6cGXZYZIl/7EVH55XyK6Sdz\nWCOe9xd7325O/JVFFkweDjq9H9Qbag43OycumkruY3CWx6QH7EznS7sebQ6Z\nFc9wsJjG98jkwwqhxH+t/doJ8auNTrg9MhjH02y8a86Y7hEKkC2jV8Oj/2ZG\nAp8+pHsUqHOL5rlgYC8+bX3EUleonrJo5Hf7R3HXsFv+321SsmUU3iATGRLY\nDz3kr1driOAnolc40f4Q7okDKvVdq6M/Od8p1JRGZ8YK1FNJATvsAnQ6FP3Q\nn0YXM+UGgE7T6lZDZfUHjcQupeWvijFEQdySx1mb9ihC6xrn7rXYQyqMkrX6\nBg5fIq6UPbWHBAf6SqaEHZSUKvbB2aMpzxn8wKr//r18y5flqe8dX5+JP0ZE\nq1YXtY3NT7rLuMauFDsyDaWUlWGOhBVHix65MPG2WExe32OMWQRQoKNjC7Ow\n3LMYyk33DVQjnzrtLzWxakv7esG7y1T/q5Cx9murvMxHat06ngyOV52h/ImL\nG+StStw5U3KrkzvbzFZmD5Qzh4R8FhDXqpPNNethSl+1aTXvkl+0WUoRs1yN\n55mmMNaiycv2wQ3F4aJFXboWgM+axNoaTiRRFxgVSkjf2DQsMKoQxxEpwAhi\nAnlI\r\n=UIzI\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d80a557dbf32281bfda8849c5e1be7f83bd99e2a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-07-16T08:03:13.674Z"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.62","@balena/jellyfish-logger":"^3.0.45","@balena/jellyfish-metrics":"^1.0.311","@balena/jellyfish-environment":"^4.3.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.7","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.8-ui-schema-tweak-d80a557dbf32281bfda8849c5e1be7f83bd99e2a_1626422805801_0.7724225627117971","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.8":{"name":"@balena/jellyfish-core","version":"5.1.8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.8","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"06a1b804f917b5c8de2390a285cdd1d1b631b436","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.8.tgz","fileCount":196,"integrity":"sha512-VcydFBiBKITI+5oQ15++lkyWKk8JcHyEi1GAvF23P7nrBmB5mwqelopVKippxXLYF3SHdnbLSp3BW7tap3MjGA==","signatures":[{"sig":"MEUCIEM+MihEa1kH70Y3Pdxt9Gl+eUA5QvjyZ1WDLUSzxtnWAiEAgO1xXCs8ho8ThGso5e78H1evry1BBENtmD6KVRflqzA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":731734,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg8T+aCRA9TVsSAnZWagAAtPYP/iQ/VAhdy53a5kNDjxxS\n5a44FP2aXTg+H806FI5qYTK2QAT7VRuSNF1HqTdYWVZGJ/7A2ktqK/RQvxv7\nJlXtWYUQkkSTuNeykz9NO5tRB7qzPPDa+bFeqxZt0PGg2KOxxqFC3sz2AHK0\nIZ8lPXLKRpz4Yad8K1FmQH6v2nAgDbPj0acTqr5dJz/pZXwv+Aq5Vi6SsslR\n61kY0Q385dhEucBajuNjNU8lb1LCNRWIZtqPa/EJu7ucXZ5EliYEP0HMZ9/V\nQ9U+RkelLfmSe88AzfMMLdNnbzh76O0GEKfe2LWGiFr/Uhaoz3FYpnAZ9/Ka\ntzJdQAULF5Y+dyDf92D6g5F4FYmHGYB3VEMnyYCs8+4TJtn5wBXY4NIOz4/O\nFRoyNQ5NbnSqoVIEGivNQHkN3F/0KiCN1+9JYOBO7Ujug2ShPnrl7ZdtUIuD\nu8+VY5FpC4dd/ZuSJoT6gOhUyuTr6lyw6V45PRMKkLdyFIgl+i3N2yPED8U2\nio457LYcDfxSzkZR6574QxWa03sgBtlODi56wA/Qk7Lr6SDUnJfY6akZmZMD\nLCj+oYFU9XSKottq1O2/x4QdKCOssisAHRM7fAbiwV5ad6q/g8CQNc5SZU5J\nw5vNw4U8xipAHhW//gQmQKipE1Qlj/bYFRKDFMrg7x+qAcppMXKf7z/eQPL0\nImOx\r\n=H+eJ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"468c59d0f2645fa46401738bd2e3dcabe6a01b8f","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-07-16T08:11:22.030Z"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.62","@balena/jellyfish-logger":"^3.0.45","@balena/jellyfish-metrics":"^1.0.311","@balena/jellyfish-environment":"^4.3.13"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.7","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.8_1626423194782_0.3750173072859817","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.9-remove-whitesource-805fe59d8082209dfa2e29c0559b16652931702c":{"name":"@balena/jellyfish-core","version":"5.1.9-remove-whitesource-805fe59d8082209dfa2e29c0559b16652931702c","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.9-remove-whitesource-805fe59d8082209dfa2e29c0559b16652931702c","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a6edbf6879a339a3cefa7116ba2ba2bea0a85271","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.9-remove-whitesource-805fe59d8082209dfa2e29c0559b16652931702c.tgz","fileCount":196,"integrity":"sha512-+gafwBHbnY6S9W8m88NzzvlVq6/RU4WQbVzJvykJ3lMgI4dgwPKdjda4GI8AxDZSbBWkrstdILChFxwynenYRw==","signatures":[{"sig":"MEUCIQDoBr58C8qwjTjd46mvxVGqTD27M93LSs8q/PS3iOiBAwIgX+zPgC/vnZZ/kJkaEgDzmjYXJC+qladGTL8TQBsV6eo=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":731861,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg8jbgCRA9TVsSAnZWagAASlgP/1IiPhdP2JjjK56CPX4C\n8jh58KMcZKbaEiN5oDcc6GgaRul9lr8Bvb2VolnN6br/AeiA9LlHmxz7rg0w\nrJv/URmQqlUM5Cd/1Jbv6DQG47xZg21HGJqHZp84LMqXJBUG3qWcU4FDF+cW\ns4de23AT7ohHvapft6ThqMqICQ34qp+mvwNmWc0Dq4OY0l3szCRwgrzJjSfo\nuwccrIm4wg9KYy/Z7CER3gyOt2krMlE5kJtcQxBrJ9Cd2DtOjimtRyLYhdZ6\naMaK6oy6WmOTAutjKVUSEXJk5TfwLKKh8cZsNxXA4R+j2D9aRGFfrXLfvr11\n4XGljT+DMqG0J6Ej8wOQZ2pHgN1li7MeEkGZI8/r8hFALyZKADrLsgOzRGsk\n2DhZBesaHcR0I/ZCWjYEUAw4IpsXg6nAeeUeUzzVwfpda277lea0GXH86/+Q\nAOJFms3XEz6VzqhYgLz1hUMa/F2lBeelP0jX4tx35EXqfxAlm7kMPEfxubpo\n6IFR+JM1glKZTfcgtDHdo7Utsz+ytDlRMmCQciHi5Zg3FP6o9KzX5DjuGBs/\njQca7eDxpM1o13CNIcQXZRnGKH0t5a123Sytx98shmMAySpgss47vMPZ5upm\nSHIhChZluCRBNrQQZcJIqNc7jqog5a/1Nwyzf4bjyzD7px2ims8ZjWmZYyZk\n4ryB\r\n=zJuL\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"805fe59d8082209dfa2e29c0559b16652931702c","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-07-17T01:46:02.197Z"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.62","@balena/jellyfish-logger":"^3.0.45","@balena/jellyfish-metrics":"^1.0.311","@balena/jellyfish-environment":"^4.3.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.7","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.9-remove-whitesource-805fe59d8082209dfa2e29c0559b16652931702c_1626486496379_0.8598286373673849","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.9":{"name":"@balena/jellyfish-core","version":"5.1.9","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.9","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a80dfd662998cc66dd46b239ec16ced2ea378938","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.9.tgz","fileCount":196,"integrity":"sha512-M8+q0WkvburZQwUzKHIP9DRyNHvHfCmVQx90eLTGhF/nIM1vBXBcuF8RjzE/v67lEdbT6mhaJo4U2S5tf1LKfw==","signatures":[{"sig":"MEUCIQDKc7ag+hSFCGPXn9+6g5iOB/eehfJm8w0wM7V+UuuisAIgWtE2c2JLj8NfplOToKspeAvUSCKgOoJkum2hkO36f9w=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":731801,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg8jg/CRA9TVsSAnZWagAAQXEQAIVA8IaCT9XiclKEhubn\ng+A0O6JK8qcRsRm+SpsxHbb063ONqcW0/+c9tGKmP2dhHR/ulZxjplC1aRXi\nbrzcY22pDfwz8x+UKRngkc5KdOy61V8JKt/uRkiY+T9diAIQyWYOXkwevzsI\nKlRylD7ue4IKxMo0SuhyaJi0GP+pqeRDYunn/qVpd9MMsfhz5LDLk8mkQPN1\nPUfBb58/5NFUHJxOdauTsPMae61ipLNL8Jqj1exn7S4IDaG/pLk2uLj35g5L\n2N8Eo4U85taCXTTZt3eCzdwbsamfaTwe9quhsnewLq8TuHxvIm9lrUgCakZl\nkBUWYArs4B8GiwGX8YBfPD3aGTAFDK2ZsSsBe81pD3A/c28PvyFhTNqgmSFR\nMSuxrL/c6NYCUFyJG2rzGqe9TB7h+4wEhd8jEWTuENuJW5fD+XKqJqkSDugv\nsyMpgtyBDdu7/oe2IyOMNztHxdYWGwWSzUpEE19mYTPOjM+elerpobKp4uFb\nGpGdI4hLia2Gk0oIAcz9qmdyUDesBl+8MQkkOD/nOX6udZ5RgcY6wF33CQBq\n71EC/b9wzMPqMUxIBMDOSlKkve2IHm+n2MWG4eH2DoO6UVuUXPktVfQmJNl9\nP+sBgAf43AqiDnrn7dCHErmOSu2Fklb7SRxsGjYVU3UatUqvZLOf807IoOY/\nIg/H\r\n=hY/3\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"9d286eec1f0d7d5aca760dee6040b9b5dbba3a35","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-07-17T01:51:55.906Z"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.62","@balena/jellyfish-logger":"^3.0.45","@balena/jellyfish-metrics":"^1.0.311","@balena/jellyfish-environment":"^4.3.13"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.7","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.9_1626486847646_0.1941704982758108","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.10-fix-indexdef-generation-cb5898dba25a1a5507312000a508f5cc46500bfe":{"name":"@balena/jellyfish-core","version":"5.1.10-fix-indexdef-generation-cb5898dba25a1a5507312000a508f5cc46500bfe","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.10-fix-indexdef-generation-cb5898dba25a1a5507312000a508f5cc46500bfe","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f42aefdd7d28dab3c26eb38d7217c1ea9bed0ed6","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.10-fix-indexdef-generation-cb5898dba25a1a5507312000a508f5cc46500bfe.tgz","fileCount":196,"integrity":"sha512-gB9tVHnhLKUCzO+0R2Yt+P3udSav3HeX2mROWzZA8bBFwQTlK2GGimzj2S/KD+mPC6r2/hSfeFKZDzuEUbS5Bw==","signatures":[{"sig":"MEUCIQD/I5vNGXEC4wcMcgsf07467wjPWLe5IgODQAvCqcYShQIgI3zDHZXOAvi6HecKgpmVd7b1yXs8pMRT3a6cLQSoHcA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":734273,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg9Rp8CRA9TVsSAnZWagAA/9MP/2bGvvjYGqBjSGgUW9/u\nfnRdG9BKeMnbvE60/trjQdetED+jqlN32JCgdSHk984NWKAMueKH23/TZTNX\nynndk5YlGb4ZTL42v0GuuUCdDmFUr57YAO1OeDVf1c/42WCDb/J1NJogqGng\nR36FOoRV4BUWom/dWPLv5vGFF8TxMimMBjqTcf7DiYRMKRPJvZUBz3RxUVik\nPFwzSzCYbUU4aC1QWZwHd+2T1jODSBn5r0C6NtzT7qzrJ2YLX308SUjbGss2\nh+8M46afQ5xdGyc344qjgbDV7G98zIkJ/2CGRHx9NO3VIBGOREtULHlOXas0\nV/I6qsl0NfvMDRDMVBScBGLNwjZSbjUigB1J3V1KZtCrLT52y++Jt/DCEpKn\n4SRQrVIx84vGlVSAGDIMIPX7Aatb9yYpLa8m2ck/flgDzgX8IFy+UH/11dnT\nVvjX6FBQ5MjGhxngz4WcglrXnyEtkf5B7xg59DuNrWRSkxrBHj6030jUPbFy\nSBIw2VQQn+6X5ftTJQ4/0jOKms/kvM0fFWcnM4M4pZiXM8nLWCQBF0Qlw78+\nnLCQWeCA690zBNhqI6iM6mQ5okag8F+xfLFH/VTX6WSrO8WjuewkiviFKha2\nXrFC+oS8Vlnyb+KXDitl7Yizs/vDHACO0bKUyiBDrRdpU92UlDjfiNgBsz/Q\nLQ+O\r\n=BP1/\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"cb5898dba25a1a5507312000a508f5cc46500bfe","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-07-19T06:20:32.787Z"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.62","@balena/jellyfish-logger":"^3.0.45","@balena/jellyfish-metrics":"^1.0.311","@balena/jellyfish-environment":"^4.3.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.7","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.10-fix-indexdef-generation-cb5898dba25a1a5507312000a508f5cc46500bfe_1626675836723_0.3594670879848818","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.10-fix-indexdef-generation-451fedf208a2a83b9c360b97296eb46b078492a0":{"name":"@balena/jellyfish-core","version":"5.1.10-fix-indexdef-generation-451fedf208a2a83b9c360b97296eb46b078492a0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.10-fix-indexdef-generation-451fedf208a2a83b9c360b97296eb46b078492a0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"35f72276d2430a61ad11b4f4897fd7485e2587ae","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.10-fix-indexdef-generation-451fedf208a2a83b9c360b97296eb46b078492a0.tgz","fileCount":199,"integrity":"sha512-V9/snB81YBewnGEiCsol5aBcLGcZ9jQUJjJn/efrNm24mTQVxZ0mXK4TbJtAaFAmuE8gFJwS2LYgpJLA4+kM6Q==","signatures":[{"sig":"MEUCIAloZZS0695iGQ5d4MWB2IJ+Qg7MMxtEWPkwDW3BEMemAiEAvNpcV8VbqXdWrL3nBeG/xuyp6ZqMHNWUHn7H7tc1ru0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":739633,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg9lw7CRA9TVsSAnZWagAArhIQAIoxj6jSu+HOFvYuaYpW\nldFXZYsxQAB1md44QyuzCsZKxmhOJ5JeIvELetHKprfPG1Z7hG7FLFsgLN1C\nCkiwlUeIkaXxQp0SMifDYCNtYRkZOvcPLkkd4vW/zB3/ZzNqwbQ8SS/n9/SH\nDCE1CyXbVOSQ/Z7pPmAZhy2tTkYyHU6k9gTeOPD9atIlYxc7wii1tSCrmn46\n65NCgXEakIntsO2bJ9ssYQcY/j2Oa6Kg9QOb4ZsDap6iDQu99ZSulzKWVmkc\nXcKIpp5qHz1V9KSkPBpIZxpj+4iE1lRqwHzCpma00qGtVXBVhO5lu8OZyD2I\nv5n38dtgfnS7+CEORotfb0d97Jxqpa4hn7Gu5ljwG8z3GsysBKIQTml6eE6G\nRnOVUGNKZIB69w2+E+oLnqN77tqyKA/P/eC0sOALmTNmwH7cwVJNDlfypE1G\nmWvSqHPfJXO1EnmjfUfrPeniPI/l67ozq0Sd8LV/wcwbuguYIXlIM18LYcat\nBJX4tKjaF4mn/8FOyFQzPjJ8I31rWD+2NUrTPzJgN9TAv8WGdQgsISDxZTlO\nwvRB1LQ9RpV95ReSc00WfnUeyvUHplsr2UmpbIZ9hmKVvLobVIusO4o0SBUa\nDklNFP8zTt8zXYXKPx4eBtXYgcL18EVbP8juFtQtVdODZzMcciINEWsK/s1m\nH5g7\r\n=3zpF\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"451fedf208a2a83b9c360b97296eb46b078492a0","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest --bail test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-07-20T05:13:51.783Z"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.62","@balena/jellyfish-logger":"^3.0.45","@balena/jellyfish-metrics":"^1.0.311","@balena/jellyfish-environment":"^4.3.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.7","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.10-fix-indexdef-generation-451fedf208a2a83b9c360b97296eb46b078492a0_1626758203764_0.5475104130397075","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.10-fix-indexdef-generation-178cb0a6b9ac5aa45ae957881c85022635fa4c47":{"name":"@balena/jellyfish-core","version":"5.1.10-fix-indexdef-generation-178cb0a6b9ac5aa45ae957881c85022635fa4c47","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.10-fix-indexdef-generation-178cb0a6b9ac5aa45ae957881c85022635fa4c47","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"07e3b976194439050fa4c28d9e142ce80e9485c8","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.10-fix-indexdef-generation-178cb0a6b9ac5aa45ae957881c85022635fa4c47.tgz","fileCount":199,"integrity":"sha512-/dEiuqoC43oSIprm9NftWsucRB9as8mVGg8BxQXuDaAr06upYFfk4fEOFnkTDYt7pU7NDDT9sXyHF6mPtVvvHg==","signatures":[{"sig":"MEUCIEZfE5/FPeJhYz7JrDtTFpt/Bxh+QtBBd3bpnDKS6YWRAiEAowPgLi4kQxg8A/mqgvKSyNQWSKKIdO02LrRQ2TLNAA8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":736308,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg9l1nCRA9TVsSAnZWagAA3t0P/jiEPRr8mZA5pNT1uK1o\nPCEJx7iNUss8zNrbQfkx/2w1bqnCvLl53pxcZqjide+eT3MB8QM90YyZRUsU\noWhoNmyUPG2GTg/dvLj0/MXPb74ETsLuCgRFopnb6lvncEu1N1WSAOyCV8Of\nwU6H2dyl9bDbWush4dibTxKfJAGL9Wxzs/VLT0z3pOECDl2HlQZGqZUZPX0H\nnKZHuVy0DoF7SJsFybu6RdlpzWRD1ezITMqe5Aai6+qF1XCwlIRBpAHTZQY9\nNsrNYhDfNZYtiH0ce7fCeUy/ZtpAtgwq6tTLFhTeh20x2bNyfbI7MAMXlMCH\nBUFvkl15jomATmkSj5S0ryGn4VgRtBUPL7sxHv9wWVpB4DLLI9GhQMni8pwL\nYjr+FMndcy5s7u+lzK/Xjl0Lqn3PIyyjv4eDBL7FTAmTQSd0Y7RcCuKp6let\nYNzuFbcuNraj/if8Q8N+orgnKZHkiJlPDaB3McoobCeR+bPynXmuLlIWS/tH\n/uW09do8VvXza88dXqVEDo8qhqrgVwmaJBNkLCIjeQ9AiXwbYCL7LpTktNa5\nJq+BDB50lCBvWV7aSlIpiLwD2F9DyyjSyU4qiioQqn3ihz9PxTEBLMYxj/0D\nHhDAHYgA0kVBmo1l8r/Hw1mThr5KRQdOHFA0uveYAFv+ImK4vep7hOg6mVxX\nmAEL\r\n=6zfC\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"178cb0a6b9ac5aa45ae957881c85022635fa4c47","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest --bail test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-07-20T05:19:03.061Z"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.62","@balena/jellyfish-logger":"^3.0.45","@balena/jellyfish-metrics":"^1.0.311","@balena/jellyfish-environment":"^4.3.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.7","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.10-fix-indexdef-generation-178cb0a6b9ac5aa45ae957881c85022635fa4c47_1626758503182_0.9483641406279113","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.10-fix-indexdef-generation-3df48e9071291660c617ce556292cedb5dafd84c":{"name":"@balena/jellyfish-core","version":"5.1.10-fix-indexdef-generation-3df48e9071291660c617ce556292cedb5dafd84c","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.10-fix-indexdef-generation-3df48e9071291660c617ce556292cedb5dafd84c","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ab1d53186d55c90a0b027256fa813879474ca7df","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.10-fix-indexdef-generation-3df48e9071291660c617ce556292cedb5dafd84c.tgz","fileCount":199,"integrity":"sha512-dRtNFBi3ap0Yv+txwb19flmc7Z3eCHht68q9eGqOYMwjgdHiePdGB2vJG3/o+tXMPNrOzp6ZE6CjNCRIidgDYA==","signatures":[{"sig":"MEQCIFFVg9rQ5HexfBTOFdlv6PSUaoCiFZUcPgcWB9qmSd7rAiB0G95HuC3Uzm81Qb7dgGQWBgumtnTOffpCDRLfhGJqow==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":736114,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg9l5zCRA9TVsSAnZWagAA9foP/i2gP72wWH9I1fRRkjle\nFqOGEkMZpO3R5TeMF+WVXbPtB5WhowJ3ZzwjuuvVntq7EG06wcX8k6CRaxU0\nRrW+Qf87UQzii7FdgZ0ZmAUaDxJdL+BFsKCS1ewsCPaLkHLWGr+IE7AzSoAM\nHypJq5S6p7OHJbZD97r0uwRI4ZJglH3C0C4km2eQLdhNuibDzd8DkYWJiQyQ\n3HyGh8BOapQiVnOcfO4GK1CJpgEnJc25sfC8JVDl207FvceDC0czUV9rhWEF\nLvx8QLBBsplNSNGBZXNoKR5GGbY2deJfdPJeKO0/W4J9ShVqh+dZ7ewlmwJG\n5ZoYhkj/DXNXDtgYkcFRNrwi6IyPGEYGx1CxaJBRYGjLw5tmHL8SYMsFM6zr\n10ytoXs8OXwRIW6Bydzct3/EYPPimlZtzT3BPBIj3bgbeyjD83T0h/JEvtY9\n7+BpodrTKhKhkjaLM1dJxBKuc9CmgtYzcTcRoWuCU4ybyPjgGlVwP9eoyjKP\nAekP6T2XnjoYazH2LiBdSd7kL8x2XBuayLELhK8lt48MSj8PCjCfE6gHAezE\nndg8brM+06MTJZLB3AyM3GmFi1A7ehAc1wvPnZLsk4nyYnrliFzVQmcKPheu\n9+gg0Db1H59Obnlva/av75NDoXL7k0QFGocJf3Fs+oA7jzsyrHf1d2D363oN\nL333\r\n=wEx8\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"3df48e9071291660c617ce556292cedb5dafd84c","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest --bail test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-07-20T05:23:18.455Z"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.62","@balena/jellyfish-logger":"^3.0.45","@balena/jellyfish-metrics":"^1.0.311","@balena/jellyfish-environment":"^4.3.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.7","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.10-fix-indexdef-generation-3df48e9071291660c617ce556292cedb5dafd84c_1626758771613_0.9500026037928067","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.10-h32-fix-json-patch-path-escaping-b25ff18d292d1ec67275e639eddf1c046728e3d2":{"name":"@balena/jellyfish-core","version":"5.1.10-h32-fix-json-patch-path-escaping-b25ff18d292d1ec67275e639eddf1c046728e3d2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.10-h32-fix-json-patch-path-escaping-b25ff18d292d1ec67275e639eddf1c046728e3d2","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a7be7dd44f09c4c220466f8e3d48f92f302a3623","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.10-h32-fix-json-patch-path-escaping-b25ff18d292d1ec67275e639eddf1c046728e3d2.tgz","fileCount":196,"integrity":"sha512-xJk+gfNqqF9pMxTjlq7tLdKnmubacB3cR516862KzWpksVu88NGZBKKAYprDBbBj2UjWg+OZGBagPIumJvrQGQ==","signatures":[{"sig":"MEYCIQCO36BNHLQukl17APHaRBI1fXLRni6pdxGSmHN/jt4TkQIhAJ/dywVtVlzv05oiNdt4iu0WwmTJEOX0jmjB8TYC7DjJ","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":732210,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg98mlCRA9TVsSAnZWagAAuO4P/jOxvyprzqRPM7KtTvqw\nUdlRvlYFZNWVJR1oxbUy8rXu0bClXhmxzE2rDjDKUxAxIHiyXMheOXODtpHc\n5YoM4arQ3sdXi6uhx5yYebGcb8+TvzG5o2CmeWs+RgI3T8clXMIMGYHauL6d\nYriJsJSj3VwZ30sCav/hU+Au25bolWi9WPLDYe+R6AKBCKANx8spDtO7YeP3\ngknEWpRmFYuud1ASLk2rbDZMI3rC9FK7hzMhyQVDUNVGIVJ80INVOadzL7Pj\ncFdMAoVBL5hHlszqh2fQhP/DBgiK9HRxikKUz59cH8Ah0d/pPmPPKwHLZMO9\n1q+0AufayRCcfD1zr/V3aUwUc0RF4eE0gUMpbCzsixxJBJ81+P33Jkqr+iAP\n3hqiGFNvhBm7+j5NjqwyKFt4h3gFOXRLSMYXyTT+tENsUphmkN+cZVEpXYQy\nZN32zMt0pNS4e26x7EGoNQasGUms3L9UG3nsef6lehYePVVTMe7nRJtrLflE\nnsRgjkns8jHYne8yruTcyBOxKBQcrjy0EC8a1h3t6evS+7fTJZTi02e/AHIU\n3BcgmPVwappDwuJ1FwwJr47zW7nNKLiJlvUXw2SbbDKshD2IrlVs1wLWtNxE\nxtoFbzZhzyxCMv9XsO6gcJT8uNGjeWNdP0VqaMBg8fywmL7lDD/I4+8kbzu6\n0Peq\r\n=b4lT\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b25ff18d292d1ec67275e639eddf1c046728e3d2","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test","compose-test":"docker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-07-21T07:13:12.603Z"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.62","@balena/jellyfish-logger":"^3.0.45","@balena/jellyfish-metrics":"^1.0.311","@balena/jellyfish-environment":"^4.3.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.7","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.10-h32-fix-json-patch-path-escaping-b25ff18d292d1ec67275e639eddf1c046728e3d2_1626851749208_0.3818330979804003","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.10":{"name":"@balena/jellyfish-core","version":"5.1.10","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.10","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6e7c0479d1e935023916db2ac039b742fce6b318","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.10.tgz","fileCount":196,"integrity":"sha512-3CZcPEdNvtzfxQW2D5pDl7PQP10b6dj0DaV5zV5XLfV1sOcfVM0sp8K0EfCL/bXH1mFKnhnngImSyliuSD7s0Q==","signatures":[{"sig":"MEQCIB+eqOSYZiHdjboWMxEzT9ZFnmb8VtQ0ALvdKl3NfhDeAiBDieSMl2A+gkZ5Ly6HFRz/bAGKnr5YAqw6kd5UABJWbA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":732136,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg98rlCRA9TVsSAnZWagAAKcEQAI9hmM1opsmeF/OZyyhS\n5vB+amENnCPxPsno4nW1L0OMifQl91BwJdyxcQtROXGNmpzUdbpJT/NFV/qK\nCImz/jgz12u9LIasKpgrwV4/hqZDwA8kLAM/ZcSVFQ1ebTxNxgI/iX/jJ+k5\nfLWSbJabOBSh8E2E5wu2S1MnWybBdiZT2rMRDAWgKABQsIPgcmGv0okM/UOK\nxEg+ugoHJ8BpdtMRhymFamPXZfMRUW/l6CwKf0emJs0HT6t8B3PirkLnQozy\nOV/v5RCXFpnc7sMjtdRGW4py+EDdIOz7+MR/Smxt5HCumeE8zpWgOZw5qkxQ\nGzhFRaIhTBIzIB/arWWAOhYiBEAqhfOKgZkWLPmHLKzyJHhJ8CayQMQ5IjxM\nx932fEwx1wAyM6PMOD9k3Banaardsm8/nNflTFesofWhQ+QxvW7/KP1xocUL\nc6p9W1fFv6Ycu/JiJcSAHXSfkJKBXbbFTnSDIoF1+aX/98pZetY8r6OCh3fs\nL2DhjG1gVc3pNEyd0wOs4Job54FH0h9eT0ZRKL9IOTzFZEq1V/eFVO1iRHTd\ni4qIWPGEbOYQSHIvbTcRvC2u/43cm1NipFKZWwawakjjv1UEmaxVntaZeegT\nyIp0TMabuVq3SH6zlq5UOPndRvk3dNy7Nf2xP+mzwTzDhSpt6ZIm+sMozm7X\nE7Et\r\n=nDT/\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"9734b75193c5a8dc171f3ee1861cae3d06d6ac6a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test","compose-test":"docker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-07-21T07:19:27.749Z"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.62","@balena/jellyfish-logger":"^3.0.45","@balena/jellyfish-metrics":"^1.0.311","@balena/jellyfish-environment":"^4.3.13"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.7","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.10_1626852069251_0.8571878207955492","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.11-fix-indexdef-generation-4349afd1c172b4e2661710f25b99abb8bf738d01":{"name":"@balena/jellyfish-core","version":"5.1.11-fix-indexdef-generation-4349afd1c172b4e2661710f25b99abb8bf738d01","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.11-fix-indexdef-generation-4349afd1c172b4e2661710f25b99abb8bf738d01","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6726608101923fe921070571ee139a1d6324f51f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.11-fix-indexdef-generation-4349afd1c172b4e2661710f25b99abb8bf738d01.tgz","fileCount":199,"integrity":"sha512-ikXDRt3YEww+YWuV4sqFZ3t19lI4bfP6ijB93he0xcV2uhK/Nl/xdPsX3Xfr9Lxd3qxYTCCajK2LBwgDFKN1XQ==","signatures":[{"sig":"MEYCIQC1+SE01QfMfa7nbbgG6dA5VBWMpj5tK7fdG31oo4w/RgIhAOgJGxOgzklFN4sRQMHEHrSHaEM2uG2WNFF5kE8M/xSH","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":736448,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg989WCRA9TVsSAnZWagAAwK8P/j1QQw5gAi5zMED2zxYt\ngGUq97+aUi6jdJcpJ6sjEUuR7kbnAdRImAAX5963r5JyVqYxk8b6NqlquC99\nbdF01UnniLcb3SjVPqgHGtLSqK2ZvfJXkyjF3VqwG8I174+/lHxjgLXX9Ngs\n9VvAgGhzhihi3TW0fs5ZkZGQ/1UW9a/bSJ7WyU/QddMZsi+HCl1DLsQhgx+f\n7HIMbmTundS00BBnx6AE0cLNujHLf7AZjzu5514mxfFFtX+jW7aqu4kEeS03\nv7wQrd0t9aSqrKchIw+6Zzqri2Z/emuG1mteqqoHeKVpN1p8tkYU1BxA/ZUP\nnrByRibezE7VFr4Zv7E27qaEn5F7wcpb8j9wqh00NvoN1ogPzN7TG4qeTEB3\nHxM/KM41VcnzCHVGGpxKl5MuqFqbZ/EUEK/hn6/n8Px9xT1mpZq1b9uSLu2a\nCbV0BAVhuxmFOIT6XunA8MhAhpnsLlaPYn3HEyiYBw7gQL+Qo8YKuMqvw4Iz\n6uKEJDVvNYc/+jRI2x159gwYXwAOvTGk4GgUIk0wxOAHDOJPtYIk6a6Qrjjd\nyGit1WODD80+843Fg9RmdhYAya80eH+Jgpndozv3bc0g12ZsN3F8cX9c7lpm\n5UP7dDVgApLNG4nChVgx5NQTWCBSiTN8nFkS2gHiGxJjUfsFSkcENro1rrMe\n4jXb\r\n=okfo\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4349afd1c172b4e2661710f25b99abb8bf738d01","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest --bail test","compose-test":"docker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-07-21T07:37:02.353Z"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.62","@balena/jellyfish-logger":"^3.0.45","@balena/jellyfish-metrics":"^1.0.311","@balena/jellyfish-environment":"^4.3.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.7","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.11-fix-indexdef-generation-4349afd1c172b4e2661710f25b99abb8bf738d01_1626853206220_0.5420747314348078","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.0.0-h32-simplify-patch-validation-e642b2abeac819453f3db06f9ba10d34ff70342d":{"name":"@balena/jellyfish-core","version":"6.0.0-h32-simplify-patch-validation-e642b2abeac819453f3db06f9ba10d34ff70342d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.0.0-h32-simplify-patch-validation-e642b2abeac819453f3db06f9ba10d34ff70342d","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c37f01c46e93724e33a2c4e75ac1506a8503a83e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.0.0-h32-simplify-patch-validation-e642b2abeac819453f3db06f9ba10d34ff70342d.tgz","fileCount":196,"integrity":"sha512-4ct403s0uroSOcIvn4PxW2dkZDaLZriZT0KzAFHJ3QYzikYsLQF95BthKYEGJdxaj8Pha3WM/kwD4P4uEcZQnw==","signatures":[{"sig":"MEUCIH5pPYW6he1ATT80E+uRX1r08EwDWGQUXrTUhrkLC4QQAiEAyAVt/HyGA8kOi796ADNKIQ82yvtPtDz+UgtRzaRTAMQ=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":731126,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg+BuJCRA9TVsSAnZWagAAAzUP/jN9k+L+ijFThjKf7s5d\nwTkBU68yvmfXAFRomYTV8J3A/z2/3AAQC+uUtIQYTQhI1HW9cF9aL354Zxwn\nQy4hddTSbzi4dEaNBwrDKyUpSunT49orJ3uCb6uEMfxgVLusXoAkZWZOTF5L\npIWXWEFVm3RCv/4YBCgTp9PEBAMlF8g4sibWwC8+r2SMbCXlmoV2YAWYFBM/\nyOl1uvTddbbKp/JwSHfGfdjotRAl8xpDfyFScKM31sI6ppQWxIUp31xa2jKI\ngHrPZvtqKFZWLDMLFqGIADYWIX/a39jBb4m5rkG30ilPgvUz9UgP9d+OtlP3\nAKUyIzqXsF/PStcNlUSQ6ltD7nfhl5BGU6PJ/X2y5s8QzE9YU5MMO/nq5YxY\n6BKle40OnsrSXZhpSN/gdqD6r4xribWt3G6khN7qoC/y1Kw3MOU768pRKuG1\nDv8bdzscRGOFoJIMhoD2448tMhMvrXNj/MQdVKmFeuoh1VPteUZi7HAExkmY\nQbwor4kB/Vipu/WQzHuk8me1yCk7RTCcGjC9qwzmjYK29uPEdwC+lRDY4NoY\nUzg2JZtVo2Z9X3Gs1HAUYGNR0fOxmgbR4d2OUXw+2sJwQW0h5tX9mT6Dgaig\noD3lXlT/3h4kBaccxGktoXHviG3adrcJ8tgWa2CzUKaPTRoflDyLcR59FIki\nmWPo\r\n=PG4b\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e642b2abeac819453f3db06f9ba10d34ff70342d","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test","compose-test":"docker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-07-21T13:02:02.519Z"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.62","@balena/jellyfish-logger":"^3.0.45","@balena/jellyfish-metrics":"^1.0.311","@balena/jellyfish-environment":"^4.3.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.7","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.0.0-h32-simplify-patch-validation-e642b2abeac819453f3db06f9ba10d34ff70342d_1626872713235_0.6336380379415096","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.11-repo-sync-jellyfish-config-default-6bf40d95b102c294a16a061974cc90d27f90c946":{"name":"@balena/jellyfish-core","version":"5.1.11-repo-sync-jellyfish-config-default-6bf40d95b102c294a16a061974cc90d27f90c946","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.11-repo-sync-jellyfish-config-default-6bf40d95b102c294a16a061974cc90d27f90c946","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5e5cba01fe73891072b4880b18d4ef11ca4145a3","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.11-repo-sync-jellyfish-config-default-6bf40d95b102c294a16a061974cc90d27f90c946.tgz","fileCount":196,"integrity":"sha512-irvjJPVubq7ClSOB1gy/BL7PS/KboQuRKKo3y4H4TXbl8St0uKMk5xUWbW5wuoFlmVwtUKqKHCclVjQA293nFw==","signatures":[{"sig":"MEUCIQCwGq88IwPHvfjbsl/zVhPcyA4o1oqXgQCRrNipN9hLUAIgSaDye2dD94xsNLAAdncrIRDRQ2vHn+ut7p+PsQQ7eO4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":732308,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg+UexCRA9TVsSAnZWagAAd3IP/iGzigBEPRoTu3Ldoe2v\ni8PSAaMD/ZcuxeElXql7vklzueHGtxeoPBwZCLiGtVsnORQCCQYa5qnEgJPu\nIiQF7AznpUga7iVut0edvOkFaaO1+25H2kBREYYu3EBTITCyjaXc40ot0f+i\nN0R6XyG7jqyB57mpjWW4T4j1ImLRDEYldn9q+A3SEGG+oSObkFka18jtq1qz\nMYa7Id23Q6bLCL+X84bCG7Xw3b9vHSCpQqUtrEAgewMnjO0GkzD54nf9DLFu\nhi3mxbdyYrBDnOu2d0rhHP+rR0s7DinnUdMPqwi8tnboRHpZL2cQp77i4XBP\n6JquFBT8V1i1gYj7YI0d0w/18MMdu3USNvZp9pcaxwRl50GAgYN3H5Museoo\nuZLTl+3Xu3EsJPqXvC3f9SY9WhcADoN3aDSwJ3uc6BsMf+prhpNxC0hvbqSD\nzEQE3b7w/W9TpPtdyY3gbR9aLoM0tcBobUYEI5vICYxLnq2ivNH+p5IVhJia\n8BU4AwxquQcXjLxFHdEfrUu6kBpeLW5Ue1kmE6B0DleLBFf7qU6O+8r4oL7B\n4oehFUTS0Zz7YMsvQlHOmWfOnmhl9Ug/c7VHiCKMUUmjimYtgi4qV9w5l6rC\nEtZcdZapDTyE3ClGM3xrD/LQzZDm4bNQXVbnwrL9XWLATyv0KI1wRJ+5D0yx\nAONv\r\n=Li1K\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6bf40d95b102c294a16a061974cc90d27f90c946","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test","compose-test":"docker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-07-22T10:22:04.119Z"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.62","@balena/jellyfish-logger":"^3.0.45","@balena/jellyfish-metrics":"^1.0.311","@balena/jellyfish-environment":"^4.3.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.7","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.11-repo-sync-jellyfish-config-default-6bf40d95b102c294a16a061974cc90d27f90c946_1626949553693_0.6218465569011669","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.11":{"name":"@balena/jellyfish-core","version":"5.1.11","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.11","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"cabf73736edc63ddb6f28cbf80c98721a3009d1f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.11.tgz","fileCount":196,"integrity":"sha512-yVgQGIGnF/i4kWXUI9qaxZG4Vu387nLoyf+iWZx49GQ5hISxOaAbeXhNpShFRazb11CyNa2aq9ayXZ0oGrOvtA==","signatures":[{"sig":"MEUCIQDfRH6dK76O0Rm7RIJPNW88/K7vxvMe9r8kLY6hps5YuQIgZK7qY7C/LIEuL7rHW/Fp5AY2Lomoty7YtPvb60Of84k=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":732232,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg+UlJCRA9TVsSAnZWagAAJTgP/RvsmCkNR1xt0T1Dy5VG\nytUh+VIweePVJjqxFU+3wUhY46Q+NEqYQ8pT0jobqyFslqyuObImrgKId8uP\nk/xBkhqn2QuGyzzomqvl/qsxEyS64dMY/GhOtprMT0I+Ms4Bp/vUD8K0R7dr\nnaxIkpFXjc6xdDramwAokwTBX1yllYRWgT90ZaKgJhJbcMF5kObiRlFFrV5d\nNcjxlk2KO3AtXXzts2Jd/+5PtN7mdTRx+X2zzeDODC7O2IzEPpcWyZWW50e/\nwo6Wm1gp8PlVhs1OuqY/dnmUwCf/abZG4AKCxF7/zedp5BU5vxW5hebF1t8Z\nh7mSF5761ZyyWwu9Q/jSJclXsL4838GSXG7UJfa8Taa5T/Mt/MstavuXarNr\nf2x20hbgk8WIxZE6Y6T8mJQoVdmXi1HIPi2fmyHEgLztKlEwclauDVNpLf4g\n2/gwRz1wkZoJfXu0lBYqyKXVM9xaTuuxKdze9tKomVIY5XjfJUHM0KKliSzX\nvEDcOTDFSFbNq93vk92Gi6Qh7Ln9dRDsQsBj6VjqYluTWxQoaW/vgABRaGV6\no1RbeT/qsCqwYfQL/dZBmhhkVusYU0GODIL+pYVxBuSREa7hd/QiMmNWOkZp\nTLJWY08J9X8HafRE2CAXWFY8uevsKZU23lkn/Qc9jtgxoogscLh0tZadbl0Y\nloQQ\r\n=B2/D\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"eb46b4fcdf56ba63875268b95c3bf14a052508af","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test","compose-test":"docker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-07-22T10:30:52.247Z"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.62","@balena/jellyfish-logger":"^3.0.45","@balena/jellyfish-metrics":"^1.0.311","@balena/jellyfish-environment":"^4.3.13"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.7","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.11_1626949961261_0.7220867062475937","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.0.0-h32-simplify-patch-validation-5115f91ff00950394b152e0fec026597677d2e32":{"name":"@balena/jellyfish-core","version":"6.0.0-h32-simplify-patch-validation-5115f91ff00950394b152e0fec026597677d2e32","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.0.0-h32-simplify-patch-validation-5115f91ff00950394b152e0fec026597677d2e32","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a9e3e68f01695ff4f38f93702eebf734258736a9","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.0.0-h32-simplify-patch-validation-5115f91ff00950394b152e0fec026597677d2e32.tgz","fileCount":196,"integrity":"sha512-2pAE++BmCKuzVA2GtmnigNcAnd1WWT7xhO+BaIdWL8auB6Leo7VFIjd6drc852BkaVplYQVRZ65pYmW9qrjmaA==","signatures":[{"sig":"MEUCIClAFfdRHiJkHjbeQ4oq/p9r8sdgOSBku5g5EwmdteDnAiEAuObqo5DdUcDz68VJTstpJYpk/C8tbtrOP9zg7LYXF+k=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":731302,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg+YOeCRA9TVsSAnZWagAA8aIQAIwGEQDKl91NeX4nGqxQ\n3nYZbsi7gHCYpHUOE2OTdcD5TD7NBJoeOpeGoCYXW772i/iSN2M1avTdMstW\nOG2z9GMLdXWLUMJCiGVbkn3NfzSvs9Vt9+PmbswE7DcBK695Arm7VphvGOy6\n6LI9FtJdL5WxLfj58B09zGOaCzFINRF54K8Rphr5lzq5LAE9A9+qe4fYP6H0\nclwFblnJBYRbaObN09xILS4g/GBQTVUXKpWSq7ruXbmVoDq3Js/OUBTYOmqt\nb2Y0zrrcAQrsFwnksF5PL2fPWMFtm4tcE3v6o7ZRXf17ieXT70n+cxt41ydF\nDTcqSncuUd5KC8zCF61Lqh8zdtyZmGyz0SMq3FByuvOmiaBGhcAdRcMxPWoZ\ncMz5w/pkxVr+Q1btZa+tNir66dJd74PI1HFJQpq0jrVolojka5fxY75NHTXR\nFTqQdBkV0lRzdqDXiN7d5g8uXXGbfCOPq8roAYfdfzBJy5t6W3Se5u+NkPGl\n39thrjNlspW8w3Nfqgp3rjDjh8X6YQ908/MJ+4T9Iy11+snpA3TMEgARqzd6\nkL+lXqi4dUfDBNkODa+X4rZcbIolGlB0VczXihLzEDJ+bcN21iZx+pcgfXIT\nDANbsIU21hYFIxazbO6tYHQAuc8JSj00hTnzYWYYHUu27+abZNC24qRDFxfo\nKZl8\r\n=yaEd\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"5115f91ff00950394b152e0fec026597677d2e32","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test","compose-test":"docker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-07-22T14:37:42.841Z"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.62","@balena/jellyfish-logger":"^3.0.45","@balena/jellyfish-metrics":"^1.0.311","@balena/jellyfish-environment":"^4.3.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.7","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.0.0-h32-simplify-patch-validation-5115f91ff00950394b152e0fec026597677d2e32_1626964894653_0.7230184156627402","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.12-fix-indexdef-generation-2506c0fa43779c1b07252fa2849f11d37e197907":{"name":"@balena/jellyfish-core","version":"5.1.12-fix-indexdef-generation-2506c0fa43779c1b07252fa2849f11d37e197907","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.12-fix-indexdef-generation-2506c0fa43779c1b07252fa2849f11d37e197907","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2175d5a0b0ed8af1cda75b465e59c9d2b30ed0f7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.12-fix-indexdef-generation-2506c0fa43779c1b07252fa2849f11d37e197907.tgz","fileCount":199,"integrity":"sha512-TH/gZ/tDHDgqWPeVO4sFUUQ0PM+bTe2G59GBgVMHY68LQ0fRwZ0eXUqCCHtdmdf7M1A0ndxBDOkhG3KkwTj2jQ==","signatures":[{"sig":"MEUCIQDS3RBg2gOVG0EVsHGIgp7MOftBQLFX3+y8r2oYRDhbyAIgBUnOIOurRsIUbB2XxslCRuGSPCW2JOqrdOdhmsIhidU=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":736544,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg/faUCRA9TVsSAnZWagAASikP/1cTYMWbLFehd+dzrnIE\n8L/DP44iV9l9vOBg+GF71ZsbkAqSuha9cx8v3Raz1lxCG8HAg5fK+ZhqR4Kq\nDInRkaxJbCb0Vnb/CT3yFNv1eNcAGqzefCYYMEn0iWfBMMeYq/NiXmEZ1QaC\nOU0E1TnVMBJsilDDOcSzIAAiKNWbDE1ZbefTEWe5LKNQIjISZEjc6hHzqt6y\npj0hsrZFbD0Yp8FvvqI+CZCVN0eig3DJLL59/pIzEsbuB3+LuSSrDKd4iexF\nOyeiONToADgNs9PKhPi0J9Klvzj+wcdrrEUUH5gg5Y21wAWX20DlNEqlS4fz\nThv3cnwwSDqPRAlNMVyEHk/3jkPAuegvK8651K0KKcT2QErYr5G/q/WrNZgw\n0ziSpeB78dl5+yZaL9kbe8tzl04wzHZpRtytYheNz9LZ4dC9LaK/Qeo5AMXF\nKBT6xvR3hHv5CVJvv1wTWR9AUp69TWeApe9Gwz8NXVOUXsJopmC3H7+HLq/D\noYYQvjLcjEMvo9XhDwdY28eISFqX0wag14UTwUSc5l1j+7PEPzJevpzkIsce\nFAexfwCLQT8t60NyeNPDHm1W0x0LzIBLeCtACegEz6G1rEImWW2ub82fBQMw\ntJm3/I+bBshWK2RiTb6qgES8VqmyBlF63Uqax90d0cCE5fChbZJu2jzgVFcA\nqGMB\r\n=he9g\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2506c0fa43779c1b07252fa2849f11d37e197907","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest --bail test","compose-test":"docker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-07-25T23:37:42.225Z"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.62","@balena/jellyfish-logger":"^3.0.45","@balena/jellyfish-metrics":"^1.0.311","@balena/jellyfish-environment":"^4.3.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.7","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.12-fix-indexdef-generation-2506c0fa43779c1b07252fa2849f11d37e197907_1627256468531_0.6826336264118147","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.12-fix-indexdef-generation-d7f3ffcb4c7cd539e9c97ea4bc7e03c8c71a72a8":{"name":"@balena/jellyfish-core","version":"5.1.12-fix-indexdef-generation-d7f3ffcb4c7cd539e9c97ea4bc7e03c8c71a72a8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.12-fix-indexdef-generation-d7f3ffcb4c7cd539e9c97ea4bc7e03c8c71a72a8","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4c0725717192c6b1619bcee667755a7670484bc3","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.12-fix-indexdef-generation-d7f3ffcb4c7cd539e9c97ea4bc7e03c8c71a72a8.tgz","fileCount":199,"integrity":"sha512-zjwsWTT7jRFeX5e7q+b7WMjzVrAL41xP/3KfKqxEqTGhP406i43C/KoYgg3lrqP9ky01oywyx3aJ03tI6wjvwg==","signatures":[{"sig":"MEUCIQCRrd4bNx8mraY9e7CsfJ7BmM+o20COJBmwtyAXen2xBwIgeyTmYYRatcqd9z9xO04nc4dbYhfBlo6IDy0Tm+N7eXQ=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":736469,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg/iv5CRA9TVsSAnZWagAAaikQAIZLnYeiDvzC321AWD6X\n53okV5LmTNCbiwAnOa2RVLtPM/PLvdCKj1Ko77GWvPfk7fvQODOcLWcbrYkf\nVlBB+RI5/kdaWr5RVU5kwXPKbOIGtoaEBaMgEz2+zci7nuLgkX9OWsSlBBc+\nEJqr0uT/Fb2YxpCWL45NPH1HVFecAZpOIpVak9s5KnNd9cxqg6Z00CQVwYu5\n20sgroLs4xv8zAOt6dXS0NVP8+vUZoq1xsH2XRbUJdRowG61xSIUaQzGIzFS\ncg083q0uT3GUQGxhaHPrcrJxU9IPDKDRECw+WafZY//0Vs1sGQg8HDn3Ju3V\nC5WcfTOoJxhIJm+af19UA9y+QbofgitI7/XKsJLudW20VpzFx0S2lZOrPubV\nU3Tf1QFhxixDLqtGJ5RB7czg+dT5wU55M06vRHeBhIna39cmUHxb2/cnVTIg\nYfo8Hi/K9MihFhcT11fQR67VCnpvWtissAWgNozkyZpNUvpZsgAydU8DT93o\nRgRyajnT0OcbWtf9giq7EAD20qsCmEnLhYs4sxi36us/cP4oDEQJ0DMth9gK\nU7DNovW13xGebNUuRqL73LENTke9DqYNMYiO29+58w9m4/3puydB4ang39tg\nD+lbJpTXEm5w/Dxl2UXXa5Oa0FfrX9GPmKpTQpKa6vecIhfkUWFGao18P54z\n0BJy\r\n=/6Iy\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d7f3ffcb4c7cd539e9c97ea4bc7e03c8c71a72a8","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest --bail test","compose-test":"docker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-07-26T03:26:12.317Z"},"_npmVersion":"6.14.13","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.62","@balena/jellyfish-logger":"^3.0.45","@balena/jellyfish-metrics":"^1.0.311","@balena/jellyfish-environment":"^4.3.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.7","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.12-fix-indexdef-generation-d7f3ffcb4c7cd539e9c97ea4bc7e03c8c71a72a8_1627270136842_0.1911242739289034","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.12-repo-sync-jellyfish-config-default-ff8567518fc7d9e27252703e83fb72a6da16bd72":{"name":"@balena/jellyfish-core","version":"5.1.12-repo-sync-jellyfish-config-default-ff8567518fc7d9e27252703e83fb72a6da16bd72","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.12-repo-sync-jellyfish-config-default-ff8567518fc7d9e27252703e83fb72a6da16bd72","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1487971ca43a6bb7c31d6070c8d590d239124e9e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.12-repo-sync-jellyfish-config-default-ff8567518fc7d9e27252703e83fb72a6da16bd72.tgz","fileCount":196,"integrity":"sha512-WUayehEVx1/3rHIGEr4jAjaMYVgO0/gU5XeHlb4xLSXU0LlMOqLE0XISer55uAQBRmo59St+2+woeTVfcuO8ig==","signatures":[{"sig":"MEUCIQD1eMZT2ONNiGrgLtCGThWavae4q0nTGPdEBTWtHhln7QIge1NK8s5nHF2b0ok8OUE25jGlHvloUxoKEBMo90OoBTo=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":732404,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhBKmICRA9TVsSAnZWagAA1jYQAIAhlfiiNBOOrV0EzP9L\nya6fH1eFxeaiHiv1oOQuGiQuat+GiMbrEbjsNrOI1a9fx6x0NPxO04fyJ0v/\nwpfpIZ920/CSf6i9wYg9f3KyXJOuW+/sQ81tV0ml4nQt6/alL6AAo2klfYT8\n8JjplFmmtDg+iV3Gh/CEsO9NJ7m+RY+BvEMiQBAypxJRZ4uq6ZD/P3lkaKch\nynwsHW4VFyGxYAy1T21pO7Wf/970vBzp9RwMhOtSC+C80wyNNQ3jwBSjeMrl\nYTndrIyRP6ttVT5slBJ2J9coAzC+bAP/vlseOgPYcG4eLWkr6ZLz1ZAPX9R7\nbH8Lxlqs69mLzx130w1KriNvXssmNSZCT8XQCwvHEv58K95lGj7e7fM01wem\nBKlSZ1WiF6Enj1lfxvWkXQpJjdrr/yDx/PvVPsc37Dp/MbKmmwd/xCKOD1MY\nMDprhat2R4IeL8+t84I6xEl+HLhG3j93b4/scQTjw5SZdoW79FxStHRpJros\na/gojkXGfC3V7mD2EW7oY8/aM+Q6+TD64g5tOp1vcCThV4nhcayEUoE0ymQs\nXhugC4S8Yjjvsssp9ycqWX70ozBMFwoLJDfezUb3E6nmiYfRizS585nu4EDV\nd16TdSFp7pQr9AV5rLq3S+m61wYscsrKu/omVBe/atesyHJUc0GtL7xppl3u\n+Cp8\r\n=3fVn\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ff8567518fc7d9e27252703e83fb72a6da16bd72","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test","compose-test":"docker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-07-31T01:33:58.661Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.62","@balena/jellyfish-logger":"^3.0.45","@balena/jellyfish-metrics":"^1.0.311","@balena/jellyfish-environment":"^4.3.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.7","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.12-repo-sync-jellyfish-config-default-ff8567518fc7d9e27252703e83fb72a6da16bd72_1627695496041_0.5873971564889118","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.12":{"name":"@balena/jellyfish-core","version":"5.1.12","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.12","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d239e61938cbcb04494531ac227d049253445680","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.12.tgz","fileCount":196,"integrity":"sha512-8aH5Etgo2cysoXVgxTvi2Gys+U9p8x465Qw8QqlEoWyiiZDveocwYcZZ9X2RwPOoljFjGvhvsvfHx7prsXYIMQ==","signatures":[{"sig":"MEQCIHSMvNQHfGPT0yKAX8rm8D7kr5DhtCD8V2VZs5TRmlJhAiByxGxDdLX96yS3GzAYhZWu2jY1o7SbXU6pGweNpNRCZg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":732328,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhBpDkCRA9TVsSAnZWagAAVNEP/ijFcxLDS8brduoJeI92\nj5fXM0whCUxuI3pIhl/7uJJJwib0EHsg5JzdydR2zRCtMhYcunuUxkDTiqWC\n9CWUqNbo1pqhqF7TuBpoe+iMrhPf17+ej67t9ht6MG/WoLQmC6s33xe2uMj4\nHecwpgvU0unRX7uZ8I40Z3dRldpGFSdoiPg0I3TA8QHv5EIiOcFZhcHh0fz8\n9UTZSnNDYHtdsmaR4JFbQZ+8gYPIDIztqG3ZaSsGz6dRz8x9Q7ZermrzTSy7\ng7ebofzydBhi/TVLHF4f3ZtVY+whSY8NkLrLKFZ6quzjQe58jcHd178Zp07X\ndsM1JBMK1+nTOYjtz2iH3CO+fwvRSXXV3H8ep17lAoTZkDPfXwPRfJLKcUHF\n5w0BG+XQK1gZyCInupSguIHVfnE5iAW3DcTB+8N+y+wqWc8Sxkxg823XQjPW\nLkpD/w/Qnd0qh8UKFSwoMI88VtRFc23N/yAzCSRhP8W0w8ImKWKr9Hveh/6c\nH3DJtdpq5FZsUFsqoj2slJ7a+PYXIqDdSdOBf5jIE87D0XjyAwx6p3l/D6Hc\n3X2pfr6RriQIaHa+zRfnAf8MhYq5qPF5YzZvOvMcXmAQOeM3/eUEw9qX6oe6\nwCew6u8GVpI4haMe2CIx+bT+aTZY1wDZaX17pL9PsQaIz19mj6Q4NSPLYNSv\n+yNi\r\n=woBK\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2cdec207c112de764f189a2f31db3ef0ac7b92da","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test","compose-test":"docker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-08-01T12:15:52.291Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.62","@balena/jellyfish-logger":"^3.0.45","@balena/jellyfish-metrics":"^1.0.311","@balena/jellyfish-environment":"^4.3.13"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.7","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.12_1627820260351_0.21870296166703307","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.13-fix-optional-link-nesting-103753962af165e98c2e38a1c1dd9d53d030ae4f":{"name":"@balena/jellyfish-core","version":"5.1.13-fix-optional-link-nesting-103753962af165e98c2e38a1c1dd9d53d030ae4f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.13-fix-optional-link-nesting-103753962af165e98c2e38a1c1dd9d53d030ae4f","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"17e649cce6abd7ccdd99c076eb0afca5b2018f91","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.13-fix-optional-link-nesting-103753962af165e98c2e38a1c1dd9d53d030ae4f.tgz","fileCount":196,"integrity":"sha512-Fb0WfoxL8PQFkiUBjxfaqUZYZfFW+Bm7SBZe7aNAVTZuNwGEEL5T3yfEGnuLGODQlpXHJOCsvCAM8LYFa6In2Q==","signatures":[{"sig":"MEYCIQCvpnJuI74QlNXrPgzTU7htzojdHR7V7DEvUydrXUPzIQIhAOHuJw6VsO1baOfA45/RAauMUviAWJZWesOdjpPSwPxp","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":732395,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhCEwGCRA9TVsSAnZWagAA+BkP/1rY+Nz3p3i4RifM06YQ\nIJDXpDQ4WxYtRrMGAgMGAG4tHKcvKqs7j4b0HsvvmKH/W8jFIOCrsxhlnIQG\nmeOCPD6ogRsOHbaOA3FPynTIo0dYxnY8zUiRp8X+/kHZJIspADZn9kOkDKGz\no1Ln2Qh2yllxjOGpCuQiK2FYhkzHofzVQWbBc6WTR2NtfT46QVABkCGKGSnk\neBoOblLXyRhFP+fN6X4NxBZVLFL9CyziSb7TiQjPXL9+W5XUz40T9ow6a+8e\nH7JwKHE+6Kyf6B0stLJzOHWA6cf/9OTnjtfmlagQfXACZUmauMw8B8w9iCfM\nDU8hzYlCw9WB5SLL+jMfBLKKLMRlQrSfVml/HaP4euS2BOUpmBT4SvDY8qLT\nAVTWOlYV9+LlsMlRk1bsDVIJ4QTC9qRo5HEkK4Hyx8noDlUs/mAkUmfamcnn\nqUYqCCsSw8z2OKuGwn1hjWUP0E26g0pNMkhfI1nBcfw7c7xwQAmiaU/TDKUi\nbmdGwbLnsjNRQ6zpNxdY525e5JLmUZFA36k3R5L1SsK0dUIz25++x8JMm7m5\nAPbAaFxQKwdra7HUYDTPyl+6wBRMf+yfhkWSAcTgaSCa9Cpa4W68A+SNQB2l\n65EsW8izvlA3x86n7leh+WPlQ46syYVPiC9n8b3PT9jeP06gI1BlIVtKVh2b\nfEsH\r\n=tgg/\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"103753962af165e98c2e38a1c1dd9d53d030ae4f","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test","compose-test":"docker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-08-02T19:44:41.926Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.62","@balena/jellyfish-logger":"^3.0.45","@balena/jellyfish-metrics":"^1.0.311","@balena/jellyfish-environment":"^4.3.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.7","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.13-fix-optional-link-nesting-103753962af165e98c2e38a1c1dd9d53d030ae4f_1627933702750_0.9154480369764959","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.13-fix-optional-link-nesting-bcf131d2a29b1c5aa3c2edcdb195ec4435e9dab5":{"name":"@balena/jellyfish-core","version":"5.1.13-fix-optional-link-nesting-bcf131d2a29b1c5aa3c2edcdb195ec4435e9dab5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.13-fix-optional-link-nesting-bcf131d2a29b1c5aa3c2edcdb195ec4435e9dab5","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"644c1662e4e22b64ad128b0d5fb435f6be424543","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.13-fix-optional-link-nesting-bcf131d2a29b1c5aa3c2edcdb195ec4435e9dab5.tgz","fileCount":196,"integrity":"sha512-kVY8yJhEQi6khddaSxQM3qtmWW8robMig2Kn3eJFetKDS3DFmKDNO+31dH77dibriE84fAGzfd175I5l9amhjw==","signatures":[{"sig":"MEYCIQD91a8p9te6PMQDc8CgFOabaQsDD3aWUO85PC1t5Y7Y3QIhALCl0B74xA5HhyXdDOm1I+6duHcozRQAsOvAjLwOGmHS","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":732395,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhCFMoCRA9TVsSAnZWagAAdmEP/19LEzA36D1fi5NN1Aq6\nYKa3mGa9bX2C2Bg3fwBK9fxDoemFxEOX/URg+TPoYPbrjIdkqjIpyJKjNz60\n7diu00vufkX2kw00vrnfSCaD/BsejZ9/Btiary1gsXrlhk6dpha0Zia2rlnJ\n25jOsnjj5PE234b6fCrNqVCT++wXWYF3QjDIzyrlASM1TqwkZXUQrAqPjcN1\nRfDLdn0Cx6+AcleF7E4PNlnjwigZqwnlT4BcrNA1UN4SDPhVrhEnXT7pKNk9\n9Dt3ZgM8/1VPmgHa1kpNLOWgdIaJ42ml+FdDzsQKFMXT2QQFCjQcC3BVwPk2\n8IgoAuAnczYGd5uxQXT0aQP1nqrE5uR7s/QW/NlMVd894uwl36q/aJ25/JpT\n+0wL8mZv7eQNuiQSbcQMj9Pw4oQdo+NXBlUB0H4EqRG9NT90wd/2YHBT6cxj\nG8OoXDWbKoRnBMh2FDJNkIfFaQeOxOfsAkjCWqKE+5qxpkTGbAMoVsCYyDel\nfcrAUs+Zyhzdu9VUL1vb87Kd/XQLXlYUzdsdi2KjR9OytmtCAqBiZD7c0+RH\n2cfdT/7ARJiS5G/QltVCeroqnZARKb0uulWsAEMOVN4WOYhydoQa/OH2MZ9H\nfAqCKbpf6fZct/NSWntrXs8e17kFJBSrZPIEVzjp5QMdai+ZmV/UwaRh3Ptv\nG5LO\r\n=yCZi\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"bcf131d2a29b1c5aa3c2edcdb195ec4435e9dab5","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test","compose-test":"docker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-08-02T20:15:23.144Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.62","@balena/jellyfish-logger":"^3.0.45","@balena/jellyfish-metrics":"^1.0.311","@balena/jellyfish-environment":"^4.3.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.7","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.13-fix-optional-link-nesting-bcf131d2a29b1c5aa3c2edcdb195ec4435e9dab5_1627935527973_0.41611428589247557","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.13-fix-optional-link-nesting-6c4e4daf948f8a9a0d73e63144c1b14cd649c494":{"name":"@balena/jellyfish-core","version":"5.1.13-fix-optional-link-nesting-6c4e4daf948f8a9a0d73e63144c1b14cd649c494","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.13-fix-optional-link-nesting-6c4e4daf948f8a9a0d73e63144c1b14cd649c494","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d5142c5b9bf2416d842ae6c85e7a0dcfc761fe4d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.13-fix-optional-link-nesting-6c4e4daf948f8a9a0d73e63144c1b14cd649c494.tgz","fileCount":196,"integrity":"sha512-Eyl1iVQqdbvvZ/TV4sZwAxA3JG0zLHwBNHr9XJt2z/zSbiNYl99Lr9XdSQ0yrAGR3zRtZMsLC5RMLxmTmdjjNA==","signatures":[{"sig":"MEYCIQD8kssCaAiSfnHcivze3P84H+U6UZjWDr/fOWrsfHhoNwIhANVEnWSgIS7UGlvOhJK0u6PYTQG7nENYSfTBmjRScCr2","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":732395,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhCFZpCRA9TVsSAnZWagAAMzUQAJ+zManaTWrq+fs82Bf4\ne8f0uS/+AuhDiAAUrQb0R7Vw0jc+UkumWJ8QGJfEnA5JmFrjqr5uiJmmJgux\nMhzPDxItuAT8A0caUB3s7pWPSVnIZJ4iHCmHCQX0OwZBzfhj9y5Xa5GEqgNy\nsTKzEtRx3N2QDK0WBwhbrpEi0dhHSAFjUM9uTUpsRQF0ryUtVtV1tR1H5vC7\nTa95Uz4XvmjfHNqm9bTYkEyHFlEKqTVDD/ZAYUWydLg9WoYqc1cv5Cq8chTz\nsNlD9cUhAyAFneVf2JCR/6KFkH6uOuvEGWUht7YUhfRYWat1jAoP9v2IGLot\nmMbZDCe0NqHqBj5Iuqkz0H3M1QRDoplH18g75b24wcFuKK25JAFRUliT7cSM\nNX8f9qzbnzvTvAa4Soe0WP0i39zofC10ZQ0BKJg9jZMyXdlMlM8IArzzDDgM\nqJDiMZg6ApbOPCnEI0SYQgsaTtt06jnXs5j5ovO0IdgZ5g59JZ7tMSKIv9gO\nJADkS3OCYjL54m8ZHgSWNzsB3vEOdGL9dRQouyuQePnbXB6F7BIVHWVb7346\n3YjxuGWnWbli0C3DKDcVl3Fy3Y7chL2rFQYCtxjSygtg3i+mA3hJRG6E3xVg\nVrS8+e+YMIfdGy66YtJk5fSpkgGqC7t8kYAXJoRYSN8DIwpiZqfkkwRNZsSa\nfX4E\r\n=390B\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6c4e4daf948f8a9a0d73e63144c1b14cd649c494","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test","compose-test":"docker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-08-02T20:29:42.720Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.62","@balena/jellyfish-logger":"^3.0.45","@balena/jellyfish-metrics":"^1.0.311","@balena/jellyfish-environment":"^4.3.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.7","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.13-fix-optional-link-nesting-6c4e4daf948f8a9a0d73e63144c1b14cd649c494_1627936361534_0.4677058604336881","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.13-h32-unify-script-names-9bac8f7924b5bbc12abd42dd1b976c3d8b126a93":{"name":"@balena/jellyfish-core","version":"5.1.13-h32-unify-script-names-9bac8f7924b5bbc12abd42dd1b976c3d8b126a93","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.13-h32-unify-script-names-9bac8f7924b5bbc12abd42dd1b976c3d8b126a93","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"41f326101f35d00ee19a2410d33afd6cbb28b7e9","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.13-h32-unify-script-names-9bac8f7924b5bbc12abd42dd1b976c3d8b126a93.tgz","fileCount":196,"integrity":"sha512-r4kHdEVtZiNkVPCQtAfr3fWGtHf66p4mh6HOihCgrGOBwkLMB8BL4j6WcDx3/abmdlYNte78qqxuP/SeGuoubA==","signatures":[{"sig":"MEQCID4Ply6wJnVQqICYM2f6B/6mdESGubYZcS2THBX4fJkTAiBBjGKEBcrnOIcaJz6Bi5JpQ1qaynvuJJ8qeRrOKrhMDw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":732495,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhCmI2CRA9TVsSAnZWagAAtYEP/Rbg1oOELcF6KWPuXd9R\n95ZJFR6KKvKma9G3y8BoHDzP7mvld+tUKD+7sgUA/xIYkB90uoFOwLVcuiVB\nrZe1osffTY6oMJmfrAhSAGspit2W08GWz0CSQIQBg2+YajM7XutJqlnpkPpL\njbbPhyJATTibQ5nfiikRLvgaBTFRcReS7kkI1V5XUNrmL08msZChA58W8jcy\n2iVVqkN0ebeVE4wSG7mBFC06Xj20g9bZrRjNoUHnU8RpDF6ig5JI+APcGOpr\nkucNG9mJC7vNpdgmClTHTQphxabaDeHlYmvX3v4LXkvUe9LMFprrcjccnuhB\nXHFsvWKPMg8dJ00HGR/t40tbQyECxLTpLdaLM1X4T2M65/pvDKgtT9ID0qbs\nY2uYHxLa0HlMP7WJ0nbVla3Th3+tSvyM6584ismrJQtjF2GBPP3vS1MgL7Xj\nUj87wSTt1bH+CWZS7Lw17zEKNgw00fmMrd8ZrGnbOKxHVLeUpfLrEy5k13JV\nNS53S9/tZUQt9sGpNytdw432Wq+NoggIwml86EQozw3jxTlu7zS9yq2pyMOz\nVHFKwj9O5AmYJ/vWikrHWol0rFI8H/euehiRdrW3MhXVpI8nOweOavKV7up5\nq9hk58RqqxpvXUt7sgu4V8QohnFPhDIxLlfQI049JeM4FGlOcCcVOC/cXja+\n1/lm\r\n=l8tl\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"9bac8f7924b5bbc12abd42dd1b976c3d8b126a93","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-08-04T09:44:03.146Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.62","@balena/jellyfish-logger":"^3.0.45","@balena/jellyfish-metrics":"^1.0.311","@balena/jellyfish-environment":"^4.3.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.7","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.13-h32-unify-script-names-9bac8f7924b5bbc12abd42dd1b976c3d8b126a93_1628070454019_0.2450144077376759","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.13-h32-unify-script-names-9267983ad48dfaaf12c5cfe2336d4c9985bc208d":{"name":"@balena/jellyfish-core","version":"5.1.13-h32-unify-script-names-9267983ad48dfaaf12c5cfe2336d4c9985bc208d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.13-h32-unify-script-names-9267983ad48dfaaf12c5cfe2336d4c9985bc208d","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f719b5789b25cad5ec7f1eb60ce67d657465122b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.13-h32-unify-script-names-9267983ad48dfaaf12c5cfe2336d4c9985bc208d.tgz","fileCount":196,"integrity":"sha512-zvRHKZ/LVRFDIFWDVWcWKrRfCOckf7rBz8zaS/1kKb5q+bXZDfh5SRD2zxh+doRfUC7TMzrmACqnhi0EycuKXA==","signatures":[{"sig":"MEUCIBih9patfcyDZu0qr7piO6X52yYpEXkYTjN1F3H+pqwVAiEAorQd2Unt24DE5uV95V5N7znNb+RuQ+We22VWHave14k=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":732538,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhCx6pCRA9TVsSAnZWagAAIeUP/2foriuk33mvBxteilBq\nD1AWHX9+VA5zQO5oyf+7A0Yfzh8r8gmssG04J/eNYsJ4qLu3V1EJbYuCCDJx\nKK6zPWzEg8yBy9E9o1ajnrfElbAonZOI/c1GNysYY70kiwOLNi1mW0tENOdH\nQGQaX3xoTlvN/rCqo8Vi7UiKlJPdopREExSMvEj5imEHgprdh+s9mDU53IyB\nGptHnVJ1bg+yQESlVQ6vHwrAJkRjW7gZxefbmooYZGv4QEnuPHevPJZQNj/T\nNakoJ27rIequD4lGy1oLz5sZxC3VeBHBWp7sBNAhjDixyxhj0G6llon+5hMq\nIXRdQqAMFK7BNdmyCzO3JD+KkhxhJfhZXiITkVQBksVMZqfl8PCsQ4U5gfGb\nz9FIY57kEFJ2c3zKyrAW9oQmlX9HTII3SjoWa48VbTPmox71WPb3ViRecyHF\nYUJUtU5EkAzjIznt0YudGwZZpCPlDqI6/3f2lBVRXKPwoUAlzXdj3VUHhuER\nfSUF7S1M2AmVetle4L9eUtwIvBIrfU29CH5/mIVHXbpBQUKielE9kJL7GhN1\nv/BMQYX7x1MJrmPH0g2MeB+4CZd/8urspgx0bKCu39FELlNpD+QeL1BH9Gws\n5x0mq6cN1GI4T5DUsL0ru9k9TnLk6tb1iIUp7FSrgAZrjfMaPWymkIesLYhG\n3YPg\r\n=eN3k\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"9267983ad48dfaaf12c5cfe2336d4c9985bc208d","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-08-04T23:09:03.802Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.62","@balena/jellyfish-logger":"^3.0.45","@balena/jellyfish-metrics":"^1.0.311","@balena/jellyfish-environment":"^4.3.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.7","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.13-h32-unify-script-names-9267983ad48dfaaf12c5cfe2336d4c9985bc208d_1628118697470_0.3200335972950603","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.13":{"name":"@balena/jellyfish-core","version":"5.1.13","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.13","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"72718d9a3041bbda5a162f79e7e801b9ce9e5046","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.13.tgz","fileCount":196,"integrity":"sha512-b5vBVxSAZ5NfEhEmnlLkAXlqMwQopIyv1TWZJmta/L/5hfWd960Rv1twlizzRqk1iIU+gMSdXNyNDP1qo8X8Rw==","signatures":[{"sig":"MEUCIGHY0c6G5iON/F/ZS4VrpT280ZEOR4A4SYluRwsXNd6+AiEAtPHhc7o1p6plLQU1T3A3PHMJDE81E2P8OOrSvXPgCTY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":732474,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhCyC+CRA9TVsSAnZWagAALiQP/07nZRrVcwsSzbYYFbOy\n1GYGYt7PS7ZbTpHBGYwWOo7OEjJQe1cynnzGkAxSl+L/eUdqVEqp0wgzz5vP\nEXxeIjqXtl2JQ61Ar5lhlSkXhNMkRBUxiQhXH1En+/CrojxGXbEB1mLsYNyi\nX/rLtobgZIv6hz7wFQkG92WH2J6lmXN+Srz5oWx8rgqcieAZucEwPnG0f7E7\nQStJl1HbnI9PN9tS9GnRkyHHn08B5vynA2k3KP7o/fCfim6bhGAWiG5hdSpM\nl/6IvDmvCAZd8h9VtHoPDqALi2fRH7ermrzh1Axx/1fPXrvm/B9YbMm8V2he\nRt67lf3ITKbr5FmNCJDZ2Bh0A0vgt28siR+ib5jdsat/LCbFXSo9Sk/rGdwZ\nzH16hsSqKwgEJ66zvDzMAHPccen/8fGUPo2KL6rXGNlCHKQtTe8QRwRnyqSG\n64iUhMZEXEQEeC0lLAEl5INOyj9VkKmSdS9JI3prvALJ4PY3wlTm8v2If/n3\nfdq0KdlgxiwYJor41i7+Q46cbynzYF5fNEGKhCvae4hExoEYO+7kWqZGrdWp\naYULxPqjonCYvUG4gE4y2aEpGA99zT/TY4SI16bneOd+XdOfU9SxviJc4Wqk\nL6rqecPlZsB++B6yjtv/Ez4TNLDKcdT80Qt69HD98ugOOcZnS0HVHVLE2QBB\nUKPw\r\n=+BV6\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"698c4280aa4b56ce942c05d27490c0e356f1ffa5","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-08-04T23:18:37.984Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.62","@balena/jellyfish-logger":"^3.0.45","@balena/jellyfish-metrics":"^1.0.311","@balena/jellyfish-environment":"^4.3.13"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.7","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.13_1628119230760_0.7195257979363978","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.14-update-readme-command-2ec67d969e3127f8d6c1e2feab92c92daffaff31":{"name":"@balena/jellyfish-core","version":"5.1.14-update-readme-command-2ec67d969e3127f8d6c1e2feab92c92daffaff31","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.14-update-readme-command-2ec67d969e3127f8d6c1e2feab92c92daffaff31","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9884a4cc89804cb2aec2e70aacd3dcf6feda4dd0","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.14-update-readme-command-2ec67d969e3127f8d6c1e2feab92c92daffaff31.tgz","fileCount":196,"integrity":"sha512-NUEyK5dD/K2kSCbbLbkozrIXnEksgVnGYu3V3gCq6CMxY7091UaFUqbu5dzUNSgEG/q+56z8gaSNoHlFt/1NHA==","signatures":[{"sig":"MEUCIQDQT+CSmYvtR64UqQC/PEonneYWyh3/3uKcfqln/6j/dgIgRSH7kuyWIIFsznQB1qqmix6BmGwxqQHFKUzTSVpskXg=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":732669,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhC0qDCRA9TVsSAnZWagAAM/4P/REC6SD9s6LbTmpgVmtI\nRgE3S47n7KVVFkdBwdRh67MbSLg/IqllmfRWe6JU4MWM5jbYpmLdM5DosXfq\nzc0ewlm82xDUtAW2JqrZ8E2LyZajD5IeH2gLM4sPwse4UgzgWEWFc6kw988z\nQ0bvI3t+U2omhmjz2RVMbc6tj9Cpl4yiuoX7QLu0lNEWiMmMxeoFllisOxJt\nW9AAYubIo82CfwDnAaSZDNR+WsRqlLsyv0Qm/nFRskqlt0QBlpVUd3SbJkj9\nd2oc8vB3GsBgKT0LeHqTKWllLWZqXUP/E1ZGlboF4h49yCKdO5gZoI9teLq0\nJr5z649KJIYEwWdF2F/alGFuvvUR4iiz2m3mhptwr2+Gv6plSo2rTMm86xdc\njswdASHXuu/KFhp0mkTFPaNr2txGeU18K13h13HH7MhRIcFoWtCjM8LHaiF/\ndYU9sdCIWHHy5rpagrajtdZI7BK2es9BQfgnVaV1tFgbBFjKvgDhUKulu93P\n6ks2Onvhoqd39tUrp3mrR0mtxPGuxr/eQG/pQhoQ5KNF7S7mqOdIminM3COD\n3vpv1x1wYcBYjjN9EoYpJFgEo5ey9RNJr5kyrSWEwLrRGuqmhTea7vulFykJ\nAM7CDYjbtwQygMKgjfmL0gAXbxFID0Et339Bv/HVQeK7tYQ3bCHiIlMWczgz\nJQD4\r\n=2Tlk\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2ec67d969e3127f8d6c1e2feab92c92daffaff31","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-08-05T02:14:39.370Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.62","@balena/jellyfish-logger":"^3.0.45","@balena/jellyfish-metrics":"^1.0.311","@balena/jellyfish-environment":"^4.3.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.7","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.14-update-readme-command-2ec67d969e3127f8d6c1e2feab92c92daffaff31_1628129923355_0.8300249481329902","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.14":{"name":"@balena/jellyfish-core","version":"5.1.14","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.14","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"24a78a0ac6b5e2515d916efa39fb475a83e1bfd8","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.14.tgz","fileCount":196,"integrity":"sha512-ey7YnQfGA5rWM0HAu2g+MyOAKleSF/I0JSajYFfz3usknqb0o0wLDQcmDNxQ+gmNZbgP862ZJiF1cO84BtYSKg==","signatures":[{"sig":"MEUCIQDlOKukJRCPcC+G66FKCXJgHEVm0P0xbkIkb73mhBeZtAIgUl7it22WxiL50r5KMFjUa1u4qOx4AB4NIxCmswNktc8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":732606,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhC0xeCRA9TVsSAnZWagAA334P/0/Bi5Xv4dw9dmQ99Q34\nJtop9DmW4Ph3nHW2T3L55mnLE+Q6Q/w9+rcb8XlaJOppnKF9n2S4uMNPaNiE\nhy65gvp6Xv/29dLriSHCRSG01dGvkPWjLOGJ12SzpxD6ldr7toU7uUONZgKT\nh5EwuGmmkjS3uzupMUul9T29NMwUNbhssbgYPLMS58g16Bujjj4jYjYNpNwR\nMcIRPLRAYEuoW4yUXll9HL0gStAwazd1bpXdXVmwo4cGvMe11Ut2PsXEb+2q\nXsQCn1BCDW8c3JWFA5JhHgVChO5yEbLNPfmfduIFeaHfm1w96QF48K1EssOC\neghpY4MRuv+DPUaXi/c5nPc+KsWkavUP6GQPOXF9G8/FCLlddTxaSFnRuzes\nSMg+2ro53Jr4DsKnBqjns2QdnfduRVCWjXs/k075Yj7iDBFl8v0dw9eKYoAM\nbRyuD+TCJet3Fyh8DIB5ASgmYqDKozDllIeswaxqIhVtov3W2gMjf+kS8y3H\nCv3UrAQEsOrokk3rkcH6v0Ab0Ggj+ccsyuTiDmi7WldQngIJGFrN7A/lwUhf\neazHOqm0OCJqhmHh87CSd1ApU9vBNDwdKD05jzeQ5+usIz5GzHUDV/AQJeHu\nDDOAg6F9RNm832H8VLXnyzJsemA9LwNCpfz19dfgU9vE3Om9rC0L4EpfW9eO\nZwSL\r\n=r93F\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c9ff85468732ad9b5d988aae0015023e86e3b625","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-08-05T02:23:32.903Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.62","@balena/jellyfish-logger":"^3.0.45","@balena/jellyfish-metrics":"^1.0.311","@balena/jellyfish-environment":"^4.3.13"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.7","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.14_1628130398131_0.6798098400361696","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.13-fix-optional-link-nesting-5e847d6a627412da09406f5fa9ce77cc5cbe3a43":{"name":"@balena/jellyfish-core","version":"5.1.13-fix-optional-link-nesting-5e847d6a627412da09406f5fa9ce77cc5cbe3a43","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.13-fix-optional-link-nesting-5e847d6a627412da09406f5fa9ce77cc5cbe3a43","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"72f0378f9e9c9c901884ecb55b537ef3377b085a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.13-fix-optional-link-nesting-5e847d6a627412da09406f5fa9ce77cc5cbe3a43.tgz","fileCount":196,"integrity":"sha512-euo2mhnqKeaDStf0IySwfIzUqp1io7idUlGf13jYYIUdzQCPTQGCLtEgEIhOlh+A1ptFk3NUGVO9APBrm/ycmQ==","signatures":[{"sig":"MEUCIEWGpMDqFfvrnP1HOq5ZH6x5sekYwk5DcxP3JJS7LtItAiEAyahG9BGanEFWX4+6262qi0V09vV3eXzWLiG2V6eeuXc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":732395,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhDBEhCRA9TVsSAnZWagAAlsAP/jAjlKf7IzWMFlVS9UU5\nTlxVVzkKbrF/spediVcxHyksaH9pPROnj6Oa2P8ZLP0TnXfuE0f0sb7bSBGD\nrfI2W8mGk23TEeECPirxh7ocEHFjWGsCQwiTyXmtg8KL87BOm5CjeUs5UGqV\npvP9ZxBOkmz9uD2cFuEt9UUKGc+GHMNp6N4Y4AwmLchklc5/B59kY2uS9RV1\ntIxa/BJarvzpxGOM/jzZksDyb9NKTTeE3ntY/gCztMH946kKMLVUssOIEHTM\nlr+eBaWmp2TgOmUSIBL2gcECzyGg+NcuNPjTskwRP4eeBPQMEVmQkrymqvJY\n8dZG9maTWbLGL6gON07nQDxLdeBytjvR+YLHG2EKyhQSfXu3jgvDr5k4EpA4\ncJ9ZyovDdfI2b9RT9l1I0qk/IGYTXeqZ7SbOy+Ao6LKoQOrzdKm85bShnkoi\nTt9o5N7rQKm8gJAzhjFuwYN/lXbJeCxcmMC3CpfuATiF8T4vZ0WKozpOdGt3\nA9xYYtFBfA48NKIYennE+75nYLviJp1B9y7HA8uhhkcHpppstOqxyrOUAsHz\nP2vxlrKZ/uqwhkFXusMAnF9ib+ZqFqxQLG/FyBhC8t5OWBKB/BrwnaTG5CWF\ncptaWR6X1emFPzHDlIeA0P3MfhlLb79zifgJ+JH9oRryPqT8Y2lSHVne1zBs\niuWu\r\n=7Xf/\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require a postgres DB and redis server. The simplest way to run the tests locally is with docker-compose.\n\n```\ndocker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"5e847d6a627412da09406f5fa9ce77cc5cbe3a43","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run unit","unit":"jest lib","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint-fix":"balena-lint --fix lib","integration":"jest test","compose-test":"docker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-08-05T16:23:21.900Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.62","@balena/jellyfish-logger":"^3.0.45","@balena/jellyfish-metrics":"^1.0.311","@balena/jellyfish-environment":"^4.3.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.7","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.13-fix-optional-link-nesting-5e847d6a627412da09406f5fa9ce77cc5cbe3a43_1628180769086_0.6939602981276942","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.15-fix-indexdef-generation-01d7bb8a0782867cf0b91f7d2d4112491dc11ecf":{"name":"@balena/jellyfish-core","version":"5.1.15-fix-indexdef-generation-01d7bb8a0782867cf0b91f7d2d4112491dc11ecf","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.15-fix-indexdef-generation-01d7bb8a0782867cf0b91f7d2d4112491dc11ecf","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9447e69aed7a14f0b5ca55a6b1fb0741070724ac","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.15-fix-indexdef-generation-01d7bb8a0782867cf0b91f7d2d4112491dc11ecf.tgz","fileCount":199,"integrity":"sha512-5GlwktlcxUqqIZ8HSv3qGMzBUIj5AUOjbyBYRveIJCvOe3n79Qc2Z+Vozk+PMqi0PrPKlFexRQ/zAU719aW23w==","signatures":[{"sig":"MEUCID7S4fj8TPmS5cII5n1rMYbC2kuUJJlvFgdnHLDDrpmOAiEA7ybVWTsOhWuBq+XEHKPC2AXy5133wJLQ6DkbLOzEf54=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":736737,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhDHngCRA9TVsSAnZWagAAPRUQAJbVw2anj/RgEEXbd42I\nraLQ4O304m4rc8IzUArviO90Nyk8Il3OVafujgj0fY0wOj/R/MeXqeHgjbrV\n8/WwPvt2v/xw7qo+4oN60m3DyR3FObJwfUHdNSJDhBWLqmrgxor3N33xUgAv\n2xK+KmrAwSSZpr9grFZm1JsRttzSWUNKtK6lO4v+A7k2s564XOwXtJqh+jkA\n0dsqEpiSEjMk5S46RvgpIIJC4FEeqVFzoPffSRI8Z60N2TNXcGSxHAo48Xau\naayxPUZCNdCO66a/b5+6YLa7+ma+XeqPZS8cuH22nOBldF+nOuBTsExK+qy8\nh1Ag0GvnpoTtQul/IgRaRja3NoKO3J5NC8ZmCBz/Illha4SLQim6Bjga+qYr\nRMBLj+IHSCyIPx9JmdH3ILUHjuhiRen8fZ/5aExbee2UpKyyfnmgAClJtlrZ\nBQw5eVswD20HMiAt2XdHshHdfO25L3p2hQde+bEQiIvNj0ouuWD6qQEIEEXd\niRgFF5t4hFg/OP6+1EXRYsTY+YRbQqXQJMguFMl5spBCop1AmzLc6QyAF1kg\nCsKGQalyMwDgkXFgX718pgDkRJvIjdE7zFdyZpLFyJjEN72E4x2JVA2xwaao\nw+m2nKAzKQZggCHR1pEKEHdD0nMyAPn/eHjZ1PpRWMvr0XD6SfjFx3Zyob+/\nGA0S\r\n=l+gf\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"01d7bb8a0782867cf0b91f7d2d4112491dc11ecf","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-08-05T23:49:30.341Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.62","@balena/jellyfish-logger":"^3.0.45","@balena/jellyfish-metrics":"^1.0.311","@balena/jellyfish-environment":"^4.3.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.7","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.15-fix-indexdef-generation-01d7bb8a0782867cf0b91f7d2d4112491dc11ecf_1628207583911_0.6462819986874937","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.15-compose-8d45a918effd7a88023e4a0a0c2ecba1c685fd9a":{"name":"@balena/jellyfish-core","version":"5.1.15-compose-8d45a918effd7a88023e4a0a0c2ecba1c685fd9a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.15-compose-8d45a918effd7a88023e4a0a0c2ecba1c685fd9a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"de1fc6e52d1ae93b26cbcaa5207a8f1f2f7ea031","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.15-compose-8d45a918effd7a88023e4a0a0c2ecba1c685fd9a.tgz","fileCount":196,"integrity":"sha512-pnBmRD3Qmezfx5DLDwjoEEtIQnct2QBMXx2CWZGps3cP7Zc/tMQ8fP8/EhYECYONTTQsV384QTzzUDelUS5sxw==","signatures":[{"sig":"MEUCIA5bsAIyjUsBKkh26IbR09fisO/PFzkB3q2tua2zaSd7AiEA5A2RxS9HbGb7dlfFAzz4Y3KWiktI+OZyW+qjp8QNFLM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":733122,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhDHxjCRA9TVsSAnZWagAAniMQAIzb+s7eiVoVC5EA8m5T\nZtCZIEegZHrSYWdyAqCvSq5Vivu0mv6B2zFlR/p0g00JO9hg7uuP8WJIeaDV\nodEpAfYg/Q5D5Eo1lTkWfJJlyBUWFL1YNU++UNygLM3qSnUMjgrIiLXik0HF\n2haB/30jRUqWyvPQViQ0uIW8U+iWkprjJE2xhlJ59UYM5b9LEEaK/7w8s6ne\niRszb2LotC6TwPS0v9ounWYdZVpc5rAH7HFv7PSEgYwYShbXmpttt3ngkPCT\niiVfZPE3gZvxGk6VWekNtctFxf67ebmwPCeCEPsWfh88j/6ZnwfPv1POotkD\ntEXHUeTHb5sMgGw5xWPXmQG3Ww2Ysm+WvQQeuHT+tsjev+kYhLYo5eIKT14+\n0umwZ+TsfCiZ+KikORB/3cjbbQVELFy4sqV5dtm1ms1y2c7A3iE+Xvp0ecLv\nafM91SScePk7ewg3df1Ph5/qZEeHbFGV/3a6kASayzX0Vbm5OpN3dakYLPA8\nww+vHuVWF+aQ0mpoiLb7ZB0stqyR6wYZvnQ6wSGA01z6oPKYXzkGKaFfh47d\na/EnenfMG0EVE6w4YJ1SmbEAQKRF8OG7hzPEIn3iMfM9ZMw1RqCkk1faXTq9\n9pkWNdBeU2r3qY4nN/pg9v1bXRX6a8Eh/t7pI0yYs3p5gImQVVt941w89jJ/\nxgPs\r\n=Hzld\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8d45a918effd7a88023e4a0a0c2ecba1c685fd9a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-08-06T00:00:20.334Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.62","@balena/jellyfish-logger":"^3.0.45","@balena/jellyfish-metrics":"^1.0.311","@balena/jellyfish-environment":"^4.3.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.7","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.15-compose-8d45a918effd7a88023e4a0a0c2ecba1c685fd9a_1628208226925_0.074338855740685","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.15":{"name":"@balena/jellyfish-core","version":"5.1.15","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.15","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ef1be04815a34e3ec8b45e34a119ca66c9dbdd93","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.15.tgz","fileCount":196,"integrity":"sha512-Pr1sZw0szu6urH8NOZ3hmkbnRS+lNPM/jakBDAsCOVpwD6jdjUpbvnHqFWIa1IjVCjynK8b29n7oTl7R3OPIzg==","signatures":[{"sig":"MEQCIAL2aoVdI8U1wrnwNwN1kZw3YhXD8JRkyW8M1otuEIUdAiB07qY3D3/OCks5yfi5pDahQQfk8PIUbDEX43NObTvIwQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":733073,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhDH/dCRA9TVsSAnZWagAAeyQP/2fOaglzt9sfkwJeDS/X\nViVUh0UNqslMpSmf2mMOSncKF+HhzqXWOHHjHNEctXlxEmmncYyAJTO1Egc3\nkrdA8wWHG4T7N23fY2mj0qRX+FpNzdkTUMESKfO3FP2TAi2V+tw+bFnjkpUn\n9YZ2J1rVWWfKyK3wJNoplzR1zl6Bwr8fquEImOQGES5lsRQ1LvSyKoObyFCv\nnnXx9V+6x1oP+txiwHBMdNI6Q4EBbgdwOB+SEgguv1dSA1HZFBBEbPfdccY9\ngTrR5BBIj+adBnR9wE3WSkLR+Cvtx5mkb9ZV9escJ5NdBm/XADY0m6esi06M\nDgz4raLMj1CLSZ2CEpvDFszuNjEy+gANJdoiQtBntCJf37R7TdnapqNLn9Ds\nQKoNVEvehecAilCtiLE/y2kekQmdlorNH7pjdSXE7qc1UrQY0c8Yh9fQ8id1\nIT42HSNFrOn+UZRxt3jO/F8xzqgMTK+3eFrBAQQ5LbCESvIkgAMKeV3xPbr6\nCAbWykwGx39eKyn1WG7GkV4P6/OBadz0nNQyfwyhA2VvA+0W9mDoiiyAX1ZL\nGbm20kYXtvkKnk5zttZWSRzs7cEpAUjC8vtsDBT8ouJLPALpmiZjODQV5hfD\nCv60eljyTCWmmaqRfBoWzjhYEOzwGNg4ZMaGq0Tp6QIc3wA32pmzrG/BRfa6\nnb2e\r\n=GtSK\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"26f3eb27a0e4a496c9d2cf98efccbac0247d5f3c","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-08-06T00:16:50.927Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.62","@balena/jellyfish-logger":"^3.0.45","@balena/jellyfish-metrics":"^1.0.311","@balena/jellyfish-environment":"^4.3.13"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.7","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.15_1628209117179_0.6898563257663293","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.15-fix-indexdef-generation-c19b6f760b88043a548d5be338700af986fe9d84":{"name":"@balena/jellyfish-core","version":"5.1.15-fix-indexdef-generation-c19b6f760b88043a548d5be338700af986fe9d84","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.15-fix-indexdef-generation-c19b6f760b88043a548d5be338700af986fe9d84","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"596e12a56981a93174b6a8d347268014be22d48e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.15-fix-indexdef-generation-c19b6f760b88043a548d5be338700af986fe9d84.tgz","fileCount":199,"integrity":"sha512-SFk3icDPkGsKvjRb4VaGEwpEZ8Bfng8x//uWZxORgKQSFeUddnj9HpvyebVc8McilE+r2+32y5k8RA0e3F3ttQ==","signatures":[{"sig":"MEYCIQDa/lNgEzfilT1QenRvjuK1MjDuzHfE6rzZ+ouZm9kP/QIhALUuCYRfUCeytXaVTCh+CLnLOoTHQQlggt5Qv2mvoTQp","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":736398,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhDIo9CRA9TVsSAnZWagAA1KYP/RhXsfmCGvv56Hj1DTqa\nxDq1cOnmvtJY5+Y3AGiapB2ad6GiAMNM3czNWIhHjTqosYFyioz47mYHFQt1\nMhohavu0O8eOWR/kp2j31vGbLsY2OW+kkMxh8akUPAIq43ros2NMNLhVoND5\nB1CIDkrAKP8BIq7WXwlIHGE+g9T63u1pH6772f6uxnuiKGOWzCU/I2lgj2YO\n5cZvOUIKL2fxZ6M9mEYPnZJuyf6wnc58V9r38jVtjz73I/Vvn6hP4HTp/XiO\nIEUf2KLj17ZdG4X1+pQ9BP9ccvoa622xUSS/XY9Feuy0QYESDo2UM1R3Mzak\nQ8L4h+LfDOJq/ulO0QLp/2H5XBsdqMBDQZlTJYMLNSI1wssau9tAQc27LZ1e\nvfyN87rw7gOrY9sAvK9Zb77dUuyw9cfgt8yr+K9rvsYFiAzFwlkD8Mb9s7VT\nHTV9kB1AC7YMob0ujt/vIj+b3CnpMsW0Dgvj+YCjbboEWpH9PEBQx0zKFHTd\nNHn4VZDdJ0o4IpL/yH9OPMKZMDYFPw7Fo93aPXBS8xhCFs6GDdkhdKlQtGS6\nanadj68qlPlsOhWHpJxIAN0mrix3SKJwHFK8ik+D+oTkT44pNmccEdNCwlxm\nea41M1+Fo9+oyf4NRTWyfdl5A6GWjrjTLdOFudK5jSlvRywQ4PWqpj4LUZeO\nQJx6\r\n=DV3n\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c19b6f760b88043a548d5be338700af986fe9d84","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-08-06T01:00:40.897Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.62","@balena/jellyfish-logger":"^3.0.45","@balena/jellyfish-metrics":"^1.0.311","@balena/jellyfish-environment":"^4.3.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.7","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.15-fix-indexdef-generation-c19b6f760b88043a548d5be338700af986fe9d84_1628211772823_0.07585788725821963","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.15-fix-indexdef-generation-2ffe4ab865ba01f72118b8d8a160d6d46cbcb415":{"name":"@balena/jellyfish-core","version":"5.1.15-fix-indexdef-generation-2ffe4ab865ba01f72118b8d8a160d6d46cbcb415","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.15-fix-indexdef-generation-2ffe4ab865ba01f72118b8d8a160d6d46cbcb415","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b3a462f52f097c00bf3d949c92a9785c86e66272","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.15-fix-indexdef-generation-2ffe4ab865ba01f72118b8d8a160d6d46cbcb415.tgz","fileCount":199,"integrity":"sha512-YEyJvhdPI3qr4hRVv8VN2AXUEDiTIuGffcaH15H0L7rpI4jpH8A3mlgCv8fqge47qzLJicJ31TV8ZMT3BdI5ow==","signatures":[{"sig":"MEUCIHjUiepAabZ8gXk5cwvyU3/HATaNO9G3BunyUnQtS/lhAiEAo6AaOEEOuu2BPCczjLnBKhHGajZjcuzhBbza6JaYvvo=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":735997,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhDmTkCRA9TVsSAnZWagAAchkP/0Mx6RVPeSeOYsmioi//\ne4Rfcd7RxvbmeOhW00FJs2pZwDsgNGE/x/T4jgOe7BrVLLaD0ionruSvCIA+\nY31m4eAAsl3tmcR5/3s1qJM/KWnXY7q+TuBq49WaQMCklhfCZl9gJmjVeFty\nHhUpiHj6LTWeKAOKTD8K4QVwNJXvBr7lRUprx2Rj1rI3GiSE9HDILEiUz5Zp\nY0IqOQDBwXb9VCPastFqxJJEXlsxzJ+ctq+h1qeA/zvgafA/98hSjmKppV9a\nmzTU60gcwnE37jm4uT/m3395MU1fdQFRwdfc+/EfPWedm3pwqxXVQbGv/05+\nI4DUgDGdt/1EEZUsk5guw0x0pfHCeCJsNzRlZ66Si6FkRCVTVrDqH3N/6Yj4\n4gy8N+Sk54m2IevCDTDaB9NzOu8I2Smk0tmAMccA5Nhy0OxZkhWxAqwj3Ppu\nS3UYGulyqljWkhZl+Y3i3NemH6CG8ELEk2FOSSfu/mt1kdPlaAtYVCgZXClY\nqHdiFvKpxhmbtsHj0OSR37ELOdJLlg1UAqLiFjaKmS7pGllsxqUHDGf2+Zs+\ni6nasfVMBpmOCqN1HhS+BrifgPkr0fapWzZY+qA8FRLjNBAbnwxsZyYEguaG\n/XpHXCni4+oqhDqPFCBOFBzSJE4P6oeQFDeI63Y60YmSrBavP0tA+8UjEqgH\nerds\r\n=k750\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2ffe4ab865ba01f72118b8d8a160d6d46cbcb415","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-08-07T10:44:21.602Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.62","@balena/jellyfish-logger":"^3.0.45","@balena/jellyfish-metrics":"^1.0.311","@balena/jellyfish-environment":"^4.3.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.7","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.15-fix-indexdef-generation-2ffe4ab865ba01f72118b8d8a160d6d46cbcb415_1628333284167_0.011637840842352754","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.16-fix-indexdef-generation-4ca7ce4ad09eac90f32bba5b977d7993ed256002":{"name":"@balena/jellyfish-core","version":"5.1.16-fix-indexdef-generation-4ca7ce4ad09eac90f32bba5b977d7993ed256002","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.16-fix-indexdef-generation-4ca7ce4ad09eac90f32bba5b977d7993ed256002","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8f057330bf71c852e536545d5e5270d5e1c8ef39","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.16-fix-indexdef-generation-4ca7ce4ad09eac90f32bba5b977d7993ed256002.tgz","fileCount":199,"integrity":"sha512-UbzWp/nOzIOOkgz5q7hDl3eI2wYzlTVQXe6Eh0HVoEnP8vr10fS1X0Wfo6GNeB8QcLgFS3CyJePMoPq4i1dGSw==","signatures":[{"sig":"MEYCIQCyiobVi27VqqsKCCiGype3BUg8dLqKvYV6AjfFmnaH8gIhAKhZoYSjanwfyr4GlDIYbJ7m33pbmMTpChRZ2pnTNJRC","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":736464,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhDopJCRA9TVsSAnZWagAAPiEP/38Hb1v6mqRb0sQHjjJU\nsZMdzhEPdoF5aD45I5mOR9XxDIbigKzkQVwyTPPAvwOMf6drUDntaKxm6/b8\n7GufrggjPflCBNQbMv49OXN6dH6Q/20xCkAUjxPUTsixrLgm+R2RcwisL3Cn\nYe7crMk18RSqpxy1SIm4PtpVUJtlHufDMgr4Zg/jtySEyHK28bP3lz2HVkxs\nGBFB9TzXQ0Xz9G4sP+teggap+6Zskv6KcI7AHP4453PzZlCAGPcAnvKFHCCg\nZo1UJao1GTgfb5Y7RMPuDLNhB1swCj7TXnEzBs9A1+r26hLjT+9J8cBOyjUj\n9W099GaNEkBukgtfbq7YwTdrwvAzsLuRCtWxcH068zwGKrfeLJes4h82tngP\ngZSC+P+ZYzmYYoyQm5oVIynyUZJyWxDa7qV4dTBl/lyFMPkf0hGwZiFndMfI\nScJTy/92nCS8bxfIWrNFLrF6pPO3n58IBPrRa00VArQFmUkHBfOsMB4F0QLw\nLZEwL02QxZZI6BDg7KSiB31XTQqHpwk8n2J6h2ebRNMfuY+x4mSs/unRAWm1\nusL1InSzBYuCM4LBRXsNnrM66uBM/DlPfQhQemOqU+9UscMholkoIy9Z6Ffk\n53sXmOr2nDp+ebKdxYwnik3N+1clEgbmJ9AO6dZyTXuMHEib+dRox8ok1aLO\nVurL\r\n=5oAr\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4ca7ce4ad09eac90f32bba5b977d7993ed256002","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-08-07T13:24:31.168Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.62","@balena/jellyfish-logger":"^3.0.45","@balena/jellyfish-metrics":"^1.0.311","@balena/jellyfish-environment":"^4.3.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.7","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.16-fix-indexdef-generation-4ca7ce4ad09eac90f32bba5b977d7993ed256002_1628342857679_0.7719950266017375","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.16-h32-type-mergeable-9c831b4596d3e597c99d509a21a1ffc67f0b13dd":{"name":"@balena/jellyfish-core","version":"5.1.16-h32-type-mergeable-9c831b4596d3e597c99d509a21a1ffc67f0b13dd","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.16-h32-type-mergeable-9c831b4596d3e597c99d509a21a1ffc67f0b13dd","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"43570e7506d7e7426a942fc6d83956754b0b4d06","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.16-h32-type-mergeable-9c831b4596d3e597c99d509a21a1ffc67f0b13dd.tgz","fileCount":199,"integrity":"sha512-F5OYgzChrILLLS6dkzqtWGEl6Av8vy+BEO9BaH0FbHCjyKc0UZ1XuKIP8ROqSGWgvEp1SbqNSzNjgdoecAUxew==","signatures":[{"sig":"MEQCIHF7F2Mg15fVUSMgY4TR8pxCgorDQdbpCNBd/Z5sULKUAiB7CHyTxNqDBuCboa46ie4/wNSxI5DxoN6OsfexgaDHMA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":740010,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhEUcUCRA9TVsSAnZWagAAekoP/RlAWlaOk++P45IJVapc\n7rmlT++pNo7anTD8pcEdWuCMJxUDlJVuOyKawjsXI0h2GiNqNvlqxFodMMKC\nJUJUXcYIpbDgC8MeS7zMoGjKTLus7UnObmCaDcXU4TCyVih6CVEc3FcCeQVD\nAgD1BPcfFDa79Y5bVQhJanTA15zQWU24C+/PnyUZN0JUBu08ZOBhuk8MpUWW\nPrnz1674ZewA7zBnoTNJhw7QUvBjaxyi5abNigEAWnYqFWXpMVR2G8NvBlWO\nmSn/OwMLyXGb5RWnSVdkp0dK69V/T+fWZ8e2VG0JGnkdiUe3NM66qpCKfDJv\nsmd7BFRC/h9y08Mrb53Bcg63bZQAoD8OzhCVZe3rq65ccE913a6CJoi5S5vi\nG2qiNx4uE+Kl6uJiOb0Ig3XJRMTa/Yz9R2A91uuei/jsfFkUYpAPq30M4Jy0\niQUqsDBV6/oB7voOczR4Gld4oWOfNLGWVP9JCvFesGtsAw5HST5mbxtodrWm\nDjgQpTD+LEPh7NgfdiqhViGVDV531UlN7b29MJ1tzVLhN10rCS60Tlngj2xi\nsh4g7JBFblTGTIOTf6wHL/04xKr3HxzqEMWuFoZRf3TEdn5Hf67K8l/QMIIf\n991TrUBQKFIg4OK6zWm+CXcFIOpPwgTVzxcrrpwA/j7QvVssnIkl/SJ+gJ64\nUvhg\r\n=KzPX\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"9c831b4596d3e597c99d509a21a1ffc67f0b13dd","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-08-09T15:14:11.725Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.62","@balena/jellyfish-logger":"^3.0.45","@balena/jellyfish-metrics":"^1.0.311","@balena/jellyfish-environment":"^4.3.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.7","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.16-h32-type-mergeable-9c831b4596d3e597c99d509a21a1ffc67f0b13dd_1628522259836_0.0271947311965568","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.16-fix-indexdef-generation-90a6e31e127d823edc90930297fec9888f357671":{"name":"@balena/jellyfish-core","version":"5.1.16-fix-indexdef-generation-90a6e31e127d823edc90930297fec9888f357671","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.16-fix-indexdef-generation-90a6e31e127d823edc90930297fec9888f357671","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"bb0ddb802ea8d6f401bf20c09adce46cfbfe7d00","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.16-fix-indexdef-generation-90a6e31e127d823edc90930297fec9888f357671.tgz","fileCount":199,"integrity":"sha512-ioptR426O32kTzavzCcoa/txIjgs0xjN263S5eFnuWCBnfm/A+FM8+EDHOTr1+2xw3DTmD2ubO5tV7Wm3kR+ng==","signatures":[{"sig":"MEUCIQDc41XSWsuM/i7we+q/JFdXFCXVXxuES2LBoKGNUdnzkQIgKRv9+tqBKQN90i518nKWr9RQm4eG8WGZfZRjQVpzp7E=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":736508,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhEfJ7CRA9TVsSAnZWagAAuqEP/2ZVP3yeo0IL1uBPFueG\nK+CAK/IxOZVTCjtKED9e5JXi4kNyQFILFDK5ljMyDmpvvxMUeV+btwck6nUH\nJ7mATA4bINwRuRZ055/XLvU6KwqjEXVkNzL07SkXt74mAHgMuN5JJloBGa6H\ne+RqVuv62r8n9b3D+ZF5HVuagZfSiMCYIPpTlJpVwHZhS2BWXYXswX3g/6fE\nlC0zRxlOz9LdXA3VjfGHcMm+xyjW44O141Eoes86bYpTCPLh3KbeK2b9A3Uv\nZ6dRlj/KHQ3/vKyTyVkQC5MUtr9D/e9Y3Bxf+g0H89iN5LJgFs6C9e0onpn+\nTxPUUrzXFGmEZbbZMdkI9oXHk6eJBiJnY6vqMp8uQABpUdeB59m1oETM1SLG\npFp4vvJNcwaWFKyLakcvNMrz1YH/P5GqcLqh91AFUvwRVN9dOs6GfEXYRALO\nU3Zi+enN3jBEE3xJQN5zGWxiN5HMsmRb7wVbgZTHdOahUxLFryFYdPO/hAWg\n4g0KMRWU/z5NXeqXjp8HJr56T4m8nxVUoU1mQVWFumyx1wGrxlfGc/q2VgUC\n6c9y4qoWYeTvg6JJ8Ex0ukLQkUDJ5K3K5YOvvzCGdPvKmwqaWGibqyAiFJ43\npd9TSXKCKgFMMhFXOE7sU238f4XC4x91ZQ/D7HWDN0cwRS8ocOfn1BG4/ZOP\nl6Of\r\n=AI4x\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"90a6e31e127d823edc90930297fec9888f357671","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-08-10T03:26:11.951Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.62","@balena/jellyfish-logger":"^3.0.45","@balena/jellyfish-metrics":"^1.0.311","@balena/jellyfish-environment":"^4.3.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.7","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.16-fix-indexdef-generation-90a6e31e127d823edc90930297fec9888f357671_1628566139167_0.4509807002210564","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.16":{"name":"@balena/jellyfish-core","version":"5.1.16","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.16","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"118e4496d483326b7320f62f882471ae470388bc","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.16.tgz","fileCount":199,"integrity":"sha512-J3+hDbkeTQKFVA5KBxYemetrcO2FhlGQ5/uUA1qr7ZSa/mNTCZawnK+vtBk9/zdEZpQJgai5KfvIC67jrJZ/Ug==","signatures":[{"sig":"MEYCIQDXg//PRVohF5+ZxmVARzXI7QH6EwU2HN0YkWpuGFdI1wIhAI9z7xeGDeOLjy0kmpuXaXfNX/NVfZmiidOlUDwXaxRl","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":739950,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhEli7CRA9TVsSAnZWagAA+AcQAIJoq8l2/WVL9SQWeuIH\nuh5T4qkAcztLeVBDXU/zGsVNz/MfH0c6kKX/+T5BOXNIfhBPuttLF17AkFpd\nD6+Dhy7csCbQhatsEgEBWqWvinxdfvVThLt3juRDMfrxidpOPx1ovzRGoGqL\n4g8svXBt0kB1CrWNO3Q2e2bxOno8O6VdUip13AhPQeWKyG9vXsKuFT0nAlfH\nQFNQrxI+eC6wAz1Ixn9Z2WbpzxzzsjXYNhKbaE7QJ4Hos5xBsO0HrBefF68O\nS0eHRFYl8MMGyNc41swDxlazyQKg48/bK2t0fDZP9121COkSZ6s4v1Rmykid\nPntvMFDwyt1UDvpT7K1BidqLVxY43xarvpPO1AHZJdqjryK9+EQu29gNx8Oc\nvA7kC/23xltExUTW70aJcswcvA1yq/dry2df2YwcmiDE+/KcCty4KKZyeSkh\nrS/Mr/sNb1XDCKExAwM3zzJanx3jJ2byLOaZlbGUOktYG95+PVqWVyNk+5jf\nV2JMK2tElrjvyFQu3c1EIBS4FRggzkvq9CjZq3DZRvlFLTx0YYJ8tn1EdRKK\nu6q2HcAhvKUyi7uR1yLcd+Q83Shv9XEazrLsYAR7OkNURU3Fk6mW9v4jvx0e\nL+jRu6VKSYBCf5wx4Txkt/w+e7sbigthRyNuxN/e/UrV6zQ1IADpUuynv08d\npkom\r\n=n6mA\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b79150f87b0efd69ea1001715d67c1193a10e298","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-08-10T10:43:31.457Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.62","@balena/jellyfish-logger":"^3.0.45","@balena/jellyfish-metrics":"^1.0.311","@balena/jellyfish-environment":"^4.3.13"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.7","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.16_1628592314869_0.9489394520374732","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.2.0-add-scheduled-action-ef970ac469bdfdc39841d1bf4d6ebfdd1b05e395":{"name":"@balena/jellyfish-core","version":"5.2.0-add-scheduled-action-ef970ac469bdfdc39841d1bf4d6ebfdd1b05e395","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.2.0-add-scheduled-action-ef970ac469bdfdc39841d1bf4d6ebfdd1b05e395","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"40e0c535ba6416f831dc66fadb1cb51ba6a6c31b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.2.0-add-scheduled-action-ef970ac469bdfdc39841d1bf4d6ebfdd1b05e395.tgz","fileCount":202,"integrity":"sha512-ZkbPvWjprQCUE9WuByzBndUxLpxhSzZ2JJJnNphH7f+PNEB+Be6Gyvefv3u56t3UUiKUrA/hOFANQzONx0pJDA==","signatures":[{"sig":"MEYCIQDeMNgM+mr/zfS+8hoYZEV8peVtrZoIpIK8xYNO3y4oTwIhAMIBRWD6dymXMXPxblvhPk5JRvxR0mWkfiXa1sVi0beS","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":744517,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhEofACRA9TVsSAnZWagAAOe4QAIEtBLNb5EI6bDhzb0N6\nk3457OdVz8xNFbfEvD6aOOmLsTZBjdw5gkvOuedtigmU7rtQHfNSRO78kz2n\n3odZtAwMAlEJ1Nv3Yx3Pa/AhrvvPgIdgaCw/bjez78wc0IbkXd9DBBWmU+4z\nOR01D1811R3ZB2K/ZQ0+Na3GrdGiIdabp29hUuMuZsV7UrcNSP8GKbZ0AcKd\nLzFgyVpE2e9fIFJNyWfTiDwe2zzIfnBtTddVKOS5HsYw7qsjs6MCDjhG882g\npfwMsV/qB6hEgz1J/aRYSzTLAf6fy22CZQY/nZ1cETMKSjcuZ/gHUYNk2SlL\njFduAXuUc1ThQ56tluO08jCUqvSQyprzv0vzcqr9wIGcASFPtK+aBHpQXEd1\n7D3BfDnA6XjssRxZxae358KHI6nW/5on1Lwhmm+r4SaNr4C761ySHIgi0WTd\nQMeZcv3FwwUcDq/6ZL9T+UXp1lk9VWU9eooKWBonngcIeePwDjbM1UAfE5JU\nofVJln9RHmqfPwLEgw/491Vk3JZ+uAgdqRhj7N4+rTpP23uperN7i/WjAsvk\ngk4hErPNaVhTJ2CoGJnXbZtEXZLOSf5zt6BIQXM05ytyPkwua6jlyO6NZu1B\nvShkYjCLoQbv6Bo/qA5evzhPwGq63CvnY/57Y2frqaO4SGJCh+XPPXN22brQ\nRkQG\r\n=8cAx\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ef970ac469bdfdc39841d1bf4d6ebfdd1b05e395","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-08-10T14:02:51.215Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.62","@balena/jellyfish-logger":"^3.0.45","@balena/jellyfish-metrics":"^1.0.311","@balena/jellyfish-environment":"^4.3.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.7","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.2.0-add-scheduled-action-ef970ac469bdfdc39841d1bf4d6ebfdd1b05e395_1628604352639_0.4915775014296584","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.17-fix-indexdef-generation-27d3b939390a44c59cc101fbbdd96d8867046973":{"name":"@balena/jellyfish-core","version":"5.1.17-fix-indexdef-generation-27d3b939390a44c59cc101fbbdd96d8867046973","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.17-fix-indexdef-generation-27d3b939390a44c59cc101fbbdd96d8867046973","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f4caacb1e096a74ae81814e55a6f7423ace9f3d9","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.17-fix-indexdef-generation-27d3b939390a44c59cc101fbbdd96d8867046973.tgz","fileCount":202,"integrity":"sha512-wvug56xCXARkrZEQmOMRhbVRzGrLn2sgcRJJ/tsdxtmu5dCzoKznEzBXHgF2e2DtTSEgkugC/qie1GnlM6CTwA==","signatures":[{"sig":"MEUCIQCTVef2HKO1ogZOVbqiXTQkbFm3NvaMGjLdLCBgX/nHhgIgWeFHLBQX+tQkYZWKXeTIE0rR93yUGnL09dO89RK69F0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":743385,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhEwRzCRA9TVsSAnZWagAAGNAQAJhEf2cMO7Xi592wX4ml\nWhWgk48ZvyEjkasVAURpRjU/WDQA8rCmHy8BL/iys24UuvtES5uLrwJBxUaN\nKaVHIZIl6bGSuqs29Wrx9463wQlM0gsFyA79905X6KA2ygYxg6yOqSLSn9xp\n+c0e8mgVzp7eBZ91OZxmkIlmcuqpF1hzZNi4c45c7xw1O0bm3zkc0u7CzkOs\ndczeCwo+ZAWTUB8Kku/yEIOW7w4z7mDCCkMqsMcHvTGns57ES//lH8IV9QBZ\nnfN76/2BLNkxTJL1AbWmLuCSTTu+TtZHLWKr1oO3SzRzxRCEZtJmIhd+9VTO\nQDXS4yqfkV+nrnr5/zgPQXZE9761psIGd87koihVSep2hkVYPWzxIXjAvjVu\nOpr49f+hu4cytQUwCPAbqL7hkzIbFyr24Ih+cY9zv2ynbe9gQMFyOjhGjd4s\nISLMi2c268rUvq3y8HFtYUUw1tNGFTQbztkezq+vJo2sEdhxLbE5hsYqxqdB\nEVPo+IoqXbWbbINHjwVH5ix9ZhaSBmdfA1i+R3HKFT1a1upBDCQJ1MiRJRO2\newhWcRWNM1QiVFn887uy7mCrD5Bzswv/6dNGWs3sGyAdEImr/9j/1/z/Jmyr\nPhi4Hfcol2QozMtWdA9asDEPyYDEM69DpPKrnbTfKc8GkmBN/eHlN+1Mhta6\nkjej\r\n=MRg8\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"27d3b939390a44c59cc101fbbdd96d8867046973","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-08-10T22:54:41.878Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.62","@balena/jellyfish-logger":"^3.0.45","@balena/jellyfish-metrics":"^1.0.311","@balena/jellyfish-environment":"^4.3.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.7","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.17-fix-indexdef-generation-27d3b939390a44c59cc101fbbdd96d8867046973_1628636275442_0.3144148510204121","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.2.0-add-scheduled-action-a4536f1f7e5ef18e77827564308dfd99884dcc61":{"name":"@balena/jellyfish-core","version":"5.2.0-add-scheduled-action-a4536f1f7e5ef18e77827564308dfd99884dcc61","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.2.0-add-scheduled-action-a4536f1f7e5ef18e77827564308dfd99884dcc61","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8d425ac8cab7fe1d611a18242126c90b12aefa3a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.2.0-add-scheduled-action-a4536f1f7e5ef18e77827564308dfd99884dcc61.tgz","fileCount":202,"integrity":"sha512-a3oNhCVZcT/1HOym+017IvwjXQVzQAqg4/Hx+/i2yyinaK4SDl101J0/VqM+Z+Mb00jjq4JqB/oyohGjQ255ZA==","signatures":[{"sig":"MEQCIFaXaYb0ILiR+azCKXwtOXahvqz2XcV0oZviiCJCGrHRAiA89ZMnn2p8yo3bM/iukdeDtE89HWxSXjUSMHBYtm3Qtg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":745198,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhEyWSCRA9TVsSAnZWagAAC0AP/Ra9mObkhEUJZFx5Cn8c\n8v3v5hUI+JBkTUpXE/HIctmFtQt6HzNUtUDA3hpp6QSmQZZ8Nbpsgideg7/P\nCRMr+yyVo4f5VZ0r9/xoD6gBiGex44mJ5qjoWwaArktgPYQWNeRp8Ac6F3K2\nzgCQ/KX3W1LBdISN3qsvMM1DIle7HghhYRCB4CQhGYcGiL69nVUoSM42q/vx\n/g/g7ZrUW7tsaridcs7wwK2tyrjvFyXK5WDaKdkzXBTNJVB58PPIimoUP+6i\n55hcox0eW+vNX4+hVHnCM86Ktrh5DOkBmxEt/Q4z2mmj1L+8SrBvktGxErVS\nlqgKMFOEDwSHmNRkvZ1zWsD/RpmzoQHJaLExdv1UTR/iLOozc01WVwp2fUb3\nA90yFYeMojFXHmHdvIMGU3sUxT5btmWUQhrHvgPsfqWSEPaw76w8PwsVGb2A\n7Dl0NS8hMkLS0O4M5L885DBGTiTq53a/uNkeh3uEovttBBG761U6grBwxD/2\nuBOhJHqB2EWWAbsgZY5tGu/kifrxuBykoh6/Tj/7tTeSOHmz9O6bUxMhXkZL\niUc+HVUHRSC666tDI4y7PzQzQ6YoO+Ihw9uHAUp6Sw0FQggKuHxS18JrSpAw\ntBDYLypN7MrM/H3qg/vLoFe/gdKxgWGv+t1qkaeX1Za7e9T8y8hkdCMR5FwS\nsPx/\r\n=l94z\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a4536f1f7e5ef18e77827564308dfd99884dcc61","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-08-11T01:14:58.003Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.62","@balena/jellyfish-logger":"^3.0.45","@balena/jellyfish-metrics":"^1.0.311","@balena/jellyfish-environment":"^4.3.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.7","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.2.0-add-scheduled-action-a4536f1f7e5ef18e77827564308dfd99884dcc61_1628644753792_0.5265577887559556","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.2.0-add-scheduled-action-b70346d69800063ef1439b2c2f59acbb68919435":{"name":"@balena/jellyfish-core","version":"5.2.0-add-scheduled-action-b70346d69800063ef1439b2c2f59acbb68919435","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.2.0-add-scheduled-action-b70346d69800063ef1439b2c2f59acbb68919435","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9c095dadcad239231510660e8e60e5151e559c8b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.2.0-add-scheduled-action-b70346d69800063ef1439b2c2f59acbb68919435.tgz","fileCount":202,"integrity":"sha512-bq5ilkWwd5YlO0cH/G0Bw9103P9oup63yja+UdkMmsLdPLWc6wvSWV+HoBYd8DDAPy9isj1Igpxj7PXYJ65gzA==","signatures":[{"sig":"MEUCIQDEmzwsdWbzwCW/kHtFreBD01dZE8mpKZlYdw30kibnYQIgdzSk+YwfsI8wsTwqvHMH2IjF1P6/RjC3XGrTxp+S9b8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":745073,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhEzGyCRA9TVsSAnZWagAALh4P/iGZCI0j2OfwRXwd7Cfu\n/ZDfR6RlRE0oBsCNPP2YGxiZoJuT2TwnB8acngM0IjAVsvct9ugcVbluYNny\nXnkvPva1CTdsgvnew/viUnaxVKYnOQ/vxpyLlBUh4kPQcjcSmhRLlEBnu7br\nkUyJFVA2xEo1K7aa7qwmx5zPFUT6z7fsbG1GVdK96CmmmoAsn/OrLgTIMdXQ\ndWx+yBhN3wV41v9C7h5tlezp/5aMiighMCKOX5z7j16pt3Qgq0faJW4AnOlH\nn8CLNV2MtvDXCh+PVlkGVOyLu6jl8/BjyTKnDQEARe6wj5UHjx2y6KN+bglc\n9MKZU6VbaoC+wtBTf8SrYMHjtLBWFjZ2lPOzrFBxSVXqJYU8M/nXqyWVh1fu\n4YxffqdD0OBSvopKve8i5wDV+6T/zyxd7QnBIX1zo7YVo3jpGMnJIGIYtxjS\nJjhyUFTII1+WX4TUI6e1ujbBdX6zgvmFYnmfpDiB3ZOL898PoJZJyy+olhH5\nj8VLoY5Nc+zAnw48zLsQqtly/ixNlVwbCzIkSsGlkEeVUxNjYFFUTwNXZoJG\nG7A1Bw4PXzfS2KuGZPi1bX/R/rc14KB2s/nbR2LcXbzCediUX29soOEKZx7J\ntZfcJ/8RseVcugR7JYscXyvELGSgkRl8NixWQuvLiyLhkAjgpCc8GIn1WebV\nDEf6\r\n=Tirj\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b70346d69800063ef1439b2c2f59acbb68919435","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-08-11T02:08:41.689Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.62","@balena/jellyfish-logger":"^3.0.45","@balena/jellyfish-metrics":"^1.0.311","@balena/jellyfish-environment":"^4.3.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.7","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.2.0-add-scheduled-action-b70346d69800063ef1439b2c2f59acbb68919435_1628647858155_0.859261727382044","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.2.0-add-scheduled-action-5f5457dd622ac01b4a3e0261bb971471b6f6cc43":{"name":"@balena/jellyfish-core","version":"5.2.0-add-scheduled-action-5f5457dd622ac01b4a3e0261bb971471b6f6cc43","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.2.0-add-scheduled-action-5f5457dd622ac01b4a3e0261bb971471b6f6cc43","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8934ce18340474e31db76cf483d86bfcc0ef363a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.2.0-add-scheduled-action-5f5457dd622ac01b4a3e0261bb971471b6f6cc43.tgz","fileCount":202,"integrity":"sha512-Zm/YyJISsG7SGng8xvea7X+FbP/fmrY/QOmNtyceyR5RzVWPVORhZdlXhLSoPu1YlQUkHgKm90InJf72dLcT0g==","signatures":[{"sig":"MEYCIQCeN+fwrjNHr+i6cQaYN4n51e91zG1oDDLYLZpal0SgbwIhAJyVN3neHOHes3gaXsF/diIGOZLKfn108VCetCMJXtah","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":745650,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhEzqWCRA9TVsSAnZWagAA6xsP/RgncsXK1xvSarNjmCdK\n8QHIauWHoznYmtLF8hx81aNaE7lQaG2fNTKjQUKObNE10Kr0ZHkZgtH1x9uY\nP/3QcgPSYbpwBhtwvOrO2n4CJ++vfZgggvu0RR54JBcoJ+cjnkN3vr20izF7\nNBl/NM8cxNJWlge+dJ+XHpu90bUguOqu567xro0epQh8EZlXM5iPmeKo/c/e\nf8fQdTWiieTM6MBry8MYTQ/2XRrtbdhrsf3lVDtn9nnq94l5sL3goKG/7Xqk\n7HTArn1yLKQU7GElsDuIEFE2yGz3UhmwEBmavLqnvas8vxd9NpUzwkSmWPJA\nNEomp21q3NNDAaBXHJDE/g+RoMh5umEaemkb4Ieaq6S4NSS0wAuAAd7wiLwW\n0LI9lLsm5HSRlnKRFqwDU6K8bN8a6t7SIgh/vQThGVxV1yq2vuM+Hu3UYrZP\nl6Pj6D/5K7g9/ItpFwRfYZ9yDg334lYZ2SDB+R18OYb8nYUCpTScYlzvHRsi\nmg27uAlFDLXwGwE5Fpeu+Qjh6k/FvdtwWxq+u/5vjCAVhU2ijpXtGhAhfGLk\nCFCXaRX0DOfa3WyPM/TUQQEMEfa5mE+47eFxqy0OLcXvOWYXG56tl4Ago8v6\nJoIZM6t02784mLdQjemPNugAAAZALQx0MT/FNiUGAC1Wf01nVvp8DtsN8Bsm\niJx7\r\n=Th6q\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"5f5457dd622ac01b4a3e0261bb971471b6f6cc43","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-08-11T02:45:05.565Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.62","@balena/jellyfish-logger":"^3.0.45","@balena/jellyfish-metrics":"^1.0.311","@balena/jellyfish-environment":"^4.3.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.7","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.2.0-add-scheduled-action-5f5457dd622ac01b4a3e0261bb971471b6f6cc43_1628650134514_0.9904901620105626","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.17-fix-indexdef-generation-55315978d1a83b6e1e397f7080d93685d6bd1473":{"name":"@balena/jellyfish-core","version":"5.1.17-fix-indexdef-generation-55315978d1a83b6e1e397f7080d93685d6bd1473","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.17-fix-indexdef-generation-55315978d1a83b6e1e397f7080d93685d6bd1473","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1208ab70ba95ffd3f24314877c41f74b2ae22eee","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.17-fix-indexdef-generation-55315978d1a83b6e1e397f7080d93685d6bd1473.tgz","fileCount":202,"integrity":"sha512-66YebJNJxN1r6D6i62gDraCkeqMw3liZWBlBYTLN10P5L/kAdzfxvWv7+LFCZ6aPbmZecG9ChE3YIhqffgMt6Q==","signatures":[{"sig":"MEYCIQDp8TgoBgTkRrcNs8oesnugeLI9iGZz5Plnv4x5xMozdQIhAK7g6mzlfWqAQbDs+s0g6c/2v3KU/5PxxK92qpXn3307","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":743455,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhFGkcCRA9TVsSAnZWagAA22oQAJGnJw7XSnnY0NMwUr7O\nePw1OD555fXsS4Qg4bEcOdMR+vSOLKRZOGMRIsQ2mSjfgypF8Az32JE1WfpF\n+NMz7i0KL1zEo2+YO58y1SzY7nGHmGKcCv5bjq59sCRhbB9axJFSHQbI5+iw\nmCc4sa15NtYwBbjS7gT2nvlxX96Ao2is1vRkYzknclNTPqSLBVw4JHrVfvwH\n368hI0ztgt6Ki1swc9qzI0TeZFvcRJMZJRWj1TpM08+jWZHyUrQ3fZQdkQHf\n3L/31MY7fewZHVUNDO8sdJTLMSVgr0OpNTkuDa/4EYiDDwMJlUv35upD4OSl\nO52bNX9yv3c90KoylsdCBAV4z6Avjobh2hBUIRtq3EPqdwoW7ANchvutb5Z6\nvsaU2UP2OyTwkdtdnDiIoUDXACP9jPDT0nqrRNwBcJ8TKP9nRep3NgU9nRcD\nZ0VC6R6X6SLWuMRoUTs1Pdv08T06Kfq5XeaM4d5r8aGONnrMrVGmmCEeOuvV\nkXvfv0nUvoO6HuXSyOwtp0BXe0wLD08lSZG7NynJ+4CUHym/N3Z10YOEbTz5\n11ECL6YFHUDwc1GFZEbmXv0SDyD7raZETYrY3VoC2W2jA5Au942ZVJ7MaGuk\nqnMeo4qQlBRsIkP9xuEO8Ekovq8g+iXY3jQ/ARKbGVseowf7H5jatoSLnReP\nJWzH\r\n=z4NY\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"55315978d1a83b6e1e397f7080d93685d6bd1473","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-08-12T00:16:51.558Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.62","@balena/jellyfish-logger":"^3.0.45","@balena/jellyfish-metrics":"^1.0.311","@balena/jellyfish-environment":"^4.3.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.7","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.17-fix-indexdef-generation-55315978d1a83b6e1e397f7080d93685d6bd1473_1628727579828_0.9657362364397641","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.17-fix-indexdef-generation-db5718907eb0a890fbc2cbc9a2deeb4c611ad2fb":{"name":"@balena/jellyfish-core","version":"5.1.17-fix-indexdef-generation-db5718907eb0a890fbc2cbc9a2deeb4c611ad2fb","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.17-fix-indexdef-generation-db5718907eb0a890fbc2cbc9a2deeb4c611ad2fb","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a12b57ebf3617ddb8c489617637dc3d9a84883d1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.17-fix-indexdef-generation-db5718907eb0a890fbc2cbc9a2deeb4c611ad2fb.tgz","fileCount":202,"integrity":"sha512-WZbqb4ZQgPZui1C6q6HOXvtHxOL5juC7k9d2/JhFA9haOkBN/86RScarts/URmkfBx4S9nzq+x0EjSw6qPiUjQ==","signatures":[{"sig":"MEYCIQCkYtBxEaEJm4vmivrzM/s3d4LQNv0i9ug3nxHyDuOBpgIhAKbFoaWmNpX3CyK1qTh/IYrMduz8Us4jtR3ca3iCWi70","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":743326,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhFHwvCRA9TVsSAnZWagAAfFoP/R2jXY3TtYr8zq1g930Z\nkvpHMOEEtTqsH13m4gDJEeNaElsaC65SPMEJXNsrGENqvAc4XSdzdkC7+mtX\nXUsTnW2YK70O1G31QNmdZkQTiBGtVEh05oM8LGBgvlNXTi/Du/qmFIgapRCT\n5oK8RJ1IxQ62q7XB0LBHlY1uFwVN2WTsj7rJSqXd63TD8M/ECfoMTSQC/Eom\nwgX5uEH/WZEfBKk/sCdoPyQaAwR1PdvYNBhWvTTReTgNcfZAUCFPnmfgZzg0\nQi6jhBh/b4C5nHOEVuilT6eQ0f8N+bprOjFzBXCmNPKUJ7i6Le9GtFn2bg+l\nSuvEMoN7mHWojUVjSDWnVjtN+FI0Xz1QYZfAEViPiJJzoCy12rUbcdxAmrer\nB6Vq9zJM3tK1ZqxeiupZoNsK3gGezihV94ZDklkdjKHC8KSURyQqGtOJcdLA\nUDNPrWsNmb2uIuDVX4kS3+cFRFBVFsZOWsG+w5TygZOdzvIVPt5DzIJ+a0vi\nvBH8L7LeVHFqpaoceSF7WWzcVnbueff3jLI4VUs3WwiiUXCkCId9O5Eh+RNQ\nMq6BBA4UYGU+SCT3G2ya/uHNBbvcY9OQbDx1Ezk+RvRIXxLM2bEKD3Sf3kp6\n2oUMDaPB56dh/YaL28V0UnxAm/Vmv0rGBFKyJI5lOSgDluNRlAZ0+oDRl87e\ndSIp\r\n=5HoL\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"db5718907eb0a890fbc2cbc9a2deeb4c611ad2fb","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-08-12T01:38:11.391Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.62","@balena/jellyfish-logger":"^3.0.45","@balena/jellyfish-metrics":"^1.0.311","@balena/jellyfish-environment":"^4.3.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.7","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.17-fix-indexdef-generation-db5718907eb0a890fbc2cbc9a2deeb4c611ad2fb_1628732463045_0.5963108154899517","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.17-fix-indexdef-generation-1b65cba711ec3119cda9d2cff373a5bab29c3f62":{"name":"@balena/jellyfish-core","version":"5.1.17-fix-indexdef-generation-1b65cba711ec3119cda9d2cff373a5bab29c3f62","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.17-fix-indexdef-generation-1b65cba711ec3119cda9d2cff373a5bab29c3f62","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4c97848610a956c126f58c3cff754300881f987a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.17-fix-indexdef-generation-1b65cba711ec3119cda9d2cff373a5bab29c3f62.tgz","fileCount":202,"integrity":"sha512-NZhUNN010AbIS3XG2XgIeD0UlDI3KpPyHs2YJyf8DbXlHO2eC2WJhF+nQXJiXaopKHC9Imd3j9V//VA5leDv5Q==","signatures":[{"sig":"MEYCIQDVfiOyjNRBB9mKI/IDNEWnf57ViVMeNWsNLpAFHV9SHgIhAO0r/bx6MYGqQaGzo0WmaaTlK5lNF5XiMT75WjkHKCOm","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":743336,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhFH1JCRA9TVsSAnZWagAAMLwQAIiVB93JZdgg1K4Ung4C\nn+CK0UGDyXJCkIYxvt9k9BsvGG6DQdRD2XKrdCnsCSjrG1CGfYiw1JrTCgf8\nPFgOH+3YHYnrwC7cunRCGcho5hGXBG1NSsYmJtTEP0PAADrQyGy7f8gzK/XZ\n3r0IglVXKULg4dIWXNS1qrXhcVdcqaRrZYKvtyug9Zfj1+MY84cC4+jdqDwk\nr7PATreahkso0bPJgVcWFXY3L16Rgx1BsDZclokCnDGvZwxT41Uw+X5gkiE8\nsp9SZ7owMTR8KZEeuAzW0OERStpapYpJOjKLmafp8jpLso7DB3BlIFN9nXS5\nDLMPIk9YI4KrqWzx0d1HfA1mHTqtCXb0dKeUj1EfiBrZPGbgPiSNK9/TxsE9\nS/kTx/4WWDh0SXECsxXrT1zOTDJRW5avcS/buKoi36Y/8HjFPW0ANaEdRqHb\nR3dgSvPzQggTtddCq6P49B4Ya0+4CGo8aq/lypqfD5DknSLeWUI55U/OdIDh\ng56gPLSdnGr1cSjS0aJ/IoiDmQK6iqKrHQKwqj5GRRsSfTGWRlwOwWNTDtVd\nlEW7d1iscIT7edxfRdbsezCUftBQm+UPQmo1hrVBuzkIMhprcKuX/EWgn5xM\nk5vbOjZmyhDWibc0pjz0pZEu+dOfXftdatBAE+EaKME7IL0RVPMoouVUaBRv\nkMwx\r\n=wQgv\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"1b65cba711ec3119cda9d2cff373a5bab29c3f62","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-08-12T01:42:31.448Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.62","@balena/jellyfish-logger":"^3.0.45","@balena/jellyfish-metrics":"^1.0.311","@balena/jellyfish-environment":"^4.3.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.7","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.17-fix-indexdef-generation-1b65cba711ec3119cda9d2cff373a5bab29c3f62_1628732745194_0.2659473849974541","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.17-fix-indexdef-generation-fbf98a25e5df2074c35709e9f8f3957370a1679a":{"name":"@balena/jellyfish-core","version":"5.1.17-fix-indexdef-generation-fbf98a25e5df2074c35709e9f8f3957370a1679a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.17-fix-indexdef-generation-fbf98a25e5df2074c35709e9f8f3957370a1679a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"94fe4d3a49a208e8da79a9e84eb7fa2e8c77c3e6","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.17-fix-indexdef-generation-fbf98a25e5df2074c35709e9f8f3957370a1679a.tgz","fileCount":202,"integrity":"sha512-GjQNrg4s08pyOqknxflV1o3ibDbcK9zU7kgyDmik29YRgjgUWettWsy2s0f4f+qZw+EwcgkNsQ8H2fzgcs49Pg==","signatures":[{"sig":"MEQCIGnSUKIGewPrzumxAjLVNcrkzxqY/zS4Regi4dMjLpWUAiBJ/xjXl6NIckIVfhOMc/a9xepxAZjR+E9xSUtuvPEIbw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":743346,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhFH8mCRA9TVsSAnZWagAAu5MP/0ao9wYaITwTPi0jv2rw\nug3a3PE/y8GYuQzQGANKO5Ox6UnvgJlNqKUkk4vWiIdk11VBR6/Cs5YVXJ4m\nvhCYPT1ZNI5L31vVtl5rbqLHeJ+dcZE8dzbK7/h024ZnLW3gsORVteIIP7at\n3oq+yFGfNGE0r37ZIzv0Ykn1VL2HeP2RsCHVKQxr09ZiWQadf5TRsgOO7lLL\nARvI25EhK9fEFfSSpofw48+SYPNt6/JzG6vBXVEfMuNprs/EUUI5E7PM/S85\n79XPoA6ZOCZxCxoZKFj3ux2Gziu6dr93fLrV0V9t4CbQmOY45of7+IfUcs4V\njMnT1xy2M4/cykd4mKdXux4lCQzBO3ENJM3ZpAgrvsnt6dUhyr+iKrcGRpSA\n4IsDIkdniqaMAh+tGL8JjWV3UEvjBPz4kMOMemevU6XoOnaVJ59rXQJ5SgVF\nqys2t7oJEtXoEz/2TlJ3Ar60x43k+MSiYGzZVrYHRrPy9xoleYksqU3vpzfa\nHa/vpu3PfYqvQcdjHF4GPWpVoKiIyctX8P996/V0NlOwjZEyj1T17RhohHfg\nXl9sSGUwSY7GKJVHTft/cnGm6G8h9XwDAidLQJ/EdF6oGxoO8G8d9cSwUtud\npiu61OgUO5A6hZpewx+j0WiJ21WgEJhbuDkEddb2oOn/zuT5UqXiyJfKDknp\nQsLe\r\n=dtQq\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"fbf98a25e5df2074c35709e9f8f3957370a1679a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-08-12T01:50:21.449Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.62","@balena/jellyfish-logger":"^3.0.45","@balena/jellyfish-metrics":"^1.0.311","@balena/jellyfish-environment":"^4.3.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.7","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.17-fix-indexdef-generation-fbf98a25e5df2074c35709e9f8f3957370a1679a_1628733222045_0.8125572285257034","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.17-fix-duplicate-views-34f4da5d5ede3296b079a57f1264d86e8af878f1":{"name":"@balena/jellyfish-core","version":"5.1.17-fix-duplicate-views-34f4da5d5ede3296b079a57f1264d86e8af878f1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.17-fix-duplicate-views-34f4da5d5ede3296b079a57f1264d86e8af878f1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1252bf8dbf897a98e5e936ed269ef30bab70a8fe","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.17-fix-duplicate-views-34f4da5d5ede3296b079a57f1264d86e8af878f1.tgz","fileCount":199,"integrity":"sha512-72U1/+oQV3zLtM63hpoEU2gmrY4V1qpd2Ff5hV/+dhuni7wzUgpmULbftZQulNYOl1kqzwQXrValr8N4B46UTA==","signatures":[{"sig":"MEUCIQCuRyGwUQHyTWv5CkyRRHZ7rz3Hk/GaR0ROapWpWCifgAIgXUprIhWgEvjxzCA6nkbsNi4dfvLCFp6WRdy0IMvTliA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":740125,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhFTD4CRA9TVsSAnZWagAA2jEP/3letkgkl4vlp+3xtPU9\n42sIcskRNY8c5xYyaZGaYxJQSgF0hwkKGleVxQdan+EQ1NNmFAvXzLSwsW1q\ndW8Tw4xCUnKUoqEYUQNP95lMCOyS2dCLq9rZ2ejB1PZeBPbyXt2mBEjVDEZr\nCcezrXx9wYp90pqmpHpYovNTEx3IZlfvfkF8nntZ0pV686VJALulYx59AtEC\n/N+eGhuz1hQd1rr+Md6gStAtZ8mRD8lMPcsssawK5hL+jKBxYHqwy5DbIAcr\nUqaC3UWrdMl1qTvaDrSVoSbZTWD0cnTLxn/QNkHtT2dj6XZfK0shyO6f2dEE\nWQTcmcFsznkDgQTDueUJ7rdJH9022DaHciVyUDP2KjR+yr+qcmCUWWm4ZyiF\nJt9mCM4jdPvGhHgw+77iV34ykluOWFKi/IThtFsE1LD3x31MPrbyZ8luQphK\nw03fL+GThwHlN4xyse+IF4z+BWERr9qs236hCWkJHDNvI0MoB57l5X3kB1hR\n0HTPgfmJB8vPafZ8KIillKMycePYx6ECW7CQThxE7RjKeRM7Zy1GVvmy/zBq\nhRzE/L5S9/wuF4CrTxI6p3Nd9/42E9mg6ZeyjHHyIBt6zznyg0wrdotjLwqt\n+8u0/x7nXteKYpbWraEZRhVUT8rOVJV6ac/LHsNT2oVbEOtE/n4SoeUDHpc9\n9xxs\r\n=sxPp\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"34f4da5d5ede3296b079a57f1264d86e8af878f1","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-08-12T14:28:10.965Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.62","@balena/jellyfish-logger":"^3.0.45","@balena/jellyfish-metrics":"^1.0.311","@balena/jellyfish-environment":"^4.3.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.7","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.17-fix-duplicate-views-34f4da5d5ede3296b079a57f1264d86e8af878f1_1628778744633_0.6655945831135932","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.17":{"name":"@balena/jellyfish-core","version":"5.1.17","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.17","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c31575f286584486ce4b02cbb01a17772ef25e28","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.17.tgz","fileCount":199,"integrity":"sha512-URxqO3QoQdIhqs5gOPN8h0AuH9m1PbX2UPHrXYVpPHUK0RlsfnOd0TJTBPcEzoCDaB3yzsz9EEqibl+KmSgUGA==","signatures":[{"sig":"MEUCIFd/Xxv1HDdcadov2qG+XbMWtZojkBYEuZoebwklzBDhAiEAhS9+daQXl+dVLbzGm7nnclzlK8XVRhNVhcmLf1Wjbcs=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":740064,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhFTUgCRA9TVsSAnZWagAAqKUP+QAuwlbYRGuGgkmQQccC\nR/vSKlT2pTpnQnv5y6KTn8ErJ4uHdeJVKjW7MYkb9+lg5/r2iJyha042MVfg\nd9rQ3jTqib0+t3fOQF5yJmCmzyIzONvDev6YG2/xXviR1apARwOG9teX+2YY\nf/kD/tumnnxN8bKH+3wkMkyfQwrxhPCqgk0/ATQAzsEyO1R7gzXn3RkOze6+\nBd7dr2rnstYCbHuWR22c6P/JiNO+cOP1EcBJtOWA92VosRqoJP1WClyOf1kh\nE+tNGi6L0uqwcCZHndNYdsP20y1iOlRnTZLTXWpvz8zfJ6CtVbAVPlypnfWm\nWaPDtMo4fWAnHfSJsMuappxB+or7b2yQaR/GoT0nFyHfFv5+5VFrwuvV+Azo\nzrrKNZcb20iHDUpyvXhtUOTq4g5hpO4wzNYvqIP9ojg0nVDFpXBpEVXFrNs1\n4k9r0pwR69YtswCCN98cIlyyjToO1WuTShBCC0ncMsQv7oYKvHsJpLceiTN3\nYMyPO1ZRpovM/XWebwyM+x9ifs55hrxlJBUxT38hhvu6gAe+9oK6zqsWis1Y\nnRSTzSJDQZ3wVpj3Zi6SmI7z+G9fN5Bk5CFnLlYDykmexXV0FuMsXNCDz290\nMpn4oAklPXzq0IqcvBpgmgKsKHjoWsruCN1D9ceGi58bOAG6iocgaYj1ddgV\nFXTv\r\n=qC5Z\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6256710bd2562743741376429c7f25cd7e633ecb","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-08-12T14:48:21.779Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.62","@balena/jellyfish-logger":"^3.0.45","@balena/jellyfish-metrics":"^1.0.311","@balena/jellyfish-environment":"^4.3.13"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.7","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.17_1628779807862_0.2821573589140911","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.18-fix-optional-link-nesting-cdc30113f390449e40dbab1c95c9c3a0a06fa4ed":{"name":"@balena/jellyfish-core","version":"5.1.18-fix-optional-link-nesting-cdc30113f390449e40dbab1c95c9c3a0a06fa4ed","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.18-fix-optional-link-nesting-cdc30113f390449e40dbab1c95c9c3a0a06fa4ed","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"08940599995921c90b580d13d5b5d35450becbcd","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.18-fix-optional-link-nesting-cdc30113f390449e40dbab1c95c9c3a0a06fa4ed.tgz","fileCount":199,"integrity":"sha512-M9tsp6n53MUOVLRE7sFhnx+1Sg8JsDJWAx84ITMbyTu22HUqCwmNE+r8JpGh86++J83hBgfFT39iA0eEqr+eZw==","signatures":[{"sig":"MEYCIQCGm3BV5QkbscRbHCACy2Bt21yrzbzh1WuGbGKnKa1YOgIhAOfQqzQXgKoGyJBQfSewNmKS4ZWVjauv/wGdJb0MPDUd","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":740131,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhFTa3CRA9TVsSAnZWagAAT/EP/ilpUldlLZY0JoQPOQS5\nByUwEoJ903l38C/s5ty53rYT/pVDSnAhExRP6PZeUF0X+OB5xf7yT8QSvzwY\n7gS07DfKtwvJbZmFI2VpoIm9PTqXRYgsZ4lf8f1KvXPQyD9jj+FnRj6RgKiU\nXLp5MVP5B0lvhBiPL+V7LIFR4i+Xv/8jb68hbtt9BiBwYN2rawLawwBXl4kT\nZ9w2uBxGRJoWORxyw6UuXo+DHGIyXaDZI/q+L2aNhD+yWdAfhRI/rFL/TiH+\nA8q4Ejxo0uAcICJLPO9SZj+bb6YvePmSYwgFzN+0HEkaWoXEu0x6D1qJNNjs\nqeLF7gaQ0m46X0Cd1yKBT5ZSpNKF4D9K9K0KSKxVJmY2Q/+GAN0kfAUvscva\nCIqJ3Ce5BDwWcnLCbrTiDnPBMeko+WAQ97XMlB3IqQBvyRnDYtVKcEKiG89H\nLqjly43DxduWJnm0/uIrXAaCO7FoCbtr27H0wTncVEqz9rse2elF5pZ4ciqY\n5X8zs+Z0e7Q0rJpszdigK+5apP7kOuUnWXUt59Nxp5W777V5GWbPx+0pMCYh\nR9db9jOkod23T79sFNYeAoZgoPfRszY+DpbrKqLSExxVCGTf9PxAhUP1QhAA\nCQqGRjRfgYqEGtdvTlNfXJeeTgO5vQYvzZ4r7OCeeZxgHlg0x7DJ6YmdEUDr\nymaV\r\n=uPsr\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"cdc30113f390449e40dbab1c95c9c3a0a06fa4ed","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-08-12T14:52:52.667Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.62","@balena/jellyfish-logger":"^3.0.45","@balena/jellyfish-metrics":"^1.0.311","@balena/jellyfish-environment":"^4.3.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.7","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.18-fix-optional-link-nesting-cdc30113f390449e40dbab1c95c9c3a0a06fa4ed_1628780214894_0.6261718410639812","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.18":{"name":"@balena/jellyfish-core","version":"5.1.18","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.18","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ebd51f7444c322c994884ae9c4f08b472d807769","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.18.tgz","fileCount":199,"integrity":"sha512-M68ovLp8/a/SnjS+Wae72bJCjjLfn3gfEGz8b6upH6S7h7ROZ2qBQoG4YD1lgFbmAS2ZZaj7pK7L6Ax08Oeh6g==","signatures":[{"sig":"MEUCIHCqeX+aSbHvQvRo8IWzbYAxV5oyEFTaoTOJ8wBes4QRAiEA3Tg1p0ePFqG3a008a9HZ5+rkAIpdCHb75uZJ1d+t/VE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":740064,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhFThaCRA9TVsSAnZWagAANZ0QAIYiKbHrl33rrZzlwdfH\nsTnyxWPgEjWho0LnfzdBhIJ+I32j6XKwO4nEKqLyWtdx7cQlt3JJCx2wIm6q\n5rprQ0CPuwadHJHL1QvIm90aw8wD12G9VPmXgcm5pTAl8yy1wbvQ2H/LIjFB\nntjW31kQiDDrViM1jgvj6mWX9Eh7T2S69J4T1wUqf+UbkMjGIXJf2iBTLLqW\nthQwJ46W5c7L0qMShHsvQZlgryGFeqfcpF7Vfwr/B58HYkGdELv5SBNuNHTS\njzI0XQh+k28SWG+SX4cXG7TVJikTurR9HS7HkRiSZHcl8+kNIB8JXr/KjuHo\nG6BXpJ05pVHJB5Ka80LRziGwXwnTf/0uqvap5BIk0VVoSyUWzCnhWT5no4r3\nr8KdlT5B1o2ZhCuX8Op5GjTnp3IylToSNkHC/8R7RMwHkuXAs42oCwXsltJh\n5xbp4Xw+lzzIVhI8Ez1NhGB1vOYKypgMfkz6aiqB/RNMvlGO5LCBvAbK3BBN\nlNgKSgkPL0QIcaYj0VF5jSapzEvP2aabKMqtFfPg8pZopkFEEhf31stuA/kk\nzaRuFJAeCn7c/xj78S+kWnKcb793VwIEgqcGW2laY8p2bx9ThKWAVsxhpSni\nYl84avdYHhga15EwNjynop+dwSVeKPIVdGqg2VPVt/Wtwa/HEQJz5WQLHTCT\nZ33B\r\n=WZ3W\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7a0d93c7d9e564802c27bea1eb1e65fd1a576fea","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-08-12T15:02:02.871Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.62","@balena/jellyfish-logger":"^3.0.45","@balena/jellyfish-metrics":"^1.0.311","@balena/jellyfish-environment":"^4.3.13"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.7","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.18_1628780634840_0.36117552660611363","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.19-fix-indexdef-generation-28ed699cf27982b7c3230d52fffc92a03d314141":{"name":"@balena/jellyfish-core","version":"5.1.19-fix-indexdef-generation-28ed699cf27982b7c3230d52fffc92a03d314141","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.19-fix-indexdef-generation-28ed699cf27982b7c3230d52fffc92a03d314141","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9ecc3028d8f88210ffab773f4d98c924936e88b6","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.19-fix-indexdef-generation-28ed699cf27982b7c3230d52fffc92a03d314141.tgz","fileCount":202,"integrity":"sha512-YtsMpHbn3UMBvIF8jSHmLDu4PUDXw1nYccHKxiD50sSYBRiGrN1J41HSXwUczwFCqw2u89oJdpQTaQsNxqNS3Q==","signatures":[{"sig":"MEYCIQCg2pHQk7dEALVlzJTw1wkqm2VlSQaAGNi+CncHirLM7gIhAIeVZWLKdk6MwRtPyYRhO+eo64aZ10jSVTqab0dGXMAN","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":743460,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhHDmICRA9TVsSAnZWagAAGZ8P/2f5hSG5/01uAIDwJKJ+\n0l6hw2l7PfzQpClFzKxK044bo00EAvfgJEYYzuj+X3XV6043Bk3qm7rIqjFi\nHxLLFeCle242fX0MvOONk76ATkSppc4E+AFkcM3YnAPFY1BhuAP7MKVSdKyj\nCUtS7lR23wC1CXEU49qrxlCIA0E/c9JcLi4h9Y8y5nSVwbwXt3HhKhzvAGEw\nJ/6jJqwAqwAjhFQ/Gz7jaZoOUMxV5vgAktsgt75rNigP9rWQA/4h3bR3KxN3\nVtRB5wGh3bCpWX7MLLfXJHxmRP0eV6bjmhxY86+xZZ3H186Yl3N735HhrPTT\n9wA9hoVP5MHGe3PF4BS/BLCMrzTaLnaOw4rsQbzM/pQLHDPOnxilYr4577SP\nauPCiLTm2gCG4gpRuxw3AGCjWboeB1M2S9hu+sZQ0Fllh3o/LecfMH91UZ1g\nnjq6gfSCRPVkTIX5DBnZE7sIssMXi0FwjyLsoop5G8eFxPlAYkTQGJR6xt3w\nn89MuYmAV1QXwHsaSDTxzpmm4dWKJQE/enKhILh7cW66r2HvOZL4lnw2S+03\nYZFnPiCt2ZugE51ftHgawVLIoG8NuAsgC6TUgliGx1KFaES5PUyz8Xfjib4I\npgT9B+BDg6exhHCD4+UjD9FUxAlemeBrSwHH1hOPZeIOIFGeM5HQyZBwkHtQ\nzMPA\r\n=iqMp\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"28ed699cf27982b7c3230d52fffc92a03d314141","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-08-17T22:32:31.461Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.62","@balena/jellyfish-logger":"^3.0.45","@balena/jellyfish-metrics":"^1.0.311","@balena/jellyfish-environment":"^4.3.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.7","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.19-fix-indexdef-generation-28ed699cf27982b7c3230d52fffc92a03d314141_1629239688431_0.9050282435686969","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.19":{"name":"@balena/jellyfish-core","version":"5.1.19","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.19","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d00c3b243a65d685f4227cb5feee8655adf89618","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.19.tgz","fileCount":202,"integrity":"sha512-CYJBU03hQgOCSKSsOdY9lgf7//lBTpMYFQLduSsTU8dkg8mqNDboxYYtINnIRnRlZY+5kf9f/w/Je2XloHNI1g==","signatures":[{"sig":"MEUCIQCMaN07bY8bvQeRU4NTXtD2aUfQYTpNidSNIr8QB4iPLgIgTSwwB4pGfPrU4AlKNyLMI2WI5RVhkbTM+f0IdH57WCM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":743395,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhHEVeCRA9TVsSAnZWagAAMU0P/1fL46WUSp3SSP8oZRNG\nvAgj2hF82GqfCkGy1wa8M2A4Cd3Yio+mjStwds8AbxwmDg3R/xIwB+HDYSTx\nyNQbSEVpwixIbCWxjZKJyohcR7FVj8QKdC6xtSPIAs6fJ+qCtPtRzDxodgZ9\n6k95Sc/0ptOSlfSPWh5qn1YpKrFTMQD1H0+eY56zNK5E1dp8Wn51Kgkpsyok\nRC+uiXhmG8sjYZ5l/56Atvb3fRRtNfj0I9HhL6GS/qtI3MFLX1ZOh2Fp2/kJ\nGKvi0tMHkcr8fjEI3RQYHuXV37ggj9scR0/VoFjgqHYjUsxwkAqO/Cja/gp6\nVsXi0j56j9c3Ni1E47x26pH/xJVGX0lN4FOPhgUveTgY3s2iAGPPoM/CUXWR\ncOp0caboSiK9ScfLesorAlvKj8ug+O9Cc7vk1awVfnHL8qjQKubilu5hqjh6\n6qyYHeZoETAnZEkDd6X2qk2tA9DYWCCgSJWfBetr7NvyNtIIeKW4Io6iwMKm\nceDCLCJYZW2wFNWQ2154mOWGtiJVx+gP36DZYCueHzkU0NuPbggtYeJPbdun\n2oxGiSZ8PieYEStojvw4Mo2EEgnHr0tJPklaBFk+l0LGT7vCDMSl7zMofhW1\nl7tbTKeCQYdmr2zTPuFariq2POsRsjsIEYuDdsELMxeczNHgXD53fXOcAiiT\nBwZy\r\n=SL9p\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"3c79233f3e1750218854ee68bc43f770fd62b894","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-08-17T23:23:31.409Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.62","@balena/jellyfish-logger":"^3.0.45","@balena/jellyfish-metrics":"^1.0.311","@balena/jellyfish-environment":"^4.3.13"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.7","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.19_1629242718802_0.22402365969973848","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.20-assume-not-array-007be281bfaf816dfdc61a2cb64e4a539ec32446":{"name":"@balena/jellyfish-core","version":"5.1.20-assume-not-array-007be281bfaf816dfdc61a2cb64e4a539ec32446","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.20-assume-not-array-007be281bfaf816dfdc61a2cb64e4a539ec32446","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"58237f15c9025a80b32ac76bb9aa8819c11abd7f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.20-assume-not-array-007be281bfaf816dfdc61a2cb64e4a539ec32446.tgz","fileCount":202,"integrity":"sha512-tql6MBBBE5NborCgyiOLIMVgpuFO1PHcArBpd4lfy5IvtiP0lMFOgxJQFNECyT+LqsuT2AUkK4sBFmzoX39nWA==","signatures":[{"sig":"MEUCIQDCCLCAoDqHsvn87kooG13GjdSSWGsps5hGTqHJ6R2gQgIgJ9Tx961rRsFVMR0JPZC9oJGoihRfY/YLG4B0jqUvPOI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":743167,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhH3FrCRA9TVsSAnZWagAAcRYQAJnPl0Z9LIgu132QTZDz\ncnYe/3lXW9c6uenYws208S2EKOP2IErh5xvxC8MWJsXm23msiT64mBjR9b1o\nwpVoM7FjpVBGJHhGJC5eC/0neeNwu+/HYz6rVec+UQ2rPHxUnsN6ZMllGoNG\nY1yzA4+BtQKYu58T/t5XGAnQ6xxwoFwTGpG7py1qXT62HZuHWKgCNGivNaag\n4OERTE+pvLLzRLWsFBgCHciwAGb6wGj+wfxwqjaydbk7sXcqmvTvQrlFRqVr\naZMNbe9tiwWObwGC/c8ehvhDhy+VfGQkrbCNGW2INfoVtFgRULMlLLFwE0wV\nLM1kwP8bbvKSvconBAJlLLUqA6dsJbzM/1BRaXBmtolBHitsKLHtnNeRtSge\nfF593VFntl7uLTdbcp2RF4EvqPqBdA8n+QgPZ8XCJCEV93VcXi/SSvl5Pbku\nBtxxSISDYmxIocDg5YhVMkQdHFpu23fyG3lx64GOo5aBoA3CY1HnL23Ze7/3\nEyTuzwD7buWY4+0kI5C7naufRamF7mPniaRsR8yhHZhE6a7p4DWloMQy+7Fn\nORvvwIdNodZWU2RXHlsv4haPs5JjQcYdzeFEcZVPbsFvFU4IwLwe6xRcpmM4\nsBeMitFESuBV7EFYTuXX0h/JKe0xtHrM4L+1OFyxnLYo8v14IpP22P7YffiB\nXbt3\r\n=BQNw\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"007be281bfaf816dfdc61a2cb64e4a539ec32446","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-08-20T09:07:01.853Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.62","@balena/jellyfish-logger":"^3.0.45","@balena/jellyfish-metrics":"^1.0.311","@balena/jellyfish-environment":"^4.3.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.7","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.20-assume-not-array-007be281bfaf816dfdc61a2cb64e4a539ec32446_1629450603520_0.3880301271412494","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.20":{"name":"@balena/jellyfish-core","version":"5.1.20","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.20","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0b65a270cf3587c1d1e894c1e980f87f7a47ec12","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.20.tgz","fileCount":202,"integrity":"sha512-+2tYyS/ZUDuNo89/dRqi4q7F73rzTtzrKtXGpPr9zDLhgjOAL//ySbWHiHgYoGkWe15CND5e7Ynty/U1qIKabg==","signatures":[{"sig":"MEYCIQDfRLd1sY7rC/PnaGGjgEmaPUu/N2/4+WsWlvlIeu3YxQIhAOfNfMUY7wKOxbxHAfyTVRDrt2DlC6SRbeG3D4xxYZ3w","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":743109,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhH3NhCRA9TVsSAnZWagAAHPkP/RmH/jxb+TMNlVmxq57J\nFk9eebZFI0bzK7rWi5Ixm4xe0ZCJAjBn6w94GG6lmkvTfqm5pUHNku+2hv+3\nK9ljmbSswjFrU0Sxbw6IktX0XPZw5TJI7rkbL3KcICoBxxzBr2i6g3xflXim\nAi2yy9+62nu8VqrTlj6U/ItmtGMPI2AwVP8+NuMfSSoLEusELWbsyMYyD7yG\n652apm0OTCghzhYZIDop29wLg/9dloiYTNq3wfaPQhQfD00N7ao/ichRCsbj\neXO6w4fO+M18TUBlrphBqNY9P18ylmhEubs5d7t0srpbQPsnI8IENCvgta8J\n2c4Jh+Wjd021E6mlpiBItZDdKU+f/uZsBKWgO+Gdiwm/6trhzfdazZ3Ao47R\n/9B4EBoH+njVB2nToVENGekP5yKJqLDBjAIZNi023f/dfLEQT3dBENiOxEJ4\ntR5utovYTdSlixoK+2aa6qGIKOZVAeWFZl8P0ytM7eEQus9MpBuUR6rdIl6r\nN1vSjEZi1qAUsFKYE4iJyR3EEMfD0ZJzlcwaIOYcys/WtcwP4N1NmK1AtRd3\nozpPpbP41H2Mv7pB+80dvR79nN5yciHrwxTQQn5k6huyEdWx+HoEHbWmZP16\nD0MF9EAKpjnbVPNqgI9Q2ILdAiz/Lq6u7uKaWxb8/5sIR7maI/3Igz8+M3JO\nVyV2\r\n=YHpD\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"23ce16ebbd9d841748a725b0cc8c28e6e66d955a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-08-20T09:16:08.176Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.62","@balena/jellyfish-logger":"^3.0.45","@balena/jellyfish-metrics":"^1.0.311","@balena/jellyfish-environment":"^4.3.13"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.7","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.20_1629451104965_0.7304354216828537","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.2.0-add-scheduled-action-cdd89093671ab828409a505fb83041781603957b":{"name":"@balena/jellyfish-core","version":"5.2.0-add-scheduled-action-cdd89093671ab828409a505fb83041781603957b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.2.0-add-scheduled-action-cdd89093671ab828409a505fb83041781603957b","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2cc1503fd0abb890fd784f18e0f40e2760f80072","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.2.0-add-scheduled-action-cdd89093671ab828409a505fb83041781603957b.tgz","fileCount":205,"integrity":"sha512-oCYrDupKixDvyQOkP/M4bopnJwWSazq3tEc6VUAlUydkwAmnan5h070iTcbsmOlYGkLnKpbMzcXtnukK74QoQw==","signatures":[{"sig":"MEUCIQCCmZceNMRej6TF4G+IePBXeUGEn2pQclKYcUoP7xeEwAIgEMftCe2QsjM910vDMfyYR4mjcJ5yYYExadkLg5g/uxI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":748809,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhJcclCRA9TVsSAnZWagAAKmUP/04mkWKqHnCooXaHTmB+\njdo4HHk/CaGUG9ccRoMaaDhBU4mDJ+lyMSeDWWJ14iqz3gN+D+Pe2jK4wF3M\niocBuHiFRmUHiM+fKHGUKAQJ4+LQX5PDyo8gdfFCjXYcJc5H4oTxz0lg7gnM\ndGtkPeQE15nbxG3sKsrrMBj9qvxHpPHh8/Mx5SSAmxiagQCQzRi6oBTkPuSl\n7Lq8qLGQW/aN4DyahdSL2UwvIQA4r0YQuxB6hhxE7eJ35wXSE2ADKEksNNe/\np4PpaTDLRULlRSm4ldRYyKK2RYrmWDJmmptzv7su1gvdC2VQ04V4jHoOyDqi\nnXWeu93jaqfPUXFWjbptOQTGpGKw9+pCKRwByD2rCk/Y6HmHDnp82hJ+8WSH\npAGrfo2SxoD1VjxMdv4QSkqZ8FI2ZTU0SWAlxPbkK7pYt2HKGFCkZqGemhXY\nBpEWT6ViSJeSAQHeJBLKSV3QgiXKSvUyQeZ5ZoagTGSU7n3jtpWxWjtvIUdZ\nI7MyQmodxg4Qjh5r9My00e1zxpMszfywr1QemlA8o6fmo9Dr0Ul7f16Mcz0B\nGuoQVs7TE0Z/zKJSGSQKG0Eo+cHK67HV5CLrp/DBjQtCbPdnKiMr0xne8L6d\nvOtPW1T3hAMt/iD/AdsYjCI6x626e+79KSPhpNxTNd9egGkmlFrxulr0qsCh\nkDcc\r\n=lTX6\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"cdd89093671ab828409a505fb83041781603957b","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-08-25T04:26:11.742Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.62","@balena/jellyfish-logger":"^3.0.45","@balena/jellyfish-metrics":"^1.0.311","@balena/jellyfish-environment":"^4.3.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.7","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.2.0-add-scheduled-action-cdd89093671ab828409a505fb83041781603957b_1629865765395_0.15012488291571247","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.2.0-add-scheduled-action-1d53957f57ea7f7f219489b84d455ebeb3f6575c":{"name":"@balena/jellyfish-core","version":"5.2.0-add-scheduled-action-1d53957f57ea7f7f219489b84d455ebeb3f6575c","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.2.0-add-scheduled-action-1d53957f57ea7f7f219489b84d455ebeb3f6575c","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"924afa73616ba7fdd095115b28c9e9a239f71b9e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.2.0-add-scheduled-action-1d53957f57ea7f7f219489b84d455ebeb3f6575c.tgz","fileCount":205,"integrity":"sha512-ZQDZbbESYBKZJiA91NFylIz8f29y7hUQ4hZqEHyU4piy7J90ugb34tA6T0mMK0comm/InfuWqp5wcwBxbwGe+w==","signatures":[{"sig":"MEUCIDv4qUCdVyyCtQU1+NYx2mirL3e66ngXPa4yZv2Sg4/DAiEAqlfJ+7441E9tp+epWBvNbal451FeyXRBC/qevaWp/Fk=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":749769,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhJcl1CRA9TVsSAnZWagAALDgP/R4UZ8DFqxO50AGOTrRd\nbF4so1ekNtzU6qUtnZnENHoZTQNjdjVdDc/u1WYEX+aq/6HBpXPOzlQ/eyV9\nbzvFT5eXoKwO8H4zWyuqT9Wm4t0jqJ7v6hIxWBhM34tk1VIk9l4Q87fYUGxx\nDQoeTnU5zFAUtlF4i8bRFRGvIvtuQjBzgq5eCI+0i0h9HM56QKJr/1Lni7dZ\nxSBxbhLe3C/o442yapcCIJ2gtJ5KLiYN+M40FElM1BGtu8ubaWo3umtRK0Wr\nI/Zcdy/PjDlpZpJh52qFDR5VjggdHwBULh4YHLyQVXqQ62scnFjtlGfccJj5\nI3LDdSQGJqjTgtG7b0uIjk2muwkcYHsx8Gvm4Nhwv4PdHjvwCglWq/vVDvtK\nz0Ob6pJOCsLoFzNpT7WnN4CmWtw3WEeSotcXqgcodJkw6fehrGo5Y0u2cxUW\nn8HFqEw/e7eeTpmV2GUnrtalNn/gizMp4TMut2bFwziiFfgJlu5VnPZoGixo\noks0xYHOuzEZdSh0PyqUNHPzRYHxZQ7QjAVyRkHUHOZE+pRtIXdw2zGATQnk\nNh0xBSPckiOXfphm78SEMCiQaug4pRY8kyCMudhuTRHTFzkJyNhEWvBxMzrk\nafVjsAoHG+h5giho7EP9Kc12KbnIVvhmTk+nSrUaL3gfL5TD/RllHiKGhdCl\nh+Yc\r\n=LmXC\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"1d53957f57ea7f7f219489b84d455ebeb3f6575c","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-08-25T04:36:11.777Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.62","@balena/jellyfish-logger":"^3.0.45","@balena/jellyfish-metrics":"^1.0.311","@balena/jellyfish-environment":"^4.3.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.7","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.2.0-add-scheduled-action-1d53957f57ea7f7f219489b84d455ebeb3f6575c_1629866357755_0.5690060330576325","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.2.0-add-scheduled-action-6d1816189a0883b59e876fa6af0077571bc267d0":{"name":"@balena/jellyfish-core","version":"5.2.0-add-scheduled-action-6d1816189a0883b59e876fa6af0077571bc267d0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.2.0-add-scheduled-action-6d1816189a0883b59e876fa6af0077571bc267d0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0b7a8ca2b7dbe5c12285e6c8182d649c1b7928f9","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.2.0-add-scheduled-action-6d1816189a0883b59e876fa6af0077571bc267d0.tgz","fileCount":205,"integrity":"sha512-+HzNAx0srsYJxM6Ah7ikyyKzgOxjWmWAIVgiJpcDZHiiMjyo/ZKwrpLPPdZOjWPpk6AFbqX4h4CJWc95V9PRUw==","signatures":[{"sig":"MEUCICxcTKjMvltMc7siZnKXkFr40iZA/uR7aUeDNSUn/c/uAiEAtewewywL53n6M6gjAe0I1C6PScx+/CeAD31VT1cJ8dM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":749919,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhJf2VCRA9TVsSAnZWagAA+sAP/3Y+4/gQTC+1RzHN23uO\nOulUogKRXsgtYwYiMFHAtRDToTISGBfcRGpSjxVS5Zx7FaPQ9h+kbc0mWhx5\nfi9roD9tQRUo3eZPxvEu/FzOoleZpSa/o4I+2oRmI0RkuIIOIXatHheP51Ft\nep+7TRR/k3+Pd2i5fu9vdr6tXmbDSgAHxehOsWqzpOvvfmTERkKaOtvAuj6C\nJ3WnNcCsCcpfRZxJzainKYJsJfc6nsKu/4kIUvQCT1QThbtYYt8Qgppib8Ss\ns1MV09wRMW2fRrdoPaiW9fmuG1iAxJPOjOtFUtIOqy3pZGIgv5Xi/pZY3Mf3\nNutwhs1FQ4+AcRbZTmiiO9Y0GEWAfrjPu7qxcciM4vj9iZ1hVlL7ifKkk+Z4\nZiE2Ar8wKbc0baRTvIUXCvRzfz5f1i1GZ3BxHE/GYDmaK0OqSMtEn1VKiJwt\nD6gaxbrseaPxZlSojT6cfRIXRZileY6ABIRkTQIfwyLf54iclKL4kCm0oLed\n7DjgXmFP0AdCCIBlo2CcuvmSHpR3/oR1OS5n4i3wKiN6+yoKUrEupWdyRokp\nEym+3JQdCaszFH8cEI3CX5J4Uvo6LZB4TL86wNRG33G/ujOMPEtlsabQ0/yo\ncxAb7uNM0y4ThhIQGoK/L6+QP6w/K5KxsRgxevidB4fGwhfVgchTRinPEsW+\nCDBs\r\n=kUQB\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6d1816189a0883b59e876fa6af0077571bc267d0","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-08-25T08:19:01.676Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.62","@balena/jellyfish-logger":"^3.0.45","@balena/jellyfish-metrics":"^1.0.311","@balena/jellyfish-environment":"^4.3.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"0.10.0-update-action-request-action-de237c0a1e7e148a615456a8cd83cacba961bc3a","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.2.0-add-scheduled-action-6d1816189a0883b59e876fa6af0077571bc267d0_1629879701467_0.8387565047212167","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.21-update-renovate-config-368a330acda79579743766fa419141d64cd551b7":{"name":"@balena/jellyfish-core","version":"5.1.21-update-renovate-config-368a330acda79579743766fa419141d64cd551b7","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.21-update-renovate-config-368a330acda79579743766fa419141d64cd551b7","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1e31ba7462889eb03facbf8539313f4d9a56387f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.21-update-renovate-config-368a330acda79579743766fa419141d64cd551b7.tgz","fileCount":202,"integrity":"sha512-6XjnYBp5pNToWRpJYolSdCbUo0fnWcl1JTrb9Rbm2aE1tG3BjOnqEzceRHEpNzmkaD3CuHeCrBRI3tjoptY+Ew==","signatures":[{"sig":"MEQCIE3shu0H+aa5hBJTo55W639UYbnrucMsvCmfsxs6jzmzAiBmwiAUFUoNxuN/1rSlXWtjjHbRD1O+Apc0LIrt1nuOYw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":743238,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhJf5cCRA9TVsSAnZWagAA2FcP/jL5u6r2+8c33/4KW5ZR\nMbb5FkuOJnBRMNg+zI+1rVQKrbqZbNPxAUvAra1Prh9hMf6x2g1HYZA6qQzA\nn/S0e8xmBE9R1/m2uo558YbEkQ+8wqjXQlVuUPb7e68yHWg9NX9xVJJRwtJK\nm4eAyFU+K8XZxXV/Vuwn/Fm4NTZVdz5t/kF6yUtcqqqDhu0Pq4EgX82898CC\nmCKwY9INNhC7ml3eGppDd+hZlMKiLBGWZK3WgAEychefE/sq1vsnpou+E2wf\nG+u45yWiQol69JnSWZ6Y8jH+ubp/XXznSil9WsIqJ3z3LR6mn3eFjhPeUPGt\nYXyuJbLzrderw9xk7O5UzivgJa3RdgMzCbw/9S7zLX+uGBtoxVOLxbYqgbBg\naDr8crt8EXmDFT4jyq7G4lKEDTLVLxK6G01cfZ//RONdbqPDagOCNHHFMuIm\nIM8SSYSeStwMHD9iVTXZ6rN21j5XnVxS7l/dG6kKOrILI225BAQuAcfz+n6R\nCsKnSNU/KfZOxGBaWLEC+JKs3AxPN2TiItg1fNS+mpo4Wmoocr949nE1FYJF\nIzzqHRXb4H53xOoRwyfOPJbEUAmt/F+VgDntOHMuo/JTZwFwfKvmLA1+8sNY\n5K1o6gFe0dyaFPMhw4+VMMpoxNFs6L2GcypCkd70TQles63dTm8vz7bUejGx\nxygt\r\n=MRqQ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"368a330acda79579743766fa419141d64cd551b7","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-08-25T08:21:52.541Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.62","@balena/jellyfish-logger":"^3.0.45","@balena/jellyfish-metrics":"^1.0.311","@balena/jellyfish-environment":"^4.3.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.7","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.21-update-renovate-config-368a330acda79579743766fa419141d64cd551b7_1629879900521_0.6411194558891375","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.21":{"name":"@balena/jellyfish-core","version":"5.1.21","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.21","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"743f387ea6c8d1dbe61a3143620ebc0d40d51f52","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.21.tgz","fileCount":202,"integrity":"sha512-5RYiqRqptqzUZBZvEQKkTd2IpEyePLIvsbCyh7F4WIqfKuSygxCuZEBtMzEtnC8Hc2xJ0/UIvEW2bnWO5fK7pw==","signatures":[{"sig":"MEUCIQCY/K9uDyYPS/AoEE5RAWjJf45gM/O3LZqZocdSPfEaZQIgHOjvpsyT6xCUU3UrY5+lSKHnnf9dq6G5zz04qEf0ThE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":743174,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhJgAeCRA9TVsSAnZWagAA6WEQAISA9yaWmQjlHC7mxzPH\n3bOrZJdnXK325jkfOHyCCugzL9gcPwN2YErZkHDXJDdGL7LFAEpCgSwwujnS\nCHrajwTwU5djTN6ZqeDFasMVQndXcMr7iJyvqQwo2Rn5o7jBjCTWGD6f2pCC\nBLgnkFIGJQuJEeshV0sHzAnN40ETmmf3vJzBzhqE5qnMSH73lxg8V48zZrfB\nCkSCGC6MIArQAJ1d93w2d7QgR2kzv+yqH9AmbSFO8r8cnb/GMYY9LdPv0EPk\nyD7cqhsUntdszNNSzNnY/PvxH1aKRc1J9o9oPFRuLcHZfgJ1VowZx/eaw+aU\nAZ4+sP3wNxUa6Sm64rRND5f50AGvOOc5dfTa70GVCbvGCmDBxjhSl8h8SthN\nAJ17N7FewBBrmZIPMsqBZlEp5xoAQ9j+u9YZT05RQ0gi5fx52w+g/Ek0HSpY\n3bg2YEiyR/eCzRDPENxOH67S1Arybce/g4C08erM3mJsC3vnWJ4EzzBUnQka\nyn0znyJeEBMqmI1fKsLmWcTJr8BH+ZGN1cW4lUJaKGBM0vSgJ2pDNPeh3QBK\nAb8QfbsGYJl67JPirtNgk+YvVnCiOQSlQSrIoPGH56N4UepIKoRJ6ksrfrB9\n7a/Y8bbW6r0Ylfn/C/C/nq40SwW5WdQx7jNcsmHlrgO88v4eLPT/rrrzeaYE\n4rX4\r\n=u5Ad\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"849efc386b43669811355f68aaa96fa232057215","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-08-25T08:30:12.822Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.62","@balena/jellyfish-logger":"^3.0.45","@balena/jellyfish-metrics":"^1.0.311","@balena/jellyfish-environment":"^4.3.13"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.7","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.21_1629880349836_0.7090368385317203","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.22-renovate-internal-patch-6a57faad01385daf3f3246c5850d4de3393d96bf":{"name":"@balena/jellyfish-core","version":"5.1.22-renovate-internal-patch-6a57faad01385daf3f3246c5850d4de3393d96bf","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.22-renovate-internal-patch-6a57faad01385daf3f3246c5850d4de3393d96bf","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"81b9f99101319fd0aa292493e2f572b645817980","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.22-renovate-internal-patch-6a57faad01385daf3f3246c5850d4de3393d96bf.tgz","fileCount":202,"integrity":"sha512-nWAf5i+qGo4vaR6fSHkK1DNKYVrqJr2FuXsdAOD48SXMqk6Xil9s7VEOv95ZjlhZbbOV9pcsqywD4LiI3Ia2ZA==","signatures":[{"sig":"MEQCIB2KCFrJDT3g8/2tPO2HzUYjStLa0J0+vmRTEx4VxVs9AiB4p2rBwr6QAfQM/iNDes2E4irfIP5/uh2f25aF3ZbWpw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":743310,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhJgEcCRA9TVsSAnZWagAAGOkQAJV5RmdjSgLG1ABAF/kP\nCcbUbEqBuiG5T9ezZsSgOPbQ0V49wn4PsMg06emk/lrU9HT4B76X3oPZZ2Hg\nSoyJYkKNpXWF7zjWP81ihK9C6EAFSVKmvSa/BAi/xOSsx2hHM8b/ndUcchy1\nkl43lZAtjQ3n1rDjmyfHMIFC+iaFCWHL4vMahC1Sly3vw/yoQca19L2oPmen\naVRz0GGXOn3UTxO3vcTarDfzniq8L2/XOacD1Co67vkvtbGtxgPfglcPcZKG\nuBrIXFmUywwVN3AhQFGMFulO+Rs+KUpQnbRiIq8H5X3Of2T9iDMfEx7okdQy\nQk4nyHBiwXWgryQrEhxR7+Un1xuU/8inrIgDhOpHO7l7Vi/rLLROfErunDoB\n9DPrtBSWy4BIJCTeydzYsYGOHuLTYjN/o2rYNvJGy/c0I6MWJm2EDxV2Sbzp\n6AR/g1vICQGG+2UdV/AkRjvUdanfuYl1rXvdQP4FvNkQH+kh+S1jdiAQiO9u\naeMvf2BxRQjxDQNrKPmuuhBgBuv6d8tgHVKQN6qugtewA7ooaIC+uUv+rkhP\nLndbee38sF08i1ZLBvKzMkoUgUWFaiyvMs4SMSO5ToYFl02Z9YGPvTOzub2D\nWXsWXrsRzo/lReYgk5RDmZ5Nr6lQE88ZJiafV88bgEwpFO3qYOlSEzaAi2fn\nOPmk\r\n=RIu2\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6a57faad01385daf3f3246c5850d4de3393d96bf","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-08-25T08:32:31.014Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.64","@balena/jellyfish-metrics":"^1.0.333","@balena/jellyfish-environment":"^4.3.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.7","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.22-renovate-internal-patch-6a57faad01385daf3f3246c5850d4de3393d96bf_1629880604662_0.26625147522057047","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.22-renovate-balena-jellyfish-environment-4-x-bd0059c47172aaddbcb5b59b52cb8b0ce0dafc31":{"name":"@balena/jellyfish-core","version":"5.1.22-renovate-balena-jellyfish-environment-4-x-bd0059c47172aaddbcb5b59b52cb8b0ce0dafc31","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.22-renovate-balena-jellyfish-environment-4-x-bd0059c47172aaddbcb5b59b52cb8b0ce0dafc31","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"83dbdf5b29adc63ca21453d26f89817b46de9704","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.22-renovate-balena-jellyfish-environment-4-x-bd0059c47172aaddbcb5b59b52cb8b0ce0dafc31.tgz","fileCount":202,"integrity":"sha512-1cs94+yNlhImFyLGvvyTZkeTU8vcRJ9QDngAhYeFZDPeeTM9sTWs6+16CMHsyC32onAs2asD0OfWshB9Kqcy7A==","signatures":[{"sig":"MEUCIFW+Bx1k3/QGTAnxVoZDgoTpSvQIvCzWlHVujvi/I+fZAiEAvL4FSnRdJsqakevHIUzJ4Jcd4XZbLTPgVARRqMHIqAA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":743363,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhJgFICRA9TVsSAnZWagAALtYP/RKCOIRzMAxY3hWhg+XD\nPFAQHbbIP/A1fZaOjN0ofRQrRx9c9ewm4C08Okvc9ier/AHOQzyQYxu1hzob\nXamc1NsOYlLS84DKeWOz0wsMT6AkInoZYOPLXNkaL1HEqRaVrIDaWO52azAr\nxodEGLAbDRO18FOTe8y7ZG1lrTXnytnQ/UZTk6xX9UiHBtdqb1dDRdT6MAtf\nFPQ/eAvz8H0gTbFlJo7Wa9sGjFHcEMMjpqiX7N2t3bkarNwcJMx6kWr0tMD6\n0iIUNj9Px+8qOm+sZtrh83pZTyasUlJxnYp/kmxZIifUCMOF8iPrccmgkL/s\nVzdbBjqNSGxAsysQdnanXiQT+qXoGUlFB70GLd8PU7v7iawl1INBl9imdepe\n8djrBgWjJcJcOt3+BukuDDyliVLIQE4OrpRMFIyPIS0LmXWPpbdGRt0vU8tA\nQl5TpJN58oRVjyBDC9/8Tpu3AnQyHtUEgAQAHises1PLYHY3uamQG+qhxY9u\nJ3aIAVW5Te11HmKAOGjmwpk9tZncuA9KUQTMLbFpxzl0wfKiax1wiifc1ioG\nTMJBy5eXbCNDaPssXVZZ5o0okPlE1lz3IKBS9/tqV1n4vj8bAtZGppEnFyTM\n7aSu4fen5FXMBNLkpB3f1Pa6D7mscHgOzQCM52ET6pvMmqB3EnSo1d6BujEo\n5VVw\r\n=Ncmr\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"bd0059c47172aaddbcb5b59b52cb8b0ce0dafc31","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-08-25T08:33:53.067Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.62","@balena/jellyfish-logger":"^3.0.45","@balena/jellyfish-metrics":"^1.0.311","@balena/jellyfish-environment":"^4.4.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.7","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.22-renovate-balena-jellyfish-environment-4-x-bd0059c47172aaddbcb5b59b52cb8b0ce0dafc31_1629880647931_0.37094740447732866","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.22-renovate-major-external-major-6c2b6b9d8bdb7e68d9a31be8f6593432b9d32593":{"name":"@balena/jellyfish-core","version":"5.1.22-renovate-major-external-major-6c2b6b9d8bdb7e68d9a31be8f6593432b9d32593","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.22-renovate-major-external-major-6c2b6b9d8bdb7e68d9a31be8f6593432b9d32593","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6725b29105646f537a74f32cfaa5692f97078af6","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.22-renovate-major-external-major-6c2b6b9d8bdb7e68d9a31be8f6593432b9d32593.tgz","fileCount":202,"integrity":"sha512-Erse5/XRpmj63gS8QWhHieaRilx/m6xjG9jxQl5jC/+dYijx5k3DWJOHAvRGMvFYOgebA9dTXt0JVPStxywP9A==","signatures":[{"sig":"MEUCIQDOfpVUIwHtSQQftbMybHwle80oNZyfvE+PpSMgOT348wIgJA1NNcCAhgDF3IQsjhiCpyyPXim/V1gE2gGusHVS1Hw=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":743315,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhJgGYCRA9TVsSAnZWagAAFvwP/iwHp+O+daB3s53Eeomn\nb1GYUOGIhR79Ftu0sX6wcQtjQuRoE5XvdGwDQb4ah1K5kDb/H+sJYYU650W0\nXY2aOdTtb9VwYZifAy34JlMEjkngIEC5My/PywIFhZnGgUmKXvNLQXrsqEo/\n8VIpsxiYxSLhq8nNc5uaoCv2rwdgwMKn+2h9pnnczKEDSfqauXk7EN3emrsL\ne+XI3Z2iGphzXYj1YtCVUkngMo7DO07v/11bmTzjKie+iNhMoNsSD/0ifV8B\nVxd/VmP5fwk4gVXHiW0zgH+F+JcPHQutwLD3L3koHF3nwRm8N8dRpettaT5H\nRJXSZus37eXSqpCOXQ7inXbL4U1ZexPqGBA9x896kE3OcrFYx3wUKmi9Fdwe\nqX2qrP1xrNVpfm6bMpgliUgBG9lD1mEqbBbtyq+E3Fp+tZ/ACwaegk0VRRwU\nInX7l/jE2imZBIgTQyhA2Ctd21bn6B3R44Gh+MN7MHHBN4CjQ8n4aJISDZAq\ng0J0cqVqmspYEh9ByNx22jx9YZ1Y0zxJ+2Fans/Km9oECZU5VCNQnsKEX547\nwJrK52gHYNiAKbhgbeR18UBYW0KK8UdAQcoAyUR8nsSd5gCwUvUaE3SaIDvB\n0hYa0XzhPrTHhlOQbBC3wtIBtw3qhwWGLrvCypEibMMcyxgUQ3MM/F07rh9t\nCWVb\r\n=Z9vu\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6c2b6b9d8bdb7e68d9a31be8f6593432b9d32593","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-08-25T08:34:05.712Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.62","@balena/jellyfish-logger":"^3.0.45","@balena/jellyfish-metrics":"^1.0.311","@balena/jellyfish-environment":"^4.3.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.6","deplint":"^1.1.3","ts-jest":"^27.0.5","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^27.0.1","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.8.7","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.22-renovate-major-external-major-6c2b6b9d8bdb7e68d9a31be8f6593432b9d32593_1629880728171_0.8229441649633","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.22-renovate-balena-jellyfish-environment-5-x-019eadb1fd9380e00ddc923e1979008bde051897":{"name":"@balena/jellyfish-core","version":"5.1.22-renovate-balena-jellyfish-environment-5-x-019eadb1fd9380e00ddc923e1979008bde051897","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.22-renovate-balena-jellyfish-environment-5-x-019eadb1fd9380e00ddc923e1979008bde051897","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4313d6bd584e39d4cc0306f2d1a765041e98088f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.22-renovate-balena-jellyfish-environment-5-x-019eadb1fd9380e00ddc923e1979008bde051897.tgz","fileCount":202,"integrity":"sha512-23MUeAABa8xcIXrQSN1dk+Lfy6QrNs892cZJ5kMl83RQdxtBez3rk5/ZzdGbYf/xNgqHMknRUlwG+iTdQcHYWw==","signatures":[{"sig":"MEUCIQDuWYWOWGSg9pdivCGZ3Da+A5pOOo0G0NWD5y/8/K90fQIgVKuE4Xo2T1CjsQjhgWJzGPqlg8XUJzbfHXUNfXnOyxY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":743359,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhJgHWCRA9TVsSAnZWagAAAK0P/jykn1rmOEFu+r7wYTda\nNmmvYxxyW1NlpY8UwRXE79k1Tv1Wofz96rd6+UUTL6ih6WtNZBk1ccy/0aF9\n4jnh4h45YK1aTBmvyujjbbjuBsVs1/lX+qW7DEDMXKKJ32WXwzHSPgS9C3Um\nak1AAu+Jd8v6Q2tSXSu0hyxQriVBEXseULKnMMVOXAQdxL1LjUCDHdShQqiW\naskZ/4E//i/tYQcgoV59Q3PsNPbuMBkXJCxa77yFLK8SoX+pEL3yxnJYTBvR\nmr7uEXYNccLSvfiStWSuIdosFAId/d6OD951+JbpQ7kSl1GIkQ58CEorxhaR\n6xIHayN6Q2XKCqLCbmpRsH6vY7PXGBycS8LXEROgUOxFzai+1Ioph49LlySt\nm0DaYiOnUl69a+9Dxf7joRVTSt4NXrBZfst4LCyhrW3pbtbQAVOVKNYmu6zl\nztszjj1s0lj936BfTewUl7bdXh/4k4RBzpgrbQwQvlBBQDYtZDRfA5Vqhjve\n7NQmy7SjmO6Y0INVbAInpXrrZGSRshIwZSkbtC3ArH3EXHYd5UOrZnzRsOs9\nKD7FVedMnE38Mu53TR6ABkCL8AVKk30bSbsrz+MoKlhAkNfIKMCvoqHJPCxp\nMuWZgU+439L2l2MfeBjydVxQ1ZcX/N6QeJR447JFhEf5KhNZNzvRf9CfLVSc\nBak1\r\n=KWti\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"019eadb1fd9380e00ddc923e1979008bde051897","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-08-25T08:36:13.501Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.62","@balena/jellyfish-logger":"^3.0.45","@balena/jellyfish-metrics":"^1.0.311","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.1","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.7","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.22-renovate-balena-jellyfish-environment-5-x-019eadb1fd9380e00ddc923e1979008bde051897_1629880790498_0.7236223670688591","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.22":{"name":"@balena/jellyfish-core","version":"5.1.22","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.22","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"56465f68164ac7db1a460150c8f25f96c09db546","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.22.tgz","fileCount":202,"integrity":"sha512-MqZuC4qv5xlxMq7gfMFO1vlicNNPwU8VRz/gMzu/ayGnmGlYmNWloVlo3zMukyLu5dttjsma25t4N5Oa2pWx+A==","signatures":[{"sig":"MEUCIGvIPdUJJJUozQikl9xy6rIzARMIbMaRMkU3f8ITJyghAiEAgfQECTdS4DwMfY0wJ50HiZNOtFk2fOKEwbLiHj3Zc0U=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":743245,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhJgQ5CRA9TVsSAnZWagAA7LcP/3c9Bez0myvrNrb/KPlF\nIocpT+p/kd8H1AUO5aPbQdFtwTCysMtwuQqoUd+0XiTzUO1XdBV77u3VQVqY\nfI6PsM5QYH4IzCaIdktGQAkgRPL0x4RDoqd0NH67yV1ddXpE/J3tM9BXpYlJ\nxpLhgr1GHHAJj9yrz69FhFR0oMVufZbTkQNbl+WCvzi9zn5//G08MRFGIlp5\ny21tzNVczb2wUA54RpsFfX3RmB7jtjboW03CXKUsjZ7+/OsRQ1IyxpUB1vpK\nTOw0L6C3/oce5r2AoL6F3C0HMMBNB4SFetH9fdtlPFns+8m7HU06sSgCacdK\n5J3OyVut6Qqzse/HKqYHH3L1Zkgjs6Typm5IXNUX8+J9XbEpw9braiieiKea\nNXX8Fst6jEjVgT2LGKlP9aIWMTDxxh8aBEmQHe1KnjHB2/PFN5caphGD2VHt\ndVv4FjoWSsVxiYGdaGKdiZfHm4CXn+2qyoU8VK4c4GJpxwIKRAlMnJs938NX\nimsOTtojmUx/W/a9ifzg627zU94Z0sIzdiXvwtBrjUMueECdCQXQE8e/iVUC\nK74YggOuFvXhE26BKRvDdoZ1IoX5xy8AOeKb/rT7tCnPKVxjBzepYGWAD72a\npOJcjumhx+l6fqKbuKL5SR5e1bVfmwc4elxOVol3DE8vz4RZaJ559iot/cfm\n0F6o\r\n=XBzW\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c155c158fa3fb9517eb592465f8d003a1093bdd7","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-08-25T08:47:46.552Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.64","@balena/jellyfish-metrics":"^1.0.333","@balena/jellyfish-environment":"^4.3.13"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.7","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.22_1629881400834_0.8273161158137401","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.23-renovate-balena-jellyfish-types-0-x-916fb21f9c1e0819f2aed04f16344593b1c35dfd":{"name":"@balena/jellyfish-core","version":"5.1.23-renovate-balena-jellyfish-types-0-x-916fb21f9c1e0819f2aed04f16344593b1c35dfd","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.23-renovate-balena-jellyfish-types-0-x-916fb21f9c1e0819f2aed04f16344593b1c35dfd","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"07bd33f726fa8876d0a9a35c0806bf5c63de8a4a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.23-renovate-balena-jellyfish-types-0-x-916fb21f9c1e0819f2aed04f16344593b1c35dfd.tgz","fileCount":202,"integrity":"sha512-BR1B5MkL76weAcjKmEZcC6iIEyRm/bAW7NGnzD2TxTVGcM3ifRsii5h+gpp06AtGRFFao0a9WQpUJTnBIhemhQ==","signatures":[{"sig":"MEYCIQCvWoEw3jqW2v4bcrlakjureA1mD8PQvqQ9fwHfbL0eggIhAKAAw4c+xWQaRW3Z6mhoJTnyaRJJMK83oX38+GYrySwV","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":743423,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhJgVICRA9TVsSAnZWagAA4i4P/2BS9q8ZziPH2QHroaUc\nK9c7Tieo3cUnuBhcShxSLXKx9kIEUjywGGDWlqN3DwHWRrya+iFyiz57SN2+\nBykbwpAPRtEsg8ekP3EHrjv4GbCiuAeM3Zh+JwDLwyfdSxFmuULzm1qpe5Fm\nMDmRzgFsX7ZeB05lJiVvQSURQqqqeAywgS0g4rP6SxsTifmc0uhq4i1Wx4vQ\nSVXucKImK2VJszljAB9REpx9/IecweyGYVMu+VxlxPx0Csepf4mZSKtPhpZv\nD5YVE7yS8ieYFl9K5cF8C6ddi6LxArZI49Fivxhp0zqwDMvKA48fVwEHdfnB\nq91hEKxZZbdCQ3K+5m9Hppav700zSS7qHjGYbIaqjb1IJW/AKhQEpvelRs0e\nxrQtp2teiMmCEzEEa2cuRaooDOG5HcwdOQDEA1tSFWk/Z9PmZbdJb8KHJgnH\neH7WjurSA6Fn/0FAdEUp5xAaFAfQZl2Z/x8CIs1rbgiLYNcwf+aQOUD3lRFQ\nLTuDG2F5hihXjLo3G1ece9/AmQ9EggqJG73TTjmogybob1pBoJsGhCGSzISd\nx/YXyFOoRyeUmiY+zOyJMBA45hih2PgklWsAHFbP9kmdTEa9VKBKFWjgSor7\nzTx9kDloo3Q5YaqiM+2MOBwXDa9uaG9Fl0RNrRtnv1HRnK8WV/AQRWAanXK3\nSOHt\r\n=FC0u\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"916fb21f9c1e0819f2aed04f16344593b1c35dfd","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-08-25T08:51:51.576Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.64","@balena/jellyfish-metrics":"^1.0.333","@balena/jellyfish-environment":"^4.3.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.9.9","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.23-renovate-balena-jellyfish-types-0-x-916fb21f9c1e0819f2aed04f16344593b1c35dfd_1629881672148_0.19776817017545079","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.23-renovate-major-external-major-25c69ba5b336859915d9195245cba5f57a10efc9":{"name":"@balena/jellyfish-core","version":"5.1.23-renovate-major-external-major-25c69ba5b336859915d9195245cba5f57a10efc9","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.23-renovate-major-external-major-25c69ba5b336859915d9195245cba5f57a10efc9","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ddca52c70a9bff8e36370be254157713f87f9135","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.23-renovate-major-external-major-25c69ba5b336859915d9195245cba5f57a10efc9.tgz","fileCount":202,"integrity":"sha512-H8UuRJ5h9Sd01mIBEwjlPiSEedpKtV8RS0e0oFFlIU8pSByQdBEI/NmV9xI0Tu69DOo12it2GfiBmfm1NWoOng==","signatures":[{"sig":"MEUCIDLE50s8/kKtnGMTy7wZcJk66yRxUMb4o5xyKgkp68YeAiEA6m2220txEmNjP1nJ4DjysVhfHldNw31p9v15XDyoca4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":743386,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhJgXMCRA9TVsSAnZWagAAJU4QAJ6zOXVP3mcDtK4RaHXe\nCKTZITz62ffL8nNKxi4kro5GnuFQfREpZisMU1yyG7gfGii1O+ltZJrbzNPF\nijMQNs8NQwmhAfP0vJ0UI60a+20i1xhKGbhlRr5Zp65O5EvHV5nvnJpwwNwX\nNAejgSLsBiQIqBsMCzyZhGYxz0EUXrvEZD/f7EKJp10l73AqEh/yBOekZjcN\n9V6vdWZ7y6q+v3JZE2lyhfGPQu/Kn7aGpGxv0dqgfraBWdj/NauuH+eTl4Dq\njP0i6nK5hYwLWb1McH1o0v15UJolkHHMKNOAbPcZ7tBD1MDAsEcPyOeIzZEM\nOXg1qepz9kqAPHI3TC6LPy3P32HkErcEUjBAwryTfSyfaT2RFz77yGuPBr3U\nRwfy2fNjzjjpHVUr9KXa0xOwOParzwoxwLbFCArU+ub/OY18hMfzLXDxyn/u\nV0/95lu7sbMacnxIQdDtFN0LwGPxo9jRwVjryAKQoH2T8kQZO/QuDi+bW/Hq\nE7qrk/JyDhgkroFbAPELRA+6jAItsZ95XHTgOv1rwElGoEDmaEqj/xAUuvCy\nMb8HT/Z2pGNcwq/zHVS/fQpmsP4FSP8gjMUijCTjm4eo6Vh6RtGJxrTFD7Vh\nCFIB7vX+BWwibsCwng+ANT4394h0iwVF83MXw8dl8dSK1fjXqU7aqpzytdb2\n6M+s\r\n=3xTC\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"25c69ba5b336859915d9195245cba5f57a10efc9","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-08-25T08:53:31.533Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.64","@balena/jellyfish-metrics":"^1.0.333","@balena/jellyfish-environment":"^4.3.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.6","deplint":"^1.1.3","ts-jest":"^27.0.5","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^27.0.1","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.8.7","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.23-renovate-major-external-major-25c69ba5b336859915d9195245cba5f57a10efc9_1629881803899_0.705040926486139","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.23-renovate-balena-jellyfish-environment-5-x-7ca6d347fe45763e6a4de6c6dd1cdde99ca77902":{"name":"@balena/jellyfish-core","version":"5.1.23-renovate-balena-jellyfish-environment-5-x-7ca6d347fe45763e6a4de6c6dd1cdde99ca77902","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.23-renovate-balena-jellyfish-environment-5-x-7ca6d347fe45763e6a4de6c6dd1cdde99ca77902","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a4772753413424d03e40841978946af381341002","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.23-renovate-balena-jellyfish-environment-5-x-7ca6d347fe45763e6a4de6c6dd1cdde99ca77902.tgz","fileCount":202,"integrity":"sha512-mhwG235bgEiJ9ReiaGmJkHvqh4VjwqMXDGUopjbJtmLegVAeBuVT5ypoYoAlKE5wdAVtP+IBZ5kTNGBn1YwVug==","signatures":[{"sig":"MEQCIA6ZxrRLrwfEjoZjCAjm+crezF/y8z6tVNa1B2YkPLJIAiBSTCV7350Lhzgio3hZHvKs7NCYcg3SyO4Ucp3yFILcCA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":743430,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhJgXpCRA9TVsSAnZWagAAE3gP/AvpN+HeF5brYWlL9Dpp\nK8ky4vBerc/UyDSTj+/eTrTEu5FgZykxsxwXMvWH5RYUKe5I0yIWRFiBFpuL\n8CG+nPHs09igDF8RWyqvyXgdHuNx/dfc2JptCN0qTAyClWuAYhjjeKR7wMrB\nzs67ipizIepmy5QFDvIhMpHLBRLlCFAzTzBQkj5yoW3xDJfKJvqfZyX0ySjV\nHDHB0+rPOSyUaGejCpRTdcqsMxVO7rrKbnPoCGYSbfAcPif6YVYaWtrBtUAw\ndOQSsl9Ttj0/MJPiC476+LN7ptGkTOa4cqYRwj7Tz1dwB4MwE2FLke3nSHe7\nE1lbaSdTE4wvg+0scX0/XwOv4dSTZ+aIk20kQc5nH3cvJ8Yl+OY3qzcbcwRx\nRgT5BGKz2ldnUKQXYcoRiqKbJrs2QnWBea9RyW0VY7O5pX1m80d/q7B3cePe\n6DOrdXFAusueyTEu9mZICQdxgtPBKA2Q/FUWy+XzSszPVxUdgXbtSJ+437JZ\nsJBVYursxKS7+orpet9jZLynlswOiNYN9sn6vxBstm7Jrt/b+107CFLAwfxo\nr9l8wQspQFY2yYk5hFRqXra/h0fQHIbvZFFWkLK04MFXXBt3Be8Z5GHWqtWB\nlhXsXp0TbT/Yj/iuBGIqPNy2VlsFvjgu920U05gzJh09l5AAPxWCiBoXjZQx\n+g7Q\r\n=oECK\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7ca6d347fe45763e6a4de6c6dd1cdde99ca77902","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-08-25T08:52:31.668Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.64","@balena/jellyfish-metrics":"^1.0.333","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.7","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.23-renovate-balena-jellyfish-environment-5-x-7ca6d347fe45763e6a4de6c6dd1cdde99ca77902_1629881833434_0.5331229379859765","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.23":{"name":"@balena/jellyfish-core","version":"5.1.23","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.23","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c99f1f0e4a6fa768d50e1c3d3008eac79ee483c1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.23.tgz","fileCount":202,"integrity":"sha512-Wpp1XeELzmiVPISElHSFBFyAt8WGa6aQL4T9XlMM6k+4D7HQRYCdi7s3DaYveWzVx+14/GSJKNQN1ABQIjnfmw==","signatures":[{"sig":"MEUCIHg5fR8cWDNTQV3ebN/gw71Yh2RgQzYwssPUejajReHNAiEAgcRENQF480yWroaNFkadFrx88ebfvx1TP+xncA6EHms=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":743347,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhJg1BCRA9TVsSAnZWagAA8RAQAJQVj4bDpXVOXOnMkycB\nGXz10APD0y6EMhroJNfpV292qzkc7IZB/hYicJvEcIWl8qE/pXg0/iouDTlc\nqcMFAbCyM6FQtzwkL+5w0I+R9LgU+ZcU+NNTVd8QGO6bLofU+UWN72X3dzqE\n+WFaYx5+pFMJnjvNvai5+T6/3V6viZVA39/FAclHT+HiVEkD+6jntjBnM5IP\nQ9x1vdjypA56W/eUQ8QhTxv5Iqa8GrHqOPP3AP7+HWANHDmTSyU29WUr2owG\nBBBBWpEjcrqAjKNuKVIvB5RrpUM6tftOfa9/d18zxoOPqs4XXJwix+E8SRI+\nwowYXdzQMOvKKxMmFeltkQMTz3pORNgJ9Ik/IH2CkT5ybD3RnXQv7hssTUoL\n04xYNdM+Y2kk7WUIQ5tbbz9NfPPJrXQ4LD7gPSkTD8drQndQFZ1CHP9pgYWX\nLdggEheFOF81jcQWVhyfLCUeJvxmfDYLcI/YJdeqgDkqtnsh5p30co6qtp8q\njnUORisw5c520+SxviUEDKTEApeJyqI4yh/lwvzUjMA19aHP8hkLsPY4IvfS\nr1v+meZzKvefj6Xh/8CInzt5KujjJTQFJksnz9b1IHsiohvo9f5B3nI7qsl8\nbp1C6sIQ9X0EXhhaDLUv9kIzwmGX8ojrhcCdsi5ZZbLMsxUawBTNlC2PpA5L\nJOVi\r\n=ZVu+\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c1a84c1cc206e8818fd96dd3822f72ed4ab9ccb2","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-08-25T09:26:25.431Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.64","@balena/jellyfish-metrics":"^1.0.333","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.8.7","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.23_1629883713437_0.037339717419394125","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.24-renovate-major-external-major-95b6cfcaef5536d3b17accb39b0817c4b6e5c5b4":{"name":"@balena/jellyfish-core","version":"5.1.24-renovate-major-external-major-95b6cfcaef5536d3b17accb39b0817c4b6e5c5b4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.24-renovate-major-external-major-95b6cfcaef5536d3b17accb39b0817c4b6e5c5b4","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8221b8e2b6b19b566d09776c18a2dc6f822d1514","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.24-renovate-major-external-major-95b6cfcaef5536d3b17accb39b0817c4b6e5c5b4.tgz","fileCount":202,"integrity":"sha512-XsYTltfXvh8+3VYaWItW8p0R13AMF+fRtcS8aLJHAqbZgji30oUiKbsH1XFxZiL1e8lDf2ldnYKY/d4AZS803A==","signatures":[{"sig":"MEYCIQCY9g7KUEhrsuCc8hFR6ETKPYpUa9574Dcx/MSTOFRazAIhAJANR6dtEaMWHIBkKrzuYCTWOr7WTNGMgPB1X7tPNU+5","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":743488,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhJg4wCRA9TVsSAnZWagAANNEP/3wcUSdgfU3xFIuMsLj7\nAoXWVs3ny8r1LbmK3MhFOXRUYTjV/MbJmQjgTSKYpvxs5Pj49gHCCVb4VVw7\nsWNQ8A2hEEzwRdgTNjuA1gxIFLhzd1EM9pQ8ShbfEBfSRYKsFi+9U6EDCgWF\nf+lnCmmiYJ609LEU1jYEOaAMtfR6nMBfSY+5xosc0+7HIx+L2zZE7P5D+wnI\ntxcDPqobTjlaLX6vDSL+bEZjPY7X6sh/JF9ERdgCPINQTC2DhyBtGjPkz284\n4q1857GOrrOv4wYvukH+5y49BBqAakjTCiFLgRXemfFZTsKAcw+IaeeisOeN\nHHJcGBTqpCH2efCZAJKiR9fxaFn+i2By28haoovDsEGaQH6kOzkgeeyFTtN1\nID7G0zuX9g7enRbPpIDN4/K1cr6ZDsafWaMSEgqv15vSsItGc132uGCVzYaq\n0YU435KAXfYbADXlCtdhNMd78swJvpIjdo1cLpT130Ue3K0zY/bOvezBJMoi\nyqyu9TMiL4fpzkeHHnyxp6aF/iR4ESnT1tw+7NtUNBoN6rJTbM/53jKZJKFQ\nny4yc6Lhb+JqzL2dy6OVtXvpRZvLp1u5nxtL19wjC3I8oPe+3bq55JMFUSQE\nFVRIG9MdoL5dz6DBGl9LR1WyDHULlKzCsYD0PwKUNWiq1dmNT7lkyQfuBMt/\nLDPm\r\n=1xjJ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"95b6cfcaef5536d3b17accb39b0817c4b6e5c5b4","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-08-25T09:29:54.555Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.64","@balena/jellyfish-metrics":"^1.0.333","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.6","deplint":"^1.1.3","ts-jest":"^27.0.5","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^27.0.1","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.8.7","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.24-renovate-major-external-major-95b6cfcaef5536d3b17accb39b0817c4b6e5c5b4_1629883952436_0.18052285708091143","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.24-renovate-balena-jellyfish-types-0-x-fbb0f5e95ac4ae8ded7f7bbf639e47a9d756daf8":{"name":"@balena/jellyfish-core","version":"5.1.24-renovate-balena-jellyfish-types-0-x-fbb0f5e95ac4ae8ded7f7bbf639e47a9d756daf8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.24-renovate-balena-jellyfish-types-0-x-fbb0f5e95ac4ae8ded7f7bbf639e47a9d756daf8","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2db09b717614ec09a1d4570fe03f76304773a976","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.24-renovate-balena-jellyfish-types-0-x-fbb0f5e95ac4ae8ded7f7bbf639e47a9d756daf8.tgz","fileCount":202,"integrity":"sha512-sokBR/V+LBuB5chcmDKw0zcrl93kjo99WmjKONzEaxfvMKoz96qE0O2AUSSYjmIQ065/X+8N+XYS7t+WYZ26UA==","signatures":[{"sig":"MEUCIDtLvurxMA7B9FLNo0ixHcw4EvJTte9UltqPmpd8JiwJAiEAmoslzvsCkYwgit37CFN+OOTeqsdBx2+IjyqAcsLFygc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":743525,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhJg6wCRA9TVsSAnZWagAAXiQP/2KDUH7n1qNnFzUmIIjR\nc/KZfJbPHtsuw+LmlvCUPif8jvF19jckhvMtUCGohhCip5FARkxjwiqENbOe\nVM7O0l1QoEMgMN0QlLxWoqNS212dDlRgOEMUOeLNV/XTFiHOYRra7VgbanF5\nNuHkVCvByAPq1PS6x907g+iv1nrB9fV9YHG5jzI71SGQfJYyKzpu3SgP/yLo\nl7+JmqcVD22xzBCOOB0jcsGiamVxwjbVSta09Sra11Hlvi3d+USNB30MbJmx\nnULpG2pTqXOpbmjhabZ/E1tFxdyShVSpMaDnRUvZUF4RlffvHHMpDByq+42L\nA6iOL2+dYFHLpZsXbT76XDCR/oGLU4J4FNgkj9eGYx8Y8mzyWwGHDlP5w/io\nVyfcAjNG1GIzFi3iEm6Sis4pSpDnYc9ENZpJZT/CDYHd0+lLNFgi1zgMf5JP\nV6D96mCgGCP5naHFcOE7Yj2/pU1KbrkQtGDbqIlGitk8KyoULtUbxRfX+i0Y\njl5pPpbQQONU10HSaactJkpH+RVHPNOTH0jgU7ReITVeBMbFSIIgODPx7UA7\nEbvu0565+VVmCHolgqHrZ0Ag7Qqyi5UTHEL0ZVV+LCc0vn6mPzT/FcNa0Ks+\nlS2EskDMTQjCTRGYJL/XvB6LiHf5s+enBUwob8dO6lZIp78bBcOCNQlWrKiE\nYrRR\r\n=ldA3\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"fbb0f5e95ac4ae8ded7f7bbf639e47a9d756daf8","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-08-25T09:31:02.083Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.64","@balena/jellyfish-metrics":"^1.0.333","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.9.9","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.24-renovate-balena-jellyfish-types-0-x-fbb0f5e95ac4ae8ded7f7bbf639e47a9d756daf8_1629884080037_0.7031738624540249","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.24":{"name":"@balena/jellyfish-core","version":"5.1.24","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.24","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8e5af199d795ce4d233dd5d20f6060c68dc9fc70","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.24.tgz","fileCount":202,"integrity":"sha512-0gDx/mNHuHRjgBuJQ49rrhzFNPNU3mwwAj7to3J1Ey4mbdfBbtdep2o5tFzoZRBDLnPnA5jGZynZDmmo98UFPw==","signatures":[{"sig":"MEUCIQDYodsqXwD0T4X44xWo7TA6AATjjK9rfsLVibrqPX0VyAIgfNp2eOJsB8cEHotvBfobMQKzlIpO1CtwrVwxeqIyR/k=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":743448,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhJiePCRA9TVsSAnZWagAArM0P/1HeBWFMvOxED9PBDFYw\nqca7rtUQ90e9eV6nLxCDfsfjeNKQUuBILp0qZJAbiUxRS2mJxzh/WPA756NZ\n7i4LnYd/X0PgeNvHshLpnEuiRs9iXl3DGI48F47x7FZpdPmohjynGrUzJxzR\nPEMMT9rMKRUIRJpjOGgEqXMGNJaqpTOk2ZEQjAUD1p136wWvI0EIspJT9F/n\nU7FdcsJFOpijj32sqkBBKuZDPStzlkS9HBXNK8er04bqmzTCHCxWdd98Tv+i\n5QVioxfoE/Aq1BlV3WyxTlGx/0Eb50a9eauYV1+UkIiE9ZUq9wwH0RrUBqEu\nGLnhsoMy+/H9F9sH634TW+x+crWOQpAgvV535nxud2HM7xHMWHFAMEyG3R6p\n4pN8h9oBCVIsHtJk9SGk5AC2+GrXNNCSFDmVDuTS/XwMS42Ym3aaSOs5ozFS\ntQgcDlgaKs0CbvP916nLK6xWQBY0LMY/7Se9X/qPrjBb1FBLRtToI7lCq0gp\n9pM2bOqN8kZ1N39PE3TIuxeafgm2jnXAn7h6CUDkgBc92QpCFiOT9EruNpiA\n7DbldwABL0D0J9JFWH48Vw/g7hXztBvjqT0n6LmTzCOj1s9F3sItaOmzjHR1\n9ndI7zOvwYYJ/v0hLp/Ikrzf/E8YzleS90dgzQBTOz5KNXBKg/c9Gc6lDuPk\nZ4h7\r\n=pIHV\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"53f6cb3f03b857b77589622a9c70dccedc63c7e7","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-08-25T11:18:51.720Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.64","@balena/jellyfish-metrics":"^1.0.333","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.9.9","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.24_1629890446959_0.0920554173775252","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.25-renovate-major-external-major-aa77e9acb8e3de2e8772c6be7d2ac48a2485db5f":{"name":"@balena/jellyfish-core","version":"5.1.25-renovate-major-external-major-aa77e9acb8e3de2e8772c6be7d2ac48a2485db5f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.25-renovate-major-external-major-aa77e9acb8e3de2e8772c6be7d2ac48a2485db5f","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"cbd1f2b06ba80575971c5cf715a89b5a5067ee31","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.25-renovate-major-external-major-aa77e9acb8e3de2e8772c6be7d2ac48a2485db5f.tgz","fileCount":202,"integrity":"sha512-DRw4Zy7mQq+GoTbLxi6lfmUpZNV8/vNDHrREncmDAOTneeULDp7UGR641qgn/0ixNXWby6g2JiDwCsUKdH7DcA==","signatures":[{"sig":"MEUCIQCRy7HmVIW03Ylwc6Grv/B5LcKi09gqoJY/zgP/tSH/EwIgHEsARRKtNyaA8rYFKOeAMj7WUVn+YzA9gEtWBilxQ/s=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":743589,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhJijCCRA9TVsSAnZWagAAbRUP/iSie9ZMArcOfWuwqQuM\nYUE+TgdEzOjZG2yTX5XHGQTU3oEoOJLTkFBSUqw2tYB4NzFCq3K67mF2RrVr\nWj/4PNnFi8ZGeE6R0m+Uwk24Mz8snZoaKFlDXyAigNvoPgsj7/3zdwC++6cf\nSsmqazLUSPQzF38M3IAJo2Ic9j2ONHkU4kb1aMsoKtQETyJ/+hawb5pAaMgr\nzYH7B8OwdwAiwx+9Tpsz3HkPNjWEHE7qN2ieve6Ga9Ye/x9Td237EDCohLw4\nPB8QOdYZVAKauNjIPc+iU8milxGCKpH09rWq4dEjt3afS9eB9KQAjkkEJvZQ\nmMPY/wiGT0V67UAU+3CorGmb+FHuneMMI0b9msX6n5D2lfZuaaL6fIqg3jjQ\nP8NJ+eEz9IcD+4g6sspnoqhw7MzLrFjXW9yTyBpPxvpIFI0KrI+NhxPgrdVN\ncB/fTL+vVvB0onSVToqyqDUeAYFO5jLXjpt+ERvv01PM9WAlnmmMBk5zjPiF\nRZ3fUQ3iggjp/XG6ASVet+WFSPUkAdt4Wga8jPY9Sqs8U/LRz3X4Dl6JyaS3\nE825hWHAqxeEfhvQMNXfFlnVCGBioj9I3DUgntrv+6N3q1Y/aHrpb1RlQzuf\n/WgeHvQkVuI4wmCIuVfAvSmbcLu9bKF6q93X4LfHqziU+AFpTJMC7l8iwr9K\nsPhX\r\n=Ozwt\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"aa77e9acb8e3de2e8772c6be7d2ac48a2485db5f","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-08-25T11:22:51.742Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.64","@balena/jellyfish-metrics":"^1.0.333","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.6","deplint":"^1.1.3","ts-jest":"^27.0.5","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^27.0.1","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.9.9","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.25-renovate-major-external-major-aa77e9acb8e3de2e8772c6be7d2ac48a2485db5f_1629890754003_0.02533998666962911","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.25-add-json-schema-types-75685f79a1bf846fad631fa1e1dad68272f25fae":{"name":"@balena/jellyfish-core","version":"5.1.25-add-json-schema-types-75685f79a1bf846fad631fa1e1dad68272f25fae","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.25-add-json-schema-types-75685f79a1bf846fad631fa1e1dad68272f25fae","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"371a564b92b10fb63749c9b04e8d9a5473ab2577","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.25-add-json-schema-types-75685f79a1bf846fad631fa1e1dad68272f25fae.tgz","fileCount":202,"integrity":"sha512-rRxzlAnFzq27gnVM+q+NpWcq82uIJ6+giNzLcxPIUovUvRHW2/7k8iYVW5uxSFjbw5VfGaJZwBwyig1rxWQEJg==","signatures":[{"sig":"MEUCIBqJUahS5dSGbH2dAb+p27dCrXOIN5XgQjTibBzZkBKiAiEA/dCVkIBAn+F9iAp7tdh4op3GL5Gb4E3egzYtWGQYccs=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":743612,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhJuO5CRA9TVsSAnZWagAAtCQP/3FMeSdfqy8knQxfXiQU\nVgNbaboM2/aKTkj+E6CTxIYhBa2OwsV+mk4dIkdqIkuUfPCLjrqXzFgbTsWz\nbh3S7xT6FNxKAEaBITEi0cUfBMKScEiucDrVmuYr9v1r2evwNtl0n1dKAS7M\nY1+h/vQEGFaF2oqhOJjnBdXPC+joPBc7n6kqJxk6SB2QEounEvm/jIAreLeC\n/HaSx+X4RnG96+8hGSvArS798+maU716HL7De5QldD3EnrgG4m0R5w15DRyS\nYgVA+RJ1cFJakwXSIajVtEklC9O4r5tBYdsAvB6wgDtOSvEfaupdSYZamRXz\nCHgmMSX+H080p+EakkZzvJ+XeoDf6TO9wn2POPku/IR0Wtq5tEaSm6vDw9v5\nTyCCRU0VByAVvdgNW0vy9vRlBrvTbui2C75vgpsPSJxUnHlCM1J3Iv1Bq//K\nVtmnCeCXouGZ4usRLukwvYDn6ZKcHaX+4EC40hoEHxw11JTQmo5j+Wp5j3is\nv1bWYHS0zNKffrnrxHumLWdnb4vPeTymc8R6CajAjTSWd+dYLbpeh/4648Kr\nebDRayeJr5eNfZHzx/F4Iwk7nSYDNcbhTG8Kf5bqIOAi96XmbtlRLMD1SnWt\nD+StbStbxbRxE784y0pFw4kgP+Wngagfp/b0saEYtBDBlObzfaChJeSWH9vQ\nkz0m\r\n=QbuH\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"75685f79a1bf846fad631fa1e1dad68272f25fae","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-08-26T00:40:34.492Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.64","@balena/jellyfish-metrics":"^1.0.333","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.9.9","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.25-add-json-schema-types-75685f79a1bf846fad631fa1e1dad68272f25fae_1629938617423_0.2558600005812033","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.25":{"name":"@balena/jellyfish-core","version":"5.1.25","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.25","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"de173fd878cf7213efd54749b953441dfbb4728f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.25.tgz","fileCount":202,"integrity":"sha512-sWZwfrriTIwsZqGyG3yMJJdWny7cHUOnVqpZxOpAJp1wgFAB7sGwzPGI1wiwFvBR5CFw6RGKrfVzUfo9szQkWg==","signatures":[{"sig":"MEYCIQC3Pee7wH7PUe7bsblQabyEMyE2AH6K04rAYpYyx80+OAIhAPkR6OWQcZYq1dIhF1ZGr14JVWsmcFlqwvwE0BVAJMVQ","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":743549,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhJuVzCRA9TVsSAnZWagAA+fkP/j/I8hKkPnHAZ6JQIHWb\nBC86Qoowo0EcturNG1l+RlBx9UBNleZFnlONgZCha5dKeuaejmuAhqi6K0sc\ndz6i/mhhITfeIP50RAy5bVdSyju+2pZOsOqXraJvqDbYxpW5iIV0Z4oosqqR\nMC6jadBbZTWrds/WpWYHgehJVu+vyuQ/fphvxCtnGydwAqv5q0N9bVYhs58e\n7DAKkUrUGtTtFj19jZ3Vvz4ed2MX62+1Of6z3zKLz7GsYvBjDgB3PpXPchJV\n34Ftqb3zAfMdoyZvmU6l84bPA8AHY1eCOqauYlknC5mtrqq67wrVXYweW/8K\nyGTm3LCUDOttWWjiIkJLcNHm+cCmzkb/l77VTtWK5aobXatpZDAS0OHK8O6l\ni4xaj49R8NvTsK9ZDUPLKJxDTp4RuqntjtetVMxwYBIzbUjv33uIuLGJRu6n\nqS6b2j+j20zYYpE9A36zuTiH9lLjbzKH9Wzv3F+pz5mn9ArG8DOk1aOI+F7Y\n2B7QH3yBddXWnYtxP1YdLYyFg2Wp9r/q9plvLKkEJsqClTt9cyBC0aP8yC6m\nlS5mJ0bIipCZJoglTfGkKEORxjIecY0Ou2HLE8vJAIQNK6ET+foDHJABe0CP\n+MzixDiJ4fRIbeH30NG8yyeEiGlSdiYmLX4pnPiFLZMcQidt4vfj2GIVzEAU\nuWW9\r\n=UJr7\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f90493e0ed7fb1e15fddd344e9f4e02804c6c326","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-08-26T00:49:11.599Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.64","@balena/jellyfish-metrics":"^1.0.333","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.9.9","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.25_1629939059647_0.7649123452941844","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.26-renovate-internal-patch-a35686e3e231fb3044e7be4aa56f628920e60ac6":{"name":"@balena/jellyfish-core","version":"5.1.26-renovate-internal-patch-a35686e3e231fb3044e7be4aa56f628920e60ac6","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.26-renovate-internal-patch-a35686e3e231fb3044e7be4aa56f628920e60ac6","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"103c5604a96593ea42f726006b6ba541ac346103","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.26-renovate-internal-patch-a35686e3e231fb3044e7be4aa56f628920e60ac6.tgz","fileCount":202,"integrity":"sha512-LsH0c29SH4KgE0Pvt074cfCfvYjA7pZjOuO1t4I/d49HYCcNXFFcncBLxAYgdDQdemAqu8fhi/9G7npkskZ86Q==","signatures":[{"sig":"MEYCIQCbOJ8GsGnLAG3Tr92iOaLOD/RyISRuBi+lX1+5OeHy3wIhAONWtyUqI1NA/otlHIUTo/75U2tIjc06sVwc0S1bD8ma","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":743719,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhJuZnCRA9TVsSAnZWagAAf2gQAIMvIbiZSmvkEHxpQgub\nIBJFdOk7CyevFVodCjTuISLk2Ly+QqpWPuac2T4aRNAe60NoiMEzWcAn96QG\ndFX9isb8Z2MtIZuGIVPwWtjmeU76W8BkazPEYvDmK8Q4r6Vc1iG2ZoEUu5EO\nEy9Ow0tUxL/anBTOee+9ItmEqZdZKGwpHpMvwJdXr3p0VrDG6e5ENYaKjmzf\nBdEVPRjQJ6c9zNfxPLqL8BrdQ3VAL2KBPPHQeouinKfhXQDbXLbZ01oG9MP6\nrpvaVaglpWwFCLM7Rb+e4yybUfKO83rC/VzMpYLQ3MB0M1y0VTCa8Cw79buy\nKwP8RxiCkvXy1wdoWaE/VF7u5z8KxI9r6WbzMvY+1Ni146sRjkdWu1//eE+W\ncC+looaWQhMat7VpdFp45CQmmMUFNm+k/AeV+gy/NzOEiyqxnI5XLSqECsm7\nnsSY2WEI0FJhE18X4drG1HhHu0ZHCYEM9oweh653ojHIUFQ1Dwi5rGvJOMm8\nu1LzqMFC74XVMBHWFRDmqOH2vTncZw6UsQsZ525YFmsRRomcCv8X+8SfyvSz\nagDReEaNc1lzUkVFfIVkm2lAdWI0e/yXo3DIDH0aLe+QZGg0eQt0YIPWL9NF\nWxoIVcki/OOf7SUpscbcQumTTr4ND0jvbU5MO3uEu9LRfORBYvljpYbmf2B9\nLkc/\r\n=tyM2\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a35686e3e231fb3044e7be4aa56f628920e60ac6","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-08-26T00:51:21.974Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.64","@balena/jellyfish-metrics":"^1.0.336","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.9.9","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.26-renovate-internal-patch-a35686e3e231fb3044e7be4aa56f628920e60ac6_1629939303332_0.9846517711386498","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.26-renovate-major-external-major-d837f0c5714c8d22dfd381e70d38d4d8093444ec":{"name":"@balena/jellyfish-core","version":"5.1.26-renovate-major-external-major-d837f0c5714c8d22dfd381e70d38d4d8093444ec","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.26-renovate-major-external-major-d837f0c5714c8d22dfd381e70d38d4d8093444ec","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d9aa94b4bdfd6e84e3d4b0d01a4d89601eea8b25","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.26-renovate-major-external-major-d837f0c5714c8d22dfd381e70d38d4d8093444ec.tgz","fileCount":202,"integrity":"sha512-MEDe0RpHwGe5hKYDUvO/N0DxLqkd0YfGF5Hk8r1BzWNRyBjZ8lD4pn20yeApgWXmW9Gb/1cNyDkmAy+Yf9foRQ==","signatures":[{"sig":"MEYCIQD4M/Piq6EEKCBoZxjmzHblt+rK6ggEGKK0cK5Q25jhrwIhAO/m5+QNWVSKHO9CkGeMVGV/RHZrh3wrhytXXSoqDsbA","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":743690,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhJubWCRA9TVsSAnZWagAAdpYP/jOhyvLAIA1bRgr6NbDD\nyXhuVdSrex8yJ6rKBczg3Hu5SSes/t7yOK3r13jnGH70qr/2R/dr+LpGee0g\nQy8j9Gr/LLRYOKvYINwy0UMXg8K8IEE3FNRDIaIFQxGrNf/5e6fyroECJVwV\nWchGGkjpahsUKoiF5jMbv8/WVyN/YnMSQL7+Z1g99Umpp9RzlFFlAgY/CHZ+\nS2VcEmsSjGsm29ZvcLWWMU0VsHz+joKLlKfl45jHso+yRFoVKm8XBIhrMRyd\nuvHBjjz6z8axbvNqqNDNVKAtsiKhsJvJgQbSz7HZWj/w2znX+TdFjWKoDOB4\ncmAejsKaYY0K68NlngkA+knsJcyEmmJCW4EdYqHwFFkurONj+Xf90pPu/UK6\nSFWbJilWd7SWfkMvDSZI+t1BqzveFe/VHURDThGXB5aBYBvY2W/K6c+juksc\n/t/3q5bKFJAf14Idl5zwLfv403Ej6uNOQXDZbXuoY6WvYTd1iPu4sgbDQU8j\nFONCy7qo1szu/KtAJuyMaAYmjCq2KaUu1pZgpSd81kZFwURFDQtiIa7Og3im\nfdIR22JgOq8Rff2ze83uJQY58yH1Y4MI/lcsQew2Din0PnvJfq+xYAV/2qV3\nWuBOLnwPpFeUK2UQz+4hSimYM60Oij8KsvdkKVUXj83lOVLNiXct+9WzfVaq\nwAXv\r\n=Gahl\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d837f0c5714c8d22dfd381e70d38d4d8093444ec","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-08-26T00:54:42.766Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.64","@balena/jellyfish-metrics":"^1.0.333","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.0.6","deplint":"^1.1.3","ts-jest":"^27.0.5","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^27.0.1","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.9.9","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.26-renovate-major-external-major-d837f0c5714c8d22dfd381e70d38d4d8093444ec_1629939414427_0.9585002849852748","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.2.0-add-scheduled-action-725bb992508038e16e6f1363d0d9aaf29a3cd3dd":{"name":"@balena/jellyfish-core","version":"5.2.0-add-scheduled-action-725bb992508038e16e6f1363d0d9aaf29a3cd3dd","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.2.0-add-scheduled-action-725bb992508038e16e6f1363d0d9aaf29a3cd3dd","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3e5b12ce56a68ecf51b29c50107fb0ad34557ae7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.2.0-add-scheduled-action-725bb992508038e16e6f1363d0d9aaf29a3cd3dd.tgz","fileCount":205,"integrity":"sha512-yxAao6/59aEkK1kGAJ5CeTBz6o5JB8W3j/IPR/yrcCx7z5IaonkKjqWmjiCLxESEgF0XFQHpjXTSfDEEPqOCgw==","signatures":[{"sig":"MEYCIQCk1+XQqmYyJIPtn79cBSChBq0G10TA2ARxZ2wvT4Yf1AIhAMg50XsUjew7RibYFe4gN7OSR+ieDR/eVp8vCUoJqMZr","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":749774,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhJ2BeCRA9TVsSAnZWagAAh20P/Ai5RjVUYcVRPMxZ1IyC\nt+u2WP1xM8q/UGb2hQsgwd2rP0fDFNQlLD2Mj+evuvbE9vU/d4gMG5ygXCg4\nkaz1HjECy+Fbf+OFx5gJCEJVQkNQzF5NoWhl/KZhTuZ1SfpWyDbuh7fsnNFo\nzsTuZmipkw5nU0iAUDVf1JZKcgxsrZNhHW3KfW4+WHeqv/WxwDpjXbvOSXJP\ndxjMIBjczVmmKgtmgLM2QTKUpL4VVznxdksveOfLTli+I27utFoUc4HIn8xU\npK62zl4TAAXamVwGuWT74oWEkfei/U/8Rkc/nwftrSGL1KXcxxXhznVHIPr/\n8Zw073TNEb4gbfaH232FNi/RSncCgfaDJJBsYtXhHuVJrOXL4BihvYf+ZPDC\nEONb7/6b1FaVYK/u8sSmWmv6YRMSbeWc1WOkL5eicBOgnmuRDUpho7Jvlprz\nen7p2QYx0fdiw6AHsMUdGsDCAayH5SVy21b/Zs85C100OOmYiA3ZbOzAE0Gz\nULrquVzolgDGSWHHmHxpsAUSDZW0SzL2AnVHtrem5cqgB+kB6b43gFPJ/qf6\nBEvYOwJG3o7ckB8cNgCQHkWG00oaDRk6LzF/+leTS2Y/LDPS/gan6ofvvLen\nOdk+GJrGb4uLJgvjXF/l18VZ4+lV9ZYR2GhqhdEeSG+tG2RSCv3vNW40/04A\n8wa7\r\n=dfN8\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"725bb992508038e16e6f1363d0d9aaf29a3cd3dd","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-08-26T09:31:34.824Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.10.2","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.64","@balena/jellyfish-metrics":"^1.0.333","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.2","depcheck":"^1.4.2","typescript":"^4.3.5","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.0.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.5.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.9.9","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.2.0-add-scheduled-action-725bb992508038e16e6f1363d0d9aaf29a3cd3dd_1629970525731_0.9778542653699971","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.26-renovate-external-non-major-fa4a4033ac127b4ce329df9c63b269b9c11f1142":{"name":"@balena/jellyfish-core","version":"5.1.26-renovate-external-non-major-fa4a4033ac127b4ce329df9c63b269b9c11f1142","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.26-renovate-external-non-major-fa4a4033ac127b4ce329df9c63b269b9c11f1142","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d8c562c076ed8f3589809d271c123035460bf3a8","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.26-renovate-external-non-major-fa4a4033ac127b4ce329df9c63b269b9c11f1142.tgz","fileCount":202,"integrity":"sha512-AsgsdjPn12lLh/M4+vvaqn08J5sXR5akpzQv2uoUpfqC7BjSyOTlUwdwWkjJHN6vQ+8jUDaTb5S7OoMUBDluzQ==","signatures":[{"sig":"MEUCIBRLDUwfbRZZ7THXSuRg/OpCBJVR3/H/ukxixpdlhvIXAiEA2po49+++p4GX0USUBvmSlmCvbiFZIBIZFRM8DL7NPfk=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":744094,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhKuvCCRA9TVsSAnZWagAAES0P/iEki5cNM6BR4wPu7ndA\nA1BqGjEWtLFa+LTTIaEcl/9VNZ9DJ66/PPfj8mHnqv1NR/ZwxY7mr37XuXE3\nnB3Mle5mZM8KRBE7ErInzGB9gmQsvFIHI81bgVFyfjPu9saFgKo68cZReH0Q\nca8/kJExv54TFdDbDlOvKk03vhQ8mqncSyA81YJz56Gz3SM3J+DIlL//eoT0\nF1Oc3dDT5sMKBh6u9T52vQERHKnk7xV7kKYmqf5MXg7o04ZTLPJ03LVn5hdg\nBCxRYWps6Zk9qAJua7iubsc5InLlYXNwRhGr96tU9I0wkCm+qii8v9sKl/zr\nILnENeWYAWdMM6yRZOpicfNyKtRB0TXtAUt3XYIJ8A1H+v8UsqB5AbLvXCdD\ntKoOlOcUBj0HPyirZej/DjMcHq5JYj49bUCnssqZYopaT1sht+lJQ4WWhQgu\n2MrfXBUcLoW9XoZ3EJOIM2Zk4EvjnzAymLykyS6qjAQL4aZiUQLmKcwAAcAs\nJ4ejUvK/2NjBqFgswD5+vCJ9PwnCfnc0v8Td/keW67x8rMiwr+DllVwLL+P/\n2oIICHwdDKl2SE1lqg7SBxNbb4/Z6lCaegT5LNCMC5j8Er4om8o2C4MHxT7L\nfXAutrILh5qAk4ssUUPbRUdePqNZn438g8K3xx3RoWPEiLMWP6Fy623sloui\nwk/6\r\n=RtOc\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"fa4a4033ac127b4ce329df9c63b269b9c11f1142","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-08-29T02:03:15.226Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.64","@balena/jellyfish-metrics":"^1.0.333","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.7","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.9.9","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.26-renovate-external-non-major-fa4a4033ac127b4ce329df9c63b269b9c11f1142_1630202818133_0.9390477593731483","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.26":{"name":"@balena/jellyfish-core","version":"5.1.26","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.26","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"513e7679f51b08ca0f7cf3ac09cb5c1ee8410f9d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.26.tgz","fileCount":202,"integrity":"sha512-9+UsOpLT5fO2I0zDWgZ2bmyxjYPkYhLFTGPO+rl28xyNL6PXO061+MyOOu8lWkj4xeozctHaNQ+LimQSnRQgsw==","signatures":[{"sig":"MEQCIF3TeWP2a2kHFr/59hx/iAd/If9lsaK8YSkEj1B/6j4wAiAp9kMGHB/xA6ECzqIc7mswEozC9L5iJmPGhm+awZrWfQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":744025,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhKu2GCRA9TVsSAnZWagAADZ8P/A5lNU5xhRETMOnC7Zuo\n7KMeoATOKIMsVf5zs0AJET1o2sh/aBiD46MHd3J4SsRIDpRLnYRozQ+p55z+\n8FGoZga9G1YHmu1sFbI8/IRsL2j/pRdoszD20x47PSuges1DTzKj7GH/aHs1\nD/k4a+xiJL5E/ytAxngmzXoGu+44PKeR+vT54EyPdFGF7/0cFOLrkUvfKoul\ngeI8RS6jvQJmKt+H0HZajDtrbtvhEZ1oM0DdvGX8D05a7BPAh9eCaHrXU+8e\nusk8q7Bred3mNz2L6gUvQjdB/KkQVsL0xKE2384s8/MFj32BK/3TAlDzDevO\ntfZW3kaHfcdQofNTevonRwtFMuWbSRbROoGXKOjaA8EhAG3MJLTenQDuzZul\nZftMNT/CBK4zJomB26rO5w27LGESKvEJzmeqXhURW6Q/sIAXTAzZFLpAIpnm\n1USdfSby+BEFvdCsc1DTFc8SiESQL5XDFJJTi5Zpm5zLYQHgo0KAF/4eVxdu\n4eGy56clIVX6ro+Vm0beifEFRtpBeppkflXx0cENiwVK0pGEHu3+19ob62/8\nOiNyguYeNUXwqSEcu4SKgmUX9CbyD9/ddnTSE+IYZMEGJgE8LvLd86xGEg5r\nApUO9vF24b+IHdMxoALYf07oQmLT6zfeEwr+a/JojuNtdNsZP8sfkP5vsbub\nVmib\r\n=5/0r\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"797523069ff22e6ad20111ab09358587fe374508","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-08-29T02:12:16.570Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.64","@balena/jellyfish-metrics":"^1.0.333","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.7","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.9.9","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.26_1630203270137_0.9843132267410535","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.27-renovate-internal-patch-e15b6d0a90bba5c16298c9a36d8a486e418bd2d2":{"name":"@balena/jellyfish-core","version":"5.1.27-renovate-internal-patch-e15b6d0a90bba5c16298c9a36d8a486e418bd2d2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.27-renovate-internal-patch-e15b6d0a90bba5c16298c9a36d8a486e418bd2d2","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9e5e4753fac1fb3839659e5f4edfb184fbfe181b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.27-renovate-internal-patch-e15b6d0a90bba5c16298c9a36d8a486e418bd2d2.tgz","fileCount":202,"integrity":"sha512-xAq9JDVxNypAYhvWVX0HoB5hg41/JWlFeFEfshmGMaVcrq2ypp+JwI7s4wowEHYHFAguQfo0fvxhaFNNk8FnsA==","signatures":[{"sig":"MEYCIQD0z3iNOpCjlmbhbWXAzgJ10L3kF1lUUYTfhEhdV7C2BQIhAJ8cRamroDzLQk2HY5FEsn4DAhAZ+ofsDp6PlvA0rKmz","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":744195,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhKz61CRA9TVsSAnZWagAARJAP/AnXhv+r3+TkvRmeOX6o\nUzK9izqUQ0Rdomn1tQ+v9NSThc6YjoUUFlOqYfKFZSVuXXzFhw8fJEPapfT+\n6pvuFQcVq2oGOABmP40IKsGxL5xElqVlrv+qWS1SOhWDtMtdYfF4GeN+QeDz\nF97Fzy2Nmrv07EK2BIWuOkoG0qAb5lNQ3ZMRuL5ixYbqZcuIA0KutZQeIVgh\nBmu15f5U33PUWqyQnnDp5VF/jK/qc4WY0BEmDJitJyIy4hOQG97ZTC+xUnyX\ni0Cfk+hZUjeZG08YcfiJESA1hY4kfKwESNNHAwzt+idMTO9ThhkhmV+9dIbN\n/6GSkN8hEqhlM8N/E1UwFk1eUXDDLERfjpEBBs8Ut5msVOgyna+d1Ce599XA\nVOGXlJpXLYjIzDKsf1b3AGPR6LVRWR1gmYqqleghLEKGHj5UbhqCt/6VDkcR\n7hObcq1LxzJR7GcPijSW8yUQH1SFKudvfPiRXORGrdNoW9z/VTEzLcoPEwuk\nJTtC0+gfhJ4Xlni6GCPSV6wLxJeDGsl7FYggxDsvTgxDVJN45mh8BXf7on9F\n3fG3++y49Y4cXvTXcrcnhbBlizWB0ZuqifE4T5Pi3Q9huIIXFTQh5dcu0bIF\n0fu6m7P3h0nwvxsqam5I9dbKo0sTx+bNj4XyqqVl1urc6Da633XnLxvnl1Ur\n62j+\r\n=S1DN\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e15b6d0a90bba5c16298c9a36d8a486e418bd2d2","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-08-29T07:57:07.331Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.64","@balena/jellyfish-metrics":"^1.0.336","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.7","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.9.9","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.27-renovate-internal-patch-e15b6d0a90bba5c16298c9a36d8a486e418bd2d2_1630224052979_0.7853295000216374","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.27":{"name":"@balena/jellyfish-core","version":"5.1.27","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.27","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2eb7e910b630475f0c6844f30adb79238b398a78","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.27.tgz","fileCount":202,"integrity":"sha512-Xbr3+m3xDqFBs7iK4r1wSIVQIjOXYxLnI6qCdvJ/3WM33vBaK0QlvJZvYuV77j78r0ilJWfUAH/MYWNHrLBWdA==","signatures":[{"sig":"MEUCIQDr7SmyAjMKXIc3uqjjwiuEvhCIfw/C9obnJtmkADMfHAIgZZmy5WSY+jsA3XdZzUgDraoIeSWgbDh7WOF8mMbfoY0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":744130,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhK0B5CRA9TVsSAnZWagAA0eAP/jQ74XsfQDLHL8GOLz8A\nsM9IHrGnaBg/y7JP675ptfAtDmP5vgcOM1/T0dzbLhCbHNzvel5qW4diMadC\np3+Q50bxj946BKl1cxUAGcKgrjYoUi1fLbYtEP7FF2kG9QJp7H7m+DxEDLHU\nxOFGLSraCCRVhxTNkcqinKKjKR+YlWnBLRDjSDalSrqYtk6YSEpOVPVnWyR2\nw9WSEPNrQ0BBqNVHQuboO5vRFgMjm7BBgM3Y0bZ37J6eb+rH+oOGffym4thg\nYCERfl+KbzOdSldDr2XbNGQn7xnqd+d/kevSZYiMFUbHJVlBZoWmBrTz1kPo\n2Dz+X5fMNTWP3JXCTsxT+khxJokvBJmeKj8PSwHhve89lgvpfdgcWD5LxRWu\nROBhIYZBaG6M2hKFKOkkcIE9bYEW6NVsMPhAg1Dlx+VJeswGNy2q1ztOugdM\nJP/UG9wNL/bumS8uOfBgEpP6Wv3kBuQvGTAIOkfnChJUTg3T1n9QJwaHNBW2\n6yXNlK3P4fQbhgieWvOSG9ta4P0i7K8pRHmX7iMpTyzs+uoHF6auqIasndkR\nOH5sPgkN4w8vT/DA8rGo4+n5RbVkU51+DsG+oEjtR95aT4MSkfGHiczwBXyj\nddN3PxBrtGnaKTCpoFQLRGhH0GNHt0vTNW+tcoaTAUDS+/l6eAj9TyhaqII9\nmk4P\r\n=IiSp\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"348f174f2996073e907cb7ab31cf36bbc86ce1bf","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-08-29T08:06:41.537Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.64","@balena/jellyfish-metrics":"^1.0.336","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.7","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.9.9","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.27_1630224505410_0.5810817425981472","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.2.0-add-link-indexes-626d389592d11a0b3b16be7023f9b3228c5f3e26":{"name":"@balena/jellyfish-core","version":"5.2.0-add-link-indexes-626d389592d11a0b3b16be7023f9b3228c5f3e26","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.2.0-add-link-indexes-626d389592d11a0b3b16be7023f9b3228c5f3e26","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3223655fb9a53dafb8fac12c7678956f724237e5","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.2.0-add-link-indexes-626d389592d11a0b3b16be7023f9b3228c5f3e26.tgz","fileCount":202,"integrity":"sha512-Ni0I6vXIc+KJgdmkcLK6Abo/docfRTpymiRpMR1wpP4YqXv80Ixx9YwOPdI952yUy1iejO48cjsk1F+egsq3nw==","signatures":[{"sig":"MEQCIEYqA20ZQLHlc01RbXnXSf3R1bTumOyb7Y3YDMh5JEYKAiAP4/ENDskvRJpCZZmngAG0lgFxNmCAXAqmSFYdlSQGxA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":744434,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhK0ChCRA9TVsSAnZWagAAKt0P+wRosAHHY6gJSsNd3AhJ\nF17lYUiB2uAav2+QEvecR3avlynVaCNzX+6ltLhJzNZ7Jevxjgx+IJSsLj3q\nbQtstgpL+R0mNfRBa57hwoYfBBXBmQMdd7n5zrjFP+R0vMq/ZSIU/zvtvNP3\nO4RAk0mnPULYo6HCoeMmPVDqdxZOVHPYQ+tKA6qxvk7dMG8KGE6p/RVtqWtp\nXITUbHQgK9hcNw1F5L69RhIKLz69BfIHsHNGzU4jyJJD16RGigO0XMgHVLwB\nJht7Vdp+p036S2qKhuXYyf8szvfIBtCp3+RcOuaCQbB0rnnc5kuq9zijdGZA\nKJQhbc2kB/qP5MvKyyfxjLmCv3wvKtyZAkkeWsL4Kw455CpNDiJuHnm2F8G4\nrln8AIbOUUUgSrqOkMbQqtwjYx4XepY4L48aeJA5MWvFa3ZjfSADCBpHt30s\nYuda7lpctLus6TDNxdFuie8PbSMxh/OX01nyQjQGc4opS/JUkoKt4LTKo5Zm\n87jDyUQHG2GrPyGnZENuj/b9jOPAb0FCKJ64Pjew8durKL5y9VBw0I4v7yXQ\nlfy8R7DouvIzhkta+sYhU03foX2v6iQ4ZXAtVqMcgZiYmSswdnKfA6hqSaFA\nbCA1Ec5Uh6wN+1uv4yTyGiThBW/Uy/WvbiEf2SumIn/eWaK3ecodB/QVg0xK\nctVP\r\n=SOw5\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"626d389592d11a0b3b16be7023f9b3228c5f3e26","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-08-29T08:05:21.654Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.64","@balena/jellyfish-metrics":"^1.0.333","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.7","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.9.9","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.2.0-add-link-indexes-626d389592d11a0b3b16be7023f9b3228c5f3e26_1630224544917_0.787130838587071","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.1.27-bump-jest-6098c74a2fdcf09b92ca3808fcdd6f3d03c4a069":{"name":"@balena/jellyfish-core","version":"5.1.27-bump-jest-6098c74a2fdcf09b92ca3808fcdd6f3d03c4a069","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.1.27-bump-jest-6098c74a2fdcf09b92ca3808fcdd6f3d03c4a069","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"401635f864a3158c8915d693494a1be8ab8f258a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.1.27-bump-jest-6098c74a2fdcf09b92ca3808fcdd6f3d03c4a069.tgz","fileCount":202,"integrity":"sha512-AiINbanxGxdPPGMcsaiG/j2f6R2ab+scchj/hW8iSOhGgltB+hDE5UyFPBEAh2FBE4yB3dUEE9KI+7oYJ2Slnw==","signatures":[{"sig":"MEUCIHR65q0Ybw1ssShcaGWg2xny3prE10qKOvKYC7PX3SthAiEAz+nmqhsXsKj7E74xPd5MkESTybYoZ/UoiiR0J5jxJIw=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":744220,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhK0IuCRA9TVsSAnZWagAAfj8P/26M5MGsFCdyrbnmwYGL\n9BmCFMVd28GkeXwxD+Sf8Q0SHqtZE9+K7uGwtXpqXzsvUrx+uM17rZY9RqUn\nWtCFJQKCMONwHrboNMXlxz+OQh+20kQfqE0xHQfK3qvYJJkW+9288KuPSwoP\n1qXRozcbaOxJ9kAeGikolkPau5egSpjNZ8DCD9BWOJWsKNskdXNYQBVsW7NA\nvTYCW/Sww8XWYlELPg1nKjbEb2104ScKtETHQpcTu3mhNlnG7NgBruaMX7m8\n03jXOYhM9W/JsAoWIBlDCLII+tRUaXNZFoqxtG6jTqm2RVUzLhzqtxG50plN\nmDoFk0pqUaPsgWb8eo2SkhTQEARtxJjIknCPxWsOFMyPALNmixodUiFDH1zV\nkjbvB6rRCI33rpEigcqsE6Ak5o36IvpFKm7qrRXVL3mIXiYaHnts9xAs1uwb\nyKAXgg+kSH7598TWPVUb3SJm27wlu+D4+cwQo1NI47/JdgD1QU6brIpGKs5l\nxe/6SEtw43eU4XXwfWWeRqUbstPhJsicmWa4/E9T5KzwEkY21lZm/GJFuAV4\ngIffU1tNjpNgYE7hNT26BW6EN51M1deZ9gxmOLn0v85sQtpI4ksw+YzUUs/7\nLm77N0ZDYcSVu3siV4LK8Hak1qCTplO+fIqz4djCa9SExy5kVRD0UJW9IEpr\nM4TP\r\n=xC7G\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6098c74a2fdcf09b92ca3808fcdd6f3d03c4a069","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-08-29T08:12:41.308Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.64","@balena/jellyfish-metrics":"^1.0.336","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.1.0","deplint":"^1.1.3","ts-jest":"^27.0.5","typedoc":"^0.21.7","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^27.0.1","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.9.9","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.1.27-bump-jest-6098c74a2fdcf09b92ca3808fcdd6f3d03c4a069_1630224942647_0.8045279145284503","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.2.0-add-link-indexes-8b04eabe901a10773443a4a10dc3c1088e780cd8":{"name":"@balena/jellyfish-core","version":"5.2.0-add-link-indexes-8b04eabe901a10773443a4a10dc3c1088e780cd8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.2.0-add-link-indexes-8b04eabe901a10773443a4a10dc3c1088e780cd8","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8c828b62a06f1fba229d1df89d8f9e3cc2f9b10a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.2.0-add-link-indexes-8b04eabe901a10773443a4a10dc3c1088e780cd8.tgz","fileCount":202,"integrity":"sha512-zSi4c0K60WDqWgWGsvGUzJK0cIeXH9rhgYXDaL0PRHQpHDTr3i+xjxGsvECNAeosX6jTN1qOPMm6A9Td/jndNw==","signatures":[{"sig":"MEYCIQD2ab9xZUcjX1uLr9wSWwB48qzgblkYaC5B2CcRjdzAywIhAJj+Gi8SYFlkWPs+q2iiKMeh9HwuxhkVBSwz1HKzscfc","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":744539,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhK0muCRA9TVsSAnZWagAAAiUQAIVwQ4krxJC0Z148qC+o\n7X3M2lcVRilIJrh9M+CMG5u6nf7L2wZHPwyp0PTW1ImLqid24tnlh8s6cV9z\nqZvvatTG1Qtu/naya5IVp/i8zHUhKEjaFzycII7WnxOjkAhbLEzin7vvA2M5\nmzquGXZEVDVWJBmhTyc0uixEzII/Q0HRE/aguGSdUzSDlmwLwCWQX9zDpQPJ\nI35/9frQaVWiqXr1L/1XSa/AMOPAbrSr09lBttnOJ4xnFwLfF1PFcXZFt7Tx\nBAXlS3Hj671RQNj/2jOk/IGfbbQTPeazphGpEQ3wySnUoNdL/q5SJPUNg8tq\nu5BMwYm0L+YAU0YNnfhaBdcVSmbiyAxCzP4aAw/IqAwL9aFVM1rF2Qjl4zHs\nDA1GZ1li6Tk7CREDSapWviqLtC0pHoT7kD+WcLqIR23Z0x4q/ASdty7F0C77\nh3ZUyJHhPAvfvllagg3DeuMJs/e1ViWS7bG3/aWT3QauI3LRfV5PwPLoYSac\nvvPi4uNJdgQbtBltF9/VAoVlz+nr+7mJq6UTM86OOM04MIRw4asqSsSkL2aw\npALY57Vu+kwUM6XWjLKVmYoPDRyLmR6T1xoV3WSFzTz+vY/z2nMpqIE09lKP\no4QqVADNO+XL8YrOVwjl3k6djgns/fwR+vqZy9AI6iePwrxHpOgaQ2HdAUhG\nLklK\r\n=Wf90\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8b04eabe901a10773443a4a10dc3c1088e780cd8","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-08-29T08:44:41.656Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.64","@balena/jellyfish-metrics":"^1.0.336","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.7","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.9.9","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.2.0-add-link-indexes-8b04eabe901a10773443a4a10dc3c1088e780cd8_1630226862367_0.9243969802185839","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.2.0":{"name":"@balena/jellyfish-core","version":"5.2.0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.2.0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"451425e0d02d79d74fea9b480e6a492b790478bd","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.2.0.tgz","fileCount":202,"integrity":"sha512-1NwUVFIFTASAeHJUaTJJARsY7ssvWQTQMrkOaEtW497e/HBiw+Pp9THgff21tfu/W3c/9bf7H55l7H4GSaNUFw==","signatures":[{"sig":"MEQCIFGZd2xFqwRH+H3gqpOYUeaQrjWzJNvWILTawXLJdENYAiAhbhCuXY9L0UehF0guyXKpxnVJMdl3WM4qjCCU0/nC9A==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":744481,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhK0urCRA9TVsSAnZWagAArQwP/24Xzson4/oFHVqXE1UC\nenKgB5tZnZKouJzR55QSDxd68DIOu4bD+V+8+j6liykpRpdbHGTnxg8p4EvD\nj9VxKcBAjCsH7cI1GvASEr7PW/cIx9pEfTYq4F++mFiaZ2ngPmqFSMAlV5Ym\noq0Pyv0Q4eaLJJ6TSj51aQn+9whGfCKi7C5EiXm6/UgX6Zy11RnnU6w+VD4x\nFQVk1Gknv6OB1+PkeYrAY7fSD3pLzH5FKBlsCvFU98zMY8J7ePlGrOyOHzuq\nOTxM5cUKcy0d+F2fOPHBOC4X5BvUJPGe9vYzD5lzp7ibYs6zaGWKjNVzWhJV\nGyAQXNeovW5OYyGfnagumCY34aFZ+Z9KzhVR+3JUI+NIHtJAzNX/b1+vpF/S\njCxlBmn/4VW9667WuYmNSEGE1v//62fqUBLiWzGJc/4ft0enos9XwFy1QRrn\nIN0y4thkoYIO0ZHaH2crdR27XPGCyU7wBndiKS3F+0UF1sUUAqYS2heNwvUl\nnuxddMnwju26xsTt4FiOyWH7fv5dReMwo8t7crbPpn7Vk2S3CfL+MLXxVNcp\npFQOMUNZI8/r9v8xtsg5vsVue4gE94w+U3l05Xqucvtw61QqQj70gEeIvldi\n9+eVJwG/lU08uFZlS/RfmHpt4pby0QWUL024JYo0YcyGqIall3z5wn5NBCel\nZmh9\r\n=G5SU\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f1dfbe876e5592b89dc9c7e68d13d38bc9fe2ced","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-08-29T08:54:22.010Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.64","@balena/jellyfish-metrics":"^1.0.336","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.7","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.9.9","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.2.0_1630227371128_0.24493322111716997","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.3.0-add-scheduled-action-75fdf9c69b43d4354ca37e2f9c58cee917a15f93":{"name":"@balena/jellyfish-core","version":"5.3.0-add-scheduled-action-75fdf9c69b43d4354ca37e2f9c58cee917a15f93","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.3.0-add-scheduled-action-75fdf9c69b43d4354ca37e2f9c58cee917a15f93","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9ccad4f0018a13ab06ab293e4327ec600ce88d7f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.3.0-add-scheduled-action-75fdf9c69b43d4354ca37e2f9c58cee917a15f93.tgz","fileCount":205,"integrity":"sha512-zDP1Jy7COTDVLeHIkkbfQEeDMJ5l2yaCxCBiSmQQXq0qtb5eU4iNJgeE8hAdcHeUc0RjKVKomfG/jRVp1eYzIw==","signatures":[{"sig":"MEUCIQDcY7GnYNG6F3aDikAJEE7eg0Hu1GUVkvCDfFRCDgGFxwIgMvZCmIhiaW91XqegezMMjUE1JG6fdkoRMn4npN8z5Zo=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":750707,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhLX0qCRA9TVsSAnZWagAAOZsP+gMtnJhaIQfoNYe6Od5s\ncLkQ9FOhMHZGOCr7fOuBALNVH6yVaocvaWI1Ni6p1zCb5dseMurM2ZB2UzuQ\nVU1asdmOO112irHZuXMieWFgu/PcOkYXyHrb35O5iVngp0RYTz/BN/0fYmUA\nTGTPxbGgUzYyPdKqVg3+uVE4SwBCh7d0liocDYkn3pZiWG7NuULYgdB/yNJJ\nsjfLGlcUrLvf58fqkDfoSENY43CB91uT+Toz8eNElpVaTfg9N0GPE5bnkpf0\nJLJ7hr8lMpwciYOBejDhVmnQ8G3QcMvrKvEag7lZ3oOzJ0Tna0FVU9xgmvkf\nIcCzY2Au0oYam4JLxW3/3vnCHbyTnvKRxq7JF1JRuakvebAeSpW7j+UvdAdd\nPY2lLh/TfFz1oYYV3sgYqZMxPlBEOQu5DcXOs84o4FWcQtSUUz1kspACoeKF\nY8s27JtspZrem/Jxb+C/OyWjpUXlx/acbuRYd+tmBDnTMxQhria9VlKNB5z2\nvH133RNpfXWJR1IqOnTSKttuq/2N+V4j39PCLH8M+JauxltwXGMee8uNYqa2\nnM/Lnc5j0r7jykCeD+rarStGrcvzM9XzFhx+BiiUrMd/LDgz8CQGijI98edF\ne3fUu3CFR8dO1FYZk1fq0ymhdHYqplukWZai91CVZqXrxv5adzVIoLXtjtaZ\nNR6B\r\n=bjXr\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"75fdf9c69b43d4354ca37e2f9c58cee917a15f93","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-08-31T00:48:31.763Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.64","@balena/jellyfish-metrics":"^1.0.336","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.7","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.9.9","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.3.0-add-scheduled-action-75fdf9c69b43d4354ca37e2f9c58cee917a15f93_1630371114144_0.6475914240168645","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.3.0-links2-table-1ff1b1d9ca1e6d71b6d381b8351754cc47b56659":{"name":"@balena/jellyfish-core","version":"5.3.0-links2-table-1ff1b1d9ca1e6d71b6d381b8351754cc47b56659","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.3.0-links2-table-1ff1b1d9ca1e6d71b6d381b8351754cc47b56659","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9e3658f433a90c13f0184bf7a152ff015b7f22ab","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.3.0-links2-table-1ff1b1d9ca1e6d71b6d381b8351754cc47b56659.tgz","fileCount":202,"integrity":"sha512-np6qlYUMl2XVYDKt523y6xxB+hvsjNEWIa7p7IwXKkt5yDxCQQGp+AwRNR2hLVV6n813wiYRqp5dV0FNTtIcJQ==","signatures":[{"sig":"MEYCIQDScXNq67Bxwk03m0TKJG+Tu7hOpCN+YQvRfVHOvTWiUAIhAJgPx8E2A5aP25+kc4j/BzCTuxkJnSErWwMbShTrOyqe","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":748231,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhLh+mCRA9TVsSAnZWagAAj3UP/3P8GoIsIfAnU+NzxJcX\nRBZGIp4/msx7RC6nODaq4Gd9ZFD1KKrP83VyWeD7K+huhdAtFwgIgQ1SqZpA\nAxJn9oDRdPHmRGzX17DQpYOWD9tyJkYJJgvIBbtf1azZnAyH6QEe87oBlZ2d\n6SloHysrKziv0iNN2/luIpbi9zf7jXXgvyBy27l3z4zovEP4CQAUFzmeuJMn\nPqwcHSf6sGdiqcuSuyFVXH1hJ0WkMGMibDqWi51LxQF9sQzILPMasp0luRbU\nBHKPqPPNyC6J2B8lBdcvLEilAddyjM+1HdNyka50kPF8KgPMuZIeKFNV3ZHy\nNVf1GkzfmYJH2cmHkmm33mj4oP6VP4FJD99zpEmbbyMKMrZWTR7scTz9W5WK\nmTAQZMrCeUtPaZ6ozmIlrUXFaqmna+LUpRuZrhfFW+VQIMgfPxnxXgVpgGhz\nwOf/cLlg1XaKnLVecKdBCrgrb9c86z2gT+RKfsz4p179DaXYy29RSQCqhQJD\nNavoDR6hEwQETxQ1kcU6FO3TkmKu4453o5s3fOcLXzAKOFh0n8aPMkylxToc\nloFhG81BLXy8rbPPcTHuupbu3czAnnbe15/xGXq8fdrjJqcDqQcwRTorxK1G\nz17usbRh88rG5CWZcdbI2hl1x6dfK/C4A5DL2wEg6zWgcSrSDd98PIclvdw1\nWuEF\r\n=45z2\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"1ff1b1d9ca1e6d71b6d381b8351754cc47b56659","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-08-31T12:21:51.203Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.64","@balena/jellyfish-metrics":"^1.0.336","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.7","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.9.9","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.3.0-links2-table-1ff1b1d9ca1e6d71b6d381b8351754cc47b56659_1630412710466_0.31706213293891805","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.3.0":{"name":"@balena/jellyfish-core","version":"5.3.0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.3.0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e8f9332230b8732b3af247d4ed1b6a5eda33958f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.3.0.tgz","fileCount":202,"integrity":"sha512-Pcz9s7IrIWSDBEtfjkuLVvX/iNg5t14MQqPsA688oB5GMMbmhB5SJpEOqyN6HdT9f1e/KRPPbvXOstibPqZ8Ew==","signatures":[{"sig":"MEUCIQDhtiQnMPTBGhon1qzhr4S/qTroegVVINgOcep6JUB15wIgWpriA43DbSw3g8ZLQGrofZpeLmJxyq6iKF+vMIfrOMQ=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":748177,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhLk9+CRA9TVsSAnZWagAAq2AP/3gkzqguwEdln0JiAMQv\nE32wuonoZPFIMDh5g2M/K9MsY7/dEAYJ2awuJ3BOKtGIjfy15guew7U6YErB\nvr30KFkGqjlPRuDNB6qemN66VDpaPE9wuQpjnN9+wrZbpGGpXnHjLWFGP+Jy\n9t88hsR91tWhZr30yfBR5igaxbVLqLOgkXnVue6poko1mq4GQh+psyHTx5IG\n3+csSIEqRf/v5l9Wwkon5SxRqby93MiFoAKz3vrORX0hiH8FQ7VP8RVe4BHp\nTGDddjNuvNpJde7HwN29cyzkOx+KVr8Ei19rdMLYC1aDZA534NN4hL+GxZ2l\nlz8T1/GNtu7F/80r0Zy8YtqakXWc/qrgcqJYyfr9hZKjGiUSBI2nmgT74DPO\nS+YiumDEnp23bfpPWuL90ElgVU3qSJThIHarBNvCe+pVjDo/x5Eb5tqydK+e\nWNzyJ0Mm43ZOUPfIk5fJAggeJjVF5/9rPt0TmxnxVOnXVC+JFL3iuxMRHeUv\nkyusigoRGncDhVs9ww0pmsHq+dWMeguk+/o2wLFq+3xpAzHIUeDHu8FLp/Zr\nWv3T0IH5XtXP1i0XigpfLC02bvXyuOLCPe8UZJ/HUnTX+5yQaP8zFj1myTCb\nuOHO8hva0hLkC7O65G/ndEvW8C4s+6gXXynFmfkOxz6tA3MNv5F5WuYqMwpe\nuR4z\r\n=WcUs\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4a1257f29c510c3c574009142ab54707f926c790","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-08-31T15:47:13.121Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.64","@balena/jellyfish-metrics":"^1.0.336","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.7","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.9.9","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.3.0_1630424958371_0.4912325571867078","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.3.1-renovate-major-external-major-ac61b1b728708193e62eb83bc2d5d2b5835aea9d":{"name":"@balena/jellyfish-core","version":"5.3.1-renovate-major-external-major-ac61b1b728708193e62eb83bc2d5d2b5835aea9d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.3.1-renovate-major-external-major-ac61b1b728708193e62eb83bc2d5d2b5835aea9d","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e42def877f42fe0dce1fc28dc00a32eb65d0bb94","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.3.1-renovate-major-external-major-ac61b1b728708193e62eb83bc2d5d2b5835aea9d.tgz","fileCount":202,"integrity":"sha512-iriqO9x0T3TchJOHYLvziABlThtB8urSpsgIwxazekrmdjDbfWL/ADgwdNICV676LlqZK2vu8kLmh3/xkBysgg==","signatures":[{"sig":"MEUCICxSikPBlG/sNhMT2NA16+3mBqy0LP3Jl+noUN0HDAVjAiEArOewUaoJprPyk8QbnccS05cbKQs6QmzEPSdViMAf0CY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":748317,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhLlEXCRA9TVsSAnZWagAAWHwP/3UGw25tivpTNOIk88LD\nR0vbD+NCgDMEIzyMRF8wGsLFHAORFoaJtPHdVeHgjM0wr22S+35Qifxdc1qs\nFiUqJsXMMUGP/bsrIaNDomuDVsyPnHf7wnuQ4I1pbizcs2MuTzym29hXK2Az\nnZZwnmUh4bZkgI4IxxbCYwuwhGTFMN8ImmSp7/4GA90jFq2UxCSB8Rdz2YOU\nfKTmx6AggTJ8WRjgfh0smJ/natXkIzZQtgsdxWpTmRASfaFK1oBIr/ymTEfz\nKn+uU6zqRcOUrsXL0zjf/9E4lKcVe2S1T6RQso+uwB95Fh1I8uDuwVeObUgc\nlJCAAtwW1RZGyEkliGzp1Vk8FDlqr9cNhUvspdocH1FcLVSUi3VdOjPn5GwN\nYcYQOAXJjmE9w2Hjg46HYaWbQZUsB7x8nQcgj/XNB9tNOaeP0b9IJz3PxGnE\n5S/N+dNZm0Dx+G3h6byUa4pYJsMhnWHq35CR6EAUCVm+6qIuA4S4wiypaDsC\nUkMXCU9ZkDzF8I8hJTa3RQys/HR3++ofa/1s4GqLbT2/YGRUfdeVeI65gnde\nL1Ej5B8CfI2QCW9PHtkNDQNeOgStyxatM/DQlG8ZrBuDcWhF1S+LtZeQg2IX\n6KI+fkRZrnnVzRnDPtUsQ0+Nxe85kBr9D6Du6+eAD1eOetFQ05+XfPvIKqHc\n/OBT\r\n=SpG7\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ac61b1b728708193e62eb83bc2d5d2b5835aea9d","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker-compose -f docker-compose.test.yml -f docker-compose.yml up --build --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-08-31T15:53:33.249Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.64","@balena/jellyfish-metrics":"^1.0.336","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.1.0","deplint":"^1.1.3","ts-jest":"^27.0.5","typedoc":"^0.21.7","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^27.0.1","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.9.9","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.3.1-renovate-major-external-major-ac61b1b728708193e62eb83bc2d5d2b5835aea9d_1630425367329_0.5812490461799136","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.3.1-repo-sync-secrets-default-f4056fbb04c50da767b9b7602249992590428c52":{"name":"@balena/jellyfish-core","version":"5.3.1-repo-sync-secrets-default-f4056fbb04c50da767b9b7602249992590428c52","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.3.1-repo-sync-secrets-default-f4056fbb04c50da767b9b7602249992590428c52","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"dddd96a8b470e7476830d38d348781b032fad59b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.3.1-repo-sync-secrets-default-f4056fbb04c50da767b9b7602249992590428c52.tgz","fileCount":202,"integrity":"sha512-LBax2ZYJVSb83WPtN3NVnO3wecLPpcNZkn+pL4y9mGYy0yv2vwPGwpdBKeQxejUQBKsR/SrXyjvBvWsyJ2c0DQ==","signatures":[{"sig":"MEYCIQCTR8rZfyr4p0fVGBwd/iBPuqbgfNk6pJLUuwlq/K056wIhAKpN5mxSGraMhMrnGtDicaa+llFwY8bZgRvrYuKZnc7y","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":748483,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhLs96CRA9TVsSAnZWagAA8kcP+wYbS63x5oaItOF/765h\nL+nH8LnYUZJ5xvZ5rK9tJhuX4CfNSs3pdETVaN8WZiyKQebiq0aF8gUbRI27\nWs5r2hca1lV+d9fJESg7Cur9OUiB0o4azv0ImaqfzRgLF6yfDW2bXkRrcsaM\nf4yzGiKdhaMUpaCJjPY+21buQjZD7PD40LaoEzK0U6b9Z+Wtzx7KsrLyVCjz\nALf/g1m4X1doJaMQYbnUFVs7mc/s8C7o16ue+J2H1gf8wjgopxXs/05eDy/v\nJjwlHJi+O+BofPY6d6mRfA37eGj9CfS+yGgo6hR9wj5+qtGI/s4SjZhZIbe2\nMg+cgzV9maJSv68sWV0YK/uGoiLAxfqwSFvhU1YJ9DLlD5A2Y4w210JVXOcy\nC+WRvcRqWKrACbHBYNYARakTQYSBEEpVAGhsuH6swv5XlrpyMnLPF1R+zhbc\nzfq2xT0yTrkIS33n74l3V6xmkdFT/8sw2NPvMofAyM55LcjyUCZdijsi8+oW\nnAQcbbNg+9p8ahth2pecSKXc2F9dk3tL508E5dDKBN+irRFcSbETLrH4Y3n9\ncGF0/qwKOSqpnFzLTiQM+5KYt8YS4zwJ68br2+iQKdya7bmOR2SxP0pVaRFk\n9/Q9s/ds41O/JfaUgpjTZXZfjcdBW+sZnZtqc6kgIGN9cMXxk6Zw1lk9qzLH\n78bU\r\n=SGm9\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f4056fbb04c50da767b9b7602249992590428c52","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-01T00:52:21.961Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.64","@balena/jellyfish-metrics":"^1.0.336","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.7","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.9.9","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.3.1-repo-sync-secrets-default-f4056fbb04c50da767b9b7602249992590428c52_1630457722448_0.5946918069960712","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.3.1":{"name":"@balena/jellyfish-core","version":"5.3.1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.3.1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"368588c6115e234fedacc8e72d0d08bd53a6b329","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.3.1.tgz","fileCount":202,"integrity":"sha512-hIDA7roTuOovHonZOgquUvODQbtWrlf/P/HdgaYRhKx1FjkhhG1hW5j04vtsnvd9u8t3OiMn3uM3s8TqUukgVQ==","signatures":[{"sig":"MEUCIA++Xg2xlE+8murussr8wPrKycHFPpHi4Tjj6FvVDylfAiEA7Y5/V8bqXeiqFakQ+R0tOOTYJHTkDFJlafJvaIierho=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":748416,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhLtFYCRA9TVsSAnZWagAAv0gP/jDP2Fl9Irx8S1I2Bz9C\n8QDAkXylcY9RMtJeoVozHNadC+vwbWVk7aw6628EjwKr+7p796SybnEzU2DB\nh0Tu1tBh3vykfcc9mgYjaoJoX9R0XkO3guvH3k6WiEVW5r+Vx3pAM37P56DB\naqYvqMfWh01eu2ET7IvteeVn7vF60RmZGUnxep66HNcnW1BV+grkCRL2qGxA\nMNonN+8XCNdQJ0wme5M/IasvTARWC4AQxlMd/7ie/IGldpzv97kr4D2f6pIV\n2ljPNwhf9kokIa23qSdVLyiY+rjyRss+cK5YASLXPRLmVvtatVV6RBl9jQxH\nDZkEPIRD1CdTfYoKYBj8j8dXw0sPxRYbfu6jvpcT+K/tK6XKjKZU3RBJaaEj\nBkmZqtFVNONkqW+A9eLzaTR3yLaRgCJnRuFMVt96rhsyU7tuwLZv9QzxdYWk\nLWYvqxtyXv7tiNTW/aGtNy0RxoVl6yrE26PxM8Fdzl5alIpzmKKz9f/OWRBS\nK5326tJLCyYcdiNetyxE0T5Zn6Jf9RkwBkS9wcX9SA9GEukaomHQm53X5WiL\nRUccirM83KOq6jjhJ6T3T6Yie+X6Sph+EDHqRwQWktClpqL4/q14/Cl9SwEC\nS87WzmOvm0f5lZ8948+XYg8g/5OHbxF157MLIXnBEFQmFHbbgCT6tpbD5k8o\nejdg\r\n=iFr3\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d3227908aa7bc0fc8229f23d0b422c5a84487bc0","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-01T01:01:17.100Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.64","@balena/jellyfish-metrics":"^1.0.336","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.7","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.9.9","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.3.1_1630458199840_0.7382178558361958","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.3.2-renovate-internal-patch-c869bb211c9c7b0e7dda1d24d51278fd12307f73":{"name":"@balena/jellyfish-core","version":"5.3.2-renovate-internal-patch-c869bb211c9c7b0e7dda1d24d51278fd12307f73","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.3.2-renovate-internal-patch-c869bb211c9c7b0e7dda1d24d51278fd12307f73","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"713d682a4416894b8696f66db061234fe99f81d5","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.3.2-renovate-internal-patch-c869bb211c9c7b0e7dda1d24d51278fd12307f73.tgz","fileCount":202,"integrity":"sha512-y1Tn4Pdz5VWOm6h41XNVYuyual2wFr4LVGDAbKQ+9P5zzITvgnpbxDVgTDBw4oHPKVSvB1R3RrScsMSovZ2p9w==","signatures":[{"sig":"MEUCIQCsUxscclpP9KsKHhAw5DXP4xQfEWI5Op0Y2vp6y+3tiwIgfURm175ABZn3xoYNI+16y/qQ31+zZZ7TmqytdfmRh3c=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":748551,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhLtKBCRA9TVsSAnZWagAAgq8P/0/0WVpcFX2MMdF8oNOO\ncJFdPd0HzTHaQcDlVCbFCm1i9OFeUNohh4Ac92A61ZGGQLk7/o93P8xtZ6DN\nXt6bQv+g+mUqrkY82Bu4lK9YuOPwoQSYsiMuy6Q4PC+5PqPhUxuU52PXjY2v\nQy+ZM439hA9xs+aMIeRo3jypzoPjsZSxPf/yxaB4isYc7ef/HsbU637QgF+c\nbqOtmKjIndXdYEbIaOR5abtKxBdQ8cK/wjqeRzbnx2nPj/6pukd3TaaR70y/\nsp/f2o6nCXeEPPmrLp/1sUQvd9BqiZw4E2sHUqb5QNs+ivUbmh7iokVXKZpB\nloz2c2GT3Dge0Y15wbxiPCNvumUQsA2M3rIjw2cJyhwo+9y8nTD+redh1H/F\nqXpdr8UrkKB0s/ODAUChKLQXqf8K+Vx6/aZGc6J5CDBhb90R+nTQfRV+JNAN\nbx04VbTzJKIH4qZ4GDsuTF8OID3mbYahbOtsRjnzHrzu+O2ZrMqm2GHqVASM\nT7uHnfOyRGrbR7Dm0homrCSvsGDwKA9W+jiuquxRtzjALeGt4YzfifTTIGyX\n678gBVyHo5EtgVTRoF08lIDcfvKjFiR/jSGk2zdJMJNfLOyjt5PIQxK6vz+7\nsk/TREcrJNxtS/BY+SaHGQcFi9zlssi2dtoNlz7mlTZx1nLWM+RB1KOz5idQ\nO3yy\r\n=Xdwa\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c869bb211c9c7b0e7dda1d24d51278fd12307f73","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-01T01:05:03.085Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.66","@balena/jellyfish-metrics":"^1.0.341","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.7","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.9.9","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.3.2-renovate-internal-patch-c869bb211c9c7b0e7dda1d24d51278fd12307f73_1630458497314_0.326917441796553","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.3.2-renovate-major-external-major-0f064bcd2caf9acd4cd6fbdbf67d0938f133aa24":{"name":"@balena/jellyfish-core","version":"5.3.2-renovate-major-external-major-0f064bcd2caf9acd4cd6fbdbf67d0938f133aa24","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.3.2-renovate-major-external-major-0f064bcd2caf9acd4cd6fbdbf67d0938f133aa24","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"dbcec94ee15b0e4b04a6ff99043a6e9ceb107888","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.3.2-renovate-major-external-major-0f064bcd2caf9acd4cd6fbdbf67d0938f133aa24.tgz","fileCount":202,"integrity":"sha512-LsnOqasvlYPQQfifHiZM0WTS8/10Luz1iZBshoRS/VdIkKOCxepwHCzCWajZn6wlZ1KMeUxQDCurJvO50ZY01g==","signatures":[{"sig":"MEYCIQDyDEiYuia7lixlx7gSXB0f+W0eta3GGpWg2I99CCeboAIhAO57p5XWuaMk0VGKeavbGfn9W0dHWa6e78qKG6AMRjJ6","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":748556,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhLtLNCRA9TVsSAnZWagAADJgP/0+UG24ry7YmLd9Z1fYh\noilD+ZaiMcMrr9cLLQJv3d4ZWAqzuWROb65tJxuLcBwsXWQ9n0Goa55VKPwr\nCMOfZLgcJZXnQpEkLnuuRgWa29lDLlx++W5kVUi8dsh7Bub3fUnU+TnN19QH\nEGUKntevBXO3Xx8WSwXdw39CVGOzaHw7D6p9+ZvJ5jMosV491+hT0OwSxDC7\n8xSnKpQY1yFW1QZgBsKH8NdSgYCAG1il5hzpGfzf2jfb1lN6GeM8BBmdw/Sw\nn7pnQJti8bneUaxoZyNY2Rz9W6fA5XBK9gu1FRZCT4ElRUWOtmA1fowtrRgQ\n1txSsFqj23dhQ34eUuL31Se576YwRM774JznPGz5XLcX/Y7UIrsM8rcMal7C\nUOkbmDLOAHYxOg1epQc9sXD8iG8qJ3TUguxuSD87tI2g6EtGxOd/IT/mgblM\nySdUIQeOU6FL540aHDz0rSbGUbLC60Vhq45BHc6e7vTqsBKmpBhcaZAwY3zv\nySrG0Jmb5M16fXI8ZI0t/8JnNqsS9RWeSci7u9+iulEo2DjDiuld8KJRfIzh\nO29Z1PcESO2FUOeX8ys6IrpqzFYdOSi2AIgzoJxV4bE4OVApxmui/DDQdWNa\n2KuXPR5plQWd1+0ZWq6LdDcUNaAio7INsSCEJn4ZfbM0Q+aULjdcFKwE4dUz\nvch6\r\n=c8ZS\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0f064bcd2caf9acd4cd6fbdbf67d0938f133aa24","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-01T01:06:12.929Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.64","@balena/jellyfish-metrics":"^1.0.336","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.1.0","deplint":"^1.1.3","ts-jest":"^27.0.5","typedoc":"^0.21.7","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^27.0.1","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.9.9","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.3.2-renovate-major-external-major-0f064bcd2caf9acd4cd6fbdbf67d0938f133aa24_1630458573588_0.6423708751537205","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.3.2":{"name":"@balena/jellyfish-core","version":"5.3.2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.3.2","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"04adea46302cfbd3760fe1218e0513af8ec28db2","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.3.2.tgz","fileCount":202,"integrity":"sha512-J/4xGTxEMS4vd2vZiXx/WzUlg66hGTHbyDoAsREQXGcghR9DaVIyjQOObX+RLE51yUMuoBDJfyculYf6Z+oI2A==","signatures":[{"sig":"MEUCIG9uhh6oTvPwcneaZrLWNJHAYtlNFkFsWLzCdhIwWSoeAiEA9ripyYW4PyyCAwniOIfH8sL6hRmGaXdS/OJIaNp9zJ0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":748486,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhLtSiCRA9TVsSAnZWagAA61QP/RXQaSMhxT1VVhxfa1pJ\nMIsTxEI9jjNsul3FCg77Jryi3DgXSMxd8Qp9sfQKEGgiE84wo8XJIcqyAal7\nVnK1lfrWLq0vvi0f+kt6qCIKoBUeF7L7P4JtGbehboTuj4hlIr7Sn6M818a8\nBQMR5JhVRhxCHLQATdxbpzANoQRvmhwR41pq8QiEQxxa7Z3K89yUrDEy1pdq\nTV1suap3/nj/yJiZRg3DR50q0OvdFVgyy34f/4PmhisQwudLUZ6oGsLWo5HK\n5cexQZi0WBgC1opqb3MbsvRZCuXS1Lr9TDrX7DL1WsnFC0MUAgDgJ9ZYNbWz\n3/XhoyIBJ2E6UbWS9FzX1mGJY+0pHvtZNq6r5cyAJw7DqUHpFvxVdBu+4ZdX\nYIDNm7H1YuCzKm+K46PrfCOyLTUoAQW33+y662XJEvn1oBgKYGwjE+ViAr/u\ngXruwzZPrUPAkaGti/VaADQKj8iBdj7oga5j8s0eSExqPy7W7+VpmFcb0pbG\nVB17mSxvloeP0ZC68bsEnDHgur/EqsX0mL/pXD63uz3k10+uTqHAsH35Wk+0\nDWEBDGTG2BF56qSQpdQQWLby48Yyk9ELHiGjA2MNJxFTEoyzncjYoqCHoQ1F\nLWIap2ZgcFdM1eou6N2geRwlDsoAhy8gKDhPr0/RymG5+7z9XdjTZlWVYs0J\nwX8o\r\n=hkt1\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8beb0c88eb26f9b8d6adb15eabce07763c7dbc20","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-01T01:15:22.134Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.66","@balena/jellyfish-metrics":"^1.0.341","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.7","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.9.9","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.3.2_1630459041991_0.8205055944476003","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.3.3-renovate-major-external-major-9f1780b2360e3de2ed81f8b15dd49de2144b17ec":{"name":"@balena/jellyfish-core","version":"5.3.3-renovate-major-external-major-9f1780b2360e3de2ed81f8b15dd49de2144b17ec","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.3.3-renovate-major-external-major-9f1780b2360e3de2ed81f8b15dd49de2144b17ec","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"97c8953d8a6bf5b08d1e1bf97bf0113e17386c62","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.3.3-renovate-major-external-major-9f1780b2360e3de2ed81f8b15dd49de2144b17ec.tgz","fileCount":202,"integrity":"sha512-gWhjOOgLcmckQeZzZOHMtF6voEaaWjnBAL7JfIYrSsT4K/KshnjlLwSIcbk7cUzFK75C/OjgPqagoWolw8wOCQ==","signatures":[{"sig":"MEUCIQDxRakWDYfoPo+B5v+Y+5WmYhNeut8I++c1c/bqn1sfsQIgRlXop8SdQPz2QZEXneCDLkhfkVcL70xVuBL5g1NuE14=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":748626,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhLtVqCRA9TVsSAnZWagAAFCQP/1JnzmGxhnydC7l/Lh1H\nAtIDgExO6a0XuAERVqBdw/PAGvRx26mDaL2Cl2DkaqqBAb2VfdviF6AI6Isc\nasd/IT5bUmLlnu1eAp8/8ptNoRNLpmOtJC/A3HTyqNkhb0oPlz4yfWlbjhoi\nvC6T5lix4xFkhx3XXagDCaVOlvWpEdOylTDNOKJMsp1oe95lm4QPtAiieuB0\n4DyAkkRKzYnF9nIBIhILvuN8dTRQGSpP+ceUVioZXfalmp8w472KiRDrbOsr\nnYdiHwIJ8jNFqOyYyR9W14vJumPyWb/x1RNpaG0BZPC6yB9pgHwupU7vG7Is\n+N2oDksoJCLkx0t7xCJ70PWMPBFaE8KDh1lTCmjpcPKOiVMr1RLr19zjAjzl\nhyjU4Ar9boI1JN8gYYBIONm65JHnD/j18zBw1oKDJtGfeB8PZ3dIge+6SrNa\n6teNMw6dLoMY4odvdqOaEmCaWFqWREzEle/pZCp9TnReE3N3EurmLHWtUxtu\nv/im11Z0ahWiALbpENPmIbg3yDWMjPNu6qNuLD78Hlz8yWINRyq0txN5zEn+\nhhB2JD6kIhshvNooBDxFHQb5Qu7pu7+sS/6DVK5ifv3/4omEr/prEBUtglzU\nW4D9t26j841vLzSpUIBgyq5Tcvh8m9KiZItr0yG5ioRNZMSLSNezLNCimgdq\niLBQ\r\n=Whx/\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"9f1780b2360e3de2ed81f8b15dd49de2144b17ec","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-01T01:18:11.840Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.66","@balena/jellyfish-metrics":"^1.0.341","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.1.0","deplint":"^1.1.3","ts-jest":"^27.0.5","typedoc":"^0.21.7","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^27.0.1","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.9.9","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.3.3-renovate-major-external-major-9f1780b2360e3de2ed81f8b15dd49de2144b17ec_1630459242151_0.1376212232974765","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.4.0-add-scheduled-action-4b12a4956e38e4e7e41d141c561ed976af9625dd":{"name":"@balena/jellyfish-core","version":"5.4.0-add-scheduled-action-4b12a4956e38e4e7e41d141c561ed976af9625dd","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.4.0-add-scheduled-action-4b12a4956e38e4e7e41d141c561ed976af9625dd","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3d84b4320ba2c2d3d6a0f2b290a4f1c1f2c36248","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.4.0-add-scheduled-action-4b12a4956e38e4e7e41d141c561ed976af9625dd.tgz","fileCount":205,"integrity":"sha512-by74I2BzpOtT2nTEcRn6yfkXe4mvPC8YW/ZtzqJBdP0ygnrCugf3hHikJ4NtqWuiGroJ8MUukHR2opjUiQ3qfQ==","signatures":[{"sig":"MEYCIQDSsmdKfwF9HeI0DtKG6dYl4oUXDENycMhGL1CzjxdoqwIhAPjtHgkiKM9l48M4zWbvh7Uh8R2cu2jn8ZKzQoVPEhxe","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":754698,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhLy6TCRA9TVsSAnZWagAADvEQAIUDo85QhQJSQl8cD2iy\nW035p+0Y+85Ldw4geswCFAqsse/gIXAiFuKsoYw+CRnYIbrUbbY2H1LDH/1c\ncIX6t2gRYQLQxe1TuTLZLZhoHzU4U7r6WxRLGLEuwtEGMDPrRLCGfKnuU6gM\nr0ku3Ehrb6xZP/IwaV7ZwSVT4FYo1TV/BoZo4ec4aFfKfCP0uHDU1lNW2S0n\n7UronfY0w6vgMlO3ANf8s3G+/i7XnfP/uUTIwbybw3tknu49N6Zz/eJUBOU8\nwVoX8MdZ2iKBZWiW3dVMx4zAvxVXnAwgzlx9YPr6Gk2MDTaOh2x+bshNOwlo\n85hXVufuLocXNvPKstJypcYXkK3fH+nYFEeQ2MU+Q09pmkGr6WeKU1plMPTv\n87vK4ELnxg+kn2LrjVHyfSbNPLT87HHWZJ/aJRfHj8pdqUwB/hPBTadIyN6E\nApgeFxWAFB4/jclbVOGjIOe7LH8QcDm7M7OM+BHAwRsnZTpL/KIR+nfclaAa\nJswy0aJjYSDc4fU7D2KR7XWpRDs19Opgtzwll4wi1dBIyJ8MAQZxCKMchqd2\n6B/hvqQiqz3bNurJcTl43tkuYLbZCOnp5Celj5DlYb+dKQg/6O3bDX+MaFgS\nUeH1i+I2/LYd483s/OvjwUf9M2E3kBTCAs5mrM/I+amg93be+VmPYYRS9o9t\nm60P\r\n=Sln2\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4b12a4956e38e4e7e41d141c561ed976af9625dd","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-01T07:37:51.978Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.66","@balena/jellyfish-metrics":"^1.0.341","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.7","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.9.9","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.4.0-add-scheduled-action-4b12a4956e38e4e7e41d141c561ed976af9625dd_1630482067285_0.20303136922898601","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.4.0-lucianbuzzo-slugify-15ff9669857c3f72127cb29fc2046a3479bf40cf":{"name":"@balena/jellyfish-core","version":"5.4.0-lucianbuzzo-slugify-15ff9669857c3f72127cb29fc2046a3479bf40cf","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.4.0-lucianbuzzo-slugify-15ff9669857c3f72127cb29fc2046a3479bf40cf","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f32fbf19b0aca25c07db8bbac4e1567d416eeea3","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.4.0-lucianbuzzo-slugify-15ff9669857c3f72127cb29fc2046a3479bf40cf.tgz","fileCount":202,"integrity":"sha512-orRnbTvpoeot9ueo3UnN+eKXy6TDljU0X445Mh+gIQrlAjWkOtR+tGuWrsj+ZXbAgA2G2F4yqiZhZGG8Tmohqg==","signatures":[{"sig":"MEUCIAEqQn4b/JK25eAShBWU6FulZPeBvB9/+NGA9rpjZ69lAiEAj9Xxwn2AquKljDWQUrXE/nXgDRLDCMaC/fvpkbD83Cw=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":750113,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhL1LNCRA9TVsSAnZWagAASecP/3EDc/1YlEtiEuEOqBTl\nmKYcW6IKIaMkO1uJ46+uW8GoL+e8vZbgKnI9IOHtsLxf2GnBzjyfxOHRqRPK\nPMdrmwIbuCjBjSv4E0JSfvXjrnvqEZBFtvR0UZB1WL+vBZGLmsENLutu1eUS\nPfuNcQz4kJyFvxQLintiOO55TuOVTSApJ3mXSOj7dIKpzajT1CIH3JCk4sgv\nyNU7AKuAJZUOZo9VSszIxranjpuhgKP50ewXLVn3HvRGsKR22kQjjNRQ84gF\nETfzz16ytWEcZErlVdAvzC+HjuM+k2g8dRNe1b8mS9huIi+W3uNMDPQP4CPy\nfblyGxeMxRUy5nqBTzFDrvNjwI83dlkp1penPTatGZ6m5ukBN2mQBNvc+T6v\nV2riA+0colqLfIhHLrOhT5jzxSkXCcpJmVqFXWS3RZXt8Hf8ki59LzZF7YnI\neZCElYR5QGfpR83Mx/n7soA4E7GdztKCCheOhNteTen3zqNH+LFLXchJW0Pj\naSyUbez/T7jr951J6nLRUD9Ucy3ap2vZdJbkK1TqlEPDnNZK8/uhAxXc7KUo\nILlvwpG+kjXk+Ucog8NN0mvfoygovglvC20ruVFDqNKq6oY4rbx42L7GOySp\nB2Mw9CucSqwP00BHLPlXlQmWqUehQYXQe2aW8uOXUXru9O1UFTWp0JgC9o9A\nPuDF\r\n=LQPj\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"15ff9669857c3f72127cb29fc2046a3479bf40cf","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-01T10:10:38.707Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@sindresorhus/slugify":"^2.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.66","@balena/jellyfish-metrics":"^1.0.341","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.7","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","jest-esm-transformer":"^1.0.0","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.9.9","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.4.0-lucianbuzzo-slugify-15ff9669857c3f72127cb29fc2046a3479bf40cf_1630491341403_0.45935121646425525","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.4.0-lucianbuzzo-slugify-63624d8f36b0d8be86ecaa216b2b0bb7291dc5fb":{"name":"@balena/jellyfish-core","version":"5.4.0-lucianbuzzo-slugify-63624d8f36b0d8be86ecaa216b2b0bb7291dc5fb","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.4.0-lucianbuzzo-slugify-63624d8f36b0d8be86ecaa216b2b0bb7291dc5fb","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"028883ba674415eb11e8ff17e2344a995ce8063e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.4.0-lucianbuzzo-slugify-63624d8f36b0d8be86ecaa216b2b0bb7291dc5fb.tgz","fileCount":202,"integrity":"sha512-ScdYRqsQRCuk7gb8c+JPVqRnWII8c6p4rdD1nBZCf8sc8tC3smRxUlFuKSDX6hWdksqaV/HkMTH7F7E00Mpnrw==","signatures":[{"sig":"MEUCIC1mf9aldjuqb8pXszwqqG7v1KXs6zBAgJrYdNjadTzVAiEA/P5C9LychLjouVuKky9em18tOXMEQirfUMF6wqbuw2g=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":750142,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhL169CRA9TVsSAnZWagAAS/kP+gIHMYaEDFnnc0KcgJkz\ndAt3JdMot//r4Zu6F2t3UqfHLdBABuWmP1BJt9aHaR7ZKQAKDKqETavemhY+\nNkjJfIpAs0idJvZNaS7Jh2MGozlip1RZ+eST4fEM3TkYybPs4xIEpy7Q3gRG\nz5n3WtvzZNtk6+Bvt1MpjG0Cvyz6FLKWrNuiXWqWWvMkwXU8S8rttvVFBLKo\nL4jRNDQNpuKCDHhR1hUqK3UDY9BkjByn5cz3CBJ6ajMeputK5wnESpVzcSak\nW/VLNgxBM5X/UuGmEmLXW3vCQH+AjlH/q0z6PRbKs1k3jztsJTX1bbBk+nKz\nRlTUYRs5kBSC07GVvm8kdb1cXcJlq2SYXDFF5tCyiVnNdxvb3K2P3jlp6rFl\nDqe4uoQownlx6S3evSw7E/FGF0RzRtDWln+5IhEGlOHI8kQ3DRNoRrt8quph\nRHkOpcXngaWjmTB/XaLJ6Bl9vmTTjTEw0Ux3PXstoTxrzEZZM525yV3kHAto\nYV8O5vPx0zZHjucuPaI/B23/Dhu+JtjzLyluVDcKJC2WxhNjjyeHBixOUp+8\n7gnt98TUgDuxETo8tX2gZ+99tcJEMeG5HZyznGAhskQEhCq/36aadxEO9vCV\ntY34EHIMm/VzkmOoETzkwpEs5JDOxGes5QMtk70v8xYSZoxIKa8l9rYmsv0R\nvtaS\r\n=feGD\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"63624d8f36b0d8be86ecaa216b2b0bb7291dc5fb","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-01T11:02:38.048Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@sindresorhus/slugify":"^2.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.66","@balena/jellyfish-metrics":"^1.0.341","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.7","depcheck":"^1.4.2","babel-jest":"^27.1.0","typescript":"^4.4.2","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","jest-esm-transformer":"^1.0.0","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.9.9","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.4.0-lucianbuzzo-slugify-63624d8f36b0d8be86ecaa216b2b0bb7291dc5fb_1630494396842_0.8799712763441596","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.4.0-lucianbuzzo-slugify-b77542121da8e4587c780a011e5188e80841fb31":{"name":"@balena/jellyfish-core","version":"5.4.0-lucianbuzzo-slugify-b77542121da8e4587c780a011e5188e80841fb31","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.4.0-lucianbuzzo-slugify-b77542121da8e4587c780a011e5188e80841fb31","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"501cf2b94c96781ae90427bbcae5ccd13df98d90","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.4.0-lucianbuzzo-slugify-b77542121da8e4587c780a011e5188e80841fb31.tgz","fileCount":202,"integrity":"sha512-voeAD6T/zko6g/JbBPm4TfvWQY7AfePyFUf3MaZdlS6/82TbmtIFE+IMz9mkn0+nJCyGQBGEbahoWqzVmCYMdw==","signatures":[{"sig":"MEUCIQDEjnOfXCmktmCIr0q5Hm00OnfSVemd5g70Ql/Uex/BrQIgKaMlFqlS6DznW3FdDGJdnSnmz1ZmxIkggGPN/YQKjzY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":750113,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhL3PyCRA9TVsSAnZWagAAOvoP/2kmAP6IuOlKq3DUtXUe\n7ntCtuIDpHHFm80svNmcYu2uDiatMMhTQfoNsSi+A63aPuxDm7U2HpfJE+32\ncSGrAsNWQuvICiGpCli50pmr6DSykARcxzoLp6SlhzwyA0xZxTAQ2T42WAfq\nO8kUpr2Eb7FNhwMkCRSDVjW++QyxQxAE4+Es9TbBEwXM2dii31iNVIoI+GQK\nRbmzJnn0HrYCvdNfe9t0BwplaRAZ6MjDKfy2rAJ0KV4eNM13U70xllRqNUnj\nNTNo9suO97qHZn7bkqivR6fxuO1qwcvXIJW044qW7FJW1tUisBtocW3jr40M\nPyjLVPiH55YMwbySiXc0vNGygiQwkDZPaQqqRKHN4xZU+19PWuWsnQlK9pDx\n3VEdci4YuhNfpLxdE+HCcBd8kZnzeYAGUL4rYdUBc1VeMEhjEsQ1LqH0Kqhy\nAROrhDBPnGAkU/gjDBB+68zAnss/QJOYlajN2Nkc1NgoK4xsZkhJqKQWeyLq\nRY9EOpHfncgg+AcJ8j343RNZLGldsFovXMhpXUa2GMrbbwJhrSgV59TBw8Z0\nr0x4gevTEol0pRixL9CxebUdUmyWwOPta+lu+e5b4xFDq4TkKbpJdqqamjdv\nNWlGjzFzhcVnK8HWfEb0lxC9adsn7eBanyyoMCVf0MeiR4VklkmfVYk+IJdK\n0omN\r\n=XcU7\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b77542121da8e4587c780a011e5188e80841fb31","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-01T12:33:21.809Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@sindresorhus/slugify":"^2.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.66","@balena/jellyfish-metrics":"^1.0.341","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.7","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","jest-esm-transformer":"^1.0.0","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.9.9","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.4.0-lucianbuzzo-slugify-b77542121da8e4587c780a011e5188e80841fb31_1630499825847_0.6856722031924851","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.4.0-use-links2-5a3fafc47ae132c87619f853579a7125d66606ae":{"name":"@balena/jellyfish-core","version":"5.4.0-use-links2-5a3fafc47ae132c87619f853579a7125d66606ae","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.4.0-use-links2-5a3fafc47ae132c87619f853579a7125d66606ae","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b4ead0810c2cb0c2a7a45e491eaa5982b53da846","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.4.0-use-links2-5a3fafc47ae132c87619f853579a7125d66606ae.tgz","fileCount":202,"integrity":"sha512-1YIfxits1Rv/xmOJuptK5uo34m7ZT++aJOfUkiKWH95Dk1qnBpjFAch2li3fVHAGXCdCO5X5U+u/vSAHDHUstA==","signatures":[{"sig":"MEYCIQDjSiH7DAufWWTi/Nf3P9j+WmtNSkZZxAtqHmhlwQy6ywIhAPfRVkVsLed1SOYJqA1ZMf5Zlmdy6Xg9tAcE7r1Qa/JC","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":748304,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhL3tHCRA9TVsSAnZWagAA8XYP/ihXwrRSIHDt4C5ZGYIy\n3l1PV7PSueuZTlEVVKlQJMMMcqiz+x2+jbrra1JGnSk6yCkIqBcXhpE/7Mbt\nEoJdTLqtHFaoD4a/3UBMRl5xHT2isAgeYxnW/MFt0htZ4hSRMVTNIkOnxpnY\npbaJC7NJeQGfUED3Nb/Mfvyjpz2pEukKv07C5rlO1irvIf9+u8i/8oW4rRAi\nghdw0dpMUkMzlK0M/Uke0jFrw8r0Vg2m7cFrdPvhURXWEgPYnd/efnt/UKEK\n/ITuyqXmrQzw6As3FF8YgS+WPnY1XeoFN6L3vr3Vi0mBTbeAQWy47DhEet+O\nGYaIxAej+PXg5cQGNa+ZVZpE+9n4UW3o0F4eo8+nfBW2xLSHaHMPc5Bisje6\n9NyRf+yp3OP+kZ1N2+D8SxNOsAvqOAMH/hiXJVIsx+GnyWXBDhCOmuBusV34\nXx5QNlFnWsWpDEgXiM/86mIpwtr4BpuBQV6zJVBuvVRdH3xtndspAk7c8AO0\nKicwRsoZP9wAVuT+5ROUbMluQbxeH/6+BkSf7UbnU7FuP5w1uhmKk2YrYc2q\nDF7kNIMVMM3DSWUUbXPSLMXL8dNk1jJUgBVm9yw1axeoyZUUncpNgNcBPbkl\njzT3Gl/QiiV4pVMn1l4fVx/2Dmx6aGaowgAlQIcJhCKrMZh874RMyj58jlw4\nMGwc\r\n=6L75\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"5a3fafc47ae132c87619f853579a7125d66606ae","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-01T13:05:42.340Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.66","@balena/jellyfish-metrics":"^1.0.341","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.7","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.9.9","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.4.0-use-links2-5a3fafc47ae132c87619f853579a7125d66606ae_1630501702949_0.15519939405314487","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.4.0-lucianbuzzo-slugify-8b5f51366d22ffeb19004b158dffdbca132625c9":{"name":"@balena/jellyfish-core","version":"5.4.0-lucianbuzzo-slugify-8b5f51366d22ffeb19004b158dffdbca132625c9","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.4.0-lucianbuzzo-slugify-8b5f51366d22ffeb19004b158dffdbca132625c9","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2342f91785e59577014664bdd07ab2a182a49284","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.4.0-lucianbuzzo-slugify-8b5f51366d22ffeb19004b158dffdbca132625c9.tgz","fileCount":202,"integrity":"sha512-Ib1rECH8ktaPkpChKoM2R99jTW+fe+DJXryLCUYGpsiBCMGBYDsFKA/A3Q5jEKEUiQj8ilmwDSrINZvrWvgLRQ==","signatures":[{"sig":"MEUCIQCy4hCjHWPzUI7qsnqRvEvHbIRamAyyI9vx8iS7UCuHrwIgEfGR0vfNTJ3Ks6a8Fup2bDLSxJoaVTldQNoQy6EqzUM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":750177,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhL7JlCRA9TVsSAnZWagAAAkoP+wX3aEhFHs34ohmB7qqL\nXurirdZPO3w+FNA1xCsBFLkY7Y7BmwHqegrFcS7yom1Y3tiUwIJJYaNtYovu\n5EVKue9Wdc3RQqYCqNVcVDDjo+KobWDx1w4rzfw9WslwGOGDX8RfKsA5wXqm\nTqa1GKEBY1Z8Ua3ePG8cZDJ1FBaO2GyrIob9plkVxke4fCuoaJEZoQ4YC045\nddCWlsyU/zCRkX3lqhWySNeRYQBER9QBtXqfQA4u3tojUIQr8XZZ7ajJs/qI\nHiZmbKxS07VDg+32ji4gZGjFNyDxzuhw7CWYOOJaRTy7iqslTlAxOd8tjsul\nCqCYz5Ay8sTYKheqAy4YtjW3hEuEtxi/7RYLQuc3l4YQlN1Q1SCGNX48K3kk\nTTkpeyn33qpX1LRPK4m0VQPWy6tcXbwwy9FD2HkHfLkOyVhAx2UtL0/Y1vO6\ntxlJCEvI/PMX8MCG6tn0LpQ+HsBi6IZc0CcW/QWXYul7C9PLG2hOvUwL/6hQ\nNnrWAIgiGmnnKi874CACJRqiFpD2FX/2FjTRsRSE5C70kt8gX7lAG/2e6PLM\nedQ4oK7eK8u0BcIhBlwc0J2+z2pUwYvjT5OYuI2FmxEEegaXcoMRrCYWQ4pz\nUmgSgtvhZASQSCWgsQ1xbEVIGcQ9/zRa3WvJpAHA+vg5WLO9Uz9vug0easHY\nMW41\r\n=79Mt\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8b5f51366d22ffeb19004b158dffdbca132625c9","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-01T16:59:51.481Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@sindresorhus/slugify":"^2.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.66","@balena/jellyfish-metrics":"^1.0.341","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.7","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","jest-esm-transformer":"^1.0.0","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.9.9","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.4.0-lucianbuzzo-slugify-8b5f51366d22ffeb19004b158dffdbca132625c9_1630515813646_0.9306684799855947","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.3.3-renovate-balena-jellyfish-types-1-x-0b0f5534b7ccf06cf929ab28a2897b8f8f0143ff":{"name":"@balena/jellyfish-core","version":"5.3.3-renovate-balena-jellyfish-types-1-x-0b0f5534b7ccf06cf929ab28a2897b8f8f0143ff","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.3.3-renovate-balena-jellyfish-types-1-x-0b0f5534b7ccf06cf929ab28a2897b8f8f0143ff","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9aa719377b3a334524c3ab2d4110af4daec0ffd5","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.3.3-renovate-balena-jellyfish-types-1-x-0b0f5534b7ccf06cf929ab28a2897b8f8f0143ff.tgz","fileCount":202,"integrity":"sha512-eUDXx/SX7DOg8eqH+wcHhtrye+Kyu80Kb3FNjmK2pkvQ1vy0UTl/CamzAUDHyAb7zFwkMahMo/9lN5JSuJ/gwA==","signatures":[{"sig":"MEUCIQCYNbcyIio7/0ZnY/rl+VtHKFoMraP+ef7UPQ5E5toaUgIgFqI0esHH5cgPDVu+kEoF2NqyZwioLslsIXN/TGRT9W0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":748659,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhMCfXCRA9TVsSAnZWagAASUUP/3QWWN97mqJ6MM8qCVKA\nMA/KGybw0DAd/Zm1Aw0OFrf4wTf2Q9LY2QaaaE/YQi06jjX0EEKKQbUDM/hx\ns0Ac18EiYirDi4qc1KkwjpkG/8gM4SAzVwmfnHgfDeD17TGa4y4ZwE7EtZ0Z\nRPGCl8Mw2QTzyN0LiJdnYFAnfIsBHDx7YBLblSo6/xw+RSnq6OQ+DZoFkJ49\nsOpYO4itdekn1rhfcuoJKRI5RRM3/JMzG5PNqif+P33acRRfi1h1939Bu1AT\nJrtjX2fhKCMPob0ReekbkKRUkB+EqLAqrDfuwVP2TExfBiNGWk6ofaCeio1D\nRREUD2FOOnBF8bQQEfFF0fv86wGahMlXwe16rhZ7QKo1WwDozhvJccqbUYgq\nw9CTwI/ws6zGa4367DWZ7VjG7dVOAlXJdrn7StGCV5d9x2tRfeG5p6juAhec\n+SVVqB9LZRlI2aRs/QKzFxrHAcoZ9eXZacMKMaf1kpmBwnLJb141EPskxrme\nbf2NzcwDgnYaNFSKujcgNXmNkzIupNlZS7BdIOn4ABISk1qwxBb0HzUu/nCl\nwCy72ICT3SQy2x4ImH4qOOKgMof7wshgE2ZYWzeA9NdCYO6nu9aCLv4WSJXe\nhrqSWhLE/o7pQNHsXZxXJKWN4GB69n0Giu7lsD2iNW28MlXWcmpe9sMSx/sJ\nkTmS\r\n=3X9L\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0b0f5534b7ccf06cf929ab28a2897b8f8f0143ff","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-02T01:20:37.606Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.66","@balena/jellyfish-metrics":"^1.0.341","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.7","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.0.0","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.3.3-renovate-balena-jellyfish-types-1-x-0b0f5534b7ccf06cf929ab28a2897b8f8f0143ff_1630545879493_0.22651258460141133","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.4.0":{"name":"@balena/jellyfish-core","version":"5.4.0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.4.0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d62b7536fc7b94ee93eaeeb24ac220cd2b202ffc","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.4.0.tgz","fileCount":202,"integrity":"sha512-8Nfluc+967Ch9AawW8a3FNwExzIWX1KCwUvCcmjZMBlw1K/pmr31Cx0C8TikmFh+2R0x2Uu9+8WiteWmwPxVhw==","signatures":[{"sig":"MEQCICkot16ZEhnEccny+HFy7wkTdsX6FJroprd3C3pNKxOJAiAUxyaqtjyPF+g9bz/M3D+51wuA319FafW6N6K0/WNp4g==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":750116,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhMJJUCRA9TVsSAnZWagAA+ScP/3AnlRZhi3H5yBqlRutJ\n2fmsLeTs+Agqs0EtEyEKGgZ6UAGteLFsYQYJm21gBSslfJf5fE1qE3lXi6St\nTkh2AXnt3S9m6y86A4gQFcEedlYQLozmZFgIdR2xMx3H2XJSfECbxyE4mTfW\ntnXC+FlCUE6gUze36OcHF8TVvR3eMLpCcjmtbY9wRPWi0fQxyl6x0kvUUAPC\nPOGWr8UIgBQlFnaE122Q9Iim2BympX/OKDRaNGzlVAg/eXVykLuoBr62Mp6t\nYg/bwB0JpiXxW/byQ3MhKk+A6FEpdDZ4zY8f2Csuid1hvEjz2dz0oQtmM51c\nfLTE7zbdjSm2cRyO69uydb8vSfKXhLC+Ze+RcvGF2ZvExD0AxWPGBK5QMC9I\n9Z0pTo4WHGeNAg4zQ2alVpXV3M4AyCKKWhOvSaBaICjmjkJCfyhNC1e8/BdH\nmxJO9wvJpjgg7wgVOi2HdoFlmKqd97bPX1OsfGXi2Mqy0YYGV5a8qvfxoMtk\nPp8IGxqgGOYUelhE18N2qZfi4e3M3Zh1Swi/RJMekEPEVhXOUMKO9/YIefGH\nFF/JwDxDipxKxXNFpyBEQkodl6D3P412k4LvhsQE21wMAnbT36zPD9IguZmk\nOJhpCnWhLweHEtND7Fkyll2eZldz7K27XBpusqYwZCM0xuqbS4qJdc06ogFq\nXIYe\r\n=pUUT\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"008a901cf4d860f5bb2645934e7dbaf5787a0cf5","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-02T08:57:11.749Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@sindresorhus/slugify":"^2.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.66","@balena/jellyfish-metrics":"^1.0.341","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.7","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","jest-esm-transformer":"^1.0.0","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.9.9","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.4.0_1630573140611_0.11696055824442864","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.4.1-renovate-balena-jellyfish-types-1-x-ceaeeb3829c37ebd51fdad5100ab77730645104c":{"name":"@balena/jellyfish-core","version":"5.4.1-renovate-balena-jellyfish-types-1-x-ceaeeb3829c37ebd51fdad5100ab77730645104c","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.4.1-renovate-balena-jellyfish-types-1-x-ceaeeb3829c37ebd51fdad5100ab77730645104c","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"09986df041fe3e94543855f9d6b1c6e931de279c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.4.1-renovate-balena-jellyfish-types-1-x-ceaeeb3829c37ebd51fdad5100ab77730645104c.tgz","fileCount":202,"integrity":"sha512-Wa80sc8EGvaf5fT6awdVMW6jenUWSC5yx0MtZWnN9h/he6MBXy0NbY4zahmGbNxH6R6cDQbtqnMoNSfUUp7CMw==","signatures":[{"sig":"MEUCIBgrnpHeuDGxHXin46K63T9CczTH1Tx7Qtv/dmMELxAQAiEA6mN/0zafRLPcI+ZdvcYDMcgtdA441TuW96YVibz3t8k=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":750289,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhMJNGCRA9TVsSAnZWagAApgsP/AhJhNvOPHyNjXYWBf+3\nQ4/aQ8N3ZU5FRSPYvCGs7dyKlD3LZ2Y3hmfX2Htcfe/bLoHH74La2So+AllK\nE86yDkHM5fLYCmJtLdTxQ/0I/Nj+wn+Hrq8QOlJck2vgHQ7bSOucTzKdf4xL\nSdZg3FCEyxbJ+SZGqY9f0NXHRxlMJ3oqN2SL5wn2Z59GIsTFqIqrWz/JqlRW\ndQHd+Qr2fhIH6BRnzCpzWs2o1WHnh6IjzFYXN/946iln/2j/7OltO5hEYcu6\nw5/njr+z1UPYercFc/ixqnoXw2APR9JTfzb/Y5C017YuOtSP3gkF7oJuCgDx\n6vHsSHKyAJXAiS9fWAz2N0pd/zDUZ2elUlEDCfrdQ/ceG+UhlgGXS+b2Lloz\nKtvuXLYcPazl4J+5zO7W6MpEzIXoc2vVPQMPszNH8hrAE0WuAoYL+iGGq90R\nQw6fJ2TT4nuwf2iv8LO7AlVk+tfAqBx4A+qEM2GtCdeqzD1HMReo1Uc0XShj\nxzc6EZEudAPd362dqS+WdWpcLB4PIDlr6yhwh3vvtMuTLW/3EpUgKlFGjBBT\nRlIbwm07o2tqSiKW9G06iKl85r7BQT4wsNU7T2yneUzdnYb/YmjEXjYo+OP4\n/kUPuivAVg10qqkHrkj2A3r+Fd6CBAZPE1HSr4LrL2G6eulP43k+Sq9o7cbm\naIVz\r\n=CeyA\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ceaeeb3829c37ebd51fdad5100ab77730645104c","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-02T08:59:31.371Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","@sindresorhus/slugify":"^2.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.66","@balena/jellyfish-metrics":"^1.0.341","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.7","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","jest-esm-transformer":"^1.0.0","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.0.0","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.4.1-renovate-balena-jellyfish-types-1-x-ceaeeb3829c37ebd51fdad5100ab77730645104c_1630573382152_0.8852228762299355","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.4.1-renovate-major-external-major-58f3da8a1a594fe2494e6cddce10a6b40b78b2f5":{"name":"@balena/jellyfish-core","version":"5.4.1-renovate-major-external-major-58f3da8a1a594fe2494e6cddce10a6b40b78b2f5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.4.1-renovate-major-external-major-58f3da8a1a594fe2494e6cddce10a6b40b78b2f5","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c3834fbeccf50a473039901d41e288bf7af9dca9","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.4.1-renovate-major-external-major-58f3da8a1a594fe2494e6cddce10a6b40b78b2f5.tgz","fileCount":202,"integrity":"sha512-ciPTZy0DddScis2ZDOuq3Sn4otgPQj/s6eCkJ/g8Jwn/aVNq57KfnVOqVpFrTvoud4l9eff/GwNhvJhEdfysww==","signatures":[{"sig":"MEUCIQCRWoYjbCdmRS7xDYMEEC4451TBNaBgLS0ln/v9n9iz+gIgFTRhPfQiWgp5v05uMVAomYPXOkl6Y2QKjQe/Oy52IZA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":750256,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhMJNlCRA9TVsSAnZWagAA/iQP+QBYi5StF6UpAH/qFuld\nHg5yIor/jBxVwcEzH80V1LxJImoxw2iXXPrcwWF+HVtLdTQExPHz/XrbP07L\nLOk4iPQXzibuh28dngxp85PIk3+QJs1zqzvRKREAX3ouVa901SAnS4ioE2pv\nLtPRD1j4dw+14mofp0arHavlvnusf8qsKOWWvkhtcNJ7HxQK8wTOGC26bQ9X\nfOwS4iKTju/grdIWEgd2oX2QY2upeojR+iLiM47yl9hyP+9XVqihLi1M+Rwm\nNpx/s/JIBBSBXmk5PvlB4zOdiGqzdq3stlxw65z5fnrgW64KrcmPSk40j+ut\nJ5uWh4QdCRGBHjdK7NSnAfjSPNyvq7QHwEDoZTy/8fa0z+RKUoLTmH9WmlmQ\n9cdFQz17w0v4cia7z02o5A5xwFOampJ3ua9uvZDHJisybrAWVRBnyY6544H6\n8dlwOuXbGfyo2E91QZeN/Kg2ll5h+N6YOHeC/gWUwcowRNlPMMr22uD7Xm34\nF3ODYDeGB2GTM+roEbuT9bWTRMBDEJqSB1LApua7qms6VwhIdJGHnECKoaLL\ntlKMVXmij3T41M59XtIvWHFO6Mc/I0mXUFPPY3xaChot0ymfb50Wk9tVY1Vo\n6vpZ75XdLiSQZhORKs5KL4FcqdSLrx+ISpipXaa2CTnThwY8rLdQjbvQoapC\nPggj\r\n=bio7\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"58f3da8a1a594fe2494e6cddce10a6b40b78b2f5","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-02T09:00:22.187Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","debounce-promise":"^3.1.2","@sindresorhus/slugify":"^2.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.66","@balena/jellyfish-metrics":"^1.0.341","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.1.0","deplint":"^1.1.3","ts-jest":"^27.0.5","typedoc":"^0.21.7","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^27.0.1","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","jest-esm-transformer":"^1.0.0","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.9.9","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.4.1-renovate-major-external-major-58f3da8a1a594fe2494e6cddce10a6b40b78b2f5_1630573413334_0.6669340165287301","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.4.1-lucianbuzzo-simple-slugify-eeaec8e71801ea3f4fbe2d4536d5e86f847ad0be":{"name":"@balena/jellyfish-core","version":"5.4.1-lucianbuzzo-simple-slugify-eeaec8e71801ea3f4fbe2d4536d5e86f847ad0be","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.4.1-lucianbuzzo-simple-slugify-eeaec8e71801ea3f4fbe2d4536d5e86f847ad0be","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b93360e6923d2bb4f6812b2f94099e08cd4ecdc5","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.4.1-lucianbuzzo-simple-slugify-eeaec8e71801ea3f4fbe2d4536d5e86f847ad0be.tgz","fileCount":202,"integrity":"sha512-S9c0hUNAKq61xQwlcnBmOA6SI1z1N0fnE7hCtKMSOqnszfSF0q2RfHp4yZuqpDja2rau4N9M88zWJ/eNzfjuqA==","signatures":[{"sig":"MEUCIHLLuD8l0z5MA5RiCukh4f2NG+zUxw22fPr38Pej7CWMAiEAzpFzD+RFQUlTfqLOxy7JzH/B3W61dirMmYIDkWtiFyk=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":750725,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhMOldCRA9TVsSAnZWagAAFYMQAKAaZ43YJQhDR/fCVncg\nfeTL0oWJhRCSqDj2fYTw7XyTP36/CV2ViEx/PdZmB2lK6gqxBKl6vyph8lEN\nXSfgw/ZirHww1ON6lCgjVnjMP7OAP8YM4nn21O3yFcdbQaqKbhYP/+G2AxN+\n25WJnYfHJN8MIMHr5G7lJjwHpBsyHT3AXhPtFH4ybFZeqb29u7itBeu3478V\npk3IQ/Tw6UWpLckUwOiK+P18tFrJgXP7rEy9oWn4b7r4+OIJGwSgKfPisL1q\nE+qb8nno8rTVulGj+DX9EisYVzpYk7UqGGIzVAN5bK1ckjNhpPQLaTyTDVXe\nRqsQ0eDsouBmoBj13i/X4NQ1c+qjn9OSKWtzmsCWvpVgWvhegQONPRe4zr06\n6z4qzwc3Sc+BMJd01HqKzHFJPXgfQA/EaPmfiqLp3jcBCd/vJ9pm8iopOV2s\n6po6Qgwvcd4TkvzJtckMcUICI/6lsRf0hhhb2ASNegIZVQ5trMgCRRmWCU6Z\nkurGMa6Ac9W7ky4ZiKUgECpuSxJmLWYqnqN3wS1tVe32YeyYgux86WopXOjL\nLgrInL1NtXLIfm3iY9SPxo0zW9SM1UXtmiQCL7eFm6mIj1XovPiFa/olSwin\nodZxbRFiC7KdGZ02KQZAWoF+TkAYIEVtp+twixx7Ij2nkdSA14uCTl/B4kew\nK9Tx\r\n=o4Pn\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"eeaec8e71801ea3f4fbe2d4536d5e86f847ad0be","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-02T15:06:51.349Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.66","@balena/jellyfish-metrics":"^1.0.341","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.7","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","jest-esm-transformer":"^1.0.0","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.9.9","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.4.1-lucianbuzzo-simple-slugify-eeaec8e71801ea3f4fbe2d4536d5e86f847ad0be_1630595420909_0.42203097558922464","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.4.1-lucianbuzzo-simple-slugify-20df3445016e30d175614c287f13c2b76ffb9fd3":{"name":"@balena/jellyfish-core","version":"5.4.1-lucianbuzzo-simple-slugify-20df3445016e30d175614c287f13c2b76ffb9fd3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.4.1-lucianbuzzo-simple-slugify-20df3445016e30d175614c287f13c2b76ffb9fd3","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ddf1c1b4268854e29f7669d610226585eb9717d8","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.4.1-lucianbuzzo-simple-slugify-20df3445016e30d175614c287f13c2b76ffb9fd3.tgz","fileCount":202,"integrity":"sha512-T2axWZaiNNIHsrn43F4vvQGWKWStox35H1hq6lrh35w9LRcm64ouptQjuaeFcAunxRm7K8hJqYjOOnVM7D5yUw==","signatures":[{"sig":"MEUCIDu8cgX3vqnBQks6v06+rK9bGYKcJscGbueLv60imOHpAiEAlfrOtNVHqtak5ZB3lA5EcAgikROQcZNnxP9j72jB2Hw=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":750687,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhMOsPCRA9TVsSAnZWagAAOwQP+wT19/7XBvFuQqPWiNOC\nFl0vmPnfM4wIywCpV/DCAWFC5PgTSSoysr/sWdlow5igKPSvV0PX1pwsM58L\nzJEoHRNuBrfI7q5rvdeAlUu1LGTHxPdYRABbXXyouP+v/FYAF0FU302cQhgl\nybaahewpqPkaVceRETkGiqR17EYXuUTYqW6OXVwcFXe7/7FQOnrXpSMCrOOK\nsWoiT2nYXEYLvvV3Ggy2eACzY/XGsyGCFu+As5e2tgUlhLTzG18k24DqNWgq\nMADMH/aqLhrCsS0ZGjM5OJ6mnsOLJuTw1CL+02DmsoOrBVrEMJL/guOxy630\npt/3FWjASooWgXIDl78jyYdhkQtlollkc1rD2ixLzKbjhIW5rgyaZra6ojFq\nLfZdPTk2we+rSgDLllzrWjw5dh6QSJgNHQF360vRMK7LyF9hOlsfRb6nquuh\nhCbH4mJzzT49SqbzsDicv5tJQ3R/zc5RWEHseTxqQ6INu6R5WZWcf0M1tKzj\nNuYv/xNT3KNmHAFbguKJeS3PeAb5gCm0CyVA3Pa/tIk1ittIKMNg7vdQHIKo\nGmA5M7o/ActAWjJcwSWbnJwMP5v/2sWQ0odiW1wtOCVJ4vX22VYTX8zontkX\ns33nKo3myC3N1faRmamMr9X4PovRqsXdVVs2TZZcH+XEqQ1jQb23tjEVt41W\npwJN\r\n=OmgN\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"20df3445016e30d175614c287f13c2b76ffb9fd3","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-02T15:13:37.127Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.66","@balena/jellyfish-metrics":"^1.0.341","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.7","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.9.9","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.4.1-lucianbuzzo-simple-slugify-20df3445016e30d175614c287f13c2b76ffb9fd3_1630595855606_0.31824424773648285","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.4.1":{"name":"@balena/jellyfish-core","version":"5.4.1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.4.1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"68f534bf83b42ab196e65acbae01694d0ed45515","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.4.1.tgz","fileCount":202,"integrity":"sha512-E3QZxF4DsFprx1UpW3SBf2bJIVb2lZSJXVVIWdjUlM5hI+ud/LqHA6Bi3ASmigtnRBelcrG1+IEbuaHthkGE6g==","signatures":[{"sig":"MEUCIBNcaOdm05SmWR4T9hCcfYQjkQVy+19wOkyCflExCcPRAiEAhiC5G1L/GsDfmz9t17BAs+5eoor0/3QI89L2l85t9TA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":750619,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhMO69CRA9TVsSAnZWagAAFREP/2LEuZDiLnw0mjvcP1g/\net0n96fjhjDYuBQ9HV5kkl8V3ZKNqyp5K6fFuit208W+z7IXMkpW4Kt6GaI4\nyYy4a3PtxoQQOoMVO2z50Mb4LFaYeMgvv70vEeTgQobFMUEUvezokrywQkAf\n3zZ3rtbYE77xDWS1mvbigX6XRT3HoqRgfE3rxmtv8ZFdhEdOSVTq66usxrDP\no7rrYT3GB4D65i2IqwqpG5JFfBf6UJqhrnxyZueBDUjUM1Al6Ef8eLolgo4q\nbT/4y0ocrrUDlVm/bWt/MjaKCDAjKtJvYemdGxpG8DNNQ+logi8bwFT6+W1D\n2saxCocoo94bYncEiFB2WyxZyNsnboyb2paB5yYsgurGVTJtD4acAWMj0oun\n1j++pliPmAw0nbmKO3VgruPBflZoob5irgaSu7bIcC9IE7NsLz++OFVPmMJW\nVR01Fz9KITo+WKfnDW+ELvTF8WzOd2ytyp4otJc+s0nHfum/VhIlIV+vq7eM\nizY7LKVeu+KNv2Qm0tmQKdHIEhBC5mEdM/HM5banlOwMqYmKSDUG0kx8q0QK\nKXx9iSdbSxBqL5WueB0dNkGMfUbltv5hqhn55Y0nM7jFHjSkDwZNywm20jzP\nu4a0HoJAFxgeyScHp68+l0Bwc+oMbf9Ihm4FuNKTQN+gOhij1ZlrKXsLKcbb\nzcXs\r\n=oDM/\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"fb817be22f7cabcb3795588f8b1cf6c2d429689a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-02T15:31:32.022Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.66","@balena/jellyfish-metrics":"^1.0.341","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.7","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^0.9.9","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.4.1_1630596797475_0.743441868715303","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.4.2-renovate-major-external-major-372ad49960e906542b5a3dcc056385e4fc481617":{"name":"@balena/jellyfish-core","version":"5.4.2-renovate-major-external-major-372ad49960e906542b5a3dcc056385e4fc481617","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.4.2-renovate-major-external-major-372ad49960e906542b5a3dcc056385e4fc481617","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"588b2b26372ccecffae45409955d9347a429f30b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.4.2-renovate-major-external-major-372ad49960e906542b5a3dcc056385e4fc481617.tgz","fileCount":202,"integrity":"sha512-p5ZJJ7/WaYsHqW22KsnZ56hpuyNjN1SJrwSLIDhSRLCDFtAyGbDKcU4ATOHH3bK5EWgLWVORigJOXlVK78IJEA==","signatures":[{"sig":"MEUCIQDZtfq0ss6y/4zGCCbRYhWZy8GXUsipupn0RoCXma0BpgIgX/P59Fw7XWqHfuWTHc4+LtGKW2hXaHGnS7RHrUa7BG0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":750759,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhMO+iCRA9TVsSAnZWagAAlD4P/2xRSBMpgxo8a5RT1/94\nCKUpdJ6H3N1ItGdHzRMK5a1WJzXZfP2a3tIoCvdoJcwRFdxfnO0rNGEpJhPs\nGkADt821Txx8ffArBmybqJ8lKlIprLjvlXd+wSVS60v08NbSCGWv/ewqStqv\nPkCaJGIGAfqZoQfNIK0rQd5ypz7FoWu5czd1uPYsqvwFuaEtOGaZs8hz29CG\nzEbTfBDCi4fyhGjnJh0TBWnjavnFRhit5tTtWfq2FaAVX5/kldz2bOLCtKbg\nKoTkS4uOE3AqC+LffyZWEb7rnbATBSO1kjprp+TYg3mdicF6J65jDk9onqYq\nHr9ftz87tperNWrioVqtqw+5FheSoDOFmU9EEJrmd756p2rLVSqIpv4jR5zx\nFg9+NDsNXjT0q/BhsFmTpHb39NbIy+wmwuGwWa6F5tNG/JNfS1EeBo/d+Znx\nsbV+i6c2VXS9S0r05eHJw3/0sSCMlKQukZRkD4xA48nieyqVtF9rodOLSuIe\nCLp3GBeT1PquyuvR9yxn2EZ39g9lnf7dUtqA2M2jILPYIiZgqRKOGy6riMmg\nNiSf8kMdxEoNN8knRB0ceCwLOABXdGUZIBufhyDtUgHwHoaUg/mVa9FeCN84\nlDohBUZUEt1lzK9VXN4+MBA0FxtLJPz9X+CxKq9VTX9uxrabeLRiI2SN9npt\niQCg\r\n=r28W\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"372ad49960e906542b5a3dcc056385e4fc481617","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-02T15:33:33.563Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.66","@balena/jellyfish-metrics":"^1.0.341","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.1.0","deplint":"^1.1.3","ts-jest":"^27.0.5","typedoc":"^0.21.7","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^27.0.1","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^0.9.9","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.4.2-renovate-major-external-major-372ad49960e906542b5a3dcc056385e4fc481617_1630597026100_0.8334250735309292","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.4.2-renovate-balena-jellyfish-types-1-x-a59482c97c37164025bfa7b85bbcb9ff17023df5":{"name":"@balena/jellyfish-core","version":"5.4.2-renovate-balena-jellyfish-types-1-x-a59482c97c37164025bfa7b85bbcb9ff17023df5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.4.2-renovate-balena-jellyfish-types-1-x-a59482c97c37164025bfa7b85bbcb9ff17023df5","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2bc31b8154e47cd864dbc6ec77e3599d6b33581d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.4.2-renovate-balena-jellyfish-types-1-x-a59482c97c37164025bfa7b85bbcb9ff17023df5.tgz","fileCount":202,"integrity":"sha512-rjezUa69t96ehP2n8US00xhV11j9uvLKz55o/W1qsQqZaqxLcY5TNHl61j53gkdtS399JlsNXp3NTXY+SqTh9g==","signatures":[{"sig":"MEUCIQDowzJFvrt+lypZGjVkyMporp6ZxEeu1HxD91kS3D3kkQIgFODj3vjujRxbdYt7BqGpm5DileNxqtJo2s/mPbz0Rio=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":750792,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhMO+7CRA9TVsSAnZWagAA+9YP/37NaJCd0tcQK/Jm5eUt\nzODYL/NJaW1fRfm/CMXhcoNPWG3HTK7sz66RXVyeiqLqY/8orNi0fAHPaQVh\nxYW7BDjvT34w/KJB3HaKiagnLL0ApdxTfOoGcTiGKW9eJ/cvEk8DM1ftBNlz\nJPZzsAJffpSdgtGYKhifgRxkXQUH94Yb+A0vd8kVd3lFlvIvmNHEud6rsCcY\nChqTHHnMer9rEOaaWIg0HyA4rV4a/ANYhl9xJgke/t9z/tKpCKuAKO2sYhKA\nQUybnOs7505ohydEFHRd0yAzn2VpnBhssdBO+efsJDz35W2Om0fX9NSMEvbO\nIPBnOI84l4WYuI7q3LTad/0OjqihL6v0VgUMlnGoNgvYTnstAb0UfMiATKKi\nIzndyXnXFzVOZ9bnNROiU8hmhgGzNu/xh/pVBPADn4cpyPrktvSqsPIE7h1x\nkb629xZzuNy0ZdDWyr44PeIAGJk/cn5W1amqFpIMO793xVdzHz+albxCiHkF\ndqA6Y6/KHQb4XZe9mKgLHhSFb8aP8DtKI0ZODgNm5cptggcF9OjToDuxQ5zz\nibrKzEFFukQYNnAG6sLIzd2XO5mEjpQO5YC9Bpc3Rpwnd5AS5pSyZFyEzf0t\naHqkOErGqfZ8KZ+SC95sKjwgDOx9XU/GzOZXrbEJz1Cuoq+0WLfQcTOU7jyY\nTbCj\r\n=KpOS\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a59482c97c37164025bfa7b85bbcb9ff17023df5","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-02T15:33:21.389Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.66","@balena/jellyfish-metrics":"^1.0.341","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.7","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.0.0","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.4.2-renovate-balena-jellyfish-types-1-x-a59482c97c37164025bfa7b85bbcb9ff17023df5_1630597051320_0.5066102071214194","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.4.2":{"name":"@balena/jellyfish-core","version":"5.4.2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.4.2","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e909c9a6f04c8a6dd0817e0b1f87395851a550cc","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.4.2.tgz","fileCount":202,"integrity":"sha512-5Li6eHJHBJmKowyKpRXXp3g3fbVU5hQEebD1Vzvg/+FE6VzUy8OSJ8aNobrw85gfN8EwCAAvL6NyLCRKHja+FA==","signatures":[{"sig":"MEYCIQDYxQG+uohC8cqPhaXZ+PdDYv8Go6ddaCloiEbpz74tqwIhAIuJ2o5TifYPyWa3NdeH6sGG3rOYKi14Hda3nUETH+R1","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":750715,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhMXMrCRA9TVsSAnZWagAAR9cQAJ+XwSccAnSeeXdY1Ju0\nUkcJTIQx4xqJwtxAhZIzNX7Ni5NuKXQWiH2qm6tVD7RdDF2x1P/Z/YfN+GzL\nx10SEC2zgCsh5ruoZdGnFQSNC6esYameu/S1uDJ83ZfIvwSpguQqYIjXrJi9\n3edYRh5vuAmrn1pohmHzgf+XHdLAzpFhq1jeVlhBvyVdmkzQj6ks7c2nD2Rl\nD8VdGEg01zhtlVFW7vkR9OcGD/4a1b09kNckmKvwKSQwd2NSzjaVFcaGibhq\nOU02Ar++NF9HvFZgHrk1b5fw8mkBN72jVzkvlNnX5UoVGXxPwHI+mThoPLZM\neA3VUjojNWI1M2Y8nZ5o2F4n2JmBGZuSL4auKdPCwoFomAvkSRcr+Th/z9WR\nbGu+eWVzYwLppucc6pnjRSnpMcUO1c8ZRb4tShfx/jU92l/wm88fG6R08A99\njWpc7JCZyzXvFGIIqnfteWUu5eW9vHihZ4l2BqUSmDm7eLQ2Y6VC6IVlREnI\nF2BIHCVmgvqLhX6OxZ5rC6C6rgakhZQNF5905a/hnAc7N4tomvQ4Ut+Yh2LA\nvXOmmgtI5YkmPavAVNHeK1KRrEzuVneo1+jJ6ERB4xZQcf1x5ez1IWkVFqNN\nuKmvp3jNN5ygRyiUEIEaqbrSFRy4V3TxSpRrzLz6p7UErEAt74Y30/W0suMs\nuXFq\r\n=qpQZ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"30b5180c45d7fae74d8da1479b66308ae0c4c370","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-03T00:56:31.830Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.66","@balena/jellyfish-metrics":"^1.0.341","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.7","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.0.0","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.4.2_1630630698995_0.18100056946510934","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.4.3-renovate-major-external-major-ac0c0c5000e9f998bd68157ad089e802d2819de7":{"name":"@balena/jellyfish-core","version":"5.4.3-renovate-major-external-major-ac0c0c5000e9f998bd68157ad089e802d2819de7","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.4.3-renovate-major-external-major-ac0c0c5000e9f998bd68157ad089e802d2819de7","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5437663615db445d236156e6dff4f211bc07fc5f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.4.3-renovate-major-external-major-ac0c0c5000e9f998bd68157ad089e802d2819de7.tgz","fileCount":202,"integrity":"sha512-4F1wrbTgbXFDajSKiF0dwvXRy/cscLcPFfKuUt5RPs1HbAuEquzF3Y3HbgkFm6Ulo+3eHBiHiB6hUEYRejbCvA==","signatures":[{"sig":"MEQCIBQ4zQ8wtXXDXd0/7oE5XdcY27q9rNEB63yNkGPuDH1JAiAGqNE0EA++vxIvOhTP3vS/gtbImwWf5TqwwAQm8x31SQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":750855,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhMXPhCRA9TVsSAnZWagAA09YQAKMnonIQ2wmCjD5wjaAQ\nXYSuHmA6RLHg8+Gk36CorsIuRN1EvRZZB63Jq8g0NMfcZWYHnJ0ZU2tTxDQ3\ny1jT6/xvY4ObPdDRVTwp/geKDIegnDs53q1SuqF/GITnak2C8uWr2K9IlmEf\nJDDLqIle2+msKOz9HHwvbqoSOkv9vetrHMHhNjT5QULNTH9pRsd2g9fhqydr\n5AWJrUA+oAEd7z3FMKqJG/5JRPIZ/P/kBXF5IEExwcYCVcCEMzadctJkN7jn\ni/ZfJffj3/pSBhH0j1EcecAksu4NUlfXmloQPCTvos1vstFFkwmcue4jXJVj\nGKH2WhFyUZSe9xB5xYwfxefm1dbLIxKVMab8wiEzb1g8G46Jn1hOqECwKVwT\nevJGL4f3IyfnG9hsEDDQ30zfwbfQAhvoWyL6ErXcwrXFWdA1hiF/IUcnTzUX\noclKHbJYtJqf/O7uwohv/IAzlTqaJarhYwf+7WG6tMU+nYAApxps8LsRdCUG\nBgcmRS2f3VCgjEMPxFrEdh4n+u8ClVKV1sLbwmVhqdm3N38qtSorcKcheEEc\nUh1hE+xsZEjHvfV5pdaaOTwueMsJuaw2AbPXQOo2Cezzi73AOBawgL88IZAx\n+c446rg0CwW3+wi2ePTUjB16wz0VFqbXwQzCC8i0lF7Pihv36bW1A7EWqz7v\nLVf1\r\n=HDXI\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ac0c0c5000e9f998bd68157ad089e802d2819de7","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-03T00:58:11.996Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.66","@balena/jellyfish-metrics":"^1.0.341","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.1.0","deplint":"^1.1.3","ts-jest":"^27.0.5","typedoc":"^0.21.7","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^27.0.1","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.0.0","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.4.3-renovate-major-external-major-ac0c0c5000e9f998bd68157ad089e802d2819de7_1630630881102_0.7814527684086279","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.5.0-add-scheduled-action-68d7aeb5bbccf4138e5087e01d93ff3cf51261e2":{"name":"@balena/jellyfish-core","version":"5.5.0-add-scheduled-action-68d7aeb5bbccf4138e5087e01d93ff3cf51261e2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.5.0-add-scheduled-action-68d7aeb5bbccf4138e5087e01d93ff3cf51261e2","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5ba33cb4994be81d20b4f43683379a91d15cd6f0","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.5.0-add-scheduled-action-68d7aeb5bbccf4138e5087e01d93ff3cf51261e2.tgz","fileCount":205,"integrity":"sha512-sbQRJLdFWNUbzlqFnqeyKD/Wfhh0RsXlR0st4tCaabMa5rKOqr+OwnSXtfLshALpQV9YvK/4dAt9aIoqm19ppA==","signatures":[{"sig":"MEUCIBNANa4gR928+bYEv39MsNNBXYWEMezK2mhX0XXBwjauAiEAhc980oCwahad9hD9+mqcQSUDr50hU/sTGLBn6vCQ0wU=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":756927,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhMXUcCRA9TVsSAnZWagAA4asP/jXQGpJF/G6sCJA2/Md3\n6G8f4Y9zVVnl+TK56lDizZPi5Lrtj7Xsr3AkL0VcL0qTOL33eiF1r2t2R++4\nvmB5bJOwClFF7CT22nWdUMKiN4dn3yJoorYodEbp4wEdz31ioZ88pL6rTHfY\nwmkRJzC+Ak66qnCZsME7IJq8+nMOBeZIm94D3nEvvo32HFh88TKVelgugKLu\n4Kkt6YxjbRZORN+8San4v44j69bcyWvdV1c7jHtjLgNcKUHvHVdINitxbk8O\nqmYGbXVkR7BnGuYXFmEep+rE+cFs3BzL74rCx90s5zcdZhGZysRP0Qp6nEu5\n3C2RQWGY1lQHOu8kwYBpuXSXaIG0UDAqmzE11/3knbESsBfAs1ivmRPfM7X4\nS4/dlY6LD9KgaNg1VJEtv6suZKFPd6vDho+0jOAd2uUMaySOTqFS1SBbd6iX\n37lWIKqm9kUb+uZect6L78uQRR1QQmCcC0mKGWSV/egJahZkvnlYiK0RO69x\nGULdkD2ky8mbNPcTar5nVBoZ5qhGMw6X5OlLsonVbWndojFbE0uZPCk6Mh0f\nypkIQyL7cvbnM2910+5ivgYbjtW2CwSm9eXw0Golsj2NX51NMqiS26LAej4q\nMaqv5d8kFY3Sy8BUbjvjIS0bFkRzW1bYil41UHOxA7w0h3cCFuF6OiIc7Oye\nKNUg\r\n=AUmx\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"68d7aeb5bbccf4138e5087e01d93ff3cf51261e2","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-03T01:04:12.622Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.66","@balena/jellyfish-metrics":"^1.0.341","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.7","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.0.0","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.5.0-add-scheduled-action-68d7aeb5bbccf4138e5087e01d93ff3cf51261e2_1630631195851_0.9967517841881659","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.5.0-add-scheduled-action-21b1e191718723cab51248a3cc7d0aaee2df7599":{"name":"@balena/jellyfish-core","version":"5.5.0-add-scheduled-action-21b1e191718723cab51248a3cc7d0aaee2df7599","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.5.0-add-scheduled-action-21b1e191718723cab51248a3cc7d0aaee2df7599","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ac8c7647964c338acdf23f6f6689314f499b0196","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.5.0-add-scheduled-action-21b1e191718723cab51248a3cc7d0aaee2df7599.tgz","fileCount":205,"integrity":"sha512-Vcox4Vosr4btwTAMlEhFC+RTht46l2HnZHw/SYcXAZvWfkBVP5dQy3hcDv9tPh+vWZ0Qd2/kHkP05wN3p9oanA==","signatures":[{"sig":"MEQCICJt/mXzDFS+k9/HOpOsGqC64ZIDqGC1wlzOj3dW0RXIAiAHq/O8NuNxLk1Rk9z1UlSBzeEEoBmc1RtURTla9TpVuA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":757007,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhMXz1CRA9TVsSAnZWagAALz4P/RipJXodaOPtaQcNHUkk\nzW86dV9tmyisspiZ9GAkRssX3IyTk8CEKHzrBb6V7UBh2GqDhXqB4HB7uv79\nkEmlQ3Iw1qbuIE1hkllgtQAyGV+tAILpzmTQ5ojiWpTupM5CyOAOFgQQqtpD\nHueE3HPDceE1rOMoMzYFM0AtTyay7gkQYQOmI6VsX/obj1O6CQnAHUqkp8Y4\nZB0V45cwlToSw1zPgJvqpDYH3eTeOTixzD9t97ivpeOxQICUIcQpkHzVzKpZ\nE8rpjMttfwrnDfvd1uOGyK3q28I7QYz5WwFRQ/9WSZEZqlzrr7+WBw5kMj9a\nyz1WtXk/jIW77P54xaFpE6QzI8qO856jeYtrlP3W9TCrVwFlfrtkBpBEaRjZ\nqAkcktALuWOsXDH90hg2PQXEOXq4O+S4/PXMKOdLR/uoAPkhsdThnAdjwXfW\nxAKsQm+qMTb1Y7vlFLqQrtr9hzMWWTEEhX8P5BO/D7/r50v8pVjDR+JjhcMA\nY56Ba3DYz47f64gHKIty6wGiQJpyKqtW3GxZNrMpL5a2bhsnwkpPMMYUQgk5\n/75/+qKgwbgqHUnno/CM9UyL/gwOi2E0Q6cLCzsmvwuoRQ6byIP+fEH+IfeX\nC/6dJ8BeQhVLD6Ev44tnCraFx9afwhL7bw5AsU37rGDn7or9gA2niFPTvJAZ\nFE2l\r\n=ME5+\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"21b1e191718723cab51248a3cc7d0aaee2df7599","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-03T01:36:41.750Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.66","@balena/jellyfish-metrics":"^1.0.341","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.7","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.0.0","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.5.0-add-scheduled-action-21b1e191718723cab51248a3cc7d0aaee2df7599_1630633204588_0.2736867420934621","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.5.0-lucianbuzzo-s-add-error-contract-194bcf8d5ee05222dd6accb3355db4c8667c417e":{"name":"@balena/jellyfish-core","version":"5.5.0-lucianbuzzo-s-add-error-contract-194bcf8d5ee05222dd6accb3355db4c8667c417e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.5.0-lucianbuzzo-s-add-error-contract-194bcf8d5ee05222dd6accb3355db4c8667c417e","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"bd6b1eeb9b7127656c68c81c668c1b5953081893","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.5.0-lucianbuzzo-s-add-error-contract-194bcf8d5ee05222dd6accb3355db4c8667c417e.tgz","fileCount":205,"integrity":"sha512-bQ1Qo02UlK2bGgMjdBoh9GlW/izh8AOWZcw7CwWZX1RyYm6ePgxMMyQG6NxoJ+O/jB57gI+GBRDzg63Ph1dz0A==","signatures":[{"sig":"MEUCIQDeRBBADlcBuyA4knOOxser4reWzgqx3jaqBlgkU09rWwIgaKunxr933kdYgC7qTQ6/D34+aDg5GuNQmCjiidifdiM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752460,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhMhfECRA9TVsSAnZWagAAUlIP/23kb+LQ1HAWKSGqwwK4\nA4hDaO9l/CFk75qcS+Ba/k13/uviW5pw3XSh8tF/23VU2MOLwUjOwkI0FWfR\nIhJ1gclvrGX74lnn/2pO3ItQjMbsCebD4qZTqYVHMqd7mgpLTcuvqaaVX6CE\nspqHG76Ufks/A/19jUzhKbLxaSIBrN9VKnlKdFcRwqKrUwACF2xvwTimUTYW\nRAIkneoJac8p4lZIQceyX9NF2Rhi9D8mkhOP8g2LlBBeGbCCyTgUdJNJqna0\nh1IA7sB4AJP1+0lGw9Jn3EqWd7B8bshZMWH1P9opAgLrLguK/efhlPzqkb6K\nZZuZyJKigns+5r3JMwDxBaxi5aeICfmmOiwinNjYkzTT74/DoQqnZcU4H9M5\nchtNVkBc27YZl8T8sQqOvoyJRUjavNfxshcdU4k5E0baqo/ui6Tai1L0ebyJ\nxOCAHbdC41WIOpQgRnEFqtppx7NAFVM96E9bhIX2LTvWx6qjyJCBYQrq3PlI\nQHC4luw1bjFfX2UIAoGnOQ7JjwyObF3lepFT8zfhmp19P0EIXuuRdVD6nwSS\nU4FJ23ODn0qbMVuqJ6oQYlM0npOV+mgVxbpGcBP64Pml0WaIgq9rJiHpMYEN\nYCvN2p0WhuJo+8wNBmmoy4GNkZQFwgdYps5j5WEbF7QjFx1oERijGpycAlnR\n0lXM\r\n=qNnC\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"194bcf8d5ee05222dd6accb3355db4c8667c417e","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-03T12:36:52.637Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.66","@balena/jellyfish-metrics":"^1.0.341","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.7","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.0.0","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.5.0-lucianbuzzo-s-add-error-contract-194bcf8d5ee05222dd6accb3355db4c8667c417e_1630672835917_0.48341746888206694","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.5.0-lucianbuzzo-s-add-error-contract-25ff9d4d7dae2bd13cb1a69a9d4d6e8db99cd310":{"name":"@balena/jellyfish-core","version":"5.5.0-lucianbuzzo-s-add-error-contract-25ff9d4d7dae2bd13cb1a69a9d4d6e8db99cd310","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.5.0-lucianbuzzo-s-add-error-contract-25ff9d4d7dae2bd13cb1a69a9d4d6e8db99cd310","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"62fdff778e62e6e7b0287f6313cf9168209495a5","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.5.0-lucianbuzzo-s-add-error-contract-25ff9d4d7dae2bd13cb1a69a9d4d6e8db99cd310.tgz","fileCount":205,"integrity":"sha512-Cmg+TeY2tdf7fXvsPAtLdfrRLHanv0lpdZCml8y7lMvWyPFCQtLKE2HUj8+TnaZmGHDPby68YeKfQWU22KJ0nA==","signatures":[{"sig":"MEUCIHpOvyBY6sozXHySpb9DNHl846aPW3kv5I37OvpMjKquAiEA9qkHoU+t082RUMnjQiP9yesocWQZI9hjVvwhcU2i1Zs=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752518,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhMifpCRA9TVsSAnZWagAADWUP/iklZHzCuE+E9gVkEDRd\n5/W1thkb0tMzvFDhzRCTdtg0kGypW2197Qwio97AM/0easROEbNT+eKnak+7\nICbXzvvO5WWsrm2TeXL+y9HdAgAogV/jQFWbwj9jSlTHohxIIlOFmIrkv7Qu\nKh0i7/buN4pm4p8Wh9vW18K3c4i2G1ZgWyL5vsHC5k28wa8QWQMkaN4sR1l/\n7XVhfnUdkzb2BrBV1EY/lKjbSIspiO3ibrbnRR9zbyuiVoQ9Fx6+9bekoT26\nEn8HJn7Hb+GJE7EmL2KH/ntBSXKVxTiP6Xcfz7rT9GRD0GSH6lWJUSl9BqHm\nC6apDCoAzqR9EhdN6ZRPUD5srAFMW5nQ6H61jsFb5qx6QTfNjy5tPXuppJne\nFl78/6iMbZaGuW6tPit++x4spkpMLNi2c0ZLM6nhZVnfyxVO8WvXPHXJOUpX\nSPOr9hU4Ll/lzkeROGvBs6OcyQEEfvTiJxZovNRsnPUryoogFiH+BamQxxOr\nFeQJSdJyY+c6iM2EJz3WO+O8YdngyaCF4kz3hkiwPXzprQynUxZZkH0Ng55Q\nvkAcQ3odSqOpbAtCWnCgiTT8ZhRsZ0Kc2JXb2sxZamXx4ZNju8iog2vKWaWX\nFjAe2DyAK9MUgk2iFLEv01tl6kwOSZzC7swLXxTefItrL8R9gieUfdZrdnbN\nZfsY\r\n=d0bY\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"25ff9d4d7dae2bd13cb1a69a9d4d6e8db99cd310","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-03T13:45:41.823Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.66","@balena/jellyfish-metrics":"^1.0.341","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.7","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.0.0","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.5.0-lucianbuzzo-s-add-error-contract-25ff9d4d7dae2bd13cb1a69a9d4d6e8db99cd310_1630676969061_0.3726932286584512","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.5.0":{"name":"@balena/jellyfish-core","version":"5.5.0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.5.0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"084f4783dcc4da6e715a8c7d5c2b2ad15360b6cf","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.5.0.tgz","fileCount":205,"integrity":"sha512-IMLKp1/cIa3TYs0PkQXSGfonxsOSbLo8cP4Ft5WQAGG2hCITcQvg8F9BV1t9cadXnfIwU8cxqw0Qub8ROwxtXA==","signatures":[{"sig":"MEQCIFHEJF1mCKGbUaeoeJkW5qv+sCoalqIjonBFG48ZutcFAiAJ/657uzm1koFDqYQgO3HTquSwaWQwXRX7jgQAbUeZpw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752444,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhMjF6CRA9TVsSAnZWagAA6VwP/jB6Kio1hLyYuih1UY2j\nRQIPUbN5sDywR7BRRdOD6HDbWExtkyNkjDNMpmUEDmdtyGE9ejM40WgD2YSX\nSg/hGrByY38J5Nf6C5vCYtt9pY/amCu8znpaFgB6nb0JzdQEcmv5xyomeNQu\n9RYO+mqXmg3t7oIDfnPr5DaD10bA+YdvAJBvDXJ/qJ+o9T4ZcTlJC0LeBLrg\nwQ0Ddm13UyIQKLyxBddRnLjDfD/QWiyDmb8iYlNWa50FLRxZAr7BC4/T/5MB\ne8wIRnzcCjIFtJPvyAxnEKmyWDuYr985XlgMg0OuyKV2yjCuTwBfMG2B+q1R\n1zHfnYCkzD7aOCw1MgqNJNJXeproaqu9U4Y4BpzGOJjC1stVvLbwbyU5sDjd\nxSF2558LQOfyIgirQNZuqd4CEJjsBtklEk/beNaZ2vmj4O80Jo367nAulxlx\noeRMIsHFWAOrzmts8c0cucJDwk1y1gqvh+PIVhbLwZMp3ZcL21pZ5k7S7WhH\nSjNxY8HEbrBuqmOe9BdQAgHfDkL3rVNl0S2RuL4sB8nd9NRFC0lwrXWrwm+U\nglgPIjA+AkjKFTagZc2OSOA27LZC0/NAHJWWIe42WXSIFrNpbllubomi9yKn\n1gvvWDrZ7OLrDQzAqMZPnz/bEKlrk2Qooxm30ozFP4+pF+iQeZ6SEOC5IOay\n5iMo\r\n=ifEe\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2b15d2de207d277c6718d4e4032a6ca0a4fec237","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-03T14:28:22.107Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.66","@balena/jellyfish-metrics":"^1.0.341","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.7","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.0.0","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.5.0_1630679418578_0.4424174031998882","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.5.1-renovate-major-external-major-c88c7ecdd8152c7a8ef41fc8f6535f1ab9fbca27":{"name":"@balena/jellyfish-core","version":"5.5.1-renovate-major-external-major-c88c7ecdd8152c7a8ef41fc8f6535f1ab9fbca27","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.5.1-renovate-major-external-major-c88c7ecdd8152c7a8ef41fc8f6535f1ab9fbca27","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d558968f8bdbe48373474bb5237979323f011c70","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.5.1-renovate-major-external-major-c88c7ecdd8152c7a8ef41fc8f6535f1ab9fbca27.tgz","fileCount":205,"integrity":"sha512-JcYrSAP0VmgxhFzg88j9TADxbgrx3CIm8nzWNgwBsQ3Bgl5zqcj3fLY9rF8mhZZwwna5mOvjtl5gGRDwVJh0UA==","signatures":[{"sig":"MEYCIQDi5XRt00/t2qRUpZMGqjLwLYsgYeZGyiaOMPRIE1RDMwIhAI295hcg0tL1sPJX6s5WZxiMs0mQfJ85jQgYQJe5kQL5","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752584,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhMjLOCRA9TVsSAnZWagAAVmwQAIAKfXLiOoTnLQV3XUOU\notbS+YzNWfjR2ll7IjiO0eYztUloBi/JbgLubLRWGOPXvhiNCgt75O5RWSkA\nK5PpV2khytJGag/bZSnb0Hjf3UfWb/5K/2RQbpv+5ijEbnQWXcW2wtME7bNn\nWRbmbeJTHyzNL1tBthLRKLr113FUFPnREZpNJYXQ73vIKMp2Snq6D6C9C1gn\nM6ln3njZvUgHb+kdYCKTjNUTX6oruJTWJx1evpAgmAdMvQjj1/yXJJPNuWS1\nbRfFtN4cVVPisdJTd+bMp/YEYDIrAsJ8PYvrpwF0AmfPZUjoj6vRfdbjKtT4\n3wyAUvcOgKjZtYb5hdULisuSreKnXrQGJ1viUi7yqmtNzDt3+Z5Tp52+b3HQ\nhKSoWkkOUbGno9T9DENUOFmGGrs0HvrS0jUczq1t2QsDPkX0K2v4asfMV9Cd\noUqQB7y3h3RM/6YNe7Mnut9Xbpzxv84FjQFmCU242cgEV8AQw7U+y2zZ8ZFm\nAvxMCxr8QrDBQKMLrW/k4j5PIvliEplbwGj4Cx8PzM7dj+rGH9l9rRxxNM2s\nT5jgTOiPYOAPbcUkDWvUHIF4qB93sBHkgY54uR3pvvavadQWEHIJdZqe/KbG\n57hnyM1odkafqCfpVlcS4m68ZFmbu10qjP07XBHSTjjgyLTYgcKWcUFF2mDF\nFD8E\r\n=sVvk\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c88c7ecdd8152c7a8ef41fc8f6535f1ab9fbca27","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-03T14:32:12.168Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.66","@balena/jellyfish-metrics":"^1.0.341","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.1.0","deplint":"^1.1.3","ts-jest":"^27.0.5","typedoc":"^0.21.7","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^27.0.1","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.0.0","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.5.1-renovate-major-external-major-c88c7ecdd8152c7a8ef41fc8f6535f1ab9fbca27_1630679758283_0.21254762616773615","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.5.1-drop-link2-on-reset-5147b4d9e89e7a6128160a7ffbd90e4fb4a5d8a1":{"name":"@balena/jellyfish-core","version":"5.5.1-drop-link2-on-reset-5147b4d9e89e7a6128160a7ffbd90e4fb4a5d8a1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.5.1-drop-link2-on-reset-5147b4d9e89e7a6128160a7ffbd90e4fb4a5d8a1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"28040ddaa994644c50a7df1a037e508092fe3b30","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.5.1-drop-link2-on-reset-5147b4d9e89e7a6128160a7ffbd90e4fb4a5d8a1.tgz","fileCount":205,"integrity":"sha512-BKhD2PlPcRK1e9vZEczvvLkTFltQPTitagalJTMfMScd0qjLY/IzAKpONRjFewu9wKbJ65tqk7KT1mSBHODO1g==","signatures":[{"sig":"MEYCIQD3lZhyaa4+X48Vkq52KFKVZaEieagTesRUEzW8WbO+BwIhAPjpBryj9gukrH7r3EkLoLYtXqKJwKHNkL3kq7XJVn+k","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752644,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhMjmCCRA9TVsSAnZWagAAZnAP/3iLdX/ZsSvQ8QwF8gmq\nZDUHFSL+wopat0g9zJezN+VPUxGJrhwg3/UKF10UDS+PAzYsQT2OLa08D4sA\nyPF4H5PHyjmwQmc5eXqTLMcyu/flUgWY3bfw5cKfhZyVyOfguwv7uWeJ6k9e\nK3YHdgFHTewVq6uHwEveMvKDFU53fLh+TVYVzWJajF9+mK+k6ahlhX22Pjdy\nVIa93fvVAHjZ/XOzYkVV7CMyPd2VfT4JQp6enR+u5xMX+RfrDlFNxhgrnhFe\n2qmLIf3FlMT3j45sYP5d+1etdptE9QZKEbaVTdbUX9yNdsiubMjTMe3IDa57\ndE+ALxIphcHHiRJuHU++nLK0J09h4VJjQtROOK7hSLVASUoanD5gzvSulmCZ\nzh8WuZ0YKZMDkgyos/iM3I9B1U0Dpba6UFOs0P6T3wjBxt1QJ7rml3HdLeA/\naLSXXEkyr3uh+9KuSLR2jZcx7AuctBVNLCFruw+n6zNVPZ3dAvnq+nTTAcUE\nh2UGxfi5AtVzWLaT+v4XYzovnABzp7rsO9oHpJXUnujWDvDnICJXZKNuKplh\nHb9Xd+RN7BSdlkO8dI29vkIJCK2wbnBcWnA6HkhajlpI/gjO5QiH5LrBsNkp\n4WLJFNCufiayAbapnwzB1sRcMOLZC13KgkaOQoqXhDgFPKAekIli0sfM7YRd\n9tlQ\r\n=dBZB\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"5147b4d9e89e7a6128160a7ffbd90e4fb4a5d8a1","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-03T15:01:31.823Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.66","@balena/jellyfish-metrics":"^1.0.341","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.7","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.0.0","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.5.1-drop-link2-on-reset-5147b4d9e89e7a6128160a7ffbd90e4fb4a5d8a1_1630681474295_0.9588545838930853","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.5.1":{"name":"@balena/jellyfish-core","version":"5.5.1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.5.1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"59eeafa7c853029a72f78ae712d0daf060e1bda6","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.5.1.tgz","fileCount":205,"integrity":"sha512-eWjs6MN01XegX/Sep9SbyQXTbw1UOHeIxLCk9h7PLHRA4Sio5PcSvyXH7KBzZQH66rlYd1WebUZehk7fZftWkQ==","signatures":[{"sig":"MEYCIQCR57nkKBprPKoKW+fCYbmLnH+GDqsVXP2pB/aFCMYAXgIhANTxMLL9fM+WoXARBrQJtxYKa+A/k/UahhRuqlJ8tutC","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752583,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhMjslCRA9TVsSAnZWagAA4uQP/0XlfrRsyBqHGZPBMW++\nfIYb8QDwd1p8d7hn2xqtxRMvozz5IaBVKJwkCk9mdvw0lWwEpIr1ttbjRGUs\ngTYirfTW7aqNt+hYIU4mOurGSaFoT//rIS1318WY1S8wYZUYeRH7o7zWecTr\n2K5tEbFcGPtZKMcpBP6uzemlD1LbxS9/1YKhnEaK2HPqgXnaJHokerZrSOpI\nUH0yG6Ls2r2LruvUZpaoJLdxeMcid/GB+pRAqaV8INxCnFp/XQJgAqeVOqsb\nyrcG0aPlfVzy2Mw3t/KZGSZUgSSMh87ncRgyUgOkcGYUnPd/XKMKfVNDO49F\nDYT8iI2KHQZeHImyNIIyAszTfHiTJkuvvQ69zk/LccPKAUl3FQBD10HdZ37D\nF/1ex5/bIS97jL1HMlQNLk73lRrIclRHZKSnQR5+ssMCfz1qcNeQXNIWQ/sq\nDLWhVDPo8C1QhzVcPoybvJ79KVhD7BGPxBXpfLrLCbFlDitSQ8YxFsZskxUx\n34NycbQUME8ngCgrSNwh9uNqYvUOm/bnNqUeX1dTq4p1wK1AGIldxKl4pGdg\nRw6nwELf25PVLLtCtZLv18vy1/naDyqNKa6w+ytBWy1ilDKaoe5Ct52NGoN7\nc0r2sIgAljTYYb+TT75Vt3AYKxtJhhK8C23MiHHlOyMLDGPf5Ml/nV+fGZu4\nSU3n\r\n=DgAW\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"07d60c3d1f743f8c6a95eb0130f86e3dd149cb96","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-03T15:09:35.533Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.66","@balena/jellyfish-metrics":"^1.0.341","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.7","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.0.0","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.5.1_1630681893632_0.5399090526872632","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.5.2-renovate-major-external-major-cb515dd31a309a3501f48e9fd5c930d387e11fb5":{"name":"@balena/jellyfish-core","version":"5.5.2-renovate-major-external-major-cb515dd31a309a3501f48e9fd5c930d387e11fb5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.5.2-renovate-major-external-major-cb515dd31a309a3501f48e9fd5c930d387e11fb5","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8b3050f47a55c356b2829077afe7534d8677f92f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.5.2-renovate-major-external-major-cb515dd31a309a3501f48e9fd5c930d387e11fb5.tgz","fileCount":205,"integrity":"sha512-XHTHywPE6Eh4YAasctmgVuKtT+Lewt7EynkQmNH7dmm+e4AYmudkuIH3pFFPbw/NJhSQjGb/+gDyysvmmgb5BQ==","signatures":[{"sig":"MEUCIDX8RwOUaEmW1SNgGkv6a4YMW5PmS0pXGRTKF9McpKDhAiEAoR4DGN+G3vonJIRdzjv1tG2M/qI4dP8z8Kln4cP4sVU=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752723,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhMjyOCRA9TVsSAnZWagAAU5IP/1O1z53nrO6zNaLM2FDs\nuCNGlDcROo5G6ZGB0H8pqqoUcS/3WI0PFccQHqXNY5+u8fFzwQMR93iExJ8k\nKYqFYceeWS9RgErd61/TGVP/YcHkFPRNWbzatNG2ExeL3CnKm0NdrsJOs0Pc\nGarijAWeiwC6fFZxG/RvgDi2zOh4a/+ucT+XNULq0Z+LNtdW/8OIwhfkUBpa\n720191U16d9XWlab5q1HxiYif7iS994TC5cMtDK0r4E4GZhjJPpZE0kXYlB+\netsB7+EDZlruixIuXmtgvf71tsnsxspBP04FLEBZ9H/6b5tAljBXXrC6iQns\niN/KiFF7iFNBVWjIuBBkaAsd4pu4BbuZFn1QMoHFJD5zjnDf1S8rtZZdvuLy\nfZE9W1z170jA0vqEBshnvtjjLBxBJFYHnr7m8zmeX9UMKDNd/BhDQuvR2One\ncFQGAEGLIdXumTxWunVgvQLxf2Td7iX8tldnHNoNoFzexx1+f0eiXbFuXQV2\nbfWk6fc6vl6iX63xqivC9FszsY83jkO4gOYef0yLx6Aqs6drQlsLSix0dCvA\njijZYMR0b0Y1473b2OkMteHjlJ0B+p2nrtbWllEcEx8PLw/a2Tr39/KodukG\nvW+Go382SBAylWqC0nTrXMkfIDkRdtoNvr0cYkur+RfDWHPotq+9D4LqtmL/\nhczx\r\n=YVTT\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"cb515dd31a309a3501f48e9fd5c930d387e11fb5","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-03T15:13:42.231Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.66","@balena/jellyfish-metrics":"^1.0.341","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.1.0","deplint":"^1.1.3","ts-jest":"^27.0.5","typedoc":"^0.21.7","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^27.0.1","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.0.0","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.5.2-renovate-major-external-major-cb515dd31a309a3501f48e9fd5c930d387e11fb5_1630682254408_0.8361790421042932","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.6.0-add-scheduled-action-8ca6219750c01dedf2f781626060de2d4f034f05":{"name":"@balena/jellyfish-core","version":"5.6.0-add-scheduled-action-8ca6219750c01dedf2f781626060de2d4f034f05","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.6.0-add-scheduled-action-8ca6219750c01dedf2f781626060de2d4f034f05","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5b078b2057108d1d204b091a8afff0bfda4e4b51","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.6.0-add-scheduled-action-8ca6219750c01dedf2f781626060de2d4f034f05.tgz","fileCount":208,"integrity":"sha512-zon64rOXV4yniqjLqQIi+Bd+yWcOPEMu4hO4PVFxlTpoggRP8Wk9YkuMY9W2I4srlIdBnkCfeJ1lmUMEkM1rEQ==","signatures":[{"sig":"MEUCIQDGKlBprSwdZivvTCGQlG8eNB9Q9OAtMzzdcwmVf40ODgIgCdlp/bHVokY7a2F/yWVFZllAGOFq1y73YvBuGl3gpI0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":758875,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhNEoWCRA9TVsSAnZWagAAtl0P/jJlZmRKbR6EZh6aFWa/\nNiCdPyweI+3EnlOgWrGmaON7RPMap/o4JD7wbICIkFHMDRCEeJev1ZHSjqxy\nU7xz2lA0zfF2klU1c/iFglqRD88jQdsKgOJ2vt6kR9Z+Lsd5fdpf2MNWury0\nkhSGHuKidEp1XJ36AHoFDtRyus87rC7UGKW0YsSLNBTdnnlCxOP8sPUWNFbh\nF8C2Te7z192F038Dro4z5jPZEnYjb9gbXtJ75BtiOcRpLvBnYWfu90b5Lb55\n0DjiSSYzFF4vI/WZKEl90OSz8zd0tSeP2rJ/3Aoz4Yeb9m1UQVvA8x6bjNNk\nTShROcxALvWDmLg7k0pTpA0LZWshVuTR++bWaAedDd5aBot+vajFkvEZaXA8\nXhP5CwEFSbx5/ny79YFkq3yF9zxHq86rnOme2Jtkx0nt8rvU4L9X0Ti3VT4f\n9YM7nbEqpHjB6uGZneTFjm24OWNPEpAGnI46RTZjGYMyxJlsMvc6VLqAVQGe\noCwNYBWhFZN+qlqiWrz7eAbG6QZ6DG0dQXY1q2U+/+bVUURBrf4hDH0m2MOF\nAFqDj6BgHPmCVftb+S6AJujUEOFp47NoGz+SmQnJCZxIBDjka2M5MkcXIwiC\nEstmShdqgt3V7MQaNKYUJy6GFSmDWG9iIcb+Fexbz0WmkxtkJItGG3b3vt+9\n413I\r\n=LxYS\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8ca6219750c01dedf2f781626060de2d4f034f05","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-05T04:37:21.773Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.66","@balena/jellyfish-metrics":"^1.0.341","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.7","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.0.0","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.6.0-add-scheduled-action-8ca6219750c01dedf2f781626060de2d4f034f05_1630816790698_0.022915355338227306","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.6.0-add-scheduled-action-ba3cfa59559da8c5bf99b4d230fc246a2a1e1dbd":{"name":"@balena/jellyfish-core","version":"5.6.0-add-scheduled-action-ba3cfa59559da8c5bf99b4d230fc246a2a1e1dbd","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.6.0-add-scheduled-action-ba3cfa59559da8c5bf99b4d230fc246a2a1e1dbd","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"12ecd52aec699131d9d84f04561395add9b2a590","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.6.0-add-scheduled-action-ba3cfa59559da8c5bf99b4d230fc246a2a1e1dbd.tgz","fileCount":208,"integrity":"sha512-mCf3hphh5vHMk1WLkBGujdzcZEuQ2VMhTBFHmUHlyLZRiW29ph/7M2RaSbZJB+2exJEELObCmCT38+VogFEyyg==","signatures":[{"sig":"MEUCIQDWOUg9rq4PId2Z4JJaQNe3KbuDbOBMFZSi3yYXDF26CQIgSfSAnQSzUmV20VfhqGIaS3UPdfeHngxceHyFc/yWvvQ=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":758875,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhNIAtCRA9TVsSAnZWagAAw+EQAILNNeufM+Q9Q0XJSz50\n/u5csM/xxr/kKcfXEKMZPpecJh4hg9jB4JJzsW6V7FvnKDWYu56N+WCyMvkD\nuNxQIsRfD2+zjLJhge8EtgMj8l+3oevdl7aCyT6c0ECCy6szLHjaaRg3WYvA\n5smgF2uwCbt6PdzSylVLhsvK9TU8OpECDTsBVm9OsTHWXCweoCfhQDcjohrU\nqLa4KcXZ1HQQ65X4dN9tJIcu3KR+98jcjRlXNcO7212Zz4yPN3kSNFk5TdAX\nfWoLy0xm+uAuILwT2UF2leXfM7O5cBveCZLqkp990BFdpOdJfWi8eX7Aw5Ti\nhLdttR9Bvy3irU/PRUUUiTb4oMxxxT6eyFWkRmzeXkLzBBgmUvRgHRBgkxo5\nRqE0XWBKZGgeofPLpP5/8mPrSU96Kx6jqKI4Yjc3mw3silBJM5J8f/Dc+8RI\n3dNa/3unYs5zgJoAM4OqdjF1gym6eWEg0fJfoAOGN4f8mXi9lVKKw1K8wMjk\n+fOJNpfjUUiOkjSRDaGcTSK3YdjM9nR9FC5VlZ6rADfKPgFki7W/4/OgLqsb\nyqJGjmQXsNFDJriyqWe59vC8GPlrIgRGS6TtaUho9acTa2OKqPSJbPp2zHbR\nUZU33oQUcbfhHby+7fMgR2QwJk4cfoiChK/sollNXEPaQ4C96xyltcOLRx2Q\n6Aow\r\n=+fC7\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ba3cfa59559da8c5bf99b4d230fc246a2a1e1dbd","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-05T08:27:33.016Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.66","@balena/jellyfish-metrics":"^1.0.341","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.7","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.0.0","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.6.0-add-scheduled-action-ba3cfa59559da8c5bf99b4d230fc246a2a1e1dbd_1630830637137_0.11304969339951154","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.6.0-add-scheduled-action-fcad7f1b6f404601c70710e04d45a2222b4bfcc6":{"name":"@balena/jellyfish-core","version":"5.6.0-add-scheduled-action-fcad7f1b6f404601c70710e04d45a2222b4bfcc6","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.6.0-add-scheduled-action-fcad7f1b6f404601c70710e04d45a2222b4bfcc6","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1860f8274bc5b931f41be1ee920134c0aaab2776","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.6.0-add-scheduled-action-fcad7f1b6f404601c70710e04d45a2222b4bfcc6.tgz","fileCount":208,"integrity":"sha512-QCOYgEUfOYXcXHV/OGpeZ8DqUuyg2tqE1dY79KFXawiKU2xTxqPyolM1zcN9vqdMhWvvXTMZwnqoFHwCkj23yw==","signatures":[{"sig":"MEUCIAjT/veRCbjNAfucXsGL4F5U+2o3aJwKOIhKlRmLdFYBAiEAz0lhGPSZh59sSVSzMNOoD1GRCvVHI2ZzFpUw/c6GCBQ=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":758814,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhNIO5CRA9TVsSAnZWagAAQ6kP/1+UrsJj5JrxbRUz3afu\nerlsmNMXsPJFDqNGcYwxG5d69BxQNN3s2icbQf/G4xYcZvNuJ+Hwcnlqw6AC\nWEAMin27si7myude5qpT8EYiQAWdc02HEB8FKtUyGW9VgHuosr4K8ZWq4V3V\nLuibyEMK5ewPuLk/vBFajmidhhXEw14gQm8/0pUqSgeu1oouxhiigg7GIboE\naIZa3e1SGVvWOlcvAJFRAFAZjCedZubxqBJZwX/h+KXH8hC3KYNKYZWwXpOR\n9Qb9x2zowe5bYOeF8Vm4j3dMWZIlXrlpPVInuGR2jHjC9VDcVEe6+KBACHwj\ngWkTyKahlCHcB1tjRL11l8j6vDK098U9euec1q1PgPFIhKraK88IrZ4XaR2v\nwnXerl6aOC11b+Sk6/pRcljwh6DboNneMG7tFGX+I/YKZpp0n+tREluQuhBd\ntEBCTx81uYxaPY42Wp7fBINssnFgrRnk9b1llmbYFhKWDeGnKikNnpR2RwKR\nBB8Vsc9HthHtB/Gnemcxb4bLxIRUotiBg4XDMQYAhtvnzIBMnJFjCnI+4/ZI\nKITF4E+b2/b+Y8isQREbyBQuRQs1Fpdu4A1G65n70VMHrE34rlDo8d/Usscw\nJ+Vhi+qt8cE6KhDEfrP7BA+eJ4xTeQt/JzJcvUg23fHtZ5zWcs4aIqxS1L/M\nDDjC\r\n=Z/JQ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"fcad7f1b6f404601c70710e04d45a2222b4bfcc6","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-05T08:42:31.575Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.66","@balena/jellyfish-metrics":"^1.0.341","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.7","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.0.0","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.6.0-add-scheduled-action-fcad7f1b6f404601c70710e04d45a2222b4bfcc6_1630831544919_0.8578989997692827","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.6.0":{"name":"@balena/jellyfish-core","version":"5.6.0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.6.0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ef8e543bcc9be7662fb2f96560635ece7dac661b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.6.0.tgz","fileCount":208,"integrity":"sha512-ETBDJBcM3sk5N7jr7rsGIswDZW4V6+9HKvQODBj3ZD7IXbklhMRMZ6sVfHjzVj7cHeuB4Hv2PbjqUxFAXLVwvQ==","signatures":[{"sig":"MEQCIC8ZZPzvaHOrazaEi1GsZuB1wPEkmX7hPkMRURPMrT+9AiBiZ8LVayp+aj33BgvTvcftGH7dQT18Ck+y/i/IPLASPA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":758752,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhNIZuCRA9TVsSAnZWagAA7rYQAJ6z6o7RCDvBY4Ln97CF\ngzu6KrqGxmYTWZ2jNTairF0hxO2STlzyafOCNmThWCBQvbbqYLUdAX5qGc3Z\nxRnRlY370C6M2Gtq4KDZ3glpQqq+ajGgjy1mzYVXZngQKMIiJcN5/6Iad7yo\ncCmOYLDYJcie26sQiucZPbkvrYwTrKD6sIIsr9pzo+HZ1/zkMWWfJlXnXcMg\nKEmlfHIZZokxAP2e5U4zl6bgECmzDVA8bp0slEpaGWCTO7egQohiHgjZPjWZ\nsM8o52sol8iVeHbpjAeX3/3Stso4qKjuGIgE4IlY0lfG6ytPrCvC/FYDmb5Z\nwy+WLzv7A5EhLtN+aeNfPFli4MVHt9G5pu8S4KrbwHnUhKBr8A3qRiU8qvbr\n/MMaZ/IdVBAIJl19HZbk/ilYV/mrIuxq//vBEkpQf+vsaFbUXKW628WNYWgn\nqaJE/Ag0X85eIJrMCg+DGPbHT4zS91g1IIF7LmIkBxNb0g5iw83vYN0rQeM3\nR1bn4kceU9hWpHCiw4H2NUmRS0NboQ0jCS+LtyOhWiNFnurxfqeXgFGvdf2d\nRLu8KJNUVRf7Tqxa1rLs7a0a7WChsViEdDReVOWza0JWPU6yrh9vTqaM0bed\nx5TiOZlNf/UYEb5UymkufItREmEjtinri9I3CS1y7LiLDv3/d3GgqdReqE6c\nCwds\r\n=V0YX\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"9fcab6565ce186b929dded381ea7021df7695924","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-05T08:55:10.120Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.66","@balena/jellyfish-metrics":"^1.0.341","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.7","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.0.0","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.6.0_1630832238582_0.9668366636222341","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.6.1-renovate-external-non-major-d704c2b5af654392f1b38c88412ec6df40b9ca86":{"name":"@balena/jellyfish-core","version":"5.6.1-renovate-external-non-major-d704c2b5af654392f1b38c88412ec6df40b9ca86","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.6.1-renovate-external-non-major-d704c2b5af654392f1b38c88412ec6df40b9ca86","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"385e2b9e35a53582db4935a604667eb794cb5ae8","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.6.1-renovate-external-non-major-d704c2b5af654392f1b38c88412ec6df40b9ca86.tgz","fileCount":208,"integrity":"sha512-8hSxGSasNw6GjaQljNhTnM5khm2glr57As/L9Yhq3VPQM1gEgGRcMjVpQsUL5H3YvZDVeWcDJYY+mJ7C9pPLaQ==","signatures":[{"sig":"MEUCIQCE1gydn3ojZMSPy4xRmTaQWbBYZGHeLRXuTWteZu4bLQIgKUzhqUET6hqfmUmi95dbhs2Pl1TklE6cGG6hHZMQ/y8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":758906,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhNIgCCRA9TVsSAnZWagAALZQP/jznD4uYVZcrJIj8XYRU\nrd+vuawch3hNaueEBRgqtSdSX36kaKpEqripluPxsryF3oQPuvWQ91derOKn\nLLtKVinSUFbR3lzZ239pvdmOC25hg5wpR81HmPY+mFIH+uuKYx0e/pPbCOI8\nEg7G5mvHsntrloQSMCG8dIcU0JW2yZNesyl6bXRC4vQscn+kkYgpw43u8Zhd\nJM/jQ+zHTdemqi3Q1mAW4yia4k8W3ehEWKEhpFTKv0PxVMVb/Nl/5WBLVukD\n8HNlnOPzgolD2di1grw0hORoFt8nIO6MBsg1GQiVMAtSvR9FzQ6vRUqXhTl/\nKNGG+Dg+kl3+myMq3BJE44mP8Wcpb6RvHxXI1MbCoT+JbqQoYDRRk2JoUU/9\nlBg29HbGvFz4yVE0Hnc1ls66mUZymVqjXcEONpnW2Tz6FvsA53nlwHCvfiTN\nZvguwcSgpuJG+TMFHvO9dNTrFFLqgKePKCgnt3Q/BTqGHZX2DdoBd1lQNXNk\nnOFDgF75rququIlGq/u8WZsSwusGXA1OiU2XMLBotV4nlrcoyp7Qy9J9bnna\nRfB8/Ye08EHnCCmWUP+aDMzu3uHoPJy2vCzalNHTKp/qYFrM0H1b3fGrcq6V\nM0D03/i9mNs4O5kuyN16daLYWclfW4lG//dBPxOK6wU4xayrAQ0xwvbnbdJW\nKzxu\r\n=n8Th\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d704c2b5af654392f1b38c88412ec6df40b9ca86","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-05T09:01:12.207Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.66","@balena/jellyfish-metrics":"^1.0.341","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.9","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.0.0","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.6.1-renovate-external-non-major-d704c2b5af654392f1b38c88412ec6df40b9ca86_1630832642238_0.9114275505544074","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.6.1-renovate-major-external-major-1ff2e9acfcf85e0b75da50d848cd5779464e9dfe":{"name":"@balena/jellyfish-core","version":"5.6.1-renovate-major-external-major-1ff2e9acfcf85e0b75da50d848cd5779464e9dfe","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.6.1-renovate-major-external-major-1ff2e9acfcf85e0b75da50d848cd5779464e9dfe","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"da7fc4a3a988833b8af1f87f043ed3edc4b2ebfa","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.6.1-renovate-major-external-major-1ff2e9acfcf85e0b75da50d848cd5779464e9dfe.tgz","fileCount":208,"integrity":"sha512-0DtcAZP6eToqGPi9Oxs540RXYV3FxePL4Olnh0qhD7uHPGSrY+PxJD2IYnOb7A0LQxFhqytm9WbbZQxps/Pm7Q==","signatures":[{"sig":"MEYCIQDW7fb/umOyTqvWSnQh5Bvyp51Y+6xzJK5PPl+Rl7XgZQIhAI3hor4L8CKR3KDeYp5B2sJR1ji7SCWpAN4afvcOauiy","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":758892,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhNIgxCRA9TVsSAnZWagAAPdkP/iE2pyU5fo9zSSEqGWo0\nLbJeLnxJ45OnWJdkwxJ8Whb3IVepg+Ev3Mht7b6GxoMvtpHFCynXVYhEGfXl\nsJW3Nkdx2KEuTdSqTzAI3PYrXP/98OrBa5lju1FhKWP+MqTvncz21H8z78kz\nqX9O8+Leqan84wrjTiW6ZqsHjjTGAU3e8CYku+76n+h+x/kUg64JnfZ+HqdM\nf+WvugHJG6dd+PTCvLCvfXeftkBAxu9kcmmcxn9gcHIG9y2PkG1K6M8WZZ18\nEQO/krSFz77jc9cZokPLyVkGIU3xD+qQPORPh4sT1LWlO3ECrEQQ+ik19Oco\nRwh5snzAnQXEVpUPxMV9U565pn2wGGSk+55gOxrETFrd6e88MbqbSaFxruRc\nzQ7xnwE++YtSULUcMVzb9Lt6tORJt9dR5c2S4/BD3Lc1728NjtlSuawB0TTS\nIQDyOed4juXJeLbWQayTtBLPNV2soFK05x3obCfgvW/q1v7G8mon4C0+5R16\n2b97gCjxmAap3wetKYHyFZ45dSZBdMncASH8aCLfdjxmbf8Zgcv5/TMWFGBG\nNiaN+OUgd3MdEGqHHY+JtosIibdowGyWe/JKuDbWeFfUwjXKuPo1fISPHPUB\n9elAPyKfdBCSivFGGRVrkwBnPBPUTJUjRlIYqBqAh2gQL0z9/wcgcGcLY3ZJ\nXZqS\r\n=tLnt\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"1ff2e9acfcf85e0b75da50d848cd5779464e9dfe","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-05T09:01:32.611Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.66","@balena/jellyfish-metrics":"^1.0.341","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.1.0","deplint":"^1.1.3","ts-jest":"^27.0.5","typedoc":"^0.21.7","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^27.0.1","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.0.0","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.6.1-renovate-major-external-major-1ff2e9acfcf85e0b75da50d848cd5779464e9dfe_1630832689068_0.4569629203261796","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.6.1-renovate-balena-jellyfish-types-1-x-65b7212985e8d0e8e2fb54deb6fcb11dabb56f63":{"name":"@balena/jellyfish-core","version":"5.6.1-renovate-balena-jellyfish-types-1-x-65b7212985e8d0e8e2fb54deb6fcb11dabb56f63","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.6.1-renovate-balena-jellyfish-types-1-x-65b7212985e8d0e8e2fb54deb6fcb11dabb56f63","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ca6791c75a6a6d9c756583f3ec279e9cf85ea0da","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.6.1-renovate-balena-jellyfish-types-1-x-65b7212985e8d0e8e2fb54deb6fcb11dabb56f63.tgz","fileCount":208,"integrity":"sha512-4tl+8w2a6X4R1VIgnLUi62g80myodLh2+5psCycl+U4NofZXQs2Y3u1DDjTcYilBl+YTX99VNfTHw42vuqC+/w==","signatures":[{"sig":"MEQCIAszLfRyBsuLMwMj4IsDSOa3l2q0L1IMcaGm1h8BAhMwAiBOr9YZRcDZecpDRPaOlXI6BUyYCNC+3TxjRhE2psPduw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":758929,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhNIkCCRA9TVsSAnZWagAAed8QAJhTtlT/uoyxmvbpeM3v\nqaqsKwQK87TeztzMMcLgaRytIIvGb+KHzgPaFaeH2LOqKZtD4zLhsFpRdmij\nXjBe5dnSJAAv0EkQHqR8T1p8JtZPMjbqbdnpnBxo3Wu5OC9aBVDm2VTjQJ1i\nP3hqE7NrN7T+raTDl74/5e62daPNylKBLCRdoxGjBxo7xScDmK0MPI5pBPA/\nOvnrOMk8u85I00wEQqy2DvRN8o8K1g0oxImstv76aejNbvnB4Slfu4MDSzTq\nfhWaCaihBQUzqlvL9Fjsayz1ujT9TMy1xMnIrEgFb4ovMJesn+gOMnm0VzHI\nsx+swQHFfO+PCwrEuusvcerJJFJAQpoYrzYXJIdmtsYnnr9d5dtIPKXLbynK\nUr2dD49WOEdUCi8Pizjtg4dSM7BYh8qEojXfsfQjHhRF5mG0ObxF9SX1+4P6\naSy+Lyhr1PmtuKD2aB+R6muBw0Sh+hKsuxwmhfC6j0r7DmY2vWbSG36yXUQU\nQUasNYUHnH/WXYOBMjOuyTFU8RCCFeaP1lYIkL8tDtdNK7Hi+Ndq+yRBePkV\ncO3IlqQ3AURQUpyd7TGG362nOZmfEqcAZdVDv9HLNBARkX/vxk17XrKIJpM6\nQKV/AogAs0BxWPjUrUrJfk4EAhSNm0TJP1mjsWatFOt6CN+UdagEt/3kziqL\n1f9V\r\n=wNUW\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"65b7212985e8d0e8e2fb54deb6fcb11dabb56f63","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-05T09:04:53.399Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.66","@balena/jellyfish-metrics":"^1.0.341","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.7","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.1.0","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.6.1-renovate-balena-jellyfish-types-1-x-65b7212985e8d0e8e2fb54deb6fcb11dabb56f63_1630832898489_0.27917505621358485","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.6.1":{"name":"@balena/jellyfish-core","version":"5.6.1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.6.1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"47c13323ee98077bd67265e61c3d0c9654d10d3a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.6.1.tgz","fileCount":208,"integrity":"sha512-bBRTgSv7fmKStOf7I5gHXcB0UONUDvDTxLilVKt6L5dL/apLKHSMgshqUwfQKbTlUa+uoa0LFI2BWI12cw55aw==","signatures":[{"sig":"MEUCIQDXocbhOFcniI9rzMPd5juN6GcmR4smsdrVw5/FTyBw7wIgT+ENTWAeHKkw1q6/n5dgXUsFX/HJQQ0aRYnQArsFsgQ=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":758837,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhNInuCRA9TVsSAnZWagAAHJgP/jZX7LZtOcFODxNeHDzj\nI3E3CFKuNwA0x9w21EFAD/wEae9/VRD69jMwXAOPgQlgN1l7XaHPMcB2UYcq\nOoVWs/yomfYZiRC4fZclfOtEB65YH6ZuGLQLvEF/I1dBUO5lLOcFJ+ySaBUz\nA5l0/a+LiHY7tub+bfUnYoD8Mw2zc3uSu/Oajd70ZpyPHSzEGClTouSyOFVd\nAdSgOoOqsNZZXjPb9St4/6vJGL0IzPCQ1folhi0QY743sH3/Uh0MAV7nRlXl\n6JWRo79ypTVLod4EjWtVY2jJ2t+U6HpCXrmR3UcppFnXWso/KBbKv/KV67kA\nyEWyh4jjy/6awHJ8j3n4gAweVhbcXwV28vsnIMfwP4MEsdH+aPpZn7CHlu0q\nT6EtToxuTdbxnjDEcgIqz0El5pb9TRWLQZSpLxgTeMzu2ZMUktE+PEhC//oa\n1IoGgjd4dQD0Ar/BX3ypcKpqzWZ/olC/HC+8Tmq+9+Q79UmnK4AefdWg/+Kq\n234lcK3pAqRomb4cPt6Yxih4gsJglhrx9u3sTW+zmHulvl+IKrw+Z87TUNjl\nDXAscUbMaQKApTha1bZ0LufyPlDNd3P7QHIK0Zsj8z7Mp9AuMFoVVrVBtq55\nXOLHIcv69mxHE1aUxm66F7Fk+FeTp95rZ+cciUEfPOzhJfwWDW6QPbzUudk+\ngEBs\r\n=En7j\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"769501a532d447980a1fb14879ddfa8fa4c94770","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-05T09:10:22.580Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.66","@balena/jellyfish-metrics":"^1.0.341","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.9","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.0.0","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.6.1_1630833134349_0.756936910178279","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.6.2-renovate-major-external-major-576784d8e8fca0370716746fac209c5f69bb30ef":{"name":"@balena/jellyfish-core","version":"5.6.2-renovate-major-external-major-576784d8e8fca0370716746fac209c5f69bb30ef","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.6.2-renovate-major-external-major-576784d8e8fca0370716746fac209c5f69bb30ef","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3faff52040a67a2f9d9d5e4dce3f0026c93d98b0","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.6.2-renovate-major-external-major-576784d8e8fca0370716746fac209c5f69bb30ef.tgz","fileCount":208,"integrity":"sha512-LK5bnNtCLQEB+ZrT5Fmjljnv+mCOcYfn9Fg6zOhTIXN+HM8R/actHjdqS/jxpmcbe4IDnZmKX7UfWHmfXFDSsQ==","signatures":[{"sig":"MEUCICAdM8ti0dhEQfcAWC139xQCbBMhJPStSL1EScm029/dAiEAhSjmY4JqHwdo+W9ZsmeyNRYZGdFDaZG56uoMxn1BTfg=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":758977,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhNIraCRA9TVsSAnZWagAA/gYQAKQow6wP3P53fa3wxMOA\nU2Fv8dt6S6VOQe8qdE8er8Q9dkAnCBnplQfWwsggUDdsclsV1gXa55kqwU6O\nli2e/rhSmC/HJHcHKm3BSPDFlwDuBbg6xiqDvrKN/Q8rOUKod6GKbFQsT+qz\nsB9pV2Nmg9nWRtA0Op/72Bh/W+PnXcoClz7a8y8k0H43L2NlHMAWHVFO7Krs\nkUg8xLeDhL1mieM3VUX8M4Jfc8zPWV2X8+N5w4csZb6AI/QmUG6OD9mO1W8v\njnQ7HZTVW/JdaQ7MBcruReDs4XfnhJAXZezPI8t+bUUObt5Snt2lWF+ZE7tV\nxSIhOOIz5uwZLRo2jWh90KVD8aN9Ie2OOqbNyWt51HF4WveOTz4r60SedMIU\nJG/RZrUANb04RFWXH4DMZMaQsPfGDpyZqeNu7ZZt1/CQ28Gjl9ja50A2rxUF\nzDceZBi3lPfBcTRu2KStd9hrYYURbm5VE8XZyAovIAOh3Kx0E0XTmK83kImM\nPUF/p/i2+0U5DFM4yx6dMP6zlPzRsRauJt5kZYUYD5+DgsgAtYTM/6iXt46m\n/JCsHxThqKoTH9v3Pewa9ah5J0aFYwZXHwlY0gUuEWq7G1zoTGIDNCcgMmQr\nkComdWpzK5c//NjvhiYQLrnYCLeayUES5Y4wit1a4pa95S9VkCGGtCtjtWDf\nisyD\r\n=Ywz7\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"576784d8e8fca0370716746fac209c5f69bb30ef","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-05T09:12:08.229Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.66","@balena/jellyfish-metrics":"^1.0.341","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.1.0","deplint":"^1.1.3","ts-jest":"^27.0.5","typedoc":"^0.21.9","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^27.0.1","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.0.0","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.6.2-renovate-major-external-major-576784d8e8fca0370716746fac209c5f69bb30ef_1630833370484_0.7818151150552086","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.6.2-renovate-balena-jellyfish-types-1-x-bd236162b9fe7b03a563149041dcfad04977463e":{"name":"@balena/jellyfish-core","version":"5.6.2-renovate-balena-jellyfish-types-1-x-bd236162b9fe7b03a563149041dcfad04977463e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.6.2-renovate-balena-jellyfish-types-1-x-bd236162b9fe7b03a563149041dcfad04977463e","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"892f0d46e916562636af3835e833405084e0784d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.6.2-renovate-balena-jellyfish-types-1-x-bd236162b9fe7b03a563149041dcfad04977463e.tgz","fileCount":208,"integrity":"sha512-FQToajVclUvuTAJDLwWE8VY/rSBBjd7nKn4DMDAbdCK1AgW2AvQz6Q2zu0KC0+jC/3KyEqQt6yQnex8eVyX2aw==","signatures":[{"sig":"MEQCIFCWO9up76d5Re3nAOix76uFTlbJHNo6CiHeqeL77C+9AiBA2TSuiZc50cTBW7L7TFRnaxKlRt72HrYiPHg3AcMGpw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":759014,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhNIt1CRA9TVsSAnZWagAA1SkP/iUcmOvSx5pjkmbpUqhB\nqxD/2lBZXqfkqAt+tB2YK6as+DidluNYafVpFkK8D4HLPHepeydm+qcZP+tU\nDpgV3HKB7cJjng6vNNCqthM+5T4kwHUQ4u3DevssU9H+dQ3rFq08f4G8JktN\nCnWIWZAZmy6IoIpFZS2d6HzYphYVwIDGNosAekZhpXQllODeGWzwboJOgXC8\nBJbpX1E2POHUws8vJKDYohmK0n9mom+jsqzMwHux+NvgMicF4XC0W8eFT9Ah\nvS95Avoc/r212q+ttWdISEu2+fKo8H+NKNiS6gn32Kh4GfjV98mYOosbRBe4\nN5be5jO5RxAqbZ0M2j/tL3/3L+XVujy894YnN2UDfrGduSpt6kBpNHAauZgE\nC82zfFMkB82vG/pbjlTT/JEA6B/tbDuI1RWAs1U2SFxte5LmeyDmKtLGZGWH\nDvGa60daR8F+uzbEItDF2abkuonz6fka9gEDz5nvD/z+PuJUMj7I4Y2IxU7N\nfyc0R2o3TUegzMsXJJTwRePUXvo8To8RoLQVZfwzlcBwvPfSDZ3lhajWDsGs\ntLW4PcvMHlpG0WECDM8QlmZx8ZhCcOd1y/TsrcukmJtNwhUMFM4OQ2ONFht1\nOcWWpAsL6e7aGx/ckPFamS8LkNmkiyLOiwYJbUw46k25KBJIg+x2z5MJ0dWF\ngQJt\r\n=Pdgg\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"bd236162b9fe7b03a563149041dcfad04977463e","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-05T09:15:12.721Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.66","@balena/jellyfish-metrics":"^1.0.341","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.9","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.1.0","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.6.2-renovate-balena-jellyfish-types-1-x-bd236162b9fe7b03a563149041dcfad04977463e_1630833525673_0.42672620815677","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.6.2":{"name":"@balena/jellyfish-core","version":"5.6.2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.6.2","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f55f80ace2a4f0f74b3206d36fa2b9116d57c36a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.6.2.tgz","fileCount":208,"integrity":"sha512-gIV4A6k62meaxp8FRoqmBcrOFHkkOux/ojsKGlCFlrucNMYhabOO3Fgu2Xk6WdR5A63eihpUi6eUsGzTy7lALQ==","signatures":[{"sig":"MEYCIQCXw9hVQ1HvLyw7qB+GQ8NuetdhSZ7kNn2TdZ/s9P+mGAIhAIZ6eQl1un63EFGkOya1iX9XiGmsCRsBhTvCTSquTMOl","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":758937,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhNI1DCRA9TVsSAnZWagAAtGAP/1/KMsb4ayycmfe0AqoV\nIlegoBVzjcTLPhpo9koNNU7BWpzQiFhj7DHJP2/SVMlHJ1O/ppR+B0xdvbLg\nKwKCMSUrPn0OSwHuRnf1P0S1DJbfTyeUsmiWxzAvhn1qSssg/mvZsSMSgf7d\n8S1H6PB1S534DUEqU5a20R/sTyF/AjPJ3cg2n4JhN/UsflZoKFR85xWQ8qNx\nr2l9Py/8U4Q4NHR4kxmi0y+TSiQXN4oq5VfYRi2pp7KobcuFMS3vQ+6x1mWA\n9pAj694e0Kwle3P9ptZ33u5qlFi8HYDaSMuGOUsd/b8+XbqHEss/yIK4RW/W\nGM85ORXRwAzo8hfO249UK+BkTW94nlBkiywh930gmY/jHLCkDCHmBg35HHCI\nEOy9fpYEEhbCUDrxhjEWtAOXStpu6p6gu9Jc8mHVzJntXxI45lqDt/Jkl+fb\nIuRe/q9qeKHgYId6iO3nBunZrqqoah8lTnJEpODSSB+UvOkyCZbA3cmcuAY6\nW9xsUq8oqG4vf2NRIrFzeJJnLBSArwnNKanumynpgYr22bemq5qZLTf2Ea+y\nP68nhBNzSiwXky94kP+vLXBjObJ3nM3RQPXpryB5OQgvfREMo9hnxULu/JX1\nNxU791e65HQjiNayRfxWjAr+cWwmuZYi9KT0RDUmKEfXTkd870ZDf2W4Zt/I\nygWC\r\n=R3oQ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"362af34ce663963a932b63db93d586ee5224cffa","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-05T09:24:31.695Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.66","@balena/jellyfish-metrics":"^1.0.341","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.9","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.1.0","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.6.2_1630833987113_0.66801913504335","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.6.3-renovate-internal-patch-804c305874383f46021282ed53c1a536390db3b9":{"name":"@balena/jellyfish-core","version":"5.6.3-renovate-internal-patch-804c305874383f46021282ed53c1a536390db3b9","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.6.3-renovate-internal-patch-804c305874383f46021282ed53c1a536390db3b9","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2a449721b550f04b3d56f301959e28f30bf58acc","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.6.3-renovate-internal-patch-804c305874383f46021282ed53c1a536390db3b9.tgz","fileCount":208,"integrity":"sha512-JM3WDXhatcP/Wufr0FSsguf+FD8JPeaqCB5BlAxkiFktA/EpLXQu+a7K2tYk4wtK1H4DmrGhEKNwHXebt0wFAA==","signatures":[{"sig":"MEUCIBcmU9ySQKBNbDPRl76DLG5GeXGEOtdJxuDUy6xIpxX1AiEA30KDjoVR8SWa+uBs0LOgu+/W8USeLSSzmqMUyue96qY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":759104,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhNI5QCRA9TVsSAnZWagAAqVkP/1s+7hAnaJU4JHftrU7w\n7PRAJI1H3MO3NLzPowqKNBP7eK76kFVsTPmM2SCQhuR7Mi28jT1j4DOpLsaO\nEKLoilfEt54IIS0c2FrJyERyv8owTwWRmsVr5i63XrfIIH3xlRQyHiFr1aQ6\ndpaPZngMTSyGdn1ss4V95/InMBNzS/4M5oWziPOMii8yCtLX2gmRUYO8WmUE\nnjyIE2DHXtOu59N19+OghTIT7ZFgr9xd4euPPk0D57f2IO2HsFPOfWRHUZwX\nBe0tXUmYppFi/8tIwREyLtv0yclFf9KaLsspkS/tFCby5i2MSa8tEXMuZEei\nmEUfilvmc1LhicnQSF/i+2tSkop45eo1sRxgQ7Y0PwkZs37Hb64ilNhmd24g\ngSQUEHUU+MZwsN9WjF7JgdRmljTHeb+BJpEn17coaxd19F+9YBDVAjS1heEq\nqS8EMotav+1Nj9GMFHS5B3nwJIZtu3SsQ30doLdKxBQj27r1eaCtIbKQ7n7C\n+eBmwskI/RVkO7hkTeK6RW0k/YAJ6HaWBphhRuAa5/hrXt37E9GrOhT9vu1c\nOloHSwkShPlrINne8RO51HNYy0cU2BJ6Q5lQ7wa6wVGtIPN8zg5o1ec745rH\n/R3TEAYRU/sJ8hVhVkTKN1mHtES0to5GCF14E7hB2KkbeuaRaM0aHrUemICz\nVHSh\r\n=PbXa\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"804c305874383f46021282ed53c1a536390db3b9","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-05T09:28:12.225Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.67","@balena/jellyfish-metrics":"^1.0.341","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.9","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.1.0","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.6.3-renovate-internal-patch-804c305874383f46021282ed53c1a536390db3b9_1630834256145_0.7083767733265947","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.6.3-renovate-major-external-major-6f1056035b67f32f9d616b676813add893209091":{"name":"@balena/jellyfish-core","version":"5.6.3-renovate-major-external-major-6f1056035b67f32f9d616b676813add893209091","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.6.3-renovate-major-external-major-6f1056035b67f32f9d616b676813add893209091","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8c38479f32a3d81c248fbde3b5a69d66e7f0df35","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.6.3-renovate-major-external-major-6f1056035b67f32f9d616b676813add893209091.tgz","fileCount":208,"integrity":"sha512-TgvK8/dEjz9Fr+IBCAIGhcrqJ6tzeLS+EdUdGS0dxRr6t1hIyy/h4Wzt2r65h9ZdRGWxoXKTqPEOknFRpQyGrw==","signatures":[{"sig":"MEQCIF5NdD1+tBBVv4bzs3Crh0ZiHeGcH2hQEbwk8B/gxBIwAiBatPnU+l93626AV4sZ94WyxYgnPwQVcEzRrDcv1oxjCw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":759077,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhNI8ECRA9TVsSAnZWagAA9mMP/jBVslYGvbZGve+6EC79\nK5jVMhS0isXw1g1gqjZe0rNLR8rEKy7jIrhzRmXECHDFcwhw2ThmO0rb1AV1\nbA6EL1Wnejiw/F29zn9dhC2Q4rojsusTq1OqZlp55ksAmYz6uqfagWGlAyFr\nDfu2QVzkviYYvFKNeuq965OwtT/4hyXOtVjUGKWHfKwR8gcterOFmKwwJHgz\noOzs+phLwgCGm3mlz8a6pCHicqBaKcNGAw/Kj8svj0gfG1RQ0ST7xtzZlfLK\nSi8dcShkvNaO+/hYejGkkB8YBwT1woirZ6JSzn6GZMH0jKAQNKSHgk44gOOS\nKfrjgQ1/0uh7qtv0lPd7ZNzSgrondw4pj9/pQYQ1A92o6ty6kEU3lsMa2ycB\nk2iZ88h8pjxCmh6iutQkJP5svlNURsuYv4sliDa353n84SIoWd8nMq/26C47\nGjQC78SjVyVj4ncQ2OIzmgNUveiXSzF4dsoAnITVFxRDrJg5+Wj0sL7Yzg8R\nJwI3N/68KRegWbTLLr0HyvYrIPSZnRrOOSpEqL/yPzpJhJWvY9euld0WTXpF\nipSIOzVhaBK6Dr1EpBEE7DKHCI7e0V43CscipUqTWc1TyVj7wPjczIXZjJs7\ncAgVmCJtefAktrJ+pWKLSThZiHvgxP4Ib7KNhe2NlrOPMofnvzZuicbHl3US\nb/ll\r\n=tYyv\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6f1056035b67f32f9d616b676813add893209091","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-05T09:30:12.256Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.66","@balena/jellyfish-metrics":"^1.0.341","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.1.0","deplint":"^1.1.3","ts-jest":"^27.0.5","typedoc":"^0.21.9","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^27.0.1","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.1.0","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.6.3-renovate-major-external-major-6f1056035b67f32f9d616b676813add893209091_1630834436616_0.9648207924994168","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.6.3":{"name":"@balena/jellyfish-core","version":"5.6.3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.6.3","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"157513bbeea6cf3089023f2ff3d6c15b76757466","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.6.3.tgz","fileCount":208,"integrity":"sha512-BKL6P2F3W2gh0y7SxzursxNR5sovsNdKpaSHhK0SRQjPySE3zs1EwOxvu0I9CLt+LwiBreOIkeAN2tIxnOYQRA==","signatures":[{"sig":"MEQCIC53KeqwyBTRzlT2QfGruuQsa2AxKRqSBghyuzzlQvaNAiAaOZGxei+4lOh7Y+CQYnXTLa/wVKK6xPNjVtSyLgyRzg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":759039,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhNJBWCRA9TVsSAnZWagAArEgP/jrU/TY4JJRWjqlSBgfN\nHLKalGh6BoWY/ROU52E8tFD4fz38i2uNziYMTnuX59yTGpBbb6ce4ngmHfLU\n5hESCcZUEapGXx8UVgcA1rem5/ko5DLDBGflNSrAsY6nAakbu5GJScxPBMBV\naor5By8oGf2zZw39wHB5DHTML66yaLYIg6iLouXXBq2N79FUAsIO9WqKzWBv\nAKq6L4tUVkT1mTZ44CSgp1bI3vz8ND8Fp9o8WpsHB32jrqf5Xg6LWSP8GmX7\n9nS78BOGEv38+MB8niZyUHhlTZsra7olCk9zOgN0dXjt9dFP+okuN5fyAqff\np/ebGjrHJV3MgC5mBCe0D9MJqO0cGE5SrfBNY3/6CnodzwtsPWvpRzZWlfRo\nEJ2NJhDPt/gQVgrL//PqGuRwPWV1fMc7zithjiLkTormb/Ik4TbZmwLglxSm\nb+Vzm1Al41EoEwCU0FECBSINeqOUAf3kgO/GeTXW8z1XQYuxIsRMU1yQC9fS\ndS5TWlQPcAGdrD7hPq8is0aYmQg/wO5BfVA0oR8tty9nYa9CUkhFrpLddU22\nkAc4a7mqBdlwepB1d9vHJv+OPFJQPHP0QVH5o72lSvRGRlRdK/mj8Pz1sAoI\n9MqoskPeVbIwzwXI0Qx8YeV0t0O0WOnFHJAAMbCuxGgpW5c68Zx4qSYnRE8K\nQQEA\r\n=gqpc\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8b660d7b2962229deb9f8c6b75f122ef4be0e47c","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-05T09:37:24.022Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.67","@balena/jellyfish-metrics":"^1.0.341","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.9","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.1.0","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.6.3_1630834774191_0.9978100368050029","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.6.4-renovate-major-external-major-7c17dce29fa933d9ac6715c07578a252517a2df4":{"name":"@balena/jellyfish-core","version":"5.6.4-renovate-major-external-major-7c17dce29fa933d9ac6715c07578a252517a2df4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.6.4-renovate-major-external-major-7c17dce29fa933d9ac6715c07578a252517a2df4","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e20194f6b2816a8fcddc32830517d520c7991115","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.6.4-renovate-major-external-major-7c17dce29fa933d9ac6715c07578a252517a2df4.tgz","fileCount":208,"integrity":"sha512-CISxo++bs5rhzhaxBimhYTTgRwVcMry4u3qogex+zVMI1ZZQNFHNxmOJTWl6mmJfNwNw5XbYnl4evWgyJ9M2WA==","signatures":[{"sig":"MEUCIEmNSOdckLZzRJ/Y/2K+9KWGvDn4rSCzu6wR9Qa47p2nAiEA1S50xHdvqqaDlY5MwL4C7k3ZNJZWvoxQ2KkaGK3E2JE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":759179,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhNJEmCRA9TVsSAnZWagAATTIP/jsAvp45+T+s0UypUuiz\nGVb7HdILKm7n3x0zCCIrIlJEOrIATouuPpRLlOhlDibYzTtfIJk+IwcjPa0k\nx5kTt4JevsTWVypouNzHjX3v86uXCJdZVodOu8CilqyfZqBYHpFFYvgcLLX9\nrEBkr5PXLEgai1yFItTY3fKJkHR5hpi7mzVFO5eX0XeuTy+KnjQOyl65KzRn\nSYp+wOhh2zm6O8j5jjZPk2pBGWzlTQvI6WDWl0JAO0NnJDP10xk/DugBho4R\nue0RPhek6dI5ttr3gVGLjgVdSVNH4N/tmRsjOpJu/DmTLnyzUBDzj/HaS7tX\nxgk9MGAXb52WNk9IhXYQazxdihpgjDfz6Y8C1K8MQSiwY13qr45Y+ttbtH2l\neAlzBgpv1KovxjFrDVb4z41WNPu4nQzHj3L5zCHTyfzQckK5W5MhpHzjtBXj\nLOdHph5/BCejxSbUNj0haGClbRzBxCD8AsaoiRqJGb8H0ZkSx4fzUZov7EYG\nS8UJYkDg6eqCRHNc35NGazz/012XvehHKD2C1yzrWk747IA+XPjgGJevyOco\nd81oxMOTm99MiEzYkyCJ/n+C1mOpLYTMfumzziHmSazGSOMj4G1ho7juKZlV\ns0iEOE5+dkpR5h5kKoA4OBY4yGD9CVxQnEcML1eGhmrR40pFXjjAyYN1lBKh\nOxzb\r\n=ahMZ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7c17dce29fa933d9ac6715c07578a252517a2df4","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-05T09:38:56.835Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.67","@balena/jellyfish-metrics":"^1.0.341","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.1.0","deplint":"^1.1.3","ts-jest":"^27.0.5","typedoc":"^0.21.9","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^27.0.1","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.1.0","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.6.4-renovate-major-external-major-7c17dce29fa933d9ac6715c07578a252517a2df4_1630834982248_0.6175236026690836","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.7.0-use-links2-01961cf33f80efb468b34fd59c62cf99d9b24582":{"name":"@balena/jellyfish-core","version":"5.7.0-use-links2-01961cf33f80efb468b34fd59c62cf99d9b24582","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.7.0-use-links2-01961cf33f80efb468b34fd59c62cf99d9b24582","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8c82fa4e7c8ff15e3f5fc64f13e7fe3e163fc99c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.7.0-use-links2-01961cf33f80efb468b34fd59c62cf99d9b24582.tgz","fileCount":208,"integrity":"sha512-ibPCEXdqRmnw64dvYA+ti6sVaWargsB4VzyONm2I2UgqiqtHuHo2jkmI3GpXIEgQOE26m9tQr369kGPtyym9vw==","signatures":[{"sig":"MEYCIQDgQME0S4g52nKBK9BzUn64dXBK1G+sMfYPPL5eeUOmbwIhAOS1X2U/lYfUlpIK7m7qZii4SAD/RCy2aeCPIBN2+uKY","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":758857,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhNjnZCRA9TVsSAnZWagAA/8YQAIk3ALCtL+1t9njXpmsI\nsOrZFoNtaqOZmY4I89A7Eu1ijd+woBU+ShcA59xYcpNGGucu/B1hvO43mCl1\nwttLy4RCpExW65SjLqajUOMsBuJUmndsXW1idMkqfukw6Vhb6jDx7hiMq15t\ngd/0NKl4gwEe1VliNnHr0MyjQk5HbAHH7PRl9KYUKE6HS2DIzytvjyCXdCfE\ni09IQXr3elqdzRp/6lv/03UI0ZMQWBAgjkZsTVc2kIDlCgoxRn9lQ9jkMIxF\nn/s58eU3Em8sjI6jqoOYYn5oU3twJcu8bJRaXrqzJYVv5nZQUI3e76TV8hbG\n/rNEt1oA19JXkZzQhc6RIJDSwGku8pdRqtqyRuNFnIDS+29nvmKhPvXzE3dv\nthXSZwZ9CZgq8uKXl+K5ga6Y+aO0DZkxcaBGS7YPvXhJGb56Hw6q61mQBwOg\nSjmE5i+jlRgSsXxWFpvun9UrNxe74soawjSp+Fea2HGAZzPBfWD2cTTBELme\n5gP/LRJuiU2mBFO98F2Llugqb8+hC+owBdafVa4zv5btuy+mvk4iyNEodAoz\n14qqv+s6huZ/y/uNJ8nhc5vo5nTX3JvA8ljLTrCYaQm3gQQHVyt1I1z9HPNm\niQvrULk2hEJ5vRjF7sacnmh2ycZqKZ6009WLz8JBZ19TBXfRJxke+ay1YFPD\norjo\r\n=rY7Z\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"01961cf33f80efb468b34fd59c62cf99d9b24582","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-06T15:52:01.738Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.67","@balena/jellyfish-metrics":"^1.0.341","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.9","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.1.0","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.7.0-use-links2-01961cf33f80efb468b34fd59c62cf99d9b24582_1630943705627_0.8327069667903315","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.7.0":{"name":"@balena/jellyfish-core","version":"5.7.0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.7.0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f28cf40d3cd8f30ddf984340457e96247c958926","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.7.0.tgz","fileCount":208,"integrity":"sha512-Jv+JoaPiNBhJE/MdCDHT4SjcjXCO02oYaqaT2zQOeOssbc8yENKk3zcKFK1xBUJptLrweBmykXhN0CJGXHqN2g==","signatures":[{"sig":"MEUCIQC13ay00ew7diPRvtJeF7+a8AwqfCM6dElounxSySqAdQIgIn5cHj8e90qkxg7aYKRoPWEWjVgWpm0isw9RxrbPmqs=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":758805,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhNjvcCRA9TVsSAnZWagAA4CcP+waaKlpyF80dYJhsrdX5\nz+ugBEBQdgfuyq6p2H9xEEmdTh1WlDz09Dofel8SsGPPLHKu0q1eQUJVU0hc\nqxA5pOeWzB6bLSmluF0vPD4APoEyWwdNJ8rTLLoPBQ/GPuP+8zLVvGQA2n4F\nuN6gy+JoWs08wumQ9wljsCktKZt7FTCrkyq8knWvpfIZs5iEHQuzO5oF0lzM\nqsJRw/MlyK6/EmpYHzrhsLrLd4kMMHtgjdz9QFoFwrRmpwA0y1SZTxVnx/EJ\ns8zfffOufToZpK6tNM/zlV2bQ3Ua8ODNZE8xubhBiE4yLVccFv2N37GdC/5a\nTLt99Ec8ZxNUCEI+YsXO6kMG4BBCf7kXvDBvWL7HxZh3yR2RLWhwGXfPM6Lo\nW2mO2fQcvd1bm2XJoVsuhni7XB731XRmiwLr4m0oYOb+igKPTfKoQCBLqSOz\nAejA2IQv2yikFwJv/CAqY9x+5XxfWsSBS+a1bWu22VX8MJyZDGuyOY0SCDXd\nMH1fEbEZaQSr/WktPzqxrSU80VwRThkVJFAIf+Ua+knsQoKG6JzQUYd3O5sw\nWC4cMRAEZdcQRN2EhZgqnc+F7JAhVmR8vXOAT/WPXRt07R1t7i3fA6MQAY6d\n8Xa7ZVRuJv32TbVgcmnUmJ5soixWRuTWtNBaFot3OBoc3+6sHMt8oKFrpL4n\n16wJ\r\n=PF+1\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c5bdafb90f8f1d2358d406c69e4302db08eb387b","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-06T16:01:31.900Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.67","@balena/jellyfish-metrics":"^1.0.341","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.9","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.1.0","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.7.0_1630944220505_0.030871373106739153","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.6.4-lucianbuzzo-query-tests-dce31a8f19262c1585396a407c79d11f84a4aa94":{"name":"@balena/jellyfish-core","version":"5.6.4-lucianbuzzo-query-tests-dce31a8f19262c1585396a407c79d11f84a4aa94","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.6.4-lucianbuzzo-query-tests-dce31a8f19262c1585396a407c79d11f84a4aa94","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e2a3d98d01bd5c2285b66abab6a60b5b6920a8ac","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.6.4-lucianbuzzo-query-tests-dce31a8f19262c1585396a407c79d11f84a4aa94.tgz","fileCount":208,"integrity":"sha512-ogiRMKV4SL5jIai4GLQA+qhu4LfWWdrH1oaeDCEZoEBx/EbQcQRrCh82LzUKzSAB5JYglCIdNKbjUgUVy2D+Sw==","signatures":[{"sig":"MEUCIQCY1qmAj260bx8e5Xxg3CI7/OiGRG07zHBPE+Zh7YhrhwIgWHRyx+q9x2vfxtm4RzPNw5r+jW7soPKjZG7wHzVmfxI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":759263,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhNkQ2CRA9TVsSAnZWagAANCwQAJNP7mYTETjQcAH6NEYj\nuQeTTGhZ/8xPp2dQHV3TWZ3ohR8Ig/jUWw+fPirxDAAqL+SAzf9Uahu+ayrM\nBCRq6xFKdP6VH3qX5Z07WMrucbKf9fGZ9TRALg3eShujRrPuBAZHmdZ29YQf\nMiKXKgxLmkVNMsnKJVAWt2toZs7GprkjFFeAXoDEkOU+o5DUwrumGo68IDe4\nnyeFoZYH3osg5/xDZn65bv44YzzQTgp09h8nxH+IN9El/xMMDlkLnp/AMUnE\nFsJmnvGeOj+ThQP8yR3XnMx0XzXf6nURyabVw1Hx1Qai/UsL/8CSJo2ShPxt\nzJ7UOn35cDgguNuVWdUxifgQ3nu1G/HLzSioFjztSCEESKCklJwnEqvgEBrT\nEkHYQe9lxA5AQYt30MhUtetGUSKEKSZKyrYirkssWfCpvxWIwnWNXp+YFM2z\n+9yuTml8/oAurBzjKB0iKRk87oZA3SCo8FEl2D9bFqn0P6GarV56QYDzJTfN\nTr2TKyvarwp3okR8jgIVxf4mE4VMf69TQjvCw/Rzq+PeezQPLTsus/CpfSyB\nvBsdDbw/Wy9bYMUVWWJe9DqVKLF44oVQvcx9+qTdTG+rODW4k97LWKFrA9U2\npNPU6hex+1WvJL1gwz0ZZXqDhr/q6tfvUAPojtL3+iANRjKmQjhdoxKD0Boq\nxAUv\r\n=//Dw\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"dce31a8f19262c1585396a407c79d11f84a4aa94","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-06T16:35:21.906Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.67","@balena/jellyfish-metrics":"^1.0.341","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.9","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.1.0","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.6.4-lucianbuzzo-query-tests-dce31a8f19262c1585396a407c79d11f84a4aa94_1630946358778_0.1360211299366696","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.7.1-lucianbuzzo-query-tests-bb793d5f85912fe4ac40b65092f16987b09b2a04":{"name":"@balena/jellyfish-core","version":"5.7.1-lucianbuzzo-query-tests-bb793d5f85912fe4ac40b65092f16987b09b2a04","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.7.1-lucianbuzzo-query-tests-bb793d5f85912fe4ac40b65092f16987b09b2a04","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d76a56191846cf3bd87d7c78efa44f17e2a31ca2","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.7.1-lucianbuzzo-query-tests-bb793d5f85912fe4ac40b65092f16987b09b2a04.tgz","fileCount":208,"integrity":"sha512-oMuitLeL2VaEuYE2ULwmPxR+52tURVT1wM+UcSDK+0XIKrK1I6UeAi42iWpEhw2fhb6eY/96gAzoyrmSHNYExA==","signatures":[{"sig":"MEQCICzLw9MkMc7EprALDLQdtf7c4nAKBWpYu0hPi+7+t7FiAiAdVgS4Gb/MJJYW7pF1DXiQIPBpfHqCHoDzvbVpYEZqaw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":759029,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhNk38CRA9TVsSAnZWagAArkwP/jgth+5TrH9cRDB7gf7X\nMGLVEZgXZVet6mGXsExD02EXibF/4MuktnFFayXUFWSFNTQlxxzniRejsBD8\nJQGzknHE2AHu90+svsnkrnykdNTrPhGsnqDJFm6SIGeuLX61ECLIGXHt0dOc\nttrHWS0ytCZ0foBgQ+wgH1NyHvQJSSf+fno78sHaH7HilE4W/Csvv3bCLVBS\nrOBHV/oKJDVxuz/wawqcXZD88G/TiKS/X5QnDk1JO5EPu46l2KfvyA1dfa1x\nhngBATwgVv+ALqtwQrbEVGz166CXE/xspNApeFB18KermIOGk0IyInl0uXzD\n5XtYkJ+gR6YREZnh0RKB1lkxrRFIJ9h+tAzXxwEQ9/zhqVXHtODZMna9IO7t\nFEKZA7L5MYLu4okwndRDqQx+8drPzDQb8l0WsyTGdbtgw2ktDKCUmqcNxxYi\n6PbMfJvo5/uOJLcNFopZrj98PQhHEgtvyaHPpGouCspp3ePbf2cW3QzvmUUe\nY5R/dIpzpf9Ve/DxlC8Z8Wo9uPdL0k3o0qdBYz4hUwJz44hLmRemCCv8cFau\nWEXf8jK0dkUf0EwCVSJnQ3d4BpDLik3RBMrHuLYJk/KNnp2qdZZzwD5z9COZ\nd4J7uf5Sm37eaMyI2AhZtHd9pE6Uk/u29MqMoTXmg62HOwSzU65/oQwhb55z\neXd5\r\n=kPUS\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"bb793d5f85912fe4ac40b65092f16987b09b2a04","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-06T17:17:02.418Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.67","@balena/jellyfish-metrics":"^1.0.341","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.9","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.1.0","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.7.1-lucianbuzzo-query-tests-bb793d5f85912fe4ac40b65092f16987b09b2a04_1630948860489_0.955682336380099","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.7.1":{"name":"@balena/jellyfish-core","version":"5.7.1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.7.1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"122dda09a332abb2279e16e8cc66d1c74c560e81","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.7.1.tgz","fileCount":208,"integrity":"sha512-UaaCOAnk5eK6mmobanjrgCrD6EPkqpRGCtEhIIkxCEvqkLXhcq9bS5OaZ9BmDh9zv1m9ix7zjJF94vdM3xIxSQ==","signatures":[{"sig":"MEUCIAJFuAxGZEiGKMft6+6Rk/QSmbFj7YwHtgyIiRnasbkuAiEA+ClW4gjWAjiBQJ3LRaMMDCNx9K7d7iYbmAxuJcwHGGo=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":758964,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhNk+sCRA9TVsSAnZWagAAulEP/ip1v86XV/1Whadbb1C+\nYg/Jyaqk4HAGIUnslZ+R13aQFKuO3HjrmoE6yvvwMh38Dp4EK7+xDdU0wqtd\nUwR8+ysaI0wUk2bQwp/x+zDqEthibt+HFj/EKq/2B2qrCWJ4QGYgAto9X2XW\n4qIbI8vp4g/97HRf9KSbl6sTYityME1YQC5Fv4VE0csaHYIL12U5OJbLZdy0\n7pRcJlh3dHhbz2RrK9Q5wIqybDxGFNUBuJ7RaBtvKvZ37P6k/nHriIDwfa62\nYossRzZMWO+L20KWjZ59wBZjzffpH+VlPPKJOoSr3qf+bUQJWOmEPqwREzh3\nclQ7PDRWjMEviV3ng6NlUPnTAKxOjh7tFMnxJ2Ad9mSD/d9FiRereEgyIurW\ndY8i4xfjcAqbzpDkDrlui4ec0qMlGF26POwJf8injzFNXFtIb4sFqmuUo0pi\nqi+uUD/qc7r+PnlZ1LY5qWqwDkGHuQgjiGctXTltyy/4qf6OberPkwcIORG6\nW1RB+EQAr01k+2QIdhd3VBaKQGQVEL+tlJRzbo/z8UeteFUuA7ImgkopDMvj\nv1snIy4avkz/BE1CHtzn6GfcqJG+Fqjc0ImBEDp/GNoKaGArepM4xeAUJ2gX\n03zA6JsXB7Fl99A26f3MsBv5KJkKwTARH7t3jITWO2oSK5dUOfTIY2dQHvFH\n8B3t\r\n=wnkv\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"df72fe26f20be19ff2c6ed53b4f8ab37990ddca9","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-06T17:26:21.619Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.67","@balena/jellyfish-metrics":"^1.0.341","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.9","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.1.0","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.7.1_1630949292404_0.6051683021214305","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.7.2-renovate-major-external-major-554cdc4a7ce74b24e3d968aa41c9ed35182155ef":{"name":"@balena/jellyfish-core","version":"5.7.2-renovate-major-external-major-554cdc4a7ce74b24e3d968aa41c9ed35182155ef","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.7.2-renovate-major-external-major-554cdc4a7ce74b24e3d968aa41c9ed35182155ef","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2f36c2ed68f818ae4622d83ffa59b84ad0a73888","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.7.2-renovate-major-external-major-554cdc4a7ce74b24e3d968aa41c9ed35182155ef.tgz","fileCount":208,"integrity":"sha512-SQSufCjgV2qEwm6vUj8GGP1QBSWCHbo3AZ0kFjnzzYWjXtTf74Hsw1MGmndOY0ecZhi4i+PkVjd3EsFfgJI0+A==","signatures":[{"sig":"MEYCIQCkvU5P6hS1SQJaFYtxCVn3bqlKXBXZJ+d2Lql4xJVZWAIhAIbKTXveDpxQe50YE2fiPYVv0ROCl/AamvBM03l6ME87","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":759104,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhNlFSCRA9TVsSAnZWagAAnL8QAJqwAnf+a8gwGt33Rx2H\nxd4VxVE7GEjPekzLSAIUNDok6OLYE1mSuBVNAoifQ5GjkTT2wDweBBQLoY3E\nE4ICnTrLvTCgSE42isz5LgCVLzwD4rbMtYXvmZ2qUXWm0pH/8swcflAAOTvR\nnosEo5SMEzuLVhCZezA4mQjvroN5MZlX4RDgz5CjjqY3yDGXRNC8GO+wUOPD\n1kIGGZyOA/dBQ35273rHTCVAp2Ro5VVHVZSqBUtElE35+B0FJ4L3V2QiPngl\nwA49lp7ZFX+l/JlcOvtweH6Y7h8xTE/doTRkGqOaGOiZ6k9HU4s6tFRrT9JV\nSyviqJZ4meyCPud6Gvj9yBRXk4ZzJQ0OYrhOJXDJiLuUI0v4XFID3BG1nBCE\nFUOL6hI72yj+Rep5OWiia8TpiGBbdlPtQKRDgP/+b9VwneEz6bl90dEf0cI6\nnL+oUC5mt9yb3NyCLOO6bh5HHa2lDZ4dJbYFjWTM27V8kvvTrFdmhag8DHh4\nopzITLyST48ng+0YCVvhNH7EBCzL+Lgqt1UR+0/DG1EmfM/lS5qKYMz2/1si\niK0vbJQbX1yfHX+kgFANii16lPF8ZoqQtRRdxDx1P5nboWKnYlKDk+hUmVHw\nqlsZE+RcGeCWqqqEwYmoRIRMl4ujO/tKBCCsBxgz6SlinQQID7GorW3HF+Ln\n54k3\r\n=uOxQ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"554cdc4a7ce74b24e3d968aa41c9ed35182155ef","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-06T17:32:21.862Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.67","@balena/jellyfish-metrics":"^1.0.341","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.1.0","deplint":"^1.1.3","ts-jest":"^27.0.5","typedoc":"^0.21.9","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^27.0.1","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.1.0","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.7.2-renovate-major-external-major-554cdc4a7ce74b24e3d968aa41c9ed35182155ef_1630949714705_0.7321946630257099","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.7.2-dont-use-links-521ea2f80c70b86971e855647d4be91c6d15c55c":{"name":"@balena/jellyfish-core","version":"5.7.2-dont-use-links-521ea2f80c70b86971e855647d4be91c6d15c55c","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.7.2-dont-use-links-521ea2f80c70b86971e855647d4be91c6d15c55c","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6d2eaf9503a5f7ceff3c8614d991dcb7bccf2360","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.7.2-dont-use-links-521ea2f80c70b86971e855647d4be91c6d15c55c.tgz","fileCount":208,"integrity":"sha512-j6X6U1SQ3OeuFUhv0GOkSujAGH/tRY/tKKxmJ9YXyEX1vYTtkRDNyq5uTbfU7cVUMIiSasZ31OTjpCwf6yncxA==","signatures":[{"sig":"MEYCIQDW24EBQJBGX4gKiJiszGv16UIpsmSJ3YE5SStodbJSwgIhALTSKYQ1y0/RYI2G8GUrS4LN9hMZrInODF5nQvmecsxB","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":759423,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhNogXCRA9TVsSAnZWagAA3wcP/1R+0ivW9laRAVIYMQ+3\n1jIT+sxuhafyrakyYoGr+XKoLUWyrXAcidSjEDjv3JEfPVx8g+5iD9gmbZH0\nCMyY6nmU3/MasegnBXunv3jYqDpz0WNlVBBJaaNH53uqnWAlupNI8nMwCSXs\nUwKvgI+0G9uzzV6qxLZD6z/dnGYjnsDgPFuzgJPLxZWGegpEZgpZ+5CChaoT\ntbxiEUNlHqWWjejXeb2VZjJ4urUOK+wX3Tr/qUR9kSOjrNRY0V2l9BKnEAE2\nWIJu7gNCO5piAh6FepMuZniXGibGs8kvzBWjTXyI2hELkWGhCqNcp5M5XeU5\n0iBHAEJeIghGAv0Gp2gL7rnOJV2f5QC5TiIruQmLMTVLbg5MnnX02bdKKbHL\n1vXJZCLgNa+jBuqifVL+vAYJ5nLcFsb+x4S55c8RMDL648a4cYaiTbdUhYSA\nhHsLFJOTdwn+oVRzSvEQ+Jx1nXKQXxEnVgC09zcVmLfLVf+3c4nGG0rDvlx8\n5l2/KCXSPGpqbDLri8wYDVrR9UBRoftSOPVLgCpaUrAsJ2RxdVdZ7J4Q80Sy\nQZ1H1MtEeCYMRwH99UyNc905LCh3QUdwKAFgRrdSYubcw1u0HQFMbg8Jk8FF\nMEbBDhAOJq0T1FGzWJ1xoK53MY7572y3YBPB4bEqC3ynHbQ/dmjLAbWt5bM3\neVRn\r\n=Cram\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"521ea2f80c70b86971e855647d4be91c6d15c55c","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-06T21:24:59.517Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.67","@balena/jellyfish-metrics":"^1.0.341","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.9","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.1.0","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.7.2-dont-use-links-521ea2f80c70b86971e855647d4be91c6d15c55c_1630963735233_0.2546812428155325","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.7.2":{"name":"@balena/jellyfish-core","version":"5.7.2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.7.2","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"da001b30132c3e44e37ff3aaefe5f31dcb2c0c7b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.7.2.tgz","fileCount":208,"integrity":"sha512-zmAsi3inG0HT6jDyiYl/3/oJVdHkIQQ2ymJcqvC9AiI6Rm3qKIsTU9MP4ay+3UIgnVjgxZa/IokF2T3aKVuzKA==","signatures":[{"sig":"MEUCIHjVr2jCi7Lygbc0YzoKX8SrCmN6pb9vfaO2mahUhRVUAiEAuXFGu6BQUqeNi24ZqQurce/g5WIukqbXcw6ZG2HYhlc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":759367,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhNonYCRA9TVsSAnZWagAALTkP/jGLUlw7tYl410VAg9jy\n3ta2MIDXlyv9EBk4+8vF/aTnO70E2HvWj822C7WQOegqiIWq7vXTBH8wqX2k\nBekJpaTc9tykluq64YviPy9XpadIJaG8aLaE83rcuM7zDCtxDFEpaujK2KFd\npPrv41FqPtok3xYMyMQwf9XDqJ5VaJuXtIjqx9ibWvTMFyOfvfNjjETdGheU\n1FpFw0Gb8X4gynPrebEKyuKR6xQymNYtVXZqYr8+6DkusfB8S3BDeaqsz1Ir\nfl4UJ5FLrtHsOxzK/3LcYyrUCEon6XU0nSxAwKqsAGyekPn5pG4odR1G9PfI\nLO8sVY+DlCbDxQslB8OPGuIt6lrT6jVJvv5RTrbSdn5HDs7QGzA5YWbDcr0f\nNfyotEuFIwyEp1BT1pJxu3TxqWGqmDzKbC06LVpOXdIX54yTvaLcdi024TxF\nCIQ7SKNS04ocKkqq5t7VYqjhwMc36CU3fJYt37sIK3T2NPYaLDs1LJ5G98Ei\naBDrFo94JnTkw0yXIpeaEpGVp1mgabELXObXEL2xWJDCaf+brZaTQIW6GOIj\nyE/PaCpcd0+Ze82ih1iwNcNJ1RpBvchNxvQBDXERlZmpIF1IMDAEAtHP6yps\n/OBS1cx42ADFaSfyOyT9oD5i0I+1igwFoEcAFXcE0qZ/71EtV3W8N/VoyoTE\nFgDc\r\n=u+A4\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b0a374a1785ccf488e5be08979c06472a40b17ac","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-06T21:34:08.270Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.67","@balena/jellyfish-metrics":"^1.0.341","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.9","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.1.0","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.7.2_1630964184215_0.513030208860668","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.7.3-renovate-major-external-major-141d9813a083891d83f3d506eb1a9cac25a0e204":{"name":"@balena/jellyfish-core","version":"5.7.3-renovate-major-external-major-141d9813a083891d83f3d506eb1a9cac25a0e204","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.7.3-renovate-major-external-major-141d9813a083891d83f3d506eb1a9cac25a0e204","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"26f0d7e6baca106484a84ead3cd0f52628164d88","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.7.3-renovate-major-external-major-141d9813a083891d83f3d506eb1a9cac25a0e204.tgz","fileCount":208,"integrity":"sha512-il5LpZzOHA27OqB29yEh/AMNIaIuvXs5Itb1C0oXcB435rkJ5HplvSRErWXDh0DPp5Kh/I7CgADrknYRELogPQ==","signatures":[{"sig":"MEUCIG9YsufQFbKjdjZbstGff53UH4KTPS6U0jJZ+jAnWZTkAiEAmm/pkaXcMBT6RkrBTtaRen4M0RbCHH9/DO2898DBsTk=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":759507,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhNos8CRA9TVsSAnZWagAAEFwP/A1vN2m9y/vMj6vnNUAh\nwSYrpu2GFkdDLE+hKx7c90O12f5gwbUI0PPLbl/h2aXrSIma+tN0qNQyW8+B\nFX7j+2pIC7s+ia7R+93uiXWALwPjhyta2XMVugrtRAgbLyMy2Oi/eh5sysX3\nvW5ej8e2L7KxOYMtsnSn0T21TQROAglurH0p1V9yH4k6Y2UdQhiEte++zTZ7\nY3unCQQBEv0Yq3hahqfuYEy/9wRPjq5uwXbYZeHLqVUPd+WMawAhiXeZBuSJ\nLLIs+TLEvhmlbXDWV24Eeyif5F82uumDKd+0+1xN18j3CUowKyzLekbyc9na\nOc5sfaM4KSfGrsaBoi78FQ+I0emGnwltT/1mR5hjiP0hj6Wh087RmIRXUN3N\n8SRQf6y4kFJ9np9VKZFGcOJpnw1UZ67xDfbY6r65sVcSxab+0/yZsOgQpjQa\njSF2my6BZZXi9/w/Ks9ZxFd1UoaiNa41Tw6nW5dMNdnI95BgKyhVnLpRVmom\nU46mBEmMISjRYmXKEB+hvaOCJS2EeWGu+0o1FjPC/sHXblYoxNi0q6l4J+Oa\nWzRNTyCfc2fQ39ePxtGxnh+519jpIGyochEgaaBApICm/9Wv2r39KYEN5z25\nyZvHlVdPiga1APRMaGIzs4ajXTkvWVo4G+xflQgn/ftJmgzxEhUQ1MLTrokg\nBRWI\r\n=JD68\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"141d9813a083891d83f3d506eb1a9cac25a0e204","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-06T21:39:04.362Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.67","@balena/jellyfish-metrics":"^1.0.341","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.1.0","deplint":"^1.1.3","ts-jest":"^27.0.5","typedoc":"^0.21.9","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^27.0.1","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.1.0","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.7.3-renovate-major-external-major-141d9813a083891d83f3d506eb1a9cac25a0e204_1630964540194_0.5742143774317461","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.7.3-lucianbuzzo-insertcard-types-9b91bae4f9eeca814d70ae130fb470c81b8279de":{"name":"@balena/jellyfish-core","version":"5.7.3-lucianbuzzo-insertcard-types-9b91bae4f9eeca814d70ae130fb470c81b8279de","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.7.3-lucianbuzzo-insertcard-types-9b91bae4f9eeca814d70ae130fb470c81b8279de","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"62a1df3d8ae836b38ec46418b282eba4847630bd","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.7.3-lucianbuzzo-insertcard-types-9b91bae4f9eeca814d70ae130fb470c81b8279de.tgz","fileCount":208,"integrity":"sha512-wdDcTme3GS2wDJ04QxVK3VsJImthrr/d13LOGGHby25ahTXbMFEQSKauR1s08vzT49MMNp07VllGzdlb0FVAEw==","signatures":[{"sig":"MEYCIQCEeE4DUTVCIvwE82+oPlx9y0E9qJVqEmszV8FazodQawIhALmXZnXHHPRqHbaqkbHrlWy1A64FJUuw3B3NRXE+la/T","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":759495,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhNytLCRA9TVsSAnZWagAAMCkP/iPMaYNq5w+tFFHy9Jgf\nEayFAAArCVnd3vx6i8y9d94Ronpjgw2izt0TVCcaD7Zk/Rg8rP6jZrIahWt6\nijVhPMxfBHOBAxRjGQomBRLKl3g/RFRKizyt69MNQ+iYUuc6BmLvsE1GlGPE\nWLLmPi1bjgk337P4ocTXY+uiBF4RwPHgAk96hgSdKLaqCHM6o5iX5/W1aaY+\nB+6Dkc7xbJ+7CQBOseXT65Fp9WMnp/5cky6mQB0KmC7QrOAE4fUXNCFx1ki0\nHnA3MHZiWxooLVba/R1+qAHCWLBfT0F/IDJYyPGuRNtJDF5d++p8f8cxJDD/\n5eUCgv296jJVWTPAAhA2VX/Jwy7YvIqaAAFJewOGjYQaXQG+7gTTOLN/xPpj\n+hzRoiIMVywy7oOlGj0Z9R5eX/t6O0f8uhjcDapBG9Df1tWrwYgCCkx90Ez6\ng8/zQFucbrL/HoPWnNY4FSGcO/soQxC31w08z2DOV/vfCd9RJ97ry4UdIu7i\n4F/JQHQMAIzSMrzGrd4grCvHKfgeQokne7ksl8O0Oyy15YIpqKt8xTUpiHGV\np/dT8tAzNxdyn6lxr7fPKZZCdlLEUea7Jmk1A8gLyhgMvoqwZ5o2zxuR2yKd\nlbM+QJD8mVC66LTNj2QN+QRlXblX1wmUtdZ73XuM894xNG2cO5GHEfSd2nGX\ntjro\r\n=C09o\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"9b91bae4f9eeca814d70ae130fb470c81b8279de","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-07T09:01:42.065Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.67","@balena/jellyfish-metrics":"^1.0.341","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.9","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.1.0","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.7.3-lucianbuzzo-insertcard-types-9b91bae4f9eeca814d70ae130fb470c81b8279de_1631005514989_0.29676135434331474","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.7.3":{"name":"@balena/jellyfish-core","version":"5.7.3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.7.3","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a97a5409646dba36519f20882ec9e71c08e0b374","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.7.3.tgz","fileCount":208,"integrity":"sha512-v41vXpMSZETzlw466aKBMxKs28iwNaI7SCCYEwqIn3rEH24AV1wKnGk7bDma2skiLDCzMWkPWwC5zG8sOJ8X8A==","signatures":[{"sig":"MEYCIQC3I3lPmxfiD5VZfTLrJK0rxiRQkOn7qExKG5NhVM7XYQIhAORqfi0rzABP/e6SQktb+pL9PguaA+y/jta/BhJoifvY","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":759425,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhNy36CRA9TVsSAnZWagAAE+AP/1A4ueWt4rLw8WXx+DgF\nU2NU+hHEH9MdRJuGyGW1iZgQ8fcuvjcCaJ7irqQZrqW/+YZxnKriJpS3NNs6\nObWb4oQ3Lyq3WEAg1iGCNARr7wgMSZD+nB6ulryrAF1yrPPA+qEl5sQ8dH35\nSDjMpT2/VqN4wZtNYHqYJyugXLtSf67HJA5XZYpxuQxIiSqo6J5TAc3M/c4Y\n6uVvjuz0ydsVSjPNwbEpwwcXZ359eYN9PGKV6w/y3ceTQgoHl9NfOXzn608O\nlTn25xPm8QdW4H5dlw90pYXTmSyZD7sUNKoIF0m7xwf2oLHOw2Ondff73ehM\nDntWVvDL/KfILPDLyg996KIUMufs4UoXOiNsSgjL2oDYRr9d4qRiCy8XqZJk\nJCV5a4JvCHmDbJ0yiKkAKSBAFVeNuhXlWsxR4cYF0hs/QhTJavSCQVlqDHpV\nARzLOjq4CGTkLeaIcAYUJm6GGZ46cxZt4y1hkqQddS3ZdJuTMZ1e8DEbAilY\nUYFEMuerMkbc0TM1EemLUx2CWDij0twHnRLdFtoyf3NlHPDWHV/wax2iNzbS\nCnlL1xTiB+Ox9zcTYO9f0CJqONoxX7xOXO291wo6NM52HXX1qz2jh4hYkToI\nBO4L29rTY0UC8ev4lagf6O7bgBxOWZnDzqr0ThlZCaT8kDs3LKxCiX8tP51s\nSgcg\r\n=DwKd\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"eceb99cab6320a8b962669b281fd9ee7a3bd54a0","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-07T09:12:57.972Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.67","@balena/jellyfish-metrics":"^1.0.341","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.9","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.1.0","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.7.3_1631006202054_0.11184265872898358","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.7.4-renovate-major-external-major-f0121da28623c772580d15b0bd7f0b54e3432333":{"name":"@balena/jellyfish-core","version":"5.7.4-renovate-major-external-major-f0121da28623c772580d15b0bd7f0b54e3432333","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.7.4-renovate-major-external-major-f0121da28623c772580d15b0bd7f0b54e3432333","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a744ee6ad4d8d8c94f0a86aac6278bd9c4fb86c5","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.7.4-renovate-major-external-major-f0121da28623c772580d15b0bd7f0b54e3432333.tgz","fileCount":208,"integrity":"sha512-qot+8K+DmjzHz+RlpqHcrinAPuCkLioUzagcxfD30LO0ezlP02H0Ms8VVurbcUOlNPOtgSglHWQGPG5MhXzHHQ==","signatures":[{"sig":"MEQCIDmk32tZwNm+7ieHcMgKGS0iUAIteN4UNUxo/6V/oxhWAiAlXbX4JWQx+BNiLI7Vc3VYmcQHCYxD/wujI87boFa4vw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":759565,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhNy8JCRA9TVsSAnZWagAAe0AP/024ZAiA6M7GkDr6Czu4\nyQuuQg5ev6x8uoxk9F86ydg+GVtLf/imM802NnDwg95rAcLsOkkiQipV0xg6\niy3UEIaTY3oC1lwVY001oEkodlq9IfZbManb3PWT7VRsYOtMNVuIyRkTHD5f\nFx35LGHk5fYKkz+XUSx1pgucdFziNXpIIlUiAzjtNw6saHjpOY+OCpoCdaav\nRRxwfs9GiMYPBZz1mCp6S75cVQlaZ3Oica1b3hHN/UjheaSLfqmPmTmYcaZX\nR4akG9QjKiJgOl/CfQC4CRopiSWCyWEpDHazmZb/Yuhup4o8KFmSeHUAzhy2\nW3pipcSREROO2jjEWQ6qu+vpyBrkuJKh67rnK9YOM4oyTQ65hN3s28kGh3pT\nqySlyaAD9jwCN7eQZeqgMfG7LaCgemKNLJOQtoOboikKhllGWX0Y2qJSEBtK\nLGYT3fwea27jvWJ0MNZvQFs+eoZwrzNpAYtbn6YL9xBG0yB/w+v8+RcyYVYB\nJWpR5sjgZFT3VMgo/QGC19iCZtAECjbuZDOd7ryfoFwzPYEIkbgalerlmF6J\nCco0hFsFozBL+boLvsAWcXKniD8CSnyav1RfNRZYErj2pBbVQf/RRDYMMEXM\n1s7AAlrdMd24pB4ebS4i2fxpRIq+jdof8hhFkAWIM+1RU3l0P1wHJXqp3XFZ\niWAk\r\n=mtrJ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f0121da28623c772580d15b0bd7f0b54e3432333","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-07T09:17:51.661Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.67","@balena/jellyfish-metrics":"^1.0.341","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.1.0","deplint":"^1.1.3","ts-jest":"^27.0.5","typedoc":"^0.21.9","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^27.0.1","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.1.0","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.7.4-renovate-major-external-major-f0121da28623c772580d15b0bd7f0b54e3432333_1631006472862_0.5057661189647078","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.7.4-renovate-major-external-major-fcb0e9d614f71faa8c83a6cbf39b0192f5782a16":{"name":"@balena/jellyfish-core","version":"5.7.4-renovate-major-external-major-fcb0e9d614f71faa8c83a6cbf39b0192f5782a16","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.7.4-renovate-major-external-major-fcb0e9d614f71faa8c83a6cbf39b0192f5782a16","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f92e8cf44d75ad547d212651aa010a1c7acb73a2","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.7.4-renovate-major-external-major-fcb0e9d614f71faa8c83a6cbf39b0192f5782a16.tgz","fileCount":208,"integrity":"sha512-E4DTpA9usybAeR4M5TbCov6WwTQCXDeEo/G32q6d/B0gQVQPmh13OLlsc9U4hlElDQoQteLUpoxZjeCBdrdEDA==","signatures":[{"sig":"MEUCIEXGtXk72Fbnr6Zp4m9eglk2Fk1LXHzNagHz/LcZtp0AAiEA6VECnMVaRtoBi+KbzrDAbE8+W/EPVqyX8nH4aLm7veU=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":759565,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhOJU9CRA9TVsSAnZWagAALIEP/RIBfljUkg7HqQgmjSXB\nn5ZMMOhOQfhapAJotLAolKlmNcGnIEtFxDUHa4CgOc0tdv9SrKlipGW46HVG\nZDjtsXKtIKDiDZQZS0GW2tGfMG7Zjrgxxs0DLGjiGp/QaS4LYAuS72n+3rb7\nyBStvIFlVmWZpKrtEIMtBkkIGDgu0CIp9tHGkE4IMLgAyhe1w6q/zS4yVehc\nkvwJaUwRvtt2oAWbJmBTDmjTnbJZ5rMTWsO7PFLU7uMP98cB2mvwZrHzjIL6\nQdyJJlh/jQjJgxv/z9hcBMWZSqdfT2aCrTyjSZzjeYt3vuz8shTKqxij3+tQ\nKfyTBnT6Uxe+alU+b1ZcgUwuLmo0YFHSCnudXdY8PyhwrkuJOPz920fY1jjT\nkvR/l79CZpMJWjFBcdCjtpJ/Mv+AHom79kM1Xdji2+M0emB7V/O4oMTyFXV7\nG8uhI+tZrhViH+LYyXtX8gDx+eRn6D2gGU10uJCAq/ng+Qy3aPOgndBFxiYO\nYZJShWcLf5JNx2BD0LIq172sy2i+572XtPZ1jeHfajnlJdsIqNrQbbzwuWMj\ne3my+atSLgwnV7Wh1cJ09hLpFCfnyq3Zt2pWt7Q107HkLcGQfHPwrtpwYhwl\ncbHVUH//mAYvV1yGRGW+RbbFYG+GC3vASGnmC+8hJgFx6/SB8ZXyO2gp0f72\nPI1x\r\n=35qc\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"fcb0e9d614f71faa8c83a6cbf39b0192f5782a16","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-08T10:46:12.581Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.67","@balena/jellyfish-metrics":"^1.0.341","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.1.1","deplint":"^1.1.3","ts-jest":"^27.0.5","typedoc":"^0.21.9","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^27.0.1","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.1.0","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.7.4-renovate-major-external-major-fcb0e9d614f71faa8c83a6cbf39b0192f5782a16_1631098173507_0.39207820718080044","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.7.4-use-links2-9242752a13ba7c8258862382659298fad271b7be":{"name":"@balena/jellyfish-core","version":"5.7.4-use-links2-9242752a13ba7c8258862382659298fad271b7be","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.7.4-use-links2-9242752a13ba7c8258862382659298fad271b7be","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c1a4fc533cae2e405690614d017ac3dd7912fde8","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.7.4-use-links2-9242752a13ba7c8258862382659298fad271b7be.tgz","fileCount":208,"integrity":"sha512-4FEFFVr5cuOmBd0PGYjH0KeSPWb3u/YJQ48qJYtv8bBOUF+wR2xDATG0MVR0udXp7ip5TbHIEmvAGsMX/Zf2sg==","signatures":[{"sig":"MEYCIQCFpOlSeHUNw3KGbbKNLth6zzoNS4qvFdM5BY84/C+pJwIhAOAMFGGuw88qeYBEIM02c0T9UP/EqenTn1xpP181JMUC","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":759261,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhOfcDCRA9TVsSAnZWagAAaLIP/1vvwqnhluKNhS1xtU8b\nYqwCdl/yfhOejJ0MMke9+57O19/xiUydtDVqbN3n7LsVAhah9udsAH5OBPRg\nYB+2Bo9PFR2KT7xT2L0/BwR3I4XEpn1461ZHTOqNsiSeLCQyookB6GKrcYdH\nokWnt66gdbLupz9DQjfF5SM/sHiPshHnVMsskxJwLZoHmGYrvWMafdL3PTUU\nSDtwZOhk25xoxLRqWw92WkaWc98IRgplYKDF/TIHloEPpOdpxY0caosUCmuc\nEx/HabSTBO0rh6JPNuWCu+HbEz51E5ra3xfK2WaDB0HR5nVsaPJhts39GoZd\nVU060zd0uErWOkmnSE0bK6Wq4RxhVYepQtnNXF3A9RdEgF0kudxv4+O4ly0l\nG0igDciOCmA5alI1C1JY/5XyVGZtw9WK1Niz15TelA55kf3QVUqESLVs/+si\ntB++b6UE3Nky0UTM/j2+h7AXn9SytJ4OILC4sacPdWNDdpHhto27GiM22dlA\npBnUUK9U0ZF3QpTEWiM7SGY6Z7xnqrtyvqhngaqpyofKMb9jskunfqaUivb1\n6up+lvOe64oIJQ7kYpY/pnQc4DRbPkrwLMqLfAnRkJpZjmhmsrX6bB1V48Le\nnl6SxVdH1t4CXKTjKJe2MNc6jcGG1NGL79JyopcyUNRYTpGVNEhwpEftFvbN\n8Dc9\r\n=iVhE\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"9242752a13ba7c8258862382659298fad271b7be","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-09T11:56:02.733Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.67","@balena/jellyfish-metrics":"^1.0.341","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.9","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.1.0","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.7.4-use-links2-9242752a13ba7c8258862382659298fad271b7be_1631188739635_0.07063511880509421","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.7.4":{"name":"@balena/jellyfish-core","version":"5.7.4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.7.4","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"fb7356a3c9afd511f3a8d704a87e2fb1d5c56f5f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.7.4.tgz","fileCount":208,"integrity":"sha512-CR0AYbnr9GV/9mYZj7qWdge3uBdGkqREaGT/XGLN/7UKVb6AgR54Z//rqiP9lI7y/U5b5xg3CGLtqFvuGahQqQ==","signatures":[{"sig":"MEUCIE8S/3EPLT6xcHhu8kEW5ynemIxRN08fceuY75alDF1TAiEAidMSiGviH/85y+VuHRnG3VxmaWY9TgyI4KMJIBaKPm0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":759209,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhOfiCCRA9TVsSAnZWagAAyrIP/2BAvCSW+WEO4OwQPXwF\nHzsbCpDcBGQW+JoALeyE7VaUw2uNTUCBDeK5aUPYABdiWn59DqGoxflUDJIh\nDfUjhjoeqwY0RIJCNAiZ+EUja5MlaWI1n0nhYQM5gXMC5/sBmXPBrgKmQMJf\nES7qBf2ztfPiNOjwFJSh+FlHI3fsNEIpnVOoUK+4PoQD2q3gD72TRBiCyixI\n6c/woCp7q+Odgko4WNpdIwTb6yjmWUavLmSRcJPjqD5m++9OtYNsFS5J8gvd\nlpDiWT74acnE7T2zcnhDed53huyvvTcv96uVgItyU5PNahS2gSV/ns57Gxak\nxOac8j6WEq9yLQTqmKdNuHFcc+KSvkCEQQxXW+aivegDCpSdoUyHeXDwkAYF\nB9BDjS2Zf8iRN+JsXFLEo7LxZPJyHGajyIlideCFr/S3hRlXeT64y/Z7cJtf\n1VidF49Z0EZNDzVHrZJo2FK+h5G4ZzZu1VJ0PKiuiHpsk2wzGGMt8nE8C0lg\nKI5gmdWIisuEITMScOH7AcHurd8bXxBJn3P6NNTaQ4VybCyG/fGVIem6hZhu\nEArWozpFztIze4dIOhWjDBuOzKa+i1tN/hzB7B/09wKKuBeUjgJNvf5DzChI\noyOW1HXEEnqRdTbMDXZMrB13SZzjfW0qeq1HpDUszhcfF8R1pgUJtKkbl2xF\ncPrP\r\n=Wc9E\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"035afd7febfac25f0424295c3fdad5b39da3d4f9","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-09T12:03:31.697Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.67","@balena/jellyfish-metrics":"^1.0.341","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.9","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.1.0","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.7.4_1631189122856_0.6121840158409972","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.7.5-renovate-major-external-major-50a3e5535fe3cc8aab9f2a410e520182a4c45d97":{"name":"@balena/jellyfish-core","version":"5.7.5-renovate-major-external-major-50a3e5535fe3cc8aab9f2a410e520182a4c45d97","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.7.5-renovate-major-external-major-50a3e5535fe3cc8aab9f2a410e520182a4c45d97","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a9fda9f6fedb76767db36069d7901d60b5e8247b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.7.5-renovate-major-external-major-50a3e5535fe3cc8aab9f2a410e520182a4c45d97.tgz","fileCount":208,"integrity":"sha512-GRTmxMKnUucocLvDeJGilJPq0td9D5Dbm1sgcQn61VHChj93Pkp4cRpj8FcxyE/HX0asm5p94QHDW1SEDoxolA==","signatures":[{"sig":"MEUCIDg4HZyZb8kfqboxZTtpt2gHEEoMpTQlXaJIR5nIQ/5nAiEA4EroTQWSffAi+qhgojDELCzZK+W0GO9VJ+9ovYcwSto=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":759349,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhOfn3CRA9TVsSAnZWagAAg7MQAIcqQfmByX1dFuolWIO3\n2qSSoMGx6t8PnE5y0yt02wDZnRwLWjCu1Oc9oRptrzKjW7mRdoSXXIV9eyh6\n+A1ssF8GzYZt4JwpRf5GILmeD0eO2qIPh0krvid7ziBLJ5NOjFe1ASHM+Si4\nBkM44k4FlsDbRDe6lTCQNZL/0oSeLeLrBAhhXKTLrpgBHaS+kBk95hF8hAMu\nuut7E1fpLmb13Y4DHi6bVNFdrx59u7Nqg8a1s7/n8uPJd2iZ0reTDYBHeOuU\nf35toj45udcao1d2RJLOsKrLhU7vNa8Dd0nDRpFU+4ro5iqQNtKxp1c3mZjp\nGTSO8A+B7dyxJRJ8xacJxJH0bPj9E3PJF+mdQhBfO/MMamJiGQm4WK/mXMXi\n6iEJIucAQDAXr4dH4vNDTDleWjts3mVXAfeegOxYgifc7lvJJ24niWTff7vC\nc4/0FRplZ4f8/DT0Q6CMXRlgLT2g3jk977Ul02MhPLeL04XBfb2iQk/jV87T\neg+gRTfTe6AFeMrBZegyT3wv+Wp87WiGtu/lOV1Am0lKspPSdZjfSuHi6RRZ\nRghkHI62Q2nzx9v7f2UkHE+g+OjFf3W3GSSeX8aJha+W9z7F+8d2L4bEQnjD\nRtLwarllnY7GqXR0GXmJ/nac7nAqSDHNh7znmcxmmLyCBqhgHzfh/dQMiWWD\n8rM/\r\n=6rN0\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"50a3e5535fe3cc8aab9f2a410e520182a4c45d97","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-09T12:07:38.767Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.67","@balena/jellyfish-metrics":"^1.0.341","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.1.1","deplint":"^1.1.3","ts-jest":"^27.0.5","typedoc":"^0.21.9","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^27.0.1","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.1.0","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.7.5-renovate-major-external-major-50a3e5535fe3cc8aab9f2a410e520182a4c45d97_1631189495017_0.44648923887036585","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.0.0-drop-links-77f0c485f968775790a1f7b0499992bc6095cf67":{"name":"@balena/jellyfish-core","version":"6.0.0-drop-links-77f0c485f968775790a1f7b0499992bc6095cf67","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.0.0-drop-links-77f0c485f968775790a1f7b0499992bc6095cf67","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"196fc6698fc970f66716685d0c89ca5328546ce0","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.0.0-drop-links-77f0c485f968775790a1f7b0499992bc6095cf67.tgz","fileCount":208,"integrity":"sha512-6ER9f+9qfhcTt/53LLTophUc9iaXrrsq1RK2XCKnNZDzee/53eeuSSPR4wsi7TrO2LbjmGroV5AwHRdiwDMDUA==","signatures":[{"sig":"MEQCIHCm+rcABQsxqQEIkwXzQJayHuZJ9hth8+b8xB6UUDuZAiAcBFokljHm3jSxZCFEjdBcR1CP5lGHEiigSUzPYhR+AA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":755311,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhO0l7CRA9TVsSAnZWagAALfsP/iCXbUfWOOdNrswzFAQC\n+nFVui3yH7TkpexxbIkFCgoQ4u9umJkOI+HcRQYgBlPdhj9ABuAmouYHZPXG\n8cZL6lC7iuZiuK3BAp6UzmlpyFtx3/PE75aZPh8hq5AaMvW8JbckwGvzoTjH\nFb5h5FV8zBcP30wCAaI4LAQoUUASKW0ftJPoXrUcDApm3USchB7WgWZMedJ3\nnw/3aEGRRZiPc0Nw4m/uOAlezomHAIoUpf7LkQyr1De18PR3vcpgsJUnc4bl\nXgUGWNUdoR6EEzOMvAjGC1ImLBvW9jbDGiwcJ1Zrp43NXpXxoHifuIhE2HeP\nhTi14zUpMYenefOk1jLsfDebvjnsTzNGGWKsDR79CzV0p0Kg5IhbAYVrQdLy\ncCzQhgDxTYjWPnP66Qd2TCrc36SPaBQjAtHEozqHeHOeCYm9evDaX9njoE7Q\nCYcOH1fpHaS7XB2S8iKrln82E6NY064i/90s9aP57TF4cmaBaewxMPkriaNm\nWtVHzURFBeNeoynnpbt2PGl83ZDe8FQEKhMVOHhrFk7g3sl4V7yBlHikHMrs\n5KbjhyR0M6h5z+ucSV7ScxLG5DqwNx1ZkWNgViSnxzSZbv13kH0nN8m5Gg8h\nEe8U5juCRwiB7c6hPV61dGhUJ8OEoUmCT8s2D95HYc+h1krUmUQw1B1JkI8S\nbUp8\r\n=TTKd\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"77f0c485f968775790a1f7b0499992bc6095cf67","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-10T12:00:22.171Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.67","@balena/jellyfish-metrics":"^1.0.341","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.9","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.1.0","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.0.0-drop-links-77f0c485f968775790a1f7b0499992bc6095cf67_1631275387428_0.29668370785725817","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.7.5-renovate-external-non-major-40c0a304c48879d0041ee67207080d4920347b86":{"name":"@balena/jellyfish-core","version":"5.7.5-renovate-external-non-major-40c0a304c48879d0041ee67207080d4920347b86","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.7.5-renovate-external-non-major-40c0a304c48879d0041ee67207080d4920347b86","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"894d831fd57a0c74cef4132945a35cf266b3aaa6","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.7.5-renovate-external-non-major-40c0a304c48879d0041ee67207080d4920347b86.tgz","fileCount":208,"integrity":"sha512-V0GEnTWXD6XlGqarYikIw6GT6YTrlNQWiSTUqs4MnoKQ4XvnoM2KIHQ1MqxUW2pTPSatXaxnN2vLBKUtT64yRg==","signatures":[{"sig":"MEUCIQDyUQYUcC4MfpIQ+jg0N0LTnyw8dHgSSELLJVDG5i1dfAIgRKIU/q9+qZwOK423gaGzaGrLbbzuq7TJvx6RqfXxcFI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":759352,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhPAcjCRA9TVsSAnZWagAAfJoQAJQEYnHRAZoubU0Waryz\nsEQs0vmw3cMMUWyYh855Oz3K0+LfOPwryMebSC5rZCOiRUo5yLz09dQUiO6b\nghTTZU2Lq0v+ySOUFmk5UKZZFupwY4JcwppsMo6O9i0cTVSfJIHPHHvaVuyK\nh7uVdxz7FSjeflOh1mBxqbwxYarm24PXCHskCOI/XOsh9h5zmaP/bh7FpbzX\nMQKlV8ge1P/gLvnoLkxDUbPNkj8dLeMr1/vNygmWi5ufphNWzOI9b+vbkMen\nGuz1Q6Jjcuh++Ox8gljMOp/oTheO4QdFb7/2iI8Z0dtNqoHBpviLsGVSFz+Q\nTGfy0rwMUtzvW+N5ZHK5mH+/nBX6z8JaljYhwYPoz5YMqTUlRWWmo2Upl7IV\n69LQ1u+rfGKFRFbsCyoHvP599WVuBsyj7uXijywv4MBXwC2emFj9AqhdzRQ6\nFBtuR+xkpyUZYz9EjMllLC053QW6Dya80PG1Czq8vI0OsiV2PwGv6h7FJUzh\nZ2ZhxTFten8K4FaeCiCRi8JzAh9MYU5uS0Snoeth98lbMku0aqozQHmURSz+\nuxuimuhCGL9x/V2dJGtyHlgCFoFG4A3ytYoTTUJ8m4wX5zrEfokEUb1zlXAF\nXrjfIdhq5us2gabMumXyX/E4/Iw6M4X97AjEDEHstp0SLzMOHRGsAyHkg8sn\nkjfA\r\n=B3b0\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"40c0a304c48879d0041ee67207080d4920347b86","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-11T01:29:32.264Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.67","@balena/jellyfish-metrics":"^1.0.341","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.1","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.1.0","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.7.5-renovate-external-non-major-40c0a304c48879d0041ee67207080d4920347b86_1631323939130_0.26666683400709745","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.7.5-renovate-external-non-major-e3979c7fb6a05ae35500cde3eaf716df9bb8edd2":{"name":"@balena/jellyfish-core","version":"5.7.5-renovate-external-non-major-e3979c7fb6a05ae35500cde3eaf716df9bb8edd2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.7.5-renovate-external-non-major-e3979c7fb6a05ae35500cde3eaf716df9bb8edd2","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6ab46724bc3f8547622dd669a53bcb43c753282c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.7.5-renovate-external-non-major-e3979c7fb6a05ae35500cde3eaf716df9bb8edd2.tgz","fileCount":208,"integrity":"sha512-to4Vg+JlbQV1+XfTYHhaeKl1YzUSoRut6oiUTR0aDX1zoN0tNvdROeNnH8eIM1OdYLc1qUke70s7CIQ47dJ0Pw==","signatures":[{"sig":"MEUCIQC3pOTD1NvvBbDKQGesXeFIoFv0yMN82+mGU6uQndjL3QIgY33gW4t8Q3qRWDqjOUrKy5KZeXaDVNcXxZZLKzjE0mE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":759352,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhPBt/CRA9TVsSAnZWagAAtmcP/20up++yow46ZGZ5FYME\nbdtohZ2fXROJUPkSFYgymrHo6MYYofV5ynTVCwC7rx07+UNHATk4tGGa3HTT\nN7680OgztWsAdSrE7Zc4E6SAZbrqSaoz7zXmpMMtZT8daKx4ObecshiqXJvM\n6LNTBKaQJZctno9sB3IYjOtf5Coa8vw63NhBmlm4gQUU1eNSHxyH6zayX9Nl\niErud9FFZQX34//WszV52a1TeammRa7mEQLWdqVd48wLTKNyqi6LmodoTQRf\nxSufECz4AbS7k+hUfcRQg9AwKRhe1TPBaNNgS8gtnbwoT5tQAEeiDPh31mxu\n67snWa26bJY7fbmJoBgQD1u21MctXqNUNFzjg7Q16IGxLMzMfc8mJvt+pSWg\nsOrVQejmFEHSP8dLuKkmPurxA9QRf51E6YK0obZVP9U7SHYPB9hrq41JNcLX\nxE5G4tI9Pcchay/GApfLVuer/D6mArUdTLovott2vA/0j1fi/8adWrwQpOaT\nsgd5u4mUq42f6JwyqpYEj4gbIuOH93vcAEcvmCgJUOD+kRtpEuqkFkN/htYl\n6EKXkFVv0SqadzG/XHo9Slt/hhMOwIM54z2qdbeNt1nQrFkqMqTVRidL+7h8\ncGK/AQ+KSkrk0seByFU04aRYvP2KkxY5qC9mVtgCDpGJ//do4iI+425mE3T4\n7Pc4\r\n=D1Qu\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e3979c7fb6a05ae35500cde3eaf716df9bb8edd2","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-11T02:56:22.011Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.67","@balena/jellyfish-metrics":"^1.0.341","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.2","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.1.0","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.7.5-renovate-external-non-major-e3979c7fb6a05ae35500cde3eaf716df9bb8edd2_1631329151048_0.7001954428630857","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.7.5-renovate-internal-patch-a5c16aa557efacdac672dcfa60d05f7fd80b6769":{"name":"@balena/jellyfish-core","version":"5.7.5-renovate-internal-patch-a5c16aa557efacdac672dcfa60d05f7fd80b6769","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.7.5-renovate-internal-patch-a5c16aa557efacdac672dcfa60d05f7fd80b6769","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b1cfd30751d283e98be73d13f3cae43f497aaaf9","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.7.5-renovate-internal-patch-a5c16aa557efacdac672dcfa60d05f7fd80b6769.tgz","fileCount":208,"integrity":"sha512-CRhcAQAW2usGiRBE85ETFXP48JLfrqeqOCn1LeNBXdhfaF1yNMH1LCKyqqE10Zpz8vsgFVN989l57z1wep1eVQ==","signatures":[{"sig":"MEUCIQD8QI/0lOkvd4yuIAqLl6ev8SKGh2RrzgtYR7qZq9ap9gIgUrIwRzXkHl06kvF+HxrC17oYGDmT0kcoiWPwPgoho30=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":759376,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhPGUSCRA9TVsSAnZWagAAD04P/RcLpLd5apMM1M7BglkT\nY6c0E6rqaUg298ArOVTMjK5I6++7gBvtiEqyVI3QtKMWBTnZBxcsyfF6WZjt\npavc9WT7pulbCVTltWBgNjRsHLD4UuErg/Uib9vfW4nPnqFQLohetzQ/q0eq\npXUDqPDO7qJsYdI+7fTs6S/dP01aWPFg19e1zMZXkm72tyekd2bPLZuxXVxF\nTSTUnteYTWZv8s+NDFd7ZLldCceoSUD3usD5W8tvFsGMP0K5qmJp8YtIyQo0\n+TLOT+8rDhnbIFq5V1M7581HcFpeUuhF6NBxbgNPztmkuJWjSkBHer8TCRwS\n7SdvT883Cys2LCFjSVfArICXJw0SYtLdbCqMIZtyPMfgonHic1ssAdMg53j3\nOi1DCy6IPAHULfM0PL1Dq97l7GK/Azcsv/TJ+9kCRYRFrR9vg6x3LjDychL8\npKGqar/HqzBMru3hDNe8z01ROb3RZJ5Rq7fIoAKnjdZaZkFt3+C0jP+3qQbR\nsjfKs49VgPh71a3xcI3LnaLXn0TD/mc1KaajiGm4GJgtCxa/voJANzElu4P0\nqGTsuyaSk/VNaatVuxJA1R+FrH6wV9rigOvj8BX87Azt9RXZ15jwvUl+1DMZ\nLq8eg6sTTKgxfS5PHdUXzU27wsfh1S4ExY2w1W1UvSYUInEPmTbrU1L2n2jY\nMVRf\r\n=Ga5U\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a5c16aa557efacdac672dcfa60d05f7fd80b6769","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-11T08:09:32.085Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.68","@balena/jellyfish-metrics":"^1.0.341","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.9","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.1.0","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.7.5-renovate-internal-patch-a5c16aa557efacdac672dcfa60d05f7fd80b6769_1631347986507_0.6496659600466155","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.7.5":{"name":"@balena/jellyfish-core","version":"5.7.5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.7.5","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"537c1d521809a8bca35a89f9dac987cae05c915d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.7.5.tgz","fileCount":208,"integrity":"sha512-LzuvkalEFYK9z6C7QqceRBxBtTE5ChmDZPuEBvPt7rFoD7cQQ6rnqvg46Y1T+xlk091PFB7xpk2hGOikxZ45SQ==","signatures":[{"sig":"MEUCIQC2s5D4e+WTQg0iVYPIYYJj/FFyk/9/eWJ0/5rIawQ+2QIgbgZibnu/QA8dLmWCBR3PUK3iYYxlEp3qzliNxrM7kM0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":759311,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhPGa+CRA9TVsSAnZWagAAxWoP/0QQQOtd4J91sweap/J1\ndP7krJqx+DXJZLV9Ah7sXzKLWLCrtRlZvurbXDt4TLHN41SELLVg6jq2x/tM\nDSHN0jptJVbOftoAinoRHeAJSLt2vo50jFCt0iD7N6hM8MQLMs/lYdm+CY1U\nYzAgXE9aoaSYWLdcIwMK+moDsFyJ+qTKeF/oUJAeqFWzAbJOt/oUmGWzK+lG\neGB8Wy90T0+S/hkYhIIop5PpRylYP7tx+H8rED4hgtCBs486kjkYmy/3kajP\nV7u4bJ5RMVPcxFuqZGsGRumRLFlOHC5faK2n7NxzCTFJsHFUNL0cTmK3+zwR\nOiPpfvqhsZLozMPHKbHU5oNivTfvZRrr+LEJBrGLUbgsabFstSPeF4M8Wf9B\nxpB9yVCQ1ZjR5zMvwD1orRBe1LvWkOVNbMdHUVET1+0jxuw/ELnQa9CtNCh4\nU04Pkwqlf1R7crP4rK/cBbWMff3K9kKuEzxrumFTEmwWnvrryvoDY2adUt07\nAam/7+b+nGlrvPI4VjT1WAbAjjPTgSb3bpwtVYypEiP9IcywZnkj6MLrQqLU\nSZi5aePoW9ib5hfWcuGgY3cpK8xolP+I6Dtawxo6hBqaeVqm+Uf4YSlbgmFz\nGibC6dkntboCX9ZhpIzcMhfFZKUf+cnLZYjjTm/wNdf+RSDq4N8WXLidPgCI\nmeHH\r\n=L3vj\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"1f11b5d5446440250a767e84d7a12dbda3c60875","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-11T08:18:03.338Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.68","@balena/jellyfish-metrics":"^1.0.341","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.9","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.1.0","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.7.5_1631348414288_0.43520644816848963","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.7.6-renovate-major-external-major-b532040a34e72f23a870e929e6ec25dd8e4307af":{"name":"@balena/jellyfish-core","version":"5.7.6-renovate-major-external-major-b532040a34e72f23a870e929e6ec25dd8e4307af","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.7.6-renovate-major-external-major-b532040a34e72f23a870e929e6ec25dd8e4307af","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"142259a721462e5aba33c42a9b5bb08e5366e6fd","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.7.6-renovate-major-external-major-b532040a34e72f23a870e929e6ec25dd8e4307af.tgz","fileCount":208,"integrity":"sha512-bQG/KLhR5jmaB4y/Gnk/vxgOBqRqCD6NvJxnqzDChn85st/GpEJXcwVCUYfFT4WywuBf0W0f7kw3m5tOooA7mg==","signatures":[{"sig":"MEUCIQC+GuB2y2of3AT4SCvxgFeK+cyUkwweExX0uX47PR6/nwIgWoGrw4B4w3foEItTNb32m+vAGV8T3mTrigMF8Q9IzfY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":759451,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhPGfPCRA9TVsSAnZWagAAGQ4P/0/UJ75pzwT97Q3XYub2\nfhhPY5SjOrJEyon4269N2bFQTtbf/oXs3KUt5/465VeR1ftxd4JWeN7Y1pO5\nv3yf0ipqqXc9Q6ssFpnD1PEdjcb+alhtefpOFGZL9YqP++fxxtXChUcZXbSf\nKRwqJ9pBAUqNuJXKc15r05xg0POv5CLT0CSFerGz5Mosc3wZ1nMVxGLlRzpH\nDdgJf7dojd+lyXlmcuOlI2UTZcIXvmLRnjIsB5lsPYqEQJ7nrYevB9ItvwQx\nZTfzjFCf0NUDWAezM4UNV0F8o8Nl4rgLA1PulLvlzfYLL31uaGPtA1NcqCFS\nUID7mz8fpGNPEbgM5w0j1ye70snY839vBSk23VFjPsh8BYnfe9axqLqiYV7Q\nCdmjwFWX4qVviyvor1XluJ9WoPDFvyRJ7Wap49qRZRHLt0h30Nz1jt3KJjrX\nQze8N9jfm8NHT66J/q5TAuk0IK0KSf6abt0U5eq6EaVS8HEKnpMsglZhPvMg\n40PADOj0zKI0fmdZrffy/AIEJO7j8ns3twJI83dJlibFsoIO4lLoLruqufp5\n0RSVOQyt868p0RdvAwrpIygDhMmytnwM9LY+HJojOMkdKjLf/BmP+BWfl59x\nEkZtMM24b/vUYBSYBd3wudmeYYIt4UhW0m4yzOH7oxCwqShssWH9V7U9Bu6d\nzlUw\r\n=HRO4\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b532040a34e72f23a870e929e6ec25dd8e4307af","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-11T08:21:15.908Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.68","@balena/jellyfish-metrics":"^1.0.341","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.1.1","deplint":"^1.1.3","ts-jest":"^27.0.5","typedoc":"^0.21.9","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^27.0.1","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.1.0","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.7.6-renovate-major-external-major-b532040a34e72f23a870e929e6ec25dd8e4307af_1631348687385_0.08070047589073037","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.0.0-drop-links-636055b34c7dd7169f3d7f0e567e4a08c1fdc267":{"name":"@balena/jellyfish-core","version":"6.0.0-drop-links-636055b34c7dd7169f3d7f0e567e4a08c1fdc267","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.0.0-drop-links-636055b34c7dd7169f3d7f0e567e4a08c1fdc267","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3c468eeb84a68f8cf4fcb4470c0353dcfe7b6515","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.0.0-drop-links-636055b34c7dd7169f3d7f0e567e4a08c1fdc267.tgz","fileCount":208,"integrity":"sha512-wiyCy9o36P2GziEg8ErI37UWg68Aq0WryMlNRKmegockP5bs3Ex5Iej+ra2ttZxrwKp7ayxBNAss95a6qFyOQg==","signatures":[{"sig":"MEYCIQCiJ9z1T1uX2ywccbckknRtBBiOhnTu15Ib++fbaA479gIhAL3g3RCIb8Te+y0Ss6CYJMyiSAOJHeo1XlztTcUeLpI0","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":755413,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhPHtRCRA9TVsSAnZWagAAn4oQAJ1QmnXKbeT7i35wzN0+\n+3p2hc9D2++aI1XkEMwn1vWXnmdIN1dP95Ixpp7l1XB6jI6rdRvVZTbiS8p0\nsMcfSDV7DFiVpvJAlXuuvAip2HXfADXkxKRcCHtMOwZbZ8VBO1DpIbxHEybu\nBK9ENvexj8WEW9ST2uGe2gZnpRGpP9wGIZzqaJHWLmEtoqQqU71HsOfxdtGq\nTVoZmk/cPfw1r/gZrxi6jybIzQ+xjc/FJLdnuStq5earW+Cgxgt0LvW7oNGj\nr31xSXRRzQ0Wq46J9GJthGiAKzZbmmIGRDUgbQmrX1JxQYhPXvWKKtLjgo3e\nHBBgbfzgAWUDTDAB1VEjMp3AQT0v3T8Tv7JBh9ll0Eu85amO0QQqOZ5xrRza\nNnlMU9Xp+wj8TiD9oBoTYyk2pOxozs2OC05CqKG6ZYWdWHN6evbuNWurgnH1\nn69xdX3J8jOLimDsmH5Nt1vatN4Yegy27w0SDlJWSwCANXei5cUCSWjt1Te1\nr5DQfxRBZ2FdZ/U8KjOOeR3BSwNyFbiu6k+mGHiTfIu0U/iWYDIaxHK0HTqI\nAIlgh7leNPDyjahy6PiOAipuVVcRDcbjX0XkAfwx++pP3IkdM3Dj82tsKvzn\ndM+9ATSwxzGiEJ5YLnmk4urZ8TLO0YykIk4joxj4N7qsa7oYFtiow0lfsKjP\n3TDF\r\n=a0Ua\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"636055b34c7dd7169f3d7f0e567e4a08c1fdc267","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-11T09:45:22.039Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.68","@balena/jellyfish-metrics":"^1.0.341","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.9","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.1.0","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.0.0-drop-links-636055b34c7dd7169f3d7f0e567e4a08c1fdc267_1631353681482_0.05540189761631775","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.7.6-renovate-external-non-major-595e1c47d598b4dfbc0ff6c48b56161e11f85046":{"name":"@balena/jellyfish-core","version":"5.7.6-renovate-external-non-major-595e1c47d598b4dfbc0ff6c48b56161e11f85046","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.7.6-renovate-external-non-major-595e1c47d598b4dfbc0ff6c48b56161e11f85046","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9c650622385f5f74e538c230b704171533418953","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.7.6-renovate-external-non-major-595e1c47d598b4dfbc0ff6c48b56161e11f85046.tgz","fileCount":208,"integrity":"sha512-fBsoX3MGrST4Lnb9mJRKJKaZGqpt7bwMu/OTlghyPPe2odw3yAUgHXuscaz/uDs3g3oBx3oHdz+UzqHVWHC25g==","signatures":[{"sig":"MEQCIHR95rXf/wikFTdwptDDmdw8f5ciM/pDCzYCFSiPkbSBAiBNsqolvI0BNbm2oT4Ey1MgMvZCIqspJNMIjzPkGwgwTw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":759454,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhPXJDCRA9TVsSAnZWagAAEowP/iGB9wBGJ+Sm7/+VQjq9\nNwt/dKIkVtdaYzjH26w6vjt7dJXJFMuUDP0dYVswPGyH18/Vp0DTxbsGXPdx\nV+WaGaCYOzqq/EuBvYiUnXKXpeVDaccuzJB8HHoz/CFlCbkECc6CMWDGChFR\n6tPehDkPjV+8wRWuKGh0llhrNuE4H0VaSaTAd85KlklKfeM/sV9LMtlCzdJw\nuMQY0JLrTLadNrh57WbftoL1wVTWDaZKRHVrF6gLUlQ3EGdjQ7ErTn2YYtrU\nvrG23AV15Ys+swzbFCXFTr6Yl6zwRfuk1JOpdYgn4M3TI8yj8jn2QhrCer9p\n3Fgrhw7J0m6ld2+PbbTdO8ZLIgYTZMk7dLxfd016ryGLXlUuDUxL/h36G+2B\nnY7WFfWpQJZfGfOjOzsaBOUX61mtNfbTyiEclXDTIkCqqIpcVmzp7IMbpTNa\nM0GYzOcqre21aia3MUFDI9YiN0aD7Is9E+d7AoVUT0eMseUI4dcpmIhSZe4F\nMwRwtMpIvF2azhpE2PghyGlqwBvBOhfmNjvWRj4IgUP28IkpUu4B4YhS2688\ndgCRHew8JIS+Em8VfHAKDik1fKq4FO+pYkdN/Zcxxa06n9rFS2ImlilWiIsX\nT2HDVpQIPKcGru3phucm7uyCCLTGeOFA05UkiypVl65SV7Av9LokG8sTrhCU\njc/N\r\n=OJNZ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"595e1c47d598b4dfbc0ff6c48b56161e11f85046","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-12T03:18:12.453Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.68","@balena/jellyfish-metrics":"^1.0.341","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.3","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.1.0","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.7.6-renovate-external-non-major-595e1c47d598b4dfbc0ff6c48b56161e11f85046_1631416899691_0.7617198731922563","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.7.6-renovate-internal-patch-599d374604464d18708a5c39d6cceb44041404a0":{"name":"@balena/jellyfish-core","version":"5.7.6-renovate-internal-patch-599d374604464d18708a5c39d6cceb44041404a0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.7.6-renovate-internal-patch-599d374604464d18708a5c39d6cceb44041404a0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f9dbf6b39edbc8b5b7d74fbe3d54bbba2db1ea06","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.7.6-renovate-internal-patch-599d374604464d18708a5c39d6cceb44041404a0.tgz","fileCount":208,"integrity":"sha512-JEF37tvptuqKe1ithAReB4z6lGKPSmD6Ba/Z0I4knfm7w6Udld1JaJ0+oqbMrapDCswE5/BRSS8PgR6rY1fRQQ==","signatures":[{"sig":"MEQCIEuAp7nZInTqEqmHLE+KBIlKJd2ysqa9qw89muQLMwCgAiAIf1MN6F4wmcAXoU7YepiDtDlQUvUL7+Ktn+/aq+alaA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":759478,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhPZx3CRA9TVsSAnZWagAAGkkQAKSxVYeG9sdxf3mkh+WD\n2LRQyOBtPUzjfRGu3cQuAAokSeuZKnsgrLhYl4BN7fyNEULvFrH0txl/UAE8\nAScj7CKafa6OvPemdX+Oej8E24lTKjG5+YJ6fF7+SzwdEA9ApUCcz1EEpa9y\nZ9ETIpDBALgmW7lamWU1OGTIDxZ8j+GKorNDf7zM0RzvdeiRKr2C6WP75YCB\nEvG76ozWTNqhhKnE7kYrAKHduiqgDXKW4s0KO6B6t2i/F9y0UNStO1+P+i8k\nt3aGklN5gx6FMkYd5E+vhqUW4vj3cgmqx2FY7GkAngGsc3yGtyWpU2AS74ZV\nsb3YPbGWdZlbR7N7MdNdfTHRLWGxOeIDK0PLHrzO7FqEdKn9+zC1LaqSLby6\nTUuIYy5pMhaKOwdpZNpXv/PYNE+ZgsE3AoDhvoua8+OswxRU25yX8jah8moH\nQdl89wGjjB1M9sUoTaScMCTS6V24DzW7XzW89dXh4hnBy4PGe8UkU88gIFru\naRChrKFCdG02HEOkxUjal29BlQqcELLf9kIY6LGcYTPDFNt9gIvYoG2mdQEO\n2snF2QpNv/XQoxwHPVd8BnCKY4PaL22w2F6HRN0LWRGgCNmkdeu6O8pAiVwb\n3Ov9ieOcVHlDFpV2kz2cD5R9936wzDsiXHyJHH+29akzdkzvSCUIg9yuvV0x\nImJK\r\n=Q9/W\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"599d374604464d18708a5c39d6cceb44041404a0","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-12T06:18:12.741Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.69","@balena/jellyfish-metrics":"^1.0.341","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.9","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.1.0","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.7.6-renovate-internal-patch-599d374604464d18708a5c39d6cceb44041404a0_1631427703361_0.24275763401667994","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.7.6":{"name":"@balena/jellyfish-core","version":"5.7.6","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.7.6","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f607f6e3942d9be3f845939c6be3efac07b18902","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.7.6.tgz","fileCount":208,"integrity":"sha512-912S5pIoow73awlTEdxPFzFRK5c7LKAlewil4p96t1wMLs+yZ2dBADnCoTLfMAa0g7VfpEqYQCc+5PHPZMoQrQ==","signatures":[{"sig":"MEYCIQDT4jnHckSOh+3+od7CiJhoG2rs/UWuCLMrzOnGIgewoAIhANuT8yYUiyLx2OnAhNrHUy5N4j32SnCzUjsUWpmOCTD+","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":759413,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhPZ4LCRA9TVsSAnZWagAAdxYQAJ3I9QCztfrS9r/B/Xvs\n7TpWuWC0snLl91rjZciYNRCxZnB1CGLFZC1Pv1NtLvlqqHOivQj4haXgo0Vd\nB4G39VU4sV3VMC9CRVjOHRO4+iGjWkk0jcQ1P3V3jolmfJ5GQipXHEt3aERG\nXI8BZeIDIv5tAQlXXid3QkTdtDjRrU9UA+AVxdfpvBFxrKaloURUeWipvc+j\nRzjRg/ei9BmGOEYZdRS2VVnOinyHGT51JNpsEer80vIPxBlimopzk68GMaMc\nOAKYd3V8TBpzIYLS5jT0TXlM2qBJBIxQZNH4Gjeu8jDf13LHMYO4DbtSMZt1\nWaN36I8FF8xPpUl47LM6oTPuCj2lg4WIXDuihVTc3e40sbiKTn5Iyiohz6Jq\n5e1bzbOEIVtT51F3lF0vg0RdJLWjYgk/2mfsf9JzOs1M1y3br6kuln9l+qp2\nCXx30Ry8qfS4H3oME6++zz8iLks31MpOaEE38JzsG4+ioPTN5gCWHrmCoL5e\nR8dBdnKvWA37vD0c7xt8ajQJk/iIHZ2aXByXRgMScoOQSknSPegY8R8tA/he\nl3LDPUEbc/s00dzt4R6pOUwP/f94vJH7iGMDq/1h6gJpADx9lVjBG3DWX/rz\nc6+fcZZm7kFgPTgyTZbiaiBHwfIhLxAMZ1bA36yiFzdz5Zwup7bdPkNCZipC\n8U93\r\n=3VHE\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"25079e19cd151bf0561f1cdfd764e21bb3228f86","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-12T06:26:08.713Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.69","@balena/jellyfish-metrics":"^1.0.341","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.9","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.1.0","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.7.6_1631428107056_0.06150731089150496","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.7.7-renovate-external-non-major-0613d1ff50557548a52992261cd8504bf05a771a":{"name":"@balena/jellyfish-core","version":"5.7.7-renovate-external-non-major-0613d1ff50557548a52992261cd8504bf05a771a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.7.7-renovate-external-non-major-0613d1ff50557548a52992261cd8504bf05a771a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"865d9cb311b15c8508fb93c6bb63bc7fa0367862","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.7.7-renovate-external-non-major-0613d1ff50557548a52992261cd8504bf05a771a.tgz","fileCount":208,"integrity":"sha512-+ii+TickZmqUP0ce/2reb5A+Iq7HhUiavnUBjp8h5JYBWsJSXHPyAbr1KqZxyxmfjHgxb8yuDBKCQQzxLhb+CA==","signatures":[{"sig":"MEYCIQDIZ9utul9EEJTuhtgyTLOcZpmfvUaJM0gM3sGTbGrECwIhAP7mO8n0hIfMhXbZHp7zvRo6lBUteK3ULiua1nZuX6Wm","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":759556,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhPZ88CRA9TVsSAnZWagAA1TEP/2dVL/G+/hieFtnaaj3y\nDfpENHIuefChmqJ81C+NpFQqrfNhw6yYre8ylPzIuZp4cPeoozoZfoYGYiWF\n5kU+DxaS1EUmKJarZ2eHx1UTTwMysvCGVQeXhjUh0UsG+XxFd7JOxGsej1nB\nlqwQ5MLDH54viMAsRl4DCqZ6DJmYkfxPusNkVw1Q8MFg6ra4LypXyCibHs5/\n/n8nM+DjI9ACHTi6iWBdJyW+7DTb368hKtRT+tUzPnZFYBzw/wibIo/cihva\nEYIN8CE21h9DXhJEffPKcXU55ys9DUFeWi+U+CUbiBsPNvi8CPvtAPSkmdvy\nUyH/4l8Nuv2bq7lgqWm+W6PYMVnZSsYctsxarDGSMrjlKSUrRIiGtlKi5vEq\nZBDyRCxYmslxLRBTkNMA9AZ6qBeZ01NxXn+vZa7LOpPd22MShTzTbj235dTm\nZ7rv0qn0pP4tYfLuL2Lrrs/fljtTv13289fyL200odNAwxvsZ0fVtaigCNjj\nq8aDYB5OAHZJFbNZF7JmZB2CQScSBDluiB0Rv8MNMWtWoZG3vB9Q7C5r8hPQ\nyNianChJWEiEsyL1M7iP9wzSTft8xtlHhw4GbTv+6b3kr/Ri2h5KUI1/qDaL\nwISNCdpaX3ZCIscjLYSBXsbfNl/vZSuCSNtPjQoCyNWuKfh5USTyJjdTPoGt\nQjui\r\n=YRC5\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0613d1ff50557548a52992261cd8504bf05a771a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-12T06:30:42.040Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.69","@balena/jellyfish-metrics":"^1.0.341","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.3","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.1.0","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.7.7-renovate-external-non-major-0613d1ff50557548a52992261cd8504bf05a771a_1631428412735_0.7773479763573785","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.7.7-renovate-major-external-major-65fa4a42a517bc50861541af38184822c6c45fbf":{"name":"@balena/jellyfish-core","version":"5.7.7-renovate-major-external-major-65fa4a42a517bc50861541af38184822c6c45fbf","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.7.7-renovate-major-external-major-65fa4a42a517bc50861541af38184822c6c45fbf","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8d44d2d8e339d0db1c1ac376fcb5d0904d33f20c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.7.7-renovate-major-external-major-65fa4a42a517bc50861541af38184822c6c45fbf.tgz","fileCount":208,"integrity":"sha512-jkogVGRNPodCfEPe3k2KxO1RGDJLojTO3DN+dz6HNT9VKd9hsw7B8VJBEm7EN+u5MYIYwDBqBmzXiPwHH9qJeA==","signatures":[{"sig":"MEQCIDDzzbsFeaDNJhROvVxjMFVN4jH/3hv45Kh9IFDwXYFBAiA9kJlWXsghe2LXVrr8WxbNmv7Dns8/zPRv4AmeN1f0Bw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":759553,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhPZ+MCRA9TVsSAnZWagAAXcsP/3Y0LW8AIPaNsO1jEZRw\nNMvPblOPQmIkudeGJUwPWnp7fTv/P/UsrAzIILB00dshWkafxdhBq2OoAHfI\n76t4Lb4MkbiwCSgLQ9bO/o66RkugVy7DSLWFhAJE4YwTvmpH6I/9xuunMuDO\ngk/DuDfYCwGeXpWEEeFTBUdyNqvHmp9i38COChzCXYN/OeycXj/5b0E+WROx\nWR3AOGrwn/GaVd8OTtLQWU06FikKeaNPCl5DneuI16u46bjb/n/BkOKO+DpZ\n5acZ7c9v0UM6/mIWpWjQEGh11vGZSAh/JDjv+pWh6ZeAL/Xx/2z7Ubr4stG3\nXS/j/t6yNW2Z/I8dRfNEVR0r5g15Su8uijUx7eEoWeJkKANbyuwumpjPchjI\n0Au5z28lMuALwBiJAc3bd5bOhxS6GeKpQMwg4x0rprBRxfNkZNr26BRCh2Ch\nYZfqB3h/kp+7qnpFbOkq+zYAIIQY52xT38Mfk2zDHh7FGRytK3Qex7EDwnVX\nVdeJofqQM83XuwX+xcVPW/sTOy3YPNYx1ol/HDys/3dFhxk+flMuK/uPvak/\ni7cd1LMTorcswv8qyst+Sh1qdkRHGLz0T1FJgHeuziOFphHorjPaEwf4Za8p\nrY3u/uUcPFO8X0w/u1yz+k3JUqFxkkXKvWUelBLZacz/6J153gsWfn8kXtg9\nDnti\r\n=EUco\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"65fa4a42a517bc50861541af38184822c6c45fbf","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-12T06:30:48.369Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.69","@balena/jellyfish-metrics":"^1.0.341","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.1.1","deplint":"^1.1.3","ts-jest":"^27.0.5","typedoc":"^0.21.9","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^27.0.1","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.1.0","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.7.7-renovate-major-external-major-65fa4a42a517bc50861541af38184822c6c45fbf_1631428491974_0.5864229780551995","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.7.7-renovate-major-external-major-ecfb82343d55b0ec747bf9d9697012b945846c5c":{"name":"@balena/jellyfish-core","version":"5.7.7-renovate-major-external-major-ecfb82343d55b0ec747bf9d9697012b945846c5c","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.7.7-renovate-major-external-major-ecfb82343d55b0ec747bf9d9697012b945846c5c","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"fa3c3a552f36a5eeca125958990c4159f86b7ab2","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.7.7-renovate-major-external-major-ecfb82343d55b0ec747bf9d9697012b945846c5c.tgz","fileCount":208,"integrity":"sha512-iNtwfLyBQWwmUTE1xlyHaQoUqLFkvlonWmectJmhL5p/4h80Y7BpRKQD8QVFM3xOTOWT4Kf72mMXG9hgzurxGQ==","signatures":[{"sig":"MEUCIDOzdHm49XDcoOodwOZ4GDsLjCSqD2TrUMFJF+yV0k4yAiEA5W206xbkdrznqX5TigPmZJi0nCd5glcBCmU2e+2WESI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":759553,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhPxrWCRA9TVsSAnZWagAAStAP/1SbJZ/jleMIOW5tF7Um\nfn1Fmh1QQd3mpv123Q1lMw/1+kOa1iBc1utw0VClqvBj7Hd+MAP6s1VPm06H\n6oPA6PH6VIVnJBaE1rs1zr8cmFwbSwwHNz2Rp05vyvmQQTDybxo8qjdGa/o0\ncrYGbaa3RlBV5YPwVfgNdKrl0exZ342WPrN+CiTOF3evPmjaxS9RSunqfcAN\nsXjjyH2Z8JnK4ALOZStEIiHELTiBSmNJGo78L5mK8JtWoMxFxZVXaXQyGU6M\nueZ6+44uBNh/iGlH1yrDE+c5W0w1p2BqKhOm9XbiMtxbzxqJLuJEcGRi4nKY\n9c6Sy1SsbMz/LYERA8giRLpAqi3f+dEN+MZvMQ22qMhMY9LFqcGjBqg9AwuH\nwvsaMZkrkLwn5KQtTn44XsOFLfu7YwU210+xoPAj3e0MYx077MvsX2OTZebE\nCICWn/S9TRROLcYTaGLGXfA9ea+ZfQXegrtPUdJe/4rkNAzOKSkKaC2HUGz+\n7xByupV7fEH7cQTHkPsIF0TppE1lEF3gvlqZIPz+zNMIt11se1OnUjOYQ9/9\njN+OtgvvICwP6WkmttDHMyMIuySZ/PK2Ee4R9zj0/Cg5P19VcVxg04LjBhZb\nVokZoX71o1CboX/uEH4q9/Bev04mxSjYF1r4hRRK4jrUdhNg6g/Hq98SaArP\nas6Y\r\n=pfaP\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ecfb82343d55b0ec747bf9d9697012b945846c5c","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-13T09:30:51.961Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.69","@balena/jellyfish-metrics":"^1.0.341","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.2.0","deplint":"^1.1.3","ts-jest":"^27.0.5","typedoc":"^0.21.9","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^27.0.1","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.1.0","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.7.7-renovate-major-external-major-ecfb82343d55b0ec747bf9d9697012b945846c5c_1631525589947_0.2683588312275902","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.8.0-h32-safe-migrations-7aad63183259042f41abf163e0e692dcd4d6957f":{"name":"@balena/jellyfish-core","version":"5.8.0-h32-safe-migrations-7aad63183259042f41abf163e0e692dcd4d6957f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.8.0-h32-safe-migrations-7aad63183259042f41abf163e0e692dcd4d6957f","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"260c95f8194c3f3d393b192da07d8cbaf56942af","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.8.0-h32-safe-migrations-7aad63183259042f41abf163e0e692dcd4d6957f.tgz","fileCount":4,"integrity":"sha512-RkTGy9zQgWUCZtIo/aSDoSOm0A9AHxJ/MOsZ4rLB8FKxe3w572gQDWK5rzFotNJUkGHUiKlaNVPHXDhBaC4+aA==","signatures":[{"sig":"MEUCIF/xJknZBa4lR8EJJIDGUWMRCWO+Kck2uFb53lAYJsRfAiEA6DpGl5Wle+Ae36vvYKcYDMHbIe26aQeZ65HocLhdMoY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":93609,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhP3DSCRA9TVsSAnZWagAAgkMP/ApzvlyUSljjdOgn0tA2\nVLhA2bCCxZeNR5IAu+bZQRRrUudFpo3zANKj5rbSvdg2UtPnlNFgntZBIOJt\n4owFNR7StYGUg5eBdLbhliOWHUwZRLoNSqoWfe88QiSYvX5kfWZ9jPZXrNkr\nKwIq7fQFtr+dH8Gn0Gb6Hk+VjHwJiY5tuaqk/IuhNtxMKMHdaXyrbo5bkldy\nFdOAC4bKcX2gSqLv1liVBTSjty6yaSD6xKovm3QHns9eL4uCqiOpYIQdP3vb\nTeD/irCpZje0f70BHekVOvp2eRRnoFmTde46xzjKw/Os87BvX6278E2s6G/9\nNPLeGfHlonoUOB/xVd4uOIFx/RqWiQbd5SJjFB2tz5o0pykX0zpUC1ZAogw8\nDIHpyUPwivZ6no8Y/c8+LD5ZQW+rXDxvcmKVwTrjeY+76CNAYa7/madwUE2m\n/NZvizlXS1UBXQ4lI60Vi0OhZOXCO6TiKYZLDN0mTgdBsC9m8UNGyJ/+3QaD\nwbAvUoEle+2tUAWniE+S02XkPmKV0nropCk7wRN9jNAqHBNjyK5OWWGBi7Jq\neo8GjUUUcThR34+knzkcaSyhACkI5QVodKVWLBfivYOF628viACpUaUzlCYT\nDmlkBgQGYNnbl+IvLwNEHTSDRruVbi3iQlD2ctY9GU+nudrziG5JDh8Xzt8E\njO26\r\n=TNfA\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7aad63183259042f41abf163e0e692dcd4d6957f","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-13T15:37:21.475Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","debounce-promise":"^3.1.2","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.69","@balena/jellyfish-metrics":"^1.0.341","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.9","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/semver":"^7.3.7","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.1.0","@types/debounce-promise":"^3.1.4","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.8.0-h32-safe-migrations-7aad63183259042f41abf163e0e692dcd4d6957f_1631547602105_0.5827530470576885","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.8.0-drop-cards-markers-ee4e493e3362c8d20c8ffed46555a4db5aafbeb0":{"name":"@balena/jellyfish-core","version":"5.8.0-drop-cards-markers-ee4e493e3362c8d20c8ffed46555a4db5aafbeb0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.8.0-drop-cards-markers-ee4e493e3362c8d20c8ffed46555a4db5aafbeb0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"954a3f5da9391e06fc846130a16309b4f1def0df","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.8.0-drop-cards-markers-ee4e493e3362c8d20c8ffed46555a4db5aafbeb0.tgz","fileCount":205,"integrity":"sha512-R/v+5yS4Un9SoupF3L95DN2XzWSz1OazypNoVrjPDxnU4BdD9XY2aB/M79e/4CHpYEtSexAjU1BW5z8kVUptgw==","signatures":[{"sig":"MEUCIQDQYCkv4CxBhSahM+pu4iF0yB7vqxMQZSWSttrPfA0z9AIgUjaavG8XBXdXFQDUzfDI5c6iGJwoJRP0N0v3KUznyGA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":745335,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhP4nHCRA9TVsSAnZWagAAzQgP/3shUu6vw7FjRAbtN+9r\n48bo0Rho5BN5cXjvD6HEGJvDQpXF+Obf1OsdNHYcWjh7rJFmJLXBbQMUXSlC\nKxBbvxG5uWLmLfYjNKAL2SmS63LQh4CAeFGT5Lpomae00Ye8ua4klRla8F1m\nAmiaPw00qeTudj6xNn88uVPH4d8KHF10WIxAeth3Vp8nFTl0w38+A4lVEg1n\n1F7XGW52KT3Arr5gnQJAayTvJ6PaiKSFyyWQzMZ8gIsjak0jaWq6246iCSoa\nmH6eI2uH5qmTYJydZPL0Q/Bp1kDBHC/qAp7G1AGQSx17gTEDFv2/V1HPxXB9\nL4x69fWTBOgsFNsAe814Sb3WRXmVWc+7DpYdj77USl3NQoBT5DIVjwPf24WD\nHNx72VF58Thnc+qQ/XlekJneVCW5ypKUEVk4jBEoDz+RoT+ZEizmN1ksWVlX\n5UOi0YdhOfJ6jr/FfIB9Orju1yMF21RJVqJraCSMP0JkUAgbaaBAcpTtGv/N\nAK2hDaq3S6PQNFJnvQjrVHiYph51YcT+Qsfj3SXv0HTpb6uLK74aTa4UvJY1\nVLaEtM8MhqdvaoErWJ67YFNC953Ki6XpBYZXKJ18Ypd/qqEyzud6HZyGY5fY\nNCeUdTJp8MZmvEP6rF3PgdXOzwlIT9+KSQAdgby8xPxd1642zLw7RkeYi34D\nLzND\r\n=eKn6\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ee4e493e3362c8d20c8ffed46555a4db5aafbeb0","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-13T17:22:02.134Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.69","@balena/jellyfish-metrics":"^1.0.341","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.9","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.1.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.8.0-drop-cards-markers-ee4e493e3362c8d20c8ffed46555a4db5aafbeb0_1631553991263_0.6732069887603489","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.8.0":{"name":"@balena/jellyfish-core","version":"5.8.0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.8.0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"83fd097cedc952e3718e034e6f0812823267e6d9","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.8.0.tgz","fileCount":205,"integrity":"sha512-7Rx5exN6MF+tJHWX2tI4BzKZwf/aXe5H57LoLIMNa5Wr13kvcOHS9TjyRkWPyWjjsNuKbZyObPXE8GFP+quayg==","signatures":[{"sig":"MEUCIQCjkDcxHpq3gpYGADy8PKHMz5AJpN9NW2VxXSEDFshnmQIgdLlk9Lgx7mx+BrDojCXI4pqMjl3Uax2Iuz4K5Oqi4qU=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":745275,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhP4uoCRA9TVsSAnZWagAA7UAP/22XCsoK3yCyZM4NWsRv\nhSPSRtK8rxqo3fGLBuKoBGQMseykdC/P+K/kuER9b4TQXzzKXniHnjsQ2QPR\n5T3dLFNQy+k3dcwbFUMj11eamgRo7YFfAh1N0N9/gavXoB6aDR9R0Z0lLSJK\nLe0QbVSujmUcY0FNTy8NAEkcqJonIhIEcFxCqcs/mcxmPRmG4n13iULJklY2\nb40wBNLjvUPhNMfy+hRz4AOzlcqIVwnzvHkSNkP26Ci7iWOBFn4zLZoDcYG1\nmhlNUvxKgJTDUVl+Q2e+0p5/jexUiKcntIUYVDGHYtr5Mrv9LYzP0oHogwnj\nsOEdBaSYDZMZUEffal/+0M1iigCavlqhwJxvesYPQD0ZqmVPFGwJAdZfbHNi\nyWzWzo40OIk/itvsPB/2J3geJsEktnUc0c0Zi8np/dgVTelVRkHrqp/Wanpk\nv8lZoih1AB0Ex4u+pWWr6mtpy9BvEgrG2GMALQoT8HgrtWuA9KgsKTWTqrhb\npcTPN8ZSizJfxb3xCLgceAjbdNlNSw7lbSuXOStNs1QqIbFSR2t5xOgkkAcX\nH//ykvrZZoVwMfx7ScFYHobqfJhaTFcd6/WUyZ8u6Kj6P5uKqos3bFLrnnMB\nhkHgYN6mUCTbvfU2C+4UWFlGvj9pNKjL0DShuzkExX2i5dLcWJMtgjSqtKsx\ng+86\r\n=wnS2\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2f499d4a52b5d5e36e4508c030b1b96af8f30b67","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-13T17:31:41.465Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.69","@balena/jellyfish-metrics":"^1.0.341","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.9","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.1.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.8.0_1631554472775_0.5288159775102275","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.8.1-renovate-external-non-major-af0ff79581c4aa2b2554b1cc4dc2049abf7d52e4":{"name":"@balena/jellyfish-core","version":"5.8.1-renovate-external-non-major-af0ff79581c4aa2b2554b1cc4dc2049abf7d52e4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.8.1-renovate-external-non-major-af0ff79581c4aa2b2554b1cc4dc2049abf7d52e4","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b9583e32ecf8a0ce71ab876a11113dad40cc1231","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.8.1-renovate-external-non-major-af0ff79581c4aa2b2554b1cc4dc2049abf7d52e4.tgz","fileCount":205,"integrity":"sha512-CPoR0EB9rEvoPzwBdTEj4Kck7MTVvqmC44+W8d+3D9VW5DL19vPOryXcXrIcNtLIEEwWdFgfKPWE2adKOQgsew==","signatures":[{"sig":"MEUCIQCf0gm2e0hFZN/GX6tU+wFZ6UGbCIh1KY5v1rlIdIvoRgIgRM3gSVr0wJu5lmxyR3OZyxlz2fLxk83ViRD1GLlj2KM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":745418,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhP4ymCRA9TVsSAnZWagAAVDIQAI4moFSRqwiDLPtEx5aJ\n5IQdzif3p2m7/skg1Hr2RoTegO2k8s9MGe8Z6q0CBd06O16TrJ4lIbOwac/Z\nqRaNy2A0O9KsYbPrZRJpqijuVjEhPIzfJX4BbB7rMgtsOGF8YtOnGtxwslTH\n1IA35If2Do19JzQ8TDXdD7UkVxXInnpdK2o1ykuhzs5wJQqESGP0XAwC1Rfm\nZmNTwIJpi9UBhj4ZSu8Wjddkm9i1BIFbdnBW0e/g4ExAJOn5uSxRJ4IZd6xB\ndjbjEaK+ubY4shEbJJlyrA9Oc5knj1qYtSer4GnlwNLQvWI6uSFyq4vNi4Nn\n1Emm4jXTDdzU4IGPf12FwyeUXZl22Y2T71RgEYPzmVpdsNK86b1hBSf9Ho9W\nlPfTYuh88zERoGEQEr0Znr8hZOK/YmSqbGSlSsqiLNh2H+rgWo3pD6fcpb67\nfiv6bqxQ78TuP06mHfRvWKS59/qaCHr21oDSkW1ST0skW/R9FrCjppSuiJb7\nzM0j/BjvoD2GY4yxJTZev+tZqi5So6WgWu+BE1gigD1j4UiS98ogDL6HgVku\n4WjoCkaWnCJRo4sNqSnOzEyWjeI0rxhKHbEz2WczesOTgQPQKZh65caH0lng\nMsEtqZmCtEmek1jubMttQQ38dAVa49iXoEiPK9rlHVZfXWg55zW4liaTIyvr\ntVE9\r\n=E0KF\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"af0ff79581c4aa2b2554b1cc4dc2049abf7d52e4","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-13T17:35:42.088Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.69","@balena/jellyfish-metrics":"^1.0.341","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.3","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.1.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.8.1-renovate-external-non-major-af0ff79581c4aa2b2554b1cc4dc2049abf7d52e4_1631554726029_0.06550572929264531","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.8.1-renovate-major-external-major-2771ee3190a6cc8818da0579b39ac715ae769092":{"name":"@balena/jellyfish-core","version":"5.8.1-renovate-major-external-major-2771ee3190a6cc8818da0579b39ac715ae769092","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.8.1-renovate-major-external-major-2771ee3190a6cc8818da0579b39ac715ae769092","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4ca83ae9e073dfba744358783a6843443aaa285b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.8.1-renovate-major-external-major-2771ee3190a6cc8818da0579b39ac715ae769092.tgz","fileCount":205,"integrity":"sha512-/Ka2g/DIlbH6XUnFaRlphr2N8xE4SI52fJfGiDj7+U+yR0LP4Q7Ap+oOLHxr1wKDQe4th4sncRrnrQ333O/UHQ==","signatures":[{"sig":"MEYCIQDVn+Rum0hMPURZVZ/r1MtaKw2Zf2QQpsWMsXHH+kC48gIhAKiJJ1kqEZ8A6vc2XjrtiR/VlXMIVfnusvdzUOAnnd2l","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":745415,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhP40ICRA9TVsSAnZWagAAt7EP/iAizYhyKsUWK4Uuz7/W\nz1ijBhDBlV3gd11d/cm9vWYh2d56/tmhrCSfECY5k/bOFX1U/3eH7U3P1xhn\nJDhdELDscPjFkD529JK4QlCOz/AbkORrcYzX1QAZ/P9eVqZCPVvi/e2r2M4T\ns+9ufAZV2COnvVuKHbat5hE5sBv/Y0x5Llw0ZV7pSih4bHO44tbm6hl/k6j5\n3/5iKCPczj3pj1x+CTEzDT5EmVcXx7DZfSQsPVXgWw4GTszSPOGfhTcgHl3n\nE1VgWWSaj15MMBaAJSgldmRXUXy77DCFb0As2Rni2tzSVzpWN84fqCv+TBiQ\n/jA18WPp2rDqTTln8tXQrodg/4Dk1RHM5JM9Sad8L/4HHgFBWWn+IWZSmMKO\nK6cdf2vhiuDvkcMadJ8YVd0krw6RpRyWReWcwTT7DDVGwfjUk7o+Y7AGkS4K\nYfpPqbniiionjbJvYtWjv/HDhqIFejqSPg9i1+18Sa++bwGAHK+oYhhZMuh+\nxYP7g94DgrKLkn/46JDX7AI5V9MzbwAuYttnMSKx2Y57SJ6JJ9h8nraTQbM5\nJayuF66Xub57GFVhtCGGH/NNfnkzF3CTeXkz5SZfkKX6MdgI5fNEaf2q5gBW\nxXaeLDNg7baTXgVM/dbsD8F6IcXDCHV5ZRsO7vQ/KR0tnLrwiL+IbWtrhtcK\nvOPy\r\n=VCCk\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2771ee3190a6cc8818da0579b39ac715ae769092","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-13T17:36:42.873Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.69","@balena/jellyfish-metrics":"^1.0.341","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.2.0","deplint":"^1.1.3","ts-jest":"^27.0.5","typedoc":"^0.21.9","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^27.0.1","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.1.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.8.1-renovate-major-external-major-2771ee3190a6cc8818da0579b39ac715ae769092_1631554824306_0.2956407874477016","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.8.1-remove-stale-comment-56a3c18f7a2b535519425792b2cdfc11b2555e35":{"name":"@balena/jellyfish-core","version":"5.8.1-remove-stale-comment-56a3c18f7a2b535519425792b2cdfc11b2555e35","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.8.1-remove-stale-comment-56a3c18f7a2b535519425792b2cdfc11b2555e35","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"cbeeac82d9a6c49cc27743e7af03e2995a2ae97f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.8.1-remove-stale-comment-56a3c18f7a2b535519425792b2cdfc11b2555e35.tgz","fileCount":205,"integrity":"sha512-QwIhzQe+QHwYs9QXECZE/9suYAfnEcMYhobnqbdC5/zeWf+376BEYF2vvGmBukTD1T8dcemBVhMDZ61Izx38yg==","signatures":[{"sig":"MEYCIQCcs9nSWV+R5q3Rv2iBPnN4ACPSawnmBbWbMUWF2Ue6GQIhANv2L2pzqXY5m2lUJ2Ztfb+FqFWkvb40BMUycpG4Z1dn","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":745223,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhP43RCRA9TVsSAnZWagAAupYP/200G/tckf3ttyA/ZlpK\nwAG2pFimAoJp6+/QXOumTnlQ/TjT4PGsQUL2ghi+VJnIIoPHe9iydXY4lroW\nBRi01Ouy29iexr4wdxGxHIO0GnXuPyaqKK7UDKMhKEKgJfEmh7JaYGvfDsEz\n8i/Q98043CEbtFpF4FBugbi21qLVnRxNg+qyd0SfSg+NXm33URDQKglYw+a4\nBTVcZVOSVF+RyPUiw4Ak5nJZ5KTg5xSQbJ/9UK/HKVmk6czrhmjcrmRhnzaq\n7iY5wr7ymvSNJ5K8AvMIHaZeYgCD0P8GxGMMnaR9bePdEgG5p7h/LVLfjMIk\n1e4VttjSnzk0YJg/2XLCnHHCQWGMFMqiaOOhuqe1bT98D+7rCjBpFviPKeLQ\n2kANDhRXLm+nVzicc6r62j1aarUAfR1951eP/dgt9N6hnbMqgUsD1Z9qwScV\nm2jqyhS5rnEElWJfUKEWz83j8iL5mxgww709arMLEMjfBaBWfIktrHzZLrnO\nkgz2qlbHg/FfYaNEIogaFTv2ns5BJIJkImWVe+duDsxPV7j20rFMrSY94K+o\nbNxnyNIX9+pSeF92YSrNuVm+Az3IoffOixenZDK8SWEtLZk7LvDGPs7Wo/RY\nkWdFV755QX0V1fSqppaYEOgKn2Oir59TA9EwsbrPerH6x7thmkIkOLuXORCZ\nKUi1\r\n=v7sU\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"56a3c18f7a2b535519425792b2cdfc11b2555e35","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-13T17:38:53.340Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.69","@balena/jellyfish-metrics":"^1.0.341","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.9","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.1.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.8.1-remove-stale-comment-56a3c18f7a2b535519425792b2cdfc11b2555e35_1631555024759_0.8391029883677199","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.8.1":{"name":"@balena/jellyfish-core","version":"5.8.1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.8.1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3b5aed9c878f0ed8f174b67a9948cc3709ef1981","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.8.1.tgz","fileCount":205,"integrity":"sha512-2j6hNemXORBFJeun3vVwtUNRuHH+LnQzL5XH4qsJlAVF3KmPK8GjO+A8S6RnMLuRZAB52zfWgTBVwZ6vMM248w==","signatures":[{"sig":"MEYCIQDThpABl7covykfOG+mkB3DTEbHujaqu5enBPBG+YujsgIhAJlDPVU4oTI55cRuLe8vLpvo5nLNwStUsi/jpVt3Qalr","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":745161,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhP5VxCRA9TVsSAnZWagAAedoP/1wYjW+U65ysCEW7T1E3\nanAC5wOORzm9v51qKyMa4hppOS43v8f/Wz1YGdoEj3FBAljNKmeatEiHcn4N\nYhpa6mRJqD+qxF8qw/w7exIZ5NvyivAIenYw692CZ76mZL3/mAXfReOUT2E+\np/a2l2AFOv3w3k/iwV4ayAZrAqu+2vzCjSlr4t8XIkvinj7piPJ/M8BMD8I7\nbF1zpTVIQyo5lXiYZmclhM9SiJqyvcYOO7zMqDdsFjOKk6B215gDUAeWJmce\nG9zkIOOE46Iwc6+fDNHeeVTjEMK/7aqh7oSjivdX4BHPO75gYCXJYcpVOjK0\ntXRMljq27x06WWziJzW7PIxT1vTaE7qmwaPpYUG7HiDk1LcJ3nGY8dx5KaRA\nAGp9R2Q4sqacMAntOHBB3QsJz7eZcRsilUgBqgutVIiDiPlinPGdnARV7TfI\n4WZUG5ERpMqiRPzZdX51DTXKFBUXIafI+e07ppUN988R/2F5KR3ws5DnPIEi\nVeOnMJy9Z+l0/YpW47LD1hrE1dJFDGuE/s4OvJTAhXprRsCsC7B/PHex5luD\nMvQpHK93JyJcUCma+q38F9jmHZP27w9BeaKvKulybVzmv2DvJj23chC74tgZ\n1+e8o6RXt1YroaNyhhHBHpTgbNfdOs6gXmelRct/g1JeZcn2jFrU+AgyczP0\nEHHU\r\n=T4kQ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2b4ee8abe83f818a13a41d7ab6ca8b5e2f1fdfa8","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-13T18:13:52.333Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.69","@balena/jellyfish-metrics":"^1.0.341","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.9","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.1.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.8.1_1631556977463_0.7311336491642166","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.8.2-renovate-external-non-major-2b01eb6cf3f24b225fd2cbd4d0a35998bb60177c":{"name":"@balena/jellyfish-core","version":"5.8.2-renovate-external-non-major-2b01eb6cf3f24b225fd2cbd4d0a35998bb60177c","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.8.2-renovate-external-non-major-2b01eb6cf3f24b225fd2cbd4d0a35998bb60177c","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b3c681c3af4fe8f839e87760b6ce16446d820d44","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.8.2-renovate-external-non-major-2b01eb6cf3f24b225fd2cbd4d0a35998bb60177c.tgz","fileCount":205,"integrity":"sha512-g5VpfYLEPXELPT2NK1knKUXW060QlVM0S09PgkSs5bK4ZOOAtpqzqmRJd32av5KCwfk0MZ2qIHgQINzMISyaNg==","signatures":[{"sig":"MEUCIEDI2ADD/NlI9WU7l+O96L8LHyDEQEKWz8fJI38rvhLHAiEApPttkrMxcpd3UgkIfBHxEvUxGyuODBn3EyJQH4BkvEw=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":745304,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhP5bPCRA9TVsSAnZWagAAA+cP/iR2A2uQPjOKwKcqmYet\nRRT1CpCmZJgwDk1y8x1AYpCEfDVjrgiy+qkcOmp/OxWDIms/h9gryWewDuqJ\nIANTB1kiP8bGWyni5SPS7Kydo1hYb58P0HaAOnTHU2OtyAOnXe667mX+NmWV\nJmx/vwFKGTfY6t9MG3vjdtbRPMSx96aHLIpRF2xEzBfRsnPBGdZisMKWF1yO\n7czbKN6sDnxPhFineCkLjlnrIic2NeSTZG/OQKOq5bCrt31MWtI099W5F7uu\nPMBXfCi9T8bmsHSFPh4bkXVzR4LIPUJ9GnMHcJpb9R3xYv9nxwO93dZm4Lrl\nENvAlW6ooPrHoN/RnNGsrbYM3lDYHhQbYJxXFld0P0ZmFZLZUEjqjfBkUFyz\nQ8bUkDpaL9JUxqLl00VNAMhz+W84zgLIDaaaYDKNCPJ+PlX2BWSUUpbosU7A\ntryFxXeYpQLhXtJK+mautsOSoCIojqK40XQPCNXxf2P6fUhUgE11KYLpdI4o\nwwqfpBUbuUo/VZdiV8+KrLNqJ46C+gHVorwfFgU3QdwHnTa1ijM+WOH3CWXZ\n5/vdE9XKUSIE/jI7/wbORSJCTt6cr9YO3yumjATjMRwk28DQDZlTWTyP7ME5\ngEEIIarE5PuxKYymu0lML+QOJvZuv5YN/UnoEf436/ZZJuVoENc4L2XgrxZQ\nPVrM\r\n=E3bz\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2b01eb6cf3f24b225fd2cbd4d0a35998bb60177c","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-13T18:17:42.919Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.69","@balena/jellyfish-metrics":"^1.0.341","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.3","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.1.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.8.2-renovate-external-non-major-2b01eb6cf3f24b225fd2cbd4d0a35998bb60177c_1631557327524_0.38885155599402577","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"5.9.0-h32-safe-migrations-4ddc017cc4eab1472501db6ed7a646f6d95aa53d":{"name":"@balena/jellyfish-core","version":"5.9.0-h32-safe-migrations-4ddc017cc4eab1472501db6ed7a646f6d95aa53d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@5.9.0-h32-safe-migrations-4ddc017cc4eab1472501db6ed7a646f6d95aa53d","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"044381b1bbb2fe31ebd7a4e39e5d83ae612bd63f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-5.9.0-h32-safe-migrations-4ddc017cc4eab1472501db6ed7a646f6d95aa53d.tgz","fileCount":4,"integrity":"sha512-H6W91Nup+IlU+H/lTx2GhNE3QxrYT871lS4H0InF0zVVF4LzDgtkEqx5x+o9+qDSJiQh8h4HWuDumC2QfyLL6g==","signatures":[{"sig":"MEUCIQDfj2WwfI/zORAX3nsrglXiVAexHkB7T8q1LvcAF60kjgIgW1CQp/OvyO/ElY1GQVJlrvnv+9AU3QeY4vsZgaQC6/A=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":93603,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhP50sCRA9TVsSAnZWagAAoGcP/Ax281PaXCmdMrYEAiAg\nZoUoVQaQNy5cquZ4pexOr3JVoXeM4slDa3TioPrdCf80CS4P3d0LcBbcUXmW\nbbxjfQDmtB/JgHJqCbMmuuqJZWQBRuWRauytEn1EdWq6mk5OC7AEnFo5OYMQ\nfpY44CA/CNqsVFdiXHxJl+X08fzf6Vo3PbBqN+RKP7G9UdsYxCUXkGCx22nt\nL12LppVeeHKRLfm6ueJhs/QMNJJU8UK87VVaHRhMf9SB+ksNd4GrWuKbsnC3\nlMgbAYaQjJbT3JkrccZGGA6H/YOeIyh7VcRXfuInvxnc/eF1xgTH+ljIA3Vf\nlXyU96VPUmW6sCPbGjesDJ0tF7Movx/a5OAVwtdsLIpr/wvmycz0qHtujayV\nzbBCB4XzouzvtoDolpC9fE8tze84HSl5QPWL7ODk9vku0tejUHnwMoxCQ2F0\n5x6LCvK2IA9D1xpeQ6l3GYLtKWlRfPH6E+6r80LhVKcMtgKhDm9fMQDB1QY7\n3l7sz2pSvJX9ihnPdXjdbGUipmR+Czzjip8BLEP6tCSHrttyOlQ94GbWt2mE\nYsu3SYa/LHEAay59JEu18DDKocP1mME3FV01UtbGqmlvSQHVuv7UdD9i2kvm\nMYTMyZZxt4D2otxCZ5hVQqFqomZr3flzJBxC81WkYpLa3r3KuN9V5D3eK+0X\n9il1\r\n=LsFn\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4ddc017cc4eab1472501db6ed7a646f6d95aa53d","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-13T18:45:53.028Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.69","@balena/jellyfish-metrics":"^1.0.341","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.9","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/semver":"^7.3.7","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.1.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_5.9.0-h32-safe-migrations-4ddc017cc4eab1472501db6ed7a646f6d95aa53d_1631558956684_0.027142816221827237","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.0.0-drop-links-5b6a8b30b9ee31aa9c3b7833dd9592c443a53025":{"name":"@balena/jellyfish-core","version":"6.0.0-drop-links-5b6a8b30b9ee31aa9c3b7833dd9592c443a53025","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.0.0-drop-links-5b6a8b30b9ee31aa9c3b7833dd9592c443a53025","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6a0c8f980c3ed046d22e517ea48055c528a71ae3","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.0.0-drop-links-5b6a8b30b9ee31aa9c3b7833dd9592c443a53025.tgz","fileCount":205,"integrity":"sha512-4OGS1uupMmTfjCtHNUJXbePKHYgpo7IcMEbeoMkbMhzz9hifjUMEPqhuBQ5IeJ2p3CMAV8FqwxBhiov97+qBWQ==","signatures":[{"sig":"MEUCIQD7G+20zY2ejBrOeumUZ4Elub7ZSqv1XycMgx5x/UlabAIgKXZekUE5Io+EpwQg7V9msSIqGsdNaNAICZmNiECAPaI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":741811,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhP6H0CRA9TVsSAnZWagAA1NEP/RWm+NWZY+EOVcRyYCmn\nx085OyL3BNEb+ciJ8GCu5+9xybTpJYCix9zlfZQkLo9ILuXC/C9yAisOaMoI\njfVC/2vBJgJ/bbdC/bOQV8yrJI8PIFKHbyIegeOT1a1XlapHqzJJGsko2SPb\nreoRh0jGNgwmmjPclnTWJYskqPKwh4O1zQuaCMmPVUQg/xAAGNVa8jXwDr4t\nehaAxF8doDGwMaxfX/cyPQr3BqJBikTIDeIylGckmoR8Mag1Br8KxJIepjQX\nBRmfNkIAFoFkO1LOi68qRdJhJD54ChJckWJ/yxebPprsDDY6n/+jDuXAvCN4\nhkTxCUogO2wzfhRpWnS8RQWNkZ7os4VHA/puZBbMyNP4bma/1YA+mah9PV21\nA8xyUQvTkuCj6EMXJlG2mLXAyHYCPIidqhxo3CqKf0oqT8xfc2MIUIRvO0gQ\n5wcccOIR9sewqFU4dWhQulGv8hU1xN9LlmDb7Q67asOc8V2Oz5V7g0kVyRA+\nXpCCNVqnUvpQIGgMFMUGMwl2MXOpe4leTZe7qSbsSV81zn0TapAPqaTRhsVV\nVvSRn397vGdJJaR3j7WnvfTxTyeO1R0cMzrwwQbvGH1XmcTd20XCvBHw4z2x\nMRkZVBWiGitp93cMrQhL1vUjrDGBiD3eeGXQRWNz4r25At0evIdfEiihpAuk\nYM50\r\n=Hs/m\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"5b6a8b30b9ee31aa9c3b7833dd9592c443a53025","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-13T19:04:42.745Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.69","@balena/jellyfish-metrics":"^1.0.341","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.9","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.1.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.0.0-drop-links-5b6a8b30b9ee31aa9c3b7833dd9592c443a53025_1631560180449_0.20401596534080157","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.0.0":{"name":"@balena/jellyfish-core","version":"6.0.0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.0.0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8f8361c841522ee686cd4801b7ff4a0e08f94656","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.0.0.tgz","fileCount":205,"integrity":"sha512-WUVmxjmCvoyx9CCYBHJXE7dbIEkROT6zO63Zvm3JxkFkqigKPcq45JOM7sHH8XG+dvfzliSvc/D9aCZiXdRL6Q==","signatures":[{"sig":"MEQCIEUbnRjFAaQxF9AR/m/JUGLjQyIoi3sy29+raShGNnEsAiAHs5JOLcoGN3wNSQKcik7fohCrjYgINP1gyFMrLas07g==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":741759,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhP6QDCRA9TVsSAnZWagAAbMIP+wbgiFHeT1m8Dp1bqeWb\n6FBHDTXlZ646DWgG9KNHiwu58H99kpQSNSnonpOkecEQQYxpbE+OXvlPKsPL\n+I8Hx9b9krFlrSzK06j46I9x2WylZOqNOCB89ISr5G7ALGKfJqU0QrgAnr6Z\nF8xUaN0e+LPJLIE7OPeF4WhYoiewbibv27wXmcOgesAy/8a5+nGSPvoIh2o/\n+ugFO2JtSWk3didRy0HvlxnlTxmEHHujcPC+GFMLLBDpWIJBzKj7LaY+/WUa\nPv9TLZ+nWcyRV0TwQCjAWr+2cIfiIVOqWdXJK1ObkoF7iVnB1UGY2x4b3S7C\ngjx53sepKS+9htut0o8t29lnTeSGMicIefiUTCNYF9Oos/REN7H/EIZjusKz\nuUGdw6OozdY0/BMT1ZF2CMgojZEoH6hMMRGIsKqvajSlp5kkXdhJ2Y7cn2mF\nKHp8lCkue8l3JOg03L6EA4/OzSVI7CQgg0ol3PDCMJh4Cakrr6Nkfasn/whO\nzdorzppvg/cmiF/jDKZFnXhWC+WqLVdcSWrIfmeYDqVnPigqUqiK5dL3ESq0\nZz+qCNtIfei2UNq44bQ6tRjaQ5dx49aD2n0miJDpGjZu7SeiiYmM7yul5AV3\nBy8O0Df2uf6JhAoeI0pp5+vzhQArWFJElSNtH+VTUcnfdw3NejdBn+C/1qnX\nCc6p\r\n=C+4P\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d7ce0164d35b7f4f1bf41a2c922b77a0523b52a8","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-13T19:16:32.357Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.69","@balena/jellyfish-metrics":"^1.0.341","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.9","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.1.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.0.0_1631560706810_0.10866116977998308","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.0.1-renovate-major-external-major-a384c621ed719b0e95647a047363d9dd236f0754":{"name":"@balena/jellyfish-core","version":"6.0.1-renovate-major-external-major-a384c621ed719b0e95647a047363d9dd236f0754","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.0.1-renovate-major-external-major-a384c621ed719b0e95647a047363d9dd236f0754","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"629c5e4291d631a52331c4cbc80afd8e80ae802c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.0.1-renovate-major-external-major-a384c621ed719b0e95647a047363d9dd236f0754.tgz","fileCount":205,"integrity":"sha512-V7qYhZLl/BdWfsVm4Xa+KAPSrU+9kKduHwZuQzSauDtaBhdPg5aolHZalUcaLcto3OcjA/5+aibv8ERTmqTnDQ==","signatures":[{"sig":"MEYCIQDBi37MounbQH6wxH3wADrFvMxFicbsKfp99hw4XQ1JOgIhAIBn+J7eyrkswnGHj893iy/LziV4omnB4YZDhvB+xPnX","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":741899,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhP6WHCRA9TVsSAnZWagAAix4P/30YAcRot2Gyv2jyBbIW\nzw23KZEm1rXfHYd+PKVAQ1onJH1GZzTsa6+VDc4ixEeL7bIDLVAys173aWEB\nTTZyupympGNRET/ln1vuZCOsht2D6dSMvQBK7A4bmCUInSWQbtgmJuyJFv9v\nn9D7/NQJzBe1TZqh+qjD1ApUf1VHm034wi50/uizaurfVpK4lIzRa0MuORtr\nvwv7HXrB5CA3b1ChaGoNyeVpxW4FgdBQ4hwiiLfgA9blCw4hDKseAGYZsBVU\nz2NHgeoYAAHLLzcIezScTPHey1pxgfIi+GXtupGsqfqi0wVx9NC5oMfmJFIy\nPsp9t9swyXNhbYF5VwFOITIaSPo5hoxlPGZ+FOA6Iq148Dkf8kfANBFUYkRB\nRGw+d+tjW9KLIfQIfRGrUhEuAUl18QOPusBUs4lFUhowZIaInuA+KbnwckBe\nYAK1wT4WUi0QIT2C8d6nmpvEm8+KhEWV1OfjiOBHU587BOEDsK3Ma90FyjND\nls1/JwhMvkTBqITSJ/bWzW9EsezjjEEZTtZFexA1H6O+ORGc+YoLhoLW/5ho\np50Ta+FiQiTFOfZDYOAgTdF7dExuS93USNfLGp3I//qA4Q74xpRoNXG8PO6V\nSl3wTa7gYbgzgLE5BnICbByr2neyRF20bKuZxwxj+NqUzdOaimAW5szHSzFa\nVn0O\r\n=0zAh\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a384c621ed719b0e95647a047363d9dd236f0754","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-13T19:20:49.949Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.69","@balena/jellyfish-metrics":"^1.0.341","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.2.0","deplint":"^1.1.3","ts-jest":"^27.0.5","typedoc":"^0.21.9","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^27.0.1","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.1.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.0.1-renovate-major-external-major-a384c621ed719b0e95647a047363d9dd236f0754_1631561094920_0.6698549070415076","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.0-h32-safe-migrations-779957b6fd85ceab37d0080c45c9fc3c5f320563":{"name":"@balena/jellyfish-core","version":"6.1.0-h32-safe-migrations-779957b6fd85ceab37d0080c45c9fc3c5f320563","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.0-h32-safe-migrations-779957b6fd85ceab37d0080c45c9fc3c5f320563","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9a80d6ab4939361cad6eceb5e8d18f43b7b84bc2","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.0-h32-safe-migrations-779957b6fd85ceab37d0080c45c9fc3c5f320563.tgz","fileCount":4,"integrity":"sha512-UBty7ke6dT6K4ELPVVxrYFAz7b/+Bnk2ZvzKzsjXqjNSq5lIVFF6s3KOJ85it+jeyhHnNkszV9sUMbCo4uS5GQ==","signatures":[{"sig":"MEUCIHwfTlEVAFtE90QgIh5JPUHQ7QhUtepGtKP0rCMIoICUAiEA4UssGHb/FtLT3qCA8vIUlBS9MQhPCaslSQwZ+uYrepc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":93670,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhQEXDCRA9TVsSAnZWagAA8dQQAIGeqXO8KXjFnV3u01El\nh08V//DfE/254KAOnweoBi84d55AEkEZXUQmrVrXD3swpZFH+t/WMZnkip73\nWjGp4wiku/iLuFaIvd+5y310DtKiFyGDrdr2kgboCKMsPdkf8mg3Us9O7/IV\n27LCOrnz5gxJ6A1IV5eD9ENhFc3zYKXYnn1DTk9MDSJ9h0mtnbqcP7r2Xaqv\nTndjuYMDoHxUWmBalEVk+xkkDYxu/DAxXFHx3QSZY7MUcgOnmPjpMTJVNrU0\nPc/ReT3DMx4p105WVNdhWACvVh+uJgqw9rpM8x8/i7zIPTmc7IXE18VF1Qmp\n+UdbTocM4R3eZQOJcZZvCrxPwDcm0L8/Cpx7cIHIYZwguLz/+PcwpHzKYnmI\nQE+k5NKgSJBrkGxSjG8Wt3odmE5X5tcrUpIYUExX8EcnIMOxDf13JiQmqxGl\nI1zPC5jFFG+kuxv0yhq0tqPkydYKvEtTMGvX6ZHvgT1LGnk6HCCaOfaHThnX\nZPFYJaqwj0FHKQM8G+nW6Uyay72gQwo+ZS1g5SLVRd7wi9hfJ8XtDmwdRFJI\n2SqSbvN2UkW/UeLzizcNtK3KYJ/xxR2ZeR2/pt8khvkU45wvZccBczWVg9Nq\nLfp8rBHXUVeeYLE2hD/X3ZulKUVyM26HL1LiWaUYlbyVlk16GWaJUWJ5EPkX\nPbn3\r\n=k249\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"779957b6fd85ceab37d0080c45c9fc3c5f320563","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-14T06:46:01.917Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.69","@balena/jellyfish-metrics":"^1.0.341","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.9","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/semver":"^7.3.7","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.1.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.0-h32-safe-migrations-779957b6fd85ceab37d0080c45c9fc3c5f320563_1631602115378_0.6296980396931666","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.0":{"name":"@balena/jellyfish-core","version":"6.1.0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6d05adcbe835458c76e75afbcbba1070743f6017","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.0.tgz","fileCount":4,"integrity":"sha512-V7pJF4KaY1yX7fGgOK8BfZ1rZCsuqDc7on7Fmo0EolV/whfUfrjsJqhS7zKVT7ILBF7V7Ee69j0EZGwNacZk3g==","signatures":[{"sig":"MEUCIQD7UJ0ua77p5tK/br43u8iyawI8QL7196qnuMH/8l0GUQIgfYYJ2SKoH+TQmYsODX2DNmGuZmMUUqcoKYT6uaFkLnI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":93548,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhQEepCRA9TVsSAnZWagAARkgQAIzfzPJkvUOW6q6C0TbD\n5d6qlPi95tFqF5SOA/rnhVIOFzELjIzHNS4WeYULEQF6RQDtgQzYZADRFG25\nyg4UjRSWeVtqsYu0H5Uzlp9xgv16MbZNpUArIzs0yr2ESgc5NvgozJGSnPz9\nhQRP9CQzcuE9u+5DZmhArSqWkZ/fS9MzG+S9VIW9ao3eJf6acGxUzyA/cZ83\nKwj0twWVI85N5lYFBUukWEYqjWlNLhtYimsVHZqpQy5yUyWCO10GyfshzaJ6\nby1Qng6qxwjBP3PNhxX86KUpaCLVF0a4wIpPeFf0PoUF6PKw8WPpeg0XmGc1\nqYUH+71RB8t+B1SoQtxTMlvFTVUh3bG9iqqydKhfgj2BYrcmsNS00kqr1ear\noWTpFN7bHH51AO7YMEQ2Dlym+Ku6z92mY/VBHjoI/dZD7HuFA1kTS85ljWJq\nWj0F6cG/kFab29/lPIBV7oDGwrhYwZSFUlLBlQn/SOe4ueJOgRAEERA3+fGN\ngs0qb3yRjOz5NXQ9SUfXUPRaqm4BzGvzhZLL1GC/x/+uYiqX2SfiKDB6BQZn\nbdVmDdbgdM9B/VEdJiPbVvO7O5GjDzK5ysqBm1HhVXufl1uPrClfDuOID3mk\nzkxU0Brp/p+LK9tdyC4YVvDo/PFQY3gADxvUrE3Z/pcxOxQ3UGaEgG1AMbE/\nIMM1\r\n=Lp5H\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ee2c569d14cfddaac5df481c001b53e3e2f276fd","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-14T06:54:51.611Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.69","@balena/jellyfish-metrics":"^1.0.341","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.9","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/semver":"^7.3.7","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.1.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.0_1631602601281_0.23537416182300652","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.1-h32-fix-empty-package-09edc4ceba22b0ad7c2aca0f577b58e85c1f47d6":{"name":"@balena/jellyfish-core","version":"6.1.1-h32-fix-empty-package-09edc4ceba22b0ad7c2aca0f577b58e85c1f47d6","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.1-h32-fix-empty-package-09edc4ceba22b0ad7c2aca0f577b58e85c1f47d6","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3de5b0daa171914d588d1fc9f1318cc95e867be2","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.1-h32-fix-empty-package-09edc4ceba22b0ad7c2aca0f577b58e85c1f47d6.tgz","fileCount":205,"integrity":"sha512-KswQo2+cPHYmxH1D6O9XxASGakQtunTul+fwbhdqFUvc+ix4ZXJYdm8WATfCOmziL/ZWpZJZxNQIcUx/LTW/GQ==","signatures":[{"sig":"MEUCIG9tpsMIFkNZWs5tokyUkB9zigAk3w1l4f0AfPxBg8sDAiEA6YPdl8QQzcZ0ITIRA3FoT1H4fOuFAx3uiEAVPPFPQ3U=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":745775,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhQFcBCRA9TVsSAnZWagAAtsoP/19l4JY/OrqqQGvdcguH\npOVrklD3kSALUEOiWIqkqFKZteisBiK+vrQ/2lWWshjclJtYTzoIaGQdMHBn\nBTFLkeJ3vALNQxWHZWTBu37kaKN53HvJFeQvGwmGVSMj/MfcF4kRVTSW4qA9\ndJoOvh17ISEqWUtZzm2Z+VNtMK7mTGlxwGQVD6ZjIUixeXwrY68bdbVuJu3R\nUO9By+seDT+tTsBKkeQ84Bu5NUlUPmYpIVaPCZAdlKV63zmL9jKpZnpZQwnN\nJJ7itYhgO8sp+Now0aHJAMZ3axl/AQD4/sVn8Z51cg8Z1aZDqDOsHKWhDgoU\n5McOfUnYfdmmLJO1di24/qH+o+xdNVf+aOp/qDI/SsAnB4/PGeveWGAriB+r\nhUPo+S4rweph6s13dE+DWudX7Vugq2qeIMxU4ChVfMdziTgAZL7e92donY2/\nY2kmrWInEsg5Ii8mlK5HVUlGJvPdKMYM+xW01jdWQa1yYQDniWi3agO0Q2Pr\nS/oEdk12CflFhxrSAzowZIiROHYgevde0OQfZEK3CR1FXrNrWO6PrJ964Sv7\nhJAw1fLZnkfTxo/Fg9jfFw4qiiP++ssXoJLry3+tFkm/fK93JC2OycU2xo0w\nwA3MIlxej7NOrXkGBOoWSe7UwYWHaNaoj207R7MJop1s+pqiRp1fEE9fjHoi\nGcZW\r\n=fzFG\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"09edc4ceba22b0ad7c2aca0f577b58e85c1f47d6","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-14T07:58:32.002Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.69","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.9","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/semver":"^7.3.7","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.1.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.1-h32-fix-empty-package-09edc4ceba22b0ad7c2aca0f577b58e85c1f47d6_1631606529425_0.9767152589107146","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.1":{"name":"@balena/jellyfish-core","version":"6.1.1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a9d44703259054e22e4d7fa8f911609bd52c8f8c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.1.tgz","fileCount":205,"integrity":"sha512-VI+eGDTz++E7kbUyovS4qCtTQlJyqKnHXF8zE7zqAgUUatvKqcawmmcuE2lUzyRR4BtXCuOu4oeqkDrcwVn4Qw==","signatures":[{"sig":"MEUCIQDXqTB+u8pfD2dK21zkNF6KqdGnphs+MoI1lbR75z/G4QIgX5YHyoevhPrP/+27gF24tOECZ1r8Wt5t15wpuB2J6VA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":745712,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhQFoDCRA9TVsSAnZWagAAaSYP/iOtCMYputKDWhK24194\nuC8t0s/ZhfBN5lQeHVRce8d1i1SBTgibKjwKya+5QmZPriwvgO8zxzr/c7Nw\nF8+6oaHSfTWRIdkyc6DC5rRwprSsEOJXMQeUNBpwyMThii6eiK9psmk4FR0/\nHksoeu4XWUZRJaSHWjb1D6KaRloq1gW/ddyDQ3gqodybiHISxskNxmbV2mZ2\nycyNi+tyVLfReuq83vEDEQ9B/1hvCSepuMvFAw83ymrcDrPDYpyjh7njBEtW\nTeNgUaggZ6Vf2itj3uBZO0ZEoH8EKUQeqD5GsZn93VGb7Jwm5cN2ssfCo52T\nQW3bbW8CFHT6Q50Mk5jnYZDzVl0c4pFiDjYbv3CrGk+T1hkKZIKFnf9+1j7H\nUDgM2kx7byp9+2V4yi2lqlY+1ZuORSFPzL64IAEK93F54qv4u/2ym3JWCSN9\nYn/WIWr8du5BTTwERQG5PmWYWjAsobmz6ehOiKssfbNhIbXBrknCIeXU98qn\nIUTwW2cxN97PGApM5v4szxusSWRU1X1zgFtk0+7j0bSWRVXJeKdog7RglNR6\ns4RUIBA5qJkWiG/a3oHsL6qsBLFKa8YgEkGo06Hc9ANysi+3P5rHU4LvUyne\nZTSwdWKpMr82rOojGVmvxRDg5CF4oJIDovOKMucOpGbnCV8jZ4ZHE7QqGS1P\nzV8g\r\n=Gwzq\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b3b43fa6e6eecc3d54f2295d27a248bc8b13e901","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-14T08:13:11.884Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.69","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.9","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/semver":"^7.3.7","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.1.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.1_1631607299751_0.35351981733141846","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.2-renovate-external-non-major-fa3e6362ec56ebf9d440861cb85fa84064c5df99":{"name":"@balena/jellyfish-core","version":"6.1.2-renovate-external-non-major-fa3e6362ec56ebf9d440861cb85fa84064c5df99","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.2-renovate-external-non-major-fa3e6362ec56ebf9d440861cb85fa84064c5df99","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c1ec5b36d9750922662e2052a0967f935a775f53","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.2-renovate-external-non-major-fa3e6362ec56ebf9d440861cb85fa84064c5df99.tgz","fileCount":205,"integrity":"sha512-muETT5dXwD4sKSJAnfbmgPD+caLoD75tl+GUHK4RJ/WjetdS0YN3olGkEiqPjrLb7ZmaqniwP9RY1GShdNYm1w==","signatures":[{"sig":"MEYCIQCGuQqnts/NY/gg1l5yqFqn8/ftho9D46aMPSZyaS7xMwIhAKH91CKipPBVv56BIU8pRykFCDjjThFFwPm1dcjyNr5T","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":745855,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhQFteCRA9TVsSAnZWagAA7/QP/385xdAFdkod/+CEHqxX\nXbH6/QB/9fZlGp5+DrxBFnSMGaOxdFw7HXULk218F1T0slfCCO5i7Qmk82eT\nuICkxCp/vT9L9jstH0BgJjw+6bSBg+VNWlY5ALpC6Yx7lHRoqAsiuA3KJxpL\nkik9jiosOUhs32uTOV2CNlWhVsBlwO2tLEWXF/FWZgygPCpleNqXxnmyO9Z6\nISp6c+fVgvAmUEuBjefV+tqAft4hv5R3rtVkwl+pMRkaF5vdLbh5GUNk8OUq\nnSNKabZ/4PgcHM2jfryo6HFYxE9+0f9FrLzOXy3XRL2Hjdjysyd0UcRMQN8H\nb/Pwddx+i/aNOjv3t/+7FYbqadyufmsoiTU8Svo683TbKTGqSDmXv3/xdP9A\nak5TSxzpFtVN6S6Dze9rn1V2yJrqQ5QeGXR4UVBxbQ7l1EHdayjKoYPez/gA\nfKeFEGzc85JzB3Qd502Du3BtwmDImOWlHX8Mosz87oyjYhI7lYalhDvWuVhU\nmhXwUXnogn5sBSNpgzuc2ryTtBYY7JoSfscDU6DVyfg+ot8uD0enwGj+aLoa\nEYQBTlMNUH0ntSkf3p5iIgSMKDclOmD2qxF/hKn26PNeOFMMTPaKICbSkb6A\n6CQQPQYl9KexjF4v6FAAmZC8Y/PvFKUhe8bJKRafngEHmZrnUzzzTFdPlxw5\naOS+\r\n=su+B\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"fa3e6362ec56ebf9d440861cb85fa84064c5df99","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-14T08:18:02.343Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.69","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.3","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.1.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.2-renovate-external-non-major-fa3e6362ec56ebf9d440861cb85fa84064c5df99_1631607646742_0.4397077237113225","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.2-renovate-major-external-major-e5d6c49f1ae0e85df2b197c20436fc3c9c2932f5":{"name":"@balena/jellyfish-core","version":"6.1.2-renovate-major-external-major-e5d6c49f1ae0e85df2b197c20436fc3c9c2932f5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.2-renovate-major-external-major-e5d6c49f1ae0e85df2b197c20436fc3c9c2932f5","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"023f0afd2c8df37837afb5f228acace481fb68b4","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.2-renovate-major-external-major-e5d6c49f1ae0e85df2b197c20436fc3c9c2932f5.tgz","fileCount":205,"integrity":"sha512-ftZWGj0BYU798lG8ZKymrOl+iRcP2Q3XOUr6LszX+LpAReC9Mhg8rGmxe1l+3VRkowCrUnE5rD2pjIgSJ6DrJA==","signatures":[{"sig":"MEUCID0JAt/AMf1/yH6sbe0iuCa3J9679qVN63WgLMxfEzKIAiEA2HMrHByzS0H981YrHeXH7Q3craTGwJj/MyLrb+WOdps=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":745852,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhQFuHCRA9TVsSAnZWagAAZ2kQAKO/CjSO/uih6otESvGO\nsi1XkbfsnSG0h4jJfzcMCg2kNmop2lmRrvJtv/eoraPC6LNxZsTAp5q3lxZm\n8HMHBwR/8ZG/uDhaiZ5i3rkcYW4Fxtl+b62KOZvupVFKqMrnltAOoqg41W7H\nYN/2513H3/8VxhAnBM/BCPc2Gj7UCn3xxpGV1hHiHjxfrm1guQUythsu+mCR\nNDDRfCJeJrboK6blHf61uHoDFWWkQUnTITnMkE+k0tGsd0UZRi4KKxKSubgb\neYYRQ5pHMkVwgIt/bWx3rzdRev4MPulLZ34zJfvd9F7aRgtBL1lIV2NK+h8R\nb4ivmEVOCiJZsN3++2d7I3n6jU7hJ5jTZEhuvdYZ5+m+TuftP8EZxhStSVSg\njSlAKKFlh44OJds8cBSBZ+8TIrQYMTChkwOTNXdl5VWbMXrxvcs+ZVie9EA8\nnfwNRPodI7dS597PeaLIBC5wl2yNC9Y2eNePqLFIXJOdjHOhj4EKO8WGkiVi\nZAFlxBD7IkchIcxTGI5k2HftdOVkchC/xGepsV/lhgCMhrs0rHZpREclTY39\no6xrdPJlRE+rwMkrk4/4U5xX6evh1cVJLbhB7An8NgU94g/Q6fy3IUVeSnJw\n5j2SylHZ6po8DigBAYHjg6n4PhX01VxTbe7iAIuc4hhHP/LiZ9tV8Aoi6ZWP\neCK6\r\n=vQm7\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e5d6c49f1ae0e85df2b197c20436fc3c9c2932f5","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-14T08:18:04.919Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.69","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.2.0","deplint":"^1.1.3","ts-jest":"^27.0.5","typedoc":"^0.21.9","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^27.0.1","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/semver":"^7.3.7","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.1.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.2-renovate-major-external-major-e5d6c49f1ae0e85df2b197c20436fc3c9c2932f5_1631607686807_0.7423283461798953","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.2-h32-fix-conc-db-setup-34a0075ca0576df613b2af9c4d76d34b4e7a7659":{"name":"@balena/jellyfish-core","version":"6.1.2-h32-fix-conc-db-setup-34a0075ca0576df613b2af9c4d76d34b4e7a7659","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.2-h32-fix-conc-db-setup-34a0075ca0576df613b2af9c4d76d34b4e7a7659","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4d66591392ede315ebaf4d2d90c886cd6854ca83","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.2-h32-fix-conc-db-setup-34a0075ca0576df613b2af9c4d76d34b4e7a7659.tgz","fileCount":205,"integrity":"sha512-g4gn1LxNqF3R/o5iUYse66Dnrh8OEzsXJRddP7gNwcMs2dLANbP0Jb5Bfj5fzVNpFWjaMEjNWhU5ArQNq4FD8Q==","signatures":[{"sig":"MEQCICNrGT6r+8pC0gOy3s3XHYV3r4FSw2A/iOnwFGDInh64AiBS4OUfohoX7OCym51g4VZR4p6R2sm6BcMGGfUhLd7ipA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":745878,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhQMMJCRA9TVsSAnZWagAA0icP/AgIzHnT3FDIm4hD1zJH\nvdvsjsZzdPYT1CUiEGSuAuE0NYz0dcB3nlxiDP1N4SJ+v4QTwz2SOHzPGiKn\nfAbqBORuTI2BIqFGWVY5dgtO+eDmzLFz6023TPYabrpgsOeb/eHKawTqt8X7\nIXfOVqWg+XsHgFkXA5IStBJ9mTmM0utnNsxKcBJYKj5AX9GmOh/WAgK+q2E6\n/uE5yHhThi9tzYGsYv00wdM2qoNlNO0YPWLzIRzVgsKs1sg52B3TfuYgCmzj\n3KYmT1NZCXk0g1JQCdRKA1OBDOhJ7/5piPEqkymoWY+xhdLOwEbr9hmHhWlX\njp/9v95W7dbsE7zJjQuuiOXGyGZe/AkNAX5E/eKodu8B4Gv2/RCVOWD/a/lp\nCA0HHn55AiJFEpmSkf3IvP/zPKJ3JkZ77dFym7MlHbmO1lGfI2KMZIn287yD\n8Boycff/jD/jIVCOhW/icrBLiVt9SI/xpIq/gzVOlHOVsd6J8Eq4xst/Tmt0\n3/HPzRRoA/fS5ybBdNvL3e5BLiOj4DbuoNMrRP2mwe8iev0tGTzpyXF68IYg\nLWhk7/h0fKdJHEez666lVc4xg/lq9quEmJQuL8pXalxDa8F7M/nCOo/SDyoZ\nfXiC7T0T4M61t6WE0qLMzTMStyvgtD6OXMPhB2KtihID+7nfcoBWTE5ftGF1\nbLgE\r\n=uS0z\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"34a0075ca0576df613b2af9c4d76d34b4e7a7659","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-14T15:39:09.571Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.69","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.9","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/semver":"^7.3.7","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.1.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.2-h32-fix-conc-db-setup-34a0075ca0576df613b2af9c4d76d34b4e7a7659_1631634185346_0.7499289136920395","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.2":{"name":"@balena/jellyfish-core","version":"6.1.2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.2","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"df24b1bdd89127f68d84e923e3169fffa19274b7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.2.tgz","fileCount":205,"integrity":"sha512-B/TstW9YjVwDvut2ItoFB1Iql4Gd/Zg9CkhfhUYtuo9vaGnsejevY7IXkPG0e3EyFbZGJZzWy8Di6l3fiTYFUg==","signatures":[{"sig":"MEQCIFUL+mM2D73dtH+yPfZ2F1O9XM88xX3zoNbioDGv823JAiBQcfZtctxI+tTlLB0zP9b5NMTX9p2vGtssE6B3N6Xmug==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":745815,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhQMTECRA9TVsSAnZWagAAaZ0P/0OmJFqtb4u1R+LYG+R+\nWdyGyfeE3X9g0AIl7pHyYPTL1Cwgj3YQNmvSSRnzA2Y36EdkGFUeJZxKCJDr\nL1RPJ67rLL0/NYeFN61dBIm1Jwdn/8NUojRA1a1il9MB6uZf+iX2LMdJD8ji\nIb6JMWaJ4HQ9oD/OH1toGHltqR01Nk1Md6uNfc/X3m/I9Cbp+bN8qn/qVjki\nHxCEGC4WCSSRutdR+C/lzGRWQzJxBeO2GXxXE+4omvdWsXq5aKBODwakLKFS\nf3n3KBjjQSud7bGHqflue8RbOY5cgW5+MjzwszHFaHNTBTlL2EvDc51PTa65\nTeIsZIh/ZbL4OoYiJ4eHSzRTx7iUs3GDeAb58tIFBPbotN5AZkEvRQeQdGQ0\n6fwz0FlaLDtAMUXTeybd1iYZP40I8QzA4ZxEBgISq5Qt4mLfBsLpxnULNYYC\nBO/rwvBxyHqYJK/sqUD/DzaRGkvQyrbDxO5iSoIIlJ0X3b8v3EokUEkCSR/l\nww5p5hA4ldG9Kl3/7UGxigoDPeylMdDn/cUaHWzhB8paMYC2VV0DNXOekg19\ntCXjpEqjShv1CBLa08V3k3LD47Xh67cZekO8icxY1oiORr4lq3KzYTet96x5\nGhAPtlTd36JXD75A8jYQGVor5TSgPuEzn/hVOHWNsgy9viaqvGYNEkBsaffX\n/k9u\r\n=sflh\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"9b69c41b9cf0d02903368765dd387d29a3cac048","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-14T15:48:08.604Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.69","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.9","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/semver":"^7.3.7","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.1.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.2_1631634628186_0.8267938523109357","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.3-renovate-external-non-major-fd562c5d89516534476b666aa96ac5ff2925ec57":{"name":"@balena/jellyfish-core","version":"6.1.3-renovate-external-non-major-fd562c5d89516534476b666aa96ac5ff2925ec57","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.3-renovate-external-non-major-fd562c5d89516534476b666aa96ac5ff2925ec57","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"733b1dae5afd9f6b4139928dc224f82d94674f94","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.3-renovate-external-non-major-fd562c5d89516534476b666aa96ac5ff2925ec57.tgz","fileCount":205,"integrity":"sha512-nAa1pJTpPmOJltvYSXtK7nuAGRsxQfpm5MoEOUUmD4xx1kdOGi4avuiq6GR/8zgb7bsV41AA547jKjv4JzBufA==","signatures":[{"sig":"MEUCIQDD1VnUKNHybZ/yGjLkRwdIjGU1kgIANOG1IFyfpCNeqgIgXJ3u5mvQn9sgCWhW3Ex0UtuBPmPM1CeHGBIRAFncIRY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":745958,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhQMZACRA9TVsSAnZWagAAH4EP/275/xCuYn6TD5+bq8H/\nhdBhx24MimcdpNd4a5S1erjGfL7IHp/kZrfxsTAoIOPXbIi/r0QJfW03z1wJ\nGaWoFlVyR001s16ceJji64Cr32fjj7pG4jageQAMot5h3BRfUQX6dn358kk5\nuCFwdJjbV0HxmTIzK+prUaJWSOfHM7IwLAJMLE7VslEy3XuqJOmRjqwyVgJU\n8uNm1+wpEdCvjDzfePC19pWfS1zy0rK6fzq5IizUMLEfvUNNkn9WPrDN3/5Y\nA0k8Y71LZWJbJxcE2jJ3Qj3q05vAZ9kFMnu8QRE7QQSrtHAaK9qGY5RLtlzp\n1ml2oXmH1nHZhW8Gh+WxRwPL1b0Tw1I0QVuboVy5+IhBjT3YzOMJvLy58df2\nvFhs3U794bXZeV6IkniBDLqVDAppS48Q3fLmyu6HFARFBUmXYC3pFoi2WrXK\nwUx+0k5uht63jiWBcYEr+wxQYw3qw0KexlCCYbflIOtTgLl8ksksfDLBt7Og\nobM7avcoyJOO4DORnY3jcnFlQWilbnBgI4FpX/0NQUikvQWlEerR8x8JlrvR\nFFV2eoOOkTzFe/vcQbyGJ05ZL+QFFofTbeR+uFakEaULdYOdN6fLaZOGGmWz\n+NtyS7xKNw2Bbr7SiC3mtOlnZYDBOAGK2af5Lig2ToNZgPk/mouE/Zw2NLaO\n0CX7\r\n=OeBF\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"fd562c5d89516534476b666aa96ac5ff2925ec57","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-14T15:53:12.602Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.69","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.3","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.1.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.3-renovate-external-non-major-fd562c5d89516534476b666aa96ac5ff2925ec57_1631635008463_0.467804567493719","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.3-renovate-major-external-major-67e786f72254467bacefabc70afcb7c6619bcd9d":{"name":"@balena/jellyfish-core","version":"6.1.3-renovate-major-external-major-67e786f72254467bacefabc70afcb7c6619bcd9d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.3-renovate-major-external-major-67e786f72254467bacefabc70afcb7c6619bcd9d","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ffb1fc03724c5f4b34dcaa179067557cf457c855","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.3-renovate-major-external-major-67e786f72254467bacefabc70afcb7c6619bcd9d.tgz","fileCount":205,"integrity":"sha512-NXlzDiHbvH6wz0fkM4gpWyHL7JE2x8kuqITWHl9HthX/GPSyFJTIOi9cODEXQPUbXe7MObBKtVnCQcCTE1ym5A==","signatures":[{"sig":"MEUCIBw6Zqs6k0hFZY3GBJqsMXzP6XINexn/BjUUnx65DE4kAiEAnH2yESH5w4J+mduLTY/+UKwCfIEnDPr6+CdVJwuXVAw=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":745955,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhQMZzCRA9TVsSAnZWagAAQ3gQAIMDDjOsCwaZn/pmhLua\nExU/bjvR7nbn3t3hK1HLPWGYiiWCgo9Guv7Pn9R8nrtOt7mHas8gyKV12Po7\nkz/rciF4k7SBbUecb8p+pqFh+1pM/Zf1ety0HFpUkaIAyjnbQTjSdrSMW2F7\njUOYj3pG0JTDPXMHX9fRcKksmFD9En7tTPvZuYxGNQQPEO+VhT6ntXrGJE2D\nfHZwTZgjz+oGyKO6DCQlAybK0sOETHBrrOBRHbIiPLkVPc4KzrcpzdCGPHRx\ntfXz0MEsDdjFJ9Eo1xTCJMzyQ7KIkkNc6hsB2MpPcEmPr5N1R0R2DMPEj+or\ntfHV3tGmJnwmgFvjiU0EohUsfHCzyrpw3iLtDy7VoqDwbeSqK7/Z9wmOtLVu\nYkajgdAChw/pu1Yb7R0ySDwnIzfEtDBhG7p0yko/nQDLFKqlI4bHQ4U4aWu4\nyR/wMI+7wm+ZLsShY3ml1UvIjW0No2Zppkhl01NpCrnHCt0/o0NIV1MFSB78\nb1kPRXUkI8ZXi3gU1ba+Bf7YNh4+UPRfs5mJzzsR47of7eiUaSpFEsDICSGI\ngMtfmbYpMpSDEBbRDAu4B7PTX28+xiVjk8rEbpnVGAekRMmKQEBbCI4grYMq\ngXhvVYbmw5WIH0lYtiX3ZbSh+I8berIGw9LJyb14SB8z0h25Fxei218nCGPH\nW/lR\r\n=9idC\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"67e786f72254467bacefabc70afcb7c6619bcd9d","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-14T15:55:02.573Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.69","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.2.0","deplint":"^1.1.3","ts-jest":"^27.0.5","typedoc":"^0.21.9","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^27.0.1","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/semver":"^7.3.7","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.1.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.3-renovate-major-external-major-67e786f72254467bacefabc70afcb7c6619bcd9d_1631635059363_0.868207715772231","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.3-repo-sync-secrets-default-b86c63c60184559e1991dd7e687eaf51cae5c025":{"name":"@balena/jellyfish-core","version":"6.1.3-repo-sync-secrets-default-b86c63c60184559e1991dd7e687eaf51cae5c025","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.3-repo-sync-secrets-default-b86c63c60184559e1991dd7e687eaf51cae5c025","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"82785810954c83157ca3ee80e4a193d42e8393d6","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.3-repo-sync-secrets-default-b86c63c60184559e1991dd7e687eaf51cae5c025.tgz","fileCount":205,"integrity":"sha512-5FVl9vlzYvyHR+kEwRP+toQBjELtEIH4Lfe8vurSQNCfUpmuO/5Gw4SAi/OUZbEUzcZ0JwLQaWe6Q+UxlSB9Pw==","signatures":[{"sig":"MEYCIQCfwpFEQ3o2u/euExW11F4hnhW4cn3ELmlKlKJyxwlD6wIhANQ+ZngwK5y0TVsY/28LeToe/81kUqU6miPNkF2I06Yr","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":745964,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhQY/wCRA9TVsSAnZWagAALNsP/2oskjjxfY8UdNN+Z4x3\nx0NWgLQXd06IrIClSz0T84kKFLEYrnHU/GH4doVNEsRFWrqXLFclGW3rXoIq\nz4r3l1igXeypvxuBlMtqNUBdKyB3mhK79acg2nqpxL5zBZB1HtmDBiNf11tY\nZrGBBAEbn1Fw9aVLgWwWpj8QN4oCFOtxz+FMHrPffXXSmQRYFpuBvSVbfTHQ\nza/GEp9aBALTvV18YvPhUnv2ScqirdlFrVH2KYV6L7GLCvyTf0rKNOsJOzpK\n5YLr7eLM/MWjOFe+2HjkRB1I06SovLuGuJX3rOXh2iTniki85S3naYEFOCvM\nyEOFobN3WAShw2BTGOJe8FEyq2EmIoACjrAGvJnLVscRwDo68TgGaRb4DNnQ\nTimfpcbx8o+OThExRteyQmn8CoXDS65w6/VB1mmO8chXUkfWDNq1PWIgIbAp\nkbsQvikfwTOdcrZSY+hME0q7QFh2q937tSZ/kwE/ikxzYqN92euIb+3z/r0j\nSFe9CUpEcka2LbsmePYN1dT1X9E8mjiy5aqWoi3gjMG39dgCATa4eWNpFmt3\nwR31DAluY+MOmMi26bzhI5P3mSaxEq1zp6+z6qjDVQEz47TDwPG5iuRwEOqr\n21436e+k6lzT6cm8eeSgHndrcL25UMkYeE7fOhFvJ4xYoaTlrFZpnSCZMKcV\n+fyC\r\n=Uomj\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b86c63c60184559e1991dd7e687eaf51cae5c025","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-15T06:14:12.392Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.69","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.9","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/semver":"^7.3.7","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.1.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.3-repo-sync-secrets-default-b86c63c60184559e1991dd7e687eaf51cae5c025_1631686639959_0.678964076146483","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.3":{"name":"@balena/jellyfish-core","version":"6.1.3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.3","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c515298bad6f56081ea39eb594c5d996f2b29603","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.3.tgz","fileCount":205,"integrity":"sha512-BxKVwsmsSjiFIIXAOYtg118VNmz/3vSppL/063AJbXbU3/mxqSNtnf8ouaOJMuoWcARLiJPcPjXewa+Z2SzdGg==","signatures":[{"sig":"MEUCIQC7bliqwC9US+jNfFovy2o/tk7/YGxxMB4pUwAHdCCCTwIgLuQKglNRQHcZyvoE9DVPKU9HK45iBqKsPpze6D9GYbM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":745897,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhQZGxCRA9TVsSAnZWagAAo0kP/2bLQwsIFTlVY/tsmau/\nkSjXNUuKdZBhMFWd5IG11KV/3nlhO4tzcWFyZQUShy1NMhMoz+ZrTe0w4HVm\n0i4IfCh2X2eHX0B/HY+5MvRdeMPEm+AhrFzRJ1Pm/q1QUrA2BGgaLWk1o6br\nz+Q8ziVrrIxnR+chRu+a+D8qnFFxmd3PtfhgVZIkRaGpYJXrhjpyNGbDUGSI\nYftJoqkc0bcKITsukZgvEWFFoPoj2iHsrxNvCrMH0301WVsWyExtotLaxt6h\n8xe7qgPir3fyDXUuVYINJ1MwC1nstN5rOYXvpBI8hrxRo2qCWbpZE/aUiNjb\n5ug42rT4wecRugG4W6Ufr9M7MP0Gfe8iGO8vhDPw4pmwrua0+7QBZY5wakUg\ngUsEjUy5bqu/fQgYGqgaKD1n5575kmhFW0uMtyNhQm9b9tlNJWcn7g51hGjr\n3frXX8+tnm58/xOaSHBTn8ID1xgpNFNsHvJLQ42F2UsYiAe9EBB8f11eE8Ma\nJzVGgqdoatGNScBhuLZrRgECYnzy9Flv9TJdmkv1Ez/L+R9HJ4hD7n9PIPfi\nkBDOZYmeyZqAtUb+BqlIxOA36odkBoYEpG/+BocbnLUu9xcxdPB4zHYZnYOD\nE+TXmdz73xrA+ze4QVJZtJJxnlHWP6/Ez0OHDaK10vPPEBYynRH2dvDejBFs\ntrEY\r\n=qLjM\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"77578e8dea5a06b9d0816abb6a19fc82f71d5a2a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-15T06:22:52.576Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.69","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.9","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/semver":"^7.3.7","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.1.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.3_1631687089414_0.6241441310378653","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.4-lucianbuzzo-simultaneous-bootstrap-d56a042a939264d2993968b9d66fecff0429e82b":{"name":"@balena/jellyfish-core","version":"6.1.4-lucianbuzzo-simultaneous-bootstrap-d56a042a939264d2993968b9d66fecff0429e82b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.4-lucianbuzzo-simultaneous-bootstrap-d56a042a939264d2993968b9d66fecff0429e82b","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"43deca300c244d2016e6cba2f237c06afb8b37ce","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.4-lucianbuzzo-simultaneous-bootstrap-d56a042a939264d2993968b9d66fecff0429e82b.tgz","fileCount":205,"integrity":"sha512-40QpplnNBg4yUSCvGn4Wjs5b8u1dwj/bIYGnrCcMico0Qh2p4nzZA+7UwF+mj1zP6nWsSKyeA5OO6OF/tOctgQ==","signatures":[{"sig":"MEUCIQDjAznDa6cWEigcktKKH2uOSWoIwwz+i8PqF8pJPCIcHAIgQz6qc7dKlhUTXaYWWbZTd2gkBpB5B3CRXRsnccJucmY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":746081,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhQhMqCRA9TVsSAnZWagAAM2QQAJamijJKuE9HkBrBt7DG\nqyqteNuU2c21kZVs1Ll+SyBLDf3QeB4D2GtIfnGFin1zrKSVfBcx1VH5eBfu\n4bCXvyAUXK69LlzfnaevXCTZeDOzeU2zuJfTdmIOPAddee59j/8x6oE7GLO7\n3Y8EupBOkWekCIk47a96x4l12hHtj6GhkZ6UJ+8D845FSq7qaSttkxw8R8rD\nWDBarmgG28LTuubHFJeQ588M/s+MYsf59S0NgoSkehErF1fNARzyNeUMl6SI\nuOXtz+AWXCLGI0OAKazRaVIP3YYxyW0H8JiWJXjQOFE1c7F2zIKoE5etntu6\nWu1TVZiUWJviQO0J5lOlVCueZ5Ot+t7OcKfVmbo+woesb2TOBWkUHheC7yKo\nylwa6rabFqCyVJLpWUOprssRNycDwUlzvuDVa6Hm1UbrAA7LC4RSZ2m4O6kB\n8Nzo562f2s0AD75zqNhp2sYsXYbOgcYSqKQIZChFDF/mk/XWHahG4PT49cTE\nJiEC/nzteGT2feHofbHf9yxlLwXfe/+8GQCvfU9gNbVtspkxpgEOA9y3YSgd\nGX9ZD4fLI7lOdicNQb1WHr6Eu8Kc74mbCmSIiGYUWbh0oak3NpLQjS+UQ6zP\nvA3w+yRIRIOWnvopwsZELhDM9djMqt3MAQjAHrFTACQgtIhGlqmXZCxe/fSz\nD6lm\r\n=+Wem\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d56a042a939264d2993968b9d66fecff0429e82b","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-15T15:34:42.086Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.69","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.9","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/semver":"^7.3.7","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.1.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.4-lucianbuzzo-simultaneous-bootstrap-d56a042a939264d2993968b9d66fecff0429e82b_1631720234116_0.026356777793829833","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.4-lucianbuzzo-simultaneous-bootstrap-84a4f67fb60896a78365f6027d8baa4dc39446e6":{"name":"@balena/jellyfish-core","version":"6.1.4-lucianbuzzo-simultaneous-bootstrap-84a4f67fb60896a78365f6027d8baa4dc39446e6","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.4-lucianbuzzo-simultaneous-bootstrap-84a4f67fb60896a78365f6027d8baa4dc39446e6","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8511b533f3d703ef163da9da0b0fa71aca14c239","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.4-lucianbuzzo-simultaneous-bootstrap-84a4f67fb60896a78365f6027d8baa4dc39446e6.tgz","fileCount":205,"integrity":"sha512-UvlQbxEBucl1EDoON/B0tFBFGkEXNIk4q5VUW/y/P1bR6of9CcD8FGhaxChQd70rxp4HdC8ZFaRLeckHT4xbng==","signatures":[{"sig":"MEQCIHYgv6ual3C9di97KmD4/FnxAEZqaqTVQgE+j6jHcWcbAiBnAO6xkp4FwrH+jIn5u4+ssGGTB5yWvmEg3Sp1qzvO8A==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":746433,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhQiAoCRA9TVsSAnZWagAAnxgQAISEZJFlubd/J/DTJrdY\nY+Vis0IRPuJj4KvTRxn9jC8FyDZMumqnJ+4pi7B6gLPouSVmgTYjR67MCK8J\nFKu77DQy0tK1MbEFv32jv8d/vmhYKdwRkfoDhhEtoskaBFVIaX1J5KmNwFVy\nn8IM25iDxaHHnQXVTjU5tnVdxZScp6cUgglrD5qQ3t2DR3Ul/FveLporhUpd\nhg7sM7oBxZ1kXK0W2YGOiBFidJEbgwlEMisBIzK8TFjYJFpORaxp0EE+jGrl\nU1uql9wh38uXQ0pTnIgBGn84KJLqozA1irZ3YhiRJ0Pe8j/8wEFodwlekEo6\ntrhLHTf4A1lfCViqjxRwnd6RVrGk24EIlE4Ivvj7l4LlXcsXj3lR8rh+wFNY\nbNvfNdmZN6K7CQoNGDFUq1LEWMp+TYFpY+kLdydqrD5bAPum3c0AShbizqUL\nnyTD54qMoB61ejySOk9UlFJdmUZcnaMtP1RfAKAbvmlXFm5Sg09p3KwMTcZA\nv+H72FFSjvBrj4Iq5o8sTh36mY7Wm8Qd43j3efRrzcpK42FyDV42hAos16ZW\nd5DIA0a3f2IpinMnhCYoDwaMVDZiiGzjX5XEZORiF1EDtzLlsNL3e7n/Qglv\nFlkLyMQa3HEo60FiASZQC6X750vuZqOpRY+grnjzCDi5dTv4KN1JCEd9LaJX\nd+Gr\r\n=uBgW\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"84a4f67fb60896a78365f6027d8baa4dc39446e6","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-15T16:29:12.575Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.69","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.9","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/semver":"^7.3.7","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.1.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.4-lucianbuzzo-simultaneous-bootstrap-84a4f67fb60896a78365f6027d8baa4dc39446e6_1631723560444_0.8399098203460684","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.4-lucianbuzzo-simultaneous-bootstrap-d8170d23fe4e19eeb94530618d319849fd7da586":{"name":"@balena/jellyfish-core","version":"6.1.4-lucianbuzzo-simultaneous-bootstrap-d8170d23fe4e19eeb94530618d319849fd7da586","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.4-lucianbuzzo-simultaneous-bootstrap-d8170d23fe4e19eeb94530618d319849fd7da586","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"89e31057471f5f681c5667f5a1fa165520051d8f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.4-lucianbuzzo-simultaneous-bootstrap-d8170d23fe4e19eeb94530618d319849fd7da586.tgz","fileCount":205,"integrity":"sha512-Sp0lLnDp2iXuf0Yuhy4aU3guswAa5xRZv/yDV/Ts/W4X7svIAf64T55BVh7sGZvTRFRYu89LVmWPUR1eifyVRg==","signatures":[{"sig":"MEYCIQDQ9KKkosZaPgLffJpEka/rNrWOd+ewwmc9cB2MFQ6V+QIhANV3pTykZoVV6NwfqBtB2s3D6UcpG69jzVN2SroHw7iA","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":746371,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhQk3fCRA9TVsSAnZWagAA5A8P/iVStnegn04yoKojop+8\nRapmZT7Il6Wqpjav49SHZ0WrXpe4sy7Zf/lHue+otLuCefZ1vJHnWR7PiWOy\nECpwMc7GFWP9EcdhpRK+aaoqpdGxt3DqBDC9P5bMnCbaYWx9L7cLvXBX7YgD\nDFKrnTUmgnj3JkZ45u6YY3aaU9bM6LLCj2RUxyWe/n56u5+UtWU/HIZXG1CB\n6W2HOOAhQNzNsu53CXr9ktdmlps5p4WXJvmWr4AKslF0pnIweRQDNVESetUj\nKz0kCoWg4fxzklbk/9xQ0pbNPzdvIGwjZI20ymyW6VMq8Fg/eWNQXI6PjyHl\ngv+jNr/aTQyYdDS55ttZ966RJi/QDflTAhISvT6bvq/Yw7fpYhVsRO1sH37e\nNxh0OjOCah40jic1s4WtaVBwhBDbSa/4R8cwrj9JeCHlXrqzKMJrZDXMLED5\n+ylMjgdGaPtc22fAmjGJp7OyXVYJ16zIgrlOFa5AY4dPhc7MukyV/A9WkRou\nN0heFaHgByu5MVc1vDtNgsVIVx37kefEamumaeXBNZrvPrNRfZ43+RKyuYBC\nbQQK+zOLqkn5s9LAmXsu3NjFeBgqW6Yb72S4SBGvxzKd/3vGVWLBx/6aorFx\n12GGnrPtfRdhNt1t91KlSJdGUnfyLYsPbzoGGRAV4n3IW/3mrTeNJ7QwlfsH\n33nb\r\n=ULvB\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d8170d23fe4e19eeb94530618d319849fd7da586","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-15T19:42:03.583Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.69","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.9","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/semver":"^7.3.7","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.1.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.4-lucianbuzzo-simultaneous-bootstrap-d8170d23fe4e19eeb94530618d319849fd7da586_1631735263532_0.7031476606993237","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.4":{"name":"@balena/jellyfish-core","version":"6.1.4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.4","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4aebca4ccb46d86ee2db97a2c116f7c9192f4496","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.4.tgz","fileCount":205,"integrity":"sha512-42AJjt4bqMnFsaamgciaUrvhpl6ThpXq5X1BU/sm1noQfsUPt9cWcnwXYpZnJpzFxZIRcIs6jqbIWc2rj1bGFg==","signatures":[{"sig":"MEQCIEnvr1dFkDe3Y+aBkynjyPZFLPdwjPz6XXsXheo18TLkAiAB3uv4+s2qmBBGYES34F8vuKzZ28tbZPxY785KHTC/6A==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":746295,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhQlKGCRA9TVsSAnZWagAAe3sP+gLn1Y7ChSgvcMpII7w6\n3tpcrjsZuAZfFQGyN4TW4r87UyCjWQbSyJQvDlAnvH840kdhz5Z3o+KZqUCg\n5MS3iQj/XquxlrzdBdtSKl8pSPTDlacLIuVcinfXHGQo1MKcidry4oDa1jow\nZUj6uCqj4a20qx9IMgasbaxKdlXHgsjtG9g7W9ZFWr1L6/YHfKAnOfzcv3i1\nx0VqGHxs+48AcvDdrvONTU6Ua7xPx/y6oWUuQLZ3ibOtcJCYSQIm3k6uZ8Zc\n1p4fHzV8ezO9+4EiT5I0ozm476Qo0Cx658B+mBgdXCRAxoBttHZaPG6FHs05\nDZEP0xINDGo8Uo7eeNTH3VRMLRrQouthZxRo1DUEZamkF3trlM95D4BxXa90\n2dCCMF+DA9v1FBngMD4ubGaeyIE+lVi+hiJeO9+nFc9lzCHCcjvQx9dUuG5V\nTIqsXo0iGMpwIxxOTLzDwbeetoZmWnfgTiSg+uiwneFidUWNIRrbx8ZS9p1t\nbKs3QNZkF7neTKxpyIhKu655omGSVKQzU/+h7pe+81IPMuJDUzejWTin38bT\ng6/vCj0hkCBD181vQYdDENYw4S82sUrAGDb4yxKj08bSTwW3bdw+h+zsdUe7\nG8Sdj9HrwGYVE4t5NnRXg+nXm3QhmLEcNI6b8AnCEb4YfiDSzyUZqZbDtyI7\nycto\r\n=sZ5a\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"403198d582b1fb58138a643a6bdbed5ef2920127","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-15T20:04:54.961Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.69","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.9","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/semver":"^7.3.7","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.1.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.4_1631736454047_0.10841599097379628","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.5-renovate-major-external-major-bbfa3ca5022244c7e75fd34c37d61da8dc9cc30d":{"name":"@balena/jellyfish-core","version":"6.1.5-renovate-major-external-major-bbfa3ca5022244c7e75fd34c37d61da8dc9cc30d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.5-renovate-major-external-major-bbfa3ca5022244c7e75fd34c37d61da8dc9cc30d","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"81ddac312b4555cea25ca0f0d8afa815991d4fd7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.5-renovate-major-external-major-bbfa3ca5022244c7e75fd34c37d61da8dc9cc30d.tgz","fileCount":205,"integrity":"sha512-Lq6/SuocwIXPfXZVz/R+8WD9XG9Lb8DasQYDcN04CBVRgcpIfmf3Ywxz1Q/eK89CksNvTmchW3AZ/j32DOveRA==","signatures":[{"sig":"MEYCIQC4sMDPNRiuHH9uCIeDK9MmmrqT4GsvE/EiOSuS5Px5tgIhALehQj4r6Mai8+g2kmJuFWF8mcIPbTJQxSu7riRNnf/i","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":746435,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhQlP6CRA9TVsSAnZWagAA5aIP/RKMhU7RrXiYMOTe7ltD\nYKkBys6DU+asc6sJcKSr9Xikm2MlBYE6dtQCrmonQvXMIMIZbEHa+s2vN4hH\nMfwkTdQ/6nLYXWDK109mTspx+w4LAEtMNjcUUSV15FGFwLET4Eb/RKUkL4DS\n9/NgktHihpB9cGEAg4yhMe79urymZFC0MDE6FwBVC7JvFDY0MFG7+p+8qxPt\nOsvYCHVc6m8fU16BcXntWztH9VWGmwQhB1/740m2n3X/ctH2mm3HHAVjdwCp\nvmGKk65LgknuY/WrkxCn3gAXbvUY2XqGSbdiW2V6RfObLLHCblJcmmYM8Hf4\nlqyet9dYswqwHfcE/C0hC1XNyVXX/K5/fB2Q6xlMBVqZ22sVFLMs1JJzLyGz\nJj9sLBzYV6i47MKTnDfcWEB9foEtxllAZLR+eSLLBWJp8zw3GEMqHu8pBnaR\nQ8sd+SNQMEFwj1QMuDz38kUf83S4EjG3vjAcl785vqHrI3gPMmjZGKzTGU4o\nr7grvnyZV/0Kz3rNe6hUm/rrjk5tRBOnxloCzLNKg5eyE/hr8p+fimPl06bU\nLG7UCtTkD3VG89I/TrpfCU46OVgYqD/MCJpMM50LaKgY31Zc07EHtzTajtZb\nEIAEG63WHzCGuxV0MI1AohS7Pz4BNzSXiTcAYI5WgpoF5NVmBei/epG88kZ2\nSIw1\r\n=e0YC\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"bbfa3ca5022244c7e75fd34c37d61da8dc9cc30d","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-15T20:11:03.164Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.69","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.2.0","deplint":"^1.1.3","ts-jest":"^27.0.5","typedoc":"^0.21.9","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^27.0.1","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/semver":"^7.3.7","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.1.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.5-renovate-major-external-major-bbfa3ca5022244c7e75fd34c37d61da8dc9cc30d_1631736826522_0.552117573330948","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.5-lucianbuzzo-simultaneous-index-creation-48400d6992e96e9146e1d9e2cfa6e57045c0252d":{"name":"@balena/jellyfish-core","version":"6.1.5-lucianbuzzo-simultaneous-index-creation-48400d6992e96e9146e1d9e2cfa6e57045c0252d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.5-lucianbuzzo-simultaneous-index-creation-48400d6992e96e9146e1d9e2cfa6e57045c0252d","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0471b677461ec045404b767e1e4923361d301b21","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.5-lucianbuzzo-simultaneous-index-creation-48400d6992e96e9146e1d9e2cfa6e57045c0252d.tgz","fileCount":205,"integrity":"sha512-Crcz5b3SsMiBqt8UH6iKx3ckTBViz9LOsojuM5bIsk8+NE8uiyt3grHktLC7z2SRF2o/8KbEF6//m/BVBUNHyQ==","signatures":[{"sig":"MEYCIQDT6GMcUl6bTRDOXUip2XV7M2sWqtWue4Vg2k42tbuLqgIhANOHSCSDdzrnkb1iD06izEFcuf8e1R9i3qBtOvQj6qzk","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":748366,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhQyHhCRA9TVsSAnZWagAAL9wP/07L2TluqSUOzv2TRBxp\nnFk566JSMXNHda708GkF5DZKiOF6wElV+7YyE0xEGx5ClRAGdxTgz2F6Vzcm\nTcbZPiknyOLBAJdhJGTmXIcfiZPc92PX/XbF4CXSmuij5pOoEPlvX6mdHhK2\neVBC9PwlnW7wA8qvCIg27FC0lYiD5F4DaPnygXrjxcgvQMbLLu+MFyaurGE1\nQYrIRG4lkgfqFzfYz9Hywg6P0P2txY/rcaOym4s2/awbhu0g7QiEs6TilGqI\nSxZ0Dswb926lM5ttMNqcrYHCrWT+PaEXAVI8hkJNfHdqTZroFMzxEqcILSBO\nyzSGJaUmL7HEZJFHZvx3LTa91e29n/ii62hSk5Pg3kD8AzTzk2U3JwMyXWo3\ndtICLo/ZUIjchy6WpbAdVRXz0Wd52ZoTO/ApMOvSQ7soTOq7gSq9fqT2C5Rk\nKyUFFKb2jIhoNeAq9SYaW1+QKuG0+s+9GZGNqr3UQUzBsGkT/vJ71ZqUROBW\n1n5fxaL1BXgFzasgQIX6j+27PKuXIcCdAMIjtS5VeQlHYw5gxdFVh30b/jIB\n3ruJYwEaRn0wC5X42lCVjqddFn4uMTkg42rDY8ZngA/X9/ujsKFnYl0vgcD0\ntsGOlQ/CMYOtjNPRxOC0V99LkqUo/upfAVs974PEVvb2ekV60BQTbgWAZtm3\nLXjR\r\n=+Nhp\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"48400d6992e96e9146e1d9e2cfa6e57045c0252d","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-16T10:49:32.128Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.69","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.9","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/semver":"^7.3.7","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.1.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.5-lucianbuzzo-simultaneous-index-creation-48400d6992e96e9146e1d9e2cfa6e57045c0252d_1631789537569_0.16022678307528393","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.5":{"name":"@balena/jellyfish-core","version":"6.1.5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.5","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"df48562b28ceeef47b5848232597bccde7a745a5","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.5.tgz","fileCount":205,"integrity":"sha512-0ARGtFiKS9H4Ixg+5RtAO7Wdi6xfsRP7BMHBEez1EXXcHJVGz3MtS1y8uHXO7crCL3hf92KE1KkGQ2VsEYw92A==","signatures":[{"sig":"MEUCIHASnM2LIl/NpzgDQLW4q5Qub6o9ttX+f6q1wjmGejtUAiEAj4f1UHbBa5Fy4xx2QQCC/xCqxRxBTaVEWtx9HFBXFK8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":748285,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhQyjjCRA9TVsSAnZWagAA5EkQAIANq6Ocjpwrrk6eJ6yz\noGpN9sdC7hEWDvZUA3OTux6dX5L4t+jem/HmdIgYvDrT3Qn7nTZbLm7PIwKK\nhXY1YpiHWc9GgMPzHovDXXkB9xfBdIJQFywPiu7POoPzD5w0eeprCNN4HqjY\nQ+a1xy9YjdT2nqvo94Y7+2pqPIugp3M/SSSvJ21grYqJSEBpK8QO2d2GOFzB\nO4FKuSr8ZtpglgurytNQO//CJm5LKy4fwdRfZqQWv0ZOa+ZIHPhjfLF+5o3t\n8XKfMo9SlS5ep41mYdwN/lCkcf2rOPC+RcUBCxmUFvFe1gV5iCiERy2XeCVu\nhf4HYzWG1zRBep/DVbILm6pUm/5M4FuVrTrsf1ve8G7LGDnHklDUSByDcojt\nnNbeKSY15YQekGsrmcDatKJtaVguBV4lnnUVBBP7dWOkxCeXDI5ddvBB79Uq\nd80jii+RhRvTSRTKRVRD7+LC0qAB9UVj0quG6SQ4wKfsbCA89qBWWVfMvbLY\n6l27tPHTOgKdTzcT9tw3AeLV9hTPF0xOcgHax9fjYrtUS7mq6k4AIcaO2nho\nOmsdvLFGrc4PCeSxRc1vGv1O2dbJpZ0kSsIgkzXI412xHgb58M5DjtsXV1RH\n4dBmjv+iBYyXGE6+DLw2UqbQUR7IDwM+RJDPJ7IXjyDngIwoLJvemMv5yrlo\n7QC5\r\n=4UGI\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b427a65e13fb8dcacbeb029925c892b8d8bd00b4","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-16T11:20:12.054Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.69","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.9","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/semver":"^7.3.7","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.1.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.5_1631791330938_0.3359117354474306","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.6-renovate-major-external-major-bd5fa26ab656f229dc5e091b95e0790a0ecbf45d":{"name":"@balena/jellyfish-core","version":"6.1.6-renovate-major-external-major-bd5fa26ab656f229dc5e091b95e0790a0ecbf45d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.6-renovate-major-external-major-bd5fa26ab656f229dc5e091b95e0790a0ecbf45d","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8fbb5c99ac543480c5e91ac4f761e4cf16bcef64","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.6-renovate-major-external-major-bd5fa26ab656f229dc5e091b95e0790a0ecbf45d.tgz","fileCount":205,"integrity":"sha512-knbZz/aBveVRncU9Cn5ZIeexfY2078736p6fcl3yKA7byB61XloYelGaoXMgT/iUHCjXMHeJqV3lAQbkZX7oag==","signatures":[{"sig":"MEYCIQDczDhj6Kq5KDAN2WvgZnzylKid53oiP8xVdtxr52d2OAIhAO3WH+0tnCbnx0yJ3UOT+OykmLwRftmliftaBabiEoNU","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":748425,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhQyqFCRA9TVsSAnZWagAA/XQP/iuWGjtBIzh8dOm1qxnT\nJEd2+O8jNnDH0stYHsOgM76MgzQDfwEY1AF/cAJYuCZuWCP09KvhGhDv8vYQ\nrRJx2bcPHTo73rVqDo5/XXBxpv+3nVRWHny4rmiMRVYsMncbEMKMVDIycAPE\nf9dN4YMtmJOj7BthaaiAX3UXLxhmfDBgoYyGQg2heiyIZewCEvN6tUkuiJGM\npc0GQxwiKRORif2Dvauo0GLjwJKeHlDFN5o9/hOgGN0D2sT/JagfMOFASiDl\nOBy5YaDGwgLjSEBHx18VIq35fMPSwGJ5v2UQtfc23JTc3RaAbQKppWZNnQWn\nydBfrEjRs6ZA45IW3lnBgOZPU+SQxjE8oWA5Puj2rd/kjJfZT1fKHoyyHZI0\nq3ERZL4rISUqaO4LSw6inuiN9PmcnzIaCWIkkaos6ivG58Vw4Uvk5dVm5XPt\n5cXjtBWyjcIjn7DDcVeLtd+7z03Ry+NaSSzO9ipiVq1Cpbh4FthNUaJ91V0M\njIS6vZ5PKmck/EKrpXiuvv+BL73LmowN8GjFZBx42reHECZfWXaQCuWDdWD6\nrDHgOnnOgmvGBolU5vRpgAIlebPyTPBN2R7MTEbaliT2qplmVj+fUz9m8DOW\n5o9ZoBhzSbX38JrHOrEWDkk6R3tIuPj1Dv0DRVwdxkEBK4JD6ua5K/B8xxlX\nuIEa\r\n=CaUd\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"bd5fa26ab656f229dc5e091b95e0790a0ecbf45d","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-16T11:25:44.495Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.69","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.2.0","deplint":"^1.1.3","ts-jest":"^27.0.5","typedoc":"^0.21.9","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^27.0.1","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/semver":"^7.3.7","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.1.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.6-renovate-major-external-major-bd5fa26ab656f229dc5e091b95e0790a0ecbf45d_1631791749164_0.8068744491943085","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.6-renovate-external-non-major-bcce371c7194c852c68dd3de3d7208c9360cdf63":{"name":"@balena/jellyfish-core","version":"6.1.6-renovate-external-non-major-bcce371c7194c852c68dd3de3d7208c9360cdf63","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.6-renovate-external-non-major-bcce371c7194c852c68dd3de3d7208c9360cdf63","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b26040953c9e402a8ed729b953e60ef65ade06c4","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.6-renovate-external-non-major-bcce371c7194c852c68dd3de3d7208c9360cdf63.tgz","fileCount":205,"integrity":"sha512-9SSj0Hobe8bzx8lw7K7baQ9PgoPVCTvT5yWfNzDUdu4M5xuNpQBPGDRmFrzzU/NL1v9KlF031uWBszyAuBpSWA==","signatures":[{"sig":"MEUCIBowiK6EBCQwSgVq1lRi8WKHg33L5tw2N8A1jjF2P4ZbAiEAs7v5yE1/39C5xJjNgMRzHiT9WUZ9YBPqZlbcmrvRG/A=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":748428,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhQyqoCRA9TVsSAnZWagAAfmgP/iltJSkc17p67RqP/9wy\ne1eVxleHXhGTYg1vsvVPeTqVEfE1FOldtsznpqsaDjq6aoZjDs1lqeHoS4a9\nuIOigCaLUyJQPreTuU56dU4hlv3N4g9nllJme9vDO/T4NTUgQCAUREzjnun1\nlKXUnskNs5FehEi7Lv/TUsdr+z1OAihG0skC8TuPgY6A4n1VVX8ZL2TIsBff\nHxfKW5rKZGHnHKronmSBsvY3xJtEUfxLQMNCtFpTU0aqImYc3IzOZPzSzaNe\npoEPddoC15CvQjYvhW3RyZHbWL3WTtlL1hD/Hd+qArmGeQ+Tw085z1FsTpUc\nj60+EaWzQMagcIIAdaVb90mfFcAzIiKjXrRb9HdiXlfpTexFUklSOWvryBEu\ngiFdlZfQycrTVQTTvWiHUIMMBY9svgUkJkcPK4er3i0NOa0E1b64w9qc6TH2\n7TKP9z+FgWEH1yJTDg77lz/yfhi5QYadgChDGELEiyh8aBMORv5M4F52f9gN\nFHnklo6yD+tD4qHsv1XszS2j6RlHSwgkmEYGbfGPDweZ3wGOg4ep3ZwApze5\nSWE43V0dUBcSrV09hV251c1BhuG5HbxihI6WJUgqMPxGuk1VFPsiG3ujUwxr\n0DdBK7LuQkxcUxMN34IWKusi3nz46W5Q4ODeBvd9qu148gx9EfK9nXxcqqVK\ne3v4\r\n=8WOX\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"bcce371c7194c852c68dd3de3d7208c9360cdf63","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-16T11:24:24.033Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.69","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.3","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.1.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.6-renovate-external-non-major-bcce371c7194c852c68dd3de3d7208c9360cdf63_1631791784328_0.43387568814977295","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.6-renovate-external-non-major-8c03d2e0dd440c615e3c8e4c9d57f500d54ee527":{"name":"@balena/jellyfish-core","version":"6.1.6-renovate-external-non-major-8c03d2e0dd440c615e3c8e4c9d57f500d54ee527","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.6-renovate-external-non-major-8c03d2e0dd440c615e3c8e4c9d57f500d54ee527","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"16a1e523efd4b09a4cc0c42a4e7ec5919e72f8f3","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.6-renovate-external-non-major-8c03d2e0dd440c615e3c8e4c9d57f500d54ee527.tgz","fileCount":205,"integrity":"sha512-NgrezksvAm2ltfTPzLgcvqqzSWb9DwLvT5nEaLu+lW75NyxPBRBpMOR3GX3FgmEJBf06czxNdYMup+n+guV8bQ==","signatures":[{"sig":"MEUCIChf07TBAceBjAWpPynT8Ju1CMgBHSnt5FZK3zgPtgYDAiEA7gAglyvcIM56qDhYx1+nl9M0LiWvOYXtq5So/J5hqxI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":748436,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhREBsCRA9TVsSAnZWagAAKjsQAJTlaqxas9jqWquYqGlb\niQfOfGOK1cGN0Z8F4KA/Ub4+8ZFIRZf+dKCVXbmz951leYfypmB9HLUfystf\nBrVs0jAo6MAwrEykvGt9cNH3t10idiwWzOniGKtSengTNKUuj0D6Ly1r1PxF\nGFjXsgRelgZqDTkGRyzjIlKQJsQ2CBYS4U0hn3L6IMwWAgFJ05aULwXSl51j\nVABsWXQoJCtZnOCHvQKdQl6XRzOtgmeMs3PwD8NEpN26zE91uh1+a31dINYx\npI6j2qjPwTdtZYjw/tkhRt+jP4kgDG1uPSlHfGooXqAYDfcQOHzIEP1zo8W7\nZBAYHj8HRpQu0C7gpz7ZTjnCOs6aZYfkbv1ixAH5Xi5dQcJYIAmPaqZel18c\ncQebzk1VFsWmLa5EKl+JWtrPj55iseTF7cKB7lxS80ji4k6guQeNLucyfCjS\nEx2WSDllP+I+aB4L8mR8Gq39SMp+IJtpVJb5WxprScRM3J0r0jsqF3KkS960\nPRTuKQP/stXFIc7imvCcsec6UZFLuaIoF040WM1HmtXHuvcDE7hpMR+5bdgQ\njzdVjmtLMzxVvaUsZEMmXaKTk+n7KW9wAB7FKbUO4VkIsq0iOn43cKI9iDUW\nlAjB+AaqIgBKpDD2sPcxWXzl3WFx9vFXHBchtmGg5iYkbEb21kt3USyKbAQS\nISM/\r\n=fCaa\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8c03d2e0dd440c615e3c8e4c9d57f500d54ee527","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-17T07:11:32.485Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.69","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.3","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.1.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.6-renovate-external-non-major-8c03d2e0dd440c615e3c8e4c9d57f500d54ee527_1631862891901_0.052125564707788286","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.6-lucianbuzzo-increase-index-retries-77fc6d802dbab560913753b4181b2e87c992e03c":{"name":"@balena/jellyfish-core","version":"6.1.6-lucianbuzzo-increase-index-retries-77fc6d802dbab560913753b4181b2e87c992e03c","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.6-lucianbuzzo-increase-index-retries-77fc6d802dbab560913753b4181b2e87c992e03c","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3a86e6a55f3841f952e6bc96c504c43b4631977c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.6-lucianbuzzo-increase-index-retries-77fc6d802dbab560913753b4181b2e87c992e03c.tgz","fileCount":205,"integrity":"sha512-wzLiTE11t6KVBAi3uNf80F1iL61mzrVCSR6yzqQmh+pdyJF7vqD0ykzKJdNarBEtOnzdUyoYrVErIp3UsGdJ/Q==","signatures":[{"sig":"MEUCIAZD/IIXiJTCPCvOlXRAyNpxFfpK48nSCB4T6adSmL6SAiEA5E/0oRdmlVEX1T/cYqEO3XanDh6Vl3SJUEJkUAkohoQ=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":748643,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhREIPCRA9TVsSAnZWagAApOMP/2rfwWuofdOvQNf7Vemk\n/zXvLVUt7567mTdFBF8oFlt7Fo2RFUx58ja7l4v8+OqqVpmqYYRPIwA3YOkY\nvkv1oYfK26SUO/HkXSMvGkMn0NmPEpE33/DzhoIA4OWieBWkIe9JPEWQViOH\nryPak8oKm+uF/ziMJauGKDXHuMZCi4qvKkI5MWsXJWa2S56z+eME3ZC2iqEL\nQwmxIOtCuunCE4O5ReOXweRHFEedSoKb1G+NMPL0TEpHp+MHE43KTvw4WC7/\nos9NtsqQ9H45zxgPj3+BC3YDaJN+GRGaVGTUnq2c/qSL0Ep1bgjI+7beM7VJ\nBjNNJPeKQE3IAtCWgpsFCkZFhgKu01gV0vmBvR+9el33L/B+lBqa5Tp95hqP\nF29LljZb7OCl+qo7ajXzdS2AVXXLMy9D3CF9BrTZJeiGSCaIwb/Cc7XwSsJM\naku/IplnMRoD5Tptk6EDyFKMZKs3Zh2R9Afnrk8tI/DsTxFuGplUB2qfW12E\nCwhd8wReO1TqK2sllFG4td+JPUHFGIXqbbRIzlgsKYLcG0YMhofsU3uX+//B\nyWNq9BbDADuUWFKxTMjHxfDR3qjp9I6XIyKqAePnK0UckfEyVoJGRdJSKyqG\nHqhMWsdhmuAtP3z2yPiPF1VjXllJZOD4ElfwwxNwK5/Uc08L/een8JvtYi28\nTpZC\r\n=tSCV\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"77fc6d802dbab560913753b4181b2e87c992e03c","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-17T07:18:51.905Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.69","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.21.9","depcheck":"^1.4.2","typescript":"^4.4.2","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.31","@types/semver":"^7.3.7","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.1.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.6-lucianbuzzo-increase-index-retries-77fc6d802dbab560913753b4181b2e87c992e03c_1631863310913_0.3024796078853913","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.6":{"name":"@balena/jellyfish-core","version":"6.1.6","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.6","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6a77ed36c05aa3feccd2e6289a09df096b705f62","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.6.tgz","fileCount":205,"integrity":"sha512-TfrqNuHh2/oXQTXf3976H+Ngw9cG5tMCutj4VDnCUKd5UM3d+Qtn9zQPG9udLZQONkAHhNx6/UZn+H8gAnQ2dA==","signatures":[{"sig":"MEQCIDuhoO66w4xBYGb9kQQIH0f+pA4vedExbe4s7gf4tjS3AiBnM6Q2VE9jLY+bvz21iYnVr+6NSu+7BSJT5do1RgXy3Q==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":748367,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhREJkCRA9TVsSAnZWagAAjS0P/ia6RX5I3xE62GL0rTGl\nVjsq8CqNu+UcsmBRHjKcLqnSZivV6CHTIRmqbSovlQoIrT6WyX4uvdoEYGrV\nR9pPUUxC4pVBOnl2kFVy49BCw0630/8XYW0O+gUlwp0lEHuXhbhDrsyt/QEu\nlVpGjOYbrXtaHrwUardv9WBSWanX33nrPsmnNiR9w8hLSXErM1Pk7O1Fh80S\n7g2DhSgJ4oQ9twr5nhwyx6Oeaw8BwXjCOT0ne6A15Ta10sYEr8VD8KbG3bvM\naVqFhXCP7Pil0hAepKFPCmtO5vgGIe505AIrlN3KRCLQkUzBzunODzsNTYPf\ndaXvu9SVxifnDpqI5jxvBKebx5hLQL8HqE6AypGmxWte+HLEDoz3zsugoiua\n6j4QT7Bx1G+r2HQ2zdbRBtl1JdMhK7hl0l90i2rPbqSBZtvM0E+MfqjIDz2S\nPApYSpOz1TphDuPFJ3PzkLDrcDHi0iJ3Vu5IcsHUVGnGrnvTrr1xEZVU2ErD\nHtU6y6+/8CnfwdKygoJxzwwPQD2Zk/ZoMqPjyQ1DhQYvUdtCzfEqiUUiohiu\n8vAGogeOhg+WAcNeX8BC3TIr0wS6GC0UzW2BP9CUmRH/ALLdu7x8UmHsBaZO\nBVK3HRunKsx2xGSYSBlJ+Mw8VEcq1afr/vBebztihTiBK4MjVq+uKJiZaSyM\nE8Mz\r\n=H+VY\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2fcc1d9022b3a5f1a04120be3fdeb8cfb706252f","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-17T07:20:01.864Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.69","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.3","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.1.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.6_1631863396304_0.8439729579445334","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.7-renovate-major-external-major-a33c2064b2904fcc1268c245619717d273a00d1d":{"name":"@balena/jellyfish-core","version":"6.1.7-renovate-major-external-major-a33c2064b2904fcc1268c245619717d273a00d1d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.7-renovate-major-external-major-a33c2064b2904fcc1268c245619717d273a00d1d","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"86c58ee037a637974fcdab8c6c95ff4f0d0701e9","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.7-renovate-major-external-major-a33c2064b2904fcc1268c245619717d273a00d1d.tgz","fileCount":205,"integrity":"sha512-zm9QVGPaW7Q6v8tCqLOqPGF7xRyeFK5jeo1VcsLFjnmn2ObInsdN3byFzoqoU6yhTB/VtZVUc8UpMUMw12ozLg==","signatures":[{"sig":"MEUCIQDaH0Jq9JbdyF/gkE3ycM4d0dZqZnpFe3xWTutWGaxZKQIgEW47yzH+7R1SKSPp9vfd/BLN/wdiXlKb33VSaqWXy5M=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":748507,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhRELKCRA9TVsSAnZWagAAXKoP/jJw12ATFZzdzsjEXJI+\nzQuwy1JftZE3VI+1yyoAV1wZhSZ2/ziCdM83+3/KiOuxNqQj8Ch3NfBFlYVb\nV0dDiOJ/he+W2WHWGApG1YwlsaSmDFGweWdjCj2YaO5G/n94NQCpW2nmH4R3\nHNrGLzM/GA4Yh6ZgVQXv9SjI1PFYB6L7Wwt+n2lp7oitMA6ncq4F7ipZHHGz\nbK43A8MPPynAdXwv8ZR6oS/iTBXFA9E9v1rgFe7nKSwZ95BLFVkppIfgEbyP\nl8Kr275bh99ip7mxNDmY44bHLYk0yMTntyePvgZSKXmDa8Dg0lB2mkvena2G\nsc75oBXYrSMRF/6TlZ41IkWlMO4FMQCBN4shNQ9G78M5Zwxlnva+ehlhM54F\n/zsNjaqG5cZhiS/0FYHxbOCWL2rEzWxETjix/Nab6DWYpQbPzitI6F2fTUcN\nz49zHfh3P3qYqkdV2mHdX0hMndPhm/HMjxHV5n79gmHGc8xSGN7JF3yao1dh\nvCubxNmyq55BXHVMwqoX6bhgZENNaO5JKMRr84MIzcIrkIWWRc4wUiBRi6hk\nd487581vpmZITZkFbqYwOP74wLNYmnMVJVvI9RL3rv3kmQWp3YdPhV/MrZp3\nk9keqjrll6iysuwD3aTSf3G/25PA6iKsX4ssZMejDBAYpgyV+LeHQL5aqy7/\ngEHw\r\n=71/o\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a33c2064b2904fcc1268c245619717d273a00d1d","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-17T07:22:32.715Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.69","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.2.0","deplint":"^1.1.3","ts-jest":"^27.0.5","typedoc":"^0.22.3","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^27.0.1","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.1.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.7-renovate-major-external-major-a33c2064b2904fcc1268c245619717d273a00d1d_1631863498313_0.11611125942809974","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.7-h32-stream-card-type-603da463c08b954c2398a852689f098a5c4c6574":{"name":"@balena/jellyfish-core","version":"6.1.7-h32-stream-card-type-603da463c08b954c2398a852689f098a5c4c6574","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.7-h32-stream-card-type-603da463c08b954c2398a852689f098a5c4c6574","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5938775a665aa41e2eacc6b800a6d830d02fd11f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.7-h32-stream-card-type-603da463c08b954c2398a852689f098a5c4c6574.tgz","fileCount":205,"integrity":"sha512-JiAIEbBZ5AKk4QotIIQgCF5Z3Zv0DGMHt2JTvNgYnl/0RD7R4Yna0yJx0R2CLVdqHClKq6z632bYXi8GOzrn5A==","signatures":[{"sig":"MEUCIQDY/+ECRG37aHOnvrtPop86qpyGZOpXlcoZb28JrVbhEgIgdCmzQay1a+al26dpdVfL4r5Jc9WqU7Wnc7IBLaOzhA0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":748747},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"603da463c08b954c2398a852689f098a5c4c6574","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-17T13:41:02.227Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.69","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.3","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.1.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.7-h32-stream-card-type-603da463c08b954c2398a852689f098a5c4c6574_1631886285171_0.22824171966138906","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.7-h32-stream-card-type-cff4d771a5c34a6ad7f1f859215d1ccfef7dc4e0":{"name":"@balena/jellyfish-core","version":"6.1.7-h32-stream-card-type-cff4d771a5c34a6ad7f1f859215d1ccfef7dc4e0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.7-h32-stream-card-type-cff4d771a5c34a6ad7f1f859215d1ccfef7dc4e0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"de52275ce66ae4c96850f0eacaf88c6b45d36d67","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.7-h32-stream-card-type-cff4d771a5c34a6ad7f1f859215d1ccfef7dc4e0.tgz","fileCount":205,"integrity":"sha512-qSvP7b/xWEBZietWWPBRh85/9wrJW0D9h0jxP/mWkg+gpaEND2Es2p+6s9L/1LozBYWeN2EegyD1b/T5+BQcwA==","signatures":[{"sig":"MEQCICvji179IdTboOOaIffoxEtQRw6nw7Sf3oc055LQbsWhAiB+tQKHf0Iyo6stIwjmZVQErAo1MmWu7J8zbrMUKgCMMg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":748759},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"cff4d771a5c34a6ad7f1f859215d1ccfef7dc4e0","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-17T13:48:42.229Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.69","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.3","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.1.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.7-h32-stream-card-type-cff4d771a5c34a6ad7f1f859215d1ccfef7dc4e0_1631886697710_0.6502224503719436","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.7-h32-stream-card-type-06550ff5200ca17a1429e6c4a0e5b8b23d4169bc":{"name":"@balena/jellyfish-core","version":"6.1.7-h32-stream-card-type-06550ff5200ca17a1429e6c4a0e5b8b23d4169bc","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.7-h32-stream-card-type-06550ff5200ca17a1429e6c4a0e5b8b23d4169bc","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c22f8e797f44c65fc2f749a1fc6af9e1cfd1e01f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.7-h32-stream-card-type-06550ff5200ca17a1429e6c4a0e5b8b23d4169bc.tgz","fileCount":205,"integrity":"sha512-0u0V7N6eUB3UOrt4fJfTnuGFyYXqY4J0sl7Cb8i61epv/29Sk4Kjg/NmQrRRbAIQiM+qjR3JYVrTKKA5zBt2sA==","signatures":[{"sig":"MEUCIQDO6fY57tEHxvQgmJ+ECfjEG3avxUYTCQFK5Y5vNX+ZPAIgQuZtTzEBJTfVM8EWJ8GDs1WwJih8aFwg4Z48qcXAQpA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":748763},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"06550ff5200ca17a1429e6c4a0e5b8b23d4169bc","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-17T14:38:22.337Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.69","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.3","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.1.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.7-h32-stream-card-type-06550ff5200ca17a1429e6c4a0e5b8b23d4169bc_1631889685784_0.20852384919582656","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.7-renovate-internal-patch-f468b9ec2d05a37f2b3faad357ec37bba68827f0":{"name":"@balena/jellyfish-core","version":"6.1.7-renovate-internal-patch-f468b9ec2d05a37f2b3faad357ec37bba68827f0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.7-renovate-internal-patch-f468b9ec2d05a37f2b3faad357ec37bba68827f0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0d7cd598bc6172cc4730c38154f20092867884fe","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.7-renovate-internal-patch-f468b9ec2d05a37f2b3faad357ec37bba68827f0.tgz","fileCount":205,"integrity":"sha512-E+qMIihB+aUrzbbn0qmRa+XW/9iNx19azwNrt52D4emE8Po/jlApx+Q9VO9LmLbe96ySTPjLFQm8b5XGO5bk3g==","signatures":[{"sig":"MEUCIQCIFmheYctSEbDYu/TiS0QWukgVxXNv3B4aOfkSs+9VkwIgBpqgUeIEytP5LSCL0MO9ys+PFQ1uN6GydPaJJqHgamw=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":748534},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f468b9ec2d05a37f2b3faad357ec37bba68827f0","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-18T02:40:42.320Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.70","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.3","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.1.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.7-renovate-internal-patch-f468b9ec2d05a37f2b3faad357ec37bba68827f0_1631933004403_0.2906075916902142","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.7":{"name":"@balena/jellyfish-core","version":"6.1.7","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.7","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"08428811513cc143bf537f543420f52561c9f005","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.7.tgz","fileCount":205,"integrity":"sha512-8wL6zi21V4griG0tcyFmQw4LjiLZU0dyUy2zSjUlmRq3/JWyis0B3XA+nMVbKyaQaHRMaiovMuae02ehv7cKpw==","signatures":[{"sig":"MEQCIGU2FylqMugEvUQy6we7i/On4k7jwpfF9JEvk4nSFYTbAiAFPi5Ua2WEVwSzykT+4LnvpKNGuaSoDUgnXH1RzgfIjQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":748469},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"990354dad7302e70ea20d9f8655ebc2d395940ff","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-18T02:49:12.791Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.70","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.3","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.1.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.7_1631933463711_0.2494235403924323","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.8-renovate-major-external-major-b7a150a5498f61aeb3d59c2f1d711543bca03f91":{"name":"@balena/jellyfish-core","version":"6.1.8-renovate-major-external-major-b7a150a5498f61aeb3d59c2f1d711543bca03f91","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.8-renovate-major-external-major-b7a150a5498f61aeb3d59c2f1d711543bca03f91","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ad65e8aa9b47a9ded0bed035fd710fc7c56a770a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.8-renovate-major-external-major-b7a150a5498f61aeb3d59c2f1d711543bca03f91.tgz","fileCount":205,"integrity":"sha512-p/lUeUmyW1gHc/6X/fnvoCzqPNbLn7cnLvMDQGLLFJPvEXP+T84q9raNKCFCfQHzUED/TulhzakcXY9uNDVTfw==","signatures":[{"sig":"MEUCIQDowMLaK/ScebCY6WXeATn29Bs4mnPsbaEJZJN1detX8gIgYnHSFx8UDRZGV4gemxYMq4Ksh58mWjEOpBexJEzsL10=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":748609},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b7a150a5498f61aeb3d59c2f1d711543bca03f91","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-18T02:54:32.153Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.70","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.2.0","deplint":"^1.1.3","ts-jest":"^27.0.5","typedoc":"^0.22.3","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^27.0.1","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.1.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.8-renovate-major-external-major-b7a150a5498f61aeb3d59c2f1d711543bca03f91_1631933904270_0.6841203513580076","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.8-renovate-external-non-major-117e732d72057797b65ea07aefee510c3d6aaddf":{"name":"@balena/jellyfish-core","version":"6.1.8-renovate-external-non-major-117e732d72057797b65ea07aefee510c3d6aaddf","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.8-renovate-external-non-major-117e732d72057797b65ea07aefee510c3d6aaddf","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ed1a5047fd117518f4388ce890e8d9e17d89f68d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.8-renovate-external-non-major-117e732d72057797b65ea07aefee510c3d6aaddf.tgz","fileCount":205,"integrity":"sha512-bBuPiRpiebIJ9xWGf0AEZ6+TGKiO0ZY5u9k/K+ESja2qPsTCOdS6wLSUy1GRGMi3xMbMfaJyixQ3TjNbJ8/iew==","signatures":[{"sig":"MEQCIAQlNAf8MSGI8lG8tRXULJ7Fsa43t+ozGKXcy/3H8sxXAiAsQxgemxLXDjqfGpX4Gk1n7KUoo/VSB6afQOkVnydxtg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":748623},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"117e732d72057797b65ea07aefee510c3d6aaddf","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-18T19:57:51.949Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.70","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.4","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.1.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.8-renovate-external-non-major-117e732d72057797b65ea07aefee510c3d6aaddf_1631995257976_0.7300722840329066","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.8-renovate-internal-patch-fe4625dcdc421894e3151b14456fedbeeed1f31f":{"name":"@balena/jellyfish-core","version":"6.1.8-renovate-internal-patch-fe4625dcdc421894e3151b14456fedbeeed1f31f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.8-renovate-internal-patch-fe4625dcdc421894e3151b14456fedbeeed1f31f","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b65c4625be0281a074c8b7cdaafa8e5137b33877","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.8-renovate-internal-patch-fe4625dcdc421894e3151b14456fedbeeed1f31f.tgz","fileCount":205,"integrity":"sha512-gGLT2LHT6zfh1YYHRPpW5YabmOWdMqW5KEnEOG0BLr3Y7MGB1HWqifyquzVMSoLHf8FgvnyFccQ6u7kac5BsGg==","signatures":[{"sig":"MEUCIQCbnlcaF6cPdZOnR/oVjI1zyPgFPAQU98yJhJsM+mROtwIgcNM7FRoRZLEiqpY4Zrn5y72hKXYHjPAYcTaE9FJDkec=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":748636},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"fe4625dcdc421894e3151b14456fedbeeed1f31f","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-18T20:02:26.891Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.71","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.3","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.1.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.8-renovate-internal-patch-fe4625dcdc421894e3151b14456fedbeeed1f31f_1631995591659_0.6779573981361597","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.8":{"name":"@balena/jellyfish-core","version":"6.1.8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.8","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f84a1da3333bd393fb17e21b29c22d9fe6c61e72","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.8.tgz","fileCount":205,"integrity":"sha512-QC+CXkv15SY4HRu7grDXjZRHKNr+YCgRvUxQKyruYW0Myh50KTbr0gLbZD1NauKJgXxSYFdbcfjOgdgxm2oeBg==","signatures":[{"sig":"MEQCIEWyHyiURIWmM7x6fcftHcnSOhDQApMbvQ5RLCVKkAZ6AiBg5IxloZDbsBkmg9ffJjPi0uqUWSchlBLcSnT2KeKlAA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":748554},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0b84b934edc2f7e761c18864253426ce9da3bf36","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-18T20:06:03.310Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.70","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.4","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.1.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.8_1631995681750_0.47526618281857624","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.9-renovate-internal-patch-3d50d65107132f6a93f9604c9c8535cdb53112b9":{"name":"@balena/jellyfish-core","version":"6.1.9-renovate-internal-patch-3d50d65107132f6a93f9604c9c8535cdb53112b9","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.9-renovate-internal-patch-3d50d65107132f6a93f9604c9c8535cdb53112b9","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c7996f442ae952b105536ccad519356e57e1f88e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.9-renovate-internal-patch-3d50d65107132f6a93f9604c9c8535cdb53112b9.tgz","fileCount":205,"integrity":"sha512-p3ps7Ix6PahA3oASP3tpeMFOavVxTj0z1RyG2kreJN0CgTVvgm9Wv2y1ck0YNLq3aW+zAiAMkpYpwmV3lKocMg==","signatures":[{"sig":"MEYCIQCtiAcAWqNvLusjEpClIziBg7ltnA9lQhIF2fl+htVA8AIhAJnW0LxmnUJ11KcPk8TBUIAPKPXL8OQDPDjgc/se/dYz","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":748721},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"3d50d65107132f6a93f9604c9c8535cdb53112b9","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-18T20:07:59.954Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.71","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.4","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.1.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.9-renovate-internal-patch-3d50d65107132f6a93f9604c9c8535cdb53112b9_1631995929492_0.1737481837746293","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.9-renovate-major-external-major-accc2ee86a63df3fe3084675a8cc97db17581dcf":{"name":"@balena/jellyfish-core","version":"6.1.9-renovate-major-external-major-accc2ee86a63df3fe3084675a8cc97db17581dcf","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.9-renovate-major-external-major-accc2ee86a63df3fe3084675a8cc97db17581dcf","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0dc57deb43c9b881d39fce32f70356192d26b4bd","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.9-renovate-major-external-major-accc2ee86a63df3fe3084675a8cc97db17581dcf.tgz","fileCount":205,"integrity":"sha512-GrClNgjGSC13sXutKAtGVq5aAaS56762Pyo/AH19aQbFwqe74eGWxk5FgR/RIr2A8jcAn4uyZtW+qgkQ9nCxoQ==","signatures":[{"sig":"MEUCIBDjc4fQxsZfFktpAe/8eU93WCvtz1tKbo+tSOg0AkywAiEAki2OfFcg/tmC251Qp4CyxauM/nSzegktRTDAaQ/fIYs=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":748694},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"accc2ee86a63df3fe3084675a8cc97db17581dcf","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-18T20:09:21.285Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.70","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.2.0","deplint":"^1.1.3","ts-jest":"^27.0.5","typedoc":"^0.22.4","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^27.0.1","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.1.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.9-renovate-major-external-major-accc2ee86a63df3fe3084675a8cc97db17581dcf_1631995967750_0.5593611822535478","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.9":{"name":"@balena/jellyfish-core","version":"6.1.9","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.9","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"bdd5a2c06c2aa0bb6fe8f27c4f6c1db19579d421","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.9.tgz","fileCount":205,"integrity":"sha512-7Metdz/OsvxmmpbOq6rM5ZEacTmUDts0wmTQqNkUJk4HWMf5hcgcXkZoP6MopkIiQJSJHQm9XBGojaPR+dJ3Jw==","signatures":[{"sig":"MEQCIEy2I/RhIkHS9VeLbPE6oeQEVIWCOoEyuyB93cYwK4IpAiB/qftlqkms8MXDGlRcp+TlRYBH1f8UmdfHu/tzSDENig==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":748656},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"32153b5ef285dd28035cdf173885f3742e9073fd","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-18T20:21:01.907Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.71","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.4","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.1.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.9_1631996573885_0.4982160867920937","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.10-renovate-major-external-major-96027676d921ac9617a108d26aebbc2b5193cd76":{"name":"@balena/jellyfish-core","version":"6.1.10-renovate-major-external-major-96027676d921ac9617a108d26aebbc2b5193cd76","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.10-renovate-major-external-major-96027676d921ac9617a108d26aebbc2b5193cd76","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0723cac130ad406d1716e8d5e32cc7326dbbd404","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.10-renovate-major-external-major-96027676d921ac9617a108d26aebbc2b5193cd76.tgz","fileCount":205,"integrity":"sha512-CLa9TDKILf6dZ0rSntNvKK3b+eXZrEGJzNO/NhvaNUMYsBB8oAX9lQR85eF8gkmopfapF0v8autB5ko8NZWDmA==","signatures":[{"sig":"MEQCIAuAb8Dj4KzcoJbciQfx8PKH3afmUFFJimLKvbLK/dq9AiAjxAyqQTUcytsVz/rNDP5uKOS9e3HxYlS/ncEneIxXyQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":748798},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"96027676d921ac9617a108d26aebbc2b5193cd76","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-18T20:22:29.025Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.71","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.2.0","deplint":"^1.1.3","ts-jest":"^27.0.5","typedoc":"^0.22.4","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^27.0.1","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.1.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.10-renovate-major-external-major-96027676d921ac9617a108d26aebbc2b5193cd76_1631996760516_0.19312400572805655","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.10-h32-stream-card-type-522c11db8664d8c7d8eff0534eb0f1d8fb2fc36f":{"name":"@balena/jellyfish-core","version":"6.1.10-h32-stream-card-type-522c11db8664d8c7d8eff0534eb0f1d8fb2fc36f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.10-h32-stream-card-type-522c11db8664d8c7d8eff0534eb0f1d8fb2fc36f","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f516afae9ba48647ecdc2ba126ca86350cd9eb8f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.10-h32-stream-card-type-522c11db8664d8c7d8eff0534eb0f1d8fb2fc36f.tgz","fileCount":205,"integrity":"sha512-sqgZ8o9KqouhgPVA/dgnlyw44nVO9HyQdrwnZfDYuS7Bu93nJ4SQ+MGmJNLZcmDh8oCLLfevPyxhHUK8lr0/ZQ==","signatures":[{"sig":"MEUCIFTAQ9ujo/agfW0hr7hqMfGdf+ut1i3uSlBIwPew47xGAiEA0cP46IG6tWgOUtZyo+Jr4w0WhLMLxasW2n9ItF8LNh4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":749054},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"522c11db8664d8c7d8eff0534eb0f1d8fb2fc36f","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-20T10:25:01.947Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.71","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.4","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.1.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.10-h32-stream-card-type-522c11db8664d8c7d8eff0534eb0f1d8fb2fc36f_1632133713511_0.11317438248525113","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.10":{"name":"@balena/jellyfish-core","version":"6.1.10","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.10","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"797621d30a32e57bd9ce1428efbcbe50e8005b12","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.10.tgz","fileCount":205,"integrity":"sha512-xZyfuBJXNLPeGspWZvcT/g7rezmI+73YgIlKx0wbfxDzfOYRCpoGgGAQNkCvXRiOSY5d+K501a4mX4Rar3IMpQ==","signatures":[{"sig":"MEUCIGHEMFtjfX90tenUG9bCuRn3hn0uO/kmiymj5I2K70aCAiEAtb59IYWs3K94IsAYl85zG453bzNobA5PV/30slGnHYI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":748992},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"33199976c5d46a492d719148b0e694d4e0208314","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-20T10:35:16.457Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.71","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.4","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.1.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.10_1632134238121_0.2817458946002158","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.11-renovate-balena-jellyfish-types-1-x-5a284fb172f30d22a5e5735232333907a5dc8d60":{"name":"@balena/jellyfish-core","version":"6.1.11-renovate-balena-jellyfish-types-1-x-5a284fb172f30d22a5e5735232333907a5dc8d60","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.11-renovate-balena-jellyfish-types-1-x-5a284fb172f30d22a5e5735232333907a5dc8d60","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1750997ccd215ae152c2bec1b46cfbdc991596d6","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.11-renovate-balena-jellyfish-types-1-x-5a284fb172f30d22a5e5735232333907a5dc8d60.tgz","fileCount":205,"integrity":"sha512-91CiRlw6iQPu4OHzNXY8YZ/JDs5ssy1c7kicKB2e/4m5odJjl+pss99bl/io34kHxPlUa2giSerOO5EFtw1LTw==","signatures":[{"sig":"MEUCIQDfQcU/shFES4JHL6HVNo7GLVe8XbL8W+9hzLPQa31jtAIgG3H49z0mAvGDSxfD6owZAUXJk+MDrLDPMTRXsYmDc7A=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":749170},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"5a284fb172f30d22a5e5735232333907a5dc8d60","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-20T10:39:32.947Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.71","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.4","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.11-renovate-balena-jellyfish-types-1-x-5a284fb172f30d22a5e5735232333907a5dc8d60_1632134645913_0.4038199521499328","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.11":{"name":"@balena/jellyfish-core","version":"6.1.11","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.11","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"07d076859aedbd5f462a003010260df2013dc09e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.11.tgz","fileCount":205,"integrity":"sha512-91WKHGZkFQVQipdSVdt7vUhjs/51u5w58Jl4XOnI6t6c9Sf4UvOh0iWgxgkfAnUF4oXZmUQJGjoEKHuemVsj6A==","signatures":[{"sig":"MEYCIQCrJh1ahgDIJTs9OLamEkjy9LVLN5r6zXmI8rKvA+KKkwIhAK0SjTbnv/iuTTJq8PlSX4iME0sMHZqF5rXqjmrCI76X","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":749093},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"87569c66a7d6fe33f008163b31f57ebe17ad29b1","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-20T10:49:22.426Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.71","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.4","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.11_1632135081807_0.8827093920507396","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.12-renovate-major-external-major-26eeff5087fa4b3fd86889c7ccc52981d0895cf9":{"name":"@balena/jellyfish-core","version":"6.1.12-renovate-major-external-major-26eeff5087fa4b3fd86889c7ccc52981d0895cf9","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.12-renovate-major-external-major-26eeff5087fa4b3fd86889c7ccc52981d0895cf9","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ba7dec3e1d7d1f1f8ccf9ce786a8f8b50351c580","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.12-renovate-major-external-major-26eeff5087fa4b3fd86889c7ccc52981d0895cf9.tgz","fileCount":205,"integrity":"sha512-SS0ocf3+Az/ESWCZ1mgQW3lm6gdet4kcj+/pMBqtePxM96jWkVv/YvfjVu41Oi6vOqc2ZzCdazRJALHPAtWnUQ==","signatures":[{"sig":"MEQCIHN7dJ3FEnzR25XIhnJ5cmc8MltSrvZGVHo5MDaAC9LFAiA4tMzCyY3daohyDNSuqk9wiGN1TT5zAdV3Vj3xKS7JKg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":749234},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"26eeff5087fa4b3fd86889c7ccc52981d0895cf9","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-20T10:53:02.388Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.71","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.2.0","deplint":"^1.1.3","ts-jest":"^27.0.5","typedoc":"^0.22.4","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^27.0.1","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.12-renovate-major-external-major-26eeff5087fa4b3fd86889c7ccc52981d0895cf9_1632135364668_0.3520536894042985","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.12-renovate-major-external-major-e94574af0fd07bb947c7158ca2a33114ec4648e3":{"name":"@balena/jellyfish-core","version":"6.1.12-renovate-major-external-major-e94574af0fd07bb947c7158ca2a33114ec4648e3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.12-renovate-major-external-major-e94574af0fd07bb947c7158ca2a33114ec4648e3","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"cf0d5046b08fb38bff67344d542905b277b3929b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.12-renovate-major-external-major-e94574af0fd07bb947c7158ca2a33114ec4648e3.tgz","fileCount":205,"integrity":"sha512-2On8eXpUyuBIIECjKdnucCueIo38ONoRuK5C8Tn8Ld1WYBj8+CufqMWesRe433wt8MnUbCXs4ePSNMgjINkeSQ==","signatures":[{"sig":"MEYCIQC6xkGzLT3pmmsRL+xQ+bp8snv1A2rx9eDDbtcn7GOatAIhAINCoXJFUL5yZeQTqdR/HGcslGG0ikL+ViIFZY67ZOkw","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":749234},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e94574af0fd07bb947c7158ca2a33114ec4648e3","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-20T14:54:35.241Z"},"_npmVersion":"6.14.14","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.71","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.2.1","deplint":"^1.1.3","ts-jest":"^27.0.5","typedoc":"^0.22.4","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^27.0.1","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.12-renovate-major-external-major-e94574af0fd07bb947c7158ca2a33114ec4648e3_1632149981249_0.35314190664222744","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.12-add-json-schema-group-4e67bd200e2bfb776a17ab58087e0a75fcce8a34":{"name":"@balena/jellyfish-core","version":"6.1.12-add-json-schema-group-4e67bd200e2bfb776a17ab58087e0a75fcce8a34","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.12-add-json-schema-group-4e67bd200e2bfb776a17ab58087e0a75fcce8a34","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"cf10ed02bb2074b1d50cda67c184dbb8008bdcab","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.12-add-json-schema-group-4e67bd200e2bfb776a17ab58087e0a75fcce8a34.tgz","fileCount":205,"integrity":"sha512-+bcJTyYqJ44k+AxH/JbPze34CXKuqfVaSeCw4UJS+p/OQqK/piomMNYhuiv8YpbfpN26XrgiOc+t+Cnb3jJiLg==","signatures":[{"sig":"MEYCIQD6Nb6SE46W8/h6XdR1GvDB25gtQ8QwgxQMtpQmlHNYYAIhAKnUlKkYTpcjXf2oqdHAZz+4BLwh/c/LIXee8Ag/iPJU","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":749233},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4e67bd200e2bfb776a17ab58087e0a75fcce8a34","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-21T02:57:32.905Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.71","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.4","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.12-add-json-schema-group-4e67bd200e2bfb776a17ab58087e0a75fcce8a34_1632193286756_0.3113736356335557","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.12":{"name":"@balena/jellyfish-core","version":"6.1.12","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.12","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8fae5cebef72b7f0ce26b2467781bf4b256536a6","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.12.tgz","fileCount":205,"integrity":"sha512-uOENf7UQ+D5arIooDAJESeIbit+JT7G+NkhS9+lV7+9ZkoF4DZVcxoy8PUgTP5u7sH1iEGOFVJi19cAbzC0taQ==","signatures":[{"sig":"MEQCIFXeVz94tazpkvY2HyWw/iU8G+9X/u5A7sSWjgpQm33AAiAsuDyo2m8VjPkYZnp4BfABo2yDyERLWuZ7SZ/6sm/nmA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":749170},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e79fd28abbfc9e192c1db9a764cdb3273b9ad9fe","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-21T03:06:22.015Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^2.2.1","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.71","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.4","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.12_1632193693175_0.4573082289912451","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.13-bump-fast-json-patch-144facf2149c899c6f5bea70810892adb2797b1f":{"name":"@balena/jellyfish-core","version":"6.1.13-bump-fast-json-patch-144facf2149c899c6f5bea70810892adb2797b1f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.13-bump-fast-json-patch-144facf2149c899c6f5bea70810892adb2797b1f","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"316dc4b690105eacf5ed1048c3b30fff59a928c3","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.13-bump-fast-json-patch-144facf2149c899c6f5bea70810892adb2797b1f.tgz","fileCount":205,"integrity":"sha512-k7yJ0fN9yeWDqCz6k7J2Xxe9bnPiGB7UCdUH3wgHrA39LkHai3q/xG8yzIS9earRmrSbVtQ847wG30uPIJFQPQ==","signatures":[{"sig":"MEUCIAsKuHg+eaOug62TS8lBC9OpiwZAVqHTpImQr9px0NC0AiEAi+f6sItsh2EfPe8ENYkf7NLmetX+46EMm79/nwVvRG0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":749305},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"144facf2149c899c6f5bea70810892adb2797b1f","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-21T03:12:13.479Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.71","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.4","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.13-bump-fast-json-patch-144facf2149c899c6f5bea70810892adb2797b1f_1632194128399_0.14421865862372552","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.13":{"name":"@balena/jellyfish-core","version":"6.1.13","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.13","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b4039cbd7f829c8e8b21534e8996dff730c84da0","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.13.tgz","fileCount":205,"integrity":"sha512-Y/0S1mrkKGVAHIFeGt5ibladX4VuK6YaQpOpE7JvkHiX5nTN51qNeMLXuwFG6poTjMUC+QeZQMi4mIKd3And4Q==","signatures":[{"sig":"MEUCIQDtGNpZ/EE4JW9nPnZiJiRDDVVLEy1PwZhCOUwAUmzCuQIgSmDZpHZTFJqW0T4i2gwrji4AQfUdnXpymYr0Osa7Pbw=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":749243},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2856e0a6501c0d95627ac0e22217c8fab40a0a73","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-21T03:19:41.872Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.71","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.4","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.13_1632194495131_0.5674343256027328","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.14-renovate-major-external-major-304ce33a4df607d73130ff9464c1ab92ad5af2b1":{"name":"@balena/jellyfish-core","version":"6.1.14-renovate-major-external-major-304ce33a4df607d73130ff9464c1ab92ad5af2b1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.14-renovate-major-external-major-304ce33a4df607d73130ff9464c1ab92ad5af2b1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0a1b00917ceb3e28946d5508099909bc5724e304","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.14-renovate-major-external-major-304ce33a4df607d73130ff9464c1ab92ad5af2b1.tgz","fileCount":205,"integrity":"sha512-46JE85A4RcHOmvgONIXCKakSJ/Mo3rGOGm1wbbLKJ5XcNYsKIpICt9fXpezrEb3+J5awXlQ6bXJavxK3GotVig==","signatures":[{"sig":"MEUCIQDcK+go6OigclJS2SNrqwUUf56y03aTffsthSgchVx03AIgbqRbHvDYCLz/S/DJhE7yp+cPbuxdmuEUfb/8q+iu/Nw=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":749384},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"304ce33a4df607d73130ff9464c1ab92ad5af2b1","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-21T08:26:35.088Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.71","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.2.1","deplint":"^1.1.3","ts-jest":"^27.0.5","typedoc":"^0.22.4","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.14-renovate-major-external-major-304ce33a4df607d73130ff9464c1ab92ad5af2b1_1632213121516_0.7182298202471695","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.14-renovate-major-json-schema-org-378f92aa259c55df3c1b5ecb53f460c6ae2b6a7f":{"name":"@balena/jellyfish-core","version":"6.1.14-renovate-major-json-schema-org-378f92aa259c55df3c1b5ecb53f460c6ae2b6a7f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.14-renovate-major-json-schema-org-378f92aa259c55df3c1b5ecb53f460c6ae2b6a7f","maintainers":[{"name":"iayanpahwa","email":"ayan@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d069ff75712c82062431edacd8e942d56c2374a7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.14-renovate-major-json-schema-org-378f92aa259c55df3c1b5ecb53f460c6ae2b6a7f.tgz","fileCount":205,"integrity":"sha512-D1pjXxG9xZ6eQkeBW1LV4DFJK+Ue9stXCkWSLzcOVb9k2I3a6vGd3Vz5xJwFBjGnjAoc3OjTSW2xgtnts2v3DQ==","signatures":[{"sig":"MEUCIQC1VXtGb12cTVqL1YSfZHX0prPoYWpl99O0sD/FBk9kGgIgG2ypO4gjO6OdxRS7ZPiZqL0oeKiVbOvQaNLy5tLshVk=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":749411},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"378f92aa259c55df3c1b5ecb53f460c6ae2b6a7f","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-25T00:12:41.791Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.71","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.4","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.14-renovate-major-json-schema-org-378f92aa259c55df3c1b5ecb53f460c6ae2b6a7f_1632528977130_0.36914106823998005","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.14-renovate-internal-patch-d6802c05324610aff07c05ddc655790e9436206f":{"name":"@balena/jellyfish-core","version":"6.1.14-renovate-internal-patch-d6802c05324610aff07c05ddc655790e9436206f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.14-renovate-internal-patch-d6802c05324610aff07c05ddc655790e9436206f","maintainers":[{"name":"iayanpahwa","email":"ayan@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"7317663c5bb03a4b835e519a61d63b119fa98cc4","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.14-renovate-internal-patch-d6802c05324610aff07c05ddc655790e9436206f.tgz","fileCount":205,"integrity":"sha512-jrXDmWCkDh0YboiVaezoQWM7xYKshjVokTLLjvjIT6NZVFQRGYQRMT5ctxarS2t+TJSbRqgF3R5mUhuy7/LlEA==","signatures":[{"sig":"MEYCIQCJNmr/QTJyjjuVxYtuu9injMttizRlg8aH1kSmWLEbDgIhAPf6oxDHcpxVTQDxOAUngueiZezU9qB8OIjb/+aqksk3","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":749411},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d6802c05324610aff07c05ddc655790e9436206f","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-25T04:06:22.244Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.73","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.4","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.14-renovate-internal-patch-d6802c05324610aff07c05ddc655790e9436206f_1632542960753_0.407247829568504","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.14":{"name":"@balena/jellyfish-core","version":"6.1.14","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.14","maintainers":[{"name":"iayanpahwa","email":"ayan@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"57cd1389a909ea9adc04cfd1974caac0889efd55","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.14.tgz","fileCount":205,"integrity":"sha512-AQxHcviBz9soIkv0b3/bJttfQ0FxWrywwNxOsBteUZYVCL91nP6Hnm6X2Go2AfppW35+ywpNl6pkb3MteWVHnA==","signatures":[{"sig":"MEQCIDdEhlQwHDhWIeDeAhn6SW2lvxup/574XtSKocacew8RAiA7zrHqmfLLP+VwyjoH1NNeFICHoLKFtQLBqHcs+yiWwg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":749346},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b773fb4e68dcaba42deef16833263d978154e741","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-25T04:15:24.982Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.73","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.4","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.14_1632543459313_0.0077004547532544265","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.15-renovate-major-json-schema-org-fbb1fb9628c74a2cdca6d4d6d43a18f0ad9b6255":{"name":"@balena/jellyfish-core","version":"6.1.15-renovate-major-json-schema-org-fbb1fb9628c74a2cdca6d4d6d43a18f0ad9b6255","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.15-renovate-major-json-schema-org-fbb1fb9628c74a2cdca6d4d6d43a18f0ad9b6255","maintainers":[{"name":"iayanpahwa","email":"ayan@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1580fabc0ee93c82dbac76ef9b7ad0c29dd278a9","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.15-renovate-major-json-schema-org-fbb1fb9628c74a2cdca6d4d6d43a18f0ad9b6255.tgz","fileCount":205,"integrity":"sha512-DxY932aDb/LuuSU1WwsUtUTAcbI4s2tVVwcUvozOoZXW7ZImmDLLch/HVreorSB4rUyuMNTZCVq9Rlnmh23Kxg==","signatures":[{"sig":"MEUCIQDPlp9LpquvVgT8uTJ/+iS/j08m03hdjGZOCF5bAy0hWwIgZgi/AUnaHmpE1AzocOYRxCO2ix4Z5KHblSLBTsyCAZI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":749514},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"fbb1fb9628c74a2cdca6d4d6d43a18f0ad9b6255","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-25T04:17:52.988Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.73","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.4","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.15-renovate-major-json-schema-org-fbb1fb9628c74a2cdca6d4d6d43a18f0ad9b6255_1632543689952_0.28547875575888715","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.15-renovate-major-external-major-121592ab0b6523265bd4c1318e57e0f44bd4451c":{"name":"@balena/jellyfish-core","version":"6.1.15-renovate-major-external-major-121592ab0b6523265bd4c1318e57e0f44bd4451c","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.15-renovate-major-external-major-121592ab0b6523265bd4c1318e57e0f44bd4451c","maintainers":[{"name":"iayanpahwa","email":"ayan@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"03fb5194b8f4c7f7f22df11d94783943d8b6de86","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.15-renovate-major-external-major-121592ab0b6523265bd4c1318e57e0f44bd4451c.tgz","fileCount":205,"integrity":"sha512-0MxcsCPbwKV3O6z++EIPzF8I7BE2bABsjzDe5xkfDB4oUqUU2LnMC9rvcGE5WJ7nTwQvJPqXpwVTm4Cu59J4Tg==","signatures":[{"sig":"MEQCIFtLrlANxsQ4t7+51X3V492B3ukEET6ZqHR1hoka5a6DAiBLl8vTKlPYDgIFYAtApfjGd1f4YizairHI2WFXWIhwCA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":749487},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"121592ab0b6523265bd4c1318e57e0f44bd4451c","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-25T04:17:59.801Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.73","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.2.1","deplint":"^1.1.3","ts-jest":"^27.0.5","typedoc":"^0.22.4","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.15-renovate-major-external-major-121592ab0b6523265bd4c1318e57e0f44bd4451c_1632543735918_0.4166678434308504","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.15-repo-sync-secrets-default-918e83d36ce7c7a08d186ec0d8a9b17ed578e8b8":{"name":"@balena/jellyfish-core","version":"6.1.15-repo-sync-secrets-default-918e83d36ce7c7a08d186ec0d8a9b17ed578e8b8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.15-repo-sync-secrets-default-918e83d36ce7c7a08d186ec0d8a9b17ed578e8b8","maintainers":[{"name":"iayanpahwa","email":"ayan@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"81128718e1528c81ff43098cbc188049fffa9640","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.15-repo-sync-secrets-default-918e83d36ce7c7a08d186ec0d8a9b17ed578e8b8.tgz","fileCount":205,"integrity":"sha512-9BtsjYKXjvsC7zFcJdjWgh37Su4Rfq7HI4p2ztfsAGfQFBT5LoqLnyKA1Q/Viphy7VYnIT/YPKOlnXoBNLOwBw==","signatures":[{"sig":"MEQCIB9MpICpLCYGZYh+PcdAVzhULceKSzUs1AuPYzv/2/psAiBTG6H4rQkyFs+OmEpjz+bKJomrVfIS8HH0znlbsTBmlA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":749496},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"918e83d36ce7c7a08d186ec0d8a9b17ed578e8b8","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-25T10:35:40.510Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.73","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.4","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.15-repo-sync-secrets-default-918e83d36ce7c7a08d186ec0d8a9b17ed578e8b8_1632566491892_0.7304185146178124","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.15-repo-sync-secrets-default-88b7b1575d1d62f1444e18fb54fdbb30669d367f":{"name":"@balena/jellyfish-core","version":"6.1.15-repo-sync-secrets-default-88b7b1575d1d62f1444e18fb54fdbb30669d367f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.15-repo-sync-secrets-default-88b7b1575d1d62f1444e18fb54fdbb30669d367f","maintainers":[{"name":"iayanpahwa","email":"ayan@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"64ed2818c98b19027cef99dac126662f34115fe2","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.15-repo-sync-secrets-default-88b7b1575d1d62f1444e18fb54fdbb30669d367f.tgz","fileCount":205,"integrity":"sha512-zGOb2ciNnPhPMVnURF1+qrFxZcFzzK3ktc54FyeEqMHWUQVcptL5tby/GJ4GLc2H6jNo0lZuQRhuB9dBsiveTg==","signatures":[{"sig":"MEQCIGAbPAXkAb2NcwZ6+5Ekl1GdBds0CA7eW9xaZspGED4UAiA2tSmgAzWmMCKcb2znnUNO8dhCXaXLKX5HmWp/TUgftg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":749555},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"88b7b1575d1d62f1444e18fb54fdbb30669d367f","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-25T12:25:12.949Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.73","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.4","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.15-repo-sync-secrets-default-88b7b1575d1d62f1444e18fb54fdbb30669d367f_1632572884816_0.5452287391925337","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.15":{"name":"@balena/jellyfish-core","version":"6.1.15","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.15","maintainers":[{"name":"iayanpahwa","email":"ayan@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"eb74ca44eb4328d33784d39d386952390171355c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.15.tgz","fileCount":205,"integrity":"sha512-ja8o9xb6i7GDJ2ROhWW3CTG8a01yOd1XozJv7zQko0mlw5gh0zEORUefNk/g7Cahqb7PVgJyprBv1QcVyPKt+g==","signatures":[{"sig":"MEYCIQDuQyFYDqvRD2tZgN+o2AOcZyiLTRp0oGaUSOvy45PWUwIhAJzPPZMCBJgS2dhykDlr0RgoTiHTHmUPdT3HpSKqkN0+","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":749488},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"34040ccdf294ba8131a587668c445a79e573758e","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-25T12:36:06.906Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.73","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.4","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.15_1632573546615_0.08819850855983313","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.16-renovate-major-external-major-f87852f3590a4c336f80b0a3b83ff428c56dfa1a":{"name":"@balena/jellyfish-core","version":"6.1.16-renovate-major-external-major-f87852f3590a4c336f80b0a3b83ff428c56dfa1a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.16-renovate-major-external-major-f87852f3590a4c336f80b0a3b83ff428c56dfa1a","maintainers":[{"name":"iayanpahwa","email":"ayan@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"370ba932680b7c2eca8a273fc8710bbe9da7fcbe","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.16-renovate-major-external-major-f87852f3590a4c336f80b0a3b83ff428c56dfa1a.tgz","fileCount":205,"integrity":"sha512-ZIlgL8Zi8ENwT8T9nw1vx0qGeBSeFvrWA4+cEazkm3SXjEK2Ds2T1AtW7BXsXTKJvIMhjv1nqFLUclHtmZoR+A==","signatures":[{"sig":"MEYCIQCJmVF3eM9zYK/5tnA5VV32dQiMipko9LTh/uHQAUviiQIhALoErDK4divR7MaESbWHyT/LEGLukWxaLuoSiwNeS1PE","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":749629},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f87852f3590a4c336f80b0a3b83ff428c56dfa1a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-25T12:43:45.964Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.73","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.2.1","deplint":"^1.1.3","ts-jest":"^27.0.5","typedoc":"^0.22.4","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.16-renovate-major-external-major-f87852f3590a4c336f80b0a3b83ff428c56dfa1a_1632574024038_0.025907812098851624","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.16-renovate-major-json-schema-org-c9157152147a4e7ab0e099d81a87997c5c9c807a":{"name":"@balena/jellyfish-core","version":"6.1.16-renovate-major-json-schema-org-c9157152147a4e7ab0e099d81a87997c5c9c807a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.16-renovate-major-json-schema-org-c9157152147a4e7ab0e099d81a87997c5c9c807a","maintainers":[{"name":"iayanpahwa","email":"ayan@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"16581ac58067d18ce6500d486f1fefb56bfbb44a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.16-renovate-major-json-schema-org-c9157152147a4e7ab0e099d81a87997c5c9c807a.tgz","fileCount":205,"integrity":"sha512-scAFVtZxPtdQ33OCptegayKP62ysdPEzwLOvfSV3Jy4/OSd4uNuL7xWt/W/RhPjH1sXYkC4JzUui/DrKWuzZjA==","signatures":[{"sig":"MEUCIFO3kf9UvbjoXPdsz3E3fX1scCBCdTB7QB73Z6pddP4nAiEA9CN51uO0hJfDe6vyIz7xaVJf5rGO4iPnglibha4r+zk=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":749656},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c9157152147a4e7ab0e099d81a87997c5c9c807a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-25T12:42:51.945Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.73","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.4","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.16-renovate-major-json-schema-org-c9157152147a4e7ab0e099d81a87997c5c9c807a_1632574046153_0.9308269001039766","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.16-renovate-major-external-major-f62131509c24986174e1df142d693ddffb814350":{"name":"@balena/jellyfish-core","version":"6.1.16-renovate-major-external-major-f62131509c24986174e1df142d693ddffb814350","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.16-renovate-major-external-major-f62131509c24986174e1df142d693ddffb814350","maintainers":[{"name":"iayanpahwa","email":"ayan@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"55fa3b8c77937b7c5eeb58074fc7090485f554de","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.16-renovate-major-external-major-f62131509c24986174e1df142d693ddffb814350.tgz","fileCount":205,"integrity":"sha512-P50NN0imuXQqvLFhj9l1zbPoagbLDlKfHD6AWdDeM+/JX1bPMx4GtfNt70d3wq4PtV+A0LR4PlKx6TjpR7yq0Q==","signatures":[{"sig":"MEUCIGFTUDKgekhVQqfAMnlpxmPbFnzVS7UmwswN9ainHQIlAiEAwKmD5UAMAVAGoW65FXOehA6q7rUP6YKbNUY1mP80eog=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":749629},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f62131509c24986174e1df142d693ddffb814350","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-25T14:14:03.091Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.73","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.2.2","deplint":"^1.1.3","ts-jest":"^27.0.5","typedoc":"^0.22.4","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.16-renovate-major-external-major-f62131509c24986174e1df142d693ddffb814350_1632579562162_0.3093339958983192","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.16-renovate-internal-patch-a6c32593a42efb0c62acadaddd87ff0a6ab32428":{"name":"@balena/jellyfish-core","version":"6.1.16-renovate-internal-patch-a6c32593a42efb0c62acadaddd87ff0a6ab32428","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.16-renovate-internal-patch-a6c32593a42efb0c62acadaddd87ff0a6ab32428","maintainers":[{"name":"iayanpahwa","email":"ayan@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f4995a3e6ed3ac6b05d5c438b7aa952e086159df","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.16-renovate-internal-patch-a6c32593a42efb0c62acadaddd87ff0a6ab32428.tgz","fileCount":205,"integrity":"sha512-+wLR2RP4IM8S7BzUdga3ToV1Clcu6d1f+3X5oPwZHrJgNa+gdDW3+VIqDIphrkvhhcJHVwL4gqWUyyxcODRcYg==","signatures":[{"sig":"MEQCIAgEIjp8zdNHxb8CIigN0d99s5EyUECUfV6uFpmux2c7AiADCxEAszYE7YnQIIx0zR/Blt37TfETW5jsHBFB0tm1MQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":749656},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a6c32593a42efb0c62acadaddd87ff0a6ab32428","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-25T16:32:33.066Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.74","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.4","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.16-renovate-internal-patch-a6c32593a42efb0c62acadaddd87ff0a6ab32428_1632587771677_0.5749030138513924","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.16":{"name":"@balena/jellyfish-core","version":"6.1.16","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.16","maintainers":[{"name":"iayanpahwa","email":"ayan@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"aa341eb65d909273207b5d0a1ef3f5778e1d1287","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.16.tgz","fileCount":205,"integrity":"sha512-a6chM2QS5ebzmoPS7ZqWVwvAsM8f3Cuu3KoYzSmykXpTXex4XfGMZSF03GR2UzBNNrqRMw0tXuOCypZNvqijuA==","signatures":[{"sig":"MEUCIDyjHKDgMADS+hOSeouiZFqCNqUkAAZ3a1kBWOgCkMSEAiEAgmCJIxXrKDOPjZyv+pu15MNYmMb23MrND7hc4QW/NAI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":749591},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a715a395c6a743e3001654155eb99e449e71b23c","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-25T16:41:13.806Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.74","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.4","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.16_1632588189120_0.7798782661950465","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.17-renovate-major-json-schema-org-8949ebcf111022a71b16be0f62e57285a9defca6":{"name":"@balena/jellyfish-core","version":"6.1.17-renovate-major-json-schema-org-8949ebcf111022a71b16be0f62e57285a9defca6","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.17-renovate-major-json-schema-org-8949ebcf111022a71b16be0f62e57285a9defca6","maintainers":[{"name":"iayanpahwa","email":"ayan@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1c6f04697e0fa1235b55a73a09d053700f243f37","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.17-renovate-major-json-schema-org-8949ebcf111022a71b16be0f62e57285a9defca6.tgz","fileCount":205,"integrity":"sha512-ty1NtJoJ9l6dkXbg5eT5fHBMq9bCFV90gX/bsxr1L4NfQ+AMHCKyWN/4UHw5qZg4kdBp93ZsnkGCD7nyTqyJqg==","signatures":[{"sig":"MEQCIE7sxzgy1iN7LS6esXQsj9FIHFcqQdeqxdzYmxmQMTiWAiAMgdYbSW994wUk0esEN9Tqoq/uMxXklryIVISGVvphpA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":749759},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8949ebcf111022a71b16be0f62e57285a9defca6","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-25T16:43:55.455Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.74","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.4","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.17-renovate-major-json-schema-org-8949ebcf111022a71b16be0f62e57285a9defca6_1632588466805_0.38192290316070365","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.17-renovate-major-external-major-0a931ff972c7830ef713014828dd9b13dcef9b9f":{"name":"@balena/jellyfish-core","version":"6.1.17-renovate-major-external-major-0a931ff972c7830ef713014828dd9b13dcef9b9f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.17-renovate-major-external-major-0a931ff972c7830ef713014828dd9b13dcef9b9f","maintainers":[{"name":"iayanpahwa","email":"ayan@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f01a852d864c1f3d418a221f549780947f8b7a49","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.17-renovate-major-external-major-0a931ff972c7830ef713014828dd9b13dcef9b9f.tgz","fileCount":205,"integrity":"sha512-MHQkdoaWQYczFRqwlDxwn50IvC3YuKtF8eOmxg0291EM0h19+txib1f/tN03hyR0nrjDWE9bw9VLflKYz/QJvw==","signatures":[{"sig":"MEUCIQDRbrLN0Rl4iU4fAME/DVgVtQo8WVGaFA7+nCyJsYp0VwIgXbRu5RD0wtxhQkkAFnkFzCyhPYh/KrQOyCYZDNXMbIw=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":749732},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0a931ff972c7830ef713014828dd9b13dcef9b9f","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-25T16:47:21.801Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.74","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.2.2","deplint":"^1.1.3","ts-jest":"^27.0.5","typedoc":"^0.22.4","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.17-renovate-major-external-major-0a931ff972c7830ef713014828dd9b13dcef9b9f_1632588620639_0.34619912889845583","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.17-renovate-major-external-major-921d7b805ed0dddf2091071050ce7f88aba59236":{"name":"@balena/jellyfish-core","version":"6.1.17-renovate-major-external-major-921d7b805ed0dddf2091071050ce7f88aba59236","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.17-renovate-major-external-major-921d7b805ed0dddf2091071050ce7f88aba59236","maintainers":[{"name":"iayanpahwa","email":"ayan@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d2051a3e7cf44c7d6f0a131612404604a3715627","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.17-renovate-major-external-major-921d7b805ed0dddf2091071050ce7f88aba59236.tgz","fileCount":205,"integrity":"sha512-Tjp6tviGrQFstJn6KvVQau731YyiyB41m149ZT0T0NmwajwONqM8dDE0Y0BUiC3b24kWb4ny+fM/K+E0zpNWRw==","signatures":[{"sig":"MEQCIGQVAlewRK8aGWKTGQ+mIPe5jsiA9i3SKM0BhaxWwLiuAiAjDUJGkAo0TCqqNG3SDfVrr5gq54k+RlCwEOrDLv5XWA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":749732},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"921d7b805ed0dddf2091071050ce7f88aba59236","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-28T11:21:16.342Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.74","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.2.3","deplint":"^1.1.3","ts-jest":"^27.0.5","typedoc":"^0.22.4","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.17-renovate-major-external-major-921d7b805ed0dddf2091071050ce7f88aba59236_1632828309010_0.884852459165214","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.17-renovate-major-external-major-cac8dadaf5b67695d483aa0607d98e150293edbb":{"name":"@balena/jellyfish-core","version":"6.1.17-renovate-major-external-major-cac8dadaf5b67695d483aa0607d98e150293edbb","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.17-renovate-major-external-major-cac8dadaf5b67695d483aa0607d98e150293edbb","maintainers":[{"name":"iayanpahwa","email":"ayan@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"792b0a7621bede99a674c4603266f4751589b3a6","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.17-renovate-major-external-major-cac8dadaf5b67695d483aa0607d98e150293edbb.tgz","fileCount":205,"integrity":"sha512-jVP19tloyV74jxuNJenqCb1fWnu1pXdFui1aVT/4lcucPF3tuAQUvZ43e/OpYs6IPDHTVDND4HPw4y2jZ2XmOA==","signatures":[{"sig":"MEUCIQDBga/P/Tb8mPlGsTz+b8wyigF33HIqj3+en3rfoDbuWQIgLc7Vx0mmt45y497Cb8j+gm7W+CS2VPtRSD3l4+BdP0Q=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":749732},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"cac8dadaf5b67695d483aa0607d98e150293edbb","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-09-29T15:53:52.973Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.74","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.2.4","deplint":"^1.1.3","ts-jest":"^27.0.5","typedoc":"^0.22.4","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.17-renovate-major-external-major-cac8dadaf5b67695d483aa0607d98e150293edbb_1632931038810_0.20329747533669207","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.17-renovate-internal-patch-e86dcf8bd7eb82d6e73fd10fc0083a46e7fb557a":{"name":"@balena/jellyfish-core","version":"6.1.17-renovate-internal-patch-e86dcf8bd7eb82d6e73fd10fc0083a46e7fb557a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.17-renovate-internal-patch-e86dcf8bd7eb82d6e73fd10fc0083a46e7fb557a","maintainers":[{"name":"iayanpahwa","email":"ayan@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"544c201819121a968a10c65023f675278ec81cd3","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.17-renovate-internal-patch-e86dcf8bd7eb82d6e73fd10fc0083a46e7fb557a.tgz","fileCount":205,"integrity":"sha512-Ak07eaJUTuNQn5CmSDfTrmHcF4MZZ670b31CQr4hyy9QZai7gExtRGkDhBP2QmjmAJ8gJFsrVYj6YMhddOzNQw==","signatures":[{"sig":"MEQCIDJvBRiAzOEHKfa5zvhoUA4a3kMfJ/YBrMhd6FgPSQBPAiASH0jtVMQ9nVxOEPt90tCByWt1k6PowqG/o2pFNfLe6g==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":749759},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e86dcf8bd7eb82d6e73fd10fc0083a46e7fb557a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-02T01:12:39.471Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.75","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.4","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.17-renovate-internal-patch-e86dcf8bd7eb82d6e73fd10fc0083a46e7fb557a_1633137407430_0.49422884909469533","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.17":{"name":"@balena/jellyfish-core","version":"6.1.17","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.17","maintainers":[{"name":"iayanpahwa","email":"ayan@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d0022ba7244cca98c10386468c9bd29d68af264b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.17.tgz","fileCount":205,"integrity":"sha512-4tSK5qYMe+5GCUXNxlemXl9U/cgRoy5+WELVqfpA1EuqTPRl5V/QsdRD9CV3IJB536QACMc+ZgGs+byBeWToHQ==","signatures":[{"sig":"MEQCIBcCP6EITNGQjTg8bV/Mko3JXcPD81JfIP5X6bSZsNZIAiAZey5za3tgGhDavkoMKZ8AE4KtZTfZE3biR6FmdR8MQw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":749694},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"cc1fea14ac5558f3479c8cc4b005b5469ea282e2","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-02T01:21:54.225Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.75","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.4","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.17_1633137868998_0.5900103410274837","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.18-renovate-major-json-schema-org-17ba4abfea32b39a4bf4524669122c1bba7255b9":{"name":"@balena/jellyfish-core","version":"6.1.18-renovate-major-json-schema-org-17ba4abfea32b39a4bf4524669122c1bba7255b9","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.18-renovate-major-json-schema-org-17ba4abfea32b39a4bf4524669122c1bba7255b9","maintainers":[{"name":"iayanpahwa","email":"ayan@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4f662dc87aefef7d601e5fa8b92996c27e2f828f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.18-renovate-major-json-schema-org-17ba4abfea32b39a4bf4524669122c1bba7255b9.tgz","fileCount":205,"integrity":"sha512-C4lTakzfB1nn8/OJmlhIylqrkXoSfL4Rv/QQBAg4apZVp1nlzpF9kywCQDuGLs7d/H0S7/Fxy2EpgJwreWskAA==","signatures":[{"sig":"MEUCIDoIzT1/oroSGeYSXujBxax+xgud747zJgL99SB8qYw+AiEA17h6Tehh9ynRNXbmYA5TErxg72MvFd+pVAH6JBtE168=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":749862},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"17ba4abfea32b39a4bf4524669122c1bba7255b9","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-02T01:24:52.535Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.75","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.4","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.18-renovate-major-json-schema-org-17ba4abfea32b39a4bf4524669122c1bba7255b9_1633138122281_0.061829570201866524","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.18-renovate-major-external-major-08d76afbd659928e98df7e14276292190a95afcc":{"name":"@balena/jellyfish-core","version":"6.1.18-renovate-major-external-major-08d76afbd659928e98df7e14276292190a95afcc","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.18-renovate-major-external-major-08d76afbd659928e98df7e14276292190a95afcc","maintainers":[{"name":"iayanpahwa","email":"ayan@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ec7fa8e5202d05bde9e4a93269e65ea4ffea4272","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.18-renovate-major-external-major-08d76afbd659928e98df7e14276292190a95afcc.tgz","fileCount":205,"integrity":"sha512-f/QcmGzp0i7ITlO6B3DU4c4yLDrRrw5C+gI/GP1d3BY0xbXuud/eNkfJjCOQ6UjPZb+kLC40BW/RLfAs/Zm0hQ==","signatures":[{"sig":"MEYCIQDgLKyRHYkYPHWfyJ6Pg9g3yD4IeOITruUvKbZfjohXfgIhAPY1yMKo59smIbkm+i0fgJCUNgq9x0sS/QsskNj0MCtw","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":749835},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"08d76afbd659928e98df7e14276292190a95afcc","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-02T01:25:11.330Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.75","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.2.4","deplint":"^1.1.3","ts-jest":"^27.0.5","typedoc":"^0.22.4","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.18-renovate-major-external-major-08d76afbd659928e98df7e14276292190a95afcc_1633138139567_0.4038674129634341","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.18-renovate-external-non-major-7176e2e8e008c5c5d87581d330771ce54c5903cb":{"name":"@balena/jellyfish-core","version":"6.1.18-renovate-external-non-major-7176e2e8e008c5c5d87581d330771ce54c5903cb","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.18-renovate-external-non-major-7176e2e8e008c5c5d87581d330771ce54c5903cb","maintainers":[{"name":"iayanpahwa","email":"ayan@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"73aa5dbb8c7aea2cae89a3fcf3e2c6785bc220be","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.18-renovate-external-non-major-7176e2e8e008c5c5d87581d330771ce54c5903cb.tgz","fileCount":205,"integrity":"sha512-kYMFOWhgYMNJq9ex9oR78JMUC6Xa16ijE2Nq7DfhBnOFT3GmFnzO53gjRsd/xFJAKBIuBiqy6pywMQ7L4PHhIQ==","signatures":[{"sig":"MEUCIQDPEsQQp3RQiJcU9eBOmqn3gKWtV7q5hA2v3PTN+gxNrAIgM2IXeHHXD1MzJeWIGeHOnWSJNJtgYnD41Wkl6FLY12Q=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":749849},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7176e2e8e008c5c5d87581d330771ce54c5903cb","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-02T18:28:20.819Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.75","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.5","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.18-renovate-external-non-major-7176e2e8e008c5c5d87581d330771ce54c5903cb_1633199489069_0.17841665006888574","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.18":{"name":"@balena/jellyfish-core","version":"6.1.18","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.18","maintainers":[{"name":"iayanpahwa","email":"ayan@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0fa3406a01f47090eddca4e07746a7c4597d75bb","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.18.tgz","fileCount":205,"integrity":"sha512-rfmqaIF8Ry+8rSQkoUZlMH25nqWhEET9xp2jj0RPCbWLm3xkgFsX2ppMF3Rd5jpEIr/ponx71fD8tiFKl9khWA==","signatures":[{"sig":"MEYCIQC+QIpJiMBbvZeEMDMF5j4vvBPwHrvwKuOKRlDt7x6o8wIhAOoaXqsXdl/DXFwQKsu1CkObtbKUsjaMqWDg7M0P8ejo","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":749780},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"91666d23b45f31166ca7ee4fe6a88a6b9cafa3f1","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-02T18:37:20.773Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.75","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.5","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.18_1633199951090_0.4182713357753376","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.19-renovate-major-json-schema-org-3c15f7055daa1b8f2f424af64acc0028e0afa5df":{"name":"@balena/jellyfish-core","version":"6.1.19-renovate-major-json-schema-org-3c15f7055daa1b8f2f424af64acc0028e0afa5df","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.19-renovate-major-json-schema-org-3c15f7055daa1b8f2f424af64acc0028e0afa5df","maintainers":[{"name":"iayanpahwa","email":"ayan@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"7a28fcb89e0bf1be8cfadc9a3034758695ff9dd8","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.19-renovate-major-json-schema-org-3c15f7055daa1b8f2f424af64acc0028e0afa5df.tgz","fileCount":205,"integrity":"sha512-Zrm55U0/xJx3peZ8FCdpAAyVmBKTaqsYqJJ3mXCOuhQBz3LBwpG2pXll795PqDW+oLkaMpQ4Pc5chszaL6PJLQ==","signatures":[{"sig":"MEUCIHwNCNR3fin0oGJndRRnsv6td4MTKo7McYABQ1Q+6f+8AiEAtZsEHpsWpOzFbDYS5s+4owYdByLv3QyFpFzR67TBJaM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":749948},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"3c15f7055daa1b8f2f424af64acc0028e0afa5df","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-02T18:40:01.397Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.75","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.5","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.19-renovate-major-json-schema-org-3c15f7055daa1b8f2f424af64acc0028e0afa5df_1633200185739_0.5192489904454165","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.19-renovate-major-external-major-9989d67bd42f1dae26b959376a337d587dc9b717":{"name":"@balena/jellyfish-core","version":"6.1.19-renovate-major-external-major-9989d67bd42f1dae26b959376a337d587dc9b717","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.19-renovate-major-external-major-9989d67bd42f1dae26b959376a337d587dc9b717","maintainers":[{"name":"iayanpahwa","email":"ayan@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b74e82b99b02358e48c1cdda87a77115cf59a363","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.19-renovate-major-external-major-9989d67bd42f1dae26b959376a337d587dc9b717.tgz","fileCount":205,"integrity":"sha512-jV6vNuugzgNQ/RP86lk5s0D1V+LmPRs6fOrEQ7izNOvKdl5Et53y5gNtZPOQDlmiMlHvRsKi2DvbOP5v7U5hjQ==","signatures":[{"sig":"MEUCIQD5bPQIuDZC0GyQz7Rm+QZmCf/yecPTeDf/NDBVeA8VwgIgJb4PQAxdAs0nOqsVBlKVpaj0VTEHhKN+Y469gLcmbh8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":749921},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"9989d67bd42f1dae26b959376a337d587dc9b717","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-02T18:40:20.831Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.75","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.2.4","deplint":"^1.1.3","ts-jest":"^27.0.5","typedoc":"^0.22.5","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.19-renovate-major-external-major-9989d67bd42f1dae26b959376a337d587dc9b717_1633200217829_0.3949213713087194","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.19-renovate-internal-patch-377dbe3c597ce2fbff1737b42fd11f52f2492394":{"name":"@balena/jellyfish-core","version":"6.1.19-renovate-internal-patch-377dbe3c597ce2fbff1737b42fd11f52f2492394","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.19-renovate-internal-patch-377dbe3c597ce2fbff1737b42fd11f52f2492394","maintainers":[{"name":"iayanpahwa","email":"ayan@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f74591521a281ca936835ab9dfd964ec283f0791","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.19-renovate-internal-patch-377dbe3c597ce2fbff1737b42fd11f52f2492394.tgz","fileCount":205,"integrity":"sha512-UMMxh9caMTi0SvISqs7+hdQLxOr8r7nVnQb2sOZYOSFlB07bCrJ/NM0t09rxG0AXB1TKOzogf2cSwOHWvExS4Q==","signatures":[{"sig":"MEUCIAGgv3FHZqbOmR5hHWWcyTXXOUtFkHqUoZPoFvmym0hoAiEAx+HEbX1NvqRA4W2rIAx0qIyDFVaiS1sqLd8G+h5PWS0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":749948},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"377dbe3c597ce2fbff1737b42fd11f52f2492394","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-02T19:47:51.132Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.76","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.5","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.19-renovate-internal-patch-377dbe3c597ce2fbff1737b42fd11f52f2492394_1633204285515_0.01930348260922865","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.19":{"name":"@balena/jellyfish-core","version":"6.1.19","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.19","maintainers":[{"name":"iayanpahwa","email":"ayan@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2ec11ad07b612822dceffe48234c4ae7407dc5ae","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.19.tgz","fileCount":205,"integrity":"sha512-SJp21A7Afn/Hi1pw/aoVeLkDEzQlK7He1YbSqtqJmM/0q4qCapyxfX/r1RaNOUj5f/H3xCnMFBn+xMFZ2He8+g==","signatures":[{"sig":"MEUCIQCpu6vxhOfulxLtNSrIBIw+XIwz6BuozOu1uVZXn4XsLwIgJrAQBSaSK9Ctx5fPzI+zN6VdKJS8N1kcsJoF48do018=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":749883},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8a417b767af89f7090ca0ce42b547ccde76428c4","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-02T19:56:10.710Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.76","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.5","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.19_1633204681791_0.9770348612615531","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.20-renovate-major-json-schema-org-11cfdee12df7d06959a8e673788a7fa37003c734":{"name":"@balena/jellyfish-core","version":"6.1.20-renovate-major-json-schema-org-11cfdee12df7d06959a8e673788a7fa37003c734","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.20-renovate-major-json-schema-org-11cfdee12df7d06959a8e673788a7fa37003c734","maintainers":[{"name":"iayanpahwa","email":"ayan@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a09ab31d12e00f0b12fc9c12539892727358044e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.20-renovate-major-json-schema-org-11cfdee12df7d06959a8e673788a7fa37003c734.tgz","fileCount":205,"integrity":"sha512-Lwndi/suQ/kuJP8VkgUIyfsEwtRdFhbwsKBcqURtRmjuPxyw0i2q3fKhRsNLtX/GHz3IXHkTJyYIHQtFPGUJNA==","signatures":[{"sig":"MEUCIQD60joFe3oAw85BNv72CDs2vqyOE1DtBYnjOU5SjyUjkwIgBN0nzIqgWKd8I9mtb2hTUyzjyTKDCwh19MscWU4Ewxs=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":750051},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"11cfdee12df7d06959a8e673788a7fa37003c734","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-02T19:59:32.856Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.76","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.5","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.20-renovate-major-json-schema-org-11cfdee12df7d06959a8e673788a7fa37003c734_1633204958753_0.8742098575331163","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.20-repo-sync-jellyfish-config-default-9c2125a605d9529eb1015ad7d5c41a62bc2b5806":{"name":"@balena/jellyfish-core","version":"6.1.20-repo-sync-jellyfish-config-default-9c2125a605d9529eb1015ad7d5c41a62bc2b5806","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.20-repo-sync-jellyfish-config-default-9c2125a605d9529eb1015ad7d5c41a62bc2b5806","maintainers":[{"name":"iayanpahwa","email":"ayan@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b63dd2340fb732b0828ab8734ad146dcd526599c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.20-repo-sync-jellyfish-config-default-9c2125a605d9529eb1015ad7d5c41a62bc2b5806.tgz","fileCount":205,"integrity":"sha512-phomW9KdqI6jY0ANLL+9fhS01sbUvIbSM3Go9fYgg/4Asv+IN+6DbkFO0Kt6LnkNfXzgG/0lAQtRM0nmc+tvGw==","signatures":[{"sig":"MEUCIQCDBDum+jiumVQ9nGygiv9hJNcs8/71NwkNFQi89kRGqwIgPET3tPd8IhQp1QjJ1Ged2j3c4T+ZHsTCQTfdScfJY6c=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":750051},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"9c2125a605d9529eb1015ad7d5c41a62bc2b5806","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-03T09:08:01.733Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.76","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.5","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.4.5"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.20-repo-sync-jellyfish-config-default-9c2125a605d9529eb1015ad7d5c41a62bc2b5806_1633252255359_0.1693547371661588","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.20-renovate-balena-jellyfish-config-1-x-c415c493035c4a965d38c181ca45acba7a90a99d":{"name":"@balena/jellyfish-core","version":"6.1.20-renovate-balena-jellyfish-config-1-x-c415c493035c4a965d38c181ca45acba7a90a99d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.20-renovate-balena-jellyfish-config-1-x-c415c493035c4a965d38c181ca45acba7a90a99d","maintainers":[{"name":"iayanpahwa","email":"ayan@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"7733b640b2795fb212f365d2f522910c75ef257b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.20-renovate-balena-jellyfish-config-1-x-c415c493035c4a965d38c181ca45acba7a90a99d.tgz","fileCount":205,"integrity":"sha512-ZzvtU5Mf39A/X+pe9lsWrIzxqtSH8Cc/7lvIGKgzyV+4bwCKUtvWP5ef5oVcwN/o/Bte5F8zgL24t/DE9c0uTA==","signatures":[{"sig":"MEUCICkjjiM9aCddPe6pDZ7HC9QFRzTzIcJ+7YKxzo0g+MRYAiEA7pqKxVkn+mNwbMYJjJWLYR3ifDtslW9qvg4p6hqh5do=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":750063},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c415c493035c4a965d38c181ca45acba7a90a99d","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-03T10:35:03.553Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.76","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.5","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.20-renovate-balena-jellyfish-config-1-x-c415c493035c4a965d38c181ca45acba7a90a99d_1633257547540_0.11495402609654892","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.20":{"name":"@balena/jellyfish-core","version":"6.1.20","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.20","maintainers":[{"name":"iayanpahwa","email":"ayan@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"91361f627fc8d4c3142923fbae33dad344070d8d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.20.tgz","fileCount":205,"integrity":"sha512-eoS69VEGa/QD62ePxwNfKNVUKj2YddAiaszxb5Cn1crBs0LwS5xJ0ypC7NlUqRi7su6uR9P44c3hs9CXYRENkg==","signatures":[{"sig":"MEUCIQD/T1Z1IT0u4hD/hUQ6nV5+llhg8K9bC4BsFmlLaHgxGQIgb3cCMSjlp9ed3jhlGrxn6XWQocHcQT0ODnB8D84Mdtc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":749985},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d4666ec63d697e072b46f5103171842bd69e7db7","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-03T10:43:17.068Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.76","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.5","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.20_1633257936264_0.5188165488039447","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.21-renovate-major-json-schema-org-69f9452f5318630d889bebcc24f03fac9d0a0f0d":{"name":"@balena/jellyfish-core","version":"6.1.21-renovate-major-json-schema-org-69f9452f5318630d889bebcc24f03fac9d0a0f0d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.21-renovate-major-json-schema-org-69f9452f5318630d889bebcc24f03fac9d0a0f0d","maintainers":[{"name":"iayanpahwa","email":"ayan@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"982e2e825421f702b7f785e181ec884cf11179eb","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.21-renovate-major-json-schema-org-69f9452f5318630d889bebcc24f03fac9d0a0f0d.tgz","fileCount":205,"integrity":"sha512-0QftyuI0twdMijvGjaRpla3KX2IFBoFPuuJMzw0ZOt5LR0P/1lvAt1XdyFsEBESvY44qO5HkEdQqnZdnZLUXhg==","signatures":[{"sig":"MEUCIEtotrpbE3BtHnNfemRVQsI0lQq4DB5mDVJfQL7olNX3AiEAiO/x3rdUpFMD7Jb0U30PwtnLSnTHhUYNS8Q1XlEIa7o=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":750153},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"69f9452f5318630d889bebcc24f03fac9d0a0f0d","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-03T10:46:32.082Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.76","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.5","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.21-renovate-major-json-schema-org-69f9452f5318630d889bebcc24f03fac9d0a0f0d_1633258211838_0.9414520790297456","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.21-renovate-major-external-major-de5e65b0e2cf495faa036363f6d9a4007e95615a":{"name":"@balena/jellyfish-core","version":"6.1.21-renovate-major-external-major-de5e65b0e2cf495faa036363f6d9a4007e95615a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.21-renovate-major-external-major-de5e65b0e2cf495faa036363f6d9a4007e95615a","maintainers":[{"name":"iayanpahwa","email":"ayan@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ad157f1b47159773809645b7b562b02b1deecae4","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.21-renovate-major-external-major-de5e65b0e2cf495faa036363f6d9a4007e95615a.tgz","fileCount":205,"integrity":"sha512-nhZBeTDrM09SNcP8DzSaylgS++fEpsw3LjVAP6Yj9JgP5aJrgF60pn7LsdnwejHT6QWLZ4Rs6T/5ymIVE1K/4w==","signatures":[{"sig":"MEUCICyQRbmKvX6aF3UdZ2lVtHkAwPLN8YXgwTtyosmsN43/AiEAtdpBUKpVdTJZzM+eyzSdNrPzPUVKMPQ7cH8ZbkVN5m8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":750126},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"de5e65b0e2cf495faa036363f6d9a4007e95615a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-03T10:50:41.144Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.76","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.2.4","deplint":"^1.1.3","ts-jest":"^27.0.5","typedoc":"^0.22.5","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.21-renovate-major-external-major-de5e65b0e2cf495faa036363f6d9a4007e95615a_1633258428469_0.2744664337692424","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.21-repo-sync-jellyfish-config-default-9f536a4fcd04c20d549192daefd69370422efe90":{"name":"@balena/jellyfish-core","version":"6.1.21-repo-sync-jellyfish-config-default-9f536a4fcd04c20d549192daefd69370422efe90","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.21-repo-sync-jellyfish-config-default-9f536a4fcd04c20d549192daefd69370422efe90","maintainers":[{"name":"iayanpahwa","email":"ayan@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0db5e33a2a1df38ca4144570aeafdb861f5dfc11","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.21-repo-sync-jellyfish-config-default-9f536a4fcd04c20d549192daefd69370422efe90.tgz","fileCount":205,"integrity":"sha512-q8ACQkdxHWNAe1KdLbbLjKKCAvS6FbafCas3zJVqvFPaW3nY4IDFOM9nzldz0/qo82eesneIKMlTWzOPXc4xVQ==","signatures":[{"sig":"MEYCIQDJEZmpigj29RxaFRtgk7GMht6kOsNinI7QA8DmtDRexgIhAJ/pmXSHqwsUu1A2pIJdwsqg8528TPpauPvuzmAoZa9c","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":750153},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"9f536a4fcd04c20d549192daefd69370422efe90","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-04T03:29:21.889Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.76","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.5","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.21-repo-sync-jellyfish-config-default-9f536a4fcd04c20d549192daefd69370422efe90_1633318310642_0.38630792586479323","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.21-renovate-internal-patch-c1b628b73eaa77af62556be2985be7687e7c4bb6":{"name":"@balena/jellyfish-core","version":"6.1.21-renovate-internal-patch-c1b628b73eaa77af62556be2985be7687e7c4bb6","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.21-renovate-internal-patch-c1b628b73eaa77af62556be2985be7687e7c4bb6","maintainers":[{"name":"iayanpahwa","email":"ayan@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ee9933ac2c85c867a0f5d8835d5c6fba6165d036","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.21-renovate-internal-patch-c1b628b73eaa77af62556be2985be7687e7c4bb6.tgz","fileCount":205,"integrity":"sha512-gDgk8tlGVVOIO1IxKsthE9dv83X1eqQE1O6AHA3NycgqGj74c8wyLb/JHz33d6dSUqIapqGfFCDLgxh6TPXp7A==","signatures":[{"sig":"MEUCIQCehp6UPiZZzxUXhfR2oEOOhWTOfQPsOewrL+8BvVYFaAIgGYdaWS3qJS8yJOY/b3ziIO1LdxLr7x/wyzjNJcgv1Vc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":750153},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c1b628b73eaa77af62556be2985be7687e7c4bb6","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-04T10:42:31.229Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.77","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.5","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.21-renovate-internal-patch-c1b628b73eaa77af62556be2985be7687e7c4bb6_1633344359639_0.08172866909759935","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.21":{"name":"@balena/jellyfish-core","version":"6.1.21","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.21","maintainers":[{"name":"iayanpahwa","email":"ayan@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"46000bd273bb950944664e33fb0d1dc033444564","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.21.tgz","fileCount":205,"integrity":"sha512-GwOMM3rSqGElFoROfjLuhDLt3PZD+QIEgDI6XNdKs3IwfiFPYLKROPr/v0Gy00/gZ7KSpKv8vqreXBQJf17jRw==","signatures":[{"sig":"MEQCIHetNVKiMgWojBKaohnmIm2huTWtEBi9VrF8DVNbGbsnAiAL49xyRTUCIfvNQvzfihegkcTTPjsB+1wBGqdCfvigsA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":750088},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"05e118fc44974893d356c1a25431f737491240c8","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-04T10:52:14.603Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.77","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.5","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.21_1633344871198_0.07560292296996862","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.22-renovate-major-external-major-b8d687f99fab87c733a271cf45615834a15c4f34":{"name":"@balena/jellyfish-core","version":"6.1.22-renovate-major-external-major-b8d687f99fab87c733a271cf45615834a15c4f34","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.22-renovate-major-external-major-b8d687f99fab87c733a271cf45615834a15c4f34","maintainers":[{"name":"iayanpahwa","email":"ayan@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0ec2027f851cd1880dd6bb618dec9de0c49ea183","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.22-renovate-major-external-major-b8d687f99fab87c733a271cf45615834a15c4f34.tgz","fileCount":205,"integrity":"sha512-b+4vwl4g4CNH/1IBJ1jbsdjGtQJlT6ynS+prnQnKdOJZsYkFMfFqLFpw8Qa2a6mX6XqcJTdvj2mP51tcyQATZg==","signatures":[{"sig":"MEUCICu8HCIOf5J+uEm3pspiHwW7rLFNb+RJ1FpXbFVKM36SAiEAwwVry//QUBU1xvQ5RK2fuq8i2vGYD0z6DjfoL/HMxqU=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":750229},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b8d687f99fab87c733a271cf45615834a15c4f34","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-04T10:54:17.459Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.77","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.2.4","deplint":"^1.1.3","ts-jest":"^27.0.5","typedoc":"^0.22.5","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.22-renovate-major-external-major-b8d687f99fab87c733a271cf45615834a15c4f34_1633345105049_0.5743455459113567","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.22-renovate-major-json-schema-org-5cd38350d322bd683ac1f02055513ef02c373477":{"name":"@balena/jellyfish-core","version":"6.1.22-renovate-major-json-schema-org-5cd38350d322bd683ac1f02055513ef02c373477","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.22-renovate-major-json-schema-org-5cd38350d322bd683ac1f02055513ef02c373477","maintainers":[{"name":"iayanpahwa","email":"ayan@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"7af8e14f1c09895241a002d22cab984cd7869202","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.22-renovate-major-json-schema-org-5cd38350d322bd683ac1f02055513ef02c373477.tgz","fileCount":205,"integrity":"sha512-rIN1XHg8CSI4mgn9D7EjorWSLdBwxEugoU/XG0JvY7tgapmby2xabSIp/UakIxQaalrW/wTQLcoHFx3TzJyhhA==","signatures":[{"sig":"MEYCIQCOkgAIgDFYhyHvTwBHA6/Ete7IKQa/bYf03tD5Dn83wwIhAOn0A3pknWsDt0N72EfCjJeqafArxfKgAe8rGNbZhZH+","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":750256},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"5cd38350d322bd683ac1f02055513ef02c373477","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-04T10:58:31.115Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.77","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.5","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.22-renovate-major-json-schema-org-5cd38350d322bd683ac1f02055513ef02c373477_1633345336665_0.5269401431878904","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.22-renovate-internal-patch-fbf9ef289abe7c1a7a9079263d4767e425684845":{"name":"@balena/jellyfish-core","version":"6.1.22-renovate-internal-patch-fbf9ef289abe7c1a7a9079263d4767e425684845","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.22-renovate-internal-patch-fbf9ef289abe7c1a7a9079263d4767e425684845","maintainers":[{"name":"iayanpahwa","email":"ayan@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0b870f2b51e833be46ad72b17af5cb15ea6acd10","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.22-renovate-internal-patch-fbf9ef289abe7c1a7a9079263d4767e425684845.tgz","fileCount":205,"integrity":"sha512-Fb8nQXK/zItoksR8WKw+jWzjCsZAyJJNC1RSXR8aKdJNOfXouqFClCp/kpnTjidR/DugIpOPHYC5NFRvwoClZg==","signatures":[{"sig":"MEUCIDqtfaPxt4VIzIv5rf4mLage8t5rYMOyVdq+o4mm110sAiEAlcjweiI/7rvkXuerMZRZQhxy0/Jy200KrloP11oj0y0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":750256},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"fbf9ef289abe7c1a7a9079263d4767e425684845","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-04T11:04:22.583Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.78","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.5","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.22-renovate-internal-patch-fbf9ef289abe7c1a7a9079263d4767e425684845_1633345680704_0.24126734679285677","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.22":{"name":"@balena/jellyfish-core","version":"6.1.22","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.22","maintainers":[{"name":"iayanpahwa","email":"ayan@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b2845811fa5af4b968e5579705710ef1bde0b41b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.22.tgz","fileCount":205,"integrity":"sha512-EHTL3YbUnxxL90FdnN5VYkUVWmfzvJOSFYXk8S23t2OdyYhhcjczkTowR5GR3sLcJtM70wybfnvmzLwCiNAj7g==","signatures":[{"sig":"MEUCICiyYP6oPycPwMEMCa/W1Vum/NJCb1lFlfDDyM5WB/VLAiEAkbfMO+em3rTUAeoWJiTSpHF7a6DSDy9DRtihljCGs2s=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":750191},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b13673499dfdcfb91905e0b304396f9e713ec798","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-04T11:16:11.903Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.78","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.5","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.22_1633346298404_0.718303189029021","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.23-renovate-major-json-schema-org-753391df4ed0c98622a405824a6f92dd95fae331":{"name":"@balena/jellyfish-core","version":"6.1.23-renovate-major-json-schema-org-753391df4ed0c98622a405824a6f92dd95fae331","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.23-renovate-major-json-schema-org-753391df4ed0c98622a405824a6f92dd95fae331","maintainers":[{"name":"iayanpahwa","email":"ayan@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0014818311ac7a6858d262b1b7e3b83c60a01c74","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.23-renovate-major-json-schema-org-753391df4ed0c98622a405824a6f92dd95fae331.tgz","fileCount":205,"integrity":"sha512-SQ0FbXncJ76WJIJ7CToG2YvFpUqGYEZP80NxReYtlq/tSzFrCwFkOc4ewHdPWhEngU+zEOrzkqY0SJ+2bYMsuA==","signatures":[{"sig":"MEYCIQCxr83Wmadm281rwcJ+Z28MRCuevgfvtOrNipf2iBrTVAIhAIH0f/TRT7jP//58+FLw/V5cxwQgcxwq6+0SEVN5nW7z","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":750359},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"753391df4ed0c98622a405824a6f92dd95fae331","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-04T11:19:31.749Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.78","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.5","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.23-renovate-major-json-schema-org-753391df4ed0c98622a405824a6f92dd95fae331_1633346576863_0.6280650789686215","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.23-renovate-major-external-major-4dd26a894bfcf98086a9929aa181a54e2fd2848d":{"name":"@balena/jellyfish-core","version":"6.1.23-renovate-major-external-major-4dd26a894bfcf98086a9929aa181a54e2fd2848d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.23-renovate-major-external-major-4dd26a894bfcf98086a9929aa181a54e2fd2848d","maintainers":[{"name":"iayanpahwa","email":"ayan@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e6e39a44bd825f5d25be54ec60f4dfacd1b60a52","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.23-renovate-major-external-major-4dd26a894bfcf98086a9929aa181a54e2fd2848d.tgz","fileCount":205,"integrity":"sha512-c61NlZSYNJrEdAQWh64aUcjMQcrRUJ1tbQUZOVNs6c37kOmPtWzAGLZfdBPcePWFPTNfTlo/yAnzcQeDt4f1eA==","signatures":[{"sig":"MEQCIAKnigRuAKl09kxBcbceAkWG0ltaumVr8QQzMn99JZTSAiB3d045UoVgnayh6ww84peXx33WWXjJNPihMOYgI98eJQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":750332},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4dd26a894bfcf98086a9929aa181a54e2fd2848d","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-core_sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-04T11:22:41.125Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.78","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.2.4","deplint":"^1.1.3","ts-jest":"^27.0.5","typedoc":"^0.22.5","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.23-renovate-major-external-major-4dd26a894bfcf98086a9929aa181a54e2fd2848d_1633346726668_0.6452014109268343","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.23-repo-sync-jellyfish-config-default-dad33beb2affb08ec3cb58075e73f2c61f2b316d":{"name":"@balena/jellyfish-core","version":"6.1.23-repo-sync-jellyfish-config-default-dad33beb2affb08ec3cb58075e73f2c61f2b316d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.23-repo-sync-jellyfish-config-default-dad33beb2affb08ec3cb58075e73f2c61f2b316d","maintainers":[{"name":"iayanpahwa","email":"ayan@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6d0bf1da4c55fe57b33a2df05d673a70ebcf0189","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.23-repo-sync-jellyfish-config-default-dad33beb2affb08ec3cb58075e73f2c61f2b316d.tgz","fileCount":205,"integrity":"sha512-d8ATLtp8B9PLAmvvNezsZ8dLadLJIoMoo732evIsxv82Yv9vY46HEnY5wiAyZvQR1e3MBbn0ODx8UYmLs2Drww==","signatures":[{"sig":"MEUCIAIXXvFuNeMppWxTIqIyPdtG+A+fas7UVcePgTsTn3JGAiEA2WWs0IN/fFLWLzIaHeAuxn6y976k+rvhmNqaaoJCoRA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":750411},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"dad33beb2affb08ec3cb58075e73f2c61f2b316d","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-05T00:55:20.161Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.78","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.5","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.23-repo-sync-jellyfish-config-default-dad33beb2affb08ec3cb58075e73f2c61f2b316d_1633395527451_0.0844366223629347","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.23":{"name":"@balena/jellyfish-core","version":"6.1.23","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.23","maintainers":[{"name":"iayanpahwa","email":"ayan@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"369a2a580ac875dac3008b3a229204a713fce999","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.23.tgz","fileCount":205,"integrity":"sha512-3rvJFnzt31wHDwU5ILsS2rg9JylNcdLImex0PVX19q/PTd0O3tLQPq0BFIDqI6+5v+Md4/1wHvdTM6AkxoloMA==","signatures":[{"sig":"MEQCIB9esqRrbqDOrbz4+y3VK/tZVMZwkWoZCakmUuMd8ZP2AiBg1dhFfHkjwGhbfvR/06GpQx2d00BB9XBF6WRWjd/Ohw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":750335},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ed316db957a4e4be72bc11e204847b0ef1f92b63","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-05T01:08:01.390Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.78","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.5","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.23_1633396189097_0.767954026883048","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.24-renovate-major-json-schema-org-13485bc7fe2db387abae11a41b47566f11bfcc8c":{"name":"@balena/jellyfish-core","version":"6.1.24-renovate-major-json-schema-org-13485bc7fe2db387abae11a41b47566f11bfcc8c","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.24-renovate-major-json-schema-org-13485bc7fe2db387abae11a41b47566f11bfcc8c","maintainers":[{"name":"iayanpahwa","email":"ayan@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ea545d5063a66b60c71b2a5f4c7529b2daf4c7c5","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.24-renovate-major-json-schema-org-13485bc7fe2db387abae11a41b47566f11bfcc8c.tgz","fileCount":205,"integrity":"sha512-FzAJd4Eg1WRFqOWxp6RRuG0qP17mI1VTTp2MvrJMk8faMo1pzWkRubhD3dXKSbcVcVyj3sMthwBJaDjaFDhjkg==","signatures":[{"sig":"MEUCIQCMLLeYeg0PF6TgSGEIal+Z92ALGIyEBVKJSCwWlXwl6wIgd83Ihv//jqSpGR/kuheu53jyJOYueO4gPm6XnJLffgE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":750503},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"13485bc7fe2db387abae11a41b47566f11bfcc8c","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-05T09:29:37.152Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.78","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.5","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.24-renovate-major-json-schema-org-13485bc7fe2db387abae11a41b47566f11bfcc8c_1633426497691_0.40838426708209474","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.24-h32-fix-parent-merged-2e83059e9f6cc8e014b9bf7884b1b35fa7db6a2a":{"name":"@balena/jellyfish-core","version":"6.1.24-h32-fix-parent-merged-2e83059e9f6cc8e014b9bf7884b1b35fa7db6a2a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.24-h32-fix-parent-merged-2e83059e9f6cc8e014b9bf7884b1b35fa7db6a2a","maintainers":[{"name":"iayanpahwa","email":"ayan@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"98639eb3b1981f173e7a2e39c871344ddc8319f1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.24-h32-fix-parent-merged-2e83059e9f6cc8e014b9bf7884b1b35fa7db6a2a.tgz","fileCount":205,"integrity":"sha512-9utfqPY364lIRMtsjzx+vkZ57sK46BmAs5PsPhACsLBbxqpTnuf45yKFs15JYTHUWwtVVbGy+Zjed3KhktbXvQ==","signatures":[{"sig":"MEQCIEJ2co1ahOnEBgr7zeCb+gnBSfxmn0coEDKEU7vQGkKLAiAHAheau82MH+Kc9vx/t6Fh+maSimSgMXAS15EsdrmkGg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":751276},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2e83059e9f6cc8e014b9bf7884b1b35fa7db6a2a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-05T10:56:52.090Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.78","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.5","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.24-h32-fix-parent-merged-2e83059e9f6cc8e014b9bf7884b1b35fa7db6a2a_1633431652769_0.8445299239919277","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.24":{"name":"@balena/jellyfish-core","version":"6.1.24","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.24","maintainers":[{"name":"iayanpahwa","email":"ayan@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c52e96d04d328c25b911ed90f2d4a93050dff022","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.24.tgz","fileCount":205,"integrity":"sha512-aE8OHlqRscdG9Z2MecTOvgUsUz52JqT7+DS6b4LXUairO7M2Pr3T8v7y8C27ClvjcQ3/yiB3qKQcJDxaGzZ7mQ==","signatures":[{"sig":"MEUCIQDzgf1TyZCKIYOniGZ0vWbWJzpg06dxDy//vWyVl9eOawIgMU7IcGkv3vi2anFGkGwnnUhDGfxlXDcg6VAh4cvQYoY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":751213},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d061155d62f241cfb0521e8d9cbb9f3c6c870281","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-05T11:06:09.422Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.78","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.5","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.24_1633432082853_0.7399688056182974","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.25-renovate-internal-patch-714c86fae349f5dc3bae930dd12974c893b7a1ce":{"name":"@balena/jellyfish-core","version":"6.1.25-renovate-internal-patch-714c86fae349f5dc3bae930dd12974c893b7a1ce","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.25-renovate-internal-patch-714c86fae349f5dc3bae930dd12974c893b7a1ce","maintainers":[{"name":"iayanpahwa","email":"ayan@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"320183d2f8a135ab927dac4189e4f4769020e32c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.25-renovate-internal-patch-714c86fae349f5dc3bae930dd12974c893b7a1ce.tgz","fileCount":205,"integrity":"sha512-2ndhR1n9zztd5SI2qIDUNZqR1GGRdwDLArmakrrlUc1pD5eZl3IDiGtju9Fe8PudO7ElG5ZXsMciUAv1rC7IJw==","signatures":[{"sig":"MEQCIC5b1qLMxgqTJq+pHP1jx83nYPjcVEg0E/ob+r9AxIoVAiB5VZPsDnw384IYUErSPAqiRPhNIwnc5IZEoTrPGGP2Xw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":751349},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"714c86fae349f5dc3bae930dd12974c893b7a1ce","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-05T11:11:29.496Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.80","@balena/jellyfish-metrics":"^1.0.342","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.5","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.25-renovate-internal-patch-714c86fae349f5dc3bae930dd12974c893b7a1ce_1633432514404_0.3986917746960146","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.25-renovate-major-external-major-5e6baae555d405d70454e3a566be06a7bcf6e792":{"name":"@balena/jellyfish-core","version":"6.1.25-renovate-major-external-major-5e6baae555d405d70454e3a566be06a7bcf6e792","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.25-renovate-major-external-major-5e6baae555d405d70454e3a566be06a7bcf6e792","maintainers":[{"name":"iayanpahwa","email":"ayan@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5384546c4d36bde936a8a0674afcb2dec93e5ce8","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.25-renovate-major-external-major-5e6baae555d405d70454e3a566be06a7bcf6e792.tgz","fileCount":205,"integrity":"sha512-gpsseXRncE2XFAkt86vf1RwBw+yZk3nV/a8f0XfJyVnugqcwf0AyutYcG1t3OGmsUJtBBilmmh1cpT26ciTg6w==","signatures":[{"sig":"MEQCIDw0ZmFaJInBqTw+WsVyLuP5mck/s9fgVwtX+QXh3rBXAiAkRxdQigHHzGGZ+lY9rfztm+a6Fk84nlZUyltktTPTAg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":751354},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"5e6baae555d405d70454e3a566be06a7bcf6e792","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-05T11:12:09.098Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.67","@balena/jellyfish-logger":"^3.0.78","@balena/jellyfish-metrics":"^1.0.341","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.2.4","deplint":"^1.1.3","ts-jest":"^27.0.5","typedoc":"^0.22.5","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.25-renovate-major-external-major-5e6baae555d405d70454e3a566be06a7bcf6e792_1633432536221_0.20500401877161","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.25":{"name":"@balena/jellyfish-core","version":"6.1.25","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.25","maintainers":[{"name":"iayanpahwa","email":"ayan@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"bdc6b5bf782d156554f1b4d89947656f663e0c7a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.25.tgz","fileCount":205,"integrity":"sha512-TEQ50URkqqRVDEF9rf9f3xUU56zqvmFU4I/oEpWo7n1GvJ+ZiRaENwBs3VJ7vnjgH7FokkhQ1WPyTMre/nuZeg==","signatures":[{"sig":"MEUCIAGRjMRf2BVF4dY9VXn2mFNJmxmyF6BH29BUunsD9ceQAiEAu7vG+kJqKl0e30IHiNV4dW2Pc/fycWlIfCfwCYNgIgo=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":751284},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d610565ece2e1368a88fbd38abab2f3b2bcd1715","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-05T11:21:09.412Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.80","@balena/jellyfish-metrics":"^1.0.342","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.4"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.5","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.25_1633432981195_0.8896389233545023","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.26-renovate-major-json-schema-org-1a6898e63c531c8fb5cc78a31f58f413303143e2":{"name":"@balena/jellyfish-core","version":"6.1.26-renovate-major-json-schema-org-1a6898e63c531c8fb5cc78a31f58f413303143e2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.26-renovate-major-json-schema-org-1a6898e63c531c8fb5cc78a31f58f413303143e2","maintainers":[{"name":"iayanpahwa","email":"ayan@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9d795d931fc4cf2234c2fe470c06b3719c4ec202","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.26-renovate-major-json-schema-org-1a6898e63c531c8fb5cc78a31f58f413303143e2.tgz","fileCount":205,"integrity":"sha512-enOPwGXhO4B3gDYD4o67QpUU2LGGG6kc1rp7q7/TFUfMovovpGYstbxToQ1t7kNDuBFoW7bYP+oZtV8EP0XDlQ==","signatures":[{"sig":"MEUCIGpJj4/dAWEgVVDFXUePtlunRWZ+NoFGM4RQL+RU7+mqAiEA6iPodNQFJI8//jiTVv+0RqYzJz07S802e8jgNKsNvIw=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":751452},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"1a6898e63c531c8fb5cc78a31f58f413303143e2","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-05T11:24:09.913Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.80","@balena/jellyfish-metrics":"^1.0.342","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.5","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.26-renovate-major-json-schema-org-1a6898e63c531c8fb5cc78a31f58f413303143e2_1633433232685_0.5956803086313467","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.1.26-renovate-major-external-major-b7739c3246b9b13dc76d55b0aa105fcbfb8b1224":{"name":"@balena/jellyfish-core","version":"6.1.26-renovate-major-external-major-b7739c3246b9b13dc76d55b0aa105fcbfb8b1224","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.1.26-renovate-major-external-major-b7739c3246b9b13dc76d55b0aa105fcbfb8b1224","maintainers":[{"name":"iayanpahwa","email":"ayan@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"618505ec43389db0194d9cd240b51ace27cd48af","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.1.26-renovate-major-external-major-b7739c3246b9b13dc76d55b0aa105fcbfb8b1224.tgz","fileCount":205,"integrity":"sha512-GSllqV1+ZEJgjqzPXZfTK0ywViQPu0VS1+w5+F+4X99kXoHAt9vE99uP52uIqxdMsoyY84aQ1uEv+414yAAqXQ==","signatures":[{"sig":"MEUCIQDuNNp+BmMniCVxNHLT7BBco39Gr+ONwnn3rRdc1ZJtiQIgG6ncbHYVNO7owofKUKz3C6oW38w+cJ5SeDL+1K6QJ+4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":751425},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b7739c3246b9b13dc76d55b0aa105fcbfb8b1224","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-05T11:24:29.143Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.80","@balena/jellyfish-metrics":"^1.0.342","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.2.4","deplint":"^1.1.3","ts-jest":"^27.0.5","typedoc":"^0.22.5","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.1.26-renovate-major-external-major-b7739c3246b9b13dc76d55b0aa105fcbfb8b1224_1633433248114_0.4981325200961153","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.2.0-h32-updated-at-ac535ae43f90b2994caa507f7883e9f97e5cbb9f":{"name":"@balena/jellyfish-core","version":"6.2.0-h32-updated-at-ac535ae43f90b2994caa507f7883e9f97e5cbb9f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.2.0-h32-updated-at-ac535ae43f90b2994caa507f7883e9f97e5cbb9f","maintainers":[{"name":"iayanpahwa","email":"ayan@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"195b1cd7439590377081846bd4a9f8cbb671e6a6","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.2.0-h32-updated-at-ac535ae43f90b2994caa507f7883e9f97e5cbb9f.tgz","fileCount":205,"integrity":"sha512-PawRY4kT+g1IAtcJRsGJrMu8PlEdTS+1xNnPTA8b3VJ0/2F+3haJTtuBFQ5PlFEphnOJ/UMTK/r+hS4E2KzGzg==","signatures":[{"sig":"MEYCIQCoZA4VRhRPnr+ZWGmO7bkQwuoitE29wc4g1kEhI5GK4QIhALKltqCEEPqwxIyukTvIR/Q1VCoIvbrXZMTYh3f8vG55","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":751829},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ac535ae43f90b2994caa507f7883e9f97e5cbb9f","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-05T12:35:29.525Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.80","@balena/jellyfish-metrics":"^1.0.342","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.5","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.2.0-h32-updated-at-ac535ae43f90b2994caa507f7883e9f97e5cbb9f_1633437529228_0.3481411277285462","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.2.0":{"name":"@balena/jellyfish-core","version":"6.2.0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.2.0","maintainers":[{"name":"iayanpahwa","email":"ayan@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"05d27d4ae84cc98be7c3a66df161c3bce52c57f0","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.2.0.tgz","fileCount":205,"integrity":"sha512-XKQaQCO3Fqrlik0ZDMeA8doFP4OSQJcVQqeoMhSsbxBFEA1LtuvehkpmK3oRhXGAkjeilf7oo+I2Lcoiyb7BIQ==","signatures":[{"sig":"MEQCIFBYHduJS1oCXtXMtboXYnQe8uIS+lRyJ5hSm4u4kVGZAiA71V2KfbAxHwrxs6SKNoEvq75CCd3KZQ2dXNRSKNhadg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":751773},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ea1ac68e101d3d242a1cbd14b960117da4a5913c","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-05T13:28:50.101Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.80","@balena/jellyfish-metrics":"^1.0.342","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.4"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.5","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.2.0_1633440649454_0.4473413494575782","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.2.1-renovate-major-json-schema-org-ace3e8bd2c4c43a606505b8a4981ae632a8a289a":{"name":"@balena/jellyfish-core","version":"6.2.1-renovate-major-json-schema-org-ace3e8bd2c4c43a606505b8a4981ae632a8a289a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.2.1-renovate-major-json-schema-org-ace3e8bd2c4c43a606505b8a4981ae632a8a289a","maintainers":[{"name":"iayanpahwa","email":"ayan@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8e9cb14236fa2b71d3ea949b0694e5e1c1b36db6","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.2.1-renovate-major-json-schema-org-ace3e8bd2c4c43a606505b8a4981ae632a8a289a.tgz","fileCount":205,"integrity":"sha512-6n1SiGi45PU0AhABIoykSoEwpg9vq6csSEROAJkBU2W127e9BvH0WLA7xKMgsbGwdbBcl22FX+CJC2a2VYXLjw==","signatures":[{"sig":"MEQCIGASxKh4CzBoSbQ6gfCG/3SyWZZI/7xJBFvajWYaafOzAiBYaueeNRw9SwImsJhZHRQj855E80xslVoCwJIZ0KNJwA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":751940},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ace3e8bd2c4c43a606505b8a4981ae632a8a289a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-05T13:37:43.198Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.80","@balena/jellyfish-metrics":"^1.0.342","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.5","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.2.1-renovate-major-json-schema-org-ace3e8bd2c4c43a606505b8a4981ae632a8a289a_1633441354126_0.2001496536929348","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.2.1-renovate-major-external-major-b21946c42e5c2d851229a41682a641cdc56d0aac":{"name":"@balena/jellyfish-core","version":"6.2.1-renovate-major-external-major-b21946c42e5c2d851229a41682a641cdc56d0aac","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.2.1-renovate-major-external-major-b21946c42e5c2d851229a41682a641cdc56d0aac","maintainers":[{"name":"iayanpahwa","email":"ayan@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"nazrhom","email":"nazrhom@gmail.com"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"984d510066aa9f0a54eee5c098b956ce4e4de308","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.2.1-renovate-major-external-major-b21946c42e5c2d851229a41682a641cdc56d0aac.tgz","fileCount":205,"integrity":"sha512-sYL2/d/l6OhnOh028MMmEADhOFPFYZ+O/J0zerqLLOLDS2Q0+udPnOr+OBcT9OnyHH1H98HTbVAInO83Mo4MmA==","signatures":[{"sig":"MEQCIAc6KyraAsUzLiqgzEIXmOdAnaG07hCDvvqHtq+pjJ79AiBnCL5r8YC0K+YNS3ElMeRIClExEUpeSW7tpXd4FVwcAw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":751913},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b21946c42e5c2d851229a41682a641cdc56d0aac","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-05T13:38:09.103Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.80","@balena/jellyfish-metrics":"^1.0.342","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.2.4","deplint":"^1.1.3","ts-jest":"^27.0.5","typedoc":"^0.22.5","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.2.1-renovate-major-external-major-b21946c42e5c2d851229a41682a641cdc56d0aac_1633441442014_0.10225210005825569","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.2.1-renovate-major-external-major-d17b261a9965b7712ded91ad0766cc15d512b476":{"name":"@balena/jellyfish-core","version":"6.2.1-renovate-major-external-major-d17b261a9965b7712ded91ad0766cc15d512b476","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.2.1-renovate-major-external-major-d17b261a9965b7712ded91ad0766cc15d512b476","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8d2aca584b37d3c64a9f1133030dbbdea290ac00","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.2.1-renovate-major-external-major-d17b261a9965b7712ded91ad0766cc15d512b476.tgz","fileCount":205,"integrity":"sha512-S4jQwrJe/xy4GDVSQ1ZhPZ03XHikun9idW6ztdKEkOJ0ntUmnosmf3XZBehSmoHx8YWohJh/ZT/fNpv2xJZddQ==","signatures":[{"sig":"MEYCIQCc/dX0t6C4YPfeXDsObinVVBMgjpuwl6wGzVQfdw5oLgIhALtdNK9N5GQ0VNtHmIs9jvpfyNY+g+sHwG3shXsY/FyG","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":751913},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d17b261a9965b7712ded91ad0766cc15d512b476","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-08T15:06:58.793Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.80","@balena/jellyfish-metrics":"^1.0.342","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.2.5","deplint":"^1.1.3","ts-jest":"^27.0.5","typedoc":"^0.22.5","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.2.1-renovate-major-external-major-d17b261a9965b7712ded91ad0766cc15d512b476_1633705845166_0.6390971715747702","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.2.1-renovate-external-non-major-323cf1210bb798a6c12360c3d57e2110e2b870f2":{"name":"@balena/jellyfish-core","version":"6.2.1-renovate-external-non-major-323cf1210bb798a6c12360c3d57e2110e2b870f2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.2.1-renovate-external-non-major-323cf1210bb798a6c12360c3d57e2110e2b870f2","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"96791a3d66fedde6c1b61c352a8963c7b1b0e1a1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.2.1-renovate-external-non-major-323cf1210bb798a6c12360c3d57e2110e2b870f2.tgz","fileCount":205,"integrity":"sha512-PNO/vjnA3aX/Y6e8za2tG05d7FY97xnZ7PSErzLbgNtgVxBTc1fLXGpQPSMWZakZzTDz2Ag57508p0hmvEIFyg==","signatures":[{"sig":"MEUCICPU4GkS/5aPRd31xVARjvBEKtvg7spTndPSUFBK3N2dAiEA6YlQsdML/096H/xGhnC2ET+/28wXP3aYqxQtS0KmwJA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":751931},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"323cf1210bb798a6c12360c3d57e2110e2b870f2","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-09T01:29:39.183Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.80","@balena/jellyfish-metrics":"^1.0.342","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.5","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.2.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.2.1-renovate-external-non-major-323cf1210bb798a6c12360c3d57e2110e2b870f2_1633743128466_0.740860807522641","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.2.1-renovate-internal-patch-4164f5cda23e668df039ec0101f3542210168773":{"name":"@balena/jellyfish-core","version":"6.2.1-renovate-internal-patch-4164f5cda23e668df039ec0101f3542210168773","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.2.1-renovate-internal-patch-4164f5cda23e668df039ec0101f3542210168773","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2bd70a9d94ebda7f73bb96af388ca180253ac600","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.2.1-renovate-internal-patch-4164f5cda23e668df039ec0101f3542210168773.tgz","fileCount":205,"integrity":"sha512-Xid6gCDpcPKlKDqHcw++w0xSrubdFziUYCtapUJsLOVkT89yvoEPgZaKjmd90bY2zSzfPU0XRB6k1TN6zYdlhQ==","signatures":[{"sig":"MEUCIQCqGFQjdLyQ0nWw4GrUNrwliZFTHJGiiQ9uvZLkvK12bAIgQWP+xASfyJCQZvYwwF8k65Y4yXcoIwQcf3/amr+drms=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":751940},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4164f5cda23e668df039ec0101f3542210168773","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-09T01:33:29.448Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.81","@balena/jellyfish-metrics":"^1.0.342","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.5","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.2.1-renovate-internal-patch-4164f5cda23e668df039ec0101f3542210168773_1633743419524_0.591351979304628","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.2.1":{"name":"@balena/jellyfish-core","version":"6.2.1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.2.1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b2b9bc0b1691bab3720f8b734c43e56b71c13d69","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.2.1.tgz","fileCount":205,"integrity":"sha512-uCt5FIKzYTMds4zu53f8ZA+OBWXhxsGKkyU1zyiKna+4WBG8fio76jJdfiZEbbpIzSZIm1mNpjxzd+bxQoX+Wg==","signatures":[{"sig":"MEUCIQD4AM/d5qe5fL13mnrMQsoVOXiusevcB3sfcItdcySY/QIgRCyDL6Rz5Ip6A+3eMgIOlPrQyQ8G0u3TsLKRp+rw1Ic=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":751862},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6ccd06d397a2a3404e413be72492b6f9d48a98e6","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-09T01:37:11.949Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.80","@balena/jellyfish-metrics":"^1.0.342","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.4"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.5","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.2.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.2.1_1633743541127_0.8493630476036584","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.2.2-renovate-internal-patch-7f22ddb90cd5cf85c52a1384dcdc5571c5bc9f17":{"name":"@balena/jellyfish-core","version":"6.2.2-renovate-internal-patch-7f22ddb90cd5cf85c52a1384dcdc5571c5bc9f17","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.2.2-renovate-internal-patch-7f22ddb90cd5cf85c52a1384dcdc5571c5bc9f17","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c2b28a4c41697367bcc60c9ee7fbe1c5e49bbb03","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.2.2-renovate-internal-patch-7f22ddb90cd5cf85c52a1384dcdc5571c5bc9f17.tgz","fileCount":205,"integrity":"sha512-/c0sJpqgW4V1ECT7mujZQdNRJB2Nhss0kNCAFPWN8DETG2SCyfI+0ul8j2/ZMu/R+a/1ba5MufFWqoYm7nVQcA==","signatures":[{"sig":"MEQCIEexLrXuVWGe3XMyBqH4J3lBfVPW95+0zsnvRsFB96YGAiAqhyQxtzn2xIf/gXAZt1EyAjM4pOv+2m4TfJMzzTS7wA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752029},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7f22ddb90cd5cf85c52a1384dcdc5571c5bc9f17","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-09T01:38:38.157Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.81","@balena/jellyfish-metrics":"^1.0.342","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.5","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.2.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.2.2-renovate-internal-patch-7f22ddb90cd5cf85c52a1384dcdc5571c5bc9f17_1633743777949_0.549425915781314","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.2.2-renovate-major-json-schema-org-63276220494ff19b66c9c721c6393567c63f0544":{"name":"@balena/jellyfish-core","version":"6.2.2-renovate-major-json-schema-org-63276220494ff19b66c9c721c6393567c63f0544","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.2.2-renovate-major-json-schema-org-63276220494ff19b66c9c721c6393567c63f0544","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2921e606acf19af824a8719bc09010716d8aa657","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.2.2-renovate-major-json-schema-org-63276220494ff19b66c9c721c6393567c63f0544.tgz","fileCount":205,"integrity":"sha512-qRehEakgffyrZFPBscDOTvo7kzl2XLhelnzSpdmSGENsUd+9P9lBh+mG5Ka4caVxyzNjKGDeg7/wwf03os/zXg==","signatures":[{"sig":"MEUCIA1fkovScmTO0HCgUQSkIl9pq3/MEzMJTJtymkdJhlyOAiEAq6FB6Sc1OdxXqx/qDTPC6xy0tFs/2iyf9cbyP5JZ3Nc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752029},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"63276220494ff19b66c9c721c6393567c63f0544","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-09T01:43:39.270Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.80","@balena/jellyfish-metrics":"^1.0.342","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.5","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.2.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.2.2-renovate-major-json-schema-org-63276220494ff19b66c9c721c6393567c63f0544_1633743990259_0.47004268405804006","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.2.2-renovate-major-external-major-cf4f86366c6389de86f8f5300735af607e04ffd1":{"name":"@balena/jellyfish-core","version":"6.2.2-renovate-major-external-major-cf4f86366c6389de86f8f5300735af607e04ffd1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.2.2-renovate-major-external-major-cf4f86366c6389de86f8f5300735af607e04ffd1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a953d2ed99b2a9b9a49be4eb76acb036bd924adf","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.2.2-renovate-major-external-major-cf4f86366c6389de86f8f5300735af607e04ffd1.tgz","fileCount":205,"integrity":"sha512-Icyyc2ws7R1QJHlCzARcqQxKke1nzHnI+JgnT/4tWr9FwQxKMI3Y/TctrkstCnV+OOh2UBPfKcTf8e6AB6prxQ==","signatures":[{"sig":"MEUCIEGQSJolrojMjykIwMj9KN0yT9jJfmE4Kee3HEpogQ2lAiEA1ZJdbxqWTUOO2yS6axkciB5PA2+JbrFgTbwfYtcWsCc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752002},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"cf4f86366c6389de86f8f5300735af607e04ffd1","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-09T01:43:39.396Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.80","@balena/jellyfish-metrics":"^1.0.342","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.2.5","deplint":"^1.1.3","ts-jest":"^27.0.5","typedoc":"^0.22.5","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.2.2-renovate-major-external-major-cf4f86366c6389de86f8f5300735af607e04ffd1_1633744038872_0.6596415152300645","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.2.2":{"name":"@balena/jellyfish-core","version":"6.2.2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.2.2","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1d568c58baffd740418e58503e93f242ca8c2ddd","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.2.2.tgz","fileCount":205,"integrity":"sha512-z5XfCS7xjEwF4Ntk8zpHjINJj5k50gWclm67Z+3QnJlL6lpksfcFbEt7NFhHOyVpfZqe9eBs+mtq1DNe9qP04w==","signatures":[{"sig":"MEQCIAqyvJ1QoyK7ob0KZTQV1t09xTO8NssPfe6YHsGubnzHAiB5JcjjsWRtsfwlb2YlSrsyTn15wyqZOG+ahKkZmVsCiA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":751964},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"37badd87c73e320a35078a6f112c504ff045fc26","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-09T01:51:20.558Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.81","@balena/jellyfish-metrics":"^1.0.342","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.4"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.5","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.2.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.2.2_1633744420567_0.835678998961493","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.2.3-renovate-major-json-schema-org-f645b9dd3dd723a34d2d669ca626dade83aa218a":{"name":"@balena/jellyfish-core","version":"6.2.3-renovate-major-json-schema-org-f645b9dd3dd723a34d2d669ca626dade83aa218a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.2.3-renovate-major-json-schema-org-f645b9dd3dd723a34d2d669ca626dade83aa218a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"988013665583a62ba6ff1c97616891d49538d52b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.2.3-renovate-major-json-schema-org-f645b9dd3dd723a34d2d669ca626dade83aa218a.tgz","fileCount":205,"integrity":"sha512-Cp6mGKoy0SHUuNXDOUiGNu9/IjpoIoOEInHc8DFdfJj7yLfpb5V0lTVu9y3XbCokt5xx9aL+tsuU4QghFnyJRA==","signatures":[{"sig":"MEYCIQCvqDMXIGFrUFvOgvKi3t67H4RrR3/aODnNJ84QoD834gIhANplgyUvlojGvUYsrV8aIs3qDbj9789tbZ/MxrLZ0id1","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752131},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f645b9dd3dd723a34d2d669ca626dade83aa218a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-09T01:53:50.101Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.81","@balena/jellyfish-metrics":"^1.0.342","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.5","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.2.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.2.3-renovate-major-json-schema-org-f645b9dd3dd723a34d2d669ca626dade83aa218a_1633744645466_0.7728411410652729","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.2.3-renovate-major-external-major-38133380abbadc281f5163b53eb523388dd80e43":{"name":"@balena/jellyfish-core","version":"6.2.3-renovate-major-external-major-38133380abbadc281f5163b53eb523388dd80e43","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.2.3-renovate-major-external-major-38133380abbadc281f5163b53eb523388dd80e43","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5ceab6afe1cd5943aad884cfd59e4f608cd3dd2d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.2.3-renovate-major-external-major-38133380abbadc281f5163b53eb523388dd80e43.tgz","fileCount":205,"integrity":"sha512-0ZMLWQnlsNfe/v23kvjg295QXxMAX9ywZmVQc8CH7T5xSWMywpcs8s8GutXvtFe2v7y53pOnsfWw1d8chzhN4g==","signatures":[{"sig":"MEUCIQD56zPl30a7Q8Av+PmXm14Hd1odG7OI1KooSDHQP1JM2QIgCEXgC4bq6cmlNsqarSNKecKQPL0Z4Wu7tgwiuSE7kIQ=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752104},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"38133380abbadc281f5163b53eb523388dd80e43","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-09T01:54:29.796Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.81","@balena/jellyfish-metrics":"^1.0.342","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.2.5","deplint":"^1.1.3","ts-jest":"^27.0.5","typedoc":"^0.22.5","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.2.3-renovate-major-external-major-38133380abbadc281f5163b53eb523388dd80e43_1633744681358_0.4840075464017619","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.2.3-renovate-internal-patch-6f8a74a5e5a9a81f49f3069131bb6dd249eb56bf":{"name":"@balena/jellyfish-core","version":"6.2.3-renovate-internal-patch-6f8a74a5e5a9a81f49f3069131bb6dd249eb56bf","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.2.3-renovate-internal-patch-6f8a74a5e5a9a81f49f3069131bb6dd249eb56bf","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6bf68193ade8d877be5f5c1c6618320d5272862b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.2.3-renovate-internal-patch-6f8a74a5e5a9a81f49f3069131bb6dd249eb56bf.tgz","fileCount":205,"integrity":"sha512-hq5G4QOhM1+zdnVqyHVI5pADf7h5/oEBeftSX016utcdViliCeI58qJXUEIlnZ38cwJnVN/E5I25gJ4TX17KmQ==","signatures":[{"sig":"MEUCIBiE05igLzvvOetndZQm2Vqb2KHyKn1vVFFPUd7iuXWGAiEAwQM9ivVU/uZ1ngZL815HJcVWtuqpnvk3r5Jt5JyObTE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752131},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6f8a74a5e5a9a81f49f3069131bb6dd249eb56bf","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-09T04:24:39.178Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.82","@balena/jellyfish-metrics":"^1.0.342","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.5","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.2.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.2.3-renovate-internal-patch-6f8a74a5e5a9a81f49f3069131bb6dd249eb56bf_1633753706322_0.5551275942569989","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.2.3":{"name":"@balena/jellyfish-core","version":"6.2.3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.2.3","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"7f757269dd026253c31bc7bca6e29cf1456596ac","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.2.3.tgz","fileCount":205,"integrity":"sha512-sDKlgGlQn9+T2k00V+HuzeAjTnpjZi8Quut1SjNt8W5a5dc/OaV++cNNV5AftqEXLykDMxMvJl6ePoVRQdjZFQ==","signatures":[{"sig":"MEYCIQDwsgAtQED0bDS372mCMUQO8FGjjOu19A0qsaklnZGOnwIhAMI8MkEljl2dWR/XZ/kO6qXvHrlIQU+vXXx1CJFse7hZ","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752066},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"5f108d2ca36ce325f55275488523a328e17d167c","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-09T04:34:00.698Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.82","@balena/jellyfish-metrics":"^1.0.342","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.4"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.5","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.2.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.2.3_1633754203048_0.3683269472906239","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.2.4-renovate-external-non-major-84f707f1f658b1485dc4467586b53dcd6b878459":{"name":"@balena/jellyfish-core","version":"6.2.4-renovate-external-non-major-84f707f1f658b1485dc4467586b53dcd6b878459","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.2.4-renovate-external-non-major-84f707f1f658b1485dc4467586b53dcd6b878459","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"92584206a42f235b3eebef78d5650ff002d3edc8","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.2.4-renovate-external-non-major-84f707f1f658b1485dc4467586b53dcd6b878459.tgz","fileCount":205,"integrity":"sha512-Agqr9ilaXU+qIPDoaxX60dCQWU1UcQqdw/1iE/i9e+UDuHR1kxiae+3jNdR+kIcrenuDl2vsdYvxhuwV5ZPzug==","signatures":[{"sig":"MEUCIQDFvArpMNndZD9K3mlLeHujTYxTcU+07SdFj3f8FWAy1gIgB1UE0PVKNnPO1tmFHij/aFOisyhunXxXL22Uzw4GIac=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752224},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"84f707f1f658b1485dc4467586b53dcd6b878459","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-09T04:36:59.224Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.82","@balena/jellyfish-metrics":"^1.0.342","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.5","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.2.1","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.2.4-renovate-external-non-major-84f707f1f658b1485dc4467586b53dcd6b878459_1633754478745_0.10110816433811198","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.2.4-renovate-major-json-schema-org-dca8290f8c15b97d801bc2f7e4982d79f84faaeb":{"name":"@balena/jellyfish-core","version":"6.2.4-renovate-major-json-schema-org-dca8290f8c15b97d801bc2f7e4982d79f84faaeb","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.2.4-renovate-major-json-schema-org-dca8290f8c15b97d801bc2f7e4982d79f84faaeb","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"330ab4aaf0a5e6899ce891b0b83b8f866b8925a3","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.2.4-renovate-major-json-schema-org-dca8290f8c15b97d801bc2f7e4982d79f84faaeb.tgz","fileCount":205,"integrity":"sha512-ccYNhW9B93HkMrNaZyD/kyrcavpD+BDkAKxPaZlCqB+3k+hXrR7Ihp1hYNQ5YX+SomRhr3liEgB+AmZWWxkelQ==","signatures":[{"sig":"MEQCIF2Alhd1M7+j/U7GBE7xoQfikeaHzsvlFPVPGrvpL/wrAiB9edJ2BUISPXq9bOI7xHAq8djYm7FloPAR26+f455IOA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752233},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"dca8290f8c15b97d801bc2f7e4982d79f84faaeb","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-09T04:37:33.802Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.82","@balena/jellyfish-metrics":"^1.0.342","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.5","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.2.0","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.2.4-renovate-major-json-schema-org-dca8290f8c15b97d801bc2f7e4982d79f84faaeb_1633754553198_0.16713888513228525","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.2.4":{"name":"@balena/jellyfish-core","version":"6.2.4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.2.4","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d5a922ac51736f64696f5e9df1aa831fccf08185","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.2.4.tgz","fileCount":205,"integrity":"sha512-uCdFF9zjxf/8M5G3OfeHziqPHFmkxHc7ZJzHr2dMMTVbF6cpfJ7qf5kgqvq2mzfU2GwiSq3CAHI1NhHhYiY5Pw==","signatures":[{"sig":"MEUCIQDkgS1MSeL1rJvB/ywicFuCeATqc1hGJOSNn4DSSZ+eZgIgfqxXC8NE/eXB0j/n+dP3gp4F1VP9zXryeizfqVOm0hw=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752155},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a263c3cc0d1a90e951a1cf8df55be33a481387fb","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-09T07:19:56.175Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.82","@balena/jellyfish-metrics":"^1.0.342","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.4"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.5","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.2.1","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.2.4_1633764128762_0.19759986621329584","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.2.5-renovate-major-json-schema-org-4c2aea8ae48cd36bd2e77e7fa3c2cd1c657865ff":{"name":"@balena/jellyfish-core","version":"6.2.5-renovate-major-json-schema-org-4c2aea8ae48cd36bd2e77e7fa3c2cd1c657865ff","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.2.5-renovate-major-json-schema-org-4c2aea8ae48cd36bd2e77e7fa3c2cd1c657865ff","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d32e89cb0f27c6c9e83f0ab313c2df2f85a4176c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.2.5-renovate-major-json-schema-org-4c2aea8ae48cd36bd2e77e7fa3c2cd1c657865ff.tgz","fileCount":205,"integrity":"sha512-VHsgRaLW0H3Wj0oL8tOauysztqs8bZkwlFegDN/txx/5W4sG20fFVgEo0dYeidAlKgVc9B4Lmq7OMi4y1SeAwQ==","signatures":[{"sig":"MEQCIGP90d0zfoOy3oFqUv+Tb0TjF27mI54KSrUD6KKUBRvjAiADDeadxFe8VYVKnHX/vu6QULghG2zqrbwJP86Ph31faA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752322},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4c2aea8ae48cd36bd2e77e7fa3c2cd1c657865ff","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-09T07:24:29.993Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.82","@balena/jellyfish-metrics":"^1.0.342","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.5","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.2.1","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.2.5-renovate-major-json-schema-org-4c2aea8ae48cd36bd2e77e7fa3c2cd1c657865ff_1633764470480_0.1801832855152643","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.2.5-renovate-major-external-major-54d191510bb05c8ed463636b14d2d79b3059dea9":{"name":"@balena/jellyfish-core","version":"6.2.5-renovate-major-external-major-54d191510bb05c8ed463636b14d2d79b3059dea9","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.2.5-renovate-major-external-major-54d191510bb05c8ed463636b14d2d79b3059dea9","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"67a72b394ae05d97c3c6e88611ea7df0e8810f5f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.2.5-renovate-major-external-major-54d191510bb05c8ed463636b14d2d79b3059dea9.tgz","fileCount":205,"integrity":"sha512-ilXuGOixQd81OwEdEdr4L+I4fP9/WpcgW+S3WCXt/2y27tKmgMYsA9YnMROdCYgONHb8SC4MB6WgBLb3DdMK+g==","signatures":[{"sig":"MEYCIQDSpcBVAp2lHJw0E2reBnzdUyV6OHjpaFkE7ldl7riKVgIhAMeVEZc5gQm7Ou4PaFYlMvVbbGc42hi+xGz2o2Xa0Qzv","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752295},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"54d191510bb05c8ed463636b14d2d79b3059dea9","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-09T07:24:40.560Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.82","@balena/jellyfish-metrics":"^1.0.342","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.2.5","deplint":"^1.1.3","ts-jest":"^27.0.5","typedoc":"^0.22.5","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.1","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.2.5-renovate-major-external-major-54d191510bb05c8ed463636b14d2d79b3059dea9_1633764496983_0.285197781106604","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.2.5-renovate-external-non-major-be946ab66e073538bf41442bb0b004a97693be0b":{"name":"@balena/jellyfish-core","version":"6.2.5-renovate-external-non-major-be946ab66e073538bf41442bb0b004a97693be0b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.2.5-renovate-external-non-major-be946ab66e073538bf41442bb0b004a97693be0b","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f3248ddc92803766ff48556547e9f76628c867ac","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.2.5-renovate-external-non-major-be946ab66e073538bf41442bb0b004a97693be0b.tgz","fileCount":205,"integrity":"sha512-n4qspQ+5vc1XLhWWPPO07yz+2g5vb6hbtqLSh95HYXAvmzCB95VOe/UNCJ8SrmkqIJys/F3tDAQoDpOJEG8OpQ==","signatures":[{"sig":"MEUCIQCeAGRjfd7kHdwCGd9312xjudY1j+TRrO1MGnw+7I4oVAIgC90jz87i/wLMPJVC9rZlfRUUFw6GI/SJSbsim9x4nzA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752313},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"be946ab66e073538bf41442bb0b004a97693be0b","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-09T19:26:15.838Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.82","@balena/jellyfish-metrics":"^1.0.342","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.5","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.2.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.2.5-renovate-external-non-major-be946ab66e073538bf41442bb0b004a97693be0b_1633807823067_0.8243249005320172","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.2.5":{"name":"@balena/jellyfish-core","version":"6.2.5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.2.5","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"90a092120f4b7ce97fd88332abbbb7a3ea089e3a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.2.5.tgz","fileCount":205,"integrity":"sha512-PalCBF5r+VcxLPcJ7WkEk8q9T0ODfAmSMnTc0NdsiY43XtNNVFtwmTsI79hurrV8n+oOYKcsLpmEGyybAtGpdg==","signatures":[{"sig":"MEYCIQCXfR2q9uwqQdaKXur1CmzwFzeJGyqJIklaYD4v3TXNpwIhAIBkYn6iU5iZ8/sUghVy91mXhb2Ev7AyAjjF1TK/tjyL","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752244},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6ef459b77c106c766470416b9eddb3425f1f7977","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-09T19:35:19.422Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.82","@balena/jellyfish-metrics":"^1.0.342","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.4"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.5","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.2.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.2.5_1633808232644_0.30990847431587887","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.2.6-renovate-major-external-major-d3bf343fdbf620bb774e7deb7f004dc6344c29e7":{"name":"@balena/jellyfish-core","version":"6.2.6-renovate-major-external-major-d3bf343fdbf620bb774e7deb7f004dc6344c29e7","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.2.6-renovate-major-external-major-d3bf343fdbf620bb774e7deb7f004dc6344c29e7","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5bd684e476bca066925c678c5a15ef59fcebaf94","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.2.6-renovate-major-external-major-d3bf343fdbf620bb774e7deb7f004dc6344c29e7.tgz","fileCount":205,"integrity":"sha512-eIljOXQ2rLwnOoJZXqbidduebUMcPiuEuCYNh62FY/8CxeHnEYUx4ASLudjNVzvNfO8eyYhHTK5EyiIDnDYCqg==","signatures":[{"sig":"MEUCIQDh5dpeWjS5Xo4K18LNOm+SKNQU522YensAndIoZG5BBQIgRMWuIGvbjKrvRkwsi9mSBdpFIzept+rl+NA2QY/noi0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752384},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d3bf343fdbf620bb774e7deb7f004dc6344c29e7","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-09T19:38:29.852Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.82","@balena/jellyfish-metrics":"^1.0.342","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.2.5","deplint":"^1.1.3","ts-jest":"^27.0.5","typedoc":"^0.22.5","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.2.6-renovate-major-external-major-d3bf343fdbf620bb774e7deb7f004dc6344c29e7_1633808555383_0.7675283630394869","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.2.6-renovate-major-json-schema-org-33bf8cdb085e1e53422cd2c7ea4866398d504c76":{"name":"@balena/jellyfish-core","version":"6.2.6-renovate-major-json-schema-org-33bf8cdb085e1e53422cd2c7ea4866398d504c76","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.2.6-renovate-major-json-schema-org-33bf8cdb085e1e53422cd2c7ea4866398d504c76","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1319b52825c8cd251ef05c6b9d214202bc5783de","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.2.6-renovate-major-json-schema-org-33bf8cdb085e1e53422cd2c7ea4866398d504c76.tgz","fileCount":205,"integrity":"sha512-2Iqb+P7K9dtYA5vVfd5JT3Xz5v9EQFD/D8/7JOel6oj4cYAa3HVi+vfO/0QqDH5pVD8a2UE1oMc6UGdIDVEHJQ==","signatures":[{"sig":"MEYCIQDIiEGMzIHEd4VPsLfdCm45o8fjSOdpFbKiKBnC3rKzuwIhAKDlRIR3gfDfPbbifF/0CewFAd31LnAzY02Z5I1xYSlN","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752411},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"33bf8cdb085e1e53422cd2c7ea4866398d504c76","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-09T19:41:32.819Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.82","@balena/jellyfish-metrics":"^1.0.342","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.5","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.2.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.2.6-renovate-major-json-schema-org-33bf8cdb085e1e53422cd2c7ea4866398d504c76_1633808674867_0.7794858218834442","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.2.6-renovate-internal-patch-cbab302fde0aec49f02f199561ac85acaaa383fc":{"name":"@balena/jellyfish-core","version":"6.2.6-renovate-internal-patch-cbab302fde0aec49f02f199561ac85acaaa383fc","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.2.6-renovate-internal-patch-cbab302fde0aec49f02f199561ac85acaaa383fc","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4ab75ef0225b6f065dfe159ca0d562174088be73","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.2.6-renovate-internal-patch-cbab302fde0aec49f02f199561ac85acaaa383fc.tgz","fileCount":205,"integrity":"sha512-aVyIQl8qFb6rSbx+lnfYuosMz5IuxmW+/CWJzY2mwv6ZvZL8agDQrZ9z/qca7Gwx4spo+TWDF+n91sZ795YENw==","signatures":[{"sig":"MEQCIEMjrYVxSC9ahKTLCLc+42k/Ynogjiv9F8XqKtYay4gFAiB0trSkR5oLvebUB8AU/v7t8uyWaLwYRBjHZ8gQsE8JlA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752411},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"cbab302fde0aec49f02f199561ac85acaaa383fc","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-09T21:50:09.076Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.83","@balena/jellyfish-metrics":"^1.0.342","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.5","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.2.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.2.6-renovate-internal-patch-cbab302fde0aec49f02f199561ac85acaaa383fc_1633816382483_0.11590735131249219","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.2.6":{"name":"@balena/jellyfish-core","version":"6.2.6","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.2.6","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0097dc77371cc76c49497b54178d6f3507389aa3","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.2.6.tgz","fileCount":205,"integrity":"sha512-GygZsBUWEppk4mCO3lRHEOScTntUQrqF2LDnmt1T+1nT0RTPk5ht7H5Ztjq3+PNBHF8AciXGDj1r4nvbEOmh7g==","signatures":[{"sig":"MEUCIQDGhRb6t1RaQ7+HM/SkvaFChNgbZga/V7rmaQgvZa5NqAIgPVo+3ocuTE0WFH/MQ2LQ16hqTNvkezE4BIRDnMbhKrQ=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752346},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"1b206ea9603ceedab962972b59258b17242b158e","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-09T21:57:39.526Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.83","@balena/jellyfish-metrics":"^1.0.342","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.4"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.5","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.2.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.2.6_1633816776926_0.07316722949390186","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.2.7-renovate-major-external-major-3b22e9a157273e74de82d696cef02f4e476aeeb3":{"name":"@balena/jellyfish-core","version":"6.2.7-renovate-major-external-major-3b22e9a157273e74de82d696cef02f4e476aeeb3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.2.7-renovate-major-external-major-3b22e9a157273e74de82d696cef02f4e476aeeb3","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d8633d5dad18dabff1f96e311a1ecab11ac51435","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.2.7-renovate-major-external-major-3b22e9a157273e74de82d696cef02f4e476aeeb3.tgz","fileCount":205,"integrity":"sha512-3y1aaE4B5ItV7KrIUsa8xb/kkvpsInqeYszdnoyt8DVTZRW8joAELCPPiqeRSHnBqj+vUIJeQrTu0bb1QIZ6yg==","signatures":[{"sig":"MEUCIC0sXR3GR0UcH1WmFB+uNjEc8fY6ZVGz68cnhPrLY8wcAiEAp85J1jJAhtqGrVAb6X1e+lXJWE8keBW3F63JXXjPBfk=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752486},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"3b22e9a157273e74de82d696cef02f4e476aeeb3","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-09T22:00:24.050Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.83","@balena/jellyfish-metrics":"^1.0.342","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.2.5","deplint":"^1.1.3","ts-jest":"^27.0.5","typedoc":"^0.22.5","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.2.7-renovate-major-external-major-3b22e9a157273e74de82d696cef02f4e476aeeb3_1633817046768_0.924413939212078","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.2.7-renovate-internal-patch-3b4f79ce6cb499d7d39cd3f19647ac6e28feea1b":{"name":"@balena/jellyfish-core","version":"6.2.7-renovate-internal-patch-3b4f79ce6cb499d7d39cd3f19647ac6e28feea1b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.2.7-renovate-internal-patch-3b4f79ce6cb499d7d39cd3f19647ac6e28feea1b","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1a84eec0fc380f7449014a020b4702eb659d4c70","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.2.7-renovate-internal-patch-3b4f79ce6cb499d7d39cd3f19647ac6e28feea1b.tgz","fileCount":205,"integrity":"sha512-jJucFP1EO1YnBVpiEx4H1cx1aIO2NvLC/X9JNTUdNlNC8Rx347BMWtJQ0BJZctV+ZEFxc8rKDtE/XLgAGVVgtw==","signatures":[{"sig":"MEQCIDZGtOcDFMsIiD5fO+Y+LoG9Hy4VEbPHhNr5LxJUdM4IAiAAmnTK2abF4sbbI44ZI7FMdLqwwlbvd2+JbBRmFgNIRA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752513},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"3b4f79ce6cb499d7d39cd3f19647ac6e28feea1b","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-10T19:30:40.363Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.84","@balena/jellyfish-metrics":"^1.0.342","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.5","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.2.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.2.7-renovate-internal-patch-3b4f79ce6cb499d7d39cd3f19647ac6e28feea1b_1633894408928_0.27194465813323476","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.2.7-renovate-external-non-major-b29cd5950e0ae54db75357870c001b6a6bdd35cc":{"name":"@balena/jellyfish-core","version":"6.2.7-renovate-external-non-major-b29cd5950e0ae54db75357870c001b6a6bdd35cc","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.2.7-renovate-external-non-major-b29cd5950e0ae54db75357870c001b6a6bdd35cc","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"26053423daef45e2d12619b608f35ed22398c94e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.2.7-renovate-external-non-major-b29cd5950e0ae54db75357870c001b6a6bdd35cc.tgz","fileCount":205,"integrity":"sha512-HclM+kVTe0geTXs508sFHABylYmealgnhPZ7oBgfJM4n/0zqC5hKz4yRwZ3uO4gZCli8OJlNeYat0v+BBC8s9A==","signatures":[{"sig":"MEUCIQD6X+RkBHK0UinD/pdlWpIL64F9Sfme/m+r2JZ/uNCNMQIge7HaCb65qdqfA0puS147sfu02kFxua9qzf8heD5nyc4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752504},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b29cd5950e0ae54db75357870c001b6a6bdd35cc","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-10T19:30:39.560Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.83","@balena/jellyfish-metrics":"^1.0.342","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.5","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.2.3","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.2.7-renovate-external-non-major-b29cd5950e0ae54db75357870c001b6a6bdd35cc_1633894452598_0.1761371798702045","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.2.7":{"name":"@balena/jellyfish-core","version":"6.2.7","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.2.7","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"46d1d111a544856d79c67340a5a8e952addde1df","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.2.7.tgz","fileCount":205,"integrity":"sha512-6Isc6mi0Pq+g7t8whOdiYXHGIJSooqpPsFeH4aKiHZ9jG1Kfmj1BXO/DhDT3NVuPwpHMHe2ckG2xY/8dg1tk2g==","signatures":[{"sig":"MEUCIQDW5ixFymNU7YOQ8JR/d4n7cjQFPaaaB2GkgO/gWC7TUAIgTufj1VO2CGuPSkhDYfy8APR7fkPIUAH5gt9tfbPwENY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752448},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"98c3f0e23ed4c247efb7647ea38a35c4efd9c4d5","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-10T19:38:13.860Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.84","@balena/jellyfish-metrics":"^1.0.342","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.4"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.5","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.2.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.2.7_1633894809420_0.35791500180337543","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.2.8-renovate-major-json-schema-org-ab9a12f265410c18b5a2cabe7cb2c032f3c48f48":{"name":"@balena/jellyfish-core","version":"6.2.8-renovate-major-json-schema-org-ab9a12f265410c18b5a2cabe7cb2c032f3c48f48","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.2.8-renovate-major-json-schema-org-ab9a12f265410c18b5a2cabe7cb2c032f3c48f48","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"19d3775d94b209b4d6ec49b17d7675bd4b8e9535","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.2.8-renovate-major-json-schema-org-ab9a12f265410c18b5a2cabe7cb2c032f3c48f48.tgz","fileCount":205,"integrity":"sha512-Oh9lmiuniTP2cqVZxnWFt7tastKCrkRi1bEAHj+sQlr8sAAhj7DSEPLzUcI+wwePuG4V9T6uhcaTLm6ti01LLA==","signatures":[{"sig":"MEUCIQD3GSM5aJTA4WB0XHPW2DKDlZkXfl20QDu1m2M0gfCyWAIgXjgya+fqcOJMaICm2ythVkgBKRjMSD3oLyau8umknj8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752615},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ab9a12f265410c18b5a2cabe7cb2c032f3c48f48","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-10T19:40:29.124Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.84","@balena/jellyfish-metrics":"^1.0.342","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.5","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.2.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.2.8-renovate-major-json-schema-org-ab9a12f265410c18b5a2cabe7cb2c032f3c48f48_1633895077791_0.06138043075873445","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.2.8-renovate-major-external-major-7e2a469a559f5f318ae31cc994c0099b806f934a":{"name":"@balena/jellyfish-core","version":"6.2.8-renovate-major-external-major-7e2a469a559f5f318ae31cc994c0099b806f934a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.2.8-renovate-major-external-major-7e2a469a559f5f318ae31cc994c0099b806f934a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"187ce7cf2f4e805392dfe0ad5f64b6b14140cade","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.2.8-renovate-major-external-major-7e2a469a559f5f318ae31cc994c0099b806f934a.tgz","fileCount":205,"integrity":"sha512-2Vu5Wats4JzJqp2jVcvMA165tKiyIVBK0sPGHYnoQra7n2GlV5Lpy/crIhGvGlhaJ5Vc4Khn/MpxYq4nwFbv0g==","signatures":[{"sig":"MEQCIADCgvW+k8ZddSfnBC9lLzLn6QiGM/uRRI1BhwUJXMLgAiAxPpob9zK4yR6RLypSOT6gvwmR8obraS3YfBcrtzS7Mg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752588},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7e2a469a559f5f318ae31cc994c0099b806f934a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-10T19:41:39.196Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.84","@balena/jellyfish-metrics":"^1.0.342","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.2.5","deplint":"^1.1.3","ts-jest":"^27.0.5","typedoc":"^0.22.5","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.2.8-renovate-major-external-major-7e2a469a559f5f318ae31cc994c0099b806f934a_1633895143205_0.8887673303418573","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.2.8-renovate-external-non-major-4dce561e065ada8ca9c12e595a721189cb6e1b8e":{"name":"@balena/jellyfish-core","version":"6.2.8-renovate-external-non-major-4dce561e065ada8ca9c12e595a721189cb6e1b8e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.2.8-renovate-external-non-major-4dce561e065ada8ca9c12e595a721189cb6e1b8e","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"aec19a15a1f280d7da69a521e6f623c655d99570","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.2.8-renovate-external-non-major-4dce561e065ada8ca9c12e595a721189cb6e1b8e.tgz","fileCount":205,"integrity":"sha512-+cGc7MpLGo3WMKogZXGNeo7NB6FAQhELAa2C1ns9aC2okGLFGM0h2q1PXhUfKcT79sqoMRBWPJOC0N7QcNkOGA==","signatures":[{"sig":"MEYCIQCXl3OgZerPmzFFwnxwoBcuIO4Q5YMXt5O9r+9MND0kHQIhAMYtavUSse/LXCSWukoWMdqnoDxAX94Z7dNHlcJRTPqE","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752606},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4dce561e065ada8ca9c12e595a721189cb6e1b8e","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-10T19:43:29.453Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.84","@balena/jellyfish-metrics":"^1.0.342","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.5","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.2.3","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.2.8-renovate-external-non-major-4dce561e065ada8ca9c12e595a721189cb6e1b8e_1633895217561_0.466646028105721","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.2.8":{"name":"@balena/jellyfish-core","version":"6.2.8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.2.8","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5c549c8b368fc61a15bf58662270a5cfefd411e3","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.2.8.tgz","fileCount":205,"integrity":"sha512-w33U0xobnKQRUSBlj9HEh9cQHA59hAYxVf2Bn5HHVh6Jwi+JO4ubqbjsRXJVQZt1+9wBVZwufVmy+M7pX4W8GQ==","signatures":[{"sig":"MEUCIFOmX7PpiuZi1rN+ijnkLoNcgjblmM2POMrJSFvU/ogfAiEA8WPmf3f/n7pYEpxwFf5gDkl9dRcyKHmBjynUAeGJIfU=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752537},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"777a530f0efe7678a2fe06a83e739c22888b9324","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-10T19:53:38.926Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.84","@balena/jellyfish-metrics":"^1.0.342","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.4"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.5","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.2.3","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.2.8_1633895733356_0.7515351329707569","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.2.9-renovate-major-external-major-3f41b72bb34ddd3f3f03f36beb86dbae799dca31":{"name":"@balena/jellyfish-core","version":"6.2.9-renovate-major-external-major-3f41b72bb34ddd3f3f03f36beb86dbae799dca31","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.2.9-renovate-major-external-major-3f41b72bb34ddd3f3f03f36beb86dbae799dca31","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0cbaab58e5b27973024df59f8e4bd12ccfacc4dd","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.2.9-renovate-major-external-major-3f41b72bb34ddd3f3f03f36beb86dbae799dca31.tgz","fileCount":205,"integrity":"sha512-UBCwjEauHWg5jhwiXgEgxrtejnUZ4nBLdwz2lwgYdke5qXlCxkgf7StDwi4cP83JORmkaU2zw4mMo5jN9Qvcng==","signatures":[{"sig":"MEUCIQDgWpIvR+e9+EpKVXHBRnHdm2nFh+gNeRnB6Q7HUdXC5gIgegGb+CbsxX+YHnYeeaQXgqWjAmIMvGbLJsnkM+JDu3Y=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752677},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"3f41b72bb34ddd3f3f03f36beb86dbae799dca31","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-10T19:56:49.460Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.84","@balena/jellyfish-metrics":"^1.0.342","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.2.5","deplint":"^1.1.3","ts-jest":"^27.0.5","typedoc":"^0.22.5","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.3","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.2.9-renovate-major-external-major-3f41b72bb34ddd3f3f03f36beb86dbae799dca31_1633895981689_0.8337414491273576","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.2.9-renovate-major-json-schema-org-20c63cc38b7d1d9df66c2cafc5aff20677d91fce":{"name":"@balena/jellyfish-core","version":"6.2.9-renovate-major-json-schema-org-20c63cc38b7d1d9df66c2cafc5aff20677d91fce","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.2.9-renovate-major-json-schema-org-20c63cc38b7d1d9df66c2cafc5aff20677d91fce","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"7d67e46425ee988c500b89e9da10ff4591d0f6c5","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.2.9-renovate-major-json-schema-org-20c63cc38b7d1d9df66c2cafc5aff20677d91fce.tgz","fileCount":205,"integrity":"sha512-WP2uNv3CuqrE1Rtx6toBe0hQ9XNR19DSNiPqSlYt97Mwi92CGGYmLHdP1kha2kKzuCb7vN+TKYisEsx2CsXAUA==","signatures":[{"sig":"MEUCIQDHZbimgFKbIe2FxNZwg6PE15ucVLAeCV2OP3V6I0PQzQIgDHGbR9Z787m70/Cho7Hqw6bkh6d4afezhSHL6TBhj6M=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752704},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"20c63cc38b7d1d9df66c2cafc5aff20677d91fce","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-10T19:58:59.574Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.84","@balena/jellyfish-metrics":"^1.0.342","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.5","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.2.3","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.2.9-renovate-major-json-schema-org-20c63cc38b7d1d9df66c2cafc5aff20677d91fce_1633896178371_0.6309356351779001","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.2.9-renovate-major-external-major-521bb9fc5305cc44be79414f2007066e61b8c7ce":{"name":"@balena/jellyfish-core","version":"6.2.9-renovate-major-external-major-521bb9fc5305cc44be79414f2007066e61b8c7ce","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.2.9-renovate-major-external-major-521bb9fc5305cc44be79414f2007066e61b8c7ce","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8d1ed81a87715e46acdf91ef86204849cf4aa6fc","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.2.9-renovate-major-external-major-521bb9fc5305cc44be79414f2007066e61b8c7ce.tgz","fileCount":205,"integrity":"sha512-aFTisbvEVk8/MA/5CwlDBG9wswF9+vgw3WXL0zHcuJjVgDDDcZ88RMMU2vHT5+/vk5KUUUKNp2fYVMwf1CWEUw==","signatures":[{"sig":"MEUCIBgFtYZk3vTdpTfGjAFco0bLizrVaqgo6UNK8ws499mLAiEA59j1vVAO9uBuQ3K3SI3qW02S3YvTrYvsq4PAu3HBf+8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752677},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"521bb9fc5305cc44be79414f2007066e61b8c7ce","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-14T18:46:38.940Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.84","@balena/jellyfish-metrics":"^1.0.342","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.2.5","deplint":"^1.1.3","ts-jest":"^27.0.6","typedoc":"^0.22.5","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.3","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.2.9-renovate-major-external-major-521bb9fc5305cc44be79414f2007066e61b8c7ce_1634237401514_0.7190005547896821","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.2.9-renovate-external-non-major-f5e0e26161ba8ceeff2e56def9bc7c999adaa4bb":{"name":"@balena/jellyfish-core","version":"6.2.9-renovate-external-non-major-f5e0e26161ba8ceeff2e56def9bc7c999adaa4bb","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.2.9-renovate-external-non-major-f5e0e26161ba8ceeff2e56def9bc7c999adaa4bb","maintainers":[{"name":"pranavbalena","email":"pranav@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0075b75c2030781b3141aa6550014375d6bf7adf","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.2.9-renovate-external-non-major-f5e0e26161ba8ceeff2e56def9bc7c999adaa4bb.tgz","fileCount":205,"integrity":"sha512-jDHxVpNnUZ0xWXneKHJN9fxJYgnMF1XSsknqzqItpuShomfX1y5f+CP96d1pTha6t9hyRv6OtupnX1ScHhRthA==","signatures":[{"sig":"MEYCIQC20Nt0NNFXbyhqNYhihGXQz7EghFlxzyctRD7J2CJ+qAIhAIBoxlO0JuUQD275w2adDimmKwekOL/uRk3PKcyzolnJ","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752693},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f5e0e26161ba8ceeff2e56def9bc7c999adaa4bb","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-16T00:48:39.016Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.84","@balena/jellyfish-metrics":"^1.0.342","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.5","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.2.3","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.2.9-renovate-external-non-major-f5e0e26161ba8ceeff2e56def9bc7c999adaa4bb_1634345518059_0.7846981021851922","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.2.9-renovate-external-non-major-990ba3afd9f8048c348a02b4b18fae8f4bad5c2d":{"name":"@balena/jellyfish-core","version":"6.2.9-renovate-external-non-major-990ba3afd9f8048c348a02b4b18fae8f4bad5c2d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.2.9-renovate-external-non-major-990ba3afd9f8048c348a02b4b18fae8f4bad5c2d","maintainers":[{"name":"pranavbalena","email":"pranav@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"15b929e191914c56ae3450c0a54ac08dee9816a6","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.2.9-renovate-external-non-major-990ba3afd9f8048c348a02b4b18fae8f4bad5c2d.tgz","fileCount":205,"integrity":"sha512-vlqHTnVcidp/8+0I0AjbLNy2C8buY+mKwTFrj1LroaEPZ+EyHJJfUOCFh6xmCCVQtSfGP6MqyyX/0tHMOJ3fDQ==","signatures":[{"sig":"MEYCIQCMFsU1HmaXr6QVY1mLKtWIhVrP+JtKupVrPSvlWBS2pAIhAJZOLdhUjFXpgKG0+DfkqciFWp/BTHhrz6NmU1LPe7Xs","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752680},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"990ba3afd9f8048c348a02b4b18fae8f4bad5c2d","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-16T02:36:08.993Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.84","@balena/jellyfish-metrics":"^1.0.342","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.5","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.2.3","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.2.9-renovate-external-non-major-990ba3afd9f8048c348a02b4b18fae8f4bad5c2d_1634352028960_0.6557201344671326","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.2.9-renovate-major-external-major-ad806310bd4a9ca5bfaec783ae27844101fb1272":{"name":"@balena/jellyfish-core","version":"6.2.9-renovate-major-external-major-ad806310bd4a9ca5bfaec783ae27844101fb1272","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.2.9-renovate-major-external-major-ad806310bd4a9ca5bfaec783ae27844101fb1272","maintainers":[{"name":"pranavbalena","email":"pranav@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"cab1ea402510d3344aa65a4c4fbe31dd8822c541","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.2.9-renovate-major-external-major-ad806310bd4a9ca5bfaec783ae27844101fb1272.tgz","fileCount":205,"integrity":"sha512-9mRlPT2ZWx1XrKxpcjQmI6/ISsqPb3GJaWu4qx6gYcB9/80+BKGEhIakGt9pI5cE9ygQSoPXvNJ5emQWijkPUA==","signatures":[{"sig":"MEUCIQDziyd8ltD7h+wH9ErXqkTFIh/6eVxr2l9Jsv1B6H+ASAIgIVcYQb9EQdc04NJyy6JuFvnpWujBQNA9KSgH4ohiQKo=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752677},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ad806310bd4a9ca5bfaec783ae27844101fb1272","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-16T10:21:59.193Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.84","@balena/jellyfish-metrics":"^1.0.342","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.2.5","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.5","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.3","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.2.9-renovate-major-external-major-ad806310bd4a9ca5bfaec783ae27844101fb1272_1634379908354_0.21388546161011712","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.2.9-renovate-internal-patch-d05fb5ffd71764a74b12f95401f680c61e76c726":{"name":"@balena/jellyfish-core","version":"6.2.9-renovate-internal-patch-d05fb5ffd71764a74b12f95401f680c61e76c726","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.2.9-renovate-internal-patch-d05fb5ffd71764a74b12f95401f680c61e76c726","maintainers":[{"name":"pranavbalena","email":"pranav@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ca63b2afd0110a98db9bbcf67af76964696ad9aa","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.2.9-renovate-internal-patch-d05fb5ffd71764a74b12f95401f680c61e76c726.tgz","fileCount":205,"integrity":"sha512-4CGc6NIay3LMsEeIYXTcoXPe308yqP9oA1re0h+tw5EgIvkrSCfoO3EuzuC8c6VBP6cC/xdOThAPHbQTGRFNCg==","signatures":[{"sig":"MEYCIQDKvAfkVS3qPGgOE5PiY3iEtyzwm7VBB74lJcuMeRIklgIhAJftyt0NcT/I0dN9MPEDDhgvvL6YlItNzVMH8PD9vDO8","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752704},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d05fb5ffd71764a74b12f95401f680c61e76c726","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-16T11:51:50.164Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.86","@balena/jellyfish-metrics":"^1.0.342","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.5","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.2.3","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.2.9-renovate-internal-patch-d05fb5ffd71764a74b12f95401f680c61e76c726_1634385300073_0.2735250519225143","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.2.9-renovate-major-external-major-14df77c3bf4a176c47a3a8c0a0944b9a0afb427a":{"name":"@balena/jellyfish-core","version":"6.2.9-renovate-major-external-major-14df77c3bf4a176c47a3a8c0a0944b9a0afb427a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.2.9-renovate-major-external-major-14df77c3bf4a176c47a3a8c0a0944b9a0afb427a","maintainers":[{"name":"pranavbalena","email":"pranav@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ccb0836071e0146910c554154033cf4e4f70cf1b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.2.9-renovate-major-external-major-14df77c3bf4a176c47a3a8c0a0944b9a0afb427a.tgz","fileCount":205,"integrity":"sha512-qHntGplneZt6eUoERwL3/Rr5cdaUDbAKM2FHmBzOL5BU2so6DGXM/pnsAuptnfKwtXroml2Z1mCRIt3K7clJwQ==","signatures":[{"sig":"MEUCIFUyYqf+kGubJbnTtsLFj/FWlkGrE6ZoDl6bY/fbRojUAiEA+lVJj9BC8wqgnTHQ7bvFQh1Zyv5BJm58nuXX4xjR+Mo=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752677},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"14df77c3bf4a176c47a3a8c0a0944b9a0afb427a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-17T23:47:50.547Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.84","@balena/jellyfish-metrics":"^1.0.342","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.0","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.5","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.3","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.2.9-renovate-major-external-major-14df77c3bf4a176c47a3a8c0a0944b9a0afb427a_1634514675157_0.11824301119045377","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.2.9-repo-sync-secrets-default-dd19973c4ec3c7f3028ecedec84c99de8a00f8a0":{"name":"@balena/jellyfish-core","version":"6.2.9-repo-sync-secrets-default-dd19973c4ec3c7f3028ecedec84c99de8a00f8a0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.2.9-repo-sync-secrets-default-dd19973c4ec3c7f3028ecedec84c99de8a00f8a0","maintainers":[{"name":"pranavbalena","email":"pranav@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"7a7b1d3cd5119ad923f2d579992cc69c31addd99","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.2.9-repo-sync-secrets-default-dd19973c4ec3c7f3028ecedec84c99de8a00f8a0.tgz","fileCount":205,"integrity":"sha512-3Ua4z9DcMOe9in1YZ/WJf4fd6aBtGt7a/4DqzGyB2fNScGarKX6XhZHuwA1rZjPEinaM3+MELG0xPk96YIG68A==","signatures":[{"sig":"MEQCIFO/dqS5nvsNKX2eRDQYLuSl9Hq6yzkuKipwzNFfQPEdAiBgGJevX0gMBmTtMaNZBFjR4aHA5GgjbEkOnROivQM6aA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752686},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"dd19973c4ec3c7f3028ecedec84c99de8a00f8a0","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-18T01:58:28.656Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.84","@balena/jellyfish-metrics":"^1.0.342","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.5","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.2.3","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.2.9-repo-sync-secrets-default-dd19973c4ec3c7f3028ecedec84c99de8a00f8a0_1634522513759_0.5898143164763603","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.2.9":{"name":"@balena/jellyfish-core","version":"6.2.9","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.2.9","maintainers":[{"name":"pranavbalena","email":"pranav@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f429fdc6ccd4072bc6e52e38e3e6dfc30630a3d8","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.2.9.tgz","fileCount":205,"integrity":"sha512-5VdmAQp1JKaOG/X5wcBGXtmW7PJF2Hv3X/6AtvCxWhD3xYkFMwzriVYBoZy2eiUx/HQ7K/IaIsekob4ktobBhg==","signatures":[{"sig":"MEYCIQDe9QnrIPdXam5yq1HG8Ege8sq7pIEpZr6M6WLNHtlmGwIhAJKoFU3qgDtvlY3VGRFMlLKkREIU6p5xmvcX3H3RjP4S","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752619},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"5c2f79440e8ccdf55d0540a001457a9acd84f013","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-18T02:12:09.212Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.84","@balena/jellyfish-metrics":"^1.0.342","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.4"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.5","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.2.3","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.2.9_1634523237085_0.7377228810737113","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.2.10-renovate-internal-patch-c0a49147f2d80fba51ea24a0de6867a618194f40":{"name":"@balena/jellyfish-core","version":"6.2.10-renovate-internal-patch-c0a49147f2d80fba51ea24a0de6867a618194f40","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.2.10-renovate-internal-patch-c0a49147f2d80fba51ea24a0de6867a618194f40","maintainers":[{"name":"pranavbalena","email":"pranav@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e00dbea969df5dd1769da772e1e10fa10d56e604","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.2.10-renovate-internal-patch-c0a49147f2d80fba51ea24a0de6867a618194f40.tgz","fileCount":205,"integrity":"sha512-KzoQt2Qv46bO8MUGO7b0f/tTq8jJu9vq8v2JuCQZ+6BDmQPEaSBp0iTx6lV9T7eAYwaTP721NIDU9Pg0P+OMsg==","signatures":[{"sig":"MEQCIEVdieO/A2tsouc7WCONIpkUde/1BuLM8MAilqVFVgABAiAUWUfuH+Sk+Yzm2UpqDjcX7AU0UtkbVHLYoOLxTgfDvw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752788},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c0a49147f2d80fba51ea24a0de6867a618194f40","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-18T02:39:29.561Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.86","@balena/jellyfish-metrics":"^1.0.342","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.5","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.2.3","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.2.10-renovate-internal-patch-c0a49147f2d80fba51ea24a0de6867a618194f40_1634524988182_0.8627305227366737","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.2.10-renovate-major-external-major-f3c943f15e88ea99656c3cd724d4660cea21b633":{"name":"@balena/jellyfish-core","version":"6.2.10-renovate-major-external-major-f3c943f15e88ea99656c3cd724d4660cea21b633","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.2.10-renovate-major-external-major-f3c943f15e88ea99656c3cd724d4660cea21b633","maintainers":[{"name":"pranavbalena","email":"pranav@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"91dda9ddc8120d9985d88347eacb4f29594b7c1e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.2.10-renovate-major-external-major-f3c943f15e88ea99656c3cd724d4660cea21b633.tgz","fileCount":205,"integrity":"sha512-mHjkLOZRGY7nGOUfqwacCxgU3T4IWiAeYNvfKckpdflynJepVCNB0G5Gw9lZ8mNgg+MykEAwyzQfLEhUGR3XMw==","signatures":[{"sig":"MEUCIQDjiEDhIiuwQLU2utY4AlNvsOYWM38X2utRFjRnczaCxQIgDSrodS4ZBa9YpzrVFrlDQqNXGKWBmp78K7hWVw5k26g=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752761},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f3c943f15e88ea99656c3cd724d4660cea21b633","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-18T02:41:39.509Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.84","@balena/jellyfish-metrics":"^1.0.342","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.0","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.5","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.3","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.2.10-renovate-major-external-major-f3c943f15e88ea99656c3cd724d4660cea21b633_1634525052473_0.6037368613648884","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.2.10":{"name":"@balena/jellyfish-core","version":"6.2.10","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.2.10","maintainers":[{"name":"pranavbalena","email":"pranav@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0c0ffea1d5358278ef81ab0240ab02e97e7c2b39","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.2.10.tgz","fileCount":205,"integrity":"sha512-qkTf16xQue6+A4L6GvcqQSewOqtcoSw+BF0JVz4zvMfHjkoKYiyDpe8J+h1ThqyDpVhO3vcfvGNgANgOe6ZKiQ==","signatures":[{"sig":"MEQCICWfRFobcBTTM2E5DQvCoUHtIejg5lKL6BgTYXpJ+irKAiAnpLSuthKyoSNTz4WCPWZB6/U4SuU1lhqBh2hWB5NRAQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752723},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"28231cd9a726daff694b2559ba571cf6e4232f09","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-18T02:48:49.690Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.86","@balena/jellyfish-metrics":"^1.0.342","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.4"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.5","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.2.3","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.2.10_1634525445256_0.6330508017421024","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.2.11-renovate-external-non-major-a61c04e2f407c06c75c1917d0830c2b678090a5b":{"name":"@balena/jellyfish-core","version":"6.2.11-renovate-external-non-major-a61c04e2f407c06c75c1917d0830c2b678090a5b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.2.11-renovate-external-non-major-a61c04e2f407c06c75c1917d0830c2b678090a5b","maintainers":[{"name":"pranavbalena","email":"pranav@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"10f6d8474e85748d5f45ea10407c75d5bc23c315","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.2.11-renovate-external-non-major-a61c04e2f407c06c75c1917d0830c2b678090a5b.tgz","fileCount":205,"integrity":"sha512-+6a78z7ba3H1L024zXPj/tDex8qqYXiJOMPCQcB8SzXl7XRe5swReWi8CkCVhMLwLu63Dtw1/7rccoPi7Zuu0w==","signatures":[{"sig":"MEQCIF1IkczU3D9DUjthhEq8uTm689SgLzCyPz4/bLkGCX68AiB/eeAJBkwshJDWLkw/3L0AcUPFrqTI3PBStzl+nLTiBA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752867},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a61c04e2f407c06c75c1917d0830c2b678090a5b","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-18T02:51:10.809Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.86","@balena/jellyfish-metrics":"^1.0.342","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.2.3","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.2.11-renovate-external-non-major-a61c04e2f407c06c75c1917d0830c2b678090a5b_1634525716633_0.153539248643348","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.2.11-renovate-major-external-major-5b86032518faa433dce0458d5efc92a720758868":{"name":"@balena/jellyfish-core","version":"6.2.11-renovate-major-external-major-5b86032518faa433dce0458d5efc92a720758868","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.2.11-renovate-major-external-major-5b86032518faa433dce0458d5efc92a720758868","maintainers":[{"name":"pranavbalena","email":"pranav@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2452167fe511c65907bbe842c87e9a2de3cf819d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.2.11-renovate-major-external-major-5b86032518faa433dce0458d5efc92a720758868.tgz","fileCount":205,"integrity":"sha512-e1RWG5DWctL7DV8NcVAU/KZ0GFy7ICWskwtiCNq7rYddNIpcj6Ah8aIPZRlHTC/HnGFzNgUnUPrpYVB0WsKv5w==","signatures":[{"sig":"MEQCIF0EHCBhR499+Symfd/ZQtkdrzQyZbp3pl03kEWZo5dXAiBAuHOoaaDYxfyoiTIesiyjenOZTqO/w6CHOu2h6btYeg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752864},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"5b86032518faa433dce0458d5efc92a720758868","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-18T02:55:09.777Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.0","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.86","@balena/jellyfish-metrics":"^1.0.342","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.0","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.5","depcheck":"^1.4.2","typescript":"^4.4.3","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.3","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.2.11-renovate-major-external-major-5b86032518faa433dce0458d5efc92a720758868_1634525918286_0.784251311814276","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.2.11":{"name":"@balena/jellyfish-core","version":"6.2.11","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.2.11","maintainers":[{"name":"pranavbalena","email":"pranav@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"02c92d20106d6df9a0c38a6776f3e3dc4a2e562f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.2.11.tgz","fileCount":205,"integrity":"sha512-gbXUe7GrfU3Ey8sH4lIaSEHLYHl2bZQufO9hZ02d2vewqreHD+oI+3e+KtX1P7PGOZ7dbBzcrF7m5hspZaCOfA==","signatures":[{"sig":"MEUCIQDz+CwlokJuRmx/H29vghxj7Op9yIFhR3Q/3/yxmSwOIAIgMR8KW3CFsgS61qul3RpjmJllj2PNhcwMR79pbftu6Ag=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752798},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"870c8021bb69d2c989d8b04b2596286606c2e5e6","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-18T03:04:12.349Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.86","@balena/jellyfish-metrics":"^1.0.342","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.4"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.2.3","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.2.11_1634526368393_0.6327931379561351","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.2.12-renovate-major-json-schema-org-7e2553b5c41db84f6c5f3b9882441daa94957ad5":{"name":"@balena/jellyfish-core","version":"6.2.12-renovate-major-json-schema-org-7e2553b5c41db84f6c5f3b9882441daa94957ad5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.2.12-renovate-major-json-schema-org-7e2553b5c41db84f6c5f3b9882441daa94957ad5","maintainers":[{"name":"pranavbalena","email":"pranav@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f0c5ecd0c1666cb6662442ea409838ce55955b47","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.2.12-renovate-major-json-schema-org-7e2553b5c41db84f6c5f3b9882441daa94957ad5.tgz","fileCount":205,"integrity":"sha512-8vU3BhMNdEW+DXf8mYuT3B0Yft4cviC94FblRTeq1bN3B4gBK8DEm0pYYDMML5RAVRi9KelB4pPJNakKoC52rQ==","signatures":[{"sig":"MEUCIQCcdEAs7PIdhEhGy/H/VqZtYCc4kSLZ4pVVjCjKmNI2LQIgSIqqC+A84LuzoBjhp8a/iPak1R8zyAd5Q/FoDnCbEDU=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752966},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7e2553b5c41db84f6c5f3b9882441daa94957ad5","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-18T03:05:59.562Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.86","@balena/jellyfish-metrics":"^1.0.342","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^26.6.3","deplint":"^1.1.3","ts-jest":"^26.5.6","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^26.0.24","@types/uuid":"^8.3.1","lint-staged":"^11.2.3","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.2.12-renovate-major-json-schema-org-7e2553b5c41db84f6c5f3b9882441daa94957ad5_1634526636695_0.7074844795146082","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.2.12-renovate-major-external-major-4ab7334ba7b789d5b741a64adcb8f7e50d2eb888":{"name":"@balena/jellyfish-core","version":"6.2.12-renovate-major-external-major-4ab7334ba7b789d5b741a64adcb8f7e50d2eb888","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.2.12-renovate-major-external-major-4ab7334ba7b789d5b741a64adcb8f7e50d2eb888","maintainers":[{"name":"pranavbalena","email":"pranav@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e7faaf8bd05fcf46817e8b1c30da542b29725de3","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.2.12-renovate-major-external-major-4ab7334ba7b789d5b741a64adcb8f7e50d2eb888.tgz","fileCount":205,"integrity":"sha512-paZCNVnVi9ppeNN3XQFuQ5S3EAyj9kAs/EdGcswYDtoJV1nPa3hKU36VrqbxKbsB4U+hWINCT7VtVtU+qcUlCQ==","signatures":[{"sig":"MEQCIEzyhoMOrpGlcaTmwvk5q1ZV3HhFLT9vZfifE+uQ1w8IAiA49Q7/5vL9rCMvkFjvKBTIrCAWSnejsT4T0hUQ7Yp7Cg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752939},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4ab7334ba7b789d5b741a64adcb8f7e50d2eb888","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-18T03:06:50.388Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.86","@balena/jellyfish-metrics":"^1.0.342","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.0","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.3","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.2.12-renovate-major-external-major-4ab7334ba7b789d5b741a64adcb8f7e50d2eb888_1634526659014_0.34927596486197765","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.2.12-renovate-major-external-major-c3f5fd067581cc0d649acaa9b51419b79416b6cc":{"name":"@balena/jellyfish-core","version":"6.2.12-renovate-major-external-major-c3f5fd067581cc0d649acaa9b51419b79416b6cc","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.2.12-renovate-major-external-major-c3f5fd067581cc0d649acaa9b51419b79416b6cc","maintainers":[{"name":"pranavbalena","email":"pranav@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e4dc128cf53dbfa5c3db67d06264270cdadd4097","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.2.12-renovate-major-external-major-c3f5fd067581cc0d649acaa9b51419b79416b6cc.tgz","fileCount":205,"integrity":"sha512-5V8bAs5UILZL7J15FTrY4qZ6r4HM/XLSo8goNo3WAhcr/CQOE1BbVEAwohuuNnG2TMJInacoe5hZqxAQFtQFRg==","signatures":[{"sig":"MEUCIFTBzY93cobqrdfdpt/57i3ZyO9JfdCYiYQRHEOvBEETAiEA/PyfFOlVo3u0Mt31pt/IOAgHQ3D7vh/W6sBo+Q1PizE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752988},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c3f5fd067581cc0d649acaa9b51419b79416b6cc","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-18T06:15:19.464Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.86","@balena/jellyfish-metrics":"^1.0.342","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.0","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.3","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.2.12-renovate-major-external-major-c3f5fd067581cc0d649acaa9b51419b79416b6cc_1634537894590_0.9625520528421276","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.2.12-renovate-major-external-major-a7146a48fbf25190d5bfd61ba8514c62efef0246":{"name":"@balena/jellyfish-core","version":"6.2.12-renovate-major-external-major-a7146a48fbf25190d5bfd61ba8514c62efef0246","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.2.12-renovate-major-external-major-a7146a48fbf25190d5bfd61ba8514c62efef0246","maintainers":[{"name":"pranavbalena","email":"pranav@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"049c226a552fa746221c0a6b35d82ea7d1c8215c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.2.12-renovate-major-external-major-a7146a48fbf25190d5bfd61ba8514c62efef0246.tgz","fileCount":205,"integrity":"sha512-tk1wltTVSF/KKQGLSn3Ac3YBwBrt2owe4C+AD7+fNtaUcJH3OI9reOXlrPrNZjMLtUst4IbE2uJxFvjoBtBQ3A==","signatures":[{"sig":"MEYCIQDO0lKFBVzoBeIUd7QHVlM9b0QSA2XvxZbARP7fpggBCwIhANH+lIuAos24Yu7XHHbAhlDOCG/videYk97hQav8Qkgk","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752988},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a7146a48fbf25190d5bfd61ba8514c62efef0246","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-18T06:46:38.834Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.86","@balena/jellyfish-metrics":"^1.0.342","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.0","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.3","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.2.12-renovate-major-external-major-a7146a48fbf25190d5bfd61ba8514c62efef0246_1634539809971_0.0932980733536819","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.2.12":{"name":"@balena/jellyfish-core","version":"6.2.12","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.2.12","maintainers":[{"name":"pranavbalena","email":"pranav@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"7ee47681f8c82722a6e0f3bf3324a7e81179aa7c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.2.12.tgz","fileCount":205,"integrity":"sha512-5SBndQKTY2wWdRFogT61Ho9zNgTIzeqGoRWDddgGZvU0iME5n1UX1udyGIqOOniNxfeLKwdCoDh2aKAyfaJ9eg==","signatures":[{"sig":"MEUCIQCzvFj9AoBb1X8fUVCByheLC6NP/atu5c0RxQUZtAX6PQIgUV2PIlLmzIV6wJs+XyE75ZkRgPQfDiDwkqHNHw9IzpE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752917},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"69ede87304fa5da325083f36114325a62e43b382","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-18T07:04:19.772Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.86","@balena/jellyfish-metrics":"^1.0.342","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.4"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.0","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.3","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.2.12_1634540766134_0.5467653646141324","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.2.13-renovate-major-json-schema-org-232543a51a63c95692c80ff079d2fcc580f22353":{"name":"@balena/jellyfish-core","version":"6.2.13-renovate-major-json-schema-org-232543a51a63c95692c80ff079d2fcc580f22353","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.2.13-renovate-major-json-schema-org-232543a51a63c95692c80ff079d2fcc580f22353","maintainers":[{"name":"pranavbalena","email":"pranav@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"980312f767e898cc40d34264595b62206f1bf3f8","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.2.13-renovate-major-json-schema-org-232543a51a63c95692c80ff079d2fcc580f22353.tgz","fileCount":205,"integrity":"sha512-xvWi4+tJ5cQzMLYw5AFqcTMVg/ANSs+0zVVP8NfU8gXFB3N/ZBR047s46yF04wL9HM1dwtHKdy/aydGu/hbN3A==","signatures":[{"sig":"MEUCIQCap+0aBEIeOL5XTo3Ga8+M/PrQsPZnYRpEtTv9unBxKgIgC8YBt33MmV3hINP3tqRuMhbf6fY3x/6qGRUyQXeXre0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":753085},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"232543a51a63c95692c80ff079d2fcc580f22353","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-18T07:06:19.586Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.86","@balena/jellyfish-metrics":"^1.0.342","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.0","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.3","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.2.13-renovate-major-json-schema-org-232543a51a63c95692c80ff079d2fcc580f22353_1634541012577_0.41028352139078605","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.0-h32-simplify-patch-validation-7312cbd752aa118cd7365c352c2c5ffee0c31289":{"name":"@balena/jellyfish-core","version":"7.0.0-h32-simplify-patch-validation-7312cbd752aa118cd7365c352c2c5ffee0c31289","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.0-h32-simplify-patch-validation-7312cbd752aa118cd7365c352c2c5ffee0c31289","maintainers":[{"name":"pranavbalena","email":"pranav@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"897cb4b4c5977140546c6355446b5c3d904fb9e0","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.0-h32-simplify-patch-validation-7312cbd752aa118cd7365c352c2c5ffee0c31289.tgz","fileCount":205,"integrity":"sha512-q8/aNROknkiT+N6Ew2heVTc88soPnfklQeT14SsK56U08CdN0haEOdpwDVXiA8pNUAzKAx0JDjuTZWo/n7qE0w==","signatures":[{"sig":"MEUCIE2zvcZ3RMEoh8ddfa5uUUITha4x517hHeSi19PCQnoDAiEAheykwuEoewYX3wKVSRp1Vuyev0MuTfPnclptOSTF6vo=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":751987},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7312cbd752aa118cd7365c352c2c5ffee0c31289","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-18T13:54:09.116Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.86","@balena/jellyfish-metrics":"^1.0.342","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.0","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.3","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.0-h32-simplify-patch-validation-7312cbd752aa118cd7365c352c2c5ffee0c31289_1634565472185_0.38160740402818627","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.2.13-renovate-internal-patch-3009eb7ac2368cefebc34af7c6eaea077dacdb6d":{"name":"@balena/jellyfish-core","version":"6.2.13-renovate-internal-patch-3009eb7ac2368cefebc34af7c6eaea077dacdb6d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.2.13-renovate-internal-patch-3009eb7ac2368cefebc34af7c6eaea077dacdb6d","maintainers":[{"name":"pranavbalena","email":"pranav@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"573fe3fe7f892d97b6a4d27b3d797a3d5f54e02b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.2.13-renovate-internal-patch-3009eb7ac2368cefebc34af7c6eaea077dacdb6d.tgz","fileCount":205,"integrity":"sha512-yqWbLhTOhITxHeIqW18XHsmfhpVZwb6me6PJN3ROCJHf7Ut5XirrVC5MlNq2mlMo49h9bJv2xm7YOqpjs0Cvgg==","signatures":[{"sig":"MEUCIA5nOp4H8qyI9Rd4rEVGsQtb2czfwGl3RAw5Yis3SzaeAiEAxnpOBvTugTq/7eaQLCt5AFRblgF6rPU0hbiSBlSLlis=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":753053},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"3009eb7ac2368cefebc34af7c6eaea077dacdb6d","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-18T23:42:04.508Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.86","@balena/jellyfish-metrics":"^1.0.346","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.0","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.3","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.2.13-renovate-internal-patch-3009eb7ac2368cefebc34af7c6eaea077dacdb6d_1634600811263_0.5051231144803838","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.2.13":{"name":"@balena/jellyfish-core","version":"6.2.13","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.2.13","maintainers":[{"name":"pranavbalena","email":"pranav@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c10dbcbf0f9dfb9ebe4a5092217243f9c6e9b538","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.2.13.tgz","fileCount":205,"integrity":"sha512-Ob2ZDpHu0mXymz6Uk4o7QBNGvvuYOfLA2Kw6i3CBORRIHVX7CmfjjwwEeAOpSnbMQGUHelH88meErUh1+GMhTQ==","signatures":[{"sig":"MEYCIQCl1tbc1H9e0uN84lOwoSUZWFqcJ2Tna5zfuPR/Pa9OMgIhAO08RT792jMHHV5qJlzRqhn9fmocSy4UA2Ktc2Z3Rlml","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752988},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"57fb041b477a40a385d2fba8ff8ea2271359ed36","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-18T23:51:40.548Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.86","@balena/jellyfish-metrics":"^1.0.346","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.5"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.0","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.3","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.2.13_1634601235184_0.01332434171976793","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"6.2.14-renovate-major-json-schema-org-dae227d78648ca38b4321289e4e78d4830d55ffc":{"name":"@balena/jellyfish-core","version":"6.2.14-renovate-major-json-schema-org-dae227d78648ca38b4321289e4e78d4830d55ffc","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@6.2.14-renovate-major-json-schema-org-dae227d78648ca38b4321289e4e78d4830d55ffc","maintainers":[{"name":"pranavbalena","email":"pranav@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ef37b383bc5538d067308ded8dd8ecfbafa5b014","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-6.2.14-renovate-major-json-schema-org-dae227d78648ca38b4321289e4e78d4830d55ffc.tgz","fileCount":205,"integrity":"sha512-PQLlmi5x/92OlBHUodFiXcO7zL99/zhDYWv+h0lxxJaZbc5uAn8BVLfM68hC4gMkkKHlwY3MUg+r3avjUaXJKA==","signatures":[{"sig":"MEUCIDn50ZHgQok3rqVBliuLbygnUJ2y9xffFkaxiOlZufqNAiEAjqekxCP4807JAXOVawA5sXk0BYzU7i0YjBK25gmrGdI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":753156},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"dae227d78648ca38b4321289e4e78d4830d55ffc","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-18T23:54:09.721Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.86","@balena/jellyfish-metrics":"^1.0.346","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.0","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.3","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_6.2.14-renovate-major-json-schema-org-dae227d78648ca38b4321289e4e78d4830d55ffc_1634601485982_0.16078296297309325","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.0-h32-simplify-patch-validation-f6343443e8bfc32d5c1a3b0908805e46513afebe":{"name":"@balena/jellyfish-core","version":"7.0.0-h32-simplify-patch-validation-f6343443e8bfc32d5c1a3b0908805e46513afebe","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.0-h32-simplify-patch-validation-f6343443e8bfc32d5c1a3b0908805e46513afebe","maintainers":[{"name":"pranavbalena","email":"pranav@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c4bb05151b30ae60d7565b98a2e975660bffdc65","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.0-h32-simplify-patch-validation-f6343443e8bfc32d5c1a3b0908805e46513afebe.tgz","fileCount":205,"integrity":"sha512-24qq9PbSPGDZKOiEH921RH+yE5Y544EqNkyrBbLj7eN9Id0m/TVdkPotet1KXkmMK2s07v47NyK2sVzfk7JOUg==","signatures":[{"sig":"MEQCIBJAKERI118Rh7BnRNNhBq9DC3cYSUvAAR/pS6ndfCbUAiBFu/gdjmr4gBuka6FN6IL8v4dZBU+P22dyzLUy8sQ23A==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752058},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f6343443e8bfc32d5c1a3b0908805e46513afebe","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-19T15:02:49.964Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.86","@balena/jellyfish-metrics":"^1.0.346","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.0","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.3","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.0-h32-simplify-patch-validation-f6343443e8bfc32d5c1a3b0908805e46513afebe_1634655986461_0.9186239655181085","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.0":{"name":"@balena/jellyfish-core","version":"7.0.0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.0","maintainers":[{"name":"pranavbalena","email":"pranav@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c5740f86041a588e365df450a75e93ade862058e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.0.tgz","fileCount":205,"integrity":"sha512-Fg5DqS4ZLgVPYxfs1pX/FHUBgb4w8hrKwhBSZVdUuZoLLAesiWxWa8WaiEXyUDKipVsyEsvjE0Iros2220yyPQ==","signatures":[{"sig":"MEQCIEummr61/t9k4ZfGaiVyV4jTpA0re8lt0wEmMNFgplnNAiANdINnVZmfCUHGFPKOMzg7L2xt5xRN2xcd3qogyhOkhQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":751987},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ccca9adfa98a11bd0b914d16e4e49ff9d782c167","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-19T15:11:43.213Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.86","@balena/jellyfish-metrics":"^1.0.346","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.5"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.0","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.3","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.0_1634656443548_0.13414005390687933","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.1-renovate-major-json-schema-org-d40b386ed538b0ec8a75480e2380b5ebb0ca1f9e":{"name":"@balena/jellyfish-core","version":"7.0.1-renovate-major-json-schema-org-d40b386ed538b0ec8a75480e2380b5ebb0ca1f9e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.1-renovate-major-json-schema-org-d40b386ed538b0ec8a75480e2380b5ebb0ca1f9e","maintainers":[{"name":"pranavbalena","email":"pranav@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c09d25b3d1a8ed9fcbcfebd51536dc822f1a212a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.1-renovate-major-json-schema-org-d40b386ed538b0ec8a75480e2380b5ebb0ca1f9e.tgz","fileCount":205,"integrity":"sha512-FDWkG8PmmUuEWZjn6Sgij96kM1Vga/W+fnHPLXrJVsdk2ANvQo3Kpe/QWAThYvt2WNFDoVlsWTrHzKToz1TBrg==","signatures":[{"sig":"MEYCIQDSksGf29RfYj0p45DQ762MvO1oJrY+S6gefL6hXhgOvQIhAO21e+Cu3GAfKm6Ky0VR7Nax6TTeMrsVtfIiYtcc4L5B","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752154},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d40b386ed538b0ec8a75480e2380b5ebb0ca1f9e","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-19T15:18:20.306Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.86","@balena/jellyfish-metrics":"^1.0.346","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.0","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.3","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.1-renovate-major-json-schema-org-d40b386ed538b0ec8a75480e2380b5ebb0ca1f9e_1634656890570_0.7704639199079943","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.1-renovate-internal-patch-f763638cb13d1039b947c08163b2a2c74bce5e93":{"name":"@balena/jellyfish-core","version":"7.0.1-renovate-internal-patch-f763638cb13d1039b947c08163b2a2c74bce5e93","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.1-renovate-internal-patch-f763638cb13d1039b947c08163b2a2c74bce5e93","maintainers":[{"name":"pranavbalena","email":"pranav@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0b8303f45fbf4143732d98fffdacf2bcdaa9b1c4","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.1-renovate-internal-patch-f763638cb13d1039b947c08163b2a2c74bce5e93.tgz","fileCount":205,"integrity":"sha512-ZTtSpYuSH/EwK84vBWMlMicHlT4u7NDKkaBC+8Xkoqwta4EejQ7ZUuKHMqnuy0043Xpdg+WzuS78gkBhEG1HqA==","signatures":[{"sig":"MEYCIQDqi32jc3Q+OeZ7lkRsce+Va63oUn8xDjuYEamnpbGc/QIhAMP4WnUdXWqD1lL0+Hk3+tJrXwfH+iVctHdMgg9CSnpd","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752154},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f763638cb13d1039b947c08163b2a2c74bce5e93","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-20T09:04:30.392Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.87","@balena/jellyfish-metrics":"^1.0.346","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.0","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.3","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.1-renovate-internal-patch-f763638cb13d1039b947c08163b2a2c74bce5e93_1634720899488_0.6676390820640457","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.1":{"name":"@balena/jellyfish-core","version":"7.0.1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.1","maintainers":[{"name":"pranavbalena","email":"pranav@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1dce583547122978c2f0386021373f742de519c8","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.1.tgz","fileCount":205,"integrity":"sha512-cScm4wPljGQpH3uvwHrahxNjuBqeqD4tbtZnBaV6j5xijAg/NYxxV+S/41/bfUUbigVKloXYZbxYPzAr/D5ZIA==","signatures":[{"sig":"MEUCIQCDBAVtN9T7jmZKFyBtPY3DBe4Gyv3RxZ4pOYKblY8xSgIgScT0nzO2WNbsKB71MQws/rFMtpwFZen14fOtktOp7rE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752089},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2c4008c08c80bc4f508b4e20c057076be3ff69dd","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-20T09:12:40.325Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.87","@balena/jellyfish-metrics":"^1.0.346","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.5"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.0","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.3","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.1_1634721286589_0.34585065304206","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.2-renovate-major-json-schema-org-bf7371631582ca0bbe4b01833ed7b4e28251d669":{"name":"@balena/jellyfish-core","version":"7.0.2-renovate-major-json-schema-org-bf7371631582ca0bbe4b01833ed7b4e28251d669","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.2-renovate-major-json-schema-org-bf7371631582ca0bbe4b01833ed7b4e28251d669","maintainers":[{"name":"pranavbalena","email":"pranav@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0bfa9dc2bf5674d451c767d2515bfe19bdcd7c36","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.2-renovate-major-json-schema-org-bf7371631582ca0bbe4b01833ed7b4e28251d669.tgz","fileCount":205,"integrity":"sha512-T8So396xAw3ISM8/5czD2Sz5wAeKV5Sxu78bx8LJhbOkyHxN2htM+LE/UYnWBf5a6bDNwMOlqALmi10NU89WXA==","signatures":[{"sig":"MEYCIQCEifAJ+GerPtoR56y7s/oY9h7DJo2NPIbnc4yMoz9BmwIhAMhB7CxMLCyxw7KIr3u27RMWA1kz5v3sEObBZ6+iOWa1","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752256},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"bf7371631582ca0bbe4b01833ed7b4e28251d669","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-20T09:18:59.405Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.87","@balena/jellyfish-metrics":"^1.0.346","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.0","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.3","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.2-renovate-major-json-schema-org-bf7371631582ca0bbe4b01833ed7b4e28251d669_1634721719326_0.9033783815879222","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.2-renovate-internal-patch-e9623770c12535499a49ef8fc3f6cc1e7f3c953b":{"name":"@balena/jellyfish-core","version":"7.0.2-renovate-internal-patch-e9623770c12535499a49ef8fc3f6cc1e7f3c953b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.2-renovate-internal-patch-e9623770c12535499a49ef8fc3f6cc1e7f3c953b","maintainers":[{"name":"pranavbalena","email":"pranav@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"118e416a49a11d053dd7a5db7a0927abd9ebdce5","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.2-renovate-internal-patch-e9623770c12535499a49ef8fc3f6cc1e7f3c953b.tgz","fileCount":205,"integrity":"sha512-lBvW9MrQ/aiRQJEmY5pyEVE021o4wvU35slgy8JxA1Tw+wCUmjyyNfH8tjrZQ1sdGz6FpFKqr53tntV5AmYJ9w==","signatures":[{"sig":"MEUCIC9W4IfXONbQNv/BoZoZO9BMdHUFVDuIhfxhZNLSejF6AiEAx9cEBRO+Z++ggwfLtzLk6mrYhXsvx0Q5KaO0zBYI04U=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752258},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e9623770c12535499a49ef8fc3f6cc1e7f3c953b","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-20T09:23:51.292Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.87","@balena/jellyfish-metrics":"^1.0.347","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.0","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.3","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.2-renovate-internal-patch-e9623770c12535499a49ef8fc3f6cc1e7f3c953b_1634722070495_0.6606430597450101","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.2":{"name":"@balena/jellyfish-core","version":"7.0.2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.2","maintainers":[{"name":"pranavbalena","email":"pranav@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4e7e0c5bb99fd867b1a1d32545d3e434c1e832a9","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.2.tgz","fileCount":205,"integrity":"sha512-yx7imGVfDlKK+fhb+E4MunfO9DStETVH/oe7etKwltOd/yumiF5g+qqjIFy/LizdY90v0ZKWYRHmXaD1FAfA5w==","signatures":[{"sig":"MEYCIQC+cNF29FlpfSQYvlO4kKyFF39mpz4wG7jr8s1WDfgBRgIhAMnTY4y3liqMNKxN4qM4PGKqwIS2R/gNYZvZsm7Ix88x","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752193},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c4a69c937eb849d63b09329a69b218f13c49cac2","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-20T09:32:59.953Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.87","@balena/jellyfish-metrics":"^1.0.347","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.5"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.0","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.3","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.2_1634722498891_0.47933072643966956","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.3-renovate-major-json-schema-org-ce15d5daf6eb05d40a7f3778f520242d941a0526":{"name":"@balena/jellyfish-core","version":"7.0.3-renovate-major-json-schema-org-ce15d5daf6eb05d40a7f3778f520242d941a0526","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.3-renovate-major-json-schema-org-ce15d5daf6eb05d40a7f3778f520242d941a0526","maintainers":[{"name":"pranavbalena","email":"pranav@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b0f713aae5d8dfe7c6a4bdfd6001bb80dde03cac","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.3-renovate-major-json-schema-org-ce15d5daf6eb05d40a7f3778f520242d941a0526.tgz","fileCount":205,"integrity":"sha512-QJi3/ueV89N/Byx1hMi5RJQPQGt9fsKYG2CPBkaqHBHlNkLHEK1OHLuSNvyzefK0BEliEh+yn++JGcQuNdWPkA==","signatures":[{"sig":"MEUCIQD5zWBuo5a0r/sAmdqb8MhnwAKvtuCnitNbPPDWLqAmfgIgC2XzkbVj9IkZhmBhbeHJM9DuBaVTwitLaCX28OmLx2U=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752360},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ce15d5daf6eb05d40a7f3778f520242d941a0526","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-20T09:35:50.547Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.87","@balena/jellyfish-metrics":"^1.0.347","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.0","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.3","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.3-renovate-major-json-schema-org-ce15d5daf6eb05d40a7f3778f520242d941a0526_1634722800790_0.05641169773112398","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.0-joshbwlng-index-locks-a1a70527ce64507603c3f7c151ef987fd15ac836":{"name":"@balena/jellyfish-core","version":"8.0.0-joshbwlng-index-locks-a1a70527ce64507603c3f7c151ef987fd15ac836","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.0-joshbwlng-index-locks-a1a70527ce64507603c3f7c151ef987fd15ac836","maintainers":[{"name":"pranavbalena","email":"pranav@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3ac247408cd42945bf5ef1a480b754b75e36aed3","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.0-joshbwlng-index-locks-a1a70527ce64507603c3f7c151ef987fd15ac836.tgz","fileCount":205,"integrity":"sha512-KFIJLQidOiFtzup6MCqpmFLU7EITQG7+Vagvu0VM4istIRKCJGqlx1hjdxf9KSpP6bzQT23L3cp3sd+0lBDwZg==","signatures":[{"sig":"MEYCIQCbNPQ4wtfTqRwluXpPqkyRSS2bcJoRzLUOZCuUOmprxgIhAJPWVfK7aadhv0Hq8/jCH0Bxq4arJtoKmRI26n43Criy","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":757395},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a1a70527ce64507603c3f7c151ef987fd15ac836","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-22T03:58:09.317Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.86","@balena/jellyfish-metrics":"^1.0.346","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.0","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.3","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.0-joshbwlng-index-locks-a1a70527ce64507603c3f7c151ef987fd15ac836_1634875321589_0.3719761320396058","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.0-joshbwlng-index-locks-a2ea7cdc1630e65cb6a04892fc4eaada356cc1f7":{"name":"@balena/jellyfish-core","version":"8.0.0-joshbwlng-index-locks-a2ea7cdc1630e65cb6a04892fc4eaada356cc1f7","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.0-joshbwlng-index-locks-a2ea7cdc1630e65cb6a04892fc4eaada356cc1f7","maintainers":[{"name":"pranavbalena","email":"pranav@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f383ac41a3f8f3d412c25acad545e8f2ac66c47e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.0-joshbwlng-index-locks-a2ea7cdc1630e65cb6a04892fc4eaada356cc1f7.tgz","fileCount":205,"integrity":"sha512-nO5K8iFt0c9e8ZLBseOnWOQDOK0YL5ZAps6mNfoKhFSm6ykhrRiSGmZ0Dw0JXaGAF6vle56N5M3l192+736zvA==","signatures":[{"sig":"MEQCIGHCfvvhuOU1UrDPvFPM104kGhm7O2VK7m3QRtk0fbvWAiBHacEE2hVxmiD0swloqmBRZJYd/Ab/za7Ilc19PrxGoA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":757444},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a2ea7cdc1630e65cb6a04892fc4eaada356cc1f7","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-22T04:24:09.192Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.86","@balena/jellyfish-metrics":"^1.0.346","@balena/jellyfish-environment":"^5.0.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.0","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.3","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.0-joshbwlng-index-locks-a2ea7cdc1630e65cb6a04892fc4eaada356cc1f7_1634876810777_0.8209215987294087","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.3-repo-sync-jellyfish-config-default-3ee62d67e80d29ec83c39b963e16ad333b40cd82":{"name":"@balena/jellyfish-core","version":"7.0.3-repo-sync-jellyfish-config-default-3ee62d67e80d29ec83c39b963e16ad333b40cd82","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.3-repo-sync-jellyfish-config-default-3ee62d67e80d29ec83c39b963e16ad333b40cd82","maintainers":[{"name":"pranavbalena","email":"pranav@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"03c3f7e5fb18d9e1e678b163c60b6daf96046794","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.3-repo-sync-jellyfish-config-default-3ee62d67e80d29ec83c39b963e16ad333b40cd82.tgz","fileCount":205,"integrity":"sha512-vDU4pfWj6LqNPba4y/Z3aH9nW6m2FnvJ+XTP1cGxh80ULUR7pzDo8WKvSc7L8lGHVUd4zLMyuBoDdgsbiOOp1g==","signatures":[{"sig":"MEUCIQDZ4mBvvemJD8MFMZRBeNvzBzRKtEr5dcVmnnyCt8fyewIgSM77E15QptMf3RBV7/CYq3OlO9qex8IZQwctNtX7VpI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752360},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"3ee62d67e80d29ec83c39b963e16ad333b40cd82","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-23T01:06:29.013Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.87","@balena/jellyfish-metrics":"^1.0.347","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.0","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.3","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.3-repo-sync-jellyfish-config-default-3ee62d67e80d29ec83c39b963e16ad333b40cd82_1634951441904_0.04825569878488856","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.3-renovate-internal-patch-cdb8b825d894d847c3723af7ddf1034a54b4a43b":{"name":"@balena/jellyfish-core","version":"7.0.3-renovate-internal-patch-cdb8b825d894d847c3723af7ddf1034a54b4a43b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.3-renovate-internal-patch-cdb8b825d894d847c3723af7ddf1034a54b4a43b","maintainers":[{"name":"pranavbalena","email":"pranav@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"80acfe91a5681d966bff13f08cce9a71721c9db9","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.3-renovate-internal-patch-cdb8b825d894d847c3723af7ddf1034a54b4a43b.tgz","fileCount":205,"integrity":"sha512-4nJ6qv/spV08Yi3kHgts3c3n/mevz38UcM7qTJCA/iOTliJqozlX+/yCM5ftqq6Q39xBkpr08LKEj5q7eekTlg==","signatures":[{"sig":"MEUCIQDw0czFxEMuHmj+plxjgicLo5tLLE8AOuSxVx2krLNu6wIgZb7pZ/jt9bycGrsqW5zAuz7HApyMDFTyOS6NBaxrcNs=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752328},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"cdb8b825d894d847c3723af7ddf1034a54b4a43b","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-23T02:23:20.748Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.89","@balena/jellyfish-metrics":"^1.0.348","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.0","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.3","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.3-renovate-internal-patch-cdb8b825d894d847c3723af7ddf1034a54b4a43b_1634955969714_0.48899091568542064","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.3":{"name":"@balena/jellyfish-core","version":"7.0.3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.3","maintainers":[{"name":"pranavbalena","email":"pranav@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2ef88c8b7aaf6329bc3301b41087225e05facb34","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.3.tgz","fileCount":205,"integrity":"sha512-pJkl22ej2Rtdvemml0q4uWFZFA7F0DtJSTQoW2DHXYwdKNjexqHy6ueOzG9I5bLMH0mMbd18gWkfnvt+mHYbNA==","signatures":[{"sig":"MEQCIA4OVhNZZn4tcp9OTiln3DFlEL49Ij0NP7+GHODnG+40AiBgf3iTuDrqkl8OnoKSi8fGuw65bSnLcgP60wqUtwxRpQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752263},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"564d462dc70037b65bac994449f1dcb9f794496e","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-23T02:31:12.498Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.89","@balena/jellyfish-metrics":"^1.0.348","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.5"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.0","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.3","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.3_1634956386706_0.24356812959203888","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.4-renovate-internal-patch-f6c5c3141b0b8972d3d80429c6433eb6a9acd57c":{"name":"@balena/jellyfish-core","version":"7.0.4-renovate-internal-patch-f6c5c3141b0b8972d3d80429c6433eb6a9acd57c","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.4-renovate-internal-patch-f6c5c3141b0b8972d3d80429c6433eb6a9acd57c","maintainers":[{"name":"pranavbalena","email":"pranav@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6a26e341edb0d007422922930a7ff275e094a493","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.4-renovate-internal-patch-f6c5c3141b0b8972d3d80429c6433eb6a9acd57c.tgz","fileCount":205,"integrity":"sha512-QPijF+npSpjyndeGlV5372aS0k8Z07QfOv/K02DNHlMKCV7ps7De40uosDQpHvr917MUVBaCdNU3LbEdGYqYmQ==","signatures":[{"sig":"MEUCIQCbe9SorTzAKBLBU8IC3BM3d+Fpe4cxFrwrtnkM3F5y1gIgVilfUNehxbFXyDT8uwXbyJQ7OomPHI5FHL0OYj2Er68=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752434},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f6c5c3141b0b8972d3d80429c6433eb6a9acd57c","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-23T02:34:30.355Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.89","@balena/jellyfish-metrics":"^1.0.348","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.0","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.3","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.4-renovate-internal-patch-f6c5c3141b0b8972d3d80429c6433eb6a9acd57c_1634956690063_0.6177897715368663","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.4-renovate-external-non-major-3ae80e793e1cdf53204cdfa9cfa36f5881699685":{"name":"@balena/jellyfish-core","version":"7.0.4-renovate-external-non-major-3ae80e793e1cdf53204cdfa9cfa36f5881699685","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.4-renovate-external-non-major-3ae80e793e1cdf53204cdfa9cfa36f5881699685","maintainers":[{"name":"pranavbalena","email":"pranav@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"eaa971a3bafff25cde0b8c18fa602ed602c53b9e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.4-renovate-external-non-major-3ae80e793e1cdf53204cdfa9cfa36f5881699685.tgz","fileCount":205,"integrity":"sha512-o7mT2fb9nCFqDxL4JOjVPcLpNdPedjvLMC9TBEHKce0dVhSuCtFxJ0EMZ9gigVPjTiH5PFt3JLM6unT8U5+fSA==","signatures":[{"sig":"MEUCIQCy8VZXI9S1KhLQmBIWkn/V8ApjL5FiV/DCy8QBqtI9BQIgFn8OOxA9KvfsIohKXtS/6Qif99qSjlWi+N32Fy/NPsU=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752406},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"3ae80e793e1cdf53204cdfa9cfa36f5881699685","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-23T02:35:12.556Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.89","@balena/jellyfish-metrics":"^1.0.348","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.3","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.4-renovate-external-non-major-3ae80e793e1cdf53204cdfa9cfa36f5881699685_1634956762532_0.3160243011984527","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.4-renovate-major-json-schema-org-3e0df8414e65af8c3de9f751f45ff305a9b0f195":{"name":"@balena/jellyfish-core","version":"7.0.4-renovate-major-json-schema-org-3e0df8414e65af8c3de9f751f45ff305a9b0f195","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.4-renovate-major-json-schema-org-3e0df8414e65af8c3de9f751f45ff305a9b0f195","maintainers":[{"name":"pranavbalena","email":"pranav@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"121082748e52a49c3f29f2a51bf776face89d814","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.4-renovate-major-json-schema-org-3e0df8414e65af8c3de9f751f45ff305a9b0f195.tgz","fileCount":205,"integrity":"sha512-KESREmRbrK87oFvNRp9eAxLwIYJGcHBh+h6a3dI/nBhfS/vMZ0D1xgZleVqpc+BrfISHOt72gtmnWnm2RyRFFg==","signatures":[{"sig":"MEYCIQDWIesP2o7FwoNp78ggQXGPvIvCTD27G26Zxih11/legQIhAObbrW72HbsbHTYGJEz8xhKGjlZXQiRcKF2wgBriigBc","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752430},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"3e0df8414e65af8c3de9f751f45ff305a9b0f195","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-23T02:38:49.985Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.89","@balena/jellyfish-metrics":"^1.0.348","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.0","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.3","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.4-renovate-major-json-schema-org-3e0df8414e65af8c3de9f751f45ff305a9b0f195_1634956905614_0.6882486848525471","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.4":{"name":"@balena/jellyfish-core","version":"7.0.4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.4","maintainers":[{"name":"pranavbalena","email":"pranav@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0a1074ad3cc3a4e3e4c60c6ef9ca3291c1069ef8","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.4.tgz","fileCount":205,"integrity":"sha512-quJIWWiE44V6GRdsvyfyGz6oJbSMis2W//nmEQa6yLlv1S00Gj5g5frkP5lKxYGn7kfCAPYUON9ExIa/vWvHww==","signatures":[{"sig":"MEUCIGRR1Cv4qE7Dzaax5S2VaUD+bM8LoSBEj0D5hPSKyh07AiEAhmXsN62BVCotcnQUHRSIjLPx6xb+sUSkwAs5sKJvBjo=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752369},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"dbad9a2c92b60870e167f4909b9b868a7ba42507","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-23T02:46:19.678Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.89","@balena/jellyfish-metrics":"^1.0.348","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.6"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.0","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.3","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.4_1634957291865_0.14585770382151497","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.5-renovate-external-non-major-b1549ab311a7d83b4770ce20f2bf09be4ebfdd4c":{"name":"@balena/jellyfish-core","version":"7.0.5-renovate-external-non-major-b1549ab311a7d83b4770ce20f2bf09be4ebfdd4c","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.5-renovate-external-non-major-b1549ab311a7d83b4770ce20f2bf09be4ebfdd4c","maintainers":[{"name":"pranavbalena","email":"pranav@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8b1875db17cefa05cf17b58ed04046dadaaad3b7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.5-renovate-external-non-major-b1549ab311a7d83b4770ce20f2bf09be4ebfdd4c.tgz","fileCount":205,"integrity":"sha512-mGNFMwsPEN2wpPAOJKjwSjc2HWiaaADPUA/LSWWW5ocfc6eBvwZNjYDXmb1jWphDNExX+ja6kALJ33v4/aLm6g==","signatures":[{"sig":"MEUCIQCRF+4vSbUvdlGx+sgKnsM6WcvFMv6F/ca6jUF5HLt2ggIgOoJ+eVJvuqMNgV93SrayDXEL1JDaBDTHZd0Uc0ngzpg=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752512},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b1549ab311a7d83b4770ce20f2bf09be4ebfdd4c","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-23T02:50:10.063Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.89","@balena/jellyfish-metrics":"^1.0.348","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.3","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.5-renovate-external-non-major-b1549ab311a7d83b4770ce20f2bf09be4ebfdd4c_1634957595288_0.6189900666718877","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.5-renovate-major-json-schema-org-817dce5a11fca527f4149c6f11dd9ecf2c098182":{"name":"@balena/jellyfish-core","version":"7.0.5-renovate-major-json-schema-org-817dce5a11fca527f4149c6f11dd9ecf2c098182","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.5-renovate-major-json-schema-org-817dce5a11fca527f4149c6f11dd9ecf2c098182","maintainers":[{"name":"pranavbalena","email":"pranav@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8cbd4d27a97ad4bace9b5609eebf212a7840cd05","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.5-renovate-major-json-schema-org-817dce5a11fca527f4149c6f11dd9ecf2c098182.tgz","fileCount":205,"integrity":"sha512-l/GyWBj3bStns4ghVCEqFS5gFO//CkX4gGrlO5ldnXVPGCac/ic3EkkQaDjjkns9n4lMSS7KK3q+FpOriKxTBQ==","signatures":[{"sig":"MEYCIQC/4gKCpLaQCBmn59B8WEFcKSkKQyuFnaurQoWNsVL4CwIhAIm+i7Ku5Z8UbgnKaz80kiXPepGG1e/DFfHqXtNTWBBU","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752536},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"817dce5a11fca527f4149c6f11dd9ecf2c098182","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-23T02:49:40.292Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.89","@balena/jellyfish-metrics":"^1.0.348","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.0","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.3","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.8","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.5-renovate-major-json-schema-org-817dce5a11fca527f4149c6f11dd9ecf2c098182_1634957608323_0.8838399923636515","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.5":{"name":"@balena/jellyfish-core","version":"7.0.5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.5","maintainers":[{"name":"pranavbalena","email":"pranav@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"78573c7e692ccbb407d1fa0186a087002b2d8aaf","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.5.tgz","fileCount":205,"integrity":"sha512-RxkjTTVpLJu3Rg9MnVqqBin3XRxLBS/3KRnfUk437sFxyanGD088q+rSYE/4CDcBMVjrMdQJi5Rj3I1KvqBFCg==","signatures":[{"sig":"MEUCIQCyCsEUHC7pqC5xGww4vMOJ9hcaDymUeOH08DEkGyakrQIgPoR8k9DW+7JqwjrrX8u/MYFv1KuxWpUV43R2QSXVzWo=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752443},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"17e9026bf99e47515d1aca6f4785c3654ff86c9b","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-23T02:59:50.125Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.89","@balena/jellyfish-metrics":"^1.0.348","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.6"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.3","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.5_1634958109471_0.3135262689547287","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.6-renovate-major-json-schema-org-bdcdc44ef2d4c8cbc7f208c3e781667775eb75e1":{"name":"@balena/jellyfish-core","version":"7.0.6-renovate-major-json-schema-org-bdcdc44ef2d4c8cbc7f208c3e781667775eb75e1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.6-renovate-major-json-schema-org-bdcdc44ef2d4c8cbc7f208c3e781667775eb75e1","maintainers":[{"name":"pranavbalena","email":"pranav@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1aa2c3de0e2a802c399a482f2c03b7908899f083","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.6-renovate-major-json-schema-org-bdcdc44ef2d4c8cbc7f208c3e781667775eb75e1.tgz","fileCount":205,"integrity":"sha512-N5TgasWCeSf4mvTFHO8Oo64sopAXHWpmyDdjsMZIpnUPuNw8/AB+z1LThtJS9jKK0LkT8g8J5Ya+yqjOjJ4ZjQ==","signatures":[{"sig":"MEQCIEvXJ6So8amFIqSCvJJbyVQzWoQYEhrGZ+hc87PJyMuTAiARWtLk75dayOuUJ+7JaNG2PsjQgKqC29geHTl/3rS5XQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752610},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"bdcdc44ef2d4c8cbc7f208c3e781667775eb75e1","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-23T03:03:19.797Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.89","@balena/jellyfish-metrics":"^1.0.348","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.3","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.6-renovate-major-json-schema-org-bdcdc44ef2d4c8cbc7f208c3e781667775eb75e1_1634958438540_0.29922378800557103","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.6-renovate-internal-patch-a8ad495b7ae069251d8c5279e5209226b7715456":{"name":"@balena/jellyfish-core","version":"7.0.6-renovate-internal-patch-a8ad495b7ae069251d8c5279e5209226b7715456","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.6-renovate-internal-patch-a8ad495b7ae069251d8c5279e5209226b7715456","maintainers":[{"name":"pranavbalena","email":"pranav@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b33f006a806e3e04956235de33644a6c887ae2b1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.6-renovate-internal-patch-a8ad495b7ae069251d8c5279e5209226b7715456.tgz","fileCount":205,"integrity":"sha512-ADGMIrF5udnp1tYi54nDB4PJg78DjmPfoc3GMsvhY4CkcjKBToA9owr3ak0QYHjk/s6FKm4ASA3qt9mEMo6ZFg==","signatures":[{"sig":"MEYCIQDuPkedjIcu7OTLmoU3sdGsZ7V2ygfZKAXU639ww4SstQIhAKEVL31YZGbjjOiiWDKWKuhqIArWf4okQBzIENIpv7UY","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752610},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a8ad495b7ae069251d8c5279e5209226b7715456","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-23T04:44:19.139Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.90","@balena/jellyfish-metrics":"^1.0.348","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.3","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.6-renovate-internal-patch-a8ad495b7ae069251d8c5279e5209226b7715456_1634964485403_0.5507812927653033","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.6":{"name":"@balena/jellyfish-core","version":"7.0.6","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.6","maintainers":[{"name":"pranavbalena","email":"pranav@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8d2368eb4f53a90eb3505aff0c3e5f6dec1f2be6","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.6.tgz","fileCount":205,"integrity":"sha512-OdLCmrv+84k7zr0zzipK+pY5q3UGWAd96OtThB6Xp3SQT34dswXLFnEEIETtRCsZRrB9u2S4hjJfoeOeSrUlWw==","signatures":[{"sig":"MEQCIA8wHEa/4occNGyOjauL9idhtz7mNCxehcj+UzemTJUsAiAb7HtNlPqFEE1UQOYtuF50/HNbDdQ6KS6cfaxG+0ULyQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752545},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"802ea605964724e25460ac388a4c12962765fa9a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-23T04:54:49.982Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.90","@balena/jellyfish-metrics":"^1.0.348","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.6"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.3","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.6_1634965005948_0.9752593182728448","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.7-renovate-major-json-schema-org-942d00c3e920a357516cd6b46b5a2e4ca6c8dddb":{"name":"@balena/jellyfish-core","version":"7.0.7-renovate-major-json-schema-org-942d00c3e920a357516cd6b46b5a2e4ca6c8dddb","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.7-renovate-major-json-schema-org-942d00c3e920a357516cd6b46b5a2e4ca6c8dddb","maintainers":[{"name":"pranavbalena","email":"pranav@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"335e8af2001cb06a3e9fbc99148d36a1a90a9c7b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.7-renovate-major-json-schema-org-942d00c3e920a357516cd6b46b5a2e4ca6c8dddb.tgz","fileCount":205,"integrity":"sha512-tyYISEgO7iOFtL1+9RVE6ZOMJNOhYxTaOwccONh/hg4t2HQAQ39BJTETk3dbPSAv2T4ucxMy5LxTll0OzbHFmA==","signatures":[{"sig":"MEYCIQDWoJeT6n16Hvb2DkglgLlYSPYTU4KUKTT9gbCFyeUccgIhAOiF5kz58OFSGB5CiJ9hMsp3B5kX/B2iHqy7jvZFHO1B","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752712},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"942d00c3e920a357516cd6b46b5a2e4ca6c8dddb","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-23T04:56:05.875Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.90","@balena/jellyfish-metrics":"^1.0.348","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.3","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.7-renovate-major-json-schema-org-942d00c3e920a357516cd6b46b5a2e4ca6c8dddb_1634965236726_0.23256747151397694","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.7-renovate-internal-patch-e9c9d12f5d15f5dc15392044286c8bb43795eb8b":{"name":"@balena/jellyfish-core","version":"7.0.7-renovate-internal-patch-e9c9d12f5d15f5dc15392044286c8bb43795eb8b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.7-renovate-internal-patch-e9c9d12f5d15f5dc15392044286c8bb43795eb8b","maintainers":[{"name":"pranavbalena","email":"pranav@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"62415218350b42d915421e6a7e9889a2ac922a20","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.7-renovate-internal-patch-e9c9d12f5d15f5dc15392044286c8bb43795eb8b.tgz","fileCount":205,"integrity":"sha512-tCZX5+bXeg4RjprspWGaT6kWDL9vfHc6oFa7fUD25P2byHs+XjFgTkndTWfjKsqT3gLs2B2CXs55jxHgHA+GVQ==","signatures":[{"sig":"MEQCIF3/6oEQbxt/rT8eyuDNJGpPy0KEnahVke/oULAGSl6oAiAg9fJmBlDSI1COn1p4GE79PjjphbiCWQ7z0Uc+ht20EA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752680},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e9c9d12f5d15f5dc15392044286c8bb43795eb8b","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-23T07:36:34.368Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.90","@balena/jellyfish-metrics":"^1.0.349","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.3","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.7-renovate-internal-patch-e9c9d12f5d15f5dc15392044286c8bb43795eb8b_1634974852019_0.5862142887257824","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.7":{"name":"@balena/jellyfish-core","version":"7.0.7","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.7","maintainers":[{"name":"pranavbalena","email":"pranav@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c2a6bc016fb528373adf974590201491046b84b7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.7.tgz","fileCount":205,"integrity":"sha512-C9k1TblFrrdDdmHgGoYmKcY9ZxQgz3NM6bEs2CM+iUiigDHHcbebvpZscKnRjk+xYH+BMkXO9VASR9d8xa1rzg==","signatures":[{"sig":"MEUCIQDGUxAkP7duCAy5byXspKkiWLgr+7HAWE3k4NIr+t3RxAIgcZuGqBZcHXbFIUAwuzB3bHVC5Gj7PvlhX55O+r70Ja0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752615},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"9c275590b7c3b1b85e6ef4d61598cde5e445f5e8","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-23T07:45:59.334Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.90","@balena/jellyfish-metrics":"^1.0.349","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.7"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.3","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.7_1634975266599_0.02774295046365216","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.8-renovate-major-json-schema-org-7f2fc071646b340243054b07b7e56ed05682bbc1":{"name":"@balena/jellyfish-core","version":"7.0.8-renovate-major-json-schema-org-7f2fc071646b340243054b07b7e56ed05682bbc1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.8-renovate-major-json-schema-org-7f2fc071646b340243054b07b7e56ed05682bbc1","maintainers":[{"name":"pranavbalena","email":"pranav@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8b8d4ff8807fc170d03778ff6f70d913cb4a1117","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.8-renovate-major-json-schema-org-7f2fc071646b340243054b07b7e56ed05682bbc1.tgz","fileCount":205,"integrity":"sha512-tQdqSilc+XjfgXCH/EZ0CTLtHuun4/yKm3XEqEtE9Y4uYxklunNt94T0lDkBCtnLMqr3vEHlbBmWP21VGUH+NA==","signatures":[{"sig":"MEUCIQCwu5sgYmsn/CxgEppYDxpo/sOrQEgYsvSJdfg0FRogZgIgMIRVuocFTS65gweg54Wtemd5wqDQIDD5+vfnvBG59Os=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752782},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7f2fc071646b340243054b07b7e56ed05682bbc1","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-23T07:51:30.553Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.90","@balena/jellyfish-metrics":"^1.0.349","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.3","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.8-renovate-major-json-schema-org-7f2fc071646b340243054b07b7e56ed05682bbc1_1634975715011_0.2690940392596324","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.8-repo-sync-jellyfish-config-default-42de0586e3a4ec437b794f8bde03872af967e10f":{"name":"@balena/jellyfish-core","version":"7.0.8-repo-sync-jellyfish-config-default-42de0586e3a4ec437b794f8bde03872af967e10f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.8-repo-sync-jellyfish-config-default-42de0586e3a4ec437b794f8bde03872af967e10f","maintainers":[{"name":"pranavbalena","email":"pranav@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9f2556a82676b01dff4997e6c1fef7ea96376b5a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.8-repo-sync-jellyfish-config-default-42de0586e3a4ec437b794f8bde03872af967e10f.tgz","fileCount":205,"integrity":"sha512-Ll0K/M+HM+MesOhltOyjvToNw/RlcHpZdqZrt9cLgMnJnisDReJIiWcOduIODYfB7lHUD6A/zSv1ikfmDzV6yw==","signatures":[{"sig":"MEYCIQCwdPViPRVRO1MF3VyIsUlvBDGktpl1ytATDxmCad9o+AIhAPMeLiFbGUbE8ND3pOF78p+AnZlUbCLUqrYABQhSNZts","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752782},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"42de0586e3a4ec437b794f8bde03872af967e10f","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-23T08:03:29.835Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.90","@balena/jellyfish-metrics":"^1.0.349","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.3","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.8-repo-sync-jellyfish-config-default-42de0586e3a4ec437b794f8bde03872af967e10f_1634976380115_0.1440087102120502","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.8":{"name":"@balena/jellyfish-core","version":"7.0.8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.8","maintainers":[{"name":"pranavbalena","email":"pranav@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"82b6956fb0e9b2501edf6779dec5a74d67ddb3e1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.8.tgz","fileCount":205,"integrity":"sha512-DaE5yamD7ah4PHkQ1WSsClwCpU9qC93u9EHjtvl63LE6K+EQ3M2SuEwt/MbaAIBmD5k6kM2J9M0IELItBP7oqA==","signatures":[{"sig":"MEQCIEb5JswcgjaKO5z865Ua5mYXqg86oXj2wcJYxI1JMjMrAiBV8zmgL+h/vCHecfrf8126my+AOrYeWWc8dWyu0MULWA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752706},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"402693fb0dafdeca02007549b75f890fc3d0b1c3","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-23T08:10:59.956Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.90","@balena/jellyfish-metrics":"^1.0.349","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.7"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.3","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.8_1634976768645_0.7525293264180934","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.9-renovate-major-json-schema-org-48ba4c8267e72db9b193b7771e6e574e66170448":{"name":"@balena/jellyfish-core","version":"7.0.9-renovate-major-json-schema-org-48ba4c8267e72db9b193b7771e6e574e66170448","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.9-renovate-major-json-schema-org-48ba4c8267e72db9b193b7771e6e574e66170448","maintainers":[{"name":"pranavbalena","email":"pranav@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"45c27eb452e6f2cb47b10977a9df075d5a8df2f3","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.9-renovate-major-json-schema-org-48ba4c8267e72db9b193b7771e6e574e66170448.tgz","fileCount":205,"integrity":"sha512-mFBTK3W9lNLAm2qLngZDb9NSJLBN7Q/rEqBitZt0vAqQdFczbB2+BkItarI4LolWWrTytGNWq/ofPJo5MsrF/g==","signatures":[{"sig":"MEUCIQDYZaU+Pavwo5Y/LdPsb1nan/149YBpkPiaAAcpXu8hLgIgGLqBJAYANjKgQ3GRGiCl6MDoCY3NnLnsorR0C7Zq7HY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752873},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"48ba4c8267e72db9b193b7771e6e574e66170448","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-23T08:15:59.937Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.90","@balena/jellyfish-metrics":"^1.0.349","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.3","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.9-renovate-major-json-schema-org-48ba4c8267e72db9b193b7771e6e574e66170448_1634977183339_0.9185864930393794","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.9-renovate-internal-patch-d1b1769d82a0c009c471aa523bca9cc42d94024b":{"name":"@balena/jellyfish-core","version":"7.0.9-renovate-internal-patch-d1b1769d82a0c009c471aa523bca9cc42d94024b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.9-renovate-internal-patch-d1b1769d82a0c009c471aa523bca9cc42d94024b","maintainers":[{"name":"pranavbalena","email":"pranav@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"cc803318fd19cf0e5d417139345cc50589c9a60c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.9-renovate-internal-patch-d1b1769d82a0c009c471aa523bca9cc42d94024b.tgz","fileCount":205,"integrity":"sha512-LQxJAOxKJACBVj1xK+8v6XVVoh+Rpu6ZwGQUSch6hHP9in7Hl+OfJvg6wX3nXDWcdmMi7vyzyuLoRJfp8s/U3g==","signatures":[{"sig":"MEUCIQCXd2alxWG67DXDOlYcl2HXLF3dzfOO7SUlaY7V6SH73gIgGwI0qL/3sexKS2MoJR7ZT+yXEJ8ZvMpKDX6/XmXyaLw=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752841},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d1b1769d82a0c009c471aa523bca9cc42d94024b","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-23T10:27:39.851Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.91","@balena/jellyfish-metrics":"^1.0.350","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.3","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.9-renovate-internal-patch-d1b1769d82a0c009c471aa523bca9cc42d94024b_1634985081875_0.12111220574455617","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.9":{"name":"@balena/jellyfish-core","version":"7.0.9","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.9","maintainers":[{"name":"pranavbalena","email":"pranav@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"97d36c61abddf3e83322509f5f087046dac0ecca","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.9.tgz","fileCount":205,"integrity":"sha512-1/7truiqyVx89AAXIbg4gwoQKgKeIQAynTkFTonzmuDbmgRUZOGLkOxOQFdyO0GdRbXZF4v5j43Y84HtkX+Yiw==","signatures":[{"sig":"MEQCIHBgbkz1PeKMfIAG7SiWRFig2v7GfJ2Gw3TNhlZ/jw/KAiBRkhFgRjK+EWdeg+UoVtrh6zubVg0lnIp/7BKgUJhFIQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752776},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"922352be462f68acab8c3e3a981c2bcc21f3eab3","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-23T10:36:49.715Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.91","@balena/jellyfish-metrics":"^1.0.350","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.7"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.3","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.9_1634985520496_0.5173824601520529","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.10-renovate-major-json-schema-org-23a08f23529fe2c72f563a0a0f67bb0ae332bc81":{"name":"@balena/jellyfish-core","version":"7.0.10-renovate-major-json-schema-org-23a08f23529fe2c72f563a0a0f67bb0ae332bc81","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.10-renovate-major-json-schema-org-23a08f23529fe2c72f563a0a0f67bb0ae332bc81","maintainers":[{"name":"pranavbalena","email":"pranav@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"30978c268ecc6e51c1f79a1173ca4a6dbeb9eaef","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.10-renovate-major-json-schema-org-23a08f23529fe2c72f563a0a0f67bb0ae332bc81.tgz","fileCount":205,"integrity":"sha512-ZVlqn/jXdO9U4PbeDr7z/LjUh1fKqFfJ23GmSsBNVVy4Q7ip/YdlAFIJghlsihiXyiGTmMV4e3HGUr3ixh4gFA==","signatures":[{"sig":"MEUCIAUaxhsAPeVcI0XjLYgiOeJQFawpnzrI+IM6DOc4AnDkAiEA9Ot2EJ+dF4QyMwD+K3wraKejnJ8ZEPmiFIm1is3tOC8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752945},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"23a08f23529fe2c72f563a0a0f67bb0ae332bc81","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-23T10:38:40.224Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.91","@balena/jellyfish-metrics":"^1.0.350","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.3","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.10-renovate-major-json-schema-org-23a08f23529fe2c72f563a0a0f67bb0ae332bc81_1634985759368_0.5792022471339768","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.0-joshbwlng-index-locks-0a40c95b628a0332a8d6f24b6c8675904dce85a0":{"name":"@balena/jellyfish-core","version":"8.0.0-joshbwlng-index-locks-0a40c95b628a0332a8d6f24b6c8675904dce85a0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.0-joshbwlng-index-locks-0a40c95b628a0332a8d6f24b6c8675904dce85a0","maintainers":[{"name":"pranavbalena","email":"pranav@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4588833f03226f56d4265b83c89b569761ea9306","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.0-joshbwlng-index-locks-0a40c95b628a0332a8d6f24b6c8675904dce85a0.tgz","fileCount":205,"integrity":"sha512-gv9B449Kyc01UFETcrO3dc4dsyn7xknyqI4PWql6qx+695pypvbhxNebL/ORgzc3vJ+aSt4Oz79pUO7tJtvdag==","signatures":[{"sig":"MEQCIBm8vNAWr1EMJ47Rm8vlLxJOW0bkflyrO6W7d4rk0BNeAiAyLfqWXIMaZ7WiX3vI9/kY8piALMCIkC6+MVp9LFWLmw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":758196},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0a40c95b628a0332a8d6f24b6c8675904dce85a0","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-23T12:27:29.424Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.91","@balena/jellyfish-metrics":"^1.0.350","@balena/jellyfish-environment":"^5.0.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.3","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.0-joshbwlng-index-locks-0a40c95b628a0332a8d6f24b6c8675904dce85a0_1634992192385_0.6059828080457927","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.10-renovate-external-non-major-2e1a22ec19c855fab1a4f621657b2fbf7292e4dd":{"name":"@balena/jellyfish-core","version":"7.0.10-renovate-external-non-major-2e1a22ec19c855fab1a4f621657b2fbf7292e4dd","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.10-renovate-external-non-major-2e1a22ec19c855fab1a4f621657b2fbf7292e4dd","maintainers":[{"name":"pranavbalena","email":"pranav@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"630c0d673f4016479b774fcd8fe5efe185f7c1f9","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.10-renovate-external-non-major-2e1a22ec19c855fab1a4f621657b2fbf7292e4dd.tgz","fileCount":205,"integrity":"sha512-UUp3mFKlryhqizy4lYTTkPjMMc0mMhskkimQT4PtSNwYQvUqIJ8/7HGEnesKf9Hm7Q/t2iBhA6eafx7q+kG0Ew==","signatures":[{"sig":"MEYCIQDIbXWZSvgojD7oCAEQSmfHz2N4MR+mkYpERgAjEdZ0wgIhAKz+3QABoXQmXHLBmvxXVwaoKm8Yd1jLj567vEdeDTsP","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752936},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2e1a22ec19c855fab1a4f621657b2fbf7292e4dd","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-23T18:34:49.028Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.91","@balena/jellyfish-metrics":"^1.0.350","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.4","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.10-renovate-external-non-major-2e1a22ec19c855fab1a4f621657b2fbf7292e4dd_1635014320377_0.4478467316650203","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.10":{"name":"@balena/jellyfish-core","version":"7.0.10","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.10","maintainers":[{"name":"pranavbalena","email":"pranav@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"56cb1ae99c35f9213de895a650843f4ddfb20021","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.10.tgz","fileCount":205,"integrity":"sha512-/t1YCbEw9pXK9SvNzr0bvJO4uHtL3/FRu1X9ko59/T83jenDzzrCVCDgI4MiXP1WXl7/82GfFFQV+usHSEaV/w==","signatures":[{"sig":"MEYCIQCXCUQAGnbDPfE1iBXD7C+XWdLjGV1Nb5XGMsZBFEu6ZgIhAK4kLeBq92ZNudB1Gr0+YEW58XEJIbZz2MwZ0yOWohnc","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752867},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"20e78ad42a27145391bc6419bd99113af0973ac9","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-23T18:44:49.579Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.91","@balena/jellyfish-metrics":"^1.0.350","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.7"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.4","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.10_1635014804991_0.5105809019356251","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.11-renovate-internal-patch-52335d8111d9f8703dd254e6954302cba4192100":{"name":"@balena/jellyfish-core","version":"7.0.11-renovate-internal-patch-52335d8111d9f8703dd254e6954302cba4192100","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.11-renovate-internal-patch-52335d8111d9f8703dd254e6954302cba4192100","maintainers":[{"name":"pranavbalena","email":"pranav@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4dd97156917df105e088a8760f389ac5380fa809","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.11-renovate-internal-patch-52335d8111d9f8703dd254e6954302cba4192100.tgz","fileCount":205,"integrity":"sha512-aAvHcS5u5Nq92+PmqmPeF4UKLnf8cOW9hgYoRMxUyPcWDH1sZ882QsztxGKB3yP1L5jZeRUtSyNGLMmWSP6juA==","signatures":[{"sig":"MEYCIQDtgwxep4/CaAe9vqxnmEpfB3w3hVUpxKbNDQj5IhlkzAIhAKPqanab9Oute77Nvth+jMyriCjKlh4kyuReCQA7SK7a","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":753035},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"52335d8111d9f8703dd254e6954302cba4192100","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-23T18:49:09.184Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.92","@balena/jellyfish-metrics":"^1.0.350","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.4","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.11-renovate-internal-patch-52335d8111d9f8703dd254e6954302cba4192100_1635015114741_0.995450488186145","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.11-renovate-internal-patch-0fa3bb92cc7371b02be31bd4ebffddeee201699c":{"name":"@balena/jellyfish-core","version":"7.0.11-renovate-internal-patch-0fa3bb92cc7371b02be31bd4ebffddeee201699c","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.11-renovate-internal-patch-0fa3bb92cc7371b02be31bd4ebffddeee201699c","maintainers":[{"name":"pranavbalena","email":"pranav@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a236b889b6f42250159b6a8eca35e4ff1129b977","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.11-renovate-internal-patch-0fa3bb92cc7371b02be31bd4ebffddeee201699c.tgz","fileCount":205,"integrity":"sha512-Eq4AkALDPlvG0uxnDxtSh5gWIQNanYlR4SjZkyac+6FVhs7/BPuCOJirzb98QVtrOKZaVTbt3lUjt/u9igxgkQ==","signatures":[{"sig":"MEUCIQCQBoryN+h8anVckOBRedxJwoFu6Pib3vjL5AVzdvgjVAIgWOIkJWgdxv/x6DOj70AwVAQtjS5BXb72BgZMsMC2O4s=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":753003},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0fa3bb92cc7371b02be31bd4ebffddeee201699c","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-23T18:49:40.772Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.92","@balena/jellyfish-metrics":"^1.0.350","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.4","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.11-renovate-internal-patch-0fa3bb92cc7371b02be31bd4ebffddeee201699c_1635015281340_0.4137124658161617","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.11-renovate-major-json-schema-org-664b9af311715404e4c37a19b841f07e354c5a76":{"name":"@balena/jellyfish-core","version":"7.0.11-renovate-major-json-schema-org-664b9af311715404e4c37a19b841f07e354c5a76","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.11-renovate-major-json-schema-org-664b9af311715404e4c37a19b841f07e354c5a76","maintainers":[{"name":"pranavbalena","email":"pranav@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"41bbe2d0abb876fefe24e467757f08991c463e11","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.11-renovate-major-json-schema-org-664b9af311715404e4c37a19b841f07e354c5a76.tgz","fileCount":205,"integrity":"sha512-aLVg7ESZjBESRUCCukI6epR5spSAV1vTPquV5jSw6GmCMlk0Z06UNFfHvB4oTNSxbqmCDVCT8qGpQ8zexA1KAQ==","signatures":[{"sig":"MEUCIE34EbkCJOhYUjn95YtY96y03q1pINQ5sgHgnPrrupnGAiEAwS4hG4xUwXR8+tVYMzMKll7lzDCdQig7QTpUxuoWkc0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":753035},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"664b9af311715404e4c37a19b841f07e354c5a76","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-23T18:51:09.778Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.91","@balena/jellyfish-metrics":"^1.0.350","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.4","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.11-renovate-major-json-schema-org-664b9af311715404e4c37a19b841f07e354c5a76_1635015303188_0.7279703871927814","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.11-renovate-internal-patch-bba77d2d120b84b81488ebd93dd5874611472706":{"name":"@balena/jellyfish-core","version":"7.0.11-renovate-internal-patch-bba77d2d120b84b81488ebd93dd5874611472706","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.11-renovate-internal-patch-bba77d2d120b84b81488ebd93dd5874611472706","maintainers":[{"name":"pranavbalena","email":"pranav@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3aa71266c69f97657d506a99010a8c89a8b4e752","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.11-renovate-internal-patch-bba77d2d120b84b81488ebd93dd5874611472706.tgz","fileCount":205,"integrity":"sha512-Yq2pgxcOlUWCylkNFIMTp3P+eyHV9PDuKCGhRzmkRVXVvlO4Dr2HmKqPSnOvXxxrnrJREbzgaVNtL1duLnLVYQ==","signatures":[{"sig":"MEYCIQCFaYZ7n+6KWXuPtxmb2fz83ToYZtyT805Aaje+YzbnwQIhAIo6Qhx1Jn0iIRIkHr23thtStEH/aZqzdpTtH+LgMcaV","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":753003},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"bba77d2d120b84b81488ebd93dd5874611472706","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-23T20:33:05.648Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.93","@balena/jellyfish-metrics":"^1.0.352","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.4","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.11-renovate-internal-patch-bba77d2d120b84b81488ebd93dd5874611472706_1635021418277_0.6128057460006278","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.11":{"name":"@balena/jellyfish-core","version":"7.0.11","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.11","maintainers":[{"name":"pranavbalena","email":"pranav@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d1fe56b945c4a38319ba952810d82a41514cfe24","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.11.tgz","fileCount":205,"integrity":"sha512-p2QuzrHVPF3SHPPplgumx41MPV2xFH4R7TsdgO73uuqYKRuJjsuw4bXsdiEZU5Yc0PGVZJ+IhjnBxPsMjZBRNw==","signatures":[{"sig":"MEQCIGzPGRnsRNW1cx9K1C589jxQMOT1XqV8OR1qA6ElE7dqAiB+F0qHumbicSBuSEv35i/sRE5TwPabCuHoutjHRXHP2A==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":752938},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"017f85bb29d42f05de081debd0c03d2b1b9a0912","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-23T20:41:59.414Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.93","@balena/jellyfish-metrics":"^1.0.352","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.8"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.4","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.11_1635021828898_0.29272462097488217","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.12-renovate-major-json-schema-org-9c971e37312ab8f13f10cebe7522dfd94a3aaece":{"name":"@balena/jellyfish-core","version":"7.0.12-renovate-major-json-schema-org-9c971e37312ab8f13f10cebe7522dfd94a3aaece","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.12-renovate-major-json-schema-org-9c971e37312ab8f13f10cebe7522dfd94a3aaece","maintainers":[{"name":"pranavbalena","email":"pranav@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"069ff82ee9c95e18ef581a7d4cccb54426aceada","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.12-renovate-major-json-schema-org-9c971e37312ab8f13f10cebe7522dfd94a3aaece.tgz","fileCount":205,"integrity":"sha512-yfJYhIKF4+IM++gHBUcQCtl5MW9sKDp6TGQgCsudZFxl95R/u0XNSifckvgqoQFbY4tiln29e+2gU1/RcuceEQ==","signatures":[{"sig":"MEUCIQCwmo1Q8K9pH15rNwjfOzQI7D7g2Od4946vlEsYIrRv6gIgSahlUGip1mWkeUPMEvC4hnA4oAlcMk+cZPjFgp5qdqM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":753106},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"9c971e37312ab8f13f10cebe7522dfd94a3aaece","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-23T20:44:39.381Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.93","@balena/jellyfish-metrics":"^1.0.352","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.4","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.12-renovate-major-json-schema-org-9c971e37312ab8f13f10cebe7522dfd94a3aaece_1635022028823_0.9687700377384199","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.12-renovate-internal-patch-5089c2b53b47063c49cb080a9961139642636d27":{"name":"@balena/jellyfish-core","version":"7.0.12-renovate-internal-patch-5089c2b53b47063c49cb080a9961139642636d27","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.12-renovate-internal-patch-5089c2b53b47063c49cb080a9961139642636d27","maintainers":[{"name":"pranavbalena","email":"pranav@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5efe1d304feb4c86d14a0fa7195eda014f0c22a2","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.12-renovate-internal-patch-5089c2b53b47063c49cb080a9961139642636d27.tgz","fileCount":205,"integrity":"sha512-+gob3jWa5Hkzzc5mbDoxYp8bpBQ/DWcRb3dFT5iUFUP0uwqb3M3gdB20j+wAvuI+FaG58yPncIdSXTYGL53mIg==","signatures":[{"sig":"MEQCIDTNnrD1ngNHfk1TokyIaziHXhamuz6EmmvKd/aAt5bcAiAw6xrUa4v3PkVYur1AOLItBdkYn+QB2FctTahkXZy1DQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":753108},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"5089c2b53b47063c49cb080a9961139642636d27","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-23T22:33:49.180Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.93","@balena/jellyfish-metrics":"^1.0.353","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.4","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.12-renovate-internal-patch-5089c2b53b47063c49cb080a9961139642636d27_1635028642791_0.9612044541265496","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.12":{"name":"@balena/jellyfish-core","version":"7.0.12","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.12","maintainers":[{"name":"pranavbalena","email":"pranav@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3d68cc8eb595af4ccde9362894ec635966afdc61","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.12.tgz","fileCount":205,"integrity":"sha512-QNJ419GU6fvo/5w+3DoUujp7HX3hryBEmYzjclRx1r3DHRg45gIT3X2qONFpBMiW0LJ3/v/HwwuobkNxXjsckA==","signatures":[{"sig":"MEUCIQCspN4gqZpb0QC4915WVXfDVkal94aw7lZr+cqk70aIJwIgFg2WFk5S4dHJExlOeRYFd4ZqAWqBuPCpficlFx2c1X8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":753043},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"308207fa89649697b6014a9bd31beeb0f0604683","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-23T22:41:39.768Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.93","@balena/jellyfish-metrics":"^1.0.353","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.8"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.4","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.12_1635029019781_0.7848817324530788","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.13-renovate-major-json-schema-org-8e3de53a5f4febabf3c7b800f64031fb00586525":{"name":"@balena/jellyfish-core","version":"7.0.13-renovate-major-json-schema-org-8e3de53a5f4febabf3c7b800f64031fb00586525","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.13-renovate-major-json-schema-org-8e3de53a5f4febabf3c7b800f64031fb00586525","maintainers":[{"name":"pranavbalena","email":"pranav@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d1fce9fc6c83aa11b8d17da32c33610c3ed48ade","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.13-renovate-major-json-schema-org-8e3de53a5f4febabf3c7b800f64031fb00586525.tgz","fileCount":205,"integrity":"sha512-YN/jgpYt5f+QuCLEhQEd4Xmn1X6b9HHVW6b683XmJAnfoXIv9itaUY5nSpZIWLojYvM2PNS+/KOlOTGu6K9bnQ==","signatures":[{"sig":"MEQCIFw0k6GtQeFrEmERJJpcKSL+ReZyBN1V/lvQumpTBFBcAiAKr49VHzROrPumtO3JhINGWSug0yjew9qxtN2h6Z9MbQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":753211},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8e3de53a5f4febabf3c7b800f64031fb00586525","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-23T22:43:19.429Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.93","@balena/jellyfish-metrics":"^1.0.353","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.4","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.13-renovate-major-json-schema-org-8e3de53a5f4febabf3c7b800f64031fb00586525_1635029254647_0.13840630507482854","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.0-joshbwlng-index-locks-cf62e9efb7cb8d43d661b0c45488d56155d6ac75":{"name":"@balena/jellyfish-core","version":"8.0.0-joshbwlng-index-locks-cf62e9efb7cb8d43d661b0c45488d56155d6ac75","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.0-joshbwlng-index-locks-cf62e9efb7cb8d43d661b0c45488d56155d6ac75","maintainers":[{"name":"pranavbalena","email":"pranav@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e51c3471101eaaa88668327983a4412af9b8262a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.0-joshbwlng-index-locks-cf62e9efb7cb8d43d661b0c45488d56155d6ac75.tgz","fileCount":205,"integrity":"sha512-BfHV5QeQWkQX6i6XGhrt4NNraFQVy2fvBuwlB3L5RgI59tV8kOHjH6/M8Y8y9QMDfUMKJeLPaUg/ZatQUFfW9w==","signatures":[{"sig":"MEUCIEZ+y6Xdbdc33W4qJoHCcG2nkRexmmcfgvcT7YFMXLz7AiEA4T+e77FcDM52PjGmQr0WIrtyDswLzkF5EIZ7iA2aco0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":758153},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"cf62e9efb7cb8d43d661b0c45488d56155d6ac75","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-24T00:20:49.525Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.93","@balena/jellyfish-metrics":"^1.0.353","@balena/jellyfish-environment":"^5.0.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.4","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.0-joshbwlng-index-locks-cf62e9efb7cb8d43d661b0c45488d56155d6ac75_1635035037472_0.19124215964204994","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.13-h32-updated-at-fix-6444ac747f7d1b224ed0037eba38fc5e5e81c969":{"name":"@balena/jellyfish-core","version":"7.0.13-h32-updated-at-fix-6444ac747f7d1b224ed0037eba38fc5e5e81c969","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.13-h32-updated-at-fix-6444ac747f7d1b224ed0037eba38fc5e5e81c969","maintainers":[{"name":"pranavbalena","email":"pranav@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"415a800e24fdf6f6273443ed12ec18379eca8898","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.13-h32-updated-at-fix-6444ac747f7d1b224ed0037eba38fc5e5e81c969.tgz","fileCount":205,"integrity":"sha512-DaGM9usdT/vUf5LBd9feNO61PEGM1i9NSN334OB6/VgJP3gkMNfO6TLDzIRiFpILPoaXIbkAmMH71/Eqw27I7w==","signatures":[{"sig":"MEUCIDZQJdjY5hwY+Bap9n+WntGJoZerv3JqAWE2gQO2v0d7AiEAs//u0wNWTGmgsvLlao8fCrXdBqD1Ptu5qt69RBCnbYU=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":753231},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6444ac747f7d1b224ed0037eba38fc5e5e81c969","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-25T11:57:39.592Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.93","@balena/jellyfish-metrics":"^1.0.353","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.4","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.13-h32-updated-at-fix-6444ac747f7d1b224ed0037eba38fc5e5e81c969_1635163312939_0.24496240661831692","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.13":{"name":"@balena/jellyfish-core","version":"7.0.13","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.13","maintainers":[{"name":"pranavbalena","email":"pranav@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1e8d331645f7db947e8b20c00892dd7b868af934","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.13.tgz","fileCount":205,"integrity":"sha512-DroOlQtUaXummb6Qu6ADVOQZ7QGRyoW+Nrd5eXp6lGFVM/ZByPJ0txo4f610lwyunN6hgBnels1YWdxKLF/PLQ==","signatures":[{"sig":"MEUCIQCMdMJ0LzcK5y6df1/I4YYnZMfl/ztA8QgAXcIDPBQy2QIgVU24xqCRO5BYYmkqBN6NrsgseJL8nk7LJfDRcEfZesI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":753171},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"391c040ab32111ad9d8a5f9baf5d5a1004e45d0a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-25T12:06:29.223Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.93","@balena/jellyfish-metrics":"^1.0.353","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.8"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.4","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.13_1635163698119_0.9358229918807528","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.14-renovate-major-json-schema-org-b1db9f4e658f22f42e92d0281be0e112d5b48655":{"name":"@balena/jellyfish-core","version":"7.0.14-renovate-major-json-schema-org-b1db9f4e658f22f42e92d0281be0e112d5b48655","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.14-renovate-major-json-schema-org-b1db9f4e658f22f42e92d0281be0e112d5b48655","maintainers":[{"name":"pranavbalena","email":"pranav@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f1c4e4f1d5178be9f461c97caf9c425e411e207a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.14-renovate-major-json-schema-org-b1db9f4e658f22f42e92d0281be0e112d5b48655.tgz","fileCount":205,"integrity":"sha512-8QOsLJeBAL4gIAku9FfmAjRJMCwktDNliGGlcFtG8j83605NMGy+GDk5RGLiNCX2Xyvbn9jit8lupXjeieRm5Q==","signatures":[{"sig":"MEYCIQCpUS9/fINO5C8N2B2yGKNlgm332opa+1OhQhkOmBdq+AIhAKtPgEup6xLuS2GmIMxBPRLo4FPd+Fhu0VU7WSPIzz/L","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":753339},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b1db9f4e658f22f42e92d0281be0e112d5b48655","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-25T12:11:29.026Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.93","@balena/jellyfish-metrics":"^1.0.353","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.4","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.14-renovate-major-json-schema-org-b1db9f4e658f22f42e92d0281be0e112d5b48655_1635164076204_0.8668759129345254","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.0-joshbwlng-index-locks-984fb21dd5ff6caa488dbbeeab529d375c164ae7":{"name":"@balena/jellyfish-core","version":"8.0.0-joshbwlng-index-locks-984fb21dd5ff6caa488dbbeeab529d375c164ae7","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.0-joshbwlng-index-locks-984fb21dd5ff6caa488dbbeeab529d375c164ae7","maintainers":[{"name":"pranavbalena","email":"pranav@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5dea772bc246037d1f4fc5b60e394d3c1f235b1d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.0-joshbwlng-index-locks-984fb21dd5ff6caa488dbbeeab529d375c164ae7.tgz","fileCount":205,"integrity":"sha512-LSsPQfn3Uc4P1D33+HfRZi4a3KgpJWoy0OFWEYJYwDtZRFT7zF7QmQXevEs2Avyav7n8K7ds6qFHtYH9auWMlw==","signatures":[{"sig":"MEQCIHN3RhuYvWEmpNvyItjWIPEX6tEK5tBY1utmccbFsP1dAiB6tl1T5MZw32QZXzf6W1AIkNmRKv/xVKxaDuUh2hWO6g==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":758710},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"984fb21dd5ff6caa488dbbeeab529d375c164ae7","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-26T03:52:19.308Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.93","@balena/jellyfish-metrics":"^1.0.353","@balena/jellyfish-environment":"^5.0.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.4","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.0-joshbwlng-index-locks-984fb21dd5ff6caa488dbbeeab529d375c164ae7_1635220550602_0.2788619892528499","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.0-joshbwlng-index-locks-6e584a56d8e2b726b150aadf9248c20de270b13a":{"name":"@balena/jellyfish-core","version":"8.0.0-joshbwlng-index-locks-6e584a56d8e2b726b150aadf9248c20de270b13a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.0-joshbwlng-index-locks-6e584a56d8e2b726b150aadf9248c20de270b13a","maintainers":[{"name":"pranavbalena","email":"pranav@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"52c42e64bfd45b0365d0cade23aa5455355d912c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.0-joshbwlng-index-locks-6e584a56d8e2b726b150aadf9248c20de270b13a.tgz","fileCount":205,"integrity":"sha512-7RHkpyPF/7U0VTk3T9lWhBezrez/BhUo+h5rPwpTUK8iXr+EN8dkGix67nMQE0HZoL8Bj1BSRkUtAL+ywvrKXw==","signatures":[{"sig":"MEUCIDd7K8ZM4w/BeUxlImiWbhjATFP9HkWkzQpwIXju7nUlAiEAuiU9CyG14UcKUWHZYNylsgyBzpUt50FOV1y4gvOl7Fg=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":758679},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6e584a56d8e2b726b150aadf9248c20de270b13a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-26T04:34:18.989Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.93","@balena/jellyfish-metrics":"^1.0.353","@balena/jellyfish-environment":"^5.0.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.4","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.0-joshbwlng-index-locks-6e584a56d8e2b726b150aadf9248c20de270b13a_1635223064363_0.6805884198300234","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.0-joshbwlng-index-locks-220826cf028fe7550f9da45ca84314487d44d668":{"name":"@balena/jellyfish-core","version":"8.0.0-joshbwlng-index-locks-220826cf028fe7550f9da45ca84314487d44d668","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.0-joshbwlng-index-locks-220826cf028fe7550f9da45ca84314487d44d668","maintainers":[{"name":"pranavbalena","email":"pranav@balena.io"},{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b284a7f3d83ddb3593b064086853b8cfb9440735","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.0-joshbwlng-index-locks-220826cf028fe7550f9da45ca84314487d44d668.tgz","fileCount":205,"integrity":"sha512-6jV55sm0wB5Gy+skOCOzKnF9Iwsy4LKgCnHf32mCSmDspdd+HEjhaXIMdLL0FNsckQCInhxuqSnbhta4wyVtYQ==","signatures":[{"sig":"MEUCICRcLvqBLTpASIuYMGpqHmkMCzCQu8BDNHpWscgxGFwxAiEAl7ouzywKy2O4Z9yVkPurNeXhznhk0E9t9MfQp1e6mkY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":758743},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"220826cf028fe7550f9da45ca84314487d44d668","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-26T05:05:59.178Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.93","@balena/jellyfish-metrics":"^1.0.353","@balena/jellyfish-environment":"^5.0.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.4","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.0-joshbwlng-index-locks-220826cf028fe7550f9da45ca84314487d44d668_1635224950626_0.9585568420960702","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.0-joshbwlng-index-locks-f5cae14846a6e304ec798f0646432cbcaeecf99e":{"name":"@balena/jellyfish-core","version":"8.0.0-joshbwlng-index-locks-f5cae14846a6e304ec798f0646432cbcaeecf99e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.0-joshbwlng-index-locks-f5cae14846a6e304ec798f0646432cbcaeecf99e","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3233b13450568a63f109e321c19c813498ba4089","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.0-joshbwlng-index-locks-f5cae14846a6e304ec798f0646432cbcaeecf99e.tgz","fileCount":205,"integrity":"sha512-e3wX5qcFQBPxN0YRyxZmxf9O2Ttcb1jizBSWYFejtMn3fypKlFZ+ZhAySfULDnYH9n2StpPnMbmPWo6D9ceang==","signatures":[{"sig":"MEUCIQDEYQCoZ3VVkGdM6/OqSESX3WwZzA/THfbK2ACfF8ZFggIgKzfy5fMNrvmBWXhn61vQQjni67H0c7slBOQUJGQ2iys=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":758928},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f5cae14846a6e304ec798f0646432cbcaeecf99e","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-26T13:11:58.997Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.93","@balena/jellyfish-metrics":"^1.0.353","@balena/jellyfish-environment":"^5.0.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.4","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.0-joshbwlng-index-locks-f5cae14846a6e304ec798f0646432cbcaeecf99e_1635254091664_0.6365350395482456","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.0-joshbwlng-index-locks-bf7c7c6ecbdc833e0e1c5ebdf0be4462c6947f47":{"name":"@balena/jellyfish-core","version":"8.0.0-joshbwlng-index-locks-bf7c7c6ecbdc833e0e1c5ebdf0be4462c6947f47","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.0-joshbwlng-index-locks-bf7c7c6ecbdc833e0e1c5ebdf0be4462c6947f47","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"06898813a8251e6e600a3f13bdfa471bca60f3c7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.0-joshbwlng-index-locks-bf7c7c6ecbdc833e0e1c5ebdf0be4462c6947f47.tgz","fileCount":205,"integrity":"sha512-o4HJsyW1eD96Z8oHl8Yx5NBNrO0KGrGqTAt8b4PTMZHT+diTy3tifI1caqxP6QbX2lNtgateuXirgx0vHGL9Rg==","signatures":[{"sig":"MEUCIBwCtVzQbd4DKH/KBV2DZAVELVEzy0zBPOiM27Lkmog5AiEArbWR5UO1jKH7E6JtmjjVuWbUARBslDmlhFwZizCVA3w=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":758860},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"bf7c7c6ecbdc833e0e1c5ebdf0be4462c6947f47","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-27T01:12:15.651Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.93","@balena/jellyfish-metrics":"^1.0.353","@balena/jellyfish-environment":"^5.0.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.4","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.0-joshbwlng-index-locks-bf7c7c6ecbdc833e0e1c5ebdf0be4462c6947f47_1635297399083_0.8053251041602827","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.0-joshbwlng-index-locks-429c33f83299b0de0d292de9d760ae4d3aec1672":{"name":"@balena/jellyfish-core","version":"8.0.0-joshbwlng-index-locks-429c33f83299b0de0d292de9d760ae4d3aec1672","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.0-joshbwlng-index-locks-429c33f83299b0de0d292de9d760ae4d3aec1672","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"50d7823562747b53d325d35260256be0e0a8b85b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.0-joshbwlng-index-locks-429c33f83299b0de0d292de9d760ae4d3aec1672.tgz","fileCount":205,"integrity":"sha512-L6LOW+WEygWYlESGnlbfhTc6d2kW7FxtJR9vl2OYBs39RNgMZ3NciAViEaSLCqxCG/Arr8oZjN0LzqwZXbCQsQ==","signatures":[{"sig":"MEYCIQDB+ZkMgf/WLDZXtTBU6w3aIQicAH0rqf8r2TgjpWQJDgIhAKZKFZxeVFHgAP6w1oady8BDHJBB9PmGNL8zAcjRCUu6","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":758860},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"429c33f83299b0de0d292de9d760ae4d3aec1672","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-27T01:16:59.222Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.93","@balena/jellyfish-metrics":"^1.0.353","@balena/jellyfish-environment":"^5.0.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.4","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.3","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.0-joshbwlng-index-locks-429c33f83299b0de0d292de9d760ae4d3aec1672_1635297607969_0.06760956414773389","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.14-renovate-internal-patch-b85d0790e09de8de20940f798099fafbd65628d5":{"name":"@balena/jellyfish-core","version":"7.0.14-renovate-internal-patch-b85d0790e09de8de20940f798099fafbd65628d5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.14-renovate-internal-patch-b85d0790e09de8de20940f798099fafbd65628d5","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4fa95a518d9fbdf14648e41987a6bbf0f141968c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.14-renovate-internal-patch-b85d0790e09de8de20940f798099fafbd65628d5.tgz","fileCount":205,"integrity":"sha512-DEXlADi4B+9FGj7Cc78E4jOCK0hnTs88gALYsnjJicMlhcvUsfHXGlifEvpFMRAGXeIStbP0bQd5nNT4ycpTzw==","signatures":[{"sig":"MEUCIQDnZOuGDJO18Ufev1QqiAjDE1bnGGXy6Fpn7UMDnBoyxQIgRkw02cPn7++FIz3AVbKUVIB9ZvYrVCpX76FRh3miJtY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":753332},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b85d0790e09de8de20940f798099fafbd65628d5","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-28T01:18:49.859Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.93","@balena/jellyfish-metrics":"^1.0.353","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.4","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.4","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.14-renovate-internal-patch-b85d0790e09de8de20940f798099fafbd65628d5_1635384125844_0.9721255461907243","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.14":{"name":"@balena/jellyfish-core","version":"7.0.14","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.14","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e662b291c64a203970e7271f57faeeb83d40240b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.14.tgz","fileCount":205,"integrity":"sha512-Dim0qCouSsSq7Oi5NG+BMGHrRGQw8Nelf0SdGqdvG95PackUA4yod4Wvc7HSrF8zC423A+QaCAAMFpNWYoaagQ==","signatures":[{"sig":"MEQCIB08VLqBrvO+pXBJCrtL4JrQCYyUUX+dFRJctgvKQ500AiBS0nTTnXaq62IdNX8dWI7GuHYOQ5WmE4rfHCll3GFAXg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":753267},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0be176ef0e3ab7d131a8785b17e704337cf7ff98","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-28T01:28:39.907Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.93","@balena/jellyfish-metrics":"^1.0.353","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.8"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.4","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.4","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.14_1635384630893_0.15770038920328955","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.15-renovate-major-json-schema-org-ed7d1d359e9c6c6cd75c2c6b55bd05775924862b":{"name":"@balena/jellyfish-core","version":"7.0.15-renovate-major-json-schema-org-ed7d1d359e9c6c6cd75c2c6b55bd05775924862b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.15-renovate-major-json-schema-org-ed7d1d359e9c6c6cd75c2c6b55bd05775924862b","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0f38f360676177e22f39303fdbd63b29656e2888","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.15-renovate-major-json-schema-org-ed7d1d359e9c6c6cd75c2c6b55bd05775924862b.tgz","fileCount":205,"integrity":"sha512-4GV2I23+OLMJ98UTPXPuhPjM/rbwRD4PRkykuH2vrx+UKZU0XUE3MVRq2TWIExSZQGIdxZ04RnBOZz1zPBvmXw==","signatures":[{"sig":"MEYCIQCM7ePJPYColrOMChQFKxs9yIcBKD7xGPRQ9z0CPyC/jwIhAOI8oLy2PvOic6jo9GQ24ytTDySUeei/yZ8vdn+2KRCD","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":753435},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ed7d1d359e9c6c6cd75c2c6b55bd05775924862b","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-28T01:34:38.864Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.93","@balena/jellyfish-metrics":"^1.0.353","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.4","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.4","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.15-renovate-major-json-schema-org-ed7d1d359e9c6c6cd75c2c6b55bd05775924862b_1635385108357_0.28007450717173654","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.15-renovate-internal-patch-3ab810aefb9844d86e1733237e0abdb27c1037c3":{"name":"@balena/jellyfish-core","version":"7.0.15-renovate-internal-patch-3ab810aefb9844d86e1733237e0abdb27c1037c3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.15-renovate-internal-patch-3ab810aefb9844d86e1733237e0abdb27c1037c3","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5cad47302397c5580746a6614a640829a32cb98f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.15-renovate-internal-patch-3ab810aefb9844d86e1733237e0abdb27c1037c3.tgz","fileCount":205,"integrity":"sha512-oN27Cm2msgxbdIkqUfPQjo20v27k8WN0EAlKt8e90yr7VqYlQrwcAUdHZR2fCJLiYPezoBbGEkUAeM0wMMvl9w==","signatures":[{"sig":"MEQCIBEC+tQ1zB3n2pbEPm8ySZGF1a+W29Xb3whIECloKqIXAiBlciaCHKimNvZe6HApa62pGOW8iRbNfqB2eo1XfFPYLA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":753437},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"3ab810aefb9844d86e1733237e0abdb27c1037c3","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-28T01:38:21.039Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.93","@balena/jellyfish-metrics":"^1.0.354","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.4","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.4","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.15-renovate-internal-patch-3ab810aefb9844d86e1733237e0abdb27c1037c3_1635385263022_0.9261499153241384","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.15":{"name":"@balena/jellyfish-core","version":"7.0.15","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.15","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"75d788276400590e79063db61aca85e53951f10f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.15.tgz","fileCount":205,"integrity":"sha512-2oWSvG0KfZRxIEsCbcTvxGazbNSAfA5gfic6s7EJF4d8K3KXRwzitSC/2mUFLE+kQAKHRoU4hNXwxMf4rK5RTQ==","signatures":[{"sig":"MEQCIEIfQNug4OIJy0SaR4RVXfEOjjMhGPHPUyztbSQrlZREAiBfyvh3tPew0V+n/IaoHkMODJQ4b88wxTKUiOx/l6WsRg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":753372},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e9e711bb63cd96747b8c77d619f81fb155cf00e5","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-28T01:47:41.175Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.93","@balena/jellyfish-metrics":"^1.0.354","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.8"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.4","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.4","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.15_1635385784589_0.672581123563619","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.16-renovate-major-json-schema-org-c04e3ea13a58fb2357bee4f8ac11cddefe6f7dce":{"name":"@balena/jellyfish-core","version":"7.0.16-renovate-major-json-schema-org-c04e3ea13a58fb2357bee4f8ac11cddefe6f7dce","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.16-renovate-major-json-schema-org-c04e3ea13a58fb2357bee4f8ac11cddefe6f7dce","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"10feaac3f90b3ea1f9e32970cceef9fd2254811f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.16-renovate-major-json-schema-org-c04e3ea13a58fb2357bee4f8ac11cddefe6f7dce.tgz","fileCount":205,"integrity":"sha512-0WSJeun+sYKGnkQUBp7A2qTr9l4g9afKN3HtaCB9o13BtIEaEoucbqtSZ4CuXTJbjfD4rzwa7RGpJ+lH9dP7sA==","signatures":[{"sig":"MEUCIQDetTpJBcdgckySZ6Zs8ykSUpLSNUggImmzyaXEiRDptQIgWt29DJZFkQQlqLjvUKi6Arhtorb9I6TvGy7Z+3omjtc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":753540},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c04e3ea13a58fb2357bee4f8ac11cddefe6f7dce","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-28T01:50:50.201Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.93","@balena/jellyfish-metrics":"^1.0.354","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.4","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.4","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.16-renovate-major-json-schema-org-c04e3ea13a58fb2357bee4f8ac11cddefe6f7dce_1635386025819_0.48718998452145046","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.0-joshbwlng-index-locks-18a46e2076b61987f5f7fe835ac7e3709d315a8a":{"name":"@balena/jellyfish-core","version":"8.0.0-joshbwlng-index-locks-18a46e2076b61987f5f7fe835ac7e3709d315a8a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.0-joshbwlng-index-locks-18a46e2076b61987f5f7fe835ac7e3709d315a8a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4edbdf26fba213467f20dbaf942dab04a9309e2c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.0-joshbwlng-index-locks-18a46e2076b61987f5f7fe835ac7e3709d315a8a.tgz","fileCount":205,"integrity":"sha512-b8qfK4VjXFW0Z+J3j9SfVGBlfsGPPMh1cCmMp8FoNT8YgLax+k8nRbzkfjYFyqOFSFu0S+pUhwQYm7ucH2/Y6A==","signatures":[{"sig":"MEQCIFbcrtXyItUAdGlCjWmMY9GT1FoDeXG3Tr9O5cKp54XcAiAiMySyBuDgObgsw2li8d0kctvT8J1FY4jjHnVhNZBpvQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":759061},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"18a46e2076b61987f5f7fe835ac7e3709d315a8a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-28T02:05:06.172Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.93","@balena/jellyfish-metrics":"^1.0.354","@balena/jellyfish-environment":"^5.0.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.4","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.4","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.0-joshbwlng-index-locks-18a46e2076b61987f5f7fe835ac7e3709d315a8a_1635386978613_0.23794822584461106","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.0-joshbwlng-index-locks-bdff904af0ef9e51d46505404ff5b1b3c949e4cd":{"name":"@balena/jellyfish-core","version":"8.0.0-joshbwlng-index-locks-bdff904af0ef9e51d46505404ff5b1b3c949e4cd","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.0-joshbwlng-index-locks-bdff904af0ef9e51d46505404ff5b1b3c949e4cd","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"7b2b55e5dee01c4fcd81199d05c6c45165f8ab51","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.0-joshbwlng-index-locks-bdff904af0ef9e51d46505404ff5b1b3c949e4cd.tgz","fileCount":205,"integrity":"sha512-LLUfH8yxyBUqebgqLZV5zJhNXGJlan+5w8uzVTHZgyP7ja7LI5QoiNfxbqSIsXYhRPV4em2ONyQHjsm396QDIQ==","signatures":[{"sig":"MEYCIQDlrIcMwfggXxQa5kIvK404H/SqZj/NDGPLozPH1jgLMwIhAO0zwjt901Rr3fhTQ+i5UucTnftkVBYK+WysKRD6FxpF","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":758953},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"bdff904af0ef9e51d46505404ff5b1b3c949e4cd","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-28T03:21:46.098Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.93","@balena/jellyfish-metrics":"^1.0.354","@balena/jellyfish-environment":"^5.0.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.4","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.4","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.0-joshbwlng-index-locks-bdff904af0ef9e51d46505404ff5b1b3c949e4cd_1635391567115_0.6520624627165579","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.16-renovate-internal-patch-25775ad973c41ff26537e505a3b9a37193a7fbd4":{"name":"@balena/jellyfish-core","version":"7.0.16-renovate-internal-patch-25775ad973c41ff26537e505a3b9a37193a7fbd4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.16-renovate-internal-patch-25775ad973c41ff26537e505a3b9a37193a7fbd4","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1c90f5620186efdb28c80b4a87e55b87653ca9cf","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.16-renovate-internal-patch-25775ad973c41ff26537e505a3b9a37193a7fbd4.tgz","fileCount":205,"integrity":"sha512-IwBCvzW/sd1HEfr6FXH7vaUjncUx4QvVriRJaWQCP7LeTNQbbthyF8ExC+2yxrrea9DTRs2WYEKKbviFaYcHxQ==","signatures":[{"sig":"MEQCIBoW5U+sIJtjEGAzZBV5x7tUKqb+Dt+Zs7a0R4omiYm/AiBcMBmqdIRmfPYUFg5YboStmSKfmWrIr5W87GzcQixOTw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":753508},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"25775ad973c41ff26537e505a3b9a37193a7fbd4","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-28T03:33:18.799Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.94","@balena/jellyfish-metrics":"^1.0.354","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.4","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.4","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.16-renovate-internal-patch-25775ad973c41ff26537e505a3b9a37193a7fbd4_1635392152012_0.29544683873992206","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.16":{"name":"@balena/jellyfish-core","version":"7.0.16","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.16","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"73b96b93b87e2b2a44f3dcff3c2541e0e577e3f1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.16.tgz","fileCount":205,"integrity":"sha512-MytKFtHldyueOUSfC1VB5sX26Nfg1fYmUzI9LpwNNm26NeYgu7ycyn+hLrKriLQSX6HwE166XVAWJzO6Vm0gnQ==","signatures":[{"sig":"MEUCIDZDYOvZFr+OpRodDfpLfgEOXDQdTWZzwD59IZRMiAaTAiEAwZFtHJcfPQop+lsNXzgdtqjjXrwD0cV6q3hXlWdilso=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":753443},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7cdc711469573080327266cd7151ccf69ad85cf9","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-28T03:40:59.436Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.94","@balena/jellyfish-metrics":"^1.0.354","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.9"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.4","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.4","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.16_1635392564696_0.809876739880675","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.17-renovate-major-json-schema-org-1fcbcabfbd3fbf9e7b84ab0fcea0633cca5a79fa":{"name":"@balena/jellyfish-core","version":"7.0.17-renovate-major-json-schema-org-1fcbcabfbd3fbf9e7b84ab0fcea0633cca5a79fa","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.17-renovate-major-json-schema-org-1fcbcabfbd3fbf9e7b84ab0fcea0633cca5a79fa","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e3e872050d76cb2e42bf3219fdac164b1e9770ff","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.17-renovate-major-json-schema-org-1fcbcabfbd3fbf9e7b84ab0fcea0633cca5a79fa.tgz","fileCount":205,"integrity":"sha512-KMVDulclEiDTIDEeCWDo2Fglz4ejbc3QRk5SNe4Y7/aacQLfxJK4a26mUdeKRL7riDj22WjA+I2hZfk7N7zRiw==","signatures":[{"sig":"MEQCIGEG2E1cve3ENkCofsnbtBstf3B88DACZxvI2F02LpHFAiATA8W5r65raAXrLKt7clI88lwB5paG5Jb5rmXhi1uHAA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":753611},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"1fcbcabfbd3fbf9e7b84ab0fcea0633cca5a79fa","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-28T03:43:05.893Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.94","@balena/jellyfish-metrics":"^1.0.354","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.4","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.4","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.17-renovate-major-json-schema-org-1fcbcabfbd3fbf9e7b84ab0fcea0633cca5a79fa_1635392848360_0.23558588048576934","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.17-renovate-internal-patch-0e8275f436680b7c7ec96919e4bd9a27ee183409":{"name":"@balena/jellyfish-core","version":"7.0.17-renovate-internal-patch-0e8275f436680b7c7ec96919e4bd9a27ee183409","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.17-renovate-internal-patch-0e8275f436680b7c7ec96919e4bd9a27ee183409","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a2f18de4ab21a2f0c6b32f1770306dba97e3c4e0","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.17-renovate-internal-patch-0e8275f436680b7c7ec96919e4bd9a27ee183409.tgz","fileCount":205,"integrity":"sha512-WiFD2A6VRI3pZzRGqCX7F7iZdjN+yeTs9tAwSfyI4ZsHATlHzh3NVsn+9xcz1UZW1EwOnlnAkmeFQaLaWtlQbQ==","signatures":[{"sig":"MEQCIBBIubZV78Yhg+Ae/Z9m3Nnb1w8gB6FnmN9HZOV/vWjkAiAlrj7wE2jAWvbMkvD0R2F0cqM/9zmmRJovWJl6/7ql7A==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":753613},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0e8275f436680b7c7ec96919e4bd9a27ee183409","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-28T03:47:59.398Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.94","@balena/jellyfish-metrics":"^1.0.355","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.4","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.4","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.17-renovate-internal-patch-0e8275f436680b7c7ec96919e4bd9a27ee183409_1635393104535_0.3297050719918646","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.17":{"name":"@balena/jellyfish-core","version":"7.0.17","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.17","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"99b34dcd6cb0544fba3ec6059f6001870e110dc7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.17.tgz","fileCount":205,"integrity":"sha512-3Y0rxaijrzqOyEaVStAtHV0+dwlTTU63vYhYIMR0NW5yWoo2oir6gP5EOgyEJ3eoF2cij6BABJz+cpHPafiYDQ==","signatures":[{"sig":"MEUCIQCE15ABTkUVsuXRKVtA/lHFEou7HII8RWJSaENs9UvoiAIgWJZOceQM6N4dAeGPwXO0w1BgGIkRv+Tw3ePdYVlnoaM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":753548},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"427dc8c34a31591d9a787c2d90e0410faf0618be","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-28T03:57:14.031Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.94","@balena/jellyfish-metrics":"^1.0.355","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.9"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.4","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.4","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.17_1635393560324_0.4850827313883068","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.18-renovate-major-json-schema-org-9cbb48f3865e942e14fb1d37b72227882d51ba56":{"name":"@balena/jellyfish-core","version":"7.0.18-renovate-major-json-schema-org-9cbb48f3865e942e14fb1d37b72227882d51ba56","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.18-renovate-major-json-schema-org-9cbb48f3865e942e14fb1d37b72227882d51ba56","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d942a6ff140a1712cc12059fc559abb4abf8179c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.18-renovate-major-json-schema-org-9cbb48f3865e942e14fb1d37b72227882d51ba56.tgz","fileCount":205,"integrity":"sha512-mXpCNGLIh5qACjI8Gbs0NQeW43I0gJLIG5CH3n4qeczihq6YbpakMQjLKg68GOJYj7HAsuVAPFHz02WjKg8SOQ==","signatures":[{"sig":"MEUCIH6NxT5ZqwIsaD44h5TkFDMvSlIPnchvk36r/QmFZc/tAiEAkTDPtXH5x+BfbFv1tSMXcueDFyUeneDqPPN9AjpMhlo=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":753716},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"9cbb48f3865e942e14fb1d37b72227882d51ba56","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-28T03:59:50.125Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.94","@balena/jellyfish-metrics":"^1.0.355","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.4","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.4","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.18-renovate-major-json-schema-org-9cbb48f3865e942e14fb1d37b72227882d51ba56_1635393790186_0.2554119439297815","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.18-renovate-internal-patch-5dc3d840feb4eaf863a0c98af83c4ab00c484208":{"name":"@balena/jellyfish-core","version":"7.0.18-renovate-internal-patch-5dc3d840feb4eaf863a0c98af83c4ab00c484208","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.18-renovate-internal-patch-5dc3d840feb4eaf863a0c98af83c4ab00c484208","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"590f05be53a02ed9267a8a27fe8ca3ec84e05b82","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.18-renovate-internal-patch-5dc3d840feb4eaf863a0c98af83c4ab00c484208.tgz","fileCount":205,"integrity":"sha512-W7owXk2Zxe6/Bst144Of2XxwCm2+gcZMv8NSjMV3DKTzmQmVj4OEOrb2ZAJGQAh1AseCcW/mL/4Z4VYT7daPPA==","signatures":[{"sig":"MEQCIGM+lsTjKlUo8caC2Ont1mLddnEoeSp48wFcoSZGz3dxAiB5fh/CtvoPLAVhf/NgPD6wpMgCxA4VX9nlYNmrEAzXyg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":753716},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"5dc3d840feb4eaf863a0c98af83c4ab00c484208","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-28T05:53:29.852Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.95","@balena/jellyfish-metrics":"^1.0.355","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.4","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.4","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.18-renovate-internal-patch-5dc3d840feb4eaf863a0c98af83c4ab00c484208_1635400667838_0.4983136519627538","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.18":{"name":"@balena/jellyfish-core","version":"7.0.18","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.18","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b3cc94bcaee20904037b8357e3cd75095e2f253f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.18.tgz","fileCount":205,"integrity":"sha512-kE6FnuKLYv3lIpi7FR7mQcwGrqENRmyqBI4Bznmu78cV/F3L8IO4EEm7M3eFNqGkMeQ2fXVBmqHPQYk4ajOwRw==","signatures":[{"sig":"MEUCIQCquocFm9Sb/U04YLoNXSXSzjG6yxy/24y1W2YHDmUYBQIgag+5+NVl/vMtaYNdS+SepXBqys0/yOcGrcfE63M5TQA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":753651},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c8bc56fc5e83ac698b367da0d4b48668a629b49f","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-28T06:02:19.818Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.95","@balena/jellyfish-metrics":"^1.0.355","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.9"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.4","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.4","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.18_1635401051687_0.6869191779820121","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.19-renovate-internal-patch-7f1c032091b8a21213544b46accb8e592a4988f2":{"name":"@balena/jellyfish-core","version":"7.0.19-renovate-internal-patch-7f1c032091b8a21213544b46accb8e592a4988f2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.19-renovate-internal-patch-7f1c032091b8a21213544b46accb8e592a4988f2","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4211f3a5e77645a61fd6bd5de8613d03d1990218","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.19-renovate-internal-patch-7f1c032091b8a21213544b46accb8e592a4988f2.tgz","fileCount":205,"integrity":"sha512-/fhsYhXo4KGTvkUoQZVXHMIRAcjbzCOJU0/tMbKvUCfzjkyGFaqHfvI54DdKkDWbkLIJOCoY+pXsaHD6i0lAIQ==","signatures":[{"sig":"MEQCIEpTMXHJlq6XYnOFdI5lRIA2AKZICsAXI21byUpaAoe9AiBxNxkzqhQ2H5A6nLQkTe0D8zNOk/Pot3iaBmizhP6NtQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":753821},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7f1c032091b8a21213544b46accb8e592a4988f2","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-28T06:08:08.898Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.95","@balena/jellyfish-metrics":"^1.0.356","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.4","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.4","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.19-renovate-internal-patch-7f1c032091b8a21213544b46accb8e592a4988f2_1635401495894_0.6729483193020889","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.19-renovate-major-json-schema-org-068f0a47fb9429679e8705038153c655e375cab1":{"name":"@balena/jellyfish-core","version":"7.0.19-renovate-major-json-schema-org-068f0a47fb9429679e8705038153c655e375cab1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.19-renovate-major-json-schema-org-068f0a47fb9429679e8705038153c655e375cab1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"97fc16d1aa8fc3cdeadea7d417daf46ded8ab473","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.19-renovate-major-json-schema-org-068f0a47fb9429679e8705038153c655e375cab1.tgz","fileCount":205,"integrity":"sha512-Z3+Vc0CCN7+h5ROsf8+mtJbt32oxxlmU/WrqzOznjhsGXdpVfnCfLZf1FJXZGzPW33y6XaSbafQDho36WxyHtQ==","signatures":[{"sig":"MEQCIGhjQqxN6+UOaXo6475LyM6E63/AYfSQkGHscqfUpIRNAiAI7kBFhHwh+u+28+OMZsgwBnX5B+tfnFt9Ys99zkTM8A==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":753819},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"068f0a47fb9429679e8705038153c655e375cab1","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-28T06:08:05.307Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.95","@balena/jellyfish-metrics":"^1.0.355","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.4","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.4","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.19-renovate-major-json-schema-org-068f0a47fb9429679e8705038153c655e375cab1_1635401524320_0.8521235226073667","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.19":{"name":"@balena/jellyfish-core","version":"7.0.19","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.19","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1e793a5fb1ff51d874e139edb10825d3879dd8bd","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.19.tgz","fileCount":205,"integrity":"sha512-w7y2uGyMSqxXf+m8+XIE2I4mf+ZH0akulBnqkfZpU1LPso3ZAbcB0hMZ80U1XthsYouejjx8b44GVERfXqchgw==","signatures":[{"sig":"MEYCIQCmzjIXb8P+RnVoTJsQmzGT4tCbf3H2W4MEME4zla+DQQIhAITHjFMoev9W3a4OgOLY+X95G8Z9QU0orFi4OjXVnCko","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":753756},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"fbc68cdb06143a766c023cf94062bdce965bf76c","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-28T06:16:41.084Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.95","@balena/jellyfish-metrics":"^1.0.356","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.9"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.4","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.4","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.19_1635401919162_0.4920886128290398","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.20-renovate-major-json-schema-org-9e6b82d064146cba60c67b651c14399371ec090a":{"name":"@balena/jellyfish-core","version":"7.0.20-renovate-major-json-schema-org-9e6b82d064146cba60c67b651c14399371ec090a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.20-renovate-major-json-schema-org-9e6b82d064146cba60c67b651c14399371ec090a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"609b838b465cbee1369314c5ffad798f438ba9de","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.20-renovate-major-json-schema-org-9e6b82d064146cba60c67b651c14399371ec090a.tgz","fileCount":205,"integrity":"sha512-CT5963dnbQvEQmXxUlG3tMMMHXBLHBuIrXuonImvAHX/FUxHehwo6OAnANnyhEwluXjwJdLpHmspwGnBujEUQQ==","signatures":[{"sig":"MEYCIQCCc07/7wap1ughLX4tiUAiCw6lQwrnYHwltyCN4CrSnQIhAPmkoRj/dLncUl94w6Izr8mCwCswe1MGJP/rl0fo1IH8","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":753924},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"9e6b82d064146cba60c67b651c14399371ec090a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-28T06:19:49.644Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.95","@balena/jellyfish-metrics":"^1.0.356","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.4","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.4","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.20-renovate-major-json-schema-org-9e6b82d064146cba60c67b651c14399371ec090a_1635402224590_0.5514350023754959","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.20-lucianbuzzo-test-one-of-text-search-22c0a99b86e7253981d9576309a974e2068596f8":{"name":"@balena/jellyfish-core","version":"7.0.20-lucianbuzzo-test-one-of-text-search-22c0a99b86e7253981d9576309a974e2068596f8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.20-lucianbuzzo-test-one-of-text-search-22c0a99b86e7253981d9576309a974e2068596f8","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d59509567e3d66610ff4a313e9f1c97740b23f7d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.20-lucianbuzzo-test-one-of-text-search-22c0a99b86e7253981d9576309a974e2068596f8.tgz","fileCount":205,"integrity":"sha512-gMfzy+VmhNX66GLCNSBZqaosXkNXbFOkO5qXO4svHjWTwna01q/QU9wwI1/+QKCaspCDO6dQBKKvbwWWB2A+iQ==","signatures":[{"sig":"MEQCIEZJu0fF9I8j94QuPu6IN/vKZRDPT/2n+MCjrpZJxjTaAiBVijnRs40sm9AqVECqo5X6do8ZtPKf+5KlVM1q6pfQEg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":753919},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"22c0a99b86e7253981d9576309a974e2068596f8","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-28T08:08:36.827Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.95","@balena/jellyfish-metrics":"^1.0.356","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.4","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.4","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.20-lucianbuzzo-test-one-of-text-search-22c0a99b86e7253981d9576309a974e2068596f8_1635408758323_0.7039156022048705","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.20":{"name":"@balena/jellyfish-core","version":"7.0.20","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.20","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"752927c76c3a10afdfcbd061794f847a1d506032","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.20.tgz","fileCount":205,"integrity":"sha512-E1wiCJQC+/Q0Ih1dXUPKup4MIoV3vnVg5p9pnAl0rH4ZLXoetKKOXWY7FUEl55FtspLIFb5o2OtoQy6r/qh3Kg==","signatures":[{"sig":"MEUCIEEvBsk+QOZBMbNCULlS1g6TbHJMWuZBUwhvgVoSfI6CAiEAnVCeceeQF1JkUVFqFiudvjzEPNW566dmPoYyXvTHrtY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":753842},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e679f5f592e81dfede00524b647cad6882d0abf4","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-28T09:27:39.351Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.95","@balena/jellyfish-metrics":"^1.0.356","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.9"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.4","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.4","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.20_1635413369387_0.6251719590242704","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.21-renovate-internal-patch-7c64103ad50d0fc178149368ef753d8ed0f3f6fd":{"name":"@balena/jellyfish-core","version":"7.0.21-renovate-internal-patch-7c64103ad50d0fc178149368ef753d8ed0f3f6fd","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.21-renovate-internal-patch-7c64103ad50d0fc178149368ef753d8ed0f3f6fd","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e78caa1af58c92ffabbf80669c4d81d29e19522d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.21-renovate-internal-patch-7c64103ad50d0fc178149368ef753d8ed0f3f6fd.tgz","fileCount":205,"integrity":"sha512-mthwJTtv4ydE0FSXFZelm58AyWSw6zoZvZF2cQLYGERvJK1t9cRvzz576KsltkOLjdfrwefPyXAYWC2ky9qrGQ==","signatures":[{"sig":"MEQCIBCjc6F8V3mSK4VZgeyCVFxeh2Uud2kL3V/sZxIQdgwhAiAz7/PqRmonbAQummoDhUzTK4XSp2AY6XRzPtl7Q98lrg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":754003},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7c64103ad50d0fc178149368ef753d8ed0f3f6fd","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-28T09:33:18.469Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.95","@balena/jellyfish-metrics":"^1.0.356","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.4","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.21-renovate-internal-patch-7c64103ad50d0fc178149368ef753d8ed0f3f6fd_1635413821148_0.9796652592722219","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.21":{"name":"@balena/jellyfish-core","version":"7.0.21","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.21","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ac081e2119c146b5186cb18fb6b24966c78d4264","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.21.tgz","fileCount":205,"integrity":"sha512-FxzlsV9P2oVABUa9hYnErA2nUHyuSt1UpCKRWd1GcP/Z8SIIeL17efEBMZVIKiuqOw2Dt9S8n4f/iglNybsNNQ==","signatures":[{"sig":"MEUCICGCQi8hV3a6bU2cK4TTl24awzM3Q6C8LOSMv0s5msM2AiEAgsv7FRwuVW5YVBBnfTIRvuNuwn3CZfpIjIy41n8g00w=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":753938},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"46d883dfe7e5c2818c4e7e5a32c06c184c33b871","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-28T09:42:29.797Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.95","@balena/jellyfish-metrics":"^1.0.356","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.9"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.4","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.21_1635414263939_0.9170759630918279","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.22-renovate-major-json-schema-org-97d521de44e92c427cafe84ea5804357119890c7":{"name":"@balena/jellyfish-core","version":"7.0.22-renovate-major-json-schema-org-97d521de44e92c427cafe84ea5804357119890c7","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.22-renovate-major-json-schema-org-97d521de44e92c427cafe84ea5804357119890c7","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5324a8c0691528b65c2d8f2e2ace9b88981ac212","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.22-renovate-major-json-schema-org-97d521de44e92c427cafe84ea5804357119890c7.tgz","fileCount":205,"integrity":"sha512-pTjL0JFRy0HYvS5OGUHv9huoTa8mJ1VGa+NoGaXEKFhSUfObfwQNXvs85XW9IYfIndM4ZnYF1jVv2Y0E2htVuQ==","signatures":[{"sig":"MEUCIQC/zdxVwgJgH5A2jYtnuP6pv/GjtZMGAct46YfvnNu4EgIgZY93yBlKHXAx6YzfoANIDIHdsCQ02GM2cz/e5SX2N9Y=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":754106},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"97d521de44e92c427cafe84ea5804357119890c7","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-28T09:45:51.126Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.95","@balena/jellyfish-metrics":"^1.0.356","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.4","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.22-renovate-major-json-schema-org-97d521de44e92c427cafe84ea5804357119890c7_1635414536719_0.08410274270617601","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.22-renovate-internal-patch-c0a82f163cd1d7d74fd1be317dc8e2d596045b58":{"name":"@balena/jellyfish-core","version":"7.0.22-renovate-internal-patch-c0a82f163cd1d7d74fd1be317dc8e2d596045b58","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.22-renovate-internal-patch-c0a82f163cd1d7d74fd1be317dc8e2d596045b58","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f0e20d946504da9e6137725e11269620d185b0ef","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.22-renovate-internal-patch-c0a82f163cd1d7d74fd1be317dc8e2d596045b58.tgz","fileCount":205,"integrity":"sha512-HLRtQ51AqN4GRBR7blt2/gkThuLwfw/H2LZhEphT6qE8yYtu3nHUN4IZh2OuHE+pI3AqeArOfUatV+Kq/vEsZw==","signatures":[{"sig":"MEQCIE9O+OfLjsOgTwPpOsfyfmEmmskI42jvPrdohg5MZtkmAiB1H03702FZjOPbUA7VhAumYR8tGwFWrezDWyNBNYqCPw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":754075},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c0a82f163cd1d7d74fd1be317dc8e2d596045b58","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-28T11:52:49.378Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.96","@balena/jellyfish-metrics":"^1.0.358","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.10"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.4","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.22-renovate-internal-patch-c0a82f163cd1d7d74fd1be317dc8e2d596045b58_1635422210457_0.22469536009402002","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.22":{"name":"@balena/jellyfish-core","version":"7.0.22","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.22","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e5521537623716764182d87d66f6442bb9a81345","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.22.tgz","fileCount":205,"integrity":"sha512-ZcL5ORZOD9TGLAC54GAlfsrueASKtK9ZhIoOlUThH2v6WtqmTSCpb59wOHpbnhcLh7Z1hzPvHMQ4fqNciqT61Q==","signatures":[{"sig":"MEUCID+iMtOxee3UnQHHAy919Tv2OyFk959qGnbbWeM84ITpAiEAhGGefWlBN/p5yeqRCJCWJYvzc+ioz6MGJ01owiYz2yQ=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":754010},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4c9bd3dd1a99e0122c72a54fd7f6a7e811f73e33","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-28T12:01:30.512Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.96","@balena/jellyfish-metrics":"^1.0.358","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.10"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.4","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.22_1635422617036_0.11692784661618583","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.23-renovate-major-json-schema-org-acb3972d3438372c74f057fbffc83a1d39ceb29d":{"name":"@balena/jellyfish-core","version":"7.0.23-renovate-major-json-schema-org-acb3972d3438372c74f057fbffc83a1d39ceb29d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.23-renovate-major-json-schema-org-acb3972d3438372c74f057fbffc83a1d39ceb29d","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ffe2ace628822ea28562795118b44be1c8e365a2","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.23-renovate-major-json-schema-org-acb3972d3438372c74f057fbffc83a1d39ceb29d.tgz","fileCount":205,"integrity":"sha512-a61jhRoVqGm9l6PgXGzJOJZ3AdTE2wfmhMvwWiuV28Q2xBu6mZ/Zeq8vi6EDWEC45T9gVR79vqQhmUWOMBLVsw==","signatures":[{"sig":"MEQCIHt1ccv9pb0pmOK74mXP+MniJMKz513fKFdPS98mXzf3AiBgDmjYU6GF3tI0Zj6ipM0CA9G+iQgpw2Tqm0+5pxb2BQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":754178},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"acb3972d3438372c74f057fbffc83a1d39ceb29d","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-28T12:05:39.220Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.96","@balena/jellyfish-metrics":"^1.0.358","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.10"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.4","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.23-renovate-major-json-schema-org-acb3972d3438372c74f057fbffc83a1d39ceb29d_1635422968835_0.9366553734424983","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.23-renovate-internal-patch-b04355f84d0316907158ebbf74f688eb87c22712":{"name":"@balena/jellyfish-core","version":"7.0.23-renovate-internal-patch-b04355f84d0316907158ebbf74f688eb87c22712","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.23-renovate-internal-patch-b04355f84d0316907158ebbf74f688eb87c22712","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c0bff091c4832a165fc3ea31171f8d62c6ecb3e9","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.23-renovate-internal-patch-b04355f84d0316907158ebbf74f688eb87c22712.tgz","fileCount":205,"integrity":"sha512-YX+9WR5drqY44M8rHdL6X/sOMgnHw/0iJ+MN5EROapRXJUP7S9qyF7xjCIHmbt/7482GX7TRgd4zdlcLj/EjpQ==","signatures":[{"sig":"MEYCIQDgVO9QQSiahKPWQXY3imwpbSsnfFcrSp+0yG0Mwo00qwIhAOeg0IRuGUNtDdYUKlIPei3/REDtyZqQJAFVXsq/E+zn","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":754146},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b04355f84d0316907158ebbf74f688eb87c22712","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-28T14:01:59.834Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.97","@balena/jellyfish-metrics":"^1.0.359","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.10"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.4","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.23-renovate-internal-patch-b04355f84d0316907158ebbf74f688eb87c22712_1635429883601_0.6933811868454058","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.23":{"name":"@balena/jellyfish-core","version":"7.0.23","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.23","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"72858bba5cf5f136c9d61b7568743dfa0775d81e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.23.tgz","fileCount":205,"integrity":"sha512-Cdiz+Mjxp+SSTDcwc4sITzC4PsD5mry0ZNrAMjKKhIEXSxWfrzeP1gcRLWr6jc0iriMnBS8ftnPRc3VdaJQwoA==","signatures":[{"sig":"MEYCIQDvk0vZq79V2thYlyV0OeqDVU1cd8aXkeoyaG6vs/tUUgIhAN34L+V1PCWVbtAAPpyOJBBKP+/FmADP0LPkswXbAaEr","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":754081},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7e3b6c19437193ea25ecc93b0159b36a60b8d61e","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-28T14:09:59.553Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.97","@balena/jellyfish-metrics":"^1.0.359","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.10"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.4","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.23_1635430321682_0.843064855735524","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.24-renovate-major-json-schema-org-5b4f2b382644107cda40ec7135435921bd586f9e":{"name":"@balena/jellyfish-core","version":"7.0.24-renovate-major-json-schema-org-5b4f2b382644107cda40ec7135435921bd586f9e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.24-renovate-major-json-schema-org-5b4f2b382644107cda40ec7135435921bd586f9e","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c976f1f1cbbdfc8f9d91637dd18aa86c0845b140","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.24-renovate-major-json-schema-org-5b4f2b382644107cda40ec7135435921bd586f9e.tgz","fileCount":205,"integrity":"sha512-2NqgUk8O7+Dsik2hG/+z+PyV8uN7NN6xhdoNr+shUxWRP4a7j6AxYq0HdO7t58CGmfVmotqR8B5tBNV1le96iw==","signatures":[{"sig":"MEYCIQCLIwi0DY5jymNBZKJ/5uJbMqawbKgJlUbchupr0jKVnAIhAPBVFL7P0j9l3hbjNHpP+a1XwSd9Uw7IT4gl7YbUSaWO","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":754249},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"5b4f2b382644107cda40ec7135435921bd586f9e","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-28T14:15:31.260Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.97","@balena/jellyfish-metrics":"^1.0.359","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.10"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.4","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.24-renovate-major-json-schema-org-5b4f2b382644107cda40ec7135435921bd586f9e_1635430756827_0.051537407068102725","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.0-joshbwlng-index-locks-6c183e632a097d029d62b6d37eba252ec3ff99c3":{"name":"@balena/jellyfish-core","version":"8.0.0-joshbwlng-index-locks-6c183e632a097d029d62b6d37eba252ec3ff99c3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.0-joshbwlng-index-locks-6c183e632a097d029d62b6d37eba252ec3ff99c3","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b6c1fae32e88005ddbd7adc49319b57a2e3780e0","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.0-joshbwlng-index-locks-6c183e632a097d029d62b6d37eba252ec3ff99c3.tgz","fileCount":205,"integrity":"sha512-SC3RlONBdx8BOrihjGpxDvnqIhgD4dSdXfhdPl4R3DinpK1v2rywUMM4wfdaCPBWQ3FYT8U/oFC5v/6xlzru1w==","signatures":[{"sig":"MEYCIQCD9gb1qgbdsd3KF0sn3BmhfItMPfCA9BlGVvS0DmV08QIhAIcTTt4nqhdKb/KSx7f2UknC7QwvplXEzGERFd/X84il","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":758001},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6c183e632a097d029d62b6d37eba252ec3ff99c3","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-29T01:49:59.168Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.97","@balena/jellyfish-metrics":"^1.0.359","@balena/jellyfish-environment":"^5.0.10"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.4","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.0-joshbwlng-index-locks-6c183e632a097d029d62b6d37eba252ec3ff99c3_1635472372302_0.8385216280356025","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.24-joshbwlng-search-index-version-1e9bbe5b7ec3dcf9337a1f5ba3853e767bff10a3":{"name":"@balena/jellyfish-core","version":"7.0.24-joshbwlng-search-index-version-1e9bbe5b7ec3dcf9337a1f5ba3853e767bff10a3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.24-joshbwlng-search-index-version-1e9bbe5b7ec3dcf9337a1f5ba3853e767bff10a3","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"964a12312c9f3a40445c7971db4ddb750924459e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.24-joshbwlng-search-index-version-1e9bbe5b7ec3dcf9337a1f5ba3853e767bff10a3.tgz","fileCount":205,"integrity":"sha512-8Ga69T4MQ7RHRuDUo2jbtqwTThBSjykB6zTLn/lzl9kcZR/HeTbeypnJQwQ46Ev9ZLZ9DA5fmYdnqzXVjsvrMw==","signatures":[{"sig":"MEUCIQDIymvuN4BfB0JYxmHETb+KgAeskgxgZNsTAmZPZ55Z0QIgHNMUzrmuk0p99YIMeU39/wGUjo37b7UtlYnD873P7c4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":754322},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"1e9bbe5b7ec3dcf9337a1f5ba3853e767bff10a3","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-29T02:30:19.174Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.97","@balena/jellyfish-metrics":"^1.0.359","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.10"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.4","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.24-joshbwlng-search-index-version-1e9bbe5b7ec3dcf9337a1f5ba3853e767bff10a3_1635474824515_0.34723232110606617","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.24-joshbwlng-search-index-version-931218cca9888182c920da922e4b962f0c82f90b":{"name":"@balena/jellyfish-core","version":"7.0.24-joshbwlng-search-index-version-931218cca9888182c920da922e4b962f0c82f90b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.24-joshbwlng-search-index-version-931218cca9888182c920da922e4b962f0c82f90b","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"476024b312158c74d1c71797dcdfc0b5a5b67b69","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.24-joshbwlng-search-index-version-931218cca9888182c920da922e4b962f0c82f90b.tgz","fileCount":205,"integrity":"sha512-baLEBG/k7zc0FuW4nTS2wN94Fcs8HQgGUvHaI2gmu6uQKPcoT5Wy1iEvY8rp5IaDkTBjpvqwKJrG8dXcr/pSjw==","signatures":[{"sig":"MEUCIQDK7RXt2Etr6wBoRVgk0VqlG5hG1WaFTxC14aazIls6pAIgNIOLf7lYY/zkJfvsFTzzXSgZCZ7UehmV8zOvlbPnXQk=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":754315},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"931218cca9888182c920da922e4b962f0c82f90b","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-29T02:34:19.423Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.97","@balena/jellyfish-metrics":"^1.0.359","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.10"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.4","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.24-joshbwlng-search-index-version-931218cca9888182c920da922e4b962f0c82f90b_1635475107400_0.8205345419742223","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.24-joshbwlng-search-index-version-07559c424e7fc85a94f2ce55c7cd1bd19bf37be1":{"name":"@balena/jellyfish-core","version":"7.0.24-joshbwlng-search-index-version-07559c424e7fc85a94f2ce55c7cd1bd19bf37be1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.24-joshbwlng-search-index-version-07559c424e7fc85a94f2ce55c7cd1bd19bf37be1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"765e4bf8c28b57dfeda8b099c27b0f2f2c12cdde","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.24-joshbwlng-search-index-version-07559c424e7fc85a94f2ce55c7cd1bd19bf37be1.tgz","fileCount":205,"integrity":"sha512-zW9zHHdhwrRQ0vjie3j8ZSiZInpCpD+fYyBr60Bzvlkas5rS4pRjeDZKIHBjIYUsU8eZAZKrelTUGLyETeavsQ==","signatures":[{"sig":"MEUCIAiIFGnF5XguDihmpZg9W9ngT+t2oSQy3qj2gR3VDsZAAiEA3Jk5kCcjuVf0UzPghnW3KUnwhi7qW3LkCD5w/Vc0P9k=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":754315},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"07559c424e7fc85a94f2ce55c7cd1bd19bf37be1","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-29T02:45:39.302Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.97","@balena/jellyfish-metrics":"^1.0.359","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.10"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.4","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.24-joshbwlng-search-index-version-07559c424e7fc85a94f2ce55c7cd1bd19bf37be1_1635475741576_0.0910685759078611","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.24-joshbwlng-search-index-version-fadd9ff3775ffd14853c3b0471a5e571dfde6e06":{"name":"@balena/jellyfish-core","version":"7.0.24-joshbwlng-search-index-version-fadd9ff3775ffd14853c3b0471a5e571dfde6e06","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.24-joshbwlng-search-index-version-fadd9ff3775ffd14853c3b0471a5e571dfde6e06","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"7dc66d067e8192d7dea08ac4e717ac8dcfbbd1ba","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.24-joshbwlng-search-index-version-fadd9ff3775ffd14853c3b0471a5e571dfde6e06.tgz","fileCount":205,"integrity":"sha512-1AUEIa47IEqGyTj84hMoVWymZ70X8UmTZVcDn0x7hoczZ2SGPledPjaMu5lhhasjqFG9QJEIzAg8INzV9lSeTg==","signatures":[{"sig":"MEYCIQD3O/6uTSDstgioE2+iLA4tcXwMjvKGGLaUFaFt+P6T1AIhALq9k6uhQVFOnbA2VUT8Wp/GQ2zNI1l+FY59hyD/nwbS","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":754228},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"fadd9ff3775ffd14853c3b0471a5e571dfde6e06","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-29T04:00:58.962Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.97","@balena/jellyfish-metrics":"^1.0.359","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.10"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.4","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.24-joshbwlng-search-index-version-fadd9ff3775ffd14853c3b0471a5e571dfde6e06_1635480251818_0.4750034669970191","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.24-renovate-external-non-major-057cce9c234b37e58c7c3d2979e85b9dffc6a8f6":{"name":"@balena/jellyfish-core","version":"7.0.24-renovate-external-non-major-057cce9c234b37e58c7c3d2979e85b9dffc6a8f6","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.24-renovate-external-non-major-057cce9c234b37e58c7c3d2979e85b9dffc6a8f6","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2a72f230ca9fafbdfc2f231b4b87d9e76718a01b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.24-renovate-external-non-major-057cce9c234b37e58c7c3d2979e85b9dffc6a8f6.tgz","fileCount":205,"integrity":"sha512-WlEcZz8WZjjWHng19ltw40MfVFI+99UykmbI+T66IhosgY4aUcu4Y+a+lPH3NTjzmoou8nV2T60kTXAr4v7FGQ==","signatures":[{"sig":"MEYCIQDpJQqCZ2Gw7e/AgIq++gTBpJ3fZIqJ1VPO7YowIW0jDgIhAK8HfcMWIu2Uk4t8XEspK5p3+vFTbBfOrEHMwEl5gebF","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":754225},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"057cce9c234b37e58c7c3d2979e85b9dffc6a8f6","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-30T00:39:38.522Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.97","@balena/jellyfish-metrics":"^1.0.359","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.10"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.7","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.24-renovate-external-non-major-057cce9c234b37e58c7c3d2979e85b9dffc6a8f6_1635554643397_0.9651546657638819","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.24":{"name":"@balena/jellyfish-core","version":"7.0.24","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.24","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4d8dfd94ccbe8a9346e2594855630de6cdf7caba","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.24.tgz","fileCount":205,"integrity":"sha512-20u2fnaHWd7SFuAjGN288tJ1exc2LwzYRFk8FzRp5jhU6qw7Hy5Xz6gy+T+zXfQrgZsHW4YSFWDAUaBYQkGM3A==","signatures":[{"sig":"MEUCIAQSeriaKjl0O2U8dekNv/JXsLYejDYu9ufRbLgsVT7UAiEAz81qx1j+GGKTpYms0/RllZ8va5EyLAiqW4aUGmqzE78=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":754156},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"23a814db0d84d52128750a0e5945fc7aa5189f40","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-30T00:48:58.954Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.97","@balena/jellyfish-metrics":"^1.0.359","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.10"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.7","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.24_1635555051968_0.7177815256991928","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.25-renovate-major-json-schema-org-2ac84d51b836f1e24bba028814a74309e2d8b410":{"name":"@balena/jellyfish-core","version":"7.0.25-renovate-major-json-schema-org-2ac84d51b836f1e24bba028814a74309e2d8b410","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.25-renovate-major-json-schema-org-2ac84d51b836f1e24bba028814a74309e2d8b410","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9cf5faede50f6d3ca3606f2477a36181ae8a2d13","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.25-renovate-major-json-schema-org-2ac84d51b836f1e24bba028814a74309e2d8b410.tgz","fileCount":205,"integrity":"sha512-HjA9XWgslBJb56/cb0rdqpuDCFfhQDW/k4F4dTN8ykaAYUYjwJ8bLDUuXOh6769RlV0sNaIc/j34fKyQVxRX1w==","signatures":[{"sig":"MEYCIQDyQ5rv3feQa23RM0OkO5Pie5GXlasWDoZXRGjMxTEjWAIhANBV7Jes1jcMx5xfS1dqqyIfZbue5ynZHAvCsvm4hJLg","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":754324},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2ac84d51b836f1e24bba028814a74309e2d8b410","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-30T00:50:46.614Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.97","@balena/jellyfish-metrics":"^1.0.359","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.10"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.7","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.25-renovate-major-json-schema-org-2ac84d51b836f1e24bba028814a74309e2d8b410_1635555291099_0.6866456213845173","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.25-repo-sync-secrets-default-7879c857d4b4a347495c59e1f7626b5b9b650bec":{"name":"@balena/jellyfish-core","version":"7.0.25-repo-sync-secrets-default-7879c857d4b4a347495c59e1f7626b5b9b650bec","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.25-repo-sync-secrets-default-7879c857d4b4a347495c59e1f7626b5b9b650bec","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0b721b09ef876228f20a126257c6815543878022","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.25-repo-sync-secrets-default-7879c857d4b4a347495c59e1f7626b5b9b650bec.tgz","fileCount":205,"integrity":"sha512-GeA4zcjIelFy1rTAHmP5+z4rGwYfhrPDl8sOgMPnFfjWm6Fl0QhIgPYjrIPiLPdvLSJuGyiFNOzlZX4N5nghjw==","signatures":[{"sig":"MEUCIFysMpV5ypWfn1vrD5Hk8iPKzq2UMwCUZ9CAi1u1ckHQAiEAllpLjPnRdzcUHlNhNF+fmRDZFnyooV9farGuXNfQR4c=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":754306},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7879c857d4b4a347495c59e1f7626b5b9b650bec","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-30T03:56:39.988Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.97","@balena/jellyfish-metrics":"^1.0.359","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.10"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.7","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.25-repo-sync-secrets-default-7879c857d4b4a347495c59e1f7626b5b9b650bec_1635566482855_0.7800286770048435","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.25-renovate-internal-patch-e899cdf0ac7042d0715a61c575bcb2db996ae45f":{"name":"@balena/jellyfish-core","version":"7.0.25-renovate-internal-patch-e899cdf0ac7042d0715a61c575bcb2db996ae45f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.25-renovate-internal-patch-e899cdf0ac7042d0715a61c575bcb2db996ae45f","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"132bddb891ef2df057d19079636b76cd4091dc37","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.25-renovate-internal-patch-e899cdf0ac7042d0715a61c575bcb2db996ae45f.tgz","fileCount":205,"integrity":"sha512-L/nO+XcmcjuYTeW5YjtptZqzSr/BlIgTWDeah1N8X51MjCvKDtu/nbYD86i1SfmTG9LBkeO796zWfSBVW/AStA==","signatures":[{"sig":"MEQCIBH44IWZ1QzZ+iYNbyx6qeZ+29N1ptuDW6EtnOAnqblxAiB789WluZ/u8JKVU2AVffgheKbezuJceP970DXZ4MdkeQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":754292},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e899cdf0ac7042d0715a61c575bcb2db996ae45f","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-30T04:55:30.683Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.99","@balena/jellyfish-metrics":"^1.0.362","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.11"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.7","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.25-renovate-internal-patch-e899cdf0ac7042d0715a61c575bcb2db996ae45f_1635569944539_0.2652189397600335","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.25-renovate-internal-patch-6d019a1e7af09e0bdf741e5c2f0f888bf16c69f8":{"name":"@balena/jellyfish-core","version":"7.0.25-renovate-internal-patch-6d019a1e7af09e0bdf741e5c2f0f888bf16c69f8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.25-renovate-internal-patch-6d019a1e7af09e0bdf741e5c2f0f888bf16c69f8","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"44420c6e3710a574d6af766377b7c0f492bedf3f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.25-renovate-internal-patch-6d019a1e7af09e0bdf741e5c2f0f888bf16c69f8.tgz","fileCount":205,"integrity":"sha512-IRhpFcdZuEdOCaekgbw5W5UIy76LDP1fcBTOwHVTdejoRMHaQnXFiqSD+0lpmVW+MNR76DkSpvGpIrL6wX+QZw==","signatures":[{"sig":"MEYCIQDi0k1yHXi1wP2lIlGOpd8tEbkkp8DguJuUGymuB/BOgQIhAKN4aAlM8WoxeIQsHrmxa+pPQPJEY4t8y/4g5999leyF","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":754292},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6d019a1e7af09e0bdf741e5c2f0f888bf16c69f8","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-30T10:14:00.848Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.99","@balena/jellyfish-metrics":"^1.0.363","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.11"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.7","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.25-renovate-internal-patch-6d019a1e7af09e0bdf741e5c2f0f888bf16c69f8_1635589076116_0.5624750602741595","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.25":{"name":"@balena/jellyfish-core","version":"7.0.25","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.25","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"dd1c7df4a8b491f2dc002a9253a5af08c3e28364","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.25.tgz","fileCount":205,"integrity":"sha512-ns5ReLgBjdQiwdYa0wfkBuozUVpQha4aT9K/gAWqXdHt7NLdxqpMsk+4GRHczFJ1+Ch7o9M1hv3Bh+YOTlxYDA==","signatures":[{"sig":"MEYCIQDVrOE2qb3jQu9Eu4Lhtz8zPzo7H2AO66Xg9rrBX7tBoAIhALmd9t2FIGGpeU2XnKCx/d3db+o26Rm/P0aLR7HfDhKi","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":754227},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ec7f25228fdbf9ef4d1c534bb5b4af270b284d1f","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-30T10:22:59.722Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.99","@balena/jellyfish-metrics":"^1.0.363","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.11"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.7","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.25_1635589508053_0.6678859287487016","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.26-renovate-major-json-schema-org-de05a5f524620e54e152c1157ffb31e8d12c8dd4":{"name":"@balena/jellyfish-core","version":"7.0.26-renovate-major-json-schema-org-de05a5f524620e54e152c1157ffb31e8d12c8dd4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.26-renovate-major-json-schema-org-de05a5f524620e54e152c1157ffb31e8d12c8dd4","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"54da834bf6574d80194466320d6f6db11a661a5a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.26-renovate-major-json-schema-org-de05a5f524620e54e152c1157ffb31e8d12c8dd4.tgz","fileCount":205,"integrity":"sha512-Gse036UzFh8YN7d8MLKpGClt2rNmSwXBUkZslCj6gZfu9JFcC+nZ/5ak0chtyrrUFJ8C7PEILpzMYtdYLhKrlg==","signatures":[{"sig":"MEUCIQCz7Vzzr5ju5cR6akSxh+8HyaNq9SqMV7Vi0cn7vt4msQIgfpm+GctF9w0cttV/8qRU1zrJw8sq13jSew1dddomh9A=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":754395},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"de05a5f524620e54e152c1157ffb31e8d12c8dd4","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-30T10:26:59.027Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.99","@balena/jellyfish-metrics":"^1.0.363","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.11"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.7","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.26-renovate-major-json-schema-org-de05a5f524620e54e152c1157ffb31e8d12c8dd4_1635589821552_0.49987937465715704","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.26-repo-sync-secrets-default-bc54e6578a1ccff7249f9da400b96a188cab1f63":{"name":"@balena/jellyfish-core","version":"7.0.26-repo-sync-secrets-default-bc54e6578a1ccff7249f9da400b96a188cab1f63","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.26-repo-sync-secrets-default-bc54e6578a1ccff7249f9da400b96a188cab1f63","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a58862467f42a31e777c8e6ca9a30916aa7bbbca","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.26-repo-sync-secrets-default-bc54e6578a1ccff7249f9da400b96a188cab1f63.tgz","fileCount":205,"integrity":"sha512-S8ff/VZnXQcVf8vgbWZDkJ1iRY1yrFXbqObdOS+n/iJR0xXGsDAK0pqn6oJ7xVgn69pEWlvHD8DVsduwAId77g==","signatures":[{"sig":"MEYCIQD5A3utwNuoyMLWQfZ7uGpAzBbKwTPbO1hidzJU2lVvAgIhAIXaQ4P47dMAlkKj/VWySmWrJIPuTyYjcdT/2Q5MxPgb","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":754377},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"bc54e6578a1ccff7249f9da400b96a188cab1f63","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-30T12:45:05.924Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.99","@balena/jellyfish-metrics":"^1.0.363","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.11"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.7","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.26-repo-sync-secrets-default-bc54e6578a1ccff7249f9da400b96a188cab1f63_1635598173862_0.11774816878043004","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.26":{"name":"@balena/jellyfish-core","version":"7.0.26","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.26","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d99f462e5f14335e940088a24998b4e0f10ec6c3","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.26.tgz","fileCount":205,"integrity":"sha512-HrsOX7V5P7KkMyClT0cPhPHUQR/wcQttirV3R5McP9wmQfp8zjF7RYucH+/ZHCexmb6xDcJTPhCpnQVFjDyl7w==","signatures":[{"sig":"MEQCIB7w6yfDsXckIv330Rrk8B0D3ejuZVGGXQwWaAhR7haxAiBZiJiJjeZgWTmEhP2omxEkHYaw/618J7NfCiTRpPbjtw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":754310},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ead898f76b44fb0e40960440856d40cad1bf7800","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-30T13:04:49.005Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.99","@balena/jellyfish-metrics":"^1.0.363","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.11"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.7","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.26_1635599202474_0.8594352764283091","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.27-renovate-major-json-schema-org-142eed7c5e44213e009681780de6a06261b53180":{"name":"@balena/jellyfish-core","version":"7.0.27-renovate-major-json-schema-org-142eed7c5e44213e009681780de6a06261b53180","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.27-renovate-major-json-schema-org-142eed7c5e44213e009681780de6a06261b53180","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c14e23168eef3e6e8ff2b7fe04fbb04581ae0993","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.27-renovate-major-json-schema-org-142eed7c5e44213e009681780de6a06261b53180.tgz","fileCount":205,"integrity":"sha512-JyBOl295xrsy9QTaSMSSbyUt4/bkhnB0WIWJWsRDh55ofVw4SzBrd7xwo77Lodr5AVrsUc+8ZcvvU4kJOQ8IPg==","signatures":[{"sig":"MEUCIQCzIeT2WMIHGo3jTjXXxyZ+zGjXJ0+UmO0kDVscrzuE0wIgeeHTCRUdXRVr23Sj/gNFccZCM2kqQqKDsZRIQWyXw3U=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":754478},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"142eed7c5e44213e009681780de6a06261b53180","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-10-30T13:10:40.142Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.99","@balena/jellyfish-metrics":"^1.0.363","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.11"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.7","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.27-renovate-major-json-schema-org-142eed7c5e44213e009681780de6a06261b53180_1635599592881_0.3731531758801012","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.0-joshbwlng-index-locks-5250c6c8c88f8ae4cbb72bb81e908dc06c135d30":{"name":"@balena/jellyfish-core","version":"8.0.0-joshbwlng-index-locks-5250c6c8c88f8ae4cbb72bb81e908dc06c135d30","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.0-joshbwlng-index-locks-5250c6c8c88f8ae4cbb72bb81e908dc06c135d30","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d5573e0c961c22662631b58fdba9961032723535","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.0-joshbwlng-index-locks-5250c6c8c88f8ae4cbb72bb81e908dc06c135d30.tgz","fileCount":205,"integrity":"sha512-3LIy0ZkAbpJy52Wj+txytH4NLXVRHmiC3KSLtMwprrstAbln0rd9HICvBOhfZEpLu2MAvSHVe9PnFCX0ca0Crg==","signatures":[{"sig":"MEUCIHtbMLJjkDgHeHMeMvowUMXmaREsjC4ASrcpxJanyFDcAiEAxjeI6emCUfllZ4sf92/ElXk5dt5f6j6tTc9yVgsMryQ=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":756011},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"5250c6c8c88f8ae4cbb72bb81e908dc06c135d30","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-04T03:20:15.396Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.97","@balena/jellyfish-metrics":"^1.0.359","@balena/jellyfish-environment":"^5.0.10"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.6","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.4","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.6.1","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.0-joshbwlng-index-locks-5250c6c8c88f8ae4cbb72bb81e908dc06c135d30_1635996202081_0.7600432966983053","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.0-joshbwlng-index-locks-17b1dc6f44fddd06ecc32aeb8d43d7be41e857f6":{"name":"@balena/jellyfish-core","version":"8.0.0-joshbwlng-index-locks-17b1dc6f44fddd06ecc32aeb8d43d7be41e857f6","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.0-joshbwlng-index-locks-17b1dc6f44fddd06ecc32aeb8d43d7be41e857f6","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0e38a98c5278c2da9f104ac31648dbd077af4a89","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.0-joshbwlng-index-locks-17b1dc6f44fddd06ecc32aeb8d43d7be41e857f6.tgz","fileCount":205,"integrity":"sha512-hZH5PHIko+0IiNok0UkLEHySvBYrPcrBnPPWZArB8E3xsR9pgA1lEN0cWH753Q8E39N84x/mHx1vKXxdo1GYVw==","signatures":[{"sig":"MEYCIQCn0WDmCuhEIn6+bul2qCoQ8Ps8E+UPKrsz8EA3BHDP+QIhAO/wBkXC0Lr2vIpgy2M/O0vGS82qLlSpB8KczCDDPMbf","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":756240},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"17b1dc6f44fddd06ecc32aeb8d43d7be41e857f6","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-04T03:47:01.416Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.99","@balena/jellyfish-metrics":"^1.0.363","@balena/jellyfish-environment":"^5.0.11"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.7","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.0-joshbwlng-index-locks-17b1dc6f44fddd06ecc32aeb8d43d7be41e857f6_1635997878702_0.9042522005731592","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.0-joshbwlng-index-locks-f585d5740fa6c5151059760304274a048438d5d0":{"name":"@balena/jellyfish-core","version":"8.0.0-joshbwlng-index-locks-f585d5740fa6c5151059760304274a048438d5d0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.0-joshbwlng-index-locks-f585d5740fa6c5151059760304274a048438d5d0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8e89d0674423406e5edbe25c2415c32ce8fafa8d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.0-joshbwlng-index-locks-f585d5740fa6c5151059760304274a048438d5d0.tgz","fileCount":205,"integrity":"sha512-1CwB4GcNN8jgfVM+P4clR9OsYSEzrIVJp6Y8NkR2yMJltHka/rj+TJLzwJ4wMbugKEtwW8P55wcy8/+0tdgZWQ==","signatures":[{"sig":"MEQCIAYQsSZZVo+Uu08pzqNcZ1B3gUelifH7B8/WSvmXQB3nAiBUNRYhNopNUbi/NmauGeJMQGCC0uNOALEfK1E6AXM/Rg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":757389},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f585d5740fa6c5151059760304274a048438d5d0","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-04T09:18:30.442Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.99","@balena/jellyfish-metrics":"^1.0.363","@balena/jellyfish-environment":"^5.0.11"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.7","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.0-joshbwlng-index-locks-f585d5740fa6c5151059760304274a048438d5d0_1636017726664_0.2152990511618902","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.27-joshbwlng-search-index-version-9ee46bca7cc5d38981c7f2b726ac38c14e877033":{"name":"@balena/jellyfish-core","version":"7.0.27-joshbwlng-search-index-version-9ee46bca7cc5d38981c7f2b726ac38c14e877033","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.27-joshbwlng-search-index-version-9ee46bca7cc5d38981c7f2b726ac38c14e877033","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2bef4b76b37ec4d37910ea6f819616fb0d390e38","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.27-joshbwlng-search-index-version-9ee46bca7cc5d38981c7f2b726ac38c14e877033.tgz","fileCount":205,"integrity":"sha512-Wf4wKZcnlrg13W0JCEsB51cMh5JiP6HMZKw4ZVzYoSY6LjfYQ8UAGy4Me0vVFYvJyd0w4xUbeNxYulG4Gsql9A==","signatures":[{"sig":"MEYCIQCM5mGSQFr7eN/a/7E3tQUcoeWlpe/HbySSLuk/2lTLcwIhAMjBt4PYLMLj/O8C+O9/R9cnUaq/XeT5ZOeQDFtIu/D+","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":754457},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"9ee46bca7cc5d38981c7f2b726ac38c14e877033","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-05T04:22:58.066Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.99","@balena/jellyfish-metrics":"^1.0.363","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.11"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.7","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.27-joshbwlng-search-index-version-9ee46bca7cc5d38981c7f2b726ac38c14e877033_1636086535319_0.5682423703050283","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.27":{"name":"@balena/jellyfish-core","version":"7.0.27","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.27","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"97bb8f77839c58af8f94a939f5e2d1d20e7a7a26","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.27.tgz","fileCount":205,"integrity":"sha512-n/bJpHKRnWZxZK2nWib69ouPk3je/V7Yk00Onv44yQLqdFMkX9pL1IlydS6UWQ4X/VlL6jhe4sQVA747RBSigA==","signatures":[{"sig":"MEQCICcxWxSZu+c8WhBPPhI817477mNyERFHdwga1AiTykXXAiADn12fsdvpSH2Rzqs52BnyR55IHT3/kOXGC2uG5o4wRw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":754385},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f89ea12ee96fa83bc6b2d72969b0f238f09a3dfd","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-05T05:16:21.334Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.99","@balena/jellyfish-metrics":"^1.0.363","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.11"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.7","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.27_1636089545812_0.5167607766902176","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"7.0.28-renovate-major-json-schema-org-9203f29501d03f998c3ea4a48278d2cc60a3f3c4":{"name":"@balena/jellyfish-core","version":"7.0.28-renovate-major-json-schema-org-9203f29501d03f998c3ea4a48278d2cc60a3f3c4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@7.0.28-renovate-major-json-schema-org-9203f29501d03f998c3ea4a48278d2cc60a3f3c4","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"986427471c749262b3c6d2391508b0a89cc84f06","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-7.0.28-renovate-major-json-schema-org-9203f29501d03f998c3ea4a48278d2cc60a3f3c4.tgz","fileCount":205,"integrity":"sha512-wqvHKcjHW2Z6dOeDoqKIGN6QROEOyayyp6KPfoIbbU6FcGhU4DLN9YlyWafyC+RV/PKpT0zqRNC6K4qTQdTynw==","signatures":[{"sig":"MEUCIAlrargzvKMRRjrrEgjcYqKYunWZcJUcVhPNUvWcDNdSAiEA47UOV7zAYcRBLypslUOHToc0MXBNozoiN9GCnx0LHAU=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":754553},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"9203f29501d03f998c3ea4a48278d2cc60a3f3c4","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-05T05:20:35.060Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.99","@balena/jellyfish-metrics":"^1.0.363","@jsbits/get-package-version":"^1.0.3","@balena/jellyfish-environment":"^5.0.11"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.7","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_7.0.28-renovate-major-json-schema-org-9203f29501d03f998c3ea4a48278d2cc60a3f3c4_1636089774143_0.013333886286873264","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.0-joshbwlng-index-locks-9fb7b9e5d3a262746604a9214d51676773fa3737":{"name":"@balena/jellyfish-core","version":"8.0.0-joshbwlng-index-locks-9fb7b9e5d3a262746604a9214d51676773fa3737","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.0-joshbwlng-index-locks-9fb7b9e5d3a262746604a9214d51676773fa3737","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"246f6a3fa5cc0b9a0f813458f3fd2fc6938ee0a8","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.0-joshbwlng-index-locks-9fb7b9e5d3a262746604a9214d51676773fa3737.tgz","fileCount":205,"integrity":"sha512-XE5CNHYmdE87JA7g6o2WSIo72jh/rnezjxBcrAkyMXk4R3XJdUqHBj1uIEgQQbyeAPWrf1QdI42hh+H6whk9QQ==","signatures":[{"sig":"MEUCIQCipstHuLSmf/DC9EqRiheZhJodZ66fCzGbX9egEMq1ZwIgGMlqhPi4AbwIlgZ7WJw8rFXJHmzKCjS/ZjPZSoz9Kw4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":757276},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"9fb7b9e5d3a262746604a9214d51676773fa3737","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-05T05:30:25.275Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.99","@balena/jellyfish-metrics":"^1.0.363","@balena/jellyfish-environment":"^5.0.11"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.7","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.0-joshbwlng-index-locks-9fb7b9e5d3a262746604a9214d51676773fa3737_1636090399539_0.0038908669951269736","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.0":{"name":"@balena/jellyfish-core","version":"8.0.0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"245b29cddc038e7460cb4fc80558335e9ee0814e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.0.tgz","fileCount":205,"integrity":"sha512-2D6qiVYpXfsBxGSFg0qYyQnUcEoeN6FRIsHZTA91fwGpSZjmigO0yku2J4q5sa4C8QE+1rgDvgd9Iquj6RK5Rg==","signatures":[{"sig":"MEYCIQCvOu6belObPLTZb0F731xUCtWWNQ6770rWJmBvapz6tAIhAPXAcSVLpubEOjW2hJ0trFDk6bEhGOmjT3C3SEXKu6Lv","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":757213},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e8d605948a9db81bea8516bfe78204e63601a36b","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-05T08:37:44.278Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.99","@balena/jellyfish-metrics":"^1.0.363","@balena/jellyfish-environment":"^5.0.11"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.7","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.0_1636101600181_0.708049799078529","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.1-renovate-major-json-schema-org-15c7d8afcff7a23ff557f2a80fb07cc80fb2c489":{"name":"@balena/jellyfish-core","version":"8.0.1-renovate-major-json-schema-org-15c7d8afcff7a23ff557f2a80fb07cc80fb2c489","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.1-renovate-major-json-schema-org-15c7d8afcff7a23ff557f2a80fb07cc80fb2c489","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"490116f854d02b2342fee85f6f6ad7716c2200a2","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.1-renovate-major-json-schema-org-15c7d8afcff7a23ff557f2a80fb07cc80fb2c489.tgz","fileCount":205,"integrity":"sha512-Q2tczaOap/yITB9yuYi60Zu91SK2djSLSj5NAppk+7CRdWQpQEHHlev+ZFPYkYGApI8IjyymouEqlNlNtq6OKw==","signatures":[{"sig":"MEUCIQCwiIeugEfKFWPeQKQr3f6EpMFJ/Anljd4Y9NwLjlR7qwIgSMpbg5VhZQBhHDWN6F+a6YB99+LpjDVKwweEiqxANsM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":757380},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"15c7d8afcff7a23ff557f2a80fb07cc80fb2c489","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-05T08:39:56.580Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.99","@balena/jellyfish-metrics":"^1.0.363","@balena/jellyfish-environment":"^5.0.11"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.7","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.1-renovate-major-json-schema-org-15c7d8afcff7a23ff557f2a80fb07cc80fb2c489_1636101841174_0.20968909663419155","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.1-renovate-internal-patch-55fefc7c66f3f527d036674c07345cc2f01a8dfd":{"name":"@balena/jellyfish-core","version":"8.0.1-renovate-internal-patch-55fefc7c66f3f527d036674c07345cc2f01a8dfd","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.1-renovate-internal-patch-55fefc7c66f3f527d036674c07345cc2f01a8dfd","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1e07500d224242633ce286b81c2f645006a78f76","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.1-renovate-internal-patch-55fefc7c66f3f527d036674c07345cc2f01a8dfd.tgz","fileCount":205,"integrity":"sha512-rxcv+3Rq1zlWzHiolAxuNrNyZG7f8zhVbEKal9ns4DaONVYowlsFHq5nJIbFQeJXYfgyDCsiva605Uch+AOhow==","signatures":[{"sig":"MEQCIAUVCYuhwdgY/MQ0zsL/vKhTUpj3zkqt/22qczzZXzhBAiBnvOPwQlR0olNt3ft0j4sKBwerhz55JWNRBSkJlvhkWg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":757349},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"55fefc7c66f3f527d036674c07345cc2f01a8dfd","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-05T23:57:41.503Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.100","@balena/jellyfish-metrics":"^1.0.364","@balena/jellyfish-environment":"^5.0.12"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.7","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.1-renovate-internal-patch-55fefc7c66f3f527d036674c07345cc2f01a8dfd_1636156914639_0.5251698131331215","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.1":{"name":"@balena/jellyfish-core","version":"8.0.1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"cd242dcaf76010758455b3bc2ff866198efe3ae7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.1.tgz","fileCount":205,"integrity":"sha512-cSj+PMVcVKRR1ir1YFEV870jYGP01fhKPoAldjzG9HCD4XLhHtZogGSLjYWO1w54+hj9tt5+kp9rA2NX4x6Zgg==","signatures":[{"sig":"MEYCIQC+evTmip+SV1tfuck+55PFq7gL/JJQ8hcvqhYCaO0oZAIhAP+AwsWdoLjVVK2ppoX2DtQJNk/HKs/oXLGBs9Qs6z+A","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":757284},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"50db3abcdc3febeab88a0d26a70963ea25367fae","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-06T00:07:52.083Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.100","@balena/jellyfish-metrics":"^1.0.364","@balena/jellyfish-environment":"^5.0.12"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.7","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.1_1636157392017_0.9219402563283954","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.1-renovate-major-json-schema-org-b2506df40be4367dd6e6e15525d22cac770cfdc4":{"name":"@balena/jellyfish-core","version":"8.0.1-renovate-major-json-schema-org-b2506df40be4367dd6e6e15525d22cac770cfdc4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.1-renovate-major-json-schema-org-b2506df40be4367dd6e6e15525d22cac770cfdc4","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"7f103a2a05aefcba3916a0dc5b9dd9e6423c5247","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.1-renovate-major-json-schema-org-b2506df40be4367dd6e6e15525d22cac770cfdc4.tgz","fileCount":205,"integrity":"sha512-7KTySuW7T46n4rvNirMr/pc6YGRQaDHH/PBD/HbkNkTtFBrTqQODaIalAKSJV7KdP6zw5Awc92bc/h6QnJl28A==","signatures":[{"sig":"MEQCIBwlB8g85yc0sk5rAX3a1FHRJnu7fqX97seinxNfpLqpAiBGSocpfIyYRcmPoM3f/wT/pOlQjss+FUYMxozSVS8AVw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":757427},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b2506df40be4367dd6e6e15525d22cac770cfdc4","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-06T00:09:08.263Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.100","@balena/jellyfish-metrics":"^1.0.364","@balena/jellyfish-environment":"^5.0.12"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.7","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.1-renovate-major-json-schema-org-b2506df40be4367dd6e6e15525d22cac770cfdc4_1636157612777_0.23080983356015783","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.2-renovate-major-json-schema-org-5eb07b455ad7686b2f61ba4a016a06146e8640e2":{"name":"@balena/jellyfish-core","version":"8.0.2-renovate-major-json-schema-org-5eb07b455ad7686b2f61ba4a016a06146e8640e2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.2-renovate-major-json-schema-org-5eb07b455ad7686b2f61ba4a016a06146e8640e2","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"577b19630398dad4c03beb2db6e6e57b0cb58648","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.2-renovate-major-json-schema-org-5eb07b455ad7686b2f61ba4a016a06146e8640e2.tgz","fileCount":205,"integrity":"sha512-2OzGbASSmWrEfpRdrkTNd5K/g5gH/o2fifBjwL39sbL3QU44+JW64sxZLYcNmf9i8cYlREKf+ZXL5VgkJw98iQ==","signatures":[{"sig":"MEUCIEJDP95pxRHV2g9LETFBGjB+hEV0oZrbupjv7XZKdbDeAiEAxKbUlK8JPDnEv1lk67MvvyO9h42Cb7Zr/sfru4WgdRo=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":757451},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"5eb07b455ad7686b2f61ba4a016a06146e8640e2","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-06T00:12:18.284Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.100","@balena/jellyfish-metrics":"^1.0.364","@balena/jellyfish-environment":"^5.0.12"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.7","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.2-renovate-major-json-schema-org-5eb07b455ad7686b2f61ba4a016a06146e8640e2_1636157697572_0.8545040320816555","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.2-renovate-internal-patch-5ffd0c85bd4625ffb95ccabc02ac4b98143e7cb5":{"name":"@balena/jellyfish-core","version":"8.0.2-renovate-internal-patch-5ffd0c85bd4625ffb95ccabc02ac4b98143e7cb5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.2-renovate-internal-patch-5ffd0c85bd4625ffb95ccabc02ac4b98143e7cb5","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3493f4d7eefea1ef3c44bd4e8504f0ead2efc34f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.2-renovate-internal-patch-5ffd0c85bd4625ffb95ccabc02ac4b98143e7cb5.tgz","fileCount":205,"integrity":"sha512-xBbDeIPYMIU8jfEpUvdAYTn53L8Go/lP6PlQ1X9teCq1SyBbz4ofDVnwTqSFRJDf5tOabK96bJZlBywK2YOI4g==","signatures":[{"sig":"MEYCIQCuG6pmMLSwc3uMnopnwEJImxaE4WvmTJfRwknmOm3UhgIhAIkFj9Mg6AuhSbUBuXZGA76LVukhwimrONo1+7YViOKU","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":757452},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"5ffd0c85bd4625ffb95ccabc02ac4b98143e7cb5","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-06T03:13:23.315Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.101","@balena/jellyfish-metrics":"^1.0.364","@balena/jellyfish-environment":"^5.0.12"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.7","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.2-renovate-internal-patch-5ffd0c85bd4625ffb95ccabc02ac4b98143e7cb5_1636168590806_0.25170733016895164","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.2":{"name":"@balena/jellyfish-core","version":"8.0.2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.2","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3fb07403c7c942025d955d5ceb47d92dc67aa866","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.2.tgz","fileCount":205,"integrity":"sha512-ongTFpTGvmqzGkFTDcnb7MPG7RCktnedcRJ/k6ODoMud63hSLSpAQ32PZlqYvHy0Uf17ri1AGb46/xhOVnfl0w==","signatures":[{"sig":"MEUCIFfui7+88e5fIK7CTw3h/svtNmRk18bLEkhYViHDtUulAiEAmgo/uqMRDZbjKU/ru33UksjK3bEVhny/Yp9Ysu3ahXg=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":757387},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"38bf6c600e0dfc7126fef2a9d53494fcd9983079","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-06T03:21:10.454Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.101","@balena/jellyfish-metrics":"^1.0.364","@balena/jellyfish-environment":"^5.0.12"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.7","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.2_1636168977519_0.1597428422331555","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.3-renovate-major-json-schema-org-f4ef331d94edba4cebbed6aed2ec7df2779866a7":{"name":"@balena/jellyfish-core","version":"8.0.3-renovate-major-json-schema-org-f4ef331d94edba4cebbed6aed2ec7df2779866a7","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.3-renovate-major-json-schema-org-f4ef331d94edba4cebbed6aed2ec7df2779866a7","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"14699f4940e6ab0689511f5f935ffe2d7517b5d4","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.3-renovate-major-json-schema-org-f4ef331d94edba4cebbed6aed2ec7df2779866a7.tgz","fileCount":205,"integrity":"sha512-vcr2xPfkkticX+VsYdlfQEdfFFSJ8YvcGJ+sjNRmCCsrCXginjWh9lsE25ZsWAw46ti5KhTdUsJxNIoZ+hE4uQ==","signatures":[{"sig":"MEUCIAthzhccx8RYLxYXS3e23NW/coaaLSqahYkvLvbI15OuAiEAmBAsfPF9gMW6kxvlBKW1hGRzxrVwMVySm4HtJ5IyYgU=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":757554},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f4ef331d94edba4cebbed6aed2ec7df2779866a7","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-06T03:24:25.089Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.101","@balena/jellyfish-metrics":"^1.0.364","@balena/jellyfish-environment":"^5.0.12"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.7","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.3-renovate-major-json-schema-org-f4ef331d94edba4cebbed6aed2ec7df2779866a7_1636169252259_0.0063347256484029835","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.3-renovate-internal-patch-4e9955004d9503e4e8236ffd489477c5de449614":{"name":"@balena/jellyfish-core","version":"8.0.3-renovate-internal-patch-4e9955004d9503e4e8236ffd489477c5de449614","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.3-renovate-internal-patch-4e9955004d9503e4e8236ffd489477c5de449614","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"fe6a07401ae887335a99bfe6f836678982b093eb","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.3-renovate-internal-patch-4e9955004d9503e4e8236ffd489477c5de449614.tgz","fileCount":205,"integrity":"sha512-Nzp3yW10SGL3Gp/C3A22Yb/bm0fv4XtDA6eAqYxAmxeRwkDSKDRsnwaEa1R6CPIt9aF1Z1mBPpjrnVfKi701gA==","signatures":[{"sig":"MEUCIAKIZ8sWkfY/uy5bjCy21Yg20VWmGgdl2wX024BBlE42AiEAjNZk6JNunRCLg7RVGBW90RJIIwsgb/EFn8C3/90x5wc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":757556},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4e9955004d9503e4e8236ffd489477c5de449614","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-06T06:04:42.392Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.101","@balena/jellyfish-metrics":"^1.0.365","@balena/jellyfish-environment":"^5.0.12"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.7","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.3-renovate-internal-patch-4e9955004d9503e4e8236ffd489477c5de449614_1636178829647_0.39304465048002935","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.3":{"name":"@balena/jellyfish-core","version":"8.0.3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.3","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6276ec4b620ffdee9b332cd295624d2444aaabb3","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.3.tgz","fileCount":205,"integrity":"sha512-aj2YR02FSdQa3UAW17KeI3RHGOxuLZzxvo1f4HHL+T0SXSnMPPlJFSpKgdAXqj6aOf5WcrAz+uJfcQJH93tXYQ==","signatures":[{"sig":"MEQCIFgEJIrUzC4wUTpvsrdlvroh+pROmLQqxYGD3KfQEHQBAiAObnvlzsb6XTvAlIq8bFcd9hWJRndeo0yr3RNCuHS1cw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":757491},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ace2abee371bf5a5dba222d2ae959c3b80824019","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-06T06:12:13.054Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.101","@balena/jellyfish-metrics":"^1.0.365","@balena/jellyfish-environment":"^5.0.12"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.7","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.3_1636179235332_0.4558452418813592","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.4-renovate-major-json-schema-org-86c4f11e4f10a6cd09e2cbcf0f0c8e3feb79b689":{"name":"@balena/jellyfish-core","version":"8.0.4-renovate-major-json-schema-org-86c4f11e4f10a6cd09e2cbcf0f0c8e3feb79b689","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.4-renovate-major-json-schema-org-86c4f11e4f10a6cd09e2cbcf0f0c8e3feb79b689","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"edc0a35d3e94391ebf52a7742dc84f9fe2a763dd","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.4-renovate-major-json-schema-org-86c4f11e4f10a6cd09e2cbcf0f0c8e3feb79b689.tgz","fileCount":205,"integrity":"sha512-I/blbm6bSiVaEojODcAKCcNorhznFnaTCh81y5sRcI9Mk1i3R76hoGpFQA7IPykwAjtuZNgArRgCvG81Rs9EIg==","signatures":[{"sig":"MEUCIQC2nhoraXau45frobERbjdqN4aSqXEBvyi298J3aVVNhQIgE7OvAbpHKq7SzB5FO9cJAZfFrmMkfP4xqKuJFlRhqy8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":757658},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"86c4f11e4f10a6cd09e2cbcf0f0c8e3feb79b689","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-06T06:15:21.876Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.101","@balena/jellyfish-metrics":"^1.0.365","@balena/jellyfish-environment":"^5.0.12"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.7","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.4-renovate-major-json-schema-org-86c4f11e4f10a6cd09e2cbcf0f0c8e3feb79b689_1636179514632_0.16291228230685406","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.4-renovate-external-non-major-b96632a698e106b933f5368f4a70788b51c81cba":{"name":"@balena/jellyfish-core","version":"8.0.4-renovate-external-non-major-b96632a698e106b933f5368f4a70788b51c81cba","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.4-renovate-external-non-major-b96632a698e106b933f5368f4a70788b51c81cba","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a131143dce0d08efa774f909e0e9196c5381f3cd","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.4-renovate-external-non-major-b96632a698e106b933f5368f4a70788b51c81cba.tgz","fileCount":205,"integrity":"sha512-FlRrCoR0B7OkHFNL/MoDpWAwPdiaZ1XTtV8Iq8qYGzbNYZa/3i2zsAOzQyqhpAiEVoDMYkOdqQkX5mA5FEmx0A==","signatures":[{"sig":"MEUCIQDtKicX/gdNZc9lifu7z5k50zVWZxKaYcoYqQbrEkL1IwIgYQtsjBE3MDHu4UoaJBLSt7rpHZCY5AMqO5j3VBmw3UY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":757645},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b96632a698e106b933f5368f4a70788b51c81cba","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-07T01:16:04.960Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.101","@balena/jellyfish-metrics":"^1.0.365","@balena/jellyfish-environment":"^5.0.12"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.8","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.4-renovate-external-non-major-b96632a698e106b933f5368f4a70788b51c81cba_1636248035042_0.10088161550554564","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.4-repo-sync-jellyfish-config-default-d3d63012d5265a7975df11c2e3a4516276f7d6b2":{"name":"@balena/jellyfish-core","version":"8.0.4-repo-sync-jellyfish-config-default-d3d63012d5265a7975df11c2e3a4516276f7d6b2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.4-repo-sync-jellyfish-config-default-d3d63012d5265a7975df11c2e3a4516276f7d6b2","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"bf87100d0a22c9e0277f38bf44f1909a235634c7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.4-repo-sync-jellyfish-config-default-d3d63012d5265a7975df11c2e3a4516276f7d6b2.tgz","fileCount":205,"integrity":"sha512-3+NYI+4ruuBJ9UqWp2LkOuPPdSJhc46KE1OEVwww0MFMmj0hbIM6fyoCj1OKuNrohZxID+JEbjavvBnzibed+Q==","signatures":[{"sig":"MEUCIQDY0fo7CIaVOt+8NUeYYbZErRbnUVI3S/kmPJw77ZkBzAIgQOTW4wXGapak3tTx7bGtq3o6YpveOu9UKrCfwDA98Jo=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":757658},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d3d63012d5265a7975df11c2e3a4516276f7d6b2","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-07T05:11:12.080Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.101","@balena/jellyfish-metrics":"^1.0.365","@balena/jellyfish-environment":"^5.0.12"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.7","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.4-repo-sync-jellyfish-config-default-d3d63012d5265a7975df11c2e3a4516276f7d6b2_1636262127020_0.6267061144228783","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.4-renovate-internal-patch-c36137e77c0d737923b1f7783cbad821e8851e1e":{"name":"@balena/jellyfish-core","version":"8.0.4-renovate-internal-patch-c36137e77c0d737923b1f7783cbad821e8851e1e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.4-renovate-internal-patch-c36137e77c0d737923b1f7783cbad821e8851e1e","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"09da6d4bb611c4810da0d1492a518742f32b4848","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.4-renovate-internal-patch-c36137e77c0d737923b1f7783cbad821e8851e1e.tgz","fileCount":205,"integrity":"sha512-ApZLQcmQztH4uBRcYSpQfA7D5HQIHWCTSifwHweLI8Nyrm8EhgXLGFn3hztk7d/eIwUIPewZBNto2XmO06Hhew==","signatures":[{"sig":"MEUCIBaaS2AHalDwfNej6jNzXmBzAygR70KMXwtb9cjf/UQPAiEAmMmnENlFdNX1vj4Nx247rUapGrqNPRyO9K2vTlpj1W4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":757626},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c36137e77c0d737923b1f7783cbad821e8851e1e","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-08T22:28:28.314Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.3.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.103","@balena/jellyfish-metrics":"^1.0.367","@balena/jellyfish-environment":"^5.0.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.7","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.4-renovate-internal-patch-c36137e77c0d737923b1f7783cbad821e8851e1e_1636410750955_0.8002858393494316","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.4-renovate-external-non-major-6097df070da764e5df74dc87663f6c7dbbc7de81":{"name":"@balena/jellyfish-core","version":"8.0.4-renovate-external-non-major-6097df070da764e5df74dc87663f6c7dbbc7de81","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.4-renovate-external-non-major-6097df070da764e5df74dc87663f6c7dbbc7de81","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"083a0fbc3fd7ff883a0eb4bcd1f6081a7e322007","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.4-renovate-external-non-major-6097df070da764e5df74dc87663f6c7dbbc7de81.tgz","fileCount":205,"integrity":"sha512-+hiOizuj8SQR8jWLvKByWypcOKj0orxqT09Rby18C0f0zibqUvOFL9nJ68R4OHMvw1xDE4GTnkSO4NreoUGG7A==","signatures":[{"sig":"MEYCIQDJdAzucNDd+DBvX939IsUpUOgPOoqwKZRgReYibyEieAIhAIsTUBCVBH1eTTR92OkCWwZLxToS9TMph7ES2Glu656+","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":757634},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6097df070da764e5df74dc87663f6c7dbbc7de81","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-09T12:45:45.327Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.101","@balena/jellyfish-metrics":"^1.0.365","@balena/jellyfish-environment":"^5.0.12"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.8","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.4-renovate-external-non-major-6097df070da764e5df74dc87663f6c7dbbc7de81_1636462180210_0.2342394530967924","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.4":{"name":"@balena/jellyfish-core","version":"8.0.4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.4","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9df3419dc5e9529c4c5e9b19bfa47586038ca729","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.4.tgz","fileCount":205,"integrity":"sha512-/J7xXK8Dt6NErlg2jXHZCFK6OdwECpQ7tfH7fIN/D2xp/1wePmPINI7gZ+6aL3XgoYBTunyjrEBLanGkUmCSqQ==","signatures":[{"sig":"MEYCIQCS17DzgV15hyAOA96fthZD1FXm7ASWeiKhkN2c15CfGgIhANKTH+662dPc5eJYjYF2BlLhKU5uqoQG24ffi+UXegQT","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":757565},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"395ea223d36ad392cf5c053224f95b57ddb333e7","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-09T12:54:39.235Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.101","@balena/jellyfish-metrics":"^1.0.365","@balena/jellyfish-environment":"^5.0.12"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.8","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.4_1636462574989_0.15878704594040194","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.5-renovate-internal-patch-a3fe381a2bb48c7e0e9e66ee66c60fc6efe76f0f":{"name":"@balena/jellyfish-core","version":"8.0.5-renovate-internal-patch-a3fe381a2bb48c7e0e9e66ee66c60fc6efe76f0f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.5-renovate-internal-patch-a3fe381a2bb48c7e0e9e66ee66c60fc6efe76f0f","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8e0dacf136f1beb6f0ad1c50212a56920d00567b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.5-renovate-internal-patch-a3fe381a2bb48c7e0e9e66ee66c60fc6efe76f0f.tgz","fileCount":205,"integrity":"sha512-oJEFDwFo5Q1vh5yVOUG9ayYuthJtBJrb8dnnwYKK1un0nl9vry7U3/VuVBwRegfk1bCByqsiHrAktiIVwfzhsw==","signatures":[{"sig":"MEUCIQDGOnPpEm6iAUM0qwyTLwGxvRdSms69CjxomFnlSX0ZhgIgBBi12TMtnTcHYqIgnlcn5/geJGgMiXi7+q8HBCQiW9c=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":757700},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a3fe381a2bb48c7e0e9e66ee66c60fc6efe76f0f","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-09T12:58:40.050Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.104","@balena/jellyfish-metrics":"^1.0.367","@balena/jellyfish-environment":"^5.0.14"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.8","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.5-renovate-internal-patch-a3fe381a2bb48c7e0e9e66ee66c60fc6efe76f0f_1636462881632_0.09845075366972633","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.5-renovate-major-json-schema-org-47522b044866fa8ae8574bec6dc59a9632ed4683":{"name":"@balena/jellyfish-core","version":"8.0.5-renovate-major-json-schema-org-47522b044866fa8ae8574bec6dc59a9632ed4683","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.5-renovate-major-json-schema-org-47522b044866fa8ae8574bec6dc59a9632ed4683","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"64aef9116a7bb355e9ee973a249d4a48601b9c42","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.5-renovate-major-json-schema-org-47522b044866fa8ae8574bec6dc59a9632ed4683.tgz","fileCount":205,"integrity":"sha512-VMjFFZV1bgXpb7xlLBPtmGNk0e4xTISAl+FCHp+ctXjGzuEyW7XWIHi/6sEG5FlzM0AHlSZgmbV2TxbXilChbA==","signatures":[{"sig":"MEQCIAWhPmzyM8Imn2SnN7N9fIsGdke5zWmawqJ3Gfw6ObeCAiBVNUJapwJUv6BRH2NaOGpNH+BRqp++rXUMNfW9QAh+bA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":757732},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"47522b044866fa8ae8574bec6dc59a9632ed4683","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-09T12:59:42.784Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.101","@balena/jellyfish-metrics":"^1.0.365","@balena/jellyfish-environment":"^5.0.12"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.8","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.5-renovate-major-json-schema-org-47522b044866fa8ae8574bec6dc59a9632ed4683_1636462936785_0.1132152981798984","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.5-renovate-internal-patch-d987c0fd226e22102d9f0fef153984ce547f8053":{"name":"@balena/jellyfish-core","version":"8.0.5-renovate-internal-patch-d987c0fd226e22102d9f0fef153984ce547f8053","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.5-renovate-internal-patch-d987c0fd226e22102d9f0fef153984ce547f8053","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"430e255193f18969cb826f19109de52b4f5cc350","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.5-renovate-internal-patch-d987c0fd226e22102d9f0fef153984ce547f8053.tgz","fileCount":205,"integrity":"sha512-a482xDs7NIf9RpUqWao7RtmtWiaLhA709YJ/hK7npnMOsqnPKOSMi+0Xy9QEAkgTYwRirQWQrjM2a77vGGDtlA==","signatures":[{"sig":"MEYCIQDyAlfic51rwCexVLwvPHA2m/2fgzfi65/x192Y3HGiqQIhAKRUHjYQe9uokH/FSlSMHTdujlTaDgl3zxVzrzYmG96j","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":757700},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d987c0fd226e22102d9f0fef153984ce547f8053","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-09T14:36:31.124Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.104","@balena/jellyfish-metrics":"^1.0.368","@balena/jellyfish-environment":"^5.0.14"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.8","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.5-renovate-internal-patch-d987c0fd226e22102d9f0fef153984ce547f8053_1636468800564_0.1964213396495802","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.5":{"name":"@balena/jellyfish-core","version":"8.0.5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.5","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d5067dd73a4d3ce0015a2ceda3979e7b9a5a7243","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.5.tgz","fileCount":205,"integrity":"sha512-LnU+tHMp65InGCkXeC2fhK3Rvj/XtYb0xYt3gZ7YG5yKVr0gIt856gyPPdK3P7voXXfHlme4yIAVO/FN80JiCg==","signatures":[{"sig":"MEYCIQC5m4XI8B8emr+SjtDar6KxTw56xBviee4rbjjaz2HiEwIhAPJ/OI3UNW+qXX0KTWko+jlsSDT8KSlWMFdWCdAvWhfQ","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":757635},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d45a351f8b62fde82aea24fe917382895d31fedc","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-09T14:48:29.566Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.104","@balena/jellyfish-metrics":"^1.0.368","@balena/jellyfish-environment":"^5.0.14"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.8","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.5_1636469482414_0.5528863407988547","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.6-renovate-major-json-schema-org-837ef2e2f3473e2250e38861f2d90428a1e702b1":{"name":"@balena/jellyfish-core","version":"8.0.6-renovate-major-json-schema-org-837ef2e2f3473e2250e38861f2d90428a1e702b1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.6-renovate-major-json-schema-org-837ef2e2f3473e2250e38861f2d90428a1e702b1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"38d59e37d401607265a6ceba786363962bbb52d8","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.6-renovate-major-json-schema-org-837ef2e2f3473e2250e38861f2d90428a1e702b1.tgz","fileCount":205,"integrity":"sha512-TUeb89g81CGsZJvPQZTN9u9UoI6DsDfxOapb7rlSQQo/r1QyPa2dLqv7vOXzv+x4eL8PtfVdarVJjB4WYBeFDw==","signatures":[{"sig":"MEUCIQCsxGfQhVBG5UV2NQOYlZROBJurIiqq0knwPObBgCfkwQIgYokww4QdMhmhtVEoYd+5Dlv6M2NM+Ew4IW5anWPZwms=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":757802},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"837ef2e2f3473e2250e38861f2d90428a1e702b1","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-09T14:54:50.186Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.104","@balena/jellyfish-metrics":"^1.0.368","@balena/jellyfish-environment":"^5.0.14"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.8","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.6-renovate-major-json-schema-org-837ef2e2f3473e2250e38861f2d90428a1e702b1_1636469904414_0.9940912362206236","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.6-renovate-balena-jellyfish-environment-5-x-ac767498e0cc8dca7cd33de43b2f8bc77ee02af0":{"name":"@balena/jellyfish-core","version":"8.0.6-renovate-balena-jellyfish-environment-5-x-ac767498e0cc8dca7cd33de43b2f8bc77ee02af0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.6-renovate-balena-jellyfish-environment-5-x-ac767498e0cc8dca7cd33de43b2f8bc77ee02af0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ff655fb4d95cd2cffc6e93a5497ab2f2487607f6","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.6-renovate-balena-jellyfish-environment-5-x-ac767498e0cc8dca7cd33de43b2f8bc77ee02af0.tgz","fileCount":205,"integrity":"sha512-s0akj1xLCyXmIDCi6aafvzcpdviABd3UxHvMRNJ3mCntqdcjYtoDm50kZ/+UulOEC+GgbaBrPki1gjqSnIiYlg==","signatures":[{"sig":"MEQCIDmeNjBXcBdfgZZbZF6yF1HaP0cWeZRtCpG/Nm/RK4kyAiBuAy/n0PnUocQ7hbI2NbHEjAQBy1xAbkix4yaS/e5gIQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":757823},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ac767498e0cc8dca7cd33de43b2f8bc77ee02af0","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-10T04:25:24.503Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.104","@balena/jellyfish-metrics":"^1.0.368","@balena/jellyfish-environment":"^5.1.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.8","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.6-renovate-balena-jellyfish-environment-5-x-ac767498e0cc8dca7cd33de43b2f8bc77ee02af0_1636518623256_0.7114018214949911","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.6-renovate-internal-patch-aae73e3add7c65320073e7bad888b763181c3792":{"name":"@balena/jellyfish-core","version":"8.0.6-renovate-internal-patch-aae73e3add7c65320073e7bad888b763181c3792","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.6-renovate-internal-patch-aae73e3add7c65320073e7bad888b763181c3792","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8de90e7166718bbcefd6d840db59c1fee1d0e71b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.6-renovate-internal-patch-aae73e3add7c65320073e7bad888b763181c3792.tgz","fileCount":205,"integrity":"sha512-9SLRBMOb/7jzm7YubbxqxrOlPTg/acz+Kfj+UmIjyw+9Y6CRCvhwRbbUgP7ftCXGDlVIH+bkXJgcfBggYtDcoA==","signatures":[{"sig":"MEUCIQD10FEEA3zvaLsuBRY/LQkryuNptT31uNhnrS2Atmb97wIgUr8mi4JUj4hylfsHGTYClD9gYcjyZc78tWWnXfv00Cs=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":757802},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"aae73e3add7c65320073e7bad888b763181c3792","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-10T04:31:31.841Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.69","@balena/jellyfish-logger":"^3.0.104","@balena/jellyfish-metrics":"^1.0.368","@balena/jellyfish-environment":"^5.0.14"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.8","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.6-renovate-internal-patch-aae73e3add7c65320073e7bad888b763181c3792_1636519083710_0.6328481850077627","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.0-new-permissions-4d8596899904a023f46156c5af0f8bb39a9cdb93":{"name":"@balena/jellyfish-core","version":"8.1.0-new-permissions-4d8596899904a023f46156c5af0f8bb39a9cdb93","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.1.0-new-permissions-4d8596899904a023f46156c5af0f8bb39a9cdb93","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b01d326c05cb4b1c8191615755b95378d4e2c6c1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.0-new-permissions-4d8596899904a023f46156c5af0f8bb39a9cdb93.tgz","fileCount":211,"integrity":"sha512-tsu5KIgSIvtJSeT8VovCTt7WkGo2yLhH6k2s6KGaB9cTu05qG3o9j/pgLoRKRkmnnkZj2YH6OF+htTspSUvgAQ==","signatures":[{"sig":"MEYCIQCevw1WHVpPA0a96Tp0zBelHwGl8F0GQ1bwH9Q6bswYpgIhAKYqKs5mcJotZbbP08i6UGzRhU9LlzobFyQexe/14+KS","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":776608},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4d8596899904a023f46156c5af0f8bb39a9cdb93","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-10T17:45:43.998Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.104","@balena/jellyfish-metrics":"^1.0.368","@balena/jellyfish-environment":"^5.0.14"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.8","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.0-new-permissions-4d8596899904a023f46156c5af0f8bb39a9cdb93_1636566621497_0.7527330335578639","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.6-renovate-internal-patch-cd88eca8eb2778a3feee97acf7f1e4a6eea7c1be":{"name":"@balena/jellyfish-core","version":"8.0.6-renovate-internal-patch-cd88eca8eb2778a3feee97acf7f1e4a6eea7c1be","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.6-renovate-internal-patch-cd88eca8eb2778a3feee97acf7f1e4a6eea7c1be","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1c146bf99ac1c6a21d9ac62777c2adf5b5061110","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.6-renovate-internal-patch-cd88eca8eb2778a3feee97acf7f1e4a6eea7c1be.tgz","fileCount":205,"integrity":"sha512-2xyUbGCOscCEkzoPAerQzEB+LWOt1VqsXCwXyuqZQDPgJmZGIy6Kah7pKSZu6eoM4zbfKAw7xmRit03H1aO5Qg==","signatures":[{"sig":"MEYCIQDHe51oZdHTAi3gxNaWfzGbZ0NHPkDb6HIJwefHz1xRNQIhANIbubZPnNnAMumcSLSpzrc4vjQXpoFJvHE/kOBv55o6","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":757802},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"cd88eca8eb2778a3feee97acf7f1e4a6eea7c1be","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-10T23:39:58.191Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.70","@balena/jellyfish-logger":"^3.0.104","@balena/jellyfish-metrics":"^1.0.368","@balena/jellyfish-environment":"^5.0.14"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.8","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.6-renovate-internal-patch-cd88eca8eb2778a3feee97acf7f1e4a6eea7c1be_1636587755976_0.4420301692582185","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.6":{"name":"@balena/jellyfish-core","version":"8.0.6","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.6","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"7333263eb980b66275746b6b490fbf4a77e7441d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.6.tgz","fileCount":205,"integrity":"sha512-VTEbyKbVzf0i5UV674FCxzVLzA/Tk8I4EjCpQL2CluJnad7lf2+czbcVLoAbtNo5eOqi3pbsnnv6zAynyr11tg==","signatures":[{"sig":"MEYCIQCx/gdPblMZdwxg6MapLgJonYWwaiyf7H0Wg/W3V2/40AIhALF0rthZ7rO9niNf6UeJLCnB5U5F5wh8W+7MdFyJgACT","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":757737},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"267010f613e16c610877991fe4f17d3b1168aadb","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-10T23:48:54.107Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.70","@balena/jellyfish-logger":"^3.0.104","@balena/jellyfish-metrics":"^1.0.368","@balena/jellyfish-environment":"^5.0.14"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.8","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.6_1636588271608_0.9371021081029869","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.7-renovate-major-json-schema-org-2e99fa72b963160a8799edeb4719497751fab597":{"name":"@balena/jellyfish-core","version":"8.0.7-renovate-major-json-schema-org-2e99fa72b963160a8799edeb4719497751fab597","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.7-renovate-major-json-schema-org-2e99fa72b963160a8799edeb4719497751fab597","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ffb63baabd30d8ef6ffe64cc7e4747f9dcca7782","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.7-renovate-major-json-schema-org-2e99fa72b963160a8799edeb4719497751fab597.tgz","fileCount":205,"integrity":"sha512-qPpQFIhE0vLgsv3GNWBu4P+Qrl56JS7QDG03Nit7YdngKlbBDhRTwXhogqx9G7ofHUJ2wXn4RmPMU8KjV6bWHA==","signatures":[{"sig":"MEUCIQDPss8S9VdsNvR83zJof1cfK83UHwvQZwDnOL5il+4U2wIgFfw6P6Yua/1Y1RxB2BXKCcUW5PP6L97UhkxUpt5ualg=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":757904},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2e99fa72b963160a8799edeb4719497751fab597","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-10T23:50:36.907Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.70","@balena/jellyfish-logger":"^3.0.104","@balena/jellyfish-metrics":"^1.0.368","@balena/jellyfish-environment":"^5.0.14"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.8","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.7-renovate-major-json-schema-org-2e99fa72b963160a8799edeb4719497751fab597_1636588445191_0.9952258596922905","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.6-renovate-balena-jellyfish-environment-5-x-ccef330be62327013520d2cd23a4b13c040ad3cb":{"name":"@balena/jellyfish-core","version":"8.0.6-renovate-balena-jellyfish-environment-5-x-ccef330be62327013520d2cd23a4b13c040ad3cb","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.6-renovate-balena-jellyfish-environment-5-x-ccef330be62327013520d2cd23a4b13c040ad3cb","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"865d59bcd570618fb3ab879abcb23c49fc082d06","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.6-renovate-balena-jellyfish-environment-5-x-ccef330be62327013520d2cd23a4b13c040ad3cb.tgz","fileCount":205,"integrity":"sha512-+8yXH33+imgFCNrw/VH0cXK65k7csrL9A3vVoOhQiPllxmkZnoO25Ji00A4OELbdsCCuXxS/8zGbxtiQw1SIHg==","signatures":[{"sig":"MEUCIBocymKqkNwyx3lbAZZqncVhfCilRE+CvOTdd/KWpAc4AiEA9QSJfwVIDRRZObdf/Z3hQHonmONnQhpkqQxwO/OJP68=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":757901},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ccef330be62327013520d2cd23a4b13c040ad3cb","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-10T23:50:14.921Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.70","@balena/jellyfish-logger":"^3.0.104","@balena/jellyfish-metrics":"^1.0.368","@balena/jellyfish-environment":"^5.1.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.8","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.6-renovate-balena-jellyfish-environment-5-x-ccef330be62327013520d2cd23a4b13c040ad3cb_1636588464283_0.2550653948963577","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.7-renovate-internal-patch-cefb0f7220e9607038ea0443f08d40cd499030b8":{"name":"@balena/jellyfish-core","version":"8.0.7-renovate-internal-patch-cefb0f7220e9607038ea0443f08d40cd499030b8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.7-renovate-internal-patch-cefb0f7220e9607038ea0443f08d40cd499030b8","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e9e9128fc943025804778fec7cda722e8c98f028","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.7-renovate-internal-patch-cefb0f7220e9607038ea0443f08d40cd499030b8.tgz","fileCount":205,"integrity":"sha512-rOK2VWF6hyiL1IBljqp30Bq0PtwBwrVScbjtHRdBqf26PMfherPo6FCbx/CoCJ83LVDm8gI9XMDgMenFgr0vig==","signatures":[{"sig":"MEYCIQCkomkSFCKdEp0azAgri4pEhevpUvubmEsY/50jJj0wNgIhAP4Q1+hL08RRN3mYYli9E8bI+RKpu0XXqYraP1Xy+byV","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":757872},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"cefb0f7220e9607038ea0443f08d40cd499030b8","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-11T05:30:30.683Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.71","@balena/jellyfish-logger":"^3.0.106","@balena/jellyfish-metrics":"^1.0.368","@balena/jellyfish-environment":"^5.0.14"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.8","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.7-renovate-internal-patch-cefb0f7220e9607038ea0443f08d40cd499030b8_1636608839527_0.5785842802916275","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.7-renovate-internal-patch-395a694341c317105a9503e7783cd36ca8375b6f":{"name":"@balena/jellyfish-core","version":"8.0.7-renovate-internal-patch-395a694341c317105a9503e7783cd36ca8375b6f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.7-renovate-internal-patch-395a694341c317105a9503e7783cd36ca8375b6f","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ef89a5b474310f6e0cf38170b055acd74c57d558","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.7-renovate-internal-patch-395a694341c317105a9503e7783cd36ca8375b6f.tgz","fileCount":205,"integrity":"sha512-oCNfRTAphJXxssEfCiWzS8yAM7ihnq5IoMsTm0H11bDI6z82kDohkkppcj4ig8CANHcxroY3YuGsZlgT2q7bCw==","signatures":[{"sig":"MEYCIQDqJLAMkHuuTlxo9i+R214+BkcWV90teQM75nqsrCK1IgIhAMxmlUmabNgpWHTSq6b0SOR4I/qf2vSnPdrK6yzMIwkH","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":757872},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"395a694341c317105a9503e7783cd36ca8375b6f","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-11T09:29:17.234Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.71","@balena/jellyfish-logger":"^3.0.106","@balena/jellyfish-metrics":"^1.0.370","@balena/jellyfish-environment":"^5.0.14"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.8","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.7-renovate-internal-patch-395a694341c317105a9503e7783cd36ca8375b6f_1636623146187_0.19353826080917114","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.0-new-permissions-f8db081ad91e7625618d983cb9a863893738c728":{"name":"@balena/jellyfish-core","version":"8.1.0-new-permissions-f8db081ad91e7625618d983cb9a863893738c728","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.1.0-new-permissions-f8db081ad91e7625618d983cb9a863893738c728","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b364cabcac884cea079a070ee45ccfb5ef28e969","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.0-new-permissions-f8db081ad91e7625618d983cb9a863893738c728.tgz","fileCount":211,"integrity":"sha512-AscaiPQDQhoEsBn77e19NZ0rxpho/uri+n/h9wTYDMfr0RsgZ8/6TyhTsjvUJ1SGkd80FcX+Km2th75WWIaTKQ==","signatures":[{"sig":"MEYCIQCjDXvryn4uUHYWtTifH1Vd1zxvOCIYRoDD1jHJ8413TwIhANxZGAe79TQAGJRbd2vUe/8F1CLiSN3pxIruf9AQIOa8","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":776106},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f8db081ad91e7625618d983cb9a863893738c728","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-11T12:41:25.933Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.68","@balena/jellyfish-logger":"^3.0.104","@balena/jellyfish-metrics":"^1.0.368","@balena/jellyfish-environment":"^5.0.14"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.8","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.0-new-permissions-f8db081ad91e7625618d983cb9a863893738c728_1636634673406_0.8852144125788639","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.7-repo-sync-secrets-default-6a8be04c3a51b71a2797be5b2cf4727ddc44ab4c":{"name":"@balena/jellyfish-core","version":"8.0.7-repo-sync-secrets-default-6a8be04c3a51b71a2797be5b2cf4727ddc44ab4c","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.7-repo-sync-secrets-default-6a8be04c3a51b71a2797be5b2cf4727ddc44ab4c","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"730c5de8a08d82d684d3b2d8b3de85ff43c80554","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.7-repo-sync-secrets-default-6a8be04c3a51b71a2797be5b2cf4727ddc44ab4c.tgz","fileCount":205,"integrity":"sha512-+UeO2yR/1PpOnQTH5KKE+viJ+UkDU647+9O2hV4k7EAcVDvZ+ahlx/9KZdpd0D+uwIP84dLKqlYTPWgGFPRR2w==","signatures":[{"sig":"MEUCIDiJtlmYS4meVs56GMfL1HgHOwNx3mh9nFww2pL+BNh1AiEAouYftIt1cfZiPSyyhnPVFcEc79lU94l4oGasHqwQjaA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":757886},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6a8be04c3a51b71a2797be5b2cf4727ddc44ab4c","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-12T01:24:46.053Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.70","@balena/jellyfish-logger":"^3.0.104","@balena/jellyfish-metrics":"^1.0.368","@balena/jellyfish-environment":"^5.0.14"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.8","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.7-repo-sync-secrets-default-6a8be04c3a51b71a2797be5b2cf4727ddc44ab4c_1636680487177_0.19277243940953692","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.7-renovate-balena-jellyfish-environment-5-x-2348c2ceed3feabebd9237cc030781c5326d36a6":{"name":"@balena/jellyfish-core","version":"8.0.7-renovate-balena-jellyfish-environment-5-x-2348c2ceed3feabebd9237cc030781c5326d36a6","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.7-renovate-balena-jellyfish-environment-5-x-2348c2ceed3feabebd9237cc030781c5326d36a6","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4ba59c8fa0a0ac61e986ccc677480c2f662c7d0c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.7-renovate-balena-jellyfish-environment-5-x-2348c2ceed3feabebd9237cc030781c5326d36a6.tgz","fileCount":205,"integrity":"sha512-oDqxPQUsp8qnrCh9Plz90C3MvtEtDuE1cZs+SagJgNWmQLyWYwuLBasX9SQvhA2/gOv1MmcwtB6bkCzQ/wEP+g==","signatures":[{"sig":"MEYCIQDnurE+s6W9r5I54TI1mlBrLC6Ia6SCwDQR6TAeNHtObgIhAMB41xywyINxYX0o0Tk1k3Vu68htBWDoZHR+ybYOF/K1","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":757925},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2348c2ceed3feabebd9237cc030781c5326d36a6","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-12T01:44:47.001Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.70","@balena/jellyfish-logger":"^3.0.104","@balena/jellyfish-metrics":"^1.0.368","@balena/jellyfish-environment":"^5.1.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.8","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.7-renovate-balena-jellyfish-environment-5-x-2348c2ceed3feabebd9237cc030781c5326d36a6_1636681709657_0.2965919579207532","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.7":{"name":"@balena/jellyfish-core","version":"8.0.7","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.7","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"cd03d88833e8096a0902db2abd9b1e40d27d6ebe","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.7.tgz","fileCount":205,"integrity":"sha512-wdMAOU0twXqefjlERQBYyLBVMB5ybus8wmKu6DYJv2Bmy78JBdOJislRB9k/dJbnqEfn61tvnj4Fl6IA9SejPA==","signatures":[{"sig":"MEQCICkzjaRh/lgHdFOY4E5OCen2QOz34SjRbthnX7Wpi8GxAiAD0sbXoVfK5XiTvcst/33KUMXNN6JzjHoUr3ism/gSUA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":757842},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"67b8b3c61c3e098a691711ff6f44a2d8dff2f22c","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-12T01:53:24.514Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.70","@balena/jellyfish-logger":"^3.0.104","@balena/jellyfish-metrics":"^1.0.368","@balena/jellyfish-environment":"^5.1.1"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.8","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.7_1636682137343_0.868356430076636","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.7-repo-sync-secrets-default-fc3ca1e0e43ed3b0a56103db5b71376c0021370e":{"name":"@balena/jellyfish-core","version":"8.0.7-repo-sync-secrets-default-fc3ca1e0e43ed3b0a56103db5b71376c0021370e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.7-repo-sync-secrets-default-fc3ca1e0e43ed3b0a56103db5b71376c0021370e","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"61b875e95b37eb66cc49656c7a41d723bc29f17f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.7-repo-sync-secrets-default-fc3ca1e0e43ed3b0a56103db5b71376c0021370e.tgz","fileCount":205,"integrity":"sha512-2A1XS9FERLB7rcr4FoypGr/UuHaFSl8If9RK30kHCDOYLpFXZFzQ4tpXYbf3hB3ohz9axw96K2+6fGY0Ey7+zg==","signatures":[{"sig":"MEQCICMA8qLKaiyhTjXac0ceb526wtDVUCJIE+S+17R6ac40AiBJ22tOJcjGU13WEjUPR9Lh7gz31FAfloMhzoPWP5mkAg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":757886},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"fc3ca1e0e43ed3b0a56103db5b71376c0021370e","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-12T01:53:49.041Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.70","@balena/jellyfish-logger":"^3.0.104","@balena/jellyfish-metrics":"^1.0.368","@balena/jellyfish-environment":"^5.0.14"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.8","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.7-repo-sync-secrets-default-fc3ca1e0e43ed3b0a56103db5b71376c0021370e_1636682232113_0.9987483049841532","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.8-renovate-major-json-schema-org-d6c363a7847bb0381e80c4ed0e07c9b790bcb621":{"name":"@balena/jellyfish-core","version":"8.0.8-renovate-major-json-schema-org-d6c363a7847bb0381e80c4ed0e07c9b790bcb621","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.8-renovate-major-json-schema-org-d6c363a7847bb0381e80c4ed0e07c9b790bcb621","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d9da9e7014ad9a905d6c4258a92872422d220a9e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.8-renovate-major-json-schema-org-d6c363a7847bb0381e80c4ed0e07c9b790bcb621.tgz","fileCount":205,"integrity":"sha512-7N8Je9dpS5ckkAmnt3h3mF08bV03tySR3MkVrZUAC6ggde5Ru9PRcSzJk8usSQ8PPeNhy42Q4PGscgorBfLVAw==","signatures":[{"sig":"MEYCIQCYnR/cezv/uUg5f6nTPMDIO2aYSwQIjxn8Ux2ZeTN8YwIhAP45mmBVv6iGNrCNB0sDMJGlaWgYe4Qk/0QR6HvwKwoy","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":758009},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d6c363a7847bb0381e80c4ed0e07c9b790bcb621","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-12T01:58:16.714Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.70","@balena/jellyfish-logger":"^3.0.104","@balena/jellyfish-metrics":"^1.0.368","@balena/jellyfish-environment":"^5.1.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.8","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.8-renovate-major-json-schema-org-d6c363a7847bb0381e80c4ed0e07c9b790bcb621_1636682527736_0.7432264576143588","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.8-repo-sync-secrets-default-b47bc180eac925d72381ac4f6d78c86591ec91d9":{"name":"@balena/jellyfish-core","version":"8.0.8-repo-sync-secrets-default-b47bc180eac925d72381ac4f6d78c86591ec91d9","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.8-repo-sync-secrets-default-b47bc180eac925d72381ac4f6d78c86591ec91d9","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"25b2622f79a04989a8dea15503508b59c8716157","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.8-repo-sync-secrets-default-b47bc180eac925d72381ac4f6d78c86591ec91d9.tgz","fileCount":205,"integrity":"sha512-WolXkjWsu5Ga0MhhY5Tx1u0TdP1qy6gwwU+i9yWAI+wIW/gLBWq4iMwE3GGBqy5ecIzOGxH0KFZWHpyPhew7YQ==","signatures":[{"sig":"MEYCIQCwE57j8xoHZUsla5Psv5RBykhEkZdIMiaGQd6Z5KOwogIhAMsE8n98WDMOgcWg/Dzks8v7Q7bT1HKr0KelF6+hEZO8","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":757991},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b47bc180eac925d72381ac4f6d78c86591ec91d9","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-12T02:15:22.973Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.70","@balena/jellyfish-logger":"^3.0.104","@balena/jellyfish-metrics":"^1.0.368","@balena/jellyfish-environment":"^5.1.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.8","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.8-repo-sync-secrets-default-b47bc180eac925d72381ac4f6d78c86591ec91d9_1636683578390_0.1089017002599848","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.8-repo-sync-secrets-default-91dbcbe25afb2ed1d05d5c82c4c78a648b48d362":{"name":"@balena/jellyfish-core","version":"8.0.8-repo-sync-secrets-default-91dbcbe25afb2ed1d05d5c82c4c78a648b48d362","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.8-repo-sync-secrets-default-91dbcbe25afb2ed1d05d5c82c4c78a648b48d362","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"26cad227ef52b3fea7134e68a8a1e8c5a62915d1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.8-repo-sync-secrets-default-91dbcbe25afb2ed1d05d5c82c4c78a648b48d362.tgz","fileCount":205,"integrity":"sha512-JtEo5QNTXJm1BOkt7VvmGWBNr2RsLHmgMJN0W4c9l0v1tUXZq/7So8aIiDz5Br0vhgl24/99RtL2wFVUuiKXkw==","signatures":[{"sig":"MEYCIQCj2KSNHEl7WOl3UKu9EZ4r16KVWOMTGtzdX03OOIDnmwIhAJ/D+QY8e5h+4w/ReFhTYBPw+4Ne534OwOPHAMWUFJW3","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":757991},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"91dbcbe25afb2ed1d05d5c82c4c78a648b48d362","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-12T02:41:53.540Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.70","@balena/jellyfish-logger":"^3.0.104","@balena/jellyfish-metrics":"^1.0.368","@balena/jellyfish-environment":"^5.1.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.8","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.8-repo-sync-secrets-default-91dbcbe25afb2ed1d05d5c82c4c78a648b48d362_1636685152911_0.7379053498186097","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.8-renovate-internal-patch-78e53cd49fcf825f3a5c78ff84f51ed358fab98e":{"name":"@balena/jellyfish-core","version":"8.0.8-renovate-internal-patch-78e53cd49fcf825f3a5c78ff84f51ed358fab98e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.8-renovate-internal-patch-78e53cd49fcf825f3a5c78ff84f51ed358fab98e","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"eaaef6eca4adbd07e7006d4c12077df713b9cd4c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.8-renovate-internal-patch-78e53cd49fcf825f3a5c78ff84f51ed358fab98e.tgz","fileCount":205,"integrity":"sha512-Fk+AoTs7BQD+9+svSxAqP6vYSAVkVn3aESYOeNKRtJahirQlpRUsG8+P/ND5uLR9Bws+q3d/2uY2/nWudGdY1g==","signatures":[{"sig":"MEUCIQDVKwmbSaivr+dMF6iap36ZdJXfsNCofF6fa+iwD7WebwIgTVvkDvbynq5P6eBSJXn5qESZJ30FnXRI939S20AbV+Y=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":757977},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"78e53cd49fcf825f3a5c78ff84f51ed358fab98e","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-12T03:34:30.904Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.71","@balena/jellyfish-logger":"^3.0.106","@balena/jellyfish-metrics":"^1.0.370","@balena/jellyfish-environment":"^5.1.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.8","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.8-renovate-internal-patch-78e53cd49fcf825f3a5c78ff84f51ed358fab98e_1636688271984_0.37913028965699125","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.8":{"name":"@balena/jellyfish-core","version":"8.0.8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.8","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f85c70c78c8131bc9bc4c31179df5f7e5f186ae5","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.8.tgz","fileCount":205,"integrity":"sha512-em520ggVsjtdpJrgmUfcw5z0hOMFl1n0P/5Yp9epUQiy3PxqMMo5Mrm+gKqMJ0Vb81wGMTF5RDHn0smy/GR4AA==","signatures":[{"sig":"MEYCIQCrqD7WztNN/pFKX+usNi6HHJ04p/v/jNBPT0LX71aiKQIhAI32UuFLh11Ki1nN66p5G0NfOz52Uwp+pWrl/40/L/IZ","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":757912},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"01f06209744316f3d908def668bd7a13405d66b8","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-12T03:42:02.804Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.71","@balena/jellyfish-logger":"^3.0.106","@balena/jellyfish-metrics":"^1.0.370","@balena/jellyfish-environment":"^5.1.2"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.8","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.8_1636688638005_0.21959864543967145","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.9-renovate-major-json-schema-org-de37df428a28f6fa6c5c78061b03642143a7f593":{"name":"@balena/jellyfish-core","version":"8.0.9-renovate-major-json-schema-org-de37df428a28f6fa6c5c78061b03642143a7f593","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.9-renovate-major-json-schema-org-de37df428a28f6fa6c5c78061b03642143a7f593","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"721ee7e6885f4df32c3424c1433a88a0d6fda99c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.9-renovate-major-json-schema-org-de37df428a28f6fa6c5c78061b03642143a7f593.tgz","fileCount":205,"integrity":"sha512-6SIZ1FuenKZwlroMrt8Rrklhs5myKE/flepK6O6SdsOfWps9tfG+JWB/ljLMZpgzdRoTE+tPUXT2kSLHO+dOMg==","signatures":[{"sig":"MEUCIDBjUViUbeGVV6V7Q2+FKik0plMRbH4TERXtYuYyeoqAAiEAmcdPEid5fcN1/5iX0pCO9UnB/uSRbVwwp7yfETmpFl4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":758079},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"de37df428a28f6fa6c5c78061b03642143a7f593","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-12T03:44:01.072Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.71","@balena/jellyfish-logger":"^3.0.106","@balena/jellyfish-metrics":"^1.0.370","@balena/jellyfish-environment":"^5.1.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.8","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.9-renovate-major-json-schema-org-de37df428a28f6fa6c5c78061b03642143a7f593_1636688852648_0.4096777286624764","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.9-renovate-internal-patch-c99b3a7237d9a0a00f49974d2c0a464bea16b94e":{"name":"@balena/jellyfish-core","version":"8.0.9-renovate-internal-patch-c99b3a7237d9a0a00f49974d2c0a464bea16b94e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.9-renovate-internal-patch-c99b3a7237d9a0a00f49974d2c0a464bea16b94e","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"424a6d973783474164afa615471a5eed35e12f07","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.9-renovate-internal-patch-c99b3a7237d9a0a00f49974d2c0a464bea16b94e.tgz","fileCount":205,"integrity":"sha512-XrSjc/v7XLGGBOMiRDJjR5/HdSNl9lzLK9zIfZEEWsbVe1EcnG/javy9KK2N/ZgGGSacZfgsA8w3ZemsFlIxNQ==","signatures":[{"sig":"MEYCIQCpitsmBi8n2Y0zvLipWYn+WTZTpDicXww1A3W1vrrDRgIhAMfsecsHPwZ2Bv/6ScOwLitBZsJ1JMlEBavh0i0KR1vP","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":758047},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c99b3a7237d9a0a00f49974d2c0a464bea16b94e","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-12T05:17:08.090Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.71","@balena/jellyfish-logger":"^3.0.107","@balena/jellyfish-metrics":"^1.0.371","@balena/jellyfish-environment":"^5.1.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.8","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.9-renovate-internal-patch-c99b3a7237d9a0a00f49974d2c0a464bea16b94e_1636694479745_0.3585723415094444","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.9":{"name":"@balena/jellyfish-core","version":"8.0.9","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.9","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"59b0b03aae6d6e56146e611f1a7cf0c63a2f06c4","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.9.tgz","fileCount":205,"integrity":"sha512-osyogikKyJIzTTlH9VOUf2JXET4/sbhZUCjkY0EzJIxp8Z6I8RwRymESghicmF+mapss73GPxEpW7KVQTJx/AA==","signatures":[{"sig":"MEYCIQDiCx5cKgzaEA1qKF0ToT4+U47Y0657AKWjLFR3EXSqcAIhAIX8CnrSSNdQopE9j2iDagNQzmTq/xjRuuYY4YRxu239","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":757982},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7020a101f8be7352f7301cf8cb47ed625e0acad7","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-12T05:27:18.504Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.71","@balena/jellyfish-logger":"^3.0.107","@balena/jellyfish-metrics":"^1.0.371","@balena/jellyfish-environment":"^5.1.2"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.8","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.9_1636694947634_0.6065318850209642","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.10-renovate-major-json-schema-org-f29070d1e469e03e77df582714ea1a694d33a9c6":{"name":"@balena/jellyfish-core","version":"8.0.10-renovate-major-json-schema-org-f29070d1e469e03e77df582714ea1a694d33a9c6","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.10-renovate-major-json-schema-org-f29070d1e469e03e77df582714ea1a694d33a9c6","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b65f9ccfd02693407efd788790118f4be8637783","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.10-renovate-major-json-schema-org-f29070d1e469e03e77df582714ea1a694d33a9c6.tgz","fileCount":205,"integrity":"sha512-kasm6126Ju7zX7UCMIiRC65kyJFopaOgeYXIH9D6S27DJxL9cQuRwcz93VrK5KyaixVnpb2g2ETLznz/uNGtHQ==","signatures":[{"sig":"MEUCICPGNGGvx+boBOLaKIpnkkArmcFl3EaaMQcJJBj1Ve7dAiEAnA+mIg1SNcfIy0zc+YE+ZRgX8h0pstHKDYWLECOFqV4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":758151},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f29070d1e469e03e77df582714ea1a694d33a9c6","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-12T05:28:44.945Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.71","@balena/jellyfish-logger":"^3.0.107","@balena/jellyfish-metrics":"^1.0.371","@balena/jellyfish-environment":"^5.1.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.8","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.10-renovate-major-json-schema-org-f29070d1e469e03e77df582714ea1a694d33a9c6_1636695100355_0.357840847566093","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.10-renovate-internal-patch-e23e77aebd9b921c9fb27d1953cb7cac4aaeea45":{"name":"@balena/jellyfish-core","version":"8.0.10-renovate-internal-patch-e23e77aebd9b921c9fb27d1953cb7cac4aaeea45","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.10-renovate-internal-patch-e23e77aebd9b921c9fb27d1953cb7cac4aaeea45","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b292c50aa74ecacde6c690e5226bafaf18af3e16","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.10-renovate-internal-patch-e23e77aebd9b921c9fb27d1953cb7cac4aaeea45.tgz","fileCount":205,"integrity":"sha512-CpW3C0SSz1AFqbdlt+YYu77dvyRtHmHmdOaMpVbwbbWFh6UN8neza6jKPQB+OGhk72mpUV87eWhI0F9aUt85qw==","signatures":[{"sig":"MEQCIDJLpFvrcjJEi+xoHPqoVY+Z1mZikSjy3zcsWzQKa1JuAiApXl7kuT4YAvyZblB2qjmfJ9JsaekpvT7MfXy77ZcWow==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":758153},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e23e77aebd9b921c9fb27d1953cb7cac4aaeea45","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-12T09:47:48.846Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.71","@balena/jellyfish-logger":"^3.0.107","@balena/jellyfish-metrics":"^1.0.372","@balena/jellyfish-environment":"^5.1.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.8","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.10-renovate-internal-patch-e23e77aebd9b921c9fb27d1953cb7cac4aaeea45_1636710680528_0.5663372196169785","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.10":{"name":"@balena/jellyfish-core","version":"8.0.10","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.10","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"864f2790a4b90b0fefbf8efd60f87d70a74f405b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.10.tgz","fileCount":205,"integrity":"sha512-2KODGhXyQG6Rf0KKMq0A3NbzilKZgdj7LNY6pDSFdj1IKvXd7hQELlAo/39QEllu2AsE1RuKJeSpSS+QrrM1fg==","signatures":[{"sig":"MEQCIBUgrtn/xUONAGeXVrPXX08LN8FXb7qLJGt8HVpHSqXzAiBD2oYSVotMBtBG9/8xfvnKjAwdOo8vqm1znTKFJQeMHg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":758088},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"10877ab1f299575c4f2b4ee536568cb58c071afe","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-12T09:55:45.824Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.71","@balena/jellyfish-logger":"^3.0.107","@balena/jellyfish-metrics":"^1.0.372","@balena/jellyfish-environment":"^5.1.2"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.8","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.10_1636711063215_0.2027351560368471","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.11-renovate-major-json-schema-org-bd699a9b3feb39b2120bca38f4164b9ca22692be":{"name":"@balena/jellyfish-core","version":"8.0.11-renovate-major-json-schema-org-bd699a9b3feb39b2120bca38f4164b9ca22692be","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.11-renovate-major-json-schema-org-bd699a9b3feb39b2120bca38f4164b9ca22692be","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"7075f18f61077bf1fdef762afcc964e1578dd839","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.11-renovate-major-json-schema-org-bd699a9b3feb39b2120bca38f4164b9ca22692be.tgz","fileCount":205,"integrity":"sha512-W2RPEoIXmzq+JTuGUhwIqvCHWBRL2LwZgig/LELojYZjPg7QyJSheVliSjOPDrrBg4FVNmxYcW7RIQTP6IExtA==","signatures":[{"sig":"MEUCIHbVKL68UM9QDxQulFycs1kGYLr9BwLWVYhkzAf7PiAdAiEAmCBdC5HsnWTA3bT1HtLPl/YMzt11ZC1CM2OFqlwy+Pg=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":758256},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"bd699a9b3feb39b2120bca38f4164b9ca22692be","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-12T09:57:11.228Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.71","@balena/jellyfish-logger":"^3.0.107","@balena/jellyfish-metrics":"^1.0.372","@balena/jellyfish-environment":"^5.1.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.8","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.11-renovate-major-json-schema-org-bd699a9b3feb39b2120bca38f4164b9ca22692be_1636711209313_0.5739774137626839","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.11-renovate-internal-patch-4f517780ccb87a3664471347f8fd0f29de1d822a":{"name":"@balena/jellyfish-core","version":"8.0.11-renovate-internal-patch-4f517780ccb87a3664471347f8fd0f29de1d822a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.11-renovate-internal-patch-4f517780ccb87a3664471347f8fd0f29de1d822a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"467675473fab7a172ec7f977adc5b0c56eaa232d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.11-renovate-internal-patch-4f517780ccb87a3664471347f8fd0f29de1d822a.tgz","fileCount":205,"integrity":"sha512-Ay+AHyY4ByyRRmDBTHzA/Tyg/MAcEAlAoWc0kz7DFOBhLxtasV7aPhjNes1JPatyPzQSOholHJZAbPS8wBYrog==","signatures":[{"sig":"MEYCIQCcYW8+/9cFp+RrkDTG/dLjlOhcWp1/MTiI9XRtChw+cwIhAJfOWgcwoXua1XtIyF1N+oTYLge41pbBlb95VJbMWjZ0","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":758257},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4f517780ccb87a3664471347f8fd0f29de1d822a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-13T01:48:42.837Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.71","@balena/jellyfish-logger":"^3.0.108","@balena/jellyfish-metrics":"^1.0.372","@balena/jellyfish-environment":"^5.1.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.8","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.11-renovate-internal-patch-4f517780ccb87a3664471347f8fd0f29de1d822a_1636768300300_0.3785694582426953","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.11":{"name":"@balena/jellyfish-core","version":"8.0.11","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.11","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2ece5d566607603a29f9f131b761736f34cce0e0","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.11.tgz","fileCount":205,"integrity":"sha512-FK7SOiHZa5Tm0gHKkI24sYPatsAuXypJawORDLjJ6H85nQKCPAMDdwh4tuMJaCObsTNPCjH3a0e8EnCNl3RZLg==","signatures":[{"sig":"MEUCIQCvDBpbM0OCjZGhraeSHsoOjKgsfI3oCBaIDAKO9CyvvgIgES7GkruW274if56ExwZSNQQA6C4QYeQ3Pk18euRLaB8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":758192},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b9b1ff99c74860fa294486a1a5421002b28c97ef","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-13T01:56:18.491Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.71","@balena/jellyfish-logger":"^3.0.108","@balena/jellyfish-metrics":"^1.0.372","@balena/jellyfish-environment":"^5.1.2"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.8","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.11_1636768686984_0.29908072138613373","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.12-renovate-major-json-schema-org-f890dd6b87ac3a8c6c65e88353544b1b58dac6fa":{"name":"@balena/jellyfish-core","version":"8.0.12-renovate-major-json-schema-org-f890dd6b87ac3a8c6c65e88353544b1b58dac6fa","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.12-renovate-major-json-schema-org-f890dd6b87ac3a8c6c65e88353544b1b58dac6fa","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0ab948201865b724026a6e959f28ccce1f23b866","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.12-renovate-major-json-schema-org-f890dd6b87ac3a8c6c65e88353544b1b58dac6fa.tgz","fileCount":205,"integrity":"sha512-TKjGS0luSanV14i3Y2g1Zq8vFFjBuu42rnrtAeSmYTeczV3KGs/uxSPr+wOS4leNBtqlrbG4pF9YptSiE9noDg==","signatures":[{"sig":"MEYCIQDLgIZmlREIpUdmWjntBMC92BQaLWWDVsvvDZ5N5HQyvQIhALfFAAnSV9P7HEkkdpLUANgvv1Oyv/ZI30kKXWceGsft","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":758360},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f890dd6b87ac3a8c6c65e88353544b1b58dac6fa","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-13T01:57:59.630Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.71","@balena/jellyfish-logger":"^3.0.108","@balena/jellyfish-metrics":"^1.0.372","@balena/jellyfish-environment":"^5.1.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.8","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.12-renovate-major-json-schema-org-f890dd6b87ac3a8c6c65e88353544b1b58dac6fa_1636768894812_0.07199039599147805","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.12-renovate-internal-patch-d7eb62da154d0fbb89a3c1186586b0a851035f89":{"name":"@balena/jellyfish-core","version":"8.0.12-renovate-internal-patch-d7eb62da154d0fbb89a3c1186586b0a851035f89","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.12-renovate-internal-patch-d7eb62da154d0fbb89a3c1186586b0a851035f89","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e485ad26892acb9981b317d5b8c5d3093ee1a2da","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.12-renovate-internal-patch-d7eb62da154d0fbb89a3c1186586b0a851035f89.tgz","fileCount":205,"integrity":"sha512-VbbLCae76zrqwKGm01QAda0Melj5C0cpwP9L4LZogSFe3HIISXYMADCN6fG2LEOkaVceeJIxpp8UZeP30WwL0Q==","signatures":[{"sig":"MEYCIQDpz9DegcE/9lVh0MXgzL1qKmLZ0gcxR78/+zyYD3yIXQIhAKildq+++rEgS8dRg8WItKqPMuNafXT4rcT6PActVYka","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":758362},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d7eb62da154d0fbb89a3c1186586b0a851035f89","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-13T03:34:01.022Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.71","@balena/jellyfish-logger":"^3.0.108","@balena/jellyfish-metrics":"^1.0.373","@balena/jellyfish-environment":"^5.1.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.8","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.12-renovate-internal-patch-d7eb62da154d0fbb89a3c1186586b0a851035f89_1636774615093_0.42625233409670393","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.12":{"name":"@balena/jellyfish-core","version":"8.0.12","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.12","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b81a8e5b887ad8f713e0fbeafd4ce62fca76c0b8","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.12.tgz","fileCount":205,"integrity":"sha512-/+pP8LX7l6zBc/vJks6cRQvHDpv7LDTIb6nFHpWudFTOMpPEe06A8QfyMfTjm/Ne02GGhlK/Dylzank+z5N3ZQ==","signatures":[{"sig":"MEQCIBHIMLjgWom6/P+mWrSSzsh6i2FVhocrJfNDU6VmXJaGAiAhVlbo6yuGQ4wNCc3cUr6jgqvyNP8xxwbK4BSwIomPJQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":758297},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e1b917c219d4c9f6363272c58128d31dbab5f35d","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-13T03:41:59.530Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.71","@balena/jellyfish-logger":"^3.0.108","@balena/jellyfish-metrics":"^1.0.373","@balena/jellyfish-environment":"^5.1.2"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.8","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.12_1636775055906_0.9594830984694753","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.13-renovate-major-json-schema-org-2f49d0a72ffd4a944f47722b36c961580e50d3f8":{"name":"@balena/jellyfish-core","version":"8.0.13-renovate-major-json-schema-org-2f49d0a72ffd4a944f47722b36c961580e50d3f8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.13-renovate-major-json-schema-org-2f49d0a72ffd4a944f47722b36c961580e50d3f8","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1e51bb6abe19a5fb304e706f694dc486b4302f30","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.13-renovate-major-json-schema-org-2f49d0a72ffd4a944f47722b36c961580e50d3f8.tgz","fileCount":205,"integrity":"sha512-cANz7m9hE6QoK+MggysbkB3Fzb2pFqo0Y8RSCrC+mZJu1ejXoifPtTcGBrxirTJpMn+5BueIphIl2lxGKZF+KQ==","signatures":[{"sig":"MEQCIHbrvrvh5jaPMn1ePlv7EQezWLsXOU/D5vV1piND87nuAiAWhN3hhOulgRRkd6WzGUdcpGRTdABBj9wlAQECXsmuXg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":758465},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2f49d0a72ffd4a944f47722b36c961580e50d3f8","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-13T03:43:41.737Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.71","@balena/jellyfish-logger":"^3.0.108","@balena/jellyfish-metrics":"^1.0.373","@balena/jellyfish-environment":"^5.1.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.8","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.13-renovate-major-json-schema-org-2f49d0a72ffd4a944f47722b36c961580e50d3f8_1636775164881_0.02084914610707589","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.13-renovate-internal-patch-5bf38292561b5c429480362728ea8e6867e53119":{"name":"@balena/jellyfish-core","version":"8.0.13-renovate-internal-patch-5bf38292561b5c429480362728ea8e6867e53119","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.13-renovate-internal-patch-5bf38292561b5c429480362728ea8e6867e53119","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"cd9069622de1cc1586fabaf98f0898d5cd0ccbcf","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.13-renovate-internal-patch-5bf38292561b5c429480362728ea8e6867e53119.tgz","fileCount":205,"integrity":"sha512-sPF6EjQk+9SmcB67rYxbMtNqlw0PCcFgwwcRr8MnqPeOaox/mHzBRyFItdNpWbPCvYZYGgXsfjaUhwv9HidY6Q==","signatures":[{"sig":"MEQCIHzIl30i7YdFQgk2fTdkKdtiKe20mrkPtS+AGcBj9sbRAiAe3ResaHinSZ4g18ciO8yoF+T7J+D5t0Dv8XcZh8W5CA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":758465},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"5bf38292561b5c429480362728ea8e6867e53119","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-13T05:30:26.220Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.73","@balena/jellyfish-logger":"^3.0.108","@balena/jellyfish-metrics":"^1.0.373","@balena/jellyfish-environment":"^5.1.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.8","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.13-renovate-internal-patch-5bf38292561b5c429480362728ea8e6867e53119_1636781636737_0.513455953185644","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.13":{"name":"@balena/jellyfish-core","version":"8.0.13","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.13","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"dc1fafb898e3db228f91bebb3b7e4f76721d188f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.13.tgz","fileCount":205,"integrity":"sha512-4GxXH/jixQ8RTlmVI/vHVM9t2YOBlFx1R84ivENgKFa+5tfnVVcji/Nqzx9gAH6kZ94LT7YgsDtNwHZprmEw7w==","signatures":[{"sig":"MEQCICcCN31M7jUzrud71tHDzzl2E7NIaBTmw39zTHfwyv/LAiAcuaQZBuZvzeOnfdc1q1vZi0/+cXVbV1Esz9wkC3bZlA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":758400},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8d51623bae92beb4f0171c399f0fa69d09940d31","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-13T05:39:01.748Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.73","@balena/jellyfish-logger":"^3.0.108","@balena/jellyfish-metrics":"^1.0.373","@balena/jellyfish-environment":"^5.1.2"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.8","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.13_1636782100806_0.9115259853028947","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.14-renovate-major-json-schema-org-b5c018cb4c13229b0dee5ba4c068f8244d99ebf3":{"name":"@balena/jellyfish-core","version":"8.0.14-renovate-major-json-schema-org-b5c018cb4c13229b0dee5ba4c068f8244d99ebf3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.14-renovate-major-json-schema-org-b5c018cb4c13229b0dee5ba4c068f8244d99ebf3","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0f1988ec77d5bae5304c605b6347c70de90d4266","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.14-renovate-major-json-schema-org-b5c018cb4c13229b0dee5ba4c068f8244d99ebf3.tgz","fileCount":205,"integrity":"sha512-B+lJa1uRGHyX+1zZhoK0PRvnmsSNZaBRsmVp8TJJLJM72Z+EB9fehpotnbOKOy9mhipQJUjF7jVM3rPP6KzkPQ==","signatures":[{"sig":"MEUCIQCq+mpU2rF/RYdXzTQH43hdpJixPlVdHJIIihmedzlIIwIgZCOhTjq744EtgPQmKFjm69RIs3ISzGrc+XLuxmiuFaQ=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":758568},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b5c018cb4c13229b0dee5ba4c068f8244d99ebf3","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-13T05:40:40.575Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.73","@balena/jellyfish-logger":"^3.0.108","@balena/jellyfish-metrics":"^1.0.373","@balena/jellyfish-environment":"^5.1.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.8","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.14-renovate-major-json-schema-org-b5c018cb4c13229b0dee5ba4c068f8244d99ebf3_1636782219149_0.10577359011868448","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.14-renovate-internal-patch-45ca3924a731bfdc944c90b290e2d46a1519dbd0":{"name":"@balena/jellyfish-core","version":"8.0.14-renovate-internal-patch-45ca3924a731bfdc944c90b290e2d46a1519dbd0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.14-renovate-internal-patch-45ca3924a731bfdc944c90b290e2d46a1519dbd0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"940fcbdb83656b5c9884d1d7f4106958c1e9874c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.14-renovate-internal-patch-45ca3924a731bfdc944c90b290e2d46a1519dbd0.tgz","fileCount":205,"integrity":"sha512-xC9bw9jU54j6ElI6AKBKrR14ZjOa6nnJ60M0XOFiPNgM+7m1jxfZWw8/RmqRhg5L3K8ADErJ3RMPxZ0ZwvsUBw==","signatures":[{"sig":"MEUCIH4fJfcMM4Oqq7Cr+pX88ad8150a7UjvCmU1ULkOSbUqAiEAuFESgk9DMMRPdzAmMUuiD1TXGT2Dqb0zfFH0msbRPos=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":758536},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"45ca3924a731bfdc944c90b290e2d46a1519dbd0","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-13T10:35:42.715Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.73","@balena/jellyfish-logger":"^3.0.109","@balena/jellyfish-metrics":"^1.0.374","@balena/jellyfish-environment":"^5.1.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.8","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.14-renovate-internal-patch-45ca3924a731bfdc944c90b290e2d46a1519dbd0_1636799955668_0.4835017016843479","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.14":{"name":"@balena/jellyfish-core","version":"8.0.14","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.14","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e02e51a0202b7ce09f9a7ea3a8941db76c2ba81b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.14.tgz","fileCount":205,"integrity":"sha512-8O/ozOYc69jf4cEBq2aJDDHS2bb2FpUGT2e0uViR5FqFcxteKIEhVh9LIpARiI/0PkEhZEndO9vOl2PMj4dTyg==","signatures":[{"sig":"MEYCIQDqXvu1QbdPrHWkvodnfIB6RHBr1Pr6Fh/s60vD2ZvQMAIhANTMt/zeIa3MZbkRUOkwH+Wb3lETd6jwnPeDu3uvs8jx","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":758471},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"68fb4ac045b2575cb4d54fa30b4bea10e1b7b2ef","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-13T10:43:34.443Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.73","@balena/jellyfish-logger":"^3.0.109","@balena/jellyfish-metrics":"^1.0.374","@balena/jellyfish-environment":"^5.1.2"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.8","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.14_1636800409822_0.13515963902542483","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.14-renovate-major-json-schema-org-e5b10c04129b7cf4cacc696d29739fae0f29984a":{"name":"@balena/jellyfish-core","version":"8.0.14-renovate-major-json-schema-org-e5b10c04129b7cf4cacc696d29739fae0f29984a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.14-renovate-major-json-schema-org-e5b10c04129b7cf4cacc696d29739fae0f29984a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f36ec04a7e3432799fc4cee100fb36e2d16a7726","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.14-renovate-major-json-schema-org-e5b10c04129b7cf4cacc696d29739fae0f29984a.tgz","fileCount":205,"integrity":"sha512-psh2R6OYY4iqzQVaITVWfcRSJSACp+V5dbrer1sX2q7Mc5QY9uyCcs4NU1wpKQICAjP+EKqF6CxLZQ/XdNrJFg==","signatures":[{"sig":"MEUCIHqJPQDyTcGy853BGlIifT+kjGmiOtxeTbMxv7qWlFihAiEA1xqlqLOvvkca8R63By56g1sXrSlDbyyqBs2kGfyE5k4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":758614},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e5b10c04129b7cf4cacc696d29739fae0f29984a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-13T10:44:58.512Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.73","@balena/jellyfish-logger":"^3.0.109","@balena/jellyfish-metrics":"^1.0.374","@balena/jellyfish-environment":"^5.1.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.8","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.14-renovate-major-json-schema-org-e5b10c04129b7cf4cacc696d29739fae0f29984a_1636800519010_0.5226161935719438","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.15-repo-sync-secrets-default-fb96ed466b002b08378f581bb21eefac1bb057d5":{"name":"@balena/jellyfish-core","version":"8.0.15-repo-sync-secrets-default-fb96ed466b002b08378f581bb21eefac1bb057d5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.15-repo-sync-secrets-default-fb96ed466b002b08378f581bb21eefac1bb057d5","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"7f4205a57b8b40d08fadddc6892043f009b51a90","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.15-repo-sync-secrets-default-fb96ed466b002b08378f581bb21eefac1bb057d5.tgz","fileCount":205,"integrity":"sha512-ViLKSw7FinRHiDff3ghzZXTv3PANy0g7CZTj1R+yYAor0yBfCfi0mvp08EEwoujI/1tGcPdX1qZjIzePjm7b0w==","signatures":[{"sig":"MEUCIEFYcobFtUqCU7MACiFkgquovilDhgtcGDQCHPd3UCAgAiEAhZTXDdCsBZt+mqQccJC2OxXdhamPQ01zAY7b/DxwMQ8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":758653},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"fb96ed466b002b08378f581bb21eefac1bb057d5","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-14T00:01:09.986Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.73","@balena/jellyfish-logger":"^3.0.109","@balena/jellyfish-metrics":"^1.0.374","@balena/jellyfish-environment":"^5.1.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.8","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.15-repo-sync-secrets-default-fb96ed466b002b08378f581bb21eefac1bb057d5_1636848248201_0.1894558128300563","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.15-renovate-internal-patch-4f901a046ab5a54c0858972088f93722be2289b3":{"name":"@balena/jellyfish-core","version":"8.0.15-renovate-internal-patch-4f901a046ab5a54c0858972088f93722be2289b3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.15-renovate-internal-patch-4f901a046ab5a54c0858972088f93722be2289b3","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1bf40a06ddc47ecf3e01f35f5f757bac6e84786e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.15-renovate-internal-patch-4f901a046ab5a54c0858972088f93722be2289b3.tgz","fileCount":205,"integrity":"sha512-ELl+5CElBbP0mJy2Y65UhS05WrDCPfVI1BcbubFHSBUdyKSjvLwm/ta36U1s98orJ2/+iD8AavPh3GMG0zHxTw==","signatures":[{"sig":"MEYCIQDeDLLbYjHTguCP3dSnBSDD6BLMwJ2O4VMCkFF0wkg13QIhAOdW1MMozHpdu55qGYgsxNWWQpVGeCtOPWVHjBpfZbi1","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":758639},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4f901a046ab5a54c0858972088f93722be2289b3","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-14T12:55:19.237Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.74","@balena/jellyfish-logger":"^3.0.109","@balena/jellyfish-metrics":"^1.0.374","@balena/jellyfish-environment":"^5.1.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.8","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.15-renovate-internal-patch-4f901a046ab5a54c0858972088f93722be2289b3_1636894717271_0.9827013942357516","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.15-renovate-major-external-major-47ae748c69ae4cb0ba3f6b0c6cc3971bc30a52e5":{"name":"@balena/jellyfish-core","version":"8.0.15-renovate-major-external-major-47ae748c69ae4cb0ba3f6b0c6cc3971bc30a52e5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.15-renovate-major-external-major-47ae748c69ae4cb0ba3f6b0c6cc3971bc30a52e5","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8efb7bd5ceb92ad1334b4c28c18e51bc0abc6253","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.15-renovate-major-external-major-47ae748c69ae4cb0ba3f6b0c6cc3971bc30a52e5.tgz","fileCount":205,"integrity":"sha512-vbHjZCNkv4pqU5PPSQwZqya2LcmIYvzAOxp+JkaknC9FoOEPYAKvlef4q2ja48yMRuL99D8OalHXIcu8RpyXiA==","signatures":[{"sig":"MEUCIQDGVGp16uw56VS+Spm9C7tfqAqygR+KwGYYEJeF7y40hwIgFbOtqKfp/obLDMCcWhShPZC17Pcff3PCQlHU7s/3Lic=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":758628},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"47ae748c69ae4cb0ba3f6b0c6cc3971bc30a52e5","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-14T12:56:26.795Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.73","@balena/jellyfish-logger":"^3.0.109","@balena/jellyfish-metrics":"^1.0.374","@balena/jellyfish-environment":"^5.1.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.8","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^12.0.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.15-renovate-major-external-major-47ae748c69ae4cb0ba3f6b0c6cc3971bc30a52e5_1636894750325_0.40151932334144647","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.15-renovate-internal-patch-f30213283d00644aa8d11e919036e59d1f5a9d67":{"name":"@balena/jellyfish-core","version":"8.0.15-renovate-internal-patch-f30213283d00644aa8d11e919036e59d1f5a9d67","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.15-renovate-internal-patch-f30213283d00644aa8d11e919036e59d1f5a9d67","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c8626fc5088c6edc0964014b3cf01f01833abc1c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.15-renovate-internal-patch-f30213283d00644aa8d11e919036e59d1f5a9d67.tgz","fileCount":205,"integrity":"sha512-0EPXp6QMax5U1kjpwce98SwTQh/D4uyJT9kwQQn4QETnzF0joPnW/fJgRrUGSJylXw8x5x6pztwzrPFTSUax6g==","signatures":[{"sig":"MEUCIQDZdBHEslPlJZeHLoB2X0wcjCh+hCDjQwPF0PK6a7zIOgIge//ntixcuTZ+u+I5GcUKTQ7XjUHmt45nEbIitoEPnW0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":758607},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f30213283d00644aa8d11e919036e59d1f5a9d67","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-14T14:48:10.353Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.74","@balena/jellyfish-logger":"^3.0.110","@balena/jellyfish-metrics":"^1.0.374","@balena/jellyfish-environment":"^5.1.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.8","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.15-renovate-internal-patch-f30213283d00644aa8d11e919036e59d1f5a9d67_1636901480717_0.4487881953749264","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.15-renovate-internal-patch-6787164f5daa7a7f34ed355bb65bbae1ddee4505":{"name":"@balena/jellyfish-core","version":"8.0.15-renovate-internal-patch-6787164f5daa7a7f34ed355bb65bbae1ddee4505","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.15-renovate-internal-patch-6787164f5daa7a7f34ed355bb65bbae1ddee4505","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"11396a08c4d3b911a146342f05e856b986ccbc96","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.15-renovate-internal-patch-6787164f5daa7a7f34ed355bb65bbae1ddee4505.tgz","fileCount":205,"integrity":"sha512-AyWVMUHnKQ9ROQrJhf7NaZ2U/jjPgYKLl+4njJqDBVFE/MDgWgZOooWGJqzK8EeYQ0VyvXSfOCan89Z3a/FbXw==","signatures":[{"sig":"MEQCIENR7MbMqrq2HSCbFh3qb8ORUCUgdwMyUtnho6Ftv8hiAiB0jyDt7ulxG+mkPSM9A0v5w33KSeZuuUZjVQklj7+1Ew==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":758607},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6787164f5daa7a7f34ed355bb65bbae1ddee4505","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-14T16:07:21.443Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.74","@balena/jellyfish-logger":"^3.0.110","@balena/jellyfish-metrics":"^1.0.375","@balena/jellyfish-environment":"^5.1.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.8","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.15-renovate-internal-patch-6787164f5daa7a7f34ed355bb65bbae1ddee4505_1636906187585_0.3611688867721736","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.15-renovate-external-non-major-024677f5ae6eab4e8d0a92c109a5d8653cfe2688":{"name":"@balena/jellyfish-core","version":"8.0.15-renovate-external-non-major-024677f5ae6eab4e8d0a92c109a5d8653cfe2688","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.15-renovate-external-non-major-024677f5ae6eab4e8d0a92c109a5d8653cfe2688","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2f52681ce4d8473d93eefc6d2126edef3ddf6d54","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.15-renovate-external-non-major-024677f5ae6eab4e8d0a92c109a5d8653cfe2688.tgz","fileCount":205,"integrity":"sha512-rjzfy2x+jpx4X3fZceq5+P+Me2MjFZhtSoG/b/JuwFiowBNV7wJxVsP4Kev3Y7jaADFT0QxMHv1A0tqFr+9OHw==","signatures":[{"sig":"MEQCIGMqoE/qHibMYbt8H92a67RyS9uprYC12NpoURei/tHzAiB8OtmiKIIfuvkfZ9kUfC+E3Zsm1SpI+6OitjXD6sP72A==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":758626},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"024677f5ae6eab4e8d0a92c109a5d8653cfe2688","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-14T23:32:51.799Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.73","@balena/jellyfish-logger":"^3.0.109","@balena/jellyfish-metrics":"^1.0.374","@balena/jellyfish-environment":"^5.1.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.15-renovate-external-non-major-024677f5ae6eab4e8d0a92c109a5d8653cfe2688_1636932948954_0.12904764838192673","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.15-renovate-internal-patch-40a1eb01761618f839b30ef87b708360bfbf4210":{"name":"@balena/jellyfish-core","version":"8.0.15-renovate-internal-patch-40a1eb01761618f839b30ef87b708360bfbf4210","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.15-renovate-internal-patch-40a1eb01761618f839b30ef87b708360bfbf4210","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"58af3e07cb7fa5d34bddd8f35650ddda5f692299","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.15-renovate-internal-patch-40a1eb01761618f839b30ef87b708360bfbf4210.tgz","fileCount":205,"integrity":"sha512-VdaENIn2OhVktPmnMcG8OetnXRUw3gim/4TgYCRwBtebYMtoLjLNMW/b+rkwbLLjVnEGYe9HK8tOsDL7zinTag==","signatures":[{"sig":"MEUCIQCTAUktkB8FWuINzZmnKGHwDDFECDmXLVlIRBD+7UHkhAIgIaWFP6TyDHWn4ycpdpvyNi0FKsNq86FSBL/miV+1ou4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":758607},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"40a1eb01761618f839b30ef87b708360bfbf4210","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-15T01:52:51.900Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.75","@balena/jellyfish-logger":"^3.0.112","@balena/jellyfish-metrics":"^1.0.377","@balena/jellyfish-environment":"^5.1.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.8","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.15-renovate-internal-patch-40a1eb01761618f839b30ef87b708360bfbf4210_1636941342325_0.9366361519317328","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.15-joshbwlng-set-license-9cd0168f505b9893bd3d8b961d7ac97db9697506":{"name":"@balena/jellyfish-core","version":"8.0.15-joshbwlng-set-license-9cd0168f505b9893bd3d8b961d7ac97db9697506","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.0.15-joshbwlng-set-license-9cd0168f505b9893bd3d8b961d7ac97db9697506","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"fa5a7fb69578864930ba505f8d1adb329e42546d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.15-joshbwlng-set-license-9cd0168f505b9893bd3d8b961d7ac97db9697506.tgz","fileCount":206,"integrity":"sha512-ETI48EOyORIEYiUsTLtjTIdjjkCB4i0MX2+rfmHd4cJFeyQ1axyKi7jgspyrtjChRJ6aqH3ooaHmBMqbL4JgMw==","signatures":[{"sig":"MEUCIQCnZUCwR4REINGZo59UcqtFEL2ALXLFVF9FQN1EySRfcQIgSRMGIGJwVd1ts3OB074/qh5m2IaJTQk/Xclu3uGc+8U=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":769924},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"9cd0168f505b9893bd3d8b961d7ac97db9697506","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-15T03:49:24.119Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.73","@balena/jellyfish-logger":"^3.0.109","@balena/jellyfish-metrics":"^1.0.374","@balena/jellyfish-environment":"^5.1.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.8","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.5","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.15-joshbwlng-set-license-9cd0168f505b9893bd3d8b961d7ac97db9697506_1636948328043_0.9504747605066595","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.15-renovate-internal-patch-f644961246d3ce4e7355c28a35e3c9bb5232a6d8":{"name":"@balena/jellyfish-core","version":"8.0.15-renovate-internal-patch-f644961246d3ce4e7355c28a35e3c9bb5232a6d8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.15-renovate-internal-patch-f644961246d3ce4e7355c28a35e3c9bb5232a6d8","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8cf670f1bbb9151e4b47d56063edd2cfd6a3f9f6","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.15-renovate-internal-patch-f644961246d3ce4e7355c28a35e3c9bb5232a6d8.tgz","fileCount":205,"integrity":"sha512-1p6CeF1QhH4vEHY71fwkYkyJF4h2jALP0GeXgLF09wIwT0no5uQnZEq8Z0KFQH60z9XKzHSC+ReyV4gYrzthcw==","signatures":[{"sig":"MEUCIDfHTlEDKk7KDACHiEccDT26SAOEUx+bWA3FQP+e1Gq2AiEA0VAlGQvSRUpzaaMNhESAgY02dqiLw/jyVjtnEjks1t8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":758607},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f644961246d3ce4e7355c28a35e3c9bb5232a6d8","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-15T04:26:48.070Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.76","@balena/jellyfish-logger":"^3.0.117","@balena/jellyfish-metrics":"^1.0.382","@balena/jellyfish-environment":"^5.1.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.8","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.7","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.15-renovate-internal-patch-f644961246d3ce4e7355c28a35e3c9bb5232a6d8_1636950561646_0.342707114354722","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.15-renovate-internal-patch-37c73bd8c8dd3037420e032f47722f994557d057":{"name":"@balena/jellyfish-core","version":"8.0.15-renovate-internal-patch-37c73bd8c8dd3037420e032f47722f994557d057","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.15-renovate-internal-patch-37c73bd8c8dd3037420e032f47722f994557d057","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3f851f15871502d40a8bbb77143eee0fc39ad927","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.15-renovate-internal-patch-37c73bd8c8dd3037420e032f47722f994557d057.tgz","fileCount":205,"integrity":"sha512-jwonRMxwcDk5qCWRwRuXTifYv7nRjo2p//Q0wGyAjLyZW0OYe/dWNLke/3IoLp+DtkRKa9jt1qb05x4PQmU7CA==","signatures":[{"sig":"MEQCICBqHMTtj4TZdlHgdtJQIUkJx3/YTuBsSkfICisF+tfvAiAjWDbjBff37Y/p/DYF2lRxdueLCTQ7/Gc/bcN+3xbuCw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":758607},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"37c73bd8c8dd3037420e032f47722f994557d057","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-15T11:19:37.850Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.77","@balena/jellyfish-logger":"^3.0.120","@balena/jellyfish-metrics":"^1.0.384","@balena/jellyfish-environment":"^5.1.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.8","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.7","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.15-renovate-internal-patch-37c73bd8c8dd3037420e032f47722f994557d057_1636975314928_0.7451616457790919","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.15":{"name":"@balena/jellyfish-core","version":"8.0.15","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.15","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"dd32bf56ee82d17bcd9faf0d3115b8af0bea8c84","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.15.tgz","fileCount":205,"integrity":"sha512-k1wrzI4nkif+lPKG2Kgtqk1V6OQo9FoHATRtxNYhE4tvl1JyZtACdkWTZgKSp+fS0Ybh3t1U2EsWi9SmdsgszA==","signatures":[{"sig":"MEUCIQDGqol5k+nQSevQrMLW9IFmHFExsp1efYgok2NJGGUOuAIgY0X6Irm6JFTitZI7eWInI/pp82/1UvOpL+ZF3bFc9HY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":758542},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"fc5c946d72e4779d550bf2416f331b17d213969b","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-15T11:25:32.542Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.77","@balena/jellyfish-logger":"^3.0.120","@balena/jellyfish-metrics":"^1.0.384","@balena/jellyfish-environment":"^5.1.6"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.8","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.7","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.15_1636975646697_0.2823654127277462","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.16-renovate-major-external-major-19b0f0cec8cb59e2acb0a000228d62c3e6fcadcd":{"name":"@balena/jellyfish-core","version":"8.0.16-renovate-major-external-major-19b0f0cec8cb59e2acb0a000228d62c3e6fcadcd","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.16-renovate-major-external-major-19b0f0cec8cb59e2acb0a000228d62c3e6fcadcd","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f7b96a089198a1b73c58165f38834540b54a4dd8","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.16-renovate-major-external-major-19b0f0cec8cb59e2acb0a000228d62c3e6fcadcd.tgz","fileCount":205,"integrity":"sha512-tnwkbxuGUUeMmb6cUX6nLuneK2fwCoId+I0IpQ0vz1ku7GOR9R29KkGTk6DIKRSVRw2a5Y9lW7hJsBeg3lLbyQ==","signatures":[{"sig":"MEYCIQDj9KVCarC5j5tIE9x41R8CVk8ydeKF6Cwi7lHGtI4TrgIhAJ1sblnFmEbVIo6G71xRn3Vv7O922FPXUUA7lFdqMHpB","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":758699},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"19b0f0cec8cb59e2acb0a000228d62c3e6fcadcd","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-15T11:28:19.082Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.77","@balena/jellyfish-logger":"^3.0.120","@balena/jellyfish-metrics":"^1.0.384","@balena/jellyfish-environment":"^5.1.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.8","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^12.0.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.7","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.16-renovate-major-external-major-19b0f0cec8cb59e2acb0a000228d62c3e6fcadcd_1636975875882_0.9022729468471113","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.16-renovate-external-non-major-6dae037beef8d341899837b0ffd2dcc565a29bf5":{"name":"@balena/jellyfish-core","version":"8.0.16-renovate-external-non-major-6dae037beef8d341899837b0ffd2dcc565a29bf5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.16-renovate-external-non-major-6dae037beef8d341899837b0ffd2dcc565a29bf5","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9cdf3d49df3b530a478d2fd76d23991bfcd431c8","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.16-renovate-external-non-major-6dae037beef8d341899837b0ffd2dcc565a29bf5.tgz","fileCount":205,"integrity":"sha512-JBix4x+9ZHpR3VvG/EWbYWQzPcARmICmkpMhkJFLZzj4aIY8daqsjDtjf3nzpTcG+Tgg9lTblgQ4WfDg4P7pNg==","signatures":[{"sig":"MEYCIQCNDUnq5C1MRm1d5e3Zboc2NmsMFREaFZ1kWjJqjblmfAIhAJqMSmgSPsif2Z5DpdV3Po/26NU86U99vbXKSW21gHHU","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":758697},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6dae037beef8d341899837b0ffd2dcc565a29bf5","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-15T11:29:39.409Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.77","@balena/jellyfish-logger":"^3.0.120","@balena/jellyfish-metrics":"^1.0.384","@balena/jellyfish-environment":"^5.1.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.7","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.16-renovate-external-non-major-6dae037beef8d341899837b0ffd2dcc565a29bf5_1636975957490_0.5111755020677531","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.16-renovate-major-json-schema-org-67fc21ce4e6f0c5755209ec9f2382eeb11e1ef8b":{"name":"@balena/jellyfish-core","version":"8.0.16-renovate-major-json-schema-org-67fc21ce4e6f0c5755209ec9f2382eeb11e1ef8b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.16-renovate-major-json-schema-org-67fc21ce4e6f0c5755209ec9f2382eeb11e1ef8b","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"139b54f921031226fffd34cb7c3d134b792e1b1f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.16-renovate-major-json-schema-org-67fc21ce4e6f0c5755209ec9f2382eeb11e1ef8b.tgz","fileCount":205,"integrity":"sha512-nS7AwXF6qE7ksBo0SfKQObUXXL+cnHcyVKo9XBO+WSjRI/H1R7XbNiVlidIyxtxHU/ZUGUqtiKXM9bbD85fn2g==","signatures":[{"sig":"MEUCIQCYWHxS43VaZuWOEctwybqOGv74jrWXu8XFSSUAKiJBDQIgVTTfiJ1q+d0TWzrB1LcJBhuGdMsHPq7W9mbdPAnNFu8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":758710},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"67fc21ce4e6f0c5755209ec9f2382eeb11e1ef8b","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-15T11:29:35.025Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.77","@balena/jellyfish-logger":"^3.0.120","@balena/jellyfish-metrics":"^1.0.384","@balena/jellyfish-environment":"^5.1.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.8","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.7","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.16-renovate-major-json-schema-org-67fc21ce4e6f0c5755209ec9f2382eeb11e1ef8b_1636976000913_0.7505516064153837","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.16":{"name":"@balena/jellyfish-core","version":"8.0.16","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.16","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"dfc62b4f194bc369aa8ac28403e881f43a26d628","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.16.tgz","fileCount":205,"integrity":"sha512-s5dEIw6PuHABLl4fhO4J2zhQeO/75PZ1l7+hOFTukI4EmVqFN7KPFJPYHsBjx46pmS2sMemF/+JpCTqzR7w0+g==","signatures":[{"sig":"MEYCIQDxdF5et0kdadEin9hUeIZ2oi45r2fUp6za18swTncNJQIhAOuvQogOnBIoUbg+b05ihyJBurQvPZ9VVU/g4VGkq3KD","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":758628},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6437b1b755ec22732bbb7157aa8dd3a4dd9b6e59","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-15T11:37:59.313Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.77","@balena/jellyfish-logger":"^3.0.120","@balena/jellyfish-metrics":"^1.0.384","@balena/jellyfish-environment":"^5.1.6"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.7","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.16_1636976377453_0.7281036491704407","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.17-renovate-major-json-schema-org-52c4421a5dceaa57d6c07c0842a98fb1a29df836":{"name":"@balena/jellyfish-core","version":"8.0.17-renovate-major-json-schema-org-52c4421a5dceaa57d6c07c0842a98fb1a29df836","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.17-renovate-major-json-schema-org-52c4421a5dceaa57d6c07c0842a98fb1a29df836","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d313931c05f50b6e2f0b9588c63d42489f3a770b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.17-renovate-major-json-schema-org-52c4421a5dceaa57d6c07c0842a98fb1a29df836.tgz","fileCount":205,"integrity":"sha512-3Lc9K0+yTi0SZCRMoa+IKlS57LjuuhRBQhL8nciEe7Xbj5KZHBScU746L4KD2NKTUStma7V9eyt3cX6gWELRvw==","signatures":[{"sig":"MEUCIQCKReVCzFLmtstAZEzKQGbto4ExXIEItAZb9EKNqiF/rwIgNVC9p3wkrmVsPQ/NHboLC9G7MspEhzOmg/uXJO3T/KI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":758796},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"52c4421a5dceaa57d6c07c0842a98fb1a29df836","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-15T11:39:42.805Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.77","@balena/jellyfish-logger":"^3.0.120","@balena/jellyfish-metrics":"^1.0.384","@balena/jellyfish-environment":"^5.1.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.7","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.17-renovate-major-json-schema-org-52c4421a5dceaa57d6c07c0842a98fb1a29df836_1636976584983_0.01961665361834264","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.17-renovate-major-external-major-be7c8fc89893562a0e236401c6d8f23b53cb1154":{"name":"@balena/jellyfish-core","version":"8.0.17-renovate-major-external-major-be7c8fc89893562a0e236401c6d8f23b53cb1154","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.17-renovate-major-external-major-be7c8fc89893562a0e236401c6d8f23b53cb1154","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0e7861cb4fdde6584054bc3938ca911c177daf14","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.17-renovate-major-external-major-be7c8fc89893562a0e236401c6d8f23b53cb1154.tgz","fileCount":205,"integrity":"sha512-5Y83qr4TazD5Zy9HJBOWGmBPkJdahc5NCfDtuTYKB0ubvg6hbR6LGRlHazG3Jow64IpOqdsf7zExl/SoHxFmUg==","signatures":[{"sig":"MEYCIQCJ+PIFrJ2j1YCyk8PHHu29YIBrBIq1B35W2wRkKEtzHAIhAOPHTIbF5wuyiZizVfL1uBptuDRRhBUbYMdiuu/SMOFl","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":758785},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"be7c8fc89893562a0e236401c6d8f23b53cb1154","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-15T11:41:49.677Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.77","@balena/jellyfish-logger":"^3.0.120","@balena/jellyfish-metrics":"^1.0.384","@balena/jellyfish-environment":"^5.1.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^12.0.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.7","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.17-renovate-major-external-major-be7c8fc89893562a0e236401c6d8f23b53cb1154_1636976673150_0.0034089555019891637","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.17-renovate-internal-patch-e3b83ad231a62583fb29f5ec502102c7cf565098":{"name":"@balena/jellyfish-core","version":"8.0.17-renovate-internal-patch-e3b83ad231a62583fb29f5ec502102c7cf565098","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.17-renovate-internal-patch-e3b83ad231a62583fb29f5ec502102c7cf565098","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3dd261ea4ef29cf4bd587eddbd80ab9d089ce01f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.17-renovate-internal-patch-e3b83ad231a62583fb29f5ec502102c7cf565098.tgz","fileCount":205,"integrity":"sha512-Y7KL6q32UiAubL9F8Izp4Bn0EJfTYUi/QoVjtxmFGAC7dr7HwuR5xdl26BKpquFtZeH13sVoCjM78p9sl794jA==","signatures":[{"sig":"MEQCIE8MNZ7jv+spkIdy1ucNy58PnNhbt4zd4JrtVvqkXcbkAiA2ZUtPhDmcjvoLD4v3uNaaRNOWFJtun/R+2r6FMopz3Q==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":758798},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e3b83ad231a62583fb29f5ec502102c7cf565098","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-15T13:48:25.839Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.77","@balena/jellyfish-logger":"^3.0.120","@balena/jellyfish-metrics":"^1.0.385","@balena/jellyfish-environment":"^5.1.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^11.2.6","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.7","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.17-renovate-internal-patch-e3b83ad231a62583fb29f5ec502102c7cf565098_1636984326277_0.37428020522048744","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.17":{"name":"@balena/jellyfish-core","version":"8.0.17","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.17","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8b02a7d3fb72b1351d525947dc6ab8a827a6009e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.17.tgz","fileCount":205,"integrity":"sha512-HRN/Yn+48QWfVzTt26a9ZmMHEPv+xVBBwNw4+DMFi/fW50A7weC35OcmzugtbxzYI91Oa08ShVx+8T0m0tJP7g==","signatures":[{"sig":"MEUCIDL9FMi+DkVAkX45RML1GNZ6aLLe2qVMQ1+16QjkfnHVAiEA4vlKfKlUF50ZQoHhGf9aTW/3rQnVDGjUQYMcZXjg+Is=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":758714,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhkufGCRA9TVsSAnZWagAAcTYP/R4aBtIfvMh+mstC1UsV\n3gmpUOMwBlBRM2ngtBUISYlOehJ9zZRDUUcnE7LmxG6Jn31NMtwpKPZhUtW6\nZjhQk9v0Xeh3BoFAVK13N0SGz8ZGObqzCeCMvoWLPEdhZ1hO5vfD+TArpPEk\ncWUd1VGtL9DFOymGeXk/VIuRv9evUAYS8ftPqiSBguPfgK///4YpFX59ejzt\nh0o+sO7y5OwUt/7N97J9/SD9IpKQC0PXQpzB0oM5bBzpSqfqAuTQTjDWIYCW\n4pZ+BJAamSzRs+Qw0rwGnmdZosG0bCf03JYhVGmbaORWfOLV4wLmVGmEnHfc\nZ7Qqd0wyBGWzgKMjL0S/dLT3TRFr+tx6+amdVcthCeYpPG6fFZeMKJsHxCEW\njR92rlB3BgAg1ojHEP9KfqRouk1lVpYZKjFGV3dBP9TUFO8JhvtfPV7VopxA\nPssB+k+W0XqKoFPzpXx2yXJ08RMZicSVK468hZnykRp7lDbw3ZkAgg9yf4af\n12rqLLnuw6ZZsv6HElIXpsW1Q7s1V4ScmVOV6A4WKHvP7kbSdJhh4LlqfyKZ\nvUcER3e2mhPhqzpapK89BXMLnjIFnrLKSpXXhzSdMbRm3Mccm6aqblzd8E9U\nR3p+Qn9e+KY/NQEfYYs3Ro1HuQUDtugmyLZYQZiwCq4R+CW+Lw7n7oY+9jtf\noobs\r\n=Y/BR\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b8e6bcfb8c7deb32b9402912b5f57ce0c15e5af7","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-15T23:04:00.214Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.77","@balena/jellyfish-logger":"^3.0.120","@balena/jellyfish-metrics":"^1.0.384","@balena/jellyfish-environment":"^5.1.6"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^12.0.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.7","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.17_1637017542482_0.7093240573701667","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.17-renovate-internal-patch-c61910b075bc771e5ac7e04c316b6fb9896d693b":{"name":"@balena/jellyfish-core","version":"8.0.17-renovate-internal-patch-c61910b075bc771e5ac7e04c316b6fb9896d693b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.17-renovate-internal-patch-c61910b075bc771e5ac7e04c316b6fb9896d693b","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a76c9c804c206c1b2e846a4c53ac64551ecaa10a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.17-renovate-internal-patch-c61910b075bc771e5ac7e04c316b6fb9896d693b.tgz","fileCount":205,"integrity":"sha512-fGRuxNvqLMNv+pegeamyjsGyfxs1AvXnOan9aNZTsOZlAWcrvGveExplTJdxlb/4JfltxvFfWTh9SnUBpCN7Jw==","signatures":[{"sig":"MEQCICNPZCaAWo4LoA811zSM3Kdi6h+yMTCK4OqNhjHciBz/AiAS7EZkg77kVfllRiuXxtC5eb/hpU6L6gTwR1NBEs4W2A==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":758859,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhkuhKCRA9TVsSAnZWagAAfOIP/RIkv9ahF2W8GPWlByBf\nmtlEjqpYNhkLG0zA3rdMOQKvUZASP4yltzl7FPWjl98tJAA6gtt0Ho0f4oQR\nKNXuioPnIuCs0QQjTrMTAan3nbzhcyxerEbWEHMCkHpBVbbXjeb3m+Ab0uYD\nm7NQ+vnsVcMgUpq52egORfY2nxookqp59xA4Lk+y73h2DcAjR/AGRYNEh7RK\n2vMF/dIl7a7FHkgyU5miaRABGyGAV2HKCccku/Tp/oD9cCKOaxEaSS0Zknz/\n7BuxyRQBiey6fIuet4d+cTgJRVvL+4oJA1RCwl9r0egFJNGrhfoLnnjepNyI\nr6xeqm8yZ2l6ZSU/xOogfGKciDZXyPYWJh4cOx/RLVC7SvVIoGaSU5b1afEx\nvzrO99czTcVthgGhkvcrh/eyX6ZHdarKkokzwczj94cSJ+E3GOaL/Ea+IiS6\nI9HVAZF6NiamKu67lW0BczDFZdRuM9hCl07IHw/ZEdHH1TOGtWu77YzHrLdo\nNFUjyJC+KfUcDq5mCkrm5ndWB4Pb0e7gxllZljzGZHP0sAP70qutv9PCuHa7\naEXewosHGQbk2N4pfoFSY/pnnAS3dwppSNiw970pnTsLQCI0VCos6NWkWBkW\n447Jbq8GvN/9QVZthpK/QyBFKDUBf4QzMft+PQT4hAY1jNGs2ZTZg+6UtfCu\n9yGS\r\n=YWux\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c61910b075bc771e5ac7e04c316b6fb9896d693b","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-15T23:04:49.619Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.77","@balena/jellyfish-logger":"^3.0.120","@balena/jellyfish-metrics":"^1.0.385","@balena/jellyfish-environment":"^5.1.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^12.0.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.7","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.17-renovate-internal-patch-c61910b075bc771e5ac7e04c316b6fb9896d693b_1637017674139_0.10907960264631278","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.17-renovate-major-json-schema-org-20a8b7cbb4b634a3c46208b9f4cb163ae5a99a8d":{"name":"@balena/jellyfish-core","version":"8.0.17-renovate-major-json-schema-org-20a8b7cbb4b634a3c46208b9f4cb163ae5a99a8d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.17-renovate-major-json-schema-org-20a8b7cbb4b634a3c46208b9f4cb163ae5a99a8d","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e791acb29024b645ebaca0c7d3e6a69da0668b9f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.17-renovate-major-json-schema-org-20a8b7cbb4b634a3c46208b9f4cb163ae5a99a8d.tgz","fileCount":205,"integrity":"sha512-AN9setPNoojm7pvqJeS0jijGTvWmBfAaso3ZRD3i7L9/J0eyFdf8K7ySzMnOIHTnT1Z9zZngVAoW0xuqpEJKOg==","signatures":[{"sig":"MEUCIFJbbpKkXNf3aL8ryQPF2ztmpsu9nNYCI7Iu5Bv13RZUAiEAlljV2mG76NwSTb6fSZ7aqYweX0CSuqm6AxcYZf4bi+M=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":758857,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhkuiYCRA9TVsSAnZWagAAmDUP/jURRH0CXQHf9W1T2OQY\nnU67uKywiOKcwGGrgchGtKoPDRnwNel9A4/HDjXIccV4L+Xqq+Nds9cAfTDK\nWIvTD3JBKBZpZLaPbPluu6KxzqfojtkEUxJ0dTOrCqKHGQgXUJoP9kyeWFtk\np4nIRdcP10YlU48sVP5Z9riCmZx1bdN5Se6/0WSayN1aV+Fx3+mHHR5/GN75\nfOzns+LhBzB8O0ZSyrFO9hnmonHZ7+KT9n7UFlNqnaoS6TPy9NNVeoV/4Be2\ntphvwFyCMRvr8u5eY7r304x1HTPnpn3bhBhxh50NPumDH/wMsHZmeRs+Kjdm\nnX4NIUjN0oaB2tmUnKlVO6IB80NeTN3jW8GbiXbWhITa+PwWSvsTVgFnoq31\n4jM8aOS7nMZ5nOXbVr5gKj8MRoowJtlNgji2SracnF3IfbpgylTDfjtj25DQ\n3D9EfsFwedne0Qo0Krqp2B2LctMftXWyr8FW9mbsyTeY0m0lT+ETgT+hra/i\ndTAV/113UdivN2lG1ZWuVKldmO08Jru+rjm37kkPFKK9dl8DVYfhs47vAzNO\nwt+iwaceLN93uv2IqwsX4GN23rrJWPbY3O6ZzizlSvBtc5pkY4qFVd0YGoDJ\nv/zANKXFzer7m08y1/yU1cmOgFX7gOJ5SLsxnYoAKqJm5tMU6yCwD4vwH0ku\n3NNL\r\n=/P46\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"20a8b7cbb4b634a3c46208b9f4cb163ae5a99a8d","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-15T23:05:19.891Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.77","@balena/jellyfish-logger":"^3.0.120","@balena/jellyfish-metrics":"^1.0.384","@balena/jellyfish-environment":"^5.1.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^12.0.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.7","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.17-renovate-major-json-schema-org-20a8b7cbb4b634a3c46208b9f4cb163ae5a99a8d_1637017752600_0.4383564517981655","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.18-renovate-internal-patch-f60a744092140420111576c695d76f6647f1d72d":{"name":"@balena/jellyfish-core","version":"8.0.18-renovate-internal-patch-f60a744092140420111576c695d76f6647f1d72d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.18-renovate-internal-patch-f60a744092140420111576c695d76f6647f1d72d","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"fea7fe08e3267294d2a92ec6c824dfa0c282664b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.18-renovate-internal-patch-f60a744092140420111576c695d76f6647f1d72d.tgz","fileCount":205,"integrity":"sha512-42Sb1KkZKd39Gow35ljH88IqqymaXQIQp+6XfEZE08/yAIYYY2KxlNcDvmLFxe4TqyWV6WgBvxj3EjXQNCuS4w==","signatures":[{"sig":"MEUCIC/Bpsdruu/WnpD1+qpv8+HwCtx3dfZeFlAfYjTuMGVTAiEAj4Lxc+wukBn2xPflH5gIV+6eb/NAGULGK4EOtSqGqhM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":758884,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhkuj4CRA9TVsSAnZWagAAK74P/RcXf1SWbayX4/1BDP8i\n4vGEmMdFrlqvVqIumVtOmGJhRB6tpb7729PVPLQCjo4tUBml/vjwIl46W6jt\nwfxtrCPQ6BNVoAlx9X24yQV8obxFcMSpScyo8fJ0d0+77VUKPcLVIqMn4WHg\nehVnlbrj6CVGw1ZlQ+bcKTuO34kyx+r5SKb9D+b5+ryMLo/nhH+mBDi0yaNl\nUgd8EeCzZruf2PDOBucz9muJv0EUE1pCs2juaXmPOgldUQkIQm2IX16owr4Z\nHjxycxXB/H7i1+f/n3824qaCeIxif3mSE8hH5ezXPqTc4PLpTQsES1ywnEy+\nyF2hS30lOEGQUVMsNNg01tdvXt+y9ohV67kxd/Ho4BMo7/JJFM5OKluDhFtm\nmZBda8UoYRhdFZJu61iPQ+MHDs1W6LY/uAsHzF8k9GJyCDP0PQYvNQ6428xG\n4JWIN+pXyiP9duwgzoKm+L9vgSBWeuPBDCWeq5IYA17l7HbJrKD7KqkfphSd\n1tMkMUjcEg8yc44yeQu/fHGtjl/QGB8CBdhHXDnLZe5Uw+7QgXLT53cD49I7\nv3C315O8EpkGnuFz8IPWaAjA3NkiafZE3gOYSrUQwYrIvzWChb8Ce5T4w7uf\nc9H7a+S5pT0Ml97DKiWHWefP3DgoR9NVh16e0j7R5plfl+pGEEI9PPziXKH6\nBXGu\r\n=FCa/\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f60a744092140420111576c695d76f6647f1d72d","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-15T23:07:40.168Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.77","@balena/jellyfish-logger":"^3.0.120","@balena/jellyfish-metrics":"^1.0.385","@balena/jellyfish-environment":"^5.1.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^12.0.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.7","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.18-renovate-internal-patch-f60a744092140420111576c695d76f6647f1d72d_1637017848476_0.8780988417724822","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.18-renovate-major-json-schema-org-50e3b6b8f3900ac2eec353b88ca56061ee55fd56":{"name":"@balena/jellyfish-core","version":"8.0.18-renovate-major-json-schema-org-50e3b6b8f3900ac2eec353b88ca56061ee55fd56","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.18-renovate-major-json-schema-org-50e3b6b8f3900ac2eec353b88ca56061ee55fd56","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a37c5ee446c6e419eb485d9e0185500ef51fef0c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.18-renovate-major-json-schema-org-50e3b6b8f3900ac2eec353b88ca56061ee55fd56.tgz","fileCount":205,"integrity":"sha512-0IOfWesVr0b4BRHdhZZxsnYFU2vxeshdu6pFxvy9cN5lEHG5j0qcspBVSsHgsEjqR1CDUVVhhvPxJyoWRCnHhQ==","signatures":[{"sig":"MEUCIQCAMTCN4HTBpkD0oNKPF7nsScE5Skap2kQNqVg4d0k4tAIgCyETDOHn1HgXW1ZFLGd9XbfWwes5BFy5igsY1vXd0aM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":758882,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhkukwCRA9TVsSAnZWagAAxB0QAJi6rtCEUmzbeeR5KDXM\npIeQuL/q4ZesUSwctfrmEQll1Zwwp/ec/sYApmpuJwovEVWpxyVAbY9HU1S9\nI6f9d8+A7EuxbO/qPhpZvQwxciEjGN9qItrcuT1QUlIhw7nI9I34qWw0u3B0\nztJPyYIgwPHRa6vrr4L7ofZZyjusZCwIZDCXKPNqe33LamLlLIURgxct+5WF\nxqBcr37Xl9NKWqS7NsHf+uNBoT+sLqUiH3oncOeZDED/5C2Uss/9CMiMWkIN\nXTZijyKXoWSWIWXlJfJ37kAtGaHJRakp+XZNxQKGu5r+EqIK+4BWdFgCI4Eu\nnIca0a3lhC0Zhp5kezGgqAniOvbmsPxTf9lIAMERurriFNAi1GLZkisvMaEI\nPB4ohPwbNYEXmDBW3YobAp0BN0p7gFfxzAuwjzllLUqcMlUmkktL2A/aZ/G6\npx7xhOALJkvz7MhsOW9IZbmu3qVSkqzrXDZmPaxrN3psqjqhNW0IWqmxhYyO\njcKQFi7QJCUqpwobT+xXiHA/4oPhZ3cRIOLNX7KSNSTqK5hX868uhvswr8dc\nsaxSLKMVM9WE7ws8jdJ+Wu7bON4Eyu2GC7r45GaNy6ySu1pIjUtnUvkYWnys\ntM7oyFE8nnZg8Uo8Yfo9SExCeshwNimYqc19y1XECDRUHNB8+AWg+Nr07wQ9\nMihy\r\n=BEEJ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"50e3b6b8f3900ac2eec353b88ca56061ee55fd56","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-15T23:07:49.573Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.77","@balena/jellyfish-logger":"^3.0.120","@balena/jellyfish-metrics":"^1.0.384","@balena/jellyfish-environment":"^5.1.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^12.0.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.7","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.18-renovate-major-json-schema-org-50e3b6b8f3900ac2eec353b88ca56061ee55fd56_1637017904414_0.09729956057884803","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.18":{"name":"@balena/jellyfish-core","version":"8.0.18","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.18","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"fc822b5a0081ce381b54dd782cab6fe9ce4ba63f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.18.tgz","fileCount":205,"integrity":"sha512-7max3+ojgscongBN3XKY0AMgVlRcak3BzLrp9K0vMgG7IpENU8soJUUVWju8Zp26tZfDi8SgycnZ54gXAE9ndw==","signatures":[{"sig":"MEQCIANNWbDJIqvO+ompZoThiHZQGt6LR1ToKw9ZrRvpnINnAiBAff0jra8BFiDkAt01qmDPTB/HIjdY+F/JSQ8DBQo9tw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":758819,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhkut8CRA9TVsSAnZWagAAzfUP/A+0MuXZZqW1xLiQx8lD\nKHygrkqpW9/gyMCoffWMB0YQI9O8aD8VgScmYVRC7w9HVuI2YNLjn8WUF6zs\nWP18ICN7AX8D4eotvOJYF6CwucRO9ydKjDt7JBlwHtDLb2g7t0BYuw87/cgA\n+Htzn7kfN0KjroADQaM+7Hu1ND/XIWvY7cTCR2EO4HNVNAvDaSVpaMUbTs6Q\nGrD/M/JnM6l9l97O5Mw36tsjIYFZLduD3ZeorjgNy0iv5YxgkIHCm6972LKU\n3xdKZ7n1lm4q4+lSEHrOixn06TyizSlHEpiv8DseD7RP60T6rupT+IrYQb6T\ngjYXuOacSwLt4L+5SU6wjl2seCjOAr9VFjgB794nm2OMsKyJeeqvz8/tZGAE\nMEkyFXt4leS1BbntRCHo2uzI+rVxjEYnLYIEwSHtMs2mWY7jP5jikVZdb8HK\nF1xG6DdwIwrQxndIQWwO/PPKhyXErlSMHfgcaXXoUV/4ieugUBXLX0M/HUl8\nHHUZanApeuGFQ56ropbiOJpgYngg0EHxTfL1qlzjShpVqlVfmiffFe7jcoCL\nL3lPkIIAFo3QGBRF5mtN+1AcUuTeNjl6CxKVDb4KQW5ssGlcVr6AJHUIhIqa\nV5VwPcYpNN9iyieEYhaiOvLK9WWfyLgT7J0aLsDDLiF003aGIiiUb0KZMash\ncMMw\r\n=XhbW\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c853783da4f54a8cf0bcf37a4dba7acc2614d1d5","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-15T23:19:51.308Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.77","@balena/jellyfish-logger":"^3.0.120","@balena/jellyfish-metrics":"^1.0.385","@balena/jellyfish-environment":"^5.1.6"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^12.0.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.7","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.18_1637018492049_0.3854504111945267","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.19-renovate-major-json-schema-org-85c4fec348c45bfd9169196b6ae032b4e7f34282":{"name":"@balena/jellyfish-core","version":"8.0.19-renovate-major-json-schema-org-85c4fec348c45bfd9169196b6ae032b4e7f34282","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"UNLICENSED","_id":"@balena/jellyfish-core@8.0.19-renovate-major-json-schema-org-85c4fec348c45bfd9169196b6ae032b4e7f34282","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"dabe0513de40b9e56f308c70463e5f3556e63b35","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.19-renovate-major-json-schema-org-85c4fec348c45bfd9169196b6ae032b4e7f34282.tgz","fileCount":205,"integrity":"sha512-pJr3Gzp+96UKQ/DZHlrUMEwkPeSXqDQpHGJlq2Z9tX0s6ijGAnlo2hYkxCVnO+wc4sSMsO248LFIgJr9gkGRkA==","signatures":[{"sig":"MEQCIBUgRvCAH4hn3GJN2tMybYltWHC0CqtVhZcOK9eFl2k6AiBjAphFR8EJ15ac8EuCHPRTph4yrBx0WZLA0w20ftU78A==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":758987,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhkuy3CRA9TVsSAnZWagAA8LoQAJXJx7k7XsErrrh7N2Bt\nx16V7Z9+JzPDPf/T1JMbEJ01x3Y1X4IfrIuebsa/YgjIqHmAqWcTa2dm2Csd\n1axtIYsD5Wn6CAF3/ewC1LPsoLgJCU/4d1jwACVj47PwczaDcRHdqKpXTF2U\nBgPe4saLOBp+mbhgNXwP30YpsAwnPihZg0ROn4q43v4rOUNX/QkS3prhkpHU\nO/NnsEe0Q1yaOWe6M9yLW/FmkgLfm2CHAZBxRc8f/d10sJbyY2PoikFFkZZ4\nIWnqDfHjyvOQ3ZSQlke6gVEV3kh+7gIVzEC+xyxVvxq6uuDZCnUjhjmPnxyl\nHGkVf4lM+gekxhlidRcSB8Lx3ucH7gpv/UsQGwr8KJYU8sQR3LskSVAHTUK+\n1AylirLav7JdtXeqHMTCtgDX+CxFC5GIUB3Yyt4DLYHbhm/PcYX2q2fwIdMn\nMupXLjEYqVJzVmqxRkBfHh8++NdtVa3zaIR4XWTdOaUUC1yTI5cdmpshlw/c\n77yZE0fTeKmkNJJOmUhyZ9uxp175dMZQI1vPiRFKWqNDEs1Qkn3pXCt4aos6\nA6aFHDkC6vS0TuP7whj458a/wzSEqj10F6OMsBLHXwFoL/v9Fb6rhLZD42im\nLhOHrZPLWVt0CW7U5aKY9lnoGx3RNSdEpz2drgFyqYQ8O5u+cpwmNOdaV0/0\ng25s\r\n=HdaB\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"85c4fec348c45bfd9169196b6ae032b4e7f34282","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-15T23:21:33.666Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.77","@balena/jellyfish-logger":"^3.0.120","@balena/jellyfish-metrics":"^1.0.385","@balena/jellyfish-environment":"^5.1.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^12.0.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.7","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.19-renovate-major-json-schema-org-85c4fec348c45bfd9169196b6ae032b4e7f34282_1637018806910_0.5182669933786039","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.19-joshbwlng-set-license-d94ac3d05cd9108dd424b4e4462f0d4e5fae363d":{"name":"@balena/jellyfish-core","version":"8.0.19-joshbwlng-set-license-d94ac3d05cd9108dd424b4e4462f0d4e5fae363d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.0.19-joshbwlng-set-license-d94ac3d05cd9108dd424b4e4462f0d4e5fae363d","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"064f20c40113bfcfbacb7924ab69a254ca4b76fd","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.19-joshbwlng-set-license-d94ac3d05cd9108dd424b4e4462f0d4e5fae363d.tgz","fileCount":206,"integrity":"sha512-594r0CTzpsRoTu1U6sM++XaH/VT+svSxp8aE2qtgZZe1CRuCuhlqMpCZeq3mp+00OBtF0mSnclbOoVA2sJgWzw==","signatures":[{"sig":"MEYCIQCs02ODIp1MuPcpwuiQN/nEmdktLhC49wqrQZQpKTmowAIhAJto8fzxPZHtTjWV+EngY7Ped1egUms8nPA40Pf1fjrV","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":770272,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhkvnKCRA9TVsSAnZWagAAigQP/0SDpDnFUv3Ci5BZUY3d\nLkDp+egI9rQnY5YuuHxzgiuXwl6YiKe7sgzz5ZY0GMud/2moKPO5Cma2iyzZ\nxA+TPyfYxx72NoTwurWSPM4LUx9ZMHUyMx4WDBKO6NULk3wOaLvNdrZxb+at\nCV6eIf4aYdyG3ucnkIE8TOO9Jw1wy743mneLelMGm1yFSRfdqeu0F871XGqa\nWjSWHpfdORPql5/yrfcSuXYCY6f2X1v2qAHIOuYNJhPzipRvb3ZB3pu8CCG9\nIZ6i07SY+r/F9oruV4snLZZUFtjMfOEHK09/HET5j25BayTPOQiTQ6ssKMWc\nWQVphY9PX/kdytDwS4c2h/OMkzNnoUlzBP5BCoeCuBrVTZgqMkgc9hAAxuKw\nK8Ua4gkVm7wKrr4N6SEvIzpILHPHbzq3pPVVC3NBHcqadJjCbvzP608qds3F\nIuYVFF8Zmb7UmcMP3yqUIJv/lRvjdcVixF3kD82XjBrP3uw+FE+vq7KsUJ03\n2+IBqgRm2DyjUz/kC87iMpKxrF/Dacn2fvh1kHgYH6IOZKrXwdcKFJstfqUg\nm5dazkWmICShMRK46S6ZHC7q5mGN+o+GakCgPuqAuw4hQEpcSN+VvvyiPsgK\nN6xFysdiL8xJpTO4Qk0LdgacLk8Fzn6B89q3DNQh6EilhXHMh+aO6UCAH2Y/\nJUhl\r\n=NQlm\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d94ac3d05cd9108dd424b4e4462f0d4e5fae363d","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-16T00:18:06.047Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.77","@balena/jellyfish-logger":"^3.0.120","@balena/jellyfish-metrics":"^1.0.385","@balena/jellyfish-environment":"^5.1.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^12.0.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.7","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.19-joshbwlng-set-license-d94ac3d05cd9108dd424b4e4462f0d4e5fae363d_1637022154257_0.8243251225653014","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.19":{"name":"@balena/jellyfish-core","version":"8.0.19","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.0.19","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1decaeeab616c23e20500f4843b3ecb4e6813eb9","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.19.tgz","fileCount":206,"integrity":"sha512-mxpBHJq5xCJHBCRjen0a6cIvSELlcgJVt4iEY/3Sby0CvKmt+SbTM+VBrDHMsUp3Z8Ks5SNlZROQCRDzQvENyg==","signatures":[{"sig":"MEYCIQCJ2KMroF2eGTLRRUl98tCNtg6jdcahIq97Ss0Ai1DoAAIhAJ4z4rskhue1/gp2Y3KAd1U+U8Lkgqwvcai1eq+ubbGz","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":770209,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhkwOsCRA9TVsSAnZWagAAQOoQAJsUAWOpioniSRD8mRYW\nNz82SjnGJOuSdgRb9oIcHTEuEv50ssxHLdHZoCzAkR/eB4z31noRJS/RPncE\n1yFX+0FYtpHo57fLB1JOEjAZ4RrPfFv7pgbZwJX9VIgmq6RWvB6H9is78GtN\ndR22P+52fBdNmcZOS4F6qi/pbSNJv+tC07tVmRc4F/W6CWxht6cla/2KWhxS\nvn2AJYxrH8AZiYJMHKP2UMzakvOQfqzPY+SsKV8D+JIXuIrZk5gOZ+1fgrRE\nAjCRmdqjqUy2GxYDEj5xavcG0zd7wQgWtrGHvDIEXqlfsXQSYaUFrN7oIn8W\nLanQi6eVu+T24o+B9LBOFOSqD7MZyZ5FgoljJwmyCtXz+sWqi4jqnxr5plPM\n2Is6B8RsBHbU+4PChBtdCTNYURL4BiiU/G8HnDCcPgXISJD8rZSBvjJgUM1N\nK5Xlh2gfsZ8y8nj6yUWR3elxoLru+J+3+jRCpZ5g7663WyElTTOqXhcauUPq\n667DuNilXwMIs7hk1AnemXZAjEGaqqr3Xm4NORpB63TjsZZrZM2xwqbE+D3l\neULDFdGd8K+woDa7WzsNT9C1WusbQa3dkYLeMrDydf+xXcwouwKmt8FTeEs2\nIJV/p9LMTD+5B2LqHynNwuo5hUGVizpVlfRxQOsWJq0Rh2ieAi/84621pSbo\n6yDs\r\n=QNJa\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"3a3bdc5648a37d317d37099883029f142c7d7e66","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-16T01:02:58.723Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.77","@balena/jellyfish-logger":"^3.0.120","@balena/jellyfish-metrics":"^1.0.385","@balena/jellyfish-environment":"^5.1.6"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^12.0.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.7","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.19_1637024684588_0.385935472626187","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.20-renovate-internal-patch-8972de131473c361d38280e222c7031f470ba828":{"name":"@balena/jellyfish-core","version":"8.0.20-renovate-internal-patch-8972de131473c361d38280e222c7031f470ba828","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.0.20-renovate-internal-patch-8972de131473c361d38280e222c7031f470ba828","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"862b9dcb435a2a49dfb8771b11260fc81737aada","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.20-renovate-internal-patch-8972de131473c361d38280e222c7031f470ba828.tgz","fileCount":206,"integrity":"sha512-ZovNCeIRlrt/eOmrRm1k/ki6BEahPMMm3HnZaF+QGn8gwzUCWy+lkDLlY1iSySILQRYTBZNLHGGBZm5YJorHYg==","signatures":[{"sig":"MEQCIGa56C5RUhpKTwhylhbBpke0MQ0pyrbBjsDHWXcr203bAiAUr0I35wpgU4hZA4UC3qRzrUJ598QETmElbJ0EQoF6OA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":770345,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhkwRfCRA9TVsSAnZWagAASJ0QAIASBT+sDfk1Jsysbz2M\nAFj/A97y++7ZqVPOFti/q3Q4WKJiotIibbSR1gjQwzKIlRSpMStPQVO47TcT\nAr79HWedhFRQfcbigtQvnhhuOIHCML1/dgr2xbDB1ZdAxo0auHWrhzP+u/Lc\nI9oyLx/L7m1njPDPje2HkUavSy/ibARx03vBCmGzUGC9S3x+g8Egn+jTpWzB\ntYZqrHE8DGuZJCplVfQwNRfKSW9kCrnt/kMuuXwhWsUXK5L9BTrHi24mBq0Z\nxh9s7n8rIlGm39ABBm4QAzgwinDUfeQ2KgrQOD2QLGI2paO8KbRWgUnRe8XY\n/t42vw5mFsKq7se15A+xWGdK3Dem3aEhQsGj2xiwEQfzKvPgnIc8BVNndhPJ\nuKDByGwCZTSxAsDwyxIXfeeqtTuUSunbGagvbrm4hn9bPaOaHtMmQwSAzteW\nUgdTL7sMSzhfgIA7Iq4pLq0nNGspUUctogyfNNccx23RLD/2Z67LREmm432E\nGpcyeBIbPnS0sKjP49Gy5r5YycpdyUqdnGCECZizIzluxBVz+BjaqGL/iwMR\nllRo95PoVwRLseC9GpoT0w2Ofm5OOJwVFuAgIdE1ObuB1mo6skLITIyb8lYB\nGQRv/HS0Le/K1rpL6gzGgIUWIVRWWYf4P3wtPQPGLZajuy2RfB0nesVexsdg\nPia9\r\n=78bB\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8972de131473c361d38280e222c7031f470ba828","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-16T01:04:18.322Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.78","@balena/jellyfish-logger":"^3.0.120","@balena/jellyfish-metrics":"^1.0.386","@balena/jellyfish-environment":"^5.1.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^12.0.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.8","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.20-renovate-internal-patch-8972de131473c361d38280e222c7031f470ba828_1637024863148_0.9718757951141435","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.20-repo-sync-jellyfish-config-default-9e82820058da7f82da43568e3425cffc6b736635":{"name":"@balena/jellyfish-core","version":"8.0.20-repo-sync-jellyfish-config-default-9e82820058da7f82da43568e3425cffc6b736635","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.0.20-repo-sync-jellyfish-config-default-9e82820058da7f82da43568e3425cffc6b736635","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"cb059d01815fe89276a0889fccb5da6fad79a4e3","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.20-repo-sync-jellyfish-config-default-9e82820058da7f82da43568e3425cffc6b736635.tgz","fileCount":206,"integrity":"sha512-+kIAqRw4hnUTaKi12DrA0UhXE//KK1tpkDjhnVeTwemjChATReqCSiocIvAMEO0yu/yQ1Gmq9fB7+hLbCsAjGQ==","signatures":[{"sig":"MEUCIF0TtgJU66WkevuAFYr1wM+kUKP3sJS4owU7T6BgLqYqAiEA/iarpaSkpjxY1Bp3GICmfWFOxUCtHan33oQLv20ZgY0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":770377},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"9e82820058da7f82da43568e3425cffc6b736635","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-16T01:56:35.138Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.77","@balena/jellyfish-logger":"^3.0.120","@balena/jellyfish-metrics":"^1.0.385","@balena/jellyfish-environment":"^5.1.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^12.0.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.7","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.20-repo-sync-jellyfish-config-default-9e82820058da7f82da43568e3425cffc6b736635_1637028032930_0.5638817908052234","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.20-renovate-internal-patch-756ea8245c544d07c7386454edf9f29606ed80f1":{"name":"@balena/jellyfish-core","version":"8.0.20-renovate-internal-patch-756ea8245c544d07c7386454edf9f29606ed80f1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.0.20-renovate-internal-patch-756ea8245c544d07c7386454edf9f29606ed80f1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e9f8a1d58d90633c47f1f7373602ef40942d52b7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.20-renovate-internal-patch-756ea8245c544d07c7386454edf9f29606ed80f1.tgz","fileCount":206,"integrity":"sha512-o6Z43EVxT+C9WwrRWWN2CvvZe51L0w02ukojXk8vEII3QY3LyBrSHeZF5StwwMWa0oQI1sQEDaYDpwGmCvIBRg==","signatures":[{"sig":"MEYCIQCPHS7pu/uAAyac5T4H9m2eR5f/wdA0vXkbuwE8oAWyeAIhANkS0xjWFTd0Zzm3fu0MJCNLMjWMb/wf274wTlCDRBk2","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":770345,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhkxpGCRA9TVsSAnZWagAAwBkP/RKeVLx8tZ7TohmkVq09\np3e/7QJmI78Sz6htYBsJNmMb4qojtlo9GbL1JRoK91LvKI32Z5dBTtKgUK9L\nt5iXtYfGu42rdqKOzi1xxIff7vc6SbqRwTDL0LCX/tmzup8kXe6xWb+0Pg1U\ng5cRfYQLxbqqGMcsA6tJuLvhkEXSqNLoH6n6YPbfjzTxHxQP+ZykzalH/tNY\nWnj6JZGarL5C5KCq6F7MP9IbEGAkbIqBmbvdMhPGlfvvoGoivWIC6sAHjnFO\nRrDlN2cvR1Igrjt1uWZgISXvpVIYZX4uhtGu33RSDK9wghW+4S6o+/0ZkDx9\ni26nBFFu6l7NLT2ShQvTFtlZO2pM7y8kX4YD1RgLXhJjEeFmmQqOpnNi2qoa\nKSp4wEA1XFGqpyvGInKkt6JuXZPMvBzf3rpcBRtvs4MAwLpaLqi96rcuOp5f\n0Q7+wbo1gh2Q29rcaRhZLIfosQuUau+tSx2aCz0UismilOvcCkfdE4v+41gX\nLm2RrgFgXSDUBQLd3MhPSX80ovNiAvcHIufoE3DFWzyGNiIaWxXkkbpsAvVt\nA7+pTbIpyY6yodbttlK/LE6KhMLhLH8gyW9l/eLWjHFESoxunt9BRgXdF2bY\n7AWN/uSLnQSGu+vwY8LKgAB9FL8g0WPOCHSX9S2MF7UWBEyKFr6es5an6RuZ\n4fL+\r\n=t0VC\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"756ea8245c544d07c7386454edf9f29606ed80f1","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-16T02:38:16.294Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.78","@balena/jellyfish-logger":"^3.0.121","@balena/jellyfish-metrics":"^1.0.386","@balena/jellyfish-environment":"^5.1.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^12.0.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.8","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.20-renovate-internal-patch-756ea8245c544d07c7386454edf9f29606ed80f1_1637030469945_0.7809122607101096","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.20-renovate-internal-patch-4c591bd14e941c759a91db0891375090a831ce99":{"name":"@balena/jellyfish-core","version":"8.0.20-renovate-internal-patch-4c591bd14e941c759a91db0891375090a831ce99","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.0.20-renovate-internal-patch-4c591bd14e941c759a91db0891375090a831ce99","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"53e7d1af2084bb22af8ce0b6f2c4d39d434a3cd6","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.20-renovate-internal-patch-4c591bd14e941c759a91db0891375090a831ce99.tgz","fileCount":206,"integrity":"sha512-Vox886wpaxOSrOzciJzmAofE0uVywvenA37uo73s/obGb119TXYbmk4b9+ywSMs9I94iVKi3qNKkoz4cLS+sHQ==","signatures":[{"sig":"MEYCIQDr+QT3SR8NKRNWTILqaJ58vvu39QjVNQkTgNoUNHeSewIhAKyaDiB1TqwNaxsZJyf/3Q8cN32ACVmdGNG8x3wACa+L","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":770345,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhkzS4CRA9TVsSAnZWagAAYNkP/0ceAO5HR3QDrY87rhkV\nKVSjLmkUxzau+RNUretZa3wbFV2Qv09/vASUiuaf218vmK2s5DR8vagU5VVH\nCEcmSStzvyMlvTFI+TNd+bfEBMASI6hIHHlMKrDrcAR1/keUMJ84eq5zBW8W\nYRWzncbjWl/bWcf+txv5Z4k1TMffCeNWuB1FvQ/1yJSkscg4c2dDn9A72XVi\nWC+96tZ5qof9N/rc17WhfJKu+Rf1bMYjCJKsv2egCKiV5pSDDQKi/xiyu+0t\nrJ6RTAHSE6G+IiYtRYcSOKJ1ViQoN5cakV5mkcmYaoTOheI1V9RRgs+Z8d8Q\njjohBTH6pVaERbpVDQfS57QMYPA65ZVK31Uu97O5dyqgiWAWdCR57aBCSETW\n70JhIF03FZdaCyF+O/NeKpmHdHVhdPntaui4dAAIsODM5rek3UM27xgxHWXi\n0Tr9iqymGSFRSar7BCRVc27wi7hQ3AEZMbuU1pZsWqJvo1lLXztC7MPcG29c\ntWsfknQg9gLiUeR1f4DAHu6480+RGkqjV44h5HPAZoRVpDBKhFSmj7HSNBbF\n/rPABXRmx38jMaC11vkQPKhu4J406jOcaOGkBmKb47d/bhvyiLHgubqzgksr\nidqGkC6kj8H7D5GqwDT7cAMOHgx1clwgmQJF8izfJ7i/F/n+cM91EAqedoKb\nxe7l\r\n=hfBH\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4c591bd14e941c759a91db0891375090a831ce99","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-16T04:31:15.838Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.80","@balena/jellyfish-logger":"^3.0.124","@balena/jellyfish-metrics":"^1.0.389","@balena/jellyfish-environment":"^5.1.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^12.0.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.8","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.20-renovate-internal-patch-4c591bd14e941c759a91db0891375090a831ce99_1637037240586_0.05545983704770352","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.20-renovate-internal-patch-0245af36b24aafb7937925add53bb865a93fc92a":{"name":"@balena/jellyfish-core","version":"8.0.20-renovate-internal-patch-0245af36b24aafb7937925add53bb865a93fc92a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.0.20-renovate-internal-patch-0245af36b24aafb7937925add53bb865a93fc92a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f3cd794c6698931f38680e3d3fb2459b11169ce2","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.20-renovate-internal-patch-0245af36b24aafb7937925add53bb865a93fc92a.tgz","fileCount":206,"integrity":"sha512-CgQHXkyGbF467rjM/SYCKb7hkFXYQxaCaalG2fjeFPBa2gt6d3aclo4smf5X+3OfL+680wv3a873UR4BR1aghw==","signatures":[{"sig":"MEYCIQDJRmNoxN4RoPeQn02mfnECrXu3nzOGd2mHbc+Gh+mWegIhAL5BPaRMPn9IefYuywwhHf3v1bl8jR5RjFMtXglEReJT","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":770345},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0245af36b24aafb7937925add53bb865a93fc92a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-16T08:54:06.199Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.80","@balena/jellyfish-logger":"^3.0.124","@balena/jellyfish-metrics":"^1.0.390","@balena/jellyfish-environment":"^5.1.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^12.0.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.8","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.20-renovate-internal-patch-0245af36b24aafb7937925add53bb865a93fc92a_1637053010339_0.5709066406620251","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.20-repo-sync-secrets-default-f8e9e613e325f42f57629cfc545be142c09e8a48":{"name":"@balena/jellyfish-core","version":"8.0.20-repo-sync-secrets-default-f8e9e613e325f42f57629cfc545be142c09e8a48","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.0.20-repo-sync-secrets-default-f8e9e613e325f42f57629cfc545be142c09e8a48","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"775d50833917ebdfa77689f308635cc9fbe197e7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.20-repo-sync-secrets-default-f8e9e613e325f42f57629cfc545be142c09e8a48.tgz","fileCount":206,"integrity":"sha512-hsA1odElQ89lGVh27/5xX6EmTDlAcbW98kDLO67w6cnEZrdQ8tz17DYC9Ksf4Cv8Notp3c65dOqjuSlwVYd5Yg==","signatures":[{"sig":"MEUCIGiz20fDRk5vfe3s0I3gL/i3hzJGDLKUbrvRQ9LJz+bhAiEA7+kCxfsEIrsbQVXSf/cT933GtJkoxPSCNdNGE/lraD4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":770391,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhk7RfCRA9TVsSAnZWagAA+JUP/jbAXE2k5unp7t6LFJW9\np7FZgxwf8W6wXQmwNhqrfG/HIgUQqh8UgrbGjWR4tpb9bIzH+T4AC2QT1aVS\nblZWBwrbA4X/o9DuKjvdMIazWKdiPDhZxoibEUbpXK6nvUfjpqHpsHJ6KL/X\nqrVGyAzJml/MeJuD418ms6NAR+3PnGmPABW6JrGQbushYiRPZmmYnVBkW4Ja\nDewUMLz7PSd3zvS+PaJhLR3SnhmmKucmq4xDdSh2vD2rVb+vgQBHjmnRTlVw\nFbXet+KCJJWBwmob5+faStgKRvlKCrZYrXO8c6A6M2QN2G7vAEa7VkcgZXWd\nuTNETd54tdob5EsAc2qStgiuCKAjp5tEWr/F3jpCzu7tEu3Xk5Eu1jSt3Pyg\n9R7WuMOHdzXQAXVowZYhVkp7OhA+9kL52UFTNQU3XDSzchoLzNtnS2XOLsAf\nPSDreyeZiQqrg2w7tSLLL7xJvblSozy511OloShGLNBZjCYIfFkyMos5Ba2q\npF6wJW9G8ERFyI6b0KatDNvg8f/F00ist/31b3IN4kPL/8KBoyVTC2qzkkX+\nJOlWNdf1iMgz9scZNy9Rx8ND9A/qWgvHKWtv3wrber8ZeBEI5XVm6KvPy9sF\ncQ1nGUFU5L/EQhKg/WtNPNqR8nx3dSf7CPq5yCIp4L+dwnYmhTUr+fsH2hTu\nezD6\r\n=NtcO\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f8e9e613e325f42f57629cfc545be142c09e8a48","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"DOCKER_BUILDKIT=1 docker build -t balena/jellyfish-sut:latest --secret id=npmrc,src=.balena/secrets/npmrc.txt . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-16T13:35:45.729Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.77","@balena/jellyfish-logger":"^3.0.120","@balena/jellyfish-metrics":"^1.0.385","@balena/jellyfish-environment":"^5.1.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^12.0.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.7","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.20-repo-sync-secrets-default-f8e9e613e325f42f57629cfc545be142c09e8a48_1637069919169_0.4185893618689458","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.20-repo-sync-secrets-default-58688a43f3a2feb3d26e77ac76409966051531ab":{"name":"@balena/jellyfish-core","version":"8.0.20-repo-sync-secrets-default-58688a43f3a2feb3d26e77ac76409966051531ab","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.0.20-repo-sync-secrets-default-58688a43f3a2feb3d26e77ac76409966051531ab","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3fedd92c1ef852410ca4df5bbad162daebc51965","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.20-repo-sync-secrets-default-58688a43f3a2feb3d26e77ac76409966051531ab.tgz","fileCount":206,"integrity":"sha512-/LBIMMIPz6vXKcMM5Sp9rodURaFNs9Ay7p0pm2+wGb8qqZRgHP6X2Kebf4L2pb/QUbEiwdv8SenfktI4al/jng==","signatures":[{"sig":"MEUCIC4MYUaFmHngngB7bAWgcna44V0yRK+Qf50tnPVlDJfaAiEAmWRbNj498F1mq5Xa7hYWyQzqsN1rhpX2Io947NRoPmU=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":770389,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhlMQLCRA9TVsSAnZWagAAAQcP/AjKAncrNyN33ScrAm7w\nNBbGZIOp1xOQX7ophKdHDWgOCyNM/mBQSJYN0Bsoo3xJy+axcMzxx57ahImS\nfpKqh4IwyxYp/Y7PD/aXZ4uYJ1gYLR/vNZ+rQuT62XJmI24xY1kdc2zzpNfg\ndnHySJOr59gs+gZzztCAZf1SuuzhpIqhMSnbB5IDXnulHA5iz/SUaMktLb8u\nB+NzMVrqtSHyOcxR29szmWvtEItcai3FyHE0/lOxbi58QoQ88dx4VNLJAPvN\nIJpmQAuUsVQ3GC+Xy0VlEG91u1xL32DB97DkhwrrvJlsZfH6zQnWUStGROwK\naWLorXXxYjIXJ59iLfwSU6h2zQBHlLhWMDZ60jkWoMDw8Z9hXrrfben8Ilsb\npp8cO2PmZsID0mJxM30iK2cLmgPR3UiW2zP4hi9u0Ro9l4TL4R1NLVtliSt8\n3NDJmyfnzovH22YGoem1KYF3C0oKMAeoN2jCbsKjZeXwJ6/h3BqA+DU2MR48\ng8mjp5t1lGDzT4Bq8SIoS6FbYSqEXCJe5LNmftCEdwsYsqUOH2r2EtpdUxRY\nrDB+RxwWUo3iRMbPgLR3Pw4CK+2dooM77NdCLX2rBx4MjGrOHbipMYRpALtU\neZpg4r43Tn6s6QMpwNQcZfq4f+TT8QjzObAH7qRq8VAzP7ni+/QfD0Ij2AUC\nBGST\r\n=dRuF\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"58688a43f3a2feb3d26e77ac76409966051531ab","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-17T08:55:17.182Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.77","@balena/jellyfish-logger":"^3.0.120","@balena/jellyfish-metrics":"^1.0.385","@balena/jellyfish-environment":"^5.1.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^12.0.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.7","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.20-repo-sync-secrets-default-58688a43f3a2feb3d26e77ac76409966051531ab_1637139467297_0.6303208527630706","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.20-repo-sync-secrets-default-0fb8bac49574163556fa3a725d40d57cd5bb7cd5":{"name":"@balena/jellyfish-core","version":"8.0.20-repo-sync-secrets-default-0fb8bac49574163556fa3a725d40d57cd5bb7cd5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.0.20-repo-sync-secrets-default-0fb8bac49574163556fa3a725d40d57cd5bb7cd5","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"75c715088702f1db520de0d6d86a711ba277c243","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.20-repo-sync-secrets-default-0fb8bac49574163556fa3a725d40d57cd5bb7cd5.tgz","fileCount":206,"integrity":"sha512-VpRXw7wuaVGR/N9U9IiiiqGjfxcX4I7NA0WuYCY8d14t1xEX9MpxrgY2Ful5af577J20g4QNfnlJPF9NvA3x+A==","signatures":[{"sig":"MEQCIHiUU0Iz/hpw7UJu5L5PkFJ5QxVtnrxbCPfkAsuHNtASAiAaTENEXpzVmg80KWqlkNqdrMHY/XinO4wm+nB0TySkfQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":770434,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhlMVaCRA9TVsSAnZWagAAqCgP+we6V8qKXItH0btMom4R\n3P197SM6YKgkME5sbop4H6PbwWDJl253l2oDbfSKQQZnbv8kndchA49is9rF\nIHxQo+65KYff4vZh3gBVj1lPDGN9LUCkcE/TUw92+vZEoDWHM43zIvW6n7ce\nPmumohQqs1nRoVEURvNdJJQQU0/n5F1P9Ez3mz+3Si43meFCXX5cIyq7DERr\n01Ra8teBJLcxEHU3+wiiLMfsG64lTjPhtrwnFeRgh99R4aDWb/WZlrvbJ5yp\nIwqZRBtfMNVa/TWhCo6428E3JSojerLklMM2NONcnOkvJvY/5KIUylpPoHKm\nbk1y5JuAa+j6gLPf7YuBqrSG/3gVJ06CL/IcEIL7mU6Qdz5LRoRC5KUrW3yr\n6rN6ILjTG1hnMmz7yQI9bOEW+JWENLMeQuJK9sWWrBszHQGgwOmxDyhSLqXP\nrrSyTFbLKTEXwqVa2A4OStjUe1im+W1b5m80yF8xoDYekgg5cPsac8qhs6Mq\nATxYMjlpGKLuRlVjJd+a2lQdlDBxeimFDp2IYSksXRAMMlLInJVVSA42BUsk\npzkgpNK6/eayJrax/SXGgF/wctTvWirjTuzpgKmI9qhrdiBSRrUoadjmF7V0\n2jJSb08Z+gpca3W/aweSSgJrypLlGgGGHcI9b2pqb1Z6d1SE/XQrUoNuXq7a\nGxGD\r\n=Cv+i\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0fb8bac49574163556fa3a725d40d57cd5bb7cd5","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-17T09:00:09.174Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.77","@balena/jellyfish-logger":"^3.0.120","@balena/jellyfish-metrics":"^1.0.385","@balena/jellyfish-environment":"^5.1.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^12.0.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.7","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.20-repo-sync-secrets-default-0fb8bac49574163556fa3a725d40d57cd5bb7cd5_1637139801797_0.043288114422948","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.20-renovate-internal-patch-67926a3675cabeb24bc0c64ea01285027351a053":{"name":"@balena/jellyfish-core","version":"8.0.20-renovate-internal-patch-67926a3675cabeb24bc0c64ea01285027351a053","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.0.20-renovate-internal-patch-67926a3675cabeb24bc0c64ea01285027351a053","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"67e048a04e2f75ebd25423251788cccdc073e772","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.20-renovate-internal-patch-67926a3675cabeb24bc0c64ea01285027351a053.tgz","fileCount":206,"integrity":"sha512-dgpQJRlpr0QM01YvhkKeO7G/u8ZTfn2/SkLYXsBapTaCnS1EamE6PaWXPrRy7BcQMr9bzhfOndmfIhIeEFCJWQ==","signatures":[{"sig":"MEUCIQC5JCr/ddUEo/8Zdv0NRRjuU1y6oDub8Kn1DpaMFX6MXgIgcnrU/hxf7+mkPdGyU4ry+eENyVh0sd2eLppWFCCRRSk=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":770478,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhlMdLCRA9TVsSAnZWagAA7toP/icqRMB8Q6W/N4qhW5pM\n0zC/UaWFJPWS5okiU/N3H4t41vCxRwXtrT2meD0LbWh8q7ZE/G2FMsnIkdir\nUrodiqhfZK/79haix+l6+SiP/8FT0sqLAjf9PcKrEqH6NHlC7Gl1TfGuy6nx\n+tXwb7dwK3fu23mLsMuTwDktrCNiUUvcTgldkHOpWHLks0WUqGKW7FyUVLKX\nV/96ATFWQ9NjLbBJW3m8hsxYWds4yRoRQ/iz1MTw98XmaTP1iMzxMS58tu9S\nhnWkOFpSX3KHQYn59VW0TSJygYSmogBAykCuYWEsr5dbML19OAmVgGGGwluu\nJSeqErWwH2l17XFwxls5j3Qcz0mW1euWNuRlGXg/J95vmTSagmSUsdT46PoD\n4LXgssviDEFdG+YaARDupM5lN95bQn9IpelQbJ1FcYWZlx7ejF+zp6BlzL+O\n1AkiKlCB4dF50maGoQmhlh/9eOJTnzZaStwb+vBjb44rb60XaWYf6oWqQaKa\nxKGS/3z0MFZzeSuQ2pdTKcjLxCX9wPG9O47PekIz6GNpFUtGXBl0Mvc7mEc/\nS/qBmV1fkYwdJUt8A0FapK9DMVrE8WGeqv5HzvE2OY260lr/8BMn9jF5knRq\nBx8kG+W0B1Np5LD+uFNuaXZm10ck7gPn0mIAGWG8Rm7mubpF8DTmGc65h0rh\nVhau\r\n=gr5U\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"67926a3675cabeb24bc0c64ea01285027351a053","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-17T09:08:47.720Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.80","@balena/jellyfish-logger":"^3.0.124","@balena/jellyfish-metrics":"^1.0.390","@balena/jellyfish-environment":"^5.1.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^12.0.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.8","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.20-renovate-internal-patch-67926a3675cabeb24bc0c64ea01285027351a053_1637140299237_0.4013271303459851","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.20-renovate-major-json-schema-org-896fe169ac04d4b1e3ec15c267b6b45d531f49dc":{"name":"@balena/jellyfish-core","version":"8.0.20-renovate-major-json-schema-org-896fe169ac04d4b1e3ec15c267b6b45d531f49dc","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.0.20-renovate-major-json-schema-org-896fe169ac04d4b1e3ec15c267b6b45d531f49dc","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"61331259b929c59cddb5dd6efd0205779c72159a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.20-renovate-major-json-schema-org-896fe169ac04d4b1e3ec15c267b6b45d531f49dc.tgz","fileCount":206,"integrity":"sha512-qiQp/fGD9PBwTw4QDIeDnScsHDZAr1EBENCpH0M0bb/lRGQHUyHX7C0t7A5OqoE9W+Oe2Kp4tbX34Rb448RY3A==","signatures":[{"sig":"MEUCIFwZbt3O4SCalOlO9Sd4ehciVasjb/z1zrHdYSEdwNppAiEA6YnR3r1aYDpryBBV6Rb2ify19HHSNb2aDDbL6gP5rTE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":770510,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhlMeOCRA9TVsSAnZWagAAnNcP/0cRjGjQWmCy3w704zk0\n/jdY46Ohn+urnZKaELW3QrpSHKIal9ekehsk0hIx8Ua2W10D+4jRqjRgbx8/\np5g9YP4kub2F1CHibRWK+oDXIjPw998CSAKhB7zENgLpMDDTNmUbLPcFSsXD\n3HDB8+a4QDVaJzOdA3IKBoVo66JF5QRXhiYMbpzzxwc99q33ELEgDTS4qQJG\nfIwyEwTodOCaVk867mQStFOagR7J7KSLcUITNkzPSCzTpUyelQmL6tmjAJXn\nrlQEEG8N5RUDa+svekQUWw9gQNKctfRB4RaXzj0cP7SbFiCc9SWivybzsK4O\nsp6Uf+BnAbgvnShR5mpbs/tiTCMziQQIiOsTjCqkWTIZx5wQTOdvSP1mVYjb\n0ib+YY+dIRQYHD+uSsq+wfADwBfD73MADJtJddiCHoXn3xYy/+fW0+AoDB+O\n0xPNpH7z0sSfVyG6YHeZd8rJE7D57a9Rz5BHzUeZV4GXihCrEFKla2I6P22Q\njkXw9wxugFVst88rIVkP0GbF7fjpCXHtfyOIzfKt+Tyx8O5M+0QcO7hIw03T\nwuTJYTU7XRCcb5+acwohQDicEDt1hHCVr8kdj7bJG0ApI+jmji/eyRPa1sXD\n7u8QhWxKdmMPmAO27RKydiT0iSh0VRZJMt3JXpFISAxqK0URMyAiz841thvy\nNPpY\r\n=6YXw\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"896fe169ac04d4b1e3ec15c267b6b45d531f49dc","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-17T09:10:20.042Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.77","@balena/jellyfish-logger":"^3.0.120","@balena/jellyfish-metrics":"^1.0.385","@balena/jellyfish-environment":"^5.1.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^12.0.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.7","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.20-renovate-major-json-schema-org-896fe169ac04d4b1e3ec15c267b6b45d531f49dc_1637140366445_0.9574270380425698","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.20-renovate-internal-patch-09be4807700b3dc00b3ec35f47a6b661cbb8d654":{"name":"@balena/jellyfish-core","version":"8.0.20-renovate-internal-patch-09be4807700b3dc00b3ec35f47a6b661cbb8d654","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.0.20-renovate-internal-patch-09be4807700b3dc00b3ec35f47a6b661cbb8d654","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"025667bce240ae43f24cac8ff8b31787617213b0","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.20-renovate-internal-patch-09be4807700b3dc00b3ec35f47a6b661cbb8d654.tgz","fileCount":206,"integrity":"sha512-YFAEL7rymESMGlg4X/ICvHp4kGTidBF0p75vngp3cTC2DmouVFiKycWhPiKMGuc+sOzpTgMjylUwc/QV/PYTiQ==","signatures":[{"sig":"MEQCIADSVtvLQCnIOrIviLkPaS4JPiIlRMroMOseuqqaeinSAiA5fquSAvQCRg+tzUGoAg50aPAC8s39uvgQFZvNxwzlMg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":770525,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhl4HGCRA9TVsSAnZWagAAB5kP/1CBMWjkVQUOdQkD/5PC\ne6E6e0yZiat41wHHGT4GBMPrLECYyVIVUVPq/ojufwnnD2DZIjYUNTdushS7\ndgJ9luvmn0+OxexKH31gvH2qx4x8O0n9jWwpRA8TFMkGS8kw/wFz9K/+EeVS\nH9kPPGnYPKdzkHFG4dNRS5XAdCXPHjgJuBOsQ9w8IBrJw7IgjWVVwS5rdsy4\nRq9xMIBnP1FsEGkQQeIDr7hobrHfqUNS3wWrREuISMSp59GUUPPlkgcnA0Ht\naROoMQZUkBOQfRe6heYa9uuTGdU5dJjDIldjrjCIqcr2WMxCmrWX6dlHE38S\nX4ScTGs2GBOTHQ/4LVERR/V0DTDIgtZrEcowbM9/mGiV9pCijJOJR3lxIkAu\n74o5+sgkvN0aF3wvF4lAcGeIVY1A1DrSSsrbqJQ+ucAguLZPdZvnBRj481ex\n4dCQH8dH4RoxBiiFBVZ19VFoFbfaquIP0mvZwefqNrB3tv8YcVxbTI4uNJTa\nqIW1frZ+GJQecORNPX4blS5qJtsVFL53F+pl1VGznL4mkeYMo8mvtIGVQZiO\ntAsFcXQQqH0DP+7yeK7EUap3eEKyH5LHwpWbsDIep+z9U68/x2aDvLDBIMzs\nlIrcomHlGkahs1/4z9fdDnC6BA1w97iVaTk2wQTmqw8ykeKv2sFiA3oVETUj\namlb\r\n=7e9Y\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"09be4807700b3dc00b3ec35f47a6b661cbb8d654","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-19T10:48:34.864Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.80","@balena/jellyfish-logger":"^3.0.125","@balena/jellyfish-metrics":"^1.0.390","@balena/jellyfish-environment":"^5.1.10"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^12.0.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.8","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.20-renovate-internal-patch-09be4807700b3dc00b3ec35f47a6b661cbb8d654_1637319109890_0.14864842351232732","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.20":{"name":"@balena/jellyfish-core","version":"8.0.20","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.0.20","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"43d1e24c16f27250e71f8df3addc1a3677c166c7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.20.tgz","fileCount":206,"integrity":"sha512-TdsD9Y/sw1Ws+V2hJR+/u1yVqymS30uupmu4ZFRbZGoe1vUsUrYqDetl+g9QTzh3l8kbqOii4G+2MYKPh40ODQ==","signatures":[{"sig":"MEUCIALl5wMACWWfJ/UHjUUNvha2PiqdPf1NFydL2m0SL5gRAiEA17jEJ/e9k72g/+HDm8fGmQUoemSSQ/Gh2BZIGDhD4rY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":770460,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhl4NKCRA9TVsSAnZWagAAE6wQAJQij1Mn0lkJ4J1zk/XY\n9lhdtwPzqPvy8rW5BaBqBDT+60s0ESzdma4HdEJ+zM7WsvR/2ZzSM8b4nP+/\nz2mnFldJRnPunkO16YJBj7qzIf7p+8t5IPQphCXUOUMEj/BVXU6IDOsTYJiy\n8lBkYysNX3SUxAVsWI1IqVLgjrj3SyFyT6HHHgS4jOb21aRg+onUZjbfMUcJ\nETG6hh8wi6aFvEl1vWJAD7J4GTfvTb7oi30boC47icLNoogFTcVpRAvcfgVk\neHbYEZWeHxPWTZvThdEysrN6ghSnbh5lFllEnPAB0xgLi9URBBm6dRySnG69\nvlbcrPEEFZThIW7cmSm9z/aTqaViLZI9xgP3W6TvOg5nm7BEYKKOtnRKrQaj\nURQ6YNC172ff7cMmFhLpjYqtyz2WKXfm8+pEWgXC6LUWq5qg3p0Ena+sqNv6\nm/cxz8zCokyePZFMfPJANc4OeU87tyQ1OT1YJD3OdmYBt/Sli/nzZJjDbb4h\nLIO3TK+WlvWhFPD0WdU2fWVNZDOBHKm4Hcp1RikZykl/6sUa1hnqQ5+c/JBz\nVjAH6aNlqA0+TU3kIXC+bbtP5j6r1nBZE2PFZdt+of1e54TgmoUngxNBHTXs\nxwCLp0599Tgoq086kkB4UMcavnxunVUEakPwxk+vOgwsrvK5HdnOxdjV9LwV\ndC1f\r\n=A7uk\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0a525b69edc2d83ffbd1459499161a9b0145a0bb","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-19T10:56:04.459Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.80","@balena/jellyfish-logger":"^3.0.125","@balena/jellyfish-metrics":"^1.0.390","@balena/jellyfish-environment":"^5.1.10"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^12.0.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.8","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.20_1637319497957_0.8202955341731164","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.21-renovate-major-json-schema-org-749a54084582595f501507ac039abeaa884a6e80":{"name":"@balena/jellyfish-core","version":"8.0.21-renovate-major-json-schema-org-749a54084582595f501507ac039abeaa884a6e80","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.0.21-renovate-major-json-schema-org-749a54084582595f501507ac039abeaa884a6e80","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d1c69fee5f01d5f35a8b9a176a511171a551de73","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.21-renovate-major-json-schema-org-749a54084582595f501507ac039abeaa884a6e80.tgz","fileCount":206,"integrity":"sha512-QRwlroN6C/nlsTaH5NI6tbs8ej0ruUaboSPGM/W2hsyouEfkPR+pVbxNr0su9TF+CYMGIvMGD/+w0XfjWK6Atw==","signatures":[{"sig":"MEUCICyg6W1M/8gf5tQ8N0GZMoPGuOK/HZ88zFdPFu4yWOvAAiEA303DF29y9M8vU0RSQCC5ndlwImxvIFvqV4ov2V6dyjw=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":770628,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhl4Q+CRA9TVsSAnZWagAAG2cP/1GkTH5S4rw7XKRtKvsK\n+sAZgjyNqIoyjTwAjJcs4P4jXful7ZfJ1UiQiqTrGuFXmCocWyLCsxuiIQKF\nWEYl39yfDsTPeUQhDSWsZcpJAOhF+YFEFdJw6SvBw3RWznrfe93dR6wk+2QO\nMi3oSJLemznui/HyG+8D2sXKbPik3/RKXvwmTX+HoswCcbg4HTBvRHrl6IhS\nLnG/BeYMwt/oUCn/3uAhKcjubZWF1CAxo7zWFQkjJw03TQ0SzWwkxF/UOg/5\nXSUe2smfLeu1uFm/euJ1PWEUMr21Bbdg/JMWT5PYqCpajSGuiI2LtrbYmwQg\nONjoEBTfa7I98gm3LLP79q8PTYBFdsoRgE4TtI6mp54G3x053FPPUNRUoJGy\n0dj+vhuB/nHLyROLqg91hUVMnZsaFMyd3JSfcpoO0ZMtoC+jScD2yP5RtNQt\nOJiQXQSgO4FSbCVAV58tZmbykBrmScCsj8yvVP2vMcRg1csz1qfbshoFIJ+e\nB9l0RxGEq0NQ7JzxtbZj+esVpdqAM6TY8hgZzEG05jJGyzIgdffwbkyIwV0t\nYDHZyJvKlTv95gC676M7J110Fr+PkSYbtTvuyAjvsU4u/Agjcd5a4rk/c3XW\nQy5s+WzURkTkgP61IYDg85W2ItuyzXo9WzfhC0VyvHtT7xUZyhY08GFwHviq\n8sEI\r\n=PE/Q\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"749a54084582595f501507ac039abeaa884a6e80","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-19T10:59:34.185Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.80","@balena/jellyfish-logger":"^3.0.125","@balena/jellyfish-metrics":"^1.0.390","@balena/jellyfish-environment":"^5.1.10"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^12.0.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.8","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.21-renovate-major-json-schema-org-749a54084582595f501507ac039abeaa884a6e80_1637319742476_0.5787126843588277","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.21-renovate-internal-patch-a8313f3841ad03df73a030c1812aa5883d23f826":{"name":"@balena/jellyfish-core","version":"8.0.21-renovate-internal-patch-a8313f3841ad03df73a030c1812aa5883d23f826","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.0.21-renovate-internal-patch-a8313f3841ad03df73a030c1812aa5883d23f826","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5d98421a7d5cd449ba4cef2c823d1635a5b07cc3","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.21-renovate-internal-patch-a8313f3841ad03df73a030c1812aa5883d23f826.tgz","fileCount":206,"integrity":"sha512-oqPfRKv9whLsIycvgeny3ATDPOwM4fxF9B6MvcW5s/2Lf1r+b2PkjtxJnGsif4QGgVdvXPrKfaOIQ+/IVlTwPg==","signatures":[{"sig":"MEUCIH6pF09dqITTRs4yqNxbvgP5mYQ613GfOX/1mLrKhVz4AiEA5B9uyTDLyeHT68xPFKthdMI6CKtDR0DRG/ZJP0EuwLM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":770630,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhl5zdCRA9TVsSAnZWagAA8OUP/iLS66TCSB+HbgSNa5Or\n2aCVE6djY0+j28YI5y+Y6ASuABRjWxTqxvuGjHrVmjwGb+NjZrZ9v9MT8EDM\npdWSkhAd0lRNWyJpmFFukUKXtFirYIMz1F/qayp38c1YPz+nnPw8cDWHUS6S\nQPSTQLyzx5fRH5cmfZdGE7ohMBbKwLSs9I0pcSW0onrEul5cKWF5NH7GILnY\nyJJTjFcUVr4zp6ik2kA1j/VrDJ/1t6yzuIy2kTKLcj1CeF4V4eFmKzU8eGuZ\nm3yi9Y1tRfnY63sANFUrydzQ4y248Vl4t3u7YpbRNFr8zglrIavI9uMaYlnU\nFOZ+jeUry59QCPqIwTsmR4Wl6SW5R21xUvOGZgw0JPfdp3GplSuSJQBEAaMt\nRxRSLg7eqvsv8+UYd7nySLYntFr6TOyl/qPjsvNstZFUb05e619K9zz68gSN\nTB08y432Zj9IZ8tmjzB7aAT1QeSfCjOUEiK7WdSSCr6IA5dgZoQ9ghpL9wBL\nRG0pfwV7/aDi21Mv5w28+wf7tKtKMT+UyPIP+q6OGiYchJFiS8oSD1mtZq/B\n19ej5WpA4oBD3athInDTDdajyvInHF4srID5S19hmx353c/deHlEr/8RO2n+\n2pfS3pvNUQcWlKR5c2pft/LnfYKXQcsO2AKnS7EAmKd1YoAjjCfArDrLUwQU\n1bSv\r\n=C3OE\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a8313f3841ad03df73a030c1812aa5883d23f826","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-19T12:43:51.189Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.80","@balena/jellyfish-logger":"^3.0.125","@balena/jellyfish-metrics":"^1.0.391","@balena/jellyfish-environment":"^5.1.10"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^12.0.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.8","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.21-renovate-internal-patch-a8313f3841ad03df73a030c1812aa5883d23f826_1637326045281_0.04500638184715022","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.0.21-renovate-major-json-schema-org-c9d313cfd0b8d21bbccdd4fd2d69e5f712a72c81":{"name":"@balena/jellyfish-core","version":"8.0.21-renovate-major-json-schema-org-c9d313cfd0b8d21bbccdd4fd2d69e5f712a72c81","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.0.21-renovate-major-json-schema-org-c9d313cfd0b8d21bbccdd4fd2d69e5f712a72c81","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"072c523bdbbba0918c8f1b97d0ec69432d47aa71","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.0.21-renovate-major-json-schema-org-c9d313cfd0b8d21bbccdd4fd2d69e5f712a72c81.tgz","fileCount":206,"integrity":"sha512-vTUjZwNAUhxiJtipz1jAVKrPbNuBH25kRc+ZQoNrQ1r4+SEqDK8rcD3XvCXazdduQlCWzGXfAZFpdQySWJpbqg==","signatures":[{"sig":"MEUCIAvUgbcEXjwNzTHccxA+hQ0alE5dMrg3iBjrxSm2AhHzAiEAzmMXXA2Gcr5uMAMUnMkZVfxsEpPfH0t4xNKrv2lBLLk=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":770708,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhl58LCRA9TVsSAnZWagAAzcMQAIXDQGFOW2F5PA0vtuqq\nSXA3p04hM6dbeZSgSUX70/YgfOBfAGgjPGgSxwVAAQGiFenZDHlXX0ddlJXz\nSx1lAFqNKHrE2EVsAy89KclY8pWoWvyLE72wJa/ELh/snfGxewz8nQ4mKrtc\nvIluVdkwRiu26TtOfT4nyCj+S0ol+i3VKQPW51u432UYdAByCb725h2Rh1Cp\nrPJHKcdseKBWdkbauCwgecicdqe0qMx9Kfsi6kBMRNjKc3WAcrWo3NnaEX44\n9Tg1ea3a51tULS1mRph7q6Q86ssjwnMxTJaQMPKmFFx1HVB/jCL/na4Hwl7F\nBocZkGNGk4seCH0k2OtJGkUxBWrNjqXIjQaB98KlNF1bhg0NP2MuTqwQx9u4\n7+e5MjRkuweuP/uBMRSWIsorHYf4uWegHU44QwO+8wP5/iuS7AgsnsYj0g3H\nG2+MSNxxjWrFgQ31IzgoO/Gle3v1GKSDm6Vlr/q/nwOdBLDQ/mXOvKhh+4Cz\n5ItPaDnOtRSDdezNnsCZvFNnZ7NwQfBNhXzNweM1dKO/tviZFB/cSBOJpa+C\n8HUxPnvQAz+10KHvoTA+Mifp47RkGNuCHqLdMp5YtBXFoNcyjKXz94yWNIqS\niwhdfzg9YzQyXYRpkRsQEEx6TGA/ZqjqT9Oni7l5Aw6wmjuGTb6ZGn06oFhy\n197Z\r\n=PF9d\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c9d313cfd0b8d21bbccdd4fd2d69e5f712a72c81","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-19T12:53:32.650Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.80","@balena/jellyfish-logger":"^3.0.125","@balena/jellyfish-metrics":"^1.0.391","@balena/jellyfish-environment":"^5.1.10"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^12.0.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.8","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.0.21-renovate-major-json-schema-org-c9d313cfd0b8d21bbccdd4fd2d69e5f712a72c81_1637326602844_0.7724169891036035","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.0-new-permissions-dd7cd2a165ee85c0cd368d1785b41c26018fcda7":{"name":"@balena/jellyfish-core","version":"8.1.0-new-permissions-dd7cd2a165ee85c0cd368d1785b41c26018fcda7","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.0-new-permissions-dd7cd2a165ee85c0cd368d1785b41c26018fcda7","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0b667149390b511a4dd1a6c3b055088c8a81a919","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.0-new-permissions-dd7cd2a165ee85c0cd368d1785b41c26018fcda7.tgz","fileCount":212,"integrity":"sha512-4meyl0kb1lioBKsasS+c7N/cMJCSLfIqmJ4kWeTUCAsFPVAo3jjwmbSAIhozyKtqQUADSPWVXW2A1Me5su8JuA==","signatures":[{"sig":"MEUCIEKwmMAsQHQFa63VnO6NGgPQyZsbPk6mWzxhvh1MEQaNAiEAz/8e6BPjCKQmqhU9D56F+hC7Bf6s9kPeRdizmQ67HMc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":789010,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhl/t0CRA9TVsSAnZWagAA2vsQAI0dQk0R37bo8hj56OS/\nkXEhyVNH/vCAJfDJl74f/Ce4UgtUIXm3S+ulvHfGxcS/DxKIxr//qhl/sTJB\ncoWewlf2Ba6uo/7eDlg8K8Y9k1MOYaEgD11CiHDs67bEsOfsutGEHJFzFBRZ\na2lkdtdkdhwIgt2aWQK1KyhBlonaN0QkgxuF2gGS1KUcsMQ8yjKABn3r8gKr\nseNKB322kA9hqjoQjDAtdBPHcej6mThdA9aqd4V9/iXO5Y+ZjYVvFApMRjbH\n87yVJRsT2bTEvWinTc9RqPp7IEflYrFbhMsRYEgv0czWeaXhQjQ1/ThArqK7\n97I00Di8n0/JFC44il6UQQpdqDYsXz3u/PFN2oJRE8F4RUz8hFDKpWB2HO83\n5VXQfckXKI5mzVXqqpctaF8a+iRnyZ+Nj5AbT/CGVTN8b0GxpeW/78e94kWS\n8DkbuatXg+HLUa8g4byjFRv5KnQogMfMEgadPjVhP9wpGrB+Ec7sOLQHngTo\nzG0fg9S5zrqUa2zpOFAKVR9MCHccWDEJzZeTjG1ROLxUmpHRUUS8KLW/ISTL\nL0KjBfcFogYGdhkkYnD1iNgZwBBgKFNlN8XYYBSQwBu9+V7Q/+C+vDeLaxN7\nVTEucDrx98701C6Kmgm+GtciA9h+AnsQccaufHoPt6GDDwUbdQNeHJQblkSP\nH0sZ\r\n=NQu1\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"dd7cd2a165ee85c0cd368d1785b41c26018fcda7","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-19T19:27:41.425Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.80","@balena/jellyfish-logger":"^3.0.125","@balena/jellyfish-metrics":"^1.0.391","@balena/jellyfish-environment":"^5.1.10"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^12.0.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.8","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.0-new-permissions-dd7cd2a165ee85c0cd368d1785b41c26018fcda7_1637350260522_0.3241522372914083","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.0":{"name":"@balena/jellyfish-core","version":"8.1.0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"578b12c608ba5692cdb4d28a7cd4aceb75f4b2e4","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.0.tgz","fileCount":212,"integrity":"sha512-EbhygX1KYJHD09Nrc4mlBo8XI7SoaFZBbcTXwE8swedyV2tonCT3kOGWcn/ESRrLls8LbZ8dHT/Xn0R79IIiRQ==","signatures":[{"sig":"MEUCIQCb+AhYGiz8zLok7FUZJ7RNj0C13De6TJdbP0rfVFQujQIgSLMaCy7NBhVXC1FJQ2VyGsmXW0f+PxccmWlA8zV3w5E=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":788953,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhl/z6CRA9TVsSAnZWagAAtzEP/iFZoZX+Ly1FaPcmJ/hU\nQt63oCcNJOgKJ0dPt8itR+CMpVZafvda0qNrFxqA+HjF+0VDeRNJ0OcJzwdx\nGFSQvID99nBmamWm9WEiLdcbVW+FYWLsNh/mIOrnhKhqNjUUxad+iXgGvsEq\nwPX6R6Xp3dYPpZwijfaMnztxdHvbLdSIC0UeBEKsj04bFCGaqB/dQFSKf92k\nhcRD1b/bRgKaoTxXsqHGWZQ8hWknGbOeGvh0x5gKGwQr8Q0dh1WVbICzthtF\nY8R4DTCWwiWeThiLf5vVuWLdrCODuE2uoWE+4kjgH9wml1DNTnt8Ln99inx2\nkSZ3ZOkAZnf0vR6NGFYyevn272KNm9Zo+3fF8+J27f5rNkFWMkfeQb3BW0lU\n0Dj5ZOlLG6r8S4bSMDeFNvgEvSLknbhkTUZO/dOy7Q/0NkvzMCbR8EGr6ll6\nhmqv0AMRI7X6qwVakQe93UbCNrZF5sY755mWt9uSjXrEnMHiFLiibyEQGDTQ\nepjtgtnkO/wzXL4yovEmChi54itUke/8Zlt9Ze+nklN1ETsahf2vWsqO2Ta6\nwxzZltloY/i0o1Hjh5HlVa3jOjNKTPdFhBwvCr4xLJLa1xUxbV3GzocvpKKd\nXjxQ/34szU8iv9HKqsAkv2iB4h0u3rSeItiTQ9WA1zeuD6r41fHzpJ1g/mvz\n/tYR\r\n=R6EC\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d2c6a253885fff369654a4316c36c007383cc531","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-19T19:35:29.284Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.80","@balena/jellyfish-logger":"^3.0.125","@balena/jellyfish-metrics":"^1.0.391","@balena/jellyfish-environment":"^5.1.10"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^12.0.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.8","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.0_1637350650215_0.5530976933367158","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.1-renovate-major-json-schema-org-aacc521943aa9054e11e3391c985dc52651b68c7":{"name":"@balena/jellyfish-core","version":"8.1.1-renovate-major-json-schema-org-aacc521943aa9054e11e3391c985dc52651b68c7","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.1-renovate-major-json-schema-org-aacc521943aa9054e11e3391c985dc52651b68c7","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3f66c4d3604440e7de179586fde1f0fa88188280","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.1-renovate-major-json-schema-org-aacc521943aa9054e11e3391c985dc52651b68c7.tgz","fileCount":212,"integrity":"sha512-sQgi0H6ke45V1UVQqQKGfP7On5ewA+B2wAemSvbn1voyPH/kyw0UAHOkS/2Xy9DM2TW1hMXp+idYbUE+xqHMKw==","signatures":[{"sig":"MEQCIHG69n/xk+JIEEFMMP/TeIPG9HmewS8NC5dzwRSA+/5DAiA7e0tRLtvYlLwHgz/964oZU5r/CIeWNunCZ3Y9c6Lkrg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":789120},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"aacc521943aa9054e11e3391c985dc52651b68c7","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-19T19:38:52.829Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.80","@balena/jellyfish-logger":"^3.0.125","@balena/jellyfish-metrics":"^1.0.391","@balena/jellyfish-environment":"^5.1.10"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^12.0.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^0.1.8","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.1-renovate-major-json-schema-org-aacc521943aa9054e11e3391c985dc52651b68c7_1637350962913_0.5102190232687294","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.1-renovate-balena-jellycheck-1-x-64c5d436015e0aa271f1dc2c965c2b74a858af2e":{"name":"@balena/jellyfish-core","version":"8.1.1-renovate-balena-jellycheck-1-x-64c5d436015e0aa271f1dc2c965c2b74a858af2e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.1-renovate-balena-jellycheck-1-x-64c5d436015e0aa271f1dc2c965c2b74a858af2e","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c7e832fb8e7d1b48092f252de20334fcac8682e5","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.1-renovate-balena-jellycheck-1-x-64c5d436015e0aa271f1dc2c965c2b74a858af2e.tgz","fileCount":212,"integrity":"sha512-seHW1SFJH1q0Uf2icIThV0YMDbJ4Q95rnaLEr7+3inUmI9TlcXYWiH4ngfu/s3prHNoKw/yUmDfuhbm1P+F3mw==","signatures":[{"sig":"MEUCIQDcgENLQRrnNMu2/PbBEWY9kCsaZqRcR4LA9Q6A+13j/wIgEgJstMhCANMCzCgvsvVF8/cyJBYFGclTXZEof488utU=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":789116,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhmFP2CRA9TVsSAnZWagAAKHcQAJg+MgocUtc9AlUN81iT\n6b/2ExUrhnXPpdxc5jNWbJhBryjgh/M5A6Czma1s+vuQBuK9SX6nrMEua/j/\n3RYzdGkx3cawyCJxNES2T4i0JkVHWsrCvpwnp5lf66wiuZ8P5WZm7R5p1IN3\nj0UA93Eb8qzBWFzrcBQWIZiQkqitoG6elqAcIbF/LG0PrEcBOhOwcHdtRI1t\nuz6dKJxBVoziKj+16A+1L48sHTsGTP828Yy29CPCQlBT88XA7wqSeOnOoXkE\n2ddOuVOq+pH8UG/tdz59sVQi2UyAtLoIGHuwK+5FEcRcE2r3HBx8e5+RypoV\n9ltoaRjifcgWWHDW55A19+07ToWENUp7qlOJBlX1nGo507D4ah7xFsb5g+MC\npXxiMrF+bzv8K8XeRTeEWVqV6lInIADQAWUetN5goBLz1i1NgoLrv3L1d9Kk\n2uGs4SBa3T/RBviNgheka6aDZ6qwSYaAsblAcaBufiXH0YUvuFZgzoSivvxD\nSqMd3n+K7A3LOKXdxISelpAvX/9u0x0NuEVzY8j274wdkD1RyJuke6P+xRjc\nBSlXLlxyhkSBQhCiINSWhx+K0ACADphJun8Dwm3qniAgXjuOn9ZN1WmkTx/F\no4tsUXuHHhDtoeq7sAE1IiBQOiD6HNvBYobY9/8LEOz9rXaAtR1jxx0jYRm8\nFALh\r\n=BYWR\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"64c5d436015e0aa271f1dc2c965c2b74a858af2e","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-20T01:44:53.003Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.80","@balena/jellyfish-logger":"^3.0.125","@balena/jellyfish-metrics":"^1.0.391","@balena/jellyfish-environment":"^5.1.10"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^12.0.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^1.0.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.1-renovate-balena-jellycheck-1-x-64c5d436015e0aa271f1dc2c965c2b74a858af2e_1637372918407_0.7675026130765246","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.1-renovate-internal-patch-e364830db62a62520bb5a097f6dc16904cce563d":{"name":"@balena/jellyfish-core","version":"8.1.1-renovate-internal-patch-e364830db62a62520bb5a097f6dc16904cce563d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.1-renovate-internal-patch-e364830db62a62520bb5a097f6dc16904cce563d","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"660833bb3a945f05511ca962c88f23ce4b266e88","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.1-renovate-internal-patch-e364830db62a62520bb5a097f6dc16904cce563d.tgz","fileCount":212,"integrity":"sha512-Dx+RViqA8Yhq6oOsflPM7sXGCb6BS/8fFgL/J4pY7VKpGV5tGbOa74+nw+OsQC933DMMsBnAMwa4vOFK6T1avA==","signatures":[{"sig":"MEUCIDYumSzCIdGIAOmU+bHbmDtCjf/hGIBfJJdlVxMX+cAFAiEA5S8hby2N/p0rxyqhb2fs4yj5hbgzFJNfjpLXAssAHWw=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":789155,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhmL/yCRA9TVsSAnZWagAAEJYP/juUZL+RekoHZbkNrLf+\nWtlhhbUnDDRuSbnZrWcIYj3dZugZW8xABedl0mUkdxa50BP7dWxHgvUjFbD5\nDZBKASU9hzebGSVw4jg03DVZeUsYO8lHIPY8Swo9ku06iwaQh8I6Xa3lkugR\nIKHz7Kvi0KyhIMNVYJMXltjoDKJ9tihpOkFQLSBMGRJESZXR99zCKee7AYC0\nfmHnC0udB79RFKh8Bq44X3SlokVVLF7Kuggn19pXF5fH2M6SP/EBTB3vAu4J\nV/gbdKgHqczjjUFWBuSH15CXpvQ67LUUgCMnPjBUuNfNJ6V0R9NgIzeaPNJB\nFZIPRcIO/NVIJYUmHxLKQpteEK/uL1P4xl0H50RIcSSB8k8OXEb0WzZNldxA\n6a3IPdDhM/8pth0T6Bw2bA06HdZ+jpCSMpj/9AsFvoLtvJRNqk7FUti2QN52\nnL/MI6KPDfCabDxNOUH0Mke/vwDiYRaQBTHyz0PvKoLHZQsd7vfWS4j2vlMj\n/rVisqRT4sLKMoiJDbNdoznsyKSY4kHVHDECmHGzvECEPlbv0e+hTX1+IUpA\nqFgyadByjkIVu3Fucim5tPpx3O7LE6sbEbLnVb7y3jS8VSNr1IbVjLmZXXr4\ntRiE2mMUbiHpl+VZUIVpLRACJeSs/eD4qZvtKGRBt23APZa7QawIALVleIb9\n6bBu\r\n=UHRN\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e364830db62a62520bb5a097f6dc16904cce563d","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-20T09:25:53.637Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.82","@balena/jellyfish-logger":"^3.0.125","@balena/jellyfish-metrics":"^1.0.392","@balena/jellyfish-environment":"^5.1.12"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^12.0.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^1.0.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.1-renovate-internal-patch-e364830db62a62520bb5a097f6dc16904cce563d_1637400562624_0.7852455993169314","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.1-renovate-internal-patch-77942482ee47fd9d5a99c56c5823f20fcd3b508e":{"name":"@balena/jellyfish-core","version":"8.1.1-renovate-internal-patch-77942482ee47fd9d5a99c56c5823f20fcd3b508e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.1-renovate-internal-patch-77942482ee47fd9d5a99c56c5823f20fcd3b508e","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2d77fedaf5030ae2500976c62629a01accf2200a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.1-renovate-internal-patch-77942482ee47fd9d5a99c56c5823f20fcd3b508e.tgz","fileCount":212,"integrity":"sha512-G6wznwpsTacOVe6PT3/Y8LWXWyxDm814NGgqRLaPIYzaNuE2GfYC7gUvUG2JrYdTKU7FxSTKAobSFSGHiK8Z4g==","signatures":[{"sig":"MEYCIQCPSKl/UZKmJVDxLsFu4y3E3oNO568sdwFhkLASoRtIOwIhAP29+3rd5vNabUM1kn3PfRhyddXqnjyaAPUdeA3Mz5bz","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":789155,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhmOG7CRA9TVsSAnZWagAAuvYQAJ/LBXec1CDwPuqzlRK6\nysRJ8SJFgi/P8Ra2ok5lqUig5uYvIN7td6ed3OsrDLDKmOfQPJptGQ1EsL+R\nX61eC7/JEtGVg/mEXFk53CH98YndsgNRI1unOH0CnElNw1rBudPNjfgeDP3w\n2Drw+aMpTGcWvaQaLrgO+jYXhLeYZlq7LRRvCPxcp25Ro7k8suunpba9dXRZ\nrVv/PCY6mkBrZEJx3t5pCC/uVrS2eLz+KFsZLMA3xn/BXBr8N9B/Su/ynGba\ngduhdcxQqLenRVCRc2N/cffAQIKhDCQI/Q56JGS8ldLc6C7H04g7UwcCl+Yr\ngePMf6vaoaJ6e/keUOIoGutj9kS9w5hluKrt5zL2VIOuGUzgZI+JWGSNzVxa\nJQJBr/0ABoLCuDAOKnKgU9M61qNnZZx50rmSRUFtkJ/f8RpMBhwrKbImWaKy\ngdaKRBoPWI9x2P8bKeKdvdxz7zCFmFR48SxGDjmZ6mgAsDJNcw5fnzONEE5J\nokd1zUhL9BW524Xe8H2SdqvicUEuEbt0e5UU+4KPGAgOviAKQo7zTFWfLcVN\nqPEgY619agSsLiwxq7UckPtw0eyWh5W/WUW8s+VYgv57lB1ybLEzXSP4MsTg\nefRAcp+iFvmSPDNSNQk32jMiaA6pCY84x01YiZ9pGNajyPDj+Zze+3+qUakC\nz5fO\r\n=Pwy1\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"77942482ee47fd9d5a99c56c5823f20fcd3b508e","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-20T11:50:01.743Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.82","@balena/jellyfish-logger":"^3.0.127","@balena/jellyfish-metrics":"^1.0.392","@balena/jellyfish-environment":"^5.1.12"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^12.0.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^1.0.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.1-renovate-internal-patch-77942482ee47fd9d5a99c56c5823f20fcd3b508e_1637409210907_0.8397628613677319","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.1-renovate-internal-patch-8b4f5e15b49aa364a846883b7e0b9f2ce726f99f":{"name":"@balena/jellyfish-core","version":"8.1.1-renovate-internal-patch-8b4f5e15b49aa364a846883b7e0b9f2ce726f99f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.1-renovate-internal-patch-8b4f5e15b49aa364a846883b7e0b9f2ce726f99f","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"72ce4617220a08992c6483b2a2bffb3d7400eb1d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.1-renovate-internal-patch-8b4f5e15b49aa364a846883b7e0b9f2ce726f99f.tgz","fileCount":212,"integrity":"sha512-qxWyPPH2S+6EBC1inA9lubliusMrC+mp9IpoCuxS2Hvq6Vcwcz+TD45QupCeUzVrmk7efXqmvgxI0HTyc6G4yQ==","signatures":[{"sig":"MEYCIQCpMMlvkxjKuOi0rN7L5JIjUMNaBfJiHBuvjemHVuNMpAIhANJlHVdWcGeZydibtICeDQLsocGjRuSMZSVAjBMozTeN","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":789201,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhmPXTCRA9TVsSAnZWagAA4iUQAKDSPmVtUYlxd1LoyNp/\nCzAm9/GQaqnMfGbWkky8+hjEPknbJAABtEJUGAwPSXiIWSRKvWjrNOYM4XWX\ng+r/G/FDwwVVaxxWMkx7PukexKNpBRxTS0z5YOz6O98Iqhin7+29G7X3Z2AM\nwYgjSoFhr7S+HsbBc5MAL+C2I5cNvdchwUzqYR124ymc3mwn0k3jD0eM937g\nETxFOa2e7XG1kvC74HE8OY/v/aFE87KqbHqJ+mS+lrNbTq/DVp9+r0u4F4Q8\nTIseOguwBSasfyhKTD/BCoiUBd077dt8Vt0wlvx5Gvjv9HhlH/qWRzRZugnn\ndXdTxJdCd/PeCb0PCY6mXPID9UW9r4D+84tA2PJ9iTBM2T1Co8pf/ib7ENL6\n9Xh/DpMumXirFa4a5tDpnzCWFRMOvGxHmCEQRoW5q/ii8Z9xUEcCyTgwglVM\nXQLr65QPRpavg82bKh0KUrIYm4dcdHgoLdEWy6p+Y19e+AHc/dKh+p2PM4BS\nFKeP/OlVxgO3FF6RkerjKm+nbdBb5NWE33IgNpjWckNLNJbPLu2jLFLmKLxD\nxDDXV2xsBnTKI7OfiBEE8hR6rHgZZovkhwNO0orpPYd3LHAFEUSQy35+nM6+\npQeUqTMq29zvVdPv2wHqX06rtW/lZkUd9Y4Friy2k2bf9578r/Wtq2t4WSDG\nZERt\r\n=otTn\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8b4f5e15b49aa364a846883b7e0b9f2ce726f99f","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-20T13:16:28.023Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.82","@balena/jellyfish-logger":"^3.0.128","@balena/jellyfish-metrics":"^1.0.393","@balena/jellyfish-environment":"^5.1.12"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^12.0.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^1.0.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.1-renovate-internal-patch-8b4f5e15b49aa364a846883b7e0b9f2ce726f99f_1637414355458_0.7420983787454047","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.1-renovate-internal-patch-2efe143c704e175a80176c372064561b38ff8538":{"name":"@balena/jellyfish-core","version":"8.1.1-renovate-internal-patch-2efe143c704e175a80176c372064561b38ff8538","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.1-renovate-internal-patch-2efe143c704e175a80176c372064561b38ff8538","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6de49d1c1d6c3a495f606f7d8c1dfcdd7dc0a5fd","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.1-renovate-internal-patch-2efe143c704e175a80176c372064561b38ff8538.tgz","fileCount":212,"integrity":"sha512-XUVt4ntR3MqSRiU4PXFYYtbzwydOESHqlNFhqIG9I7FQsUazUZ3JflGnBmw/mJO6sbWav/Ew2v3olq9HmU3CNA==","signatures":[{"sig":"MEQCIBQqo0qwjUYNqoZNwEnAi7XlHDlHPlmY3UmeCT0HRKOmAiBpZrkGbKmuetWTqoRO5TUO0sZfXs98T7BTlg2G+E+I8A==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":789201,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhmPciCRA9TVsSAnZWagAApwwQAI80hodOzyyrZQDoPhBj\n9W+rE01GU8JP9N1Ka9/AJaTaLkmWBdy30CKAcT5d4mWzhnxSWp+UKBCRDb9u\nTTEm+VUoqRd7QNYlOIryHZgwIF2wS6czUouThnSf8DSvy5sstfHhBpXsFcfJ\nx50MGNH5nVQKeSEN6IWOxpW3lrCuIIW1UxBn1iGmnE0RE6jSsBR+fF/tMO8v\nR8oQIo5ef70LIDDUDDtMQKywmteu4O70bOIFpn4mnF6IzdJ6/DMCPMafdpt3\nG6ix9Hpt97AdZ1M5tuelVFkGVEkns8C/WXlpz4CuTH74TLDfP8uYDg7ztE4l\nosHJKLAhXSUgdWbijhZ4OV4DXEyyJhQpMMsq97h0KRoDYq0ullkKBvkU4B1/\ntJ10mSfE9HWSzCTMJTJ7X2rRFXYqRoxyR4D2cnmPC4q3Pbq3K3zQxrF2O9gp\nqYmqKbaEzttAOyihW7ur3DSu8I423vGwG58/n23W0lKMO0Z9MU2jxxVJYXUa\nxYCLXKyOlWZHO2DYarmzEf3E7wgP14B3cB5/yByX+EbQ8fkTA0DRAqewMiqK\nexnV1H9pqPInoTTRbB+cCHXrZtg/Te/o6ZYlt4sJpji/yNMTvVfy8XA6PjzV\nCdnAGLMjJcd0eB/gEWOLeXMfuvfzePgzLjdOi1dSxf/h5wPNhgsO0+jJc+7b\ncDxE\r\n=luRy\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2efe143c704e175a80176c372064561b38ff8538","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-20T13:21:28.071Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.82","@balena/jellyfish-logger":"^3.0.129","@balena/jellyfish-metrics":"^1.0.393","@balena/jellyfish-environment":"^5.1.12"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^12.0.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^1.0.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.1-renovate-internal-patch-2efe143c704e175a80176c372064561b38ff8538_1637414689789_0.6502300000094003","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.1":{"name":"@balena/jellyfish-core","version":"8.1.1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"055634f148c2b033e5dd87e09070827577f28fb4","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.1.tgz","fileCount":212,"integrity":"sha512-oB9nuJtgzTtMHV1sx9LJViN/TvzYz7/doxadm5rDRdQWNNoTCeBWy4OuomQsduthXEPiPlMN2jvHYWLQZQXwsg==","signatures":[{"sig":"MEUCIQDcM+orcq42mqEdiW5i7ash8Y/CS8FBHOxzn6X7Z+9b2QIgVp3LS2kaq65Vu1KgikL5accTBp+Yjz/mbI2FduFIGwU=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":789136,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhmPjFCRA9TVsSAnZWagAAYL0QAInlCJ3cY7i9puEFWpkw\nSGwl7kbqE3szEu0OWArVinWzN/M0fBWfmF6m4XUmiHC0GmLs9zd6NjwYoHnG\ni9lhZxe92I8VYXJrAaW+pSQbdP8XxX/gjKZ4QSezLq8Btr8DaU7K26Dv/8UY\nhetkt2+KQVQn/bzNH/I5ZlfYzELC4Fo61o6v2ksk6635xWK2mT14JNQ8+KKJ\nheBuNuKEJTeR8BZ4IvoKvu3OuG9j5THWSm6xzSXvIrS0zupmAasn6oJxTQtj\ngMRPyAdXB6Ut/konz7rPK7AZFSB3cd7+3yRAdTCldDyUqmAmixK2P4eDmEXl\nWdKQzaS/QfjATu9OhP9vcPlwnRG4kSUbdtPRimEO5dXMDjgMrzggXP6ZfzmY\nCxTt9l66cyWOtaJw6+P1Pla2nsfiQEzxPvCFCuD9e9BnE87q8+W1KX/MvStg\n6uP2J6Mdqi8NcADwwKbcCicRw0ybQBxaaza1S+vXUUC9fyqxlbXWR3nqc/0b\nQlK9WV4GrtW1pvKk+hYNTONA2zRzDy/Du1tAUi6g7NfvWOkQMniSetx2SkId\nNU70qYIgOlZoaD5SvI6eJi/dDghD1H1YzuNYbv/cXgmzADbiYUNzOziMEbOP\n83zZkXMcbr2kpgQZfqtzzYTjV+BFTEA6iJbScx7lUYRcwrla3FU4wgGHNn/h\n5RXa\r\n=F0Oi\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"cf7e54efca84b57d45b51cc6a4a9cb531f51459a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-20T13:28:14.633Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.82","@balena/jellyfish-logger":"^3.0.129","@balena/jellyfish-metrics":"^1.0.393","@balena/jellyfish-environment":"^5.1.12"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^12.0.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^1.0.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.1_1637415109466_0.8171789223374624","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.2-renovate-major-json-schema-org-4e337698aad59f1ef65be8237de3c73e6f40edc6":{"name":"@balena/jellyfish-core","version":"8.1.2-renovate-major-json-schema-org-4e337698aad59f1ef65be8237de3c73e6f40edc6","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.2-renovate-major-json-schema-org-4e337698aad59f1ef65be8237de3c73e6f40edc6","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"76269ffb96e3fc89ad0e15fad68707f22a85f929","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.2-renovate-major-json-schema-org-4e337698aad59f1ef65be8237de3c73e6f40edc6.tgz","fileCount":212,"integrity":"sha512-1ojDq5O98GGms1R6HeaeTD4joHBUqPOobziboxl3cOGPi8yHbmTjV7Qy8/JBFjGLrJLFr8M3hz1NW53rdxylpg==","signatures":[{"sig":"MEQCIBF/kcivu8DDCvYk3MirPFPKQ9XOfjXQ0LtNOQAb9YH7AiBObDIt0KO+ULOM9Vh3rUhETxl5C+YTVkJqCNMIVP99sw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":789303,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhmPmHCRA9TVsSAnZWagAA7ngQAISbw2egHBUI5cva8V44\nqrUtifCAwXwFxP5yZG7iFk8hPndVCLQS1DvqyEcFbxb7BBJN/7fsmB/47t/q\n20MYGn5pRlJURcTZAcAuG8gQAJbIe3twwo0lFt/IBX1ntwAEKKp8WqLnaVq2\nHieYRkTk3uir8xkUAomMcbKLqR5/gPzlZn6/wUFC9AOmNiHNpnnzbz5gVsXO\nMWp6XOCiEuJoL100FGA9FsB4I0Yq6acNwArunCsgCGFMybPWpBgjEC7veNhx\nBKleocB0ECe4C9le4kFAc7IfdCRAvD/1ZFrpqTQWE3xPGeCFlGF9Y8bftkdj\ngBKjHBSPq7zSCh1mNx2g8ZpYRFAlCbIbOIPDbJgDw53Uczco+NVkE23InJ3c\nqISxA+gyK15ToCU9Ee7DxPKARZTnsOvEMjgYynf9B44z1gVcAfvZsTr40ksk\nHNg8+L1gtKwmZxpbmI4skxPDadI9ebb/J/kXTCwpDhkkNJKmemHkiyUd9PRB\nCkT+0MXD1lu4O77L/Bya/OxwqDl8fwLKIpg6fl/okz14E1pgsE7e+kfwId/R\n9pzYYXPsBcjmnsySqAEbLhm2ZYl0Wcvv8acBx9Dasc+aHgFpFzeysn4Th63a\nH0YeZCkMMmIKBT6tCWV7PjDzK427jd3HU4q9a5vqyJewzIfkkIDzTRR9Qzlr\n7aa+\r\n=sj6I\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4e337698aad59f1ef65be8237de3c73e6f40edc6","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-20T13:30:43.555Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.82","@balena/jellyfish-logger":"^3.0.129","@balena/jellyfish-metrics":"^1.0.393","@balena/jellyfish-environment":"^5.1.12"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^12.0.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^1.0.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.2-renovate-major-json-schema-org-4e337698aad59f1ef65be8237de3c73e6f40edc6_1637415302837_0.7714107959485887","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.2-renovate-internal-patch-edb974a71daed990b3b1928be26a493af1f073f1":{"name":"@balena/jellyfish-core","version":"8.1.2-renovate-internal-patch-edb974a71daed990b3b1928be26a493af1f073f1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.2-renovate-internal-patch-edb974a71daed990b3b1928be26a493af1f073f1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4da315cc53d90b4e31a4e006b1352f44a19ca5c7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.2-renovate-internal-patch-edb974a71daed990b3b1928be26a493af1f073f1.tgz","fileCount":212,"integrity":"sha512-5TZ2elfJOg/xxeqx1zDfR5ce11O75GNJzCr019cL3Yl2TlWnM7HPWtn0IpTwY7PynzaqTdajcl3dnkQGYCT3LA==","signatures":[{"sig":"MEUCIAEGTMdPa1oEJCC6QTnsHNuDOFdEQ2ocWobdBgCztDVsAiEA9CmszDdasIXSFTpgODewnVFaFbODawvwxCDDh8ssswQ=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":789304,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhmRcbCRA9TVsSAnZWagAAguEP/i4U2ny6f75nZlAnupgG\nx0gzqRayWICltt/GKY04mfO6dLnMu2ztj8yv9adc/psS0Ilb02GQabkAXDau\nSKmhS9IpZH/VjEd+I8PDgz3IOn5se60M4NfgfsZMyJ9CIndaOMB6BntL77zG\neyp6XGSp/7gfSsmjadkugRHVW1u7BOA7wWnGbboocDQu7D9tHxBhEUhfQIVh\n5r5IIuARTVZFLL3fwRPCmtkl7QuMOBGJS96GVAR3BXVs3phnUHMgcCvRhgwu\ne2uqNPA1AO0xwUhTodkb05eciNKsKgcyUmuevEQ1AHJXmAp4roS22SkqYQmC\nqRdpo28g8tr4Lr4EGGogrvKsuMtFri7prVazZfgoMwBsr324a8MhoGrJplCQ\nyLiKj2d3NuOYxvZr5HI1/zqjQz8M9FSKMkStDncC4TzT9numT2DSDr7Jp0YR\nhGzpSM3gB89McolPZSGICGNm+45PTXsF8M74FSkByeNSQ9pFB78c0/5n1EhO\nTyoyWcJCYUIQQBJKieG/lnwiAmFdKh1ppqUAC4fQ/3EHOf0vbAdLYrUaBLHT\nDEbftDdv/az2BG1bTIhRsmB7y4BauMDzBVZHuMpbm1+8i7qGHeiNOt4fo90V\n5MlhYI77xStlDemeQZG8GuZ/dTAtXspJvbV59iP2PadDjaOCPWY0oLSe3zBb\nx9vs\r\n=VWI9\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"edb974a71daed990b3b1928be26a493af1f073f1","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-20T15:38:33.954Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.82","@balena/jellyfish-logger":"^3.0.130","@balena/jellyfish-metrics":"^1.0.393","@balena/jellyfish-environment":"^5.1.12"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^12.0.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^1.0.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.2-renovate-internal-patch-edb974a71daed990b3b1928be26a493af1f073f1_1637422875504_0.3503074771742516","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.2":{"name":"@balena/jellyfish-core","version":"8.1.2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.2","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b0880e066685b027c805bdf5c394bcd895a03ca0","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.2.tgz","fileCount":212,"integrity":"sha512-7jvR8jzQTD7X0ymiDikYq9411fde2JtQQtlyPOQ5ZhF8Z758dTqbCD/EO0f2ussSXOECzAPCaK5GQxsc7vHyWA==","signatures":[{"sig":"MEQCID9Fth3gkQqths8u28TIROXO9tzuBUPywFboy4LfD2IwAiBFOZcf0I9wRX3+IZM1sfwNoPydMnTBDqfBPfGlnpmGdQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":789239,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhmRijCRA9TVsSAnZWagAALGoP/1TjWnVH0Srt9TqtxCfc\nyI2V8y5+i4+ryjGEnFQUpHdGZpawRhTJEw+aQ1m2/1bX27hsmv0n98gZoDw2\nsPCCRM6PSx101I6zUyq44n0an5UXLou7fsEyrnsmK/BbmVeF2x57czEjQV8c\naiuKOXed7yGNQtvsVQ8RpY0Qo99SrQmu6omlsT4O9XgBzTHpurSRa81eJGwl\nNfnvcVKZe6Dy1PDjf/XBpIT8Bep5IAERJ3EH/VCxKaUDJ+Co1h2vSLJ9sH27\n5luemO2F2T0Ucca7rgaqQA9gnyjX+qYYZ4CjDkIA4nHc2hq/8g8ZFuAJYe7T\nm9WjoaLJMDlB8Q+fJoKDMSORc8ze0fd1COisygNM3fFZ4xpi4qSJJuxCr7Xu\n+2xJhvCn9kJRHIa3UugHtwDW3flpxUX7+ZkrK7zaSCVGNXj4jjD7hXeyHOiv\ntWypY2dIqQZ6QawgTva+I04HS3ePXa2LvVT2mkn/RMmKoY8xjT+lpV6kFZj9\naLRLLofv/HrbBIci5w3M900GtGMKEPvC60vI1QMCBgsj75yJ03r5cz6b9Rbv\n2Fv9ZAzmmbzEByqLKZ6YefMaOABA64IEsPbPB5F34XM5naxM/CmUGZJyQLTa\n9yU0uqcFrBQTAMmRliBR0Ab929+4yatQPeAE6ULdVxDpjGbJjniCsMTr9/Px\nRAB0\r\n=R4YB\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"770dd4336235e21c00c844f980ceccd6f17a8af6","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-20T15:45:54.013Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.82","@balena/jellyfish-logger":"^3.0.130","@balena/jellyfish-metrics":"^1.0.393","@balena/jellyfish-environment":"^5.1.12"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^12.0.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^1.0.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.2_1637423267370_0.7851411314325809","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.3-renovate-major-json-schema-org-c2b4d9de8718a715b26fe8764c2a35bae30bce45":{"name":"@balena/jellyfish-core","version":"8.1.3-renovate-major-json-schema-org-c2b4d9de8718a715b26fe8764c2a35bae30bce45","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.3-renovate-major-json-schema-org-c2b4d9de8718a715b26fe8764c2a35bae30bce45","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"fb80f21d97ba88c161229b23b3fb27c2e7cdef91","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.3-renovate-major-json-schema-org-c2b4d9de8718a715b26fe8764c2a35bae30bce45.tgz","fileCount":212,"integrity":"sha512-QXqI1aigMozBQbxzMHin0LGWEc68gD75eXCaX5Qij6QIRfLt+oczB+zY4A43sb6MUed3CRfdh3WDEzk8B0UFJA==","signatures":[{"sig":"MEQCIH7vvaylmxBCoGFmLIcP/XugDLlqw0jEf5wvxFIADnw+AiA+EeFb5LzYL/eZ6EAidtX1cUIEgUKwRgWt2nbZOkNORg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":789406,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhmRl1CRA9TVsSAnZWagAAIG4P/0I816HvPl7etP+0BtWs\nqSzd0BPp7Bp6j2/fsj2jILoWAjoek67Y28P7QxBvN67X89zPDq3FoLb1d7EB\n6aJeVR1Pl615XQJNT7DH7aZQRmP4Kpudok4MwnQgvczldkUOn82h/zDg+Tg5\nhHPrnmBHXC8+HzqEjp/pEXMPabs3BAr4W60hZ4n02V39Xwp2HSwQaTE4O5y1\nzluLcjLtgKJt9m69obrdyABVRAuOR5uV/5LN7AozTWmLLnj4WH5qeKhWTxOT\n2o0BqUQhvhOEOxpLpglGa6EWxOHKYhs4BEx/jszgmkGHn2/uffZfbAwxC3Iv\nTucetAT7JJHO4+1i3/EDwK9zUhm3XTmWsLBNmF0QSz+x5zR0Lt95rKf9NeRc\n4f3Q3lq7xB68aT1puDIy5Dor/IY1g2XdXIrWn71OvY7jzqq1LI3EfcO3OiSi\nw68exeRhpyMlDgLLnFTyGjApnH8hDgd51PyuDkKeZaJKDJTCsOLsggNTWE8w\nrIVtbnHTHVjbXuum5lbQ3YJX+BADyqYWqTXUpMokZKyNCK+0fCWYUKKpL+8a\n5tRkwL+VZ3swydApLxr4jBwPrUYUTODfnuIi65hU/vC89aLI7jbXttJq2LCk\nPbYflmWNdLmKo89eOcVAV/7sr33OpzSzfYLZi1eg8MpQTXjzIGmQGeaEmCDD\nsm2C\r\n=AsPn\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c2b4d9de8718a715b26fe8764c2a35bae30bce45","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-20T15:48:14.292Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.82","@balena/jellyfish-logger":"^3.0.130","@balena/jellyfish-metrics":"^1.0.393","@balena/jellyfish-environment":"^5.1.12"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^12.0.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^1.0.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.3-renovate-major-json-schema-org-c2b4d9de8718a715b26fe8764c2a35bae30bce45_1637423477553_0.7848148378264026","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.3-joshbwlng-remove-license-headers-0b9b2dd1c62356a087bc0bfecbcc6471f0c6e94d":{"name":"@balena/jellyfish-core","version":"8.1.3-joshbwlng-remove-license-headers-0b9b2dd1c62356a087bc0bfecbcc6471f0c6e94d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.3-joshbwlng-remove-license-headers-0b9b2dd1c62356a087bc0bfecbcc6471f0c6e94d","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1f1197f692122e704acbb4114e83b5f1b9488f59","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.3-joshbwlng-remove-license-headers-0b9b2dd1c62356a087bc0bfecbcc6471f0c6e94d.tgz","fileCount":212,"integrity":"sha512-mQhj1ThSKql35R249JoV4SVwRrxHnVXECXSjW4HOg1dOu7TWoDb/HRY9tTDZePbQ0FyHF7jsoX9ulfWeFOfyTQ==","signatures":[{"sig":"MEUCIQDxu2mj7EKbfhuKDc/aEMVkFmGXmSshWpdSGdOxBjHNKwIgTgqLz99e85CL8kqSHrCO86SGIUBSyw2ntDZpruo9OJo=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":777026},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0b9b2dd1c62356a087bc0bfecbcc6471f0c6e94d","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-21T03:34:49.479Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.82","@balena/jellyfish-logger":"^3.0.130","@balena/jellyfish-metrics":"^1.0.393","@balena/jellyfish-environment":"^5.1.12"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^12.0.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^1.0.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.3-joshbwlng-remove-license-headers-0b9b2dd1c62356a087bc0bfecbcc6471f0c6e94d_1637465861721_0.06442605832810355","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.3":{"name":"@balena/jellyfish-core","version":"8.1.3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.3","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d42f52015ced3dbc36f820e7361a56aabb24f113","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.3.tgz","fileCount":212,"integrity":"sha512-94Z5T5R2MRV/+juOU4CbCf06QJTWkm1flc0ozsbJjLpyLe2eBapKHfWRb/iFlFTVk1cBJ3Y2osZ9eHDGali8Yg==","signatures":[{"sig":"MEUCIB5cvCLXPwVT4mfFlub2NSHSN4F7wkVZJsoxYvDXacJAAiEAyt6kuJlPmlvUG8oeHc87wo19cO0IvrHONVoO82AFtF4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":776952,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhmfwFCRA9TVsSAnZWagAAgmgP/2HCGqXz6P9Fb4uJ8SSM\na+gHN1M9vEhKD8tKWm8RQvCZBTn0R9ylehX/BPDCb9CrWWkrqXdZyJexaJ5f\niZH/EpbMyvQgBvP2sPMJzwzf9qdLZO24sx+Toatnf7Yd46YPXRScLHmguKjM\n+XcAvILvc4cIa8umdNvGPO2HQfp1c5wks0MmR030r089I11SqyXBBvo09MZ3\nDho6ZN8MbOcX2/JaxSxpe4FMeONOhxBDOK7/TLeLReFiGLPIuoz7BJdiIS71\nwDJ7tQNPtUNYYrnAwOoHRkLHPI2HEDtD+ea/CLmEpwW6yPk0uQsTPAevw2As\nG4VlDYzuWv/hSqI8xyS3xvMZ4SRcud4qgfYmtlY8ezzQznPnX3/toakuPB9t\nBBVLMutoYBJEVdthDyxaKowaUkDDLv5sqfgLtjw7WsiJP6Czo5az1JMI95Dy\netIV+cDEXujUtXyc9WHW3YsTbsGZeC/9/1PKESdNkZ4UVFTvg1sQajKRCy++\ne0WHSZfr4Ljs+EaEWsPo8+52YmDG0SQkBz39FTQmpYs1qCCrVKGzq0yYi7pX\n+PlFsl14V5f/tErIyd1OvrLHNaXf5K4iF3BOXx7tgJbrOzyH3duXqvqP4TUr\nHtyrRiK/P4Lor00cG7CV41Xd/WtXaoW2p2U0LSDwfoY1rjin7nEMwTwHl+KV\n/rZg\r\n=lZfp\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"eef1bf5c975cd305d8baa388646cf598b3e8cf34","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-21T07:55:44.666Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.82","@balena/jellyfish-logger":"^3.0.130","@balena/jellyfish-metrics":"^1.0.393","@balena/jellyfish-environment":"^5.1.12"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^12.0.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^1.0.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.3_1637481477778_0.8053599869887553","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.4-renovate-internal-patch-209524cfded2afc36969234f536c36b698a8b6f0":{"name":"@balena/jellyfish-core","version":"8.1.4-renovate-internal-patch-209524cfded2afc36969234f536c36b698a8b6f0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.4-renovate-internal-patch-209524cfded2afc36969234f536c36b698a8b6f0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"40326280cecc31c9a79b0649fd13840311ad6d99","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.4-renovate-internal-patch-209524cfded2afc36969234f536c36b698a8b6f0.tgz","fileCount":212,"integrity":"sha512-5ncUQT+iRZ+i/zfHJHtf0UqXVnZIFW15Wpj4dXWKTON6PFjfJy1luBeCP1WEtydb09VbcYIuioVeFd2GdEex4g==","signatures":[{"sig":"MEUCIDjG/DNLoEukQTbC60DeqIoYUENjMPRftjUU/3Y5sMkYAiEAzPhXIzp8HNgFwrhscig0iXjejZ7EQPDsEPbzpY5ooFQ=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":777121,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhmf0WCRA9TVsSAnZWagAA24wP/1/i+bu6hJzIr6T+wKBJ\n71+QmMkHhEm36FiBtjGxh2rKBqLo4aH/Te/wsWS/WIayaKy+h8pB8y6S0+x9\nL5JKk8Ersfbi56L803Lxd0mJAGEfJoIeeuG6ezSq3rzLdNrfQtOdLI3A4hHN\nuhdpzvenW37geXcl/L8fThV4i2ITyfmD3XQLO/Rs0cTNpTvcvGcZUTnXyN8c\npkQgIF3OrLEtfohaYoE+2mhp8uMO9oDwx8XZwZN/TAPBn3gBUQafXKej5yUM\nhCBGnuclI+6d5V0DDThliKvze3z3esOkmZFNinbhGm57M6OUdxImuLIc2noA\n4lKCth1BOhCKx9txKgiRstJC0zDNdcGPc5rbRq1NceAeiS+jbLlsYpFI7TQx\nNIOhdhMjTXfV35dVsT2bhtm8UZFObcsHAMphnYZ2DPa6otSzwj7Lk3ibDgzb\nX2gnt63B5paM3FmE3ikCtGGj4od335br7+s4O7SvqCOkOG6tptq+R/OpJhLh\nRUT1bgt1ABq4t+I+m9MrOkwu17XbhG/e4aJKqiulGI1cyf2wgQVNUWvFgvdl\nDb4q9/NggQCvAi+FjSbKHPUMMJpWV1F8yuDLE4oIJDkpIwh6j4lgO3T7VTtx\nQxO3S3ItI7x7nCwJx7kRkh6nFxf8Mb5++3/VEVV730DpU5QLjygZIdLan5V+\ni0ln\r\n=tTQH\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"209524cfded2afc36969234f536c36b698a8b6f0","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-21T07:59:19.995Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.82","@balena/jellyfish-logger":"^3.0.130","@balena/jellyfish-metrics":"^1.0.396","@balena/jellyfish-environment":"^5.1.12"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^12.0.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^1.0.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.4-renovate-internal-patch-209524cfded2afc36969234f536c36b698a8b6f0_1637481750236_0.46280838123679224","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.4-renovate-internal-patch-aea1c86dd2d27ac2b17e5a4e1577a6a467115473":{"name":"@balena/jellyfish-core","version":"8.1.4-renovate-internal-patch-aea1c86dd2d27ac2b17e5a4e1577a6a467115473","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.4-renovate-internal-patch-aea1c86dd2d27ac2b17e5a4e1577a6a467115473","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b0f6aca30264182c7ca2c7480d17297b05e6edb2","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.4-renovate-internal-patch-aea1c86dd2d27ac2b17e5a4e1577a6a467115473.tgz","fileCount":212,"integrity":"sha512-3x1vN9vf3+KGg0loUg5BcaqOxjC3bE1pLPzo+1+UEWbeBB1/lwW9cwhk+Ul5/ttazg4yZjZu1mB0Y9YdIsvXmw==","signatures":[{"sig":"MEUCIQDmHBzxzbGTFgN4s6Cmzp8xdWjN/mgqIiNGWfjLcOLKugIgRYCXMlDcK+0NMaF8eUX+aJRt9MLK5W9nwA+avQTnOA0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":777087,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhmly5CRA9TVsSAnZWagAAa7oQAIm/fVozrOs6aGWLYmRq\nTXuE5ak/+zPTShLGZDgRLNifGAJirMmZndLbmSGW41Npg7vGJ7eHtNF/gnL7\nuDcF7G63JbfETxBFq/CQypSbcIHtDyrrVMwzm5uDz/aG86zahvRJAfbrs6BK\nULhpmrun2ynTCXwN/UOmx/U1ag0Rn924n1FORund8EuuAyhr1TGU+0Xikqir\nQfE0dqjJPFKPqK1LatfM+30ueIW28BlrSn1e5TaltVRMDwoO6SKcF2UVkKEx\nZsDG3ddniYJb4CodccHgJMkI/RMKiClENLlGcFb/FQqA//ujDhA1Fp6VexdP\nk0+dsNaMW5z2Fc6bGcxjIER6HrLHtFEIja5zRtcWep1hlB8ph99UnkzhSosG\n1OniCzp9ko7Msirun7y5JDPKKRtev20JgL0ASksoKpMIwAFniSghwjRHwrU5\nZBoYMqAVZd1oL/f9QHJzzqqPBrR7EmtHyD+buko3mTBvCe8Lk8sKN25PXuo5\n6GnMViOlNoXC/cz6YEfVuBUeIdxbLN2d4eORgHevt727HNIOymgIa6WkT87x\nKRf5r//lnbO5R3m8AhBAYoyf/5winK1vQnLQLQu+60W6rfOpXuXn2FxJfozg\nr1pQSKAV6TctPtu6Z7pRteQGoo+2gwWu3gVMeDL45K8PuvvvFsh4WRicb5DR\nmKOr\r\n=Nhiw\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"aea1c86dd2d27ac2b17e5a4e1577a6a467115473","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-21T14:47:11.719Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.83","@balena/jellyfish-logger":"^3.0.130","@balena/jellyfish-metrics":"^1.0.396","@balena/jellyfish-environment":"^5.1.12"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^12.0.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^1.0.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.4-renovate-internal-patch-aea1c86dd2d27ac2b17e5a4e1577a6a467115473_1637506233285_0.17029368353918906","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.4":{"name":"@balena/jellyfish-core","version":"8.1.4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.4","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"09a197b59b2f2ea75b037af4930177ebf0266361","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.4.tgz","fileCount":212,"integrity":"sha512-OWNsZsuDDhXjVdniDW3qCSWAirQgDipy1rsOFvWdtDYkUozLs+9MxCes8KwYIfm09VokCL0L2JWBRXWGOmnOUQ==","signatures":[{"sig":"MEYCIQC0E5MJaxi373ted/7q7UVFtijY2gz/dcuUy/MAvutTqAIhAOJ6NU7IhJI493mcZI20eHYT493Rl4f8duzwQOHo7M57","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":777022,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhml48CRA9TVsSAnZWagAANJwP/A5yG2OS9ByfpgYPgDz9\n/Sr3sIdL3Gpph1YAh5AUQtrRah2TcNSj0eqPIIYWH6codXInQSQWYU/f7KNc\n6Mnsltc87cVEXyZJRc0LmQihAkwvBsAzUQaveC6sSh7psw98fJdZfhv9c3Mk\nOdtqhoQbiCri9hT3U0ppc8QlgGwhwQaXNZTlETnO9F7MdoQKc5+gZoLe/ejn\nnUi/CzPVkSSwyhDePRJJjdMXE/WL14gC2jtDyLKeGhLUOQm4INRdTkat4Mpq\nkBjZ+B+r9ze/0eLDb23lYXNETMYxQCiit6hiSDfos9JDi0E5YtngpjyayOCm\njQT9Y2UFa0MSyEPy9SAdjgBgXxi6XlM6lub+ur+wzNXwOgac6AhJwVCvR/XE\nzrChpH6RSmNqQxVNwHYpBFNs3ltqzez3hIrivt0AdpYgCEEwGn2SolXUmMom\nqO5s0y/twQWOE/wSLv9S89bHURFp+UBI3IxmzNlAJCL7Yd9JCVrB6eipZXhZ\nsnnw3DUVEK+V66JZQtlBRZlCN0cRaV4fH6iioZVbQajteaGHyJBCVPXghX9g\nHBKCjHgks6yq2abL66kAQvpLgjUbqdtNs3ZzCdEUDiipoi/DmrxlLrm1fqWz\nBViCWJDNW9hXfwB9rVeZqcvkXd5zKPomDMTTnhyLiDrsAyQZYnhnFr5KWAaR\nazr7\r\n=DLz8\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c60ef3af7cca9251f2d0ccde102eb528968b8f0b","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-21T14:55:07.716Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.83","@balena/jellyfish-logger":"^3.0.130","@balena/jellyfish-metrics":"^1.0.396","@balena/jellyfish-environment":"^5.1.12"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^12.0.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^1.0.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.4_1637506619858_0.1590885381640299","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.5-renovate-major-json-schema-org-8f17e7c346cdf91e7a7111195afdd1779b0861cf":{"name":"@balena/jellyfish-core","version":"8.1.5-renovate-major-json-schema-org-8f17e7c346cdf91e7a7111195afdd1779b0861cf","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.5-renovate-major-json-schema-org-8f17e7c346cdf91e7a7111195afdd1779b0861cf","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b701d6477ef03efd16d80191884a5b4b23988113","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.5-renovate-major-json-schema-org-8f17e7c346cdf91e7a7111195afdd1779b0861cf.tgz","fileCount":212,"integrity":"sha512-h8aM2ebpjX5+C0mNchDrBVgg73AzTP883lyogufIAyv5U3LineT6E4Wa0o5mfuzZClKcbXtF/6bvSnPeekTgbg==","signatures":[{"sig":"MEUCIQDSI+3vbK74xK5StCZ0AgmYHB57WcmIFPGMa7fzVsO9nwIgcW+b7sB6XNhJVuuASDSDqoV+9KdHL/4xfKVUw8B/sP8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":777189,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhml9OCRA9TVsSAnZWagAAmpMP/1mjLzPkOhgV+2U+91Fk\nF9Qp3QMY3rqRCBu8TsvElr6pLDxg5mrjO9o+ExRz0FUDxkffHXReONiOJTgU\nSUlnTetMD4TyF8ohyk8dwEoe/OQyZucG7xrQoz1eDrsGfAyo2JwR9sTR8qny\nCP6DFvnBTNMr+xhqigZX4kWCgU2QvOrwiaSvbiKKthrlr2DMjKvqMBmrLj+y\n9kPaWS2zH1WgypFrMiEjTgoQmgyRhq5NJnfxYaIt/wtbmHb4Wt6jV5VQTdAW\n0y6VtOYrMS1Y0Y8kuyi/2iDTqsWDPyd6shKgJn+1PzwgQM3APi3Pvq0TOmAN\nZuvzikKzSmKIC7md6TXtdQC/jmRaCmAOz9+IuIm6hDAHxRaXmm75yQdVWcSY\npfir9+8ehLutJXRM3QRgP/ZKXD4AlbGUKVKfQ1MOxET/aiqz0RXCloyMbFkc\nPXSFvAOXLTvwfqi9Uy8ht+ZIt1ra6gp1i/cJLJIJsf5f677F0bN2lDWfXici\n7rVYYRQ8/xAl5Bi6DNezMEtays7RY5dpCQyme3kPi+zCoL9myW/tgZuLZqax\nyeGcFPFrvY+UsrcnHilXkrEV8lOZ1/cjtIowniu1bfdJBvP9z+u/D6S5II3N\nxDK5GaAIvHYwYE1KvoeeOC6oKbmc0+pbFzMNIrXgMglPnWHzRY+xkOlhjG84\ngJjJ\r\n=oc6A\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8f17e7c346cdf91e7a7111195afdd1779b0861cf","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-21T14:58:37.116Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.83","@balena/jellyfish-logger":"^3.0.130","@balena/jellyfish-metrics":"^1.0.396","@balena/jellyfish-environment":"^5.1.12"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^12.0.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^1.0.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.5-renovate-major-json-schema-org-8f17e7c346cdf91e7a7111195afdd1779b0861cf_1637506893860_0.780281135296834","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.5-renovate-external-non-major-9358a7dad24a4ed48b1bb215c8f6692a89fa357c":{"name":"@balena/jellyfish-core","version":"8.1.5-renovate-external-non-major-9358a7dad24a4ed48b1bb215c8f6692a89fa357c","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.5-renovate-external-non-major-9358a7dad24a4ed48b1bb215c8f6692a89fa357c","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"543231f64e2b7712b143e2a0cdd8a00a126bd778","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.5-renovate-external-non-major-9358a7dad24a4ed48b1bb215c8f6692a89fa357c.tgz","fileCount":212,"integrity":"sha512-JuCVn1ZVUsGgV60vtY6RVE6ahChJKOJE11w9FSpi0RFLf9lHp7AXkC+GW4WUfmkd0UCRAu6oD0DkfQsXVGuzgQ==","signatures":[{"sig":"MEUCIQDrGxzIsPh220eFOSdQ4ID43h0TkD+MdZ428RJvoF1w3AIgeOIcUqgmK5az4fzzkR0U/icz/K/ddvs1ErqeOX8OUnM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":777165,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhmoagCRA9TVsSAnZWagAAL1MP/2aLE1jP0SytJTjTH8qC\nqXbRTZD4og102FCBEwvW/nRvQEW5x7x5tgKHvbc2RfGSsn1G+sDVzbW10aze\nU2zDVbjo0MBgLyP8RVc3c/6qUTl7AQmj0BpWQWa3Wf5+ojgW3BxDevwvpanW\ng5gvBMlO9TlXwhS0D0LKVId+IJlnUVQDtJmJCgI3ongpS9OVyhiAff5QG8gw\nGC3lPDh7CQd/VcTXNvZsNM0+o/h+mq4my5Nlexgs89B5MU+YhxQV7HP5nvlD\nJSYvWxEGTB1rHlgBzCKI2WHSOjgQPIc45ZNG0XXkLhO/G6ktr3FZgLC3dLXd\nAOXWeYDiAsylBnVhDy+yS4rIwl83aUCU2Tl+e4tUIJKcJ3vfObY2uSwwKp/n\nxqzSApNNfPwNixIRowhSQETwQcLhfkFJO9cOmTwO84Bt6zsZRVPR4NLMYtca\nYROMo7r91pzu4AXlyW7iZ2OxhoGihYFEKKHi4SB5Z3F1gkUZ2pnkjMip7ur+\nW2qpZF7pSVKCJFh5Zugua9h6P+cNUfV5Q/NVYF8VjPqFTWgfZRp1nm2sm20u\nAo1pZH1o4IvDLHfhOv0yMCheo3UjBr+hirzaA1SktFZTWOPplzlHyGnBnfdW\nMzlw40mPRmklMGUWQAUSKWgoXsV6jRz1IT0DsKghOylJIQPIqjw3oTAl/C7Q\nWifj\r\n=Q5QG\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"9358a7dad24a4ed48b1bb215c8f6692a89fa357c","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-21T17:46:15.654Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.83","@balena/jellyfish-logger":"^3.0.130","@balena/jellyfish-metrics":"^1.0.396","@balena/jellyfish-environment":"^5.1.12"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.5.2","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.1","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^1.0.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.5-renovate-external-non-major-9358a7dad24a4ed48b1bb215c8f6692a89fa357c_1637516959808_0.48030193961802414","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.5-renovate-internal-patch-cc35a5df61557c6c690d8a2226bf7a99574f0a3e":{"name":"@balena/jellyfish-core","version":"8.1.5-renovate-internal-patch-cc35a5df61557c6c690d8a2226bf7a99574f0a3e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.5-renovate-internal-patch-cc35a5df61557c6c690d8a2226bf7a99574f0a3e","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"360a82192cb93b4df70a911e6c0151b31f6729d4","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.5-renovate-internal-patch-cc35a5df61557c6c690d8a2226bf7a99574f0a3e.tgz","fileCount":212,"integrity":"sha512-7XxwVXofhol+LrB5DW0MiwqleRwHUR0lG32fPOU6q6q7bGYy0yGhRSW8BbYZcu12BkRKB0m/9VCNRr7r7p3DnQ==","signatures":[{"sig":"MEUCIQDlS3qwwqaH85z5vfUHhds4aeQ708DA87rQU+AxNibH3wIgMe12vzXNFas9+3A500x6NWkN2BfRbK03UagUWDtReRs=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":777189,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhmtmkCRA9TVsSAnZWagAAD1YP/0TXB39f4p2UJTxwFNSE\nyPyu9aVY1BYhA7NJiKVTc7BxliwYVIay9I2UfoYLNyc/eysd5ItSAxorXl8Q\nXPoTHpmETc86qszIBduY/B0vb+6Q3OqLZgj03ian4qYZiOr6sCHe52m10eP3\njsYdQ6ogVE1K4AoeEu/zHtyVy1AXznhiOyBFJiZsJwG4sgOUcGESdhwEbJda\nQO7DwIlRq8593BefWFkwXjsKikXfZWFA035C2FRDr7aOVg3iiIxjuGr4mU0N\nvGRgjB/7e8nf2p0qZAYRHhh24N/9Sywx5HOD56LudbMMpFDSrirfFT7w3a65\ndD39+tmJsff2zC9iuU3lBO+YV9TNKJen21INbPBRSrNQGbdYhZuOKNIkNvEb\nOCwBGiSlxZGp1l2CZXGdK86zPVtc+QrFv0HtOgN0ajPNEDAzONwlqnH1QfuE\nZ2/f5dnIR5cYJJI5jPRaKXo0RdMQRAU2hpGDSKaYlsrch6VLHDkvaLr3TSHn\nILZgJ6KeYUkTn7Diub96RYUnjBtIUqfsEkaD1622nkRtkwy7Uf/gQ7ovJby1\n6yVkuqx8Ad90/CbFJSBbqJjXrHA0N8ZbtuFmbYKBjbMvlBOwWqbayj1+JyKT\nnXMELdDR8JBAh883AIWmWGOm1T+QdEN0ykUu/ovd5sZtKbF+eNOmq1mY6Qx+\njexr\r\n=AXCS\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"cc35a5df61557c6c690d8a2226bf7a99574f0a3e","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-21T23:40:13.981Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.84","@balena/jellyfish-logger":"^3.0.130","@balena/jellyfish-metrics":"^1.0.396","@balena/jellyfish-environment":"^5.1.12"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^12.0.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^1.0.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.5-renovate-internal-patch-cc35a5df61557c6c690d8a2226bf7a99574f0a3e_1637538212329_0.7968154241116916","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.5":{"name":"@balena/jellyfish-core","version":"8.1.5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.5","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"39452d39ae6c84c174eb88285fe9e0364eee6a7e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.5.tgz","fileCount":212,"integrity":"sha512-Xm11NRTr/4sWlNFmvtBsSLOnPgINCYwybRs+4TtbB16OMXWCuJdxzTawpMviZvGHGkv+8g3OgMeAyrg29dUUiA==","signatures":[{"sig":"MEQCID2KZ91J3vwlsQ2Bx/F5TuiE7SglA3QmFSO3uvK355pnAiAO6N7gbndL29a51DUSMixd6zWcG8N6hgB1QLHr5E75ow==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":777124,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhmts7CRA9TVsSAnZWagAAwmIP/ibTfoJcRyG4VIKmOiF/\nvNoIMSAKWmLUgi8NInKW9gMhceudt1F6QfAoRkWrRM8qY80k54DZj9oorZ/r\nZNV2AKG+RQMBwJbXrixxEaAuNWDiKhxbtulb/Ajp4kMEEEYdsjNNcs2BqUQR\nAJu+1dqP3ZErJ5gQ8mvIK2pHDgL3PrUYjUaAkGAc7kOD2IxB+xblm1YdqDHo\nkmrOeL/l4FUeno6PmhBPVnYH64oK3cAsQsahstCnbWkCiz6ojedvROpE1wpA\nmUuJgD0sbjX1XpnB2ScOufwmLUFlxCkoDjw7DF61rGTidixY+kk5V3i2U+Iv\nlTfR7BJKIck470AYeb1nQgw2E1lUw1HM0J+dzb6fIVrWFgvl1e0bSXHPvapF\n6FfkFPDjvb8ccYxQBkUVmyHBzPeNohJLtXQ42tpJzaqMc44PaRao0z/JuMPp\npuX8U53F0lzv6uDgYJ7yvT7h7Fly5fvuNaERYghoRYqL15qrtJ+H1/eicTeF\nNhdmP+IXascABAfybO9w3LmpbkAXRl0HOwexio4P99NmrTNSP0N838iOk0rb\nApfm0LISen7tpkMtp3ARWqkUydiuefDf3C3Dlqh0GdWeJHDBFRobSYOh1xFe\nd1GD5at9PD2sUu4K3uXzC+PxbSdnhYUFhXxZxsBa57SKNZ0zXZsQo34gOf4i\n3OCL\r\n=Iea+\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d2c20c882737ff9d865baf15098540062eb97212","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-21T23:48:03.545Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.84","@balena/jellyfish-logger":"^3.0.130","@balena/jellyfish-metrics":"^1.0.396","@balena/jellyfish-environment":"^5.1.12"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^12.0.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^1.0.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.5_1637538618806_0.3767929513134951","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.6-renovate-external-non-major-7ee516bdd61611b765f3775ecb6418a04cf0a3b8":{"name":"@balena/jellyfish-core","version":"8.1.6-renovate-external-non-major-7ee516bdd61611b765f3775ecb6418a04cf0a3b8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.6-renovate-external-non-major-7ee516bdd61611b765f3775ecb6418a04cf0a3b8","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ceb8b31b2938269cc87fd050c4a9c15c97e73524","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.6-renovate-external-non-major-7ee516bdd61611b765f3775ecb6418a04cf0a3b8.tgz","fileCount":212,"integrity":"sha512-WQfkeXwCBcGcIDrHvkRWjiO03SIfFhOBYkjwvcgMSnGvzO3D6hZSLw5eex+Ttfc5/P75yVgAD0vGMZo06FGBOg==","signatures":[{"sig":"MEQCIFXQfZUWX2BRRIXtD++uA/kXDkwfczIuZYWobIZEESt1AiBumkY6vy/Zl2BPl7NV5spIzHSKZ5iecLV8uKkHoG8saA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":777267,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhmtuuCRA9TVsSAnZWagAAVGwP+QFrED0nOFNJ9ervk3bD\nPSSgR9M2rQBebZ2WSK5h0HqJez3jnizVAu9w67Cheyom4NpLOFhBmuajDWc8\ntfCW/rQwN51Ye5SAzZYBH0X7kaYCeiWqtCXAHteg9/xg2fAUnHT/sZcbd/iN\nKDbzI+huYu0Lz7b4ozvfKg/Q9M63OihOTD4AUOC1vA4k2NrS9GgDa2uvHAtE\n5RSx8TGwtW6AbIk3lwFjheiB6jSsCqZ/ZlfGbg1QKUKjK9hbaUyiPR37ivan\nR46kGYIuFR3tPZqVm26k7jtBUU6ORt4PuR1tpsLML419tZZhNtVXwZpoYccF\nUdecB8oNk087sOAw4k90KTUfWIMQb4+a7b8y6TdhU+9pF+mOkNRnAIOvyPTi\n2wdejiXApxG7UFlCYnBuytEh+i0XFtG5NnEA/UTEC7a9KbcSjhEZDu3WEW9F\nGtUnqcIbCXBhS/zGaJqyIQRZ5mHh3YyqdeQEynZ3G/p0YZWAsQqdzJJHwXp1\n31Wm9uPEPCQX48jMOPP1A0IH2y1/Y5TD/MxM92NZj8bjCuypyAqD8Fe2cyMS\nb3gqtGBYjNt5XN21ICcO0vbU8KUuWyp35ycifnWkcJAWbbJevLJpExC5UTHV\nXT6OFzvlqHUK/tZ7WAOmFlwPZ56JI8NJZr6HZrJVidGMg7OU3HHM4xvr5+nS\nhHtX\r\n=aHIj\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7ee516bdd61611b765f3775ecb6418a04cf0a3b8","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-21T23:49:48.600Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.84","@balena/jellyfish-logger":"^3.0.130","@balena/jellyfish-metrics":"^1.0.396","@balena/jellyfish-environment":"^5.1.12"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.5.2","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.1","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^1.0.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.6-renovate-external-non-major-7ee516bdd61611b765f3775ecb6418a04cf0a3b8_1637538734556_0.06840829403477322","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.6-renovate-major-json-schema-org-f721bc806f83135343020012e00e3ff3f9463a07":{"name":"@balena/jellyfish-core","version":"8.1.6-renovate-major-json-schema-org-f721bc806f83135343020012e00e3ff3f9463a07","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.6-renovate-major-json-schema-org-f721bc806f83135343020012e00e3ff3f9463a07","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"06f57f2dd2568beda5945d5389a6462523e9f80e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.6-renovate-major-json-schema-org-f721bc806f83135343020012e00e3ff3f9463a07.tgz","fileCount":212,"integrity":"sha512-WDbzwUZuxMYdv0noHEsrZIj8frqEQNCIRikWGOPaAeIa5AyJhasHxgNGYJKPKYcrr+yF6m6LHj1H+VFoKwTmNA==","signatures":[{"sig":"MEQCIAh7uE/auxpOKTBnMX9BrFaMgJemEGosEPnNjp3HXwdDAiBNLFkYu6CWkFmZIlOiujAyBGFByhxrCMofK4VJ39fujg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":777291,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhmtvoCRA9TVsSAnZWagAAhmsP/0s3xgsJgWr21vFI5Rxk\nGCLLHrrl/RrbQdHGMf6bEv7gBWh7ngnkLonuTKzdUNLcbrccPllEu40KXX6/\ntNzBmKDNQSPXl70pxFXdoEBD0HyimeAx6cLOsx1jkgw94c7ADYJSX820pyOA\nIFek7Uco33B56TcYmpaKzGCdHjesf6k9gxZOFVTTQY+RK4meujCfTRe0tH/N\nTogLo+33fO4qa2780D7ix8w8U8PsA+3URlZIN8RP3Ov/KEyGHiLSO/3JZJ7Y\n+qIDBYa6tbD8EPI7vjJdcs68ippNdOr+HUSvi+KTMpKBvM5dYbCG9rDhL1WG\nyDelUSHkSQ2rZKSJXeGUSif34liTP7A5G2zNNpRNUtomkH6b9P2hwybkdt45\niSliUJMd/M0lXkRjGFOQjYga7XahikvUUW1puVIatHNDwBXsytDKj3/4wndg\nyl+U20JrreCJxYa9bJjL5rU3S2NyFsKhGS7NwwA477jMcrxXDyu1oC8YsieX\ngDUBk0nao3xfK2w1vCM3iR6b1oYzurlPklE/Ps4SMuf7vLu5agj8YzhOpntT\nbH8rskzcCzIn1tRT1uQXFiFAqqLZd2pDxIrH6qXvgE1twjIDeuMxihfjS3vM\nJ0+6fno5HEK1+JjhP11mkB4/jISh66sAYvJLO0NRO+JYT+LvEwHsfdIqdIiF\npudp\r\n=nVGB\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f721bc806f83135343020012e00e3ff3f9463a07","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-21T23:50:18.830Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.84","@balena/jellyfish-logger":"^3.0.130","@balena/jellyfish-metrics":"^1.0.396","@balena/jellyfish-environment":"^5.1.12"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^12.0.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^1.0.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.6-renovate-major-json-schema-org-f721bc806f83135343020012e00e3ff3f9463a07_1637538791882_0.06891686623075621","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.6-renovate-internal-patch-8c5fbac47218a815192bfdb3c7bac685ad1047b5":{"name":"@balena/jellyfish-core","version":"8.1.6-renovate-internal-patch-8c5fbac47218a815192bfdb3c7bac685ad1047b5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.6-renovate-internal-patch-8c5fbac47218a815192bfdb3c7bac685ad1047b5","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"63aea3c99ee94c9203bd28ddf2e8d4b1efebb574","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.6-renovate-internal-patch-8c5fbac47218a815192bfdb3c7bac685ad1047b5.tgz","fileCount":212,"integrity":"sha512-qfh3Bq9UBA0MJshu/HFXcTV+3WNhWeEGBBJtdaNSmpmKflLTbh5C2SjKB6K15FUKn3b2s78TQBeioSW8Pvb11Q==","signatures":[{"sig":"MEUCIGBiqCLAtX8Sq5GdsxxuRxJ7yUbd5orarSRX0WapFfHmAiEAoMigc8odft/Svn6ke+MFqsghOT3MNtRjKmrfxsjljv8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":777259,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhm0KzCRA9TVsSAnZWagAAoJAP/ieeMMRYXEi3fvVgikir\nn71WEMd4u1OREFfMmVBAhLjg4bkwVKC6FrDjIPqXHaKeWJAAnRoqeFUPGsRs\nhF7Jev0WSV4CEat92cK8lzlklKz5DvVRg7yOyfpjn6tUxaUbVOSvpN3eGmPk\nBcOOorYPoZniwaZbiuHm58hvPLyNrG+U8LgOWkASvQCBW1YiOLJ/26hZxmNi\nvok/IOLVCFx596AfiLJrtx63YwHh4ntZLba/0mOeJyCoNb0e5KXoaxFxCxZT\n0cJASDdUEOYouaBZ+9wS3yh3eA5S9fV+oKjDPfOD4EudNcgN3VPBpU3aMOXW\n1SDdykrE64BG9vZQJGXWhg4nqM6nJEKbjFArKy8Pj/TEYpzYdAUOkfmP2vfq\n9K0WcBwn7SiOmYSAA2+edEHsG12TfU5MNFY1JM+IcViAxb+rx2BhBGt33bUA\nrTvC93jpN9mazHcDIzdOkMzjXXqHntDX6fsZNgRhLEQVQEV2R8vncVgxVloR\nEm0MFNFpY9tMUUwV8gAs7qVsoHNO7oa59OMGyFjoCAt0K4WAOAyYRB0oF1bm\n4J4YCCnecFkljN4mstH816wpYkyCsugAA3aJPblDGXvWB+TV/Rk31o0JzpR8\nX6KbS70Dty6W1H8nvD+Qjyw/o9JB9fKc0draomL/Tj2m3RbvfZF3S+zcAofO\nE6xQ\r\n=A2Zz\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8c5fbac47218a815192bfdb3c7bac685ad1047b5","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-22T07:08:37.327Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.1","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.85","@balena/jellyfish-logger":"^3.0.134","@balena/jellyfish-metrics":"^1.0.399","@balena/jellyfish-environment":"^5.1.14"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.2","@types/uuid":"^8.3.1","lint-staged":"^12.0.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^1.0.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.6-renovate-internal-patch-8c5fbac47218a815192bfdb3c7bac685ad1047b5_1637565107106_0.866510373538312","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.6-renovate-external-non-major-9ef68eccac5a86a4c4bfe297ead2f7c26f6230f1":{"name":"@balena/jellyfish-core","version":"8.1.6-renovate-external-non-major-9ef68eccac5a86a4c4bfe297ead2f7c26f6230f1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.6-renovate-external-non-major-9ef68eccac5a86a4c4bfe297ead2f7c26f6230f1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b1caa7c264b691c855aa05947f95cfcfc537dcae","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.6-renovate-external-non-major-9ef68eccac5a86a4c4bfe297ead2f7c26f6230f1.tgz","fileCount":212,"integrity":"sha512-NWThCrI84BQtzXWjRlZgfV3N59XOqchK0QawmpiTCcrGS0g+kW+AAPdgTTV1vzTOj6DiypMiNieubIKj8GZlsg==","signatures":[{"sig":"MEYCIQCbTacNdnmUq2j1IETTqtKCUDa/1cY0kLON+mcB0ffgaQIhAMXZbdlWUrT9v9u3pLViBqfhYJVwQJIgvWtHbx3O1hty","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":777267,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhm3gECRA9TVsSAnZWagAA3z4P/iGcmP8dUQAynDEYr98w\nRJHOipC1KqdHJDha78V7X/aViW6FmQ41oG8re6tULfah1Mrqe5M52Qq2Bdrh\n8bT8FrOrDQ1T3lBrMNnU6XWFgTuszBpm2AyNXsRKI+NIIZaqwu5Qqv/oDYiw\n4vFiHQE5vHu9yZZn8F9FK0GWm+tbrV4sU4wV/NLEvCP886vmGk+Vgm7CnV5z\nRMcDnSJInuRMjmK3HOdFR2wzf4evpvQTzXoPkmOy4CLYVwC3rZ3aVMN++Lf9\na83U89ykJYY9KF2mfMfXHuUcDCFV1U1mUDddtSOT4QHu9d5+uLGRjs5sgXZf\nlbrBc2+Xthvf8ApTE1q3Bja867DfZDeA8+RXchX+u1ZGO3vwFMLpqBL4Kf87\nrNBjp2KOooF9cNF7C/ijYfzX8Q7yeMIWj48LoetTIDiE6VAGzLCDkkpR3Ngt\nqTb2GkR//eIB3sM1g4TBPgVGuNkfU4ks+W+IL/G6XncXV/R+VO6vSv95+vmb\nhu6JbkOwjGEzLO6ITIiTMwmi2cHcSgL3wzOpVBGKweMT/LxH5MthNCU2MIAR\n0IDqlh1ohuh/ObUQ1BaSwWLsAPh0l9qgLGfaTvTkZR9K2T0es0tOsKhC9vbg\nWFlIdEx4MT0UTQPHTnCt15hPzRLZRjPKiumUqiQjWLktJ58mz1axLdk94QRz\ndFfM\r\n=rtS6\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"9ef68eccac5a86a4c4bfe297ead2f7c26f6230f1","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-22T10:56:09.603Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.84","@balena/jellyfish-logger":"^3.0.130","@balena/jellyfish-metrics":"^1.0.396","@balena/jellyfish-environment":"^5.1.12"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.5.2","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^1.0.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.6-renovate-external-non-major-9ef68eccac5a86a4c4bfe297ead2f7c26f6230f1_1637578755861_0.554729982407695","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.6-joshbwlng-bump-deps-b3e7e64866acab2709bedf7fd5d92b8095623983":{"name":"@balena/jellyfish-core","version":"8.1.6-joshbwlng-bump-deps-b3e7e64866acab2709bedf7fd5d92b8095623983","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.6-joshbwlng-bump-deps-b3e7e64866acab2709bedf7fd5d92b8095623983","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"14cfef7332f052ba7ecca7858a674f4adf8c0a8a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.6-joshbwlng-bump-deps-b3e7e64866acab2709bedf7fd5d92b8095623983.tgz","fileCount":212,"integrity":"sha512-7zmh/ncMHAqOvuXHa1Hpf/DIr9MxFkAf5HtLD2/fdzL7ECUrhaD8oxC2UWUbT4NIG1+ts/zF015q/t0aSPFl3A==","signatures":[{"sig":"MEUCIQCAmGL3rKonVG+9zxIY5+wv+nM0cJkNbdwlpxHiS4z0BQIgRaUjeSupmeyekG5OXHdfsuYwVMGs8RPt73/3bvJrCuY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":777245,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhnENaCRA9TVsSAnZWagAAnXgP/iTQyImHXrXlv+27b7BB\nP8YxLfefOj3KKyGpSa0di8jE9eLE2/zYUp+6AJNkqkApbG9dyiZ4+W/WV4+0\nC3uL3FpUxncX7SRVPu4Xz0UNdrj15lQLiEBgz0bx7miTgo0z0ULXVYSSxPdf\n17Z3fVteSeB2kzxcd/IgdQslmED+qO2pOb24IFdsFgUfbj1QTXm9E2AqdCJR\nO+Bup9dbN8ehaf2ux0bdzEmbEVdfL5gr2MYpqVpb+JHP3BxZzUjzJiWtCwxy\nKbHabhHSGIP97JE9Vrf5R+VhwJU4Yi+TAbUGUOMJsEgzq/kfdLvlubkURjGk\npAYFGWG0N2LPK8iU7mT1XU4RsyfquQ1JYKWg0OXYOzUA9ndaaWPE9tGJXMeS\nkv/mq5X0XoivbZpsxkPRjWeg4mOR13SPWxmKft/iM7aTEK7w3kc7wIi3WWpH\nPGJz8yAGeNHl+//e3TQjFPODn6i0qsbHFRyi9EciBFPd9pK+j7XoiZ6rud/L\nrIWkjBrhJrbXIQt3CLiKT8pKE/0qFDaJGtHj1hj1J1VT7g6jIs0KcasrxGM7\nYn9D56lNj0oqWpoMOONTWsIf4CI1lQyLOi8BOXfQoyyW221kB92Rex1y0st0\nU+aoRZLqtkq4xPfROI43iAazV3ZIoqV8GDwwHckfqv1YS+zRpKbqjz6hnWRJ\nLdv1\r\n=uGYo\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b3e7e64866acab2709bedf7fd5d92b8095623983","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-23T01:23:31.562Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.84","@balena/jellyfish-logger":"^3.0.130","@balena/jellyfish-metrics":"^1.0.396","@balena/jellyfish-environment":"^5.1.12"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^1.0.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.6-joshbwlng-bump-deps-b3e7e64866acab2709bedf7fd5d92b8095623983_1637630809916_0.2966345545321061","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.6":{"name":"@balena/jellyfish-core","version":"8.1.6","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.6","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ece7631d3d863bfc47cc71041b58781cbead4909","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.6.tgz","fileCount":212,"integrity":"sha512-FDRZQihqA3gIN1aV8vF+RT1ADr8ABj3vZalbrgE4QPEP4uhG2CMd4slEabU2SF3t1jRlcb1OzlvAxZmHk6yXmg==","signatures":[{"sig":"MEUCIGinq0I0FOuj9TCOWBBd88Kujh5r4yw7ynTAr58NyYjrAiEA18G9Osh0/JJO4oBjAwBXJ7Hcfz67lOUaXuR//On14Qc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":777184,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhnETUCRA9TVsSAnZWagAAk9cP/1oe1jSf2H95hXxXQPCb\ni3rX4p1UZMdYMWNrW3iKniv4mfnX0PWfq6wYKUvnFqq43oNVKE7bQDRnI4sb\ngLPiQC4Fbn/g/OgpOqf2v9z0RVtT+DTVDXqsqtpagoxNLRl7rOwQ8P6Zi976\nRDAXfzQ1oE3UHyOqj2aV13wkbpaLX523/5ppZALGTdxuV+DKAzlhDq/a7JYl\nuJg+TqRSUfbXB38r3DBVnpPuEj6B3oXq7isGMU8ZdQp+U7ulRFGYKRy6UmNP\naZBYtiqZW7tSpq2nrXf/21spr1aVx1ptqUZSScVx6QEhjxL5cT0CoOpFThXF\nVm8/df6gg6XtmBgGDFUsA4KXBoQPRKUbl0xyjcJCpUxmJ2LHastJQ2vQF7+a\n0PNSZeJTLhqXupcCu7HAdPsn1XEyAUAD5deJX4CvsExI8K0brDTwleowelzX\nZ4Vwnv5F2c0U9GWy/sS2Uf6qM3MVdMiDEubOZHBFbYgI1Tgh3pYBg3fV0G/4\nELbNkcQKLEhFizbNjQn2Vqwjc4MC2g7TpPYBrSP6BaNV7bR2jWQAme6Z8ak+\np6XP62s3RnRRVT8xKFUFHGQejO4oRZpyQIqJRR/759wKUb30eipztFn6GAJi\nYFP3mDqJ+lD8qKI1WqMEa/Oc76+BiqLSYbvXZSzdE6VVxoBXbOvnB+7MoiBm\nUDKj\r\n=5hk8\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7d7b9cc69028464fdc95d49b626e59f16427d7cf","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-23T01:31:01.699Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.84","@balena/jellyfish-logger":"^3.0.130","@balena/jellyfish-metrics":"^1.0.396","@balena/jellyfish-environment":"^5.1.12"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^1.0.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.6_1637631188285_0.7797191683131872","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.7-renovate-major-json-schema-org-d37974bb73188920a909696ddeb27084c80836f8":{"name":"@balena/jellyfish-core","version":"8.1.7-renovate-major-json-schema-org-d37974bb73188920a909696ddeb27084c80836f8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.7-renovate-major-json-schema-org-d37974bb73188920a909696ddeb27084c80836f8","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2ca05fd6f125209eb8a4190f3c7e35a1f9243e0c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.7-renovate-major-json-schema-org-d37974bb73188920a909696ddeb27084c80836f8.tgz","fileCount":212,"integrity":"sha512-yCbPQJ9dVBddcKvWGF7Zr7ZEVM/adIx/Veq12flKH68a2irOU3nDGUYb6GlAyQbOkbfYc4YubfODPW8seEBjpw==","signatures":[{"sig":"MEUCID3QfYh+Mu6+8n5phUmynn6dygNA8K7AJwAzqb/7jy27AiEAheXy8tqxk61GvY19hHHr3BAGqTIyiiaDo/ZEur0ksSg=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":777351,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhnEXRCRA9TVsSAnZWagAAb4gP/j0s+COVDuSXY8nqOaso\nfOCZq8uenV5Ta1eP5FQo9viAnH6znS1XZdedbNH0/G7LnC0RbNIMk/DgRjnU\n4Cd2E6wrXKWqhRJpRA+3U5xbd8yom6PHDq88+cB6aBWgX7HSayskQ4nD/RVp\no24lPnaMTcunba/uCfkaXd5Fv0ZVLZiVmSWMaOKYJLnrhem3/+EPjpMTVZcc\nTCIaI5l5UiwYc1WJ4GzFxt7B7SQo6ehpl8bVadFi7Dj7Ef90G8fvFb7p/DOk\nUs/qCojfvVWIPI41tHH+0BFBQjOtNP0bdV+BQNxStncQPMFI7IzJvE2YgpPf\neVHl4+mm/Z+1gNHn9Dz64FtG63RIvfJN1T8aggA/EXQCWokYIVSppUP9SflQ\nNi8MOfWDKs1EEO6Ly+Vblc/Qu0K+vG84Z29bKnnLxKXMKMQ8MHKUGaMhhf/d\nLCnAF6D+SEfhy1UDB+Zoo3rzJDrS59GhpkpmptfrnBS52PAKg3KSZyAi6juW\n/Gdfiz9JHVbEj88J7vi+1t7C/upLSqAhaTNRHiduBJBHlDec1bV3+hyu6XNb\nEcDNY88VCv8n2gGCyKmaSUPr2BKkIKHVdKS0jN0ckXRL4ukbvIhgwTqe65BG\nQ5VGAUu2Q9nWfksbxbHXnZfy+PHScDyAKMOSQJ3HHOHF4z1pOFwUZSrmPv+t\n7zAc\r\n=e0lo\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d37974bb73188920a909696ddeb27084c80836f8","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-23T01:33:11.657Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.84","@balena/jellyfish-logger":"^3.0.130","@balena/jellyfish-metrics":"^1.0.396","@balena/jellyfish-environment":"^5.1.12"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^1.0.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.7-renovate-major-json-schema-org-d37974bb73188920a909696ddeb27084c80836f8_1637631441394_0.0706949925939544","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.7-renovate-internal-patch-8ffff0fffe2b729bb71dd049fa7162da47541000":{"name":"@balena/jellyfish-core","version":"8.1.7-renovate-internal-patch-8ffff0fffe2b729bb71dd049fa7162da47541000","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.7-renovate-internal-patch-8ffff0fffe2b729bb71dd049fa7162da47541000","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"597317f2edb3a6d17a241cadb4c3c83ce23231d8","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.7-renovate-internal-patch-8ffff0fffe2b729bb71dd049fa7162da47541000.tgz","fileCount":212,"integrity":"sha512-5IbRoJQJaDcGh/Ae0UDM6oJY3inXEig44b29J6P48lrKSsNeDPGRH2cOiaTIiI9Y7+RYWr1MTlcFODdU9dt0Fg==","signatures":[{"sig":"MEYCIQDKgyDCUtD/BrNBHF43Wn3SNfkiW7n4LzW7Jxk3XXjA8QIhAJ4sFdij80oVd+A1gXh/lM84W3lAZ2YeUBnG1/LrIN/N","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":777319,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhnEZOCRA9TVsSAnZWagAAxH0P/1C0YDx5L56Er6zrClBJ\nKGmu/Hui8elhfbkwdFPVRxl6a4xNhyvgjO/jYDpNdOmICbB8lHo1ePDTo7uH\nLYYo27bn+n/TN8dFqz+YejQPEKTw3jGVNh2MhD2xCutkEwvh1yXBEb8I+RNu\npOpApBta4+RgSTu5P4cv+4xS0F8cLRWqse+OOhBJuLv+p+k6OHYFNtNpsKZg\n+oQq7laGkPBgJblNN90dgxHFd8RKMz3Z2isN9xjpIJVR5jCPHboayp+ZbJnH\n9hvz0qM5P16zIYWc2TQogmQiXzqhvm0uRFRDaaeSfjzAg3q/LyXVJsEHAEgY\ns0S0Eatue8W5/YCDB7m/7ckqDW5NIlV2287DKhEjA7Oc6154sv2tvyXaJGPU\ntI8Z2gcgFkY2HAlNIhuoq8FHKQSHoty1XpPFh0CSxRbxJ3oZT3AXOluBatUK\nkwffyd8ycRWvKwsGqwyD3nFksSRL3R1BfXhX0ls/pRWIqHejp3MJroBVOHmS\nUcwHr9ebqF8ngISk+aINIC0fKuTLVHu/H4gdhByb9mN3F3UQHMpfq/SRYmqW\nN19He8HlSNe0a/IW6sTBaOUKTSpllNquwPOqXU7KsKpiGLPMX/9auwLaa4e7\nHhtwF9+ynGGj3YYegn7+wK8WVtq6yqY0I+mBc6QclVXh8A42PJQicov6abuO\nseNH\r\n=+PUh\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8ffff0fffe2b729bb71dd049fa7162da47541000","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-23T01:35:07.977Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.85","@balena/jellyfish-logger":"^3.0.134","@balena/jellyfish-metrics":"^1.0.399","@balena/jellyfish-environment":"^5.1.14"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^1.0.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.7-renovate-internal-patch-8ffff0fffe2b729bb71dd049fa7162da47541000_1637631565986_0.08127563808363458","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.7-renovate-internal-patch-49743fbae83a388c1c0dd820a52fc2e5eab19d97":{"name":"@balena/jellyfish-core","version":"8.1.7-renovate-internal-patch-49743fbae83a388c1c0dd820a52fc2e5eab19d97","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.7-renovate-internal-patch-49743fbae83a388c1c0dd820a52fc2e5eab19d97","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"63bda78b01f41859486523ad990bf43b36e6b15e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.7-renovate-internal-patch-49743fbae83a388c1c0dd820a52fc2e5eab19d97.tgz","fileCount":212,"integrity":"sha512-VfwLZ/tCdlyQVQUQ1Ytp9j/415XfEF63+6yJOfFD0hQR7BLD9nSQZ6W0BjTAJthN/wK7kDw7ZTdrBSgkuKRvug==","signatures":[{"sig":"MEQCIDzUyw5yP8EiNYmwYe79znW+S6Gojv6asoReRQOba3roAiBeet24VVXTB4t0oOh5RYazC5B4/vYxnN4gbnMOL8EGAQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":777319,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhnHVrCRA9TVsSAnZWagAAk5IQAIiK7URiAjvYWkSz0L4u\nYq3PK6+lg79SCSfPeADLBGRMXYRZc+giUC9L4uhnV6yXVEY/H704bFn62Em4\nY4qN8scFL4V+WsPM6TlKO0jMI47UQ4KEGhHtD3xv3Yoxhy8FxcLUw61YnNu2\nMmE2c3IXf2Q5o6P8o2KIe1syUNPuYgnCBejK0KeJ2z7DgF+nKA3emOGuw6+G\nLiN3VdpTYFFZpG9mETIQuTP/Q0/DHjb7qof04pj7CZrAR9TSRoV3qT5Ky+vD\n9mZeuXsTy7aFHBv2el0aRZoBj4zYrLxBndYeHcnX4XGHZcHR/44dU0QuUQ7Q\nV2IXFP6twlWxoWdbLPCdWSRemvMnwVcBC8n0L3XkmvciVfzvvhet3qWM8084\nV42BhCshkSNKkJA1/+IEu/1ezUIGIAO150/OsuXrXNZ4jr1rhsolynSjk41f\nPTqqIw0N+2UCUih3QtVeZ25RGWXMfR4EP2Q5qh+mwtKFBEdWp9d+jKSWBHx5\nncnbU2o0Cfwq+AbKXi6LPAhMZJQlMVzvp8kUDQpVmmZzP7kUCetW55jBjBNf\nkfRvr29XIPMEDQBLdE/ueIHTFsj/zmRmopUQqt3EQXP+scBFAHTpi8jZvztf\nRSp+fbAkJ90BjFLo5pFW9TLIqdEJqgOSabbW8u12IiX9afYWFf36vc/XLG8Y\nGKkD\r\n=lk3C\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"49743fbae83a388c1c0dd820a52fc2e5eab19d97","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-23T04:56:47.014Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.87","@balena/jellyfish-logger":"^3.0.137","@balena/jellyfish-metrics":"^1.0.401","@balena/jellyfish-environment":"^5.1.15"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^1.0.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.7-renovate-internal-patch-49743fbae83a388c1c0dd820a52fc2e5eab19d97_1637643627607_0.8193389929746078","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.7":{"name":"@balena/jellyfish-core","version":"8.1.7","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.7","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8db72450f48ca38f18fb476cf63da60daa47b906","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.7.tgz","fileCount":212,"integrity":"sha512-+y7qu8RLGGV6Bc4+iWceW9FM+7VZyuLw64LzZUZGc6zONaIt6YLUWmyrGzqbaRrw1FSdKp60dX1sZF1eWOrPpQ==","signatures":[{"sig":"MEUCIAMHrN2UK45FHB1Zo80HlnpAZVlUivViDzwT97nikQbGAiEAna2FzYpOfI6uetGcYmXJ/drLir6cg2hlvg5HAI7Zis8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":777254,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhnHbLCRA9TVsSAnZWagAA7v4P/3C+bfHCIS43iI9kMfsa\nrHHQZBfOVJ9PUt0ZzwoGE0bxw7lcG13CdP1Trq2o/HRETtJSMkP2sM7Eigq9\njPSCfX0j793fUdCA1CNiskL4XqScrWkB/U3aAuRZBJRqXSJiIH4KA0vSRvHo\nj92CG30a4poYVfbJv2PTYR8yeXQy9YcnRsl72zDCDZmU8GY/0kBNSNXwjDCm\nCiVNQ9noeF1JNEEf0ClIKoPgzj4oEcUqKt2mMyqID5WZ0jvAaTA43sK/D2LV\nn0g/U74puEBBo9TSM2ZhkZ+OvSkwL0JlxGp1mZ7dUMKIh0vzz38Y7Pr6t86u\ngkpkVBxeBPVsXq/CelHJPM6pULihYR9kLyZuCdxSZza2xxlvGAqpJJYPxkPO\nIvh/VaNhYRzLti7kbJChdFK6a57j0VvZPAeYV54imEOTpKhNzdRRZ9CK+p5i\nRWB8OnHCV+iDGYx4mDxI3tgcr5G1WnxZrJUnFVqcoc+Z3h+jEn/GAnwsI8ug\n/I5sg2Ad1uVN60bUvSgMO8aLI2zzYOgWb6raJhkQ5lGA4oNApyizVpJKIN2j\nx26yxxse8fDQ4PrDwPYaeTYGYfOtwRhzPd4qe3qjKeA8TSNZ97fIYjm7V2e/\ns5/2Ii5j0Y3HqpXZqPElpxvWrvVaTgdyRCUZZfc0xRB8NqO6v0RN304T6MtF\nf4Is\r\n=l31Z\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6c2afe2031ed505a016be8785c89ba35c76a6908","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-23T05:04:14.802Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.87","@balena/jellyfish-logger":"^3.0.137","@balena/jellyfish-metrics":"^1.0.401","@balena/jellyfish-environment":"^5.1.15"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^1.0.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.7_1637643978969_0.8579655085120323","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.8-renovate-typescript-4-x-44fed10faa261e8bc34554bbd72c4dc6890acc65":{"name":"@balena/jellyfish-core","version":"8.1.8-renovate-typescript-4-x-44fed10faa261e8bc34554bbd72c4dc6890acc65","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.8-renovate-typescript-4-x-44fed10faa261e8bc34554bbd72c4dc6890acc65","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3b4f634beac531cdf201dc519187fb663d3478fc","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.8-renovate-typescript-4-x-44fed10faa261e8bc34554bbd72c4dc6890acc65.tgz","fileCount":212,"integrity":"sha512-Ik9HSj5kDkFSJ/IR4yZdfPhTGvXx00O7cxriUWG5QomolqmjYhsKNBH33oVi3pINJ/sKraB28HCAySDffXQ+IQ==","signatures":[{"sig":"MEUCIQCYpqejlpvK4xw48TV6vtZpKiphuLHLLouqbnGm7dUdKAIgBb8GPwF5sOmUETgOQfRmrJUFbBZgYcXiRGdxHgHmcdg=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":777406,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhnHgpCRA9TVsSAnZWagAAvPEQAIjff2qtqH6mS4MSWBxi\nP47MicTU93tHLm+Rd9tXaW+3lPnxjyv4kFAhzJEj3bF2wL9TSRsP0415poSI\nuNn4G3B/DFkEZHDkurSZlASKQ4VHHfkB7joxIq9YebL8uQBX/4Ft6EpVanjB\noF35UaJLCT+dnpXB/ijCPmnd9A0b3x7E+KQJO4LPPuLnEFe+qSScyqoLQ48F\nsAO6eiQvN3BntP2erWACoTsnoCh+evLrrwgMIHzHqMcw5wMHxaFwMfvSStYG\nJ1Qdu6+k4H2Csxq448aCgCh3mCPOB0ibaTBUuvGmr/QmG/7o1U10U23HS6er\n54HOv/zjrrmEIganxsLEL6ak0pr/NlxjEgk0wiqScw+gGotk4g+PwLS8yXAC\nGAfx1jvLySMKMllrjfPJWUh9SZ9XwDPdhG4VIuZn2pB2arYmlmqb3p6Cn0V6\nbeuqYggLEsH3d7/tZQ1h3WQ13tPAIK12k2R/wUhcgetTMoFeSRo7ew4c95Eb\n7b7XQEtuONCzExTKKa/LwJnunkc7zNccHx+AK3GCxefBWa88WVJtjR+JjRz1\nebenbkkJdjOg4QvupO7Pn3ALqz6GQLaAM6fvM8Yp+1qurbgrDt5QLhTMF2/7\n1w1dixAN9I0tgLFSqKQl7mpF/Y1+xICC8RgFoftV1uYOMci3KJd6f6pErcFH\nYSbX\r\n=msrd\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"44fed10faa261e8bc34554bbd72c4dc6890acc65","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-23T05:08:53.587Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.87","@balena/jellyfish-logger":"^3.0.137","@balena/jellyfish-metrics":"^1.0.401","@balena/jellyfish-environment":"^5.1.15"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.5.2","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^1.0.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.8-renovate-typescript-4-x-44fed10faa261e8bc34554bbd72c4dc6890acc65_1637644329570_0.20056156466076636","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.8-renovate-internal-patch-da2887ae40c5700f2c18880f2834b57c17ac6cda":{"name":"@balena/jellyfish-core","version":"8.1.8-renovate-internal-patch-da2887ae40c5700f2c18880f2834b57c17ac6cda","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.8-renovate-internal-patch-da2887ae40c5700f2c18880f2834b57c17ac6cda","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"dd8c56cdb3d302ef08924d841a4d9411927c6cf1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.8-renovate-internal-patch-da2887ae40c5700f2c18880f2834b57c17ac6cda.tgz","fileCount":212,"integrity":"sha512-7QabmB6LihoVijuIWyOF4M4CMSwQ20w4y+cWHo7vDvuOHHTSk46dwCgO+CV/fxDB+vO4YeWhUHSuYvWQTPiljw==","signatures":[{"sig":"MEUCIQDhPYkFVDwPClVuRxfB85EIyLA4+3SP8IbCUVVqlyhQOQIgdYjLi+kseachfB1JkrRKmgG/Jf9XKoWxCgWuaQjceX8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":777389,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhnadLCRA9TVsSAnZWagAAGI0P/j+8QJWEyn5NxIXGw1By\n5rxgAo7tP4/dQfOGW/H84qHaN7nuyRG52Y309gtoCOhNg4Xz1wau3e5pFWsN\nlnr8MEAETMOmEHsf2pQmfHDVecsZhVYc0QsApHDxCh1gk7AtL3blsLl8F+5/\nOVCpV1pN91Txy+Dj1xbEGeKAkNOYEu3XZP6TXtnF+bshwYaGW45ps4dgIx7k\n88nNol1UNaci503Az9fV7JWzh4A7/+uZusyuybjC/hZ9dLddXwK/V5WOmNV/\nYF9qYrgjkNz0HpuoCd54xHAaIx7Tsi0uLI1nt5fEfdMQfSlNVRmsrLqWRak7\ncsN+ajgcvZhHlj/grrnfFekzB2tHjAgSwJtPDrHCfwf0MLXBciYszWyyZwGw\nLdHuA2vGlMzpZJfx0Z/yrp7L0yDwe4WU6AGjU+cCRviqI0LcMn+ZpIiKFtzX\nTh6JgPBCKVgGrn4CIv+NhQ+trX7O0Bk3+3hyJ7r8HSkb+wdcvoT3t6QJRmlB\nshnc+MEYg+CZqwOSK7KqEL3/WUpRIMAZeEIuuwgdjV6tyqk+1isGR6RNvX8s\noxYqbPuorg+979xDQFV4jJYBiy1rWOCa71u5lPC20+Y1T0DAjHi3zgitv4BT\ngdaq3BcGto1CLinqzsLFUmspGjD8RvhNGMdpEq5Bo0FOqAtVU3lRLFzj4TIP\n4DsC\r\n=qmyN\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"da2887ae40c5700f2c18880f2834b57c17ac6cda","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-24T02:41:52.588Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.87","@balena/jellyfish-logger":"^3.0.138","@balena/jellyfish-metrics":"^1.0.402","@balena/jellyfish-environment":"^5.1.16"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^1.0.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.8-renovate-internal-patch-da2887ae40c5700f2c18880f2834b57c17ac6cda_1637721931482_0.6161610954512924","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.8":{"name":"@balena/jellyfish-core","version":"8.1.8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.8","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"68ee42f1db4afe8f65bbabc39a6026e864c7ad4c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.8.tgz","fileCount":212,"integrity":"sha512-e0Z1Yt/X7mM3IxdwTQU4VeepHATuj90iMZ4sLD0D+wJDhVkn1iMgJMR/BmO5y3XktX7XWzgySKaRLXjkm12bZw==","signatures":[{"sig":"MEYCIQDczSkvvuV0/wfkWnsz/DvhFHuDjLWnBU+RpN5pMgFi0AIhAOD77nKst+QFEklgB2UUPFs3aEzsN8usxMS+xmblz81B","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":777324,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhnajOCRA9TVsSAnZWagAAU8cP/3zNRXvXe8DvwDQPoXXo\nOJu6HYpznwYeTWugGSYsKIyRBN4HYoRXBZPzUwD2Mc8QHnwHIQnoVKfMZsS+\nP9djAzLm91oZey+HJOpDRqKSXs4rEXGN8UQIelxkOWLFLt+C6qoqLh1Y/Yv/\nK7eYjWGKrYahebj80XWEackuUOfcHLAGN/6mDd4gOmLsSpkgNPq2jUvIaKay\nFnub2UER+ly50ap7VU9SjcYg5xe9xN2aZoRFNnc8qBi6wGfFGtiXJrHZn3nw\nJAwEtUmmEwkUg/C0wCRJ2D046s/WeEn8BB26553GD0z4mtgd8Nhz3qxaMe7U\n0tb0m+qxQ/2Rh2TqSdRbnekneW6hh4iyVVvCUrjacoWNK5LlVRuGeIKm1wRf\nSyFEyQxmZ1Us0qRQMCWMC/dWS28TNTp7N5q57VPDDmsSg7bdwoUsrVp3UC5X\n1q0rxKr8/MBLW9ffm8f9a5BFIf3hsF++wGswLowCGig+rJJC3JHUjiXrn2ys\n0d75xULDSepB+bgu9XbgCQpuOg6IF7A/atoEo79L4sq7GpyArU4n3C6cMVwf\nschDUvaA7lPeXcgq6BrJt7vplGQgrDzY77k6gh3mSKhZdvmtWFRx54oTZZhf\n3lQYYtnW7EbCocUUZMLLRCzhq1U7EpEB3wFJxa78O3RHJM5Dnhaqkm7tWRZa\njTSO\r\n=933H\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"bd807b112a553c031d6572b39995ccf6ae5574b4","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-24T02:49:33.140Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.87","@balena/jellyfish-logger":"^3.0.138","@balena/jellyfish-metrics":"^1.0.402","@balena/jellyfish-environment":"^5.1.16"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^1.0.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.8_1637722317853_0.9669637517232712","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.9-renovate-typescript-4-x-c5057df83b5e1c8ebf247740fa50fceea5318f47":{"name":"@balena/jellyfish-core","version":"8.1.9-renovate-typescript-4-x-c5057df83b5e1c8ebf247740fa50fceea5318f47","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.9-renovate-typescript-4-x-c5057df83b5e1c8ebf247740fa50fceea5318f47","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"99658dcabda8a6363ff96aa37041dc051e5d618c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.9-renovate-typescript-4-x-c5057df83b5e1c8ebf247740fa50fceea5318f47.tgz","fileCount":212,"integrity":"sha512-V3+Ij0vmMBm0JNMcDM45eUsR2C5lDV/dAra1vXyLtXOxiCjNgzkx1c2y/lbaZs1UMNwXvx6Hv/GbxQshQdiK8g==","signatures":[{"sig":"MEUCIBj8RnLFiDFEiKw8Wm+SpbaoYj+vpKsIsrzOgtSLCJRRAiEAm1YmB7DmyMwXjhiBGiQcpyeLbIePdNQi1XzOvdc13IE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":777476,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhnamyCRA9TVsSAnZWagAAi7EP/01e+a9z44DMHNv8XkOL\nJ55Xa5lzbt2YON0GxQfabySNQRF0RazxEWaS2NthZpzmNrYNAXAZdANPysu+\nmKSmLBEuY7hBIu/BQ0F+1EBhVtktw95ZTrx278fMW4ttzBC1IyymB/HDTvy7\n0H3ExRhTTrQfcFoKnm1BT4nhKfcAZ0vChiujT3SIwrDh2PVx7gUqzOTMGKNW\nvtVUlwGlx0PfzEwDIV78aL50bfCo4J8r18sheZATL1Mmw6W/u1K7yonNqCo8\nejQOFHxOmSV/j27kqrMWslJ72DpzIfYdH4oKa6lDFa9q2XPdegGdZ4R9q4P8\nKAEmC+UmMVKpS7LwJWv6+eEowzsH+n22xwNg0oPawg7aLJsUf/Lxhle2fOwE\n01RdM2wb8ol77R8eSnsIhPSJ5vLpdQxr8cK55Z1p7VHR7VB4OQ3vWrs2fKOC\n8qsS8ovEGWr9kNskFoNWFw8s7/Ev7WQzghCWfbBjxCLwEtlCbnVwYEU416qE\nzgLsMlZpzgZuPaTA+QB9wcJOIJoXCVzbxTSKiqyq0jBgTw7pHfEfLJoxOeMf\nchgT+t0KOHJOOkpozmJwZcOH59pNTBv+FcrjCe+iuyEwUruOxzSFmW7yplf8\neThbJP+zbIE9gkx/wqfEi0gLphntuud9LsRBRmHz+R2dE7L4+GzCGmuo8GUh\nxoHt\r\n=dl0N\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c5057df83b5e1c8ebf247740fa50fceea5318f47","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-24T02:52:28.017Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.87","@balena/jellyfish-logger":"^3.0.138","@balena/jellyfish-metrics":"^1.0.402","@balena/jellyfish-environment":"^5.1.16"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.5.2","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^1.0.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.9-renovate-typescript-4-x-c5057df83b5e1c8ebf247740fa50fceea5318f47_1637722546191_0.8356115424336155","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.9-renovate-internal-patch-b374796ee4c4888807f7ad131d262d370bc88d51":{"name":"@balena/jellyfish-core","version":"8.1.9-renovate-internal-patch-b374796ee4c4888807f7ad131d262d370bc88d51","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.9-renovate-internal-patch-b374796ee4c4888807f7ad131d262d370bc88d51","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9e644fce9c2e2859e2d401b9715dac08a97c93f6","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.9-renovate-internal-patch-b374796ee4c4888807f7ad131d262d370bc88d51.tgz","fileCount":212,"integrity":"sha512-coE+65pDdJS6y63HSLOfnGcBEEdz1UgynKLGMblPe2I516+HMlHlCNVwJ0xTEozgDpgGOv+9Eoxsc9yVfjJJ4g==","signatures":[{"sig":"MEUCIQCFeDSeWSU3Yzr9rnHtcojYgcuF/KkH/8/9NFp0WrOx+QIgUwGtretNxQTgDKmWif/sC+hP8ajEl5KzLE3qbGlQ9n0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":777496,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhnapnCRA9TVsSAnZWagAADIYP/Rmi/eP0MTY+7wAGixBt\nsohq71swzc0voReWuGQIBEh2UUiwWiqEoXJaoXSOiVHBkkST5y4GVWj+zu7U\nb76oyBcaOTA/Fmbtni8SowgIPCMUQ2ioDXYaPijlWpBFFSZqYofrjndESm2b\nfsSUOl0PNSOnoMAAkSASh637/GmgYIXHjF1TJwi6nhfpuuwtEWngIxWtThEm\nIQ5fnNxcUCWP0BeGjGh9GbCBuniNGB6TSzOwp7brB7MEX8RjiMqTd5K3Mouc\nAG51Wx1TZ0K+XWeiFTPd+bpv1mWzJ7roTV7NeqmDH7rRkT3TKkTngMRmZqAM\nBBQzCvtuSTP4HHO4j/7pcpZYxlqzdXSelybWCHY69Zu6r6psTLdEQyJxxX1p\nD70wfWABN6BN0ilhC0dMJDrRJgl2QrAuDDZIfcZYbkjRuHITdpgfiiUsnvEg\nXBppNOO4SKiPjZM8QcVP1aHdefeBu3lcdDW1h3cnln9Q+HGa8ota04kguJFz\n6pAh8WdO+XdWXDF6LeueyD6FPXNZUfAsBdef4fREavtwL72Ao6dKdDYKwh87\nkUk+ceCYKhJ5KFyhLHoW5nwpMNy2hAJjLgiv18QNngsuzuQkFK/WCF9yliKD\nClQfN+qZ/Lukowyno7ylLBazAO/qYBRqlE/SYMZJ4NQlnlglQiDGVpx7Sd5W\n0a7k\r\n=Gpes\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b374796ee4c4888807f7ad131d262d370bc88d51","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-24T02:54:54.002Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.87","@balena/jellyfish-logger":"^3.0.138","@balena/jellyfish-metrics":"^1.0.402","@balena/jellyfish-environment":"^5.1.17"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^1.0.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.9-renovate-internal-patch-b374796ee4c4888807f7ad131d262d370bc88d51_1637722727738_0.1953860338412401","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.9":{"name":"@balena/jellyfish-core","version":"8.1.9","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.9","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1308b40a1115948a1969fdc3703837bf4c97c0fb","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.9.tgz","fileCount":212,"integrity":"sha512-8K/ipniyknFZgGa1Hu9a2YHlrN5X3JWaScZBhMvqyDd+ezjuE1lrVwZWEVcAHhJ66BQAd0fmgGF+v/m7hyooKQ==","signatures":[{"sig":"MEUCICwj7fTjsAj6DFynEuVTJ1/pUkB085le6yFLeJjyFpiLAiEA/TA28S4PNheIYL458NPIeY+P6UcvRQruR/KMNtSPA3o=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":777431,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhnavWCRA9TVsSAnZWagAApb8QAJt70YUeicOFJE6ld1p+\nrbviSOecDjDxZOCFnwzepcWlgtcjBnKaumuFgAI72Bcpq4E/bPBC+hM+fdFT\nFmIGA20qIJFzwIrPB8pW4y3qVwnnC/SD8ikrice3RVeOGSt5CDHaiCTD44cp\njHUXgXT0AvICWjRknVS2MIjHS8TKprdJyLSh4NpMMOfCYlQ92ApoeOwZI5dt\nDNUZd8Mb4LmmKmBzPCmZ8jDxJb888CSf9zPcBCbFxCjs1ZH+WFuYuTwmCtXg\n+C3bF6Ln99xmCqvCn4tp7kiheSthNAS6rgAM0BNsZfG7gjLBwAJfRQqNwEKm\nxQAptC0XAAirXiM7hLAxkLHCpeMgoYbiyFgzh6HOOPotvytSLkJ0acweNSr7\nzONKEZDXpgkmvuEzwuGxD5bvRncVB3Y4jxvwmVTNGOf7duVpM/tzhL6E524/\n46pKXQDZ5802zqvM8O5n/sfix/IJUGcr8R0KeXqWWYEFsgjNGXKVah4+KX3H\nCZw1JoLPaZ/qrOjtTm5yUlaev7pkJF1S9bvOSaEq5WnD2gLgFj6PL/j1Oin4\nFSSFhlUE7cUwxxlAIWZ1VmB758T4u5gzCxNH9qPUFJnCLet0Ck+F800CFFgn\nSJ1zuSYauNUycWHAZdnov/i770KD+YLe/hFk0x78Nn0Qwh9ObDdaIceKSGWY\npqm7\r\n=3S2b\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"9169d28a31adbf51cf8a7bab59dc53fbfe2c03b7","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-24T03:02:54.120Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.87","@balena/jellyfish-logger":"^3.0.138","@balena/jellyfish-metrics":"^1.0.402","@balena/jellyfish-environment":"^5.1.17"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^1.0.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.9_1637723094664_0.3742214648533564","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.10-renovate-typescript-4-x-cd6322e6773b9f5a15e3ce31e94f328836d9d99e":{"name":"@balena/jellyfish-core","version":"8.1.10-renovate-typescript-4-x-cd6322e6773b9f5a15e3ce31e94f328836d9d99e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.10-renovate-typescript-4-x-cd6322e6773b9f5a15e3ce31e94f328836d9d99e","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4e6c0c31987dbd5737b25a021fd7f4598a534432","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.10-renovate-typescript-4-x-cd6322e6773b9f5a15e3ce31e94f328836d9d99e.tgz","fileCount":212,"integrity":"sha512-AwnmTDbsI5faYLtCFjvbvX4zDxpW/PaWtwko7Gs+F00fkrsT1WnfX+xztSlDYpEU7pIyAMSIwRNLlai19yxttg==","signatures":[{"sig":"MEUCIQDngylSDbv8Y2NdO6H5ijvgagTYHLjKKEBLhJfUK8LRcgIgHXVuk2/MgNywwv2X7W/uYMul+Np9xeAUAOFTBf+VXxE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":777585,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhna4CCRA9TVsSAnZWagAA3jkQAJ5vKSeVBnqiTbehvIV/\nvfSv+DhcfeBW7xtLj+qUkIwlQ3uT41vq6oDr4P/zPNWfoU7OefjYe7BiMjI/\n3+0PN55LSxK5yS6K+AAGyfhewvfcrHl+jNDo30Ta+ZHFACGeY+89pP6jJJMl\n3flSAea6I+jr5BnNFKbVDNcy7PJ01Oe9MB2sFNAbH4uR6ZEBwpb9bEqedipp\nC4nrYqW91XCizRlyEUht3t45f/P34cqQwgMlJixP+1P6BqXtYLIRmE6UZKQT\nMRAGcmm/uvyPkBUNZCOi48tZ2LTtOqle8a27nj0h4S+bxGocKUgYQPP1MN+x\nGtHyNDiEutBSTuQPcJkYMoGf9atQWkIoPmayBrh0T38U5gitBCwj//kkDmEz\nDBq4WI1K65ZXKR+fZ6RUpO/SuR1d0LgO1jtsAJcpDE7j8OERatnJfZIn9Jse\noaTR6Fqp+L9R4s0uLYSbG8U7Ci0U16X7IInAn9uVnQEPTpf1gXcowL2eVBSD\n1FXtDacTTZKJfr/AblPEhrQmjMEntdaQbQuntOV/bTCbWRt/QhgDRcpHAFL2\npIHfUhwoh0s8+lOjyqnd5Iuomc5KlWGaI2oyiphVzItFlTW7vwBBk0OZRl0c\n1mKeRJH7X7Vp4whgM3OSe3Gke2AbUul654DzIVvZNZA1MjHEGyL/St7b/IOO\nN3yL\r\n=OjqQ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"cd6322e6773b9f5a15e3ce31e94f328836d9d99e","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-24T03:10:10.654Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.87","@balena/jellyfish-logger":"^3.0.138","@balena/jellyfish-metrics":"^1.0.402","@balena/jellyfish-environment":"^5.1.17"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.5.2","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^1.0.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.10-renovate-typescript-4-x-cd6322e6773b9f5a15e3ce31e94f328836d9d99e_1637723650578_0.09311154352698292","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.10-renovate-internal-patch-21a16f7778833c14b8f64f52b78d9206c4cfcffc":{"name":"@balena/jellyfish-core","version":"8.1.10-renovate-internal-patch-21a16f7778833c14b8f64f52b78d9206c4cfcffc","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.10-renovate-internal-patch-21a16f7778833c14b8f64f52b78d9206c4cfcffc","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a0ffc5ead9df3dde621e470c408de5437408537b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.10-renovate-internal-patch-21a16f7778833c14b8f64f52b78d9206c4cfcffc.tgz","fileCount":212,"integrity":"sha512-9/ixMVIOc/v+YoHvl4RuEaulIS5+nAXM4j0VG5MoHeeSusNHxvieBy2OhCJotaaaHIg3+Y41MNKeAVIT00WgJA==","signatures":[{"sig":"MEQCIFVqbxbFmJXdYnOcQm7ILndI9god6RzMwCP9WbKziWZ8AiBQksWtp1k4+BOQFoipJ3mWXPKZszsL/2EcW03kYldb8A==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":777568,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhncxNCRA9TVsSAnZWagAAc0MP/jBRoyEj1HCIkYdqMjvA\nPrTyTE0KSD3DbVeZMLIVoPAs8NFqTsqsK8IUTJNLsXHSykfu7Rm8P9L/xfh0\nQQSy0V0/QkASKZ0PSwpklQmyFdGi0AmUOwawPfvBWROCuGDKV4Z7RFUZDPao\n9wLsD5Au0DjxXMMF0P1KeaYjmkk7oayPZ4bVVWccUJrx1e0nQFP4W1RYBrY5\nBzephSKSSUIHpaCrjvC+fhGSnDOju+RLugarFpceHRM6x3o1o34HuXHLzUQc\n2euO4CDpgLQCf8X5FmZy4ncJuRGkR7LTJuUvgBZnqIvCfdA2T6mLZMSAAUIn\nF2cQxxCWSY2DLxd0TYL1kJvL8s6ieLwCWQeB3v7HqpxjMfWxfSRQ2lXCHqGp\nAMZFHdnsSQNAVGkcCmUu2n5Y/QulbN8TKcieiWgJtCypFME/DB4M86zCI/ga\ncCGBtNZy+a5BQ/EBGcOytZ3IKJXHhnRmDr7AGdS4UpV6wvkpRxnxIXGExnCb\nlM8vb8bEN0UqXJTyRC350xYt5KXiTJlCDyXZrKS1jhXe+tXYMqg804K9yrfl\nG6VXenrIYUWfd1mdtmliShE55dKIoM3ImAFm86QQVwFMVDLnWGVxTOu6yyoB\ncjY3e9ZNMOoxUPMc/8z3kqSt+xq9naMWQuZKTylh0d9fikoxmrmwWqEBc2gB\nGTpd\r\n=Kwlo\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"21a16f7778833c14b8f64f52b78d9206c4cfcffc","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-24T05:20:25.278Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.88","@balena/jellyfish-logger":"^3.0.139","@balena/jellyfish-metrics":"^1.0.403","@balena/jellyfish-environment":"^5.1.17"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^1.0.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.10-renovate-internal-patch-21a16f7778833c14b8f64f52b78d9206c4cfcffc_1637731405345_0.8244778993282906","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.10-renovate-internal-patch-c860f60136e792db45929f1afbd8d825fdd6d5d9":{"name":"@balena/jellyfish-core","version":"8.1.10-renovate-internal-patch-c860f60136e792db45929f1afbd8d825fdd6d5d9","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.10-renovate-internal-patch-c860f60136e792db45929f1afbd8d825fdd6d5d9","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0f237003f1419833e59b51ae72925fbb08a3b242","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.10-renovate-internal-patch-c860f60136e792db45929f1afbd8d825fdd6d5d9.tgz","fileCount":212,"integrity":"sha512-y5WaQiVauwjJizNETeM8v98Dd+0Oril4wp7XpPFKBignV6RGIWcQMUlNjkHLd7wMxe++bq/iHLQ6smU4TF78XQ==","signatures":[{"sig":"MEQCIAOLb0BNnhTNX6sgKdXVDWqxuL0IAw6WyfXuE1nxuey+AiAyjd2QgacGu5nj+NnLaTMKmaWVg5ot9J/Z9syli+ZmDQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":777568,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhndWvCRA9TVsSAnZWagAAKFAP/1y4qcynLz6JQzIcWrki\nz7wgV/c9ncpF6smZZwPkl/UnegVYZERwc4/RiBzeb22fzRvZnfuS3JmVuAZL\nEzPjVsEDQ+glofXsnq8fPWhDG+yQqeCZPDceH8wbtfr2KY4zLZ+W+h11Cz8e\nKgMt8aoC78bhNFAVjWWnUy5QbChmgvsif3p5jqhJx2n8wsLdcWLu80lEmdAp\n11w4AjQgaa82/55JZbWKW/ISqwylPQRjbYaszLuurcfvtIFubAia16Gd1j5Y\nkq3jJ+TWJAw0yULFOCeVLgoSABIC88A/ynEYOevOr3lTMvFamuA+Cy7cjQv1\n7Y8i4KP975YaIFbqrtfRRIsgQ6MjuxCu16adwTptHivMcP595ZkBok+KcI3i\n1Uh/HRVOAoKWjZGFMgHezajgXtDp49XmcfsVtQMJR1Js85XYL9xiaOONWPwI\ndLRegUSq1YhCNaj0xck3BiycWDUY0EVdGqxs4RW/LKnhZ2MPbHjTu/S0GIh9\neaafwbP9FAl0RdwSp0YnDRL3pWrtUYQd7YSGC9aBk6poGwDIh7+uXhWq0+OS\nbyO+OQ5rodIpMDlKwZJLAfJIu/V9Uz1gZsxjeTxTUuxWP5H+nq9tHmGExncV\ng2SNUUL6xYQnUSu4DvUb5dibnxaKB4rIfcwwGVfoD4lBx6PttY8RPcySiqlL\n+OkQ\r\n=k+8a\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c860f60136e792db45929f1afbd8d825fdd6d5d9","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-24T05:59:19.874Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.88","@balena/jellyfish-logger":"^3.0.139","@balena/jellyfish-metrics":"^1.0.404","@balena/jellyfish-environment":"^5.1.18"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^1.0.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.10-renovate-internal-patch-c860f60136e792db45929f1afbd8d825fdd6d5d9_1637733807143_0.4914235995203129","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.10-renovate-internal-patch-4a99fed01d81bc1c557eb3a5ae9fa1c4fab85ba8":{"name":"@balena/jellyfish-core","version":"8.1.10-renovate-internal-patch-4a99fed01d81bc1c557eb3a5ae9fa1c4fab85ba8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.10-renovate-internal-patch-4a99fed01d81bc1c557eb3a5ae9fa1c4fab85ba8","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4d685e8566d50cada8cbf8ae519b08fc43c458f8","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.10-renovate-internal-patch-4a99fed01d81bc1c557eb3a5ae9fa1c4fab85ba8.tgz","fileCount":212,"integrity":"sha512-ednerOjeGaQ2ZhSc81cPbAt34WWchomRd/Fw/kiqH9nsmc4w4ejfw5Ywe6Ni4ixHOF9Rj0kM3JLpotjqyNw/YA==","signatures":[{"sig":"MEUCIHXLGyHM81Yow3pJbSIfs575i0/2pElWbr9U09yJh6SYAiEAoAfnBgnMtt8Qp1YbZG83xf2N/heOd7+Iy4/hoLQbnNE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":777568,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhngRpCRA9TVsSAnZWagAAbBQP/RHNHblBvEeoHHY5nT4t\nSPAPIYt8ID0dQh4aMcpEn4jvPrItFJS0q287X2X3TpeX5mMuO+JHmZ07xCll\n3tlDU7ryFtaaHRyocwIx9zPuG2CTpon92nEbPbcD/Y6UD15qlyOFFUX+ImNt\nYmWfDTLSu4NkCGRIXnDmO1jcipLvNc4AikOhus/+U7WlH+KRapsLMvcRh2Ho\nWG1DL6QZgYUVWixhIZaWQvlpWJiYGMnIKF/8sMjJuCPCsmSq+oTW9dXC/uO2\nnzoWSrJC2JSzWbtqNfyLwSMnDti0w/csvWvpXOHv/rx8wYBj9FeKAajYUe14\nv0c4ZIcC7Z1V4liCfR+p+KITMo19YRZL7XKkTFY5ZnHuXnpJQbFWQSNxDHT+\nE4SSo1wPQ4nTJf6noKKihHubE+OJatBV3WJYIfcv2FkpSWgDtyuNp4x40fhh\ny7x5t/+dshBGqVDrj4puw2/SJTEFGjHXS7MVgUMX63dPkIksHXXln+OJksYq\ns9N2/pXnR0gGU+XodMvVxJoJXSVbSFzORJ6gxk+JGNVigvPkoCMnadaUpv2G\nPuyWlIR+harOoQlKcP39RshdgVr9NHloaYUKTBEae3TJwm6gDuuiBL2Oh8LI\nrjcj02pkpnQyGkWSH9BwNgAu1TnYc4xhNPH+9c6e0pBO3OWT94mKRJCFFNWR\nVZhj\r\n=0VNx\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4a99fed01d81bc1c557eb3a5ae9fa1c4fab85ba8","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-24T09:19:20.113Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.89","@balena/jellyfish-logger":"^3.0.141","@balena/jellyfish-metrics":"^1.0.406","@balena/jellyfish-environment":"^5.1.18"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^1.0.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.10-renovate-internal-patch-4a99fed01d81bc1c557eb3a5ae9fa1c4fab85ba8_1637745769557_0.5030661989252256","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.10-renovate-internal-patch-4b1e719ef8d44f3add40be461aa6ffb7b5acaa0a":{"name":"@balena/jellyfish-core","version":"8.1.10-renovate-internal-patch-4b1e719ef8d44f3add40be461aa6ffb7b5acaa0a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.10-renovate-internal-patch-4b1e719ef8d44f3add40be461aa6ffb7b5acaa0a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b58d25404e1ccb4f1e1c5dea1411f1b3975fec23","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.10-renovate-internal-patch-4b1e719ef8d44f3add40be461aa6ffb7b5acaa0a.tgz","fileCount":212,"integrity":"sha512-jFNUTcdopM37XXKEMpvkLwrtPSy/WLqUNc8wKDQILGTma8pjTS8lTaMyGbgekkXo0wrTI9VNo5F9Do87fzu6uw==","signatures":[{"sig":"MEUCIQDE123QY7bHoApbIGT0hOcAHxmQgjnUrQCFnlo+Ir+DngIgEfuAl4Woyx23AidyG5c9AJ41KoC6hB/XEHFZjW4mMcw=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":777568,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhngXkCRA9TVsSAnZWagAA/9YQAJdn6RbGq8U8wzae18cD\ncIzz71qj2tH8DyS1XzI0UTOTI5DREHQWjL46Eso52VDDFqvXHQ5wNfE/x7Hl\nBWtMjxSGF47hfYN2NX0BmqlJkFmwUGZg/3VzOJ82poqknX7CjnTQDu1fqCBh\nZidYVAB4UgRfX19GG8RgtxFEIavo7UpebwlvoLc9omhsMDFQB8VK5SmCjiKt\n/S0mZdX+ooU7pzEnvB56G/HNZJmln8RfOqNu07okXjAIgnPMwRiw1Isie9tU\nusToWsiwKzkyXhuIulFmAoq2DA92nmxWcZO1xSDe22h5YOqcpbcBqum8NDQB\nRfW49YVA1JholvI6Jx+rTLpigHh5tzZK+L4j3w/7r6LAVlvQ11GpM3/t+1Ya\n9SxOGcoQ6lWeT/OEOAF6+pX5B/vH2P+NXqNX6sqC7gPPDsRQFh9ygDuYUGvy\n2tR19anlqovCiQs0OQioLbRMX+1ybJORnVXtdvIc7SYnjuZYQQt59ya1gYVo\nw//b0UizdTXztn2vjOMVdneLOyUbp5fVbRqHLPXsNV8pqr14rRkIfGqP8aEI\ndk/5NXa6Bx2UIngoexyP2vqQiKYjJz24PsQ7Wfg3Eh+IDDz06xARsUEYaJn/\nPuk8NohC3/w7H1uL3ADVrC5AD4BU1BLvrDdFAdX//gprgyjFibGWRzZ4VcRo\nlMmg\r\n=Uuc5\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4b1e719ef8d44f3add40be461aa6ffb7b5acaa0a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-24T09:25:33.101Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.89","@balena/jellyfish-logger":"^3.0.141","@balena/jellyfish-metrics":"^1.0.406","@balena/jellyfish-environment":"^5.1.19"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^1.0.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.10-renovate-internal-patch-4b1e719ef8d44f3add40be461aa6ffb7b5acaa0a_1637746148430_0.1711112322769548","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.10":{"name":"@balena/jellyfish-core","version":"8.1.10","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.10","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"caae0d4533775f3f6e33afdc00283ea2799be5f6","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.10.tgz","fileCount":212,"integrity":"sha512-HH9gvrm9ZNuQXgHXhealKvrNm1i5V4R4UJXGaCgPME5sFMIoK5ins5rFlv/pud+8q+s6k424WoAm4r6vFI9vsg==","signatures":[{"sig":"MEUCIH1uCOyUsgmGTxCEG+iOoJkHg4TcY8/UiuvhF4qjRI76AiEAorihGbooEUc5IEDe/PSSMNpk7fLbYDzWyfkcjDstzXA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":777503,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhnggDCRA9TVsSAnZWagAAiIkP/3CJrfUYm8aj1YX8zBjN\nTvDp4FodIWHVVtR8Pg4lk0F71Iy9SXuwLQN1idurwzfEFccH/Bq5L8Rd6/Pw\nGbzOPStIEBxCPA6BU3M3KE/qilNxZlsKtguJlHEEg5qYMIAfq/T7auusKrKq\nVFigRbp86zyFbhOM/Zjoa7wzwKqRoACpLo3bgtKNz4FxWWbnys1d7/OP49aP\n21793AJJQeSJwA64foVwtIOi/QpOHyyLtxUubTe7gUmfmb5Btv0ATVq0pRSS\nBxy5+K5P35Rb9EsY4wu5UgcHMQAEf1ujI9ll8UYRm4x8A3uMED8y7DREmyV9\nKmiygB1/xJHTFxhDcLKBlnsqUQQetSqUtLP7KO3BdVkFP8aEATJU5Gh176ZN\n/MPbYkWQNF8SsrKFrNlq6zjVy7WLb83hbIQnSa5CIrY/jMqnF8EVmT7kmEfr\nY4WhSzBNCGzAsu1Ewv9qvXAEJu2sp2cw1DFoxHp/NRjVl1oQxP/utdzkW+Iq\n0KI1ABj/kxXjuqCAOJZQdDPOXUTBtojkRVRzD+C1CHlXDjuZKrztq4KHZPeM\nmD5utqJLONK31xPvhmjbXH4wWJ06+aGzo75gFFWql75y6i0Eu1yqIzvrC1fv\nsNHIwPWzHgzyolmeTZiVvWBiXIllktQrYT3fsDnaETiJ51Uyg8fyJOpswWwp\nlPn8\r\n=ku4V\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"5fb6f00f3a9c64a9b2abeafdac7fdc7d113544b9","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-24T09:35:07.213Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.89","@balena/jellyfish-logger":"^3.0.141","@balena/jellyfish-metrics":"^1.0.406","@balena/jellyfish-environment":"^5.1.19"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^1.0.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.10_1637746691634_0.43886098089024306","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.11-renovate-major-json-schema-org-d1cac17cfba7a27e0f0566623b7dc51cc3e8aad1":{"name":"@balena/jellyfish-core","version":"8.1.11-renovate-major-json-schema-org-d1cac17cfba7a27e0f0566623b7dc51cc3e8aad1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.11-renovate-major-json-schema-org-d1cac17cfba7a27e0f0566623b7dc51cc3e8aad1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2f14ca5c045222545a9ca2fd9340d0878f440b90","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.11-renovate-major-json-schema-org-d1cac17cfba7a27e0f0566623b7dc51cc3e8aad1.tgz","fileCount":212,"integrity":"sha512-mjTJd2OuNyRsLgCCfnez2h9tCz9tTdREseed1u9HIMiSaO6kcT2dIumGdhER9vACHC/O/YHOM/oUHDEW03SXGQ==","signatures":[{"sig":"MEYCIQC4qjhUjxIlcNhhGt789OOuzmAyIqgnoLPCYyAdgJRNXAIhAOuPyrc4GmWn3u2qICdGBBDwFvaHWx9V/TLEvpqLOWx6","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":777671,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhngjLCRA9TVsSAnZWagAAFn0QAIPk69hkZCFhCCIAaDn3\nGIREDCNf5fLL30JfVGB/2onKG1EUkxbOk1xyf5Wo6olhLDPrb0yu6wGFVY6f\neje9A5rEJjTh0w1A2SjTad4WiQav3jdNUzrwU+tFjS3mmXOmgI3Dng57tJqW\nRDOZL+yFO1kc4LgS7G/fi1HZitnlDGrOrgFhMWR19A8d5r9p3hzrs7Hyg4pH\nq7cKAnSa1Y/e4TBdTmm89JquRUMs0i39gSRwdWGitTvcc97SuZ3hoUnsNdUi\nlRx+hxZssTb1fMscsCuBbt/dEV3aZcfvBNDjy0aBhDgy9El6wKpiivS9sJx7\nXRjo7Sgzyjiat5zOX4eyNOphwhbfdielWpzKyltT+e9nYXX/jXTBzWbofsQY\nJlrUpRWnf+HhYP7TgE/li4+siuBZUeHSormHIGRyBXUCpjRrqcftkvD8aDRS\n57F8ZjMIbvwIgi6vmAmhAJ4It8ryg9Q1LHY3nCMHg2bKl+hL2iaXw+8ZkSP0\nhpNK8CleYzGXo4cx6Bwvu7oNrCfNfCXWi7tBWVOg3neVNB4wkdLp9TyfKy8p\nUnr9Xo0fclqflWv/8hZuibsdFHTjb5em2vsWhDNLrWdfNC28ypheLEA9dfPe\n3qs0VdRhAV0HWNNr28Ebn4WWZ1lR9v6iO12rRmaUR5cmYQBRM13hGD2kGKeS\nDSKd\r\n=paJ/\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d1cac17cfba7a27e0f0566623b7dc51cc3e8aad1","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-24T09:38:00.404Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.89","@balena/jellyfish-logger":"^3.0.141","@balena/jellyfish-metrics":"^1.0.406","@balena/jellyfish-environment":"^5.1.19"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^1.0.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.11-renovate-major-json-schema-org-d1cac17cfba7a27e0f0566623b7dc51cc3e8aad1_1637746891014_0.4367968580006272","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.11-renovate-internal-patch-d0a08a0ca69efddb88b1980b8865540b5a3dc2d4":{"name":"@balena/jellyfish-core","version":"8.1.11-renovate-internal-patch-d0a08a0ca69efddb88b1980b8865540b5a3dc2d4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.11-renovate-internal-patch-d0a08a0ca69efddb88b1980b8865540b5a3dc2d4","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"bce07b5767fe7e2812e6f5897f3d2cab9420e821","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.11-renovate-internal-patch-d0a08a0ca69efddb88b1980b8865540b5a3dc2d4.tgz","fileCount":212,"integrity":"sha512-59AHKOmaqtfMjIw6Ee3D/qcVe9dbdsZceW/DXphzWXCvBSOKwKi+Itvz5eJ8hke1QhESpytD4lWjJWJ31oXrjQ==","signatures":[{"sig":"MEQCIErwDdoQ/SrxSwZdzXF0uSnsEIraowD7qeI5Vh1Gz0W9AiAK4KqtcmjlhD4rXpL6Agcm7MPyP7WhKfYokh8E7Epm+g==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":777673,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhnglBCRA9TVsSAnZWagAAw+sP/3sFzwmQqqJeLR1dDcoY\nhyuuakplyjMGqio6wZ2oTnqZ/vk1IPz4TF6VSRzBduq3lTv5c+UguQxewfub\n5ygx+Vb8NKnCtdTKGQ3K8EPpSZt0tQZg9RE+wB7ooq9AorM/dVUhbBCUAes+\nMQpwJuMcxMf3ijlqMKMspc6HRpFAOGwPIhiRbLR8OlQESqrMsaASVzKgbAuz\npt96bROTX8pMKg3n/rI+/yka+zd4JyQqhY7/J8FpJarNyGYCndFwj3eHArvQ\n+r/emudd4OtzTpuvjpBTmfiWYS7rB3x4UPM3puhQGUmnlbvo2CiBYy67bjhB\nkkbgmxIRkNvu87ETgZItEAyrDFA+8K3556faiSbkesb1zsGcF7emffMibZGG\nTXPSN5wS0HXoDd8fcWuiwcnJ5iRsVvEOisdOlQTjrpW1ZyKGsq0kBbZ9L8oq\nkiplrQYWX3KrpFlu9jvQEFvo0Etuf77irt0OaSs6eolQx85KOgOh8QOqd91R\nCD/Yh2Q+xisTPwS63bpTZiFREocUHlWqvfTcK8z2l7fBFlHKi2rd3FwenRPv\nHR9OfAI3i0wb50pKC1achquiEhY7Omqgu8xG9m4RfICMUzvguzfZnmhtzTWz\nRO/8YINYl594NxZqDmajkbmk3gM7kdITx1U2pT1IP7QlQ7qBE+BAyQFxoGb/\n5zMI\r\n=bEs7\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d0a08a0ca69efddb88b1980b8865540b5a3dc2d4","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-24T09:39:29.410Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.89","@balena/jellyfish-logger":"^3.0.141","@balena/jellyfish-metrics":"^1.0.407","@balena/jellyfish-environment":"^5.1.19"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^1.0.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.11-renovate-internal-patch-d0a08a0ca69efddb88b1980b8865540b5a3dc2d4_1637747009836_0.0975942342650129","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.11":{"name":"@balena/jellyfish-core","version":"8.1.11","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.11","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"50a5e76297c472eedb67b7a310bee9c7bd416150","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.11.tgz","fileCount":212,"integrity":"sha512-GBxp2uwRXsFnbaEpdWNdVjklxG/0aBMej9eaM6Roym7hDZvGcXPU9MoJzR3dJeScONljkUQnpqYSSM9owM9pQA==","signatures":[{"sig":"MEQCIGI9rEznVTu/SZ5COiHKNTsbQ2qWDnlysB9Ed+e0/k07AiAZaW6O/XqW0sU8ETdgIFvRumTyCg3uDIjfu1Q0+E/vYQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":777608,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhngqlCRA9TVsSAnZWagAA460QAKLGT/ezgZnkivDmvhcq\ngaKqfmhPPfeEC0PZ3DzFLqo3y3o9q5XUcaG6gUA8ga0bBLriYOH68yzSzkwR\nzHHWde8N1mSd+JCnJ29RmRWkLprCzAQVSQMdxTXYq0GQizcjehsk9/jQiiFj\n7f2TJWExcH6eK8OM5W4AFb3ghDXAW5BB2+6x6BeSpHQ+5w+3Ve7J+5GEqhIh\nkm6YjlVsu6nlDfISpX62grc/YnSY396OvyKtuG8PISKpe7FRcuCaJC2o/KRp\nAmo8Q5+/5UrM+on33uAsRzsbvP+PjNeV1sraTlGf4/cqNe9x4COc1vjvBFO9\nG1blrLqvRIdwqS2W+3BpGv9YlU9m2RY8R2l9JByHphgu5A3odS3AjOAHg0hb\n9LVuR2SBvdlKlf5e2G8iCq6+EHCVlYSyOw7HMPlPPmVwxteWFep9K5sCgqkk\nrWdxgXxBSqoEm4TcEbvMOVl4OffewAkxheCy+BrHxi5VpZGDtnDhaYEXA1GK\nKbgMCcD2+p55MBMSx6nfOH8u5dpDcD9ICWldfqFns+TiqvutGSlHwLEwujCI\nO2xD5LOUQyPyBwx7YkgeGTe4jmUG6xQjr+EyghySA0M2gORonRFaJrQ7Sbta\nIxaW3ekbdvuoDlDj46+XPYK9Z3xGWboaAtr6VddJ0QvYsGXDhXZcZBHr+SuQ\nCtPd\r\n=npKy\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"aaf762412444edd9874c8c3e9300cb868d49ddc7","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-24T09:47:24.495Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.89","@balena/jellyfish-logger":"^3.0.141","@balena/jellyfish-metrics":"^1.0.407","@balena/jellyfish-environment":"^5.1.19"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^1.0.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.11_1637747364898_0.05397645103178772","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.12-renovate-typescript-4-x-7b5191d2ed666e9c5cffae3a1e374d711394eb7f":{"name":"@balena/jellyfish-core","version":"8.1.12-renovate-typescript-4-x-7b5191d2ed666e9c5cffae3a1e374d711394eb7f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.12-renovate-typescript-4-x-7b5191d2ed666e9c5cffae3a1e374d711394eb7f","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6311e8098d4a0c2864bdd6b5d316c10969fc1b3f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.12-renovate-typescript-4-x-7b5191d2ed666e9c5cffae3a1e374d711394eb7f.tgz","fileCount":212,"integrity":"sha512-pWnnNZFGnljUjmH/0W5hOh4wbWQYS2uADTckqMyq0tEleitf1AC+or8p5Qh+z/W/Nl8ys7b/SQE5EXUzYDzRGg==","signatures":[{"sig":"MEUCIFmyLdty//djhwuEyeklYxjuuYnU8QVbChaF0THV0roBAiEAlP1UXBnG48VSMCa2ieQkn3Vi1bgjnoFdW5dzEHyV7z4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":777761,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhngtkCRA9TVsSAnZWagAA1rwP/2dU3UioE/tj9Vq63e0I\nubYBrq+OTu1HBXheOeuF8ykTdCqpaoX5foIPC7cz0zS99nMKkyWzR06CbbSy\nsRoYiBtghARSDi3g0d4mkGxeKOR3hEmz658/2Uyf0t7Ubbzk/9JqqXNPP+6U\n/wIGY5NteoGLluBxAwupp0qjTrqVGOd9tdbtWvnwFuq4hu6867Ina7ECXXEE\nrF2l4nuXATVVga3gi8mNeifYRP6sKBKack0l5P/MCqJUvvLLo2UmpYOQBgq2\nAVs7gWYocllfbtF6q1cAXSrCDoJbw/CEQikmx0bp3ZUREpECEo9PUQFLsO5O\nbO5Fd92pGhIau/M22DE862JDzGMVchdFA0tLN5JeYDwc29rc/6inEHDHrIYV\newjadkRhXkz9LXvedL0KEvA5BblfG2HZAnVb71JhmbOuZIU0kRiPWjTGwt1F\n6XABL7jzeM1mHIME7Gl0NOBkaOfPaSmCl7RhtmaJC40EtttEbp+HEScQ227v\nngDNQ6Ks7fsZtRAmD2EBZrgIqU88GJEZsnMZB1XnD8XSxb/bxezB01Ul/oVK\ntofvEu3jMEdBWHM4xqz0Pu665fbO8OECAFa1L0rP2aILYLBO6OsmJfXL3IG9\nUaURtAAYqqKRRwH9iO/pVFgUXKwPZSul1oORoONKpm8aM+tXJncDFJhf5uAL\n5eoV\r\n=YKrq\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7b5191d2ed666e9c5cffae3a1e374d711394eb7f","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-24T09:49:56.145Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.89","@balena/jellyfish-logger":"^3.0.141","@balena/jellyfish-metrics":"^1.0.407","@balena/jellyfish-environment":"^5.1.19"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.5.2","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^1.0.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.12-renovate-typescript-4-x-7b5191d2ed666e9c5cffae3a1e374d711394eb7f_1637747556750_0.2837952795167482","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.12-renovate-major-json-schema-org-b50fc1a9ecd3f36bbe2a9b4ccb7837308a832855":{"name":"@balena/jellyfish-core","version":"8.1.12-renovate-major-json-schema-org-b50fc1a9ecd3f36bbe2a9b4ccb7837308a832855","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.12-renovate-major-json-schema-org-b50fc1a9ecd3f36bbe2a9b4ccb7837308a832855","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b48c4cab1ba2f006084e661f2a623b1743d636ab","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.12-renovate-major-json-schema-org-b50fc1a9ecd3f36bbe2a9b4ccb7837308a832855.tgz","fileCount":212,"integrity":"sha512-OHl+e0dzgsRfUk1m1Jb0iV2rU2VFY0UcnT1VHaGZJoXt5uKFz4ztFhYvAyZhKo/Ia5Qbo9401N8s4qcQ9EYaAA==","signatures":[{"sig":"MEQCIHnrTJ/lHy/ajdrsEqrpycJuhD4pQu08oIBW32Q3PO1ZAiAkishRM2iKXMVsCdso8SEhhT9Jb+i2N3agGVs7gjfYWw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":777776,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhnguVCRA9TVsSAnZWagAAzyIP/1qUeqWJ0h1vtMbtrH3x\nV1SLkamPr6PIEtyEsKn9NhbYF82oUkRi7FVMVhxLBFuqJ4hXNIW/aGLdk+PU\nr5gKvRl201iZu6K6YSrV5rK59UNjMbvYwB6BGi6sEcE+RAi4Dui9VbVesPyH\nOGDanRvnIyZPlq3Y70KGJKPNbVNFWrnDT4IBKoBODLp4+EFRYbTp3xwzg3X7\nesTHJEuOk9/vJv7vqWNQz1qx7c3A/AeQpYp/ILgkUTcN6mG0uyMbRkX/YyjU\nCmVmCztkr+EBBLfFIjVw0/udiASbr/LYPTr9XL4y9uVTbp3k3mPWQuwy8jxy\nL0gDl1akrqupg6Nz30YxFa0pA30k2ma4BRofzz1pT6co0qdKXP8VZ1FIZONa\nWj0pYlyZyqwrMew4Zdw8iRwePQjByBLpesBrXpBLDJ5BWYnHQgWSzlStSOYI\n8S1CD6tHyFFztU8ZKMoun791N21jFESliVVD34VvxqhR3JBQ0pcLx0BXNwyz\nQgTVzAZfxNOTUsDsOVxCgs0tVcKBRxiZc1Huf3+CK70myLYZtJ5s2M+mnSr7\ndcwSkrQUcVyjCFEw2sNwGo0aHzCK9+UzjIOx76m7WecWK921WpRw3H5mUhi/\njWRIvQqkm6FPQXVE5DRQVrhYDgrZm6MeZHCqMQBzJWz/lTSrwvvo2iENfp1A\nJ8aA\r\n=hIIn\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b50fc1a9ecd3f36bbe2a9b4ccb7837308a832855","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-24T09:49:33.596Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.89","@balena/jellyfish-logger":"^3.0.141","@balena/jellyfish-metrics":"^1.0.407","@balena/jellyfish-environment":"^5.1.19"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^1.0.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.12-renovate-major-json-schema-org-b50fc1a9ecd3f36bbe2a9b4ccb7837308a832855_1637747604821_0.15438609266365533","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.12-renovate-internal-patch-3434db0a3733560b7a9765bcb9d718b623003c0c":{"name":"@balena/jellyfish-core","version":"8.1.12-renovate-internal-patch-3434db0a3733560b7a9765bcb9d718b623003c0c","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.12-renovate-internal-patch-3434db0a3733560b7a9765bcb9d718b623003c0c","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"18af5bba9e65d204dc557c17361acb8f73253cd9","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.12-renovate-internal-patch-3434db0a3733560b7a9765bcb9d718b623003c0c.tgz","fileCount":212,"integrity":"sha512-iU/yvw1OXYlKzgkjCJrTK077FcOVzPzoBgBQC9wV95yZc1HMtGnPHf/QFTlM8duIfEKWNxM3X1J9kxNG8kwE2g==","signatures":[{"sig":"MEUCIGoB21Z+OH22Onl5j9bngo2Qu8R4eIXtfxYYcNC8qV9mAiEA4pjFxOk6X9FoPFnWwlQwlFfsKk8vpL9mPBFset55vDM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":777776,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhnjh4CRA9TVsSAnZWagAA+SQP+wR9Ps/SruWOa/YG20HD\n6DPbeQCoFrPcIRpIDuEa4DI/yAMgt5QLhGX/BdsU0kJBgDJHuS705oZRUKi/\nRBTpHuqdUUhv6pxIxgktJr4b0EDFaMHCfF0CSi50+FRSOJXeyTCKdUWfh/JR\nUL10T9Wp3mM11SDfsORLA/Vxp0Tpqf+drimEPUWgFKks0AqJ54/2mgip40Dz\n89Ufpcwl1bXS5XPNXiE6u8sK5CaXbpU1MAGUrspFDQh1DzM9HSX5Xryy1khv\nXSFmuCu4O1CgtK4oEfEMYBMAOqA6Gtt2rQJbiW/sP28uUdIwiPD8GPBc5xnH\niV6aWDqTtnaxtZFVL5+F0ZQ5qx2oi4FmCi2AmvXOiAhDoHTe/Q+dDel4ot2F\n9tDBWTFvvfCVT9KqYKpFa1Emj4bqbwMm8ncA8vX1wdegVWeHSHP4mXX2kulA\nxERTmgNR6hqqG1FKA03GXOST04Mx+2mll1Yhjvm35vSaRLhUCWmipQ2vmz2J\nDlvObyDP6HgkMoOr/JzHhep0+NoCbCp3DEmWOCvt8we++ZEHlDozXg53BLKD\nkSj2mY3fDX+XsIstrxazHyxmGX6NA6bZN7Ze3p/fhyuhAeyywUILtDh/CePi\nz+reoaU88rSRNflB1HHtdfkpsQtjYizhPwTPzmmOJsFA1t8JMAB5PJBQphgA\niwUg\r\n=6RDk\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"3434db0a3733560b7a9765bcb9d718b623003c0c","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-24T13:01:06.716Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.90","@balena/jellyfish-logger":"^3.0.141","@balena/jellyfish-metrics":"^1.0.407","@balena/jellyfish-environment":"^5.1.19"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^1.0.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.12-renovate-internal-patch-3434db0a3733560b7a9765bcb9d718b623003c0c_1637759096668_0.38111261985700495","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.12-renovate-internal-patch-ca8fcc618a13133acf73b37ddc42a42892fd8444":{"name":"@balena/jellyfish-core","version":"8.1.12-renovate-internal-patch-ca8fcc618a13133acf73b37ddc42a42892fd8444","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.12-renovate-internal-patch-ca8fcc618a13133acf73b37ddc42a42892fd8444","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b9190faf4044df9b87c0750b4cadba817311bea2","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.12-renovate-internal-patch-ca8fcc618a13133acf73b37ddc42a42892fd8444.tgz","fileCount":212,"integrity":"sha512-f7SgUVxESNU5PPKex3iKW5dqFVHbcDZrrFmYJIhKYEQAa6hmanT8zE8ushtZtrQ3muv9IaSV6wbSU37qg/1K/Q==","signatures":[{"sig":"MEUCIGYfNtFGmKkyD0nDDZy0RZBn4asN2lnzrZMV/HXgIV2LAiEAmogl3lrnRLcHad6el0DUiW4eYRUH9LbFljdJ1CGtma4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":777744,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhnjmKCRA9TVsSAnZWagAAigIQAJXzUGBBq5APn4a80GEi\njfzVdaSi1O8oqjLGTrfBR77Q+6HIi+kB2YH6pYe6bb7Gx+CI7gqpSRaSf9o5\noIl+kZeHuTj1naJc7iuUBN6f4QT0yXYgzsvEmPIv2WcU53b5lno+KjuK89Yt\nbPC6C0KQBVLIa6OzUhRXin2az3ZZCRhcEIFcQfoC8DEz/UjhV32BYAv+7ILH\n+32CzBwnDpx7fMXYdIEabJzzfrdTudzg87B4CfFTwZbuM54UJh+Nh6kl5Yu5\n//sDeDYQowZyf3V/H+KGYgv5oO9g72sEa+Qn6iax6nBjA8kmvilmCa3LNq0/\n6ywNXOgiXVbAzxaGMnu67Us0RAkpCEsg126SCjMA4nd7T/ByikiabUq4Vr6S\nQqJxO7KSVKK+7lgpXS6XHvWaqaDD1xnIH6OQIxpNWlMcuWc4rhRNNrorlzGE\n5xDUftW5v7RPii2PqHwpvNqUrMIC9q+RO9aCOqsGB9AA4M41w8yqyYphh9BJ\nJwXWjLVmInux65433gQtPFT/7AyxoeNDyDhbBk/bJeZV0zbyz3Rmh4PhEDen\nXdFhelqk0VYDjAUVqziS1HLsCByz7A5keUR3KUbPIiSUCoJvq2h9GSlsbHuv\nWEr9zutAm1iAMQlPRURGyi34b/3lnV6+sCzn07IDyBnmGT8FCYYTTLlJvaZe\n4RK7\r\n=ZaaN\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ca8fcc618a13133acf73b37ddc42a42892fd8444","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-24T13:06:30.787Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.90","@balena/jellyfish-logger":"^3.0.142","@balena/jellyfish-metrics":"^1.0.407","@balena/jellyfish-environment":"^5.1.19"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^1.0.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.12-renovate-internal-patch-ca8fcc618a13133acf73b37ddc42a42892fd8444_1637759369919_0.3655084385062979","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.12-renovate-internal-patch-71bfb9921028e8a5d85fe4f5d28389986af2e8c6":{"name":"@balena/jellyfish-core","version":"8.1.12-renovate-internal-patch-71bfb9921028e8a5d85fe4f5d28389986af2e8c6","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.12-renovate-internal-patch-71bfb9921028e8a5d85fe4f5d28389986af2e8c6","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5367ecb27cab17e83aaa4466babf602e3d17d0ee","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.12-renovate-internal-patch-71bfb9921028e8a5d85fe4f5d28389986af2e8c6.tgz","fileCount":212,"integrity":"sha512-CaVtgTxL2Sq5E73JvAudwMKNalXmTpZsIsrvnR5owAcjA8kIYFdgbawU0T8N4GJ2P9pDT6XQmQc7celDSNxNIA==","signatures":[{"sig":"MEUCIDuwY84eGkQ5nrrfoJlqgv0qcnlSJS7Aku/qF/AxulRsAiEAxHPY60kRxUmhdV3SPGhOqJZ3X80gzYprg/JrTkv3Gd8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":777744,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhnjpuCRA9TVsSAnZWagAAHzwP/jzuz71r4kSxUkw6TS6D\nBzHiru7ByY//bLi56hr19TRg4lkyRo2MN0YnX/Xw6Zea+B9papRG7DeNW/CM\nqgHsinPWnVraTo8zIu4+fqAiWCHlPLvt9vawHLdOUV1IPknN7B21Ea1rKtVV\n1rShFW36nUTcCnooYFPs7zi/gXMBIr/3NdnJLaDCm6njfa9Jucxw8oLDclcs\n77okmX0x2ijrQgt+eZFO2Ii7UHEw8TPhVmOp89XG7ak83mgomkoSxJx+Hh05\nyiVucLHrarrJ8dDgtxj292NuWCJ/HVNLndyjZHxILwqwJwvenENdPy+iUGof\nSTpRXvXoPc7YvVYu4H+B4bKLa3WKEBchddpCAxYLzUaCSSRUXb6Si9AwUM0h\nBf1kU+3XweSR3Sb2yO6ReR7R+Yo6STwi0zJtUiHT7SSuh6KjMQdxHwP+esDd\nLuSSMAVYb/SstgRToiLHghbdi28ByRCU7CTOnAejy2lomtP+g8xVABuXqDLv\nkYDNzcTLUo6H2jZoXtXkyt68Lm5vwFKJWZrJ8LoRsJs0rLWrs3+Dc6r5Cxyw\nZA0lxq3Vgc02sfBa4Cwxu0eTqDL0Yp6TECh4FUA9fWCVXl5j6K8p1Rn3Eih/\nv6/uWMOOQ/0ocLZcmNO0OyNlSQk7zFXWfO/wh+z7H5nxsL6PnaXUoIG6+/Lm\n+rwW\r\n=9nOW\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"71bfb9921028e8a5d85fe4f5d28389986af2e8c6","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-24T13:09:41.589Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.90","@balena/jellyfish-logger":"^3.0.143","@balena/jellyfish-metrics":"^1.0.407","@balena/jellyfish-environment":"^5.1.19"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^1.0.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.12-renovate-internal-patch-71bfb9921028e8a5d85fe4f5d28389986af2e8c6_1637759598307_0.8778279758025944","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.12":{"name":"@balena/jellyfish-core","version":"8.1.12","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.12","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"98ba566985c3cbcfb028221265f532d7722bed29","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.12.tgz","fileCount":212,"integrity":"sha512-zxqoMOJG2lCNfka00gu/Qb0xptQYRheXHssM8FiXy91vYXJNaib0wKqfTQhrmwUYiGQh/spGT3pdBYH2ccVhrw==","signatures":[{"sig":"MEUCIQCQorJ/4ngMDq749Uxuo4B4hZ5/O94ajKEAGdnPeO4olwIgQUxwCU9VP4TfCVpLhk1naNOeK/yoyRzK+gSaOqBEl44=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":777679,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhnjwYCRA9TVsSAnZWagAAQZcP/jpNWXjgtaHBGrTzsSsi\nXN8n4k5OsUX0iSr1BrOqcby0+bCo79LcYDfpPJV4St5rU8JmKcQxiwAN7nHI\nWvFfzGgD99jNcdgKTU53ybTAlaEnnmrMY3tnOivxAcdAnIpFYdKb2axB6WIo\nhU4R5G9Fn4locWKD4jE2PKKK3s0Y8e8964f7NOtUD0tvyB8sD0JxmNXIFM3X\nYj5mpNhHi6PMJKOmZWu+1BoA5dIGVGPTlob/ev2F4kjyHi7tu348v93TnOD1\n4Ud5qn2Axg88p9dHT2S8abZCdJk8Y9NTcB5EBR7ucd8VecwNByyHL+DFSK3B\nh9XuHP1ToP99+ogopp5D9C2b+0XqVeK03yb2czPC8wHPbXxHN5pAlqy1TjX/\nxXvUwU94p4wFdIF2nsmYORKPVNyapkmJW2UopBC6jT085FXuU5HX4WFgu/23\n4BHQ5O9RNXbkAwXlcUw1VhNfr9j2AtQu7oqhJlUOTMiCDzJZf5Gq5FdzREF2\n8i57DOZE4ogNok2w6MlMtJHIq54oLKYzQRR58K3Sa7S7mLBqTrOvfj0lD26B\n8MFKZpBzz2AepiFrVKZT8tjy6Pj7tnnwqajefwiGaDwakrz7uviFRj4EdClc\nDXQVHLOlVWyjEQ+tcZemaY/a43OoROVy9s/VB5pS5MRmAMCr1ROWXU/vwluf\nHE65\r\n=GhWo\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7f227346f761132077b69e733181d87cd37f9408","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-24T13:18:22.447Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.90","@balena/jellyfish-logger":"^3.0.143","@balena/jellyfish-metrics":"^1.0.407","@balena/jellyfish-environment":"^5.1.19"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^1.0.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.12_1637760024189_0.6275133912270328","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.13-renovate-typescript-4-x-387bb19c06e9db531545f7082a73a39e7d349d33":{"name":"@balena/jellyfish-core","version":"8.1.13-renovate-typescript-4-x-387bb19c06e9db531545f7082a73a39e7d349d33","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.13-renovate-typescript-4-x-387bb19c06e9db531545f7082a73a39e7d349d33","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"29638445515436f46fcc161dccadb5e45ee1fc45","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.13-renovate-typescript-4-x-387bb19c06e9db531545f7082a73a39e7d349d33.tgz","fileCount":212,"integrity":"sha512-7rIYpnBYewIl58IOoU4uB5XDyEVMCmK46keuMv8wXhwphGQoBDt+/cpOABaUl0RG6SFj2/S/kKZ4frFyw2O2IA==","signatures":[{"sig":"MEQCIBTcgv1REKUmQOLAcG8GulbfhbNjSMLYvfyQimZfP5K8AiAk9SfMEY53cY8swG/elznf2FrxoKAYjJu6bqYmz3TrVA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":777832,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhnjzfCRA9TVsSAnZWagAAOmoQAKTkP0B039lEs4Umrg6t\n3Wla0dY96S4lSN3cshECUDEERk9UM7LGXvj/tsKHcvq6LqI1QohaLRWS5oVg\n87uU8aKsPjMsQTLh2FUzg27BWz3LsZSYQeXdTbg+xsFD8E4D3J9sJww7xiq0\n4UKHSC2TlHDCAzRlulPar5LbnSCWy7RhSNcvKFma300qvwTu0GvY/X45/yq0\nH7sYkgguRddYSSjbNWOuzt2hkqeh4gKe5oImcH7QxLuPfwdxmMUZ+HemFNU3\nAdg1ksPcbNFpmFZODYVp5SmAwREyTiv7vF08kjFdaYxprFeoNzDUM75ZItv+\nPRb6cRTCLHbrWGY/HDoZc8Gg0GTUAlILwE0U6y+arksAEMPimRQyp8A6Dxy3\nwPBa+EnKHEUEboz6Q5OHTJe3krAuNNuFfbVEeogj4mBYOavr2Wl05Xo0Jpdh\n22AzzZEGpFRSDocyTIoQH7QxDPewoiZK1ZFturv0Hfspo/0V+tg3USFrqnBK\nOVX5k7wFcBWFpWdqA6M7yvQydFe9RhOZ5BfW+UVz3AmGqyWWd3tifw7XO/2x\nCY3AXn4pw22RhvlhL8yOWUEGaRFvIDG2VMGxLZcnb2RJaudBeZjmMS0cW65Z\nWR2MBfDhEV/Rke72KXCct/K+u0H4M2oUfPvWyIw2KKUL40XuIblbbdE0j/9I\nROpl\r\n=0HRv\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"387bb19c06e9db531545f7082a73a39e7d349d33","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-24T13:20:31.770Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.90","@balena/jellyfish-logger":"^3.0.143","@balena/jellyfish-metrics":"^1.0.407","@balena/jellyfish-environment":"^5.1.19"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.5.2","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^1.0.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.13-renovate-typescript-4-x-387bb19c06e9db531545f7082a73a39e7d349d33_1637760223072_0.42873910143233784","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.13-renovate-major-json-schema-org-34c634bddf06d0a8dd88fe494af20b49b3d5c8d2":{"name":"@balena/jellyfish-core","version":"8.1.13-renovate-major-json-schema-org-34c634bddf06d0a8dd88fe494af20b49b3d5c8d2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.13-renovate-major-json-schema-org-34c634bddf06d0a8dd88fe494af20b49b3d5c8d2","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0ae08ff915a8a63664c6003fa7432c329081f95f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.13-renovate-major-json-schema-org-34c634bddf06d0a8dd88fe494af20b49b3d5c8d2.tgz","fileCount":212,"integrity":"sha512-x7wpOoV/4S4xx05QwzhkIoIsGXnsREXPLdId92+JHp27xpttAWotUrLS08hgYLc6EPPV8NHecFJW8lXlO/WMPw==","signatures":[{"sig":"MEYCIQCWj8nBcB6qjLOSEqsUecusRRgZRTY9jlBF2W/NKlXuSQIhANsQaIlcGJk24bDOuk4kfBw028HHmXoKZkZgzMNBwEYU","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":777847,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhnj0OCRA9TVsSAnZWagAAO5IP/jOk5Z0JHXOJPCjtYVMT\nE/P05qbxAV543Y4rklSv+5tOWf/am2W9EuhNWluLZkYIESgkEL15qeAZ6dFK\nxWjm6WHH7KYlnQG4g2rwciMxc7Je33GtUNVpIhbPqwq49W/x6U063q/6on23\nErLdibVDaZV9POkiTGC26qE1rzWaKtxifyCqhELhgCZIgFmE6drnGUKA+2Fh\nTh8a/A7bWDRacq9E8ust/8C+DXvEcQhxyj69R0Z4OnOO7+hMKGH7fdGh+wLc\nFDBv+/2Ef2/AX4BQg1wm5sZayCAv1ZEzNoxOSv7sbfec6SDoyqw8kRmLqCk1\n9QdA7iOzQOSAzx8PKCTZ22GW9rNqJLpeCgXwGMx4OBCwiVUw4yCFRorsqPnW\nf0EEeuyHw9SWqCBHJaKticSITuoRnKxssWl3uFYMSXtvEDdP2my6opHkXN6F\np6hVBRhL/26S9fbSvdKjU/2ODV3i3lux6mljO0YpwxLXG1zsKi9iR9j6IaxW\nMXicLXrKMnIEzhkQu7xCjJXMJhO+F/wqJ1wKMFDd9RafhrfG3oxzAy37MwQT\nolZK8BqEgbsYbUEz9R22B0lbkDRR6vbStlEkugFp+2wibCK6gcGVemlP5p0N\n/LlqvQBAOExfxANf9n9YksQIJN9ns2Lbl/Y9MLwZDeCCdNXvaZpa+7pHGajm\nVHTT\r\n=M1AW\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"34c634bddf06d0a8dd88fe494af20b49b3d5c8d2","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-24T13:21:01.835Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.90","@balena/jellyfish-logger":"^3.0.143","@balena/jellyfish-metrics":"^1.0.407","@balena/jellyfish-environment":"^5.1.19"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^1.0.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.13-renovate-major-json-schema-org-34c634bddf06d0a8dd88fe494af20b49b3d5c8d2_1637760270020_0.9522087101227377","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.13-renovate-internal-patch-47329d0a3a672b34046f533d912e2549d0461f0a":{"name":"@balena/jellyfish-core","version":"8.1.13-renovate-internal-patch-47329d0a3a672b34046f533d912e2549d0461f0a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.13-renovate-internal-patch-47329d0a3a672b34046f533d912e2549d0461f0a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b65a620f3b27e0c4020637f428a8139035651997","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.13-renovate-internal-patch-47329d0a3a672b34046f533d912e2549d0461f0a.tgz","fileCount":212,"integrity":"sha512-4cJaasDNlDknaCCSLKBMLvi+OxI1pBQ6KI9AvxhVvBmd3Kfbp7sASwFnkiJ/XOr5Yi+sgTm4zcwZKjTEGmGW0A==","signatures":[{"sig":"MEUCIDMX0OiR5lkKHzJ6bO9ZtPpB79rTBZ3veosQcMFjItzkAiEA6sDpG/2VMCWGPYZG8VmTgzfpVQrO0aK1+3/Liim4Pv8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":777849,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhnlXFCRA9TVsSAnZWagAAmgkP/1qvbZV3FrGDsog8mZ5H\nLlDdkcy498FFml8YKr9qB8z36T38NvDU2oVLPcQ8fU8PiHJTeMZDsKQxMx0x\nQZ14aYw0Jpe5JXmio91IMRbx7Qi9aGsrjOUPyq33dB0O5nQ+msOQiCqy9Q6t\nTaiYq5JvgQ937QaVeB4jDEstSI/iYcLpgjCLH4mmkChkdlZzuKA0+aFhKiFK\nhQZRxUcowgphy6p6bkBebxCmJkIDpHr36pq3h0PvURtdkrCryLCDAqpFz5+7\nXw0beB9eivyWtjy0O1ZBgDvm3VazCowEvNahUd+dshM93JKuyBN4dK8A5LBN\n8OtJ87gNuxN4vXeOxOOwYtfzHHr0HS7xs3sJOylQ1Bp0wVjsXv06mL9uAYQJ\npNdSFkEdrL8EbWTOA46NUrZaL4rw85FbLmWs9VYnc3U5G6fUb3j4h5SugVyl\nJCaKEaQTJPlOLAFcGP02D+nLmyHKOPR7dsW6LllSNqgHvsioHXrGIaMZ1b9f\nzKUHTNiJ4F89/yYf8M6fX5akqtdAAp9jUlD2PwSZCcClpvdNAayWbaYlIk8c\nkpntscZsfmtz2IzBWMgbCkljSc6rzGVqnje573WJdX5ym5rRNhbIXhZx/hie\ne5qqMNcMGS+WdKvPqafRO4KTGo4QPwJQmJ6GYpLkPgNqR4wplObOcB+SXQhg\nVb7M\r\n=7MNE\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"47329d0a3a672b34046f533d912e2549d0461f0a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-24T15:06:28.059Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.90","@balena/jellyfish-logger":"^3.0.143","@balena/jellyfish-metrics":"^1.0.408","@balena/jellyfish-environment":"^5.1.19"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^1.0.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.13-renovate-internal-patch-47329d0a3a672b34046f533d912e2549d0461f0a_1637766596861_0.002733622224017962","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.13":{"name":"@balena/jellyfish-core","version":"8.1.13","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.13","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"586d6fcc6183d3f058e4c57c58555f584eb37c85","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.13.tgz","fileCount":212,"integrity":"sha512-FH6sU6fYQKCPnK2FOkwTJ9m+PDVr0v/oMBv2wxToF+szRoCUD9c1L3+7uBfLgldHlT3pNXDk9YHxspHVGvncjQ==","signatures":[{"sig":"MEUCIBb+AKK9h8I0sm0e9cRzwzarBIm07WJrLj0MDDfVsHwSAiEA+N+6qa2fjkxKdHhag2mY1GlGcPFQRM9cMUNbZ1/DZrU=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":777784,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhnlcvCRA9TVsSAnZWagAA96sP/1sOq/1DmfzX25Sg/iUo\nCxog4w3aA2EDhjZFAOrFB6CL4xvIoCyKRk0MkKLgPdf3/cTGLnVeRq+UTFXc\npPVluPNMOiCqygrky9tWUw/yU1AiYr2PCJ+w/7W9gjZoURzoecM6G+zzh37O\nP/uK8oinbAZRaliP0TqNWEWNZPuOrtxL2L1IYTni9pXRnpYwWM2HwazP2K15\nzbVDVDIC9ic3qWmyVIeCFXf1CaUtugKzCyNtz+ZOmBIerjeUy0C4koZ8Rz7Y\n/kll2gpdbpbl4DgP8MrDE0CAlIi8LnrrIJepnUjccvIf4BIB9ex3yB2Asb7v\nPNmY0CWNYTfJ8byqZPradzryQYCCPBQ++DhL10Es4neVHUpB8SBjWzsvaX5c\nrjNCNyisJ6u8IsAcrTmy4OW/xz5Ti+5zYnjQwz+/yhbWejliZtRp6Mgg5MCP\n6SxxprJkVOqafJwsZuwvC0auaBVwSnl7tSsX1SFur7zwmYaHQ2aGHzEZfdbE\naV2Ig3bQCzwq+7Ddp/fX1ENbL3GaEaTjopaqZs9c8047vU+57bGjTI3vhgrd\nwVJaU2epzRIEkYxoQ8OLgK16KVDL2BgVjeGmg/UVz9yT1tXDrWZW7PMU97K6\ntdrtaT7I0C1dlXBRdk6Hu2LFkzQpdaX7ouH4PFW4Mj11Iy6dywcGN8mnv6jf\n+CNQ\r\n=3Pai\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"9378958ba6d771fae79cfb5da27a37d437da6f99","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-24T15:13:40.668Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.90","@balena/jellyfish-logger":"^3.0.143","@balena/jellyfish-metrics":"^1.0.408","@balena/jellyfish-environment":"^5.1.19"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^1.0.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.13_1637766959678_0.06152752158868835","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.14-renovate-major-json-schema-org-ec5ebee50d842c9f8381d628d06d307e3524f3f8":{"name":"@balena/jellyfish-core","version":"8.1.14-renovate-major-json-schema-org-ec5ebee50d842c9f8381d628d06d307e3524f3f8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.14-renovate-major-json-schema-org-ec5ebee50d842c9f8381d628d06d307e3524f3f8","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"db41da8d1d25ebc50fc551ae121232b43c69fa3d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.14-renovate-major-json-schema-org-ec5ebee50d842c9f8381d628d06d307e3524f3f8.tgz","fileCount":212,"integrity":"sha512-ripjr/lsJh+uEh8a9g0d+J87xFONqHf3IzDa46OKga3GW4F0PvOwO3lQ3tMBit3oYPaVmBukH5bfOfR3Vnx6Cg==","signatures":[{"sig":"MEUCIDnuIWBk++bsLMvr/K+rs3faEPMtEszlxmswhZwNz69IAiEApgkFle0EgMmD9prkuvhSCvG3TAJU36AiuFY5r/qwDw0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":777952,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhnlgKCRA9TVsSAnZWagAAcj4QAIJJIn93l7BMrONRLait\nYm6KQGuw+Hu3Wez8DVqFgRx//ZHEDUM3gSX8i+ABKKXBa4/JN7wQbwb5AGEX\nsf8nAl/dAvQL27oec10f9YFe5Qc9Z7u29wLhFE56g4PMBTBtqBWQ1A11p2hU\nt8fiVjPuT4k37HliFkTzusmqKHbhU/k3AK/EsdlxcAw8wC1iulioW4SK/PcS\nVtBHnjdUD66hcEZbbglANGcJyKf1cnpnBWY0HhU+JkzWQ7yKCqdfJrP0fO3T\nyMhbV9bnmqKtYMt+gC/F7VKJVKuwqEerCwRmQ4z+YNdVmCMgOHMz3u8U+RXn\nS7ZvLC7YPIwWnW4DyJn1GxQ5s3rOk+8BR2dqLkUWZTXqehHebVi5BkOhU43H\n/jz89awOsbhTZqgfYhmAhIasG3123rTYZoIp/MEPVyerPYFhRRoVxwOby06P\nys+2diUqyGrjcb3Bd8DI4WHNzwIbltGH9Zdx6hjdhQstXf4LRiHD3g7Q6JYu\nv+o5H6v8cEzJCrzHv/Q7dy3Uxtk4LiUHOUwsLVyFiSXQmiVOdQJiRmZQjV6w\nRQVYI3XyOQC9s8AJEYzus1c2UIf/J3Cq7uwbuePqSpx0LYzjekCa9BWFKxZm\ngedvG1dIDPXx2w7vl9JNYUsPOgYztCeVzDtR4iAsvVmQjcEdpkuBVkixnKA/\ngznJ\r\n=C7UQ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ec5ebee50d842c9f8381d628d06d307e3524f3f8","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-24T15:16:44.578Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.90","@balena/jellyfish-logger":"^3.0.143","@balena/jellyfish-metrics":"^1.0.408","@balena/jellyfish-environment":"^5.1.19"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^1.0.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.14-renovate-major-json-schema-org-ec5ebee50d842c9f8381d628d06d307e3524f3f8_1637767178033_0.840662976685042","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.14-repo-sync-jellyfish-config-default-5ea2c0476067f0bf1b94880a7bcaee1e7ab7d7fc":{"name":"@balena/jellyfish-core","version":"8.1.14-repo-sync-jellyfish-config-default-5ea2c0476067f0bf1b94880a7bcaee1e7ab7d7fc","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.14-repo-sync-jellyfish-config-default-5ea2c0476067f0bf1b94880a7bcaee1e7ab7d7fc","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"35c296400833fd77bf1d846cc49dc131e47b7cda","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.14-repo-sync-jellyfish-config-default-5ea2c0476067f0bf1b94880a7bcaee1e7ab7d7fc.tgz","fileCount":212,"integrity":"sha512-rsXUCWzdLCSHflmFZUhHdCFpneu3lp9f4+NAUKW4HSfto8I731nokjs3xlKd+n2jh4rU0jcVVbVU+3+v+DIgOQ==","signatures":[{"sig":"MEUCIQCAW2USVfKrCBO5L63eS+2M2THZndcGUWNDgGWlYoLggQIgDqbQWuGusNv/HnIWjL0KiYhGWaC2X8KYKgkhw4ZQ/kA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":777952,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhnui5CRA9TVsSAnZWagAAa4IP/A7rTj8qkpbTX1GSE7F0\nFGY+pHXsKaPOwAgRdNz3yDk7+lv267pIAvEgHwf6soogF0PmZRU3utflJTg/\niLhPpojlewl1WOSxcwQX4OdDmVhojBKsHbXCMYNuWprJ8Iv9k5554Ng92g1R\n9XdEzhEsSwrtkjK5VmBDEsCcTS4oXhlEFHhsfz15ih3IiypuGl09wpbbKgEA\nM8VfKJcTHwOUPkAuOnp+uKaVxErr4L8Wt7EkoKKkyVDykYIR3ciHdNpmxq73\n4JJJEVM8aAgM5eINJN8G7YHecrXqOOH2ekl7j0Mq/d+i+Sp4gBCHds8Eh2PO\nIAMYt+vDK8L7g+H9SLu/Rr1B41rOxCGAauSbKfGVLhQPMR5u9Nc22MQalVHN\nX0FZxZJ0kHN5IDFWfipneTArrBDkrUdTKU8k4jwNRl0URoPYwWHnxSMCP4et\nHrsiqchcCdgi0N2Tnu0rLROm5uG7KaVv8rMcdZDmZZy9KMACR+acpAayzdGc\nsL0f6ofjMRidHs8aZ5UHhIkKdsDXkpM8WVHB+UKFrX9QzAsEtexmddZOjOIm\nDXE8lJusGV//Zrt5ekGGSUXLBHEp1woKDxe4QBQNBv3BiFKRITbUYAGreGSI\nOI0ibEY9AlWMBFmwRsKDhPV8/TuL0SWcYAKYp0z6RPA5DAGKr0BJO8RRKNtJ\nahGv\r\n=ecHM\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"5ea2c0476067f0bf1b94880a7bcaee1e7ab7d7fc","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-25T01:33:41.960Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.90","@balena/jellyfish-logger":"^3.0.143","@balena/jellyfish-metrics":"^1.0.408","@balena/jellyfish-environment":"^5.1.19"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^1.0.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.14-repo-sync-jellyfish-config-default-5ea2c0476067f0bf1b94880a7bcaee1e7ab7d7fc_1637804217017_0.8755542114784258","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.14":{"name":"@balena/jellyfish-core","version":"8.1.14","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.14","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d784d1c197becdba729e8d3af29332b32290ae8d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.14.tgz","fileCount":212,"integrity":"sha512-ExoezGfHuuCiFwZJSdsiB9Iy7AF/5UNvVvbfwkxsMXlM5JY8CaubeYGFpcz6fahJ1f+UD37uCb68b981uqzcHQ==","signatures":[{"sig":"MEUCIQCS0r7088tW6zuWXOcQhzFF6bAXD3lraOHiwdxXg9ax+gIgBhj4IrT6UCik0elSpfXfRl0AEPFxK4/b1FIyJxKOt9M=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":777876,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhnupZCRA9TVsSAnZWagAAl98P/3lfy1+XpHoo1NXZU+YK\nWZ6N/tj/vWPhBFTKuHV99TUv0UYu0X/l2Hx1e4ly/cSFL942LzZATx307Zav\nXZjkXsUXsQ2a7D5xIJNcNs1y39hoH1EYcUKMzAhR4D99DewGw1nWPQ9UEgkq\nRDswTYtHfDnPpJ3B9PLfUdKOmp57czrMT6KlENMizHn6jfXvaX1EHAJrHX9e\nwzq0QG0CRgkBBtYCotYDFSwBjAO5bwMIXAD1b4dLkrc8kf0FOHI8M5uz+rXm\nJ6tCgJSrOehTk5p45AmBaqSVwBU/x/+kOLoYw+XxcvEdXUeh2PejZuHJd4J/\nKk6ezfwz2pBXoKjM1CaSzffsCSA6QTAngFgLM69+TKdedAcDVK5ouO6Z/2xG\non7jLB8xpkspxBMLPhXj9H2Nr0j+zCjrUE6MUlwbJXlrK62aOv+t1HnivlAw\nEb6arQ/Z9gYpgkw7ottEEDE2VdiMHh7scjkOFL8PEBib0q0B8E1CzMPcjP0K\ny7chynlMBVOSjPuwhzfhraSam/epY9ZAxrwpLlX32O0YpWGZpCyhFZRaEQgg\n/P2UDBI3FBtkiNX6CGgGlhJBnvlEseSg8EjjiXx1387niuVZ8CWcdigMKC1q\nXJDkh/ngq7+vBTng0A5ZeNC2fCijuWiJl0kt0TOdqfxWLlXDImuXmi6r1FUc\n2SEh\r\n=hIaI\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"bc3e910935c8f8493dadd4ff72ad80a9f5201f0d","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && npm run check && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","check":"jellycheck","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-25T01:41:36.316Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.90","@balena/jellyfish-logger":"^3.0.143","@balena/jellyfish-metrics":"^1.0.408","@balena/jellyfish-environment":"^5.1.19"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@balena/jellycheck":"^1.0.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.14_1637804633238_0.31578264079710516","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.15-joshbwlng-remove-jellycheck-072bc0771a1419e5702493dd4ad63115f9658ed6":{"name":"@balena/jellyfish-core","version":"8.1.15-joshbwlng-remove-jellycheck-072bc0771a1419e5702493dd4ad63115f9658ed6","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.15-joshbwlng-remove-jellycheck-072bc0771a1419e5702493dd4ad63115f9658ed6","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"17397be7eb7f3dd3230e294a5976b654f6b1fc78","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.15-joshbwlng-remove-jellycheck-072bc0771a1419e5702493dd4ad63115f9658ed6.tgz","fileCount":212,"integrity":"sha512-2fWAEPp/xxfSfYoexdDpkK2RA/bZ+Fg0y6Za9a7JoWev3o3yi7bNGQwP64fNNOCPXvPXY687w19ujpxXaNqRZQ==","signatures":[{"sig":"MEUCIQD41L8iDxUr82N0tmWPdQRx02KboMteSE7vk1DtHjK9ZQIge2vt6YzYIqu32JSrnhsRQ6m1oTKcT2VUuP+nwD0fwMM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":777925,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhnvEYCRA9TVsSAnZWagAASUcQAJtsMxhfjjjWwiqYLWKS\n/v4v6UmXK1TwjfVN/ZNmsuyfaunmXj0qz5sqyCtTggpSLr7xpbDzW3KVuLdY\nPZt2yuUgNtzbJKbYwJyXskGKRjOC3LjAwBK22apPMHAB9Oan9sId9vZW6CN9\nz1iyU46rOpAAacR72ewKlmf2Z0uUHmBQRBbpPHhgcOwe5EdF21UPL5uog/7o\nV5euo+JOrk9Wg3u2dFByyrjKLh7rWdRFN4idD1QtauRZoPyAVmcbqas7pZSr\ngHA9NWWR8wvSGZH4XQzIkKoJmGgmMh8ReZe2RH0tXNyDjsp00C9rymm6uPvI\n/3uywq8ZyrliKks7/cx5C+ueS4e6cGZIURjFQRsO9sDXlSHPJsrKy3tBIzej\nCQyEslVorzCPNKJMPyCodi3MVznz7UxqowBsMVUngCoPFDr0qe4JoN/r+yCQ\n7VvG7lWLyo6nFKIiULIqfaCmTUPftudn7TThTHg7OgrIHKHlo7Qf64yjkXrv\nleoKlluQV0Rn0qS1rDRsA0Hlh2TvX9kWkdMn2e+A8DkbMGQuqZbVyWFBzGMl\nVUBRSArZY+ZoR9UAqlMbgAtgAvUMm9ZSQfkReNKT9Y0rpx1j/hK6YnOUij3r\nvmU7Uo8iKhTnTB9VNdcrQz6NB4fiGp6RiHSP6R12zv9KVTVnoyE9vTlbzX18\nJbeK\r\n=Yc/K\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"072bc0771a1419e5702493dd4ad63115f9658ed6","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-25T02:09:54.604Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.90","@balena/jellyfish-logger":"^3.0.143","@balena/jellyfish-metrics":"^1.0.408","@balena/jellyfish-environment":"^5.1.19"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.15-joshbwlng-remove-jellycheck-072bc0771a1419e5702493dd4ad63115f9658ed6_1637806359928_0.9225841138839466","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.15":{"name":"@balena/jellyfish-core","version":"8.1.15","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.15","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"05f1f2965e0da45753268557cd574ba194b07b1d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.15.tgz","fileCount":212,"integrity":"sha512-I6WtMp8cVomAAkTyVglbxLnxHVyZmoaxRCOqWhnzhSrXDZPCAg+PuTWY5qNKVHqDtYx1CAqcFkCDtXWekN8FQg==","signatures":[{"sig":"MEYCIQCeh1VfoN6CCFIlrUG8zJRkZXPpgWRvJ7Hs62wtLmuV3gIhAIWxFdaJOY6zVzcnIgdQattldWPkxF8/Q+dGYbR0eg7i","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":777856,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhnvKnCRA9TVsSAnZWagAAbZkP/37WA+6d9rm1yb8EAukl\n+ddON/9Z3vOB+GvaarL7mc/curoWhkHzTlFaVbnjcIhrSZTxxX+68uI9h9p+\nlF739MyesCTgmKnCOsq2Fslj9K3eC/C4SUvB/AG/MXwzszZw7rtANy5qowoc\nzHPBMGEFEf2w/piKfO9s3xNIvW5Vn4JOMy5fdmdOvmHRJ9naKkWv53b6CbeF\nhEHnTb5nB+IljPmlUmSuE8IrddUcFZH1lZOBgL6/iXB4ST5iwuty8MkkKI2c\nbfxQtFFtTG87Chabry7yu8WRc0WTS9PPVTrIlzy3RYy1vWp1isFrMr5mUARc\nVIYqFK6bLoSBkCxHL9BCzz7LOUvw5RcmvOE1EzOBcnRHciwB9bhI2Dh3XXU+\nZAQDvuaxrQBSQWGlctMShbWl60JJUoLP/Qmypk1KL/fuCeh16FMfNhA+lsXq\nQ6rztdTZJll1wCAHpa3Ri5/N2LqNhPp+q0mBQAXP3QgFtbDCdhSh+SO7MyPT\nmwdPFPAlFQlmhNKu45TYoa9qHvxeCEgSgFKYThS3UpKEj7Wwi35ezRoTYWiP\ni5oBeHcHtJ66Mc/emz+qIYT2GH8egsWOVhvMSu18+GIsW1WwqGN/EjHNJRNc\n66zJjcVN5o2elpLqPF6OqWNWf29h53QXea7SU0/k7uXQtqVXI1983z2Gmtj+\nCnQN\r\n=vAT3\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"88b903987e6430ab47b5ed9f285db561949cb62e","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-25T02:17:15.790Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.90","@balena/jellyfish-logger":"^3.0.143","@balena/jellyfish-metrics":"^1.0.408","@balena/jellyfish-environment":"^5.1.19"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.15_1637806759449_0.2990602759896772","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.16-renovate-internal-patch-a4589da2fc99b3f6b846dc1ad515d62685b40e71":{"name":"@balena/jellyfish-core","version":"8.1.16-renovate-internal-patch-a4589da2fc99b3f6b846dc1ad515d62685b40e71","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.16-renovate-internal-patch-a4589da2fc99b3f6b846dc1ad515d62685b40e71","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"600e087073d4fae4f7acd3ee797b336a77820fa3","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.16-renovate-internal-patch-a4589da2fc99b3f6b846dc1ad515d62685b40e71.tgz","fileCount":212,"integrity":"sha512-+fpBcTTwmbqBO5fNglB7t/jlxyubSAdWRplq1WzqgGVomGtBsGeu7QH4jf68cXGoY0YydBNlF3hL1lHuImL/2w==","signatures":[{"sig":"MEYCIQD9dl5piXLg0QNYy9AUvMKlUUsoekE/Z9PFenIczp+APAIhAOPUy8X0nJ4mktyAviAOlUg/1VwG4t+IKzYVxNiBKZz2","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":777992,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhn0NxCRA9TVsSAnZWagAAyxQQAIS7REJRo9pEX6UwLd4X\nZ8NTsSfS3Lk+bt3hem7QGuix9mY8+19E20TwIZ2UJQqUggy51ADbkLAWzQzv\nhv+8pWAyZZevgdAggko72caeN3Naei3jjaK3YtF/7eOjFAtRvh7UocBa8T9Y\nmozPNGOQ8n+CiCkpFKN6hgA4GNYMI1SWWywzS7nwJiEUGeds9sweocXazHJ/\ngYdY6lC+M0CQexaM3F4lYJs/1qEOMTebw3rDGkPEAayn9pSkclJ8PZPElIzI\nK1eiXgdmIXwwkFtHzabBMN7fVYXLk68U+z0ZCC10zLL9gCs5vrWopUdcN6SK\nwgw01ccFdnG0M3tJae6GlqHdbi/h+DZuJ77iIQJBdnv5SRKC2WXqQ+8hgBQi\nVm3LmGJYGJhqoGBB1R8kBG/Jy8zaGOt0xKOWD4sM0fVi4ntuEMe+N+2IsdxH\n433Zy/dF0/fcY7KiyjmwtmWyI+vzaAnYKaenIamIFsaeUcpSa57dRT5039tr\nP2zhkQQNPjZMpPG4jwPoDBVy3vla5XW3ZYpWRKKB1idzkAppILb6vAWlrL9y\n90VxasAGKZr3R3fLsbfM6X5sgbLfoRpqR7UXeMGsfFFSSvxIzKAUBav2zHaU\n8GgohDXXyVlJR6gvEcPot9YML7nPOBNOigEd6fgpHO51MdDzoKMeqDaaXWGM\nyoel\r\n=uzEy\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a4589da2fc99b3f6b846dc1ad515d62685b40e71","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-25T08:00:19.154Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.94","@balena/jellyfish-logger":"^3.0.147","@balena/jellyfish-metrics":"^1.0.413","@balena/jellyfish-environment":"^5.1.23"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.16-renovate-internal-patch-a4589da2fc99b3f6b846dc1ad515d62685b40e71_1637827441394_0.23259331741665767","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.16-renovate-typescript-4-x-256ef6b969c9572673698bdcd691a9798aa3b32e":{"name":"@balena/jellyfish-core","version":"8.1.16-renovate-typescript-4-x-256ef6b969c9572673698bdcd691a9798aa3b32e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.16-renovate-typescript-4-x-256ef6b969c9572673698bdcd691a9798aa3b32e","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"7bcfde67f93132b1a73be192be28dc35bedc91ad","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.16-renovate-typescript-4-x-256ef6b969c9572673698bdcd691a9798aa3b32e.tgz","fileCount":212,"integrity":"sha512-R8XUJCtMQGNNqPkcw0gUPgDsMbSxN+d3IsYLSzgEInRceTpi4IsEHNxXquaw43g36dhssNoBlttG3VcWUyD58A==","signatures":[{"sig":"MEQCIHVOduN9cA9PBSXLi+Qnmjw05GwbY4a/66KiiuwdJBuLAiAA4YR1qdDr0fzo6cE2XJOngoBXeYxx0KMDQmgpF4hPBg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":778009,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhn0OoCRA9TVsSAnZWagAACjwP/1zcKW6+nBtN31j27DdE\nuZlkcj1HsVQ96cUFNzRN4HGbFY2GcUG9/DKQyLHVPm3S1PJ+zmgDudZwZOEf\n09WxIgtGknpZmlNy1Jk/sy5KwrnQN5l+4B6xLHWCV32Vrg6top+w+ImpLhCZ\nmTUDO7pH7nd6z5tX0VFpA2WWTrUo3cNsauXBXv/47zcXjkLr2/GHZIKeStFI\nGkCYSd4Hul2Ak63J5W+l4gCkMZirB3AxJIysHFDkZiDVwyYakwfXfjYo6jT9\nW3l6CONLrLi8kJzLA5Dm4R9zbRNBYM/6QK4ssDHXdLWU5nTwatdktI20v5lv\nFhrA06g3C7aT6M8Z9TK2z4DSOBc4SWWG74r6YjksYKLAOlsQsYGvEgyyBjJp\nX18TyK7gGUoTkD9IVV3Rb/YqxQmrWXe3lUltC4YZv1/orl0iqp12CQzKNjGC\nAwp4TyAsX+9FCJPPoBH5hbYMf0PjczcQQQ8wSSRhjDz08e57KibBg/lruYj5\nsTm44CgUninPOAwbj08i+G/FjIQbYJTJrNeuI2jT8WZkFVV+EHmZ9n6JmU0U\nLXTF99chAG8aZbvC3wsVq4SQEWDcP8pN5uC0otRCEL2dcJXWalZHHVE5no3r\n2kgREV7AFKqvvYiYWIGjBYkHf9tv3oCySHAFXJUJtMY2DQiPrQDc/VuvXEBZ\nXTLQ\r\n=2VDh\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"256ef6b969c9572673698bdcd691a9798aa3b32e","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-25T08:00:53.028Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.90","@balena/jellyfish-logger":"^3.0.143","@balena/jellyfish-metrics":"^1.0.408","@balena/jellyfish-environment":"^5.1.19"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.5.2","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.16-renovate-typescript-4-x-256ef6b969c9572673698bdcd691a9798aa3b32e_1637827496335_0.39348146211743895","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.16-renovate-major-json-schema-org-b70b240f68077fbaf1cb9c6166ea5cae300f48c8":{"name":"@balena/jellyfish-core","version":"8.1.16-renovate-major-json-schema-org-b70b240f68077fbaf1cb9c6166ea5cae300f48c8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.16-renovate-major-json-schema-org-b70b240f68077fbaf1cb9c6166ea5cae300f48c8","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6836381af3f5f572ac270ce41fda2b410db9a967","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.16-renovate-major-json-schema-org-b70b240f68077fbaf1cb9c6166ea5cae300f48c8.tgz","fileCount":212,"integrity":"sha512-6MiWdiQYtdUMcHlOiGdY7oa0qUGwdxqY6h/QA3Pqn4Qqx8NvddhQGPOx30JaPwH8vEtGn2fRSWt6GsaO7NOe6w==","signatures":[{"sig":"MEUCIDVUUgpfQy3VfCwPeghDYskHbrHscF1GXOKXS7xrhh4QAiEA6EOCacuzG8VUgt1UGLNdar6yuWDgWygNwVIZCMjIqSk=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":778024,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhn0POCRA9TVsSAnZWagAA2sIQAJpQ/FmQr1ZwB1h4T+Xx\ncGW9ae9Ynpx/rsMlDglBH31/MXTxsDir84IvmabPsR5H2b8AOZpKeNrH6vPY\n/5PJJgnbP+ANBKvbZjRXwtv9srGyheRcX62+XafyTlgpOmP+WNjRkirgR6RG\nt9qMCPOOb+mzZnL9aLFn5HyRLMrKnjUf1BGDKBdm7RWuOhr83hfDb7LIQufv\nge61exuK4NKTltJOwinmHLTjFkVL3OKLGSW34KcDM7ztuRClFGrR+6K7KQxC\nxHl8JYp5tL9nZ/UqUWCShJZCKb1QnjVf9XRBmFVhU3Kax9fow1K854zCw1HO\n0jZVc8TYozoRm9HxNb6UZs436DsR5dW5HHOpAYxQZxeMFjw9zORnH52G2vPO\nuuXKAWPp04E0a6YQWRmtTzrKRlSyMjqaSNrIKj/TO4LJYT1/oVlEPYPuRN6N\ncDvjxy5/NynAhLTUuy+ahpaJroR9K2UKfpP/fHTzqPIp9YZqaCLrV4ffCWpf\nlkyb74CIts54HNdS4egv+rDg88ieM06efe+kz9ygSZ+quZnUkgPixyB0DfT7\nZHBL/Asaozk1yUZaBkhgmze6BallWPQFAXxTbK+rCnbH6fKSLeefckfkLE0G\nCyigmTI2MKUWa410ED3flGeDQ4b8sb09hog3M/9s0eLOOt4CUGw4tpqSCS1T\nTr8L\r\n=5uV0\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b70b240f68077fbaf1cb9c6166ea5cae300f48c8","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-25T08:01:39.019Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.90","@balena/jellyfish-logger":"^3.0.143","@balena/jellyfish-metrics":"^1.0.408","@balena/jellyfish-environment":"^5.1.19"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.16-renovate-major-json-schema-org-b70b240f68077fbaf1cb9c6166ea5cae300f48c8_1637827533758_0.7906503528270599","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.16":{"name":"@balena/jellyfish-core","version":"8.1.16","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.16","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b98ab8aabd14571460ef4e3ef0e9dc61c2072a2a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.16.tgz","fileCount":212,"integrity":"sha512-X/PhHVkMwnaEFp0/OtYvQ0vP6DNalg4M/Em0A8DsT+I8hIY8hkVtwca4K9XpFekdMH/fNWlSQg+hRPmyhfcKRQ==","signatures":[{"sig":"MEQCIFAT7E99qmAQ/FDTczKQWWVR11xUgG2sN+aVLWRpaUQTAiAwLnSdFWDUY8jthCSXt+UIGq7JP4zAcRQ7UN11n2D0Mg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":777927,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhn0VtCRA9TVsSAnZWagAAezUP/16irq3RJuPSHxhCUL0b\nIqJZhe0Sa9KVXGvMC14Pe+vUXKSFXD5Zd39z4TuuGBFXFDabMlbf2hEXotOM\ntx2EyjZKYj8Q5p4vysn1elP/bFOx0M+hh4HVaCrX++xtudNppGVMZcSDqvgy\nUxctfHgvURxUcIKViwRk5F1BYpFB6Jl908TtOSS8EdTegiU+0pnh1pM2sUrH\nfHLbaZ+RfrY4aLkCu+OHBPD49REA8fERnxVRerSk+X3qjO7apgJxZgkcGr3c\ns4Vj/HUNFYDu3jeedRm5SmoZkLeX6XvAOG6cmUvOpkNSJh1E8CLC2FUXdtrq\n+WiCBDo5Icpgdh7O09jNLn1MtW1I12okT+LtMWux878SMvxE8me+dTEaBUgz\n+nLSjeQQcrwMjndy131uOYq2++gHVPHadvcUnR5bzqhY6E11ia7kXKGHLw3b\n4jJ4+4jsWtDdBr5ihDDdXMopxBi+hJa53bwtGC3/vdJiA7paypfVFGB27AmH\n6+EQpHgeaWB0Ny8FrcBy2v8ATURX3JDOXarNALnzMMWaI+BXsRZqLyakzaes\nOuzd79GaNzdnS1ssMpRxuzumGVedFJn6UOW3iDst/jRSHFUie8Axy800QNgY\n68idtuXbIXMQ+unw88xNmmHAUoMt4+T174+/1x76R41Y1E4AnR/AqsbgmFrh\n7ILF\r\n=Dx6V\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ed5294fdfe654c48da0d613730a120ca93584379","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-25T08:09:39.460Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.94","@balena/jellyfish-logger":"^3.0.147","@balena/jellyfish-metrics":"^1.0.413","@balena/jellyfish-environment":"^5.1.23"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.16_1637827949719_0.45423448928225696","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.17-renovate-typescript-4-x-d29c0d7ce7c5263e591b963f46cafef32e167ee6":{"name":"@balena/jellyfish-core","version":"8.1.17-renovate-typescript-4-x-d29c0d7ce7c5263e591b963f46cafef32e167ee6","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.17-renovate-typescript-4-x-d29c0d7ce7c5263e591b963f46cafef32e167ee6","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e86777933f314176bb9a17fe59f384c249619a94","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.17-renovate-typescript-4-x-d29c0d7ce7c5263e591b963f46cafef32e167ee6.tgz","fileCount":212,"integrity":"sha512-iqlSYeIf8co2Pv2Q7Jrw3IckWCDEit3CH+BBollt7t6W/rLSvHJhLePLXMI/YkNox9uTUUIfPbBjSPp7ii3EmQ==","signatures":[{"sig":"MEYCIQCn6Fv38cIJ3OgNo0IrNpD5Ibeu1oWTz8xLWi2dq8DZywIhAMuKWryErGrbkCaTOeO0rGaAfFUVaseJZVKClj10xgzX","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":778080,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhn0YmCRA9TVsSAnZWagAANQUP/i8Aa4S12ufBemtDgE6j\nu97L+2d9iJSCS7S2U3qvAo8dsXQw0yTqAzNgM6p+l5MRZn9vlrtyryW7+JVJ\n2h6NfGVUXHtvbSXw66vZaVeuuh0VF+sfrBtyAsOuGMI193tes45EOWpjOUJV\nznrkkM9oeluEiNdL+1QhB7h3SNr0R+l+gOMjEQDioKrhWoV86ZAGqsUuZxwC\nHSce2BYsmGdPjrxewkLlpz9mYci0DVC5G5LyXf2Bq3IAzj98PicrPzO32rgG\nfbhJbARWm3P7dgmEuY2Y59QU/aMP9B+7QOaQX0BeO7s0oq9naS9mWgFV/rXc\no2Mpisqb/wWqcwgokvJl03RoWip8v/wAZ5mNEG7YrJbk9JrZGozECRSQbBMm\n3Xe9TuxtOGmyyZymoKpMAFqpuKuzF2gLMdX6vu6/3e0soreJlqdrtgG1Pb9m\nysiCC4MOlbIIvnMv9E0wGObVlik1ciYbFeAKDgJ9kaxH1gYmC3K+yJOTZagg\n+3VU9w4JdRAh6qKhaLZi6UbkJOUXt3AVdGwCTOIFfKnIAQo65HffqCMRUcoX\noYCk70nCtLLPDao/5qI2x8oJTfz0d/aS/U94ujhnP078wDa/ilrfxtXMoP9u\nhQ8vLZStdEF/B+UP+WmSYosMT35hsQHO/hR0v8LjFeWRTTJroJwm4F3yd6yz\nSjJl\r\n=LOub\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d29c0d7ce7c5263e591b963f46cafef32e167ee6","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-25T08:12:00.901Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.94","@balena/jellyfish-logger":"^3.0.147","@balena/jellyfish-metrics":"^1.0.413","@balena/jellyfish-environment":"^5.1.23"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.5.2","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.17-renovate-typescript-4-x-d29c0d7ce7c5263e591b963f46cafef32e167ee6_1637828133871_0.15342663906909415","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.17-renovate-major-json-schema-org-971a400ba8e71a52fecb9e980caf01f6fdc15935":{"name":"@balena/jellyfish-core","version":"8.1.17-renovate-major-json-schema-org-971a400ba8e71a52fecb9e980caf01f6fdc15935","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.17-renovate-major-json-schema-org-971a400ba8e71a52fecb9e980caf01f6fdc15935","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1f72090daa2a0a34c0ef0552a8f98c0bf670d626","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.17-renovate-major-json-schema-org-971a400ba8e71a52fecb9e980caf01f6fdc15935.tgz","fileCount":212,"integrity":"sha512-Pp3PEcBj66qcZZl8FBGs+rnGDwEkoOP3l/cepsHK0jVDkpei8HcvTVU3OGkz6ddvsJTkr2/mY39buqnutYcAgg==","signatures":[{"sig":"MEYCIQCG+/Z1Q8cvVsI1I8Whgj1X4u1LofXwRoKgrqqd/e4bfQIhANEM4g+3N3ehffCBwlxctEscKJSo3swU8Mzfi3iThqyI","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":778095,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhn0aECRA9TVsSAnZWagAA3yoQAIQO5WHDJ28DGOcE3BtA\niEtdrAht7icTN0VNGH4/cIIF/KcWQ6uk/6R/5Xr0Blrlh9BbJMa/YAhlZ1SU\n8oR8aj38Maj/CAzmf1fISR/FXwq3rlcANY4JPUskmWiH3U73c4ygcdMSQVWX\nFKMkmqsVUadiGvF+vRYinb6yx7oZpPKzE3M9r8meuMTAtxunEB4jVOUucS1Q\n+TeckHMYFBEHoYgG9YkulfgYEhbX9+TcKhtr+0iGei7ir0Ngp7Y6vcQiXvGm\nTOPLNCie/OLpYxo1ubt85ZREmNwB5uE3NV8QHvabLoemYxmgF+sPLRbds9Ca\ngT+suGG1o/2c/jx2DNQjSO9UOQ2OqsqgNzFaAFf8HbyxXh8L1d4wwU3OB+Qx\nuJNN81BKpKIT1Pfqg4ZL4lAR7VQrdpvJXHafpuF0n1TZdnlkdCL4jdbbLyMf\nhTFUCeXa39cZ5qMLQACsNHozxZEJCvjerXEOpcgOsLwPsehDCwbRu4uTTdN6\nIMKQ6B5z+02DM7biiCy31n2+2aO8r7s/tDXHUr8xEGwCbWDumvfOcBw9aCKO\n3/bUFxJtgZgcQ5z0eFTZjRA4SfBZeh343LuAVMhWX+TvBdP9rGxU/ALq3FgW\ni7sCUibicVyZPKGxOq9U76o5wA0prTrmRTpveCuSDzoXfyZV3EB4+dmoyjB3\nGSfe\r\n=CUxM\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"971a400ba8e71a52fecb9e980caf01f6fdc15935","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-25T08:13:12.039Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.94","@balena/jellyfish-logger":"^3.0.147","@balena/jellyfish-metrics":"^1.0.413","@balena/jellyfish-environment":"^5.1.23"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.17-renovate-major-json-schema-org-971a400ba8e71a52fecb9e980caf01f6fdc15935_1637828227973_0.9813201418036819","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.17-renovate-internal-patch-428026c36d58e3a1bbfd83c9491ef34cd5ec019b":{"name":"@balena/jellyfish-core","version":"8.1.17-renovate-internal-patch-428026c36d58e3a1bbfd83c9491ef34cd5ec019b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.17-renovate-internal-patch-428026c36d58e3a1bbfd83c9491ef34cd5ec019b","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5c9573263c51ed70c2c8a087ec1b2ae4abfdbdb1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.17-renovate-internal-patch-428026c36d58e3a1bbfd83c9491ef34cd5ec019b.tgz","fileCount":212,"integrity":"sha512-z7duZTpwCnlP/6vCyKD3fpPiGxoGdWDE9tYmKA3dDbJ0HTEE4Cu8Nv6UrOfagDK40QD1z+19/0A2PVqaMYYphw==","signatures":[{"sig":"MEYCIQCowbuAZ2RMexTujKip1AZriYsY2evtolPvw+CL4DtRIQIhAIPrnNRB57iuRnFTNwT66CQoKqBxDzsZXwfp7Bxyj3pP","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":778097,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhn2F/CRA9TVsSAnZWagAA8qgP/3OtUW5sav+uts0im32i\n2upkAS2gasEf1WJ8+mmrMtbtDrFmHQr6SmomRFKlfFfBRbQxYwRjVNo7hxB5\n+gBf0yosq7qjejtKNayapnMAEqd6dC0RVhysnStxo/PCSJK+yZGuiWD2JAhJ\nD9z8YwvZkolZG8pI1Pftvx9ogo3VnKzkQoCca6IE4HBeyHIG4nHddNSbxSD/\nFk0NkpBGr1PNp9AqO/MoVr4UqWH2QzSkQ1DASO5z6g9vXY4SmrAK75Pc8vA3\nToWcE9CvIElFxG9cemkhBsRPWsaI42H8J6SIYUD5l7mE0U1LMAcQF1KkS3Zg\nvAgai7vBDFRIAaJoNhBHSyTD422C8/QLHCEclkR2Sh3WRq8xz3TrShgSer34\nST9tqsYsD9kIIF0/uTs1lww/pIfLw2BPMa4zdu49AmBAn27TEkWP4lhaXlwA\n8KVkbSviEy7BKTzACZg1Ar6oaUfNuuFn5PgyEgzlq8HYMLNqdPD7G1VZGUT6\n1kgxrIzNOLK6PBktKmojnjJYHE6pqqGTErs2ays0xcnmIgSqrplMlOC209i+\n4QJihIoBnWhos9rf6yGO+qMCAjyU6Iiod0TUJC3hlBqlEMMuRCkqCb/LqVDp\nPb9BeEPNdpbEpfh/ErZ4Up4GZm5lXR+3p0uKZPICWGvl4Xuxphkx/qK1y28Q\nzdpr\r\n=4Rkd\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"428026c36d58e3a1bbfd83c9491ef34cd5ec019b","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-25T10:08:16.872Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.94","@balena/jellyfish-logger":"^3.0.147","@balena/jellyfish-metrics":"^1.0.414","@balena/jellyfish-environment":"^5.1.23"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.17-renovate-internal-patch-428026c36d58e3a1bbfd83c9491ef34cd5ec019b_1637835135706_0.11530134789462876","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.17":{"name":"@balena/jellyfish-core","version":"8.1.17","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.17","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6b82ee22d8b786126f8609ec62fb714b81ff76c8","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.17.tgz","fileCount":212,"integrity":"sha512-+6lBJwL0IjP4IRPcHBxavTxDAWdcOto1j2eMRaLxGrSbmGsfiiNACH/rZ/X4a/yljJ2UTNmYnUy5S1WsIaoPog==","signatures":[{"sig":"MEYCIQDfZHdk8CcgXd/4FH3GPSKR8qpCd4g0lWfCdm/T2ufW4AIhAMwECfPijW+2N98Y8h3NYGjztVe1ctDfqmTphfqEy65/","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":778032,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhn2M2CRA9TVsSAnZWagAAJcEQAJ8rr2YboJsmm7hbySHM\najn9VXSJxR7rMnBkf6AYuuee2Xgj/w6ownlSR+T7Di/c1fiNQgmZ6ev/SNZr\nFFStTzhFeSY0wB8hpxnhEM8VkKuczoQ2l2sAUgYOrv48m2vBunDD3OwDHs3u\nCqky1KvzVBNDTpGxrII3ASaPigFI6qOdFeZYKC7eaiEQE0UdwjPL+2pVZq0R\nle8oy6FrWi9/Q21kVGp2TwF0R7bEyg8K1NkO7McBo7sAJCsjVvCr/Grk761n\n71jHPArDeLFcuw//brd1D9X9T2oqz+Z+RXINRw/VgAznF/WoDDKmejqLPNbF\nFLtCQjsewc8p4DEnzgKrvmC+t4bPbr/rWMqWJuNSiScL33DyJWoH5E49IWEv\nDxOFgqBug/hVtJ/i1r+2UzgEdmdjQWqVhilMWCMiKvglSSCbKjdFtDsq/ZL/\ndU52odJcsH3HnH7wzafRUlYVyHW/uGhMmuZzUTrtujJE7C0AQkUMRZXLZ/Hi\nSr+ZtNf8UESj/LrqaUxYEW1Ul7+1hhW1DlTMx5B6YLOWeJ6mfVrzCQ4NLL7T\n71L644QSOpYj9qyqKhMXzLOaPIikgIXnON+Hsy6bAWMSrbC7VyReKKQnSqCY\nIrNNB5CBOebmu9Q6gmeCB+DrchEfhHGdwYLB01zjPzE6sibXyvtvFIY8Mnjo\nEMBz\r\n=1qVs\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8f6d455a9e82ed93b3a5b9f667d8f324226109bb","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-25T10:17:29.394Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.94","@balena/jellyfish-logger":"^3.0.147","@balena/jellyfish-metrics":"^1.0.414","@balena/jellyfish-environment":"^5.1.23"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.17_1637835573992_0.32415237844610045","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.18-renovate-typescript-4-x-1859c6f0c29c5597c9e86b2543693cdfb13e7c4a":{"name":"@balena/jellyfish-core","version":"8.1.18-renovate-typescript-4-x-1859c6f0c29c5597c9e86b2543693cdfb13e7c4a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.18-renovate-typescript-4-x-1859c6f0c29c5597c9e86b2543693cdfb13e7c4a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"55422c49e786ab385d03b248268bfd82e3676eee","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.18-renovate-typescript-4-x-1859c6f0c29c5597c9e86b2543693cdfb13e7c4a.tgz","fileCount":212,"integrity":"sha512-DDsEryi7lfTuOnO5GButaf0vlrw4Rv4Scbce24Wu52y2EclCWLPC7HHzG9rikLzpGzmpSAsDm9I/U00nk93lGQ==","signatures":[{"sig":"MEUCIBHvM2vBUR80VGxohOJPZJlgnUlJ+D06Fg+cFl83E/w/AiEApXgqbn4Zua7CaJMHKEAMpsDId0LWrzMj4ChUk29CexA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":778185,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhn2PsCRA9TVsSAnZWagAAhNMQAItjU+4qXYitSG8Zhelo\nWArGSTmI3wGsLxrilO8Kxtz3lERHv2ORgGBwTDc5pQ3EJkxmU3pqohuKWjmy\nnPVb787H/yGejgXhkqwLhl3b7pgZBQ7kyTdvEYZSwRI8nR5X1zAUPC1xWd8H\nEZxU0RnVwmJohT3GCINYeKWShb1JGJr1r6Y6uR+9sSE7Bj+3d0MFPuAUwebO\nb8OjqFYGOyXvg+vdIIx5ld8qIE4QBd3YpjTXoZecRmgdGHqr5jqveq0TjNtI\nGlqi4F54XAGdIbCJUHqijJC8KEmIiLqwKYknWqs/RrbP/zWZ83LXTiK6jZqm\nOPRQr1dS0uTCBY6rFhrx2+M1HsqrUMWCy8OyP4FJXR9CblwNRAFIdEBQPQ7k\nkx2/i7Ym2F/USE21l1rjt3aFuSn3B8f5+s9PgCjJzQ3odiijPuSLe7pM5wgK\nworBmozc1UGuHkwUiwTnQS4s4wPnIoh0tCwnrjRSP404LyOp3d8QwCQVicnp\noKYWUDivBXIYTSYtWTOLzy4LOzRTqs+7Tbt7XsZamL3ruRsX9NJfhYSl4fQ1\n7ryHDWFJViMT4zG+ZlGYVvYWdioR3T1ygTJDMQXSYpcgk2rQkEPmOHHc7uRW\n2JHGBSYPVrYBCg5nJT+TLUhiCC76PitfbmYwI3Sf+7OQn3b9K3OHi+tmVURy\nUmSB\r\n=wvHP\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"1859c6f0c29c5597c9e86b2543693cdfb13e7c4a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-25T10:19:39.830Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.94","@balena/jellyfish-logger":"^3.0.147","@balena/jellyfish-metrics":"^1.0.414","@balena/jellyfish-environment":"^5.1.23"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.5.2","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.18-renovate-typescript-4-x-1859c6f0c29c5597c9e86b2543693cdfb13e7c4a_1637835755967_0.7328219366261384","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.18-joshbwlng-use-renovate-config-88f9dd1e027e9fdbf16718590ee06d2c3dff7dfe":{"name":"@balena/jellyfish-core","version":"8.1.18-joshbwlng-use-renovate-config-88f9dd1e027e9fdbf16718590ee06d2c3dff7dfe","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.18-joshbwlng-use-renovate-config-88f9dd1e027e9fdbf16718590ee06d2c3dff7dfe","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"48fa11f565fd974e3f95bcfa8c77afd06da0670e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.18-joshbwlng-use-renovate-config-88f9dd1e027e9fdbf16718590ee06d2c3dff7dfe.tgz","fileCount":212,"integrity":"sha512-BZvZzXXkIESvDz8ER7V74+hofum0eoYSnSYDDbqYtd7Awq8qFt6rLvnZK1JrXDbKCtX381TiiIcoz39Lb0NN0w==","signatures":[{"sig":"MEQCIHxLqduaFKxBObLSigAH0s50LvIlZhgOZGJUCzdT65QuAiA39Ii7bAZbFGKc0sJSHpW4Uj/AAbDDFMJJECKy/Uo5Jg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":778176,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhoGUzCRA9TVsSAnZWagAA+GYQAIbVo8DRvgUEjADewvci\naG/sFGsbpo4a1b+EIbQvenhRZtkrgD24/u6R4kS1LE5HWq65VXtV6d6JFh4q\n2mB/F5jOecxX9qZK3Gzswx5SvV8wEkedyw6SFQ5WNaoW/ki9ld93nTda2sA+\nwsLckEjTqG4msLmT9RhstFqu9oc6Jj8eIb3lstgzBh/y1c7191Z276xmj2AH\nkti6ub9A3HGoyTcs+AsH4IL70wv81dRzR+IHHZh8LJzuvD10Ag0NXTn+i9Ye\nL581VnXCDPjp7eTG6qWPPfyvbEQ3kscL6YF0S3OLj9hMQOuCpz5WP9Wwvdqe\naw2V/P7T+oktXGKZwVwvATceaSO8H61tqQj30pTD9ADfrAUaulDRCtT0d1Om\nfpcpcfKMK0CrydNUquQ0hdvlKc1gVdbfv7daC5YcTDHEBx1vtxTKPb1kX/sB\n6rk5sdobzsNxiZK9gO5/IN4Nn3acgYLPmn8KCHvjjXuDusySMZCJnfqT1VGi\nntApPDidGO+HTsiF6beuJ6fh3sR4JcBNOHiV8H7Tqeh3zrB3FNkPtP4JJWEx\nKUbTHUGV6PcjmEf3CyZR2tjX3preDVjH/3HPW1L6EE5VjtR0A4Vy4qdGabyu\nU7jMp3dEEDV+x/haIXzzQPAoP8wLrIxrg+8RjeBjuPXteMy9nxw20ToL3bpf\njFJ3\r\n=nXk/\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"88f9dd1e027e9fdbf16718590ee06d2c3dff7dfe","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-26T04:37:41.206Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.94","@balena/jellyfish-logger":"^3.0.147","@balena/jellyfish-metrics":"^1.0.414","@balena/jellyfish-environment":"^5.1.23"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.18-joshbwlng-use-renovate-config-88f9dd1e027e9fdbf16718590ee06d2c3dff7dfe_1637901618816_0.9944059779027619","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.18":{"name":"@balena/jellyfish-core","version":"8.1.18","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.18","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"121853419aaf1181afd33cc45c5ad2e56820296f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.18.tgz","fileCount":212,"integrity":"sha512-/RTDy6eqD25ebkiN2a9SYSMqjb7bmjiK3WeVkE330hl9+hVXDhD2Nen506rGLX/BPIIhhjUKtFyTVzYm4XHhEQ==","signatures":[{"sig":"MEYCIQCYsSJnO6rZyJ52qXsbdNqf3+Ax2xvj1eY9G44MwRoGMwIhAMWLtvgDEWPLuWFeZ0ExS4xcV4v2elmQyRuRUHSzj2oi","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":778105,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhoGbBCRA9TVsSAnZWagAA4K8P/iPK6PKOMpmQPPO8HTBj\ndbkHeLzTfORWNw3YrK9lcZVYHk1O+T2bqMWFrQExuZnOz1UGGOyF+pCXNDFg\nCdoi73e742DO4K/v6ueg2bL2hpodn+MCsD+wEzdcq4DAASkqGUdnb00lnDTp\nLzuf99RD+X/o+K0FDS6OQkdFmxU5BXZS8bQgYboFgs+RHVB86S1eMg4bsGog\ndxxTJnD9riCwLWflaWixWkqUgJ3+S28Aips/4KCsiIpzhbqsuTJw+Pa4qM/6\niKZexCejY80f5+8RIEM0DO6Z6v/3jYFz1unq7gfnMCL+p1aqhKGhr2AvhhVX\nNUxr02iTzDlpKaGZKd8ulstgV8oYLB5IUvum803PnlYrj0tjZh3HGWSMPC5k\nv3HE1VxAxmwMdOCvB/N+p8McZSMvhCBKueOm3IDUyzGRF8tHuwFenyJ3mK7e\ne0jeYnLxcwSegCJykCOZJR3F72mkwRMm6kHXARS5dap5OSdhfBrk0yUYXdLi\nmst2BOlCL/10ivKKzZgMpApBubjSpD25lR/XAT1ThLa71tqgeypi0Uu0NL0+\nAeK5XsH31hvgv7ISYojmPh8mSgpWlMYTTx78EYvxbsnPbynAlY3eHc1BkJTD\ntgU4OkzdxkVc8M6PJcJ7/whhJtNN/5QkhhYsKWbLo/n9gCg5S8fxQuU8wTtZ\nGfeH\r\n=1ndW\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"5b58c4d3a2e098134484e3cb45910aad7a20834b","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-26T04:45:03.268Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.94","@balena/jellyfish-logger":"^3.0.147","@balena/jellyfish-metrics":"^1.0.414","@balena/jellyfish-environment":"^5.1.23"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.18_1637902017531_0.8869949944373283","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.19-renovate-internal-patch-84ea563e2e0bc2e1c79eecc1fd2259bd87d8f10b":{"name":"@balena/jellyfish-core","version":"8.1.19-renovate-internal-patch-84ea563e2e0bc2e1c79eecc1fd2259bd87d8f10b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.19-renovate-internal-patch-84ea563e2e0bc2e1c79eecc1fd2259bd87d8f10b","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4479eaa84ce105e3cb3cd3e968bef38f3e87aead","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.19-renovate-internal-patch-84ea563e2e0bc2e1c79eecc1fd2259bd87d8f10b.tgz","fileCount":212,"integrity":"sha512-H9bFTN8w6C9F/0Z9JPnwytM+RMn+rgQlXwhRRsFrH5Wym9Ox9eRP2rSkKGEpW8Am+CV4XWSpMbL407V9QtEFdQ==","signatures":[{"sig":"MEUCIBA9Ynfw+a30p2V6DhmdV4RMwiPowXnHsoQGlvvoy4YWAiEArViK8zH4ApNGxuqb9gq9JKaKeSQ97rQoiJ3+xTmqtYA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":778241,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhoLrfCRA9TVsSAnZWagAA8dUP/A3AYJTNuSoN9A5kVHxK\ne/ZIMzUwRTVS8ejeAzGtNGqyDkHzZpH45HkSBo6W51AxQ7doK5qXvHi8AFx7\nSx3Q1f36poG7aC0yVUKZg/W3vSj+d7YxW98XwcixI6vE79bI6qlRHm70wah5\ntY7nzL/Rss9smnL0syGk3kuRo+X81iwgq6eZpUFX6bJ3w+120ksmeMpAJmYG\nP9sWfdMSXitsKgZexra3J/CRc5B4DOy+ATqCdjMdSdJW5RJPXOtDB3qSpgS3\n7ZUwTO4scGNCy9HpPa7+ZD2mup809YM+qmaj0Q2xSgrvn4u+ICKPFkC9tMEj\n6QqrTUIASD3A1IIJp7iMDhDVLwOnIoEKjawSO9jhZ+68BeJGlTqSXvReFacM\n1Uc8L2OGmhoLBYa7glqutpk4Upkirwa9FQoqp3K6eMMvG85GLf/NHQecFCGB\n5V0Wk84mS+LZk7SJWrtfL8p9a1y0EbzvN1cucdH1cE5s17eixNB7Pd++IOQY\nNUeGYi3PaEZ1JC3SuVOV4jMQ7KX3CCwcIjP5oOJqFrERjyAuLxWWhoT+r+rb\nwS06E3PRraF/WV2mVk511fwsfnqtR8dy91bjI4mpfDA66Pd+z7wfCnZZkVdD\ntDwdynuNNXfsFVeRLJ4AnPyvGBVwIG9lmRVZe8QIzT9Lu/AvLBFtrxO0WsT/\nzYvy\r\n=SOFB\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"84ea563e2e0bc2e1c79eecc1fd2259bd87d8f10b","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-26T10:42:24.721Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.95","@balena/jellyfish-logger":"^3.0.149","@balena/jellyfish-metrics":"^1.0.414","@balena/jellyfish-environment":"^5.1.24"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.19-renovate-internal-patch-84ea563e2e0bc2e1c79eecc1fd2259bd87d8f10b_1637923551183_0.6756941957725615","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.19-renovate-major-json-schema-org-8c086d191f6af508df32e315701c163be8691954":{"name":"@balena/jellyfish-core","version":"8.1.19-renovate-major-json-schema-org-8c086d191f6af508df32e315701c163be8691954","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.19-renovate-major-json-schema-org-8c086d191f6af508df32e315701c163be8691954","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f227119612c49e0e8916a8baf91134e748792216","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.19-renovate-major-json-schema-org-8c086d191f6af508df32e315701c163be8691954.tgz","fileCount":212,"integrity":"sha512-1bVdTUxxN8ennS52nGnH0SOc3Oh6yEMpIDFD/6m33/lMJ1Od3BWq1lc9LUFF/Wk8wFATNybbNBhVg96OCvewoA==","signatures":[{"sig":"MEYCIQCWZQIoZy+QNYXKG+8vMop9nbfjJWlHRVMiCBiPAADUgwIhAIhMau6kxtUaC8v8ZL4gAnQHrZ5fs/23bgmFo+V5MEsJ","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":778273,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhoLsOCRA9TVsSAnZWagAA8KkP/RR+HA+ohChWGAxHuCfB\nWZsy6/HXE8kc2GlkAKH0Z+oQSaMbRBwhwfZ2uQqf9BD3gbxJ49+UOSzjO9+c\ng+dpU1q5PBuoXjm5+GWdOF9anvP7LgXtzX0oacZ3+g8BZoDl6dek7hIYoTGL\nHIFdsRWcE6XttIa8Hbc2gGyRhmbN6zkWE+TgBF9DUlsP/hZmIKJn88J83gje\nTBij2HeuIESqvaFayNjd7pl0iBzwe2cc2bWuOK+qWVZmQeq2d3TAnpmeg2Ms\nBkB072IZNY7lnY+Ac8o+dgrQQchyblT0DVilScRHDre3dOD+4GBDUWl0rEZP\nw5fYRTdKD88bwu0EIuU09/iyW2AlHr/y+yCCTrIfXvQHsjNNkiL6DV9OKjgU\nFIjcbDBQX0/umkKl3kbbkf3LgADHiHwQseHwtqAc7GTKyoeA9u9sUIBaCQ01\n/xU0qfZ/lyieS0lo+0QRydyor/O36Js6iNXYzkhbq+Bkoh8P93bcyfA8V4j9\nKEhIBGudB3EbgK68ind1YdiRs3y16+ZMVSZGOus2ae3N6G0yKWzcm76wHXCt\n+DUvooVRwSFtvzBicqsHpypI2EqIjiQxSFzJjkCm7otpWMzYfKs46T2eXx2z\nZYnSb+YsBwSF67OrkUhjjJhXbEQeiRlmrca3dRFlUj0faiklYKpdvRRDN1hX\ndoBR\r\n=5llR\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8c086d191f6af508df32e315701c163be8691954","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-26T10:43:55.259Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.94","@balena/jellyfish-logger":"^3.0.147","@balena/jellyfish-metrics":"^1.0.414","@balena/jellyfish-environment":"^5.1.23"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.19-renovate-major-json-schema-org-8c086d191f6af508df32e315701c163be8691954_1637923598670_0.7684917564406559","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.19-renovate-internal-patch-4c3e160738c9501566ee1276e01a505b3f70dd89":{"name":"@balena/jellyfish-core","version":"8.1.19-renovate-internal-patch-4c3e160738c9501566ee1276e01a505b3f70dd89","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.19-renovate-internal-patch-4c3e160738c9501566ee1276e01a505b3f70dd89","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c3307f723675190f38ab371a10f0d5994d45746a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.19-renovate-internal-patch-4c3e160738c9501566ee1276e01a505b3f70dd89.tgz","fileCount":212,"integrity":"sha512-QknlKytsok7tGtj1LtXVNyzERzi4tM0R0txJbqlLA5KEKjfKWQx8qVd30BvYbdJHxYWnPJjwr5EomUfp2+yfjg==","signatures":[{"sig":"MEUCIDGJCk5uAhB+PMcZPiYpBvUaBsQq3jTzpvfbwTN0+j/aAiEA8Ac5xAjgVarLI/7PYpzBCyyWMyB8qQFTnx74TLAa0A4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":778241,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhoRFzCRA9TVsSAnZWagAA3tUP/3exnSeCs4KwdWxKNlUu\nfjV3k4wgjXOlC2rLBHGQWfwBmLsdGxT1HrV/iO7mSNf3YYL54ACRyAS5YHRx\nDsrDHccpo9cVDsbYJ5oxzw6gZFSi8VB5iwRoLFjfEXO19UeRQYS2dBv+/c4G\nd8l03dus0EpZgSSCz9hbqw/e6s7b6zie8C6r/bgDI6B1tWxkwbrezl9Orbxq\nwqYDrcn8+2UvsACIxoo4HNyoutvTLh76D4mggOCNk78/KkJ0ysiy6/6miL0O\nSPjXetx7DJw8X2FkcTXpYociGFfLVvx6q1Tj9n/pMGvXOGX7QMdryyCVxwMF\nHnEPtdaQHQiNaXj5WebTNtFvk5JsaQuLpt1qQ1LRfa5lIBiYeAcbrysCVS9/\n++b7eXairpI2J9YWuzqXMjsT9mvcc3fTQqBocZmJBoVc04y/rCeSAROfbaji\nvbhTryPLEX4vv74fAVPuwgVruIHwAUyDsojcZBSGrFbyDl6xGYpfMITbSHIS\nAfJDsBIa5hoQ8BM/zIzMCyII0EsIF5WrOrtgJJx9JQx20f7kU9fVO75sA0lS\naoJTlEFKp7UykWJmCUyxAPo71gJNjbdfOc/gMixJAeHvDZXGBPPsq7r7x09T\nV3IlsVa4TcfVczp0rTR7Lb7VfWyNOsBpeJD5vhztY2yrrlEbSR0dR3ztrKmb\nKieC\r\n=vAEh\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4c3e160738c9501566ee1276e01a505b3f70dd89","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-26T16:50:57.217Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.96","@balena/jellyfish-logger":"^3.0.149","@balena/jellyfish-metrics":"^1.0.416","@balena/jellyfish-environment":"^5.1.24"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.19-renovate-internal-patch-4c3e160738c9501566ee1276e01a505b3f70dd89_1637945715320_0.37468875866753826","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.19":{"name":"@balena/jellyfish-core","version":"8.1.19","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.19","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"daa786a32d225c380ee5958fdf314ce4d27ec4c9","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.19.tgz","fileCount":212,"integrity":"sha512-2/gbPutuZRaU95zTDGF/MTxazg2mm73/m+p3voyt64H7zw1E+5dl/nnOzimxIjec9FvGiPP6b7nhwiXJh5V1HA==","signatures":[{"sig":"MEUCIQDx8eIb/sP+GTYACtusplAjELzXOVW1ikEKeX67Pjra8gIgFu2N0/aJmAjz+Sl0VYpWAnbeSP1m43KStrsQpYeCz5c=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":778176,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhoRNUCRA9TVsSAnZWagAATx8P/iYoe9PvvkHmp1IQaXBJ\nWxfkzSr7sV6XpCf2HEDPTEqZM89dqZkrZdrKFYbPjWiELH/NDmmeSyhHkqRN\nFaREHQeMnLQfzfD0u5tEmKQIsKj0BW0UJqmKGZhN95hBkuz/VcsUmKJWC8Bf\nnNaITqxDoov04i9Lh0L7iOSv64SDse3lbYIlpkek857nnH35P0L2/kbKJBcd\nKCeNoFIutht/X8w7WtMBI/fRy5bX59rv4euCVOu8iweQQiewS6/54bKAwX63\nOM1ES46mffjuFjnuiQIcACqOhzZ+7zHeCpgQvUdTWZqP9A7JrMUZ3rtPdlht\nvfIIE6Be0K5WsOtH61mBSP62nfyMGIt4rxdOtl/x4FstN0hns98WGJJCPphZ\n3G+3BoBh/EKnG8Og8lRLHTq51WB3hsS9EXFPgbdmNoX37TijGFTUC4HgOSqS\nmfChxP9nzj6qNRRCkEThcq65FkJWb6Jff/cwWeadNNg80bRIhvLyTEWnNzew\nlq7sUejMXmhQsPqFC2FKfkXq5s32AQ3awT+t0im0CfEvPXNJiiLHbwhgdnYg\nZr/nL9ydwdxIHbtL96axye1X4eZHAlJG4TkbkgA8MuQpkykIho9q9i5KbvpJ\ng7hwZDFGDknQz33YNu34S/LTWSXrFl2dJQDnjmI8kAJJIVV138U8yNL7tQlX\ndnm6\r\n=56CW\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"14cf7dc01bf705722dc7f9ae514da110285b6db4","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-26T17:01:01.391Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.96","@balena/jellyfish-logger":"^3.0.149","@balena/jellyfish-metrics":"^1.0.416","@balena/jellyfish-environment":"^5.1.24"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.19_1637946196105_0.7999269758726508","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.20-renovate-typescript-4-x-65bf4cb1b697f597ae76fb59b2b9ec8b10935655":{"name":"@balena/jellyfish-core","version":"8.1.20-renovate-typescript-4-x-65bf4cb1b697f597ae76fb59b2b9ec8b10935655","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.20-renovate-typescript-4-x-65bf4cb1b697f597ae76fb59b2b9ec8b10935655","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"855c6a312ed3c454b6abce5923f5a8ad64ac2e3f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.20-renovate-typescript-4-x-65bf4cb1b697f597ae76fb59b2b9ec8b10935655.tgz","fileCount":212,"integrity":"sha512-2mJ0MKNanMlWLesh5rLzZ4Yb+/eUU09Tq7L92JfT4WaLzCGyl5g+9CUNuV9Lnv0cKBVvukNiJtkbe1m0xJ1xkg==","signatures":[{"sig":"MEUCIQCjLsqCnsTU0I7/dIh8bTH8t8TuKohvvqo4PUs9B4iDZwIgUZRd5A1sdrYWjZd7fWXlHwmYV7sgL9mHe/KZPDw22J8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":778329,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhoRRLCRA9TVsSAnZWagAAUKsP/R2c+w0mRUTkFXBsULxG\nD53Zwfoq0rFbxuTf2GjhKaygSoMhMeKA5Mjo1bDXsC+h5ysd4S5O9fRpbcWP\nT75/x69AZIroLhtgXJ9chqWG+I6D0Pd7rz9PlGviJSMw5dVADEFyDYCsOO8T\nsWNySjJJvXvLiSyfWL9xrmzSSorCf23MLz8rBrsVDIMkSkBt5uEbNBwYTFMy\nfHdmsCciV5uEuGnJ9adRkjHCM5IyeDU0XnRFSleIHyC4JoPttF9mh0M8Agf6\nvN4G9EDhrPg/zc1PzxrInVnaXzIx9ahwc+vnxN1OeBwA/NjxQQlfR1hkbZB+\nYf6wlzoG32i0+mkQSwumz+6yctheC12VRVR6gcJvuE7Rz5qrxrNgriDyiawz\nVfiXPSJyxdiUAF0ZtX1C7EcvI4tamuH3udwzMzl5WnJK+cK5GT2wkK82uhNa\nxte2DgBadBv+Uk5pWSmhj1lwamLwlEYD7zUDVDkjZSEjCoNbFIKr5MVLYKff\nVym4ZkAnQteoEYetCxvmcSMXJW7eMOUOqsIsOZM5Ba43/2dUIycbaG5mYWcA\nNWU0aU9gIXupXWuHWFnLmgZOb/LPydldH5aVQ9mSyxx67IOLw0r0jANd4FDI\n2JHHS+C6DyPrFn7pbvhgNNsR00hUjmz+8L3ud/7/2LS4CbGYK2kStc1fGM3Q\n8ZkM\r\n=Vd4s\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"65bf4cb1b697f597ae76fb59b2b9ec8b10935655","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-26T17:03:36.141Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.96","@balena/jellyfish-logger":"^3.0.149","@balena/jellyfish-metrics":"^1.0.416","@balena/jellyfish-environment":"^5.1.24"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.5.2","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.20-renovate-typescript-4-x-65bf4cb1b697f597ae76fb59b2b9ec8b10935655_1637946443543_0.7765071550877289","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.20-renovate-balena-jellyfish-config-2-x-4021ecf54b499d198d34e00c266334f14315e06f":{"name":"@balena/jellyfish-core","version":"8.1.20-renovate-balena-jellyfish-config-2-x-4021ecf54b499d198d34e00c266334f14315e06f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.20-renovate-balena-jellyfish-config-2-x-4021ecf54b499d198d34e00c266334f14315e06f","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6f1ce187bfa530b50d29ef92f0a03292e832a332","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.20-renovate-balena-jellyfish-config-2-x-4021ecf54b499d198d34e00c266334f14315e06f.tgz","fileCount":212,"integrity":"sha512-MLsnufSDRN/QfThSA/Mk4ItsIa/0FpfyyHofMIXV3mJBi9wo6WJAYDjj2Bp4wdseRbo4Hs4bc5Ehl5R32voqXA==","signatures":[{"sig":"MEQCIFtTNUA9imb3Ta4ar8n55Aag+NsMQAnGEEvAxSJ+zvSLAiAzNR+Dv/HbLUXtFyV3kY5jr8pLsWGuOi+ymmR65e9d8A==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":778352,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhoRRhCRA9TVsSAnZWagAAZUsP/2IEvR+r5B5/ewqABl3q\nfZb7J+T8XjI3bWU0edQH6nbPa1ErGF5mOz7U46U6lEoeryumy1ryxfhvnu1H\nIFHI/9MpBLhGZoN2re3PvwU9TjU8b8CM8Fprc3bdiOAVcMdyBRs9e/2DOx+A\n4QNniCNoKlJgXuz+CiDVOx5geB4PY7jMIjZHNplcp2hW2NR2Y/S39+TUJz9j\nisJcu1AkKUHg+fSmDmORzMXIGTUZeVdY70LRgcQaxmnF4CP1qKUT6jbI/SoB\n6puef4qVOUNnmA/EDHPP3Iu4pNO9du6FTv/93K/2kTAfT8a8EuvKiVC8ANnF\nV/g2x7AxSvd9H9PLuhmkTyVoA14PU8W+FgaNa6FC+z2q2Bi28xHdwqxYhxts\nOD5wlrQ8higrIReMAzgAGOPz+K94Vqjjx04o0MnwlMZGFfHslIkr7v2ps6Wf\nm16yko/m4zj1x6YQukr2UzNUWGtbcrmnGM8gVA2862SwUi1qj/Mj9Um+9chQ\nqu1dwbgrXz6GpsxJjhm6kczyLX58lRGo4/t4kraedsGQ1wG3+EwlxRO3V6Mv\n5ToqBQN0+OmFaInjirCObLjBzyR5HusGyZthWPkLEu9kXoSvWMG5s85RXaCV\ncOcyA+vsXbklj8EBBgtSMTcMGmtUzW6v6bALMbrPlVZVdOREeceTQ0wsiwt5\nnGEa\r\n=ql6J\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4021ecf54b499d198d34e00c266334f14315e06f","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-26T17:03:39.745Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.96","@balena/jellyfish-logger":"^3.0.149","@balena/jellyfish-metrics":"^1.0.416","@balena/jellyfish-environment":"^5.1.24"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^2.0.0"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.20-renovate-balena-jellyfish-config-2-x-4021ecf54b499d198d34e00c266334f14315e06f_1637946465657_0.5358163821096167","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.20-renovate-internal-patch-8c840e6251f45b3bb27cea31d6a5d05f8c391022":{"name":"@balena/jellyfish-core","version":"8.1.20-renovate-internal-patch-8c840e6251f45b3bb27cea31d6a5d05f8c391022","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.20-renovate-internal-patch-8c840e6251f45b3bb27cea31d6a5d05f8c391022","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"57cb44bc99e7c2de74b45abf868f633536343b56","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.20-renovate-internal-patch-8c840e6251f45b3bb27cea31d6a5d05f8c391022.tgz","fileCount":212,"integrity":"sha512-K9qjllSkJmBeByXYJJlJBMcnSukCYqyyd2eA5Lo36o0XPwaS3gVGdZM8nDWLhCC2X5mesevBTUBzbUDBGVjIjw==","signatures":[{"sig":"MEUCIQDHqCyg+V0ddibs36tp3H8Ag0JwLxy6VX8dR6cCZcfpQAIgBoKQu9sNPRF4sYyCPFXqDqTrcRqWaKWJA0dd3R9uXCs=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":778312,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhoRUgCRA9TVsSAnZWagAAJkQP/0s/JhFi5SQ5zugWvwD7\nONYW6vomvgHUHun1nkcQop2aZGxnl+44UnOD2gCoCLdnWkvtWInfy7iu0ZoL\nkfggttG2QhCqVC5hX2LiNpCpePEUdlsUhtHS/9oOqZRUunRTnQdv3dcBKRE2\nxHWGRdXQGRswzv0gNv+vr3k+0zEcoIPAeYjc4ybPRupLiQ2/Eu8ul9LAjbjm\nYZyyFplbVQ5br94X4ppTBrRU42x5fwhGNr3NbwTxkjLatOOEZtINz0vkFQ2j\nilpVQUpHYfwWOggG4mS950PkiaAxdjfgT2n14kzilc0gakGJmD4tQmM7cG2y\nA9/b4/jlYBSkO6s7BqLg49yqWP9dKFec0/K32hSCFeXB0ninBMmpQ9azMse6\ndGj8cb1tPp42+YcjNwo6IjGCc2ARthg4Ih2q7JtfJn0RW2HgoZkXwzc03EQo\npx/nC7xZw76v+YMXKuJU8w0KUOvNKqAGHN6hnzA0HO1R1R2nqun1wJEBmnRs\nlHv+i2PPs+PmPh0AtTb3R+qEsBfi4h2w9MvsV43a2aU4Mbu6ni2xHFvhWKgN\nNQCXJIjLA0yoIRYOaHk8kPXFO+v1g9TeNFhGW/8acLRr6FVf4PELaFAOsHfv\nYbBAdAJvbl1aA/ktxXm0y/XUhVpbHR1rJQD3H+5HimwfTuM0Wz7aQ/eGA9QW\nCMe+\r\n=raF4\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8c840e6251f45b3bb27cea31d6a5d05f8c391022","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-26T17:05:36.201Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.96","@balena/jellyfish-logger":"^3.0.150","@balena/jellyfish-metrics":"^1.0.416","@balena/jellyfish-environment":"^5.1.25"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.20-renovate-internal-patch-8c840e6251f45b3bb27cea31d6a5d05f8c391022_1637946656240_0.5732497959341976","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.20-renovate-internal-patch-be0af9a0c39976bc6fd67dc60e5807a15a500ecf":{"name":"@balena/jellyfish-core","version":"8.1.20-renovate-internal-patch-be0af9a0c39976bc6fd67dc60e5807a15a500ecf","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.20-renovate-internal-patch-be0af9a0c39976bc6fd67dc60e5807a15a500ecf","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"876365a9de9691a3fc7f565b7bbfefd8125fdf53","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.20-renovate-internal-patch-be0af9a0c39976bc6fd67dc60e5807a15a500ecf.tgz","fileCount":212,"integrity":"sha512-GYpzN3k8NCU3O37qOMsF4Yt7Ba8FY7pw4MGmU4UJF5r8TPu0DxGczbUghSRZHHRG4X+0mI3czY0C++92JxDRZQ==","signatures":[{"sig":"MEUCIQDN7fbHjjGgf027QmxlBGQnnQ4qZa87/o6h0E9iG1Vb0QIgfic9bB1ARCLfbDIIJQfXgj7BotItQ0C2FaQSqTyOdXQ=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":778312,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhoVGdCRA9TVsSAnZWagAAA/cQAKBrE+Lf0ukGk0eW5j4t\n8+hKf7CLohQnOnzdxINAnxxKXUFMaItkdt8qem/e4isR1iLUb+dVxKLAxFa/\njd3zJ0r/sq9RTgjeYTqUK45dW2Gahs93cWtigs1T28g0nAv21G915sKx76nW\nNa71aB9ps+PCKAzmZ4EFQpqlyW0PhsgRNEg6LLfH8k+vs2XZolJti02stxU4\nlJ9rJYwCjL8z1R7ip+KtoCk2tZLt+9BZFZxGJTrWVlp144jZNw+7QgHc1QmN\nHv5s0WxXQ9GIFWuAShlTTF/N22UE7jCgUa81gIqYmHXE2KJyHqOw7AmQ6yUo\nW+BMrUpdurOaPeAy3pKa8pRcwbunJsAcXdVgDwUFKgzJK8pra9nSPOd+pfFu\nhFo9bY0/UDWZ42IH64Ch2UUYm5TUZCKVJY7dYtorapWdjAXKTvjcNO1lbIWu\nz6qNLljE6JAHT0xqnUXkfTZ6WRRLHwql3qPrwBm/Gk7aQFZdtmmDyuxEoekl\naPEJyfg346YV6TBCotCNLC+RGZA4tpwhbKizOJxxbjsPvQriPFPYZV9pkb5A\nAZaLDEzzfP7t3CuxTHlcG6cHmfrVK+M3X0uRJehRAoJUn1nf+uXoX6VULKo2\n0Xndf/mWo4HLuOer0MxcNAPUH+nqERRmmdSQuPXS3MoDXl5MmUDB/FK4G3KW\nMFC7\r\n=Qh2P\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"be0af9a0c39976bc6fd67dc60e5807a15a500ecf","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-26T21:24:41.296Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.96","@balena/jellyfish-logger":"^3.0.151","@balena/jellyfish-metrics":"^1.0.417","@balena/jellyfish-environment":"^5.1.25"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.20-renovate-internal-patch-be0af9a0c39976bc6fd67dc60e5807a15a500ecf_1637962140788_0.7231573858642684","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.20":{"name":"@balena/jellyfish-core","version":"8.1.20","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.20","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2e2aef82f24d988770b0d3c4dce4518bda1b6d3a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.20.tgz","fileCount":212,"integrity":"sha512-YYeoHHJeyMt81FwhmxmxNLVcVBxuNYhsCHBheJEyL6vCfOdvHtnZAKKWzXj5maWF92Mf36TITG48ZRP+MWFCdw==","signatures":[{"sig":"MEUCIBgK/skjavjGwgbv3UcqF886lxa9oPFu/ycS6TFg3Km+AiEAg3ryQAr85rs0yh95ltwEIb64JI2drulh8S8AssbUm94=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":778247,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhoVP1CRA9TVsSAnZWagAARfcP/i3siQ7lQDjC/ixH45SY\nppNPSZszsbGDHExB3dSH0oO3NkQB2wIql4hxSMQ3mT87J4O1MoI+SIwMlWHj\nmuByruBnyW5wtOpcxna0itxdm+AyH0p2lMSIEk1fiInJKVmsi9rVda2OCOZZ\nENwK1qLsORAI9CCUZ14mLrX0D6c+t+wOhhvQcrl94s3ZHGfYig0vt6RUuZbI\n66SV5++1XDeszm+WsOHgoPnlYmDIxUFr2315bMU36I70mfK01VtjtBu4lhXV\n0kywCgpd4Wwyyie7snIAfgbSQAOFbUw4LCspKd9TgxDDb+XHiJp+cFSz9ii1\nZonKggeuqYXphb4rjtNYrnm3fF/yNqE2TImd7gM/8Tfoiq0ohJeWX7iDUPq2\n9d+VC1btMQjd0HKkWXKZE3Sp9NssIlmN8EOf1BwCMb+L1yK571rbZ08C1j7D\njbNDlwo0Y6WkNB15jzawhM02XlKoGy8t0jnWCcP44yv0vgC0NIlgKAdtau/t\ndDdFkKSy6QBbXYur7fFuCkjNw8lt8+2Sukj/q9mUk6h598aTCpZ7KiYAO/+u\nuxOTrphPL+S2LwgA2EQzk3eYJg8zWNpq0xqPlrzntcBf0nx7Sxoqjx4ck+x4\nANHr62ecN18yCpHbXdMwjZDA9zxe922MlkFzHsT12VQ6FbknxYWDG9GuzUQu\npGXc\r\n=yUhb\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f632ccb878098db5d22282559124bae78c8d449a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-26T21:36:52.442Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.96","@balena/jellyfish-logger":"^3.0.151","@balena/jellyfish-metrics":"^1.0.417","@balena/jellyfish-environment":"^5.1.25"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.20_1637962741398_0.7573747195427916","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.21-renovate-balena-lint-6-x-2bc9d4ae9103678e71be6a42c6717a6f15d62372":{"name":"@balena/jellyfish-core","version":"8.1.21-renovate-balena-lint-6-x-2bc9d4ae9103678e71be6a42c6717a6f15d62372","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.21-renovate-balena-lint-6-x-2bc9d4ae9103678e71be6a42c6717a6f15d62372","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1db8a6e2c170f1a95e9603940e94842e794f925f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.21-renovate-balena-lint-6-x-2bc9d4ae9103678e71be6a42c6717a6f15d62372.tgz","fileCount":212,"integrity":"sha512-uFw7QutUVuNM2FuGsfWkDzcH6P6xrGWgAmXnE+cOmhbbRVQl/ReT/ct+0adLBhkycNysgNdBXZMXVQ06ESo3pw==","signatures":[{"sig":"MEQCIDElXYNORN1rCuzVxH07jgu/2on4cr4S7LOtRvjpUirnAiB0jHvuYqAxj733QZDBTmBkoRrcHRwuBQI1dQMxcbu7IQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":778403,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhoVVlCRA9TVsSAnZWagAADvoP/38uSVWhA4QC8uQUmBmu\nBWxjkJyLZy0FYsLI5Pps8D/OguWqbv47d5HRPUGTJ65i6Te1mt4Jc1zvr9Rc\nOY0U9FwyUGKznB09GRP/P+6NzPaoSluRGWQFLa2UXWKIeC9HK0CUQ1e+2xCR\nNHwcl7vjg9jpzYocGjY96VAXlLA+2EJwmWQEApk2ECHzt9oosi+pCw130t5C\nQ0audvcUjIpMldwF8msNIa4P3fOONKVpGnvAv1/4jdJnGkaQLn3vmRYRWe/Y\nUOYaMx9WjG1PG/gQrwe+0Bq0bJi8D/hnFMQXAtNzNOmemefBVIROv1i6gCD3\nC63uGTaslqtxr3MjKLrf4RyTMSYQ6wyMQcHfpLbY2eusz8DcRX4eefI41Zt+\ndRtcjzSxmq+45LBxUvJdKf2DZ5B1Gs8yhRd3N2CAsQNqvC/QEJ4dHzotCDEd\ncOg98/G7cDG8m8uCzgH/W2aHvLJZZJwckWYtr3ioK4aDPp4/5/8T4IWgP/jh\nBeA5ghQOZoBwswv6YuasY5QldahcIZNuk8hKfobyg5K+otYD2SfKnh7sqUUz\nbJX8n+ChZQhagFv4/3EPmR6C5l5RAMPmQ8s65QUKM3Nzx7aM7RO5M4srVOmS\n3MB3sIN9IItebJyRkwHHAB9AGNrG9iQjUJDO/pemNYA+zLiem9N9J+PIBdZN\nj1mW\r\n=N3xa\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2bc9d4ae9103678e71be6a42c6717a6f15d62372","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-26T21:38:49.123Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.96","@balena/jellyfish-logger":"^3.0.151","@balena/jellyfish-metrics":"^1.0.417","@balena/jellyfish-environment":"^5.1.25"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.21-renovate-balena-lint-6-x-2bc9d4ae9103678e71be6a42c6717a6f15d62372_1637963109138_0.8050566218453685","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.21-renovate-typescript-4-x-c4cda2539f5070c58bd4d463e3fbdb864a9769ff":{"name":"@balena/jellyfish-core","version":"8.1.21-renovate-typescript-4-x-c4cda2539f5070c58bd4d463e3fbdb864a9769ff","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.21-renovate-typescript-4-x-c4cda2539f5070c58bd4d463e3fbdb864a9769ff","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"23c85870c5f76c41a5009c3db1397ca8b1d9c9cc","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.21-renovate-typescript-4-x-c4cda2539f5070c58bd4d463e3fbdb864a9769ff.tgz","fileCount":212,"integrity":"sha512-u0El5sIvgRb3R8uQKCLxMqfbRcNX30BCmvQO2+DWuN6pNWYdhkgeXHkmfWrTljFtwh1U6qLS6257beXf+dG/jQ==","signatures":[{"sig":"MEUCIQDIGUr6oG9N49TullSWsvf+SZxPjXEG3c1TE3hT4HGU5wIgEaxmjFj04qRKW2qOKQOG+zybrdt5JRmB8UlzIoEof24=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":778400,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhoVX3CRA9TVsSAnZWagAAnLMP/RN3a5SqLgC9dOjsLQcW\nilK6J0zuJXPIQevKd9JRVC8XW639wX+C3hxn6uDv9U56eSrN3D78fgKQBwNf\nlbvoRlazemOK5JrgjloQdYAxXx2M/E9uVm6IeW4egvrujsAOAJYLdkZjdYrh\n2fTQ3O5PMFXWpsx/Hklu7nu+vJKurCdZF0w1RnX+awlkkwoH0uwyK2nkIVEK\nXO5LrYGX45DW9MN0BJZjVASsg1C1ItZAr+jEYlah5O117J6tkduipUoL9rxD\nVD/IVSw+KI1rxs5ki1QlNP0D82f2Szl0tJwxa8oucxWKsDVYU8KOPT+rAo9f\n/QTHHEPRIab9mLKGsfN8RTUyEGUciTjWn6Gv0MDZRNmJo8cXLncInc+PXwja\niNpWjuspXCmKG4t+YYY4CV3K2PKtuh8SM5GBIISmB21FjOHKOc3TOXE1QC0D\n/LCIylfnOKYvNMi29hCx0h7HnELkJGIMIKUyJ0f73cY107OjnPUG52dXsmOu\ngbGQLUxOq6cArLx9w44LaYTJBtcyTnoCIOVsUpaxidHcvmipovOsPuAGSGAA\n1gfbyDNX2BDL/QMJZu0uLrPuiTB8RXPE1/1n3OhoMjnYxQYJxPplmvquEI/+\niKsiRpX4P9qF+87TYmePlZ+lW2Th25OZpv5ubcrAgfCNbE7WiJVhKQlQk9ho\nxErC\r\n=/eQk\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c4cda2539f5070c58bd4d463e3fbdb864a9769ff","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-26T21:43:33.712Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.96","@balena/jellyfish-logger":"^3.0.151","@balena/jellyfish-metrics":"^1.0.417","@balena/jellyfish-environment":"^5.1.25"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.5.2","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.21-renovate-typescript-4-x-c4cda2539f5070c58bd4d463e3fbdb864a9769ff_1637963255388_0.785926805361326","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.21-renovate-major-json-schema-org-67c64c7fcd276ea87c7f3a6297ff188d02621170":{"name":"@balena/jellyfish-core","version":"8.1.21-renovate-major-json-schema-org-67c64c7fcd276ea87c7f3a6297ff188d02621170","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.21-renovate-major-json-schema-org-67c64c7fcd276ea87c7f3a6297ff188d02621170","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5f573c09ca7e46c188e959d19a56917a7e14fd82","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.21-renovate-major-json-schema-org-67c64c7fcd276ea87c7f3a6297ff188d02621170.tgz","fileCount":212,"integrity":"sha512-fZqwcwyp35mYOAoJv+ZUodOqZ5yvWFk/RceuMwCbJAxrpo9l3cZ0oQFhTBWI4PRN4hD4sDmoyI1jWFTURpdIKQ==","signatures":[{"sig":"MEYCIQC2kMMIUockdczM9oAgxrIfZkn8lA8Z2eg2eEGXvM/PlQIhAIXS35XC3xvOHmTpWHkCLKAuBiDP+U9Uun0cFb1LzHqg","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":778415,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhoVbZCRA9TVsSAnZWagAAImsP/14xWS4doWReSdiZNhgy\nJWcZa0NyL7moMn+AeEWJ+8T5b7l30BBrbhl1Soh8xYYbQUFQjAfXWvTmt9ML\nIv8ZBBnGuzvOw7BpK0jsbEHFnt2XiqUElw3+lvk5BtKgASrvLvQMIeZfyrFJ\n52rwYWo3PffJMe5Ao6ALOBu8PZxb+dFcoIm8iKsJSHOO8APPLKNuTRpMWdQy\n7TxUGw90Gwuky7VoyIcj5jJwEMf0Faufs8MDYzp23Ya9oi1qA2X8jeICIRmE\n1lJNRWTEWwlPkzB6oLceekL4qkrleycGS4F5MnxgR3baNedy0tKy3o1woAnp\nKdCCFnFhfJW1PsEoWzQtrxpk+aZOOP+4hIIswyDoHs8oRVPo2J4OxRhabCKb\nLjYPzjOS62zKHKIeS7CUjWPD8wPJVUNY+YCv/1H48aFSg1ektSzOVee1ifNJ\nUT80JWSYhlJRsiQ1Um3Cpmbjn67CfcrF5HX4iWu60UI4ou5QLn0BDK/GwsKZ\nW1ufxEr8phDYCFrOOMSIJI5vXXPdtyxg2gAr82DpJb2F5rP6KAzuVyOzOzF+\n4nyZbzCKpfg8wQAOOwIgPXk9NfxTptbUKZYIHhtFe55LpcOrQNP3HOEyDWgC\nuS/shS8Rq5q2y9q1BTzc3Uz9+RDRF+fEi6mGQDC+pCctID73zxIApuwPd0qQ\n8yna\r\n=41bo\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"67c64c7fcd276ea87c7f3a6297ff188d02621170","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-26T21:45:09.718Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.96","@balena/jellyfish-logger":"^3.0.151","@balena/jellyfish-metrics":"^1.0.417","@balena/jellyfish-environment":"^5.1.25"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.1.1","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.21-renovate-major-json-schema-org-67c64c7fcd276ea87c7f3a6297ff188d02621170_1637963480789_0.4551017012938916","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.21":{"name":"@balena/jellyfish-core","version":"8.1.21","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.21","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"bef4df0e52abb3be60842f313fa88601db2db1fe","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.21.tgz","fileCount":212,"integrity":"sha512-4XOjeaNlT9nrtQxNLmOx1HZvKC2bB4BMYjfJx+wDo/Mwb1VE9Yz7EgV/wR86/hKvHElSSslOLZnbA1ytiX25wA==","signatures":[{"sig":"MEYCIQCWRTeLn0VO4WiR2jJ+shyJdKZlvW/TCPJ9vk0at4bWQgIhAMkh9ODkPHxwohsfNA0vFaVPtY1t+SubAc42fuMGyZY3","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":778337,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhoVe2CRA9TVsSAnZWagAARM8QAKD9Pf2n0i58qHxQcvt/\nTL4nCHs8u09OQqNpz/4+HbtEI+A4el3eac2Yqp2PJRXFiP+I0d5XXJkobtVa\nkBtCW5oNzjT6EoIk3bobmpUs1qRsW4OFTDbyKpBpVeTKGtVQ2qhFb09L3uPV\nGbpWRG7UQgtH0TPoTUvhF99lBoIiq029DX6BqlrnqgBUf4HmnHjW+RBsi8Ey\noDIoX+M+bnEZZIa3AUqfpcIVLDdlVecsAct/x3IIQUHLwBDfVZtu627tFObd\n7DMdoSNXUqNnpkZf77loJiOBIzcnHFLkJeNEkhNSsvyXrrgKnRFTYoi8C38e\nGFsHRkCK+iHnZWPzT8/IkNSZ6bPhlV9gWATcgj5ElXSE4f3Dwi3PqXbXSwV3\nodyxhHdhhK00z5MWiF85mwmeFROyaxsT7ASiQDHJq0Q7M4D4cdCghkkwjgH2\ney2dcqNxa1mFJRg0+cE5GGF+eZZ7IIIGqOH4D08SchPtTF2V7BYsxWZWCdR7\nHuWmbVbdeqYG/rQXb2Jxm759H7X/psxCXgfNhDGkqh8ffwdzF3iGZOMWJOfb\nePfv04NClf0aiGPmfsMwc8bYFX+DU1Py7d8C80eJzHVcZjx8j8keJhPFMAZz\nYhReiAeDBZyZS8DhxwdCgn4TVuQVz4SP/fsET4OxQa1mavdE1cK5sxD0j6qE\ngmGY\r\n=5mzd\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"9e5f55d59f9c7a5c7c2d0d67f6467902a9b8bcbd","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-26T21:52:14.953Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.96","@balena/jellyfish-logger":"^3.0.151","@balena/jellyfish-metrics":"^1.0.417","@balena/jellyfish-environment":"^5.1.25"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.21_1637963701981_0.3973658747206916","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.22-renovate-typescript-4-x-23d82116e4ddc7b34278a7843625a6f0f53cbf4a":{"name":"@balena/jellyfish-core","version":"8.1.22-renovate-typescript-4-x-23d82116e4ddc7b34278a7843625a6f0f53cbf4a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.22-renovate-typescript-4-x-23d82116e4ddc7b34278a7843625a6f0f53cbf4a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"03a143e57691e32413860f18940bcaad98ab105e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.22-renovate-typescript-4-x-23d82116e4ddc7b34278a7843625a6f0f53cbf4a.tgz","fileCount":212,"integrity":"sha512-alovSs+FY+ancCORNlZs9h0y+oqxdy55xBBJS3gBfh1Rk04TJWaCRHWuf02uuhVF/rabbQmFFOjDkPRGq+tdeg==","signatures":[{"sig":"MEYCIQCDX3cZXVtHBcxqvXaubsxSxn77p9u6MONSx0xokbpy/QIhAPWVpUU6/UpqfcHjLyZhRtgyKrI47g8aGYkOTK5efegK","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":778490,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhoVjHCRA9TVsSAnZWagAAWfoQAIFnc7q8GROoSajcNTNM\nEpOd7kMDS5puRq/9O1jYIGTUwtv7JU6aJ6LiA7344rZpWcnFM9iz+xrg2ue2\n40oUvBte8nZgtnAf8arEwjTqTLzols4P/wKi1cnYphOOpSk1Y8JHBWOTuQm5\nYWbL08uyBk6rqzdoPumtCiC0/bbc7HhR9SjHB9EvWZfS7BIsFNy6Gnw5I0fY\nP0casE90GAuHvyefESORUr0+KlMSVxt1w+YmARHgReztUY+ZCCEgb8f9YgGa\nDjMmfyuS83IzJWPffB7flw2bPmCjzYm+JFZYMHxljzbSiImqoycVTz8VAYAW\nmYUZagQEM4q9FxoCzCgd0qgzl/EybwlBmlJ/pKiqTko/no8Z8p+n9KtnsnEa\nweXmIVs6ipRxGJoVL74/d4DPKeB8a+3R7qSGTTOMjLrDalj31fZtcKurvLOE\nCrxXFzsnQVBjSo1ZHqf8KY2BknwF4RiJ5OHWXJsIE+WC1g4rglMRQR5U4XRL\nTGVzKlgvZL0I4daVCel0w/D1R00dYwpBMHxx0ZpW8kD/fVUxLIWRvVijKtmw\nKbyYPQ+sIxMF5SxFTthbeqIwBCpNnIBSA/jl9Lkv7gyTAxmjJtRwOSIhejKk\nIV6CrRMGLikVhVypHNZaXWARfGsV9EOTNfXa/HdIIkMcPVPEygdKJoA2Xoax\nbL6l\r\n=k1XK\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"23d82116e4ddc7b34278a7843625a6f0f53cbf4a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-26T21:55:42.964Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.96","@balena/jellyfish-logger":"^3.0.151","@balena/jellyfish-metrics":"^1.0.417","@balena/jellyfish-environment":"^5.1.25"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.5.2","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.22-renovate-typescript-4-x-23d82116e4ddc7b34278a7843625a6f0f53cbf4a_1637963975217_0.702133320889514","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.22-renovate-major-json-schema-org-ce87fba76fde04499419b35513a4f480c9cc9d60":{"name":"@balena/jellyfish-core","version":"8.1.22-renovate-major-json-schema-org-ce87fba76fde04499419b35513a4f480c9cc9d60","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.22-renovate-major-json-schema-org-ce87fba76fde04499419b35513a4f480c9cc9d60","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b5ccfcf94e2ea4ffcf5e820c08228b2d5a147e73","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.22-renovate-major-json-schema-org-ce87fba76fde04499419b35513a4f480c9cc9d60.tgz","fileCount":212,"integrity":"sha512-kJaz9oIiLcshRAvJl+2a6dngkYFw4guRNwtdYq2vwzxL4EHMXYmlx+09/oT08k6wSAp6w9auxCgHjQ92Z4hQyw==","signatures":[{"sig":"MEUCIQCfTuO85sgGmFXd8FiOcRoDyRh6NdD6p001/dJQygsANgIgPZ+OTbvhZgMtHI4NdlYn9YTuBpIkB+7Oq6aBFmysBto=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":778505,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhoVkkCRA9TVsSAnZWagAAegcQAJ0RfWX+4bOqg2Dne9qY\nTP9xY21ceAQVzISuzJbMKAiQBjdC33d/EZFFMpWhZIysC3EaeUSy5/TU6CCI\n+/9OXc4wbQ6IAvGpBEN5uPDMheud20mNTRwQ9dWletOvCzw+xcE9CNbOnpXn\nM07kZW7ppnaSJ/W7Kx83PBmRsEJbvs6fH/tmMDqxPXwa7w6v5ezDWKXDT5pB\nCKrSdTDUEa5t+9jC4t+aHBxbtZg590ADd2vleQCLUes4EQR0Uc6+ICueiBNn\nYJZOPfGyILjxiPgC1HwTyuI0Qiub+D/sQyRH8OsjuyIJCzqMUpFPa5XxUNUJ\nIKdlJhKrmHlmG3SrZNzPBWOweE/j7RyQEBkHw9IHf8EYLRWAlsWd/jwO95NG\no0xbmt16Qq6zu2HEBbKOROe+ISWlXLmj7ChylHVSj/IJS8pVjhkVABb1qacf\nWs2zOWycP5lAHZHR8UOoQlml/hSXu4dfn/Yie40siz4Px6Lidz3yO/7plkwJ\n9WHAUwC0vb910lUVA9C2LXpWkBmkoiXMoR5n1or2RzgITD2ZsLGxw9yiro5+\n01QEhodzEQEWhRrbvA4v2+g8PrzT5PCbpQp2fK2FuqXxwiurxxYtRKMSW1O1\nuDTmNWtd+QGnZmB02OqaM7m0G2Cno86XyPntY36DlkEdhnl0azi/gH0JdMTv\ntApo\r\n=SV+n\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ce87fba76fde04499419b35513a4f480c9cc9d60","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-26T21:55:14.939Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.96","@balena/jellyfish-logger":"^3.0.151","@balena/jellyfish-metrics":"^1.0.417","@balena/jellyfish-environment":"^5.1.25"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.22-renovate-major-json-schema-org-ce87fba76fde04499419b35513a4f480c9cc9d60_1637964067888_0.7327628068019472","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.22-repo-sync-jellyfish-config-default-252b832f8c7bce7a2db54eaa51983bd09427910b":{"name":"@balena/jellyfish-core","version":"8.1.22-repo-sync-jellyfish-config-default-252b832f8c7bce7a2db54eaa51983bd09427910b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.22-repo-sync-jellyfish-config-default-252b832f8c7bce7a2db54eaa51983bd09427910b","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6b77ef22ad4cbdce34a129c203bfb359b4452f38","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.22-repo-sync-jellyfish-config-default-252b832f8c7bce7a2db54eaa51983bd09427910b.tgz","fileCount":212,"integrity":"sha512-GnyGLxdxpSwzdgz6sfDHN4cSr3TeDIQht0JNtdT2QmolUgbazqWIUunUo/TsyXaqupaPyoOAyAxkYIlGTf70YQ==","signatures":[{"sig":"MEYCIQCDLY5TKJjZzcZTI4zJB/hCJ6c6IMRmBXwTgYdzv/hosgIhAIXUJ7iPMeYt5a1Um1eULctBlsbkksvubnU1dhf8nj6S","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":778505,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhoX2rCRA9TVsSAnZWagAAtRoP/jcS9pqZQUcI2rDMrH8z\nIblzQnnThyCP5PLJqA+2nAzzzN8s/2RJCLBXvHny6zFNa0DGg7NqlwKprok+\nGuzv/7VWBgGaQOhMHDEkgFmwO5jIzIQ+NR0r2NRWPbSc3T3qzIToMd6QNDdr\nYmz1ZsE+hyC8PaEy+AqYPP4FAVA/rgjScl+HoVbijHgUp7HAS1ehgrblkYIC\nhcUnbl1yo67h4vet2/dFh/h7lLa5AfkUvCyq7RG+UEItTrmu5EvVgD7jUEZK\nAhPJ8hwErEGYcSTbkbWHSl4W27aYm4X/71I59QejRG4yEPsnBEhydiaGoV3c\no2sNnqRccrLdW2d5bGGQ6lhIuc7z5gNIGU4WSsIQFZPL6nSEvQZrRthDMP3v\nHId2oVizyGaAZMA3LmRc0Cozn3LaNarjIFuHrwpcLXmh4BKw//2w2y5FRieF\nBCpsihAvdSEKIxn+dF4YaQec9Bcyl10vwo29uZzDPCAiImA+emPnBAYdeI73\nYbOU51v6AlTKQ5h69nqObgfpYGu77CodVuzyI9yXOq0keQ/v6Qzg3OCKWO3T\npLUbPO1SrAOMIdXO5u+e5QZJ//i4OYJrqAxaci6tkOnDKcq9kfNNII9LkQdj\nED/MZ+SthnQ+pCrcNOYXEIis73Fu/It29tUpcwC+DzYAc8Apg2w6JhQZtaZ5\n0UTh\r\n=BYdk\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"252b832f8c7bce7a2db54eaa51983bd09427910b","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-27T00:33:35.844Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.96","@balena/jellyfish-logger":"^3.0.151","@balena/jellyfish-metrics":"^1.0.417","@balena/jellyfish-environment":"^5.1.25"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.22-repo-sync-jellyfish-config-default-252b832f8c7bce7a2db54eaa51983bd09427910b_1637973419414_0.4025835407101952","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.22-renovate-external-non-major-2ad0af8bbbedc0ce72d1054c844f2dd2faf94203":{"name":"@balena/jellyfish-core","version":"8.1.22-renovate-external-non-major-2ad0af8bbbedc0ce72d1054c844f2dd2faf94203","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.22-renovate-external-non-major-2ad0af8bbbedc0ce72d1054c844f2dd2faf94203","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"066b0649c3bd092e8ad77475c9c60e40202be7d3","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.22-renovate-external-non-major-2ad0af8bbbedc0ce72d1054c844f2dd2faf94203.tgz","fileCount":212,"integrity":"sha512-t3RSEtpttPihUvtlZ5pJ6qP3cUYHTD8hSktR8JiU8iOI2DNVgYUUZGFN925Pxuf2cRNB83Xcu/8TvKZTyTTESw==","signatures":[{"sig":"MEUCIC4N0FrqLV5/Weh7zIQGLefAlrT65M48cumN9Tw8AUDSAiEAtJHC5edbKjexmCxWLO6EvjPpaW4ZSSmWxsIlszKVlwQ=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":778494,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhoX5wCRA9TVsSAnZWagAAPhQP/3PnEQfze1Zc98eDDtro\nR3lFcCr4iSpOr+k4Vf0Xxi1Tp9yzLYDFrpTuP7zbFBV3xmjgqG3Yrb6AKusG\n4QSresMS/OCJP1WEp0aBZfgKyoVrAGltgCSrvogjgEKKKdopErAdhgotXWEl\n0j6nKGS0N+Z+LqpW5V/NYcApMY6mGoJvn+94son821dlU+sYEZ5sEv4TALQN\nj4r71DWre7x42BCINZOTyoHwwSLMuSM7ZGhlS/gyIxI9bEC2xQ+4tAIi3ymG\nOZbD50G1A2h5pQhE8MnIQLQZIKzOf2TAkIOJOwd1DmIA4LFm8jwAlMB/FCtr\n3gprsfRoBfLusliy+SLXlX8pIaRUDeYjHltTgzBEJhC46XrhnlrQ8adG6P7Y\nisyznK+FCYlpOxnAz73q3sCPGaYGTz1JeTsZFQZYRnSA9pVEHvOZtKwot4Mp\ngqoREeUUMNYN5FpNo+zKAyuqzADmHmDvL758TNfqaVJ00HyK8/G9zu4p6Gna\nrPHV484V8pfhKSUzVbZ68dHPNKmn+l47Q9mab7z6oCnbfbL7K59mOXRARqjp\nQtjgVJsql42YfDLaTLgXCZH9fYwzbyHY4+MsuOEFCfXy9HWhNSCcpPr4RR2R\nYRLv/0tH7OWvtE9sNGZTJiuBAsQNOh338En861GIfzkmTOfpKd8N8G/1k0Ap\nZZrZ\r\n=v1aM\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2ad0af8bbbedc0ce72d1054c844f2dd2faf94203","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-27T00:36:01.322Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.96","@balena/jellyfish-logger":"^3.0.151","@balena/jellyfish-metrics":"^1.0.417","@balena/jellyfish-environment":"^5.1.25"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.22-renovate-external-non-major-2ad0af8bbbedc0ce72d1054c844f2dd2faf94203_1637973615874_0.9316149545547674","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.22-renovate-balena-jellyfish-config-2-x-a4985c382204f02a7a354231156a39585b5de82a":{"name":"@balena/jellyfish-core","version":"8.1.22-renovate-balena-jellyfish-config-2-x-a4985c382204f02a7a354231156a39585b5de82a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.22-renovate-balena-jellyfish-config-2-x-a4985c382204f02a7a354231156a39585b5de82a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"23bb5a636b13fb8bf263993e5d2d274a4a6faf44","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.22-renovate-balena-jellyfish-config-2-x-a4985c382204f02a7a354231156a39585b5de82a.tgz","fileCount":212,"integrity":"sha512-bgymw65oIC3Es4SccCH84IIPtRIhv55DuVCuZ1gXgbgCEZOtbLtzT1sLPD6himrDJpjgYPZYR5Pb/1EYlFyL1A==","signatures":[{"sig":"MEUCIQDQsjQNEnjyyeiJ8TpXmWORPdFOaFKIiQqlKYRbBs5e/wIgLPWCM0OEy7mbMaxTx2I5CNlZ9+x1c75Gru9MMV/eFX0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":778513,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhoX6NCRA9TVsSAnZWagAALEYQAJRsBEV4YStRTjH6Vw4M\nzoV3f4iYTOmuN9PcEZQB7DwcV9G5E/h4jHoDTjOlpNgqHVLZxc42mjHfT9mo\na3aOeXH7gVdNe5yeZGFzaE7f/n2598vteWQj5x/XwefUNSsWP1miBXcRcxp6\nmSRd4v6Bq2xBduau523TZTCmsphP0sfT+Cd9zBr5ZHql9cGOyC92G+cYrq75\ne0VVLSHXszAehnQLxampA+QFf2TOQoZ8RlIxC25hWzxpA+rx1cRK+qL9mVF2\nj7/VrAJLFD9O+3Le6SHe/aGsxJtkAwwr2hQ5vOkH/kabshYZGtlaL7Av3ran\nr90GFg525i5E2lQQA2EyMgmOXfdCFGKidQpcoyVXYFd95NVKBkVY4fM2ctMz\nwiH/IFPqhWkJuzWyrc+rU19+Hk5t5fCn3HM4ZawoOX6ABh6ucsFUG4DoEEXe\nScLq01RdBQg+C25JzDmcmNCLf2azdH1e9hGUIzDz7QF9gWCj4SSJdnYiXgRX\n2OkV9vYCcclTX5iyZCmi7qd3cDlDxSckoOPb0NmF5wMIIknMpHWWyLc6Ku2L\nvNwdyjFJBrdMFh02CL7ICYc5KomcdpENl5BXSsvmv4oxIpk0EhSuXh4FFc0h\nPfymE9qjCR9y4KQOEU1eKRn8GskGj+rMcpEykQVZbbxNxN+rYO0mqhrHaX+9\no38T\r\n=Aun0\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a4985c382204f02a7a354231156a39585b5de82a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-27T00:37:22.714Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.96","@balena/jellyfish-logger":"^3.0.151","@balena/jellyfish-metrics":"^1.0.417","@balena/jellyfish-environment":"^5.1.25"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.9","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^2.0.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.22-renovate-balena-jellyfish-config-2-x-a4985c382204f02a7a354231156a39585b5de82a_1637973645055_0.01448446117420299","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.22":{"name":"@balena/jellyfish-core","version":"8.1.22","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.22","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c146338cec615404ca7ea0bba0c9ba50b322cf23","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.22.tgz","fileCount":212,"integrity":"sha512-NEe6KxqhsLK1VNBxYTOWh58/A9CI17BDMbfeaZ9d4eho33BP4ULBTd6GrcnPrP3SJkitue1ABWDxuqhLT6nv7Q==","signatures":[{"sig":"MEUCICnqamPaSZgzSYsvyPP0YTJhRvgxTG2QMAERK6nc9Rx2AiEAwej97nUerwI+f9I1yjeLZUgwsShjOZWLKTOtx6E+Us0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":778425,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhoX/0CRA9TVsSAnZWagAA3lUP/1qyBsbDiuPRjif0luR0\nZl0UFOY/VEsVQF/BnJdJjeHc+cMhM5IY+y35SNK6rWKn34SB+0MA7FlKecbM\nG38fPefRWGb6MaXKGhY3QokVOn3+3azBOZB7crCUZP8Shtn5jtIlFqZCqWge\nHuy3WiCfP+I36z6O/BmkbRnLTGde16qwBkbRXIWah8tNUBcUEvzal7tYXwH8\npKyZVrSqjlhGr/z2N9482LsqFlnjnsx4QRR7xPiKX5sHOeC64Ogy0Indq1As\nYW3AT1WuXLBuuypLaX4TE/1GHTxOR8aqhnHF6O4k3Ol4KmQhBj5S8i/PGmE/\nT/BUnJsO6uKYDGrk7Fen+c+qh4NXpMe07pA85i/LUgxO+cRlknoERhJ1VH7y\nLjZU1XpkTWo1QQCHT0FBWA7oQirtEAUpswjgZlSG8CS8nYcQfp1M6j8nW6Q9\nw5lP44HcoLFA5IPoNl2zgeKzkzsxdr4DPlguj7gLASnLthN4oged281D9f9W\nO/T4p76CIWiVYuBjJSjtyUJGBrl739shaqtk5GERrsUnDayi3pQRWQsFNLzF\nl0jjLblunU7SxaqZ7YjKS5aTquvei0t4rwPMfeeDfsbfIA9LnHkNMlTw6Sgf\nvYWAjJruKZi2elN6hWbMEwZdH+XJvFTeineql11vp784a6mR8w+LQY3xAWnq\nWlIm\r\n=pXGR\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e8936641199408ae31b6f547824b4bea1e76abe3","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-27T00:44:30.818Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.96","@balena/jellyfish-logger":"^3.0.151","@balena/jellyfish-metrics":"^1.0.417","@balena/jellyfish-environment":"^5.1.25"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.22_1637974004669_0.06723172337729166","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.22-renovate-typescript-4-x-42ca459cf8b0bbd1edc0851b6934f302b218891f":{"name":"@balena/jellyfish-core","version":"8.1.22-renovate-typescript-4-x-42ca459cf8b0bbd1edc0851b6934f302b218891f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.22-renovate-typescript-4-x-42ca459cf8b0bbd1edc0851b6934f302b218891f","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2fdb6930987cdf1fd86463c919dad57cd0549438","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.22-renovate-typescript-4-x-42ca459cf8b0bbd1edc0851b6934f302b218891f.tgz","fileCount":212,"integrity":"sha512-CkKFSfx9IyU/5TekywCwCt7xoXiu2fr6id5Vo24bCrn615rlQQc0ZGzScB86DBLI1zV+jcPn2u7LFbPv54bjdQ==","signatures":[{"sig":"MEYCIQC3HajTnv9HyFxJkZDzOz9KWruVFTmz8BbALGCd0NFH+QIhANo+EdTqK/hm9l98tDx7rVj6S6qSB6NU47kZA+vu1Fm+","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":778553,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhoYCUCRA9TVsSAnZWagAA5VwP/2R9nuJjlKOj6rzd9FCU\n7Eqj0ABKO7UlsRTxb6e/SgHmqoGnp0RMxU4V5zYAyfuJZeFTIUKUnyiMKf8f\nDmtrA6OiYjXOQ4PEs4Yf9/XUsL7OnccfhDujQ6qQbpKWfrJGoAxYJSjlGCHL\nII4oIS+4sLxwqEF46wDFpR7T5taxZdWC7FqDInOhLTfypqi/gPJ4mWULawt+\nmfS4FGL40zz7oVwFowuWWncB0B6qksP/nwiQmw6L7SCiBAGL4a0HOCokyZun\nL9hPAr6bJrD6V4pnoZpHZq4Oo4+RhelRtB4n+4TKlWT43Wm0NDOO58E5VBhi\nh+uiX8CpDrfF4hfw3WNpWlI6AinXxqyOFkSxcnbU98CUqnpAZyS4FEM2xXIQ\n8MyqjOu1bjQferdzaM+IpTJ3d5O1iKg5lFKs+5W1YYqGTTdgD2e+zYzp3exn\nPbUD/PFINqyYX2x6LaLKkZc7gzirZd0JAtaXL1GgKo1RxvGqspqMvqvkWREE\nbCE8e9yvdY9mxP2FZIXeejo+FqF42jdvRh7tAMZKWCEoaWz0uEMCSAKOVlIH\n9aB1F8gH9P8zoual7cVoGj3xGcCDFQvYu5PTq/+SKLiCGO+1sGh189S6xfKV\nnZSvR3WrxgCS02fKyIMIiP/vK1qnu4z1Zfh6EFiYQkAGJuF6m6qhEqIfTCLi\nYfkL\r\n=bdaT\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"42ca459cf8b0bbd1edc0851b6934f302b218891f","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-27T00:45:36.042Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.96","@balena/jellyfish-logger":"^3.0.151","@balena/jellyfish-metrics":"^1.0.417","@balena/jellyfish-environment":"^5.1.25"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.5.2","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.22-renovate-typescript-4-x-42ca459cf8b0bbd1edc0851b6934f302b218891f_1637974164231_0.4009769680747235","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.23-renovate-balena-jellyfish-config-2-x-593a0c78dc38ab80f104871d1fd80af6bbc43035":{"name":"@balena/jellyfish-core","version":"8.1.23-renovate-balena-jellyfish-config-2-x-593a0c78dc38ab80f104871d1fd80af6bbc43035","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.23-renovate-balena-jellyfish-config-2-x-593a0c78dc38ab80f104871d1fd80af6bbc43035","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"46cc3a46ce415abf16b8561b9ee40c3085012f16","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.23-renovate-balena-jellyfish-config-2-x-593a0c78dc38ab80f104871d1fd80af6bbc43035.tgz","fileCount":212,"integrity":"sha512-N4F/x3aHh0uYhrc0BsZS3lyuW1a+mqhFr3PVQLKh7VHnojav/lznrLh6Gp4wgV1aS1emfNBW38kLuN0wJcxVYg==","signatures":[{"sig":"MEQCIGZiQbcbue4Bc1DttzQB5EojJpIgWUAOgkCeUZKAXxkMAiBq9BJq1GqG+MkMKf4Huq1pGj+t7D5M8x/0rWK1TDVB5g==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":778601,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhoYDFCRA9TVsSAnZWagAAsXwQAIBFqJU/jMncVp3QM9md\nrqAmT2gHD08yPd8uypXlQS1QxXLrYCOTtYJV8bdGCrfYc+QBZRrkujVV/eAT\nLlkHby383B2s1H7gfehEdh41yCPpm7GrGD0TxjX2z5daoqlsebnCx365775g\nipahwJpDakNf5xCp8nBzNtUIsWI47FxNbnYy/eos0HQ0hl8aOJfUp5wHHNpS\nKUdMEGsoI+9XHPZs6iO9oy9w/Ns6XGEJBOff8I87OL96tT6P3AR/aJ486RW6\noJ4B0cdykkBu533qob78Wb6C79vrxgTx0vLtg31A5ibQq/2lPmdHe/lAXnFV\nixQpWQEhcLrdzaDTiiGPlzDi26B+scrbibC1cNDkdEtG9ytK5+n63cQt11Mk\n7oO7xmC+RaAMnsDMFBKGobL8zqNYcyQVN0oCgJt9nDDQxjdkI1cNykTIzC2m\n1xxgYPFW62X6CWi8FdXX3s1rXVKG4KyLJumTjwlUfgmI4x61uT66NBL4V0/O\nmna/KXkFe61odXI6EqppvZuIinGFRMzYbGfwQ+C1cbbnWb+05Ihn+fH8KhYj\nxlpBbtPVqFln/YRidE6pYulNkIWG2Ce11jzmHxAAGgAnkzb+dTUfa/G8kMvw\nTS0mZzy2qlIOYCfw+bVq6H3YfwGz5/02wPAIqGQIrj9aptEOwWqGe9XvLLaW\nugTL\r\n=Oj+k\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"593a0c78dc38ab80f104871d1fd80af6bbc43035","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-27T00:46:58.193Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.96","@balena/jellyfish-logger":"^3.0.151","@balena/jellyfish-metrics":"^1.0.417","@balena/jellyfish-environment":"^5.1.25"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^2.0.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.23-renovate-balena-jellyfish-config-2-x-593a0c78dc38ab80f104871d1fd80af6bbc43035_1637974213700_0.2435242364535437","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.23-renovate-major-json-schema-org-6911f871b062509f9184feec12cc6c89e05190c6":{"name":"@balena/jellyfish-core","version":"8.1.23-renovate-major-json-schema-org-6911f871b062509f9184feec12cc6c89e05190c6","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.23-renovate-major-json-schema-org-6911f871b062509f9184feec12cc6c89e05190c6","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"7b9b568580b9f2db5fb017ae32c749e77b133bec","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.23-renovate-major-json-schema-org-6911f871b062509f9184feec12cc6c89e05190c6.tgz","fileCount":212,"integrity":"sha512-Et7CjZn6vOGDyPVoXbgr5plw028qyla9PAHII8djMBba8Kh5GpYncgqLJ/e34icTtRQxCVyKjYKKjIYF0Qxrdw==","signatures":[{"sig":"MEUCIQCYqTtWN2QsbDNwWajRwF6HZ0UVt3bGPE3m8iEYDx/oQAIgCITBeH1n/dG2a7kU9oLzu+03JKqYsqShInRVaRTiJ5k=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":778593,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhoYETCRA9TVsSAnZWagAAvaYQAI0DN6rQugIoZkefCeP0\nNmNz+eXsFyXGwM2vs5jSv6t7wcDO9xUcACVtiQ1gofH+WHD85VssWbX682KK\nUfD/CLIHvv7ETokp2F6lq3NimiY/1wnZXUh50YOGIS0KTPGhBygrzTN153jE\nkHLTbRUydC7BJrgppibJn6th/1Ut19DP4RC18jQEZ2ckv9YqRQWKLRaFGEbx\nJr3JZZPSt5ifaktUu8+GQwciZ8P2VAjHG4wwXWJh/GejfiaKt6OuIcDgkRcL\niJWBy7gXtHTU5fuR5qupyVmvua5XZq7qhf46F0s6wbWj1LvXqDVy2/Siv7e/\nxmPlICKKnoxejM0x+5nSf7ke5i0b2MYJ+kVRSZNo9LYm3hsrHLfcChoSBVl2\n04wgVpxi2xubFP3wg+C0Wx20S8wa9v96r2nFldqqqrcDtpm61hLUCkQYxNnJ\nQBhuFNmDfyA2L/X0fdPXql5QpnJahB++X3qQSpS8c2VGDIIqsXRRlRaiGoAh\nVJ7bVU7cmB2iEPSd5uw404bOej2WzLbsuaczbthmCuGpNsS++zc5L/s1CVLi\nCEjMlI6IRoPCkUBaMAMoExUsf9VSDP6iQYBw/zDz84HZKPcM2+YFWcT9KIBu\ngmRBU28HeGEFvNBol1tXoZkkNQQQxlCJ/Nku8Wi5OxFqVYnxZaE8E+tX3iKq\n/ZpI\r\n=8CLW\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6911f871b062509f9184feec12cc6c89e05190c6","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-27T00:47:06.634Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.96","@balena/jellyfish-logger":"^3.0.151","@balena/jellyfish-metrics":"^1.0.417","@balena/jellyfish-environment":"^5.1.25"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.23-renovate-major-json-schema-org-6911f871b062509f9184feec12cc6c89e05190c6_1637974291270_0.9960170406483979","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.23-renovate-typescript-4-x-1b27b6e3cb99c8aa164c67b9169ce70aafa219d9":{"name":"@balena/jellyfish-core","version":"8.1.23-renovate-typescript-4-x-1b27b6e3cb99c8aa164c67b9169ce70aafa219d9","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.23-renovate-typescript-4-x-1b27b6e3cb99c8aa164c67b9169ce70aafa219d9","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"fd0d5e495e197da824f7d1cc1219463788af3db6","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.23-renovate-typescript-4-x-1b27b6e3cb99c8aa164c67b9169ce70aafa219d9.tgz","fileCount":212,"integrity":"sha512-QeIbgWgWo8CiLOnwQYAAups+gfMDZ2IPyhdSopCoDvqUqMQoWl88SVZsr5q1nEwKctomswLYh6m3vssfa9stLg==","signatures":[{"sig":"MEUCIGw+RmX6sAjOV3cof6q4eGYQvCJ7H7Z6KImXM+9fldaFAiEAyg6F32dfcLzkm5H6/9tUaBEEluM3jFT7wkentYu3WCI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":778578,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhoYGACRA9TVsSAnZWagAAO0sP/RH1rsN2wMKUgc3WcteK\ndfRMC8P2K3b2f/mDV7kv+rWr+Kqp7k7z5NWhrUKhe8fY8vvefC8KuXY0Cut/\nbVuSxNggU9hLRHmr5DvTBYK5qcPE5xIYA8h9konSaUleX7tAOhBWp0z3ERTM\nzUt5lrKM7TJUh0SjmajwYWeiHItVG3Y/n9+UIMGpGN1sEV+vY5qud6XxP5/4\nlOiIGGhpM2rJHO8JHB2bw90GvKnnMDyhf+RZAJ1Axn/hTZ8qQYP61qfiYhYd\nKXYXGGFJJhgq5CuIE0NvhIHMET7YGj2qkcreBLISE1H72oJjLDcggdSq84hP\nYB6CAvOEsm1MaRp5J31/c/HeOQuLtGO40HPyVsKTMxn8o4iANPNZZVK3u+U3\nAlmZdKq2+j9zNzlT38bqybOgj0b864ebEabdkupDO1jJiJpGrtc5w81OgMld\nt+1esr8rISZLZm2MAb4yPyhSilsA3Bo4nNdywsoZGtkK3VKJqmCdGt5GylV2\nyExp+jnDYZ1N4SspKX5L0E4t8cJhhTLbiffaVyccPrUCUiBdVH6fZvJ5bvnV\nZb21B9WG4SMjvrwz2IdI2Kv78hXbWAAsO16zlwLwg0TdOsif9npmDFYrlZV0\ntMS0TUuNJjJt4enN6l4OiG2SadiAAn6jFfeq9q99e8pCJOhHIu9ShKQJ9xPb\nQREh\r\n=ShOv\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"1b27b6e3cb99c8aa164c67b9169ce70aafa219d9","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-27T00:48:44.514Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.96","@balena/jellyfish-logger":"^3.0.151","@balena/jellyfish-metrics":"^1.0.417","@balena/jellyfish-environment":"^5.1.25"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.5.2","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.23-renovate-typescript-4-x-1b27b6e3cb99c8aa164c67b9169ce70aafa219d9_1637974400290_0.37654062252953535","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.23-renovate-internal-patch-bce6793191dfdb8869b25078ac556a6dd18070c0":{"name":"@balena/jellyfish-core","version":"8.1.23-renovate-internal-patch-bce6793191dfdb8869b25078ac556a6dd18070c0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.23-renovate-internal-patch-bce6793191dfdb8869b25078ac556a6dd18070c0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0ed45d60b0c8560ab2daa74a91524edecd050f01","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.23-renovate-internal-patch-bce6793191dfdb8869b25078ac556a6dd18070c0.tgz","fileCount":212,"integrity":"sha512-s2rCEB1ccGYhTzWwNEPuXNo4pDYK0qzDGNEiQc4byXFUqop3zTo+6Yyyyr7IIy5kugbK4W2DnDwpZCf0xZ7akw==","signatures":[{"sig":"MEYCIQCsdDiEEy8lw6h8qFb43j764NUd7zGhaKPEszMLp3mRcwIhAMeQVce7LSBLJxw5tCKPwg1ziHDKk5Ojj1JMyCc15ELh","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":778561,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhoagtCRA9TVsSAnZWagAAbAQP/i6dklhtADi8tq0pTxcn\nUw/FESU+b3pAWvkuJU/3MkLFwuzU2vT1mF0BreQmMEbJ914h4sF/Dhf273eK\n0VPYrLvWzD725iYD9Hz8emVSns34vH446h+WA452wdDWqhXBYvhV3hAS/gFN\nch+1fzE6t9YwNEbUC7pMO3PkRMo/kGZURBdXD4uajW6+9fQywQ30NThIzOGU\nB477OA8NbtUvUWSn0STjpEMP6vDnwZVi8J1A+xxfH3i9gKbyDmfEQ0W7YxYo\ncVldGTckSbrizfgJy9J8B6lUF9qfZkPwLHAsPZNiTv6YIvrt5JZP3zme9aHZ\nK+cf5WlISYvKQ+QXCy6YOc5Px/FdbOLv9ud3ZxBPcRL+c0LMe9DvwPo8no51\neRU+45TUV3bxagCswLDd4m5PEpq700xgd0EtWrYdgM+5OrQsMzVr+nHpEitk\nHUYmtdatwIHEp+WPjJD7rCo5GKQT1NUNAPveztJxJ2ODvqU0zZ9AQnvuAjsJ\naZFZhCaLuH6bu/EBPwZ17TzBYN0fQG0y4dzI7RioA24db2ZZSgWCokrmRJ3I\nQMelDJXn5M/PB4n7X274or1fLZmuvDDgwv8kqdhreHcBVsDjJyFMdSCqIgl5\nyNPZuzO4R762bTe4GmASH/8v4qNbmtEih5fsyFare/WEsRkTaHgDp2k3dDuL\ndn1T\r\n=D/fU\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"bce6793191dfdb8869b25078ac556a6dd18070c0","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-27T03:34:44.386Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.97","@balena/jellyfish-logger":"^3.0.152","@balena/jellyfish-metrics":"^1.0.418","@balena/jellyfish-environment":"^5.1.25"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.23-renovate-internal-patch-bce6793191dfdb8869b25078ac556a6dd18070c0_1637984300998_0.02950998179091302","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.23":{"name":"@balena/jellyfish-core","version":"8.1.23","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.23","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0ecb666e9f893df38b7ea396f3f644cf3ea861ef","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.23.tgz","fileCount":212,"integrity":"sha512-zGomkXOlFuTnjnqoXNuB00c1ISeggYrbTWOU4ok9vxctOyzLwk7/IjB1hlFFO499guasMuTcNIKWHAXYxxEGkg==","signatures":[{"sig":"MEQCIGYXcg8ZNKAetWrfG134cVnx0TvjtUPdXgzGK4acBlEIAiB6GHs7aSUF6ALTewomTg3+W1wJFpjVBa1mpG+YRv/Beg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":778496,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhoamKCRA9TVsSAnZWagAANUQP/RkZvFhOGeuID9VZ9jAs\nWFkRWMsYRw8v0W34EbAXwnff/rzb3zpIoJ6ftpUCgDrCuuyK+xNYhXNrNlbP\nTQCinbsmD46/TYcYPk4YBlYEGX3xCZ8R57r/Zs1JU59C9wLPtXZ/kqbPnMFE\ngsJNZUGOxKJfo+YLfGalMWEbYjO5P0HsG6LR0YmU5knqTSOZ59yItxcSOvNB\nOcMBIQZUl990tzBjsdJEpfh3/eS2U+yMF8s+T4ZK6TibguJ1dPCSQRc0lY0L\nZjmLmUu/hnhxI2nWzt+7KTZUePUvVDdrhFNw1FykGZtUKodyNJlZ8cKiONjc\nJXNOeHsttgG0CiAHUK/Yz7CATJWWtH9DfDGwwJz4PM1PmzwHttp0gDKlIRpe\nrQV1zIhCODuJ5gekplqwE7pebPHVsTd06CI9M+wSOvRACq82zKjIwUVXBilQ\nN1GPoe7LemsPcfzUiswGPy9vbcVVPNQ9JQPa3F3haP/HWlJIUfY8qplyNpBb\nI6GB0t2tbx6ZMroIcC0HqfLAHzRkRtKDh5HUBLQshAprT8vzcjQ37kqgqT5C\nKSiDw6bS8rtBtyuRxvgGRMqTd3WKDAYWEZniPJlp9ctlZ6N7nw+ky3BlWhdt\nS5EJvM0oSAGupBzELd92Ut5e4VhrNFzRYzHVBEQJuCtEgnvjEMP9QlyVNwQ9\n4+qd\r\n=gc+n\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"57e40d34232f4ba94b83458e80bdaafd194f4a6a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-27T03:41:59.294Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.97","@balena/jellyfish-logger":"^3.0.152","@balena/jellyfish-metrics":"^1.0.418","@balena/jellyfish-environment":"^5.1.25"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.23_1637984650548_0.1405093716119139","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.24-renovate-balena-jellyfish-config-2-x-4280b59198dcefb005db8245e54f1c9113d9210b":{"name":"@balena/jellyfish-core","version":"8.1.24-renovate-balena-jellyfish-config-2-x-4280b59198dcefb005db8245e54f1c9113d9210b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.24-renovate-balena-jellyfish-config-2-x-4280b59198dcefb005db8245e54f1c9113d9210b","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6063422c9e73ff12d9e822fb2817017bcc173486","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.24-renovate-balena-jellyfish-config-2-x-4280b59198dcefb005db8245e54f1c9113d9210b.tgz","fileCount":212,"integrity":"sha512-iAIgXHCU8wfJuHyb8VaGr6N18j3Xw2S9fmJt6+76c5HLDcVMDyreYwyfYgjfC8ukJZV3eTrCem3p/GUdBCCn8Q==","signatures":[{"sig":"MEQCIAboHCtF/p3p4X7xFDHbOKPLTZPGQz4gSo8jAfCQTGfLAiAdiVie8kqlNFv2BfiKZYcbHeiqBVWxg1g6NQSLazkdCw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":778672,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhoapmCRA9TVsSAnZWagAAoRoP/2WmSe8KDTe65awY/he/\nszCSyubdhFeUBL8qd/1CDM2BBRO3lcbTpqU/7P3/7/YGfkJT0ibJFbUN74ow\n4As0G/B6qh9U+rWpcAM3WPvYwo/N0MgR/OF1DFbPN6/oSrgTH2mASy2hZWlI\naqXy+2yo4bLx3avMjYRaggsUEhobxu8hIzLnaqYd6+sqMAelpt2ucf/9CfbG\n6D3eogkTwjOc58k+5PL5VSePlOqxA+SHMrg+aNfPTaR2SXWL4so5Lu3Hs8Dm\nd939l+K0duLHri/SFw759uVQPuBO+MZRAXsxdt+iy4JZ9yeuS/+/1QwdhCH+\nvcX4o64nleIKTPFuqyD9HDy5QHP8D5Z4kxiuCglLTaKNhrfJIC9B59xAKf6N\nuEz4CPM9IrpzWXNy8d9/eKHd4HzDVb+MyPQ1YepdIoKt6sIALJR2vJGH/Quu\n6ad0J7ttXA4nyeALrvFjy5mEDO8EpPpk5psrkayomDpBPXKeAg6IAg/sZ8ie\n2eJQSZLwKigrjGNs/LE1vR63LVoXyzqk9v2usxm7q14YV6NNo7rXfHnTbg96\nvz3O4MLztyyMaU626ShkzTa2i1Np5JszS8zaLFd6qRsTgS95LjqDFC46GyY6\n0WqtCVa6YTPjfoF+A7mWmSTTVJruHEoeFOUiCg/Tir3dhW28JkSNHk7dTJuT\nhA5w\r\n=+N/S\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4280b59198dcefb005db8245e54f1c9113d9210b","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-27T03:44:15.739Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.97","@balena/jellyfish-logger":"^3.0.152","@balena/jellyfish-metrics":"^1.0.418","@balena/jellyfish-environment":"^5.1.25"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^2.0.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.24-renovate-balena-jellyfish-config-2-x-4280b59198dcefb005db8245e54f1c9113d9210b_1637984870342_0.0514897942475121","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.24-renovate-typescript-4-x-610e2c48b57b40e99cadfe8d0747272c6a2aff75":{"name":"@balena/jellyfish-core","version":"8.1.24-renovate-typescript-4-x-610e2c48b57b40e99cadfe8d0747272c6a2aff75","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.24-renovate-typescript-4-x-610e2c48b57b40e99cadfe8d0747272c6a2aff75","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"abe35241656cddac48c06534a0194540359b2c48","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.24-renovate-typescript-4-x-610e2c48b57b40e99cadfe8d0747272c6a2aff75.tgz","fileCount":212,"integrity":"sha512-lOi10qPBvj9tkTcbYsl1SH4IxDa7Gdy4KE5ZKf/uiDCOFhjPyL3dIrvka+a94maQcj2APyxv/9qJjKi4IXgWXQ==","signatures":[{"sig":"MEYCIQC9hx3UFbtDKVFMUSWOSVvYmrY+jTOqumCAHjdRcDtemQIhAJFS2h++jTqhNCAM5KoLYJcXMD2oFTshwE8mc7a7k0d6","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":778649,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhoarTCRA9TVsSAnZWagAA5IYQAIqjX6zb/fYzF2+2Y12U\nw0zYmOCIE0oa3cY+x5uUj6pTPqV1wkQ7jBaiXzh93nhf54c66iuabzhWZWUk\nCJoz8UBmRZrMwLaYToKDx/UptmRdYjIB8Ysl1zFYLFaao2wU/6Zza+ntYyoR\nHxl+Py/DWfgmYjX0wa/uSirIr++KeoNdK8riVl8+Fh2eap9TqKsrUXdagSyP\ncoNQEpeZz11pwo7dCW3+04W8rBuRPz+T0gM7P9LugmtZH91Yp/p3+FHISsZA\nAnIVS6O/OkXoRD3Fi2EpDgLexTtYxjUxYNNMTDq9QppDXT+s2A9NAVdPl1iW\nCO0VR96BmYmuATaiGYizBBbWZ5vIYrkC6MP6CFdGFjggAj3PgFLvXTzn34Wv\nf/hfnJntd7rCv+scEzu5nTpHH7blVqyvPWLvNEz0HQpKt+2+Ta/Z7Lu2tdaO\n6vgDjhv2ss7RrPb87QO3Zq0pMJRwi4B/mkULrI3pYDpSll0tmtXZBEbVGwyv\nFR/Ebxcqi+JAXd8QhZueinOHrVpbyRA4NXNrG1IdkKY7H4pHiC7Ekt4Wtors\nnRdbXiPQjBTXlgbG+yb2EozkR/De+p64WEW0TyXNL2yfXRj1VHcz2yi/AOyx\npSREf97/OIwrrcirpqMblV5uUHsP/Oz70O1uMe5BZsDVyBu3lcfRwrgrZ4M5\nGNOO\r\n=JOEk\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"610e2c48b57b40e99cadfe8d0747272c6a2aff75","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-27T03:46:19.600Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.97","@balena/jellyfish-logger":"^3.0.152","@balena/jellyfish-metrics":"^1.0.418","@balena/jellyfish-environment":"^5.1.25"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.5.2","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.24-renovate-typescript-4-x-610e2c48b57b40e99cadfe8d0747272c6a2aff75_1637984979276_0.9571237086869504","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.24-renovate-internal-patch-2c318284424187da14a7c3e5ee7e15a178d97259":{"name":"@balena/jellyfish-core","version":"8.1.24-renovate-internal-patch-2c318284424187da14a7c3e5ee7e15a178d97259","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.24-renovate-internal-patch-2c318284424187da14a7c3e5ee7e15a178d97259","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c91d741cac57a697e8ae5b6dd82341847da6bf93","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.24-renovate-internal-patch-2c318284424187da14a7c3e5ee7e15a178d97259.tgz","fileCount":212,"integrity":"sha512-SuznEjS6qqH5gwkmCHLmaGdKBRQQQyWuXsfjBOHAUeghw+38AWat4qDTT4Lkc6e42Nx+/yWz3F5O+qADInuMrg==","signatures":[{"sig":"MEUCIQCFPWn2M+pfzwq9O/455jhUinFftSbcHqF/VHnfsBa8rAIgDjTVyWZOgF1nfWVqFUTt7I3bEXqK65COXcCRrSRv4yU=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":778632,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJholTKCRA9TVsSAnZWagAAiGsP/37hjbQ6Tu/TB3KgMfVj\ni6DPIVfXaWgE2QitDZQhb90by39qipIzQBaW4Et9hRtcR54AR0kct0Q6yU/F\ntSmXlsYlEI1Wh3GFTsRi/UzqoXxTdY2T6GbVw+d71FX/0EyQ3DL69bS5VOg+\nYL47NUKfQNuRzf/FnVBqXTtr9G1Gqu5cKmVwypIJWPALDBjsn5XUuOlDUGmy\n+X+tAut37jtD8Bc6O3u/nfkohXEud2ahXeuxh8CebQKoLhY1ItcrOd9Mq1IL\n1yULBBCvF8iKDImJ7QL7GfN9i1QKyKSw+5b4MFtaoWwH6o1CxQhgv/7rUu7y\n+sa96xLUx/y15gI1t6pjYzByjxuA89jIX75K7hw8OqmmZXQijjMC0MARvbP8\nMa5nrKol4a6je7iWqC2H4vk5XXPE799rDx6SKT4+TTfvSPucbcbcimPTTd1L\n9p7GXJ9OpKE2MXkauk1Ksy4Y/6aWtj7IQTxSS7m0L4lFzDvX+6bX4YQQ1/4i\nd8G3Huo7gkUqfwefaGrGoYyQrq5JXyVGKcPTycctWH/eVFKHrx/K6/3YIrVa\nqtyKt9VoT6JcLlXmIVcG20+SKQ67zprC0H9mgch5YhKAQkkVQo8tMxZ/CaYO\nzJIWbcvw5bPRAVpaOgxGmw5wIu4O02Tar5bBZqjoVT3j4xJ8Em9rgBGvmLQY\nmzhe\r\n=v/ps\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2c318284424187da14a7c3e5ee7e15a178d97259","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-27T15:49:23.892Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.98","@balena/jellyfish-logger":"^3.0.153","@balena/jellyfish-metrics":"^1.0.420","@balena/jellyfish-environment":"^5.1.26"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.24-renovate-internal-patch-2c318284424187da14a7c3e5ee7e15a178d97259_1638028490256_0.827173297905917","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.24-renovate-internal-patch-7d40c39756c6d75f983b9a6f80c24f7dea27d9e6":{"name":"@balena/jellyfish-core","version":"8.1.24-renovate-internal-patch-7d40c39756c6d75f983b9a6f80c24f7dea27d9e6","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.24-renovate-internal-patch-7d40c39756c6d75f983b9a6f80c24f7dea27d9e6","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c1c564fd494ae678e68d70798fa2e433d60e595d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.24-renovate-internal-patch-7d40c39756c6d75f983b9a6f80c24f7dea27d9e6.tgz","fileCount":212,"integrity":"sha512-rKQg6Broa+H4CE9Pe64A7bYvu6xD8kW653iW7hFSJDO6fBIPHNlt6N/7VeOuAPqR31PZT8WHjykZh3T8Il8E3g==","signatures":[{"sig":"MEYCIQDmQtC0x1j7PnZaokCQcpi5lIjf/4JmFStg2ek5+BompgIhANHpm0byWsJJZyg5JdlLRYrU2F6ib/w4fAoh3eixEeEW","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":778632,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhomxHCRA9TVsSAnZWagAAQRgQAIJsa8BfOrvBHNP14tla\nl49j1WX1pRRHTKAhMi5RIc2zscpovBmxu++Cuxg/u1FtYxRPu0ll3IqhQ9jh\nJAPhUoIATBrjtyFQuWYjPbBRNXFhPmTd47KcYEIHVa+AJX38LOJQlrocLhKG\nrkvBOYHFaJvTTRbDy5qtVYcOtZuF1SWotZEUtMKqELHT/AWkiPUvoeaN1omw\nRTLB9Chpx6acHoNMNXpOH9vK2lIeXPSgm8uvAhspHx8FC/UgIRQQLlVHpyQA\nNNy79dx1/Hb4eYuX6HAa5DljQsEALo3mKgUTEycUEAnpkxuHywAo01ERb3QJ\nUmsJp4wQ5Dh9hyE2pmpFzfZm+m6BfxOXgwwk0pMzFkddRD6kRjPZ8R8iX7aw\nGrPNkZfZO/1VZI1eqiM/o/GxDLuM8tRR83BLsMwjxUYl0bJo3lu3CwSUQdk7\nHLBEfHqXgVvRoXfkTWa0U81WPh/CPkmGqV0azB4wSkNfQNGBFrkZxzJ2WNZ9\nCpU0JBnA6a+G0m+ndxcw1rngpFxi2lz1eB6PYXIkcOmwlMQBcv8y6dZ5px3D\nfAnAYvfYT4Pd/N5VKlY95U2Kqmcg5l7aaTO+zRY4ID2MBv4gyaPndKdYZ5tr\n1GaCwB3aaqZ+SDTc+IMlLQPY15vKmLUSe5WCJAYwXt2PHE/k9xvz02fcMNlL\noKKE\r\n=8QPu\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7d40c39756c6d75f983b9a6f80c24f7dea27d9e6","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-27T17:31:10.088Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.98","@balena/jellyfish-logger":"^3.0.154","@balena/jellyfish-metrics":"^1.0.420","@balena/jellyfish-environment":"^5.1.26"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.24-renovate-internal-patch-7d40c39756c6d75f983b9a6f80c24f7dea27d9e6_1638034503243_0.3456415400215824","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.24":{"name":"@balena/jellyfish-core","version":"8.1.24","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.24","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"88eed2c3d9957797b1ca0d4442a14ede3cea561c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.24.tgz","fileCount":212,"integrity":"sha512-+LrpPLxQM0X1vGWQCL9NQXg/elcMwvfJq2wfuZwdJU/eP8ef5LCyXtxohRtGPwQA6lvpgygrNbxQwzY9MrqEMw==","signatures":[{"sig":"MEYCIQDkC90D59qMn7ADepq8exarOcumCRlbvIMi0NjoEtdBxQIhAK8nmbr8Ov0UMCmJJcEhlcasTfEtbNblzCltFPU42BwD","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":778567,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhom5zCRA9TVsSAnZWagAA/8IP/iA/i0PnS54v34MOfJAb\nH18bLQdCmeQN3jqFp35VZ+gMMqG6ez8qRIh4PMkMjmxgFXAQRlQX36Mex4b0\nOt4rMpQUsZhE0u687VVh5vZXhA1Dt5/6q7tNQeOl//1ey9MFre/bWUM+h4fs\n0Niaaz/vytyDY4oazcgeYJNNLjOLD8QGlK+YAcav4PgsUiaVQEYk15DqF1ut\n5jzaSs0HxpgX+qkR5t2D5k4fcLJrVw7YcVs9zyR+ueIj2+Hxust9dZQUtR6j\nybYmyxht8e+Cmtk7DZwvWnwvo7ipZHd2vQuY4VgRzudZ1C35JxmiSHjulS+S\nGr9Crkg20EtFFosBnrZ+3AZoyIclRTxbwtyuCT3D+8++lbrEXJ/itccpdUyn\nz4FQFbRCrUzeYX9DkL/6YgCI+KA1IhXqccn2Flajp7NnOwOjMr6A+yeZUNCm\nQQiSklKnncQFatHPjb+vOr8sDddMLneEJC4h7ltfgvaIU+iQURnlmDj0zMEV\ncytL/jk1OXU6I5k786cgMt7UIJN9wj69ecT0PzLxPl/pK84P4H9O+gkRogeh\n9W9z82LN5M1H0yBExbiy+wYf6+8rfNJ4PctoZrpSWST73I+gDBu9fCYc0qFb\nPKTwxO56hunX+6eKQVCR+a8Pxd4V2Xbj4ffTPhqJEtUvYMt8FieXx3mKQPj/\ngdVU\r\n=48ka\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e29d99671c090f6da3f7e319c579283331bb6af5","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-27T17:41:56.927Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.98","@balena/jellyfish-logger":"^3.0.154","@balena/jellyfish-metrics":"^1.0.420","@balena/jellyfish-environment":"^5.1.26"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.24_1638035059266_0.3476355793272634","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.25-renovate-typescript-4-x-c3ca89f4f4e5894ac812928407388f783c9e5e4b":{"name":"@balena/jellyfish-core","version":"8.1.25-renovate-typescript-4-x-c3ca89f4f4e5894ac812928407388f783c9e5e4b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.25-renovate-typescript-4-x-c3ca89f4f4e5894ac812928407388f783c9e5e4b","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"59508afe95f27785670cad45017d872c97e6c4f7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.25-renovate-typescript-4-x-c3ca89f4f4e5894ac812928407388f783c9e5e4b.tgz","fileCount":212,"integrity":"sha512-FKX4TY91KvUAyKC7o0PG+YOLbdNeNZMqRkzhdACHtza059iGZ/2ALzYxAkrlI1gXiIk79FDsttDNzHk4MzW7ew==","signatures":[{"sig":"MEUCIBBvq0Ih1NBwX8C/kOkEXQ6Bh0wk5qAMfnSJah0mXD2VAiEAs8Nmpth18cnUI8VPnfW5GNyMl6lZ9BUXrPp9zK0G0fU=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":778720,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhom85CRA9TVsSAnZWagAAVlcP/1uDJ+/MGAuSf6PKDoGQ\nIUNNdE8Cu+a2uE3aQ5REdM2MZwG2XbJuYRQfjTzuNYiR54D5nZ82ctIF+CBD\nNVkawVRkEm+GvOVTEIzAiIBca0VKSqdAMVD73adB8CMASngD9ySkG258RNML\n4XJDhOFgegGjgiTXdzC5UgnHa4hrv8I6SzH3X+qy3qYi3hUh2ogMi+Bcefyr\n3XnUaEemdAqmZK40Ph44TD9Smwb6YwauTdToTrnLG9AKyA0eLry61G2kKkZd\nwFPBwJ+D70BgkioSSDioRRaRofi6wo6pYBkejrveMepNKAFlTKNCuZ6xEv/4\n7XDHiWYDeHjCOR5C6wAffzoJoe9wBaTrgIdKE9zk4Vb9twxGCkGAHP798rXS\nk+k7LgjefAAM/HCoueMr8rRo/Ph8pFN5Im1givcMpOl2qlrVUBivZ6WxW6MO\nH1LEr/v3U8lGOOWZ/ZqtgRf9C8EhJHWcHnAXEwlCcHpsp582ehi/d95d4rnZ\nyf/BWR0SmCJ6MsEIK7sB3qRrM5KGF3M4tbpxcaqwRf0QhUNyqjsY9nO/WqkA\nibB7KurWNvVxMHnA4hYE+sjWUBys6fvF/0jbts85olla/3lyz75OyFbmrJEr\nqt0nR5v5dlBH32GZxMOOvoD8+gX0umtdwjUERq/8O6lvpsXF4ieusZas/gwz\nnPup\r\n=d8Nr\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c3ca89f4f4e5894ac812928407388f783c9e5e4b","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-27T17:43:41.853Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.98","@balena/jellyfish-logger":"^3.0.154","@balena/jellyfish-metrics":"^1.0.420","@balena/jellyfish-environment":"^5.1.26"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.5.2","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.25-renovate-typescript-4-x-c3ca89f4f4e5894ac812928407388f783c9e5e4b_1638035257262_0.9373498242316438","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.25-renovate-major-json-schema-org-4ad2102464df8510fbafe8b80c0542b7f4dac0c6":{"name":"@balena/jellyfish-core","version":"8.1.25-renovate-major-json-schema-org-4ad2102464df8510fbafe8b80c0542b7f4dac0c6","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.25-renovate-major-json-schema-org-4ad2102464df8510fbafe8b80c0542b7f4dac0c6","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"bcae87d7d194edc35522a124a27bda2f2409f2f2","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.25-renovate-major-json-schema-org-4ad2102464df8510fbafe8b80c0542b7f4dac0c6.tgz","fileCount":212,"integrity":"sha512-WEBWjWxz5l7wYECMwStVFFsiNxPlP8yRAC7xHxI4OQQcsPX4H2h64CMX/yCZO9Xy72UbJpFQJBjwSPsFSUG6Jg==","signatures":[{"sig":"MEUCIDks/p2ZFElBBHiI7at+m6nxqCMRwkMk2pjQ+GFrkYADAiEAyM6d2VRgk7E0U+b1rFGBXHOKuEMjFI3Mlyp7WNFo6v0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":778735,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhom+JCRA9TVsSAnZWagAAuuEP/1ROkRouZEbNksYFzJdY\nzcyghQHi5wC/K5k2JB56lit7bZfsWPDk5dNgLZYKoJVT3gWEbk4QBuZUVODn\nSR+7qAjFna27J12Ewn8wjQepOeM0b5HkStjYAEsjOsatcmevBuWWBbUtLyr6\nvI+n0CsA2HU2gVCycVDx2brx1lWZ7mchZy4ncKZXqXgCcrMFuDmLS1nizfTn\nlBJQsyaiTCESzC16xO/KE2gcIkUYoOBPOsIrAAQgGlrfeIQPWu/xxDTKhBcV\n/qffhM/6+UcAL9Y2JnLp6BhI87PqhHo0KB/D3yeRtQDSR7Jd90N1ufnsGE0j\nlTQ8bMq69RW2nq0J9WN0xVkUniMihjbPi70iwLdW7wfmkzWGMzMARld+kzJF\nFuUd7TXhnd+BZSF3H1YCIL4+r5MjO0mccW22Dx6O+nSsA0cxPffz9LmqZni9\nOV1AeQUvi1AFuD5Ua3ML99Np1m+YlK63QR+b0nYVIOPHOKf81Hn/R/abo9rL\nXfsviIRVpqVrCwvvliwD5fmFReXTeF7Qx4avDnTJxfDuJIhvhFynOevBuBQr\nGkG57SQpF39rZAytT6PCYUMVUGRC742u/ePFIeRaxKr69hSHGBkyFl0RlNZZ\nvgERLvZviNLIzvsXhFfSKicypK7tmhopt39mkXWyRcZZc0tTMFOxpIvJ7ysb\nmlbZ\r\n=Qf0v\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4ad2102464df8510fbafe8b80c0542b7f4dac0c6","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-27T17:44:16.450Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.98","@balena/jellyfish-logger":"^3.0.154","@balena/jellyfish-metrics":"^1.0.420","@balena/jellyfish-environment":"^5.1.26"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.25-renovate-major-json-schema-org-4ad2102464df8510fbafe8b80c0542b7f4dac0c6_1638035337686_0.9259666537724647","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.25-renovate-balena-jellyfish-config-2-x-0b96e15a5901fa6bc7cfbacad582a5442fbf28a2":{"name":"@balena/jellyfish-core","version":"8.1.25-renovate-balena-jellyfish-config-2-x-0b96e15a5901fa6bc7cfbacad582a5442fbf28a2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.25-renovate-balena-jellyfish-config-2-x-0b96e15a5901fa6bc7cfbacad582a5442fbf28a2","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0c0e26f0de9fcdbf4c5f705deeb706ac81e81aed","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.25-renovate-balena-jellyfish-config-2-x-0b96e15a5901fa6bc7cfbacad582a5442fbf28a2.tgz","fileCount":212,"integrity":"sha512-NLm9B7AxtiN1p5ZKunNUdKCKuixoDZqPKP1aNaL814zspaOvyBhRmL3w8txx/Ok4TXgHFyXUkWHTiKeG4Zs9mQ==","signatures":[{"sig":"MEQCICTMi9I3F6E71eOD08xBAZPKkaZg0Rg8xORflBrFy1nOAiBik+LtTUy+WwX3LBnkl3qs4gk/4E85hop7SsDxvI04pQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":778743,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhonAUCRA9TVsSAnZWagAAvN0QAJe3XsWM4ay1a6ccKTkZ\nSwSULmyCfoOn8KVzyiVM/IvsJsyXkpwMHW2Cmytf29oWJLfbnfBd/O1bvU1M\nwcsFK3u+QyCSBi4Wxhr9BWrXtxFBhwYvFVHTv1ZQbgHarb51zFF23Tx9gCCb\n7h7kGRhsAD6KFnHIDnbxGWoVUQN1y7lzgDjDsTQ9LGpvedlR7WYii3lf9HSQ\nkw21Jkz2Kxqi/zbeR3LYRl7FoPzENGpefbFuVFE6wgunQY0Tb0Ymu9iZQ0sf\n8JQ2+9SuGXVvzXDsDsV72Sbefa5igYr1lV15AGdsR4994DG7u9/RYn3xD94g\nVqgGRQGorTO8F+f3hghGdTOHoEXQIb1U37KGC+ISe2MM+6axMkQw1wBwS9iU\nLbfA5sLPqEfpvypM/lXMUawACZfmNNEdJl/fQSWHArC/0qhGI9qUTp6q5Vxd\nei8P52ILYN95/euSt5puoM2HLhZoPgcVnawNOmeBHhALxq0DTkjZgd/wtaII\nxA+zZRI2qsdir+mqjWBvSB8YD+6NU+U3i/a+WSH73GiJzip3UL4a+9WwVtO3\nszbAgth7ViCXJ5Vp9dbE4SHY+CwQR/+ECrNx6aj3MlaUBG3n+AGrB8qQAkp2\nrewy/xcEHQvuNG8ER4yNBuKYUg/Wa4d6FU3DhfLkwDqFbG+1AH82rSFbmcWm\nBo1Z\r\n=cR7Y\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0b96e15a5901fa6bc7cfbacad582a5442fbf28a2","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-27T17:47:31.554Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.98","@balena/jellyfish-logger":"^3.0.154","@balena/jellyfish-metrics":"^1.0.420","@balena/jellyfish-environment":"^5.1.26"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^2.0.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.25-renovate-balena-jellyfish-config-2-x-0b96e15a5901fa6bc7cfbacad582a5442fbf28a2_1638035476656_0.26242386533249173","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.25-renovate-internal-patch-725533cf3c8dcadbe190c2f055986c14aa1db661":{"name":"@balena/jellyfish-core","version":"8.1.25-renovate-internal-patch-725533cf3c8dcadbe190c2f055986c14aa1db661","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.25-renovate-internal-patch-725533cf3c8dcadbe190c2f055986c14aa1db661","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6cdf08c31f63f53c50f8c67b2e12a21ea1d7d6ae","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.25-renovate-internal-patch-725533cf3c8dcadbe190c2f055986c14aa1db661.tgz","fileCount":212,"integrity":"sha512-HtXWam8ScnweK2JppJAIxG8VPAsFEB8BnhTXeiFXZCYp4UdOL326+AI9UdZtFA+Be+QwgjYDerZ5/Zx/YwyhfQ==","signatures":[{"sig":"MEQCIFQVopwzh3ZRosqS1ugPLjcEh368US8rCRMxP+bnB22BAiBCUrBXbGtgE9sb37ZDJ/2KEkJneZg/7MTLvUywc0JK/A==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":778737,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhooSqCRA9TVsSAnZWagAAVRwQAJDtqoaFaiWvirkFEQWE\ndUvYBA/VGhojVNOcASbMhZkZT0UHsJomdktKnxevf7a9yN6lsnkaKh9ANoHb\nEEVd/gpoKRR5bMzwPRYjSDhqD+cYy0uTWl8b98YULYFLzjWFE9YX157JQ2lQ\n8B3372NludcXSPVfCE72O4aBg00k9PwUFBqbf7LF74Wfwjcro6ZBZY0y3DgB\nSMHsQsOTunbaDn/+wc+eVIWH/wb4oiSGro2O4m/IHzfcodJt+F9jrN4pFOLo\nz3tdRg/sBUvjdI/v9ZjQH0ZCSODFu4e2KDqLlDC/IQoKug4XbXDFOmLh57TH\nIzubq4nvRf4RWXfhAuazltH/Uc8ZCSKZlMw4H5bZfDuWiwmtvMuUIKqRlaxP\nSeTVPtpjKod7Dyo/BIIrvNl0+gUyjVZnX+yku8Ciu2TJY07MUtwcCAL8HJ3L\ngYR/wEc1gqMBy24qf3Anon1ZUpnMjXQBsoTp3Sdih2K/Rkzsq14YrdHBYloD\nJ57IVDnoqd5N05jk3tkWCYkY/+qoP1Ir9YO0SNUXEyKREv+lWFRoJujZfnp+\nY9QcCfmu23DU/yqO9vG4/k50oKLUCotVq0/R9k33hPUVW+cVCbhAUMV6fHFn\n7KKuUCJ4Upk5yDz3/873943AnutmDLgdR9PLYuXD5nlqe9hUiQHaRf73cJvD\nlSpK\r\n=n0Jr\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"725533cf3c8dcadbe190c2f055986c14aa1db661","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-27T19:15:20.498Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.98","@balena/jellyfish-logger":"^3.0.154","@balena/jellyfish-metrics":"^1.0.421","@balena/jellyfish-environment":"^5.1.26"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^1.5.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.25-renovate-internal-patch-725533cf3c8dcadbe190c2f055986c14aa1db661_1638040746240_0.5945792139475785","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.25":{"name":"@balena/jellyfish-core","version":"8.1.25","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.25","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0fd51173f81d3fc6d3ed44dd42d5ed29d926fdb3","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.25.tgz","fileCount":212,"integrity":"sha512-D3kmEDTHkBTHgAsWQ/r63gAo4KjeFZKb87hFVSQSBYuipaP4vT6CQ/yM2rY6NA5WDgsv+B9VTXPep50kzwTShA==","signatures":[{"sig":"MEQCIGdr6vTq5mzV55cawJLTXL0YkMZKvzqlL40evMNsozt7AiAjA/nJwb5HECiE3tvyDR1O2eGi+e0MUgM2JqLpF0IO4g==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":778665,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhoudqCRA9TVsSAnZWagAA8xMQAKCRLND4tgn0WIsZFq/C\nWOQGsVoeX44Mb/g1Yl8SABB+P4N7A50rkfD63q0US8JvNwfT4kRevimgQYf8\ne+fg3pk+W2HeFjQB/r0f0krXrAmxUBml6KfOTpUK7vmIvHh/cDd5ZLk0NuZ8\nn3vYcjb7+mx8lwjoBIM+Lgw21LnbqrGvasOgSTPbi2BO9/i/1rIrSiowdImK\nksPo0tlYFkRSbDtlBOvxx58pSRUjIhDXymR6rTi5ZHPZibtDjDfJOmgNxTm9\n9StaCQcHKyz5uD8n0zVrAMeMwDzllL2CnT0ET4OutMuRm7ZDjb4oyBxwMbZE\nCSlN85l9h3dpasTgNGFn/wfSm0iHI9OInxr4e7Upl79uSuucazeX/2zIUy+H\n//IWvTgqX6Lucyb68KYONxlGN0uyEEWoIk92/SsSrcHr/ZOanfl1CLFdpDTG\nMYH9tFngdzgbCzgZYzCKQ+U+lYEiAtKmSMGj8hCwColooWGtxV136WYv1mnZ\nYaZCVUcVxgB/fPutL5YIlaIIOF9lVmMC5AijMWVcs3Rj6FGJ7d1U2temPAJn\nMuR+m+3sliBShClo/B1/V+L+HPFeHXRO3q6QKGdu8KJjdV4Hpa2lDFE++BHe\nAX5s1C/JurcZ507mOwgLt0cmVmfQC6IKxbPIUSvcJ2l7xTAralse4Ni7EoFR\nV8+2\r\n=orw0\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"afd02f7ba35d5480fecb440e08b1f8ae40d4303b","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-28T02:18:16.324Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.98","@balena/jellyfish-logger":"^3.0.154","@balena/jellyfish-metrics":"^1.0.420","@balena/jellyfish-environment":"^5.1.26"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.4.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^2.0.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.25_1638066025886_0.6508011068432242","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.26-renovate-typescript-4-x-8a59fe7afe151bf84ccd7586f6704e78e9e99966":{"name":"@balena/jellyfish-core","version":"8.1.26-renovate-typescript-4-x-8a59fe7afe151bf84ccd7586f6704e78e9e99966","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.26-renovate-typescript-4-x-8a59fe7afe151bf84ccd7586f6704e78e9e99966","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"12b0392a2bf8c6c77e23f0b205d5f9f702cbcffd","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.26-renovate-typescript-4-x-8a59fe7afe151bf84ccd7586f6704e78e9e99966.tgz","fileCount":212,"integrity":"sha512-0xkNzaJeGQHY3dioP0XJqrKuq7IvErMlk38+pZsJNdL+4B/oY8ej/DgJci19UbHkfLcEWc8M27bvIOuhQrE4Aw==","signatures":[{"sig":"MEYCIQDQIqoRfnfu0JYjv6SLiRYtCArE7iEUAo/aC7R3qDLbJAIhAIOc9t6VMHj6YigA9kw18FGJMtfWMqYohN4LBzF4/Z56","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":778818,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhox4rCRA9TVsSAnZWagAAccsP/3NV7q8RpCSVEryibczC\nG6JPgtyX3e90QsUwUKd5G/ER5mgDt819Gqym+MdhqtzkfXabi8nrtIW/NU2/\nBUYD+MzZPQ2khfgB3Mr3hhp0mv0tZ2+v9e/24LI6hTexnByqvkXmgDBC11rQ\nhT1CXbLBfDXgLe7IyOkcCfuLj01ssf0GAGwqBIvv+HRJGhZUglVq5ILASg11\nIQnHBkNza0NSwZwsZvDZ2gp8yZ+uAmvSPvCxTBhmS9Gh55OM0pYaCsC/9qQv\nsmFevFYgE3hYWy8UZ3VkqDwoF13ZlqCZveX9P+U329PaBqV9FygtkVwpa96J\nmBrC+F83MiWpALR8zaaPbx4IRa1xOp8xgNvScLPD3oFGggbetyTXRCzw6PSU\nnswz4lOZlkAJdN0P3jhuuog+QsZ4B8VUJDfEVblhQ7kEXR4P68CwxQG7lpxl\na9IkaU2v8M83Jewgc1ls1nUC11RcOBtroMvls7pmQunJEFddwoaB17sU9hiE\nCWVjJvSSNEavsj6Bv6AP1gujH5vD4Kg+CLCIN7FGs3UZKDM4ZL2AU0ge7eyp\ndM2sHfQ0E9cAhJafYr4tyR0xYXqOSVShA2UbGa2tj8UqplLXZ7ZL0QTN92RU\nfXs5CwOKK++vgkZOn3zu7hmIXPaB2CBLCTkYWrgXscTphM6yg+qMMveSW8V3\n+CYh\r\n=IDVg\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8a59fe7afe151bf84ccd7586f6704e78e9e99966","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-28T06:10:49.715Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.98","@balena/jellyfish-logger":"^3.0.154","@balena/jellyfish-metrics":"^1.0.420","@balena/jellyfish-environment":"^5.1.26"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.5.2","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^2.0.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.26-renovate-typescript-4-x-8a59fe7afe151bf84ccd7586f6704e78e9e99966_1638080043620_0.22885465115360337","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.26":{"name":"@balena/jellyfish-core","version":"8.1.26","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.26","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5743d6b262adecaeb2fa7d450a02b20211e196d7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.26.tgz","fileCount":212,"integrity":"sha512-tk0w67gM2rCNauREWaFbNgX+Yb7o5PDdHHQhvJX+CEtj34FZWC6X0I4viDm8+BsnlHfcOU/fxfD3MFBqxAQAvQ==","signatures":[{"sig":"MEYCIQDMwaU/ZRIXPPXTA5RP3iKjfWb4Vzab1V/6OLRUoiSvJgIhAJNHKwK7bKLgCZNKRIorh8Dhluo58dLvluPSzzHTtgu/","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":778753,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhox+SCRA9TVsSAnZWagAAYvkP/RBBDuEtlS9yDQoQMwIY\n+Nz8TWSjz1QBju9uwgnoggDFKsxImK+AZWMBrJNo36pBbgL87yWVbTlN5/Fl\ntkmkkHArz/BSf9xmaAn3PqVp9D2bYO1iqeah5YHuZLc1pxDoobB0QZUzeZgp\nhYd2ocY+WnpbnZQDv6ZUU332ZL9jSjz1xvk/UrZkGBodfOZRe9J6l3ll6RnI\nePAYHKGCyQFG9/rRK5y9W2oQp19Ry9BOjzPVpdWYLfC9Fea+3Zb6KMvCI1Bp\n7w/r7ZLgqafeCbPPXX77xub44pz0IIp+kd0VrLikMfSo2T2L4ITDUeCxPTEh\nu5TAdocZMfEcWyoT95nWH6jeKG9/iKe0ZuZTWjg8JnG7pvx7qvo6WsumJIJn\n6tdI0TGChqzZRkmvUl6qxICEFv4cc4o/HRXa0PmGzgxqB89y61coZZurnedo\nQM5NOak0jmXcqOv3QaTUFGTO9/3ZfBwlfdOMaKI2u27OvQisR+iWYDnJQCal\n5lGuEPZdYrUziG1BQ7J2dKOZTM+TuRkcI9QokK43COy//przrO0Dw5cctgg5\n4CrEf+rjmzEWZCa6jo+3TorWmzkrn11DSBEBeaEgSeEMR9AK2fTvYmQEYMKs\ntB65Jz1lnAlBVDZo1JoL//Q0Yt0adawrr1JfC5UYn3uXgaQGwvvEO8z9gB7p\nQAbe\r\n=Dbik\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"1a8c4cdd6647e20c98d91587380014338ae33a96","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-28T06:17:54.922Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.98","@balena/jellyfish-logger":"^3.0.154","@balena/jellyfish-metrics":"^1.0.420","@balena/jellyfish-environment":"^5.1.26"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.5.2","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^2.0.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.26_1638080402371_0.411468355553412","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.27-renovate-major-json-schema-org-6b56145c1ed25a0bb36bb12707541dda34448402":{"name":"@balena/jellyfish-core","version":"8.1.27-renovate-major-json-schema-org-6b56145c1ed25a0bb36bb12707541dda34448402","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.27-renovate-major-json-schema-org-6b56145c1ed25a0bb36bb12707541dda34448402","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"00cd627756ea32549997cfdb692a038a74565d97","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.27-renovate-major-json-schema-org-6b56145c1ed25a0bb36bb12707541dda34448402.tgz","fileCount":212,"integrity":"sha512-QNQu5D0VY3WqwQ/TwOkkcMAApbhWOBvIkHVBrR1zhKW94D/8xXsHmxaVHaQhDH6sT9UljGqzY2Aoc5WNz7aphA==","signatures":[{"sig":"MEUCIBavrzPel9i9MYrcjCw9Qhyq9z+8fLIrbrHqW7nDNZq+AiEAkZeUaHeqnlsYWiv3mO8XeyM6WREWseV2xFvDipFwZzA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":778921,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhoywDCRA9TVsSAnZWagAAU9cP/221Q6HEBo927HZm/F9+\n1CzLNMCFae9xOXQPgqntp2bUB8aA8D097JIQqNrBBNuhtB+zMS0UmCk9r73w\nJ1ok70lV6c7WaVBoFZp5Iv+vZgqL23fzfhnFb+j7ApXqLSP8Q7VUib5kSA9E\n3BoVC3d7EhbYR5d1NCQ9PD/oH29cgY0Wfvb8k5sZpdv5eSfojSqvxbJ3qaVA\nSNPj5bnAWXR2XR1wKLzlE3wlnv1GbiKJlzLv3X2lNjRCZvDUBqGOdn343dFG\nUVfv/DDNE8q3VcYuh0nhC7l48/IRbzePqg7s84KVs47defWgbAx4HB0qsS9W\n21U92ziKDYDk36hUwnRecLExYyAX2kUIsOMlkCJZVqyvTFPAevtY0ebfC1+G\nGhUGI0uuWP5AExqCI4nvyyQVrGb6xXmpAhrLSTBboMw8Bddj/0BdyesXq3gK\nSzFwz/6NgauSpESEG39TKT8K6kU52D0J9O1ikL0A+O5/Tlt4WPJ/3HwiMORD\nOU0DQFmPzzx46tYYJDym70LEPE9Rmi7JSju4IJ3y+wfqaJ/86BkLr3lPXL2L\nOf6HaA7Io4tSuNCbE0QYItdVRB9uDpoIkI9tIdUxmNtfHPlWz4tVSBH0wI9F\nwoit+gY7HFCh5Sb6QxqGXrAvmxJvM0srL4qplZcC1xbpeP1O8VU02gyPXc1W\nCI+q\r\n=Y5uA\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6b56145c1ed25a0bb36bb12707541dda34448402","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-28T07:09:53.247Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.98","@balena/jellyfish-logger":"^3.0.154","@balena/jellyfish-metrics":"^1.0.420","@balena/jellyfish-environment":"^5.1.26"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.5.2","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^2.0.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.27-renovate-major-json-schema-org-6b56145c1ed25a0bb36bb12707541dda34448402_1638083587672_0.6246419829650334","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.27-joshbwlng-remove-sync-comments-18b09b73ff9e9ba6bebf4f75a887bf2ee8be1677":{"name":"@balena/jellyfish-core","version":"8.1.27-joshbwlng-remove-sync-comments-18b09b73ff9e9ba6bebf4f75a887bf2ee8be1677","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.27-joshbwlng-remove-sync-comments-18b09b73ff9e9ba6bebf4f75a887bf2ee8be1677","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"428b4b3f12b0a36f41a56b561fbf3f0dd7a8e249","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.27-joshbwlng-remove-sync-comments-18b09b73ff9e9ba6bebf4f75a887bf2ee8be1677.tgz","fileCount":212,"integrity":"sha512-LJZgmIihaNCvQhobO6kta6PopGXXchHBOxLzE2F+URDOvLDQGPelWrRJ6hMqon06cK3Dt+uh+t9PaArnNJ2C9g==","signatures":[{"sig":"MEUCIBQH9EufBmfx6bgLVuEattFrX1eOJylZkYU7k6qPVj10AiEApJAT7DiY2vPTF5300aGtX7TuA9OzJJr9fQ9iaUGeebI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":778892,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhoy0bCRA9TVsSAnZWagAAAj4P/jHMQbuK6qDgnS7nNkz5\nLpIiz+c+CsQ9jm+UxUgYi4ICOD8nEHRgbQmpqPvmosweBd6EPZC6UvJg3VKD\nptB3e+DpE1uRNEXTkMe9jbQFkgE1JqhAXxMS1xruOn8DoIIbS8pakfe+/Z7S\n2Eh5iyac1DSeEEF9oJP4OD9BYYLxMH8f3vf8JwyzsD9//LCWOAkVOcGx+5wd\nNoSZA5lT8gwgpT+2/ZcCd2ZPlAYj0SIBZBzOrwcH5k/Xz1ViSKWjGcyDeI3V\nuaw6R014gQE2aB/es6e7ZZwTxgp1rsF7c9GyKq3gEmBV52Zyg0rhCkHocmmm\nJMToG71P5yEhKz5HZYY5tjG+JJI62LomZSYVmBbdP6acxsXm2iVjC7VxeuwN\n1CDJgVVejRuFRmf/YGZelvJXyK1huce2dWDQnOg25SVY1g8U+GSInFoKzb3A\nCveQ2b1xIjNEfTYKavbhhIFZ6WySTWsjZVnbovkHr8+cKHt5bfXVRBTVXeG9\nw0Tp+wUlMm86NBF7ZwtA7jyXFKBtxJxZx59RIeaROPhV76MVaXGpo7HFI01q\ncBctBW+3Xr2Aq+J5fBKjq49tXoF6+cQ44zJmiVuob5g5HbN1J09/dy6smCwR\n+bIMxLZUH8OAkTMjTHqru5M/rhPDjdrJA++8cSiJzVYsvDTMYzLNlybsvueS\nUq1V\r\n=+SVZ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"18b09b73ff9e9ba6bebf4f75a887bf2ee8be1677","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-28T07:14:43.567Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.98","@balena/jellyfish-logger":"^3.0.154","@balena/jellyfish-metrics":"^1.0.420","@balena/jellyfish-environment":"^5.1.26"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.5.2","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^2.0.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.27-joshbwlng-remove-sync-comments-18b09b73ff9e9ba6bebf4f75a887bf2ee8be1677_1638083867593_0.6070358382542866","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.27":{"name":"@balena/jellyfish-core","version":"8.1.27","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.27","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ebe4902baecbec067dae6aeacbd9abd4f4a9e0ac","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.27.tgz","fileCount":212,"integrity":"sha512-/+op6HNoTTdr8p0+s/Fplsh2s+igg7OLK+a3m1CbO1+53dqSoykKAUbKGbwCH8RY1upL0/zcieg1xKjLItqUig==","signatures":[{"sig":"MEUCIEngeXJrmoiS48304lrx6ukn9Gjg5gXEKkTBz5lLPGZwAiEAz2km1EGx01cf40DGeeGNbowb4pxKbxTvfXnSiGt1QW8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":778820,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhozO5CRA9TVsSAnZWagAAkWYP/2PyzrldbL7f9BOO6UCL\n2TvWCVL+46cSBzNVjQW9oIooNDOmVFQ5nHLR5e3YXJ47ewemUtr4G5lYzztp\nallY7K9mM0J3ePb99IC+QzD/nWF115Zuco7CsggF61dBNbyaPCXW2pCQzjms\nmZCJ89voNOiSFaF/iQp1QDTZ77vE/V3V632MMUqVNeDYNfiOn6KngMz+fgTF\nw7xkkaTdkpETjC3gjxLJPMFeGgJFAKR4ZZsuzpbRc2aQmXKoaeDa50LSVIGG\ntKxE4E+iyRwkkiq0APpZEbWT7PDT9/4oy2T+8HgDssGYbmxeyPhkm6eMeh6z\n2Aq1dIQrBCARxHM33xMXARyw94MpGgvLeDNNy/kbgRr5UXS0di7rHG7MwznL\nfwmG8EMbSHKjtGgToBuVgrKlK1xbnYeJuKBc5KhlyzQh9zJoO7ecYkQZrmNg\nsbg1HdvSqmf38o3Sw4IyAOvLhzfzUj7ELVIpSCmImtfnXvpPvm2FfDZ+Q1NK\nG/iULKv/C4hXKvPFrjc3ZoCpeA/GnuSuRTgfTWi//IxCDv9cN/4k8vkM05rA\nD+guX2VprcQeXaMYcIGSMW0JsgYUwPjV0XvFPQcn/onBHpGF2rr+c+vh8/gU\nxeijhgYaXbq4gYO1+Dv3qCHlW5vCgCx5F6Oj9UChoEhWXgFZjGs3NM4Q0YpO\nINQD\r\n=wIgZ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"65f8c00314f6d0ecdb356d30eb0f1f84688f3587","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-28T07:43:58.638Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.98","@balena/jellyfish-logger":"^3.0.154","@balena/jellyfish-metrics":"^1.0.420","@balena/jellyfish-environment":"^5.1.26"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.5.2","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^2.0.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.27_1638085560867_0.3032594270608431","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.28-renovate-internal-patch-baab38f1c1547ca1c85c70177aba30177f870c8d":{"name":"@balena/jellyfish-core","version":"8.1.28-renovate-internal-patch-baab38f1c1547ca1c85c70177aba30177f870c8d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.28-renovate-internal-patch-baab38f1c1547ca1c85c70177aba30177f870c8d","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"47691baf3c3587fe4bbb2b6a2b6777a17fc8f17c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.28-renovate-internal-patch-baab38f1c1547ca1c85c70177aba30177f870c8d.tgz","fileCount":212,"integrity":"sha512-kUAsb0YBI2sCZqWAudBsQzX1gNpwRChAPjrFU4f1aUfPW6bqQKXH92VwDNpCmczC61M9AlMpch/SfyhXObvGYw==","signatures":[{"sig":"MEYCIQDUirXZF/SZGODo1f8HAEwe+Pa83k5i+wMfu6+XwupIMgIhAPqBYfgk7H/U0Oc+i9zBIBFz7K/65TTBodMgXrfV6GGF","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":778990,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJho2erCRA9TVsSAnZWagAAxlAP/1uj/aKym4prITgs+rve\nX5TdUIcWQCL6mSeAVX29TtaQX39iNszsPJJ4YX0COq1TTqefSPG+GCklGAKH\nsyDSQ0dBcJiBAWk8tbm74igMHjAny2vjbrlVSeZ9UHpbUudJW6csc74vFGTe\n3IE1Fy0KPJihlVRI+1oFOHkul67JrF41wAf/Ni5u1rLgAveP18c1xhe5lBPM\nVDIT62Uh7XfFkef9zEA7YemguK9/zbNXX+By0VaPI4RpKklG/5PYG+u656Qf\nWKgkzX11hBEkKxR/yg507AwS6Xe2At1lsLRWLM9vKl0f3/s1KXwgtoDZgOR5\naSJiL8GAb+HjygT9/4eO+vUzPc7oCfdaVxHIsuHFQFkeGKR00kq6oSNNf0f0\n+6vwbzpuaB2TqH15g8JpBGK2Qg+1Z2QigBGs4pqEzztxNCPX8uezW8IyI4fD\nr8FOq8KLNSYsudIpLASxGzL7O3ZDvGmVX3hLorbmBkAZpYWTJrhfFqD0c12z\nLVUGDRzp1KALiDRECwqIaoKBlLIpI4K3KXN3OMWWzfvu5pyfsNEhaG8TIjnb\nzSULdGECV5e/CmtSNgWFiUruBZvo5VRanNzg5RoVIUqVYABDniG9Napl2Urx\n+VwpzqkQnZNxEPyoh5JJIZiCj2J2bFpbUsr58iIuVR1DFyRAt0j+MdHveIET\nHYm0\r\n=l9/5\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"baab38f1c1547ca1c85c70177aba30177f870c8d","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-28T11:24:18.065Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.98","@balena/jellyfish-logger":"^3.0.154","@balena/jellyfish-metrics":"^1.0.421","@balena/jellyfish-environment":"^5.1.26"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.5.2","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^2.0.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.28-renovate-internal-patch-baab38f1c1547ca1c85c70177aba30177f870c8d_1638098859673_0.2278407231625761","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.28":{"name":"@balena/jellyfish-core","version":"8.1.28","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.28","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6a38cfcd01c7d11725c3e145d08e21374ee4658f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.28.tgz","fileCount":212,"integrity":"sha512-LhZmQ7h5eOeVB4SDdtzZW/rWT3YgwXbgbkUHN0oIvwuHIBuid0tutk/BXr0SHIJXTsuiOKTssJQww/G73EcHXQ==","signatures":[{"sig":"MEUCIEtqgFTyTOJ1nNgEWUfdSvwDtGm+1FsyhB1QVCEX02GBAiEAmLZLDheAHZtMAtBAMCgIk6q3hzJYEJLwqzk7Yt7Oo+Y=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":778925,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJho3J4CRA9TVsSAnZWagAAMtsP/jCh2bXsHv7GwCImRc4z\nyDsgyHE7NWrePmZBBAs5IkmnwotZji5jrBWm9sPnvVoEJipsmg55y100R7Mc\nQbQLA2me7joDBVWB8VluAeGGv2Wd9pcNLv45xqgwYowwgs4GQhWTZ19sOy29\n2CDUXSLlS7zZN668MMysrCLw4samnrZPb4D/Wgoj/RfbRZE0QGCoHe1eZokk\nQ0c3sEIIyGIiPYgl04IJtNtwTpKledfsoOqFx311i2qd6qm70oXDs2UXegWf\nMQGOoqLcU9yKB63uP3kJU3QaTPN9WVLb7H+1I7Iv7bbrwGXIVUBNJeu4Z30T\n5IePYogdK6Y9SzYFGX35xkW50OM+CWggfPKYNNsNOuYIESNTsatb1TLn7DFc\npmZUuTf55+KYK3sv3KjUrMfHLBoZbskxD2FU5pIIX0KyajU5uMHmlDSYNboD\nvhpoAOh99NGisl+PAEIBSMYFb2qwMc+Fx7mma4w/99/wj05pp5cis5kItAVR\nEYYqk/EVLAxBJ26y6NT11YEF0ANW+KlXakZGIuZuNKVNUcVPRaja+Y3/nTqw\n5XoV0hCM/b+dkGT61KsGPXyZ8oYJn0+dhIUdhYca0E1LfZaONHJGenhuKZvx\nMVX2MQY0ZwQDfIKcDsWMCJL5a+v0T6JSkqcdcmfYlt4pIw8qDb8kdQizJROJ\nA1Jg\r\n=k1M0\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"999f5e956959fba85a28e5d09b3bcd75665de67f","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-28T12:11:51.055Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.98","@balena/jellyfish-logger":"^3.0.154","@balena/jellyfish-metrics":"^1.0.421","@balena/jellyfish-environment":"^5.1.26"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.5.2","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^2.0.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.28_1638101623894_0.12678643073199036","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.29-renovate-internal-patch-922307996c8d704fb078720a596f69e56e7d5587":{"name":"@balena/jellyfish-core","version":"8.1.29-renovate-internal-patch-922307996c8d704fb078720a596f69e56e7d5587","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.29-renovate-internal-patch-922307996c8d704fb078720a596f69e56e7d5587","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8a929bb12f3b9d57de60f13cc353fb7752cb1bbe","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.29-renovate-internal-patch-922307996c8d704fb078720a596f69e56e7d5587.tgz","fileCount":212,"integrity":"sha512-Q68cMfr/ZlXFJ6Grg+TxY/emEBkiGfG0Vd8COnkgByR9caJVhlFd8xsRJ5yWnV+8h7NjZ8KXS4rZ9HvDshltmQ==","signatures":[{"sig":"MEQCIEqhd2pEL0rV5kcllMWEcNYSlyux4cGLtOMtHaGfWqsdAiAR4ORRXKVWuCADHNItFNUIbBKp4U94y/VZmWupAfjSGA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":779093,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhpIJTCRA9TVsSAnZWagAAK5UP/j9iSyZVCNL3rBz4hiHn\n0LdyIKo/kdtx/l490WMxokoJuWaIXbJoYj9R/rJVn25tQfL466UkOWq4vmSU\nPTIHaPn9Ab5jy/O6I23Elhz6bS/67wMNPDbST/jWxaMGxAq7N0xuEgbbHMIL\nK+8F2SAF5bg25h+kantG1psJKC8LZm+TVWeBnHzXxpAd7RVRGgGPqE/ELvxT\nL6jMkxbywIrF3bByh4IlMzmvqPO3GaceqWK696krNawU+RzVNs8AYW1shDbg\nimpc7SO26JUwXN+EIcJKTQjUoxbv1J3eHycaovH5Ff1ImlmOftKvq+cOWvqu\nL4yOCZ4swM2sG2fM6As2QW6ekc/s2UTHnYOqeXaRXq4ByLe42VJruWircXNO\ng7wXEc+6E1M9dYnQ0jhsQpXm1SC9Uc2WzpWEEUB+qQVObf7cOy0EO3jVPBq4\nddq4MJ7yCRgkPcZ7yTuvBl+kBYuZr3gKa5xF+NxJTLPFPfgH3k/iDLZ82zoA\n92GV1dp6fUqAPS+AnNKz5zAW2Dyor8w0uzhxktAx84YtJP/uUjqiYccYrMu9\nsNifsWxrcksu35NADOtYpIXq5FDOkyoQdUMdNX3n/FBaR9mAtedqG9vspFYM\nhHXnGW9V6S3d6cOmjUfUOjRkCJN7zIQ//vh+kmt3ghXDnKbQ4KOaPv56j4zU\nxNXI\r\n=LMpt\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"922307996c8d704fb078720a596f69e56e7d5587","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-29T07:30:02.515Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.99","@balena/jellyfish-logger":"^3.0.154","@balena/jellyfish-metrics":"^1.0.421","@balena/jellyfish-environment":"^5.1.26"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.5.2","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^2.0.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.29-renovate-internal-patch-922307996c8d704fb078720a596f69e56e7d5587_1638171219596_0.8904345913008325","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.29":{"name":"@balena/jellyfish-core","version":"8.1.29","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.29","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a9af79922197d78843a3e28702a576dd077c26fc","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.29.tgz","fileCount":212,"integrity":"sha512-+8vcqIfz+qGRKQnmV8eUsbO00nC9Sr5uDSW6Us7yexsSqQ2z+sCzzOAUvS4V0d5+nqf/CmzymBS5C+28Maesmg==","signatures":[{"sig":"MEYCIQDiGFrZCUF33EZSmfkDQ56mtIDUeKFP3Ll1VXMw6jfnLQIhAMZA3acj+ojKkMb62FVBLPq1rlLgBVjUVCA3J3jn6J4e","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":779028,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhpIQMCRA9TVsSAnZWagAAWBsP/RkoGT4k+/Q5jjQC8ZJl\nGWObwiME0VJBsem7kF8nat51m0QSKPp60irvhXDQO+wxpClPVIizvKaGTyVt\nZtC04Yodn+DTpAlWGA0IR0z3SJR8hdvBkCazpj2/ApSvloVR+jKNZkQgA9xb\n4hugkRaih4i/Nlm9eF81onMrBupp5mmu/Tn2v5z5Nj/dwbuf+Y8YaAIS2iyZ\nMyh09kb6MeSbSescJvmwGqywmlaqmDM/dXlM39kdyTlROa3J/0MbI5w9IbnL\nO1fkH/98Flzupj82uuwU44xhZ0P3xqcZIy65QkC6a9kAlp1hlFQjOBP74MUf\nAi+Eoo3JdCP9QYb+ZalYMGhkVjKR91OVU+aARegRCdLSOrKjYWBaW+V+mHLv\nMuoxCSeMHCxv9n2tJm7zqRI0TLQy6ZviJ4AnLk/Y4FKUxzZHGPe40SrA/dXA\n82Fp/EXdjMul0Q3i1GR7WDqPuzhBueJgH6trhuHI2s6sUrIdVMCa81a59p8I\npJQvsWUI3Q0VIi4SA1kUDaqqI1/sRVew9aEor+10sGy0dKNpaHiS3XAghwqB\n9UU8B4tSslBkXJdKqICx/0k/WenfwzyhVtHr6lTCUrq+QN+U/PEOnGr3fwim\nmyTiCyLw5dmF3axOXFh2LXpAjXnigjHlEaiDlv3UccpjnW9Mc3sA+0I8v5sO\ng3N2\r\n=538G\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c5a67dba496a931b04cbc20feb85769d05ae6d56","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-29T07:38:59.670Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.99","@balena/jellyfish-logger":"^3.0.154","@balena/jellyfish-metrics":"^1.0.421","@balena/jellyfish-environment":"^5.1.26"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.5.2","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^2.0.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.29_1638171659832_0.7600387725178792","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.29-renovate-major-json-schema-org-b09418c635447dd1beb6d1c25fe8ac2b8aaa4ba3":{"name":"@balena/jellyfish-core","version":"8.1.29-renovate-major-json-schema-org-b09418c635447dd1beb6d1c25fe8ac2b8aaa4ba3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.29-renovate-major-json-schema-org-b09418c635447dd1beb6d1c25fe8ac2b8aaa4ba3","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e1a7abdcf776114c01cc51f53eef98a1f6a5a6bb","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.29-renovate-major-json-schema-org-b09418c635447dd1beb6d1c25fe8ac2b8aaa4ba3.tgz","fileCount":212,"integrity":"sha512-8c04w6PtTOfYHwTRHg1EwnWpM3pzek8e+MyeLfyXrQGgJuttjYhCjAtuK7iO4o9yYS0IEN5+mbpz9uhS6p/zJQ==","signatures":[{"sig":"MEYCIQD52YTr0GtYYKG7C3C+UCC6wuksaTls3T5kksDyUE3qqwIhAIUpkYeLRRYDly0wJO0niX/nRFK6KhRQYhqGuscuFnIN","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":779171,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhpISWCRA9TVsSAnZWagAA1K0P/3RMCUz4UkmmYZ9ifSKQ\n+SVxm8sbspClgUJzTkSkubnNmoK7qfya4HDRmGYAYXFyRCVMQOLbXstFAnDX\nikXoALSJBi871aI+81uhPQ0dG27/FRKF3oCpnVmNH3Tbq7M63w+xXLQH72w1\nXoXwkBmUvZA8jRuS5S2i+3BVae2finpvAxXbxg7N4znWLHjmO55J36ZHfvnn\nAwEhdLB6Vy9IRKspHog+MQKqJJv0JAk57R/ON+MUw+yceWjuw5vyj+TBRx1p\n2CbxT/99OoD0+//ru/jjwPEzIkP0hlbgJOl4YU7xP2byOno8BsXaVM89a4w3\n3HZdki8x51qSs/RYhpgXaEvr2HCT5e8bF6nonmeyK3CdPx4VzZcEBrtHH/zF\nzpQxNgF+OddxhA1VgdKg0e1x7/gBO86X1dslj0GbceLejyPsdani7PFXkIle\nOjAd4+rfIbFox3Ze5u4bt8/pkRcSB0RujQSduaglfGyZl8IO4WG9TrXloVIa\naJbUsWI7zMGTWZXpiPMOTyBxQjrIo3xPLLNQgOaejebuiBE49U8/qPn/JqfR\nqezDbjPbEpS/5e5zADb/ccN9TCi5w+CznyrRR/xrXHcy8zLQCXHW+Xsu01Os\nikrC/gKUGW3SJPnJ9xZAfbT1uwWFmtTgxmj3cSdTKrYjfiQds+lT82RRlQZG\nMFZb\r\n=QOKw\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b09418c635447dd1beb6d1c25fe8ac2b8aaa4ba3","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-29T07:39:58.092Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.99","@balena/jellyfish-logger":"^3.0.154","@balena/jellyfish-metrics":"^1.0.421","@balena/jellyfish-environment":"^5.1.26"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.5.2","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^2.0.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.29-renovate-major-json-schema-org-b09418c635447dd1beb6d1c25fe8ac2b8aaa4ba3_1638171798814_0.41742719063960676","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.30-renovate-major-json-schema-org-bae04cd0657efc8d8c1decbfcf0914a9964459b6":{"name":"@balena/jellyfish-core","version":"8.1.30-renovate-major-json-schema-org-bae04cd0657efc8d8c1decbfcf0914a9964459b6","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.30-renovate-major-json-schema-org-bae04cd0657efc8d8c1decbfcf0914a9964459b6","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"eba93a700f0d14521fe91383c24792b6579ce8dd","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.30-renovate-major-json-schema-org-bae04cd0657efc8d8c1decbfcf0914a9964459b6.tgz","fileCount":212,"integrity":"sha512-3kt7wdjhGcaU01tp1FN2PrcBcYLTx4jSRq6hXF3ajLCk8tJRKKWAt2jNntQO6lc9VqylM8LqEZao9hXZu9tAlg==","signatures":[{"sig":"MEUCIBHaP6ePX0ycKmR+ka+RSOHzJKCIC1NQIZJxi8sF/o3qAiEAskf5Y5ZBREXJMoz1VmcWS+8YT5dyBiO8MM20mEgYzM0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":779196,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhpIWnCRA9TVsSAnZWagAApvYQAIDgRxiwE8JvqmWrBdaC\neTu/3aLyhIn9KJx5IQWpr8c0bTSMlJYbvzjwOiDk6sBXfqweT6TzRrcyar0K\nLMySD2U7U+zcTgcQqYz2YuqocVO/8GA0XqzUtrUYfY71CyAtgBKmxqfH4Nqp\nBJldJ+uke3oW1Sxqb/u7YK7tDhnW1O5x1MbARxQR+AFI15/XrxQUcCeZ0Oeq\neZKWjzgB99OLeoWGXAnAxp9z7OEINk9OjMPzIIiokH2hS7ZpUs5oYfSpqZNK\nCsVFgBpapWtXfkwqJmmFydXzGAASIdArXcC92WKzMxWv65DVbc5EZ2H8av2h\nXyrk0Ds7CrcJGhIbFRdUgRJtaL0u+Hb9JslStmcjxJzB3V/UQTqgJCBt996d\nDbPzUp/AGGGsGK4HNrTPdCtK9izwsXfh6LsoYFNa8nSDOocm8NddPXedMvex\nKauyGuFE8k2Kmh7gIhMhKhEuSbExvdDNcheQZsDIHl/sw1nXJhrSARTzqzSW\nBpeeq/cOBNgpswAQKuNVSfiaTCKf3nO9aJrhf7x/6UzpvM+ZY6bJw6QGUIkS\nmomP/6qBtlfCj6uWWQgITDYbhXeW9ac3p95XUlzT97WdIU/PM5iTLVzSYflS\nvcKN56qYRHpWo7gL8ZydKzztxOHACYBhV/yukPyAibPSJpTChH1PPI9DTjp3\nqzOs\r\n=kb54\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"bae04cd0657efc8d8c1decbfcf0914a9964459b6","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-29T07:44:19.517Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.99","@balena/jellyfish-logger":"^3.0.154","@balena/jellyfish-metrics":"^1.0.421","@balena/jellyfish-environment":"^5.1.26"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.5.2","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^2.0.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.30-renovate-major-json-schema-org-bae04cd0657efc8d8c1decbfcf0914a9964459b6_1638172070817_0.2604255662643893","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.30-renovate-internal-patch-98ece36cd70b92565f5125641c47aead7da05a01":{"name":"@balena/jellyfish-core","version":"8.1.30-renovate-internal-patch-98ece36cd70b92565f5125641c47aead7da05a01","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.30-renovate-internal-patch-98ece36cd70b92565f5125641c47aead7da05a01","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"fc0b5319e14716cc598371b3f04fd3dda37d7bc9","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.30-renovate-internal-patch-98ece36cd70b92565f5125641c47aead7da05a01.tgz","fileCount":212,"integrity":"sha512-QWE2KQHXCXB4RLTT0LBLsfdXm128wU/e0MKQdpO7Wo5p1PyIMc6m/CbV1bsWfnw6Q6XW5LIT302UuOyxqYc22g==","signatures":[{"sig":"MEUCIEdUI271llZI/OaI2RNjHet8MQpljJr5RhvyKjDEjnS5AiEAvoIEaYcLR5amD3Feu0w88CFWjbnr8RM1NwDlfFGBb28=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":779197,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhpJVaCRA9TVsSAnZWagAAvDsP/1tbhwVy9tZ5oS944A6i\nBYuK6pRAy/ubjjRfk9M3SpzL2iwCyfnj7192SKq8fOO1d3mKxj8swetz6gML\nU1hu2zLMQLfPQf89YQDTvohYG0/WVakD9Cxsr5qbzkdcJ45A7vwxP3vqQObF\nQNpYR+qqPrGTKIDaSDf0H50hl89mCTvoOFdvwHAUBUJHkO+4Mm7ATu2UsWA8\nAB1hdrqXKvK5OBVoLeqnT1qyBt/oDOycrw97GXtUoPHuBZxd65xzWvq/XdMM\nieyAShn2Dhg/dqqapT7XD8Xf2hreGQfe6ZezBlJx7m4r/0MnTsKoURoDCeso\nNibmrg4wzO6DiyTZXu+/Dd8daXIoMK2BK2i2k7qHT/iYcT/gEsFm3gLKmTYn\nNaU+iDT2itlZPoXUvVjtH/khZNvEO2YLEPYTdYZ8XxORZUUmSzunC6qTqsZu\nKutbzKCbQjGvOqmVEcKBDbbhAlQuinxR9/TL2yXi/ht9FqJyL3r+2k8oJoV1\n/gubF0jNHZYI52ujVq+1wU6qFgAHPlqA9q8/s+E2n/iyzEmSx7HFRvi95S2/\nXt6jwKCe4FeU6e98nlBPNK5/Gy5wuHdkgHP4TWtyDJYclYMGHoc2nzP0la7+\nseu0BxehcoPv5Uc1EB11xlf04fZ7yYs0mx8EUZv1x85YWhcELP5hArczikfZ\ndmHU\r\n=DJZS\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"98ece36cd70b92565f5125641c47aead7da05a01","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-29T08:52:03.590Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.99","@balena/jellyfish-logger":"^3.0.155","@balena/jellyfish-metrics":"^1.0.421","@balena/jellyfish-environment":"^5.1.26"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.5.2","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^2.0.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.30-renovate-internal-patch-98ece36cd70b92565f5125641c47aead7da05a01_1638176090421_0.4035930830462675","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.30":{"name":"@balena/jellyfish-core","version":"8.1.30","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.30","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0bec4262be621aa423b06af25101a49eebe73497","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.30.tgz","fileCount":212,"integrity":"sha512-kNyX5NQG0DHonUfHNhY9hv1fnBzs1x8DgTPOZ6WA2YULWyE4njfqwj8WkFmTpg25FCC+XLUEKe0UCTgPriECyQ==","signatures":[{"sig":"MEUCIDlufQKPbcR0h3jUk/M0qtG1o6qqvhYb7ShQujl0hH7qAiEAkAoPIoGeIxrUe+4vwdJBTSScZIBZiLS2vBOUSLsYaRM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":779132,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhpJbLCRA9TVsSAnZWagAAmv4P/2Pgy3hoB4Oa3VsMcvcg\n1ajAXspk+tR2VPDcH3g5aGJ006anAC9oNf4aR+Q3y4sL/7KBlhgkViIxMP1l\nDqOBVUuXU1Ip+K2ymuIE8q19oL7F9/OMkBxuV7cMauKcekEysGs/YMQQf0h/\nG1XPcgGCa8QvXv3dXPzXEdwVER5yakL3PXOBic05PA0qaVCZ2l9affNwCBRJ\nip5GBs+CyCPDb1Lo6LC2xG5zG+Q/kU34V84oiua8/L/KvdxzjrOt51dX2J+G\ni0O3Dd7ytDkvjtrMPxThBvYDvm2rqD7l2a+SNNYbomaPNo1duRLNU9jC3VW4\neSuHLjgMZTmxTPNaJgT9j4CEIekJfdyQ6qyEvTN2S5wyhElnpsDmQQ3zysYL\nkDW8KIgPgs3SmWO+e1VVP4cx/Gd+TE7l2Kd1bCUeRk36hF8s/Fpi7e4kx4m3\n1sKtHplY++IJGciLhkokDVxcxdhPwcMSWHOL5CbGv9mafAriVR/kEYcs6mC4\n4zRsUNEq4wwERLEPS/njE7zHNbTTL8t+54gjsuaGUBmg6VpTFpMfvGQqnfjw\nzPAia85vbF7ZHbyosc9+nmg3P/Jv9JzDhhS2bUvI1YKfaBM5AyJQNZBpLRsI\nMwUbZAGKlvYkYMZIVi/XXDlXGexlC7J5Rw7iH7OoVFjeqnnNO8Pb4uoD/G9V\nCQ6V\r\n=pMfI\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"371126adf1046958f944d8eb4b4d15deb8ba48cd","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-29T08:59:03.664Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.99","@balena/jellyfish-logger":"^3.0.155","@balena/jellyfish-metrics":"^1.0.421","@balena/jellyfish-environment":"^5.1.26"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.5.2","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^2.0.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.30_1638176459329_0.9051362311218882","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.31-renovate-major-json-schema-org-a9cc8c9908e1e00b18917afd85285f1b9f58c714":{"name":"@balena/jellyfish-core","version":"8.1.31-renovate-major-json-schema-org-a9cc8c9908e1e00b18917afd85285f1b9f58c714","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.31-renovate-major-json-schema-org-a9cc8c9908e1e00b18917afd85285f1b9f58c714","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5f273cf3d5707be435b6d5a1ce7bcd9e620f1f26","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.31-renovate-major-json-schema-org-a9cc8c9908e1e00b18917afd85285f1b9f58c714.tgz","fileCount":212,"integrity":"sha512-pjdANIrSq6QVACoY6RrnK3drvaSTkBwU0cf4JS91jRtQ7pxAHWmKB92JY+h/kwdweZKpXt3EhdbwC4jPrJPKkg==","signatures":[{"sig":"MEUCIATec67LiMIXc44SkIm8BL34cbJ6hDwVQ1IlPWiD0RocAiEAn1xL5GvFz+vxlhJP4lDp6hCSxCa+wlCm1gT8EbjwIcA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":779300,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhpJhLCRA9TVsSAnZWagAA7MgP/2F9ho4ZzJdCIZk7Abar\nms3SQa1pPAZOjhCGrTGL/djs+ervgN7Am8AcBPyd5p207JQ5Ms82u1PpBlkY\n0CRVWPCy2S/jOdef9tXvXE6TbOFTbkU72ScbAwnSJK0PpVACGGOynXFB2/l7\nDaZ9dwKXvYGB/xAhVfYb01isC3rYXx9H0k7GQvTBmPAhvhQG7dM85UO1lU3C\nrm/6ck/bCvD0/eTPLoOKalEaN8mm0q25mI6uZaEuRwIZsKr3JAB2P2AHXXZN\n7J+/5gAB0j2aRY5XRhr8ecuOMM7adqUsk/D0cM9VZVzb22G+tDkDz/n/kvHg\nzPn7Z/vEPRSsAcTk+y4xMZyvODzVBUTiL2E9fPxcDidtqzEUkiLlpBmsut3/\nS7hEY2YDCAmSlF6PBflef2F3DesoeZ0cfwtdwvhjz715nU6N1cBULqgXhqcH\nhMXVr+X49wsEus2bet0HqqeBSTm7+q7kbSHIavYphGhB7HdNrfdON7eNFlVk\nwEnFAwlVr9NVbI6BAfVhqpJNaRNiU73agyUJwtNOFGuLuzi2fr2xqzsnM2uN\nspK+5OF3PFUcCdWz7NdvbSLxJLfjv7bs6FECWEQlQlBaaCmEGn3lUXxqnUOe\nU0TINaRYS+RaZWDFZt0es1Mvv95n77hJIU/xKhEhQpRNiebvSrdqkuQ/ssM7\nDamR\r\n=pP9w\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a9cc8c9908e1e00b18917afd85285f1b9f58c714","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-29T09:04:43.768Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.99","@balena/jellyfish-logger":"^3.0.155","@balena/jellyfish-metrics":"^1.0.421","@balena/jellyfish-environment":"^5.1.26"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.5.2","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^2.0.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.31-renovate-major-json-schema-org-a9cc8c9908e1e00b18917afd85285f1b9f58c714_1638176843047_0.39476221197466566","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.31-renovate-internal-patch-4847e06acefc5a9ba6b4396c9421a1e70e8f8dcf":{"name":"@balena/jellyfish-core","version":"8.1.31-renovate-internal-patch-4847e06acefc5a9ba6b4396c9421a1e70e8f8dcf","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.31-renovate-internal-patch-4847e06acefc5a9ba6b4396c9421a1e70e8f8dcf","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6ab563a2dfff2324bbbafb98292983cb8540bd82","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.31-renovate-internal-patch-4847e06acefc5a9ba6b4396c9421a1e70e8f8dcf.tgz","fileCount":212,"integrity":"sha512-YwVNn2HRL5diLxmB3DgSLDzd/Dh/4DMrNCdhCixFFRPyy8wQTpRZKygq2LJXmD+zB9HZ/afUgm4rJluqvmIgTw==","signatures":[{"sig":"MEUCIE6gAUd29PMaeKd+3Ol+UiL3B0msJdq2GiOVeKk5NvlQAiEA3T/U200taY+j7NeLfieZh8tvzIUALbNmh4mlvjPfygo=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":779302,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhpOjkCRA9TVsSAnZWagAACpEP/2au3E6En3fk2ClAV7fd\nO/jolB/2AxkSF2upcw6zMzzsysZ4c+6/b1halkzOt6a30kn9uxYGsaIsk6vx\nW1/qaZ807Q8zM1CVk3UyK1gghCm50P8nXOTzodpM9J2HE9tAG3zfO3FqX36+\nAky0aTEqgiLAaTCis8cAaHFSzwyqZl8ZaKz1WhXT4S8GZ/1YnflxFDXz/zd3\nOZE/aVKKYBxc9NIgE6Aj7kBcpJhMnZHcsTIlFAxRNd7Sf4Eap9I/b56xh46j\nIYFMUu+kdF5/VxmniIBRgt34lk8P31J1u8bucxZ7Hy0N8PuTEO9HC1/mXNNb\nlDbsN1Lk2WaAzG5P7jREk1DBOnRfGxnkohWIZOA+D1cXbFl7XYb+3s1Lm9Bx\ncAlgJ+0uQiUNVkMdfHPeM7hlwAZxgEf9X1WE2eB0imQEPE26A8rG8g26/wi5\noYeEPLmpVCcS4xZ2ezij1iAAAeIMrWkaredt4iOeJpEm1eWdTvMdjp8UXU2g\nQwMwRvAUBIQv3uuP4s9O86v2YPlCP7KizbG37RStqsTtiPrQ7x9GoHP9RJiX\n4nIyYHJCEb1uxfEvgiwCBgKjzfZKH7VhYOh6+JRzBOBgOnQX9PbMvWCe51MF\n0oq9uTgE/jVoCnfC0/zZD4D6YOKkhdKeBQofwNqOQFv1nNOj24M4GAGfD9+1\n28lD\r\n=M5BH\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4847e06acefc5a9ba6b4396c9421a1e70e8f8dcf","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-29T14:48:22.268Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.99","@balena/jellyfish-logger":"^3.0.155","@balena/jellyfish-metrics":"^1.0.422","@balena/jellyfish-environment":"^5.1.26"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.5.2","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^2.0.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.31-renovate-internal-patch-4847e06acefc5a9ba6b4396c9421a1e70e8f8dcf_1638197476504_0.6378813196188717","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.31":{"name":"@balena/jellyfish-core","version":"8.1.31","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.31","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d17f7db63ff4132629ea8b6360509eaaca12f6c8","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.31.tgz","fileCount":212,"integrity":"sha512-lI+LcLuzAppB7cFOU90hF3c6XznSwhfOLJSFNvt3tLAqfYRicFVMJuc2ujqoUA/E/8DWTTjDWuOXwRiaXzrfwQ==","signatures":[{"sig":"MEUCICLYm89RUicaslqpKm1opcciINTLRF0IsPimnLKKuQG9AiEA3rOgZ6EGi6i9ZjU5YJsL8Xe+LtFXTwSuXQWU374rIxs=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":779237,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhpOppCRA9TVsSAnZWagAAGfYP/0X3uAjb58GAtNOPqVHC\nPUOK6zQMWIaYbq8SUIOKDQ2iNYuvKyuRWP7sYcrTPNlzBebF5mjri8ujVw8C\nOxPf0yYkB2dLIPq5M23v2PZ+n3nZ4D5Cu979mq84ySohr9OleQ3vobwNHf0I\nR5cDMJbkoZePbhtj5HF2qdsRIp4q40j+oNEbZC2kZpEDsmxxmUCISaXp4JAd\nujhhumMNhxQoF2+vZeFJIXyQbRUp+KSGOIMpQV9SeBC/dZDBMYzBVdP85NpZ\nKTRMx1rZ7b5+XEX9mOZFyUfJIzdAlzj61l6I4wz9vmlQAmSGbXcVloPoJPN9\nopU2kcEr4wN1905+vsQUGkq12oxS+BSPULGP3JP2wOUgC3c2gSAabGz8dIeD\nnYgFUv/4cyYyI6uwzkhNCXsWKQ+jqbjcisqQqR5jeLLLfHpBpwan5mc6extU\nPbz6JyWTjRXDZI8p4PMPq2YC63ze4YUkdUY/bPxRZ5qzmPjl3uxNug0bx9rT\nwz6cMSei1xS1GZyeAyej6/Q81AikUCxcSbDUAkspw07BYtlyytqJVBKFYn68\nEuS7TNoO+OFf2WfzjD9BYk0sBskD4oEyY11ykrKaSFIxpxBSnaSs7u2VhKM2\nEy++BoCDosqUUl+c/+qrPoTaHjETGwm8y8g5fNQ7T/6zk/NfpNOjOdYczyI4\neCu6\r\n=fkQ5\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"22d0d422bd420a4a055ebd52c633404cf8afa300","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-29T14:55:43.023Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.99","@balena/jellyfish-logger":"^3.0.155","@balena/jellyfish-metrics":"^1.0.422","@balena/jellyfish-environment":"^5.1.26"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.5.2","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^2.0.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.31_1638197865298_0.32672675489540337","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.31-renovate-major-json-schema-org-11fa6e484579706e8823823220c86f9097ea1694":{"name":"@balena/jellyfish-core","version":"8.1.31-renovate-major-json-schema-org-11fa6e484579706e8823823220c86f9097ea1694","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.31-renovate-major-json-schema-org-11fa6e484579706e8823823220c86f9097ea1694","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8f24b35aa2ff3dc8b998a94df24b55ba16fa00ab","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.31-renovate-major-json-schema-org-11fa6e484579706e8823823220c86f9097ea1694.tgz","fileCount":212,"integrity":"sha512-R93zatP5/SY+fsDbH26+oXytSIoJSiqufGz7GgsRx2QGFlMFaqHxlMcCXSGW8pQghXU9rzQmtGtBJcbCCTekpA==","signatures":[{"sig":"MEYCIQCPeE1cB6taTBA9s0OtT3+8yQrEouI4vFjuDliAxO8oUwIhANvliqkkb+4o9Dyad8Ipjc5xDZQ4QtGeGIRGaUPD9zC1","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":779380,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhpOsMCRA9TVsSAnZWagAAaUkP/A13GTuovZ/lubAbsfDx\nGEbo9TSBj3K9D2th+WVb4aVaquimFNLlbrEfZiVGhtIMy+X5zje7NnRJ9Buz\nfDauCC1VzF14pU4pBV/7QcemIk75TluvFA49XV2QZ75TIcemD4tYISe5ie65\nMaWUwfVYvki50KsN0NGIBrvkUiXthclXVGNKMQLkmHarzQcTecres4/lW0UW\nvuNQJBd4I+9mt5k5XG8j+KQnPtibqyODW3QqgpePrBkSNE1KFVt74axBveEF\nlWihCDJ81JcxERVz2iQh22BlWSp6TshA45jvrLAESKxE05S/tuEW4SEMbxuw\naJAzakmk9dDloX6CQAFphxaGzte+xjjtdJAY79U27lBJTVV8HbQM0EOWEeV8\n0UafPp3ij+kbcF5Q+XdjBfX7l8xNHDPO7iEIfNyl7NbGqAJW6WIArNc8YkiP\nhTQBnPHeg8QwqJtXDDmulGPEzz3cpwlBQm2j08IKyr7lqKTO1s1xSgYfRvCl\n9l1bgbNKWzf8osRYri4byH3lPYXZLQHs+oqlVrvm+p06gRME22Xe2cw9BFvG\nT+mPmMEmZIOBu7qZghN4wd65tdY1+uWjI7XvdsByYLRAHlvWtFi2J0Hcn1wH\nDWklZcU+x0GU78XRMZRHqr0IY8JuTc5ryqXLY2tFh4SLvWiJMHl7BppHZpoJ\nbyGm\r\n=QaY8\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"11fa6e484579706e8823823220c86f9097ea1694","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-11-29T14:56:53.725Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.99","@balena/jellyfish-logger":"^3.0.155","@balena/jellyfish-metrics":"^1.0.422","@balena/jellyfish-environment":"^5.1.26"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.5.2","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^2.0.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.31-renovate-major-json-schema-org-11fa6e484579706e8823823220c86f9097ea1694_1638198028682_0.29164132612255034","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.32-repo-sync-secrets-default-ebc1de642f99b15a5119a271a98d5abde3a47c1d":{"name":"@balena/jellyfish-core","version":"8.1.32-repo-sync-secrets-default-ebc1de642f99b15a5119a271a98d5abde3a47c1d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.32-repo-sync-secrets-default-ebc1de642f99b15a5119a271a98d5abde3a47c1d","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3779aa68b52647c76430878b3e0ffe7b1132cb75","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.32-repo-sync-secrets-default-ebc1de642f99b15a5119a271a98d5abde3a47c1d.tgz","fileCount":212,"integrity":"sha512-2aOAhjQ0JU+z3OIFniZhYJdgt6x+ecmv1Andb/hwZmgUFOI6tyUIBBekQsZ+yXZ+PSpTVpLmfA2djN+R4GfBCQ==","signatures":[{"sig":"MEUCIQCN0cXEvRMpH+QSDwT3xDVEBt1CXMMJhwjaXoYUDH9J2AIgUf9f1kvBrUIdFMcxvd1mu5eKKVIA8nvZ0KwbecfCh0Q=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":779387,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhpsRcCRA9TVsSAnZWagAAfPsP/iLId86pS+/3PjxIq62L\nZMIh4QtifxpmQl8Az7L04vjnj43YuLYKcE0aVQLdBDa0eOK47S658dMq7rxZ\nrXDUJjqubRgkfHy7jMLlT7RvqoH+unRnonCzPP/fkDQzI4mlNu5wGYFVsQwp\nFD94nKXPNubBhK5DGoShDdvG/pORCVnCJCypjDv/NW4LyyfxakUe6TXbv5I7\nS077RtUovwI/oeGqHFzkbTXhZInalUe3j3uon0f8u7dT/9mI6Y/z8mqXfK8G\nW+5iTVMURceBkb/EVpCE0PSCwiq/tpQRrX0HxYRyOgzRw6MooaQUlcWn4i49\neDnknSLFxGPGyAl7nlhuqwL9X9l39KqeEjYXfwvYYeayduoc+xXWtz4/dxmo\ntCpNf4Cu8BZNvY+3PozNWCPb+rkwR8rjWpc9tkLoCgSbv3y+A2e/I/vibaQo\nNjErwR4CyNOv80ouSdKsy3N6kb8AcNFbjAoWnSQTbrK4d5j1xlDhwHQFwTBT\nmhXT54c+9DwhR2T3V5upDU9x22+9RVe6ekB3wnKjfh3qCAfd2RSuDCyIblcR\niDvWTaFvkZf0B8s+662U7PLwHOEHoTTnxuGRwbmYSStastLHRgD/ntB4Cifn\nhnNeYvsctPkVNtbmfwc9uzjvV24+O8xz6SQVtvAlwxwmbue3vf7kgzu5Z5Wj\nagVb\r\n=0uSn\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ebc1de642f99b15a5119a271a98d5abde3a47c1d","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-01T00:35:17.516Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.99","@balena/jellyfish-logger":"^3.0.155","@balena/jellyfish-metrics":"^1.0.422","@balena/jellyfish-environment":"^5.1.26"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.5.2","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^2.0.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.32-repo-sync-secrets-default-ebc1de642f99b15a5119a271a98d5abde3a47c1d_1638319196519_0.05872986206615893","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.32":{"name":"@balena/jellyfish-core","version":"8.1.32","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.32","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1b243f93799c873fea8e805f6b6392c815922fd2","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.32.tgz","fileCount":212,"integrity":"sha512-JxaAg99mGwoTKp39UrgG4NvrA915HxIGAR/SwD6/Pl4pSnpE972xE9Hfdbtt6loyISJDJYlUm4iajxlSVJj+2A==","signatures":[{"sig":"MEUCICt5iy9u/40+8gT1xwQJ/3qTWfPGTYUpgqMnVcCQOv5GAiEAmOdaF+VFOzkB6IgaDwbUEdQpsmzXxYn0of/6lSkLicM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":779320,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhpsrQCRA9TVsSAnZWagAA/ccP/2KgF/I3QOdsJCIV7rCN\nK5n/tnR5diwsPK9T/1DrXY6MoH3pUH4174HuafXGznr14+BwpInMzWj8Dsge\nqO5pFq1CM063YqYP5B8G0c63zYBanpiyzw248CsjOm+qb/nVDDWj/Vo7M7TJ\njbm/Zno6O+c5gj7JBL10zTHUWT1e7l22KyJds+SqWponLqCfP/j2RLUrjgPp\nJ0qhjFTyDVMSnpenM0z5OfPkeEdnSzAMU8GxqfhfVq7pNSLhFhGjMqblx66i\nxpj7bpiODGhpFOBlN1PkRk9ugsgzZSu9tYMb0uMahnom1oQVEX+O1Zxtc+g2\nIWytaoYvMZw0m3fC5JcDuNvSz/ryVo+zG1TkjeLNeNQ40xrwOsbuX7jaUOpB\nV4FQRZEnZFKX3xQ/UiCg2uIBtpW8GzedcyUDmqB7axpAqv/QkyAxk9NhtSYE\nLFcSt2IQeVmQSbvf1ujlnjDbdnOhmBvt6QKP+OjJ8D4SIVq0t6VFtBXt1X8S\nQXxQgVQFkOmwlPDaq4HtXr7rKS+eq6eITp1selhghm/jZjsn+tDBylivZXBU\ntX2kyaVhlp60zPPxcxFNBgddpyLHH8lx7iKoKPABw24RqhCqKuBHjh2+K46G\nOgNhPUfjtrmmE5jciQVxtO3gvC3Ru0UtBHcNgbhOKpH8pnTsoyhpW05jj5uY\nf4A5\r\n=9Q81\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"15bb7d6d3bdd096c5ae489c7b94bb16f3c259922","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-01T01:05:35.130Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.99","@balena/jellyfish-logger":"^3.0.155","@balena/jellyfish-metrics":"^1.0.422","@balena/jellyfish-environment":"^5.1.26"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.5.2","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^2.0.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.32_1638320848314_0.8284121597268919","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.33-repo-sync-secrets-default-b801c8437c7e7a25aae50ceae03b7f88c53f5aa1":{"name":"@balena/jellyfish-core","version":"8.1.33-repo-sync-secrets-default-b801c8437c7e7a25aae50ceae03b7f88c53f5aa1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.33-repo-sync-secrets-default-b801c8437c7e7a25aae50ceae03b7f88c53f5aa1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"40a502f6101eb9f7ee1bb26f01b76ca8ed1809ba","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.33-repo-sync-secrets-default-b801c8437c7e7a25aae50ceae03b7f88c53f5aa1.tgz","fileCount":212,"integrity":"sha512-eiBcS/UBkmz5utnxX7mCj37feVPeE0hyVOawB6n6B9bIt4kLqn2eA2zw1qr5P23sUqWeZXAXA7Oux0d9uu/iOg==","signatures":[{"sig":"MEYCIQDVxT/joke2q1zvVBxaKznJ/y6qArwEHctAgTfOGg7GpwIhAIpvv+WyYCjc+QCK3aCOHW2KrajEEceq6Urg18DNpqXr","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":779470,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhpva/CRA9TVsSAnZWagAAqhIP/j7W6/0JSrxEkAr1qjlg\nEXlEn/X6L5whIMXpgdi48uPwSA5+A7+FBHo/obtbn1iCKtMASj2/EKFrSq78\nbI4CAY44B/b4Z/wMVVGngohABdEcGAdjpDKwQpOrf6ikN9UDjMOVabUfpw5l\nJnU/biX7cEOaEIAlDyItl2EBZvPx1dWs6/XZmXmEk8Nv5FHINPLP4ijcyu5N\nHeuFLcf+i+v79Cncw0XvdSZOklWFqNZq5X21CasQvFRzR0J4GlG+pQqm4Lqi\nHnWmG/mlsamD3KpJelU6w1evuEiIUm5584yNwDRU9/dxXx7YCIh40ArYx/DE\naOmvkiJ2ESmHnv/TEC54BcPQR7vQUxf9gGZbBNsuWFSWw1hnMjcD22xn4yIy\ng6axqR9zWjHNs6FFcdXR5pfmX7jd4nI4uJZHQQLohPSdolBOcr90ZIaKZMq2\nN/QJtfW1huy3dt5goC5OTNGHznvbz8ogptoBcAYfnlobRJxNrtblPIo9LN6A\nyzGxgyUkUfcDm8kJrVpQdyzgv6TVyyAiEe6UXZV3RQ0lxZPhccqaoBIQ3OY1\ntvH4itS7b2dFO7xLoMHlfvKrI0MyQeMVuKcnFd+y1wV5mpaSe5z0cirB/2bs\nNjSALpwN5G7RhXlmcgbQ1aAZ2tfMwZtzncE9y/gU2Fn1VOXqIh5pc/CQI+kV\nfbtK\r\n=J+/t\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b801c8437c7e7a25aae50ceae03b7f88c53f5aa1","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-01T04:10:57.242Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.99","@balena/jellyfish-logger":"^3.0.155","@balena/jellyfish-metrics":"^1.0.422","@balena/jellyfish-environment":"^5.1.26"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.5.2","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^2.0.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.33-repo-sync-secrets-default-b801c8437c7e7a25aae50ceae03b7f88c53f5aa1_1638332095454_0.6318927041360614","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.33-repo-sync-secrets-default-3682dce9c0c976ec4befcbac45ff5a54ccd1de19":{"name":"@balena/jellyfish-core","version":"8.1.33-repo-sync-secrets-default-3682dce9c0c976ec4befcbac45ff5a54ccd1de19","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.33-repo-sync-secrets-default-3682dce9c0c976ec4befcbac45ff5a54ccd1de19","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ab3bfd13dd2f3fbfc31c548af8172e0c156b872b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.33-repo-sync-secrets-default-3682dce9c0c976ec4befcbac45ff5a54ccd1de19.tgz","fileCount":212,"integrity":"sha512-b2m6WVP/fMIYk5hrfI5y/kqVLL75gv0+KV9VurnN0B+0LcsI+B54cMo8+S+zAde2PLpIfXiYlmlF1g+8r60T5Q==","signatures":[{"sig":"MEYCIQCyNEly39dHd/CwFUmfryQt+W4ZlT3sdxzoULq8Ty1jNgIhAOHoHkiKatxXBvm0lKDX1mPo/y/InOTCmfmWd9ANlVIV","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":779521,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhpyOJCRA9TVsSAnZWagAArAcP/1i3h5C8A2TdB05LypRx\nS0IERdvXxD7Tm0LyOi6uJ1sBrw9/WYE/OeSC4W5z+VNmGfygVyUwbrH12pfm\nMIBqidwU7hZgcSoR8o8Grkp6GiND2SZuZadM3skFbLVI2gAyPgDooDTh7yct\n7qzagZ3luzsMlKTq/oTrA2+HQC6H+iyQ0mIA9ltavmFM7BaN1qiWXsLfA2B9\nefo15V9RDCpG4alBXwxX2Zubq/WBYMEo8OE3SpIrweNh0hwWfovuFCtetkUt\nEN5pypd8ClMZW30I6ic3YXDe+eLA+zayHCKqDQfORqt3kpxZeLifXNkTU9nM\n8vbD2iXMfm88OjrzSEeab1IUh/YBIBCXYw7USrsgJuNF0nqVJYtsOCy8uu7J\nqo8ayNleSw3qho3tP86djwlimFeNjSB3qJH7qj4y30Hf14Yeb5/uv4viMrBE\nuTIAEX+8ubLwJ3ej67Vd1m1YBIclxI+zeGTftJGB7qmYaxiiIsyuW11MUiM6\n61BL4TVtpGVm0DE1405E61dmWc7KLX9TKycMYbYWBd2bGcSNFnA+qCFXNm+S\nwFKD06/oiHywgMTiJQTyzviZqIk3oSLw1XnrL2hkaDFFSjCQkW4dux5xazwa\nGwgrPMwOERRjHoQO9c9aUtupCtm8AQXDe26bwx2djDYC1o3I0oUce6zkiKYp\neyiZ\r\n=Xe/z\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"3682dce9c0c976ec4befcbac45ff5a54ccd1de19","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-01T07:22:30.657Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.99","@balena/jellyfish-logger":"^3.0.155","@balena/jellyfish-metrics":"^1.0.422","@balena/jellyfish-environment":"^5.1.26"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.5.2","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^2.0.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.33-repo-sync-secrets-default-3682dce9c0c976ec4befcbac45ff5a54ccd1de19_1638343560945_0.4698336604368225","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.33":{"name":"@balena/jellyfish-core","version":"8.1.33","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.33","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"fff1b7d87730d3a878ec1d7a35d75ffb2f914a39","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.33.tgz","fileCount":212,"integrity":"sha512-pCNxMinQ8KptZmqZFeYZHLLbbFNn4egdDo6Nu6R89rCFlcvTr56E/kGlY//XKw0nUPYn3tSqRNxcsU43smIX+w==","signatures":[{"sig":"MEUCIQCLGs2zYZDLh3qYFOyW3bh33VCtbKAjhplZOBBoRpsGGwIgeTQ4darkSJ/IBNjlx2V2/GkIwFQ3JMJKawRAMHR1T+0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":779454,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhpyVGCRA9TVsSAnZWagAAw6gP/0g/SAic3ZQ0+g6MFtlC\nAZv3eS3PNSh188oH7DhpkqlRRjcLcDqTFCgBUQ/zJc1VWjMM1A81V4W2wC1/\nUjc0bh73kHeK2sPFUNg8x+InEWxQB9OR87XYQI4aLg2b/XQEqiklhe8I/1Gd\n3fbTCcG3LeVAKuU69ho7q/jRUXqm6i0hZ5Y2qd7oLVW56t7Tk/tRnchCxgl8\nmR2azJC8etLIfXr7UofgCdrUHxqp8SW1JkXsKm8G6vydl8IVqWBusu1U4l9R\nQE1ebWsLUB8ttleOZcCJ8KkEOjnWC2kmI7lQ2hV9h7REOYm+ic4wAXnYB3RZ\nvojR4cSd437AmS6nOReD7FeFGIjTbnwvQacCZRCddd4P+mfCUffQKEPQFyX/\neB4m2Ct/JhwLsxHS/RfgVaF2wi8s5SJvQQKlp4d/cwrG0cHMOeCKJL6GzRm1\np7tLAKFkZAL7Svnsm77yVxYUdXcCfF4huDuWVlwmxI2YHH1lhvOjHLbtcbWt\nZLCdXFZTUpxeZCAkn37nxz8Ry1K2JLZ3uS22kwvMpN+ctTO5ylOplNk3NOrw\nVy1UZ3f8tFbrouhemhi6BC5NgpR1DVvIpZCr8LuzERD1RIQFpZeBBNa6GduQ\nf09NqkUoOI9unMsidwczV9x3Zl+LbhxvuoP+cjbX1PXQJJHU5cSBWjz4n854\nT0J0\r\n=4MB1\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"57abab2a31d16e361d6c5759e7125ede73c0f21b","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-01T07:31:14.392Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.99","@balena/jellyfish-logger":"^3.0.155","@balena/jellyfish-metrics":"^1.0.422","@balena/jellyfish-environment":"^5.1.26"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.5.2","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^2.0.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.33_1638344005998_0.3082514228265323","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.34-renovate-major-json-schema-org-7db833b4893bd3f4992d2ecbf47173212cde279e":{"name":"@balena/jellyfish-core","version":"8.1.34-renovate-major-json-schema-org-7db833b4893bd3f4992d2ecbf47173212cde279e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.34-renovate-major-json-schema-org-7db833b4893bd3f4992d2ecbf47173212cde279e","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1c91f48ba73fa7a7e381882e01609682a4de4c45","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.34-renovate-major-json-schema-org-7db833b4893bd3f4992d2ecbf47173212cde279e.tgz","fileCount":212,"integrity":"sha512-4bDRnCp7GuIkFI8Ch1pKDvG+krT8mtf9DhKa9NJWm1S+1NEmWg4OCm3+ZHpBzdXS7pVuFeZqypwxnx5t3cQleQ==","signatures":[{"sig":"MEYCIQCEbiYbHPrX+BnhPRnoK+Paj/lfrTOcIgVAP9GyVRO0BQIhANZJZqYCGiKho6QPFBs1CPTGwo4buIJ8lLSIkGgO4smg","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":779622,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhp03aCRA9TVsSAnZWagAAyT0P/ibRBLcKvVXDsef3oIpr\nma72J+aPdxigODE8crCka4WAAFcSnurr7lXTLGtmpuVA36ds3zIo8aOgodpG\ndnT0AK9wGeROvQbmXGpRyTfQV5nHveDarMfO7eEGbX2WnYIzgHTOBYijVnzl\nrfN2gVzCb6H/mjJUE6RpszichENWRSK3VpooE2FjpmQ3liSkL+/yRol7BNud\nqLlDzFGRtZ/vuhK03suz2cQH5cyEbdu/b98JlOlJp4xKmj8K4j2B0qFU0rHS\nI+UrSHXm4mOz5283AUeWhwZEQHCHsbri/K/acHB7LclxoRwiNCwUGYqN+4jD\nJ7a/RCSyZBmVXs7Ekd5E0++5b1czbynKROyKHLLq8sf2DTw+cjTQ3Pq1hy2y\n3lV1UTM07Ev9+kupzI+QjXtXkiZyL8BwcHe5QF3V+2JDs1QBT+q7JiOAyqcU\n4W7gw/N5MsSNHttW29sU0os5m+go9pmKX6ccNt6su/U0Jmh+5AZEXd0hlh3z\nhjfYXcHKqs76JtxwwSAsTIB9UIbbCKkQ7EDVYHmTrPD6O2h+GiRRNsiIiNrn\n/7zyvSYJXZA4I6NxmtBIeTTE7vseE6HVjnMS1QIzYicgp8EehRIDeIGQSYPX\nNe9CJIUqtmZ+6MuL6W9GMmaGCPFAzAuGZlsxmexF5o4xKYHCoQT+EqU9CEkS\nlRkq\r\n=qUh6\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7db833b4893bd3f4992d2ecbf47173212cde279e","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-01T10:21:55.666Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.99","@balena/jellyfish-logger":"^3.0.155","@balena/jellyfish-metrics":"^1.0.422","@balena/jellyfish-environment":"^5.1.26"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.5.2","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^2.0.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.34-renovate-major-json-schema-org-7db833b4893bd3f4992d2ecbf47173212cde279e_1638354394564_0.41474497086215756","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.34-renovate-internal-patch-30130e2988add0f0efe77f989db963271b5d7c03":{"name":"@balena/jellyfish-core","version":"8.1.34-renovate-internal-patch-30130e2988add0f0efe77f989db963271b5d7c03","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.34-renovate-internal-patch-30130e2988add0f0efe77f989db963271b5d7c03","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"71c650edc81e54c343d0ad37023e43da2d4fe15c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.34-renovate-internal-patch-30130e2988add0f0efe77f989db963271b5d7c03.tgz","fileCount":212,"integrity":"sha512-ovYqcHDFdWTBgtbBmrTEbwbFTwevdC5zEXi5fDcHaFvbosr+UV/h2R0JSZTb5pC/YbXq0qjU8hgNwCUsj3ZknA==","signatures":[{"sig":"MEUCIQDH6AHlOw31AYriYhzZWGEQuQu18UGZx/gEt6Obi+WmyQIgXIlTSV0XLsPhqJogRVr8i2jRBgE3iFQbrMSfwrKBiAE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":779590,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhp2feCRA9TVsSAnZWagAAmcsP/05mruVv/HMANlDhdI9X\ne8AXJ5+CJu3E78q4ghbDIPUDDp/54Oqc92BRhxZntiopHken1bcdaywLYLbp\npIqkfM/zUKI8eQFpcmxPtWMpx33lHdGCYrTfL21P8pCwhu+vMtDXrWNoG357\n/tOI2SDzE5lPo1AB1TofCCt4kotu8LyKQpTdOcpG7x0ZocMnkuP0QCij8C5X\ne+Ttb2jz92qj0CLofFWx4i+pXxub7YpBmQ+K9jjAIqr2scQ64Pj7AxmoKUhj\nalimDIVQMQYySFhLYiMB2MAZuzI5k6ySlx2rO8Mk4LM3nbC8R4MKD/W5XMGQ\nNatmQnDY/8RUtj2wheH27+2Ur7/m3QjGfozH2qZNux+qUrkLT3t/Uf3o8whr\nlpd9eV+wr4bFi4KmqpGoQVMCb2meMS9CBffCEsI/IkGJBPk63qFRSxyzYEL1\nOShrMSWy6qB8ubjb4Gdk43k3tJVn4biHGWnqcH8MrTBytssEfWOBgUFzCjo5\noScF1St4uqEmdl/qnysAdlqBq03JaG7UCVWd4rJHT477MMbMTBchxoZl+6n4\nequSZyhfDXI3KXv0y8yM9G50X2kU3Ctkaxg2diCYUxXmfwr8dUZy5oBFjUQM\n8hT+1ALspueTOv1tI5a/Vwv0d9b3ZVkLIelByYi2mwL5eNd7rGUJyYDV1OI1\ntho2\r\n=lc2g\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"30130e2988add0f0efe77f989db963271b5d7c03","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-01T12:14:05.261Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.99","@balena/jellyfish-logger":"^3.0.156","@balena/jellyfish-metrics":"^1.0.424","@balena/jellyfish-environment":"^5.1.26"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.5.2","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^2.0.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.34-renovate-internal-patch-30130e2988add0f0efe77f989db963271b5d7c03_1638361054821_0.43596365253642033","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.34":{"name":"@balena/jellyfish-core","version":"8.1.34","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.34","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"796043549cac17ec465f6ca6f4c2946dee650842","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.34.tgz","fileCount":212,"integrity":"sha512-7j8jUiBwctpjmMGZhVUZXQ+DMhvKnJGXei2BPLzq3/VJWcq+wP3RGnfpX3/lDyUw3zFK7iQ6nHlcbDgUNfz+SA==","signatures":[{"sig":"MEUCIFSCSm5aLJWANNbe0ygrAAB8JXAKbmJ5BLXsU0FWxZ8sAiEAyL+oHAXL+IDAqRGaB+wV4PR11PEgK/pMBL2SzR0K8Sc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":779525,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhp2mbCRA9TVsSAnZWagAA9HIQAKT6L3p6aWm5rhBDI3TE\nW7f40H5QjP8Ttxa1eAHfzgK0sg9szjuR6LMA0qVsMcAX8R5SlHZr0TCcpK6C\nTaHyTtD/b3yR0uID+X5WbNyVy3GBC8NdDvMPGbL5mEyB8kMP4zhrCqv1lTMD\ntJWixCvkYsdLzSFCxxNC7ghAGVsY7HqaQ3Xi8pOG9eYW2hL67JcEx4UplHRt\nXrcbNXSodtLp6Pl/lTmoRt6qsasXHOvIZV7GabA3t52JS8LU38XFdtYa31z5\npzD0Og4pkFKVWnYx0/zXddcnb64AgmYXsON287D7magcCCPNDkK/JWUMeKx/\nEeRGrZlYRuWTW4vrCd/SlfbFuWqjXUzkEljEpqkaDV/oDXgqUeQmDKNdLnHb\n7IPAwHgnoX7VpRgRmh8opOQiPiQ0lWHhJCE1MKVcKGcaQqlO+N+u1ZlN0vyc\ncOZYj1Xf11wM5eWHFi2bkdvSbJxWRYfzg+n2XtBg7PbEfBiEP4pOomqnOFxO\nlbkI8aZoafAdpbZPMjXGfycmPfmYXocA7+mf6FFo1xaYA82aGLlzvnVNnyUa\nTLtDq5roOw+Ir7v2428wf00GlxHx8Q27pBe28wq9FzlzhXG3wnj+09N4EUAR\nzB5AR6MK+cWHiyoAZUJa3R3a2UEzHSwb/QpPajTNrNCAGzsR2nds/91hbvdB\n8sKK\r\n=wgaT\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"3e818c5b017f3558f4e283c5344f513a62e410e7","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-01T12:22:37.505Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.99","@balena/jellyfish-logger":"^3.0.156","@balena/jellyfish-metrics":"^1.0.424","@balena/jellyfish-environment":"^5.1.26"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.5.2","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^2.0.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.34_1638361499582_0.37707317325490686","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.34-renovate-major-json-schema-org-34cf4d3af22c250695a1fcfbe9f759010d9d09e5":{"name":"@balena/jellyfish-core","version":"8.1.34-renovate-major-json-schema-org-34cf4d3af22c250695a1fcfbe9f759010d9d09e5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.34-renovate-major-json-schema-org-34cf4d3af22c250695a1fcfbe9f759010d9d09e5","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e1c1e90b415efbcfd91c2f7993c231ac436866fc","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.34-renovate-major-json-schema-org-34cf4d3af22c250695a1fcfbe9f759010d9d09e5.tgz","fileCount":212,"integrity":"sha512-tCylikFRTov6EVtL7y0luBjoybD/QXnoMmy/9MmjmYb7+Klbti4/rQgvOkwfvOdlKzt5UdMw5/HpeC7OKrSlkg==","signatures":[{"sig":"MEUCIQCN1NXGK2KlKhvx02lDzzkCOxaVzU9Yppl0XVWXuqzBOgIgP+FrcoicHRrq7XMgAp4w7Rk3ecYXnBxC1sCib0Wxq3o=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":779668,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhp2o9CRA9TVsSAnZWagAAnkgP/3cfZIWH25mgvKICJA2a\nDyGcxtE7cfY6t5ZitV+UFB5y77ujxbuPLsyE1EUqcooC2spSI/RctPF3VIE8\n4SYlb+gVUmJOAhLmcI42OtOTANGlMBJDmYJdJqFZl+t1G3Yi3dJ2Q5Sp+rO2\nJZ2cUR//adQogaC5Q8R/7ErfpaK9ap83kRlkutHr9YwqloTbiW7NKpQ7kdT5\nenKHptOEumWgxyYcrquyOelVv0JPSSjrrlAFuv+f4q9P+0hdlg/rgxYA2MbS\nwT8TbjnKJOLaFsEnmv64ON+aMxb6P3Zf+3B2ZVkQqEp6JxdR6wUDtCnNrtij\npuQJ5demgq2DUaXOrOTxNhbFoF2dypmnVH8ad7PVAZ5v/JXOa5SlhiP3QKvx\nQEO+c/WsqTjHuTp2OPzcGdPdfqU3+Iz/XbOi8bdNN3xIJJ/hEkQfYNkmwbSW\nkdnyJxVxNuOqz6tImBokdyT2uB8Jn5qREqHQWahX2nT+WAoA368cmBPe2MRT\ntRmi9/l2K9MQSJ7Sq2z8veSB3DOz2PQyNh9e925ruHkknn7S1liUm2om1rnT\nv9nkOiU2YsI5jmehsomEpdKP1WbLBjQFWyaTEBPuj5R72LVBBdenwQo+sIff\n/Vagzzgt+6rXeYuM/yp3TZLp8jRIDZ+VW5psWBW7fuTgUSHBEUL0+Dglciec\nPb8L\r\n=brP8\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\nYou may need to export an `NPM_TOKEN` and values for the environment variables defined in `test.env` before running the command below.\n\n```\nnpm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"34cf4d3af22c250695a1fcfbe9f759010d9d09e5","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-01T12:23:57.889Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.99","@balena/jellyfish-logger":"^3.0.156","@balena/jellyfish-metrics":"^1.0.424","@balena/jellyfish-environment":"^5.1.26"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.5.2","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^2.0.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.34-renovate-major-json-schema-org-34cf4d3af22c250695a1fcfbe9f759010d9d09e5_1638361661204_0.5582369961213616","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.35-joshbwlng-update-readme-6a48566e682e456dbbc30f8afc5bcde1053d687b":{"name":"@balena/jellyfish-core","version":"8.1.35-joshbwlng-update-readme-6a48566e682e456dbbc30f8afc5bcde1053d687b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.35-joshbwlng-update-readme-6a48566e682e456dbbc30f8afc5bcde1053d687b","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ad3f40436f535b631e1ca87c1729d4d8205a4e14","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.35-joshbwlng-update-readme-6a48566e682e456dbbc30f8afc5bcde1053d687b.tgz","fileCount":212,"integrity":"sha512-9p46OyC/6xNrM4YSKWo1/QEvwecQj8HD9vQq7RMYeRTmrRjWA0lOpAmaPB+vnXZjlmdbwVrUHNbQbki3QFs91Q==","signatures":[{"sig":"MEYCIQDrpUx0Fm1CBmWtxY5/p2L92KeaJWszif+WzjSmDbk96AIhAIERHQ9nZ+WaPrmXCJGrdEKHfdIDBMkiani96yrZixD8","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":779558,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhqBYnCRA9TVsSAnZWagAAZcIP/jDjAXKu9Ti0ZabIRht1\nBoZjnRyp+xPN9j78ca5XDL+e29NlKGl/+qZ9+yc7d5iwhG1Mv64MFzAQKo2z\nfQZJ2ltie/shiI3/NhnCr7z7SbSr5ow5qKZ/ea+SKzUJUotzu+PImJVva2TE\niynXoKgXf0QrqnQMLVgn8vzhBnmrb3c8lZ2ab3aoqCwG280CWMTOBtexEceE\nTV6BRnwcbsF8VORsTZLyDfr4oO9qH6+sBG1gYA4SGoDH0wS/NpNxnpPg9j35\n+HwKG/03IveJBZbsl8gtH5UHu4CF+HPvPqQS6iygCvrmSzgGeEmqliKrbZy8\nzlagAI1teXuWXYEZn+VEjIfa0qodNkZ9OOWAQPS7igcpkY7wNNazJ4ICf185\nUT86DY2gNiVR6OIlVtMHZ2HrhoHi9O6DY2Xya9fiyjXsXDD7OtFFPuCC2Lsz\nPp2BSi3veCUgkGSIQNghIqm+c7WlS9MKXLMt1BKVbyGNqo9l/5hy5EdM5yDR\nfDS47kUXwjJtwxgdpaRdf8P5RcIlpunFcBHuGUoGVHGCjOxTp+VanDZDYfid\n4ezROf2xhLipPkQ5EzxyE2P/Fl75TCxBs+PAwJh3y09znfFXLR2epkQT6FO8\n/TB0JYLVZ6rT/aZUFH0405WAi4AuOLBW8egKUpNJS4LnPGggyBRe/jfbYJH/\njfab\r\n=v6g8\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6a48566e682e456dbbc30f8afc5bcde1053d687b","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-02T00:38:37.969Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.99","@balena/jellyfish-logger":"^3.0.156","@balena/jellyfish-metrics":"^1.0.424","@balena/jellyfish-environment":"^5.1.26"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.5.2","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^2.0.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.35-joshbwlng-update-readme-6a48566e682e456dbbc30f8afc5bcde1053d687b_1638405671209_0.22299161975737647","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.35":{"name":"@balena/jellyfish-core","version":"8.1.35","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.35","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"bd1083f742e7c651121304f1ccfbc24c7f819787","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.35.tgz","fileCount":212,"integrity":"sha512-yYKaYC8TmuLBSv1FIWbUPCEATmjoG33UNhReJkPAJ+6BTTqJUg+9g7UnA8a6mN316/9wb6CbD3xwrvdNXdrLjQ==","signatures":[{"sig":"MEUCIHXskfwCOuQv7/ytppIHROOSIQMgaDPt/THxaq4FgpjjAiEAutxed1b3XONeopeIEULTgxO5n8up0UaPNBI7BLc/xfA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":779493,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhqBfECRA9TVsSAnZWagAAeC4P/AvveO0ryYxo6R2r2ZoW\nXwDTUXwQTRYSp3p5pRtDXFXk942P/HJl8JVZYmFEfNzDyAK0pISe5RdkCI9a\nJ14AjKWqSj2Zyojk+WhNClIl5Mkj+mIwkDZL95KOUTPC+TRdmnVD6snr+Jw6\nP/N8gpvR/iX6rzn11w8EGSWSyaW5lDglVogW1Nzg7/QpCROdNdDOANCL7KKU\njuAkc3lxutZ3CaimSp3f78oufbpSrMA8StUPNmTSAZgMt/vf+hh7vQxvun4N\n7Nd5Gx2Vw3lAbb+Rg9YP2lFqQVIgLpAev/zYwhPqdNAAtLmwcXlJCVNqshBm\nu5aeeTlF0muoQ5EBY0jsRQlxJD6/xagTkWsrLKAeGGSpeUPokarqWShjRaaP\nQa17fCksktGEtQ/G8l+249XmIrdECMQ8ORZR6IbkqBivWxWEdLcGZ7Z/3JAh\n4V9UVn1SqZMIc4Co0SqCIJ0+PtfCDTyHbLw6v1ljZP0ZU4qUqeVf9Nv0637p\ni5kcnVfio4vYk9Eu4YnzClc9rPgJ9Z6St+UBWhHFXywFQzxRILMS3YKqlCtM\n9rlivD0QpyeHFnfJA+vNTeyKpKGSgSKWyy8/uwJTxUX8Fl8DwZznRX/OqEbB\njeNfUTV8H+GudrYSh0ZzK2UgYFerS9RRejt/GGBFwMhYWgB/akRmZ0z63g6b\nz1Qj\r\n=37Z4\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ede0245530eb21442284286a32d5e42d36f03d9b","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-02T00:45:48.901Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.99","@balena/jellyfish-logger":"^3.0.156","@balena/jellyfish-metrics":"^1.0.424","@balena/jellyfish-environment":"^5.1.26"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.5.2","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^2.0.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.35_1638406084313_0.7404985489473732","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.36-joshbwlng-remove-secrets-6d5a5545573b7b2a9a9fdac369bf2aeb97c4a77c":{"name":"@balena/jellyfish-core","version":"8.1.36-joshbwlng-remove-secrets-6d5a5545573b7b2a9a9fdac369bf2aeb97c4a77c","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.36-joshbwlng-remove-secrets-6d5a5545573b7b2a9a9fdac369bf2aeb97c4a77c","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"76c641de4886a21c2eb36201f8ef022a9f731fb5","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.36-joshbwlng-remove-secrets-6d5a5545573b7b2a9a9fdac369bf2aeb97c4a77c.tgz","fileCount":212,"integrity":"sha512-JoHPEyBYsYVxNILK+Ii3q8XSB0qp9ycUxyhS3DSdnxBiBcMnoYGXtGD4C30nmbJPzeq/ANfChu+ZOg2U51aJ0g==","signatures":[{"sig":"MEUCIA3wjjDVKof285TiNC+Ofj4acp/bAo2Lupz5QWPLc7M8AiEAgFsckF0Q0cHuzP/aRUQaVcOtJ0eu4sgZNB5SxyruzE0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":779623,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhqD6lCRA9TVsSAnZWagAAjuMQAJRJ3GlaARgMTMVt5Xlw\nke4KPh3LB5Ky6o/Zq6PV3kIoK6rVGavRVYnN0VeMlH9giEX9EfuZsoGfZlQW\nmBt/dEQggNmtwWSpywa7hVT0CtUFgMFaxAm6KwsfWgrgzM+tkzpEeQl0S793\n2xFm63wsXcvvOtBW3/yb5xfe9JaZG/StkYPSXoReJgDrQ3qv9hXMU1GxKM3o\n/3DEmPMEZnH7dBVY2m/2v2fP6dm/aW2Qwuhjrk2haVSj5YzkAge2pAep7tbH\n3TbRBCYDtQTlMXzJeYBs0aX++SsnzrzCYpZbXQG92mHIbqQKYF5pd7Ozw76V\nty5/jZ9fa00dugW7NRjDamobJeewMlBdzj2nqPiZfjW8s4uuU8EqHygEPJ1Z\n5R/9oTfzQmYY3joItHYlqdvC2cqH1dbN3nm4pqSXefx1HiRtxFH0NKb2l3aJ\nOX0smXFxi7ppw9d067xv4v0VO09yvDwyoQMUAr7ftIeuVO+iYyS+DCY2RRqg\ndANwoOKBQtWvNvvBGXwrhW3yXNbYOno2ypvDRn6CYz3t6OSAQ1RqExfASydf\nesu2Gdt68s7krST6JxDiBTm49yzp4hEYzzdNoovUjUjkyPhuX2DFXeGvbms5\nDiiUiq516njjRdmWqvTAOt1BMlR9sxYPpUr9mmNDVNi3e4jLEt+e3KoCHdGi\n3NGQ\r\n=0W1I\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6d5a5545573b7b2a9a9fdac369bf2aeb97c4a77c","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-02T03:30:23.588Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.99","@balena/jellyfish-logger":"^3.0.156","@balena/jellyfish-metrics":"^1.0.424","@balena/jellyfish-environment":"^5.1.26"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.5.2","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^2.0.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.36-joshbwlng-remove-secrets-6d5a5545573b7b2a9a9fdac369bf2aeb97c4a77c_1638416037589_0.5546687780679562","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.36":{"name":"@balena/jellyfish-core","version":"8.1.36","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.36","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6373354ce1d8eb5d97f5b56ec7b369ce7bb3276a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.36.tgz","fileCount":212,"integrity":"sha512-QyX8rl+QVIhiKUpSonLrxW5ZfZZs0t8naAXa3nf5rz01LC/OzhKFnWh3KcBYwD5WMTtg4WBPCNpdAGWh1BHvKQ==","signatures":[{"sig":"MEQCIEa1kJUhcOCUy+c3RZsNXx5ETxY4PozfxyxMM295WgDqAiAcHka0dqki6RW6LyYaSWYMxT7avrSBbKRYeSd1YycP5g==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":779557,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhqEAYCRA9TVsSAnZWagAARdAP/jiizbrmybrwpf4SJ5iE\nKRScr1N2bciunM8zUTLEjVWJMKPcW3Nt1aRMj2f4P6tXiYXq+bs8PAt5r7RH\nO5aey9PhU5SgOM6vlNQ0QgV/6sjaEsOLXs7bNrVCVfv1hgn5gA0Pe429i43o\n+oGCB1yenJt7iuI6QfaWOTyq1Zq9Y1I/4qLwa5MEWfRgmWGOi7NNIpqjDEI5\naauafdbhj1LYI5oC+jnSfJgbPnZzz4xOseVWBibSQTM4XSlr1QnuIV2+7u6z\nDcH9ku//OVG7RpxEATJfHqo7nxEYQP3kx69CxSc9jNi9+tFoHKbIBShnZtWd\nFG2X5h/eGfswjg0x2of6yVfgUtpthE2fpi8v9PQvKcHaiDmIWynBfRIQ/+ya\nruhl/x+J8wK9405VQO88em2CNFso8jUl0V3KU/uZlW7VElwbJYCwJ1KkIGL0\nO2S0fZ8gjSyVuMDZh+bnapvTOYyYrIj5c0SunbyY/T0vZTOgFi0F7A2JLS6l\nw8H5Esmcu654+l8es16dTlg1TG48uygr2j2kwu9TOuXN/pEdi5q+pljPyMog\nbibYDbxsCtoyL9F4JwKJ4bBaEWIFxZ/eItLgLJc0q+TAAi78F+bWaV+IY06l\nM65g2O6+8/05oamyNIA+BUAcaCxfSdJI5l7Op3w+PA3QJFYCJqpq6KW7q+UE\nWkZp\r\n=eQZI\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"90856420ab9b0acc9c2bac14d0f7c256d77e0fd3","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-02T03:38:10.276Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.99","@balena/jellyfish-logger":"^3.0.156","@balena/jellyfish-metrics":"^1.0.424","@balena/jellyfish-environment":"^5.1.26"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.5.2","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^2.0.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.36_1638416408445_0.27922983008114577","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.37-renovate-balena-jellyfish-environment-5-x-286ce1681ea8adfe06627b43559e260e0b0a9b8f":{"name":"@balena/jellyfish-core","version":"8.1.37-renovate-balena-jellyfish-environment-5-x-286ce1681ea8adfe06627b43559e260e0b0a9b8f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.37-renovate-balena-jellyfish-environment-5-x-286ce1681ea8adfe06627b43559e260e0b0a9b8f","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"94c2b0ed2a9eaa76d0a5c547cfabeee1764a4d70","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.37-renovate-balena-jellyfish-environment-5-x-286ce1681ea8adfe06627b43559e260e0b0a9b8f.tgz","fileCount":212,"integrity":"sha512-yRF+hNdYt5MkawdfcLKGhJ/FFgtidjeB4yqPbwpBU2PgPjPFazZJKvnD8aNzzuyRWVE8Bq1kzUX/HKBdcKeBGw==","signatures":[{"sig":"MEUCIQDyT2dTM++sr8ZRufwgRikjzkk7/vpLih1/7Q6w76eM5QIgAxJs4cCh77Lc3Q7RLFsgvAF2lt3cOuMiQCEXL7ykvMw=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":779746,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhqE9MCRA9TVsSAnZWagAAFNcQAIAffJHxIOXbc9l5Byk0\nTZdCzJeEe25TvhoWYDR8c4QJg+iWNnPCUlrryUmLH65CJ5nRt/cKpzLGoUGc\nLqHdGxavQmNRA3BB4Sc87C6n2vL98WeInTfnPWEPgbDmXkWvDEutPSS8ZUYO\nL2w2fTMlBSwFFKA/VD5+d7uEPrLUtf3T5u9QKwJqCLXeAXC0jyaOOa5ISRfh\n279lyrJNb2mslnBadaHAlCY2tNcBY5TRwJoRl9Mow1MtSyMnfHpKF/vKZgdT\nrKGMnyyfjJLM4SH/gyKjXo4Tb8uM9gGaH+Nyb45D0jserzgwccY+M4rEI9hd\nEMrhYA0bOcioWUVSamg7BH/VACPQ0S0qoGxQ09IEyjHEdAL0ReVGVYIOV5Fq\nR3JS+2GFc77bEHDBvnuSn7CWcyf4rZnN96g/8DTq+4niC1voQK3bhPCdUMUb\nqGLMwI7OhBXTp98Ph9ObiilYDiXu16aA5qZAXMzidLSLJl0d/Bs2UzIYoRJT\n3tSx1PD4gb2004piTdAncDGnXwjkt1WZiZiIzeUgBnmyZKPx0F3Nfjv0A/dr\nsZOEEY/ehbdst4VQ1ryUInSD5VoGL76GJyXRbAQuhFm3hUwn9yUciDNno4mJ\nKD87gqvxFRsV2KIe4CX3v9yygUhTvY4kIoU9BRV59rKDLr5YIFM61CUT8xaE\nPbbv\r\n=5bSE\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"286ce1681ea8adfe06627b43559e260e0b0a9b8f","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-02T04:41:50.673Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.99","@balena/jellyfish-logger":"^3.0.156","@balena/jellyfish-metrics":"^1.0.424","@balena/jellyfish-environment":"^5.2.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.5.2","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^2.0.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.37-renovate-balena-jellyfish-environment-5-x-286ce1681ea8adfe06627b43559e260e0b0a9b8f_1638420300132_0.447385507254094","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.37-renovate-major-json-schema-org-9abd75a3efcad8e4480e180da26a3fd5d71a0ff0":{"name":"@balena/jellyfish-core","version":"8.1.37-renovate-major-json-schema-org-9abd75a3efcad8e4480e180da26a3fd5d71a0ff0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.37-renovate-major-json-schema-org-9abd75a3efcad8e4480e180da26a3fd5d71a0ff0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"7fad52db0f51fc589ce47bfe4d9d2161764002bc","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.37-renovate-major-json-schema-org-9abd75a3efcad8e4480e180da26a3fd5d71a0ff0.tgz","fileCount":212,"integrity":"sha512-wytvnmGX0Ss5njHThta2IaHI1uj4KelOgKRXC863qNE9TZ8OJqAplakaKVDhHPlYnLnCggR2WgqYyrRQ14J5VQ==","signatures":[{"sig":"MEUCIQDsgNhxAMDHRXt4zFVPgeohKCPCypD5EDeAD8PXU4Hn7QIgbXgtiDgap/DPvsQ3k/eHcMS9DrDlaj4p65T/WSxHA3k=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":779725,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhqJozCRA9TVsSAnZWagAARY0QAKHUdD5NYJ0MSasO7/nw\nPgfELO1O9Xr59y7IDRJHi+kL9NyPqFbm90kNxymT3BmKpzd7tnuFaWJq/AUT\nlry+4NnpLzFDyYz2DrRskcsjCqQ+oTDEyNS2VB4toRUJAy/pp7xqMFFFTdr/\n1M5Gurjwm+RXmtw3PU0vvj0ymFtUSu+hDQd34PvIhg+e033ylodVqLhmbOFz\nKRLgMtvB2MJeP6XWY6227wBvep1s29xEKhNjpOtZkjw6yHYqD+wXTmn8iooV\np8Se0OSZ+zmt6brmBQxfTaXe9CU5jxpfZvOnE7NpTvedtD8rp6d570GNjcu2\nY0/optAP3casJxLOoHcFMGttkjay/WP7UqwucyZuUTfr9ixweorl9+ZedFgQ\nlxrBF1R3CCTjll5J0K7Eg/YvuK7esUxz2OmTWa07pVSftyny7/HUMnp9sKr7\nw15Oe5Lnn4Effk4uOjjyWQLoB8S3J7re1lfkEmMduZbZPhs0NxtV7mI/cd6V\nZ35oqep18xBIarPwVUffZf/Xr2WlxN4lZDH+i92SYy/nh3onLgZqpIUe7c3Q\naN/c9TF0HKCPYI2sRGseFM+p7NZbX+CgfIkdHzkKybfYL9Cv3A5ZzyDW/Kt5\npUN5LIqddlxhzpnpr3F2TzRpwkpcxdz/3d30fzyKCMWqDrU6eG6UomcsPFL+\nqxmZ\r\n=skTq\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost POSTGRES_USER=docker POSTGRES_PASSWORD=docker npx jest test/integration/example.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"9abd75a3efcad8e4480e180da26a3fd5d71a0ff0","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-02T10:01:04.366Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.99","@balena/jellyfish-logger":"^3.0.156","@balena/jellyfish-metrics":"^1.0.424","@balena/jellyfish-environment":"^5.1.26"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.5.2","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^2.0.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.37-renovate-major-json-schema-org-9abd75a3efcad8e4480e180da26a3fd5d71a0ff0_1638439475079_0.5145563573142253","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.37-joshbwlng-update-readme-07c731b7231adef5659e71b0b5c43ff8e9c91ff7":{"name":"@balena/jellyfish-core","version":"8.1.37-joshbwlng-update-readme-07c731b7231adef5659e71b0b5c43ff8e9c91ff7","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.37-joshbwlng-update-readme-07c731b7231adef5659e71b0b5c43ff8e9c91ff7","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f6f706456cfee2d6f4f731877738e5366ba2542a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.37-joshbwlng-update-readme-07c731b7231adef5659e71b0b5c43ff8e9c91ff7.tgz","fileCount":212,"integrity":"sha512-6x/gVbnkwkxIsC5QvaCmXKlOOTDYXsQTSKjz0rC98bsB+oQJ1e6/LWHc5Wg4aGkBg9hfsIpZwG+M+/ryXXgiOA==","signatures":[{"sig":"MEUCIQDMJM41WsF/o49icabVPQn99tzmBoA2JaSdyCBTPsIRtAIgOQ+wnrR0u0SeLHSoL6pCd6J4ka6XIER0cfWPCIhiv1U=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":779642,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhqXsGCRA9TVsSAnZWagAAeYgP/ii2pUh6eOyeDLwBxnOA\noDEkqkYo98f+xytMxyPHn3M5I2+my8Z5BFcPTgcDmmhQhXdnOh1ldJlcVeue\nbtvLA0u29/TKJQ9H1r59dFN4cVn8CldEBITfsGvv3Zttrc24l8FJS+/E5S3R\nhhSd/NG1YjSIZ0LvvfxDhZF5t6tbiuEsu0ZSbR2gmBzHJDrzm+qZTFvKWcdi\nYk3X7M7rZ2ZRmbXlGKx62UJCmmc6wXrAewhiDU9QwzUMl+DABtVV6n6GLekc\nIhrjniZDtjDjv+f7YvRFCsrsyJ/y6/zM4RaDlp2b9zZ5H8fJBmnOkwGEygbD\nDe+VwGINdExN0pzSJbWfFbZohoUuzGcNYP/NUvDJnI2jXpfGr2B4IPOAodRW\n99OB1u0l+fXlHdytmutGySit9DQQ0kfJusAxPImTb4DOYddvB4SzSQqrXHzh\nX07Li+oNfEiiYC5wqJF260Nxxo4riV0dbcgKcsi7mLScJYCepou5PZUgMBpx\nVxEjXj2I+k+P49bnZg8QlrCatZ20J0SI4eEsGMUwWOpi1uODnDHYdh6ilnro\npSHOccdhoSPrQYwwT1aMcYzGXLIu6BGlwioV4M502rORHHSAovzl6a+rbdeO\n4qPOrXmVLme83NEST6HSmDL6Hin4YayZzMo22ukBStAylCORfss60Cydne2D\nBN/h\r\n=BjGj\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"07c731b7231adef5659e71b0b5c43ff8e9c91ff7","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-03T02:00:28.025Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.99","@balena/jellyfish-logger":"^3.0.156","@balena/jellyfish-metrics":"^1.0.424","@balena/jellyfish-environment":"^5.1.26"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.5.2","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^2.0.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.37-joshbwlng-update-readme-07c731b7231adef5659e71b0b5c43ff8e9c91ff7_1638497029941_0.0467755926492055","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.37":{"name":"@balena/jellyfish-core","version":"8.1.37","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.37","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2bb50aefd6a56ea7f4f3fde3236253ad69ce968b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.37.tgz","fileCount":212,"integrity":"sha512-Ri2YFCpWTOIOGZezyrfLEgc89GW9SbYM3Bs2CVQP9ErBN7XI84HtzyNgs2kqW3z+caCccp3dOzwErhJxuX5WPg==","signatures":[{"sig":"MEYCIQDdCaZHzNCoYaH5p0o3LemWjtOVMjrqkIWETYUd4iVG9gIhAJ6IXkXhuuTeeagwZPt8JvjMk9lBs6SsO7oCN1BjIsCX","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":779577,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhqXx5CRA9TVsSAnZWagAA9HEP/3SAwktFsiCZXT8pm6Kx\nfGKQ7zFXhAL+zxq1I+tbqFdELQmaNJ0aspkfWquq+HzvWoyEkbHvxKQ/JZYK\n7y4oWKKn1Q/nXAfMEeK1q4+/vM5t3BhnSfPdnWp41qD8U3Yaihe41cfT7WZj\n1XhxhhCGubHJwEKENpA8YWb80NOlzv9eu4RcojKdMwJGK3DgmwmsiyaUTcnO\nA5056k3NnbbYencBMe3Y18rkw3rODFMWVjh0dG0aCUekUvsudBX9IxqnFSGp\n8MqkvrgeqS3nZ1Tgf6bdWJ45oUmtja6peiHxgXbO6hoKum4SxfolkC4jKpqH\nW9ZzFT/nYkvkhCZWob+eYPFt83LeotSl/T3o3qPYC24XzVxf9dYBgSHhi0HS\n5I2I6Saiv5SGCvdO2siF5/x5gUD+Wfj1JieUQg/ogyua631Gs9gIoTGnLOXI\nEDZ7FhwoY0tqKdLat2TLDkJ4IDPxttG/C6aIM02Z+P7vKfsZOiIQF8VlPE0f\n1C1J2CmB6E1mnIIABEag6gFpZEZZocOTYQDkO4WA9UhE0X/QEy0GYEHPgyFp\nyzjW2Aqhoza5aXiRJIFCpbyyGCZHtK6fO2CugSFCRo3RpYxWn/7VatuCCmJf\ndqCH+56Zf6PYjfUrN150xGlzYDxdBzZBY3vj09fJ+81KjyVKmzkCsftdELZx\nUOOB\r\n=c6rf\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"be626524d34ccc3db9b5630177bd8f430c34b772","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-03T02:07:40.432Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.99","@balena/jellyfish-logger":"^3.0.156","@balena/jellyfish-metrics":"^1.0.424","@balena/jellyfish-environment":"^5.1.26"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.5.2","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^2.0.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.37_1638497401088_0.3076027747917278","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.38-renovate-balena-jellyfish-environment-5-x-b451c7da3c48246b73640248540e67a4ce0831d6":{"name":"@balena/jellyfish-core","version":"8.1.38-renovate-balena-jellyfish-environment-5-x-b451c7da3c48246b73640248540e67a4ce0831d6","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.38-renovate-balena-jellyfish-environment-5-x-b451c7da3c48246b73640248540e67a4ce0831d6","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6b5bbbbeba18f9b03ec3d127042fe7f9f7a352b7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.38-renovate-balena-jellyfish-environment-5-x-b451c7da3c48246b73640248540e67a4ce0831d6.tgz","fileCount":212,"integrity":"sha512-F/RC2CbjhkpCH01rimvguiEWr1Rje0NUkl3wo8WfNa6ZMhRo890KDyBiposc+gC4C8AMsUWvN1lwhh5qrvK3tA==","signatures":[{"sig":"MEQCIFzE2+5OlRF7CAfgazXmaMm1hDe6oxLugHTFz/4uONJ4AiBxoeTdhEJ9hc9MmPH297csFSwpil/yuQ7roOuGgo23DQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":779766,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhqX2wCRA9TVsSAnZWagAAZL8P/A1dzcrvQi7g+ltrHQgc\n8n/fxiOsZ3scKFnu4YOvlmQfXxAmc6VEUeAQpMpD7alMU5ULECG9wbN9m6MJ\nWwA48AzgYD58qSER5iAq0/4WdkN8JVmzWgefW4RUNyPLnozfEVBYFAAV1j8E\nDwnnLEy3Oo2+ZBH9XfqQE4L9CJFY3vug3yq+fraIEddrZbbLDtyFXqCFDWBJ\nJRElTM47Ty1UAL+tArBhP//8ahrrKh3y33hygIRw8tltVMV3cO7gQDDlL9FJ\ndL8Uiq6T3d77x+0gd6FgJkGcCPBaWwIKjtuODgGzm5PQKFRDmkpTMRJsui2M\noSCCjeRZJaEwsSL5W+byFL2OrryZfAIFr5DdC0BVC1iX5oXI3B2jj2HoPegf\niOmH70UVQmMGW0S6BDzeb/a9L52l+yQZWDJhhsyt29gJ0yiwpY7WjQ7fur+Y\ndmq0W1OB2coEfXPAYkmU5ZcW1eW94R3EDDDvUBfE+uUrrgHiQMa3W4+be1BV\ngCSFSfeI7y2EDh4CgbtuEMZr72yQSnzsrptKGiAcxemQynf7KxTsDm867oSA\nN5XntFua6M2/FrPfX+rYyB27eNRNRvQOqzyz8i1qK3h0QmMnbFsIa+JYFdsL\nh+8PLl6OBKZ8TETZ+PqyywmSMM1OyVVeo8QxObTXNUixTg2irygk2DSpxdcc\nzINU\r\n=/Gmc\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b451c7da3c48246b73640248540e67a4ce0831d6","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-03T02:11:11.240Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.99","@balena/jellyfish-logger":"^3.0.156","@balena/jellyfish-metrics":"^1.0.424","@balena/jellyfish-environment":"^5.2.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.5.2","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^2.0.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.38-renovate-balena-jellyfish-environment-5-x-b451c7da3c48246b73640248540e67a4ce0831d6_1638497712009_0.0852708315590347","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.38-renovate-major-json-schema-org-50dc4d2dbd39f1d52d840cf6bdcea083665ca0c0":{"name":"@balena/jellyfish-core","version":"8.1.38-renovate-major-json-schema-org-50dc4d2dbd39f1d52d840cf6bdcea083665ca0c0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.38-renovate-major-json-schema-org-50dc4d2dbd39f1d52d840cf6bdcea083665ca0c0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c49ef253bc6a0b858e0b0a782749922132ca315a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.38-renovate-major-json-schema-org-50dc4d2dbd39f1d52d840cf6bdcea083665ca0c0.tgz","fileCount":212,"integrity":"sha512-IgzElNT6gbgo6XF9SeE+3fs77/Sa2vFIic2OvISG97+brvagC1TEWcbEFHmaQUUF3FQyuQAopnyr6e6pwpg15A==","signatures":[{"sig":"MEUCIEAEawYBZcmIdWeMRtgsU7UCJAmLBH+IfsiPM+JqBYU7AiEAkNDly6SDaRnGdIVPwhcWChniRAUNT0qjS50JZ595o5Q=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":779745,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhqX3gCRA9TVsSAnZWagAAUCYQAIEctbJaIR1EFvmBV4Mv\nlaNSZe7xosZNYlr/mzGpU5VGkKRPgBRiixrjaXJErnOFUORlaTpSmFLKo8P1\nNPa3/95Aq7ZRB6myjpSnFwV8G0iLUcj325tixhD3CQzl9x9VLPMKkcnkz417\nTL8B0UuGfDz1Hm56b2hZgcKcvuZO26c0b2PLOP8rd6fn9cNsKMm3TnlDmFur\nhgqdgku0EKMgbFYEHyiDWELzPu/EXVaM64hI9+i1zMgyGUPcUnSpVkMJqwh+\ntK6/pk6jNCx4axR0vvT+d6iUOuEjjIrVu8Kn6qQEThYUIIei7O1XWmreMVkb\n7PDgauc2vAOndgtpJkanY+zOrWHwWMoL1LA2JjyoqxDSA19kFufMSsXmYkpe\nlh2JpAesTu+aKb22Uv9JzFrI32LQ6jMo8SoILYer4r3w4rATxEN4jWFHVv7n\n76YPUFX03Zz5HYlNLsVJ5cn63o3I+8H/pvq0/4B/RSD+bHqPQMKOEfMuvFv8\nJ/aV10BHJB9mRo1JJ0J+GOyzgSQymEiP0a3+Cvr2HoHb5LRQ84jTGE/m5ksD\nEeTUc0UxjeFXyeu1fsnEla09Py+LawKnlDyBPLsX+2zkSQLAVju1QJmrAQZn\nYarLt1oZdmIAjKlSBKQBAJKJKF0Q/blUvz4jaZOm7CsauHfjTXt20JA1s6If\nBoct\r\n=ut2m\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"50dc4d2dbd39f1d52d840cf6bdcea083665ca0c0","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-03T02:12:25.913Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.99","@balena/jellyfish-logger":"^3.0.156","@balena/jellyfish-metrics":"^1.0.424","@balena/jellyfish-environment":"^5.1.26"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.5.2","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^2.0.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.38-renovate-major-json-schema-org-50dc4d2dbd39f1d52d840cf6bdcea083665ca0c0_1638497760072_0.5135464426707119","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.38":{"name":"@balena/jellyfish-core","version":"8.1.38","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.38","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5c51fc4edb90aa210d05bd872cc1e1aa04ab78c1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.38.tgz","fileCount":212,"integrity":"sha512-1z++MATQnOQ3k2q3ljkK5ytj+CVSD7PkErVKitgGcG9IAooXtr1M801gW3TvLbJof+ZoT76uZdA1Wky6Z4oHFg==","signatures":[{"sig":"MEUCIQDNxzsxcGTKrLWvxR8xwfyp+K+6qrdIBGYCmk2QWvowCwIgFFAF/LhhS9KL4ITYQ/RRUq0dfCztBG03G7lUKyI5sRE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":779683,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhqvDrCRA9TVsSAnZWagAAYKQP/jphba65XQSaZAyzQ4oH\nNtDX9f2PH3sOF1bGkXdzTe/qTrEl0y3Jh8qlI9jcjdCdaYpSmA+uaXORB8mi\nWMgHy2fHKas72YeUJFUE5Mz9tJP61USLyCkbuy1fBb6QSRNGIMpG3nofbBH0\nJXlqVf1yU5r34VUIlzaOE6k2VGodKUbtK3n1HjOkjLtUgDteT9j+fuUnvPzb\niWZFXmtx7FMGJR/JDkPIcqqNAo7f0nOx18m5zv1je1RXoRzlGxnG4Yp9JW5c\n/M+6RmIhvdJmtpwk9xa+ppjFZIF3DkX89JxrIWMg8OJv5C/ic8y45GOHqCX0\n2f3tenMtrrjeLg6/aIgXaHzuARiqDB3ckU+H3XG+3ZEHfgn4UimZBQD+OwWs\nVcmsHR4fcKLGScjF3VdQB6Jy2cAPtshbGg+xyNiEcru7poScTbnXNJ70+iIp\nqQvECl5p2LabnKf6W9o4ifQCLsa4vRtv5rWAa2GnoBN3/omsR68rp3mBAKKW\nrBoIjY0TWiVKkyT3tN3ZtIJZ1njRYuEAU0ckt5MQtFr2ttVgBsbThAaeba1F\nCz091I2RzVGrpHiQ4FWA60se9tJjc/8F3aH/rpchRwTypVMCf8AGtFG3HhF6\naHv4ofiucZbMrKp16eyptf5bJrcu2zLYYyHk6xub5DUoWWgC0AafxutzijW1\nI6Mu\r\n=waxM\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6f474bad2f50c7b59fb48b3f52fa4f62bf8489ff","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-04T04:36:58.409Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.99","@balena/jellyfish-logger":"^3.0.156","@balena/jellyfish-metrics":"^1.0.424","@balena/jellyfish-environment":"^5.2.0"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.5.2","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^2.0.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.38_1638592747759_0.4403620134655657","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.39-renovate-internal-patch-3d30b40c84a7ce5495b612d8ca92e89ecd8d6a6f":{"name":"@balena/jellyfish-core","version":"8.1.39-renovate-internal-patch-3d30b40c84a7ce5495b612d8ca92e89ecd8d6a6f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.39-renovate-internal-patch-3d30b40c84a7ce5495b612d8ca92e89ecd8d6a6f","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9c10ca024970357a65a10bc74c82397fb773598a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.39-renovate-internal-patch-3d30b40c84a7ce5495b612d8ca92e89ecd8d6a6f.tgz","fileCount":212,"integrity":"sha512-yOD1GWKrMHNsR2L6/0R7PGfsif7zs61xMfUnhyo95+wlCAxot/4fdqJ0XaEFB0SplNXMRYa6+7ouFb8MrKQwcg==","signatures":[{"sig":"MEUCIQCNeCMMDhK3wE5TgtWveS4myP6nFtPH+CFoxfQhxuI6gAIgftRtQjhlyhhFifzQGTaVHvGFFkOXs+LMXYlUh0/Hops=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":779820,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhq69ECRA9TVsSAnZWagAAe9IP/jlnRVQV/4fXA7mqWEak\ncNA7zZrRbdV1uwRCq/g3NoBdjrT3eswR8rw2lzWqxWlAULIGAs7JztXoSPi+\nlQj0TDT0M2Hv8fFE8VolzzxNmwrgEGGWFYgGeWVM0XIiN9po+8n082kvPHRG\npce6o00o2q9+SXZ4X81FiRc8xX/CNCB1IqI2LstLF1QODQl4EN8/0vn2EYek\nmg1pzUst+/N7YBYWziCoEiQz1BUSYDde/y1H8L0iX6imRPbANfkWNr5nZy7d\n9xh3wEnnGvR8bvoLSCsbqMOq013O6PDQn/U/ueLcJjhiSlbCoknO4t1SuWUK\nVSlF/aVmXF+miLwNiq6mRqB/PQfJyWAneKszvZZGDBiVUQBww9Wuq93XZ0UF\nlmamdHW/WQMf7+dFko6kPQhEGRRNlFFimEfQhIciJ2ByGsI2Wo7AGys+INRY\nGYu8OESAa09ys6mdzzzqvzWcabjXNzga80VyjJbRCBTWoeHnSlj8d0ZYK/dB\nGoYlNgW32zfEz48V4ogzUEOid8PDrjGylpxCrgO8P2D6F+i5NZOcdvXL730U\nrryUGf7i1BprAYTg/8NcWFkonq2s98ftjvYXsViJ8g/04qMBYcQbYpO4zrXb\nrs2IUO/Jf/F0o47w3+R9blfN4UFiUdBVYP4wvGJcV5Kt9Nu2ROdNADgRv3wB\noSET\r\n=0pYC\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"3d30b40c84a7ce5495b612d8ca92e89ecd8d6a6f","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-04T18:06:35.062Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.100","@balena/jellyfish-logger":"^3.0.159","@balena/jellyfish-metrics":"^1.0.425","@balena/jellyfish-environment":"^5.2.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.5.2","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^2.0.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.39-renovate-internal-patch-3d30b40c84a7ce5495b612d8ca92e89ecd8d6a6f_1638641475851_0.07909456737744791","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.39-renovate-external-non-major-4f944bee203bcdef20000b427531849e29115d0a":{"name":"@balena/jellyfish-core","version":"8.1.39-renovate-external-non-major-4f944bee203bcdef20000b427531849e29115d0a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.39-renovate-external-non-major-4f944bee203bcdef20000b427531849e29115d0a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"996e81d27b670e08362d8b826c552bc72a68c9cf","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.39-renovate-external-non-major-4f944bee203bcdef20000b427531849e29115d0a.tgz","fileCount":212,"integrity":"sha512-S1EiYYdw5xX+lBwBS5/A+7+M4XVA6EP+7POOp6gUO+yK72cuHjPzxNM3tLsYGZXXekcG/YQSqso7R4qit9MsNw==","signatures":[{"sig":"MEYCIQC863ZwF3ZQW1B9MUVo1wNJ3RCk4MDgQ1CrkEXBZfZbAQIhANUmSSMY9cBLoMlfqC/J9pFMwKuO+ZwJKOs/RLUXhJfw","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":779827,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhq69/CRA9TVsSAnZWagAAUKMP/jch+4xM5qr6IliyfHdv\nxreUlGdTquPlZjYgE6U9OV1ynFqz3ygP5JkcvBmbit9rbx4CQTmrOd5FiKw/\n32QjoUEB972/dxxW013qFYifAoAhfdY3FtVA8zs/lDSh20ak6LuJFzW+QysM\nOF2l4OugTesQMmg6/BEm6DTs02syImL+8ucyptadgp5lYjCgB7w0SMkgOocU\nY2YmL4C4Tk56Noaz3S+jh/4AX5qQP3dJwOpGTP6xBjIU6L2JIWkatFpCPSmI\niIMRT2t2SSpwOvMp2sXaRxIE84kpF7fXybL02dq/zTUYzJb9d33OaPAj3JBs\n3EE69AHpMDRdXu7La5oQFFhm+VX8Afnh4T7MJBjEA3+OD2XE3eR1bQNU4yLz\n0xEe6VMVVvlgIEo+HblpvtRMLCH+x1uEeeWkIHRHsJgxsPEQY5E9Etrku37Y\noabhSPxy11kW7SDHLnAoJVM3NbKnj0lbRLHg+ZylLCKSI+YretY+tdwLLVtD\nITH7Oi+CrEMu/g22kJijgQJ5RzovyYCKbBbBjes/DoObJE40i8cEm7ytjcxi\nhZvmhiURGX6UZu1II2scusqIL0PVEFfVqCXtLnsUY6T7HbtshAcpB6IMgFZ+\nt/1oZSOOllbMnd5CE1Cez3zH8xBjRI0xXs440AOqlEsgMHPbf2RlYO7Jz35d\noZhm\r\n=Z4JV\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4f944bee203bcdef20000b427531849e29115d0a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-04T18:07:32.604Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.99","@balena/jellyfish-logger":"^3.0.156","@balena/jellyfish-metrics":"^1.0.424","@balena/jellyfish-environment":"^5.2.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.3","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.5.2","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^2.0.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.39-renovate-external-non-major-4f944bee203bcdef20000b427531849e29115d0a_1638641535716_0.6895870825401191","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.39":{"name":"@balena/jellyfish-core","version":"8.1.39","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.39","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"162e8be5a6091311fdbc50caed2791d2bb0da8cd","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.39.tgz","fileCount":212,"integrity":"sha512-tT2nfE6tPhAxZEOD9ogtAS8rJRCvAgZbBwkqHut9YBNjmkAyYYSc3/sbmORo1a3SxMG8b01SI7Oa5uxMJTbPzg==","signatures":[{"sig":"MEUCIQDG43ohT+98iHI1j+lXGTz21dztDAeedLJ6Xy1b+kJd8AIgXK8hpK0fbT/TriCkX48vw3H8Aoe3t12o9JNR4q4Mpt4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":779755,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhq7DXCRA9TVsSAnZWagAAn74P/jlVPVGBLmvja8uAf9Nj\nqhU3kYWQbXoJH3wkC+GbTSWsViB6ET2KN/UMqkgsJ41rZ9bLAY/eL5HJGvGf\ncM1DqsGGXXE99OJ1GdTG0/Lh/jGgLlowgctoR3Kbky4wB8vnhezVdlwNrfsK\nRWtMm2MCccgAVGCaaxn6JedRG5Ji/gYSRlGRst2tAqWRhlF0FfMoGJqQKS5I\nGqHJO/QJnB+EWFQcU4DfTyBM4XMf4RUmuD5IvQlaQ1fQ6cIMyRNcyfEuGHhZ\n2TKuptM05a4tgsXXWx+whsE0/JLALeHoYe0wo5bpm7rjjf2REeSs1CxY3zne\nuQyUL1B++Nt/n/CzG6CP5xks77Q4I7jJxqlErnAiX26Vv9Ej2xfFxJ+xMW/Y\n7um4E8q96N1KEm8htQcwnaEpCwqbxVNH5q2C6PL4pKnmIeYw/gekK73TrlUU\nmxx34I7lt6ZWqxEiNOdCUH3f1r4ZfErBqMXLJlW9lvJU88RFqgPJuHnmhCpY\nS/IidjRWSfN3cM27bbi9PJNGnOwHibxdDVsEvS1DzdbeWvizNry1kgAUa/ln\nFXfZbo0D16LjdviLNVpW/T6L+rXjaLnIhgSzZ6Sn0+2cSGRogJRfWwXgK1Xw\n4VfSSEh5pPETrH/DGN8fKGn/yLZOjXGpVbujmmMba+Zze66jbnPBHoILPpQH\nxzIi\r\n=8y3/\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"453d325beef247b984eea894207c3eee42b45112","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-04T18:15:23.379Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.100","@balena/jellyfish-logger":"^3.0.159","@balena/jellyfish-metrics":"^1.0.425","@balena/jellyfish-environment":"^5.2.1"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.5.2","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^2.0.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.39_1638641879730_0.0902606913614672","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.40-renovate-major-json-schema-org-8fcdca46b96478c8b932402f5fa9a52cc4a45bd1":{"name":"@balena/jellyfish-core","version":"8.1.40-renovate-major-json-schema-org-8fcdca46b96478c8b932402f5fa9a52cc4a45bd1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.40-renovate-major-json-schema-org-8fcdca46b96478c8b932402f5fa9a52cc4a45bd1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"fa8c77d79d67c1be60370bc0bf2d77e3b6a5f76f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.40-renovate-major-json-schema-org-8fcdca46b96478c8b932402f5fa9a52cc4a45bd1.tgz","fileCount":212,"integrity":"sha512-DJ4RFJ0EiqZAn3W07wp2/9AMIjbSr1EFGULmReDWYjPzPbXm4LrLVSckxBl4uWVpLfs1MQj7ie50Jd0ZykQqxg==","signatures":[{"sig":"MEUCIBhn96bEYB1WCYvJgBk7NUVJuEHA7Vi8WB1UsSAHgmzZAiEAyXk0nxA4aF39vUSs5DcrwtzdBmjJBXEBYRM52PnTXts=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":779923,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhq7GaCRA9TVsSAnZWagAAk5oP+wVtKcUEAut4S9yxIPvl\n6iB+7vpug4bvIJJjZYJcqZIWCkKnt6DnUAw7D5IrmgchtT7BHOCiTokfB7bQ\nFZEc3OwdzMD4FxW2taEqOXGE5zZMTqmTf402+DQjMi2vunT7q/zrI/fp4UTD\nI8q0ji0xxOvszAoCQty9m/kPITf7bZMdVPChdIQSLDO1z04heID35kphISYx\nkXEVMwj+lVDBL6KtVRc4cVx+qSTp9UhGptp+Mp2zMtRMDgOkWZd5QxGMf4Ae\nsdy/Kgjc1Wkrh46niZK+nD4/70geFq9y7lPi4nB5tVUnUwaUns3WSoDD2boi\nDb/wigAUhQW02r6xaTdZLCTt5GNSaFfrM0VE/FG97tlhGK6vzBueStZrLp69\nT/EQdK76ILCr83/8O2E8DOCShU8YZ/zJLZ0jCCnJ260in5tNVqk0O3+V+Fw1\nS5TPefha1VQ7sq+Pj8xJ3RddFOlRcXaIUsrLTM0SlZh3CqHKtImLnF8JRDdJ\nCA0qI2jNmL9T/tVvvYxc/+parrqg6xaHTc6gBHMv/41lmFaTTW8XX3w4FfDe\nDZ/rVCen34vNe1i4iwoo7nD0IJ1qNGKzOLBfZingga0fAR5dGOSr+UPFGeCZ\nbRX89+KLp0n69+E9f5d6XF8PsZ9rc68dWVDCx0Vq/nSPwpY65guk176vlXPH\nDWgT\r\n=Gyk4\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8fcdca46b96478c8b932402f5fa9a52cc4a45bd1","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-04T18:17:26.384Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.100","@balena/jellyfish-logger":"^3.0.159","@balena/jellyfish-metrics":"^1.0.425","@balena/jellyfish-environment":"^5.2.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.3.1","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.5.2","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^2.0.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.40-renovate-major-json-schema-org-8fcdca46b96478c8b932402f5fa9a52cc4a45bd1_1638642074237_0.4970365030510582","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.39-renovate-external-non-major-10ab6f773073f866987cb58a7d18ccc0d23279b1":{"name":"@balena/jellyfish-core","version":"8.1.39-renovate-external-non-major-10ab6f773073f866987cb58a7d18ccc0d23279b1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.39-renovate-external-non-major-10ab6f773073f866987cb58a7d18ccc0d23279b1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"78652bbb2d28ee4b96a71c3d741f33c9f812106b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.39-renovate-external-non-major-10ab6f773073f866987cb58a7d18ccc0d23279b1.tgz","fileCount":212,"integrity":"sha512-/813WhlQ6B1Ry3QR7e8lt0DXUSpvDd0dzVXnPQfcbmkFGEq3N8wjmzj8ZF9zI4U9yl5rM2W+lnjrSlXKB7wqrw==","signatures":[{"sig":"MEQCIAM9HkOmFqCfIwD6DmsDUeWG8npnvNsLiDU/JbV09ZvrAiA2OJcB02QCBdcGiGP+J2g8MsOpY80hL9nYWngZIgpKhg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":779874,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhq7G6CRA9TVsSAnZWagAAk7gQAI75UXy6ckTdQfSXMvse\nezRXrNwFx+iEZCmqXymGwgm/knIQppVs0z3bBeOFyIBTjgH31iXaJQdCkpuP\ntm2PHYH2DMfozGSszEGQhbLF0nwb9SyMkhbJQIbNvmViUYzQzW3NekyrSFnQ\nrOPWKkiEKqqsJvUe99+hr7GFfqMCMDEQoQCAMpIa+XWeKwGe1j5eQsaV7bRn\nMAshslhTqBODNi2IQgFuW6rg3w5pmbdW9WNPN3mPWeAH83rAyb4/8a88VFVz\nJYRK94Ay4r4p7ZbBfBUc6P2pI7zgWQKE5gfiWdIWTwse13xQl68h78tO3Oga\nRjzZ7MlRVm0akxqLpEd51Jd42sOFvAT75l6TmwbZzqHs0KZOyEVs1GbrW2KJ\nLTe0bmARjJXAFdtd0C63eUVwa8xkeYnA9qtkoKHLCNRK60fYjheaCIq78kDl\np6bYXd3EXPWhFNQtFr4om0qVz2hX2Fz5ySEStfwb4c9/sC26ZcQpGBJmrxCe\noZqWlKqZFY4V2KQB7xMC1jpujFWBRV08ebi4oGLpMFiTm/Obs8+otVZ3j33X\nKayopRLSbNaxyra+jea/xMEZe5GZVdeqmVjg6yWlu4mjDHBYx+tFBMgQZEPp\nVaavtOQX1fuuSw5R4RUiSJYxea2oyBfvitPQXV5xpaErpfZqnrkB6Tqfbw4x\nQcWU\r\n=u41J\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"10ab6f773073f866987cb58a7d18ccc0d23279b1","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-04T18:16:37.596Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.100","@balena/jellyfish-logger":"^3.0.159","@balena/jellyfish-metrics":"^1.0.425","@balena/jellyfish-environment":"^5.2.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.3","deplint":"^1.1.3","ts-jest":"^27.0.7","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.5.2","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^2.0.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.39-renovate-external-non-major-10ab6f773073f866987cb58a7d18ccc0d23279b1_1638642105956_0.9978176451513798","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.40-renovate-external-non-major-a050891f4154f5ce8852f9d149ef0d691922ed30":{"name":"@balena/jellyfish-core","version":"8.1.40-renovate-external-non-major-a050891f4154f5ce8852f9d149ef0d691922ed30","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.40-renovate-external-non-major-a050891f4154f5ce8852f9d149ef0d691922ed30","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e58ec75b15bb9587914e5676b73c850984e348fa","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.40-renovate-external-non-major-a050891f4154f5ce8852f9d149ef0d691922ed30.tgz","fileCount":212,"integrity":"sha512-oESbjbkco9Qyrr5iNrhSts7SON6/+L1l3xSVZseChVmhx60BBtRb4U9w5DD4BW/o+AZCIQa4NPDJ/xsgMkvM9g==","signatures":[{"sig":"MEYCIQDgHBdnNPAsYwmjtIroUEt5Fs73XGXIO9scxNtCcF7GIAIhAPQNSMBbm6DsZTQisOSEsuf5jAUtH3kI6JFdv24wTUiZ","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":779899,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhrIW9CRA9TVsSAnZWagAA+LoP/j9iBKjQgy7lrhKKce7s\nn2kXzMmK1ArWMJqdnqMW9014Fae+eg1gYsq+9+BYO6jtOd9736igPPzRJH6O\njJuF5Q6Npw7Xd4LSz+0T1WukO00Dpj3X+QXYwOE1FWKRrI3NGWGuYPCiPU+M\nO634/pCrRSw3aKWBgAqZP9wehkcsm/gIPxSoC8cl06hmu1vAsbQKmEhBVNJM\nIqdWM2dCGwtHb4fSO7wGgpF/1dgKz89mIOKR3LszcJSPVitalA/NrWQ7icN4\nl454qPRPrNi+rYwV90JIY8iczytLJen8dO1ItLbC0u7mgk4wQB1dsciWkFYe\nlY4giADSGx9/f2igbbUzEuHYSQQiT1ObpSBKqHC/CGigGXEiZtPT5uPuw6td\nwoVLmmgxc5BDtjC8SrqQPQWThT2G8gGwWIFBq9XbbUHsEwC0F9UVlcNFe61Z\nbDZGv/9CroKfzIHxFM+Q8PHpfUlqVZOLe/tPvwzYvBPmevBCuvV1kVTK1fjv\nSOax1m+SHoqLWjB8CNCC/udiA/BfAPr4atMVTACA2gFCYvhLgQL4AX33QbUN\nnKNft1utBqVbHzgzdllZdtMTcqIV2Un0kt/OCbfwaR/B5+XWNel+33U5g+df\nyNNdcFU1my64jrHokaSAMasryM/Yd8oIYr9YTvqL8SUsZfswLI3HFwKEWBZY\n92fh\r\n=EcuM\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a050891f4154f5ce8852f9d149ef0d691922ed30","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-05T09:22:38.064Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.100","@balena/jellyfish-logger":"^3.0.159","@balena/jellyfish-metrics":"^1.0.425","@balena/jellyfish-environment":"^5.2.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.3","deplint":"^1.1.3","ts-jest":"^27.1.0","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.5.2","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^2.0.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.40-renovate-external-non-major-a050891f4154f5ce8852f9d149ef0d691922ed30_1638696381597_0.9188638590661322","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.40":{"name":"@balena/jellyfish-core","version":"8.1.40","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.40","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0a0553e97077f17e36e2d399b2ae09f19ff08836","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.40.tgz","fileCount":212,"integrity":"sha512-ixvjEHzDsbgAecuvg/xhWvyCBZ5niofW2kLx/BqY6smmgmYBeiUd/WukM73BYWR6YA1EOPqHF7Rx1rjCxOtHRw==","signatures":[{"sig":"MEYCIQDH39jWryfi9XA3qFCMbCn/kAPCI5NkNv6WJmVwUDrt9gIhAPcEa/kPyhsYs9l5dU2BkvYXyUk1/W1KzPfG1veuNyz3","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":779830,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhrIclCRA9TVsSAnZWagAAPIEP+QAlhVTGZEPWR2V/VNJ0\nparZu9vsZ4PQ3E/v/DXMZef78guyUhESeE03cgtDXA8Jm0oOV7aMaVh+V6xM\nH6+CAbfi9fZJkIG2BV6QYs73KGODFaQeLKtcoNDgkg201Kk5GrulWuWtYwZy\nHzwLzGZ49YRknEA+/Pc/hBMzSo12mlFCXWXpFio09E0sOrnW2eXFiRryBtws\nSiuZfllWkBiaRmOn1prX9EcTGb3eeew2BUFNLSMCfY4INuONixVKQihVbNr9\nkqSvBu5jf+3uv6uDUMNb8OFE0qLZLthKp8VKfksBFdaSz4KxuhTL6bdSFhMe\nEiiCIDYnepgysrOJ0e4aTFxyCpAYpCC4WowfExviFhwtET5P9Z4eBz2+f3jF\nX4h246SRK+lFq2j4eKJVmuhTuIU9RfH1TaejunZyXLiVnQyBh5KXFjvR35TX\n9PnF/wRRuiEZ9wcRsR/opEcBa8zuXfx1FxnQ17ffzSUBlvsskuwq76wOZC/x\n4tXE2KJQ5AETiRxBkfRmzHE73LB1UekAuHc/KOWJo8+zdMSm+aSi66IK9o0E\nNU1D0VAb95rT3lx5Oln/uPfxRlLDAcP+Qc6EP/e00HSa+lgVWmj1RUi7xXTV\n1RBqa1x/z+dMYDn4Esu98e8Eg7J3Imz7nMckOOD8v8nDhUXyLXz5mgwP+/it\nDiOe\r\n=Xtkx\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7421fb4cedbd083b6fc5ca67797ec77bda94bc87","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-05T09:30:10.472Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.100","@balena/jellyfish-logger":"^3.0.159","@balena/jellyfish-metrics":"^1.0.425","@balena/jellyfish-environment":"^5.2.1"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.3","deplint":"^1.1.3","ts-jest":"^27.1.0","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.5.2","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^2.0.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.40_1638696740858_0.2513916832552434","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.41-renovate-internal-patch-be985397f516542c6766e840001f0f91dd141437":{"name":"@balena/jellyfish-core","version":"8.1.41-renovate-internal-patch-be985397f516542c6766e840001f0f91dd141437","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.41-renovate-internal-patch-be985397f516542c6766e840001f0f91dd141437","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9f6c1ecdfbfadb7c317d10529094104c61c8df88","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.41-renovate-internal-patch-be985397f516542c6766e840001f0f91dd141437.tgz","fileCount":212,"integrity":"sha512-4MX6VmGdTNo2zwrE31Ff544W3DDlU5Bl4BotDuEkEKhswtr3u7ajcUI6EScMQmT6e5CvcDg06M7fHGUD7WMvng==","signatures":[{"sig":"MEQCIAcDVi2Y14oBpOnj2VwtKDBYksembxBXYo3nz0+7b/xYAiBzspFHB7mVEttH6iB1ALpePsqqe3o6oqSC7ROr7LRUYw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":780000,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhrIhaCRA9TVsSAnZWagAAvxwP/AwU3jIiczfmcSFhgq6A\nAL5I40WQ0WxpZKIX0d51/GU++/5IFl4sj6Z/3uFINajuhsCbj4qaPPmioHTV\nJO1WxvXV4ZNG8dHux03NAHMhfehDmyqJb1oGyjIw97qU5G1+JDi151qbB2zw\nFLqN9fwVQvUJ/LB0RrZ+I0Fdxrxt+6r/bUnsHtadY5a2Fugrak1BlFZBqOBj\n9uTLK53t1y9UN9JQ3Wq/oXU1nIa6Otx9I+7kJbviB4MFNAC5B1OkVGva/Cs3\n93vbw6ac28QGymy9fuErWsZzfCiuEb8ig8eC7VxuXe8WIGL2UWtzJKZoowdy\nfgroNS+X68qUhXb/EsWEqumycYu3pgMP9rB0rWO8e88rlPBBYHYQ9Yt7ZssG\nSUyRAAay3SAnGmcNyp6xOGf6Tq9vE+QGELYFwcaXa3FWnidX8VGP9bj/9f5Y\nQ+gQVDCxHgygXEdx+kMPzs+h/gv5NtVEIXsZUx/01o9ZCfG7ukH4T1fQ+Gz2\n4e1w2mWWF7DeMBi9wKNEGvIV/Zx1CsCpu1XxW1RLnXkCmmb760+Yy6oLvPC8\n7H8mrn25u0Km7PN0gJkYWlQvWdgju/p/6Wpfan/TFPTOCKQX9LQEkyn8ESt9\nNLFuxM1VN7Anc3PmrT6mAM0+ZRmjC6pMY96BJ9pBf5Sz+2EjMlIqwd36n2FL\nXVwH\r\n=Th2U\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"be985397f516542c6766e840001f0f91dd141437","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-05T09:33:28.510Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.100","@balena/jellyfish-logger":"^3.0.159","@balena/jellyfish-metrics":"^1.0.426","@balena/jellyfish-environment":"^5.2.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.3","deplint":"^1.1.3","ts-jest":"^27.1.0","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.5.2","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^2.0.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.41-renovate-internal-patch-be985397f516542c6766e840001f0f91dd141437_1638697050262_0.4312201403227489","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.41-renovate-major-json-schema-org-922fd448d0ee1a36a118ae1a9bf7fd7c126ea674":{"name":"@balena/jellyfish-core","version":"8.1.41-renovate-major-json-schema-org-922fd448d0ee1a36a118ae1a9bf7fd7c126ea674","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.41-renovate-major-json-schema-org-922fd448d0ee1a36a118ae1a9bf7fd7c126ea674","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"7293ddb84c7f40560dfe53671b138c93ab85a10a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.41-renovate-major-json-schema-org-922fd448d0ee1a36a118ae1a9bf7fd7c126ea674.tgz","fileCount":212,"integrity":"sha512-U3UZSwq2wLJLn/L6SDq+kgY36a5iaVNbqumPKGjTjszsD/KPUuB/p2p6Gl1W0pKShso2UUQmej20UtjzpVkSgQ==","signatures":[{"sig":"MEYCIQDVRjEI5Kwl8Qq+gwO2WgEC7aRZ3tZOp/hkzaESVpjwggIhAOeBj93tfSlhtTZxupB4nh0tdXuwAEwyW/os90GZIEbn","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":779998,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhrIiYCRA9TVsSAnZWagAAqioP/3h+mWqLmsYZGEd6qb0k\nlX5HmhD9BuDpMoHjm8EghCWRIAgQVymDBhfAgqerM/0GQ4NVHIE57/Fv36Ku\nmNmT5gxfcVXPerSR6yg52u5Jacdq8c1eL/ODHJeGHEvjjTc25ie4n0armFzJ\nkMYI+VVOqaHjRwj19+P2q53u4EjMGjtkrmNv7SMTmgbroO41AnqFKH95kZUi\ncxLhcqV+58lXuSti4qd7MbGFyAE2xrOh40WVwFL6f8YIMFQxrCe61mfmXxcn\nVTDyfxX10gWQ+mkX1oX/kmWzrX4LwEWSPoqpbSsA/CZqFpcpHMo886Z4GZIv\ndWSWPFzNE/CI6HhjqcoUkpzx7ufQB+jny0Xaxm9e6Bm9JaaW7DGq6KySczKA\nqNHN0GMVD/XVy5agUUGBVU1BYJOKro91fmsV0VwWrgv1BtRixRajAFONmIf7\nNu/OiRQbmJvceT8ijkDnd8raYvstthOV66M/nM6UlJVQWBXXwnMqpg6Ei2+J\nELWSGisw/6xMQmjyLwmm98nbgE+m75dTxJfM6QDcnXLM3/Y8o2easw/mA3eY\nTfzN467QWBVQXNs2btUH4ZRHgtMefdqbJjkXY3jOV+ZpucuWghG4YjHCZOAO\nwxddycd23VSCiryr4a+hMJm9l9mg0jJS6KV227jMWpyf5c6aLVffgJ9hggjz\nIVHr\r\n=GIVo\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"922fd448d0ee1a36a118ae1a9bf7fd7c126ea674","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-05T09:35:11.225Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.100","@balena/jellyfish-logger":"^3.0.159","@balena/jellyfish-metrics":"^1.0.425","@balena/jellyfish-environment":"^5.2.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.3","deplint":"^1.1.3","ts-jest":"^27.1.0","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.5.2","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^2.0.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.41-renovate-major-json-schema-org-922fd448d0ee1a36a118ae1a9bf7fd7c126ea674_1638697112745_0.9336004838208589","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.41":{"name":"@balena/jellyfish-core","version":"8.1.41","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.41","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"42f30669f1fbdd121750640d8272a04407dc3091","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.41.tgz","fileCount":212,"integrity":"sha512-CdKCtDJz8HKs/96CWWBOkNjnaf2Jr4Na2Xw1sEFvDI2oXxL/REqt7tWtT88fAN1mDPnSvL3Eo5CYCWFKXuy8Fw==","signatures":[{"sig":"MEUCIQDlbXGUYj9r4kMgKvDDxbjcMdTG9iPcEv0EtzmFeypGmQIgVr9JM+klgaYXYTKqF4HlwlLfOSigp35tjzb4MDazgvs=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":779935,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhrIooCRA9TVsSAnZWagAACPMQAJ0ZGQAzlH7qbNBmN2yt\nUDwZIuu3bUnT989rtSp+IdZ5Pdb9EjD8p2qadFGBTX1VzrHUUjihB7COLzFZ\nMYMcmDST8zKFnYr6CDDw1YyiCM9EvmhXROTAIhMhImqaop5OUEXHe616IAvC\ngZfa90geBaulhdr1r+kI8lUv+v6aI7dgDIGjfLMejuUUc8n3QoUfVNXb3Efb\nWP+rX7LPEjgLNt2tABfpzqFY67lL96vU593v43oBfi06X5kiUt4222DXrtPI\nRscxwEPRRkkm52xmRymugyBL4wLKGNRumL4hCONLBc0OFrliYUpWmL370Hex\nQJghUjkqE6mXgBzRaklBAm+RiNB+R6u0Eko4vsJdmManXkj3DqIgIP79EMOl\nqxUG0Uij0jr0fIs7CfE0pLyAFaJ8o4+zh488tTkqcmqJ/7YIN+BUMejZl+fx\nUg2rlMkgfvQG6CKL/KT70exWo72HjQzNBUkEUrqpe7nmL9DnnL3UFn5aLBF4\n/V8fasVIVIhRABR/MvRySxcwZQaS/2XIxN0YebeXnRMXn+OpdnUf6RfwCLD9\nVXkgkvEoL9OJtu2+98yY+fT5u77tn1AotxJPaarI0UCD8Ez/y3cgC96r4LrT\nOIGBYm5E9a9+CWOtJEf87EbCOrDFbMFFBm8DuwLxXPEOx7trEWXF/N36sbeB\nud91\r\n=5YIk\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"23a273023014a505e7f1c6585113d194ca2c5445","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-05T09:42:23.190Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.100","@balena/jellyfish-logger":"^3.0.159","@balena/jellyfish-metrics":"^1.0.426","@balena/jellyfish-environment":"^5.2.1"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.3","deplint":"^1.1.3","ts-jest":"^27.1.0","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.5.2","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^2.0.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.41_1638697512075_0.8515097819650685","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.42-renovate-major-json-schema-org-cb191288cc47af4987fea602c0b5237deb20798c":{"name":"@balena/jellyfish-core","version":"8.1.42-renovate-major-json-schema-org-cb191288cc47af4987fea602c0b5237deb20798c","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.42-renovate-major-json-schema-org-cb191288cc47af4987fea602c0b5237deb20798c","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"51e51b79400435019685df90126bb4cc811663c8","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.42-renovate-major-json-schema-org-cb191288cc47af4987fea602c0b5237deb20798c.tgz","fileCount":212,"integrity":"sha512-OSwTkjmmaDZsw4PosJxPqobBOgZQMMw5qE7iNM4vAisCwTunzdc87m/I92jrYsMSRfT1uLF6e0fdlFfOTEaT7g==","signatures":[{"sig":"MEMCH2SH00c8hJFymNMEv7G2RqMfdnI58rQ4hxl1VChU8DwCIDHu6KfmKfzdJmjWg1P91ZA14R1xKhiFihOFe/kTPscL","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":780103,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhrIreCRA9TVsSAnZWagAAoIIP/A5W3aNQD1wgSW5vO9kV\nNSznBXGnmniUGIY1W+UFEqcwBt8B/SYr1L7hVYJ86653Ag3y33AqqvAD8tZl\nMbvxLesMHBT++VcGJYpeajfZ8N4ZJkX/K5Dnd79R5JhFANLFiW9+gaDcRL/r\nMz2wxIZKX2drSyTMkpTtPF0QkXs6HUb03iPDXC6AdhbRusBBm7/RyMx3B1pt\nvcO12SNfNp1lLJX20VXOLGfavwQBZ6K0Sb/+QqcKugf/cRrkNxAEkP4rOgHq\nGaXsZo3y8LKB9w4Hcgc8xUpEl5gVTOcqtr7ZmC30fmqZ9b2RjmmgKAHNCeEZ\n2eI9e+Xla79EbU6Lpmh1M94+0D2Ry5ac3HG27LyIAAqb/zJeZUOtlHkz1lzC\nh8FtBSyDfFYFzYmMHFJUH7EXd9PjJ5IoJjkXm9JUTdkls5qFgmePb4PjHJmz\nEqxcj9rrzmkydD0c89bp4wQQovPa+saa/SJmP5fpclZT59F8939lH9wWsqYy\nQxYI3g0sJGwbLotyjhBask69LKNE02nb7Jsh2qCgX/77gP5t+K5taoQry5w1\n+DqV4LtJ3lrojyWFHXKhHtj2s9LQrYe8zCrDC6uusHHi6A14iU1goMH/0est\nvfiH4npre8BNa6HWNuZIeOlplS29zzs6DRYL0U/TCWStg+2ugI9LRhfhoP3F\n0k8r\r\n=KiPI\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"cb191288cc47af4987fea602c0b5237deb20798c","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-05T09:44:47.062Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.100","@balena/jellyfish-logger":"^3.0.159","@balena/jellyfish-metrics":"^1.0.426","@balena/jellyfish-environment":"^5.2.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.3","deplint":"^1.1.3","ts-jest":"^27.1.0","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.5.2","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^2.0.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.42-renovate-major-json-schema-org-cb191288cc47af4987fea602c0b5237deb20798c_1638697694532_0.15251009043950292","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.42-renovate-internal-patch-24ef2e0e08597f1e985dbf25837fa48b29cc513f":{"name":"@balena/jellyfish-core","version":"8.1.42-renovate-internal-patch-24ef2e0e08597f1e985dbf25837fa48b29cc513f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.42-renovate-internal-patch-24ef2e0e08597f1e985dbf25837fa48b29cc513f","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"305443d0ec4eff0658568ba8b40210fe52afc9a9","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.42-renovate-internal-patch-24ef2e0e08597f1e985dbf25837fa48b29cc513f.tgz","fileCount":212,"integrity":"sha512-xZ+VB+ju2cSQbXjnv3QiymdAgfel0jaM4PvNG+XKKzMRysrII+XfA59T/sXU0gp506LxptaiBfmUVWlg0rFhPA==","signatures":[{"sig":"MEQCICu303snjcL3sMqGwu0hj5ZyyMheng3whDsMDXsFc2EEAiBDAj0zxF9jworyS+VIePkjEfZ1BFt9dg8cDl/wQl0TTA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":780071,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhrKZFCRA9TVsSAnZWagAASNoP/3sqebit8b0tGM2YCEAg\n2vktsUh2SCvoK2mJG4HDvvrI/G8yhwMkfRBgSuk7kPT8eQEnagJkMD5aRl/n\ngDyFXfVMH0mj7VUExB24/4q0JcPZ+kC9RgW8RCgPN72O0uwbf3ekXNUXeO9a\nTHJ4Sl/aUDvIxVnw7N9j+gnqvRk7OSdPJ2PdG/92XtrKQveCTWTJ4MBIoYTK\nVRBftb/9EapUiysu1sQNA+VTma4izCqdaUug3jYap3rkky3fvcpFvb/lT44X\ny6a1vTIaDK2+11J/sYvkPN6oBvRH2W15Eknpphf25BQIhX/Y5gvUhvK0OuJ/\nFEr8lMeqimv/MzS6uNtxL3m2qmnsMBzIm3lRm+INatkyBRvH7CKOYnAX1tXJ\nTj7yCHQElb0hdlJu4u2DHCUbSUPIIhoVe5EZd68qYzj0ZuVCrdU0Sfv4Q1Tw\nDc6i8U8Jsn/Habnr4mdh6j96/Z4zJuoNXk9TGiY1kvGR73RYbzCh2nVIPcdd\nVlTe/Cx4FYVV48GKVJqjkQtFHgGQ26dwefV4LYwzLKDKbMH8hehv3MRK4uJ/\nphzax1zYi+QWttaBa5xNzNoaRcqoinfjhkpfeTXTkfucPJiCRkWkRL5fOJ3C\njNglDi9PtHhHOOKFyYX+boWdtpC08QWiceLrmxvFk4dA0+n+DM+KTUknmTCr\nU+x4\r\n=U/LP\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"24ef2e0e08597f1e985dbf25837fa48b29cc513f","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-05T11:41:40.571Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.100","@balena/jellyfish-logger":"^3.0.161","@balena/jellyfish-metrics":"^1.0.428","@balena/jellyfish-environment":"^5.2.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.3","deplint":"^1.1.3","ts-jest":"^27.1.0","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.5.2","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^2.0.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.42-renovate-internal-patch-24ef2e0e08597f1e985dbf25837fa48b29cc513f_1638704709326_0.37186865895852383","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.42":{"name":"@balena/jellyfish-core","version":"8.1.42","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.42","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"285a4e3b5afa9f67db9f4b7d7120599c9cfb9317","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.42.tgz","fileCount":212,"integrity":"sha512-bzc3Ak+8Gf34wtUlvT31qvBzt/3fZRf0f6ciu8Ov21TjnMTsJOYsY8ieXr0rHf6ZVM5Xn8SJYPzC/Nj01yT0vg==","signatures":[{"sig":"MEQCIGNRuQAWaDa5FVqKoCaCCZxdGHWbeCRmipJZoKs5oaizAiBnIBG/t+N7PqK1U2O/4VoH6kJCt8CcxwQg4FNejITNRQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":780006,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhrKhNCRA9TVsSAnZWagAAGd4QAJPJjORPUzVgUSyo2ig5\n+HUsRxptVUkVlMt1gi0wO/HDCUQtexvrWpBhwUqEDx5NeHxUfEp8e2kB3cwZ\nkYHF2YrVMkuO6iuS1teEZj0jg+q1mmOp8PcGQc+4phOUNkM3hb+wOWek3RZV\nQNLFv9oVDxWFGrtAdSUdPry8jzfQmfc4WKmhQ19o7QyygXTVuydkKW0sEUu7\nTkeYYtrwG15x9xU/C2pGvnJMGSiUAH0MAvW+qo0YTpS5j00VoE88RyRXEOPm\nJwPcrzfHZWBvLvttEqB5dnlUISqJD6S2/GgLrprJwIyUx3vOKiFZOZa9v8GR\n4x89P+52DpfU2N1Ty0HUbfZ0JpqCTlKr75h6C7cIC3CIICAFnboXEZIEhCg3\nCOe41zTLC0/yBQx7gO5Wluwx6lUBter9VjEjU2AaynBEPFRdwFyi+eXDk0kj\nYO8jPM6SsLwvwRDmUNCoKaQjejftzSmJW7j1pAU3YEXujF0Mtnik7ImPDnjw\nJgN3ox2Klvuw13L6Wv9H0+7e2tvmG1BV7hVbynJcC2qVlApeoAFn1bjBCCWH\nlmeTL0eAcWqEt4+gp9q3v3hqEWKH2e9fcY4PXE6f40+vI0f+qTw73KQ4cwmn\nLvGgUN7Lrm+ILZcM0aw161tUn/gI6+5DnweejojCQBYJJ+ZxhJrFXim7grbU\nFhQX\r\n=CcNY\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c10382845b6b12f72c880f18ab41b6d504b73233","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-05T11:51:04.902Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.100","@balena/jellyfish-logger":"^3.0.161","@balena/jellyfish-metrics":"^1.0.428","@balena/jellyfish-environment":"^5.2.2"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.3","deplint":"^1.1.3","ts-jest":"^27.1.0","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.5.2","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^2.0.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.42_1638705229085_0.762398849064239","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.42-renovate-major-json-schema-org-7926134f3515f87caa44e0ea78160f0817f5d637":{"name":"@balena/jellyfish-core","version":"8.1.42-renovate-major-json-schema-org-7926134f3515f87caa44e0ea78160f0817f5d637","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.42-renovate-major-json-schema-org-7926134f3515f87caa44e0ea78160f0817f5d637","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"7f177823d51a07363ae733a0b69dca25c5b76cf6","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.42-renovate-major-json-schema-org-7926134f3515f87caa44e0ea78160f0817f5d637.tgz","fileCount":212,"integrity":"sha512-vx13zlEcn2vHNYNIEYgSKWT1qEpIT/jG1VUEhF2ZqzfwYGLPiP/La9dv00PxngxY+RpkFZlw64atlxMHdC7L8g==","signatures":[{"sig":"MEUCIGyMcmWoZan/Dd5kmT9LZmx8i5KwwI4vvQB3X0dOv69hAiEA5Sz5XgctdyvCc69l65tcoDuhDLfLKHVDGqGfgowTABw=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":780149,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhrKjWCRA9TVsSAnZWagAAlbMP/07gruQhLlWvTpYyEadK\nJPQv2bnbohPb2Fv68l5t5RyF/7b/J6O9zpYxaqCGq4lb3QhW6yIvWLfRWq/L\nUqW6AAwjr/dYlg55vYCOVXZHzzc5tOB6JoyefZBVuyAsXXjwLGGoWLW0CXKd\nWDYnzgxN+iy/nOxyqqKqw8ulb+HSwUDeZLHrSJnkHtumxT0YgfDBkMDQcP37\nNUhPKg5VwgIIO/Jdu6hj0u7EMu8+FBoxpBjYl2Z8fbaaFibloWo/pSYWYJU4\nuJWEuouna5N1kps+FUUopsb9MTMKozpCiKiXa9ut1+QD4q645JcdO2Xyg/QD\nJeuCGQMO/pR1OGVccSFIUE5A/oRWQTFlG69idg86AGV/OblfnEeHacLcKAHu\nv8D9sxqTILQc85z5Dt44/DxpsdW103iaf0LjgqH6WvHXQ1S8XLvFAUxIrJd5\n7kwFLhw7vTNKDwY3TomxyEtkjV5Rwnm/gFum+siDf8rzk6N2okgfx5teEiEZ\neqKGjItZWEejKfTJhbjE/a4+Vz8l2ddeGMPDJzfOdVQpv/AlsWebuPkz/fLm\nthxkWSP5tMxThl6ePpR/Rp3SpwjpYSxBA5goZlmotQQwuQobRpNbjYq4ieiM\nr9DFMXRG1bBnwWML24AhKyzXBWbm+GQuVtbK9nZ+EP8YxlPkzb9nPR2I/zOT\nR3Pf\r\n=WMRG\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7926134f3515f87caa44e0ea78160f0817f5d637","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-05T11:52:07.522Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.100","@balena/jellyfish-logger":"^3.0.161","@balena/jellyfish-metrics":"^1.0.428","@balena/jellyfish-environment":"^5.2.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.3","deplint":"^1.1.3","ts-jest":"^27.1.0","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.5.2","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^2.0.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.42-renovate-major-json-schema-org-7926134f3515f87caa44e0ea78160f0817f5d637_1638705366560_0.05179101147495424","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.43-renovate-major-json-schema-org-af9aeb88a862c659e5185273b900b9cd5e4b8cc1":{"name":"@balena/jellyfish-core","version":"8.1.43-renovate-major-json-schema-org-af9aeb88a862c659e5185273b900b9cd5e4b8cc1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.43-renovate-major-json-schema-org-af9aeb88a862c659e5185273b900b9cd5e4b8cc1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ae86870815fe922b43c5666a627f2edf0aa9841d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.43-renovate-major-json-schema-org-af9aeb88a862c659e5185273b900b9cd5e4b8cc1.tgz","fileCount":212,"integrity":"sha512-8FbGmJp+VqtSszmyYOQrxUvul++/YOFYEafxymaHbFLXV7yySCqFhLsV2LrpLNtnnOPH1uibFwtr1oHOek0G0Q==","signatures":[{"sig":"MEUCIQDq9b/zDccZeLqpApf8MQex3AdU21XeciJA0k0w/XrnGQIgcKjLh6dJ+ecxj3O3xHl2HgTsRqJyj1ijiwHxX0HlL00=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":780174,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhrKn9CRA9TVsSAnZWagAArmUP/ievUQgY+mUKSjfM+8VW\nAyR3yzdjszjhBjYY3fpCqE0lxpldBOPEYURCbLfTylIPx8FRXfGm2Q34Ttbx\n37lgOH5f0Pz4BIJ1aFj1i+2oSnqfl5D43qev7Ay4o/j8gOT/egzmtvGc50mQ\nGq4KMNw9CgF8Pyoze18mI3xdVc3nS6cGVzhLauY6p2olI48entYV0f4ixeiP\nLOUX6fWsvvExsH3Ajw0Z0haNAhtnwkRmWOe/NtO2CM1ZVcRhiBvMDOiZzY5Q\n2psvu/YYsP+TbV8VJcRCTKthkSDP40O9GjNvGFmu849MJghIrJG1oTke4dNC\nyIUJZUyXGc/w9WIk8+pU5aQJIae7b59eFFi0Lo5gnPVIk353EdsY3vp7BzXi\nirrFP2qKVJ2x0qg08WJ0OhJfekkr1gZsS6yodwHnFigv66bz+WUqGYQ/KorZ\nNV4ktpzw6pATJ+EsN0UizfXHk5iuGdtjLvEQXH6+Qzsc26Syv6ZeScmZ1/J9\njN07upi7RABlBPsLscC1qy9ToTZp6E76u0SMTDqTgCMCj2oKI28NBMqMYATN\nBoXvIpMhrO3qJGd3Rv7uZUnKL5+KGioVQoiEd7iGAvWhwASYYUVkODZKnqXO\n2T5hDPZdwc08JCF6b2iZ00ms0ZNSSOtd5CKbvBz3Ss/X4I+tC4nua3JsHi2J\natZb\r\n=x8Kx\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"af9aeb88a862c659e5185273b900b9cd5e4b8cc1","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-05T11:56:58.499Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.100","@balena/jellyfish-logger":"^3.0.161","@balena/jellyfish-metrics":"^1.0.428","@balena/jellyfish-environment":"^5.2.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.3","deplint":"^1.1.3","ts-jest":"^27.1.0","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.5.2","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^2.0.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.43-renovate-major-json-schema-org-af9aeb88a862c659e5185273b900b9cd5e4b8cc1_1638705660812_0.5972187901207175","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.43-renovate-internal-patch-08854fca928356acc4a264fdcfcf665f0717bd6b":{"name":"@balena/jellyfish-core","version":"8.1.43-renovate-internal-patch-08854fca928356acc4a264fdcfcf665f0717bd6b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.43-renovate-internal-patch-08854fca928356acc4a264fdcfcf665f0717bd6b","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"86f215d0d2f0cc63e13d1b4e3985e0ffec7ef4d5","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.43-renovate-internal-patch-08854fca928356acc4a264fdcfcf665f0717bd6b.tgz","fileCount":212,"integrity":"sha512-4LZR9oEKlmnMG9qxvR1Gs9cp/j42SZOTwO42bPMhXehRfvb1sz94EUcTeseyD+loy4+oaqOoHlsfYrGI3W2wOg==","signatures":[{"sig":"MEQCIFKMDu03xNz+RV6DTpRRV97sbGJkQTtYWd5xj4MugrGCAiAUcdrUgzijUg8M1sSB6Na6oHF9h4NHjauj4B+h1rMzxw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":780142,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhrO/YCRA9TVsSAnZWagAA9S8P/0jopYeG5FycyCidMSny\nb/T2ErtfYVZo6ieLrQhFok9TvQUgP8NRYBJ1n59hTtbMgCeXbFbMC5BhCjcM\n4i8Il4biR/a+h29+puNIL1+rnKtC1g4sVWCA+Q75bqhjOfbCY+gFIYQB80K4\nab5r5+ItEmX4kc6XP6HULd2tTeD4qrjslNMTUfxwlnkfN4D0xNllZ/0ouLnr\ngXsOMCZX9PpsjyCxjsfW0jinS+9wbLjmwU3cVDgwgi7Sypt3uY5HdxbGdnzv\n+ACvDYg2I2R2GORXPs4xmF/mLT5aicse5Kwoz2MbyCe4ajuaBzkNxlYckT8G\nsydNcvvM0iBMnIXFIOxBUvCO6DpODqkNj22Uzl4dDJ3bL1NT4i95lpMTzEBs\niAxRs8FXPmA33uPCJ8mydgIi61DDRm5Y4LSfEHxvE4zGU5OkpPRsOsGq4L9c\n3hQ5orG6Wr6n5nY33y+Nuk0ZnX2f+59dj08zHCFOxKuYBZ7+W09XCpJ++X+4\nTNRKNDTUkXEew3+UPq7arl89QRXCF9HoiRPNl29OPwVzTlqlrucgxYGTd/eF\nJuVceKWkZpx2l4cQtK0HWEHu9xG5tuf5BUY6DtgodljpFCdPz7mr9CFGAsi5\nU4EVhRUR7IKQvHDBTjeBDvhrkVAgrAY9aechiMLJv6xM0Kq2jYuQ6kJmyBhD\nc9RV\r\n=Pg0D\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"08854fca928356acc4a264fdcfcf665f0717bd6b","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-05T16:56:29.023Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.101","@balena/jellyfish-logger":"^3.0.161","@balena/jellyfish-metrics":"^1.0.429","@balena/jellyfish-environment":"^5.2.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.3","deplint":"^1.1.3","ts-jest":"^27.1.0","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.5.2","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^2.0.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.43-renovate-internal-patch-08854fca928356acc4a264fdcfcf665f0717bd6b_1638723544783_0.07515726792949895","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.43":{"name":"@balena/jellyfish-core","version":"8.1.43","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.43","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9367d9870669909d3738abff82758a5241721d84","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.43.tgz","fileCount":212,"integrity":"sha512-8POhydOCNHuepc3pqQwKxs9t1DAc/KtgmRmrW7HldxQUQ3ZlhSdEtQ8SmuZvuM3EUgy/uDqByeB8mNnoOVfpqQ==","signatures":[{"sig":"MEUCIQC2Mky1e+iSQcGqD5otqcaBC9hq6d1ZtRqokQmx0g+32wIgGlO7NsBTDdaoDy86jB74rXmwJJeTsvfSFSLYQmtvSOw=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":780077,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhrPFqCRA9TVsSAnZWagAA2+EQAIbML/gjGks2UkHqqs73\nVk2lhZZ4Vl5My5h+N7Uv31kd0gB8EN9qpjwn6jbN7H48TcLpl1an0wE5jvil\nhJ+Yxy0LLpZrHKCGBu8Xy1g8DSZj0McRiQvPQxXRrsB7nBpj8ReWF4hzqZJh\nqWZUBiQ3TP7KnI0+rGvzijvGpYY+kwO2I98my0dHbvc1jUsm8rROzUip7hYm\nLpYbWxalwuh6dlhJevJn86XMzZqaVBs6xSJJg5TOLVxw0ljGTzJntkU2i/M1\nU3BEizi64AIKPJlrWit6mZmsIz/MSeL6jKmYIvUPVS+kQcP7YEORfC3vQSm6\nC6hNoiTWJ+lFfwUvGZqyrTTfPMFNDjAAzKI2bJ62xmNqR8Mk25zXCtUhCoEz\n7+BYbl5HgQ1izIrlTG0TGu53f4RZVG0evQbyBe3HWUsCw9bqhkODlOjg7r97\nKZpQhgwN4ak2g4NCs8q7YoHC3EZt9XaT33Emf+Siu93tK1OYXafcDb4+PYKg\nzz+EBoUbsm1F00gj23w83zuFs2UHJwUwjP/IkoxPZqnVAGxou6K5sEz1JNNW\nag2quwc89aOWsarCIHif2Lt8R0RMiNTKNPURyJkcO211a/BeUCS7LmWCNac9\n4eql958OZPLVcsol8UJ8jaZEM3uKZUxy0Yl0hNd5zYSykOa9npZJnGzXXWch\n9NqQ\r\n=9LrW\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a37574efa5738f9b4623d76a63d8d3c0f83cf48a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-05T17:03:35.155Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.101","@balena/jellyfish-logger":"^3.0.161","@balena/jellyfish-metrics":"^1.0.429","@balena/jellyfish-environment":"^5.2.2"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.3","deplint":"^1.1.3","ts-jest":"^27.1.0","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.5.2","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^2.0.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.43_1638723945823_0.49978504868664153","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.44-renovate-major-json-schema-org-98a32a2e8d1db941c3dde5a63fcb8acdaf0aba47":{"name":"@balena/jellyfish-core","version":"8.1.44-renovate-major-json-schema-org-98a32a2e8d1db941c3dde5a63fcb8acdaf0aba47","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.44-renovate-major-json-schema-org-98a32a2e8d1db941c3dde5a63fcb8acdaf0aba47","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3c2519761fab7a5ff6485076c3290f6c4b5cd941","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.44-renovate-major-json-schema-org-98a32a2e8d1db941c3dde5a63fcb8acdaf0aba47.tgz","fileCount":212,"integrity":"sha512-l70Uhx/Tl6pArH131LKsFzQULgnJWN5/jaZbJgk+irPgAi3v3wAve9Wzw2GUX+rVQvtPhNYWONv24H/ifQWNLg==","signatures":[{"sig":"MEUCIQCvZXRytyEZvP38gy+2rONIsBkbDckPYzxqOsF/CIbnQwIgIhvLzuPkS7F063oGBpDEpXSgv1HlC/mvm9fkIJT+0Yk=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":780245,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhrPKTCRA9TVsSAnZWagAAolkP/3nD6CtXVnwZnXoUaPEP\n8t9jCNok0hFVBnVPnQVyFk2NR0ZDDpHMyKksoAd84QU9gQGuwVo5vl0/qJOv\n0kM6GzM8AXIcvqWf/QBPUfWK0krN/M58tsUfESMxL/OebFHA0JJbSzhlTl2k\nyN/KwYQzjJytWpWCb1X/TY5CDfhwFSKsXiYLUSzzjN1CfQPLREmXBgleEDwC\no+vecnpCH6TjmplibauQl3ciABqrhIZtXMLVkH8SjXgAt0axJ/Qe+tPiUiUG\ndZLmYQTOHl11RGmGz1BM1YZiDwc4MWHngLJKXyxfLIdXHGSuAXS5p+a86QFp\nJnFaaPDPBgdRptDki4zwUbRSudeLo4J/kl8ITgIcGNNI9TciPmyEMAcMgKXB\n8fVhtWWZTXrGGlEzQBc/X7AP967vmaTW5QdVn/HnuhTSXZcwtEcZVTzBdr5m\n7n0P79CZWHbKowExjiv4utI7BTmFdJNTlSAk3+ndUn5i6JoM08P37dusEZEt\nEbPadkOT3PE7iBpIRH+qG3VLXikQEa0RDE6RxRclgwp7/3w64/IyxuLPJ+k0\n+yQNKDaVST3Ke8F+lhhoaSlg8KCmtMSl8bpfdNDDybDGe3uwVne8X6XVYTm9\nQ/5NVRVLUKKaz4njnY16WlLYKHM6TNsnqUm7tx28Wz9ihygj2gyP8vf8mDZ3\nFffx\r\n=hD8R\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"98a32a2e8d1db941c3dde5a63fcb8acdaf0aba47","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-05T17:08:02.304Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.101","@balena/jellyfish-logger":"^3.0.161","@balena/jellyfish-metrics":"^1.0.429","@balena/jellyfish-environment":"^5.2.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.3","deplint":"^1.1.3","ts-jest":"^27.1.0","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.5.2","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^2.0.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.44-renovate-major-json-schema-org-98a32a2e8d1db941c3dde5a63fcb8acdaf0aba47_1638724243417_0.12429887041807963","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.44-renovate-internal-patch-f1c16688c27aa54f48358648435f745d808e6ad6":{"name":"@balena/jellyfish-core","version":"8.1.44-renovate-internal-patch-f1c16688c27aa54f48358648435f745d808e6ad6","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.44-renovate-internal-patch-f1c16688c27aa54f48358648435f745d808e6ad6","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a82d1bec3c4ab2a5694f7645b0c5bbec2c8110e9","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.44-renovate-internal-patch-f1c16688c27aa54f48358648435f745d808e6ad6.tgz","fileCount":212,"integrity":"sha512-e87HOTei3w3m9JLroqc5eaATlbST1NjynFa6dhwe1En0bs9F+r8YHEMaQGGUWHckOwkGLFOYLzjj5pojz+PX1Q==","signatures":[{"sig":"MEQCIBvT2jSaHT9cyy9EWQNEcfRR2ER2sH3BKWaiHbml30iVAiBgMtI+6fKN1S+CI5x9qrsKjIcIQoChyfGkbhcEZ7Ep3Q==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":780213,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhrVqtCRA9TVsSAnZWagAA3HoP/i0hgm3EoUq5fUyb7zeG\ndmffI0bgdS0SUJQ673DlufpT0i9/hPHD9YnluZ1vUqk1xuHZ1j4jMN9VmwoB\n89wYBCMWyv5hPNQ9szqB+0A78C7+TXOmk1MD1ywyq2IiW223a0d8/YL5gs+G\n6GJYVrE1GypZLori0Atf3zsItmnsuzpr8R2hWVhw2d8msgTiLHP4jkj8rvOE\nhDMx5sPhio2nlDZMDZP2zojeHH3+Oy3Tdxf9HypD1bZDpTU6sNQbtbQPtxjL\n0MTg9IKPLFuX4sB/8TDKe0RB1cvffJwxA+jyiGMywJX+6HyJAFDdCRUkp9DX\ntpT4a7WDI4v7UyQVXIa8kJaawVZxGU7gUgL6C1fNZH4Rv56ZgLpsQ4U8gvXI\nfd6yGgyhdydQXVkUQFlg2U7gfJ5fQMelbrYpIax5eBnMEpDNE4sFhFlyiSP7\nrVP8FArkyvE+Z4Wfkdq9AZOLHecbxLfx8vuIYMQEWYPOysQN/Ewfbix0ApNK\n1hSlDEABD4umwv+K+az6IRmOfd4wIFFR+ODGTIiifZXqX7UVxMnKKM589D/W\nO/1BQCbLa7EGJMHUEcd+6KH+uKI9iaXPwEPLcQJoATOT7IOTwk0qYfDL9O+O\np6mGiRCCRDH7AtOhQTx+83X5Cf1GU+WfTRuS93q6cRZTS529itwxwzXZoIeX\nk+RO\r\n=qIBv\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f1c16688c27aa54f48358648435f745d808e6ad6","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-06T00:31:27.105Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.101","@balena/jellyfish-logger":"^3.0.162","@balena/jellyfish-metrics":"^1.0.430","@balena/jellyfish-environment":"^5.2.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.3","deplint":"^1.1.3","ts-jest":"^27.1.0","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.5.2","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^2.0.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.44-renovate-internal-patch-f1c16688c27aa54f48358648435f745d808e6ad6_1638750893412_0.7687737615669503","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.44":{"name":"@balena/jellyfish-core","version":"8.1.44","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.44","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8cd32ac60ac9e1c3ef93e753fe500fa1c1e1edd3","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.44.tgz","fileCount":212,"integrity":"sha512-EFYagDHPqgq/HGiSFvNFJOokfvEuL5k5LmaDwI+L8ra7qNyrVcGL1IUIrKBIueKLr8ntHtTE4gdzK9KpjEGToQ==","signatures":[{"sig":"MEUCIQCRRTKNqFVSa00KRRcKQ6w90JC96VJDIKAAdynzOn5A6gIgE/g5rtVZsiunN4yKTEKgDmnd9a+HVfc+d9oLL4wVq88=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":780148,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhrVw3CRA9TVsSAnZWagAAbP4P/ixvpjCFjzlg/6gJOk0F\nf4yNBycdCELLuPKzFRdBBX/zCZYy/bOroZiyNq45GJPHM5W0BzZFEszE52Ga\njYox20WipahJoXxu7F3usCvt4scTsRTVf6dxaziXuZLmG9f0TMSWaBmoWbtX\niC/xB6khqVJtnwsuFADRZImpLkURv5PnDBngYkqiyF/GwWEkrV1FREk1/ZBN\nHtWjOxPhkRexHNrbc6Uspxh5j902zrOSzUnTMcp8YmD6JAfbLCbxZq5HpD6L\nFhFrQ+Ezubv+r2rGNOAEb8giqGZzs01XOa/QigX4kQlKG5IcR6rlqaeHi/Jj\nCLXsfYEXK8WhtusO6+XIssPq+v0kTBps6nO/kkA2AE2IGVegaBEeRzQhfHPc\n9r2xxFTXS3/dBEYMeTP8gdRRdjqnrIC3oiQVBVj7WFloAL7jQ9CCYba5TaLn\nJqvRQRLBU7JcK5LGwMo9dukkvaxnJubbRl4KtcyLkzCRxsbHBshWoyaxrKi7\nDxT6+vFaV0QsCnpwdujlMf+PicfFsJkDt1P+akg6QkfMnldaZMmSjYbQWpBj\nNEOwXV6Der2jQ4tPf/mOL23ATyon0k1PKbguiwiZm0bSZMAX59wYkn4AkYeo\n4ucchfCPkzDa9s+Axm8G4p84wPtjtTeiv5qbivu+vSGksYrUzKOnTZa5cinw\nSPvO\r\n=2CY5\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f27297b8aebde740b6e7d31acd8da4352023d7c4","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-06T00:39:17.224Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.101","@balena/jellyfish-logger":"^3.0.162","@balena/jellyfish-metrics":"^1.0.430","@balena/jellyfish-environment":"^5.2.2"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.3","deplint":"^1.1.3","ts-jest":"^27.1.0","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.5.2","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^2.0.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.44_1638751287159_0.5195241328892768","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.45-renovate-major-json-schema-org-50895845d49c9a96f389c4145fd33bf007d1f7cf":{"name":"@balena/jellyfish-core","version":"8.1.45-renovate-major-json-schema-org-50895845d49c9a96f389c4145fd33bf007d1f7cf","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.45-renovate-major-json-schema-org-50895845d49c9a96f389c4145fd33bf007d1f7cf","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6128f66953d3164f78c941ca28ec789c083cf4f4","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.45-renovate-major-json-schema-org-50895845d49c9a96f389c4145fd33bf007d1f7cf.tgz","fileCount":212,"integrity":"sha512-curznVf12Q7fcYR8DZLHgDMft+VSHppMVOvnNLHWUscAHd6vxGuEF13HBGVkWGWcDFiv+Df9z9KMOgOmRN2VIg==","signatures":[{"sig":"MEYCIQDizk+aSQjg6oXJIqvjCVigC0GCwvqNoltnlyKLfUuZywIhAModsUsE6Q/B4R/dQpte5gJqRXBhITT8r335lKkm847N","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":780316,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhrVzuCRA9TVsSAnZWagAAvtgP/ia3XwlQvjQalMgAyNY6\n4Y03kjPzE6VUlnyLvtolsgPP5FPQU9B7cb97rB1BHlcOubhnOUPjrpHlwvlB\n2ZbRxSWa1eE2/B16Jam4mbmHnWRnCflYf0JGmIKLB6RSBVykngzKvZMJs4uK\nqAnJKtQCIeYiB2szNueY6g8+KXJMGnr7IuvhS2OjZAUYGzxx+vkyQxi3AzMo\nVoP8tTJ+RcIvQ9VA/2/JrShmEaLy+Fe0r7MhHiZvpC7mdc7ZLN8Hontw/6OW\nVEu3LPP7xh2I+/On4RTmnKQJ+y2FmLSW3mGFCZExOnzP6OQ1Fyz6vpeTaSQ4\nm9XzDXrnpZEQeqsTjZ1MKOBg0sYwOR8ERexiFJDwDsF0VWYCrVNzcYYDADFK\nadQ3vNfG0BUsSmouGlefJJffHZsmfI8AXq6JW3mcXXsjhs0y/QsIYJXB2r7e\nGG4NTfei7XXros+MUuvQKWUXlLbh/F+4ZykfKyTgbb94erw8ipsngO0tUepW\nHgR9xiURRZYo1XwZNuzU8CntpVLPQc3ty1GRJWaCJ86ZSNQ+YZ6gOw0DrppJ\n+LTAnssWg1uhX+TMr0h2byXBLapV0zO0U6s3hmlkALceKHwlJtqMF9do6CVx\nox1pIGMV1HhhQFyvPYzXzf+5jsX3oyyiEg5LVUlA5DrX23CBMAiS/uhSJ+6R\nvOu/\r\n=Eg0e\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"50895845d49c9a96f389c4145fd33bf007d1f7cf","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-06T00:40:48.145Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.101","@balena/jellyfish-logger":"^3.0.162","@balena/jellyfish-metrics":"^1.0.430","@balena/jellyfish-environment":"^5.2.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.3","deplint":"^1.1.3","ts-jest":"^27.1.0","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.5.2","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^2.0.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.45-renovate-major-json-schema-org-50895845d49c9a96f389c4145fd33bf007d1f7cf_1638751470161_0.15490342675299495","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.45-renovate-balena-jellyfish-environment-6-x-405be500a48497cb8d2884e6dc9d0a7da600bacb":{"name":"@balena/jellyfish-core","version":"8.1.45-renovate-balena-jellyfish-environment-6-x-405be500a48497cb8d2884e6dc9d0a7da600bacb","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.45-renovate-balena-jellyfish-environment-6-x-405be500a48497cb8d2884e6dc9d0a7da600bacb","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3639dfa5d932429cd5e81b2070133e9b9ce582d7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.45-renovate-balena-jellyfish-environment-6-x-405be500a48497cb8d2884e6dc9d0a7da600bacb.tgz","fileCount":212,"integrity":"sha512-v61yDmIkn5xakTEtDzm09FOGy+WzoFkotHg+qhbANmOZOtufV+w8ExoKgD9U7Sq4tDUwfA/nhF6XfplhONhwcg==","signatures":[{"sig":"MEYCIQC7TdeGtft41vXeRvEeSUBMc5dcB2VAFNNvwewe9QZkUQIhAKFSHefWJHzH0AEqU66DUOdC//fXKA4rrNFwSiKYBy8o","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":780334,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhsVwLCRA9TVsSAnZWagAAEBgP/07lZWbGcC75+kDuErRx\nZVWgRy856v31PeXykvedKuWin4vXtSJ2dwm0AEJx/NsPT13TVHsVXIz4pXYA\nlR3vjiSHtYVxBa40Xsa6S3JBaD47MYZz2w2kJ2qiNvXAbMxcqdyQsQipYkl4\nZw5tGTk9c9Mw6pU1VR9BCjv0TZGBg7OzSRDyWYpCrjkH92pA6kfjNqn+1QrR\nQjk8ZHc5Mtp4djrujijNHm3ldS7R6WkS9nBLZh1Xn3Mv/yfkcVsYB6w50cts\n3jbO4TgtBCQQgBRSSgvWu0/z/B0T/OeR/zpVeiQ24F+d7gSR3IMAK0/lGpGw\nMDMQaRaCH71vuY5uCj76mRxWRUKAby+wDJYxMTd60B+QD5/dN0axs1uyLdz5\nS5Ju/9cwGRdszwWceqpM2h8aUXXI8X62/2WMhI0SjhShoxDIbQMSj3tlAS6r\n0+kIyKksEaTMZD4xFLc1y3rAhuDIsOwUm0ANmzF7C6zFqcyPKdy0Y6pOg+Eu\nNRkOdBxl+qFA4L/emPNngFXQA9FtsJSJ5uJZONbJk+FUnmJHRVeGd/W6BC9n\ngdK8Na5qmiBPbWrqChnu/ocSRJMO9EUGDg30ginIIQ/Vf/OZdbbv+A4BiLP/\nMi8V+NHMGssgxWjQyEFakgiKOZzw1pR7or74ZSJqn0xoFMQMGeF1gBn2sKLn\ngTgu\r\n=Hnrb\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"405be500a48497cb8d2884e6dc9d0a7da600bacb","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-09T01:26:20.347Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.101","@balena/jellyfish-logger":"^3.0.162","@balena/jellyfish-metrics":"^1.0.430","@balena/jellyfish-environment":"^6.0.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.3","deplint":"^1.1.3","ts-jest":"^27.1.0","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.5.2","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^2.0.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.45-renovate-balena-jellyfish-environment-6-x-405be500a48497cb8d2884e6dc9d0a7da600bacb_1639013387483_0.2935573198890895","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.45-renovate-internal-patch-2c51ba5ee7cbe7f9260e88c54ea38171e5250bb6":{"name":"@balena/jellyfish-core","version":"8.1.45-renovate-internal-patch-2c51ba5ee7cbe7f9260e88c54ea38171e5250bb6","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.45-renovate-internal-patch-2c51ba5ee7cbe7f9260e88c54ea38171e5250bb6","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f7f71bbcb439571bbc0e05fab7f42325c8dcbd9c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.45-renovate-internal-patch-2c51ba5ee7cbe7f9260e88c54ea38171e5250bb6.tgz","fileCount":212,"integrity":"sha512-vJfkydPGrzHJHo5JUJYZwNZDWJzCp0eDU3H7TIdrbYYCNly8nrTQxEWRuYQw7SkMVr0GePOIJStSGw5eT/qJJA==","signatures":[{"sig":"MEUCIQCB4O66MtJCBDuns4IWx0nfZ0+EQqCHuEyhgpjzmu8XuAIgXJxALBnSn0hPUYop3T2SP5hq82KiZYZ/tqoRU/E+UBI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":780317,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhsltdCRA9TVsSAnZWagAA3PcP/jQ9faVNom63bq8gy55r\nC+txoB1kUKu0OVvLZTObn+FdEN25yPmAOkEA55pF/Xu3iYmhLb6C2iuR+9n8\nUFCvesvKKEe4F2Egr/30dxaWMAYhn2eQowhbG/zYuko7LxjSZoNBQbiSMFZ3\nKJ5PLStTRHQ9fBMulWAGrYX22lVgn8B93hzpQa/WwsnvJVnSxbqSyx5dIT0z\nXq3vXQyEckSlE1TneM8GCYiLbQSr500R9HXAWAnHst5POMWUJSen0T24wUjA\nBoacn0JkQ7PFSNxdS8jK0xJiaPg4PX3v+Icc7VQCFyBzMXOTVphjYgG7/ks9\nfOTGIyV3K+LvsVdxvgDMWP1ip25yGSE0d87AfSCd+LvgnnnnMA6/vkhGDoVV\nLSL2t/4tC4v0f4Z7VRWF2YSPmjljO2pm6CLsj2zC8bKkXzmp8g7HNc+MuNqU\n1UbAUjXkd6opdj11iv4dCwsDI8MiwHcx7Tex2/l35Ap0C7CXCP23LGU/ey35\nvpen/owY8qdA4XI1fXy51rq12ew20rzHGmKDjIH3es4K1z2Ur9HBylzAb418\nBEwea4OM+KHvB3ycUpfqE9LSpAjL9Vzzq7SlFXVi155uzXoOJZlyPM1NvNKB\nZA5S2IxiIZx9vaKRjQ5FU/2Rv1nT75jUCku7ktayZWUEhqLmwSX+waa1hnk3\nLDfv\r\n=OuBO\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2c51ba5ee7cbe7f9260e88c54ea38171e5250bb6","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-09T19:36:12.958Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.101","@balena/jellyfish-logger":"^3.0.163","@balena/jellyfish-metrics":"^1.0.430","@balena/jellyfish-environment":"^5.2.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.3","deplint":"^1.1.3","ts-jest":"^27.1.0","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.5.2","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^2.0.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.45-renovate-internal-patch-2c51ba5ee7cbe7f9260e88c54ea38171e5250bb6_1639078749509_0.1063597735123536","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.45-renovate-typescript-4-x-a666a75481bef828beb569ba83c01cc17d7842c8":{"name":"@balena/jellyfish-core","version":"8.1.45-renovate-typescript-4-x-a666a75481bef828beb569ba83c01cc17d7842c8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.45-renovate-typescript-4-x-a666a75481bef828beb569ba83c01cc17d7842c8","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"fbe6e35d15422576a60a57f20758023c4359c009","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.45-renovate-typescript-4-x-a666a75481bef828beb569ba83c01cc17d7842c8.tgz","fileCount":212,"integrity":"sha512-YkD1ftQRvBiSgxUXZOFozeRZn/E2GgpfdFfsAtZ4gsoZuAIOb+c4QGoc3iUAn5m+5Bn/Q7DYiiS3Y+NsnJPlqA==","signatures":[{"sig":"MEUCIHSzb/xbfraEdiajJI9Iz4cvoWztaNnfZ/Z2wWS15RWrAiEAyPORs5JbNQ6qdOaeMLr8NhQiYJMpEzW/1pBY1GUKqao=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":780301,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhsp4rCRA9TVsSAnZWagAACjIP/imitrExfpNX1MENUH3t\nHh7KncGIttUVrmjQQzOy2+HGdF/RkLaiF/2xI4AwqD5FVHzYfY6zHREYddoR\nDTORLQHjP6CV1JoGrW7qaqTLPHZmcaCC7PKXs1A41mnnTgJgEwe5fkwsTAuP\nJyD7OTA5OiS7dyK7MJUmbJ0hGBM3NR25CbzRef3XJXytKvkePTdg5Cyi3Ecf\nssdVldpqFueEFF3Ff5vNh59wbvavHzjx7sT5Ku9HO7mZ1OqZ9OghH49oGaL3\nzeUvO2KEgxTGiMc/aCd9UvyUUT7oRJSh1YZBzU7yJfy09xnr8rgf7nnQyrna\nqjuPGlbXfr3hLojebIUwJxGfSsSMxoGEZgaGdSctiEhlqgk8SDJ4rOHqd+Ge\nTp5XzWvgXX1qsvQZC5crTSFecpuqCf/KBSgVuoTaXfynWm9k6KyM1VWqYHZM\ny/tLXYzJ0YzzTW8RqXpaLH+7cOsTz0dDyBnMztsJ4Ca1CwlEzAEtIMdtdcbb\naYSHBTUwR7RhrBCCoi0gQyYXcmi7qKO7x3JZZWe1qdfSPxZv03zHPZ61ZzQ8\nFGV3o8qWxWkXW78+sPKmjfm6Zix143f3S9o6RMGDeWXaUsP9NvQLDCx5Radz\njztQITZPWHczhask4tS4jy+Y+6DeYebsP1fHnRP13uErmMNR0PDEKEu1xN9D\nf2Q2\r\n=7v4R\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a666a75481bef828beb569ba83c01cc17d7842c8","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-10T00:21:11.321Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.101","@balena/jellyfish-logger":"^3.0.162","@balena/jellyfish-metrics":"^1.0.430","@balena/jellyfish-environment":"^5.2.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.3","deplint":"^1.1.3","ts-jest":"^27.1.0","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.5.3","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^2.0.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.45-renovate-typescript-4-x-a666a75481bef828beb569ba83c01cc17d7842c8_1639095851189_0.7395169082862663","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.45":{"name":"@balena/jellyfish-core","version":"8.1.45","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.45","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d26fae3528e33f765c8de78d368ae12b4ea6f2ff","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.45.tgz","fileCount":212,"integrity":"sha512-eKEjYeFykoDoPkqHZ4bF3Z5p4R94TaLUBj5O1MaOE5a5JxQjIJarsVOvpDzVpBltJ/3/WhV8v5ZSmGtbshxUuw==","signatures":[{"sig":"MEQCIBXZUWM9KbaZKxmnhnQKOgZAikFo9W2EDh5FbhhAFd2XAiBM5/lQcYgf4Sup538lYlbP8xfKQxbjDI/7HXvQ7was3A==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":780236,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhsrgNCRA9TVsSAnZWagAAM8gP/joNtapsrJEbRm3zrxQV\n5yccbNqcaGq37SLdz39RL27sQWlFUW22NrdU4ai7TtfinOXC99usR0GbRkE3\nw1pfqtEJjZtXlyuBZZFfUQjrtwHPsNa5HRs+XJeaT2l/N/3OIat03g7SwdtI\nuCYXNUzkl7wprIaRU+MRHkEEESrlnDU4M1qN0EIEXM3ngE7Cg4Ia4CjSpj09\nvDMNjzLnV6zCSIdAvV1Jub4lfBET8XPli3dS2WClb4NS9WNmZ9c5+quPY0uT\nEWUqRHbPTxH1ptaCredVv8HNu4AI2EzizMsY27A2ABNY62cYyA50AVekliW4\nL5bDfXpSDp7StWzuI3ML8zrEntwcfG7YcDVL4kTld1r6yqlD8dtUgPP4q/Dl\nWYFjd6yvzIm7N5bCsjoam3xwhN1eEyZ3n/c9ufa9D2dOMk+dFb9EyXMFVYzO\n2pLier5IB9uGBEM7bVDjD45pb8liS1L66T5oOAdXbufG4yhn3zHEk6hioTwN\nlXsymiAlOacOmTkVBr9/m2oXlffoyxW3GH//3/1d7rTxt3TJGtR4nOPhnCyr\nwjXC8ck3o76xSks06Nv4CSAlKgVuFgpJ9TUgGE0pW/99owh3l6UqZGug0Fdf\nMeMpzeumNUNpBYmYEImNiYNah0mgfGJyIo78U5pvqJXEMoYS3LzOYnGnDZoG\nLAFk\r\n=21iH\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"9075c5d2e2589cd90b65df16b99ddd49ab83aee0","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-10T02:08:48.958Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.101","@balena/jellyfish-logger":"^3.0.162","@balena/jellyfish-metrics":"^1.0.430","@balena/jellyfish-environment":"^5.2.2"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.3","deplint":"^1.1.3","ts-jest":"^27.1.0","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.5.3","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^2.0.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.45_1639102477725_0.9314613027761451","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.45-renovate-major-json-schema-org-5a2437700e169e8b7afe863326e24d0ea4cf3ed7":{"name":"@balena/jellyfish-core","version":"8.1.45-renovate-major-json-schema-org-5a2437700e169e8b7afe863326e24d0ea4cf3ed7","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.45-renovate-major-json-schema-org-5a2437700e169e8b7afe863326e24d0ea4cf3ed7","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a4bd2d2faf256bd9f9fb2d78d54a02df2749c7f6","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.45-renovate-major-json-schema-org-5a2437700e169e8b7afe863326e24d0ea4cf3ed7.tgz","fileCount":212,"integrity":"sha512-oznX2o9JsiX1Z4FeOQmGX7tQiieqs4AugEMAJd40zDsygQEfnLfNvxVnRJNoGoUP0iMsND7HoLRw+yl1KsAwbg==","signatures":[{"sig":"MEQCIAPPP02lqzRXW0xZsq7Vlb9EB8S6sj65XPaUYFio3ZmXAiB4wu0KPt46rzsyMRwvUyaC3hv8ms86ngGIfD7pF7zaJQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":780379,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhsrjxCRA9TVsSAnZWagAAudcP/20VCEcDc2pDZ1ju8l6m\nDrfqOwyYc9WtE/xKlndew8rF48VM2kTUMgG3oH8R6jsU4zmRaxXYNr/WBZuf\nupsnBW5W4yP4Jpxefz0Skq0zxga0unicHW6xUKQd9Jm76q/61AELaNGpKKX4\nJX6O3UTyy8vHyTs7KlQ5TJcxQ9QHmcKIL86YO3oixXkfG64udGxQ9+odngyB\nMHtMZKtVJV7Yg1+U0EHpSXn0i+h3Bp/+msaylqMwAQrpzOQA0mlmG6/uUaAl\n1TH014DE/jRjWt86JaTMWWCEIDwdZ5oEBtkbfP+OLCEJywtRCjxh6JeHXqTu\nofejtRwLhbk028yc2HZ5BheyZv/7eY4bRLofcPPkvmD+5Sdoo+ukVltdNjJF\n44ckFKp1XMt3r6XH3iAfA1CZQ7Pzcxv+e4gRkx0eFk4VGjkB3Hbev/x6jl7l\n27uLgqYhfIcpmV9BhvDmJF6tjJfXj967fjs+9+y0jBgo7sAvH84k201VmpaN\nYDvPf75H96Z+xNxKbJt1gZVbogglxpSzHV7fDsUeDbU2vfZOkDzVR+l5xMIB\nAQoAfJtQicUdWBJW287E/j5sE1FObymlf7/hgq0tW23Fnn7d98vPNmPtLstn\nONjlQfbGjEvuPdjB6xGZ7lM3d5BFnMPRV4QE7Yhb79ECHh2J2Bc5Ejfsb8MU\ndibv\r\n=c3Va\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"5a2437700e169e8b7afe863326e24d0ea4cf3ed7","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-10T02:11:32.980Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.101","@balena/jellyfish-logger":"^3.0.162","@balena/jellyfish-metrics":"^1.0.430","@balena/jellyfish-environment":"^5.2.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.3","deplint":"^1.1.3","ts-jest":"^27.1.0","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.5.3","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^2.0.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.45-renovate-major-json-schema-org-5a2437700e169e8b7afe863326e24d0ea4cf3ed7_1639102704794_0.15673299210005198","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.46-renovate-internal-patch-4dc905430b1083b807eceba5b361a76d87274a50":{"name":"@balena/jellyfish-core","version":"8.1.46-renovate-internal-patch-4dc905430b1083b807eceba5b361a76d87274a50","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.46-renovate-internal-patch-4dc905430b1083b807eceba5b361a76d87274a50","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3bf2da7d3aaf73e511141d71a86db38e3e4ba1eb","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.46-renovate-internal-patch-4dc905430b1083b807eceba5b361a76d87274a50.tgz","fileCount":212,"integrity":"sha512-aAB7xe18z0/TZVsiu931y7hiwsu+0X3DCWbVakprTX3u/lut+XqtLCsbvn5bI/C6YDHMYQdUN1Ttj/JVbbBRmw==","signatures":[{"sig":"MEQCIAv3OPrfzTnC83YK76RaClrso4eBTHWjhSdtRHq07ERFAiBat2SNW3D6PYVkwnYx9LdAzJ+RPvRwmvTLIMgmf06fxw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":780405,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhsrnUCRA9TVsSAnZWagAAmeYP/1AAvRfwJPrhebc+LaDE\nvPlDjA+HQCn50Mk0HrNWRG2U09PFZ1dL+88cwqpix9wq1jM8+UBw90ArWYEs\nHJBaOktEjrSXfBYOAKYjRFtMU6g5RVBIOgaSMl1kx7f3AbLwdZfcqC9uDTXK\nyg3GgL0qNFpGURJ9t3vSDZjZULF9tNhBGexc7SsGgUyTymBqwHpTw+pDwb/M\nQs2gDo7gMLZMnL8sq2NFUWBrx7c3VGcCCYcgPLtGiOBnN0T9PMhW7CfN+Mgt\nctMbhi+2mRVmZCae2B1Tt6RLN1ZAQoUvZ/2oUTHdzDNCEJ0cCYrlJlS5GTp1\nB0ezjwq7J4niYb5qtTpNJ1T1AG8gQHl0h/bA7C7bE5Mk178KuO+GYuyzPVFL\nLZzjRI39abVjcSS2aoG3YsIP96gtMLg4KXdJXqu63dXk4fVektjzMlzpZmec\nptnDCGjalZl3EDkZHQJvvI5A53WS0sGj+hRs8T/aErwxhdrrYOtgLR+iQZt+\nfdhs1s4G6CX531aex2UICTeYF0iTn3mRaEDoo4sMVXgzsnokK7bCYsmDFt44\n/v5Qgk37Do1pQINEGXz/J5hL+o4nY4IBHBP3nPxQMqTQ3NmXUXWpL74CWUZv\nuzrBmXxrGENpXEaRiTzxq+tyvem5GPZyasvGhpM0gVdlo9+MB6w25ukzst2M\nazq0\r\n=sq8a\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4dc905430b1083b807eceba5b361a76d87274a50","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-10T02:16:34.377Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.101","@balena/jellyfish-logger":"^3.0.163","@balena/jellyfish-metrics":"^1.0.430","@balena/jellyfish-environment":"^5.2.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.3","deplint":"^1.1.3","ts-jest":"^27.1.0","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.5.3","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^2.0.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.46-renovate-internal-patch-4dc905430b1083b807eceba5b361a76d87274a50_1639102932554_0.009414121509045748","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.46":{"name":"@balena/jellyfish-core","version":"8.1.46","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.46","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"22cd921954eebe044e7463a206fd38326ab7548b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.46.tgz","fileCount":212,"integrity":"sha512-sAEd9HhqlxPmQ2S66gWzDYK9JG+SAK/GQRzVtNTX15xk1NMeMLh6jaqLF4xBRhJ6fOcUo1+mhm0H94+EqOW1uQ==","signatures":[{"sig":"MEYCIQCOKZzsgzVG9j/opjDmFMERrymsgbunXxP6Pjd7V+U5cwIhAINgN5jHztJP5j6WCKGLFPLxuKawUb8uiTB0ON2p819r","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":780340,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhsrrLCRA9TVsSAnZWagAALFsQAJMbnZD92MRbJ7MC+XGd\nnnnk4VoWuhsPqOeGZOIe8X0KqG3eP/+AbFuNBrlw/rU8i9NTCLgQlDK6sWOs\npYtTlup/bNlZrjWLldYqbzDx3BuPOzU0/GAHReIoyNYjBnbjqESobDR67SnR\nXTb+x88GrUt51Hyoic+1jj1Om0uS91GHlCzcn7TB6C15QX7yIvd12O9moZ0o\nOnDstQApG+bwv/qgRY5QHh+IO1vbdSS3zS9+NcGNJF6aVIIWsLR0fpsevF05\nJ3uRUXWOx/v8clbhSL8VCAdQoWsIlToB001OVC5JjcfU4MiFa38Uh2chgIwC\ne55UAnMHuiaM/5mYDDA7LET/3jf8tT2yncomAVHFNaerZlvENV6LdpA9TSur\nt2xYDiqV/DHpI/iiZtR4Q/0pSoUpjXTnQ4TGzSctt5QMMUfpKQni9IlHpQK2\nPJm9t7jN5N4GQo+RXRTpZVp6/GmJBrUtQg6hzeHK1O+N2JRoo1gc2ZVaqzh7\ngcNISJV0XcDiZ0Lel7ocz3gkGVIAYVqKPrKPV+zvPFWfNxFxOsi/ZV953Hmb\nyuGcds7DzlFyycB1Kt+Uxq9BH2po+vB51aLPymmIsHUsVUDZB1KbzSv7Kqsd\nuenUepJgaCT67X9YNAzo/t0oPdioQmx82zqs1sx8RRzhKrWVnp6T5jDY0Ycb\naGft\r\n=RciD\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"477ec800c84a17b0e31017bff6e19f9b2b4a1f0d","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-10T02:24:16.221Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.101","@balena/jellyfish-logger":"^3.0.163","@balena/jellyfish-metrics":"^1.0.430","@balena/jellyfish-environment":"^5.2.2"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.3","deplint":"^1.1.3","ts-jest":"^27.1.0","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.5.3","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^2.0.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.46_1639103179539_0.2850585456372614","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.47-renovate-major-json-schema-org-0f5af6176417850790bd2fcf7a1d3a2ead51bb4d":{"name":"@balena/jellyfish-core","version":"8.1.47-renovate-major-json-schema-org-0f5af6176417850790bd2fcf7a1d3a2ead51bb4d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.47-renovate-major-json-schema-org-0f5af6176417850790bd2fcf7a1d3a2ead51bb4d","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"38eaf5effd5af3ad8731de92b91a8b0fb53d6cbe","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.47-renovate-major-json-schema-org-0f5af6176417850790bd2fcf7a1d3a2ead51bb4d.tgz","fileCount":212,"integrity":"sha512-XZi+1sEqeYKSen6imalw+QXBd1wWTT58pe5JngMzBbHkLRM39rgXrAaruOfcwehWIjp4LSL74b7P5f/9BzYnLQ==","signatures":[{"sig":"MEUCIQCY0aE6/oufeWevGc5RXfYKkYqx25yimpzOMkh6A5QsPgIgJqu+ehgHfgJgYcgX9uIKyBBd3xak9TOtqSso+UjMQww=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":780508,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhsrwxCRA9TVsSAnZWagAAR8AP/REJS3NgSZdatgj/y8mA\nligdmiru6mD+Kmtz9JbZ4T85o66Jrm1rSTpBiDjgKThd/w1C6JaN3zTQRzlF\n4rFDcxcKXiduaTk5svR8E4sXc+jSyimvUztoi2hrcRV3te4v3o+YwHkBkFdw\nWLaW+sQZSoafUrXOLSkmSF+4JVtUvHLd0wlCN/NHM5CUwOYd/8kA+a0sVkdJ\nX0me3AwsIFRfF0uPhj6nmYA14zNi0nrOeTH1r6MAQRIgMO59S1qB+DhMpHQK\nJgJeg6cmpH/hTjrw5SDI5e3S2ce8PnQbh0LXs6IoMjLvWFO6nJ+ItEKFpPgt\n6IR6Tc9JdKzhHD7geNDQISpAPAImfZbHfgbpPHmjFThkDTe65tizh/AXA/J+\nEUGYx52n3zEnoDpv826w3YYl8a1DZFN7XedI5yFUHbJdtK7uZOa735+9WEOD\nbrOy2WHG/gDhRRNtsiZDiHaHtTHon83zciRomQtb8EbQJCCjR666Voo6bksv\nu9W2IH+QAJZyjCw0GaAqV5FxRvDJfZXjMYDCnH0Nr03LVlF7QNQ9egcwLvvG\nHS1Qo+9c90zgZa99HmdEHkfvfKnLLSmF8+c1CIfsOABpIckH34OxRcJuse98\n6cKRuarzVXjfVvK9xXWQOTGXG6nI9eL7iG8KplKjlMmhjziczFviWvg12h8t\n+Li2\r\n=zkil\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0f5af6176417850790bd2fcf7a1d3a2ead51bb4d","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-10T02:28:42.936Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.101","@balena/jellyfish-logger":"^3.0.163","@balena/jellyfish-metrics":"^1.0.430","@balena/jellyfish-environment":"^5.2.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.3","deplint":"^1.1.3","ts-jest":"^27.1.0","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.5.3","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^2.0.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.47-renovate-major-json-schema-org-0f5af6176417850790bd2fcf7a1d3a2ead51bb4d_1639103536986_0.34978007465436245","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.47-renovate-balena-jellyfish-environment-6-x-98aed2f78ed4254ec3bbd14fc1e85c7c9c8b7fd9":{"name":"@balena/jellyfish-core","version":"8.1.47-renovate-balena-jellyfish-environment-6-x-98aed2f78ed4254ec3bbd14fc1e85c7c9c8b7fd9","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.47-renovate-balena-jellyfish-environment-6-x-98aed2f78ed4254ec3bbd14fc1e85c7c9c8b7fd9","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8cceadeaf4e187dee579b298b04cbf934c8be0c7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.47-renovate-balena-jellyfish-environment-6-x-98aed2f78ed4254ec3bbd14fc1e85c7c9c8b7fd9.tgz","fileCount":212,"integrity":"sha512-s9ze4CtE4i8Z7GfBt+L08uAB6c36+ra1kb52e4kmNcZvxlqyyv4M7PlgVJPT/R8Ji3aDwnk/vchfHIIsV4cJjA==","signatures":[{"sig":"MEUCIQChZPzDGEa8ya2PB1M34YiHr1wz3gmlAK3XsK1q90YciwIgCg5SYb8159t4w2JlDshdaDrJD4ee9CN79k9F03I7A6I=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":780526,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhsrxWCRA9TVsSAnZWagAACHgP/R/2vYhfWy7CfqvNfz/C\nTHHgysRia/eh9XRvZQ9lKamY0h4/JAtlirmHeS0EGLZ4OYzfDwhdE7qK3z3C\nm8peiMsF+yx0iIviw1tZ8YC2J7sU/k3fdcWrgEZvF6cx6a8HZOUNvJ9epMLw\nc4a8Z/YbIOBu878oWIFDQzceSRSecomUMdRdkl8Xj7BS8jOv/BkPthrApf4u\nYcrcs3dsI9bmIuYiH61RRGPQYyMSM4PV28CgP998aQJ6c5TpTTCR/fkMzH3K\n1Uq1gcwUt8vxqMJ6MIbGGhjfsjOAf8PeaSHlLlT75VnlVuI8+2mD08zMO+Dd\nOw2oW2shE/ilJyOjOD+5k5T1JEgEJaWpPHOCNYh63HwgRbDImNx3zODavU3e\nwHPYT8EBwFwZ2rFg+DhWtgZgCDlEyJy3o9COaB/hOX6ObsJnzIM5BmU3KNOl\nAVOwcIUcTchtlWG9d9H2fyyjFqso7HBAixQVFUgbvZI05C0njm377cBhZM7r\ngUC4UsP4pfnfydxbsoQzY2ZkyVF4jFNB+LLMubq7+OynjS8bYC1kiJWL1Jdk\nB7KDEIV6gL5TkwE6AxLtophml10eKuGF93nkJ99Qg8KIQWP/VinnQy4cPmgc\nPDnYykOXNCNgkasl+97BOM7u4lAuiZ0a0eVdFLPTf7r8W4qxr/j9Uo+WHkDD\nGl0V\r\n=Yxq6\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"98aed2f78ed4254ec3bbd14fc1e85c7c9c8b7fd9","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-10T02:28:35.763Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.101","@balena/jellyfish-logger":"^3.0.163","@balena/jellyfish-metrics":"^1.0.430","@balena/jellyfish-environment":"^6.0.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.3","deplint":"^1.1.3","ts-jest":"^27.1.0","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.5.3","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^2.0.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.47-renovate-balena-jellyfish-environment-6-x-98aed2f78ed4254ec3bbd14fc1e85c7c9c8b7fd9_1639103574739_0.7504390360310249","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.47":{"name":"@balena/jellyfish-core","version":"8.1.47","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.47","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6d5a1cd6ada2567cc59dee7f77463e7686f395e7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.47.tgz","fileCount":212,"integrity":"sha512-XA6Dp5VVCjlZg6fqK7CAkRF9E8U19XkjOcvwbB9dt/ozKV6IIfwJcnLEpK5bvuSQG1OZGTQVclEAbVAQOZV6Kw==","signatures":[{"sig":"MEQCIBC6g9muhoojJ5LOlGdnFhHUX3jMaLDOCwEob8XlsjLHAiBwfOpIp7TfPRpdAWiXl8bVU+Hqh2RUeoXwiT5LEsOK6g==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":780443,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhsr4ACRA9TVsSAnZWagAA84UQAIpk/qLVgdX7p7HslaXh\nXs0vkJEiKBn3bmXd2Of7L9s+PZwZcTj7WdQviWU3yvOVae+WMJPJvQqmRRuC\no4gwtmeT4bS6FyWGyJeJWp2NsJh/tGyjcblmqXgI+I1uNTLlCeNc1Z2MSq7Q\nCD1dBRMaaOXH0zlv3gaAbK7/oHGvS+7YHzBGLBLjGO1DzxskfCeqSypxrRft\nXq+JKoeXNyxgOINKj44TtpQla7aKeimdekDTXWvmdfAlP7kSJ5UOZFXznIP/\nLszJUxNUW/Fu7Xr+XXrWAokhu6bsxRknAZkvwNPhB0lRoPFw1gh3vxzA8n0p\n+DDoq15Avrkqx04v+cLlO/fJ1D4gR3M/IhQtjxQ7VlAsHvF1X3dA0MKXbLEr\nn+EUAIpJBDrR8q16N55Z21s/TM2z+vnIBWAsgF/Ojqv83G/DSebTG2oEnUm7\nu31qhWz3bSffK1ugQAqJVFsWpgdjKZK6zwh5XeUsBtmdOJSz3wuWW9CikfX1\nCv28fpUTR10d7srU6sc9kfxT2jB2g+zZHCfmDYTmnq2w5BdsK5fx3A1BYTTn\nGz7bEnWmZBgQclFV1htqa5ZeGNY4n0dD9zBmm/Zf/7yCf/aOJwjr1nljwOUT\nnTwX5D1pu04Oyp43mGJ1KTNRdyBaZ9zsc50y2VczH5Wvx/3QG3O2T5TgrI6K\nk9FK\r\n=uJIO\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7f9ac3dfa5b7ed465a085a6df5e90d351ad0d1a9","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-10T02:37:12.350Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.101","@balena/jellyfish-logger":"^3.0.163","@balena/jellyfish-metrics":"^1.0.430","@balena/jellyfish-environment":"^6.0.0"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.3","deplint":"^1.1.3","ts-jest":"^27.1.0","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.5.3","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^2.0.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.47_1639103999640_0.3631534059525223","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.48-renovate-major-json-schema-org-bf985a2dbdcc0840521d12480f9e68f79a5ac764":{"name":"@balena/jellyfish-core","version":"8.1.48-renovate-major-json-schema-org-bf985a2dbdcc0840521d12480f9e68f79a5ac764","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.48-renovate-major-json-schema-org-bf985a2dbdcc0840521d12480f9e68f79a5ac764","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e97c378832e23f0d7f4e8ac00f7d4f39b635cd5a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.48-renovate-major-json-schema-org-bf985a2dbdcc0840521d12480f9e68f79a5ac764.tgz","fileCount":212,"integrity":"sha512-YumEeuN2I8beriBOIMsPFPqK71aHqifZ9edbM1nXP47B3TXuKD72Gwv0ovMx/iWuAChlccr7+D6uggb8voz9QQ==","signatures":[{"sig":"MEYCIQD6y3AwetCRcDBtoY6DSyDLd0dscxZw3O98lb8oAPMwjgIhAKJjfo0i92jxeSPNUX0zV4nDz/tWI4wnKysjv/NA/eX5","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":780611,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhsr6/CRA9TVsSAnZWagAAWAgP/29MEY+FoHxbGYaOU5/z\n938tBsICpeWhnT2sYfXFEOha5fzrg+J5BMEfp7G5N9jO49l4OoM5SWytS6Pj\ntuqWFPb/FG/gjEZGO+QxkZWL3/Z/m4SFaImx+o2schB6iZeHinhL0kWjWwTx\nQLq6NdvHFKPF/y+Vcu4gFJkuRlyColNUIHIxp681CsRPhl5cVeAECjf2B6xf\nrixUQNAet8XRp/1kte2wQJURbsdTtbKFnMUlMyMYemaWfCuM9k1j0fuTr+QZ\no+rl3Lv8B5q7G9BTL78zvGdtYcftGu1njb1a5fnb73+U5qQ/+enhMNnD9P8P\nsxoXMz1vzn11XW8BuFQEeGrJRICixMyEhBTakV0PAiJ7UochH9G41SWuDL3e\nphXcZKkQu8BXMonZC9+rlYOAeyT3OfqPDjWRPLKqSdzT1cGn8o8hYAn2nG+t\nQqb1UTmMQTbvccHcz1ZG1SQIl5QahcxAIX3Ce2WcVdYtbAIgoUjPmo+CeFhF\nUs5N0ZFpdxlP7OVPkZjMbnZTpCVuDBEi6BqnA66RS4EjY+wUhTdE2tTEKl57\nlmve43CeBbhFrk9Al4bXpOUl64i1Oy7EyTPWgSnp4k5JsRY/GNVdKDT0d/ag\nyw2W7ncGosdHnNnE+EH43ayGtaKaPNkI+sASqCVPi/GHG5ezH97QQZKHuy/p\np6QM\r\n=Q4Ot\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"bf985a2dbdcc0840521d12480f9e68f79a5ac764","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-10T02:39:37.307Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.101","@balena/jellyfish-logger":"^3.0.163","@balena/jellyfish-metrics":"^1.0.430","@balena/jellyfish-environment":"^6.0.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.3","deplint":"^1.1.3","ts-jest":"^27.1.0","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.5.3","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^2.0.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.48-renovate-major-json-schema-org-bf985a2dbdcc0840521d12480f9e68f79a5ac764_1639104191732_0.4048312805970853","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.48-renovate-internal-patch-e82c0221f230b147e365cc133e3d73f3820fe052":{"name":"@balena/jellyfish-core","version":"8.1.48-renovate-internal-patch-e82c0221f230b147e365cc133e3d73f3820fe052","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.48-renovate-internal-patch-e82c0221f230b147e365cc133e3d73f3820fe052","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3e5c4e568b38b42fe13e4030af52124859e67244","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.48-renovate-internal-patch-e82c0221f230b147e365cc133e3d73f3820fe052.tgz","fileCount":212,"integrity":"sha512-287dFUpPCIseFAtWFl2qdQccOaCKhiGBSHeRo3gvLYOs4S1gHs50Vo9X2xZaYVqDxikiw9xxHaLiSoc631ocNw==","signatures":[{"sig":"MEYCIQC+v7Ez5dRazawcdXvj6oYwpffAv5LuqAV6TT9tdjKwZAIhAJ0XC74hWL9lGY2eMv2407EsIM0VftXGzzN93YaCBmfz","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":780613,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhstTECRA9TVsSAnZWagAASC4P/RT6JuRjkRVQdidPQU+b\nvjN9yCEydk7OVJi2sRgR5oNiXtPRpEnD9aAxqXb2KubolWCDhmdWugGykb+z\nJj7vJdpvxO58aVdlRsiKuiIl3/pCo6mZAh24QcOkXuCTBDsmYNYGuOH+UhN6\n0uDoAlE24dQXe2QptW+TCo8Fr/B0OXTJa6kJr4l+8iBysrInKpljrNSZd6qF\nOSBYbeGsM/1aIit5qpnqj/GX1Z74mNXCjIGu495KMMNRS7N7v4ejOSPpBRlf\nuSLHAlJDqAfOQr9T077iPK3yho1L1/wDwct1auKuHvvotkOqna1gXHAYLrJc\nz8b5ozJL6Z7GG03dThnndggjmxST7T+rCcy3BCX+EHS7YBGYupNLtg7r3ul2\ntwAlzqHtExIPM23CsEIoOH+6VgDGwD1jT3HDmY4F4jWoxLNCwZfpiEj+YDj5\ntzWfauQUEpGr11w3jxviwlILGkTZhIk4IxbvItkSPwTSkLXQiugzHVKBuOv3\n6LoMuOVejPIrhKIHWdhFxG3wDQ+ORnkOdb8zvHBYnPpjTXlDHEmIhO794rDf\nHNsno1/U2bjsxeIsX5eYh/VJ35dD6h8cgOXARXUjUMx5msTf/EsiFw+2Z2aK\nVSjNWoHPDoDwj2c8z7Pxb0tBdJKwbURyM66pX8UTkCu8tXh0H7jpEF+9nW9Q\n7ONV\r\n=urtD\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e82c0221f230b147e365cc133e3d73f3820fe052","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-10T04:14:29.958Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.101","@balena/jellyfish-logger":"^3.0.163","@balena/jellyfish-metrics":"^1.0.432","@balena/jellyfish-environment":"^6.0.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.3","deplint":"^1.1.3","ts-jest":"^27.1.0","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.5.3","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^2.0.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.48-renovate-internal-patch-e82c0221f230b147e365cc133e3d73f3820fe052_1639109827779_0.20094666059525457","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.48":{"name":"@balena/jellyfish-core","version":"8.1.48","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.48","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"10762f173cfe80289c381a841e86e38e95304a7a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.48.tgz","fileCount":212,"integrity":"sha512-pcCz4FFCJWzdlHjX0kRwbKPfs2CkascezrW7aSZlaL5LQ39PxOjXxSnHqvpjEYuZatH0uKBGk1yim2R1fCk40A==","signatures":[{"sig":"MEUCIEpfKQjQvxliFvrT1q8dqzrqE83IveMpJhE5xnI4zLhSAiEA0uZYGSLURorL6zAq9MEhSvdrIvgSKt5jAm2snaRv+3c=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":780548,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhstY7CRA9TVsSAnZWagAA65cP/REhmCEmF2PXXW08/Qcs\nLGzfE+QeNRQ22bbe0HeiolCgpqklWPfX024PuJv7vyCGlmFKikzKaK6urZ5x\nLd5VmyMIhi3grg9go8Lpgz5rzSrh9G4sd2rUiI6FTIjq+KLfn/eNmXEwLhFW\n9D2NMc7uCXfqs4o1xRqr26GamshdZ04InNil6kS/zhHXrdCEUPiKz5DACI1M\nGPCGHo2Sbakn1gc4P7mFoL2HxmU2kfSxdSmtddBd5jFrCvnft5QkakxeWJQy\nYltjf1c91palswLiKOAtznL/tF3+nQ1JBOFkYtNgOTp+gdDkOaxpd2V0OmsS\nFMomTeoTWAcsAXo+by6L0Pc2rh+9CofCg4+tiNAG0iPkvf/jWP/lgZu8/8Mh\nTuDNNd6bpXuouJja1pEjfYQcrUahvR3EKrYzq7EPW8hJD+xAp5xjs+mCHHcX\nODHYCnPLdFVnzNd7qmvrWEViCk8Er0nfJuehYVaXs/NKhMbGivjBBLFQ+cDz\nzK4JjxYun7J1GOHP3MttpH0FteighDlzIssLHv+TFANo7HbS1l1D3XLAqeKH\nxNmnp0XQGybzmBmjcXw5kZdGWLlvgwBDtSEXvVS0ohTnZOpMqN9HZSNy191A\n45mKWU2eHSx3yzqU5S3wDi73bVFhglaCHRoji/tODsG8R4Rnk2O8GDLxh9+t\nWOB/\r\n=F41I\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6bc52f09e2712f56f6860894654fe142fb8f5e57","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-10T04:21:29.484Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.101","@balena/jellyfish-logger":"^3.0.163","@balena/jellyfish-metrics":"^1.0.432","@balena/jellyfish-environment":"^6.0.0"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.3","deplint":"^1.1.3","ts-jest":"^27.1.0","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.5.3","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^2.0.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.48_1639110203274_0.46921069800874204","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.49-renovate-major-json-schema-org-eb3d0a1300a52d0b3620226cbbbab8a11e0c038b":{"name":"@balena/jellyfish-core","version":"8.1.49-renovate-major-json-schema-org-eb3d0a1300a52d0b3620226cbbbab8a11e0c038b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.49-renovate-major-json-schema-org-eb3d0a1300a52d0b3620226cbbbab8a11e0c038b","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"eab8ae17280a61f03e0797c9b2be27a59abc0b04","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.49-renovate-major-json-schema-org-eb3d0a1300a52d0b3620226cbbbab8a11e0c038b.tgz","fileCount":212,"integrity":"sha512-HgD4OXvDevSutZ9z/nRWOeXygK+Bj1oYMA9IpZUtchLuMpFa0YrQPLgwleLqJo9IlrPjRSqoY6oAW/wVSbVt3Q==","signatures":[{"sig":"MEYCIQDU6PlMRacT62g4jkQrveMhlAUhIA3UtYgbHXSdVpbJhAIhANnTuzLs6sN3ZZoF2SoIdwqZGMUAISxt9bQghG0L3qBK","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":780716,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhsteXCRA9TVsSAnZWagAABGsP/2anlQOi7bJSZELgLFR7\nVbyHH2AKPKQrluJp/1TN2Nr592ZcTyeXC2tiVDuT+qHKzYT0xy3cT4wOwIeF\n239TAjc53rwkiHt1QMPzFRTx2GFzaMKzZBiHhFcaWCk472ylLi5JWZQQMQ+w\n2H9mhLCDAYAfzgI8z65eLu59GTU+MzQFPQrh4uz44BQ04wOm2MIwwrkRQcoP\nCwd8wsQLEgZEtYw1AZFrK9r3AK+gocO7PcpoQx+HEyHmJDiahDMV2sKMfaL6\n+UfiiBWLvuSCvlV0cv/ZbDjw7/822Hkv5Uu1BioBeLOTXZquCDFBfc82Sr73\n3mp2nLGmDz0VeCJWiiUCEW8h5OrrNw2cLDtNSqIKMwZd5LqwZmTpymlfpwcP\nLxrl1Q8uA0VTakWX8zj0UUAeYKHkNlu6XInIqthbIeKABarnIfPrr64695Go\nHow0cKtPkmtNwWA6cMaijTji/2zekXOKhT1qBdUP5iYj6niJoVe1Iw5Jrake\noFz5d80JipN/HS4ntEq4XRx2TSXmEYIVmotmpSFA0zQDFK7qrgrywvn5Vrsu\nj7x26n8O3vjLOivBpKdEl5Y+NMc8DUc3ysuZJb8MmDg/EzwQAQDXHLbeq5/s\ndxhfHDQWEc9ItQk1n/B/G11gfReSpqUH0uIfo1H12Z8vCyRgv0ztLIlFA5XP\nSuN+\r\n=xUNT\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"eb3d0a1300a52d0b3620226cbbbab8a11e0c038b","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-10T04:25:50.558Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.101","@balena/jellyfish-logger":"^3.0.163","@balena/jellyfish-metrics":"^1.0.432","@balena/jellyfish-environment":"^6.0.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.3","deplint":"^1.1.3","ts-jest":"^27.1.0","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.5.3","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^2.0.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.49-renovate-major-json-schema-org-eb3d0a1300a52d0b3620226cbbbab8a11e0c038b_1639110550877_0.7055876759199815","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.2.0-h32-error-ui-schema-5d00d8fc9713b9062ccc8def92c85ebbcce8002e":{"name":"@balena/jellyfish-core","version":"8.2.0-h32-error-ui-schema-5d00d8fc9713b9062ccc8def92c85ebbcce8002e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.2.0-h32-error-ui-schema-5d00d8fc9713b9062ccc8def92c85ebbcce8002e","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a83dd51f54ab97c1eb7f257a7e6f3e786d52afd0","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.2.0-h32-error-ui-schema-5d00d8fc9713b9062ccc8def92c85ebbcce8002e.tgz","fileCount":212,"integrity":"sha512-hpnqCwSKunxI651ZWvqkqG3b7UqnsnwUA+nIT05cmSaZvnvy9o+Rcltquq/GTdPDPAxXNLVnsiXXma58wObdhQ==","signatures":[{"sig":"MEUCIFjFGLIlMrHBN0YO7d4w1epuMpC61FfU31tNxCpnxHeCAiEAvob9Fe8UDgPcQ92ONhhQHlshHJKEDOhUmllK7qIU+LE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":782001,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhs0icCRA9TVsSAnZWagAAPRAP+QGHyEYATMLM3j5db4wz\nPyHcG+piONoQzL5pGbtYF68O+3aP+CPlHy7KU2nb4/Xz5GPNx/eeyGNal/ol\n0gBRTVzTmfPjdx216aJD6cNwbAmWcWSD9SeI5Mswp544D4tKx703y3g6C7Hs\nRReyEAawq7UUyMkvspGEGOjjwGds4zpGjUeYhn6jLp91uZbyC7tz6HmO4KNn\nbHEDs2QVGJjFP1/LSHIeV0EVJw5FGVjlywqkLjTYVTQ0Ob5hIg8iTEE6qDrw\nKEo7nKfMw471z5KgFf1XqDU0v0c+p/yYTjWsVfwfuZFuUptBzhnEmWc65x4g\nhnEfULIhZeYaVyIqLpc3t7FhDIRTeLZhYb0IMVdrbT32ywVZ4mptZ7Z1WfFZ\n5gpy8/seS4qdtHnCHjeGSl9E6xKkwMKDQyqK5Nl18UdKYPQwxtYjjMP5AQ0Y\nnHWMAdsyGmhp7/egiKQRPRs5X3omxJWzIjyD6+hqOA/EGTZqF+nR8J4wXlX+\nO+FZkk9Jg8ytHUrik9upQsXLCtn8PF1Elkhofead3hCo9BKS1RrwZtoG+h2p\nnmHjqJpq18wqeFQiJO4tZEyZ7dy70J+5VDcAdlkRdX1DHR7T9l/nMBu/Tbyq\n3YJd5FP8NUJecOSNqi0gZhBODU+ce03Sl+Jp69BJJzHRywoPZU9b4FceDXCt\n7MxR\r\n=Ig+K\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"5d00d8fc9713b9062ccc8def92c85ebbcce8002e","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-10T12:27:38.526Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.101","@balena/jellyfish-logger":"^3.0.163","@balena/jellyfish-metrics":"^1.0.432","@balena/jellyfish-environment":"^6.0.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.3","deplint":"^1.1.3","ts-jest":"^27.1.0","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.5.3","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^2.0.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.2.0-h32-error-ui-schema-5d00d8fc9713b9062ccc8def92c85ebbcce8002e_1639139484523_0.27344763390117044","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.2.0-h32-error-ui-schema-7777f2be309316ecdfbaac857011ea089238ebe3":{"name":"@balena/jellyfish-core","version":"8.2.0-h32-error-ui-schema-7777f2be309316ecdfbaac857011ea089238ebe3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.2.0-h32-error-ui-schema-7777f2be309316ecdfbaac857011ea089238ebe3","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"be6e50aceac793e9b23c0608461b70b366f1bb80","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.2.0-h32-error-ui-schema-7777f2be309316ecdfbaac857011ea089238ebe3.tgz","fileCount":212,"integrity":"sha512-UA45uz6io0yiEFNjrWltxgbbEbBTCMApzmIY8RC/rLL/6Va1Byh5GfNN/N7Yt1+8491KmBsTYTFNqdkX9IJZ2Q==","signatures":[{"sig":"MEUCIQC46yvPen6kW6xgUFHQgQILehkOv3+8qka5Wx9B7ArxqgIgJBgTeFPEM+vL0QqBauryzjFaeMeaGX3fl+BuziN+KRc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":782003,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhs0nuCRA9TVsSAnZWagAAVC0P/0OVvj3LtjaxLYY5FXhM\nQ13AxbKKLH/9sB3c0/GBiZJENCnt7Y5PxCqGNXhURRTFTl5i7f7q1eroQVHK\nQUB9o5pWcharpUrtP/1SEKpecNwXZjWeQ8Mm54TBBYG0CpjPQhmKejdawnAj\nic6mmuXc5vdFvRkuNYItQkUU5WMWnaCfHpAnHdVbtzzuur7tyAdq3woYKpYx\nVIwAezgbJJ5C+vNiwxncal6d4Xuh3j3HagawXHshhzwOzhBNOP8e/9XI+hR4\nuXd10WTS2oWmnXsdrVghtV4K9+1trMw600B7lStZzo9F2+7BprxXM9A82lQs\npSqqCNLe7Kcn+qXqq9XJ2xKr4V/FC1m36TnLfmtuHrobrEriRE0gKOVpzxS+\nDH26Cx4FyfUqcV5SOVG3CWu6yW7oNLC9DTUQOpUS/Qy4dB5x4jRx/t1dn2VG\nQBYzXyhrOKcKSJSqwe0Sq9qNuDnT3whVU854JFQsIMDQ0gRZhmXYTiGOOrPY\nTHLViHWG4s+f80b8HQhdivovWIZM93zBtgEqSODEMzblAADmGSVnXKth5rN4\n/MUsbgxvRAYea54mlDwoR9LxGoDCHtVFga/4bg7gru1Uys4xZm98Kmy1ZZVF\nzYNaSrkBEL9EnSLndwtGP8EnUJXzsT9+ufbiTn65/Q0vG7to8W59hfyJ8yjd\nIxe4\r\n=vAoB\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7777f2be309316ecdfbaac857011ea089238ebe3","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-10T12:34:02.174Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.101","@balena/jellyfish-logger":"^3.0.163","@balena/jellyfish-metrics":"^1.0.432","@balena/jellyfish-environment":"^6.0.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.3","deplint":"^1.1.3","ts-jest":"^27.1.0","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.5.3","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^2.0.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.2.0-h32-error-ui-schema-7777f2be309316ecdfbaac857011ea089238ebe3_1639139822028_0.8937298246598768","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.49-renovate-internal-patch-58847bbd272c183b467bd40ed8a1c15427887182":{"name":"@balena/jellyfish-core","version":"8.1.49-renovate-internal-patch-58847bbd272c183b467bd40ed8a1c15427887182","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.49-renovate-internal-patch-58847bbd272c183b467bd40ed8a1c15427887182","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"22ff456388fac7b1e11b3dc59896245b4f0e3b1a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.49-renovate-internal-patch-58847bbd272c183b467bd40ed8a1c15427887182.tgz","fileCount":212,"integrity":"sha512-BQi20cKiY9BFbvkuR9NAdmjPaHqJPPF0nCzqiCqlWJPwN109OmP3MDvLBBIBaHbbzz51ceojUAZ/Vy6L57Vfiw==","signatures":[{"sig":"MEUCIEe/LgPi4yNnzuP79oVgT9NRz1q9NEIygFRU7xiKhjq5AiEA2/ExG0Y/QZ6cCbokJRbROe4AGdjcEXTDkniAgHqC58M=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":780717,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhs/L4CRA9TVsSAnZWagAAfHEQAJa8Qdq6EGA/HxjVHRQc\ne8d19Fv/Z2XQtxJmbVhp7mwSCrqTZUJ93sonNWvielS2sVKGkryEX9ZNATZQ\n9A3I6Qn9YRH8OsD9/pIYP5QSVQ/fj1g+PsGiObauxZXPw4DWXdBFtl4ylh54\nfHlsYfrUCLT7vZRl0EdgBnpEgiM28mToN6F05QSX6sk6dD6QJhJI9mi72Wo7\n4f4ucjWsi/1Ffu2/0ryHrDzX+Afthq3fUBoJfuhnJVwfSmujGtLGL9EeNRF9\n9p8EtM6a6YsEwyhE5qPoE4qyljwXOLpE3usTLNJ2lkILznyoKGpdmcuoRK1Q\nhbs6cL007iwciUm0Lst04P+J5PTGJSrohHHgJSOwiF5M+a5+IKaYrajnsEU8\nE4DSi3XuYnFNcPZlkNDK/P60wq62HsbqY6zCuf4KmbvrYG1bZHWtdJdoP+RT\nMagFv1jdL7y0w4teVxtwg8vq4I/z+Ut1TinPoyS0CR2uoYDwk2QIGyxNVDU+\nC4/84WBXGKVS6FCSnf1V8dBw+WCv6sLA2m2iKi4Hkex3YofjBhpCU/bzzje+\nHOTp5NGymURy9Qa/647SfejjM80fUwpasC3gnZb8kSpi1hDw9eCBQhJMDAFD\nDinUUzqcdgcPb+a5Vm+mmyKBFUrgfw3+1dYiskSXjNlxwHzN5MJ0xW4OG5pl\nuP1Z\r\n=LSdn\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"58847bbd272c183b467bd40ed8a1c15427887182","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-11T00:35:06.766Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.102","@balena/jellyfish-logger":"^3.0.163","@balena/jellyfish-metrics":"^1.0.432","@balena/jellyfish-environment":"^6.0.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.3","deplint":"^1.1.3","ts-jest":"^27.1.0","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.5.3","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^2.0.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.49-renovate-internal-patch-58847bbd272c183b467bd40ed8a1c15427887182_1639183096614_0.8329025664112029","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.49":{"name":"@balena/jellyfish-core","version":"8.1.49","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.49","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8e9a070d44c90c33dfa3064be30cb80c4e2c2be3","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.49.tgz","fileCount":212,"integrity":"sha512-s+DowcIwDFr2w+RVgCa9pq8mTfogNUiQrFAnudoISvORQnBLqYLiFdz86K6LT595Nge3qvhmhBC+elXHrizbuQ==","signatures":[{"sig":"MEUCIBWHzlc1UOPM0y0S2n5tLwLSrDVJvnOfE8Ru1Cim48iMAiEAtzWoqFerrSMsYTLuIAV0Wq/OjZJPaSv1fjUC/IBprn0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":780652,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhs/SmCRA9TVsSAnZWagAAnxoP/2+bsuWHe1tkh1IaGdip\nBfTtTAOc2Cvegyo9IaVwfUmn1QmZKHp5y1PSlK3P0wrh6ib//1ttlFuvMF/M\npHCHwTOkvP/dg4793I0hXbwe1zU9mLfweQEDPCOzLzEPh8BOupWM+YLw8y/g\n7tz5/VqsD/hnAzakKhMhbGSAhoC5bj0AB03MvgZmIre3QvUtBUxpyC26f81A\nwmxbM40bG5abmfuBrueS9G8WF2i7X9fHFQVK9gUtrJNeG/4b3YNs23+x/MSE\n7dF/9jl69RxxfHMzEha8fRrEMk4m7oWxpGigtO5xRHrD9kMOIYpHCr+VLhd8\nGGj5v54QHZ76cWcbViVihtbpMO4V3rcJ7xgwY37nzW7PKWCj2cfTo4rO3sx2\nmnqdMfs+Y33ZyVdgML+K6ziwx94wJKiLEb1AXa+S4XjZ+pJVNl0misNk8qBZ\nMliO1igynYej4rk6HRqUBvTj7Mh/7vj1E2NVJlJPwZcc2yfChHtSiv4MB6xt\ni44i+/oJdGfEQhifrEkrlVYqlKNFE3o/n3ceyzJrFm8Upy/uqHvRt4v2Ec1L\nQ+HFvtPYRhw4pr0OQ2hdKTIxKg34tVR1yMyBnWtlBszUKwGvqtadznixMa/V\nOcrGOdFMSBjzWPY5ZpypCKIUaUWbb0avp7t+/6llwoGccS3GmvU2NrZVcAVF\nfgvI\r\n=c1Qg\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a08cff93ef9f7e8786bf6ef6fd21ffcddf3d28fc","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-11T00:42:56.268Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.102","@balena/jellyfish-logger":"^3.0.163","@balena/jellyfish-metrics":"^1.0.432","@balena/jellyfish-environment":"^6.0.0"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.3","deplint":"^1.1.3","ts-jest":"^27.1.0","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.5.3","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^2.0.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.49_1639183526349_0.8039363016177257","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.50-renovate-external-non-major-f770b55431f83b85d1b0cc955f419ad2a4a86c04":{"name":"@balena/jellyfish-core","version":"8.1.50-renovate-external-non-major-f770b55431f83b85d1b0cc955f419ad2a4a86c04","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.50-renovate-external-non-major-f770b55431f83b85d1b0cc955f419ad2a4a86c04","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"679cefb309e4d6a9e7edcc0a8b720eeccb458f37","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.50-renovate-external-non-major-f770b55431f83b85d1b0cc955f419ad2a4a86c04.tgz","fileCount":212,"integrity":"sha512-AieruUA0hWuBqog1kCsb7k7qs9bMZSywGGh8troXeFicmwJZEmMpdnD0tdW7wATUWCvcCqo3hMF/HF632+PB0Q==","signatures":[{"sig":"MEUCIQDUOrnhTLNwnOs8IaXY7QhwgNLMQyE0xqbj/ACBm+UwmQIgHd1kfygJgBRS6aZIm8CB9F1xUhJfdN/7v22tNYxi3pI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":780796,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhs/WVCRA9TVsSAnZWagAA9lUP/iDNNTiEE87tkL1QaXJ5\nU3UpqNkOLjEdfBRbqqww9kiag+NVgH6/Wtdb5e2z3qvYzebL6JeQcmMQ9s/x\nMyYNR7K5Fxx06LLjnqxuPeuiXYH363iMGl69m1Vs8zDLOAnF/9Mz35cdyw6R\n13+BH8qceD76HGyJEaXfwnK/vYTeOn6MWpjMJcAmoZlHfXwkGHdYQZYTum0i\njkqDAA8+7Qq/sGSNZUKMw9cnWcDXQsCqtqCSEvif8Cyjlr9josm5m6Q5U9Ao\nmlFRQYCi7XRjaVoLkzTIjoIbeWcha3Dra09eDN3C7lEarQ/0PbLznhLdIb9/\nTsMF/kNUbEXdIzCJfbfZ2uEW9WGDMp2/wV9ivkj0plmMGIKPP2ep9UgE0/QV\n+egs83K5wvliOVusEtfQDvsueMmQkUhYmax+ez937+p2IQFigP4u+ckat0Bk\ndRIgAiw7PoYX/ppiPXVcMR83NwVP0DKYVPj7B9dc7oanQqahgD+UcPCL4YrO\n3aer4lxYAgTxjsGAWPF2CSNYP2aFGBeubQcXweIgvtz/1msNcG+KQrPCtwm+\nUyfrVUn/Sg1md8tT8WCJJcB8IasgYm71sSeD8al/MZqkZT2wmLnS4W1nzGpo\n0x2MMLwKWB6hezFl4/1mOZJfZgyJtwdgKa672FMryMJJ+S8+BSCdT11HY6mW\nkWGn\r\n=shnC\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f770b55431f83b85d1b0cc955f419ad2a4a86c04","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-11T00:46:00.584Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.102","@balena/jellyfish-logger":"^3.0.163","@balena/jellyfish-metrics":"^1.0.432","@balena/jellyfish-environment":"^6.0.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.4","deplint":"^1.1.3","ts-jest":"^27.1.1","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.5.3","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^2.0.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.50-renovate-external-non-major-f770b55431f83b85d1b0cc955f419ad2a4a86c04_1639183764996_0.1355327548599412","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.50-renovate-major-json-schema-org-14506aeb19702bf2296300dad8c5075d7ca18576":{"name":"@balena/jellyfish-core","version":"8.1.50-renovate-major-json-schema-org-14506aeb19702bf2296300dad8c5075d7ca18576","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.50-renovate-major-json-schema-org-14506aeb19702bf2296300dad8c5075d7ca18576","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"82282f57df8e8acac1857c4ceb73984bfbd55a53","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.50-renovate-major-json-schema-org-14506aeb19702bf2296300dad8c5075d7ca18576.tgz","fileCount":212,"integrity":"sha512-5VlmNaOhuJzr59Mwi+EYCw93ryjZ4TgQS+Kcl2DTXJynEIpQ3QXQ83pdRl3vhVvEoQ+3a0Bs6nv4WMkc/jMyCg==","signatures":[{"sig":"MEUCIQDR1HQGbSGnQDYxOqDAXpj4hk54FBvL0ny9fp2NMfiougIgAzavMOdtjTeQL8ooMbdt/NGHhYcmH8nASFaZ7hSCbeU=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":780820,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhs/XYCRA9TVsSAnZWagAAAQ8P/2c5OSeo19lHeMOwdWYK\n6D3dJ2wCSPH28Cdm0xebX11nvt69qRqWk7GGivV0tK0pBjVtCA1n7rVj3Xvi\nzE2KXCc9atmsYqWlzuPZd2OHaQyB8udOdl/fPZYXow8BrSK9NNiYMq0KiivZ\nnryQnvRdGlj+lT3VMc27ZMH6S0RrNwE2YRgLzTml08RonMLfIcgeVURZtJBg\nzBdCuBXSCz+4KWO27j1pQkBbgzs5rEZRo5qH5Nv2HSXxtwPgW1tZdyloKOvt\nHuXmUuAHIiwtyBw6I5YHRcP5lhOZVNnbhjrZkH9XnvAhCz8L5QK7p7ijoChr\nyLH44xQiyTS5BmSjXaKxcQpbvtcokmNi3TMmm3/4u4rXx90w32pChFmKTMBm\nNm0H6ADznD6XN26b2u6G80Zk3hm7XD+F5ewobWnu8oMQUMQyAM20bTDFgoMm\nVbUHd9tlx/Ttw1QI5a8P/Dc9dJ3TL7zHqOSz6ZXDAON9Zf6dxVwFP7bPO/gq\nJQ9lO6dGq6AmLYIRY8rNt5rLfOIhAyUG5qgTp2i/NV9OpijvAXWMaGDdbL6d\n7yhTAMq2DdwXhNNkj4h9/zrpAA+PunmDqaAijmNPqpVYeFlw3EXqBRlH8soS\ncNTmj+oYMFqMnZNp2jVT41GTLg1q49MalNbtSEROkJ19M/LPoPjHhSZjBIzw\nZqa4\r\n=xhr9\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"14506aeb19702bf2296300dad8c5075d7ca18576","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-11T00:47:01.131Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.102","@balena/jellyfish-logger":"^3.0.163","@balena/jellyfish-metrics":"^1.0.432","@balena/jellyfish-environment":"^6.0.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.3","deplint":"^1.1.3","ts-jest":"^27.1.0","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.5.3","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.0","@balena/jellyfish-config":"^2.0.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.50-renovate-major-json-schema-org-14506aeb19702bf2296300dad8c5075d7ca18576_1639183832813_0.2419713117617479","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.50-joshbwlng-bump-deps-d3192879ed8f1a1e86c2b10bfd680504ad37c45c":{"name":"@balena/jellyfish-core","version":"8.1.50-joshbwlng-bump-deps-d3192879ed8f1a1e86c2b10bfd680504ad37c45c","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.50-joshbwlng-bump-deps-d3192879ed8f1a1e86c2b10bfd680504ad37c45c","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6a133b14312ab77f131a90d0a0fbfab508681467","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.50-joshbwlng-bump-deps-d3192879ed8f1a1e86c2b10bfd680504ad37c45c.tgz","fileCount":212,"integrity":"sha512-sXpsYba260veCfyl0mO3Z4J/HjV5J8jNnifiw18+SFmEAq157QlDUtUUoYWZlkkHTyggeB+fgLUTVrmEAicsaA==","signatures":[{"sig":"MEYCIQDSWHnbg/QEJN5it91KY2s2EOHaBMGh5YPHKRicae51vwIhANsplfgwY8e5jw5fFfjWqEuFkclG4mQ5iyQoqWyemiqQ","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":780783,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhtCt6CRA9TVsSAnZWagAA6NoQAKTw2T/Dw5EnKE/GNIUM\nQZ7XZU2Nlu232+HzFuFU/Sl1JejyIR+YVPECxqyM0lPi3ffcHBE6JBFkNVsS\nBFljZbRjQTuR8gd0byRNNJWfKJv7JymFN55mEPDqZ/0qO/1jCNcxuHed30MF\npKehSOsZKlKiG61UUr9abptyj37r0KiTFNX30fSt3JjJaYWWntgdsiLpmlHP\n4cJn0BFO4WbvUy6IuduFPeWkDQY1MYmC+cgBy4n7mUkqIdZwVt69maGuAnmE\noXniNOhzjbKi1mYAQOaHSAgduoRkhc+tIKqi0+ImY/DPd7vJmBx4RpFkOKlY\nZW2VYkTZQT9Tbc+ABhn1Xwlxcr6Ng3wIo7lVS48AwmdVs0+EVJyaOPfiWQSE\nVXIlaDSu+B01Sh71x76bDAet/+c/NzA6XsiMi6bPm2DpM5Smv8w05q5TBalP\nREESDI5XkW8/qvCDxVbLMHboaKRX4jYkAJyrdS3QgFBs8B91QuVy/XgweKlX\nQIVQxaZyQffvW9ZKxvWpaBF0z7/NGcjgEdJiWeYgR6BHM9ri1sILSGIZQHyP\nYpuaICOLmvhf0xoWpDkWGpI/T3zKVITUZFSju3IeVGrlzhKdWR+zoi+fm75w\nLe9ODd9MB9hahOtY6C+ZPS6xu/XrP0cMa+JKqj5+m8vUY5CbHOVD3EaF/oNy\nuWOw\r\n=yzIl\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d3192879ed8f1a1e86c2b10bfd680504ad37c45c","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-11T04:36:10.140Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.102","@balena/jellyfish-logger":"^3.0.165","@balena/jellyfish-metrics":"^1.0.434","@balena/jellyfish-environment":"^6.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.3","deplint":"^1.1.3","ts-jest":"^27.1.0","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.5.3","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.39","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.50-joshbwlng-bump-deps-d3192879ed8f1a1e86c2b10bfd680504ad37c45c_1639197562137_0.08695099032520748","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.50":{"name":"@balena/jellyfish-core","version":"8.1.50","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.50","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"49c90c33b699b476f165a9e7ed1d60048a2f4836","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.50.tgz","fileCount":212,"integrity":"sha512-FcgWMBi6Kdhk5U1DrXAjiRe+2TxC6L7cwjts/8lM7T0axqQd53n1BZaNOwlOcFxXSdwSqmMGV1Xb80bDpxBG/Q==","signatures":[{"sig":"MEUCIQDxoi+Qgda6U8/ARWJgZs1/VqLu/XnYX+DCPeexz45qAQIgci1yEOG1Y60FCEe9PKJ7TjpebibX00DkQrKi9oLDus0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":780722,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhtC0+CRA9TVsSAnZWagAAvOEP/jjhMLSQKHk46vKvt275\ngL9VJRunMWj9xZRX8HyY7banf8NTjeYd5EGbgHobPakx5bFo6/xPl1qdHPVC\n7VDFa4v3IfLjX+AzebysvhzNCLzIFQoVS99MPzZBA3pqOqUAG9hwOBKa+twT\nHP+xXJvaNyZ6FIkPUovLPzb6BkYThFRc8ql8k9HzGFCDayzQg/zMi/UveqhB\nASgVuMpE6owhpVKjA+jswW2FSTTmsqKQoBm+NM/smg7dD+Y5crcnXIMxZ90H\n3eX+Dq0jBXe3Bsk34gJOcxR3kyuDEEk0KaVdoyh2sxujnjwdUGKan/PtqqnQ\nzFIANJMCboRnzQ3XafhJC6dSmi7mJFsMC6XmpOg+krkbxvBamNEq+x01+VYi\n0PqNPHJ/IlwD3AqFkquULM0RvE82u3gVCeMQHD6KZ3HZPvvfEQTGZmrZVVTX\n9tAqbyWfrr/VlUMAuggBMiulUCUFvSExPH/0ZoLuNA25qY0PHPNVxPwGz6s7\ncnP1ScMFgqq6dIYBw2d3kMwDRV5tyiO9C2gG2jMIqtT6Ntnx03v4V/S3Hno+\niI9V0sqYWJJvg0dVp5wQHd/KFAQvOV9e0gRthV7Iv9+Iwi95AMUBVL4efOUG\nZy8OMEBriyP28GmnWbGZ9w8A2ZqpMQ96WtLT6Zs7VOrCSK5WwSnZh0pQmqpw\nIhtR\r\n=fRIs\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7e6294d6a9a22b3e79d9f21304670eef6db79817","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-11T04:44:44.025Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.102","@balena/jellyfish-logger":"^3.0.165","@balena/jellyfish-metrics":"^1.0.434","@balena/jellyfish-environment":"^6.0.1"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.3","deplint":"^1.1.3","ts-jest":"^27.1.0","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.5.3","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.39","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.50_1639198014731_0.37643275268113174","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.51-renovate-internal-patch-52802b62052a21e0a1f308a6cd0bf004d9d4df5c":{"name":"@balena/jellyfish-core","version":"8.1.51-renovate-internal-patch-52802b62052a21e0a1f308a6cd0bf004d9d4df5c","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.51-renovate-internal-patch-52802b62052a21e0a1f308a6cd0bf004d9d4df5c","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4bfb4a19956942667f7097e47fcc33c6a1b45f93","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.51-renovate-internal-patch-52802b62052a21e0a1f308a6cd0bf004d9d4df5c.tgz","fileCount":212,"integrity":"sha512-5ADkdCZ5pKWZoXarWdCaiTLN36dhW1yTlQmomAJYnXRFhfxHYI63A6wHXDPO81CHnkHMMtgSy2dH7BCguaEGVw==","signatures":[{"sig":"MEYCIQCO0ykxbaHb60d+PqeCJp25Aum536mI/X8jiUmiq6+rZQIhAL9mmrSGK12WNRuEOwEfGozFCY0jkLcAZ72R9vwET6oR","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":780892,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhtH5WCRA9TVsSAnZWagAA9wIP/jwblYkGYlBWPWJBxnpO\nVECEtAthJo43c8eDb8Jvh5LmTCSzQbT3e3Lry1jCmMh4JbBxtLtFTyNxezg0\nzZoUgYCko9Vfw3kT90XEf/KtBQfh2B2ch0z1deg40G3b6VnqQG+rUF/RPXYZ\nmRoH8ACd5ySdn+mf+JamQNLsCRRL1LMKMqufcEItmjqrdrRM/cOvbyUN+2V5\n5hP97s13P04HG8gcPeiKfOtDoizV6kKPGSlO3tmmpby7dAjpL9EzVV9taCgz\n34G6zmL8KlQZkzCOUDncQlJvVHOq2MIRXgRZpR3F8znFwszkkY+qHmqgXYr4\nlX/ceki3I23flcolzHIHWkhZMoIgKBi2sq683LRD9u0wUXG6C5l3t7x2Dz8b\nuB/+jfaWz658AVAcPOwsNxlmvd8b6rrXKRN5w4tgdeWCOMIQFzheqNfd/bIe\nUUpx58cLZ/oGdgKTTRKJyxZlYO7H6PT36oBT7fu8Nj/qtI0P4qBiKtkrdlP1\nMudPs3Vf8ww1PoSP6H6gd9GVob/8qDf2Mj1Jf8DWeZZ+okX3gEMs07S6Yndl\nyIpbYNDkJX4LBTL/FIzbU/Y8sgmW/iGbMCh7ijrrIG+kdAvi6KjZq7B+z7yK\npR93sDz2gvds68tO5eXmN2eQ+kmSTsJRqNBDQZn/De3nZhT6ruestiAjjGNE\nVdc4\r\n=Y4vR\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"52802b62052a21e0a1f308a6cd0bf004d9d4df5c","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-11T10:29:36.774Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.102","@balena/jellyfish-logger":"^3.0.165","@balena/jellyfish-metrics":"^1.0.435","@balena/jellyfish-environment":"^6.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.3","deplint":"^1.1.3","ts-jest":"^27.1.0","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.5.3","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.39","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.51-renovate-internal-patch-52802b62052a21e0a1f308a6cd0bf004d9d4df5c_1639218773844_0.37975954935648515","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.51-renovate-external-non-major-3287a77ba5f9ebac8550e4d9e41478df73100081":{"name":"@balena/jellyfish-core","version":"8.1.51-renovate-external-non-major-3287a77ba5f9ebac8550e4d9e41478df73100081","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.51-renovate-external-non-major-3287a77ba5f9ebac8550e4d9e41478df73100081","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"078ed24dad8ecb4b0ed996c7b838f8e195c486f1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.51-renovate-external-non-major-3287a77ba5f9ebac8550e4d9e41478df73100081.tgz","fileCount":212,"integrity":"sha512-ZmVMQtNGE3V8CQ5dGDB2Yz/GYlFzBoxFFFg4nkHnzSfyVVCfKiaeY5L48v4H308LqKKeFIOjCy0znQu0j27MAA==","signatures":[{"sig":"MEUCIQCD8rNnKh4fR5cl97SjmEuEKQ1BV2oqmwEfaZkzN7J+YwIgXPHl73mGcI/6dZ4k7mg0SlLGd3UBNH7F9yC8cYFAqTg=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":780866,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhtH7sCRA9TVsSAnZWagAA45sQAKDSQxgrmc/VSjgmOXHf\n+bRWpHZr4XAl8B3QiqeBgLNXDdWLr4T1yBB0bzYywsPOobInvEFOTsQq+e3E\nJunfnPBLWAqVbOmP1lFls0eNjvfzRX7ybUzEa/Fhp0cHq5114QrYV0Jo3Zve\nnpPNc5WiiwGBMk92OFTJAfY6a1HxeYLb5pJBtCVtTua36bsP0SntOoEOTBRI\ntkcHBgH8kar1TQ78e1pKg2aTpgFCssWt77ZnwPayNkrAvOYBIJShQsPYXn/V\nZsHViJRnsQhZ2fSJw38zx6oXU21BIzXL6xUt5+Wff6OAwtpTmYPx5Jf+s76q\nDiHAIMrFxGAqqbdpqFHFqaDTAbwvs80scjF1Rw7R+Fq8lso6l09jhUBaOJEm\n9TVWcuxHXfa5p/IhTVMpVmhLK6OXNdGQF3Bi0g3/0pQyf/ubgbGlZRVGe05X\n5mq0snBF33QF9ifuWF2U3WfgPJHt+jnNghmOBDZHyowWL+w6rOXzJW4Mo3bE\n1lv1ofmIOTN0izgUgmuxxnJPru1CAHLKfityyPduFBdYx5b6sSMAZpYdNolt\nQwDDu2MT3I20OTNukdUVCetJeG0NpwB/U3zonHs+jbivBuRCPN8cLOrbisXx\nKJMz1dr55NJ8FYaHlO7YkQ15XyK6BKrOuchDlw10KgrsN7BsX4wnbJI/plGR\nKlWo\r\n=watP\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"3287a77ba5f9ebac8550e4d9e41478df73100081","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-11T10:30:25.034Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.102","@balena/jellyfish-logger":"^3.0.165","@balena/jellyfish-metrics":"^1.0.434","@balena/jellyfish-environment":"^6.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.4","deplint":"^1.1.3","ts-jest":"^27.1.1","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.5.3","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.39","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.51-renovate-external-non-major-3287a77ba5f9ebac8550e4d9e41478df73100081_1639218924023_0.25352825276192315","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.51-renovate-external-non-major-19f18e8d5b8ffda99c80842a0df27c74c1696933":{"name":"@balena/jellyfish-core","version":"8.1.51-renovate-external-non-major-19f18e8d5b8ffda99c80842a0df27c74c1696933","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.51-renovate-external-non-major-19f18e8d5b8ffda99c80842a0df27c74c1696933","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b4043b4bf3675e7ac39105fc513d63a29d662dd4","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.51-renovate-external-non-major-19f18e8d5b8ffda99c80842a0df27c74c1696933.tgz","fileCount":212,"integrity":"sha512-Qhz08nCeuj68Lf7yOLIXDjueRykgo1F5WRohfiqoqsiqiUrI62wML1D02BeM+P16V9x/8IcVi9PgkHaMJyTLYQ==","signatures":[{"sig":"MEUCIQDcJzaY6i/gmTcuZO5Eh0CVpDtdIVITl+asau/TphsxzgIgZ5U58KDEKKXSPcjT++YSXTGGTZcO0Ut+1GLJb6LMkbM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":780866,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJht7F+CRA9TVsSAnZWagAA6coP/RidPNse7XdIgBWT6MFJ\niTQB+LD1eaM+VVdlRQBPXvdouKx9pSUlAwTWAX1+Wb9lJeDd33aQuN0y2ff9\nI3ra+Y4LOCQVf7DJCupuIq/iuT4/M+uRVCQ/qHjkSuBUAffesNUha3V3Z7B5\nbkIHhI9bIbAyHjxLm9V+hYYjZDEuHi2yh3+gQIm6XFbtSs9LQUDRfI2+Im6F\n0hldqWx8BCEEkG9uncVQg3CVxtNTf3oPXb8sSk7fX5OckNI1+1pMV+4Nh9Pz\n+c8AMK4qE0SNnkPJEu2RsuKXH4NgEfD66xiwAw+p4ovMotJMJ7eEObuKBfLQ\nIwkQ97P80ePxYIpLlqgDKCo85peXZfujIQPX6UcFgez/4j43Fen3V2zFBVAT\nfPbNM7Zpdxoknzd1qpPTgmSPl+N02DrvUBHIFuW/7eByf2bUCno7xCYMpw58\nBccQ6dRMUgfptpCL2vJylHOWLTDMwF1FkptIzqK/iRc1GVc1e6/3l7vv+qiK\nYrYRckgyew83YmZq3wa5MmzRiC3QgalelvtJShqz5ME3nEFE6dvBke0ejOx1\nm2bESCko2MxuggtbuYEHJq3UdQw939G4C3sz7CAU6Jz1JcGqMw6pigwgbeio\n0p27q4XIOt8Qy22MjpTUHkCPHh79QdeAHN7dfs/JsrN5ah3Tsn7hX97T7vxA\nODGd\r\n=t7CH\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"19f18e8d5b8ffda99c80842a0df27c74c1696933","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-13T20:44:38.117Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.102","@balena/jellyfish-logger":"^3.0.165","@balena/jellyfish-metrics":"^1.0.434","@balena/jellyfish-environment":"^6.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.1","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.5.3","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.39","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.51-renovate-external-non-major-19f18e8d5b8ffda99c80842a0df27c74c1696933_1639428478147_0.03847439691157173","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.51-renovate-external-non-major-afa00dc7cfb841888861e1152f057ec86e60693c":{"name":"@balena/jellyfish-core","version":"8.1.51-renovate-external-non-major-afa00dc7cfb841888861e1152f057ec86e60693c","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.51-renovate-external-non-major-afa00dc7cfb841888861e1152f057ec86e60693c","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3eea712fc694e29f1bb940c5912f67a0246a007d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.51-renovate-external-non-major-afa00dc7cfb841888861e1152f057ec86e60693c.tgz","fileCount":212,"integrity":"sha512-JJCY5rCBloft1rbFl/eBe7S3/JwikSgN5pxo3oHbE1zkNf7JFqgj3s9yKaiIxXKL5IgLoMWTPz/XOKOPXr/aOQ==","signatures":[{"sig":"MEUCIHeOZK0dRSiTqdcILKLsBf5f8IHfOuPPOq+3mnL/soibAiEA5+6JxJc90K3NoAoi2Y7HWy46Xe1sH6ZVV9BhXS5Cm2k=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":780866,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJht8uZCRA9TVsSAnZWagAAlRgP/iyOmh1ssviV+jEufzYV\nwZijTfKCNwN6u1cuD9lgqyIztSjWMVeZtb0DJgG9I6Z5Hr/AlYsOdCj6LcbN\nOzG2dmr7EX459wbyioLs2zDiKBgBINSCJjjVovJNSauRJv9iNrL9Vw524Owa\n4d8kvuDQ/bOw6+8EAgFZk1K00X3uebvDBjITrdSZpyxH15u0VjzR6ehpB8qZ\nqfLzFFIu6dHw83BiQLpGdbSO/mE2S13VL/ymcSJdH2N2SS6kh6A9yXBtlTK8\nsmC94hkKVg3vfPpqQJpCWroJmk3fc3zh5Tv5lPW5aDYUVO/5RY4nB4spnKCl\nmC1KNFLgTst8V83dKb280v1bMRWzCt4bYGXNX6sf7m+whmOAsFrKbIkFukKQ\n6QuhupWXBEy6i49fwdJ1I8YOafx//VCMdRuHRQ9St0e8p4aX5mzyi39GIcpk\nNTByFBU0UI1y7gIVA5dhYwvv8SBLuN1CxgPsmuhh/BG2FQksDNfvuQXs4Gsn\nP1gYv6oAKUXKR58d5hhTBlv5tp0k3bjO3yFFH8NjjT89aq6xgPaz/I8aIuKZ\nQKXjMa7Q4udkLH8jlNABDGj6YXe4s9B+tPKWOl00pOKszXfg0PKGGccVtB0U\nxjivxtTert7dqmL414o6Hto1YsaeM0olO8CVZPG95ORFNLRiagUDHXtPMTrR\nBRRI\r\n=8Cw1\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"afa00dc7cfb841888861e1152f057ec86e60693c","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib && deplint && depcheck --ignore-bin-package --ignores=shellcheck,@types/jest,typedoc","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-13T22:35:52.855Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.102","@balena/jellyfish-logger":"^3.0.165","@balena/jellyfish-metrics":"^1.0.434","@balena/jellyfish-environment":"^6.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.1","typedoc":"^0.22.10","depcheck":"^1.4.2","typescript":"^4.5.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.39","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.51-renovate-external-non-major-afa00dc7cfb841888861e1152f057ec86e60693c_1639435160841_0.7879253883171067","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.51-renovate-internal-patch-e959e228408fad839552c663d5ef4e2f0f76224d":{"name":"@balena/jellyfish-core","version":"8.1.51-renovate-internal-patch-e959e228408fad839552c663d5ef4e2f0f76224d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.51-renovate-internal-patch-e959e228408fad839552c663d5ef4e2f0f76224d","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"42b9d09b42c9684b54d7a765538bd3163d4bf084","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.51-renovate-internal-patch-e959e228408fad839552c663d5ef4e2f0f76224d.tgz","fileCount":212,"integrity":"sha512-I6QUtUdrr0jW9ekJS+PX2yCmUCsXQEMAnjfHZFYgWHdOKjY25R8lSULajAzehBeQN7sv89F8+Ye6yZ3UIT5HqQ==","signatures":[{"sig":"MEUCIAafs65W9Kzf0TeULY/f6nOHCYPTfdqO0IBFTw2G7FDaAiEArjcCpD/zQ4f560a5aO3PBBoluz1bKANfjyksczAs5eM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":780833,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJht/I8CRA9TVsSAnZWagAADmcP/32nlth0Ro6O+HKDNefW\n6UXnT9e1vqZp6mXtBYvaLCtLJxwh8CjlEqwMx2rpvEFnqwJEWNJvJvO19t+J\ng4vVMesQR75gFPGGLfTo+DJMc35Chcjv9aCqqLlH04EUBg9DNMFU8j+JT+0Y\nFOZ8mXB8ksNjX26bYCKFul7GEsVjtSLonjSrp86IIfsRvsU/r58+P0T7CcBS\n0KWNhQXXAJoPMnbdpP408mXZQr/naYxxBtOSBB9rGiGVTRhscm5eTkLMvk4Y\nn/KBNaTVlI3L7DmTvTawL3iCJO8WNn/qNnj+HrFZ4PnyMc8t9S/KatJImKJ7\nZKUNBXsb4S+i8bFcwgq7wrdrJvahRWcgvrN40Q3+s2604jzjTrHqS4U7HTzx\nftlt+0//m3DvI1Sh8zydY1Qj39K4yd/i8GFU5CIyQakXYYPjEQ+PenzphdUu\nJLqeOrHSbUpF4HrNRKv9GQx5uUzngbc3gqQTvKC/iLAYORBo3ZLa0WCE3mLw\nrlYl6kWM35vzkfCrvqtPSa5wzyoLWuZcG6QkN/Xrvx9gOsPWfQpLyu1QXzhq\ny/VDpLCOlal9rjxYkGNC5eU3a0oJgU8V8YJyztAKxEP86i55F+Z3aAUWnT/P\nbz4/mgf4IncsKxoA7ibtGmiEvfrsBRgiieQoRXkMuG6EI6Vsus1kmLKUL2Ib\nimfH\r\n=JlL1\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e959e228408fad839552c663d5ef4e2f0f76224d","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-14T01:20:38.057Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.102","@balena/jellyfish-logger":"^3.0.165","@balena/jellyfish-metrics":"^1.0.435","@balena/jellyfish-environment":"^6.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.3","deplint":"^1.1.3","ts-jest":"^27.1.0","typedoc":"^0.22.10","typescript":"^4.5.3","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.39","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.51-renovate-internal-patch-e959e228408fad839552c663d5ef4e2f0f76224d_1639445052646_0.5649853773754094","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.51":{"name":"@balena/jellyfish-core","version":"8.1.51","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.51","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"384912cb99a416bc8ee6455a52943a06e8c78aff","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.51.tgz","fileCount":212,"integrity":"sha512-lVlhdd9gNWI1cmhUfd/C/r/6li/dwjq4wj9JeGDpkzt9jl364CwIAE/TZzdkuhT4UP7ZVww5XC6ouXt+HnKGJw==","signatures":[{"sig":"MEQCIGn3HB6NGbdnssXEYjh06KuGTDf+Vg0HIBJL08YypfNTAiAU1Sgtr2yB8ZOF56QvLH3z22sk2S96Ci1vb2jrZaA2sw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":780768,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJht/P3CRA9TVsSAnZWagAAFmIP/ivm2ixDJcMJrlCXuDBY\n3xcK3mRCI4YzqocsN0MEErXo+Rsz31JcECo630EDuGqARn+7aAqYT7un8Dri\nwZsQZTa03z7sX4nyiig8YpR1XFGAbKJzOTCD9XhiH91ln5+RGXoTlqL2QMDU\nm09/yRWnbURY11gpfrJbOfHQhhGdGWkJwikCV66m8ZQ0sLz48fSEzAcAw9Ds\nYHlFGfMqmuH3DKd2EJeJccQG8A0CeqhqjpdOZcBMjdS+fQWGa/DM1EiGKL1w\nHbR/FN+Kp5oksq13FYcXkPgsKCE306tkVC+DHLPeH+0qrCrlNFZC8pwQ2BMn\njDX1hjiFGVaijl/aixHRHN5Yz9FlpaNFzNyG4sKhSAmJa3Ai5EcITEdUyvSG\ner9e+I/8hQS521M06RHk9nXGODmsEKIT/JnJkT3cYgnLBtWFQsX4TI8mDaZ/\nXHtuJbR+nNAq+QMdfMmfezV1CN9fF7hdF36Ad3j2b/0O+5KKNPinpMF9nnnJ\nbdcNvBEDZ/rDNVI+AGLFeqqX6yEj6DxEgCiRIC4AEw3s/WmQVi9FtLz+MOha\nlfVVfPMSxboHiTl43CCT1A+rRyL42mtiPb849Ilcsockdnix2aPxfvQfgNEi\ncUog2ewEu8+HONDlEWSbHWFJnNYL8/rrpnkwEEacAFGoWqTtCTBhKFO2x0oa\nPfiD\r\n=eV+b\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"209a5f377074b8c9132155274343c2ac0e5cce3b","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-14T01:29:30.391Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.3","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.102","@balena/jellyfish-logger":"^3.0.165","@balena/jellyfish-metrics":"^1.0.435","@balena/jellyfish-environment":"^6.0.1"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.3","deplint":"^1.1.3","ts-jest":"^27.1.0","typedoc":"^0.22.10","typescript":"^4.5.3","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.0","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.39","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.51_1639445495082_0.4184049420141067","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.52-renovate-external-non-major-6e252068bab8bad0921948b6202b9a4189eeef66":{"name":"@balena/jellyfish-core","version":"8.1.52-renovate-external-non-major-6e252068bab8bad0921948b6202b9a4189eeef66","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.52-renovate-external-non-major-6e252068bab8bad0921948b6202b9a4189eeef66","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"62f7cce4d52e13d4293d576522ece1c8c4ef1ec9","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.52-renovate-external-non-major-6e252068bab8bad0921948b6202b9a4189eeef66.tgz","fileCount":212,"integrity":"sha512-Va67icaKM+D843/j5Tr7tom1NcDZr1HFAMxKZWf3HqJ9clQ1D7/BaUR9y9xRABHvvhEAuyzL1919XR5Dd76HsA==","signatures":[{"sig":"MEQCIE1c5pL2UBN7jXOK2E0DfijX3WHNYJ3VXMxlBpM18J+1AiAV78hWL56rUWEXXiTN3QMVCQ6ChEBo9AfiG2M4xviPeA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":780912,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhuAOkCRA9TVsSAnZWagAAjCkP/3nGu1sT5X9kVg20zeqp\nWGOYNbNZIlpdB3H6SNs82jcSCacmxN6tFe7sy+x2RIiYT5rAWrnRkUoy3AHi\nt4CU2aDksn7r+1mE915yW+ZXEomaRjibeEaRG6VMEyFV8z5ECIk0eIOyiFb7\nieaYCfi61wBgDiiubTx1FIlfhH9coVgtxhkU/155QAaTPYaFpO8N3o4EOX+o\n2sM/N15sYVsIpmofJuiW1Kxb/elT93YM6aIlCSf+beI1O7iwnNChOiWmDzOB\nH8DoLC+CJ5ece2JI04HKhPMU5WOyqnTFFLS6KMMgKpCSc6F26Qsjb0aqC+Uh\nZn2zq8uQB1V4i3Xc9YUhSiEC1cbMBd+ZiZy6EwOjLdRiGGjKwpT+JPhWwyvc\nylVVPV7vXr24ZITJLrnI+gXbLo5/Y/CwrTOl3RXGOmfAETfgsnThnZOF5/VU\nmnfm9VxGlrhWWL1BGuNGoQPfAq7MMeaB8iHGcHb7iFI3GJw+cgEDssGFw7eC\nM3e/02OSjEq/4Pc4brkkGEMPBDoiI299p7NulB7/tKIO5nVKmh4oFVqfqaNq\na8BT9/MmjFz3k9iKb6QX64BhrU9cbBILvdnAbSILI+MnyJdz1dUIMOXn3AHI\n0b7ldqC6rdmmCy6toHQdhW3iWkV3Y8Wke2rXx4X5oAAh1XPrx2Mqjqu1OddU\nYWJt\r\n=egz3\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6e252068bab8bad0921948b6202b9a4189eeef66","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-14T02:35:07.107Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.102","@balena/jellyfish-logger":"^3.0.165","@balena/jellyfish-metrics":"^1.0.435","@balena/jellyfish-environment":"^6.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.1","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.39","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.52-renovate-external-non-major-6e252068bab8bad0921948b6202b9a4189eeef66_1639449508238_0.6140644493213876","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.52":{"name":"@balena/jellyfish-core","version":"8.1.52","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.52","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8526190af1e01f3b13dec6b7fa932533d3ea79a7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.52.tgz","fileCount":212,"integrity":"sha512-W825d+L9NgYFJQrwN9uqO2nRuIP+HUG1PxnpUQryrylfiILTclJxyTRLEU78g8xv3/RZZNmLmWTKCkJAEbG9KQ==","signatures":[{"sig":"MEQCIAUoZbEuLnGu3E3kpr9wQPJm7iYRowDVN1iE8LbJXh3lAiAwaOJR3Zp2wqdkaW8ajl5W/7Qakz6xs87wvSyp12ZKkA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":780843,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhuAVtCRA9TVsSAnZWagAAlVcP/RcsgPsmfuTpx/ZWGe5k\nimFQ0N7DVS7VBZ+Y4rpo4KrWFYNMoJNNiwDSsOjdRWBvqIXxCc2dLNUr6QUT\n1PPi4HRfwoiKjERNTIZDoQSdjz/d5ztePP4r46RkQIZBysuBpdyG1egPr3qL\n54ozNUOYJEjNANDorcqTKa9+BrQtHhKLtzpw/cuFjhceUbi0GmRg5AbbSyJa\nXP8tbB24v+Iz4NgAl30LJpHlNVsKhlvaBuRfqJyVZjN4qH+ij8aQTviGX1be\n+qa15kX0JOApzi1LXSjrOJT2HFBB2+z3lsE25EI1oHPVUI7VBZbdpoyMAKUF\naYlrWYYoGbu+ds+GK0PMNK9zvTilbpihb3lfrGWohxdPhehqmte0pZ6fjTkf\nQHUw1Xp0l+h3I6FgW2965y/TrhHUwmaYgIlUxnLmTQRMkJ6QuYsJzrGMMx7V\nvhg2M5LpMJD09nRIA0RCkRtH/BnnQ6Tg3Fa8LkM/hh+/ZHgyv8iuqXE7GN/y\nUzHtJRudETQFkkXFQdEa92DGIOA7jJeLY47kJMR5EPsB0XjATIFZqpCLo8iP\nD85eO71ERkVu8rKyuqvozCoCHZxmFW+/j2QUC+yF36RaFWGaJFcnmB8PKOEl\n6aBlV3aVSdbJAMN2YFGx8p5obPBswTyIRSLFnTCWaJJGAKpDd3WrY9C/NQlA\nfJYV\r\n=UN9h\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"659da88980209de7e91839d99f6efc042b756c2c","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-14T02:43:51.312Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.102","@balena/jellyfish-logger":"^3.0.165","@balena/jellyfish-metrics":"^1.0.435","@balena/jellyfish-environment":"^6.0.1"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.1","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.39","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.52_1639449965624_0.22576869254398124","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.53-renovate-internal-patch-a29e6d741c6d732251389c725ab3c99d09b912f8":{"name":"@balena/jellyfish-core","version":"8.1.53-renovate-internal-patch-a29e6d741c6d732251389c725ab3c99d09b912f8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.53-renovate-internal-patch-a29e6d741c6d732251389c725ab3c99d09b912f8","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"cdea363b69d559bd97277af5df3ea20d269c7789","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.53-renovate-internal-patch-a29e6d741c6d732251389c725ab3c99d09b912f8.tgz","fileCount":212,"integrity":"sha512-kSIrIdOaOgVO9SF0rJGdOXmSxaTEFa626tyGeCpXhrKyJNLx1fz/SUgigwL05kVQvVGRcAeey1OT8P/Or507Ew==","signatures":[{"sig":"MEQCIAo6prZkAQ28BYXRHGskSAW8A3o26yRqzGr7urgCx/CaAiAvuKo74cFld1y7mhTtLOxkECpz0Ska+yOdr0G1IxGJ6Q==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":780979,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhuGbwCRA9TVsSAnZWagAAdj0P/2CX0xDSeIt4sNEkUZ8b\nAxZWTK8wvgRuzBuZZB4DDfxsOeuS75NwvOejSMxb5G94So/roosdvim0xUHy\n+T5OoaF3vabRsAh/e78zq1WHt9NcJ7HeNfVT+1lMKiQUKttPgZwcKOkEhDsW\nwe7NQ2HsdH69eVmdCWVl0CWRfPIB6oJdZW0YbsWQMfwvCYDhshBSxtJKzofd\nVzTf5M+ZgTU/eTOm9wHL/EAtDzJojUIRbD3Kb0WAe9YnGDYYR09ptgQb6hXE\nnlA1omgkL7uW56pseuNyPKMsw0Jpz8ZD6USFuv2oz02Doimr5Agp3CPTVX0o\n8BGy7Z+/1366bFdUsgSdyunJvLJteioXqkjRBNoDhvP3AtItpygQUh0/GKaM\n/+JiIRYltSH3yit7d7EN6AX9F/fhSfIIWkPaz85fqVjZ3+7gAfBh+VAICm93\naNfUwutlkMSZxxHlT6m5l8rjfrpPkMFo+//q+ySLmaz6rcs8N8sadYPgZJS+\njXRoQat7RgAEHBQ3YtX+KeqPdLm3bXsCxoK2RBp/imxNexnEWazrcIzIF3yn\nARLFhUiVMbYo8qLvQHBa600iY/LdcYWmJKtNV0n4kUj9rHFyQVTI/OLR9tvY\nLWXCFrGU85hTT5TMAe1xKaL4wptpYX8BtI91z3ef/s8/eBA67au49XwQRwKl\nkV0a\r\n=1B6m\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a29e6d741c6d732251389c725ab3c99d09b912f8","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-14T09:39:03.007Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.103","@balena/jellyfish-logger":"^3.0.167","@balena/jellyfish-metrics":"^1.0.437","@balena/jellyfish-environment":"^6.0.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.1","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.39","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.53-renovate-internal-patch-a29e6d741c6d732251389c725ab3c99d09b912f8_1639474927939_0.9102071619497654","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.53-renovate-major-json-schema-org-1c93e21f53dfe6d3419120a382845f0a4ba60e7d":{"name":"@balena/jellyfish-core","version":"8.1.53-renovate-major-json-schema-org-1c93e21f53dfe6d3419120a382845f0a4ba60e7d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.53-renovate-major-json-schema-org-1c93e21f53dfe6d3419120a382845f0a4ba60e7d","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"552d3a3aff0771f8a87cc818d40b6b5170d5fd53","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.53-renovate-major-json-schema-org-1c93e21f53dfe6d3419120a382845f0a4ba60e7d.tgz","fileCount":212,"integrity":"sha512-d8up4Ztbx2TJ7dJfV+Gx6udI+ZwziQPP8m2gDTrKx+GhjuO4sPR0NrxY+FN3No16jAnhn19WanKF276xFGYJ9g==","signatures":[{"sig":"MEUCIQD51n7rhRzkJIJGmscGBW9w28ZDxSC8omE/hAK0JUWn2QIgZfRytLkBPrArcK9IDp5rURFWMVbLVCap4GtvLkdO1xo=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":781011,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhuGcvCRA9TVsSAnZWagAApS4P/3fROMRrqktHKncRB94l\nZID2NG+EHqFpA6eVqIDmjNFxW7Br3SgE6Tz502hCjqMp+DJO3ikP+mBVQcXS\n2IlwZ+Ogz0/4WrIp/vWkd/gHB2rFk5+xhXIVVhacp8ZWorLxr9/ZU3worT61\n82MzZT+2Lutmgl8P4T3NMZj+6g50TXtc3S/bMFo/c6kW515eYFn3cSpnIyHM\ntx5pJ4Qb493zjxHcLmuF0wLr107y/3AV3fNrzTHN8j98gtJhJqYBRYSI4/O9\ntLlwHysj4/P2BHgiaiyIy1bXKZnxT5BJ/WQlcg8tojW80OD4oA4lVs7+CF9N\n2Tais6j7mxpr0Cfi0iUvE0Zkh/rJGLhxbKCbE3txnNkfCE9m2XJXbG7wDY+W\nPoDapUYYS1rSMwyYALO9+bEpv7k8YkPm7rKCls0kGv/xc8WIh9v0+epxEscL\nMvTXRRU2O9mCOZo3hRIsaY0WDEBIEX+o65t1s/40utBcdrCOaI/ux+5OylKy\n57irOaRST30iPySmnxD9DNY5yri0cHW25py930V9qpk+2E/H+MhzZwJmwOIV\netYxnE3SJrWGuvGIpXbspIeYe37BfcZ1nmMNZoKdfr5n7T8j2ogFLlE0KLs7\ndgu80s2ZH85i1mOamNhhmJo7Wl/kyqcBJQ2/gYR68+rFlMvorvGwN1PNs6GL\nnwED\r\n=VLe2\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"1c93e21f53dfe6d3419120a382845f0a4ba60e7d","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-14T09:40:10.165Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.102","@balena/jellyfish-logger":"^3.0.165","@balena/jellyfish-metrics":"^1.0.435","@balena/jellyfish-environment":"^6.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.1","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.39","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.53-renovate-major-json-schema-org-1c93e21f53dfe6d3419120a382845f0a4ba60e7d_1639474991470_0.13304660749412078","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.53-renovate-internal-patch-1751105668a66a0faedf5e94e32f1063a7e857cf":{"name":"@balena/jellyfish-core","version":"8.1.53-renovate-internal-patch-1751105668a66a0faedf5e94e32f1063a7e857cf","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.53-renovate-internal-patch-1751105668a66a0faedf5e94e32f1063a7e857cf","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f2473a147a7dd0f5892c745dbe347321bd0025eb","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.53-renovate-internal-patch-1751105668a66a0faedf5e94e32f1063a7e857cf.tgz","fileCount":212,"integrity":"sha512-2agXMsKAyvD3c2QWmUM9vUgVlzDBUdLNnkjywfW5yoGqLw0dwPYkKGRejbdODDyDAbCB5m12Ka+ZYwJKOp7/VQ==","signatures":[{"sig":"MEQCIChmGPVt7FRX0GWKNo62tEP3nwulaI+2FM58blXZjHN5AiAmD2aq8b0zloAGX53taInUXDeeT/hX+1LzuThngkClOA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":780979,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhuGgPCRA9TVsSAnZWagAA/BQQAIVFBx3fBlZIm63tK2Xg\nwMsWFHvyVxak7PHmS9y+6d8OxgRFyHoU1BZGiIznZRgBS4AOYwvAIwXwOMaV\nidln5RQ4y+dckKvthK7Z5FRQI7sDCOarodpqnJOYiY8lh8hasqtYOkIGrUl/\nhjt192XpVjeBhXRgux7V0jKjKHHUlqChsM8XWWJhteFTwARWsiQfha6QiPxy\nJKfMQoMnSuEDzNXtvA305GTi6hosDt/kTDhu2PEZH2vsBiSEoz1xVtw2K205\nkLW09RQWq43wMnvfScKElTSImyNjibzyxao/z1M5APB/JBmGqpYvmzY8bs8I\n9MqFBy/efeTD2KT6MdHEkE7FfQszxT/1HIJoAboKHSeZCYsB1qHSSHbet7Ev\nVc8XybhVNvph2Ujk6bjsQ674YbnYWOo8b3zPejKHKiqFYeMoz+rh/WOyWnWo\nK7ZprirAA5GmYzSMR9Eol2efr41liToBXX+eG9ZQzFBqCvFp/WYHxMmfAynx\nFfmts9EMDPmF7l1Y17DaUtz9ARe/lBIrQQqcNeheyiSuCs7oxuPIxNPeMigd\n+MuIq4Uh+zDswG/O9+y/e0WHCtkdv7eq7dKMsl08abucP8NzPeQhMeqGh2q9\nwnKhekhRBfmIW7nw0UAoNzo12+c2++EpwhA4Ond15TxHfIikJ109kNUMK6XV\nLRFI\r\n=zaZ8\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"1751105668a66a0faedf5e94e32f1063a7e857cf","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-14T09:43:00.538Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.103","@balena/jellyfish-logger":"^3.0.167","@balena/jellyfish-metrics":"^1.0.438","@balena/jellyfish-environment":"^6.0.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.1","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.39","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.53-renovate-internal-patch-1751105668a66a0faedf5e94e32f1063a7e857cf_1639475215590_0.7217958192696992","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.53":{"name":"@balena/jellyfish-core","version":"8.1.53","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.53","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e7285dfe975d86c8cfb6915bbe03e0ec451f0acd","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.53.tgz","fileCount":212,"integrity":"sha512-OSOWqP3qaCs0+/TrBS4gXBv6b0WRVx6QkRK9wqNhyK+//m+dzTqS6uJS50bn2chJeh8KuSp/1gAMiU44QtuDDg==","signatures":[{"sig":"MEYCIQCtAq8aCRfrIYDE6ZF3mZ3ANNvTKHlXeZqWZIVFAKLquAIhAIegLjHcMKGUSVL1AB551uso68b+60QzRWPXCRyOk+QB","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":780914,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhuGmJCRA9TVsSAnZWagAAbWQP+QDMOIeqcIcWZXQYD05t\nJnys+aC+Ih08KBSRtncsGAbtvPdX4APgO3F6ogpHBBJfBCKlumAohdEp3yYc\nYx0hXohZFySHrUKn/zMRjBHIR/CKia8wfuAzXLNC4M/tvdLrk7stUCRikNY+\n3ttv3PnGMB+u7M7myX6woQy3YrQ+5QkjZEhRxH//6i5Li8YNiMsdQJU+TRru\nspZ9g5ghCMslTl55BqOemk6V7zEfT5PH46gPbnXiN86kmZeo4HCXq67XnTSJ\nqy8VwRio15ssGOJA90lSKlj04j1qfWPwL7MeqEsqHXB6WxkkcCZH65bjxJfv\nMG2yas/1tFF29pVKRu6XYpxsC0A8pgz9HyhDme6uvWNWrkOzCQHIeyYldLdR\nt6QRgNhbAKZ0Hl+FOY13lr5v4UwaWCfCRiwzPhnaDU1cOBd4JvPULRuCsOgU\nXOVn0uumkiuLOzG2TbegLCH2ibZtH2fObdw22BDl0y0AaYZVrsFILXAzESa4\nSgPBTgZAzjisNO/ynqKgsGe2N3LMB6BqZX9Fi11QlwE8dajVVj9pyAXMBJwP\nF3bQHn/qmIGgfLJgHy/oyfYpQkuzlBHJou9jY+T9O4Nogl9G0gmqJbsEzlO/\nttkXixZtiEPslYDtb4UPmGlpS5ngA/oB0feutRlN4roHrlCbMgQNBhSrp7t6\n/e7t\r\n=C2Ho\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"499218f7a5eea9d00f6ccd8902c94aa0469ba14a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-14T09:50:53.302Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.103","@balena/jellyfish-logger":"^3.0.167","@balena/jellyfish-metrics":"^1.0.438","@balena/jellyfish-environment":"^6.0.2"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.1","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.39","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.53_1639475593779_0.0027937665820896296","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.54-renovate-major-json-schema-org-c15eed885b524bb35982575797e3b83a40406d7a":{"name":"@balena/jellyfish-core","version":"8.1.54-renovate-major-json-schema-org-c15eed885b524bb35982575797e3b83a40406d7a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.54-renovate-major-json-schema-org-c15eed885b524bb35982575797e3b83a40406d7a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"22f1cd5bb9c5dd3d1479eb50309262607c313e66","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.54-renovate-major-json-schema-org-c15eed885b524bb35982575797e3b83a40406d7a.tgz","fileCount":212,"integrity":"sha512-m2aQjz1Y4DB0NoJU5yckRdmQmQjodL9SspnML7OTBlRrt+WCMAjpni2+7dQ+hPoCiNP4AnHvww7GKGuUpEHw5Q==","signatures":[{"sig":"MEUCIQDuuhclQ35Vo0SIEJY1t2PdYvcAn+FhRz/FAVDbWCAT0wIgQTfSkiiiHQKEoctCrc/+Eef0GwT40gNn/BrOv3dx7kQ=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":781082,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhuGqfCRA9TVsSAnZWagAA3t4P+gIlcHGXGBlnVViPau0J\nU+HmtBk/uvM8Vwr86TePQT+Gnecdj/KI0E0ykfMUpBfcVhVyrwTgunZOYFGH\nxZgS4VMM+mCc79dhpERdxHNWqz5jPVEIv4HKg8mtIJZd6lffUfYbWZ/P8WQu\n+PuzSgVh9jE6e66agRGxn15aHu93OHLYWwMEjOsWxDFfH2jyRdC2CZZ8KK1u\nWMgZpYF1Fk023FB5V3qR1Sgr8DgWfNM3mp1kP1eloOyGcnektU/9VJEMq4Iv\nu3gTToLeOho70N/oNL1fvSV4KqDhgCfTurLI5gMsNamHnciBadebhkcCnfEe\n1kwVMTecqvIRODjAEnpYof0tMQ2QsoUIFdpee335cBieUEAj8ypNjpgQlbzN\njwHyoM7qj1jii7sZs6TMZ3noNaeCYjxIPGWKmesTyJelzqgJrYA7V5aKkxx2\nsYKoM6NErlTM3apYeO76piTsqlFYsQTgQGQKGAgZ+zzUS+tHWUzvezgGM3IO\nOnic7Ux8eY6Pb+QgGOR5bGOPKZz/KAnFsbHID3AL4xFXxqG4yqmEpTmjsCzK\nVdkXE7jY2LZd3byZufn9dw9T5MTX33zqmgEHrzyGfxHZtKIGBw98bg2uUNWE\nw79GL+WK5MRbeELLSqGVembGkyRBgdv2y8UB8fVAFdSonazM0LXoVyOkeVmF\nDGL2\r\n=Umyh\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c15eed885b524bb35982575797e3b83a40406d7a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-14T09:54:47.767Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.103","@balena/jellyfish-logger":"^3.0.167","@balena/jellyfish-metrics":"^1.0.438","@balena/jellyfish-environment":"^6.0.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.1","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.39","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.54-renovate-major-json-schema-org-c15eed885b524bb35982575797e3b83a40406d7a_1639475871219_0.7061031682786274","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.54-renovate-balena-jellyfish-assert-1-x-d3ef3372cd00a96b138c13f6786841ad9b63ee04":{"name":"@balena/jellyfish-core","version":"8.1.54-renovate-balena-jellyfish-assert-1-x-d3ef3372cd00a96b138c13f6786841ad9b63ee04","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.54-renovate-balena-jellyfish-assert-1-x-d3ef3372cd00a96b138c13f6786841ad9b63ee04","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2b1bf9111a407ff94bb07bb5ef550ec896510add","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.54-renovate-balena-jellyfish-assert-1-x-d3ef3372cd00a96b138c13f6786841ad9b63ee04.tgz","fileCount":212,"integrity":"sha512-l3MBcQ3ktEcQq0vrC8kqXOvnK4W1ARfctVOsVJ/bX9OtiQSqC1bx2vGzqQLR/dr09Wvyb63b1zWvJAsaDScFeA==","signatures":[{"sig":"MEQCIE7y2Q56M0ImTb6xMDcXM8eH4nzc9e9DVxqvKnhcXN2AAiB9cM8hSjC/iE/9uDULMMuBM2C9/7JX4dPBS3Tn3Ap7SA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":781092,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhu4ltCRA9TVsSAnZWagAA2zYP+gI5EavnqhDcOF2h/vKk\nXmClU3K0KLzdXXOeza6vWYzMNvv0USgS0djsg6Bgr3rzS3/DYejX14nJoDyS\nPz8agJSqvhNIwDXyi1NP3qAhloS3u4XCCAM48RjdEgc7d53nHJAlEsqQHaKw\nSrJm/4nkMMlpK7cRgSX4BbNaIG0kQqSFHKAE5B2xobq/Z3fmtIcsRiS0wcAS\nRtIRkthw85gTnX6/alSrTLWz0eA2oh9NXcQtNdcN3QgHef8fUXpzeOM7EP4+\ni4angO+dSgQsF6HlXM9zIlaf5FXtvwOcUCtIm7b2kq2rSHYwGE2X+N/XnqpC\nUEq60/5PZGD1L5ric6tAQulXsFhTg7ax7oF255JMvrSoF6iDpuLjjw0cyytF\nkpKSCmHQA/uhlm31e3ae3VJPh+qUNyDKZsRhYwU7NkxWe5tm598fMcyhQnNG\nR+sPzT07A0wXUWdS2jAgoyCyJ0fLm/vol6omGE8kpYY4mKL0JsAytjlwzrrA\nhbwIpGVO6OP2mtvCiWtGxkWyLi/mOfPfUAZ3ysIwQ4oBcPG4J9wMluAHqteD\nmz3gHCuqnjQyBivjkl9HXGwdFTgHUXzKzRe9SlXVGhnRj3/xssRFAwHeRMlk\nuSCfyf2kyhQdkDXE9vhtXjoGUdHfBILHHOKWrAJdKBu1kXvzU90I1eb0aBNE\nPoLO\r\n=ETRy\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d3ef3372cd00a96b138c13f6786841ad9b63ee04","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-16T18:42:44.182Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.0","@balena/jellyfish-logger":"^3.0.167","@balena/jellyfish-metrics":"^1.0.438","@balena/jellyfish-environment":"^6.0.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.1","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.39","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.54-renovate-balena-jellyfish-assert-1-x-d3ef3372cd00a96b138c13f6786841ad9b63ee04_1639680365338_0.2644612484364188","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.54-renovate-balena-jellyfish-logger-3-x-a380ff651383ecd0c3977d69f5fec037c86f40a1":{"name":"@balena/jellyfish-core","version":"8.1.54-renovate-balena-jellyfish-logger-3-x-a380ff651383ecd0c3977d69f5fec037c86f40a1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.54-renovate-balena-jellyfish-logger-3-x-a380ff651383ecd0c3977d69f5fec037c86f40a1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ba94a6cf405ee1c2cf736e8718b4f4329fc57fff","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.54-renovate-balena-jellyfish-logger-3-x-a380ff651383ecd0c3977d69f5fec037c86f40a1.tgz","fileCount":212,"integrity":"sha512-3DJ3gfYQvIGExm4fObO8LMj953tFua6MO95/e+/9LYh6xV2x4gu2dE8fseqsFCj1awHw1NUEpaBTTO25/aRTWw==","signatures":[{"sig":"MEYCIQD/NCSVGeITSJEfKDYu8v1ASQepplocqT/OkHRu+9PhDgIhAO1ZNAcOJN2veQ8f9jUEu0VX8kt+gc8c8s9JvK1Ghmuu","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":781092,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhu5XtCRA9TVsSAnZWagAASIUP/11P+V8H8hFRAo+N+KzV\narNSq6djacQJSPI11X8cl3kzKfdSdAMk1mUnhnKbrWLCk6HqOQPMP2ko9RRm\nChYLsdpiMAzwE4ODh/JvT0QiOVeg3v7DHbqDTcAz8Lss255AcH8Z3BkkooxZ\nAd421LQ/IAG29iDbxvO1fqNrdvOT7a537thtpLtjxDJgxPuYWd0muJsWtWhd\nuA92mhBDFytKJMnDvwTHcLK60Fi6Ix5d+KwTZKpGqCxSgQ5ttcKnTrInP0p7\n4k4AQqBwwQeBZQlDaWVPLLIm3ntwgpW5ytHWkSxDkVYolPvlY5haUUhYxUE2\n0zVVpLWTKGNdTlyPdgClTdS1KzkwCmXNmoXzkwkIkvfm2CaA2FVHncduX4e8\nJjS1mr8jGfyofbSGRghCbVTIjK7xX83pfH8KMWWEChFT/rmkRuc1A9CWv44h\nzXQP5WH0tTXvLH5NUMhrnSKRBTVqpr7MAg5j+DLz+IdWVVRhmCHnSZGavRxM\nx02znR8QRkzlqyQ8mUEcI7U+7WjynhApZoLsIAamxlpYvJEc+V9FUSY7r0Iw\niS0oQUksCiivl0LSijLDc9anZ2903rltiTYv8p7rPDacQStG2VLxykNddIeP\npAztJ0vQvfJDQujtB+hfyAIIWz8El2Azkxjwe2lyVbjj0Ob8Hst9EKjycdgD\nQVpd\r\n=ucBL\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a380ff651383ecd0c3977d69f5fec037c86f40a1","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-16T19:35:06.984Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.103","@balena/jellyfish-logger":"^3.1.0","@balena/jellyfish-metrics":"^1.0.438","@balena/jellyfish-environment":"^6.0.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.1","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.39","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.54-renovate-balena-jellyfish-logger-3-x-a380ff651383ecd0c3977d69f5fec037c86f40a1_1639683564842_0.22951214147402488","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.54":{"name":"@balena/jellyfish-core","version":"8.1.54","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.54","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ff9baa8b633045322e1d93ce7db4c4c70e148df7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.54.tgz","fileCount":212,"integrity":"sha512-5PGmcihH1DtIjYDxuIgBOfZKyjx1gK1W6lG+7nl2f4nDLRSyKAlajk4YNk0VboDQdbEp1qjA0FYY9qzg8gS6iQ==","signatures":[{"sig":"MEYCIQDg4twfGNtdlVWJhEETlWh6oNxisXmKZPQFiXQoZ7ILnAIhAMyULCR6i71sC/7hqEtLtl3qJ5QLH9Kuw64Y92yyuW6r","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":781014,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhu5f/CRA9TVsSAnZWagAA9fEP/RCndR4hUGnJdwQMhg9w\nBaGaeWrR2abXMiNc2UPIVvxrNpmvwmW9mW7RDxsHa4+ICAHn0AQVhQi9WEGX\nyVr82pShbLy6TJoqZDc99E7UiDjboMFy6tvh3bXB6uPUe1u+FzzbAbw8ufY7\nj2dl4N6jRtUgZqyBay2/dmCMSbV88eIjuuQUr/VpjLcVWMvDQy/gzJt5DF9u\ny8mKFkNnLzVo9DGI4/4z2TPb2lJXpAKN46y8AO+w5CmX1cyROrc1WnH1IKQ8\nRoSYp0oFRBvwjJgIJtwVOVFS/BUMVug1lVyArjgsMRkr7a6hVK7DGyLFRo6/\nnczSqB0uI3LZZ5mfAY33HROAafppgzuDYopjkdMiub+vs0zKCfNRsQjbK7XQ\nYD4kU1erOjOeNb99ydUFACdpHV3gsWKDtZT0a5eURSX/2EDTeZC03cG/Wp2w\nM6L3jsQOAiymjJByCDHRD6MgZeCS3BQgc/Q1TRpk5O2VGgDfLIGjE438SRC6\ndxCyrEjkZVg42ZXs+qW1ThrFv7MZitJE2ZQZwQvY5uEAoFK1rmrvZaGwMRpC\nMlALJhou4VZ1EIyQjlskITPbWLMZwCWpsX/6HUW2ecXfhe+2w5vVFJfFqvWn\n3MRWwyzq3osFWQB5TeK7/3AGpcY+cZzs6q92fNfv9az6DanyFJTrV12VX71x\nGD39\r\n=/NNE\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2aaeb4ca48b92fe0e0d5e8669c0409da0a81f9ec","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-16T19:45:05.487Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.103","@balena/jellyfish-logger":"^3.1.0","@balena/jellyfish-metrics":"^1.0.438","@balena/jellyfish-environment":"^6.0.2"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.1","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.39","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.54_1639684095141_0.6742535834979146","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.55-renovate-major-json-schema-org-e48da5d2589edcbb1a8084d3e6a2645d7758fac9":{"name":"@balena/jellyfish-core","version":"8.1.55-renovate-major-json-schema-org-e48da5d2589edcbb1a8084d3e6a2645d7758fac9","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.55-renovate-major-json-schema-org-e48da5d2589edcbb1a8084d3e6a2645d7758fac9","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a6fdc15d98e5e0930579f46c2a74d37d390c60a4","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.55-renovate-major-json-schema-org-e48da5d2589edcbb1a8084d3e6a2645d7758fac9.tgz","fileCount":212,"integrity":"sha512-l/E9C4/9jTL8XsyOfzgsE66+Cv9R2ptCvqsqaPdRadqdgG2y6EpNr7V+7PL8emLy/glb217WXTYdCp29JWG7LA==","signatures":[{"sig":"MEUCIHxz0hPQwbPuv8AM0nrGhAIUL5/L2qdictzRJPuBKsuTAiEA3h6GqbKbaHHDUC6j5m6MnvX4mbDufKYh8sN3FlkTahA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":781182,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhu5jRCRA9TVsSAnZWagAAOGYP/RzDvGA0sP8kOvJ7K9s3\nxkmjln2ICGtv6gyiW4K6pj9g9crxW72rER4IEe6JCU9ipGGdJojZ8c9dfVFt\niZqvO2eNIfwfnAmjI06bfWLhSqDAMd6rkDL+Zr88QiYUyN1EERD3PsMtDs4N\n7JKGwF/atJ+eG6SzDeGUDuxm+jbUqCrfsd6G8w4ug1jdAgCmIaBzp7UgVsv2\nzk9Ow6vp7WlRgobpId6Vw/XPqQzlREgb8SZxFWsXPpJMs/jSyFymRwzLSjZ2\nzcgxY8+KfcKZCVQpc/QHXrYCMuozFVQXcePEpeLLL5ZoJ2pWweXMkWEIYtOT\nTwB9nqP0gCK2GlQqYlxJacfPr41zS1GAszNOcfNfxQQSGVBg/3AL9D21Splk\nWVMq7cAMCI4w+qxdTou2gaAd+dGGCpB1H5o6YuvkQkw4GNzKESHTHfZ4Bcdv\ndggd6EGbKY5mMvZau080mK6OCdD/gY4OdQYyS064tZwdrlWYCMaPDAS+OayC\ndifgeYC+YawAHvOnAyDpENqWBViCHbniNQHPYY4QdDtJXx49SljF3g5ljkGo\na+f49cNCiQmrCAiZKrVn0PLZLZfYFXagTH8/zPM3yToQLbl/nopWXnX/oHip\nFJeiiu+jQTsvP+k82lW2BdYlkaiSJAFAu7RcUnTAPs5o9m2YkAC/85a1idHu\nohq/\r\n=XHy6\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e48da5d2589edcbb1a8084d3e6a2645d7758fac9","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-16T19:47:38.453Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.1.103","@balena/jellyfish-logger":"^3.1.0","@balena/jellyfish-metrics":"^1.0.438","@balena/jellyfish-environment":"^6.0.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.1","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.39","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.55-renovate-major-json-schema-org-e48da5d2589edcbb1a8084d3e6a2645d7758fac9_1639684305202_0.9274580334421016","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.55-renovate-balena-jellyfish-assert-1-x-1625e88b622866682e4243613990146bdb8d09c6":{"name":"@balena/jellyfish-core","version":"8.1.55-renovate-balena-jellyfish-assert-1-x-1625e88b622866682e4243613990146bdb8d09c6","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.55-renovate-balena-jellyfish-assert-1-x-1625e88b622866682e4243613990146bdb8d09c6","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9fe0e7694b7d8c76bb7358d9fd2f522729a0f029","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.55-renovate-balena-jellyfish-assert-1-x-1625e88b622866682e4243613990146bdb8d09c6.tgz","fileCount":212,"integrity":"sha512-fthSjYddZZUK+bj5Yn4lkqhZAyUfFD7oTosre+p5rcm8Z5Jeg3ElWkkR1z49js4rV96DFlnA9saw4sIQ6CEiGA==","signatures":[{"sig":"MEUCID2HMcWz7tot1vuuRScaoNkLZ9aTtUpgbNZyIrdHfoGKAiEAktewjMjTqzMD4KmJz2TXXYX/Cac9+Xgk93mKly5RSPU=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":781192,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhu5ksCRA9TVsSAnZWagAAZi0P/Rd6iI8uNHVBtImZina9\nvHjjPaL5Wtq75JO1lh61qvp6AyXyakb0upFztldEptdJeBvRyl3UDhfwFEv/\nKUYiDQCYEgs6aSiOumlZfEwt9+o126P/3UIahhGUE+dQZMI8EQCQU1ChO/Bg\npJ04L6B/+WJDpwX8L2wjBFdxB/chLZk4NUohi3Ewz22UtH3IJcCzcRaOwuPn\nJd14GHqGSUPECy3pSSXA6DLIgTaRXk7GmRC8AsVN7HHSsZq6imH4ax5xQK0E\n9PgqSoT4vIv/CHASzijdfjxdzo5YdolD6vHzGu1ez/q1f3oe3q+8l4pWGx/V\ngBpu9W2UufZB4PvQMtWpo+4e6k3xTmWVOa4+GnUxsf2NUN0BK/CmvvW5gFhB\npm6mewid6dAZZ7zAZG/5uO9FxOKPiZeVIETTRpV14xX8+vvWs3wETM05WArd\ngencr/m3bn09kAq8KPpoQpx4yPZVbwzPyMzNPOChV+XDUp/5Zr9FHcdSKFfq\no0qTFA4lJYuZnqpg0fa0sIS41WfgbPpMXad2f1yX+zlUN8XI94ydE/ImEGFx\nY3P9aWhFHi9sBGDHVTAMFpvxu3so7iBVihg7OFgiTpBN+mQ08OibkIs9Aukt\nYO2uEJDPLslDzOaDhlJezEg1B+Rg06LlMnTsD58rI+rzUwueRPjHhc+dQvka\niYHY\r\n=DVx2\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"1625e88b622866682e4243613990146bdb8d09c6","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-16T19:46:51.523Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.0","@balena/jellyfish-logger":"^3.1.0","@balena/jellyfish-metrics":"^1.0.438","@balena/jellyfish-environment":"^6.0.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.1","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.39","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.55-renovate-balena-jellyfish-assert-1-x-1625e88b622866682e4243613990146bdb8d09c6_1639684395933_0.8784629126825008","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.55":{"name":"@balena/jellyfish-core","version":"8.1.55","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.55","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8eccc18882fcfe1060dbd4fea79b81290a73a84c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.55.tgz","fileCount":212,"integrity":"sha512-O2snO/XwX9HhOHvw4Pycs6gJk/Gn/N4EkFNrVgP9SBQxflggqQxtGkDGwfPDwsDEi9y/MSTrtR6dRZ3OuwovVA==","signatures":[{"sig":"MEQCIG6JbGectCsvzQH1zuu9+3b5wkC50nILSmmn3WyWXBfMAiBtLsY7fdgi8+dh4/XU7WyBgFqJSt57svOlAzQXprn9ug==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":781114,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhu5sGCRA9TVsSAnZWagAAtrcQAIsy2hnKVIHpzygB2i3t\nypOMwAOiSMD1PotpjHWainII0mZ5Rjp2zuk3kqOdWMP9ZzDVpMmCdYTP+MNb\nAYKcLTB+eIWiL0tvcXc/BO7MRpp2FQAfq7sTDH2vUuTqFRF8YNuVl7uU8Pci\nw2qPxFsh+Uaovdm59nD7GdUi9KDwDWT3PWmZQKHTEtGc2a525cwwlAsW5F90\nzse+KT2k79D14WTdypMwsKFKLbPyCQWZ39Md7TqIk2jmIBr2H6CiCa8lICXM\n3Z4rbL0AK81Mw4GOMw44h7rL4tx22KUBx5AAob6UFG9eB45Q4Nq256WGG2a+\nSn25dMpNzxec1dOX7Kb9fyJw/My7sIWtJipiXAD4ZHpublm2KXKH+pCCAtjG\npii8JWQG+8isR/ddfv38hawXfBt6/lPuCUJ0jtn3w4xP3hlYpQ8bgHugnsuk\nZRljbCK45yPc6nvdP+uJsBIKpeN+oINYvVEe1wgtvTMVLSnycgjMDmJ6ZufE\nvsniWylVyMMdp1RaNaWj2c19PASHWH/UKX7T/XCX2HxdhrwrdgpjVjCx9VcT\nhBTz45ETr4tPl1kK77mP8Y/3WO2KwBHZvpphoEQKxq855jfl0vIaYYwGOtrx\n4aeOu0U+grAAdB+K3xAxo0fHZ9ALgj/vCaKIMKHyeMZ7Z8Jch8iewzbW7AsO\nLmI/\r\n=sQP6\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"5f0cc2468b044d7d005f88ed4d91fec0e573b6d7","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-16T19:58:25.647Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.0","@balena/jellyfish-logger":"^3.1.0","@balena/jellyfish-metrics":"^1.0.438","@balena/jellyfish-environment":"^6.0.2"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.1","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.39","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.55_1639684869962_0.40763790030755476","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.55-renovate-internal-patch-84ebbb75a37208d9ec106f9104f37bf6db07abc7":{"name":"@balena/jellyfish-core","version":"8.1.55-renovate-internal-patch-84ebbb75a37208d9ec106f9104f37bf6db07abc7","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.55-renovate-internal-patch-84ebbb75a37208d9ec106f9104f37bf6db07abc7","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4b7331973becf86418ac0243916c3e8b5859d930","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.55-renovate-internal-patch-84ebbb75a37208d9ec106f9104f37bf6db07abc7.tgz","fileCount":212,"integrity":"sha512-ZrfUqr3PDyj6WbOHgQ898k9SLOKO5uM3jp6msburWM0+GtkENpLoIrX2dOiGwy2rd2P3OcJmz6C2G9og8CdlDg==","signatures":[{"sig":"MEUCIBo5LhOnkk3NSj5pGU5fhkrFjt/H/NgQeZ9K/BCcgq9NAiEAgCSX2VwGoakkL2X14nnXdKM10+h6dat4psFkh7zLxVY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":781259,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhu5vbCRA9TVsSAnZWagAAAcsP/3NUdL6Z1CV0w8DPMOHl\nUDp5ET7bn5x4s+/SUXo+Eu4bz4XJ9Q+QHSIys4CsvKNKvwfn7mPV6/3aotlP\nl8PZgWyUIvneRO5yQFyviO6B+0a3TO3zpR1VwZsDNI2uuSKc320C14sPBTTc\n1IcqWiw8UjKRxrddTLDnuRQXtETsTvG+7oX0NBYua0uFl0ad5jg8+xQDJRm8\ng109jg3Qdkvti7F54ULvxseAEC2tNRbG+ORWxlXq4CKwODxFWXpQ3jm6i00Q\nye6+xLU1/X/t8OAxdq1qXFX1MPTcVZja1fehFN2nFA1cK+X5vGWm5IjSZy09\nqQEqEMGTpjBwRCoo4XgtuoxBsv2XFMmVeP2NwFwLp6LDxlL3zI9ieGZiazDT\ngC43Wn+4/EbsTjKOsUXEkjWO0puyuipgZez3DbxAA8YHYtgDiZd/cDXRNt1m\nWIetGTsqZhic/2tPJcD5ZtrFlIjqHggKiNv6XLomsIpGGOMSJkJB4t9fM5r/\npfrvCfBb2am2U8pzvFQZY+Hg/gnCzA4tbzjSYEzMFZ6+cpTnpgpNeSfOEMW1\nMVCf0PqD4kBjXYt2ISTMCZKxL6/i6OXQR39Q7lYkIrkyhBk7rldsNjT0hD3x\nBgWbbBjYNTebr5Jez/8mVxEK6fgA+Dlf/6fVR+50RDhNVXzfaFxlDOuZVQNa\nzwYr\r\n=hUAe\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"84ebbb75a37208d9ec106f9104f37bf6db07abc7","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-16T20:00:16.056Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.0","@balena/jellyfish-logger":"^3.1.0","@balena/jellyfish-metrics":"^1.0.439","@balena/jellyfish-environment":"^6.0.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.1","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.39","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.55-renovate-internal-patch-84ebbb75a37208d9ec106f9104f37bf6db07abc7_1639685083389_0.25821865762980667","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.56-renovate-internal-patch-d0f57de2b2302d8e76c04e16778ccb6f87de9c8a":{"name":"@balena/jellyfish-core","version":"8.1.56-renovate-internal-patch-d0f57de2b2302d8e76c04e16778ccb6f87de9c8a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.56-renovate-internal-patch-d0f57de2b2302d8e76c04e16778ccb6f87de9c8a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8d20a77aaa17f8799fca3fafae6fc70470e476bb","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.56-renovate-internal-patch-d0f57de2b2302d8e76c04e16778ccb6f87de9c8a.tgz","fileCount":212,"integrity":"sha512-8jWvsgKf7McZi5iynoSdoECzEsGm5v/5Ytjl9DnycdXbM27fj9pH91DsrDa5Um5xPi6+PtiEkRXPaLcyi23qZQ==","signatures":[{"sig":"MEQCIEh/cOfvcL0zipmhz4vTv3vvsyXTZUkWVAKOrEPQ7Xf/AiAMMOAhzUJu5wQauLm9i0ITyJ5Vo9pcDzGGPFSJT0nOyA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":781284,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhu5zsCRA9TVsSAnZWagAA/usP/3dGy5uY74UiSC7ZjVtO\nwvVrYFRKVK13lcv10qXnMfVhV6ywt6FAi+M3dnF6WVWUs5co4JU9rLa0eEwi\nH2AUYNzt5ktgGGy8QWXNQHd1TfSrNNxQk0OxCqAeF2VqmKfy6Bpq5n8hBwcU\nCKqm+a7D6SJ3eZ86aq8Q0YIlvLm+FEIw8S5ADRWvZ2Xgdmtl9Kj3TSV15Nh8\nmdtsryMnO4CEoAwEHksdNzGQ2juYS2h9TgktQcbHh+pe6mse4FpRcAu1F8Xj\nv4pTdhrXr4TSWykuR9/h1SbGCelJ3N4xAed7xjo+5fwCYnHA4litZcJTDR1B\nCti+dEkaajHlTXK8EEevirzzBKGLBXPz1cnwZ6oqqPt5rTaCA7fGCu/Vvbsu\ni9nElYx7AXKAU1qFWn+2EOkH1JA7DKP4ZXPWG08u1RRqRp5SSmplpvc9EAKl\nED5nnDt6hQeVzmrxvZw07BhWp0NhVk+uY20EsEl7GKqgGGV4oIYbSuuWfApP\nnUQstw6o8gFFtuHgVmdXMT+7I/W9QDnyQA9AviBciOqFbF887rP7x5zet5FI\nDIgmndsqMu4cBU1hYH1P6SB8ge8DBrZYby45l9qVYw9Z4g59X+8kXUKDVJpr\nsrlNiF6hey+s952y0s7wn5RgflqDl17zI4j9ba1fLn4tft9+X4vgEK4iSzcc\n+znR\r\n=a84V\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d0f57de2b2302d8e76c04e16778ccb6f87de9c8a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-16T20:03:20.766Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.0","@balena/jellyfish-logger":"^3.1.0","@balena/jellyfish-metrics":"^1.0.439","@balena/jellyfish-environment":"^6.0.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.1","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.39","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.56-renovate-internal-patch-d0f57de2b2302d8e76c04e16778ccb6f87de9c8a_1639685356437_0.8501191223832103","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.56":{"name":"@balena/jellyfish-core","version":"8.1.56","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.56","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d5c81fa0d97bc04d0335ab5b0ab3cf87411ec649","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.56.tgz","fileCount":212,"integrity":"sha512-LY58J3cLTHq3GuVq0Zu66a7sK+Vcgu2qpKTUQ6Nazy1v2/c9pwb53XndtO+eYw2UJVOZJ42ABGg0ciy+CFbfeg==","signatures":[{"sig":"MEUCIF51LsVnxV5Hf0Oe/11YVP5VKKrSlgevrXPmf2j3LrSeAiEA0lLkVGcqYtKxAdSXvtRNhQ3n5JnpHzhcqHwydU/lmGg=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":781219,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhu5+yCRA9TVsSAnZWagAAm6EP/iPYwBeI3IAkXu3LOno1\nlHn15HiDuywFgLR0EfVKwGnQpBDz9OvNKPhXjK9/FSKZjPJGhm6GPNViduqk\nwH8YsBMN6IJSiFDIm/WK0taU/qc4kXmuauJg9GbUYs7Sh3VoPypU+QoZseaw\nPyFq68/B/9iBmwj4ePlb13NW/JDHJBcVY2/UgAonnw8bsCVtoWArnKjJk7ZX\nFRJZoNDflor8fSCPBr3cpsAbcu36DMSnS5Z7jDSV1vj6oNUgJ7aZ+BNjBTQH\nnuveX+ZUhMg2w1SJfv0RJEOfDQ7gsJDldwOGL5UfGD0aw7rteg3+hb81V4bv\nwLPxQxQh2HibnAi3FRdIG2RH9AISu57ebkGYYA5taBMuY5KtCf1p+1bZfQEd\nfzGkPQiVTKKcXhJZkZH+xTxv0StbHR57jR+L1MK7Oq4o+II0bdy2MrzenBTQ\nvtUytnj/x4vn6f8GJiC25MdgKbezdKIxZdWPH+Qbo5gUCHkGkrAv1CixB3T1\nIl4vMT3aG4MIywWWf6gnOd/sUjRX16twehEabeOMqJjuYm1ppBEMWGRZWI98\nrCYHxJtzRXxlcp3nDYzbPCTbRQbwq+/vWKvWwlFT7Wat2YO9y2m1LEbwdQNQ\nCZxNyu6XSsrZWaz0ddi3AFqWDicPmLE2SwMFcHHz1+xwW16DI3x3hk9Dijqn\nghIq\r\n=ZpP7\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"05b8d0f9fb031ff2058ed272c69dd0932a43c9fd","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-16T20:16:52.477Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.0","@balena/jellyfish-logger":"^3.1.0","@balena/jellyfish-metrics":"^1.0.439","@balena/jellyfish-environment":"^6.0.2"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.1","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.39","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.56_1639686066165_0.9026129760218553","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.57-renovate-major-json-schema-org-844fc0fa80f0f7ff6f139e967057da26d618024b":{"name":"@balena/jellyfish-core","version":"8.1.57-renovate-major-json-schema-org-844fc0fa80f0f7ff6f139e967057da26d618024b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.57-renovate-major-json-schema-org-844fc0fa80f0f7ff6f139e967057da26d618024b","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b5214003f7a7a8e7c0ef803da2cadcacade8a1ac","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.57-renovate-major-json-schema-org-844fc0fa80f0f7ff6f139e967057da26d618024b.tgz","fileCount":212,"integrity":"sha512-ltIGPxNu8KsTR14yxVmc2CSAYBvaFoCrhbzPUOMc2/nWLSmojGbmvqWZBl+ziC+vbl8CopfILE/dtrY6CW9N4A==","signatures":[{"sig":"MEYCIQDk+8PMMeh5qaJ+LnVfP6HLvSbWj7+wx78XeskyrIhLAgIhANWaVHjvWscUNsoYKxlYQi3yA0jKbeT47z0u7xkZJ/75","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":781387,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhu6UlCRA9TVsSAnZWagAAiQoP/3qjfxHhJG9c+45N04E1\nCLhXKp+gElA15ePvU38PlLowE7jIkVnuG0QFwcCYNB0myQLzSmhNkg1SkVeg\nczweb9TJIt1mEENqJXQfpcqALtg13Z9QFv3lYNqwz1N/WcBzf231MYLFveoN\nazfUaWk8GAgepicsnwtO+qRhJTzxtkbEiHtumVYp3REUbJeUdQyzujqOni3/\nywrKzNqKxJ1659jaIG32WtWMBRC/cWFsWlhlm3MMvRwQKvAtem5wlm10X9YR\nSB+3z+Q7fTvYIF+cDheCYpXnE+ZYAbxzQV5bacT8ZRdRwyOHh5u5FRYwLHxC\ngvhwODbRB2c2SFobXMPNQ9yFnPTS5HdX5i1iWFD/Qg6XzdG5x/dmP1gnOsrY\nR9o0OCPnoSDCabjVNmIkFh1XmlvaVcQjztFSXBtKmfVQuHWxxYAcbdW/VuA+\nsK6n23gstUq+EuJoUgzfzRCizPeKVhc9PD53uUNMBbsdQ4J5HZ0mdJVD0eOh\nHEvFm1J+urz+JSW6CO0Dm79Cj2882U5xaZR0v5ilVHZvHRiIAKGY9q3AtizE\nryrY+VesJ5DxWfsKp3OM3bPao3fc1rBDkGQ9bwPx2xAdlRdp/RrANsJ2nqGu\n9eSNAr2Ung9fYokyl+F2TRftqH6WosrixbUF+bWZPcLvqRAsQNMDEtXFZYmZ\nQeK3\r\n=LmTo\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"844fc0fa80f0f7ff6f139e967057da26d618024b","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-16T20:21:53.432Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.0","@balena/jellyfish-logger":"^3.1.0","@balena/jellyfish-metrics":"^1.0.439","@balena/jellyfish-environment":"^6.0.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.1","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.39","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.57-renovate-major-json-schema-org-844fc0fa80f0f7ff6f139e967057da26d618024b_1639687460770_0.24207053518926136","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.57-rationalize-context-91d79f6badaa549277de6ff1b0092b61f0f27a68":{"name":"@balena/jellyfish-core","version":"8.1.57-rationalize-context-91d79f6badaa549277de6ff1b0092b61f0f27a68","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.57-rationalize-context-91d79f6badaa549277de6ff1b0092b61f0f27a68","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b9cb0f1d3f1c6aab444413ce00c29f22c488d126","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.57-rationalize-context-91d79f6badaa549277de6ff1b0092b61f0f27a68.tgz","fileCount":215,"integrity":"sha512-SAmA0XpUa2kSWQzMLTLsl3z6Crz8npGnRr0UT6hZz9eZ4u0APpK2foQGLORKRXLp9z31Yl8BzaZVdmJfpEs0Mg==","signatures":[{"sig":"MEUCICFOSfws9hdhjh5RKjYWw0n4627VHvM2qXI4O0n2Lnk9AiEAsQRA1Q55GotjslRjTM15JfdD2qi8McE3RfwmWpiA2A0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":784657,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhu6U3CRA9TVsSAnZWagAAYUYP/2R518f+bWKoJqV22eWm\ngBpcmbESz/lBTFLTetsawjMdr9QIzbB21CSpac5nTzgfpIT0UZoO60W1smFU\nTUq12J5TCKb8xLi1S3bJjD9tTONNMG826Bc0XPzxeyny/9R2j2SMLi5dSufm\nKlEfUA2vtBAhkrJg4ThL0Ux4svvGiE7l5RJDjK4Nipak6SeKT2cJv27R5GpS\nYjuWO9kk5O4HhCNaFlIFkikJEEypNwIGEa9qceO0z7mkVvWwHv43AffBYSNS\nP+ScwSwoONXwMkdbmcTs6+prn3j/P29JHKqkdyH3PWn9sjqzBft7Fo0oLs/t\neY4y/iJb4UpgPojscxu0o0P+9yDw56JZ7Zt8AifYkixoAwL+dpI4tYtPPvzM\nVQveNYOrwaULeT0AtJIlxsjHcBAMhki0ruJdZhuecrmbziZgZM7SBitMruKU\ntYJjhuPpaFmEIVOmMhWydm7Jc3XYG9GygoqXwdUcFF3AAZqj/qdEgbS9nkv8\nTrVsIUAo5TSsen9YIN3QzafUXp/cJVXrmSCMk9CuA8jXqVf/0v+qDzPScsjY\n43BMLYafVIzkhfY/rQQQ6Pv2WErHTf3NMAkzmZCti9WfjLEtYdhUpUkaEnhY\nibtMiEobYST+6W6t/VlZX/787yqHgqQxdS4FYq2PEdzK8NcNVwqySbT14Cpu\nA6eB\r\n=FL5t\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"91d79f6badaa549277de6ff1b0092b61f0f27a68","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-16T20:36:51.241Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.0","@balena/jellyfish-logger":"^3.1.0","@balena/jellyfish-metrics":"^1.0.439","@balena/jellyfish-environment":"^6.0.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.1","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.39","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.57-rationalize-context-91d79f6badaa549277de6ff1b0092b61f0f27a68_1639687478884_0.5980175574444342","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.1.57-rationalize-context-73c6a9b03bb9c960b3a025d6515568664eaffee7":{"name":"@balena/jellyfish-core","version":"8.1.57-rationalize-context-73c6a9b03bb9c960b3a025d6515568664eaffee7","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.1.57-rationalize-context-73c6a9b03bb9c960b3a025d6515568664eaffee7","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"aa7fc56d225b4435a69e29afd0f2fe96f24bef46","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.1.57-rationalize-context-73c6a9b03bb9c960b3a025d6515568664eaffee7.tgz","fileCount":215,"integrity":"sha512-gmZX+HVK7K/IPyCJ9WKnrY1eqAzf6D/aUuMzOHY169Ikd3zR/nR+CSfAsp6PrkJgFuUKvQUitHpr+CrJUqqv3g==","signatures":[{"sig":"MEQCIBWdJZA1Tgz3VrXyvd8Z0i6hncgpGNfNcrahlVJLRbxzAiBwQNB6yMg0Qf65BIZ7FFhLQSanBx9PFpyAAn6gMGVeww==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":784657,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhvEuLCRA9TVsSAnZWagAAHZgP/3IR2C6vRApD3luUQcrT\njYWteBwPEnKX8YuDFrPDBipXzV/kMFvjuv2aHgWz0iui11K/aJ5G5YWCC6Eo\nYFjlhCmcQPErKc18u40s/6TowFZXCmY8kRi/viWGtkIpOOYhanKjg69q7kbn\nVZAErFskAqEucoRxScftc48fKwPW7CRIzCtsP9oMYv5s9hHyymZ8RFrZ5nsE\n1gwoeYwlDf7uBoEY/U5luowqJX8oMwMmD8cDDOrk/pTjHnh/iezgB9blch+b\nEmY2S40nQOahksBoiCZNgK5bkVmBs6Bbo+vTNUK97NMzMBmDDBROal934dAZ\nR1gptz1VTUr76rMN9LLEMjN395wzoEMSCxAQIQfB5xKRNd8UwFt59SV+rxiZ\nGdi3FkrxcyfKY16wsRlIbgGLlPHMENqYtSlu0E9lbsVS5JHYMwmjKKw0f+mO\nxsZ6dy1HRcKNJC8aTYGDc6R0WMQdMy/2UyLG8LfFjZEIJlFya4ISgmQCyoTV\n2bHbtJsfd51MDP4LzbJWLA7UhPBzzGputLLKjByzJqrr3oDkyHlgfTng1g+L\nUBglDTVumzqCgHi2h1NkXzPGjjEjZCiDXolWVR/TZN1S7tQJDYDpTJueoQmb\nAsaKhToFYwsGJZzNB5moy11E2HYmWVGbgNotCjthCus+6rwEHbWRJRZfHKWZ\n5IDo\r\n=xvni\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"73c6a9b03bb9c960b3a025d6515568664eaffee7","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-17T08:31:10.039Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.0","@balena/jellyfish-logger":"^3.1.0","@balena/jellyfish-metrics":"^1.0.439","@balena/jellyfish-environment":"^6.0.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.1","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.39","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.1.57-rationalize-context-73c6a9b03bb9c960b3a025d6515568664eaffee7_1639730059613_0.0011163020757305464","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.2.0-h32-error-ui-schema-c1f5e3ceb6e7768dca134f1216b869014aeb2b5d":{"name":"@balena/jellyfish-core","version":"8.2.0-h32-error-ui-schema-c1f5e3ceb6e7768dca134f1216b869014aeb2b5d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.2.0-h32-error-ui-schema-c1f5e3ceb6e7768dca134f1216b869014aeb2b5d","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a9c48dbb1d6dcdc6937195faf48de3874fef65b1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.2.0-h32-error-ui-schema-c1f5e3ceb6e7768dca134f1216b869014aeb2b5d.tgz","fileCount":212,"integrity":"sha512-QQuIWtJENmF/nZdt4aLEwEDNMOVUbyFIr1tuy5vKa8iaC4iT+scL9k5rH6swd258m0iS5E6oCorMtFJ7RaPDGA==","signatures":[{"sig":"MEUCIAzf3rZMJy4czsSiae0M7LbWOdLXC1LA4/1g7Hss7JPmAiEAszkLl/tKqYFb5qOQkhW2mm956RcjRgIbKVttRUsAoXA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":782674,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhvFGTCRA9TVsSAnZWagAAZDIP/0RVbBo43XJ2qOM54j7Z\nteyIWIF0Hop3s077E63uhyH0c+pXVkGProjM4GSv4hJuwENfw+F4ZZ1uh37V\n4zy83zJVHQ1OE8fZ35bhcYsJ9pK/9C1bdrBefNfv2qgTtxIKdjzSjYWxdWT3\n5I0Svy5zq4Sp2eqP20GszNwsiVj/HrTpRM+N5mmdnagjU3iEymePO5vnw7Z3\nycfcUjER2CtllPqOqJcO+izcGFbjjhklJpQI/MK7stx2RzTDHFHLRt3RlOyw\nRKRNySgcAvOxKN3aBYWwhPNdPndne97IyXLSeLRg/0MbBVzzDyW9kymPr954\nRpR/oBFUcbFRkBDCtvedIggtsDF9hopehWMhgaG+IaMIniGZdxLkC8yBphuM\nn4BPrg3d1AxMUy9+o8OsZZF3Tz2YNo04o3fhgADCrUCFE/qUuYoLvE5sQaiS\niabt1tBaPpr+VA1/cBwv0O8J8R8vV+kA6gqpd4kMpYWVrOrLXjLauQ7oN3Au\n1AVgN0iiH8auDs5cP5jaYz3xnmbvnxiEQy2VRJ0lby60UkOA8RCrkfq2ME/A\nZuuapqie+YbUc6RE2TqPViWvtjMx9bdNIwvbGZTok9synjQjCV2mfqMGOPUM\nl8A65gt37yUbgWsRAUX8HqVktgV6A2YXn31s8S42fBKwqYh3I822+wwumuvt\nwiWc\r\n=5iAn\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c1f5e3ceb6e7768dca134f1216b869014aeb2b5d","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-17T08:56:46.002Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.0","@balena/jellyfish-logger":"^3.1.0","@balena/jellyfish-metrics":"^1.0.439","@balena/jellyfish-environment":"^6.0.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.1","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.39","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.2.0-h32-error-ui-schema-c1f5e3ceb6e7768dca134f1216b869014aeb2b5d_1639731603779_0.7170862831076985","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.2.0":{"name":"@balena/jellyfish-core","version":"8.2.0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.2.0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3461fb594450d0921d00b73cdb8614a35e1962b7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.2.0.tgz","fileCount":212,"integrity":"sha512-zAGOl9t65OAi1Ophl6+51qdxkvVjAGpJDrrkhmKZLnJ/v6Ps5eFKnsEsg1EDK0MfiHMlKkFiCB9GsH5mNeNc2Q==","signatures":[{"sig":"MEUCIEr5XrqIeI9sBIlp7SZZ99zuA/M7ofNUu1a3aO2HlKahAiEAkMsNNaXPut6U0IPhwX39/grjrzAaNFNnubF7ew5xmqM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":782613,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhvFMACRA9TVsSAnZWagAAs1EP/3OBtKedGWUck+41NxXz\nrHiGZXT8x7mpkXTzeWSDua0wg58Z5OjfyH+yToKyWV7hzj+I5ZfWqnj+CP+H\ndEQXYDOQuYMg3QUVaKRMaF6Hv1bo1Mb9aRU3HaCmzXCyyf+inL47+f4CPBUi\nabsuhCj2cvMO8VIeZ53FNESrdaxnlg3QMMZeQlOlCyso91cu4Uo1zn1B6AHe\nxarpIaGyJZFBxO1AcKmkqeunySCrSME2WZ5/dUX8Q/64CXRigeT8gJRVY5ri\nCVPBntjRS6m1LtJMMtUK3kQIG2gFJRrTGiiIrSsqHF6q9UN6zE/cwUX+Ct4/\nI6Jdd4XDj1UfDdZpxtQVr2i65FbF1+XKC+EuAiSiuihauB7a8AGHqg4a83/c\ntgeZawFnPqDJ9u5m+4mnvpwv+02mQmCtklYmZHmfEqMuOtnt2t0ypYKQ6S5r\nmV9bztafa/qHPAB20EJe5w1RgQTTZDIihUhI+Wc2yovAqClPVypDWMruWkDa\nkN7wdglUXaOHNscd3runRBFPzLFKt7bV/aNVWtE2ld0BcHEqkbCH3AGQ8SD4\nRbnzY+Qu0a1B/Pu0ZC4rZ/lQkVm5p/qLjbyRDAliDK+KMXT1Bt+RefDxV6KZ\n9c/xKhUbpnAoCGJJAFEaoeafr8bG7Cuhm4ZKKFaMtYCuaY+dKR4vHCzeVJXN\nBSvX\r\n=+l58\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"61cf6183758c5802f2c907baeaa6cd8b184f27d4","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-17T09:04:17.487Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.0","@balena/jellyfish-logger":"^3.1.0","@balena/jellyfish-metrics":"^1.0.439","@balena/jellyfish-environment":"^6.0.2"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.1","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.39","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.2.0_1639731968664_0.8531519950209987","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.2.1-renovate-major-json-schema-org-8c1881f33eea4b1566cd595af70d4dcf2ea9e77f":{"name":"@balena/jellyfish-core","version":"8.2.1-renovate-major-json-schema-org-8c1881f33eea4b1566cd595af70d4dcf2ea9e77f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.2.1-renovate-major-json-schema-org-8c1881f33eea4b1566cd595af70d4dcf2ea9e77f","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1f6101cc925fb882ac2195739f37f2e20bab0842","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.2.1-renovate-major-json-schema-org-8c1881f33eea4b1566cd595af70d4dcf2ea9e77f.tgz","fileCount":212,"integrity":"sha512-qKsePSijQ3O3K1oMFzTvXR/GgnxMlj/+sHtSJtj4oCtElEkZbSsSmr90k49K9bkG5+koZe6IM18QGoBQFGv2JQ==","signatures":[{"sig":"MEUCIQCqfLK5z7QIojKH3Bi2v+hNkg1urRef/esgj3K4LlnPTQIgWNThmT6uQDtN2whzJQ+kbO/sgyVJ68vQWXU5VpMo3Mo=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":782780,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhvFQGCRA9TVsSAnZWagAAY60P/0EKDxs3kb0BaABRupQ1\ntcJ/3BPq4LG4cZS8Q1inGghD81JyxkQ5t4V873pF/C/4uTEXN6Oos/5neZAw\noY5PuENbPijwL56kcx9keZzSJ2bVH4M3d8YIWlx4jICWe4l62kDJBCcrkY+o\nb2IzhZpgJmH9unWGh913AWO2f4LDq7p/3dtrmgVtstuicnY8zMAVvh1gkxao\nDKsEfn6GKNc6S9V53+249XC4IpBlDAiHpJnu1SNlevK/EAneNRzFvX2eqhox\nYsTTK0JzcVLCaIkN2bq9Egi3H6Y9qxrSyxDMMrtGKc3Lm5rve1LQl0XIjuiQ\n1rNJA7qMpDueAu5psyDERUaAVKetqMkcYxU6YTHNBqQc9nwfmQqb+3ilLVZ8\nt8jImQV66LrHH5b1VWCS246tcwaX8fnxUyEPyCOiR8na+vk2tM54jmQXG4pO\nJUQ+4M8S2yaS/Nz5Ol1cqo/gtud2z2/PX547hb0IoUThjB+LW37hXYUeXMHJ\nS7VB9IO0JJ6dU9C6jEazDuunqjexCp+W9L92cBTOT1Lq3qODTF9Mnbcy3MKr\nIJyynTzaBhM2ImuKokLvzRpZZdqYFz2f7ORNuEsCozs8t7WZYd4rsuv4ItW2\n3xsCMNdMtN+pDLjgreV0fqCkW3VlePxbsuL1GKonR1NxLC5/PLPI1Sn6aKB3\nW/jR\r\n=Mtth\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8c1881f33eea4b1566cd595af70d4dcf2ea9e77f","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-17T09:07:47.292Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.0","@balena/jellyfish-logger":"^3.1.0","@balena/jellyfish-metrics":"^1.0.439","@balena/jellyfish-environment":"^6.0.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.1","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.39","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.2.1-renovate-major-json-schema-org-8c1881f33eea4b1566cd595af70d4dcf2ea9e77f_1639732230109_0.26189563177977404","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.2.1-rationalize-context-522f21e00e63929766c8a1a105484c049975331f":{"name":"@balena/jellyfish-core","version":"8.2.1-rationalize-context-522f21e00e63929766c8a1a105484c049975331f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.2.1-rationalize-context-522f21e00e63929766c8a1a105484c049975331f","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"26d17edafe89ed6b98c13e271200d231ef40313d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.2.1-rationalize-context-522f21e00e63929766c8a1a105484c049975331f.tgz","fileCount":215,"integrity":"sha512-m+JXhcn4zHMeHzIUVQa3oLD8I6STbzb5qYC0mC9vHB8/36rItUBG4RrtMcUz87WF2lh+A5PANtKj9SLhluY2zA==","signatures":[{"sig":"MEYCIQCcmzvnCFi7YdNmhgwaIv0lA5iyatU8+/X6aDsU9vECTwIhANLT0BhTu3LlyRoYEOU7iP2JBhKZcKsBDBmagslp3sWZ","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":787049,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhvFvTCRA9TVsSAnZWagAA9VwP/3CrZN0JARQf02LLjrsT\n6zQrYIpFgLLqafnfIlOTVTu82WcyiadEmnIfdQe0RSHZREBSOqcgexIUB93z\n3Xx7CksZOUqZE52BoEysTa+eE+3Hjc6rJ3g3MOJANLFiElu8PTb4vJMLaFMo\ntfm693c77Zl+HRVZXSqJzJ/z9yHaA1jvdVAkCVtl8SI5fzw8yecKsjmw8NxK\n31qoQs7bng8JCfnEjKxFbbfhOSA6RWe9xPTnn0qNMaoF1KXqj9Dg8xCKgQIi\nzkOpGp2QYnugjZay+ns1xAoJwysZfV3A0g7CUJPFLP5R22jN9uNEFPHyzTR9\nvb0Bl9e+rPIf1rVa7MAb761E2xoEQMV79u/d0SNKSoArFjKbzbnGZHpEiIp4\ntZSDhATpFzJEI5DXl1rgaHxqRUQW7oQQHXnUoYZTs1ALs6TMew5BQt1CJqLZ\nphEsX6wBuH0tYXFF5tTTWz7xZLfRj+f+W9OhDxHtSPT5sZp37ulJaa5x+mEt\nUBHk/rQeXCRwvgUTfaXiCk1+tWPhpcysS68dPMd1tD1pCiLPnWigeQh4OCxl\nntJ5adA2AnCY8ZkVfgKoFMqmgJkn1eD+bdMML4+QPl2+2UFylJu574jY7NPP\ni45JctGKl05MQmFkN8VPDoIldD8se1Uk1d6fS4N5ku07TFuBI7bug+hQ5ITW\n07v7\r\n=bOSx\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"522f21e00e63929766c8a1a105484c049975331f","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-17T09:40:32.763Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.0","@balena/jellyfish-logger":"^3.1.0","@balena/jellyfish-metrics":"^1.0.439","@balena/jellyfish-environment":"^6.0.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.1","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.39","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.2.1-rationalize-context-522f21e00e63929766c8a1a105484c049975331f_1639734226825_0.23008527646036758","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.2.1":{"name":"@balena/jellyfish-core","version":"8.2.1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.2.1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"dcdc43b4730dfb6e078cf12360cbcc0e5d4c7e16","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.2.1.tgz","fileCount":215,"integrity":"sha512-dMaAgW1DarNhZ9KyvO1uUOGw/t0iD83vFFzhqt27grX/Jb9vl2kXzjXu6Uq/YwTvzfa1L94Q4BQ6P49+Mv1nzQ==","signatures":[{"sig":"MEYCIQDoZtWjeFl3FlqpNz8W5zff23fX8Php9RVq3cuQTeKeaQIhANgwn/tyBBIuQeHUyslW9FhKTvLU60I2y71Zh4WJV9Vo","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":786988,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhvGPOCRA9TVsSAnZWagAAbg8P/0yasMmylFiWg+beh+Ub\nlpwFh43/uhefms8IKd7qC9hwWnLGWbrlakaJbgx5GzKHhhOGme8BORAQvmJD\nPi9AraIx6tzOiDfrQ/P5Hp1dbcRmo2gjwvL8b8oMAJcUeLzmkQRi4PMKM7rb\nnoiFsDHnPuU3HIZKgngSNxJJa+Tgt6YGzJ48+ZWY9OzVpCVCeqfdCdZe7vEx\n6xjSsietCYCGfimNQuE7pbNY98hq2iwoQjOqcADTp4PQMazE+uzLYLcaVKpv\nBwtyBVDMeMAVhBQOPrOi3YwqaHNcM3ics4NjkgeJrqSs9DeBoXsKGDYxg21+\nZ/pofR7PemZ4QssP9czRUcN3SHFzx/UdICNnKdfAk6Yg38/lYPrkHbcmXVK5\nL1Fev75YwuspEDl4aXAvHnzFag86KHWM/xYnKDH2lBtBHFQV1qELBAzttvD9\n6N+rwjCIsMrm3oCcqa1KbpxaFLoyXOwbtK99ZS/66n8wispRTdnEf4znsJrZ\nkaSPnaZmbl/ukp/RhJjV1cAQ3YagtXocwE/8+L41swcKgyk/rMUs2QPcQFn6\nhE+1LAE6p/jcxfNPYRxfL1eAtvko873zjjQcqN+HqtuxPXKeB+bbC4dpegdT\nHRqvB7r+oqEbxxZuCS7j0tpzPMb4pHhYcDxfcKDcN65y5HyTKpk45DQXZ4Ir\nABMc\r\n=hjgh\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d938e9f1be713530c0290a0296374cfd2a8b86d1","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-17T10:15:31.148Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.0","@balena/jellyfish-logger":"^3.1.0","@balena/jellyfish-metrics":"^1.0.439","@balena/jellyfish-environment":"^6.0.2"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.1","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.39","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.2.1_1639736269790_0.13233493063548285","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.2.2-renovate-major-json-schema-org-9078f80288c78a55992702df47d4270e5caceced":{"name":"@balena/jellyfish-core","version":"8.2.2-renovate-major-json-schema-org-9078f80288c78a55992702df47d4270e5caceced","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.2.2-renovate-major-json-schema-org-9078f80288c78a55992702df47d4270e5caceced","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9c26b1f3059f7f9a2f4462d91c443b1fa39aad39","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.2.2-renovate-major-json-schema-org-9078f80288c78a55992702df47d4270e5caceced.tgz","fileCount":215,"integrity":"sha512-8WMRlctX0nFCTM1qYnN43eQ4oShfJoRauxqWge4LsjGuFIe4PmTzzf8++3BtgLn45JBY5XnZ8T/0j8s4dZaV3Q==","signatures":[{"sig":"MEQCIGVTLcX3niQuuYDHWkwM75tA94Is4vNeFxEO0Oy/KnilAiAA387d+GdaWGop3WBFBbIwAgfiMiCp8iN85dyBwmP6Lw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":787155,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhvGUBCRA9TVsSAnZWagAAIbcQAKE/JCmZ7hL1frS7DqVf\nrGh4YMMAqW8YXzrHdTT9rxahicgIWgE0ge+oQghHN1IIBV8FS+lqGp92Qc/v\nUuPzYC8qFzRQnacZfwCznlNxeGSjwGtlA4wZRPuA8XowYoTosz+tFQ+vxEdg\nwtkc8YD2MFSHLblxH6xYqgkWauNlSrBSWFo8j/HbuG4FetS/Gwv0nJMns8rw\nDbQRLayTnM9XUq4jHiEAgAs5UHtUVXYaRscuLnmc6HdduNkkLNGCSCnKovGi\nv3Z5pOUcCed/azP7mwuErU8FzAApyuc5Cz/9tpQyx2Gs+K7kSWKpK5kTkxXM\nATnZRstk+Mj8Fkc2adaKUWWYfGHhwaJX3Ck+nz3vSYVZxK2qg4bCf6QDiGbw\nzpbvd358TyQEJJiGU1PxjAZQy6t5LQLEuZ2EHSv2GYs8w/wkINRhgipvmUBj\njoMD+lx7yFoSnzP+1vCrAE2HxvIBy7nsMnZre8K4sDXJrp6Vyz0soJzdlcCq\nSZIvX5JSOQsW9+BhYI3p7lIiMtL4dF7cNzGA4NbLqP5kZumt7ZcRo1bsDH4R\nJM9JozSGs+tgpw4Rk3kCf+lsQQj4a8dM/tEZJeT8UHzy0Ksa5L/ReHzIftdI\nW8GOj6DE0/vbSy1iknh4LQmwkt5sLDelvKE4LinrIjPJZ0TK+qGNBRFM0N5N\naEwg\r\n=F8CW\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"9078f80288c78a55992702df47d4270e5caceced","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-17T10:19:13.335Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.0","@balena/jellyfish-logger":"^3.1.0","@balena/jellyfish-metrics":"^1.0.439","@balena/jellyfish-environment":"^6.0.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.1","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.39","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.2.2-renovate-major-json-schema-org-9078f80288c78a55992702df47d4270e5caceced_1639736577287_0.1263055971742233","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.2.2-renovate-balena-jellyfish-logger-4-x-7df6d348df9ab2903604efde36aeccfc7a94e543":{"name":"@balena/jellyfish-core","version":"8.2.2-renovate-balena-jellyfish-logger-4-x-7df6d348df9ab2903604efde36aeccfc7a94e543","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.2.2-renovate-balena-jellyfish-logger-4-x-7df6d348df9ab2903604efde36aeccfc7a94e543","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"49316b0f1e8fba754910219e865ea23ee819467e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.2.2-renovate-balena-jellyfish-logger-4-x-7df6d348df9ab2903604efde36aeccfc7a94e543.tgz","fileCount":215,"integrity":"sha512-5JN2aRTLBge9DMKiIUkWOectwd0MN/w+w12Zaj2R5/ZL14Ja1RXA8A22velvuYMJTk2FEgdjFkiSVdopgogFGQ==","signatures":[{"sig":"MEYCIQDDI+Ye7JR1egQGlM4sAVUK1lCVugJLod5Da74thkR5VAIhAIe/s8inCbXATH51MZJSfp7oFa3udv3KNWp1AuXlmLxc","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":787163,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhvGuZCRA9TVsSAnZWagAAWYYP/2d9ICnV5wYcFAUR6q1w\nc69LTslDsNnjoNTP/JpveiBCqsbUPApt2zR7oVVhODpVEbSPbR3KL7eNjoXs\nu4foamCVkZTjfGWUzTgWJh9uGyIjF7drJlcpJpt2r0ZVyCZREYi5C4iBidRx\nZp1cPnAApwKITJZyOWZJzh75/iNr5bfF7bx8rSVERTbBbyLoJm8J6DXTEZrC\npd7QCc2OQB/+iDIejoi4QsC/E3uUjmnSY/KzLo9hQ7nAtAfoGAF9IGeFvoVO\n1Q1DGJV6KUK485/tzq8WXAgd4imQIZ0IBis/bnDWPSvJ0niFgQyBRrxVK+Fw\nASnU/ZeGYWFg+W5sV69SQHNw+KPlq2wFovLdjCM+czsDu1zhjLZH30emPH5Q\n35UnnB0VzA7vrK7tXtQS/UEU6nuIR9bdJageiUoQs1wfCFc7xSxdkTWsaf8+\ntZctfYU6DEbnAW+Sg/ROF5zK5wuCSoKrocUQS8lv0EHLE9j/YXCSZkLzm0KM\nxN+qKNq/eqdLRxPEVfdBwbfpPfJgTBLgru0Jj+z1E0gnYFabGgkPYvV8Dusw\nCN+VrRoocZzVxV5uhZ7dXczv9l5mnD604asSPCIahE3B8XqYK6mRsLhN2E5z\nzMP7Enfi+Sy5ciCAwQXjdHmPwktOvqBq+DSJ4qPn1GmD/eInJ6o6Onw6veYH\nwq+c\r\n=ztcC\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7df6d348df9ab2903604efde36aeccfc7a94e543","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-17T10:47:51.979Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.0","@balena/jellyfish-logger":"^4.0.0","@balena/jellyfish-metrics":"^1.0.439","@balena/jellyfish-environment":"^6.0.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.1","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.39","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.2.2-renovate-balena-jellyfish-logger-4-x-7df6d348df9ab2903604efde36aeccfc7a94e543_1639738264837_0.44015929145248545","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.2.2":{"name":"@balena/jellyfish-core","version":"8.2.2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.2.2","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"dc9523b852ee00c4e63185b035f05aff4a6ef3fe","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.2.2.tgz","fileCount":215,"integrity":"sha512-9NRaRm4WT2Ccg0ziglle2CgcVQU6t9uXAZjJorUFU1u6KJ309nK4FyyRSURzreJq9efZdD3uKbeVtcNq0z9ZKQ==","signatures":[{"sig":"MEUCIQCR05r34Wl/ZCbc5pZLAhmV7Jhm9rcTlzIyYp/p8Ms0VAIgTpjcRFgwgacXanDzl2o5fI6vShnJZ6me7qlmgN/c2lA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":787085,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhvG13CRA9TVsSAnZWagAAyXYP/0NJM6HnKKOm09o1kl7H\ndsODiWjyUtE9+FcH2R7uL1YzUwkpZUeJrct8W5DHRfzTtLOVFxGYmR8mraba\njVe6Xjmd0iZE1BskZ190MN5/3JF3h++NtV/I5/ZYinzBCHNDkPX7tvcy0B0n\nGAQ5D4cJphDKWOxZ0S+ob0+Hv9W2qY2XwMkQFo1QSRTEBMHp/PcK1KiE7sOz\n8hUpM8zmKuLCzExvgV7PsTM0yh5YQ+rjZNAgHSQrXp9YYtWTgpyXXUH4Dwbe\nsGaXpKKsNfXaST/01z9sJghgK1Dv6bvckyOMqveD/9dYBxJ60eUhhby4SHwR\nIE4GmORAClF8WzlEE69ZZKr4Hp0RtsxFIcZ2KZaLCWtlnt3HTPDLFiFvQomP\nspt5YcCTIFocVaz9+8ZwXn2bZfkVLlen7FX6yiB9ZixsE/yYUueJ3cpwS4zs\nmdOBSlwR+Px+lU84ikbitGDl2AUNRvGuIKw+0auY83mXHwZ281xPqt6d8lpS\nDwUvSu7ANQ1vNqYwnmlBxduKnIzq6UFBlC2GdQQgQRSXDj+dyW6CMuKzDS//\ngBqm0lczJkQe3U5IuXlYC0VbKBy5kqw+vYas3HCxCxe/oHqHjCfkaWIXZZ1X\nEyM7hIlnqhVYUEH9xeMbZ2LDUQvXsEXLXKiBDDrIcwGcI0bCD+um2zMevtP/\n6k7m\r\n=Kv7O\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6cec5d013a15b012089a70512abfc5009a0660e6","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-17T10:56:54.385Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.0","@balena/jellyfish-logger":"^4.0.0","@balena/jellyfish-metrics":"^1.0.439","@balena/jellyfish-environment":"^6.0.2"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.1","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.39","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.2.2_1639738743447_0.07884781291290266","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.2.3-renovate-major-json-schema-org-96a4c3e5041833fc8cdfdd4da738ec3d1012d8de":{"name":"@balena/jellyfish-core","version":"8.2.3-renovate-major-json-schema-org-96a4c3e5041833fc8cdfdd4da738ec3d1012d8de","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.2.3-renovate-major-json-schema-org-96a4c3e5041833fc8cdfdd4da738ec3d1012d8de","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d7fb244d5be850fd6fd2586298f1a21931bdd27b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.2.3-renovate-major-json-schema-org-96a4c3e5041833fc8cdfdd4da738ec3d1012d8de.tgz","fileCount":215,"integrity":"sha512-thnd49iJKlogHuWQklXBth7oJ1/lANn0OvELKctp7ksX6Z/oQben8GOoMB1Rl/10rTnTwAMsa+fFngIanVPoPg==","signatures":[{"sig":"MEQCICXpX1fgeWHoyyApzdNRMGgnpE0Ydvb0iY6Ki10EGFsIAiAcUfPxTdDAyv1XF4GxpOaK/rrEE0xSh6D0tiCLTSfPFw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":787252,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhvG7GCRA9TVsSAnZWagAAl30P/3DO+mD/OQ3P7lK8nqry\nuFFv06IOUYkow0Xr2sr5hu3i3gAeecNer+wU+CHu3E3j+ZE5UHtKwzq/YLuD\nL7zCuUcG2+8tNoI8zxBughWA1aODKgCXs7EDwucB2cBpAAZf9UaD9/BW78gh\nC/E9lDIoMbiPhWXyOMfodcWpoE9JLwdcShsOxy8leNcYuRjMzqDofU67OfOm\nZ2XjQGbDZq2TzalMBbyisEybmIU1xxKeYaGkztukYLSFA4ucYz63znIVoWap\nT1pBQtoErdxBI25XNMCtRMBXlkI5u7lOo6PsFlfnyaLYH8hYIlsr7yanaPfg\n1n6glKaY8uPbfBPwmSgsMHQ9G11XXBHmWn3b58jp4wgxcx33d7VizJHQBJSF\nBGTgmmDuV+iTIbYSIkToB3NPKhw4Bep31SQVA4zHB71sDTV5nOgEdkC2dvdD\n2oj+lBsYSS/IwGwAIE1qvVxamlRv4QmGBuw6B/2gB3Vh60QuPSQFdjl6b9+7\nyHmfD13f6Ef3B95tkYW02FlJc8SPjUTPMT4N1m+E0ektNHc8KbzP+4IBibMX\nEIBxEAX4Eo2ZYw430ulaUBJ72N96U/u3BS4jacni4lUUFAix1lYXGMeD0Eap\npBu7r56MdKuLwdE+p8gavVU5yLunnrx+R3hT+wrDKwQ/zj8LEIDixehlHh5q\nAh8P\r\n=wm8J\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"96a4c3e5041833fc8cdfdd4da738ec3d1012d8de","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-17T11:00:49.683Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.0","@balena/jellyfish-logger":"^4.0.0","@balena/jellyfish-metrics":"^1.0.439","@balena/jellyfish-environment":"^6.0.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.1","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.39","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.2.3-renovate-major-json-schema-org-96a4c3e5041833fc8cdfdd4da738ec3d1012d8de_1639739078286_0.7344277234667007","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.2.3-renovate-internal-patch-f816615aa9b722aa729716bc42daba1e5e145736":{"name":"@balena/jellyfish-core","version":"8.2.3-renovate-internal-patch-f816615aa9b722aa729716bc42daba1e5e145736","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.2.3-renovate-internal-patch-f816615aa9b722aa729716bc42daba1e5e145736","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6162024fd7f00f53b383e28aeb68666f66a976a0","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.2.3-renovate-internal-patch-f816615aa9b722aa729716bc42daba1e5e145736.tgz","fileCount":215,"integrity":"sha512-HH92OIVwgCFuMxAUTARVXY3fAQ3D39orS8q1xGKqR6fBOzKZrnOxJS4E6WRkH2/fkY3aQdy0pNOy1ZUt+BP/1A==","signatures":[{"sig":"MEUCIQDoYWhMC22vKI3TEDiYqQpuGkHL1ZbOe+TcbBbCBwjidgIgRRBNe58deIkUXNWuG4Lc3jRyQGOhfZKVi7NtsyfByfA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":787251,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhvL6aCRA9TVsSAnZWagAACfoP/iw8wcHL/EMPjVO3W1ZO\nXZX4TBa151wW2G9Bg5a9FqWUOjlkEooVYkgMdcrRsqoPoBohAGCBF8/7DSIx\niF8M1p6/9S42pdwl1yhrZia7b40X8Z2EuPMozJNajMvGMfn0AZOZxDuDYKc0\nN4xvARNyhJvHkGOHpFAZG5WbpWY264bY4yFRJZc+Cr9Ly9RrSq4dUd5D0sNw\nR/lvQB2byFT9BgN+lppyj5GaqWRsBzvUgK4q/1EjrY9yNjFqceHc/QRjuoSu\nltD6Hjl5loi0lVyiUovEPOKKkzLwBtl3gcb0i/L4OmyOlbaIGf5jH4fMv3r0\nH0yOLhxyXs9f29OX3fwsGWNHbGfD453+zH65mIv3CRoErJDL/NRThfb9rAwj\nl4pQe6hlqUIyPCX56oC4b5qPQoNsGjXFDkk1E6ln0qyaeTJrJE/xj1ky6N5r\nkJFWPHUw3fVlzo5cMyMKxAjWITQ5dPu9ox15dPDBRl9cu0k1DWZN3kSBQA8R\nB1Lh8rXIiP19vvvbApG113KeMVBgJJ3S57Z6RnrafW45l2spJgGD1E7zQtn/\neqN2nLNDBCv0wOBeSBA6Os9mX0CEg/nWfLlZITQfFmu4DLRcp364alcCmz59\nv6gQ+H0OubhMDUJMPtSIjPm2uAuNJib/SyRfJgxwtOjfCxIvMjfu25+J1kLt\np3EZ\r\n=g9Ai\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f816615aa9b722aa729716bc42daba1e5e145736","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-17T16:42:26.381Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.0","@balena/jellyfish-logger":"^4.0.1","@balena/jellyfish-metrics":"^1.0.439","@balena/jellyfish-environment":"^6.0.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.1","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.39","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.2.3-renovate-internal-patch-f816615aa9b722aa729716bc42daba1e5e145736_1639759514355_0.7292261366809687","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.2.3-renovate-external-non-major-a7dcddadd8318f46f133954ae7d6deb04a44611b":{"name":"@balena/jellyfish-core","version":"8.2.3-renovate-external-non-major-a7dcddadd8318f46f133954ae7d6deb04a44611b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.2.3-renovate-external-non-major-a7dcddadd8318f46f133954ae7d6deb04a44611b","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9bde77bcca143ebc81b51d81b3809a3d058acabb","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.2.3-renovate-external-non-major-a7dcddadd8318f46f133954ae7d6deb04a44611b.tgz","fileCount":215,"integrity":"sha512-mmevgUl99c3Cc7TGwvQ1SVxvwBo/irePtr0brVAdoGZK9k2jSKS4rbae8u+GNH0j3NTbjWOxUcVEiyqpy1P7bg==","signatures":[{"sig":"MEUCIBEWtTjcZaDQROpG8kZ78P6cvoAb/n1zTkzrhIO83u5lAiEAtg+O3kTeJn5QzfSkXT8qdpDcvsFAzLnHyST/zEXkICI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":787239,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhvSecCRA9TVsSAnZWagAA3iAP/2kQVtz2INbYaGb3kETz\nnosO32rUgPTMPycmEZflFet1aeMYmsZBBCoy2sw+BJSDrrPGc2Ooghn9f1YU\n3HdgxxDKoQb+lpPOtvN8WH+DQaK3x1ukvX86wzfcZb7k07e3R8AosFvofHof\nX7XqmHSLIqFmeGNCEwOtsi92mZRWpGrWbw60c5fgwGzPGIqXaeLJDIj5Visd\nZfnRGtc3bj586+awaARQXuNgniIZhEIhmGiMHE7CDd0rLvYIGHbo4bPDR0rB\nMV+HJsBc+kj+g4AAid7cil7RbN73VDwVsn6gG+m3tBLH8eiptayaDiT9Xcr2\nLBkkrgDThSKF+xZYBJFFGx9yPevQ9+eaLi7UGb7tAtMEHEr8MXLcYsyGJaIB\nGft4L7Co1cOUh9qMSiinZFHbgKyUlKS9waHLLrTqyiB4W/vLIlu9y4VVpijB\nHMKFAgHfSOk16S/9L9OLZbxfNhHcA1qiSRYMvabtbXN+RFU/2dMwakXnwUuQ\ntz48rqq7QvE6R5KMr9UsNYEYki7MaSurUT0YpKZhz+5eVaNvpInwYbSQszqV\niM+xlUqMqHMpEx710b0SSHMBt5Q3TPuKLj03Ny4ZS8YipVWRzsByYyVDHvij\nMgW6BowtzkJNJ8QaOcM9ySCPe3g3b11fD55Md+MVJt37XfcNQd5jt5LevIMv\nz3Zl\r\n=hXVA\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a7dcddadd8318f46f133954ae7d6deb04a44611b","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-18T00:09:46.873Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.0","@balena/jellyfish-logger":"^4.0.0","@balena/jellyfish-metrics":"^1.0.439","@balena/jellyfish-environment":"^6.0.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.39","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.2.3-renovate-external-non-major-a7dcddadd8318f46f133954ae7d6deb04a44611b_1639786396364_0.5673763041537483","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.2.3-renovate-internal-patch-0189a504a523dc8588786abf98a7393031b4b080":{"name":"@balena/jellyfish-core","version":"8.2.3-renovate-internal-patch-0189a504a523dc8588786abf98a7393031b4b080","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.2.3-renovate-internal-patch-0189a504a523dc8588786abf98a7393031b4b080","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"08fa1a01ad5ff9cd03a62e9887b401c180d00a6b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.2.3-renovate-internal-patch-0189a504a523dc8588786abf98a7393031b4b080.tgz","fileCount":215,"integrity":"sha512-FtO4vdrfmQMlHmxlaFvsSwRw/xJ9/xIU/BUU+z+M9IP7gRDtDcQb5yxAN2R+QEfCWLTE3IV1Lo8sSPEgvgzwnw==","signatures":[{"sig":"MEUCIBikql/FU6Yyhukig8dSs5ZHJIyyril1sSd1oq8bPMHhAiEAvC/0sf9qX6kgdfJnrrbs/uU5HtWpSRG2tdZ+//IWSGU=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":787220,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhvSe7CRA9TVsSAnZWagAA9aIP/3bhq+5DoP7HMtuDJtPE\niEfqLllxCuqtJAC3XYH+1Oapg3YJU9zAlTDS8YKcpPicH8cVIb7bsnvmjuRN\nOXGEi4+FRq+QKcXtto0+GFcwrpZNhuzhAmbrr1JfS7lYzFPazExI0R2BZUMP\nR+8S9xukV4HujDuK2uVB3UExtEKbNXk39njbayhvG7gEhUmv3aaHGzsBNZNw\nFyUXhxyMu3k65I0NMwXmXievA9lE5yoLmrC/xdJUBqBjBtw3km4xgVhozh1j\nQ6CRs9xgvpf0N+J++7YaXroNBbneG3Aor4pzPnJv4g54kEI8uBB31VQ4oOnG\nymK88gIHCxxqLS1VLf8nHTn8IbLlsn7pHO0rQZ28ebUyHIXLqdGtjD6UMPT5\nplh7D77P4o875N2XLCvp9IfCWdQrQdPq+rad2+fyMn6XRaSFF2D86XSUXN/9\nG1346JSy6oToMMjL0WeS2yKyy6KCfLpSosh7zV53/wt0LhmEcl/n0sZyCl+y\nxNGMPdvSRrXyF7aAaf+n+pgxDzPexfylLOBfm+Ep0pa3/EwG7DZJ1Rrj0V/a\nq8a8iv4z/E0sMqe+seNjxCrQnLoL9PJ+JBUxllfrdSQ/Rm1kN2gjGL2wH8Uy\nIJuLmO91HDf8+cW3xD92g/h/WnYms7sK6ZaMsGi4tPBXhYkezVBU/G7/JLFd\nsSIN\r\n=TXUH\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0189a504a523dc8588786abf98a7393031b4b080","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-18T00:09:56.561Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.0","@balena/jellyfish-logger":"^4.0.1","@balena/jellyfish-metrics":"^1.0.440","@balena/jellyfish-environment":"^6.0.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.1","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.39","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.2.3-renovate-internal-patch-0189a504a523dc8588786abf98a7393031b4b080_1639786426875_0.02566568275519665","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.2.3-renovate-internal-patch-cf67d991d79e0029d1fc20efc4ab8aa60ce14979":{"name":"@balena/jellyfish-core","version":"8.2.3-renovate-internal-patch-cf67d991d79e0029d1fc20efc4ab8aa60ce14979","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.2.3-renovate-internal-patch-cf67d991d79e0029d1fc20efc4ab8aa60ce14979","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"7e266d8a6fcb974f0a68dcd667418e5245d18d3e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.2.3-renovate-internal-patch-cf67d991d79e0029d1fc20efc4ab8aa60ce14979.tgz","fileCount":215,"integrity":"sha512-DrsIEgMDfkJMvVpxA+woHhfVjX4vF6NgZFIiC+vIrjmf+dyz3IH99b42xAiJ1bOPtXpRoMd8deCQCxBnfUFZhQ==","signatures":[{"sig":"MEQCIH5UaaqHiIAaqDri9WIe30F7LB6MjhZ3058T5Bme76eHAiAA8IwvcKdm03KCZuaMXt8vrLjj2fDu0uIJoJfxPCAwOA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":787260,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhvT/CCRA9TVsSAnZWagAAUDoP/0Wv5BQRgAS9zSU+MRQi\ndyRZIFl0xJTpxhdQyPbGRCW+kBEye6dizzrnR1TJTQfgnbcsTgzHOm8EJmYG\nyuZBXGmxEFTXStIMhzEDRJWakmNU7ctSZFTEoA8ca3FeVsWfSpZObst2bOJd\n60zl5bXkdCE32LPUbAQKRcWzxm+8fc1DsQEaMrRElC5qWUsVwvZqz1BhOphy\nIgZHCuEWJ2oDYf0c8UVO0oi5upwzuPFL75NW9be72boSjgw7ads2EfLLqEXz\nmPi2i+W4fS0EMrlbuKJrzxFtaSiDj4KpFY681ZHcXbDjvazGSvxnLcHewYk2\npnG2I3tkbQ/PqDSrg64yphhsNYykYayeUWPTNEM2o6OCouYocHp2+5th15Ji\nOW2V9CZmZrIEAdiQwsNSze8iG4e1/WXGJT3xNpk2Z18h+UHL4e6Yndw4/HI2\ncfUnuli6yNDyqV1AwAnUQs9vh+khKR2hYbBwSEJkhMQTgkFANIPRJfAUxnsm\n3hJDUQicZmgeq/AdLB4EsYGhJlvmYtll5kxrV3PyKYX61SbLRMYG+1EC8F5l\no56Qpbfpp236Bp2ukmOQ2JTwUuDrdDh9EL9jG8XJl4OzCaykkhmXo4BVSlRC\nKc5WSgLcg9l17n0mDVZEe0d5vC9fCSwF0CbcXMf8ZU22qOf5+qepnLg0+lUs\n1Tcb\r\n=3XI5\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"cf67d991d79e0029d1fc20efc4ab8aa60ce14979","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-18T01:52:59.972Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.0","@balena/jellyfish-logger":"^4.0.1","@balena/jellyfish-metrics":"^1.0.440","@balena/jellyfish-environment":"^6.0.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.1","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.39","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.2.3-renovate-internal-patch-cf67d991d79e0029d1fc20efc4ab8aa60ce14979_1639792578180_0.4027969162756664","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.2.3-renovate-internal-patch-89b2d99eb417729b29942e17d13a84e4942045e1":{"name":"@balena/jellyfish-core","version":"8.2.3-renovate-internal-patch-89b2d99eb417729b29942e17d13a84e4942045e1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.2.3-renovate-internal-patch-89b2d99eb417729b29942e17d13a84e4942045e1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f6e1adb95a16be01c64dc3bad611cbde6b012095","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.2.3-renovate-internal-patch-89b2d99eb417729b29942e17d13a84e4942045e1.tgz","fileCount":215,"integrity":"sha512-25ybbko/tXU8tVIm8UfKmFS/pNl+G7a73WlnyPaswDQATUpoa7RxtJuz4qD1/StcuPhTAO/yR3kUA9pfbVRRQg==","signatures":[{"sig":"MEUCIQDm+XJUX6c929IDf7Wgos9WWNPOi7NmLj9ZMpfR0UGqRgIgWfgoDzQU+zRP1Ee1X/11tbZPSR1HEn3i0GHN/tv8IBc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":787260,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhvatVCRA9TVsSAnZWagAAbHsQAIA5EDo18LRfxzAyANTr\nPl2tBYjvb4R2WSW//xn15BU/WXnctb1ox8XxBqKd9PLRApfu+yaBHVRWC8bn\nIU2mLszoTgT2hGBkNkaduhG4HAbdOWEWK91zPoRZxdI17PBSqXrFbwfp66no\nRNpsnRU8YnNjMBEs/4EzzYQSHPgmLQT6Gv4SFk1iaF/Zu9GI6kpUqt8BRK6W\n4VQRbGkPZ2Vge29kmffendA0UPLQ9HihfrB3FXSMrmTiL6D0h5aAIa3nV5lY\nkFnwMukvFD/RZGxBuz7OwBkAV5lQXvemDBQVweHZ8KTwvfW7R9UU542UTRnE\ne9XvsaQHDKL4zrAAW0V59OksXDAWNxIviggn142wQqnl1ARQaOJX8iBjWecN\nOH/2HlHTizJg8RiHmm+oyrhwzyjCe6I1jBcwReqThwcjO+DvKiatz7XeSwCB\n/wuqlFYWnxFQ2JF81vCSgYi9WLfc9j9N6c9m9jZaenuNdCzj5eVq620kqKrx\n5TlKmDPdfI33tI4MxjaoX34cx6cIuBI3pBQROO8PiZ9AYoWpMkt5HS3Tj3Cx\nx4F2mHtKdX7+vyjl744bhFbsyPxqjdg5EEn+FT7J9g86iVTZoQ+xdaAtnp8z\njNQiH4fi3UHA2rY7TA/XBNKQAfZWgEzU0nauKOLD7IoII9CO+cEskQfT6aOf\nqzeF\r\n=D2dI\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"89b2d99eb417729b29942e17d13a84e4942045e1","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-18T09:31:28.403Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.0","@balena/jellyfish-logger":"^4.0.1","@balena/jellyfish-metrics":"^1.0.440","@balena/jellyfish-environment":"^6.0.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.1","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.39","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.2.3-renovate-internal-patch-89b2d99eb417729b29942e17d13a84e4942045e1_1639820117675_0.04974966259855074","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.2.3-renovate-external-non-major-377963ec3313962a0ae916e4079809e95d14603b":{"name":"@balena/jellyfish-core","version":"8.2.3-renovate-external-non-major-377963ec3313962a0ae916e4079809e95d14603b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.2.3-renovate-external-non-major-377963ec3313962a0ae916e4079809e95d14603b","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ff168a28187c3d7e03efebffa4266c38434d7384","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.2.3-renovate-external-non-major-377963ec3313962a0ae916e4079809e95d14603b.tgz","fileCount":215,"integrity":"sha512-AWxAzXGEnsmGvm6dZpqCYaaG0pgtl2iaUgIS2FIcfLzKD1oHTFOBCI+uDi6pTibzFQWd5LfjCdKL/7vwozdz9A==","signatures":[{"sig":"MEUCIQDnBtuS51LbBNec4WKqCkM4OaU2COSEHvH9jpDecxleaAIgJONSSAK7LRvdyFvTMjT0ZaQgNoU+x8FEIPiZtyybaWs=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":787228,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhvb39CRA9TVsSAnZWagAAXMgP/3MlWKczPj9MeW8vzZcs\nIukK5axOLDm4jiS3HKyKS6xE0urbBuFAC4rbguCLYlGgip8xA+exySHZtZ1w\nWOw07Rj0D8fEibMKUQ4+nU+d/YLCJjB396W3yl7i0NWsH0lcPQ162f0VPS+d\nDRJoQKfdOTz0hxc4/9QXz9TLrK0OpLgKcASk+Sg1J8oGQfXDgq4aIbjhxTpy\nxJAr/3opOaqICPw1Plq8wxVjSe3CKY7i+pgNUJ0aQ9zbVlMAOeanMPUQUtWP\nOwibf6oeD2+ZIEJ4BhaSRRCexVApX3kVoiA51xhxUjsdkM6irtavQrYXseDJ\nzNBc7hy+D6RnP/fCkTUc9s8UQmDvFWFV1QxFN5mtnBTC4kLKz8quvbv17G/A\nUCHoUQUvUejFYxUL8c/D6xTW2sdROy/pNaA3EF/Ih/XhrQ35Wf6fJrUoxTel\nVDH77vsQvbTPdxDRS3uxehqw1lX/5HN3YUI81b3M4DypZr3885BHdiJ3khz6\n/es11VnImtAn5QYAW044ai2zF6DYB+/MXFczeq+T4pXP7K3LGYyfR+BSy5ye\n5bfRpKSVtQyrOZevGi4ymX9rIwtmfKkUOiysFNaQxGfOBO3XNNX9NwYVNeh8\nH7N2g92ccDD3HBWRZ/ZeUR+j6Ha/7pjfWeCAkSIoOrmID9W1mTGdvSVynvTc\nLLrg\r\n=0rna\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"377963ec3313962a0ae916e4079809e95d14603b","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-18T10:51:36.519Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.0","@balena/jellyfish-logger":"^4.0.0","@balena/jellyfish-metrics":"^1.0.439","@balena/jellyfish-environment":"^6.0.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.3","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.39","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.2.3-renovate-external-non-major-377963ec3313962a0ae916e4079809e95d14603b_1639824893772_0.3625297280378461","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.2.3":{"name":"@balena/jellyfish-core","version":"8.2.3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.2.3","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9f7d0e2553b30aa6da35b45beeb835204ff780ba","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.2.3.tgz","fileCount":215,"integrity":"sha512-kFLWE4ln/pgVuhk0xDXL8ffe4IqqhmQRJxG/ih0rJEO6zLXbAkG1ru30TMCRCPChNkwA2AdjFfccGFLS2907qA==","signatures":[{"sig":"MEYCIQDzbdEl2uSclE69cC12x/jtzIh4pJT1CRgKxeqQ5aiT7wIhAMCHlvUKYADE+4THCFky5E1TMTE+VvTEL8eLkGyvMmHa","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":787195,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhveQ9CRA9TVsSAnZWagAAEkcQAJ3yvVJOcDAf0H1dhV1v\nY4RNX1sn5F+hiyuil+/ttOLTzg85y4sh76/sJeyPq/uPnUmT1Fp2hToXxUFJ\nmsj0sQFLDFakoIXPd5qNN+f3Nrpo3OMQdFzsuT11vJhRj8viWjWSKWl+TVwQ\ni+ZhE3313tKNL0a1HknLW5kA68MgAriq7bg9Q+aOtH7PQ+5jRhVwpUjiTxOO\nlEx7cvhpbtNKFZCgOK+voElThVlqm1Y4RggTrHqI3Pf0dU+K8CssqKU5eV1K\n04MxW7f2EndkFclf8FE1uNRQYuJ84jStWPhk1XOTjAZGN+/sQt4Tj2OUlcP+\nS0UHnPVRm9Fa4VDPUKfzXjXsEKeGpakEcB0a2UEqt/gAF/BGolFjWQnhoFIz\n8zKmVQ75HVDa5IMYG28kkaFp9hfR91eoKFQDVIbi4qxVEr8h0cvU4oYwKklx\nfHnJlIRA5FnwI/N06+xNLBj+dTDwkK5NDU2R4lHPMyIiiEhWbB7p94cHU4Vv\nZSJmgUVQzHZMGKtiyTQbX2+cml4ifEyxa/FFyDSlPKEHmHOXHaOwWhOaZASS\n+M4jAPGzWJq8rkGoNb1w2CGaSllwbHKBaqFKVDIbGoxyEVERUYKDKtZffK4e\ndKP8dl4klylHxbSIHxsYJ3lj/HMhUe6h0P3JUW87M7+KX2innJPynl5GV7jv\nPMI5\r\n=/cQW\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"69644c11c6a933da8fa106cc1984ea3d480e54da","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-18T13:36:04.916Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.0","@balena/jellyfish-logger":"^4.0.1","@balena/jellyfish-metrics":"^1.0.440","@balena/jellyfish-environment":"^6.0.2"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.1","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.39","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.2.3_1639834685191_0.005309304581417207","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.2.4-renovate-internal-patch-dbb7a6916661dbcda8bad1ff4ac5a35c4e574f17":{"name":"@balena/jellyfish-core","version":"8.2.4-renovate-internal-patch-dbb7a6916661dbcda8bad1ff4ac5a35c4e574f17","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.2.4-renovate-internal-patch-dbb7a6916661dbcda8bad1ff4ac5a35c4e574f17","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"156c29391ee4372beaba6cfc6d9fb19f58b72876","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.2.4-renovate-internal-patch-dbb7a6916661dbcda8bad1ff4ac5a35c4e574f17.tgz","fileCount":215,"integrity":"sha512-CKvJNUOF7j4oLGlRsCqsePdXy65aD0JXZHkdoZ6eXHEe1QHef1GUMxRGL81B4nkW/SUTvfMG7joJqIDzRzPkAg==","signatures":[{"sig":"MEYCIQDxBhUhV5izSUydmBe+cbvetF+Cl6/vZ/fUqOoI27jkewIhALoCngxnoAnx1al8lDTTHVPpmvf4hy8qOykSE5jTs1yw","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":787330,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhveUdCRA9TVsSAnZWagAAOowP/j8OylAYdzX+pt3rcYMc\n9Uk8WhyLeoONdVHP2ulklG2KGMkct7eC07/eCB8yPirKMYNcXQG288+8GaVL\nR2xFuu4tyCjh52Wt8BS5d55HcsDOD8QKrrzLjeB589Qi6WztwFcPqXHkWNms\n2DdB9SAi04pxm7ZrFe9ekGOY/j+r0qzSi9Ekch+KIdQshKSutSt1wkoIk94o\n/x9+F/DibY40FEKyAmgjkIjP717Tb2x0xDTf7isd73JnBOxyPq8A5igudZqu\nyxvamtCgyS3h8ePm0o3FjLCeD3jxcOVmLCoH6URxa5xCJHibXB5Sbp67/qQ2\not2TJdFxedJl63a4mJvxMyau8FYY9wf3r4iwW5ka+iLf6nX7mpoQncdvroqR\nwPWe24axJ1b6PlsPWZxsSVgy5Dd8shGIyQrCdMJNXDxSHJiFZ8Fb2MQHNom1\nU5I3K1XJ0HFKtUL1lvczFv72dN8u43InjjL5+VfuZcJZOdk5C8LNLImsa51X\nePL+hBFuUezTSxsHX0wBf3hNccE8zbpfw2cTRZQMitM1DvfbKYbF5WUknt92\nMmKpPKDAD9ZoqzH+KZTOYZV4vRHOTjySb4Ch/DYuuuRMQRofEEqjuAFj1W1p\ndqORyiGYwEDZxp8lRAALe276QtHgGv9TSmWhj0T+3DRBJTzCIbBQPCq2Di6T\nHj/t\r\n=1OYn\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"dbb7a6916661dbcda8bad1ff4ac5a35c4e574f17","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-18T13:37:56.864Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.2","@balena/jellyfish-logger":"^4.0.5","@balena/jellyfish-metrics":"^1.0.445","@balena/jellyfish-environment":"^6.0.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.1","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.39","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.2.4-renovate-internal-patch-dbb7a6916661dbcda8bad1ff4ac5a35c4e574f17_1639834909128_0.5991479459713562","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.2.4-renovate-external-non-major-6ee3e372266608cdd573d448289ba141032d07a4":{"name":"@balena/jellyfish-core","version":"8.2.4-renovate-external-non-major-6ee3e372266608cdd573d448289ba141032d07a4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.2.4-renovate-external-non-major-6ee3e372266608cdd573d448289ba141032d07a4","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"dbba6cd7a785007b2096de3aca7118a8f3be3df1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.2.4-renovate-external-non-major-6ee3e372266608cdd573d448289ba141032d07a4.tgz","fileCount":215,"integrity":"sha512-uWN6UAMXjTA6EoDNtQVTz8F5P5OgGpAm+inpFE6EBsI8cHR4TbskaVAUaE8q6pCi5igZwek9I6AnDEUjFqC4Lw==","signatures":[{"sig":"MEYCIQCCfnzRogGr4hElSAAF0MbZ550Y+fQYX76erqgUV5BfagIhAOW1xFLhosmsz1tLWC+VQPRdjZNrwMBN6QCpRvCrLFgJ","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":787338,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhveWWCRA9TVsSAnZWagAAGKwQAINRjaweNYxK8ldix/Ou\nV6AqCNZkmg8DUWNcSF4SDlWjPVbSU2cfRgZTd/JExd2CeDI1rUXJHre3ONrn\n0/PWZQ9Uv7GRwQuXG9DvwrLVRt9I+jn+wx8ufxyqboMVUEnYwu3EUtnBSxJd\ny64/mGkp+jhyJA9OAKqz9N7geq8t9kEVGoZIPFJh74fQ5MxyDVkMyzkUOYWS\nlpeN45TWYCKFDoh16wnGd71RALLMNF44v1KG9FIwpICTxF7eLX/+sYIElJQh\nPUA4SrJSKE4ixUkea+oNeaWGQh8TyMGTdcbJfFPff5H4VL/FnT8/gGJhSt2W\nohvSI3xmImzVIGV07Fnqm70NVYphw9a8/QHeQBxZLd0pYXH+/6QoeLTehkfP\ntDnqSHYM4TLEmTzFo/HwJyFqToeXt/sd65RlKdNuWKpK/oOyzyeRlStIu3BZ\nof/sc7PPMgIoqnKzCrdhSzB+a/tjUu+QlcdV2/0YlrvVPfCnQFWvr8+FKEu0\n2GT3zFzPFRoQzMwdIFsnGwsno5YhItq8ScrRPLi1bb1VRuNYB1qxDgmd7CUj\nIsJiFBzk20Vf5PLEkuMnVGQdkAyTKFLToGSmnUQSr2fN5XlsVi0VEYMGEN8g\nmwLpjHMhn4H0ivu01PXCW3sF3TzQ9chh7xenWVDPXPCmz+xaEC3Peo6eRunv\n/Q/X\r\n=ZNee\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6ee3e372266608cdd573d448289ba141032d07a4","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-18T13:40:10.882Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.0","@balena/jellyfish-logger":"^4.0.1","@balena/jellyfish-metrics":"^1.0.440","@balena/jellyfish-environment":"^6.0.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.3","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.39","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.2.4-renovate-external-non-major-6ee3e372266608cdd573d448289ba141032d07a4_1639835030274_0.5381211540024275","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.2.4":{"name":"@balena/jellyfish-core","version":"8.2.4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.2.4","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"85c62a431ce055b8ed9476455af4659703d64376","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.2.4.tgz","fileCount":215,"integrity":"sha512-oATDxgS/EEU/eL2jemWH5asOCN/Ymp72mTmTEzYNCNmmK15qzLZHZN+vSdTZTPw8xzmTdqFDl2Kujh3mJ6U/pw==","signatures":[{"sig":"MEUCIC3TVaoa7oTlhs8+OrWSitO5h61MN7oGdoaodm3mW0jLAiEAl5D+R43hpbJmU8kL0BK6ik+4ytQBnVgxkalo7CpaNHA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":787269,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhvefwCRA9TVsSAnZWagAAen0P/illv2GimXloHufeUTww\n0tNVHmsUtWtADuVGdomYYQAM9COUz6w2Y6CccxCavYGTMTOBhxZBKr8sAKH4\nMfQ0bPn8Dg3Ehi7vF95Fj6jopStijlzXro5XnIjqOMmVQyuFrxHHLRi9h5a8\nbj/ROWjOnq1u3g8MBjgq8S2DEFRonsxQlTuU8rMcjEDe9tM+Dajl5g8eDkuj\n9vTGkRnF8yAMAjvrhc/RggTnJpqiPGYLLMzuA1PeMDoUO8psjeXtaIQm91JS\n2CI5N4EUt0Y23YNHFNWQEg4/4NJSSpOIxeYdC9V56HuurwKIv0SZBrEHRa0D\nXind84Pz21aZj5kL+1uRGOGngtRrkFbrwPm47w2yXRCc8UaWjVDpsbzXXfnz\nmardIAzsFlfq6cxn18q0NmiGWGbnRqqFTwjr00j17zy94sbNgapttKv49Yii\nysL1lUIyvbBbKb4O6MbHwpDIY5GsJ1B775Ed4hLlWNMzn+rvupzUvOicLKsm\nUvrIwSdwKwmeFcwETPPdMCqyfd2fHFVRqRhSKLLRSfUnDb3I91tuLrDpYk68\nDjTCxtPPqnM3rFextf0ni1py38Lcj54x8VDkPde+OV5iWL5+qfcJS+DkcrSM\n5b75KDGuFuXrjoFjh1fokA0iCjk1WtLKudqkUExC2uzaLhP1Gc6TlgEulRTh\nd9DS\r\n=9r08\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4f6a51940cef6d1873f71d3f883f5effe860369e","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-18T13:51:40.456Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.0","@balena/jellyfish-logger":"^4.0.1","@balena/jellyfish-metrics":"^1.0.440","@balena/jellyfish-environment":"^6.0.2"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.3","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.39","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.2.4_1639835632129_0.7651797515346912","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.2.5-renovate-major-json-schema-org-1ce0c43e75fe22728c6064986f7063315e62fcc2":{"name":"@balena/jellyfish-core","version":"8.2.5-renovate-major-json-schema-org-1ce0c43e75fe22728c6064986f7063315e62fcc2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.2.5-renovate-major-json-schema-org-1ce0c43e75fe22728c6064986f7063315e62fcc2","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"490e6d3a9f076f27abe844499712d71f61fba035","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.2.5-renovate-major-json-schema-org-1ce0c43e75fe22728c6064986f7063315e62fcc2.tgz","fileCount":215,"integrity":"sha512-8A9X9Ryfqrdr2GDOtMKSTneE0HEU5wP2YmRB3d212CDcIOUtFrfY1+Y/NuFGAV/2Xp36dFyFKhma0Zv8Dtj3Rg==","signatures":[{"sig":"MEYCIQD3s5Vu2JPgVLJ8Ay5MZSzGG5MhFex+awCbwnhgOklSrgIhAL22UBj/RB14OiiT8WbRUEMOZuHfK2/qI7I5AWyTMvdE","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":787436,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhvejBCRA9TVsSAnZWagAAM3kP/RSJVj9TAIe5ISunflxf\nWNon6UZnGhGH88maQp4utN8l3T/FSuM7IQmbyGrv9o9SVHBVR5TmsR7NirrX\nkxX6+sOipQsqinxu/77wKbNmMtaE1U83PclPMnw6ZT+kkPfiuTnFPmI1aQzY\neDHIFTsE1YtNkl6wTW2dlWnrD1z6e5+mvHFAvAgHNrCgKClFqwMYPvCKKZ2b\nl5GQCQ91nWVxjdNqOCwI1sf/dYTZ5oEjLTn0uIDyBSmXNrV/kkRjfMlVYrsn\ndbbVmyGkZLcU1fItWJziq2wtjawZM8is44Zezx5T+g8f9d5qi4NIo6A7EGZw\ntbsUY2PVYIEI3xrAlkbmAYOpeqQ8ijIYJnE7Wjwjni0JO1XFei3LJYqwEmDV\nqP2mq5WemI7txSY5bpN6S1DEAcjgFS2Lz/cA4G4fzUOiC352ULkIN3TrEW+m\nmO0G85fcV6Tgg9g3IhYsE2+Mooj6hkt9QmbmJeHRk1/Fdd9vQPMTGANeURLT\n/JY1wOFSr2vwW5DXKwX26kuuw1sVcnHy0yQZJevGGD03fTDT0AsZjgyxfGnE\n13hQy7jIo/Qvlw8bKxObz3n6xJc8bVtOlgQHj2cEzsKytm/IW+Hq8nm3J+e1\nWAlbNR95XeqK2/24aE4Rm7D9QycwwKE86WlbGoTZOIQO5ZRx2j03QXPvi+Xx\nj5E/\r\n=uGXD\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"1ce0c43e75fe22728c6064986f7063315e62fcc2","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-18T13:54:17.235Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.0","@balena/jellyfish-logger":"^4.0.1","@balena/jellyfish-metrics":"^1.0.440","@balena/jellyfish-environment":"^6.0.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.3","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.39","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.2.5-renovate-major-json-schema-org-1ce0c43e75fe22728c6064986f7063315e62fcc2_1639835840749_0.9521781916313519","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.2.5-renovate-internal-patch-d5eeee914e438a515cbeda86865aecb73ca967c3":{"name":"@balena/jellyfish-core","version":"8.2.5-renovate-internal-patch-d5eeee914e438a515cbeda86865aecb73ca967c3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.2.5-renovate-internal-patch-d5eeee914e438a515cbeda86865aecb73ca967c3","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c90a62ac8b906a6d022a12f126d20a80f8b59acf","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.2.5-renovate-internal-patch-d5eeee914e438a515cbeda86865aecb73ca967c3.tgz","fileCount":215,"integrity":"sha512-bkQ3g91XO5TeiyltMhZ4mu/c1aZR58CV6Z0LiD2Mr+Kzjz1Jlyjhzl18sD1p3aKxYA9ojZdCPmrUhPEPh681Mg==","signatures":[{"sig":"MEQCIFEt36zp1WHhp4WZaICuxsTwg3OtgB2LaV6toXh4M0ZBAiBhq+gnSCXDEYcHntp8N7VeUwTdnF0gSGfXQUaTr3iPDw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":787404,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhvemPCRA9TVsSAnZWagAAK3QP/1labPtXXexUZHm2YOT7\n7XWjvPkb7k1qUKVI1wdKapAahWbsVHAkZSy2Qt9fo6hBIasNhwEDGonNVOpY\nGJ0dfe3XmQYqi6EpbWlzztBAsBf7m0Qxrzd7xBXtvFCCI8FOxrrwD3c/EZFs\nWrbAGlgNwM/LSOW5QEgvXcwIbdSSJnsz97KInO67TvmwoJdwIN03zK/JP9sA\nIlWkBaZ0iOlera8jT7MWsXL26D6/U7mLZ37h023iNX6Iuhe+Frzpjw0uNLuC\nQIRTmdiXSpzKkMfQO2GVPfsaSkt41p9Kj7+wLdL0cW5Ca35Qm7kgpqUUj1pc\nkOrXKCZ31YOSIug5mOlcb2kP9w/xXMxqequYOumTZwPDnUKTpaFaN+LwuJA9\nkiGli1kPpH2EGGxgLcUVyZEi8iFeCTnV+Wx8mz1IJzm/V5A+YK2yOX1XCo+m\nF007rJpUknIlaMmMAV/3U3B8aU4dK3yhLdY+kjP06JODM6t6XCptEa9Z7yN3\na2VsV4wQANCYpEXg4X2rBG/dbX7kAL1igw17xpHdUccNrMX77R+Il0ulWChd\njMLX0KdGQVFK0YvHN1fldtRIgzU6P1hBveAlcmNQyVVQNQ9iceBTOdYsXizE\nveEsxHn5kjqSbtejrDZYsxXK1y92MOogat0EfsnTU25c7eNuGDfdfoYdf8xy\n6UiR\r\n=+ren\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d5eeee914e438a515cbeda86865aecb73ca967c3","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-18T13:56:40.239Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.2","@balena/jellyfish-logger":"^4.0.5","@balena/jellyfish-metrics":"^1.0.445","@balena/jellyfish-environment":"^6.0.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.3","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.39","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.2.5-renovate-internal-patch-d5eeee914e438a515cbeda86865aecb73ca967c3_1639836047012_0.16707288859299485","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.2.5-renovate-internal-patch-1baa199da991d4ccec8628b3768b41a9c7a9c245":{"name":"@balena/jellyfish-core","version":"8.2.5-renovate-internal-patch-1baa199da991d4ccec8628b3768b41a9c7a9c245","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.2.5-renovate-internal-patch-1baa199da991d4ccec8628b3768b41a9c7a9c245","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6291cc973f08bd9e94c0c7f6a5dcb3e549383e18","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.2.5-renovate-internal-patch-1baa199da991d4ccec8628b3768b41a9c7a9c245.tgz","fileCount":215,"integrity":"sha512-om8LJ2bjHgnfKrYSWhRpvyyvqsvbJdXXx11STjI6Dk9Z/t6ZUsZcyShVVtndMMJPMi40KN/zcQa7GYMLuDbL2w==","signatures":[{"sig":"MEQCIFzRpHGeaZ2Kx1hbBoFwgdawrdxsqwaZsurpwgAD1mTGAiBLDZn05B0vxknY3HqWj/s8oubSR8nL0Y5BE6y9S4yh/w==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":787404,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhvepsCRA9TVsSAnZWagAAw+gP/021nPlrZVvpeAsXNmSz\nRdJvsn1Vk6jllxBXuU5gt/cWSxPKrxz7jxg7v0zteEU6kwDV3GoVp5vSBz3J\n2ng9mtBq76fETut/1xie5l8Ep9uh2RwoO3JO3eG+14GTy94xhR94XJnW5r9j\nUQRjqBkF9rUcnpCxLpbh+lAqaL2CLZ6MZLOV+QI0gZ+mIjzBYUv6bw60b7vZ\nzp+XEioiEd39VzcR2vf3HYqu9CLwWfsiQLozTNr4oooTXYCrE7A20NBTgg47\nU+lV+gysz2KEDhwGM8C0W5q+zyJDve6kbDuxFgPyKa9QRgwNZxqudp/Wm6J0\nX2cmYd8yDCfBRX+2z3pQm2ZED5Jv4eixFWkjT1e1nu0oupirktM/vrcXZmMa\noN7/ZxI/TfDCcYRdaPtTEDuucFUjuqoXpCSwOJ2Yj0SuDffKkmFidnACRI4J\n2WvY/aVSzaG/yAOgbZCp2NwMOfxRS0rrvQ+VRsL0aJwNREckrauIwOuhzQ2a\nH6Svz43c7+r5K506xYwrLUIqxBSgIx06riIkxBJ8Tocfc7Kir184fA0hgAZf\nWa3c9fqDoya4I3yGLWcDW4a+L2IgNQ/7TPu0jrLiK5L8MS4Nz5dQADUyKylj\nNrmIQS7kF3J1XLbTCkUrziAg69txwU7hBPsE2F5IC4NPKmNePrFeZoWmPHNp\n3+NW\r\n=kqre\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"1baa199da991d4ccec8628b3768b41a9c7a9c245","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-18T14:00:58.079Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.2","@balena/jellyfish-logger":"^4.0.5","@balena/jellyfish-metrics":"^1.0.446","@balena/jellyfish-environment":"^6.0.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.3","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.39","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.2.5-renovate-internal-patch-1baa199da991d4ccec8628b3768b41a9c7a9c245_1639836267979_0.3720471196870341","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.2.5":{"name":"@balena/jellyfish-core","version":"8.2.5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.2.5","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d2b112243473057ca74539d6c9ecded4f83d1d08","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.2.5.tgz","fileCount":215,"integrity":"sha512-/iJbJKHhI8XvV5kw+d+VrP3ybw1Hp3r4GWZ3DY7hHUONQ/8O9eEyYUuLIczRQuFiAFu23FjQr1SRf1r/apHZdQ==","signatures":[{"sig":"MEQCIDtxGYLHCGVbo8z/0Hx95jxLHX9ug2EKcbkZ4tVA5uWKAiAkisCsOMZAN9FqxOghLTICam++8DJ1HVP2oemJrJHKyg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":787339,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhvev7CRA9TVsSAnZWagAArFwP/RoQOeIc6tG7TuROMKIT\n4uewoaQj34KY8mit04NiZnsSJxZ7HZwCQ1epHNvMIgFpF1Z9gMpqRyL8gF6e\nYsJTX/BJb8AY946RQSAZ4+kFv1mbyB1DLwqO0e22JFFK0KPreYjNTgTNwRsJ\n+2fgpUC4x4IXDPQ4tqe+4IZYa9WesD+RbnfDN49mpnS8RxWV4WsYAhZ/Prz6\nifiBdjcNUC++baWz7JfGTXIcJ9yTlj8CP+YlCP9RGdQCg0kVap+QEDrBUfP4\n1j0CQN2N+N4Rx2D8WgDdlc6pqrrxAVL5T/lmM6mo+uQhd2EbJpVEhGz7eR++\nWi8liPCJsL/v/FMd3UzS6dnH4Siz9j3RZqNQNN2mRXK9ve+/y81mOQVYubmz\nFBCqO81YhxrFKmNqHMDWHRdGKKmu8RGUrJ8rxYMcddtKNdeEW+Dyr7WebvxZ\nmCNDguLiLXUcoDVYTWkguK+LLGZFccRl8YPourtz5oFM0m/P29be+McMCBJS\nYtDae3gijyx0Of4j0LLw/YsGeAvXCDdz2UdFxrbLTEvRyXC7W7FA6q235R7V\nNqo+KvquYcEC9lw5ttSrQaX1CbkdxIugwPeM02HxPbAp6ujFgCKbc34jWuIF\n4Y41jFq3YgUoEsSzeVps2f6TUq3wtKpwJRYkRTh7uI0Bhun/aP6Xwhx4y10h\nwL1i\r\n=2WkR\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"aabe6c2b9b6d4c6b8ef40534a9f0feb7cd4d3065","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-18T14:08:46.993Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.2","@balena/jellyfish-logger":"^4.0.5","@balena/jellyfish-metrics":"^1.0.446","@balena/jellyfish-environment":"^6.0.4"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.3","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.39","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.2.5_1639836666797_0.1209947748436917","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.2.6-issue-1128-82d78dd87e93d386900d4e385aac7a620c0a42c8":{"name":"@balena/jellyfish-core","version":"8.2.6-issue-1128-82d78dd87e93d386900d4e385aac7a620c0a42c8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.2.6-issue-1128-82d78dd87e93d386900d4e385aac7a620c0a42c8","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"431d06e53e6bfc834071248d7f7835bb15fcbaa4","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.2.6-issue-1128-82d78dd87e93d386900d4e385aac7a620c0a42c8.tgz","fileCount":215,"integrity":"sha512-BcMnS62h4of305q9th7QHOqdl1cUsIaWLzt8g6Sb8/lmR+Qoa8dww7YvmxVL2Twd/rSi2HIyiyWs9loCn3M/FA==","signatures":[{"sig":"MEUCIQCBULCFEKwIfitka9Y2YIZ6DQpR6MZdpw8qasMYTXzK7wIgGpIXkMgErRN8frpAnCf4QWvGomQAqbGatnBnjruOhrs=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":787456,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhwLnFCRA9TVsSAnZWagAA2lUP/jqzbVmPyBWinxWhvtDt\nN/yvMgnmQ4rAyp1sl1bsHLY/KSo/pDdQJFGrRLoSh4O57w2cl1bzBUu41Caf\nj8bEVoB91M5UGslymHJ5js/qBUZJM0nRQNko8/mc0oIcC7B/FRghJLcHGqZA\nQc8Gz6tgl62q3Lj8r9iy4wYKiUke7hOHK6tnlgBmVAMp3/d0cTWCl5TKQneJ\n9cGeDm7qmdWp7Lq9mXfM5KPRR+KX01no/tlA+A2pHeyHBlx/iOvWE0S1TBBr\ncHIJxMmNyHdKaSJDD6oOnZLJwGvWpNPX/B7x96HIqWXRJp6R/6nTyt6qSOJI\ny16l03EjOVf01BEMhYHFB4ebbjlf1cFIpuRoNvOSG5FUrTrVYz3maThEgjYk\ncjFLf4pOknIO+X4JtvUn8AfxHdz4crwVnFP+EK6REiwbaYIQxt/8royakTCT\nyCJ7j4nme65AZOK9i0MSieBzBf4TUcunvGE4RuTNIu4iZNT9utCOAsmv82Q1\n5CL8xJ+s70YCrwvha5/7aH7FuuAEWZEFMiKKLwATAtt71KhQj2x4ojbn3Lsv\n3A5VLdJ+nCx55jlCbNu666WcV+uy/9vTbeMKyNOQgkpFkcSnu/fhK0lCEB+3\n2DJKCviuuaPGV4C0gXsbITZuaylNCHE96p1p1akM1iuEz1Ms/dS8EE3Ad4Y2\n6v5D\r\n=rSdb\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"82d78dd87e93d386900d4e385aac7a620c0a42c8","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-20T17:09:23.822Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.2","@balena/jellyfish-logger":"^4.0.5","@balena/jellyfish-metrics":"^1.0.446","@balena/jellyfish-environment":"^6.0.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.3","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.39","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.2.6-issue-1128-82d78dd87e93d386900d4e385aac7a620c0a42c8_1640020420945_0.33325143211801","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.2.6":{"name":"@balena/jellyfish-core","version":"8.2.6","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.2.6","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b550a8e36abc82a94bce5d7c348f1e12ff8a34d7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.2.6.tgz","fileCount":215,"integrity":"sha512-U3oirYlcOUqjcn7TrFeRPNjPaquKAkCNY6qjTw/kBbAx5NUh7XuQedPjAT6hN2QtwQOUtyLDVPDDTSwkruZNKg==","signatures":[{"sig":"MEYCIQDHIFB8od9KDbPEusCpOsGPVZWyCAilh5QPBz4EopiNCgIhALFI8gHXLI3y3Jd7XePNH1RXZml+XAzpdjR4IPv8I0BL","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":787404,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhwLuJCRA9TVsSAnZWagAATP4P/0w1pyjJqxCgAPeI6Sr6\n37VbQQRtfFPPL92hAmJM0TtVAEN8whQQtC4g6HItKvY1SCQqsAT/Jrj02Pe9\nzBzaWSz3NZ6TYimeK4lG4ZLl20CBAFfyiwFn2uVy207mPYjM2IDrQETw1e7q\ngtEpKRLB19FW5f9tS4qfiJnWxppZis4J5eOQ3uepo4QKA36BXkwZ/b0CLLQK\nOT+KtdWFw9DCfeIr1ox5UVrotZAggMgc3UoMmVXJcqd9Mzsk7BaLwXPfrNX4\nl3Oa8KBLlw+PMOdngdG1wXqQ31dsJvEZpDGB1LE3Mx4YWYUKPmJacjeP4MUq\n1vsaIiWM2vI+g+O60cZFaKyKnx64mflmnIK+YbSQibHir/Aw+ys1hVDOpI8d\n8rtx11mfY9iH9DKjjAtBCfN4MSqgPCGanKgrOm7ngovZAy0V/c+TG/Lh5UAg\noDASbYuQLRW9tvmIyHHfL92rnLsXm/Pp4Q56KwphGbyl6puk8jbYijI3+bw0\nfPlkB8goYW84JMbcUOhHtSIxQefUqs56bCGPA0Ou0LJMqFwIkmPjimsobeL0\nSm0x9TVcadLFpXnTqfzehd69HhYwX87x6MkPaEf2M4kHdl79CbcTSDMlp3mx\nrVSzrScG2lixGn6M3cNPWQpC9qcVtFLhp/HP6HAM9QgAGJfDOOo7Z6BZdzWe\n+TEC\r\n=0Wmk\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d2aba2175894c4f4d4208da2790833daa781b095","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-20T17:17:19.403Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.2","@balena/jellyfish-logger":"^4.0.5","@balena/jellyfish-metrics":"^1.0.446","@balena/jellyfish-environment":"^6.0.4"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.3","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.39","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.2.6_1640020873606_0.732482436883702","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.2.7-renovate-major-json-schema-org-9bd7d35259b2c2c5f01070388e4decca09cd6b8d":{"name":"@balena/jellyfish-core","version":"8.2.7-renovate-major-json-schema-org-9bd7d35259b2c2c5f01070388e4decca09cd6b8d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.2.7-renovate-major-json-schema-org-9bd7d35259b2c2c5f01070388e4decca09cd6b8d","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0d4e2a223229e0a7e604c793d0ac5142590b803f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.2.7-renovate-major-json-schema-org-9bd7d35259b2c2c5f01070388e4decca09cd6b8d.tgz","fileCount":215,"integrity":"sha512-/Ec13bCYVTp5MTDItAicT5B5Y8WS/sg+abRCxv9OKZiPGvqvQ8x2yiK0pg7kR6fJnLaH46jKsNmUpBTPDnzWfg==","signatures":[{"sig":"MEQCIHf8IWtLy07Jo3fCbBHAPjn9dpFjoGl3rho4OgD/HbjoAiASYxkSNpIXuJJvvoQ4rk6+qU4MyJd0P/ZToZChzZdF3A==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":787571,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhwLwsCRA9TVsSAnZWagAAXB0P/12N22jFsXlwAVXFtCyb\nVhjsF6runirOJvq7yQO2bjExzNlTFjJMUxJ6u41RDDvhvQPFGXzTjdkUXQjc\nvtBoxBp11qMDaLq4p2qDYD+Jf4crRoyG7gVPtIh4nRweZ+Fz2AlRMqBnjqRC\nNf7ZIi3AAZ89NBxYOFxiW/XjHeinnfUi+gTh1B/3sgifsnv0gtEuwGwPkKih\noK3rCKXVaa4AC+gVSdBnoLM03qTYhyiPnD20keqBeBzomXCXHXdl8HnVM35O\nk1xETtqPPe2oALK3kUmFQiwK2qnF5QqyHu8QtbLyi++Nydb6ZuEXnJ6Mrvv8\nbI5NkrtT6Z3jzQN2wUe8R22g5AGtp0rFFTw7Kb+bkwjfFmsJ/35mUcc6J2aX\n2eUq1wJiMSvYZE6ltbiDscFmlxEEIbTURuUXv07D11dkx2XWoC1+dE4CapSA\nwy0WSQWbLAm8pfouPJzH+dwgfttYSC9j1EvhRIx0a6mrb14gqhNieu7EY+C7\nZyy424ySzudyZI9UCxbtAekfsmR0GII+EM45VIN3H3c7ADZ5w+I9eC1ao8BV\nZphnkqPNBiCtlgsMoOhRFFjMgZAvwA2KXmswgCw66SBOBGMRi3Xpo11lTmRx\nFFw4EOPZPvBb2QgfdfgCOWQOA2yrsDtPBNBKJFOACFfXBbnY1HFJpb9QOdM+\n1hWs\r\n=eYuQ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"9bd7d35259b2c2c5f01070388e4decca09cd6b8d","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-20T17:21:09.543Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.2","@balena/jellyfish-logger":"^4.0.5","@balena/jellyfish-metrics":"^1.0.446","@balena/jellyfish-environment":"^6.0.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.3","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.39","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.2.7-renovate-major-json-schema-org-9bd7d35259b2c2c5f01070388e4decca09cd6b8d_1640021035936_0.907237797407888","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.2.7-restrict-jellyfish-types-f4e200f7e865fb460ffed05f47f5122e843ad016":{"name":"@balena/jellyfish-core","version":"8.2.7-restrict-jellyfish-types-f4e200f7e865fb460ffed05f47f5122e843ad016","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.2.7-restrict-jellyfish-types-f4e200f7e865fb460ffed05f47f5122e843ad016","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4f37cb440767f34cf54fa273f8d4e9058a137df2","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.2.7-restrict-jellyfish-types-f4e200f7e865fb460ffed05f47f5122e843ad016.tgz","fileCount":215,"integrity":"sha512-RR781JGOAhk/bvPOynZVK/GDH+wMwnxW3/YYY41uXJYna+d8FgXjOXsrdLBaTHvt1qpaz31+M+yeznOmi4cmFQ==","signatures":[{"sig":"MEUCIQCSt+wumbITxRnk8ClROL3xC+TgQhY436s1Dva5Kne5qAIgQnhdDQHGq/rvDsfxaBriAnSXp7xkL/YvmApQe6NPiBo=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":788117,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhwMZvCRA9TVsSAnZWagAAFy4P/iNzmpEpWx0k6Q9xdPmX\nvmWM4TLINvZEJbYR2VM5QF5d1bM00JZPEmCkSqtlEdj4UjaUWt+JwTWjo0cU\nilTZmvc+etR1brvEvFLZzr52qS8lTsTxD0jAo13LKgzqCYDBK4RrixJyOmrg\nYrbvcJZYNurCMdGU4uGijiRTNdigYq7Izuqu+9qLQJPufjky931uKawrFnud\nhjVtzpXfqZDUXSb0DKrdvJMU8RvveisxNAF/OKtCNabOrV1FhxLHrBrMt+wz\n6xT5htJFLPBX+C8u/QmIIfMi8RFPeD5gri9DIakbIqafljt5g1vz6/SKsWko\nTBsxa6vprZFe/NlRFL8Vea2X9xd6pd5R2ubaJY+0fE3yoi9pGxYq3QC7h54L\nUtC4BepxFDnMbqMbSMBdG7eoMygqSdPOzzbO9Fo8bPw1JdJBPZGB1v+mFdV7\nZugihkTAG6DXbXxkXgxmrQ4UYSR0XADAcxpckz8+Ek55h3PN/5vvBt0ee+wW\n7pmti/eE9SefFq31s9897UsYHscGyDF55HItaHlEY5V1m5wx7VI2A+Ehfpks\nsT/iATUdtuae0JWQE2pWSVIGVxHQn2aKZcSDYxsbkXViJ5nXfcWS2AwpoEn8\nX4tOPOtZZLCUCS/YY7vDUXhPObzgtj6Q/CCfBW6lPAnHEPcNRFwWXHlxYrWz\npNVx\r\n=yhf/\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f4e200f7e865fb460ffed05f47f5122e843ad016","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-20T18:02:51.360Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.2","@balena/jellyfish-logger":"^4.0.5","@balena/jellyfish-metrics":"^1.0.446","@balena/jellyfish-environment":"^6.0.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.3","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.39","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.2.7-restrict-jellyfish-types-f4e200f7e865fb460ffed05f47f5122e843ad016_1640023663504_0.9245871594124935","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.2.7":{"name":"@balena/jellyfish-core","version":"8.2.7","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.2.7","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"947fca30e1b29b3dd4ec6b4114ddbf10c61590ed","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.2.7.tgz","fileCount":215,"integrity":"sha512-ugY7yD0JPRpfA0SNe0P/xRSI8v7/O/vbyUeVKoRktkzdGj3uHZVZcGVGCEKFlSi9uSiBEnJUyZW0YNjb5SfGFg==","signatures":[{"sig":"MEQCIATohihcf+tuo3icV6t8a5q2PduWebhSqGYMTD4owhgBAiAbPAF96zixPcWiZV/gv2Y6H//t+efkbtAE8Pa0tJcbgA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":788051,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhwMh0CRA9TVsSAnZWagAAaqkP/imLP9tTcgAnycqTfcMM\nlKewDa82hbuYp6XeT5W3WodagOsfVclNar3U882xvzv8yRSJux+fE39cLsaC\nhrHw7fOD9T+ObQshJWvkXQWSBBohW/zVlB9xJbyI1T/M84nn+sUE9VXmCEwW\nPFoQoQA3XJQCuJ+aiyyOYjXWvJfNHaTWKrVKntBrUecygEafx9/1luAegSFG\n616GSWy0ZEKNG3Wfy147c0Z8UsFOdMg6H81kg6sBPzqauPfFUYbaDtcovrS9\n02vo5BctAWJjIOsXxUCosnWJ2h6o/blS/j8UjhvN7R2WSH0v12mgLkXR0UTU\n4wB44mpkSVAT9YCoa1Cqlcmdc9oEHDzLxICesm//Dp8VXWLFLUFmUD0TjOqA\nm8ce4jtjfc+DytPUqWudeC5ZwaIX97sxWDzjR8JbqYPr8dF19ZnIreLYB5iQ\n/7H2KGl2FP/sPJ3WPCpuBNQvTlgQRc2LhyJojRNaVBVlqkCW3QMXDZ0e+xr2\nLwTRoOCv+NQHPhTa9FU+RKrquq54CFgLp9P4ApWUCjn1t4+2k3rlb0XPhmTC\nMohts8ZHNXPPx4N+TtrUTtz3H+xdx9m1Y2LOvOR7gVHTf/AY4l0PR0reAWRt\nwIH/FENbVdDSSi+dJznY1IVGuURA3IlUqGD6n1gWerbxiFHr8KYOgbU2BLXF\n8JTB\r\n=Cc9e\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"804bfe612915b24551b90d61882d69cb553d8084","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-20T18:14:27.375Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.2","@balena/jellyfish-logger":"^4.0.5","@balena/jellyfish-metrics":"^1.0.446","@balena/jellyfish-environment":"^6.0.4"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.3","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.39","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.2.7_1640024180143_0.9976937017999923","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"8.2.8-renovate-major-json-schema-org-2be6e6a1098ad1bd2028ac241a123598a2ea4681":{"name":"@balena/jellyfish-core","version":"8.2.8-renovate-major-json-schema-org-2be6e6a1098ad1bd2028ac241a123598a2ea4681","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@8.2.8-renovate-major-json-schema-org-2be6e6a1098ad1bd2028ac241a123598a2ea4681","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c2e888ae1b72051c6417357e6aedea5756b68a31","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-8.2.8-renovate-major-json-schema-org-2be6e6a1098ad1bd2028ac241a123598a2ea4681.tgz","fileCount":215,"integrity":"sha512-l5309qnb7VZPOm6Och3W0EYA5my/rnEh9ss8mOVodvf4itnnU1h7AJR734wan22BwYH7NSCDMzOvAiXwMEqFDQ==","signatures":[{"sig":"MEYCIQCbTqguMWxP+yOZj1stM9VBuffyv8u4BHjliY6O2NontwIhAK6h1lr4gFVJKV7ZdGUNiwfKwoAEK9UTwGmjQ+WVEEZr","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":788218,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhwMmwCRA9TVsSAnZWagAAdPsQAJL4tVUD5DWfDeB3SNss\nvDJa6bIARw3di8X+g4A6YHVhS4bFIvjwWA4OumlCwoUTp8k+JebdivTyURf+\nEwB3/3WrwuCBIn6B54B/ob52jPK5p2gYZPtFSm49AIGRch12t1yS1DglpYtv\nVMRPvDW/orSfq84cfTk0DUHrz9nb+dqhfkXSP6k41bDf9YE0ewA2ss85VhYR\n1SSuVADLuUU2D1tZRMKS4nYKxxQHe7P8bnQ4jRvXROpqJUVRijsc/QM+TbUX\nuOqSn6fAC4AhdymeE15gBmns3GVF5sTG1Q3V3k2snKBMuX9rVxlFR6HTGthE\nefLEgNP61/Ty0mfPZ0ScUZQ4oAQAKWKhwVbfvehjDkasv+CFuKvN9Sl15Wli\ntitSJ5FkpoTRoJIjG2q2+xoDZNiZOTNqs5ZRF1aqanTV8bKiseecBk+rv3Mc\nbG9HDwQkV14QecuVgfubw9pcOjHQ5z9sIQZEZcINBYBS7U0+2OVArfb+iHkq\nYTJtp7hJA2qW4tmM9PMLjm+c0X/LRfdtgHJSs/opiAWB522pphCLEIcOAXbv\n1AhCZLMxDL5eVQJpFLiQNXdqRGfxkgRHyFMeIzfZtTagPYWfBciWErIKDVbK\nzpmOyzqR/9dVU7vud1PMEGNITXCebOXFeE49fe3ZH7TNwML/TvdTm29pneMd\nE7yb\r\n=74wM\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2be6e6a1098ad1bd2028ac241a123598a2ea4681","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-20T18:18:21.453Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.2","@balena/jellyfish-logger":"^4.0.5","@balena/jellyfish-metrics":"^1.0.446","@balena/jellyfish-environment":"^6.0.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.3","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.39","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_8.2.8-renovate-major-json-schema-org-2be6e6a1098ad1bd2028ac241a123598a2ea4681_1640024496694_0.6250701293046446","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"9.0.0-export-types-662a2787e7ec97c8b0bfc3826c22708fcf135e0f":{"name":"@balena/jellyfish-core","version":"9.0.0-export-types-662a2787e7ec97c8b0bfc3826c22708fcf135e0f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@9.0.0-export-types-662a2787e7ec97c8b0bfc3826c22708fcf135e0f","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3e21525467f9da4460104f29dfa05fec8d659f05","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-9.0.0-export-types-662a2787e7ec97c8b0bfc3826c22708fcf135e0f.tgz","fileCount":215,"integrity":"sha512-rmKGcyudyphcztAJETgFapqO8nbQcfgb2RVOzf6xc6EoxQeO9yM7ATc4BvtFAEZwVGQZZu1vErdaHxWunCtwVA==","signatures":[{"sig":"MEQCIExEIDCJayyqceY7uOV4iRgRjpV8ISXiasoCOcB+E+iLAiAiGshdyAPVAvArwGgLlfu0m9FrMzUycmCo9Ta2m7+otQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":787935,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhwMy1CRA9TVsSAnZWagAAc80P/iR4Qh8czxTFsEK2JHK5\nMUF31WlR1HAammDoids6sgpHGcfoHDRJ03FT0emETkhvoyinZ79fyRl5kJ4z\nXDoVhiy1LTE/fUdZ8O+Eqw3tidPr1xm+ljgalFqeRSEjgRTNEe9p90dqSyH4\nDQi1yKnm/6xA4ioqHutsKoXn5y2bZkGG9e3YJ4Elgjt46d+kOZNzw3IO0Iys\nvSslASNdI3L8yABBHB7FT0Zd+YRNwtgkpRpLuRY2cYbkLPN3F6/viHMpeeUJ\nIhxm1yIlqvnd1lU4kh6D4SLg0x5y356m7G2oOEWJqsnXLTNqIfKVMhwCcgP5\nMtuVkkVbJ8bdWt8QXK1Y9DI5pAvaNhmnHZOYRKuZJhTHXQW4VJ85y59+nxUx\nXSEAMVB6GsELWgvu79HDkPcihf5Tfev+ifsDjxtUJF3pX3ZraX344E0+7NKS\n3gy392ZlTJD3UYiPQnQJeGyZyxdAMuk/hNuY01KqCsW+4rg1hazmQimCdqc8\nQxxpBsnWX/MGxO3C55CS1NwEOr4Iaa04TbfU6CEIz177C+VY5Q5fvL3tbmhz\nj6nU/gP6ej95GIUzheinMms3WodagB1lOxmGv5EeC27knuWqsYLFamDdebAS\n+z0xjyaYALJm9wIJZhsQXMHSBRVx0iFnaNVzKTPNsD2V9ytxVEL5ZPc0FbvB\nfPzn\r\n=WzNL\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"662a2787e7ec97c8b0bfc3826c22708fcf135e0f","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-20T18:31:30.872Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.2","@balena/jellyfish-logger":"^4.0.5","@balena/jellyfish-metrics":"^1.0.446","@balena/jellyfish-environment":"^6.0.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.3","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.39","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_9.0.0-export-types-662a2787e7ec97c8b0bfc3826c22708fcf135e0f_1640025269325_0.29934270568384114","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"9.0.0":{"name":"@balena/jellyfish-core","version":"9.0.0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@9.0.0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"7a97d070fd55d8c240727a40f319e132b22841f9","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-9.0.0.tgz","fileCount":215,"integrity":"sha512-C3BgOZckM91S+BwMf9lfvhmlVoVUppyXbQFwweJxHqo02BveWMjI9reIrjueGg0O1Psr0K0ee4+XMtzYD4khBg==","signatures":[{"sig":"MEUCIGFbrEShTOdWYhc1awZPEtnsWCR/+I0KcixkGg3DZoC4AiEAllBX8S0dMhvE0al2KG0xVgjgXPtSyD5Z79ieTr3ysV8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":787881,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhwM5+CRA9TVsSAnZWagAA+90P+QE6bV5auOliwxSe3QG+\nK4kYxU7RvJTFGefvMyhgqTUMWFGQ2t8WLTrzCrA7VREPFb2CFTXq34UPD7kh\nW6NRcpYHtKJIF++tiR3sL4KqBkc3ROWdkuytiv5CPt0YuSjmZP0/KMwqoTMX\n0wjbGnENqAkvAykC/jgUB1FJWwfnFIY7GdcaJfRKjKBxYShvlyTrdTQNgso2\nV8JtJyxXgUDeV3qyq+qS6qogUfrlVdnnR5/NWpNRgWXjHBDLcJiCByEIEBhW\nBBMJQxySLH6A3zuVV8oQcHGdow1YCxcqjMKBNTku4VLl66/ij49MFwQJ1jKD\n+J0TnPORVPUlDXDDPoAUm93ag81VtzuLltxROneGIcSfwF5i4vzM0y7bgmzk\nF/JAW8r9n54pA+e5tHVm/S+jHUU6tVAcj0mB2DlQkrJdwJU1eNdtLNCbSmMl\nTS49A3xVsNRamaJpnxNXZI7y0ZZ+6LxEz/efAOI5sFBDYVVSuI8+yhjdyGW2\nvwR2eViKfxnq+N9H6ht4Hogxg/0MnFbFjUeBmbJ/AWvFSWOG4Mp7ogyQriBh\n56XQx9GmjUX5tx8yYATkTYhgfxLmteWYKQQX8PkcVUCjFnXT6OsSV+ppSFpO\nRMHjKrlcIpsmTL10IaF4FOmMd52a5BCedkbjEn6rlrDPsfI8bLUZWInNsF/1\nBn9K\r\n=7M2D\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e7f27ca27486991613a5c0ffe98c73f5c84a1291","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-20T18:40:00.141Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.2","@balena/jellyfish-logger":"^4.0.5","@balena/jellyfish-metrics":"^1.0.446","@balena/jellyfish-environment":"^6.0.4"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.3","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.39","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_9.0.0_1640025726534_0.5409854535081311","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"10.0.0-more-interface-changes-17432d595f1d02ce838e6c57b36039e82107e21a":{"name":"@balena/jellyfish-core","version":"10.0.0-more-interface-changes-17432d595f1d02ce838e6c57b36039e82107e21a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@10.0.0-more-interface-changes-17432d595f1d02ce838e6c57b36039e82107e21a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"943d38beecb933c5b8ef3403802d1b0e3d6610c2","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-10.0.0-more-interface-changes-17432d595f1d02ce838e6c57b36039e82107e21a.tgz","fileCount":215,"integrity":"sha512-s7JYr93YKwjK+3ch7UINrm8dHDlmYOJAe2zjfPZBv3wHal7ggYFVoXwtnrbQXPDMUynC6csgSe2ae//KemMVug==","signatures":[{"sig":"MEUCICFAl/+CjEdHJ48bpsvT9fYKWX2/023Z4aezILj904czAiEAhPod92HlN6yECkQzdezfYDFBPj8Cn62nIk3fAr/Nks4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":788276,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhwNstCRA9TVsSAnZWagAAJRwP/jFo5d5D5Y6mPVBHrhrp\nRfubvMA0DDX0MCeQQdsZSQYByhTF7SbLNxSAZ9LtMLOWYcBCWX1/Bp5jhFJL\nTikjxHQG5PrS7zMmIGsAh6XBRGYzmgvI3xPfloDKG93hwTYUvoZofFX07cU8\nPyD9LtklIa+xTHRIGkb20lY4oHfskPXEqhriGbNOvGCURST+lvL8qzI4IEj3\nlC94DaoKHVxq84mbD5CfZQhikAjw+X3UkP2YU427WHFGBGIHnw+B1t5XH9d8\nTsi7OvByVACRxxcc4bFdkvF0ouBl96rrQ5b9HOG0kVu/SfoxsvzQh491VWx/\nl+2Z7EVwD2ci0I/PmSOLjNn50Xm5xHAhwwO8wEpmyQs65GliB49LfwTsue6E\n+ppvfS9s+DYbydVo1QbVWOMJOD53+Dj2vCtI7GOp7iGezJsuykp5ATESRk+V\nOi7SeN+mHZwRWMX0YDfUSbfBs+tLX9Dos7hBXXe0AM/a5TvkSWm8XaBPHDoo\nPFJl0J73kLsr6alVbMuzjlzA8Etj7G+EyTXw5CzWKBxQh+vVyRpVFASs2Ybw\nCWSHnTK1C3EAuLQ3r+lyWZMjX0pkgRiYtA5VGED6i+AKVxQkGdGXeL6cnx3G\nA+x2v+kak2pJ/BvbSQdXHpCyM7HpU5jA0F/fIh41yfNsB+z4jJimNs3aOwnI\n1lnw\r\n=HHoN\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"17432d595f1d02ce838e6c57b36039e82107e21a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-20T19:33:07.349Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.2","@balena/jellyfish-logger":"^4.0.5","@balena/jellyfish-metrics":"^1.0.446","@balena/jellyfish-environment":"^6.0.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.3","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.39","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_10.0.0-more-interface-changes-17432d595f1d02ce838e6c57b36039e82107e21a_1640028972960_0.41709683176258894","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"10.0.0":{"name":"@balena/jellyfish-core","version":"10.0.0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@10.0.0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9fd4cba2f0d8a18504771cd56aa68df8fe2816af","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-10.0.0.tgz","fileCount":215,"integrity":"sha512-21BI2B/tfMXM/DyKhh6I23LLujeW6aSSUXdDdwbtD0OP2I9d5q0Y79khltpZGzj6iV9tYgmL0wrKJbPGa8FFIA==","signatures":[{"sig":"MEYCIQDax8p8zEWCElKH1MMI2Noi/G53QSOVuX+op23IY+5WkgIhAJ0rSPeR4ZYB+7sbsXwUjlnZRX990PBbH8p6AqJxv9PP","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":788212,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhwNywCRA9TVsSAnZWagAA3LYQAIkfoDO5PJYlf/kLnN9f\nYxAk+ErW9k06DNjbtwKdzAqsGxW8nYUS4sRUm3bRBJa114PrCgk0b18OTL/M\n+qoQo5SC9tXK1bNbEwEUjcpC71pLzMODD+QDHRCgSgR196nyir6OEKy39Yr5\nXRNoTJqgQff5KVvRo3HlNcGgKNZV2fsglPk4i4JCIMo6in91+d7z1jdmdVBN\n3cMyRYsjg9ClBjIplDEZMLqJOX+67ZOP4yFGreKRYY+Bf0oY3c9xmoe584vG\nau5ATW+zsLi/a4308bksl49jJs1DBs1dJO2AT8papzRXFIQOdjKC5lGVHpaR\nynMWfLEFalNicD07+JWFkNxlPFfGjs3xFHqxFd3vo+OUrslbp7jI/capRVsF\nSvEnsUg1hNc8tcX3+QWtmQNg4Pwm9GKcnHwm8YJ8P3Hh9oIKuizpoZ2rhkJd\n1sm70Guu2IT5AgZNIBRnHuEh1uSEhDWjtSisAxPg2LgdfnJ5WvF3sGnQy56u\nnx7h6hXXFYWc84uyIO7G8iRCtYEDFOKuFfck3lSqWe4cBj1js21Sbzo0+7Lw\nH5o+Q/AYZa/C2GYVd6oZtXfW/52mMhMuFlC+wGDoAqHRv1tTFjq6JPhuuLIx\nlbTjByF5dSF1wNDjqq+Rl+UGOE5d+9yIntBb/M1vraswdkrTmdHvPK3BMIVy\nVPIT\r\n=oJlW\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"61557fa03177175d47e08d4b0494c45ff7a34706","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-20T19:40:32.787Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.2","@balena/jellyfish-logger":"^4.0.5","@balena/jellyfish-metrics":"^1.0.446","@balena/jellyfish-environment":"^6.0.4"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.3","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.39","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_10.0.0_1640029359913_0.5216762422489076","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"10.0.1-renovate-major-json-schema-org-e256a8dfb36145581d88219ead51e8f16e5eb81d":{"name":"@balena/jellyfish-core","version":"10.0.1-renovate-major-json-schema-org-e256a8dfb36145581d88219ead51e8f16e5eb81d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@10.0.1-renovate-major-json-schema-org-e256a8dfb36145581d88219ead51e8f16e5eb81d","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8204e10557420292c636fea77a9afe169c0c882c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-10.0.1-renovate-major-json-schema-org-e256a8dfb36145581d88219ead51e8f16e5eb81d.tgz","fileCount":215,"integrity":"sha512-+o1YhzYpHOFvZiXmlop/FpGnX1cENeCccROAy/O8rDHoIlAggC25nPl4l0oxo58vTdHvJv5F30rcb9M+l8Z82Q==","signatures":[{"sig":"MEQCIDIQRbVzPbkoHDTo/r31oDB1gyHEGi8iA0c/onyKGEgNAiA2UYfLUOiGY7It8km3vqNoYr24FmkqWBd0H/lBgT664w==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":788380,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhwN23CRA9TVsSAnZWagAAmzcP/3xC4TdSgyPgKONgSOEq\nI+vcPkFIuRkDYD/Pvtz3nmmupJZMn/2QxLV+xpZt1gl8LixjdxJqb0LUAexn\n4xGemPBOmS65EzDqfLzwAJkPGlfhjF70dMmYoAGjc+LUMYi/hhVayVwVxPgf\nAkGAjr9Vupiw4wGxC4MrgEQpDEXMhY1TOGPGAuwGoP5vKFC+rkE9mz1waNh1\nTemNX1nPksCngLRe1pT+Nz1GtQ5Oqjuruat9upfJCZpE8pLN6oLe9Dhtj1Ai\n7d3tkX5ma0EoDWXmUJ2gCRiWSL1q3roWFRlZuRAndA4fhUTVjH1tzMnPoSW9\naxsnpA3Ha9t+EbhM+7N5ok6a9sc0N0XGz+UgZ+X3A0tHtu2BNb9/+ZEHijhZ\nZPd0XdEhBhhktIrYdG89grfeZjyuDtPi8/CN7hvhPj5UcSQ5EPCF0KvKy3KV\nsYMGiKUouMaWhyYvASgs7ZLb6qV7m8dLCWnqM2R8S/kwjUVPb94RzNyvkVtD\n9yQf0hfpoc8kL6FjgN3s21YUn4wd4UKEXJFqMebhYyDkFHxYMPk4MT0hmNkD\nmUN0GnSwiPwqe4YXR+4qrGB21R39qZ1ZrCi5c66gPRV6AnVNS7cXiUKX1maP\nIgetf0gNNU7ZAvDHC3XGhAhZvLLmDfrfqrfoltFKGNbfL8wT67fLdRiLGJye\njoN/\r\n=uB0y\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e256a8dfb36145581d88219ead51e8f16e5eb81d","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-20T19:43:53.541Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.2","@balena/jellyfish-logger":"^4.0.5","@balena/jellyfish-metrics":"^1.0.446","@balena/jellyfish-environment":"^6.0.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.3","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.39","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_10.0.1-renovate-major-json-schema-org-e256a8dfb36145581d88219ead51e8f16e5eb81d_1640029622868_0.44146517429684984","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"10.1.0-add-kernel-drop-0b03286282e4e7715728cee4c7622d94b9d4356f":{"name":"@balena/jellyfish-core","version":"10.1.0-add-kernel-drop-0b03286282e4e7715728cee4c7622d94b9d4356f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@10.1.0-add-kernel-drop-0b03286282e4e7715728cee4c7622d94b9d4356f","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"cceea0d689fb8a0f552164cff34cfbec0fa4ad8e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-10.1.0-add-kernel-drop-0b03286282e4e7715728cee4c7622d94b9d4356f.tgz","fileCount":215,"integrity":"sha512-xE+DF891WqmKexLYnWWmK1DlkZaCExiEbqKsWpOb+NgxyvHCuYfFz7ZawGccWQgeFpOur4IGEk6ImYoKiTCs/g==","signatures":[{"sig":"MEUCIQCc8/sY/KFaCEpWIDsTU5vGTfBPiuVPadXNbwe4XH5VXQIgIMzFYPDKhC9jK47JilnJFEZy3TFEaGHAeCVUpjLgfQw=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":788844,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhwOIXCRA9TVsSAnZWagAAUygP/AlCc4T1hEV9nXDRVBjI\nLSgY8oWGe0tTcIGwXh8PiLRZq3J5CZRxpILII+XH96ns/NRVn/Hf15VOrSeX\nrre+eb5duptd03Fg7qXAM4Vh4ySDdUCB9ZNEIff9NGutMs9HMPlWPJZBv5Pq\ncsO0fpii3ElEGFediqisyMrxXjYxDHFOeAP+ftBgCSU6v3F/fNcwBZt2GaGg\nZKWbh4aY4kgyjjXHrqRl+1i0r906D/MPOh+bH+QkvYg29CN96WtxXe+lOeGM\naC7dc+E2dsVCcESvD+VMvZk2JR9taPNRlp+BRX7auBWG1LiQRm9GYPkb0QxQ\nXpl9QP0UIZ0YN6XcGW+j3IEyVUwK9ljGjubDoELAGibGcg7dKMWeJQ+knNkd\nvV5ylY9+YkyKYBn34D2ZOy/a4MQGjGPM00fLxB20TCtN4NLwywCGrQeOGRCh\nGpitJYoBTUHd32TReRdy8PqS1kpwvRtE8OpeMeXK9M9LxoHpgUphvv+TAgrp\neVDUn4P7YmcdRY6xZt4hK5WuNurJzpncBPe8/J2vsxAl31Ka4zR68vSoYgM/\nSEwpwJud9bqoU7MtgLiPGXU3pZ96h/aRBoPhR8SUFltpXd7tRoEAtS3UJEZO\nzB9Ynj6aAswS+74cugoE8eSUnJLQHzg/86UZkD+tcNeUjOXnvL7OTud4GHnY\ndACw\r\n=GPzt\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0b03286282e4e7715728cee4c7622d94b9d4356f","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-20T20:02:52.024Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.2","@balena/jellyfish-logger":"^4.0.5","@balena/jellyfish-metrics":"^1.0.446","@balena/jellyfish-environment":"^6.0.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.3","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.39","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_10.1.0-add-kernel-drop-0b03286282e4e7715728cee4c7622d94b9d4356f_1640030743064_0.3366897440319083","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"10.1.0":{"name":"@balena/jellyfish-core","version":"10.1.0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@10.1.0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6871dab74dff35dd6922194f54d2ac14d345404d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-10.1.0.tgz","fileCount":215,"integrity":"sha512-7IzEbsXzk8oMF3/Af0c086Den5AiUP7r/4SB5yMrRQomCYtB6ya+Y2TkgBIrVYYV6/mthalZRzc8cOTONlhKsA==","signatures":[{"sig":"MEYCIQCw05q/hizVQ/k2j1cxFc7rwDXolCO+2W+sxNlB6ORLzQIhAIjSgpDn7Rm6QuhT2jKbxWE5v8NuxXf8Y6kFIBsxWQ/5","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":788787,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhwOPYCRA9TVsSAnZWagAAQ3gP/iYz/RPYxwQqzo4B+hlX\naZ57hHSQhrLvpFDI0BEGPakp5Hv5INPzLknniIyABhH1y9WH9GgK4GJqJ9FK\nDdi5ZXRSEeAKI8AZ6Bg5oQ+gH1RBlrSPV0dDPiq+dgWnswc+mZB31nlVJWTM\nQ3lTpFrhQ0UfKljPPk1OWfgUE9AXraCy//OSbk5B/qRiFPeB1x+GaEWh2xgU\nonfr52qLrIfrQKJW7mtt4m9el4oeQEn3bPS3Tdt0acesZgKIXh9HJCTMbaIB\nhu87KgrjzBDBb2kU5UiPJdqzH6dyeRfgJEMp/VAv4QymQ75mWp9M/ck/PGf8\nzIEhQPxlqjHnTok0PvuYZkC7T6NTQnr2McBm2W0XVS9KGqa6edIpLajby1Qv\n3r9Jz7nPdZf5FQ+aTh+z/ARpXBD+XL5eY40cnFn1RiNdVfjvgg+Ty22hPzDe\nlewBcjC8MifL0GzaiPLGPcOqobkLiqIDFE3bqtlwJTpt+mkHRMho4EjsXWoQ\nzlbQtPFunIh8/6BeR8M9OuQDjroCmcBosdFlsaelL63M/aC0yplYhtweu1Nn\niNbCGwgJFxvYvTXMGn3tcGq+3+QUp+YB4VlL8gxorqgDNAxH4I7UBt3vBBNO\nCRB7RYyJs/AY8qFqfNrObj4ifiKjDmetReAHUkHq3T4YT1mt8KzxNm7AJt9i\nscPJ\r\n=HvHm\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"44162aba26d214ce382787d51fd0d7ec15b36102","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-20T20:11:01.333Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.2","@balena/jellyfish-logger":"^4.0.5","@balena/jellyfish-metrics":"^1.0.446","@balena/jellyfish-environment":"^6.0.4"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.3","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.39","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_10.1.0_1640031192758_0.3279592804945459","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"10.2.0-kernel-reset-1edfbcfd379fd4874c94ea0e5eabf85f7ec4bfd0":{"name":"@balena/jellyfish-core","version":"10.2.0-kernel-reset-1edfbcfd379fd4874c94ea0e5eabf85f7ec4bfd0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@10.2.0-kernel-reset-1edfbcfd379fd4874c94ea0e5eabf85f7ec4bfd0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"72277710cbb6ad260c93411e1bcd256a787e30ba","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-10.2.0-kernel-reset-1edfbcfd379fd4874c94ea0e5eabf85f7ec4bfd0.tgz","fileCount":215,"integrity":"sha512-6EA8llPDOyYob3G+az1I6LcPr8EtGH83Yhdh6QBwKsXk4otQdM3WEFmX09SrSVDs0lgi1MMTsxtqTtObgxlftA==","signatures":[{"sig":"MEYCIQCiURqDUViUfKmrSJRiic/OOqD7QR4sQovpTQVor8Rn6QIhAIb1IymTKbg+8hMmhFGtM9MgZNRbtqeBSdrUz2CMHLrk","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":789404,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhw46iCRA9TVsSAnZWagAAj5UP/2dowFpwNv/sIeSuO0Ln\nheJ7VA//Ha8el3NVbuX0IXi38/aLysTjuCjMfAnriVdJGPNQ4Wr8k+VV1gGn\n7vrU2vJB5uE9qNB4Z1fuzh7iCRU78FkOn9meFaoXnqXBLdW544Gbi64o4Ba5\nEU7082E97aqbQ1AltpqGy7/2FePazqymPEiJVXauG0wCxcy3XKQSivnAuFLF\nYkE7LsoaA4c5vtPg0nOTJTmqaMnc1zDUMCaGhrMREMdm9sXOs0/xBXRtFM/b\nH3T7zcrGTOj9vvd/qGqny4NbW/Rh4nkZkfXMQx0OEpfC4zyks6rhRkm7jWBd\ntKkS0HidfsMFdt/XyhHjVwJmoLKmfBK/oMeqCMpe4c14LPMpwam9V0q7+sw7\nTpHWjKn+1lLByc2KR9N65EBGFrzizWHgRC8kc1jz+2MWWMA7KBm1X2Fgtgth\n6t9x6FB59+zeQzFO8mhuk8AjxPM9siEA+1SrYxFZ9U09ESXCf6soI95BhFuk\nwni+lJSXKd6TN9UnMqOpBmb1VVaHaOJgb7wdNTLQsuij4pzObF8yXxIyvjXb\nuEMfPz1Y35amTHwijHIy/qRKbkIpTK3SrTgwBneJabjoioQ4/aI669qHM8xO\nPhK8wD794o2BofxCGn+r/eYwQB8RNT27HFlkNM1RkeQPZnPSOLls48cdOtQA\nD733\r\n=5j9z\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"1edfbcfd379fd4874c94ea0e5eabf85f7ec4bfd0","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-22T20:42:40.933Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.2","@balena/jellyfish-logger":"^4.0.5","@balena/jellyfish-metrics":"^1.0.446","@balena/jellyfish-environment":"^6.0.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.3","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.39","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_10.2.0-kernel-reset-1edfbcfd379fd4874c94ea0e5eabf85f7ec4bfd0_1640205986015_0.7330383733355448","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"10.1.1-use-truncate-e2e4050e271962ed0259a169e25a2d7ebf0e0285":{"name":"@balena/jellyfish-core","version":"10.1.1-use-truncate-e2e4050e271962ed0259a169e25a2d7ebf0e0285","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@10.1.1-use-truncate-e2e4050e271962ed0259a169e25a2d7ebf0e0285","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2c0357520e6e707d8e134a2bcf85c4e540545e50","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-10.1.1-use-truncate-e2e4050e271962ed0259a169e25a2d7ebf0e0285.tgz","fileCount":215,"integrity":"sha512-m1gg4DEKCzJM0oCgaF/9AFfr2W1Zob3+c3sxxqwXUKFy8MCT//aQQuYPQjEO2oT1qeS1aNozkyNDmTPVAHl36g==","signatures":[{"sig":"MEUCIQC9Zg+95x1sWv7F9hqwjR0g+452+oK+RFof5dB5ME/G2AIgPQhL1AOBNlEtLws5JdDCCtf9NNNdYg+UKuQl8OBH+D0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":788944,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhw48ECRA9TVsSAnZWagAAmEAP/1XpS7ENyL34+3/Q4+Be\nY2kJS4ZHuMNF5acl8Vo31aym5d82CU2wRz9OM2yEQRHqU0wJB77zsJ9WRiiY\ncOMTqI05ePzQlqJhXL0ttyWTunWplSRR+7y/PftrjM4Es+HEuyy+XcDOdnCM\n0izwsUoSIAzbk3jcCcLoi9viT0dbz14CqI0pko5+ikwBtnkP37ZEqibUO/tl\nOOkm7YzovUbQJVn24CKhJFC7deAGnYTk+nFW6M3QFbYx4UlRWefx7aix4+vX\nI/3mm1f1UnAdKtlOmlvb0FXd9gwoXNfAkzvxhEsTf5eDQ0wK3XIWN+3x2e3Y\nsioQP7qTq1VVS1MitOy6H8q1o5ayRmXSFqXp//M8f0oBvJudMfkuRFgnqmyP\nRLcGcGqMODRzswqOOOc9caCRTow7Cy95wIjKqLpgsYVXCvTC91yyg9pf7726\nhOrnGKA5+/Ko+42ckM3dSnFCrb+7W8RMvw0FlYfa7esSL4vbDmHBzVr9h/oO\nSBZoJfzHeCGa6cevzcJx6OT4rzLjrvxQ6+N6ftq2XXXKNzDuIIFAS8bE0/UX\n6NIE74G625Dla3K6PzGr4rQaaar7+b5ZxxQbPSux8OR9s2vnBhef4nuLVm3/\nB5jQTw72UmdajNIEaOUXmhRPN2eSGN0Qieo5UHu5GQkGBEZe6N+JRl0zvNiA\n/c7M\r\n=QmG1\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e2e4050e271962ed0259a169e25a2d7ebf0e0285","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-22T20:45:03.994Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.2","@balena/jellyfish-logger":"^4.0.5","@balena/jellyfish-metrics":"^1.0.446","@balena/jellyfish-environment":"^6.0.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.3","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.39","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_10.1.1-use-truncate-e2e4050e271962ed0259a169e25a2d7ebf0e0285_1640206084183_0.005639100411706011","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"10.1.1":{"name":"@balena/jellyfish-core","version":"10.1.1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@10.1.1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"14f8607b86aeb6ad63c57a34879f91dd719aef7f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-10.1.1.tgz","fileCount":215,"integrity":"sha512-O1nSuIhgOMchpqC4+UgeGs6iY2dz6p0mv4ESj/5Bctg7AJ47ZrsSwKYTfQ1sR9hheF048saaXapX6oNE2TjLFA==","signatures":[{"sig":"MEYCIQC1Yx7Za+/sg5hnf3eFbrSWZMuqm+wBKKgxacsXD+azJgIhAIcnLDD1x9iNwpIkZPOxf7IRcY5SAFpk4uZF2RCLlzg0","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":788890,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhw5B7CRA9TVsSAnZWagAAku4P/2Jms6nLOwJq+kdKZiAJ\ni12yGgjPE1yptIrkfSYidcdRMLvlnFywUOhSUSf9CT+De30xSajeEu+EFISK\nvrAcb+03/h/F2v0fNf3jOHGPWBNNNWmGQ25+l3DySuAmyhCGBxJmJzNX3diZ\nM0WUwiCP7HglalAByrlxwQSNsQWrBLLcPwGaPEyVslTWk9CEA7FX2rBzJAZ6\n3FXzoMRfmgBKe8JCfp40Ha/vhkMgLNeYV+c7JDVeZ5S5Gqx+heYsxBnn4h1s\nitqLFXwWZjCssc2FJNz0hrHuzCGF1/+4ZslaWhX4IVjZV6svy3oaoxQHjC1e\nkISMpQFC8OveL/SkRDEZ/q3R9WRCFFq4Sw/OaOr6eOddng4RkelVjPa5h1g8\n0J1tWubmPjxqpB2W2RaAREajG023HzhlWJR0mYU/878rs/WbLLoNB+nK4Fws\njfMeYi7zFp6mMeStGNOrX6cxQw47IeRCQJ4CQAuP5kBLUOnDYWUf7KxgzHtf\nGG2QQKybiUhVgdWRiXzVuWiplTDnKdo07zH2BF/h71GlkkKyo2LCpXFsJuza\nhaE65fSSKmoC7jK1XvB1BTNvF2BB9WWgewqRxVHro8mf9AGtAMKNN10jQVT+\nHQdT6KH7KLJ/Ypra0mNNh8XilgG6kovp2VqsmMCcaSW1qF1wWDnme3kWhYCf\nWBKc\r\n=YFSN\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"bc863c944377ff4ec1f773edb431343270fe25fe","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-22T20:52:13.084Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.2","@balena/jellyfish-logger":"^4.0.5","@balena/jellyfish-metrics":"^1.0.446","@balena/jellyfish-environment":"^6.0.4"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.3","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.39","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_10.1.1_1640206458868_0.7212707576824282","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"10.2.0-kernel-reset-aed69d7afeeaa1f2c8cfa2888548703dcb8fc21b":{"name":"@balena/jellyfish-core","version":"10.2.0-kernel-reset-aed69d7afeeaa1f2c8cfa2888548703dcb8fc21b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@10.2.0-kernel-reset-aed69d7afeeaa1f2c8cfa2888548703dcb8fc21b","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2a970af247e19220b9f09b57b83cd88a555688fc","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-10.2.0-kernel-reset-aed69d7afeeaa1f2c8cfa2888548703dcb8fc21b.tgz","fileCount":215,"integrity":"sha512-gwIDgMhnaMd/XDQ1mYJUSqCr7GXAmY5jCvlvdy6o9lPq1krcD8vvpwL8zkF7bxjrW19OS00dTbyLADQMckZwJg==","signatures":[{"sig":"MEUCIE50+yeWibSotJ5zqNjmsu1jqMVrSIUdswlkSdf4+EpbAiEAlRe014r5LiNsl3b9S/THlnZu7lUf4iJLGCvSzEYdv1Y=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":789507,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhw5LHCRA9TVsSAnZWagAAaxkP/ibAyLx47aRpgpJEfoya\nRbigK2UlsNz+h9jRRHhDIpIrRxJSNqfGj7tLpVC0HnPLjmEqmHGgY+vLktlb\n8qzc532QcGlogOXsHMI0Ql2CR23wiafhJeQvz+ldWxSWTwCgBEUoAOA5yJEF\nHQXlsnh3YLPMj1J0oCQ3Y061hCfbGdfZo3dKnKD//a4G+0PIfjK4LGwS4MFc\nTFcCQ//C81s6ltuEUfp1QNgarkE8oHRwJp7MfzCEMxzo/STKNh4YiEsSuIX+\nHnSIsC348qFXqj51TL2JUXWICZR83yZQBXMGRvm3G0Wnv5Y2THv7fd3h9Na5\nxg5VEFcmqdiwT4bM/FOjKhEw4rla7wy9U7Vnj2Lm34gp4I2j2CcVIfJ1vEuQ\nX0bVmR6d6iZOGMW/WI7ldB4Kg8okfxNmn9zpy+w/8JLRKew4UXdtZY1yXUuo\nwHGsBFZogKmFgy90Y9Upr6N7jNq7nyWmLmER4uD1vPuR80KAko19Bwl1sogX\nlKP0Tomw6sf+XYemM7+ddWrCCH+nFk4hEu4brOmW8KJKk3jjmgudTI04KUxn\nJiYY6rLLyqW+J0XMXuDvNT9UNsPIQ/9cMx1q1dqo2tzTNqTYCZRk/QDEak1H\n7vnAq2wu/P0qaXMon592WK17PMHKP/8HnW82uAFx9FDg5ep2+CnJ/xQZlUGf\nqHbd\r\n=8fDV\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"aed69d7afeeaa1f2c8cfa2888548703dcb8fc21b","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-22T21:00:44.716Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.2","@balena/jellyfish-logger":"^4.0.5","@balena/jellyfish-metrics":"^1.0.446","@balena/jellyfish-environment":"^6.0.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.3","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.39","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_10.2.0-kernel-reset-aed69d7afeeaa1f2c8cfa2888548703dcb8fc21b_1640207047250_0.7779763487926128","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"10.2.0":{"name":"@balena/jellyfish-core","version":"10.2.0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@10.2.0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5726ad5ecb1348837fb5b55fb7ee418de420283a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-10.2.0.tgz","fileCount":215,"integrity":"sha512-qhmuRVwHVe/pHgfJkQxG9tKPHT7liKSYPTnEpmzWbUAmlyramWENdIPtGZdOTQdyq+shAEtI5Xrkh+I3lg9zfw==","signatures":[{"sig":"MEQCIGGWBEjtTeNrmh/sOqklUuQpah/gzi7FAE1ePfEAh1ENAiAfgqbNkl6tU6ozKrgj/UtpT4AFN9l0PgAHuFPC+fceRg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":789453,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhw5fdCRA9TVsSAnZWagAA0GcP/Ajo/Eld08KU1g4a96a8\n6m/UD+/BJQH5iD5HYOxq6gI4EYzbfTFuEMqvMggIBgy4wYdMj0Jw02IbYxgu\nqqP9pOXdP7/KakoRcgNZbOwoiD+NZ0MqPnVUKgdVlgTrbAY6Mn0uSvteqEVk\n4VeRf95d/g/Hol2m5I2FLOD04k2kLhAoUP6bNKQPb1ZGxqvhGZDnoYwhYJ/M\nCvDWV8okxK8VQRK/aBxJQWiucab1+zbHIQ8tjo0kvNDDYhecGyFzkvoubyEp\nBsO96kaV29OrXlI9gcdgKp31/SPStePr34+2pSgZ8TIAGLWCgaJVnl67tfAx\nDCOSUeedom4fggZjftblnsXhUTzoW48LMLSDsG28z5G2qhw+yV/DcOA1ODDR\naXNkTb9rXbc4T59r5ovU07Z4DSehvtY+n2uveCUhCtiI0anGwxBigtVKhV8Z\n3Lz6+NXxigAX4rLOVtvl3LhZubr4ksjFWHSe3teikDdJkOriilQoZrjsCNmX\nenL85gen+ivYv7cO6hSPFLcavdf+0K17tQbLxdOsXAoFhLSCVPmRPl5rEkAE\nLVpnfCItzTWIBbiqU4hrXmGcSGFpkUv5XQm+FPgeNZVpaiECc9rSnGgedP2b\n6hcVlcBweJZ2oWmsI4hALziCkkrzQ4DLTEXD5Yzkq88WAbvVAtPbkb6+2h35\nqo33\r\n=+RFw\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7e335da43bd3822d5ae147f2e00e009ab5408dbd","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-22T21:22:51.637Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.2","@balena/jellyfish-logger":"^4.0.5","@balena/jellyfish-metrics":"^1.0.446","@balena/jellyfish-environment":"^6.0.4"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.3","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.39","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_10.2.0_1640208349568_0.7395992645525409","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"10.2.1-renovate-major-json-schema-org-0e23e3ae27d2aecfd523f898da37882864f976f4":{"name":"@balena/jellyfish-core","version":"10.2.1-renovate-major-json-schema-org-0e23e3ae27d2aecfd523f898da37882864f976f4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@10.2.1-renovate-major-json-schema-org-0e23e3ae27d2aecfd523f898da37882864f976f4","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f3524c9a88d16d98f08665ab8b07ae70af106876","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-10.2.1-renovate-major-json-schema-org-0e23e3ae27d2aecfd523f898da37882864f976f4.tgz","fileCount":215,"integrity":"sha512-bQtQwUm5dGg+fhFMPOaXm/T11jK2l3N6/yhZInG6BZ6/zEGMCJCBQHJi5hXoe8y7yRTgud3RMbRwUNNCJvHyAw==","signatures":[{"sig":"MEYCIQDDcW4Q7QWgaeI/7oJqrP1Ps0N0GFK6c9lvT2XqfnhaWAIhAKTFNd1J3XHJBqPAPBc5kJp8UTpiyvAailhfApCvtI7P","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":789621,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhw5k1CRA9TVsSAnZWagAANGAQAJcdaKRCY+ScL0WO/hGB\n3rZ2+1K52KmuoiMu7ouCZjLFzASDS2aWE8GDy9tKD3XvWXZO0ntdeJwib3Gj\npAJHT0tNq9u0XYkEJ0YVsKQJYItdkV3Mq3Cqn4yPNptUIH3XljqwV52qx51e\nx9ja3DMDfpidhQqdrdjO6lXQHkofwPmWBk1IvYbau1esNAl1MEmitfReUkTm\ndgEUA9grBm3ObLIZTIgEPT2u5guzXZ3QixA4+CzOd4jF68+oaAeul5vyKWoW\nyVgUC2mb2o1PXegYief0kwOokIyVIaUP9WnELenRIHg2IA2NJBWb/tgTHk1s\nNHBYHZY6FJH94O6aKLk+qFtUCaYyO1iQSEvtefm0UTsmOQE1DYY9NMxnQ4jq\nDO97TTHEWbu/k+q0ol21RHpQ3wBJ3lHIgdA8gOAj7t7EPnhDsl1TxM6ZZuG9\nEXOeicHr4YrFHdMO99684WlZ2HyN/JBrkhRCz9IDkk4V7jlGz0/hSW2uIki2\nuYZ6n86oOlnwPCYaBK6znpUMPAWhMtGvAN+O/XMyy9IAjnauCQfKxK2P9dKV\n+C7f/SXMKeEyFA8N0ETt6Am8AsZX+Y6Dua1P2QohY5+T/d7uhUe7Q+W4NIdh\ntb5ahi/WCKvnunuSQ3RMp/Y7N/Fg8LyBt8POpUQW3x2qqlEmfrya/A7kcof6\nvvVE\r\n=nyqb\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0e23e3ae27d2aecfd523f898da37882864f976f4","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-22T21:27:49.318Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.2","@balena/jellyfish-logger":"^4.0.5","@balena/jellyfish-metrics":"^1.0.446","@balena/jellyfish-environment":"^6.0.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.3","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.39","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_10.2.1-renovate-major-json-schema-org-0e23e3ae27d2aecfd523f898da37882864f976f4_1640208693713_0.08580482114726529","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"10.2.1-use-import-type-6e435de82b0b64646fd11a8385b08622d1b9c583":{"name":"@balena/jellyfish-core","version":"10.2.1-use-import-type-6e435de82b0b64646fd11a8385b08622d1b9c583","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@10.2.1-use-import-type-6e435de82b0b64646fd11a8385b08622d1b9c583","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"eb7bc10a64f8cfde3ff02e0f5fdaa814c902905a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-10.2.1-use-import-type-6e435de82b0b64646fd11a8385b08622d1b9c583.tgz","fileCount":215,"integrity":"sha512-rIvr2bRCuNCYyQGETXOxYNfpUov76P4mVNqL3fwY1+5jNSjcOmnNwslWBNBcgPf4jiHiwKyJiNDl5qG7HgMCyA==","signatures":[{"sig":"MEQCIHj20e8iRIIKSchkWj0rhnWs6S5WcmQ+caUdQxwLwpuoAiAdpuE0pygwWpjfjSi848yNvzrLyTysghG27JMcrTCbyw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":790025,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhxJ4fCRA9TVsSAnZWagAAEycQAKO90eKRNPTy3S818U85\nX56JZtGw6WZl7X67UhrTVbzdnr0U16th+WbmiyfhD6N0cHBnsOIuVddAG3Po\ngMPy52nJXeJjOMGqKDXqeRr5Gnk6crLwH0LsNTykiSzIKgAaMJu8JqDEMSnW\n7JNp0e4E5vp5HJbWZXrWiCaNgZkQpIMGVVadlVK3tLcDGKD3fu43iwOimWi/\n5rLNEegzJ9m4C41HbyydGZ/o8MauOwy/zYggqaxTGy4M0VCHVMs00aYyf8sh\nI9uM6fo+W99Q3zaox9Uu0Ygd/OVGO4jvTdcGgw6Y3C89m5KKhmAt+e4mHt2L\nJBklGY9r9qXrEppOuelIS2sEB0hvd2SJEOTOVIIZOW5ShH5swnL+eFeYrXL2\nQHsuFfeFHwJDQzk4mITzGKkHc8OKIxf1obLHIOsAYR+8634z/nW6SHJGdg0d\nORTfuv3iBpOnZAdF8Ax11TDK8fjqcJkLDUbZ2QkVswSxiUxpdZGW0VYrlP1n\n9nz5G0jFpXSqhcIs4c9bHSr+B8VLW8bVs1P45jrxtlVPqzuaiA1Li2/TRsYo\n8lhbpi5UKMWZV91KVdXp/yPhhQq9kk64xhRZRQfBsyQhWnHCGRzCmK9QHpJD\nKLHV5xuwkZtPXumt9v2+CnYEBeL4cNV1YvzLnBALyMGMacyCSI+wqfvOpguw\n/C+U\r\n=7Eyd\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6e435de82b0b64646fd11a8385b08622d1b9c583","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-23T16:01:44.433Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.2","@balena/jellyfish-logger":"^4.0.5","@balena/jellyfish-metrics":"^1.0.446","@balena/jellyfish-environment":"^6.0.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.3","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.39","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_10.2.1-use-import-type-6e435de82b0b64646fd11a8385b08622d1b9c583_1640275486907_0.42204330294224945","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"10.2.1":{"name":"@balena/jellyfish-core","version":"10.2.1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@10.2.1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"97b7b7590f3c93576cf4412568a973e833868fdb","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-10.2.1.tgz","fileCount":215,"integrity":"sha512-w4/hhFf0YezdIRjf7tXNLEn4+5jDSRIy2Ea0UBYYYJas2Jx2eNi5H2I2L01cIj6JJAxXtdqOaXWfME8LtjZoug==","signatures":[{"sig":"MEQCIF9IGwYHJQmMT6BYlOxgNf1o+Hg5bASj/4Xiz22R9xm/AiAnwOeNS8GUJ7aLe7GcI37Dq7zYEnHRAzb3wBFaKKc0DA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":789968,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhxJ+tCRA9TVsSAnZWagAA568QAJDa3ZASYbXd1XOqRe4R\nfjhacq7Oa3zeqrlXewK3b6lYPnOHRfHtDp9la8nvI/Wm5hz/7iM95r+bm5Re\neoDwidqt7LUHBPT4lnxXM1FqeoZN00IAmGMv7UEYoukJ1HuCD0KkjsFuYTOS\n5vTQA+ZV/0q0h/EpC+hBU2sYvQLscf8gr81tIu2Aat0G2ydlbBvfywNWfSm2\nwpIfisijStGxUNHVchi51kniUtyf/r5EHi22f9KsM6kO4z4NWRoRNJlmqyHM\n0WBMvFX/XaeEqsEv5fyzNX1T32bmi++dz6J/wYfUkqRIThyOKKP9UoYYyRP7\n4WRhP21ABxfUiPzOn2VgdOLyJyeZQy2tydhrGzGuiZNFU/nMa6zczYqbLLSS\noL4qbICfPZ5p2VcEDuGtNIN7nqaRizG2jSl+q5jrk3oh2w05meFwTVM7QPbC\nHsxuF+7XO92qr9+wF6xZovpcxZd1T62KDbYCMULha1yd8TCk/8aZHV2BI/lg\nGeL4xzDiO9BKXbraf4+sDUTy6eG5oGXUktwSLBk48n2MHteludBV3Gb2eBC5\ntNtGQMVeGY7VXLmgo8GC48p5V8BG1zzeL5MqPmMbR1ZDsjdc+4uiSETig4xl\ng+McH48IR228Hy/dTDdr64h3Jr+pPVEfxyA9KrqXNzxbkUQVVs2AliS0UHzU\nAPl8\r\n=2Qny\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"cebf0f65b910b3f05464f78a397b2504fe1f7168","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-23T16:09:03.621Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.2","@balena/jellyfish-logger":"^4.0.5","@balena/jellyfish-metrics":"^1.0.446","@balena/jellyfish-environment":"^6.0.4"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.3","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.39","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_10.2.1_1640275885362_0.6445280202685444","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"10.2.2-renovate-external-non-major-8f8dcbd92f2543ef4bef17083f6ee6856f58f713":{"name":"@balena/jellyfish-core","version":"10.2.2-renovate-external-non-major-8f8dcbd92f2543ef4bef17083f6ee6856f58f713","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@10.2.2-renovate-external-non-major-8f8dcbd92f2543ef4bef17083f6ee6856f58f713","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"79238953bca47feebaae6af2f9609edcd3a25638","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-10.2.2-renovate-external-non-major-8f8dcbd92f2543ef4bef17083f6ee6856f58f713.tgz","fileCount":215,"integrity":"sha512-JzFZLDyHzziLreqxnCWtNIrSliGeL/xjHMgQywo5kZNcymvaJ0cDyE6OihCRkTCRNrQpXdWg46rbFZJI3dgwdg==","signatures":[{"sig":"MEUCIQDxGWSPTH7UddB2lsmQ9pxvwY0ml5bjlD/3+ZSP5VAgQAIgQu03UswWb052lFIoeoVnCDCmQ75jEr/NfCxwtnvDqzQ=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":790112,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhxmaeCRA9TVsSAnZWagAAJk0QAJTTtkfgFfdzuPaqUXfs\nMDvhsTukKZ1+2JYXHx/wqdfBuigWRL6GjomoWjYkFh/C5+8CQW6y87HCBrzc\nWKAVu+WDU6sVycw5vyDv5jZhIeThmBAwTCrewPM58Ex3RnWqdzJtehI200yv\nlgwP8IuW/Eor+0MKi5uNsytFYcjD8X/AGfiG7QwlhhbzE9sEqbHlDFqhsXEh\nWhy4rKM4YzkJ8hL6nEHJeFO+5pVFFh72Pk3O6fzthgydXT11mMf5qq8lcCP6\nVeAjESOd7skZaDM3FF8dAKPyJosvVs7hT/fcfG3tmMSYN2u2P7N4GFJqHwby\n7q+hnEXbikdXEYz+q4gr+kfYPSQhcDJnWQCcAAhvFYi1mjJjt1GmYxQwoxyJ\nWh50f3pZZ2M2R0rfTti4cXrjdjrt1M2npJ3unXVshyg2Hzevi1Xi5nH6Ljw6\nmHCeKKHN6ecQaoGcXLnxCK6rqXHXjGq+WTZwktVyiNROOEWz6OtRMDpr1SaW\n/XaOmPBVvNkaL/iaeMwFHiNt+YF9fkhRrRPaiaZy7t5ZcDxVcHQv7XluEHui\nf9IoTyOGAGSzncyTw+gmyfn4m90v7bKGjBihgT37UMv1u9AWDrK5/x34/8uN\n0oG2FhIf4/VTp5jMMuBhEz1O96/bqIywI+OY+QIKwEtOqd/LP8BdWJ4EVURL\nENBg\r\n=NPCP\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8f8dcbd92f2543ef4bef17083f6ee6856f58f713","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-25T00:27:15.257Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.2","@balena/jellyfish-logger":"^4.0.5","@balena/jellyfish-metrics":"^1.0.446","@balena/jellyfish-environment":"^6.0.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.4","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.39","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_10.2.2-renovate-external-non-major-8f8dcbd92f2543ef4bef17083f6ee6856f58f713_1640392350757_0.7783271758548405","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"10.2.2":{"name":"@balena/jellyfish-core","version":"10.2.2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@10.2.2","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"21a3f13c94392cd142da4e2e97d9a23731bb1074","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-10.2.2.tgz","fileCount":215,"integrity":"sha512-fDp8WEA7sUUjZmiSCzHRmrhhwjOC3pqQ0V+CtoINXLmJ6uzBpn+2xATKLAxQGJSr3+O9A8mD7KqZyRvkFkzh0g==","signatures":[{"sig":"MEYCIQDxVS75bD3xq+7T50qURjD1BHcEcAGZ++bUfzzenqPydgIhAMLY/NR4nvnJQPz7lDU4cDJLXv6k3nzcuuL0YoE2e3Qp","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":790043,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhxmg9CRA9TVsSAnZWagAA+RwP/R8N8rOICTiAiXX5PinG\nMzprMjW+7DzLfFMhAKyNgd696tQ1vpMxd5G7tfLALPmkxPYfSdTbMGDccPzU\nlP+tlTgfbAdhmtRMY2nHLZkbVoEQ2G4HseSoavg4SRTyO2ZbhGptfan/1Id4\nVQkZuAMHYqlO3FgnGPj50cZdkDHUlAjqKma9iuDPvSQHDY22LfbshhnD/u0K\nhjYfkjHs13a3hMTKiM0uP7fvmLPVi1FtgLIPleMp5o3MCwXCGIF532IlIgy5\n2ev2179+qdXyvwN0B8ottaFiRHbAYPYhSlklmdvqJzLjXUD2w87TMFKT/IGl\nCuh2p4lnUS9n1F2PuXibEnrawmMo3mcdeurYfBTZTOFPnCSOeA3iRMOkGYb3\nXb8Y1BDk+Ow7AI3E52zGw6x0ZbtAdXZjekCM71O6Oh4S52454SdeIOWRemO9\n+883bsY0dMjLbzt1+6CUb+AM/pgEwD89s7MFFMP5K+z5Q9RqrRHsS7WcjRGj\nYJ73X+AsThoXIBKh9R1a5z2loFuGar/hYPxZLtH1RiT0IOOuV0KBfLacJcdH\n5gpykBK6dKG/z/33uq0BPh22M/DVxLrOmgIbxrtoH26ciFJ1cjTPhOT1Gavv\nADLAtPQobbOcU10k2dDue5WlOP0qORQpBA3Fd0dFe4TEYs7lBWArUBye31jX\nQ9tb\r\n=G+0y\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"46bde9379b5e0dbcacf96ea08da192c918bc95d7","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-25T00:36:45.477Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.2","@balena/jellyfish-logger":"^4.0.5","@balena/jellyfish-metrics":"^1.0.446","@balena/jellyfish-environment":"^6.0.4"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.4","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.39","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_10.2.2_1640392765214_0.2440560341963547","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"10.2.3-renovate-internal-patch-f3526e849dfc787608bea74fb162a1b0d5e63350":{"name":"@balena/jellyfish-core","version":"10.2.3-renovate-internal-patch-f3526e849dfc787608bea74fb162a1b0d5e63350","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@10.2.3-renovate-internal-patch-f3526e849dfc787608bea74fb162a1b0d5e63350","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5dc63fcabcc08641d41a550fb68e2243131da842","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-10.2.3-renovate-internal-patch-f3526e849dfc787608bea74fb162a1b0d5e63350.tgz","fileCount":215,"integrity":"sha512-XydCNGqwnYe1zuKQ4yucbonTXA96ivKBZ1rvB6ih/sMriNpMcH8nl4VaZdNEgVJfYlUoDA/EABUK2J1jN0EyeA==","signatures":[{"sig":"MEUCIASYjUvXj6VDiky1AtEtVzN2Yj/EyI8Mp1QF3r8p47GbAiEA+uSsf1PkKSqMSfb1nU7HIloIWqLZGDE2OG1a6wwECdE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":790210,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhxmkTCRA9TVsSAnZWagAAr1MP/1EcUhcfGbBf7wEki4Rx\nzCm16zYHWERVhW/x8k0chQ6vjT4+OQCN6/PDzrkHPsdxNEMi5A28BSjJehqk\nMg7gYTBGKmMgrztlQT6WJmM2aSVYMKehVlWLt4XTv09azrNya+I8MSPCbIlE\nkcTayjdjWfrCgFAfHZ0Lwu5VxhQjB+5ydobQ7QElhyOaqBjwTVNEYCdnBlBT\nelpW/ofJM6EXzFEbfjOvxW0wKps9hZB59pFY3kBJJyGLqPNvfFViYauA+gBO\nKqldG2e3pBWjvxbjMRo4dOD4/IxDZvNhbf+Y0I4ovAazzOApERmX5Efo75wV\nwUx0P5DvuCs9NtmdhIR5EviwjGFsD1ubGEOk0Lsi4s2QUbgUnB3U5mP3gaBY\nfll29Z6DZJ4q4Q3t5lFQw+tXSbagkgovg4b9pb0LVpQ8t0RZyn+lgcngA1Rd\nPCJkbf3K9FsuwrZ71au7EhO8GWj+I1PM9tWIXEBdOGE4LfTuPRPS/UuiRNw7\nzm1R8VO5pQ1jyV7Jau8+f7aIony0spdRWSnFUefTzGUrmFOx95H80xhJ1eb5\ncLaAJE9KqePsUWXnvhDdTlxMgcCrRLRu85AGn2ddO702A9VxXFVenGLxJpYp\nchj4xXdNVMa47waS7r+e9PjnS4pnmxTKATJkAvDRjzRU0f8zGfeP2BjjCDr9\nhUFx\r\n=PMqa\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f3526e849dfc787608bea74fb162a1b0d5e63350","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-25T00:39:00.286Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.2","@balena/jellyfish-logger":"^4.0.6","@balena/jellyfish-metrics":"^1.0.446","@balena/jellyfish-environment":"^6.0.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.4","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.39","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_10.2.3-renovate-internal-patch-f3526e849dfc787608bea74fb162a1b0d5e63350_1640392979231_0.8628998472687117","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"10.2.3-renovate-major-json-schema-org-9ac505f37ca4dd841cb8c7e77ab6726b2198d052":{"name":"@balena/jellyfish-core","version":"10.2.3-renovate-major-json-schema-org-9ac505f37ca4dd841cb8c7e77ab6726b2198d052","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@10.2.3-renovate-major-json-schema-org-9ac505f37ca4dd841cb8c7e77ab6726b2198d052","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"80a52d41c0206c1a2f4f6fb6d90b331ed5147a4a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-10.2.3-renovate-major-json-schema-org-9ac505f37ca4dd841cb8c7e77ab6726b2198d052.tgz","fileCount":215,"integrity":"sha512-ykAk1mY+p6+ey0+88kAhdNEFKRN8ROin8R6GV7GCMKDLE4nlkp4Vc+SPYIC2zxC2I4W0UterGihp8DwkfJ5cPA==","signatures":[{"sig":"MEYCIQCPgphbdDEL+6JTrT6AvOTu36MiMB1l7vjdY5E4QGwT5QIhAPRHt67vZIPJl5sbz2xhkCDOe8AcNCOWbFOXHvyKZ7BP","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":790211,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhxmouCRA9TVsSAnZWagAAYaUQAIL8+3c3hSbxePAtOCkJ\ni4V2X5zHnFN45qpjTKb09zgQvvQ/yS1bcphmjgOgUpNimS0Qlj2uIn1jiCiK\nsfCmNIairMy8Bc1Cm6JCxvRvy7khLQe3O27iFB5r7dKQx6yANZDJWi6CJH8h\nTczt8xTYj/zyIowJDomH+muoSy1H5VJBS5wZSgu/WYADRI5cDIPrjDnUUKGc\nthSoTkdw5RboFvFXlb/b+ffwFPvTqF8oYbHZWOyips07FOuJH8h3u4Yrp7Ub\nUfxHDoJRbvi0lp78DmqfOPoUOtOqEhHu4vwwSRscPpcKSVtTq/cgk3bg2EZM\ndIhbqrnJK2qbNKwis8al5Ww3jKhCk4UEAM6p5Y5TbnQjUJ+e9WfPvhm3oFk9\n7x5/wioag+EfFJkv9EDtDeZRBTzzB1KBKNja/3J8K6BqrEG8ED2CE6G5wDoI\nTD9Wwgh1+AhtqRTyPzwrpJbyTXiOPR6AexxvEZQikUw3b3ufIS3iCZNVlPJO\nt191bw0PBHFNWCVNBe4wjGEcttxxrNTYIO/JXnrr9y8dPyArr7bz0yd75SPI\nYfJCf74cGSrNJqnQNk1whbO7SRthsm75UbN6A4daxutCQDek89ggq3MDyPqC\niV6fs2FSqfUcYuHPRGzXSrhVRgX375SS/NstIyUC6A9DXSivQZYvx/DGd1sp\nUtPR\r\n=pPx1\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"9ac505f37ca4dd841cb8c7e77ab6726b2198d052","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-25T00:43:24.181Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.2","@balena/jellyfish-logger":"^4.0.5","@balena/jellyfish-metrics":"^1.0.446","@balena/jellyfish-environment":"^6.0.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.4","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^1.2.39","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_10.2.3-renovate-major-json-schema-org-9ac505f37ca4dd841cb8c7e77ab6726b2198d052_1640393262030_0.8950117604029486","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"10.2.3-renovate-internal-patch-c19678b6f47d67e4dcb973f6e8284610f1284a66":{"name":"@balena/jellyfish-core","version":"10.2.3-renovate-internal-patch-c19678b6f47d67e4dcb973f6e8284610f1284a66","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@10.2.3-renovate-internal-patch-c19678b6f47d67e4dcb973f6e8284610f1284a66","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b171a4a9db7009c61398eba8e917329fa516c72d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-10.2.3-renovate-internal-patch-c19678b6f47d67e4dcb973f6e8284610f1284a66.tgz","fileCount":215,"integrity":"sha512-eDIb37BgbPwg7OFzYpkajuNRbE/CaGlNi2OYZtsH0v3o4pN4pShL3GIxUt0e5uMix92k7kcaW+jarTFKW0PmGw==","signatures":[{"sig":"MEUCIF8XkavaL7GIgIiOxiO3FqnhB89Wio0SRpXcJ1oaTs3bAiEArqTWgkfWKGgbEjNMMQv4ZSJSFfkjM//GdF0seNHv9xM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":790179,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhxoMTCRA9TVsSAnZWagAA+zwP+wTUOBVvG0JIgKWpKFtS\nP/0g+2EKftbRq5Q+1lNJidXLhaSDLWE6nUmMQp8PWDyZ1Xr2xGIJcDqWPitN\ns9Es+88PnLfxaPKvD0lpF/OMfWrVIcYRLc4CQlQCs6RGfk9PfnloPO3+pn4/\nTZbIcwnBO8X3Nu0lkH1SUtcKbkYH7tIS4QX+PmrUcfDLuPikYDMYT7y7n7LE\nSvwQUHvF+gxAjrM51JgKE/a5rqKPfYD1AUG1I4tlHNP1H3Iak79/lT9D/bph\n4bAOw+e0dZsCDCt5rq5C/AbsvGUoI/SenkbHwsuk2BprO8TBZuC1lDZbkXp3\nBtNN3I3iVh8WyiwtVIAZPAbXri7XWqyp56g8alzyHevKPkolt+GMGC2GF5iu\nMAcxIqLdqTfkEpVK3FAfVDTuDg3viv00OHqdWotV7Iay3rXrYLe4Gpaa8I86\nB7ah+pL2Z9hWa3NtOSUIasyNVeEPqcYT4eA3PwueD4vsQjj3iPy7qlcmDWkt\nrR6kN/5x4gW43Oy57yw64gaAWWnh04Hh+pwndy8yWUAc3z7UskGuoVzmrTmL\niAQKLN5pdE3dGMFCGsxsGsuDR7Yald2md2MWGCpf1/fRHDJkqn3RReckEI9g\nXUgU+DigUq/xAknGkQlC2OXm5tvAX0NYiaK2ZHaWbhj0njUmyyqXCJSQJDdN\nAoHr\r\n=Mh+2\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c19678b6f47d67e4dcb973f6e8284610f1284a66","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-25T02:30:32.446Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.3","@balena/jellyfish-logger":"^4.0.7","@balena/jellyfish-metrics":"^1.0.446","@balena/jellyfish-environment":"^6.0.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.4","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.39","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_10.2.3-renovate-internal-patch-c19678b6f47d67e4dcb973f6e8284610f1284a66_1640399635133_0.04201467267559367","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"10.2.3":{"name":"@balena/jellyfish-core","version":"10.2.3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@10.2.3","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"81eed3c2ddfe656cf36cfe3eac6af9f0ef1f9b9c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-10.2.3.tgz","fileCount":215,"integrity":"sha512-pIjbML1c4G8s4CLI6rAL+Hvv8DP+fszcGXEXC/fQpDLI8Q0pb4PA4onLliMNEYjsdxtCP9V6vnkqNoFC3kFisA==","signatures":[{"sig":"MEUCIQCF6F655sBeN+1n6i+a6rdzNsmSgH/fS+9IGRAaC72sugIgBGtetufCeouxCnffFnrM+dIsnmOVtcs98WoniveU8HI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":790114,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhxoZ2CRA9TVsSAnZWagAA4fUP/idY7V+p+iPiFSMsAQrb\nkV6HDD2NyMPyQ7eM0hop1KlJT6ScOeIFe0vBTxTNEV6LTIiUzJq7ZgNydDdu\nDJcGpaDfLjpvEGwuQXMUYHfbIcCq2NdWIZBuo5WBz1vcMCCazy6CNQlZam3R\ntR/UYeO5gTkj8QXWotrN2r8SCMR5tHnH0tQgq7E6GoygL6s1mlXAndLPGmW0\n3VA2FtI6pog7mojGBrnLwb8VIPa0CFOTTLHiPYQa5ezQeFRwQ6+734QPHcn9\nKCvcp3M2YXbDnguJHar8ivAKpCf6sbQg+x3/5VSYAzODlFDlYD6fN7EQyW8k\nFTTu+8kXtMl2HOB4rgmZCEe7FFSbwCBizBzElc24orvzrHyxKum2VkSCTnOb\ney5WbXb9YvXrQ09STc9bHKfc/0X+hHVoePdvV1yQvCVqClJ6Smdueh5j/s4Z\n7easTEopaVIKaeQtLq8Sddba/4MGMTj+FhgCwjGI4lBqPRIbJPUKV+jYy/pS\n8jIQyznxL01qKej2zwezfHzRndTc7PdIWmAUPMuZRffnhuUU+dxtyKDwUDYh\nMuZ0D2KZ7XBGi6v/02AT8nEtR1EWdhFMVeBpQacgZNIU0f0eEk6TAFNACQyH\nkY0XVWvlVWmvapLp3FKPCkwPc6JHJZ4lCPMJc2ddq2VTCUK1lw1GdX/DewPH\noxlx\r\n=i0ch\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"16dad6b4a33db6e87131b7529fa28c5a1c589939","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-25T02:41:46.515Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.3","@balena/jellyfish-logger":"^4.0.7","@balena/jellyfish-metrics":"^1.0.446","@balena/jellyfish-environment":"^6.0.5"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.4","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^1.2.39","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_10.2.3_1640400502558_0.4108450333522038","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"11.0.0-update-interface-24eb2981f78518a404b6ec6cb2e205230fef4b14":{"name":"@balena/jellyfish-core","version":"11.0.0-update-interface-24eb2981f78518a404b6ec6cb2e205230fef4b14","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@11.0.0-update-interface-24eb2981f78518a404b6ec6cb2e205230fef4b14","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"fcfecf0e62f528ead2417f2d4e9790d714f959a1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-11.0.0-update-interface-24eb2981f78518a404b6ec6cb2e205230fef4b14.tgz","fileCount":215,"integrity":"sha512-qUXjGoZKH1rlvgn2HRv//XcisnNMVll+d8+ZnVHMXz83V/pt4/OfANTb5/GvNmRkj01NHHG04FEXoousoqsZ1Q==","signatures":[{"sig":"MEUCIHpa0+y+S7SkI1NgcGr75R59Wkdq5zOQDts1boRM4QCzAiEA9DUySGfqK1i1xROm0OfMenmFlZ/SfmiESdRu8PBrV08=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":789636,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhyfeUCRA9TVsSAnZWagAAzwcQAIySjrnoOzOyYCOd7w2v\n4gMVS/DwZdZPfkb0hvK1w2rmeBuwm/IS7pp8/AGIEt1dkcukzQRVRcSvWzom\nafEvMUrodTBNjRc74oxvTuvX1PW4PaODwl2Yruv8Crvm4d4AL4AwWQT+LVmZ\nRri2OT3U54kWp/WaVIcxCJGjQUQGeLMX+c9x5nCAEmM5dDvXugwPBUoADhli\nSWnqBujY9QpuOo3/kgTIKM2iOoaRj42MMZTk6Duuw1IKqKAiwic5dg+0A5/J\n5SyIkiuCZmmAEq0fyOvU5QT/8Xunrq4QPA8Mb8gv5wG9Hfj2S3iYUbU4qKYx\nZcZVPcUuwmlGQQRsCoFHS23k2Zitag4vEcF1kdJ7FgJiXnwhlrjhD17SNLfP\nTPlpfErsuM8W6TgPuWfVk/MGKrx+7RHemTJlgO/E9rhz0Q1ZI9rrliUYQK1g\nPdSEm01ktqWaQ5EzgtfzeBS8q+B30UmHkAJvhT77oHbnqO7Wq+8qeKklhfmk\n1GnE0/QCv1BtKnoK8Pu9V2uY4l2cEzmgAoKsftly3gWCEndXd0PxqmXSbcJ7\nqIg8vDkQmKFpqCqBg5Jwu064Jt8SwrqpPsKhxU83eWI+w1JF4234NanM+01Q\n10MFuI8G0CzVXNFWAIVu5zQInQNfet/rq6WoJhMkj7aue7YTlg3zM/XJmyib\nV3yx\r\n=vFmv\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"24eb2981f78518a404b6ec6cb2e205230fef4b14","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-27T17:24:39.909Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.3","@balena/jellyfish-logger":"^4.0.7","@balena/jellyfish-metrics":"^1.0.446","@balena/jellyfish-environment":"^6.0.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.4","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_11.0.0-update-interface-24eb2981f78518a404b6ec6cb2e205230fef4b14_1640626068213_0.9163383346668528","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"11.0.0-update-interface-8ff74b0ab9c47855d74c3c3f39b830e537d5d31f":{"name":"@balena/jellyfish-core","version":"11.0.0-update-interface-8ff74b0ab9c47855d74c3c3f39b830e537d5d31f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@11.0.0-update-interface-8ff74b0ab9c47855d74c3c3f39b830e537d5d31f","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f5e8d51cc69c2cf99f82bbe763c17355268cc817","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-11.0.0-update-interface-8ff74b0ab9c47855d74c3c3f39b830e537d5d31f.tgz","fileCount":215,"integrity":"sha512-LQhdNzgvRkQtMPGtgelTDPqF7SCNaimNr1iGijRAaoCmkE6Uts5+iJ35WpxpmF8IZVfdhLFwBn1Zjlgf00Nvyw==","signatures":[{"sig":"MEUCIQDvh6jFn/PonXiEERFXn2QROoQaiE9IlsjD05aXliiwrAIgayfwSE08ELpzTFZ1MndWLW54eAKr/QoG93gVVW3avEM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":789636,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhyf0nCRA9TVsSAnZWagAAq/oP/ircHgQV/RCd+Vwr7rkT\n3qwhJmgaxOy8OgO70v2TxaOT1upmZlQLSuE7vn+JVxE/wKgCFqgZcNRKytqC\nz1pSPbF1VFMu/2exbf/WFgC/a1ZwaaUrJS3PJteDnv3e29Z2j023y5M/4qbj\nwCQeuoi1FsWoVPfrSnq25UgjIqc8+Xa9+kLx1HxBMc1caTsHt2XIWymZed1g\njYRBhVPkkAjmh+B5Y2zN/DiQ+eM7qtTuJeLHcc3rbFBexqAzK2XFyXmM78hq\nE76T99W5hBqS3NtBvh3Lt5A/OpJPTYMe9/2MEi6WugRwXIkRKT7LtVQ9UVuw\nVDRAt3hFjzeREr74TjvOendtl+UcnDvT7hZCMcBnCKJzjN2So8LJTUIzvKkJ\nQL6EF5PzHIb7bnDqqlaJx4LYg4AKopZdRs8HvptheYvVftV0SUoHyKUhB6Mo\nmguA1+BNSXPIVQxN/cfCAr2NKdM/npAJBz+urWkpZPXNMK44bWZaV93Pdr+N\nAihjPeUjfpzuDlSYDzL3lhun/dproxgZAqeJ1UPWf4qH/IMc8H/o9khlbOlW\niXzL2GUWaT1o0dWxQbQHwh2C3M1jQEekXFFCEM42OE3M5wCVFpg5ZO2TvHbE\nvn1rmYPf1ClReeC0zD69rtwP6d4TNIRY5+NqdgO3GCzkfrXBQeQVxrOpBNa4\ncxbi\r\n=Uqmy\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8ff74b0ab9c47855d74c3c3f39b830e537d5d31f","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-27T17:47:44.251Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.3","@balena/jellyfish-logger":"^4.0.7","@balena/jellyfish-metrics":"^1.0.446","@balena/jellyfish-environment":"^6.0.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.4","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_11.0.0-update-interface-8ff74b0ab9c47855d74c3c3f39b830e537d5d31f_1640627495269_0.23117191435122963","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"11.0.0-update-interface-4e89c3c081abb1ae407e54bb2aebf96938b478e3":{"name":"@balena/jellyfish-core","version":"11.0.0-update-interface-4e89c3c081abb1ae407e54bb2aebf96938b478e3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@11.0.0-update-interface-4e89c3c081abb1ae407e54bb2aebf96938b478e3","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5ec644ed20f3ff7d49a3166cf3e24168ec898d2c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-11.0.0-update-interface-4e89c3c081abb1ae407e54bb2aebf96938b478e3.tgz","fileCount":215,"integrity":"sha512-igO3JgNXEkdAtVm4qdwjK3ZsIZz3PAPXkOa8ZjCMgVAAlyjTCT065/U/ta5+aOyk5S3EgEmsCoEgtrCyLupwoQ==","signatures":[{"sig":"MEUCIBp2VMeTY+igxpLUh+DJuY+68FGh79cS+XWtzYgpt4RPAiEA+vshL3+HGvdZZUHBOXFJGSkSnIm744T+2rQpEbytAQY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":789636,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhygHYCRA9TVsSAnZWagAAHW8P/jTaQYBSrL2SO2+Xm5pp\ndfoehxR420jSO3/GO9GuI9VGklE2ZoE7uq3JFAhpLGM0MROhYO3WnrcXD+rv\nebrUdrHxEyKjsA3yjCr40rHlBLD+EvolfD2cwik1pFQ6oQTXXznkT5zYfo1O\n1Dn4eR1iDeERKPWEW18VdkxVKHstPd7jVN35rO2+PAeZSPJIN/q40j1vYHCs\n9McsfJvgmbYwHY4v3+RzvB440sdN1a4xkZN3bdiuBQb5fl4R5qgcUZZ+K43O\nPVsr7zXLNdwvbUc1eZYE7dw4XnAYiraEqZoUyv7R2caiY6e+yz3bWHLGIRsJ\nV/XtF41V543OXZ4XPQZ7+cmzycxqSXirBqDVfpAAQvTwfG7r7WFEKGZFInnc\nR+LK+B9POqm9MSqFzu/AOBeJvzGUm1dJJEGsPsXpPkCQ/33wUO3Sh6NYr0wK\nuQHcLghTVnNrBWQo/PIrLx+Gjq+GBGGsZY+g28pgpkqrTOyhwm0DlYgv5Ej/\nHk3JEn9FeVNbXXdXcRNd0QF74FkHEgANctm2I8BDQ4yosUVvnE7vIw6u1WUx\nXRIAjxnzDgoURJy6KG2mAydE/6rxc0BGlymJp1s3vbTm7BRdYHSGlBS0Q+L/\ncrxlYCG6Vnhye1iSWz+QRhZ399FJbtEubxzrPrr6wb2j9LaJQ9KltV9IFhqN\nBuXQ\r\n=ltSz\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4e89c3c081abb1ae407e54bb2aebf96938b478e3","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-27T18:08:12.197Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.3","@balena/jellyfish-logger":"^4.0.7","@balena/jellyfish-metrics":"^1.0.446","@balena/jellyfish-environment":"^6.0.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.4","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_11.0.0-update-interface-4e89c3c081abb1ae407e54bb2aebf96938b478e3_1640628696679_0.850538667735766","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"11.0.0-update-interface-dea38774700ced45dd05ded5359240f0132bf681":{"name":"@balena/jellyfish-core","version":"11.0.0-update-interface-dea38774700ced45dd05ded5359240f0132bf681","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@11.0.0-update-interface-dea38774700ced45dd05ded5359240f0132bf681","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"452f5f2ccf1a000b019baa4abf0b32709bf5b812","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-11.0.0-update-interface-dea38774700ced45dd05ded5359240f0132bf681.tgz","fileCount":215,"integrity":"sha512-bhRezc/B9WSdqZnHndtNaPtel3raBoSL3/MMylFcD48GkCJtOD00NYX1r4j4e/JWgUr5YpcNAcQ4PpmdeXTOJA==","signatures":[{"sig":"MEQCIE/rvp+dqpcooYyVuv7NyYyRU4xBuuB9vVGcAaohBdBSAiARHzMurmmUGgcgyQ4yPNaMgjm9+fIYCOY+PCVcoaWgug==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":789636,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhygLyCRA9TVsSAnZWagAAh0sP/3I7JSpLK4Ex3PNTa6ci\nUZN2dLf5eCmUpPWL4+5+Yf4vMPDUmL5RYj/z8tYfzT4debPvZ5V7dWceZra6\nSWAOmTBJ5rIbXcTTYeJU8bZmK5ok+IKK3UqyqLzeENLtUgAQi8E+0EbEja7t\neUFbkNc2bD3tleTWRRIvGT0qg73MAaovQDJbNZkvIajG1PmfrHBU6xUIcZZJ\nuUNjM0+t+e71HeKan1hKMGGAADuWf/+k41LC+mMzXsJY2tJeT1ugo3tF74oh\n8wXSvdayvvoU4q+VgHChTnzwJxPXx9r7ueZP5lro3HkB3rPEtNM9S15vtXkK\nux9C3Lwt4T4FG6MM9GJu6a2I2hqyrI7hCb/Yssbh5qhkctMAgWTtQgZeNh77\nDboNygOagftpf7taVD2yQFi/KnIaYePtEPcl2OAWmxljeJ5/9+oIqMv+NsXF\noJfvFEk/scS+tVwxAxJEltunOMG2rLMguaiYerlCDwfczY1Eru8kwAQvXsdU\nkJRWoyRq9bfLo5MgxwoGe1ynq8KIDNV5yCSinICNA4R3fgS2iirfku2voNRl\noU24A8mb5rchIgjrx8wx5EmkuIsal569OHhaxuJzBXVTqg9lcxrl53Fntaw6\nc+XsJXOig+C++tPQXUEaRkTEe3kow86EaA8exyaQkZkOrdQcLfjNNxvMsJCb\nrnMe\r\n=nFtd\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"dea38774700ced45dd05ded5359240f0132bf681","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-27T18:13:23.593Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.3","@balena/jellyfish-logger":"^4.0.7","@balena/jellyfish-metrics":"^1.0.446","@balena/jellyfish-environment":"^6.0.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.4","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_11.0.0-update-interface-dea38774700ced45dd05ded5359240f0132bf681_1640628977855_0.67422424433877","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"11.0.0":{"name":"@balena/jellyfish-core","version":"11.0.0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@11.0.0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"350e4394d169f6790af4c32340f155e855ffd513","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-11.0.0.tgz","fileCount":215,"integrity":"sha512-hYBp9qZg8NXHazZImv8To6ysfCjWHJSeo1s8s0g4OY+03OrdQ7+nm8O/50OlzMuGEx0m9t4O3hRIhEZ+e9xwqw==","signatures":[{"sig":"MEUCIGZyZjRiMtbnHnx80llZwhkHEGUSQVpUSZ0tBy9KD3JBAiEA2hgbjusQCSISjaNpxrCumHslYATXkAmlzWbhojpFZ4Y=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":789578,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhygS/CRA9TVsSAnZWagAA5gQQAIURp+4ZczEVt4aBoDMT\nYwPoQNRY0o3pp9cS/56tDZjSKyVmt35tze3MKrmDK/Yqj5wkatFYZGTDca8x\nwGPnU/5Lh11zGRuQ8vH/YFvnTvUhy/A9hg3ooOWzofL/kJNEtbJu5NJgrOVd\nwGsxb+X2VwkiVksqWCqnwz7dVK+IXLYj50iz1NjZii2Cb1U+d5ibXBeyKc6m\nxQEraM3T3U8G6KnIWE18QRDwepqN/RxmXF/hzmLxxPcrMRjaxd3KB36bfErI\n/Rq2gfQ0VDqz5lXoQb0eAnNS509sXaxusZUtE//0+R8JbZ8srZxJhdKLVBVe\n8wcuMu5A0WLKh6GC1CvmopjIlDC/VGk5oOf8P7Loep8fCIjmPM7N++Lo8w5K\nmDQer0p0yWAMeipzGLOIEWTbPEpqQyaPSJB6MOM3PhP/ZsssqilDJIO6eyz+\n+i4tLkDjTeWtEo6GC2JLZkMDyeABEGQetdUbzd6Zym6JceFm+LJS401Ygu7Y\nDNiQuiCSVMKuVFuiup2uyOORpPLEPjlCFb+lFlLmaH0Mw0fH7woTk/aZCTnO\n8TB3Ju/IjXm1pKRbH0FQihQkU8vp88r26m3rnv9gQPFXQiiy9yQm4RvSqEtI\nqfzb1HU5qqVR5bgB4IN4aZ+YRasgL2KoJc4+ydopUcUT43SaDtyg9bjcUTeM\nQL4X\r\n=Zm3b\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"84164d2cbbedf7917cb80bf39e6f78a26226bef2","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-27T18:21:40.322Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.3","@balena/jellyfish-logger":"^4.0.7","@balena/jellyfish-metrics":"^1.0.446","@balena/jellyfish-environment":"^6.0.5"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.4","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_11.0.0_1640629439727_0.45215419513143096","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"11.1.0-add-test-utils-44660dbbbbeb90eeab16d7a881acc999a2919a36":{"name":"@balena/jellyfish-core","version":"11.1.0-add-test-utils-44660dbbbbeb90eeab16d7a881acc999a2919a36","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@11.1.0-add-test-utils-44660dbbbbeb90eeab16d7a881acc999a2919a36","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"7e86b48a2564640ae8ec81f3b382da86f147bb30","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-11.1.0-add-test-utils-44660dbbbbeb90eeab16d7a881acc999a2919a36.tgz","fileCount":218,"integrity":"sha512-/b/NUZuLzlk0LhRahvii3IV3cwYcjxFNTq5muYOw/nKHRuJ/peHZRsH6+mlE5Eoxoe+PpnKWaw6QQ8llZuiiIg==","signatures":[{"sig":"MEQCIHeBCweb5ofeGfxMY7U9xwmwEAF9OEvWb2HtDE3zg3uJAiANO98BhXSxrpzmlp1c2X9d1iByGJMuh3od78yw+AeMtg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":796218,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhyg4OCRA9TVsSAnZWagAAa8MQAJ/60JFGwLmi1ufAItKi\nEh0tDucRnAwRD7wzbvrN8dKKcvbPRBVSc/MD6QLSp8i41zUTmSKPZ+qkA1Zb\ndLfiyTh6qamvbowYkUyCfpgX5qVF5NYxf23Sd12mInB3nOcLN46VPX7vWBOJ\n/N4nvKCmxWdCU7Ce5LACWKPs5iBaOY4zwKON26kKG2F+FVsVhv1234XbnnHE\nQeAiwu49No8cFS9haMCvz874sggqH/9Tz9nlXSGwDzXFyiN2s1dtR8BJ54VC\nC0yQK6rXqnpPyntzZn9/uTmvViv1K733bca1kYJ8ESs8q3XfSdsBOBcU5dAl\nJYDiolijLEjT8M9oRdZ5nffOQ37Wc+SVBhZ3ZSJlOxc0sS/euDZA+aWKbxWR\nD40abOufz+8Qa9AenTXx1IGgcp+y3upcVly+14h0eciT0FU9VukzYDpPPkIj\n6NpMk0EufEzoaaga50TiPHuZDbcl4zLhMmXw+7bf0+USW06+Behx8SpbzuuE\n13ZCrnN6SJnl1EyfmK2hSIJaZEuMFfnSbBsopeTsfEme3rsvr9EqKLsC3WEM\nLO0ve6Ke0xAE+MX2w9LOGP8M25vrWI73Qp/lomRqX2FundMPZEe5l+RVgjLa\n/Ryyg4pDXeZUMC9jgnVbA82Czb2d9UOV9KHreICdi+xQMcnNkXJK5kNW/N5a\nhp2I\r\n=IWCJ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"44660dbbbbeb90eeab16d7a881acc999a2919a36","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-27T19:00:40.367Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.3","@balena/jellyfish-logger":"^4.0.7","@balena/jellyfish-metrics":"^1.0.446","@balena/jellyfish-environment":"^6.0.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.4","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_11.1.0-add-test-utils-44660dbbbbeb90eeab16d7a881acc999a2919a36_1640631821789_0.9931063142090979","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"11.1.0-add-test-utils-1cfe4ff0a630295974a37da41b6c15c67d1155df":{"name":"@balena/jellyfish-core","version":"11.1.0-add-test-utils-1cfe4ff0a630295974a37da41b6c15c67d1155df","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@11.1.0-add-test-utils-1cfe4ff0a630295974a37da41b6c15c67d1155df","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6ac5205dae7391a16c1af8ed023a295de6101482","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-11.1.0-add-test-utils-1cfe4ff0a630295974a37da41b6c15c67d1155df.tgz","fileCount":218,"integrity":"sha512-7bk2cCbyC7iMH7bj108baJrFER48udEBqU9HdnYTqDDW5rA4ytUfmASbHZlGeiwAoSj8SqTxTRklHqFghImc+g==","signatures":[{"sig":"MEUCIQDgsK6MpyytovvuQmknR5PZesKeD/bkzppEW3rAHjaQkwIgXFTQHweEr8Vfpw/jEoxQzAKc3WnjpzvJel0L82Arsp0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":796740,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhyg9yCRA9TVsSAnZWagAAbm8P/1AOw9wEZAnjF6I0Wtx2\nFkm4GA3F00K3m/2lWJFxNOePys5f2/jt7JDxL+OsgisJm/zuBogN7uTZ1Th9\nSUlTUdQ959bNLtf7840f4OWNmgUkat+gSyyY5et4GcXdbaokKh6/WcvP1Tj6\nqqXM/aYlP8hLFmvGmh/mlmLJwKRw2JeOu4mnm6Y/f1JgSKPkoiKgsLPf8wMv\nrzWOp8XELASqbB8n6av8a6VpUMG+ywqzzyWoQ2UqgWb2vMvMCyHG3yTu6ngw\nhEdKEj8kYn2AIxXrRwA5A+UqQTQ5aAd5Ujnf0GOXlW5dXzLX46YdyVO7KTgo\nJppxpjyT4qlmdkCop4cqzlMg/eOIRrLdkDbjcRlgirFSaVYlfjktB1juCaFD\nmnZcEqmHJgwEsXYLnFLTp7YfG+30CCk9qOTFJLferO6rYT5Jum1LYrnju2zE\ndiaaBxtZQ3bFLeJaSflfCuG9YoTCvlYWbRQ3NxdWI1bDYaJMnfbeGxazYMOi\nTm0XXfNH4CL0xW4K7d6xZwNMwFBTDo5Pr+yKYhpq25ZXKmtSB9ggp2HXgLJh\n0ULDh8aN4admh59fWjHiTApaxvaILWbtpC5AEmQi7lNaoHSlDzAR93hewXoU\nRohKUDOBzFZ3j/Wl5ZPhp2i90/WArAFlBtxz3qY5xWJmqX5njCqZzMFVzldb\n692I\r\n=qUSJ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"1cfe4ff0a630295974a37da41b6c15c67d1155df","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-27T19:06:07.510Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.3","@balena/jellyfish-logger":"^4.0.7","@balena/jellyfish-metrics":"^1.0.446","@balena/jellyfish-environment":"^6.0.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.4","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_11.1.0-add-test-utils-1cfe4ff0a630295974a37da41b6c15c67d1155df_1640632178205_0.08299231962352893","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"11.1.0-add-test-utils-92fefe6b037088a00868d58e454a1c20356d4b27":{"name":"@balena/jellyfish-core","version":"11.1.0-add-test-utils-92fefe6b037088a00868d58e454a1c20356d4b27","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@11.1.0-add-test-utils-92fefe6b037088a00868d58e454a1c20356d4b27","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e85f78b3598f7e45ac8b060837478d69fb49e9dd","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-11.1.0-add-test-utils-92fefe6b037088a00868d58e454a1c20356d4b27.tgz","fileCount":218,"integrity":"sha512-KM6Gy/Ltm9Bn3jfHQeeAbgrkFdYyLWMf535qiBF/Zf+QxX3o+Jwlw00KKlBjyqVT0UdHWS7vK5z6w+8zlieWEg==","signatures":[{"sig":"MEYCIQDIuXZUK/94rNi6ZBsdJc+BS40hcs9ZIcTKShFLXJGfiwIhANh0UDUV2PBAQBqdhjskwwrt+zzxAUf4wRwgxuIntulv","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":795683,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhytykCRA9TVsSAnZWagAA3UIQAJxWiHOrKSrVjvPcbaA4\n/aaZxg9pS32ltp7Kwz8b/5RzjAL9HgP3w9teH8SReJ7Gw1/qcLTXtpr2wdxg\nCAi/x4G1FWLmg91mCfYKUeMAyrVlErCifeHIjJ0BHdAmMTQ9K0pLxo6br78l\nwtNw5EVSezrRGCafPRoTfj+WvBLFJ1l+MC3CLzj10LBW0d23FsFNEHS2mngs\nwp+eQZF7VYeZC3jJog0+zX1EUFLd8ix1S1X85Ml+WOWDTXrK/CImFCK/xr8U\nRK4LcL7jpO9BKqvLhFYTvYp4AHQxnTKLORK/0dih2XE+Tfw80m35+HqiOY25\nOAgEq/c//zm//qN/D80QLm7eU42hDb34EXpkfK0f3BjExdK4XRTfwcfAC7Db\n2R6bd+unIFU5BxpUfhqM32AsRuU0kz4YF5pDWpwAAXZ/UitiGd9VkWQ4FoyB\nEByOQbkFFREBERxjdSUEsPMpRrwCJYkKI1RCVhtvJY7MCpeD0a+w7NPlGlYB\nosCgGm1brl9N4CDlkYWFALLaA1oX1ot62OJei1++z1EaVU3n4S8fRB9vZsvr\nJww38xlLwVobvrn/H5GLiRSqebIEmYeS1Xfa3cDe4hE5Bzl5TTBUHBNUF1ru\ntg/sNLkNWseGaLHBYH8uDGv81KyZPXBwlwijt6eW+sZtJdVkqsaEIj2RO6Zm\ngK5/\r\n=jo8u\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"92fefe6b037088a00868d58e454a1c20356d4b27","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-28T09:41:32.646Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.3","@balena/jellyfish-logger":"^4.0.7","@balena/jellyfish-metrics":"^1.0.446","@balena/jellyfish-environment":"^6.0.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.4","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_11.1.0-add-test-utils-92fefe6b037088a00868d58e454a1c20356d4b27_1640684708585_0.4305949154580724","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"11.1.0":{"name":"@balena/jellyfish-core","version":"11.1.0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@11.1.0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b458cdfb3e854fbce423de28bb868ffe623d97db","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-11.1.0.tgz","fileCount":218,"integrity":"sha512-w+wglGHdxBWU3Yl5MXZYUUi/79KTATsNEUR/ekXEKwWWdN5P68/Lae+OF8Vva2cFZhV4FEMU8maSXOsFa5p2NA==","signatures":[{"sig":"MEUCIQCoHdz2Kbqmm9hBtdtYE0Gmmuy1srE5Oa5PY7CpvS/SmQIgUKTvCrMOhEGPSWIfMLz/yTFhBWUErYsnTyJWO8zb4hA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":795627,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhyxM3CRA9TVsSAnZWagAAphAP/1g5TBGhGYJ3G1nlZz1G\nAXNw47H0uLl6ZLkViNGlYi8JNU0RUP2h156s6/IrlsWebecSbjHQ6kRMvnDo\nXElBFEPf5k1wY/lGzvJe16FIW0OI5vz7Ga1imOyzinpKpczKEcn6iRacBXcP\n83REJOkK/Dey9sSoKou2/T8we/0T0VyZnbvkXqT/f+OrTr/xarzeaGWkJmQY\nap4zqi8YO7xpWrBd4Qjqh3YU6Vf00iSH0jr5ZjO9lrR7Xckcvjd7FF4loojo\nrf1qjV8SazClPAWGlT0WmRQwj/YeEJhjNzBHa3lPOziYIlVK1rYm8s8dSYdn\ncuACpmSV3eMKojkccCILAd3d1ty3bi2/uthdTd80VEz+elJQFBAxjLMOmlW8\n0HlpNS7o8vzapdqGmjvkq900xlePZGKB4tZHTcDrDP23UBig3iknjDRx61vo\nSEHAN2XRLZhFvw+jHr5SH9CRCnC8ke8z8+s/QkVUJzfesFEYQW84BAAuUnQi\ny26ndnoKtWJRSXeKG+xhGndiP9YNQh7uB9Bu3oMLNXlTGrZgXy1G9fgeDIk8\n0vXuoTtlzoR3jdJLLdrSPVnUf4b4ZsCKPJJugW0YzSPBgqmpnC0i9hpjvnIp\nt4jr0oLxC+fQtkXSMiim7qTIsrVR6AcQpKLQpnmoeb37rc7EiRjTfWehdQTV\nlLFn\r\n=Vf3V\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7c564b46e0fa4f8d225194dbefb669eaef5fbd84","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2021-12-28T13:35:50.654Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.3","@balena/jellyfish-logger":"^4.0.7","@balena/jellyfish-metrics":"^1.0.446","@balena/jellyfish-environment":"^6.0.5"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.4","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_11.1.0_1640698679729_0.6823650122066616","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"11.1.1-renovate-internal-patch-4ffda376b640dfd2eb3cc710831977e485084f6b":{"name":"@balena/jellyfish-core","version":"11.1.1-renovate-internal-patch-4ffda376b640dfd2eb3cc710831977e485084f6b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@11.1.1-renovate-internal-patch-4ffda376b640dfd2eb3cc710831977e485084f6b","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"215617cb3f4eacfbd1c364fff3f3074b6181709d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-11.1.1-renovate-internal-patch-4ffda376b640dfd2eb3cc710831977e485084f6b.tgz","fileCount":218,"integrity":"sha512-sUqyyFBZSmjacJxrmqUUe7fSU6l4GIedAhKrJe3r6JEq1BLIO2lGT822tIab9EG14Gjy0oCXS2GJLvrHYPi//A==","signatures":[{"sig":"MEUCIQDdvYLmDUDMfjuM4+XWpPeY5KPQH5ry664fjVho3Q38kgIgA4eEnKU600/KyctGG1jT39wzznjQemxkJowbZ2UGWuc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":795763,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhz7Y+CRA9TVsSAnZWagAAqaAQAIxciRPXx2DSMeyzFL7P\n67PuJ9d55t1+pkouaKeNMUGQ076sPXzfPhIbVdLTA8d1sCGB0wohSi/Yp8qC\n3jVZ2cumXaPw81X/UKh8CT+QQxddLMk+uCOQKWaBhfzyavKnEM4VGjnyar/h\nbYfFNJiCSc5oDJ7eagJPhooadeJJQdeVG+UpQ+SWjry2hkCDBrPVR/MC6BhB\nT+44YUOCWd5AW4Ez0tqPsqODZiMci1xpkVLK941eH/AXKqntDn0/AMzD+/u8\nukA07soLlkLdJh+8F60PAUIWYxsn1TCzFZ2LEaZY22Ivnh1SOTNLwnisjqei\nu/Ng9BNPPyCm1YyH9p7ExZ1ER3g3Kc+peb8MJXV/+lxr3LOH9gHUK3K0VA87\nzrobIZNT/RBkmaWniuA/DojjOYBiwzb6JPX4dtiT4nukB5TfJ9Xyze2+nCvW\nMNPAr5WDjLNsVKKoCkGSGAqEJkew87xjzCKOB/8Iy0KfZ5qEzTvmzbN0s8mc\nUKDc1godIG5LVHFgLiRTxevZ1PwLhS217XpzJwj52Xadepblzmk1jtTjU8y/\nLm3yedGWYijckjSp/0pptZysVVx9wxfUY4+NhZD8Kim9x1NNi0gPvM5uPtfz\nTQYFfRCeL5HrFPyAC1gH5VNTqnXcl/KyFgipt35TIsCBH313OB6h/PC0E95+\nwomP\r\n=XhU8\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4ffda376b640dfd2eb3cc710831977e485084f6b","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-01T01:58:39.821Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.4","@balena/jellyfish-logger":"^4.0.8","@balena/jellyfish-metrics":"^1.0.446","@balena/jellyfish-environment":"^6.0.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.0.3","@types/uuid":"^8.3.3","lint-staged":"^12.1.4","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_11.1.1-renovate-internal-patch-4ffda376b640dfd2eb3cc710831977e485084f6b_1641002558101_0.5102425795864427","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"11.1.1-renovate-external-non-major-c29a7e547d98b21477180c10bb47fb5331e8de7a":{"name":"@balena/jellyfish-core","version":"11.1.1-renovate-external-non-major-c29a7e547d98b21477180c10bb47fb5331e8de7a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@11.1.1-renovate-external-non-major-c29a7e547d98b21477180c10bb47fb5331e8de7a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0ba06dd335701469175307dad746e3cb315ec31e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-11.1.1-renovate-external-non-major-c29a7e547d98b21477180c10bb47fb5331e8de7a.tgz","fileCount":218,"integrity":"sha512-azGB4cOvWiYLZjMvLlIHnycO5SMxw4Mkw9NTA9ivORoQeSdXfEUtOnQ9/ali35YBagt9uQmgfOwl2eHcTRo2/Q==","signatures":[{"sig":"MEQCIE5ddhbQNUHbGvTDjqsg9+qQOpSEy2KgqZi6a/eWgm/+AiA/kvB14aior+mfKto2dhArWmKUsk7rKUT9euMMR8+tiw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":795786,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhz7Z3CRA9TVsSAnZWagAAvusQAJyYlG3UKJGr8qdkJXYV\nvCtZHCxhOqhziJHDWO/Pz6wShFo2wbQiiJneyhMv85JkygmR5ZXJ6Byz5VsC\n1/ZOmiBe5g4Zvxkj19IMnghZsDkSCJv7EgEfj9EUQhAZoTr1BxIYdG3niPIo\nUiMdtZI5FrYjhFzg1g0G8pb9iSt38iOqL3ZC+q9/jeuuBff/0gLTvB7BxGNB\nuCZkOZ0X/4wBFAK6gKgMzVqyvFr5cEkY/B785ec6YqhuGKXaOR61E8N352dZ\nqeAKAkpRVWGV/M1CjQP1OfMr+wga/m6KODtZBUmcyRBh/3K6vAk6AuN3SUFp\nnbi0il2a/Dt6TOoywvePvOfMeZnUKpakueKY8sYIcP36SADiEAnzMNlIVWID\nLxGDTjTtJz7AZmMpSg1aSh7EYmiH12fKQ4AsJxoyvic1t5+YVMoW2K8WFv8Q\nxHDO7oMfhzr0YyEAge8Wgs+wy/Sc4BJuASKSKDvMfJvMbsrhY0ZEZ+eo/3Y4\nvW/2nGjTaL1e8EQTn6LoZL3a42jKNQh7o+IslnV49q40PaOWs2DsjulzeTBm\niB29ZYtYqGu02zW27xWzCC5rPtQCDYlcaEmq/LAKSlHIyyNbdVJaw2IC8GBD\nlz7w9UT4g77Io3OlXEmCD3RQB3qC7U6FQL3urjDuWdsZtYTLMfkB2nUXUQFW\nni+4\r\n=dGXQ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c29a7e547d98b21477180c10bb47fb5331e8de7a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-01T01:59:23.604Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.3","@balena/jellyfish-logger":"^4.0.7","@balena/jellyfish-metrics":"^1.0.446","@balena/jellyfish-environment":"^6.0.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/uuid":"^8.3.3","lint-staged":"^12.1.4","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_11.1.1-renovate-external-non-major-c29a7e547d98b21477180c10bb47fb5331e8de7a_1641002615437_0.12826642737823502","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"11.1.1":{"name":"@balena/jellyfish-core","version":"11.1.1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@11.1.1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"805a15736d54c7577fc641e018bb6692dba3317e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-11.1.1.tgz","fileCount":218,"integrity":"sha512-oZ7pL1Ucom7L6fpW5LeUX+y4yEwyb6qGqAPf+rOM3wdXw0fz0BCT8o48vdfSJeF1F/EBSBAGLw/tt3eulQdDHQ==","signatures":[{"sig":"MEUCIQDYkx18UbXnQqo2YSo38V7XJ19Rbaj4BztaJyTU8oG6xgIgbdwEEBuwmcReJ+gbfkqOEv4ixyEJsRulgICJxh35d74=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":795717,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhz7hMCRA9TVsSAnZWagAAN7kP/0H5KGKb8T2lHBaJgL3O\nPe0pm7LcbGWYUc3YBZTpP2fEtxtBbIA8mVZNgQJefnzMH35ynJ6/DGvWxKp9\nyKvQGRudbDctp85LHcT0CM7fHqGIEp6mjY6LRbAQw+nKd9oPoyvt14vVqV/V\n9R5bI4DwcoahoE4CxNjzXjzZFk0McNhlaEiBl0I96zPcvsSf/0iwvZ4M8nx8\nBuiAvRKklPF4fNHqwVzh7gFLJKcXudqmXCdbqFE+iQ05PoRVA5NOieXmOaAr\n8sI7GCJmrR7UIb2DhZ8d4DOc4I2G3Bks2LHv/sbAdPX88xzFrTUwnjlyJKv4\nkOmW0i4jUvHF0TBAae80C7oZjh6e7fA8LLj3Ken2sL3ejDkpl8PdN1Irkd+9\nVFMwVCTjWOKxl1RQ1F5E+ZPcb9PM97TSsDz8+Co58PDVhFPQcL/rVmgGQdWB\nahvirxEQ2QQv9he7zr6eUXP03We4z7pGBglP4aWd2u4Og+ZUCYeqw2df9ol7\n+r3ZyRwHdI6g05dbzoBQt5z/+87PVviVnMbUYvx/CEfIOv+FQwkk1Eckv/4A\nIxbtm0TFkHQMM4Y/q7j8tFHAwRuE5ItbluZQagh2UgGLBTwHVne2ojUoJtU3\nR355teGNghPLRH0Tk+ApFhvfnyeBEk+j4TJVdHacqmzI4VrIVWtblPRpWZiw\nkB3D\r\n=6ICV\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b9274c564e7392517681ac9013ce3ef1a725e03f","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-01T02:07:40.476Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.3","@balena/jellyfish-logger":"^4.0.7","@balena/jellyfish-metrics":"^1.0.446","@balena/jellyfish-environment":"^6.0.5"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/uuid":"^8.3.3","lint-staged":"^12.1.4","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_11.1.1_1641003084432_0.9645299978838386","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"11.1.2-renovate-internal-patch-59541ed03ca0d3ea6942be951e4efbdeaf8ed949":{"name":"@balena/jellyfish-core","version":"11.1.2-renovate-internal-patch-59541ed03ca0d3ea6942be951e4efbdeaf8ed949","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@11.1.2-renovate-internal-patch-59541ed03ca0d3ea6942be951e4efbdeaf8ed949","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e47c29444ea1ceb0f1dc9b4a643212115148af84","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-11.1.2-renovate-internal-patch-59541ed03ca0d3ea6942be951e4efbdeaf8ed949.tgz","fileCount":218,"integrity":"sha512-76gI/ZDxscUcoXvgjWfbRtRUdJgs85F/bBpn0WSm87fncFyGFhQLqwGWt2lhEnrIDWL0pIuAVw4t4Ayh/PqhBQ==","signatures":[{"sig":"MEQCIG+cmed+iaqW82N5ijPGABTOs+2nx+pHkv+g0nM3Qn2PAiBZF79L/+fKPjgBtFqbHwdGPVf88PLRPfNImu1l747l+A==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":795853,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhz7leCRA9TVsSAnZWagAA6BUP/i+dHAMr0rW9wQ5W54Tp\nEj1CiDyBeJ3lK4vLLpcSt0FgqCyQvJobO9G2805ZspPqtQ4oR6FGIi0n0dce\naVdn2pt82Yqv9fd3hIEM00gI/iH0onQIQQz6xTqrXVD4O//ziTAixfVrLWkI\noC4R66Cur5ohANMMYq67vmavD+K6cPqcygKYvQmJXLj5wD2pn2RGgdydmR4i\n8TX4vvs3FI72UiNA/gCxVxrhh9TUGbUPMD1AD0lxaESsiJ+MyT15Obhik2ko\n3G93gmQN3SlnJ8jusFcDCaD/DtdD0BNsMDd16yUpcaeadNvw6K2/SsdU3Ke9\nRngI2AtI8Q9XonI+HGK2PcSdVNOQNAZXl3K66HCuJiZoe2dO2tPicyU6yzvD\nvgY/OzR92SVIGaaJg9/1zkM1UwBl7B/Ot9bu0oxjL090rOK5cfc6pUoEFDIz\nKMCvDyfTkfYon65o6GMgSyA7W1MCu5tDgptVXmgY6cML88SwlfxvwYi5E6qX\njRhz4oWeIUc5IURVJw8veHyWC8/FssTq1zpSSyUl1jhGjq57Z7jkxtUS2ajo\naPBahSvFPY+3orC0o5X6JqcGy9VZcNfqxb3Hh47P+4T7fP7rK3LXrpvUjvfA\nNKx4DmIpcQkyb6nFRTohghemK1JBQwDnfh+9weSF2fHphnnSlY1gpfLH+wBa\n9NWY\r\n=aiaV\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"59541ed03ca0d3ea6942be951e4efbdeaf8ed949","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-01T02:09:58.668Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.4","@balena/jellyfish-logger":"^4.0.8","@balena/jellyfish-metrics":"^1.0.446","@balena/jellyfish-environment":"^6.0.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/uuid":"^8.3.3","lint-staged":"^12.1.4","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_11.1.2-renovate-internal-patch-59541ed03ca0d3ea6942be951e4efbdeaf8ed949_1641003358531_0.9800009307983357","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"11.1.2-renovate-major-json-schema-org-235594c2b72f5a5fe24fe8afe475a2f527c22ef3":{"name":"@balena/jellyfish-core","version":"11.1.2-renovate-major-json-schema-org-235594c2b72f5a5fe24fe8afe475a2f527c22ef3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@11.1.2-renovate-major-json-schema-org-235594c2b72f5a5fe24fe8afe475a2f527c22ef3","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9ad32a34db3653b48d05e5455248e0c632958de6","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-11.1.2-renovate-major-json-schema-org-235594c2b72f5a5fe24fe8afe475a2f527c22ef3.tgz","fileCount":218,"integrity":"sha512-GmjuBFkmkg/eKYccWMhj4X3hGQuMQ88it4F/bI0YZFRER/4MaHKhehDg108+/bb2Ua9KZXAC1/scS6O8x/D8HQ==","signatures":[{"sig":"MEYCIQC1z8tCS5jEuV+uAD5Z48V2hm2AfGuB7TKlcXwJ+Uo3RgIhAOcyg8oaEpcj2+4tLH+DJDAgxycfkDno5qMrWLQ3f8XJ","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":795885,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhz7oACRA9TVsSAnZWagAAW4wP/jj6/0l7oIPBwLbgiRwS\ndSg5AkFnTayPCuvHRoLZrKHYJu1uCRtd9bfBeZghnrztT/oNdhyxhhLcHuZC\nd24QuDM1G8CCwfawZq2BajcSHZbZuzTIjlvaAD9rbF2ygCGRK1UE5dthmwuQ\nTV9zsrh0Wv/Kct8fQrpHQCNYdr1+VCyDSBBLdQHsc4gEcYu+H4SVnai5rPDr\n7MEmoIAxETqgj1cMTHMixFvaJOtRTnvt/HQ3g12b/Ez2DRoco9G/O2Wdlo2i\n2nBIq5yLEKDsiyBgIQFcbC2ANOGb6zks9EyDdEgpHm7mxTrYuHHMTkVWE53z\nSUBRYa9fOECHEqQvaOaTbSSGDwTGWqVjgm/6lI3ImKU0QRMDxziq5pBcaEE+\nHclnPyHkWb2yiP1J9mak3HeoSSXDPiOwbQNyWkgPZe14vJ59UClCVhKgbCqN\nl7cZEDp6yuds2C8FwVCpLj02h0aKN2aYoAdY4cT6Une/+Jfn5dd1gFESQpLI\n+wThsd99Trgky2Gi8XHIrEnYQixVhfsvXK1EnU2ACC6R9/IP/C6ngpZKaSiJ\nuow425XnCCDlH+XDlKNzQwWvv+uXpCZyjIOn0tgQT6lGVSz7oA20ieyKicsJ\nKX1i34R2Qe3zIqkWdeHxhKI8kFjh+dGmBHx1dt6AHpgy6Uy/RlxTtflk7Crn\nzvLD\r\n=kab+\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"235594c2b72f5a5fe24fe8afe475a2f527c22ef3","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-01T02:14:35.161Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.3","@balena/jellyfish-logger":"^4.0.7","@balena/jellyfish-metrics":"^1.0.446","@balena/jellyfish-environment":"^6.0.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/uuid":"^8.3.3","lint-staged":"^12.1.4","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_11.1.2-renovate-major-json-schema-org-235594c2b72f5a5fe24fe8afe475a2f527c22ef3_1641003520568_0.8337046863103694","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"11.1.2-renovate-external-non-major-abef2160b33ddbe7d294d88f52ea35d17a931599":{"name":"@balena/jellyfish-core","version":"11.1.2-renovate-external-non-major-abef2160b33ddbe7d294d88f52ea35d17a931599","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@11.1.2-renovate-external-non-major-abef2160b33ddbe7d294d88f52ea35d17a931599","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f954a7504fa7e77a1a8df2cd8a9401e0e8ad6017","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-11.1.2-renovate-external-non-major-abef2160b33ddbe7d294d88f52ea35d17a931599.tgz","fileCount":218,"integrity":"sha512-x5/NeGXGLD8xOmiXA0U1Hnnf4Ha5in2fSyp9P9gfEBh0AjIVELh+7IWAXqiinpB3ipBh7bmyPi0K/nbxeIeFaQ==","signatures":[{"sig":"MEQCIGz9nYmR4rjbE4v3bQAXpV9N4Vzmrqk/uuiANGQ1zmxOAiBXQKCCX/7UXCL7d2XQWh1p1nHvT+4C5BioKW8lhDvfuw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":795876,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh0hiLCRA9TVsSAnZWagAA7SIP/2s6QJPQZ48W8LMm3g1J\nsLMcgcOcp6jXFXQgb6SDL0g4dhSsSEQ4yYK34T4Ur9XSVeuLetk+9pmKkKxl\nZD0cG8J1uJTZksXB32QSHzsxkv7hCBWLrz9TW44wcngORpDXD+0oWo7BE2B8\nm7iZ74skYWbuAjln3ioIgTMc9TptHu4v7ZUZcHZ2rMTB7bXlzdz80eFevd8g\npg/Uw5D0owQP5UXPZ9tIg7xbd6Y/8qNX+vKW3aT5ptpUNg+rySCqGAVxE/7T\nVZN5MncFzNroy9oRGfLR5toplPr6R4jfyoVRlyp84e6Ihcq6Xiwx3quDIYdC\n9Q3yrnvvvzSW0jhUGN7N9Rbc/Lycjo1qnqyIM+admG+p+JsxHx4vg68F3BoO\n4KLQ/ss53FA72hUhdfM/BhGo0Lc4bd99amkU5/r8Y25+dco8TEhRLS3nqwDg\nV7G9H4aCcNFk48Hn6wgXOhcHKCr/mWbNlo9DDmceNy8vJGhkZFmDZNeMQLOR\nC36f4wx9z2adr4Sv0nKwzo/rndCbA6ZXK5L0YmeCMK3LQ2CpygoMMjLdkM5k\n4pqIBhPd6DG/uAk6oUxKVAGueqCdtxIVTwEzmMKbiymaUKCJSTDskNf6jweJ\nwGMi3kUNn+Hcgob9ntOjld0r0YHxylSVncV3MuZ9+/419deYHg8pnzE7u+gS\nlgR1\r\n=tRCG\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"abef2160b33ddbe7d294d88f52ea35d17a931599","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-02T21:22:26.963Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.3","@balena/jellyfish-logger":"^4.0.7","@balena/jellyfish-metrics":"^1.0.446","@balena/jellyfish-environment":"^6.0.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/uuid":"^8.3.3","lint-staged":"^12.1.5","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_11.1.2-renovate-external-non-major-abef2160b33ddbe7d294d88f52ea35d17a931599_1641158795161_0.17317822130630534","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"11.1.2":{"name":"@balena/jellyfish-core","version":"11.1.2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@11.1.2","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"987654da93a3a5faa69e0060e279ebb2842ece25","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-11.1.2.tgz","fileCount":218,"integrity":"sha512-KxoL5/nb3CYTnMMVJoAlN1LkQOJT9YPOW/IuNhkw4hKoWoqyZqGLRz52eN25gVoxlbx2wuJFs87OxNSp0BJavA==","signatures":[{"sig":"MEUCIQCg5l2Z/GwdKDFfHzK7I0+gaQDZOurybFJDyGAntVm19AIgeLYCcDWNfAEIKUmWwbAnIJkDKxs0Ko7lqoE6qsV6V0M=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":795807,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh0ho5CRA9TVsSAnZWagAAykAP/3OZ0nhjRxowfX0afiDS\nk5yVBn9kkeQCtE+pUGDmuelLCecutbPXk0MbutRMymJaMIvvU0NiIrO3EFxM\nZ5aniB8LR0nQWrYzXp9KGZPB9iJNRe2u4+fYxOhmVWvp5lqxl1SpgeRN4NdX\n8YAwaNDRG7Of5j6WjQUlmD9rD1GRvP2kG07WkkQ7T6e9HlEKBVC32MfTk3rM\n6Dh2WnuEHJoT9AIqnNISM+4Lvkoyz0hs1pNw9LRZNG1ujk6AXy52XopGvbd8\nIL8Jq3E1wZXjiq9iOqyhD8Iu1xVa4x40qyI4oKwupNu7QR33iyM8IO0FbgYc\nLIqKCeNMB0jbh0lGjdJrEO7X65g9th7KgDNd/0QFcPAe+lZZz3SVXGgDgGuF\nDd0ZsfeqzyHB7Jf80x1jIJAhLm/KCZRSjwy0X3hsj8LtXTwaY1CXJGtN1REl\nQ7+vdBxTQ+pEWUgjLLtwCpuvp+IfJ18B/1X5dDNe91520lWEFVxg6Ta/6no2\nmPD//1srrtpT7HDfrZ9gSckAL75IUAK/RhgH59wYGMLevi3H7f9WP4F62iPg\nS+IiMUgcXzVIGauyolcvTDGkskkEHOWjnaVA2djA+XeQ+i9gOJiVkaqFVztm\nC10ZpYSX5dikoZgzVpwa3WQUaXxe1a2hG+FCmVbI22rKc1CeZSnL7GwUESES\nZdTB\r\n=qrub\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"21dca9dfbe89b9e8689c189fa80479e720f70cfd","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-02T21:31:08.774Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.3","@balena/jellyfish-logger":"^4.0.7","@balena/jellyfish-metrics":"^1.0.446","@balena/jellyfish-environment":"^6.0.5"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/uuid":"^8.3.3","lint-staged":"^12.1.5","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_11.1.2_1641159225515_0.0008716662168610778","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"11.1.3-renovate-internal-patch-d6af82132b84c4294c99a81b2c47ad150f424ea3":{"name":"@balena/jellyfish-core","version":"11.1.3-renovate-internal-patch-d6af82132b84c4294c99a81b2c47ad150f424ea3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@11.1.3-renovate-internal-patch-d6af82132b84c4294c99a81b2c47ad150f424ea3","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"bf64891dbca75ec0b72b70d50ec005e9f2dee1a6","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-11.1.3-renovate-internal-patch-d6af82132b84c4294c99a81b2c47ad150f424ea3.tgz","fileCount":218,"integrity":"sha512-BDdKuu7atDzHRN1u1PWkCDES6ZZr7IXyyvmkXKdAbIg6D8jjTaVM2NS4YKqTkcvYTSdzu/2Fawss2EX/QZhLBA==","signatures":[{"sig":"MEQCIHMeFuIUCSdzObCTz+hWxYO4FasXrCwa9hdUb97EAT1UAiAjhgdiFXBDtKekObNY0SZif4f6+h1f9KcuVH0X57esWA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":795943,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh0hswCRA9TVsSAnZWagAAXLoP/1kRE5g17PIScswmoClH\nRrSu9/HIf8DVOUVTd97AChfLhIih1oYhObnhyCeO9aGT3b3gVV56rb2zUJfX\n2ThmIF5Adgrq11vGuod4VKjRfeRZpLv6/JyJlsG1gr8LHxs67S8BY/F0cddE\nSIsOyV6YuyLxvHye60zg6hHDnO2DdCfup0VZwl3VKWyKMJ3fIyrA8qFIy3PD\ncdUfN+E91VCQ6QmqwVHCUHunuetWp/VXRMufdZvuCkWsnWD07GD+e6WcDNdq\n2jBv8v3XZN14wzcA3z8E0rjT5rP0uC0yntVeN+ij+xpyRpiA7WUm/XTLVOxJ\ncOHj8/s2LmwGUBOb3wsVjBc0lJuT/b4PPL0VpeK3EEaRlWPjULqJSjXsL2W3\njoqi2kCGw4j7eBhWvMR4RNm2nXb6E3sc/ulqAtn22mtxsNxD8am/fwAkkGM6\n07lIXEE7d5VcL30h60kD8WvwfZWAaDI6SFlTR+shvA5PgLYy/ndCmT56BSMq\ns9HEAsJrGt5MB4geQET4psWSy7NNzD1a4PEkmmwwkxScbjTmDEEgYVDq0lP3\n/UQ96Iy7NMZOcTGJO8Rb/fNVYdc761T0d/zKh4Nti0fjuxUvt0Q2RbnJuHUJ\nC5r4E3XYIeeXruTTMwIsC23jopYfpqA5EoqQz+dCG1Qg5SmUwjBUa0irfz9y\nIG10\r\n=i4/t\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d6af82132b84c4294c99a81b2c47ad150f424ea3","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-02T21:33:53.306Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.4","@balena/jellyfish-logger":"^4.0.9","@balena/jellyfish-metrics":"^1.0.446","@balena/jellyfish-environment":"^6.0.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/uuid":"^8.3.3","lint-staged":"^12.1.5","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_11.1.3-renovate-internal-patch-d6af82132b84c4294c99a81b2c47ad150f424ea3_1641159472281_0.8003877177280112","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"11.1.3":{"name":"@balena/jellyfish-core","version":"11.1.3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@11.1.3","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6909fda780d49fad54ab6092389d7b59976ab659","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-11.1.3.tgz","fileCount":218,"integrity":"sha512-ksUG2xoQJZwVFiguRI8exI+ekWOcK7pjbI1qu9X8LVhQxvbXF2asxKS32rqt442mU2iioLfbKVSpPeWBmE4gAA==","signatures":[{"sig":"MEUCIQD1p4rjtjSMETXIyZdhXFKhrNWiYbZ43fC4YpIq1RAD7wIgBP/dR9IZiIDn2HjHcEHR9sXsSUfuzgb5sl+q+aRsqLc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":795878,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh0h4+CRA9TVsSAnZWagAA5OMQAJ/Z8894mmVy+E1MidSl\nL7DScN4FAVq/9w1CnG3eOnuQZ+MSGU8U7R0I5GnxVF2ogamVAohP2rY2YM1d\nJkpOqlNPvovq549bOX1J8KciIQjwHmO62oFKEbFGjns2KCrL1E0RD0mfYJeL\n7LmuDftnibv6a4eVEOE8ig96KS+qbhGkPJLwmi4PpFVhZaVu/4KcuPgl4e0C\nOZubvw8F6l+U1J2rHNXNwXWIJENNuAZPLiG+tPtZv3k5Bci9W4yoctR3hbgJ\nldYhMahmioH83tXzYLL5/ZnDABmYcmSmbSwYTZ8DaNbtNSwHKEG/Y/5alUG3\n1+g7/3zFe4AjzPK0wMFU7nDz3GCel/lSmoZ/5SBoVSX4xe1WnPtLu1+KOpFw\nBseKljXt/BYnaIou28KU8uW5H9TBw7q7ANHwC6gTAPgfz7w+XehEEkLTCuz4\ne4Qbmmavk69sLuncKzX9KYqaIEhkaP2+pmh6vempgmhMplJpSpMLk32/0DrC\ncB1m8rmjTVQi8E/NpKaoBjmB6YTSbmc8HMgs5tqVQ9ImZxTDf9Gga7gZWra3\n1U+WkPE8gyYVL7KyXyXoFWpoFoBnfYgN3XRlJuL3IrefRpk4wu8CLVyCNohz\nEDQSMbQquSRQXr2GWSZ0lL3wBwvIUmrmCAPJW+V7lb+LBJaRsouttEiIkO3h\nG397\r\n=p8ds\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"569e96861c952853da9dc777a676bb75aa4f86fd","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-02T21:48:36.101Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.4","@balena/jellyfish-logger":"^4.0.9","@balena/jellyfish-metrics":"^1.0.446","@balena/jellyfish-environment":"^6.0.6"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/uuid":"^8.3.3","lint-staged":"^12.1.5","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_11.1.3_1641160254696_0.768822174351333","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"11.1.4-renovate-internal-patch-57fd3b1bac83c866e7922e8bf25f223210f42010":{"name":"@balena/jellyfish-core","version":"11.1.4-renovate-internal-patch-57fd3b1bac83c866e7922e8bf25f223210f42010","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@11.1.4-renovate-internal-patch-57fd3b1bac83c866e7922e8bf25f223210f42010","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5bdfcdb7277f6917457eefd0e16fbe6bd645ca4c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-11.1.4-renovate-internal-patch-57fd3b1bac83c866e7922e8bf25f223210f42010.tgz","fileCount":218,"integrity":"sha512-1ARJHSK4hX0OspOstn6J/sPHt+if0xsOyogZINSVrgIRKSkOqVPx4fyemstWi2MRXaR02iS1tAusndvEoT6b6Q==","signatures":[{"sig":"MEUCIQDPdZzzSsLecsVSVT1AYL8VoVLRNJai4S3Z/rK9a7YIcQIgDuLLbbhFDRs4AdNmZCkPOGqJdq8m+DSteUGDLBuQQM8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":796015,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh0jLpCRA9TVsSAnZWagAAhPcP/R2c6GJfO3ntYzZ1mk2l\nOHbjfPhITB9jA5J0bFYv1wBt6y3edp386vrxdW/DIrL3acDw/Wr3mIr+MoWF\nsaDRwjmQHEZ5lVdz2ShBkLxaVHjWwMX976qHEs2pdvDMwO5TBbQnnsar1cX7\nB4ly5HSRzpThNGRYml5BWyVDlG1TZzUSG0hX2pxwZnOCPyqbBZ1gByHd8oGi\nhN9Bbsepy54p7DpxHuAN9dAcwVvshzihmCXspCvFTFp/VPrjrbSP5nlBWQBN\nBMgfdA5WAKcRMftiRTXY4Iri3JGJKyaP1xBsk2/RkaMcZCmDYv9auCbQJqGr\nBbja/IXEP1TMDHP88Lctq55Jkx5PyJcI2419/FbaJsU/OJKM3gVAr5TBiA3f\nMk7m692i1smvJKYIUVlUfZ4Q4D/COJwXsTT1VR3HskTQ7Yo/TP9vt2yuN8k4\nSpqQ1M++t3f8i+L7OZG1cP0JG5quaF2oX1aqp4n0Dy33kqIn3w/wQWBcL/0x\npe0ANPDkxZJC8cplfj+lJBrdd7PODSYe5r/l5Coo6cGGRgC4l14NbLQGYJwG\ntbJG+yAI5PN3QMKaIY8adB3F9lsi/IVhaWowiR+MOmK8GQ0jl67h/Xr9I6hg\nb4JTHAEgbhFpzaBKW0dCQsfCZdHmf1ypLPZhW3x8Ocl67A5rLCqkGN5RNuya\nnwLG\r\n=lF/d\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"57fd3b1bac83c866e7922e8bf25f223210f42010","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-02T23:14:16.664Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.5","@balena/jellyfish-logger":"^4.0.10","@balena/jellyfish-metrics":"^1.0.446","@balena/jellyfish-environment":"^6.0.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/uuid":"^8.3.3","lint-staged":"^12.1.5","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_11.1.4-renovate-internal-patch-57fd3b1bac83c866e7922e8bf25f223210f42010_1641165545611_0.6848666273262454","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"11.1.4":{"name":"@balena/jellyfish-core","version":"11.1.4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@11.1.4","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"153a5222966eb6f4b58bd1777fd755d52f91107e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-11.1.4.tgz","fileCount":218,"integrity":"sha512-BV8mnkVNDh/FJn6r9jFr0/QASgqF4nMl4jtDGgCLTPYHTGs3USbq9A6A60ZSwXEApcCmc3UBPEgwzhKtHSNnRQ==","signatures":[{"sig":"MEUCIQCnOFv0oM9FuHtU3NFH5nPQqKUS2E//xyB5H+kLre9UEAIgbWTOoX5XkLNhj8ryGZ5zbKWhmb3r7L4lzsxg1OiuOxg=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":795950,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh0oNzCRA9TVsSAnZWagAAuD4QAKRthXTIdo9V6DtqaKdI\ns63wcxfE0/pofiqC6tappY7J5/HhT2i4uqhQbHlsM2b37kh3RyPpiWCFz6Kg\ng0h+pN7uPaq+5nqIhhGsSrFA8CJ3yaHZ5QIi/RIMP3jrO/q++8CmSTGafeo3\nQsqi/+KITcCQllvxnYkCnvCGax+3cfGl5kM+quUusVQ6V2UwRzDdvJRewAIe\n5ejgidFPAKz+LqE37N642E8AQWmHL+jPjaH5miNX1fJ7dl8jVEZe8GYK6GjF\nG7lATd4Vngkn3q1RuauU6QO3IRM19eEESM5D+1QWZNKYTXpvgur8Hj0rljFD\nlTGjdO7J458d78ZADfTcQGg5c9nlK8M4W+FO3fOv6yko9LTivJ1SXumrR5gV\n8xI+Xn0Rc29KDMUSNITr+IkNARr5yRrNwME+0vClHsF0Qyjehp3Euvn5sdDc\nXlYvPJSpMeTmWd0v+CcZr4C5DLavof8P4VZAjdxu2J6JxkODOdUN8TwEnxpN\nXjFVMpEg8junNG855XaFyQU0KDCC8h4G6wfDd8VscMyY3nYrUXv0FJ8GMr8h\nMu132kI26SFY+YJvnmd6NzrNdBS/w8zoEjBVN+TtAJBo4ErjBndp9G6w1dC1\nd7PlZDb0TO1XDYTClhq2OOk9VniUI+IRv2XrjJ2SDy8OGZ93cUTnHdaxN/p1\nVGii\r\n=yfpv\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ea80b613d30e21349edec0d2024f458aebc5374a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-03T05:00:30.533Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.5","@balena/jellyfish-logger":"^4.0.10","@balena/jellyfish-metrics":"^1.0.446","@balena/jellyfish-environment":"^6.0.6"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/uuid":"^8.3.3","lint-staged":"^12.1.5","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_11.1.4_1641186163519_0.4830463817957442","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"11.1.5-renovate-balena-jellyfish-metrics-2-x-e272cb7829cdef1337e1b0428b2117e661a7e9d7":{"name":"@balena/jellyfish-core","version":"11.1.5-renovate-balena-jellyfish-metrics-2-x-e272cb7829cdef1337e1b0428b2117e661a7e9d7","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@11.1.5-renovate-balena-jellyfish-metrics-2-x-e272cb7829cdef1337e1b0428b2117e661a7e9d7","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"bc9e6e9aefad9c13538a55967d68f326ce7cf263","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-11.1.5-renovate-balena-jellyfish-metrics-2-x-e272cb7829cdef1337e1b0428b2117e661a7e9d7.tgz","fileCount":218,"integrity":"sha512-+tOTucBcT31vC9OvVlB6Pusmhv/ppsFJMF6fFozy/dPNyJTEUD/YeTi5vW2HD+8As70lRwoNhDZfo/b2RhunKg==","signatures":[{"sig":"MEQCIGln0iNP1/SA2bunG6r9sDRykmm3Dci2NAhUz3HtjggCAiAMbyvvocJioGqDyPUTgHnxPd1mHAipoOI2HWYJ6Qn76w==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":796231,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh0odPCRA9TVsSAnZWagAADRcP/iXPUZBR+cx5+SIFNQ0y\nTyX5+d1tZSKt+JCLCT4TG4kXZvCCK0mc6pr9vQ8WUycPn+Y4j5LUaAdG68nU\nUNM0rra3mfwmVLxoAwsAA9GJUeur1QuURgWK2yjDfyFRH9untZvUT4Cqr044\nrWfPL0qtnCohFb+h9iHlB6pF3kJ9HUOiG2wYa4Qx3iY1bWHUJakONto9VrPH\nqMdUf7c1vAW6sGcTEsGzPPr7FYorovZMSH7bK0KBe2d7TOOX8WJdy9WX1BIK\n3FoHKfPyNKdl4lTVxMTL23PaLhKVas+vVfhVwJb+rPZf7LxTxiCd0KzRzMVa\nJT3bxdz+LRB48fR7Xc0oEkBCoedBHO0QZXRvj42NYrw9uEKtiXFvIRyfZ+Ay\n9qyecKFBsqM6/jNeO8W4+5oHlVXEEfLGzN5q7w2SOJOlW07iJepBN7D1SzHI\nH7aWfpFItqd2a9nh1RdeO8EcFig/TQ2N0RFKyiJXb7tPUIOwzugd3YUfQjox\nTfkDgTAV3q2CiEwsvKrLuPh0HcjOMH64tsEwYbGB867cxub7KYRgjsU9Uu1I\nlOknP0qBKQOWc5q118qI6hKJfISBnBCWR3QK6vpR+tzZE4zmTNQ7LivPpvIw\ntqYc1KzlgshUFWK9tiejvDi4r9f+ZSIq5NSIAJsE0cy+/NjpjftqWfIPv+AA\nZCCM\r\n=q3rr\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e272cb7829cdef1337e1b0428b2117e661a7e9d7","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-03T05:16:16.579Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.5","@balena/jellyfish-logger":"^4.0.10","@balena/jellyfish-metrics":"^2.0.9","@balena/jellyfish-environment":"^6.0.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/uuid":"^8.3.3","lint-staged":"^12.1.5","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_11.1.5-renovate-balena-jellyfish-metrics-2-x-e272cb7829cdef1337e1b0428b2117e661a7e9d7_1641187151470_0.7291212251412698","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"11.1.5-renovate-balena-jellyfish-metrics-2-x-54742fe1d53b8d1381df461947cb07358c02b845":{"name":"@balena/jellyfish-core","version":"11.1.5-renovate-balena-jellyfish-metrics-2-x-54742fe1d53b8d1381df461947cb07358c02b845","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@11.1.5-renovate-balena-jellyfish-metrics-2-x-54742fe1d53b8d1381df461947cb07358c02b845","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"399fe4156297266c944035858b3f3f987bb239af","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-11.1.5-renovate-balena-jellyfish-metrics-2-x-54742fe1d53b8d1381df461947cb07358c02b845.tgz","fileCount":218,"integrity":"sha512-bnnf7aQpEIIuJCWL/BSOSjO488kU56GpZnstZwHHfmtfzJiF+msHvQsO+XFFJJpXvFBrmK0v8okJPXkAjsP/ww==","signatures":[{"sig":"MEQCIBKg+nywlJcMQ3tEGyomOZRmMiPq3DuRbKErCTxNnPwAAiAdrHvmDedUI+qlLuD3kMvjlWC/wA77kuKrWp2vG4C3aQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":796285,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh0qG5CRA9TVsSAnZWagAAVdEP/2B4v4j3Bad9lLdL9BLx\nNNpVnmx5e6Gqp68PkefTwaPR7VlRquPcekFVoI/kgQF4ysYQeZwAnp1P36bi\nL51OJc8ErNVmksZgh7bmwIgsnCNuiATiWMXQgarLO2NNmVnhUa8+7wby3Oei\nNU5OVJKxptlkHkD1BPJu2G4sUv95+aIrhFtHEoFXQUykAG2XpgX7UghElLI/\nZgQr5y10gU3snsbOgFLvOr58xPzoxlwjplWPuoo8rDw8hFaAuDBY1SqiJj3s\n/s2F4SbCUs+cKPlTRXakqwTGtp5DBu276l49PfEctlknI4zwER36SU7eKzFW\nstFhTM7VDMp97ZnXu67KxqP97ElEo1iquWayp6z1EL5ArHjxJJODXBKI1JTF\n+bVjLXH32FYeFq22RU5RDWD2mZ8FT/CY2urxi/OtbOWbEnwKZOB3QuEGMyo1\nUiLn0Q2GP6hak8rv/GNM4K4MB6v/qKXT0PGteU65vq3F8wWkI35h/Gw9AllX\ns7xFfBFAqGlIUlStNcoGmaD9Y/1oSr8oF++GXtu/aTiqkPPiJDUOxF5VT43r\nLBVN+tAiq+bJ1IpYPTadToT4RRaHL3RBqkCHFBT8+monW3/re5PaaCS+2zWJ\nPvlEUL5RZt+cpuU+m4JNYpY8hdgeoiuzoF0sx8TEhOqcIkK5f/9dcEbNktJg\nWKPB\r\n=rSHb\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"54742fe1d53b8d1381df461947cb07358c02b845","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-03T07:08:25.277Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.5","@balena/jellyfish-logger":"^4.0.10","@balena/jellyfish-metrics":"^2.0.11","@balena/jellyfish-environment":"^6.0.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/uuid":"^8.3.3","lint-staged":"^12.1.5","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_11.1.5-renovate-balena-jellyfish-metrics-2-x-54742fe1d53b8d1381df461947cb07358c02b845_1641193913055_0.3074060997807999","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"11.1.5-renovate-balena-jellyfish-metrics-2-x-7c12456a4e342dfd667d26b95adc1104874721d7":{"name":"@balena/jellyfish-core","version":"11.1.5-renovate-balena-jellyfish-metrics-2-x-7c12456a4e342dfd667d26b95adc1104874721d7","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@11.1.5-renovate-balena-jellyfish-metrics-2-x-7c12456a4e342dfd667d26b95adc1104874721d7","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1bfff71792d111b5b490e5b63e6630642b14d0cd","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-11.1.5-renovate-balena-jellyfish-metrics-2-x-7c12456a4e342dfd667d26b95adc1104874721d7.tgz","fileCount":218,"integrity":"sha512-hQYX6h6fXTQZb4Msot/NVZiADcx92p/W4p7GolPJwzWl0WkDxrnOn8LL6psGXKCjC3QM3k+WK8pUBG7w/SONdQ==","signatures":[{"sig":"MEUCIQCNA9fIizo9FawNj5rzDN4jqCpPtEOp/yZONdOzFxhb2wIgT6tkYiXXL2aFvA09oz+sBSyi6zQiEzJrszRXrwLQB0E=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":796285,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh0qsQCRA9TVsSAnZWagAALFUQAKTyxIffwwvBy+PYTG3m\nCb3ydzOxFcD3+LujkPFK3c4JC31KNRww3s82qcVBuyJhhAknqZJBUoNqugYZ\nSK9oO9fKYnKSO10y4JrG1/2M7Wx7wImGpOBp+SzzjxSMFc1C9FWvPbC5LOSo\nB384ujqgsTC3t70Quwul1k5z1E9151Vsp+GDpHKirbxztE8U4SXYj0BoVReK\nuAPwhF1JokeUTkt8HeEwi8J3IrCkMBPjcS5qkdu7ONONj4PniiUo71aSQMIk\nBlUMxfGuan/N/BKsfH205fOCBdwFGZToF6Qo6j+ICv6617FTjubYpMgJVhMI\nrDyiQLt1J9jOeZS7l52Bffbqj17Ez0sOg6BgFdWD7YISQ0Wsc3+V8WONYLRK\nX8RACXFXRtWoUiGz0b8u0Frq/XSUlqpCOf74pJp0jHaYnuUIVV2ytsHM16dk\nquQJFS8YVr9ywFJ6b783FI+mqdEe+HsY8g5UpCieiPqs8zEzv35G7TTZHSKG\n6+A7SYTfkfNv9mgi8i/0IIMhOZ5ahu7Fp3iaZoR9C4bpITlRxuzRsawqzNmT\nwpFGSDwv6idgPH0eCoT5di1gDVnzVY/eXWuGzp1njKMXHZCCd6znoGNm9eMq\njagmMC0bXsUb56ijrqncCtbMers8QpUu+Cuiwyy/9fBd0bXG0Z23fLeNXr2K\nsEka\r\n=FvLB\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7c12456a4e342dfd667d26b95adc1104874721d7","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-03T07:49:02.361Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.5","@balena/jellyfish-logger":"^4.0.10","@balena/jellyfish-metrics":"^2.0.12","@balena/jellyfish-environment":"^6.0.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/uuid":"^8.3.3","lint-staged":"^12.1.5","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_11.1.5-renovate-balena-jellyfish-metrics-2-x-7c12456a4e342dfd667d26b95adc1104874721d7_1641196304027_0.3434101640187628","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"11.1.5":{"name":"@balena/jellyfish-core","version":"11.1.5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@11.1.5","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a88c178224f9eb87e99faa4f1ed71f083175a166","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-11.1.5.tgz","fileCount":218,"integrity":"sha512-dfUX2rajPW9uB0OqfUD8wOVenF+TqgIJk9KNzZ02plvUs9RgNMNjpBGHJyTnKBD5HktxQxiAoITYBMEh2n1Lqw==","signatures":[{"sig":"MEYCIQD8g3szm0Q9d0UGzaaoNgUnaF2UnE18ySs0DqTCnNmxFgIhAOt1BzyJPUTAPzE2aTxob4zQY/WhOlLGrrHcTCUZQclz","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":796206,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh0qyeCRA9TVsSAnZWagAAq9YP/ibvVueLkbuIZ837mOyt\nereyzZP/evhCvVZ/oNPjKxH2v62vOJgasyzS7M1+bI9KN1fGryeu7hPzE33p\nhNnpKIPs5xOYc4NFEHmy8WeyrgYPbYhu+sxO/jv0bPyDVYuBWz1+68cmbcfs\nVjcRbycX7VIuCrLuXYWJG/LreAHkbt4imTheX/TpWhbO/4CYawk5RvKzLafc\nsCD0gudX0ywVXVl7kxGYsFC1odOzNQpEFSo8LKiwmR5+ZxarMAqYPsPnv52n\nJc8uWBLOFv5anKLs1wA/w65XHdvaQ+OB653ov+ib2HUzyznJcRusUGxcy5A2\nCZOy3puy7ZNFfT0VDGyRqY2dPOm8uJXRFaf2vAcKWFevFExk6vzAMOK6AsXj\nNfI40DAHae8oF+5G1edRC0ZEXqoZkKNxvtxfnFhVP/a1+toDiqNYeTivciTt\n2Vx4S2jAONdHtP0H7YS5p0DKAnwxJiKuRwzWCssdSFXeqlkmU60Ze4pGANaC\nMJAuGLR7NoIBg3Z5wF8G2tGJKI66oo7P6vTaCD9eYdvrbRGwqv6SRYmHLC6w\nyU1j+xi3NHdmz+ZD0+7RTy/fNupDUkHXFPduKIP56PN4Z/b1YkLvKkg0IJnl\ntEoukgzWxrYZNmvS8qW3zPEaWOvgRfKtZOMzs7SRok4h+NerRTnsmZ5Ut6Nw\n2wHj\r\n=E3Nh\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"802c455039b0bd8ce8f05ee859f87957f2c6589c","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-03T07:56:26.221Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.5","@balena/jellyfish-logger":"^4.0.10","@balena/jellyfish-metrics":"^2.0.12","@balena/jellyfish-environment":"^6.0.6"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/uuid":"^8.3.3","lint-staged":"^12.1.5","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_11.1.5_1641196702148_0.26009254066797327","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"11.1.6-renovate-internal-patch-4853aba76d00e653e5c72738d1bed2d25a0e4238":{"name":"@balena/jellyfish-core","version":"11.1.6-renovate-internal-patch-4853aba76d00e653e5c72738d1bed2d25a0e4238","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@11.1.6-renovate-internal-patch-4853aba76d00e653e5c72738d1bed2d25a0e4238","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a5d371aca95f6e25b550bb1e7c6877d54d6c25b5","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-11.1.6-renovate-internal-patch-4853aba76d00e653e5c72738d1bed2d25a0e4238.tgz","fileCount":218,"integrity":"sha512-apDtdGHIebDPHBC4zipkRcCLphmiYEj5+p0eGmLZcVB4cRdFuo6Zd5m1ZaDGM+SAnsLA4TS6GHUDEWuHwd7roQ==","signatures":[{"sig":"MEQCIEekYldkngMwjzVS33MSrTOTzjyhEUkTeDvuKii3TUkaAiB19MgFuPr89DMamCQifmSZXzT//8nZbBLBM/ZJEs/U1g==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":796342,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh0xDRCRA9TVsSAnZWagAASz8P/3JnWfqcJpN25F5zN8fn\nb2Zm3JOuBJR4ceWzK8aO8zXZA68pCbkQ2HiMh36lk2HnS2A4R+j8TDBSJDzM\nuaYEvf7C5yQJXvINvUrkUOwP0OYyz2k1l/aH5SZCX7RRa6JFqcRQYmPXOmaR\nKFOj2j2TWVJPHEhvxUQYSaauXgLxB894tj+D38Ts6BQnyUAnOS5rgdApGjb6\nFz8nsnIex8aTfZCEfFD3MxNU5iTzVAiTktipwrN5hh+r75dikjDsC0zJnmUH\ngmb1sMiOHQafUkFpaBa9gfGpNo1eVLWbf37ciJgZ98fSJPlEHwRH6vYTVcow\n+J1iwROrL6E3fWWhSWzDVEE5ZmQaDqEqJEIyNaIRTy2Qp9gUQDB8bwQLzUXt\nutzxQ7Utz5VkfxSShW51s/beOazsV/cCTB81ROQMb2Fq1v+/CZnes4xC5Ydz\nWDA8lVEC2Ymo+lE/AOU/CYNLwGpNn6FzjOdfMI3ZpLGj5CDi8BVuUGN2cFvC\nnJd4xhCxoG/lkXGXuVP1XYNdtuoMKdiTUoHPaq5U7FVoq2hIr54sHZRaJQF/\n6D7jPudl4AMGnINU4LxrGX8PiIn4oiTmAimcdWmSSSYFlRRb05XMb+53goWt\nfpCmOcVBWX0tI2QH3qBJu/IWzQ6cJEVgvIPmvVVqX7IcW1Oe+7LDLIxVUSnR\nmuiL\r\n=oo3g\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4853aba76d00e653e5c72738d1bed2d25a0e4238","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-03T15:02:43.795Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.5","@balena/jellyfish-logger":"^4.0.12","@balena/jellyfish-metrics":"^2.0.14","@balena/jellyfish-environment":"^6.0.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/uuid":"^8.3.3","lint-staged":"^12.1.5","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_11.1.6-renovate-internal-patch-4853aba76d00e653e5c72738d1bed2d25a0e4238_1641222352953_0.25307542305569575","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"11.1.6":{"name":"@balena/jellyfish-core","version":"11.1.6","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@11.1.6","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"beba6afc70ae02592a3f291a0a88592b764dd1b7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-11.1.6.tgz","fileCount":218,"integrity":"sha512-CYbHjN/GCQZ57FrC/qQmxPQRIJiSqAsIwZqv0TsiGipoZCnDvza2NP7VcshykRqu69YPjbE9TGzqNwoaMhoL2w==","signatures":[{"sig":"MEYCIQCIaHklJq4Xeqlw04WVHjcetRq2UEcbyMmCgxmGkmC1RAIhAMV4+O9tnQ7mZoUSAevGIje/c09GCnvsKcCzCO6GGbHy","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":796277,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh0xLXCRA9TVsSAnZWagAAB0sP/2KjvVpdustSSkIz4jBF\nSyCkis3FUbBCYvpzOJpnDQuOC/h2Gs+V1d32moBwpbes+YzUwbVh+oxr/CoZ\nSJMeU6aYv3KyiHl7hONzUj4KcbdXTfxAcFAVPlKtU9fy4N/yW0XY5Q6iIwe3\n2ktEfMyGNk2wFWH6M2gwiHXlsk94gzEM/Kuj9N0MC4rzJWxwOfpgu7vyvswk\nbXItu92fAtaE/Scd5QXwOKFDT4Zt/z2TFn/yNF+N+eU16dmUCpVFzGZbPldr\nffi2dYdck83Dre6Se6QJy8zUuPKP9gaksec9w/1MyND0aGB/nfr+OMtgUANy\ndtzugd4LHaHxY2H3RIg2zwRnoj1kYve+hqGMlK0cciQI4UH9m63LxFhY0PjS\nO6wSSuxgPqLVK+uAf2aSfEOCIYc+PfZeS9NhCDDpsHbu4LmORnkQRSn9RKYK\nXflZUkLG2ZW/hBETkSrR9Oq5IE96kjkmedxl9x4PFkxCVX8SuNtfER1P17zd\nw47S3q33KGgJ0Uwg3woTPlWrgaNDD0IrFxuso4YZmbtz80HBw5fh5E+cxV+3\nQKB5Zic5zG/OXvnQOa2dVcagEiPbBZBADqJ4353xKoy8Jqel6ejwMTtum5/+\n2B+3pC3JMh2aYbs3IS+etrGxSzfrsI1oErP22j201ubmhShMSy61WacXiviw\n24tA\r\n=dQBB\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d671788f268dd9bdf4412a6729770bd630acfe30","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-03T15:11:25.417Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.5","@balena/jellyfish-logger":"^4.0.12","@balena/jellyfish-metrics":"^2.0.14","@balena/jellyfish-environment":"^6.0.7"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/uuid":"^8.3.3","lint-staged":"^12.1.5","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_11.1.6_1641222871592_0.33855340744835494","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"12.0.0-rationalize-db-connections-037493bc656c3290d0e13bfe0538ca1a557b3f60":{"name":"@balena/jellyfish-core","version":"12.0.0-rationalize-db-connections-037493bc656c3290d0e13bfe0538ca1a557b3f60","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@12.0.0-rationalize-db-connections-037493bc656c3290d0e13bfe0538ca1a557b3f60","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"93bc108eb34cb2e5dc3892c36e64ed5485db62e8","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-12.0.0-rationalize-db-connections-037493bc656c3290d0e13bfe0538ca1a557b3f60.tgz","fileCount":218,"integrity":"sha512-bPv5ayZC+cutgXxl8CfUD4UlrQsy5sUyvRIZirrmnx2+LVvgEw2MMJ65w/xEk6yQc3zGdPFoCMFdkuhGe8m0cw==","signatures":[{"sig":"MEQCICZhpuczYHkTI1T8FIR5kiLyOxRXr0NpvrKR6QL4oiu/AiAmswpbHdYbYDXKsazQIKlnoFnptE1avbhJUqgBY0yZSg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":799971,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh01g0CRA9TVsSAnZWagAARlEP/iqj0sNewmKVeU+vyoCe\nYsVwTZBenmePerd7VovL7Tw4eq6gnt0bfpVDlUIm2kWwKuUre2A925xUCP9g\n8jq1MJuVUJYnFtyL/nrnK6AZcYxQoD9IGMmsNHXYuMoPfEnh8rur9eya7rkV\nTL2+uDu86qImYeli/MMAw5O8o86MD+kUEiO6KTacAB9pHcfy6xE+eYQdtWeH\njABZUi6BF/e2nHyCUR8HDMilWEXxdjHrqoUg3svJVasdPMQg58HQFHI8b5Gx\nAWOqrhvsE37ceci1+Zz0XcyK4wyMge4Z47WrcrAPNi3WsArXO1tHWizOFVv3\npV3Z7TW7lL//n5EKqT0s+cbWhil2Fd0fSNRmcGsByx3zCt7ujQctT6EgdZ1z\ncrLIkUqDNqdKM3lXmAwHk3MwCgGRLCAwRjZbop5vkWtO3amdT651omfsOw/+\nvb4Dd6ubbrq5Th58nwNF9iYNDpVtF+7YEghRWkuU0vDPi4WoWF51ZDtNB2Y4\nbj2pcrTRjtpUcmPWhn2jWZTvfItcjwLUBK+Znvgf6MH+Qx1dgbZUQRETmbcf\nUWlEvKHBzVYVvFaDSRl89yi4mBrYlCTLEYSDKsmIaBMv38MxCI/pFEG9AJ+b\ngId12eA9iDZCyEOXBgOCSw/AjpgcLtglhFY2MMXTwjepURrszm+qXHqx28+y\njnkV\r\n=qdiU\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"037493bc656c3290d0e13bfe0538ca1a557b3f60","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-03T20:07:05.297Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.5","@balena/jellyfish-logger":"^4.0.12","@balena/jellyfish-metrics":"^2.0.14","@balena/jellyfish-environment":"^6.0.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.3","lint-staged":"^12.1.5","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_12.0.0-rationalize-db-connections-037493bc656c3290d0e13bfe0538ca1a557b3f60_1641240628593_0.5733615461929447","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"12.0.0-rationalize-db-connections-0d7acfa37f83a41993efe6e596bcb922b209b20d":{"name":"@balena/jellyfish-core","version":"12.0.0-rationalize-db-connections-0d7acfa37f83a41993efe6e596bcb922b209b20d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@12.0.0-rationalize-db-connections-0d7acfa37f83a41993efe6e596bcb922b209b20d","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4ed137ec9f5fa2727f4d57d9a0c49551321ef35a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-12.0.0-rationalize-db-connections-0d7acfa37f83a41993efe6e596bcb922b209b20d.tgz","fileCount":215,"integrity":"sha512-ZD6at4MvC/belV47VdxVftoE8vK5yMjpDVyTwb2rqCAKMVovOvwQAUdM6NZpyfYm1KPJkNnrf9Xd4eJG+vcBFQ==","signatures":[{"sig":"MEQCH2Gg6KhccphA2u/JRbG5A6t71PRR8ozjASpGMp8Sey8CIQCu+t2u3yqhn3mXTHb3aQqKNKGjE6y4LKvo10KAR9RDIw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":802508,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh1JVOCRA9TVsSAnZWagAA2iEP/jOBnKRz4cVpBFPG4dO1\nDNZRscULLbcJN+ABTS6WD12nrynlDjK8sqCSR+kvNmY9uBDxCyckPuoPbjT9\nk/im3HQbexAzie25MQN8MwUS6TP/W3q6YxeoJOmVoO79M/MZ58aUWJHmrsHx\n/eZV4XPG/JD5A/1oxn8vMYX+F3PKqOCulfywbjQXFTnrKbwVoqpbekA5fTOC\n8mQyFZA7TlOOPZeN8LUZOB4kTfypz0bz4ZzhHeyp6WEHyUoTzwthTmPHiTGM\nM3UsObHbE8XL8OTsipCMrtFFquOfLxUO7Qa44/R7BiA0IKed32jZqbgLCJYH\nnnb3zGt4HHLK3GmmYmUitbc0aIdBbstUQ8g8KlzDamWRs3cEcyihrEQJWrVf\ndNWEuZyLkLjRYFAki33Wxih3o6rfqKuQ5jNIfbNXSUvhe4yT83bTMEmZirnU\n/R/yyRgNTcF4sSprUm4DImeek9q7nkXeD4FQ4P/2SNFtY0dGOLKpZhY9dhk5\nZA//sfWYNSRr1mXfg+iOeSqDZlf3gEE5ApUZrMoQIiWEUxptX7b2bz1TcD7L\ngqtHMsBICtx56K2QYZ6wZwV7WuwUYb6kXh5tXIDx70m5gSypnL3OUibUIPwp\n+bAzxqwiKsJS4zbNFhnEVVMAAMvCn2JRA8QQ+iRWkQkzPPpkBKC40GeOvTfm\nUXwl\r\n=TSMD\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0d7acfa37f83a41993efe6e596bcb922b209b20d","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-04T18:40:05.376Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.5","@balena/jellyfish-logger":"^4.0.12","@balena/jellyfish-metrics":"^2.0.14","@balena/jellyfish-environment":"^6.0.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","@types/pg":"^8.6.3","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.3","lint-staged":"^12.1.5","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_12.0.0-rationalize-db-connections-0d7acfa37f83a41993efe6e596bcb922b209b20d_1641321806094_0.8704362476910232","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"12.0.0-rationalize-db-connections-7dc741fb3eb79661042631baf027a63c0149fc60":{"name":"@balena/jellyfish-core","version":"12.0.0-rationalize-db-connections-7dc741fb3eb79661042631baf027a63c0149fc60","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@12.0.0-rationalize-db-connections-7dc741fb3eb79661042631baf027a63c0149fc60","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"245e382f229046a7fc55d5e33ac4456fad6d84cc","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-12.0.0-rationalize-db-connections-7dc741fb3eb79661042631baf027a63c0149fc60.tgz","fileCount":215,"integrity":"sha512-5fUtpy+F40laE/USO1ojIgaqWmJQNKbqKoj0Op2JFyxF91z9j/IrutwZhBfQLmkQ/8IgRBAXxdAOy0FfQOHqaA==","signatures":[{"sig":"MEQCIBMOq7R1rGP6/SKmHFElzXjSB6pvRyXpUliP8GrMcPA7AiA9DNpxyoOcaIYeqv8nw2y2nLWCfetyV8z0Br+pxD9xcw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":802607,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh1Jq3CRA9TVsSAnZWagAAVnoP/A3LGlI8nrMJ0HkRRb/s\nXGnE0v6VSyJuy8DPjLLnElxhTvYLcopnIccso0IyhWBjDfPO5lRx4YVySjpN\nda/eGoYMgfEdjA4JvZx5RSt6407PjRAU0rfoz+G7gl+WvWc8KEsXB2G3ecz4\nfkndLfTtQZeK/AtKx8qmkTfwV+XWjqm20rJpb7jS+z7sIolQ+DGbsRrzRsWl\nUdMBsQM4bmemTZaNzlncp9YrSowOA4hlzZhIuyjdEYkzPfFbmnxlsF/jWCBl\nKMy+p07pKTbVGxrZksIwL1Gtv9UCN/49LOMh39FzTYYyPnkC5KvgyzewI0zw\nGyKP0CqnmlbShPMvlQeCN7VHWIZFbVZxhp2g3vwy4DpWb9XH+y1K2+kl03dy\na7PgErlubORXyBgUl4je/4YE/Xp/OEMLsBH9ISgECCdkKmhnH8qDGMGZGbq5\nFN8FXxSJNvRSidrUSzD/wbPka5DX+yGcAKZbu8n8ixRe1IfwVidrtgD9MZe3\nrL2w6AP/UzOgwe5rJJzRmf5iVUsDkylS0rbDrhUJ+uxRvJ0sGJW44CwrDRcR\nRP1mNw1QsHPrWP3vUzQc9vySWP3apNn5sBi8nHEj0Xr4cBnaZGLZ4D7avRNR\nGfM9ZIqOmxXd6NfCI7qtJbo0XhdDaZ0UXj6q7vDZSCmNc0wY9K7isjRE7ZHi\nwDWl\r\n=LHNT\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7dc741fb3eb79661042631baf027a63c0149fc60","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-04T19:03:00.027Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.5","@balena/jellyfish-logger":"^4.0.12","@balena/jellyfish-metrics":"^2.0.14","@balena/jellyfish-environment":"^6.0.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","@types/pg":"^8.6.3","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.3","lint-staged":"^12.1.5","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_12.0.0-rationalize-db-connections-7dc741fb3eb79661042631baf027a63c0149fc60_1641323191710_0.19876892687328307","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"12.0.0-rationalize-db-connections-f2b5a3a0d140e9e126781820b1baaa535a30fc00":{"name":"@balena/jellyfish-core","version":"12.0.0-rationalize-db-connections-f2b5a3a0d140e9e126781820b1baaa535a30fc00","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@12.0.0-rationalize-db-connections-f2b5a3a0d140e9e126781820b1baaa535a30fc00","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4acfdec40f2bca9d8aef198bd1a6865c50c56a4b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-12.0.0-rationalize-db-connections-f2b5a3a0d140e9e126781820b1baaa535a30fc00.tgz","fileCount":215,"integrity":"sha512-3wmZ56f8Orws036VpU2nIpK0oqeO5EMlP3m+gc+WjuM3bBvO3ejzg3eKwHI5+zehPiNab964b5qDGHhowCxjJw==","signatures":[{"sig":"MEQCIHDkBVq3WNykDN5XAg/dYm1sjlF89QMZVdS4baEZeaGSAiBsf+wEvg86pnhnG0ekg8PK2ki7ZrK0UUryyYr2iHi/kg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":806107,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh1aoVCRA9TVsSAnZWagAAmhEP/jBfYeJLz9bpXAQujXgv\nLf1UXn51GPNCLMJ94svW6TiOS2vE/FgRx5ylrk6lpfITwgE7zsXK6HANpfTA\nKeLEnyDVjmFlhi5ju7E6MKAJ9B5TrpS8K3gwwsoNKVkVNxIKyY+phPc88P90\nbIxCkt7XAJdqaWjuUhAj5M7Gf27PCsu2jghdRlZbEFPSSlP0qY5qBPhqL9nH\ntsslfQjOa6xS/jZTcFyKftyOA46DWzOPIHRxiBz2oWLSmcEdkD/gfw84wPos\n7KQOs6TloUi20ktn2v2F1AaxFIaIToEoGXGZBe2S86uNsmskGs/9/Cm+2slC\nNEYLm4ODTPKMpLts1+eQdq8ey9C1z3+wzBMfD9p+apsUVlA6Qe987FPARx24\nCCtqdWRJOXolUkEtyWRWKH04rHU7Qm1UG+4unEoR30PwRP6ER5/MvG01Ga+0\nA1cEt9RnTV4rCQx5/yFG234m4Tpo2kHPKrflHt52nLy9a3Ck2v5+30DjflF9\nq9nihuRrlFJfvxZNqsWtuWsn8IOJ4i4rcWto5BudfnjlkuPii4Q1WooqyaL0\neCl359oI4roj4l1wg5GAScfeFO+6OeoNFz+QmAdyiLmimjauFeu4sy3xDbw0\n/ZiKMEg957f7cv/wNfF84BC3z9XYgwRAdR/J13OkR/Id/OCwb0nDusIfGY3k\nrJpS\r\n=LzTw\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f2b5a3a0d140e9e126781820b1baaa535a30fc00","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-05T14:21:14.650Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.5","@balena/jellyfish-logger":"^4.0.12","@balena/jellyfish-metrics":"^2.0.14","@balena/jellyfish-environment":"^6.0.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","@types/pg":"^8.6.3","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.3","lint-staged":"^12.1.5","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_12.0.0-rationalize-db-connections-f2b5a3a0d140e9e126781820b1baaa535a30fc00_1641392661386_0.10683405136446922","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"12.0.0-joshbwlng-remove-actions-e2fc8524d0ce37f158a870c1f51474c3dc5e28cd":{"name":"@balena/jellyfish-core","version":"12.0.0-joshbwlng-remove-actions-e2fc8524d0ce37f158a870c1f51474c3dc5e28cd","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@12.0.0-joshbwlng-remove-actions-e2fc8524d0ce37f158a870c1f51474c3dc5e28cd","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"46284c072d0de75aefa96621d4cad42966170751","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-12.0.0-joshbwlng-remove-actions-e2fc8524d0ce37f158a870c1f51474c3dc5e28cd.tgz","fileCount":209,"integrity":"sha512-gN4BeqjN86rNrtkAPUsqhrtNmkYcOklrB5uOUkMss0z+GHZ1glHHF0KUwhCqn5yZ63q6AHeqGUYh0vP6Fa3hAQ==","signatures":[{"sig":"MEQCIBktHIrUhoVfY6L+XUIJzuIH0/nD8vgWDXDJBqsBKmX2AiAwtecIzI/lsi9MNLpXQBwI/Er/Xc2bLux7yYkmy+EYJw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":781155,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh1j7bCRA9TVsSAnZWagAAo4AP/2VBNHfmt2thTyOfGoFa\nfn3volmo75j4crIGxeWsf1nGeziydFCssuWClbgUkZvFNEMsqia9Vuz/ZOcf\nWYty7OFwH7YkEZCLLkOXYbpbGDUlWSMlsRAvHvHh/oWOOVyN2ScgN27EX2G0\nF8W2OlxkgS9Q1f0CoqZvwMdEdt5FMAK7/rklMwTBmhdqzFDd8Ws5LbPLcEWh\ncxlvG9OpOULxYObGmyEbm+8dwnXqXMCnuWANPdkkTmRHoW2p6vxMlALSYKjh\ncEihNX6q68Bc6u/pKXGuahNe+dzxpwBhN8sgqw2rqZsY7aJbR4czpe+oe9h8\n+REVFIoYwi6iUPR661u81C2ei6zcqzjMpFqoRtigaHN8BhNNTeDQw2wSpC3y\nCEZ1Za1+dNeXrJjEW3iIDDTpWt2tyQQ7y2uastTz+VWszblXLyr+2liDAwVd\nqovTAmzaatNd8fi9K52QRwpKE/G+KSiO6rr3oW+CwxTjzckZdpafemO7N+9k\nz3y8BZ9XNamJcopr7fILAaxRKAINPQkLUJfuWq27usV+yDWPR1qWpaj5fW3B\nZ+UgWUyemfWXo/+QqOeTnrIYRpTgoJOoO2qDeUnmcr3C8ETH3bQ8jCCSpgVa\ncMvT4v1XocFTLwEB9rXN2Hk47lHcpmGPuPpqsaWYp2wnWJsBlsPxz9+FSmpD\nI8hw\r\n=ayzL\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e2fc8524d0ce37f158a870c1f51474c3dc5e28cd","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-06T00:55:48.685Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.5","@balena/jellyfish-logger":"^4.0.12","@balena/jellyfish-metrics":"^2.0.14","@balena/jellyfish-environment":"^6.0.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/uuid":"^8.3.3","lint-staged":"^12.1.5","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_12.0.0-joshbwlng-remove-actions-e2fc8524d0ce37f158a870c1f51474c3dc5e28cd_1641430747149_0.8493541471241985","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"11.1.7-joshbwlng-fix-reset-193ceaeae4516a58c5054bcdf7bc1df0db4c28d3":{"name":"@balena/jellyfish-core","version":"11.1.7-joshbwlng-fix-reset-193ceaeae4516a58c5054bcdf7bc1df0db4c28d3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@11.1.7-joshbwlng-fix-reset-193ceaeae4516a58c5054bcdf7bc1df0db4c28d3","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"87f842853986dfaf6b5d19cf8f4f365800600f2e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-11.1.7-joshbwlng-fix-reset-193ceaeae4516a58c5054bcdf7bc1df0db4c28d3.tgz","fileCount":218,"integrity":"sha512-Jr1Ss1HkuiAOjh+dXtRSTEoxNAYHR34fia4vX1tftJ6/VWBEDVU9mgZ+HanYmW4BIGbjEtY1TB+cAcZzThxtwQ==","signatures":[{"sig":"MEUCICgUTQ3SG03Mk2BoeamEsyVcCbMGM5xeMXLGvODFZdKvAiEA1matvvwIs/D0wl1HwR0l6ygz4byVJEqwfccVgPRryos=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":796438,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh1l7mCRA9TVsSAnZWagAAAfcP/RWanRx2ySAHn3yPsVeX\nmFoLAzuNRw4+nXxP6xJjewrLII/kK49zg96iPfKLg/szZz9OsHZrO8DXmzah\n0GQYiwsBGVqCwpnOKVK1UQyvs8/rQEmoDTwafFxbOQDg/wIRbKMKLiaiz+uo\n9fj8IzrhhFmXt37v9dTlrgkEjeFv2hLOdu6A7g67UMwkXgQGAFm+Jek4adGc\nn1mGARIrQjNwP9Y9Fsqh1c3i0q283KBYpc5+p+ggbswkKZgOV4F7BFji0w4K\nUjq1CY7nIHOTa/8JPyMNyH7EJUa4dzp5FKRRQpWJCXq3GgAWqhrlNvzpMKS7\nwFV6gVib0dgXNLNl2RxvEiiT5irv89EU66sWUAUANlerFYqT5JWeYKXVHVGO\np1kN2GJAGe1Ma7OxfHP8mblzjLZzijDeGpvhcotuVqxfbDDWES0i3GYU0Szj\ntvUsgX46pAHVefTKn8cLC9k6Byp1PaHQqUEdH5eNWcJLUx1wu7+Vxj0m4Bia\nhCioIuo7vHxf/10b6h0NbM6CayzEF7KpPaeXrXZCX358Wrc+X2TPHltbz83r\nJ2Kic8wuV/rLBsUwRf0P62mpzR5BupnTkK3siuDLTcXNIALQK7Ekx8gni7is\nIravSKK4pwMyVJ0PHANLAUUsa5YMmCKHonx3gq3Fi/Bo/UJNtmrqqPLhuDnq\nnywt\r\n=ovkW\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"193ceaeae4516a58c5054bcdf7bc1df0db4c28d3","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-06T03:12:32.260Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.5","@balena/jellyfish-logger":"^4.0.12","@balena/jellyfish-metrics":"^2.0.14","@balena/jellyfish-environment":"^6.0.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/uuid":"^8.3.3","lint-staged":"^12.1.5","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_11.1.7-joshbwlng-fix-reset-193ceaeae4516a58c5054bcdf7bc1df0db4c28d3_1641438950255_0.5039601116070498","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"11.1.7-joshbwlng-fix-reset-4565e933cfbd13e8de355a510393cdf018ef0fd1":{"name":"@balena/jellyfish-core","version":"11.1.7-joshbwlng-fix-reset-4565e933cfbd13e8de355a510393cdf018ef0fd1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@11.1.7-joshbwlng-fix-reset-4565e933cfbd13e8de355a510393cdf018ef0fd1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"63936be75606b345ba27a4eac470bbb152f3000b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-11.1.7-joshbwlng-fix-reset-4565e933cfbd13e8de355a510393cdf018ef0fd1.tgz","fileCount":218,"integrity":"sha512-qRP6jaFQIwaXoE0jjOnez+aPPL67HQhSN8YPDzFdmYl+0VCWitMCiTUol/h7ucPPzciT08kvIp4BKfmZPa44Ag==","signatures":[{"sig":"MEYCIQD4Fn63WGJJD5yqw0jUFebT/FYh5kbMx3NBcDsk1PrGEQIhAK77G+jvfvkmmt7e9xo3PuiN/rSPunh1CaWEz6v5TP/h","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":796390,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh1nMWCRA9TVsSAnZWagAASzQP/ifSG0EjNPkk2uA5UPMu\nUIuA2UWE/GAb2azCClOJ/bS0zjw+8+67CxC9BQYH7WPTp5gxRP8GSuVNqNYK\nsiWaGGwQ0F76IVfX5E3PUxKMWjhoc8g1l69cgpKgSpsw925xBij2Cf/OMiE2\nE7seZsSxrY6xtkIJulrP5zaRshgcC1E81QnR95W0kwGWpLPGdD/rYOe3JpU4\nWVjd9+X0HTJ8Tb0egbbGWC6t82tpt28miz4WIXAlWcWMoTbRhlyR7j3/NPia\n/BP5NAGoLBwqvUdWgkn2zlChQSBOPxijyWChqP2DtNDpQADkPVU1EjC9YB0/\nIsZTn3Egq2Aw6T5GNq3T/7c3LcY24TnvT50HsAF0R947ty1Rh5PqHCzpc9I9\nuDEC8A/EMZxR2bKzE/sQTToXbnJZCPjlAAAJtFnyKBz6wKRcaJ+Z55lDuf95\nzSEqfV7JMXrEU8laWSdbjGf/XFqywwqFj2elwxf8O8M5bL8AngL7rikE4eiO\n1hfArNdgTA+TPg9+ukaA+t+hZlEIPtHUC9uQpMjTTMJw3Fw/z31R6izij723\nkTv6s6qLrUh+ESMY9pjFeOG3vEaxT+XKZRmbSl/7acUIqFpXrnKpx6eajmtl\nai3laX/cY2TCmUPx6YSnj11zVHhieTPLXR5waY40KXl/GkMyV4vVhu71P/vx\ns76v\r\n=bp5V\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4565e933cfbd13e8de355a510393cdf018ef0fd1","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-06T04:38:54.362Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.5","@balena/jellyfish-logger":"^4.0.12","@balena/jellyfish-metrics":"^2.0.14","@balena/jellyfish-environment":"^6.0.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/uuid":"^8.3.3","lint-staged":"^12.1.5","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_11.1.7-joshbwlng-fix-reset-4565e933cfbd13e8de355a510393cdf018ef0fd1_1641444118502_0.1839699341761778","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"11.1.7":{"name":"@balena/jellyfish-core","version":"11.1.7","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@11.1.7","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"38ee3e494686a4ca981522dd15fa29a44d8b0739","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-11.1.7.tgz","fileCount":218,"integrity":"sha512-/iYId60FCc2mDwrCJM4/t5t0BcIJF1cRETdPfGx8yYCGhZiY0fqrUGxvq3/CbEQpUQC+nwm8jeLFk/5yNPqpNQ==","signatures":[{"sig":"MEUCIBzIYBfgWMOmXYgUTCkyKHfofaoXYH90hVDLbr+//tY5AiEA7HZZgzOgMhL4dluUzNKzcFane8vWP5juKOKl1oUDVcA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":796329,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh1pCWCRA9TVsSAnZWagAA9NUP/20WPbPDIn8+s+Cm2XTy\njt0HESFBThcvguRRS54xSz5wVmjFwEHr1Do5txW60ibdngLxslhyPxmghLGa\n5cQPkktAI5qhCv07yc5P9mx0HvDmR/wwIg25VPoUoy4va6j3METGhZ/t6FnQ\nCs3IidnqDIjFWHV/+wDMcW1f0zWLpLa2EXWQEAD9MQiPREjiKvq/OQuE0pF8\nu1pjhHXCgNcoyhUu0eua3IhXuw79tTCDmAjwogB6+60DhOfsV7mH8PuE+/3H\npFps0lss2c1yzLLTkwRk2/e+QPH/IkgAeLGiesUfX1bRIZSiLNoyiOjIbZ86\nBsong6Fj+DCAw3DF7BjDSiTXrgH7477YaKt83vDasOxKh3hZtcNcP6Yao/nj\n9cu8wnsEiDwyXAvAL1YXRq4gWacOJ3vjqUlZ9fIcA1Y+6YdwIgsMQbAtkwQv\nhAkhZVosq2K3H9/QhLKKJmZWK+TzA8RFuOxsn6ZkHaAWNuAfEwJ4tu/mRwu+\nGWEqZEBxxYi7W6n0PPsqE85KlD0JxJFjW3WU86/zvRDcJcrIoKj8/3JyQYOu\nrR7hAfHzPwjVZ7Etu0/RJSLnztTEtLLSjCkv7+8WEjlcgLhGWPLyjVIPX4wB\nOews84kkpm2D1aY2sxkbSYx+llIUmRMSmMEQjA90Iwr2IY0mQBzEGfDWYzqc\nNQ1+\r\n=2uNi\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4093b2c0d216337675e2fe93231e87fd226be25d","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-06T06:45:37.642Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.5","@balena/jellyfish-logger":"^4.0.12","@balena/jellyfish-metrics":"^2.0.14","@balena/jellyfish-environment":"^6.0.7"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/uuid":"^8.3.3","lint-staged":"^12.1.5","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_11.1.7_1641451670039_0.8653960101350651","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"11.1.8-renovate-major-json-schema-org-67a5cbc68daf744ae8efdf19a783fb587cd5a17c":{"name":"@balena/jellyfish-core","version":"11.1.8-renovate-major-json-schema-org-67a5cbc68daf744ae8efdf19a783fb587cd5a17c","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@11.1.8-renovate-major-json-schema-org-67a5cbc68daf744ae8efdf19a783fb587cd5a17c","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c096899dc099eaa2a4f493a13e03cde5372330f2","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-11.1.8-renovate-major-json-schema-org-67a5cbc68daf744ae8efdf19a783fb587cd5a17c.tgz","fileCount":218,"integrity":"sha512-EgXsm5XI7dYNO5I1AEpq7LIILJAyw3PrkKUtTOkAQ0NpGtkapPHN+eIN7GYon5mpjKiI9j0aZ5+9VdDn6NjxSw==","signatures":[{"sig":"MEYCIQDxzebXc/+RDgDGXcjdoe/Qoyf8/BpQR5NG62coG9UvvAIhANaKtLhcrJWsn4iLvqK5JD2lr/OutMGi3FhDOxxANTXM","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":796497,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh1tWyCRA9TVsSAnZWagAAXQ0P/RmEa2yDEkWw510X2nH+\ng9zFoAqKJc93gF2FoAcqz3Jr6HFNJ6bJApJv/VSP4M4M4GPAC2CL8JtRUi1N\nqhWkGnwY22K9Clgty2LaqtGxaCkKMc5iYl9obAhzzThd2eP6DegAlNsbU75/\n6Ob5ggU+FrT8DpVSjgZf16nw3/bwhItDAveDHy2f2knCmgLgKNFBueRwTeuS\nItPXDA2wlorwr1Tv5S9A6EnmuxZtNHjNqSCTRVqNfrhsJ/rfUZYFl0DAhwS+\nm2Kw5kXtCo3tf8CmdolASfMmuQYuUcCXGE2ZTH8cfQVFRVnRWxL5LS1c/amL\nBXiasFGR9GyUtDG3mjCO1J408VLwZgvu9QKYWBaCpSSrn/CHhDEhP8JbWGp4\nMb4ius/1U/tsFq00BaQCFMO34aXqf+NFbCvt59s1NtYnAeoKhQ4LZipAzMuG\n2BSqTlwDCYx1T+NSVWe8hXNCoBOjlKTRrH/83Jd5XrngMJxo/Yl5YBSuhhOn\nH8Fp7AmtRTGJo0hg5Zp9r/iIOf3cB7LoxxZ9MtIXuOhJCI19SDCF0zov5h64\nJkhy2OaZ3zVpGP+JQj6/0Cxcj4nyHMLizXwQS2hqpDA0VusGon8yY3jviRPN\noqTeAWudq2zDjK8pYRAiJuVcaqlCxkVwVjLcnJMt32AqnVuFX0WzJa5exqBe\nMxI2\r\n=f4DJ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"67a5cbc68daf744ae8efdf19a783fb587cd5a17c","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-06T11:37:38.031Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.5","@balena/jellyfish-logger":"^4.0.12","@balena/jellyfish-metrics":"^2.0.14","@balena/jellyfish-environment":"^6.0.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/uuid":"^8.3.3","lint-staged":"^12.1.5","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_11.1.8-renovate-major-json-schema-org-67a5cbc68daf744ae8efdf19a783fb587cd5a17c_1641469362728_0.9733142336705873","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"12.0.0-rationalize-db-connections-0e7ec6f5d249b83b41e084eea43f30b215475f8a":{"name":"@balena/jellyfish-core","version":"12.0.0-rationalize-db-connections-0e7ec6f5d249b83b41e084eea43f30b215475f8a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@12.0.0-rationalize-db-connections-0e7ec6f5d249b83b41e084eea43f30b215475f8a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c1dee7f8f4e3457424a3260d8ac2d14be366c098","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-12.0.0-rationalize-db-connections-0e7ec6f5d249b83b41e084eea43f30b215475f8a.tgz","fileCount":215,"integrity":"sha512-+XqDp5dqS+b/o+YFsdr82jkujhwVrXt/Nny9XpccywtM5u/CU38UFqYnrDW1D99io/ZuC2vHeI5AKu+xldFzrQ==","signatures":[{"sig":"MEUCIQD0JF244T9/zK1gWeLorezCwGxtO6Fy26dvTnHhT2v4CgIgSDaKGpzJ43Vvz+BlxIDH0oQ4DRl9mKaOpui+o02Se7k=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":811266,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh2JJECRA9TVsSAnZWagAAlTQP+wQMLu8oSbEVpOMFqOe/\nSxhxrTALjbs0WxNP+7I9lK2NDx2Z/SM1fZE72lqELMUencUwcO1itDZwPAud\nTBD7cwgwtrB+sN0wbuMSaPrlOLFyK/ou+m570pQJhWDH057ICAr4JMnAsPIY\n/eae9FEO1sRDicn2VPbZ01EOs1rG4UYE4Oqapwnw130yGnxNTPpAu9doZPGt\nCtngcG7B+DUzwu8PJk+cnL5hZF3mcjnW53er39+0qOcmvSNSwCkGvIywaDNy\nArkSW/ZfdiayvqMSU3lUyAuDdQDn7uWEuI1sNb0UA/bn3BfYiufnkzBJdpJJ\n6i/yzrmr5rKofBgDn2/86AYu64t9tweOHil4UaQmD71xktnm9jf5nElCScdE\n3Qje0yLczbc8GwhQJmfrat/8sNE1BWZPMkR6YahLC7M7vtXaARZZB/YP0YkD\nVa0vSA0FWkPTMPCxZleV3KIfqFWieoRJ6zYFk3bGx7erS2+v6xcdYvSpRAH2\nZfgGATCdOFVvhMbH0lY0KR/CXKqPFKc5QgaYciWxVOJSQmpxeZEx8RpjXJQy\nFbnTC1jFypSaLpAs0mM4Eo+2jdPSskVRoAGVQJHXL3A9XhbimQZ94H1fO7dZ\nZJWBzFBl49TqIptcwLeEx1AupWEJZr/ogOiKynxq1NW77NgqJnoU0Vk4Brnu\n0Tgd\r\n=SBzl\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0e7ec6f5d249b83b41e084eea43f30b215475f8a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-07T19:16:28.460Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.5","@balena/jellyfish-logger":"^4.0.12","@balena/jellyfish-metrics":"^2.0.14","@balena/jellyfish-environment":"^6.0.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","@types/pg":"^8.6.3","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.3","lint-staged":"^12.1.5","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_12.0.0-rationalize-db-connections-0e7ec6f5d249b83b41e084eea43f30b215475f8a_1641583171580_0.22601113205590617","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"12.0.0-rationalize-db-connections-a112328cecc5b5bb5d020261f0cb6ef607ca2b17":{"name":"@balena/jellyfish-core","version":"12.0.0-rationalize-db-connections-a112328cecc5b5bb5d020261f0cb6ef607ca2b17","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@12.0.0-rationalize-db-connections-a112328cecc5b5bb5d020261f0cb6ef607ca2b17","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4224f9ca15531684b216c11d812a555562ad65d7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-12.0.0-rationalize-db-connections-a112328cecc5b5bb5d020261f0cb6ef607ca2b17.tgz","fileCount":215,"integrity":"sha512-yGzW0u+ZwCkfCZaed7h0ps9n94zLT77hSVswyLig6QRWhTEK0dm68u+L9rPTamyUQCCegjlBS4XfQL0hu15Uzw==","signatures":[{"sig":"MEUCIQDrNK0kvhHouZ34vcuuAh8GMA0x3HASHnhH7O3XA2TqqQIgM3zkbpoxXAxaqN/PG8f0UUaSE7RW2HwVcA9fkklNSJQ=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":795282,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh2LG7CRA9TVsSAnZWagAA0j4P/R5z/pgH99z5yjq18KS6\n9CnTNoVMvxI59W28xNVqCAdWUh0vQ03vE/yTkiIuK+ARhUMKkfHDcDJDfeSe\nu7P/M5ZWJkevnhO17lKJx8nK52+1Ht5GUOH1UfvaMHg8BEt9EzXyYwbqlWlT\nmfVuGPLZ4v/VqNjQbGRF+gDsG43nR+7gBM9xyjhn0s2xmcKrj8VXDYxZprJ6\ne69JoadKHNkc0vRbuPy3RKot5e/d63Dy5/uUG8GrFr/PYOHdNZ4Cqlij3tLd\nGCiSKUsHNB+HHcuin2CkQuJR61pWVhI880LZvsXIAwmsJN9qZN+C4vHI1Kwz\ndkxFr8FqcW4Jssl5L++bc00WQH7GB/Fafd9nBT8ZVBXV6wNAi/Hue3A1ScYo\nFEJ3P57AGE3btElgd1OyvFsLBZk4vKtVLf8byPkSyNn5Q89iCjHi7KeMYT6V\nfIMOzOm/7+AbVoR0Sw7KZVbm2aQq8U64XL7wQorY0yPrqwuKLDXWoJRXc4Ei\nlbM6F+ylDkD/MiIqep9B2knc1284zicXWw01JMb5Wzq8qtDIhK6f5zdHsgfB\nSsFoX5D+lI4bs09rSZF6M7XbvkOux9Q98TLtmRXbiXz79Fb/GrkoFsZ2iTkK\nffrDl9N+44RbbphxxXr/KP7Tb76Z4gw5YW/ZG3m6dHmtcKHVFk6gHOJ+6HNf\nloFF\r\n=oSWJ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a112328cecc5b5bb5d020261f0cb6ef607ca2b17","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-07T21:30:50.636Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.5","@balena/jellyfish-logger":"^4.0.12","@balena/jellyfish-metrics":"^2.0.14","@balena/jellyfish-environment":"^6.0.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","@types/pg":"^8.6.3","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.3","lint-staged":"^12.1.5","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_12.0.0-rationalize-db-connections-a112328cecc5b5bb5d020261f0cb6ef607ca2b17_1641591227381_0.7588590940277418","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"11.1.8-renovate-external-non-major-b56c7a567c9b5f4d2a3117238403967f0fbaba02":{"name":"@balena/jellyfish-core","version":"11.1.8-renovate-external-non-major-b56c7a567c9b5f4d2a3117238403967f0fbaba02","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@11.1.8-renovate-external-non-major-b56c7a567c9b5f4d2a3117238403967f0fbaba02","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6e2904d1bc106ab510e7db9df8a237e35424dd06","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-11.1.8-renovate-external-non-major-b56c7a567c9b5f4d2a3117238403967f0fbaba02.tgz","fileCount":218,"integrity":"sha512-lr6JWbfxz5qb1xXsZbxTQbGjBadaonY0j8g7F4GdS5W7svXRj1x2avpq/md01mPMBMYEiuFZJ6MVHAfbtTAGjQ==","signatures":[{"sig":"MEUCIClOyog4nDHg3lgPHj9QCQckA2Rs/GpzbmgqlMbFuH9gAiEA6+juRMxOcVaLp+YQfXWl5W+VV+cTFMFOFI5izI2OOfw=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":796473,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh2PF2CRA9TVsSAnZWagAAKQwP/iralYeQwQmRZ9BeNqWM\ni6avuuDT/MwGvPyvzXIImTbmzaAsuGmi6m1yxr9BKodaih0j/QqI5Uk9B5ZT\nogHYVY1cLWZR2S2Nyi3pdVE67/BVkpXmzJhSLcYHZkPlgsgj7lg+S/lnHwFz\nZ3jcJANo+W+WUkQiL0ls0WIC/XPmEyRank+DW/Jqp3zP4B7PjKtV02mcH2Wq\n3oFyHBaIBnOj2xveN0nza3bm6ZPD5bCir9v9ZB1CqD2j3dtobJJzGzt9Z5SG\n+ouv8mvYNn+BuuMZyubMlDYwvSGpoFM/kc5pnKkutO4pYJ7ILjyp790rvQgf\nQSuTKxP+QucmB5xMQFMGnRy/syU2iu4DbAZrgZUd5FyZWgDaPPKEAekfKIBg\nWkTAbgtW6T4ef1urV55XDbXSXsz+04mwsaTylgZhf2ZVudm6GGXQtiHnZ5zw\n71X/EVn3nghmbU868H5ISSDIryJN9iXwsCprzMo1kH5MVo2ARx/HVI4a7Xpn\nn1CViOZ6U2/kGxTu7Icw59pKF8XYzDK1TfqL2tr4oQWI0yDWY/xFxSiVeIIH\n/Jdy8KUTSBf3SczrWZO9WD/QMmACjJQT09Yi9FeODxpzkf9OaVXSeI4k0zOb\nzrEhZmk42EdwJvaUyWIg8NbjNQUiF+FliSXSlnya5LholXugaOH8UsRt+ayA\ne5SJ\r\n=OUGp\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b56c7a567c9b5f4d2a3117238403967f0fbaba02","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-08T02:01:03.643Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.5","@balena/jellyfish-logger":"^4.0.12","@balena/jellyfish-metrics":"^2.0.14","@balena/jellyfish-environment":"^6.0.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/uuid":"^8.3.4","lint-staged":"^12.1.7","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_11.1.8-renovate-external-non-major-b56c7a567c9b5f4d2a3117238403967f0fbaba02_1641607542722_0.18866694658825667","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"11.1.8":{"name":"@balena/jellyfish-core","version":"11.1.8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@11.1.8","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"51e9a52fd8538bd6a0ff2ea002cf93b355bba1de","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-11.1.8.tgz","fileCount":218,"integrity":"sha512-Eos4tlUVF3MfsNsAblhBJ57jPZJ6yJ+KveADjar8+X+UKJMPvRHN2ASJ9crwSfxnYaSv6jUb/e6NMUdgQK8WbQ==","signatures":[{"sig":"MEYCIQCjYudfZY1oMD/6+jM6cwpIO7FagMHuMlE4MXI+TsXMTwIhAKOMKx04wWoIEfGzOb+m2sc+fqx+xcksORmwDOlT2Jog","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":796404,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh2PNpCRA9TVsSAnZWagAAwQIP/jFRu6R8q9nmEl7Il6FN\nzo3ug+w/dAzp0CPII7sIABtP4UkYTUVk9Cpf7Pm08dVxikmRbI7tb0GJkq6o\nmLh5s3KJFGHWFTvC9zkY/2bZmV+g7n9E79czoHrybg2nQOI4G77Y1G4s5we7\nrBrcgQn0Zy9ROqteNkVM1zl+aQAv6dFWL5BcS/PukGjsMOE7MZa8h9Cz0PI5\n5SVuEgzU5kBkS0lZlCn9ejm7ImqDEVHoyf2z4QDLpMOARDG/6jF4u2U966/3\nyZCeKIRCUW3nINoe4wfH+bvi/1vxmTjz5zVkNUpR64qKM4G0RdYAZoLrEsPX\n2lg28v5p6GrNtw9ClhSwn6NGi5tHElj1UWtFtpqMVil+kBdWhtU56vkbZhXZ\n/N2Xold3c+nE96Fl3LUy5mcGTpjRPOc7mV9ZAvDNB4eaHQMeiL2oqMzNnPU9\nf2VdTwt987+9q7S4N4dRRHUY1G3IFBpZZU+ln5OiHxV5fMtu4/oNeZVrbXyu\nOUQ0DrY15g1LcdAw5gjX+TfBUV91hZ/R0Hy/ris5HM4oTrBp5u215GAtjQ7l\neMqb5Mrttj6B/wJsG0PTacoySAZHCzLwp+KXx/t7nlrtBf3GyFnbbAgLeKMH\nkZCaMwbXG3U2lfcu4ZUQsE1G3xE1qN6QotnrKguXksJNsdWSsH9hkZb6mNyF\npCqu\r\n=Qhe5\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f90c176c27be36a0bb0393b956d33f067a01a32d","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-08T02:11:14.661Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.5","@balena/jellyfish-logger":"^4.0.12","@balena/jellyfish-metrics":"^2.0.14","@balena/jellyfish-environment":"^6.0.7"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/uuid":"^8.3.4","lint-staged":"^12.1.7","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_11.1.8_1641608041588_0.34853519602836514","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"11.1.9-renovate-internal-patch-d8d710f501ca436784bb2afe29f4181135f3754f":{"name":"@balena/jellyfish-core","version":"11.1.9-renovate-internal-patch-d8d710f501ca436784bb2afe29f4181135f3754f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@11.1.9-renovate-internal-patch-d8d710f501ca436784bb2afe29f4181135f3754f","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"58905f3a8765e3523de9061579b50f5f62b08108","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-11.1.9-renovate-internal-patch-d8d710f501ca436784bb2afe29f4181135f3754f.tgz","fileCount":218,"integrity":"sha512-ZBY4F/YCuod/Rzk/BzhctlJwqEK5Gv2w+DzX1v795ZqMxR9CdMvdtxl8sqYS0IvuZLh6IeiHSMSBVXAsXuYvKQ==","signatures":[{"sig":"MEYCIQDTyjJ4HbHfim5RpJ1H9OQDJ94fwJzogIX8GSBrxVtlqgIhAMjxbArQ8YzYzSURp2ai/AN2bhe8zsWtmpBuYNC2WcI4","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":796540,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh2PWeCRA9TVsSAnZWagAAg0wP/RKVDWawvzEna717Ile5\nuc9nO8O4nvtQcDUJLOAafYtSjK597vf2kn7IB4RyZxHpVHyWUvyzH2UKRDf2\nZ6YteVrCpkfY/n9dsBku8sh5hHX/zfhqBw3KKShs99jDkeS0RQvOPS420dJe\ng7HZbvY+x6Kmxmh6uO3+JQUQTEhV8U8PX3c6gJvlvrnEnCIlLW4H6CBF9yNC\nbcxG/IU/KEWmssguOFRZ7d7BGmc3oFpfMlRax56EZvor9Wb/T3axJIxAGGSg\nsT9HICSX1s+8LlZdfRWMvwAQaq9CYToqsViBdLDy5zZTKJtihfbx7DJA3m+V\n7zzE+MuFFxmn3PwgaUtlmkg8+rGD1NwAGfdP+haZ1g86YBZHVIaLfEynzgS2\nBxa+yU35iFFbV4aI9biWuX1EuZbwzP+lYtc/4AYoPQpV+jsnjCCTy4Et3gr2\nP5RN0FkxgCSdmfZtG3jTXis182sC0cqs5+k7/1AE7nMbYKPA3C5DcY2euVvu\n1iGbM9lpolpF3/uX2c4Qha6sVbHi50QQdrBLB0Sq7Jex0X9dPDTaKGc61WM2\nbmIrZrb8ku589rZb7BdvWLDIS0MCwyy9mAUu3sob1CPqdNEEZATuluLf6ULp\n3A8nR2OzOlr5N/B/Ydh243sLDRzz+1EbUVr5x1QvObwWn6Us1biaeGGgUV+F\n63cd\r\n=nDr8\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d8d710f501ca436784bb2afe29f4181135f3754f","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-08T02:18:01.233Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.6","@balena/jellyfish-logger":"^4.0.13","@balena/jellyfish-metrics":"^2.0.16","@balena/jellyfish-environment":"^6.0.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/uuid":"^8.3.4","lint-staged":"^12.1.7","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_11.1.9-renovate-internal-patch-d8d710f501ca436784bb2afe29f4181135f3754f_1641608606719_0.7242688625817477","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"11.1.9":{"name":"@balena/jellyfish-core","version":"11.1.9","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@11.1.9","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ac92726294e50a1d98031baf76189de6547ea136","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-11.1.9.tgz","fileCount":218,"integrity":"sha512-AhaOYpiiTlTdBd39dpOkHbE7u4Xh0cVnEL5qvMr62mmrm7RX7a6iO+u8bjLuzB8wps2Ns7+lQEIHc/HKY1ZiQQ==","signatures":[{"sig":"MEUCIFPH/GnSeV9Cys8Uizt+SNvA+x1nNV74qMEiPCBZTQWjAiEAgQFa4Y+CracInNPBdULSASY8/j2N0zDD8DRcGn35UMM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":796475,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh2PegCRA9TVsSAnZWagAAiRwP/iAQm0EuC3V4dQsl7bZl\nj5Z8FmiolUsr4457UkpUCebZneULDHp7xTFUL0Mxt7G5WpQ4mVPsJN2gfdKU\nIu6ChaCJx4mjyy706sV+TXRljMuwhvAuEdGc/k/5bLe7fJNOpaPlY/aDgT5S\nlFmq95egeK7kT583HENLoeiY5WFh4GS1aQ0IGhXAP6Z2Yy8+xvraWRrCrW46\n2Rif6rwEF8Kp6Cu1J1AjbB7/hHv4uXylSth5ZBD2tcWQZ5BkkhbHdD+swW+Z\n5DktpKSsaPJLjSn/zyavHVZIbjhHg1ahWhkbfLSYK4eaRqMsUTCP9BnKp0f7\nu+BHrgtr7vuJhkW9Nrf9fa0RrdfiKPv8fmafV6eGQHo4H1cOPMHq/xpdTFCi\ndDEi1mWIIwoQa3YJmPK3avY+jzGTMn30nd+Tf9W7HV6EhTcAzDwykFoGWoL+\ndSVG1f+RzYewdNNECFpfVEjRQ2UfxS99vr2R8TnYTTNqEsvoVcZdY6bKPsB0\n6jJjcubABgjbebYrqJH8ypx9ZY1N5gadgm2Mqox/y0fvd1BbzE7Yx/iL9CWq\nsfPV0vXq6d+GnmbTGw6pYJPHCjf2UP+I+NM+niB+PZS3iUnhIz7C8KtC7oaf\nKbyzXnNxOAeoqNromCHMN2GudcM+zLhzyZs3iqXUWX5zzARqHDcJT2UiZnkH\nfFsp\r\n=7Qde\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a3547e38353019adf8a31d73f297f97b3a3a1a61","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-08T02:28:45.191Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.6","@balena/jellyfish-logger":"^4.0.13","@balena/jellyfish-metrics":"^2.0.16","@balena/jellyfish-environment":"^6.0.7"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/uuid":"^8.3.4","lint-staged":"^12.1.7","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_11.1.9_1641609120236_0.2745973781008042","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"11.1.10-renovate-major-json-schema-org-7203a9d2701e7fe6af182cb06bb34cee875d44dc":{"name":"@balena/jellyfish-core","version":"11.1.10-renovate-major-json-schema-org-7203a9d2701e7fe6af182cb06bb34cee875d44dc","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@11.1.10-renovate-major-json-schema-org-7203a9d2701e7fe6af182cb06bb34cee875d44dc","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"07a51a6643fa9a8e5d5fc23fcd3c37c0dac2be63","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-11.1.10-renovate-major-json-schema-org-7203a9d2701e7fe6af182cb06bb34cee875d44dc.tgz","fileCount":218,"integrity":"sha512-UNuKB6g7hiNrU06auFlG2auGFSTwfEnt5UE4MC4GCOqteygMdPuP89XnF/yDd3d8iVUF6ojiZgGS7HkBeBg+HA==","signatures":[{"sig":"MEYCIQDaGgz6IlrgH9VuuezHbDcDvCMDeWvjrhUb66SjyYbuBgIhAJeyMIrTz8MWTfT/PeAOK50jJ+h0fsrywIS3BbXcYNXZ","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":796645,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh2PlhCRA9TVsSAnZWagAA5kgP/0twpb12ZZ3HefT2SoLp\nHxtdnM05W7UaJ5d52B1+C0fW0xZEXYjpdTdn8EKXwqUa7LIRwmqe2AstRqxq\npybCHZ2vIKRfMfjS9pTzXTXhF1LkKHSO1tHZWz2q0AjEKy0FGtxmdSXLL/WS\ngWK43ZgtCTLyp8Ljhs5StTPZHBUsP7WdE3zaCQQkNQyNWzZ3pO8VGZhCvoQj\ncjm1mbXr71N6wlY5EFxraPrD8NrT64hQLMnuu+VW/50DKY4Q/nDTS8bzl1H5\nnnaEiszlbdAJezCNBci7QInlyDDs5yMKtNIct5WBI1iFg1re9O+KLQfOUxGS\nxkuSLEXaaG+4nAdK5IrOow374ynBjb89PYFbYnwfttxIBEKcmPTe629gzY9w\n327xgPRsjMQmtSzzV+HzIyCVpuLb9ygDvzv9VYKuhlOsA9IgaI1k4O1wvB3C\nga1IUFN2t2juRGS2/M/yUospHaJk4N6vgaDTJlU+GsgfqXJ5+xdziPfeqXzd\nyKrd+ie+iSn0fhQjQHZZPnNpPYuOBGXndZ2sQoRLZkvAuC8G0Q777/oX9zwo\nkR3LXCTjHUUVXQgkZoyP2M1k5rgy5HZ6VZqAZvGYGAmp/Xhh5O9grsuBU4mu\nrhy+/j2/jJ/vF0alpc560Qy3E9bRMnNzjgm16zeDLrkR6Vk0an2iBqOHKo6w\nU6TK\r\n=6W6t\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7203a9d2701e7fe6af182cb06bb34cee875d44dc","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-08T02:34:37.634Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.6","@balena/jellyfish-logger":"^4.0.13","@balena/jellyfish-metrics":"^2.0.16","@balena/jellyfish-environment":"^6.0.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/uuid":"^8.3.4","lint-staged":"^12.1.7","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_11.1.10-renovate-major-json-schema-org-7203a9d2701e7fe6af182cb06bb34cee875d44dc_1641609569218_0.9566396759690339","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"11.1.10-renovate-internal-patch-8397f5643a2c44d16bb5bfb36f0ed5bbee2f09d1":{"name":"@balena/jellyfish-core","version":"11.1.10-renovate-internal-patch-8397f5643a2c44d16bb5bfb36f0ed5bbee2f09d1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@11.1.10-renovate-internal-patch-8397f5643a2c44d16bb5bfb36f0ed5bbee2f09d1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2a946a55ce27f6d49abee1a524e077b22d16057d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-11.1.10-renovate-internal-patch-8397f5643a2c44d16bb5bfb36f0ed5bbee2f09d1.tgz","fileCount":218,"integrity":"sha512-1WlT/+rwEr8yGKrSlrfmNRxRz02J3qIl3RQdn1jxcfXP/ViiuyvO7tbf3iUwnCWVk+oFppaU8isN4rHHRH7x+g==","signatures":[{"sig":"MEQCIEu1T9cmWNgRZIk05AKh8Y/V1yd3XrsvrshcToJogBZ9AiAx0HycSGhwO3t3W5E7Je+89TTfHGAY0KV1X1MVgsfv3g==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":796613,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh2R0ICRA9TVsSAnZWagAAP6EP/RVgOF7upc3lpAMY+izW\nhLdn2Haf/d3A0Yh1LXp+jvD6hztTCGVYEMvI3FDlrVyuebk0+PESLr1heCRu\n7/ARZsp3AIkiSXzR1zUENJcVF6CIwGe4R7H4eYl0HBoOxd44hjvOpX7raPf9\ngZT6PHisNPXWRw3Sa59bpCGBaRG03Kw8l6LLB2MxhnM6R47HtOux8vPTn+u5\nJZHzjJwYOhJJQMx8BZKimhAqlTMFMt2+BKG/DZccorghD4gjSe6ypxpF/12k\n4jcbkml55EDPm4aQtHE0pBQLC2wF1FPYA/ZVVKOwQw2PfHcvh3K2WEK+mT8B\neNphd1YwW7bHDmAv3tnlKbgx/10LwGls1O/2sdyFouyx8C1d0LnQrzB2j29d\nwrKh4IckKbIJ1fa7ZY6rJs3olcIc+YtBW0pCgC5SaM3ur25QjCU8gs8mtCt5\nc0EwS/rPlvo1gZsG3hyCEhHgi8uaKsEQQSPaOmfbtYJ3WZADpGSc2V1CceiF\nZwgbQZz6DhY7rNsHcWWbjRp8Xq1F3rQE89CkrVys/DNLOjYN6v8LexL70bJm\n3HRyvh7kDomMtYcKi65mE7/RBmBneXkZR5CFhQDZ0Ohs0GR7hpgZdx2EkEMY\nhTQWOACxLvfm5D0mK7l30PH8sKrq9MY6STZfICQYgYhPFbHHcPb7AOenBkZP\n5MTJ\r\n=NNoJ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8397f5643a2c44d16bb5bfb36f0ed5bbee2f09d1","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-08T05:08:59.546Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.6","@balena/jellyfish-logger":"^4.0.14","@balena/jellyfish-metrics":"^2.0.17","@balena/jellyfish-environment":"^6.0.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/uuid":"^8.3.4","lint-staged":"^12.1.7","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_11.1.10-renovate-internal-patch-8397f5643a2c44d16bb5bfb36f0ed5bbee2f09d1_1641618696080_0.8804108621918003","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"11.1.10-renovate-internal-patch-33f34b581b8e4af9a80d35b4a0a6d0b31bc6f5d5":{"name":"@balena/jellyfish-core","version":"11.1.10-renovate-internal-patch-33f34b581b8e4af9a80d35b4a0a6d0b31bc6f5d5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@11.1.10-renovate-internal-patch-33f34b581b8e4af9a80d35b4a0a6d0b31bc6f5d5","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"7d6792ccacefb2a3d5291356a8b20a870edd4efd","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-11.1.10-renovate-internal-patch-33f34b581b8e4af9a80d35b4a0a6d0b31bc6f5d5.tgz","fileCount":218,"integrity":"sha512-+5juU4OMVKUXKHWI9mhSoj+y/X5oeugj4e54ZkfhWKO8qXFeEQDR5Zmcqsi86S77TeVJ51Ln7BKL0e6DJ3AeuA==","signatures":[{"sig":"MEUCIQDoqP29TgQouqt3E3zu9uC5E8S5ETNNqIK6VEQ81qL5mwIgN96yp3z7fo8t3FGggFFhceNM9ozBkO7e2e+06ViBgyM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":796613,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh2YQTCRA9TVsSAnZWagAAl24P/3jbqrrqVD7jpAUF6eoY\nM0XVLDl9SCea1iec/I9/aXqO1HFKISPsa619BIs8Zrov/7vV8ju9lWLX3paY\nh/svo1hwU4B+Ds2RBDi+SbthhUQUMGCGD1p+viZVepHBgG2oN7kiP7n0t/dp\n7PbgiE/oDymOQSGr5+Z0/keo6HTY3fl6sC8ytMEwN/dJ6OXh6lEHt5xFZo1i\nfyxPkuodIxyhHYXMzjjaV8MpxFAYGhjqC6cq9hbVZ+0qV+qj0HqbtpEPo7WX\nJQVvT7Of1/bPqL0sc8XqubZTZ6sDYAMVRvPC0L6wo4KMaOmWqwCZJJgwBT2Z\nNoSWHjgKv2StUQddcXan+Jf9Tne1Raoi6TJjZ2ltfRVfB/47dlbU3PMRs/ZZ\nM8nl7hHYCZbWzJka/YHROe3uQSP4XOpO/2WRO7pz7csn4oKqp+WMpGhT2aj8\ngXvMBYhMQ9PO1Quy774VZQXOru7Ruba8LbxSZh4YWZAX1+B7Z5YIlBFB10Pn\ntUBYwiVIwuW5ZsdxlldWU6acUF6CeJ+j6+z2SvqOpKBM0cm5m7zDanwuVl4e\nCoAeUzodCL4+8L1YJat3B+GvCCdXtqWnrwGzKDQDAMsCnmnceMnC7Mz4b84G\nj30+1zvnHOPSDHmhiDQZuY8Ap6jCl1iYhy+W/nOFlMupH9jPQzdRRAEz6ylB\nDOJ8\r\n=r+07\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"33f34b581b8e4af9a80d35b4a0a6d0b31bc6f5d5","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-08T12:27:21.790Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.6","@balena/jellyfish-logger":"^4.0.15","@balena/jellyfish-metrics":"^2.0.18","@balena/jellyfish-environment":"^6.0.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/uuid":"^8.3.4","lint-staged":"^12.1.7","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_11.1.10-renovate-internal-patch-33f34b581b8e4af9a80d35b4a0a6d0b31bc6f5d5_1641645074926_0.335459975984834","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"11.1.10":{"name":"@balena/jellyfish-core","version":"11.1.10","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@11.1.10","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6055cd5e7611b31c5fb54d6d82fbb844d0a9b36c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-11.1.10.tgz","fileCount":218,"integrity":"sha512-7ru/AFIVXImzOXjxDWMQ0ef68i+dSRfSAeKrgdJRofTQ/ZvVhk1tbeztSLSd4HhbPyYE/kyb+43A8nJa997Zag==","signatures":[{"sig":"MEQCIH4v757Elk4byHzAfGcbs6qAv+GUQMNjIEZL9Zn9CRFkAiB5yIX4lFCJGKthD3cnXWkGOSNb0LGDvCIvbWPkg8LiDw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":796548,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh2YbJCRA9TVsSAnZWagAAWGUP/0Gpfhqj+cazv3nNNHqF\n0jkT/fpwSfc41BYxqQN4v9ecdLDO7tgvR1B1e+QE5JK1cXvf+hqWWn65irCv\noJq4pc8LNZjHt7pbEkNELpRIG9rTeBfkkKFTxMZ34eQoLbDyoHUYXlUzXN1e\n4C4ZMhbYcebgEosmkJ/BNbNigRheZZ+yleL4yYVIFX9AM/jTBY1rIG1quIri\naQsHhwqd0jNgmoZPYBQXtn7NverGv3Jj0Z88qriBbBdvMXKDIJ1QkWy4/DG4\ndeIeMOJiZJMzvJHeUffnBYuj/aeTelY95tSRY8/agxCKb8W29mVURZ6fH8qY\nCEiFKRlofYslVQj7RxyzOz6fDmpgU7LkuqpqB8a/uijvkeyX7O7CA9Veopy/\nvBi5mIfpym5WjZagF/RQkuuir3uIPVH0GarDu6gEv3djnslYkdHtrPSwZM2b\nxj134suT+O6o0+pWI3mQGczbg+ffq2kf7FjlUJw62sk8O8FJlD/aTU/D0HVe\ntEBvtSB01MmKDzWQfVPGAegCi/XVvx74DbYKPgx/FP3nNHcMtpL8PhdkhGOF\nbwFUIkKqcuZa2PNKqmH84iUGq/cWCN6r4LSRT3dpXir+9exzkq62paaB0vy+\n/h9RmEtUVAjnuTDM7kJ0KvaRghPjL54bzuFBngGL19LGdx8DOXDKEC+CWaGi\njNAb\r\n=n1md\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b74db096d2f51f07dbe0e80f90fb6b448b5e0ace","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-08T12:39:18.563Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.6","@balena/jellyfish-logger":"^4.0.15","@balena/jellyfish-metrics":"^2.0.18","@balena/jellyfish-environment":"^6.0.8"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/uuid":"^8.3.4","lint-staged":"^12.1.7","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_11.1.10_1641645769422_0.19351824232985804","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"11.1.10-renovate-major-json-schema-org-81df527daf5acb541454c924051a18744f2178ea":{"name":"@balena/jellyfish-core","version":"11.1.10-renovate-major-json-schema-org-81df527daf5acb541454c924051a18744f2178ea","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@11.1.10-renovate-major-json-schema-org-81df527daf5acb541454c924051a18744f2178ea","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"7a185bb699cd3076096261fcde33ad4078f72f4c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-11.1.10-renovate-major-json-schema-org-81df527daf5acb541454c924051a18744f2178ea.tgz","fileCount":218,"integrity":"sha512-JbkJeXZbjyQjm5C7AdLFOsUgdMmIcm9j6pq1+o1m4HGwM/ZKU7ioYUZFUjN1Y4rL8QM3jwgIQLN6YC1HpeknFQ==","signatures":[{"sig":"MEYCIQDAQl/SEXi0Bhc+TxMef51/666qmciGvFoPBk5KnPGsiAIhALzRdNXsNdYCNloPr7JfLTYqpGjHYYaCEwifssPtrMjJ","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":796691,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh2Yd2CRA9TVsSAnZWagAAl84QAJOrsftn8cLtT/wEB5Ma\nKvcy6x5v4bN6LDsXI31UROM/HgOHeafcEF3njC/Xcnz6WjG21jVOeIvdiDJS\nkg9Mj9JXhnw5om452uoSS5BHWn+nyAyIWhcTV2jzfAktt7saVd9bgiH+d1qg\nHzMbFNqpKmH+m9RzweVwLyLXGEy5FaleM/7DjUbw4pAMsVlSfBekFNOAXFKv\nbA8ESKRKEVYaQJ7hDHopLiLUGdzPL+yC4j2B4Te7qZOKkzMTxTAaOukEGKu7\nAY6JB+NnO4wZHQYLl2vdQWwwbIyO2AVATZRQT/8YX/R4enVdJ9IoGJUvd8Mp\naOyPWc+tXr16f9wkT7wj4ai6OzmtISBd3yDI4XU1uIuzPQU7coS2IiE2jlL4\nIOkpHxaueFc1rtvM4xc560HYeBUhPmF6MOTuHjUHAgzkHo4cuGohFWMSROJt\nO1gHVjy6Ay1ga6k2XI9gG4VeOCZ9aTYeJZplpeytk/Vk8/3+Y2qhH4bCYpTA\n2FyQF5u4OeU3WMljn78lyybaBXect4toPSFcV2p+AqfUwnnieVlTR2Sh5Q8o\nrkRYuKK0OETolclI0CJMv9G8w5hN7e6pIooIGTRGTPuGUVJNgVBt/kBZRMhn\n5ty4166iwv5/cqccvOQBY5Qixqm5kLFuQm1h0V1rgDNeZw33KSxb6xRakQ3A\nt6pz\r\n=WO8J\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"81df527daf5acb541454c924051a18744f2178ea","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-08T12:40:58.345Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.6","@balena/jellyfish-logger":"^4.0.15","@balena/jellyfish-metrics":"^2.0.18","@balena/jellyfish-environment":"^6.0.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/uuid":"^8.3.4","lint-staged":"^12.1.7","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_11.1.10-renovate-major-json-schema-org-81df527daf5acb541454c924051a18744f2178ea_1641645942025_0.8192874916835737","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"11.1.11-renovate-major-json-schema-org-d51c5ce2ff0f60b3dafea9117df420c1e4ba16ee":{"name":"@balena/jellyfish-core","version":"11.1.11-renovate-major-json-schema-org-d51c5ce2ff0f60b3dafea9117df420c1e4ba16ee","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@11.1.11-renovate-major-json-schema-org-d51c5ce2ff0f60b3dafea9117df420c1e4ba16ee","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c60155ec804d03a7c5b6213e5b8e15803b2b26eb","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-11.1.11-renovate-major-json-schema-org-d51c5ce2ff0f60b3dafea9117df420c1e4ba16ee.tgz","fileCount":218,"integrity":"sha512-irNz0au31IdIHGyZ7wSRuhMMDtbyqEk7E9RQJ4ZqOLQ3PkmBQvKnKnrnM27/8Zp08oj16srvlf6f+CbrupLrzg==","signatures":[{"sig":"MEUCIFqtiJFUyLeKcJjjkgR/GhdKr00a/kbhKAgU+zoFZJqIAiEAxYLIq23YfefInsDRQ9bQXp/ook1Cf5gaRGXxjV6LO4A=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":796717,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh2Yg4CRA9TVsSAnZWagAAQXEP/AlpTtOsLwHMi1uydaT4\nmdWT/CabWncKGLau2Zxa2cIo16RWUE1JVOr9/8bv4hdwwRY+MYjlbWpgLZgR\nYhoeafwgN4b2b/CTIX3zU7GE/N/8p3PLZnqAgMGPKkIRNTiQbU8bV3eBSCZf\nfSPyoPMUXxUuYVVUhapXrThfeGs3vfVmf2+6qKAdNQs/oSmsboem/cvFJQy+\nXTnKu9x3GVIXTVpuqUCNLNVx0PlbiMAIcbveRYT6iuX5e88/kjGWlh52FEbM\nPBZH2P8f5vuz8An3Qi7aib+vh6N6PMvEGfEFG8VKkppX0/pV5KPmxHVqTwBe\n+poYP9Ra1xc0gFm0RQ2anUcLzU8T1Fojopo795C6HsS2vDWrtzTVzcBLMrJ0\nwdKP0fqoU03erfLOFz729YjoU95lRKWnOLg3O04fcM3NaICX92GFxBuHHITk\n26hXYjw2/y2gO+dD6kPJfB9ADlgFfpRtrrEk+QCIN0TwcQD2vqkjRdyy2hPg\npvSzPs1D7NRbuMi8dPQZqHG3VcU/R3bTqUmEXjIwo/SH2QyrUMq315B2Xh01\n7K3GyxX2fiJCx5A40iFPg75O9pz7Uxp2Oe2VHtZJShai3v60HHEQl0pQKUsO\nmQkN0MDnhDLsiohTot0qd2fUWJH5ICoL/ihYX5qlK/PBqzoCYPU0e/o9L0YG\nSE+b\r\n=wIXX\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d51c5ce2ff0f60b3dafea9117df420c1e4ba16ee","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-08T12:44:05.198Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.6","@balena/jellyfish-logger":"^4.0.15","@balena/jellyfish-metrics":"^2.0.18","@balena/jellyfish-environment":"^6.0.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/uuid":"^8.3.4","lint-staged":"^12.1.7","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_11.1.11-renovate-major-json-schema-org-d51c5ce2ff0f60b3dafea9117df420c1e4ba16ee_1641646136261_0.5258930935965","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"11.1.11-renovate-internal-patch-c9d4586d682724316c8feae15ce2f22f63abe6c7":{"name":"@balena/jellyfish-core","version":"11.1.11-renovate-internal-patch-c9d4586d682724316c8feae15ce2f22f63abe6c7","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@11.1.11-renovate-internal-patch-c9d4586d682724316c8feae15ce2f22f63abe6c7","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f58b7601d2661b3416a2cec186f90e8857d1df45","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-11.1.11-renovate-internal-patch-c9d4586d682724316c8feae15ce2f22f63abe6c7.tgz","fileCount":218,"integrity":"sha512-l19Pnb093znMfaFEzin3TAC69HFEkDDG/1QeJXuAObp7NRJQFqImdpSsbfnEpzb3FCr1fH6G1k+zw1SbV9MgZw==","signatures":[{"sig":"MEUCIQCF22G2pIww3M7+GgHHwiKOG6YdQ87TWv9wpN/6riIJVwIgYytmPtWN4mpQodU4jZCrZCtkpZ+d8OyvIB9/U7yP96E=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":796718,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh2ZxNCRA9TVsSAnZWagAAb5MQAIV1gRqV+AOffBS4bjUW\nraiUN7KawJT+vXwaNSN0PteZruX1lQ+a3Mx+wC9PX+Ya4Xxug7oUj6qx6KtO\nH+H5ZiWudxc4RPmBIdzWA6gdKlkhw9VVrkNisuHKzpUxKc8bgdGCG9rBB3P0\nivfh0Zqof58P4WR8/FveFkFsN5E+lOeQAV0cZqk7x1KSIylHVLzUxO4ZS4D8\nLXEVEKo8TZ25iJ7cyUe8ogZFLOfcSn7xaPzZFgMzdbfBKq0NRvGM8B1tde7X\nCkMvwbBr7N5zxWBRgk8i+lHmgrvoYEyo6vX9rldvWzYLhS4mLYdXLyIEXyXK\nMUkjx1YLduDufppzGdoHR8YTvKJtaSQXRRaGmKoLNIycx5aSUshOhk3va2CG\nZSmcyagZ4I8TFRgDX7i+T01qQqHvG8vhxG4+JbLZe+MXUSYfmuIG2150hNHi\nTFiO5xjCuVvCr5wKnGkTQUzc0M2AT9NtfEGxgZ/5GknNFdEy9uTgJHaoXpbM\nM1dotRfznG86ES5U0Ve9n605XNrlRCoKKWce6wz9O0NpsqKIS2Vi7kc3LFhl\nHzFAam2p2A5Wv2UloPzhWFyDqlhlt5RQtmXb+/X4tCIxmDyCELFPyRL4LQPR\ner0wB5jxb0x6fy782ianWfvQvTtWNS+8+qI8tRfgHXP/i1h+XTWD+aQXjsCA\nH1OT\r\n=XDyf\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c9d4586d682724316c8feae15ce2f22f63abe6c7","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-08T14:10:19.325Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.6","@balena/jellyfish-logger":"^4.0.15","@balena/jellyfish-metrics":"^2.0.19","@balena/jellyfish-environment":"^6.0.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/uuid":"^8.3.4","lint-staged":"^12.1.7","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_11.1.11-renovate-internal-patch-c9d4586d682724316c8feae15ce2f22f63abe6c7_1641651276944_0.9850300175992326","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"11.1.11":{"name":"@balena/jellyfish-core","version":"11.1.11","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@11.1.11","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"aa968cc35d32211dcca3e46f3294ab2d1d0b4e61","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-11.1.11.tgz","fileCount":218,"integrity":"sha512-jeLWzJ2/Ssc7IRNZHsBX65I07GNV3tT8Mo5dFqnsuddEf6Gqzcao+lULK50TlStIRDQ8RhVVdXgrZHBoG7wrqA==","signatures":[{"sig":"MEUCIQCd+3lZwg8tTSQK9yBNL3dAEKgJ2byVpI4leSh+vy1yQQIgJd9lTiO9rVbR8jNKTjcNTwdYTPZV12ogRkVueW6yRyw=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":796653,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh2Z3UCRA9TVsSAnZWagAAsB0P/R+YQnTDvhLzXn0NFiM1\ntkLmn1fPIYcI4lq9kjb5HxfrCwwgkSmEvaSgq5D14V9OzRwLntFosBp5tEeC\nX3log6Kq5cNULNYYOa+1GqGqYntqdZXLSe9qwAKPZ7tIFpHPcJULCM2CUh1b\nsRG1us53dZe9mhsaTtA8kBueU3pWcajhzhazOMxPYEe4ix4Xu4O5MnScwgGq\nwmA67PBPF8tecXLFh0N29CP494T3b+TSsfmcfa0R18X6Drgxr5Jf+KzoeJhA\nPceTrC7ridJZzNvZn7JUoO2kJ1yeYqRYQSE7o3o0qMUgzi5155Bi+RnEgTlg\ngF3eaBdEvkS5G6DAeyhUZ+giwq7C2YFUWttXNIwEWJ8j9Ck639LuZbB1rPvf\nSJTSTmy1OvngqSEjOy99wYnQgWtt5ofcs0wiwofaYlmDLrTLfd/t/RXGAkK3\nIrZkflNLbUcZ+RgGMYxpsSAwToJ99dLDQtp6WSQazDqZgs8SKqAazcMevYua\nXokalxm0i/b3GcZepvZnl2A6HdLwGCmSHCImmHQbPdrGg55lplDA3N1rHXgG\naI322IwYc4prz1TyzCoKMbppJI6NZP1ZpMr3pAZMcaLdTij81vpwpr2Bao5n\naGMCO1Q0xCRGOwpXtN3It1XKJhWq/Xk+9F0N0rPIePUqiJujtvoooFyQiKKF\naWMv\r\n=wVah\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"27158143a1c60c033e23d5ccbc4ee5249c8cfe82","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-08T14:18:17.938Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.6","@balena/jellyfish-logger":"^4.0.15","@balena/jellyfish-metrics":"^2.0.19","@balena/jellyfish-environment":"^6.0.8"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/uuid":"^8.3.4","lint-staged":"^12.1.7","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_11.1.11_1641651667927_0.7662102115977851","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"11.1.8-card-to-contract-ed7ea8ab29d0e786124aa4d610c04f94b911b4df":{"name":"@balena/jellyfish-core","version":"11.1.8-card-to-contract-ed7ea8ab29d0e786124aa4d610c04f94b911b4df","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@11.1.8-card-to-contract-ed7ea8ab29d0e786124aa4d610c04f94b911b4df","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e4b4da04b7adcf53106d84224ae6b6190c2e4386","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-11.1.8-card-to-contract-ed7ea8ab29d0e786124aa4d610c04f94b911b4df.tgz","fileCount":218,"integrity":"sha512-8ovR7G5Flm/p2AFsYKvMeTl46kUvjKZZsWJjV4p5hRZOOtq+oBLi42hX0NQYyHnNCJ1MT9Ef1RioPPJNGinBlQ==","signatures":[{"sig":"MEUCIQCgyxrWJO/9f6IKacNyC8g6bfIC+AXxLOGq6rcMstXofQIgZxKr6YIaTPA4WUpbtwtPNMHWXnq/8qDUSUrk+nII8A0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":798300,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh3FySCRA9TVsSAnZWagAAcqEP/RPWwtDFmwv+zAj34+uI\nlfpFZBqAPDMlHzAXJS69fnLMJJ4VdOca2j6mBX3SQqTlwFT8kSAWdF8NjUy4\nG0+Nar9orsCZQKSq1iZy4UaERbgGoDQle4wJneZFWt6Bu7ye9mYl5VmPdROU\nOgYyxBEHyp9u0oc1y5w6vZbwRjRYEWqQZhLfu6Nh7BkFNCxmpO84ZWTjuDvv\n+H2cGbIRj03ThKna6iH5zI1YqP3tZhLCl0eg/TXrMy4qIQdyckOrJriBW4d6\nT4IDvc8m3ijE+OWusA7kPFa38NOzaZbK28hnygcBLZR8p1KIBxp6HxHL/ZyA\nRRwprUvh0P07teD0nd5R3tfNUvZZExMIqXgEpyd3pYt4PeMoNP8n96OLhq1O\nUwppmK9UtwYnwnUXzumKTSte4+UbkB5CvACFHVgNagxrNtrJPs3bAmIifN5H\nojfoiWAePgl/0JkpGO9uGr7iBvA+gpReQViIkVJxAdox/s6X85/aM902+peM\nry0G/liILXuFVl+NZnBIW6i4nOpsJ0ttzT2ONdir+SrevZkR85aXEolp/BVC\nnm3Opsh1LIh6I4LnFuHqbX0CeiKnkc+X5Q72BXgEp0N9zDYSxC/txy67tHVh\nTDy/iEG49e861U7qV82UbgfQ3uOaOm0UgYJSgEDIos8V0NcvLm1l+O/w0me2\nRvUB\r\n=3WGT\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ed7ea8ab29d0e786124aa4d610c04f94b911b4df","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-10T16:16:16.844Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.5","@balena/jellyfish-logger":"^4.0.12","@balena/jellyfish-metrics":"^2.0.14","@balena/jellyfish-environment":"^6.0.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.5","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/uuid":"^8.3.3","lint-staged":"^12.1.5","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_11.1.8-card-to-contract-ed7ea8ab29d0e786124aa4d610c04f94b911b4df_1641831570519_0.269622213210557","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"12.0.0-rationalize-db-connections-c188b81de6f5c14dab3912e5fe9175276e3480a8":{"name":"@balena/jellyfish-core","version":"12.0.0-rationalize-db-connections-c188b81de6f5c14dab3912e5fe9175276e3480a8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@12.0.0-rationalize-db-connections-c188b81de6f5c14dab3912e5fe9175276e3480a8","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b96ab34f7b82b722374173d4f0cdda00a9702bb0","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-12.0.0-rationalize-db-connections-c188b81de6f5c14dab3912e5fe9175276e3480a8.tgz","fileCount":215,"integrity":"sha512-8pfwrl3aHoEItYPEkyzChQkGxHJLiJAWHERi1lhuKTnHUcIFprpVPTQHJQG31u7z3vbBu8iFbIdSZuig8UWFkQ==","signatures":[{"sig":"MEQCIGh2bbdQ86AgedWTUhfZac6SO1KBG/PxQvIS5iPYLboJAiBYIpY5yg/x4cDi1+pwVFkYYazB1GtOHT0VTK+U+sU8VQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":793482,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh3JHxCRA9TVsSAnZWagAANS0QAIgcDVohE84x0nlyl2O+\nBGGdmFcenQp5AbjOMGxlSiqakT1GlH7EPTDa+CFcVZ3ONs/thDhY71rFZrmb\ntcKgVGjmHUoNA3RdjgweuY55kuKlrI/FhD7vW8YAFz4QDgADKKaVRBJBEXwH\nTkfbapGRi3CPCJS05nA9GAP0NSUl9k8tLZfYw7BYk1Bms8CHC0n/RaZu+J3z\nEJVCGyiosapw4IKNujJppywQCKvC4EafAkdsssjzBQ33mvGjefMSk/brMKcf\nFu8baFCpttiDy+LhRNkC2iNn3Jk5uePq8sa3ICNtZQn1v5g/4qm8KZPsb3Nu\nlykezuAUCBVPqodRschW5UE/WXODGPvGKUwoLhzb/88dWoadRuejWo9W9QJT\n9E3XRf3CXSzUBOHj48A1nosHQo6iQc5aOf8fFET53M+uYuPBdyEDDl+a7zwY\ntYrBYwq9UwcIonkZJdKLJ4gUeak9GbsRncTOWIiSdw0OcUBcSfvP5mN+rdDP\nFfE5+Ye4v51bkgEy3x7rSpZnMKxEvWJDDrN4lCxYdlmO5B9rmvUkD+UtzkZ/\nbnK+uKL0uJ4Nij5L+Q1UvxBMjRwXBd4R9vFA7+D3bD44kGgTAsOa8GOik+oE\nDMlFRz9b0vnJ2XmfRP6MdN7oHin7xj30oMn0tESmacfcaxwI78IYq5bJkuZL\nRSGK\r\n=Hxg2\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c188b81de6f5c14dab3912e5fe9175276e3480a8","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-10T20:04:15.491Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.6","@balena/jellyfish-logger":"^4.0.15","@balena/jellyfish-metrics":"^2.0.19","@balena/jellyfish-environment":"^6.0.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","@types/pg":"^8.6.3","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.1.7","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_12.0.0-rationalize-db-connections-c188b81de6f5c14dab3912e5fe9175276e3480a8_1641845232987_0.6924443245055991","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"12.0.0-rationalize-db-connections-128d785ef0df69c3b0c482d2b8546c0ec1a6422c":{"name":"@balena/jellyfish-core","version":"12.0.0-rationalize-db-connections-128d785ef0df69c3b0c482d2b8546c0ec1a6422c","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@12.0.0-rationalize-db-connections-128d785ef0df69c3b0c482d2b8546c0ec1a6422c","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6f5e5521636aca51431629e283d83f26286db724","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-12.0.0-rationalize-db-connections-128d785ef0df69c3b0c482d2b8546c0ec1a6422c.tgz","fileCount":215,"integrity":"sha512-4b5x77Xa7BggVgdYaunA1zOXMfwKutsmDigqeqIAbCiOf9NQ9EL7UxYsYGRHv1LhlLbI2XktfdoExVI2/6juGw==","signatures":[{"sig":"MEYCIQCnueqhshoz96QvWPXVl03zkAvyasaezYhAm0HKrxlTkgIhAMtIF0cgCSf6OAeFYgvsMksveUttHH8OzVAzsolb1+Sp","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":793482,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh3JQACRA9TVsSAnZWagAAK5kP/1StUYVr18kR+ABmLj5e\nnzHtmLXJUAG67AlW96VIJZlYzajNOtomtwhzrDLTdDCp8TnWNZvo2QB2hGZt\nwaRkWtlta8K1HvpFN9yqYRtT9XxDXfLAT94LUawD66PWqYs+4xfBUa1vUpVv\nFiAVXaMpTrfzGPXIIfcS+u+RyY9f/+cznSVOlLyiujYvdawU/ZJ0FZfYW6+e\nSYdldt+jFOy8NaU0LR/xmaed/cDvcf1URkZnyUDCBB4Q2lUCU7RLH+WsrquN\nE7zb6YL69ms9DUTtoH1nxCW3CniGSRbiehfDlzOeHf4ojEtFdY/Oz5Rgza2z\nhbNCkcQqMvfoG/49zO8iC/5qVnTawW6sqlABdx6o9SMg+YTFF4SsSDM6P51l\n84rCBOKqRgJ2Xu5i3thtyajvhNGh0D22grdXdzfiZCEX7GScbNsuvGH2dwaL\n73o9UOWUaJRezeXWawysv+OG2797j5b+5GlQOp2FEWL6NjjhzvDV2VYfc+b1\nklU+cXf1c85BDl9fATfW3sC2R7/lB8Hs/gDSAqdlFpPX4iyZH7OnEQ/e/Fde\n49XDX0g42LTvztS7VSYQv1GmtQ4R38HRirGf7G1xjYx+6juHDdtV4VylmAD9\nYW/d4esPV5fLSilfg8BsoaOwJp+ZEXDyp4IjfUMMXBZ8ywVgMfA/qPmcXO+G\nqnTE\r\n=sH/9\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"128d785ef0df69c3b0c482d2b8546c0ec1a6422c","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-10T20:12:35.864Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.6","@balena/jellyfish-logger":"^4.0.15","@balena/jellyfish-metrics":"^2.0.19","@balena/jellyfish-environment":"^6.0.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","@types/pg":"^8.6.3","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.1.7","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_12.0.0-rationalize-db-connections-128d785ef0df69c3b0c482d2b8546c0ec1a6422c_1641845759885_0.16848339995649986","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"12.0.0-rationalize-db-connections-bf563b7f06ce9dea78f409a50e39ecdd39bc9cf1":{"name":"@balena/jellyfish-core","version":"12.0.0-rationalize-db-connections-bf563b7f06ce9dea78f409a50e39ecdd39bc9cf1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@12.0.0-rationalize-db-connections-bf563b7f06ce9dea78f409a50e39ecdd39bc9cf1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"84a7082c2fbcec7b8c8927ee139b15c9a51b0450","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-12.0.0-rationalize-db-connections-bf563b7f06ce9dea78f409a50e39ecdd39bc9cf1.tgz","fileCount":215,"integrity":"sha512-r+H1Xnx3oRIkXwONWkUcLk4Rajw/XsWoiQJkjZ32UbZjJEORCwg6ZqwD0tyUq6AzrHzYcHMIX1ypqMhED3S1Vw==","signatures":[{"sig":"MEQCICjlSEtLxa2AyrF8/egV0cKVws+W4v8Nd/O1mXxsFsWsAiBKnEv3woGRq+NA+r9Fo6Paj5APwwrM/Md5W5aaCQQkig==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":793500,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh3KOfCRA9TVsSAnZWagAAE4cQAJh1ycTnyZo8yzJmUpNw\nmQxHaU8hP5FwP3/ay3JY8xpAKPRcarZoimAnbJn4yZ36TAjcGFXNNwqN9xOe\nI3OBqRzgGRpnYInNaas5sfnyQGybSTd+lCKPi938HlLBF1wBhTHV/9v626vG\nvxImJWR3rqu3BenXG5XgMOTiOGj9iQuPKB4Z+McCpP4yJ03I4SYz9oEplD+k\niW2zSu0XmEki3pYJRZt6QjdiO2QJDI6EPT5ScyJ2Q7rcP0y8LbyAj2c791YP\nO9+n+G/Iw5RGZTa8SujZnVhVmIliZG+PkOu6LSzbAD7mlTZTupnokkS11XsC\nkZks+exOyVa1IKwKYTIz4LZz7HmCgWoUPUQ8f29eszPRFqZTTbLZI27qWz9W\n083bjYQpcj8KlkJJwd5fzE0IKE1emICrRkkcoyEq8RzNhlMUKWQKxSoWZxkt\ncDJqSOl72IpixEOazNIIsbO/ihyY2npiFSggBBElK5mFBFLpoFcKlldgsM4x\n3+kyG10pdwovmFjOW3ldx3CKA5Rnc5T/wgZIaXZEvmZswePy8Q8uM+58Imo/\nIWn5e3i8O44PH3m1ieH71rojCo4CKqgkD34TcUfGLZ3l8lBxogNL8hSI6e3G\nKD/XOC2JwD14lQw3PMo+mVMpp/VAmJIwrKxJ9xlDr45Spqm9bYHziTyWI8I/\nUaH+\r\n=mVbL\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"bf563b7f06ce9dea78f409a50e39ecdd39bc9cf1","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-10T21:19:33.882Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.6","@balena/jellyfish-logger":"^4.0.15","@balena/jellyfish-metrics":"^2.0.19","@balena/jellyfish-environment":"^6.0.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","@types/pg":"^8.6.3","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.1.7","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_12.0.0-rationalize-db-connections-bf563b7f06ce9dea78f409a50e39ecdd39bc9cf1_1641849758817_0.7299307318700037","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"11.1.12-card-to-contract-26fcc8a6ec26d34370a0841822be4f718ffcc72c":{"name":"@balena/jellyfish-core","version":"11.1.12-card-to-contract-26fcc8a6ec26d34370a0841822be4f718ffcc72c","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@11.1.12-card-to-contract-26fcc8a6ec26d34370a0841822be4f718ffcc72c","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"41a09b2e49969186a29e727dabdca3a3ea9a7f1a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-11.1.12-card-to-contract-26fcc8a6ec26d34370a0841822be4f718ffcc72c.tgz","fileCount":218,"integrity":"sha512-MvqFsKbdHymcFkikLir2JRCVNSyWBzZSoqvisl3zuc7iQ5t6k4VHIkofPvbmYGkcYfShPk2ApAAlvMoKKX/UTA==","signatures":[{"sig":"MEUCIQC1T/uDJvqIFd6eD6HTjuGOTXmkXMEu8gJJ7uNPgQJG4QIgcpiWq0MWbZlUUNUKs/luqu65xjJZa7ckpVTRBOw703c=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":798705,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh3Wi7CRA9TVsSAnZWagAANIcP/0GLy8zuxbCF6IWbFoLs\nr5P1c8GzjqHGX9iLg8A9oNiQ3VWN6vgiMMJkRy7TG5SqDWviVUysgiLioe+F\nx44go5YpPBl8BUehJjd0iuVCXcTDBFbHzyIn3URxbn9m9G495OtmxIZzJhnm\nNcbF9UnPLSm3/bxdrzC6WwuoFulgea4sM5T23Ixh8ajQWOChnFgjxncqzi1h\nisBNNo4r1pL57scilvhmM3WS5ouy5kvdc7+VEE/PQpVSyiTAmVgmsHkrwaay\neybygvXBnGyLufj7M2I9p6kbsjPCbQD1sE0mVjjwy6tAZcufm4fGy1v2H3nL\n3rBC8NLHjQLICJKTNFd7FmbkldMaXmXme3Kc3Lx86HDM/OM2CUZpLBUouwzy\nuUGgecH+da87s1Cx/Z5t6Ylou9aZFIzjqxcSvLq+PLFIe/gXAFkNtegEhJu1\n6ZCttnF8PegDPeMCg00t669lhWLiVrVLHXL5I0pDvzpCNMjAAqYeoOKlTIEi\nXKF+silVPMVrpPk/JQAt7MqPtNxbR0CeqfifHttwqYbhfvD+3Dw+4Q3u5IAA\nhFuAqP61gMOwN0NgCYa9n13GlMSAHJxz6FSL/lJdAvyeazdMl6e4LThv19ep\nejhY2AcLDrsEnOuXLEFnbFQe4FMTTOHlCaaQbJs0oUcgz7+O6Z3eP48Ht8CX\nhC3x\r\n=BO+H\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"26fcc8a6ec26d34370a0841822be4f718ffcc72c","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-11T11:20:54.476Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.6","@balena/jellyfish-logger":"^4.0.15","@balena/jellyfish-metrics":"^2.0.19","@balena/jellyfish-environment":"^6.0.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/uuid":"^8.3.4","lint-staged":"^12.1.7","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_11.1.12-card-to-contract-26fcc8a6ec26d34370a0841822be4f718ffcc72c_1641900219553_0.431548545164373","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"12.0.0-rationalize-db-connections-69134e4eb641b1f52cbed2f7fa865717ff71a231":{"name":"@balena/jellyfish-core","version":"12.0.0-rationalize-db-connections-69134e4eb641b1f52cbed2f7fa865717ff71a231","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@12.0.0-rationalize-db-connections-69134e4eb641b1f52cbed2f7fa865717ff71a231","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4fc6349602c3e6911fb021216d5061bb79646f53","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-12.0.0-rationalize-db-connections-69134e4eb641b1f52cbed2f7fa865717ff71a231.tgz","fileCount":215,"integrity":"sha512-LbrevkkUdBWQQXCRPc5jPEXOtqLkbjG8ivCgA1FaTn3KCq30ApvR8L9sw/LAufEWJGvJQFJha/ah+Ej4iHCBCw==","signatures":[{"sig":"MEQCIHVFLzHWvupXjHE0jxG3Z62wzIGQSZ0OHSLQ4bc+ELpeAiAwm/wAlsQsCzYHpW7bvTAdVUIblqHxldI5rNXsXQ5YIQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":794112,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh3YcbCRA9TVsSAnZWagAAd0AP/3XeWIA3qM+8WCc0AZYP\nxb1oSZ03tFOTcOubqUTx5ZAB9QPwkYBmWAI1UpEgmWr4D2wToZKgLbti4iDB\ngoEaK9zfhH1VOnKI9vWlvVV+0dbAhYgASb8cAFSdSVB+xOJhSeXNgrvmi8ZI\nUrUHd1NFeVLMqsyhJ047uTZ8XHdjSfOjYUMXGDbZU0TRze9CjcXuouk6hPSj\n6+EZDsSW5iVJN/Wxz3vbEqsNsffoV1/6J77ZLPFzeroMAQiAejMdGxLSkPPm\nxcExyc6X4X+js7oNRwnTHWNkYxCxTR+IHNkADvuKRS7sC8WY20a+ZzvhZoJJ\nAc/7YWvtrmSnQbjMAl5947K4WGq8kbcv8w1tcv7UjM8tLtf1ivZDWbvv54HY\nUoeotu9rxch4a2RHhjfR7dgjQb9GgY/nwv83fqlRv93iBbT/OxyZ4H6HmT56\ni8SIVoJbTQIZcHXsPAx2KDJh5rXr1L21+PMh3OWdYlvK96KzN0XYpHGMPTCb\ng6FpTngjnREDF/y1nX0hVm6Low99z1dC/aIAlegzT0/yNTsAgLjYkcSkTZI/\nYwHGOZZ+kXbYq3eYNobpTmKDSvypZuUsAaxmuNObTQ7NN+syAwI4FA3reTSY\nJMUNSqrhD7huX+fjk6C5NVXADkCQx8RwYZw8xDq+P0a6C97eVD6f1XY05a+O\nNQq0\r\n=D+tG\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"69134e4eb641b1f52cbed2f7fa865717ff71a231","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-11T13:30:08.426Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.6","@balena/jellyfish-logger":"^4.0.15","@balena/jellyfish-metrics":"^2.0.19","@balena/jellyfish-environment":"^6.0.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","@types/pg":"^8.6.3","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.1.7","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_12.0.0-rationalize-db-connections-69134e4eb641b1f52cbed2f7fa865717ff71a231_1641907995194_0.4274586724330496","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"12.0.0-rationalize-db-connections-e56b4a8f7a561ffac61ac5474d3a751b5c5c9497":{"name":"@balena/jellyfish-core","version":"12.0.0-rationalize-db-connections-e56b4a8f7a561ffac61ac5474d3a751b5c5c9497","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@12.0.0-rationalize-db-connections-e56b4a8f7a561ffac61ac5474d3a751b5c5c9497","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2cd544f3e0f241ac5d7bc154e997f4330db857b5","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-12.0.0-rationalize-db-connections-e56b4a8f7a561ffac61ac5474d3a751b5c5c9497.tgz","fileCount":215,"integrity":"sha512-MmdDfuaUtnM2YzLvpvVaA0iyfAciFKV3XA9vt9bhbUVYlOfjlKWspfFljeHo8Ud61nNoahYbCvs6c1+FYyYdZQ==","signatures":[{"sig":"MEUCIDJoYq4Q4Lum/kIbOiTk6FIJD3y/EAdNvnxtuKRQbcriAiEAg0sj+Ogjyxlmq5TT6g4kgdK5tQjrH71k3yUbyc6/HMQ=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":794278,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh3Yw0CRA9TVsSAnZWagAAEuQQAJwG8VYK9owAmi+WPJb+\n9dwsYAqiNpZm042SzUqUcFuhkEf2PjTALKtKMURIvoKCGeRBaF/yD6cy47Di\nuvOtKv1d7khdCT4gJVRMl4r9AzitxK7Ij7ICZZtYAoQEMeaRo1REz7KCQYIy\nwhYbNXXI5bwUmmUbjpiLY0AA1wS7ztklwwhvYfOeJ3J05W2YUW6/9MNOLdHC\ngezU4qzqJd9ua6bgbCGfiiGULGSUiZ2jomJ1DQM7nQ5OdfSXJ+hPUKYwaSxT\nJ4beGv5dXoiIoyJrteLexlk/VN6JSMCasHMJe8BygwUO32j/B92228+gjQZc\nSunUGqGJED0+yU8wmsS6x2+DzuVnXug4GL/SafHAIxzbP4zayRcTVvnHG03C\nCeRoftWzyVALZoEAqiKt8AzfPiKiLtn33dNu7Oild1MFlLHj9GBRfkaVVblR\nWiplt4NQEL/xt3XhvNfJXuViXgaYTdnXGWHB4ZuPuRVKxnYIquqAE78rLGIK\nkYJ2z1zX7EgooRKucx7FW6uOj0aAKg+uEiezeIY8zWtg/dF2mpzdD+8oAK04\nt9Eduk13DNj2B77QYrPw+9DhyYrh9CTjkoJR881MuARQ7fx0G+KIBPgCZLJV\nymE6ol6+LCqssy9jTQEJGRyE1gXCQMTff0VAT9UUFkP446N1/MymsR3umAMo\nQazL\r\n=v3hq\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e56b4a8f7a561ffac61ac5474d3a751b5c5c9497","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-11T13:52:20.931Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.6","@balena/jellyfish-logger":"^4.0.15","@balena/jellyfish-metrics":"^2.0.19","@balena/jellyfish-environment":"^6.0.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","@types/pg":"^8.6.3","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.1.7","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_12.0.0-rationalize-db-connections-e56b4a8f7a561ffac61ac5474d3a751b5c5c9497_1641909300519_0.48223382987144436","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"12.0.0-rationalize-db-connections-d5445244710f92e735a9ef9a549bd4366d86feb9":{"name":"@balena/jellyfish-core","version":"12.0.0-rationalize-db-connections-d5445244710f92e735a9ef9a549bd4366d86feb9","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@12.0.0-rationalize-db-connections-d5445244710f92e735a9ef9a549bd4366d86feb9","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8b21a8d17b5fb1a078889017ab12b83b2abb10cf","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-12.0.0-rationalize-db-connections-d5445244710f92e735a9ef9a549bd4366d86feb9.tgz","fileCount":215,"integrity":"sha512-ZqX9nEfNuHFeyh166E0evZtGlFv/zqO+qpcYNjGoDGglvPjo7YGZ4RhSmp01wMplk6IyEkJvlFGGcwkbT5Jsgw==","signatures":[{"sig":"MEUCIQDc4uEZOpoYt6ylo/dk4Kvtc9KAg8OnflhK78CTF7RdfgIgCExEaOd1lCcBy1U9hnoTKNTZ/SkBdRYU54aoPtVQXH0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":794274,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh3Y+rCRA9TVsSAnZWagAAA6gP/1A6qkayvDqrWXzjZST/\nEWAt8Nvbeq6P/+L6ytRKHEFLHHzl6gsvWuvOdBG/2Aq96r8/wlIhr6cY8cqS\ndze/AAlTRYueMqN/IrVbZTOpOPK6YkZJ6WG6RoAL/6MdFUL1HVzSjEtOtnuU\nZjhkh9HMl+8iqIQaC5iW1uXV7I3m7NblX3d8dMMhfMF0RUyQRXM5SnNqxLBB\nQJbVhPq2wTagQ8tA7bQWCrnaGncyRl8y+8/VX0HesCCH0hrYQoY84AiandYp\nIqO/oyh3JAc7ODUibiCAV2Mptw0U2sd/H2AlM/zASM1V2izyFecWrXhrGWqQ\np79+HSyy+sYODcdYvAYjv/16cYvN3G+B+GKLC4slO547FnCZMolQVYO15hIs\ncdG0DTFA+Bvzm9b0hZ77B5NWOv8n5/kA39gNsGS0Rb0S9tAHOisidWc5bPu6\npbf9OlXx5qIgBdWQbXopPhnDkBM5iFqtkdinhK7gUr6BycxifstK8bHkH6pM\nDUqW68fDmQb4JR0MQFbuVxfSd+lrIvLsJIWLtA08FXAh49j0jhCAI3QabWa2\nkPzmTUGoDQuGz4ByZ/LGyijpUwNnIvwtoEelzAl4O1BgjDWhjzADWZUZCvh/\nzC65Eq7Q7f/IE3Kf2mes+glBVEeoVdMw0IVXYRmoODlVfhRjAYLTW2xYrmiF\nkEh4\r\n=17Yd\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d5445244710f92e735a9ef9a549bd4366d86feb9","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-11T14:06:52.904Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.6","@balena/jellyfish-logger":"^4.0.15","@balena/jellyfish-metrics":"^2.0.19","@balena/jellyfish-environment":"^6.0.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","@types/pg":"^8.6.3","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.1.7","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_12.0.0-rationalize-db-connections-d5445244710f92e735a9ef9a549bd4366d86feb9_1641910187742_0.3221425432209344","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"12.0.0-rationalize-db-connections-f7802eb14acb700cf73fb1e9f08eb8281bb4ee8e":{"name":"@balena/jellyfish-core","version":"12.0.0-rationalize-db-connections-f7802eb14acb700cf73fb1e9f08eb8281bb4ee8e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@12.0.0-rationalize-db-connections-f7802eb14acb700cf73fb1e9f08eb8281bb4ee8e","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"fc6a69721d17f5ee30f2c2e3f0ca8caf8d5fbb6a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-12.0.0-rationalize-db-connections-f7802eb14acb700cf73fb1e9f08eb8281bb4ee8e.tgz","fileCount":215,"integrity":"sha512-FxkI+eBmvfq7ncJb3KJT34/Bv/ZLHfGmbvtwyTvXH65MwswZMMYoLYIq9IoF0FLf0OF4JF+MeOxXEYNN7tGtNw==","signatures":[{"sig":"MEUCIQDJQDQjkQw/KPIZRBk2GrMH7g0rS1b4UF38l+ABQXcIPwIgZRuSdiRYI5Ik571R3tKxsni0MNl0oKfOgthejq3dmEk=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":794275,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh3ZLDCRA9TVsSAnZWagAAaG0P/A9UKPQvWeyke8DA7UH0\nPEALri8hYg7/mDjWOdcemOSDyTpcc9XYRHNRlmkvBgbHyUDV27BKIkw7EKlQ\nlgj0bQiOMURfmsS/54JNJbMCURVQh0Oja3uEX6Vmt740q6CEH0dagmUG+fwV\n3QLzKH+oO3fL0Tx3wvaNzsq06hVqlht2JOkFBw1RuLqHJJ4DmnwNhuGMk5CD\nMGvJFD4D5lkpfrEyEDMjgdxMRIP+3CWLr2tu+VXWpwGvJN2ZH00AFo0h8/3F\n8jvGVoCa4RqSkaghZlN1N4q7vHDfppQXiDoaHh7udLWCffVRWEG6KVHjlKPo\n9edI3yHX1NUmiLRdPTM6sU9/XhpQRRxeTVbmtcPADg4LvAzY6Jdb9q9tXkGR\ngeU2wv7sPUljuBKEzxoNiCGaYm5KiWXkRROJKi3M8YjaLNqXjJvNSXFmj3Jj\nEtu7u1tXYkRIOe0+az88M+X7QOA5w8REUAj1uRrTru6ZkRY+7nPp2kdbJsZ2\ne+hjmH6QNSxEQMhoDtnTlUJPESzmm41jUs73Bjw9sBZl+3Tv5QtU62aU4dQN\n5ivOPSXsHlORBRcYe6euOdp00o4PLaGjYYjradPvk8wPpWx6vAMENO8rhBBC\n0K2oXtn9ClryEuJLibtnJAN/pFcCfUDhl7TrCZMjkhNuvAaNqUHb0eQNFR+0\nvKwg\r\n=Ne14\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f7802eb14acb700cf73fb1e9f08eb8281bb4ee8e","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-11T14:19:54.378Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.6","@balena/jellyfish-logger":"^4.0.15","@balena/jellyfish-metrics":"^2.0.19","@balena/jellyfish-environment":"^6.0.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","@types/pg":"^8.6.3","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.1.7","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_12.0.0-rationalize-db-connections-f7802eb14acb700cf73fb1e9f08eb8281bb4ee8e_1641910979535_0.12369536791373359","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"12.0.0-rationalize-db-connections-41f458b874db826bc9f94ad00a70e35d8eb101f5":{"name":"@balena/jellyfish-core","version":"12.0.0-rationalize-db-connections-41f458b874db826bc9f94ad00a70e35d8eb101f5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@12.0.0-rationalize-db-connections-41f458b874db826bc9f94ad00a70e35d8eb101f5","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ae7f1429c78dfb6e7d4e77e147764ff555fbaf33","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-12.0.0-rationalize-db-connections-41f458b874db826bc9f94ad00a70e35d8eb101f5.tgz","fileCount":215,"integrity":"sha512-Oo45klgy+Cl3SZquZM8k6bDcOx2C9ciRe5nGFBAvrjaphBXQEvohSJCVYJ10wqKhMs8/6oWUnD3WqiwjtZSEAw==","signatures":[{"sig":"MEUCICjgP4amNLMgRW5DuXOeR2VWNigdw9PyCjK3VD0BVijtAiEA/r67dWLTXWlAN0/CXi0yHX/ZdsagBMsP+3FnVRbL+nc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":794275,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh3ZWoCRA9TVsSAnZWagAATiAP/1XEFqKJajf6rDbkBYIi\nF0v28QThitid2tuNyKoja0dnUjFTeS38N7ZaI3y0Uz7oTBh+GNVO/HNsSwCH\nd8+1ZENM17syUZiJAMpVCLMzfex1WBR4AcqeTLQQP1ZG/dLXSJV5fa2ImI3D\nIcR1HqZ0qX4Oyzhjtgt9c/oYaKaGXgWpuHRZGGhJAES5/O6wS1Z2CXyikbuU\nTa+5yNT+yi6MbuS5Q8UGOCS42Ga7SAtGC2jwguqn9GFzVqdzXDqlk7Qsh5x6\ngyRJyjc5GJ3Z6k7jelsRm+bzMqOR556qUttixYbb6V/sZTXsJ936PuK/D+lB\nQfm/TtOwqJ3rrrHAZXY7/QM3F8wBq5iG4TNaCEQA2VQ/SdtPd+LGXFUD+BV4\nYAsDUL/C7c7wfhgOBQAahGHvkDPAvZHY8Xr6nk+f9Xy9Be9PVkGuBbYFgyOC\nYyR49UBIULsVRrFotrvYmX+2f5ZIR4wT+XTYKYO9IUJi3wEyxXoWBZ7H9hWs\nRpo9MD52cNTQfQymL/l4epeLWCF2xh54DWCZichEQNEN2d1Zc7UhrG+wMkGV\n1Biumv1pG11AT6ESP5aX8uFb0WBTfKh/Duie7r7D/aebaSu4riUC6g2av6QQ\n3BSYa5PeQ2BvqGiC+/10XYFC+UCNUca40oBkr+/C5dwBTVzUR0CfGcIVTooa\nbTyD\r\n=W8mO\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"41f458b874db826bc9f94ad00a70e35d8eb101f5","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-11T14:31:56.924Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.6","@balena/jellyfish-logger":"^4.0.15","@balena/jellyfish-metrics":"^2.0.19","@balena/jellyfish-environment":"^6.0.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","@types/pg":"^8.6.3","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.1.7","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_12.0.0-rationalize-db-connections-41f458b874db826bc9f94ad00a70e35d8eb101f5_1641911720293_0.7160967070813411","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"12.0.0":{"name":"@balena/jellyfish-core","version":"12.0.0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@12.0.0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"cc1914bbd3bcf43e1b5ae4792d6a8083a46cf94e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-12.0.0.tgz","fileCount":215,"integrity":"sha512-Q5zNtqAmYmk5vQuAtN1S919+n24g57/cVv7SAEuoe2LmTIaVADFbcwXy7QPvshOwqGQjIOqmo2FzrJImUyyyCA==","signatures":[{"sig":"MEQCIGni1nsaVBcBQjbvt3zIPpZGRi1gFFqSsMF/DU10LThKAiBcKk9mZL11jqQP/WUCKqUrmTkoFgwMhFNZ7CPWfJgARA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":794207,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh3ZcVCRA9TVsSAnZWagAA5hsQAIF36pNEDJ6Kw3PNgKUN\nI+WZPchtH+tqRNttbAd1GcVsot9v2USS9ehFkUsMFDYMr/7wgXkBu7sudLQY\nxDLz9g8vqNS8QJUSzfe2fRK42MpUAlKetPzMfWCTl7at5vNUKv0EMwCPM4Zk\nCxD9YxtivbVDuRcR4Vuyr2gZs/3m9t/dVo5lIOhYR/SsSPxwiG7N1ucRcWm6\n/vw4hj2B+6raT/Nu6bVfbwi2rr22bWXuVIaf8D+0C05f689qPBAlpXAAdSNk\ncuHmOV3pQD+pxE1eocnAWCGm4w8wQb8pGBuvBADJN8l65zhuIYIpyDyscvtv\nAvK6E0RNU7W2dN1X5VlrrferER0NStJOGQOqIowGDCrNoZKlRV16T6g6a3J4\nDKl1l558U/jvHRMTv0B/LcoRfMiLUmb4KnTcY3x9Ssnozkxo8vkm9Suyp/x9\nKwSfRdrT3l43/D0ZdH1p76vnaKh93xpEl+MqDNdXVX1aQ1O0ZDSN1M3wxup5\nEVvr8MlV9vL6e5Br8alzuX4aVpKWH8GN60VrFOJmeU5i4f52yyl77YHBTxRO\nxVWZT+A+yyxuVbMnqKSXE+WrPOlzx3vu+V6dHVXD09ceX/QPcuqAXJw9UCOo\nJcOjioSXgkBOedpbfTbbXo83zfpki5xCxtrPkCaDJ+4EBUmxxsfkvKN9338c\nEx+l\r\n=CFiG\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c4acb3a02fb92e737964bed73fe9b277a268a0ca","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-11T14:39:23.826Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.6","@balena/jellyfish-logger":"^4.0.15","@balena/jellyfish-metrics":"^2.0.19","@balena/jellyfish-environment":"^6.0.8"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","@types/pg":"^8.6.3","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.1.7","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_12.0.0_1641912084993_0.6146180384834916","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"12.1.0-pool-test-utils-a6df7233954e2c068b151223e0b9d28bf90c93b7":{"name":"@balena/jellyfish-core","version":"12.1.0-pool-test-utils-a6df7233954e2c068b151223e0b9d28bf90c93b7","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@12.1.0-pool-test-utils-a6df7233954e2c068b151223e0b9d28bf90c93b7","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"cc879aa7424098ec333ba5559e76555e60f0d770","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-12.1.0-pool-test-utils-a6df7233954e2c068b151223e0b9d28bf90c93b7.tgz","fileCount":215,"integrity":"sha512-qJWQGnfDyCAz86POfmIt69Rg8jeuEcmVjcUYCCS/jTclA9M/5nM4GnmL3LUzo5KOTjAR9I0DkLuSmoBQJDIoZw==","signatures":[{"sig":"MEYCIQCvWqSWE382ovg72jNewDysJ/AxECCx53nXAz4t6hElOwIhAKpz9cvNdRrBbAjFqG59zpQTlEcQPcv2vLn7DLzHO79W","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":794430,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh3bZOCRA9TVsSAnZWagAAi8AP/RQYgPEOU2nOI1/CKrWR\nZeQQpHcAtLK9dlxmXRB2v5G0RqxXYZlCRZtxDO8iGP/+UpKCBGXrglhso0K0\nY1zIkdTSwSNlIEzBL15VuTx9NZyJ8eah6Tn6qWMGpta1udbM7EVIFcDFGV+A\nzYxmWKwr3d5OKxIencJz3s7/Lp0Qpqw1ZZuOYKYhf4xne3Lo4rjjhEVyiP4q\nd0/XYTyvtfc8th2zUuAQ4GKBoI2bTfbj30OYW1zsgTLZ+Z1ISxuvthU96XWH\nbZqr676qi6m3Jtaw2MA927qwh7h0sBxrtML8nCE6N7x/ESu8UdyxAnH/pFzS\nomssDvOPAkR5BMOOtJJAlZMx3haC1VhF2EcjrJa/W1C9BmYUAJTUHm9yxu0G\nak2vxbTx45CpKNduzAHtHCn3cPFMu/nYHrf4KxJRRfbzfr30EPU6ZTJEBhTf\nO2l79Frzxg0fx5DAAc92XnRIHvySY3vlcrbmlX3EorovIfLZefK/2IYP2TmC\nb6En+A4C5U8Yc75J4Nb+Wyv8IKjBqs4b7MJ+WIVlZyaZYLUG9Uz5kDdH8FAF\nW0+USW/WKR3MsgeyDVTSctDDI7IXHs/Pu1yuaStRYe6ErAa/xLAIIEm4+b1d\n7IZxUfJo4QtrvGLWiX2sjnj4N5+bh0W9cz++XDS8mE4AVwrnYYwKDy0Macae\nbsuS\r\n=hdDR\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with cards in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The card data model\n\nEvery entity in the system is a data structure we call\na \"card\". Cards are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery card has a `type` field that specifies type that the card is an instance\nof. Card type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" cards contain model definitions in the form of a JSON schema. The\nslug of a type card is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type card for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/cards/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a card of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a card that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any cards that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nCard updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser cards model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" card in the system.\nThe data that a user has access to is defined using \"role\" cards. All user cards\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a card\nof type \"role\". Role cards contain a schema that defines which cards the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual cards to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a card under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a card, a user must have access to all the markers on that card. A user\nhas access to their marker (which is the slug of their user card) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a card with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a card has no markers on it, then the card is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview cards with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view cards with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a card.\n\n### Soft delete\n\nWhen a card is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive cards.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nCards can be linked together by creating a card of type \"link\" that references both cards and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual cards by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a6df7233954e2c068b151223e0b9d28bf90c93b7","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-11T16:51:34.312Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.6","@balena/jellyfish-logger":"^4.0.15","@balena/jellyfish-metrics":"^2.0.19","@balena/jellyfish-environment":"^6.0.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","@types/pg":"^8.6.3","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.1.7","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_12.1.0-pool-test-utils-a6df7233954e2c068b151223e0b9d28bf90c93b7_1641920078741_0.04969101968224576","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"12.1.0":{"name":"@balena/jellyfish-core","version":"12.1.0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@12.1.0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5209471c5f9c9828862e9e8aba841a235dae2265","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-12.1.0.tgz","fileCount":215,"integrity":"sha512-AATolIsJHOqYv3jj4Pcdj9/uwfDzBY+uH39xBd1/F6DFKGIJAHgMkWod8OsvDdBJbY/KVxsWHEDzghZbvkaORw==","signatures":[{"sig":"MEQCIBqiNNzP9v+z0hsnjM60ilvs2sbteY3pTTMcgQxaLDLOAiAmDOrLqnhDd1N1RWYb/rEmVdavUUou+z//nAAvMQrUhA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":794373,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh3bgPCRA9TVsSAnZWagAAwdQP/2GBYP7zKvB0+oZcQit2\nktvNXMkstaa/qckFerj2iR8BHS30ltRlnHoWGukvguElog7+BfdJeayN4mE+\nQlKfrBYw71u1oW32NuctiPXWTbCtKB59Mbdz3o3Vg8ENfQMiMvBr9yUd5Q/X\n95pVgiMN4OlabvNcIbe7kvFfFfjOBIVLnfNfi+jAlruJsX0slXxfw6L6fphI\ncgpg5pFGevTKZYHP/1YR3TmQz6iNTc13JjFDlcuEY+pzoqP9p6BniZXZr6S8\nwFnvqex+Vwq0UjDWRMB7Fu7WRK06nuj4RvBS2+LfAcIAJDZhi12EijSdxyUe\n2Ybu+p5vrcFKw2Vhd/A+vokXKGzfhyQ857woihAYbYtNiaQfeWBwT7hI1aBU\ncTaUqa8xuyHWeA8P3TLVWLOCdDNLxh9tYM4HdnVtN26EdpdtoehsgOCwXLof\nOVmE38EMu0YvVvyC9FFJMx27Q9jVVYZibdS/SHjIdKuI4HlHwp+qM5lQous3\ndIW8mVRwp7zCxOZdHiZ/LE4ZhGr1HT5M2DXVn/m68mJVvSx+DNlSLpPGmBsF\nAD+Da7gIsOpVAxl0i98M92oq7q9hWD0f6dvMVXa2jbhzQFLswZTSEXe3ejOS\nhTwVKdvNoQZttaUJt1WLdQS20WaySlOUzeEqimTBdW0bTqGhz0TywuTRGIrl\nTzlL\r\n=niBf\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"3e60f3dbcc793ba2d0551a7c194c64d0f5ceea2e","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-11T17:00:00.391Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.6","@balena/jellyfish-logger":"^4.0.15","@balena/jellyfish-metrics":"^2.0.19","@balena/jellyfish-environment":"^6.0.8"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","@types/pg":"^8.6.3","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.1.7","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_12.1.0_1641920527635_0.5003278619098535","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"11.1.12-card-to-contract-00b8de434ecd873b902984eecb28aaf7771225be":{"name":"@balena/jellyfish-core","version":"11.1.12-card-to-contract-00b8de434ecd873b902984eecb28aaf7771225be","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@11.1.12-card-to-contract-00b8de434ecd873b902984eecb28aaf7771225be","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0e26997af931893e2b69dbeb3ee02ac0a5d688f9","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-11.1.12-card-to-contract-00b8de434ecd873b902984eecb28aaf7771225be.tgz","fileCount":218,"integrity":"sha512-stixt+NmPj1Ch1YZDzI+02aKVv5KmY5lgJx+eIimtRNBworNOyDbHVSQPR5WO0tPF2bOdL7CsMvYoEUJzM5eGw==","signatures":[{"sig":"MEUCIFeYTcEvsJbm4JZqz/732cG5M0MPvfqQus1WPtgytqWYAiEAtjRqUU8j0YAdtG11OyFOWndFxw3gBQ9nUJYcExvjrOQ=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":798705,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh3cDiCRA9TVsSAnZWagAABFYP/0EvMT4+QnyfYlnMnyZa\nu7YwhyZhY0T/d2LGRz7e3y5BdAnDxPiHMGpkaeuQKpBctfOSS0UWroWKSRbB\ntaIHT3TRn/dXRD8Rnxihrw06C/ypdPCzWN93y2iRDcziY70L14myXuFNchEL\nBMj+QRBMQWu6QLoeMlsaxkhLgQIAIXs/FTlOF9N3HBp8Vy6sf5rF+tAQPHz0\nOiHJgn039xcAIlsChYluNpyqq4e0Pbf3LOrbZKPVp2JRSnDxsJF7HawHbiv5\n8d/iT6ZACuHjkItUtduCSY1bLZLm6t8TJLX9bgexX/Q/fivizt8iy5kTj679\nJrd/bm/TUgRos+xbG0HmCJhC+N5mIhdAuXzMFZltR3Tj7PsqMq26sJG71lDE\nue2eb2PybS1Pq1EinkVHG3zBEiEh3ZHYsbF7Mc8Y8L3uDIfJgfMXTo4Ke53w\nrqGIraJ2C2iKN9k2E7GsHSJp/Bp3LIFmZchA9jEYAFVHQtT+i65wJZXEUUI9\n4sYW5gCqxeu/ttcuVXyQFZVWbp8KRAFozLT1Tak6M/95ZrRoXu1wKjuQ5b4C\nDyldJuWkf1BWc7J10gUSehRA8zubJGvb34WABsguw3x9xSGpMCZGYB7w0pwO\nuhJt/RTFWcNvniLP34GWcnCvPfyeSGLhEp0OFZ2UZpW5xTswokTtSnTVlAAu\n1v61\r\n=e6OE\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"00b8de434ecd873b902984eecb28aaf7771225be","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-11T17:36:28.253Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.6","@balena/jellyfish-logger":"^4.0.15","@balena/jellyfish-metrics":"^2.0.19","@balena/jellyfish-environment":"^6.0.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/uuid":"^8.3.4","lint-staged":"^12.1.7","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_11.1.12-card-to-contract-00b8de434ecd873b902984eecb28aaf7771225be_1641922786492_0.44439359631758557","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"11.1.12-card-to-contract-2e3116b67e5999d4c9f75a8b87ac45e86a0a6569":{"name":"@balena/jellyfish-core","version":"11.1.12-card-to-contract-2e3116b67e5999d4c9f75a8b87ac45e86a0a6569","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@11.1.12-card-to-contract-2e3116b67e5999d4c9f75a8b87ac45e86a0a6569","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"200c8936735e508ba6977b1721d09dbd8a89790a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-11.1.12-card-to-contract-2e3116b67e5999d4c9f75a8b87ac45e86a0a6569.tgz","fileCount":218,"integrity":"sha512-f3hy9iihTAcm5jlEQICDwGFZ0XWI+0pUm6DHpKYUCm2U5LmlhBLkvzXFLbc3Ka0KqoqJ6qjAUpI3CAT42JXT7g==","signatures":[{"sig":"MEUCICPRRNmTsWXf/CjLfF0i85HaOGrqU3MsCq6kIyQxaXnbAiEAk3eP6VhCSvsapPy7zotGRJQaNo1PZKybXjcdZaI6C2w=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":798719,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh3cfeCRA9TVsSAnZWagAAPIkQAJOVY2J7kKOGK1EtLUpP\nqr2hKn/ey568Fib+KbUOEEZ6qb1eIJwQK/vroyjcNRNxkre9gHoquEtUmnk2\nKAjWKRKdI9WApbDJMp9amkp9ZlZ77ru/NhJ9kgorJjlafGitHeIbaipaReDx\nW+S9qkaMKIpdWUnLmNqdjZWiqFmxCR3IvvhEJ8di+hDs0tLM3629TuBgP0Mi\nmry95c1j2SKLXqgHWbkH1XY21fDxq1tTyOAAJFW197f24Bm8K5q7x7EuesSo\nU3XdpfznZRXERXmYfKN+OA1umMFfJCUkai6rzgaU2YpduVc+EfHonL1JWySN\nRPEOV7k0YJvu8yHory6320HH59ROVzxr2hCyyMcPYo8pfgpwxnsxAgi2gMZt\nARPbdZIdA6lqn8T9iM1vCHdWtDjCbZ6j2wosbvRoUqJh6nrztdnOvmM7k3Jj\nDCwPYYs4w77n9H7XlFmfvsgK5t93JjI9ZCoV5IkYCC1PiLX2gMNORarOPrVK\nBzLr2u1S8Fvx5LDYy7z8hknoNPYEmCzFJtl7+TVxOP9YSPkxR7rAq5lt2Do/\nUo6Bdq4DiXsCJoF7MVtuI2n2B5bcIcVM8TyeXiFV+c0a1zNeYrkupHxu+mLe\ncqnMy5e+zZkVxrHJaYIkjKOR3xMt6dxPFMkvgnOTStabW/l/USTdI6EJq5S+\n04W0\r\n=XT5h\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2e3116b67e5999d4c9f75a8b87ac45e86a0a6569","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-11T18:06:12.348Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","pg-promise":"^10.11.1","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.6","@balena/jellyfish-logger":"^4.0.15","@balena/jellyfish-metrics":"^2.0.19","@balena/jellyfish-environment":"^6.0.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/uuid":"^8.3.4","lint-staged":"^12.1.7","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_11.1.12-card-to-contract-2e3116b67e5999d4c9f75a8b87ac45e86a0a6569_1641924573944_0.17322186277552643","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"12.1.1-card-to-contract-e5f1b30618dfc6f103b2e8577e7e4d0facff557c":{"name":"@balena/jellyfish-core","version":"12.1.1-card-to-contract-e5f1b30618dfc6f103b2e8577e7e4d0facff557c","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@12.1.1-card-to-contract-e5f1b30618dfc6f103b2e8577e7e4d0facff557c","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"465d720ba28c9ba2e76c6e01678cf6e67313e8b2","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-12.1.1-card-to-contract-e5f1b30618dfc6f103b2e8577e7e4d0facff557c.tgz","fileCount":215,"integrity":"sha512-oOTBGTMuoD3Yf0ZiP92JG/WNMTL8X7AfoGwb2QWNYZFywuTeD9OUQVsmZifvJ2vC1q+lSp9mCSu3stVdrurMcA==","signatures":[{"sig":"MEUCIQDLap7KHxWglvOuqP9QIl6NMEYsrPNZvbrsimyzhsuHwQIgWrB+BMujhGmhBC8a00y0sK+hpbQ4EV2Cb4LEcqtUUQc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":796170,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh3dY1CRA9TVsSAnZWagAA7EYP/0vy1pn03nVKpFfTNIKx\nGdn5NCtRslgX32yggIdiWE3hVeQky0+YgLwVzIl7C/racmeZ6AD2MsA0PHJt\nDSFwdCy0EK9Yp0iOZopNWX4DCUHaXs3NPb15HEE9/KMZBYE206vMhv1lG//o\neB+SDdNFAkYHloyKU08Genub7bZJV8mVUi27pfRk6VytVW6CyM+4IbWRRXaD\nIWmUF5P0b6MWivw1eXy2ZdXIksMV4rCqrxg45sa7p9VOZ1oy//PLw2Q4B/nw\nV6b8hxOwTTSYKi1lHMu3sUJX5fd4VfkE5OjmiwkdjWynsqKC6fvOptK1bXpZ\nzkOMxYLe9dxFXIuQsu3+zZ4eOFo0DoKy0Ch3qRfKBebHQzDrb6zuNnaUcx9d\ndsS1PO9a1oaVNjzCuAxAbe8ys4MS+1tSC4e0NF+COoOnlTqehGYLK7PgcuTz\nctpbL3/YYa62yAgOceDEnUTe1Tcg5CSG2S04RS6N4ie5pZ3Sh4ock+uzzv4y\nDVztzNakJUCDR9QwPhRyq6d6SEf/tA3yai/msKsnWl9nuwLv3Lgx2HNQ/gCP\nb2WfFHqtogLj9j5FBS2LyTmHwFLDBCUOORScwfYhMdA+BmUzDQUzQTjkr19C\nWS8DiJKM8/foD5AT7sADd4llhP/qMmtEcrF/LkpPJXFc9k9SF3LP/g0JUQy6\nW2NA\r\n=M/kW\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e5f1b30618dfc6f103b2e8577e7e4d0facff557c","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-11T19:07:59.394Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.6","@balena/jellyfish-logger":"^4.0.15","@balena/jellyfish-metrics":"^2.0.19","@balena/jellyfish-environment":"^6.0.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","@types/pg":"^8.6.3","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.1.7","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_12.1.1-card-to-contract-e5f1b30618dfc6f103b2e8577e7e4d0facff557c_1641928245621_0.5700952052685773","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"12.1.1-card-to-contract-b270245786b6e7b4ab32e3929655ec7916c5847e":{"name":"@balena/jellyfish-core","version":"12.1.1-card-to-contract-b270245786b6e7b4ab32e3929655ec7916c5847e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@12.1.1-card-to-contract-b270245786b6e7b4ab32e3929655ec7916c5847e","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"68ec6da29976b43654f60e730e75c55bb810a55e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-12.1.1-card-to-contract-b270245786b6e7b4ab32e3929655ec7916c5847e.tgz","fileCount":215,"integrity":"sha512-BYWRI7RwI5clv6RV7PI6aQdB7+de2dwCtgmOKVGRRyLlE96rBkaw9PiI8sLiad3jxVVCD76WDw7OC2oNDOzRvg==","signatures":[{"sig":"MEUCIGkpJESAdBLzvDE2nSPPjwCcC+tv8aSo7j1NlEkl3TDQAiEAjdNhBC2qPl56KLtCAO3eCMSozAWja2rGoNuEUXPjyys=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":796142,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh3qhKCRA9TVsSAnZWagAAMVEP/11Hl1x74rowenDi6EgI\nj8/SBd1veECl21ikCULvt7lc4hCn6+X4mtIekCC6RqunBebCyMuenLEBIHON\nb8numyXffWGIoWWjJfRhcSQLMjPx8qxN5/G9T1L1SkBlXPxZK7Kz3Eszza3p\nFRNG228+afMPwtUTP//XvcBHDZLnwRn7vv4H1tIQod6S0IAAX0roCsYrnRQE\nTE+PAJqlRgY8uOeMgiX+AlaysNmfRH5fO2QnK1mctQyqW4/WFRml/McMxRzA\nkZYUT7AL05EO08KcGDyHQdaFEudNIzJjbMoJUfMNDWKelH7FYoXQoXKFLhWU\npvO8BeOz3NzofRGEcC6QCRmGqezwSDQUU7MCjr/kV/zEWiarToyNA/kDy0YF\n0ltgz/3hM0E8YKPdX0HS2LItoKTzfQNgDghl+Yv7xgVZAqwH9GTYoUKYvJ+q\nhitQwB94zmESFDMyOGDpyvTHA+adwV/5ltvRYi/o3BHK/AQ9CmpCFiK9qgXG\n7kFzDGLqEjILFfswnnnVCYLrmID1CJ8af/jKJ7HXwRcpKWZmFfuE1/SnIgZv\nR3aUafWjIYsLwBaykkXl4EyNOixJO/Xmz3Z8vKTZMbk9Hy6a3tcYQkENRspd\njws1n7nADKKwjq/is80akm8pzFt6OhwAr+b+I45MEnDEZ0JZwgUaN542fZiT\nqX4j\r\n=oEo1\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b270245786b6e7b4ab32e3929655ec7916c5847e","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-12T10:04:13.352Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.6","@balena/jellyfish-logger":"^4.0.15","@balena/jellyfish-metrics":"^2.0.19","@balena/jellyfish-environment":"^6.0.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","@types/pg":"^8.6.3","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.1.7","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_12.1.1-card-to-contract-b270245786b6e7b4ab32e3929655ec7916c5847e_1641982026009_0.693308797197941","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"12.1.1":{"name":"@balena/jellyfish-core","version":"12.1.1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@12.1.1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4a2aa601b3539cf57670e7cdeeb9d98fb8ef6b47","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-12.1.1.tgz","fileCount":215,"integrity":"sha512-2pbb7dtt2opYBVN1PAH4CVcobxaNp3wPi6xCiFNtUCrwMZEhPFF+wG1tkcANmVXRKLMbHaJlITbxx7ZeblXjng==","signatures":[{"sig":"MEUCIAz708VQj8i4KGdaPraf7VhQGOUQnGNCldcpGmdZd1OEAiEAxaaLvi5QvEv7p/Ktc5QiDG0ehxdz5fTxU4FfPAkh4JI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":796084,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh3smGCRA9TVsSAnZWagAAjJUP/RHCJ/PZKWGRZDiYcoJi\nU9kRmamevECawBRfl7YoCJAxDWJBqVJPx5OSpIqe57JnTczeux1p6N5p0C3p\n0lE+kZV+s7HEOFWK9qq0ekIxaCu7slplnVniRpJcQThCB7FiG3o+v2S+hz+O\nVqjpbFCY2XEK1D+7Nq5vwq3WVUvBOy5TJILEeMuib5aL9Qzc8xZtCOUQ0nQT\niBvGuj+SpGiqkYO1SV/Irgj9EyJEEhM/QXvA+zm4CcGC47SmK6yTg9iS826E\nr+x2UjfdHa4Zei9hY1uuilVhQ/M9Q/el2hD6saWjJK/aa6jDq+Qfat8WIJPb\nxVLeDM2i1VLdf9ufx0Wf746WZZg0CexUshbOenhMW0j/7WFm9X+AiT/gTa0Y\nDUbAj47fTJSq3PB+4Y4/4nw8KxcTuWpB9uUQpwzGXWnlfWIXHkmKe9CjDylL\n81S2tdDKm9Rms9h2R5CuAkXPWpmBpJkrAlazfE1kIYgQtJHDliXeknG8mdtj\nHRYrS2uTG24/bObUBkGkzhXong6srLsz1b2vzhYWZ7Mgi4Hmj1GysqyFJtdS\n+JPRT2KWvg2+OpQQJEVSjiDxCQAbPz199TcvaR/AricyqvpgKDRijHsT3BB8\nSDS6cdIADVLtj8/yVvwA0kCcvhxuxc3dZxhu3MwusntSWI2RT0ik7oyAtMLn\nndXS\r\n=kmUh\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0386b91bef4f6ef444efab341629698e0c26f365","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-12T12:26:45.551Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.6","@balena/jellyfish-logger":"^4.0.15","@balena/jellyfish-metrics":"^2.0.19","@balena/jellyfish-environment":"^6.0.8"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","@types/pg":"^8.6.3","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.1.7","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_12.1.1_1641990534257_0.5554186272529908","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"12.1.2-renovate-major-json-schema-org-2facc93423802e5e633119e1770caceed3d16950":{"name":"@balena/jellyfish-core","version":"12.1.2-renovate-major-json-schema-org-2facc93423802e5e633119e1770caceed3d16950","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@12.1.2-renovate-major-json-schema-org-2facc93423802e5e633119e1770caceed3d16950","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a93b35eb6ec4d84111834861ee96d67fb031379f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-12.1.2-renovate-major-json-schema-org-2facc93423802e5e633119e1770caceed3d16950.tgz","fileCount":215,"integrity":"sha512-SCExCQzNlqbIutmFrXBcyGJwUVwEUoB1AuT9YgCHm4bySk6bLvNnnFRjEDnuCKHmQ58KsDhL0OtFOctncBXfmA==","signatures":[{"sig":"MEUCIG6tgeYiKHsaxc0a+leCmcO2jwr49yB40ll7vUwbhhF7AiEA3u9N4HRUfCw2+zlckkJoW7eRLjDbEOkqL3LLQrWJzQc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":796252,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh3uFCCRA9TVsSAnZWagAA3IAQAJVe2HO9LcUqkMVyTdV+\nedeYOYMwrAd9iyc2FGI4W2FKZ7ZZSjO6FiODiOfEaV5gAc6LvlrhbdcAmsFw\nXFGlR5enriuldoQydPZXWnwTdJehl+P2BXKiaRP53VyMEEB8Q+xRjf+o9A+H\n/SobO9LkJs1F0xkMaHznGMKpv7Qib3F6OsZunIfmVRR2CSKigPeCHjvD028X\nbWVXuWajJEi5nOek9vWKhBTbBBfm93Xw12MB6ONWIHCxjLa7bzGmR1CuAtxl\nhDqyUIQCB0A3oXRsFNupfVU4X/yvRsWGDSj31Pwqm53D5l+48pHhirBWV2JX\nZ6bd3exbKv0ZBOVpiMBZbHfe9/pMNE8OCQI2i59zYw6wCFbRqPJdkAfoUWjI\nF22Y541dmZsAQBypgihldi9HtU5jpVRcVE36Vv5aOTcbSNRbrftgER+cUGih\nObR/VCSDfAOBh4kjDELaTSboeCxaX6b4iLReXKL1S/YNuBIHjYnJxMG4iZZ+\nHG0GYDhsXcNPGip8I1C5zpnyHVSjBGF3gxTfP2sIv3ua4H6Hbfa6oOdkVCxV\nzLvh1y/+6B2crLV9ItPuf6+w5oaDpbUl40h1LnGMobZgCJMZeIKafINpMaUc\n7TbUJW47ohjHxI2wyhr3hTgz7LsmsPPQDcCwMWEjjCgixI4sOmiBlD0pFqZU\nj48Z\r\n=L/sJ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2facc93423802e5e633119e1770caceed3d16950","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-12T14:07:06.065Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.6","@balena/jellyfish-logger":"^4.0.15","@balena/jellyfish-metrics":"^2.0.19","@balena/jellyfish-environment":"^6.0.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","@types/pg":"^8.6.3","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.1.7","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_12.1.2-renovate-major-json-schema-org-2facc93423802e5e633119e1770caceed3d16950_1641996610222_0.544884839556959","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"12.1.2-alias-public-interfaces-card-to-contract-7f9cee9d3f23b14586e42dec62e395ec95529e24":{"name":"@balena/jellyfish-core","version":"12.1.2-alias-public-interfaces-card-to-contract-7f9cee9d3f23b14586e42dec62e395ec95529e24","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@12.1.2-alias-public-interfaces-card-to-contract-7f9cee9d3f23b14586e42dec62e395ec95529e24","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"68eddf2d6fd4413bf9a8ffaff81eb8314b733186","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-12.1.2-alias-public-interfaces-card-to-contract-7f9cee9d3f23b14586e42dec62e395ec95529e24.tgz","fileCount":218,"integrity":"sha512-KYezM3WUh7lTk82+0bXAgBgRn7LREe3rS4ijGXCjHKmkEiBCWK+7xofPAutZKIoNfQ3OtxAATOcPrEtYEUBPHg==","signatures":[{"sig":"MEQCIFWQKdf5U694rv50UoNCyMv+Zr0Xz6g7theMjQtAYhI3AiAdPRyoOi2qZM4AIQfBkbQSAvnO2Wxe+omFdkGHRiKRXg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":801690,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh4a5rCRA9TVsSAnZWagAAxHcP/jsqWqU1N+E/GUg2apxI\nCuAwEFAL9IexWe274Qt/Y1bgrSImdrsFHR9+afmA//VHEUdelrdPEMw4A9MN\nwCBQckfL5VmCe1IseReil4QpxIUcvP2di/DD4yzAG+VAeUJECVIEaXyouvCG\nlJlY5gpsWLPhmeAlJo93z02+iKOEDmAneV3NuComB6uOzHOzUyjn9EkzvBhj\nLRVULoWJMFbXYR0PAbyBQBa31ASBGlPhgiy+EB1psxp20GvlRdMOEn7RenKO\nejhpovu9Aso5Qcu8GXxfOKCii6kGmraHiu2DPAByrPV0SgtnbjN2YwqvMsGW\n0DfSkngC5hrj1R5EEYSaIvuWnIH6+lbpn4qN8c4xpRlMSNXP9iL5wV5Ipo/l\ncCLoWiFfHV/qnzZsB4ucrIQNQpAxTCfgo8/cHfEkzKRE9tXsEYvAQZMNl2UI\nt13r/fE2JoWIQGEqAlPS9eI1jUAo4Bzc8InEuD/+g+czbyaevMLwXyE1MAl2\ncozXKcotfjuu2gYj1m7tk3UYzLRfR3MJGBUQLkSPfK8G17U+3kLkQv2y2ifW\nJfDokApShgalzVfib3Vmu3Ib/tnjuoHs9cNrcOI25/PhzVjVJPGNjdsiL8ZB\nmgRHTZcpP7ZrQO2XD14pwTDlzn1UPcVDT3Zh5t/au6e4/uswFgf4NJhmLvfv\n+6u1\r\n=dVzC\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7f9cee9d3f23b14586e42dec62e395ec95529e24","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-14T17:07:17.382Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.6","@balena/jellyfish-logger":"^4.0.15","@balena/jellyfish-metrics":"^2.0.19","@balena/jellyfish-environment":"^6.0.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","@types/pg":"^8.6.3","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.1.7","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_12.1.2-alias-public-interfaces-card-to-contract-7f9cee9d3f23b14586e42dec62e395ec95529e24_1642180203229_0.34225509978839286","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"12.1.2-alias-public-interfaces-card-to-contract-cd8e2d905131bc2513e8a1db2934f138f0518220":{"name":"@balena/jellyfish-core","version":"12.1.2-alias-public-interfaces-card-to-contract-cd8e2d905131bc2513e8a1db2934f138f0518220","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@12.1.2-alias-public-interfaces-card-to-contract-cd8e2d905131bc2513e8a1db2934f138f0518220","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3ecd512733d215a6b22539bbf87437ec465e53ec","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-12.1.2-alias-public-interfaces-card-to-contract-cd8e2d905131bc2513e8a1db2934f138f0518220.tgz","fileCount":215,"integrity":"sha512-KTcM204oBFtkDlp+bHYw+FwJReWRmBdGzaI8vwpnut5bs5+Wq+ozf58g3BOgMnsbM2By9ABT3upuNYMOzRRzKw==","signatures":[{"sig":"MEQCIE8BkQrrE05u+2tH+w2W26o0++Lc1pf8uRBfQo5LxDLpAiASfZcs+bBxJUCTaY41tR8ErHUC3gydBX3JHi8IJT2YWQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":799751,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh4bUvCRA9TVsSAnZWagAAPfIQAJute4Jk/PmtQKWOnCjw\nInQUXB7vwrYnKADQIr7dp6kNDz9fMtEr7jMGfSzDh96BG0uEehX4mGSXwXnE\nkbFre0gHretKqep4Asr8aeQ8XUSYPyxHHC7MfX5kjMs+OCMDP4ixeYXwRitL\nV5p6dq5yyKG1D8aG1G6wFJcBY5t1a95rVMQBVNkKaY/Ndch/+ko1zo4V4Z99\ny9tUxz++YQKSYmbRRLW6CU5Lo0g0MyBHjL3VQicGxw1VL80mtck46raeKZkK\n7PA6jLFaymSaJgR1UCTt0em8LnjY62cytu3POs0qqB69E120zyXnRaHRa3SD\nBDlUUSNaFjgHtRtBtL8jo6PgouZ3WEsW8ogaC+2i+qPWHFyExtupl6G1ApDC\nkfxf1GvD3N1XJxn00TBiaUb3ywN17hgAkPMfNtbcDmzXD/50klU/cVQbZwSN\nUZdY0q1MkCNjLu/SuvAudiMqjgPFMSK3QeaBfzCm2yyiy0D8eUImrW7LnctJ\nOPodOPHNxLPkdn1b7J350ps+5nE2LG1ZoIViJ+nObXcTXcPNZ9pnjVjjrsXB\nmSGSS+H2xyRiGB33pcaD+mRz1ihgvtN/DEvYaMl2MCEp+sEGbAQnY9ou6uFt\nNXPKpaCtcO8NOt1kUegHxNlOhRcNCJ5myRrNrUxX7BhUVvjr0lHemYEX2rHq\n7OUK\r\n=iLEX\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"cd8e2d905131bc2513e8a1db2934f138f0518220","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-14T17:36:10.615Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.6","@balena/jellyfish-logger":"^4.0.15","@balena/jellyfish-metrics":"^2.0.19","@balena/jellyfish-environment":"^6.0.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.2","typedoc":"^0.22.10","@types/pg":"^8.6.3","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.1.7","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_12.1.2-alias-public-interfaces-card-to-contract-cd8e2d905131bc2513e8a1db2934f138f0518220_1642181934975_0.8303652099703838","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"12.1.2-renovate-external-non-major-e7bb4f1cf9033611911b383e9fd418bcc998a20b":{"name":"@balena/jellyfish-core","version":"12.1.2-renovate-external-non-major-e7bb4f1cf9033611911b383e9fd418bcc998a20b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@12.1.2-renovate-external-non-major-e7bb4f1cf9033611911b383e9fd418bcc998a20b","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3c40d2de4c4180ac9ac79f9e300014f3f93aeea4","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-12.1.2-renovate-external-non-major-e7bb4f1cf9033611911b383e9fd418bcc998a20b.tgz","fileCount":215,"integrity":"sha512-oB+JwXTbO3VftMNAILIkfEAcdQqkPL9s79wNs9Nh1f7YHGxJeLOYxhZfBX3lPaxs0a6uXox2U9/yXkWhnhBcOA==","signatures":[{"sig":"MEQCIBPA3ruQUmzMgDTS8bhCrT+wwT4jnZOGnU1LEsCa5/SyAiAVako2sd9OzYysjDc4tPo5Yvna1lhFY4AoYdTQcUBZuw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":796228,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh4hQ+CRA9TVsSAnZWagAAG9QQAITBLQ6PJbhP7sxuc65y\n5eVdvbGalo4xyOUTcFaXDvYSHKBuD8FsQy/WkwJQL9VVxtvsAJY5/kYYM9SS\n54A2eLpejOBNWNk55BmfIVacQuzP3xPU2oaEUDTTcVeLTwpwiyRMmOb+WUVv\nH6FO//IaoC0fp9pg0SB+99xfLSmTthHrnFuwZG4XAczFCbW6pazhaIaLBUJ2\nsDJuxBQdZ0QOH3h11U8li6d9jGqefG7ELeoj9Jct7zYVRb64F5VCF/qqtNKz\n1pqknHkKEuFTW5/QyBad1aBvOugQxLc2iwAW+BFPeFwv5H0KGI9ntJ7uZSFw\n1EqTaWF3LEY+06t3NyTxIC4cyNhcAu3PFq9u7Ytvuyyup2k0NRFuH2edOPsl\nN572Sc/lOfxwiIrg69byhRUANaDlSmssy7hDrkITNTO2Bb78TeXA3vWW3Y2k\nrSHoBG1Y5dQOd94NWOR9cXfZ3jhs6HbYiGpXOwOZVbBYkz8ZSF7S+XYq+M9C\nuZUGHxTOfQWc/CrkzLlqdJzhTozrGLnTlrOhKJatDp7r384dkYQg00nSQSi2\n+X0kmiSCTrB5GHW8+fOcTvG4fp66e6338n/MXIC5I7wJNUNeGIn72JkIi0g5\nTW3KEXVXMD7UNiKaCvZkOW8SO4PdVOOwKQ0QrnwBiDUMfdpaKcZW+YyqqNtZ\niU/5\r\n=obZl\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e7bb4f1cf9033611911b383e9fd418bcc998a20b","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-15T00:20:52.473Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.6","@balena/jellyfish-logger":"^4.0.15","@balena/jellyfish-metrics":"^2.0.19","@balena/jellyfish-environment":"^6.0.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.10","@types/pg":"^8.6.4","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.1.7","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_12.1.2-renovate-external-non-major-e7bb4f1cf9033611911b383e9fd418bcc998a20b_1642206270748_0.35044802711410483","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"12.1.2":{"name":"@balena/jellyfish-core","version":"12.1.2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@12.1.2","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a9df77705736d94f3438ebbfb7fd98628fbb0839","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-12.1.2.tgz","fileCount":215,"integrity":"sha512-+8f0aJrQMDWHGdWi4J0EXtvEUrPwoYoh9mhkBx+0IOtEsIyTLo5mcP9Z8OpilG0L8oh6VzoG4BjxqlEBr7ufIQ==","signatures":[{"sig":"MEUCIQCuns61tyda96728CbWRdm7CFIZx6DvUmP8VyasXL+i+AIgUt396g/13pQuQPCIyqQ6N2+rVc0+8KilPJCmGV053qA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":796159,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh4hW0CRA9TVsSAnZWagAAhUkP/A9i/3Oxjky/TaCo5Sh5\nhFtaVfrGutqTSvCl/nQ4Ab5tIuKZVZRRZxSQ3ZYmdjDjTbBlK5a9msOUW7ml\nSXv3R6OHrTW/0Vy6D0Hhnkvp1LKk16aOgpX6BWZLzTkSdj4nBtAoeHP7dM5R\niplUHZREoxjotub93YvwIsDrKt2p+ylQyftt3HLZvmVqz1Ohcce1XvHIT3NS\ne8y1RcWGnVq/0cVDkmm0WDy/cIbsc/NJWZkmJoNJDwsXyo2kZZbr1MXJbePG\nzYJoed6F0WFdA2fJpwF9fSmgaxgADdECW2m6HJ6RpZ7kl3ZiMNZpocuQJL/f\nEHMV6oXr43k2KVLOp3Ll1djrGgDJPpxqfFGTq84d33fn4rxfupHHocujichT\nVWmDgdyjoAW62u32HGyYapHTkExIgq9/Nv/U758NwQsKaUR2Q7m/04Wrdzac\na4vZaTmvP0jl40DbFy4DKcpURApEeN0S6XEgkh5zNOIBaeCSf1ZL4dCdgE99\n7Zn9LGazTvJ5/0N+ddY4NoQ+57jwRW3TUOHiQjXaSTH6+ADacWw93wU7sJxh\nvHjt84PTBKMIK1Scv7GJMZJNSZfArGP5n4SY8FWY+RjjE2LJLyidZ4E6EzRa\nzP1Q8Kyr6uoeVGCqmzVr/yk0lxA++aSNNjkwscqw+GRmNeu4/Rp8n4yAMMZN\nSNDi\r\n=kYaK\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"153a182074691581725a93e08b69b77d054853d2","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-15T00:28:42.649Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.6","@balena/jellyfish-logger":"^4.0.15","@balena/jellyfish-metrics":"^2.0.19","@balena/jellyfish-environment":"^6.0.8"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.10","@types/pg":"^8.6.4","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.1.7","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_12.1.2_1642206644023_0.03561622147790189","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"12.1.3-renovate-major-json-schema-org-44674aa1d965ae75f33a86db9684e6dffe77711e":{"name":"@balena/jellyfish-core","version":"12.1.3-renovate-major-json-schema-org-44674aa1d965ae75f33a86db9684e6dffe77711e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@12.1.3-renovate-major-json-schema-org-44674aa1d965ae75f33a86db9684e6dffe77711e","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"da0f99c0a1716cd3b246db6dfe9cc4ed34492068","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-12.1.3-renovate-major-json-schema-org-44674aa1d965ae75f33a86db9684e6dffe77711e.tgz","fileCount":215,"integrity":"sha512-LtGRT/fPyBYr3i95oVnq4qOHTUPSvh+CxHuNmbfNTWK3rsYk1mvsXJkIwq/l1iljGopAVJmLlMnj396qJ6tkDA==","signatures":[{"sig":"MEUCIH7g4BXw696PwpSACC2PWnnnP1f2tbxWOgVkKjfKa4OkAiEAom+nrZ7mtfF1G3TVus2o1Kc6+AhKES+HDmPvmqO9cAo=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":796327,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh4hZ6CRA9TVsSAnZWagAAk4oP/RgtbOnVg4yREc2NpSyb\nej9WcCG1JsKhVid9tWkUnCR/9iI6G5kBE3w6gkumQsrpowLP0uNpHngb9D3D\noFqAUV7HWRcxwL/rUZHU/GLElnXrbZy66I/A/HnyAwc+Jyj7OIPPCvy8HdOK\nEkT6EOcgoiv9uNiFATQRHGT+f90gTao90sxEMms+sn9TqRl+eFvdr4/HKUeK\nxMfcwCCG6W8pxP9u/P6j7VduXLjzSLQwwIf0xpdQw8CKttqpBRQ4yRcu4BuD\nWbqR70L08gowlT0gFQTV7eg+H48bqVXTYPdUd2jfBBdkQBmafc59K09oyML1\noDBGMjBOJabtY0GbIMExaNYaRD9q5OY2TlxcecVRU/UVuEjqtGsfupkKAiya\noRTWdMe6AsQODq6mKq1NvvSEXpDhjQZzYtWtCkunw3v19FR6jZS8alx8glOi\nUFNhPT45HT5SHA7XGXnCf3okHqLypIXNveKwpDCFKFWUoRJs0wxv8LVjUE8S\nb6HIzKzoOqtCqdR0P6fqSP7Ree6UBMiw5xmR7jYkZO3L9LyaFBcW9rasftbR\nNvDh9W0QfQkih7io4F8iMUe6qRFYiw+ye+hdpnUkczGlSV2pKEA4ggZ7Hbkq\nZSPcwW4+VY1p2WZgo/FdvcbI3xqJl7Ia3aVa1I7Ozq4Nx9bI+NNiJ0WLT5BT\nSEuC\r\n=aLHA\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"44674aa1d965ae75f33a86db9684e6dffe77711e","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-15T00:30:16.736Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.6","@balena/jellyfish-logger":"^4.0.15","@balena/jellyfish-metrics":"^2.0.19","@balena/jellyfish-environment":"^6.0.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.10","@types/pg":"^8.6.4","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.1.7","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_12.1.3-renovate-major-json-schema-org-44674aa1d965ae75f33a86db9684e6dffe77711e_1642206841767_0.3322939714415376","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"12.1.3-renovate-internal-patch-878476e50a75b35686e0803dee220eea81c7b722":{"name":"@balena/jellyfish-core","version":"12.1.3-renovate-internal-patch-878476e50a75b35686e0803dee220eea81c7b722","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@12.1.3-renovate-internal-patch-878476e50a75b35686e0803dee220eea81c7b722","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"450647877cd36086e98e3b2b3a86b04df754b88f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-12.1.3-renovate-internal-patch-878476e50a75b35686e0803dee220eea81c7b722.tgz","fileCount":215,"integrity":"sha512-L/4QdD40e4LD0YeMwhb5MCxynw+6ftpRayFjV2zXNMWIg6ks39YNRfTtRXCUygDk8qcPK99w5iTKtjYWmFEtQA==","signatures":[{"sig":"MEQCIHOuB8PumIFtkfaPSXhomNmt3iLmUwof1i7oFFx/zVvTAiBAJK/DdhUvj7DcSd9JRYNef4q090VGknzDy3RVQObV3g==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":796328,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh4hdcCRA9TVsSAnZWagAAYDIP/AlWvcx6eVQSB09OAltH\neVK3DrZkdi5GEjdpzSj0B+gYPPb12eYDCNCVMSir/aPjabsecSiV2Ejb26bb\niB4niVkC0IhBwCy0sXcOTBpcDhyDPtdotKf4K3Ib4/ffEQ/jeSJXW3LyT7Ou\ncnjM/kVD3cEJiVwUPtrfSqAIFPuUkt3iteqg5n6tFjSgMLMRi3+ogBUmFChu\noIMCdGjNYpuKojJklYSz19ipANWGiTjf2PGyP/hOZaC+5xmHhoHEySBeX1nR\nOa/WXPY9VW5mvmx5xy1K6fj/50CQ23mcGdsW7y7YIDLTGNOejy7XhNd/XBxk\nfyc+QNhZ/BexX9GNZ1spZJO1op5LWOoG20b3/+wp48KCOrYeT2/CcFBPY8oX\nnPtaUJTW3Ht2wWQGsiRL+iY8Ofa/nidCcBwqHYyjVHy+QpPf/FuC0FGmJ8Fx\nU3yE0eqWSP0678kz0qpGqnCj8qIrUT6QuMb9kkDBWDPQupr3Z9SLh1a/IGGz\nQebuOXy+U3w1HLDH3kINivnEHL6GfusjAJRpJ9IZLfFJ+Fb66OQhwsfzws05\nY/oj2+D2jmKoZSMLAFXq+WoEhqCq5yxwdgH17O1dtYURTDcomAEOFe9hWP+o\nt97+gh8t+X6R/hGgqz1eobiA8qbBbN2FR0MDT8VBRZoscAjZ04nl/GvV9BlT\n63ZB\r\n=4Ysl\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"878476e50a75b35686e0803dee220eea81c7b722","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-15T00:34:28.597Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.6","@balena/jellyfish-logger":"^4.0.15","@balena/jellyfish-metrics":"^2.0.20","@balena/jellyfish-environment":"^6.0.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.10","@types/pg":"^8.6.4","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.1.7","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_12.1.3-renovate-internal-patch-878476e50a75b35686e0803dee220eea81c7b722_1642207068354_0.3048831139146355","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"12.1.3":{"name":"@balena/jellyfish-core","version":"12.1.3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@12.1.3","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"cd2051854f23655ad20afd3f778ef85b79d52093","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-12.1.3.tgz","fileCount":215,"integrity":"sha512-zfwhDtj+Agn/bLxNm79IfvG8yC1D+y9ZYz5Znw9h7W0hal+L1qA6tvwM3Vmm19q2M0CDtfPdwtBfsjv4ucEbfQ==","signatures":[{"sig":"MEQCICYYB0rno44X2PNxRlFfEp9AEdaA4iIXpfHucD/Nj4vzAiA7/EiPjuo3YsmstcaAZeo41pMrDV6uFjbqv4DRqYFKyw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":796263,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh4hkYCRA9TVsSAnZWagAAbLIQAJHX+g0xVMF6ZnL2uCix\nkkJmjNbCeZX7UDTZmcIchr+RD/T/T6FDxSIjc5az+6tu7cNwBatg+Mot+K2q\nNl+OETAznjOEbtLeZDiL8xheESGLmXSKFnq+Mr7ug6clDBqB8+AKVqhCZYrg\nZnW1poA6gEYeHDJWJb7Va2yioLEGQs6WiqygVDmgUu0u0Mwa+Y9bTjQK9m03\ni2q79i79+zyWcHdfLmg3vFvmMaCJ8NVnmfmqGZjwfLCokrK8ZHc/v/5Ts8Oo\nS8SOsMF2eHOUVL97isvnJavBhcIO1hAeC+W0yf/Y0wn5tThPCAK5rElALOWj\nYuaTMiRKDK6UZNWuSMAA4o3uMO2kzSNfZBtcqwQBVEWBqHPc79XjG54Jx6jG\n/6aFWclke6huNSE5faOk2YrWsu86m9r21DYnWvhnUCABhsvhS14MInxQYni+\nvIxDdlkiFiO2do+ueBaH5u+Kh95RwqNJpgomQd7ulndziHuIKsmdKRsyaP7X\ntfUpttcPuwCOGHbKGjd8V8wMX7GrDUVQpzlsKE/zsCM65qluIGFwgekdt59s\nRB5Im86LjpwFOb7VR2gptgSa4n3M0vC7E/opjr0lfJpItUX9NMAQcwwmxGv/\nmdvFkwGPxirshiSEdiRDccNS+lOuFDcEe9xTDWehhPgqZkBBiFsPyFv18Jct\nmNRp\r\n=nFMh\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7cacb73fd3b22b88191de1ad4e19d564f4bccc84","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-15T00:43:05.302Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.6","@balena/jellyfish-logger":"^4.0.15","@balena/jellyfish-metrics":"^2.0.20","@balena/jellyfish-environment":"^6.0.8"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.10","@types/pg":"^8.6.4","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.1.7","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_12.1.3_1642207512587_0.6191834107345457","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"12.1.4-renovate-major-json-schema-org-e39d7bc287c45514ea9b674a68683adbd287694b":{"name":"@balena/jellyfish-core","version":"12.1.4-renovate-major-json-schema-org-e39d7bc287c45514ea9b674a68683adbd287694b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@12.1.4-renovate-major-json-schema-org-e39d7bc287c45514ea9b674a68683adbd287694b","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"622079fa80f1d62dec71e19de29123668bef682e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-12.1.4-renovate-major-json-schema-org-e39d7bc287c45514ea9b674a68683adbd287694b.tgz","fileCount":215,"integrity":"sha512-KL5wtpyYLdxktVMtYayaIdVCEgVdDFQKjNRtonKXvGeVwW/boj/tnSXrgHkXkhOP2jF4Qt57FBMac5p9uCqlVw==","signatures":[{"sig":"MEUCIGNCpDx8uevqW2kx77fp/XpZr2d0jl1NA6ZNCmE4BYHrAiEA8vv/pJXPP0fQdER0ZPQwDMfSX1sOJXZjEQUHUgxIn9w=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":796431,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh4hqrCRA9TVsSAnZWagAAzTUP/jSnoBiws2stVpD+1T/3\nUx3X65E0Q3UwMmKy2hEhIaUMfturtQbQ9jXX0NIrEKT25SGeSzvToy1EQnI5\nK5/Awco3nT2RjUoLBZfxc76ynWHbA6d/Bsr/VWbpYx/6EFNQy1IBHeklhAUu\n2ljTmRKmyLY+yb3tz+Z/c4GBUdWZ5fss0Sl6ImFrYmrSwKbgJG5MJFnwEZWP\nZtN5QCLlUF76EPbMo6igwyKifBkFBpcjUzGJp9Njm9rtYyrGdp0pcmPz2BQZ\n4uNj2+j8YWL64xGVyDeorzTWwXbQ/68QB6I3H+eV3L0VI791QPe0374BfWfQ\n6+eg8rL0K7KTQLwvQcl8Af92lTOAQPQVVA0LMdGsLHDJo6iOvcwXhuHdyLye\nEYFpD+nrefOqoy+0sRy6SttHKN+rinOAGPibueBWQZOPAd0w/8VgJQQTKI/Q\nVYFlIKAupI752371/gq4PwHVbNjpPOvCB+Br2doa7sMcez3WEdEOkyOmEiZ3\nae0OodZyCXh9s8Ir+5CyMW5reLlooO91gCximfSRJsL6BExdXFaaZ2gFWBKL\niy7qM+a1BJwZeABrMlHqFTt46diSqYsYf9YECksGpbm3GVqVO1Xco5I9zN4Y\nybqok/IpYuCsRIj0MCEALivUtWmdr1JD9X7NKiZvo4+xTeBVqjB//mP+nO+c\nNaKi\r\n=yj9d\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e39d7bc287c45514ea9b674a68683adbd287694b","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-15T00:48:49.040Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.6","@balena/jellyfish-logger":"^4.0.15","@balena/jellyfish-metrics":"^2.0.20","@balena/jellyfish-environment":"^6.0.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.10","@types/pg":"^8.6.4","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.1.7","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_12.1.4-renovate-major-json-schema-org-e39d7bc287c45514ea9b674a68683adbd287694b_1642207915648_0.782358388287161","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"12.1.4-renovate-internal-patch-1fd4fbc3b8b1d857a97b1626a28ac76f08114dd2":{"name":"@balena/jellyfish-core","version":"12.1.4-renovate-internal-patch-1fd4fbc3b8b1d857a97b1626a28ac76f08114dd2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@12.1.4-renovate-internal-patch-1fd4fbc3b8b1d857a97b1626a28ac76f08114dd2","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"bf3e9ccf0ce26b851ce1cfbbac09a4608f8e8bf0","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-12.1.4-renovate-internal-patch-1fd4fbc3b8b1d857a97b1626a28ac76f08114dd2.tgz","fileCount":215,"integrity":"sha512-g/jJhxWFaPEcQvfZxV/ST9TtQKrHCSzkXVioxM0xW6gSFO9O1scdCusRRSSu1JeMJHqv44OHojciT137NiwZfg==","signatures":[{"sig":"MEYCIQCuicHew2BUph7BTU1PJ033JXJleYcubOB5boOc+Hq7dgIhALNfA+2pcRqfdERcOeXgXyr5my9krefNxtAs+smgIWwe","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":796399,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh4juOCRA9TVsSAnZWagAASW8QAKNbeoBKspl5UyXpvLKf\nK6L7rxp8ttymK2F6P18V1ET8OagCqz7uPBxAS37GJVkhWCFLatBCHjuepDXI\nWS1NREapEWrN3560aokKCZCqQKcQoqJiBooXspxfsOwqIxx2Ek/KcjsE47x8\n2ka8BnH3337TetWstZ2KzVTmQw3CumCsm2kuBnXCLCLjESxKrUmU+LWiLeeZ\ngFkXIsTbuIp5EfwdoyhcqjXwXM/AzdiSXdV8jDonz38msvRKeehkXwwWcX9J\nvr8XYuQj5qQC+mAUp1IeMW9uyxhU17yJ6KQUKi5l6tToLy3r9aeWUvGETpcV\nNaEKdSwWT7G3d9sgrssB1salba1Wd5dH8sUer2ZGcXnqSbyYrQvzefYiW7ug\nfGerBmLtduEedmjdEkfdGbAOum36fcCwHhKEC6A5Hvx2E6bvScyCfifK4Afb\nYxkQCqjJQOaHPfFAnX0c6sHrFPHSSS+UmeJ5Q5/sxUmVrLEB6rXbeAlAEE+2\nMjbUaPsX10YB+0ICBjGBvQxWRsvaxBnz8lK0M/Cyu8C3t9LbSh1LhXXrs3D4\neLkN+A8MR0mqxTCw4E9CQ4dbRwgYvXAh3Hk6x9xYWT1gV9MRj+Ox213giaKX\n8yygfXuAeE0VWWzLWgLZc+vYAwrRR4uQResWm5Vm2SpUuCqulQjZ9gPgKxqO\nY6Bz\r\n=s3Lq\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"1fd4fbc3b8b1d857a97b1626a28ac76f08114dd2","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-15T03:09:33.503Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.7","@balena/jellyfish-logger":"^4.0.16","@balena/jellyfish-metrics":"^2.0.20","@balena/jellyfish-environment":"^6.0.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.10","@types/pg":"^8.6.4","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.1.7","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_12.1.4-renovate-internal-patch-1fd4fbc3b8b1d857a97b1626a28ac76f08114dd2_1642216334462_0.34451198066396005","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"12.1.4":{"name":"@balena/jellyfish-core","version":"12.1.4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@12.1.4","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a6e8cd7812a9358032154a545304c5c61837d7b3","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-12.1.4.tgz","fileCount":215,"integrity":"sha512-9KGiwwYMw7+aWNFBtCU+4DZEV/1nhGcm4UO3eW2PirwB6a/WjqtFjrHax2PlZ0gLlmBKxuEhKhFjErQ88d6O6A==","signatures":[{"sig":"MEQCIG4YZ+jkT+vrarsEeX/rUzMHemC9YjJt2Zzh9/OQxnMbAiBKIt4bSFiGhzcC5JMVMjnHRKwhUxYlzyMX+AQ/oWhXRw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":796334,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh4j0qCRA9TVsSAnZWagAAyAgP/RoWzOEt8krYt13gHO27\nYpK9v0GEkhA/3QI5/pZmJsuX3RRkA50EeB/mdDNM51vWpbKl7dheVuQW0YJR\n/xF++7YgxRzIoXP6Ttd/WQISowNMnxsri8Cmk74UZjoiRIB7nFNN6bUGZA68\nHZWZcuBziMj9DqalgSweXC0A40uax7MnDesX6O+s/Gql3dkp5OD8SV9Z6J1/\n4da60OCPV9OjtOGhCkDojO5e8jKmsl21d0T1YrxoJxpv8zm4nD+/BxLIb49l\n7HTq2dZggiKRa1tdCCElOSwBGE7ZIJibPgaYC7W+4FlpQCd0W0Sbiip48XI8\nErl/JdclYiwOJQvD7e5gFbrRvsT1ScZD6AGsKokGCm+1eMjFy+Mj5wvP12N9\ncnz8nmBD6qeKOH+B1CVKJG/tBh4dTh89aUTQdI/Q8C5N/BheDBg9SZmHAcyx\nsN7UV08oGMeV3UHunhSMZkK5iAXAuzGINkhn8st8zZ0BK9k06sYYOV+iEm+A\nlatDir6NO/vRS/bvHGpw8N7rmD1172JP5MWoSziMS4jUjbd7D9nb8YWT1x8I\nPSfU0/pJOLa+x8U1E/fuHhy35xDMYd/R3W1g8ZYc9NC8jIBm6uN48HAN5g5Z\nRwNxlpp1RrVxB1ocNPEwjK9e3OIK5+84AWb6ySKCsX/K5+4Q3uGMDukHLqn7\niz/j\r\n=OBH1\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c1c09b4702831d59d9ffee57de80eb7daa6f9bd8","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-15T03:16:39.551Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.7","@balena/jellyfish-logger":"^4.0.16","@balena/jellyfish-metrics":"^2.0.20","@balena/jellyfish-environment":"^6.0.9"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.10","@types/pg":"^8.6.4","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.1.7","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_12.1.4_1642216746156_0.8790074598689632","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"12.1.5-renovate-internal-patch-1f59a51b41d0215d800b73be354f08e9450ca5aa":{"name":"@balena/jellyfish-core","version":"12.1.5-renovate-internal-patch-1f59a51b41d0215d800b73be354f08e9450ca5aa","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@12.1.5-renovate-internal-patch-1f59a51b41d0215d800b73be354f08e9450ca5aa","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"808cb921ebd7258f09d0beb29d32ac378b2d013a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-12.1.5-renovate-internal-patch-1f59a51b41d0215d800b73be354f08e9450ca5aa.tgz","fileCount":215,"integrity":"sha512-80FT7qPaTsqPBpds3M2SzhvyQJbtQP6WVeiXeKizEOb2N4LTSI23rM2QWDecxP9rFs9GobK4NDEmkppXZyctog==","signatures":[{"sig":"MEYCIQDdQMFAj7yFOL5OXMG89bynHjj5+hro7G4/nnvsCVBX0gIhAIPgOyOAxYWhz6rkljHZZOPX9fZ4kO1Mq4mkrnHYtNcX","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":796503,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh4j4GCRA9TVsSAnZWagAAqcgP/jKwItiGiG1ZvvuptkrT\n0CzJnomQ035lA3Ajh4LlexWfhyshslvmT9PbvrTNaIk0JQ2tTD73icDjYdB6\nYOQErKSy2+ShtCe2I4bl0HwS/mmOihOcbVsVzQbkY566OXyRghOzEPME8H/0\nlOj+8c43uRGKSTXcC9i2rwkeq9UhsNc7t2fdAb4F093Ym5aIDFcmCkhaUFSo\n06hp9lFzrg/QHCmuhHhJJKUxrGKs0606AtDhVuinXl0CELhSrMiODJANOB5u\n5+SJnUcw7B/o5bndsHRElMh8Z3QPfAlm/mJXJztXAr1uaGk3foKWnkN0ygTX\nJ4mjIMktI8ubwvYanprTk8999/gDl9PRgRJvQe8u1YTWIZqtkxlV+L2e+mNP\nygsltjxY7wB+lJOYJU/w/VQUg5dvyAV9aa15qC6u4OUp6gwy57qc5pBFmuec\n9jikbHEJ+OgXjm+Ixj25jKphLHJtp5C20OLKOLBVbjD1WgSlu9LdwcbfTSZl\nZ9pFBgI6+V8HRRsVfLPqvV/44fHWwFHGtPXfyIkjSA12kzYhFiuS8ByTf8zV\nVXbt7z0cKWXciqgSK5GHeMhfFXeUfu9gP/MO1Hfj3I/3uETg4bGughi76s5Y\no72i49zo6/xHfOLu3ZDqweI/mls+3pvDj8+y8Dmgj74Ecgjv6a4X5IyD1JgG\n0uUt\r\n=5nN3\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"1f59a51b41d0215d800b73be354f08e9450ca5aa","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-15T03:18:51.231Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.7","@balena/jellyfish-logger":"^4.0.16","@balena/jellyfish-metrics":"^2.0.21","@balena/jellyfish-environment":"^6.0.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.10","@types/pg":"^8.6.4","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.1.7","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_12.1.5-renovate-internal-patch-1f59a51b41d0215d800b73be354f08e9450ca5aa_1642216966592_0.46708364880073727","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"12.1.5":{"name":"@balena/jellyfish-core","version":"12.1.5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@12.1.5","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"dad8c1365303f979f447ba46ca6756026a6c66f8","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-12.1.5.tgz","fileCount":215,"integrity":"sha512-mNubJkReKLFArpDTqsfVEIWI3Fsb7dPR60NonGxfSwRfYMi8f5PnSSC108J4SRTnpC4YBQReRKOyddnSpcvuWg==","signatures":[{"sig":"MEYCIQDQm3MT1JXsVi8CZ0K/X1uZzHDiV5eekJtgLBGMjyYkXwIhAPD1k50k6ParKFj3zuqtXPTVixecDMq20sfKMFv8qw+O","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":796438,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh4j/4CRA9TVsSAnZWagAAoWwP/3yS7TXeLrDJj77/NW+1\ncA4F57+VytXbiWBoCiG7IREezwnxTpJq7X1+cJwdFJZzwva8sWz6guuvJcgs\nGr6eMZQuG8dhJXSHdiNgyM2bWVvpYuxCBIKKv96dfYIb6AcnyY8vu2AFfUsa\nBx8/LLlz3Q0HQWSeMy7/ILvSlCzu5R6aRAKDxjXdn+1hXYBaEE4Dq9IR3SGe\n2W+ak5UQ5YhwvmZps9MAtWNrTPtiavC4EP/CfMSHUvuBiLYG3St0gu+Ih75r\nUpZvD6Lx/b0ssneb7qlh3n73iLv9crEjhclGMmZYMIm05xQzrK2PVik30s0c\nb8kO76KntyMi9x9snfzSU5WengGaMZiUBH+q0PiR2D8ReMma5M+kRf6Y7/1F\nr74NsOC3wSTwSZitEFDWQKiHy+iZOVXgsLv9rJMGMUSrTR7yoWkJ9oqOalGQ\nViEtjE0MDmlV5ut/9qNLAoy+okO5q6LmmzDLzikwiOMPQsc//CrAjU/I7byc\n3Gg619DgjkixGqpMuBaxVWIeWOfT6v5bBOyTaZ9PB1UvAfC7DWcXr+NTx6wc\nDj54EUkju5B7DbOk43INZtQ1vEBQd8d/TrHU6Q+2G+mwU9B0Iro836OYsX1f\nDy/Jett6zhFE5kCEjyQuaW9VYjExn2RFoG72swp8JVmyKVeoC9QjEuwx3qFS\nUXvC\r\n=mR3W\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7a5922c3394f1805ed8f1617e25c3597660b8459","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-15T03:28:57.273Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^5.3.4","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.7","@balena/jellyfish-logger":"^4.0.16","@balena/jellyfish-metrics":"^2.0.21","@balena/jellyfish-environment":"^6.0.9"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.10","@types/pg":"^8.6.4","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.1.7","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_12.1.5_1642217464655_0.7246914699416465","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"12.1.6-joshbwlng-bump-skhema-6da3c0e996c3462e33a5003c54029f63f0786467":{"name":"@balena/jellyfish-core","version":"12.1.6-joshbwlng-bump-skhema-6da3c0e996c3462e33a5003c54029f63f0786467","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@12.1.6-joshbwlng-bump-skhema-6da3c0e996c3462e33a5003c54029f63f0786467","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f01dd6c51cd99f14015ed4248a8b52b4beb88f44","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-12.1.6-joshbwlng-bump-skhema-6da3c0e996c3462e33a5003c54029f63f0786467.tgz","fileCount":215,"integrity":"sha512-BL619LLQcwsf9X2qh2sIsSdv22x14EDKkxKWCtxQ2nXPE/166HoevY2pItkulrZkxfCyrzRdsT7w3zC7DOtONg==","signatures":[{"sig":"MEUCIQCX5JK+GPpOs6at4Aqi/nMssG9kf96baUsBhph6jhdvxgIgeLWoSHchrM7yiQSGbbcq1xZCfjLkKIm9m/6wZlBbawc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":796561,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh493kCRA9TVsSAnZWagAA8v0QAI0FINLfFtwkM4F9QYwl\nXPY9cyKen6GC3FK5IouHOq3DSd2jbf3st64LYvQFkelyUDg4ii6um2e5Zrb3\n7dYAKFPugX9o0rrH0lm9fzxNCuxPh+Ax6fDlS/YjyoDTe89WyEUbBd44KUr4\nRqeeLPJOHwaO7imim0FO5/yXvd7q+EomVJJC45tvUAj5G8V2RW3fjptJssih\nRK+PK65XUQsqZC4+zhn8o9Hq+3UFXB84X5/LP3cNS35HV7WmYwnY4Mu2AyVV\nFby1gQd6Kuq1HGd8MTQ/L4UcfBVN+cv4T1C+JzOeFPnfbMHgJWuXYlCSXXZn\njpssU3+4Z6lY8X+CGARBQlneL0/qrkmtD7oEh+ScB5qFttBPyIXGT7GBuXKl\nN92ILTKZ5IUAtnJbK6zF3lXphZS5bfHPk+V4YUYQvi38MnJpUl5Hlz3V3Pi/\nEZAwM2ev50XjUJdlKbxLH4jyJluWce6x3DCr52OqFBZ8RjmSlb4+g4T1QnPg\nGUr0C+bEmlL8zHD5LkTsEZEcIqbLEDxYK4uuXvwpDGFR2uuru3StITGxBU8T\nl3VRfsyToL/rCbsKBdpxhc8aGIq99vtnwRIil6Jpy1LV7/4dWgp0AtzqAiNH\nutCABvSw5CRONYs52/gNz1bhOl1qufgHxORXV+yMAVyUFwPAwRys/UieQG0X\n6AO5\r\n=abS8\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6da3c0e996c3462e33a5003c54029f63f0786467","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-16T08:53:53.966Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.7","@balena/jellyfish-logger":"^4.0.16","@balena/jellyfish-metrics":"^2.0.21","@balena/jellyfish-environment":"^6.0.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.10","@types/pg":"^8.6.4","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.1.7","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_12.1.6-joshbwlng-bump-skhema-6da3c0e996c3462e33a5003c54029f63f0786467_1642323428740_0.051128413593837685","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"12.1.6":{"name":"@balena/jellyfish-core","version":"12.1.6","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@12.1.6","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"edec65b0f96807a6096f1261378c6ce035c34f34","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-12.1.6.tgz","fileCount":215,"integrity":"sha512-ICiDeAKnNCu6TdGieq+wwRGxzBy4bpntAWPcjFyu246tpn2fS6n1cBQZQxYJV7bDjLhScYaZniDf/9YbDWtrdA==","signatures":[{"sig":"MEUCIQDwmq3GxfHBzJ3k7+XwEPLbJtsPEvxWwQxTTlY6yc1vSQIgOMKdPum3t0I4cKNyHwPo3zbXoDEXiuCXuhlfmmCfIYc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":796498,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh499qCRA9TVsSAnZWagAAFyEP/RfjqtDhvELq12WfNxpS\n0Fxl0GyP+oMwCLe+RS3fxojoAfGKquOrQ26z+v6xw87j1Upfc3c8nSVOdruE\nhGRfjbG2UDD+d2uBSSVju7XhqsrqHvVXbzJXkqbdl+1JOWs7/txYvOCFJ+lE\nJtrV7aoBqclMc+EulaqzMe/yCgTuIaCFhqUr7DOtLVxQ9UxTFg2PHWqnGc6o\nAAAR5x5z6ErTyK81GQ2XE2A9kABh2zcQyibt2AEpeNsxxtptgPwRGqSTwdpt\ngSHK0huBWtDNPM495zXcX5vJ3CxYpBeK8GhockFmEGMfg+9+KHkRTWOzXf3Y\n3bineTFu/SsRNYBc1KrZ0qcD8wLl8Sq8Y5jmBmNeXCSbZlni27bFRpJkEi8m\nxm+vtin0Qa9WJKoZDBOzUSu+uhv4rBnsPKm0ODL2kMZOGiKsKdUX4TVNSv8P\nN8TU9CdWZRaOH/ynofysc6/ONC4CxgPTn0A6geUTy0ds//PRJOQDarFZ6DrX\nxn0jB9jCXzMruRLWHoZ6ZWPoenbdAAdLoCBxYu908mCfXlnBjjTMfTCTy7Cm\n+20YMVIZG1ur8GbdmKvCKuU/KlaMTjMd2O6S5F5ai1PWjaX9H3zAsYMq+DC/\nYC+l7HVMy7J9Ec96KQz+l87qoGP4flUvvMFirjYi3cjPX/lbfgn07IvqXx1C\nd0b+\r\n=4s77\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d15b0e461556400dd8e57ec416c8535144f34e1d","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-16T09:01:16.651Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.7","@balena/jellyfish-logger":"^4.0.16","@balena/jellyfish-metrics":"^2.0.21","@balena/jellyfish-environment":"^6.0.9"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.10","@types/pg":"^8.6.4","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.1.7","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_12.1.6_1642323818676_0.7537801100434334","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"13.0.0-joshbwlng-action-request-context-02798feea07faa7f3fde13348c3c3653263b2378":{"name":"@balena/jellyfish-core","version":"13.0.0-joshbwlng-action-request-context-02798feea07faa7f3fde13348c3c3653263b2378","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@13.0.0-joshbwlng-action-request-context-02798feea07faa7f3fde13348c3c3653263b2378","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5e9cc2c9c3f2089d5dff14eacf6df8e085db1cb6","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-13.0.0-joshbwlng-action-request-context-02798feea07faa7f3fde13348c3c3653263b2378.tgz","fileCount":215,"integrity":"sha512-NGTvXRuz/t2s4+dymQV+d8n7xP/X+tuUk7GG9gqsiEjBYQBxF4npDT7J/r18gOHppfZnqcq99sP6SEPeMocCHw==","signatures":[{"sig":"MEQCIBFscMigVPlLuBVSDxsXgaIwy/DI6ED44CLSEeJAW26eAiBWTegVxT4T6hFLkzkQLw33min+XK9ipiC82TpSsr8tdQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":796663,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh5QW4CRA9TVsSAnZWagAAUxEP/162qMoncCtioyo6WLlX\no3PAi0TPlklV76LOynitcpnaWoBSEHi42v+INzZiEwETTO26XEGePogNhJiC\nGWbttLIKOPepiDXdMneojjsuCCC+VTonLkxrTR3Ng3Gbw0pasj82b7dRas4d\nTSiYpEBcKg/FoJdt14FStPNEzsCAG7YeBITdFviorG70Ufvx1pNW4+LrhbrR\nhbddDJJrf34kME8OX30oBLv2UEyPw944n5X4MRF/wahU4ficTT61gEIuwYOA\nOTMVgR0B2ENog3BRdpuTuFjlKxVwQoaPO99hKJSRDmJzMFNN557GX/Z5mEB0\n2tp0kmCNv1scryN694hfh2g5IqbdyZHJHOZng/NkZcrMxvY2YAsvWEUUqbOW\n0iq4QAKHXMGVzTbDQX8USd7L6sruYx66tHEmadArqPPwLEgo8SD1+IKnz5AU\ncUjwkzD51ckH64PZVJuEDY8szS5qwFdKT4mFpyX5L8kHUf1bLbHqu1dxy0JW\naIAOJwxJUnfYR6Jkl/5/zixpPKebe2TaJS3F5R3kE+9vPnjtHyUwD68i+nBp\nYUQW76WRiTrpQ8BTy761nyqy+Lc/hzFwERe5Xa7smsgoy8Oa8ZcddXgZGYGR\nLQIZSyK+CK/slKAsxKmxTiVG8k0PorpT6g2NWsBa3f4/lG27BVsK5lp+iwGL\n4+Ma\r\n=1uAz\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"02798feea07faa7f3fde13348c3c3653263b2378","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-17T05:56:07.110Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.7","@balena/jellyfish-logger":"^4.0.16","@balena/jellyfish-metrics":"^2.0.21","@balena/jellyfish-environment":"^6.0.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.10","@types/pg":"^8.6.4","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.1.7","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_13.0.0-joshbwlng-action-request-context-02798feea07faa7f3fde13348c3c3653263b2378_1642399160446_0.3980321303414702","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"13.0.0-joshbwlng-action-request-context-440059bbbd0bcbd09e2878c143da5cfe3bd369b6":{"name":"@balena/jellyfish-core","version":"13.0.0-joshbwlng-action-request-context-440059bbbd0bcbd09e2878c143da5cfe3bd369b6","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@13.0.0-joshbwlng-action-request-context-440059bbbd0bcbd09e2878c143da5cfe3bd369b6","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4856675e81cb477f97e8f0c85274ff0d28ea2c7b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-13.0.0-joshbwlng-action-request-context-440059bbbd0bcbd09e2878c143da5cfe3bd369b6.tgz","fileCount":215,"integrity":"sha512-rttCdGVOTItMgW8vR60v1vE86b7Ly9lbp5CunsFupQc+/wEpGJU7O20V78IZ3FZuhu/CJxjTPR4svk4mgEtgrQ==","signatures":[{"sig":"MEUCIQDuO678bMqei9QvuZla9iKGoVx3uGsrJ1SMsLqzGiuCpwIgEnuSQi1cteyU9QHvVy+Z6Apx/dgLey5ssAjiLe+qk4A=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":796663,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh5Qm8CRA9TVsSAnZWagAAe1oP/jQU/DWCi5vJcqte9Wpq\nUH9MCeg4e/j+exMRzpHy//gJV6FQKYik2kUSz7DqG4dQBEdsyRKnPFC2/xgi\nKCfNxt9mUC2d+cT/sZ+95phWeiAf075Z3Rxp9p9ReObONF7w/vntBk+Rg7xi\ndQiftj/L6lWdMxjYdn6VKQjM1oVdMCMhikZY/nJkbOdKj1FbB55Aa6lc5qnY\niaz+gIGcFYj4P//xMLgcrPFlkKhTGPlcQ+ELKnf36J9fbt6QTc4UR4paTGUC\nCbENJHwvN0PbxaiEEgxK907jvWFsX270nLPaheYNF2L6aft2Zayhhv9Ffvrv\nC8Jl7zXT66jnUtDYngfL7xTRlKSULWDLn7wDrqCAW6pRgAJE/0EB4xoK33E/\nvy2T0YZsmeRrERwHVAyp78X52JeBIsz7P+C28EcQHHPMcHnchw64nmHMiMlm\nXXnW0vQP+QDfU+Zy+nCUmhGScqQ2AguLQhTWabLUTi5ITz4b7fMSVeh3NaHQ\nvfRXlHg8geR2kjRMGbrbYoy/m5ccOk8jf5FlLkr1Lm2JOoN22nGenj575ci6\n+/yhw/WGkA/Yp6pkufgxqtDaCnvDxuPbDaQ/wWOiKKxYuwny+iNSfCA70x+D\n+MGsmc8juNQyXhEYYZu27V1x+b32Vi4iDvObY1n8TSbk+PWJ6IItw1nzZuu1\nKPVH\r\n=Evzq\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"440059bbbd0bcbd09e2878c143da5cfe3bd369b6","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-17T06:13:07.264Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.7","@balena/jellyfish-logger":"^4.0.16","@balena/jellyfish-metrics":"^2.0.21","@balena/jellyfish-environment":"^6.0.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.10","@types/pg":"^8.6.4","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.1.7","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_13.0.0-joshbwlng-action-request-context-440059bbbd0bcbd09e2878c143da5cfe3bd369b6_1642400187622_0.40558724912176447","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"12.1.7-renovate-internal-patch-7000e25f0f43782200588200ee20b407eaef8ab4":{"name":"@balena/jellyfish-core","version":"12.1.7-renovate-internal-patch-7000e25f0f43782200588200ee20b407eaef8ab4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@12.1.7-renovate-internal-patch-7000e25f0f43782200588200ee20b407eaef8ab4","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"741cb108489c4f23bf47c19fe017649333649eb4","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-12.1.7-renovate-internal-patch-7000e25f0f43782200588200ee20b407eaef8ab4.tgz","fileCount":215,"integrity":"sha512-Xz4mbuZN5yVfJgAIOvkurAfcVGkqcw3qD/uThc5a3ITZW54E0jq3uh/Eo3WU7UkfHRD7IN79bsENOhAak+F7Dg==","signatures":[{"sig":"MEQCIHc4dhsbl7F1xQVzZwGIQEk53h1BNS15a7zB5WI7Iig3AiBtzdC9CUw+LO5KfM7SXHH1Jl3bC2p6F5AYdL23I2cZsw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":796634,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh5UUGCRA9TVsSAnZWagAAdkIP/Aw73KAFuqbc7bRN4hmA\n/MI64kGdpUtzJNpF4RxvamdEIVkfHNZZcmybH55pKWg8gyiLv5/jR6hqecQf\nb0VDhI0m9fFtkwmLkPt92EQRz4yMqa+VrsqS7QmMla1A2by7R6VV8KLbnGOA\nVzhgTXXUplVuxQCbP5pEcFhBXqQGJm/fjkFW78E60TruLyXpJNyFHq2eHXby\nk9pwXs93nw5RTPm8eLzLjjDDo1knMI8yo/OILTsVqDUbRa1/d2F6op8drLcA\n0GaOFkwpWtRfLbUE9RpotAafjwkgaIeZ9CoMhGrYAqKZgJbNwo4oNNyKVMRL\nCeEUA8UAopwadGyFPM9lwFfAKMuCbXVHIvTYtz8pT8jj8lWtPZBGer8xrojG\nYil129u0/1rJIoFigRqhpme0QcASkLl7Dnt/arJTms++eAoQEZ6uDG5J+Zeg\nq2nlh8ezWPfCGEgQhroroIjFg7NX3/YGAoMU89mIC0zhafwvXuXZCgysA9Qa\n+4j6Omc+POPu7kamE26HrujeeSoUODFocpXWYoXE8tc/V2ai1d5mZwEYmpCH\nLs/UpXi11eJmjIPFsJfu3TkI/u2KO3yuP5ihwcpycXd5bYPKnnVej8ZvIXY+\np9l2DNQabzUSg5E+tgGbvKT4F1phoVGlbdB3uuOWSnfsvH0PIDFmvOA6rkvp\nnFPv\r\n=Jtoh\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7000e25f0f43782200588200ee20b407eaef8ab4","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-17T10:26:14.435Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.7","@balena/jellyfish-logger":"^4.0.17","@balena/jellyfish-metrics":"^2.0.22","@balena/jellyfish-environment":"^6.0.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.10","@types/pg":"^8.6.4","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.1.7","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_12.1.7-renovate-internal-patch-7000e25f0f43782200588200ee20b407eaef8ab4_1642415365782_0.3221393802025232","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"12.1.7":{"name":"@balena/jellyfish-core","version":"12.1.7","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@12.1.7","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3c5622eaf3e0f486634822a00e48497354a0479d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-12.1.7.tgz","fileCount":215,"integrity":"sha512-5L/PEA/3QdU3f81XTHYYqCsUXCDpWmbaP9fa3dhK6YNOpToY+wVqNA09B+JeieiVFHSsPHJL4X85s5NCMtK9YQ==","signatures":[{"sig":"MEUCIQDUeB+m24ugZAUtYKsCzFQE7X1Kah9Ll+mw29IHqvqUJQIgbDNoDnrx8hd5PpZ003KZwUgY2XRgR0m+r8PXg6hT+mk=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":796569,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh5UaZCRA9TVsSAnZWagAAkOYP/juyuKbnNZdNQsG9FQys\nFKhOOEFCyIcoXdunkKzx3o//gcUUdI/ltDu8iwaA1+Es/7Wxw+0FPj+EY5x5\nokO1m/o6aYxPg/NLKHEDYDnWeXBe1pgBwBPtsYktZLrqBg8/8jwFzT/78vu+\nrzuYT9Al0kZZgg8MFYGXBGk4Arnv3HrelsZrxz3HKs6Q7H6jYyj60tynl++H\nxzdQkIOf8SyJfFX+01bOx5tAZXatY3Xp2z+t78oGYkBZ7j/Jj9fqyEO64ozb\ndgRXdQydozghg++6+lQjm22eWKJlZhpLpORfANfYooKVxa8Fd660FbjiHk5u\nLvNdOzapAoN1EqZ9sjb6ekDYzkPA7AhInhKeeE4uga/BDbc/wKidGDIupkFZ\nhHk8wl/uJ51ur7lcfoO50hhngBhOeuEkU9c6aEo2uWnFtnKLdNIDVDErlp20\ndSlEILU86RA8KrRh3op6Th54lT5cnXSvuuL/0u1lOh4PYIgKo95vQQY2Tyh4\nkESoVf7pRGRx2KywWZoDIf+Nx3l+jBSHdPItYGdbaivpLAvZSVZLLn5jP45s\nJHYBMm9ZBuexnsOf2S0hF5Hb3m5XqAN0cQRw9uhSb6/O2mGjrcFbjkb6VPwK\njs+xh9OCepjSlbihYf/QlUm2ZddYBmvBZQd17PFfIDuE013wPMyAN+3pls4x\ngM7e\r\n=7PLG\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a3148e3b1c622029b71b98eca13eb01f05c47a49","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-17T10:33:57.910Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.7","@balena/jellyfish-logger":"^4.0.17","@balena/jellyfish-metrics":"^2.0.22","@balena/jellyfish-environment":"^6.0.9"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.10","@types/pg":"^8.6.4","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.1.7","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_12.1.7_1642415769476_0.27463214467838704","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"12.1.8-renovate-major-json-schema-org-d205003eae867e441d216dfc6faac0ecc522a2b7":{"name":"@balena/jellyfish-core","version":"12.1.8-renovate-major-json-schema-org-d205003eae867e441d216dfc6faac0ecc522a2b7","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@12.1.8-renovate-major-json-schema-org-d205003eae867e441d216dfc6faac0ecc522a2b7","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"60f78ccc3fd4b7fad74ee94009cb954634a1c248","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-12.1.8-renovate-major-json-schema-org-d205003eae867e441d216dfc6faac0ecc522a2b7.tgz","fileCount":215,"integrity":"sha512-VLrBKaCEK5J/6neQBtnmdKQ/y0rSAkxTGaAVfMt7B4ip35zycOdoe0DcHZsFIEkkVa30OU/09MD+G4oZ+qfIEw==","signatures":[{"sig":"MEUCIQCNIkzK6oSOsQ60j3k/6lUhiAlCWkdci6U3vxxhFEl4UgIgDSFdjtgowOZe1HF3G9t0SnasTCbK94gcjHxMe7UC1FU=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":796737,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh5UflCRA9TVsSAnZWagAA4zoQAIhbKMBw0NO6aQVABNxc\nsw7TVf0CIP1l07rvtbjOdsgr/QU5IGdvRnEg2GZcaN7gfLCkjg2tj7J5Mh6o\nvrgCYacweXKe1JYGKMHNdIx16BIow+PYtWFWCbzBbdX38gOlRDHh8b6ijnI3\nlgXWAm2gRFsbw2ysXrwHdw+a9J39NhIuijzLU/WLo8IrSqLfHwacJRn1sEAR\nabBtEU7TsWLbZCmW7X8ozkU0D4j+r1ro24Lf1Ei9/X/7X4ayITOgqS3FZ/ZL\nNI9f5pxw8mhJqMso1VshMf+jj3M4MOo0SFvJ+ae/V8yVXaPS0zeB52LiLP1I\nmXXyeVMUHJP1Gj6jvNiYcaHVj/VFll0Cz+HPHv9v4RMsVgR1tpoqmwXwcGNk\nThkPuSF56OUVSRXcjRoCLzXNJtzgU//ItKktVGo5HVcksqgreRTtQsZ1xiCa\nH/f7Uwu5dHge8F4l9WYzm4gQop6emW4Snc/llMHAz17DwTQSMgdZfdJILlvF\n6+tIGcMSAYxlghv3/RrhyWT/QMhrmZ9qQaD0qehZ1WME/HoSZzTGT5rAj3f2\n0ejpU/pXEH49Yr5wLq3EDMnPCQuDN35pWGLKMLVUz2CdQ9XyH729v4EsB853\nHvgjoxmGTwssU3l32iEt2Z1ASZSl3lF/ZqOffhpepA4I0c16fVs4fIQkcYBa\nz79t\r\n=BUCM\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getCardById()` or `.insertCard()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d205003eae867e441d216dfc6faac0ecc522a2b7","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-17T10:38:52.030Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.7","@balena/jellyfish-logger":"^4.0.17","@balena/jellyfish-metrics":"^2.0.22","@balena/jellyfish-environment":"^6.0.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.10","@types/pg":"^8.6.4","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.1.7","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_12.1.8-renovate-major-json-schema-org-d205003eae867e441d216dfc6faac0ecc522a2b7_1642416100989_0.6757057782125953","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"12.2.0-alias-public-interfaces-card-to-contract-86d2a81e4da41dd5b1a56ac6a0d5321a934487f5":{"name":"@balena/jellyfish-core","version":"12.2.0-alias-public-interfaces-card-to-contract-86d2a81e4da41dd5b1a56ac6a0d5321a934487f5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@12.2.0-alias-public-interfaces-card-to-contract-86d2a81e4da41dd5b1a56ac6a0d5321a934487f5","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0505a2fe5ca4bc197a95a1867b5644254f0332a6","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-12.2.0-alias-public-interfaces-card-to-contract-86d2a81e4da41dd5b1a56ac6a0d5321a934487f5.tgz","fileCount":215,"integrity":"sha512-mXSe6hfKtI3h32bOYAxbPy8icMZoVmd9WRI1pwtAsNGX+BWxPC/vZjzsAL+pIsJanm455ctmATq+sfHCVyntlA==","signatures":[{"sig":"MEQCICNzzv06MrOHLfNPvn07lbP8Kxu4C3d/XYfbJbroS+JBAiAjZ/jms0FdMpthC7SY72vyW/WT0iS2tGQx1RzE/RVQ3g==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":800236,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh5XptCRA9TVsSAnZWagAA3h4P/iUT4G2hAtmRo7pvRXTX\nqMaloRfQPjcLAqMrEg7EFUhi64RDR66oRU7VutvFxSiVSdYz7wV+TiHBZGe9\njhdiXWvNMrNUNPnZYgolz8T5zCpYpZvWtaQ4gcxTYPbuwDvPb/Gu8LLDbEeh\nvLZu3YqO9Y9NMfbhIungGYets7mSJr2x9FSv9aabBuaWUyO58yj+F1vDzf9i\ntScqijbEiCMPqO7rxjPWUvhjR2T/jklQ/2qRlgIqo/lyrX7tfaE3SwFul7nZ\nyKn7+Cxsejch+3gKgnCK9h9MeMrN9+9oRRcJbj0PlP1e5HBWxVSSSpWrN/+Y\nm64SyY9jgoWLXPIbRUuiKBMTCFiu1CfjoNDYhh2vH4RUv8ev07zxwnWhORRw\nu+xTdl70xkJnb+8kO9efAeisUq2+ZtEyDpsJMbTZMR1znM0v+4bml3S1tC3T\nQaKnXQKTQJm2C1A/wVUzEWGtrHkmmsumZ172HdYURqwgH+Ox37qbk8H4GtWj\nFyVdv8X58pqfj6xaGDUN1x5F8E5s0cF3OWT73rh+bSnVkk1Uvqq9kyhjvKOc\nauf/Z9NtyZZcUNwzaua2nvDgkEy6/CLKj/UbIIozxOEdWHLJatYNu4icQz4c\nRdMuCpcSU8tjsiw14p2c8ZtfKFaP7QgHgWXu86AegJ0bw6whHrRoDM6u9UZV\nyI+h\r\n=VAR+\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"86d2a81e4da41dd5b1a56ac6a0d5321a934487f5","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-17T14:12:27.836Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.7","@balena/jellyfish-logger":"^4.0.17","@balena/jellyfish-metrics":"^2.0.22","@balena/jellyfish-environment":"^6.0.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.10","@types/pg":"^8.6.4","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.1.7","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_12.2.0-alias-public-interfaces-card-to-contract-86d2a81e4da41dd5b1a56ac6a0d5321a934487f5_1642429037309_0.78482783000107","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"12.2.0-alias-public-interfaces-card-to-contract-5e877d17669cc45abc07dd110820b29bf0cb3a85":{"name":"@balena/jellyfish-core","version":"12.2.0-alias-public-interfaces-card-to-contract-5e877d17669cc45abc07dd110820b29bf0cb3a85","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@12.2.0-alias-public-interfaces-card-to-contract-5e877d17669cc45abc07dd110820b29bf0cb3a85","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ed1c664bedc8d4129e3ac31849868a773a151bae","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-12.2.0-alias-public-interfaces-card-to-contract-5e877d17669cc45abc07dd110820b29bf0cb3a85.tgz","fileCount":215,"integrity":"sha512-3b0fqIx0jJ1KlKNu67nRgg2RPsFMXsgJswjW9TJaStcX8ADwcT0cH2XUktF3g8w+nxk4sVv71rpHAz1Tru3d/A==","signatures":[{"sig":"MEQCIECczgpFRwyC6bFv+YR6b58Z26d4Z0sTb+66JAvuEYBYAiAloKI/Ov1d3VyfgRnkGn02v7yBstxFTTpz7R2IP43qtQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":800229,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh5YO1CRA9TVsSAnZWagAAarwP/0m7op7MjlHGhB91Tr/m\n+A9mt675rzo0HwMGhp07AZaQepBOpIt2aITA+4bDdp1Zj1Jj9RKayvWNs0PL\n/noOGJgHFt9m7AHKFw3nLe/GGO50DAUbMw83Q6uG+JwWnkEnBScgSU8dvqlk\nWymEmtG5l0wPOZegMR5L6nMgwTeEjU+Kokiq2Rv6U6w13TtSp+FMHo8rSDn7\nlh7HIJd8yFA1bB7/AKiKRsg/yLhErtUxhJgJOldeCNQ1psjiqBQWn9/sJOHR\nb3iU6BFGJucSyW8NzpZdAZPuYmwmQplf9ykIx5zArXD/cifL5Wsq5OTU+Tf6\nHiFtwjAyn9wVy9ZVobdbFIas5WLJF2Jwf9vhIThsPR0a8vxpmDo5i9xWkAEk\nmyRZxEX8BYUdO6JLy+/OQDpKPojJXU7wntQJN+mZnjJqUXPE3skf3d5lOAXY\nrfaWtlBZjVis8TNnYo4T5rfbFiGogSHz8gRgBNFJKuayHwohDHc8QmsIApwM\nrn5mdUvzObvfzPUzkO9IhuvPIEVOWpgoE7+gX9jQNehcFZZ9Y53Gs5m/8qnn\nhmDRDmNNvCAPBDRtV4HNh/55+x9cfF9xMofvn360XnIYlb2aVM3nl6jM7EEN\nHh1w0b/QJ5eRNa+KYX+0mZ4COLN6Rb5e63wEZ9aQ/mjWOiKVMGcenFVd0z/7\n7bLK\r\n=FpLg\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"5e877d17669cc45abc07dd110820b29bf0cb3a85","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-17T14:53:35.886Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.7","@balena/jellyfish-logger":"^4.0.17","@balena/jellyfish-metrics":"^2.0.22","@balena/jellyfish-environment":"^6.0.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.10","@types/pg":"^8.6.4","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.1.7","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_12.2.0-alias-public-interfaces-card-to-contract-5e877d17669cc45abc07dd110820b29bf0cb3a85_1642431413546_0.6996509700921423","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"12.2.0":{"name":"@balena/jellyfish-core","version":"12.2.0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@12.2.0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"7419a7ce70032265bcffc8ee0b1f5f8c5e508b74","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-12.2.0.tgz","fileCount":215,"integrity":"sha512-tPbgvdnxMBjrtHcmxGJ4QPsV0EnINJSzyGhirMY7QZtA2pCoxfAWe+LwzhLITjqFFDi27bKQ94maknVDtwji0A==","signatures":[{"sig":"MEYCIQDPxIevgV5sDS6n1jmvtMStXXF9i82E7h5D0pF30yyZggIhANfdmIjFfK54oo665GHaY2af0gPmt+IR0obAwLbBLrif","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":800147,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh5YUnCRA9TVsSAnZWagAAJYUQAIkQeLvRc4hZ8N149ZTu\n+wAZ83aiaYJq+nLNFiTI+98CWPPtDXs8QapUQMpYpJNzosomnXzWDq3Pvr0H\nWDTILFi4P9q1ZSFrUX0sZ8zFNj7El2feFiG+IOIBhW8sf+dkfdh4hO8TuJvM\n034wBm7Ynq7aHO8iwe+ljkeBBNh/Mn+83gaK4WRqYmX36ishUJl27GTFw3u8\nr4xT2NB7ylvWDSk3+IsXUOWt+l4/5QtBj52kPon6QrUs+FWuqHiI8tIOAWHB\n6C0/B6Wu5C+SPEpF0E3iQYn/IqmL5O+vJYx9v9L6efkPyJjtsVPGY0JPr36i\nczt61h30ghdilLCif6l1uaZ97E7qynzNpp6KQfyHTAB2NPERrhBpJmL+lZup\nzc3+aMJXx7tBrV1JCdIm9JEQzgLw+HYcJXm4u7VMQzmdSFO4Y0nQx8xBTy7A\nzPS8Pk/eNCwaO8lDzctx6RD9ZNxKMaZA2LahbnbjLR8DL5f97J9QF9Hp0ZJj\nXlLr28omI5fRBjS8COi5y51raHNBjhuK+zHaNY9DZM9CCoJVoAkP/zEqfRDS\nA/P37xsVUisklFPbGR0eY0uMNnu25T0WS6buO9feYeylASkTvUpsCZB92CSW\n83/cNdhC4NHod1A+gCyUcydFgcq0ZaKUJQZuPMpbxBlV2L/+Xm+i7iE49egP\nUyv1\r\n=k/7N\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e6ff2f6e890737afb945e068ccc6ce8f27cc53ef","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-17T15:00:52.000Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.7","@balena/jellyfish-logger":"^4.0.17","@balena/jellyfish-metrics":"^2.0.22","@balena/jellyfish-environment":"^6.0.9"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.10","@types/pg":"^8.6.4","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.1.7","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_12.2.0_1642431783010_0.8944320008119053","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"12.2.1-renovate-major-json-schema-org-d682858bf04296d525fd6f7d37c3ed8afefeb517":{"name":"@balena/jellyfish-core","version":"12.2.1-renovate-major-json-schema-org-d682858bf04296d525fd6f7d37c3ed8afefeb517","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@12.2.1-renovate-major-json-schema-org-d682858bf04296d525fd6f7d37c3ed8afefeb517","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"58625f9f3642a326596732e95742e244c2725ef1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-12.2.1-renovate-major-json-schema-org-d682858bf04296d525fd6f7d37c3ed8afefeb517.tgz","fileCount":215,"integrity":"sha512-RNDwgaTALq23qJmUWYleZeH3+JuCdZdsC4QbIEGzSZIq2gHqIL93Hlnq+fpHGeL/CQ/i5ehDtEXIAu7ccTCWrQ==","signatures":[{"sig":"MEQCIEHdJbsrBFHPR/F/y8wVu1HlU9c79FntSdbvCguEKDVMAiBiyYdcMJ0/AD1SyO1WGhqqIQ1xLOT+2ew9LOLydxcGqQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":800315,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh5YZdCRA9TVsSAnZWagAA9IcP/3XNStfIT7imhy/IQb0w\n6CkDm/+/qvAO5qClPr1GXW88BjoO8fqfz/gzYqQqVOuzQLjZNHV5ZKrgnuh3\n+d5Ge0CmCc4hcs+kApOlckjPhBpjylyF/msnLzbb4LCEraSTQ+nHLPnhrQkG\nzYyZsThJEDS9uuvazJxXJ6hxbhxKCncdtxLTRPnsa3aJ14zsp1Witm0SPskg\nvoxAt9A/LLBukssQHdHBIzV59w4f7KSsoOAhKCv7OVszccQI2DXPyUjwAfdN\nULJTbALHXmw/qNHKdKGgc6Ui4lP9I9cn5CG55h8cGqzfYgX/POsA7TlG1X7s\nD573T1ENOdjzm9bX6n+np4S/E74kZBtz8Ky9AM3Y9Bju2Ky++XtOc3iG5Yfc\nGzJxmssrIDR6k0uJYMWvQow8VZK6k1JffaxauPuN2Vl70sMe1qlIfm1PukZq\nIFbcs18Jww51cP303kdawn42rPYytBXSQMKwTbxxqRtXu5/47bXAFUae5CwL\nCk3UavQbiu2rN5TnxIB47HnsmxXVxbvRwTfgXhR4Ujgs2hxnKeDLTff43pdZ\n+46AkEoI8jRf45/4tEauZjJVmRDnqLtHuUCmfz0dV6LNZBDd7lRacDeuqcYA\nRRqUCjfPFzWFQO2Qu9t5KITTYWqGvIIEUq5fZl50GuXh4C2aIDMKlU5iYX9j\nUn7W\r\n=SRzK\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d682858bf04296d525fd6f7d37c3ed8afefeb517","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-17T15:04:37.667Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.7","@balena/jellyfish-logger":"^4.0.17","@balena/jellyfish-metrics":"^2.0.22","@balena/jellyfish-environment":"^6.0.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.10","@types/pg":"^8.6.4","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.1.7","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_12.2.1-renovate-major-json-schema-org-d682858bf04296d525fd6f7d37c3ed8afefeb517_1642432093720_0.377283876579839","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"12.2.1-fix-typo-insert-conctract-5982cfb63638b48f2a00ef37022d4487510f0515":{"name":"@balena/jellyfish-core","version":"12.2.1-fix-typo-insert-conctract-5982cfb63638b48f2a00ef37022d4487510f0515","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@12.2.1-fix-typo-insert-conctract-5982cfb63638b48f2a00ef37022d4487510f0515","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c96ba20dcce9aa75d0a9b1719313b486a56dc430","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-12.2.1-fix-typo-insert-conctract-5982cfb63638b48f2a00ef37022d4487510f0515.tgz","fileCount":215,"integrity":"sha512-8qJZ5Ir84GtYS2Orw8BgW02q6yBijqJwfUvksD0u/XsHQQessjJYZ72kFlQB56bcWxffOnX7z69SEo1utpNx6g==","signatures":[{"sig":"MEQCICHPUHAeL2BnuC82k328jI+QNrerYCF2dBRPg1f8TAsFAiBcYb7utMDgjZzH8ChhFZdER/HuQ7F3Ue9KLnJZQbLBJg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":800291,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh5aJ0CRA9TVsSAnZWagAArJQP/jODrgx7lTv0QBpNg803\nIKOO27KYV0igTEZiNwrk3WqTJNqxUAyRuI1cYXI/hi/TcGdratqJJB8CLQvY\nU+u8u2LIdbgU+nUuo9GNxAAmf0lNE9pLtgIcKZqVgPUOnGECwDExKixQAq2m\nQginRCI4Y3F16BljQzQy7yAYkR2WjovGyfPTqrSAmTclopIPCaQtMM7SVK0g\ngHF9wvpLobgOmVqMQq75E61cqzLxuumkd5IOvDW7qg6FcAoupHPm0QRKcW/Y\nLSTOLPzrt/heDjPBakxxzuyLHzePwbveUpVjq/hYY//Dr7yZejcyCjISsTWO\nHDJ7LR5tGOXRcls4OyMKNlLqSoONC2CI6yEf63fYnS6Ys8uKxwvD9MYxWbSH\nkgC6xIJRvxt7FQK5uV7BelgjSMzy80fumcOk8jXWxuKkrC9J4fyYzuNlx/TQ\n1j8a5HLCosJAoQdLmKUEiJ6WYPQRFTbJ8Y4O1OQk041IayauNuoeKC38hFoH\nz4+vIWRaZcb2qSeI2kWN8LqTvRVycs60a5ZE0q8x1zVAn0oZ+H7ugOYZ97gc\nMBc4KiScno1UJ/hZJteNV+lqskgOh1hQ6DedHXmZMfboJuMME8nVlpmrh561\nv1m1s9UbWx7tZLYeNIayHhVMUDVsV7rYMy4UtFrJ2eAYqBQ/tYvQoFhrRQut\n5v/Q\r\n=Wkqm\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"5982cfb63638b48f2a00ef37022d4487510f0515","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-17T17:05:00.409Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.7","@balena/jellyfish-logger":"^4.0.17","@balena/jellyfish-metrics":"^2.0.22","@balena/jellyfish-environment":"^6.0.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.10","@types/pg":"^8.6.4","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.1.7","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_12.2.1-fix-typo-insert-conctract-5982cfb63638b48f2a00ef37022d4487510f0515_1642439284508_0.7952468306584615","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"12.2.1":{"name":"@balena/jellyfish-core","version":"12.2.1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@12.2.1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9751ec8bd93aa5bbcab576f1697b2d096ac6e659","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-12.2.1.tgz","fileCount":215,"integrity":"sha512-W1c11k6MbDd5vomMv+jbSu5T76oEWIJ0Q6bAesCL3rgeVl3nmfR/YZsMDBri1moruSKnWUBRe+nj8wh8te/+Ww==","signatures":[{"sig":"MEQCIChEjzGPtJLuiQu8hA95MD/eDokXCPvEn8pKpQuGXa8UAiBqBqwuOdQPEiisEiMaq265km7XBtUwArRNexcVUEoMWA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":800224,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh5aQ0CRA9TVsSAnZWagAALkEP/idNYfad5pR7Nk8q+bT7\nE05QyQbBX0Y4tuwrlJ+EWCFq5pUHt+hWD1wbDP6c9vatFrIt2DhfRyyolOxl\naq1GBy26FhPxmZIVBcer0w8HFQqjMGcpbOBM1hBNhzuqvj/Q+5KIAU0y2Ah6\n8ZPX4UtJP+A9CRHVhJ8ZlpoyFFrsCdKL2cQuzQB3y0JHfGOMUPA1PV7VR4sK\n2JWigBM7cN4JFD63bc7pM8bWcIeIZDSDXAMWpukhPiUjm8SG0vvMGKkd/Ff0\n3eoaIWokHiy3X+lBri+NRTXG+HgQSip+s63rtWd8RhAk6oarIzDyb47kDIH6\nIQisKSgaAAZyfoD6sQHMZ26q2svfBDVcpvid6xExblaLjMhNdM+FEHOULL/p\n4XwW08QLccJenpFLwuCdG1ry+eli7jOIhz3r7tFpqhEnz7WxJKXBdM8CBhjb\n1rtzZE1giL6YW6JaO9pRsbSC0uBBKPZp5mbczNFGjY2FiAgswrCPH44x6L5g\nAFskzApbI6TCqJZhej+30VM66OQLpA47dFWnMkstZHjUsBCqXHkwxvxOeW2y\nTNf+hsvMH+2HIA6EbUO2PO5BBzdNW9GU7ZUogBx/KJTDArK810y5mAB0zFRn\n5W+itOv3fOnxFlQZyikKNpD1fNozCWZs4YhaObPe09p7Dbh+ukIvAmnDCVud\n6rJ7\r\n=Zr61\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"40cd18f78736da51ae27fb0abfdf82b61d9b906f","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-17T17:13:23.241Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.7","@balena/jellyfish-logger":"^4.0.17","@balena/jellyfish-metrics":"^2.0.22","@balena/jellyfish-environment":"^6.0.9"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.10","@types/pg":"^8.6.4","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.1.7","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_12.2.1_1642439732467_0.398821292770019","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"12.2.2-renovate-major-json-schema-org-1d7e2e1d0f0f7adb2bf9f89af3644b7c0efcdee4":{"name":"@balena/jellyfish-core","version":"12.2.2-renovate-major-json-schema-org-1d7e2e1d0f0f7adb2bf9f89af3644b7c0efcdee4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@12.2.2-renovate-major-json-schema-org-1d7e2e1d0f0f7adb2bf9f89af3644b7c0efcdee4","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c69f67013f698e157b45f91748fc8a5d72d2523e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-12.2.2-renovate-major-json-schema-org-1d7e2e1d0f0f7adb2bf9f89af3644b7c0efcdee4.tgz","fileCount":215,"integrity":"sha512-2SRUc5nD1py8QSFjuTh9koFH37aneK6FnBdqyzUJOJI9lonSOAqUmBQluYZAQDaFMcnf+CFe7a6TQ+ZyHM1u+w==","signatures":[{"sig":"MEQCIG328RiqrZEdx3Xvonup+V68B/CbU2v657mBLsbbIHjjAiBGWJPz0hy6lrQo4OV0MjzyDMM0gQ4vsJi+mRXU4sKaZA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":800392,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh5aVLCRA9TVsSAnZWagAALxUP/jprIZFd841JHybZcE16\n6hFqOmXz1JvM8s5+qT8aq3U0EH8eCg1wuqlwQDbT39Oq4ML9BC2ieFG+evfj\nXW06jH4EiYKVrfQSX0VwQM4uTd+ScwJrzojOi8rO96lV4autFaQAnttgBnqt\nZqxNPFiH72aq5GIb5Ne7b6ROn25dU9Sk00LR+hXnKDDqUZVLT+kYyBbrX7UL\nfCpwTUGIYSyQ/7njB1BDEzYa/RCA58ZKfmWhVYV5pa7cRbThjJFXC2RdEMn4\nMzEAWhYV+vi6ityO1C7v8rReQiVXzMnkhFoDCJJdGV4G0EEu02lOau5xCXRT\nWVZjZQWwhF5GcU2M7kGElP3Cr5YxJqDRVorJJJlu3gdugFICkRC/FaUDXxHs\nt0SnUm1tM0HqZANOXyGErGnvsqy7rAMwTAkjrEoT2B6SxhRxx4vyi6oQIncJ\nv5fP0pMX0w8WFZeIzC5FU+v18DSsoNcyIbYKpo/SctbhMURlZ7d/aLSgNL51\nO26HxYdhQaYI/upYO7cVxBk8RUG+aOHPOuMX7aj71zYzprjG8kbH8dg5F+2u\nqpZIP775Xr8GPNfvm1L/Num47HuRqA9YtDK2hUBbGARJjuN7FW35DdKjkwsZ\nWuKoaBTKtpkgrZYNAaNhJBC+sn8yLlBUl3T3FB22BKm5s3I0ppmYSw3fa35V\n38os\r\n=aaZu\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"1d7e2e1d0f0f7adb2bf9f89af3644b7c0efcdee4","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-17T17:17:32.777Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.7","@balena/jellyfish-logger":"^4.0.17","@balena/jellyfish-metrics":"^2.0.22","@balena/jellyfish-environment":"^6.0.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.10","@types/pg":"^8.6.4","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.1.7","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_12.2.2-renovate-major-json-schema-org-1d7e2e1d0f0f7adb2bf9f89af3644b7c0efcdee4_1642440011012_0.8317405333276326","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"12.2.2-use-typescript--deprecated-718a5eea94706aaf3212d4327505dc1b6071ef00":{"name":"@balena/jellyfish-core","version":"12.2.2-use-typescript--deprecated-718a5eea94706aaf3212d4327505dc1b6071ef00","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@12.2.2-use-typescript--deprecated-718a5eea94706aaf3212d4327505dc1b6071ef00","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0763d08a5ac19735e11411bb41270b263662728e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-12.2.2-use-typescript--deprecated-718a5eea94706aaf3212d4327505dc1b6071ef00.tgz","fileCount":215,"integrity":"sha512-/UiBM6aZT2/rPUP8Q8YBAMB1iHDqCaM50hIWiOyBrc9WyO8NSgc+kYM73rmwpVgjeadQ3u9A+zAbULU0vAwLgw==","signatures":[{"sig":"MEQCIB5fVh8LY46pxO/pdQ8g4JMv+6NhXLStsfOBZl3SuQdEAiBAaN49wF+WDp8sPIpIISYaFi+xIfitWdp7fj0/Gz+CPQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":800385,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh5b0TCRA9TVsSAnZWagAAknUP/17GhwNL4TPahIiZfB9W\nnF5mBhjjSzUuxObNiuKrUhRHz84m+yCgRJVOakEzPtzzaYJaoKa8UfNSmY9x\nOo2X8JNiaVR1YKiTvsQaZIvRptrktbhwIuR1erIh4K7razuzSknoXA5kf2NK\nyGrWLUMPz6d1NaaRwYvt9co4PtB8w5Ufck9iZsW4a1s0vJ/JN2XSGQECL1mp\n2m1NPF65sMOQP+kYKOgOhqW+tzw4hMvED6q9KgKG8SxtyiwCB68Xdh29L7rI\ndSc4gdJOQQrt1ItgerwnkdukoVMKAzEy1vhBvBOFwdC2rl8krLXibZESpJsI\nh5qc2QJa/zoCljzRljAIw6Nh+X6Ac/AgMmunz2pqzFAjhCLfB6/CZs67sv9/\nXLGkDKvXDQB6DJMlgMLkEPUDuP5PVcL3cO/ku8qwoGawKuGEED3kOpzMzxCu\nfG/M9F/HS+D7UiNRChZuPlJOauP/+b0OQ4Nv2fLMTa/3zqbBYefmLMQcGzm4\nNriwr62xTbtLRbvRAR48KL/cXeJ4RZNahl3m2WQMXi5zHJQh2JkcOTuh5zBb\n6R+iUn4E4h99Q9TdCdn6UarVLpLSRq7CAA2Xz9033SP7iZg4H2AqSUAU9UCF\n4kQl8WiwrCD6mS2HBn22VpJTriFhQhXIMSu052VaTkT2oaispURxYu7xW6za\nwVAt\r\n=Dehs\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"718a5eea94706aaf3212d4327505dc1b6071ef00","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-17T18:58:40.303Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.7","@balena/jellyfish-logger":"^4.0.17","@balena/jellyfish-metrics":"^2.0.22","@balena/jellyfish-environment":"^6.0.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.10","@types/pg":"^8.6.4","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.1.7","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_12.2.2-use-typescript--deprecated-718a5eea94706aaf3212d4327505dc1b6071ef00_1642446098860_0.40278065961184595","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"12.2.2":{"name":"@balena/jellyfish-core","version":"12.2.2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@12.2.2","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"29f87378da2d35b0eaa7bff6e199362d32427d14","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-12.2.2.tgz","fileCount":215,"integrity":"sha512-Q1UXiwXamhqnw7HyEO9n7d9YL7x4+NSXFHBj5DmUeXeQXt4xdHlbAzEYg5JaNYHgre2CpouiPJIqZfF5J8H4fg==","signatures":[{"sig":"MEYCIQCNOySXgIuhzIuiEeM8EOYRWh1OhpkzrM5dXHbCm7tUHwIhAPeaopf+T3wxU7W32E6Lq4satzKRiFh3ZueN1KfzODVA","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":800317,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh5clgCRA9TVsSAnZWagAA+SMP/1X0Z9rHVfi3rMh7qQ8B\nyBL5xR9j2eNI5Ui9GCGmZARo6dxVwNRZbyGsAgHWzSCEp9XGX3eUtND2S2fQ\nZVZmN7YPQpruEGdvwsXQ2JUcquI9EyU5frUWv/zuqIh1wvoDH/O933r96go8\n9f+O+FVH4TMYIjq0ctMDDGMDr6kwQ866gPv65NIn7b8NNEgbrY/q4Pr1Rdub\n5qM+xKFoV4JoEi9JK3/e+yQhk1mTdH2jXiEFjaYvsG2TC36JMuyRXEdb12ua\nP2faztWHO1/5VEqC4ahAjKlLNOTOnYimLc9CWiYmkElz0v+5cYQoAwYBvRv4\nbN+grpp4TX68Vtzt5ztdE5kJWtlxjIunhDXQZPBbWjZn7y6WtFS9pTcGClFW\noz0izOYf7D/obrM6JgerVyBiVtbuyINfW+VP3zJDMXGaYZFIk/atzArTPFLi\nle6fJZ7zh49SWa+k7ULdYRzrfqrKV8/lDPAI/+yGrVQFhaNUe5avc9oUhNop\nDD2igqCNVuhYDZFyOo+iHUvjcoKfNkEObRYFrzP4y7DdIzbaZN0m12MXzLMw\nSHnVnuR0d4pFhN5lSp1RYxNmeD8SToMEkcKG/+ZycwVnNVqLuhlRfeNGIFy9\n81U8nQiFRWVW+eWaqHQt7E11IstlZBZvf48NiyJJE+pK2VrFn8rJf08CVmZH\nMixv\r\n=EurI\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2dbbc86053ca604a94badd34e9cef562c16af0f9","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-17T19:51:56.745Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.7","@balena/jellyfish-logger":"^4.0.17","@balena/jellyfish-metrics":"^2.0.22","@balena/jellyfish-environment":"^6.0.9"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.10","@types/pg":"^8.6.4","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.1.7","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_12.2.2_1642449248664_0.974555143383768","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"12.2.3-renovate-typedoc-0-x-0e3fa280d319fd54915c1f5637b46737bd904e4d":{"name":"@balena/jellyfish-core","version":"12.2.3-renovate-typedoc-0-x-0e3fa280d319fd54915c1f5637b46737bd904e4d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@12.2.3-renovate-typedoc-0-x-0e3fa280d319fd54915c1f5637b46737bd904e4d","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4da346c9a00ea85a03c16c0acb0bd2f2840114e7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-12.2.3-renovate-typedoc-0-x-0e3fa280d319fd54915c1f5637b46737bd904e4d.tgz","fileCount":215,"integrity":"sha512-kU+BDtdQ7TXQLTE9oFIQOsQMAJYmfiLY9wbpFK12PqohX2SwFRxQt26R9MPcXeuHTnDRXBkDk+EI4p8nD3TdOA==","signatures":[{"sig":"MEQCIGrdpnj/PadSSmsdWomfQfKTDKGxrTtFiOSXq4SMIktQAiBINq2OlehvpBfS5Ac/b9VdQ1PtHlVuKoIAtpabmI6d7w==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":800466,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh5pE7CRA9TVsSAnZWagAAvLYQAIOWRmOxjHzJkNoH2c7/\ntQqipE2ykSxoCjoGqiHxOGkfDCJV5SopAo/msnBblYcBUxuftk8vjqDF5Xg1\nEoYPxxag3lSljUhByytNGZLu12VcJGN+43eUWKPc+vsiG9Juf8pt9vpAJhPO\n01a33Lw62cyVz9FmgQ6UviqNMSZDjVfL12M2Wp7ddqHM6pkl/ryOvneDkW1i\nWzNMzsbXi1RGj6MQf7BHohivXaiR0P9QccJJkfN5m5Vv7ixTa7f51GO2iXYp\njTQ2Y9edHiwbeVhtMTlfgCmCO3+qLReOQLdGsqmD/FZiu5KwgETRZujiplUx\n0h56K4/23p3WZbLcPCPq5JleG0woX8wbBwGsNR2p4yJsjb4T3BCECvAbGUq/\n9N53SbwtRNLazkRrhLa0lMoIYpRgfah9KTIdoVKRVjm8Noa10OrwGQ5sOx/F\nqQRmXzBVCYw1NKXou0qfHSsct0Ww4rAGwLmnf+wJ66xko/N9JWOEk3kgDlcM\n3Zn62J06c/ONEBxTd14pDsTyKnvozluA1tJFd9d2sDtYVXZmKkSO5RfaaiWA\n+3x1TVi2bOXY6BTsFMoUSzuRNf9TyKzvMdYjNar8pBvFXHLl9CwRc5VWl5OL\nPISG1gz668cvTqP0FTI5PseJXGohNmiQK+UtlNI15XiNFykZeSAbf0ZwsrrG\nCFP6\r\n=Hq3q\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0e3fa280d319fd54915c1f5637b46737bd904e4d","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-18T10:02:20.816Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.7","@balena/jellyfish-logger":"^4.0.17","@balena/jellyfish-metrics":"^2.0.22","@balena/jellyfish-environment":"^6.0.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.1.7","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_12.2.3-renovate-typedoc-0-x-0e3fa280d319fd54915c1f5637b46737bd904e4d_1642500411220_0.8081365142773602","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"12.2.3-renovate-resinci-jellyfish-test-1-x-3df26e29a5f2586d3890905b28949bc64e997362":{"name":"@balena/jellyfish-core","version":"12.2.3-renovate-resinci-jellyfish-test-1-x-3df26e29a5f2586d3890905b28949bc64e997362","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@12.2.3-renovate-resinci-jellyfish-test-1-x-3df26e29a5f2586d3890905b28949bc64e997362","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"72f3be938f2277637010a99ef97c187cea0d87c4","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-12.2.3-renovate-resinci-jellyfish-test-1-x-3df26e29a5f2586d3890905b28949bc64e997362.tgz","fileCount":215,"integrity":"sha512-DsEoIuhD+PRbxaXcTTBO4sc/gk3sR2/2wJnGjTPPhELHrK9tMgHDUrIP22bcNo+oz9ErIhwoJ8L+c1k94fF9Bw==","signatures":[{"sig":"MEQCICC8MwpLREe2z6b8RcRKm0SKD91hVz1eP+X/MFziInKtAiB13Dqtj39OBZMJgQ4611ff6a9n6DHF9xXpo1g7bqtqIA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":800495,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh5pGBCRA9TVsSAnZWagAAsFUQAIE16zb9xKccXNN/mv1y\nTDpLRwExq7X7KoS7rhVTyzi4YLoEN9xIVu42YpjmoLLkSx5BwLp7TGlTThHr\nvOawU9908HL0sgaSribfytImvCMsizovbFkSQ3aFJbHdHPgkvq0OGbERdoVF\nucbY1bN7XsKYpvs/R/dc3IoPJJA/ZAd0vJhPVtno4L0AxE+p2j+0OOrK7sP2\n3XOqPiMSTINAXrGWq2dq0NUrY27NZv3jrEdBlvK48YY2taBAwjRurTn5wOCw\nKFw8v4vkxSPy5BkRitICT4rEem13lrO/8yzZlmlkpWFUtAH5OdgOiB5LACH7\nNCZU7sxV1aLiI7uzJaWSGkkVrXyZ2sUt8HIgCUelZUz8IYjexjhsdR6wR23o\nFdef5jBEFo3MxwskcwBOJ7Qwz+toKCAEx4iBbTVey2rSTo1IY3Gw/A37n0vk\nAt/ZjJ7E3br3YCkQlppFzd1RXazwD7oECNPdzyLf/vszzJa0As91YABWWkbu\nZIXKN8++ZjoMKUJxLc8dLtKuT5XfgW1K/ljT37KKp2IJQMFVRdUdV2iuZ9TD\nKHbf4ssO9f5GbsHEhHDHgUGO4zlVKugEI6nXW7mcceuL/BKF9nFAP2SNofHY\nfA6oDARApmlaIIgjLLwNWker4sL4y5ygDeMYdR7DptYQTCfy2pydAeo6G01V\nXhE+\r\n=V28Q\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"3df26e29a5f2586d3890905b28949bc64e997362","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-18T10:04:29.734Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.7","@balena/jellyfish-logger":"^4.0.17","@balena/jellyfish-metrics":"^2.0.22","@balena/jellyfish-environment":"^6.0.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.10","@types/pg":"^8.6.4","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.1.7","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_12.2.3-renovate-resinci-jellyfish-test-1-x-3df26e29a5f2586d3890905b28949bc64e997362_1642500481150_0.9687632129291643","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"12.2.3-lucianbuzzo-stop-using-json-schema-ref-f48a3792d16f1d7972a95d5235542ad58e4a469d":{"name":"@balena/jellyfish-core","version":"12.2.3-lucianbuzzo-stop-using-json-schema-ref-f48a3792d16f1d7972a95d5235542ad58e4a469d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@12.2.3-lucianbuzzo-stop-using-json-schema-ref-f48a3792d16f1d7972a95d5235542ad58e4a469d","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a7c32c7acfc4cad0d5ad7a41ff9228b9cfa73e83","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-12.2.3-lucianbuzzo-stop-using-json-schema-ref-f48a3792d16f1d7972a95d5235542ad58e4a469d.tgz","fileCount":217,"integrity":"sha512-1iN/nPpRHFXYRW6FyDSkyAY4fRLvOrcP/Mz3bvlGGo67rHGWv0+Xrj/3XGjAqQm3N5ng4MJbwXIjCefqDC/etg==","signatures":[{"sig":"MEUCIQCTyhgOrdtHx2zU00X61cxl/1Bk11UNAgm2O72JajaUygIgFCbKB8UKTSgotW/qnknbHOnZECYPwGDGj1ZzPP1yjfU=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":810874,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh5pjsCRA9TVsSAnZWagAAzd4QAIbv//yN8sTrWxcX3017\nB9yrxpDyIl6Zm2i95jMMmri1d8Ih1IPk0llc1ALtxV2T1PbU79owh/mVuhH1\n1B3t8aMgjG2F3At2oQcL166adc3/p2Cc4hjOOdpQdBXDzKGqtwcnHh+Mkusw\nqdyrwofFygaiIomFxrvE024DhiLmTW2ZqmIgjOVU+gvnkomCEDep+oB37Tv0\n1Z5Yp2EssPsn0RIYqtfLUOvVG2vfTcYT0HBrG6bRmQMkCR1uWztNifqIw6qg\nAhTX8ARg1b2nPPE9OQDYueQTaeHnbxevySp6vLP4KnifT5KUmUtrtR7F2UiW\nR14d7vra3INYGvl6j04wvHoQyFWuVtNCdqOi1G53vymnCkQPlE21UtIeaFW+\nXWmzwsC0HMgazkJOeYrDerXLPqmSzJUAqDLb8vscuQgBeDNF4l93a8KtM0sk\nNZ5SxOJGWqtNPfFnKCSLpa8QIEJ6HEnpQcji2QfS7bClijiwRM9LqqxCi+gb\ntxHFgvdisVSITWVm8cGbEq33aMoghH2bXNh1tNrgKnoKQTkFStjsfFGpvDWb\n3k5STAs4U8ULZEL1beHmPrzFwplc/BN0tBPT8uOvldrpyE8NZ1NDUwnt1h4U\nc8/bNmu9HwmCIGl/M1EsUBdwaXPZShuCmG2J0L2U/7rCkb5H3ba3h3bKtvnG\nGa8d\r\n=xO14\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f48a3792d16f1d7972a95d5235542ad58e4a469d","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-18T10:36:03.711Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.7","@balena/jellyfish-logger":"^4.0.17","@balena/jellyfish-metrics":"^2.0.22","@balena/jellyfish-environment":"^6.0.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.10","@types/pg":"^8.6.4","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.1.7","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_12.2.3-lucianbuzzo-stop-using-json-schema-ref-f48a3792d16f1d7972a95d5235542ad58e4a469d_1642502380243_0.900935210635339","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"12.2.3":{"name":"@balena/jellyfish-core","version":"12.2.3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@12.2.3","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"939a7f199b45435b6f725c555a3c0536b04c3750","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-12.2.3.tgz","fileCount":217,"integrity":"sha512-yGoUB1MD/1qYXqfXUqiYMa9FaSA2OLWpGIcK+cGxSB0weTnUQ3xB50XxDvKb5Z3VL9AhXFCyVpG/N/2Un5wM9Q==","signatures":[{"sig":"MEUCIEfVUX1bHSNHy3Mh7uHD0iQCsbNkyR9G/X0DnFal8I+HAiEAhcn+sv3JMuPMrwoBf/sWEHCS5+0Mc1geMLw7hAL6+WM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":810794,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh5p/TCRA9TVsSAnZWagAAzMMQAJletZTaZr2xbq2u5pp7\n+Pe5B9hoaXIJpSnJ58goNSXWSlfw6ej2wfT5Dj2OkqidGoOlhhIXZRyThUo9\nh2C9GAzbIiyRlb0Q9N1F2nWO93UAjUWAoXyJK1FGFOmby0iQW6XgmX/HwgYH\nI/rwJZrHcKkNa3/bebWWxw9HNhd0JbSUsNDAPvkJ3FOJ5atiX5oK6kI1+1Za\nOSrWeISG6+bEjgm2j5egoVyY9Db/QsKrXkiFUOBcdrHBTPaZyp+uoE5hSbf0\npCwjzTqGU2bhMVzi5pk1eEJXiInEg4UUxG2Lo0Thn/oeN9MRcPFVdnGBNFNm\nWLl5f+VlvAMZhc5pOJ0SKJs18rEe3v22GQSRvWv6s6nbVsOvtfePLaXiuoOZ\nAfyQ90Z3b9iZSTO3lOTvsB848ED7/527MufLuxK7L2UMIH0otDOXiEtXaLS6\nz1M+/AGWmo9ogFl4i/gOiqAkT1jGldeULNv6BRgeAtgCnft4XyVBoUWgTfmo\nq4HIWnJJGCRmjwDQxx4gyczMNTDWujrOxayDAvr9OdKgkKnqt4LIP0E0Hw41\nDQTbMwBy61vzB4e/KD1TAmFrrDTvjNAEKJqKEEopdlfLA1J+IT/4zcc+hia2\nvTb0Wno9uwx0rfUMkx8BEWDz+lPWr+Z01iAKYL2Bez23a2gGg2KTWVat1+0/\nJnlA\r\n=vE4j\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"cc93842129c9616584fee3588058c0d70f74f736","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-18T11:07:12.457Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.7","@balena/jellyfish-logger":"^4.0.17","@balena/jellyfish-metrics":"^2.0.22","@balena/jellyfish-environment":"^6.0.9"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.10","@types/pg":"^8.6.4","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.1.7","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_12.2.3_1642504147119_0.5162375809911741","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"12.2.4-renovate-resinci-jellyfish-test-1-x-ea18934ed6938aea284d771b9b12a4c3bb4063c9":{"name":"@balena/jellyfish-core","version":"12.2.4-renovate-resinci-jellyfish-test-1-x-ea18934ed6938aea284d771b9b12a4c3bb4063c9","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@12.2.4-renovate-resinci-jellyfish-test-1-x-ea18934ed6938aea284d771b9b12a4c3bb4063c9","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"99573a1be61ef627c3c3cf76652a7e2c2df61f7e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-12.2.4-renovate-resinci-jellyfish-test-1-x-ea18934ed6938aea284d771b9b12a4c3bb4063c9.tgz","fileCount":217,"integrity":"sha512-x7qUV/nCCW7mV61WUkE+XIYXRu6HizZI++PqBa8SWQuusmSduUnxvKckOcy+eP7fQ6TIj6aqJi95WhhRMF3kTw==","signatures":[{"sig":"MEQCIC+sBtRfvBSbQDq+qujJvjEe1lMIZC95etr+JD7NsEBJAiAP/i3RET1hR+0WLbS2TdursA3BPJmP+i3DhBsDISvEOQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":810972,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh5qEwCRA9TVsSAnZWagAAf+EP/irT2vjJ3tw1mnXD22/L\nrdg+4vqCrx/P9X7ZjDp1HxfSdjHY6N7RBUBltZknPzPt7R62hqoe4fXPyngL\nDmStdz9X1kVOm4WKo9oerjrx76JQJcGqdFX7IH1kapEpBEjyVxA57wVqdKcT\nPepG8BDRraUrKHkcuosYXPf1EUVwbcqom6ZMtXbyFxM6tX6HLMUv3BcPh9Yl\nsV6i0mJ34gfabkF7JxvAaExQRd5EVoAAo8kctJI9szEGUnzIHNKFSCB0eCZO\nphidLlXIP/1B6OyJGiLR0Dsf+6AhxP/zgqo0l8jfYkEfB+pRgjfhi+juNH29\nXXmA8jLZjBMU30fxxlxiOEmoyf8lElWTYXiz05skIREEHalO9RdaQcNdhOqb\nBuJTt3zef2hSWy+INiB/Ndk9OQxuydf047hIwVdPmMNbTeH7wGPilheAGoER\nkh5nfu1SsWApfG2IqIodDYUdMDCIJC0eoBDubyp1ZFlnsiVJHxk1FLihmTXZ\nYq9H6sg136ty5vz3xgc8BSZJDkIu2P+3MVFxTUnOEhCi94XMhtetblYQ1maN\nGR/3on8KofAE4tkP4drEgHl+ikqomwzZhNzjsZHjJ0RDzFw0sFlz2Tq3/Euv\nbng0OAfs/zHhBilN+hTrNyvLVQ180f33gh/LEpoulzTJntdFSeC5GQsxo0eB\nswHM\r\n=o+F7\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ea18934ed6938aea284d771b9b12a4c3bb4063c9","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-18T11:11:42.274Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.7","@balena/jellyfish-logger":"^4.0.17","@balena/jellyfish-metrics":"^2.0.22","@balena/jellyfish-environment":"^6.0.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.10","@types/pg":"^8.6.4","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.1.7","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_12.2.4-renovate-resinci-jellyfish-test-1-x-ea18934ed6938aea284d771b9b12a4c3bb4063c9_1642504496716_0.06863392200181218","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"12.2.4":{"name":"@balena/jellyfish-core","version":"12.2.4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@12.2.4","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"529e87909bed05471534202ac2c6184f83e9d603","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-12.2.4.tgz","fileCount":217,"integrity":"sha512-Hbk68vaOa3meFFFSgdxquuN9j2Add8ERFmd/TzgrF6uGfxsdCETYE9sqrbV+pcvZ/UUAlDYIRfWH9icfCvHnYg==","signatures":[{"sig":"MEYCIQCrqZTZrmeOlYimJZ28Zyz5RO7HSuf97U/S/VTH+bDnrAIhAKrPX6KusCzxt3cE9l8YJaMKp8+rfu2ScK9HLW6gfac8","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":810895,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh54S3CRA9TVsSAnZWagAAkO8P/2EJTqCyBCNwNHrTHlOO\nRf4Oz5JzLSn7Hb1rwSKSelJ29T9MMtQOulrj9cVPgMDzukN+1JKyH8/ozKP+\nXI0ZvopGDiBAO46Q+vc6otyvjecXPj/vcNaLBxoT30tFF8a/cplnjTrFFKV9\nQKnDsqNnbNr8xYJMDteeAQyun5CriiAPsgwiTyqaIU0M6QiEFUnHF6glb/8i\n3RlVCaYzJfFdYzyuRocssqsaw5G0arSVpGYXtWLHSWwhUxERGfzU7DTfh6u2\nyGBInkF5GMtJZngjlIq51eRaJeaiHbuPRm3gUAQHG/SJYhWUA5j8mpIzoqx1\no/w0P1RLkE3x3z5YbWEin7ynJHtojHFq8Bb1Q9ziwoo9C4ZU5ppHWr0MXHKc\n8X07pqFUIOLzkn6BWpl5egLyotL3wCM7vf9RBLRFiIlqOTZqV8+ceyTgm29i\nKKwRCDkvogUl6tdvxmKMGXS1LoFRR+mHKUmyAiqtLzsi3n+EZLvjE5tk0gix\nvz1yYqYlULnMAL+Oz/wr17xqNfkyL3992Gv38zS1pnTJ3n1bXFQVUaDbD1oa\nooeaGEl21l6LH8EuQKAO83fA/wY8ka6E3/i4Fp1or+mj8JzPg1BnilsKba5P\nK0MomBbqkqRmVu1zyPyxp7LOU2kq9ZS0SJX4+dIJU4DHth2yfTj0bosdQkr8\nZgeH\r\n=GoIZ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"1e829ecb62c5ee81439aecf5dfb1cd4db2602703","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-19T03:23:34.898Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.7","@balena/jellyfish-logger":"^4.0.17","@balena/jellyfish-metrics":"^2.0.22","@balena/jellyfish-environment":"^6.0.9"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.10","@types/pg":"^8.6.4","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.1.7","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_12.2.4_1642562743080_0.03852807177267148","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"12.2.5-renovate-typedoc-0-x-b6cd39ce858eabd6f6a1fc1614acf3c22736ccd4":{"name":"@balena/jellyfish-core","version":"12.2.5-renovate-typedoc-0-x-b6cd39ce858eabd6f6a1fc1614acf3c22736ccd4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@12.2.5-renovate-typedoc-0-x-b6cd39ce858eabd6f6a1fc1614acf3c22736ccd4","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4a127e002a264c25d90145ea2fa0d8b5b39fb661","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-12.2.5-renovate-typedoc-0-x-b6cd39ce858eabd6f6a1fc1614acf3c22736ccd4.tgz","fileCount":217,"integrity":"sha512-KgSRUkPe0bsf+IYHhpRHNq1ClsJ0xJidDttwDQajIFZ4RCB9dB87qzD8Wl3jDbHfzXhnagk5/3O8c60A9KkRDg==","signatures":[{"sig":"MEMCIFQ2MFn4Wn94NnV/4ylFjbykjHGVwmNr6GD2UFHlkzaxAh9L4QEfnDoxi6XzBL0i24qlyIAl8nvmm49PWij8K2DE","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":811044,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh55tECRA9TVsSAnZWagAAtwQP/i67oNcMt/bA9/S5//h9\nGDlwwKVdEdnCtZAunHqbA4xbJw845s3ir0umJtHoiK7DD36CLo5WGoExiNXq\n5qgXuBs5fDM5TD0IoKin4usOYdMtodEntAFTXFxSMp0RSEloSGlp2kvARZbZ\nHPBGV+EhBkjjQWkjrpZjr5YSqvpfa1PNUg2LxSvmPp7dHY5D/wLFP94+hLL9\n1VK6kyk+VFd3Y4wgneAXjp82UAeJZwzQmMa+YtpQ/QZE+9b9w/aPhDoF5SSC\ngxUJT6L0jjIugdAbc0X35en8+NF5MWrKAeO+kWunB0BZ0oJYYY+OtSxlCshM\no77ljPGMlKMfY0HIk+rylD4q50I8v6TyDzv3/CYJdjJ3gQvwhqjbbJHnZPz5\nHEb7YgGrrLhJO+Nl3ZEkUuI5y+4H1Eap7VUBcDN1ugXu+47PLZ9tDsO/0WKY\nCLDvEkFRn1iyAPFKfXkLcN2HB9JWnlt0Ysu/5aE9P3qxyaJUPXN9a1VIws/N\nSP8kFM7WfeEaFpKwDcaOa5WGNw34YO6y/HhXyhvu9pxHIxTqi//LEUhA8IdM\n1FRheEDqVigKYk1e1Zd3RgWERhzh+95A8fQL9juFmO7P+Bn4mNDSnCVu8PmR\neMP9HjYBZ/Mwiu48UfoDT8K/zlJ7zAwRWqY3Uo3Dm7zdzdZprROx5UyfK3MB\nUHq/\r\n=7tsQ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b6cd39ce858eabd6f6a1fc1614acf3c22736ccd4","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-19T04:58:28.284Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.7","@balena/jellyfish-logger":"^4.0.17","@balena/jellyfish-metrics":"^2.0.22","@balena/jellyfish-environment":"^6.0.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.1.7","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_12.2.5-renovate-typedoc-0-x-b6cd39ce858eabd6f6a1fc1614acf3c22736ccd4_1642568516586_0.13447313505727054","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"12.2.5":{"name":"@balena/jellyfish-core","version":"12.2.5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@12.2.5","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"bda4c11153257207562a23afd48c23c63ee326d9","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-12.2.5.tgz","fileCount":217,"integrity":"sha512-bfcCoC2k4hn0+vXl6Cp6W8g2o9TlernOy6CctOB5umU86k9Q1U+lgP0Eu8ylkFIZiwOhig+konB+kfPzdHmh7w==","signatures":[{"sig":"MEQCIEbG2jxXWKCQfzWfQeDq5UBtMW2z/1ySq6w0hj4gSfMLAiAph+lF4W0I+TaiUIjYyGk30rY6dlk0yN4y7zi4PWbq0w==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":810982,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh550+CRA9TVsSAnZWagAAlKYP/RPLiX41bDE6yp18Y32j\nM3hoMzuk513PAcVUKehretBsaRYsDVJ2ST0ulXUnMXUOzQpRiXMIUJ74HKte\nWqcQ9AZPi3FpeechfFGUuII3f42K2jW2Cfwc5yrb6ewioIR0lK+pTUgtqMB5\nybQv6o916bVJmVUANP6Dxk9l286z9TEJGZxGF8hJzQ+cBKKM2kiwO9/0PW4m\nIXkYtQjO/a7YqL7hT9aBpJmIPIPYt9cxffGG9Fps5UtoI+Jrnai69eswpxzU\noWHnNTCFvC3ZICrmODAt0gGuq9ffGs2coq8BUJDc3VR/41jdZcsimvmkV0/W\nKxWMW58gdsBfeRNFg+Ny9mvP4bkTs9QfQwyBCJ1Wj2mHSOCk3yfto8CIoTVF\nnKi3K+kvv5NOH+bJ8aC5jeYA2VfW/iu4fB2uZEylH4KMb0+tvP+WiJ10x07C\n29rHFpOU8doUV5J8MVdz+jGZz3gfNV/3yiaB0VVDB5BuB1H1Z1E3MVy1j13X\nnV/dhfVcuVSE77SSPbdWcXYl/I+b9f+9wpUazoD+UP1UdNbiCYUdvdSzhvwm\nB7XA4h21qIxk+Db5Ntajh58DTY9DU6e91J+yDmATA2JVXaiKhgEGJh7XgQBa\n5qcVefbdpS1DNkvRruJEbAgDBomadab1FL7y0K1dDhMbP7mgO8PUtJIqduAN\n/fCa\r\n=zQwK\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d9d63208d74a854f111489ec3b9b67073cabd456","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-19T05:07:52.738Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.7","@balena/jellyfish-logger":"^4.0.17","@balena/jellyfish-metrics":"^2.0.22","@balena/jellyfish-environment":"^6.0.9"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.1.7","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_12.2.5_1642569021866_0.48122829941635326","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"13.0.0-joshbwlng-remove-action-contracts-92098221205075a5db44ad6a4f8ee5dfb7d992e9":{"name":"@balena/jellyfish-core","version":"13.0.0-joshbwlng-remove-action-contracts-92098221205075a5db44ad6a4f8ee5dfb7d992e9","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@13.0.0-joshbwlng-remove-action-contracts-92098221205075a5db44ad6a4f8ee5dfb7d992e9","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6654dd30b713d71be2a8f85d104dbf229ad3dfb5","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-13.0.0-joshbwlng-remove-action-contracts-92098221205075a5db44ad6a4f8ee5dfb7d992e9.tgz","fileCount":208,"integrity":"sha512-ywNjwCFJp45J1wbaeaoh/dA/kr/Qm0AlAIS7mG88IQN+3hpsDqbMEVPUJWEWyPYgnIxq9FBunDjVKBqTMG0qSA==","signatures":[{"sig":"MEUCIQDTL+R682QoalhXycf3pF5gCiMeFKdBVc6RCghfrD7A5gIgL01bAeaujyiVwDgve1vvWiV7h+TER9vnaJ754iB7KOs=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":795844,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh56OJCRA9TVsSAnZWagAACygP/0J+CVsy2t2G3gI443/h\nTF4aPImQtEzE4nO5RAx6bvYjA9JtxLnssMwV6eXR2gWbAOwUz05KV1g8WioD\nbG/+e+H15GZtK35BvPPD1hlPnBwyfaPTPNtj8zmdIu16RYu9n2ut4xEIpkFE\n/APk1Gw6150LTOd05GOr2C0As1pDED3EIKKFr9OAjwd+eig3VYDLeRFeUvS8\nmDqECm0ZIbkP7l0tPOzODbRSYO4Csz4ZJVL+kjGNGA8lgJYRywqtASrEICDM\ncXalnFlntda3DL+qXTWzFwHCJizNaaULzW4QNFDFE63lxG1TKvj7baKMnJgL\nSP0hJ0w7UTVF+35IlnuIvnVvMXi6rS2KHfvrNftQl20PXe4SFOUzpHCoIR6X\nY+F++AurVM934far7t7S9JSH60KFZVre9lT1LNUS9Q64h5Sosz8AiSVZbSUp\nMhF7RlzeCzLmhUzzfBcmPUil5mGnma/zOc7zi60+EpXYJXsvsCpZQZTPrgXX\n5HUtDRKmSw/nS4rnJ1bXHS8GPJWNonPRJFXMtW8jXWZcEi+eBsa2lawO28iA\nSNot1BLiLj+NYw8GUJmuEjcLZt08WhzZ/2YNv9OM84EzoDiwKaT+hGhUfHmQ\nuONUOVK/sthKjjx2xS5MaMoCpYqQqYtpBKoVSqfmh4pOw6YPvB5E6F88DXEq\nZm+G\r\n=PvkK\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"92098221205075a5db44ad6a4f8ee5dfb7d992e9","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-19T05:34:15.181Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.7","@balena/jellyfish-logger":"^4.0.17","@balena/jellyfish-metrics":"^2.0.22","@balena/jellyfish-environment":"^6.0.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.1.7","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_13.0.0-joshbwlng-remove-action-contracts-92098221205075a5db44ad6a4f8ee5dfb7d992e9_1642570633369_0.3478181153567874","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"13.0.0":{"name":"@balena/jellyfish-core","version":"13.0.0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@13.0.0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5496f9c6df0f05eee3a2ade921c19ba26d9059d7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-13.0.0.tgz","fileCount":208,"integrity":"sha512-QmoMkT6x6cJZwThepx5BBa1u0HM1mo9Tj8Fiw5X/aQssrUxHcOLYZhbev4gPUsHB3puLknuTHxIOg6QUDmfaAA==","signatures":[{"sig":"MEQCIDzeBMkqhnPuQXsTF9aeNLfki6BAAeyY27c3OUxzBoZ7AiBu1qgVZc3vVmaK5cd3upN0jHjS0DdHmQqavsFca1v0fQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":795769,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh56YCCRA9TVsSAnZWagAAxY4P/1eEm7Gt4uIneSWyt73M\nzRPoBEua7xPJSxbc7AueHjoRoHO99z7jgaeJnC9sCDzUupOybSMwQv9195No\nz579RU8uc4q+LCsPi54FFI5AAbpcbJjEeypCrHLwB1coxTz8wEvSpjdbpMZA\nValcxWngZBdF7bq2nZm7uo4e8NzuUQJK+iNdrihRRFyUjXgaDbjkyPjzRdVq\nyWP6nR6KdONYcdWwJGojkO0Xjx2sQxmNbCnDIMQ8RM9XmqJnbWZlCZKoIw0Z\nceXv3xEBLkLzcL8ke/Vjj5DYtK7qufRvotwXThOdx743jjMEHZr1pMFOOD2O\nahsAYeiqrbW0fhpl2DqcHfjN3JNGUrJ19UHJVRW2dvvh6wB/mGsNLLUDtiFH\nqrVFtqNcowOfqPCUv0TlFsnHAy9AnqwKGfrPf+kVZzxqXjWcbeC3tp86g2MW\nrsaWTjzyKSBr7BF7bTyceITpEiSDmsZMn2HWsRRRozkPf8vNYxvQ3AbFcYHy\nTYBkCg88L5Vh5uHO8tMGR/J0sVDCnSloMYquVYtYhoLzTSNJ36Y2oIlTVO76\nQFtX/uszH8UjsV/iWEMRIPEZdjyaL57pMgAh/676y8zbEG7Pj5jgaChgL0+C\nhosBEH+mh8eaV7kLOcUQU046+cBRDM7lta4q7ZE6dqmw7bh3cmUM+jxe1aHn\nXhyv\r\n=cR7x\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0680cd6d5ddbfa14f16bd5decc010da96aa89baf","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-19T05:45:38.387Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.7","@balena/jellyfish-logger":"^4.0.17","@balena/jellyfish-metrics":"^2.0.22","@balena/jellyfish-environment":"^6.0.9"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.1.7","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_13.0.0_1642571266726_0.2978617469092588","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"13.1.0-joshbwlng-role-guest-d626c0eafbf22674ad379fa781545133a9933e42":{"name":"@balena/jellyfish-core","version":"13.1.0-joshbwlng-role-guest-d626c0eafbf22674ad379fa781545133a9933e42","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@13.1.0-joshbwlng-role-guest-d626c0eafbf22674ad379fa781545133a9933e42","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ed524cfa91d19c9c96afffaa7140e9faed2ce220","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-13.1.0-joshbwlng-role-guest-d626c0eafbf22674ad379fa781545133a9933e42.tgz","fileCount":211,"integrity":"sha512-REGihNfSXB9fQ4t1pa223zuCWKAO2EA72i07q7qiocfXfPzhmsmJ8m3xOtnjZjLy3yVf2RWEk/dh4FVkcSNaMg==","signatures":[{"sig":"MEUCIQCGOYwGxwthmvnHWM4c8+vF6CwgA1KLQZDPjVj2XE7ZAgIgPd6AlaluYq3EPota2+YKJNPKxLGpfkR4CcXypRxelP4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":806751,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh57QlCRA9TVsSAnZWagAAoYsQAJ47W0ViFXQC3uv9A7wB\npPHgt4DwQ7Mt8JrOKWXsxqL6fUVu+w15BxzZOdRkZrbAxzXW+zx/zRy6Rn3T\nRfc4inhO5Gm2CsVR6rP9vH0BrX6rnHFv4SDFg9DLNtw1O/9lR5tTOol8hKK1\nC7Z1FlJlckuoXZOByTfX5YJ06jLqdhaUExtvxpdXbS06LOaQZ1gVjP9VcJt0\nHYtNJQu7L7BSIQJ4syPJsIqEPs3DE0MgyO/F+R5sivTap2XAwBxJfkzR4oLT\n281Di8AtGx3hLJUU+G1vOa4Oc48ZBHzifMjjkFuYA7WV90RXxwwwWVYWEzbc\ncnJsmLGGD5YobWrjhZz+zQBf0YNorOoIHbrRI8XokZRu1fM0yd/oPIuU6s7z\nfqR3D9EiVv5zBMpKlH7npxHeaaHD5b8BOy5UiMGRKvId7PZ2l2PSPoLJnH4Z\nGcOz73E6eaA/JOotdEzP+y/vIjzSqjerNIZYGfQMNcmr3/KWnlKo/NKig+4n\nIgZTxtzsiaEtHqP8257V8DgQ2u+3WcXt+GRCeqlfH73dM2MSNKj0nr3XuGIT\nxI4NywtOcVBttU363c5m8mUHXINQyd8qOQBNzQ0id9t9I6+e7D9yG0YcKxW7\nMEY7a5IgQfFTGzq7kc+rg8Q1f0weG6SN4C5Eyx21KoboKvd9nEoXtS4RPDmL\naP+B\r\n=6vHv\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d626c0eafbf22674ad379fa781545133a9933e42","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-19T06:44:34.201Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.7","@balena/jellyfish-logger":"^4.0.17","@balena/jellyfish-metrics":"^2.0.22","@balena/jellyfish-environment":"^6.0.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.1.7","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_13.1.0-joshbwlng-role-guest-d626c0eafbf22674ad379fa781545133a9933e42_1642574885637_0.8077100982747936","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"13.1.0-joshbwlng-role-guest-d40eb822451a3a64b25d0e1edd26e886b5d37592":{"name":"@balena/jellyfish-core","version":"13.1.0-joshbwlng-role-guest-d40eb822451a3a64b25d0e1edd26e886b5d37592","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@13.1.0-joshbwlng-role-guest-d40eb822451a3a64b25d0e1edd26e886b5d37592","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"04148da12643e01ecab7280b4ecbdead9546af90","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-13.1.0-joshbwlng-role-guest-d40eb822451a3a64b25d0e1edd26e886b5d37592.tgz","fileCount":211,"integrity":"sha512-MXtxLx7dngeFRSqaxgQ3ObYyFatK9VjmPC9VOem0biym/DO6E1UpHIM4a826av/n3nvhDm6BK+mz5+4iN7EC4w==","signatures":[{"sig":"MEUCID2PEMZ4jsLETdgi/Cpzpn9tbpcoqXMnt8L4bDH7SCHmAiEAyMzsSgeR3McnBJ/7Al2whSKLFY3VgWFppNEP9IKSVbI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":806863,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh57ojCRA9TVsSAnZWagAA6JoP/RbG1OakpCQsbNOIEQdj\ndXD8gDYFgbPxPFFcD4vpnaqVGHv5OGSiBRxqiK2DEkkzwR/64Seow/kjEZi8\nExEXTm0TbGYyeNysemMeUGYnRjN6F7ROOg2GoE0JqLyQpBSYTimh3nMgaFCW\nQ036EgxFa6/nCdtbmTylcNuuvVntUf84w1BCoWkQ+5mOPHZyMRvdOYhpIwVK\nyxU76vLpRwKzEHf0SF5rFkl75pHSmBwqtGB+nP0lUoYk/+3oOyVZ3eityXxS\nqIpAilhypQh6PJ3OnY7T++XWlwLeYvuj5i/qul1pWClysg7ogEsWthCuYY15\nBFiTl3NsUBN3nRbueAxJbqg+7pfbGXzZi6hyikU5RTPGHDas8tGKJN52OKJa\nuvNZzwKjI1L5mTz9PaXRW6S3ksiYaFH3qQ0CdoSIX+sKDGGBMUvEPAZ3CXgC\ngW/wY8GLKKYFaSpDvCkfhtFFTxbjJpCFD17JHuFj5NbKzR9DaXmX8AOTVZx+\nhUHIckuf5/fUJb/Ppq73zm93aRiBR99yNf4+jtrA6sZ8Ph2Gv1+b2wq3QjEa\nksYw/TENs758nJ5zAdwJhzPhapA2SV6RM3M4xvyLmWHLJhslY/uixLnK+GGg\nXefqy8ZO7yAx/igyUMObD6iN9APZkqNux8xBYe+Y8AqGbsgYesoYTeoKJ+ik\ngzmy\r\n=Dtt8\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d40eb822451a3a64b25d0e1edd26e886b5d37592","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-19T07:11:07.219Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.7","@balena/jellyfish-logger":"^4.0.17","@balena/jellyfish-metrics":"^2.0.22","@balena/jellyfish-environment":"^6.0.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.1.7","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_13.1.0-joshbwlng-role-guest-d40eb822451a3a64b25d0e1edd26e886b5d37592_1642576419550_0.06132111318679634","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"13.1.0":{"name":"@balena/jellyfish-core","version":"13.1.0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@13.1.0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6fe9728536df144a0c9ca52b703e41fe29aca524","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-13.1.0.tgz","fileCount":211,"integrity":"sha512-t8zhx7KjgVoN4z7L0uSQoiJghtHrLPLZxqP2KyHa3yGFdsWc4H2SpsN8qNlNejzLyo9mc8qo01t1jjHgzZVFjA==","signatures":[{"sig":"MEYCIQCwjysBnK4sKkycp1Tt1ABc9T8LNAEhNzwZtCcxSq1cMQIhALf3tcJYqyTUfD8/zCllnlsjepsdpG4HG9e2GAoJZQXi","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":806801,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh575qCRA9TVsSAnZWagAAuy4QAIuHxw/yIoVh8uXRgtHm\n2pR8yaIBKRqekRE3xVZvzk2YpMtOAIvYpFBp00u27g3oxgal9rzNyykN35C/\nzaRBWEl17/v152IGj0H0MPMt5QMrvBFjfCcy9AxSLNyIjKkZvzY6E524poTE\n/PnWl7jq3eUV5dhsaxo3K/AD4HHWf4i8SRxGAUCjHVdGfPcX64s4fLHRo6wT\nc2xpAVim4oWSwcUnN0PmR5n43vGCsqIQ+WGsWmHmKkUa0YgQWXePOmmjTnU+\newoIO6dgEfbV7+n/1vlKj4ku4SzCiQKBpaMHEcb41SJfjz2kQ0VNbiG+sM81\nhf41Io8O7FUvXfRDH940UIEpKtWEKSoNNpJUqhaixSVwwMOt4bNmWyiWCC8b\nA9T08KArzAeYoySgfcvMIWA+1q3Ndy0h47LUJ57CYoW15iiNRyjx/O7EB/OX\ngb5SnSKRY+7yH7+LbhetZaCT9fnOiMyoR4B0ZwAHFj/umsYMzEvOqNsh5LJa\nNKjn1+8a+s4PCCrBWuEtBJus34KzBRwcV8AKoZQ3Dg4Yc4wzvfhAeqBp0J2g\nc6zNFLRDyFwigQyRseyVPlbKPs1tZvOlVCwKW/CF98WdisRsw0KxbJ4IKPNV\ngRGPr3co3poLzFqbtQMxpONQjyeKdFMZWeapyJ9YufcSRXIvzVL6pxGggqRT\n/ukL\r\n=Yjfq\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"9c85a6bde8aa756873ff59201d94bd334d98b929","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-19T07:29:45.944Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.7","@balena/jellyfish-logger":"^4.0.17","@balena/jellyfish-metrics":"^2.0.22","@balena/jellyfish-environment":"^6.0.9"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.1.7","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_13.1.0_1642577514072_0.08682662765747584","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"13.1.1-renovate-internal-non-major-310ad6dcb8005c474b1db89e89697a43cfc843e6":{"name":"@balena/jellyfish-core","version":"13.1.1-renovate-internal-non-major-310ad6dcb8005c474b1db89e89697a43cfc843e6","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@13.1.1-renovate-internal-non-major-310ad6dcb8005c474b1db89e89697a43cfc843e6","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6d48fa3702171c61dca4e353d639f0d237867881","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-13.1.1-renovate-internal-non-major-310ad6dcb8005c474b1db89e89697a43cfc843e6.tgz","fileCount":211,"integrity":"sha512-lICwbR/FXE0Qq9VEq/wKzmQoXvu4/YWSWZNH7U8uSJz3RN1OEAIBruJUitBCb6XLjtH9uhEHjrSGobY87RmpLQ==","signatures":[{"sig":"MEYCIQDin/v/bvvgEcCUUNksyf7CCd2YAuMVEi2+or+4Qc7c7gIhAIX/i1enshxQQjgju+2uDgOspCv6A8sI46HT4XQIPMlZ","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":806946,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh6Bw1CRA9TVsSAnZWagAABj8P/ReL8Tywv0khJDOQtnoj\nsFVA0VtU1zZcC1KU7FBt4C9Zpsd+gTm//gCPfjSjdGcnd4KkTn/CQhH5YQKy\nkwSveEWYkNW1x9ZeYO5XST0kA3k+000N9+bu9jMhc/hVOLz9wy8sb7Fe2ZOc\nC3C/KdqcBQZv8EaehvmBfVPQUmNYPvOgihGR3zbC0BTyuz3TwLKS8dTl9uxG\nMkOQ9a3ySUT6/36Q+2UA1dnp70r8efef+Hm3i5GpmNJSN+/sCN2Pr0b2BnRI\nBc2xGgLUhWEa3fZTDCSHH/x1nJ/+emqr8NU6ddN0iuCzElplWsiQFLmsqgNa\nuve8EnxzGfoUcuiCjVVFwb5YZ0RmP4s8GVkDlc8iMx2SXCWXKrJ74DD1CBg/\nj/MvLpJPeZy3bVEI+J3u1n2JGwTNvYXBPajM3lORuu/pVtdnQTGz9T1tTIcu\nzcy0oN7OMxmmlfyIYK/4rFDBTgvsqoueQfSeUVc5BRbnmIw69uVhbtIk7EQG\nu9TMP5hE0haxAOC+wbmrKcT8poqqL5G2fyQxDmkPo5ErPfi8r1u1Xr3G84RR\njLXxJvJUyjHChzqb9ZwXdek19chq0HDPvQ8Nups9/KuNCmmRyvw84hmozsxf\nJxH3TSKCFgdj7fSr6TM0h1O+W7C3iBrlYNF8Zjz1YII/A7XNmXMbjA6wifgx\niQqv\r\n=PUID\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"310ad6dcb8005c474b1db89e89697a43cfc843e6","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-19T14:08:33.328Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.8","@balena/jellyfish-logger":"^4.0.19","@balena/jellyfish-metrics":"^2.0.25","@balena/jellyfish-environment":"^6.0.10"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.1.7","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_13.1.1-renovate-internal-non-major-310ad6dcb8005c474b1db89e89697a43cfc843e6_1642601525011_0.02879421057772591","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"13.1.1":{"name":"@balena/jellyfish-core","version":"13.1.1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@13.1.1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"030031f41d4ed4c63949f303c02693136220cd4e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-13.1.1.tgz","fileCount":211,"integrity":"sha512-UMvlxtvRIkBtDB+G0ecn2P7CPFZfnya1NSBJYEYWGJjpWJAHMuWj3mrljtTeENiNaKAAJTuYI127BXm4S7WxIQ==","signatures":[{"sig":"MEQCIB6vfTAxW/WR/e7tQ5gD3pvkOXIy8vmV3A0k6/O03VMkAiBvEq4K9sUEAiLRQNyK4lN2+UOlAH57kVM6QRwZ3qnuHw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":806877,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh6B2rCRA9TVsSAnZWagAArp0P/2NixDFfvOo+b6TSRM9o\nV//+nf4mSDQXiJ2C3eAq35WQmSwgHWnBCsNhKeYlAlSWmssgFq0hOqErIYIa\nkP5H/Qp4CM4HBem/A/e0dwXWEww+D93T6I4NAo6gU1k7wn4Tp73qWu1KYDmW\n0MGC0a1M06VOk4fC2LuwVGNGJQJcN/XV0ESXRhaBCrodLrwBT1hHO7iHze6z\n4+DlxHJDRhRrzedq+fIUxwKz+/Adkf0YK+06TVHXTH8qSwXgenGQE1B5yt8o\n1d8WXDe+qlDhlyxL/DBdfMarNCrIEqadw+I3u3ulBFr+eY2r4VB3soYMoyBV\ngeR8q4fmDZpq10kc1+KMMDwgwDyni0rk71AglawsbyM5swVD8rSntAX4PuOh\n7CRug/JrJDD73/4Ic2ccVJ8ot8+Q59lY9IjoRosIAMW9oIzO4AXZMaKrnrFG\njt5rynEw32oWBFPdfSwPL8Df69bQX2Jrk4nuCUBGxAeGXp6d0I6R2bsz/JE1\nItng0QF9rLcJveObFRX1H9EgH7bVrCkKNOJASuCqaatWJKRennQ4fvozq/sI\n6MHml+T/cpiHZnvtisoIIa/rkRuMyvtn+a43QBXk3hAb9Dczm4vKPRzFYyd5\nSFXuE2ju8Bqq3YkPJDGv2Jx9MArH+N8x7UtcVZ/GFZwLxapppDlzImAzkzTV\nXt8O\r\n=gJ0/\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f61079ff04c34ee0a81c89b79ec59cf53dd5cc02","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-19T14:16:18.918Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.8","@balena/jellyfish-logger":"^4.0.19","@balena/jellyfish-metrics":"^2.0.25","@balena/jellyfish-environment":"^6.0.10"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.1.7","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_13.1.1_1642601899344_0.22941498564836316","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"13.1.2-renovate-major-json-schema-org-40d2d92b2539e2a26ea467c90ca981ee7e4a217b":{"name":"@balena/jellyfish-core","version":"13.1.2-renovate-major-json-schema-org-40d2d92b2539e2a26ea467c90ca981ee7e4a217b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@13.1.2-renovate-major-json-schema-org-40d2d92b2539e2a26ea467c90ca981ee7e4a217b","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"53077d238086822aecde1295a1e69e7992894ab1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-13.1.2-renovate-major-json-schema-org-40d2d92b2539e2a26ea467c90ca981ee7e4a217b.tgz","fileCount":211,"integrity":"sha512-bf8WPTJKdAblV3/aaZXvr08s5JY9n8PswIylNAe4GQ9xvhXtPyAeb5OMpjIk0WD6ivV6DPFl97h8YBuGQJgrbg==","signatures":[{"sig":"MEUCIF7llHIyea8Ld4WNe9l/HjuEp/N2HH1+iH94xe4hSbz9AiEAjH5awdUXEROVHqZipwwJKTiMNTJ/51EHS+K/0pfriqU=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":807045,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh6B5tCRA9TVsSAnZWagAAQQ0QAJcDyMyac93wX9ZF78k6\n2PkYVNWA4cGiCsabXt4g8rraGnAlcriE1PCmup1/ReIR1XbLVDIyGW16Cvzy\n037y+78O/K/nmlXqdNMjjncNRdQYVDMANpbmztPZCr6LoxSMIxXyXbuMQac0\nNhKn5wUyCLaqe34G0GrwWw7YkgxnG2w5BKov4vVNE59Mvl05b8NJjgdVZoPN\nPtAgMG1UiktUkxhRMaZGOGpeix2AjYsBGT7zk3rOP96AbX5egNHkSTh0fGNp\njeuZAVsYJA76Q88UeRSqJHTbJaOwNK18H7KqZvV9RncOL43neaktQJRFu6xT\nVAw3lPUywkkeSTbiWXAjp9UI8r+01hS18WEtrGJ+Zo9RMFxKd82A4ymMhCgk\njp7heKHG0C19jY6IZSGxjsd9bMQk6AsTkDZG0ZXU60M/gVcwzD/JriwF76ot\nFlnFrtpqY1VcXnGancBh06uZZyWD1BY4dB0XOCUXzFuZC+3V9viw485RUs88\nxB3vMHzl8ZnPi1M95IZDIkz6QrDGlmn/YJgspFbUt9olN+EcmjK/C3V/rSg9\nCRzMXw/YEzKb7VlXXTKUUKisfiP2r0t8sIOwSZUXqVkrru+UVw+U2aGivwnQ\nvLs7dgTJYk0y10vAEGm75EZHbpOdK8JNGZNLTVhK66DVM9gLsVK5sKbOfAJV\nFXdS\r\n=Sns+\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"40d2d92b2539e2a26ea467c90ca981ee7e4a217b","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-19T14:18:29.229Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.8","@balena/jellyfish-logger":"^4.0.19","@balena/jellyfish-metrics":"^2.0.25","@balena/jellyfish-environment":"^6.0.10"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.1.7","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_13.1.2-renovate-major-json-schema-org-40d2d92b2539e2a26ea467c90ca981ee7e4a217b_1642602093006_0.03019675146964329","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"13.2.0-joshbwlng-move-roles-eafcdd96cd036f523db8c7d181ff56a7e3f53094":{"name":"@balena/jellyfish-core","version":"13.2.0-joshbwlng-move-roles-eafcdd96cd036f523db8c7d181ff56a7e3f53094","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@13.2.0-joshbwlng-move-roles-eafcdd96cd036f523db8c7d181ff56a7e3f53094","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a70c8f478c5901b1fe35392705a554867e9f2dce","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-13.2.0-joshbwlng-move-roles-eafcdd96cd036f523db8c7d181ff56a7e3f53094.tgz","fileCount":220,"integrity":"sha512-3TAiXwgFtxNOlp5YuF5VZaigRtwU5CGX16AnsGVrBpJ0+kzm+GdSFQ1MgnWmSKYToE+arcJLs9hKcxJhPG+DLw==","signatures":[{"sig":"MEQCIFy7Hu+Yjo36GUWKmNzi6NDcWzLyfs6Xq+0nz1j7U9ysAiAnk+vG5EElh0Pj+bKaVkbSLeHQzCJYrCMZV4x0AZ40AA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":827715,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh6PHNCRA9TVsSAnZWagAATgQQAIYmqEdNlcah/Se+yduk\nXpT5vFEanTZV2vYG8UCmN2/k8ch3nRhoUAIJiCdlq3YMt2OLIsKKKGZhr+jJ\n/VFpgbkumtzS3vwVBYHNQ96AzlZbwtY79UHJ5omf7+8AizkfWn62BNI8sEAX\n1alfhyLI+IM4kNpU7p7HNo0IlGv7p0HnnpMXwDr0LRkkhN1tSHZFF47B/yAu\nCqWCMa6+KwgS8zlstECZModYWIe54a6LisKiOFAdG8aTNZXPd+YwY8WeDfJ8\naCuCttzrRhELq1ctY44rdAfO4h1MlUc3FKrAURcZL21Ry3EReE+WK5Y9FUFI\nCNlcNYRH3bINsdFUjG3j2Nm/SLU0uO+4hEn7rHilNTnhMgJdduTbo3Sbxdru\nvpUZyTY7mDt7DzFBXTMPYBTPkPQLgVOIqzcotCcEIV/YizbZwXOHlbnbKBSI\nHow8ve1RSCoIP1UAIW7QJ+Jm4KbELW+hMbKPN/NoEJqo0V12hx1KkD7ntLhh\nifkkkit4RyFn35g6RbzVHVlUQpa4PbNsIZKZNnqxFw0utwHS+/wyBsp1piAi\nWPJo+5eizwjiHgNd6IqHj2LS1VTQa3HBRJSPuXCDqSyDXRx4Yv6hr5jMfy/t\nkkpjmcCFbrnjDT73RrQz0uBrSBIH2nfEZw11Wv4YepLvTuTBYVvp9xm1Gb0x\nWoeL\r\n=npSj\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"eafcdd96cd036f523db8c7d181ff56a7e3f53094","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-20T05:20:08.339Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.8","@balena/jellyfish-logger":"^4.0.19","@balena/jellyfish-metrics":"^2.0.25","@balena/jellyfish-environment":"^6.0.10"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.1.7","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_13.2.0-joshbwlng-move-roles-eafcdd96cd036f523db8c7d181ff56a7e3f53094_1642656204853_0.046997556187144296","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"13.2.0":{"name":"@balena/jellyfish-core","version":"13.2.0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@13.2.0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ee61094935fc8becc1e47023937fde76c92965df","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-13.2.0.tgz","fileCount":220,"integrity":"sha512-IciLoEGH/bV+HRxARkje2sr6D/a48BV3/zIhbV2AaL3Khda7zl2pIFX1kyTw/VtDwVszNOf5MOkA8qbBCwH/dg==","signatures":[{"sig":"MEYCIQDeQ9tA4LF7Br9M70wlnTxk0C6OO6yJXEzevRBXolN5UgIhAOtG/E6Qpzg2v6MBy6B1s6mto5Q07wUEijpNZigdBB5M","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":827653,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh6PN5CRA9TVsSAnZWagAA1UgP+gNovQhw0kiphS4vhMUK\nFfWe6Z3KpBSgCqHeyuOhARu85o1FKVg3M1cJn3jQ84V+plnNA+4IvzuRnavg\nF84b9iIr3mJx3/LpZQa45fKxGYry87QbFIzuMgt2Twd1BrWZ7z0VfkvlAiW5\nqrZAp/sCOqBXdreUCWZU3+yKOesoHa8j7AGHBl3NhOw21DVcqCW+ZDW4TPPV\n3NeSYaa1sIP7pPodPzQkFFdtl2YCj20Orh22WzSaihplcldH5EUwBowfPCc+\ntgHPu3Jdpw6WhLy0ib3/JLHJQsUnwAAcrMu17I8Ds3TnzKDCSBl31Tgts85Y\n/KWWmBUqo0XRK3hsz8YW1ZhEfIIsbp2aGeYeCcPDf1IWQXZQVllJgrqTjZAU\n4qOKOo4xEUg2/LdAl5bq7hVZ0PETPARxAUvZ9hYgf8l9Kf5oK4S3N75/h576\naInvyvhz54jEDAuvR6U9G6PrjpvL4+hrtfiIgwdMqY6IgPRmOZX+SAk0Db1L\nV8bg9WmQtShySj6jNEvgUp0is4VxV2NU/j3AZjoEIycXUo2ttY+2r2vbSsF6\n1QdJQjg7N/lJXWj+T/bSP8IHSrATvfUg+3LtDx7SK3yYx6+UWyWIlmi1ud59\nU+GnoeGqy70xDqaNOO2IUlnvUkHkWL4D3QJYMv0LHZ6zVmxtzoAJqSo0jTG6\ntSeH\r\n=hP07\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a141bf66ebca46ba95b295b37704dcb428310ca3","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-20T05:28:38.243Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.8","@balena/jellyfish-logger":"^4.0.19","@balena/jellyfish-metrics":"^2.0.25","@balena/jellyfish-environment":"^6.0.10"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.1.7","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_13.2.0_1642656633400_0.4262920011226641","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"13.3.0-joshbwlng-community-role-test-a42731e0cd6bf270e026961b0ab9053220a21e3d":{"name":"@balena/jellyfish-core","version":"13.3.0-joshbwlng-community-role-test-a42731e0cd6bf270e026961b0ab9053220a21e3d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@13.3.0-joshbwlng-community-role-test-a42731e0cd6bf270e026961b0ab9053220a21e3d","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"47757f477280e6b065a3bd78cba0b716f8c85334","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-13.3.0-joshbwlng-community-role-test-a42731e0cd6bf270e026961b0ab9053220a21e3d.tgz","fileCount":220,"integrity":"sha512-EKgSnc2zG35SMIAVGPKfsPMAIvGt7f+dHhM1L06Nn6pzDl6+5JSYiAXdPVJXX+I/4mVoApAbE6jagBHzIqjEDg==","signatures":[{"sig":"MEYCIQCWlmnHhSBcROXRiWFcV3a2TtSNNZb7pIaFniAIRB+MkQIhALRku8lc45289yZHqQdaLqACIOSBi4Zi8dVDjaliMcPV","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":830213,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh6QpVCRA9TVsSAnZWagAAhtIP/0GfCKqp77vNcIS0ozS/\nYgq6aO75MldqKD6H3HSgFY+JRg8AHVSldEUi27o41xjDeI1v6X9lrQGQerDs\nuNY+N8EAlDUlGCNLBo4zyMeE6sCmi9KL6LgOQWIexeId2J1SOO3ffZ5GCOeC\n5jIUfuXtc6IvCT6UZdWlJLv/2qmwBnJD7PncZHpktv/RFtHUpmtRfp54QS3w\nEVjGFf4xG/bxLyk8qSjLuCz0A/tAKRh17TS6IaHsUJFmCByT5OEwQ5d1NJRV\nesNjchIYfHBOM0F0kjCxd3H8fk+V8AXN4pFliRKERIkD9SuxtchP+V1PqhTo\nQ/BGBC3+nXcT8ph2AENKW30iFK2VRl0R7nWxTUc0jpKiuonI1bypjAGcSi03\nlHVXY1cExC0ql0L/A7Yw1fkq2gjSXm1kLv0ywD9uAR81Kt0/TblCf4rVmsz6\n2JiUpsb/KydbpyElnnaKfj1w6HfAc9hRcj+7QjDEtDoOTh1/mHPuGo0BNvrM\n2YzcJv9LmZ4RwgunzBiiYb/kNTbRRXSpGV3nYE8sZ4o8Xmcs1EEW43UmVClR\ncYjNxh1NLwCWidNEHTAXA0QFJI3x6T3W+szVyL6ddRj8tg98oOSfjYVUB2nK\n/0lAj1qLt6NEOZgrAjoxpuheOqkUlA0gXgJXpvkyxnQHW7IupamEUTLiTAV4\nkoZ6\r\n=k5qw\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a42731e0cd6bf270e026961b0ab9053220a21e3d","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-20T07:04:46.954Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.8","@balena/jellyfish-logger":"^4.0.19","@balena/jellyfish-metrics":"^2.0.25","@balena/jellyfish-environment":"^6.0.10"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.1.7","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_13.3.0-joshbwlng-community-role-test-a42731e0cd6bf270e026961b0ab9053220a21e3d_1642662485230_0.5945633485152655","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"13.3.0-joshbwlng-community-role-test-60ea23c8a211950928f01e62f81e863bf4e1abc1":{"name":"@balena/jellyfish-core","version":"13.3.0-joshbwlng-community-role-test-60ea23c8a211950928f01e62f81e863bf4e1abc1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@13.3.0-joshbwlng-community-role-test-60ea23c8a211950928f01e62f81e863bf4e1abc1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"87d14c80ccdd1e46137e82c09019e6049db156b0","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-13.3.0-joshbwlng-community-role-test-60ea23c8a211950928f01e62f81e863bf4e1abc1.tgz","fileCount":220,"integrity":"sha512-ZJeluTD4oaEUU/I030cOBzVtAxDf1nNwRFJhfcDVAuD7OX0BWFZ4alGiOnsIj3sYm3iJKOd2OMafb/vq5IA+7w==","signatures":[{"sig":"MEUCIQCJ3na5y1WZW7MCrOQCxcL+DnVmy+/6gDvvZ6DVwcn8TgIgDdpT/ZJOWoLQVngokRChmkfC2lVomZFe/RXwLXC19Ag=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":830242,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh6Qx5CRA9TVsSAnZWagAAzUUP/25TcbCQHu+T2Cq4io5m\nMSNX+GZqKvrcVsnS2r3VWe+QxxEkXBUIyz4s1BfcSw5gEzL4WawGeG/Gpbhd\nNxJ4jKh9NU5i1NQCGuVpRZmMVm8ew4gg47LG+IubmlfjHlr9mN33HAivILtX\nRyBCcfAgheu4/U6Kop36rIvemnCOe0CglGprvTGy+AY8CAr+Cavwg7z3FfkJ\n4eRFHGSoGBJR4OdbWOtwzPQ6UIa2JrYSPjUfRvz0VlZGEVbVmuLJGDAMZb8T\nYkzpU/PIrznvdgrZd82vO1xKY2vnMMCv7ZzHZ8fCZ9GNOVz+whoiVCvDLLEo\nNENZnlvhW+OwcTKS8mRme5/67SjH9HG13kcxjXG8CJOJ+EhUBe5epr+Gkq9k\njP+GmfBOVTaZKN+yiheqy3U0UVGJ5X+pFgKxDEaEi9jekyzra9rQw6Hn7rbv\ny9I0qytaFFNTzHNkQedpWxLaKo6A1XhZ0ZetGI404sF2QWIXhKG7akYoOMGx\n9D/XZV/UF9lhKhc94h/DYmdzChBfLsLzJrYLOQyV+syURY2DVfPXof/evYnj\nhKR1biMQl1YucHG2P/orbPnmztl1bsK9RSMeC8INJr2IRzk8xM2FadcPcqcX\nU5Q+VM7/orBLvG5EvJj9cfOr/kgSFg/qFY0vbewlx/cQ+AUV29TQAJXwzIfR\nG4gQ\r\n=7FkR\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"60ea23c8a211950928f01e62f81e863bf4e1abc1","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-20T07:14:11.786Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.8","@balena/jellyfish-logger":"^4.0.19","@balena/jellyfish-metrics":"^2.0.25","@balena/jellyfish-environment":"^6.0.10"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.1.7","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_13.3.0-joshbwlng-community-role-test-60ea23c8a211950928f01e62f81e863bf4e1abc1_1642663033634_0.17830667267418","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"13.3.0":{"name":"@balena/jellyfish-core","version":"13.3.0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@13.3.0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"54c1178756801f33813cb2fe9af4a2814d7460f5","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-13.3.0.tgz","fileCount":220,"integrity":"sha512-WpzhX6xUWs1JctjNeFKqw4wgT9cqVISmSx99tAppS0bvpF4EcxWwXyZvKZl8S/d+LC7lCoVhBJAaaWY0Man/vQ==","signatures":[{"sig":"MEUCIBGlz7m9cGUB5cq3L4Yy3cz1+wekwlvUr98tdER57vGNAiEAg80YacfgbjD3eYiJ2qhxHoWlQ2lzYzOM8E1AhVL6n/Q=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":830171,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh6Q3rCRA9TVsSAnZWagAAoz4P/iEVeZYMrNV59PLzOLtw\nfW/7uWr8EZBQhMeVrsPiPh0MmLbbmnTE8VcUaYmKRrZKJgJn7oCC0+ECWcx9\nO1f6lbVfRuoi7ik8B4ESjjrCcfBHEidm3mvHLa31Q92UxpJqHG5l69IWAXTD\nzUMK08AGTtQMePS3FEECV3j9+ZdruBGL2GsNZW2BfsWxdZQPLMTvd2yFR3ub\nR93Bwvg9IQRtv4xyFlcAbHhuSSGrHwG3BjFUG8icPHDUJcPfQQZoFsHafhV4\nxB2z2IpTkXLyCKKdZzPKDjRRNYbqqrOskeVZM5plz9RBthwsakDn2zedCXjn\nb0oKh9XpnOqNX8XQaPaBlYmtV7K/K7XufOp8AOI3gKoDR5/q7KZpFuQ1gxOu\njBg3bPs3gLqvOzqJNJiDd5ZwaXOWu5Phl9VbR3xBQ/5RPMIstO8h+SGOohnf\n/IfdnNmIO3RKWFvAI0J5BFntUTP/I9Pn87gDaDUai70GLzQ4M4l/EX0ja7kQ\n7BedjrpoJ/6zaub5z6qC5YQzeMIywMnFTj1NYBbzYmvwG4oAktJJi0b5bznb\njzArxHrzqm5v7cKE+4bx94HXzaIPp9FdPNEv92rMmsDAsB9FVnAM1m5P1Eqi\n72ZPGMAbFgYjgtoJibd3nesl6I0S2Khg0l6r71OD+J4fxr+kd7zTFI17dpy4\nbW96\r\n=f5DB\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"43df809a577165debedd72ada9dd346a66eac644","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-20T07:21:23.462Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.8","@balena/jellyfish-logger":"^4.0.19","@balena/jellyfish-metrics":"^2.0.25","@balena/jellyfish-environment":"^6.0.10"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.1.7","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_13.3.0_1642663403370_0.9595242697780175","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.0.0-fix-defaults-83cdba9f77941d7cbbd42ec73e40ea84a109e6eb":{"name":"@balena/jellyfish-core","version":"14.0.0-fix-defaults-83cdba9f77941d7cbbd42ec73e40ea84a109e6eb","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.0.0-fix-defaults-83cdba9f77941d7cbbd42ec73e40ea84a109e6eb","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"16c693bd661501fc8282eca911b2248caf811330","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.0.0-fix-defaults-83cdba9f77941d7cbbd42ec73e40ea84a109e6eb.tgz","fileCount":220,"integrity":"sha512-KDtMeAEX9pKVwi5A3CPCebI57V4VdVWtkNkoQtC1uCzMRTETzBA239+maKmPKw9EMuBvDZtIoFm6Rzp0aok+ug==","signatures":[{"sig":"MEUCIGC0jyGw636/Nndt+fNIOy7Fa8dCCMrEbmbqPHJ3Be0EAiEAztocqoR84HGiAMzxy+/RWEJpu+2kFTON79kns9+7UDI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":830177,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh6dOACRA9TVsSAnZWagAA9S4P/1ZSjAe4P1y4bi4t9tAF\ntvGGURjg45wAz/OZXY0sKWnC8EgFk4s/x+orTSw4weXzXvh1tSusESh4f7gG\no5X/2Pe2zHa8DE/SHMVRX7vhqmV+zNAskvM3LQ322/LV3SUP7+4pGPpWacC4\nhW/LISLU29UbkJirurYIcCKmU/yCz+ShBLytp9XN86PzVhdUQTk3eOy5ESZc\n11jc+F8QCXF8qEa8g0tV5Fe3/+8XQRyq5xzKxP34wUA0n7UBE6aDw7MZKbL1\nU6IooxpOewETyzyKERHMvldvpv8z9YI1WEzuoQy6bNsOSmkthzKwXjwDq5Rf\nehFdev/JxQBwIKnaWYHJEQls0dSWNidpjxGTLcXNOViGVpSRj9OpD+0BTEJf\nhmpSaDwjkOGOMG9XYqLmcICWZAJZuzzhw2LO4IHpi7NWMnYFciNuwqIrTsq5\nAz5BU7Mlhq7dI5ZkzY7Y3dwLQCCFTmYVHpiAwXzsnXD+sTVuADQRAis7lVId\noEopla3tipc1uM1J0Wxz8R/zhlvA+z+9GA0fadpoOkblkxBwiGKtNXUPwNkg\ng2labotzkI/E0uXh7hye6roJ72v5NMfKvQQeDMPUZ931geZoSoonJuCNGqnA\nK6+FtOc/E4PL/dyOSEFUw2yjlgP9fZkyuE6R0hKqQk5DNk8L6cuJd9onm2W3\nIkK6\r\n=5JDt\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"83cdba9f77941d7cbbd42ec73e40ea84a109e6eb","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-20T21:23:40.034Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.8","@balena/jellyfish-logger":"^4.0.19","@balena/jellyfish-metrics":"^2.0.25","@balena/jellyfish-environment":"^6.0.10"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.1.7","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.0.0-fix-defaults-83cdba9f77941d7cbbd42ec73e40ea84a109e6eb_1642713984369_0.3192864480258466","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.0.0":{"name":"@balena/jellyfish-core","version":"14.0.0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.0.0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"29aa693193573578c85f283c96ccd64c58805304","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.0.0.tgz","fileCount":220,"integrity":"sha512-fso3TU3ku0ZriFHLbfwyz1k/RqYKtpK4oU9DWSm75dCGKXD414zqmFB+wQiwjOK1aeJ4/w66e28rfrZBfJh2rg==","signatures":[{"sig":"MEQCIHkUyfFBU6MfzZKq3f/gclX4HGjVwMSUbaiV9ipMnXucAiA6S3V5+A7FPLbeaBw9cTRACfGV/zTnUhxe56fhXsF+xA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":830123,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh6dTuCRA9TVsSAnZWagAAv5kQAJOUgSTbWPYNDyJs+ll6\nreLnEO4emUEyCx/XN/bQhIjlFxrhkFu6nkRY3eOe8i5fiw6Na2ArlWZuzbZM\nRnbvZBbPrKIEU547TVG8X89dbEf6mcyo/6r3PqczesXs2XcTbZSPCRCoFYy6\nNv/P/xGUkW+ILgm5ShkhXnmOAfKkPgUPNsOVPlbWtcL1pfjIQj0mxZqUBwJ0\nGkeRnqj+oykv+kE+/+wCzKpABosfiGTM54m9F1pwPyGmA5QAM0mn2PH4MFJd\nAudFEMAon+5T/sFiN6HhpAjBe8vq6TZBYi5qrQyYMucwFtJh+Yaq6LelkWwo\nFMvXLND7mfHCdvJZzDsknXxkYz3BdKOrFURKn8N9NVhbN+WnTpy+rAhc7Rmk\nphudnGuwOdfc0WyUNI0snkByWm0PRkPIT/lSSWNBmtmHAFd6eHX4fzsWorXl\n/ZKt/gBlOF6LXp6l9UoHj52CPSiwNSTknYEUDnb15gvW8Ix0Y5fuTtJD0sVo\nbWFKr85ApGg2Kk0W4hiRItgyNhHYwztM3/P6gFtKMST1Racz/BTn96Jjg4X1\n9an0NpQHb83ZiqcctEXh5U7UVJOcPwGIfPqGv3IkG+ja+wDxX+xRjDfukAn4\n2TwfznwHb+FZ9NMs/d37N7m241QlHnZIJKgr/BkX0hjul+UMXUVFL+yPlXHp\ncbaJ\r\n=/kQm\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"aaf9ccf10071e779737be753a72056cfb59219e9","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-20T21:30:39.110Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.8","@balena/jellyfish-logger":"^4.0.19","@balena/jellyfish-metrics":"^2.0.25","@balena/jellyfish-environment":"^6.0.10"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.1.7","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.0.0_1642714350576_0.18508399922930696","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.0.1-renovate-major-json-schema-org-7e5690d854d0f892714cb7c11d24ac729b84fb07":{"name":"@balena/jellyfish-core","version":"14.0.1-renovate-major-json-schema-org-7e5690d854d0f892714cb7c11d24ac729b84fb07","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.0.1-renovate-major-json-schema-org-7e5690d854d0f892714cb7c11d24ac729b84fb07","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e17ad1747c38dca58e20e780cc293cd2ce804ffb","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.0.1-renovate-major-json-schema-org-7e5690d854d0f892714cb7c11d24ac729b84fb07.tgz","fileCount":220,"integrity":"sha512-k5AI8N8+DOfHW98PKsX2Lt49oGihJP3OavkA7NTcfIUKOafTrxfDXLM7AAgVNFi8TdXj3Sge/blnLpinc9LbCQ==","signatures":[{"sig":"MEYCIQDhseXspZdbSjyKssHZxOH/LG0a4aQoqCHcToJhZ+XRswIhAKeCxLntZ4FcRgquHIJim4UmsWbTClJ0Art0a+kltZLc","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":830291,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh6dYCCRA9TVsSAnZWagAAvCgP/AihJuS1tI8Uz/ReLUw3\ns1ZXGqo6Yr+zNQvVISaDniYQav9+sZATqMsAicyeT1YwOUYiMOAebyHsrWH6\nHWQKRNPWPOBEsxMYDl4nxbrcxeQa1MT3+nHG/KjTmnIQC0mq4xKFMVmeSqi/\nGKaaqs7oRPbNTy2hBSpa8K+xdrNbzQQIp0lNrgTdE7WleM1YsaC9m0xS7zpi\nSL4HHbKs7lDl4idRtb8YVqQJHPQ/DW7iRL6bjQ4l6/O7WeF7sJPdJHRCO6Nd\nHxJvl9hczUObXQut+bE5Lc5OscamjKE/Jp9s/9fvgjuEiAtksuB8/UhGAesd\nKiHPa/zAR85B1gWJIDJ5hTml4ZGXuGDHUgd7E5pNcbCa5bulvfqGrz6Culsk\nnOSTXzMWEkWvEbM7d+GKoO4T07/1LFTOZLePH7gSi//sW2wIRuI05xsKBSOt\nwilaYYmylzEyqiVZ5HpVMxLIYyyw05pS/aCFkysVDnoh6aiflOW0Zw/x/+XU\nq8McrsShtcPlUOR2Y9+d6g8IyfI8prD74nN7aFm5DMlWOPlvPGl3AC+KhLeZ\nbC5tRCt4yD4UgS69fpa9AGM5D2ffnKmiG2BQTo1n/4NXp9L3vvZoWFl8b3Qc\nJw+T0mII+4ynBcSAoO2CE6bdsZI7BeiHzpIcTBMvNXA7OgmiUBbv9FcbhrlE\nCjI/\r\n=CDTv\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7e5690d854d0f892714cb7c11d24ac729b84fb07","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-20T21:34:08.360Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.8","@balena/jellyfish-logger":"^4.0.19","@balena/jellyfish-metrics":"^2.0.25","@balena/jellyfish-environment":"^6.0.10"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.1.7","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.0.1-renovate-major-json-schema-org-7e5690d854d0f892714cb7c11d24ac729b84fb07_1642714626667_0.9866712366289179","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.0.1-edufschmidt-move-types-into-core-8d998111aa63471cb241b10cd2142dca33d21ed4":{"name":"@balena/jellyfish-core","version":"14.0.1-edufschmidt-move-types-into-core-8d998111aa63471cb241b10cd2142dca33d21ed4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.0.1-edufschmidt-move-types-into-core-8d998111aa63471cb241b10cd2142dca33d21ed4","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"352828af608d5de47ade988136aad2f68f31dae5","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.0.1-edufschmidt-move-types-into-core-8d998111aa63471cb241b10cd2142dca33d21ed4.tgz","fileCount":151,"integrity":"sha512-opVp68r4ikxlcKaIJ5CFJ/BIOF57JRqq1651gZbgKxBBcXkYbtiN1QzVNRXX4jf+gUzBdcYVh7tHO0r8kGPBAQ==","signatures":[{"sig":"MEUCIQCYvgtT7CTniQXbo7suEtgAeyzaHURbzw6SMQcNX1psPgIgBBlcoxjadT3eN+I9OcmUE+VnappaETaMmca1YizpK44=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":684759,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh6ujxCRA9TVsSAnZWagAAnskQAI46EJMUMVmTwgyQ/F2X\neKAivJEMXfJohhTgbDveYhwJ00ljSO/QaOyoCXTKKKZFv0NxRAyD+SJLoiYa\nOyHbc+b+7GxQmF2slf6Uw491EjyJxpaDoAxvmQ8MSXh7l3uaYIorNdI9G5Bs\neMyxG01fsJJMkgXwTEOhEY5J13EzbEllDmjgc4c2al4S1dhcWSB2LyMzHXAQ\nZcXLyI1MXwpwNZuD//Oke1SyHpO1/sNxbBKZ4CQMPRxUgG+z1gUAVikmfnu1\n8KejyKC8dJN6deD4Ju63JY1g38+NE24NyyCElARlQD4deffj1X8lEacyoVRw\n+OmtYm6h2h3st++AG+J7ZP6ve7YnJ0ayRG+xafeERYsLjOUrbjlTnLecHmmc\nTiD1ZXqUtIJvE3II5Of8dEjd4hKem5ZGmzDK3I1Mqzzf2is+944siKd3FPzT\nfqh9tLlM8syq/fLXWTJghCsfg6plaFo02Mhz4USR1eAU1jH33785ekgR+We2\nWLrEpBoisaZnchPNmiu3YfFlqpj26XatDmjdWUBx3aC2aqy3VWWMtw/tSesW\nGci+p+iHD+bVxTZb3ql2Z2G2GOKSJvWq9gM02b40VvawqLrEKyHh5GMOZlYv\n2cVXHV+33tpbxzTWtbPOIc8DTC+GQht6rf9tYQ9nZHQ9HAaTcg6k++wbte9G\n44+4\r\n=7pUK\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8d998111aa63471cb241b10cd2142dca33d21ed4","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && npm run types && tsc -p tsconfig.build.json","clean":"rimraf build","types":"node scripts/generate-contract-interfaces.js","compose":"docker-compose up","prepack":"npm run build","lint:fix":"npm run types && balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-21T17:06:09.973Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.8","@balena/jellyfish-logger":"^4.0.19","@balena/jellyfish-metrics":"^2.0.25","json-schema-to-typescript":"^10.1.5","@balena/jellyfish-environment":"^6.0.10"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.1.7","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/lodash":"^4.14.178","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.0.1-edufschmidt-move-types-into-core-8d998111aa63471cb241b10cd2142dca33d21ed4_1642785009349_0.02024507306767198","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.0.1-renovate-external-non-major-0a00877898255fa3331416e5e3ff0cf90834b2fa":{"name":"@balena/jellyfish-core","version":"14.0.1-renovate-external-non-major-0a00877898255fa3331416e5e3ff0cf90834b2fa","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.0.1-renovate-external-non-major-0a00877898255fa3331416e5e3ff0cf90834b2fa","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"cc456461c96651531c33c0ef9df5c3ef2f038ae1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.0.1-renovate-external-non-major-0a00877898255fa3331416e5e3ff0cf90834b2fa.tgz","fileCount":220,"integrity":"sha512-7PbIhuvMHfFXJkH0IaMUdYaTXu9+MdmjYrAbkbZbEnok+3ggGlVzUt7kdkuDgjAwO/Jtiz/GAjfDglt5iWLJtQ==","signatures":[{"sig":"MEUCIQCaxMXNs1oGRSAEdjwjEKZ6M72m+4X1jqXaWHrTfovKHAIgMG0lZgjjF5AERouHZB5Jj4WwVzUcwBV/nJYPeQ1gXZk=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":830267,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh604qCRA9TVsSAnZWagAADvgP/jWFjjK4Qoi+Sxmh6U45\nSPYyGzt8jbvIFapuD168XR15tAkHPHB0s60jTCQrvRJ92eNsUutBhzB4+f/o\neUXAzxSq0wOhE9UvlJw/LStGOUl2+PmY6jnI1Vszmgew0T1cjpthZTR9rWop\nPrDvR7O7Y/TADmnPAXzjrGVl03q8jBtXOEo3o4ezH/vZtiW66mjQfxcNOfou\npF/UKOiU2jHCCkyd5+zOcwWVWHpYX4YCIYyGXNNi5B+4H72WFQRxbj3REIyM\ndRj4dPNV5IpYXY77mgkwZv87DeYc8QaunYXECokEWIlFPvWsbMfiN+d0Rpj1\nDbKX4oK7Xkfo5Ov4j4UJX506gNRi86fRKMrqKw/djIkgCeikLAuo/Nw/LY9S\neUlWz9pUk8cuG2J4OblD8C4suoNJuq9uhMeCiOnlQk1xOMQdG6yO25l88k7P\nRq7wkkP1qN0J6r+A60K2ErVldOX37B4/oVsZdEyu+UPZEbmKltzc26F36NIp\nF8aGBNIv5KNwlSHsT/sU6BW93r4UiAqEgCe80SEjPd6AIIyKt73PLNgPcoNL\n5nfW5tjGo4BK3UzYveRVfdtVO/+UjW5rr9HmphBknau0oKtkefJKpM9yWCNq\nuVwbxU5rHF7GB5c4wN4FlMaShWsA2KPwh8H/sCeiDQO81bzt9LJDbmRMk9u2\nTS5O\r\n=S6MU\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0a00877898255fa3331416e5e3ff0cf90834b2fa","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-22T00:18:34.624Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.8","@balena/jellyfish-logger":"^4.0.19","@balena/jellyfish-metrics":"^2.0.25","@balena/jellyfish-environment":"^6.0.10"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.2.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.0.1-renovate-external-non-major-0a00877898255fa3331416e5e3ff0cf90834b2fa_1642810922460_0.30833345517482136","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.0.1-renovate-internal-non-major-a5a707c7023968315fb5dc7272b869562524f771":{"name":"@balena/jellyfish-core","version":"14.0.1-renovate-internal-non-major-a5a707c7023968315fb5dc7272b869562524f771","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.0.1-renovate-internal-non-major-a5a707c7023968315fb5dc7272b869562524f771","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"7a5cad3c8a4b05e2366e1b0f94bb2e80537cc93c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.0.1-renovate-internal-non-major-a5a707c7023968315fb5dc7272b869562524f771.tgz","fileCount":220,"integrity":"sha512-wvNC3uajuWUHsQobWBRe2oxyBsocxz1MC4tpYJW3kjh+XcFD4/JGFa/1sMa4dViqh6I+XFB8vIJNaRaUZMIMng==","signatures":[{"sig":"MEQCICqo4cYn9vptwv/7ycTa1KkTrFNplEAUDdp+G7GRrWEZAiA8AbTZMbOYburnYNOhRH+w8uinsoGLPB16EayTCPj4Qw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":830346,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh61D9CRA9TVsSAnZWagAADAQP/ju4PWqhXvGOS710iDsj\nDYoxGx9gfDCz9wNtgAVZyxzvm6Iq+qdTFBH3GGPh2Ge75X6sz10mKw+jvaev\nssAM2DKszcbid1NiKq1LZApYToHVgeiq47xNFNETxhdiIkhvEmKUYZt1xexR\n87UfsPK8R5ohMiPNOqbw6dJetQ4vEHL/D7g8Fl505mYNhAKPNPt/LDD/k2A5\n5f3WSoFB4sxEa3AKO8XW2yI2u9GshWx3JtA+76PXWdPRB4mn0ibj0DCKjchk\nQffQ8HB/eYdsPwj3pgw5XlzDxqz3p/qmYzNa5q7mnGVnuJtL+w4iUCJT/YAv\nkVBs7vagX7+O46k5LLKdFW1fpAYC2d5gIX7pZEertf5TbD0BcPQcEiOGzohm\nwGm9CTssO5IbKWvdYGR4qjefOtUpgu9Ru7F9RcEPocPDwTOPo3q/k11FKk8O\nhMSUDTk+uRlFADyLfQgm6/meugCTT82mXFp6xLulpewkSlBMGTC2J8sxkiEg\nYgnU3AQpkH1zd1q75QaoscHskdmynwojAeQm5yT0LsY0PZf8NBlHdwuNshqz\nyQGjP3Z8syqqxAViAan0gsVXkrBlxXiIGThvCSFxDkoF1y1oO6Uci+MFsQlB\nLGX9qLbBMo9xhVp2CzOzgfbod7dWZnk9qfZc1y1Zhg0aYYmCcpQd1MSxsZxC\nH4Qq\r\n=x7De\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a5a707c7023968315fb5dc7272b869562524f771","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-22T00:31:05.964Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.8","@balena/jellyfish-logger":"^4.0.19","@balena/jellyfish-metrics":"^2.0.26","@balena/jellyfish-environment":"^6.0.10"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.2.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.0.1-renovate-internal-non-major-a5a707c7023968315fb5dc7272b869562524f771_1642811645480_0.06729499865713806","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.0.1-renovate-major-json-schema-org-8442e8b0faded1dbe0292d94485f86c088fbe13e":{"name":"@balena/jellyfish-core","version":"14.0.1-renovate-major-json-schema-org-8442e8b0faded1dbe0292d94485f86c088fbe13e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.0.1-renovate-major-json-schema-org-8442e8b0faded1dbe0292d94485f86c088fbe13e","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"67af2bbc09133d5cbbbf8ddf417f42a3dbea3bc6","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.0.1-renovate-major-json-schema-org-8442e8b0faded1dbe0292d94485f86c088fbe13e.tgz","fileCount":220,"integrity":"sha512-gLDt76LuoICD21aWrzRo2soMsDp/L2pY51IaFKBNbyjlJW4Bg4sAuEyoghz3ZZNsgqv6ndgIgrCtdo7pucqRDg==","signatures":[{"sig":"MEQCIA6TR9yykvLTnwTptPyqrJjDKr+N0uUo7n6pAZgoRji0AiB9hIvZ77ZX98SJOE6kOfbp+JjwtxYb6wefDeIq7F9xhg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":830341,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh61EjCRA9TVsSAnZWagAACLMP/1Rv2yKJX580wWMzk8kC\n5lND2R5YQkVR9IskOW2vXiK4eHBuy965kLtBauIhKAIu5tUztwapkVV3nBzk\n1eJTHbzokQ9i+oCuN1hHeC/Zb7XF78lgcwJRHqPudhf21l/4IA+MvPQHIHoz\n5Mt4rqCGnNbm0RhF1sEiKMrDVyjlMBCUEHWJz23EQtMK4YpisXgvIPgvKjzf\nSyheIEZp4ZW+47C5OuQv9U+IKmc0eFeG+vf5k7FpoRiBMMkwqhrVWqDC4NJv\n8/hxg3v6RNo+Ml3Z3d/om/SCVSl40gBsjbjdjRkMTLwG6jc2geMtxdbn/nfG\n9HqdnPZNN/I0VU9OIfDPuY2tv36X1xz5WuxrW2lMBjVCmGDpcaEGKftk+eXZ\n1Dh4HgQzQ55FCDGjupMNZ5l6LkMR7hShOFUYwM1JBpGmVo62HIUgDtxnQwrn\n+ffLvN6sAmuA7fTU9rw/AdDveacqER70u2XD4zjafTwXffN/mhN4iG68meEz\nBB/noGLsa5N1z21fTj04f3S37uJoOmCnbfTL+AS5TTJKTGs9YmjhFEP/wMrf\n/nlCyvGkG1DUBXHs9eUQjqCsCLyNktsKro/cB5aqCH/rfNkxegdphx+vW4mn\nshrIkgWfsAwOMT/NjuvgL20A+G4oRtd0y2LXos8st1cQjPUK/fuI4SL4PZmX\nOTs7\r\n=kQX3\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8442e8b0faded1dbe0292d94485f86c088fbe13e","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-22T00:31:10.235Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.8","@balena/jellyfish-logger":"^4.0.19","@balena/jellyfish-metrics":"^2.0.25","@balena/jellyfish-environment":"^6.0.10"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.2.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.0.1-renovate-major-json-schema-org-8442e8b0faded1dbe0292d94485f86c088fbe13e_1642811683606_0.3405322937180766","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.0.1":{"name":"@balena/jellyfish-core","version":"14.0.1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.0.1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1bb04ba1e1ff5b7212d26f7ca91336705e91c559","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.0.1.tgz","fileCount":220,"integrity":"sha512-EInJDHvZP5gHL3vSDuYE9klhwVw8mE1+BUtsT1N3mXHKT4e9fpMyKVV6I18jrJO454KrZxPoN/i5ntnh156nwg==","signatures":[{"sig":"MEUCIQDEZGe3vzImkitP1lCwRsV7GPd/zJOgtHVMOA3d9PriNwIgH1dJl5Q1JDdGyldhVCyL+U9EFnXPCwfYyDmQKWVio7g=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":830277,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh61K0CRA9TVsSAnZWagAAGAQP/207gFTRPY1773Q7mCUn\nWkQiudAJBA8xnf3J+2TjBoi/qXiWWeO0DAIp4V6YhlLuTA1rW9v96U1id7vH\nDPcBth06e0fz3RkRTs++RSbSSKHunxxE8vIwUpNZ5B4+WVO/f8sXNVpIlGj9\nG3hxS03Za2tBCanyJCC1hlZflyqu1r453FqRBOUuCH/02YPEivo26lWeEY6P\nS09ZSlgAaPWCcVb75ruVa92mrH8hdMYwDf4ot342Mt20HAEb5OyVw5Xk2OT3\nTpM0NH3ccxLZYGpYEc3gXAKildjrM1tp/OVXDBnkeHzBnOeW7zE79YQ/JIJZ\n9xj95+IPb5FcjnIVhqrqgh8VtqzBziNHWl7REFU4yafJljB+qVaia5MWKVMl\nB/UwuvQGPo6zC+0Bkbc6xx91DXOWWtGIAzNJcUw8pbjMOTAVlOc3xTTvRMoN\nZ2g6/z4WGzOkGeiy87s0uuoGNTOPq+/rqSg1vMTQv+Caec77Hhu1y+n2pTQd\n5qy7sptWHSHfJa2CC42/FgrItP4UILgjZploAkazRT3pRxpzWR9nrp7yiGEi\nqME0NFXjuANp4+/5r6kFobjkK4ZSO6ggNGKypZjm02j2YXVoGQ9OWCM2B/Gw\nrDuwgbZh7q/LZarT63+FelEBZozcvrV5GjEevFTpOxwC/lfOGwKuo/ZQrX8U\npHQr\r\n=lFPU\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"46d5cf352da757114590be40010e0b353f580dfa","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-22T00:39:03.867Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.8","@balena/jellyfish-logger":"^4.0.19","@balena/jellyfish-metrics":"^2.0.26","@balena/jellyfish-environment":"^6.0.10"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.2.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.0.1_1642812083771_0.4721406786693747","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.0.2-renovate-major-json-schema-org-c4001fc267fc90648b22fe59b57c69d1bd09f9a4":{"name":"@balena/jellyfish-core","version":"14.0.2-renovate-major-json-schema-org-c4001fc267fc90648b22fe59b57c69d1bd09f9a4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.0.2-renovate-major-json-schema-org-c4001fc267fc90648b22fe59b57c69d1bd09f9a4","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e45c544f709401ce8ee5a0def4d59a8b88432949","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.0.2-renovate-major-json-schema-org-c4001fc267fc90648b22fe59b57c69d1bd09f9a4.tgz","fileCount":220,"integrity":"sha512-J0Zea8s2+2RaYGBmFGAaCBji3xrhOR7nj6akO88PymwC6Ty5geHMBgxFjNMAB7aIFKe6hnpKw1x2XWNoK6PURw==","signatures":[{"sig":"MEYCIQCuIncoO/WY1VsSwW3NnGeJsSatBfDhh5lOVPwhZ4nvswIhAMSp+/BTpOOwz0lYRB3hCqVEAHu9uhiovxtDeabt9HtP","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":830445,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh61N9CRA9TVsSAnZWagAAv1gP+wdwjC581k0+G0zLZ5Zt\nv8IalZ1sWZxgzWiaUfCZqJ2OG3nrVmK54nyur8jBK96mnG/esmk5J7Gyt68B\nfGsPc5N12EHBKrUpXiuDWb0z0WF+CRQgzRFLDwEoL2mpGUz7E9trFLw7LFZE\nebQUoAmRZLXxcUoOWuBuuyNgyYXteu10nXAwzE2X1+yZISWLdIoscMG31BBn\nY06tlo7N1/2qDVUL+ejP9ANPdL+cltOgIN4qfDHHSvhAGIqGDzG0uVrKfqJn\npTD7GFpAYfwSOIBbs0d3ZWYT3MEpidgGZaGR3ofdhfdmUue0gv8ulkEMmgmf\nc6t97UkKQ/QATGiGmXJ3TJd4Si2b0JlZ+kLvCosSePvggSwe4z++6NPwIdcr\n6/6hWxLlnao0jLQyRwlZBIK0j8Qeq0EmjYdDilgu4bR+TQpaTD1c/fsNoJFL\n5bbog6o0XuN0hSxSUD7lZApMyVDKgjhU8RkCAT6ooFk8CVzvcLEjS2jP5j6G\nXJmCDJTnOb72X4N7ty9NpZYK7mKEWsZk6yd7+KYAQ6XaJqc4dqP80seyToXT\nJqO+8mhNLZBh9LuMOrc9+WSwGlveaPn/eYRNDLeZ5fV35epT6blWs/0fJ9kt\ncOV3rxepd9lcYZWRta1hBOiq09dKqNzW6nW7/8ajN7N97Q4RiRkQ2uSQSY3k\nV8gE\r\n=Z+TU\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c4001fc267fc90648b22fe59b57c69d1bd09f9a4","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-22T00:41:29.591Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.8","@balena/jellyfish-logger":"^4.0.19","@balena/jellyfish-metrics":"^2.0.26","@balena/jellyfish-environment":"^6.0.10"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.2.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.0.2-renovate-major-json-schema-org-c4001fc267fc90648b22fe59b57c69d1bd09f9a4_1642812284862_0.09188960066671203","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.0.2-renovate-internal-non-major-954739e13c0e3f078ef19650851038ba7ee6ee99":{"name":"@balena/jellyfish-core","version":"14.0.2-renovate-internal-non-major-954739e13c0e3f078ef19650851038ba7ee6ee99","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.0.2-renovate-internal-non-major-954739e13c0e3f078ef19650851038ba7ee6ee99","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"bafb53ff55c99f155ee8c7a4f72857db4566cf72","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.0.2-renovate-internal-non-major-954739e13c0e3f078ef19650851038ba7ee6ee99.tgz","fileCount":220,"integrity":"sha512-WFt4We070mgi3SfUSXzkr42tLGKi4E3G0Wk6U5rdoW9WqGVfm0EmptTPGE6bPR3e39g1HgSP6k85ozhPa069Xg==","signatures":[{"sig":"MEQCIErLNlpAcEVtMLj0kn8rQP/A/aTFTEZ+ffGdB5rqezACAiAV7eLB6519w0+Zc5cmDmEfLa19bhpkNm/ryaldrGkomA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":830421,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh63DmCRA9TVsSAnZWagAAWIoP/0PsOEMCXozcd83rIyJp\nA3iDG4Ynq8nGOBMqQ8ehLuQWbwSlrRxuKTSy7YbrCg8f3I5t5DkxDuO14Zfg\nJSWzaD0hwIjG8ZxMqiJY0Tg8cvz7OWxrq/1Sk7rxkzsHHwPlZ7r9NlXbABUH\nd5XBCXTRypF0u0gqoX/kzbbbxHkgff8t2ZsMjwf1J7PprR5i4PiwUhQwxFLz\nV0yThgxzXNFz1ZWur/Qr/icWVk69ccQyj3uhd26q2gMKh+zvyWfgTANLtunM\nQ4yqkjhbK/Wxt0DSS9mktkJK5v1JCRPtoSjv7lwXceqwF0UPpbLJ/bJSu/Iq\nOwCa0+oiHvHaZ7qOgaWx4mQRaYMgzyhOGr68D4yn5mIsJs6mquwX9vQVdAYM\nd10j3F/jlv7tmjkUBbw4WxtOGOPD/irRJlU+bpLVeAQ/N3pxBZKPhpitLFUY\ninFPTqMaq9JQ4rkX/Mtb8r+SJfSdEK26Vfd9m8v/4bSt5T98GYCDBDZMrnxs\nDBYW0w9ZHO76NtTL6PIW+UrghrECYSxGYHctJzxw7S8WbPjXZTpHhH1BK3CX\nQqVQc07RRXzx3pBmUoaJe40MQhPmDm5vqr+isztW5bkRdwaZTuDru7+MlzmF\nz7/jkrRBXg0QTDt0SbVGtoEKedBPRoY9Q5M8SGco9v06oK5WSLF9iodbab4U\nDPU9\r\n=DNM+\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"954739e13c0e3f078ef19650851038ba7ee6ee99","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-22T02:47:17.129Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.9","@balena/jellyfish-logger":"^4.0.21","@balena/jellyfish-metrics":"^2.0.27","@balena/jellyfish-environment":"^6.0.11"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.2.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.0.2-renovate-internal-non-major-954739e13c0e3f078ef19650851038ba7ee6ee99_1642819814308_0.7372859988015594","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.0.2":{"name":"@balena/jellyfish-core","version":"14.0.2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.0.2","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e9f9fea794a6620151732bfdb43044bf93f0f280","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.0.2.tgz","fileCount":220,"integrity":"sha512-4WI2nztqKvkbgS3qJ++RR8p3VCzyL5IeUS6LwVJiV1akSx0DecOj9enjCX4MxAqtbrY2DDwlKC+L9WGoBBZWqw==","signatures":[{"sig":"MEQCIDnzU2G7q65tcqufNYUDL1Gt0fLu9egpzBc27knrbTNCAiAU7y0bqt4/vbtAHz42796LTTeOT3vi0UZD2o2ATGoO7w==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":830352,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh63J0CRA9TVsSAnZWagAAMk4P/3YQdqKRy8JlC2vd+yFE\nDj7Z5iKYlWjvZrAu1UMPo+QSxJ3B8UXUBRWE0sDbY1BUrmzjx9kkU0OvhNl+\nfkuU56UinvJW0PJnOlk3Vf1JOhGrIVOYHI7ouwZ4OW24Xc8D10t3ffn3akrH\nIyVf5udw9q372CA3XW3C/vqD8Swk/KcTEU8mn5GxsnwlyFK0TvbCYiAapC0Z\nUJzzLfC5iSziXmIgQZwjeNnDEHkyvg/tuRhFmAYEJj+oON6Db0YEd0V2VPbz\nSZp7it5SsLvfnGh8jf1MWrL7OtSNxhev45gj02IWEMvEyihDMl1Ml5palXbJ\nqpwHMaRbGFanBP5CybLM64zfjFkXXffxLX5d5DD6zhwh2DTQverf0555p3e9\nNyK2D+ZhlaqBq2uS6j6rrj6DXHfIgVGvQaUm/NWvOkiL714QUk9PioTNziDG\nWgAwVBhozBGKe5alfC8GxFRtCs+AvXeqp4DYFa8Ht8Pg9GJAT+diA1B4Anq7\nFfWl9a0FnrghDxRlyLw/HJBMxhldcHlcWIl9PSUCO7sZVMTTuUplrV4eGBnM\nuN21PX2kezltb3vRk6uZw2ZmNw//QiR10Gkbs6miEiTCKIWCsdTdcWTKN1Qd\nGNKiAUgutsFSksgrPvWMelt74pzY/6yuWaM1yTCNvuU20QDkkHDTEKOtjrIJ\njUF5\r\n=AFFW\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"221fe0fde42df7256a98ca6152643decd58d8ca2","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-22T02:54:34.786Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.9","@balena/jellyfish-logger":"^4.0.21","@balena/jellyfish-metrics":"^2.0.27","@balena/jellyfish-environment":"^6.0.11"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.2.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.0.2_1642820212606_0.2999979566269628","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.0.3-renovate-major-json-schema-org-06f8115c54823b8d11e81064fdf92130bd0435b8":{"name":"@balena/jellyfish-core","version":"14.0.3-renovate-major-json-schema-org-06f8115c54823b8d11e81064fdf92130bd0435b8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.0.3-renovate-major-json-schema-org-06f8115c54823b8d11e81064fdf92130bd0435b8","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"164b260f0eb299896d66118d9c5ad68ca7450d36","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.0.3-renovate-major-json-schema-org-06f8115c54823b8d11e81064fdf92130bd0435b8.tgz","fileCount":220,"integrity":"sha512-UWvCH021gckq9ztQrv+3s5MQA1v0erBcmEjoB/MYWNMZvpPSiQDMPPDoIpvmeiLW/C/0VGMg7PMcDw+CFDd7Ww==","signatures":[{"sig":"MEYCIQC/eZoU5tIxvgHS7RPbPgBSA67L1jfQw3m+EJdKqocv1wIhAIV+Hu5YNH5Ul9/elhNSWbHDHlF6ftY9oYXiP8x8Ez1k","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":830520,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh63M/CRA9TVsSAnZWagAAsogP/2rmvXXgx9s/6uyTvz/A\nnDH0/lQXBQN3JQA1ikFuBDie9RylKWfJWDXMoSVmzxbSGjTF5G8rcuV8QPKm\nkpVMX0Cha1szMHF0I+7yvblLjVUxQx+elff5ymIOepNiRgi4tzcrEsZUIEj+\n9Kl9Azc4XW/pRSHEwpvdOEgsG3rI7Q6FCamJoGRrPwTxg536ufS/oRIpkiCF\nbLwegZL8RfSsYb6pyV+HxaAY+EHOyz3OUGX4vOr8DvJCbmMLvLpE2RFinE9W\nnXLhKe64jj/NAkA03Ij3DtETQ0zeynm6x5iavWMmVMUU0sDlywf2d81FXHmT\nwVPQikfzhEMBwMJ/r15h/atPFuFAfUE0yty1rT40iDTA6qMB+/4433aO1eCe\ng/ayVl0Cyq+lRaguDpS0REJ+USBXfolC84s39yqzNj0yCvUJol9hniww6rQV\nYDAry9pWrbpxKshMfKhAKg1Hjwflrwm1e3NgLNlWCYISe83KyuLjhWsXLG/l\nxtmmKal06rM5VLDfHrAh6k+I0zXxkkojGiR8YJ+A4NLgux0gYLipF7/V8X+C\n9a7KZTK0Glpmi+muD7fqcRPpniLMEo4hAiLoIwYRc26Kdo5xwajWRqnYEwUO\nb7qsuz8g0mA66wtInUxGM9sg9RdDnvs/hUexO7KfsujfC+NHsYGLHbNXf+Iy\noG70\r\n=R5yM\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"06f8115c54823b8d11e81064fdf92130bd0435b8","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-22T02:57:18.455Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.9","@balena/jellyfish-logger":"^4.0.21","@balena/jellyfish-metrics":"^2.0.27","@balena/jellyfish-environment":"^6.0.11"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.2.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.0.3-renovate-major-json-schema-org-06f8115c54823b8d11e81064fdf92130bd0435b8_1642820414909_0.7487300564225128","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.0.3-renovate-internal-non-major-eb00c09639a41d9f555bc61c991e5682083bdc9a":{"name":"@balena/jellyfish-core","version":"14.0.3-renovate-internal-non-major-eb00c09639a41d9f555bc61c991e5682083bdc9a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.0.3-renovate-internal-non-major-eb00c09639a41d9f555bc61c991e5682083bdc9a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"7f827e20fbdf85321b837b1b5336dbff02772401","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.0.3-renovate-internal-non-major-eb00c09639a41d9f555bc61c991e5682083bdc9a.tgz","fileCount":220,"integrity":"sha512-pX7xQSOEtguq9z6GWzY6OK38QK3jberIZIDvXdKe0rVvzSFx5JDvqoPM9OX2Zxyw7U65gLHxyAut7sf3509eCA==","signatures":[{"sig":"MEQCIBhBUHAlll+Dz+Skvv9qRY9Yhnt2+7vRQEAoeaJ2+EdSAiAzK9M+VTvyjefrGhw7oS5DdMGwUMn6geMIbdB6HmJQZA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":830524,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh65I+CRA9TVsSAnZWagAALD4QAJxNshqsluVQpZTyuonE\n1vc+A+lMsrKeHlVsErgxCUB3c1XwsqvCTIWIWOCNEO7NARv3enWJ8q1RXJwc\nHFAUFr71aPnEuD7Szm4x/zewfwbHS8V16UaQtWX5vuQ0x3AEGLHnYS/cKvkS\nIobhdq6iV399Nj0YWHyhmNeWLsrwe6Oxc/2q5OS9t6rUX+5xRyZvwIz2Tbe/\n5C9ozCjSbVv2yuLNdsKFxFNwMpGc2EG5opwFbLtWPGxt1x2SR/EbKuf1hBya\nmxDsDmTQa3Rd0Z8EBgPFksZTU/Q99FS51j11pQldZ3H+eLgwix6+AU5nO63P\nTappYeIfsMU2FZO+XH9nWA0yZmL5+1rmIWpTxgckAVC+9mQGr8dGjgBbNhCH\n1d+mMAPvn6O+F9SzklKCY4JHsm4zMbA6TWDS9TphY8Ub4Q8C04W3GSJxfuYa\nB1S6hkmenEifZ2hIxSEz17UEvwfbBZ7K9KQMUS3ikZKqRQz+2+Sg1mglcAJ7\n3A0R4UfZt0BlO5UJqrzytZ5WVeyW6pbwA7MOBwFmfNq3rIGuUJOqkXXZxW61\n2nNVFgeA/IJRqpnd+lNDNUDDjAo62X6ZRUshHmff6QxPwU95xbnzW7HJtl0q\nr1c6e6kMexn2wjFraa4+u+u22nOB1UQFrmd3gv5hbWtdG1p0FC+Jx0cRgl3B\nblE3\r\n=rUxp\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"eb00c09639a41d9f555bc61c991e5682083bdc9a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-22T05:09:05.566Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.9","@balena/jellyfish-logger":"^4.0.22","@balena/jellyfish-metrics":"^2.0.27","@balena/jellyfish-environment":"^6.0.11"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.2.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.0.3-renovate-internal-non-major-eb00c09639a41d9f555bc61c991e5682083bdc9a_1642828349793_0.4375411122548809","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.0.3":{"name":"@balena/jellyfish-core","version":"14.0.3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.0.3","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"028e1e70415cc9e6900bbf0388783efdc703e47b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.0.3.tgz","fileCount":220,"integrity":"sha512-z0TTgaFGm8w+DWkP+lo4URrw5jaKselRDSUcPGTplCQ6CFKSSwGjbqfa9oRU1AGsrpO6ZbJ47GAH+wMdsMvFrw==","signatures":[{"sig":"MEUCIDItbjtqD9HUVJXRsTRwOpqiVLDZ7zR23YqqCg6knYicAiEAn41AAWw4tAiDB2JA4wioJ0qH4u/lru0lzvvYLN7m8FU=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":830455,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh65QZCRA9TVsSAnZWagAAOQwP+wUuH706xzq4nWrrD/88\n4iXAogvcLv43Abdpqs7LobrZK8ASUNgZZ87a7JZVxW8efxg8V5+g5e0ZpDdJ\nvwNrYrwA6iddz+GfQo1pF+HA6rgjPguHr6ILSOgb43wlnn0AN32s3/FYlZj2\nkQ3kk7nv1ERRQuU9Tddx9n7WCd/mYUaTIB6SuXNjMFb7qTFD19pLotZGwNQv\n3grmSXX1AxOF4ncpBtmxjcH7PDDJTlPRQDGb1iKZgQwSlqnvN2oDAkl95TaC\nSvqiDbNzfp/AVsNx3poFPDDnDvPLV8G5weE8IjzVt0RErBjgar3bHfDvdZW+\nK1TuZvgLbGqHUUi6ZJjCLLdpfst/ZO/zs0p7txqUwwE30wAi5oXDDNiQGcjz\nUHOhdiN0N4B5znaMRaqnCTAvJo7X6YmpaJkCJDHZBNxmK0m6jsB4dBU0VDkz\ngdS0ffaFe70TWocnT+R5Rk51NhMse5dNBMe3WWOI1y/RFb9BFpxpkkgZj+h4\nD1bSB2ms3KWQ7/T4ceS4xU1/Z8uHPjPbIJm60PBxBsSC44FvNoQZALuYaQdQ\nDm7V8bldFTdCEUUMzr7U33poyoudExN5m+gHDFJsYEm61ky/q6lDTHSXuyEJ\n7AB2U6WN0nxnAF8fOaGyYUO0dBFMkJrHBYlpa3YsQnTKQjQGlvTFZqIM0zpL\n39Wf\r\n=qxt4\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"1730dc79b95c199c5abb27f4b623d48cce1f6e92","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-22T05:17:51.333Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.9","@balena/jellyfish-logger":"^4.0.22","@balena/jellyfish-metrics":"^2.0.27","@balena/jellyfish-environment":"^6.0.11"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.2.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.0.3_1642828825384_0.24787148968784267","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.0.4-renovate-major-json-schema-org-bc592c18c172f289b4bca86490ae025ea5826451":{"name":"@balena/jellyfish-core","version":"14.0.4-renovate-major-json-schema-org-bc592c18c172f289b4bca86490ae025ea5826451","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.0.4-renovate-major-json-schema-org-bc592c18c172f289b4bca86490ae025ea5826451","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"83a42d2423ec712d4bba17805169ddcdb1106b0e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.0.4-renovate-major-json-schema-org-bc592c18c172f289b4bca86490ae025ea5826451.tgz","fileCount":220,"integrity":"sha512-E8jcKS0XJLgvN5GMMZXUCnZIU/Qt3egmyl/cHEsh45SgfHKrQ70ajUyaKjBIm870UVkCnenu3HD+8yBycTWuOA==","signatures":[{"sig":"MEQCIGZH7oC7wUX8i0wMUdjleJ6e4JxeLX2sm5FTcG0m3kNkAiAtI+Ew/7umttv+0ZfwN8RRsduacZQ9kFUjStzXFwAGWw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":830623,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh65TgCRA9TVsSAnZWagAAcOEP/1ZLl7QPXrSRlJwHfPzI\nmMJsjPJGNWzS9Knv7T5cQ8EMUKwHtPqILQ7Jzckx6OWVnJ75RaAHmmRdACiS\n2S+3Vvf3Sam05+AIAxwQtEbeqkgglRi5PQydl3VLrOrXWMxjtu5nW+dYwGKk\nmVF0iXKR1a/2J6MuepF2JHBzjeoaQRZGQ+t1J+t8bJlBzHQiQlj51kt5eTkY\nkolRehqg90HLRZn0fOxbCstxCawd4StSelQJhAfYAvfWyqOTUhbMO/by0zdt\n1NwyGepwZsC+Fb096/PzIBLMZQ75U9v5ZmqE+jVQkVfwiiJUa9/NXFGvIe0k\na0O2hR37FCq3xdc/EacDVh3yCxCn0bOq2msOaU7OfwiL/4vIhtawB6HSMAYq\nh8nXQpQilyG5ncZSvDSehEqfkBM+eUWJ0ZVOVGWI+CzpN/F8mEPFC0hYbcLK\ngvDetvwKNRiI9G/+O9yfq9EtzYRzUBHl1NLg07NpM0hBAER3NFNdclp2P3MI\nBnsuIxJA8WgWro47fjcZMddXI7yO+Gs8c8L/Ah0AHkG/ZoZeuLEkKOc7Z623\nHS2IfccwDx4cxMVpah3iVvNjw0/C5ZXYZmSBT3ydsxo5104iEtxIu2rwc4e8\nyecksrUxxdie6rUc8y0oMrnEGFwQVNOx6LLA3ufVhU4g5I8hgDVPRAy41kJw\n/9Sz\r\n=Uou6\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"bc592c18c172f289b4bca86490ae025ea5826451","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-22T05:20:10.371Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.9","@balena/jellyfish-logger":"^4.0.22","@balena/jellyfish-metrics":"^2.0.27","@balena/jellyfish-environment":"^6.0.11"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.2.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.0.4-renovate-major-json-schema-org-bc592c18c172f289b4bca86490ae025ea5826451_1642829024588_0.8050092389439312","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.0.4-renovate-internal-non-major-800840ba3186a22e1a6d6e5e03ed5f5d32f1cd41":{"name":"@balena/jellyfish-core","version":"14.0.4-renovate-internal-non-major-800840ba3186a22e1a6d6e5e03ed5f5d32f1cd41","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.0.4-renovate-internal-non-major-800840ba3186a22e1a6d6e5e03ed5f5d32f1cd41","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e7beaf31204501972c3166e31f6fd927166df6f5","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.0.4-renovate-internal-non-major-800840ba3186a22e1a6d6e5e03ed5f5d32f1cd41.tgz","fileCount":220,"integrity":"sha512-B4Jdo6Ww3o77h2l4U+YZfKKztMQ2j01V/F9cj4REFAW94VP816zoipAr2y80vh5apZoJ/UmRG283HA5z9K0C/Q==","signatures":[{"sig":"MEUCIH1J2DFahOp24riMKzMcLx0g8dxyp1ltD50m6LeijBs0AiEAt0kdgM5ON6gd3fnyh1J7z/toq6kx7NJC/xJ49KtqCNQ=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":830628,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh69DVCRA9TVsSAnZWagAAPWMQAJwKFHHQYPMjQpsoJmfm\ngIkH3s3kH25MYK2NMGf1eO+h8ppqgxnMrS2Yh/XrOdUE3O941HwirsKy7F9d\nag3Zw+RywE4jr6AV1Dp3KlLDf78LwuiYj8Jg56XQc+0NrNNNaeJSxTButU1r\nv40QVdeFcMPq2++7ZFBbot6vb5f9B4v+Dfi6Ykl7gKG+cpQ4ICReE8MoYvID\nGJfB04RX4cFRd4PYSA8vJkQ6IMNmGoExlqkfjXYTMunnlc8jaO3HrOtzCZJn\nSQ5wC4T9aiwew6E5YrtBbFlPdrvXXgogbwJr5peHtyVdI1NtUXl3ip59ZPmV\nYiTQp3nUeLw9XfS53p5wVTDaRvSUyiZvWOHn8jcfI397wuN9D3No6KMzNNVS\nfhhHEUKZt4ZMTZNh/lHLgv+LHxTSlBij4SgwLWXZMokjdAgQ7tAoJte5RPrO\n7G5cl+1QWDPFmkENrNss7b87wuZ6j3BzAQoeOT1yCXjKIzUQiCsiheuSH/ll\nwVw2hccpIDlQJOzH9UKEAKoN9GqdCWSog9uXMu55egf3Y3vvnL5cehqnV+7z\nBnlMcvW1h+YW4O3E+Ch4SMb5S46HS+II8HsmpGbzU0DT7sTU7Fy6p0s8OPQs\n231teNCtFYCtGt1GXe1K4LpuyJwiNQg/nn7qDJFUYGEAhLZrI+DsOGYe2uSt\nOtRK\r\n=o8QL\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"800840ba3186a22e1a6d6e5e03ed5f5d32f1cd41","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-22T09:36:09.836Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.9","@balena/jellyfish-logger":"^4.0.22","@balena/jellyfish-metrics":"^2.0.28","@balena/jellyfish-environment":"^6.0.11"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.2.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.0.4-renovate-internal-non-major-800840ba3186a22e1a6d6e5e03ed5f5d32f1cd41_1642844373373_0.010499192426439707","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.0.4":{"name":"@balena/jellyfish-core","version":"14.0.4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.0.4","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0d8141ad93478a2ac304d72902fe36f18df50d1a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.0.4.tgz","fileCount":220,"integrity":"sha512-wME07Itgqa9u99dnipUnHrzqzlrGkkxq6NsdYfsKAZZV0IGXBLbk9UUrHasv5biF5yFS6UXEofXV5rkGBeu5cA==","signatures":[{"sig":"MEUCIQDKVbsNgN5LewrZcy/DH1kP6COaQopgwXQu06qtr6jwEAIgVebjELsIaTQSKI3x480BTvoXUsv3jBgxhda/iVrHvzY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":830559,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh7LjZCRA9TVsSAnZWagAAV9wP/itukDS8E89K0vUtwyZk\n3tdklsL7NJYom3bCPP1c0uiElGcwz10zO4n1fBqbzhCR6uKcw/0hwZDcQD5d\nwe9ucpQ2H8TpdMl9obTFPJn7WLvxAgh0U3jBW7JO45gC/QJ8FnIU4uR63B+j\nY6BC3kf5GJGJ8kkihMbHbup2i+bUjARzO74YJ84D5XcNH8oBseBEw9FyKdsL\nyq4qmpAUZo53Mx8GWuONrplW1hjdIp/O8Cv+WDaP+Rd3+0XH+tCL1a38oc9V\nlYcMlnvezRY6ZhwkhDErxJZyrdAgYzO++aZZG/6CUoUwgvhYP1rSnwjHDj8n\n/KHX2EVVER1R4zvOIyAJE3mfIZFe6cH+Fur4rF7GoYaznm372Z0qX2weMNWy\n8JjapFqSZ9/eY9JyqA84j9Pr7oDyAdSHZYg7waTCXHUjxviOmSXmi2ciFSb2\n3tBa8s9jab9wupYwuPXlueqYGxTHumyQ4ZJHNmTYoM7UqVhY7wlJCbUdoYbq\nfL4XFIcZB3CcvGgNJ9S8ETHtUXIgHTHaF+3omh8eAlWL73+/KS6Ijf8HUfMj\ne2ztaKUeMO1G43bSHMZSxidfS4SQH5m3KZA4mxRaAHUU/kT+7vku+jDhviji\nT3rWfVyd8HKJkrGGhUjy2ohkyaDrcvb98j1MeXEGDiL/JFs97Wo9M72xfyrI\nWyt6\r\n=VdnP\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"938a627a9c4fb85893abc224af2b88677ade4ca6","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-23T02:07:36.332Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.9","@balena/jellyfish-logger":"^4.0.22","@balena/jellyfish-metrics":"^2.0.28","@balena/jellyfish-environment":"^6.0.11"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.2.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.0.4_1642903769656_0.44081795564560333","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.0.4-renovate-major-json-schema-org-b2b219fabc8008880fa24c923f6ee133ed68f2e8":{"name":"@balena/jellyfish-core","version":"14.0.4-renovate-major-json-schema-org-b2b219fabc8008880fa24c923f6ee133ed68f2e8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.0.4-renovate-major-json-schema-org-b2b219fabc8008880fa24c923f6ee133ed68f2e8","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4e10d5583f087c244bdd7ed3a59e212a04d21653","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.0.4-renovate-major-json-schema-org-b2b219fabc8008880fa24c923f6ee133ed68f2e8.tgz","fileCount":220,"integrity":"sha512-urmxF8H6fdEvcS9mm5MvcjSMx1EN2DFIeQT9i7dunxoPw5dhWDYiqT654L7VY0v5J0ehOZ/DaDAFhx1R6vInFA==","signatures":[{"sig":"MEYCIQDwo5PvjP29X8ODrpl2zF+P5Epz4G2oYKzylvzbSdWp3QIhALlgmLIzgGyt6dZ0t6sRupO7oW8GMb9fOJHqqZ/Ylg1v","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":830702,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh7LmKCRA9TVsSAnZWagAAECIP/3HtAcRGIsH4hA1uJRzf\nQ3P4TeTAyy1PnME/NsjKh2WVyfSVTkdCHfOxgRzBJ6k3W0NzpsWuR1cZ+v9X\n+ufwRM4ySCXNcUn6+kLpaHUVNxYG54imVtBjcKXmGr4c5PM01Wd1xiPQt5kO\nWvvHnalGds0GFEx1+pVWDbXqfnUC1rT4APQWI9qIVilGbMWpVCxihZ7Clxkr\nzGucFqlNLNp5BQMn4dPje8usopK+SU/p/V7uZHmbJeLns97+GlNWPRMusUO1\nqzxu/cDXnon31QZBuyAWN5GKDFu/WHTDY7M2AAko2xaFHpSLNbOnnTnbaAGG\njpWSGqvnzw8poCEWCbcCy+c8ogMFDNH8b+jxs6AEF3XqlvNzGfe/42UIKqPV\nrA7fPZmB5LnLdO2XRmFrgTUJ1C9Qw6hVjqYz1oSW57EJwsYzrWvVPfom9Url\n+V34YzR0QBW3jLuOWjPVMUk6G9IcK4HZMtSlmEjfHfxYU/79ktOg5xVYEh5O\nCOd3UsXA4nXdE/S4QHPIP3YcksktXla9QtWZ78c6+FoHpNsVrMddE+Vk4sXv\nLwC6msKzTsaDnPRfV99ucHmXryiIejVt9DToX1b15O+bPU8gzmFgiLDzyUyP\nCNQ4tQBUNeB1jJfu57P1UnVNy2Nir6e4YlBTj9/EeUZ5dSHaA6c+xVwgiFX9\nHQwb\r\n=/i7i\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b2b219fabc8008880fa24c923f6ee133ed68f2e8","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-23T02:08:48.143Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.9","@balena/jellyfish-logger":"^4.0.22","@balena/jellyfish-metrics":"^2.0.28","@balena/jellyfish-environment":"^6.0.11"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.2.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.0.4-renovate-major-json-schema-org-b2b219fabc8008880fa24c923f6ee133ed68f2e8_1642903945748_0.8227660138319353","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.0.5-renovate-external-non-major-c0c532d16a2746f658f2de1b5f08c7a97bb4f989":{"name":"@balena/jellyfish-core","version":"14.0.5-renovate-external-non-major-c0c532d16a2746f658f2de1b5f08c7a97bb4f989","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.0.5-renovate-external-non-major-c0c532d16a2746f658f2de1b5f08c7a97bb4f989","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"fbdf1553041acf4cd8561a2e25187c59bde69ebb","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.0.5-renovate-external-non-major-c0c532d16a2746f658f2de1b5f08c7a97bb4f989.tgz","fileCount":220,"integrity":"sha512-285A2OZOafNziHREBffrhzlWgDx+cQolSUD3+VYr7HzQBQbBhAQ9aAZcWgo1ap2JT4z6bby3OuaSzyISHaxCWQ==","signatures":[{"sig":"MEUCIH7HsmY8IwgCSFn/XkJk/0xl/Jq0/FF95j9/iMGxbMBJAiEA3p/JtkkZREZvzGLuEJ9beYHEkeVZ8d06B+Um317IZjY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":830718,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh7TwJCRA9TVsSAnZWagAArx0P/iUBDQ4O5UmViS0/WukS\nIx1oaf7qtNlWuWExssFYezS0IsqR0IzkKTCKHQJIkyWUvhgi+8/lW0UPAYwr\nIh3hg1eQjar5b6fVTBm9FIHX9YpLYHi/9iBPUA4ZT3V8HYkrNjkMnKZnWLAY\nLZx/i1PtHkuoWDBNacgzboRmyJgy0P8rDrI3ivMo9I3fHHbcmO/Kv5FgDYLl\nCD09bgZkVv91Ttowh1GhoLrk1gW30fTTwZc9sRZxoIw23dkl1rX30nVjmU/5\nPMG2Lu+HQeTjivcKscKkIQ2KmYaQA0z9x+EmKkOUjfJr+k+/nDtEbWUmLVr3\npy4BPrxUafhWChnaRV5qgS66Eafju0iKVAx1pog7QSXW6MoTI7w1SfZmkZI3\n2n2UwXv73FYfHcZsyOpTk0pEGhCEeoW4QaF4Z4IrOiNI0K/YxbxXfx1d7ZyQ\nze21GWirnEFrjcdb9uV8ARsSpOes4gCd1UCq2Cs0V5cEJdgAFr5JOb8DdPJ8\n4Gbb/HvpcXZ1/m95mgupwEcxyUdHx6Qf//CqbIq0Qg4UD25dXNpcUdGNvMyU\nlHLpcM1pU8cwCBkMzRErcLj3VIguVMSYtM10oOi9eSEzLqz3bHRf7t0TYdh+\nyMPUuo7V5ZJTtnEwwo76OsIAHBCzdrBil8bhCgf3pIifo1nxZMc9ovZzsbFZ\nMuw5\r\n=gUjJ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c0c532d16a2746f658f2de1b5f08c7a97bb4f989","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-23T11:25:57.819Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.9","@balena/jellyfish-logger":"^4.0.22","@balena/jellyfish-metrics":"^2.0.28","@balena/jellyfish-environment":"^6.0.11"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.0","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.0.5-renovate-external-non-major-c0c532d16a2746f658f2de1b5f08c7a97bb4f989_1642937353325_0.7673430734018547","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.0.5":{"name":"@balena/jellyfish-core","version":"14.0.5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.0.5","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9b7451fdc1e8dd4d5a031624ef64d28651eee87c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.0.5.tgz","fileCount":220,"integrity":"sha512-q6k2BM2WjlIUDSrvCfrFBelKTtuj3dLYfod0IU2dkq3QrPmoUUUPRfObMNLNa/NJ7qDPKYW1j/ESBCqTlfuzjQ==","signatures":[{"sig":"MEQCIG8Ou+PsT+iYnLf9JAXaWfRbXoFaN7xlCF9gRpuCjM6uAiA7h8WEgGaxS/18tGTnR+cfoaxu6o82a/P1spHCmgQePg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":830649,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh7T2VCRA9TVsSAnZWagAA1GIP/1gT6Z4LkvUX2YvjwuED\nnF0EJ0OPzeTSdBnke5pCbFY8ZgStPzTeuIW2dM04SUeMG4PbUbGjfVj9jIvj\n3UUG+YH0QDParpkkQ5Yxm88VuufmXmoazpkY1UyvDOSFCdt0dCeN2fUYqDKc\nMlCaKYwDF1jeAHZ0TSAwt0l8tCNzbD9d9lDm5VjJnrSkhWJF3/iTql+96WUI\nvGyyqo6MbK1FPASinLQ10nfv6Zbtz12qFHYFSqlbG39H7L9jU5p6QDhsWnJ4\nqXRYPjLcS3aU9m/Cwi4gh5u85yhNC4wU3pfMUaQ/8nONuq+mT4A4xtrj/QGC\nsM5ULTHlyR12CSxKLoknIIbXfPm9h4NA8EIIUUtv9hyndCdR8nrvDUW2Z5gw\n4yqZNf+cRGpeklnbD89NeIhGkIIbrQGvyg4Ovkr8xI+CLOamvHenJdCT4pGV\nsNFe/SI8OV58lX0qT+egVYsqobL8yBoLe0gmqmaLGLvkyerK/PjmP4bYNDgs\nbGGP4iUw+QZFPUPzF64kMVAspISPE0kZB5+QHZk0f9TTEDy948ZVs31gJKnP\nSXuFFetUsYpNTdrjjj/5LGZy4VrYF2hKJDppG1xOJnhC+lTCu4mbQvmRfUxJ\nqA2GdRlV0QXzQQIRt+zQ+0ffSmIGuZZv52RqFo5dVmQqWZw4OhJex0v9nzW0\nK25j\r\n=GR7Y\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"3de2962cfa348bbd7c212d5d3c06aabc1faff67e","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-23T11:33:41.940Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.9","@balena/jellyfish-logger":"^4.0.22","@balena/jellyfish-metrics":"^2.0.28","@balena/jellyfish-environment":"^6.0.11"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.0","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.0.5_1642937749466_0.7666887158021536","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.0.6-renovate-major-json-schema-org-5f9eb2acb6ed26f76ae1e7253c87d1462ec15aea":{"name":"@balena/jellyfish-core","version":"14.0.6-renovate-major-json-schema-org-5f9eb2acb6ed26f76ae1e7253c87d1462ec15aea","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.0.6-renovate-major-json-schema-org-5f9eb2acb6ed26f76ae1e7253c87d1462ec15aea","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e21f8ff6add7618ad841e1e5322d7fa4fca72a7d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.0.6-renovate-major-json-schema-org-5f9eb2acb6ed26f76ae1e7253c87d1462ec15aea.tgz","fileCount":220,"integrity":"sha512-LdHqVdUyUsLAnh5GIGX3h4gAHOlW8VgLOS/NV8dUFISnMdF5htgjzOQJz19PNF6FXISbRhCv4LyS5PHYwjM26Q==","signatures":[{"sig":"MEUCIAOw7HZ8gUErv1lNoeKytgp2G9m9BtF0zAFm02/uI82CAiEA4Lng/0tu2HUpJD6Q5q22o2pOsxQeprIvX8rMji1K5UU=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":830817,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh7T53CRA9TVsSAnZWagAA7BwP/jPmc3VfYmIitxPRWkb0\npcwcLJnSPwMi/1+8cjN69SVG58jmZHHv/S7EIS+FaCjYZLtBXFvtjgX4iJJI\nEfgIIVmz0Po5/FYmdYydyRc6lKpCHwXcG4ZtrwDXL55XWZv449dSFdCvkI3Y\neIkGbfs7F1VYXgQKEh/pUKCexCHfzfWn9bIFgD1Vwemb8aqsjB44xDZ5FiR/\nsy/RgkdHPj2DPj0WrrwTPkH92foyzJ/dfzr3qk0B/RijXzIt/kWIgqp83sM5\nsfbE11IrGOkIALW2nl7egto2Emed7NWHZi7jfUlShJwXqQrf9TUgzO7e+FlZ\n+87LSeSGAL4ZdBNQi4bsGJuGsH4Cpa8uC+zkmScCtsHChlZg9548YGh/Z76r\nz21b8PNn1VqGDw55KtwOuYdT5beHE62TJR6pqDojQy3J7tCZkqX6ofcbcoYv\nQVf+ZqWs8trT4EbNVn+XC7bhWlYaCE/D8ofQYitolBRcig4ZRUPTfRtTsZhL\ng4ikjsyRCW5voEGjhoH4zMKXyXLz7/RY8mHfD1f9prDi8t0PllAkFeL3bdYi\n35R5Ij4oBYsmt8NyNrkNT9jQ0my2sCRWrYL4zrElchOKIvoNlxZzJyU4BK/D\nKRHk1wzVdP+dTrF/lWilgMrkUAZPbqukFD/RaCIaR2w6pWge6qtMPXvQbdK9\nUJJU\r\n=TAgI\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"5f9eb2acb6ed26f76ae1e7253c87d1462ec15aea","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-23T11:35:23.366Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.9","@balena/jellyfish-logger":"^4.0.22","@balena/jellyfish-metrics":"^2.0.28","@balena/jellyfish-environment":"^6.0.11"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.0","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.0.6-renovate-major-json-schema-org-5f9eb2acb6ed26f76ae1e7253c87d1462ec15aea_1642937975671_0.12375818192904786","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.0.6-renovate-internal-non-major-2c80a0439f59d8d63106a75c7e8199efc4a4ca87":{"name":"@balena/jellyfish-core","version":"14.0.6-renovate-internal-non-major-2c80a0439f59d8d63106a75c7e8199efc4a4ca87","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.0.6-renovate-internal-non-major-2c80a0439f59d8d63106a75c7e8199efc4a4ca87","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d082518461693f27423231f4ac5e09ddb48f2bd2","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.0.6-renovate-internal-non-major-2c80a0439f59d8d63106a75c7e8199efc4a4ca87.tgz","fileCount":220,"integrity":"sha512-7hdOYT49/5ZodZbdTPw3LLyOaYl295BIdZ+a2RFbC2MtfCYn9bxylnjyBYKY3jVK7rqtgZONZfzfz/PW3qUp9Q==","signatures":[{"sig":"MEYCIQDh6rBZ3Qd1u359I92NNkP3WweAcInnyWsmUvEs8Id6rQIhAKfGGcSfiKBkn4u+RD/ODnZPp4uy4DkdFpsH+zfXXx9o","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":830794,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh7WPiCRA9TVsSAnZWagAAq48P/1PP+LanV6o89S3WfSe1\nB1gKPzrOgrfhDxTQZXaJWRwKxvdfVqno61B+jQYj8y3mRN3PABCrUEQWUxPc\nO8seihY6seKOmFgJDCphKJv11bskeLmE36hzjQwu97VaviQ6poVAyr3ij923\nWHv8h5e1MCaATjh8wal1hrtt4l+lkDNJuUSW6dmDNqNHmnR92K2HLFtcH+DO\nhTyZkJ6gSt3bAMHjV89zc0H2Bax4WKKqomPBAip65eAMa4+TveJV10rGfsDy\npVTU4rf91GXCCpXTXiB3ge9u0t6OPrBJ9bjfQC+pDQolkPa1yHq/wIntkt+h\nR1UHnbLZkICLi7sU/eLZFLF8dQrG1tHbDQuAlAeU2h5vbuN8XGah6QnjG/6G\nzdX0LdPMKPLT4w29h4HWaNmNFJIombnmnaZzkRRa6v4qzaoWxe+Zk97HSazF\nqss2uoRZmmSGjB6f//q30AGvKLC1kS5Ir7F1QztIFBklPdgvVkQH9517RhIN\n6vTyKblqhQ7qIRw9Xm5fcZ/+QKakSqFoofiD8B/o0RIchYp+8kD6cTFLcJNw\nwXEQvAXKSRTn9HY/JGCJGcXh9T2pZ6v2AJR3ToMDgOUFuXKFz2HngPGpZIA5\nKdPmpj+lnYJdFGDdHqi/HfXlAPu0cXfzUXz0isW687TcG1mpXTWvynEplC6o\no9Ck\r\n=3n7j\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2c80a0439f59d8d63106a75c7e8199efc4a4ca87","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-23T14:15:34.930Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.10","@balena/jellyfish-logger":"^4.0.24","@balena/jellyfish-metrics":"^2.0.30","@balena/jellyfish-environment":"^6.0.12"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.0","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.0.6-renovate-internal-non-major-2c80a0439f59d8d63106a75c7e8199efc4a4ca87_1642947554723_0.4598249125312295","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.0.6":{"name":"@balena/jellyfish-core","version":"14.0.6","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.0.6","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5779c338d2de713a407061620d504fb8f38076f7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.0.6.tgz","fileCount":220,"integrity":"sha512-LmmXw2JPHvyP6iJxzy9yrAS3t1IEXctRmTQjTKjDJ8+SgqQjLQnE+sDXJLzFZlGtOlYCmAgqEU6zWzhKAB9OGQ==","signatures":[{"sig":"MEUCIQC5GKPpzgg8mxGYJd6Iarrovm0bjS5mTmfey4SRBfWWBgIgIMUYMZ4uPgJzjAnPosc2DXw86NxkhvBsv9H3dCTILHY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":830725,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh7WVCCRA9TVsSAnZWagAAzfYP/2k8T5RFiJiJ4/pIiF75\nbWAKmDF/+pIi38oO0a34kSaRQtWAapVSJujsjUrMwSSs03QKakeLLSvs1Np5\nk+lL6gd/i5uUd3Mv+GT2/+l2DJ1fh/1/D4Kx5ExAjLVJ3/RxD0w8zSZV+0du\nMzZxgmInPGik817BT3HD9vJ6UX7t0ZgrfRIp7603mSUqVvSV6cXfWSc2Rm7p\nd4Sg/EV96ACqMtZyCmtQGb0CMf0Z/07Z/vHSU4DVnCv7wb9DEUWyaK/zmqDh\nFpfEO8i1ya9spE5Oq2ACEhSJ/oJUHv9PyjAh3bGXhHNQgL79zJFgZJ+mDjlo\nzbzm2Lq35TFjH5EMK7WqfYakGARCY1CU+i0V42lpX+uIo4Z8V2yPm3n2EZYo\n+cADYmw4o5Jt5GDMv8awjAPn8qaIHoJM7nduqIzo2v9GR+Lzkt8VR928eq2T\ndHvQmNitpBrt1extGGSrcQmfbm2+n6/X5ELPEq/Hc9L+tnABlVkC13fn+xp4\nYTPTF8VSzJzkLeuK6Q6GrlqLM0vmgC4113oPJLmPcskb9MgHfCBj/Bm8WHdX\nsWi/z9g0Mx6hM9Af7XjXp2SV/DKIaCladTix6XGowNLXb+1FE/k5Ji+Wol+0\nyDnjBf+NuPU2YxKxx7RKCioXA9DZ1cV5bNT/jHd0dsUvOMwwv+T6GKLDVA1W\n0c/y\r\n=Wepc\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"9c70bea05eef708486d07b7b68d0621e4430b806","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-23T14:23:05.100Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.10","@balena/jellyfish-logger":"^4.0.24","@balena/jellyfish-metrics":"^2.0.30","@balena/jellyfish-environment":"^6.0.12"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.0","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.0.6_1642947906234_0.2401196025393435","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.0.7-renovate-major-json-schema-org-92496ea67951ed75ae06f8360c1446911469ffb7":{"name":"@balena/jellyfish-core","version":"14.0.7-renovate-major-json-schema-org-92496ea67951ed75ae06f8360c1446911469ffb7","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.0.7-renovate-major-json-schema-org-92496ea67951ed75ae06f8360c1446911469ffb7","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"fc63002975c6a7bf0b01b70b579f3487b13064ff","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.0.7-renovate-major-json-schema-org-92496ea67951ed75ae06f8360c1446911469ffb7.tgz","fileCount":220,"integrity":"sha512-M/kfpitRd07Rhtm94hEwXGWq9TkEY3ddaMAqGdy/vTrAuP9NBuCZMRs4rF4y2hgIQARBwgD/lywBXnyp21klbw==","signatures":[{"sig":"MEUCIQC79+JCHztw+1oMtXb6gXJy9jIHE2OURcnxDfDGYUfkvgIgHLzQ1UYDaTvz1cdutwOhqK0h87PEt0G7D9n6kPnDJAo=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":830893,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh7WbECRA9TVsSAnZWagAAuAQP/3PQ9lriF47VI5AxTpKR\nN5c9rRAsKFomAQ5HQW/w6tYzZfYz0N9CjhJC3uqNqPZVVaZe8/I0RTKfneT/\nFtexP0aslfqHsReuWVvCi2Mu7SnUxaCtjxPyCSxr0k+wwiLkXu1a3u/Xtshg\n2GaCtKIExdrutzBVmwQn/Rv6aVaVUAd4hV6pjUdJ6S75fnMxqCh5XEbCnWA8\ngefy1qrg5Q+ylYjigxCqTL71ggG1cGODWGVxXiFYgkAMIYD2Cr9AbjkzIOGT\n1T+aDXuK30rbSGs1vCr/4aTfCY6KVbW4CSI0Gv7Wb/K+aLJ0e6aClXHOUk/v\ncCk6ldvJuRNaQspxajjsXUp3TUhbqGVrHvp3xabdSbPhlUD0LmSjHoNzqYte\nEr6jG1g9hiPJ78jDzw2sR1dJlZRS9uR+xbrSZmnymuX2FY+9806jCZ82Y4Ey\nAl3RoWctY1tCw8y8GdDJOIEsV4FLR7VQMjdbeD2OEGsZvMe4lRpRNX9zQ8c0\nRN898Qp/gRRffBR3JBJudjJ1AKEM95zvtfLyynoXoYrrzYW1vXh+1OyCW5qt\n97X22elgdJrYxf3RdY/gB4RslVqSzVE984IUMjDbrqaHud1aFbPQs5TZltiP\n8z8en2i0FICnNWUdodL0oUGsxHky0W1OPTRtogyAJ6vIrele+5c1FV14Mu8b\nC4DW\r\n=GPz4\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"92496ea67951ed75ae06f8360c1446911469ffb7","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-23T14:27:40.502Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.10","@balena/jellyfish-logger":"^4.0.24","@balena/jellyfish-metrics":"^2.0.30","@balena/jellyfish-environment":"^6.0.12"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.0","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.0.7-renovate-major-json-schema-org-92496ea67951ed75ae06f8360c1446911469ffb7_1642948291830_0.7895913083621213","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.0.7-renovate-internal-non-major-8f8c530fcac4f52994ab2b2d52b83be84287519c":{"name":"@balena/jellyfish-core","version":"14.0.7-renovate-internal-non-major-8f8c530fcac4f52994ab2b2d52b83be84287519c","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.0.7-renovate-internal-non-major-8f8c530fcac4f52994ab2b2d52b83be84287519c","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"eb88820b1080385cb15b6a186c39c50513b049c5","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.0.7-renovate-internal-non-major-8f8c530fcac4f52994ab2b2d52b83be84287519c.tgz","fileCount":220,"integrity":"sha512-1q8qxp0ZxevRS1L92iYg+X7jB6bHsFHH/ybsTmkrzML5V9u15DcP5bjm1N6/5Lb/e/58M7f9Em0CLfE+8c11pA==","signatures":[{"sig":"MEYCIQCDvFq1Uz8L4HYZ7IPeaO1lYm0R8YmH0D31nuW0zCbNqQIhAKhf6KQCiYxdyzvHtyB+kx5mDFKoMl3tRGnAdoVCbSsT","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":830869,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh7YGcCRA9TVsSAnZWagAAEJ4P/0kGkn/KXMfOmvMYpPA6\nVMtWG/VAOj1PS19kfcdBYmfqIvOmxkD++BnAgZP3q4tL0bR9F1+5bwwi1ev6\nZpm/bnasXN+iEi42W8jOY527qk5BGlldI1rF3J5NNwpb+M/RGx6lKTOlU/EH\nJpFRfTd9mM43cbrkSwYKMcELQ+KwvG8BpKsO/+JTm3LEqhrb1p23h2E9EcUY\n2KSP8RQiC+DIDUD36dpfVBxr+xQugcZhc4WDJCG9bZ2PB4Oe2LtNtVLWu0CA\nQa17GM5eYrQPmg7o0pipCnJN83Z+MCrCvk4n2IcZQdm4Qr6zSO4hlpqyMQWh\nHY9tiyhkUD67PYkJhiEHknzs4ToM8P/2hpwQcSzUq/VGlipi/EVv47WRGsnK\nJHwgkgFoQvICu50R5tzP0xgF5OxMpjveJcx2OOWQ5XOCo122y1Y6VvQM0rBO\nREmQsI7HChF1vbrOU+JobvgZxD72ioKxcpBUk09SL/Ff1D+ntQHPGAMGm6X5\nmFjkAx2ArTUybP1KaNlkyUS/oWsAK3Z9AifJJqGh6UHAShjzq/cdZyTW2vei\nm33D9az0R4Q6ScN67P8zCeqWCfr1QCu6mH9jjyb7o/NJRPBhKQyN0coZ+WPV\ntoDqhNLsoh/3PpFNgp9xysdrxXmj0iKJit025O2lAfaY9aJ4Lu2sybydhUon\nisB1\r\n=0nBj\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8f8c530fcac4f52994ab2b2d52b83be84287519c","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-23T16:22:39.643Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.10","@balena/jellyfish-logger":"^4.0.25","@balena/jellyfish-metrics":"^2.0.32","@balena/jellyfish-environment":"^6.0.12"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.0","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.0.7-renovate-internal-non-major-8f8c530fcac4f52994ab2b2d52b83be84287519c_1642955164481_0.3841062186357034","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.0.7":{"name":"@balena/jellyfish-core","version":"14.0.7","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.0.7","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4af3ece3af3fdaa20601719cc455e0b363c993dd","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.0.7.tgz","fileCount":220,"integrity":"sha512-XZkgrPQae16AMZERb0fHj9HoonJqujUrqZyTAEhLdxW0afTZSTIuVx5YS+0hJAJQLLAo3aPFDQYE4UuKU7kGlg==","signatures":[{"sig":"MEQCIETpNQBWgRqD14cUmGZqx3D5QCdIG9HuIazhJFnCIVg7AiB/aWBZ3AZOiFbgRbsvyvvMQbYYeObctdmQSej6fLIGtw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":830800,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh7YPuCRA9TVsSAnZWagAAetoP/AoXoXldBW9qNTq8CaHa\npmpyew+PdlkztBmB3U6qQ0Bih1Dy1abhnb93SuBuFqwrqNLfxfHuZ43CLPjL\nAwOctg5TcRrOIR6Y8/rtWNTsB0m98DCVIqsD/LsGv7Zk0/jvCQPhZ5Yx8Xnp\nz/tlAiNNpz0aABdwYjMiuJOiQxPKoq+YJBPSjYhJLNJDZpUccElCzSc6hB0y\ndO5EX+6f4fNUW0nMDimA1UF6jsYeotF1FRfj1WPLt0Gl5wWjGA6pbsVRQNOI\nFb233qW9PcQd5fPYcIElAZjaojFHm+oiOqX02WpyTmTzDL35HRMYU90xtnmM\nLJe2zmJzh8DW8EnaPB8tV50ihuPjdoT87daM12Jkg7S5mHCdfRxfIJ7QMPtN\nBhUBfphNR7RKKR3eurtaIhCSGf8+9qqE/kRPdFxzBfgUscNVZAzQPtzHKSSK\nSUj0dfmK6xNmfd+BDOGDP8meD2R3GyH0ghteTPWTtzcvDaYb5WCBRI8+K81x\ntJpIXd/EpBMR8/1APPl0/V5mrx9vfeMb7t0oJhsW8S68t+kVetEYqJMznx4g\nKbm6iu8dAubSmB36HTHTgc/U23qS2qJTMIP0zAAwECcIT1zxkviG8uQUwHEJ\nKqw6u833yQyO/tGgRSyiDW7TaNGOs6AbidQeokBUpmjLOtwoQpW6Nq/MYimR\n1Fa7\r\n=0H+z\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"1e78c0396df25adb7f1f838791ab48a4036eff48","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-23T16:34:01.545Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.10","@balena/jellyfish-logger":"^4.0.25","@balena/jellyfish-metrics":"^2.0.32","@balena/jellyfish-environment":"^6.0.12"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.0","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.0.7_1642955758214_0.46679658184992423","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.0.8-renovate-external-non-major-b9d6386590a9e5f3fec6a8cccc0b10b9a3a1d1ba":{"name":"@balena/jellyfish-core","version":"14.0.8-renovate-external-non-major-b9d6386590a9e5f3fec6a8cccc0b10b9a3a1d1ba","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.0.8-renovate-external-non-major-b9d6386590a9e5f3fec6a8cccc0b10b9a3a1d1ba","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"7a744ca0c9105858a26a0cf2ba5c8c64beb76898","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.0.8-renovate-external-non-major-b9d6386590a9e5f3fec6a8cccc0b10b9a3a1d1ba.tgz","fileCount":220,"integrity":"sha512-7+D1zSzEbSuupUTmSPuyzq4r5fAxGSIow7Bqa+DxvwF3cx4bIr8e6d5s8PTmqOd6VNW4meVajOhDmWlv09cdPA==","signatures":[{"sig":"MEUCIQCAybDLb+AWQB+JTAlzT7fB0jxtt6gYTXNBwDlR+/0GEwIgLLJpLnIdFMoc21ktGUVyOXjPX0+oo1/I1gHMBVy8IWI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":830959,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh7YUICRA9TVsSAnZWagAA2K4QAJ7ajw743LolM2CloE7e\nsAfPAjkeLDDNePuaLt5oaE6Xa46DC5J8yX9uiENCMWTPNRdjXYHbuINovxOu\nMCysLa2fh/XVm8Zc6P6ZzBkvLAc+W9XeFoCag0cy15HqqmGhEF7iWp1XejLc\nB3/HyUDq2nI2lrjOzxegMVSUg0fHw5DhmiJm7gQ+z/HA5A5XathIukU022ew\nn06U5aQy/BIwTrivHuKsyCQOsg5VMKMFg2c3h6eFr6mecVh5TpgdPEITh819\nmNLAfi7W6sQheDgVv3hRBnAq8tpdTvhvrB5bH7g50nW2X9gIHO375M0svJqq\nMSoCLvyIKEkb4Yf0EcX+55mLywMyJhhcoHmy8IPHT53ZG/YffZGlWvT3FOL6\n0RZRE1k0nZGeWn37BS8MK6ytVtZ3cxMrRKJ5wh5TQySCsrGv2DaxEU1AlvJE\nB9ON9J1xuBH6L2ynmbgwGnWuIzgZpN5G3/TawczZM2ImtWlNae3r3xDUuEwf\nAQFFMovyMr/zzIAgwG9J8qeKRxVvycdzfDrAjXiaxRVv8ft0niIgPo/KF6Ze\nKsrzrGvkE4wXT8id1Gk7m2ReKOGFmE9elHQXZYztOq5u4ld/enMNOjUujUNj\net6NXKwWCEUICSUeqHjbFa74QfN0UE1HumsOi/1Au/RVsGcQ58GCqAw/G+SE\nVe5w\r\n=w0Gg\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b9d6386590a9e5f3fec6a8cccc0b10b9a3a1d1ba","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-23T16:37:14.180Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.10","@balena/jellyfish-logger":"^4.0.25","@balena/jellyfish-metrics":"^2.0.32","@balena/jellyfish-environment":"^6.0.12"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.1","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.0.8-renovate-external-non-major-b9d6386590a9e5f3fec6a8cccc0b10b9a3a1d1ba_1642956040290_0.16402056674159393","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.0.8-renovate-internal-non-major-cdaf7d803f3fb664c951546036b02950e610fdf5":{"name":"@balena/jellyfish-core","version":"14.0.8-renovate-internal-non-major-cdaf7d803f3fb664c951546036b02950e610fdf5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.0.8-renovate-internal-non-major-cdaf7d803f3fb664c951546036b02950e610fdf5","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"42c10fbecf0baa4fff155d200b700c53a22d671a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.0.8-renovate-internal-non-major-cdaf7d803f3fb664c951546036b02950e610fdf5.tgz","fileCount":220,"integrity":"sha512-povoBi4JJoGpM+FD20W2ZR2Dmj0uTHJDhZbecZ7O53S5bO0DEvYtSvT7MkNBuqqC6n/munkXDc1dRDNcrZZTHA==","signatures":[{"sig":"MEUCICI0pR2mvDyDOVIfMQB+Jid7gFCtv+yBjOyPjRMAN11LAiEAmq9ecqLNs5yWoCImD2FL8E7ArW5H06IltJME7hTXRU8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":830944,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh7aTOCRA9TVsSAnZWagAAuXMP/iOmQOSRoJkCfhwzYFX9\nYrehPWAL6D/bti9uccUTgN/n9mqPn1i3zKjkkJYUc7DN0LTAUs6aiq3uU69J\n8yQ6kFJ/CegXFhUdlqc5JeC2IXjyKg3Ztk7VcDVujj+ABuPk4gdlhXGrXvXU\nhrPtzHH5Q/tBhkTZg/5IJwoAqbnZAURcx+qQOXRfVZQNxi+XZ3MUmQ35UIRl\nYVa5dcN3npkx8TxSasVUrGTdwWkY3IhDjgtQsOIXWROHLzw7tERVOFiTH2/u\n6sLn4WUgIMaUc98Dd89ARWe38tKhoew9Q+F4ubSVyEh1YjqKjlkdPogMjj3d\n47Au715IDo1htxCc/GTGXLmLnnbvOhfFpKYapMhs9nTrJoCxo/nNc/j+CUDj\nRuA14ySpq5llBvl2zwen5YVMkkCo+laDA5ESYgDqA9fagablzekXFD4qN879\n8QWbqFhrzxGyccWt11+eBWADYGv1DwkG54AnbtliQFLLB9AxboA6clIHfrrK\nkCwGuY9ACEnGZQ1zBAiRbu7uz2GkyO4ihbulNT7xwY9EHiM9dsKRAo2/d+GF\nZZ9lwcxjDqpEDkEfv0jdeeGKiHHfqL7sAVZ6S/4lf6EaUOwmeyKH9Pyr/NJz\ngeHB5FK6Bs7J36eF47/8gfd/Td5doJZVHO7fIRvGP9T0fDLisamZY5aOnjJY\nVCuf\r\n=6lME\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"cdaf7d803f3fb664c951546036b02950e610fdf5","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-23T18:51:58.721Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.11","@balena/jellyfish-logger":"^4.0.26","@balena/jellyfish-metrics":"^2.0.32","@balena/jellyfish-environment":"^6.0.12"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.0","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.0.8-renovate-internal-non-major-cdaf7d803f3fb664c951546036b02950e610fdf5_1642964173691_0.5529024542425343","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.0.8":{"name":"@balena/jellyfish-core","version":"14.0.8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.0.8","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9a6acda47e8f0255ed35afb632bdcb33b9b1ef95","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.0.8.tgz","fileCount":220,"integrity":"sha512-SZYNhUVqmDLKfm4VVuScMU5+0fHBJB0NekB1hHGj05kja1UP2Mr+dA3U9Q01nE91HWIyqsyC4+Dus29n42Sbjg==","signatures":[{"sig":"MEYCIQCKf5muPhhzCzFYZDlU7Ii8UAdPGr7rBBPglLstjJoVJgIhAO/uy7AwfLx9f1lypIoVDvnaLX5FhcmbtkgZsOywvWIE","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":830875,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh7aYVCRA9TVsSAnZWagAA8moP/AytzncYRHqohhetN7eh\n96YojCey0B83gm80CAeEySy2SCOdfLIhoFFx2wYp+s+NnMzYQPeWz8UsoAQg\n06myZaedwh/KQ4QVh/KALrqDNYnwPMCq1Hl6EDWBjQwD/JUaamH4Za2iBikQ\nMlK5FtBVHIEiCNOs5y50hAV1hEpwKBpUs1NZYo8BmXqpVsDd8RahBZgTyGP5\nd/aoGbDvvVJpO/9aFxEiXRa5c3TXgkA0azmieo6hU6tOwvVFdv4z8Ve30F4s\nAoMfYiz/Mu306lEkUI7ZkKYGAGJHNFNSw41h9efVPLGE8jBWjFymESvhwOKs\nG5gwv32HlGoJC5Hwd/8EfxVg9B3E0hwjpbwHI06v3MdiFT6BBJ2UW7W4S6L9\nwm8H3er6VtkAbxZfpZvuh/PIxfjLZfmGLPpM76b7Ja97MkvpIafKQTaX2oZg\nSlrEHb0fpn1kkLVYUVDjg98EQ9q4z+DmVUDiGjI/NnGcT9Q0G2xFKs/9voVn\nZJdoPdp4pnfLvzWrHUZzdBYuwOrPELHocm+rjWz9FYO/qVMqZQwt3ONTSbhK\njjWFgrecexplbiVjiptKLkTjoT81EJHRWBgf1n0qt36/xwO9iMutfTguP43S\n/wIyIlt3kbioJuCw0oUIPpEiu4BomGicN7rRuhsoc2wjFVKLOWFNuaNAc8pq\nuK9a\r\n=u8+g\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"332b99d72a39951a8c48f1026fbbb213d10ab91b","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-23T18:59:43.985Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.11","@balena/jellyfish-logger":"^4.0.26","@balena/jellyfish-metrics":"^2.0.32","@balena/jellyfish-environment":"^6.0.12"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.0","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.0.8_1642964501155_0.7450406508655714","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.0.9-renovate-external-non-major-b4d1fc190e7eff4c361d69f02f04bfdf99a83245":{"name":"@balena/jellyfish-core","version":"14.0.9-renovate-external-non-major-b4d1fc190e7eff4c361d69f02f04bfdf99a83245","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.0.9-renovate-external-non-major-b4d1fc190e7eff4c361d69f02f04bfdf99a83245","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"88a96688a1bdc3424ca4c333f4c2d96e37d2544c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.0.9-renovate-external-non-major-b4d1fc190e7eff4c361d69f02f04bfdf99a83245.tgz","fileCount":220,"integrity":"sha512-TinCeg/zPXry9ukJF3aZMnYSlytEFU7vYgjCiydAn/7exlbKz/SS6ZZmvUsViRAbbakN2EGkcsMVvNTQsGq91Q==","signatures":[{"sig":"MEQCIFWA0J8rVS2T9QYMmTtUaDClP0VHDE6oUxsqYmGp8QRhAiAO3sVmLoAQsR9Gx9BC/OiqA8HrDP9OWmhxRwjVWc9MoA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":831034,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh7acECRA9TVsSAnZWagAAAmcP/0LbfwvY+H6necKeOXvk\nVosS/44MKERHLEUKLBdQ2o4+4z4wcts2iDH5N70mnfIIHVV0qZkOG3FEPR+5\n+c9+xhG4p+uC5lXWY+nYIroadB5c8Lu0qj9QZblsW7b42yWNk3bWA1ooa9DK\nq/khBkwrnfzz5rKgJw0Z20i3we47BPUBztDYAtZVMuidLwrScpKkP3ganm9n\nse1VEUa0Irij6XTArUSZZR+iNBsJr7mRh5Zr7L6Di28Y7fJkMO0nT5x5Rc+0\n9wAbXlph2z3kcLP9kMEqdOSs4eQLE1Rg0snqqDVdeoMakuJCYhxZP0pI7JRt\nbmHVNzK4VLnWDM5ZE3c045yY6alLFiyDilh/Q7l0BBROWt9y4vR0DIcoeEwM\nS+u8fryAbj0imQrN146RGw8akTWywbeBqB8anWTBsUrZlKA1VvlYeCRyoCFv\n+zuj/zqq8nLvjQ67rv0FNg+chs67N29kx1/0ReuykNMP9hjSRhDxsglkhA0b\nV/z/EK8E0OzXe5XZWRUzd8zj0SknqPSqe22c89k7c/TUnUqRcPS97DJQVdJB\nrumsTG7BeZV44g14omxfLiDwPfXiDqJMR5GeUxla4JK9b3z0NbuqQzjVA/ka\nou8VdSn2JjcgwrMOqP+S9eBwAtE3Xso98ZjDT1jCcb88fQIqOtKjVhc+xQFr\nqvJM\r\n=BnnF\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b4d1fc190e7eff4c361d69f02f04bfdf99a83245","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-23T19:01:42.025Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.11","@balena/jellyfish-logger":"^4.0.26","@balena/jellyfish-metrics":"^2.0.32","@balena/jellyfish-environment":"^6.0.12"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.1","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.0.9-renovate-external-non-major-b4d1fc190e7eff4c361d69f02f04bfdf99a83245_1642964739765_0.30596457417232714","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.0.9":{"name":"@balena/jellyfish-core","version":"14.0.9","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.0.9","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a6b3a1729e7521f24ad3ffaf83e95b518f56a396","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.0.9.tgz","fileCount":220,"integrity":"sha512-aHJLMvsXGNSIK2QjWuZBJIlX2KNVK+ga5G4Cu1YqgE1Tbs2Iw7QFRXzB645etaRwOxl1uKg6MWYuQaXXR+BVkg==","signatures":[{"sig":"MEUCIAuAoM2dMCfIWkQ0Eow3yCal2P1sOB1Nlg3HJV+Nw1hYAiEAh3qraT8e6pqPXGnUbMGyYCmBAEvWkzqpIdnZbCPRuc4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":830965,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh7aj3CRA9TVsSAnZWagAA3KcP/2whJ/e4YLt2XDMtKNIc\nwYRhi7Ock7+dOgqSzsDRALPiTFK0xJ5RCeEi9U+dO13qoHGZxb/D/0VaYde7\nraZk2PY+gDUHj6qWRR1aGfXdWgjoiSWaTBsTZc5xEJ7yBDAlPdlJX/FxqkpQ\nBt62AnHwx2WlZ2kCRx1EYU38jmUImDflXN0/VCVd8hno5uq8Yto0Rsl+4t06\nCN/04xSkg2AibpxUFIJy7n9dvy/9wyH0l3hNwDkyQoRI/n7ctrsrvqOLmhWh\nNg1w38pj1Tc0iilQLpeYxBhMdcgUecOUWwvIoi3r9t1tHiYmDJUO3mFnf4Bl\nD7qEeGQ5rdIHI+d0df6G/z49q7v1CS5/W783IEnR4KuQ0FNAlv/AbOWwJ5yV\n2PPCu20Ky6d+Y14zXSe44N1iAQ1Fz7N/2bFCyIFF2jX7K6cNHByNgWJfG+14\nvFgNz2e5AGPxO2qe7qcgvHi6teEZhvdcrMmchvkGc4gJTsdqV8GJXz+HjuuV\nK72uhQxrU6wtEAtiB7Gp3Oil34cwf2Khej2+9jnfQER7+BH/rOwPMODZWHDF\nWTyjfN/qOuTwT5tE6dfBxu5pMyHSAuESWHYV2VitMhta+TqV8YHuz8X43zCC\nCAHeRlI9fcZ6aVSBSpd2uL1pHWMdLsUtTZ3EVtNt+/Q4SJcdxwp9UYvrbL6G\nQPSC\r\n=x/UH\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"04604849eb736195a110f59ab698cb819f6ca078","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-23T19:11:58.761Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.11","@balena/jellyfish-logger":"^4.0.26","@balena/jellyfish-metrics":"^2.0.32","@balena/jellyfish-environment":"^6.0.12"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.1","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.0.9_1642965238871_0.09284425706599309","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.0.9-renovate-major-json-schema-org-415b987dd1963497a572fec87f82b0b77f0c9acf":{"name":"@balena/jellyfish-core","version":"14.0.9-renovate-major-json-schema-org-415b987dd1963497a572fec87f82b0b77f0c9acf","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.0.9-renovate-major-json-schema-org-415b987dd1963497a572fec87f82b0b77f0c9acf","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"49ea758e1a2a367181fdf609257768e38448f31d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.0.9-renovate-major-json-schema-org-415b987dd1963497a572fec87f82b0b77f0c9acf.tgz","fileCount":220,"integrity":"sha512-cbEocYXdpbQ87wDUUrpM2WT+Kw+wPM/ar9EgLASqcOdoTEMF04Ss5NUw6eSFgn6/LXUPMgZKhdHF3BHAwOrptg==","signatures":[{"sig":"MEUCIQC41SbWpsbr32plPkJI4ctOciXIyiXSje6WZKGvmJxNaAIgHtlpgNd45p4ddXQhh5bvHaj1qI+S7wuAjhBCescBzec=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":831108,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh7al8CRA9TVsSAnZWagAAfaIP/A+n5UZRtaOVzp5xiJVt\nXEtW9NUNIIb7zyFBklnxeaey0sxlqd4jm+A13Vs+6FeWUUQPYxWE/A7iSnB0\nQvdwaHrap8PSuVbY9ZPgbhrJbAgE+oYTKtL4Kl6MDjogtCqCX2Y4duQuW8z8\nh3h2Y+5VoNsLqt8y+hTBV+hvSsKVDNiVWD/JcL7nTZc9eFYzoNoOa9YH0nwb\np6VrDtUct2uQVLJLOSjj1FuP6ITEc6owZSMTpPxny7HS4eWq2dFlS9BaNGJq\nArGoUiS2/mQxjymXIEzN5a2+g/LF5xVzC9vm5UTjNeBvgR7yozUu7ejidw2n\nq13xczKvNRcWPwndAmyD9eSn8g6UPdW2iHK6TTjnFBChytx+UKFXhv7VPVt8\n/pMJI/YwSbUAyrEwkR14s61BWCDVlCbyMMt3I/AA/AgH4wDQETj4JE9pgZ8z\ns656AurMQZ0hqUJYPo1OOnf232WKWDqXGLLP3g4AMyKz0ye7VBvHf7tPEV/J\nhOAVHk7LlcWzIg9sYObXKuA+BVyecfnCV5RuMjp8SxRtAGuuMMEj/HlprNzT\nS+mXtXwyqPUokBMJuS7FVZsHClSvYsvPjeLlfuXsqQ0DXzzyOJJUzh6up223\n7RDuTw1+q7May/lS6oylvS00O1cImT3Z7R5U7xjWF7JmgEkC0T0KTQ6FthuH\nVSIJ\r\n=aCy0\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"415b987dd1963497a572fec87f82b0b77f0c9acf","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-23T19:13:07.433Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.11","@balena/jellyfish-logger":"^4.0.26","@balena/jellyfish-metrics":"^2.0.32","@balena/jellyfish-environment":"^6.0.12"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.1","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.0.9-renovate-major-json-schema-org-415b987dd1963497a572fec87f82b0b77f0c9acf_1642965372083_0.533996776834982","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.0.10-renovate-major-json-schema-org-1df37de2428b29977b80cc3667fbc607e0d31dc6":{"name":"@balena/jellyfish-core","version":"14.0.10-renovate-major-json-schema-org-1df37de2428b29977b80cc3667fbc607e0d31dc6","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.0.10-renovate-major-json-schema-org-1df37de2428b29977b80cc3667fbc607e0d31dc6","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"149a204650aacf040abbc7cf31f577998466809d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.0.10-renovate-major-json-schema-org-1df37de2428b29977b80cc3667fbc607e0d31dc6.tgz","fileCount":220,"integrity":"sha512-15UzwVAYutLMP4rVnvCRcNWEA+GXNZJq+csQ2fQAKK+85+hPkpDcOgDVpEUDe9EOiTZksVHXt99eThFdmp9rvg==","signatures":[{"sig":"MEYCIQDxOYicPEtpkCMDLaAKYO9mrMA+x6HKowRfuUsh8A1hMAIhAIC1ynXnDvcj8rpeKbtAFNsBr2od5X1+lHc1o56O2cwi","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":831135,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh7ao9CRA9TVsSAnZWagAAXSoQAKUB4/ih9M2yDCxkMQq0\nFVpRxw+gWk3XTXFtXdWf6ApCzWJYuuBrDWHBG8tKwVh8coEm0OtzW09FtPlR\nPJtAE3r1nIOX9FlFHJBeR2wYJP7/7bPeOo2LAER1HOTH2z7NpPMu8tDZ0ODk\nwnTDZRar7gqCvE9VtgkK0bK4o6i3R1qgS2lbZyK0VYgE+6ZTAg1aPDzIAmqM\n7ePIiTMNoLfwikMcDkWNO1shtU8YIw2piDOLIZ9CIEPEqWRyyGffjcyT7VZW\nt4NYqbwgD0/RxMRd9QPmrQbwvcSt0lVtqYKXjyp7t/y3+IWb14PNQo1SDnZZ\nxU79gW8uxqelDVgUUFY4EC13paF4bI5J6m4Yhl673ImIOMaXOPgk7Q6UJ2rG\nH8sM5IxcGFpg4OCRHa7k25wkO6YWDEgnhZykWmkqrQTnzOMv2lVuF15rR/Ke\n1ZpX6Rfxgsr/UU8aa0FWQWjwbbp/wdc8qT7H/1xmfeVXpXflEgX+Sc7vXYSp\nE2gzd3GSfJC9UpMCuO5Sy9cDXHPqHEyiT6UDx6xFH6JS3EYsZncVfs1QjOze\nbqaQpIJWpTEKJeIvSRWeF30r4UV2uCoKgWoAZy0uqVeustzq5cjF7jxGEzcd\ndiWP+DXoMbu2IisJiuWPGh9fK2W5yP8eTGvyGLbipSVfJQAlQzxfMsyyNZGL\nD5oN\r\n=vjha\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"1df37de2428b29977b80cc3667fbc607e0d31dc6","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-23T19:15:33.645Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.11","@balena/jellyfish-logger":"^4.0.26","@balena/jellyfish-metrics":"^2.0.32","@balena/jellyfish-environment":"^6.0.12"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.1","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.0.10-renovate-major-json-schema-org-1df37de2428b29977b80cc3667fbc607e0d31dc6_1642965565125_0.5577541367856012","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.0.10-renovate-internal-non-major-188ac1cf955a6a129f2e8de7ee27ff360df75d01":{"name":"@balena/jellyfish-core","version":"14.0.10-renovate-internal-non-major-188ac1cf955a6a129f2e8de7ee27ff360df75d01","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.0.10-renovate-internal-non-major-188ac1cf955a6a129f2e8de7ee27ff360df75d01","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3c6579cdb2ac4f8a962e7603b2e70de21467603c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.0.10-renovate-internal-non-major-188ac1cf955a6a129f2e8de7ee27ff360df75d01.tgz","fileCount":220,"integrity":"sha512-yTq2dU5XvPNOtiiGPOO55s/trXjpbKhdmEFg3H8u6EudWO84KOlMWak8P/CLPBllZ2Na4xc1k2d6Axm5w9l+gQ==","signatures":[{"sig":"MEQCIE38jXCQdZddm+Mj3rj1MoSP9GwQuSrPKQhKvG7P5Y3RAiATtJrUzcBwn86IguY+twdNswTh6QR51r9PvJg0Zzp4DA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":831140,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh7cYGCRA9TVsSAnZWagAAdJAP/2Q4bvXA75oyc5HKquVm\nbR3TqnbNlBWGUZf7hKw2qCUfRhqE8t16cgZe3wJ7/i/5yO7zkWjkwDlGgxdc\nAgd3pp4/OeCmwdFk6wHCqcZagSxpMVvIIqywHwd9HwMAxmrg/sjHM1Shs8R6\nAE49ILvPCLAUFJ/cZJwLMQgJJM4ukj2vwN2qqdv+pfoj+IFoPB5DOcTTg0/B\n688HcgLIVDi3yxVduyVR8UxbgQUqflbKtAv/TawNd7XXeVeN0GOyo4glKxkC\nNSr1Wt13KvE7gtpEQgahqbWds9bV4ibP/kEyznwuWNFS4/Hu8k9UgbCppz26\nc9JGLPxxkiP0boNLqcO4qsG/r7yTB/VGgxDwJMT9noL+B+1cWDO6QS5pdbd8\nFmv3hmRpBOEHGrDMHVa6UimUxsuVkjCXjm7zK9HpEauO5eeb1v3ZXTOAmDxB\nTjMDd7dF/Gkg4vvGn4NM2d4maLZ4SCOPsq4kr7XYBP7RbVQ8xM6TNmEcCimZ\n824CHOVv0kIUX6EJ2SfJ6u9Ij/EJOmFh4GDH50b8XGs8Lwf8HMKWjj297Y4i\nVXD/IKro1CvTHUfvsFbtq0W8kCTgFbkehQsTx1QOQCpmKZkf++xj4K/TLJny\neHgsZemAnCZqaEx393ka91PIJztS/e6wtYSqm6t/wfC4vFL+mL5sJGg3Ru0r\njOMJ\r\n=dntM\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"188ac1cf955a6a129f2e8de7ee27ff360df75d01","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-23T21:14:45.559Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.11","@balena/jellyfish-logger":"^4.0.26","@balena/jellyfish-metrics":"^2.0.33","@balena/jellyfish-environment":"^6.0.12"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.1","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.0.10-renovate-internal-non-major-188ac1cf955a6a129f2e8de7ee27ff360df75d01_1642972678720_0.9793063154258324","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.0.10-renovate-internal-non-major-e51af3bd9d2bf6a7d46172ebf33a737bb477aae9":{"name":"@balena/jellyfish-core","version":"14.0.10-renovate-internal-non-major-e51af3bd9d2bf6a7d46172ebf33a737bb477aae9","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.0.10-renovate-internal-non-major-e51af3bd9d2bf6a7d46172ebf33a737bb477aae9","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d8ea193d2d793cde1d51f217cfda1053e9da1711","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.0.10-renovate-internal-non-major-e51af3bd9d2bf6a7d46172ebf33a737bb477aae9.tgz","fileCount":220,"integrity":"sha512-w2Gp/m/b8w5sOcQ0xWtONiiPhrSfNRYstQzoWrPRLffNALUugouoD5qFUgAm/ekZMtBJmofGIIpjF3lHKHkhIQ==","signatures":[{"sig":"MEQCIFhIKw9rM3YM+Jod1A48m0L8scsx853MLu1SUn4g93XgAiBRWosG6aeGr/I8p+oqe2lOiy++lzyH/5ZCzoqWNsu8tA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":831111,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh7rO+CRA9TVsSAnZWagAAScgP/jk6nfwIgGE2kzT4ABAI\nO5b4Qq73HdDr+yB2YmV35069KUREcLMIo2jQaehd2US9jkVNOPf9OzVcUEZW\n84KW58BOu5RKZTODOS3NVaNMCwgwOcEtnGvs5sUKXuEAgj/IGlsiWikKfwjs\n1Lb42pHnqnooV9EcyAq8W6rDPjLGymtdm/vkE2M7f8MzpngjIb/0K7Ks8Dt7\nAcsvvmZ8iRmyJmkB1Wqym5zAZj3mWXRQTTAI5exS7FlFi84WHjAVlFsQy+2P\ncdfSCPEGGqu4K34Ar5sKRLg/3JW+vIHjh0ertN5b62jmKqajFxAdzsr2siuU\n2A5jrj35KDeQcS/1iPxCB6arOu8NB2/cJ2p8Kg/MElizOwenbUL6WBR6+C29\nvRYg8hpIyxgZ477e0EEaGGLoEISvQ1s61LZqjX0mEziu1MaHHVmLSFg7ZkCE\nwDjJCc1f0nlyG+tQT6OWhAnFmdvyB3JX1ovU+rRLW3ivpL3aDooCc4/1W6xH\nt0jbXcE5SkiRth65I/EggGu10EROzX/LsW+6D9kRvzJq4OCj8/7o9twftXAL\neAY4+6D6XpzKYQRuZ3xa+tQahj57sLodXVTUs9g42os6MRtHqzp/ppcUMh4I\nIfGblGC34J0RQ7IUqY98K3wDhPgl/esZrs2VXHBYpLaxPOcqo5EzGvrfKefv\nEVkv\r\n=NmQ5\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e51af3bd9d2bf6a7d46172ebf33a737bb477aae9","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-24T14:08:57.123Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.11","@balena/jellyfish-logger":"^4.0.26","@balena/jellyfish-metrics":"^2.0.33","@balena/jellyfish-environment":"^6.0.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.1","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.0.10-renovate-internal-non-major-e51af3bd9d2bf6a7d46172ebf33a737bb477aae9_1643033534002_0.13689569609897978","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.0.10-renovate-internal-non-major-cd86823f83e4ba7c368c609ae4a223799e86ab3a":{"name":"@balena/jellyfish-core","version":"14.0.10-renovate-internal-non-major-cd86823f83e4ba7c368c609ae4a223799e86ab3a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.0.10-renovate-internal-non-major-cd86823f83e4ba7c368c609ae4a223799e86ab3a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8dcc4e85736d5fa1147158a126b1ca64fc37a17a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.0.10-renovate-internal-non-major-cd86823f83e4ba7c368c609ae4a223799e86ab3a.tgz","fileCount":220,"integrity":"sha512-pt25vjHkW2tw/yqzHStbl9lJMtXd5iNKND/zXrkXpavDPJYEWJRTznYhlXn9EPi3ajCoAbd2j7uNQxpYvFf1UQ==","signatures":[{"sig":"MEYCIQCTBPJJky1G7YtvG4VxUIEOSOuSlgKnij4Jz+a7jutt8gIhAJ1Zoa3C2bJeZJymD/eeB75d4JD4TX+1pdxyvrDbaLR9","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":831111,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh7s6hCRA9TVsSAnZWagAAYEwP/ixr9yxtiGUmtbzOb3xy\nHu2nnZ/TVTFuPupyIaKo8q71eH7LThuUE3csdsSmroHrRAaWjDigpzKgcc1l\nivy9n0+d7wUkh02wM0PaA+26D1pirFb97W6TrG24gvopTfm8EuOzt5ajOfEf\netfC+7FVHDQImmf9GFwyqJNOYqHZD9aL83vrxPTQ0WCxqzZDXhYTTkJ3H0PE\nVWqoD/iIDpNm0V4frlP8e8S12D8lk3EFMWRA6u1sFa4McD4PoT6PlOGthY4B\nJuKWDlPhizRGQiCzIxrYeYobu92gq/1heXPAcyiUau1JfFAgO9MTcsgNU9XU\n+YUuzvT35KbnY5CU0A+uSge8NTVeocdcEKI97NdLNkFzQ9plTBtp5Aauvxy8\nDGRlRM/yEkGm5orqH6H4O2c+geiYFCKcbYUFwWHJL3uoc9UQAY7YW0Dc/vTX\nN3vQEJ1i4B8E4uBtKy9G7XTRdTgr+n8i/ttyRPpvKGeycyF1PPo59+Ngd33K\niGjFZd9eq6McEN8CYfcWirgnm/0TJsyLppOypXdhEFKSt7bUTnG1JsuPG+SV\nSOerJfUYRPDMAKkd/X7gxuYHxj8m6AlbEWCyr6WFP7nppCkoNUigZdapfWhS\ny2o6eMH6GVNxY5OiIqsWhLmhqlxh5v7oGG2kuUv3kdsZbzEll9XpoFjO8QyG\nTtUJ\r\n=d6zH\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"cd86823f83e4ba7c368c609ae4a223799e86ab3a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-24T16:03:26.624Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.11","@balena/jellyfish-logger":"^4.0.27","@balena/jellyfish-metrics":"^2.0.34","@balena/jellyfish-environment":"^6.0.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.1","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.0.10-renovate-internal-non-major-cd86823f83e4ba7c368c609ae4a223799e86ab3a_1643040416963_0.15527770268088337","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.0.10":{"name":"@balena/jellyfish-core","version":"14.0.10","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.0.10","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d399e05be49408cc5cef25c866ab1bb7d0e3f1ff","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.0.10.tgz","fileCount":220,"integrity":"sha512-O7ONa262g7A5eDcOQwUYNXeK/sl7NJWKLZsvOuQStQJSLISfvc1QM6r27s0WbuM/+3FyIOwPRAg19Hnej6njBg==","signatures":[{"sig":"MEUCIQC+EEWRiLH7msdCYtlcXItPjrhL3pOXUH5Abh888UxjLgIge1Pul8Hk9GV61GPXNISgeg6VUv/Cf3FaBV9O0bjbjxg=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":831042,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh7tBrCRA9TVsSAnZWagAAxZwP/iLboDmU11frS3OlNrcA\nm5JBEItMijlKut3LwUg8vmFXm2xjKA/Qt4te78E3w1dH8YCBBC5UYyqvwSsr\nsCzpJ/R+zNUSSvfYfSmbjm2D8XFpeVovXLAi0llDZZ5C/hsApV+k5Q4ZwlIK\ntNUSsP2OJ9tr+G+F6gt1eufl7dxwe9mWHUr6auzdcZxk1dv00xLWX9HTKRLG\nGc9sBsp0qj10qU/mSrBaoCvLg99qO1FxpFPW2TjHBj06dXUDeNpT9yrhYJGK\nB7Iy5BfIEPrGR8/rqc9vpoVthes5oWaa0dfpPOmD9bsBzSeN6A8zDJ+VJzoj\nu8X5QR0EWgh/mqoMWXj26KrCZgrhF5nINi8XIerwXEU6/5klr6BmSEWGr2nu\nwddDc5ZwJI8s6rVF1Vq1I6oFVWuKx0VIrsjDO+zXvi/hnl+RmlQHyuy4qIsb\nVAib4koF4yJIJkGH950N8q2a6opfcyyd9kxcNuXdcBeo5vbwqsqRmvvTvw+P\nioMvDfdGMEHiJwX61LXdb17GZhrKp16mGq7aL3YnlQ/Ek8B6uiT9oKVLxIsy\niQwKC63qXLPm1YJhhRH6/yK8MdDosQWe0WGU2e/CDRFcUiiPCNtSnWXO/pbF\nGQVNnCNYfakSuUmr5JZhs8KqO8gtKFDIR+g7fcAHrziCSWq6bNpqVfWeF+RB\nrboy\r\n=/PSO\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a5168f5ee067eeaa281f3ecf9211e6682f5bb8ba","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-24T16:12:29.195Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.11","@balena/jellyfish-logger":"^4.0.27","@balena/jellyfish-metrics":"^2.0.34","@balena/jellyfish-environment":"^6.0.13"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.1","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.0.10_1643040874988_0.5198447835242641","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.0.10-renovate-major-json-schema-org-6c52baf6ceb547c707f47f1bb5c344c0004ed82d":{"name":"@balena/jellyfish-core","version":"14.0.10-renovate-major-json-schema-org-6c52baf6ceb547c707f47f1bb5c344c0004ed82d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.0.10-renovate-major-json-schema-org-6c52baf6ceb547c707f47f1bb5c344c0004ed82d","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"88f996ada18d2e327d40b1a75431048e0f9b6e79","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.0.10-renovate-major-json-schema-org-6c52baf6ceb547c707f47f1bb5c344c0004ed82d.tgz","fileCount":220,"integrity":"sha512-dbV5W7p3JhHJIOG4iQgjzMbh6nGk8G0wb5e7GSF+BcBMG5v0l2rrqQjhYedXudxn0dS+sI0vHpbzWLtdTP6oAA==","signatures":[{"sig":"MEQCIFLPFJTPoW/VlWYWvAZS2snH0cHr6bGtOsQGvyGgI/jiAiBadPFlwYEL5CrMcSkrVNrSC17gkgk+pnLAxBqrHL9Q2g==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":831185,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh7tDlCRA9TVsSAnZWagAABv8QAKJyQU9pnk8RUjISGZU8\n30aMbcOdViWdrj8tz8Xqb/Avmm8DlZrgxfnzqi0du9ph9JrOvbWshM+6RdqX\noHFYEjt9YIkohh1LkzaXszLTjwhjrlhaP92kSKcTHPNSgCi7N9K1+S4hiqH0\nfA0tr66ebwmhcf6Ilaepr919mJguQIK80KXzKnsocjDy3bQsm9GEfbxug0fA\nOvFjyiceJU+B5BbI5Hn4FTKee+CuKpuoohe1Y9wajIo+VfS+3S/788bXn5F5\nS95M6nz8reQUpKK2FGErXU65Khtbg5sWDJvHS0dTiPfnneO+cb7F34Yxgpru\nIfPKzR3BeKpfclXjWLi2L19VlT9ZMWy/MsdaY+KtJOGsLFMCtefZyAydudp9\nrjXcd0A2xcXG34w3ntz2+oBAPZD/b0q9kU8WWRVkIrPCEmaIoIFG1s89+ROf\nHbdAWkH7+08zX0bxbXvvSbmnu6HVhEILzECJAXSKocxyvLO4nZqBUaHMTK8m\nV7qQj98K/O5zZv3DcS9oQsnq8To9+NC3DIVenOv3qXg/PL8Gci+LLZGJMwti\nEbHOuKc4S4P/nb7R6NOYtjbpluBmoapgWYv6ODAeBWzlTqVAP7buuYJ1fuuG\nw01GAHy9tdzR8zKuZiLLAUZQmwfG38MaV0I9iVp0qCSXx+qb5jwF2Se7KaSl\nHAKZ\r\n=no8q\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6c52baf6ceb547c707f47f1bb5c344c0004ed82d","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-24T16:13:28.278Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.11","@balena/jellyfish-logger":"^4.0.27","@balena/jellyfish-metrics":"^2.0.34","@balena/jellyfish-environment":"^6.0.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.1","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.0.10-renovate-major-json-schema-org-6c52baf6ceb547c707f47f1bb5c344c0004ed82d_1643040997437_0.8913487285126787","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.0.11-renovate-internal-non-major-2121310033983518fd267ad9757d50773e0b528f":{"name":"@balena/jellyfish-core","version":"14.0.11-renovate-internal-non-major-2121310033983518fd267ad9757d50773e0b528f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.0.11-renovate-internal-non-major-2121310033983518fd267ad9757d50773e0b528f","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"702d41677b3f7967c0457e1755190fd9aeaae82a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.0.11-renovate-internal-non-major-2121310033983518fd267ad9757d50773e0b528f.tgz","fileCount":220,"integrity":"sha512-2Yov+8JxsOEOcoDzv18SUgCMixzY7dYT55f8vHEunW+8l28eXuf0nJQKNk6SDj1YByq31fNZ9g3eMrdQdzL29A==","signatures":[{"sig":"MEYCIQCqtsB3XSLi1jt5GLqtna4oG1YCOcaluTrb4IAV/Oh1VgIhAKF5YRfhn2bd/Ui/nACW+Jf0VLxw15tP3fBm+E5RLG1D","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":831216,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh7vRbCRA9TVsSAnZWagAAXsYP/0EL1gZ2BBFHUk7hsFaT\nvsehV23j+AMKX8hG9OCuxR9ePA3xncAMffzCeLCm1JbiPwBWgPwymWYOLZWy\n7gSzr3JJq8Zxla2QgGgzQOzIkjgv96OiIOXn/wnu4BbwsM32hh2cx3slN8rc\n1J3G7/bjyC3TiJqDum2/gf1j8TXqhEzwQPorgQOHmWYFR/nqTDjEB32zuRr3\n69P+sgIOzmhxWAI+OMziX4YnFMM5jgznd566UCBPTsUVH9onK30frzCTEQ2R\nDTZQaansV7Kt1PaO+pfwRSLgaUtE1VsreBRtWmVWpyqUXAb0FM57rkXT97vN\nxLLusix/Ho3BYwOKy1ierfa2UFW+QXJT7SnukY4fdWaz1bhwk9eYuUUF01CW\nTy93WZnaqwKaK9TZvI6bPzVbtRYQJmIHQpTrfxyJsY5l9AEDrWPF44BQ6c1o\nJ4hU27U1WX68F+heEMb1I4IvbGFVlGw0qsaAVzw6Q8AtbNvDgXyhB39Tu1QO\nZn5ceBIn9lGeU6P5j86ICPqznwhmbHNUzwR7gUMjfzKhsz2V2KVbuPl6/NRs\nl/FQCEFwaeZsckL+7GRauItXWVO2n3wlA8vnVPrMbF20KB14k1R2fGJWF/Wd\nmcJ1R7o9w8aztHSh8EQxRTGpBDtSvLNxpeDMFpRHwg7c7cM9M2SspCOdXWqH\nk6LB\r\n=q+YS\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2121310033983518fd267ad9757d50773e0b528f","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-24T18:44:49.782Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.11","@balena/jellyfish-logger":"^4.0.27","@balena/jellyfish-metrics":"^2.0.35","@balena/jellyfish-environment":"^6.0.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.1","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.0.11-renovate-internal-non-major-2121310033983518fd267ad9757d50773e0b528f_1643050075481_0.3893349805559734","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.0.11":{"name":"@balena/jellyfish-core","version":"14.0.11","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.0.11","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"dbd272825a1465e91493379c2e51ad5d2b4a8ebe","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.0.11.tgz","fileCount":220,"integrity":"sha512-690CBC8sfOSEarFJoXuL+69ErbOfdo8af865Ml2vECOVSicmdui7c+EeP2U3rW8dZPH5ZJ7OriafflWdQDwvkA==","signatures":[{"sig":"MEUCIQCG3MJDrBph4kApGozbYAfTfBjYXCgqyMgYpNgvB+lzhgIgDjD8CjFfzC7D+YvuKVMVc4giKIFqdbgnCIlSDYR3IkU=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":831147,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh7vXqCRA9TVsSAnZWagAAHKAP/jCZgczwj7WoiTuc2tyY\nEL1PFqwL3WjKYfqJLCAiM4ZRv1RGzhY/YhL/Fvph7ZtGhWvam+ZntabKyJaW\ngEsBAvDTRybKoKrPm72Rvu+TUL3FDqmH1So4FprD9O23wcqrbTDMKPbuA5aV\nK+pGgAJjHRQsDYZRiuWpttplfw4FcfC7pAg5Z0V/mMwzLK4y3q9TWDV9FjAw\nS7bgDijAelfO4Xn/3tut4Yybjo1MPHSnwACFI8JFyO7UN1M24SUV6hQ9LlND\nz1laEBZ+XXpZThxTrqgNQhrn4RUmOyOdH0Womdo3cxb1vcBX5db8p5l+j9nk\nPZxleJ/OO9nXvwmB7V5Cf3Dn5+qAw4pujPh1X8n9LPdwgpRRkfbliRabd599\nUsixKtFBHHOuxPGQQwo7p+EULtCtRfV8sJK4UMtHNim7vjAK1bcGzsKbEeWk\nRFs4XUbRn64k/FNkzONNIKl69H08v+EmvzCEDFBv3QcbE13gBLTmfXoAfg5F\n9dVlyyjHBq85PAkhI0sxGREYKFSm0jCdaE35YFj/wzm0+RA3kZq2baGbofdA\nVO00C63b9xcZ3GWzig9W8N0NG35fJVSkMgHnnpxAOhOnXzF5jjX/HvVDCyYQ\n2XzRRFEGTYyf6uiC89S5+oK6FGAubRNMT7MHSOg3rvGWQeNwMX9LJmqMkCgp\nZAJ/\r\n=c5Xg\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"12db17d9f8fdb700a7548a39a0c03866412fbd00","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-24T18:52:29.691Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.11","@balena/jellyfish-logger":"^4.0.27","@balena/jellyfish-metrics":"^2.0.35","@balena/jellyfish-environment":"^6.0.13"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.1","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.0.11_1643050473850_0.237521609183303","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.0.12-renovate-major-json-schema-org-31b825c75d747411c456159ef476530182d3c781":{"name":"@balena/jellyfish-core","version":"14.0.12-renovate-major-json-schema-org-31b825c75d747411c456159ef476530182d3c781","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.0.12-renovate-major-json-schema-org-31b825c75d747411c456159ef476530182d3c781","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"fe4a5d036ec366cdb9d36da92aefac0f77555f1a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.0.12-renovate-major-json-schema-org-31b825c75d747411c456159ef476530182d3c781.tgz","fileCount":220,"integrity":"sha512-SAVqRVX9UaMmKtIGRw+1QHm80Zl06YoGZ80U6KMpmx+fc/zAib8hreeSAVZkGt4m/4v4IPIzatdPVRZogLCwZA==","signatures":[{"sig":"MEYCIQD/GBxgRJr5TLyIYglQ47nlmaOcfHSIMHD1xkVPwY1KagIhAKepT3QEiAeNGWumiy/adPaKZ1upFe5kLnue7JAWTfvk","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":831316,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh7vZ+CRA9TVsSAnZWagAAgucP/3o6ppTT4vaBZoeTEHtX\neRGMM+jN/H/4uY6gvrAT3qWumUCKj3fdWrNX5aUC4LDs71BNceF9ck0xxm5h\n8B1zRHrFu1XLVOAAZM0uKQaCOY4CF99jWoq0VNfAHeTTVZ8f1QuwFTMDgmR0\np7EQMp0cj1jIg0ulbFT8gxS/FIfwDuZEsj11ZE39qCzP1bM/4ozrmdSqk9m2\nrSM/RMWN/XVJwEToGt0VnfzfrtRA6x5v/tk1YawN9dc3+EinzbIM/hfOdFXO\nJh02s277Dhd45hHF4RGXZPGA0U5EmB2wvY/n3TeIdJPNKxteexcPFU/MZBK0\n8LsolEkPw6O/mLi0k8kkVKBVH1XUZmGuQ9wLZEYymTVo3eQ7/TIYFC1zC4XN\nZC4bCqNyN43073aLczrv1TZMdr6UlABznhOybvNFqq/fzTNPU2bdupGwsKTV\nL0Rz1EWyK0VYZFwerHUrwHgMGFvN1ZRr8X4tO9ku9hzNYK7SGmN6fBkFxojz\nADDpLac90U2CNZTvILJHPyxZFUNA4PQH5U5gkq0FtZDE1Gdq1hxQXW8KDfcX\nlXRgd1z+4NDoE/L12m1QYhXvjn4X3hHYvxFHiyFXMW9Grep6og3rzySMWlvF\nHva0ap7CR7zW2kQC26pjDOUXwxKIZaQr70/uNMlHxEN+viVzLOuUeu5JIiNe\nJP0t\r\n=n6TL\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"31b825c75d747411c456159ef476530182d3c781","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-24T18:54:17.636Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.11","@balena/jellyfish-logger":"^4.0.27","@balena/jellyfish-metrics":"^2.0.35","@balena/jellyfish-environment":"^6.0.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.1","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.0.12-renovate-major-json-schema-org-31b825c75d747411c456159ef476530182d3c781_1643050622733_0.6461966921879023","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.0.12-renovate-resinci-jellyfish-test-1-x-bd42d5d358533551b2573b6a354b6d7b4b4e33b7":{"name":"@balena/jellyfish-core","version":"14.0.12-renovate-resinci-jellyfish-test-1-x-bd42d5d358533551b2573b6a354b6d7b4b4e33b7","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.0.12-renovate-resinci-jellyfish-test-1-x-bd42d5d358533551b2573b6a354b6d7b4b4e33b7","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"073ad3b629fef351e513c53e01f28b8b00ad3de0","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.0.12-renovate-resinci-jellyfish-test-1-x-bd42d5d358533551b2573b6a354b6d7b4b4e33b7.tgz","fileCount":220,"integrity":"sha512-T7U8HIQ2mi/6ppSjItIHQeqf0YsLuGseCmtbL/eDSOYIM7uPON9YE5zdcFsCd34PtPvGuBxGkY8k4jVYFFrE+w==","signatures":[{"sig":"MEUCIGipVCLd2osbW2FTuU9GG4RqLBw9UIsLCPPkVq8bPhU2AiEAoknTEqsKfOiA88rGL4a9abEVbugE2amtKatxcSsc64Y=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":831326,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh79qqCRA9TVsSAnZWagAApmwP/AoqXmDrRz8k4vE9VVSU\nUVD6EBuaI4D+tnrF7TMg2p1WEf1TiQUzPL2RQMrWVACJ58H+gG94oD7EYrcp\n4ODSu8GXGCvGSDectcj8adDJkZYyJc+FxLw+8G8WYvmKIY2RWecA3lT62/MK\nt02eu/WA45iRcUyTLjUoUjrMLeg/KEHpCuIvyWUXhmdA7Qbx/4njhHC6O5fv\nyc3yns2loiG1JsFB4/ChiX1Cyjhy2oYsB+0B7/XcjnQToh7tW1aNBqtqizcb\nrFRVVk0OqLWmfh/JvPbhE/JKbfRSmJu8ZqxzShDwUEKnoD/YuheCHm1UGDnz\nJzRCWF5br4TftC/T/0aTN1VVf8khbPgJ8atiyv4R7Tq2bq/Ml3isiqaaN91A\nIM2MDfWUZcfV26105F3LzxO4z092XWAcI5OyHpkE8WK8fRQJK+im4sMYLU5c\n1/x7FBgMzT2i3PIPmpibCc8M/zVEtRTwhrizyWI1ERN/3h5ntpNC/r16rT9h\naiIYXAa3P/h0bRFOdH4PQbbW/KnYhJova6/KdCpowj65PW//oo+z+4hoPAxF\nl0XZ/3OBEYZ6MY4IiR4T/s/qauHkNZg7HXVjYvk3o0BHJ9ne7jFaAm6ALClT\n1v/HoRjy9gYcgyzYNgELB/wmDDuXBxI7iLSyGnSjXXat5sEQCUNYHU3p+D50\nktCs\r\n=fFLu\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"bd42d5d358533551b2573b6a354b6d7b4b4e33b7","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-25T11:07:42.242Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.11","@balena/jellyfish-logger":"^4.0.27","@balena/jellyfish-metrics":"^2.0.35","@balena/jellyfish-environment":"^6.0.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.1","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.0.12-renovate-resinci-jellyfish-test-1-x-bd42d5d358533551b2573b6a354b6d7b4b4e33b7_1643109034569_0.18887395478517477","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.0.12":{"name":"@balena/jellyfish-core","version":"14.0.12","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.0.12","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f853f8a95c35d473fa3cb7daddf6c2f9bf613920","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.0.12.tgz","fileCount":220,"integrity":"sha512-lXEQNbJMXe5qrY48Zp+nZqwXDHpXfXqA6t8NXRaL7Pe+lcSokds6fIUWp6SfjU94Qg8r3k7mvIKCTKM8O6p1HQ==","signatures":[{"sig":"MEYCIQDHQ9WyD/+HCbtNZpcjsHfMGlScdOmdVlZ6sA2dkhozLgIhAPeSkZs0RvU042j+/HhGXwSJcpfoKO4sFQvHKtC88yGw","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":831249,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh8EcFCRA9TVsSAnZWagAACfQP+gPL7/6ds9TwE0qqq6sy\nzZaeGJ7W0dfOBqvthGc3xHYJ2YH2w/0yji9GKAQP5YL2ptK7y5GsjJ0txo2J\ngZuzgRgvebb6tN3IfREqWdkjxSNp7UPnzxI03T8stTZ7f2lmEsI74Cat85jq\ncWLdmQ9yu+4u15GP/7zHyinpGv5QpA9jm0vwQLVU12ls8i8tdp9xdkyPcUU/\n5BcuVPxH6q1bTDywqENE/oqdn7gt5FhsKBEruIoOnRWzyqOPxIhvTdk5jH5l\n/L/h2jcRDRsmj65HCPcqhoQXZFCSCUusGM6j+Vwrc7krjb31Ub7T0BYpUi5O\n3LkH7gCFlmJenH9Lge8BBoU7RTn7+tnFC3Zdf1+WztY+eCl0hDxDbr72UxQj\nf7K3L4r/LBAb9M5bFhIeEE9HP5VzJl/c1fcD/ZEuuN1BQMWfKA78EBCmFkmy\nDirBkNUcE/sabKVbwGnjSHljC0bfRhqiC1WYM4Zo0tncIk6TbOolyFpHzE9a\nEAOFDJ3p6hoQ8lw2EhN0mwhQqwFOaQYQqxX0RiyzsHbP5iIzxkMGktQMy93q\nWzmnGJQn+6RIPXF7dE5w3zShb7rAnYsG179vmk+ndRjbl3rr8Neuv+xaZDRR\nJMh+XoMJQrQ9OuQpV7PInAELbDdFpdq9VtILdtbervOjyOkbRdv9yhUQXPvU\nCkkI\r\n=a0Ge\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"eec33650b2b0418de012bb9a9306285cb02315b6","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-25T18:50:37.074Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.11","@balena/jellyfish-logger":"^4.0.27","@balena/jellyfish-metrics":"^2.0.35","@balena/jellyfish-environment":"^6.0.13"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.1","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.0.12_1643136772878_0.43723798450336493","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.0.13-renovate-major-json-schema-org-9c9e65ccaf83f247ccca4cb4c4162598fbba1f56":{"name":"@balena/jellyfish-core","version":"14.0.13-renovate-major-json-schema-org-9c9e65ccaf83f247ccca4cb4c4162598fbba1f56","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.0.13-renovate-major-json-schema-org-9c9e65ccaf83f247ccca4cb4c4162598fbba1f56","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"71041352b52bb32ae2985c13cba226436109f812","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.0.13-renovate-major-json-schema-org-9c9e65ccaf83f247ccca4cb4c4162598fbba1f56.tgz","fileCount":220,"integrity":"sha512-+QRbOAvmzU8VDtBDnEpa7jqMzSR4L0V6cV5Na5FAWXoKXhGQssVwB7DJnFjROWPd9Q6Nq1ryTJrflmlN+rfnUw==","signatures":[{"sig":"MEUCIQCebb4wRduaWXX4TsjChMjmmhJEUKBHuRLMEsmmK9k3SgIgG6h5Fg4QHOvtgGv9Fk6gRV+OQzjhz78TNXhipjTbFcw=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":831418,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh8EgPCRA9TVsSAnZWagAAqSkQAIgI0L/Bk3LZ0gJifWY0\np6E1A3qDwZD9C74DYHK/kYkib6fBv2kfsjBMnKuTN1cV/AcaTqHc6mkjSNbm\ntB7xCdzIyS9Pj2S+Pk4ecrYpQjnB768wGYj3mSHTtBYCvjHomFozAG2WkFNc\nw3GVCdwIq17d0eKy02a4eDoxVm4opgRhCv3LiarRAXJuXA46zVtQ1nno5TOQ\nvKahqiRkVGGC3puxjvlT75D0tkFNrpxaZ4iqb10LW5pTlGB60R26YpShFS3Y\nxQhhutlKQuFazyP3F80H8XhQO03B2pP1ArzQLZylrcyLJavZwLZhiFFx2877\nxNWd0TE6OcprZJn5lBCzYRTGHCKsfBSUQbTrmzKdknD1sfyfL8CmRtdmBLRR\nZVgtSQj51VzUQzLzxuLPw+JUNySug1cd1kZAHc0CO2TP7zouR0gRneTPBwhY\nnsDSTa5r7lXz1meXEgtxiVdgOPsycUS5IWDxARfesIzjSMz2RZmcqkyD6mao\nWIilhaZNKVNV2px1Q+Ih5mcy4bkQ/IvHUST5BDkwBC08cVT+tsAgQr1a9v8k\nrL3lugg+vTTDyuwImuqWJvuK5Dwxb2NF7fa+g3FATBSEwjK7DO2K1nadSLTX\n15CSjyQhZAyd/Ed0Xz6N7nEdN7JXViYjQxGu25RYFy0ky3ePm04vaafjXUKb\nracj\r\n=BLSn\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"9c9e65ccaf83f247ccca4cb4c4162598fbba1f56","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-25T18:53:02.225Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.11","@balena/jellyfish-logger":"^4.0.27","@balena/jellyfish-metrics":"^2.0.35","@balena/jellyfish-environment":"^6.0.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.1","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.0.13-renovate-major-json-schema-org-9c9e65ccaf83f247ccca4cb4c4162598fbba1f56_1643137039257_0.6518508462485291","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.0.13-joshbwlng-export-ui-schema-def-901d5ce1e5bcfb2aae74609e03d70384267bc046":{"name":"@balena/jellyfish-core","version":"14.0.13-joshbwlng-export-ui-schema-def-901d5ce1e5bcfb2aae74609e03d70384267bc046","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.0.13-joshbwlng-export-ui-schema-def-901d5ce1e5bcfb2aae74609e03d70384267bc046","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d5718fd51b5769aebd917fa3c511f6b579ed58b1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.0.13-joshbwlng-export-ui-schema-def-901d5ce1e5bcfb2aae74609e03d70384267bc046.tgz","fileCount":220,"integrity":"sha512-1fULwoFgWtqRgb5S8vRPMUvyvvmwY59kHRBY+VJ+iyoOqkAxgqf/YkXfjrFhE0KG7hOCsgbLzvtn0lwiFs7skg==","signatures":[{"sig":"MEUCIQDA97FLL9Vjm9xTJXaKMmBV7pqxnOgtNkuD5f/9Ae006QIgCIQQRFCVuZa1JeVmjE4qXbh7AYQXuIdjcHr5C+fZTNI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":831683,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh8JbkCRA9TVsSAnZWagAA5TIQAITL65a4WMn6sO845DOh\nFDypMyculzLTsZ9pwjAwEk433Q4rGM0NbPVNKlo8NTZR9T21Zj8/gqTKZ9uw\nE1ZMYfMk4oxj8Y9xhzgdZdD5Vsxg/Xwpx0mgHNXOFyOO5DlCMxiiAeUXbbhf\npSYNR5KIwaRpBnhnLl4Zhn/IwXjR9n7QxJFvUtTvA2V/+lZ8TTgzbPQcT3OL\nHncV8AoXGaQP3W6DuwiK5LWc+UYoEovFeTYgvu8pg8ipHyh/r65eLlaSs6SH\nUP+sVHyYgJJlOW86c5zL8RTR/2ddsoBDEnu4w8sj79PT2ADvqQN3X48SfkCB\nKEyc6Yt4Acj+L7SO0R28dzCIwleeY5KC9zp1IawrLUFogEVhpg0FzTQ+eNgU\ni47LXQiyw4pwWpFCTbs4Z35rOloe3ccm6xc4PZtoJPoklYlGSVY/CFgTJbEd\nHLfrIsbipSM0JMGug7K8KUrsY27o0GV/P71w5KKp/x+FZ7jPhzZknj6Csxl2\nznl4bVTKwg6P8ExNbiVE4gKn43Praqpgoy8NUEWqTT2dhouCC2+TG+sCTX57\nw9w4dweOwRBbzkPVuWxgLNx8X2nZZFmjgoCVPnTUSWyxHpnzE/nTEqhWDOdF\n5I+L4XceoBP6YDSmBJ1cahUYhIbQyeNoxKTcz07Mck3dH1YwyCJGQ0QLwg1D\nS7Nm\r\n=UFX2\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"901d5ce1e5bcfb2aae74609e03d70384267bc046","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-26T00:30:20.938Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.11","@balena/jellyfish-logger":"^4.0.27","@balena/jellyfish-metrics":"^2.0.35","@balena/jellyfish-environment":"^6.0.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.1","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.0.13-joshbwlng-export-ui-schema-def-901d5ce1e5bcfb2aae74609e03d70384267bc046_1643157220666_0.9095170269277302","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.0.13-joshbwlng-export-ui-schema-def-2489e5c1f43f4c698e6ad2e3ff486f77201ce9bd":{"name":"@balena/jellyfish-core","version":"14.0.13-joshbwlng-export-ui-schema-def-2489e5c1f43f4c698e6ad2e3ff486f77201ce9bd","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.0.13-joshbwlng-export-ui-schema-def-2489e5c1f43f4c698e6ad2e3ff486f77201ce9bd","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f6bfc425afc67e7f7e8a089949a7479098dc37f1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.0.13-joshbwlng-export-ui-schema-def-2489e5c1f43f4c698e6ad2e3ff486f77201ce9bd.tgz","fileCount":220,"integrity":"sha512-r4BlvZk8MoSyidTptqcumTm4Bb2ytWWGMHDHrRBt0hv8bp7roPE4UB+2P2VjsK1alXQsAxn5chzQGEkypWC70Q==","signatures":[{"sig":"MEQCIBpGhWBeCC+Sym8xqKP+UrKWh199hjsfm5fk1KLRPZskAiBmKmEWSL5DVRumZPsd87ShVI9QkDGlJOxTeSTBChr84Q==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":831683,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh8JjsCRA9TVsSAnZWagAAOHkQAJuqM7PQ9tKTXPjfUg9e\nShEWYQPFyvRjAMYDw4XEY7cGREx6tzPpfoEQ++ZAIPuVcD7UXU9PcKahiSXt\nLba53COlkWWOLfrgSL47V8Lam9GoSCQfQNwscaDABsAJsfZ5pzrc3XOsFIQd\n/wS8Wb7EkUqTZNr7QDr5phmOk8Ujh6Gh6nK5Cvv+PAaCD2l3DTP3BOK8MGti\n3KLl6+PXTHu75weLhYIhbLKfjH/CMfjgkAJHx1cdHZNFL/ymhriLYM6nsQay\nPjJEvDPh8N2gWLgJ0RQFkfXcDxjphf0GFxDQcFDV3Cr3Ixi4E66PRNuDvGuM\nXvQeXGOr7Wvc9cMgk5RTikSjelLjuk/2DHmi0P9Y3dDINaEjXYInOCwNvS+V\njeGJ22bHfK20aujDoPjqYXzpx4EIW6s+1aZoJHv1VclKrPdow1/50nooLOoR\nggEe3KJE5nIIdXq+viW1jquWK2AEl4cSH2LR59TkxeGXZYbxYcRlBPU0KYdB\nDs3225j7s/6S9mAmp0fnu3pTv9YfTrJf0yOCz8Q5isMdoDAqvjPLi7sg1Krj\n/w43A/6g87GfcuvE41eGa0PhMehQ1jE8mcBvTb7QUg+J1wO3YgpGdvrv8nIz\nhL50+zfXWQAGK2HSZKDuvaZa17UICGiP/oDd1Oui15WrBKmpMeaCfLap65Rw\nQiNG\r\n=RuJO\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2489e5c1f43f4c698e6ad2e3ff486f77201ce9bd","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-26T00:38:53.379Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.11","@balena/jellyfish-logger":"^4.0.27","@balena/jellyfish-metrics":"^2.0.35","@balena/jellyfish-environment":"^6.0.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.1","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.0.13-joshbwlng-export-ui-schema-def-2489e5c1f43f4c698e6ad2e3ff486f77201ce9bd_1643157740370_0.130142784695924","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.0.13":{"name":"@balena/jellyfish-core","version":"14.0.13","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.0.13","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"00ae2ed5afa1deb9c8851defb0fc111e3af2d6c9","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.0.13.tgz","fileCount":220,"integrity":"sha512-Mt95Rl30BITq1/9TONEWX9OnOsaiJPEQR8WIKHYb0XQkH9YWHbhs8mEI0oaqrWZs29S0vit2Ju/8TGeO7EpbYA==","signatures":[{"sig":"MEUCICqw5YVU305YZ2HblMXtg9r2CExyczJdH62TO35XptwBAiEAqxr4hO0vyUABjHV8tlgUpw1mNK/kZDmtmJP2HeNOUy0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":831611,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh8KENCRA9TVsSAnZWagAAMlMP/jUtnXr7SUXq0rK0hzxT\nWFJMJZ/zY8yUgUM5jxYU83oKLc0/zfk06yOsBUGb44f2oC18v372R6P+dyZH\n/w6YYkfyUV6sQHtLMyREuu8Blm7IFt1LJ2gEJfVft7T7HVHcA5A84ceC3Epw\nPITrGwCgGvLJPquC6cjKXJy2Inj2vsXNU5ZAZHbI8D4ukwulZfiPYg0sTbQg\nCSbZ1hCn1WVEKDaoFyNj3jbONbeUCm2sJGuMLPOKOmQc4dc7Oradx1lNnjoo\nxkh14DnY48EVMt1pr1a3pbbnoNYqZzJoQBwtP3rkCKmwbB/riqNM9oHjvooR\nZDfQqlvT+OLbM4AVhS6TTPUm8EtsBkBzgrOGXqVYc+zDoO+EUMBdO+yttGdz\nuoA1+aqFTfqzUebnzAAGJU5MwcchJJqbsVmXHmxX4fymGckjCiqK/gmw3aB+\ntYRYXNygMF2Rgz+8O2DNk/YF/CH2yappVNX+gAFLC0Vqe7YHLicM1eiv2dUi\nr/j8nKD/gUVMZWSP2iEbYadczii2NIOsJrpyNl66RtjpIGIak21mX9rlDE/y\nV/x817fZ7K2o1B9mlaOCBkcD1VuYltPF1GpP+XJp6sIQ+C5vDnGmuGJMrxtn\nOHWBi7ovqMmZlK1bIfbMM23FgYYLKE2ofbNU5i9BNz+fkbNQvQsfmmMRuYrv\nKk9V\r\n=KasM\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"abcd649e83cc0d9a1cefcea36fe39a1bf8423d1a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-26T01:14:54.464Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.11","@balena/jellyfish-logger":"^4.0.27","@balena/jellyfish-metrics":"^2.0.35","@balena/jellyfish-environment":"^6.0.13"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.1","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.0.13_1643159820997_0.5885394794712242","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.1.0-joshbwlng-add-mixins-35cff6c7c843c7b3b847a77db524fa829371a1b4":{"name":"@balena/jellyfish-core","version":"14.1.0-joshbwlng-add-mixins-35cff6c7c843c7b3b847a77db524fa829371a1b4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.1.0-joshbwlng-add-mixins-35cff6c7c843c7b3b847a77db524fa829371a1b4","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"18e19c55f696631c3e236526dfd2c3776b83d6b7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.1.0-joshbwlng-add-mixins-35cff6c7c843c7b3b847a77db524fa829371a1b4.tgz","fileCount":226,"integrity":"sha512-K/YpBbf8i834UAZ43O1oSClv76Us1rrVLd70CKjK/EROQud2RvWQz24VoX62XpWg9RJZEHqRmXb+BpHft2kmVQ==","signatures":[{"sig":"MEUCIQCuADAG3bk9VPsitZ9tbCbnuTsLrBh6C5dtJsbGBtWVhwIgcu11pAAyRC+k6+JX+WryQZcGKYz+dercL1BF+Qy7p38=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":839025,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh8KhCCRA9TVsSAnZWagAAfVoP/0iIzaYxISSlzPqO8Wfi\nesLR/gRcfopw95EgKSYBCZU8JBWGZyStPjAdiROwsAfCYnxZvWX8fyWlDDsj\netk9PZLZBHgYyaqyP4N9UYdM3rgRWnCdNq8lIu9oN+Hv1TtFwD0yT+L3imnM\nczvgjSTWrh4Ni3098NV7eM3rvTAT59BArNELR/ke2l4Njp2B6LTeiG1Hxrtg\nyr6ktFDwCbwVqdhLGtViMz3qFVOhW+jCnmLwZsW+bnr3/r7NgU05Jcl/2UyX\nW28mEi1JK3SKvcmJV5aCb/4Rq+0dYSkRoKsabz7RJ06AqrNZiPaE93mLLvI3\nyWRqmiXmsvMF20SO4rg9/DEcCoNgBHTwhkzoIGcb9s+AZne8sx/upWMtE+KV\ng7Q+RtIBSLPa8w3jLfV1ngAz0xlju+g4OvV5Br5eLUgMWbJF7owmzQ5+yszi\nI4RiPharP+s0x7qus0ZqpJmV6XYp+SVg71i0vr3CJxCo9WfOLt6Rj8Cvj5vR\niy8nuaHoqRbLwnwqZwI2wKi+HKNFg/SjGAqGlPgaVRtlXmnGShsYEmi14zlW\nXNX2s3rGXAuy55h4r9Ir2pSf6JpZd3xXfEZphZ6MwARsi4uk9cl7FjEyhagC\nICbKgtONz46ZuPaBpwYZY7mvx/6h2bBk3FHfEHGgcIcrKcbHIYphNGxp0Ye0\nNnn1\r\n=J7CE\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"35cff6c7c843c7b3b847a77db524fa829371a1b4","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-26T01:44:12.704Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.11","@balena/jellyfish-logger":"^4.0.27","@balena/jellyfish-metrics":"^2.0.35","@balena/jellyfish-environment":"^6.0.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.1","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.1.0-joshbwlng-add-mixins-35cff6c7c843c7b3b847a77db524fa829371a1b4_1643161666085_0.03659293358043625","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.1.0-joshbwlng-add-mixins-239f2dca7c683e701973c0f89c03cbbaabd93981":{"name":"@balena/jellyfish-core","version":"14.1.0-joshbwlng-add-mixins-239f2dca7c683e701973c0f89c03cbbaabd93981","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.1.0-joshbwlng-add-mixins-239f2dca7c683e701973c0f89c03cbbaabd93981","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8c3485e407586cbc2110bb76ab3d15d1a09d77be","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.1.0-joshbwlng-add-mixins-239f2dca7c683e701973c0f89c03cbbaabd93981.tgz","fileCount":226,"integrity":"sha512-nbUexbddbgrLqx2gpVejAOtIR7J0wMJSmubk+gQlF+eJLQjwTwwGTTNwCIT+ZPZwqbZJnOog5OlNlatIMBJV8Q==","signatures":[{"sig":"MEYCIQDsWHlbdMUDBM53ZCKf72r4z9ordhC6b61MlikifCSEHwIhAN07ONQHdKNM2ujKhZVwKwT5k4yXAOwRCBGHXkZy0YvM","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":839413,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh8LMeCRA9TVsSAnZWagAAWskP/00uuxUbHQpVjcDslp0q\nJKr3CpxbLGqAc2grucFqI+6M46zOjYZM/HXloexkaJBWTIO5na+uZkWcKYR4\npOfgEpwmpLxsmePISnb2kzCz0mFT07uWo2p0rR0Smqs4vMIpi1cvAhwHcP46\nB6N8AQCDtR50euTGL51OEWpxbUif6o4uwYDJBm/R0cIZX8vHXO+ITJuEa35d\nAgqDMcRwTXsQqfd95RD9JUtIgI27LDRberjAt7XMVplEFDrd+fnw/Wzn5lCw\npeN5LA6x6dGZIhhrCzELL0DjWGvuDBVSs6rcaOEWcRQHpTCimkkJETS7qnJy\nf/HxcoXvq505bV5OxNMuvVarXt6IsIDbd9PGrXQmDLqwMusg4adyE11rZtxD\nPcYyuzkj9gLV6nb0FnSbWQMbFPkrqPSxdX3iv58rdSIS6Fqm0Ko8+qY15ot/\nJEKDuUvxV4N6ZJ3PrECfKUbgDfJdOtunzW/9JKENNJeMHlBpDXcWAuT/lRkN\nb2KjoJlhP5JvirdN6CHCaqXemY6gSLckHZl7Z1lKoV4yvrcKCVWrQW81ujFC\nQUDCZM8lRgXq75aqoSzJi4aM1RejGjDiDJ/QHxkJv0yLbcm/BiHSkZcZQiOc\nnsJvYTsz2EUVoBMxNM0E5gKPKTcDZzMu9j7Buk6x2xUWWwG+/pE2thH3hHHp\nIRm/\r\n=VsuF\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"239f2dca7c683e701973c0f89c03cbbaabd93981","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-26T02:31:05.068Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.11","@balena/jellyfish-logger":"^4.0.27","@balena/jellyfish-metrics":"^2.0.35","@balena/jellyfish-environment":"^6.0.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.1","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.1.0-joshbwlng-add-mixins-239f2dca7c683e701973c0f89c03cbbaabd93981_1643164446459_0.9856577831466506","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.1.0-joshbwlng-add-mixins-fad6415f3b66290434438d99a5a448f821a64cac":{"name":"@balena/jellyfish-core","version":"14.1.0-joshbwlng-add-mixins-fad6415f3b66290434438d99a5a448f821a64cac","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.1.0-joshbwlng-add-mixins-fad6415f3b66290434438d99a5a448f821a64cac","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a6b634b77de3231cb6e644980a2a125d96a41dd9","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.1.0-joshbwlng-add-mixins-fad6415f3b66290434438d99a5a448f821a64cac.tgz","fileCount":226,"integrity":"sha512-kh4gMaUFsixHNYNAEGLKbEjU5HpkztPwM5duldC8WGhIPCmZpdbSebSeyh30ruOrWX4fPe8HKyJ6mSgSXUK4Fw==","signatures":[{"sig":"MEUCIQDG/c891KNcKm1o/Rl2k70fKcvPFtjHAgUlUOijNNy7twIgH4BYD9jQ9p04tFiAl0gqH0P5eNyUrXJOCy+lSdBIF1I=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":839423,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh8MFSCRA9TVsSAnZWagAASNgP/RntPmW49c6yBDVYBiv3\nEuEGi7WxAtDDS00H2OU223EY7s1U7zugTGYUsH8uj1Tk9wFS61oTn+sPB9d4\nYJZABntTcmqFTo/K++lxYr/hErP1oDHH9x5cyu6PkJbYuBTcJJpFS/5jbuIy\nKh7xFwf6Xm19m900Pa3uEnB/W1I9i5PG7bvFiVQ+yndNOxa9NbZshc/DkAky\nBvDiE1BHnSXwnv0wegnvL7f55UJAjGT3yRWyBfCMMighQw+r3kAwu+aag57n\nL8ivgfLG8EGRsWh5s3oVb2ArVAeYwBPfrm/ir9RlL6h70KkUzB6f6FpeKHvz\nJW4F5kxQMLHbKNQ5EE3RBPpM/Ao22OrMMCKiRGsati1mo9UG6rITEcs6uw62\n/i1eUxAP7mH2qE/JqUZDIJiPzn8gZ3pTu78bk5CCG47saFdBz7A892Qm9xgH\nUgpC69yahO0WXLLhFD/dTMuqaqHqOz5p8VAe90L7eU1M0Kjcgg8MvNikxiMJ\nPwdjTvhEwVpjuEFh7bZ3pPz9hLuvlzCKtN2iYEDsefGp2p0bQBwrrDff3Sj2\nYDnCYLlqO0KBylJ/Qg9NSh2iyFtt1kZZnkJUHwK7n2u5gStUjUs29hW2fw3K\nQmnmTJXMw/ktW8ZYOMXnBuhLch4FnCZvlwKdw74jN6deOlIB5ndKRdyyk644\ntz2G\r\n=sD78\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"fad6415f3b66290434438d99a5a448f821a64cac","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-26T03:30:25.893Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.11","@balena/jellyfish-logger":"^4.0.27","@balena/jellyfish-metrics":"^2.0.35","@balena/jellyfish-environment":"^6.0.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.1","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.1.0-joshbwlng-add-mixins-fad6415f3b66290434438d99a5a448f821a64cac_1643168082106_0.6582990339740393","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.1.0-joshbwlng-add-mixins-fbe1b95ac4f01b8fdae7a5db2af29b08d2dab6d9":{"name":"@balena/jellyfish-core","version":"14.1.0-joshbwlng-add-mixins-fbe1b95ac4f01b8fdae7a5db2af29b08d2dab6d9","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.1.0-joshbwlng-add-mixins-fbe1b95ac4f01b8fdae7a5db2af29b08d2dab6d9","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"202963f67d919abe5e8d196d8ffeee529163f483","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.1.0-joshbwlng-add-mixins-fbe1b95ac4f01b8fdae7a5db2af29b08d2dab6d9.tgz","fileCount":226,"integrity":"sha512-h19ogxEMncQYhlwBII9pzYHO6y3ILy3JwJlL7iYcCTYNfTzD3y8VyNb3s3uhMyi8H7J50xaBfc7yIC759X2WxA==","signatures":[{"sig":"MEUCIQCpJWu1rKyl1sKVTT9+EBh0U3z+tfpip8S1Wqpz1JlNWQIgb0OFk9nc8c8yymtcMBrooqe904x6b/h0LVXDkvd1wFs=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":839430,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh8MK7CRA9TVsSAnZWagAAzm8QAIOkvNASJciA+hf5LOGB\nQT2G1dlSBlq3JxmKEZmAeoggClzwOhy62jz8V7RNWtmL42M+0x7sQLSePQSF\nKBrUEZCAc5j4rT2imYMfrbzGe3wRRbvHiKX4dlUgd3TlDcTIAKAUWM7jBvZq\nglv3JlSqAoN1FlqrBmK16N92qU7Jlcqng/owuxEaYTvSqgyfpXLwTEi1WZjO\n2e3KhfI7NS5LpKVWjERVriNGqT4NCu4wukmg5VyAd77lHaX+zFzrmoVTeQSE\nuKv7OINfTJZix6ibhi7/9S0Kt+2EguT8nCb+pIsZcKFwFLb4NE3Dw2eqnxUs\nSDKu8E3NCNQ+9LsGot88MqGW/qyPE13xecq5mMbBhiTXe42EICi+IQFPihLt\nMBFh6hdUmq7xj4bSy1QWLgiqIvY2nod898bz5Z+bn5qZt02cE1qW+SFwit9e\nZR7LT8v/eL1L0RK1M8mhD35V7VePSOgsfhkUFdzg4NpausndY7PJ1SRdHIsA\nzX2qZsSMKLtSGAfy4/deiGzgeSXxJ4DaISSdMdZX1oBoPawlEoWj0giA3pBR\n9gDg6mrtt8+pCXziis2jPDah9E6oRMqmbkAQAYU8xLZXJq6m5gHxElo/ceJk\nHGiCBrfMr3ou6KWKKBc/qqbAcAmP71Ctx44UkBGz7pCLCGD3cK70oZOkck7T\n9YRQ\r\n=yqVd\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"fbe1b95ac4f01b8fdae7a5db2af29b08d2dab6d9","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-26T03:36:56.496Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.11","@balena/jellyfish-logger":"^4.0.27","@balena/jellyfish-metrics":"^2.0.35","@balena/jellyfish-environment":"^6.0.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.1","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.1.0-joshbwlng-add-mixins-fbe1b95ac4f01b8fdae7a5db2af29b08d2dab6d9_1643168443543_0.9349868464696112","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.1.0":{"name":"@balena/jellyfish-core","version":"14.1.0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.1.0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"84fe9f9b4438124d65162b8df3260fd4b1365244","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.1.0.tgz","fileCount":226,"integrity":"sha512-fKMhertsqM91kJQ1JeheGP5r3SXf21PglPj95yhUKrMEw4KyHhJ/WFnCFjdE04jklUwyxmTQsr/GbfUOnmwrRg==","signatures":[{"sig":"MEQCIGdw2aDLBaCF6q+ECOnu9kQR3noP2ssKAoUzvvpqe45vAiBS+NT64l98EIc6HnsuzISRga0FvUvbLuWny9xOlmBmiQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":839368,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh8Mq7CRA9TVsSAnZWagAAaWIP/1EC2tpMU/tb5N1bhtV+\ntEjFiVrp5rfPjVGz6qh7lcGQ5NZrmpWOTpgpNxfBRBGaM9V2As9WN6VmnyZN\nbS19WVm5Okc5N6VKFalzT5BCIZKWtd3Tdjb764CaW39DGx5yM8leWPJZ1fuW\nrrUVRelvbNSzNJ5UUmcPXrYeXmQ1KL3u/J77Z9/WGTzehq2CikCb6tsSyjlq\nPQVNi52QeXeAUK5vjVLRQx9aEpxmvToZBa3EO42SH7+nPmgh4vfr+fTyxxfy\nndoUphXi7+ItF5PAMR5iclrt1nv0TlWGuKnsRRgL9B5EFB8T8mPbDY9F9pIO\nmpW7DRWwReYRZBPjeUytf37ES3mY6zota+AFhQksK1NNnR9Gdy425fAz1h8S\nsn7xu/Cmf65nIVqJ1Cb0KfOtX3vM/EWlBBjuZsFAcD/auKw7NRhLGzxaV0LU\n95WDyrm3kv7tk6W0/tBCB8FeG+gQrC4QV14sQnYhY9dpL9FDedyZgmIVV+ms\nxlgXZbBxld7u8y9M69VVXA7BceexkgbtoP4OSz7M4TfrFzicN89Thazpx/PP\nNDSOWvpR50r6z1ZgCw1KZ2NZPekbp6vWxtkAOTelmY0FnDIrb6DPYjN31mco\nb5pw4lgG7pfVz1gHKbF1KdcrNtACSVOa+7xHxVRuGXwUD967B0Z19IlF8HtW\naB40\r\n=gm9Q\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"9bb34755e9c110116b588143bf05a3c4b3fc37b4","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-26T04:12:43.750Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.11","@balena/jellyfish-logger":"^4.0.27","@balena/jellyfish-metrics":"^2.0.35","@balena/jellyfish-environment":"^6.0.13"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.1","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.1.0_1643170491621_0.030789125115722404","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.0.1-edufschmidt-move-types-into-core-c9ed3ab0f6a2c92c6646bbfe76e8217ccbf1af16":{"name":"@balena/jellyfish-core","version":"14.0.1-edufschmidt-move-types-into-core-c9ed3ab0f6a2c92c6646bbfe76e8217ccbf1af16","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.0.1-edufschmidt-move-types-into-core-c9ed3ab0f6a2c92c6646bbfe76e8217ccbf1af16","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"99f3a6dd8b5cb976b602f568a0c44cc3abbb5c0d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.0.1-edufschmidt-move-types-into-core-c9ed3ab0f6a2c92c6646bbfe76e8217ccbf1af16.tgz","fileCount":174,"integrity":"sha512-q3NAxvi75xP04nV4tFVexUjiBybepNciftqr/uQ90NQnMZe9HzqAMVVNL/SbnDZh2pWIF4XzMTDgbrc8D8zeoA==","signatures":[{"sig":"MEUCIQDiSc8hqLnWCuYZFIidknvkLKEeD7Qezo/op9NGRmZ2rwIgOVvrzAFIobPoqk0oNdc1QKTB4PeK1L1faf+qLE+I8fg=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":731151,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh8VeWCRA9TVsSAnZWagAA8zIP/1t5QesDMTYC93GHgdPl\nyBD18NHl48ziwxovkEd18y7LLHBE/27wihi5r3GnMa8g0gN/jm9aZZc/PCva\nO0qwbOfKDWpB4iG9fNfzGjdAMZxiA28zgZ5W5QMNoWdEH1Cku7gldWWKXcpZ\nlmXlYEL8SkTCjTtdt8ZHfhogfRU3XXvId1zP2h90m45U3hUMHJZFb6V7z6UJ\ny/y/DldX+KgxSsfksbOEOVVp6Aqv8O6BAj1P/jpBENorHQrdmsKOWTNc9yn/\nTw7HOtz1mzWU9zV8guCmJBNT49MuGFpx1GKlXXXhEivHe8eGXYQ4DZwcZibf\nBajEyV7X1Ramchu39Chi7X29PBGxAU57IqOWSYC4J3TCnT3BGOaRfl/q/IUQ\nGZ9xkntpzF0fO283BDD3sjResH9IZSBDvjCNYv3nQcVyB8T38dcBAA6aBAE3\n9I3XaSKi2i2Zxnn6d1GbU4wXpoBD0Fm/K31830fkH+5erH+XYg/zI6XUrnf9\nMkL9oDZQLGEZSRQ86V4sTB8cxCXIcX4zUn5Rn+CFRiZB6gUACWsOirJ5IE3h\nbtcwp9asGPJRphgSC1nfRnDZVXS+ObqTnUypAEOnpjLe3C5Oo+1zMvhJG6/D\nF7ndZZFPMOLyn7/kftf52PWD85jRejYuzHHzw7E4YaBpesWlOcGESI+Rqupo\nXQU1\r\n=WWaa\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c9ed3ab0f6a2c92c6646bbfe76e8217ccbf1af16","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && npm run types && tsc -p tsconfig.build.json && cp -r lib/schemas build/schemas","clean":"rimraf build","types":"node scripts/generate-contract-interfaces.js","compose":"docker-compose up","prepack":"npm run build","lint:fix":"npm run types && balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-26T14:12:39.696Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.8","@balena/jellyfish-logger":"^4.0.19","@balena/jellyfish-metrics":"^2.0.25","json-schema-to-typescript":"^10.1.5","@balena/jellyfish-environment":"^6.0.10"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.1.7","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/lodash":"^4.14.178","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.0.1-edufschmidt-move-types-into-core-c9ed3ab0f6a2c92c6646bbfe76e8217ccbf1af16_1643206550404_0.009040037443195814","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.1.1-renovate-balena-balena-redis-0-x-786067a4ef0da24d9da335db280ab1ac92391366":{"name":"@balena/jellyfish-core","version":"14.1.1-renovate-balena-balena-redis-0-x-786067a4ef0da24d9da335db280ab1ac92391366","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.1.1-renovate-balena-balena-redis-0-x-786067a4ef0da24d9da335db280ab1ac92391366","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1f7307358109ccc8d144b83f38091c3f229e61a1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.1.1-renovate-balena-balena-redis-0-x-786067a4ef0da24d9da335db280ab1ac92391366.tgz","fileCount":226,"integrity":"sha512-ednOM1jZcX8/W9dYw9T+VlqM2z5iNMIK/vEDD2hxYwKREWyZUkXvKezfwYuvS4DLR43HbnXmLnBpEMbh64b6sA==","signatures":[{"sig":"MEQCIC2nrSZ9E4SgWR7QhJqp5hALrB1AZTxNICNf0z/gJC3hAiBG/MDK+oTaVcQNX6tLLODVHl+V7xja6UkGzpAz9lKPCQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":839532,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh8z/DCRA9TVsSAnZWagAAtVIQAJ19rBNwvaOMh/1VPFHy\nhQMq6EPLVAI8UU+C6gOad66YaH968VF4oKUe4q4mEK954e34hlbIFwfG3l3r\niH7TQ4B1Zh7WadMZFEfxxgqK0EsT29O0dB3rl4Kxu7fqIvcWaBQx5VbOu0v3\nWKYISTZC6cYiOvnoU//XOIqqpANuraWVN15u2If4w/O4Tvy9f/uWmXkELD4L\nXqmem95q0p5e0YoJ9BzskClOJG34yZxdhuJ7vRyT+tm81ovlRMaHSGG3TJr7\n9pkfH2ZRZQoKV3qPFiUGlANTyJGl3iaBs/i9WgFy5GAnQNgMZ2yd00npad+E\nvpmhCfgwoB8boWvm+IBxgrBfK40NKkiIVFsy9fmqg2B4Uh0e3IHWgMdawz+Y\ntu4rYQhY8IyU3aEB39hnTpVY1HjfsgRJjOVvHpyjt5/02A05raDSPYCMMkrE\nDfmCcCN0l/4q5MansWvnJI/9zKT52nSz5lXfzmMjt2KMtAOdg+aa6PD9v0MV\nYA9GmHVxTLjnVXDndORibo8eRIChBLSJQR3RLJWBQjzPmNHhjymVOjyJx4mN\nkj63t8I14O4Dr+7oIabDYWrEtquxc16LpB+ztWzaEHbRZxmCTbbpmY3jiiGt\n05UqOuET+AogQI+kliXcCIkJWArMPms/jchYZg5/JCXZVfnVZmsIwjtwla+n\n7VTD\r\n=AUK8\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"786067a4ef0da24d9da335db280ab1ac92391366","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-28T00:55:43.624Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.11","@balena/jellyfish-logger":"^4.0.27","@balena/jellyfish-metrics":"^2.0.35","@balena/jellyfish-environment":"^6.0.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.1","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.1.1-renovate-balena-balena-redis-0-x-786067a4ef0da24d9da335db280ab1ac92391366_1643331523364_0.4375369699581635","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.1.1":{"name":"@balena/jellyfish-core","version":"14.1.1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.1.1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"81ccce02cde34004377f28149c60978bebd39473","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.1.1.tgz","fileCount":226,"integrity":"sha512-iZnqfdfOUHu4y6t5thszCioAPETecLpEnCPw8ox9NIuJ76ekk5ulRT93l4v10abjZwnDqdnpnwlCEuAf2HkWSQ==","signatures":[{"sig":"MEYCIQDAXlTDwf9KFqUn1ENVQIcIUVtW+T9Zr4WuaAQZYrHeFAIhAJSpjhqgJkcpMNSoeWwgMFvWjxuQV8z6+21hxP1lrw7N","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":839458,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh81bxCRA9TVsSAnZWagAAT3oP/RaMD0vBebaQjO4F8vdR\nebaQZC+3tS3Id7eHc4Z/xwbqd8W+DAsBD8mLTIuFXBpkHm0htz+SyDki/i9q\n46Ikg0QMIWPEGjrw8fNTj4hQqg7eFjtShUi67ay+5V6DDvwVvEeBdVw8+6du\nGb0nhzFL56JXRIK0eNZXI7KmEp80trtvns/xowau0b6hWMny/IN9zTnNjmqq\nyGPUoVVU3tEhEDjBFb7/dWAXExBoQAwrZn1Plmztt3+LL59lEh5yGk0sfTVe\nkliW+YZQFjDN8jIXXBasCrKdhcxNZjRX7y+WUFCB7eRrh5CuZwJeKIZ/SYLx\ncuF4Zhr5h3OAzLWTsRSLShe9Dnu5pwpDNI/ZzGYNMQwRam0HdaEmn9J7IDGx\nkt/DjZYBcWXJOtVqlbUxzLb+H8c9c/aGFAKakNnLLkVUHvtKcBxJOPnJMCsu\nbfpIjvHf15ql9Tc8umhT1z0C9h5jCIpI76+y9UGnjHHvMY/o+S4nTpAZpgyC\nFCuO5T2sIeeTtQDdng5wFPuacuIXCbeGHXTgriYa54J9majTi4ai8UV1RhJG\nitrzjTyyrV6sVLWRJCdnrcKGjBD3uM8x7QKo+/sZZq5BGIqlyKjo19LpL8LX\nZ0275rE2pVhwuFRskUqPLNQIB7cuoQkYB0YD+XPCfi/vXagUdju/2x9jz11U\nXvsx\r\n=cdMx\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"5b85fe65d65495d312c6369a427805ba5be2157d","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-28T02:35:25.270Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.11","@balena/jellyfish-logger":"^4.0.27","@balena/jellyfish-metrics":"^2.0.35","@balena/jellyfish-environment":"^6.0.13"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.1","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.1.1_1643337457012_0.09278952192840473","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.1.2-renovate-major-json-schema-org-b99967c426309562551c93afb83f313ae5bc5a20":{"name":"@balena/jellyfish-core","version":"14.1.2-renovate-major-json-schema-org-b99967c426309562551c93afb83f313ae5bc5a20","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.1.2-renovate-major-json-schema-org-b99967c426309562551c93afb83f313ae5bc5a20","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"dd3d5b5e27403100656830929e4b32319b45d26f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.1.2-renovate-major-json-schema-org-b99967c426309562551c93afb83f313ae5bc5a20.tgz","fileCount":226,"integrity":"sha512-1kAf4K96gYnCP2d8rWa+uVlOD63NXIJt8s1MIGOR5sGQVgsKq2ZvtV/gODDEaECldJ80wgv9xUNW8llpDHDZtg==","signatures":[{"sig":"MEUCIF6m6WtX3jrxJdR9Emo7fCuDFr/EgPPLVo192dOk582GAiEAntzXHK2rbWot6e2q305lgIEt1cFVBN5oJpD0/VqBNmM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":839615,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh81nQCRA9TVsSAnZWagAAdY4P/jKRxkCUkx10cJUoYjc/\ntzQnvaR9XhIUqL3i7Ex1k3szSduYVs2bs4Wqyw4c6ZS0arSXghhbgBi+J4CS\nSvi29eUCdedc+z9b+n2gPYHFBOTPwnDPhRdaevgI0G/Y4TrnfhBDk8bPYwbl\nChZM0OnKkUN2nVrNS1BSgGKnHcKwVKSwQSipgjwwrGAXAtG5TjQqV052ZSe2\nHS2UbI/vahJBWSUelcfjuxLnve/pS+Rc73BTUkYYtUqanTIgLqOHvqX5gyxE\njJhxilELF2wMsO6h4i9uy9Bbfu8Nc6pCBP1P96XqDPbmt6IrO/lb6iq6fsk3\ncLwZRYYb3YnPEgfNJYZjYVdG6cxQ70X1WLONXT3SmbmJZgIvFCiFot/LcNqK\nQltc4qqUxDbH/5UP+TAgXincIQbELPZmFS2+QguQsvwtdpiOH3RWX9HIA7PY\nOB+3WfbX1XQq8WXmCaXP4JyVTnZ8dcxy/2km6PILoCEEIuAGUyLi9g9jYo94\nOCftB5hOJGgHv0AAniEQBOrhL0sr/jynuOZGH1xg9WcWKbKidEV52Zbn287b\nuub53G2WpDES2EDQ4krgch0XLpqT4/qB/E+P52u8xw5wKJ8csaIhzKTfahZy\nWdL4IxD0W3JXkGnkT3pnNAvpeLoysiaugYBQmnvrstFE4WUKlvMtkFBSTW6h\ngyPD\r\n=Eq88\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b99967c426309562551c93afb83f313ae5bc5a20","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-28T02:42:47.511Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.11","@balena/jellyfish-logger":"^4.0.27","@balena/jellyfish-metrics":"^2.0.35","@balena/jellyfish-environment":"^6.0.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.1","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.1.2-renovate-major-json-schema-org-b99967c426309562551c93afb83f313ae5bc5a20_1643338192710_0.2304305250925498","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.1.2-renovate-external-non-major-3624e0e73236881673786cc9362938221d8fe3fa":{"name":"@balena/jellyfish-core","version":"14.1.2-renovate-external-non-major-3624e0e73236881673786cc9362938221d8fe3fa","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.1.2-renovate-external-non-major-3624e0e73236881673786cc9362938221d8fe3fa","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"fb53918a18b48e05607187ad58e559c4d968a3e0","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.1.2-renovate-external-non-major-3624e0e73236881673786cc9362938221d8fe3fa.tgz","fileCount":226,"integrity":"sha512-FZfQHzVeAU39EFlc7xWZgO15brdACLuE7dRQagN5FPt1ZIX/32ZyhwJLNJvfNMIhkDDL0vz7KkorqbEvEKcbEQ==","signatures":[{"sig":"MEYCIQCzfDmNbpMEEf2wIEhsNe8fwGCsigG7xSLSNSefCsS74wIhALvLLpcyhR7PCw/F0H381GjPmzTh1N71dplP/BIyRRhs","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":839616,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh9JGiCRA9TVsSAnZWagAAtWYP/2/U5vz9xb1K6L1CzZfl\nVScoJ7JwIdgsiMPuH036N9UbWLkkqMJbGNAytrv0QEf2NDrO3oozzF+rXpMr\ncOkCTL2Z90Uyx+15KLonXjm3epbtDJKDGGHHeuQpY+1cyodVn1Rb1or/JVaj\nuCLiTtPcLspuGbVNbx3f3APdaDCGesI2uLaJVRYP55PUAydghUWgNh8bED42\nggrz2WkXG4BemfyUEdctRr36SUIHXtuZj04wzOnSIjmRs36VarNvSdt6glZr\n7gLWMAnkPBZiZ5jVal/AsGHfi/gcAt0Vy7U8GxIvQGD1Be+cH6qCRZKqR08Y\nR62iCpbQIAjwVdEEu9V9TxT4TW2q2wiF1ei1zwCsbxFUkPnenGZj7NV2pNOR\nfFfOwjvLnzTmFS20AGcImxIwobAKrcyWdLjbypvVFZR89zSm4H4epPIudTum\nuldPBwAWeT10aGPEJQiCRdjaUCwZSeS+3BbSW71lU3zkHihL6c3atsCUBy2U\nnQMWRqqX+S3AHC7Z5Hiv+NhBsg/4FXTGqpGLRx2j/EQGXcJrNkHVDYayfU1G\nANxP0GecdzM/4nL4VL2XFdL7Bb1PsyNhJ1Y3fUbgGEZQ2lm4yiL/OkfQKs9P\nab66XS9KR5ZG+72xBrLNPUtiOG1uni+yk7K7bS8cbfR5alR0mB3Zw7ItnPNf\nKvCm\r\n=gE93\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"3624e0e73236881673786cc9362938221d8fe3fa","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-29T00:56:24.552Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.11","@balena/jellyfish-logger":"^4.0.27","@balena/jellyfish-metrics":"^2.0.35","@balena/jellyfish-environment":"^6.0.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.1.2-renovate-external-non-major-3624e0e73236881673786cc9362938221d8fe3fa_1643418018150_0.1659435076535123","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.1.2":{"name":"@balena/jellyfish-core","version":"14.1.2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.1.2","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"64c9ae40124aef7acb73027a60cf343a604c5965","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.1.2.tgz","fileCount":226,"integrity":"sha512-lilYikxesoOUDphCkDUlEqoat3n9ff754TdEVSrv90eBt8KPLAW8OmsNmbnu1z9FU34SjEsigVWPlHQZ0wISAQ==","signatures":[{"sig":"MEQCIETZ+WLkT4SSEu3/L0s4XFqApcBzzlzWLY4cOUsuOZEjAiAyJNZ2ecjvnBFzBO1AtcTLEpAoFiAOiv4MIv5BLpqk0g==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":839547,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh9JNLCRA9TVsSAnZWagAAadoP/2fTqdZHtJrCef+L9+hw\nCOjCMRc+W8w30hiJx2VtalEtIqUy+KzZBplXIs+OEjz5D85db1e4kfe1Hv9Z\niz3M3Vkb7GSBUcukyVgHT1pLFtiEun0rUJ8gOh5GCv2X1n3MT1kc0xOVNDjm\nRCjYLKRlJ7jnjlQcTxCIN7q+mlZwUl13PLntZCfHh68QS9C4T+frsR46y882\n1JPTOGxJYoJwFCddglRwC7vkkUPhjVG5c/aupXL583yTlOIqinMB7khqblFp\nO3DCcdP8aoAsZG+DA4/1z5vt1FKaGP3V+dznOOsrDTwHl+2hZpJvrn8QpvPE\ndA1wBLPgPchPDRk5sfBSOGxfoZ8itcihj+poCrWKAS3UUcT7FwJA9n4GLwUk\nZyPzJIvPtFGycZ6m89QE1+JNehJWyvV6O+G8lLhfk+FfxMfRtRqnriuyu/Pe\ne4vKT86J11XAlxjMBpA9ujHFTCASM3MkHoCwIxn+7vvt2TnUdk/tlM3aD0K/\nwcDUflwvo3PLJB+BFkeublJowU72EHp/2jZD+pu4gkZk0v5Xmkz/oJuK3JXK\nY9XOrIl2u08cfIctj5aaEZ8U7B+6rsAtTZMfTXLVI76kcnfs2G2yVmMr5tiU\nz7+5nyrFjiaVcrOyCnl6hqf75HkT1wSkE/f3OwyiP+CwgmdqBr1y/ZK0XZCs\nQtMy\r\n=me3U\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a55ccc847dc3b493605c00cb0fb38b6ff07e6286","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-29T01:05:01.370Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.11","@balena/jellyfish-logger":"^4.0.27","@balena/jellyfish-metrics":"^2.0.35","@balena/jellyfish-environment":"^6.0.13"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.1.2_1643418443734_0.771280078570846","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.1.3-renovate-internal-patch-45f3940c2b6f906c92063ba240a10a5bc9823b84":{"name":"@balena/jellyfish-core","version":"14.1.3-renovate-internal-patch-45f3940c2b6f906c92063ba240a10a5bc9823b84","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.1.3-renovate-internal-patch-45f3940c2b6f906c92063ba240a10a5bc9823b84","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8736747b52114ef0c1c1ac0dc6c0e16d63b1dd2b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.1.3-renovate-internal-patch-45f3940c2b6f906c92063ba240a10a5bc9823b84.tgz","fileCount":226,"integrity":"sha512-0GduCRx29HZNu4F2aijij6YQCubIeS4JsGNeA+0AX8iBq5+Ob+zlZVM7ED8Ads2Ez0GMlAGtp3ppnZKTWoNXbg==","signatures":[{"sig":"MEUCIDt6mNXh0rrPBLqhSrpcl1rkaWnU1U3j4s3ZPi7IXpzfAiEAoB5fD/Htae8N9jSA0VN/+XorPnR5L9cZEsn7A8mJvvo=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":839697,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh9JQpCRA9TVsSAnZWagAAvewP/1p9MLVL4xh1BJ1dHfY6\nF3VEA5afdl887V8G0EhqWE8yX3tB5n6iS0LlE0W+yQmdOECxvmS7Ft9BsM5l\nGlJypNsXC1dozYdzlxwsalBt5b1wegxWZ4PG/A/YHrB1Nh5LldaxBs/j8k6z\ny0lV67z/dWGkj9RvSlpksPYnNHoCx0xBa1B40nDmqJfLwnyefCvhOZUK3EiI\nWUY8TGzLC1l6jdsM1qHWWN6ynBLd23QUgrgdMH9maoZlnUEqSAaLbA+5mRdp\nWAG/QO6for6CxA1On6ZhHogPepv2YhsjRd6mQZ6hf8HlOYhY2oahbbWM8fGv\nGU/QvvnSMwkviqNgyYqiCGG8mR7vfaSI7t0732u1mjwdM591Fr/rQVVyauCP\nfWaOquXwX/YKRR1SaqNLQFIWPiSs7SAv0SvCvKGKriLh8LCAGe0G8Hf6jFfP\nUHiOz6L3YKsoquc3ZcWnh0ZNc0xZ4ucS8MJY1o8MjxmtscsyxxnIJDyEvv8Y\nORPIDWjG1xbzseXhYmXrSSPVlD+xH7GoIBiWGp7XyFiZLc1Qoc76LmdbL5Ky\n3hxiVYUHgNYPejR0QM1ENA++Bhai1BuW7sZU+/cqwoabuyi0ztIdEDaVIKl4\n5aOqhoi0H43M1cvNZ4mCjWbe4BiFg9T9lgGyYJjIZpMpce/0zHIS/3VM/cq6\n9wcU\r\n=3wDX\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"45f3940c2b6f906c92063ba240a10a5bc9823b84","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-29T01:08:01.301Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.12","@balena/jellyfish-logger":"^4.0.27","@balena/jellyfish-metrics":"^2.0.35","@balena/jellyfish-environment":"^6.0.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.1.3-renovate-internal-patch-45f3940c2b6f906c92063ba240a10a5bc9823b84_1643418665258_0.9793934486532307","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.1.3-renovate-major-json-schema-org-eec9a004f68dcdec248cbd0f4a8db8efea68d785":{"name":"@balena/jellyfish-core","version":"14.1.3-renovate-major-json-schema-org-eec9a004f68dcdec248cbd0f4a8db8efea68d785","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.1.3-renovate-major-json-schema-org-eec9a004f68dcdec248cbd0f4a8db8efea68d785","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"cd411c95f6daa12b06dd3cf33280ebbde1873e16","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.1.3-renovate-major-json-schema-org-eec9a004f68dcdec248cbd0f4a8db8efea68d785.tgz","fileCount":226,"integrity":"sha512-XFMlo0MZvy/aV84pxetTi1mCka6r7WfRHH9Te3s67F4xP2OWGCmWs/ew3Nq32LdNaMEUmYWdyT9Q745p1ZVj/A==","signatures":[{"sig":"MEQCIEG4K7BhCSlD3vSTRA9f6d6g9QGtr6hUTiew+mKrDzU2AiBN+sf1Wd15n4ATVBoJFTXqJewbfLT+2hN4Z0bcHnlp4w==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":839704,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh9JRnCRA9TVsSAnZWagAAx30QAJCWNMJJFSc/n5EWbylW\nVCKN8El+WvEjWmnqhcBYnUWXhdLEvnsewtsjY9UfFQtvqK2H17R2RhvWWwNh\nu/l9qzxHHmsoNcKL/U7FKGMVHHACNJtsC7vdUuCZ0QesFDYACnUnwn3CeyKb\nQzM8LXiGZB7VC4nv960auOgaZRczYTYuoYXfSIWFJmx0Zxk3hUSfo2ohhzI5\n9lG5nQ1YLA1TQArJEeQ2sNfT8Mr06kqQWnEwidCrJIHVPVufbc/c3jScjmx1\n8PKM8+7VGwyC0jDGtCPbVCEe+R9idCNw8FgC0vS/rZk0XIul9pGXF/5XX4+V\nXGgnE7qwWXg5vY/uPMEiHc13RjBBXOMtd/okP4uQx2Tll5qBqpFsiiLdcuey\npAuEJdtCFC81YgaAdLcwdnUB9xx5GeuprI8P6YIJOUnz6u6R8UfJ3KqJJXn6\neFqzAITKdCvmKzEduKkOfNdflBfmGNGYmE/VjhH5a0m3fxe4ZhwE77tFaYuK\nVorqwD0DNM6i/yMFSUJ3AWFEEamuElfI4DhXQpGRzVok8T0Ph3NClKcdMWqR\n3JNyBk+mv4kmAqsIk1l6Mt1goTAQB41vGahwojYuSRDz8mnyyRaownJDidX3\nEVtZeV/OpZmxETEEUT9coiRm+i+31GiBpOEixXi797hiNJZVVutRngrIhLVZ\nkMZ6\r\n=kXSt\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"eec9a004f68dcdec248cbd0f4a8db8efea68d785","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-29T01:07:17.920Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.11","@balena/jellyfish-logger":"^4.0.27","@balena/jellyfish-metrics":"^2.0.35","@balena/jellyfish-environment":"^6.0.13"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.1.3-renovate-major-json-schema-org-eec9a004f68dcdec248cbd0f4a8db8efea68d785_1643418727748_0.9269019377833922","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.1.3":{"name":"@balena/jellyfish-core","version":"14.1.3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.1.3","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"43ea9db9188093d9951f72b4684f8ef662880232","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.1.3.tgz","fileCount":226,"integrity":"sha512-btHUuoCFB3UrBYis7FFSM2LdIrnRemrEG1HRsiOE4hyfWglD7PPu8MIY+w5AcLCPcPYMq5jsQrLDI4WghqDZ3w==","signatures":[{"sig":"MEQCIH9BzH7V+wcmJVEnL0RP1Z3l8ge79AsXODv+jZsZEhhJAiAQDVSFX3qO8Hz6ycjVTtSUBncbJUeJBdBl6+fAGJunOw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":839632,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh9JaxCRA9TVsSAnZWagAA3gEP/igr/q9fauZh1Q2Tt/ab\nNTnD++tvWvBWxAiFJM+3pnonDw+W5maPx9hwaOLSTXwqo2s0CVeH1D3kcv2z\ndTbAnPIr01tLI7i0uaycrH7clvtrq2e/Hu34cI26ZsZbwrN+kUWBPvn/Pj7t\nVWSxj/nfeIOLH0njOtDISkAWE7yJmKt+cAacmXiMRs8TxpTkcnPzPkpGWt3O\nByelBfU1uVoaSyx+0BmIKUWk/omtS/JUKVzfrUeo0Fz/Z00xLoA/0kW6UBlv\nq1tJNYtbDhEAZEMZgTuc3V11TZkr194QnqSyqy+qS4EVrswgmR8u4WbnoLT5\ncjSDZ7/UL7pGiJdUC02e62WFrldjENxoGi8b4Y/vkFrWGkLBqLJWKBDGVuRi\nTaQeorwvahoo8m4Vb//ASp0tprJEiNy91fwizRC3VxqXPm91GGu6m2gYMFLB\nTjSXDcidVvE2Q2tR2nWJqgc0rlGizPYrT2x7dRM5ZkJXwYAwdBYEwHpP5ISe\n+fyL2isjhgdfsGr3GUq8IB/YbGBTQO47R8VVmgfLIIdzc3tyBc4sSxgBE9K1\n+hDnboXBAnnbUgyAeIaVlL89SIT664C91/t2gRxVwThUrogVsjF8HuAiz7S9\nw+SKR8mnqf54p9HGXN4uguqUMS14zgiyYSeoayhRlr0RFrtjrFYQV1Q42lx7\ncQJr\r\n=6Gls\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f4ff188296003d48f0ca1406aafb0d461f05611b","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-29T01:19:51.852Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.12","@balena/jellyfish-logger":"^4.0.27","@balena/jellyfish-metrics":"^2.0.35","@balena/jellyfish-environment":"^6.0.13"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.1.3_1643419313753_0.4934522123121283","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.1.4-renovate-internal-patch-97f566aec734b298fb52475ef48c191823c3168f":{"name":"@balena/jellyfish-core","version":"14.1.4-renovate-internal-patch-97f566aec734b298fb52475ef48c191823c3168f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.1.4-renovate-internal-patch-97f566aec734b298fb52475ef48c191823c3168f","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3b032bad216691497d8c0f6716f33fb6c4dcf36d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.1.4-renovate-internal-patch-97f566aec734b298fb52475ef48c191823c3168f.tgz","fileCount":226,"integrity":"sha512-gk9mAtgPI9rFbXMpKbo1KvsP4W1jVYKnial5qeC7cThdZ/5HjxBrp1LcMZiGR7n5lutP8iIE5RrOU14KUKWpIQ==","signatures":[{"sig":"MEUCIQD5gz4VbMIok3t1QFVMM/i5sQWUZFV3VXGI2RrbV0VgcAIgSMJkV1iQFGkGSztPCC7717Srx9ZzIhWj9fFp3ePuw0Q=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":839761,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh9LvaCRA9TVsSAnZWagAA37gP/1gl0pmQdarUWz9rlBVj\nJP6oCFlVXpy5YdqJvePTUxLDr1Z09Kx7M8HiUELe9e9rK6wqXL8WSNZLF33L\nOzIaZzl3f8LJdLDe4c+a8zFK0n6ApsHWBDXET6PjPAkdKs92a7DwNb4oFLCA\nTst65NGzDcJpE6cMchmnKiX/Rx9r+l2f+jt9eZmRGuNZs1vahonR7dHI41ou\ney133WbfeOlabzGMzhqBEhx4NXtTs3vLHeAhc7WpOxmaxi9rwaMxf5mQ10LP\nxzTOrULK23r0naLpV/0mKuH5u4tiy86cYFndaHpIzZ3rI2eZARgVOzp0ipA3\n2zBrpoGc6G8CskRYGm9tJsYTp3BSghK7qxzIzmoJoHfhoHlj8b7oInZzB/rS\nP7Rg1XY18ZAwCbvduQ+3eU0IvxLh8IiRttvGtZRODIkaRpNVr67uOV6kC1cp\nJftaiJFDp058yZ9GJXmlH6lAAN1HY9C3MCZuPgNThK6tDxs7TW1rJC5Np6sG\nv4Y7lx1lfwuQPUD0ygheEs3A4yHHrkVNxMKjo/aPo9ooqDPyTdPd43s0cnez\n//naNAnbDIiwlQjcLmIO1OOrY0l2yinxP3sjFgpg0u0Q6qJ54PgcTGLrUKbh\naB6h60eXTbnK7TiS+irdPq8pNkIFvqR4Rd1FWBKcrFJm3Ln0QOumAzLIn0w0\nPY5P\r\n=sZhz\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"97f566aec734b298fb52475ef48c191823c3168f","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-29T03:57:00.539Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.12","@balena/jellyfish-logger":"^4.0.27","@balena/jellyfish-metrics":"^2.0.36","@balena/jellyfish-environment":"^6.0.14"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.1.4-renovate-internal-patch-97f566aec734b298fb52475ef48c191823c3168f_1643428826232_0.4709583499839973","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.1.4-renovate-internal-patch-bc1ff757068fc858c82b9e3edc32ec55ed558346":{"name":"@balena/jellyfish-core","version":"14.1.4-renovate-internal-patch-bc1ff757068fc858c82b9e3edc32ec55ed558346","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.1.4-renovate-internal-patch-bc1ff757068fc858c82b9e3edc32ec55ed558346","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"87a880c1dbc25847ae8ae8e668f0b76007c18623","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.1.4-renovate-internal-patch-bc1ff757068fc858c82b9e3edc32ec55ed558346.tgz","fileCount":226,"integrity":"sha512-KKz1BkqIe5q7CiyX15/K++5HZC22CJKDDeDo4XzAhs60pDW/tc7mvYdB+EhOnZRtUSy9+jiJEoebYZac1AoWmw==","signatures":[{"sig":"MEQCIBsafgPETyeiWZPwRZWgfFAGIwerTbQTM0QxyKWV2p6mAiB9LKrTQs+POWDEVOjyATKzwAORy2jfi4kur0aO4xsG0w==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":839761,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh9NNSCRA9TVsSAnZWagAAcOQP/02yu2kQjqk3EGBS5EGJ\nTttMSPiAE//SM4OYJZf5/cMh0YJhV1kS2rIJS27u/MW54M+TN1GW2YWrtE38\nilu0RkQJ8VOeynPuHe1io0WKfcYxP0dpXZw42WdXtCyjeub9QepPu7fU/aDE\nEfh4Wk+Na6ZV272TrxFJTL3RJaDtu1yINjuRbyf6mJjSho7NFVD60g9gWb/e\n80sZGkrCeOUX/yN91Oq9eFGZ0ze/6ji3RpTcySp/ndodIOm/RVLeFNVeEW9+\noPJ8r+YntrJsMea46aY4JDAQDwUFhef6rEMmjHoFkNEmrfcNiML6bsBS+Jf6\na77OuB77EkapVV6lVttBJSNWyfJsxtw1/tsg9CGD+fH4O3d7oy1Anq0zbRSF\nA9KuEeIefk2GEJzikQ8I1Y+w/rF2SX5MoGFhcIA3fdhsnILZ5giqN+nr2AJK\n6tfr6q5aoTn75cpZCfHN9VSgUP1rCfF4U0C+VAkvlKzeLJaufiulVuvSe0/4\nzHi0jw/5XamBqxdDlSLtD4FxIIdi4e3UqaRNdpMHf3OSqXqNo3H6L5hyEuN4\nRIcYZUr5IzZd8s7hDFvuEcL6r0+GnZzwmpXsay8Uz09CH9HUsGGZ13WJV0xj\n+oEismTSv7ijVimOQngsq6xOs1Qa0pOTKHd1u45FGuvQBoSCSPs0WPb53IAV\nyJVX\r\n=KLML\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"bc1ff757068fc858c82b9e3edc32ec55ed558346","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-29T05:36:53.603Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.12","@balena/jellyfish-logger":"^4.0.27","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^6.0.14"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.1.4-renovate-internal-patch-bc1ff757068fc858c82b9e3edc32ec55ed558346_1643434834757_0.18549098985556722","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.1.4":{"name":"@balena/jellyfish-core","version":"14.1.4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.1.4","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"dbf32227a1576b97ab609af576ae1998601da3ed","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.1.4.tgz","fileCount":226,"integrity":"sha512-nbB17WPwuFrg935lg10ogmPtEDY4prrwbRqB9D+ozfRNvi5S4lSww1eLFFm+Ndaw3AJuTdqNdi2xdR23ZXb6Xw==","signatures":[{"sig":"MEQCIFRf8zgQUzjJloYoPR0l0va2v4Ncer02J75jQ+8FVDXsAiABAEuTPKRoxLAhQ1Wgztl2wChiJER3pstgca7b6i61hA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":839696,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh9NS9CRA9TVsSAnZWagAAw48QAJDa2qyy1TiJl4c+Hyf0\n4eB+A+yH98AXzC3xZjxv/SyO1XxrNdcRucpVgMtd9w18HaDS3NGRXfMXtpjx\nrCENfcDue6dDNqry5ur0Qsmidlcc38oWqlfnANPX1b307/r3ClRu1KEU3W/P\nNeOgryhKSKtW9LB8YBm2lT2SnhUruBP5TNyhtmvLRMkV1P6o8sBwJcsv3pGf\nJPPBvC4qBrExBYRCw1m2zeI+vJ5RxOsNqnpotuSS/ay8lHbmjzNkVUxxFFvA\n8NKCzgSduzLSo/6CRcfjx+YAcLFmyZ59VANticfpV1NYxxgHSeidutALjpx7\nSlKdu21Fbr1EyNLTULUJur8QtgGpmugZDi5TuAhSsicXUr5nDnEt/QTzRCd1\n1OEGF1efAiXWwns3p59b8AB6vodMIrH/oktiZCw9j7Ia0u/Q5l3jr/qmz6YU\ns9/HPpXzGtVPnq+23ttWc0IGsSgEV4sOvdfCaMZ33QMemkxCYbgcHvIB5kn8\nRbTevtWbB9YkOE1RVZ10Mo9G2Pod6VIWlYz6ubyOUBCTARO+7POq3y0Mvbc5\nPNy/53+RXBcXO2+BFqign6gxphcg4jWW2HuQiQBtOs3k0JbF2k/GQUfXYhwG\nFf1jsWTUsOh4kXvqcx757XUCF6sL77oQhCi/WMsEj+Rgy+SCzAx7iXCOTmV2\n4E0E\r\n=gqd1\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"bbc5be2600bde4194ab75cc0c610ecf127d51e5d","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-29T05:44:20.164Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.12","@balena/jellyfish-logger":"^4.0.27","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^6.0.14"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.1.4_1643435197647_0.01681725493866648","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.1.5-renovate-major-json-schema-org-c5cb3d53f0124ed0e005e144ad22f3ef601faa6a":{"name":"@balena/jellyfish-core","version":"14.1.5-renovate-major-json-schema-org-c5cb3d53f0124ed0e005e144ad22f3ef601faa6a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.1.5-renovate-major-json-schema-org-c5cb3d53f0124ed0e005e144ad22f3ef601faa6a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f2f27d1805c88bd706e0f7feb52179eb500451ca","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.1.5-renovate-major-json-schema-org-c5cb3d53f0124ed0e005e144ad22f3ef601faa6a.tgz","fileCount":226,"integrity":"sha512-FybwqjtlckiyeJtcsspMunQ/HQlq9FlLGA40xylHoHEhRtLf2NlMd1HU6zuX9kMCMNPN/yPkOMw4Xjrz3SX4GA==","signatures":[{"sig":"MEQCIHVHivFdAGsqaAnAFYyfEG98jKRPUlUVTZHPRhWC1KVsAiAgoz5CDnqdBKe08doxizoYODu6rRZsXKfV5TM+/wjuLQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":839853,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh9NXHCRA9TVsSAnZWagAAciIQAISpOc6VrxTTBWgcpUg+\nzLvk990SsE4VtQoRvZofkuj3Oo5+9KpEOSZ6f1Yoc8zdZVZqFWjL0YDwjmyl\nEo+mz6t0ktbZbTQ266NiEMUrT9Jrc9GZKxm2dLvtCBnLUGeVON5NwjfQeQEi\n/t/0XXxBglpE1N/+UrP34DCnnfKGAwaRAlWG7SsGpDWlLk3rRInvokMD5qHm\n0x51zorPIuPc8KTq3Uw3VGgOZY83rSrrmJ5YuVwB3Rb6KmYYz4mQB773Wgj3\ndhr2VIziLKbPFgat4ewXQNNdmVAHdd2hB7KU0kODQ1a52Xt0G/cJu9PGiPX3\ndsPclFe05YWvcY/StazoUG6heUvZrRbWBTH8yERcb9LJ7vpGdLo9dHgh5fgD\ndLMc6GHgVBuTx9cBRL2gy6WBSumaYOvzYiG8fQCIQw3IkzuV0MEF9fR2ZKKD\njPA1GOBnt0ckJJvwSNeEmbnZMbvkHNP8HWImcMKadpv1X2jxAEcQYaDnvr/W\ni2EQ48vSXS8ktTk64Flvqsr3GTJ9bTYGJewE9wLc21rqNVpikisHe7LhT46m\nw14WRROsMTvVKZ2aXTXTLdAYaOo4QB/zrgD2dHRncqRxEzQ5Q4ixyr1zYzOK\nJNKpiS69jqXB9Kh3le4O+K/4eY6Xcsz7PefDEhrhhTqT92CR5mjMsJoEFh+t\nKAvv\r\n=oTMN\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c5cb3d53f0124ed0e005e144ad22f3ef601faa6a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-29T05:46:03.203Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.12","@balena/jellyfish-logger":"^4.0.27","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^6.0.14"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.1.5-renovate-major-json-schema-org-c5cb3d53f0124ed0e005e144ad22f3ef601faa6a_1643435462835_0.41244004498252074","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.1.5-renovate-resinci-jellyfish-test-1-x-2553d08a2e6e6e4cff25b5b6d7bef5e9d82eb4f4":{"name":"@balena/jellyfish-core","version":"14.1.5-renovate-resinci-jellyfish-test-1-x-2553d08a2e6e6e4cff25b5b6d7bef5e9d82eb4f4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.1.5-renovate-resinci-jellyfish-test-1-x-2553d08a2e6e6e4cff25b5b6d7bef5e9d82eb4f4","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9153d1fe2f925bd4602eb1eb74defba113a7db3b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.1.5-renovate-resinci-jellyfish-test-1-x-2553d08a2e6e6e4cff25b5b6d7bef5e9d82eb4f4.tgz","fileCount":226,"integrity":"sha512-1bvMU2Mhtg7d8CW2MTg5ZQnUnC3waX2qIT752a10WRrNk8IQNt9qCFVbjDNkBFox3qypaHFzzVGrbe0nz9C9aA==","signatures":[{"sig":"MEUCID5D3qU4E/tP7tt6uWIYg6T643xR/eVrwZX0sSFICSNLAiEAowb4pXz7Oo4dZEkCIBGFPi2OBrIdJZaYCpsUyB00p+Y=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":839867,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh99C8CRA9TVsSAnZWagAAHtQQAJISbhBegLlpf2A4Rt4c\nZas908tc8xDyIiQ1RSfRfz1M6RV1Jv0K2K1L0lhsTXNLRlUTWuRBEZ8cUNdE\nk/KG319xSaFPaUtXX3YARgda/Nb2HJqNxekSXKGbrZnm6RNmxyMtIw59GX/i\n7fwzcDzK3gpfsh2EAUJQdNXTjnllzbc174SdPUiIe52ut3L9O2ppqSRrjun8\nO2WOctkWUp4sszRL3iju3C/1+hP6h2cCDgQ0Or27Wtqt6OYVZSoXSi9v9Whd\nKpQ/2uyzYpJE9J3GO+QlsTMAyo5O1usJKdj2VCblFiVIWdSpaBkubJZDX71C\nGp3Y6BrO4CG8rky2A+LWMeaCmFuQAw9dpU/AvEec1NYmNTkYQhFOGmFfmOhx\nmr2pY9WnM/j40srS4ggmeTAKhVagEtoMxFZV3FFoj2HTS7HqXUZtBxEO++9W\n66zi9YOJCWRsCbF9ZTbRFH/0dvk392cBnN8nMJjuyLR1HF3IW5KkHwEpyxO+\n1tQJwsuJqVGhK6UlqAbOG+7agzMrdlbd6YxjMzLtvu5Mv24gR1gOQmLhnGPH\nIgGLUHdgpKFHsCx8J/BEiOh/P8FZrytZV4ZD3tOCGRbTjRqUzSuhMBcMu12c\nVoqBMcp3cSMtAX0YVgTGJWYIdJRGPfp/RTyv7aj5N7kJ+Dq+bs27UQELq8qC\nropn\r\n=Woxi\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2553d08a2e6e6e4cff25b5b6d7bef5e9d82eb4f4","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-31T12:03:20.887Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.12","@balena/jellyfish-logger":"^4.0.27","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^6.0.14"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.1.5-renovate-resinci-jellyfish-test-1-x-2553d08a2e6e6e4cff25b5b6d7bef5e9d82eb4f4_1643630780075_0.4155485838747801","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.1.5":{"name":"@balena/jellyfish-core","version":"14.1.5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.1.5","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"24b2c8625175efe1e9d8e66b19953637552b88e7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.1.5.tgz","fileCount":226,"integrity":"sha512-1gOfOrO2kAjMmhfyFIbjSYeXBmoMpBTHO6nfjtvgIvhKGiaOEFu12RBLkVjB0XbVHsFHl8qrhTBAKGhWwjmV0g==","signatures":[{"sig":"MEYCIQCTLC0hMAutInY/6ty+cWpk7rJ446pESRLJvtpo0YnWmAIhAPGge/vHLtwyL4ENVuFSJuiJsIFJDIVkIkSmkYdGVgQu","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":839790,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh99IsCRA9TVsSAnZWagAA5/IP/i//zFAPdSGjidmFpT0w\nHTMEcxsSYsRKr3J9nEv9vURzrCwe1WyzxE9/PPj9is5Ue6nSMZMupMoVVUuq\nn7SSsj0qwPoJgsvGCj+JMmZvypJxqkunfZ1Xiyk6ingFwCnGpXiDnJNAvXSW\nsLzS1Zl4OQbUCCHb2wnI+LWDSKgZx+tlxii5US9vhHNPM4y8jJ7ctwQetcGf\nCo2NU0t2ewoeyrjmXK5puo2OmyT3g3T6aJrXG1UY5tC8A/ErVDU7RKm7hDeg\nMgbkaGHYrHI7jeiQrf6bTNBiqS7TjlfBymorAIcLBbwqDwpOG7HsbRvYfDYz\nklJ5NCWHHoh7jSzcVVzDmwSF/whuwpNoiS1W49DJckich3WXzwW4t1Liyi6m\nseXWaoq0ltr7sNbGKiqrmfgdfhaau9V17Ilov2a7+Q58bNx/RhVQDB5Ehxuc\nua8FbT3M5TJG2MNTJCDFbjCeb1pe+nv4Q8P6uXYTMkilh8MqrBf+1IVeyNWG\nDqmE7tvNhcpT0CsVRU/MwGSU93EwvvOAB/IXOYEHJLOEcaCsujD2E5RDobXy\nQ4gyDR33RCoCgTFyPdJAGClDrLBxuqFg/2oh8W3ic+e1QvLSX359zE6SU70e\nTk6qhErFFLWuRSA/TL5zPirPxP+w08yBk526BqzWp9R+2/2XGlVuVD6tFh9w\ntQPX\r\n=hHGg\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f4bc47f2c32536a5dc31f44ac6e1c9c670a8a0a0","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-31T12:10:31.143Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.12","@balena/jellyfish-logger":"^4.0.27","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^6.0.14"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.1.5_1643631148048_0.03210750878652813","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.1.6-renovate-major-json-schema-org-cdd219e4caaf288588c3415948ecb9d595d27f89":{"name":"@balena/jellyfish-core","version":"14.1.6-renovate-major-json-schema-org-cdd219e4caaf288588c3415948ecb9d595d27f89","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.1.6-renovate-major-json-schema-org-cdd219e4caaf288588c3415948ecb9d595d27f89","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a1950af69959e2d9bc145b333cc9c1545bd3515a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.1.6-renovate-major-json-schema-org-cdd219e4caaf288588c3415948ecb9d595d27f89.tgz","fileCount":226,"integrity":"sha512-ZGjfrf5PfnmlhdjCWp5JJc70ld3saEgIomjrYZoIRS/IqpVzm+Mmf76mdL1jIJORDxWMdnN8HotjcwHT6whxUQ==","signatures":[{"sig":"MEUCIFEnrNmMiPEz0oC6yKti7B4CSURcn0cJUNwg9Ts17+XVAiEAl1n1gnWFKrfZ/fhXTyDOHrw62fWfitWpLKCg51SCuFs=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":839947,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh99M9CRA9TVsSAnZWagAAb7kP/j0+QhehpE/LoWrDQ84P\nuGo2/zsh1UMJs2ZI2uQhJy3p4j+1Lk2i144CBoFxduvKDpvRzjPYSHAknA61\n6VqeBIDJmhoeQGEjWnUlNScvaMLfOxAxpD8HpoA6Ca+kgyaMeBqX3hA4EMxw\ndlp8UCBaIMmaVI+EAUnlzCo8mIP78YcgJJdCI2HJVcrURQ8W+xnwDGOnKZAL\n2vvAkjYuJctv5EGpMzPjN9zGmR23pq44Mpf4hQaQ03RKpXiUT6nwL8E+AI/h\nmjMQYPcZDQ+RUhqNSbqS+efueZsH7+Pc+9CMNmiranLya4hP0llrw4lUQuhi\n6SD5abYL1qysgzGUAGw+zdbuj/007QRJ9RTVYRMBNWFOfiARprzMmY9xHwBR\nDKouzNHonQCGIaP8Ku4VtjmKfp3s52W8E5LKESbMZueUFerbrFKyK/0WdFVG\nwPdTJEY7NpQmK25aF5xCNZNBtfNB7jhSh/wmy/6CBnOeOdbVizxOBGykgNwF\nuVXMMXRA7hPFi8xBPWDzqhWtPtO5S3mQ8zmFA7Of+XdIxE4ZswXlM4oOXo1X\nl0c7sOi5R+luAcWtSTp+DYmCFOsIT7E2eJscECa4mlgyCgz3+iJ4nqb0fdhJ\nyMEu3rncvhLlGUoFeMjuP2BQhfTfOWUWLi1aWxdBO5Sj7PUjjlvLuxARbkme\nNPdr\r\n=sJmu\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"cdd219e4caaf288588c3415948ecb9d595d27f89","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-31T12:14:23.272Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.12","@balena/jellyfish-logger":"^4.0.27","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^6.0.14"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.1.6-renovate-major-json-schema-org-cdd219e4caaf288588c3415948ecb9d595d27f89_1643631421051_0.8227625550980022","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.0.1-edufschmidt-move-types-into-core-ab091f439574f28c259cb3584d252ec7e57e8f94":{"name":"@balena/jellyfish-core","version":"14.0.1-edufschmidt-move-types-into-core-ab091f439574f28c259cb3584d252ec7e57e8f94","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.0.1-edufschmidt-move-types-into-core-ab091f439574f28c259cb3584d252ec7e57e8f94","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4903d892e75af6a3bccad22e4118d9f641c4b2d7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.0.1-edufschmidt-move-types-into-core-ab091f439574f28c259cb3584d252ec7e57e8f94.tgz","fileCount":174,"integrity":"sha512-gkGxBRzN5xZf/Q6KrifG0a3hOgAVFtVIdKg9Q5V16p1bmvAGxt+Zjk0ZMvpRPYcxmxfRS8cM2ZQNH6PCldRkdQ==","signatures":[{"sig":"MEUCIQCaL8fWINST8pwjjVCiubeDE2xDeVUGgUqhCjEYX9edMwIgcubsUVorK00djC1zob9TU8MP7ZIcRu3Bmsiutm0sVZY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":734077,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh9/RXCRA9TVsSAnZWagAA9MIQAJkmR++1jyhQVgJ9wm3b\ndc0XHXw5jTnLw12RyaXif0STVp8v9u7+NUjDrObbwT5x56udqb6NwvOPAXFt\nHaTBlhwCw+ZLy9UR7Ul9enr8nELv/A3ADVInhRM9XvjBuh018/DrquXjEeXb\nON8n4B9bN/QpvD4nek6ycH4+KETYCgvpF2YP45u/zEIjftW2ZPlx6x+s2rlX\niClmHF9aRf3Itld0vymTZTdd8dh5FjMHA3N3E9APdN646Zbd9Y9ntdg/0KDL\nmeJ/FzHDWrAdqit3rJ7nnP65pYlpgOMHu2iGv4p8DTOD7vxWBduK4I9YbH3b\nYsw5DLArDf+a6g+zzJ2GBTeIKzawI//NQ/vQ2bdjrmp1CNVXUmMOBPw9lWiu\nxnhEKmrJrf7EY9Df5owQEV6SVW/PaAQth7cwyA4GbpNiPPn0yMJjxtiMUOzW\nhQgb0+ty/WaO5LVzAF2ebe+RDg67MB9m5ZhIYhhVwyiVZHZxa8FKUaA93xb5\n1DyV1jWGS0F+AZeux6zng8vKHnP7eJYGavDHMA+VSMe2bXfUi2VHJRpl13cF\nZLxzjTeVb9eEY5mk8MQ9A6GqSjeVEUUy0oELJ29dGHJ7qgaM+Lt331TuJjTd\nmfDA4Xk+zQlP8845INRLz18M11X++RTmAU60Bq4OIXE1/TPkoQqT3HCLASMK\nP4PG\r\n=Ugx3\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ab091f439574f28c259cb3584d252ec7e57e8f94","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && npm run types && tsc -p tsconfig.build.json && cp -r lib/schemas build/schemas","clean":"rimraf build","types":"node scripts/generate-contract-interfaces.js","compose":"docker-compose up","prepack":"npm run build","lint:fix":"npm run types && balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-01-31T14:34:14.117Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.8","@balena/jellyfish-logger":"^4.0.19","@balena/jellyfish-metrics":"^2.0.25","json-schema-to-typescript":"^10.1.5","@balena/jellyfish-environment":"^6.0.10"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.4","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.1.7","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/lodash":"^4.14.178","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.0.1-edufschmidt-move-types-into-core-ab091f439574f28c259cb3584d252ec7e57e8f94_1643639895513_0.7193043906998771","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.2.0-export-query-options-7b127336e187bf1f551c9f1afdee868d792d07b0":{"name":"@balena/jellyfish-core","version":"14.2.0-export-query-options-7b127336e187bf1f551c9f1afdee868d792d07b0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.2.0-export-query-options-7b127336e187bf1f551c9f1afdee868d792d07b0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"38a5c166fda2b99f29ffdee47eb5746b0b76fb84","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.2.0-export-query-options-7b127336e187bf1f551c9f1afdee868d792d07b0.tgz","fileCount":226,"integrity":"sha512-R5X7NdSjHJVwGqBhKKlis8aoiPZlz58ptv/8Co7J51vacRtS2Ur6Qw4+PRCiXu93tDz13QmmV3Fvu2wFEUCdPw==","signatures":[{"sig":"MEUCIEaFE/tBt3cg34Rm1IbORB8Wis31TU6BybwnAlo3HE6oAiEAlxP5kYo384qX45XGkXWTgbwT5hvFTwy0yEEm4Oekabs=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":839568,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh+YCgCRA9TVsSAnZWagAASPQP/2IBpcXsCOP9xcELiyw8\nhgDBBVgkOgMZHDoe2iCcsGhg5LGzHrwU3RVtHeWCrf/5H0ixGo9a8AeBDcMA\nhxU+Fj/pVrWpF/nNQPvoTzHI9/lJ3e89DTzyU8D3emhzyHMvJBLHGXoahhQm\np/+y42tzRRfsM2o27KzzgS7OxTt62HdlYirgxT1kFdEhSbz5va9MB0K0CwhM\nGYwCD36rMuFPE4NMCHKd0anEJf8egYva3nY9RB29/d9uwqc5gEaz9P6y6Zs1\nhRKwptkji63RaM74plilaENkJUbE/WQZ2VEbXrr7isRi+H9yXfygwCCBIm/L\n17NfXz6vN6pOFKlSSZpXGVtvXaNl7q44h9AqOLRs+tyizSYwZ669HxCQOjJI\nDMSpLVvw/3pbcMdFWEm7hSPHrN7nv+nMudNn8SUctwAngwHP8N/4n5iL+ntZ\nGpiyX4/89vXoHBDHC2oUhDHICbGHyewmWDws1sAk9KHK9l8w5Cwuu1J4NZtt\nO+KYYLu4LMVqu36rI+wMpxG3OhSUbfcY+bFFkHaZFI16x1Ako6eZVCt0AQi7\niw6SyaymjajFp6l1w/TxJZ2E6Q1Sjpts5l1e1YqqVZt5ge3J3tp364OgUO2u\npxeftirei+H448HjoCN7TQ8OtKoemdE8JwtQDxbYbPHbabfhlw1L9GVn1f4p\nwi9g\r\n=r5iQ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7b127336e187bf1f551c9f1afdee868d792d07b0","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-01T18:45:51.304Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.12","@balena/jellyfish-logger":"^4.0.27","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^6.0.14"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.2.0-export-query-options-7b127336e187bf1f551c9f1afdee868d792d07b0_1643741343988_0.8011323860803576","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.2.0-export-query-options-daae8112f216177dff5817e22cf26097d2d3b491":{"name":"@balena/jellyfish-core","version":"14.2.0-export-query-options-daae8112f216177dff5817e22cf26097d2d3b491","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.2.0-export-query-options-daae8112f216177dff5817e22cf26097d2d3b491","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a1646c5ff748f909e4dc6dac133d1697652eaf39","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.2.0-export-query-options-daae8112f216177dff5817e22cf26097d2d3b491.tgz","fileCount":226,"integrity":"sha512-m74XbUwPpqwlYlgKAJl51T/H5KAqPJi3HGLFP9zjVMfpHD6WAavS4JOO3xOwpcLKjt8Lc6XlX+CL1APN3aQo3w==","signatures":[{"sig":"MEUCIGbhISLY6BQPbd5sKdfoMUYoIEtjdfP8C/SnzwjJ5djMAiEAsOLewcmQZYxD9SeXjYf7kzGtzpnjvVf8PdyDjvtovJw=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":839600,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh+YFqCRA9TVsSAnZWagAAr6AP/1yfu4J4Gy2wTw9p0JaS\nOJcOA87ecmCQDkmSxa4zKS77NIJG0fFbWZskXpJPGEdi9p2puVJA7cpg4k5D\nOGijtiKEMT6yiCpumsRw3C5KInnfv+02hoDb+znDa4KI4v2tcjvigCoJmZGb\nTKrigrCr2M9jKmiZbwEXyqp125q7rcPVDGEi4/4uzadw6mND3mUFV2zMTYjt\n5s9y4Aftv/uITmBLsJF8n0hNAMOi3nLdpO8QEYNT7KkRmaS5b/2TKc7rcZfq\nVIM9H+TB2A6VtBNZ1wrS2KWU3a2n+sWOh0wrISMHuRlrn9llth1EH/u0v1VQ\nh7aLyV9tOrkf+/T3TfzpplOBpj0S4N9n9NfppnsSqp5BJZ8unfc0N/FCA5nN\nVzcXGiKQm3ogO/zVkubvXLMLa44TiQ69zRXD8gmMLmI+2cvRjxe5SebvrQop\nA574gjiuHjLb6sRLcBY7+D3k/P6/htr+wjw7JIQBcSz1NP6ayLkXd1/kb+hs\nxqMskLcZ4jAuusFOv7d5g/ZUDlog2vtFr81Lj/BK8en8Kwe6et2a8xCaghu0\nsd/F+Ns43dZ5TgqhI3vozh6yrn58oG/DQ8jhI0QjBgeGXvO4M8HqWa98ZXN+\nxzzJFULMu1707BeLL/pLqYD03/DDzzDZXBSGpVcIWY03manvQD5jfiXXnmdb\n/G7d\r\n=Euu5\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"daae8112f216177dff5817e22cf26097d2d3b491","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-01T18:49:52.688Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.12","@balena/jellyfish-logger":"^4.0.27","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^6.0.14"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.2.0-export-query-options-daae8112f216177dff5817e22cf26097d2d3b491_1643741546030_0.15477467427237945","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.2.0-export-query-options-59acaff98e1429a918f2cad78686732d294cae29":{"name":"@balena/jellyfish-core","version":"14.2.0-export-query-options-59acaff98e1429a918f2cad78686732d294cae29","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.2.0-export-query-options-59acaff98e1429a918f2cad78686732d294cae29","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"fe36120b13aaad2c06dea1899ed9e70bbc47a60f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.2.0-export-query-options-59acaff98e1429a918f2cad78686732d294cae29.tgz","fileCount":226,"integrity":"sha512-ih9iHqEICElKsQ7QdoWKd379MsLW9VPXV6s/ywdXUoGA4tBnAvnEyT5mBslJIzGl4IP3M/SbYCiDn944GL04XQ==","signatures":[{"sig":"MEUCIQDU4SOi1+Vypf6lqKxgKk8vnITG4kSDHmmZAECNb0hzJwIgcKDVr8hCtlwxGHlW40T5f3+omltCkDGW1g+XK9TWJ2E=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":839837,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh+YRoCRA9TVsSAnZWagAAotcP/233F+e+iuwoN9G/63NY\nX0ATc2YNddkN1+bDLSqP6vDqLsA7asy9+Zzw4846imqdQloFrzsa1uQauKLw\nQHCU0p8USmWPrNewl7PKZzGHTi0Q/wLoqc5a8p+/Iw758N2rKQ7hgxD7pS4K\nkbj6GI1K0wOMQV3JgRhWA0BilS888dgeHXNhC5dxDzsD2U142XjuoTeDrV/f\nMEH2ijUt71b/9ue03ZEackBuBtpORuk24uRhdcCympL1uJvRGlh4RbuKfEpI\nF5p6ZJbdqt6HeLyTPlFVjM5VBr6vkav8r1Q6eEgvZZ7k5MJkOGPc5VPywdsL\nJtew4QAoDcZmvCqqDHuXvKe5i2pAzOO1trobV/MtV34bee90jN+LS/4CHMl0\niI9Hqr+Du1y1KAuBqB+B9yiiKg+/GLG5lCoWrOZ9zV9lIE8uWV3CiKUcDQqH\nAK1YWe6OWTBA3BlbuZj2R7FhxdFMKoblB+bZO/sKPpO8n3NdiDFBxmh0Ml2V\nKlyh6rYgpyA2fbKIIcMh6mZt86ZRv9AzJK3DPJskopNZV3Pi2+b0uMwWwuNg\n0DssyHsStjVmz0nSjqbTlIkv8jfixN9RncbQa3xeyUciZ6lQZPkJuZHvYDpV\ng06dhFPgqoCOXqhfVB2AK3vynjaIL6+FBeeuBws/Hd2TgYcB6OZA7Mlrxbsd\nPdF1\r\n=KLJ4\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"59acaff98e1429a918f2cad78686732d294cae29","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-01T19:02:23.710Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.12","@balena/jellyfish-logger":"^4.0.27","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^6.0.14"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.2.0-export-query-options-59acaff98e1429a918f2cad78686732d294cae29_1643742312122_0.7399954636643185","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.2.0":{"name":"@balena/jellyfish-core","version":"14.2.0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.2.0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8f80f95abee12ee78fc1838dedd64666a6c804b8","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.2.0.tgz","fileCount":226,"integrity":"sha512-fOrOSqD4z3mbG+LQco6Kenik/DPFa5/qpbhg+1S94P2yrpGaYnwiTzDIWgSGTm2xmBi1dLMx7cdK5Ps7JBwxEw==","signatures":[{"sig":"MEUCIQDo7xE0scsZEfkKLCeoU8sNXQJPSTMek+38hCaj8XDq9wIgOP4LnpXR8wQ9JziIt1z+nxWoNe0AN/h2FYyHn2wdMfY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":839775,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh+YXCCRA9TVsSAnZWagAAJDUP/3t7tWKtEN3cnQLUXf9O\nD18zuVcWPNveHmsEOCUJj53ZWymH73bvV55kliqkPbfcHWBqlLKINGgKboSF\nnVPQ7KcS/MCcTwPWqSuHimUDc9A3h7r3tt+HcK4sxHYBqoLg7sI+B7D91AyZ\ngntRQA/+37E8nsJAXdCuDcHIW9Np7hwq3ylGWfB//rHfhHOFudmXKmYAHgEV\nU9w4Hs6p9enrtmi+fgBAhe8g2rcG3OGHONLolHt8t9M3U5lyQo20PJZMETMr\npQhuJUkEhezDom78Dkuk+2GSLa5QOUPM29UaTqzDI0z0u+BtS/Uy8nXwX1kV\nXEObPz3Mzz/FoHtP+3sJJ/4qCXAJUpmmOumE2RgslFtXhBChNmaDvyVFrjUA\nhdHwR2aGoslv8drF9k/3+dBbR1kgTNQYKxWHuiaQTKw7Aohmma0GKllnGTJu\notrBtX+bM3BALhhLhzukz2SDDMyfayhgp4elXIYRoR6pUroEe9CUKoGTKd4c\nxayHRV+jhGgDeEVEYfd6QaG0LyvPweqBA0FFWf/tS7tmB625hHGllX71bm4S\n4nwsQhqar/jpcq49wzIlqZiP+wjvwv1yDcK7CzWViHV8IM+kP+u/ARzCqfEh\n0wx/wnsWjYcPA2sM7aMKDgTlhhLSuJ/WmUDm0rfCUooZg9YWdnfPdDaFnJUK\nT1Ab\r\n=WyMY\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"db61ab5f8f3b5caa13a6474d7aa9ab14631fa90c","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-01T19:08:57.272Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.12","@balena/jellyfish-logger":"^4.0.27","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^6.0.14"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.2.0_1643742658814_0.43715343377006755","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.2.1-renovate-resinci-jellyfish-test-1-x-9040e71ae9a02b4f6b02e06e95eb6b4dab9e6942":{"name":"@balena/jellyfish-core","version":"14.2.1-renovate-resinci-jellyfish-test-1-x-9040e71ae9a02b4f6b02e06e95eb6b4dab9e6942","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.2.1-renovate-resinci-jellyfish-test-1-x-9040e71ae9a02b4f6b02e06e95eb6b4dab9e6942","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3f8ff38ac2195afa70fb56ff34e0283c47429ad8","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.2.1-renovate-resinci-jellyfish-test-1-x-9040e71ae9a02b4f6b02e06e95eb6b4dab9e6942.tgz","fileCount":226,"integrity":"sha512-LIoaSdsoy1r2ZIYjwsHf5sShzeRW3MT2yaObKbN6KKPuZ/e+RWMFTMVuclgC5zKy50YWiiVeYY3uVUd4Z3p9DA==","signatures":[{"sig":"MEUCICx+GLVwtRY7hlfLtC7KLlgmm3C8g4EdRUCYA/cl2JURAiEAsh9EC1QweU9nfdN+ydCTv6b9oZSMzeHrn6+p7QajB/M=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":839946,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh/HmICRA9TVsSAnZWagAA4dYQAJe4PfDh3aYcXdILTElj\nSwfRHMFChIEzYE7jeb40uZofQLmr+Ym0kB1M3ypuate1XpAnhlIr4yM1KWRD\neEz5f+l+poTAN3FrhI13kcK5pgN4tx4ow3mWkNLTfs/5n3X5gY8tQcuttEAt\n0/NTRxzGgRyA6uXYby2Ceohara7Lv1lfGLneFNU83+jiwRcTrFZ7vJgVPW/s\nXswi5GYqjDIbxYvJ3UcZr9UXzZpNld2m5+CIIAbH7fu1nifLnz6Cdmu6Bw3J\n1XMfB5TEnws70fw8LnSBUChxmwChFcXJ7QCrtXVvgqlA7ARYT088yVm8v0Mx\nkxk3t66kFtHzPPRJ+Lu0bF3twxOYLmKEKXt0+o2MY6c1QXqcpISTHC61GWlJ\nXBW/yrHcNcknkNjLFM65n0YWhVVkhSsxLpL61gUUCjSnVNUw7wn4QtHGfheb\n+TqV//6YNf/po24rDfZ5OV1Zy6CER6GRkkf1vOBID9ALsqgAFJUcyL61YD1n\n7CeR4llxeWWKD6GqWzAcVawElINfmWNZjWAblKNRXUD2kJlMu75WKy1HmrjB\nEnyNbZpzm64VzAFqR1vu/lZnRtPGqhGC5KWFfnWwDr4ZiqFeb20xH5s/I9Z7\notLQtlhFgYhQo5+PBqkLFLqUdWhmxgXxXIGBiCMsjSpL3xCVO3/WMTgNoS+Q\nGTiP\r\n=BmF8\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"9040e71ae9a02b4f6b02e06e95eb6b4dab9e6942","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-04T00:53:03.752Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.12","@balena/jellyfish-logger":"^4.0.27","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^6.0.14"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.2.1-renovate-resinci-jellyfish-test-1-x-9040e71ae9a02b4f6b02e06e95eb6b4dab9e6942_1643936136163_0.396192997050844","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.2.1":{"name":"@balena/jellyfish-core","version":"14.2.1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.2.1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"113918cfa66b477fdac0c517ed0c920715442970","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.2.1.tgz","fileCount":226,"integrity":"sha512-q3m5FGxRzxMemjEDznXYdYkmba4rFq+pdDucvt5qN9BhPdl58/K0BPgjJEeVk3KnZmpcX6SitJA8VJd2vMbjDA==","signatures":[{"sig":"MEUCIQCL3WfCAtY7Z3p4sGEDrKTDgwcorRuowZkBMfddzXAdSwIgJvLNh7P1azmTFCuDQ/kdCJZM5vE8s3UDJ8DU4eWEoR4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":839869,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh/RiACRA9TVsSAnZWagAA08EP/AllpxbeHqri3CcmhzgI\nbwiigHtGA1f2pmuOUNndpryVBzqJ+QOusPLXEfZB4S6kGEt6kzfLxSPD2LuV\nzspAdW/LXcru9s0J0VDX2WPL+3eexv5poAoNGfeObhiGuJtjwlxFLFKYeRrv\nVpYnZCnQR3/ZAmk04kLaAMy1cJmXyyfctaS1qA/Uy6XbDPGB7rQPyc5RuMyb\nuxp7pG7cyqZoSvWVPFtjLGaPLK6oi0ya+Iwf/2ndZdNX5LL4GtuB5/wJmGxh\nrBHZxHG76RZFuO81QUu1qvcN4jlMwgV2t+ve+0J5tNpNE7tSd2y6rzNnG3Sf\nO/jAruxu8P9Q+R6tr1vizt+iDN1NS7ruvyrIfmZTTOYZniDzvMJRLe5PKgv7\nXJZRoUWeECq154W7HwWHrzsYaufINtncTzpEtMjytqpCXYn9/PH8+pG66Jrn\nysZ1Lw5zksT6a04UnGs9L5SWrez3QiOv5fE0UtDst7z2zcR9Xt35EvbbLljk\nv+5l0m1uWl2V8lwEEEe0y3JvXCdAauL9HeOv+NFqhN640ODsT2PA/uHgZ+KM\nn5yftKmdY5M0oRdY74UkLvYqp4/WbFCN96NJe7rPYEGEXRmS2WhQ5qFz81h1\nsVAKTDErdbiGj5cVxssewDyMsfpf6O7GC6tUaXKzvJwnka1MegAzsjTfozl2\nnZD2\r\n=J9Ys\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b3d3029ae16d579cff46cb69e74e0633aedcee22","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-04T12:11:25.265Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.12","@balena/jellyfish-logger":"^4.0.27","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^6.0.14"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.2.1_1643976832782_0.936298000130372","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.2.2-renovate-major-json-schema-org-6af605b3e83e6d69cb28d33d2f65cc29d86742a8":{"name":"@balena/jellyfish-core","version":"14.2.2-renovate-major-json-schema-org-6af605b3e83e6d69cb28d33d2f65cc29d86742a8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.2.2-renovate-major-json-schema-org-6af605b3e83e6d69cb28d33d2f65cc29d86742a8","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"008bfc46649dccf165bb940564e334201753afc4","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.2.2-renovate-major-json-schema-org-6af605b3e83e6d69cb28d33d2f65cc29d86742a8.tgz","fileCount":226,"integrity":"sha512-3gORSJtre7kqVarvS15uzNcxMCX1sgqJcNRy1k6FBsYm1686obXWivBVfIC35L/6gCuOnpXdqwKZ8cK0fK4v1w==","signatures":[{"sig":"MEUCIBmk4r6G+zwJtZfyZLGttDml+zPtOAM/jBbZ9sg/YXLdAiEAgC5zXTnHfikggTvrJA/70u6mPX2luJflY8CYK8ilQtQ=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":840026,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh/RkwCRA9TVsSAnZWagAAo18QAJbnBC4tyvXqjovuSMnd\n2DST3W5mwud7GzmOo21m8468YgmpzT+BTuecKicm5ed38PheGxLC//gzp0U+\ninQbbXMjcmpV6mHYo0b0OIipqUpUE5S+lFSD4kYMLH8vSy0yJX0xgHb9laU4\nlORqG7zPYTbCWdutGQJYSqps0DgrwFO2nGWA7E4CSbTwnJw0z2w9ZFUe+MdD\nVepilzmD/GCSqNnQJdZNxJoXLThbm1W0dTZ0S08MNZMafB4rrl/9acizLzJA\nSj1dPE9IH/NGPsFkxlv556hkyG9k8YyfV4BuEBjWiJ4QD4ZFgSVsp/du+2I6\nK5lvHx5zYIGzpaLDaFbnm97m5gT2uRj56+OEbU3N/zEYLmXD2k/E+ilrDgrs\nZyUk+kuknyMHtK7eYe3zzmhdJAvbqNUi6jHurAF7b+0vSS0BiFnMEiDKoKle\n3xDJEHbZ2o1GyGccOZ2EuHMRCiakiGQ15vgvFm2nN0F+Yb/wJRqfwby78xEW\nmRywz6Q3TQKzD/DLe8VZeLLecI8XQbShF6cZW8ekLx2GguuUlVEczkZZUmaW\nU7iQl7ukP4OXTclyhYHHnW14Z5tpnkI2eMGag+TrCJQQHRJHj1VVVdDu+beb\nY3rveJzGNyufOEjy2a0odOmyVnBtLhUhRiO3I1cVXwYNN7GDrcr0hJFfmcDf\n0pWT\r\n=Rq7V\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6af605b3e83e6d69cb28d33d2f65cc29d86742a8","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-04T12:13:39.142Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.1","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.12","@balena/jellyfish-logger":"^4.0.27","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^6.0.14"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.2","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.2.2-renovate-major-json-schema-org-6af605b3e83e6d69cb28d33d2f65cc29d86742a8_1643977008769_0.48753036111389325","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.2.2-renovate-external-non-major-bf955f43fa621342224c578c991132052db2c748":{"name":"@balena/jellyfish-core","version":"14.2.2-renovate-external-non-major-bf955f43fa621342224c578c991132052db2c748","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.2.2-renovate-external-non-major-bf955f43fa621342224c578c991132052db2c748","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"47777d20f889a171838d31924165bc682f498fce","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.2.2-renovate-external-non-major-bf955f43fa621342224c578c991132052db2c748.tgz","fileCount":226,"integrity":"sha512-yomSLGD/t+BKrEemjV2B9GfgoSf4WNCp/Z46kTPOMnZVdnR3zUbkRnQ3EoVaWuz7ZG0dqoz2dLPrQDD9yzCAgw==","signatures":[{"sig":"MEQCIDDiMj65SI1LgZpbGcsOhxfZwP3cu5VIzu5yTZnRyxxJAiBdDfxDVIL8FbWIV0SWTgBpq5bq4TkBwLUyMEtPF8wxXA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":840006,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh/dzJCRA9TVsSAnZWagAA0VEP/0KABQSKmGw1HU4mFu9h\nmbVyXhjoVbU+GGM6qmRNMpxs8oSybiNEYNFAByW5EGtObpz4Nef2ZkIQHPWv\noeeb/gGLB8C717D/F56wzjzXxdmaYKNlyEu6auYb93FtMuh+3sB6MoLBuQcm\nIttssoqaFV9KvbHf2yB6lkz60lioVMYugNcnAlao7jAfBneZDcVtKG0/3xpC\nbXmemcGaN7cQla2Qha6CujVtQO3Sa6zXHOMPr3/PAoRKPgFSVQPHuXTbcSAm\nHDKHxK5V0USZsu13kH35P9MeQDCWllOWDQ4gk8ZCiXZfs1AXZ2jdaMJ7PKf8\ncwr7W2ONV+xie3tc7qCcFZXduRDqaDI8U+s4OR+YPGLVJE8fbUoNnmMDucK3\ngeW5+bKJ7bHxI6unmHP1c9izDalDsnQbrhTpJbs3RTxqVx91AzRZUdibiKF7\nV3fTndDM1JusrSZuPAsplcQ6l63GJffy1VX1OxObdBNUybBT1TvOiRwWkKhU\nUaF2wGEmd/uJViP5/NQRz1giMlYVsBbt5drd/S9tg+5Em+lOf5evFIRVw8L9\npxpJfNl8udBfEpuj4odjOuHyvEwKEmixm7ptAUopXfqg5DUThZhp9uMJw59a\nkq+vg1c8RuYkSoV13uXBQah8eLy5bZ+owens/Yl8sW1wDbCUIOPMYk6j1ELF\nZqPe\r\n=UvXz\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"bf955f43fa621342224c578c991132052db2c748","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-05T02:08:07.492Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.12","@balena/jellyfish-logger":"^4.0.27","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^6.0.14"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.3","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.2.2-renovate-external-non-major-bf955f43fa621342224c578c991132052db2c748_1644027081590_0.23708399745608544","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.2.2":{"name":"@balena/jellyfish-core","version":"14.2.2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.2.2","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b76133347d4e2b97fa0e164b75c04f9350ade3c3","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.2.2.tgz","fileCount":226,"integrity":"sha512-ojaGnT9dg9ysleXze+GApwlzYzgInO40OBMKCRAaZnILXcjShjC2FtsnBAgnNRH+w21VPjifTEtUcRqsD7ML+w==","signatures":[{"sig":"MEYCIQD1Bow5iscypKoSU7PyCwzpKTlkjLLQa+p1Mrat5492VQIhAPlGKfg0l/OYxxYGppppGYV/vdxuctMXA/PmtRQMDdHD","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":839937,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh/d4/CRA9TVsSAnZWagAASNcP/2yO3VsUOh5NGQwJa1bl\nr70HaBjM3wzCIFEeZSN/9YPpSlzmDi6RQj7wM7MQWhUCs3Lrw3ie6Zj59yZ/\ndgRd5liOVEtmK3eL7tCeBYgcTMpfIGcfkCOKne8Eec7wcGzEF5fPK9w+EIz2\nCBFLzp52WLOnjUrqm2khEZNsL2BxVGB1vvC3RoTb05JEQZKcAiE4aVBRtPBP\nCI1QJUYXjROUzvU8DiU8oAXFzF+L9wjqas+kuvbw3qNQShHsxSP6BctPlFLC\nIDaV7gSGrEqnRzxHbtGe5ZuLfmmUjpwghuGYA18IgVdqn+BkGQQ6SJIz+GED\niZL+TQYgvKKLV5/msSLgj23YpZb73BipvoDyvBetOCbmfaz8zHyeWiymHuMr\nQGnNXMuwy5Fboe7kdjHvP0W4FvO3GxmNJz1F+4R94CYqpUiYaxmk/0ZBpqCp\nIxnhnr4rA8v4AOWUgO09cCM5tmMb4fgv3n67537SOJEIE1M4RP8/xZjj1AbJ\nfMJnkG1OxGJmX0f18srEZHJtQAe18dliELL3WT1QJpeaZre++n9uwxMYfLw8\n1GKL7PzZgXigtcpO4QkPJqqmmE1YylNNNL2ZLqMKHnzNKFmqdS0lwVz9SVCO\nOJMnglT2Wtavj3Sp7Wu/NOvhUO0poX7CjRjUO1x4W2hwrw6AfornGnQN7Esg\n2X59\r\n=9rKb\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"237092f1d17de1cb0d0a212ece2583e2d948547c","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-05T02:15:27.119Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.12","@balena/jellyfish-logger":"^4.0.27","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^6.0.14"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.3","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.2.2_1644027455691_0.544595891681753","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.2.3-renovate-major-json-schema-org-20612ecd385f24512a22d861807060ed531a52ee":{"name":"@balena/jellyfish-core","version":"14.2.3-renovate-major-json-schema-org-20612ecd385f24512a22d861807060ed531a52ee","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.2.3-renovate-major-json-schema-org-20612ecd385f24512a22d861807060ed531a52ee","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"758aed68075ee91bfabf42860165491e375bfc16","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.2.3-renovate-major-json-schema-org-20612ecd385f24512a22d861807060ed531a52ee.tgz","fileCount":226,"integrity":"sha512-XYMybxzen8oH/ZzthXHuVfpvlxf8SkVOPCmzs/oq7ijNT2qXC0VygNXqE4JYPNQpOnc7PdgYRDA4kqA73ieKNw==","signatures":[{"sig":"MEQCIH25/o/qE+JjrNsbe4rRq+cymNms14Cq0I/LYxXY1QXIAiBnYzJJklCcznpe1WOf+8ts7ZE3OQ/NhP9qlRQ+joHkcg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":840094,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh/d8lCRA9TVsSAnZWagAAT08P/1I14r2njVWW7ySlosCK\nz40mpV3ibG9vvNHb7UUeBfWti8hAdC6MQ2JEx83rs074NxqqsTYj0N1Zompy\nXL82PBsA7v70MjGiLzsQrosB552UHzFo1wkYA9s98v5wTeHGhogzIA04DWXZ\nrw5fuIPsaK2WOAPnQLUhFmZRdK/XhxjPLdd3m04+AubENjJeKRkbgZlZz/8c\nv4ODDEaOgJgJKa9mrTdjdMpzO0PTwRZsZPO0CGdQCdBXkfApXUzqUtCkikr6\nMvVKOWFROGpknxpyiJsvREf/a7QIU5gdXc6CRPocIII8C2BxRsf11xq9N7ZD\nV8kkmqtElbjHWGYeKBe6SHuew+ExnzhIhqGJhYFN9XCYpebw/zjWH5HjohDh\nPwXzWE339Rnr4FGbKwDe7epFkjIUpvG+dzNWzP4XLmN6IS+vZOiYsKztgDHl\nf45R6+bHNASszpXvkVsyu57YSVRnxKCbt/HNGKa7nq2aBkiW52tsVfMG4pch\nXIbEisS125eyc4DwAUbzOjtTmjPY1gELWa6w9z3CcWUTi78CdalNc3MKq/C6\nkBTZWX4DzGb29Uw+F9doUY1ixFp4i76rCHOa5qAoTercp+dR5qm0ae/vwYxD\nya4a9Vjakk0OaEkyvU1djSSGz4M7O+APB88UozcZQwL+Tk7ClXMpqbF0nZlV\nKZkX\r\n=IEp/\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"20612ecd385f24512a22d861807060ed531a52ee","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-05T02:17:21.683Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.12","@balena/jellyfish-logger":"^4.0.27","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^6.0.14"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.3","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.2.3-renovate-major-json-schema-org-20612ecd385f24512a22d861807060ed531a52ee_1644027684859_0.1633692894822898","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.2.3-renovate-internal-patch-c5927df596939279e84a765435c2c3f90ba9d026":{"name":"@balena/jellyfish-core","version":"14.2.3-renovate-internal-patch-c5927df596939279e84a765435c2c3f90ba9d026","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.2.3-renovate-internal-patch-c5927df596939279e84a765435c2c3f90ba9d026","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"cb8046cc4c749d4f6cf33336cdf4b4e463702cee","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.2.3-renovate-internal-patch-c5927df596939279e84a765435c2c3f90ba9d026.tgz","fileCount":226,"integrity":"sha512-wk9kgir+g7o9YpLuMy0jhyMikNnWndCgaC5D63ClUTgNl9MYL+vfOrarE7NEWvhS2xBv5+aXSjQGbDCF9xkEmA==","signatures":[{"sig":"MEUCIQDYr6/zCkxmV4PRqd3xQB7rkLBwBy+oICkvkY7wKji0pwIgBoUUdG++1LCztvt14qTQKfhnL0Fo511uoVgtVFq7OVY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":840066,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh/lyICRA9TVsSAnZWagAAkrsP/3xeSXHPZYGEgtivWR0m\negje4/f6rmqH3K9r6iecurfVzjxYg5KD7JE5qBMkusmRYMo6mo1TPfsEU/3/\nWlha6H2fTcRCWfnBoS0hyWIC6+W0IQRwuDM6qUK+WAHSBiipzCEUAwQG9C1d\nnl8oshllK2i5zpgxYshOyhDwmoaf1jMYBZ+S6EqOagLEkLODWMgj15kc5JA/\nAw3+cbJ/vpQM8kSl259pfMfwhKVwP3gCHOvpfjICSCLUfBoIz7A2qD66ea7P\nOHcatQDpDMRcVinxSmg19Rx/1ffoV6NTFiKgub6rgOk+S57KOKM64FBOgtzT\npH6w401p6k1V9NhURyTPxvOUX7MDEnb7R0e3dAgL5FG+l3xkxI8vTuLeM5Vy\nl86JCuSpnXLo8f/j0FYvjSSfGC3taOTe2oHmqwLPKS6lHm+dkIbzRdcXXJdS\nKpg8l2KKQICGp3BPHo7eUzLEItT3zn8Yy9fslYu4g/Lh+CKKTkLGqrH7wHXp\nNcgr7hCm45VcNEn3VkiWbeFenn+HcLD8RrVEEq4lqaFXfsX26kThCByecZFO\n58qT/AYn/1r84C9PG85wusNfbwR0vcbO0k8vpBrYc9wtRCWSkts+tXpCURtH\na5xOjELc3b7J104Z5fx2Yqfq+9guGLqV5u5DV9DDHTZPielvTz/I7ndENtxC\nzZhW\r\n=8V/L\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c5927df596939279e84a765435c2c3f90ba9d026","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-05T11:12:45.257Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.13","@balena/jellyfish-logger":"^4.0.30","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^6.0.15"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.3","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.2.3-renovate-internal-patch-c5927df596939279e84a765435c2c3f90ba9d026_1644059784028_0.6138209535027148","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.2.3":{"name":"@balena/jellyfish-core","version":"14.2.3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.2.3","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5e2de7b4174c83a5c138bf3d6dd9c606f9a13d02","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.2.3.tgz","fileCount":226,"integrity":"sha512-QArN1CP/qKJtNQX/AU3if+iujcdJtwh+aIPl/CX0W4gWparcoHIeFYBgc/ohmi38sD1ucFdEurt/FmH8p1qetQ==","signatures":[{"sig":"MEUCIQDWptI6/uAT/a4+TJ4TMUHFfgKLp2ORNLIFCzPI9foOBwIgRt2ZjSjKkkyu1kA9kJPLNUdVrwrcmrIP1hgunHjK6og=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":840001,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh/l4sCRA9TVsSAnZWagAAxiwP/114ebppWnfnG651/pYK\nLHLTzHHnRTZbX+1KFan5n/az2Kuy8sHC9vVPVJPq+Q78wNELBA9YYX+ofw2N\nsLQtq26egXIcvu5cEweMYJuawAuWWtQ1hF3B6k1orV6da/IAMAUE/DWTQQHb\n1lRY/1B5kF0Hk4aiIzFybheXZhppBS5dbFTvzU0yap1TwH27sC5plkymTinp\nNma64YkZJy6tYKrNHXgjxE9nnxlnd/6XWW6g8SDttIn9JKeXimIy9Ml+w+Qu\n7pyGQaQYW4zT+aOq1xbSYya0v/8IPPhNSk3XhjGW1l3Iz1ilgGKqjsF1AuGy\nH6AIH5Sv34mLO8MhEWdIemMfjcwrU90Nzdn6KYxWd/1ZsOzXwVxC+yFsc2+1\nI4orCcGbdEAITWgHHri79RSzf5+OnfXpQpAWLfWjzMnrEumJrbhKh7tystIz\n9E13Sb49xCW81ZH0+h3IG4meV6Sf9MxTc852pJ0aO7XsjlByMEiAOMMJb0sv\nquFxCOELUw9mK2yzsV9ekqInJmwncQ7EBprd3Da7GaXvWNlIs/GsRqSw0Wfi\nGNyIDpaBB/kaOi0PEOlZurKtXsTNE8SPkMGnFquVPsicvvnX5GMFT6iUL5H1\nBg3bL2dGGvghGb9O8oAenLqeAB+kKU2XD1lAp63gHCIqvjwFDFb2ZtNEO+AU\n1J2k\r\n=Ukzp\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f5ed75e7f6cc990cbdb55e60fe9d1da56fe581e3","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-05T11:21:29.370Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.13","@balena/jellyfish-logger":"^4.0.30","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^6.0.15"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.3","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.2.3_1644060204029_0.9068220822991957","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.2.4-renovate-major-json-schema-org-5875b27696e9e3199d5151bd1c3d6e738437e800":{"name":"@balena/jellyfish-core","version":"14.2.4-renovate-major-json-schema-org-5875b27696e9e3199d5151bd1c3d6e738437e800","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.2.4-renovate-major-json-schema-org-5875b27696e9e3199d5151bd1c3d6e738437e800","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"364607f03cb0c4cae60584dc5377ca2b36faec09","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.2.4-renovate-major-json-schema-org-5875b27696e9e3199d5151bd1c3d6e738437e800.tgz","fileCount":226,"integrity":"sha512-y8U/bPaPXb0JHyPYeSBXIwhuAB/g1SKIqMvhm7AI8N232zFO4li2BwNIQFg44aAnksVCOrYi8YRVMeNe6XLSZA==","signatures":[{"sig":"MEYCIQCfmI61beMBxjhw3vehWcukEAps2CMUYyzMPv6vfeZ+lQIhAPxVm17RjRUfV+Z1c5RSDBKnqp5/VjzjeuP1TSw+iHxS","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":840158,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh/l8qCRA9TVsSAnZWagAAeZcP/ih1CF/GD3js1T/62MGI\nQ9T1Xr6cDjdDHdnRslao7eN7zDjj2tNRfgrRASKckdlcI3CovlpPbrBjeCkb\nIDQgkFRI9PtIVP1VxZLoqN5Cm/l94YaSP1TAFnhQpLW7f4Q4N+vREt6uG6Jb\nz/2fyRnK6GA5GoHvSwmn8gt++2JhrcOIRUxGssTvEoB1AJ73WImGiaq8hGGT\nkNG9P6nXkLqTs2m1RjKwuntFe085S3iuwDTY0Usk7HGU4b5Gavv/gxSA2ZYf\niKOvQn5Pap5h+gTI2wWDCzLjl5ZFGh4V0+JqC+Nt60sM+N9qkjGcsBuTvlQU\ncfuKu8LWZ3jdwYGn56hUxviOPSRLR/brzDGg1wgvphxOKqEyGjYpy94ePI9e\nRhuqoCtchTnjRSvRTy1xSY16xn+j/R5wAxxgpRgvXGiBgKugc/W4kMwvCRXA\n9wRRs/9ppQ4ONeN/i8OoZRaWvFAkNzx159eB+MQaDg1IumnWRP87H6MS3fv4\n1y3d2Zmcq4H/c0fstsKggo/Xl7yJr31zzrpBr5xGrZ12Lax6oXCpP7o2knOH\nhlaQnKyYse7dn1HXnuzij81gl3E2Z0Y+BTFa3l5QynxxWDQ8SUgFB4vFM1Un\nfYi8zXjMW0OJdXu94sZquDHJOXR/Wymw9CLVCGCVU7pmKBkCw/H0hme6ZYmU\njVMc\r\n=O/61\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"5875b27696e9e3199d5151bd1c3d6e738437e800","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-05T11:23:43.207Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.13","@balena/jellyfish-logger":"^4.0.30","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^6.0.15"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.3","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.2.4-renovate-major-json-schema-org-5875b27696e9e3199d5151bd1c3d6e738437e800_1644060458488_0.6250368092189389","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.2.4-joshbwlng-postgres-df2c183848b9278f609779af7f89c02fb5f0f9dc":{"name":"@balena/jellyfish-core","version":"14.2.4-joshbwlng-postgres-df2c183848b9278f609779af7f89c02fb5f0f9dc","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.2.4-joshbwlng-postgres-df2c183848b9278f609779af7f89c02fb5f0f9dc","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"09ffb6726edf6358825261d99501d80b60a37d12","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.2.4-joshbwlng-postgres-df2c183848b9278f609779af7f89c02fb5f0f9dc.tgz","fileCount":226,"integrity":"sha512-Q+VzWITExfjJzRvrqALQeQpTlpZxwk2QFBZUod4xFLLl3QE4v9Xmg7TB+qyviXiBxW4Yvj/4mMbw3OEvuTtRbQ==","signatures":[{"sig":"MEUCIAgduLuJ1YNuqWHRRQ0CtzROu8xDa7Qhh2QCyI8m6+rhAiEAoVG7jFLCvCkwDbQL0WTPlyiQM6yiMfu1vhQ090gMgho=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":840131,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh/7+UCRA9TVsSAnZWagAAPD8P/1cG2ejC1OBvzm++eM/c\nUG95nrnWUp5ZZ+r/wI741Z3apyqEKH0fRlgW/5MXuIwmcnPO5PF2jOgxHQrI\nNL1p72Z/J0RKGACB4LM1LCnoC03Z6ZmYWFIOzD14JHWurOtlQfzf/vQUhNFH\n8u8SVox9TG889irLvQQR57CiPrxjsZhgAgQwvEh0TOchf5mvB90bceNxJqA4\nIvkkrJFwXxajn4sB5676tIgqOuOupAnswEWEhCoAiLk5qhNMFvuYoZEHthkc\nBX4SPqpymJx1rNt32DqsRvAsFsYwT2tjVVx/06i1wC9RJWT2F61wfUquEk94\nv2PHRLz9nsccjZtPC1hawixu6SwFPtt55n0XoS8A0ehMC9zSsoCTbkGYN/ea\nWIHwLnKmdkMdVRTmSTjG32M5qNTio6HiVn1923n/HpuF4q2K5d4iNq0FE+i3\nxRhbgy+kgfRBVN7MDGgOMwRlOXFUDV+xcyaRAfUtyqdRGUN6A9fa+tLRW4gI\nmib1UXslPHD0K6stjviuylVb+TbPTJzUZ27SA7umvrefXecpQdwBB7bfs43d\n439mx6S3jmFpVEByFaVq0vhdtlINXA5fVsyVQcOM8esJ7EVlcv3aeT5GEipy\nJaMNr+GmolJRgQrEjseGvm0rJeJtDoqbt54mDVKCZKw8lRjvOuqIBDhOo2ac\nzwJL\r\n=IurA\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"df2c183848b9278f609779af7f89c02fb5f0f9dc","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-06T12:28:19.814Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.13","@balena/jellyfish-logger":"^4.0.30","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^6.0.15"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.3","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.2.4-joshbwlng-postgres-df2c183848b9278f609779af7f89c02fb5f0f9dc_1644150676099_0.9736858496867069","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.2.4":{"name":"@balena/jellyfish-core","version":"14.2.4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.2.4","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"303efe41cfec64f9edd344196ed0161a2bacc7ab","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.2.4.tgz","fileCount":226,"integrity":"sha512-LRD8MT8tGftwNmXF1QAfKDzHSH3Z3qROIbxSgO+8KquG5Y4RtO8Bk7FpK/PspFsRfAW5O2mZKupxhvnlFroG6g==","signatures":[{"sig":"MEUCIGrPZd/WHpfd7YsustVXNjjL09HbqwXQ2w8zesQVzgVrAiEA7YDqS40kWEIzZ4KgJbsK/OfsW+6IOFDllCNqOUZymgA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":840071,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh/8FCCRA9TVsSAnZWagAA8RcP/0RtBQ5exXJoP408/HQB\naiKfvhg32CoknEnqgVUxgGLv+vTEuv0NXiIyfb8kBdn0jiGJ0HwBVInc3LTd\nw3ci/nPDp6EiBB0HCQA+ZQjIpa6eMoy02W4DaRh8zxu84cBNZXQoH6aqsQ1T\nHTaEg140a5QXUzmtDnH2J7r6gN4zaY5z++guMFudyXBOuWa1IvNUXWQA8nmT\no7UABD4DPdyKBljD2JsXsr5fQJqvswmALUFsBpR1LRE+JfxctguP3fgl04Wv\nuILQBwasJyW4DKKGbkTEvCKwLM2GDmwIjm0/4EoKRpL+XLokw5HEMvMIuqeR\n6a10DO/wFCQtkQTXVLzQQukZPzMZ07gv4DBXO8PRWwPCZQmbR7Y3jC6s6HlW\n7yf4lqDmxvVjvMyRJ9AEQpRDBgC94sgoqgRxz7DNmpcuSUVIwxEJ7wZZrpw1\nV0eOPDeH1NC28TF4Plbk9FQQgODNMwcGI2YoLZLG1IsvsiJaNEVvJFDIPrRx\npJW/c5ybB+fuzx5YbEj7SlRGlQ8Y+v+2x+Yi42llSyp6lDkQg7t6h77Jq0NB\nDEdQI7Bii/nOl5hLko7r45RffxRXYTUcE7W5NlivDWog8Qe0sBytePU4vFIq\nuCOm4lNjjlqTFwYdiNl0hsIOI2SRExaHCDZQ52Oxx7qLr7JVEDKT8TQ0DYvi\nHowz\r\n=5NWq\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"811724ace9364b12b289ed09e5fd4d71bccd1df9","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-06T12:36:34.173Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.13","@balena/jellyfish-logger":"^4.0.30","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^6.0.15"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.3","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.2.4_1644151106136_0.7577267337394065","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.3.0-joshbwlng-stream-change-07b60ffed64bab940a3723fa8d0c1b073ff60a0b":{"name":"@balena/jellyfish-core","version":"14.3.0-joshbwlng-stream-change-07b60ffed64bab940a3723fa8d0c1b073ff60a0b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.3.0-joshbwlng-stream-change-07b60ffed64bab940a3723fa8d0c1b073ff60a0b","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e322bcd7ef1bc15bf938b231a5e401de8119c658","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.3.0-joshbwlng-stream-change-07b60ffed64bab940a3723fa8d0c1b073ff60a0b.tgz","fileCount":226,"integrity":"sha512-KYMhPpQ3ZVGzjmlpFosN5Y9nq7tsGu1QbNaDwxTKbcad5lJgRh3G6QnIkZM68wOs1WDui3tjDSND3ztIhnKRAg==","signatures":[{"sig":"MEQCIQCdafH0+vjlbPA911ICUud+L6Anz8kVRgo4PVtFuL7JZQIfVbmL6+CHApVMCjfd9ivpOVGX1UEyDMY0lW1zxUU0dg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":840508,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJiAbydCRA9TVsSAnZWagAAirQP/Rz6il/SBlOfBsK7Rc2X\nsrQBKZ9muuRwEJYJs/pJywvUSpAnRJVmOuL9yIlKq9mZm8cOGPWIWA2GXWj3\nnJJOxFaS0OYCtuTZ9ix7RHoQ9yPV4v4nGVjsCPK0QKpySsqaiIvBN7exoQJR\nXcquGDr4pqvbFG6qldu1JVF0PmVv9fy3RmyxOgy4Er37t9gZl0SUCk2H0hcP\natFxfJs+EXUb41BWHSvQTuYjNktuGZUCO3v+85XIRJWWELrCYtsSVXFAlMxq\n0A/dWxmpehlU6NY3u/Xkntgcvs0/fmUtUULEO3r3s+HnpG5h+kjEY1nASIiF\nm+Mcr9zpa5tAeT+T8yX8OXs/hwlV5MLJxZ7yBK/zM8PLnLrY4PQBjYGaFQtW\ngaMazTAYFsnVhXq7Gq+12uQzjvCJ+D48RSCRNnzvd0sxaHYOiKx4d0HDv7Ix\nItKj6+o4ihnGdqLGS1W/QZJR5p7peepnEA6FxSxnERGcuI8cXlYODfrD06aL\n6O4UzuJeQTJQarrca0Ydnq8yvwbRyI1iW7M47nz0Ey+API99YPuy5hp/cMKq\ncT1rHPIMP76D0s4l3K9jmCX/f6W5f8ZExHn4mUEDWN/51XoYPMIFCriRsXCz\nyowAPiu6YD9aWcpjEojPAeQ0Ok3u0wNyGUcNSen/ZUhdV1HMlTZiisgJKFbl\nNP+A\r\n=8Z8i\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"07b60ffed64bab940a3723fa8d0c1b073ff60a0b","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-08T00:40:03.504Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.13","@balena/jellyfish-logger":"^4.0.30","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^6.0.15"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.3","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.3.0-joshbwlng-stream-change-07b60ffed64bab940a3723fa8d0c1b073ff60a0b_1644280989370_0.29108464583886806","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.3.0":{"name":"@balena/jellyfish-core","version":"14.3.0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.3.0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5d064f1733aa2a74721bbb2cfe44616855ffb341","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.3.0.tgz","fileCount":226,"integrity":"sha512-VHFoiul/a1el1mR3teJYPdQonpgWsY/K7YELfNa3bLvgbm8rrr50SP4FzRpsjk0QG2IIfkXeTWR0ZaMCyHVdFg==","signatures":[{"sig":"MEQCIEaUXtJfU9vTsOmWSXV56T5ZNix3cK7xPTpfkgrXaRAxAiBnroeoI1CBnIHZpc/82m4zHVJA5Xr23IQA5jyIb5CIjg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":840443,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJiAwklCRA9TVsSAnZWagAAelEP/3GWJuXWlv6Epnp9l8f7\nQkyHY90qEkmxb1CrlVCQ4GnxpdS1PA1iomldttAmPB108tYG7hzoixRcj1Oi\nxfx1VxdZBP8B3Qgl+tRX3B57mu8mO4SIBeeMlfa8apeAEVfJq73z5LCPL9Iv\neT4otukYrK8QlSDkmx70IeM33jgPOeBEEN8yFglaZ4KoiEUOZ73M2d8UKUzO\n8zTNbDS1Lf9Bh33dpMuUhHORqTmjTNCN3YZ6vPVJXX57l3uHKJiy4MLhT+9v\nF7ZMGrPN+NnFIFMTZk/42TRMAZKFrWQCD13JsqqJlSm7Qm0qqUAw96LumrpT\n7vX3jm/IDCBhGswzw+Tijw521Hk1+jfe2CoFZuiaSQdu+dHRqEqchYbXsiQA\npw39169JqY37kWFP7b/9pKzQQ1e8GKTHqO+f0c/ElXmFIrNSCynBdQfNGs1P\n60SgyIRM/izR/BOmTsJP3ZmiKM+PJNH2RmMGKsQedFGnjErszPdUHkyoB9lU\nAqKEtEchOKQORNigeEc37BZynEKKEaFtCUrYwTQcFD0mVn4DlhEFlwGkRp6i\npVI87nw5trMG8sQ7uE4r05w55XF74zRGLb/63OEH32BwArYOstNM7ZsF6ssq\nBj7vpB5nwP3t+99xVj5ZssU221V5QrA+xaeZq5xBhzBhBgpfzLekZBALby7p\nJYZX\r\n=j5N/\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8c10c4ab20b452ca1c0eae7c4bfecd1143df3084","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-09T00:19:54.599Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.13","@balena/jellyfish-logger":"^4.0.30","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^6.0.15"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.3","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.3.0_1644366117474_0.18108394269734163","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.3.1-renovate-major-json-schema-org-d34b435a7f9b6c5aa990a6b3f745033ab9e44956":{"name":"@balena/jellyfish-core","version":"14.3.1-renovate-major-json-schema-org-d34b435a7f9b6c5aa990a6b3f745033ab9e44956","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.3.1-renovate-major-json-schema-org-d34b435a7f9b6c5aa990a6b3f745033ab9e44956","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6c29e5c39d8f6551927b6f6b9116707c0b1c6659","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.3.1-renovate-major-json-schema-org-d34b435a7f9b6c5aa990a6b3f745033ab9e44956.tgz","fileCount":226,"integrity":"sha512-FlvBx/EWEL87qqpiGf5uH97JnT/f5tKjJ2nI8GXFhwlv8UCDXzowPADA/QoFMXMuc9FhzE+PiX+YRjsCGALIpg==","signatures":[{"sig":"MEUCIQDzOTrWlwKBe7XUytxtoAeLua6djtYyob/wJe13KeFPugIgLVTMjLDNYOWOM7xddeCSVSUmq1WkjalFnhXGG37cSH4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":840600,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJiAwqfCRA9TVsSAnZWagAATPYQAILNRDOorEOR6M1ouUQa\nbEXdWb11bKJXpz0WmlvUGLqv08U50S9ktFp3MvGbUixwvYm1cEa6KYTNpHVT\ny/jv5XKSni0wwIFm4BIMknLqkn6bo+p17fMS8uxGUYKYrjVhXmh/54iPTD0N\nozWTAdNfD6GxwdqeLZ+aWKN6xyRGZevF6bylsq6P+7TkMfchX+YQDE8XpmxJ\npFNCW+eQ9m72IjwUqYEXmrdFQYtOq2xh+1wqcL0dzzcpizHin2GOCsUvv6wB\n/QcZWaW3YQqopXGuTkSFl8AhqiRxTzbFb2iRDiAHGDAEnv4r0Wcqi2Cx5syy\nWRbpbEhrsJV5lo4OA/sIoQ5OLDfQsQC/hpejIKzYFfMff4s/P0DIUe53aDaS\no2L4qu0wBu+XXnRNfxp9JUF03ICoASv5JD/jGJ+uiLvsCiP3+4QiLBHzaA1c\nWE3JMtbFoxHYlIIrVWwRPNrO1/B8pHPku7uaxSIzOMs36v9VCjaNlvoytS3u\njazxKxwZLWjbXkdTSX/ItXvfjaj/NAIuyaBqKYRCSsH9RdBq+CxTW+jJhWS1\nWyD5T32BPeuUQNIOt9nwQFRjy3upLNbX+fGJoWdxnMGoliHuKQ4dMj/PCxxy\nC503d0cHcUQxSdVCwAd32qLPCvczEIl5hnY5Gn4LiFEHXSaXmyNDC56xFsnD\nNrn/\r\n=YfYR\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d34b435a7f9b6c5aa990a6b3f745033ab9e44956","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-09T00:25:08.216Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.13","@balena/jellyfish-logger":"^4.0.30","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^6.0.15"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.3","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.3.1-renovate-major-json-schema-org-d34b435a7f9b6c5aa990a6b3f745033ab9e44956_1644366495510_0.9200677008627187","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.3.1-renovate-resinci-jellyfish-test-1-x-1eac1f2d739794b80ee361f480ae46858cbfad88":{"name":"@balena/jellyfish-core","version":"14.3.1-renovate-resinci-jellyfish-test-1-x-1eac1f2d739794b80ee361f480ae46858cbfad88","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.3.1-renovate-resinci-jellyfish-test-1-x-1eac1f2d739794b80ee361f480ae46858cbfad88","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5f2ccffd3b5ef8feb1ae3e6b966161b2a7b3e5b1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.3.1-renovate-resinci-jellyfish-test-1-x-1eac1f2d739794b80ee361f480ae46858cbfad88.tgz","fileCount":226,"integrity":"sha512-LfvLmbgaqGrzYuG5NEBqoDMU8/rbr1eTKZ6eY9HyOoHdjU+gKaaOWkwM/4g3pxa3fXiIK/jEPRSuNe6QaOmqyQ==","signatures":[{"sig":"MEQCIFM57Rphefd77nkfwkYKCxaUHypOqnerUCOM/JRBJNzvAiBHolfrAfsHfMdIO3dVD5Vm+55x8Q5KAiRVRq+LGDY4dQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":840614,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJiBB7bCRA9TVsSAnZWagAAxnQP/3L/x0xtNmti0MjvnQ4r\nBZuC9IWXobj+YMuJAFot9ZGaucMf+X2wrITNpGUA7JCrfMCsiCq5n/Z+om87\n40LXEZOD2y98sT2Bj+QNQuxXmcSblCqAbwLqkiqgQ+6RYzColO1FgDoS02TL\n/w2o+3Q48lM3XidMs16qr/1V57stXmjb/kmxJPazCvpTul6c1G7r4EtCFX1u\nnqLipYokB/gqD8z8+dxd/s/0ineaklM8tZvDrVU2HLb58HC4hIBWVJiEl4wj\ndTu6OnhDN0TUdB7CN5ug5fSe4p5Dy7D8+42T6kWCERS0MWH/EoACTomg+x9a\nqJLIv91T5OopcOsx1RmsH9ICrvgys7xRMH7vTmQ4giZbaVmt6Gn1xQLHoamm\n1SlTmjUqORSREu3geV38s4FiU+BC3Z1e0u+PwKHlI8DN3VsxkYCN5d0+WrAj\nFkeVmhl+g8Xx6zEQCliuYLyXl2NGoeNaGGQkrqLpR4SmVHxvt7Yp+tD2qx0p\nCubFXWg2183MfXIrwhZtKFSULrTfEhzXSPJogMv7rM4R7iQbgmK5lcZICTvq\nuYH+ocUNh3BKuwhOr7keZmJaJL1ZAtPl6FfrS9zQqc1yVDvyNZYFSKAvUszx\n6Xz4AveEKTmSt/AesGuD+mu1+z/26CtrXt4dArxfqOrxtszkZKdDdEmGyjHa\nAL+N\r\n=wKSS\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"1eac1f2d739794b80ee361f480ae46858cbfad88","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-09T20:04:16.523Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.13","@balena/jellyfish-logger":"^4.0.30","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^6.0.15"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.3","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.3.1-renovate-resinci-jellyfish-test-1-x-1eac1f2d739794b80ee361f480ae46858cbfad88_1644437211759_0.024595747516368505","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.3.1":{"name":"@balena/jellyfish-core","version":"14.3.1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.3.1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b65f3df43e5faded1a0409a61c328a8773293f15","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.3.1.tgz","fileCount":226,"integrity":"sha512-lNaB6U1DmdggXFO2ktH02WYcFPk2nWtnOrr+YguqE/gvTTMfAYWBunU8nnOvac9fXz+4Ri/sI7gCJQwIr97cFw==","signatures":[{"sig":"MEUCIQCs8oau5cOhdHOEsZc7ANHQldAlprD98UMfm8V8BU7iSgIgBXFfVkj24L0AAQ9v8WGy2YtN72hS8YFVhKpJTXzqr/8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":840537,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJiBG3ACRA9TVsSAnZWagAAoBIQAKGbtfF9K8qeGtbv8Umz\nzUFAckkH5kJHcCrx4Pt3YSQn9VTuyR7pV/aGbnyUr8fQd741nVnk5KWqzt8y\n4XI8mtbLUJBfpczbxijrg2U4Plmdi8TJNOwmwx8lfb6dqtbeIz3NSMU5WYSB\npas/MipwK63VKg4R7hUJ+hgXQ4ZRTnoRc6rxoGB4J2aHrVtaloUGD2enRUkB\nviFfXla2xVPAIhX19WQOUONNcrN1DERobDDM+CQN1BSp3QE0YKOueqDsyS8z\nFll+H7RHB7qZYsPoOp3N/+1BOil+0KZ/BKRLeJSUGiskvhrzAsT8NCdc3siW\nHmUN/fEasveZDkbwgWrir0r4zvt6BLqdX9qu1OZaooJw2KiJa3eBD1/RkQFC\nN3oFWBul+rTxw8RjDFgGt0Xwnot53gY1I54ouxpX3Roy20HHYuqY1owSmmUl\nkJGUuoeXHU3JJhaZ7gbyvhey5sjq3+3JkmQ5dlqduw7clf+ICHM97CxbFvEE\nK+lwzkFvtrXrCkhWm5apMhkUZI97hI4nv1a/QPaZhKDDIGbYhDNwgsAdhtU5\n1Sw7xMKq1PGFVavnRFlTcxPc5V568PBrgKmTpD/zmRnUM3pSD2/jHWdtVi1V\n6M+e1ZB4Y5B2eD57aNaynypmuHq3/XQMZyKdziq7yNRWyDEJbaH9QiugAqe9\nUQQb\r\n=xwPX\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"98b017448b283e962c5dfdac0fb790c13e30043e","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-10T01:41:18.268Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.13","@balena/jellyfish-logger":"^4.0.30","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^6.0.15"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.3","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.3.1_1644457408056_0.03776586969025342","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.3.1-renovate-major-json-schema-org-b668240c365f490dad7d52518e660e3ec2f2acbd":{"name":"@balena/jellyfish-core","version":"14.3.1-renovate-major-json-schema-org-b668240c365f490dad7d52518e660e3ec2f2acbd","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.3.1-renovate-major-json-schema-org-b668240c365f490dad7d52518e660e3ec2f2acbd","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5537101284baae27ff89d9386b420bdde70c8f13","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.3.1-renovate-major-json-schema-org-b668240c365f490dad7d52518e660e3ec2f2acbd.tgz","fileCount":226,"integrity":"sha512-NC55S9xipGN2yq2ybPSmTyg/a0syktYWAgGvcXhYBJnQGxSjbDE4NzNITbsa0aSUbBvvE7E6YrRWqtT590TjKw==","signatures":[{"sig":"MEQCIBp+CeZ65R/cnPaUQhqZ6ZJhTHzCE9UV69WR9DJBBtotAiAr/0dq2vYMr2j7HqiRVVEle5edE5k5iSTJjFjhHQ3acg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":840669,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJiBG6GCRA9TVsSAnZWagAA23YP/jXy3e5xP1VR1D/OgTyI\nl3CkfxEn5xoEp+XiY05Uubwm80pV3bZM9bmi0nJyYThDVsZEWNmyVLqsUD5B\nP72mIfnWG4pxz/MoeaUt3ehS5cIF1PwpyLHVjMaaRqSXpscLGFwR5dcs9vNz\no3s45MWeWhtlFGIbtrTtfeYRbNLg1DCb92GIYf1h3fsF5erE5LSO1nBOFbQn\nF0tUDi+9Zm5K3urF5G9NX7PjHi/sUCOaCQ0BsysdVX+tz3ICvKBnKH/oE1SJ\nvvfn/SU7efhKnm0jsamCQ2Qdl+/769VaLTPLK3+chZmzm6YsXeYr58xVce4R\nFiV+PiRBeY0v7lZclhPXLmOfTKInwpZdZrFKnP5RakyiwUCMJzVl6ziMYYZG\n+H8CzMd/urKlIJebjLiD+61dsHCXbW0teJpe5t/WGBQYn4axlBecGdbINUYk\nzPEns19uTi2pqmKUo+uv7kUuGZ+JLy4ptln1V1CrbAqGI2LoqhjHXBFc1SK0\ndGo03AkPyD04VahFVKZMsr2TKzYibvBUKABgsnncnYIWxUtc65AKeEJe9SkG\nc8cwfF48U95tF8Mi16bfRdfADm6u/RtDNJn0HOAQcwu8dsJIy+k78zDWtZ0A\nFooxHzWw1GyQn3+EgJg9Q6rPUAE/7Yds2sTi+Reuowvt738HFE0ow4ycwAPc\nffo+\r\n=uQIy\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b668240c365f490dad7d52518e660e3ec2f2acbd","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-10T01:43:29.496Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.13","@balena/jellyfish-logger":"^4.0.30","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^6.0.15"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.3","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.3.1-renovate-major-json-schema-org-b668240c365f490dad7d52518e660e3ec2f2acbd_1644457606470_0.5621510841478918","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.3.2-renovate-major-json-schema-org-38175ed9c60e4fc7d71332dab3c4a718e36d1c0b":{"name":"@balena/jellyfish-core","version":"14.3.2-renovate-major-json-schema-org-38175ed9c60e4fc7d71332dab3c4a718e36d1c0b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.3.2-renovate-major-json-schema-org-38175ed9c60e4fc7d71332dab3c4a718e36d1c0b","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"cd108585be7f6402c32010f5b386c92fbc7d766b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.3.2-renovate-major-json-schema-org-38175ed9c60e4fc7d71332dab3c4a718e36d1c0b.tgz","fileCount":226,"integrity":"sha512-j6TU+l47gFwzDDvoPa5tJ+nX9cDkKZQbnCW8GPcFiyu6ozCphSOxtC/GhDvdhyuEtL78mj5u3zGtILQoU19V2Q==","signatures":[{"sig":"MEQCIHTnkfmVgfzK3sWhsdUA/q98BrMGmGa9vCl+333OkV9ZAiArGMNehUhhaXwyN9+aCiyCcAcErY9+xcnEkNrLULiqjg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":840694,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJiBN8DCRA9TVsSAnZWagAAQ4EQAIbOLkNrbn3CuU4ZgJzL\n8JizPwtvpMePNYWIBCtbng97xafjIlw0dMGwH+K1YoM9r6LFbufoftQFbKq7\ne03HYH6ftDizVtHvcLB/2dXDLLhYOu3W+7fluwGoCOj4UD8rZRBCjZ1ZRxqv\nt6EB/GnPfxK+yYzc/PlyiNVuqD4hzrj0N4ErlfXOD8uTsbZp7ZvPqWNBhWkQ\ntqIeyYYLH6YUtlH6criOi+R4AM5z8ooUGzd1OJhY2V5nnQriAjay9ah80uvU\nifkEZqHo+PN+q2nVxYY9OpmZuZwXz3yXByirui+oE4b+eKskWRtJkoUD5EfR\n9hABNsxup6WIsuckh1uF7nSF5aUSydwAyB6wpgSH4HJkq6Ko7dwZ9nm25DV7\nWfb27QbaLJzYZOFB/Y4Q3ijhezxw0PPXFqOWs/IgZHNllozOgT1LM/1jmroX\nh1K2V6GVdd13+MKNa1qM8I2NXz87RICPsE/1p+5FykOgOfRg1QP5uLFb4X5e\nHnt4tyUnyEgYvPyyK4toIlazSklG2CNupv8TJ+H8L0HNpkoW/r4aDaBCgrm+\nJb9c7wsuArpyNiCJrt9EzvEEKcXdKkeUMC9/m8BEWCdBdZX2178+fIhKzaQg\n2sJLrUSVrunkvuEUcHgW0bAbLkc6M9dK8Yp28JMaxykUtjB0pSY5T4yxb0go\nnlYI\r\n=GFpK\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"38175ed9c60e4fc7d71332dab3c4a718e36d1c0b","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-10T09:41:58.119Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.13","@balena/jellyfish-logger":"^4.0.30","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^6.0.15"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.3","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.3.2-renovate-major-json-schema-org-38175ed9c60e4fc7d71332dab3c4a718e36d1c0b_1644486403582_0.865854865507049","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.3.2-renovate-resinci-jellyfish-test-1-x-e92e01fd63dcf3189601bf0bfc795d55e93fe114":{"name":"@balena/jellyfish-core","version":"14.3.2-renovate-resinci-jellyfish-test-1-x-e92e01fd63dcf3189601bf0bfc795d55e93fe114","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.3.2-renovate-resinci-jellyfish-test-1-x-e92e01fd63dcf3189601bf0bfc795d55e93fe114","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"cf0e17f852417eb41d531dd346d49894956a9f89","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.3.2-renovate-resinci-jellyfish-test-1-x-e92e01fd63dcf3189601bf0bfc795d55e93fe114.tgz","fileCount":226,"integrity":"sha512-uCKiSXS2TIsRKxAd4bizbOR+jP2LuijKNuwiAfEx9gifUly08c8C9MAe4Wso4m7C5MINjo1one3L9Wq7Ji7XAA==","signatures":[{"sig":"MEUCIQC4hOD/iaTpq3kKn0y6G8yZcEkqqM0TQoOM/W7H/OPsSgIgK76jgX1L+byaNOpWGTSDTFfuI2/srgYE++7+qVgucmY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":840708,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJiBU7SCRA9TVsSAnZWagAAA+MP/iYIUDsRO5Ka3L76T4H1\nuztscWiBIPR9oextysCpm7iUx/jdkWtuv8xZZGlRY9E+taiWcP7jOAznhahR\nFkeT0BmeMeI9mzdEMRJQCTze26Ym+YGIuaXW022uJUErR4eQn19B4nD4NttO\n+IoxtcYYeh/upcIIbIrV9HO8jPhozCIsomSqiw09a3jxSsyuGLhj3oofxSPd\nSSQo+vk2th2/3D+naBj54rxCASbEIhlEcehUZmLtadTjP4cyTwC/DWs6uWvB\nRGjiccPUvhr/MhEDoAXkurHMoY7ianh2SCeO/ITDwskj8jhExD3pdGZqc6wz\nPnz5Ye5IHu1HXLVy1So6SEemPYVB2y7HZqaX4P88rbX9EDNOCBKDG51Cc4MP\n9ugJTtNCqNML+DzLXn3kvGi2m8lu3RyP73K8YBKN++IVY7U67zEesGjOukeJ\n/5uxAouzL9ZHk4GEnrEI8xOz9pIFe3olL2ju87zNXJ5Dm8U9P5K/AzbnTKOg\nwSQVqSLWYQtcEo0uzKcwoLsN8QdiqM1TEfwNkd1RCZimC/pNgqKbzKQuXbhN\nPLSErjTEeyvu2gdPKOqapEwq2vW1b3K/OI9So/Q5bZwveKTqOKMnHSaDG8gc\nZL5d6QzKHHttuCoca4BHeG3yt2X9DRp3nHbjdDEvKiEeZa1o6j+ED/FgoR4l\nVpwr\r\n=MJ0d\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e92e01fd63dcf3189601bf0bfc795d55e93fe114","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-10T17:39:38.081Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.13","@balena/jellyfish-logger":"^4.0.30","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^6.0.15"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.3","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.3.2-renovate-resinci-jellyfish-test-1-x-e92e01fd63dcf3189601bf0bfc795d55e93fe114_1644515025870_0.9696760138879787","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.3.2":{"name":"@balena/jellyfish-core","version":"14.3.2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.3.2","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1d37537b3e5a045e5e25e1d7aa1e8da47cf1dac0","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.3.2.tgz","fileCount":226,"integrity":"sha512-mR/l5rjjG56o6Xh02euPgr9AgKb3b/EdugrSE7BVQ+9oN+IO/uIucFAvZNpjIwl6LopslPx7o0hJxuG6v1MIuA==","signatures":[{"sig":"MEYCIQDYC2Ak6mivTlT+36Z9bzOjkPPDF4vGF7o7QV2JmOQq+QIhAJeH9gDig2Ma+3e9/5BdczJcce+QI2QmN02+YZ3n+35v","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":840631,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJiBlWjCRA9TVsSAnZWagAALa0P+wUCeFhrKKUGUfyPjGim\nSshk9XYM5/FpL6NYMMxN2iNnk/JFzlEfGQdToMwqsumEF2+sbOun+cpOrDcG\ntQDaJ5Wna0PpUkXsPARaSENxzrLsOK2m57J48PxJZhvagL4LI1jyrgOf44nj\n37ezYM+3zCBd3br7HhVbBZFfkllnikrJndpLvbRRjZuJfx6qjJpDBuMNgR+P\noVUR5hkTRbya+rBDRgXB7OiUvVjSqo8w1SBS449RxpafuWZlHrdQKJtTQ/LR\nE1osvLJEkAkl122MqgxV12TfFmcAdvNeaPnU5EQJgyvt7wNR9SlAokHCfqTc\nJZIsWYvovKukWQn6Gfr9NbL2iriobSw5YFcofUL/M6oJynRyGFHzWwHNokYH\n4CBgZ2VPr8MFtjRl0Re+oXVHIt3x9YYYb07uIsPhX0AsOs6uOyMTIIcCZ1Oj\nk5ZdPav/L2hqem+Y8RtPnbCafLwhCqxwVOE0trKbToMXgI7xFK+Z1evUVlXp\nR/wZkraKfNc8U07MO+k57gItXlh+jfnGvprrB9Ce73xZpO6l126Kn6JS3A0Q\n0Qo3IQD52iqN4+9YWkx7B+zmOOURXA4gm2x3DrR/+LKE8+Cq+8/aPUIjvEBG\nENj09Q/u7+1++JBKRR3Qg6Gcx+90/qX3Gqd+cHbX98PYDWmJxXvN3mz9rqyu\nvk4Y\r\n=hKC6\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"71c5e40ed2468fb1b4406a83338c339ffd68e42b","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-11T12:22:57.803Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.13","@balena/jellyfish-logger":"^4.0.30","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^6.0.15"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.3","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.3.2_1644582307253_0.5845216409803851","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.3.3-renovate-major-json-schema-org-2598189d971596afa02cc13ac99dbb366d7390ba":{"name":"@balena/jellyfish-core","version":"14.3.3-renovate-major-json-schema-org-2598189d971596afa02cc13ac99dbb366d7390ba","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.3.3-renovate-major-json-schema-org-2598189d971596afa02cc13ac99dbb366d7390ba","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"49a27b9e8cfd2d59499a143df9d3a564664cbd08","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.3.3-renovate-major-json-schema-org-2598189d971596afa02cc13ac99dbb366d7390ba.tgz","fileCount":226,"integrity":"sha512-BIz4tcLWEOCdbwE0ooR6ZnamOf8IT/7ezSYhRO2z+HHZPgEQSMFdPyx7jg5g7mOK7qcr5nzu9KHFaFuh3sJnSw==","signatures":[{"sig":"MEQCICDR5NR8kdmfZrrFytrG0h/majDkW4zef45clDd+a7vsAiBqPZ/N5ACWpiKAoDbfi9ndKjkULtDl8dQIiOvKGQ6WuQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":840788,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJiBlZ7CRA9TVsSAnZWagAAkWoP/2EKoVFGbixL6lXeyVms\nZEb8V+K1+I3bMtwoVskNRODmQReTRzadahdxFZVuZn5OcDmT5MoFnr/fgVbm\n0S6c0hmdyxT5429Vp6rRqBnOcODKNs61euSISzWrq2cHk3R7zh7/3/NOlf4c\nk/Rr54DYbMgzXh+18uPobU9pcdKNIxl2T5nE/zdcZuWk2jfV+ICxE0DFa9F1\n8lcNSEqF/SUVA2QMtcBqiGH757jBFPNYnOocCwQVz1ahifvl43KYuodUjgYf\nfvzV/o7Il2iywBuFfg+u3iVNwUhtBGKqJIBr2IAYuCXNGW49sN39C7gzNpn8\nZMPJY97Wq5rGFFhdKhzbWvDCX1wTnl1d7TJYTWAqXjJ+1nFuSPv7lMm/nH63\nF7Wa3mH0Og39Iqz1TDwLTKpXCUq2Wz62NkuJ5pXWP6MctecUgfY6QM1OhcSH\nLBD6L+egGDdfh8QT9thqc0XBVW1t8zoj+meYz04Pf8cR0Mbp3wypZp0AXdWi\n72i3P4iT9WFrE56kYK24O4pHz5oSBHpRWhY6EB4yiOz5+nJES7egYtmkrum9\n6D/jQk0MVMZip4BGVCi6YLOvv8g+2lBIymRVfjQI/+tG3mdmp8bn+4SiCe+2\nhvqKTj2txcZHNTiTVnw2FVl6h1iHvJ/mHYTEDJMm0z4ND4KW0v4NsCrorx7t\nHZR3\r\n=IEAL\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2598189d971596afa02cc13ac99dbb366d7390ba","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-11T12:25:44.210Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.13","@balena/jellyfish-logger":"^4.0.30","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^6.0.15"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.3","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.3.3-renovate-major-json-schema-org-2598189d971596afa02cc13ac99dbb366d7390ba_1644582523543_0.5427348572575976","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.3.3-renovate-balena-jellyfish-environment-6-x-d15ff577f48c8b1de3766944bf00c4b9f0157ab4":{"name":"@balena/jellyfish-core","version":"14.3.3-renovate-balena-jellyfish-environment-6-x-d15ff577f48c8b1de3766944bf00c4b9f0157ab4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.3.3-renovate-balena-jellyfish-environment-6-x-d15ff577f48c8b1de3766944bf00c4b9f0157ab4","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6ad65a3469d76ec3bc19543e3f04bd6a95bd2acd","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.3.3-renovate-balena-jellyfish-environment-6-x-d15ff577f48c8b1de3766944bf00c4b9f0157ab4.tgz","fileCount":226,"integrity":"sha512-TxMplcDBG/pKoeIk1xKTO+Mm3GsljPevRWdLmEahjRk4MvpOC7RRpEyvnTd2rtZ7XKfAT3pJXmlzDN9Im4W0oQ==","signatures":[{"sig":"MEUCIQCWPtkJhmfK54JgfZMOw7voic88cKZFCYThWFo18t+vPAIgFvhe0Z/gsO1rSrJ/VqPft/v9KXn9hoCZAN8P/vrXRUY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":840813,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJiCJTACRA9TVsSAnZWagAAOOgP/1NiSq0YJbDz73zo6+4t\niS/d60M/0+emWi8QKcO2tEutLsS1zwdwSkfubf0H7rEVWU526N8vBVlJotCj\n/TKFlm6OZcVah7kgD5fGk3fE4PbzWPF/6McVSH9m1mcq24PrJlXXOrpdjC8K\ngCaYDbAXx2zn9KXgHOzHuhEhCDtGQ25II1OkGD0F6+3OzZlN4/LYjGTUD1K/\nl3+U+b7jh1Au5yMUo+doy82mcGZAwM/LEF3DL8lpAJ/7qwSwfW7qMR/eh3sp\nCayTVKrOwcGF76kp63lZx0EH/eVmhZp/Fbcfjoi9VBhKuj6OQ/wj7o7d4tZf\niRrIqOXr0pehlVdsSQ7g+9Agi88nTK7nA2W+B9jnnd5epbpP5xUTCO34eLRc\nsfWy0bHoD3mfHzzsBriwIdSXLXOyr4v1Rl0a8VG3jsy4F7Z25qmWgi8VphpI\nPTBdc9g6bb5VovBqmDg/XsF+e7M0JR7dRGl/66uxvquMcxiIGS/RuiuJScVa\nu2hSV78lSzyeZ1KSqJeqfDoDAQJ+bYHMVQPcIxOgKGSn43hGpH8e4bO9Oell\nwRFXoMvYsttYqqFH7CYTGmP9iP3OFU2APSc9DNAyyP2/dwu0raRk/4/dsftC\nnMh19rYU63fBwNTV4pK3sYfNVfbPGDWnbfNir/T3uahGVRyGeGLZ9+ElqGVQ\nFhTF\r\n=hauc\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d15ff577f48c8b1de3766944bf00c4b9f0157ab4","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-13T05:15:24.793Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.13","@balena/jellyfish-logger":"^4.0.30","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^6.1.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.4.7","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.3","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.3.3-renovate-balena-jellyfish-environment-6-x-d15ff577f48c8b1de3766944bf00c4b9f0157ab4_1644729536195_0.901161953007618","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.3.3-renovate-external-non-major-95c1ced0b50c16851962b6e2a085c11bedd8d449":{"name":"@balena/jellyfish-core","version":"14.3.3-renovate-external-non-major-95c1ced0b50c16851962b6e2a085c11bedd8d449","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.3.3-renovate-external-non-major-95c1ced0b50c16851962b6e2a085c11bedd8d449","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"09a0cbad6d54d8427f5f6d2e1c3a7a115f7a0b0b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.3.3-renovate-external-non-major-95c1ced0b50c16851962b6e2a085c11bedd8d449.tgz","fileCount":226,"integrity":"sha512-12X5exLvu8fFsK6wL17WlK8UXvO3P7Cbdtnwjcjo7hZEVgVAvWUFpaCNwzcJ9f+DUEzlk7R4Zz3YmiuHF7IxiA==","signatures":[{"sig":"MEUCIHEoPOgrI0KB6C1/r7eGeD8jXEzWmCNtFk6XEooO5hzwAiEA8K1ZhQpD5KTXzwA4/jxy/8NzbBRH8/tV3qfQPTeUbhQ=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":840768,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJiCUkFCRA9TVsSAnZWagAApx8P/3x2lZIJFY7WerF7VT5b\nf9O28hl+8CnM6Wkujb7VveCn2iYpRlUnq5VyruhQV2uDtwY/EDkRErjk5Ve8\niIVgdMxVmfCiTONE6kWZz4TdBymsRkMX2CFCJDIJ/vAxlMgAz2H7xGPGyyHT\nPUFZ96/kXQx3jL2B2t/spGx180VJ1/Y2Tf5A+gn8QR9+68VSGJ3sFw/ysbWq\ntrKFZ5Ea1Ijg3xm8FNkbp3G+gbhWTBGuzxdG28QvX5z7HwmIbKKsOfhk8LMi\nE4oDtvCtLuZHWKBoqztNEbYThb+VILKc8WHzCnkBtjqZfIntveIAO9rc6c7e\n7hpHc20l1YIgTOgbgfpVGIUPwRALg97Ejj5K8TZY+C9J7AqqnvO6P6qXlRLI\nIkOdgO2cshdg688C/s7wC6XuxhgfJgC82AGNYhz0Z/7TCTBf5QriIQPqPFtS\nsf3czlUUtqy88x1znRDrtyGe1gtlz7zEPpOOy6q/2Q3R1Rel3Ds512aNGiQt\nMFEOgUtiWcnyC0F8wcd8B8C2uOJaweAXLaYyMOcFGi/ekjQukyMpjr3bNG0x\nFMFey0Pf4i+TgThrnx/Y6T3YBlfiS5uzHAY5kt4at/3XDyMoW964fCTPgfEp\nPhEs0Tep+qLUuPQaIc/+qHHgqKX5ViydsIuYOKHFCl11JDe1IDeYR4MWYw+i\nh9+a\r\n=HIQp\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"95c1ced0b50c16851962b6e2a085c11bedd8d449","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-13T18:04:49.350Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.13","@balena/jellyfish-logger":"^4.0.30","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^6.0.15"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.3.3-renovate-external-non-major-95c1ced0b50c16851962b6e2a085c11bedd8d449_1644775685282_0.2871182063577138","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.3.3":{"name":"@balena/jellyfish-core","version":"14.3.3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.3.3","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"df54e1d0bf27a4f8fa1510d964d403ff6c60a182","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.3.3.tgz","fileCount":226,"integrity":"sha512-ZnVdc0mFmThICPmYeL57oTbZuu06CRnruTrIb8GisJkeD6FIXYkxv75muzW0D4gS3VDlQP1xF5tXeIdin3SSkQ==","signatures":[{"sig":"MEYCIQD0zSNeX55ARyMBK6hayccdcsjRZG7a9ZUu4Qalxm21HQIhAOC4w9cOaR9NkVTQrKEMWxUj24l58ihlktgh70BxrsaZ","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":840699,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJiCUoYCRA9TVsSAnZWagAAbDMQAJKu69lsWBjy2PL8qaUC\n2ueJ1mKSPn13PSWB6Qm1TZxksN/harqhb95b0U0L0Z8nU44WsMYSXzokMg5X\nfTzZFYqPP4RvFYH2ktTpp7G0TWsicDHEmr8t/PLNt8gQVdRR9kkQh3B4yXWZ\n2NiXBHWq9Oxp7CdmzmSkg7flHbTLER58bstRaBdB0FNul3Bz4XaDEY0A2Oc5\n47XE0jrr4NBwPu+4D5pOCOrn9HQ4U2mulHx2PAte4dExcr0whH2ENfVsRN9f\nlD43i3cuLyC3n+1JlezdsE6gCJGdmnzT2sPF3+Q6XF44ZLpE0kuKdqdxjj5o\n6HUbA64sAuPGwi3rtbes+0nDvS2Hb30svUBiVVC0V3UZR5zorv4mHJ/KMaO3\nv7H2SoO8S3+3LoiBSdQ0MgcENuPgDkwdnkGDVrqBJOaYKRqCJzbtOUEECSFv\nzsv4Ett74A0bU9jw7wyeiUqdharp7JV5sOCk1FnAChhuDHWaBak4/DGC/nZv\n897Z4vTScEZ5l64wxxWOk+derSXoRyaBdllUMNkbZ9ADIzDVn0sxjOu28U+G\nnBELpfcawAfl0a2l8PgfoZj3aiwiYIU5u6DSO/xvFO1uXNIHZyd9momTQYiC\nFMsijAYzbxdUHzIOAjbEMKCaxic6TUF7WK3GWLWO5eDfjR3y2qkscQiqUsTT\nNQR3\r\n=fhIW\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6b987feb5ded4f8713660ea2177c8416060b6e28","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-13T18:10:24.509Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.13","@balena/jellyfish-logger":"^4.0.30","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^6.0.15"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.3.3_1644775960497_0.1117822606538108","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.3.3-renovate-balena-jellyfish-environment-6-x-4e1565c1517a052462fefd7f98cf4bd9c8046243":{"name":"@balena/jellyfish-core","version":"14.3.3-renovate-balena-jellyfish-environment-6-x-4e1565c1517a052462fefd7f98cf4bd9c8046243","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.3.3-renovate-balena-jellyfish-environment-6-x-4e1565c1517a052462fefd7f98cf4bd9c8046243","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f56252fd6671d822f9d5983f2655ae96f3eb2f47","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.3.3-renovate-balena-jellyfish-environment-6-x-4e1565c1517a052462fefd7f98cf4bd9c8046243.tgz","fileCount":226,"integrity":"sha512-WX/b5eND6vAb+QLpSr5osqT55hnccbDC5Nu6Ku/38zs7EqknYWfIcOqD/L7Q2cuwuzGncY5bVJ9v/Z95t00O/w==","signatures":[{"sig":"MEUCIQCz04lxVHaTACqbq+IcDZOwk9phQJw/YOI9Voyr/wiORAIgWF1o3gscz0BHreVnhSa6lSsm3zSxxxJSEMgOWYMfgXk=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":840856,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJiCUqXCRA9TVsSAnZWagAABUQP/1wIkg0ohoh7au1/a9fC\n9JVlicUUec6Z5pCRUm6dWpqcQUbyUXeM88ab9wrRjabxAw4rWR4oQTp2rEGH\nppB9gq0vV1o6u9Oa/siJ6801MSiLt2cF8v+qXO7R67sH2vDxw9Fx88qEw3oQ\nZuGvf5tbQTVzFXtO8YUzKEuXcjd7tWAoE/qmYrkbzI0zxVqGybVa0ca+rbjl\nLiHi9saMWku8UUAIEigklHqZCzeobei3nSFUqzgYrYdT3MAKdqDk88ueg2aB\ncAphJu+wJ3ybvl5tpF9/ySX61X+JWD+V70l+oG4/pSTaQ7GFI0i1VKWdPr2j\nRA/5COZeCXlAPjZ6lyDCUbKQbZQJoy899nWzIf8dR7nSVF8JahIyrNLJqvNs\n0KVJd1GcqOtzM1JYiOYD9JwNEBoI2lxHhmXOvT3VmUU+ZZTxPA1JptJN6O/x\nH5S250yvKxr6GUClVfiFspR/47qze6bvwqNrmQc0tIaR1Vn2bCgVTPxgywPp\njh6dMQJcud8U8bogw+Ujh+LgLDvfvwRW57S/l49hDRfUwQqzQ6s+jpmsjtIO\nyRXpYEcmaplJz0jn6+q5N9r9oubatj1dHE+Ys8VRfWSi95rBVEmFbLPt2G5m\nVw95xHOZgqJW7h+/x+TlNCIB9y+x09cinipcXdbF0Z8joEOJXM+HSNDbhBg2\n4YyV\r\n=tIii\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4e1565c1517a052462fefd7f98cf4bd9c8046243","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-13T18:11:10.224Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.13","@balena/jellyfish-logger":"^4.0.30","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^6.1.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.3.3-renovate-balena-jellyfish-environment-6-x-4e1565c1517a052462fefd7f98cf4bd9c8046243_1644776086811_0.9262132614277041","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.3.4-renovate-balena-jellyfish-environment-6-x-4e5ab76e4b2759d5dac51137472b2890a86a27c4":{"name":"@balena/jellyfish-core","version":"14.3.4-renovate-balena-jellyfish-environment-6-x-4e5ab76e4b2759d5dac51137472b2890a86a27c4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.3.4-renovate-balena-jellyfish-environment-6-x-4e5ab76e4b2759d5dac51137472b2890a86a27c4","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"7361dd07f597de15228265e5dc82f087263500f5","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.3.4-renovate-balena-jellyfish-environment-6-x-4e5ab76e4b2759d5dac51137472b2890a86a27c4.tgz","fileCount":226,"integrity":"sha512-a5bM0BKeV4V7FGEt1qUpWbzI6N+/6nywlr5qeM3GKimOPA/TZjiITVpwGqeRlCvoWdyde1Vb5MM/X8gfSYkrtg==","signatures":[{"sig":"MEUCIQCQSudLXEeyQp1HOE6sjmwXxKNPvuKSv0fgnxCuZUqqfQIgI07itKH5CNYVVqUhYOe9clk55k8OqpGZ2owg/ZH708s=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":840881,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJiCUs1CRA9TVsSAnZWagAAYpsP/1Vu8T95JOAXoR5Rq7se\naQygEve25lfqS8ezD0n2tIH7qrp7YlNa4UuH+TNz829E4w/uku5Bi64sKmM6\nTPVjonplv0zCA7mPqr/ZwF5gOYzqNTvjkctecm2tXq7/MmjkyJV8YqXLg83y\n7sNqjc0PmJ6g7pYJuM7HRxoPM+dFGqPe737VDmk/+xFqx2BYGmk75m6/tjdP\nD420x127WTL5Hc4WpIo73AWCLySkkHtl4cL+DS7WYesdqb324lj/LfgfcQlU\nxUb+jvPHSMoHJwvbM3aOQth6FB08v9sENKqMmLClT4wJNl5hk8AP4tgPo+Ee\nvtMwdbC3eksk+C2jpTzWux6Uxv4Sxh1FgNOlUB1LRnaAajl6jMUq4cY3WtQP\nO4Ok2LdtzQkZATHRQiaR/XexdbLcrZY7SME1uWXc9dZU35NSrllDKGvOiSed\nian9GJvEPrqSGJLika+sLYcpyfim4MbEfSNxz9jdtLCht93dJ8v1RkFiodDw\nWpsu/OGCu6C2g32Gtl+wyMgBzUmhYXfRp2TZAxp8tILWo6kiyPC39P723znM\nZeMF8TiD/+fnvhu5gdCqgBouU8yXaURM17l8UdOW0uk07JWRr4ZVEZxO+7Mi\n0FNJRl1uTpNMoQC/JTIT3fGt+NfhfDf5SKBm1Bz3Q7FRdQsZ9Sqa1JpEHMDn\nIpcE\r\n=cs2t\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4e5ab76e4b2759d5dac51137472b2890a86a27c4","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-13T18:14:03.043Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.13","@balena/jellyfish-logger":"^4.0.30","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^6.1.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.3.4-renovate-balena-jellyfish-environment-6-x-4e5ab76e4b2759d5dac51137472b2890a86a27c4_1644776245430_0.12088481316798005","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.3.4-renovate-internal-patch-d29d2a4d70c446f594873868e014085091458786":{"name":"@balena/jellyfish-core","version":"14.3.4-renovate-internal-patch-d29d2a4d70c446f594873868e014085091458786","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.3.4-renovate-internal-patch-d29d2a4d70c446f594873868e014085091458786","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"49489d8ca67fa3ec0bfa713a61eb698d0e9bffb2","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.3.4-renovate-internal-patch-d29d2a4d70c446f594873868e014085091458786.tgz","fileCount":226,"integrity":"sha512-RrpwQFeVzGy8jZmSwrF0EXH9sUPWutyVTKBtSAu/kvOcagcRWNCKX+vfcl/qDyEZLdVnXBm9IT6MEjNmiRt0Ng==","signatures":[{"sig":"MEUCIQCh2Xv6aImKvOlCJkpKWwC9FjB3eAgrhatrvhWJH0aRwQIgZMc6qa8kzTWUI4ogCN1+uAwzWt8au+n9Ha53Xlcwbyo=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":840828,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJiCWh+CRA9TVsSAnZWagAAwEQP/2uaYpTZbMglmdQ9A8vG\n6XEoKMseVp6SLDeczZ5cQQcc1QZM7gIwO9OD7xzDkxK5sHoYLwGeNotHGrPn\n/sTKjJIE+kgUYi9Kl5OQ/lklVHFjbPKVp4Gu+9fYn+pUsQUYpCFaOrnQTHnQ\nLeHK/ehMCpeTESYcCgjjrW6p7XqhGqhVG6Aak+a4NCV9SD39WFgPu/BVUeF9\njxIXwRFgN+QvUsCU2DQWqgfouqu+YuBrOD+oYzPvNsl0ywiusLGC7o6PN1x1\n9gy1Lt5v3Bw5MCmgzDlMP+mQQnsR0YhDWIrr8S5tWlBR6H9PUTPEs4OPvs9J\nPq18QmJCAuqRiBIQ3gCATJcU9mISmziEBW5X+ymIQV5RNgYgKO1jBepTWaQc\nL7d8JUxFuzVP1Xrj+irNyRCQIbLY+E5qRxhMSGFiyllkmraQEuUtSsSproix\ncA+8Gg6vMOFbRtY7EjOH+Qgc3kbb81egcfD+tDbJZd/7yYzQ0d9wbSILhm/e\nnBlMQoA6cJxiq3mY2VulEu/wDkQRB+SgPakkslXAVn6KKsylgUhz8bh/5v64\np14Ipg5OiuNX+roupQqwjG6ujFiLWcfj9jqDG/uptqxLFoleVzFJOtHcyzJj\n45OLUqj4pduJJgRWQFz8tr4AEslsejlFd7BIfbTyNR9BlJVhhBjK+XMwgwGV\nlW/G\r\n=ZrE0\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d29d2a4d70c446f594873868e014085091458786","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-13T20:19:31.258Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.14","@balena/jellyfish-logger":"^4.0.34","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^6.0.15"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.3.4-renovate-internal-patch-d29d2a4d70c446f594873868e014085091458786_1644783742486_0.13232181559925138","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.3.4":{"name":"@balena/jellyfish-core","version":"14.3.4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.3.4","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"22b7cc3241bfa833a005e1ebacf04da40f251298","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.3.4.tgz","fileCount":226,"integrity":"sha512-+edqgji4ewQyILGvvdfcDq0vaupiaNiPb/ysI0Qg4vo16+HlLOXNrb7lpzt+l4mpY40snaVU44hKYqVDqYKYew==","signatures":[{"sig":"MEUCIGH1+s7ndaMtaYu1thmeup4zGI0OgHNmKU87QMNrSgM1AiEAkjfwhEiQ1XGxS7R4/UhJ37ioHgzsIgWW73g5u6qI4No=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":840763,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJiCWl1CRA9TVsSAnZWagAA6N0P/iUYbu4lDqv+VD6OGEZ0\nYDOYuJvico86jDIIOLXp2gwroAYacYNcNT6NHJJH5dKhcp4f2tTasZKVGmCt\nBkY0+hvdjtmJaLL4HcGcZWN1HOIHGZs5ymRMqNeQhjXqpKn+kPonHPOugRVF\ntJwpvIQfBA79Be0r9Vc2GIz4AbKNz3hh5UGcCTO9pQOgA8Qs0wtrmtw29uA6\n6GRAZTSe65L92qzIhim6Sn7OTYlYkxu6ZquLK72xfn7DlNz0PuvblDZC23+H\nLJaT/HCFYYxuhuVzFLxFct89VRlv06BQfoBlBSSEsxClZqf7SmKi/R8c2TC1\nu6C4v2iAnU98RMqJqfOU4WDfO7ZXdHtnOTXyn2NF/z2Md0o9G1CXMJJjVyVJ\n70//7EjltNB44702qDiGWW4rws1gYgLHURfBvicDhFnBWum41iPztKw3l+hu\n5GN0mnsI5ePKy896+0aEXOm4uzDdFKoATB0si1mxqJfhxRx8J3vgC0GipE2H\nBMIXhhEbABJemXD8hwNoKFtoNhJuk05EruSgzp6M73ipR+o1MsHeXTW+7sMp\n+8AxBchVL9rW3fMzrrzxVHzJjpGfh+8efeOCKxJFzwVF+wPPoGgTijkVB+Hk\n6YaT/9pC3Hpz2CoVbfxXUBC5/NM0ZrNz+FJ40l1v+/oSjjBLVtzVz0qLdw0T\nN1hR\r\n=lCzZ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"67c88d90c291c106ca58dc054bc46dbbefe0757c","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-13T20:24:30.925Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.14","@balena/jellyfish-logger":"^4.0.34","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^6.0.15"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.3.4_1644783989349_0.36340956091748966","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.3.5-renovate-balena-jellyfish-environment-6-x-e592e2b23559a7fe2d794fff4692d83fbee07df4":{"name":"@balena/jellyfish-core","version":"14.3.5-renovate-balena-jellyfish-environment-6-x-e592e2b23559a7fe2d794fff4692d83fbee07df4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.3.5-renovate-balena-jellyfish-environment-6-x-e592e2b23559a7fe2d794fff4692d83fbee07df4","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e14a9fcdac1f76dbcd67663d9e03e9d518897619","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.3.5-renovate-balena-jellyfish-environment-6-x-e592e2b23559a7fe2d794fff4692d83fbee07df4.tgz","fileCount":226,"integrity":"sha512-+CFfhg8oY4w7PrsKcd2c1wJTYnSVJ85RFlf7FRzAUM+/w2YA8OTtFJcCgNHjB5j0/tpgfN7cBjm3WC0CiXWZag==","signatures":[{"sig":"MEUCIDSE7KLdpS6rqJD4TwGVwDirGrGrrQnsXFs0ZBeXGRe7AiEAq7CtZejRhRReMrQ6K0A8v8Mm9aEqIlrcf4cm05YuVDU=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":840945,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJiCWo5CRA9TVsSAnZWagAAFDYP/R3dSZioxqswd39BE1bM\nGDMEMO+LTzRNV7goC0wvrZGfTXz0t4YpWHS4mZrtlGwmzz4QjpKYCyNtd/kf\n/+3+Ld9pG7iBU8nHgnAmVDgTTv7PlS6VDDXaTCdMqv4yNhX048GpNHRpJUp8\nczVKa0P4jgdnU6LtRUn5zA9UNO80cf0FDV5t83XgvHyiLbQWGwBJgI5bWRzv\naAj4+LeVTi7oUBJv6LQcn0gSbmts5ugpWbqe0Zq5cAqXuPbyo2mymkIUFNei\nbRSwKM04cZk0HaSyNG9rf5d1x4owYTnfCCpPrE4pxpUlu/zs6Wc8k8FLcFXT\nFAMWW2ZvN0cB8p8jYGrEkCUpgrDh7zqpt9vWoPLJlK2I3dlz27XjkKgOvLsa\n6wyGJ90BP2hrnKcnRUQDtnPV2uct4N4rIYojm6icOjcPnUJj4Ch8pilAS44I\nRFeGDLRa7xOJ/Hha93xJ1IAaNT4EkJSUWQdra+2Qke8ta5jSx6TuBJ+JQUc0\nqiSs8HDiV68jqBSs7IZxFZkpg6oM9R84/Ftp2ApZK7T7e6tXPdbrg1sm2wRd\n5iX1dRoZ6hF1YXrTpb6TYs6UVmmQTwX+7+naEMK2tM61pSCSv1HT6D965QWA\nUNw6whcx+SKIL9v6KuEDr5x1l29xSw5EgtKHwLuoIiafEA19V9+NIfXpKgDd\nB7cL\r\n=pEYC\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e592e2b23559a7fe2d794fff4692d83fbee07df4","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest lib","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-13T20:26:16.976Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.14","@balena/jellyfish-logger":"^4.0.34","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^6.1.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.3.5-renovate-balena-jellyfish-environment-6-x-e592e2b23559a7fe2d794fff4692d83fbee07df4_1644784185168_0.10089504966309115","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.3.5-move-unit-e89be343de790f0f03e0210acb36675fec58d337":{"name":"@balena/jellyfish-core","version":"14.3.5-move-unit-e89be343de790f0f03e0210acb36675fec58d337","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.3.5-move-unit-e89be343de790f0f03e0210acb36675fec58d337","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"004dd8eaf1fb57d0b04ff141f4d4f47a71fbca85","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.3.5-move-unit-e89be343de790f0f03e0210acb36675fec58d337.tgz","fileCount":226,"integrity":"sha512-8GhBKrELsIe95sL1Ig0uz5YLPcWT9H9i3hCILEeVOpuVav83dLL+ZCYKJnhk/cMTn3mrIM/e4VtVUr92M3gzsw==","signatures":[{"sig":"MEYCIQCwIVs8lVH0DNPEGOiYofFI6trRuJVhFvSstept/9S7NAIhAOYGP6zWDg0XCtdez1nsTcEXWVtuO7ixViJiiLfYiKV0","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":840908,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJiCkSRCRA9TVsSAnZWagAA8jYP/3LcajG1KzyP1CAM59Ar\nmppICOTngwyxQLcj/r0vu8HwPd4FensfAb4/F4wl3vTUXELnX1UTwlrNMV/6\nHSDvacAC4sWAlCkws3N0Y00C7lov3zYM6rVcQXMCjEIa58wtkJXpZLVLpIS1\nIbiGlRiq6mSmEnZaddqmPg4Kj8n3geVhRJk56u2hlRFxMQMsMkH0MSw/zT+q\nQrLWzLcPu5jy2UfqIKbJaSGM/W8FU5AJpEOG1eG9XsAS4VJmzovi6EcEeYlT\nkrSKfxe5Q3SQr5ahawh8DcWH40yM6jRLomi8B8hw06Ctlv5p2hHWFMF808RM\n/AyQ96gIKu7mOE63+kvSdMIP3T0C2mRZFjUXTq5MOePT3NYv0IoMTijArJuG\nBbDxK8IXXJQGTkP39e0DOEazC4p3TWVVagtZ60HJxbNzQv9wAQ6wIdbR6m3x\nfn9vKqBxxdCdVFNMg3suIF/QI4ULk7MJwdAkNiMy4rrJJZkXEiPf3yuk+9A6\nwYRibxYiIqc2L12VILy+HW6sxUkysCtMF3lmVdcV5R8xvCH/SWsaGuCKQ+N3\nI1jHcOhHy9YJ2qtBSDNsydW/Z/ocoKM+OGrT/NtiBMtaAdDHQROyyuLnz+rT\n15dDycE7b7uuKfFYqOkA6I1bUgiR171BwNnOiIe5bAM0TZLn8xD2fBrSlvPQ\nFit7\r\n=jJac\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e89be343de790f0f03e0210acb36675fec58d337","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-14T11:58:17.708Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.14","@balena/jellyfish-logger":"^4.0.34","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^6.0.15"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.3.5-move-unit-e89be343de790f0f03e0210acb36675fec58d337_1644840080791_0.17507855723609578","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.3.5":{"name":"@balena/jellyfish-core","version":"14.3.5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.3.5","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ed8d0826dd3c2abf8d23ef8427ffde3aa0ac6d43","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.3.5.tgz","fileCount":226,"integrity":"sha512-1BxXa4lmDfpatjt2Zs0vAPtGW/FpqKLgWrFcDOsVPmHr4m4jToRQX/rUZ0EtIN0V57853HnQxCkkQ8ekTOZ93A==","signatures":[{"sig":"MEUCIQDOMj1RThTqi1uzyuq8hYgN08r8nR3lmPP7nsQdeLyB5AIgS8hzJTxHRmOIXrqjr2ZHOJQZd43x8GApOVGUMbr2vso=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":840857,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJiCkXICRA9TVsSAnZWagAAnSYP/jn/fADfUbudJS22W1f9\ngJtsAui/UZDLeBSDxqyyRKWSJ8wfFv33xZ7QfKvFFXIKEz073gPJa1MOGGHc\nj0tGMBvyo8u+ERl6nBa6Py66vd/gxequWCxq2q+0SUy7tF2r1FOihaJx7CzY\nYl3zCO0QsFVuB0/ywNhIrYaGFIclO5arL2TGPuUvGLR0aynnzKKx9tayVGCF\n72aKS566N6oFf+Z5Q4u/TkitBAyPm8fM2JWezHuKHs3dG6s2sCqCpH4GhV3Y\nQhAIFHPFljhx64ud4RK0HO7EbJwjRGofY85sFrY9FN65Xm44965lVW871CdO\nrqDP2xIqyb9tO8XyTbMC48vTvt0laKQsFfBSxzBnHtmSgbSfhzLxolSHuv7W\nwpjK/kzUaEcy9xxYYEuyB0M50lyGtEYngM0QKb1k4HxHDNJA4YTYifDnGj0N\nDnfizV0q2tBSQmbVVWa9qQWwxmiEdjqtdzEtJDGRfPRZzdYCnTGINuD8+9Yd\nGGChiWzGm6hsZwVyjlJ01we2+ZoTbUvC8pOaVR/GeL501byNzYIXQxaK437P\nRvRHsHRWe9RkxDHhMUKrWlDjsXJ7eIUVbVQ4d6DZwuYLsq/JNgwI7JhxjHg6\nrT/13JakY/iy0r1H5k017AtuRtVGbD/4u2f9KRvNbpyqIWY2cMhTzw0S1bqX\nltRF\r\n=IEep\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"5b02a5f9caff6f7b6775c8151bbc2b7c12de66c7","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-14T12:04:15.054Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.14","@balena/jellyfish-logger":"^4.0.34","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^6.0.15"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.3.5_1644840392176_0.11132272394926379","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.3.5-renovate-balena-jellyfish-environment-6-x-7879f8a166c6e32fa36f7a5a81bf2ed47ff32d3c":{"name":"@balena/jellyfish-core","version":"14.3.5-renovate-balena-jellyfish-environment-6-x-7879f8a166c6e32fa36f7a5a81bf2ed47ff32d3c","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.3.5-renovate-balena-jellyfish-environment-6-x-7879f8a166c6e32fa36f7a5a81bf2ed47ff32d3c","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9a9b73c8e684b2a55ed8a65e1b55dcf84c137abd","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.3.5-renovate-balena-jellyfish-environment-6-x-7879f8a166c6e32fa36f7a5a81bf2ed47ff32d3c.tgz","fileCount":226,"integrity":"sha512-wS1vR1lFTLwyiEiIcUAUWqlZyFCOHlm9fsQ9+4NAH0PHtkM2VQ3qceE3/U6B2UykBPy5Sl9lGPmULf/aoV/RKg==","signatures":[{"sig":"MEQCIAjmbVO1GgkmQiIZ1okraqllbk6JY8Z0y7Tn/uRY1HIQAiB64SVI2VTWgEeFKBfLFeK2vQqI+ypUvkGdnEvxpjpzrQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":841014,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJiCkZVCRA9TVsSAnZWagAAuW8P/RwECUup44vUCfULmi98\n3NWjW4uQ6UgixQgz9mkMNZOOtTc3AeixSpUkbl/j+kgxJ/+qClZbKWJeeSFW\nqoVNoZOcWUBjyTfu2ES8mOaMCDjXD6VtHiF7HLyxZxdk1r351vxsilVVqwC4\ndrujPKfMNB39ja1Bg/WH4LnrJcfA2eMG56dpvjkIU/0iKXLQESaSvTJ31sxH\nvIUAlmPhAn6dncJbJ8xWx/Xd7QdBPdQ8qvg/7wrElLF92jXsQkGfblYRGbQI\n+0u0iHqtyQUK6xJyCx5zqfPXNOypsei5rYAmAjU/KSTF2q3mgkQysRgHsc02\novPD1cm8rKcuk2YiWsa1h547q8F8KTXaHhw6L08VdAZN9uL6wloCQv6KT+xy\n89nJgcrKlKO51FWRdBqdyStt7XthJYHRs+aX9nnAoRIin3OI2yz1hZWJGPy2\nqwEMYFQZ1y4741VIMHHhL6bppHd6nA/7CJ2bqAHVoIk+13dHXJ14El14lxuY\n/tacwzbdxI5GdTApvGVeQn/DCOtXR5FqQ+/cHLw0d5krt54cxZQeiYzQaLec\nKGveQClguMsCZ/h1ero+3B76sH2MN/OhPqAZVwcoT3IJsXLURyrRwaF700xL\nJpFGqvK4sjPpOf1QKBW3F4CUCxcCruoijUS+eFiF61EQRsq7zDbUwD7YtM6V\ncYM5\r\n=npL0\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7879f8a166c6e32fa36f7a5a81bf2ed47ff32d3c","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-14T12:05:40.951Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.14","@balena/jellyfish-logger":"^4.0.34","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^6.1.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.3.5-renovate-balena-jellyfish-environment-6-x-7879f8a166c6e32fa36f7a5a81bf2ed47ff32d3c_1644840533046_0.36383544442789195","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.3.6-renovate-resinci-jellyfish-test-1-x-f578e35570f46b65fc448b8ff065d2a8dd68bca0":{"name":"@balena/jellyfish-core","version":"14.3.6-renovate-resinci-jellyfish-test-1-x-f578e35570f46b65fc448b8ff065d2a8dd68bca0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.3.6-renovate-resinci-jellyfish-test-1-x-f578e35570f46b65fc448b8ff065d2a8dd68bca0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"cdd333a65def8e13a131a73fe2d831ca365daf87","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.3.6-renovate-resinci-jellyfish-test-1-x-f578e35570f46b65fc448b8ff065d2a8dd68bca0.tgz","fileCount":226,"integrity":"sha512-A5Rn6ha7MmCA3rtJ8IreMzRiazh1EnQvrF14AeyLxWbL7vCUjK4VP4yVHXp5yPmz5TKxFqInS4R0STDWLJBxHw==","signatures":[{"sig":"MEYCIQDLwS1VlA0+5ciFbeKHxHncMdUyT9a1PdNHTJLeYlGkzQIhAOnXw4E1oI5xIO5lUfoD6plcrSCuK0WlvAP0NXDIwLS5","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":841030,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJiCnDTCRA9TVsSAnZWagAAcacP/05aCWh2KfSsuUCmpVPQ\nkTA12P3N93itHEibSMP3qAnFQKLSlUEbkEJ9aUvnBMKdXAPA1j5yAjZHbwDu\nO9BbHsGdnv4ysi6RRJNI6sxCXScgQECu0Btny5vmT5wvbdpdhwfXL/0ZoapY\nSl48HWWILJ02STwyswuWnerTK1CrAv7v0LUIjxWr8Q7eQQ0evz2fT4h32WNS\nJEiHEmu4EotkCpQdyoH3dRFLLfJUYGd32MYjHMzNKUvaW4MewMo2fRCzkrUz\nIEzuvktWmmEstUC9d4Yoelm5Lf5boKQM4+2Cxv3aJHUpHn+Kvo3sMXXomcwk\nGsHiiP6vAV8eUlGs5/wGV9zZESgJZtp/+g3yCcsAqhNblo14SOEavol5wxEJ\nDxhElbtLwmCMe6C4MNg4mDshY0BoR/GDxt82kvfaEPIPtAln2/Se8y1bKYla\n+Yh0iaazfKqoLFkvS7oQhlpRkgrVoLtQ1JH6VjcpFkiZctX4020PmwTXAeEy\n+x5j6+3wdi3wuzCN/dC8BSUpV9r1rktoZ7euVITsHpHREPih3de26gj6jYKZ\nmQUdkZZv+GhuVySgxSbNqsG7r0SdzGp9Y2jH6qES5BJW4aPFTo6nb0FVL2un\nugr3JJ+YKeGAQLDftPpcmrSY4zeJAL6HpEdzhvQIgdAU91FGBfP3DXqRtM8N\nG2Ol\r\n=GNgW\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f578e35570f46b65fc448b8ff065d2a8dd68bca0","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-14T15:07:19.605Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.14","@balena/jellyfish-logger":"^4.0.34","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^6.0.15"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.3.6-renovate-resinci-jellyfish-test-1-x-f578e35570f46b65fc448b8ff065d2a8dd68bca0_1644851410898_0.1369956882486032","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.4.0-edufschmidt-refactor-permissions-filter-4668b0f1eb491c806285dd2092e493bb00a4a561":{"name":"@balena/jellyfish-core","version":"14.4.0-edufschmidt-refactor-permissions-filter-4668b0f1eb491c806285dd2092e493bb00a4a561","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.4.0-edufschmidt-refactor-permissions-filter-4668b0f1eb491c806285dd2092e493bb00a4a561","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6669966210167f798353cfea695e4584a7924d2a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.4.0-edufschmidt-refactor-permissions-filter-4668b0f1eb491c806285dd2092e493bb00a4a561.tgz","fileCount":238,"integrity":"sha512-W2mkvIxGxyAaUwWzmtAvGaCprcPCk0QWyvYXq20r/jNBRjsihYadSYSWQTe9n2QTemt3MhXu13wUnIvOqNnQdw==","signatures":[{"sig":"MEUCIQD2AWkWkSyk4k7xWoikdhxlDtLCjtyxLaK9wP0SBFG7zgIgDnVQqGJPvXDXqgCDPzmJlWWVsfd+RC8LsegU1vatABQ=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":849789,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJiCpxmCRA9TVsSAnZWagAAOQYP/AvhUqxRvqg2+KkopV8K\nZeuS7/X6WXVBSmsj6CKY3OfVier7ycoYCt7aQk27AR55UxuAuEiN0+0CByA8\nOxSlwAmMGOuOPTjN8OZGHPiz0QDaC2kiRR6Ww1lM45aGadN3o1vWbrvmgyuF\ndu2A0ZiaKffusMBJNQwEmzhzHuKbOwfep45hMqFaj0Xp+LQ5xM3EY5+QKk9Y\nRmZkUy8doujagbKFRuMH/zUtSjTh41+oUvauJYkkXnTF31LkUm2agpEFvDe1\nF92OtoyHBFGm8ume4z2L30ZEhZgaor3jNuSeligKvrGfnK+HIB475qaFMELU\n0UicPzyH4nW6RXCPunNFpwQV9Vx1PTtNjbKHX3ieUjuC8aElcvr5eXoQk16G\n3ga7K6y7YVVs7rjbxKeU2sCwxIxProicATQ/gSFTlQzfOrD574+ogsXphSZc\noFLMxaJR1Lo3/vvv5KMfrRX+PhwCZo57uPPJ7NYONMvp/+WstZNT0F0v8bGo\nqsXXoLr97tvQ20PGgyOhX6dYvPLWVwT5v4Or3iHR2FUt6iRDZU8o99XBRsOY\nVVGmlZ/AgObyzECRE6q83Q+R7p0gaavVVDWzs+zjMgcffBjB/45a5T1yNNTA\nI9IiFliN0xpjBC7zBxwG+v+OCnnuhpyPOM9F2zgH9gJ8pyuiBCP4x5M7iRXM\n3ecT\r\n=Dia3\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4668b0f1eb491c806285dd2092e493bb00a4a561","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-14T18:12:11.142Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.14","@balena/jellyfish-logger":"^4.0.34","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^6.0.15"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.4.0-edufschmidt-refactor-permissions-filter-4668b0f1eb491c806285dd2092e493bb00a4a561_1644862566154_0.8411033867742592","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.4.0-edufschmidt-refactor-permissions-filter-8b8d0afd6fd734d1371355632151e5c71908ac5f":{"name":"@balena/jellyfish-core","version":"14.4.0-edufschmidt-refactor-permissions-filter-8b8d0afd6fd734d1371355632151e5c71908ac5f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.4.0-edufschmidt-refactor-permissions-filter-8b8d0afd6fd734d1371355632151e5c71908ac5f","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b35d03b31307433abeb3a2ac4bb69dda9dcddb47","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.4.0-edufschmidt-refactor-permissions-filter-8b8d0afd6fd734d1371355632151e5c71908ac5f.tgz","fileCount":238,"integrity":"sha512-xKoL9AobjyK45A6tPpNUP4rTuWv1URxddFL2uF3x8c69qA0gxCjq7W3cmORs9VsWkk01bb/TELRmxDzegD8pow==","signatures":[{"sig":"MEUCIFUrqHH0Ysv0RVgqCcyp5YqRYeplhCBaD3ddWQJGAnD+AiEAzS4H4bRredUKt51T0tWqnwzY4voVbfPiQ2+QZdDl/EM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":849903,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJiCteoCRA9TVsSAnZWagAA5XIQAIoC5d4NAoSDYzwoAlqQ\n+Bji348hhrbTgXaVjY9cN0t6wqzP/br6zXgAa4ULk8aTaZaTgRFdUnGB7vYT\n1k5TVkWei3099mFaggNofSo7cR1SPMVBkEBDOx9wmYOVCMQjhv0G/6bL/MOo\nPU++3rIq+FjBm0xF1kxko73Ln5Vu1ZnBEyh0ep2984sdJkSWopZ0G5+J9mwq\nuJNQ5R0QQuTRw5n060ia62wriJlG1Yx20h5ATOrWGf7TPFyzaP8KzfTibB5i\nNCbiSwOSUfQBqWWRTLWE2AdcmAVAul3K7yTKX4HGE213PWp9NauvC9bVlhWo\nw16enG4U78Yx8d1r6OOt+LpcoHkcYbUsb+tM07KeAi+e12fsLrsjFBiYyZk6\nr8VWEgLr/aZt617pu1futaRegp21bSuXtrWnlr616Fs7VITG/rrWP8KGWFLp\nAhwFnu/qWNSlozLkbZIEWLcMp8AZyFfofJv1aPX/YfssYomxW8eZ50oNG6zr\nkPxex24JfT1n0R+yb7uCpzm73NyGbXelowsd9E17peE0gQGVmjHLYbZZe3Fl\nuAhQHgflKqO7XEjPeU0D487UF0s8s/l+hfzPgBFNTiP8kyJ+momX7aK92VmG\n6DLQGkrbuCjhYyfwN/bggmQPQuQGS8tiKSbt9TBNvyQK7sVX7miEXWU9/lUM\n/gRH\r\n=e+Bl\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8b8d0afd6fd734d1371355632151e5c71908ac5f","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-14T22:25:15.994Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.14","@balena/jellyfish-logger":"^4.0.34","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^6.0.15"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.4.0-edufschmidt-refactor-permissions-filter-8b8d0afd6fd734d1371355632151e5c71908ac5f_1644877736584_0.41787434957632197","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.4.0-edufschmidt-refactor-permissions-filter-1cb920da7647a528d82ea8904bbdedc77a44335a":{"name":"@balena/jellyfish-core","version":"14.4.0-edufschmidt-refactor-permissions-filter-1cb920da7647a528d82ea8904bbdedc77a44335a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.4.0-edufschmidt-refactor-permissions-filter-1cb920da7647a528d82ea8904bbdedc77a44335a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2b7691db2fb8ea4b1cff1a41dd771c78d042e16e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.4.0-edufschmidt-refactor-permissions-filter-1cb920da7647a528d82ea8904bbdedc77a44335a.tgz","fileCount":238,"integrity":"sha512-Ehb6GVqo/I9vjUYwpSaOVv1lIIcVnoSeehn85eg+72/Y/LlJLWsUHDQ06BDbk01VoVrdNuQtrnn8uoyCFpj+Wg==","signatures":[{"sig":"MEYCIQDa+TgxvgqkEVGJmEYo/x3a/TAVZNAJqX3fMRpvJVR9gQIhAJ1XNGjl6ZNy0MdUCWFgxpw7NFg1S4vftrV5uF5Qt/lj","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":849889,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJiCtsTCRA9TVsSAnZWagAAYBwP+wQMz3H/kPK+lBE8LfGW\nKGlBmidZP/fgbbkHMraIe8FrtbeVR3Y0wEeqjyOV9CxOtJ9+LLwI+tH0fFjW\nigG59QKtmJ9iTbRHlIwvagxlpDkMgsmM5kCy0ReZy/0YCzDMMqCybat+YABG\n7Nef6OqLrA5hmO+jjb5ml2LFDfD+yOfhZQgrOaXYtBoEt6cF6n6L3HmjEkbD\ns43AzKZeLvD1BfQryDjq2+qpw3aDrkOYlP4WxsazB669DgJVdL4DY0teho9J\nqsGJJ23eFY1nksgdvrHcmgwEdFpYfURtkm6WQ6644Xef/ZkwYomOsgO8xuUv\nLx6/VPGWA2Mlf25cnqjGpml7Y52MgE2qmhilsQgah6a2Lnam1Q9QKCjVNKMU\nkptsH87Mi4I59CdCxkp5LgwWIkd/h3RvA7NSYCV36wa8EOAvsCQzVsJGwvYK\nqMe25fVkwHG8HhrLmOCplUjt+EN9dbGDL1WrS1/iCOMi4kvicBA7PyzmApms\nXdJN+v7CaFmLpiZzT28RMZoQyKqPzUWchZOK+QvJuux5wcDRKmLm6gke7Yr5\nCsJkirR8+8xhLOU1Mn3XOQmVfxhGKi68DTdnClAhj4wCzQVhgFsgC8zZERIf\nl94YhG7xnhIOfYvkUi28BkL6LmXQudI/Z6cOClAs11ff75+dFy5+kclER/NB\nqyPx\r\n=DXGI\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"1cb920da7647a528d82ea8904bbdedc77a44335a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-14T22:40:52.450Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.14","@balena/jellyfish-logger":"^4.0.34","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^6.0.15"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.4.0-edufschmidt-refactor-permissions-filter-1cb920da7647a528d82ea8904bbdedc77a44335a_1644878610659_0.17059208024758732","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.3.6-renovate-balena-jellyfish-environment-6-x-37a5d6edce1a48bdf490c72f96660e8178d6d82c":{"name":"@balena/jellyfish-core","version":"14.3.6-renovate-balena-jellyfish-environment-6-x-37a5d6edce1a48bdf490c72f96660e8178d6d82c","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.3.6-renovate-balena-jellyfish-environment-6-x-37a5d6edce1a48bdf490c72f96660e8178d6d82c","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"004bdbba03c6926ca91153992c90582322c49f7a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.3.6-renovate-balena-jellyfish-environment-6-x-37a5d6edce1a48bdf490c72f96660e8178d6d82c.tgz","fileCount":226,"integrity":"sha512-3fpTKq0Ycqz0PLAU//RbFqq9mIwZMwb0sNRRkJ6UC4g4HpEP1ycrvza3DUftbXA45+tDdM21mTUpGyg+F5e3CQ==","signatures":[{"sig":"MEUCIG9Cf4hyzB+czJwNGwxAS2mcnu4Hn9XmjVnANLWKh06EAiEA2IsmKqDleghQ9gsDKRvJoXWNo1UVQRon6W+7s6+zts0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":841110,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJiCv1VCRA9TVsSAnZWagAAe88QAJsQfsSGblideFtkGzr8\nP24jW2Kbr6ZfFmqKAzV1EvSt5krbKHlLBamcoJXOgf3V1aLrGaTtdw2iNhq/\nf8H1x3hjdn/QlAPwmg3RM3N0rc4d5RZ4JxR0FHr3YvIsdoYgRbdzPl8CqSkw\nr4uFIt9tSZf7zScpB1QD8sZnGURAp2QkG8K3fhmZVJj+4llngPZgWEaw3+Ny\nvznDfzsQOkssvwOhPAG9g9jUDN9aq5H/8eND8XJLP0xFOi6EV1sFntsf9JTc\nDpysikl6GkPwnTH4pAHgv86BYGY1XAY6tFNyncrYS445gCh3svyXJLVllQOo\nGjINSbL7Xzs94N/J8MlVW6ofI7WHmoKNqjnL10BgThFcY61/0tTHOrwRfK78\nsZoi615jhPlAzrekCWi3uNYg40rIg1CUTjEZmoPxf8j40DPxdxlbvIIwlUxI\nqfGrnOucJ7IZD9KqwyPnVTj8IrHXNvufqZUhGVVBwU6QaREaaJLOF87uh/od\nmclfJ06J8ejhHeBegf/jXjmotPC7a1lIVUr+Myts02fpyiINJZ6gQq/nk7pu\nPxwZ7qELZbFBGAxAdzSNHXZvzjXLdVUhJJY4aH4LPsXI14eqLnybvwEc9K8v\njcvqCxRwa2qwRSPJTBR9HR3ZOocp5Ss0l/6BjrLza3vDAPSQtbedkSpGtfY0\nfxq6\r\n=xCje\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"37a5d6edce1a48bdf490c72f96660e8178d6d82c","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-15T01:04:57.800Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.14","@balena/jellyfish-logger":"^4.0.34","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^6.1.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.3.6-renovate-balena-jellyfish-environment-6-x-37a5d6edce1a48bdf490c72f96660e8178d6d82c_1644887380772_0.023354832189303787","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.3.6":{"name":"@balena/jellyfish-core","version":"14.3.6","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.3.6","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3b03be4c1018156a42b549b6c743cbd3304ca188","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.3.6.tgz","fileCount":226,"integrity":"sha512-vyheyw2NMmr1KmFYRj1MI+qZr+zlqIh+eUQqcGFpsU2Pen2EEVazS4omRye97cynvLPV63ucrS6PbbnzYSy/Sw==","signatures":[{"sig":"MEQCICxt/KmTc8XGuo5X1WWmzYpMxPlKlp+kV07rerFjTzngAiAMN2Z/TrCNX6DF0aFEb7jC1DCwS40mN6KGu6cSfnqYoQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":841027,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJiCw+bCRA9TVsSAnZWagAARrQQAKKVt3nr0PRrQCDmkSOS\nOG4tjy/aHdzA4i/eZvet/iBBedNaQ93w+ptZ4N3GWLamug/i181emv47PZkA\nwJuoPeaL8/Fk7yJnU/6reJs6JWCj90kYHbbJpZyvTUc5ampGi6pWtkHaXten\nmbPt8nYvkBEzZ6xhC0iLBIKT9Q6QV9tW3NNJZPbqMH7HkhYfgJL84d5Fje8j\nIDwx3YS/E83ZT3o1JKldSRJ1I66taxZNhJXDOOFd4I0xmfgTggXjgMnjjyOR\nOVg51EdXkWJ5KzHptMLgKehM3oPIWd7rKfCeIJdNeY1pJ9yfQkVAJVWYpsmP\nOsQhMdsr/FNTBVciQpcSP73Td36hxB/tDyGee1ROt8tlM4C/7/e97GqdXk5Z\n871JZZcVhCEnAHEmuoRV2B7dko5zGNQwGJE4OlYhVRZDcUA2r/elM5aHxnYH\nIXQ9Avk0W4BLbKjUFHaXy1ZFpItxHkZhanAJvz/BOgMulxdy6CPShbn4trGp\nGO0LN0P/NhjbmsNTt3FFGiailOtFqdG5t2QHpB5eyV9Sij/IpqZJYahSbijW\nM/smijlXxQIvhvENwsACZt/yJWcF37kn5HCVrI3xMRF0juDVzjJYMUvKFv8Q\nSD3KrXUHUE0ehO5mqyJo3iTxpRj3ksylygCA9UVSBPfzURdi1XBuuf5bHJ5h\ndHwK\r\n=U/S+\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"93d343e484b36b07a48172bd105e8e8057645a4d","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-15T02:25:16.991Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.14","@balena/jellyfish-logger":"^4.0.34","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^6.1.1"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.3.6_1644892059203_0.9208832631902184","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.3.7-renovate-major-json-schema-org-bf1507f99bd2dea1cf42640db2cb4ab000696c5d":{"name":"@balena/jellyfish-core","version":"14.3.7-renovate-major-json-schema-org-bf1507f99bd2dea1cf42640db2cb4ab000696c5d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.3.7-renovate-major-json-schema-org-bf1507f99bd2dea1cf42640db2cb4ab000696c5d","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"46e83d6e23c5c516ad4840ad89243225a96e1e8c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.3.7-renovate-major-json-schema-org-bf1507f99bd2dea1cf42640db2cb4ab000696c5d.tgz","fileCount":226,"integrity":"sha512-59nUEFSYhOFR50ycklO9VwGYeQdUL9RZWcz9jB2c01PrC03MjiJDn4VoKZQDeGjFHbdN5PBvPNdYCaBZ5x1AZg==","signatures":[{"sig":"MEYCIQCc0JUlLR1VKQ+Nx65bmPMxsxOwjkYKvGmDs5pdvPsHjgIhAOYKjiwygsY+ZALd1FUK1i4FjA8qimmoehOav8a22mrQ","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":841184,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJiCxEjCRA9TVsSAnZWagAAVdUQAIeWiCstHpkgFgx9h5Se\nTcfNBeqo2QW5OlKjvXM7C3GZooAYWNmmTBvt5phxWpCHJhvQHnfqrLgJRJMd\nh83SklJJB5+hWbZABVII94ail5UWI8YoIRp189dmffaOMnWUQkM4P1cCNxxF\nZ8WSYZHjmrF2IDa1fQm/f1FzO141Pbb1+MJFH4aJmM9lwdVKAa75834KsZ2q\nVrTS5MtQCfovhX+GZXv/L+0YR7G/biw6L1jj1VRgddEJmy1SqfulMiKYjmic\nFHZz/0x8VKfTHNCwQmR6VF5cJ3CryvjGkt4dcWga48DVdALFU/XjIh3BOFOM\nNx13r/gZiU+kHObpUQd6lgctQmFLWIY1UYRg+V/PygysEXmqiLFV8IicOVPc\nLBAw9KFj982MKyzzUtjAmy7jQiPCjwQVggM7Ae8f9wW/WAvIlqJY/0CF9mgs\nfhwVh5hjCZCcx+Ja/nZyu+GTr91VLYTdL6H2Vj+424tTDmie2+8P+8Nh5608\n7TlqE+u/BHRzP+MQg5hGo3AUwDCuXH/ci++4K1yoqd566oj4UatyGLNPLGni\nvKa4uRVkvYPJOlHxlu8qbynR5YNUHbKX1o0Zpl+wjyk0+ZaVvsRdkB5pweaB\n3vU3UNuN5noKQ2bdD7lxaS+oe4jppO2n6J1e71MX3HuaMkcVYR0g4UuFEQPO\nY/YN\r\n=d03Y\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"bf1507f99bd2dea1cf42640db2cb4ab000696c5d","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-15T02:30:14.727Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.14","@balena/jellyfish-logger":"^4.0.34","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^6.1.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.3.7-renovate-major-json-schema-org-bf1507f99bd2dea1cf42640db2cb4ab000696c5d_1644892451571_0.5083516624017381","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.3.7-renovate-resinci-jellyfish-test-1-x-310cf6c2fb35fcce93c9ab6e4d361059948adf32":{"name":"@balena/jellyfish-core","version":"14.3.7-renovate-resinci-jellyfish-test-1-x-310cf6c2fb35fcce93c9ab6e4d361059948adf32","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.3.7-renovate-resinci-jellyfish-test-1-x-310cf6c2fb35fcce93c9ab6e4d361059948adf32","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"52871d1d0e3662602f3130d1d35ef14adc241858","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.3.7-renovate-resinci-jellyfish-test-1-x-310cf6c2fb35fcce93c9ab6e4d361059948adf32.tgz","fileCount":226,"integrity":"sha512-gTE7R7GU4vchBfoPWkgcX+Edc8yxebHZn/erSPBE+sHzx2dCHV53UgA3VkYfueaqYIAJkKduYhXOFznwVkQRew==","signatures":[{"sig":"MEYCIQDP38DV7/04rRwxFvJSr5JjPaN1rTFEdVPhV62c7/biPQIhALhVzXE0/qK85Kkddysy8JrpN2xohmCaiB/JCtOCNlb3","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":841200,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJiDCo3CRA9TVsSAnZWagAAhjsQAJny3EBrzGiUjGsqIX0w\n5Foz7hKn+jTuBE32J4pALtJ0CNwLr+1DkELaLfD1ZXQhDZ2BhG/sf/mwG7fZ\nCjeRyDBj5Hc+udolSa5AH7GanF1YLM0XNu268pKuLS/kSILRUkMVezj2n859\nUcYDigvoQVy+hyyYds469nLyAQ8Ha/6mSfWucgyD7unnZLacziKScua9bb8y\nC0hzwLt6bowA/SQJZbDyhk/jBIPSjEpN3WCBTiDHP0BD/QcuLWT9CqW07Mg9\n3humhXz6IkpArcGT/MUA7MadfhrAdLvSZnwe41GcPYd6t6mumsgEh6RU/fmu\nJ6V2jlkP5oadar/Ee5DHOKRF8fA+YPngxeJhuiO14P5D5FrurNkH1OAx0cI4\nNGEftsMC+0/94eIryiVfw1N9kSvpxrdqLT9d7+3ufiOUHtGTRqQ0wgHWlvIw\n0Sexzi7XvR265DjUe0QTVM6EQCWdzh0O7CdwHOqbzEuVcHBBW7pDeDIpA44d\npGa+4f6k09wK1dRbWXFI4ckxQiV3xQOuhmABTReZyGLsrgK9gQ4MPHNLCgix\nc/zT380AxT1awZcpEpLT1fbTNeCfPVz7wIdRVpHvSmgOq9tI0q/xeWXkCyFf\n3NZAeguXFm82z673+/2aECy3H7GKSCWHyAwp/mPKrn8n+Wz1SAxhWIYjbdkF\nhSWD\r\n=WOgN\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"310cf6c2fb35fcce93c9ab6e4d361059948adf32","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-15T22:30:09.642Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.14","@balena/jellyfish-logger":"^4.0.34","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^6.1.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.3.7-renovate-resinci-jellyfish-test-1-x-310cf6c2fb35fcce93c9ab6e4d361059948adf32_1644964407436_0.5133054606030547","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.3.7":{"name":"@balena/jellyfish-core","version":"14.3.7","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.3.7","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"fcadb41494de6bd0b70f0b9a0a4008943719021b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.3.7.tgz","fileCount":226,"integrity":"sha512-V3nvA5Yq8LOMPel4XL/mb+F902vJiGFsHRzr9iFnSwmwWfqFQRbJ1+ydvPP5vbqUauyi6K/CfcUsQUMfseaeQA==","signatures":[{"sig":"MEYCIQCBVm2FLAKbpwX6cs3UL+rxOLcjPaGF1/Y7EwTul2QUBwIhAOrYRiJyq8E8Sr7xq5WdkICVirqX4ECf2rnTJ9y52Llx","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":841123,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJiDC5iCRA9TVsSAnZWagAA9fsP+wZrmROiAFjlAP32QeS8\nRB/F0alZPMHjBCMWrFp8QksZ6bIJpCWEUdXpzCTqRLr+km8Utu+UyLKyj8Jk\nNN2Ng+vlTycfWAOlSCYnX6H3tKI6BHR/SR0rsaqLqeG7ENFcboXRWVw/YRVT\nr7DicAstTWc9WR8zPcjuLOtvI/oghXev+HNOUbT1w6MW9uxmuUXrSKxeHuQg\naud7ZQLekrN219kG9XNps6plru81P1IPlFYllfwUido4px2mWicCuFROdiWP\n3q1FZUl48qFz3jBtcCSCNoy9itSx6TSLlI2HM5SDHxF8sH8ZrAh1qA9WmYcI\nu++LiZQPQPIoqe7CcQGseIIvLc+Jhi1x6RQMPK6TGDDY6PIqTzaLS4GK5259\n3aHKRv2vx/E/OvF24wAvxFuPlfQP3kgmUN/JK8r6yrEc7iUMt2s0P4OqGw9z\nk6c51x2u6AG2caLcjPlAmMEn1V5Xoh7YyWKqbm7C/a50nrmwXP7eKadRkKqa\nkIh0YLJdpoWRF7zaa1S0KVqVEmOXs5jB68JMiqdcie8O9Z6MpXt4cpFA1dHd\nuEDc//TETIyUYzVGVJ4MY9vqYGlalDsjmAV4bqCWWcnDa9Q3H3GyiDxQQAxI\nDr4oKKLeszvvbRFDkeQ08i+fPIVA1/Sc2c+01jRF/IiV15+BMqkHnCFzXDZL\na5Wt\r\n=Vev3\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a91487e484ed5dc20fa432c64fd4641e91209f60","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-15T22:48:59.021Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.14","@balena/jellyfish-logger":"^4.0.34","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^6.1.1"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.3.7_1644965473862_0.11997056572898668","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.3.8-renovate-major-json-schema-org-e23d3704f3e792df5832658fa5e71d1119ada484":{"name":"@balena/jellyfish-core","version":"14.3.8-renovate-major-json-schema-org-e23d3704f3e792df5832658fa5e71d1119ada484","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.3.8-renovate-major-json-schema-org-e23d3704f3e792df5832658fa5e71d1119ada484","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b21481057f50a1a7cff1a949a813734e5ea8b1aa","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.3.8-renovate-major-json-schema-org-e23d3704f3e792df5832658fa5e71d1119ada484.tgz","fileCount":226,"integrity":"sha512-9l816MEX5MPo5LrGCPm4U60kt9b0Jei4JtiG7sXGN2H6/+ceCY2XYOdWxLf8h/jH1zO/b0TUw7LbmRo3AWPIRA==","signatures":[{"sig":"MEUCIAaXCbVbf0Mw5NXra73aQeOd9v4W0kGQospJKdeU/LkZAiEAxyKF4cP883N9Navceina6hQapoWhnHW4VabsyWt9Sa4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":841280,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJiDDAZCRA9TVsSAnZWagAAUAMP/3Ns7HBazbaAey0Ies5s\nrgHVjxp6oXOI5e1GJMUsuOqgszHkX+0Oi7NuFnb/t9lbADFmWpgI6mdMBt7x\nTA0Ojp0+d7iTkxU/Pf22oS9nMnms7vQPXzMgF+26WGbtTDOZkiify6HJJWO4\nslTbPgjEj9MH4p6u+Wuw2BjWGTmYa7NWHA98hwYHb6phajQCP8H1elZhZ7Ca\ncn38HVsP4qBZul7tEgQTP124nTj8d1QjJbv6hh2gvyplWk+glXP9naIP4FU4\n/UpvpWosWBRJ1qENjqXjI11QVfQ7ziLYU8NyRUlCLXk8ilHcZx0rDU9H/r7j\ncVFVkSPPRSARirQ+3OBlf9fZhZynrZWFv+qMhMruXGkszo5ilPzMJg1nBHB0\nmdYYm4gCgU/TdMXu/WieiBwcHpmZJgJ/dg+YMAdDOIEpUBtkLX9AxMM3tK2o\nvwAAmqPQB33mHUM40nOiAkOsEhsEuF+GZGFf5VwL950yuHTG3E/XKOHIPJJq\ndUd7Ya7570myATGgnVB7p5mRiBgpiJv6kGfjgDDz4J1M3Ruyq/wULX/HPcrR\nGC424BtHaMXKic91RFZ5xxRCOhDy2C/bWf+/LgOV2+LWMCfrwuB40wYGV6fD\nkQOW2ZhYTkTHQMrsAoCiv+hEkCJ2dSkKFSF2BbsFyFY8xC+zAj4Jx8BfY3VP\nKvZ4\r\n=rOQF\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e23d3704f3e792df5832658fa5e71d1119ada484","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-15T22:54:36.349Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.14","@balena/jellyfish-logger":"^4.0.34","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^6.1.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.3.8-renovate-major-json-schema-org-e23d3704f3e792df5832658fa5e71d1119ada484_1644965912865_0.8640015209103857","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.3.8-renovate-balena-jellyfish-environment-7-x-0397e4e84023aefeaa31ddce5f50dbb603ea7a4a":{"name":"@balena/jellyfish-core","version":"14.3.8-renovate-balena-jellyfish-environment-7-x-0397e4e84023aefeaa31ddce5f50dbb603ea7a4a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.3.8-renovate-balena-jellyfish-environment-7-x-0397e4e84023aefeaa31ddce5f50dbb603ea7a4a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e67c829380352f3030f4a777e01afa6c20c2b1ee","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.3.8-renovate-balena-jellyfish-environment-7-x-0397e4e84023aefeaa31ddce5f50dbb603ea7a4a.tgz","fileCount":226,"integrity":"sha512-6B7kwuoapJFWlpyNZEsbVS3ShGgbeAlPJQY7CPEsi9UHK8Q+TODn6dufdSxVRQlVlpyRTjdpW1YoGC/YUwSLgA==","signatures":[{"sig":"MEUCIEt4+LhiamEIplNIkuttqerwAj5YlQM78PIbJuHtOlyDAiEA+3umZb8OoaMvWiDIX3vliHz4GJu9fcgc+owtcbPqLAM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":841305,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJiDMTACRA9TVsSAnZWagAAD98P/Rt1xfMmksYfDDXtfcCN\njDxy+GKS8ulGuiKeCGtgHXtAIB5U3/+ziOYn1uhB6KGKCclJyNWaj5r8oGSC\ncXD5BBlOiLho/VfvywN5Jhdi3WD2RSQLcnNt7A0JWD1C9Aowl+LbZyL+LGCn\nNS9t3D1UWBII/qzDc7gffCcO2efMbm7aGpDVkBGVl1Ec57J8NvCGd3X/SoxT\nVwqpXA8egarFzfhhmNGN8iGffTmviRTHiK+wAaCKLFNwGov8hbeL21TzWIpt\nyy/q/ypCTh1ve2Yd+vp3pJd+n64UyaGNPtDRmCarnMn/YVYbY5pFh+ox7T4f\ne5wB0/h27AvPAhU6uBzm7SeZVD5oXbwZbskZOQfAgtj7q+gl+SJqw38JY8Zk\nk4ULcfME5U/Aa+yT0b/Q9OpEcYhdOpC+PABPGTDJCUlZ5O20JLOqre2LwUfL\nU/gkJZVLeNEfK09s/VvC+4PDdRLe6xHQ7qBS4CwFP3uMdEUa1/ahTqV25sVK\nIgSByLbGMyf1o0CaUupLOJXhzB9GFYfnZLhcD4YZF4HgyJ6jUiDwzUEk9LKA\n6AGCiwzRdFg3xqfrq2O5L2/IxULj7dlKa+n88crLfcyNidqjxaZBzJDOpF+b\nV9kWX2YneknkG5ZeWgxRw6o1PwkdcfqP7Bxrpw/LfW3rY12JnX/f79qFSzsE\ni7AL\r\n=4hLS\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0397e4e84023aefeaa31ddce5f50dbb603ea7a4a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-16T09:29:55.965Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.14","@balena/jellyfish-logger":"^4.0.34","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^7.0.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.3.8-renovate-balena-jellyfish-environment-7-x-0397e4e84023aefeaa31ddce5f50dbb603ea7a4a_1645003968597_0.7596931424454862","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.3.8":{"name":"@balena/jellyfish-core","version":"14.3.8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.3.8","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"76650c268eaf65784f954c433c493c059846f701","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.3.8.tgz","fileCount":226,"integrity":"sha512-P8HqE/wQGLZqK17WtmF47mI4gAkP9ksGplovpDV7t8bJQg0Mz72ZtnSu5CDkdWSaqFGIZTBWKdHtObmYB62WuQ==","signatures":[{"sig":"MEYCIQDEPsndA1iBxh5qs7yEa6WYT7bIW5S1K91rRuztbSOBqQIhANd4eWudMNZLV4JPdeeC6il+hAkxPHK2OuRxVX28cIFD","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":841222,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJiDPAuCRA9TVsSAnZWagAAA28P/RjLz03FWutSY9DNPWyS\nfXUlxghAokNkCzfDCBFrdGqYbHgZJw7GB1uTyN5oC/CwZ0HsBc0vlPEqxFT/\nCCKYQypyNlh4q+JXUUX8G8D8pM4IdmRIa7gWokK8HbQJLD7oFiovTmlH0dex\nEKRsiMGtQGmV9/JecfnW5Rvbfx1QJLLIndP9doca8Ocxt1kTWxs0+Ml4VZw0\nz7hr+sAPxjZiKpJjbO5gQ3y2zGiDooROEQfMtlMIe6oCNovIbXSnaxGSBIny\nitjE8XJ21kD/JRTuFNypei9pm/DSMN+j/RZg48psQozscWqVdXNfkpkDReuq\ndyDChC49xvzQSu0qBR97fB9OMt4G85Y1Is4P0aQ0LXPEzJzfTzlS/V0qiAGl\nHqHoB+lHi+RiyvTlWByD3mjcgGv//gPXf/u3pbMCwWsLUYC7Ct6R4tOuETJJ\nEvpo68lwySCFN+nofsa+x7ygxcCm3hJUUnsjFFwSiHdCEHJZHAy5aSz9iZXO\nmpbwVWunC2roOHN9IKrl1fmzOUBIIK2ZDllwB82FxsGQbW5IrA6TI1FU+OuD\n0QNkYhsGmqQwcQZHHf+/FXlBAcHnZv8oitbip9UJb2BGDxHXONw86YewRb8n\nFqeS4eqY8jECA2scauR/6Ge6OEQhPsD3asga8D7YUHZWwe1tF8g2cvdbE9kf\nY6l6\r\n=uZTx\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4ece76892c74bb0e39b67202e3711bbd876302e6","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-16T12:35:56.569Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.14","@balena/jellyfish-logger":"^4.0.34","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^7.0.0"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.3.8_1645015085829_0.04606920221379518","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.3.8-renovate-major-json-schema-org-0853a919062ca7fa92315ddc7bad1031a169d795":{"name":"@balena/jellyfish-core","version":"14.3.8-renovate-major-json-schema-org-0853a919062ca7fa92315ddc7bad1031a169d795","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.3.8-renovate-major-json-schema-org-0853a919062ca7fa92315ddc7bad1031a169d795","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3df72189d474d8cfcf5dda229823efad0a5a0a05","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.3.8-renovate-major-json-schema-org-0853a919062ca7fa92315ddc7bad1031a169d795.tgz","fileCount":226,"integrity":"sha512-7c5FWD4HURYto5y2fXUj5FrJVrsX79ZYy6UXz7uUefDDXIF09YXuNbOp0zehBd7G2mL7T0rRTRELQEkQMntJQg==","signatures":[{"sig":"MEYCIQDsiB9pQiCSSJL5mJ5oi4CtwWXGLZ6bNEO2f1LQW6AT0wIhANknjhXB8Cz92Pp1OY6ZoHcI+hlxCTlCt1zb/+7UNkQC","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":841354,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJiDPDTCRA9TVsSAnZWagAAV20P/1rihGoiPyEKHGWWRO0T\ne/+apxDtWZJ5wIizIG92zWaRc/cGjEgp07c0UXc9iCqeX/bHshLAXPUkaU3u\n14C+tE3AC7NLSE4m4qDDchd3VrjT7Nv1pwRyjtYtsxyz2fLvub3Rkmut2xbI\nZVJwOQQGiv9/X80Z6IJr13QN3G1iGaQAn98mJOjeVc3TQLeobGP1OnhKNE52\n6pkjqQEmXKqNY2cz1/mjr8n+eLFV4AJ/cH2rwXGOopDMLF63A3EFXfZMvoMP\ntNX55mvIdMGpXtxf9xleBBWXMdzji/AeYNbnFsQvaTHCL1SHI21qB+wTIcen\nb9sH5PerrcEHJ+vBfMHla0ZAV/aI7wZiN813cGgJYecLGSR7fOEE92u6J/hW\nbpxCzHSO6VTblUPAgccDVuHz5E0WB+bUCIjSjJ2zwRhQFaLRsFxzOZgt7nSX\nc0FBZ7ON17jldLpWCOf7poDozhHMWhL+v/tVulDzC9j6TZEdf0ox84VVVfo4\nlr0wK2pK0Na2zbA1bBsM7vXssy5atcFf9TmZ9kEdmPl1wawD7o5fis635N2/\ngT5/8Qhtu2PmevyR0tNZh979uzPPNnTP7okBvBJH/AuUkoOWjC+vSndioZlI\nHFJix7+YoYnf2erdJ0mXe6w8Krl/q+X6XL0+nAIswMJMe+sgGHoG4hUb8hkO\nfvLw\r\n=qsQ4\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0853a919062ca7fa92315ddc7bad1031a169d795","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-16T12:37:22.176Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.14","@balena/jellyfish-logger":"^4.0.34","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^7.0.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.3.8-renovate-major-json-schema-org-0853a919062ca7fa92315ddc7bad1031a169d795_1645015251301_0.6577762738080339","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.3.9-renovate-major-json-schema-org-27c113318b5caafc2c684abc5e4495fa4a600728":{"name":"@balena/jellyfish-core","version":"14.3.9-renovate-major-json-schema-org-27c113318b5caafc2c684abc5e4495fa4a600728","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.3.9-renovate-major-json-schema-org-27c113318b5caafc2c684abc5e4495fa4a600728","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"985004a210f97aa2a4081b6d27790e4f14b8f86a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.3.9-renovate-major-json-schema-org-27c113318b5caafc2c684abc5e4495fa4a600728.tgz","fileCount":226,"integrity":"sha512-Glw3CscdoR5AKghYtqBDfIOFh2NXJiF/mEZndQJp+/rWtup0MSxDaIEKWEStraJbuWBNdgnLViru0FdEr/OJkA==","signatures":[{"sig":"MEUCIQCuE9/fYiK6xcqtm3N6WRxjLy9mcvNljYAfuufg+PvipQIgKbRPQYWuNzawBfYQtZPk7xFtwx1MoOnVupQMop/pVJs=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":841379,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJiDPGGCRA9TVsSAnZWagAA+2gP/iGtGk0u0z7w28KTmfC6\nPAjc/OgUfYSkrktzzt5ajPdWXBD0gv5lBcJDdx5mEmMjbA4GQk5bt65TtS8M\nDHWMB2zzLpsw9OCeY40HIogzwqXXGNiu3LWhOfpfwls3hC/Poj1FuTqX3lac\nkZ/KWqhBe7zPFrQ+RE9jUUn18fIkYczaDTXfE4OGGSOv9RbaqIDb+U6t5FfV\nBpU4cE7yiOlG8Bf1zH8rY0hiqWozq5HAlwwRVXmavcyc4zZDn2AeL8A9Tl64\nOqnzqZaCdO8lF23X4yNcTixrDHpU65i/1aVmN0mhwAn+TZ2cDyMevJOobMBe\nGbF6EEY17pE5njVyVHj0F6xTo/wg6MqnKJ8aeEsnCVdThuS6YuidiQNTa5Fm\n0KQ8ty0FjpZavz9KaoQQ6u1o9iHURNKjXrMO9n0G0AQA3/urwYVUZWtkyxyq\nPcUEtt3KeQsNC5GPp77Hb2uk7GnQn3HBlpYN8Lvgc4PubsNvaepojBoAUxEz\nZihKPCPNYmmD6YA8Ts/7dbGSzX8T5HP97cHyeAwxbETfK3Mb0IY9HjPfGrJr\nk/phXv76h9VroMGy19hhOXbtUcZpMGKeukHa+fyGtSUv1YjTQXjANtC6cxjc\nqV1Zdc9SNkHJu2MeWzwsSNXQwNp6dPAiK5UC5SkWin2+0ZCnWvRLq5nvqQ0o\nWGND\r\n=kihl\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"27c113318b5caafc2c684abc5e4495fa4a600728","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-16T12:40:25.729Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.14","@balena/jellyfish-logger":"^4.0.34","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^7.0.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.3.9-renovate-major-json-schema-org-27c113318b5caafc2c684abc5e4495fa4a600728_1645015430199_0.8070181525885016","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.4.0-edufschmidt-refactor-permissions-filter-8ffee2372510e8a35cb83af12877f463ce692148":{"name":"@balena/jellyfish-core","version":"14.4.0-edufschmidt-refactor-permissions-filter-8ffee2372510e8a35cb83af12877f463ce692148","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.4.0-edufschmidt-refactor-permissions-filter-8ffee2372510e8a35cb83af12877f463ce692148","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"852b710105a910ff0185172407dfef6b31cabdde","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.4.0-edufschmidt-refactor-permissions-filter-8ffee2372510e8a35cb83af12877f463ce692148.tgz","fileCount":238,"integrity":"sha512-SymbHSn44pqobi2LoJswjqdEVnhXILhxFKmMeidbbVTpdnIEABTwILAmOa64WpyQqDlDk2cjwagkcHjsHEizMw==","signatures":[{"sig":"MEUCIQC8TJRSXOwDnT6efl88gjSSHlXzHcAFa7mncvhvRPEEWAIgEwPauJAPQ1MI27h5eJ2GmdVSdkk2mppH0VKp4+sn5IE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":849000,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJiDRBCCRA9TVsSAnZWagAA6WYP/RNL8M9aBWGlC9DEdQsz\nJ52tMSy2BcZF58RPXP4d0SV3YwT9M+cJ/DLLLgcx/GKtMmNxdVpxZxwjipVn\nC4b9fKuN/uEHRnM5QoQ8Xo2etnrasau2RtW7NacpB/Q+NxseiwKrki8V0h4h\nC/hiVI3S2e4nvl96Gmz/5ThyL6gUxn5VX4UjWGM6IK9ZGf1deXDplTqK86O8\n+4C+icW1q4ZkojrWwyVDx4naIvNp2OntJw7DrqvlgYdvMbuvdw35sZrsVUAq\nkmQjFgfpTh24W0Pe4ktLtfIxKkjiEzdFo6sAzD1ap0oljFT0KujTai4p50O3\nO2W2kL8SDSVrssevwSgA+GA/msxge6bYq7ALhGbB4Gc8Nptk5M/Ms9LYrMq4\nOGQ2gY9zXtgxcVvN3WRrbKKexoHpCu+YmYyn3FtzC3wEQTJhGYlokBHzPEd7\ni9sqmoZjUlk9FWgF5HZUq+Gp1sYGAMRn2DE+6eeD4dMqtDjfSEKGKxFEDl+n\nkIB3ZwZAQsydEn4nYKet+mZaOXjah+D1FQKktiIVhZ6W/iY7hZCimQg6aVYL\nGzkYo3j1hjw42b5drh4/QbJOOOqYVzi6wKLrD9mOmqV6EtMmjumwo1MWF+Wr\nCHfuEhTZKaYCNbDlzq5xyUJF9tvWQ3zRWW9YAJvx186AyRBKu3xIu43Dlh73\nXnMH\r\n=HDSU\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8ffee2372510e8a35cb83af12877f463ce692148","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-16T14:51:36.638Z"},"_npmVersion":"6.14.15","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.14","@balena/jellyfish-logger":"^4.0.34","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^6.0.15"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.4.0-edufschmidt-refactor-permissions-filter-8ffee2372510e8a35cb83af12877f463ce692148_1645023297783_0.12550038981217937","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.4.0-edufschmidt-refactor-permissions-filter-821ad0198edfb77d8cc45683b383a9afb15b0a82":{"name":"@balena/jellyfish-core","version":"14.4.0-edufschmidt-refactor-permissions-filter-821ad0198edfb77d8cc45683b383a9afb15b0a82","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.4.0-edufschmidt-refactor-permissions-filter-821ad0198edfb77d8cc45683b383a9afb15b0a82","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b9bd67196b43c6c7f1117003162c405ef2d1420b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.4.0-edufschmidt-refactor-permissions-filter-821ad0198edfb77d8cc45683b383a9afb15b0a82.tgz","fileCount":238,"integrity":"sha512-lJmwgvJjoHNTfumaK0Yp6C03p1XerfLf1UU/qdRyrpY/Pr24SHCZTgM23zaRb8mu1l43+GQsZT7uRtb+++Czig==","signatures":[{"sig":"MEYCIQDoX2LdJ9qf5BPOIwnXv0qZjO3Zk9pUoCPsxTmNnaByVwIhAOzj8TAOhH/8wyTkebKauRqUZ0el5WynveGNGPjDml6V","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":848912,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiDpPjACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmqOSw//UBE4myBbTUQ8SBkxLpHwOl39gwyJe7hpxRcLNOeRZSpv8YnF\r\nHIZN4p7uv5isS06AOXXPyOD1ei2uf7kMk4+gZMYLCVbfPgCj83tbbpOZdRap\r\neOAuqHRSWyWHDgYqpQ0gk2fnxyczFe5/Nb2Y5r7tWMi/6vtEvMER+8yEBsDI\r\n/16bYWWGmhnTaLENbJpty2FaX4eKYDdo30Zo0NHEu/6Q0JIAqtXBxFdW2aJ6\r\nQ2ITK0e49ncOsgD9CBfnETrfrl31LSB+/5SNvANQ7yRPRHX/onTeaHLh+KvN\r\ncCnuU20tIWxW9Jbyjxzh16va3Mc8fh0BYeFMVaKvQWrFiS6hgm4nzAiAlqzW\r\nSskm3Vi6rZCN7MKykIj3I6YsTURYR0MYtD6vf6y5ItycUyul8BqP2p6TFW+/\r\nuCiccLl0dsGAd5sizsAvYZ+wToZ65TdROvQhApHUtsuzn+jh/DJldU3DoUiQ\r\n4M2hLDfYhRBGxr5FoVO5oE1DSEI7oBZiAss0ANslU3MDThwkjsvL9S8543cd\r\naIxoRGCnSHvxsSUQpav2FIf5XaMx4X0O4kAXk/Qxjlrw25Sh1BUloKdN/1rl\r\nnf5PMaIith1yLq5MUKHeAMlMLZSyajFILH9ijywE11jPeiDM9KOS46mcfDxo\r\nG5qIcaxhVgoSGtlxCnl0bCrBCorzIpUiNfY=\r\n=A77B\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"821ad0198edfb77d8cc45683b383a9afb15b0a82","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-17T18:21:27.324Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.14","@balena/jellyfish-logger":"^4.0.34","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^7.0.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.4.0-edufschmidt-refactor-permissions-filter-821ad0198edfb77d8cc45683b383a9afb15b0a82_1645122531511_0.7284070213058076","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.4.0":{"name":"@balena/jellyfish-core","version":"14.4.0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.4.0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9914d088b305420b1a7ae05cd9bda61afd6cb93d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.4.0.tgz","fileCount":238,"integrity":"sha512-arJEEnDX8hPQn4xDZwPMidBWvlZRGkQnXiRn7DHbcG4VtwYucQ3btQGJldQzOWZ39Y5PxA3SqPEhPyTqTPjRyQ==","signatures":[{"sig":"MEUCIQCc1oK29psa8bvI1dzBT39E3A6I0skpQVIywVSkXFGQeQIgcmCURjaomY0gJUPGylByrzLojBLb0RjtXPmzRWePkEw=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":848831,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiDph5ACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmrV4xAAhlM71iMPTXQme8LZDmVJ1neD+920qzrtaTBLdJuPHXHaMb7r\r\nTB1JiB6gY7Nx8EYrVMEJGc3i1Qp44QW8/bta72+xNpMnCrZQvU7TNAaP2oE1\r\nmVoA+AL8KOMrhs+6VzZ6hisGJ9oqVDqeNkhgRhC3bbfAaqcEuMeosrQeD2Kq\r\nJxTGEQospEYxVS1oAoES6k0by5eWtA4ZF4xGzxGZmmdtvYhzUD8X4A+i7ZGV\r\nLDWdMF+cVA6UTvCIsnmKxfibTzoA9CiYvLjIsZIxGBZZtSHgE5DsXESWd9aA\r\nMIsWP8n4Jg+WE8wSoxQ/QmiDeJJVz0HVCmz2i5fVDTXkdMQDCKtB2r39kDtV\r\nEGj6MbjZCbliWKEyLP4dPtweXcUobxv3iM27sKI5v+Td8SQUJyghDX4CcLIj\r\nWSkb4thGrE7CPFV1e5ojV20M7HTh3+/OjgEIrcSgFerxTIKJjrhB/A7DluUT\r\n8MHHjV4qgbMP5A4QWvz8vXS/hSkRtqrwV/TSsJidTI4sZeZa8EbqCkV6x7lt\r\ngerYf7NEZR7iDOascaHscwO76oDb8M4NksVwHVBjdHLQgf+We4QMPefzUQYe\r\nlID5FynFnN4yeddhBuhu+UUK18+/ci1FScJyUzIx53w1fTYijbMpw0A2+yOT\r\n0M5DdBWn0cw8Y5oB+sh8D76yuROnSgIOoy0=\r\n=nHXT\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"293e821126860b3ebc4ec14360d1d49b014b65bb","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-17T18:44:12.716Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.14","@balena/jellyfish-logger":"^4.0.34","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^7.0.0"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.4.0_1645123705485_0.15287429031735345","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.4.1-renovate-major-json-schema-org-fc4e8f10368751918a09f6878388854d4c84cc0b":{"name":"@balena/jellyfish-core","version":"14.4.1-renovate-major-json-schema-org-fc4e8f10368751918a09f6878388854d4c84cc0b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.4.1-renovate-major-json-schema-org-fc4e8f10368751918a09f6878388854d4c84cc0b","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1bc7a72077d1cc0cec7ad84d0a1be59322bee7ef","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.4.1-renovate-major-json-schema-org-fc4e8f10368751918a09f6878388854d4c84cc0b.tgz","fileCount":238,"integrity":"sha512-7WTXy2PLCsA3fH1hFfrUnhDE5ITNmOfx1MFJuLMPvdohUO4W/t0XflBqqkU3rvHzr8U0IkuXObg9ELwKzlu/Xg==","signatures":[{"sig":"MEUCIQCyrKJUpP/HpJsto74nkYn+E4cse3B9Mf38qA+LLbI4BAIgIvk6xK39skvVGZy5s/ydpotBWeaei54Y+pErGov+mcg=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":848988,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiDprrACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmrPVQ/+MV3PHClJbAPb6HR7RAN6yZR8Y8NfAReBH57F4OGan3WJNtrP\r\nZLai1SN1EkjsxQN/KRU4kR4LeX+2NPieiTHd8DauUnB5iCw42s+IUYLsfFjU\r\nMVllb1f+8w3dMVKCueq/bL9T0O7AydCX8YgI9l37r/E11atHwk81zq6atOzR\r\nhrSHjADtBXLpGO3I2+aInlKdvEE73sEeftUg7Wntr9yUTFI16rkAu3+S36tJ\r\nkUPsNm1qjhspMesX2UANYNfd1myl+sbOV9TYKJLfmzOFtH3kDiPpwsmYGkrq\r\n2zcbzl3z5UourM2QQaUBD7IHpg9Pm1BdH3Anv5WPnnBNxcpJHcb6h0hmbSIG\r\nG34JR2yJte/jkX+W43QVOUMjb4d9pP7aXiCa5QkJVU0NBK+Ml+AzHVJhsTpZ\r\nTMGQavK6UZ2hAHLA2aIkpQiIyk59AcfdVAE5NdLEJADBeb3r9fI8phfz2BrM\r\n1c9RlnOsKesfzNdht67uuYbEVu0R+RdcKmUoEueatFSYHdXUo3LPnjX7UvsF\r\ntXJYnM+f7wGwYcIm+GNUKzn9L8YxHfgDzYACPaj0DPVR2zKLynywzYWoTShi\r\nhh0S7Chvie2Th7z36+uTHEuwlfCBRWLVfm5nWHxtbaC6/cBD39p/7nMqrJLq\r\nSme9cLacNB9Vo+b3oh2m8l3Q6LekIwT8AzQ=\r\n=29cL\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"fc4e8f10368751918a09f6878388854d4c84cc0b","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-17T18:54:39.800Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.14","@balena/jellyfish-logger":"^4.0.34","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^7.0.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.4.1-renovate-major-json-schema-org-fc4e8f10368751918a09f6878388854d4c84cc0b_1645124330997_0.6576982048994036","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.4.1-dont-merge-23eefb30244cc78cb1ffdc667519d2fe7166abd8":{"name":"@balena/jellyfish-core","version":"14.4.1-dont-merge-23eefb30244cc78cb1ffdc667519d2fe7166abd8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.4.1-dont-merge-23eefb30244cc78cb1ffdc667519d2fe7166abd8","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8326e457539bbfefdb807fc2660a01badef8c8ef","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.4.1-dont-merge-23eefb30244cc78cb1ffdc667519d2fe7166abd8.tgz","fileCount":238,"integrity":"sha512-JGQwhhnmQqEITG/e8PWShEdRHM1KpgbNjhgH9qbZ9JEXz56Gi+4rZRz6jf3p6M51a+xDl8u5ugz0go1lQFs+Jw==","signatures":[{"sig":"MEUCIQDFNA7dtm++xw1KVqCTsDOInmY/CBBHTqr+C700ZknXRwIgVllk/BcMbxu54gaoi9CQcp6YOG/cfpBVSzE1dKa3MR0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":848280,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiD9hiACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmqD1A/8D55AL39JGSw1UCZBVbK3kKYEg0d2dhgqBby+YdEvzH6eH/Yk\r\nz8fB2hGfYjctstvaKwej8TVp40vgYme02gAOwAj7qC+pTkskiht61FbpVE/W\r\nowIJxHOIEozNsO1S5u5bGB8tGWpntNCtU8FHwHoFHgof11Sv2jeeSiI1Ti1h\r\nZ24wei1cjQ/CyRxvFSrtX8L44Qus2xwm/msh9lNBa1z+G3/VjoTej7QRXajt\r\ng5eeq3UVHmyI7nfudYPWr12XAMEngxhh8tsxLIBZeO3UyDni4QjJPYugB5xt\r\nblJC9BkmdorRTT1lIHQRqM6wvSs2rN6O0tA3xgx2dvbB8fOdp9BoAIfe2KV2\r\n22XbQ5Rn0Ah4UPHUreFCJqOLYrCJJBD+t8B2WHdDZgd9ACdueBZsPX0OZUkq\r\nump6SVxZ8fe6kc9B1oObmF/S7e7ClcNAATWN7+ZI/XVuUkUFC6T5Q7zr/xj7\r\n/jyz0REmII6BzSfx/nlQyTYMirSh4gGq7T+7apcnMXHjGQWanPu9rH9TPN0t\r\nbeMc9y8qa6p3YJ4oslu/tni5MuRMJmwngZb+o3/aPoNmGHpDlW+GMGESsRua\r\n7q/EfmpirYZFTX0ZNRA8eboBT4Z8btHcNkpEFZzkgYJDantcNntICrPmsrTt\r\n1BB/CP78+LgajmbqCRY2gc6UadXEJRQGbXU=\r\n=d17o\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"23eefb30244cc78cb1ffdc667519d2fe7166abd8","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-18T17:30:49.952Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.2","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.14","@balena/jellyfish-logger":"^4.0.34","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^7.0.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.4.1-dont-merge-23eefb30244cc78cb1ffdc667519d2fe7166abd8_1645205602456_0.05253110953920315","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.4.1-dont-merge-eee07c234e5da3391dfaafe06f6865368fa0ab9b":{"name":"@balena/jellyfish-core","version":"14.4.1-dont-merge-eee07c234e5da3391dfaafe06f6865368fa0ab9b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.4.1-dont-merge-eee07c234e5da3391dfaafe06f6865368fa0ab9b","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e0f58347ce78ed213413ccce3f8184d73af3ac37","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.4.1-dont-merge-eee07c234e5da3391dfaafe06f6865368fa0ab9b.tgz","fileCount":238,"integrity":"sha512-N+2udjUBq6CMS0wpbRg2HbzfRmJqbhcNXnnw/hYz8/MGwnmNeXrdryxOBWS+ZFPzv2u+W7oF41y0hpId1bmi/Q==","signatures":[{"sig":"MEQCICgQyEBsQpdvN5WYNm05pBMyU65VmPecj/GrdmGcWOJDAiB+ir6YLPJIC/iQzjtWjFRaCq+LJvXsQzRUhH9qkNTQRw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":848151,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiD/CcACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmpHLw//RsfVbulvQC8WobyAjFwZAInmvn96BUMipvir5oKjDBK8kfzi\r\nxeO6wE7ZegEliQ7i/MpSEJHFrIEmW+blBO+FjJQRYa1BX5LywkLFtORNkpmq\r\nitzBad+EbTH9FFbg/BI6Ct60s8m11j0dS9ZMgS5eD/+00WA9kBzQ2nveucF5\r\nkIVLyCHBYlhytJtXB9nW7cOhKT4bgolr64ku3u06Io/DrnK/64+iMI1YNkj+\r\n5KiXmGzA2Hgs3+OFXqGauEjCbNq/F++xypquRHnt249kZTjqzvgWju2rNG3Y\r\neURQmY5N5MVWGl6SJ9ka+JUgVle47/jbKnLk32ZHQ1P/+T6gk7NTCpBWhAtH\r\n2/CIDdFYYK/VozJujg5onT7KUbV797b+MFhCVmjJv1UkmaWuXtvwz2ITdbcy\r\nlex0Bn8B7fs9C3Ndi1GZ0RctCd/NDQR5hZYL89c/r2IpiNsP1u3/R+mmoCte\r\newS/6x9Fb8QPOOIyrg4iKgD05bhkHC/2b3kxNHGUotHJT4Q/Eajx7yMQOy0G\r\nyV7gWFJG55AysxoEAFLopgHhFoap3HYWizWvhbJ8Lj2XzAyjd2k6aKx5FDPv\r\nK4bdUFoCw55mwnogkkQIfg7R+gOgi9ec/CPx9C0ZhROq1eq67jAgjiHI2MDv\r\n3ezq8sT14VbAn1+N+wkZfb1HVImaeCDSu8c=\r\n=55Hm\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"eee07c234e5da3391dfaafe06f6865368fa0ab9b","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-18T19:14:02.260Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.3","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.14","@balena/jellyfish-logger":"^4.0.34","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^7.0.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.4.1-dont-merge-eee07c234e5da3391dfaafe06f6865368fa0ab9b_1645211803889_0.5415972032826011","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.4.1":{"name":"@balena/jellyfish-core","version":"14.4.1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.4.1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"94d7a694a9674914649eb0957958915d7b0f8423","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.4.1.tgz","fileCount":238,"integrity":"sha512-Qymdc4+g3cNyjHSzYFBjaePYtvo3ox7awMITplXthcjKe2bVORtvhskEwrc0UZopON302Wox4sqixpRI/TCBug==","signatures":[{"sig":"MEUCIHrcAGrq3zKK78T/c+s+EKoHWym7R/I1ZUdOgNlQvwfaAiEAtQIGuCj35GWznJOOB8Akq1NLQFS6G4KivrvzlNpg+mw=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":848099,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiD/F+ACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmoeCg//TtOOFpmGsCb3iI0dZx7A51DborvRYTufMns1hB8IvzkrzXEn\r\nFMG3rzb5tksnNnhmQdd5rVmwL3ubDIdMbvF7hU7kQjRRC3y96cumGPHL9jME\r\nGJ4nsL5fsbW5TVqI7WNzLvjPuzAsC5QXy8ZDazwiUVyZVuPfWN//TT9V9F7B\r\nopXIx3fVoKOA6hFWaNgAcCcOBx7lEPmesVi7sPWgMJlRQf56xNP8qiJk/BW5\r\nSWxzfFY4ka2MGr4ar/cLg9j2O0EBU9rXlLwIym8lnYTTlj84u8Agq6u7bMRp\r\n14jfhy1GmVeiMpsyTGAO+7rs4A9k+Jcg86e9upY61XQEzO+utGJY6cl7Syvk\r\nkQwSRlVzQ8YjbnkO+kkr642qsXhvJAOWw5Gv5Kn6r1/vX2OYNXCTuI41Tt4w\r\nUkVu35CMV3XyDlM1JrpTl10stRTR1x6IrpiLpfN6uX3Z4aAauR3aY7eb7pFo\r\nUj+THPB896e3VmY/6jTBmLGmFNGZLqcojhqrf3WUffH54/eysw60lRkDPd66\r\noedkQWLYB0al/2yufcHCWk9ANFf5oELPM9/vbNtnV9y+gbuoCr3KJl94/SZW\r\nJPtT6dO1ESifN7kI6idmAfR4y7A3n1nFX33jxA4MkaFQjdHZiL/NsuXf55FG\r\nRsBS+NJ4CIkAX/O1PvkPZ0IAyJvCre2pz7s=\r\n=mapU\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"29e52024034a05cb756d2fdb27ba94dfbd4669ec","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-18T19:18:14.300Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.3","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.14","@balena/jellyfish-logger":"^4.0.34","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^7.0.0"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.4.1_1645212030751_0.1718055845178179","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.4.2-renovate-major-json-schema-org-04dec2273a40306304b74248b93b6096edba390e":{"name":"@balena/jellyfish-core","version":"14.4.2-renovate-major-json-schema-org-04dec2273a40306304b74248b93b6096edba390e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.4.2-renovate-major-json-schema-org-04dec2273a40306304b74248b93b6096edba390e","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c1e9f6992b77c36f2799bc485ab7afad59266daa","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.4.2-renovate-major-json-schema-org-04dec2273a40306304b74248b93b6096edba390e.tgz","fileCount":238,"integrity":"sha512-M1v94VnUumtdbPRMDyO0n/nPxYP/MPQ7tVZf0cq0JdU841C0DmvKky92aoAfD2vEBCXH7qSCmVSktVq4U5dlFA==","signatures":[{"sig":"MEQCICaq/aLQpo2QFlg0mUS+wjr+eOprLA/SmAEioZOUfOKdAiBkszGcQQGo0KR+4EyPuONKLfyujX9RntxEQQw0bNXN4Q==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":848256,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiD/J5ACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2Vmqawg//XMAJo1kQELDTPf3//8pksPeRi64ZPOIrSvTmTd1ytSYMEZrs\r\n6j5OHUkVaOUO/G8dA5yYCIqOEjACnvCrUz0j7I6/84+mitouyHLJk4LOduT0\r\nGbD123/D8IveXTsKjUHKsNC+cmRFI1tvKxlfrgkiLhoCk+exJkXWJw+zI5d4\r\nheSnC8WSAFh4WDyklefsOFEiAH9+fkR4b+CPKe/UVZz8JpoUQ55IORUCo80F\r\nhlhDdAwRbtELJny77gZ/7QwbDYjGNyocF1ibzsgGEh19mXrtEG89nhkqljVZ\r\naCI+PPIDBOMT/gMfjGxfzU8uoLfh/u9y46J0PwQsEJQ0GVYHJJdE1HIOP0eW\r\nhUIwvPEcqCM+5tL3KdfCsbCWl0mbZ8HVM8YhI+TN7+r+oqL1VLqsxmHq/Um3\r\npTvfrPT+rO3fOS4EMOnITUjm//Y9acOC3aMRqUTkmJUpHAUWavV09MiZtBgC\r\nCZszI2N5wJ2Ns+stm60cfbcvL5RSGxufddWB06dK+LxPQ8PRjJvpNQbxhPfy\r\nur61XUGb85SjZIKCdDUJQattCSet1zvDsWpO6WnXqCMEvw2QIu8y/sPRaDQW\r\nqY8p1iNav/0uYjlCY3bWyeb7O6KHWH1KTXY39xUPbSa/tl1vNTEWOZeA8KxH\r\nUFNMYqGmLoi6dobQFhCodS0TlOGcCWnXuis=\r\n=RJSA\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"04dec2273a40306304b74248b93b6096edba390e","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-18T19:20:52.759Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.3","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.14","@balena/jellyfish-logger":"^4.0.34","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^7.0.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.11","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.4.2-renovate-major-json-schema-org-04dec2273a40306304b74248b93b6096edba390e_1645212281113_0.8977110234584618","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.4.2-renovate-typedoc-0-x-1ef67195ca9c703c61ea6b7e14bd011473c5ee11":{"name":"@balena/jellyfish-core","version":"14.4.2-renovate-typedoc-0-x-1ef67195ca9c703c61ea6b7e14bd011473c5ee11","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.4.2-renovate-typedoc-0-x-1ef67195ca9c703c61ea6b7e14bd011473c5ee11","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"81d0101725b2b1c0b6f9666f229bb80d3b55f094","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.4.2-renovate-typedoc-0-x-1ef67195ca9c703c61ea6b7e14bd011473c5ee11.tgz","fileCount":238,"integrity":"sha512-356Axcfz+APKE2rzXu/2+0MlM8Jk/1Ir+SysW43wJZG1YdPPd6megotx9hNpRSoepPpKSqQDsYOoNhpk8Ejahg==","signatures":[{"sig":"MEQCIDbcfxVHFKbV5EbGpreqI5XkS1V4jUwuK3qZMb6P+yHtAiB3pe3lopbfaHpW5MHuP/X46FYV79epQ4VliBS+SbeqBQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":848242,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiEb4RACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmqG3g/9GeLDaqIwwj9mcKM6IMFtMr1+HsEizirFSJdslKsl/vNtlMr4\r\n3zZc8glq2gfvmaL7/qBVHqUPPi9LNGRyTqlDaKozAEg2GZkh2zsyM+a1SAWS\r\nIbOdIKwjm2Kx3PeZm0XCP1n25VCk93FnVbdeCal6HIi+rTpeoCB+vtEz/D1I\r\ntWqaiO6uWVJJKX0ZJkxD4VVQpQQka+ahUSDdqdBeWvClY0d50wzUwqAtmYZ9\r\nPdD+DSixeAXQAg2X1XewzphBvzzDe3VlHrQKoO5RrEWKOYIT1FJrZ/oDJQzu\r\n35nZoxs7WByYCTPRpyQTVA1obRv+J57NREYKn9/vFYdFqiuQRS6QPGExPZkq\r\nHPsCMtEPbfXFXOWvmIiFfcIfTMXjQEcfyRm/V0BDDCkPbvZ8y1XaBzVpC8oZ\r\nxQ8hh8u6VxmtYM4JqgxCZeyU+Kquk0ZbyZY6zse8hFgbFsgmyGTpmQ1bilWj\r\nXsusYACsAeQwrtw5QY6LuBqvjlyr3iTNhwDGTEcEbEL/BDi7IX2HmTJRh3F7\r\nck9ov1hdfs1QiH3vzDmAgoj6dIWiaF2/Miwj2JEXtV0m3qf0dMMymD+t00dh\r\nFnh+LCtK55nQ4aeqEapZMn8i0AdKgw0b93xrN2XvDn1SFVk9czMu3kqer7nv\r\nTtx7UtIDWZQ2j5KauTWRad9ynWlIlL9+RYg=\r\n=tETU\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"1ef67195ca9c703c61ea6b7e14bd011473c5ee11","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-20T04:01:34.526Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.3","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.14","@balena/jellyfish-logger":"^4.0.34","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^7.0.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.12","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.4.2-renovate-typedoc-0-x-1ef67195ca9c703c61ea6b7e14bd011473c5ee11_1645329937187_0.43667756114783196","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.4.2-renovate-major-json-schema-org-7a63080d971e2bbbb9838bdd9916eb24f0abb93f":{"name":"@balena/jellyfish-core","version":"14.4.2-renovate-major-json-schema-org-7a63080d971e2bbbb9838bdd9916eb24f0abb93f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.4.2-renovate-major-json-schema-org-7a63080d971e2bbbb9838bdd9916eb24f0abb93f","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"270c7d287adc710887bf85dceaf417e364f5c17b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.4.2-renovate-major-json-schema-org-7a63080d971e2bbbb9838bdd9916eb24f0abb93f.tgz","fileCount":238,"integrity":"sha512-Nl6A+HYi8YelnpKZ5Nmt7i0OsaQDs5eovgO+6+AACuZq8/X+u0INMkYlDHUDyQZdxQOMKgcQY4pZs1pQ2o4vdg==","signatures":[{"sig":"MEYCIQC9SKdE1gFooP8z4rAPQtyFvWMv7jtcYw30jeBAs2VycQIhAIiuxnssXW0AK7L4+Ay154VUdPcZyGzHNPnOd9pUVXPe","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":848312,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiEeNcACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmqsbxAAjOlHA9q9tgRzEVBqejvwOCpo69iku7wJZsVg/3BMTPNLrAWj\r\nea+vUZqhhqK/PQCbkJTbYjdcllNUFMhbFqhWkspE7Hpq8x6m6iuXP/lwRQO+\r\nyw5wGD92dGIrgzT5JPB5fuiC2JWEGo+et9qHECUp/B6NifLfvHC6Z0axylJg\r\nqjk6TwXuyEStVYXa2Lkcv05NpxokquQN6OncRH98Y/2fa7JNnOA1gvdoEemO\r\niTZsC5/CgjZOLIAgmgEp2rQYqkl8zZv4adrqENGZcNZQzKfDas7Zgc82gQiU\r\ngTxT/3v39pt2kcGWsLSsbeCILDCBLUtXYXkHe1+Otkm4OYzg0UfOIEtlK5pE\r\nimUvjJzpnv7QD9Cb0qir7b2IcLfDrC44Lj/JUvadREq9DaxBrn1tXmZDHYj/\r\nFd3XspIP4IGfrMMerXzbDLw8sSMteHXfgZfuNrDI2JapsroefEBQ85qED0OM\r\n2gzDfjKjZIoxLRTcmYeJCIwNs7+Damwl5thZXNWhCP1uhudY2WzLl3SpbnQW\r\nkyvf3IHns7NK4kDu/DIlOm3AdiKb7tEw7Wj2J07Akv8h2jx7CGi7N9g0Xy5D\r\nknYGhkIF4f/dO+ne+jmL31i6SuDFxtQx4hpIM0bMu7kVeQMViRcGcLAxmkxQ\r\nqI3G5m0YhXbsEUWWm21TCPSEVNwNRGHb89g=\r\n=umrN\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7a63080d971e2bbbb9838bdd9916eb24f0abb93f","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-20T06:40:36.292Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.3","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.14","@balena/jellyfish-logger":"^4.0.34","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^7.0.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.12","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.4.2-renovate-major-json-schema-org-7a63080d971e2bbbb9838bdd9916eb24f0abb93f_1645339484723_0.6448271047023899","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.4.2-renovate-internal-patch-58de6b483a49ab55467c83a82a10bac2671ef2c0":{"name":"@balena/jellyfish-core","version":"14.4.2-renovate-internal-patch-58de6b483a49ab55467c83a82a10bac2671ef2c0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.4.2-renovate-internal-patch-58de6b483a49ab55467c83a82a10bac2671ef2c0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a7091002334073c859fae55dc69621f25bc95e0f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.4.2-renovate-internal-patch-58de6b483a49ab55467c83a82a10bac2671ef2c0.tgz","fileCount":238,"integrity":"sha512-6JqZrscuumbSsGmOW9Z++YwWVmJPscjCKcXhhCTDKBv3u84PizXNZJulJ/mh5kwWvfcf1ZcWboy2Vbfcrcbf6w==","signatures":[{"sig":"MEUCIQDQgB0+3KD1NtowbcAZixQJ8NVwwP5+UeVQSCeL1mhf1gIgfQUlo3qquD1lOdzfM7sTNL9Fe/Pol9ziFpMxKIH5e+M=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":848284,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiEeSGACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmpnWhAAls5vnlxdernROIFVESibfYQqq+SluNqh13J4LWUxZ4BuN/ol\r\nkoWhLU3lJ3M8PjWdACKFeNNNArzbNxTw0ttwPjfw5k7a5SGQVqZgEA5uHbbU\r\n+mX75N6QYfTyX1ZyZlcK5Uk8eNjdTkugNO6sHsnPUBb9/AezERMMyEZV6VmW\r\nJ+jWqQyH0NjYoeFI4AGxBLR/Kknlg7bRQs1Jc0k3FfglcOegk7r0Tn787FwD\r\nDsriWynm82QuWR4WrNmA0NgGHPuLxSDqh3UkWjet0xXmglpoU4/eT/MMO9mX\r\nweVQWL3zG6jblYqvWZsTLrnRkuu98SJ+4U3cmu70Vo8iMswZc03JqLa8bs5M\r\nSWh5qieU6DCrtXH5K87nFwin6PmINZDLJyYhvIqGTGpX1ILi6UW0qv6c0V/t\r\n+t0TdGe8mhUNix7N13FjbucWya/MEEfu1N/eof5KEqM+wIZfD75c44kM1aT6\r\n94OM2l/8wlqjaPmno01jArXezlyvycrAtxBQzGGi1B9jgSEWCxwqw817C33C\r\nHXMGJZXhCPMo5MjxIafzNwErVIPxayQyhBr4cRAmZrsufX4u+7mIbvEHGDdJ\r\nP5jZs4y3ra5s1FYbdiNjDsKmGPmrorAjU+w3o1u3eH/GnnXfcZaJqqxXdMr/\r\nPiV9WCabxEbzC6uAn0NeBXSXsyQWZhtPHzE=\r\n=l43S\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"58de6b483a49ab55467c83a82a10bac2671ef2c0","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-20T06:46:06.333Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.3","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.15","@balena/jellyfish-logger":"^4.0.37","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^7.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.12","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.4.2-renovate-internal-patch-58de6b483a49ab55467c83a82a10bac2671ef2c0_1645339782130_0.9856174677912652","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.4.2":{"name":"@balena/jellyfish-core","version":"14.4.2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.4.2","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ca2156fb058d4c66e0570b5a87c6694166c30943","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.4.2.tgz","fileCount":238,"integrity":"sha512-X68znr65CUlYxm7TaSXLmis43IjdMBTe+qPtRHNPsjvuc/jCvD1BNQRhlocajiMuvfIQStp7b/ON68Lpc6pJUg==","signatures":[{"sig":"MEUCIQDgy9FU6NNFMJNAoSnKnXKCKO97O65iWMVgr7jXpFeV7AIgRkMmRVWoZ2/PI3t+JExUFIgrqpOczHVC/qp8BU08K/g=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":848219,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiEeXCACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmoY2Q//QiLwFzutUD8SBZFjq9w//3VyH5IGVFP8dRfoXyQl/PV4QOWq\r\n76bj4y2ClwnEskuKpu9iEb8L7ZR8eJ3u8PQvEPvYzJp3oCAkrRIAijqYbchy\r\nI5o1uAou+stSbPWMxp3c3otf+12RRPfj0Y9AYpCoekbNnlgXbFkcdFwz33l0\r\naYcip8+wMNs1v2eoTceqIXdkbDMcsHpJurSQ48uZlYt2RDnHPOnS/9ewuIVw\r\nysLT1LBdZvgoGU1iYu9HmjxeRLlLzQdcCylA1dGRf5hqXxCFZVPPX3c/6u0C\r\nXPINIBk5oo0qymoyHP2PSdICHt5aIRQTEKrAAvZ1RJTJ30B2bYcGpska2WZw\r\nqF0pm0ZDJVzTNt2kFPLWcwg1pH4h45Jrhx2HQhnNgkpwh6ftj3l1NZ/EKdgV\r\nDutFhiJaNXBOmeakwk8rRwyo6iTnmQPDu++HGE+VVXlkGdkWRCd/gBRxA3Wy\r\nCVr4n9gWuZdaeMs5Rij7AMWybIB4GDzLs6n8OrghMqi5VZAdmg2DKSaJghcy\r\nLwGnx1TGgQx8uZGxLaquo8u0cMK8aKJBRIvCQqYi/RD9mGdZ9uQXRRZA4Z9f\r\n05mQO62iZgLDuQCeov5cgFzhAn8TfsvYIdLuAnfPBXI6npiUikeKVSWefreF\r\n0tmwihl+W0zJ9fgDKVpFNlmINGaVtLF+8BU=\r\n=HVGQ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"fd83f2ed67b671551e37e04da4b08ec649f937b5","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-20T06:52:33.570Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.3","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.15","@balena/jellyfish-logger":"^4.0.37","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^7.0.1"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.12","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.4.2_1645340098013_0.09746822817130019","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.4.3-renovate-major-json-schema-org-d33e6819769be13926ac247bfbaa51bfcc316e8a":{"name":"@balena/jellyfish-core","version":"14.4.3-renovate-major-json-schema-org-d33e6819769be13926ac247bfbaa51bfcc316e8a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.4.3-renovate-major-json-schema-org-d33e6819769be13926ac247bfbaa51bfcc316e8a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8e95ed77fef5f7a646a5f18875f7f0e713b07465","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.4.3-renovate-major-json-schema-org-d33e6819769be13926ac247bfbaa51bfcc316e8a.tgz","fileCount":238,"integrity":"sha512-kvslJXEcoLewbrMzdccku41xFBNW7N2ZeRXu/NR0UFnHf8hDpTuUzobezJ/IoFRnNlYE1lkiCKMhgetjbmI6eA==","signatures":[{"sig":"MEYCIQDNWtM9OFW2lM99xleLz9MxQC2O4e3qiJEYeDPYUVtu/QIhAKoKhuTgXHGSDLWCoksf2k2lzaxNBxhGVBrSctaNYxEr","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":848376,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiEedNACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmpW/A//TpJgcd4f2JYIch9dC40hgRdtFUDiGTohd8V53b0m0Uxfe+Js\r\ne7vMA5PVm6SmuKkn6/39zSWJgxzR/GHYQr8Yh4asun2fsHEmOt6K3GgB/kj+\r\n/+bAlC2X0Db9aECIaVHmlgajaYZjQAwsTf9n4flVEqUuJidUkKO8WztWOKcY\r\nBXccSgtlWLlhsVZIwSV5w3wm7RpC8OmM+WP2kpPOdHUj1BhOjXmPZxN05aD9\r\n9qTURL7EuTTewm32Ih5yV9BSz0oqT4cVfyu4z3mYORCkJPoqMPuLXvKtmRW3\r\nC4clOmK5RQ+GW4pvpvony7Zfth1rT+66ryDg0v6ikGoZo1kjNj7XO3N6NHd6\r\nwDnO1P6oHlMDk1QoW63vlWKVnNq1sNCnpdL4FR44NPAOiAgoSDZnffHVxbOu\r\n3dWx8B/PwycykKtbK0lo3kFIdVFmoYZq/7DUtuhC5iBuc2HJS+fsfEJSzDKs\r\nFCLM2xep9PIjiThyb4q09q2uxyl+FMjvdaFcjs5r1w+CDyxm0ry0MxaKMFIi\r\nEUwM5gUpe3ofmssKsv/pwktN1eqfenN4LwW90VC8cmUQq2CmiwYiKNOFrG4B\r\n0BXE2S/mJe69IEowq0jr6aS+M22YKODUB0/F3IwdE3+epIaWWqo641tCgVUw\r\nUEsCzH574O33ZJi8BD43HKztsXmqHYCugb8=\r\n=3TRK\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d33e6819769be13926ac247bfbaa51bfcc316e8a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-20T06:58:20.143Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.3","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.15","@balena/jellyfish-logger":"^4.0.37","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^7.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.12","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.4.3-renovate-major-json-schema-org-d33e6819769be13926ac247bfbaa51bfcc316e8a_1645340492999_0.23940678240413016","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.4.3-joshbwlng-fix-org-query-8bdf59543abee36ca0f5abfd26ea1345a434ba2b":{"name":"@balena/jellyfish-core","version":"14.4.3-joshbwlng-fix-org-query-8bdf59543abee36ca0f5abfd26ea1345a434ba2b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.4.3-joshbwlng-fix-org-query-8bdf59543abee36ca0f5abfd26ea1345a434ba2b","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5fcd226fa22c77a3ca93f5d7c4cdd43f0678f66f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.4.3-joshbwlng-fix-org-query-8bdf59543abee36ca0f5abfd26ea1345a434ba2b.tgz","fileCount":238,"integrity":"sha512-aKgEnWRg4y+bF60MRdKgzaUsK1r4YhbZtYemy07aH9WEc62H6mIHH4gYlDX4pVZlXAGGPHolZzJvFVpCzGGipQ==","signatures":[{"sig":"MEUCIGZuRj5epnSgkH/1mr6tN26n6n/TBQtc7qtksg7hs5VoAiEAviNX2JhnkeqQ8LOtzfKn2rq3ah01ogVnIZNuA4sDJkM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":848344,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiFIGDACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmoOtQ/6Ap8kESK2/U1hqhVAMK7C4U2HaptccrpKUifxTRB72l6uoQB6\r\niMxsweax497K9KcGfIS43KmmPJH45KBC4NFa4ZOMUxp7Be0hSU8F4vx0T11N\r\niQAgNtNIfKYBydyX2RKv/yO0cDSHCrCRF3FxHkYe+/M2N0X4EGmnhXik0r31\r\nXMN82JzM3PGBqz0YN7/A61oBrqwvDdQ/k6t6UBkKXjVZ9oshi0FBO/r4xB3u\r\nYuBMdvQcS6f5kDxuu74lhwzVWeQt/scNz/E5/KpxypwAlg6+VZYDmaPzX0Cl\r\ndCADAxGR7NCbp1kO5nvVWHcO4Kwt4nVomjuTOKmxxss5Ue1/UFpZd6mfa9V/\r\n2Da9kZQ3gFK22giTV3z+qTEkpVV2XfjpZhYc+xvXdB9rZt2t0h6uXFeLpR0T\r\nbtl2htqGSBYX/GfVY0yrG2Zh5ayUMAWsvsZC3//UHmZ1TrKzLuZ94fLN+hOu\r\nhZTM8fMdg5CyyIbIxeZOZrKt7moLJuJbo6jOVzKM0q5tD5pnSSVydQEDo+a2\r\nSJUHFHYbd6eFzXJtiJMM+BpcCf+fKyxUKZm4kkMg1C+GcOWdKgJe+hb19woV\r\neH8gNDnFlq6gnQi8PSMa2+Vv94YbOF7GbYKq43jxNb5GEYIXPn264FrjO5X8\r\noKrwK7JUkFU3tX0SLMYNahSKQn9vw4AUCQM=\r\n=VJvF\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8bdf59543abee36ca0f5abfd26ea1345a434ba2b","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-22T06:20:51.568Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.3","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.15","@balena/jellyfish-logger":"^4.0.37","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^7.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.12","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.4.3-joshbwlng-fix-org-query-8bdf59543abee36ca0f5abfd26ea1345a434ba2b_1645511042851_0.481592297545266","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.4.3":{"name":"@balena/jellyfish-core","version":"14.4.3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.4.3","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"514cce3b8ee71456819ff99af24ca6de097b8e4e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.4.3.tgz","fileCount":238,"integrity":"sha512-a2xDeAsZi+/hVRDty20yntIfoAFm0kmNjdTmSGqQtdnbrF8kaPl0c5m1oXrQkqkTJRVvQgeUZjDjnxVBz5Wjog==","signatures":[{"sig":"MEQCIHyg92UYKrjVZ+iDdyxNinTfdgMxl5P3BecpVCO6SNEMAiA3iKFf/F++I1cXTCRzVyW8ETB9RaFgDOmxD54qkDj6Cw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":848279,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiFIL7ACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmrVfw/+Lc2qwbNeSBxqMWBWlsAh9EMmzyoa+HQ6f7ygZIAdsXbjRwZt\r\nJrdA+jtEfK7ihOKBMaoVLvOnwTTjCZxH4jYz+kNCP5VZCK+euZ3qRZs0Vo81\r\na2BbFMpGBdZsYF2xnyIWgHPVcZ9JrZIT6VjlpP79bGV3ed1QD3AtCc5DiG+A\r\nIM31nYidlah0cmYGPOrw1dGAeYhEQFlfURHh6FZF6N6po0IBKsQS6A6oFRdn\r\n8IVObx1qHctNwYI4JZPCDbeUTAvp7k13JKO5x4CQcOJKbPMqOEjARg5SxoMy\r\nUYcOpQpOl6CiTKAsBR9yEktVCl/xwbyoWhDUEnQ93GP+FEPsKP84vB1CiePh\r\n1zJbqdCJFhHPEOFfDYrHUIkaBY+h/wahBPIwmOnglXlfD1HsJWHQksIUlOOe\r\nm9q8P6LV85sS40hl12jMbwqkCDbK5ow8pFOrYl+8BvKt6wCmPRDCeXZ3E4Z4\r\nDrosQ2xyN7r+j+fztW+WMh7ecHJq0LeXOlTrqj2r4goMb2FjiOIqQ4XCU2lS\r\npZ2zWateAfkwMvGctvPtB8oLMeh+/2f5BvEXWWu5/WI30nZPrvmfjxtTg/lz\r\nt9JAvCqnHECDNm1wyTCaSqYA6QR9pi5kBVtVNi33+i5QvqwJLtPlHam3rRHG\r\n1wawEaWLTxmp5G2VSLjfGTQKsFxPXNnDx6g=\r\n=ZVBa\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b832abb1af94551053ad153cce547a278fd2215a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-22T06:28:16.086Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.3","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.15","@balena/jellyfish-logger":"^4.0.37","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^7.0.1"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.12","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.4.3_1645511419410_0.15957316785496256","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.4.4-renovate-major-json-schema-org-affff30d4d01d8ff6199cb3d23edf0057c4f62af":{"name":"@balena/jellyfish-core","version":"14.4.4-renovate-major-json-schema-org-affff30d4d01d8ff6199cb3d23edf0057c4f62af","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.4.4-renovate-major-json-schema-org-affff30d4d01d8ff6199cb3d23edf0057c4f62af","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4caa54a51cd877b14a4cf7b82588adc167632ea5","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.4.4-renovate-major-json-schema-org-affff30d4d01d8ff6199cb3d23edf0057c4f62af.tgz","fileCount":238,"integrity":"sha512-F9FuwNAHfAuPa9SVQy0sPJhkkb80T4YR565O9kJTx8aaQpaLZJzClTCyDVbX/tAi36gZp2wQmbma0gWjTrbWqA==","signatures":[{"sig":"MEQCIHj+duUExCVMakmfZJnoHt2uyCfKm88yLjRNb7mogyDxAiAx6g1mlp58j0LI+u45cvj87dCkZDpNXmfVDJRpvyTrpg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":848436,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiFIQlACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2Vmqjlg//RSA/uWHAc1s1sxS+QpEiKa9RcqQqk5Pur0b8+ZEknHJ85y1b\r\nyMJLhcVtMnLdQ5rk8BIAXpc+8LbpCmE1JZCjQfCv79updLTw72L1Xmn4Z2+C\r\nR1/u2kLnJjW6WlM3dCNH9nTdp8Fjdak83i34oc48e0WT1qSsLuxZhFJJ3MJQ\r\np2sWDkeyFzmXb+eXKosYw7HWgZzHbVscFCoAlEv5M5d3KdYTUsgo/HyAHun8\r\nm6a//7aLahcgroXhpdYOh7rQxj2NRxvjDS6p+tbNJS0GU9DWo3j1cdgpsAaM\r\nUwjQPNiUN7MdhTPzzni5aaPItPUtgWRvD6vGDT7+hXlRKvBJdGRg21Qcx86/\r\ngZ/uuSLx3I0OL3v8vjl3W0D+qkdErev6srnpOvBJ6VrIZJhGZ6cs7HXvKQtn\r\ndWdOsU5QIl8z+LGME3hUBv1hMxGs4DkGGAJKa18A6ry1EEK+DphOCXgsxCYl\r\nPziY6+t7HGCaSe7p/LH2cRM32O0rPOvEDuXQj8xzvzvh8Zfg12Bkk8tsQrP4\r\nCiJRQLEBl8hm1xoPoJRJbdSoo5wlc7t3vyJ/CbtacbfdsciZIgWes5OrMRFd\r\nNENgxsIXhqXT7dCaU/GFUbT4zBuPtrROlq9KSZ30ctnX3jjFvCgXw9df+pMT\r\nWkN8CmXYkzscOk9epkpbsdcdeTBvLpWghMk=\r\n=vaQX\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"affff30d4d01d8ff6199cb3d23edf0057c4f62af","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-22T06:31:51.725Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.3","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.15","@balena/jellyfish-logger":"^4.0.37","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^7.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.12","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.4.4-renovate-major-json-schema-org-affff30d4d01d8ff6199cb3d23edf0057c4f62af_1645511717621_0.396114947679175","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.5.0-joshbwlng-add-marker-tests-f8e06caa0993cf42c6b85da66f86eaae4393ae7c":{"name":"@balena/jellyfish-core","version":"14.5.0-joshbwlng-add-marker-tests-f8e06caa0993cf42c6b85da66f86eaae4393ae7c","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.5.0-joshbwlng-add-marker-tests-f8e06caa0993cf42c6b85da66f86eaae4393ae7c","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0630daf2f8b6a86cd750be288d8e216695020166","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.5.0-joshbwlng-add-marker-tests-f8e06caa0993cf42c6b85da66f86eaae4393ae7c.tgz","fileCount":238,"integrity":"sha512-HwpLNcS6eJYQG46ZE53DZ3La2BDKggvSuGwJv8RINV7Litn3NUOmSuY4a7bkrhWcnpyzsL8eW0c/7urevwZADQ==","signatures":[{"sig":"MEQCIEXCnmE/Wc6NNk4aydhtOiullJTEqdx52h/V2rU0lmIlAiAOpacL3+KuP7P6/xwTN0R7EBM1VCtsxWEGJMFd4Oeo0w==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":850636,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiFLaYACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmpvDQ//SDTG+2SPwwp/p1j//tPTx1n8YS5QHWQQ6vh4dbiVDQZWJdRi\r\nEDEtm8ZJ5GRU18jBifqgEVH1ZT02rd/A/yBjZhK7q/k8n6z+RTgv2PO42fxu\r\nwlLVgWJ/5Be49h5IDFvY6CXXCVMzpoTG1TTyICuynWI4UDn/EEDt542gv9FI\r\np81l4a/orsYWC8aca4ZtK5CVR7Ha8goF9RLSipW0WVbNA2HnZ3EJASm3leGF\r\n9EoU0PWJiiR2md7e8vVQ6oi5Se+aS7DGWcvL7VI57J/cXR2rz/E1rHJ9dNaD\r\noguN7RotMOaHVT5zmRWT2KGA36GqHERW9H6AiH2P3Wakf+GvbPbZzc6W9D7m\r\nvgL4BO5D6/X+6c5PJxViRrvq40VO42tpYSRubAIPlEH3tv2M9y3iJuHzmE6I\r\nNtKBWz+W7H2QdGEm/TwGRB+Iw9stOH+SZ+jY0CRB0gm9BkIF+pwjkEYQBTSP\r\npD+YivcaWj/kufz0ImfZ2U5WAkV9Zd9iCJmKrOz64oEwqKHXZy8oJM5YzgVI\r\nzHp4X2KuiGmIXwUcORmE1hKuGdicsgUsjKLcahgR/pZNXkdm6nkbwEhdBJpS\r\n9In8ZFGAi+jvMF2P76k1s2bnTiGL2PHKAXSeyuk5+YW67XdAZUNxkXyoHK9f\r\nt4/DjOH1VqPDXQuNxyK3C5IIGWy7FEPR6f0=\r\n=UAZU\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f8e06caa0993cf42c6b85da66f86eaae4393ae7c","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-22T10:07:43.059Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.3","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.15","@balena/jellyfish-logger":"^4.0.37","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^7.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.12","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.5.0-joshbwlng-add-marker-tests-f8e06caa0993cf42c6b85da66f86eaae4393ae7c_1645524632398_0.46129401180407203","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.5.0-joshbwlng-add-marker-tests-f8dfab006ef6f12b89ed5ad07eb2b70075fb0af2":{"name":"@balena/jellyfish-core","version":"14.5.0-joshbwlng-add-marker-tests-f8dfab006ef6f12b89ed5ad07eb2b70075fb0af2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.5.0-joshbwlng-add-marker-tests-f8dfab006ef6f12b89ed5ad07eb2b70075fb0af2","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ddedbf6f0440b621298aae25ba2db345d9cdd3d8","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.5.0-joshbwlng-add-marker-tests-f8dfab006ef6f12b89ed5ad07eb2b70075fb0af2.tgz","fileCount":238,"integrity":"sha512-joJpc3ouTugZE8044D42n7IvctaAt4jYCxRcnuxxLoHwtFNpBFNmZkaX7wRjKmCvwj1iuKAEn9eojfKd+aao0g==","signatures":[{"sig":"MEUCIQCBAJZG4mg20GB/oAjRvPxK/Zki93EqKBsQoDlQ59v0vQIgY8Zq52viS9Myoj/ZZ7Sffz/q742oG78Z+hVVuhAcLBs=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":850636,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiFLgTACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmoupRAAoiyn7wdWErasYmksWgHoyQxb7yExBNqBSOorLiYKdmNlX7TF\r\nCrKQheKI58xXk+giJxiV3F+zMCUgfFA4JtBBJvcK9vzajoKvdUizVdZC4fXJ\r\nI/slsPg3aBb3Ml8JzieH3jo6FSMirCqE1FEYEFws176WK8TLg55B7N+ddmPD\r\n39PTyEhcpcQYIOEfhU3y5uavJUYz5iqF/Gsl+taSvacPgarv7TWE2Lh+y1UY\r\nj8k6VMTHUHgbXcwJgJ1wNgFB9+P7zoASWMbYAFh98JkC3QqxmbYbPG6Jqg18\r\nHeWr2BXjDqT8kFIe+mkdxdVyhI42P1/dbYqPMSvO8R4U5Z5talTorxHWCU1A\r\nvLJnqFbiML42Ogugg/C0yZrzc8Mbhzm9iiPokxJth5IS8Mw+u1byU6zrV9cT\r\nwpApiUX4hOWnlvRtnOjGQw0PBIVvxXwFfKfUcfsT73QG3JgQxQJr5fogs0r6\r\n1R19tISIeymekoMBMl4G4d1fIe4xN4RBy8a/3qhPrR20hY3kAoBfsQzPwFmL\r\neEe8ik7EvbmtgElVwmclNIA6s8DgmM3EP5Ch9MPYXk0JZ3X1MaiCM14D3T7F\r\nvrkSBQwWGY+rX6vKHpMfllFngzHAFk+MayO7NF/grCU8evQ3lj3Dlx8MKTRW\r\nLkBmDE9GvpVNH7AsOYxqxMo6lCWGtae0Lj4=\r\n=WVLV\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f8dfab006ef6f12b89ed5ad07eb2b70075fb0af2","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-22T10:13:34.166Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.3","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.15","@balena/jellyfish-logger":"^4.0.37","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^7.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.12","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.5.0-joshbwlng-add-marker-tests-f8dfab006ef6f12b89ed5ad07eb2b70075fb0af2_1645525011757_0.2505846315834077","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.4.4-apply-mask-a10430ad05ccb68e61234dc9460b1555db6b89d9":{"name":"@balena/jellyfish-core","version":"14.4.4-apply-mask-a10430ad05ccb68e61234dc9460b1555db6b89d9","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.4.4-apply-mask-a10430ad05ccb68e61234dc9460b1555db6b89d9","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"81b2c094cce70cc62f1d5ecdc3413256d4006409","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.4.4-apply-mask-a10430ad05ccb68e61234dc9460b1555db6b89d9.tgz","fileCount":238,"integrity":"sha512-6FsTvffVx/E++etaR43PdoQ/9DNnJefh0ACmOeBMRHgWkwXBGnxKCeJGeGOIZLHNb/61nb6Tlcka2po0fMUncw==","signatures":[{"sig":"MEYCIQC9lXWXWj/CUlx5O4jw+CXQzIxxOEbAirLwTqm3TZhirgIhAJSQQdAdAvMNLK/UdeQlaJmsph3rwbCzUC0wVBVf+hTD","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":848373,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiFmEXACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmqKjw//XVaXmnOQAAwPGu+LXRK8QgIcPFiVyWLb3NKB/6v46L+xub0Q\r\nsAVndbb0HDmBrzOLBip7O3nis5CGE5+DGD/JMQzg8BaLna9Z9fVwgs6MBIJ+\r\nsDKSflJfI8cpWEHJ0iubmK9JZc1jgdx4azbliuLlTAglWhLzLMpRVmSmtNqm\r\n4ywqXHy9FUDgoOOfiH38jChtY4Vtgu8pp+03/6dCqu1DphhcDOc4RSQHZ5KR\r\nS/FN/SCswD8niRSAEziwsggz+BHLf9VgzdRMRA4hVAvBBQy0GnQCd17H0H7T\r\n+2/BuzPRgreaVxsYyTAsJXOn0gBQyR3xbRu2P8DOAMDUj5YubrH+Q5Z9aV/J\r\n89O22OSnay/krtnuT9d8WR/8jwUrWlRIHIe554RSCiy+P+zdBLGmF575k/L1\r\nefGsX6cVUja+U41xfpYIxvfi07lJtpbA8DDhifEK7Z2lL0StLYbjCyFedgal\r\nEZqa+tujP/s40UennAKUL+uS/H+1S3SdkqwiIVJ1jWEFSTrcuS8KbiKFAD/3\r\nxv8XQCltpRNTvm+ENB7/RVPBuGiD2Qe/nS/OlJxylRe7CnUXqB6s4DN+WbIj\r\ntvlb8FJT3kfH8pMiODPQvlIqmgdtVhKOUigwf1/SP3ac0F537wxP9zIg3OJs\r\niulLeDU8GdrqqSTzRmZrBXjmUn0918XZlng=\r\n=JuLr\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a10430ad05ccb68e61234dc9460b1555db6b89d9","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-23T16:26:18.052Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.3","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.15","@balena/jellyfish-logger":"^4.0.37","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^7.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.12","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.4.4-apply-mask-a10430ad05ccb68e61234dc9460b1555db6b89d9_1645633815566_0.43851513869150627","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.4.4":{"name":"@balena/jellyfish-core","version":"14.4.4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.4.4","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8fb0cc2a5d478766d922833dbf298372b50312d4","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.4.4.tgz","fileCount":238,"integrity":"sha512-rwcWCNFRQyO/ogO9k/YN4e+Q5cN6pqQCfhOPLqdvRaRzoWE3KN/2WXzcSS/5y6iqH+PgyPrpAYRPBi3D5aXz2Q==","signatures":[{"sig":"MEUCIQCqzErOzWYdATf5PVCIF2rNKp9vHHtZc/ZkU78y8QV6RAIgCZFV6xhhuUR/mA6njc5jf39huF/pM9GXhW3T13FH1iU=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":848321,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiFmXqACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmqX3BAAjwlSLJj2sOYxAqmPiBn9MGx5qz3hohviBnc+8HvNu+R2cxvz\r\nff4rbib3CE38KIciV6toc2nRJUEOGiHLhIILdeL8V26C1VxLLqdUbwPbm8hy\r\ngmY+fUyG//EXYqgmr9BbO2JpQMtJIeFIYWTeJbtT25eDk/JX4ke9h91JTnRf\r\nik1+KtA1BQXLmPls9bbUkysHtiQn/WTxIbXbiDYF9UPhl8H+KUIuB997Lz9S\r\nCWZhjFZKOHGqMiPRIlZ6BtCPY5kLqQB1m2Dwo+02IfzR3urH7al30VcWfDbT\r\nM367M/Up4qBKUfk3X+GDRTWAEBbg1tAAAuvheWns5B2WAtSB9Rsn6RxhwnPU\r\nVI4QHBLT/25rVmKMNq0Ovb1LkaCUfQ4lOsI4v8XVmN3qLbX6TuouHgMar7ur\r\nCngP6lsfLSi1aHEkLwBqDNQc6+G6kjZs6lhK1CCQ1T81mBWoU0PsLWqw3oLT\r\nmzm2ObAx8PlPdeG6qfKj9y9+sJHVhKhvYGuXKuu2qSdlHnh+nUJZBxKIOtMq\r\ng/1RSo/RPp33NytoAJjIjR4UxDqGu3F9N0eqN+/DMpESikMKcHZAZTBVGNkK\r\nv8Q1VJkUM+TxYGMJkf4aTcddmeZi7GdWIm4/GVKvGQITq6qcY2CnByL8XlOo\r\nJuUdDuvZXWpximiHEniAm2UqakPNPyeBdCY=\r\n=4JEL\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c221971a0e26da5105d166641e4796b57b2c6ed8","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-23T16:48:54.893Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.3","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.15","@balena/jellyfish-logger":"^4.0.37","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^7.0.1"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.12","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.4.4_1645635050664_0.996542208440937","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.4.5-renovate-major-json-schema-org-0956e1012d5146f10a5a60b035af72aca9d6c781":{"name":"@balena/jellyfish-core","version":"14.4.5-renovate-major-json-schema-org-0956e1012d5146f10a5a60b035af72aca9d6c781","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.4.5-renovate-major-json-schema-org-0956e1012d5146f10a5a60b035af72aca9d6c781","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"7a6da6f20fb373047cdb669a6f84450ffa8cc109","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.4.5-renovate-major-json-schema-org-0956e1012d5146f10a5a60b035af72aca9d6c781.tgz","fileCount":238,"integrity":"sha512-7Juym9xl+m7dlVsmkfUXZbSFRQnyh1AXiS+9uBNKY0xT7daeLkzNAFxCLniYDdYpPTfsMgmH4+AiFZhDsYfF6w==","signatures":[{"sig":"MEUCIQD2kZDWYQgmq+Q9J5lBKUOYISWw3FuEXVs3mah+s7THGgIgc59HgZ6xYFsva5CYoj0uHrv01HmwFp4M1LgJiSuS4DY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":848478,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiFmbQACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmreQhAAlDvaLHuqbDzMeYWtAyzsVtXX2aSNsgvLFOjZyt0/inTCv2EK\r\nw3qaaOB+x9RGSJyPVV7clwg6H8FvSsYozubode6e96fo54qyhqzM//645y0o\r\ng40x0JOK/rHjkM6cJ5RUThVCelfLBrqyxo64F0VZBdPDubV5Gcs4gEy28aDE\r\nYMwf23bipENPvVcdimg6TRakjN3+EhaRDQysxEMuYIeVuxSCcnw1khPU3cSr\r\n/TOw6a+bPN276ecS2Vc/1Id/ngQ6uoFVGx/Eg5K8pqae53wktw7prhlmh/Ld\r\nVNqruscxAr1PzXSyzv6ZnsW4k/oZzaU9IF1fQHJBhL+kbmP0F7TPdj5qJPlJ\r\nIwC5JkyLuBvMHU3XBS9SNNWv3IpBUbpaMXayBUPsmWzP274K9F32oecNH22h\r\ngtGdGzTQ2qHdTeS/J7eE9QNCKKHEvMeQjDv9d4w4pK5bb9W637RqIm9fOwqJ\r\n0mKXS45YfU3lwQVLERP/3otTMDLXKLeb5/uklls6Zj3vsImMW7xuowv0qEJk\r\nCX0/O6/hF+TSIj3HxpKG7rYSbJK7DFyxmxdGgYzuhKNyjOgSPueTaeqD+HU6\r\ngk/zGBu/WUBsM4+S8G2JfmLpswyTqONZ2xlzK45VI4wjiutILKY6NxOv6+Os\r\njZHccD49ETfioX3Oz4vQQ5HAVjt4VDtBX7c=\r\n=HJS+\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0956e1012d5146f10a5a60b035af72aca9d6c781","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-23T16:50:55.697Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.3","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.15","@balena/jellyfish-logger":"^4.0.37","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^7.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.12","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.4.5-renovate-major-json-schema-org-0956e1012d5146f10a5a60b035af72aca9d6c781_1645635280624_0.5990480690504671","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"14.4.5-renovate-balena-jellyfish-environment-8-x-760ef4156dce923552efb1ff12c935d96f8c618e":{"name":"@balena/jellyfish-core","version":"14.4.5-renovate-balena-jellyfish-environment-8-x-760ef4156dce923552efb1ff12c935d96f8c618e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@14.4.5-renovate-balena-jellyfish-environment-8-x-760ef4156dce923552efb1ff12c935d96f8c618e","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"01e29358980ade67e4230fa95da5681df7851516","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-14.4.5-renovate-balena-jellyfish-environment-8-x-760ef4156dce923552efb1ff12c935d96f8c618e.tgz","fileCount":238,"integrity":"sha512-shtPbQcHJ/GNH70KRBpXZgbGT+bBHOAtnnA/clu6Fa/j6YJh/3gE5Pm2Vo9dvvsMh6hr5F4lhiXPg8Ntvb6dhQ==","signatures":[{"sig":"MEUCIHvViaNMbVdFljN2Aqneuz/D6FAHrT+Om1zPLzn5YRk0AiEAzAuVsrQ+5dRtBeYpwCAqw3lVFjl7B30lSf1+x3hAHeY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":848503,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiFohiACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2Vmp9AQ/+LAkycKqulHAOidxfKqBQFSuL+Sb8mM52kNyKFJcrfFES/37L\r\nDesComALEcFasTyZjQoY62EtSrzxaasd84TWic6g9R6Yrk16FL3iry4n1I8g\r\nUixA8c+2am5mH2qlBEheXsG3loNqknBT0Urid2HdVMpqq0ygDTBH2YwX72po\r\n3alO/q0HMUmy+/IyEd3vHa9wPWBc7XLrEW/IiUjFzieCNA18vI16oWEZtJ6H\r\nDFi09wpsLPNUeKIS/kWCeoH+H0Gc6tR2QCk6fWR+G5B4igaO/EB9YZGxFTJJ\r\n4c91rDSO6mjTsIL6aYoK5UkZWoCx2CZynmqozvNX8ARcuqkSEAO4aiBEdqWn\r\nSpmqoGpwtP3d5Wg9lV2t3YIhM7LkLEgojFakkjIIfplM5MXnol+9qCkhaL0V\r\n2wGalBG/iZvWj9DL2GiP140eOOFH3CzD1n67KKToptzacYQz5zuZYdco0fNc\r\nr57x2Lej98+VVUjwU4taMc5WovpIGi45tWkDpfnG1Wp2RZbplRirvMwu+ET6\r\nRfmizDfSTJZP2yYEJgCl0lujCJPkYXBJxsZZUixW3VZVqU/ByXPtXWGLUeWn\r\nim/4BRJAaUy5tu2JKlL7nJ9WkXgPSRtkc7xMGcpnv/TPU+wFNecO8O9cIbpu\r\nUyvp4M3657hn8mMHSKmU8sye4GfK1O2Eb3M=\r\n=e0Ki\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"760ef4156dce923552efb1ff12c935d96f8c618e","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-23T19:15:13.455Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^3.1.2","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.3","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.15","@balena/jellyfish-logger":"^4.0.37","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^8.0.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.12","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/redis":"^2.8.32","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_14.4.5-renovate-balena-jellyfish-environment-8-x-760ef4156dce923552efb1ff12c935d96f8c618e_1645643873969_0.7031593183770561","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"15.0.0-update-redis-daace13eecc294eb29e90dbb6a4fe86441f8347b":{"name":"@balena/jellyfish-core","version":"15.0.0-update-redis-daace13eecc294eb29e90dbb6a4fe86441f8347b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@15.0.0-update-redis-daace13eecc294eb29e90dbb6a4fe86441f8347b","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"068941ba1f8d8164a5f0f42186d03db88d1d65a3","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-15.0.0-update-redis-daace13eecc294eb29e90dbb6a4fe86441f8347b.tgz","fileCount":238,"integrity":"sha512-LZ8is74CrvI1AY+hmQz55phbJKXq/CyREfiYV+99e5EeAuSv3Yjqxt+cAK4bljmN6xiUShVZuf6qTdYeG2P4yg==","signatures":[{"sig":"MEQCIH1tLNLrhyU+mB3LV6nUbHYJDUfYA1cli6m2jY94LQrGAiA4JoCgu1hl8ppKaxy/WoqZcIdC2ss/mWlETFYlCBcJlg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":845856,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiFqGvACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmoJHQ/+OCFMVwhzTLMYIj8N70EfqLY9fyCduMc3N1rpdpgJ38lauMHz\r\n7L4BJibx0OsWkC1bKOt5O87OaQSVmEjGXjklJ7rYR/yIEWHvhxy3KJ4TDqI0\r\nVScThNN8S+P9NqGRYfWRaqMBRIThL/BsSRU5w+wvO77iEHJcAu7o5sJ/n0Uv\r\nasL6qdkwFSP9qCWDa7XI4hm6KORrQRiMDOuvWgwLur1tZzqsyjZh/OyPhjMR\r\njUtUGEPYRsO4SoqSxAU73hUgg7SScUg5URe7cr9Qvm001+5DrIkxknCUC24N\r\ngajwtN/CAoYgNAD6LGOGwgVVRy3CsYFR0F0x7MFg3dMYyoDv8p0qdIlk9/Nx\r\n5P6bAq38E93lj9QIxbB9/uRsauChJlGvHyDCh8AGjKJQmQniSOdUmFv8asvc\r\nk2auN4LlSBPzbePCM1fss8fijdfB70/6tTDMexCTb+nSc5NHyzg36tWhy845\r\njOdjZf/xOHF81YKA14recjzxpEk1hGRz+dkaNdGZGTKyxR4w7SN3QnHL091z\r\nFFe8ZXFQOG3znwZeORvoqY6l/uFhgGXBglbTmdmBvhcuvokwRnpreJGeYqxl\r\nLd8K3cbSUTTLHWJTcmMXW1uHwBIXeU641DTv1AaB+xNN2NEkuWTKQQjDfVkg\r\n3qCRkWg8qy60INn7eZeDkpEqWlvXqxZ4BwE=\r\n=wnv8\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"daace13eecc294eb29e90dbb6a4fe86441f8347b","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-23T21:02:22.299Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.3","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.15","@balena/jellyfish-logger":"^4.0.37","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^8.0.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.12","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_15.0.0-update-redis-daace13eecc294eb29e90dbb6a4fe86441f8347b_1645650351059_0.1599770283667865","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"15.0.0":{"name":"@balena/jellyfish-core","version":"15.0.0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@15.0.0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ba004d468b9841dbbfeaed4f2fa3de310573685a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-15.0.0.tgz","fileCount":238,"integrity":"sha512-qV9iaZrGGa4p5ZXQ3XCcOxOqr9hFC28cqTMm49Vul8og9MEfoy0Rjx3CFKANqejg3zwlUivWWSkkN93dUYIWUA==","signatures":[{"sig":"MEUCIQC+2JN3t3YfT4JBJP4GuXmus6tP31PyKBQ9gQM1o4wxcgIgGx8tZeH3VUxYnqsi+3eNAqqMasTa5t26AjBuuXAXj/c=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":845802,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiFqNKACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2Vmq8wA/+Jbgp4sANE3K/4YqBAGu0+EXwnaiHQxwi7QibHpA6J/PGuaKZ\r\ndHT3E6mo3qerazGaKGx9jjltGDtsknrwBR9/dIXI9izCRCzH+hdbkvR+q9M9\r\nMQkqK03UGCtN2ufr+w/7xzs8VMISICm8YiQfFSafGNM3dYGNaIXlJd6TAOnT\r\nv6LfB25N2NvxfB4akPz3hUeeITaHUMR9/4Wj4jAUZZ0Bfz3HATQDW2edo3og\r\naVRofMu9I+W+6zMTNdQdDbeLvNAuNHHUzE6hCG50pWfbiQ1+3mO22C18evjz\r\nPBcIazEUCelYwR2Of4SK7R7zLLP3eie8B4hGWaaCFJXrLU0sT1Z+TGb31KSj\r\naPbPHYp9Cbfgz4kYsukms77c7CcJI3wKhTrQRazX/ilI6fjNImEKxuSDrvYc\r\nmsYEM/Gg6/5w6OnOfsg6KaiLOtapbaowOukSsuLgCNZPInf+nlY+ZE4PhYqP\r\nOVreGuMiweyLye54yNbhaJFeJ8SEEpxgtRN9/PD/gG9Ccow+TF9v2VTj8HZ3\r\n0L+aiPC9YELBQmd7l2wpygVetJlZyLQnO65tXUa8fbzQ91RPr8Gv0ou0yuuH\r\n2R1Lv0czb+D2kAc0yYcWOciWEeeFKFPaODg94OgEYspGuHWrvsI1V3oM7GfM\r\ns7t0PTT6i8GmGpWfTz9tDNw9duUASfpiV/E=\r\n=Vrjj\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"fb6293ecbedf97bdc7d879a5fa8f5a433542152a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-23T21:10:31.201Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.3","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.15","@balena/jellyfish-logger":"^4.0.37","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^8.0.0"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.12","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_15.0.0_1645650761788_0.9857888891892908","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"15.0.0-renovate-major-json-schema-org-e0a7b6aefb17fa5a3e8264545d3226395a0da784":{"name":"@balena/jellyfish-core","version":"15.0.0-renovate-major-json-schema-org-e0a7b6aefb17fa5a3e8264545d3226395a0da784","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@15.0.0-renovate-major-json-schema-org-e0a7b6aefb17fa5a3e8264545d3226395a0da784","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1d605d9dfd4e7d597c4bbb69d62d7bca97fdb8fb","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-15.0.0-renovate-major-json-schema-org-e0a7b6aefb17fa5a3e8264545d3226395a0da784.tgz","fileCount":238,"integrity":"sha512-t4vTrmOk2SB12WSZ+oqWtcwFR5XZ4I5OXp4LAVZcmqPnnlOPYNwGVSHwOz5vucFkituJdP4fpNzvC/Qpl7XK9g==","signatures":[{"sig":"MEUCIQCkqYe9Rub7zqCUm5F5gjHI4s/NEiEPzFaj1IEPubiOCQIgWtNakk6Gf5oN3zgmpAqCz43WZhs1tdm36cOSe566+Yc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":845934,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiFqO7ACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmpMzRAAjUkK17geT33RlqFqMv/CeJX7Bj6EBYojHLgez1aSWmwvrk3F\r\nZOGcCfbB05lrm+T6pouUYY726pFcx0SyNkFxxcj3rSi6yOvhUL0cGcBChyAs\r\nNj+3GmUTEXJL1AvhLDRv6nxxzlhJcjq/eNxuQMElhiqExxuCJ0OdIlNpAZZa\r\nlb1z51Pm5sywLstMDXAi9rSZsq0fSnwrw1CKFmix+88wNYnd6nuXYSW82aFh\r\nxAASgopw8VcPO7LawZeupY3f8XUdlIhf5NnPhc/iLiqo7u3XscBa31RsNUd0\r\nhJifLwPuLepsj38pShkjn+WQVQkhIZO1pOnSuUBkwHkl3aUE92Ud48Fc7IJ1\r\nae+nrqd8TQKGfjimlUaFoBGMCbgsd9AGTLoMDAiYWyo/p7+E9rEmT/35m458\r\nB97y0sj0+JP9zhR1ba8mQzi8jEAhptqPH0qX8FB7YbvYJE6MPciy3f3A4ig+\r\noZpfWXCyViE86vro14OE0kuGhkBMhrDfsDpmjUXFMUgVNU6IprxetwEB4dSF\r\n2Yf9bF75bgG41sdnQXRzdkdTy1+JQsFCNd45T5Kh+jcAQK4Nmh8B5IWO/Rhz\r\n9FuR4z5uk89xMplGZ0t/Fpr7b6qWkqtZ1tIOG0L/52ZFFWDZuWz1wP3j+88s\r\nd8c/8up7APgtgpv4IZigoqF9kJMTsdOJD0w=\r\n=AVKe\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e0a7b6aefb17fa5a3e8264545d3226395a0da784","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-23T21:11:49.948Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.3","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.15","@balena/jellyfish-logger":"^4.0.37","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^8.0.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.12","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_15.0.0-renovate-major-json-schema-org-e0a7b6aefb17fa5a3e8264545d3226395a0da784_1645650875472_0.19763164490947793","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"15.0.1-renovate-major-external-major-8cc4720f0882914747191bdd911b66c08d339c0e":{"name":"@balena/jellyfish-core","version":"15.0.1-renovate-major-external-major-8cc4720f0882914747191bdd911b66c08d339c0e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@15.0.1-renovate-major-external-major-8cc4720f0882914747191bdd911b66c08d339c0e","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9bc38427ba395c491dbdb1b04575ff7a73da3e9b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-15.0.1-renovate-major-external-major-8cc4720f0882914747191bdd911b66c08d339c0e.tgz","fileCount":238,"integrity":"sha512-iDsXk3rrbqeYE+OK7uoER79Ps80UXR/TPqDvprWHY6tjuiZkkRnMFlN7+aHtov4FOD/207GQ9sqw7bZQpjM4Nw==","signatures":[{"sig":"MEUCIQDOBPGM+AcSOtHykRjqewBrkb9O1WR2U+7O0mUaZltudAIgK6Zd4ANOmvsTbFhlugJayl4ZgNLAquL2z+MZP8OXMpk=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":845937,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiFqQFACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmqsBxAAnpScN2xzNoU5z4eHT14uskS8061soAnrPBq1m8F2BBMuIcGD\r\ngM6mn4DZym2EEXWLQoPLAmORTyyJkHXUkA3EQc9/BLnrhmCzfvJ14KJfxPCj\r\nuL3MoVDsds+0/hrgGAuCPDEvEnYVEv8+dCnhZQGX8BoROydsW/jauYCmsAbL\r\nQBhhxBOEqGASlQ2XPWHr22QDDwERqn6+7KjefeHTE7Y9s3eSVOnHJDx8YGsT\r\nujGAiq3h9ygd38jh7GmDJGC6LbDyHwwCKkPz5kh9nlt37rVFCAblfAwkrD73\r\n6VRlxF+Usi3UPiBifEZTd5hgNKh1kIFxrNbtV9CZmQPIIUp5/CGjCSWL4se/\r\nIIT7kEYmaN8Z5FVJG0PpNXJkgC9sA1FkwF14ODLJQuy7VccWhv0BLCGwC4ee\r\nQkUCmEfyghL60yGN8MtgOb8v2HvpT/PC5GBC8KESgG6Z+rrUZ5C71Va1SMho\r\nihmX0wTh0ijFzjm6M4aVyuEYyfsERTVIi1zZjYsJ5TGL7pRwLQ4bXfWh/Ca1\r\nCbJq9/tA5ydi5GIxgQhOONNqtjdIpII6hBGNqWlgCj5ppgS5OtgXLlbQlzsw\r\nX8jjMpB8jQ2hLKEurvjfmSwdghnf9R8FCYGkZZvcM5YrMwHhukPzeiXS5rVo\r\n+0qU205mhfbrtwEtarOPlJjxHSsf2g4nFlE=\r\n=tbAL\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8cc4720f0882914747191bdd911b66c08d339c0e","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-23T21:12:26.511Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.3","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^3.0.0","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.15","@balena/jellyfish-logger":"^4.0.37","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^8.0.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.12","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_15.0.1-renovate-major-external-major-8cc4720f0882914747191bdd911b66c08d339c0e_1645650949296_0.3715662369852477","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"15.0.1-renovate-major-json-schema-org-c9b89744602adfd220f52eb19bb7f79e11d68e5f":{"name":"@balena/jellyfish-core","version":"15.0.1-renovate-major-json-schema-org-c9b89744602adfd220f52eb19bb7f79e11d68e5f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@15.0.1-renovate-major-json-schema-org-c9b89744602adfd220f52eb19bb7f79e11d68e5f","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"58fc44c540b79e942c45955ccedcdb2a13562a8f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-15.0.1-renovate-major-json-schema-org-c9b89744602adfd220f52eb19bb7f79e11d68e5f.tgz","fileCount":238,"integrity":"sha512-ug1+4P63ZMWDBbgcJgWChC4JwQdshEN57QNcEsKD36w6YADRze/Ue3oyrVuXzv6xSwOQfE54wSplf3VsY87lpA==","signatures":[{"sig":"MEYCIQD2SaJBhSStwAToM80hrC1IcSMaEIgapACpEe1u9lhPQAIhALxLcJ6cgpSHq7DYAyIuYZ+5epPmpTEazStYS8n85T39","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":845959,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiFrwsACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2Vmp/wQ//fyGaoXZiOZDv+ckZpwnmGCe7fndIlvDaLAUyby2/oLbhPKkc\r\n/feJlISGe4BbggnpSFQaeRNohQ/eI/sagVFZXGgFtKIq8RnJ205WFQhREcgq\r\nyNCDXhMCibUOctKOGiHqLdULYqewWsVnv/87ebVn4mYwKJejd0qUHkpmQoNv\r\nltpd6C9dZUzQYTURsa3boQVDmQ8bxLGqu9Fo1nfyHvb80Biizba1Owyjveje\r\nXOctZge7tpESya+9cVaYeNYz/m0zC3eleROPr3k6s4K+c73klOWbgUCuCccM\r\nXgOC6IMKkK7nclpWo3dDB3vZNk5fKebQSu1vUKZb/oqVXMtm0gOshl+NpkyM\r\ndT6g63qI7LrY1sOcWJExJGrfJdroaiK2FgRtjt37pYx3f70H0rhkfGo0dN5K\r\n4XerX+CLCKkMoKgZnsMnuZSktv+O61Ku/d5JtubcR92CkxIz/3ebLeC1a51S\r\nVER8DcarbKAAqsEdcNCKSVZ37yEskn5GcZ+DUjN6t+5pWR5JobUvcBc4KFvd\r\ndYwaIBlgX/3FjMkOiSX0xQrG1q1J3QdD71ucZZX4QvPLAnS7xNYumXG3gZUP\r\noBTdfySInVktYA7WT83YwzoWG7R8BTZhtlAInXGIbQWT3R/T4/PfVamALG3O\r\n/q6XTR/znfFM6Zoe3ozMPk0OnRIUaPJwx/E=\r\n=pZrn\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c9b89744602adfd220f52eb19bb7f79e11d68e5f","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-23T21:15:20.603Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.3","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.15","@balena/jellyfish-logger":"^4.0.37","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^8.0.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.12","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_15.0.1-renovate-major-json-schema-org-c9b89744602adfd220f52eb19bb7f79e11d68e5f_1645657131941_0.10314328147538698","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"15.1.0-mask-test-cea9d4924bb888d8e1c063edee4b00e46ebdc2d1":{"name":"@balena/jellyfish-core","version":"15.1.0-mask-test-cea9d4924bb888d8e1c063edee4b00e46ebdc2d1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@15.1.0-mask-test-cea9d4924bb888d8e1c063edee4b00e46ebdc2d1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b25542bd2890d618b7a4bab32391018919657af0","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-15.1.0-mask-test-cea9d4924bb888d8e1c063edee4b00e46ebdc2d1.tgz","fileCount":238,"integrity":"sha512-ICHDlxIMiaBCTwLt4Ua1zwNoncvwKfL/Cv0y8evRtrYoOI0R4wjCSg5iZnLUgNWo69CgGYsaBCT3XY6w38dxXg==","signatures":[{"sig":"MEQCICTFIYZAFD/gYOphascgxjke0v+ZoXroWMlXA3CeteqBAiADwjhYMiFFLGKpPCUX3B0HCQeH765Vlu99XD9nlyFf/w==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":845939,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiF0ZKACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmpaOBAAgeKLkZNmK27XxQpHzgtVqdJBJabkEQuZ02Ycu70AVs4EA1Ta\r\nik9jVaWTzn8aRemeU8y9h35pwrFu29TQudJEPj9FjqCop5NnGnVYS0B7iC3X\r\nQCiS9rQm+n317rnQW+0d5CE7pdPG0zWRQBnM6AZQj7+TfVPm+FeWRpnhR5Vs\r\n7jEkBEjSYJISoMEdtmoB6SplNCGY3+1p+UXTufEOh6jfX+VVewpqoV4k5urS\r\nAhAtYsQ91ayYrAGsC6SgFHQx5wuE6GMBpBDW1TItK+Pt72icQnc1TxD0dXNb\r\nmmQZIe9fGACAjn7zt5XSvw+YuZvYdqkhsZxaPC0o1oLL9tTVkllHwmTEgNxs\r\n3Zj9kM5tctVA7ugVnsPE7ayeiir24V+7h8RN8maCqPOYgnGTBNSFErJw+pae\r\nvsRU1jYkmpGrxL1nBnP99r26ZihCE0FkCblwdZ8Sa4c2KyUZkBhej4Nq0eN0\r\nh3c5DCx5HLT7M9bABiMMrtmxfG0cY8dsRxiasoC7m6Oqnu74SOySWZJ771QK\r\nfNfw7/+s4SwNIkWDp7jxoDSLQ0INhn6vXEkt1HuyVAYEkBRQa+6F3Q9VZBDo\r\n/P5N8iAKZxrHv6iSb8A30hv+J9gdzfW00QTK3xoTydpocoA3mQUqFpYAfNBc\r\nHStmK0Bf9r+tIXQaVT+LoQryXs2CzzLTEP8=\r\n=PlX9\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"cea9d4924bb888d8e1c063edee4b00e46ebdc2d1","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-24T08:44:35.052Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.3","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.15","@balena/jellyfish-logger":"^4.0.37","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^8.0.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.12","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_15.1.0-mask-test-cea9d4924bb888d8e1c063edee4b00e46ebdc2d1_1645692490207_0.5781444036731798","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"15.1.0-mask-test-24be3323a51a3fd1cdb5f4a0cf0de9d1f42ea1b8":{"name":"@balena/jellyfish-core","version":"15.1.0-mask-test-24be3323a51a3fd1cdb5f4a0cf0de9d1f42ea1b8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@15.1.0-mask-test-24be3323a51a3fd1cdb5f4a0cf0de9d1f42ea1b8","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c870426bff21bdebbbde77f721d22c41a9e0c6d2","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-15.1.0-mask-test-24be3323a51a3fd1cdb5f4a0cf0de9d1f42ea1b8.tgz","fileCount":238,"integrity":"sha512-GXteDUjbKd3thB3qeC/IvtTgUR622shjc0wYhGz6HPxHbiroXtdoS45Yx6++1K6ULSjKI0SlYZwk//ICMoUkuA==","signatures":[{"sig":"MEYCIQCv167KmNqBPBiAHnaBevV4Vhf7rA9VdjOtYgOH1LgQHQIhANqQhKjSgMLuf+KX62gwgFjy6K07OcHIvw9l6imiq43x","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":845939,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiF0pMACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2Vmpr9BAAi+yJI42aMq6SW6qEjMIjpECAsQ4DUrClqNq/41lmQ1v/kr1K\r\nHJfdei75ui1dYj176tUNEYuCH98fpAlwbvrT6aX9J41cIB/T9EQVtbAcqe06\r\nYiOvAQ65uiw8RRENmkRy2lsy0BPdjTkY2Z+Nb6KPWMq3PbLImGRb2uoyS10i\r\n3QjJPzLlaZXM9cAF8psl2q1XceV5dNRGVndQ8I5IGBfcJXMJh64Uh86GI3Wa\r\n6OG1NJZ5S47669I54QZTM8JrGB2Pycr36o+QpYt5ASQlnDapY+3FmRv4ppYk\r\ni/rUhoEyEvBTRofBggK0FU/yjXsIm6dBfJvpmS/8ixfetTo6cmpInZZ1OVnT\r\ne98PzKUo6CgbrccLv7Y6xSmwgyEK+/8VjerKV47IFGCwLYZKBPct9QG5tuhw\r\n785WGGmvW9BGxkeMW0do7MurCwPkZWKYXw9hDC3L6UGFTZi2Axl7kWrkqw/w\r\niHGPWx5Bo8Bm6w+4G+JA+IcQfxlNP3aPjFNaxchcBzzSztB5DfPiaGKULgDf\r\n9FqRpD5DvXxCYW1anDDErpS1EkHJAzGahsjjIkG2nzfRvqCt1xJUMDPTBSlB\r\nRqlxoDHRLfLTqeWcG+sg/TuDOMT19kMKf1V1EOSvk7hLtKoNxcJFKchZll1M\r\nc+EBRi/PvQnBtv+CR8Dehu1XVcE6ejug5oo=\r\n=VUEh\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"24be3323a51a3fd1cdb5f4a0cf0de9d1f42ea1b8","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-24T09:01:58.627Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.3","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.15","@balena/jellyfish-logger":"^4.0.37","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^8.0.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.12","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_15.1.0-mask-test-24be3323a51a3fd1cdb5f4a0cf0de9d1f42ea1b8_1645693516741_0.052801722134031115","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"15.1.0-mask-test-7abdd05595eea18e511451cbf787e6d42bad5e0f":{"name":"@balena/jellyfish-core","version":"15.1.0-mask-test-7abdd05595eea18e511451cbf787e6d42bad5e0f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@15.1.0-mask-test-7abdd05595eea18e511451cbf787e6d42bad5e0f","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b871c4de40da051ecf7e85dfb8fa999f95daf659","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-15.1.0-mask-test-7abdd05595eea18e511451cbf787e6d42bad5e0f.tgz","fileCount":238,"integrity":"sha512-yvkiPX4Ry96DiRMCeg1ZSLUqdWYEjUTFwgIqNVZEBrPihROqoEJdHbmjH5gYHFFX6NyIHZPqJGGIdLJdfm5YFQ==","signatures":[{"sig":"MEYCIQCspe5gY2WI78DiSAsEjdGzCjPEUSrl/rXJdjglZvV/SgIhANgQap9oUxaMSXHTMrPmDRSpkiyU/Ac994t7mg9tPdFT","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":845939,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiF01yACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmptRw//R1GIQMv+uwyhJtpCu7dWq9dbj+mp9KtBxvLNkkxL0ZVzzrkN\r\nMpuL1iTTnH5NSv58iEFsAtDggfNEy4Z9bDFl9gngrTUG71lsoaUFc6n7C/i6\r\nPhsTakLd8V+aFkblVszWZbx5RfCdmz9bfMqj7O2ByYP9G81kt9g0tsbAKG3N\r\njaeSyx49g7xTPr2NVl/LIqT7pQWsR91dVR05XhPHMzIyMzV2wR4qC0vu+PCe\r\ntxIjrCRUXatZeQlJQBSds3Fkqs2iVlkSD/SBxlMgCrnHLfb0rbSxliKTD19r\r\nK5p04HScA+7JYzR6AOwRcVZKPbYgXO+Y5v0PIUfBk9EoH1D/oEz9flMXoxxC\r\nJdTQCyDbsX0v/k7nawYtEEhJl1pVQ7GvJxORBzA65q2NY2fFpQxcuiFkLDnJ\r\nzpUgx8fmPuwmWWAKrl/EWI1+nF0pSF8piTZUjRcdID287C9IxsXGWNRbYtMM\r\nCn/OxX9ADmRStcRiL6dcuRNxm08JmQ+5IBp1yFz81T5nOgJfubTQ0RrCef/z\r\n97N8i0agpbhuozxxgPJbIJ4jFHWorUsDWJXUoDwFYes2kK+qOOkpeb2i4cbN\r\nK0+UQOFZChM79r8u+d6Dfsfa2VFMZ0t3LTPzDouWv8R1FUFjebYQLsb5iaHk\r\nK2O1iOLlCuINiQezRkIlOK9DrTlE12nsin8=\r\n=aNAV\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7abdd05595eea18e511451cbf787e6d42bad5e0f","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-24T09:14:52.337Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.3","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.15","@balena/jellyfish-logger":"^4.0.37","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^8.0.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.12","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_15.1.0-mask-test-7abdd05595eea18e511451cbf787e6d42bad5e0f_1645694322596_0.8248503370539482","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"15.1.0-mask-test-ee8da7205fe71ca4d65b978809ead2855d4c4258":{"name":"@balena/jellyfish-core","version":"15.1.0-mask-test-ee8da7205fe71ca4d65b978809ead2855d4c4258","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@15.1.0-mask-test-ee8da7205fe71ca4d65b978809ead2855d4c4258","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"03ae51b22b957f8668af9f3bc65f59a71e602247","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-15.1.0-mask-test-ee8da7205fe71ca4d65b978809ead2855d4c4258.tgz","fileCount":238,"integrity":"sha512-wTB22jPBCUGY2DrL8EpUHuS24H/As9CLuZ1/Q7c2iqo33OQXIKGyGYTsiwycWLeceH21yTRXjebnfjXzGyIypQ==","signatures":[{"sig":"MEQCIBvjXnOxRSNrpjIhzZl11Ynzt4L7a59V/tf3mZj2yFx7AiA9MKp7fwR1v0Olt3I1S9dlTXACVP90Tgg+OTdZHQJ8HA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":845939,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiF1GwACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2Vmog9w//d6lIWPAWMonMxRXAQNLvLpXiE82Up0T+/gDM2TDvSNw43+HI\r\neuK9bs7uOsGTT2O3v+HZ2N7l049YCmdd1nlxToulttU6/mBV+9Zg3vy7ww57\r\nf0+YPfqHxW5IjLFEZnhAbKYjTO+CSHJS00H4NxPbz7XU/Lm39EdOuWnEF+vj\r\n25FjkckXiRHeemva3dXS5R+hOzqY1xUEbKXTILfoi7TMTLKdOxltOoRXTWOZ\r\n+RDJy/0k24iLj02aVSzhFOv1T5vdwdd6SyupPfPd1mPZ0nbGYgwJPJnZc7LU\r\nzU4+Irkb0UJvqKbkP6QNJzg4BxDrK+zsfaTf/UcrPqnjdY1IfS5lfBez7Kvx\r\naFRf/2xEvQjzEc0tSeSb0ASmMSTUayM8/cE2Xy11XL5WgX1pyvCOOvh8OYWa\r\n59z1WG3V3t2s2FiZgptv5YnbXIWu01UCs82YF0AH0WoO6lOxe+yOOlbNYr1P\r\nEnW/DBUb1PZDbSss2W2srx2qeLp6rMUaWk4hVunm/m0iDaXKJ0Ti4aY06Ftq\r\ncRh1i5ZZRhDWL5Ro/Fg+YSd6YnRX5PVunZLS6qdD16mO7w6WazYPelfwCg1o\r\nDBxki/BMklRltdERpxu4vCS8T1wxkORG7GmJuyRacr59GfowR1HE9+7ADCG1\r\nErxfTbaG/W54Am1KaJFurXOds11tDFXPq38=\r\n=gRrm\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ee8da7205fe71ca4d65b978809ead2855d4c4258","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-24T09:33:34.095Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.3","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.15","@balena/jellyfish-logger":"^4.0.37","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^8.0.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.12","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_15.1.0-mask-test-ee8da7205fe71ca4d65b978809ead2855d4c4258_1645695408764_0.5339947131130982","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"15.0.1-renovate-major-external-major-c3f6eb5df02a2fc50a545a7e395a9aa0631bd791":{"name":"@balena/jellyfish-core","version":"15.0.1-renovate-major-external-major-c3f6eb5df02a2fc50a545a7e395a9aa0631bd791","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@15.0.1-renovate-major-external-major-c3f6eb5df02a2fc50a545a7e395a9aa0631bd791","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"73b3375f8b7c4581ce7826ee7afeba4c61a50865","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-15.0.1-renovate-major-external-major-c3f6eb5df02a2fc50a545a7e395a9aa0631bd791.tgz","fileCount":238,"integrity":"sha512-WD4ybhP1OdjzEcEpLLimFhPVd3xckYLMizdoK3jXVEUv4HQP1otqh34JvTJA/A06iCqFUe6lpbtV9PcCSV/smA==","signatures":[{"sig":"MEUCIF9rIa/RCis3knVej+Ergo2a8uJD5G8pNMgsUvwJ6kOfAiEA4vnWNpLX5NEW5NCuWjZP+DJymeNFA5D6ZisoTCesaWI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":845937,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiF96IACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2Vmq7nBAAjfcZg/09zBR+N5uBWDw3qiQMgvQW5bZB2WYy/dAtdMUt4tOk\r\nr/GjihtiEheQF0glTE6G7q+VB95bzCOe2MXaM2287Ll+A+8v7dVO+ZEuF44N\r\nF7pTnhHPXKVj2KIznjb3Dea4zvWo0JX72ePBXc7TKVycXFYInu5FXjmf8BDE\r\neJkNFamZnfoKZVX5rc/TduMAeK0GiD8dmICRS2zWot6DwhKyV9SVH9a1Yqi2\r\nexi8cULgcI78wP4CiYqBgQnNP3ABhfythu0DWOYcNxs3bbSRxCdUPIG+srS6\r\nsX+8B8AuKOAmJ0taeomyDJISuLoFs0KRjnc5GcF0rxXuQ9BIn6G6hllGRRyx\r\n9HY2S8kfRw2FImAicoRzVfBMcJUJuCpGXDQHErMGVuHmu+9guD+Rx2w5Zsqc\r\nPKl9DflS3KnNuj952MKOZIkcwlCJHoR8//DuIyAzBYvmOI6j2JMskUqyr+lX\r\nUwaqZJS+/1zuhGqEgvga51kJMSO8qQQY5df3Fhac90NvA4VQQLyN736yaB7P\r\nYukAkSCRKHimfUdb+HKYnPO6clAJPgDdiFRxKeKzWP4gMVvI+kZ5W33XqQ8p\r\nhOlTtm2pG2L88O2tULAphWmZb5tKiIY5q+TNCAA/lXUQyykK5ZljeB66z4HU\r\nFwlIvLTBb+IeaQboSYpWTAGoNtwrhD4sAoE=\r\n=wXwB\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c3f6eb5df02a2fc50a545a7e395a9aa0631bd791","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-24T19:35:11.627Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.3","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^3.0.0","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.15","@balena/jellyfish-logger":"^4.0.37","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^8.0.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.12","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_15.0.1-renovate-major-external-major-c3f6eb5df02a2fc50a545a7e395a9aa0631bd791_1645731464100_0.8230765416208035","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"15.0.1-joshbwlng-bump-image-6d075210ee14a67fe5fa4bb01880a61fb6952146":{"name":"@balena/jellyfish-core","version":"15.0.1-joshbwlng-bump-image-6d075210ee14a67fe5fa4bb01880a61fb6952146","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@15.0.1-joshbwlng-bump-image-6d075210ee14a67fe5fa4bb01880a61fb6952146","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d99fed8dfba519e9cd6bca05d767e8b096fbc6ee","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-15.0.1-joshbwlng-bump-image-6d075210ee14a67fe5fa4bb01880a61fb6952146.tgz","fileCount":238,"integrity":"sha512-R3QNlR32s2QuxZGS5WOY4iH03H+iqdyI2OGVP6x3oygZmZU+zYsgvlknqLRmZ+DvyktqMED8feMuqeveN9e4XA==","signatures":[{"sig":"MEYCIQDAxhmXIqgkh9GW4JhtFcKOeAofE/sCIUfSFN/nXYepSgIhAJHCSz+zne4gSYydTiPGYGIvtRuSO4gU5E7cVsgk/z6F","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":845925,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiGEwjACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmoP2g//csjAk+oW9hbUlh7ydvGbO+0Mrzwk0nKofURS2LR/zLg4iPyN\r\nMOzAbfbRBtL4tU/8PYjBvNU6t3k92T3fH/3vTjBP7kQacNQ72+FROSWOSxon\r\n6x8a8Is9EaFqPypBSdFhMbtQ5tsHECtTOLJ6MKpfPhjI16G0Q0muMglIqMyj\r\n3d1aYVpp2QRDM9c/KeoQH2Lz/aDWtUdbjEjYYK9aCzQK4k46+Th/FUGIzjAC\r\nTJGOEANeu49BkXscTwgMD5T66VLjJL9gsY+HBXHGzGZ8YETLGoZFwg/0H8oH\r\nhwPxjvxalEZTqiMxKOhsKAhT2bxiMZM588K0oD4G7PMmO3F0nEIOqjP6bPr6\r\npyCMP/CNrtN41QP7L0aN/p3h8cpdMkCQPyntm0ZruvlziVB7ZYN7ZaOkL4f0\r\n9pFhnxOQA/IIDEMEyDXlgYF3Mih3TQNtBjTgSv3f9+8fT51M/xXvqFF1o1Vb\r\nEs1iemCFPZFlbpO5DyYt25IH8S07hG0Z0el6A/ym3mdeEllz8iKXnQjmB7L4\r\nsTPs26fDoh+0R2c694iLwCsfi7gmo3Zhf06nYWNmigwBdOlViPsCPZuOd/eM\r\nOMDwjU6nSZt7gXm7rBGS4v4gNRDCR8Ib1x+SL3EFYwc/lybqJ+e+MNvhPN90\r\nFAQgdQCV32ilYj7W80mviNqqwM49HMoL/Q0=\r\n=8dvq\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6d075210ee14a67fe5fa4bb01880a61fb6952146","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-25T03:22:02.753Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.3","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.15","@balena/jellyfish-logger":"^4.0.37","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^8.0.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.12","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_15.0.1-joshbwlng-bump-image-6d075210ee14a67fe5fa4bb01880a61fb6952146_1645759523601_0.295781018660666","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"15.0.1":{"name":"@balena/jellyfish-core","version":"15.0.1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@15.0.1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"dfbb11be10d3a79ea6427719cdca7c3d14bd7479","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-15.0.1.tgz","fileCount":238,"integrity":"sha512-arMj5rv/I5VPTbHz7EcmF7ay7DRfUvXGitcFJgGPDP65GcEfcUWNWcLJ3XfG4J4TdbNg+NVLojOxBSVaoq+f7A==","signatures":[{"sig":"MEUCICU0+jCnIyl2eDBERx9sdzInouEW5rzvN+C1Yw/GDRcUAiEA2+rh2stP5MlkfUQRpB+6ptHHTMR8cKs2CNFf+cXWkAQ=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":845863,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiGE0tACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmrVlw//fi5X+CS818nEnRlPKBVcBJn2gnrkkaBzeNjZ9By+ONtRI3nV\r\nnmU6u9D0MLpcnSG0lJLaJkvOgVvuEHpHKsc+75LUR/KJopcVI3CsFWUjytfY\r\nhi1Wg0Ftfo7NXUOK2Qc3pJw43YyFaWR5+I7frblVfUY20wfZNUOiaYlKJvqP\r\nA+Scl+lKherlLZc8xDgeriUAlUYCR7B/eXUjQBu5kZUjAUAt9a+/jTB3jh+V\r\n3NUHuj/92Rf4wDNN86wQK3poNldB8ZCwK1KPAJJ93XWHylGJUF8dLKd8/d3N\r\ndyOla5yVztM5teP4h2Q1VUvL2wwQ+g8tblmpRaXhZyfrW/FSN7c1RzgnvqiH\r\nO/MLYUtVy+sVKEsecBC951k9M97OP52ySbp+D9dSq8fKRyaFqyE5TdWmkUsR\r\nD1PYwZjLSwniGI1Ki23RW0rPy/4dIY2Ps6gAfrQbtvd313SF1aeTBwHwlws2\r\ndRwTlGADDpRZRfLYwi367y/mEHXBSFWzqzEHXFjSFp3tBCtphs+u0O25Ivo8\r\nE+yVfV7x9t6YEwCEog/prW4sVj5pGdteolF7TjSmn6Mtnq+WYFeCQzjInWPW\r\ntYK5zxCp7ypbfyK47n7PZqTiyk6zb5Xo+LJ0F5boRkH4GkDJWWxfOcomzKCv\r\nEE5FjS4i6Sn/Sfl6MkA9fUrPm4AHNN8JgxM=\r\n=a1yk\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7fcea11b58917d2fd87e507b15625fb2ee13ac0c","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-25T03:27:54.710Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.3","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.15","@balena/jellyfish-logger":"^4.0.37","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^8.0.0"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.12","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_15.0.1_1645759789671_0.5476218185459643","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"15.0.2-renovate-major-json-schema-org-eb27ca26fc936c2ecd52c0540590c5598bb6b3b1":{"name":"@balena/jellyfish-core","version":"15.0.2-renovate-major-json-schema-org-eb27ca26fc936c2ecd52c0540590c5598bb6b3b1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@15.0.2-renovate-major-json-schema-org-eb27ca26fc936c2ecd52c0540590c5598bb6b3b1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f9ee04a4f6787ff5e83e6c68c4e21818ca81f5a1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-15.0.2-renovate-major-json-schema-org-eb27ca26fc936c2ecd52c0540590c5598bb6b3b1.tgz","fileCount":238,"integrity":"sha512-qpahr3Sr/Q10eiQgPwFfy5ZKOR+xSeggHY5PQsxNMqzYzVffThvA1x+BSy0YeRhlJv9x0GppR2VHXfCNa8Gq0Q==","signatures":[{"sig":"MEUCIF4b1X+UF/JE1YsKWr/ZRcWnaQDuDjGYD2rxEg8xGyQnAiEAnWQ9ib/B3cSM0/XHMCC03w5RSCLDyOXW4zHP79vRWu8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":846020,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiGE4EACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2Vmp80xAAhs6s4qQ4ACcwMCPHDK6YvPB+qPC9w+vyrzhVqpVan5sX7i9M\r\nuB5lH1hSl1wzO0FYAhJDv2oy95tqCe/4TmiYdz2NotblPSVKAQtfjZbv5KYY\r\niAs5ARafAxxOHGJYB5roblC8L2ZEwhghijf5OTpH5MBBbHrZNnYfFn5aQUQK\r\nuUVprz62u0b6PB4dVeHFstREKYfsqGHIwHuC5pkQS1dEMygdfGv8hq+5gvXO\r\niwg9UGvz8gMSCFUJZo361v3ylm3/7ltFHpC8JI8cs/gWDcceAYESCToqjEbr\r\n0qRolOMl68S+vqoGeczq1PZaVa38+rZtbRy/iRIzMWTnjQ+SwEqm//R/v7x4\r\nOBQICqfZMRYbnIeq1LiG0/SaPFMtr8ToEjPiCTruS5RtunoeJWImYOKfr9fG\r\n3LRowetMcr6SApHICVdk//v+2k7UQ42pRQt7FLRwrcWgzTGHbEkjfSkFRitq\r\n4GrMyPdN/v9x3AOvpu/hEgVzAvKdX9bCOlEql4os6is7dtZi9V3u0EmCJmDV\r\nliqcb16+TyyNAd6N16MiJir5xDAcgj8mOrJHfKOHosd+90Xs6SZbfJePqolD\r\nXuYbhhkYjUZVWcu/IVhuWjFTGan81qP4tvsbQySTvQaWmKDtj+zg1u1wUiIw\r\n+J/Jc+aD+Ay8F4ccWTcJRW6v1MWqMoFoITU=\r\n=vk/k\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"eb27ca26fc936c2ecd52c0540590c5598bb6b3b1","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-25T03:30:04.701Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.3","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.15","@balena/jellyfish-logger":"^4.0.37","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^8.0.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.12","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_15.0.2-renovate-major-json-schema-org-eb27ca26fc936c2ecd52c0540590c5598bb6b3b1_1645760004296_0.4894760411234851","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"15.0.2-renovate-major-external-major-675b3ea148cdb27a8976d041c7e796388542466e":{"name":"@balena/jellyfish-core","version":"15.0.2-renovate-major-external-major-675b3ea148cdb27a8976d041c7e796388542466e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@15.0.2-renovate-major-external-major-675b3ea148cdb27a8976d041c7e796388542466e","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"36bb2f199c1e59fd15ae364662256a981cbb46c6","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-15.0.2-renovate-major-external-major-675b3ea148cdb27a8976d041c7e796388542466e.tgz","fileCount":238,"integrity":"sha512-K3ZZVGTXirRwMHbzdNLKuU91dsXPQGm8PZWQyWmQPrvqFPjy0UsPIxBlDAZcfujiuG+na40mJmi2IIU89H4G7g==","signatures":[{"sig":"MEUCIQDYP0ML+fzLGhtDxqp+zSd1z3YhwQ6Mh398o1+2nFw76wIgIIPTVLni+3I7NqJkgcP1ThqFys34aYRHBNWmwbeo+3o=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":846019,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiGE6fACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2Vmo31g//Wi8Qt/6GK+s+jQ84GrIctAqIUG8qUIFd4d0vahZs9hUDLngd\r\ngMaG8rprEDLk8JN3xR/W4kc6zPZCribSnweg0Z07pvfQeGiW70EDRHlKKhz9\r\n6dSnr5V3GXgKObTVbZb+cAxBXwKeTLFx9Hzku2M/iReT+9YGxTKM4m9fU6NP\r\nLv2Q4YyILERnc6bF4SKCTOGVRwLmSBjcDTg3cJwbvTzI96Qfuw/QSjcd2uHT\r\ngyhgS4rikn4QN+F7fW9HmVZat2D0hz0OiX9KQaZHDlsD5hlSXzAWazOluyMP\r\nxe2SoIFu9m2XSGMndVnU+aWVBLbFRrh/vkfRTSOGmOeb4wu8J9qyA7ao8dtr\r\nBJvtTEKa12d9tXTeFqQGkiogmMIBuZVDsgFDST+rgu+vWpURTsCcRCIcTkuz\r\nT3dsmqzKG0mltNQBqGttSQ2zE8iUhUw+HOUt7x7a+1WTI1HluZDx+VASGqnW\r\nVJYSA0v8eBrbmR2oobSW2Pv+eV+N5v0Cw1LnuTvmnGne7lJc5cz5DT1x/k5D\r\nyTDHp2WDSXO9T4uPSoma09wTcfxeycxNxJcliDn270p6l5zGtuoBORjI/CHj\r\n5pqlMBmjCKZY0VRRZOIDzxfld/HwXYPxO8rLYHSx+2KCOBMpZJnt0FpGiBPn\r\ngCbh13tNEFxJHJ+V0/ciG4bb7mB3daHYpmc=\r\n=NUtp\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"675b3ea148cdb27a8976d041c7e796388542466e","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-25T03:32:08.922Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.3","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^3.0.0","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.15","@balena/jellyfish-logger":"^4.0.37","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^8.0.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.12","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_15.0.2-renovate-major-external-major-675b3ea148cdb27a8976d041c7e796388542466e_1645760159285_0.6958374000538823","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"15.0.2-renovate-balena-jellyfish-environment-9-x-cf033c51519eb65a39da51f25a17429f3bfa91f3":{"name":"@balena/jellyfish-core","version":"15.0.2-renovate-balena-jellyfish-environment-9-x-cf033c51519eb65a39da51f25a17429f3bfa91f3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@15.0.2-renovate-balena-jellyfish-environment-9-x-cf033c51519eb65a39da51f25a17429f3bfa91f3","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1af8b6dddbf35715ad2f9d51f30471c9f628c659","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-15.0.2-renovate-balena-jellyfish-environment-9-x-cf033c51519eb65a39da51f25a17429f3bfa91f3.tgz","fileCount":238,"integrity":"sha512-ruugCHzknvo5jF9t90jZ2cN0dvi6LBgr5UV6X4aeuEjMFCw1uvQTGZQ7FzXoxnaAZqQZpwZOACEh3t7dq2Khaw==","signatures":[{"sig":"MEUCIQDmXxc2JSySnDZFyTh+JttLkPkQg6j5rEfbC4c3WQ6mUgIgHfdWiE5d7L+OO9WObGuAjx0ftkB36rnCbZwsGBvSCac=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":846045,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiGHBlACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmrwwA//WJJlAufQnSIMhrYHOAKOKjeydoYEA+k8qTlCLXstIooJ1W4U\r\nycsL5rvA6vBivSVNa52HpuymmBMN1FRWdAa6HrL/VQk+4THC//ZS1vG46Zzc\r\nc/9pl8C4MllzQ88cuo2XKopNtFrXJ/lzk6tFpNPbRPO4uNxUCpKwa/mNqHp4\r\nYmXHYNcUrsxcK25Ht4+UwxVG1jU+DOXpWX98TGvJ5k3BaNWlqAXZfIyCkE2B\r\nQodOBsp0b/nexT6WgQvY2Tsh7b9sjYKG0jKizHcRFQgJOnBoUur++DsAhLRI\r\nc6bOLwtnJLh5awRXA6DtdYJK8v7HGiZVKlDCOjmno+u8jrbPYr1cwEe+d+n4\r\nRPEal0/7h29UkMDH0O1/MZDG54V3QqC7nIZ3Gn/eKcN+ByDKncwRzZvjJ2Il\r\nCr6t+deoDbHaA1AD52MlYyH8FoB4/ddIiyFw+wvd04Dz3c1are6YCFzdnodm\r\nrS6rnIG8etIpE0xUlmphAuCCwynUcUQXrS3G+J5+3TIvUKmxt/h6VhgCCsrV\r\nEgUpUjiS1KQUTlAzFRQVJZhNY8tikFjlFWIYDJL6aJBErFOBcoo6ByBKcFAH\r\nK1AG4vCT26pnmyS8SYArpHW/U8/x2e5mJJptBqkAEbfOm2PRIVtArxugsAHh\r\njp0JtPWi/Ao5AR3aEUAsoTbM2eLvxRlR8WQ=\r\n=s7wU\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"cf033c51519eb65a39da51f25a17429f3bfa91f3","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-25T05:56:27.277Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.3","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.15","@balena/jellyfish-logger":"^4.0.37","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^9.1.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.12","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_15.0.2-renovate-balena-jellyfish-environment-9-x-cf033c51519eb65a39da51f25a17429f3bfa91f3_1645768805133_0.6905328493170364","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"15.0.2":{"name":"@balena/jellyfish-core","version":"15.0.2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@15.0.2","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"dd24455e36c946f93551fe84cd352e919be73284","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-15.0.2.tgz","fileCount":238,"integrity":"sha512-5k9DuRPNAoOtWooLk1nxQ5nAVp9u6+KS7SCPm0RNKaFsJtB4eOXJob4jLPCOsQo01t3iugud92OstUYWx0KtZA==","signatures":[{"sig":"MEUCIQC700fLpnw8rpwS0C2pS2u2y0n2u7zDv2RlZm78p2kSIwIgI1PetefMp//r79gLCLupK1zYfAMQFdtiRR1k5FAJPTY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":845962,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiGHStACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmrobxAAhjHqouXazkV4pqYXyQUfR2o6vqYl2ZxH9N55/rfZqgDIdGy5\r\nPOus8fd4QP/9KMW+EDjtqFegKAqs8SpFiJf+/kRrWhGQBEndUm5mjb+zlQNw\r\nrarCM23ZGcZyYoc/TtiCbnhBvgWbGBMU3E64t7bZXjrAaRtuohnrds4zPV0a\r\n1tpU3Pb439UHPujGq+q1qtpIaP7QrVeSjFrb62b8nHwE1jHAN4XQnflxhwXL\r\nGMAmZIdCjgBwdB+GeeBhJKPgmWWKmpZvDsyg1OHg9vpS3pDXtdmnO7Pu9qsS\r\nqjiXNJnyPZ1grlbDoCOIepc+FMSTSt7kj/S/gEkAhhLEimz8UK5z6BAzvg6D\r\nYfft3+C3ngDCxxiW4ufXyYCBAukRqJFsowrdzJwNTF2iF93PsvPUeZabKFnv\r\nCZOdNpzaFnqfZKNlpnBSfD9djyRXjxCWG0pVG707UnigKHVjV08Gs+aD+QO/\r\nZHmS2EXPzKLF+FyVsq9ip8bNRihf+UAuVKaMSSIumaLMjla6s7Qoyp8MrRDL\r\nu2Yefy5smTK+72FFZnEOcHFWmG9xvIxpzQXocDNXZWh4j7e3ZbJ8S0Z4YgMH\r\nkGV6WrXmo3EQKtOp1h042h+Q+IYPcImNpwQJghkRGsfzPFy2TOP1QlQN4jGO\r\nUjVsOoCh1zqFh0VQoUMaKvO22dlWO7r3E0Y=\r\n=AG7A\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"462dbc40df3879d0f73a19d534ff995c94efd227","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-25T06:16:16.199Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.3","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.15","@balena/jellyfish-logger":"^4.0.37","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^9.1.0"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.12","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_15.0.2_1645769901456_0.8829881428771875","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"15.0.3-renovate-major-json-schema-org-6273d5dc550d87705e63b03504106f81e81cbbd4":{"name":"@balena/jellyfish-core","version":"15.0.3-renovate-major-json-schema-org-6273d5dc550d87705e63b03504106f81e81cbbd4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@15.0.3-renovate-major-json-schema-org-6273d5dc550d87705e63b03504106f81e81cbbd4","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d880b2310e1930ba102c6482c11128df12f122af","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-15.0.3-renovate-major-json-schema-org-6273d5dc550d87705e63b03504106f81e81cbbd4.tgz","fileCount":238,"integrity":"sha512-X171VGxBeP1ahbEstxWauTSm8wpsOVPYb2rfLGFihBsYZqO1oi9jklxRBQGdOcndQDYfcMWfSVk+penrgzzd1w==","signatures":[{"sig":"MEQCIAkvvNcm2v12G8Nx2+hkjnxirWXalCgatAEiyslrxK2KAiAjKGfwLSAajnC2wSF4jssexHUhAwsyBx+sgR9JTbtkYw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":846119,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiGHWEACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmqRlhAApNHdNV2ZYsH967rPBB91GGx+rOKKTUhwarOvKJNKIcggmyFu\r\nRQDFQtI2eEUU3+8b7LtHKIt7+UjqPGs5Lpfmfbp7Le2T5TXqICb9cH9YHkQH\r\nEfCufjs8DlY/ulp8JAIz3mA6fMtIPMadASbH+PgYPvmUlD+K8Vwy/d7MprAe\r\nd4Oh8s1vNCLsMU0XcBO84rkFcKHnuW1orE0+F1eYy35Ejl8XIFM4c7bfSGow\r\npvtd0vDB23KZJm35BLYxOAu1gmkrtxJcVoFARwb+BXjb95rScaVBen176oo4\r\ncLPQne9xGbrelDaW8jE+JWhmzJFIsG/DT57v3wmBfGR74IyKAySQQeJulmqt\r\nbnrQju4WtLI1jLXAx/Un64QjyjWFwbdxn2uePtC6WKIsvpxEj3v87hg/mtlY\r\njMJlflRnBKVRPr00eyJk6sH+Z+yFv2sVYtZyHpoRa6uN4FOjPl3YKwfh1eX8\r\nY4lTKnzp6avLTme8+1xvpkk7AkN3bxrG8zCPGI4dWtg64ySS/CRlYAAi1/1P\r\nIQCbmQUkknYKfSrZFEI+M2fsQ3CTNsOCMNyJL3ztBxl/VJScHCS/ImZLTxJO\r\n0gEwpySmBnfsTVo8KsukQPDaGTx3bgzH2V31wnUEA9VMZ7QXLhNC8eUBs7dG\r\nNtf3aWRPCLRFS8DGm2tMZqhRXQnqw3/hkVg=\r\n=1M5Z\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6273d5dc550d87705e63b03504106f81e81cbbd4","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-25T06:18:36.955Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.3","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.15","@balena/jellyfish-logger":"^4.0.37","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^9.1.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.12","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_15.0.3-renovate-major-json-schema-org-6273d5dc550d87705e63b03504106f81e81cbbd4_1645770116575_0.16669533952050708","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"15.0.3-LucianBuzzo-patch-1-0c3ebee45e71a56fde8255cf14106b8b4a7b5b00":{"name":"@balena/jellyfish-core","version":"15.0.3-LucianBuzzo-patch-1-0c3ebee45e71a56fde8255cf14106b8b4a7b5b00","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@15.0.3-LucianBuzzo-patch-1-0c3ebee45e71a56fde8255cf14106b8b4a7b5b00","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d1d88edbcae0b8f76d272e1e5f6b86d9529c3058","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-15.0.3-LucianBuzzo-patch-1-0c3ebee45e71a56fde8255cf14106b8b4a7b5b00.tgz","fileCount":238,"integrity":"sha512-OHajV09plZC+w7NCzOlopUyZY3gGopQkAbb7dKprnfJwcNe9x5H8YWTKQDNyVPvsiigLdgOepFfbNdYvhZ3w2w==","signatures":[{"sig":"MEYCIQC69ZBPHRpWStynBpIFvFBtpUGHVTjmXKVxWY2Kv3uP8wIhAInOqZWjYC2ZQPrAnYhlIC9Ux7XHsJIpQDDzIGrzNyJ+","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":846103,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiGO0yACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2Vmr86Q//Uqecpzw7evhvHXNXtR0bndSi9+Q/mtzdqW2Ft5kNpEAXCmZE\r\nwCr246b6wE3vpuXl5vvtYhreGCZPKMA8U7nRPk9sH5QqGazIaWzf00cPN0US\r\nrXhAxEUhrgY4dCtDpslRABXzdta2UBKRYDjxm86ZYnNKYfb23xXOLB7IwYS3\r\nKAr5l4y7cYac64UGWI9ezFqo+G0W0sixoAO/dL6rxYbpRDN3SYPEDfleiEaD\r\n30AwTXXjr/FFdTDySd6kopEPhX66PDbC3B2WyLjHPeWO9qCcnRgRlUYlXlba\r\n/TDMDcRsDYCc9YMLOE+HcD+SkzJM9DCFGWB3e5fSxO6k0vhLSlRs/21JBiRk\r\nzEvlBecromzLsVdNFgvg0aXEZGuj9MxmR5n/53rK4RlKQKZH8x/ZDO/XcdWH\r\n4TigF7ZbY18EFniWJA64kLmkfUpJByq8lrqYUVZ63d3iHSlvdEX2OYC71dNC\r\nxkuVT9j9nxXbu/NJTQOkYAnDcAHqg8JNZ+rocOAeSNPn238mgSqJr4lLkX6C\r\nZceID5zBYG7GkYColR5WHx1veJmRbzC+8H3y4ywdm74KHonTcNpWTzi+ndl3\r\nMZ5dclvAQTZkfTlDoniWlN31yMafg5W3UKVcBX/SKjeis58aWDzZPyovcRvz\r\nBimvi84dJjzKi+dQzPDfF8VmJoNspOp4Czk=\r\n=8Aw5\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"0c3ebee45e71a56fde8255cf14106b8b4a7b5b00","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-25T14:49:20.983Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.3","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.15","@balena/jellyfish-logger":"^5.0.0","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^9.1.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.12","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_15.0.3-LucianBuzzo-patch-1-0c3ebee45e71a56fde8255cf14106b8b4a7b5b00_1645800754561_0.8007896215532768","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"15.0.3":{"name":"@balena/jellyfish-core","version":"15.0.3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@15.0.3","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5cc2613e6de14f9c71c1fd4ff62afc0d07adb8ca","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-15.0.3.tgz","fileCount":238,"integrity":"sha512-6dcXjz8T1dGZ6HhTzv5g6XFJB3kaNsJm4oJJgyjXlUzo+AU/QoSbAHO4zJ6oOCbiLcENW647XTkwdKaUEpEpXw==","signatures":[{"sig":"MEUCIQDrtd+dwSNK584NtCsjjFIBShbP5D/zM3YN1K3UzTzX8gIgF7sIB2jznTs+fMJXv14kTqPBzWJ05BMxIvInQKB9yLA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":846042,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiGO5tACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmqkgA/+OZOjnExmSQOFGMfM2pfaoJNVE7H9fCVhzNHs4oPBTeMOpkpr\r\nNdMcOM9Jv4+SNQKz5QkjFCLqWJ1tVLbBcrTjIyKZKqVuBFAjjwHWEfZG6guZ\r\nxs9DBppkd2vliYW1uQvujQwCQI2SmRgtN0TF3D9WCtoput6orhbf3dfCG2eD\r\nVxO1vgsXidw+2n+HD53TUUo9UiG4SH0fcKNwbxerVNhIzTe38kxPj/Vww/iG\r\nU45UyFR6VsOjAy20K/BqVeWNnnIVkpigjJw68BVcJTBGUSwnyz7Ez1JdtbuJ\r\nrKP1XtJaM3E+nKfgTBF7TOjyefKk0QLziP+ON/U+AtEypBnF7ci1I8gppHoY\r\n238kmGhLs8GxPptepR/FNxfQdZhBMkQhZEntANVtiJDMCSWAhgWfM1UjePwU\r\nuEH5N3Z6tvn0Ufi+raX8/vVccf66/ELLPQfOLNzYP9Ohx80GculHmu9qFJ5O\r\n77KxtOszqOMtZcaSey0gtl8rAbWgS0iOhj7ZRdkBufiHDtBeXPHNLcRl3rTM\r\nLyXAvFt9O8oOG9Ud0oJHPgGBYSYSdpmXllld9Oo2DPri7WHJ9u/QNctUlOCn\r\nI1t8ELKzFAEhCt9CbZ4QRyyaLmFNhUI33wcEPt/4nYJbw08mmuirflKd0kY5\r\nGVnx4dCOwOJvzAFlwVqloWHAVrUCMdRO0qM=\r\n=gH1w\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"01899d6e6549391c416cfb4015c6ef874b4c86fa","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-25T14:55:28.195Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.3","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.15","@balena/jellyfish-logger":"^5.0.0","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^9.1.0"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.12","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_15.0.3_1645801068916_0.17044707589695096","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"15.0.4-renovate-major-external-major-cc354c17db4c656d28f6e573770ad3601f347006":{"name":"@balena/jellyfish-core","version":"15.0.4-renovate-major-external-major-cc354c17db4c656d28f6e573770ad3601f347006","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@15.0.4-renovate-major-external-major-cc354c17db4c656d28f6e573770ad3601f347006","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"29f2d623f6263c77488a5a2d0a35b1f835e89a58","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-15.0.4-renovate-major-external-major-cc354c17db4c656d28f6e573770ad3601f347006.tgz","fileCount":238,"integrity":"sha512-HZ7jgTjnbGLns7WCUMIS4YU/rghxjkU0sfmZYFJjnuUyplbGhhXR8uM2uKyMH6zXYcJL9mtj1LO+IoLBjl03vQ==","signatures":[{"sig":"MEQCIFuxCmO7sLgP6kOpY82tkSvu9+BMqdaVf5m8jF/q8QNjAiBshz7DDqoHVl7yOAcorZKKDi2kMB2LtEfE+bRLUKuhXg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":846198,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiGO9cACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2Vmon6A/8CL9BfaGklP6fdBzL7s8ivwenPZfRBr6+CCCuOLm2FWmLc3r9\r\nWAyya4dyDaI16C4k9AMBO7WCMCRB6FlTardcZXmYkZoqejp1Jq4KBzJRsiEn\r\nnAWy8EI0akNWmA6NBSH6JtGt/0vcbhYvhrezj35ajBsJrOaLpAy9qCpCYd77\r\naL+HtNC8vumBZFy/1IO0R/HEf7TeULiHhxwP3Ap6LCpuZDCVDbjskHVqZqaw\r\niJQQJ+h5E98iEg0CqQ5SEEIPZ6bw8pliM2zb0gNnKA9j3j2G7mh3p0QelYpR\r\n3UdPZqkbpuYSpFrvx3Znns6mj9pz21cNtXb9dgpfaLaAv6p5th/04ElJVRWt\r\nMuHtAHHE4nQ902qS6T1bS2O9yp0eEho4m2MUJ3fBZK5pSViuWr+ut8xSQ4qR\r\nSPZMTMVFd0aaYG+USSaJlS31TKPGM3v7Wlr3uYX+50PieDrgUQuzgu2PVYCP\r\nWGk9oGiftykOAeTx14viB+sRopmgKSYnB8kWZ2WEgKHPByt1PWx1JrQMmHwq\r\n3QYy+771rs/KNKS+SvNtTQHGDmwesBXFowQjg1a5ynDWztVNAbKHb+iiQySi\r\nilxEJ2+FsCjekvv9ZjelOX0riDxfSbgqmbf4FY+NG/J0SOZ46cGdsw8SgcB3\r\nvM/+YNz0UglHynSWGVdsXv+HgxQgXZrmK2E=\r\n=T6/T\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"cc354c17db4c656d28f6e573770ad3601f347006","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-25T14:58:37.725Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.3","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^3.0.0","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.15","@balena/jellyfish-logger":"^5.0.0","@balena/jellyfish-metrics":"^2.0.37","@balena/jellyfish-environment":"^9.1.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.12","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_15.0.4-renovate-major-external-major-cc354c17db4c656d28f6e573770ad3601f347006_1645801308675_0.05441697021009495","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"15.0.4-renovate-internal-patch-9b055a5ac5945a25d93fbf659653fc9a5864d3d6":{"name":"@balena/jellyfish-core","version":"15.0.4-renovate-internal-patch-9b055a5ac5945a25d93fbf659653fc9a5864d3d6","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@15.0.4-renovate-internal-patch-9b055a5ac5945a25d93fbf659653fc9a5864d3d6","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5bc9a3416856d31377bfae1207ddf02524f0924b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-15.0.4-renovate-internal-patch-9b055a5ac5945a25d93fbf659653fc9a5864d3d6.tgz","fileCount":238,"integrity":"sha512-uEjitFGZVO5mxtd7+I3xlkwSLiHRDH2NS09YY+UPw47R4nyc/pBH7NzEnRAmHGGqPzgt5v+vS8Q3mJiDhIxflg==","signatures":[{"sig":"MEQCIAFVfU4J8ec1sFqXmajMgDA5H2XVoFx7dARpm5tnFv+QAiAlNJ0Fgl9BcTkVMwiJMsRAZ8R1A9yjNziQSujbuozWaQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":846171,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiGYvXACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmoE/BAApHrjtsH5tojMic/tehH2QEikDoRvhP3KxAduB5tDNi08MqVw\r\nKlVLpwYzDvTd3nlb/MAxJ6Ufo+Y40O+PmVw0qeaOwclbtTftBhQW5GyIVG3C\r\nfH71QrfRqvlECqeavJvzK9VZnkMTW/KYU/wbsjpIAf2x8NYE17srAukOYx8c\r\nTNs8wq9qiAkSnPab5DD5lakPvgL+zRqKiEvHdUuboi8i7sU7/mtkVI1feCmN\r\nmyr6jr3XE7h+kg8M5Fx6CyFwdgBDYjrHIPZWnChOQObBIIYwPthvFWL4t5s7\r\nR0n/TLhLj47x1CCxEl3UIbEIaZumOaqeJShUXKInO3WSU0gJQiNboCBEYTJb\r\nYuQscOYoCzlaCKC0S1YTNaPvhoN23Mm71/w+mKTa6t1JL4mAsD2SPc6qrX6h\r\n2F5ssBEwIvzK5EPqNLcIhNE0BhwU+LtoSa0I/oUjZUFlngFxtcnml6bS//e6\r\nMTyBWUdvFdKQPmhdAmzi2hT2iyX0515Hqgp9sIxhr2HD/xwG3wuJiFEEuAXS\r\ndwfJFs8yjwjWrKj5NWNPDb2axAP1H6TlrveL3YjYDwmtK/X5SJIFUvquuBUt\r\ns3eBALAKg7SkFIC0Yc/cKA0qqIqQDRn4ZBf/oxYvkdPTWwwCULbFf92iFMM1\r\nJFEFycAHsICjAuejbcn97YSOwNpf8UOdF+Y=\r\n=l4WS\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"9b055a5ac5945a25d93fbf659653fc9a5864d3d6","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-26T02:06:33.817Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.3","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.16","@balena/jellyfish-logger":"^5.0.1","@balena/jellyfish-metrics":"^2.0.41","@balena/jellyfish-environment":"^9.1.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.12","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_15.0.4-renovate-internal-patch-9b055a5ac5945a25d93fbf659653fc9a5864d3d6_1645841367630_0.30139814833129264","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"15.0.4":{"name":"@balena/jellyfish-core","version":"15.0.4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@15.0.4","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"02d3e618c60e22632c0d87f4c5082747a2cc92df","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-15.0.4.tgz","fileCount":238,"integrity":"sha512-leE7h3jOVR6SkYg7wboUGQYmQ0Bi8t9zAfYUCUuwpDvlXlldUZkGevGbujUXffCifMSDIg67q+icrG77XuuwAA==","signatures":[{"sig":"MEUCIQCm5+X178rS2TkTFx73N96A6XCVK7UoNsnzLrLJ1AqzzAIgfB93TJYkyNQEdFazu+02/l52ZbA9VoAxAntGZD6h9bE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":846106,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiGYzcACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmrhGg/+IpzKv2Q+4rvV+icQrY2TFUgQ8JBtmkqIp819gVyGp2G+Rk0T\r\nzOCfDM8Cd/KggVekCSxLZ2EU2g9bqqB8kQBFQist4Zq5CjJPZ9UXQWQC1G1V\r\nshJotxvb4LuaGp6ylhMy5NC4mmrIKe90EUv1mRHDdoPg77xz5k/2bKokbr3q\r\nNHE/cBVoxQ76++4azMoi+Fb0SM4wic/eItdqZ20IVbG+8vy35UHF9BUDcVDW\r\nFx94VqJoVkSG428pDF1swuPGvsz5hi3ah2HEV1KlyoVfRvBpiMgf+x3y8SDI\r\nE1j2CwIzsx3Z1gSo3NDo8Ee74FaaikV2o9kDmx3NhxAe8l1dUZGN8g6+FXYT\r\nMS0LiyM+KpKCHxgeo8aXtaTd27UMjGtlQcdy+2i4dYK8kVvfRfKL5Vu5kLQW\r\nJtMoj8wITQi/h71LLEptfJAKuzpxk8BqJDyliKTTB/jg0JWHjIg6n6KUDOC8\r\ncEdZtldccPCJtO6sPS4tULLNs1wVokBfDFBMBPVkvw6nLNy4lnsc/NcryAsL\r\nD5ilZa9Kh/Xk/0k8SdZb0lwTMqnSWAsQf8KMP2enoXC74vu4wqMC+pVxonIP\r\nQOQuHYTAnIavxBmTHEUdfcsje1lNcd8+u837+nUvW2NnWGpm+ZUllrrWRSqU\r\nv6f0Vj3qodhEzUVAnaEILsnQCZFv0eZA/9E=\r\n=1ym4\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"4b0177c90f2af00083a09bfd963832380d1e60fa","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-26T02:11:45.358Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.3","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.16","@balena/jellyfish-logger":"^5.0.1","@balena/jellyfish-metrics":"^2.0.41","@balena/jellyfish-environment":"^9.1.0"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.12","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_15.0.4_1645841628537_0.8828403844621122","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"15.0.5-renovate-major-json-schema-org-575a4ee4699b260a45a77df73d72d6a49f8a914a":{"name":"@balena/jellyfish-core","version":"15.0.5-renovate-major-json-schema-org-575a4ee4699b260a45a77df73d72d6a49f8a914a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@15.0.5-renovate-major-json-schema-org-575a4ee4699b260a45a77df73d72d6a49f8a914a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"00c8dfdd19df4c8aec011e572e908e2c452acca6","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-15.0.5-renovate-major-json-schema-org-575a4ee4699b260a45a77df73d72d6a49f8a914a.tgz","fileCount":238,"integrity":"sha512-/TI4NU0aDL3p1UEnk0HP0VIsju/ERQpLmvltlreN0vuDECEXgJUOyw5MjbAVFRDxWGorpa094xWw9cSaF+agVg==","signatures":[{"sig":"MEYCIQCpwALolN4WNxyE39rMTjazGefEmzYYlfqCuTXWAahVLQIhAOmbjNs+HtwV6ufjx0PUfskp0lVBikcW+i0MdWeRGJmV","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":846263,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiGY3jACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2Vmq4FQ/9ES7XkhOl62gdbYbwJ+BsE/bOqL45PSF4rhyPvDbEy6767tBo\r\njWOoC5lX81lcOXD7APB67b+F888PuG75wdCSVIPT7c+dc5RdxJayT7Au42ip\r\nOA+cA6QvXxr380aLbCxJwdBUTh/tQk0y7pNHQTjHNJjvp5UvyL1TnE4TemKK\r\nv9vrA5awbQDmJ+RJFSNkq9wIzURtOehGNKt47Lalvl0S0jqCYleScMtMSuIU\r\nMHloqXtn0SEuU3NM6578UDnBgtLi0w8JyQHq+Yp0PjY97Iw3LTxuHBfxoqLY\r\nieomeBkVQWO+GM3EgoCJAGS0lU3y+7KcC1d7YpZCHYrfiTRcw7u3YuNevvnF\r\n76P9hHgfI8g2kyr3FMbIzi6hNgYwzbykdb4gByXpi1uKLujRYTiRakFG4PIu\r\nWR9KsGbrEUz7z6fzs8B/Yu5LPAqf6wwYzyFy141ERx+nzfFtwmCdnjVm+kBv\r\nj1KsYe4wFGpfuiQSHMRcB/qCQuXRJSzRJX4nWRhJGsLvy2SZz0D/sDW4jjYY\r\nIxl33LJ8j1HNpK8xlDS2SVGu4rWqWnz3Ru7yArPgvkEygcwpDFPFkJhw34Sa\r\nHit70FihIjNnXXURSoQ9rtTOgi0sUr09aNkJEuhhGkuaDwIYc72tOK3tug84\r\nAdTy9cP2qnk2204+Y2mjY2CNlnaI3/J/i5A=\r\n=w0Xm\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"575a4ee4699b260a45a77df73d72d6a49f8a914a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-26T02:13:42.655Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.3","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.16","@balena/jellyfish-logger":"^5.0.1","@balena/jellyfish-metrics":"^2.0.41","@balena/jellyfish-environment":"^9.1.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.12","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_15.0.5-renovate-major-json-schema-org-575a4ee4699b260a45a77df73d72d6a49f8a914a_1645841891012_0.825216964564657","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"15.0.5-renovate-major-external-major-38d3ad9d6030447bac5b8c54512f9488360f8bf2":{"name":"@balena/jellyfish-core","version":"15.0.5-renovate-major-external-major-38d3ad9d6030447bac5b8c54512f9488360f8bf2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@15.0.5-renovate-major-external-major-38d3ad9d6030447bac5b8c54512f9488360f8bf2","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"66eb848a8720b40ae3d125f9fd8f2954698f2465","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-15.0.5-renovate-major-external-major-38d3ad9d6030447bac5b8c54512f9488360f8bf2.tgz","fileCount":238,"integrity":"sha512-CbNgqBbNqri50gZDLCuD2vZtcrIvHxFQCPBeGRHb5qx3H0w5j2h+nV+IR2gkXk5+6/FodaDct25qOPOqFgiV9g==","signatures":[{"sig":"MEUCICkOvjtI95wbkBi6jErJBNdW0W1V374Kl7uvwwz8k5MsAiEA2hiyNUc+6w0+QOwbK52j9GmVkOA7x6sF+ipE3hgnw6w=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":846262,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiGY5ZACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmriCg//XCsTf0Bc8FmVe5UGgsJllDcHGzfPUZP7LSXJH3pi4m6BJK4q\r\nREMmIRk7eOnfeV+FF+BiRt2Pjw/rcwpK87FC9rSJD+8cJv/DBwQB2Vd9i5i3\r\nUJ55FP7IYcdSeBBvw1XKKmcRrF1eSjZA3rfYsXHbz1M+YjywbtBu+GoCHa8U\r\nc7llOPXclIBhKkFSN3G6FFkcvjOzOcULgH1kv5L0JNmHe+xzdA9SBerwygL+\r\n0zo3Kf19zQinOo+GqtZurbaVA35QI8x4LNZFMx5JsjXwScdpYVcu3Uf+VR1S\r\nmW0YzIpkWqS7CF4633wnzWHpCryzkzsFm8ySQhRrDaH2GwvMLAKOp0o7pENG\r\nCNaP1eIPIKDqL5BlzEHc+cwrnhUMJ1sVAvVwh3zLSuJ9DFdfvWmp/2E1inHj\r\n+of4JkmbF+Ox7Lp2Q5uviY5u1xK/4BVvw0IvrQQu5QBTtDjjKJs7yUmx4gvC\r\nyo9fs492eUMLqhLa+G0aEAVErG76zGpumhFto9cGhKQW+I5+F1Kis7dkXg0C\r\nxqnT6GCDq8GqSukHGzkTTKg6z7aQ6ecjinBBDCx9ZtDUK7vQxNdHm8bfwblg\r\nKQi1kguBv2g1kSE7pPVFlu8F8Dwe3ubjUvbxpYvS/tPPfq634OObvmfGJBHl\r\n9tKD0YUzjNwdHdsA26VdVldjMESVz1VbtIo=\r\n=CfoS\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"38d3ad9d6030447bac5b8c54512f9488360f8bf2","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-26T02:16:47.687Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.3","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^3.0.0","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.16","@balena/jellyfish-logger":"^5.0.1","@balena/jellyfish-metrics":"^2.0.41","@balena/jellyfish-environment":"^9.1.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.12","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_15.0.5-renovate-major-external-major-38d3ad9d6030447bac5b8c54512f9488360f8bf2_1645842009358_0.8919089628524006","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"15.0.5-renovate-internal-patch-ff84bfa7c3fbcb53204ee4bec9726448674bfced":{"name":"@balena/jellyfish-core","version":"15.0.5-renovate-internal-patch-ff84bfa7c3fbcb53204ee4bec9726448674bfced","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@15.0.5-renovate-internal-patch-ff84bfa7c3fbcb53204ee4bec9726448674bfced","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"39726aeca790244d479c600b65b0e36e6cf8308e","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-15.0.5-renovate-internal-patch-ff84bfa7c3fbcb53204ee4bec9726448674bfced.tgz","fileCount":238,"integrity":"sha512-MMlYhzPBZf/PZYyJ709p3iT570hJixN+TO3ZDgNIgxbdV4AIZJ66/twcjNrM+5TbOPt5FHIaDJmeVD8TDUebMg==","signatures":[{"sig":"MEUCIEpT5f/LWwZ9jgXUNkNbaRRqqsZzSOOIH6TYkJQPGgayAiEA+ynUXIeShG90pmrpGBEKUGmRP33RFbaP77umMS4NS4c=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":846235,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiGbV8ACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2Vmr0BhAAhqZm2zxABIR7R/E8rP/v/OiREmxGgjZ/u/st1EieilIw/jnc\r\nvqMF3UBMxaUZ4fxRD4XTDSiPGHXhrs15IdFeYPKT4wCp5uBVL88XsBuzTIwM\r\n9ujEKr7qDouZNZRQC8peoesdqVyABvJMho6PkjI3HqC+Xpf1WKKpRJjDAAJD\r\nVEfrBz4QZ5XPqWJAVFDgD6SBOYfiy9ZpJs9VfXMNhOXzkfptxrdzmVOyK5aZ\r\nLC3FKd3lsXVGVkWpiSzkb1MBgvWQh+mcp7ZVn1LN6GC7HuWf2+c3z1ZLXTA7\r\n2slWK2SuZ4UMapKm7SJmQxhf9NOPxIerY42VnCKbYqhzRuoSlAinGQR4T2lw\r\nfxSviHBHprOwJsm8o/rTuqA+GjOUuAG7TXIchTr9OuG+itCkhRKrDdO4GyQV\r\nT+3F2MbqfoZWF2AJmrzhWWHHeRfXesD33DpO7aMH89G0/jWHwQqEtBKJR0zM\r\n1B0gf8mw7Qk5iFBL32zd7+fLrpgyzlXTYYehbcqLSmsvAko3X8wAdrUZdA9y\r\nl6e8kSKZWCsxiJLuLZjyN5kEqi059L9kH56FpqNEcsp/1ik7kZ2rWz6nQP/H\r\n3QzeMYeoxF7U5ToGtBypge22b569oueroy2hII2mS68ei0d91sWfMcz7qlmX\r\nsg0CGp2eYQInTxDlqsHZdyQc2a1HzdFVf2k=\r\n=tsOm\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ff84bfa7c3fbcb53204ee4bec9726448674bfced","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-26T05:03:34.390Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.3","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.16","@balena/jellyfish-logger":"^5.0.1","@balena/jellyfish-metrics":"^2.0.42","@balena/jellyfish-environment":"^9.1.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.12","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_15.0.5-renovate-internal-patch-ff84bfa7c3fbcb53204ee4bec9726448674bfced_1645852028511_0.9407431184188728","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"15.0.5":{"name":"@balena/jellyfish-core","version":"15.0.5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@15.0.5","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"29f6fe1c9cf80d1cb28790ab7295ca010ed7e550","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-15.0.5.tgz","fileCount":238,"integrity":"sha512-St0fZ3zSG4PhX0fe1ORafgsrFD7hOOBpZa4uE8YrM452EriQTG8yQtYdjdjuapGYJ/1rYD/U0JM54915VD1aqg==","signatures":[{"sig":"MEUCIQDcAdqKrzaEvH1X7qBTd3i23DhAhPDh43B5PakEn37NYgIgCAeaoj0s1p7sO19GTR7J9CW1x/lBrUSW41BeRSPes0k=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":846170,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiGbZ1ACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmrFnw//YBNuJDxo//d48cMPNpYLygpFLqG7oazN5CD9HCOTsk4HGlB9\r\nXEGUyXK30BCWAIEsfbSigTyIl+ctx3DQu/TenipM5QMAW7bL0tASrrtxYsYq\r\nwU/efBgYPtYsws7whJwo7jq+EDmp7x2Ni5tbQnOp4b7gvHEx+Bap73AFcRt4\r\nZBMEMf3dEqZlO6XansE8BV9TD7N2DIDKN5eLhsDzQX2dgs/SwpbcPHBsMHKW\r\nWV5rO5ZrQWRm4PjrhBn1xHrySKVvtL/7oUPTJybgloDQCQRWiQ5zJ13/EbM4\r\npJBOyW7yyVRPaTbDSb2/tHNYQJKG06tu+phfEcHuFMBAExdQKIxjNn2Ph/0x\r\nalDAy5eH187BL6xb8mz1zQTThKzMAHSOKOPPlNQT4I+PEMIEf3OU+kqVBQpJ\r\n6tSqwiYf4RKybf+bsTcHVG2+ujIRfTEr5yYVYHzIuYlscZo7njfgoVTskT8R\r\nLyj67s/4MGZ1ueOnlyrjKsZq5UneISh3lWKsy7VkWD59xODBy8y39K29gWJK\r\neNj0t9Xe/riPTSlFTHA4bXFqBARerPsW6g92fKXAH98j+62ROGUI7JuGgk50\r\nG9FsOTMe8G/UYSp0VsA0JbFw45uRYZSWWnUgM3nhDTPXshRtDKd++UTMN3dz\r\nUfM8vMCUQQuI4ykBG9KO34bIIS936+hl1QU=\r\n=/nmK\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"26afa2b15edb116c226658f4627b2a4b0d71ee2d","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-26T05:09:00.721Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.3","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.16","@balena/jellyfish-logger":"^5.0.1","@balena/jellyfish-metrics":"^2.0.42","@balena/jellyfish-environment":"^9.1.1"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.12","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_15.0.5_1645852277476_0.9360315346213282","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"15.0.6-renovate-major-external-major-301d3a8ee61846e38de14eeccb9360af3a7e807f":{"name":"@balena/jellyfish-core","version":"15.0.6-renovate-major-external-major-301d3a8ee61846e38de14eeccb9360af3a7e807f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@15.0.6-renovate-major-external-major-301d3a8ee61846e38de14eeccb9360af3a7e807f","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d565ab3ed765e793f503661f69bb90a475bf8df7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-15.0.6-renovate-major-external-major-301d3a8ee61846e38de14eeccb9360af3a7e807f.tgz","fileCount":238,"integrity":"sha512-kMt1OcJaVqQD6K/Beq/oO0kL2r2y2ee9yY68IP50Kp1F7ddPrKUWts/JCfd4PbiWj21sezAwzn47XCK9wlw7uA==","signatures":[{"sig":"MEUCIFKSCe4ZwjoGHc/qE5GLefN3Um4WpLtUsJ43CWgytIxzAiEA6hTyshZixPGLnaJ/E/JccXM9O95AAbGud5BIHB9SUZY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":846326,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiGbdaACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmpkyxAAnQHatOJ7CMnzPv8Bpan3V+H11wH63AyrTnDxFLk4ilLUMoqN\r\nGP/lDMfb5QxbbXssLAPdd2KTdpdtUD9R0Go0q35Fr0Zvt2Zc6fenfLltvF7E\r\nTTcx7Wu3+t2p/sxVaepTWDX2pBTohiAt6YNNBrf30fYs0tmfpDVRQMlRggJb\r\n/YHA4EN5mBL8FkTKnLckJncqTSFynUoACLQa5qDnmBmsqRSWgVnOVoYTEQT+\r\n7Q4b1djHINtKBlYshaV+I3hpsQMnAZipTbcmt5kbUJ5FRkQmD8jgxB535xZ5\r\nHWV3UCkxAtDyP4xZNsF+7ikkKSuoYGFyIR0dLYDK3O66fZe0TZjnQTbUs3Gr\r\ngmvQTnZEjB4abv2j3KrT4TNu6E8YqXICmVued3X+00ZQC5i3wyo7gGrxnPLb\r\n22ToNighutfQXjF0T0TSYZkrwLsjX/ml/m61/tWko2i+mHPURGQ61cZsWCp4\r\nKHRKegGYh6ET3qZga9ohGJzt6uBlLxgotd3ie/FM0GY4rGTAp2Ps3tq2eLDG\r\naMFjah+NdUqYILTULXVlRT681TrPynTrIsRUtHokMTA3ul2MXkIpY6OiL+Sd\r\nMAM3GKkZ4TCt1ZRHT0rDfhkFhIZcRB5kGO07jx8RE0RFO1NVU89wD03W/vir\r\nTBWf0WG/zL1WnNmXzRjBkBYd7PgVoi1JtvI=\r\n=QCy8\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"301d3a8ee61846e38de14eeccb9360af3a7e807f","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-26T05:11:55.726Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.3","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^3.0.0","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.16","@balena/jellyfish-logger":"^5.0.1","@balena/jellyfish-metrics":"^2.0.42","@balena/jellyfish-environment":"^9.1.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.12","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_15.0.6-renovate-major-external-major-301d3a8ee61846e38de14eeccb9360af3a7e807f_1645852506402_0.6222073456962054","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"15.0.6-revert-allof-c9397b95678cffe804a3bea9d89bcd316044f7b1":{"name":"@balena/jellyfish-core","version":"15.0.6-revert-allof-c9397b95678cffe804a3bea9d89bcd316044f7b1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@15.0.6-revert-allof-c9397b95678cffe804a3bea9d89bcd316044f7b1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a69bc4efc8ae7c4000430f7d33ce1f57476429f7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-15.0.6-revert-allof-c9397b95678cffe804a3bea9d89bcd316044f7b1.tgz","fileCount":238,"integrity":"sha512-IE954r4yRfYT54t3TFegg4TyXc9sLRa1o64CQLyNStu1ATnWzVrukxkDyC2QTFDWSMhCXwX34n46S41XB+n/Jg==","signatures":[{"sig":"MEUCIQCHtzmv44sbKkdqHrkYQZ0QGG6Of2x05Q0UpvYSpv02MgIgG2AI/AujMW76TanJLrrmhcbz04u9wfMGMIVmJIJPJsU=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":847037,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiHMgWACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmqnXQ//UXeZBm+WavwITTrz5razXP+YbY+3+iz64ElfCcT/LjvAu7Rz\r\n+C1ateSqnNRZLihAdJbXrSOgKGd92YqsI+snDh4mRaFZKEULl2s21/ObWqWp\r\n/DVz6PXXblLVuV4VlzoxdbGdDFBJcKiHH/0jBhZag4NSfjUmzk6X8FPRmeGk\r\nntzHW/UYUohNCF+S3Jmlni+lvx64n0mTfU/2Lb6/Kgs53sKRtOXuqYQmpY8J\r\n/qu1GM5WtgsXIUjMGTbn/jal4OYhSTA5FI7npOgacQTvi2ujHb14meXsBK1z\r\ndfqzr3y/zzgpisMG9e3XcEGIdrVeAAAbkyn6cAPb1r8c2/+iVpAqVjEM99Rf\r\nbLwEZgK52+g7krHaKlZTHg7Y6Hvc8kueflarjb3yFFoMPKM617MINPC+iXe4\r\n9x2crL7FaqPC2ZGHad4/NaPSAWh9vDxljOKowbzOAb8TuZD9FvpBUTTML3SS\r\n6hfLtEax527v3QMGorqBpDjIG6sa5lhjVydNt5JplqJNs2ButG3lgR2D8eae\r\n2dxeSbbWsvZsYMVCs9DCbqY/IcRV/XFXc3vBpKQl+nOKh1pFF969RO3oE4F1\r\nA+gg7QeOlpLA2Mbk37rWaBhebS/P0qiLjHFlOE3oVEs1q5vT5kbK5pIy84i4\r\nM39IGfEfQi2LTBWH4Hm25oqXdkFrhPzgiIc=\r\n=kScR\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c9397b95678cffe804a3bea9d89bcd316044f7b1","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-28T12:59:45.669Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.3","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.16","@balena/jellyfish-logger":"^5.0.1","@balena/jellyfish-metrics":"^2.0.42","@balena/jellyfish-environment":"^9.1.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.12","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_15.0.6-revert-allof-c9397b95678cffe804a3bea9d89bcd316044f7b1_1646053397783_0.9245791271882717","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"15.0.6-revert-allof-d8880301dbc68f143958a244b514236fa6480458":{"name":"@balena/jellyfish-core","version":"15.0.6-revert-allof-d8880301dbc68f143958a244b514236fa6480458","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@15.0.6-revert-allof-d8880301dbc68f143958a244b514236fa6480458","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"c767ffa3f13265ddfeeaba9774af93ee073a86ad","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-15.0.6-revert-allof-d8880301dbc68f143958a244b514236fa6480458.tgz","fileCount":238,"integrity":"sha512-WRR31rSZRmod2Qr/wHhf1K7fuFZdaEGbNJQ15K0HEqnF2zSUeFhG8VOIaMwyqHEj9Fhwx1XJ0kRePAjNVJEZDA==","signatures":[{"sig":"MEUCIE47tZ83J6v8nzq0Hh5h6RoaqFErh5DdjEYSkWwsBNGqAiEA8SOAv7HOyp/gTYOHdqgQRHQ2ySa7AtSS+750KM4QSfE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":847166,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiHMx0ACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmqoqQ//SSn3oy/EEaGiHkQjDvr/UYaKdUI+iZCKpTPHOVrk7QxSz7bz\r\nuRefVw50CM/B4O2JoHAsifUKyZGmv+hoxn2tHHV/uJTCyOBZaavPOG0Lcc3F\r\nh9uCw3cs5wQQUzmIxaqwoTm82Lsz/r4ukR7Ovahtx1c50YY5swW+mh/fBBAp\r\nqQiBtwVDinq9MCd2gDp+nEDbQoCjooFULsaK3slNRif2/8onQ9Scw85wx2EV\r\n0lyK7mhXcfLzbKDIo9XF6CUKyoYM58zrRIKjjHCVAxK2EXvwRL+FUuqkv0sG\r\nOKUSIS43+galFiJUbiaTdpD2gT+GAF8xcOv9Xjv8+pfZQj+2PRyJEifqOhhm\r\nUCmFg9WsXgfJFNr+zSZgiQKAvd6nbptrrRnhj8YRxZY7wxZEuXZiNkdDPOwW\r\nrKNWNS/8vIgYNyesWt0O/mC4uWHUZYOFT4ayMCM6d5yCySw9yJUkuTw/5aSC\r\n+DoYGCOQhkafUAzlzxav4iXRb/XyJMjojhx9jgqG8OmwRPgN7T/xGCCud6ua\r\nR1+9YxdphvhMuqk+SXJ3jRLMee4GkUN7uxmVSAviudfGhNdeotOSlCn2eW4/\r\nqKw/Kb1DmN1xITKSEc7INfGIOPezm/Ppt0zkufbpYFJCM/50ngxSqRBnszHD\r\nb0RyTsmvL10H6ftonChVKUEZdyaAPdpC9d8=\r\n=oEbU\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d8880301dbc68f143958a244b514236fa6480458","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-28T13:18:46.491Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.3","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.16","@balena/jellyfish-logger":"^5.0.1","@balena/jellyfish-metrics":"^2.0.42","@balena/jellyfish-environment":"^9.1.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.12","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_15.0.6-revert-allof-d8880301dbc68f143958a244b514236fa6480458_1646054516657_0.37429495220899844","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"15.0.6":{"name":"@balena/jellyfish-core","version":"15.0.6","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@15.0.6","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9b5d1b02705bd00a9bb5daac92a7731cbd96d816","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-15.0.6.tgz","fileCount":238,"integrity":"sha512-GnWp2D6wBIj3TWqWbxJZdkCBSe7KzhXP9b5OL4Sz5nDOcLjQitUxFjFVALWO8fpNScwUeHHbJM2UMgp1FqnxXA==","signatures":[{"sig":"MEQCIDBsK49lGuOmQ4bJxu1AxcCKCfI3n/4AUyZWDY3h4vukAiBijxt0LmeWJ90sARjMMg6d839HMtF3UnAdoef7zYybag==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":847112,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiHM1mACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmqcrQ//fPCNEu06uzdRWrGhm/+MJu2m+cjhRh0oos11A57lPd2ONtjw\r\njcZADUYoQ1e8g8hEOKACzePm+c5h8A9mTRbXrD2HcSw8zFDokl4ls3ZzJikW\r\nxGxeKybEBXSGKfBDwpCt70j2ShDlWEdvFM/ypd0AM3AVowNhabrx0v99O0U8\r\nVTjrDCk7Q2xNqxOWUXMvlFb410HKnmG55IVcDWD6lrOzOi+ROWkLshIjugA2\r\nZlQTA/fzS0S8nWwqfYOzs3jHozn+jxJ8nz0jYy2hafhQKRkwlrNi8ifTMwxu\r\ns1kErPQp1gPNNPn3XU8qgjkJPH3V/PQfw5Fukyy3YP5ML2PIDuGgBZLDogIb\r\nDxcFGsW44SMDOQZHJ0JSCaN380fguTjgF763k+h7FoBcD/9mK1gGDPLH/0jT\r\nOITIfXH1SKZn6tV5Yu6wViRfeZ3Fg7F/4OyheqLAxWccXLB6U+MR4SmRKON1\r\nfehFGGSTVLkEggEQgeiPXvSaC53ivZ3xBgdvqlu4KNgdhPDA5+5Z7SuQ+sGx\r\nQQqivfVrt6hIdVjf8quGY/LXUSRTnHpNNleE1mmprlaJdYyvtNNw9NyKQXa9\r\nRJFPUJZ72XvFZMZ245G3jnLyt2Vd5Y+6q5fpk4yAv8AsscOH4fn0DhotI/7z\r\nzuJKG6iHldD+D+owJXnn4VHJKcG/95jtlEo=\r\n=PsnM\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"af8e3e86246b0674b3fc448979e570f9c3d0fd20","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-28T13:24:01.117Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.3","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.16","@balena/jellyfish-logger":"^5.0.1","@balena/jellyfish-metrics":"^2.0.42","@balena/jellyfish-environment":"^9.1.1"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.12","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_15.0.6_1646054757826_0.46927415241174963","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"15.0.7-renovate-major-external-major-8ba839cfddcd8c472a92e1741df7286501be2446":{"name":"@balena/jellyfish-core","version":"15.0.7-renovate-major-external-major-8ba839cfddcd8c472a92e1741df7286501be2446","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@15.0.7-renovate-major-external-major-8ba839cfddcd8c472a92e1741df7286501be2446","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"1aae2a97d665bf02e63b19a541f75241638b5c0c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-15.0.7-renovate-major-external-major-8ba839cfddcd8c472a92e1741df7286501be2446.tgz","fileCount":238,"integrity":"sha512-0WsobCXno2skOFN/Vhd3HcqZcdJPjTwv+H1QQBHuGMImf+G7eiltVwloFbanQpI9pC8BTQww3nH/VnlGx2Zv0Q==","signatures":[{"sig":"MEYCIQCZsSh2CB+LS7TUCSP0J5JmhZvYjDDz3W5pE4sM6Tg2QwIhAN462RalOL9gMY3GRuhHnQzTXh/AxHja4ycrtbRO+K3/","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":847268,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiHM7ZACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmrrMw//fOtNT8slyjBVTbGXLvurWEOcpivdBbb6DXVjrpgARNy75wzJ\r\nO7Sw8hagmRQ3wJXzuGVH/ktxav3btuc1hJmHNEg1Oggro/OYvAQkIDZFyURs\r\n9zTQxS/ZIzoGK7dOWvygqPczvCyfxUKaCKh7Sr92NKVxTio/VgCRQyUsfs0l\r\nSQ2piD613IQF6gRAtCBr9vsDBS61XjY0aOk/YF7LX9icwjxZoMYAY5nX4Bd1\r\nUnPo2PRzTliIpvXeivREVSvpki0q1L3rt2Yxx7Jgd0hUjv1IFwBBJUTo/OCV\r\nFFYDvu1KYDw/BODFhZzs8Db8qJ97nJu91khsR0/4V9nc3nHZN8apihWLzGtW\r\nRpYgnmoLuoGTyRqkcF3orjH8DLOxFguBPFnnQLunmFHuuj5oIE3nxexaXxFU\r\nJeBDSGuWij8junVdIhr8t6EADcm+ru6ZqS8uYuWiG0XDQcAT5ipy6rGgVVFU\r\nXDt3p3lmh27DhFcFtAZ9/BrWlqhYKQScJOioUhZzMOLY8yj8pKX8uTTNPAt6\r\nPUyW/BXugH6mXSDyuNAuWLfyhseBR6WjeBZTHm3vuN+lHpY7zQ1RtL3cY+fM\r\nUuVgonwOzRMCtDCz1XHD1D3FgZxriCormDtE/BKp7XDwu+J8ZcJ/1LOmrdLh\r\nC8d6xbiitxDxeSWnAN7pDFKkw6aHb1WG1Ws=\r\n=XjiU\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8ba839cfddcd8c472a92e1741df7286501be2446","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-28T13:29:05.957Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.3","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^3.0.0","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.16","@balena/jellyfish-logger":"^5.0.1","@balena/jellyfish-metrics":"^2.0.42","@balena/jellyfish-environment":"^9.1.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.12","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_15.0.7-renovate-major-external-major-8ba839cfddcd8c472a92e1741df7286501be2446_1646055128923_0.921223486432559","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"15.0.7-merge-workaround-82362bdc62a232effd1e4fd5479607aa3b294caf":{"name":"@balena/jellyfish-core","version":"15.0.7-merge-workaround-82362bdc62a232effd1e4fd5479607aa3b294caf","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@15.0.7-merge-workaround-82362bdc62a232effd1e4fd5479607aa3b294caf","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e955601f1cb3f22556a1c1fe4d4ad2325019c854","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-15.0.7-merge-workaround-82362bdc62a232effd1e4fd5479607aa3b294caf.tgz","fileCount":238,"integrity":"sha512-02xrv1YAdTrY8MUsMa6M46WC8QBL72WNbB/5YahYIRyxFtm7CGPqZTrNIjqJyyuim/b6HLs27R6NfiMB4y18tA==","signatures":[{"sig":"MEQCIFNzp9rPA8w3pyatDG1O/wGg/BOidtYmsXhZwigJMETBAiB9AKDQ78QtDCj/z1n/0yADRzZSnMojRaVTG7kyBfrGfQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":847282,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiHM8wACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmpGyA/+KhzVrG9sRssSf5TROBpsJv4o2q10WvFlsmzpo+Xb+OY/ODXY\r\nTfawe7PFsNiJYbzTJ6XGtkxHwqPJ3F2pqZ84sDqkueT4393jA1a2jds9jBV7\r\nv4NBBjVr7soOPI4yCYLKLkqDgRDW0GwxvL6gJcdyn8UN2CSh1GUtrW8qN9gj\r\nPRIZw8J7Y+0xw8UNQ1KWIBX8zSae4NX/cgemNiHbF6SCdb9pGqEimAJ2smld\r\nNcDtZsfALEytRmWra7ofCDe2VcA/W9bc9xnHsvfYxxd8LxGSqHDnlNUq62qc\r\nULV3J27qfqqkUDJN9bnmCFuZ5+DrRZI/zlkQq3fhS9Eqa0GydYhV33c2rfGZ\r\nt2TIdsKvCzSd6Si8XZlZNjThfGwRZBbrjev+v2IkEkICYOX7ZX6TFyi5PtTY\r\ns5xKjdavERoKjOw0nJLQ5ngma3LwU47narF1WXwxlMpHHujvWV9pdMQlmQBi\r\neczIwDfaxifQflYlZWKFZCbLVsELwQfuH5A5/5E83baECQ415ppTstKf4A/w\r\nySC6+0Q2ItaflDDeJ6CQ9M8LRlQAD8VZvyKwwE/fCEq4u0Y8AS4wgfNfRh8X\r\nYcfBto6CdwzLW+q03JZLHQpHnVxL0efVHhif676exs1VJDmzL5vtkroyCdmP\r\nKRpmqLT+n2ao/dqscaY86gTtn0ObANVZC84=\r\n=gJW0\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"82362bdc62a232effd1e4fd5479607aa3b294caf","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-28T13:30:16.043Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.3","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.16","@balena/jellyfish-logger":"^5.0.1","@balena/jellyfish-metrics":"^2.0.42","@balena/jellyfish-environment":"^9.1.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.12","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_15.0.7-merge-workaround-82362bdc62a232effd1e4fd5479607aa3b294caf_1646055216024_0.6993072108320875","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"15.0.7":{"name":"@balena/jellyfish-core","version":"15.0.7","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@15.0.7","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4610719ac94d632be8dc865e5225d95f165db06b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-15.0.7.tgz","fileCount":238,"integrity":"sha512-P0HNdBb4hbjwVZ2j8uQhNrrp5uoTJPg7tNjZJjKcnPWSYwlRQ/O0e/+g0bLjSA3njakD/ZOIMbSPs5oohZGgQw==","signatures":[{"sig":"MEUCIHuQ4wkYY933dyFInYsVd2N4GfEEoxUn9O9IREjKY4C8AiEAxA46TvHN6i/1N5CEABI/DTwqVofmAuVvJJybYR5U+0M=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":847224,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiHNA+ACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmoXSA/9FcA5QmDzAfHrfq0EnKvkibM5z5DcSHg62YTys0mnfvLm3xY4\r\nwnr4H6Q3xi/4vpCsZ6qXLgPl0Pp5TtaYVWtjzuerVYgQv2WiqsVmoIxJNgbq\r\nSLW8Zz9vFWNyihfLjZUUj1CId/XFtmMXe69sJq0A3KwljBCOgb3gJveUxIUU\r\nwWHXoH3BynYO0Wja1uNY6gPWTK8WCYBV4XXsOOhEfJRukJeUM4INOnvDWwJI\r\nVIhgJi4Ge9WJcS7AM9yLoJ5jMXrHi+u8UxShnqiBF+dOlSXdS3OkQBR6GOjj\r\nVv9rVSUsSi5RSLyAF7nmna1kkoWrWfcmlr0NKZwdmRX3fX8WpzyMIiIJz0cF\r\nG1FzJ68eDUtgOo9xMMR1KxKRT6K/A7OolqonYaDXcEmoFbNv6i9T1DgWd/Vv\r\n437l3T4J9yWCJnyLHu7rCK8s6PPyWemq/624FEl5123UEY3NiBBzNmD9Gni3\r\nSVEDD2JguTzY0JQreDLdzV7hucG85kbEqCAfWjmnwP5ZKpitCUy0aZwkKj6D\r\nUG2vWNsAbWCqo/LoUJTovvQU1JpcQsZxkTNCoi57E9uGNdLk56TcIiGWxPgi\r\nromPVd/ZpAvlNu7Vo69nokildkwHUVn2zELUK8sY/RcS9QkZude8QuRYYb5/\r\nrSoHEFIdSBaQh827+Qc0NyFOuqn1Ec3btp8=\r\n=L8C8\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b96e5767d6ec550566f435e5a36b41ba84e21d79","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-28T13:35:52.809Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.3","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.16","@balena/jellyfish-logger":"^5.0.1","@balena/jellyfish-metrics":"^2.0.42","@balena/jellyfish-environment":"^9.1.1"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.12","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_15.0.7_1646055485891_0.6137441693249661","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"15.0.8-renovate-major-external-major-1768d495c55737e649ce87fbc9347ca8c8e968b5":{"name":"@balena/jellyfish-core","version":"15.0.8-renovate-major-external-major-1768d495c55737e649ce87fbc9347ca8c8e968b5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@15.0.8-renovate-major-external-major-1768d495c55737e649ce87fbc9347ca8c8e968b5","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"09e7c93c5fa7175cd1ff9885f41dbc75e1fec3b3","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-15.0.8-renovate-major-external-major-1768d495c55737e649ce87fbc9347ca8c8e968b5.tgz","fileCount":238,"integrity":"sha512-On/QthOgPlgOpoAnmYlGyklcFT/LMa7zFBNqejc34OxBlaHD5t9HhkzOqyY/CsUwOushjkNsoa/P1wp4FjJydA==","signatures":[{"sig":"MEYCIQCoapZnCArIEzIBauuZRAb/NHBB0cR+jhnMDFX3AH+4rAIhAIcyQ1M+thl5tFlMTyw/8OG712g1jJ7I9b10seZZQZUw","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":847380,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiHNGWACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmrF2xAApFQ6hpPMlfLf0Fvnh7u0I39D7bnk1Zqz/TBpM9l+ME+Ax0Kj\r\nB4McKJ2cRgGV5/nSvaL9yssuxFjKlOqu8BdwrZhLwJdZG3guSmkjs+zlF46K\r\nPgJFuQva5wXzpnnQ/OiAsQ2llBEw+Wn4rKwveZrT41a1AbD3uhqQrgJtNGrg\r\n/jiCVJ4SJ72VcNjKR66B2hJHP3rhoVL1cyTm4LOuo6fYihD9o0K2pKF427Ee\r\nO7Jjyj4909onxBOCiUCeVzJ+y01POhBVzes8MX1Q+spZvjWQUfIlFxVjw4p1\r\n/+kvZnYe6jjBdfEgJTDspOEyIBPkRcqpT/U8WQYjqFMS4x3g9SyOqc/cGIxy\r\nbprdY8g0uESVnQno+i5EwkL13DscUT6ykAGDIS5GzD+82wPOR67piP0h6/p2\r\nmlGm9CUiGDWsdpCSzNmt1rjzwtkYTjAh3cX+bjkjvMa7Cwd6/Uoz95fAYDP7\r\nklCATTP6xGhGKeC39oK44oq8L3t1g65fLbOx7FgDAOQpK08sbRACdrLyqefH\r\nSM//pHefPGW2PJlbEhKAHvlMOIZ3hu4BTiuQctE1sLT2qGgVROwzs17E2m+E\r\nmvuo1fqnWXrmbAXDE5XTfE7daK2V4ztwxGk4ar5hXCy5Uua9vt1h12a/FL4w\r\nJqMb5PhXZhp6aBqaY2HI/wuzcdxO0JYCkxQ=\r\n=UPOU\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"1768d495c55737e649ce87fbc9347ca8c8e968b5","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-28T13:40:59.764Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.3","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^3.0.0","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.16","@balena/jellyfish-logger":"^5.0.1","@balena/jellyfish-metrics":"^2.0.42","@balena/jellyfish-environment":"^9.1.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.12","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_15.0.8-renovate-major-external-major-1768d495c55737e649ce87fbc9347ca8c8e968b5_1646055829894_0.7191189772128099","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"15.0.8-renovate-major-json-schema-org-19426ed642f6a5f54d4bd8b45e9d4e47fbe2fbea":{"name":"@balena/jellyfish-core","version":"15.0.8-renovate-major-json-schema-org-19426ed642f6a5f54d4bd8b45e9d4e47fbe2fbea","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@15.0.8-renovate-major-json-schema-org-19426ed642f6a5f54d4bd8b45e9d4e47fbe2fbea","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"590d63c130c1815c4219302f5f23b6a225619d59","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-15.0.8-renovate-major-json-schema-org-19426ed642f6a5f54d4bd8b45e9d4e47fbe2fbea.tgz","fileCount":238,"integrity":"sha512-qINjiyy9PtUtXDHgkZ6N/ScZ8cTeGVpIu0gjV273tFeIKr2TUHGkn8/a/RA4ZvZ9os50Mo4Zxg5AtkS5osbavg==","signatures":[{"sig":"MEUCIQCf/0g9fiWpk8jmdkoorQUrrEmEGg/1XwRXIQ2hwaPImAIgLZ+wxbXgj4jNJ6dBVmDJBUEoBakKbcCvWAbDHLqaxjc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":847381,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiHNHAACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmoZTw/7BTOKVhvV/jofZKCfVC3U/HKU7dcARdXh7rlvrQxIq9v7f6JP\r\nJtguyj7hLzjduB3g+gPas4g7PvK3XRv4jI+UC1rStUEPxnZjh/EUII/+D7Wi\r\n1cgI/wQ7COodKPLufVw2PggECUqHsfl+/4pCje7LffJhhyZNYYLtfDFg2LQz\r\n3z87qQzDhWTof7n5BJjkyglwTxoyE8JTu9ckkPpBW3wZ0k4x389DDWNmdlN4\r\n0vbgd5l7e5jeCwckxDg48k4gaWAG2nUMZ9uuUKI5yB5BJdZkaZptSth8NDkz\r\nt6NrFvWSGRNqxaYR41UPWl6HHXY8jtQBFHfx8DfQZNygziF8HtRFReEjTarW\r\ndgbjsqYPaPTrQJB2UcpZzA9WMhUoVRiesvJQxMOx2fQHKBhdYnJrqEcoNDh/\r\nYPPfloc3vplT5hpHqZxRQvQAtJ7mwB7TFobVc7z3MLZ6rEipBuwG+saFg1bm\r\nGY8Pk5oZQniX1KGo1XnoMWeaUI7H5H2UWG/PShlG3u7EgeGlACPLufo3PLpe\r\nHG7IGrsiV1tuEqLp35013im53nEAkt6KNNcsa8u5HIgQMf95GHaLi8kdzDxe\r\nqINYRW4qFr10GoRePDa1UZQiaHTZpRgzRwOUc3MwxnqEoMqtREQykktRUY5f\r\n1OD8Mv5RItbuBqdt9OK2rEMHRXbPEuO6oZc=\r\n=HgeF\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"19426ed642f6a5f54d4bd8b45e9d4e47fbe2fbea","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-28T13:41:40.987Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.3","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.16","@balena/jellyfish-logger":"^5.0.1","@balena/jellyfish-metrics":"^2.0.42","@balena/jellyfish-environment":"^9.1.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.12","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_15.0.8-renovate-major-json-schema-org-19426ed642f6a5f54d4bd8b45e9d4e47fbe2fbea_1646055872027_0.786374791954427","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"15.0.8-merge-workaroud-a423bb692d0455a6862617432b9dbcd9b011896a":{"name":"@balena/jellyfish-core","version":"15.0.8-merge-workaroud-a423bb692d0455a6862617432b9dbcd9b011896a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@15.0.8-merge-workaroud-a423bb692d0455a6862617432b9dbcd9b011896a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"911118f2d818c6389a02ccd932d998b260f75ee4","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-15.0.8-merge-workaroud-a423bb692d0455a6862617432b9dbcd9b011896a.tgz","fileCount":238,"integrity":"sha512-JzB+Or/k29s4ItK1htdjA9OajMB89cKtyvYhEI60J7yaCRA/9FEcXrh5FQztHRFdI6gHEPTPsHOoJ9ED7puiiw==","signatures":[{"sig":"MEYCIQDTAn1tt/GBrEpl+PC/FQS4Vjp931bcDRfUvQpiz/tdJwIhAJ0NQ7CZjIXFEtdckvQEVEL0DuP8uPbMnHU1M7OfjfeF","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":847435,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiHQn/ACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmpvAw/+KA/SeWHCa4IiHMkKvBeF04FDCAU5y8kWdPIgOHJKQUMWVE6Y\r\nZntLPlMu4x4e5aDaRBFfWYMwkiubUYVa+DiT/TEfZxgvcry52EWhqT+/iY6t\r\nYXaG2fm8BgiMsZmwI/hZB0q7z4kvyuliIiXlz6d+KZIjC1aJfeKlaQfKlFWN\r\nvLWoPnIak2pcNy88v2SQrt9pEdeT9Vgp5A3frdpECKJ8us+CUzKQLQr5/TJ2\r\nJwVW2hJfCdo5vEyJCWJWxZmcfS0PTrOKQkOmW27cIVDYlOrOzwDWULMME3OJ\r\nxY9549NN2U36X2bK4khQ31eM0lHFMTRSJcZZDEbJ182suPkAvOs5XLyachfC\r\n9UjGdRxbkWhEi/GADS9SokenmtP5uhi0lwabbR+FrS/GQ8dvR47ZFI6In2UW\r\nRP36amsEldQD2IoWU3gLlciXHsrP5Y1SCbkD1IjhcKYEmw9mmeaoPYc9fr5u\r\nOtc/EKPo01OkvB2CmVq8F2iPrz5iCGueYJhA6y4CsBfGNIu9u0QYVoMWOsyj\r\nhzjX7/RfLqitk+44WXRD180/TZ9zOAFVXL6rcqXiyX84lkYfIBxjJKblbs3r\r\nWFywfu8mLoPtkZQW++NBZbf8Zez2MBgUfn+xNgsTyNiTr5IHS0JsBAQH5t9T\r\nQFFuJMRPDyEvtMQCHaPI2XUv+yjEv3ul/G0=\r\n=iAPT\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a423bb692d0455a6862617432b9dbcd9b011896a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-28T17:40:46.356Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.3","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.16","@balena/jellyfish-logger":"^5.0.1","@balena/jellyfish-metrics":"^2.0.42","@balena/jellyfish-environment":"^9.1.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.12","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_15.0.8-merge-workaroud-a423bb692d0455a6862617432b9dbcd9b011896a_1646070271372_0.3265652012998179","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"15.0.8":{"name":"@balena/jellyfish-core","version":"15.0.8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@15.0.8","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0a4793b885f08e70691f3e43067b62016ea933c4","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-15.0.8.tgz","fileCount":238,"integrity":"sha512-t+zsmLQ9iqEq04Vks0V4jxrx+RWEhr0EAQO97JYdCGjlseKGgbxJnW5Uq6CRBwDYfeVU17OsFLGarUGOA1PmZA==","signatures":[{"sig":"MEYCIQCkY/mkACIIV+Pf3DpDAu/OZjkN4ZQsuW/scsVoAMiFjQIhAMOaBXUMOFbFr262Oj0DCVNImyAew8UycKexeWAdfrLw","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":847378,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiHQrjACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmpHGxAAiyypxLNIlpsk9k9QLwuvJGLypiCGWJ/rpheqsSVYqLsD1Q/T\r\nY/EgyadkZtYu/JloS3upAMEfQnEKRWjYQA/p6azYWB+4ArGq5ZfpkAwngie3\r\nyoRpBjZbN2iNOoDOrRFz0pbu0xoZiA+bQotCM6p5DFwS+vjA6cWYnxeAej8f\r\nSgiIyz+F6eawm0+qj/ipMjgImJ0hm0TsNjmcjsD9A1SZzy8hS3zhqz7UnxLe\r\ndCXBH4rDc5irP1IpnVkxdd2r+VVso1Bi4kyzLJKLaj9m3kWQx5wTAnNjpype\r\nutf4eapArxp+WHCo3on1889Evbt19z9Wz9HPt7jHAKECCiEOlBnx3l4/6e9q\r\naBC2+aQTUKOkgjIk8zFQhT1sJk45YxCx+N1xCtzDdfpTrasiPgfRczaCIHH0\r\n6kgA8cbqfm+TaGJiPhMOv033vvIZlrdv5LqPC+nmDBuEeYsg8XuwYYB70pQ9\r\nTAb100ERuiE9oy0VhRK7x6qMdMlHtwK6OYANbpGFZ0ndP4H9dtR4flMYL9hN\r\nyKpuKcGQuLv969lhW+8Id9rS70wpZvFIM7c6ENYfLFJ3HhYQ/Hii7jBp5JDk\r\nfQ4rUzBhYa4I5c91SlgH2oWeV0Rs6xkmMUnfRJrkiBbaR+auA/c4LdS3+3Ex\r\nyRKM2WmhZO1y36/XCMVzGMVbuixprwzoeZ0=\r\n=KYby\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e79d0b3816a0a772ad0b00a31326abe340c1e9c5","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-28T17:46:23.912Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.3","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.16","@balena/jellyfish-logger":"^5.0.1","@balena/jellyfish-metrics":"^2.0.42","@balena/jellyfish-environment":"^9.1.1"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.12","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_15.0.8_1646070499132_0.8309104659699913","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"15.0.9-renovate-major-json-schema-org-a80c7c7f5c5b9669a69e8552f23fc3492df49daf":{"name":"@balena/jellyfish-core","version":"15.0.9-renovate-major-json-schema-org-a80c7c7f5c5b9669a69e8552f23fc3492df49daf","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@15.0.9-renovate-major-json-schema-org-a80c7c7f5c5b9669a69e8552f23fc3492df49daf","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5bc604bb98376e178d79c5be8e45dfc665a91555","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-15.0.9-renovate-major-json-schema-org-a80c7c7f5c5b9669a69e8552f23fc3492df49daf.tgz","fileCount":238,"integrity":"sha512-UNQ77LxTis3tF/fZZ+cHSpdVfsrq/3R0NBl4SlkRQtHXkaaDm7ZHsKkj4J/Rkx11tartutp+W61bbEgGrTRCxQ==","signatures":[{"sig":"MEUCIB/h62LUfC6XZQF2XksVvY0bdJmEYpi5xQNVbu0qoZEiAiEA1QqYn1a+bA2UTZRjLPlGpeOmBoex9ZPeoOPvN3ahP38=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":847535,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiHQxMACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmoG6A/+Labn/BegyAkng8NEPShVMtY05+3c0awg0+N5RlvXOPJva+7u\r\npV4qNY7/hX7jevjExNO0eK7jKlH9BQNCCfabpnBEs6iCTQjXDJJnbx9DEELY\r\nHKdlrgFoau7nbF0w57TzISZrApBnpWE2ZtUKxHfn6OKiY5/w8KIR4ThGgHLW\r\nepKxxaDeAR7qerTlGiZlru4EuecsSJZG1RHcJiBLkE2HFnJ7wz2PvwLmnv6s\r\n4Rc9gJDOLS/YU89SruOGP0CQGuhtCUMCDYP1Jozlc6enkOA1MSBcdDHdKd6S\r\nD9P0/iWRN9AuHKI/Y+dORW6ZVEFCIfH+EaW5IBVX3tv01cYK5+jjo0ZD6qMR\r\n8xUs9uq4EZB8etiqSRVeRB4tFefQXb+onX8Ao9YPH061eabPdIvFEkVmo8re\r\nvMZfxu8Sj9jB/SLzithgJT08GVmJkDyhRJ594mRnaORFychE7o7HHCJcsxnQ\r\nZqSzvP2ZDRWzHMI8LgpZy7+6yhiMfKPts8jLdCmwM2OzLAzWP+hZlp87a7g2\r\nspZ1JDjtjBziYfVcraTtpM5mtmGPTXxkeTMrHKp15RzAh8MH6uQ2Cul3TM53\r\nwKhYWprieS7CffVT/i9mqVbHXcOu4UbscfWVj/nzDw+y+II8GpNK0a2XiDRa\r\nUipFLr2+KEqJarKstznP84b3ZTaZVuCHwxs=\r\n=fbbn\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a80c7c7f5c5b9669a69e8552f23fc3492df49daf","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-02-28T17:51:23.740Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.3","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.16","@balena/jellyfish-logger":"^5.0.1","@balena/jellyfish-metrics":"^2.0.42","@balena/jellyfish-environment":"^9.1.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.12","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_15.0.9-renovate-major-json-schema-org-a80c7c7f5c5b9669a69e8552f23fc3492df49daf_1646070859899_0.4520820100613614","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"15.1.0-joshbwlng-add-marker-tests-7c5e7f1629c07ccc5ec20a5f30cfc0eea91f55bd":{"name":"@balena/jellyfish-core","version":"15.1.0-joshbwlng-add-marker-tests-7c5e7f1629c07ccc5ec20a5f30cfc0eea91f55bd","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@15.1.0-joshbwlng-add-marker-tests-7c5e7f1629c07ccc5ec20a5f30cfc0eea91f55bd","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2141519763880433fbf3b4e2623ed35e11be39df","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-15.1.0-joshbwlng-add-marker-tests-7c5e7f1629c07ccc5ec20a5f30cfc0eea91f55bd.tgz","fileCount":238,"integrity":"sha512-QOwq31SRWND9mdP/uZLZPdP7sr6iYouzUvNhVsdhlWYv/5TLc59itbOESjExXq+HvcPFYZ7x8SJcKZhY+dvHXg==","signatures":[{"sig":"MEUCICmVl++X6Zxr629BqyCpnJPtlr45QaLP7yTp9KW/ViZKAiEAmL9y4lrJx6mlNp4JXZyCUBQ4JjbwR6efAV8qK/vejJk=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":849735,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiHakeACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmryWQ/5AQw1ZXFXltYuWF5rJfL4nUF3/U6hQygrX20TL2dbExOpZgce\r\nc7g/Af1NU9d8He4KgVdkE55OHYieGAXBCIC1tnDWRXiuHiFI1CEKHlbYIf7y\r\na+f5VRKAflrREuCgGx9OHlMC/va2zRUjNRBUpOewN/AK6FPAlofV7gDQTdpS\r\nm6H6SeCpCpC6LmELzDSXtnZjm5jjBH00AWpdIAFexANoGuS9qVaduFBvaulK\r\nVFac97dbjXxTURUIQPBXfyJWSShirdDAb3gTxXC3U5pueyqb9HUYC0iEaD7w\r\n0pjHg0SXT88swaUulGJoBGC1ncU6fmclfxRV4LiAGa3Cxl5f0H48SKpNrPwG\r\nmiPcwjTa1peG+7ZN3fcgkFk9YSP+Ljw0qLq6fa3U14wATOeYhE3Yqq05XTby\r\nNul6u7q87BQ24nf6nEQwVpp1To8eO2bbbQrNHmYxAtKJFcjr5FvtawrMJQ8E\r\nWjbKUZWBr0U2yAMQUVR4w0tqODHvhQb8i4Sr59WPpBOS79PLPIVN+ujbpTKU\r\nTV0qUoqKY8TcxHlH1vHE4e8SqCjQxodR5jePI89OVSQkZcXbIOwQE552ryIH\r\n5FOk1R5K7a97z1R3eEgBDak7j6V5t7aIc6bgIxCJYo0qGe1aJbvleyvYd+EZ\r\nyb5j4v+s0CjMTlZv+G14TIZCA81CZ0gT1ro=\r\n=wadP\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7c5e7f1629c07ccc5ec20a5f30cfc0eea91f55bd","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-03-01T04:59:42.849Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.3","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.16","@balena/jellyfish-logger":"^5.0.1","@balena/jellyfish-metrics":"^2.0.42","@balena/jellyfish-environment":"^9.1.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.12","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_15.1.0-joshbwlng-add-marker-tests-7c5e7f1629c07ccc5ec20a5f30cfc0eea91f55bd_1646111006021_0.8664811608962553","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"15.1.0":{"name":"@balena/jellyfish-core","version":"15.1.0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@15.1.0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9e4a718d9f387bb0ef1b7b47b7c0520ae5d82d70","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-15.1.0.tgz","fileCount":238,"integrity":"sha512-HcD7TleN7djGo/8vSN5OglWG5YLlN2roiiDUtwTcm122m+m8mZlquq32eipzQiRMNsHcJoc+DYhQ6FUv/G92wA==","signatures":[{"sig":"MEQCIHLFmaRGZ4WtrRQg/dll/j4Vu1SIYGkjsBvR++TlVjsBAiA+C0vZr1YYjeH66zyq2rIcgs6Yq4aOUwG2HqhLOp1MYA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":849667,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiHayLACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmpAHw/+Lh3GmcWopUewobQCvpChRBIKxfiBwN0gNaEhEHFkks29DAub\r\ne3kZu6hQRyfWGm9I6unI3LoVuCm3HrSEOMgCkhwSdGYfptlr051po53Tmefh\r\n+xuoal9RQ+tIY4eluva2bq6v1+MnRX92szuQ07YdTjjw77GdFUChniwgge8Q\r\nvO15qh/DL7NWlVFkF1fuhpFKh6Cmmj0AF+zukq+zPZnuVcJPXswWpEqIdBbk\r\nYmT2aiqS9uKocanDrkfqPG6TZIUykfFM3gMkAFXeO2S7JvW9iUrC5iW3I3ja\r\ntih7fKAcy8cRGcXt8ZIvcG59pD2LgMYcENFU6bpckAYfML9YuddUveMQ7ywR\r\nkofO4AJYw+oVzX/YBDHBZ78TGsUc2Q3LYdxcMvt34imSdLVBnn+QLFrLua0e\r\nJ1feBcuSU6olAPfKD8kFmfHCh1/ESZfcNEbCNetTK9cbb4hydqTCXrZ2nDEw\r\nd0VLbnnK75OPFSqTSIO09xp0yNCeHwNJxrWUtli50z1HgRvpXK8u8JVOhDeN\r\nOoPw1MlZVrCAy8IbeaIZ3GnWJCwogq+KUxEhltwIMqxncqLms1qOk9efJWM9\r\nBcjJV/JW7QOJhHaVwalbDEj91bMW4V6aF6avnAGXnBqxm+6Rlc3jof+rxnNE\r\nztGYRjDWCogN6x57fTqQbNckYRDi/DZTwkY=\r\n=gTYc\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7868701fadd02fd5381911f5d93460dd5c861e86","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-03-01T05:16:09.402Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.3","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.16","@balena/jellyfish-logger":"^5.0.1","@balena/jellyfish-metrics":"^2.0.42","@balena/jellyfish-environment":"^9.1.1"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.12","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_15.1.0_1646111883316_0.8938256815244736","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"15.1.1-renovate-major-json-schema-org-ae7b670d1dd9009b04568f386be82c1b3694d151":{"name":"@balena/jellyfish-core","version":"15.1.1-renovate-major-json-schema-org-ae7b670d1dd9009b04568f386be82c1b3694d151","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@15.1.1-renovate-major-json-schema-org-ae7b670d1dd9009b04568f386be82c1b3694d151","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b66e6dbe4d440613e30ae061462954b7f9a9e907","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-15.1.1-renovate-major-json-schema-org-ae7b670d1dd9009b04568f386be82c1b3694d151.tgz","fileCount":238,"integrity":"sha512-bMVi/8WwvbVsxwLlLoY4UsEMCuxSYyjcnE2iAxklTMNq8+V6Di+AqdOWWLwWFG/WOOv26ovHcOUTidgp+6fBdA==","signatures":[{"sig":"MEUCIQCMWjEYEP6SBTXdo2poKGYaZj+zkF6Ei54fEdGOjscb4QIgeZAH6grLwx6ZMP7fITSXlObQYHem/QUufcy3pOVtH8I=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":849824,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiHa3kACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmophxAAk0UjBCbIwpTDxRqqfNSdAVEjdi8elvIXqAdoAzY2I9xQ1Ybk\r\nEEBrpIHJNje0EF2Ga9pIgEvN4L/VP+auA3QH0Ce1V4OetDfQQ0JcjcEXdM8s\r\nnSOCh0rjWuqdT4nfJMLwLj9uyxqFnv5gMto35d7jrMvyVDtuXc2eo25vTXMD\r\nA+UO7r9Nz/yXjuTZbqhWC7oMjFfuUxBU99dzlpzEShTb5I27SUgn2Olorldd\r\nObBGpsZc41NmEUqB672wmwz+aghjLjX2Qc3D+GtsiEnCJX55xmGzgK9PtKlt\r\n8EpUqwCBlAqZBV7BnqiiKoLLsMsfeqVADED1COeMtqwFV5KKtTgMCBiiLKfB\r\nJMcUsHSGzG13lvkIEdwP9S2AmGnoxjo5ZRPmHA12GKws6AMljACzvZX73BBM\r\nFXgq357jOIDI6VhuCorOoNtaIVIGjJ09QC3iSIlwZ1WEN9VC1JWzFR7BNMex\r\nwAv6sSMSCQ39TpcJYwps5A306aX3lUWV8e///SGc0UmgiNwKX8NJw7+i0Zml\r\nVKxcaJBHRt/3ZgD8R5DUh+9b36FzkWt2JE5R4KfSgo1qn3zx+POqnSpopaLq\r\nmNk3MHeL0TKJI0eYB0hsSTA9XzWzUyl5mVrHS3QCQ3No4G3kkxfBR/8/OYvY\r\nJleySH+7BOWO0YLa/ijD1DY+ANkpCORNliE=\r\n=hgNC\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ae7b670d1dd9009b04568f386be82c1b3694d151","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-03-01T05:20:21.950Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.3","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.16","@balena/jellyfish-logger":"^5.0.1","@balena/jellyfish-metrics":"^2.0.42","@balena/jellyfish-environment":"^9.1.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.12","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_15.1.1-renovate-major-json-schema-org-ae7b670d1dd9009b04568f386be82c1b3694d151_1646112228147_0.8850674577137927","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"15.1.1-renovate-major-external-major-ba7884b43615016685c6816f8a0f051c823c0a01":{"name":"@balena/jellyfish-core","version":"15.1.1-renovate-major-external-major-ba7884b43615016685c6816f8a0f051c823c0a01","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@15.1.1-renovate-major-external-major-ba7884b43615016685c6816f8a0f051c823c0a01","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"322deda0d858bbfaa4c445718b27a783cdd44e71","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-15.1.1-renovate-major-external-major-ba7884b43615016685c6816f8a0f051c823c0a01.tgz","fileCount":238,"integrity":"sha512-DMxm2ytfovTcEvnHbuOr+qps4mk9MiQz7EJkY6LSqn4TYDGHqtFxC9jGFFRRiD9uXemtEX5uVw9JyCx8klgIzQ==","signatures":[{"sig":"MEYCIQCmx/UxXm8do28TTjeP9yUfTA9X8ZXVYHmgaamxBHwjsQIhAN18SZ/aZeHMo66fbUSDhCME+gnG7O6Smj5KaqPBuNYL","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":849801,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiHa31ACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmqpTQ/9E1YSQA0J6XRaJmCHnludSWQWLu7U2588KlpIiYB/qHVRsHk6\r\nF7uBUNkykD6EUKxcZ0FCBLaKRlZAsPKqTH1ZS62QwSJjRklKKQYniBfgX+jn\r\nQtyHT5fhfEPglY71RHUSD490cnmwLJ6wBOPKvupM+kbzgot2PdRn921foImp\r\nvA6FfAr0gThcIiSFkkBUW6G2gpb0dqvChNW27s3BjCM1QEYKOC1qiiNQgsAx\r\nPkodzHNvidrTSaXB0egG9ymWQfS9hpTCRZooDlkxwg6zLQ5gk++DlmfGmPY/\r\nS7LHXjY7vRbeaT/SjhLIoxV4a2WS1Uhi89p05mkDg0EAnB3DNd/z+rEJuBHe\r\nAT77WTWSQyO0yXM3XEpzcLSWn2YehyCX2DfgeLIqrLZgh1qyZMbSXK5xMoad\r\n3neIkh6yYb4Utpn1i418mwJNrFwrAVCECssRZivX2I4sadXrgOyExI+pgtuI\r\nobttDsY/xMoi6H9k4j5wEVHtL8DhsApirOZsGN8YKxHayN7kC2LIp1HbWmzN\r\nxzT8jL7Km7EG7HEVxdcWrJPtU2d0M8UmEqCcqnfrukjdg1sQ6YpKzSjq2vW0\r\nWnskWvpUmhxjYM736scHNKhHLyXCti3OKUb/OaVLfKKwxhdKz4E3zheJlNa9\r\nBO6LvBlhRCE+b5flIdkpvuRK62bic8gb6gE=\r\n=u/a5\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ba7884b43615016685c6816f8a0f051c823c0a01","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-03-01T05:21:30.799Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.3","bluebird":"^3.7.2","stopword":"^2.0.0","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^3.0.0","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.16","@balena/jellyfish-logger":"^5.0.1","@balena/jellyfish-metrics":"^2.0.42","@balena/jellyfish-environment":"^9.1.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.12","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_15.1.1-renovate-major-external-major-ba7884b43615016685c6816f8a0f051c823c0a01_1646112245553_0.723289213561016","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"16.0.0-lucianbuzzo-restrict-guest-user-role-8388f93dfa8eca0fab82015771844f77b8ab6237":{"name":"@balena/jellyfish-core","version":"16.0.0-lucianbuzzo-restrict-guest-user-role-8388f93dfa8eca0fab82015771844f77b8ab6237","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@16.0.0-lucianbuzzo-restrict-guest-user-role-8388f93dfa8eca0fab82015771844f77b8ab6237","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b5c5b01954fa1c0ab03077f59d187d8a5638b927","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-16.0.0-lucianbuzzo-restrict-guest-user-role-8388f93dfa8eca0fab82015771844f77b8ab6237.tgz","fileCount":238,"integrity":"sha512-ZA8WCXUwmfx9c+pJFZNwc5sxIeIAMTaP4dzz75f41YwjKq2Az1UB0jjEiazZSEFjL8P6RWX/dQJXPeoLIIvNKg==","signatures":[{"sig":"MEUCIQDGsAB8WDMrADdJ+kz9wMymJEuK0K+/qSgzeJChjItL5gIgewOARDHfuqzsZfghUnbAMGepHl4JPhztjCrCgjQkDBs=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":840542,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiHgAxACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmpewQ//eGWyTYx/aII9/JomnC4tySWZZQIoWM/h3eBynLW5r701sf87\r\nnG6znDIORSvQQDSOaKO0PWhf/5MK7uHVZJyi1BNFZvhty3y8iY3lY6M833YP\r\n6cASsrMB91ZJcjuU1Bl/OGCY6lEK6y2yxeXAQxg+vC+IB4fdytt+s1MT/xT7\r\nePJ83SDY1WIRMOQVT+YElmfeVZLYbrwI2fK13cM6QCpFZfuxkoJlG5Gt7zLL\r\nZGphDGR89yL8PKZOSwFWW9fiL3mJYAWSoOGf/tz5NxDNYggV0i2i64dMzuPx\r\nio3sxuY0XwR1kXvKxzGIkqQ8BOZjJ96LIjIVFJiAMWIvXCHsw423g7aglK/V\r\nBrP10ZZLV/VS1IljExwFSObUUun6oZQ6UQNSV4LpXthoNE+1oj5VpL2Fe4X6\r\n/Sr3gWYV58NFSb455Aczo2aIxf3XXFYc469PuS/0ISoKug3PQdMTZevFtVnY\r\n/Mkcce9p3+/c9fUVaLHtEVTQvlzFZHCmFiR/uldEZ2CVd+4cL1vL3m+d+PNB\r\nFPyD8HM5kfsqZHA8cn8xThva+IvY6tYtHnOctmz9dEeehJWjfy3ZdMp4JwV9\r\nvJuhYNhQO1/q6NbbGhzKSG1KqJ3ElqG+ufkYsblhCQTWQ9FreCO600K0D/oc\r\nMMwhUwJNjgZ0kBxYA6XtFDK3Bdp8q/I52X4=\r\n=orQE\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8388f93dfa8eca0fab82015771844f77b8ab6237","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-03-01T11:11:49.490Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.3","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.16","@balena/jellyfish-logger":"^5.0.1","@balena/jellyfish-metrics":"^2.0.42","@balena/jellyfish-environment":"^9.1.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.12","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_16.0.0-lucianbuzzo-restrict-guest-user-role-8388f93dfa8eca0fab82015771844f77b8ab6237_1646133297583_0.03485234404870208","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"16.0.0":{"name":"@balena/jellyfish-core","version":"16.0.0","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@16.0.0","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"dda35fd6fa5c8e2bc3be3ae7d6bf0f63765b6f0d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-16.0.0.tgz","fileCount":238,"integrity":"sha512-yY1HbC1bVejE5zbDuA9O8rov1imf8uM2g8kxuzEjJeBe1ID0LULoEQUJSiJ+DN+ddnLqHqvBHL/Rbh+BXjrBfw==","signatures":[{"sig":"MEUCIFVEVpSrDPSLUaD6r0MxXAnrxf82gtp5LLtuGlgtiVKfAiEA8DRyibHiXXlHfjt+CWp3+6JZpHucg7HLTqx8n2rTmqs=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":840464,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiHjFyACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmpL+Q//Tww0JnvlnblI+0h7y36ztfPDpPgq8Joy4+KIagFs7zkAaFLC\r\nOochqrnVj/iiz5aInDO6U25dtYL93GcQVXV+VgWFA1R5BP2hgCbnS81UzE4V\r\neVI6bCyknKr6Pbk+UAuX6jjfZw6qf3FRU3HUI0M+nreeJr4WE1AFbMQuGzOB\r\nFY5NX6I8cm+4j3pv7Q4jIM6otiiHP2cwvmXF5NEbWTnmxLCZnvGPMZp11OTU\r\nAsxWW9VN1+qecFaeZynWry0NyJ/5NoCajE5Em/+j1p4zev3qFWrOL9VvJlrG\r\nYDElIVIzQOV7/JBywui5aDuIzuwseTcShJMMspQ6hO4QHnYZl0rH17hVV7PH\r\n1ZSw0cdGOtgxCP/h/NG3rdGw+rRzrqjFZd4x6DAq5Z5NJHq81Q7t7Z82rzFf\r\nT4rUr7mVjuREQFMihy+uRBv0S5d1S0+Fn+arp4tUOWAjnhdXA2H0cuWIP7tk\r\n4V0mUYZZVxNYICPL41jjzngZnwlVKvdohuqkgFjIWmsL4v94zdYcAEwikMtz\r\nluoAlCxRVcypkotzQbFJd7tLMNsO5IoZrAS1gVmURF+SfbeeSjbboLJtW69w\r\n6Z57YEyKx561IJrVFpcBdRgsAMbjGX5IQ0q10V6PpJrIBuWH2kENHmzMUDtV\r\nmqaWxrWzb016DQmcZaPEFZrxAYVq21Zgbug=\r\n=QyNa\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"74777c34178431aa65dde554db1c0e16636ee010","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-03-01T14:43:14.691Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.3","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.16","@balena/jellyfish-logger":"^5.0.1","@balena/jellyfish-metrics":"^2.0.42","@balena/jellyfish-environment":"^9.1.1"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.12","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_16.0.0_1646145906803_0.055128167112262094","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"16.0.1-renovate-major-external-major-a2a7667433fa90dbebeb67128b1f8dbe3c91596e":{"name":"@balena/jellyfish-core","version":"16.0.1-renovate-major-external-major-a2a7667433fa90dbebeb67128b1f8dbe3c91596e","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@16.0.1-renovate-major-external-major-a2a7667433fa90dbebeb67128b1f8dbe3c91596e","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2cbdce5db509caa6c05aa83b91439454dbf6a10a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-16.0.1-renovate-major-external-major-a2a7667433fa90dbebeb67128b1f8dbe3c91596e.tgz","fileCount":238,"integrity":"sha512-mZ4iufLYCyV0zRJXvYKdMaw/vjXjK1nNGqEe5YY4mJz/Yr4mrxHXWojuyJNjPNijfUWqpg66R7l1UNOMxiHCWQ==","signatures":[{"sig":"MEQCIHqyRgIEUPRtI6/5frps/z8MJ0HMg4apbOMyC7SpX0AGAiBRoIw94G2MvneCR1tF51egzzDZ53rmPLn5txWM2izQ9A==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":840598,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiHjLnACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmoyuhAAmfkBulR+YvUNtvNiuAvcLO/ppagrgN3lUpki50LQFSobwQFD\r\n4k5vXpCFM9wNg6zpErNRb1XcHAk1Mle5VGKamg0TywLNY5VBz0zqRbq/olod\r\n7uNNwIA/WD1MvuYb3hblmuLSxrmc8DQ4RCQbppZR9syJy9S2kiBYLyLlD3qC\r\nvodYldh15XZpUXqUybruww1VbotVzIIHx41fHlcwvG3Ox8Fpa+Ctx5zFwh5N\r\nh2pRG9vAa+cuOHSoyWTguvnrBgLS/GAqHD98H+ZsW9o65/F3K7HepXsShd87\r\n8kB3ywccSidYSC0e6ZbYsMD6eef1KoveVS+06IABq7HU5wnxk1G7THN1cmHA\r\nvTN2YsHp77snCKgWxdeVaFGa9SQOWMOA0k8Zp/2Q5rQZWy/lfL3RtDQQVBRO\r\n83XX5fohMtRB1EYoXf4eE7ltDplyUXPEKer+4j3pbkUDLyu423IR8cMxQpNN\r\n0nj7FW21JkGZE9F7rAAzLCe7AaTBLmwaoQ+5b1eEGrKebNLtLqBRPMdrCC1z\r\nCVd9Bg1VwP6nbVx42jwofiCbtzw862x3jqPImFEzMNt2mulgN52NCpaR77eU\r\nNqFzUJ30cJpadyin/MHqoPy3eZSMEzJQnGSJt/0j2gt1TW3dPiAJKqUhYAA1\r\ndLAlbhWRNRtQ9OXjtPXDsPJsAbGL0b2njrU=\r\n=Qd9s\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a2a7667433fa90dbebeb67128b1f8dbe3c91596e","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-03-01T14:47:31.011Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.3","bluebird":"^3.7.2","stopword":"^2.0.0","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^3.0.0","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.16","@balena/jellyfish-logger":"^5.0.1","@balena/jellyfish-metrics":"^2.0.42","@balena/jellyfish-environment":"^9.1.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.12","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_16.0.1-renovate-major-external-major-a2a7667433fa90dbebeb67128b1f8dbe3c91596e_1646146279252_0.5664788450929239","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"16.0.1-renovate-major-external-major-5fbade75ae30d403cd65f2cccab93b946ccead43":{"name":"@balena/jellyfish-core","version":"16.0.1-renovate-major-external-major-5fbade75ae30d403cd65f2cccab93b946ccead43","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@16.0.1-renovate-major-external-major-5fbade75ae30d403cd65f2cccab93b946ccead43","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"981f7e9475fcf4368e34a4d2eecbbc5e33d96fee","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-16.0.1-renovate-major-external-major-5fbade75ae30d403cd65f2cccab93b946ccead43.tgz","fileCount":238,"integrity":"sha512-lLt9Jq9nE51ojoKVZ7g0sHqbtQ/Cy5Y1CpGHFnXLc8EG61APXflwFZViIMQ2TTRWK5R5a6VT6E0X++ZfEYLkDw==","signatures":[{"sig":"MEYCIQCLwMKO6OOjL77GzTSnilvhAK7cKVSIwyHUuPGbr+rxAgIhAM2fpViiDSPZh7swNkHiZgJcsm+YD5PfxXeBD4lh8Ghq","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":840598,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiH60LACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2Vmo8Mw/+IJrE8SLza/y1asjZdO+Zv7O3WSxAkw1H2cBHDB1td/Zml7ub\r\nCHMgG3eKhNB6TzscnpYjwUBHL9weQbcSLL9Qs+Bsmqc/LWJ9f7AadUEnst7z\r\n3D9uGyNsoD8ChzR+0DxUlAc3ZhjYuyDTitNKgX/L3rGoi2FzVfmFVd5y68ZZ\r\nH0JzBxQj3goEIyy/BOsGRC5UKDar9JABOeox1brp3moa9AGRc+BhUmUaEKgp\r\neXUqipfBN/MufbIrMeTvLfKbErb2thbFTitU77EitSX6aIrZPRfkzBMpt0rg\r\n18m1+8hZiBi3I05Mc6RN/Xiu2VBDNF3qVBUjfL+sxgR0uapukgLZgAmlfmBC\r\nQ+sScCH3cTs/J3wm0/YdQUa+NeboHVthie7MDD+vYVM5nWD85+kUbmo6u/6N\r\nZLXhlxaM9+UbIb244GERbM052s4/tIiGtn3Uv0oHrAJtBKGn9Jw+Pw5uhuHb\r\neM8Q/usk2SS0gKnayPKuIgCw2FnJ5qwwf9h/v4qrRaiJVk50eHav03KizXgN\r\nrI4kf9RaQb4+jBbUtpB9+ply0k2aLvJqj1OIYRdBC/ugwNR/4MAJTjaUpQsH\r\nzLDKOfk0wiSw/JGUwzFv5niCjpyGedgEMPktitqkZFp06DclRz8KqxPCQvGr\r\nRjRyTg0JvrKR4xLrrr+o4MB2RcsEkS3YbqY=\r\n=JFo1\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"5fbade75ae30d403cd65f2cccab93b946ccead43","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-03-02T17:41:19.353Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.3","bluebird":"^3.7.2","stopword":"^2.0.1","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^3.0.0","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.16","@balena/jellyfish-logger":"^5.0.1","@balena/jellyfish-metrics":"^2.0.42","@balena/jellyfish-environment":"^9.1.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.12","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_16.0.1-renovate-major-external-major-5fbade75ae30d403cd65f2cccab93b946ccead43_1646243083781_0.4552954036500856","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"16.0.1-joshbwlng-test-skhema-d5690e54d90af6b919b7698a4823a124cbda6a31":{"name":"@balena/jellyfish-core","version":"16.0.1-joshbwlng-test-skhema-d5690e54d90af6b919b7698a4823a124cbda6a31","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@16.0.1-joshbwlng-test-skhema-d5690e54d90af6b919b7698a4823a124cbda6a31","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"bee98e9450ad772b83d50ac96aba5b87559242ce","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-16.0.1-joshbwlng-test-skhema-d5690e54d90af6b919b7698a4823a124cbda6a31.tgz","fileCount":238,"integrity":"sha512-zIEl2iF7yj9L9xdiAeDNO9IxMtduZM6I4t98PC2TIv2RGbZFgmAiO7r//qDYc34m6iDPdQz/plfU0oGpYwL9fg==","signatures":[{"sig":"MEQCIEiVFBVF66F0sApPVPzFDt4x6iWdJ9wXlzyB8mbi/3KcAiALZbm06onbhAIpNKUUkuNziW2sL57tXLeX7CqqsyOn+w==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":840662,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiIFJmACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2Vmq7Kw//SdTW8UJretB+/5EDdUdDuc2DU1Ukg/Gngx4f88Zg04MQetvf\r\nA40XkXvqOXC12o0e5oovwVlwrIdET1trZXfRObvkVSD3ACDbJgnEk9zS+Pzo\r\nJRau4L4xhOhPaYRckgDRnvMjZopGUjwo0C1evvQRMFkmmwiwo70NG/PGmWau\r\n7E8eQBHlOzO2UjtlMz60yQqMymRkzlzkl+N1APFjubgIIDQ4i9xupxhHA/C2\r\n9vLl91bdFW4gYjlXORBM9hvbAJf6OE+l64bd/pRGUa6ak97a/HxnY7+44WFy\r\nTvC+TL5bYFplR4GDlxuTE6PEei9nG9C2gxtqH/U5VStAou9utFF8GjcQXg1+\r\ndGvQaTZGGVZWsnpMGEY8Cr9+fd6O2f1b/fKNiIYAAeaEBWwk3fMbMrH9Etcd\r\nLf1dtsAWSvAVnqc0cK6PosFQFIvI77b2As1NuTDTf3Zkrk6NfKJM+WodypLl\r\nCS2b07yrevgXsO4pJi8SCiLyCxIux1p79x2IiKKk2tm0Bel6M6pO9OtorVj2\r\nGMDtcJneKASk2B1QYuwRg8Ey3WFyJ2iylbjyaxUh4+S/LX02N2/m9P1ej90X\r\nIEnkWvd1pWRo6Hj0WglWGXo/db/+8mC27eYyZpUU6+Fgi3wIGKD3CcbocN5F\r\nArCOci+/wAFH/NL8l0Py2p34uow24/Muwuo=\r\n=vZEY\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d5690e54d90af6b919b7698a4823a124cbda6a31","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-03-03T05:27:28.523Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"6.0.4-joshbwlng-dedup-required-a24254a88ee7a1b7d9733c7fee0276edbacbb9fa","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.16","@balena/jellyfish-logger":"^5.0.1","@balena/jellyfish-metrics":"^2.0.42","@balena/jellyfish-environment":"^9.1.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.12","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_16.0.1-joshbwlng-test-skhema-d5690e54d90af6b919b7698a4823a124cbda6a31_1646285413895_0.18673372947807598","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"16.0.1-joshbwlng-test-skhema-d9834ff10945f049ec0f661e1bd72d193122ec5b":{"name":"@balena/jellyfish-core","version":"16.0.1-joshbwlng-test-skhema-d9834ff10945f049ec0f661e1bd72d193122ec5b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@16.0.1-joshbwlng-test-skhema-d9834ff10945f049ec0f661e1bd72d193122ec5b","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9dd74611934ef041c2c1276dd01d0ef080830efc","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-16.0.1-joshbwlng-test-skhema-d9834ff10945f049ec0f661e1bd72d193122ec5b.tgz","fileCount":238,"integrity":"sha512-gQDFLQsJhaFLsPLrReMqxdfVXgWZYa+6EhpLD6oLZSQnGxUSw1Lg4qAGTiSTeu6qkiim06gSXJgr0mkN0LxHuw==","signatures":[{"sig":"MEYCIQCAa3w7oLx3UCD0oG50tdF56X4DZh4EZFH15/vsW3aaCQIhALbpvzqF4itkL9RrUP0FspqxpI3h+zoDvblYpTWnFhkC","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":840656,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiIF4uACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2Vmqe+xAAiQBJpAquf4MR6DG1zPnetdisC/stA6E8EHdmScILqGzKhwnr\r\ngelpPWD+vjsCDxGY90OWdwpdq/Dwylllcu/pynAPMr/01It0dSM6In2zGBfd\r\nLY/TqpT5gnHp0/0/dj7Lw5HMfzBIbkVHjVyoMm8Sm+UsXmX0jxBU21AHqulT\r\nTN7KsuGGSlUfEl8TSuDgBs1CplvX/0Jqg3xtqkew/Lj/R+ZP/5KkTiW8mVrt\r\n5jGrb0Dt1mWUk7dEYjP9Ol8sFaK8cDmNnZKRbvEHBLGChp7NFyPpXA3pRmKQ\r\nBvRFi0uB7GzurI7AcwL3PALticn4Pdjy7qgBhV00kKD0FrJcWIrdHp8fQGWq\r\nSvs0pziaKOv9hInxWMBGxST155pxQBlFfyzot+oJlar2dhAxcJPsMsdrTLIs\r\ngGHLazPsOEU+Xe3vrs45o7xAMBN3CPDgsMPVztrY5tDjfDiEQAg8c0y+1ixI\r\nZdiU8nRbWvsD4eeyMcFrWMT89vq3DeZGQMLPjoRRmsYVv1eNyZDuftIACSfp\r\n/dJOZhWErtSSdopV/wHvpXryCLWLU0RfLt7+24XFAOtXu9v0NZp5gwQFqAvL\r\nnjqGuz1q7HjGpSsepTat7s5qhXinF/IT4VBgpsIDntK6kvQJT16ZyHDDNuOE\r\n7d1L7Zn/JBRcAKou7RO4sIH4fW4zPpvBRnQ=\r\n=BjaN\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d9834ff10945f049ec0f661e1bd72d193122ec5b","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-03-03T06:16:47.571Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"6.0.4-joshbwlng-dedup-required-87d251c60204999fff8003ec64eacfe6628a8e13","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.16","@balena/jellyfish-logger":"^5.0.1","@balena/jellyfish-metrics":"^2.0.42","@balena/jellyfish-environment":"^9.1.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.12","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_16.0.1-joshbwlng-test-skhema-d9834ff10945f049ec0f661e1bd72d193122ec5b_1646288430281_0.9952086802396103","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"17.0.0-joshbwlng-update-error-checks-31dd351aef0241471e099aa6445de27ccc76fc4a":{"name":"@balena/jellyfish-core","version":"17.0.0-joshbwlng-update-error-checks-31dd351aef0241471e099aa6445de27ccc76fc4a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@17.0.0-joshbwlng-update-error-checks-31dd351aef0241471e099aa6445de27ccc76fc4a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f48269cd57cbeca810d801d3e70df45d7ad4b9d7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-17.0.0-joshbwlng-update-error-checks-31dd351aef0241471e099aa6445de27ccc76fc4a.tgz","fileCount":238,"integrity":"sha512-kGRJfNKBC5tk9I1VK3L0TAbk9B16ufrOkaDJ6ZPtKjr8dMhKlC4pOepqqb08SA4YLPwY0u0gEtZH8hwTotTlrg==","signatures":[{"sig":"MEYCIQDzpfB/P3v0ZTpz074wgW9IBJ+njc4MmAaOyqmlxl+MLQIhAIvZ/S+VuDVUYRJUs+zyT2zNnSbcuX4vEJxclWFjbslc","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":840427,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiIa25ACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmozrhAAiMel/8JJiUkVbUy97emEuCrSkTDjtZI9b8ynlraxf5O5Vn9f\r\nqN/eYu0345LR3t3JrrelgkUlXh5tJMnpv08pnIoK5B4tuFpyBB8Tfmsb8R2F\r\nykRK2v3rOtejXAN7+GeD+hKrynhtib4aIB9yuJs2omP/bujWlF+IGG6CbKVk\r\n51XhWR8Id96Q6LJwj2Pe+JTIoyPcZkjJHWulIqTrTDa+xCxC9+EeRA6U18by\r\nxvTpCnpuOKPaHYpX4JMLo7Ztak2p1NL+7ilyjRecLdpbSbfaCnfSAOK7eWJG\r\nnPLFh5NFsbJW7Q6UW19nIu2T+VJ5hDF4EUFzuudtJCa24rewxdDny91bTVyq\r\na2BTj/0xjbhvhRRV6NJ7SSNXrKITXCZKgZTDdwJi/57E49yfxJz94iliFt3D\r\ncF09cUMm3sQ3zrzRXdG8DynmlrTOfbehEoIoyO5s8NxD4lGwRaBfoSrc4uK3\r\nmHCooBS0CcTaIV2aYnrVx0C6I/jnjw5wYYjvYa7bjyKAkX3BwKGWv3j5it7Q\r\nDrnvDCmyXgn89hcAS3hfYVhgImcSZR156qATtPVZ53HiY8fN1GiDlzlfpp55\r\nGOuQwqu/cXYN3BY9UtMVPJN8Y3mjOjOcGvZRoBCoJhvSyBUko2JsR+Vkc5AQ\r\nxeZeBTTyYZ+en2Qe5yfGZxRFBNCvos+XDuA=\r\n=+1wD\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"31dd351aef0241471e099aa6445de27ccc76fc4a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-03-04T06:08:59.433Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.3","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.16","@balena/jellyfish-logger":"^5.0.1","@balena/jellyfish-metrics":"^2.0.42","@balena/jellyfish-environment":"^9.1.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.12","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_17.0.0-joshbwlng-update-error-checks-31dd351aef0241471e099aa6445de27ccc76fc4a_1646374328766_0.4447170557927904","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"16.0.1-joshbwlng-update-error-checks-f0013aecc61f8770ef816d97ef9febe7335938ae":{"name":"@balena/jellyfish-core","version":"16.0.1-joshbwlng-update-error-checks-f0013aecc61f8770ef816d97ef9febe7335938ae","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@16.0.1-joshbwlng-update-error-checks-f0013aecc61f8770ef816d97ef9febe7335938ae","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5aae30d76e128d77740127ce25d17b4785780417","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-16.0.1-joshbwlng-update-error-checks-f0013aecc61f8770ef816d97ef9febe7335938ae.tgz","fileCount":238,"integrity":"sha512-aiFOwv8a3a4fPHit9tqpLocdttzFvhw5FKse2J2N0MaUaDeYrQaW7csWw/wkhQH7PgfeFwPgIIWAsoM19h1fHA==","signatures":[{"sig":"MEUCIQDacJok+klbHElVPwITGdRp/q3YLh6KUanvG26/RxG5lgIgOwbnoiDvFPVjUJR7CSr2NEytMCSWPDPtu+iKSeWcGoo=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":841491,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiIbfJACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2Vmpj4xAAiSNgRJGpBnbF0YxD1toSDmqL2aKsmYzl4ALhMG/3MoEtm3FM\r\njMsh7RyNvjyfj82+zTim8tvuHL3J6ioxBprzC9zgdwDoJ7b9XyITHOo2Cg2d\r\nDDAv/2bDay4lYIA57FU9t2aPqGMayt866//GOjRUcdyMd3cAuyBCwnhL5xbi\r\nAt/QoifOgLw7L+0GTBljBnS8bkLY7aJOkwGiRMGj/nxXIzo+FjbBH9BpXWCL\r\nxf0x8MHsixoZeBD3viv7mpWjetqraXdZS9zE0MXE52SxQ77LuL14ncyfCsU3\r\nwht5xL0Nm8Lizn9tBLuOD8Dw6LMLyZt9X2t5xHXYvlqs0wSDvubHxDzgFZ9E\r\n2ajT+3cWKInLH6kL7YaF5jSWLLMi1n/KDuMoga++LYM8XTERwMacW3UEW2QF\r\nzQ8jMeNy2JREUksrLANqx6kMcfMfIS5Qrw6DA7c2h9gEX28Ttvt5yyBphFsz\r\nr58CaYR63U3MhElPVvX2G8SAsdFgb8rZxa/cj8rfEmAmbOLFNHwXVPIhhjxI\r\nWNiQ2Rjdl+kLrY2U11h5W/0cpgyWEgZ8QRvcZD6hjGQS8BcGbn0StE6I2g5+\r\nVfQTbryTpoCmZS14Hy+y8mRs6U1FpSkdokaa+3Rr5Mr+sMVCQPM7qMyX6SyU\r\noiQ9O056DniPnoTiKENDypexJ+bj2YENRiY=\r\n=30nF\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f0013aecc61f8770ef816d97ef9febe7335938ae","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-03-04T06:51:31.821Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"6.0.5-joshbwlng-add-error-type-a7dc337166a74ad9570e30d2d10c91e00661d366","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.16","@balena/jellyfish-logger":"^5.0.1","@balena/jellyfish-metrics":"^2.0.42","@balena/jellyfish-environment":"^9.1.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.12","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_16.0.1-joshbwlng-update-error-checks-f0013aecc61f8770ef816d97ef9febe7335938ae_1646376904846_0.9138882580781127","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"16.0.1-joshbwlng-update-error-checks-8c4e342019b82b2cde291ba053452dc1d1708e67":{"name":"@balena/jellyfish-core","version":"16.0.1-joshbwlng-update-error-checks-8c4e342019b82b2cde291ba053452dc1d1708e67","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@16.0.1-joshbwlng-update-error-checks-8c4e342019b82b2cde291ba053452dc1d1708e67","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"48b98277f506977de6888cc5e22cb10f120e22aa","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-16.0.1-joshbwlng-update-error-checks-8c4e342019b82b2cde291ba053452dc1d1708e67.tgz","fileCount":238,"integrity":"sha512-45H4S+9sNDwAftgMM04VRJoVjdnT9rYUV03gULpYZb5gAq4wf2ur0fsqjWHZZBc5rztVRwLG1zFHEf7LRkjYOA==","signatures":[{"sig":"MEUCIE+2wiTjm5blzMpKibGdFV/8l/JUZZpk3+0XXODKnVHEAiEA13Y3Kt9x1WElHrQCN8Yx5D0uxmwQiXmXFkdlImP7Uxg=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":841339,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiIbkKACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmpNVg//W1oQ4baalp5v6Gvc5KiHo371KQr5WYR8kBTK6IMBHLFOMqUq\r\nyPbJs3gg3ALjGiUXolNN2m2f9xZxjd7UqF9PmhUaNLPkUTdmVVIhs5lzdJwO\r\nUUZwC0Z3sp8sb/1pOmu0C3eoitF2/cL+wJbqLb/Lxzbt6JESLyWJig31DQf0\r\niYCnceO1fyBiKbwk1OvBxxo4LQwrdweKeVHR4F3//ujNJxGY2XCUebQH0W9b\r\nRFOwJmbhUnXd4sO7gH/xrWo+ZyYvckgEGXwruplZlOk5vvUmH8k7iY4cc5I5\r\ny43ii88Qx/iKeF6ur3ONCsmqojYjHR5V2BxaePOTCZo+XwbOR61ZCe5z9RE8\r\nbJuzNNM88MqwjXc7u5zh9xHKh2YitxsLs2a+lKm7u1EzeVLtR2MIIyBJqqSt\r\nr0DLIEmxxDD0xRzHKOZWF38GZ6QKsUxr3QEw1Qnw/DcoNXjyz94RNGzq0Fl0\r\naaDKVKEC6AipwKRS/7nLIIRfFd9+WB3Fq8sJ40BgCZZEvi5uXDKa6D5dkPwO\r\nfmYuxrALaeq/ZdvuQt3Th6dxymA/Ireb1P+Ycs+NvoX19VXB7DdmYtuel8o3\r\n4Rl7iCwCo48/0B3gDLFVr4wm+4K8I0mS8A11lkyj3xfkI20mixvbmAqRSzwy\r\nS/J67OZ3AIqS27Ecq+QQ4NZB0ynsAZopxBo=\r\n=SS36\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8c4e342019b82b2cde291ba053452dc1d1708e67","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-03-04T06:56:16.511Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.5","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.16","@balena/jellyfish-logger":"^5.0.1","@balena/jellyfish-metrics":"^2.0.42","@balena/jellyfish-environment":"^9.1.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.12","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_16.0.1-joshbwlng-update-error-checks-8c4e342019b82b2cde291ba053452dc1d1708e67_1646377226235_0.8096954157222978","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"16.0.1":{"name":"@balena/jellyfish-core","version":"16.0.1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@16.0.1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"09e87ff0a82ed218d1f7a5445d17c2c7bc62f7b9","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-16.0.1.tgz","fileCount":238,"integrity":"sha512-2V1XenDaLVXxntwB0mJnmQaUiSfyrRJ4E0nKMoFYasK6IOG0IT8a9JlefzIWNywxatJSV6h9Uz5DpaEubHvRLA==","signatures":[{"sig":"MEUCIHr7N3zPMm0K63MUiOf+sIqQKKjmG3S0JeasDhlFrTxvAiEAwwhKmfzNo54FU3pL2WTJ+nE+dQVsar6DX9GIIgDFuVA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":841268,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiIb97ACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2Vmr6KhAAl+yQrLT7g9wiSLcVkZrc9PeY9wAnnjKRePt+hDTpneyoTkaR\r\nYJjLSbZyd3CuA0e9nmorHJF2Rad33nQw7WbklB+EzqnuP7OBvAWMqrBpwtfQ\r\nPccMqh4eDRHCfJFfWXPYyLRtnc58Fps4B+jhGrtDowt3Yp95a58gS60h8utk\r\nPvpROaTayAOmVmaj6+TVxKgY2Nwov5TthK4Jo9cFv8pcMGbrQh31BohOQXeZ\r\n+ZGuSLdexqxCtZjAaQ31feS+5L+wPF05YV3WPuFv4bYjRdh0RtuhAVAHIbXe\r\nqAQfUjEkTEyFymfHpsYvlZNi8WFbTInCHPwTGaLpHuqntCAWIJC4zqj8tDUg\r\n5L5x2S2bXVuFos6jRw/UwoS6Ri68tMZox87wpTugJeu7GX955GqPOPJzcpwl\r\nIuI3yXW1rRKn8/bMV8o9zjT6pHufVeAwc8pYI+yykAScloX05ynIUwxWNs4i\r\nj8PPlTiswA7k9d1HOYknKZyknLFW9Kd93NPVX2oZidBYVC+URESviQXmOYv4\r\nIJEXxZejDq7HHBkwg9KRc7lRe+M1T4iw/I4nWlxDV6zZeaHAD1zVhH6NDOSn\r\nJnrqi/vOTKoX2i8BacnwmlTEZIpjIS6ToyAWaGQ9+P7fimtyA/gnkQSYrCeo\r\nAf/JbLOzBjN2Z9hainYJARyF0vDTHUJX2Hs=\r\n=rt+r\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"41c47046a85b04b633d79bdc1867ae8351c617a6","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-03-04T07:25:47.657Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.5","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.16","@balena/jellyfish-logger":"^5.0.1","@balena/jellyfish-metrics":"^2.0.42","@balena/jellyfish-environment":"^9.1.1"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.12","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_16.0.1_1646378875387_0.44179580516650674","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"16.0.2-renovate-major-json-schema-org-fe1c952e6b203f06449b7b3e6f7d81f320d12360":{"name":"@balena/jellyfish-core","version":"16.0.2-renovate-major-json-schema-org-fe1c952e6b203f06449b7b3e6f7d81f320d12360","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@16.0.2-renovate-major-json-schema-org-fe1c952e6b203f06449b7b3e6f7d81f320d12360","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"577392aa331ccb4011cf93747eed2efeec784277","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-16.0.2-renovate-major-json-schema-org-fe1c952e6b203f06449b7b3e6f7d81f320d12360.tgz","fileCount":238,"integrity":"sha512-kDIPWVn5CELmhej0lqENbTp9j+yRskOcXFEOAjf+9xZhPjRwVZXsu6CI+HjcsPaTfv/yLInJOJSNQkyKzuEomA==","signatures":[{"sig":"MEUCIAlpzq8SzyohvmlBA0Q3f99WceyYzDr4TwSR1hMyNqjNAiEA2SaIEjbRm2tmZrljmuVgOOgmxt9GqHknbxcnI99IiKU=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":841425,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiIcBBACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2Vmpgog//awmSBVQ/1xdiPBHV8bmudaItvoEOXm/hZ9OgHxt6KLy2PsL/\r\nwmxpIQe07sXDd4DEZUHm342uTlLDhJ4oIokuEr+4MreWT17HuA7INz0ZV6Qd\r\nXavYxZcwqYaNCtcvYpqwE9elh0XqITBc4rz4UtLTCKJOex31+V5jh500kFtJ\r\nKME+P8B1wDUIpXx0GKYO5ZeVxXkicf+V5k+OKlWmradUyvpOnnXepCiRGvvh\r\niqVS/IMYCZDYt0BzbF061mgRns/o+xuRTwD9AM4N6f1W1UZP89l+2qYbI4WV\r\nhHePGhX7qYDyusfaAmKWoD1NttN3uEgeOcflEnCzCAgJahmZO35L8V+qEZpu\r\nT2yBLqvXF7KB+KT4x4D7bqxOJZ1t6TKKT5tX9bZXKyy7My9fZsQ8LeVnzcKJ\r\nF3v7AmUNnYmIcEFz2nRyIFTLTDSYi0KYGlQ9rFB3C3B7a0u8cptv8dbMX3/6\r\nJeHQ/SX1DQLaM6asfV3/692AICpqKyiShBGifKEIkP9pe3qtS70SUGdeK9OY\r\nWsFWxHdttaWMOHUl5s2d+RsKKZIx8jjdU7PMjEboDaEZnA/fzcL3ZXlJc/Xj\r\nE7F+zodg72Wm5AMldNFFwnQwkS810+qQE5E5BhiZSLl0vWAWuCiywhSe9umA\r\nrCkrfMsLb0qkZgfbYNVgMMVsgVXv2RJe8ck=\r\n=OQ27\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"fe1c952e6b203f06449b7b3e6f7d81f320d12360","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-03-04T07:27:52.685Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.5","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.16","@balena/jellyfish-logger":"^5.0.1","@balena/jellyfish-metrics":"^2.0.42","@balena/jellyfish-environment":"^9.1.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.12","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_16.0.2-renovate-major-json-schema-org-fe1c952e6b203f06449b7b3e6f7d81f320d12360_1646379073147_0.7317009767145155","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"16.0.2-renovate-major-external-major-12201dbe25666afe28ea93dc661dee472ce7916d":{"name":"@balena/jellyfish-core","version":"16.0.2-renovate-major-external-major-12201dbe25666afe28ea93dc661dee472ce7916d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@16.0.2-renovate-major-external-major-12201dbe25666afe28ea93dc661dee472ce7916d","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9b4fd3210c97c53e8ddd88819e0b2d43d0e8931f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-16.0.2-renovate-major-external-major-12201dbe25666afe28ea93dc661dee472ce7916d.tgz","fileCount":238,"integrity":"sha512-C9l+ISGMADICT7UWLKW60nVXoAxSKjKMyR8n5dsMNtLmSFMYJgpqCI7MS6r9Ou2E8Tf6v1oXsf3X2yjKeYS1iw==","signatures":[{"sig":"MEUCIEZ5yXBVPgrHkfeDDwBq2vzR8KctYvNwVm01ijgmkWITAiEAultt2UAeFsR2p0Kom+Inzx2Bk5naJOL4cQeBIUMHq4w=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":841402,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiIcBzACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmqUMw//W3k9EFDi5psUVuHJaeYhfuWCnc3OKnJOLB36syc37kwYDP3j\r\nZr6MEBHW38zm3RPEWEmgQljH/N7jmRBXTcXN6I9VOF9cmpzofZup4ncz6Ozb\r\n6DOwPNvzfHSWEyUAzyoBnOjW84+x9DlXHTLkPFFFvvwuLrtf1331y9BN5biV\r\n0cRCbhEKay1qQWBrAkmamZJxay15En6diSmBM0+FJ1EHC9GlNAoqMIMMelr9\r\n2HhsYRR37NmhsDBcx3srCUEY3nqLMQ+KmuBVJ0bNnEYVezwKNdfh2EJML9Yu\r\n78XvUfwMWDDVL8yKZUoMJoR+eDhuj+q7egWXmPgSAoxHdZS619o4Da+Q3Qlw\r\nttpqMSAbD8XnRuNEtwzRGUqaNUfedemDpyvJafyAF0LXfOv0vxYIEnGRUSCx\r\nSoAmGLHD7Li8nr9cgo/jFc60MGNFtwqTlKVdSftMor+r1oaBO1g/nhOT49UY\r\neZuPntgRA8alJHy4KeE8Ss5td88WWSot8tHhNcyE9pbJ9C+Ol6XoHlI8AKlB\r\nAiyxbyJ+Zkpmy2UEH3jRyHUo3xzn4g+DJ6C8/mRuGwken6IfeLlPHcM0XD3B\r\nRn29r2BpjQEE9/srSRguOTZiqQjeOVhH6hXTavV/2TRMS1uQaWXQBMNZx1Kz\r\n0eiUuOVgZxCdVXObjIvWDXhWu7eKmiynIk0=\r\n=J37G\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"12201dbe25666afe28ea93dc661dee472ce7916d","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-03-04T07:28:23.727Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.5","bluebird":"^3.7.2","stopword":"^2.0.1","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^3.0.0","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.16","@balena/jellyfish-logger":"^5.0.1","@balena/jellyfish-metrics":"^2.0.42","@balena/jellyfish-environment":"^9.1.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.12","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_16.0.2-renovate-major-external-major-12201dbe25666afe28ea93dc661dee472ce7916d_1646379123230_0.3400932519748714","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"16.0.2-joshbwlng-bump-skhema-7384982d0852a741c156cb5cc07dd7908557f10f":{"name":"@balena/jellyfish-core","version":"16.0.2-joshbwlng-bump-skhema-7384982d0852a741c156cb5cc07dd7908557f10f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@16.0.2-joshbwlng-bump-skhema-7384982d0852a741c156cb5cc07dd7908557f10f","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f88dafb04606bbd0ef541647649932bb7dbd9018","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-16.0.2-joshbwlng-bump-skhema-7384982d0852a741c156cb5cc07dd7908557f10f.tgz","fileCount":238,"integrity":"sha512-J9CgpLObia44ddmFN+oh1+0EKzbLyfM4JGwcpwNcTOB7zhKoO0vEt4l2nHkYvzP4MCZs97cWluJZUfZsTuI0qg==","signatures":[{"sig":"MEUCIGtKNv+uLKopONyYCbwjufXvbpD44gOOsRDSSEgQq/XeAiEA7Px3SL1exxIyKKPsHa76QUeSraj8h+thDl4bJRLRFi0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":841407,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiItgGACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmouTA//dMnCCrVcPa82LiR5vyjOCZ29Orqavzg7V3DqBEt5IG+p8aDu\r\nCoJVVF+skiISrUpwK5dxCbtmbZmtG7XM0zLZJbz2tLm1vtk1phUJq25DOUrM\r\nt5AeAun1ruyxrsMl+eCCjMbpFdVBs0TN33dJ82jWkoN7Nz7bqYHLviMPww3Q\r\ndrnLL103ITmFyCkyC+KPvz0+RSrR+o1rwnRi+voM/yD+pBewea2diM5h1Huc\r\nl1QpLgtphjDMSpDgcil/hJj4mwpOdhS1dFyC9jkWS4fWwxwTXiuz5vs9x6gJ\r\n4KeJhVJWzEBVQPo2R7Okt9lFc1AqOo2YQOY0LdG2cW4oziTQIFgK/TopFdt5\r\net2s1VbJfEyThHSs1DLeUCVv/dwfvIXKo077Ti6bQKdZ2p4JD7+pqMcL7anx\r\nnf/nbnM/HTvhYjaYpbl9HVzf17mwJi0P9Gdizchnea/zMdSum8I4TYAs0Baq\r\nQpb/5PzO+IFdvAhJl7Pg4y1spDQOn4/b1WoKuEZtxnIxt2rYsDkWclG8M0Cx\r\n4QuAwo9BEyL8o784AP4g3dXc12O0WVgLI2OPm73SZeJ5d/SwrovZu9RiiXOt\r\noBFWwrrUL9f9No7RW/d4IFSBHk3il9bB5Ds/+ezRzYDUC22M13Vwn2fch8ny\r\nuKAGg6mJBWPMtl1JX7kdJxN3yyzUh/R86r0=\r\n=ozRl\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"7384982d0852a741c156cb5cc07dd7908557f10f","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-03-05T03:21:17.509Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.6","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.16","@balena/jellyfish-logger":"^5.0.1","@balena/jellyfish-metrics":"^2.0.42","@balena/jellyfish-environment":"^9.1.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.12","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_16.0.2-joshbwlng-bump-skhema-7384982d0852a741c156cb5cc07dd7908557f10f_1646450693815_0.040638769403148256","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"16.0.2":{"name":"@balena/jellyfish-core","version":"16.0.2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@16.0.2","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a89b44073c974e784496f083419fde148876c29b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-16.0.2.tgz","fileCount":238,"integrity":"sha512-W+auHAJDynV6mkxgRxf60q1oBa8q95kl82w2NcK8UJCbvjsvVpr8weCVxuCvpEgG39VvJgqfkOiarmFeTZnRPw==","signatures":[{"sig":"MEYCIQDcC4s1RcaQ3LPy643tqQQl+rffgom48GAJfQas17n3iAIhAJ2xHYCZ3/GSchU0SR3w+72J6PEBKAbPn91uyi2j91zw","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":841344,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiIx+tACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2Vmp9mQ/+Lh9eve3SJLAwyEynvspC4mX72AP2xOjRNZ8iwBtpJfjIcQaN\r\ntqnXe2hQftVAQ7KAqSQlUV0Rk2ymP8MpC/PDbhuMzlF2zFg1NTLB2cYZxGNE\r\nrpXappNvpDtFqAu8F9dBBsbRdSe+yCntACc+N3aHmCJdscpEMS9cwc9D7cz8\r\nRRGTcPGadUZPH0Jahy14tB7g/RByI+ZzrwHRX1BhzS8aHmZVqOleNLOr7kQl\r\nta1hSpHlH5DHy2dG7/MmLj+BZIrT6rcUAhtmk4x/e5WuqoSnH8V1CuhLY6eW\r\n+8DcQl6OwPQbdZsQTfQLMNnl+1l5hpOvkVhnWLukT2r/Zbn5ZkxYvYf8k9dv\r\n6XHrOTU0vHArhyri2eDribj2qtmgZKtQjshT7Bfxl/aP5kGJC+KTVpgf3dlS\r\nkM3vBbh/W8e/N4zHAADvwYdNEGeYNpAz3cY5ybaDA46S81IBPKSWa52fB05K\r\naF51byAdjXmcdo9IUJyoZ6hWpS2NIZHjHvwJ9AdJaDbGY94aM95H2vnVOcXS\r\nGH3p0KTWzpnv/SMBeHJeAhxfTDCuslFRspQgjV51lrk5KrNPd9u2OXUPoH/Y\r\n8bpCUdN3XA1M4PemDRM1Gk51dy5ydhzWFUFlV+Yqrc5v/aPZueCi6BBp35jo\r\nSMQl182aXm4Uscsy0hzGRxpVItzrXt9vY8E=\r\n=ivBv\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"cc7b64aaf140916a7c21c5099f395f9270180dc7","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-03-05T08:28:30.557Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.6","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.16","@balena/jellyfish-logger":"^5.0.1","@balena/jellyfish-metrics":"^2.0.42","@balena/jellyfish-environment":"^9.1.1"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.12","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_16.0.2_1646469036976_0.35928025812839537","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"16.0.3-renovate-major-external-major-f5b87ad196b9b1e0c05acc5e912a1bccc13552ea":{"name":"@balena/jellyfish-core","version":"16.0.3-renovate-major-external-major-f5b87ad196b9b1e0c05acc5e912a1bccc13552ea","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@16.0.3-renovate-major-external-major-f5b87ad196b9b1e0c05acc5e912a1bccc13552ea","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2897fe487372e2db6fe737dc79c4ce52beb48243","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-16.0.3-renovate-major-external-major-f5b87ad196b9b1e0c05acc5e912a1bccc13552ea.tgz","fileCount":238,"integrity":"sha512-oLc9fKvvA2jpfFeqXRQbW7ePl2mjnPUtf09Xy3/h28E97j1l8LwHTIYhZzh106QGP7iNZTPQw8W3kpXm6R3nvg==","signatures":[{"sig":"MEUCIATzSdwRyXUDYk0LO4R+1gRVWd0pN/jQNuAnshYK/74GAiEAixzH4B4Rwy9yqdorsuSYSy9oYqgQRp2c61xBC9FZLRA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":841478,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiIypSACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2Vmpq5w//QVmMgYs1gs4qkntGxes7oCRYVHqYaEiwnyQDJRhCrPn/JshQ\r\nVwfoQkKTdH5AVlDVv+RBfzc0j78Z6PH+rCrjGw5hvZSdEoSGKWgtcru9Er0o\r\nCyyF9vQFga/N2eSW288Xx15bP10bYcOBAqjEyj6YcOXPdOW/lTye0oceerMC\r\n+apkrLf2WlckJTMcr5A8W49MAlGxgQ8AI4yBiDnsmtcYAOCS8+G/Z5BR+Q9R\r\n9eJvpZ9Vne7DAWnPSAtPHGs8WqSaB57yUH5QSHV1W6wWRyk5cOEJ+1SgQpNH\r\nOSXVWMVxP2hjIMljNYghFmQq59gx5MQDmt/uEyodyk+crACjMXHgNNKHa3Qu\r\ns/Ot+6cFNfXFwr1jjEp9Bfp7WR0myQDk4DUor/DcJnWITwPM1bCf5PLBtPJs\r\nrscDsmaqzdQLn0qmUdsi1KkQ7wTCl9ewXTu/ymYcrVHFYVjfwJqXuhPpkcoo\r\nX6cTDv2bFqsgK306Edqdr8liZaCymFUUup8uymPywE7bxIhAmyT3OH0GRHoD\r\n3/c2H1Mj1VKS74jKYbmJtX/f7zu6WMdcI/qUsy3+PtMuPlXjA9H56p052yGw\r\npIZKcJzAF0eX806GTW1L4kM3XD9KWw7smTEb2zLN4phDoFQAJhGhLqPnjogC\r\ndmM+LXAtOFaG/Da2zZbUSOKI6vwpZ2ruGLA=\r\n=KoMQ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"f5b87ad196b9b1e0c05acc5e912a1bccc13552ea","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-03-05T09:12:28.583Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.6","bluebird":"^3.7.2","stopword":"^2.0.1","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^3.0.0","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.16","@balena/jellyfish-logger":"^5.0.1","@balena/jellyfish-metrics":"^2.0.42","@balena/jellyfish-environment":"^9.1.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.12","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_16.0.3-renovate-major-external-major-f5b87ad196b9b1e0c05acc5e912a1bccc13552ea_1646471762520_0.8390495430657476","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"16.0.3-renovate-internal-patch-37dfb8602c6692c4848c391d7555556389d1b4d9":{"name":"@balena/jellyfish-core","version":"16.0.3-renovate-internal-patch-37dfb8602c6692c4848c391d7555556389d1b4d9","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@16.0.3-renovate-internal-patch-37dfb8602c6692c4848c391d7555556389d1b4d9","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f48c1572fe1b9f02352ae89eb776fff0dd4a64ff","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-16.0.3-renovate-internal-patch-37dfb8602c6692c4848c391d7555556389d1b4d9.tgz","fileCount":238,"integrity":"sha512-9XMpSRFsus26AeGxxhkMjkO/OMbyq5ytAnh5zNhFkJ5gOY0VrViXilbfd7GNUcc4didzE6jIssS7IfOCZqx3jA==","signatures":[{"sig":"MEUCIEFyROGpgCYyqBiPwuBZSllNvvmwqEtA4hqHIBpP9Vk+AiEA2dxWyJwQ9nwWK1ZnwGKk3IFwlSw5ubW/Urj2Hz8RARI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":841473,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiI4+DACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmoQiA/+MWcUZ2lwnAV+GKaMzKusuGjs1taQeL31Ium7iLMctS45ai4g\r\nYd77Ss/mPY0oRhST5IwYRKCXH/eqg0YmYSUoMZTOXDUWteX+V1v6afSskd7O\r\nCAh5E+n5/hIF/nVXg54ODovAwkGmXUhjdhbDrh+VdcbrZGPgo82VW/bLBgZD\r\nSd6AxQG0VRVUg+T7XS6P7KCKYtD6Nw9+0ytwY/AjwE5u09PMnhYhLcPa4JvP\r\nZgGkz+0jKElW7X8YMSWWxlbioXZ1sSdaByuWR8fziX+VMjbZlUl0Tkj9if9u\r\n/T4qYwYZqnBsmZ5cze1LymBWPye5/pvj1q2XHUFTTMU9kOG6HTFU0MGgPVcO\r\nzMQkfIprZ41er2E6n6wzjnkXylqXsKXNGclxpRviijmljr5oJbVtsQO0N3yk\r\nDW/i1naewpnD+WDUpQSuUrzeCfSDJrYasHkDVyijZ3Y1+uZn4Fimpwf3DJqI\r\nJKgMLJqIkDwZWhJYUtKYeu7x6bu/JD/nu/DcIFnkRRqIX6Ro/7rhSSy02W/v\r\ncs6n9Reohoy/KwqNSV6TLjlzt25U4189QYsaeo8yNSuprKMQ+lfNCMIn4yQe\r\nDxCSqOsRElJgr6+ilAtGaNPQjwCoCLA27Z7/DMul5uDSexL+ad/pn9VHq2U3\r\nBD/tOaU2r6/jJBFSbvF2KXCCUYh+eSRcJF0=\r\n=dKYA\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"37dfb8602c6692c4848c391d7555556389d1b4d9","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-03-05T16:24:00.088Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.6","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.17","@balena/jellyfish-logger":"^5.0.2","@balena/jellyfish-metrics":"^2.0.42","@balena/jellyfish-environment":"^9.1.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.12","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_16.0.3-renovate-internal-patch-37dfb8602c6692c4848c391d7555556389d1b4d9_1646497667723_0.5507065864529483","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"16.0.3-renovate-internal-patch-83cde9b86dccae88ba377777be3706661ea23fd7":{"name":"@balena/jellyfish-core","version":"16.0.3-renovate-internal-patch-83cde9b86dccae88ba377777be3706661ea23fd7","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@16.0.3-renovate-internal-patch-83cde9b86dccae88ba377777be3706661ea23fd7","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"243ac2ca45c1a1c4577ecebb62cea67a1451a5ae","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-16.0.3-renovate-internal-patch-83cde9b86dccae88ba377777be3706661ea23fd7.tgz","fileCount":238,"integrity":"sha512-YNFEDY28omkE5Q2U9GR2bbAm2Yy9kQD4axZ4iVMwqj5nQHcEhNgN7L9zjGKtWK0uP7QNzvDi/HC9hPPt67mgkw==","signatures":[{"sig":"MEQCIB1lgYNhHM2V3INahpttO5PPLd9Li+EQaL7GxRA60MByAiAqkOn+sgeMGmzwuVUpRwlYfvdrxFAe1/NE05sA+/P6ig==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":841473,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiI5DRACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2Vmr1Uw//Srjo27KORVKqHZs0Qv9NzjB44GFPGVssrs/uBWdGntSViw3F\r\n8FKmAx2ZzSPeK2SUV93D+SGssHDXjUpzrdUg8T9nhdFA/r+oE/07lzVhwEx9\r\nHcJSEbtD0Zk2PNVOc2Z5rNJ75UwVC+tPlUKvzUoerhl+pSFzF9+FNb8pE8gp\r\nA29CtJC3NvaRlWtLRVBAfByiAVRaohgWdJW49Fb81iMNphSLUV+/iUALOkUH\r\nb5DD3S+ykjSfqO10NbpnA2JvkB9WAV6IHdlFSpaupAOQ6pOWDg7rJYwAAQuX\r\n2I1k/gQdLOb3SEa5YjPOYW1O/0Yd+tznzptYUc4Xj/8cjl60/XVmCD8dI25O\r\ncTaBsFgg3eHgw0HSCRgzKgqC5w3N4e1Z3xL8afcP/jUGqyyejbgFD3WtG7B7\r\n71MBimk1G34cLdU9wEQiK9mrsNHFsZF9mMdrsfHHNXh98i8d1vx/BkJglCfq\r\nibNSXD4FLeC73/k89CtA5Oh4je3pglFa4KxBNmAvLF9tcfSogLpvT0/Ir8ty\r\n7d13ZJpx36g9PoGRDGjApV0aDSNI19xwIBvjqATf444XSZWvLJ3gxByFxun/\r\nUGBgCIIs0rYcrkhn5I8oC3RtrKu9hm1jsGpHj3sdXouwvdBC5qDLM34CtNeE\r\nD9G3IvAdUrxfMz96rWeXki0X/PMHDX1BKfQ=\r\n=LPEY\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"83cde9b86dccae88ba377777be3706661ea23fd7","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-03-05T16:29:28.709Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.6","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.17","@balena/jellyfish-logger":"^5.0.3","@balena/jellyfish-metrics":"^2.0.42","@balena/jellyfish-environment":"^9.1.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.12","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_16.0.3-renovate-internal-patch-83cde9b86dccae88ba377777be3706661ea23fd7_1646498001334_0.5519601039748434","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"16.0.3":{"name":"@balena/jellyfish-core","version":"16.0.3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@16.0.3","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6fdb1c3871bed8b21e3743cce8ca6468315f685d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-16.0.3.tgz","fileCount":238,"integrity":"sha512-Zhl+lYwf/Mi3gA1RQyRalBX1mhDhk8SL8ZMsy8mtzi0tRj2rpx15ggJGrqnBF7Hnr9LhiidObvf4TE3PaSSGgA==","signatures":[{"sig":"MEUCIFZakEt2LAms7fR5peCtiKeJSCyqKvqiq7CGhva12+mPAiEAkvgYVjXlsccCSLx5vBvJFnrNik3uh80b4U8AvjH612o=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":841408,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiI5G9ACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmpISA/8D6+ryURF9nkjxvGWehffyTtpI5A8j9NgXr7H4US+UTg/Q7dH\r\nJk21g4rBGH4tZMOAmR0mbvmBmtccfugDBH7C1H/2sjGghxvtABtASCrdn7ph\r\nvjt/Ns5DbcBzWLM8y7T3B+ns5WqH8UXYGyZW9mnOchPJgihFkNEEv/NGhE09\r\nCTbANd/OC81g6NgzZpzdYqHxJc8FF1802YBGkdoZT6/+Zjp7mszRN0ZPRaCb\r\nAUYfBP6onAC9q6EOYhLIqWODT/VkAV8WQ2e3atvrXm5S2sABQI8OGi/q35qY\r\nhfn8Bu1/eIyOtTD/syFiZODLhGAiNx7kkAR/saMICPaGfv/0jUA9VLqy/ceP\r\nA9UdVVoCAIGMtzOg93/slZaNpiL75XJe+RJ9abmeN71xEQKHzoGSF+D9blWG\r\nQS135dtTqDfqqDnSqvjF8kH1KR8LP+Tywvk0qbZUXzKajxwtbvLV/4hwbmnp\r\ncndVWircnJAg5W75fWHBlzctkG3Hp/v6dHQw4iofj300DxOeC7OVzsEkqo7A\r\nlGgZ7pmpZtK3U6oTE/xxvhp/FioP6KbWtad5CLdNppBOjcdvjG53eZZhTaGm\r\nLXlt+8P8EBadinuLLQSzUB3/WpQ9Y1mdIiLR+pzt4U2jO97UUZ/wUwt/+TRu\r\n39AisUgZ2dqOeqza9UWGmZt5EjS6EPtARIw=\r\n=SYHU\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"1c4506c9d6e982e1f59463e39130fa165e5cd441","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-03-05T16:35:13.707Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.6","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.17","@balena/jellyfish-logger":"^5.0.3","@balena/jellyfish-metrics":"^2.0.42","@balena/jellyfish-environment":"^9.1.2"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.12","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_16.0.3_1646498237586_0.4237371347368126","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"16.0.3-renovate-external-non-major-1676b51a51c784add64ebacae9c4146f2b30582b":{"name":"@balena/jellyfish-core","version":"16.0.3-renovate-external-non-major-1676b51a51c784add64ebacae9c4146f2b30582b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@16.0.3-renovate-external-non-major-1676b51a51c784add64ebacae9c4146f2b30582b","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"620d88e38ad86ba6ef26a783ff2270f7622ae9ab","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-16.0.3-renovate-external-non-major-1676b51a51c784add64ebacae9c4146f2b30582b.tgz","fileCount":238,"integrity":"sha512-Ak0l30o7UpXZFnPQEVCyZDaY+Bw6m3KDVEXRj68rsxIM0Xu491oCPO7be+N8EAp5gifvdQIU5WwK1O3nHLvuHA==","signatures":[{"sig":"MEUCIANBaPxjP8l+thApkdH/zPlRT6Xr4havOYQ8BJGYl8ZwAiEAk+rw+l0CsRCEmC+Z5eFmiAjCSo8VcHBV8WU/EOePsF0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":841520,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiI5KBACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmqRjA//W3PVL0i8sED7nhrW0Z68GADHn16xULOMi5wDh3KvsKgfK/Xz\r\n1NcShIHQnEU7EYHGQmWz6EqJj2QAs6Y1/erKVUeKBtIAREBgAMoLRsEgSjbZ\r\nrDb2IMW/dTdDGvRvAQcSOQwckjyB++DYyWf/GhoUMoxJzxXk9RDnFBSKiOfB\r\n2LjKVMQ+4jNSJ83VFYLkmOPzLm0kD5pue1nUxuHrz2+CNYtd//tEqi09pO5e\r\n6208dUdWXTItJDaswk3yC/DVENlE8TmBpZWOUQb4nnQjWWQzInusVxe2c0Q/\r\n8NTcKO0R4AxxOt4n3ankDhXWvkuiLgKlRpuf9iUXQr45UmjWGbsVfIJWT9+Q\r\npnAuP1BNssnPnk7UhEhk6osTNjVdTsLuBj5GBZMf9/ZpjrN3TXX/acZurgjB\r\nL4OeiX8JSNC8SL9pcz18SJlv0X13xdzOP39h+6t27NlWngonO2nB3QE1L62B\r\nt6ScZ2+19+6BJym8/XojGtXUlh5GGlHOTrygP7mT+VicG+7DKE5wtXMR6A1V\r\nUObPhxRgt8Ez66t9B91R7L5wdzRVRYrHClpM5IZGqkmGkgIgublsqmT64ct/\r\n6zEdFviCghmWBiCXDssXM2FVuWghWg8obg7jQvBFG4Ay8Mcj4uxMqBUnuL8W\r\nqIzEJCb7pie3ySvlwpAOyTJJt443EhVfRP8=\r\n=1dwL\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"1676b51a51c784add64ebacae9c4146f2b30582b","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-03-05T16:38:05.614Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.6","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.17","@balena/jellyfish-logger":"^5.0.3","@balena/jellyfish-metrics":"^2.0.42","@balena/jellyfish-environment":"^9.1.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.12","@types/pg":"^8.6.4","typescript":"^4.6.2","@types/jest":"^27.4.1","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.5","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_16.0.3-renovate-external-non-major-1676b51a51c784add64ebacae9c4146f2b30582b_1646498433475_0.8903694264984516","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"16.0.4-renovate-external-non-major-bf826596bc71a510fa37c46e6a44647371d10f2a":{"name":"@balena/jellyfish-core","version":"16.0.4-renovate-external-non-major-bf826596bc71a510fa37c46e6a44647371d10f2a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@16.0.4-renovate-external-non-major-bf826596bc71a510fa37c46e6a44647371d10f2a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"e0de82e66641e619abd9c2846d275d1d82937e5b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-16.0.4-renovate-external-non-major-bf826596bc71a510fa37c46e6a44647371d10f2a.tgz","fileCount":238,"integrity":"sha512-MGrV2aQKeDbcIjygvzkGIyDQFI1YPUwPtRZ3jPbM/UZj6VgDl4Da8L+r4u+8L9PBoMF/+A+E9javfy/frIzmMg==","signatures":[{"sig":"MEUCIQC7I4Br685obzCvlWhHM6HlTdsiZkFx++xkITS2b22ACAIgTAIdROVz8mKqHnh2QSF4jGI0QxopysXXaE2bwk9hl6I=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":841545,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiI5NuACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2Vmrglw/9H9PX3d0RPcvYAErD10XM7OXv8Y2U+Zn8MT01kW1dG1aaRC14\r\nGc5qnNFnInynzqDXZZuNCY4sn0NDzfWkG6WFzk+NI/vHq9W8L2JmM1aJ7KhF\r\njkIQNr6+0IbbD8HM7MFQKaZ4Kp3lZpblrF2Nd7pUmcIBJkEsHD8eCuY+W40k\r\nG7Rf5t1mAapKCmWXXg5RXtR8h9QXYytuTBwjIIuEFYPLW9H2aqF6p6eiIF+A\r\nRnGajAqCVWyF5HV8YGhTGXmbNL17NcgzxmAB1SvdSOB0PjYZUBbWQt1au10U\r\n+IAT7WXclvlDSi2IPeguHed9cpLBPfyORotLiPYhiez8E3ZowzJhXTKamPMa\r\nqs48E6GvCEn57PahZK68sQD4sqmO0bwZkUHh9WPTSx/MW1E8WHLaz5IympCK\r\n+y3KvguDGkky7s0XN7gxDK3LX7NTDv75BGeEUhzdwf8w3VYT8R5KJMNP1hWS\r\n2ijqV+l9H9wy0fEhXWBU1k9gRrEKjCnKbXraU9nwIioVJvTOW4wo6q/8TXnR\r\nbXUo6t/v0aZfiCn4cY6gyFrKcl3eSO9YQwFkwcCaEssianHPaOZKGOXdy6k0\r\npCvKqRKlGljPG9cDLHeZLora24+aGsszxAjq2K1sER+etYAu4wcCMYrAExEm\r\nPcszIKW0rPhB/DHnuz6qg25P83vlNl63H6U=\r\n=qAxs\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"bf826596bc71a510fa37c46e6a44647371d10f2a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-03-05T16:41:35.803Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.6","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.17","@balena/jellyfish-logger":"^5.0.3","@balena/jellyfish-metrics":"^2.0.42","@balena/jellyfish-environment":"^9.1.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.12","@types/pg":"^8.6.4","typescript":"^4.6.2","@types/jest":"^27.4.1","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.5","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_16.0.4-renovate-external-non-major-bf826596bc71a510fa37c46e6a44647371d10f2a_1646498670690_0.5830083037518721","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"16.0.4-renovate-major-json-schema-org-2ba62ed7cc747b50e07290f10183efd34689eb90":{"name":"@balena/jellyfish-core","version":"16.0.4-renovate-major-json-schema-org-2ba62ed7cc747b50e07290f10183efd34689eb90","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@16.0.4-renovate-major-json-schema-org-2ba62ed7cc747b50e07290f10183efd34689eb90","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"2be99d8f182147a95797a80385dd550def5277bc","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-16.0.4-renovate-major-json-schema-org-2ba62ed7cc747b50e07290f10183efd34689eb90.tgz","fileCount":238,"integrity":"sha512-1BfvHnobJWnyJ1KkNh29vWL3prDScXpJF21pVVAwUEpJwPL6PWDsmC/0JVPbgbBbdDy9Aklu6zHsKerR4Ml9Zg==","signatures":[{"sig":"MEUCIQCuVTROEp5xSdHqfyt2RTrWUB2Iv9xTi/Ye43A0NgPDawIgDhj3is/wLQq7pm29k9Yd4xZHP5bLz9bCpgmEQJ/c4kc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":841565,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiI5OkACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmptfQ/+NGauX/qKRExo0+K9q5eHbQZxYcQqzCry6CxFiFhB0BSHLlLj\r\n+3jFQ/JxpGfGgOBzxo0EUAbabGe0Q0E6bIPMnozmDU5zUf4aS+Wxok5oEvOi\r\neYIpsjkvjumJc2GIvEbMMHYujGgUaE7ogKeaPCn5Swpjja2znffT5fgF9YO3\r\nqlP5FM76DFrvF2Rii6PIMI4TzqNLKnzMVUkPQ6mMpz854cBU6yC7S2y2D/3u\r\n/U+X4JZYqeLsR8/dOxinT4du3wlNZM/hGg4dXftfMBg2dzMouO7wESbIDuF5\r\nE6W60/FZBUUp771kt5WC5834DqEJyivlWChzJqRsHrEG7mJ00W+7RURuFa9j\r\n1jKbJXjSmD+YCkGVvwdLRWot2UXF+q5250BNPumLruygHyz8hbpWKmh4U/H1\r\nGXadSkKBO6+VHrcked5qwzRK0/Ic5l4CJQiP3fc6mfnPbkohJImbocZwGbtA\r\n3Utuw/tFaO6EyNIgaa5UVE3kkUxWoqDfbNrcGCq16NDEnBETlJl/T9P1ph5Q\r\nzhWijg55iZDH5yJ02+KSm9nryh9fWlkDAMRdvM2MOcwZPd9kakq3MJ/GNc3F\r\nCD7nE6fKk6NE6sA00oztFXD0HTE8cQ7zDHF/xoaeoRZwKG+KQt57fX2u9siu\r\nqRu/47fTTkgFH+QKpe5O2/f0EYGvn4pbDTM=\r\n=IvCk\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2ba62ed7cc747b50e07290f10183efd34689eb90","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-03-05T16:41:53.081Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.6","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.17","@balena/jellyfish-logger":"^5.0.3","@balena/jellyfish-metrics":"^2.0.42","@balena/jellyfish-environment":"^9.1.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.12","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.0","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.4","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_16.0.4-renovate-major-json-schema-org-2ba62ed7cc747b50e07290f10183efd34689eb90_1646498724008_0.12734777386964113","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"16.0.4-joshbwlng-bump-external-32045f2116f3a516fcf6290cc29241e76daadb21":{"name":"@balena/jellyfish-core","version":"16.0.4-joshbwlng-bump-external-32045f2116f3a516fcf6290cc29241e76daadb21","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@16.0.4-joshbwlng-bump-external-32045f2116f3a516fcf6290cc29241e76daadb21","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f025e9cb39759e8f22da0a7b902e944db8ae90a5","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-16.0.4-joshbwlng-bump-external-32045f2116f3a516fcf6290cc29241e76daadb21.tgz","fileCount":238,"integrity":"sha512-R74nB4AGe7q7/1bwXYTqxO55mKbdh3MCAx8iE0JXO6XPJDxWxH/i096ZXfmjizFA7s2oVnQ5B7tdMFXr3S2G7A==","signatures":[{"sig":"MEYCIQD3A3aNdjevALVjTi1eXtwEgbAftueYJHyvInk44TDIwAIhAJjT4p3I5DTCh8Hoog0G/TcsorFu14JF8VXCE40Dht/r","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":841538,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiJBptACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmrAOA//boWxzimTgRQF4cCDhf0fXLee04nl4N4U9RP8oNy//StB/rUo\r\nKB0S4RcZB716TV8m8Cd7ZyqiuP6hYx7UH3MnmBAMgZph0h3DCJQ4DQR8NMZk\r\n+wjDS+5BRBv/FHKxYRGptglb0qrLOb8DGTuT/b0b4N9EScDwhsrjQk1G2I0G\r\nbEFm0i00VBu/BokcrcpPJqLMnaGW2WsTW9q/JF/M8aX6IOJsehyEdz2CiPl7\r\nIL9EMPMYVKQVZTOZvRk80764eyGb61LmAHv80Aw3zqqV9O0Ss2AUpYRnRoy2\r\n5euOBXVGzbTVgjX0wZzJmz0pch1RKZgWzFJNqh3QbaqHTEbVQ54uFRyySUu/\r\nOEmvXuuR4KVNC/GWEHb2Vy0OkTv5E/XQyxNh94JnFCimAOwvL4D/GwWXeU63\r\n9Haw9ggI6VO91OLI1WNoNYpB3J4ylj58Twi83CqW9PdZv7BYIKM9u4feWFfv\r\nH1A5HnUnFA7dpd6CSb+UQ6YF2d3BFQeUDQo7BXf+3RxiJJVL7/zCcYntZ003\r\nsVl4rer41J0ulfeXx1xgbFtWPKmmr7yDUCgxKTyflZWstXU1gQomNYTqUcxU\r\nWvWM+13Wryv8oum+36iQ9/OFZhbQEvPVz0kXIJUyTF7Xn1Sr75LQ+ikIOIA3\r\nQYLZr8H+YN/ATJlODraIEnUWuqMt8Jbolfk=\r\n=63kX\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"32045f2116f3a516fcf6290cc29241e76daadb21","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-03-06T02:16:29.273Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.6","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.17","@balena/jellyfish-logger":"^5.0.3","@balena/jellyfish-metrics":"^2.0.42","@balena/jellyfish-environment":"^9.1.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.12","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.1","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.5","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_16.0.4-joshbwlng-bump-external-32045f2116f3a516fcf6290cc29241e76daadb21_1646533229418_0.41756948899854196","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"16.0.4":{"name":"@balena/jellyfish-core","version":"16.0.4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@16.0.4","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0f24501af38176f75874ce8403234c182be0cf4d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-16.0.4.tgz","fileCount":238,"integrity":"sha512-4+U6uIJ4of33rVcVIc+v6p+0UZTQRlUu4ZDLpWWpZTPq001JwF2WVJvyR79LKoUstsM99cgzsoRe4Sj0Ge53Mw==","signatures":[{"sig":"MEYCIQDmg2Kw/tMzM3qhj0BZkOs8KzBleKRx1h0ILqoS4bA+IAIhAJhNzTCAC+4xAKWqC/hG2VS9qAqRURjiQs0pZZ87m4bL","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":841473,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiJBtVACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2Vmokww/+NElu7LI0mrNUY3VwxiZwK4YpUcEFxfULMq7mIO4Qx7/aVs/q\r\naALjfQDj+DAuLEAsxptsmT4RWZBQV/vRdQpB2LNWM0hdgoMYzTpSut++pszB\r\nY+YxtlCsbX3uSJ5lxlWWGW18keLjqnmfYwSInapHv139GJDWmqSLm/FGX99R\r\nKYkz6ILbenNKX7cQi0xVcWKQWtKAUVj25CQ7ULRaC4vcmjEhIDA7TOQ5MZ2Z\r\nYO1DGoofEjQM5KRpASGA4TXEaAkF2Uv62nLhwdagAZp4o0P9ZvUuRWNRrxxl\r\nAtUdvof4CjuyYhwDxWK5Vj7YXlc+uoye7bmHDa1UaraQPXuiUrTu9B8odRqi\r\na424jfpee3uzke818H+nX/lBb/c0hn3Pa4P75ayrn6WWTvdtdWvS9+GP9+f0\r\nb694Wc/JKhPMobEMlC2cP5E2ZFPZbIH0iQmkynMBka7Iw5SOwyrguDjJcC/8\r\n6gPqARGfxxwIRmbGmcWr33X6y7x7S8VLjGHy5pZDY5JNZ6hzBj3vJwZ7JDwc\r\nMVYiELMb27vZuCKkJYZCVBHjlmRfQWuPEqAvwysYDlRLKePH2wcRPTy/0LBN\r\nyn/fUSVSE8HgjQAVv+dVLAWiuFpQYv0BJp3EmFoY9YSVZMzLMpdDnSs5tDmu\r\nXoPTh5IXJW4YcyvwHZLZJhr/aCLYOdpRKdo=\r\n=ERe2\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"11f9762713ed11932785dd1b6458b6f9d60b6eca","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-03-06T02:22:16.654Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.6","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.17","@balena/jellyfish-logger":"^5.0.3","@balena/jellyfish-metrics":"^2.0.42","@balena/jellyfish-environment":"^9.1.2"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.12","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.1","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.5","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_16.0.4_1646533460934_0.16462373352588644","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"16.0.5-renovate-major-external-major-ba45fcfb36e4a6dd83cf4f6e3327b3577e3e7a9c":{"name":"@balena/jellyfish-core","version":"16.0.5-renovate-major-external-major-ba45fcfb36e4a6dd83cf4f6e3327b3577e3e7a9c","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@16.0.5-renovate-major-external-major-ba45fcfb36e4a6dd83cf4f6e3327b3577e3e7a9c","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5fe862f70dc7d105a15e59f213226478fe608632","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-16.0.5-renovate-major-external-major-ba45fcfb36e4a6dd83cf4f6e3327b3577e3e7a9c.tgz","fileCount":238,"integrity":"sha512-vuUVaQEip8ZFWjRMXZcfssLPfYZT5DnzZkxX67Un1hdkVOwreq/Kdsvrgm+U1mKSeLRIdzXm3OQUutbLDj6s2g==","signatures":[{"sig":"MEYCIQCbXCQVyd6NqbK24xPmLtWhfG5/E4nyV2ABvBjJGMMCZAIhAJ9cxXFYGXLL5XJfGZ1KBuvuyrtEqgt5JzqCcrNEHZEm","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":841607,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiJByWACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmoOPQ/+KU/0xnqdkUFvLTavKZ+aBUr1qKIAoGyFazoEkSa5fZgg5bVk\r\n31FLIOhAX5jxFMJTyqHyXAo5xe9PCSCIcp0yNttnjM9pO1j+J/T5iz5vycqt\r\nA6SWLwLceTPiOrzCoj9yIBYdikMpr4F8afTYVLZ4KqPmUD0i7QlnnwaFRh6L\r\nh8SWu102MoW7ORHh6ObWWuG0HK658EIb5eaDKdtLm2wISKKeH3+UseYiIOHM\r\n33IzmmQWNe5uIQpxdu+ZHShBs1YUzPR52kErd5/T8kcDHLXEtFK5aNbe9Skn\r\ndHgV5oPGxNuZgJiEVwO94xKJNNCNk/ICQOTALnfBEm0DtJu5HOsjooLINv66\r\n9C45HgrA+lxBySoeqWlftQtPG2ZC0pgHmu8CZ0Rgf8p70U8f6Ii75116KSxh\r\nldH8bNkCa0etOlBjFeE2bANamRXWTalp4mMDxvJdJyvZfzgBuHPckrxYg++Z\r\nlWw9cBZ6hYxnQb80Ioa+gqud4/Tpslp4f22n7WavyIV9Sa4yS8SDueB4j6rj\r\nVZNcYhpdxbzWBEO6OPjtUGBdRgy0wi8bLRn5P+xjPFoupDDA+XPcMLIvJjXf\r\nodkwTPcCffo1RmNuRGCKt9UuEO6Q1XemB4sUMBp+xqxXzsPFM6SQxay1Q3fF\r\n5f/u34jzR/wyqWTZ0cP0PnOgoc6FC3RkUy0=\r\n=6UK1\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"ba45fcfb36e4a6dd83cf4f6e3327b3577e3e7a9c","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-03-06T02:25:48.083Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.6","bluebird":"^3.7.2","stopword":"^2.0.1","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^3.0.0","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.17","@balena/jellyfish-logger":"^5.0.3","@balena/jellyfish-metrics":"^2.0.42","@balena/jellyfish-environment":"^9.1.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.12","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.1","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.5","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_16.0.5-renovate-major-external-major-ba45fcfb36e4a6dd83cf4f6e3327b3577e3e7a9c_1646533782733_0.37422458772901623","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"16.0.5-renovate-typescript-4-x-c04cedc39baa995528b1e97f5c50d82e346ada87":{"name":"@balena/jellyfish-core","version":"16.0.5-renovate-typescript-4-x-c04cedc39baa995528b1e97f5c50d82e346ada87","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@16.0.5-renovate-typescript-4-x-c04cedc39baa995528b1e97f5c50d82e346ada87","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"29c99269aa7f5a45dac1be854a112b478d231344","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-16.0.5-renovate-typescript-4-x-c04cedc39baa995528b1e97f5c50d82e346ada87.tgz","fileCount":238,"integrity":"sha512-yp3ePkjRv8rG57wO4HUx6dqXUTOpC/RoklmcLV7VUDloT2KmkBBD2yVbc/zwM1vjEwTcQrSuwdyNCGHE5KEoIg==","signatures":[{"sig":"MEUCIFORrbOVkQGNday25mw+mYfKt35CQk+ogo7U/qrkSfsfAiEA3McraxgFztdmfXNMHRKoOO9SXhWDDXyfV+pUBxYgk0A=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":841618,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiJBzGACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmqrvA/9FzNG76QjcNeVa392COSeAUjFHz75VlzoaE//DgrXWsfywVsU\r\nblrP2JcXcKoUBTMzlKckfY0PXSfMfD3ymlzecbvL5TlE3033+9Hx9pIN+jUQ\r\n2he8F55ndkxm3xTw5q+US1Gkvpn9Ukp2hPojwhTbNFYoFpU7/wRHKx62h0tl\r\ngNFpv9zCKAVUXXn2kCtT5/FDnwk2QiW890wrqmMDas62pkWA2l2xgBUBV5O/\r\nD1tr2AQc9/nErQizQ4035vIIZEWDV1++ktF3VCwiTFF6/8uZURcCk9dXYsKX\r\naeIoWJoK7U2iiz3hBFrLlQF0BE2XDW7zt5yOpjlLe0L4KMUaRQh8r+iCLW+l\r\nxH8VyT6+I86SPYHGZh2eFIO4Pyx262ccfGGnduvivl/qN6YyDRFQHchUNqxm\r\nShnXQ7SpwfJOGt2lJ6saGB2mdH9vzCHk1+nfSAB5cQC9kcRvbFOEVbFwV1A7\r\ngHC4zfLaIbAgnpxBmyi5jIqKKclL/qLDB15NQ4+JUjlm9bJnOSneTMKjYAqZ\r\nCLL0srF62iVcm4YHEaZfTjCDzzbwcsBVXKjxkRTlPh3xhKNuClh7MbR98V2s\r\nnHtYuKhJeZOlthW/gIZCGgkrI5cmge2KxGwQ4Kh9CEexhAAflLWf0NtCc7+E\r\nQCoeJA1na3aN571ufALHOHPgUsXtIwgYsu4=\r\n=TsVa\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c04cedc39baa995528b1e97f5c50d82e346ada87","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-03-06T02:26:31.715Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.6","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.17","@balena/jellyfish-logger":"^5.0.3","@balena/jellyfish-metrics":"^2.0.42","@balena/jellyfish-environment":"^9.1.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.12","@types/pg":"^8.6.4","typescript":"^4.6.2","@types/jest":"^27.4.1","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.5","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_16.0.5-renovate-typescript-4-x-c04cedc39baa995528b1e97f5c50d82e346ada87_1646533830103_0.9276628066268222","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"16.0.5-renovate-internal-patch-75c1bf3e4ad26f40af537ceb1383b1fa26d0af92":{"name":"@balena/jellyfish-core","version":"16.0.5-renovate-internal-patch-75c1bf3e4ad26f40af537ceb1383b1fa26d0af92","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@16.0.5-renovate-internal-patch-75c1bf3e4ad26f40af537ceb1383b1fa26d0af92","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"163cdd4fe046e7b4f09e0e720fd57b53b8c9993f","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-16.0.5-renovate-internal-patch-75c1bf3e4ad26f40af537ceb1383b1fa26d0af92.tgz","fileCount":238,"integrity":"sha512-g2DQxttU7Yplz2JDP/dcYIj6d57AYN3phUG7OEARSJB0a8tUmRPdEatad4OYsX1k22GO/fV2DntHS4yhAZc9iw==","signatures":[{"sig":"MEYCIQDBJsWxR01rV2cwKGLlHuqPhV5ckfbkLfnqMbFTmk8Z1AIhALWBJ/1lsfOpx4UHvjy7p2aKjC9ri2kxP2EWwEGpPRzs","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":841602,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiJDjpACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmporQ/6Au23p98PCnlMEcUYv4ijl0UhProwMQz1hYpLw+5fBxY9ib7H\r\n301ozig3fzvXeDgzifXLh0Ni7fdXKWOximdbRuOYanekg0na5g2tw5DwOmEm\r\nO2DdPGQ+CHOWReHj5YJnqkLPsXhx7Rv05BHdf5YHKoTZQ+MNPPdGQ94o6AbY\r\nuhfEhNhAv+8kmWp5L+CXPOqeiQJbnhpq1eKPJNLysSFh543K92lN/kdxEupB\r\nus5sm1E1Ks990KPMBPosWX37G7I5iNvzEOtvDo1jDtl7QS3iEFFDGk5Byrh7\r\nLu4kYVsTn6p+Iz8sYqC915GWd6RQAtk9/Z23++BGFZjuIVn3mQtxeEypH+m9\r\nVJhSWB+EqAZiVnV30JRTflFMmuBcAgiszUOphERIXjxjaQWQBO0yeaqzbr/M\r\n8PciWM5TJ7Lz43VAPuUQeMxFimddiNEy1/Hf+7MYckZZbhjd2+kasCJjeGHO\r\nbPNwlJP974rE2GUBbOg6CvfoRFlurkSY0DX1UX60AR8xqgPx/UgNMy76pr0Y\r\nxdVRMVmECq+KgmrldVSmS4tvbcRDGKcBED5+zQX3Drsaa1AAHbyRNxoZM68J\r\nocwnOLXM0q1OLXmdwzKsUuEYBqKq03V7sbAHR4GXrM6BgJYrdA9hj4fj1l1j\r\nTmirqUtjZqbZQCSUppjGqB1k5Id5Q5KRCQg=\r\n=nFE0\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"75c1bf3e4ad26f40af537ceb1383b1fa26d0af92","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-03-06T04:26:14.701Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.6","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.17","@balena/jellyfish-logger":"^5.0.4","@balena/jellyfish-metrics":"^2.0.44","@balena/jellyfish-environment":"^9.1.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.12","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.1","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.5","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_16.0.5-renovate-internal-patch-75c1bf3e4ad26f40af537ceb1383b1fa26d0af92_1646541032600_0.8405515111170543","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"16.0.5":{"name":"@balena/jellyfish-core","version":"16.0.5","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@16.0.5","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"696b63ab64693fb3a0b7d88d784af5e63a20a062","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-16.0.5.tgz","fileCount":238,"integrity":"sha512-uHIgSW7RjE/fqak9T1GGisaCp7cPPItLuFdE6GhDn3vcQRKbh93S3sbk7jzomyAIsIMhkUDLdxukncZU7JjdQw==","signatures":[{"sig":"MEUCIDgUh2IS8tAI6M66rEIMtrkNaUBWxhwCItlJrPRsv7coAiEA6fB0MqTDKyNenfXpFJade9hG1X/Z3oF0L4DM5GdzeOI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":841537,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiJDnnACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmrkOw//SbOM/dRnm2CglSmVEfRf0BOexgtiyYfn7w8U7KvD5exyaNP1\r\noEJtPJb8O8NdWOAxRohKaYsnx+EpRxRkjQG8CylxeYwlWX7LiTApT4wydMaT\r\nB/km+nYuCTGA9lSL/WQ5RhWvOe11ei/UagO56GlQgxxO/xZ0ZTInyMomBBik\r\n8uXhRwTN9HrUNEqJ6aIf2GLP6YWVGW1FDeTbXaM3+yFtFTFldjm0+LQMP41H\r\nP2HwhAypqUZ5McZmJebj0FfRustOoV6Ld0ipx4vDf7FepRVf8AId06p8aii4\r\nVpcWynqGAul1bYg6ergRV196xONe9WTfBBUiDu1G2MU82phBYGbtYIVG2+Nl\r\ndEPV9rcemiYeyy5cWeyc5xU2LOekhx66hZbt7zbx3EZZJtgwaCzi8XNfEv9P\r\n1ilx8QSyyXLYKE/x6AVBOKAVS8PsVME0JFuZPi6nT2+JfSQVSY7C3MJCzSBA\r\nZm+cicOPTsikWliwe4+ounSFWGP5NAm2gFCVi5A9CW05UuGCY5JuEBAh7K9R\r\nYNpeRM7ujTmdMT8uBjr7ttY5N4+89CFkVGYAEjbNhIu9R99xz3dKPhp7TWKh\r\n5ttCL22FGPUyu6C/CNJ+MDp4EiodMHLGN5RY5JOPyGcDy0GFaAlG01NFMvTP\r\nKzbICIkq9xzDtrebQ0sOhxZzCvhfilC/hHY=\r\n=4eY+\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"16ab92825bd20bca0810402634a70d9afb2dbfd4","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-03-06T04:32:47.082Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.6","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.17","@balena/jellyfish-logger":"^5.0.4","@balena/jellyfish-metrics":"^2.0.44","@balena/jellyfish-environment":"^9.1.2"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.12","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.1","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.5","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_16.0.5_1646541287394_0.48755889603774705","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"16.0.6-renovate-typescript-4-x-48d74e2d38bceed25f5ef7ec6e06c4c287c08ebf":{"name":"@balena/jellyfish-core","version":"16.0.6-renovate-typescript-4-x-48d74e2d38bceed25f5ef7ec6e06c4c287c08ebf","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@16.0.6-renovate-typescript-4-x-48d74e2d38bceed25f5ef7ec6e06c4c287c08ebf","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"fff76fb8a8222378b495b284f73b98c09199691b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-16.0.6-renovate-typescript-4-x-48d74e2d38bceed25f5ef7ec6e06c4c287c08ebf.tgz","fileCount":238,"integrity":"sha512-aAYjDdECuax7YMDefjZUFeS4RdHv38PlMi2XmeWkFMTKNuwz37boua8JmO6JbmNbIH72XR7iQdfvN2FPv0EIkA==","signatures":[{"sig":"MEUCIFjOiPXUC/G5IWZXOftcSU9L9sNKlCkiPxLSJKep3DhxAiEA+5LjrzC7xYMhkFYXtqbpM4PLS/S4TmpqID4AUzMjyX8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":841682,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiJDp+ACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmpMyQ/+P7B6+VC6J31yKU6jGZy86YeQVS6n2XJJ10JPUF4vAPIpsFU2\r\n320H6j7ZcmAL0ogZ7yAS8C0tE+Pjqjo3g2HrXewFGjAB/pYU4O1ytLbK2UdB\r\nNtNuqdaafSGFWz+a0xQRmGoaPyePMhvgLk/nzZ+hl8TIpkBturavnGwEHMnU\r\nLP852yBGYBijr7fz8/PcP4DsPIn4AXgyEowJCOzvc7wXlDPBGuF4u8CVZc+g\r\nEM8ENJiREHBU7ah8zwJtB+XeIdpHuLdy7D4+U0raVi/t2wFnxDF/8ICVjSGu\r\nznQyimCIS3Mxfh1TRlz4oaE2IFny44eK0d48ZpC3ydBnEfgZw2cPQXec3pCF\r\neykYCozmcOgrSqa2z34yPT7o/8CZ4YowiuqvTMPuQSna0EUOytAEYbN0R8kX\r\n40xJfH5Kjxo9cckimLRbJvClJZtaIBd8kmkPdzaCHEth9XKsv4QyTWsQPIHC\r\nL08K3UdMbdP0pzcc0DmFhw8xcEutlBN/cZPQHlDKTfcy1WBG7OheZvVoHjEM\r\neho7LcAMOqa+cPeHqtCl9svsyxHshP8ZAwByEDgqfWwaIwG613Ts+FHRou0E\r\n2sOmC9X9WF/mIsRdH8Ao7bmrtS/H8lwQ35ENVNOf4/0OWBuVDBdCaIWVBz08\r\nL8yArTTzzp6fLBDceJFop4Pvto4PzPHMxAU=\r\n=tzKH\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"48d74e2d38bceed25f5ef7ec6e06c4c287c08ebf","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-03-06T04:34:25.687Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.6","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.17","@balena/jellyfish-logger":"^5.0.4","@balena/jellyfish-metrics":"^2.0.44","@balena/jellyfish-environment":"^9.1.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.12","@types/pg":"^8.6.4","typescript":"^4.6.2","@types/jest":"^27.4.1","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.5","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_16.0.6-renovate-typescript-4-x-48d74e2d38bceed25f5ef7ec6e06c4c287c08ebf_1646541438445_0.9039151691260432","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"16.0.6-renovate-major-json-schema-org-c6b1a8c76d1b9397523afc979019a6396c9635e1":{"name":"@balena/jellyfish-core","version":"16.0.6-renovate-major-json-schema-org-c6b1a8c76d1b9397523afc979019a6396c9635e1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@16.0.6-renovate-major-json-schema-org-c6b1a8c76d1b9397523afc979019a6396c9635e1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"a76f55d0fd80c13d1d39e6a4e0174d13b66768cf","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-16.0.6-renovate-major-json-schema-org-c6b1a8c76d1b9397523afc979019a6396c9635e1.tgz","fileCount":238,"integrity":"sha512-cpV/yTQCZGYGQAvYJpZTCiq0b2orEBaQXuikL+7CQmxgVfcWmc0ZRd8mnqDmWGC3CMk+f87YnFlt+GVqIr7UCQ==","signatures":[{"sig":"MEYCIQDVGCkGmTsHAhUNSjGrRTgUuPCTLZNEcPiMe7FFGUwYjQIhAOREJuHrl5jztwx1krgKQv7yZyMveZfmg+hE3yUcyyNr","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":841694,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiJDrcACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmpstA//d3qb/S6JEIWWGg1hAu53KYYcJwvnYrnAf4DyuTNjMyjYKarf\r\nK/petGc6VC90PSI26hfzlCaafqE3d5HBp1MiZdPeV2eqzTvQN7Me8fSb8um1\r\n3BtKy12L1yCeCON41OL5uAgd+rm8PzhEtQVwRM5LgThCbpqpnin65gSw64Rp\r\nuxd1fLSYbGkrtnsa8esUDHEJwgrq49/99FI7weXKXoDdBPD+ChaXmlYmGtxs\r\nhy4wZdv8wsUkN+YafYkeu6H5ObFS1ug8BtiomzKMvH3iDc4NcoWe13YiHEdV\r\nJ9MMPuc3Mjc8nxp1Wywu5QLi8B4ghjoeG/KJbKy74/yTD7y5WDUzOZVbZap8\r\nj/wgpBoyrl2PjC9qWS6szajNC3Y5LolcAlJd5WwF7NjCWnMVqJtZ6UCV2Cz0\r\nyvvHs8RinfNmc5Ofu3Lo+FbQWj3oK/28uBPXDFfA3BKKC5UvzZc6o8rFK4Vr\r\nEQjh1znOeEP2M5j35sr2n7lPTeTBdXV36r7/nDrBRvZEjS9Q3nu5HipEnT/Q\r\nPRzjGdRF3sr7R7NysnyB6f5mNMdoluR6tu6K4E7XEV3qXIs6ATDx7XLsaJbh\r\n1sJfjoN3gVB0fRT/QW5+j5SrYhJBWLomc6aKXSD6yjH9T67lilkrRq7Bwmbe\r\neqidf0csDkiMctyUHw6wlJwGly/baDIsdnc=\r\n=4qEg\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c6b1a8c76d1b9397523afc979019a6396c9635e1","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-03-06T04:35:36.452Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.6","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.17","@balena/jellyfish-logger":"^5.0.4","@balena/jellyfish-metrics":"^2.0.44","@balena/jellyfish-environment":"^9.1.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.12","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.1","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.5","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_16.0.6-renovate-major-json-schema-org-c6b1a8c76d1b9397523afc979019a6396c9635e1_1646541532583_0.8974447268990067","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"16.0.6-renovate-typedoc-0-x-cb3a201cb0831ea8538b29ef6d1dd3e1e79ad26c":{"name":"@balena/jellyfish-core","version":"16.0.6-renovate-typedoc-0-x-cb3a201cb0831ea8538b29ef6d1dd3e1e79ad26c","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@16.0.6-renovate-typedoc-0-x-cb3a201cb0831ea8538b29ef6d1dd3e1e79ad26c","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ab7808d1a1da89f37171dde7e331a57ad3897539","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-16.0.6-renovate-typedoc-0-x-cb3a201cb0831ea8538b29ef6d1dd3e1e79ad26c.tgz","fileCount":238,"integrity":"sha512-zKVyWOfbuZUeF7UsqjkJ73JnYkSM03KMpvocvo0yULtJhPSCJrDB9un8gEpLfR29sYNxPJKYy5Q99NHQkdrseQ==","signatures":[{"sig":"MEUCIQDd8wBTDWciWTv3DmHbNPx6jETizigILteF/EooDaF+lQIgUfLhhCZdVTu3lpVWKS1MwhHqrbaLUNAC4DQJrnqfcQE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":841680,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiJWz5ACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmorIw//eWiaXohUJvf/Zu63LGqFPhRm3JfCfdOVnvBA6A+0bGyykqew\r\nzbU5S4ejmIctG3aUOX4Zzb2VzikOUYMpDWQE98jvMihJZlwXTn/PrfDeEPLA\r\nAZLP72Svc4rc3eMQu86OOKx22JbbZBFq6TtCKDP3QUkklibMhdSJC3Ho+sLk\r\nFfgd2su4Q+U8RFsa3C0QnQ8HYRbS8Hk/PjnceftdiaF/cQEgJNYCqKFkcNZU\r\n1FDi+0c5dEalUhUCvUZblwK+88Nb4SxaB4g9A56JT3r/zimv8iYV7ADkCAnH\r\ng9rFnT6I9IJs96oCcwxYOZo0Jzhq+/xA9g+MK5ayHR4BYPflSNAXstyXzXaP\r\nPrPiQwGhxyb8GZ7D66ZcMT2BqAdIXJO/3ISztBTQv0UEpE+XZ3Ql0jRCj3aq\r\n4Ouo3Wb3DxiYu+2l+Nq/2BShomyEz6P5UYKfESKdQ/aHLE/wErkmaOTabDLG\r\nAAqv+t1DXqsbBvkkYahUovcgyOC0YNlX7kQl+Vs7gfgUjMoGNRlVfik3nOlG\r\n0W9DVqXF9G98szr1J6J/Zzqw/YFFd2Y5vs2kmqNNc0qC8e6xmv0uN8YzAUWx\r\nbZcJED72oQyz52yvOeh9A251AkumiWVFZ4hCd1PXhztHH2pjiweAkDwwuy0T\r\nJuub7BTk172mgH2X4qpLGwEJs8cKfHXm06w=\r\n=1iDi\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"cb3a201cb0831ea8538b29ef6d1dd3e1e79ad26c","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-03-07T02:21:08.826Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.6","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.17","@balena/jellyfish-logger":"^5.0.4","@balena/jellyfish-metrics":"^2.0.44","@balena/jellyfish-environment":"^9.1.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.13","@types/pg":"^8.6.4","typescript":"^4.5.5","@types/jest":"^27.4.1","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.5","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_16.0.6-renovate-typedoc-0-x-cb3a201cb0831ea8538b29ef6d1dd3e1e79ad26c_1646619897211_0.8127960900507079","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"16.0.6-renovate-typescript-4-x-b2d61dd76fd8f74c71f903f722c24814483cd15d":{"name":"@balena/jellyfish-core","version":"16.0.6-renovate-typescript-4-x-b2d61dd76fd8f74c71f903f722c24814483cd15d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@16.0.6-renovate-typescript-4-x-b2d61dd76fd8f74c71f903f722c24814483cd15d","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4f2e2cedbcf2ab5da75bc98a4bc136541927173a","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-16.0.6-renovate-typescript-4-x-b2d61dd76fd8f74c71f903f722c24814483cd15d.tgz","fileCount":238,"integrity":"sha512-mJ/mEep3fF3xRu0MbOQib83bGkp3YWHhvjHpxlBowo3tF28dneWLL2KaxjITuUkk9IN1091oXtI8PB2oXUwaKA==","signatures":[{"sig":"MEUCIQDNIfnd9eOBz6+xsWtaHzm6lHKzHucl9fyPUYrGsICxIAIgZXGNZhI8CqtKojffv1v7Wh/LY8jK2hFm4RzIY6eqHZk=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":841738,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiJXFdACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2Vmp6Ew//e1U5KdUEZf79qVFsQgZ+8ZDET37qbbAsPb5hNVbGhLcul+1u\r\npqHSfZ+L3I6mr/rj0wTZNCxwcQ+Z02Ygpra6baWUb+xySijTnJhOIlmOoy0h\r\nwNg9cge6gecfOVuSGrkrkqnIFhCbyPJKmUNbOHi3UwbK/qzW3WPrJEbYoQA2\r\nkp7ay7o4K8wetg4tE7TSUO9BMhHfVQ/AlO4ENClOsUVDmHbsXVXsZhvQi3sk\r\nRUeyWqaFfbVMBnUyVDLCOacjPcyXOq92L+MKka57zglcdTPMycQMQzjVdSOz\r\n/SRA7dsGFAJEaHDgDr9XivnR40cBh+e6xxMeqUTg9uLTnuLaDDbgguIztjFc\r\n2Zh3jICN9BEK0PE+jYFeNmqz1ys1r05h6hLyry2t8bQpsjGttFs1Ta7k2tF9\r\nUpzrQlS4h4OXMUiRnDKru6rGpvyun95dHRa9dje6tAM0tV+5QSfLM12Po4M9\r\nK1XqEQ9BuV/pXCqrqdPCRgxH+mL3hvp1/0N8oCEE4lqeRRiAvb3Zh5FqW7u0\r\nAQSoqePdhUATRX8NEWXgoKv038KbcEwxbqajfMoDrhDy7nE7c/b+a9GmWGbf\r\nw7HRjK4ytiwArcaQtnnrZdxbLVwcUL6xo0hAnPCgB5Gcnsl/+9nr8qXqCg0q\r\nclCRIagPgXYNhCZLLXpGKrQ/XjqctaQwIxg=\r\n=2Rvc\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b2d61dd76fd8f74c71f903f722c24814483cd15d","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-03-07T02:39:19.281Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.6","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.17","@balena/jellyfish-logger":"^5.0.4","@balena/jellyfish-metrics":"^2.0.44","@balena/jellyfish-environment":"^9.1.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.13","@types/pg":"^8.6.4","typescript":"^4.6.2","@types/jest":"^27.4.1","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.5","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_16.0.6-renovate-typescript-4-x-b2d61dd76fd8f74c71f903f722c24814483cd15d_1646621021692_0.8167391224109679","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"16.0.6":{"name":"@balena/jellyfish-core","version":"16.0.6","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@16.0.6","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9fc96b7d60dbf63b5d01d5582abe9db6c3dcf03b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-16.0.6.tgz","fileCount":238,"integrity":"sha512-Xxz18BwLSuQRfQ8RAP18jwtHbsBZzKQmmiWQRUKyzyVmHYaBf5a6v4m0M6+XA79yC9rjzg8vFFAPhBtAvXSb+A==","signatures":[{"sig":"MEUCICbj1Sp8eB9njRNOmiKnXLZVuLr3+IoHjSM4b6O2teo8AiEA4MknZ7clDAra0vDzKWIlP/212czWOFMs1uf6UR2+61g=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":841673,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiJXJ+ACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2Vmo1Gg//QBWNPyRhK0d8zmBW4u5wAnvU9PD+nryfpi6My7UiPKLKfx71\r\nP42t6K6deaOiAdw39QpNHdQ88+hMRACom7LmaI1A+FWj7vrDE7qTMFTAPDYV\r\nQ+od0HRme8RQ7RgixehOvWSIU9MBvszsVlxsrD+c8ev1RF3qf+sgvvDQysN0\r\nG+AWdUEHrN0BBe5LadmMu1DgpVQvcgGnLuhAJgw+Kp6pXACi2qq5+Y1j5l0h\r\nBafgwuLPOnvBIHJKbA8d6AQIdtkz6kvyxAwpxgJEI0XzX97YlyUvxu9SdQfW\r\nsL7ohjz8gUMJUED+gnfQb6SGZMgNzMsvAb5UbWOj/qQqqpki7+2FjVSa6zoi\r\noEYnhvMmc63QugASsrW2Na4/QNfaEleRlI9w5EyhQQZyQ9zKc3km1RL80CR3\r\naekS/r4L42BSXsWSb8cQ4JZNDJGswgzvxw12rY6HxmqSGLMdlArk8gQ2V2F1\r\nmB9+4LmT2OXByMct67gt9SpIbpIyfhG2Ncy/8zj9md+MSixHr3SKlAtkKpbW\r\ndUJ9LxBlMLG2ncnirstaWI8+qYaQSj4JNrlk2ZDl3ca4I6o+AzqEGQ8wPjh2\r\nXy/3ZbuMS4qw6VAzeWlSuNDqcxQZ05h0l21/lJcLKDWtYaLdaHWpkgHcwb3B\r\nhIkbaJs8GXzoiNY/XFZ3mbZPIBIvBF3kHp8=\r\n=kQ3k\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"054af15d675c56d4492d9aeee2da85c1ae53ad16","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-03-07T02:46:22.434Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.6","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.17","@balena/jellyfish-logger":"^5.0.4","@balena/jellyfish-metrics":"^2.0.44","@balena/jellyfish-environment":"^9.1.2"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.13","@types/pg":"^8.6.4","typescript":"^4.6.2","@types/jest":"^27.4.1","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.5","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_16.0.6_1646621310114_0.8170209412306435","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"16.0.7-renovate-major-json-schema-org-e4574cfb5ddf565c89ea6a1a850ff062f32e1f54":{"name":"@balena/jellyfish-core","version":"16.0.7-renovate-major-json-schema-org-e4574cfb5ddf565c89ea6a1a850ff062f32e1f54","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@16.0.7-renovate-major-json-schema-org-e4574cfb5ddf565c89ea6a1a850ff062f32e1f54","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"bd4df73324e8cc234937d42dc02c782f35f1ba83","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-16.0.7-renovate-major-json-schema-org-e4574cfb5ddf565c89ea6a1a850ff062f32e1f54.tgz","fileCount":238,"integrity":"sha512-CSUzuuYzF206z2kmQOZ9RGdiIaeWF+FoWt7Uv8J9uqk5YxFYgZOh6d2EEpo4tQlBrU/w0WQNEycdJTYXpv2viA==","signatures":[{"sig":"MEYCIQCHBGP/xEiTUMbihrne+dl5o2YJLgh/+Rf1Fxiq03v2IwIhAOPCnQ7/uKP/pOdTr1936DTtczogF6pvCw4Iz9AXta4h","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":841830,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiJXPjACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmoT7w/+LCxqywdfxdPPIC0wSp7GJ6gVXXqwALXKM5likhMawycg9Qzb\r\n75pOBLGsaws1/lyK2rHNZsb7e0l08KPYJ3atVlA5XmUrmMPKSMTdheczB0NJ\r\n9D4Yz7Sc7SQEjBnA2eg9VRWksvwjaaXJwQCReYS/LnJbCLsnvv/4QuaBpm8f\r\nSPEPtEWkbkAXnmfgyqi+oS6AMoVrUGjIWJGFxgQ+zBco/iqAv++A6GjGEnRO\r\nsInJTm8mXsRcCwYnbPN9iFeYXET06SmQZgWJILwkQEyUvY87eCnwyM1/mgSr\r\ndJK9CqezEVFywp/iYxvFdtE0lS0NNfgymM3bLxsGrulxHDYC86wYdMZaAEQa\r\nVxa/k1vVaxQSdtDCZv9YDqXRZMU5LGMKVYY7E6xPYbYySSS4PMIquu9Ylzur\r\nY5aHn/Gm7Ptf0VEOGGNaRRawfrCi1IdmDqKsXO34Sornt8HTT4DgJb13AtcY\r\n22jxr8S8lZij7EKG2Vg/0ljaiWXrbP40d+9KFa+QCVlP5aNZmQB3kmDosrdK\r\n/vo4fnCytunePV6+lEY+3MxnGhk94PYJsf+R3YP4pJj7vkMo2PLWGZe4gaZK\r\n2sReJiLFNSgdP3S66iiTPd6xfSpahhJucYxoXXcLw+85brG7ydk+ZLcsjdRX\r\nxFhd0dfwUMbLh66UavE44GYKDp6iFeEZrnM=\r\n=fKZa\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e4574cfb5ddf565c89ea6a1a850ff062f32e1f54","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-03-07T02:49:25.119Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.6","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.17","@balena/jellyfish-logger":"^5.0.4","@balena/jellyfish-metrics":"^2.0.44","@balena/jellyfish-environment":"^9.1.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.13","@types/pg":"^8.6.4","typescript":"^4.6.2","@types/jest":"^27.4.1","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.5","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_16.0.7-renovate-major-json-schema-org-e4574cfb5ddf565c89ea6a1a850ff062f32e1f54_1646621667584_0.9336077650380796","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"16.0.7-renovate-major-external-major-748402b58d390d8d402c2a7d632ddda526cb268d":{"name":"@balena/jellyfish-core","version":"16.0.7-renovate-major-external-major-748402b58d390d8d402c2a7d632ddda526cb268d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@16.0.7-renovate-major-external-major-748402b58d390d8d402c2a7d632ddda526cb268d","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"084e9a5f868d25eddb105db543d67626a2faeae4","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-16.0.7-renovate-major-external-major-748402b58d390d8d402c2a7d632ddda526cb268d.tgz","fileCount":238,"integrity":"sha512-BfQ20zfGdbwh0tfPDFFcxbqAxImtSaOPGIj98jOxPdjWmg6o1vo08Aj6RvTjK0Mr3d+gHOTJXdaIZMC9m3vzsg==","signatures":[{"sig":"MEUCIGr6mEkXhRpbtW3TUwUPIdCzRytUzOi8WnIZON6ZnwbzAiEAsgiWcny4h3XCLD8vLPyzmM5eohkZ73EUbPE6iAx1Gq4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":841807,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiJXTLACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmrQ5A//W6K/BtKeEl7SHbyvi4Y+ft5kxEkuA5PyH/qnu7iLNa0EPxop\r\nFHrY+7iKz70qVB9rxLsTXhN4ZvIg2nmZcCa+iq3SAB0hRquSwtawCtHsI+2g\r\nidXCHpLdFfw3O7rjh+cUSefz0Whg9gZPHYF352tpqaKzH7OznKbahkFdsocS\r\nWXR27Zi3TVHPCrBSsu2C1ZF+4vA+hL8cKaKZ9lxwoAjfrnaezTrBFJ7llBvk\r\n8ycEKPjoFRs31sGrUs6YxTQEFzf/k8GLiASczCL1gGS2y0FBkDGlF/wAKjth\r\n2IwViv+yjLlexnZqCcsjGArqrfALp6RJwTSybxy6kKUOI8Cx7xVBaKP6vbAr\r\nvF3uLNPe3HhLFzI0EptrzE5A6EEmG7MU8FZJhOV5CXz7bEfoXADpVe47wat8\r\nC1ZM43ozFIrPgR7jDyAAEFGYWT5coqRgzkWqazGkEtGnAhT5aB0cK8bjwvYw\r\n/dzyGFThRqUivUyERItGDnPdXDxbUWfGnmeiJlF2nk133R56JsYqnVpu918Y\r\nWTGKoe+rr4hTRsxFBsoDIpvxsBpCgV7DeSyD+NFkH4alnuKwwq85g6OrUA2U\r\nfuZcs3LugURkIVBdw6V5eWNqJdKbnRKuQRlCytlTZbGfzvt7sVCFwA2jvG5T\r\nAFFYxzQapRuZXBRVPaUcjR8bbfLR8F+AGOw=\r\n=GauX\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"748402b58d390d8d402c2a7d632ddda526cb268d","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-03-07T02:53:34.489Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.6","bluebird":"^3.7.2","stopword":"^2.0.1","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^3.0.0","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.17","@balena/jellyfish-logger":"^5.0.4","@balena/jellyfish-metrics":"^2.0.44","@balena/jellyfish-environment":"^9.1.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.13","@types/pg":"^8.6.4","typescript":"^4.6.2","@types/jest":"^27.4.1","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.5","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_16.0.7-renovate-major-external-major-748402b58d390d8d402c2a7d632ddda526cb268d_1646621899289_0.06598449847499555","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"16.0.7-renovate-internal-patch-6a60f8ee0d662cd1fa9cae03d901712c249d54f3":{"name":"@balena/jellyfish-core","version":"16.0.7-renovate-internal-patch-6a60f8ee0d662cd1fa9cae03d901712c249d54f3","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@16.0.7-renovate-internal-patch-6a60f8ee0d662cd1fa9cae03d901712c249d54f3","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"50593eb5f61a11cf3a21653d6a6eefe0e4f1f711","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-16.0.7-renovate-internal-patch-6a60f8ee0d662cd1fa9cae03d901712c249d54f3.tgz","fileCount":238,"integrity":"sha512-aqZ2iCgFTMSSea6kFpMlkY39yeIt9g+KxG61MT1N+kRdTvol4Pxmc4qYnbPOw5kdtlsV2hsmQv74myLFg4QarA==","signatures":[{"sig":"MEQCIHJSGhqwEGS379j2LnzV4g5RiaL2pSTNY5c01IsXkpenAiBbJevcMDL8ulsIqDi577WyE5sgSkCNc0QDJgBtQ76ZRg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":841802,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiJZ80ACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2Vmp7Fg//bvzbuCV+nP3D9qLWl/aBTkS+OwgOlrHmGKufhP7woYAD7Tz0\r\n69H/wEsCJql1G9Zx3aX8k/PCMNH/cZR6Y20rszUymoY0z7Ys2E/Q+ejIGDY8\r\nMQEv5oajaKpTX0d9jOuwHdkW3fgQ5Y8M4htwXMfwGoZ7lz/juWdl2ETdwO6+\r\nhSDEMuoSv0IXA9itLtG5CWPY5z/f6lEQ1JMK61ubrtSBQ/K2jYrJSZE7ZJfG\r\n5fvBJsAH8mdhg+UT7n62n0TQbJFNlEt2b0/aYwKmpn/NGeX1fONW86y6GQvC\r\nNOlDeydpE/3hcicL7rLJJPEOYsmuBMif4QSASsmRPCr0kWWyA7V2Jq2x7EMU\r\nWdQJsz1qM/ZGauB2OtYOGYjHBUPb0oNwxIM2Qb7GIJ0TwKOxQqp9cchrsxhy\r\nV2I2ES8Ebc7k7wd54CJcjVUimpPX15GWAwiR1uCcMiFInN9awdYuuhZupudy\r\nP2ak8THu1zLWYSUP0JlL6xtMiZfmnxtYkLrdiLA8z8YIkc1r3YFjY8yoHapJ\r\neItufhm8cCEeDTzy72TA4JXzh+8g4Snu8NtXoNGonq8E88Mhzm6BCaj7n6Fb\r\n32OSzF1Xvsv2Qz4iMJLuGj3UETWSdPkr1jtJPDbkCoW1GeH07MxYfvUTu6ou\r\njEMGUHSuQ6VnHQdeTl9Y64yk0xTIyAX71OM=\r\n=FiqB\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"6a60f8ee0d662cd1fa9cae03d901712c249d54f3","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-03-07T05:55:23.469Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.6","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.18","@balena/jellyfish-logger":"^5.0.5","@balena/jellyfish-metrics":"^2.0.47","@balena/jellyfish-environment":"^9.1.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.13","@types/pg":"^8.6.4","typescript":"^4.6.2","@types/jest":"^27.4.1","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.5","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_16.0.7-renovate-internal-patch-6a60f8ee0d662cd1fa9cae03d901712c249d54f3_1646632756523_0.9156887952408173","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"16.0.7":{"name":"@balena/jellyfish-core","version":"16.0.7","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@16.0.7","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"df7ddc5eadff1af5e65f4a72564facb194e01047","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-16.0.7.tgz","fileCount":238,"integrity":"sha512-z0NR1JjgRRFl49LKQP090LN49A/aTQC4A1MhgSwuPHQXnW8zadDojugvSzdleF0fMnyh0obbSL3OErTy/Gi4qA==","signatures":[{"sig":"MEYCIQCNKUkU10KcOfu1Cmkfx9ySZ+X1paEeSxyLjYSOocxcMwIhAJd87ycMfN8f9IpOb+1NWzDLzE81k4lwzpME7sLGTx+a","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":841737,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiJaAgACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmqKDg/9HG1kCt3jqphRfgfcl0HWhUkr29APD8l6RXGVTHcAQnmDrdOk\r\nqOjhpMAg9pd6+RNbswcqFt72LlDM4ZIwRVqiYvI/7AUtUUSo22P7rkRMI1fm\r\nogZY6fTMaF/dOF2bLv+B7AUGGFYAR2vORhRzg43ABOsj1rcGYk1pmEXCAgYU\r\n+nc5ka3YoWg2TZh+nA7dbQccCMprYIK+kylrbH0Gc1m9HlLUf0e2MIObFx5y\r\nUokutsQGZ8euh0vpJvFy7/ua/CDRW4YbehBYKNeQGZklGOZYWjtW3XoVIx3R\r\nag7xgv8HM81K5aT0QwY4GmOHrp+kv0ul8i71y0/VvtYartcCFyOROhrmYLj9\r\nKgF24mDi+fDhyz/mOSKpicwU9U01ZkO8Yj1lWa+T6LaFF670w83s3hZ41pjG\r\nTdDcs2PLAu+26aekpg53vQ73xyuS0eIIoKJoZmjaeqCJZ3g+hONJOWlhN9Bz\r\nYmYiTnDwy4V27ENSjnHz0lmqquXBvEscbVtl8aLW8siOdZCgE0sp5Uoi1hl/\r\nTFBKtVGtRwCQrHArwIrFqTuWZc3CQc3IueVogSgZ3Pnj+OzBlxWLA/mCqW2y\r\ntDRErHZK/FVeqm9VK29vP0aOOuTQS08FqTADdlcSaYidHXEI4xU64N6cMUAZ\r\nHOPVExYfMVvf2jcIplaWRGql0t8RKwBEIZE=\r\n=x9XR\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"591c52f73b820f68d224ed0b1038ec0e9b372034","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-03-07T06:01:01.796Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.6","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.18","@balena/jellyfish-logger":"^5.0.5","@balena/jellyfish-metrics":"^2.0.47","@balena/jellyfish-environment":"^9.1.3"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.13","@types/pg":"^8.6.4","typescript":"^4.6.2","@types/jest":"^27.4.1","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.5","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_16.0.7_1646632992172_0.39301567004800675","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"16.0.8-renovate-major-external-major-443ed851f25b9443c544ce7822b966789a234d01":{"name":"@balena/jellyfish-core","version":"16.0.8-renovate-major-external-major-443ed851f25b9443c544ce7822b966789a234d01","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@16.0.8-renovate-major-external-major-443ed851f25b9443c544ce7822b966789a234d01","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"cf3f0c09fe60bbfc46d540bb028fb2821d0df7b5","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-16.0.8-renovate-major-external-major-443ed851f25b9443c544ce7822b966789a234d01.tgz","fileCount":238,"integrity":"sha512-2DpG0Gg9MnA1yUwQ8sbEmjGmddGk7rtYeX1/yRbj/wpOqHRAF9I7XS6rpB4fqQ8PDhtErP4/2s5I2Cuo31gBjw==","signatures":[{"sig":"MEQCIFtHKSHOnkWtkTtIFGhTzrb+unfKwi5vMjy581VxYA3NAiAsTJ8CKjBbMeOnRYFvfETF3nY46sCS/lzPMV1n19U67g==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":841871,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiJaEHACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmrBYxAAg7XxAsVqVo25MMcPKZmy18DZpetShnfMpGiUcekSc7ZswCDf\r\nOZktAqEpUT54Ti4xYOreezfG9hUqevc1RXiVvkS5GGZ0D20jOPDTZo1mfffv\r\nhSEgPTXbCi6AqcAzwLqOUMWQ4QvyGFi2BHMm5ayeDOpjyJpKB5DQZtZ7OBF6\r\ngW5rB+glswHj0KusaBTRz0GwsE7oU00Kv/BbxpgdIhlh3Ras27zx9PZy6epZ\r\n3Be28zBK4aQfM7Qss4zXE8OJNoPrOYhCqGox/P3lqanEOKfC37+gQsIJpe/3\r\nBA9IKP7vg2lOpdT4rKajrYg2FRqEmA+BFaf3+PxGiuP2Yc2r/3nyowrum7xg\r\nfYGIXDShHqoSeFg5kB8qZCOcN9Js+pAtPknO0X5JDHEsJay55uAhVMCStXDN\r\neclreG0z3dEl/gbxN9W18NJmRfUgiZeAncnjJKnFbrF/NuRzpdgynOgQPF+M\r\nzOkN/xmDDqsygG/wxlietPhE/DFczzLgXOCLGRMFZ0nX0N+wEuksaGLO+Orw\r\nYIES+/lMtt6wd6ppWO9OxWGYUbpZQLei34/fTLmTchpMwvQ4+2TdERW9yjf4\r\nNy5+RwKwPqNsC9tjD9hCXegIiSuuiMDZ+Fml1AeHUPZY9O7tazJJQr4gw2VP\r\nGKG/wY+hJmrmYzv6+/qeatPYIPJ5yJaGdz4=\r\n=MUBt\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"443ed851f25b9443c544ce7822b966789a234d01","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-03-07T06:04:14.305Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.6","bluebird":"^3.7.2","stopword":"^2.0.1","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^3.0.0","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.18","@balena/jellyfish-logger":"^5.0.5","@balena/jellyfish-metrics":"^2.0.47","@balena/jellyfish-environment":"^9.1.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.13","@types/pg":"^8.6.4","typescript":"^4.6.2","@types/jest":"^27.4.1","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.5","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_16.0.8-renovate-major-external-major-443ed851f25b9443c544ce7822b966789a234d01_1646633223124_0.033766501705963625","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"16.0.8-lucianbuzzo-stop-using-events-33d1e545c34cbc0f099a106592f5a6f96d1e230b":{"name":"@balena/jellyfish-core","version":"16.0.8-lucianbuzzo-stop-using-events-33d1e545c34cbc0f099a106592f5a6f96d1e230b","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@16.0.8-lucianbuzzo-stop-using-events-33d1e545c34cbc0f099a106592f5a6f96d1e230b","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"4ca48adc48de31d0d190267fe1ccac648156a705","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-16.0.8-lucianbuzzo-stop-using-events-33d1e545c34cbc0f099a106592f5a6f96d1e230b.tgz","fileCount":238,"integrity":"sha512-Q/7PQg+VfY7xHyeUpLGdxunaFdOZ5wNiwa+bp0WpVtQoPheO3hJdD7x9iuw2vlYJA1SfH/2eNUwmaGNTvMc7jg==","signatures":[{"sig":"MEUCIBx40lu8cWJpe1xma6Os6JuljpjEO5932sKUyQ/MAwgdAiEAtwuBE5gDgz11HFVXqSevsChs0AxsrXnW8bXNbWA09dI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":841671,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiJiD4ACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmpnVBAAjBXEFEl+hK//lwxdK/9FRpcVcPzwpxcjPUQH+2bUbQ56M8Fc\r\nvjg7UOZnC+UWOcD+NN+wyitCH4KgXuFJVTQ10oXl2frqHeXRvROZFtuSq4xo\r\nwrIDjxgssk09Byvtz+Pa4lRg+JWDU1njRonccNILC8mw+SBAiRilL9pVOvM0\r\nT2ESd+KuFQGrZW5o90SjfxHOmlqNe1r4JK6chfK8AP6Iw7kDl61X2wDP76Hr\r\nKKnA9WfN/vQU5XPsN4KtnnFCatF9U/LTIdxpm68OBIVxboE7A9HP3kZjnxoi\r\nGEPbyUeUK0zBXXFl2bP6KCXRzpC0/kU9C4/ll7mgDp4LvKX95uEkCuiux1B+\r\nmDFY+kdNCOBCMrOu+EAGo2jNUudImqCXFwp4+P+U1fwJeytMMzEhr5B++pKy\r\ncN3D965bKLvyR95GSZcU4Xx+OE+ND88ZNX+QVFILZyTSPjGLeOyOpU+glwsw\r\nACxvWqo50U7NrSWAa9MT8k0SPWBkwYR4li7nb4LBvFVTVI3c9jgmnCAFhvP2\r\nwDXQQzTXY3SWl/Lrc31jaAonYgdmO0ep/BjQZjoSWV2aWnNrxale5/IUG299\r\nS/fHliDryLU/CeOvx97n84/QVJjO1Mfqkm7goUkRqswfdxsqkwCcbnckk9jW\r\nBs0DsxyFOYOyhP4FpFNe6Hk+JJAri6DQuGo=\r\n=qmR5\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"33d1e545c34cbc0f099a106592f5a6f96d1e230b","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-03-07T15:09:40.586Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.6","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.18","@balena/jellyfish-logger":"^5.0.5","@balena/jellyfish-metrics":"^2.0.47","@balena/jellyfish-environment":"^9.1.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.13","@types/pg":"^8.6.4","typescript":"^4.6.2","@types/jest":"^27.4.1","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.5","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_16.0.8-lucianbuzzo-stop-using-events-33d1e545c34cbc0f099a106592f5a6f96d1e230b_1646665975918_0.2404422089298457","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"16.0.8-lucianbuzzo-stop-using-events-93cf56665bf0369ae7aa7ef5a0928848090cb4fa":{"name":"@balena/jellyfish-core","version":"16.0.8-lucianbuzzo-stop-using-events-93cf56665bf0369ae7aa7ef5a0928848090cb4fa","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@16.0.8-lucianbuzzo-stop-using-events-93cf56665bf0369ae7aa7ef5a0928848090cb4fa","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"58e519fe425fbd611e312e5955589213b9907a41","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-16.0.8-lucianbuzzo-stop-using-events-93cf56665bf0369ae7aa7ef5a0928848090cb4fa.tgz","fileCount":238,"integrity":"sha512-bkrU9gooNbyyizwCVH/HjIouNBcTy/GhhV0YYeKITRPLN8sbWXKNq/USntTs0IGLA5WmmtywebamIaitdBfmeQ==","signatures":[{"sig":"MEQCICwLa2Rr9HT8/UTWSmDamfKI0QSr8VSWn057Jgy3xsnMAiBe5DmJjnW30CvEkgGjzHfqJImZNhDTVS/uPASBzTMNuQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":841688,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiJzg+ACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmqoXg//cdl02S8RDUEOJ0LPkec6J1KMyJrbzYSL+IyQDCnxN44aU0jp\r\nsD6OZ9CDihesfOCbkcJNUJwOYf5BnsXHq29FzbH1kDHUm12BxfKwTOeo8+Ld\r\nPea6i5WJ67ViuC8j7z16i+VH5Ol21cqiBgf3tJApSDt7KiQsYMRfbm5dF44J\r\nR2dzRq6qyXY2HEEnpIHfPb9Y5WmD4DbjlYprFzVbX7cSXdoYnxKRVVQv4OqU\r\nFs0Cg7f/909Q5lG2q/tcSVsmsibGbP3JfUpdLbUBY/pf5iOpD2hHWdThAx+4\r\n1T1p+3R/7wzAFTjYOMaSMejafyNBzgG1+ofrTaYixjInIdVlMMmSWoniiXUn\r\nS4zVo43ha2tjbzfcXsZxCobcHQja9IFRuB+zcrqIUlmIIyqSGsaJMnByp5dp\r\n3sGl6Qz1kaWpYD3od7ckgxCRfUXNHkYrQhmtDgiwNV235z+VqUpRg5kFhYhc\r\ncoY/9LygPeI5zlD06F2JYyUMaZXo/kpDmLK8OaiD6lHG/D95O2pPqwfYOrcM\r\ns2TOgPRt1K+nrcXIxxqm/Uba26lvgnafG6r9KVAfrNzfM8Aq3HKTv5YJO1Jz\r\nnTNX54woOac5/Uq/JwndR328IqkSM9nkYcSKe1+LLlvoQlumP1Trs5rGqM7O\r\ng9mOdfJmVuC/05Uv5qCOppUZmqkcW+GFkkM=\r\n=qRVn\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"93cf56665bf0369ae7aa7ef5a0928848090cb4fa","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-03-08T11:01:39.133Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.6","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.18","@balena/jellyfish-logger":"^5.0.5","@balena/jellyfish-metrics":"^2.0.47","@balena/jellyfish-environment":"^9.1.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.13","@types/pg":"^8.6.4","typescript":"^4.6.2","@types/jest":"^27.4.1","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.5","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_16.0.8-lucianbuzzo-stop-using-events-93cf56665bf0369ae7aa7ef5a0928848090cb4fa_1646737470455_0.776493830055768","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"16.0.8-lucianbuzzo-stop-using-events-adf28a130e8292d31269ff7bc63b6060efd6e782":{"name":"@balena/jellyfish-core","version":"16.0.8-lucianbuzzo-stop-using-events-adf28a130e8292d31269ff7bc63b6060efd6e782","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@16.0.8-lucianbuzzo-stop-using-events-adf28a130e8292d31269ff7bc63b6060efd6e782","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"ceeb1557de90612dde93511144b0b14a9b30d344","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-16.0.8-lucianbuzzo-stop-using-events-adf28a130e8292d31269ff7bc63b6060efd6e782.tgz","fileCount":238,"integrity":"sha512-yp9pohXM+Oysd8WAe/7XS+1G7sAhdyTQmVsr9cLaJd9vAga4Nh7EtUsnkt1zuQ2fOXlkYJasUFa6VlljRCGu8g==","signatures":[{"sig":"MEUCIQCZQkg+pQBOeYMqLEshN72bMxZOWi47nfH9ZMkQ7gHUZAIgav//eT5HBz9MIZqWwmk7hWpQUYL48Wart77IswrgEDE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":841689,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiJ0HlACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmojrA//Tx8uGxMEXKk1hY41DWD8qZnQzkMZAdfr9xh5g03++1WzkeqI\r\nKjqXH+sPgxUh8K5s6Svs0I0blkByb4ItL2ROHEwVx+/IizQc6aJPT2BR5QqY\r\nRylefA5NvaIWYCKqHtBp6qSWEZaRotrEiqqsITlHnTmuwUryZscoTfTEhDb+\r\nsG6NJ3XxMdq/Vdk9QHU7YAg2Y/iFEOptDgSzl7Vn2wI0qoWMkPBB6K37Y6r9\r\n847lzuw/yFBbhDKhIesfNB17+j4g1AWmZchQdm7c36Ln5hETVp2PbFviHjJK\r\nMFN6F0wfyPG+L0Ja8msqV3nspumhB+bFSs4nuWSVxeXWLLdo0GOsjKsqgDaj\r\n7CwL9P8WHmweL5A4bpWG23UoryXlrYw+klh8BVN4NpIAzscXf7I81d/NIwsV\r\nigosmJQoYhsFkarLGk8nCwO7PjA+DkbYc6jmNwViwdJTeMAmpJfwo92duviv\r\nWdgVFO+4KBnLh7nMysJehpb2V0esKcVz8ZcLM9JogfYPXC9pgyz+R0SevQ6j\r\nbdOCahMFVuGAheh/h88zRmdBq6FYX6Fed5KEd/ILvwtXZJuVa8a/a3KxesCk\r\nQhqPbFpxVhSYQmdvJCvt1If1IfKWadCJiJiUZhCJBHH2CzLcPz2i2YqEUDUd\r\n5wv58mS3QH+Lz/DZGFcrnbEmN6PyZoWMj88=\r\n=3iUr\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"adf28a130e8292d31269ff7bc63b6060efd6e782","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-03-08T11:42:34.471Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.6","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.18","@balena/jellyfish-logger":"^5.0.5","@balena/jellyfish-metrics":"^2.0.47","@balena/jellyfish-environment":"^9.1.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.13","@types/pg":"^8.6.4","typescript":"^4.6.2","@types/jest":"^27.4.1","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.5","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_16.0.8-lucianbuzzo-stop-using-events-adf28a130e8292d31269ff7bc63b6060efd6e782_1646739941781_0.35300967053495924","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"16.0.8":{"name":"@balena/jellyfish-core","version":"16.0.8","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@16.0.8","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"9694467828c3eb7fe1572e2291b3d8673e42e255","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-16.0.8.tgz","fileCount":238,"integrity":"sha512-rzacEEd0KQoA4vmDd4vb6E8UkqkuGOsrP+dZe4kJzaaanJkLtVqFWeW2j4s6ohwBaMoXxSarf7NpiMGdm2+rZw==","signatures":[{"sig":"MEYCIQDGtGyzhW23AgwecJR//E4Jv2jiNISYnOAXr08x0vOargIhAODZpZ8auH8iK4FavxXUAv4fpiQbYcxu36AsWy3MwD72","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":841618,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiJ0ynACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmoOiQ/+ORZw4a+HTXebMXBLS4rh1yhvhHUydVMyyw2bN+D+DDN+wN40\r\nLoM2yyhWOeOxxGC5RKI6Jaf4MEZ6JstOMApbg1eImkAOkyfRtqtLz6whOTLM\r\nYu6b90wmPyXFfCk0g5VahqtCUNGQxczO0Gh1AmIkHzzUdCq1SI/JTdfZ6GEZ\r\nHxiMVcaCTmD7LBZhNo4vtLzw4w3uS2CbJwTJPP/2j5f7l0/At98bAytv6P1z\r\nizAeoGH5LTO9Z9OWWEu3Wq+fgoZe1pKUZxi8x+xGOKkUOVwNUokLGORp4QA7\r\n04o7LMmuU7s2IOiVGHOUH82vY1Yep/FaGZASLRfzXo2zfu3duGs62xZOzzFx\r\nz+UEtSQhIUuiM5c6ywR3lZfs+OVgT4SXHgoVGbeZp23dMWXl3uGhmIm/n3A2\r\nla7o41SKwgiTO0r/cooJSWihNdypiWKdfsNjMzW/V+M+ePum+ddo5WfFOvNH\r\nFdDW9eW7GdTu4ioJk7u4Vn6RXWkTwV6r4803OWVFFd6DIx86T3cT3Z5Um3Wr\r\nIuufGOlmJlxET4klwLk6Z89REAjchGF8vuH9IGSSUMFBr/jWDiairQGdLWDN\r\niOFwXVlEXyBMfo9zDzVukPJWIG45jZGE79PhtzBvQ2DqX/LRDhmhzRs6D3Ca\r\n6ep7WHqbyHuFTZdrPFBCfwqEZh7HszgR2g4=\r\n=2XVq\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"3a52600837853c49016a45c7552bf3600f59d33f","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-03-08T12:29:29.003Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.6","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.18","@balena/jellyfish-logger":"^5.0.5","@balena/jellyfish-metrics":"^2.0.47","@balena/jellyfish-environment":"^9.1.3"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.13","@types/pg":"^8.6.4","typescript":"^4.6.2","@types/jest":"^27.4.1","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.5","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_16.0.8_1646742695249_0.07487190990194126","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"16.0.9-renovate-major-json-schema-org-d6a2b7fc2224a69efd39382e44643abad8747eac":{"name":"@balena/jellyfish-core","version":"16.0.9-renovate-major-json-schema-org-d6a2b7fc2224a69efd39382e44643abad8747eac","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@16.0.9-renovate-major-json-schema-org-d6a2b7fc2224a69efd39382e44643abad8747eac","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"3b2d096b20a7b872d06aaba46a372cd82498aa0b","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-16.0.9-renovate-major-json-schema-org-d6a2b7fc2224a69efd39382e44643abad8747eac.tgz","fileCount":238,"integrity":"sha512-upBRK3Uwz9cn9RrwLW/hUdwlKqikxlmHZettu4WbwAU9SmguCMPiHrD0EBkQYOVmNaIzavVmne9lWK/NKRD96A==","signatures":[{"sig":"MEUCIH1sNTC4LOEV1/Ados6ycStv7SD8hMBPNxPbOq10SewOAiEA44lGKG5JBE25pRrB6ddTxinwOUM2+vlf6qMaEBBrb6I=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":841775,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiJ04tACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmrYsA/9EzurtswEhzjRAXPJ2exhhVI8NGJDkatwl4oz42mD1EaOenEI\r\nbS+WfgiPaV5qAag/HIvZv0qCfM3fqOYeZd2Bs7LRcQg2CZB+PTrxZL/9nr+x\r\nG/s8NOxr2yeFssm1ZPC1L9MMrT/dSyelURKtNpeUOlW1J30sJrY+uuWCOXZs\r\njignkC8MgspPCGR5I+O2EigolquMW02P9fRjhFPDr7n+eZ3cRsWeX9JKFPUH\r\nXyyzVQ5jyvEsLZLp7YYwVydRtlOuF7iKu6uL4H2Ci47E3ypJ0GU+pz16baH+\r\ngdQtDkVxCHz8D/1iM1WMRXQbZsi2BAL9BKP9OTMcHJGfnygy2IntuY3C3Oej\r\nYaArZSs5bkypfPr7CO5KWOeai75B+j0BnakDla2ytz9KiiiWZ6MW6Dh8Zs0L\r\nfPJE0e/hfSTRp0XS1PHWwe3b1EAG+m3ZX7mmU6FUwCx4e9ZdrJ69jYchzfyd\r\nQnWyQHc4xne+YrXaizuB4b3IpFc8OGRLfAlpcj5VLVvTdN9/M/bIqJU/48q1\r\nqbXUhKMIxCt2MGRDvSp8nLryorp0y9KGgcdhVFy5UB/noo3h6e1nypFrq8Xf\r\nKdMxZW0gLTTX0+tweTDYic6IbvXZz7bKpVjM7TC1uqWD1OaevI1nkxOYeldT\r\n1viMgOPptbhJlhZXeUjN1QTf9v5u7Tb6o04=\r\n=xqMc\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"d6a2b7fc2224a69efd39382e44643abad8747eac","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-03-08T12:34:11.045Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.6","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.18","@balena/jellyfish-logger":"^5.0.5","@balena/jellyfish-metrics":"^2.0.47","@balena/jellyfish-environment":"^9.1.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.13","@types/pg":"^8.6.4","typescript":"^4.6.2","@types/jest":"^27.4.1","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.5","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_16.0.9-renovate-major-json-schema-org-d6a2b7fc2224a69efd39382e44643abad8747eac_1646743085729_0.9437367132677572","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"16.0.9-renovate-major-external-major-9818ddd10769b7f38e9f934bb3b6999cc1ad5f5f":{"name":"@balena/jellyfish-core","version":"16.0.9-renovate-major-external-major-9818ddd10769b7f38e9f934bb3b6999cc1ad5f5f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@16.0.9-renovate-major-external-major-9818ddd10769b7f38e9f934bb3b6999cc1ad5f5f","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"02f63510a38ac9f1c51fafb9263a91a400961e48","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-16.0.9-renovate-major-external-major-9818ddd10769b7f38e9f934bb3b6999cc1ad5f5f.tgz","fileCount":238,"integrity":"sha512-zYZEEaVHrjMesUJIzsxRzGVdV2ZxuAr/2VRYvob4F+d51BSDNhYxAO3FaIMhJWPE60VJIxE3geDJuBKy8ZheBA==","signatures":[{"sig":"MEUCIQDMprSJ/ViKRF+6J8Qn0AC8KnsLzo7cmJlzj0fDX53sbQIgV0aZwLw6QG3nKVyXEnHodKpNDTU3slq51h1FZqlq+R0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":841752,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiJ05AACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmpbBQ//b3c6adQxaJw7smjTisuLLtgiUtNBRokRxs+YT9/5HrYBC5jI\r\n3YiF1llKrdy5PeZn6qJlwjWut0SWDWSSxU7VxF4os67cO6ec/vteYftM4Zdo\r\nA/yg+yLOW+mzcUfQNSmTr2sWWDTNaoM0ZP3nbAhQsfTd22wdL2I/XeVQD/ZB\r\n0qp//vjVUKIIv+Q8BKFgvRIcDDon5lRGNvJ/UyT9Z5KMepLCSCWcjbWIz4cL\r\nd8s9p/yI8cjkeh9KFBc9Mmv3p/TNBVUpi2MwvtCwEXwKpy3nfnznslxaAbQV\r\nBIVl8d6LzmSotMeG2KgXoQ9PKkzqVgMRcxGxArd+HPwnAiHdUaA+AtavKBPq\r\nZo2m+HMHs8R9OJ3Y7aoffbiQwiM2VOAchAmMVVcDK3EYYC6CFEASR8IXrzYb\r\nWomLEgD1zzcIwiLSkXxGZRPVQgft0hT9Q7q90R/jtIrXjKqW0Qxv6fiwwJmf\r\nLcOq09Fp0cT739vN2CZfLK9PYqoqFhHEpWRqOdksqgSYPtJp0tQ9r87Qw/sN\r\nHq4ITcCS0K0gitYjujZFeeasA1EXT65upCEIVZVmzD4fXj+RKxMsF4W5vjzl\r\nHCT6F287C7DqU2vnrRKlXRw25mT7ri7ACOjVIYAWwDbVwBTW1ZF9D4AtE8oV\r\nqMf2F2cJvRXAJYJbmQQq5pOK3dsNgA9TWis=\r\n=+xVo\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"9818ddd10769b7f38e9f934bb3b6999cc1ad5f5f","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-03-08T12:34:41.957Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.6","bluebird":"^3.7.2","stopword":"^2.0.1","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^3.0.0","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.18","@balena/jellyfish-logger":"^5.0.5","@balena/jellyfish-metrics":"^2.0.47","@balena/jellyfish-environment":"^9.1.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.13","@types/pg":"^8.6.4","typescript":"^4.6.2","@types/jest":"^27.4.1","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.5","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_16.0.9-renovate-major-external-major-9818ddd10769b7f38e9f934bb3b6999cc1ad5f5f_1646743104369_0.8528127429731065","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"16.0.9-joshbwlng-fix-events-partial-2612206d68a09875af8be4adca73a88d5f22329a":{"name":"@balena/jellyfish-core","version":"16.0.9-joshbwlng-fix-events-partial-2612206d68a09875af8be4adca73a88d5f22329a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@16.0.9-joshbwlng-fix-events-partial-2612206d68a09875af8be4adca73a88d5f22329a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"69ecf2bafa0d0919c94778b0cbf13e690195797d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-16.0.9-joshbwlng-fix-events-partial-2612206d68a09875af8be4adca73a88d5f22329a.tgz","fileCount":238,"integrity":"sha512-AWjrWhaXpHpfUZ2KsDYp8pqCC3UAiI9ynm4LtWnxKSPC2ULbXE2Zp4/g8xaqzdO6wQtDOm5dPMzDHbaDOuxDBA==","signatures":[{"sig":"MEUCIBR4RuRwcMTW/VqPPMKz616fUM4TtTR+94LFKcvnL1wjAiEA79T8pu1qlInsgxpjQHooVIBv3vPEwQXIbUqzgDel0II=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":841779,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiKFFNACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2Vmra+w/+LQjFN2OYi6cwX/FrnVV4rn2iyxpT4PWRrpPxpCfZaKrgJsLh\r\nToT/zo3ucEmATkpWqywbW6SOJcXkjntesiPMS64fR8TpEpIQWRuKMKAA4MFl\r\nc90s1anXPSAFhK3bWEJY/6Kha1IeMiBv7TY/maWmHt5rShibSb3YIOnYDDC4\r\nQQMsFDJeUI/NKDyh2tIgL2Qr6w+YL6OXPLUOaNIRNz1L3W4PqAYLcNi7bLJw\r\nf2CRv0FtSznmfS/LB4Cl5G7fDQwuepTqMaqxSthbvZvdwPpXsBdoQ0Z4goQI\r\nxumHtR1veALrCldWGq/XrRppQfdK07yf7tPZDDC1M4KppnRDAetbj60Xa49d\r\nW681jM7sHOaCQOAqnm5lOw1zJ1nOpSxUJqDOS1UeeKO/klie/6znuHxDkH4P\r\n2fbcojzifxaQdm1frw5sCvQwjGXJkvKamthUBvF27e0b4OIim/6KRnsuod4I\r\n/WCHXuwyxM5booNhMAKlrtxw2hlGnGS+qI2JCo7E68xLGU2IJze0Rq1bzlFe\r\nk9AITSsLP3Cqcv8Owx9mA43bnKhHBKKJr/jroviyE/Wa2nqDaizFfNYA7anH\r\n9CxXLFWwOyBoq4FpDF4ZVeROfPnv5Esm/SFwDlM3n5pgsMSPngYkRx4LDiLn\r\nKeI5wGNlM8GNZwJcmkaT2nCP7TpjLJXGCew=\r\n=1V28\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"2612206d68a09875af8be4adca73a88d5f22329a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-03-09T07:00:58.069Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.6","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.18","@balena/jellyfish-logger":"^5.0.5","@balena/jellyfish-metrics":"^2.0.47","@balena/jellyfish-environment":"^9.1.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.13","@types/pg":"^8.6.4","typescript":"^4.6.2","@types/jest":"^27.4.1","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.5","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_16.0.9-joshbwlng-fix-events-partial-2612206d68a09875af8be4adca73a88d5f22329a_1646809421242_0.6722277862150916","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"16.0.9":{"name":"@balena/jellyfish-core","version":"16.0.9","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@16.0.9","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"18fc10c13eefdc3a9d5839c9c3426ccce0d78fad","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-16.0.9.tgz","fileCount":238,"integrity":"sha512-yG59xJSk5V5hFSzTufxP+1JshdMdW4CMaVQ+YgxWntsIj54AWu2JTCMNwNaQ4mdDts5tqDC/vRGjWsg5CxGKdw==","signatures":[{"sig":"MEYCIQCIx+aFts8vuYAofoa7VMp7utfYMt6oN6STSEfAID082AIhAPdvRCmDW5WZ9D9kcJE6UpdJV53flHFgK1pA2CGMQs2O","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":841709,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiKFoyACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmqJug/8DtpdvJwUI6C1VfTF5OS3GFL9JE2pUI+jLIC8B7cwBqndEtvB\r\nyYhRUEfWjP27RfkTWyUu6lISaYfKkim44gUM6Yp3eYWf8GKasI7XPUXKdmr3\r\noHZpR8WRzzVJF+/4/JeNhBFg7xXAZOe/lKZQgfM8sF7o2UUnJl8c3WAoJrNl\r\n96XtJv3Td6jsGXRdE2rScQH6q1pb+ThL+DIkFGWusca+V9PKPnbXcR6M9r3y\r\nJnzR8/KkRfjEaolb08FAI1CoD44NFEZHIgEspFHVHrO9EoYQNGeIqAQf8vT3\r\nrcdbRWBBHmWWzt2KsqjlKidimsCcS3j+OJ0NjakikkyveAKpHxQCPwVErxNM\r\nHuAlfBN9KXqmRh+0EYs9yzvixX0gRuokCDMg9ZxrFB9/qcZozMMLqZW4El0C\r\nGqXYUhrO4nW+wdGfkYSMKgrIkx/vjXfd1EaRys7HTpHvLWSxa31rSHz/2MVg\r\n9pTdGLGA7LtRGMu6cUOMpyEHq3NaCefNXGXXyppbIW/N/T2ffKCM1qBsuUnM\r\nekNxMEklpJGvcI9IUxN2nOWrCYcRyTDQbgAlfqaROyZ2lpQv22gtBYVccFld\r\nSPnHradQ75ZojEXzW5ylNrXK8SnRXySwr2uYZjoI7MZc7sR7kE8CQ3pmkryP\r\n8y7SMpVzPeh6stk0TTO05sCjrP/jwZFs3YI=\r\n=9jZx\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"9c10a91670faebde9d9304d49acbc9fff2a2e448","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-03-09T07:39:42.436Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.6","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.18","@balena/jellyfish-logger":"^5.0.5","@balena/jellyfish-metrics":"^2.0.47","@balena/jellyfish-environment":"^9.1.3"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.13","@types/pg":"^8.6.4","typescript":"^4.6.2","@types/jest":"^27.4.1","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.5","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_16.0.9_1646811698234_0.543655997801628","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"16.0.10-renovate-major-json-schema-org-b049e6c8839b178c374ebe76425d0385606b3a99":{"name":"@balena/jellyfish-core","version":"16.0.10-renovate-major-json-schema-org-b049e6c8839b178c374ebe76425d0385606b3a99","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@16.0.10-renovate-major-json-schema-org-b049e6c8839b178c374ebe76425d0385606b3a99","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"b0b491e8daab35507d0ec10a46050bc5b93b9adf","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-16.0.10-renovate-major-json-schema-org-b049e6c8839b178c374ebe76425d0385606b3a99.tgz","fileCount":238,"integrity":"sha512-OlbQh5gQTACc6xNG1kf9rLKQo4tYtQ6GTZm7srmLumrYdN13rD4pxuFFadOYoFPF5zaK+wmQnelkCa/VA2IoRw==","signatures":[{"sig":"MEUCIQDzBWhqj82Bx6Y0pEiI+/s4fc1o0dDtKh8x+KcC3N0S3wIgU/kAzFSK+/RHyrKCcNlvDaZAKSNwDhhF5hUMdvsOEA0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":841868,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiKFuKACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmomPQ/+O9cm+mFgwsqFfKUA0WHosAREX5TdZI3UKeFY/rSztW5holkA\r\nH4EXR/6D78Zys6hqjqSLVfGbuatvuq5W0skHI+Pgn9RGHP+pxz60H1JfgBCK\r\n70hj3sUXT7b0jlSnTml3ZjTRt73TTNNxhwQnrumSZmZKJO7QYcUD/isSXQoJ\r\nnpkulgvFrjeWCOvvjUIQxXLWdRZoCSu5ZuZXzDnmMSmMEYRgsHnNkyLMysgo\r\nfTNx7xKPn/pGqqGUaGbI00/GQbAs1vMQ3EmaPvvqtRJNQVP3qvmdDmtv76y3\r\npfcdP82xzyFkE+d6SqsshU29baO3I91vJPsb17Rh5AZZTmGI00Lut+bS5Grx\r\nUHKly6OogfcLbCniZJYRvqhhKwlXQCG4ixDYiXlIzwLsh0e3tCbVaYI8cQ9q\r\nlGuLl1HODcJ0WLiRFQto5VZdSF8uwQ3klicZuasP10HK6FgTiFzWJhLDNMeR\r\nLrckeeybMuLK/5WTJY40MFD44YRbm6F+iukqGqax7drhhIqP3dTW3p4VVu3L\r\nuuYdK6qQdrMNyu1l5Et5UbPyM5l3WYGn1kNnss+nnb48U6DLiIC0hkfZmp15\r\nUdijR4zNWNkKNWFQPQ2LRQN5Iosd/3b8fv5X3X2woSuX2LVDKmnZH2mDuGY1\r\n7FRqM5HrTr15wBUhc3QKRNp1KE0dFdsSYDQ=\r\n=Y/zL\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"b049e6c8839b178c374ebe76425d0385606b3a99","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-03-09T07:44:44.004Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.6","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.18","@balena/jellyfish-logger":"^5.0.5","@balena/jellyfish-metrics":"^2.0.47","@balena/jellyfish-environment":"^9.1.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.13","@types/pg":"^8.6.4","typescript":"^4.6.2","@types/jest":"^27.4.1","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.5","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_16.0.10-renovate-major-json-schema-org-b049e6c8839b178c374ebe76425d0385606b3a99_1646812042207_0.4441402824525513","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"16.0.10-lucianbuzzo-unit-test-formulas-a30e09e3a502d37251e9f146baa326ce00212f6c":{"name":"@balena/jellyfish-core","version":"16.0.10-lucianbuzzo-unit-test-formulas-a30e09e3a502d37251e9f146baa326ce00212f6c","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@16.0.10-lucianbuzzo-unit-test-formulas-a30e09e3a502d37251e9f146baa326ce00212f6c","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"fb5efcfc20be3995b72a3cbdc4f0f23c7d52f6cb","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-16.0.10-lucianbuzzo-unit-test-formulas-a30e09e3a502d37251e9f146baa326ce00212f6c.tgz","fileCount":238,"integrity":"sha512-he0GtmBvbbfdk8VWLeRimUclwdDkashy0sJ7k7f5zjUDMqF5sKDPnYnSoKwA3rZehIaxkZHDfafD0sSczwFXQA==","signatures":[{"sig":"MEQCIBvw2FVTNj266qxe+Berx1ZhEXwzKbRtUKnEiDpfLsaqAiAFNUlPL2BrAlQs+kqd3kpFtv3qFBlCFP8Cm5rZjTticw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":841991,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiKIh5ACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2Vmqjtw/5ATOyHEdv3j8uG/L8/wUqly9uk+Rr+r1hnJAzDLycAdpzi2Fp\r\nGSvLVpVq6bcAvOGiWm7q1axw2qDyiLT3L5G+trsD9JpBLMNoZrVhuPvDc9Hj\r\nkexZDoz39/ehGTgq6y0jBeXjOLUxJM/8GfqNcIkE/bQTOMnfFI6hF/8MYTx2\r\nCLWnY8unAu0AiDLCEEXJCN+rJeWZzG7IFD8BvLP0kBKBzx6oFM4zkr8AcAbG\r\nKpkMMFOlGu2DwWm0iFvZp25s/woF9V2HVJ0iGPsu9v6tNWrBLY4nystCape0\r\nMdHNLbTmQi7imcaoTSebBRx1Tww6D1BvIOBD1OVNJ8Hrpq0zc1jrEPSRqZLM\r\nyPnm6alF/YBAORbSJFuno3SMJhZzPc102QiucNGFUPrIndBeU0Rgd3rV1xNm\r\nhZ+N0MReH4ZSVNyMd/kNSc2nnSZPrmje6TjJJZeShfO+VxqiQMccndojj0Qf\r\nbYnPuZR6NOZhxhaC1ihLzOC95obQqzRqiqttAHfVVGzZoKNyF0KVVh2ZM3p5\r\nAToMr5VFQhSDaT+MKhr/RgF2S87RWvdn1cAYyu5T0jJPAKGxxf6D2ktYcVhX\r\neoJN4Xfcl9s+2JPeGTJdI6zuqDTW7NV98RbTnMjNTUskdxGDMWb+7ZD6q2L+\r\nh+E7qqKbWmKLDrs1dFcTQKXDFwQqraumGAs=\r\n=hTBM\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"a30e09e3a502d37251e9f146baa326ce00212f6c","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-03-09T10:56:28.298Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.6","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.18","@balena/jellyfish-logger":"^5.0.5","@balena/jellyfish-metrics":"^2.0.47","@balena/jellyfish-environment":"^9.1.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.13","@types/pg":"^8.6.4","typescript":"^4.6.2","@types/jest":"^27.4.1","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.5","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2","@balena/jellyfish-jellyscript":"^5.1.51"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_16.0.10-lucianbuzzo-unit-test-formulas-a30e09e3a502d37251e9f146baa326ce00212f6c_1646823545557_0.8102144652288701","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"16.0.10":{"name":"@balena/jellyfish-core","version":"16.0.10","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@16.0.10","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"15fc04cb2d8663e01859d57961b84e7f9736e62d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-16.0.10.tgz","fileCount":238,"integrity":"sha512-Z8F/wjgpbRI2/+T/JKQYdQXxRq+9a1Td9299nscASfPNYZfiufybt5oAvsSz9fOVkrLWANUv6Hc7Ez/ejeULjQ==","signatures":[{"sig":"MEQCIHHJK1ecom8rrYZZpZK9kvo9eSicQxB67rzVAtt1pv7NAiAs/E7mwgiCz2PJ9B/bsoAxXfsYjwv+QovzkimUKpT4Ew==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":841919,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiKIubACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2Vmq6lw//bJgbZm6K0AFc+nuUaUrvk1pVxB9XcSKHazIZ/TVtidpgWA8H\r\nkfHpfsqbKmtTyVnfukwiXb7g35DN/VOyzFpHBThWPoPx2yK1//fVNylc9iNy\r\nkXvciX0TCcP49VV4TQPgAPYFUWN0AX3HDHZNPzYcnhIoW8MSQ7aym0cQuU79\r\njgM+PZAg+CEkNCClRrXOKwnYuBHqA30QAL0i07Sjf3GEG00wNrIAFTjk7oh3\r\nwbTinadngjDX9C0IszFUiOdqxqArjYfqKoi7PaHRkzOpvG+fRFJVJ0gpl4E/\r\n6o99CRJBIslqWXfqpIBDH8Ivc/BTSFX9IbdeHuFAAdr1d3WMWSinw0OEc8GR\r\nmM4Uj2EVaEc/kJvMZOJ/HR5QtP2zR2u3loEDKxRbYPgNt9WpmsOpSmP1kB4O\r\nQaex+l4Lvuxp15fzMXY1g10/FZTsdTbKrRSTeVO4BBX7s3pYSRwsYr4gU9qV\r\nAr4NoQ4LOdaHAiHgZ5C5LtjHkNotO7MrKTRpV30WSZEqqRxxVP94kpsj6FzX\r\n4wzcCVWUYh8JVBAQgMABYgiCCX7ZYjwT3c8WlrenwV1Eu7Bfw7moteQ5Ubxz\r\ng0y61lifq4+dPSUu9MhJrRE9dNNikdcmFZXkbuOa/xuRQ08mua5ppqIhn7+u\r\nKXqmIq3ziI3hzaufXfpuZEI40glWLRIpJ6Y=\r\n=CE09\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8615ca561eb360ba62571c42a7fe128ba1088c85","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-03-09T11:10:30.586Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.6","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.18","@balena/jellyfish-logger":"^5.0.5","@balena/jellyfish-metrics":"^2.0.47","@balena/jellyfish-environment":"^9.1.3"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.13","@types/pg":"^8.6.4","typescript":"^4.6.2","@types/jest":"^27.4.1","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.5","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2","@balena/jellyfish-jellyscript":"^5.1.51"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_16.0.10_1646824347309_0.5167639533637995","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"16.0.11-renovate-major-json-schema-org-365a8bf7810af55a242aadf856d11d56b9c1fa4c":{"name":"@balena/jellyfish-core","version":"16.0.11-renovate-major-json-schema-org-365a8bf7810af55a242aadf856d11d56b9c1fa4c","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@16.0.11-renovate-major-json-schema-org-365a8bf7810af55a242aadf856d11d56b9c1fa4c","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"f17b45045cf054832819842a84b869b0fd0482f4","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-16.0.11-renovate-major-json-schema-org-365a8bf7810af55a242aadf856d11d56b9c1fa4c.tgz","fileCount":238,"integrity":"sha512-bP9HNbjMcJrPX9C8oyDufIlrwdAv/GfLfvwjAsvjE+Q/E2OINKEWdLbp4uacled1O1jD3MiHOUUPkotywH12kQ==","signatures":[{"sig":"MEQCIGRp0WhJ2GSxOsJ0JIA94jTMh91FZd/J9KsWH8I+kysPAiAf8v0MN3qCvQFlJdG5Gk2+irWWGOjucJpfKD9uGiZxKQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":842077,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiKIxmACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmrpIxAAhKUxvTlE5LIiUzMK2LPpvbTsEb7p0qccnnxmhHbG16JqptB9\r\nZrmhjd16iaDRjIuFR8JsRrvEuRlkHJnNSoGNwLBlNV7k82eB5kXhOg6qLrFB\r\nD+mjhtZRdeA5gFp9GsA7GoiSLwqHc2QBXh16RLvCpjtALT3nmhm8dnqtC1zD\r\nDVf/OM8gWHIWQ3jejh8v12gGMGWAepDKPuTEzMvhqCWuQZbxIkeMGjd2eqrs\r\nvTa4Vms4wpRGSMorFEDWHlSDdDjo6WqJRuaeVm+qtzhyOeFpX/+df+Zr2Cpd\r\n8VWsPx+L7b4b2E19lvp2pwhKZr/jX2fCbpcH4rSt9BLqfMwh8Bb9pUd8dQSU\r\ndxJ00pByISqCQchcsKG1X2M9G+sXjgCRVneipkiGVSl9jz1HhOIXPZ89vW+a\r\nDz8hGFSvaCRpG3Yi2DqA90Gd50fS/4hBxGveAYXClB2Z4H2Kv99ZmIGMyRaQ\r\nzdi7f1+h/HROR5bTsY3njY8RBDfNFvnusVBP+BraNnRH75D1idECBLws4Cz2\r\nZXEc96rWpuOmikaU2f83xPgVqSUoTq4xf4vpKbCUTReUNZlmaTb2IgzvWILb\r\n1i1JHgZ9yVamOEwQrV4J0GXCxUI7r6YxlxkPZhkplvpHs4CUjZFpG5p9BhiA\r\nAmm6DA+yxrHgmsnjINbjAwQptot9R8uN1hg=\r\n=Exum\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"365a8bf7810af55a242aadf856d11d56b9c1fa4c","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-03-09T11:12:51.384Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.6","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.18","@balena/jellyfish-logger":"^5.0.5","@balena/jellyfish-metrics":"^2.0.47","@balena/jellyfish-environment":"^9.1.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.13","@types/pg":"^8.6.4","typescript":"^4.6.2","@types/jest":"^27.4.1","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.5","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2","@balena/jellyfish-jellyscript":"^5.1.51"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_16.0.11-renovate-major-json-schema-org-365a8bf7810af55a242aadf856d11d56b9c1fa4c_1646824549843_0.1435572791397819","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"16.0.11-renovate-major-external-major-8794e1157d1f54fd2567832193914822a6bbeed2":{"name":"@balena/jellyfish-core","version":"16.0.11-renovate-major-external-major-8794e1157d1f54fd2567832193914822a6bbeed2","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@16.0.11-renovate-major-external-major-8794e1157d1f54fd2567832193914822a6bbeed2","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"63343581622bb7e85dc32c15022ed4608848eeb1","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-16.0.11-renovate-major-external-major-8794e1157d1f54fd2567832193914822a6bbeed2.tgz","fileCount":238,"integrity":"sha512-dKChxpnSKDqOnN6F/49GT9Hg4i9/IjHsUQ/MPD4r1tKKp/7KJiynr9nmUbeQ+KnVmAZ+DNu5BbONaIrrlZ2LIg==","signatures":[{"sig":"MEYCIQCCWEc5wiLhDuo7ONfmYYrqJR9NsIYiculx6zdPaPa4hQIhAMMftqcCa4TembpCNjO+sC4qhjJWcPY1PTEYJtgMRw0n","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":842054,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiKIyNACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmrcwBAAnjs+gWGGstnUitOj4pJUFkYEKYHi7fBtPc4TviPfiZHEo3gg\r\nO4A8PtHejdCTPQgBjg7oUDI1tOKBhl+DTiczwa8msojKfTKsK2CfKhF3W71S\r\n8+LNjaNGirxxiNRAkfBO3xmRkOSf2Mpv1iRcA078gR1a6KkVyNdRyNclfrUi\r\nMkjm1MvVudcpby78heQ00MsIhde2GNMDLxWeQTsndTbJ61GE0Z79c840qq/4\r\nNu/ojL0qdCVnM+zSPMtw3Kz9thipbLQ61M2n2wKE8UBvmUSOZZVVwyh6xD9+\r\nyHwFeccXaNwIkWVXNhv1Lhl79QWyBaNhr2rqrHjkmZRYb6SqTHk3lpFdn6t3\r\nc65bHTUuefHz+aUfPscDrd2JtOctv2ersp1/Rs9h/R/9jnwEzHAhpaRAP+SC\r\nF88b26rFEvpuxmu6+8e4imYf+z5ysyWSCL3wIkSaStnLoeDNLPsMgpexTzv1\r\nqcHNgkWLpplSrjoK//zWuw/mFCt+DjByoFq7WBgFadE4A2TSg0zSguxOmHO0\r\neqVVsh2Ny4roA0k9Ki4JkUyg5e/UZDVKowlNJ9yMY3DLhW1DJiRsvRsz3Ciu\r\n0ju7qhtSVHxecQRxcrLAwi/8zzhQpJykrNoDLbENDNQOMdsJnC0wUmLY3X6R\r\nVrS3vOm6jQYMS4cOXnP8iEZlPFjo8x9e164=\r\n=39E/\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"8794e1157d1f54fd2567832193914822a6bbeed2","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-03-09T11:12:37.145Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.6","bluebird":"^3.7.2","stopword":"^2.0.1","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^3.0.0","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.18","@balena/jellyfish-logger":"^5.0.5","@balena/jellyfish-metrics":"^2.0.47","@balena/jellyfish-environment":"^9.1.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.13","@types/pg":"^8.6.4","typescript":"^4.6.2","@types/jest":"^27.4.1","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.5","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2","@balena/jellyfish-jellyscript":"^5.1.51"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_16.0.11-renovate-major-external-major-8794e1157d1f54fd2567832193914822a6bbeed2_1646824589246_0.10523555295825338","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"16.0.11-renovate-balena-jellyfish-jellyscript-5-x-3178964e95309b930477127ee3836d977fc4557a":{"name":"@balena/jellyfish-core","version":"16.0.11-renovate-balena-jellyfish-jellyscript-5-x-3178964e95309b930477127ee3836d977fc4557a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@16.0.11-renovate-balena-jellyfish-jellyscript-5-x-3178964e95309b930477127ee3836d977fc4557a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8facce09f9099f436f77c59183ed98dbb7acd0ee","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-16.0.11-renovate-balena-jellyfish-jellyscript-5-x-3178964e95309b930477127ee3836d977fc4557a.tgz","fileCount":238,"integrity":"sha512-ISLPVxPFBtcIqaKdIRKN5oJ3YnqPj+jRyo2zo3wqCmapF35TawNbxmqD4HJeKm0MVZdjg1DAhQ12Rg8F7S8pOQ==","signatures":[{"sig":"MEQCIEMf6ZOAYcp4ddZvx4NKGBeZn0DkyyKSuMzU6pG2kbqAAiAYXURlK0Cw3yBdqvv8UChmOCo4QaQNc51liMB2dxqxbQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":842102,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiKLICACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmrVNA/+OH5UhRh76czX0PSnIV5FzLNizBr6ON7nOnYlE9Zmiw8nA7Tu\r\ngaxoJqeLwzXl/bOhDTQyBcZ2Nus1oVOBCqrRGspqchfl+YhEnYFyLKke4G2k\r\nMHQw0w7RpscUGYPTyIepBQC7hqsv4Dszy0L9n1sNe/cWpR8FRxk1yfHXLdDg\r\nV36gvLPOcKhSmQrD3jh2W6TAZkUchY1ze6wwEctRRjC6KPNZJNQEN6uREcK6\r\nM/7xV7IVVpV7N/5UN753uLMZ0HFIsib+4Eg1iVqgn3teWP5Ah+5rsoZHCEV0\r\ntIQAUpmV3duX3GqKv7o0AZOs1csRiugJTHxuwHRZDy+Sjbym0nlq3vAKIZpy\r\nxQf/12xDWEz0+IwTHSLpXT9yV33bUv7iFrq5IhkCFTdsHYOsqIlxOAQxc+1F\r\nkPdnYkkvcoLee5n6o4zZJ4My8IikybAoQu+mFk2ckFA1ZuuOTU+0yS4tS1yX\r\nhXgCcZY6Kr+XnkcDUtGrdxpWyyJG6p0zuA7/pPo+dtOAVRr7XvDUE7wfILCq\r\n9COh9+hBzkNyMdBGAP5sMBPWnoXMsdhtFqsziG30c8FHL7VhifWT6uS/byq3\r\n5676g6aoEVkqXdXFb3wy4vl/jmMO8JKeYq1SCYYwd0f/lWMo3E7sFOOc4HdO\r\nH3mcNOGgsBsEn9KtXufcAIovVU3fcfxu5Bo=\r\n=QSA3\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"3178964e95309b930477127ee3836d977fc4557a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-03-09T13:52:39.768Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.6","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.18","@balena/jellyfish-logger":"^5.0.5","@balena/jellyfish-metrics":"^2.0.47","@balena/jellyfish-environment":"^9.1.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.13","@types/pg":"^8.6.4","typescript":"^4.6.2","@types/jest":"^27.4.1","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.5","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2","@balena/jellyfish-jellyscript":"^5.2.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_16.0.11-renovate-balena-jellyfish-jellyscript-5-x-3178964e95309b930477127ee3836d977fc4557a_1646834178797_0.10324908425929058","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"16.0.11":{"name":"@balena/jellyfish-core","version":"16.0.11","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@16.0.11","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6f4a93d05d41094553defa5bb82c8d530cb8ff76","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-16.0.11.tgz","fileCount":238,"integrity":"sha512-4S9b4pQJjzOE8BGD1Rt7PxspwXBkeTd/uiaSLAdl/JLRJBK/Tm2hFs/5K5dbHhIMmzk3chgD5XcbXawPJRjJcQ==","signatures":[{"sig":"MEYCIQD+H+swgWm4hMJBfc/NLsst6ldrkLYVrXIvQFjhMwBxiQIhAIrCBTIln67TnjyLFtDCXkmaPlYkBae8uyq3OKMFnGS8","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":842019,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiKMEIACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmqSAw/9Fq0WKlz36Q7OVgsOCLg/L+aBMShYAg+NUW5b/XSb/nwpznJ4\r\nEY6KMnrXyLDWPvsmq+z7ZpKBJWHxxtkIw3wJsuULdxhpmY6xisDZpwaEkAbM\r\nbkqMMUne8quy6Oxu2p00zk7lKtnJBY3FkGpcUArmm3/4DlaDu/DG7xZP6r+w\r\nHs4n6G3vbg6L0IG9x/bmh/utuq4hp2DdjarlZH1QO8ewaJaSgsX1ZBd2Z9Ks\r\ntCS2EyGlMMMwpGn4hHANKvZEpShisNYn1u9Vpg0B+o2UwPdT1EDtNuQvTKNB\r\nQoiLHLmsqoFzWLBU5fyQQ47gxkku+Ab8+AidDse/c3Ms/sx23Xh4aM1ul58B\r\n34IiKa0OpwNvXCQJ2518bbGybkapwoz239eEvri+iFzhUn67P9CRYh5ggxvd\r\nnCtav5kh8q0IgrfVCdCNtQ+qqdMcAG9ZKTh9nIZPPFoSyoKnrchXLyQ/V4/b\r\n84u8M3xUQ3fr903LADsT6UXoRlqM6c1jctN4GmwIiXUhCywlRPc4Iaq7v6lt\r\nsD/L8Co57ZCI9Iln9A12Zn6XaZdJbv5nV9fj3dx51ynYBLdyAIcGhQQl0OwQ\r\nOLW8qeMWYXdvBX+lPrz+MBxyhcvR/HMOBgMyecxtus3UXqBVZkrfaQTd8xqJ\r\nhgfOB8wBiXDEiCIy07d0X2QgOIQebECbyu4=\r\n=/GqI\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"c4034a204f4b272a10f62e7d0fd02b7f739cf351","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-03-09T14:58:18.574Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.6","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.18","@balena/jellyfish-logger":"^5.0.5","@balena/jellyfish-metrics":"^2.0.47","@balena/jellyfish-environment":"^9.1.3"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.13","@types/pg":"^8.6.4","typescript":"^4.6.2","@types/jest":"^27.4.1","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.5","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2","@balena/jellyfish-jellyscript":"^5.2.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_16.0.11_1646838023928_0.3832008965087894","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"16.0.12-renovate-major-json-schema-org-86d45b23e4c0411305a423dce82e51609b4f240a":{"name":"@balena/jellyfish-core","version":"16.0.12-renovate-major-json-schema-org-86d45b23e4c0411305a423dce82e51609b4f240a","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@16.0.12-renovate-major-json-schema-org-86d45b23e4c0411305a423dce82e51609b4f240a","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"dc921faa4a7894efbf0899da14d23ca414cb6f76","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-16.0.12-renovate-major-json-schema-org-86d45b23e4c0411305a423dce82e51609b4f240a.tgz","fileCount":238,"integrity":"sha512-+ECxYpDP5Ub19fGv0vRCAFqMo+JGDtbMYj4I9O+t0LWEaZIMep+NHmp0Pxrrg72Cx9C+Mx3nonoMzo0oVbv3og==","signatures":[{"sig":"MEQCIEZrm1/zBC5rNRPcXfuCKLqxj+w+LfvGH2bsu6/9/kz4AiAXBZx3hzFUe0t3N3g2hk/qH5Wg76niYMzY6HFRUuCR+g==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":842177,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiKMJCACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmpWwRAAlWt08UzcX/b/7Ljy3X1t7FYtJNIrDUO46rGW9WdxZUw4ZcRO\r\n2GtkBLjMXQ/z4svf+zs+9bgk+2NTklqleshLu74EP2io+eE8CJEtuFLPM8Wi\r\nxG3pMNLVms19e1h+Y1v4ZBzO7zvgLFOwBJdQpLK4vFdmaHpSSq/XHc08C4ID\r\nn4Lc0Q8b3CwcUJ8wceYPtbSzxsCiP1/wA7ve17lqGFur8eiUJo1B1+oIkXoW\r\nxGdfOHAjL/G3waFf8dVtecwjTLGMOwk10vPeaDwdePrnKzS7MnC7yYEmq7AX\r\nOzH1Jtyz//Km4HIZfjDgMUexTr6yXI4PAIDG9swXd2zTL+hqkLXjLi3LSgix\r\nbcjR1xWAZ0upUq5gxOgRJ9XYvjGRMAznJWRSxdIfUJgIhXq6h9wSFw7edB0s\r\nBttRcTO+RLVPRvdAX2rPzpJ4IBoS+Fd1l2F6mSyL3S5WpH64XLcSw2PCj+Ye\r\nYrFMv4IIqhKlE+DHomgMfAqMvCuJ8tTfuDE6rZ5yLIvtWAQJ05lyFf8JgNy4\r\n2/gfSkJHCY8Uq682e2QhQZOzfPsoj4L6xIhsy5XceGFqLpLBE9MCjTvYhwJK\r\n1oJKgpU5bJT/jYE3r+0XysT5MTOYIj2uz0JIzVn+za2k8AgKgF318vLeddSt\r\nyt2OyMIVHHNZ9x0DSRjLpLrujrcLe8mQ3Zw=\r\n=CNUH\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"86d45b23e4c0411305a423dce82e51609b4f240a","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-03-09T15:01:56.918Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.6","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.18","@balena/jellyfish-logger":"^5.0.5","@balena/jellyfish-metrics":"^2.0.47","@balena/jellyfish-environment":"^9.1.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.13","@types/pg":"^8.6.4","typescript":"^4.6.2","@types/jest":"^27.4.1","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.5","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2","@balena/jellyfish-jellyscript":"^5.2.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_16.0.12-renovate-major-json-schema-org-86d45b23e4c0411305a423dce82e51609b4f240a_1646838338629_0.022000549614204612","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"16.0.12-renovate-major-external-major-783dfaf8a526b558177997c33d72dcd49e9a62cb":{"name":"@balena/jellyfish-core","version":"16.0.12-renovate-major-external-major-783dfaf8a526b558177997c33d72dcd49e9a62cb","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@16.0.12-renovate-major-external-major-783dfaf8a526b558177997c33d72dcd49e9a62cb","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"17242c6f734d2d0c45e654db76f9102ce4709d9c","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-16.0.12-renovate-major-external-major-783dfaf8a526b558177997c33d72dcd49e9a62cb.tgz","fileCount":238,"integrity":"sha512-sVoRkbnLgTUpJE6YC8IkHK7KC/uCyYeIeBwPacg0bqIgrYqEhS0Gm++u0hr0bdNphirLFz314T1opIGjyY6pxA==","signatures":[{"sig":"MEYCIQCgM5XOcMv4L7pJ99ji8T0IbhgGZxiY9JZ/zgQV1KqRMgIhAPSGV2ZQcW5/AieFzps8K2lkZmwyl5+nellW47xkBMit","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":842154,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiKMJrACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmqxCQ//b3qlqhVoQredokwsEmEW9Xa/TrX6wYmM16K14cLnmZrZl/Cy\r\neGYWUq5vbGrylptb2K21g2tXM6EjU57OdzsDaoZsVyVwemnI2qF6BbMCprfH\r\nGY0OUhb93FrlurVQEJCOvIX7i95856wACW4juoubpiWzthpl+psomrlqCbOS\r\nlfZmW3Loiuw5fFTPYcEFEYflw0TbT4pV/6xqED5B7b84bsOyvWM29C2mhZHW\r\nz09QD0dr6kuqH7m6iuOpoCPx33LzneUNnfFqUA7PsN4PqG0G6eGfEbOqLTaw\r\n8Bi8GuCcooP19LduF9vWgy7Th0tH1oM4zsIW0RqCpgAEML2UC7wgzlG0V4AT\r\nxmrIofWe9dFB7VrmiNP+zrCebX9E/hCRcvf+HLj9rv/mKMtKC5K3cJde0++1\r\ng8ymbkJh+JDZrb0eKqecxdyES1154Z/QewdoUc8C0Am+wtFoPKKraURfDWR9\r\nPn6bkKluxHc0X5YJ0VAI6FFFZd3yEecKCwc5VgSZ+7P6Ylj1RqEAcxuImGAa\r\nj47LeirzbdJjvBAVSWKfkJuYCtRTHUNNKC6nc8/E6LHt0F4IhLvcukvJxRI5\r\n+N49heYM5RLulW3RLkQSGAU2Ofjz8Q/LodPHTr0tIF1UKFYv8Uj6qRzeMxIv\r\n4VtwdyxKL7uxu8lvw3UshMLbd87Dx7wkqbU=\r\n=E98L\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"783dfaf8a526b558177997c33d72dcd49e9a62cb","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-03-09T15:02:58.700Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.6","bluebird":"^3.7.2","stopword":"^2.0.1","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^3.0.0","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.18","@balena/jellyfish-logger":"^5.0.5","@balena/jellyfish-metrics":"^2.0.47","@balena/jellyfish-environment":"^9.1.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.13","@types/pg":"^8.6.4","typescript":"^4.6.2","@types/jest":"^27.4.1","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.5","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2","@balena/jellyfish-jellyscript":"^5.2.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_16.0.12-renovate-major-external-major-783dfaf8a526b558177997c33d72dcd49e9a62cb_1646838379141_0.6547796434235578","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"16.0.12-lucianbuzzo-aggregate-initial-value-63545856b2dab2c15df0220dcc04cb35c7bd884d":{"name":"@balena/jellyfish-core","version":"16.0.12-lucianbuzzo-aggregate-initial-value-63545856b2dab2c15df0220dcc04cb35c7bd884d","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@16.0.12-lucianbuzzo-aggregate-initial-value-63545856b2dab2c15df0220dcc04cb35c7bd884d","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"d830becccb1ae0c7f0bbe81e3c9d9819e05bebdb","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-16.0.12-lucianbuzzo-aggregate-initial-value-63545856b2dab2c15df0220dcc04cb35c7bd884d.tgz","fileCount":238,"integrity":"sha512-2qxNEUz6t4BAhFJsBWhTKN2xDDUfW/wCZtQzUzuteDe6dWaWUxijGkU6nIRWF6RWHvmJr8ssYpheTHaAIXFfuA==","signatures":[{"sig":"MEQCICka5rZ/a1jMZ0NpfKr8ZDTyEtRN/FU4X7lWqPNgqGKBAiBjVQSdYDXOlrvGxx1M4h8r86HdzeuzPPHMas6RhLZ1pw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":842196,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiKyx/ACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2Vmrk1w//dr/VjOfmLa2lXm2Y1cgYnKyzQBPo0RvxDKWP5giyhwj5QkKa\r\nV8ZEknHdH+8q9rdxsD8kDHmsrEjFuFbXKVLVor6yIAnmGDL9se1JChBMgX9p\r\nadB9JnGQEzMIWycHO1ZYAXDy0cL9gPTMBHkva2PYab9NKS0HP+vFZeqCBC8j\r\nkJx/ekAffOWhgl0lSzPvretZCyU7ro73Rf0/t+TsBEkoxNqXcBH04IStloIu\r\n6zi9StNvHdRv6Bkbt/HvYsyunTWUmPA4PvdOFqbsYxNhljfRueCEbSwNQuZl\r\n3+q4GbJ05POXTBaEG7cNw5wU4oP41Y7rlp4gVYL0Kqbb2W4Wj0ZiuicK78C9\r\n9aXt6TGhrguu4InhQyt+mdRdP/8rXtorVW/qy99Y+YqgVYEepb2sEvp5KY2y\r\n8rf11koBaQ+8vR3tHE4OoacDnVBSYSPm1vpe/Cr0XZv267DQRIdNVzkdm0ir\r\n6f3mV1234xEmdZ74OWA3TY+TY6crO9hZo8UaoqHNnfn/msaTaAuuSykrOmfy\r\nreoUl3qqxvjAvkFgKq/Eobfh8Ha/6eN68nWbfTj7ZPqWson22TIaqeQ7hRIW\r\nwcTyF9JA9DRNZouNzJWSr6F/pcVqVmYQrRgxZOAEBDyojkhxb96+Y5E8BvG0\r\nsZp32GaL7cnkYpgn2wVJHvjSFf3iNExfNPc=\r\n=mRue\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"63545856b2dab2c15df0220dcc04cb35c7bd884d","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-03-11T11:00:11.115Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.6","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.18","@balena/jellyfish-logger":"^5.0.5","@balena/jellyfish-metrics":"^2.0.47","@balena/jellyfish-environment":"^9.1.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.13","@types/pg":"^8.6.4","typescript":"^4.6.2","@types/jest":"^27.4.1","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.5","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2","@balena/jellyfish-jellyscript":"^5.3.0"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_16.0.12-lucianbuzzo-aggregate-initial-value-63545856b2dab2c15df0220dcc04cb35c7bd884d_1646996607256_0.5721538182272301","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"16.0.12":{"name":"@balena/jellyfish-core","version":"16.0.12","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@16.0.12","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"8314bcbc3eabb92bfbf72d48e0007cbcb6fdc91d","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-16.0.12.tgz","fileCount":238,"integrity":"sha512-YiPW+dPh1pPmSZwf8Ex8kUbbgWIHntabMrJ1M2e1QLT38HrMrsoaAJHrqzBQARrz8ajII52qnpiyY38F+ZE4ww==","signatures":[{"sig":"MEQCICf1c8QF6mmIXayq24C3ry19DyU63p4cggkmFQFHf0SuAiAs0AbXlX8p5Doef6yC4sKU3crs5owAiZ8W5c+WQz3e1g==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":842119,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiKy/wACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2Vmq+rBAAgVwTvR8k+yhnK34C2SaG8kyYKDGv27AnQBPgbiiqBBeJ5v4m\r\n3MEPYORMszec9uaW+jQKvEjED6AkAcmzpZZaQSqyLWFEgfSb5F1D5rq1PQMF\r\nc26JI1Qmo+YGG0AYHchmHGx/gbD1I9CblwfPoyOjOaUvUrkqCUbdeJmgguFj\r\nPwbWnT9cr+KOMf1xaIq2bdCwe19nzNqJ7Q9T0kSKQT17HuE+b+HKvHamPABk\r\nmv8EZoja3ivAGw8WqAUxbQDqXVTdP0j7rTBKEm8WA6tqK1pM27fSb6yxVVsP\r\nyFF12ShaaSBKTN6X6SBg0q+s/DSY/pHFHcRkt8bqbqjZBHP+lf7HUDSgq+Yp\r\n0GdjyPMx1CROIGUL/llxMr2oXMm7ODUnqBdOlxF4+ZwFjRPNBWmornVYCm1k\r\nZ3boBK6dDXTzi+u7RYzbQRIQwMQ7J+oxZffeFtyJbRA50ou0pKETfIOSuKu8\r\nXnR57vD7sCegC7/TmZwQCAtTqQcsSlKZe0nDNC2bIxkua8mHnHSd2uc47WGa\r\nflzU+jTmO6/jVhzXr72JHkQkNrhG6leJ25ra4wPai9Bs4w3c5/Ml1U77JGmv\r\nx704Nk1GZzMeTOgG8ESkKuHii7EX0wAor9fBgtiOWpZLIkMq7GVXvbu84Exb\r\nJUW8TNPlQd19zc0s3EgTw/OEKSCDpLPYOCk=\r\n=tFqx\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"61c716b92cf49fd2c5a3d83316e615658a49d384","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-03-11T11:16:01.884Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.6","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.18","@balena/jellyfish-logger":"^5.0.5","@balena/jellyfish-metrics":"^2.0.47","@balena/jellyfish-environment":"^9.1.3"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.13","@types/pg":"^8.6.4","typescript":"^4.6.2","@types/jest":"^27.4.1","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.5","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2","@balena/jellyfish-jellyscript":"^5.3.0"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_16.0.12_1646997488510_0.2846076016315575","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"16.0.13-renovate-major-external-major-3eff9aa3eb767ed804395d4477b50b212303af31":{"name":"@balena/jellyfish-core","version":"16.0.13-renovate-major-external-major-3eff9aa3eb767ed804395d4477b50b212303af31","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@16.0.13-renovate-major-external-major-3eff9aa3eb767ed804395d4477b50b212303af31","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6f6fc9d4499edc285c211d37b2d5a362519b6115","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-16.0.13-renovate-major-external-major-3eff9aa3eb767ed804395d4477b50b212303af31.tgz","fileCount":238,"integrity":"sha512-nabR6BhQ6WVF7WtZVkQ+Xex5oZCJulUrp+31W5Q0uhTjNRkoUi5Ro4DXLxVUN95KAT0FDhvsIngS/buomKEjOw==","signatures":[{"sig":"MEUCIFfPsFuTA8p6koJ0DSKf8dC0iV6hzvP5eQs5h5ABHQZAAiEApNRXRRjxtLZ3W4/Kd6u+6IGMUiosZxFu8K+QzufLo54=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":842254,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiKzDYACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmqeaQ/9EnmwuXiB7+6pbTHzouc037+hONQJvCIEoSfIjYujxMb1H3HX\r\n6diKePt3cmJSJHjdCey1qoPzg2qLT0L5F6ZzCsqxs9RAkP46J2CMrUQKBELo\r\nxy+u0f11pLaMA9pwmXa/eP55LDWFuvm0o+U5pRDEGXSwEMZqBBQGUn8YBYYF\r\n/PuqaVFC92Iv7Pv1f9ySP+DfCOaNk/be927sLLeIBnaQAe1OlrLB+PQxjTlp\r\nB9Y2Q4Lm5miFzWiBvWQmcG854AY2CLZzI8scPKDIh+ZDgR9FsXtRg+7WKdBL\r\nHVCZLZadmJ204HggbHLQB9XwqgDcPnGW4p8xBWMao1PofyBXq5b8v3XOY7sN\r\nLM/Pm96o2tx33Icj78nAYgdTCXXRRhnfcmXM8Zs6Kyx/Hc79cT004pY99ga1\r\nqfKJpdCBIKLNRsV97Ob857eYMXj3k/faPSywnVnBOo8dnntIw1OnfoMF24vy\r\n49N9U0w0eQND9AzkdT4TowlDCfUCfb3eWONBD++NMwNMFPZ6PCASMObxolDp\r\neZJV2vUqy2XbY0phbGr1heqsxnKZniET6IO4b6gxqGHwDVYnaA/pGXGiFyQB\r\n46oAJ95UuMrgj1I9uvPfDm2Jdx6bBIDs3WhKtMtebPcsXV3OvPKQ8uPoiy3B\r\n0bYS8lSaeoQ+l941HG0Lb/Z8l45m1TndA/s=\r\n=e916\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"3eff9aa3eb767ed804395d4477b50b212303af31","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-03-11T11:19:02.350Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.6","bluebird":"^3.7.2","stopword":"^2.0.1","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^3.0.0","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.18","@balena/jellyfish-logger":"^5.0.5","@balena/jellyfish-metrics":"^2.0.47","@balena/jellyfish-environment":"^9.1.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.13","@types/pg":"^8.6.4","typescript":"^4.6.2","@types/jest":"^27.4.1","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.5","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2","@balena/jellyfish-jellyscript":"^5.3.0"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_16.0.13-renovate-major-external-major-3eff9aa3eb767ed804395d4477b50b212303af31_1646997720772_0.037590899824877066","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"16.0.13-lucianbuzzo-jellyscript-6-x-64677214f1f0600981e3c511c962afd5a68a16a1":{"name":"@balena/jellyfish-core","version":"16.0.13-lucianbuzzo-jellyscript-6-x-64677214f1f0600981e3c511c962afd5a68a16a1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@16.0.13-lucianbuzzo-jellyscript-6-x-64677214f1f0600981e3c511c962afd5a68a16a1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6f1f54206748fb77cc1a1d5f6e52960123206ed7","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-16.0.13-lucianbuzzo-jellyscript-6-x-64677214f1f0600981e3c511c962afd5a68a16a1.tgz","fileCount":238,"integrity":"sha512-fmMorVcK+wBmVXa/36bmIJw5fXszJ93eXcJCs3Bf6x+n95E01dS2/PCrp8MuhtIai6HshDXrGpcbsdmPM44Kxg==","signatures":[{"sig":"MEYCIQCDcnnDA6wI9SPyg9Yojn9lNwUIB7U1E3ES91SOdJ6FXQIhAPzaM6nBFa8oo/BfSIO0ohFz0h1KMvJFlBQ3c19afCwv","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":842272,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiL03iACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmohbA//bCo+0T3E1Vq4Bjaew0H+173i1fV/UvkDzp1nZco0jVNYbFar\r\nu1K0XrHJwJT1KMaBZvt2ClXTepNLvINV3HoBWIgI187XBYb2Nsjvnu5xrnvr\r\nTVtm9MMIbUDKI/OACXLiVulcmgnAlc8wM3J+2yYAolJ05GTlkV/ZElcNVUZ+\r\nfKvC8EYUISaq/vYzGTQ47hF/SOW7fgQLhREtZAG2Ol/YIekS8PhoZ30PwksW\r\nNfZVfxtboRY0X4p7phoQTRj71Oge78PvNZYhRGXHfDiM7fyGT5MfL5OQh1vo\r\nViSdZkfXwF3IVZgAkTzCPevET83/YWkunXv92I/jsSdx5//kwQmSnUNJZaAp\r\nqxtJkeXvMZhWRQLnVP6/dFSgOqgjYJTsAo8dsnKRmJRDKf0GDRubpkcP3qAd\r\n9N0M01+OcOwAxASwQJhwL1y7io1BDvpYsB38BcU/epCa5wslhudbJy/kkBXJ\r\n/QsH+e6AsQPFFreGjiEcQWLFrk3aKBYJSeZpMY7VBfhnbmrRPbbbbIf2C8Ln\r\npzdi5YA2X3+mTVNOmyOTaPD12MT0nkPC1cAlQdjphRHBLzpC+vbus0vNrjVb\r\nfKsznxC/dqOUKLcfBVenTBiPIdFUbKcnWMAA6d2hHpcAPxGlt7X01zpV+Yk8\r\nzSQvKcV9ixyuVZORitz6iAx7jl864tkHK5M=\r\n=kulI\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"64677214f1f0600981e3c511c962afd5a68a16a1","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-03-14T14:11:33.285Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.6","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.18","@balena/jellyfish-logger":"^5.0.5","@balena/jellyfish-metrics":"^2.0.47","@balena/jellyfish-environment":"^9.1.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.13","@types/pg":"^8.6.4","typescript":"^4.6.2","@types/jest":"^27.4.1","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.5","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2","@balena/jellyfish-jellyscript":"^6.0.0"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_16.0.13-lucianbuzzo-jellyscript-6-x-64677214f1f0600981e3c511c962afd5a68a16a1_1647267298061_0.22781816151754786","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"16.0.13":{"name":"@balena/jellyfish-core","version":"16.0.13","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@16.0.13","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"6f5971f7bf1d57d010bba08e30b2e07111ae0334","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-16.0.13.tgz","fileCount":238,"integrity":"sha512-Wcv7Buu5rg0B/QO+E2K1fmWcKNMWkDWaDlr5w283egPF2V3huLSujskk+/gTm9PmxWOL6SE3dPwQf6YQ4xqn+g==","signatures":[{"sig":"MEUCIDXEo5yp/++CeI1yyXGYxGZtRymn7Pybt5shRTCL8acmAiEAzEn+2GImKXR4gwsX/j9T0sgTURpKUpE4wLcBdjPPRRE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":842203,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiL1KvACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2Vmps1g/9GkTx46wog61j0obmfoMA+MeSTyS3Sp/BVAsfiSMQe+28z0w9\r\nGBD9Im+mPiLr/WzYO9RWs0WnXnOSK7iiX9U394R66izkPlgU0CVZzm2Iv63j\r\niiHQU/vqQraxIP42ZHLifg8PhI3XxaaMTRuf5U8cWe+E24jvCHQx5b+jV7Sx\r\nOWuEjIS/ZE7PVjXoyeFllVMMvusdNtuBum7ZEvkjbip5wRYlmfqqhv1SSAMI\r\nsX4W6Ta/UrUgxfldfo3V0iAGC8jnlBjCBUHoBjpUrUlBxIiiwO86KWil76qg\r\n2s/3EvD4AlljOdk4pXoR/QT/Yd+ef6FbRQJakdGGXrJHEhcAsXTvca/N4kVr\r\n72blq59NKe+7Q3PpQOAXF/a30iI1wViRdfkWILu2JkZi4B5moC6+X+OkKxvP\r\n2PgM99vphI4RkAD2Y/AAiiIVX9oNLGEqIKU6eWpwytOciotPRvmLZwXeOfqs\r\nlR3pC/1r7vYFAZL/6kA+JUhx5DxpTMkN0tzjxIMSYvpcdTO+WTKV0NP11WBI\r\nU8bUiL6mT+OcbyFHrF2XEtclw56zYLdXNjAqhqz5nmL6pbHy7DpjsFG/NrbM\r\n1d53RGpt1BkjBCLND6unHxwavpRUQTzcGAvbVtpH/Ir41bVly1rN+I1dDU5i\r\n6/SP/dA1zeRWEpdJ2LSnSIVB6FTf20I5U9s=\r\n=H3iN\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"45bc2943d9d0613aafefcf12aa24abef1872a6f3","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-03-14T14:32:45.262Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.6","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.18","@balena/jellyfish-logger":"^5.0.5","@balena/jellyfish-metrics":"^2.0.47","@balena/jellyfish-environment":"^9.1.3"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.13","@types/pg":"^8.6.4","typescript":"^4.6.2","@types/jest":"^27.4.1","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.5","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2","@balena/jellyfish-jellyscript":"^6.0.0"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_16.0.13_1647268527632_0.40052106030685763","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"16.0.13-renovate-major-json-schema-org-e993f43d3529c835a57b48e6251c768dd1db5a6f":{"name":"@balena/jellyfish-core","version":"16.0.13-renovate-major-json-schema-org-e993f43d3529c835a57b48e6251c768dd1db5a6f","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@16.0.13-renovate-major-json-schema-org-e993f43d3529c835a57b48e6251c768dd1db5a6f","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"0db44f2aacd09f300b42bd22163f716e563319a2","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-16.0.13-renovate-major-json-schema-org-e993f43d3529c835a57b48e6251c768dd1db5a6f.tgz","fileCount":238,"integrity":"sha512-G52zGzkaH9z1TMGhVpkol40ASuNQPEFXyrfT71OTqbusrx7tP9zBtd/lVD4IjfUBu81LQ8OIIj3gXl2EQlR0kg==","signatures":[{"sig":"MEQCIBzFmMIvmrO9ZCQnPCW0wJtE2xlwbkRXZoX2J4oIbQ1bAiBIWUJzVJ6gFjA2pprRfaSZBhTzLdlORVAnvDeWQ3wLEw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":842335,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiL1NdACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmqyNQ//XW1uGn7U5yXLavZgWODNwLBUh0MXadWKBs1NIJunzbn3L7Nt\r\nsXu7o9bSQrcwTvWsRH4b8K7JDcoMZq+9FtPXu/T1IFMzR6fTjQrUJtQ53QNh\r\nnhFf9jEgibmEMKVnrWkDy0EKlr+B5g+V63tTbFBrVbGRgRJ4iywS3FDx4x2I\r\nyYo2Intb2kz43nFa7JaKEUkzgdm1Wp+NV228sRzB0gVtfaTKLVWiaYRg8imk\r\nKtAntpTjjVQvQFWLLNbrczi7UGc9fj9WudNjxNg8HxXKkpxuaCTTjggeS3o5\r\nKDHe6/GlLUwCVBxJhG4EQ7QbpBq9ltW39jqZyVeHg9vJEi3qbacQZz8RrHMm\r\nelpCje0jjIAp15OU+CQy/FTMTCqCbZ+GR2fGjRlze1/1gwI9Up0VcBlZFvMn\r\nsWyWWXMQVFadkc2yy61aelw5a7a1avUDpPLLuWNqNSQ6ZRtZU2IJDSQYNoQ2\r\n9hPVK37k9yKyLKx/MPFn1IPRluT1tigcD//VWBELqN0zVkW36AgruIQn75JK\r\nZroyux4OYG7RL0SlVagB9T8BtHy26IjztPRDwKd5W0RVh01arQkREXvafRIo\r\nQG+iPhOjmaAuQ+jT/J/ENbkvFZb0FrAg/kbc5IdL9pgpy1fupGCCgNVINU1r\r\nzpL5RjqkevSukGhyNfEIW6tVdpZEYnN3pJs=\r\n=07SJ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"e993f43d3529c835a57b48e6251c768dd1db5a6f","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-03-14T14:34:58.035Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.6","bluebird":"^3.7.2","stopword":"^1.0.11","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^2.0.4","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.18","@balena/jellyfish-logger":"^5.0.5","@balena/jellyfish-metrics":"^2.0.47","@balena/jellyfish-environment":"^9.1.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.13","@types/pg":"^8.6.4","typescript":"^4.6.2","@types/jest":"^27.4.1","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.5","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^2.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2","@balena/jellyfish-jellyscript":"^6.0.0"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_16.0.13-renovate-major-json-schema-org-e993f43d3529c835a57b48e6251c768dd1db5a6f_1647268700770_0.7257190585250044","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"16.0.14-renovate-major-external-major-98a3ec3f7e9d2fdf7ebb67e5ac08936443c9e9e1":{"name":"@balena/jellyfish-core","version":"16.0.14-renovate-major-external-major-98a3ec3f7e9d2fdf7ebb67e5ac08936443c9e9e1","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@16.0.14-renovate-major-external-major-98a3ec3f7e9d2fdf7ebb67e5ac08936443c9e9e1","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"5b74084bc774aa4400c8ce5bf5e49513fc8efaa0","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-16.0.14-renovate-major-external-major-98a3ec3f7e9d2fdf7ebb67e5ac08936443c9e9e1.tgz","fileCount":238,"integrity":"sha512-lwdPXE/MdqplToSRWvqeaQWRp+KOKyknHMLimewTjznNgQYk7fGjkOlMkm5f5GcnzDLZZ7kgjKmCz2K2PjnT6g==","signatures":[{"sig":"MEUCIQD1QxmGs121kd590ncDK2p82UBgF/hLvTAueujGyEO1XQIgXBYqVPKqv0eH8xUHVpeFtNSnWuMbAC0vdiLvtpTsFxw=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":842338,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiL5o8ACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmrhvRAAgMvnM884p2YrwYMeZid95qkflQlg1zBgPFmFL2rISH2S0uvi\r\nOUr2J09l4/XGuE+tvJ8kJjPZsm0ZR+kqktIesmWQTeaE5V2V9hIcfBJxr0XN\r\nB29nisRaRGbs9wATwidxLDOeaTcVKzLcXytkftJmPIeUzmPdSKsm98PGYrdl\r\nJnY1IXVQMxJ1xDxpy3BawIsKtnx+5xY3+1L5xtzZL1AyC2Q9tQ7MA4GnWS1N\r\nYzUrwGZNQG0t5vE84GklNHdCTGyRYtyvOUfGQIGoRlMVVEbb//pzTdBiCdkE\r\nAYyKOo/DhZWl37x4+3XT5exND5PkjyKVDwoRCeawVFEsYU76VqOoxwOlRGbQ\r\nzn3+GsM3PqiH9RQt+1tqCL428+/J72QFLyZo50azIY1/HACjpaoOyy/SHVNb\r\nQVbnax9+tanN3E66X7Tn+SUXMb3DjfJvC/+kGHCUpLIGb89jesPRPhcXrfyB\r\nw+ov2W80D609/cIO1pCiIyVPRr0+nmuE2JTFgiwnJtp0ZYSeraiq8t4+5cDP\r\nAgq8XWDW8PCAu9CnyNO3HiLd1ot5EUDUXAnNCVCg1T9Y+cMiUrbd8e+bNZVX\r\n1RWnpeE+zMERGqhlVHMnz0d8edJSQx9UT9bLAHK5RIOLkpRGGdlhbRIh4N2f\r\nQuBVXVZT9vMX2U4mMJwX9Dq6TnurQYNiYMI=\r\n=UVH4\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"98a3ec3f7e9d2fdf7ebb67e5ac08936443c9e9e1","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-03-14T19:37:30.954Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.6","bluebird":"^3.7.2","stopword":"^2.0.1","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^3.0.0","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.18","@balena/jellyfish-logger":"^5.0.5","@balena/jellyfish-metrics":"^2.0.47","@balena/jellyfish-environment":"^9.1.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.13","@types/pg":"^8.6.4","typescript":"^4.6.2","@types/jest":"^27.4.1","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.5","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2","@balena/jellyfish-jellyscript":"^6.0.0"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_16.0.14-renovate-major-external-major-98a3ec3f7e9d2fdf7ebb67e5ac08936443c9e9e1_1647286843778_0.37384629301260586","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."},"16.0.14-renovate-major-external-major-84ac7e3cadfc8d0fb229b0a916aef804637c15f4":{"name":"@balena/jellyfish-core","version":"16.0.14-renovate-major-external-major-84ac7e3cadfc8d0fb229b0a916aef804637c15f4","author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","_id":"@balena/jellyfish-core@16.0.14-renovate-major-external-major-84ac7e3cadfc8d0fb229b0a916aef804637c15f4","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"}],"homepage":"https://github.com/product-os/jellyfish-core#readme","bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"dist":{"shasum":"30745b0c5d3eb2dc4eb086f3d889b7f622098208","tarball":"https://registry.npmjs.org/@balena/jellyfish-core/-/jellyfish-core-16.0.14-renovate-major-external-major-84ac7e3cadfc8d0fb229b0a916aef804637c15f4.tgz","fileCount":238,"integrity":"sha512-Gu0n4rmuaPxcqrPGQn7uaQtqMCh6pIft/wy61IVBnpjGFy0ThevGUTTU4Pcyj6EJ/3euo0LR2oS3mtKXvP+Ozw==","signatures":[{"sig":"MEYCIQDnTF5LZfqncndW+/mExqQjwCLjvTOIKnoUIFYw2IL7kQIhALK4TMytDM03pwRv1c1Hp0AgCjDw+1ZahSECa0/9/LFx","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":842338,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiL/TaACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2Vmrokg/+NCJSLsez5R6tGrwO2u3xkAe+SVb7Kl1TvPwwgkk9Wm3ydH8d\r\nO3TBLNatIL9TiNZN67Tn0VVjnoemuGwHM2rRpPgt/oGeNfgy4VoQYWIFG9HP\r\nEjXc6xym6nDsz6/RfopIcx5eS5cm0z76ec4Uwbl1vv/nfiTNOAbfITyNHU57\r\n2vpQvzhgnNU4cxi3gvRHZ/e3bN5ZeniQ56ryFD+S5gRigog2wrum+JujpgQy\r\nqFDLoQigFlSKTTG9uxmjW0M8UfCiE+I5zKY30THF77mM3hSTH81l/YmQGcLW\r\nXZSK1ct62jv1HIfT06MfVYCXlGwu8BKp3QaVOFx+B9iixXg2p4HWjrUJ5wfh\r\nSN/PV+EWBH0Zq2f+gWLlQPhOik+BmZdRzRbYlr7Vydc45guBHCVOQf+OXMGA\r\nTrzniPLji+6CXyUF6CkC3kaB3I+u7AC0kyPtEwbSfSgT052lfMnCd7vOH1tr\r\njZGIVYNm4sKDFK9L7unAObcZpuIr8YLmk3Qpi6bXwRtb3lHPg05/ZVKmg7Ny\r\nwo85hbxCMD/SPNF1JLWeNUn8BTHG+FesBc06YtlQ2oHuO4JAc9S+452xqKMQ\r\nZyKbpfgoXsx75hy61aD5xFszia/0oYjkYTqO9jEVQYDcMc79DluPJREzFAFu\r\nRibEGYSlPwq+Z/qEBLiC2PoNKlFAvOV6sf4=\r\n=oNWY\r\n-----END PGP SIGNATURE-----\r\n"},"main":"build/index.js","types":"build/index.d.ts","readme":"# Jellyfish Core\n\nThe Jellyfish core is a low-level internal SDK to interact with contracts in the\ndatabase, providing functions like `.getContractById()` or `.insertContract()`. The\ncore provides the foundation library for the rest of system.\n\n## Goals\n\n- The core aims to expose a small and simple interface\n- The core aims to **not** expose any details about the underlying database\n  implementations\n- The core aims for correctness and speed\n- The core aims to be the only module in the system that directly interacts\n  with the database\n\n## Features\n\nThe Jellyfish core provides the following features\n\n### The contract data model\n\nEvery entity in the system is a data structure we call\na \"contract\". Contracts are an implementation of the [contracts data model](https://github.com/balena-io/balena/pull/1002).\n\nEvery contract has a `type` field that specifies type that the contract is an instance\nof. Contract type definitions are indicated by having a `type` of `type`, e.g.\n\n```json\n{\n\t\"slug\": \"message\",\n\t\"type\": \"type\",\n\t...\n}\n```\n\nThese \"type\" contracts contain model definitions in the form of a JSON schema. The\nslug of a type contract is the value used in the type property of instances of the\ntype.\nAs an example, you can look at the [type contract for a \"message\"](https://github.com/product-os/jellyfish-plugin-default/blob/master/lib/contracts/contrib/message.json). You can see that under the `data` key, there is a `schema` value that defines the shape of a contract of type \"message\".\nWe follow the JSON schema spec, so if the schema allows, additional fields can\nbe added to a contract that are not defined in the type schema.\n\n### JSON schema based querying\n\nJSON schema is used to query the API, with any contracts that match the provided JSON\nschema being returned in the result set.\n\n### JSON patch\n\nContract updates are made using [JSON patch](http://jsonpatch.com/), allowing fine\ngrained updates to made to JSON data.\n\n### User system\n\nUser contracts model the actors that interact with the system.\nThere are two default users, the admin And the guest. The admin user is typically used for system level operations or operations that require unrestricted access. The guest user represents an unauthorised user interacting with the system. Users authorize function calls using a session, which corresponds to the ID of a \"session\" contract in the system.\nThe data that a user has access to is defined using \"role\" contracts. All user contracts\ndefine a list of roles that they have.\n\n### Role based permissions\n\nEvery user in the system must have at least one role, which corresponds to a contract\nof type \"role\". Role contracts contain a schema that defines which contracts the user\nwith that role can read and write.\nWhen a query is made, the schemas in the user's roles are combined\nwith the user's query using an AND operator.\nAdditionally, roles can specify which fields should be returned by interpreting the use of\n`additionalProperties: false` in JSON schemas. If `additionalProperties` is set\nto false in a JSON schema, then only the defined properties in the schema will be returned.\nWhen combined with role schemas, you can set permissions on a per-field basis.\nFor example, we can express that a user can view their password hash, but\nnot other user's.\nThis behaviour is based on the [AJV \"removeAdditional\" option](https://ajv.js.org/#filtering-data).\n\n### Marker based permissions\n\nThe roles system is complemented by another permissions system called \"markers\".\nMarkers allow individual contracts to be restricted to one or more users. A marker\nis a string that corresponds to either a user or organisation slug and they\nappear as an array at the top level of a contract under the key `markers`.\n\n```json\n{\n\t...\n\t\"markers\": [ \"user-lucianbuzzo\", \"org-balena\" ]\n\t...\n}\n```\n\nTo view a contract, a user must have access to all the markers on that contract. A user\nhas access to their marker (which is the slug of their user contract) and the\nmarkers for each organisation they are a member of. Markers can also be in the\nform of a compound marker, which is 2 or more markers concatenated with a `+`\nsymbol. A user has access to a contract with a compound marker if they have access\nto at least one of the markers that make up the compound marker.\nIf a contract has no markers on it, then the contract is unrestricted by the markers system.\n\nFor example, if my user slug is `user-lucianbuzzo` and I am a member of the `org-balena` org, then I would be able to\nview contracts with the markers:\n\n- `[]` (i.e. no markers defined)\n- `[ \"org-balena\", \"user-lucianbuzzo\" ]`\n- `[ \"user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-lucianbuzzo\" ]`\n- `[ \"foobar+user-lucianbuzzo\" ]`\n- `[ \"org-balena+user-foobar\" ]`\n\nHowever, I wouldn't be able to view contracts with the markers\n\n- `[ \"user-foobar\" ]`\n- `[ \"user-foobar\", \"user-lucianbuzzo\" ]`\n- `[ \"org-balena\", \"user-foobar\" ]`\n- `[ \"org-balena\", \"user-foobar+user-bazbuzz\" ]`\n\n### Organisations\n\nUsers can belong to organisations.\n\n### Streaming\n\nA query can be streamed, creating an event emitter that will emit an event on any insert or update to a contract.\n\n### Soft delete\n\nWhen a contract is deleted, it is not removed from the database but has it's \"active\" field set to false. It is recommended that users should not be able to view inactive contracts.\n\n### Rich logging\n\nWhen a code path is run, a context object is passed through the call stack. Each context object has a unique ID that is used in log generation, allowing logs to be easily aggregated to observe codepaths.\n\n### Built-in metric gathering\n\nMeasurable are gathered and observed using prometheus/grafana.\n\n### Data relationships\n\nContracts can be linked together by creating a contract of type \"link\" that references both contracts and describes their relationship. Relationships can be traversed when querying data using the `$$links` syntax.\n\n### Caching\n\nRequests for individual contracts by id or slug are cached, reducing DB load and\nimproving query speed.\nThis library contains email integration functionality for use in Jellyfish.\n\n## Usage\n\nBelow is an example how to use this library:\n\n```js\nimport { MemoryCache, create } from '@balena/jellyfish-core';\n\nconst cache = new core.MemoryCache(environment.redis);\nconst jellyfish = await create(context, cache, {\n\tbackend: environment.database.options,\n});\n```\n\n# Testing\n\nUnit tests can be easily run with the command `npm test`.\n\nThe integration tests require Postgres and Redis instances. The simplest way to run the tests locally is with `docker-compose`.\n\n```\n$ npm run test:compose\n```\n\nYou can also run tests locally against Postgres and Redis instances running in `docker-compose`:\n```\n$ npm run compose\n$ REDIS_HOST=localhost POSTGRES_HOST=localhost npx jest test/integration/permission-filter.spec.ts\n```\n\nYou can also access these Postgres and Redis instances:\n```\n$ PGPASSWORD=docker psql -hlocalhost -Udocker\n$ redis-cli -h localhost\n```\n","deplint":{"files":["lib/**/*.{ts}"],"modules":["lib"]},"engines":{"node":">=14.2.0"},"gitHead":"84ac7e3cadfc8d0fb229b0a916aef804637c15f4","scripts":{"doc":"typedoc lib/ && touch docs/.nojekyll","lint":"balena-lint lib test && deplint","test":"npm run lint && npm run test:unit","build":"npm run clean && tsc -p tsconfig.build.json","clean":"rimraf build","compose":"docker-compose up","prepack":"npm run build","lint:fix":"balena-lint --fix lib test","test:unit":"jest test/unit","test:compose":"docker build -t balena/jellyfish-sut:latest . && docker-compose -f docker-compose.test.yml -f docker-compose.yml up --exit-code-from=sut","test:integration":"jest test/integration"},"_npmUser":{"name":"balena.io","email":"accounts+npm@balena.io"},"repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"versionist":{"publishedAt":"2022-03-15T02:03:42.377Z"},"_npmVersion":"6.14.16","description":"Core library for Jellyfish","directories":{"lib":"lib"},"lint-staged":{"*.{ts,tsx}":["balena-lint --fix"]},"_nodeVersion":"14.17.1","dependencies":{"pg":"^8.7.3","uuid":"^8.3.2","redis":"^4.0.4","json-e":"^4.4.3","lodash":"^4.17.21","semver":"^7.3.5","skhema":"^6.0.6","bluebird":"^3.7.2","stopword":"^2.0.1","traverse":"^0.6.6","pg-format":"^1.0.4","redis-mock":"^0.56.3","fast-equals":"^3.0.0","json-schema":"^0.4.0","typed-error":"^3.2.1","uuid-v4-regex":"^1.0.2","fast-json-patch":"^3.1.0","json-schema-deref-sync":"^0.14.0","@balena/jellyfish-assert":"^1.2.18","@balena/jellyfish-logger":"^5.0.5","@balena/jellyfish-metrics":"^2.0.47","@balena/jellyfish-environment":"^9.1.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"jest":"^27.5.1","deplint":"^1.1.3","ts-jest":"^27.1.3","typedoc":"^0.22.13","@types/pg":"^8.6.4","typescript":"^4.6.2","@types/jest":"^27.4.1","@types/node":"^17.0.5","@types/uuid":"^8.3.4","lint-staged":"^12.3.5","@balena/lint":"^6.2.0","@types/semver":"^7.3.9","@types/bluebird":"^3.5.36","@types/traverse":"^0.6.32","@types/pg-format":"^1.0.2","simple-git-hooks":"^2.7.0","@types/redis-mock":"^0.17.1","@types/json-schema":"^7.0.9","@json-schema-org/tests":"^1.0.0","@balena/jellyfish-types":"^2.0.0","@balena/jellyfish-config":"^2.0.2","@balena/jellyfish-jellyscript":"^6.0.0"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/jellyfish-core_16.0.14-renovate-major-external-major-84ac7e3cadfc8d0fb229b0a916aef804637c15f4_1647310041883_0.5218288460231078","host":"s3://npm-registry-packages"},"deprecated":"Deprecated: no longer maintained. The GitHub repository has been archived and no further releases will be published."}},"time":{"created":"2020-08-28T06:31:49.054Z","modified":"2026-01-29T08:59:55.814Z","0.0.2-copy-over-code-f165a242db858fecd4814346025bb67ef774ab89":"2020-08-28T06:31:49.383Z","0.0.2-add-githook-8837f9d46864840a697c34b473fc6d07f8ba9654":"2020-08-28T06:39:17.827Z","0.0.2":"2020-08-28T06:43:56.263Z","0.0.3-add-ci-docker-f76696aeb492c6d093a89bc98455d61111a0c830":"2020-08-28T09:55:59.897Z","0.0.3-add-ci-docker-a3de8f7a9fc811d89948403e67af6a94b82a205b":"2020-08-28T13:51:35.005Z","0.0.3-add-ci-docker-f3b4985112a0517400b1e8b0917723da95ac2592":"2020-08-28T14:32:09.995Z","0.0.3-add-ci-docker-08bb1326f796ea7110439cacd36cefdad2ee61bb":"2020-08-29T23:37:38.202Z","0.0.3-add-ci-docker-98834709dc290f61db6907bb807aa5cb113b32b9":"2020-08-30T00:00:12.163Z","0.0.3-add-ci-docker-9e89f292e448e38f06cc035af08c974f255610cb":"2020-08-30T00:10:21.261Z","0.0.3":"2020-08-30T00:21:03.450Z","0.0.4-fix-redis-dependency-a76acc0aeabfce97b9440d4e7000846a4c4f73ab":"2020-08-30T01:40:58.731Z","0.0.4":"2020-08-30T01:43:44.303Z","0.0.5-set-commit-body-56baedd3e49aee5a373ff6bdd7b203b13945b386":"2020-08-31T07:00:42.626Z","0.0.5":"2020-08-31T07:03:52.350Z","0.0.6-add-jellycheck-43221b07284aa7ec7008df8226b8c5712ca73459":"2020-09-01T08:56:31.204Z","0.0.6-renovate-non-major-1b3799faef5bb054113ba6a8c4dfd1dda5915796":"2020-09-01T10:11:31.018Z","0.0.6":"2020-09-01T10:14:05.492Z","0.0.7-renovate-non-major-ad4b8ebc0bc4daea85c88c525ea43da91437b8d6":"2020-09-01T11:52:33.040Z","0.0.7":"2020-09-01T11:56:27.652Z","0.0.8-renovate-non-major-0a840d11a9574d544e2b8f359fec976a7bd6627e":"2020-09-01T13:50:55.231Z","0.0.8":"2020-09-01T13:53:45.372Z","0.0.9-renovate-non-major-6a60997e16204e71ee00a5272cbd52eb96017474":"2020-09-01T13:58:29.524Z","0.0.9":"2020-09-01T14:01:43.010Z","0.0.10-renovate-non-major-6dfb53d1f613f403274f16e8b988ad00391dcedd":"2020-09-01T20:23:25.522Z","0.0.10":"2020-09-01T20:26:15.528Z","0.0.11-renovate-non-major-8b0d6f20c440a7d91ed8aab51b86bb51a8428197":"2020-09-01T21:58:59.087Z","0.0.11":"2020-09-01T22:01:45.972Z","0.0.12-renovate-non-major-e81ee1092a5bd250015401b029716442ec40e7ee":"2020-09-01T23:18:03.404Z","0.0.12":"2020-09-01T23:20:57.461Z","0.0.13-renovate-non-major-9c857d267f90a66ccb25dc837f2a3f5f92849882":"2020-09-02T00:30:36.359Z","0.0.13":"2020-09-02T00:33:50.623Z","0.0.14-renovate-non-major-7ef385285db8bc28de10ef31a75fca29fcad741e":"2020-09-02T04:27:08.060Z","0.0.14":"2020-09-02T04:32:38.181Z","0.0.15-renovate-non-major-dd3935d5bc985f26507f941f06138fd741a51db1":"2020-09-02T04:37:27.656Z","0.0.15":"2020-09-02T04:40:04.367Z","0.0.16-renovate-non-major-4cf9d16ae6508ad7c8b141473330c191ab461504":"2020-09-03T00:44:50.915Z","0.0.16-renovate-non-major-1821a7e1298fc5302a138ce8fbd8f9f6b510d705":"2020-09-03T00:49:25.562Z","0.0.16":"2020-09-03T00:56:05.414Z","0.0.17-renovate-non-major-0a8b11f1c394d1dd209eafe1fbcab8516c5619fa":"2020-09-03T01:57:11.775Z","0.0.16-update-code-f6755339893fc3a6d2a344ff5e67491253e8a3a9":"2020-09-03T02:03:03.568Z","0.0.17-update-code-12fa2d531b476a6764a1b312a5378510ceac07dd":"2020-09-03T02:08:45.724Z","0.0.17":"2020-09-03T02:15:18.210Z","0.0.17-renovate-non-major-b1a2d3c9e370c8a14af54416091a0c2228bf6792":"2020-09-03T02:18:30.990Z","0.0.18-renovate-non-major-8104568bb18b997bd80be456345d7af864622647":"2020-09-03T02:20:21.851Z","0.0.18":"2020-09-03T02:23:22.099Z","0.0.19-renovate-non-major-383be0a83c7b45fc1b60d21f3b089bbd4c2b6ece":"2020-09-03T09:09:09.618Z","0.0.19-renovate-non-major-a85355dcadbef6f171e3c8378b0fd48aafe4d8aa":"2020-09-03T22:09:39.269Z","0.0.19-renovate-non-major-6a07965026ae42bda3c8c917c3b2473f4858de3b":"2020-09-03T22:14:03.739Z","0.0.19":"2020-09-03T22:16:45.322Z","0.0.20-undefined-linkedcards-5a69a2c9ce2704f329d3aad206b4e6134f4b8906":"2020-09-04T14:13:44.351Z","0.0.20-renovate-non-major-1b777b33da0f890b673ceacf194cc04aadd2bcfd":"2020-09-04T14:16:29.051Z","0.0.20":"2020-09-04T14:19:37.058Z","0.0.21-undefined-linkedcards-39fc59c5aa53bcf246d49e963e0ddfd68a1ea87a":"2020-09-04T14:24:55.988Z","0.0.20-undefined-linkedcards-102f8c14e81b8a95caa479af7160c590d9396293":"2020-09-04T14:36:19.539Z","0.0.21-undefined-linkedcards-456b9e574caee63acdb445b0fccb2bb4c9e294a5":"2020-09-04T14:42:45.000Z","0.0.21-undefined-linkedcards-e9452c2601d1c3e61a23200f4f3f9bab7a092394":"2020-09-04T14:54:02.768Z","0.0.21-undefined-linkedcards-cc7a7c53517793431daf4f0ccdaf0879f99c320a":"2020-09-04T15:10:04.415Z","0.0.21":"2020-09-04T15:13:47.836Z","0.0.22-handle-null-linked-cards-c3f52127c0b3541ff83f34798e489d0ebc196017":"2020-09-04T18:17:31.896Z","0.0.22":"2020-09-04T18:20:26.058Z","0.0.23-renovate-non-major-ce150eda592c3fa5c75433d3bce4304c17028a81":"2020-09-05T02:58:32.207Z","0.0.23":"2020-09-05T03:01:18.998Z","0.0.24-update-make-test-9cea8f690b8621f00ad9c56e995bc53f16fa9855":"2020-09-05T03:22:57.170Z","0.0.24":"2020-09-05T03:25:45.344Z","0.0.25-renovate-non-major-f8f48dfb9b9ad904804feb11f52bcb9d74c19dbf":"2020-09-05T03:31:20.275Z","0.0.25":"2020-09-05T03:34:06.532Z","0.0.26-renovate-non-major-1759d8db7c7e551bd6c9534f77f087f5c76afc85":"2020-09-05T03:37:51.115Z","0.0.26":"2020-09-05T03:41:39.219Z","0.0.27-renovate-non-major-c0287c1a6e8058d4b43b54c707910ba6d9405789":"2020-09-06T03:41:22.538Z","0.0.27":"2020-09-06T03:46:33.540Z","0.0.28-renovate-non-major-41fb7102f0db8c985a1b163dca11ab32da857958":"2020-09-06T07:54:59.226Z","0.0.28-renovate-non-major-1b7f66a05a5386065a1541e7ea56435849296bee":"2020-09-06T07:59:42.524Z","0.0.28-renovate-non-major-24bb63f19a49b17d820909041bcf9ac79d539653":"2020-09-06T08:03:32.037Z","0.0.28":"2020-09-06T08:09:07.631Z","0.0.29-renovate-non-major-c9b4c77f125ad4ae1a9a0c11c512acbb46ab7cd2":"2020-09-06T09:20:03.114Z","0.0.29":"2020-09-06T09:35:16.999Z","0.0.30-remove-node10-tests-fdeacb1241b21559b3a5666c81203d393973f396":"2020-09-06T09:40:53.666Z","0.0.30-renovate-non-major-5ff13c2e5ce5479be37832aadeeda1926b95bde6":"2020-09-06T09:41:52.692Z","0.0.30":"2020-09-06T09:51:38.594Z","0.0.31-renovate-non-major-ba748de93ebb9744473537579483b07911e7db96":"2020-09-06T09:56:08.926Z","0.0.31":"2020-09-06T10:02:07.436Z","0.0.32-renovate-non-major-50d1789acd494e0f7c8ec40dbfe64cbfcbb00fa8":"2020-09-06T11:56:31.891Z","0.0.32":"2020-09-06T12:01:57.766Z","0.0.32-renovate-non-major-243a8a281c09c1300b45231be7a6ede746a2fa8a":"2020-09-06T12:04:51.476Z","0.0.33-renovate-non-major-8230f59b4a27cefad8b0bb2c7f6e999a4641783b":"2020-09-06T12:06:29.406Z","0.0.33":"2020-09-06T12:12:06.053Z","0.0.34-renovate-non-major-38dcd7a9d93f9a020cff9d41469987ccdf9b5a1b":"2020-09-07T10:15:00.635Z","0.0.34":"2020-09-07T10:25:15.829Z","0.0.35-renovate-non-major-2a763789aa7df6a9777dc6855613dc5f3c1116fb":"2020-09-07T10:30:03.733Z","0.0.35":"2020-09-07T10:35:32.271Z","0.0.36-renovate-non-major-fe026cc9d1bcb85ceac5738f8754e36544227bbc":"2020-09-07T13:14:07.712Z","0.0.36":"2020-09-07T13:19:44.434Z","0.0.37-renovate-non-major-43863c5967f4ce783ae1b10c05f0a7a585846f11":"2020-09-09T01:54:54.183Z","0.0.37":"2020-09-09T02:01:30.682Z","0.0.38-renovate-non-major-a3a12a7332dba2b5cb1e51e1825300f3dd81346a":"2020-09-09T02:04:31.292Z","0.1.0-ui-schema-updates-6c1575f57db4e239d6cd8081ad472c4fd8ecf25a":"2020-09-09T10:15:06.645Z","0.1.0-renovate-non-major-4b7e9c63b5ee20ce7c49420380c235ea506e6af8":"2020-09-10T01:28:00.297Z","0.1.0":"2020-09-10T01:33:42.129Z","0.1.1-renovate-non-major-b08e4c69de89f5b48eadd1f25ebe502db817781a":"2020-09-10T01:38:33.091Z","0.1.1":"2020-09-10T01:45:35.472Z","0.1.2-renovate-non-major-64ddd1f472ce2867b4d9f1f0855a13c08c5dedb6":"2020-09-10T02:49:17.656Z","0.1.2-renovate-non-major-5bab7a162d0f90ea25bdb66b79307a125707a59e":"2020-09-10T09:00:24.682Z","0.1.2-npm-engine-strict-3118d4f4165cbaf41f81882b980c075b6c59c146":"2020-09-10T09:27:54.222Z","0.1.2":"2020-09-10T11:14:03.376Z","0.1.3-renovate-non-major-a80fda08f499bce04e8a0a9ece01a369d311c5ae":"2020-09-10T11:18:35.513Z","0.1.3-renovate-non-major-645cc3994b57b9dbe4cddb787a08660491fdc35d":"2020-09-10T13:39:52.144Z","0.1.3":"2020-09-10T13:47:38.505Z","0.1.4-renovate-non-major-0afee84544e3b21db88bf9dea45666986aa7ab30":"2020-09-11T18:36:08.421Z","0.1.4-renovate-non-major-d0869340eb12003a1093273ad6f5ce1abdd5a78d":"2020-09-11T18:45:00.333Z","0.1.4-renovate-non-major-50f9f1c7386ff2886ea67bbbd085ead49773377a":"2020-09-11T18:50:27.516Z","0.1.4":"2020-09-11T18:55:37.518Z","0.1.5-renovate-non-major-cddb6b3d596f8beb5730a1a3967d263196339a99":"2020-09-12T11:26:03.995Z","0.1.5-renovate-non-major-14997318e656cd14aaf0d47018294b7459f025d9":"2020-09-12T11:35:33.856Z","0.1.5-renovate-non-major-f79fc2d2c7d158e3a329636b9250268043be8025":"2020-09-12T11:39:34.115Z","0.1.5":"2020-09-12T11:44:47.951Z","0.1.6-renovate-non-major-5497ad084d6a8d1ecb7168474f46d832401ec1bc":"2020-09-12T20:33:51.476Z","0.1.6-renovate-non-major-113a6525cc9961aa2bae8d8bbed2f6a664b379bc":"2020-09-12T20:37:42.714Z","0.1.6":"2020-09-12T20:44:27.693Z","0.1.7-renovate-non-major-f05b534c6f73a9546bd1922ce7183cf84504bd66":"2020-09-13T10:38:45.192Z","0.1.7-renovate-non-major-5b10357906701c3963883599adc20e2db952e741":"2020-09-13T11:55:08.316Z","0.1.7":"2020-09-13T12:01:20.035Z","0.1.8-update-uischema-1-7a14bd8c5d6324b09ebeda46c54c4672d221341a":"2020-09-14T02:56:31.738Z","0.1.8":"2020-09-14T03:01:46.725Z","0.1.9-renovate-non-major-4bf69e7929fcc3fb199b6e9a06944a86c64acb6a":"2020-09-14T08:10:32.248Z","0.1.9":"2020-09-14T08:22:12.168Z","0.1.10-renovate-non-major-8765ac5670d213cb724353c1bacd238a82316671":"2020-09-14T08:31:01.536Z","0.1.10-renovate-non-major-8dc04f72539949be88d855c3ef4a51c234aeb362":"2020-09-14T08:35:50.723Z","0.1.10":"2020-09-14T08:40:16.884Z","0.1.11-renovate-non-major-cb28ff3685a1fb2451f0323019659e4046128851":"2020-09-14T08:45:38.402Z","0.1.11":"2020-09-14T08:50:54.646Z","0.1.12-renovate-non-major-285c372d10a3667051538bda3850e5519e91c920":"2020-09-15T04:00:31.238Z","0.1.12-renovate-non-major-92eff74d09b380e1f4cc97ba8820caf039d72ed8":"2020-09-15T04:07:22.397Z","0.1.12":"2020-09-15T04:10:26.410Z","0.1.13-renovate-non-major-2ec000929541e694e381fec4ffaa00ceb85501cc":"2020-09-15T05:18:16.704Z","0.1.13":"2020-09-15T05:24:18.745Z","0.1.14-merge-linked-views-in-sql-1f26a0a56f006c220991579e2c92f9fe61a3a72f":"2020-09-15T19:38:47.970Z","0.1.14":"2020-09-15T19:44:47.194Z","0.1.15-renovate-non-major-68af3185d1a90c3dc1f1fa57a50fd8f7fdbb4c7e":"2020-09-16T00:46:37.803Z","0.1.15":"2020-09-16T00:52:56.909Z","0.1.16-renovate-non-major-e67430db9919b6ab80c17fe772b3d9ac0ff2c63d":"2020-09-16T12:46:24.101Z","0.1.16":"2020-09-16T12:51:57.770Z","0.1.17-renovate-non-major-818e59d98284fd3fff140c3dcb9ba9c223e17a8a":"2020-09-16T13:29:58.416Z","0.1.17":"2020-09-16T13:36:39.817Z","0.1.18-renovate-non-major-9b552606404c9cd4a48415a8eb0fbff07416f8c6":"2020-09-16T13:40:11.609Z","0.1.18":"2020-09-16T13:47:49.767Z","0.1.19-renovate-non-major-59e3431ef2e65694e2030f90c14a6b7ab29ce083":"2020-09-16T13:53:00.809Z","0.1.19-renovate-non-major-b23c11cbf3c1af0a4fa8fd5484a22f5b98c358be":"2020-09-16T13:57:06.387Z","0.1.19":"2020-09-16T14:02:53.692Z","0.1.20-renovate-non-major-416c112ba5db55c9745f5e7f08b86a32c2b03a07":"2020-09-16T14:07:41.745Z","0.1.20":"2020-09-16T14:12:45.706Z","0.1.21-renovate-non-major-2802d5cc4fb1bd4c771a47450d0c2c5f6e54bab8":"2020-09-16T17:18:41.415Z","0.1.21":"2020-09-16T17:24:18.054Z","0.1.22-renovate-non-major-554fe25b02a6f53b8973694664921a45dcf1f80e":"2020-09-16T17:27:56.327Z","0.1.22":"2020-09-16T17:34:31.595Z","0.1.23-renovate-non-major-9d84b649cf790eb1cea926956a9d33497ea1d80e":"2020-09-16T19:16:09.713Z","0.1.23":"2020-09-16T19:23:18.805Z","0.1.24-renovate-non-major-3ecc8b15081cbbe7859f9d31c93204dbd5ffeae6":"2020-09-18T09:51:50.537Z","0.1.24":"2020-09-18T10:29:15.978Z","0.1.25-renovate-non-major-4f61ad9b9f135c216bbe0d34a06b411fbd004ebf":"2020-09-18T11:47:37.604Z","0.1.25":"2020-09-18T12:33:00.786Z","0.1.26-renovate-non-major-1a25cb64ad43f10756cf40fad354491f4a6e9907":"2020-09-18T14:49:05.080Z","0.1.26":"2020-09-18T14:54:34.763Z","0.1.27-renovate-non-major-b1429d5fa1359ab916ee4d4bdcf3cd96d943a47f":"2020-09-18T15:52:37.752Z","0.1.27":"2020-09-18T15:58:15.815Z","0.1.28-renovate-non-major-604d11e3380052f47c0388a78b879853d641e429":"2020-09-18T21:19:20.142Z","0.1.28":"2020-09-18T21:24:53.792Z","0.1.29-renovate-non-major-c23c318509b6e745c05483ae6454d148a6367aa4":"2020-09-18T22:59:04.518Z","0.1.29":"2020-09-18T23:04:33.164Z","0.1.30-renovate-non-major-d3de771361acd2e6cc1c7e2e501c5a26a0b92d04":"2020-09-20T11:24:19.650Z","0.1.30-renovate-non-major-19de51434796f1ede49660d977cf38bd6b3388ab":"2020-09-20T12:47:08.334Z","0.1.30":"2020-09-20T12:52:28.530Z","0.1.31-fix-aggressive-optimization-27ef63afe853d19888332aeac9e8501e6af4c853":"2020-09-21T17:12:57.542Z","0.1.31":"2020-09-21T17:18:20.034Z","0.1.32-renovate-non-major-e36671af037db062e2b16dc25a55126ebe33d608":"2020-09-22T08:57:43.426Z","0.1.32":"2020-09-22T09:05:00.662Z","0.1.33-renovate-non-major-649c3c8750033cd4d47594e9a209a2b1601bac0d":"2020-09-25T11:55:42.808Z","0.1.33-renovate-non-major-e9a41b9c2f9476ac115f14c7f06107e678861659":"2020-09-25T12:05:14.620Z","0.1.33-renovate-non-major-ad37a944682d7161109dea9c975415af8de3dd68":"2020-09-25T13:53:18.555Z","0.1.33":"2020-09-25T13:58:27.557Z","0.1.34-renovate-non-major-943bed3961221b42006379bedbc4850bdf51a30a":"2020-09-26T06:28:30.390Z","0.1.34":"2020-09-26T06:34:14.409Z","0.1.35-renovate-non-major-dc237afc47bb69a5fe27e3060d61431164f18fca":"2020-09-26T21:57:16.789Z","0.1.35-renovate-non-major-66a7ff4b48350e3c5669c604b6b124ce027113c0":"2020-09-26T22:01:48.452Z","0.1.35-renovate-non-major-a97a3885c640cafd6ee91e746cdd9adeb9abf655":"2020-09-26T22:05:48.212Z","0.1.35-renovate-non-major-a4d4b1a053a58d44ee53f5b91bc2e2e9277dda48":"2020-09-27T03:12:32.572Z","0.1.35-renovate-non-major-4b7e37d97466513f668da07c53c47523cc760470":"2020-09-27T03:22:50.388Z","0.1.35-renovate-non-major-41ab07d86d3cee7ff8c54ec3bd5d04e1b5de5c94":"2020-09-27T03:27:10.973Z","0.1.35-renovate-non-major-2e9e60b2fe98297d9196f1f4372d275a153acafc":"2020-09-27T03:32:40.886Z","0.1.35":"2020-09-27T03:41:34.325Z","0.1.36-renovate-non-major-4b0b8c3c5c16e380e8f7e28200ddd34898c13e6e":"2020-09-27T05:00:53.025Z","0.1.36":"2020-09-27T05:07:53.358Z","0.1.37-renovate-non-major-30c146ec0129802f711517c80c71e63ec0988bb5":"2020-09-27T05:10:41.248Z","0.1.37":"2020-09-27T05:18:19.504Z","0.1.38-renovate-non-major-9596fe5007cc93a790664f7ed5c8345f398fafbe":"2020-09-27T11:48:22.925Z","0.1.38-renovate-non-major-29293991cfb62a288d9c411f7f49800087ec79f2":"2020-09-27T11:57:56.418Z","0.1.38-renovate-non-major-2c84d4b8a42f715d804985071a3c3ddcfcb5b480":"2020-09-27T12:02:43.818Z","0.1.38":"2020-09-27T12:08:32.864Z","0.1.39-renovate-non-major-8bdddef9ce860e46193ee783888384a300eaa0e3":"2020-09-27T13:28:52.047Z","0.1.39":"2020-09-27T13:34:19.021Z","0.1.40-renovate-non-major-d96f66fa2dfca9349c37bf046ae781c39d671931":"2020-09-27T14:25:22.479Z","0.1.40":"2020-09-27T14:30:47.231Z","0.1.41-renovate-non-major-49b0c89257c55437c2a93a34ca5789c8adaf33f2":"2020-09-27T15:46:29.312Z","0.1.41-renovate-non-major-b25b39f16501d6342e6ec9efd9a23d2d9f996b8a":"2020-09-27T16:25:42.835Z","0.1.41":"2020-09-27T16:31:41.711Z","0.1.42-renovate-non-major-e18af3c425d27a8331a06ba54b1c55bb40b63717":"2020-09-27T17:34:10.688Z","0.1.42":"2020-09-27T17:40:00.654Z","0.1.43-renovate-non-major-b2007c977adb1df6a6128f9d9e328ae5a7236682":"2020-09-28T01:45:26.237Z","0.1.43":"2020-09-28T01:53:02.403Z","0.1.43-renovate-non-major-e377390138f425486f7c1855ca8cf3944072d003":"2020-09-28T01:54:19.552Z","0.1.44-renovate-non-major-40942da98f56c1ca7b950fdce940452c193a5e5a":"2020-09-28T01:58:07.407Z","0.1.44-renovate-non-major-4969d290c42b523d766d7aa1d1955de3f22f42a1":"2020-09-28T02:02:02.069Z","0.1.44":"2020-09-28T02:07:54.177Z","0.1.45-renovate-non-major-7962407aae3bc297012febc7b925050718003c41":"2020-09-28T03:54:02.286Z","0.1.45":"2020-09-28T04:00:08.323Z","0.1.46-renovate-non-major-7121a4183a84a0d39b6cbbe4e572667ad7c58061":"2020-09-28T13:08:05.356Z","0.1.46":"2020-09-28T13:17:36.898Z","0.1.47-renovate-non-major-ee1871bf227e80b820f7315005196d0664c3c166":"2020-09-28T13:28:28.106Z","0.1.47":"2020-09-28T13:33:50.495Z","0.1.48-renovate-non-major-47f420bdea35092dda57d191015af0d1d91fa832":"2020-09-28T16:11:42.410Z","0.1.48":"2020-09-29T00:39:08.019Z","0.1.49-renovate-non-major-ff46d2f62e8aa7c236bd499f203c935a6fa046eb":"2020-09-29T00:42:35.207Z","0.1.49":"2020-09-29T00:50:55.973Z","0.1.50-renovate-non-major-d318a07668f91e863b2a787ae7d1f8cb863d7781":"2020-09-29T16:23:23.307Z","0.1.50":"2020-09-29T16:28:47.708Z","0.1.51-renovate-non-major-8789a58fd39d39845900c66bab9fe01a57ae786b":"2020-09-29T18:12:47.084Z","0.1.51":"2020-09-29T18:20:18.333Z","0.1.52-renovate-non-major-7f4b7be070a6b3d3ccc61944252ca563f93efb79":"2020-09-30T15:31:54.432Z","0.1.52-renovate-non-major-e2379e62145bc941ae8ab074d8466fff15e17e24":"2020-10-01T01:13:08.299Z","0.1.52":"2020-10-01T01:19:20.997Z","0.1.53-renovate-non-major-5c81eb77e058ffa589d07de90b29f85b1246c12e":"2020-10-02T19:45:31.979Z","0.1.53":"2020-10-02T19:51:44.971Z","0.1.54-renovate-non-major-74089117770574411dec27fe2bda0bafab0da4fd":"2020-10-02T21:53:24.185Z","0.1.54":"2020-10-02T22:00:46.201Z","0.1.55-renovate-non-major-59e9efbe1e4608b34003e05b1421a1391c02f19f":"2020-10-04T09:55:59.843Z","0.1.55":"2020-10-04T10:02:35.887Z","0.1.56-renovate-non-major-9c8b905dbcde7c9b8fc3ea999f4de1835343f539":"2020-10-04T15:59:53.573Z","0.1.56":"2020-10-04T16:05:16.678Z","0.1.57-renovate-non-major-8c8433015e8fb7167ae953101905233168790417":"2020-10-05T13:13:52.359Z","0.1.57":"2020-10-05T13:19:16.330Z","0.1.58-renovate-non-major-1166d51ef679495841e66d35a7c038e80e50a166":"2020-10-05T15:28:44.745Z","0.1.58":"2020-10-05T15:35:52.741Z","0.1.59-renovate-non-major-41be2e4304bda9b1abcb583a1131b13f94539d62":"2020-10-06T14:55:36.381Z","0.1.59-renovate-non-major-84d3dc333cc61bc2ecdb8113f6371771be102794":"2020-10-06T22:29:37.525Z","0.1.59":"2020-10-06T22:35:00.833Z","0.1.60-renovate-non-major-efe5c29c089d5b0bea5a48593ed4596181adfd47":"2020-10-07T10:05:03.392Z","0.1.60":"2020-10-07T10:11:16.563Z","0.1.61-renovate-non-major-d38a7dff79dc40da5f3ef9161b4a0b7310553332":"2020-10-08T03:18:41.321Z","0.1.61":"2020-10-08T03:24:15.048Z","0.1.62-renovate-non-major-5fe46f4759e00c7e2007aea06c2e1eb5695f543b":"2020-10-09T02:34:39.993Z","0.1.62-renovate-non-major-bc212dd423a646371fd54a2a1da69dab75dc672f":"2020-10-09T03:11:38.911Z","0.1.62":"2020-10-09T03:19:27.011Z","0.1.63-renovate-non-major-b960ba8a60611d0fcee505d1b3560df7c73ff779":"2020-10-09T03:23:38.028Z","0.1.63-renovate-non-major-274660a0361a13969acca336e8c8ccabb7693dd1":"2020-10-09T04:55:43.206Z","0.1.63-renovate-non-major-d2fdd7e139e213d2c321a3845df7bbd7f48844ca":"2020-10-09T21:05:17.863Z","0.1.63":"2020-10-09T21:11:17.654Z","0.1.64-renovate-non-major-59ee1c5cc1657abca31f89f6914f35c866bc88e0":"2020-10-09T21:14:17.466Z","0.1.64":"2020-10-09T21:21:57.966Z","0.1.65-renovate-non-major-2278a485de86bd5e882daebfb5bb6fc168c1e2b2":"2020-10-09T21:27:12.561Z","0.1.65":"2020-10-09T21:33:27.064Z","0.1.65-renovate-non-major-cf5b80eaf983a10ad37c46a285685bbeb5c2adca":"2020-10-09T21:36:36.457Z","0.1.66-renovate-non-major-d7f3d4b8f6caa2ec8a990e7914b6c2d3e8bcb2db":"2020-10-09T22:36:17.423Z","0.1.66":"2020-10-09T22:43:30.697Z","0.1.67-renovate-non-major-5f39d9242f194eab2ea5e96faa33035c0e1e8bf5":"2020-10-09T23:49:50.983Z","0.1.67":"2020-10-09T23:55:25.747Z","0.1.68-renovate-non-major-73cf6c6dc1dc7d2ca8583cdec0abf5e75890e692":"2020-10-12T17:51:08.205Z","0.1.68-renovate-non-major-daf6830d6f799edaae398c0b4e0c6c4e4d4c041f":"2020-10-12T18:46:31.481Z","0.1.68":"2020-10-12T18:52:02.987Z","0.1.69-renovate-non-major-05ac715ee9813a6051071e744087bc453b8e2d0a":"2020-10-14T00:39:57.608Z","0.1.69-renovate-non-major-d7677bbd9e2bce8330dd5242a95faf5fc6b2741d":"2020-10-14T00:49:49.711Z","0.1.69-renovate-non-major-ab5c220bd164607a6ed61b2b6acdba33bfb6fe30":"2020-10-14T00:56:03.936Z","0.1.69":"2020-10-14T01:02:04.728Z","0.1.70-renovate-non-major-ec027072b732c206708589e05c19eb595b8a70a1":"2020-10-14T01:05:22.246Z","0.1.70":"2020-10-14T01:12:59.480Z","0.1.71-renovate-non-major-b8fb77d28618cb72368d4bd31e9941c30e712ac1":"2020-10-14T02:35:36.739Z","0.1.71":"2020-10-14T02:43:39.287Z","0.1.72-snippet-ui-schema-395df86551c95026caedd374443dcf11ab58a269":"2020-10-16T01:49:26.001Z","0.1.72":"2020-10-16T01:57:21.125Z","0.1.73-remove-default-snippet-fcc9da54f17e22b9ca739acedb6faf664677cf04":"2020-10-16T03:21:37.777Z","0.1.73":"2020-10-16T03:31:30.085Z","0.1.74-ui-schema-def-reset-3730ceecf319a8826aa79b51b7366fa6c1708a2d":"2020-10-16T08:44:49.959Z","0.1.74":"2020-10-16T09:12:23.762Z","0.1.75-renovate-non-major-a2a71e2e3da1a9975088d36fbe9cfc11c462be5b":"2020-10-16T13:42:43.834Z","0.1.75-renovate-non-major-eff0cf7723a990bb664e2e1f9a99375ddbce7b2a":"2020-10-18T08:44:07.494Z","0.1.75-bump-test-image-dc6cfbb48b6f0f2522a9c64a2989ed884cfcffe8":"2020-10-18T09:28:14.141Z","0.1.75":"2020-10-18T09:35:18.496Z","0.1.76-renovate-non-major-1485517122ecfce366e7eb08d6591ddbebc2aa8e":"2020-10-19T14:46:44.181Z","0.1.76-renovate-non-major-44e1d3365aed19a760e70695559a6259555def7b":"2020-10-19T15:10:49.967Z","0.1.76-renovate-non-major-82a1924a83f5d5930e004ea6a14ef8c9514ed6fb":"2020-10-19T16:10:59.713Z","0.1.76-optional-links-aware-expression-filter-4b1e23c68bd106a5a19120e732e8e3c20366a1c9":"2020-10-21T21:26:37.716Z","0.1.76":"2020-10-21T21:32:12.602Z","0.1.77-renovate-non-major-f3bdbafe44d4f2ca67a216cafe065e3f54b87070":"2020-10-22T00:22:11.475Z","0.1.77-renovate-eslint-config-standard-15-x-d7cb185b1c1d019883b1cbf32405d10100dbcab2":"2020-10-22T02:16:24.859Z","0.1.77-renovate-eslint-config-standard-15-x-d34a1d1cf0c1064c4b2c18bc08e4217bb720c3b8":"2020-10-23T01:38:24.504Z","0.1.77-renovate-non-major-20ac05640a4b87f03e3c72c9adf3e152bfc8f383":"2020-10-24T02:54:33.375Z","0.1.77-renovate-non-major-cbecb048b86f8d38724024b6b805d28dbeb435f2":"2020-10-24T22:52:01.138Z","0.1.77-renovate-non-major-e0811d5fa4166cab39a5493774901263082dc691":"2020-10-26T18:20:38.546Z","0.1.77-renovate-non-major-60e09b0e459831b7e7085d04a4053f1e8db8f5c1":"2020-10-27T04:51:13.921Z","0.1.77-renovate-non-major-46be5ea55c8fe668a65f2aa47f3acf556a0c133f":"2020-10-28T04:25:30.748Z","0.1.77-renovate-non-major-60790510447e728235c6381046c687b4ed759361":"2020-10-28T05:29:13.541Z","0.1.77-renovate-non-major-154cbc01a07110a307cc18563e7a4b019bf220b5":"2020-10-28T09:49:43.139Z","0.1.77-renovate-pruneStaleBranches-88159df60fba9ff483c5ae1a438fbf9392fce800":"2020-10-28T11:58:49.457Z","0.1.77":"2020-10-28T12:08:57.428Z","0.1.78-renovate-non-major-62530994013102770bac423aa96bb2710fceffb1":"2020-10-28T12:19:33.457Z","0.1.78-renovate-non-major-ca25894325e5bc71e4305dbc12557ffe15a2ccc2":"2020-10-28T13:13:48.333Z","0.1.78-renovate-non-major-21224f99d207a580b4fb93dd24633998e39b66f0":"2020-10-28T14:23:12.160Z","0.1.78-renovate-non-major-1e56df2dac974197ce0c036850ce4d30dbe22007":"2020-10-28T20:13:40.130Z","0.1.78-renovate-non-major-913a6e82a62b4559f3f9f23b5034de3840b4988c":"2020-10-28T21:44:18.319Z","0.1.78-renovate-non-major-4cf1bff3f0eea83ce88be237f463b6adda757f62":"2020-10-28T21:49:08.623Z","0.1.78-bump-dependencies-manually-ab3907691d621f571c2580fbf6adc7e6843a3d13":"2020-10-29T05:33:57.653Z","0.1.78":"2020-10-29T05:40:04.413Z","0.1.79-renovate-eslint-config-standard-16-x-8acc12ba8d8389bce749d22f89a273157b32e18a":"2020-10-29T06:28:49.572Z","0.1.79-renovate-non-major-5612d24ae09ab06076eff2f9c796b00f14087847":"2020-10-29T12:15:20.108Z","0.1.79-renovate-non-major-7308e2f4e33008a9bbe6a4604e440816bda5c2eb":"2020-10-29T13:31:35.368Z","0.1.79-renovate-json-schema-deref-sync-digest-9a9e5bc797c16478901cb3a92af78c3faa0fb157":"2020-10-29T16:56:14.644Z","0.1.79-json-schema-deref-sync-update-54e0215db3acd9d4d443e94393c71330f5560077":"2020-10-30T00:45:15.600Z","0.1.79":"2020-10-30T09:53:25.191Z","0.1.80-renovate-non-major-785eb4705430641250374cff1afa88635f737440":"2020-10-30T09:56:49.849Z","0.1.80-renovate-eslint-config-standard-16-x-b8cbafc1ff3198fe1254ee70d01d60dfbc051f9a":"2020-10-30T09:58:22.725Z","0.1.80-renovate-non-major-e63bf3207c7329bbb30aca81eea1e235aadb4b16":"2020-10-30T10:00:14.347Z","0.1.80-renovate-non-major-b6f17c5f6ae7e5b1df18ee16a7ebf473ecabd56b":"2020-10-30T11:15:29.570Z","0.1.80-renovate-non-major-c1d68a73251f3b772eb620b926e3a4c568fb988c":"2020-10-30T12:41:48.709Z","0.1.80-renovate-eslint-config-standard-16-x-96460dde743ad5b62a11dfd63d6a9914dffb2955":"2020-10-30T21:24:15.659Z","0.1.80-renovate-non-major-d7e1720ff168c93df077f34b39c22d91f22519ee":"2020-10-31T06:26:34.762Z","0.1.80-renovate-non-major-e021f917708c8f98bb0b4a2276a8d6dc26bb4db2":"2020-10-31T06:46:02.791Z","0.1.80-renovate-non-major-19e89cc535401532152ac0a72efe21ceb157d989":"2020-10-31T07:59:09.470Z","0.1.80-renovate-non-major-4d0fa59c55056caadb2b9a3a0efdf92905abc7df":"2020-10-31T08:39:35.160Z","0.1.80-renovate-non-major-1fe01b352be9b032d27f3aa2f5fd0d60b0b1391f":"2020-10-31T10:33:19.059Z","0.1.80-renovate-non-major-ff2f5ee8e03ae98ab41443b1fa447cbcae0ed016":"2020-10-31T13:33:28.855Z","0.1.80-renovate-non-major-bdc1ce41558eb076e8916e7b3f6b0432b3672618":"2020-10-31T14:53:25.453Z","0.1.80-renovate-non-major-743e53c6d59f2f24d5ea9fbe9c8e2bc6b0604221":"2020-11-01T01:20:53.881Z","0.1.80-renovate-non-major-0141d2ecd76ff7cf73697b51811417d7dd85e972":"2020-11-01T02:50:44.891Z","0.1.80-renovate-non-major-b797e9c6d30d648b1561b06f4e47523c6586802e":"2020-11-01T03:36:52.888Z","0.1.80-renovate-non-major-15c237a220e7a709c5636a061f57ff9f6c94b75b":"2020-11-01T05:32:42.092Z","0.1.80-renovate-non-major-27a68c791ee7452142f1238526b844d688091684":"2020-11-01T06:32:51.626Z","0.1.80-renovate-non-major-7255989fb5d4c0aab85689d20e55348e9e63019f":"2020-11-01T12:59:12.125Z","0.1.80-renovate-non-major-e1241a2d7a5499e783dcd5554c12bc5c67641b77":"2020-11-01T15:41:05.140Z","0.1.80-bump-test-images-837f6efd8ff8f422a7b20625248a3e8b1ac67e29":"2020-11-02T01:06:59.149Z","0.1.80":"2020-11-02T01:13:25.234Z","0.1.81-renovate-non-major-8b5083a780eec87fd108eb659578ef89080fc04c":"2020-11-02T01:16:36.714Z","0.1.81-renovate-eslint-config-standard-16-x-7653594f93ade96b48f7a0d093c55b4110be7d18":"2020-11-02T01:17:53.072Z","0.1.81-bump-deps-e2ea4ae3fb67c2369e1bdd15f11e0047d88b242c":"2020-11-02T04:27:11.621Z","0.1.81-bump-deps-2026f3fc48590db56ed5a35bc6b198032b1785af":"2020-11-02T04:35:28.360Z","0.1.81-renovate-eslint-config-standard-16-x-154fdf1987f6805c4967ed9dde2be7f4bac9e54d":"2020-11-02T05:25:12.920Z","0.1.81-renovate-non-major-0f92595f33583d599a57240e0322e9e12aa9def5":"2020-11-02T05:27:23.196Z","0.1.81-renovate-non-major-b46dad1778668e77c06d4d85c77dce09d31bfc2e":"2020-11-02T05:30:44.118Z","0.1.81-renovate-non-major-fae9e47474311aed79685529112069ef0427f6da":"2020-11-02T06:26:55.113Z","0.1.81-renovate-non-major-970e0b4a31d5229baff426ce8f49819112531757":"2020-11-02T07:49:31.294Z","0.1.81-renovate-non-major-f830548ee7ed398d1e618d27bf03eae8beeff067":"2020-11-02T15:37:16.780Z","0.1.81-renovate-non-major-80e574110563b9116fd20fa7a3bd2611d0f8d29c":"2020-11-02T16:53:08.742Z","0.1.81-renovate-dockerfile-6367d8565cb4b772d7775fd4a69549eff1336e57":"2020-11-03T06:57:26.372Z","0.1.81":"2020-11-03T07:02:01.186Z","0.1.82-renovate-non-major-d7f201b13eef642ca58b657a5c20169fe894e400":"2020-11-03T07:06:15.012Z","0.1.82-renovate-docker-non-major-dfb90b49224f77770359dbb1137f33a38f5b4895":"2020-11-03T07:06:57.855Z","0.1.82":"2020-11-03T07:12:22.097Z","0.1.83-renovate-non-major-44e65721d77343b4b663c4e722143d591da96cbc":"2020-11-03T07:16:19.836Z","0.1.83-bump-deps-abdde24ea8e8e1643ccb65ed978a192945d9c23d":"2020-11-03T07:56:56.720Z","0.1.83":"2020-11-03T08:14:07.312Z","0.1.84-bump-eslint-config-standard-380d560bc564afdcd01cb60c0e3506370163bd5e":"2020-11-03T08:26:26.404Z","0.1.84":"2020-11-03T08:32:23.617Z","0.1.85-renovate-non-major-bd3983c3f83670dbbe4c8449094be38929874008":"2020-11-03T11:22:24.995Z","0.1.85-renovate-non-major-b5338d0a759297d94a15103ab0101b178b04c635":"2020-11-03T14:42:36.590Z","0.1.85-renovate-non-major-fd4c2827bd69fc1658382dacfd37b67fcdab08c0":"2020-11-03T15:47:47.679Z","0.1.85-renovate-docker-non-major-5f26ff7379a43113defba86f1c66f1da6144eae0":"2020-11-03T15:48:44.643Z","0.1.85-renovate-docker-non-major-8ebaef6ece0075eec3409e9358c4000df5a7ab84":"2020-11-04T02:54:37.412Z","0.1.85":"2020-11-04T02:59:12.193Z","0.1.86-renovate-non-major-6626df25123b7056c7619f1521d3b71078a0aa44":"2020-11-04T03:03:09.871Z","0.1.86-bump-dependencies-9fd94ff1fc00a15d0074515a216f1b7c6b36a792":"2020-11-04T06:41:51.471Z","0.1.86":"2020-11-04T06:50:53.712Z","0.1.87-renovate-non-major-01bac244fd6585b93f6d6be391d652b113c3ad25":"2020-11-04T08:13:45.764Z","0.1.87-renovate-non-major-6697444ce5ce815b5d437afbe50768457d5dae54":"2020-11-04T10:02:43.873Z","0.2.0-add-default-view-mode-to-card-a93691bc5b0ac9099d9407ec7d1f70a2f5610739":"2020-11-04T16:29:11.200Z","0.1.87-renovate-non-major-2d2f7ba8769e1c0230b7ffee3e8ab20916f5805b":"2020-11-04T18:11:27.742Z","0.1.87-bump-deps-5bbe368e1953f4730856b16b7fbea98e9eb3faaf":"2020-11-05T03:15:15.719Z","0.1.87":"2020-11-05T03:19:38.987Z","0.1.88-renovate-non-major-ef0074b11b4338711436853513a836bd05a0fa73":"2020-11-05T04:13:45.733Z","0.1.88-renovate-non-major-acb67743c07b14f8e0f4718abaa3ccaa75e40dfa":"2020-11-05T05:34:34.289Z","0.1.88-renovate-non-major-a171dc016bb41d0e0e8df47eba846fbb50b668fb":"2020-11-05T06:32:53.859Z","0.1.88-renovate-non-major-c504776912b3e63e3ac668761d5cbee056cc3bc2":"2020-11-05T19:33:34.821Z","0.1.88-force-renovate-node-version-f64339452ea92a55f9e6aa71cb1c615824acde9b":"2020-11-06T02:33:27.633Z","0.1.88-force-renovate-node-version-3e3b367d88853092c5cfd5328569799ab3db2c89":"2020-11-06T02:38:02.060Z","0.1.88":"2020-11-06T02:43:56.155Z","0.1.89-renovate-non-major-17136a725888846b436a39963fa32fc3bebcc618":"2020-11-06T02:47:41.486Z","0.1.89-renovate-non-major-0783d63886c03c6555685db01e3e31ed0dc89c0c":"2020-11-06T03:56:41.311Z","0.1.89":"2020-11-06T03:59:58.500Z","0.1.90-renovate-non-major-3a0040de3079102c210c76e1c6a99ffa3c100f13":"2020-11-06T05:33:13.706Z","0.1.90":"2020-11-06T05:39:10.041Z","0.1.91-renovate-non-major-82a096b4b4d6f8c2939bbc58ffee25c19651117e":"2020-11-06T06:19:17.069Z","0.1.91":"2020-11-06T06:24:24.075Z","0.1.92-renovate-non-major-7c25c128cf4855ff2a96f167edc5f6ff128ad41d":"2020-11-06T07:51:24.479Z","0.1.92":"2020-11-06T07:55:44.223Z","0.1.93-renovate-non-major-759447623379aa9b18e0f23823392ecb790ee30d":"2020-11-06T08:01:01.926Z","0.1.93":"2020-11-06T08:06:20.810Z","0.1.94-renovate-non-major-383640aa912406896884530f123f75ecdb7ab99b":"2020-11-06T09:34:39.399Z","0.1.94":"2020-11-06T09:40:34.256Z","0.1.95-add-to-and-from-slug-to-link-card-dcabfef5c1dadb3343cd61fccce89a2b9d814d46":"2020-11-06T17:50:29.141Z","0.1.95":"2020-11-06T18:00:32.765Z","0.1.96-renovate-non-major-ade9abac2b2e91d9d544340c4ac82fad198c5a22":"2020-11-07T02:52:12.196Z","0.1.96":"2020-11-07T02:56:41.814Z","0.1.97-renovate-non-major-464652bbb74a1f582dda649bc415749a9989f297":"2020-11-07T03:44:35.972Z","0.1.97":"2020-11-07T03:48:45.615Z","0.1.98-renovate-non-major-57f27ccab4a80c78eeb2eb4440816c3a89f3abc6":"2020-11-07T04:36:49.281Z","0.1.98":"2020-11-07T04:40:31.026Z","0.1.99-renovate-non-major-52e6b9c509ff9725abc0bcf176cb5b9048d4da60":"2020-11-09T17:18:58.392Z","0.1.99":"2020-11-09T17:25:44.426Z","0.1.100-renovate-non-major-f52c8153ff926745b35b01778f42d7551af39489":"2020-11-10T00:48:51.593Z","0.1.100-renovate-non-major-b24f91e9e87169156d5a8d568e31ba02837681f8":"2020-11-10T00:54:38.367Z","0.1.100-renovate-non-major-e165b0839d453c1537c4917b2a6ee6034c7484e0":"2020-11-10T00:59:09.989Z","0.1.100":"2020-11-10T01:03:47.159Z","0.1.101-renovate-non-major-d273d8586911171488cd362b617fe2f60c42b4cb":"2020-11-10T02:34:45.199Z","0.1.101":"2020-11-10T02:38:53.749Z","0.1.102-renovate-non-major-4e0bfcbd733187690e78eb227a704d5530d2cad3":"2020-11-11T07:59:29.488Z","0.1.102":"2020-11-11T08:04:36.948Z","0.1.103-ci-use-node-14-83cb196da00f385ab147fd969c2fce362048f92c":"2020-11-11T08:57:45.274Z","0.1.103":"2020-11-11T09:03:26.997Z","0.1.104-renovate-non-major-aa8ae5e0b06838d568ccc10e676da971bc639d73":"2020-11-11T11:11:40.443Z","0.1.104":"2020-11-11T11:16:19.007Z","0.1.105-renovate-non-major-58b3c24928389221120c5aa13acde163fbcc6aa4":"2020-11-12T01:25:35.839Z","0.1.105":"2020-11-12T01:30:50.435Z","0.1.105-renovate-non-major-b1ce6d778ce76a9d144e5c3184ae01b528b81dde":"2020-11-12T01:33:36.761Z","0.1.106-renovate-non-major-87129b12366c1527a83cbf5035bed254ccefd223":"2020-11-12T01:36:21.362Z","0.1.106-renovate-non-major-9d9219f5b98bbcd1034fee012e5a926213ff6eae":"2020-11-12T01:40:48.486Z","0.1.106":"2020-11-12T01:45:46.587Z","0.1.107-renovate-non-major-920241b5db99b37ed9d28d274dc35f8967cbcc54":"2020-11-12T01:48:46.775Z","0.1.107":"2020-11-12T01:58:23.149Z","0.1.108-require-node-14-da578b65cf05c8259cef19064a216d2b289b68e0":"2020-11-12T02:05:28.126Z","0.1.108":"2020-11-12T02:23:21.899Z","0.1.109-renovate-non-major-4be97e41838e8d2944c67cde826a7d2f6dc29dc0":"2020-11-12T06:24:37.253Z","0.1.109":"2020-11-12T06:32:14.158Z","0.1.110-renovate-non-major-ab5a8448a50a800a66fe1448b6a74a8b4c6ef738":"2020-11-12T08:03:12.646Z","0.1.110":"2020-11-12T08:09:01.929Z","0.1.111-renovate-non-major-028efba37a52adca618d56f9350e87102b019c27":"2020-11-14T09:35:44.085Z","0.1.111":"2020-11-14T09:40:43.291Z","0.1.112-renovate-non-major-2ff57097b1cb87449c2676ae12f9e5b0312c6f8f":"2020-11-14T10:38:08.008Z","0.1.112":"2020-11-14T10:42:54.058Z","0.1.113-renovate-non-major-ddbc6de5b54c86783741bc147d004759da4b9e81":"2020-11-14T12:04:50.252Z","0.1.113":"2020-11-14T12:12:02.928Z","0.1.114-renovate-non-major-9ada229d3211708b36ae0ed69c8e97acb2215ea5":"2020-11-14T13:15:50.664Z","0.1.114":"2020-11-14T13:21:54.952Z","0.1.115-renovate-non-major-aaf443da4c885ab8d28451c1bd54cc10167b1b43":"2020-11-14T14:16:20.633Z","0.1.115":"2020-11-14T14:21:43.952Z","0.1.116-renovate-non-major-546cf5b0dd76ad88a85bcc78e49d3625a65c2f40":"2020-11-14T15:23:40.929Z","0.1.116":"2020-11-14T15:28:31.190Z","0.1.117-renovate-non-major-7fa1cfa84a106ac421d5079e3db3c4d9284f4135":"2020-11-14T16:17:07.090Z","0.1.117":"2020-11-14T16:22:24.170Z","0.1.118-renovate-non-major-2a3fc367b9bd1830cb35fc02037a39d9e12a78ae":"2020-11-15T20:16:50.069Z","0.1.118":"2020-11-15T20:21:12.882Z","0.1.119-renovate-non-major-801a8b8ba130b0860613e2cdd497acc4e7e6d256":"2020-11-16T01:00:26.399Z","0.1.119":"2020-11-16T01:05:52.138Z","0.1.120-renovate-docker-non-major-9c1d72a17ffce9a199dd868bf887ab5a11d6d123":"2020-11-18T14:02:12.535Z","0.1.120-renovate-non-major-5c0bb964805ec77ea5c4819ba53a5adfd4b9f5dd":"2020-11-18T14:03:09.599Z","0.1.120":"2020-11-18T14:07:53.399Z","0.1.121-renovate-docker-non-major-2c88638ff2bcead731e2e05ab8f25148d1fd6428":"2020-11-18T14:10:56.752Z","0.1.121":"2020-11-18T14:18:05.191Z","0.1.122-renovate-non-major-6afebeb900657db261eff4c1a363f5de7a0731a8":"2020-11-19T01:22:22.248Z","0.1.122-renovate-non-major-8cae75dabffe7229b7c13c7ea5815a1ad9175cd0":"2020-11-19T01:28:45.586Z","0.1.122-renovate-non-major-3b56e960995e7a61af9897c2f4c7f5e110d6fd7f":"2020-11-19T06:46:00.573Z","0.1.122":"2020-11-19T06:51:46.068Z","0.1.123-renovate-non-major-d407ac76390de5c3f83ba70f98c958fe61549085":"2020-11-19T23:43:26.574Z","1.0.0-links-aggregation-ea724df2434811e19c7c62118b9740234915b0a5":"2020-11-20T00:41:39.152Z","0.1.123-renovate-non-major-5c551096b18d01b45f69e2bebdcf2b213f06a35c":"2020-11-20T00:44:48.234Z","0.1.123":"2020-11-20T00:50:06.787Z","1.0.0-links-aggregation-3b98dc6356d914b706771d598f167923af8058d3":"2020-11-20T05:43:57.769Z","0.1.124-renovate-non-major-de310b6498321141ef81cf7054f261d7ac470376":"2020-11-20T13:45:16.170Z","0.1.124":"2020-11-20T13:50:32.632Z","0.1.125-renovate-non-major-62fce54f8a39422adca0d8fb054f1e5dda5ed1d5":"2020-11-20T14:56:19.293Z","0.1.125":"2020-11-20T15:01:46.555Z","0.1.126-renovate-non-major-84e59e6dc8477d1b559934b0161d4e6bb393340e":"2020-11-20T23:48:35.705Z","0.1.126":"2020-11-20T23:56:01.662Z","0.1.127-renovate-non-major-26ee7f4fbd4d4d0aad0389f868bd83e09f522df4":"2020-11-21T00:01:27.834Z","0.1.127":"2020-11-21T00:06:03.274Z","0.1.128-renovate-non-major-de5fea194c3d769cabb63b4935d548206d5df1f6":"2020-11-21T02:14:30.489Z","0.1.128":"2020-11-21T02:20:54.756Z","0.1.129-renovate-non-major-1e523e9364312492f4acbbd25649f6dbd97e194a":"2020-11-23T11:35:37.521Z","0.1.129":"2020-11-23T11:40:45.421Z","0.1.130-renovate-non-major-244155f4efc97504a52324a363463bb159445678":"2020-11-23T13:18:53.137Z","0.1.130-renovate-non-major-d852e7c639f108f37a81b427c57d68e5a05d702e":"2020-11-23T13:23:31.396Z","0.1.130":"2020-11-23T13:29:21.751Z","0.1.131-renovate-non-major-ae614ba047bc8ff91fe0f7d3a332ad7920ad6641":"2020-11-24T05:11:22.119Z","0.1.131":"2020-11-24T05:16:05.855Z","0.1.132-renovate-non-major-8346fc161ace311c987b45753c9ceac98dd78659":"2020-11-24T07:18:08.679Z","0.1.132":"2020-11-24T07:23:40.393Z","1.0.0-links-aggregation-10e70839e1ec2ae2b2e3a21d1d0601f3c60bfd15":"2020-11-25T07:16:19.390Z","0.1.133-renovate-non-major-8a0ad80910a21d9a08e43c2232fd1ef1af43ee23":"2020-11-26T01:50:40.642Z","0.1.133":"2020-11-26T01:56:52.545Z","1.0.0-links-aggregation-4231e0b454b11b4b876b4d1393aefb7b4f0ed42f":"2020-11-27T03:50:36.533Z","0.1.134-renovate-non-major-72215d2e4c0a6dbed4be66e5973b0a92c5d65918":"2020-11-27T07:49:41.139Z","0.1.134":"2020-11-27T07:54:32.036Z","0.1.135-renovate-non-major-a6c45e0ff814505a663bbc963b060885202ff4f3":"2020-11-27T08:22:35.787Z","0.1.135-renovate-non-major-9b665114a2c2ce7cc6f863d4471ad588d221142d":"2020-11-27T08:27:02.131Z","0.1.135":"2020-11-27T08:32:23.539Z","0.1.136-renovate-non-major-d3950a1039389c4d5439759067d8374dd06d9e86":"2020-11-27T12:44:34.626Z","0.1.136":"2020-11-27T12:49:04.943Z","0.1.137-renovate-non-major-4ab74eb653940cb8efb3be9455bed61e705f53b5":"2020-11-30T20:24:28.546Z","0.1.137":"2020-12-01T02:27:11.286Z","0.1.138-renovate-non-major-ea5317710c33e5918b5b7f6b5d1065bea625a53f":"2020-12-01T15:31:25.705Z","0.1.138":"2020-12-01T15:36:07.138Z","0.1.139-renovate-non-major-8fef61184edad6713522945eedc272d05988eec9":"2020-12-01T16:05:33.993Z","0.1.139":"2020-12-01T16:11:33.156Z","0.1.140-renovate-non-major-c7045141b6ab3a7d3075dbbaa5cbb269390a66aa":"2020-12-01T21:03:31.407Z","0.1.140":"2020-12-01T21:08:40.688Z","0.1.141-renovate-non-major-3bf64a635c0bd6456bc3c581d983fd956110652f":"2020-12-01T22:59:56.373Z","0.1.141":"2020-12-01T23:05:41.675Z","0.1.142-renovate-non-major-566d5e94f33ea194d04df60126f4dabe03b6c3ce":"2020-12-04T16:33:39.687Z","0.1.142":"2020-12-04T16:38:18.672Z","0.1.143-renovate-non-major-2c8fce784e29081136daef442d58d18c8228531c":"2020-12-04T17:32:22.806Z","0.1.143-renovate-non-major-7276c22be367bb6da54d95a5a4589b6b3ce3e24b":"2020-12-04T17:36:38.407Z","0.1.143":"2020-12-04T17:41:55.633Z","0.1.144-renovate-non-major-f11afe23f32f65794bcbbe4b6f625ada99e60889":"2020-12-04T18:55:53.844Z","0.1.144":"2020-12-04T19:00:12.567Z","0.1.145-renovate-non-major-49c3eb1cf7bdae0ec9610a434c4e5db1a7fe252c":"2020-12-04T19:04:00.474Z","0.1.145":"2020-12-04T19:11:08.851Z","0.1.146-renovate-non-major-e98f44816cbbffc04fa6c8084679ab07f4eab236":"2020-12-05T09:56:08.980Z","0.1.146":"2020-12-05T10:00:35.207Z","0.1.147-renovate-non-major-da3a51a808a27fe1e46093bee3d1d7c452416d9f":"2020-12-05T12:11:46.514Z","0.1.147-renovate-non-major-bac3cd731e68905fdd852eb105b3a76518d9245c":"2020-12-05T13:48:36.422Z","0.1.147-renovate-non-major-ef6c47e7bd1f250c7945169425af8456ad52c5a6":"2020-12-05T15:36:12.177Z","0.1.147-renovate-non-major-93c31cbbc1081fb37cd58dd1b6c4cd7a4391a321":"2020-12-06T01:22:20.192Z","0.1.147-bump-husky-v5-8cccf842075b260229e2f260ef8b4f66b34cb496":"2020-12-07T08:13:45.904Z","0.1.147-renovate-non-major-68d3a7820a80f2f2c30e0f2558813691aec4cdb4":"2020-12-07T08:23:07.164Z","0.1.147-renovate-non-major-6fcaf24dc2f5e40628a052195b75085a26db825a":"2020-12-07T09:31:47.096Z","0.1.147-remove-postinstall-123afe3ebefe869c4429a366676e4cf58bd5cfa1":"2020-12-07T09:38:18.043Z","0.1.147":"2020-12-07T16:37:25.823Z","0.1.148-renovate-non-major-71dd1ffe75caf4e33f53c9c6bab5ff21f3cebfaf":"2020-12-07T19:05:54.485Z","0.1.148":"2020-12-07T19:14:35.141Z","0.1.149-renovate-non-major-9d1cc0a9aebe6b595ed0643fd6578737221280a7":"2020-12-07T20:43:01.996Z","0.1.149":"2020-12-07T20:48:55.882Z","0.1.150-add-husky-postinstall-036f69fc1a480bbbbbffa0f3c4caa4e2442987fb":"2020-12-08T03:43:46.259Z","1.0.0-links-aggregation-05e2e39fa633f616c55c3c8ca1c9aab0bee73600":"2020-12-08T05:32:24.948Z","0.1.150":"2020-12-08T10:29:35.373Z","0.1.151-renovate-non-major-d8817dc6ce14689f5a3f820ce5c49f17a87d992d":"2020-12-08T10:37:52.384Z","0.1.151-renovate-non-major-d0e3b239bfb50160a2a4889ca8bdbb68da02f9f4":"2020-12-08T10:43:42.273Z","0.1.151-renovate-non-major-8da95af6d034468d09261ad16484912bbed9f601":"2020-12-08T10:48:17.470Z","0.1.151-renovate-non-major-ab08216ecc883c4a2599dff61d692d41d5885d3c":"2020-12-08T11:27:15.023Z","0.1.151":"2020-12-08T11:33:21.702Z","0.1.152-renovate-non-major-f13f71224b0f007ca66cbcf069bd0fe7fe99a5ca":"2020-12-08T21:46:55.769Z","0.1.152":"2020-12-08T21:51:37.245Z","0.1.153-renovate-non-major-e3da9a60a9a66a3b77df8aef8f1f61854f7abe95":"2020-12-08T21:56:56.937Z","0.1.153":"2020-12-08T22:02:36.874Z","0.1.154-eslintrc-root-true-6990f0dca048a9f7195feb9d7d52d50be5b376bf":"2020-12-10T06:19:23.604Z","0.1.154":"2020-12-10T06:24:18.970Z","0.1.155-renovate-non-major-c4770195ab0b7e09c314165f7dad7fdef41c98bb":"2020-12-10T06:30:03.318Z","0.1.155":"2020-12-10T06:36:41.701Z","0.1.156-renovate-non-major-411023935429c92da8049608844bc6b041b65970":"2020-12-10T07:51:46.157Z","0.1.156-fix-sql-alias-84ff5ed25f6b40a2d293faf42cfe4e493beb927a":"2020-12-10T11:08:51.558Z","0.1.156":"2020-12-10T14:26:53.377Z","0.1.157-renovate-non-major-7e8d66bb9d256279df1866d349de4d1ed37d0dbf":"2020-12-10T14:32:05.589Z","0.1.157-remove-option-type-df0aa15ff35e0cb6a0bc0fd5ce30095a08a01d45":"2020-12-10T16:46:58.399Z","0.1.157":"2020-12-10T16:54:36.429Z","0.1.157-actors-ca37136ea01f294eb7bc98718affe620d3b0c46d":"2020-12-10T16:58:48.631Z","0.1.158-actors-81bf99ffe1fd5e8686936dd5f745019a2eb3aea3":"2020-12-10T18:10:27.669Z","0.1.158":"2020-12-10T21:07:11.886Z","0.1.159-renovate-non-major-2c07823e3c7b97a825ad5cfc19693138e3d86f97":"2020-12-11T00:14:07.206Z","0.1.159-fulltext-search-string-arrays-16516c119f4407ea7f975da0e0e15da1759348d4":"2020-12-11T06:19:06.118Z","0.1.159-fulltext-search-string-arrays-befc7dd4da5cc9132130cdc70bdf5a59de0ff31f":"2020-12-11T07:52:34.147Z","0.1.159-fulltext-search-string-arrays-28dd532a2ce1b00145f6ee1abddc6356098f78cd":"2020-12-11T09:06:30.979Z","0.1.159-renovate-non-major-b253755628d26849b8b9f64dfbc5e46f486a8654":"2020-12-11T10:54:12.225Z","0.1.159-fulltext-search-string-arrays-1f8d146a46c9ebd02b1dfadddf43f5c799f1216e":"2020-12-11T11:12:17.727Z","0.1.159-remove-debug-statement-1ab25df8dbf8fba28824aa06027678fc54981c1c":"2020-12-11T12:33:23.562Z","0.2.0-allow-id-on-create-c7bf7daad5bc3fd19581be5136c6787cda378591":"2020-12-11T14:14:48.999Z","0.2.0-allow-id-on-create-a870a6176a11f3cb61a1581b05714c579e9f4770":"2020-12-11T14:38:00.536Z","0.2.0-allow-id-on-create-5885da5c3c4ecba3350c53bc89508aa4d8cfe8b0":"2020-12-11T14:42:53.156Z","0.2.0-allow-id-on-create-c42e34e4be14a8e5a30314d934333b3092ea39b6":"2020-12-11T15:06:19.525Z","0.2.0-allow-id-on-create-23f787edd8ad15b611e00b5c152975785e07ad72":"2020-12-11T15:36:16.860Z","0.1.159-renovate-non-major-a7d1a3459db7655ea03ef33bdcf216515ba51e62":"2020-12-11T15:43:43.004Z","0.2.0-allow-id-on-create-31e52f26a9944535c778493c8b572743ac80fed8":"2020-12-11T16:33:43.846Z","0.2.0":"2020-12-11T16:40:23.334Z","0.2.1-renovate-non-major-2d77aed9e8e04481a8103e4b48d6764f2d1814c9":"2020-12-11T16:46:51.946Z","0.2.1-renovate-non-major-7580bc81d2829e3428c0864afdcb7e42f1f7fed8":"2020-12-11T22:54:40.734Z","0.2.1-renovate-non-major-c7e74cfedee8eb789a038ffec9eae8562d928ecf":"2020-12-12T01:27:09.306Z","0.1.159-fulltext-search-string-arrays-bedce67095fa2a270681f6cebc7237ba87b6cb43":"2020-12-12T01:43:49.807Z","0.1.159-fulltext-search-string-arrays-c7c9f83aa739aaf557a65c30734ba5dfa792c59f":"2020-12-12T02:06:00.705Z","0.2.1-renovate-non-major-45fa1fe4a3c55f6be9aa665bbc2aea9662b8cd4c":"2020-12-12T04:32:38.078Z","0.2.1-renovate-ignore-node-65985b6a3e96e1efac69e9f59996de84c5c042f0":"2020-12-12T07:59:42.082Z","0.2.1":"2020-12-12T08:06:32.041Z","0.2.2-renovate-non-major-e0562743476e0ad038fb49f1c3e3a4b0bd577a09":"2020-12-12T08:12:51.002Z","0.2.2-renovate-non-major-299c9c2172618958b6e12893de4dce1519a6e1eb":"2020-12-12T08:18:37.933Z","0.2.2-renovate-non-major-977944ad5d97ccfd81769421c93988f832f26b6b":"2020-12-12T08:23:33.325Z","0.2.2":"2020-12-12T08:33:02.966Z","0.2.3-renovate-non-major-4315cbb39bcac2f817a41ee5c121fda7fd7b075e":"2020-12-12T08:36:26.827Z","0.2.3":"2020-12-12T08:44:37.045Z","0.2.4-renovate-non-major-700ab375157740ec380b660b7c499f0f455f122f":"2020-12-12T08:51:02.578Z","0.2.4-postinstall-sh-compat-2382a04170bc9289ba59d17ad7f27c3b10726286":"2020-12-12T08:51:17.667Z","0.2.4-renovate-non-major-687496d8b6661a79e41fd321bf5cbdb06ac323e7":"2020-12-12T08:55:34.820Z","0.2.4":"2020-12-12T08:59:23.989Z","0.2.4-renovate-docker-non-major-28cf1ca6af4bd3e9eb3575b7a21dc1f6bd29cb8a":"2020-12-12T09:00:12.814Z","0.2.5-renovate-docker-non-major-e93235f4853ff3542809f5bdbe5ddf6f13bb4d16":"2020-12-12T09:03:16.804Z","0.2.5":"2020-12-12T09:09:16.787Z","0.2.6-postinstall-sh-compat-91f698490b77fdc6c7a212fcddf4556eeee7ba02":"2020-12-12T09:16:03.039Z","0.2.6":"2020-12-12T09:21:54.579Z","0.2.7-fulltext-search-string-arrays-fd4ddfd0aa5a659fe5c3f777015945eac3fd4626":"2020-12-12T09:42:42.143Z","0.2.7-renovate-non-major-764532366c067ebb57c935ac482a7bff36dff25c":"2020-12-12T14:42:19.938Z","0.2.7":"2020-12-12T14:46:57.051Z","0.2.8-renovate-non-major-d74e1813c23b3cbd51c8a250fb5074b6de8976a5":"2020-12-12T16:37:57.088Z","0.2.8":"2020-12-12T16:44:37.623Z","0.2.9-renovate-docker-non-major-6bcd3ae3f0d8539e3d1764605a2b3624f2f398d4":"2020-12-13T02:22:10.187Z","0.2.9":"2020-12-13T02:26:56.151Z","0.2.10-renovate-non-major-22610d91660c32f114c52183b934a4b4a17dd269":"2020-12-13T19:00:30.977Z","0.2.10":"2020-12-13T19:06:04.294Z","0.2.11-renovate-non-major-4bbd691525f6f00f224dcd128a8ffdb3afec2bd1":"2020-12-13T19:09:12.377Z","0.2.11":"2020-12-13T19:15:06.919Z","0.2.12-renovate-non-major-d739d1489f7d419734646523e23ef2dffda109f7":"2020-12-13T20:39:29.629Z","0.2.12":"2020-12-13T20:44:54.012Z","0.1.159-fulltext-search-string-arrays-1b1fb54ca73d3196f77dd6895f1cec3aa2fca4eb":"2020-12-14T15:24:40.183Z","0.2.13-fulltext-search-string-arrays-9a8e7d3f500c0511f607f95ba611890fcb2f7d79":"2020-12-14T15:54:42.883Z","0.2.13-fulltext-search-string-arrays-58b0a6200e6e67a666e847c8590f8a66b465bc3c":"2020-12-15T00:24:26.995Z","0.2.13-renovate-non-major-97a04100d0197403050b09457dbc555bceaf6e24":"2020-12-15T05:53:07.044Z","0.2.13-renovate-non-major-39aa6cd23f940c50c32c82e1153db99bea905067":"2020-12-15T05:57:37.172Z","0.2.13-renovate-non-major-f1fb8d244d111aeff36dbf9e420ff2f776ceae57":"2020-12-15T06:04:05.769Z","0.2.13":"2020-12-15T06:08:46.240Z","0.2.14-renovate-non-major-672e56a165eba1893e75eddaf2b8072fe55c3c20":"2020-12-15T06:12:33.639Z","0.2.14":"2020-12-15T06:18:59.568Z","0.2.15-renovate-non-major-ce726ee1af9c1336b156add855336cbdb43c11d2":"2020-12-15T07:13:30.865Z","0.2.15":"2020-12-15T07:20:39.033Z","0.2.16-renovate-non-major-7da54c94893b83d0d7bc5799d990f8d93b94446e":"2020-12-15T08:41:51.277Z","0.2.16":"2020-12-15T08:47:42.317Z","0.2.17-renovate-non-major-c15ecebcaa790a4f655a2d73a8bd4a5dbd7674d2":"2020-12-16T12:05:05.452Z","0.2.17-renovate-non-major-2016b24e3d2c64b797fae8a575e3cbc8a61f2f4c":"2020-12-16T13:19:05.504Z","0.2.17":"2020-12-16T13:23:44.661Z","0.2.18-renovate-non-major-9f0139cc25c375781aa4af6424e2f9479f25b09a":"2020-12-17T22:05:29.972Z","0.2.18":"2020-12-17T22:12:03.448Z","0.2.19-fulltext-search-string-arrays-5a9cc5f81761767c6d3e8a53a56d68a5df4a83be":"2020-12-18T05:41:30.449Z","0.2.19":"2020-12-18T06:28:34.951Z","0.2.20-renovate-non-major-f8e0c1620299d822821c4e720272162b04a971ee":"2020-12-19T00:43:13.862Z","0.2.20":"2020-12-19T00:51:18.917Z","0.2.21-renovate-non-major-fcedc12d0df63112636a7598516be01baf47715f":"2020-12-19T00:59:12.195Z","0.2.21":"2020-12-19T01:05:26.190Z","0.2.22-renovate-non-major-321a8b855842f4ac18090afc4d439314b9f2d762":"2020-12-19T01:11:42.115Z","0.2.22-renovate-non-major-00ffbf02adff961ee1509e84eadde41427129b50":"2020-12-19T01:41:35.373Z","0.2.22":"2020-12-19T01:49:28.939Z","0.2.23-renovate-non-major-7599cad719eeaaad2d6681435b2dc8d5f0b9f669":"2020-12-19T02:46:02.093Z","0.2.23":"2020-12-19T03:27:21.252Z","0.2.24-renovate-non-major-35965e1798625f068cad90b561057e3cbde81cff":"2020-12-19T08:05:35.231Z","0.2.24":"2020-12-19T08:08:36.136Z","0.2.25-renovate-non-major-83424606ae524b985756b65a8a474084fac89d94":"2020-12-19T09:53:53.549Z","0.2.25":"2020-12-19T10:10:02.711Z","0.2.26-renovate-non-major-8119244d7a92fa550392a28e39b2bfb3628b98c9":"2020-12-19T14:58:39.378Z","0.2.26":"2020-12-19T23:27:49.020Z","0.2.27-renovate-non-major-ca1238f809afbf98410d26b6f0bd24dfdae8c100":"2020-12-20T07:13:45.079Z","0.2.27":"2020-12-20T07:18:33.261Z","0.2.28-renovate-non-major-84607d62abf57359df0f049b241a6fe15afcc1f2":"2020-12-20T07:57:23.724Z","0.2.28":"2020-12-20T08:02:52.238Z","0.2.29-renovate-non-major-9c59eb4f1781d4d3bfdbba4f289d6036655bda1d":"2020-12-20T11:35:38.606Z","0.2.29":"2020-12-20T11:39:52.330Z","0.2.30-renovate-non-major-4885a018004ff4fe6eac7b582596a9b0866930f5":"2020-12-20T14:48:13.919Z","0.2.30":"2020-12-20T14:54:39.761Z","0.2.31-renovate-non-major-02bb90187e1e5a8d57f3cc18e156b4df29495187":"2020-12-20T16:55:30.288Z","0.2.31":"2020-12-20T16:59:46.359Z","0.2.32-renovate-non-major-d93717ed9bfceaba448a47014d5b39709eec85ab":"2020-12-21T15:14:25.466Z","0.2.32":"2020-12-21T15:21:07.255Z","0.2.33-renovate-non-major-31b6678e459a7a3b3c5d5fb33d72fd48a1b5e7fc":"2020-12-21T17:15:16.061Z","0.2.33":"2020-12-21T17:26:15.767Z","0.2.34-plugin-docs-dfbfe6881b43dd6d085b923e756079a8cb6bad7f":"2020-12-22T09:20:04.214Z","0.2.34":"2020-12-22T09:24:33.082Z","0.2.35-renovate-non-major-de4c428c99f40e3c72b0df50d9dc095768eb872c":"2020-12-23T02:31:30.590Z","0.2.35":"2020-12-23T02:37:28.921Z","0.2.36-renovate-non-major-b3567bee450b500abc0953b76e25adaaa1d2ba25":"2020-12-23T08:53:56.433Z","0.2.36":"2020-12-23T08:58:57.331Z","0.2.37-renovate-non-major-c1b2a76b4257eacbd3dcaf1e8ed7295a521a18b4":"2020-12-23T10:01:43.622Z","0.2.37":"2020-12-23T10:10:00.429Z","0.2.38-renovate-non-major-ad021ee951596202aebd9df14d309117583ee2cd":"2020-12-23T11:32:53.213Z","0.2.38":"2020-12-23T11:38:39.454Z","0.2.39-renovate-docker-non-major-f5935c63c88e6be6f59cc4b4d37b24dc31c4af3f":"2020-12-23T17:41:15.605Z","0.2.39":"2020-12-23T17:47:41.367Z","0.2.40-renovate-non-major-04bbde4f8b251b23183abbe9fc1c5934107ae553":"2020-12-23T17:57:49.108Z","0.2.40":"2020-12-23T18:04:38.955Z","0.2.41-renovate-non-major-07c74417d98d9c8f3984805a02644bc0a6070301":"2020-12-23T20:03:54.772Z","0.2.41":"2020-12-23T20:08:43.856Z","0.2.42-renovate-non-major-11538408ee43715e7fa447d5b6021619516b5837":"2020-12-23T22:44:30.816Z","0.2.42":"2020-12-27T01:09:03.420Z","0.2.43-renovate-non-major-cf670f089fab700493658f454c308838c4b5961c":"2020-12-27T02:59:46.732Z","0.2.43":"2020-12-27T03:07:49.043Z","0.2.44-renovate-non-major-85d4a741a7c892d6837fdbe63e99bb09daa1a3e9":"2020-12-27T06:06:30.954Z","0.2.44":"2020-12-27T06:09:42.981Z","0.2.45-renovate-non-major-6bd6718a9bea91593a90811ee3353411c4e8db94":"2020-12-27T12:33:54.662Z","0.2.45":"2020-12-27T12:37:31.247Z","0.2.46-renovate-non-major-6b7957941899c20ce47ce7f00297302347cb374e":"2020-12-28T04:30:05.268Z","0.2.46":"2020-12-28T04:49:50.840Z","0.2.47-improve-patching-6323104071996f167bb5e2a721404f1c28a94264":"2020-12-30T05:14:52.926Z","0.2.47-renovate-non-major-557aca3e5565b93678b024765597885a7aaaf991":"2020-12-30T06:57:53.405Z","0.2.47-renovate-non-major-fac73fdf33dbe5f0cf12cb5dba0ec61dac3c3945":"2020-12-30T07:53:54.742Z","0.2.47":"2020-12-30T08:02:27.718Z","0.2.47-improve-patching-648e903172fb8ddcd19e6e47e91a830d7b238001":"2020-12-30T09:30:27.327Z","0.2.48-renovate-non-major-d47b9436a88fd794dd974dd8530f00d58e8a47e5":"2020-12-30T19:34:47.327Z","0.2.48-renovate-non-major-2066eb9254e4632df848baed23cf2b626aba64f0":"2020-12-30T20:31:24.700Z","0.2.48":"2020-12-30T20:37:26.570Z","0.2.47-improve-patching-44e43aa7ab40220195149f2afc6c743cbf1b673a":"2020-12-31T08:32:40.675Z","0.2.49-renovate-non-major-9962616fe3c7c6e6de07df88ad27ff9842267b4b":"2021-01-01T02:12:32.854Z","0.2.49":"2021-01-01T02:17:48.314Z","0.2.47-improve-patching-c37c3ea420c316a61cc071ab40cb533685f5b804":"2021-01-01T02:59:34.719Z","0.2.47-improve-patching-85c9be91964aaf25d6bfd5f23b43bc43dc1afa14":"2021-01-01T04:53:17.881Z","0.2.47-improve-patching-0ff8046466bedcade01fafa70ae41eecee6377a8":"2021-01-01T05:20:50.441Z","0.2.47-improve-patching-8ba8c530aca08e77e962e904e0c20ba019fd4967":"2021-01-01T06:48:03.229Z","0.3.0-improve-patching-a371e1081a21c53f231d263f16fcf7ba8ed489e0":"2021-01-01T06:53:43.931Z","0.3.0-improve-patching-535e941615c2d2d41de94534950e6980083ff97f":"2021-01-01T07:54:55.220Z","0.2.50-renovate-non-major-69100df31343a97344367c7e7e401e54f64f754f":"2021-01-01T08:51:57.645Z","0.2.50":"2021-01-01T09:01:13.810Z","0.2.51-renovate-non-major-345e66254bb9ee3f0e1ccfc582db557a2a8a9aed":"2021-01-01T16:51:58.610Z","0.2.51":"2021-01-01T17:00:16.315Z","0.2.52-renovate-non-major-937f7df7bcf26a600feb95b6b352685ba26325da":"2021-01-01T17:05:37.098Z","0.2.52-renovate-non-major-066d7746bb46c5a2f001f306928b58578c2f075b":"2021-01-04T11:18:37.528Z","0.2.52":"2021-01-04T11:25:02.590Z","0.2.53-renovate-non-major-cc0729ad6d5653c7aba2506a160b1af0c7e6817a":"2021-01-04T13:01:09.128Z","0.2.53-renovate-non-major-bac4907a08634ba48675f0f0a15888872af0df18":"2021-01-04T13:05:09.044Z","0.2.53":"2021-01-04T13:11:30.984Z","0.3.0-patch-transaction-53b085fea6ccccf34d09d5a05cd891e255ebbbb9":"2021-01-05T01:36:45.279Z","0.3.0-patch-transaction-fe9a7b422fcc6a82f2f913cf463122373ac6ca17":"2021-01-05T01:38:59.420Z","0.3.0-patch-transaction-6f6da61c1ff52e744c1dd9ba10561ca8ace68fd8":"2021-01-05T01:40:58.085Z","0.3.0-patch-transaction-701f135bc25627e6dc559fc2c7317cdeeaad0394":"2021-01-05T01:46:36.675Z","0.3.0-patch-transaction-398e391a660fd8c3fbb6e8daf412ca9a9c7d5a17":"2021-01-05T05:53:56.506Z","0.2.54-renovate-non-major-3fc2084b44d9a806448d589433569053e083b7b9":"2021-01-07T02:37:16.310Z","0.2.54-renovate-non-major-af15d99acdfb9a80c1c745cdfb3f8acfc712c576":"2021-01-07T02:42:26.089Z","0.2.54":"2021-01-07T02:48:03.832Z","0.2.55-renovate-non-major-dd8f324a7a66254cefcd849b6afa51ee38c40970":"2021-01-07T04:18:25.160Z","0.2.55":"2021-01-07T04:23:45.151Z","0.2.56-renovate-non-major-3ee6b17d9d0e5447e7f277ffea207f7e07ef7b80":"2021-01-07T05:43:56.782Z","0.2.56":"2021-01-07T05:49:53.783Z","0.3.0-patch-transaction-2439e8a823ec0949f89c979680505c356d0873ce":"2021-01-07T07:03:21.512Z","0.2.57-renovate-non-major-df9ff1cf27b480856dea1667b0d4f5a4e6a976d4":"2021-01-07T10:20:33.931Z","0.2.57-renovate-non-major-0345cc86f6491892f4e1c7aa166da7ab55398c3e":"2021-01-07T10:25:00.392Z","0.2.57":"2021-01-07T10:29:52.988Z","0.2.58-renovate-non-major-7a64530763c4479985be14468e926555da713cd3":"2021-01-07T11:27:46.968Z","0.2.58":"2021-01-07T11:33:13.540Z","0.2.59-unlimited-connect-attempts-c84d032356340526462377ce0363eeca07abf362":"2021-01-07T13:35:04.181Z","0.2.59-unlimited-connect-attempts-f79d569a8f2160cbeca9732e2e9ff059f7da9a1e":"2021-01-07T13:44:39.700Z","0.2.59-renovate-non-major-3995d98fee38689d402cf8fd2f1a7d00cd6341f8":"2021-01-07T14:00:07.787Z","0.2.59":"2021-01-07T14:05:00.085Z","0.2.60-unlimited-connect-attempts-5d8bb7b8cfbdd65523bb7563a8d90be9ed9d5cbc":"2021-01-08T00:30:01.844Z","0.2.60":"2021-01-08T02:26:04.908Z","0.3.0-patch-transaction-4e652b6853d23cb8f9792a55557a6001d064b181":"2021-01-09T03:49:10.612Z","0.2.61-streamer-db-client-reconnect-36e27d1384d7a856d5923c56e2cdf1946b846e86":"2021-01-09T08:50:37.684Z","0.2.61-renovate-non-major-c2d8193d6f16519d44aa55bf633ce00e4859ce0f":"2021-01-09T21:56:45.863Z","0.2.61":"2021-01-09T22:01:52.405Z","0.2.62-renovate-non-major-7479d38742fbce930de1de8004c8c1a6b02c31e7":"2021-01-09T22:04:33.945Z","0.2.62":"2021-01-09T22:13:04.255Z","0.2.63-renovate-non-major-6c470c518c1cd148efc1a18d86f3be17c819765a":"2021-01-09T23:17:43.354Z","0.2.63":"2021-01-09T23:23:25.216Z","0.3.0-patch-transaction-53d7b6cfc8cc5a9188433a9df6d0c2e12751fa43":"2021-01-10T15:35:11.747Z","0.2.61-streamer-db-client-reconnect-2be81f7e20e8ed67961742b6e8f346fd8facf7b3":"2021-01-11T03:08:52.651Z","0.2.64-streamer-db-client-reconnect-f994b3310efcfd4fd87329797d8d30c07285b108":"2021-01-11T03:19:57.519Z","0.2.64-renovate-non-major-692f59ffd825f8dd5e0c654d9838bb4bd49f0402":"2021-01-11T08:05:31.465Z","0.2.64":"2021-01-11T08:11:17.056Z","0.2.65-renovate-non-major-1b4e2324061ccbbe48f655f8cffc8b5cfa10387d":"2021-01-11T08:14:10.826Z","0.2.65-renovate-non-major-fed52f3c703e568dc2a6ffc4ff9088b59963113d":"2021-01-11T08:21:41.106Z","0.2.65-use-company-token-19b19568124fe90650e3d8227fb0015dee1c666e":"2021-01-11T08:22:27.259Z","0.2.65":"2021-01-11T08:26:54.983Z","0.2.66-use-company-token-9249f39679711fdf8538d0f2965691a467a02583":"2021-01-11T08:33:33.068Z","0.2.66":"2021-01-11T08:38:37.561Z","0.2.67-streamer-db-client-reconnect-09461da4e98244c6f90b72fb35bf72f2bbb8b5d7":"2021-01-11T10:05:59.845Z","0.2.67":"2021-01-11T10:11:08.231Z","0.2.68-renovate-eslint-plugin-jsdoc-31-x-99971b316149e93eda632f7931669bfe6b0a8c57":"2021-01-11T11:35:01.047Z","0.2.68-renovate-eslint-plugin-jsdoc-31-x-39436da3290bba50fc07e4a3b742995aa2e13824":"2021-01-11T13:37:33.335Z","0.2.68-renovate-eslint-plugin-jsdoc-31-x-a4cb4a6a3d78cb2fa551dc413a4ebe1f3d7c3b6b":"2021-01-11T16:48:29.494Z","0.2.68-renovate-non-major-e83c1a0441ab4d0ccdbff34bd60471497b868c62":"2021-01-12T01:32:59.223Z","0.2.68":"2021-01-12T01:37:39.226Z","0.2.69-renovate-eslint-plugin-jsdoc-31-x-21fb1281d7c8e0d2c8e266d36e17dac452caa06d":"2021-01-12T01:41:08.662Z","0.2.69-renovate-docker-non-major-ba979142707d915c03cdbba5ef57bf81a49f38dd":"2021-01-12T04:44:03.844Z","0.2.69":"2021-01-12T04:50:41.132Z","0.2.70-renovate-eslint-plugin-jsdoc-31-x-ef87ee1a61e8a5dbfc3f22b26fb4bda378763b2c":"2021-01-12T04:54:47.918Z","0.2.70":"2021-01-13T00:20:54.406Z","0.2.71-renovate-non-major-43d0f4de85f6460f412b38615ca908b9ec14b1ef":"2021-01-13T05:33:42.316Z","0.2.71":"2021-01-13T05:40:44.062Z","0.2.72-renovate-non-major-e41057726b2bd9f5c585cce49172833a1ce80687":"2021-01-13T06:49:26.709Z","0.2.72":"2021-01-13T06:57:15.334Z","0.2.73-add-card-patch-metrics-fd60d1329cac2dc254d7d173fe117e2eecb53a53":"2021-01-13T08:17:45.673Z","0.2.73-renovate-non-major-d52dcc64899c4ce20abd67bece147643acca69d2":"2021-01-13T12:52:46.894Z","0.2.73":"2021-01-13T12:57:28.376Z","0.2.74-renovate-non-major-1700f9439bd07ca47d74273022a297e48c9e5877":"2021-01-13T14:19:30.165Z","0.2.74":"2021-01-13T14:35:08.196Z","0.2.75-add-card-patch-metrics-d251ea411b67634ab7467bd17b6692c9b678c3e4":"2021-01-13T22:00:57.747Z","0.2.75":"2021-01-13T22:05:55.479Z","0.2.76-renovate-non-major-d3e13605ea786a17105982637f75dcb4c50e3df9":"2021-01-14T02:42:18.419Z","0.2.76":"2021-01-14T02:47:14.346Z","0.2.77-renovate-non-major-cac8d24fb6032173378f1cc7630b75d1d5f9aba8":"2021-01-14T03:27:24.150Z","0.2.77":"2021-01-14T03:33:13.710Z","0.2.78-renovate-non-major-25636c66427918c8949c2a8caf8977ad28eac789":"2021-01-14T04:55:52.665Z","0.2.78-renovate-non-major-9a4a7e30e914156179749c555cda90772d7b4e98":"2021-01-14T05:00:16.665Z","0.2.78-renovate-non-major-56d0a1489a17ca66059705ce6313a9d32d432057":"2021-01-14T05:04:47.737Z","0.2.78":"2021-01-14T05:10:12.704Z","0.2.79-renovate-non-major-05374af3167912b72e36ba29f911f227fcddf288":"2021-01-14T06:22:08.858Z","0.2.79":"2021-01-14T06:27:11.444Z","0.2.80-renovate-non-major-5be420fc1f284cb8bcffd8ba2e5dfe8b8b930b4f":"2021-01-14T16:14:03.142Z","0.2.80":"2021-01-14T16:18:53.031Z","0.2.81-renovate-non-major-5c753165251c983601114d7ce94a1ce5c4753bf5":"2021-01-14T17:50:25.517Z","0.2.81":"2021-01-14T17:58:07.395Z","0.2.82-renovate-non-major-18aa3e52520794cfce00088329f17bd8cd53b61c":"2021-01-14T18:01:23.440Z","0.2.82-renovate-non-major-d0a10f9a4769eb3ec0ebd61c517b523a8c33b669":"2021-01-14T19:33:01.803Z","0.2.82":"2021-01-14T19:38:11.648Z","0.2.83-renovate-non-major-97a52082584fd304fbacfc1e211b14c862b12107":"2021-01-14T23:55:55.824Z","0.2.83":"2021-01-15T00:00:48.257Z","0.2.84-renovate-non-major-865cb4542fe387edf3f76951a78212d984fffd46":"2021-01-15T00:05:57.302Z","0.2.84":"2021-01-15T00:11:04.448Z","0.2.85-renovate-non-major-67d321b80eac6ffcf5da5a6a91161167d3691b50":"2021-01-15T00:14:22.662Z","0.2.85":"2021-01-15T00:23:54.285Z","0.2.86-renovate-non-major-7a0a04e98c4c93810d37f9103cc23c96557706b4":"2021-01-15T01:47:54.000Z","0.2.86":"2021-01-15T01:53:16.437Z","0.2.87-renovate-non-major-a3c58ed93801fdd829d5472f21170c899b99d8ee":"2021-01-15T02:55:58.345Z","0.2.87":"2021-01-15T03:01:59.227Z","0.2.88-renovate-non-major-ff113d571168d729ab839c4aba5af9d7d469cfc7":"2021-01-16T01:17:51.928Z","0.2.88-renovate-non-major-f715126a3cb3a8e592590d6a50a450aabeac9158":"2021-01-16T01:22:29.854Z","0.2.88":"2021-01-16T01:29:11.390Z","0.2.89-renovate-non-major-fff68d03f744fe3d651ed8094d61355c6b994e0e":"2021-01-16T03:53:07.061Z","0.2.89":"2021-01-16T03:58:45.015Z","0.2.90-renovate-non-major-3d67702ae38f0ab7399727ea980205446103f3be":"2021-01-17T00:17:46.641Z","0.2.90":"2021-01-17T00:23:58.120Z","0.2.91-renovate-non-major-5efd364ec193b28ecd07f5124d0b5d7f235d56b2":"2021-01-17T06:16:12.102Z","0.2.91":"2021-01-17T06:22:35.339Z","0.2.92-renovate-non-major-02a375c3dbbd2d7743d87dcde785b857547f377e":"2021-01-17T12:55:47.912Z","0.2.92-renovate-non-major-30797ffed67de64e314aa84f32ae791fea7c1cff":"2021-01-17T13:00:04.100Z","0.2.92":"2021-01-17T13:04:52.719Z","0.2.93-renovate-non-major-49e22af49a6ae0f3ed2853534e06cd6269498347":"2021-01-17T14:46:26.141Z","0.2.93":"2021-01-17T14:52:41.258Z","0.3.0-patch-transaction-70b783b609bb4b43529021860b22c03f34a45024":"2021-01-18T00:44:04.683Z","1.0.0-patch-transaction-e51c9797b6178eaa94a8f1e18dd47bd8ed080c79":"2021-01-18T04:07:35.659Z","0.2.94-renovate-non-major-d590a3ce1da5a0f244faf3eaec6f15d419922ac3":"2021-01-18T08:35:07.925Z","0.2.94-renovate-non-major-ccd269dfffa29c5ad900b7190ce3ceec1546c948":"2021-01-18T10:00:37.115Z","0.2.94":"2021-01-18T10:54:05.246Z","0.2.95-renovate-non-major-7a7aafffbecde58d562031a9af8db279b8897715":"2021-01-18T10:59:21.853Z","0.2.95":"2021-01-18T11:04:35.028Z","1.0.0-patch-transaction-f49b1365303af4d3839da8a272ac3cc11d9d126d":"2021-01-19T03:03:00.831Z","1.0.0-patch-transaction-a30a918214339dd86423eece51762d6196af5b17":"2021-01-19T04:06:44.037Z","1.0.0":"2021-01-19T04:12:11.327Z","1.0.1-renovate-non-major-6fb4836a72b0b818fdea9287c3368677a43fc1bc":"2021-01-20T05:01:22.586Z","1.0.1-renovate-non-major-a5e0d61546f7648fce2112bd48cafe492cd64ac3":"2021-01-20T06:53:24.030Z","1.0.1":"2021-01-20T06:59:24.820Z","1.0.2-renovate-non-major-08413e0211493fc0fe17242c990b0adfe0822edc":"2021-01-20T07:03:16.191Z","1.0.2":"2021-01-20T07:09:30.931Z","1.0.3-renovate-non-major-5c9d1eb91c368ccca5afa9e3986eedb7cc7a206a":"2021-01-20T08:33:39.807Z","1.0.3":"2021-01-20T08:38:41.597Z","1.1.0-with-ui-schema-snippet-078e79ef728ca876b58fb3c4c66de61baee24e4f":"2021-01-20T09:26:07.972Z","1.1.0-with-ui-schema-snippet-3d8249d0a22d548e2f8da483d478393ab299e048":"2021-01-20T09:29:48.940Z","1.1.0":"2021-01-20T09:50:11.158Z","1.1.1-add-link-patch-logs-c9caab8459bb78b7f4b90530cccbb8d48ed11407":"2021-01-21T03:47:52.056Z","1.1.1-add-link-patch-logs-5943cc89bd94d6a9f89e052c885f27c36f3aef76":"2021-01-21T04:21:46.680Z","1.1.1-add-link-patch-logs-e90727f0ba28ca0e19c723463b382ad653b6d69e":"2021-01-21T04:33:17.161Z","1.1.1":"2021-01-21T04:39:52.815Z","1.1.2-fix-user-email-field-a07c130429c0bcd663b260f20cf97047943a0099":"2021-01-21T09:26:54.742Z","1.2.0-5508-pre-build-versions-01664d58395f1147e6b7b23e3a29e70f6df16aa9":"2021-01-21T09:27:35.707Z","1.1.2":"2021-01-21T09:32:29.137Z","1.2.0-5508-pre-build-versions-c9fa904fe9323f6e22335abd78d26e015a0ee6e1":"2021-01-21T10:15:47.016Z","1.1.3-renovate-non-major-6c843919bd88f01e92d009833731433ee0b3082d":"2021-01-21T15:54:55.581Z","1.2.0-5508-pre-build-versions-3280a8d6a912d2fff33e389ff211ddbc0c77aa51":"2021-01-21T16:31:04.745Z","1.2.0-5508-pre-build-versions-8da05ca0d2e19f2c91d06b0c5a5d420edbb29ec2":"2021-01-21T17:19:08.805Z","1.1.3":"2021-01-22T01:05:34.995Z","1.2.0-use-ui-explicit-in-ui-schema-7e8b94935335dd69efa5018b2112b7d259464522":"2021-01-22T03:45:55.933Z","1.2.0-use-ui-explicit-in-ui-schema-944b88d55357af1202a81ed409fd562d5d964c5a":"2021-01-22T03:49:44.571Z","1.2.0":"2021-01-22T03:57:04.740Z","1.2.0-5508-pre-build-versions-7944f166d5e64b27a093486600eb5f9f8f8c3333":"2021-01-22T08:17:26.100Z","1.2.1-renovate-non-major-efa8f3053a2b3c135b013235ea637277e8a95731":"2021-01-23T04:33:33.705Z","1.2.1":"2021-01-23T04:38:31.692Z","1.2.2-renovate-non-major-1cace351ce631c1d4b1b8aae2250f352f943fb90":"2021-01-23T06:02:04.707Z","1.2.2":"2021-01-23T06:07:12.486Z","1.2.3-renovate-non-major-a08133d1939395559e2c52643b730580ace491c6":"2021-01-23T07:17:04.921Z","1.2.3":"2021-01-23T07:22:26.391Z","1.2.4-renovate-non-major-1a2d159daac8fb8d2d204fbf221a4f409a921434":"2021-01-23T09:29:38.242Z","1.2.4":"2021-01-23T09:35:05.261Z","1.2.5-renovate-non-major-320f54b092d4dc07e565e1f3fe8aa7dedda0f9d7":"2021-01-23T16:05:58.373Z","1.2.5":"2021-01-23T16:12:37.896Z","1.2.6-renovate-non-major-79572816642bf3fc92dfad5b99fb2ca581aa7941":"2021-01-23T17:20:58.191Z","1.2.6":"2021-01-23T17:25:50.886Z","1.2.7-renovate-non-major-94b08f6b6c0804a40c10bd7355ff192c26f0a2c5":"2021-01-24T13:43:14.885Z","1.2.7":"2021-01-24T13:47:42.934Z","1.2.8-renovate-non-major-dc220fd68fdad4effb42ef986fd86f0e2a2d8db4":"2021-01-24T13:52:41.461Z","1.2.8":"2021-01-24T13:58:00.279Z","1.2.9-renovate-non-major-c2c2dc420acdf455726bb76163ea6727b61de99d":"2021-01-24T14:54:22.250Z","1.2.9":"2021-01-24T15:01:21.709Z","1.2.10-renovate-non-major-52a0b509bd40a5d9cba5cc2840b95c91bffbdcd4":"2021-01-24T15:04:23.980Z","1.2.10-renovate-non-major-aba0fa08a7c101ad991fa2696f35f5843a69bd1e":"2021-01-24T15:09:15.946Z","1.2.10":"2021-01-24T15:15:54.729Z","1.2.11-renovate-non-major-720fe7942f52e3f65803f696c0b986f489fd7cda":"2021-01-24T16:25:02.111Z","1.2.11":"2021-01-24T16:29:56.552Z","1.2.12-renovate-non-major-4fc9587e931dd5497ba5d83844b913f030904edc":"2021-01-24T17:08:31.081Z","1.2.12":"2021-01-24T17:13:39.462Z","1.3.0-add-session-scope-71410c2a5ec896984bc44a755011ffa9aa691d57":"2021-01-25T06:00:57.675Z","1.2.13-renovate-non-major-6eb64b2378f521503e962996b45938134eb042a0":"2021-01-25T09:09:35.202Z","1.2.13-renovate-non-major-72eb9ec7ef75b4a0a3ec130f8d733dd1ef5e5149":"2021-01-25T09:14:57.272Z","1.2.13":"2021-01-25T09:19:55.792Z","1.2.14-renovate-non-major-350ee9aaf6de4b523bad9b0e1837f27a534c8550":"2021-01-25T10:12:56.619Z","1.2.14":"2021-01-25T10:19:16.063Z","1.2.15-renovate-non-major-9f6105f1567e98c34b837feb055c5d3d2192bcda":"2021-01-25T11:24:01.848Z","1.2.15":"2021-01-25T11:31:21.163Z","1.2.16-renovate-non-major-dfc4f4712d4a4c4829659c13c6f98cb32f855309":"2021-01-25T13:35:22.632Z","1.2.16-renovate-non-major-843c21c80d628fdde8087b926d8008e3ada11963":"2021-01-25T14:57:14.063Z","1.2.16":"2021-01-25T15:04:11.719Z","1.3.0-add-session-scope-c49abf2a59dadb72ab67c3dbc51c31b01458dfd5":"2021-01-26T07:25:40.996Z","1.3.0-add-session-scope-bcadac6d252feea83704167a7b14dbe172c6064d":"2021-01-27T03:21:22.403Z","1.3.0-add-session-scope-b5c445409f82a6a6892cfea34ab070eff6fded6f":"2021-01-27T03:35:25.404Z","1.2.17-renovate-non-major-5e2d76c7d030b42a1ef343b284035055b906ee7f":"2021-01-27T05:39:07.657Z","1.2.17":"2021-01-27T05:44:38.532Z","1.2.18-renovate-non-major-e661c265ae6dee61b123c7138b021f8b22a92eb1":"2021-01-27T06:20:31.630Z","1.2.18":"2021-01-27T06:27:32.367Z","1.2.0-5508-pre-build-versions-f1b168fee1704cc09b134cd7586b426e8ddfb27f":"2021-01-27T10:42:45.482Z","1.2.19-renovate-non-major-4a99705044dc40658b7925a9ad049fc848b65d7f":"2021-01-27T10:47:37.518Z","1.2.19-renovate-non-major-332438b1e3c49cc9f104b64e63ce5c1378dacd0c":"2021-01-27T10:52:05.023Z","1.2.19":"2021-01-27T10:59:28.188Z","1.2.20-renovate-non-major-892e88ef9ad820627375c3d1eba8caa199ce9193":"2021-01-27T11:02:42.802Z","1.2.20":"2021-01-27T11:09:53.391Z","1.2.21-renovate-non-major-9886c73e167d295cb4bac297875c0d202ba77b28":"2021-01-27T12:47:27.321Z","1.2.21":"2021-01-27T12:52:55.415Z","1.2.22-renovate-non-major-5ab555ca210d87d3dbb44f7c8eb7a836138713c9":"2021-01-28T02:25:07.830Z","1.2.22":"2021-01-28T02:29:56.275Z","1.2.23-renovate-non-major-0bf4e44fa3142d4cd1683bfe24f508ae3cf471d9":"2021-01-28T03:55:40.299Z","1.2.23":"2021-01-28T04:01:12.649Z","1.2.24-renovate-non-major-93ee0cb6b2d737f529f287a7c2f912e0f3fb5b9f":"2021-01-28T04:04:22.295Z","1.2.24":"2021-01-28T04:12:58.150Z","1.2.25-renovate-non-major-9e61f156da5449d5e80a6fd82275eb89b788b773":"2021-01-28T05:07:34.197Z","1.2.25":"2021-01-28T05:15:00.515Z","1.2.0-5508-pre-build-versions-0008543569e79898fd04b84e2be46cbd25672811":"2021-01-28T09:04:07.756Z","1.3.0-add-session-scope-ef78abef0fb911379013333bf0ad53c74e5f07a5":"2021-01-28T09:14:58.175Z","1.3.0-add-session-scope-b25d6a72037fabe33e21a829721e5b78cc70bfd7":"2021-01-28T09:18:59.383Z","1.2.26-renovate-non-major-8f2f7668a46939a8675cdd882571bb3817627322":"2021-01-28T13:56:56.432Z","1.2.26":"2021-01-28T14:01:33.937Z","1.2.27-whitesource-configure-b7ff8c9b1ab4e6523a3d2ff03358c7cdef6c94d3":"2021-01-29T05:09:46.964Z","1.2.27":"2021-01-29T05:21:50.941Z","1.2.28-renovate-non-major-2b528d689c905ab4613c5957ce032ba6d05d2a58":"2021-01-29T05:27:23.100Z","1.2.28":"2021-01-29T05:34:58.273Z","1.2.29-renovate-non-major-84f381087749a8fd51be814cd4bc6ea5dc2838cf":"2021-01-29T17:48:19.217Z","1.2.29":"2021-01-29T17:54:17.122Z","1.3.0-add-session-scope-402bb7eabcdea892d8bb2284da825b66d76590ad":"2021-01-31T01:58:53.118Z","1.2.30-renovate-non-major-8c1b049488c672537563a115b2ffb32d1340505b":"2021-01-31T05:19:31.149Z","1.2.30":"2021-01-31T05:24:22.213Z","1.2.31-renovate-non-major-ff7cf2846b39fc87e6834deb9759c551cbf76d3b":"2021-01-31T05:27:53.680Z","1.2.31":"2021-01-31T05:37:02.700Z","1.2.32-renovate-non-major-bb3db86ce43b87fc521b2079f1d16e822d915baf":"2021-01-31T06:59:56.763Z","1.2.32":"2021-01-31T07:03:41.894Z","1.2.33-renovate-non-major-fe05dd8a34143fe684559ca4e6f67282e07ce9c9":"2021-01-31T08:12:36.163Z","1.2.33-renovate-non-major-be66aed21dffba6e790d0100d3a3ba79a6d84259":"2021-01-31T10:33:09.129Z","1.2.33":"2021-01-31T10:38:35.837Z","1.2.34-renovate-non-major-ae4232f69f923cb4d87842a30a09af59192c0daf":"2021-01-31T11:20:20.898Z","1.2.34":"2021-01-31T11:26:41.116Z","1.2.35-renovate-non-major-c4ba32f5082db077b02ccbff9a21ef2a5b39a3a3":"2021-01-31T12:10:01.094Z","1.2.35":"2021-01-31T12:14:24.210Z","1.2.36-renovate-non-major-23b3cfd8e70db7ca08f879b2284fec04b3872144":"2021-01-31T17:19:04.144Z","1.2.36-renovate-non-major-58eba00feb57306a7794e5522b8d1530063408f4":"2021-01-31T17:23:16.924Z","1.2.36-renovate-non-major-facdbb1ffc8f471ef15252449bf5c6d1c568c653":"2021-01-31T19:31:25.807Z","1.2.36":"2021-01-31T19:36:55.901Z","1.2.0-5508-pre-build-versions-8d2e180f26673910012c3192b011e491cd0f141b":"2021-02-01T09:11:43.543Z","1.3.0-Add-options-param-to-stream-logic-625e3cddd2132c42aef7208e8bb58a5ffd8cb896":"2021-02-01T11:03:08.202Z","1.2.37-renovate-non-major-b50d97e7fb96b44f7866b5a2271bc7ed733e031f":"2021-02-01T14:45:57.398Z","1.2.37":"2021-02-01T14:50:51.428Z","1.2.38-renovate-non-major-8eb175129bb77d568f5f03b68d2ce08c0969ac15":"2021-02-01T15:39:26.295Z","1.2.38-renovate-non-major-9df47a57f7b8ee740455b09a38aff0756a514da6":"2021-02-01T16:55:34.722Z","1.2.38":"2021-02-01T17:00:23.271Z","1.2.39-renovate-non-major-1035a787133beb11e6c6603035cf066b1ebb159f":"2021-02-01T18:06:18.049Z","1.2.39":"2021-02-01T18:11:05.381Z","1.2.40-renovate-non-major-7643ebc8812d718ad604c49da746d744f21b6b0e":"2021-02-01T20:35:19.965Z","1.2.40":"2021-02-01T20:42:16.138Z","1.2.41-renovate-non-major-f73b8cdbe585895ff42f5edd7fcd08d33e9d895e":"2021-02-01T21:12:29.233Z","1.2.41":"2021-02-01T21:17:03.497Z","1.2.42-renovate-non-major-b4f7d8e727fc315f16dcce2ac1bfd6cea18b770d":"2021-02-02T01:23:29.440Z","1.2.42-renovate-non-major-36f98f20b678f1df4f6083d1e2a2b9ff303c62cd":"2021-02-02T01:28:59.913Z","1.2.42-renovate-non-major-898d627a2411e454a585aefad2f9a76a5ded98c4":"2021-02-02T01:34:09.604Z","1.2.42-renovate-non-major-edfb427780d41c28731a9e303aeeb9aeea3dc131":"2021-02-02T01:38:58.575Z","1.2.42-renovate-non-major-9aa4315f363f75006b107d1967e3d613905eb88f":"2021-02-02T02:36:00.478Z","1.2.42":"2021-02-02T02:42:04.579Z","1.2.43-renovate-non-major-77c6376cb087e709e7595a980ceb2e0b2c664eb9":"2021-02-02T03:47:00.862Z","1.2.43":"2021-02-02T03:51:24.616Z","1.3.0-add-session-scope-3318b089fb4f5d0a1e513fde35b6e23d84e1070e":"2021-02-02T06:31:58.763Z","1.2.0-5508-pre-build-versions-12bb8506076dd96f4399d36d13dc94b169adc1e2":"2021-02-02T13:20:17.052Z","1.3.0-Add-options-param-to-stream-logic-56126644a8af6d3a53397c198fb3a09879178846":"2021-02-02T16:06:15.496Z","1.3.0":"2021-02-02T16:12:29.619Z","1.3.1-renovate-balena-jellyfish-logger-1-x-edb08b973a0a77ae07d980cf7a9cb489341a58a6":"2021-02-03T03:24:10.064Z","1.4.0-add-session-scope-d9e92d6fec120104343a2140dcab41fdc14d472e":"2021-02-03T04:58:51.791Z","1.3.1-renovate-non-major-d07bcc343e308db3682c88662255c2bba5a8762c":"2021-02-03T05:13:03.373Z","1.3.1-renovate-balena-jellyfish-logger-1-x-2101aeb90c2215a5fd1c205ee8e2119f1fb13064":"2021-02-03T05:18:33.158Z","1.3.1-create-indexes-concurrently-651ec9eb92559a340e8a0fd6ef245e3d1c5a2497":"2021-02-04T03:29:16.568Z","1.3.1-bump-logger-f2ae67e11666b5fd79965db02d6e55d6a0f5a0b6":"2021-02-04T07:07:15.665Z","1.3.1":"2021-02-04T07:14:43.200Z","1.3.2-renovate-docker-non-major-c177f548dd4787171fd62b4681b7ed31e1936ecb":"2021-02-04T07:19:28.655Z","1.3.2-bump-non-major-d19ee14bda18bad8b0dcbf8644b94378e200d312":"2021-02-04T07:20:50.680Z","1.3.2-renovate-non-major-c5c03dcb7a52cbd5b3558bd935a938d364b6a340":"2021-02-04T07:21:26.368Z","1.3.2":"2021-02-04T07:27:34.853Z","1.3.3-renovate-non-major-2400edc95b4dcdcb683a6b5da18e2db86d21661a":"2021-02-04T07:31:07.898Z","1.3.3-bump-non-major-9c8bade9a893c27d2bdcf3d465dda97201d54c18":"2021-02-04T07:32:55.944Z","1.3.3-bump-non-major-a923a53c7806a882e7e3dcd754564d1a06b31434":"2021-02-04T07:43:00.399Z","1.3.3-set-npm-version-763359197298fa7b500876f0a66dc910bbcfd4c0":"2021-02-04T08:20:48.083Z","1.2.0-5508-pre-build-versions-cc7d5b28581985d51b041e8a9c68178aa6297272":"2021-02-04T08:54:42.377Z","1.3.3":"2021-02-04T09:43:48.052Z","1.3.4-renovate-non-major-cca9f09f472f813174a770f218513ab99dd1ae02":"2021-02-04T09:46:40.657Z","1.3.4":"2021-02-04T09:55:19.019Z","1.3.5-renovate-non-major-19586aec8dfdd6baa155d3d6b982cddf07b5411e":"2021-02-04T17:39:59.870Z","1.3.5":"2021-02-04T17:44:12.379Z","1.3.6-create-indexes-concurrently-9162a1b7849b13f34472421b4c9111c384e9b269":"2021-02-04T22:49:11.040Z","1.3.6":"2021-02-04T22:54:05.394Z","1.4.0-add-session-scope-a5607e7be92779875530545e29479322e20eff62":"2021-02-05T00:19:08.779Z","1.3.7-updated-at-timestamp-index-91315f94f3e305da9e5919c2cc7d513cb632fcee":"2021-02-05T00:24:22.887Z","1.3.7-created-at-timestamp-index-0393db2d2f51c9545755c7e0f7637d7d357d0c53":"2021-02-05T00:30:49.749Z","1.3.7":"2021-02-05T00:47:43.062Z","1.4.0-add-session-scope-3fc206addbc441ba817acd667f76a2d8daa99465":"2021-02-05T03:21:31.922Z","1.4.0":"2021-02-05T03:44:06.578Z","1.4.1-renovate-non-major-c78e58a492986364351519309244583d3bdc52cd":"2021-02-05T05:44:26.235Z","1.4.1":"2021-02-05T05:56:16.622Z","1.4.2-renovate-non-major-b25f9101b180c50a6462833b37b721f8fbc18bff":"2021-02-05T06:00:21.993Z","1.4.2-renovate-non-major-efddf86cf1bb006dcb3a9381ca333c39952047bc":"2021-02-05T06:05:14.651Z","1.4.2":"2021-02-05T06:10:03.172Z","1.4.3-renovate-non-major-cdf9a3ac65a32f68a426a0de05f7dc01fa409ac8":"2021-02-05T17:57:42.090Z","1.4.3-renovate-non-major-31b52ec8d9d460266e29a2dd80bdfe99ce903384":"2021-02-05T18:50:44.825Z","1.4.3":"2021-02-05T18:55:41.986Z","1.4.4-use-shared-renovate-config-a409a5aeff42706e498c976f53c57c07887e496c":"2021-02-06T01:46:38.692Z","1.4.4":"2021-02-06T01:53:22.829Z","1.4.5-created-at-timestamp-index-e3f9876f9367f630689ac149d88b40c3d9720a5e":"2021-02-06T04:13:37.592Z","1.4.5-fix-flaky-test-4c708dae4cb67a1606da068494ede21a339d10c9":"2021-02-06T04:53:59.022Z","1.4.5":"2021-02-06T04:58:29.143Z","1.4.6-created-at-timestamp-index-c4066188c8edb35c868e8946b495b079b3941750":"2021-02-06T05:08:00.282Z","1.4.6-handle-deadlocks-f553fbd86f6687abe9f4a804d84f136d9e2baa1d":"2021-02-06T05:15:02.078Z","1.4.6":"2021-02-06T05:17:18.905Z","1.4.7-handle-deadlocks-06743b7888183be54e07b536d92818640a9ac5c5":"2021-02-06T05:27:34.832Z","1.4.7-handle-deadlocks-c41b439989da01197da6304398253ebca28438e3":"2021-02-06T07:31:02.970Z","1.4.7-handle-deadlocks-4bf9c6e2d945630acfdd2bc56ca2f35ee6105dca":"2021-02-06T07:42:04.655Z","1.4.7-renovate-non-major-45b7cabf88e84c703221d29a284944b357ea8b48":"2021-02-06T08:08:53.744Z","1.4.7":"2021-02-06T08:14:25.854Z","1.4.8-handle-deadlocks-8125bed0062aa4e8cb754fc1492e7b8fe6d51094":"2021-02-06T09:44:00.097Z","1.4.8-renovate-non-major-f4d602a087585de2600cd79b984ff831c4ddd3b7":"2021-02-06T10:01:30.647Z","1.4.8":"2021-02-06T10:07:22.547Z","1.4.9-renovate-non-major-6b0ca1ccd77336d0ce52fb7eebf5db111b1c8076":"2021-02-06T10:14:52.877Z","1.4.9-renovate-non-major-311f1e67800490b4bcfbea5963e41b37cb30407e":"2021-02-06T10:19:29.955Z","1.4.9-handle-deadlocks-37af21e0966fcc09c03d4d855b7a1bc5e85b889b":"2021-02-06T10:22:22.157Z","1.4.9":"2021-02-06T10:25:31.022Z","1.4.10-renovate-non-major-d8a4937e240214ea752cb0f685974de960573638":"2021-02-06T11:53:36.831Z","1.4.10":"2021-02-06T11:59:15.287Z","1.4.11-renovate-non-major-ee25b8f6541491d52d025c795c85b12786125fcf":"2021-02-06T12:02:16.335Z","1.4.11-renovate-non-major-f2fa1b6e5c944274576337707de673b0708996f9":"2021-02-06T12:10:08.132Z","1.4.11-handle-deadlocks-7e057f48788c358ba39539916487a99354f7e328":"2021-02-06T12:11:12.948Z","1.4.11":"2021-02-06T12:14:41.550Z","1.4.12-handle-deadlocks-bd968e48f7433008f0b937d0047fac947c12de2b":"2021-02-06T13:02:37.485Z","1.4.12-handle-deadlocks-4ff91d80bcaef0e1470c9eab67594a77d5f77bf1":"2021-02-06T13:17:40.626Z","1.4.12-renovate-docker-non-major-d2a712066cbf0cfcbf8ef010b031d1b7900b51c5":"2021-02-07T01:44:32.655Z","1.4.12-handle-deadlocks-afafbf3e48043a58688ceaa2a592cfbd3f2bb36a":"2021-02-07T01:48:41.195Z","1.4.12":"2021-02-07T01:50:03.704Z","1.4.13-renovate-non-major-3ac81ef15543cd037cb359a6c3f171782b55032c":"2021-02-07T09:39:55.695Z","1.4.13":"2021-02-07T09:45:25.413Z","1.4.14-renovate-non-major-814197c9232eae169a7263ccb2fb9055d4d00352":"2021-02-07T10:29:42.428Z","1.4.14-renovate-non-major-840d51b1245f96a13e2bc197d27ab445cebecdf1":"2021-02-07T11:24:19.599Z","1.4.14-renovate-non-major-5afa1bb8a7e525172d081afe059a28d4c5668c59":"2021-02-07T12:58:53.131Z","1.4.14-renovate-non-major-5bbf3bd8121b52becb2b34e3f155fb9297d0688c":"2021-02-08T19:01:46.980Z","1.4.14-renovate-non-major-461882a44e7d22e53440a85b361950cc9bcf2b8f":"2021-02-08T20:43:30.126Z","1.4.14":"2021-02-09T00:33:08.755Z","1.4.14-renovate-non-major-625f553291231a3b7bf7f371598209063f7c7d41":"2021-02-09T00:36:34.040Z","1.4.14-handle-deadlocks-146b2bf8949182a935a92408364f39eed6d9bb49":"2021-02-09T00:37:30.581Z","1.4.15-renovate-non-major-76803c8cc2d25a5b518775b8cd90b1dedf7736c4":"2021-02-09T00:38:47.613Z","1.4.15":"2021-02-09T00:44:08.409Z","1.4.16-renovate-non-major-42ab956b26e7ad9f0202f04a5dab02b66a58cc1f":"2021-02-09T00:49:28.628Z","1.4.16-renovate-non-major-c903e7d465c58a8ba7eb8c0028dc2757a78f02fb":"2021-02-09T00:53:19.857Z","1.4.16":"2021-02-09T00:57:55.145Z","1.4.17-renovate-non-major-0b884ec5383513ec664713b9c7613422d72b2129":"2021-02-09T01:47:19.240Z","1.4.17":"2021-02-09T01:53:08.781Z","1.4.18-handle-deadlocks-36b7c91088c6eae3f700a7b0b25f75a7195e28b0":"2021-02-09T02:51:21.554Z","1.4.18-renovate-non-major-1d2db731d5d21ca9eab93ae97a75a5e45812462d":"2021-02-09T02:53:29.298Z","1.4.18":"2021-02-09T03:01:51.340Z","1.4.19-handle-deadlocks-4a957b4f074141c7b6480a5623b780ea27dfec5e":"2021-02-09T03:06:24.712Z","1.4.19":"2021-02-09T03:24:22.815Z","1.4.20-renovate-docker-non-major-27c3c42da20185ead5627ce7b8e0cb9d9e3b33ac":"2021-02-09T10:40:58.483Z","1.4.20":"2021-02-09T10:48:37.940Z","1.4.21-renovate-docker-non-major-36b1753718afb1f599babdb7a031ec2f99553e37":"2021-02-09T14:38:47.935Z","1.4.21":"2021-02-09T14:43:54.819Z","1.4.22-renovate-non-major-823eb79d6d13e14b523a2e56a76574e57b51e1f0":"2021-02-10T19:41:53.667Z","1.4.22":"2021-02-10T23:24:33.660Z","1.4.23-renovate-non-major-2fd25203120e45acdb80c4c32c6b509ec927927a":"2021-02-11T02:33:18.986Z","1.4.23":"2021-02-11T02:38:52.997Z","1.4.24-add-fullTextSearch-b089cd5f6540c608d3ea34960e12ca81e1fa948e":"2021-02-11T08:41:36.716Z","1.4.24":"2021-02-11T09:09:53.952Z","1.4.25-renovate-non-major-ceba5130e720f7eab15f320eb696b24497dd24fd":"2021-02-11T09:14:56.885Z","1.4.25":"2021-02-11T09:20:01.507Z","1.4.26-renovate-non-major-71e713b2aceb33412d463a547de8d3fb263fd56d":"2021-02-11T14:11:05.090Z","1.4.26":"2021-02-11T14:16:00.220Z","1.4.27-fts-index-concurrently-f74fed198e9fe973a2d13a1759f76a765626fe4d":"2021-02-12T03:09:27.658Z","1.4.27-fts-index-concurrently-8a245049a3f4e27221e06b8bd528b25422c38393":"2021-02-12T03:30:44.812Z","1.4.27":"2021-02-12T03:44:11.386Z","1.4.28-renovate-docker-non-major-a5c8de597d827d5c0179117993b041e5a8e7148a":"2021-02-12T13:25:40.240Z","1.4.28":"2021-02-12T13:30:08.999Z","1.4.29-renovate-non-major-78f3691907c57bccac280284620abf0fc3059637":"2021-02-12T23:18:55.527Z","1.4.29-renovate-non-major-982c553fb64e385b26c2e09c59b9f194d57f4d4c":"2021-02-13T03:44:25.299Z","1.4.29":"2021-02-13T03:50:45.655Z","1.4.30-renovate-non-major-b42f3f94b1bebeccd290d21d958b6d516732bd95":"2021-02-13T05:11:27.477Z","1.4.30":"2021-02-13T05:16:43.780Z","2.0.0-replace-date-columns-426b5d8f9c0df4cb7d8722bd089e3e18d3017498":"2021-02-13T05:21:48.746Z","1.4.31-renovate-non-major-0bb73f9a98726f4ac1a362c74a7f94ffa6194162":"2021-02-13T07:01:42.451Z","1.4.31":"2021-02-13T07:07:29.699Z","2.0.0-replace-date-columns-2d9f185741e075b5e48608630750798e27286262":"2021-02-15T04:22:58.203Z","2.0.0-replace-date-columns-44379a3f4592b6cf2687e97e1e46986d29e8723d":"2021-02-15T04:38:25.666Z","1.4.32-renovate-eslint-plugin-jsdoc-32-x-1c430f0dc3ace78fa6df312087ed19591336bdeb":"2021-02-15T04:44:18.052Z","2.0.0-replace-date-columns-d800884904c486e33d2c2fc2e2f4017ab3e1cd8e":"2021-02-15T05:41:50.369Z","1.4.32":"2021-02-15T05:45:44.695Z","1.4.32-renovate-non-major-a196ea7fca733f14943edfec3bc818c543d9364a":"2021-02-15T05:49:01.168Z","2.0.0-replace-date-columns-f525b0648e99bdc114fc0dd9e867f6b7e2e6d910":"2021-02-15T05:52:57.020Z","1.4.33-renovate-non-major-c25071a787175cf70400de67f95122d52e5c0936":"2021-02-15T07:27:29.952Z","1.4.33-renovate-non-major-8473fceb35817fc5cfee8b2bd70b1ed2132d61d5":"2021-02-15T09:21:20.983Z","1.4.33-renovate-docker-non-major-425cddc01e61e874294de224b0ce6c442f60e141":"2021-02-15T09:22:07.442Z","1.4.33":"2021-02-15T09:30:08.249Z","1.4.34-renovate-non-major-4012be64bde9d5b13cab443a12a0cd2402b860c2":"2021-02-15T09:34:06.528Z","1.4.34-renovate-non-major-e245fc58c77815e4a0e95d0f747095db9e12a8f1":"2021-02-15T09:40:16.674Z","1.4.34":"2021-02-15T09:45:17.652Z","1.4.35-renovate-non-major-d5b51f9715243a7f5878aea2fe0d8014130a1a4a":"2021-02-15T12:13:24.187Z","1.5.0-generic-oauth-1c7c0dedf2c52a9fea1ecf3a50b9ed50c2438454":"2021-02-15T17:15:36.884Z","2.0.0-replace-date-columns-d526b6a7cfc7f89126e11241ae1029dd938f22ac":"2021-02-15T22:52:12.587Z","1.4.35":"2021-02-15T22:57:26.395Z","2.0.0-replace-date-columns-9ad4cd83f2d2dddc4e248127dc8ded1d54b9dc64":"2021-02-15T23:27:12.627Z","1.4.36-renovate-non-major-a15b203617c3a1111bf160100749b0c9b486cb66":"2021-02-16T04:48:59.112Z","1.4.36":"2021-02-16T04:54:19.510Z","1.4.37-renovate-non-major-f8ff72947d4fb9660e8cc80a55093081f912251b":"2021-02-16T04:57:20.110Z","1.4.37":"2021-02-16T05:05:33.593Z","1.4.38-links-index-deadlock-fc5d0bb4e9d98e33424df2062e1cc99b82f51248":"2021-02-16T06:03:10.373Z","1.4.38-links-index-deadlock-ed7c1d653f3dd16b4ebe189258bb7b3a08053aa0":"2021-02-16T06:16:38.812Z","1.4.38-renovate-non-major-38c42070489ecb0a340c68e457c7fc234444bb4c":"2021-02-16T07:42:38.568Z","1.4.38":"2021-02-16T07:47:08.701Z","1.4.39-renovate-non-major-489ce933ffeb5b1e4fb694a8c421ba35c5742ae5":"2021-02-16T11:51:32.837Z","1.4.39":"2021-02-16T11:56:43.475Z","1.4.40-renovate-non-major-65ec93f733a45194633930935b56c6d0c256c3bd":"2021-02-16T13:03:54.662Z","1.4.40":"2021-02-16T13:09:12.170Z","1.5.0-generic-oauth-f430d26247bd2b5ecbba7d20c2633b1d52cc2150":"2021-02-16T13:09:43.523Z","1.4.41-renovate-docker-non-major-dda67c292f0c8a888208be6d1913a4504eff44bd":"2021-02-16T15:03:33.976Z","1.4.41":"2021-02-16T15:08:19.105Z","1.4.42-renovate-non-major-cf0cd445a2282889e377f2baa66aebc53e50574a":"2021-02-16T17:34:01.348Z","1.4.42":"2021-02-16T17:39:36.368Z","1.4.43-renovate-non-major-294b268c1cbcd428fc561f13fc49dfb231d3d403":"2021-02-16T19:05:39.221Z","1.4.43":"2021-02-16T19:10:47.430Z","1.4.44-renovate-non-major-28aa69d2f0fe70d18c51dfe0eb7e26181c24dcf2":"2021-02-16T20:52:19.182Z","1.4.44":"2021-02-16T20:57:14.250Z","1.4.45-renovate-non-major-2ba811273b37ae1173ed01a02a594490095ea45a":"2021-02-17T00:23:16.082Z","1.4.45-links-index-deadlock-0459e8da7a823d8c40c2e83cb9e2f53fbbed6b57":"2021-02-17T00:29:14.157Z","1.4.45":"2021-02-17T00:29:38.196Z","2.0.0-replace-date-columns-cf9f2d83ee3968391bedd7b7dff29ba64a55650f":"2021-02-17T00:33:08.234Z","1.4.46-renovate-non-major-87d4616296be111967b88617003242d48bae8644":"2021-02-17T04:01:49.201Z","1.4.46":"2021-02-17T04:06:56.530Z","1.4.47-create-index-not-concurrently-80ab3500eb8cf22e69499007ccb22487fa56d1d7":"2021-02-17T04:49:38.282Z","1.4.47-create-index-not-concurrently-187f2ab560a35a5b927ce8ef2bbe720dfee34c40":"2021-02-17T08:10:33.437Z","1.4.47-set-upstreams-5a9d9389d730545ecbeba32b2131fc776f6e4682":"2021-02-17T08:57:31.688Z","1.4.47":"2021-02-17T09:02:26.746Z","2.0.0-replace-date-columns-d6a4d94dd6d5e5c51ba720c484c24e75b66fc7e8":"2021-02-17T10:35:21.727Z","2.0.0":"2021-02-18T00:57:42.075Z","2.0.1-create-index-not-concurrently-5c9c433e083223bc6e96582171e58bf626810ba4":"2021-02-18T02:56:46.665Z","2.0.1":"2021-02-18T05:27:32.955Z","1.5.0-generic-oauth-b6425ad6fd65add1dc6ed054a70a14ed8678a6ad":"2021-02-18T08:14:16.007Z","2.0.2-renovate-non-major-abf1556e4109657527dfab12a13c32ea7dc8e19a":"2021-02-18T08:50:53.574Z","1.2.0-5508-pre-build-versions-96d29700400169b9f1cb17b0135ce7a020c10c6e":"2021-02-18T08:51:42.428Z","2.0.2":"2021-02-18T08:57:19.232Z","2.0.3-renovate-non-major-f3ad5c7ea2425c31894cf52f2567f0b0bc70957f":"2021-02-18T11:59:44.702Z","2.0.3":"2021-02-18T12:04:39.803Z","2.0.4-add-versionbot-dir-53fd1c673290529f6b6179435e594b90d6c12613":"2021-02-19T00:39:43.915Z","2.0.4":"2021-02-19T00:46:28.012Z","2.0.5-add-changelog-yml-a16eb66eea661d9902728aec0cb8f12c6cee18fd":"2021-02-19T01:06:09.932Z","2.0.5":"2021-02-19T01:13:12.819Z","2.0.6-remove-references-to-old-date-columns-86b5f01f87598b972c87a04346e1a516c9e61a09":"2021-02-19T02:08:40.538Z","2.0.6-rename-changelog-yml-aedfb49fe211fee98d185479fc8d60510d8e9ba3":"2021-02-20T08:15:36.498Z","2.0.6":"2021-02-20T08:21:06.088Z","2.0.7-renovate-non-major-0505bb5b6a7a275e258d0e30c19d7d7f48a94b4e":"2021-02-20T09:51:03.729Z","2.0.7":"2021-02-20T09:56:50.039Z","2.0.8-renovate-non-major-2da079d5922e39e93ade061966ca6d019f76a82c":"2021-02-20T10:25:22.136Z","2.0.8":"2021-02-20T10:32:00.519Z","2.0.9-renovate-non-major-0deddaf7c1d6b02f9d00a62e154b1e4c59a7da0a":"2021-02-20T16:45:25.541Z","2.0.9":"2021-02-20T16:51:30.921Z","2.0.10-renovate-non-major-ef3e11f60bae284d350b18d7e6fcd097fddd1ba0":"2021-02-20T16:55:47.917Z","2.0.10":"2021-02-20T17:03:26.457Z","2.0.11-renovate-non-major-d036cdcf1b7465da7ab184459ffeb201e642ef4d":"2021-02-20T19:09:31.520Z","2.0.11":"2021-02-20T19:15:05.723Z","2.0.12-renovate-non-major-818e13c9da713a5cdcdf94c23c48f1116ce108bf":"2021-02-21T04:50:23.866Z","2.0.12":"2021-02-21T04:56:04.702Z","2.0.13-renovate-non-major-2fd8f88fe0d70a5fc1eb896963a22f5fdadec77c":"2021-02-21T06:09:41.738Z","2.0.13":"2021-02-21T06:14:55.040Z","2.0.14-renovate-non-major-36642058c18426d842938fcd537e32e16d86aab8":"2021-02-21T22:32:11.156Z","2.0.14":"2021-02-21T22:37:43.867Z","2.0.15-renovate-non-major-62e920ee879ef3bed393599fc0135fd9e4b549c7":"2021-02-22T00:55:43.957Z","2.0.15":"2021-02-22T01:00:55.611Z","2.0.16-remove-references-to-old-date-columns-ac2618d2a774f0c80279ffca5267dc33a3d9d86b":"2021-02-22T07:57:56.296Z","2.0.16-renovate-non-major-734e82ce82b1bb6fecfd255be358cc9c76552c73":"2021-02-22T17:10:00.490Z","2.0.16":"2021-02-22T17:14:16.254Z","2.0.17-renovate-non-major-93bd20a6637bd2fc83571564cd215061ed6ca90c":"2021-02-22T20:10:25.268Z","2.0.17":"2021-02-22T20:13:52.319Z","2.0.18-renovate-non-major-5ddff8252e902592bbf887c1ec7728a2a98fb6bb":"2021-02-22T22:48:47.596Z","2.0.18":"2021-02-22T22:53:58.827Z","2.0.19-renovate-non-major-32a05ff06756961e11ba54f2982857a82b9b0ddb":"2021-02-23T03:04:32.578Z","2.0.19":"2021-02-23T03:09:16.399Z","2.0.20-renovate-non-major-010578f906ea2231274cce91ae590111afbeb88b":"2021-02-23T04:38:49.411Z","2.0.20":"2021-02-23T04:43:33.664Z","2.0.21-renovate-non-major-536fa2221f0a5a5bc98ef61da3dbcfedf01b2433":"2021-02-23T11:42:36.863Z","2.0.21":"2021-02-23T11:48:15.748Z","2.0.22-renovate-non-major-7da77aac8e9eb924cc25fad343b1d7ab930a9542":"2021-02-23T13:04:05.452Z","2.0.22":"2021-02-23T13:11:06.132Z","1.5.0-generic-oauth-332c516a482fb82054d1dcc1dc1c77b07f208130":"2021-02-23T15:51:22.102Z","2.0.23-renovate-non-major-d2aa6345a204166342fb8559f32b80f23bd4eb12":"2021-02-23T16:06:50.017Z","2.0.23":"2021-02-23T16:10:27.002Z","2.0.24-update-date-time-regex-0a8a80e00400e27a73f06e0ceb503acc73c75f71":"2021-02-24T01:13:50.968Z","2.0.24-update-date-time-regex-710f2563ae6b4d8fe780f20f9c2afd7f8e8bd68f":"2021-02-24T01:39:56.055Z","2.0.24-update-date-time-regex-8b971ab980f173835ac536c75a5e8b2c30e47764":"2021-02-24T02:06:26.995Z","2.0.24-update-date-time-regex-89a7ef7aeb00196684b033dfd44eadd1a15a3dbf":"2021-02-24T02:29:33.531Z","2.0.24-update-date-time-regex-3ba08bc9d13d6c13515e42876f5d3069addad775":"2021-02-24T02:34:51.426Z","2.0.24":"2021-02-24T02:44:29.189Z","2.1.0-remove-lenses-from-view-0e311517c1e18d01db78e4c0763d2d4d1eea525a":"2021-02-24T07:43:02.667Z","1.5.0-generic-oauth-6a8a0f9cca88c4ab5fa4075f00f17b94f5769c00":"2021-02-24T14:24:21.168Z","1.5.0-generic-oauth-edb38f80aba4c9c511a1b9f93892bbc502f6a774":"2021-02-24T14:37:10.823Z","1.5.0-generic-oauth-b73c422bf4d1f704206757993a919bcee4f06c02":"2021-02-24T14:54:03.208Z","2.0.25-renovate-docker-non-major-794c536eaf7530c1019787dfc8c7a77e1ba5025b":"2021-02-24T16:23:25.542Z","2.0.25":"2021-02-24T16:29:05.842Z","2.0.26-renovate-docker-non-major-2a2d47e36a888d6969af087d2b2e5dc45bf05c8e":"2021-02-24T19:01:37.459Z","2.0.26":"2021-02-24T19:42:00.808Z","2.0.27-remove-references-to-old-date-columns-ec0c3dd0cc161280d0a13d4f872b91804e370901":"2021-02-24T23:25:23.658Z","2.0.27":"2021-02-24T23:43:23.584Z","2.0.28-fts-index-check-exists-cd309bbaf18f955badfbbe660f53b772e1913b83":"2021-02-25T03:20:09.622Z","2.1.0-fulltextsearch-on-user-name-9805651b6c500a61e5ceb1380612387fc1f152cb":"2021-02-25T03:36:06.056Z","2.1.0":"2021-02-25T03:52:22.925Z","2.1.1-fts-index-check-exists-563c7aa0df8b30eed0547ffa43bd0c656c712845":"2021-02-25T04:21:27.763Z","2.1.1":"2021-02-25T07:32:07.845Z","2.1.2-trigger-ignore-versions-0b304fe28db848e244ff44515295b07aa4258b07":"2021-02-25T23:46:53.942Z","2.1.2":"2021-02-25T23:53:22.513Z","2.1.3-renovate-non-major-9ac87bed469bd63edf444cd05ab7a7006ef6012f":"2021-02-26T05:47:57.232Z","2.1.3":"2021-02-26T06:11:28.615Z","2.2.0-add-authentication-token-58edd7fdfd604936be81270a67ebf30d181bf6f8":"2021-02-26T06:46:04.880Z","2.2.0-add-session-scope-5a39ce1bcc9d174e0cf10d5a9c7b7f4cc5b176b1":"2021-02-26T06:55:59.308Z","2.2.0-add-authentication-token-05415ae14b3e502a05af9063a6c1728c2df0782c":"2021-02-26T06:57:55.666Z","2.2.0-add-authentication-token-1b858902261497524135b1d442e0560cc321edf0":"2021-02-26T07:07:19.168Z","2.1.4-renovate-docker-non-major-f5e0a3ee3009f6b5185ae81e584961d37cbc4d1d":"2021-02-26T12:28:41.483Z","2.1.4":"2021-02-26T12:33:40.725Z","2.1.5-renovate-non-major-e6ea857ea00bb83cf1549883bc3d79fa7028ce62":"2021-02-26T12:39:25.409Z","2.1.5-renovate-non-major-aab7f35b0721576ef244008309e89a09959d0212":"2021-02-26T13:52:13.062Z","2.1.5":"2021-02-26T14:02:30.691Z","2.1.6-renovate-jsdoc-to-markdown-7-x-5a123ecc78c3ab1c5c2e2e978b21222a6a42af39":"2021-02-26T23:16:57.125Z","2.1.6-renovate-non-major-83b30455a74fc62e146dce5dd833e48961f4b5c7":"2021-02-27T23:45:16.231Z","2.1.6":"2021-02-27T23:51:39.233Z","2.1.7-renovate-jsdoc-to-markdown-7-x-aeb530c429842d9826cf98af020ace1dfd549960":"2021-02-27T23:55:25.907Z","2.1.7":"2021-02-28T00:45:29.323Z","2.1.8-renovate-non-major-7c4c67a1c3c11d99fe2be84002423f5c1f4c80ba":"2021-02-28T02:43:25.690Z","2.1.8-renovate-non-major-71868f9283c2adc063718d3caeb4e32c57052858":"2021-02-28T04:38:16.062Z","2.1.8-renovate-non-major-36477c225c83cf589c1d4adc4fc5855ff4608aae":"2021-02-28T08:18:44.304Z","2.1.8":"2021-02-28T08:23:29.964Z","2.1.9-renovate-non-major-51bcb6ca6ec4936c6352da074d1ee8372cb5d543":"2021-03-01T02:20:53.806Z","2.1.9-renovate-non-major-2351fd045b889df0f920062a6eb7c197b4d52233":"2021-03-01T02:25:51.066Z","2.1.9":"2021-03-01T02:32:02.685Z","2.2.0-remove-user-slug-fulltextsearch-162f62d3b8366418372d16f88ef0a8b63f6db8ff":"2021-03-01T02:51:10.640Z","2.1.10-renovate-non-major-04114a92beff255ac09ff8e4e76757c6bccf721f":"2021-03-01T04:22:18.761Z","2.1.10":"2021-03-01T04:28:12.343Z","2.2.0-remove-user-slug-fulltextsearch-6afc2f4eca1f9825f4fdbc52d085713d17c38562":"2021-03-01T08:50:26.611Z","2.1.11-renovate-non-major-25248fadaa0918b17707c4050d6fb8e6769bec37":"2021-03-01T08:54:26.330Z","2.2.0":"2021-03-01T08:56:32.138Z","2.2.1-renovate-non-major-c40e42627e98704e35d8319bf25ef3cda2240302":"2021-03-01T09:02:42.500Z","2.2.1":"2021-03-01T09:07:28.419Z","2.3.0-remove-lenses-from-view-ba57b51ca19c3f9b90d12e6d830e4d3447bd91fa":"2021-03-01T09:14:12.686Z","2.1.11-handle-error-events-on-stream-1121bac66fc06460aedacc5cdd55f6864b19a6db":"2021-03-01T10:04:59.433Z","2.2.2-handle-error-events-on-stream-b7d7e526e9b476e26b35f6e501861cbc0c78e0cf":"2021-03-01T13:03:31.513Z","2.2.2":"2021-03-01T13:28:02.750Z","2.3.0-remove-lenses-from-view-8fef319d47a8ff8e0acdd58c71ec1d1610d1064a":"2021-03-01T15:59:51.341Z","2.3.0":"2021-03-01T16:05:23.939Z","2.3.1-renovate-non-major-d0faab464230debd406fe2899c4615e099f0d5bc":"2021-03-01T18:01:03.006Z","2.3.1":"2021-03-01T18:07:11.696Z","2.3.2-renovate-non-major-6600b7fa397317fa775bb9921ae25fe1a2b9ae69":"2021-03-01T19:25:34.447Z","2.3.2":"2021-03-01T19:31:23.522Z","2.3.3-renovate-non-major-991e70371362ec72be9c43aa68741ff62438edea":"2021-03-02T04:23:30.177Z","2.3.3":"2021-03-02T04:28:35.925Z","2.3.4-renovate-non-major-09cdf8ec82c144c799ff7a5dffbbab23dcaa62d1":"2021-03-02T09:35:49.054Z","2.3.4":"2021-03-02T09:42:03.645Z","2.4.0-5508-pre-build-versions-d2eed7c182d4c3d1f8bb76cc39c1d7dc00924143":"2021-03-02T14:46:20.337Z","2.4.0-5508-pre-build-versions-26fafe7cf76bb822efe0b733c40dc337ee3e628f":"2021-03-02T15:00:48.900Z","2.4.0-5508-pre-build-versions-370416e7e775acd5445c12b4014821d16abe7f6a":"2021-03-02T15:20:33.492Z","2.3.5-hotfix-test-error-959a174eb83cefb31fa7b38ab36ed180e8225787":"2021-03-02T16:00:15.182Z","2.3.5":"2021-03-02T16:11:02.522Z","2.4.0-reinstate-user-slug-index-0d3c229927a1c418e89ae134fb18d0c62ed16927":"2021-03-03T02:55:44.423Z","2.4.0":"2021-03-03T03:01:44.273Z","2.5.0-generic-oauth-0ed63c3b0373cabf6b2f138900883756a89dfa0a":"2021-03-03T23:05:02.965Z","2.5.0-generic-oauth-188d212d7367f1e703e284193f04c6b0e7872240":"2021-03-03T23:15:17.979Z","2.5.0-5508-pre-build-versions-a94cbb1bfaab74b543ec4418229a5818763a2c87":"2021-03-03T23:35:58.792Z","2.5.0":"2021-03-04T00:40:08.362Z","2.6.0-5508-pre-build-versions-c15bcf123db9df0bd64f4804bf919ee1012f35b5":"2021-03-04T00:48:38.747Z","2.5.1-renovate-non-major-967c6308b933f426c35b89907b17e277256e80ec":"2021-03-04T07:46:12.024Z","2.5.1":"2021-03-04T07:52:25.905Z","2.5.2-trigger-include-versions-0f9822215cfd16cb88009f335b8f3d1ccc92d8c8":"2021-03-04T08:32:06.523Z","2.5.2":"2021-03-04T08:37:33.127Z","2.6.0-5508-pre-build-versions-1c1b01cf26fe99677ea729f379542a7b5782e08b":"2021-03-04T09:58:00.715Z","2.5.3-renovate-non-major-e9f2008f0eade0de03163df1cf9a58066439be34":"2021-03-04T10:05:17.738Z","2.5.3":"2021-03-04T10:10:28.203Z","2.5.4-renovate-non-major-4008a66dd362fa23724b0d7a0ef85ee852523d21":"2021-03-05T02:24:47.848Z","2.5.4":"2021-03-05T02:30:55.326Z","2.6.0-5508-pre-build-versions-a11482ad7b48c760366fc8a3244ea9c4d3dde694":"2021-03-05T09:24:25.561Z","2.6.0-5508-pre-build-versions-99bc3a916b4a6b1068eb867244059c9c2be32856":"2021-03-05T09:33:20.732Z","2.6.0-pagination-fixes-18c1799eee418c41d33c2f8846c0a6d3355ee81e":"2021-03-05T10:56:23.915Z","2.5.5-renovate-non-major-d31725bd46d4d0e85857f06dc15c588c0d3dd666":"2021-03-06T18:29:05.169Z","2.5.5":"2021-03-06T18:35:03.301Z","2.6.0-5508-pre-build-versions-ccd4cfe66ba9453149322805189d126d5057c83d":"2021-03-08T04:56:12.037Z","2.6.0-disable-notification-sound-486623552df2d68df0bbce1d48bc17c08ae54741":"2021-03-08T09:48:33.434Z","2.6.0-disable-notification-sound-6aef2c24c38bdb7a3e346af7005e7b13cea9fbea":"2021-03-08T10:52:29.824Z","2.6.0-link-name-index-c8aa052d28f9902b5ff81282eb93ae6390a88452":"2021-03-09T02:54:44.358Z","2.6.0":"2021-03-09T03:00:44.987Z","2.7.0-5508-pre-build-versions-325f81642157acebe265ab6ebd5eb4f56d183e21":"2021-03-09T03:39:43.990Z","2.7.0-5508-pre-build-versions-7d71a5299004fd67c4bf5e3367384f597969d9a6":"2021-03-09T05:01:56.567Z","2.7.0-disable-notification-sound-661f3fbc8cbb6bba63589af80b771abd8628a888":"2021-03-09T06:18:45.105Z","2.7.0":"2021-03-09T06:28:00.986Z","2.8.0-5508-pre-build-versions-ddc85469f359d17404888ac66b5ce0394c324676":"2021-03-10T03:39:22.038Z","2.7.1-renovate-non-major-e211a8fb90798c1af3887db49d4c21843a7a781c":"2021-03-10T09:34:15.259Z","2.7.1":"2021-03-10T09:40:33.207Z","2.8.0-use-jellyfishlink-widget-0d722de501182049612e639adb40ed555836580c":"2021-03-10T10:04:51.969Z","2.8.0":"2021-03-10T10:10:51.200Z","2.8.1-renovate-non-major-626887742da78e873412df341953e7ed98ebb755":"2021-03-10T10:16:55.888Z","2.8.1-renovate-non-major-849d7802ce4a071e9f0c3ce5a79e087ed9c5e6b6":"2021-03-11T00:42:23.134Z","2.8.1":"2021-03-11T00:48:05.078Z","2.8.2-renovate-non-major-696996055950645ba7951fa560da599207445dba":"2021-03-11T02:42:10.194Z","2.8.2":"2021-03-11T02:47:04.994Z","2.8.0-5508-pre-build-versions-f63c974af79ac7fa5bb2b2857669febe3e9062b8":"2021-03-11T07:54:16.344Z","2.8.3-tidy-cards-export-50696fcef4e13db126cff42424d727c5ca19cf4a":"2021-03-11T09:44:43.906Z","2.8.3":"2021-03-11T10:23:52.131Z","2.8.0-5508-pre-build-versions-4bef413c4329a57206e11a4421b957b7989b8f9b":"2021-03-11T10:24:37.541Z","2.9.0-add-session-scope-ccf308c644fcbc2d9b869f063dd96d5838447552":"2021-03-11T10:41:29.325Z","2.9.0":"2021-03-11T10:46:49.409Z","2.9.1-renovate-non-major-300223f5649836d61e9191c849ae8788bba31a4e":"2021-03-11T13:59:55.708Z","2.9.1":"2021-03-11T14:05:21.353Z","2.9.2-renovate-non-major-e78e5862c36b72e730e1c704c1da5f1f3556ac63":"2021-03-11T14:13:22.815Z","2.9.2":"2021-03-11T23:38:55.781Z","2.9.3-renovate-non-major-9eb62acb4590bf40f05167d881b84b6c572d5d84":"2021-03-12T01:58:13.636Z","2.9.3":"2021-03-12T02:03:25.687Z","2.9.4-renovate-non-major-04424fe349564d15b7c773a496b2f85ba5ef31ea":"2021-03-12T02:18:02.890Z","2.9.4":"2021-03-12T02:24:02.754Z","2.10.0-add-authentication-token-2ad4a2ce61ac14299c357a9be9c95571ed551cfa":"2021-03-12T05:02:39.091Z","2.10.0-5508-pre-build-versions-5d325a5396ac2644ccb8752c516a38e8b150bdca":"2021-03-12T05:03:51.692Z","2.9.5-remove-new-version-columns-52033885c8dcffadf5366bf3baf89bcff904afa0":"2021-03-12T06:02:44.967Z","2.9.0-use-jellyfishuserwidget-1d9028e2143d295b9a52f7fdbe8af6a5fc1f36cd":"2021-03-12T06:30:50.010Z","2.9.5-add-order-by-version-test-b71a069839cffce0685bf8abd56156494e8e106a":"2021-03-12T06:58:36.914Z","2.9.5-add-order-by-version-test-7a1a0949b043ef265c2f156cad5f0899bd9609f7":"2021-03-12T07:16:40.852Z","2.9.5":"2021-03-12T07:21:37.610Z","2.10.0-add-authentication-token-5a1aed2bb4d78a414e88d9e44ab80a9d6295e863":"2021-03-12T08:15:03.173Z","2.8.0-5508-pre-build-versions-19f80f3e1350709413de9173d06ace3463b215d2":"2021-03-12T08:20:34.571Z","2.9.6-renovate-non-major-a449bac2dfb0164628792a29ea490fdbd4b687ee":"2021-03-12T11:30:58.328Z","2.9.6":"2021-03-12T11:37:16.240Z","2.9.7-renovate-non-major-565f4d7cc71a552cb04f2c0e020df7504efba2c0":"2021-03-12T22:21:05.855Z","2.9.7":"2021-03-12T22:25:43.859Z","2.9.8-remove-new-version-columns-d988e0746c186d47452d01ad1822d90056e43fcb":"2021-03-12T22:37:53.257Z","2.9.8":"2021-03-12T23:58:54.483Z","2.9.9-renovate-non-major-8fd14b183d45150582cfda6f71074656371093ec":"2021-03-13T00:04:02.509Z","2.10.0-add-authentication-token-370922573c1152d370ae9ac119346b1e6f6598ed":"2021-03-13T00:12:57.872Z","2.9.9-renovate-non-major-83743254156ef242099eb5748a91ac03202717b1":"2021-03-13T02:34:13.226Z","2.9.9":"2021-03-13T02:38:57.660Z","2.9.10-renovate-non-major-fd5976185cb9311e7dfbbedbbff2747e0e4bcf79":"2021-03-13T02:43:11.573Z","2.9.10":"2021-03-13T02:49:47.612Z","2.10.0-add-authentication-token-40c36bd75768f562c82148e3da89778a721591a9":"2021-03-13T09:44:22.976Z","2.10.0":"2021-03-13T12:25:47.534Z","2.11.0-5508-pre-build-versions-7477a2ce1ece38319bc4986612699b0a9c22f59e":"2021-03-15T01:30:54.620Z","2.11.0-5508-pre-build-versions-43fc98f5db7d9d34c0dfb01e26b2f3eab2b31653":"2021-03-15T01:41:31.609Z","2.10.1-add-version-sort-test-f10bfccdcf38c0a8bfbabe9871cb19c69d2d0a27":"2021-03-15T02:07:40.746Z","2.10.1":"2021-03-15T02:12:50.849Z","2.11.0-5508-pre-build-versions-f0cbc2cebfb1f779795facbff7aa6656d92b50fd":"2021-03-15T02:16:18.448Z","2.11.0-5508-pre-build-versions-6c324acaed79703c1aad101938974060117232bd":"2021-03-15T02:28:51.189Z","2.10.2-renovate-non-major-8e617bf24b2ac76794cf56c701e0db6c22d5b0ff":"2021-03-15T13:33:56.298Z","2.10.2-renovate-non-major-4fa134862af876f977cb773213f7cc1f8b3f67e4":"2021-03-15T13:39:48.456Z","2.10.2":"2021-03-15T13:47:37.536Z","2.10.3-renovate-non-major-183b3a05f986afb29bf80ce5428f5f60faf9aea1":"2021-03-15T15:39:39.352Z","2.10.3-test-env-1bb68eafba6b7743976d98830410c210d9a4e9ae":"2021-03-16T01:18:40.665Z","2.11.0-use-jellyfishuserwidget-beb2de350e7f7cec95ad5a9fd3b70c98e577b0fe":"2021-03-16T01:32:36.269Z","2.11.0":"2021-03-16T01:36:04.933Z","2.11.1-renovate-non-major-6ef2dffab4c246c2de42adc2a6cdbaca8338e93a":"2021-03-16T01:42:13.982Z","2.11.1":"2021-03-16T01:48:44.254Z","2.12.0-5508-pre-build-versions-df6d57c7711b72b16d1785ebdc72b4d9ed92d633":"2021-03-16T02:08:33.431Z","2.12.0-5508-pre-build-versions-d20a679c13f93e1649ec37622eb547809207d290":"2021-03-16T03:32:51.631Z","2.12.0-5508-pre-build-versions-896cfba43ceb6c74214a53b11d6c0e3cb6c7a12f":"2021-03-16T06:55:49.293Z","2.11.2-test-env-c02d1447f3c4045d6433f16111c67933ca2928ef":"2021-03-16T09:30:11.963Z","2.12.0-5508-pre-build-versions-700434fcd514ae46d516c7cee002dd5107b90f1e":"2021-03-16T10:37:38.988Z","2.12.0-5508-pre-build-versions-0f7a241bb8fa57dc85198af05c154b14cc1bec6e":"2021-03-16T20:27:35.346Z","2.11.2-test-env-d7c9e7fa6446f2e2dbdf00ef65b7651b59e1f55e":"2021-03-17T01:13:32.935Z","2.11.2-renovate-balena-jellyfish-environment-3-x-c9f9a27a600d6be539b4a0e8cecb4f8be02b1a3f":"2021-03-17T09:40:00.252Z","2.11.2-renovate-docker-non-major-2711eafbc0c0ce98662cc5102680697016861c94":"2021-03-17T18:19:55.526Z","2.11.2":"2021-03-17T18:26:13.842Z","2.11.3-renovate-balena-jellyfish-environment-3-x-3248a7d693ab90f4dd4c485f8acd965ddeeecc63":"2021-03-17T18:30:52.468Z","2.12.0-5508-pre-build-versions-4fd557a0782a5c4ff7555a4e6f0a4ce7944e6029":"2021-03-17T22:57:21.325Z","2.11.3":"2021-03-18T00:41:22.646Z","2.11.4-renovate-non-major-07cf39f3f168feea1ef0a8c105dd22ef121104de":"2021-03-18T02:47:52.610Z","2.12.0-5508-pre-build-versions-1f2429c85aa65ede8256d59bb5eabaa666623ca7":"2021-03-18T03:50:45.303Z","2.12.0":"2021-03-18T04:00:50.208Z","2.12.1-renovate-non-major-6ea838b4307a4190ac6048f21cf02761e2f09bbf":"2021-03-18T04:20:42.799Z","2.12.1":"2021-03-18T04:33:59.294Z","2.12.2-renovate-non-major-37f51b036c92e4c3e98091dbdcfff7ca88a3b02b":"2021-03-18T04:38:00.111Z","2.12.2":"2021-03-18T04:47:54.572Z","2.12.3-remove-version-migration-3bc335f8e28bebb08739f781438e271ed605392f":"2021-03-18T08:58:41.462Z","2.12.3":"2021-03-18T09:06:40.624Z","2.12.3-add-version-triggers-c768bab218beb9f864fdd6414eca90b102fbdff7":"2021-03-18T09:14:07.033Z","2.12.4-add-version-triggers-8a48c5b9562142d5a129d98c82c0a6a118d373ea":"2021-03-18T10:48:24.984Z","2.12.4":"2021-03-18T10:53:07.226Z","2.12.5-renovate-non-major-0a9981d97ae92fb00a950818842997413acea783":"2021-03-19T21:31:04.808Z","2.12.5":"2021-03-19T21:37:16.437Z","2.12.6-renovate-non-major-f148a7dfc0a88c5fb0a50ec9a4a6de051043302d":"2021-03-21T20:50:02.400Z","2.12.6-renovate-non-major-d0d8c8da1158cff79a659271696412544151fcb0":"2021-03-21T20:56:37.736Z","2.12.6":"2021-03-21T21:02:53.012Z","2.12.7-renovate-non-major-a64d36a0c11d31443307aef469235516d8f5ef54":"2021-03-21T23:13:04.451Z","2.12.7":"2021-03-21T23:18:29.958Z","2.12.8-renovate-non-major-38852a566432bca645dc14263f5481428cfc79fb":"2021-03-22T04:28:48.422Z","2.12.8":"2021-03-22T04:34:32.748Z","2.12.9-renovate-non-major-8575625438e8218d4be6e9c268186c8a99613f9d":"2021-03-22T09:31:32.171Z","2.12.9":"2021-03-22T09:37:41.440Z","2.12.10-renovate-non-major-40691470b4d981b7b127eb54dd186a951927a120":"2021-03-22T11:54:55.001Z","2.12.10":"2021-03-22T12:02:24.404Z","2.12.11-renovate-non-major-68c1093b2b0b0e99c1d69943142ab3c9420c2a5e":"2021-03-22T13:41:27.947Z","2.12.11-renovate-balena-jellyfish-logger-2-x-675b1730c5c2156efcebd2b77571fb3b0cd77c7c":"2021-03-22T18:16:02.191Z","2.12.11-renovate-balena-jellyfish-environment-4-x-3f8facb26d0ead8be819218df4c58595e46c5ffa":"2021-03-23T01:35:40.844Z","2.12.11-renovate-balena-jellyfish-logger-2-x-c247a9d50d0b5a4dd1498d599a711ae4d3453509":"2021-03-23T03:46:20.109Z","2.12.11-renovate-balena-jellyfish-logger-2-x-2f9eb839664a93ff83219f6820749d800b3ddae5":"2021-03-24T03:36:41.861Z","2.12.11-renovate-balena-jellyfish-logger-2-x-9e7dddf4637c0466e1a3a3a2a122be42c493c1ff":"2021-03-24T05:39:21.823Z","2.12.11-renovate-balena-jellyfish-logger-2-x-8f4fdc74740009ce2dbbdeaf4c2890839fcfbefe":"2021-03-24T10:46:49.005Z","2.12.11-renovate-balena-jellyfish-metrics-1-x-40ec0069938950f71a9f4f868240e8537bc3b24a":"2021-03-24T10:48:05.340Z","2.12.11-renovate-balena-jellyfish-metrics-1-x-81c114611dba7211521e5af3c3f007f2c7139746":"2021-03-24T13:11:15.477Z","2.12.11-renovate-non-major-b5b9ad91ec958d04232cd28395b63c5864098d7b":"2021-03-25T01:27:37.815Z","2.12.11-renovate-balena-jellyfish-environment-4-x-15944305233e6e95c6d63d8bbe749d28b82f162a":"2021-03-25T01:28:04.452Z","2.12.11-renovate-balena-jellyfish-metrics-1-x-e3e24e5d18f87edff0cd5ac18e2c78a67ee2361c":"2021-03-25T01:32:54.445Z","2.12.11":"2021-03-25T01:35:13.053Z","2.12.12-renovate-balena-jellyfish-environment-4-x-440f3138ad7f067b0f9dc864d75fadb832afb487":"2021-03-25T01:39:49.214Z","2.12.12-renovate-balena-jellyfish-metrics-1-x-6d0c8863d39a66b90fc148ac5b385a6ff127a530":"2021-03-25T01:41:34.559Z","2.12.12-renovate-non-major-b0a9cdf254ba36d62b0d1b960b6fded5a479aa85":"2021-03-25T03:54:58.657Z","2.12.12-renovate-balena-jellyfish-environment-4-x-c1c4eeb1b8b98e4b2059ad2e7b36baf47b209e1c":"2021-03-25T03:55:15.116Z","2.12.12-renovate-balena-jellyfish-logger-2-x-8788913981f11494e384b1fde799d6d955d3a0e1":"2021-03-25T03:56:41.513Z","2.12.12":"2021-03-25T04:01:39.769Z","2.12.13-renovate-balena-jellyfish-logger-2-x-1258cdec2f5350388ccee8ae5dd0d6a18bf88625":"2021-03-25T04:06:02.406Z","2.12.13-renovate-balena-jellyfish-metrics-1-x-41ad835ab97058e758dd369926b3a4e00d6c589b":"2021-03-25T04:06:28.526Z","2.12.13-renovate-balena-jellyfish-environment-4-x-f62d86f7578b64be338fa82ba1f9970d8c81b4a4":"2021-03-25T04:08:07.884Z","2.12.13-renovate-balena-jellyfish-metrics-1-x-2ed56136e50369ad0cc2a111d46033c675044143":"2021-03-25T05:45:40.128Z","2.12.13-renovate-balena-jellyfish-logger-2-x-7161784381e7b71bc74d32bf38e6b270eb5813c0":"2021-03-25T05:45:51.520Z","2.12.13-renovate-non-major-76c503bdfdd15a87e68883e98189112cefa7fa81":"2021-03-25T11:30:46.409Z","2.12.13-renovate-balena-jellyfish-environment-4-x-a5f46f36d221d2d221c0e446a54544f446395d8b":"2021-03-25T11:32:08.900Z","2.12.13-renovate-balena-jellyfish-logger-2-x-086b40e0181baf4bc10b70a3c59afb87e16edc45":"2021-03-25T11:32:59.229Z","2.12.13-renovate-balena-jellyfish-metrics-1-x-b889446cf40b51789172e2064258815201ac8c1e":"2021-03-25T11:34:17.620Z","2.12.13-renovate-non-major-edbf5f0cd6e4fbc6b3fd267a7a8d5fbdea0eaf09":"2021-03-25T11:36:01.693Z","2.12.13":"2021-03-25T11:41:45.720Z","2.12.14-renovate-balena-jellyfish-environment-4-x-8471870e2e5472dbcd41cafa912bf72834566e62":"2021-03-25T11:46:18.061Z","2.12.14-renovate-balena-jellyfish-metrics-1-x-308bd3f5551523d70eaf4a943a33f3ad8bdbee4c":"2021-03-25T11:48:23.116Z","2.12.14-renovate-balena-jellyfish-logger-2-x-6ba38452a0045487e814bb249c8c4e4f49968e04":"2021-03-25T11:50:18.589Z","2.12.14-renovate-non-major-71a11d8f50701305e85dc28c1bf3f44f3073b690":"2021-03-25T14:58:53.628Z","2.12.14-renovate-balena-jellyfish-metrics-1-x-062c52276f82980cea97ec070559211fb853ee5e":"2021-03-25T14:59:22.573Z","2.12.14":"2021-03-25T15:04:20.551Z","2.12.15-renovate-balena-jellyfish-environment-4-x-a0a70ba9079e7b5c2b35f5e54c3c929ff69e73f3":"2021-03-25T15:08:24.819Z","2.12.14-renovate-balena-jellyfish-logger-2-x-cd346ea7f0ae04587c208a37f579927761cedf41":"2021-03-25T15:08:51.940Z","2.12.15-renovate-balena-jellyfish-metrics-1-x-a29200eae0c286cad5adaa451e1e7501d4df873a":"2021-03-25T15:10:31.799Z","2.12.15-renovate-balena-jellyfish-logger-2-x-84e1d413b57d67da57ff913202b5f1a8972aace7":"2021-03-25T15:12:56.896Z","2.12.15-renovate-non-major-b9539893310f72433383ae8ba3d06ff2ae386d4f":"2021-03-25T21:22:37.429Z","2.12.15-renovate-balena-jellyfish-environment-4-x-462d6ee999553bd05376f3148a70de27e854e455":"2021-03-25T21:23:33.183Z","2.12.15-renovate-balena-jellyfish-logger-2-x-9169a26a3707684316f78a46d7f12d00d0e03010":"2021-03-25T21:24:04.498Z","2.12.15-renovate-balena-jellyfish-metrics-1-x-eac9c722a1a6883d4a24117f4f288895c98e884e":"2021-03-25T21:25:14.923Z","2.12.15":"2021-03-25T21:29:22.708Z","2.12.16-renovate-balena-jellyfish-logger-2-x-10911cad6463b2b9339fda28c91c245e1d4255ed":"2021-03-25T21:33:53.308Z","2.12.16-renovate-balena-jellyfish-metrics-1-x-55efbcc9a70c8b6562901323bd63b72631e5295a":"2021-03-25T21:35:14.497Z","2.12.16-renovate-balena-jellyfish-environment-4-x-c487b57677382e858296d86f50e63fee9b77e638":"2021-03-25T21:36:08.952Z","2.12.16-renovate-balena-jellyfish-logger-2-x-3b8349cbb0667d6955421eeabd638795388b5714":"2021-03-25T21:41:50.519Z","2.12.16-renovate-balena-jellyfish-metrics-1-x-18a911ad65ecd6eb19ea6474a9bb640b059dec0a":"2021-03-26T00:02:50.174Z","2.12.16-renovate-non-major-aa2636ba7b92603ee83125920fdef7467a447470":"2021-03-26T01:52:03.748Z","2.12.16":"2021-03-26T01:57:24.906Z","2.12.16-renovate-balena-jellyfish-environment-4-x-64bd0ae5240ebb324cf1b997d7603ac10c4ca6f8":"2021-03-26T02:01:09.986Z","2.12.16-renovate-balena-jellyfish-logger-2-x-6cc6050818ed1a7997b3e848f8eb279082d9276b":"2021-03-26T02:01:30.171Z","2.12.16-renovate-balena-jellyfish-metrics-1-x-5b990f0cb4658a853210bdb2dd6b83c7bc6cb9c0":"2021-03-26T02:02:47.405Z","2.12.17-renovate-balena-jellyfish-environment-4-x-aa784006d7112f4439cfb3dc661cef04f2023a35":"2021-03-26T02:04:27.105Z","2.12.17-renovate-balena-jellyfish-logger-2-x-b70efca723c84c22cf2c5c91552526ee81290a53":"2021-03-26T02:05:06.583Z","2.12.17-renovate-balena-jellyfish-metrics-1-x-b2f8c6228dbc495b8ef7b406b9c3e55194b5d97d":"2021-03-26T02:06:14.725Z","2.12.17-renovate-balena-jellyfish-metrics-1-x-b6836db6c51853ee31f81c185021a8356dff6d4e":"2021-03-26T03:52:25.134Z","2.12.17-renovate-balena-jellyfish-metrics-1-x-2f91e3c5da8fbb59ee37d60a010f8769fc54fb02":"2021-03-26T06:31:45.147Z","2.12.17-replace-husky-ef38e9e1445b443ab3800501af2ffd9b4e1697de":"2021-03-26T06:33:28.551Z","2.12.17-replace-husky-3a5c3f8cc3b4efbe0ce8c39e190719ad2b793b29":"2021-03-26T06:40:50.353Z","2.12.17":"2021-03-26T06:47:18.216Z","2.12.18-renovate-balena-jellyfish-environment-4-x-8731f8cd083e98bd60e061986da7a98478581eed":"2021-03-26T06:50:22.049Z","2.12.17-renovate-balena-jellyfish-logger-2-x-267858fc77741436b00f78748f2190e2ad3e7267":"2021-03-26T06:51:02.862Z","2.12.18-renovate-balena-jellyfish-logger-2-x-33edb55687951fc0f30fb293a076ad264f93b12a":"2021-03-26T06:53:51.689Z","2.12.18-renovate-balena-jellyfish-metrics-1-x-7524add97b394a369ec3dfd13fc69b8de9d8ac89":"2021-03-26T06:54:23.388Z","2.12.18-renovate-non-major-f55da8d4b4afe5b204b92e5900c9678896be3220":"2021-03-26T23:51:59.317Z","2.12.18":"2021-03-26T23:57:36.269Z","2.12.19-renovate-balena-jellyfish-environment-4-x-b1e4ce5aebcde1179c64bff81154c2e4d1e81d00":"2021-03-27T00:01:25.924Z","2.12.18-renovate-balena-jellyfish-logger-2-x-f674dbd085e2b4ac0f2bb9c253e094fb84288593":"2021-03-27T00:01:59.665Z","2.12.18-renovate-balena-jellyfish-metrics-1-x-5fa787f9062bafc14e16f794153c90573b6fc677":"2021-03-27T00:04:11.751Z","2.12.19-renovate-balena-jellyfish-logger-2-x-cbf29ad396d708adfb64569bca6d1b90591d0b4d":"2021-03-27T00:06:14.800Z","2.12.19-renovate-balena-jellyfish-metrics-1-x-f9c08c22c928fbf3b4bcac28b75cb90bfd7086ed":"2021-03-27T00:06:59.934Z","2.12.19-renovate-non-major-ac1a27077dfe8554f4ebc0ac856e8dc07a510a5b":"2021-03-27T00:19:51.941Z","2.12.19":"2021-03-27T00:24:22.348Z","2.12.19-renovate-balena-jellyfish-environment-4-x-8d7d244b9097e55cb8184eaf51d60090c3decd47":"2021-03-27T00:27:54.183Z","2.12.19-renovate-balena-jellyfish-logger-2-x-ea5b463412e863bdabcff44ae27c8bf9424305ef":"2021-03-27T00:29:34.817Z","2.12.19-renovate-balena-jellyfish-metrics-1-x-300aaa2c833aaa01853102fc06f3957a397f8670":"2021-03-27T00:30:45.391Z","2.12.20-renovate-balena-jellyfish-environment-4-x-e49acb01859134b6c39edbdadea41bc2b1941def":"2021-03-27T00:31:53.299Z","2.12.20-renovate-balena-jellyfish-logger-2-x-bde12b165c91c61977fbb6255952f96f10fc1388":"2021-03-27T00:32:43.444Z","2.12.20-renovate-balena-jellyfish-metrics-1-x-b9daad3f7d3c2ab2f83e3d5e43c6fc75b5729494":"2021-03-27T00:33:34.637Z","2.12.20-renovate-balena-jellyfish-metrics-1-x-d73c1610e8fbc6e173c194e05b787b19effb3c34":"2021-03-27T03:37:15.036Z","2.12.20-renovate-balena-jellyfish-metrics-1-x-4c1fd42c58724d8e0921a97dadcde1440b566669":"2021-03-29T13:28:59.165Z","2.12.20-renovate-non-major-52486384bec7f3d45ce02c0cfe84689989cbc94b":"2021-03-29T20:12:35.145Z","2.12.20-renovate-balena-jellyfish-metrics-1-x-ca71dba77447cd2b222f09c42677e7f1a2b8413d":"2021-03-30T03:18:01.195Z","2.12.20-renovate-balena-jellyfish-environment-4-x-c5ba852198d3c3bcc3502b31d77896fd6ef17b69":"2021-03-30T09:18:56.239Z","2.12.20-renovate-balena-jellyfish-metrics-1-x-c88d3e749c021d8cee8f04d28949be4233d58d57":"2021-03-30T09:20:32.765Z","2.12.20-renovate-balena-jellyfish-metrics-1-x-9811de98070e59e53573aa6f302afcc342a8faab":"2021-03-30T09:23:55.367Z","2.12.20-renovate-balena-jellyfish-logger-2-x-b709274f94fd21d86da677680cd0eaf4c6057d96":"2021-03-30T12:06:46.892Z","2.12.20-renovate-balena-jellyfish-metrics-1-x-e3dad7aee3d524cf29d30eef52357e51c273de1b":"2021-03-30T12:08:30.454Z","2.12.20-renovate-balena-jellyfish-logger-2-x-4e2c23d4dd6f656c0cfc9aa1bf3b0cf3875bd3c3":"2021-03-30T14:00:20.412Z","2.12.20-renovate-balena-jellyfish-metrics-1-x-ccdc7f3cf92858494b46799515ea8b5678e9234a":"2021-03-30T14:05:31.240Z","2.12.20-renovate-non-major-3d4b3e264bb81a2d32401e2b54faad48eacdefc9":"2021-03-31T01:08:45.874Z","2.12.20-renovate-balena-jellyfish-logger-2-x-9b814ffa3a6d43c536ea89cd1ddb51663a7fcbe8":"2021-03-31T01:10:02.948Z","2.12.20":"2021-03-31T01:13:51.090Z","2.12.21-renovate-balena-jellyfish-environment-4-x-520cdac8484282ff5ad477b8a3b8607d40e3da1a":"2021-03-31T01:17:29.595Z","2.12.21-renovate-balena-jellyfish-logger-2-x-c1e485ede023890c083d4477e532f728d79e8230":"2021-03-31T01:18:00.213Z","2.12.21-renovate-balena-jellyfish-metrics-1-x-73ca7a547b55ac6ba36bb1c42a891cee2f3520ad":"2021-03-31T01:19:55.123Z","2.12.21-renovate-non-major-6b112faeca453447302c4d954d6f9a8c4386de6f":"2021-03-31T07:07:01.405Z","2.12.21":"2021-03-31T07:13:21.849Z","2.12.22-renovate-balena-jellyfish-environment-4-x-51694bbf01dc362eb2bc6a17c8f9d1d420e8f5b5":"2021-03-31T07:17:12.319Z","2.12.22-renovate-balena-jellyfish-logger-2-x-e0749da4fe68bb0b88a3695a24b80bbd09cf237e":"2021-03-31T07:17:49.252Z","2.12.22-renovate-balena-jellyfish-metrics-1-x-1063e4b0f99bb24f9964e4847a9ffa7bbb83db39":"2021-03-31T07:18:30.440Z","2.12.22-renovate-balena-jellyfish-metrics-1-x-b059dcf1f80f5b419a91cbc16d41b494974ac668":"2021-03-31T09:01:38.013Z","2.12.22-renovate-balena-jellyfish-logger-2-x-c591146521ff50a1a1c80cfbc703df45605e5b3e":"2021-03-31T09:06:00.821Z","2.12.22":"2021-03-31T09:36:25.889Z","2.12.23-renovate-balena-jellyfish-environment-4-x-1463a4ac0d6540e459c82799f3bccb59389f9ca6":"2021-03-31T09:40:34.318Z","2.12.23-renovate-non-major-fe88181f32b5937f7b6cd6b9b39f74f8115b4101":"2021-03-31T09:42:44.114Z","2.12.23-renovate-balena-jellyfish-logger-2-x-c135a9a9f63d6a6941fe93b55a306cfbe82855b6":"2021-03-31T09:43:36.657Z","2.12.23":"2021-03-31T09:49:16.704Z","2.12.24-renovate-balena-jellyfish-environment-4-x-0df2cc05f0c65ca7a20242c7a493354bf0d64db9":"2021-03-31T09:52:52.346Z","2.12.24-renovate-balena-jellyfish-logger-2-x-8e36f4b9cb18d54f4234bbd6964910c7e68fd6c7":"2021-03-31T09:54:27.961Z","2.12.24-575-slug-uppercase-chars-0f6e4b492b1131d93a6a685536237840fcc6c022":"2021-03-31T11:26:32.736Z","2.12.24":"2021-03-31T11:31:32.268Z","2.12.25-renovate-balena-jellyfish-logger-2-x-02901d29170de27752eaff40f95ebc7cd373888f":"2021-03-31T11:38:14.269Z","2.12.25-renovate-non-major-c3610bda2e2f1f979e42c5f2ff1ec10f8f9b7180":"2021-03-31T23:09:46.347Z","2.12.25":"2021-03-31T23:19:22.007Z","2.12.25-renovate-balena-jellyfish-environment-4-x-bbea043098c6926b1023ff093546d31ee025dd86":"2021-03-31T23:22:10.126Z","2.12.26-renovate-balena-jellyfish-logger-2-x-9b396d6eed27016dda13dcb70ff86e25cb9a9de5":"2021-03-31T23:22:51.896Z","2.12.26-renovate-balena-jellyfish-environment-4-x-9630b1cc6d765258c3b3056ba2cc2231188ff59a":"2021-03-31T23:26:24.679Z","2.12.26-renovate-non-major-5fc5d23a4b768bb8bf5565b2ab10147a2ea4dcb7":"2021-04-01T01:53:38.243Z","2.12.26":"2021-04-01T01:54:15.538Z","2.12.27-renovate-non-major-e5d0839102688009009cca0e28607105d1aa7e06":"2021-04-01T01:58:20.020Z","2.12.27-renovate-balena-jellyfish-environment-4-x-6e4efb6269bcec96d0efe09b4f0dd75d102f6279":"2021-04-01T02:00:09.324Z","2.12.27":"2021-04-01T02:12:24.889Z","2.12.28-renovate-balena-jellyfish-environment-4-x-3970c9dbf0b823a40ba2e8401dd605c57d46a706":"2021-04-01T02:16:31.628Z","2.12.28-renovate-balena-jellyfish-environment-4-x-322c8c1c92c63dcbc2778cdd8e56783631bd2f22":"2021-04-01T03:23:33.617Z","2.12.28":"2021-04-01T03:39:54.798Z","2.12.29-renovate-non-major-b67548e790040e680b6643afe5f6a9dd5fbf83bc":"2021-04-01T13:51:56.087Z","2.12.29":"2021-04-01T13:57:16.290Z","2.12.30-renovate-non-major-62b0c17969afc5a21429504177f53a478afcd20d":"2021-04-01T15:26:21.229Z","2.12.30":"2021-04-01T15:31:44.488Z","2.12.31-renovate-non-major-a4d614b18c9f545b07f35a3fb66a868b78ab8068":"2021-04-01T23:40:56.978Z","2.12.31-renovate-non-major-9a38f67a1ead75e3e4e582445037b6b03a6301de":"2021-04-02T02:35:16.040Z","2.12.31":"2021-04-02T02:41:12.013Z","2.12.32-renovate-non-major-35c8eacfdedaa6ea29405b19b3f27ed6fba37573":"2021-04-02T17:04:41.761Z","2.12.32":"2021-04-02T17:09:53.783Z","2.12.33-renovate-non-major-28cd4d165706a1ace2374ce571462dd13f7bda3c":"2021-04-02T19:42:07.022Z","2.12.33":"2021-04-02T19:47:32.640Z","2.12.34-renovate-non-major-6092395ebd444366732b392c59c320ed2b1ada80":"2021-04-02T22:43:34.442Z","2.12.34-renovate-non-major-b3ffb216d56a14f6ccf1f1a86c56d1a6b68a93dc":"2021-04-02T22:48:30.670Z","2.12.34":"2021-04-02T22:53:51.893Z","2.12.35-renovate-non-major-14560e82334671f38213be59995a1ccef4bf6417":"2021-04-03T02:06:52.342Z","2.12.35":"2021-04-03T02:15:06.589Z","2.12.36-renovate-non-major-d9b814a9aab3201146173303e7f1b715f6ec67eb":"2021-04-03T21:55:57.586Z","2.12.36-renovate-non-major-cfa65b507d87d142a960f3e5e864649782f66750":"2021-04-03T22:00:58.888Z","2.12.36-renovate-non-major-41065e01a2842ee5cb3d4fe2581318167c957993":"2021-04-04T01:58:03.154Z","2.12.36":"2021-04-04T02:03:50.170Z","2.12.37-renovate-non-major-cdabab6fb996d9d338bad0431e7ac7aa3a5a9bbf":"2021-04-04T03:27:53.544Z","2.12.37-renovate-non-major-174147c285264e8f8e214d46621ec7559a412fde":"2021-04-04T08:11:57.081Z","2.12.37":"2021-04-04T08:17:02.511Z","2.12.38-renovate-non-major-3558eab138014058dd2ec4826c90e67e8598705b":"2021-04-04T09:59:28.069Z","2.12.38":"2021-04-04T10:07:12.437Z","2.12.39-renovate-non-major-bf33855cd4dea800ba8e70cd4b37e7f7c073ffc4":"2021-04-05T00:11:20.395Z","2.12.39":"2021-04-05T00:16:12.656Z","2.12.40-renovate-non-major-5a4b9b58c06236b13b525166c9191991483b37f0":"2021-04-06T13:03:15.660Z","2.12.40":"2021-04-06T13:10:53.841Z","2.12.41-renovate-non-major-383a413227cb9bb6b23f672cc2f997e1352d31f4":"2021-04-06T16:08:16.079Z","2.12.41-renovate-non-major-3eb6739d9d0a812b0fe7f2d1aac6d1f1b72ec0d9":"2021-04-06T17:48:52.375Z","2.12.41":"2021-04-06T17:54:59.202Z","2.12.42-renovate-non-major-cef3af3f35ed4f1aced0d775cd538cb95731a192":"2021-04-06T20:54:17.911Z","2.12.42":"2021-04-06T21:01:17.353Z","2.12.43-renovate-non-major-82b1ea06c69254acb25270ba930103499652036d":"2021-04-06T22:17:19.025Z","2.12.43":"2021-04-06T22:22:21.482Z","2.12.44-renovate-non-major-670620cbb2e6c3af41c38bdf2bf0aa9550bc8928":"2021-04-06T23:37:56.931Z","2.12.44":"2021-04-06T23:43:31.075Z","2.12.45-renovate-non-major-10c4d7b19f58c72cb218231f7831f9ca5f5a6737":"2021-04-07T03:06:18.387Z","2.12.45-renovate-non-major-279c9fa4f62718631039d653cada6fe27f980f98":"2021-04-07T13:45:41.330Z","2.12.45":"2021-04-07T13:54:31.558Z","2.12.46-renovate-non-major-917ba8d3836b0b6a1fee33bd3420c52f7552f7ee":"2021-04-07T21:15:17.300Z","2.12.46-renovate-docker-non-major-c18bd02e7f1bea44c8c86308bb9a5d1a76f398b9":"2021-04-08T20:17:09.236Z","2.12.46":"2021-04-08T20:23:31.657Z","2.12.47-renovate-non-major-d85f1d7d8e1ec5058712aedecd0cd941f3c84154":"2021-04-08T20:26:31.818Z","2.12.47":"2021-04-08T20:35:28.046Z","2.12.48-renovate-docker-non-major-6b31e0ca38e4b73e9b6578ae1083c7082cfc1d69":"2021-04-08T20:40:21.892Z","2.12.48":"2021-04-08T20:46:57.303Z","2.12.49-renovate-eslint-plugin-promise-5-x-3e4dd9f1c1997ba5e64520211d20e28944657112":"2021-04-09T21:00:48.909Z","2.12.49-renovate-non-major-55b6700d6b7c39af29fbf9bd566b5d1bfcb8f39d":"2021-04-09T23:29:03.557Z","2.12.49-renovate-eslint-plugin-promise-5-x-fab02614543c01c9407360365f1940af5c687e08":"2021-04-09T23:29:41.659Z","2.12.49":"2021-04-09T23:34:35.157Z","2.12.50-renovate-eslint-plugin-promise-5-x-fef95bc389a96d2b3bfbdac6330abe85e6aa982d":"2021-04-09T23:38:46.931Z","2.12.50":"2021-04-10T00:10:30.315Z","2.12.51-renovate-non-major-3885e883451ce910a831ff90332dac6f132b3ea9":"2021-04-10T00:17:18.538Z","2.12.51":"2021-04-10T00:22:49.755Z","2.12.52-renovate-non-major-7ae6c2132c5bcaadce410929d70076f7fba7e112":"2021-04-10T00:30:33.571Z","2.12.52-renovate-non-major-e80180f10f76ebf9a93e9e26513ae88f259ba172":"2021-04-10T11:02:28.834Z","2.12.53-renovate-non-major-cfccb8b4c60163fbe73e318a10431036b3f4f95b":"2021-04-10T17:26:38.303Z","2.12.53":"2021-04-10T17:31:31.221Z","2.12.54-renovate-non-major-c264e3059829a4be9eead4feea3ea9ed25baf078":"2021-04-10T19:19:54.876Z","2.12.54-renovate-non-major-82ecd1b07fa832e7923c8b60b72f7cc17a3750bc":"2021-04-10T21:00:46.609Z","2.12.54":"2021-04-10T21:07:59.252Z","2.12.55-renovate-non-major-cb56b6fec52b7f503591f517bdf70fce31f154fb":"2021-04-10T22:38:47.539Z","2.12.55":"2021-04-10T22:46:56.409Z","2.12.56-renovate-non-major-fdf6edd63e678bb048228bad2a282870effcd7ac":"2021-04-11T01:29:24.732Z","2.12.56":"2021-04-11T01:35:33.170Z","2.12.57-renovate-non-major-8caed919404c0a9721aa1d1025ae6142ff6b6bf1":"2021-04-11T05:44:11.162Z","2.12.57":"2021-04-11T05:48:02.029Z","2.12.58-renovate-non-major-9a57377b70e27c5ddcb30bd9c4460eddc2dd2275":"2021-04-11T11:15:37.201Z","2.12.58":"2021-04-11T11:21:01.162Z","2.12.59-renovate-non-major-e01061c6ab933f2253ec858a7e2bcc4e212475fa":"2021-04-11T14:42:46.537Z","2.12.59":"2021-04-11T14:49:33.464Z","2.12.60-renovate-non-major-db03dc89ad66b1119b9d62ec7bdab3e8074b1dfc":"2021-04-11T16:18:12.491Z","2.12.60":"2021-04-11T16:22:43.906Z","2.12.61-renovate-non-major-84bf177b8f837fddf04ffe74e20541347e878757":"2021-04-11T18:18:28.355Z","2.12.61-renovate-non-major-8b5d687d740f44a9efa78a5f299998c61c743bee":"2021-04-11T20:15:13.339Z","2.12.61":"2021-04-11T20:23:07.917Z","2.12.62-renovate-non-major-066c6cdd2597d0e8bb5aa568fae03c22c98b04cf":"2021-04-11T21:50:45.551Z","2.12.62-renovate-non-major-49eaf288f2ebc57502954257cd2042a9724df54e":"2021-04-12T00:42:57.180Z","2.12.62":"2021-04-12T01:27:01.478Z","2.12.63-renovate-non-major-0a3fb293b55eee0b0fe7e1699308474f1b105394":"2021-04-12T03:56:51.182Z","2.12.63":"2021-04-12T04:02:35.960Z","2.12.64-renovate-non-major-aca66fbe920f2737e6e65bc017b793555e32e2d3":"2021-04-12T10:27:07.740Z","2.12.64":"2021-04-12T10:33:34.896Z","2.12.65-renovate-non-major-bd843ead64d19a41a57a77f50b52a3e5be516e4b":"2021-04-12T12:03:17.836Z","2.12.65":"2021-04-12T12:09:27.729Z","2.12.66-renovate-non-major-dc306c93bedfc105cb09d162c95f2b60fd5138bb":"2021-04-12T12:50:33.507Z","2.12.66":"2021-04-12T12:55:42.046Z","2.12.67-renovate-non-major-09623cbfb33326ddf05625b262b8c1be8c660244":"2021-04-12T16:58:26.339Z","2.12.67":"2021-04-12T17:06:00.899Z","2.12.68-renovate-non-major-f840204521cfb22239151314d48429444876d867":"2021-04-12T19:14:36.036Z","2.12.68-renovate-non-major-693866599a1480481b1fc93ddb9a980ea188a122":"2021-04-12T21:39:51.660Z","2.12.68-renovate-non-major-3f1eea833d661969de687137bae0e02b83085a61":"2021-04-12T23:25:04.637Z","2.12.68":"2021-04-12T23:30:26.753Z","2.12.69-renovate-non-major-8411b3001610d1f86396f59e63772c71f5a4315d":"2021-04-13T09:02:19.789Z","2.12.69":"2021-04-13T09:10:02.074Z","2.12.70-renovate-non-major-e5b134a275deac8cbd1a530bfa4ec1dfe718ba19":"2021-04-13T10:35:47.681Z","2.12.70":"2021-04-13T10:43:12.216Z","2.12.71-renovate-non-major-27e2d99ee3fe682c2b053ef6e4a32e8b9616aad3":"2021-04-13T18:01:07.427Z","2.12.71":"2021-04-13T18:10:17.043Z","2.12.72-renovate-non-major-f02d16c67982409d80e431c3ec817b44a1454bf0":"2021-04-14T04:03:12.106Z","2.12.72":"2021-04-14T04:08:59.838Z","2.12.73-renovate-non-major-9a2c24fbfbaeda4d063519768688e116dc8fec31":"2021-04-14T08:23:57.502Z","2.12.73":"2021-04-14T08:28:37.209Z","2.12.74-renovate-non-major-9e05fc9601ff440f6b139b4f04437876539cc552":"2021-04-14T10:07:30.155Z","2.12.74":"2021-04-14T10:14:25.071Z","2.12.75-fix-empty-jsonb-1d38df3386409861411ff6d03c1c84c06c75c285":"2021-04-14T13:18:48.457Z","2.12.75-renovate-non-major-7fe80cba9009ae12f760c645d4a565385c45726d":"2021-04-14T13:33:05.208Z","2.12.75":"2021-04-14T13:38:49.870Z","2.12.75-fix-empty-jsonb-e97031275cf50a60e52499625a9f82f3a0770745":"2021-04-14T13:40:42.222Z","2.12.76-fix-empty-jsonb-84ca1e987fd54f9b1065b6a3946be1ade41294cb":"2021-04-14T13:48:01.439Z","2.12.76":"2021-04-14T13:54:10.286Z","2.12.77-renovate-non-major-dd5e88736b812f5d2fa7228276bcb9fb960448f1":"2021-04-14T20:22:49.222Z","2.12.77-renovate-non-major-2a453d2e70bdc8ac8d803912f67704fb9ad52109":"2021-04-14T23:13:27.284Z","2.12.77":"2021-04-14T23:19:50.563Z","2.12.78-renovate-non-major-7577e8313abbf81b07fa383ceb0f619ab0684113":"2021-04-15T01:42:49.423Z","2.12.78":"2021-04-15T01:48:29.150Z","2.12.79-renovate-non-major-866407a217da9166769276e6c2d30d4afd65e648":"2021-04-15T03:45:37.194Z","2.12.79":"2021-04-15T03:50:32.543Z","2.12.80-renovate-non-major-1d717cd40517eb9decce4d8fe6eea758790bbbc3":"2021-04-15T08:59:22.191Z","2.12.80":"2021-04-15T09:05:57.239Z","2.12.81-renovate-non-major-0199ca60b1cf4e1a6cd0b5946ef30103ee6cb6bc":"2021-04-15T11:34:21.690Z","2.12.81":"2021-04-15T11:41:41.299Z","2.12.82-renovate-non-major-6a531ef2f5e0cf8c4e92bcd24cea9e6fbaa244fd":"2021-04-15T15:36:43.500Z","2.12.82":"2021-04-15T15:51:16.525Z","2.12.83-renovate-non-major-42447d82d5da22a867ceeed9b7a2da66c1f92d94":"2021-04-15T17:36:24.040Z","2.12.83-renovate-non-major-929f749282caf5fb3b593a643a3d7ee73d64a4f3":"2021-04-15T17:42:09.140Z","2.12.83-renovate-non-major-fe1418128265eb239071ae9856d843535cb6770b":"2021-04-15T20:29:05.957Z","2.12.83":"2021-04-15T20:34:57.821Z","2.12.84-renovate-non-major-970e1fa2878b9115f0bf2ef589c7e4b1bb797b78":"2021-04-16T01:09:53.201Z","2.12.84-renovate-non-major-0c5610a3467c3534aac78368156483e94da109d1":"2021-04-16T03:00:05.129Z","2.12.84-renovate-non-major-79d5fc0962c95dfda3536dafdf5e605ed2cfba3b":"2021-04-16T04:09:33.534Z","2.12.84":"2021-04-16T04:17:25.783Z","2.12.85-renovate-non-major-9dfa6002f7368228de2371a06ead356c19865883":"2021-04-16T08:55:15.025Z","2.12.85":"2021-04-16T09:00:36.636Z","2.12.86-renovate-non-major-3389eec065d4a599152d378815279f49eec250e5":"2021-04-16T11:22:02.793Z","2.12.86":"2021-04-16T11:29:24.160Z","2.12.87-renovate-non-major-4ceeb380db496f3ba544b41cc2c73f89be6d84e3":"2021-04-16T14:14:53.435Z","2.12.87":"2021-04-16T14:24:20.965Z","2.12.88-renovate-non-major-56c989b6cbdb9fdd6e3631ecd8616198ab601761":"2021-04-16T17:23:56.497Z","2.12.88":"2021-04-16T17:29:13.061Z","2.12.89-renovate-non-major-c8dcef8a6f26217f5c1db842cfbc6d80a59a38dc":"2021-04-16T20:31:22.110Z","2.12.89":"2021-04-16T20:37:12.880Z","2.12.90-renovate-non-major-c25ddb263ccd951f44fbda465177c3e51fe4bea6":"2021-04-16T23:24:26.026Z","2.12.90":"2021-04-16T23:29:44.803Z","2.12.91-renovate-non-major-b0ff199a094e139396507e4af72664d2d541fe10":"2021-04-17T05:34:18.779Z","2.12.91-renovate-non-major-7e8610c966869eb869e4d5849c534971f7f10e6d":"2021-04-17T11:49:19.610Z","2.12.91":"2021-04-17T11:58:08.377Z","2.12.92-renovate-non-major-30268bb40b7e1038a6a9e29f3fe03c39b0e05461":"2021-04-17T13:39:31.126Z","2.12.92":"2021-04-17T13:45:00.840Z","2.12.93-renovate-non-major-2566b98ea28e08e5c6eb616cc4111b5ce12dfd33":"2021-04-17T16:30:39.171Z","2.12.93-renovate-non-major-d140cac7fbd8a25199e1fdbaa29982fa07d15c77":"2021-04-17T18:12:25.158Z","2.12.93-renovate-non-major-0e2648d555d33915b8fdcf84eefdf39c0b4e8591":"2021-04-17T20:19:25.662Z","2.12.93":"2021-04-17T20:25:45.463Z","2.12.94-renovate-non-major-6665a1ed143e0fe5f13d45c3dcf7473152759e49":"2021-04-17T22:16:18.036Z","2.12.94":"2021-04-17T22:21:43.319Z","2.12.95-renovate-non-major-29ac1d3ee9d0dbfbaaec5e179476eaa3f24bfce4":"2021-04-18T01:54:31.495Z","2.12.95":"2021-04-18T02:00:45.324Z","2.12.96-renovate-non-major-6fb878ddbce41cb88f742dd612d08945a5e3bf76":"2021-04-18T14:58:30.079Z","2.12.96-renovate-non-major-cee39111439337f7f8203fd8f532f50657b5ca06":"2021-04-18T17:08:53.824Z","2.12.96":"2021-04-18T17:14:49.826Z","2.12.97-renovate-non-major-10940955929ef9071d59de062907bf3fbb2fab0b":"2021-04-18T21:13:09.486Z","2.12.97":"2021-04-18T21:18:07.126Z","2.12.98-renovate-non-major-5de39f4340d8f8e77a59223a15ddd4b9b6d5f3fc":"2021-04-19T06:03:25.911Z","2.12.98":"2021-04-19T06:09:37.938Z","2.12.99-renovate-non-major-4149e7c0b49242627b48d3206261fa66e1b9f52f":"2021-04-20T00:38:33.779Z","2.12.99-renovate-non-major-29f37700f77dcbbf106e82c1fd9e3c3a2b4def21":"2021-04-20T03:08:15.998Z","2.12.99":"2021-04-20T03:17:35.757Z","2.12.100-renovate-non-major-75130e96f81145c41d77bfab422a827a960fef31":"2021-04-20T05:13:02.390Z","2.12.100":"2021-04-20T05:19:18.088Z","2.12.101-renovate-non-major-e0fd4a076a2c79ef1a7bd028485d8f2cb7d9e9d2":"2021-04-20T13:39:46.043Z","2.12.101":"2021-04-20T13:52:52.215Z","2.13.0-top-level-exports-89a207c62627d1542653c7ac5c99c50dcfb89dce":"2021-04-20T14:19:44.880Z","2.13.0-top-level-exports-bc83291befde3abee1ca22b289e23d4dc770991b":"2021-04-20T14:51:11.656Z","2.13.0":"2021-04-20T14:58:19.972Z","2.13.1-renovate-non-major-e3a6e3c6cbd7b565c5d5f0a7c93f373ea41d03e0":"2021-04-20T20:58:02.619Z","2.13.1":"2021-04-20T21:12:16.751Z","2.13.2-renovate-non-major-9674c9619a0b466c49da8f05cf47141ecd68b9b2":"2021-04-20T23:46:03.197Z","2.13.2":"2021-04-20T23:53:53.093Z","2.13.3-renovate-non-major-c6f03ee979be66f69abbdeaaf909a6ad617f8ec5":"2021-04-21T12:49:22.281Z","2.13.3-renovate-non-major-87240af26b040b2b0e1e0000583acae35b468ee1":"2021-04-21T16:15:11.496Z","2.13.3-renovate-non-major-a7947509feb51268e45697c62bd2c6c73872a984":"2021-04-21T19:09:06.070Z","2.13.3-renovate-non-major-03cb821b98635dbf64b1461014587674ff38f2db":"2021-04-21T23:05:28.788Z","2.13.3":"2021-04-21T23:11:13.026Z","2.13.4-renovate-non-major-366de783d6c9b21c9dcbe409de86728d32d69a37":"2021-04-23T02:40:47.235Z","2.13.4":"2021-04-23T02:46:44.397Z","2.13.5-renovate-non-major-6da21eeb43e65468495cb4cababac82c553c9c23":"2021-04-23T04:33:35.439Z","2.13.5":"2021-04-23T04:39:47.213Z","2.13.6-renovate-non-major-52f21c903b2618b4948ba987b5b8f97d6e41e735":"2021-04-23T09:45:39.911Z","2.13.6":"2021-04-23T09:53:09.965Z","2.13.7-renovate-non-major-41e79b21ee992765bf2b42605e51c3e3a45799fd":"2021-04-23T11:37:24.384Z","2.13.7":"2021-04-23T11:44:15.739Z","2.13.8-renovate-non-major-21e1036bc83d47b7dd9a5d588e07c462c497611e":"2021-04-23T17:51:51.041Z","2.13.8-renovate-non-major-d0b642704c2366cb0edeab5ea2bc3ebcd573e7ba":"2021-04-23T20:42:14.009Z","2.13.8":"2021-04-23T20:47:37.319Z","2.13.9-renovate-non-major-a5f2285727c56914c94c9f1dad691f5dd5f79267":"2021-04-24T00:13:52.319Z","2.13.9":"2021-04-24T00:22:36.472Z","2.13.10-renovate-non-major-04638c0fe56392b6d4bef62b6d2dac342ae2775d":"2021-04-24T04:09:27.223Z","2.13.10":"2021-04-24T04:21:33.907Z","2.13.11-renovate-non-major-81f9a0d4ccfb18409f2e7b6d780915fbe11e1664":"2021-04-24T11:57:23.828Z","2.13.11-update-repo-yml-af2954a0cc31c5d487b071f8edb5aa8660ebf038":"2021-04-24T14:19:29.538Z","2.13.11":"2021-04-24T14:41:57.069Z","2.13.12-renovate-non-major-c6c2887e392083f3b89c493db9bf6fbc0a2e2c25":"2021-04-24T14:52:02.728Z","2.13.12":"2021-04-24T14:59:42.449Z","2.13.13-renovate-non-major-eeff73f449f6020242330f5dd36f3fd0e4d381d8":"2021-04-24T18:30:11.729Z","2.13.13":"2021-04-24T18:54:20.556Z","2.13.14-renovate-non-major-92197ec30dc6ea0b24118714fd611282ac3c9cb2":"2021-04-24T22:53:39.396Z","2.13.14":"2021-04-24T23:01:04.955Z","2.13.15-replace-jellyfish-uuid-aac8d5b22ba8bad477ef1998df13b18ab4a4e1da":"2021-04-25T02:52:53.908Z","2.13.15":"2021-04-25T03:03:17.173Z","2.13.16-renovate-non-major-202130e28fc55a2a99c12b6aeccf3b7e8b1c9758":"2021-04-25T03:07:53.163Z","2.13.16":"2021-04-25T03:15:58.864Z","2.13.17-renovate-non-major-3caf9f7f138e8b3c0be42bbe219c017321a3da58":"2021-04-26T00:23:47.025Z","2.13.17":"2021-04-26T00:30:37.118Z","2.13.18-renovate-non-major-3dd379ef5b056719957c70e503544c8a377f5535":"2021-04-26T01:58:28.933Z","2.13.18":"2021-04-26T02:04:24.159Z","2.13.19-renovate-non-major-82cdac3db674725e2c86675b678f90187cbeafb7":"2021-04-26T03:11:04.500Z","2.13.19":"2021-04-26T03:16:40.909Z","2.13.20-renovate-non-major-287da357efe1ff433b3a32ff66cdd30cc7c3780a":"2021-04-26T12:16:11.644Z","2.13.20":"2021-04-26T12:23:46.801Z","2.13.21-renovate-non-major-e3302af9102606f5db7067c2b25b3d06a3daa24e":"2021-04-26T19:22:35.743Z","2.13.21":"2021-04-26T19:28:15.774Z","2.13.22-renovate-non-major-6667bc600861793ae33ae53305290f246c79f7a2":"2021-04-26T19:31:46.389Z","2.13.22-renovate-non-major-ca5b18ee7a9c4d3d0c3dcfd18ce728390175cd08":"2021-04-27T00:14:30.696Z","2.13.22":"2021-04-27T00:20:34.323Z","2.13.23-renovate-non-major-347252a5ddb25b9533d1f6e095b9ca2fe4e55f75":"2021-04-27T01:44:42.004Z","2.13.23":"2021-04-27T01:49:51.135Z","2.13.24-renovate-non-major-c156750c7fcdf7fcbbf354754ce2fa9dc129d275":"2021-04-27T01:56:09.287Z","2.13.24":"2021-04-27T02:02:03.205Z","2.13.25-renovate-non-major-0cf8b8fb5e3a51127482a2950b83fdae1c7a74c5":"2021-04-27T03:30:59.799Z","2.13.25":"2021-04-27T03:36:58.636Z","2.13.26-renovate-docker-non-major-53d067c4c5e4a6b8f2de35de7a684e177ba970f9":"2021-04-27T05:01:22.946Z","2.13.26":"2021-04-27T05:07:39.128Z","2.13.27-renovate-non-major-57537d7360fb4aa6c67e720b19f07b6d21671f86":"2021-04-27T16:17:26.040Z","2.13.27":"2021-04-27T16:23:26.536Z","2.13.28-renovate-non-major-e4cb9b7b2735559d3043b50ef10319c302bc6f73":"2021-04-28T01:48:11.759Z","2.13.28":"2021-04-28T01:53:54.374Z","2.13.29-renovate-non-major-07b4e72071a4d55dbf3af9b964b590362d54d5ce":"2021-04-28T13:04:47.237Z","2.13.29":"2021-04-28T13:08:55.790Z","2.13.30-add-user-email-title-c6e946bdede30ca77e1ba411fdfdd6890dbbc337":"2021-04-29T05:55:24.363Z","2.13.30":"2021-04-29T06:26:09.297Z","2.13.31-renovate-non-major-ee2265120043c4b3a9395f1ac02a7c76722b9de8":"2021-04-29T10:31:12.772Z","2.13.31":"2021-04-29T10:36:10.205Z","2.13.32-renovate-non-major-c5b8bcf2b21cbc99c8978c74220012fd5edeb6e7":"2021-04-29T12:17:15.486Z","2.13.32":"2021-04-29T12:22:51.598Z","2.13.33-renovate-non-major-5c276ff0709f400fd5a712bebec379f04104da5b":"2021-04-29T15:51:42.290Z","2.13.33":"2021-04-29T15:57:27.174Z","2.13.34-renovate-non-major-2bbcb50f084e42fa507329b660d3efd91c4a08cb":"2021-04-29T17:53:32.143Z","2.13.34":"2021-04-29T17:58:51.328Z","2.13.35-renovate-non-major-02846c53cabafa57f5fa004a0af35cd85625058c":"2021-04-29T20:51:16.503Z","2.13.35":"2021-04-29T20:58:44.610Z","2.13.36-renovate-non-major-6b806a81b75b0730372ee5f17ef2559c4a8de885":"2021-04-29T22:26:38.126Z","2.13.36":"2021-04-29T22:33:40.658Z","2.13.37-renovate-non-major-e5d346d22360b64be239c55f3a5632f04dd0f1aa":"2021-04-30T02:36:29.155Z","2.13.37-renovate-non-major-0fe1f53ae541cdce2a1e6bb764a0b2826fc810c6":"2021-04-30T02:42:58.790Z","2.13.37":"2021-04-30T02:48:26.023Z","2.13.37-renovate-eslint-plugin-jsdoc-33-x-4b38643ed595a6820c4c315c94e3f9bbd8e387f6":"2021-04-30T02:51:32.831Z","2.13.38-renovate-eslint-plugin-jsdoc-33-x-cb9c2f01e058cba0e072b87b3358fd48eb831847":"2021-04-30T02:54:51.048Z","2.13.38-renovate-non-major-506b030eb86a02b9bc9eacf990b7116fbd6ffdbd":"2021-04-30T04:44:41.359Z","2.13.38":"2021-04-30T04:51:05.185Z","2.13.39-renovate-eslint-plugin-jsdoc-33-x-1e3e5bfd65c5d60eedf710c09589c72967f785b2":"2021-04-30T04:55:47.625Z","2.13.39-cache-disconnect-99376b4f2d353ee0c988de92e2322405bf30064e":"2021-04-30T08:52:14.700Z","2.13.39-cache-disconnect-e5022a312514afedf8a6fbc2daee2a707c4fd8e8":"2021-04-30T09:02:14.062Z","2.13.39":"2021-04-30T09:13:50.159Z","2.13.40-renovate-non-major-39f2c0fae085b3495ba0e75571c2f2f882e12944":"2021-04-30T09:23:49.965Z","2.13.40-reduce-loggers-ad7cd22208f7017aceafaef8b8774504eceedf5f":"2021-04-30T09:24:43.130Z","2.13.40":"2021-04-30T09:29:53.606Z","2.13.41-renovate-eslint-plugin-jsdoc-33-x-4bb7b6dcbde311619e7d40f9cdd00551555fa302":"2021-04-30T09:34:53.009Z","2.13.41-reduce-loggers-a9c119ae240f07a1d6183b2d1486bcc00c863502":"2021-04-30T09:36:25.557Z","2.13.41":"2021-04-30T14:46:46.337Z","2.13.42-renovate-eslint-plugin-jsdoc-33-x-1a446eabec41afa06d49b85d494e4acda66472eb":"2021-04-30T14:52:56.777Z","2.13.42-renovate-non-major-daab7d9ee5a43fdfebfccbf27bfbe6909c038137":"2021-04-30T17:04:19.713Z","2.13.42-renovate-non-major-46695cb1cc30330cc208f5b423339aa7ed539d20":"2021-04-30T17:10:40.224Z","2.13.42":"2021-04-30T17:16:43.828Z","2.13.43-renovate-eslint-plugin-jsdoc-33-x-854e6aed2a18b9a6c86b65dbddd72738f98c8037":"2021-04-30T17:21:40.819Z","2.13.43":"2021-04-30T23:31:24.626Z","2.13.44-renovate-non-major-6b2712dcfb02adce15da549beb83670e2e9f22aa":"2021-05-01T01:45:00.067Z","2.13.44":"2021-05-01T01:51:02.576Z","2.13.45-renovate-non-major-596790d7fa25ec770948ad2b7c70a822637724d4":"2021-05-01T03:57:07.062Z","2.13.45":"2021-05-01T04:02:48.433Z","2.13.46-renovate-non-major-ab14c1689cc92c0d26a28b1dab9d9fbf7f4fe2f4":"2021-05-01T09:55:49.079Z","2.13.46":"2021-05-01T10:00:43.770Z","2.13.47-renovate-non-major-90e1df44accd9fe937af7d7156b06600f8569146":"2021-05-01T12:49:15.309Z","2.13.47":"2021-05-01T12:55:04.643Z","2.13.48-renovate-non-major-2ff90a25136c4be976ed37929ea048462ad1765d":"2021-05-01T16:08:40.572Z","2.13.48":"2021-05-01T16:14:22.465Z","2.13.49-renovate-non-major-15b3f8ca8e9a879bfaefd79e34b266fdd63393c4":"2021-05-01T17:40:56.240Z","2.13.49":"2021-05-01T17:46:37.130Z","2.13.50-renovate-non-major-1f6a219ea4647190587ad39c1fda8622e2dcffac":"2021-05-02T02:02:11.275Z","2.13.50":"2021-05-02T02:07:25.579Z","2.13.51-renovate-non-major-92b9cc6740a4c00787c0708d6e0c5dfd34178c99":"2021-05-02T04:38:30.231Z","2.13.51":"2021-05-02T04:45:45.049Z","2.13.52-test-ci-task-runner-5cd0aa3e85497d555dd2230e4cbda41692cf99ca":"2021-05-03T00:09:37.988Z","2.13.52-renovate-non-major-7d85b1babd43934a2a37bf2e60f717edf0bfaa71":"2021-05-03T01:09:47.899Z","2.13.52":"2021-05-03T01:15:15.537Z","2.13.53-renovate-non-major-058ea2ffafdfa8fd3c87998fe5bb0ae190ae3a80":"2021-05-03T03:40:11.228Z","2.13.53":"2021-05-03T03:44:50.986Z","2.13.54-renovate-non-major-548155009daad52c5521ea4f5d4f9eb48f7d7dc0":"2021-05-03T10:55:09.355Z","2.13.54-renovate-non-major-17b749d2acf996bc4c994426c9afba7e000f2ee9":"2021-05-03T12:16:06.957Z","2.13.54":"2021-05-03T12:22:29.382Z","2.13.55-renovate-docker-non-major-87722f1b1e4d7d5c0eafc652df3cfd2d2808e24c":"2021-05-03T13:29:31.710Z","2.13.55":"2021-05-03T13:37:13.094Z","2.13.56-renovate-non-major-ba45b88f31fc72c78dfa135c2887d4c81b4633de":"2021-05-03T13:41:25.041Z","2.13.56":"2021-05-03T13:49:41.171Z","2.13.57-renovate-non-major-beb628227eca71029e4bd4636230ec087273364e":"2021-05-03T16:49:26.589Z","2.13.57":"2021-05-03T16:56:52.185Z","2.13.58-renovate-non-major-ba18571476c4c45b947a68a96f7667779f6f4a66":"2021-05-03T18:13:57.444Z","2.13.58":"2021-05-03T18:18:46.660Z","2.13.59-renovate-non-major-5199c36573bd70369b2e1d1d748d95c4d5982bfe":"2021-05-03T21:39:31.448Z","2.13.59":"2021-05-03T21:46:41.769Z","2.13.60-renovate-non-major-401b9bf084b02483430cfa4415a8c55cbeaecd76":"2021-05-03T23:33:57.996Z","2.13.60":"2021-05-03T23:40:46.343Z","2.13.61-renovate-non-major-7e66fe0f8cf7f7133f4502c8e62dbf1fde286451":"2021-05-04T00:56:28.076Z","2.13.61":"2021-05-04T01:02:44.575Z","2.13.62-renovate-non-major-194396ae6d1ec59403d4d25154fe6231c334c753":"2021-05-04T01:49:37.540Z","2.13.62":"2021-05-04T01:55:18.425Z","2.13.63-renovate-non-major-f289d7f9354e88a03fbfe3836cff6519fea41451":"2021-05-04T02:03:11.484Z","2.13.63":"2021-05-04T02:10:56.132Z","2.13.64-renovate-non-major-80210e9a97f767abd62089c64851ad33d880fe54":"2021-05-04T07:04:25.826Z","2.13.64":"2021-05-04T07:10:13.823Z","2.13.65-renovate-non-major-5debac891515536bae8b0b700065a0e095f4c8ff":"2021-05-04T07:48:59.390Z","2.13.65":"2021-05-04T07:53:55.830Z","2.13.66-renovate-non-major-0b3a6f75f0e59f1687f3c23e48e7817f4b0b475a":"2021-05-04T09:51:05.480Z","2.13.66":"2021-05-04T09:56:34.561Z","2.13.67-renovate-non-major-0c3e59e98e606c7300c7505cc9147ad7a7d003a6":"2021-05-04T17:40:35.856Z","2.13.67":"2021-05-04T17:44:51.363Z","2.13.68-renovate-non-major-cda0eb71a324a15cfe6ea2cfed5d992dbb9918bf":"2021-05-04T21:59:07.976Z","2.13.68":"2021-05-04T22:03:36.973Z","2.13.69-renovate-non-major-1d28f4cfef665caf78d627aba0366937a43f4a9d":"2021-05-04T23:37:04.940Z","2.13.69":"2021-05-04T23:43:43.771Z","2.13.70-renovate-non-major-a77c8dfcc290897ee83804bc69d66b0efce56023":"2021-05-05T01:13:52.488Z","2.13.70":"2021-05-05T01:21:29.581Z","2.13.71-renovate-non-major-205ae8cd63373e596e32599219e84263748beb14":"2021-05-05T10:34:59.447Z","2.13.71-renovate-docker-non-major-8872e32d9af6337c5d0b1eb9eaf76c0343229fcd":"2021-05-05T10:36:06.321Z","2.13.71":"2021-05-05T10:40:52.460Z","2.13.72-renovate-non-major-ccc1279198280d9aba510f040b7f8ccf9e5892c4":"2021-05-05T10:46:05.800Z","2.13.72":"2021-05-05T10:54:42.971Z","2.13.73-renovate-non-major-d5840a03c6069ed79ee3939b82dd3e9b46ff748f":"2021-05-05T10:59:41.526Z","2.13.73":"2021-05-05T11:07:28.892Z","2.13.74-renovate-non-major-f6947b5892c1581dc7c93ef53b4cec34ecf63989":"2021-05-05T16:42:00.010Z","2.13.74-renovate-non-major-5c27ba07336c90c7889a8b09f130a7adc465a5b0":"2021-05-05T17:56:41.134Z","2.13.74":"2021-05-05T18:02:05.216Z","2.13.75-renovate-catch-uncommitted-2-x-aaee9401caee0c88475bd53e3f41275d3cbc246b":"2021-05-05T21:36:17.686Z","2.13.75":"2021-05-05T23:10:15.667Z","2.13.76-renovate-non-major-219e028c760248706e337ff90413655f51b775a9":"2021-05-06T00:20:28.004Z","2.13.76":"2021-05-06T00:25:52.696Z","2.13.77-remove-dead-queryv2-code-98a5aac8f283b5b650b44f52d5814bc83a5d14e7":"2021-05-06T12:06:23.041Z","2.13.77":"2021-05-06T12:22:45.234Z","2.13.78-fix-scope-mask-7160a6e01471c833e84790c953068dc722e0a850":"2021-05-07T00:48:58.214Z","2.13.78":"2021-05-07T00:54:13.361Z","2.13.79-h32-log-version-on-conflict-ffa179fb0773fdf4dbcdce744021d1f3d01c788a":"2021-05-07T06:54:01.267Z","2.13.79":"2021-05-07T06:59:41.967Z","2.13.80-renovate-non-major-618c6326302a9a4a9520d8418ec81513bd0ad983":"2021-05-07T14:50:23.931Z","2.13.80-renovate-lint-staged-11-x-0bdb8d9b16a6e68d04d24e477354c4126ef41fa9":"2021-05-07T18:11:35.331Z","2.13.80":"2021-05-08T00:11:05.141Z","2.13.81-renovate-non-major-5f36a6ff214ffba89a88de7241023b61d12f3451":"2021-05-08T00:20:52.967Z","2.13.81-renovate-non-major-105c8a4d2bf3cd710ba114a272bba86284120b11":"2021-05-08T00:36:41.707Z","2.13.81":"2021-05-08T00:54:51.868Z","2.13.82-renovate-non-major-84d59f2c0e34b0ab0bf67257cc8983987eff6c32":"2021-05-08T01:00:02.977Z","2.13.82":"2021-05-08T01:12:07.427Z","2.13.83-renovate-non-major-8976eea745a4bc98d8dd953eea6cc8ff1913c3da":"2021-05-08T01:16:12.441Z","2.13.83":"2021-05-08T01:26:43.843Z","2.13.84-renovate-non-major-50e3c4129177a37e767a266387ffd93785f763cb":"2021-05-08T03:28:48.222Z","2.13.84-renovate-non-major-c99cced5277e55fbb7f7f8b63f83503821a48f44":"2021-05-08T05:25:18.640Z","2.13.84":"2021-05-08T05:31:42.106Z","2.13.85-renovate-non-major-42bed61d9bfab1e50677c2cf65c29cfab41d0952":"2021-05-08T08:17:20.350Z","2.13.85":"2021-05-08T08:22:06.282Z","2.13.86-renovate-non-major-0c57d02fd3b450e035f463fc827ac308f070230a":"2021-05-08T09:28:44.800Z","2.13.86":"2021-05-08T09:35:50.179Z","2.13.87-renovate-non-major-0c6b28b84d0bc4dfe6692cdfb1e0199d24381a37":"2021-05-09T00:35:28.272Z","2.13.87":"2021-05-09T00:41:09.264Z","2.13.88-renovate-non-major-70f436cfb9e7aaefac1ea87efdece4a2b46421ca":"2021-05-09T08:26:49.022Z","2.13.88":"2021-05-09T08:31:59.050Z","2.13.89-renovate-non-major-bff644f29b809c960077c9656a588754d3ed9f3f":"2021-05-09T16:18:10.195Z","2.13.89":"2021-05-09T16:22:55.815Z","2.13.90-renovate-non-major-0fe2d1bf85156c214deb586bbcf62184b7aa6ab9":"2021-05-10T08:06:50.975Z","2.13.90":"2021-05-10T08:11:34.709Z","2.13.91-renovate-non-major-aba9bd04ac58e7a50ecc96a412474b5dda656ec3":"2021-05-10T11:13:16.334Z","2.13.91":"2021-05-10T11:17:45.748Z","2.13.92-renovate-non-major-992b08cfe553cb822a3031a453fabcc908e85cc6":"2021-05-10T13:20:29.178Z","2.13.92-renovate-non-major-a18df3a88ae4b4d9987c5946d5e481d7f98fed4e":"2021-05-10T13:25:30.565Z","2.13.92":"2021-05-10T13:31:38.560Z","2.13.93-renovate-non-major-c4c62c790b767b5f0548c526c72871e7d52a18b4":"2021-05-10T14:44:18.853Z","2.13.93":"2021-05-10T14:49:08.468Z","2.13.94-renovate-non-major-4253bf34dc03c17fe46a6245afe8541154f23fb5":"2021-05-10T15:49:49.067Z","2.13.94":"2021-05-10T16:11:29.301Z","2.13.95-test-multiple-prerelease-7944ca2c838844e1d6d6fea155a339d8d124ef9f":"2021-05-11T00:41:21.924Z","2.13.95-renovate-eslint-plugin-jsdoc-34-x-15557152097c156d657a55a7a02fc47331ae08e1":"2021-05-11T02:26:43.690Z","2.13.95-renovate-non-major-3599c46571b33c93f16ab960aff2fa2a190faf9d":"2021-05-11T08:10:10.174Z","2.13.95-renovate-non-major-9f7ccebbb2212b5940cc1ed8aedce74f3f1f5c65":"2021-05-11T08:15:39.380Z","2.13.95-renovate-non-major-dadc554e9e52747bfacb95d9f5377b1a278d1fed":"2021-05-11T08:20:03.686Z","2.13.95":"2021-05-11T08:25:29.536Z","2.13.96-renovate-eslint-plugin-jsdoc-34-x-f82d528215e0549834c8db50c19697b11d20b9e9":"2021-05-11T08:29:23.548Z","2.13.96-renovate-eslint-plugin-jsdoc-34-x-be9c116d65a9a2612d494fd8e6322019b7ffcb74":"2021-05-11T14:07:02.274Z","2.13.96":"2021-05-12T00:27:13.268Z","2.13.97-renovate-non-major-75fe6bf5cc8309721f1eb1874565a2034ed22430":"2021-05-12T00:31:36.406Z","2.13.97-renovate-non-major-51e3baa398cf2d5eaf25e3c1c8f87c69f56d6994":"2021-05-12T00:33:51.384Z","2.13.97":"2021-05-12T00:43:06.827Z","2.13.98-renovate-non-major-220e2204ae7b9c9859c07a103e9c9d815352ea5a":"2021-05-12T02:10:14.475Z","2.13.98":"2021-05-12T02:19:50.505Z","2.13.99-renovate-non-major-135c1d581445d4617672954c2f4826047eb11f3b":"2021-05-12T02:24:06.483Z","2.13.99":"2021-05-12T02:32:48.629Z","2.13.100-renovate-non-major-456a923d82845d3428b575b7d5badf490c083326":"2021-05-12T07:43:15.515Z","2.13.100-renovate-non-major-13e26c30f2b708d4f47568f142ffd362199fadc0":"2021-05-12T09:00:48.639Z","2.13.100":"2021-05-12T09:06:05.668Z","2.13.101-renovate-non-major-fbf266a39e62a646aba519931c92f4cd6c6282b1":"2021-05-12T09:42:33.486Z","2.13.101":"2021-05-12T09:48:35.174Z","2.13.102-renovate-non-major-e775f86ac59b9ce1b4a560d26cb7e11499d89503":"2021-05-12T11:08:28.230Z","2.13.102":"2021-05-12T11:14:05.856Z","2.13.103-renovate-non-major-26bb61dc5ff6eb245f012eb52815ab49b4b28950":"2021-05-12T21:23:24.897Z","2.13.103":"2021-05-12T21:29:17.694Z","2.13.104-renovate-non-major-f037ea6ab95a889c6ff71fff8fdb8f13bf0e28dd":"2021-05-13T12:53:01.793Z","2.13.104":"2021-05-13T12:58:43.022Z","2.13.105-renovate-non-major-2092d9cc37d6e187a514491cc90ca20c3f72a0af":"2021-05-13T13:34:53.219Z","2.13.105":"2021-05-13T13:39:24.960Z","2.13.106-renovate-non-major-afcf40355709c36caef289b0cbbe6ef04f7ce36b":"2021-05-13T23:22:46.514Z","2.13.106":"2021-05-13T23:29:54.776Z","2.13.107-renovate-non-major-c705d4924ba2959dd26331ebb65cdfe2e703bc7b":"2021-05-13T23:35:48.858Z","2.13.107":"2021-05-13T23:41:43.764Z","2.13.108-renovate-non-major-314d577f737e52fba09b730fa57f7fc9db5494e8":"2021-05-14T03:14:00.472Z","2.13.108":"2021-05-14T03:24:47.698Z","2.13.109-renovate-non-major-45c4e4e10ba587286adc2663ab19aca54c1a6417":"2021-05-14T05:05:05.579Z","2.13.109-renovate-non-major-df92842fd555fba4cf2440dbe79c8a1c7740dda3":"2021-05-14T08:14:40.800Z","2.13.109":"2021-05-14T08:19:48.674Z","2.13.110-renovate-non-major-f91db8253145412b1bc749ba824c6e216467c317":"2021-05-14T16:13:12.315Z","2.13.110":"2021-05-14T16:17:54.683Z","2.13.111-renovate-non-major-68eb467abf7b598b92a233f23d7bf326f0a89154":"2021-05-14T17:59:02.605Z","2.13.111":"2021-05-14T18:03:51.323Z","2.14.0-typescript-conversion-2379ad2763b23630c66c87dd198a687e083e895a":"2021-05-14T20:58:37.224Z","2.14.0-typescript-conversion-2e0d5020c3342792b2fe3f24e735bba8926454e4":"2021-05-14T21:31:35.749Z","2.13.112-renovate-non-major-3182a5cf2afc6144384200e67c18f29ca840af20":"2021-05-15T07:37:02.208Z","2.13.112":"2021-05-15T07:42:33.327Z","2.13.113-renovate-non-major-cc7713d96f96f2d6df2e42b69bfffe8317546c20":"2021-05-15T16:48:58.110Z","2.13.113-renovate-non-major-917c4a0d403c2fce4d0f0a5fd0f0e4cbb82ae529":"2021-05-15T16:54:33.064Z","2.13.113":"2021-05-15T16:59:04.510Z","2.13.114-renovate-non-major-ec5fac6682736e2e036e68e55113387295e7be76":"2021-05-15T18:07:13.266Z","2.13.114":"2021-05-15T18:16:02.974Z","2.13.115-renovate-non-major-8c1ae376864faada40887af84fee932947b500b4":"2021-05-15T22:38:55.197Z","2.13.115":"2021-05-15T22:44:35.834Z","2.13.116-renovate-non-major-90e1f999e2dbb1e925e470ad85b745403d1e6422":"2021-05-16T00:06:27.231Z","2.13.116":"2021-05-16T00:18:42.484Z","2.13.117-renovate-non-major-52996d14b258594e296af7147848e08ddcd94a77":"2021-05-16T01:41:00.110Z","2.13.117":"2021-05-16T01:45:49.050Z","2.13.118-renovate-non-major-f03d73b635ebc6c895f719d6d2c5c0dc60114f31":"2021-05-16T16:23:01.083Z","2.13.118":"2021-05-16T16:28:05.072Z","2.13.119-renovate-non-major-e169ade06943a690e5cd3d2f4559af0ca92759a3":"2021-05-16T17:44:27.118Z","2.13.119":"2021-05-16T17:48:56.166Z","2.13.120-renovate-non-major-c7825cd9232227a111e655c75e412925bbba4971":"2021-05-16T19:22:31.438Z","2.13.120":"2021-05-16T19:25:57.013Z","2.13.121-renovate-non-major-0e72d50d91ff4713a8715df625806d8ea6aeb353":"2021-05-17T09:55:04.825Z","2.13.121":"2021-05-17T09:59:32.903Z","3.0.0-add-loop-field-0ffb755f89656879abb466e5067ddb117e421f70":"2021-05-17T10:37:32.822Z","2.14.0-typescript-conversion-2c051419f405ae2f8af770b66519a01cb3bdf902":"2021-05-17T15:11:13.592Z","2.14.0-typescript-conversion-6bf04a712788e879d35764410e33fc214ae2d7b9":"2021-05-17T15:20:55.773Z","2.14.0-typescript-conversion-05a47a84d6d5962679175a9ff77ed72e8fbb7662":"2021-05-17T15:39:09.331Z","2.14.0-typescript-conversion-7dbf8379191cfc394bd564b603a375586124d6a4":"2021-05-17T15:56:30.991Z","2.14.0-typescript-conversion-85dfacc7e1415844b1a3480ec71fb9a2d4c30bdb":"2021-05-17T15:59:18.152Z","2.13.122-renovate-non-major-e36d4c5dedf4ff8bc0b030bf2a89c6ba46a1b952":"2021-05-17T18:32:21.406Z","2.13.122":"2021-05-17T18:40:23.970Z","2.13.123-renovate-non-major-8daf31b7993984f003544172d92a5a8dfe339f85":"2021-05-18T00:25:26.965Z","2.13.123":"2021-05-18T00:30:42.143Z","3.0.0-add-loop-field-334e746763605307543a5142e8697a7d814030a8":"2021-05-18T04:43:01.861Z","3.0.0-add-loop-field-f5d2d00f8916db6324172044495538c7d0c7115b":"2021-05-18T09:26:58.824Z","2.13.124-renovate-non-major-1b239062ec45e3650b06473ec1835c735b9a79f2":"2021-05-18T18:20:56.702Z","2.13.124":"2021-05-18T18:29:23.077Z","2.13.125-renovate-internal-non-major-3f7834e3e142d813f08c5d747eb08f36afac1f90":"2021-05-21T08:13:59.243Z","2.13.125":"2021-05-21T08:19:08.975Z","2.13.126-renovate-internal-non-major-db2d5d078e8b2ee56aab7c86d7f514e313260778":"2021-05-21T10:29:06.062Z","2.13.126-renovate-internal-non-major-a417a006ef4c0b913910934416ae16bcbd1e4a04":"2021-05-21T12:28:33.900Z","2.13.126":"2021-05-21T12:35:02.502Z","2.13.127-renovate-external-non-major-ca170d67daa948e245960d5e74471c8cc7e05121":"2021-05-22T00:14:30.601Z","2.13.127-renovate-major-external-major-d9c2a19e19b5d74e7b8dc99aa588e00619d616db":"2021-05-22T00:16:00.954Z","2.13.127":"2021-05-22T00:20:27.243Z","2.13.128-renovate-major-external-major-491a38db789e53fab365a229c3e8205c67970526":"2021-05-22T00:24:17.387Z","2.13.128-renovate-internal-non-major-678d7daf30369b9af4c30748cd204008a114bbda":"2021-05-22T02:34:29.787Z","2.13.128-renovate-external-non-major-fffc7e1a5d2ef816a84a97875ff410c734873d76":"2021-05-22T02:35:46.468Z","2.13.128":"2021-05-22T02:42:17.654Z","2.13.129-renovate-internal-non-major-5b474adb46a196c5629c53b8f69cea1c519966fc":"2021-05-22T02:47:16.981Z","2.13.129-renovate-major-external-major-1c75f89720ee983ad0fe700581a07c35da3a33e2":"2021-05-22T02:50:08.260Z","2.13.129":"2021-05-22T02:54:57.682Z","2.13.130-renovate-major-external-major-08f70d737e1c9a27da3321c8b0225ee06fda831b":"2021-05-22T02:59:04.128Z","2.13.130-renovate-internal-non-major-6a1717889387ae343581bc542dbc273d893769ed":"2021-05-23T01:59:37.644Z","2.13.130-renovate-internal-non-major-f27a9145f6d72c50b15bb0329a506da46f03c935":"2021-05-23T02:05:23.634Z","2.13.130":"2021-05-23T02:10:12.472Z","2.13.131-renovate-major-external-major-40722245c87f5a4df2635914f0dc500c6f63cc97":"2021-05-23T02:16:23.039Z","2.13.131-renovate-internal-non-major-7c38ebf10c3521cc42967faff3b718af0a97806b":"2021-05-23T02:17:43.031Z","2.13.131":"2021-05-23T02:24:39.204Z","2.13.132-renovate-major-external-major-07e08cd372b45c97d34e2fb9763daddaadc49203":"2021-05-23T02:30:31.199Z","2.13.132-renovate-internal-patch-d6baf22094611f0f56e6f0de262cb7d4210737c5":"2021-05-23T04:59:31.889Z","2.13.132":"2021-05-23T05:05:54.945Z","2.13.133-renovate-major-external-major-ba36b8a590fb8c24a3afa209174ca8c19e28bd0f":"2021-05-23T05:09:41.102Z","2.13.133-renovate-internal-patch-df3b277b039dc43de3dbd4c32dd7f00cb42c78ba":"2021-05-23T10:04:40.735Z","2.13.133":"2021-05-23T10:10:16.016Z","2.13.134-renovate-major-external-major-09b0efca3a03f0f31baa93991630f391f16ef08a":"2021-05-23T10:17:07.450Z","2.13.134-renovate-internal-patch-13d896952759434984e3ff53f95f72899b4fb080":"2021-05-24T02:22:37.155Z","2.13.134":"2021-05-24T02:31:03.449Z","2.13.135-renovate-major-external-major-58a549b9ceafea8c181506fea2c64132f4cced23":"2021-05-24T02:35:07.735Z","2.14.0-add-link-indexes-d3c5abcb22aa231bdee56a1183b97e287af59b61":"2021-05-24T04:45:39.342Z","2.13.135-renovate-internal-patch-8b990005e1704e274b3bde216ac631a3a0e12968":"2021-05-24T08:00:37.673Z","2.13.135":"2021-05-24T08:05:54.531Z","2.13.136-renovate-major-external-major-6afcb841e072abd0f5a986b9caa6b936a857db38":"2021-05-24T08:09:57.143Z","2.13.136-renovate-major-external-major-a850d34c1ec194bfe9ebd225dee48f2ef7f79760":"2021-05-24T10:15:54.669Z","2.14.0-typescript-conversion-7568de4d664e954101f5c8052b72d5fba6ab443a":"2021-05-24T10:45:44.508Z","2.13.136-renovate-internal-patch-6206d4bebd14fa64bba890288d13b67179a00bb8":"2021-05-25T01:31:15.390Z","2.13.136":"2021-05-25T01:35:55.578Z","2.13.137-renovate-major-external-major-53727b83318658121fd9f556d13bd329ce24f195":"2021-05-25T01:42:18.757Z","2.13.137-renovate-internal-patch-a54e99f226a7fe6cef8e7c13350fbd8e15c9afa9":"2021-05-25T04:17:13.182Z","2.13.137":"2021-05-25T04:23:12.581Z","2.13.138-renovate-major-external-major-22cf2fe7b49b78b56c142c0602cd2a9a7cba34b7":"2021-05-25T04:27:11.976Z","2.13.138-renovate-internal-patch-db5ace0e19e3646e80bb2328e5032be307e87ff7":"2021-05-25T07:36:59.821Z","2.13.138":"2021-05-25T07:43:13.268Z","2.13.139-renovate-major-external-major-da41a3e495c53f901821e6f8d8d37aab04196610":"2021-05-25T07:47:14.547Z","2.14.0-typescript-conversion-db3b86f4818ce3e19869c21b666d294c8483de51":"2021-05-25T08:25:25.117Z","2.14.0-typescript-conversion-ede35f4b3c167ae88f3bf254a3756098c131679a":"2021-05-25T09:05:19.294Z","2.13.139-bump-eslint-plugin-jsdoc-60ae652f43313ea08d09f5df5c1aef211d916a32":"2021-05-26T01:42:20.868Z","2.13.139":"2021-05-26T01:48:31.911Z","2.13.140-renovate-internal-patch-04cfbc18c6d1089029f040ef23e919a412ec0827":"2021-05-26T01:52:57.059Z","2.13.140-renovate-major-external-major-2857686ceef248075de97850a0e52db2fbe3fb3a":"2021-05-26T01:56:00.292Z","2.13.140":"2021-05-26T02:00:42.811Z","2.13.141-renovate-major-external-major-a1ae72fcc75a67a4ef059cebc29f6fd33c0d750f":"2021-05-26T02:05:09.482Z","2.13.141-renovate-internal-patch-d122e94d31641e7c43f907e7d7eb638fd20b3c1d":"2021-05-26T03:06:10.768Z","2.13.141-renovate-major-external-major-28723f8fae65e70c0216b715ece05a955588e081":"2021-05-26T03:17:21.829Z","2.13.141-repo-sync-jellyfish-config-default-8453876f5745c64582f4d128c39cd41c320758ea":"2021-05-27T01:20:39.865Z","2.13.141-repo-sync-jellyfish-config-default-6d532490eacb30d3b2e8e342bd948bac16387dc0":"2021-05-27T03:24:34.872Z","2.13.141-renovate-internal-patch-db163e8e51d4043f7fe50a47276e4c2cc6e3f256":"2021-05-27T05:26:59.721Z","2.13.141":"2021-05-27T05:34:15.843Z","2.13.142-renovate-major-external-major-81b9c0d67fa7e410d38f805058b6896990c89257":"2021-05-27T05:41:51.206Z","2.13.142-repo-sync-jellyfish-config-default-399f33945b156a1d469e1a16dc2f993a697b82af":"2021-05-27T06:51:46.250Z","3.0.0-typescript-conversion-9fe8747542b2543ea2eeb13fcd95ec0e9eefd772":"2021-05-27T07:15:07.506Z","3.0.0":"2021-05-27T07:51:31.180Z","3.0.0-renovate-internal-patch-3154fa2af52d73a792d232018c48e97579461e20":"2021-05-27T07:55:57.013Z","3.0.0-renovate-balena-lint-6-x-cbd196635993af7e583172a5c1fe4984e63de2c2":"2021-05-27T07:56:23.236Z","3.0.1-renovate-internal-patch-62ca8398518b7030d225f1d84c565bc688bcf32b":"2021-05-27T07:59:43.327Z","3.0.1-renovate-major-external-major-8b0659bcbd08ba1a306ae7a9f67791d39af719dc":"2021-05-27T08:01:48.635Z","3.0.1-repo-sync-jellyfish-config-default-e9bfb15b21a1ae3885df843bf3cf0f4b05963093":"2021-05-27T08:46:24.128Z","3.0.1-repo-sync-jellyfish-config-default-fbbc6de294cc9124bbc0dfe705a40727c3d832d8":"2021-05-27T09:11:51.563Z","3.0.1-renovate-internal-patch-e71f354eaf0fba9bcb549bc1f0cbddc62a18ef68":"2021-05-27T09:44:59.638Z","3.0.1-renovate-internal-patch-5fb49f0cfb15452b65652b16b2549655647c2d67":"2021-05-27T11:27:08.204Z","3.0.1-renovate-internal-patch-53504721cac5fbcc9999080fcb4024f4f028bc30":"2021-05-27T11:32:54.312Z","3.0.1-renovate-internal-patch-8dfddc6f7020caa41aa83a0138599ffe0f466ec1":"2021-05-27T11:39:20.272Z","3.0.1-renovate-internal-patch-01362fcc4446d2cc0bd3f579c28247a64aee2b7a":"2021-05-27T13:37:03.069Z","3.0.1-renovate-balena-lint-6-x-6ae5ca9c43174b9727fb46cda3fd682ddf2d7ccd":"2021-05-28T00:22:07.475Z","3.0.1-renovate-balena-lint-6-x-1ac2e7daf480750f211edf1c613203ac404b253a":"2021-05-28T00:58:50.415Z","3.0.1-h32-setup-in-transaction-9cfc811158738f2c1005273361a0271baf14ee0b":"2021-05-28T14:22:27.860Z","3.0.1-h32-setup-in-transaction-6fd70e1f0fbd8da1c15853ff5efb294a4161101d":"2021-05-28T14:39:15.187Z","3.0.1-renovate-external-non-major-63b2cd74132965460c2cf7ed38b5e969e2b4e868":"2021-05-29T00:39:15.851Z","3.0.1":"2021-05-29T00:46:23.386Z","3.0.2-renovate-major-external-major-6d62cc13348c3af94bff98cd0f725553232c570a":"2021-05-29T00:53:35.732Z","3.0.2-renovate-internal-patch-e03e392f2f42d36fc89369899669d1857408324d":"2021-05-29T02:03:29.508Z","3.0.2-renovate-internal-patch-f310f08940ae95adaaa4f2dd1647be5f9f0daebe":"2021-05-29T03:12:36.180Z","3.0.2-renovate-internal-patch-b5ae202d5b8684d0162e71888fd0d93627296295":"2021-05-29T04:43:51.965Z","3.0.2-renovate-internal-patch-5161973b007046a280c79f8dd28baadd60242050":"2021-05-29T07:40:45.216Z","3.0.2":"2021-05-29T07:46:10.757Z","3.0.3-renovate-major-external-major-6eff559a2cdd26e577d4663eeca04c549975a46f":"2021-05-29T07:50:46.626Z","3.0.3-renovate-internal-patch-156a43bc8c77e4cdf0c94d2c4daca3a4f9bbbeae":"2021-05-29T07:58:34.191Z","3.0.3":"2021-05-29T08:04:59.374Z","3.0.4-renovate-major-external-major-49c1b50b8001c6167144f2a6dc949e4beaeb5a66":"2021-05-29T08:10:13.840Z","3.0.4-renovate-internal-patch-ae76a04aabcd5f1ba62b31278c0008410d6815af":"2021-05-29T10:02:10.556Z","3.0.4-renovate-internal-patch-1740d29b676c6edc76d4e8ae8ce7cf006bca889e":"2021-05-29T13:56:03.032Z","3.0.4-renovate-major-external-major-1be9a439e8d83f91eb017339bb5282b677f90152":"2021-05-29T13:56:50.841Z","3.0.4-renovate-internal-patch-5be2f148a82223c88c2ace52e2b6913b4f71f3a7":"2021-05-29T14:40:42.749Z","3.0.4":"2021-05-29T14:46:04.463Z","3.0.5-renovate-major-external-major-52437616f683e1b8b78740a5d28193d8ce634998":"2021-05-29T14:52:34.258Z","3.0.5-renovate-major-external-major-da372a749c227cebd5cfb69de1537f2cdef86762":"2021-05-29T18:31:41.779Z","3.0.5-renovate-internal-patch-d6f45eabba58a1610c9407e456db241009a134ba":"2021-05-29T20:10:45.862Z","3.0.5":"2021-05-29T20:16:58.808Z","3.0.6-renovate-major-external-major-b6f579bc3cf4131f4601678bff56b329a55c7460":"2021-05-29T20:22:46.980Z","3.0.6-renovate-internal-patch-e93685f11c71b544045ba730c675d2572d0bbbf8":"2021-05-29T20:25:24.372Z","3.0.6":"2021-05-29T20:30:20.360Z","3.0.7-renovate-internal-patch-e113326a187605f8cc1302bcde42a53f7b910b3f":"2021-05-29T22:28:19.671Z","3.0.7":"2021-05-29T22:32:56.273Z","3.0.8-renovate-major-external-major-6738339e14d40176ab9c437d18e1d626195e92bb":"2021-05-29T22:37:42.488Z","3.0.8-renovate-balena-lint-6-x-dcd6c23c467c208370f83cd89ee37a6806c35fbb":"2021-05-30T01:07:37.784Z","3.0.8":"2021-05-30T01:49:31.107Z","3.0.9-renovate-major-external-major-b090b8f8dc4a7f0a19dba3d68c30b91b290f2891":"2021-05-30T01:54:27.167Z","3.0.9-renovate-internal-patch-f0e4b1e30f6eaa7a8de308fd464396d35c855fa6":"2021-05-30T15:30:48.721Z","3.0.9":"2021-05-31T00:03:31.407Z","3.0.10-renovate-major-external-major-72a14586525845c9383614216aa7bd924ced9500":"2021-05-31T00:08:07.556Z","3.0.10-renovate-internal-patch-09fb0dcbf77343f25031e24015cbe3ee3ebdfab7":"2021-05-31T04:21:31.708Z","3.0.10":"2021-05-31T04:26:27.931Z","3.0.11-renovate-major-external-major-56b5d8ff623d41050a44149d4482a3cc9b7898c3":"2021-05-31T04:30:54.898Z","3.0.11-repo-sync-jellyfish-config-default-903c0b7bc3bf1bc78e04838e48d7dd9273254fd5":"2021-05-31T07:44:33.149Z","3.0.11-renovate-major-external-major-7f635812eab0539cf0bc0fc550865329fce5f9d9":"2021-05-31T08:06:25.170Z","3.0.11-renovate-internal-patch-4e1cb6123a74add0f321ec677d648c041e17b401":"2021-05-31T09:33:42.108Z","3.0.11-renovate-balena-jellyfish-logger-3-x-01169794b1e2dbf4e683f614b1c1f013628922f8":"2021-05-31T09:38:06.575Z","3.0.11-renovate-internal-patch-6fa60af3663ca1e036aa68266efd222733843bc0":"2021-05-31T11:25:54.747Z","3.0.11":"2021-05-31T11:32:23.110Z","3.0.12-renovate-balena-jellyfish-logger-3-x-8d05dd4e07b9818dd6c1c7e6fa3f81def088d6af":"2021-05-31T11:36:45.056Z","3.0.12-renovate-major-external-major-76f62f3ff377ac7ee1b3c34ab662031590d9d4b5":"2021-05-31T11:37:23.790Z","3.0.12-renovate-internal-patch-4a9eae5034fe5ba46864976b8159725eb1934c7f":"2021-05-31T21:28:34.436Z","3.0.12-h32-setup-in-transaction-6aeb4788ab619c3dfcb9be5717eb8af67145c45e":"2021-06-01T14:17:53.945Z","3.0.12-h32-setup-in-transaction-5a7a58ef30df3c1eb44a3c9cb2b135eba889e9ee":"2021-06-01T14:35:49.135Z","3.0.12":"2021-06-01T14:52:56.334Z","3.1.0-add-support-for-query-mask-28b12b8285a58bbd4e695a95929e874db703ce62":"2021-06-02T05:16:24.190Z","3.0.13-renovate-internal-patch-7bb87a3b3f965f91ae318d941829b8a7e9354b92":"2021-06-02T05:31:15.322Z","3.0.13-h32-test-node14-b79a66b71857deffdbc504b507f524b8c5dd574a":"2021-06-02T07:34:23.102Z","3.0.13":"2021-06-02T07:39:22.362Z","3.0.14-h32-simplify-transaction-d3c1947ad6b73a811275de2622027918e3517916":"2021-06-02T07:56:06.104Z","3.0.14-h32-simplify-transaction-bd9b6d65d305b3157044184ebda58e31af1ba697":"2021-06-02T08:18:13.214Z","3.0.14-h32-simplify-transaction-00a7d260024c25209139b148e0c399bac237dbe3":"2021-06-02T09:47:31.481Z","3.0.14-h32-simplify-transaction-1476eb418c5afbb7af78f0ec2e09eb9701c62535":"2021-06-02T10:11:32.481Z","3.0.14":"2021-06-02T10:16:17.897Z","3.0.15-h32-fix-storedproc-names-74d80e7597bc307bfb08a81147968c87c94b8e46":"2021-06-02T10:59:07.267Z","3.0.15":"2021-06-02T11:03:57.622Z","3.0.16-remove-catch-uncomitted-9cee188aee92592a659d0a5964788904a7cd7d3f":"2021-06-02T23:19:10.135Z","3.0.16":"2021-06-02T23:24:07.963Z","4.0.0-add-loop-field-ts-5d73002cfc71714b91693c7d0d1419e708097a9a":"2021-06-03T03:20:08.223Z","3.1.0-add-support-for-query-mask-c319525cf67b1545fe44f80522b92c786dae01bd":"2021-06-03T03:24:58.802Z","4.0.0-add-loop-field-ts-be5dab956805ddc9178d51967e7bac801efe4570":"2021-06-03T05:47:13.600Z","4.0.0-add-loop-field-ts-d2a6e0eaed1c3ba5dc48657713d744f6d42dd067":"2021-06-03T07:09:57.485Z","4.0.0-add-loop-field-ts-dcfa269f5238c40c226a1fa9c25ec37a4eaadf26":"2021-06-03T07:25:51.346Z","3.0.17-avoid-underlying-connection-b81f67cabbfcbe4f085ce7d9ccfe7cd46c21937f":"2021-06-03T13:27:24.766Z","3.0.17-avoid-underlying-connection-9d8623ebe0074ded2abd66a2d17d2f962169b580":"2021-06-03T13:35:18.896Z","3.0.17-avoid-underlying-connection-04c7809752e25b3d4fb877d373e09282bee92468":"2021-06-03T15:23:17.746Z","3.0.17-avoid-underlying-connection-f7b787414548452e1b0c05afbfcdc9926d881d2f":"2021-06-03T15:40:11.384Z","3.0.17-optimize-commit-lint-ff2ff7ea118414e782f7e85afc49aa04fc04796b":"2021-06-03T15:42:56.081Z","3.0.17-h32-ensure-use-of-tx-conn-bd7a8e7735e34e3051b5cc9433b89491b946b16f":"2021-06-03T17:09:11.540Z","3.0.17":"2021-06-03T22:28:13.400Z","3.0.18-renovate-balena-jellyfish-logger-3-x-44cc567c02aff994cc2e61ad83295ecd1c70c81b":"2021-06-04T00:37:10.543Z","3.0.18":"2021-06-04T01:00:00.829Z","3.0.19-renovate-internal-patch-440dea294d6128008d0942a1f70b289085537253":"2021-06-04T01:13:52.147Z","3.0.19":"2021-06-04T01:18:21.720Z","3.0.20-bump-jest-deps-137a30019c5b29059e3bd6a704ce6be714d533b0":"2021-06-04T01:40:29.700Z","3.0.20-h32-ensure-use-of-tx-conn-b26e02a3f16852308bc35440d78c992a8e790b5e":"2021-06-04T08:26:38.623Z","3.0.20-h32-ensure-use-of-tx-conn-ba0dc768c09edda96f23aba3305627775be03cea":"2021-06-04T08:59:12.251Z","3.0.20-avoid-underlying-connection-a7b7cb427c6c7ad048da8337b103e89abcf89aa8":"2021-06-04T09:23:12.801Z","3.0.20-avoid-underlying-connection-bf25f3ef37dfab3d3a7068c6fca7d375c7d11597":"2021-06-04T09:41:03.576Z","3.0.20":"2021-06-04T09:45:53.591Z","3.0.21-h32-ensure-use-of-tx-conn-cd1b4d3188b16d874890ed09065ecdad1bcc6ce5":"2021-06-04T11:00:17.897Z","3.0.21":"2021-06-04T12:07:20.739Z","3.0.22-renovate-external-non-major-7343bac31a033935fa6f5b579b09e32d0bdddaa6":"2021-06-05T07:32:44.460Z","3.0.22-renovate-internal-patch-40caf879743b4b8da16d5241a82d62bb1f888184":"2021-06-05T07:33:17.790Z","3.0.22":"2021-06-05T07:39:17.582Z","3.0.23-renovate-balena-jellyfish-types-0-x-e963d69059061179abe599ac82c4982aef2aba1a":"2021-06-05T07:45:35.185Z","3.0.23-renovate-internal-patch-b1250aef076d198f654ac7cb36ff11208376d5e8":"2021-06-05T07:47:53.047Z","3.0.23-renovate-major-external-major-c094627dd61ea4dcea820c4d3e2ad84aa910fe64":"2021-06-05T07:48:25.504Z","3.0.23":"2021-06-05T07:54:14.120Z","3.0.24-renovate-balena-jellyfish-types-0-x-a4e68cd887ebb37aae93dd70845435e9f03bbed2":"2021-06-05T07:58:19.442Z","3.0.24-renovate-major-external-major-5373fb43a729971e2c8a5ad58969c59b405b3876":"2021-06-05T08:03:13.574Z","3.0.24-renovate-internal-patch-6bc39ef7473aab9e9b0f04633b4e61689692e68c":"2021-06-05T08:03:24.565Z","3.0.24-renovate-internal-patch-8713ab9234ab2e6f5a4da0c70db703c9872f7ae3":"2021-06-05T09:21:58.439Z","3.0.24":"2021-06-05T09:27:31.443Z","3.0.25-renovate-balena-jellyfish-types-0-x-b6c1cee5360f801eb13966911f944d1e8564739c":"2021-06-05T09:32:12.290Z","3.0.25-renovate-major-external-major-e15c031e02a822a83780d71f862817efcda5172d":"2021-06-05T09:33:42.088Z","3.0.25-renovate-internal-patch-39743676e754e33d78434f7c817e52f70a87f67f":"2021-06-05T11:02:36.509Z","3.0.25":"2021-06-05T11:08:07.118Z","3.0.26-renovate-balena-jellyfish-types-0-x-d40f535627e27fa843d7d7027512bc161143d2a0":"2021-06-05T11:12:45.799Z","3.0.26-renovate-internal-patch-4f88ea4ef252885f9f5e159caa653a6ccb17732d":"2021-06-05T17:04:19.275Z","3.0.26":"2021-06-05T17:09:57.256Z","3.0.27-renovate-balena-jellyfish-types-0-x-4de789b6bfda01621393ccd76ebf5a1e9ebb70b3":"2021-06-05T17:14:27.113Z","3.0.27-renovate-major-external-major-973af80f6b5437cde3e58fb0afba6dd0b6f275dd":"2021-06-05T17:15:52.461Z","3.0.27":"2021-06-05T23:39:04.308Z","3.0.28-renovate-major-external-major-4e97ab1471bd80d1e09aa99bb2e8b1aa38e7c845":"2021-06-05T23:43:36.896Z","3.0.28-renovate-internal-patch-731e6d58d5984331fda249cc5f290d9aaee452cd":"2021-06-06T11:36:37.380Z","3.0.28":"2021-06-06T11:41:48.351Z","3.0.29-renovate-major-external-major-c5eaebe5fc9c058f6239e69087bcdcfe8a6bb8f5":"2021-06-06T11:45:59.319Z","3.0.29-add-version-tests-05eb29da65e6fe8161851ef927f2ea57a0276c96":"2021-06-06T15:19:30.764Z","3.0.29-renovate-internal-patch-3a111985d106afd23ff3ca2715e9c3410e2765e9":"2021-06-06T17:36:40.854Z","3.0.29":"2021-06-06T17:43:55.221Z","3.0.30-renovate-major-external-major-df906c3f6ba63ce2963e0b8737a07591a0a8cb4f":"2021-06-06T17:48:47.374Z","3.0.30-renovate-internal-patch-138e15f7c0dac3f033c30c77d167e48afb5a535c":"2021-06-06T17:51:05.962Z","3.0.30":"2021-06-06T17:55:22.569Z","3.0.31-renovate-major-external-major-4b2580dea25def2bd1db2c49d77afd4c6062832f":"2021-06-06T18:00:38.874Z","3.0.31-renovate-internal-patch-e7eff6db2a65748449d72cb84ce60e58aeaaa7cd":"2021-06-06T18:22:39.539Z","3.0.31-renovate-internal-patch-a2ec4246c9476dc27c1526f2afe248722359edd3":"2021-06-06T18:35:37.465Z","3.0.31":"2021-06-06T18:41:32.691Z","3.0.32-renovate-major-external-major-d689e49010d9a915502396a519fbed923cf44209":"2021-06-06T18:45:18.341Z","3.0.32-renovate-internal-patch-59bd4ed91cba1703c10023ca8444a0e4a54f1392":"2021-06-07T01:09:44.228Z","3.0.29-add-version-tests-66bb05fdaa469c75c2993db4247c5f73afefa6a8":"2021-06-07T01:13:21.181Z","3.0.32":"2021-06-07T01:14:29.441Z","3.0.33-renovate-major-external-major-0921e1bdc1e8f356a22059a3e703a408ac08588f":"2021-06-07T01:20:30.068Z","3.0.33-add-version-tests-2e41531055c370ae82d9925f1779a931e0c88acf":"2021-06-07T02:26:15.500Z","3.0.33-renovate-internal-patch-513e65d48a8026a3f086d543535e519c40061786":"2021-06-07T03:12:41.927Z","3.0.33":"2021-06-07T03:19:12.008Z","3.0.34-renovate-major-external-major-8fa1007c4414e53a44689ff6c941bb82fb8fe188":"2021-06-07T03:23:37.457Z","3.0.34-add-version-tests-e84f813251e3639535b67476af01ee36f94f0e89":"2021-06-07T03:31:29.838Z","3.0.34":"2021-06-07T09:05:21.374Z","3.0.35-renovate-major-external-major-34895ebd5af7f16a641f432105fdeebcc0a40ae8":"2021-06-07T09:11:49.080Z","3.0.35-fix-type-deps-52a4bbee34ca1c785fbcf19d1e246527a8f8a7fe":"2021-06-08T00:54:06.046Z","3.0.35":"2021-06-08T01:07:13.935Z","3.0.36-renovate-major-external-major-a166913d81791efc7b24a8546c5ca7ecc4394cab":"2021-06-08T01:11:18.387Z","3.1.0-add-support-for-query-mask-a2b11a083444e50043e290cb8c4e4fe8a7e4ed62":"2021-06-08T02:13:42.236Z","3.1.0":"2021-06-08T02:58:59.775Z","3.1.1-renovate-internal-patch-c15f1432b4e8d12a13e2a20745f75b2c6780957d":"2021-06-08T03:05:31.927Z","3.1.1-renovate-balena-jellyfish-environment-4-x-7f10c16b50b595540ba527e5309ee7fbab352330":"2021-06-08T03:06:48.608Z","3.1.1-renovate-major-external-major-6ba2fe486fec6ddd28abb829fd4603499857fed1":"2021-06-08T03:08:06.514Z","3.1.1-renovate-internal-patch-3a4db6793e2bd9fc0243ff30dedecd9a30732607":"2021-06-08T03:13:04.780Z","4.0.0-add-loop-field-ts-f961e784304b1637dd83cb426f6b709bd12b1a54":"2021-06-08T03:15:00.766Z","3.1.1":"2021-06-08T03:18:18.877Z","3.1.2-renovate-balena-jellyfish-environment-4-x-90391ef1d142c1dd71cea168fb5f2ca3c8644d4d":"2021-06-08T03:22:52.473Z","3.1.2-renovate-internal-patch-07064257fc114f8942651041dff4be99e89e2f5f":"2021-06-08T03:28:25.345Z","3.1.2-renovate-internal-patch-e579a995ed0f2189a0c75680ccbda8006be5917a":"2021-06-08T04:51:45.476Z","3.1.2-renovate-internal-patch-734536481fb41d0f41c8e50bb092b646f0904019":"2021-06-08T04:57:12.598Z","3.1.2-renovate-balena-jellyfish-environment-4-x-026dd2fa6c479d575858641651ec72b86b9d6903":"2021-06-08T04:57:32.407Z","3.1.2":"2021-06-08T05:03:04.457Z","3.1.3-renovate-balena-jellyfish-environment-4-x-dfb60e952274136fc5fe8365b057f60b6ef5c1ef":"2021-06-08T05:08:33.146Z","3.1.3-renovate-major-external-major-10bf066c7c56e2239168dc9e2b59840d08d41cfd":"2021-06-08T05:09:03.841Z","3.1.3-renovate-internal-patch-8583639a48577ffaa005c5ed8a7b4284c88976c7":"2021-06-08T07:57:32.868Z","3.1.3":"2021-06-08T09:16:05.810Z","3.1.4-renovate-internal-patch-dad7226aeb041746ffc23b66ee85683d083ac540":"2021-06-08T09:21:02.062Z","3.1.4-renovate-major-external-major-1fc57b5f8cef8882bbdf0db11ae663d8f7b805b4":"2021-06-08T09:22:43.652Z","3.1.4":"2021-06-08T09:30:57.324Z","3.1.5-renovate-major-external-major-d4a5b464df3dd6d4c38945e64d8abc5c248c9ed7":"2021-06-08T09:35:02.856Z","4.0.0-add-loop-field-ts-aa785a82497616fd2b2aad809a5371af0cc64dbe":"2021-06-08T10:00:43.514Z","3.1.5-renovate-internal-patch-40519c9ac0822113a96f58cf0019b037a9979f2b":"2021-06-09T03:28:28.113Z","3.1.5":"2021-06-09T03:34:40.327Z","3.1.6-renovate-major-external-major-5e75147d71de080db6ce5e40dd882cbccce7cc07":"2021-06-09T03:38:57.672Z","4.0.0-add-loop-field-ts-99b7965b07e7e5566b9f24aa9295da8928841c5d":"2021-06-10T01:56:03.306Z","4.0.0-add-loop-field-ts-c957d59d0363840e9116016f2f561bcfe888b9e3":"2021-06-11T01:40:23.321Z","3.1.6-update-docker-files-dbe1298a14a7dda06521a40f502c47ee688ffd81":"2021-06-11T01:48:58.714Z","3.1.6":"2021-06-11T02:06:15.235Z","3.1.7-renovate-major-external-major-1de233416c53d43f8d69113ddfbd2236c568b8f6":"2021-06-11T02:11:26.222Z","3.1.7-renovate-internal-patch-e6d12768c1a13b89526ae819e1d6ec83bd0a1f0d":"2021-06-11T06:36:06.205Z","3.1.7":"2021-06-11T06:40:52.529Z","3.1.8-renovate-major-external-major-3204299bb5b8bb748ce598aab3a5c572a655d3a1":"2021-06-11T06:45:26.091Z","3.1.8-renovate-internal-patch-d6392ba30b76aaf5a87077bf7ade44009f8160d8":"2021-06-11T09:43:50.642Z","3.1.8":"2021-06-11T09:48:16.331Z","3.1.9-renovate-major-external-major-1885ddd7351926cd2051641acdfdccbc2b58c2bc":"2021-06-11T09:52:48.441Z","3.1.9-renovate-internal-patch-70ee23bc60779c25d01417e138befde9348958db":"2021-06-12T01:14:40.439Z","3.1.9-renovate-internal-patch-a03d218787f7d423bf90e5036ceb7e5a0b2f7937":"2021-06-12T03:03:12.634Z","3.1.9-renovate-internal-patch-9e99a68c11846a48457861ef1de974a3549b4d3a":"2021-06-12T03:08:44.356Z","3.1.9":"2021-06-12T03:14:25.714Z","3.1.10-renovate-major-external-major-da75953c0b0c651288a6d9be25f6c75703bd76d2":"2021-06-12T03:19:19.981Z","3.1.10-renovate-internal-patch-b6da586d57653d188491e166e26c0c9603521a80":"2021-06-12T10:04:23.853Z","3.1.10":"2021-06-12T10:09:24.809Z","3.1.11-renovate-major-external-major-2a3f493f28339420aa9b31bd6ed62b4849ebd5f0":"2021-06-12T10:13:45.610Z","3.1.11-renovate-internal-patch-93bb531f3c8cbe588467a79543162abbbcb8bbf8":"2021-06-12T17:01:57.548Z","3.1.11":"2021-06-12T17:06:58.131Z","3.1.12-renovate-major-external-major-9739884d35970597debd6e248fbe674acbd5f36d":"2021-06-12T17:13:40.732Z","3.1.12-renovate-internal-patch-55991aa41b0bc9137ff0cb2beba8a7b89f482531":"2021-06-13T03:37:00.192Z","3.1.12":"2021-06-13T03:42:00.725Z","3.1.13-renovate-major-external-major-fd6b04e94c1b23ec32ace8082b11564b1cbbb02d":"2021-06-13T03:46:28.272Z","4.0.0-add-loop-field-ts-557df42a1e1583fe0d25713179048ccff8c764e0":"2021-06-14T07:25:11.141Z","3.2.0-versioned-slug-07b219e248ddf7e060afb69a523bff1ae98da542":"2021-06-14T07:55:42.326Z","3.2.0-versioned-slug-f72844447a3c67c6a1e0f2753fa1121fad5fc194":"2021-06-14T09:15:25.396Z","3.2.0-versioned-slug-efbdf1b094684a4cb9f028d86ed4892f955bbfe0":"2021-06-14T10:17:04.825Z","3.2.0-versioned-slug-296db031adb90f7da5b3f2820764fad6aa9d845c":"2021-06-14T10:56:14.836Z","3.2.0-versioned-slug-1853e77437a405b711221e662737eaeaa4be6850":"2021-06-15T00:50:28.250Z","3.1.13-remove-relationships-b6eff9bd625751e78877edb9abd8639a71cda410":"2021-06-15T01:02:49.739Z","3.1.13":"2021-06-15T01:07:58.703Z","3.1.13-renovate-internal-patch-589d1667d951d76e04d69b0f30d8ae9975fc0a7a":"2021-06-15T01:11:00.119Z","3.1.14-renovate-internal-patch-53ff7eac56b5481353ad397e15c5fdbb0a5c754d":"2021-06-15T01:15:48.331Z","3.1.14-renovate-major-external-major-773911e7dfeb49c973e3398da254216b174cc621":"2021-06-15T01:16:50.408Z","3.1.14":"2021-06-15T01:21:00.865Z","3.1.15-renovate-major-external-major-20713bb4936a837f0aeef1c50da6538fa426483a":"2021-06-15T01:25:58.564Z","3.1.15-renovate-internal-patch-dda806fd9a7dbfd35841f89f35c225acc9f13ae8":"2021-06-15T02:21:04.772Z","3.1.15":"2021-06-15T02:27:14.542Z","3.1.16-renovate-major-external-major-bbf5c0c6f4cdcea619d9a3a2f3c227b2cfbd2c38":"2021-06-15T02:31:05.563Z","3.1.16-renovate-internal-patch-60abd8fd660b6ad6409113ded557daa53ae8f6d1":"2021-06-15T02:40:36.802Z","3.1.16":"2021-06-15T02:47:03.906Z","3.1.17-renovate-major-external-major-0b88d179e5f194cf8dc8876b7afd446aefa5bad2":"2021-06-15T02:52:23.576Z","3.1.17-renovate-internal-patch-08d87668e3772c16ab32f058fab490feaa8e5d1c":"2021-06-15T06:03:51.235Z","3.1.17-renovate-internal-patch-5428d4a4eef1a0c1834ef695f253ae92acd98af1":"2021-06-15T06:09:18.095Z","3.1.17":"2021-06-15T06:14:11.114Z","3.1.18-renovate-major-external-major-25b172a7ad58ea597f31415f08ee899b5df089ac":"2021-06-15T06:18:39.845Z","3.1.18-fix-indexdef-generation-7b308651c53f1f029c45d8fe9599099373805b00":"2021-06-15T09:42:29.192Z","3.2.0-check-link-targets-ae1db0714258c95eba9a8b8ffe7d971c7830a394":"2021-06-18T00:02:50.791Z","3.2.0-check-link-targets-cd16cacf4f5d83ac791bea38e1ef8fea05a9c1d1":"2021-06-18T00:33:14.216Z","3.2.0-check-link-targets-bf499820409cf51fedf6f1e88f25bf959f3daeaa":"2021-06-18T01:01:09.936Z","3.1.18-fix-link-target-version-bfc7fd3dd01cc18d06bff32ca8ee4a5a1b2dbaef":"2021-06-18T01:29:08.476Z","3.1.18":"2021-06-18T01:34:05.494Z","3.1.19-renovate-internal-patch-0df62ccc271f892980f999783f001d942714d155":"2021-06-18T01:41:10.939Z","3.1.19-renovate-major-external-major-2232b8fa6da2f9a6f565c7f9c7126e632e2e9896":"2021-06-18T01:41:48.795Z","3.1.19":"2021-06-18T01:46:16.297Z","3.1.20-renovate-major-external-major-8bbaea222a0ec721793657ec014072d5369fa204":"2021-06-18T01:51:04.244Z","3.2.0-check-link-targets-3f5752e2498c5f4566ba1b1f860ba24f5f5be05d":"2021-06-18T16:12:31.630Z","3.2.0":"2021-06-18T16:18:20.188Z","3.2.1-renovate-major-external-major-1134256b881ca43ae00109c921b71d4b4e0a4893":"2021-06-18T16:25:18.867Z","3.2.1-renovate-internal-patch-32deada756d9ba04b159d026e6953f99d55e3f95":"2021-06-19T00:50:06.684Z","3.2.1-renovate-external-non-major-5d54b0415c493b1432cf994b2ecd17f2b7c1662f":"2021-06-19T00:51:07.125Z","3.2.1":"2021-06-19T00:55:38.158Z","3.2.2-renovate-external-non-major-31c173eedbd7fde77316bf9318404a142bd792b0":"2021-06-19T01:00:28.873Z","3.2.2-renovate-major-external-major-a9ea2af83da82635e384ec90539970b858f8ecfc":"2021-06-19T01:02:57.121Z","3.2.2":"2021-06-19T01:10:58.190Z","3.2.3-renovate-major-external-major-d2838d48762fd5a007fd424f5249e8c2a67f106c":"2021-06-19T01:15:32.149Z","3.2.3-renovate-internal-patch-1db23ae9a9607dfe9306e1332857cf4e68718701":"2021-06-19T02:52:07.783Z","3.2.3":"2021-06-19T02:57:27.545Z","3.2.4-renovate-major-external-major-7d09d7535413462809890eb73e829963133d7f81":"2021-06-19T03:02:34.542Z","3.2.4-renovate-internal-patch-d6c63137a8a35eb6186d0319fe8f4203fdb91870":"2021-06-19T07:07:49.231Z","3.2.4":"2021-06-19T07:11:37.898Z","3.2.5-renovate-major-external-major-bb8e996a61981097114aecd8cae5b766c21f67f8":"2021-06-19T07:16:14.633Z","3.2.5-renovate-internal-patch-d3591a769b6ba14119cb71661de79d99ec17cf64":"2021-06-19T07:23:34.556Z","3.2.5":"2021-06-19T07:28:37.094Z","3.2.6-renovate-major-external-major-4ae4f076bfff14f9c957a84bf1fd80b153d6c86f":"2021-06-19T07:33:08.524Z","3.2.6-renovate-internal-patch-a041df8b11e8af97fe9a6ce7d74ca623ca750bb8":"2021-06-19T10:30:19.526Z","3.2.6-renovate-internal-patch-7978b3fdb774af9785e9df3b600df5351ad03296":"2021-06-19T10:35:13.036Z","3.2.6-renovate-internal-patch-fce47545d9451c3bf10b0e51656e651b19e29f7d":"2021-06-19T10:39:47.160Z","3.2.6":"2021-06-19T10:45:07.131Z","3.2.7-renovate-major-external-major-c1e790223adfec9e32ff4d8eb1c8d5a90dc25bb2":"2021-06-19T10:52:00.207Z","3.2.7-renovate-internal-patch-333295ad4e6fea9411bbd4d830ad32db6e5c486a":"2021-06-19T13:37:28.434Z","3.2.7":"2021-06-19T13:42:51.608Z","3.2.8-renovate-major-external-major-58c46ee493e66990f690caeb5b754ba3e2f5d04b":"2021-06-19T13:47:25.305Z","3.2.8-renovate-internal-patch-d7936546e09ee95ffd6850681d43e3267f58c619":"2021-06-20T16:01:36.205Z","3.2.8":"2021-06-20T16:07:54.795Z","3.2.9-renovate-major-external-major-8e5fc578db156b5409497534c9f9d48f80448bd4":"2021-06-20T16:11:51.052Z","4.0.0-add-loop-field-ts-47d940022a5408f9ce9614f0f72206f7d64e806a":"2021-06-22T03:58:54.828Z","3.2.9-renovate-balena-ci-task-runner-0-x-9cfb9dc73c0fcddaaad8a690b6a5aa5a23a9c69a":"2021-06-22T06:57:11.707Z","3.2.9-renovate-internal-patch-df980219692af1b53e235c1fad0f526db3d1a76f":"2021-06-22T07:52:17.807Z","3.2.9-renovate-internal-patch-3c66c3df98c28c2622418a4875fd58aa045f6777":"2021-06-22T07:55:53.957Z","3.2.9-renovate-internal-patch-6e3d3e7afc9c60a4949585b7c1b0a69abf1080dd":"2021-06-22T08:02:18.708Z","3.2.9":"2021-06-22T08:07:21.135Z","3.2.10-renovate-balena-ci-task-runner-0-x-b49d1b255aba0ed8898f1f7060770091ab8bfa79":"2021-06-22T08:13:02.887Z","3.2.10-renovate-major-external-major-79c5ef7bb50d4ee39b6f80faf25a9a29926eba8f":"2021-06-22T08:13:52.629Z","3.2.10-renovate-internal-patch-4b564f52a1d4d72df71a7af06f4fe6a959549405":"2021-06-22T08:14:31.576Z","3.2.10":"2021-06-22T08:20:26.687Z","3.2.11-renovate-major-external-major-91f9b22a08f553acc291b4ffe613a348b61fd147":"2021-06-22T08:26:04.350Z","3.2.11-renovate-balena-ci-task-runner-0-x-e063f563bdca87ca61ca832691110d54bc319e4c":"2021-06-22T08:31:18.908Z","4.0.0-add-loop-field-ts-2b06124c58bbe94078f43bf5dc2bc8636c17bc9a":"2021-06-22T10:17:09.096Z","4.0.0-add-loop-field-ts-9045dea5ba47a375828bdbd4f48a8116029cd7f6":"2021-06-22T10:39:17.510Z","3.2.11-renovate-balena-jellyfish-types-0-x-7fcc894cd436d58a452f7c763ac69be32917ad95":"2021-06-22T11:01:04.367Z","3.2.11-renovate-major-external-major-ef92202a4c71ba589ff18014b96c37495662b115":"2021-06-22T13:13:10.159Z","3.2.11":"2021-06-22T22:58:42.379Z","3.2.12-renovate-balena-ci-task-runner-0-x-e75586ee77d22e62810560011a59dc18b492aa4c":"2021-06-22T23:02:05.851Z","3.2.12-renovate-major-external-major-56ab01fa0aea2517d4995cfaba8c23f7003ab767":"2021-06-22T23:03:59.262Z","3.2.12":"2021-06-22T23:11:00.630Z","3.2.13-renovate-major-external-major-d175994dd1998147bc6a2c78127c3e1f4440f55c":"2021-06-22T23:16:05.422Z","3.2.13-renovate-internal-patch-ae811eeb388610903d430a48c1161b91e0404bd0":"2021-06-23T01:06:00.308Z","3.2.13":"2021-06-23T01:11:56.612Z","3.2.14-renovate-major-external-major-09e4c1ff3a32de58d7afa1181ae2b581c079ffce":"2021-06-23T01:15:45.329Z","4.0.0-add-loop-field-ts-1dd5a52d8f4db664b3af206ddd5106813040d94b":"2021-06-23T01:54:40.205Z","3.2.14-renovate-internal-patch-77e66462b32f10e49e36fadec29eb1833c8307cb":"2021-06-23T06:49:39.777Z","3.2.14-renovate-internal-patch-21ec1b88107a6a47d79b1a35318ea2c0dfa4fb60":"2021-06-23T07:52:06.648Z","3.2.14":"2021-06-23T07:57:31.371Z","3.2.15-renovate-major-external-major-4fde2c2c7c782eb392d851ed995f883fc27477e5":"2021-06-23T08:02:39.526Z","4.0.0-add-loop-field-ts-2d6259558086fb4fd446076b5590981f6e0a07c9":"2021-06-23T12:00:53.558Z","3.2.15-renovate-internal-patch-a7bf8c024dbd48ec832f3ea0729f79fcad205f11":"2021-06-24T00:01:02.831Z","3.2.15":"2021-06-24T00:08:44.071Z","3.2.16-renovate-major-external-major-45093d10ba2231402f3e2bf1a79e8454e81d2c13":"2021-06-24T00:19:49.832Z","4.0.0-add-loop-field-ts-db67490e3a984ec221a108e50b74bfa625e39841":"2021-06-24T00:38:38.095Z","4.0.0":"2021-06-24T03:11:13.178Z","4.0.1-loop-type-check-441f31761704581dec2fbb857a71654ce18e9472":"2021-06-24T07:29:06.472Z","4.0.1":"2021-06-24T16:18:19.359Z","4.0.2-renovate-balena-jellyfish-environment-4-x-bd322b84adfcb5fbfbef24257fba01b388666821":"2021-06-24T16:25:55.098Z","4.0.2-renovate-internal-patch-26f77e380f05ca35294f680b70d740f8b1317f43":"2021-06-24T16:26:38.415Z","4.0.2-renovate-major-external-major-5d08636ed5712972bc1a42d97e425f214bf21d0a":"2021-06-24T16:28:26.459Z","4.0.2":"2021-06-24T16:31:38.172Z","4.0.3-renovate-balena-jellyfish-environment-4-x-1bf792c8d29f092af63d5c7519676f2b898b4042":"2021-06-24T16:35:27.619Z","4.0.3-renovate-major-external-major-e76452f50ba2d6fd1727da1442929333081a6b0e":"2021-06-24T16:36:51.584Z","4.0.3-build-include-backend-types-f1bf0920369994b81301a95d158664175a0d279a":"2021-06-24T16:39:25.951Z","4.0.3":"2021-06-24T18:28:24.521Z","4.0.4-renovate-balena-jellyfish-environment-4-x-716a13d07e9ab7b6e2ff4f1adcdfea0a8d92d60a":"2021-06-24T18:35:45.584Z","4.0.4-renovate-major-external-major-003f6abe5f6939858c146057c0c1ae4fdfe45ddf":"2021-06-24T18:38:57.276Z","4.0.4":"2021-06-24T23:00:21.711Z","4.0.5-renovate-major-external-major-4ee1c293b8ae67bc8a7426e2b34e9512fe28bd15":"2021-06-24T23:06:11.138Z","4.0.5-renovate-internal-patch-db25471665ce1467a5ffa62f10ff85844ccdad35":"2021-06-25T01:43:37.921Z","4.0.5":"2021-06-25T01:48:16.174Z","4.0.6-renovate-major-external-major-b608af4aa6b8d48fa2f859909c7998fda0e8f607":"2021-06-25T01:54:19.578Z","4.0.6-renovate-internal-patch-79756a154a4b859e0b9f181b19dab4ac82f030f8":"2021-06-25T02:38:23.423Z","4.0.6":"2021-06-25T02:45:20.799Z","4.0.7-renovate-major-external-major-a6c86ac1a6fc1f65bbe68bcdbf7d98d2f8ccae78":"2021-06-25T02:49:38.178Z","4.0.7-enhanced-type-generics-7dae7cc67bcbd478c2b9c0bf33731416651eff4a":"2021-06-25T08:54:14.878Z","4.0.7-enhanced-type-generics-bdc4abfc8579cf1edf14bb8d83beae5f3c731806":"2021-06-25T09:10:22.051Z","4.0.7":"2021-06-25T09:20:09.159Z","4.0.8-renovate-major-external-major-a3665e6417a4a002c7387bab779e073c28e82396":"2021-06-25T09:27:27.061Z","4.0.8-renovate-internal-patch-885f0f579567d1552ffa6dddfaf95aeaca09cb5f":"2021-06-25T22:52:22.335Z","4.0.8-renovate-internal-patch-611e0ff8804819a941fa6b9268b69560450b01d5":"2021-06-25T22:58:33.940Z","4.0.8":"2021-06-25T23:03:53.533Z","4.0.9-renovate-major-external-major-9fd8f0350a14e1869584c359110c0e4bf096dbb6":"2021-06-25T23:08:24.585Z","4.0.9-renovate-external-non-major-208f0de58e77dd3289cb1bf44c694b8d60a26198":"2021-06-26T00:57:50.437Z","4.0.9-renovate-internal-patch-02ce9cc2b60d1566a40b555af123693ab891cbe6":"2021-06-26T00:58:32.517Z","4.0.9":"2021-06-26T01:03:08.167Z","4.0.10-renovate-internal-patch-19c67c73f5731281232dcf4e7f8ec01c6125a098":"2021-06-26T01:08:17.870Z","4.0.10-renovate-major-external-major-e9ce07bd1509fab16a87287ac4d6ab6735a7f9c0":"2021-06-26T01:09:52.682Z","4.0.10":"2021-06-26T01:16:42.814Z","4.0.11-renovate-major-external-major-92d28f042cfef5b8956f0e267ec0745d5c69af46":"2021-06-26T01:21:16.027Z","4.0.11-renovate-external-non-major-85672f0bd00447e398ff7953ed9c06b2746d4562":"2021-06-27T03:06:21.612Z","4.0.12-renovate-major-external-major-dcb2c922cdea3ccdac97626ac853621a88c80dc8":"2021-06-27T03:18:57.630Z","4.0.12-renovate-external-non-major-f2a544f72c0bc8130714f02fe3b52e3b3a5ec036":"2021-06-27T20:30:02.899Z","4.0.13-renovate-major-external-major-f4c30634aef8d90fb31c65eb98aa13da67fe43b1":"2021-06-27T20:39:12.669Z","4.0.13-renovate-internal-patch-1d0ec0f41ccf1bd7306c843ab759d8d7553eb5ec":"2021-06-27T21:39:44.938Z","4.0.14-renovate-major-external-major-8e068964a5b4d6ce2a19761a06f1422362f1352d":"2021-06-27T21:49:35.490Z","4.0.14-renovate-major-external-major-329d3da161a515541780fd2d382a5db33b502da3":"2021-06-28T18:28:51.746Z","4.0.14-remove-package-lock-4d629d328a67f41e72bd1c2ec65752b5d9df2cb9":"2021-06-29T08:06:32.375Z","4.0.14-repo-sync-jellyfish-config-default-4089216a79fbae926a54e8246ed965738f17c387":"2021-06-29T08:41:04.541Z","4.0.14-remove-package-lock-3377ced918b196a7a811b599b8915287120e98a8":"2021-06-29T08:41:46.814Z","4.0.15-renovate-major-external-major-d42452db99ffbaa64aad17037b5e99bb050732b4":"2021-06-29T08:53:08.369Z","4.0.15-remove-package-lock-eb67e76fec7fb764d8ffe3407607823f767bd827":"2021-06-29T08:53:26.253Z","4.0.16-renovate-major-external-major-a3af8fec3e0445cb314f62dc269ee453c7985a7d":"2021-06-29T09:12:47.605Z","4.0.16-renovate-internal-patch-600b8e7a428564b13f118e256136d773ccdb323f":"2021-06-29T10:32:55.283Z","4.0.16-renovate-internal-patch-f31eaca5463e5d2517d94c5a963b8371e873819a":"2021-06-29T11:51:16.240Z","4.0.16-renovate-internal-patch-b32e15756ca0315a4eede57c7600b67eabef7dbd":"2021-06-29T12:31:47.460Z","4.0.16":"2021-06-29T12:37:45.191Z","4.0.17-renovate-major-external-major-a6de17f4272b5b929d756d6ed28d18d1bc45ec14":"2021-06-29T12:41:23.062Z","4.0.17-renovate-internal-patch-29252fdc6ab6316db9449e1b4b96e7e6d8523ade":"2021-06-30T00:10:03.740Z","4.0.17":"2021-06-30T00:15:31.734Z","4.0.18-renovate-major-external-major-013e8a5998ef7f741d531b5223f8bf34eb81c24d":"2021-06-30T00:19:08.289Z","4.0.18-renovate-internal-patch-2631d15f9e37b717976c8b795b9b8d8fa623769a":"2021-06-30T02:07:16.913Z","4.0.18":"2021-06-30T02:12:45.663Z","4.0.19-renovate-major-external-major-d88673df7a7a99349eeb1997c56f4c381dd20496":"2021-06-30T02:16:51.971Z","4.0.19-repo-sync-jellyfish-config-default-b5f7d39816208e5442793c9a57acca909c572fe5":"2021-06-30T02:19:43.226Z","4.0.19":"2021-06-30T02:24:49.967Z","4.0.20-renovate-major-external-major-0ed803bc1dffe1e01442a6414dee2713d2565917":"2021-06-30T02:30:44.852Z","4.0.20-renovate-internal-patch-3c0c0934ac58628fe221c5e6b1fce31d63c7558f":"2021-06-30T03:25:51.607Z","4.0.20":"2021-06-30T06:07:07.332Z","4.0.21-renovate-major-external-major-f116730b0effe47837690f7442a962a13ef15628":"2021-06-30T06:10:18.167Z","4.1.0-loop-widget-d3c219911e4da8f8a1a2db170f25162bce096fa9":"2021-06-30T06:20:29.986Z","4.1.0-loop-widget-cd8f5085ee1cef4714d8aaaefb8e312f5b1e0502":"2021-06-30T09:35:11.831Z","4.0.21-remove-loop-migration-code-3c028d44707b3df4fa9b7dc27671f758be43b0bd":"2021-06-30T13:02:14.855Z","4.0.21":"2021-06-30T14:47:17.850Z","4.0.22-renovate-major-external-major-8bfd05c3a0fd3ff1feb4ed894a861ddddf4bf9d1":"2021-06-30T14:54:11.864Z","5.0.0-fix-create-export-c6d0162568c83328f13fa442243288c6b01ff296":"2021-07-02T01:11:41.637Z","4.0.22-use-go-task-4e808a5b4271ef4ebaa8a3ee971d573f9b320f01":"2021-07-02T04:07:25.694Z","4.0.22":"2021-07-02T04:17:59.904Z","4.0.23-renovate-major-external-major-fb656039c267dfb7185c647361e9f5cdb7142d83":"2021-07-02T04:21:36.337Z","5.0.0-fix-create-export-17310b16687f45bdd12d84ba75a677fb1b1bb1c4":"2021-07-02T06:13:25.605Z","4.0.23-renovate-internal-patch-41f9e16b438c3b3224afa0bd4d5dca5c315bf003":"2021-07-02T06:59:07.252Z","4.0.23-renovate-balena-jellyfish-types-0-x-8c3500568b6b1bbaa1c05f1c9ff0f07109745a3b":"2021-07-02T07:00:17.625Z","4.0.23-renovate-internal-patch-50c55a9d3c5ae918b1925b57b49d2ccf42f6302d":"2021-07-02T07:50:25.737Z","4.0.23":"2021-07-02T08:00:01.736Z","4.0.24-renovate-balena-jellyfish-types-0-x-5a1c178bd211a3b9d8fb38e9b92dea5bafb3d66a":"2021-07-02T08:03:46.767Z","4.0.24-renovate-internal-patch-be60a0e0fe1b541092eb25292f8333c28fe82cf5":"2021-07-02T09:12:33.593Z","4.0.24":"2021-07-02T09:18:45.540Z","4.0.25-renovate-major-external-major-fb4e9dee413847eb609f390f2db2c95d74aa6db9":"2021-07-02T09:24:42.978Z","5.0.0-fix-create-export-3201a24f17ba8391330f9c8d41f4b2e48fa107fd":"2021-07-02T09:28:54.812Z","5.0.0":"2021-07-02T15:02:13.379Z","5.0.1-renovate-balena-jellyfish-types-0-x-cf2397e08910e674ba1c8ad54d9d8054aaaa3613":"2021-07-02T22:31:08.247Z","5.0.1":"2021-07-02T22:42:13.530Z","5.0.2-renovate-major-external-major-bff2e26bcb9045e12ff903951ad2a3d61972729f":"2021-07-02T22:46:31.412Z","5.0.2-renovate-internal-patch-6c2d16bb450d4d49aee58f2a15f725d526ee468d":"2021-07-02T22:51:25.656Z","5.0.2":"2021-07-02T22:57:50.740Z","5.0.3-renovate-major-external-major-34dc199ffafae88e698583c1d7e82f6bc67b1c92":"2021-07-02T23:01:37.659Z","5.0.3-renovate-external-non-major-9ddf40c069d949b8805844c7337cadda9136f814":"2021-07-03T00:18:41.489Z","5.0.3-renovate-internal-patch-083beee7d106c0c5a9882ff07baa64b125484ac9":"2021-07-03T01:41:18.604Z","5.0.3-renovate-internal-patch-ade6e77ba9cdca66c7ca3177808a0b627a4b5e22":"2021-07-03T01:45:15.808Z","5.0.3":"2021-07-03T01:50:10.793Z","5.0.4-renovate-external-non-major-5dcf727d376f7d79b7e0794f0094f8055a3ba48c":"2021-07-03T01:54:28.629Z","5.0.4-renovate-internal-patch-bcb3af4dd07c8dcce87fe4d4ba74e149eca88ed4":"2021-07-03T01:57:13.124Z","5.0.4-renovate-internal-patch-eec4c5363705b6684f67c9ba2717d9050f99a5fe":"2021-07-03T02:01:57.187Z","5.0.4":"2021-07-03T02:02:29.163Z","5.0.5-renovate-major-external-major-5f66873831fe2dfc8d0d62dc615e791f39d76141":"2021-07-03T02:09:26.092Z","5.0.5-renovate-internal-patch-1c371a0a6c1631a2c8b3166d7c3fccc306f5c14d":"2021-07-03T02:14:28.694Z","5.0.5-renovate-internal-patch-23a654794c58bf889b5f76b8263ccc9c864d5bdb":"2021-07-03T02:18:52.692Z","5.0.5":"2021-07-03T02:26:06.343Z","5.0.6-renovate-internal-patch-656521503b16894bcd8c02946610c787fece7a52":"2021-07-03T02:29:32.531Z","5.0.6-renovate-internal-patch-0a095ee8548474c06398e89bb41912b7548ad744":"2021-07-03T02:36:33.443Z","5.0.6":"2021-07-03T02:42:15.346Z","5.0.7-renovate-major-external-major-86bbf249dabe5900c0e9c6469f3c71675a57a41e":"2021-07-03T02:46:45.675Z","5.0.7-repo-sync-jellyfish-config-default-67e8e074a3ff143e9492f139bb9e7cad3d757107":"2021-07-03T14:51:56.701Z","5.0.7":"2021-07-03T14:57:48.441Z","5.0.8-renovate-major-external-major-f36562462a6a3438b8d434ab62f76988f22b146a":"2021-07-03T15:03:10.569Z","5.0.8-renovate-internal-patch-6a7777ffe66a4b05463610d40aaded7859b1bfe9":"2021-07-05T03:01:15.629Z","5.0.8":"2021-07-05T03:07:07.806Z","5.0.9-renovate-major-external-major-52489c052acce6f69dc4dd9469e38ef64e643dd6":"2021-07-05T03:13:41.028Z","5.0.9-renovate-internal-patch-9574a50ab94e87002f8f96cb84b36b9454226178":"2021-07-05T05:21:33.116Z","5.0.9":"2021-07-05T05:26:57.631Z","5.0.10-renovate-major-external-major-e6797bb0296febe49a04229f8de4724027c64b40":"2021-07-05T05:31:19.647Z","5.1.0-failing-query-8e587c49860523ee99f5cd1d0381fa1cc0950b74":"2021-07-05T09:56:39.534Z","5.0.10-renovate-internal-patch-59fde4e49757d007625311a5fd0db8930d9a067c":"2021-07-05T15:58:17.919Z","5.0.10":"2021-07-05T16:02:30.692Z","5.0.11-renovate-major-external-major-e1e9bc6f600d9a2324ea2e27976084635ae14be7":"2021-07-05T16:11:00.541Z","5.0.11-renovate-internal-patch-1e82e70ffcc50ffd95adbbbe05a82ae1a18b782f":"2021-07-05T17:35:00.411Z","5.0.11":"2021-07-05T17:43:11.025Z","5.0.12-renovate-major-external-major-1e55e6854e6074e479d6e4ba25fdaaa33a1b2178":"2021-07-05T17:45:48.117Z","5.0.12-renovate-internal-patch-9d66a642ab5cfdf1cec889c0b55c480f76b7c7d8":"2021-07-07T01:03:00.669Z","5.1.0-loop-widget-0fa8e42354d9c13be137b2eceb2bb9d149f95dbd":"2021-07-07T01:09:10.879Z","5.0.12":"2021-07-07T01:09:29.133Z","5.0.13-renovate-major-external-major-037590442258bb988fdc514b27de4c70c4d21a42":"2021-07-07T01:13:56.454Z","5.1.0-loop-widget-6ec113521b9dafcf5284f7f4717ada79533ec6bd":"2021-07-07T01:22:01.353Z","5.1.0":"2021-07-07T01:27:41.399Z","5.1.1-renovate-major-external-major-2bdf293c5fdc812ea01780408b565cb19dce211d":"2021-07-07T01:34:19.727Z","5.2.0-h32-safe-migrations-694c2a05c645f3d5631a5b6333ebfa34031836bb":"2021-07-07T16:56:39.236Z","5.1.1-renovate-internal-patch-76d347d04510c3e9856065bbfdb6486bd1b1249f":"2021-07-07T23:32:50.100Z","5.1.1":"2021-07-07T23:39:00.279Z","5.1.2-renovate-major-external-major-b1f972433c3e75f6beb7729b2110a93d890e4367":"2021-07-07T23:42:34.717Z","5.2.0-h32-safe-migrations-0cc23bbf5baf1cae4b817198a7cbf85ba08e0af8":"2021-07-08T02:01:16.781Z","5.2.0-h32-safe-migrations-96ec945323e39670438469a654e6a0734133b224":"2021-07-08T07:28:24.025Z","5.2.0-h32-safe-migrations-0bb9f55f66eabccc582b63dd600b2dc80f5999bb":"2021-07-08T08:06:39.020Z","5.2.0-h32-safe-migrations-b3bb83d0a83d3c77cc896d4fe873c122dcdd71bf":"2021-07-08T09:46:58.979Z","5.1.2-no-inactive-sessions-a0012a11fdd3286908378ea9549ead1101caeed1":"2021-07-08T11:16:31.891Z","5.1.2":"2021-07-08T12:15:58.211Z","5.1.3-renovate-major-external-major-e7185ccb1e7575cd80f65f3eb6e3b7efcb5c1c46":"2021-07-08T12:23:30.214Z","5.1.3-remove-only-test-959046e280a15dafe5f99f50e85467cb5d92188b":"2021-07-08T14:45:36.302Z","5.1.3":"2021-07-08T14:52:28.141Z","5.1.4-renovate-major-external-major-186b7434bc914188bd34e610afcc4f77c83d8e3a":"2021-07-08T14:57:42.097Z","5.2.0-h32-safe-migrations-573d52b6eec9967ac27f382061acc1790d99a9fd":"2021-07-08T16:42:20.595Z","5.1.4-renovate-internal-patch-f1d7dc177a8ab4d229a2974c6b858d5233af18c0":"2021-07-09T02:31:36.281Z","5.1.4":"2021-07-09T02:38:53.314Z","5.1.5-renovate-internal-patch-e2f237fc8a997f9a89c6a23682240fc071baa136":"2021-07-10T00:39:02.613Z","5.1.5-renovate-external-non-major-ab4dc888d5e699257b02c17772ab6a63c37aca22":"2021-07-10T00:39:24.630Z","5.1.5-renovate-internal-patch-c70d55f6ffa2734b12902043bfa84ee66386de93":"2021-07-10T00:44:29.897Z","5.1.5":"2021-07-10T00:46:01.368Z","5.1.6-renovate-internal-patch-72f67b9cc9a8fa6fcd3f5c2b4331ea2be5cade53":"2021-07-10T00:50:25.927Z","5.1.6-renovate-internal-patch-428afd2e63016830858ffcdbab83f1854ba506d0":"2021-07-13T00:34:54.234Z","5.1.6":"2021-07-13T00:44:12.729Z","5.2.0-h32-safe-migrations-a66c6b27098fc38136bb1c4bc128fb0a3a1184f0":"2021-07-13T11:44:03.816Z","5.1.7-use-jellyfish-config-682aca56421d869284cf93c0b8fa793a14ef2309":"2021-07-14T07:38:08.962Z","5.1.7":"2021-07-14T07:45:19.827Z","5.1.8-ui-schema-tweak-d80a557dbf32281bfda8849c5e1be7f83bd99e2a":"2021-07-16T08:06:45.951Z","5.1.8":"2021-07-16T08:13:14.954Z","5.1.9-remove-whitesource-805fe59d8082209dfa2e29c0559b16652931702c":"2021-07-17T01:48:16.603Z","5.1.9":"2021-07-17T01:54:07.823Z","5.1.10-fix-indexdef-generation-cb5898dba25a1a5507312000a508f5cc46500bfe":"2021-07-19T06:23:56.946Z","5.1.10-fix-indexdef-generation-451fedf208a2a83b9c360b97296eb46b078492a0":"2021-07-20T05:16:43.908Z","5.1.10-fix-indexdef-generation-178cb0a6b9ac5aa45ae957881c85022635fa4c47":"2021-07-20T05:21:43.453Z","5.1.10-fix-indexdef-generation-3df48e9071291660c617ce556292cedb5dafd84c":"2021-07-20T05:26:11.821Z","5.1.10-h32-fix-json-patch-path-escaping-b25ff18d292d1ec67275e639eddf1c046728e3d2":"2021-07-21T07:15:49.463Z","5.1.10":"2021-07-21T07:21:09.489Z","5.1.11-fix-indexdef-generation-4349afd1c172b4e2661710f25b99abb8bf738d01":"2021-07-21T07:40:06.408Z","6.0.0-h32-simplify-patch-validation-e642b2abeac819453f3db06f9ba10d34ff70342d":"2021-07-21T13:05:13.430Z","5.1.11-repo-sync-jellyfish-config-default-6bf40d95b102c294a16a061974cc90d27f90c946":"2021-07-22T10:25:53.820Z","5.1.11":"2021-07-22T10:32:41.519Z","6.0.0-h32-simplify-patch-validation-5115f91ff00950394b152e0fec026597677d2e32":"2021-07-22T14:41:34.865Z","5.1.12-fix-indexdef-generation-2506c0fa43779c1b07252fa2849f11d37e197907":"2021-07-25T23:41:08.722Z","5.1.12-fix-indexdef-generation-d7f3ffcb4c7cd539e9c97ea4bc7e03c8c71a72a8":"2021-07-26T03:28:57.230Z","5.1.12-repo-sync-jellyfish-config-default-ff8567518fc7d9e27252703e83fb72a6da16bd72":"2021-07-31T01:38:16.297Z","5.1.12":"2021-08-01T12:17:40.607Z","5.1.13-fix-optional-link-nesting-103753962af165e98c2e38a1c1dd9d53d030ae4f":"2021-08-02T19:48:22.941Z","5.1.13-fix-optional-link-nesting-bcf131d2a29b1c5aa3c2edcdb195ec4435e9dab5":"2021-08-02T20:18:48.333Z","5.1.13-fix-optional-link-nesting-6c4e4daf948f8a9a0d73e63144c1b14cd649c494":"2021-08-02T20:32:41.805Z","5.1.13-h32-unify-script-names-9bac8f7924b5bbc12abd42dd1b976c3d8b126a93":"2021-08-04T09:47:34.190Z","5.1.13-h32-unify-script-names-9267983ad48dfaaf12c5cfe2336d4c9985bc208d":"2021-08-04T23:11:37.709Z","5.1.13":"2021-08-04T23:20:30.930Z","5.1.14-update-readme-command-2ec67d969e3127f8d6c1e2feab92c92daffaff31":"2021-08-05T02:18:43.825Z","5.1.14":"2021-08-05T02:26:38.339Z","5.1.13-fix-optional-link-nesting-5e847d6a627412da09406f5fa9ce77cc5cbe3a43":"2021-08-05T16:26:09.341Z","5.1.15-fix-indexdef-generation-01d7bb8a0782867cf0b91f7d2d4112491dc11ecf":"2021-08-05T23:53:04.104Z","5.1.15-compose-8d45a918effd7a88023e4a0a0c2ecba1c685fd9a":"2021-08-06T00:03:47.219Z","5.1.15":"2021-08-06T00:18:37.337Z","5.1.15-fix-indexdef-generation-c19b6f760b88043a548d5be338700af986fe9d84":"2021-08-06T01:02:53.018Z","5.1.15-fix-indexdef-generation-2ffe4ab865ba01f72118b8d8a160d6d46cbcb415":"2021-08-07T10:48:04.399Z","5.1.16-fix-indexdef-generation-4ca7ce4ad09eac90f32bba5b977d7993ed256002":"2021-08-07T13:27:37.891Z","5.1.16-h32-type-mergeable-9c831b4596d3e597c99d509a21a1ffc67f0b13dd":"2021-08-09T15:17:40.036Z","5.1.16-fix-indexdef-generation-90a6e31e127d823edc90930297fec9888f357671":"2021-08-10T03:28:59.400Z","5.1.16":"2021-08-10T10:45:15.098Z","5.2.0-add-scheduled-action-ef970ac469bdfdc39841d1bf4d6ebfdd1b05e395":"2021-08-10T14:05:52.824Z","5.1.17-fix-indexdef-generation-27d3b939390a44c59cc101fbbdd96d8867046973":"2021-08-10T22:57:55.888Z","5.2.0-add-scheduled-action-a4536f1f7e5ef18e77827564308dfd99884dcc61":"2021-08-11T01:19:14.070Z","5.2.0-add-scheduled-action-b70346d69800063ef1439b2c2f59acbb68919435":"2021-08-11T02:10:58.351Z","5.2.0-add-scheduled-action-5f5457dd622ac01b4a3e0261bb971471b6f6cc43":"2021-08-11T02:48:54.807Z","5.1.17-fix-indexdef-generation-55315978d1a83b6e1e397f7080d93685d6bd1473":"2021-08-12T00:19:40.156Z","5.1.17-fix-indexdef-generation-db5718907eb0a890fbc2cbc9a2deeb4c611ad2fb":"2021-08-12T01:41:03.192Z","5.1.17-fix-indexdef-generation-1b65cba711ec3119cda9d2cff373a5bab29c3f62":"2021-08-12T01:45:45.405Z","5.1.17-fix-indexdef-generation-fbf98a25e5df2074c35709e9f8f3957370a1679a":"2021-08-12T01:53:42.221Z","5.1.17-fix-duplicate-views-34f4da5d5ede3296b079a57f1264d86e8af878f1":"2021-08-12T14:32:24.865Z","5.1.17":"2021-08-12T14:50:08.065Z","5.1.18-fix-optional-link-nesting-cdc30113f390449e40dbab1c95c9c3a0a06fa4ed":"2021-08-12T14:56:55.076Z","5.1.18":"2021-08-12T15:03:54.990Z","5.1.19-fix-indexdef-generation-28ed699cf27982b7c3230d52fffc92a03d314141":"2021-08-17T22:34:48.664Z","5.1.19":"2021-08-17T23:25:18.980Z","5.1.20-assume-not-array-007be281bfaf816dfdc61a2cb64e4a539ec32446":"2021-08-20T09:10:03.823Z","5.1.20":"2021-08-20T09:18:25.104Z","5.2.0-add-scheduled-action-cdd89093671ab828409a505fb83041781603957b":"2021-08-25T04:29:25.609Z","5.2.0-add-scheduled-action-1d53957f57ea7f7f219489b84d455ebeb3f6575c":"2021-08-25T04:39:17.964Z","5.2.0-add-scheduled-action-6d1816189a0883b59e876fa6af0077571bc267d0":"2021-08-25T08:21:41.669Z","5.1.21-update-renovate-config-368a330acda79579743766fa419141d64cd551b7":"2021-08-25T08:25:00.742Z","5.1.21":"2021-08-25T08:32:30.064Z","5.1.22-renovate-internal-patch-6a57faad01385daf3f3246c5850d4de3393d96bf":"2021-08-25T08:36:44.886Z","5.1.22-renovate-balena-jellyfish-environment-4-x-bd0059c47172aaddbcb5b59b52cb8b0ce0dafc31":"2021-08-25T08:37:28.234Z","5.1.22-renovate-major-external-major-6c2b6b9d8bdb7e68d9a31be8f6593432b9d32593":"2021-08-25T08:38:48.437Z","5.1.22-renovate-balena-jellyfish-environment-5-x-019eadb1fd9380e00ddc923e1979008bde051897":"2021-08-25T08:39:50.733Z","5.1.22":"2021-08-25T08:50:01.109Z","5.1.23-renovate-balena-jellyfish-types-0-x-916fb21f9c1e0819f2aed04f16344593b1c35dfd":"2021-08-25T08:54:32.362Z","5.1.23-renovate-major-external-major-25c69ba5b336859915d9195245cba5f57a10efc9":"2021-08-25T08:56:44.257Z","5.1.23-renovate-balena-jellyfish-environment-5-x-7ca6d347fe45763e6a4de6c6dd1cdde99ca77902":"2021-08-25T08:57:13.651Z","5.1.23":"2021-08-25T09:28:33.629Z","5.1.24-renovate-major-external-major-95b6cfcaef5536d3b17accb39b0817c4b6e5c5b4":"2021-08-25T09:32:32.583Z","5.1.24-renovate-balena-jellyfish-types-0-x-fbb0f5e95ac4ae8ded7f7bbf639e47a9d756daf8":"2021-08-25T09:34:40.281Z","5.1.24":"2021-08-25T11:20:47.181Z","5.1.25-renovate-major-external-major-aa77e9acb8e3de2e8772c6be7d2ac48a2485db5f":"2021-08-25T11:25:54.226Z","5.1.25-add-json-schema-types-75685f79a1bf846fad631fa1e1dad68272f25fae":"2021-08-26T00:43:37.600Z","5.1.25":"2021-08-26T00:50:59.883Z","5.1.26-renovate-internal-patch-a35686e3e231fb3044e7be4aa56f628920e60ac6":"2021-08-26T00:55:03.523Z","5.1.26-renovate-major-external-major-d837f0c5714c8d22dfd381e70d38d4d8093444ec":"2021-08-26T00:56:54.654Z","5.2.0-add-scheduled-action-725bb992508038e16e6f1363d0d9aaf29a3cd3dd":"2021-08-26T09:35:26.006Z","5.1.26-renovate-external-non-major-fa4a4033ac127b4ce329df9c63b269b9c11f1142":"2021-08-29T02:06:58.309Z","5.1.26":"2021-08-29T02:14:30.362Z","5.1.27-renovate-internal-patch-e15b6d0a90bba5c16298c9a36d8a486e418bd2d2":"2021-08-29T08:00:53.128Z","5.1.27":"2021-08-29T08:08:25.627Z","5.2.0-add-link-indexes-626d389592d11a0b3b16be7023f9b3228c5f3e26":"2021-08-29T08:09:05.101Z","5.1.27-bump-jest-6098c74a2fdcf09b92ca3808fcdd6f3d03c4a069":"2021-08-29T08:15:42.833Z","5.2.0-add-link-indexes-8b04eabe901a10773443a4a10dc3c1088e780cd8":"2021-08-29T08:47:42.592Z","5.2.0":"2021-08-29T08:56:11.364Z","5.3.0-add-scheduled-action-75fdf9c69b43d4354ca37e2f9c58cee917a15f93":"2021-08-31T00:51:54.435Z","5.3.0-links2-table-1ff1b1d9ca1e6d71b6d381b8351754cc47b56659":"2021-08-31T12:25:10.756Z","5.3.0":"2021-08-31T15:49:18.570Z","5.3.1-renovate-major-external-major-ac61b1b728708193e62eb83bc2d5d2b5835aea9d":"2021-08-31T15:56:07.565Z","5.3.1-repo-sync-secrets-default-f4056fbb04c50da767b9b7602249992590428c52":"2021-09-01T00:55:22.595Z","5.3.1":"2021-09-01T01:03:20.107Z","5.3.2-renovate-internal-patch-c869bb211c9c7b0e7dda1d24d51278fd12307f73":"2021-09-01T01:08:17.513Z","5.3.2-renovate-major-external-major-0f064bcd2caf9acd4cd6fbdbf67d0938f133aa24":"2021-09-01T01:09:33.820Z","5.3.2":"2021-09-01T01:17:22.170Z","5.3.3-renovate-major-external-major-9f1780b2360e3de2ed81f8b15dd49de2144b17ec":"2021-09-01T01:20:42.382Z","5.4.0-add-scheduled-action-4b12a4956e38e4e7e41d141c561ed976af9625dd":"2021-09-01T07:41:07.422Z","5.4.0-lucianbuzzo-slugify-15ff9669857c3f72127cb29fc2046a3479bf40cf":"2021-09-01T10:15:41.691Z","5.4.0-lucianbuzzo-slugify-63624d8f36b0d8be86ecaa216b2b0bb7291dc5fb":"2021-09-01T11:06:37.077Z","5.4.0-lucianbuzzo-slugify-b77542121da8e4587c780a011e5188e80841fb31":"2021-09-01T12:37:06.021Z","5.4.0-use-links2-5a3fafc47ae132c87619f853579a7125d66606ae":"2021-09-01T13:08:23.202Z","5.4.0-lucianbuzzo-slugify-8b5f51366d22ffeb19004b158dffdbca132625c9":"2021-09-01T17:03:33.940Z","5.3.3-renovate-balena-jellyfish-types-1-x-0b0f5534b7ccf06cf929ab28a2897b8f8f0143ff":"2021-09-02T01:24:39.711Z","5.4.0":"2021-09-02T08:59:00.916Z","5.4.1-renovate-balena-jellyfish-types-1-x-ceaeeb3829c37ebd51fdad5100ab77730645104c":"2021-09-02T09:03:02.389Z","5.4.1-renovate-major-external-major-58f3da8a1a594fe2494e6cddce10a6b40b78b2f5":"2021-09-02T09:03:33.576Z","5.4.1-lucianbuzzo-simple-slugify-eeaec8e71801ea3f4fbe2d4536d5e86f847ad0be":"2021-09-02T15:10:21.128Z","5.4.1-lucianbuzzo-simple-slugify-20df3445016e30d175614c287f13c2b76ffb9fd3":"2021-09-02T15:17:35.785Z","5.4.1":"2021-09-02T15:33:17.727Z","5.4.2-renovate-major-external-major-372ad49960e906542b5a3dcc056385e4fc481617":"2021-09-02T15:37:06.308Z","5.4.2-renovate-balena-jellyfish-types-1-x-a59482c97c37164025bfa7b85bbcb9ff17023df5":"2021-09-02T15:37:31.535Z","5.4.2":"2021-09-03T00:58:19.283Z","5.4.3-renovate-major-external-major-ac0c0c5000e9f998bd68157ad089e802d2819de7":"2021-09-03T01:01:21.328Z","5.5.0-add-scheduled-action-68d7aeb5bbccf4138e5087e01d93ff3cf51261e2":"2021-09-03T01:06:36.163Z","5.5.0-add-scheduled-action-21b1e191718723cab51248a3cc7d0aaee2df7599":"2021-09-03T01:40:05.082Z","5.5.0-lucianbuzzo-s-add-error-contract-194bcf8d5ee05222dd6accb3355db4c8667c417e":"2021-09-03T12:40:36.148Z","5.5.0-lucianbuzzo-s-add-error-contract-25ff9d4d7dae2bd13cb1a69a9d4d6e8db99cd310":"2021-09-03T13:49:29.249Z","5.5.0":"2021-09-03T14:30:18.752Z","5.5.1-renovate-major-external-major-c88c7ecdd8152c7a8ef41fc8f6535f1ab9fbca27":"2021-09-03T14:35:58.491Z","5.5.1-drop-link2-on-reset-5147b4d9e89e7a6128160a7ffbd90e4fb4a5d8a1":"2021-09-03T15:04:34.546Z","5.5.1":"2021-09-03T15:11:33.840Z","5.5.2-renovate-major-external-major-cb515dd31a309a3501f48e9fd5c930d387e11fb5":"2021-09-03T15:17:34.808Z","5.6.0-add-scheduled-action-8ca6219750c01dedf2f781626060de2d4f034f05":"2021-09-05T04:39:50.943Z","5.6.0-add-scheduled-action-ba3cfa59559da8c5bf99b4d230fc246a2a1e1dbd":"2021-09-05T08:30:37.314Z","5.6.0-add-scheduled-action-fcad7f1b6f404601c70710e04d45a2222b4bfcc6":"2021-09-05T08:45:45.100Z","5.6.0":"2021-09-05T08:57:18.805Z","5.6.1-renovate-external-non-major-d704c2b5af654392f1b38c88412ec6df40b9ca86":"2021-09-05T09:04:02.435Z","5.6.1-renovate-major-external-major-1ff2e9acfcf85e0b75da50d848cd5779464e9dfe":"2021-09-05T09:04:49.263Z","5.6.1-renovate-balena-jellyfish-types-1-x-65b7212985e8d0e8e2fb54deb6fcb11dabb56f63":"2021-09-05T09:08:18.717Z","5.6.1":"2021-09-05T09:12:14.557Z","5.6.2-renovate-major-external-major-576784d8e8fca0370716746fac209c5f69bb30ef":"2021-09-05T09:16:10.689Z","5.6.2-renovate-balena-jellyfish-types-1-x-bd236162b9fe7b03a563149041dcfad04977463e":"2021-09-05T09:18:45.886Z","5.6.2":"2021-09-05T09:26:27.310Z","5.6.3-renovate-internal-patch-804c305874383f46021282ed53c1a536390db3b9":"2021-09-05T09:30:56.426Z","5.6.3-renovate-major-external-major-6f1056035b67f32f9d616b676813add893209091":"2021-09-05T09:33:56.835Z","5.6.3":"2021-09-05T09:39:34.409Z","5.6.4-renovate-major-external-major-7c17dce29fa933d9ac6715c07578a252517a2df4":"2021-09-05T09:43:02.403Z","5.7.0-use-links2-01961cf33f80efb468b34fd59c62cf99d9b24582":"2021-09-06T15:55:05.837Z","5.7.0":"2021-09-06T16:03:40.691Z","5.6.4-lucianbuzzo-query-tests-dce31a8f19262c1585396a407c79d11f84a4aa94":"2021-09-06T16:39:18.968Z","5.7.1-lucianbuzzo-query-tests-bb793d5f85912fe4ac40b65092f16987b09b2a04":"2021-09-06T17:21:00.884Z","5.7.1":"2021-09-06T17:28:12.632Z","5.7.2-renovate-major-external-major-554cdc4a7ce74b24e3d968aa41c9ed35182155ef":"2021-09-06T17:35:14.924Z","5.7.2-dont-use-links-521ea2f80c70b86971e855647d4be91c6d15c55c":"2021-09-06T21:28:55.424Z","5.7.2":"2021-09-06T21:36:24.504Z","5.7.3-renovate-major-external-major-141d9813a083891d83f3d506eb1a9cac25a0e204":"2021-09-06T21:42:20.441Z","5.7.3-lucianbuzzo-insertcard-types-9b91bae4f9eeca814d70ae130fb470c81b8279de":"2021-09-07T09:05:15.191Z","5.7.3":"2021-09-07T09:16:42.314Z","5.7.4-renovate-major-external-major-f0121da28623c772580d15b0bd7f0b54e3432333":"2021-09-07T09:21:13.058Z","5.7.4-renovate-major-external-major-fcb0e9d614f71faa8c83a6cbf39b0192f5782a16":"2021-09-08T10:49:33.746Z","5.7.4-use-links2-9242752a13ba7c8258862382659298fad271b7be":"2021-09-09T11:58:59.878Z","5.7.4":"2021-09-09T12:05:22.975Z","5.7.5-renovate-major-external-major-50a3e5535fe3cc8aab9f2a410e520182a4c45d97":"2021-09-09T12:11:35.464Z","6.0.0-drop-links-77f0c485f968775790a1f7b0499992bc6095cf67":"2021-09-10T12:03:07.690Z","5.7.5-renovate-external-non-major-40c0a304c48879d0041ee67207080d4920347b86":"2021-09-11T01:32:19.394Z","5.7.5-renovate-external-non-major-e3979c7fb6a05ae35500cde3eaf716df9bb8edd2":"2021-09-11T02:59:11.301Z","5.7.5-renovate-internal-patch-a5c16aa557efacdac672dcfa60d05f7fd80b6769":"2021-09-11T08:13:06.694Z","5.7.5":"2021-09-11T08:20:14.558Z","5.7.6-renovate-major-external-major-b532040a34e72f23a870e929e6ec25dd8e4307af":"2021-09-11T08:24:47.717Z","6.0.0-drop-links-636055b34c7dd7169f3d7f0e567e4a08c1fdc267":"2021-09-11T09:48:01.667Z","5.7.6-renovate-external-non-major-595e1c47d598b4dfbc0ff6c48b56161e11f85046":"2021-09-12T03:21:39.947Z","5.7.6-renovate-internal-patch-599d374604464d18708a5c39d6cceb44041404a0":"2021-09-12T06:21:43.582Z","5.7.6":"2021-09-12T06:28:27.368Z","5.7.7-renovate-external-non-major-0613d1ff50557548a52992261cd8504bf05a771a":"2021-09-12T06:33:32.909Z","5.7.7-renovate-major-external-major-65fa4a42a517bc50861541af38184822c6c45fbf":"2021-09-12T06:34:52.179Z","5.7.7-renovate-major-external-major-ecfb82343d55b0ec747bf9d9697012b945846c5c":"2021-09-13T09:33:10.167Z","5.8.0-h32-safe-migrations-7aad63183259042f41abf163e0e692dcd4d6957f":"2021-09-13T15:40:02.445Z","5.8.0-drop-cards-markers-ee4e493e3362c8d20c8ffed46555a4db5aafbeb0":"2021-09-13T17:26:31.573Z","5.8.0":"2021-09-13T17:34:32.968Z","5.8.1-renovate-external-non-major-af0ff79581c4aa2b2554b1cc4dc2049abf7d52e4":"2021-09-13T17:38:46.471Z","5.8.1-renovate-major-external-major-2771ee3190a6cc8818da0579b39ac715ae769092":"2021-09-13T17:40:24.543Z","5.8.1-remove-stale-comment-56a3c18f7a2b535519425792b2cdfc11b2555e35":"2021-09-13T17:43:45.007Z","5.8.1":"2021-09-13T18:16:17.667Z","5.8.2-renovate-external-non-major-2b01eb6cf3f24b225fd2cbd4d0a35998bb60177c":"2021-09-13T18:22:07.746Z","5.9.0-h32-safe-migrations-4ddc017cc4eab1472501db6ed7a646f6d95aa53d":"2021-09-13T18:49:16.857Z","6.0.0-drop-links-5b6a8b30b9ee31aa9c3b7833dd9592c443a53025":"2021-09-13T19:09:40.675Z","6.0.0":"2021-09-13T19:18:27.002Z","6.0.1-renovate-major-external-major-a384c621ed719b0e95647a047363d9dd236f0754":"2021-09-13T19:24:55.132Z","6.1.0-h32-safe-migrations-779957b6fd85ceab37d0080c45c9fc3c5f320563":"2021-09-14T06:48:35.524Z","6.1.0":"2021-09-14T06:56:41.433Z","6.1.1-h32-fix-empty-package-09edc4ceba22b0ad7c2aca0f577b58e85c1f47d6":"2021-09-14T08:02:09.631Z","6.1.1":"2021-09-14T08:14:59.991Z","6.1.2-renovate-external-non-major-fa3e6362ec56ebf9d440861cb85fa84064c5df99":"2021-09-14T08:20:46.931Z","6.1.2-renovate-major-external-major-e5d6c49f1ae0e85df2b197c20436fc3c9c2932f5":"2021-09-14T08:21:27.021Z","6.1.2-h32-fix-conc-db-setup-34a0075ca0576df613b2af9c4d76d34b4e7a7659":"2021-09-14T15:43:05.544Z","6.1.2":"2021-09-14T15:50:28.398Z","6.1.3-renovate-external-non-major-fd562c5d89516534476b666aa96ac5ff2925ec57":"2021-09-14T15:56:48.673Z","6.1.3-renovate-major-external-major-67e786f72254467bacefabc70afcb7c6619bcd9d":"2021-09-14T15:57:39.605Z","6.1.3-repo-sync-secrets-default-b86c63c60184559e1991dd7e687eaf51cae5c025":"2021-09-15T06:17:20.177Z","6.1.3":"2021-09-15T06:24:49.578Z","6.1.4-lucianbuzzo-simultaneous-bootstrap-d56a042a939264d2993968b9d66fecff0429e82b":"2021-09-15T15:37:14.291Z","6.1.4-lucianbuzzo-simultaneous-bootstrap-84a4f67fb60896a78365f6027d8baa4dc39446e6":"2021-09-15T16:32:40.657Z","6.1.4-lucianbuzzo-simultaneous-bootstrap-d8170d23fe4e19eeb94530618d319849fd7da586":"2021-09-15T19:47:43.727Z","6.1.4":"2021-09-15T20:07:34.296Z","6.1.5-renovate-major-external-major-bbfa3ca5022244c7e75fd34c37d61da8dc9cc30d":"2021-09-15T20:13:46.844Z","6.1.5-lucianbuzzo-simultaneous-index-creation-48400d6992e96e9146e1d9e2cfa6e57045c0252d":"2021-09-16T10:52:17.799Z","6.1.5":"2021-09-16T11:22:11.174Z","6.1.6-renovate-major-external-major-bd5fa26ab656f229dc5e091b95e0790a0ecbf45d":"2021-09-16T11:29:09.363Z","6.1.6-renovate-external-non-major-bcce371c7194c852c68dd3de3d7208c9360cdf63":"2021-09-16T11:29:44.533Z","6.1.6-renovate-external-non-major-8c03d2e0dd440c615e3c8e4c9d57f500d54ee527":"2021-09-17T07:14:52.042Z","6.1.6-lucianbuzzo-increase-index-retries-77fc6d802dbab560913753b4181b2e87c992e03c":"2021-09-17T07:21:51.123Z","6.1.6":"2021-09-17T07:23:16.458Z","6.1.7-renovate-major-external-major-a33c2064b2904fcc1268c245619717d273a00d1d":"2021-09-17T07:24:58.591Z","6.1.7-h32-stream-card-type-603da463c08b954c2398a852689f098a5c4c6574":"2021-09-17T13:44:45.365Z","6.1.7-h32-stream-card-type-cff4d771a5c34a6ad7f1f859215d1ccfef7dc4e0":"2021-09-17T13:51:37.921Z","6.1.7-h32-stream-card-type-06550ff5200ca17a1429e6c4a0e5b8b23d4169bc":"2021-09-17T14:41:25.955Z","6.1.7-renovate-internal-patch-f468b9ec2d05a37f2b3faad357ec37bba68827f0":"2021-09-18T02:43:24.667Z","6.1.7":"2021-09-18T02:51:03.867Z","6.1.8-renovate-major-external-major-b7a150a5498f61aeb3d59c2f1d711543bca03f91":"2021-09-18T02:58:24.453Z","6.1.8-renovate-external-non-major-117e732d72057797b65ea07aefee510c3d6aaddf":"2021-09-18T20:00:58.258Z","6.1.8-renovate-internal-patch-fe4625dcdc421894e3151b14456fedbeeed1f31f":"2021-09-18T20:06:31.819Z","6.1.8":"2021-09-18T20:08:02.013Z","6.1.9-renovate-internal-patch-3d50d65107132f6a93f9604c9c8535cdb53112b9":"2021-09-18T20:12:09.682Z","6.1.9-renovate-major-external-major-accc2ee86a63df3fe3084675a8cc97db17581dcf":"2021-09-18T20:12:47.954Z","6.1.9":"2021-09-18T20:22:54.055Z","6.1.10-renovate-major-external-major-96027676d921ac9617a108d26aebbc2b5193cd76":"2021-09-18T20:26:00.720Z","6.1.10-h32-stream-card-type-522c11db8664d8c7d8eff0534eb0f1d8fb2fc36f":"2021-09-20T10:28:33.702Z","6.1.10":"2021-09-20T10:37:18.328Z","6.1.11-renovate-balena-jellyfish-types-1-x-5a284fb172f30d22a5e5735232333907a5dc8d60":"2021-09-20T10:44:06.162Z","6.1.11":"2021-09-20T10:51:22.067Z","6.1.12-renovate-major-external-major-26eeff5087fa4b3fd86889c7ccc52981d0895cf9":"2021-09-20T10:56:04.858Z","6.1.12-renovate-major-external-major-e94574af0fd07bb947c7158ca2a33114ec4648e3":"2021-09-20T14:59:41.430Z","6.1.12-add-json-schema-group-4e67bd200e2bfb776a17ab58087e0a75fcce8a34":"2021-09-21T03:01:26.954Z","6.1.12":"2021-09-21T03:08:13.345Z","6.1.13-bump-fast-json-patch-144facf2149c899c6f5bea70810892adb2797b1f":"2021-09-21T03:15:28.576Z","6.1.13":"2021-09-21T03:21:35.334Z","6.1.14-renovate-major-external-major-304ce33a4df607d73130ff9464c1ab92ad5af2b1":"2021-09-21T08:32:01.785Z","6.1.14-renovate-major-json-schema-org-378f92aa259c55df3c1b5ecb53f460c6ae2b6a7f":"2021-09-25T00:16:17.489Z","6.1.14-renovate-internal-patch-d6802c05324610aff07c05ddc655790e9436206f":"2021-09-25T04:09:21.143Z","6.1.14":"2021-09-25T04:17:39.535Z","6.1.15-renovate-major-json-schema-org-fbb1fb9628c74a2cdca6d4d6d43a18f0ad9b6255":"2021-09-25T04:21:30.203Z","6.1.15-renovate-major-external-major-121592ab0b6523265bd4c1318e57e0f44bd4451c":"2021-09-25T04:22:16.160Z","6.1.15-repo-sync-secrets-default-918e83d36ce7c7a08d186ec0d8a9b17ed578e8b8":"2021-09-25T10:41:32.136Z","6.1.15-repo-sync-secrets-default-88b7b1575d1d62f1444e18fb54fdbb30669d367f":"2021-09-25T12:28:05.042Z","6.1.15":"2021-09-25T12:39:06.871Z","6.1.16-renovate-major-external-major-f87852f3590a4c336f80b0a3b83ff428c56dfa1a":"2021-09-25T12:47:04.300Z","6.1.16-renovate-major-json-schema-org-c9157152147a4e7ab0e099d81a87997c5c9c807a":"2021-09-25T12:47:26.373Z","6.1.16-renovate-major-external-major-f62131509c24986174e1df142d693ddffb814350":"2021-09-25T14:19:22.371Z","6.1.16-renovate-internal-patch-a6c32593a42efb0c62acadaddd87ff0a6ab32428":"2021-09-25T16:36:11.898Z","6.1.16":"2021-09-25T16:43:09.743Z","6.1.17-renovate-major-json-schema-org-8949ebcf111022a71b16be0f62e57285a9defca6":"2021-09-25T16:47:47.014Z","6.1.17-renovate-major-external-major-0a931ff972c7830ef713014828dd9b13dcef9b9f":"2021-09-25T16:50:20.842Z","6.1.17-renovate-major-external-major-921d7b805ed0dddf2091071050ce7f88aba59236":"2021-09-28T11:25:09.217Z","6.1.17-renovate-major-external-major-cac8dadaf5b67695d483aa0607d98e150293edbb":"2021-09-29T15:57:19.098Z","6.1.17-renovate-internal-patch-e86dcf8bd7eb82d6e73fd10fc0083a46e7fb557a":"2021-10-02T01:16:47.570Z","6.1.17":"2021-10-02T01:24:29.247Z","6.1.18-renovate-major-json-schema-org-17ba4abfea32b39a4bf4524669122c1bba7255b9":"2021-10-02T01:28:42.521Z","6.1.18-renovate-major-external-major-08d76afbd659928e98df7e14276292190a95afcc":"2021-10-02T01:28:59.850Z","6.1.18-renovate-external-non-major-7176e2e8e008c5c5d87581d330771ce54c5903cb":"2021-10-02T18:31:29.282Z","6.1.18":"2021-10-02T18:39:11.282Z","6.1.19-renovate-major-json-schema-org-3c15f7055daa1b8f2f424af64acc0028e0afa5df":"2021-10-02T18:43:06.057Z","6.1.19-renovate-major-external-major-9989d67bd42f1dae26b959376a337d587dc9b717":"2021-10-02T18:43:38.046Z","6.1.19-renovate-internal-patch-377dbe3c597ce2fbff1737b42fd11f52f2492394":"2021-10-02T19:51:25.755Z","6.1.19":"2021-10-02T19:58:02.064Z","6.1.20-renovate-major-json-schema-org-11cfdee12df7d06959a8e673788a7fa37003c734":"2021-10-02T20:02:38.953Z","6.1.20-repo-sync-jellyfish-config-default-9c2125a605d9529eb1015ad7d5c41a62bc2b5806":"2021-10-03T09:10:55.584Z","6.1.20-renovate-balena-jellyfish-config-1-x-c415c493035c4a965d38c181ca45acba7a90a99d":"2021-10-03T10:39:07.928Z","6.1.20":"2021-10-03T10:45:36.448Z","6.1.21-renovate-major-json-schema-org-69f9452f5318630d889bebcc24f03fac9d0a0f0d":"2021-10-03T10:50:11.959Z","6.1.21-renovate-major-external-major-de5e65b0e2cf495faa036363f6d9a4007e95615a":"2021-10-03T10:53:48.679Z","6.1.21-repo-sync-jellyfish-config-default-9f536a4fcd04c20d549192daefd69370422efe90":"2021-10-04T03:31:50.898Z","6.1.21-renovate-internal-patch-c1b628b73eaa77af62556be2985be7687e7c4bb6":"2021-10-04T10:45:59.860Z","6.1.21":"2021-10-04T10:54:31.396Z","6.1.22-renovate-major-external-major-b8d687f99fab87c733a271cf45615834a15c4f34":"2021-10-04T10:58:25.281Z","6.1.22-renovate-major-json-schema-org-5cd38350d322bd683ac1f02055513ef02c373477":"2021-10-04T11:02:16.820Z","6.1.22-renovate-internal-patch-fbf9ef289abe7c1a7a9079263d4767e425684845":"2021-10-04T11:08:00.900Z","6.1.22":"2021-10-04T11:18:18.538Z","6.1.23-renovate-major-json-schema-org-753391df4ed0c98622a405824a6f92dd95fae331":"2021-10-04T11:22:57.043Z","6.1.23-renovate-major-external-major-4dd26a894bfcf98086a9929aa181a54e2fd2848d":"2021-10-04T11:25:26.896Z","6.1.23-repo-sync-jellyfish-config-default-dad33beb2affb08ec3cb58075e73f2c61f2b316d":"2021-10-05T00:58:47.642Z","6.1.23":"2021-10-05T01:09:49.315Z","6.1.24-renovate-major-json-schema-org-13485bc7fe2db387abae11a41b47566f11bfcc8c":"2021-10-05T09:34:57.921Z","6.1.24-h32-fix-parent-merged-2e83059e9f6cc8e014b9bf7884b1b35fa7db6a2a":"2021-10-05T11:00:53.000Z","6.1.24":"2021-10-05T11:08:03.035Z","6.1.25-renovate-internal-patch-714c86fae349f5dc3bae930dd12974c893b7a1ce":"2021-10-05T11:15:14.610Z","6.1.25-renovate-major-external-major-5e6baae555d405d70454e3a566be06a7bcf6e792":"2021-10-05T11:15:36.573Z","6.1.25":"2021-10-05T11:23:01.450Z","6.1.26-renovate-major-json-schema-org-1a6898e63c531c8fb5cc78a31f58f413303143e2":"2021-10-05T11:27:12.845Z","6.1.26-renovate-major-external-major-b7739c3246b9b13dc76d55b0aa105fcbfb8b1224":"2021-10-05T11:27:28.306Z","6.2.0-h32-updated-at-ac535ae43f90b2994caa507f7883e9f97e5cbb9f":"2021-10-05T12:38:49.367Z","6.2.0":"2021-10-05T13:30:49.724Z","6.2.1-renovate-major-json-schema-org-ace3e8bd2c4c43a606505b8a4981ae632a8a289a":"2021-10-05T13:42:34.387Z","6.2.1-renovate-major-external-major-b21946c42e5c2d851229a41682a641cdc56d0aac":"2021-10-05T13:44:02.230Z","6.2.1-renovate-major-external-major-d17b261a9965b7712ded91ad0766cc15d512b476":"2021-10-08T15:10:45.386Z","6.2.1-renovate-external-non-major-323cf1210bb798a6c12360c3d57e2110e2b870f2":"2021-10-09T01:32:08.617Z","6.2.1-renovate-internal-patch-4164f5cda23e668df039ec0101f3542210168773":"2021-10-09T01:36:59.748Z","6.2.1":"2021-10-09T01:39:01.408Z","6.2.2-renovate-internal-patch-7f22ddb90cd5cf85c52a1384dcdc5571c5bc9f17":"2021-10-09T01:42:58.185Z","6.2.2-renovate-major-json-schema-org-63276220494ff19b66c9c721c6393567c63f0544":"2021-10-09T01:46:30.538Z","6.2.2-renovate-major-external-major-cf4f86366c6389de86f8f5300735af607e04ffd1":"2021-10-09T01:47:19.033Z","6.2.2":"2021-10-09T01:53:40.817Z","6.2.3-renovate-major-json-schema-org-f645b9dd3dd723a34d2d669ca626dade83aa218a":"2021-10-09T01:57:25.654Z","6.2.3-renovate-major-external-major-38133380abbadc281f5163b53eb523388dd80e43":"2021-10-09T01:58:01.593Z","6.2.3-renovate-internal-patch-6f8a74a5e5a9a81f49f3069131bb6dd249eb56bf":"2021-10-09T04:28:26.556Z","6.2.3":"2021-10-09T04:36:43.275Z","6.2.4-renovate-external-non-major-84f707f1f658b1485dc4467586b53dcd6b878459":"2021-10-09T04:41:19.172Z","6.2.4-renovate-major-json-schema-org-dca8290f8c15b97d801bc2f7e4982d79f84faaeb":"2021-10-09T04:42:33.367Z","6.2.4":"2021-10-09T07:22:09.000Z","6.2.5-renovate-major-json-schema-org-4c2aea8ae48cd36bd2e77e7fa3c2cd1c657865ff":"2021-10-09T07:27:50.709Z","6.2.5-renovate-major-external-major-54d191510bb05c8ed463636b14d2d79b3059dea9":"2021-10-09T07:28:17.135Z","6.2.5-renovate-external-non-major-be946ab66e073538bf41442bb0b004a97693be0b":"2021-10-09T19:30:23.286Z","6.2.5":"2021-10-09T19:37:12.841Z","6.2.6-renovate-major-external-major-d3bf343fdbf620bb774e7deb7f004dc6344c29e7":"2021-10-09T19:42:35.640Z","6.2.6-renovate-major-json-schema-org-33bf8cdb085e1e53422cd2c7ea4866398d504c76":"2021-10-09T19:44:35.117Z","6.2.6-renovate-internal-patch-cbab302fde0aec49f02f199561ac85acaaa383fc":"2021-10-09T21:53:02.678Z","6.2.6":"2021-10-09T21:59:37.141Z","6.2.7-renovate-major-external-major-3b22e9a157273e74de82d696cef02f4e476aeeb3":"2021-10-09T22:04:07.019Z","6.2.7-renovate-internal-patch-3b4f79ce6cb499d7d39cd3f19647ac6e28feea1b":"2021-10-10T19:33:29.081Z","6.2.7-renovate-external-non-major-b29cd5950e0ae54db75357870c001b6a6bdd35cc":"2021-10-10T19:34:12.989Z","6.2.7":"2021-10-10T19:40:09.591Z","6.2.8-renovate-major-json-schema-org-ab9a12f265410c18b5a2cabe7cb2c032f3c48f48":"2021-10-10T19:44:38.029Z","6.2.8-renovate-major-external-major-7e2a469a559f5f318ae31cc994c0099b806f934a":"2021-10-10T19:45:43.414Z","6.2.8-renovate-external-non-major-4dce561e065ada8ca9c12e595a721189cb6e1b8e":"2021-10-10T19:46:57.783Z","6.2.8":"2021-10-10T19:55:33.492Z","6.2.9-renovate-major-external-major-3f41b72bb34ddd3f3f03f36beb86dbae799dca31":"2021-10-10T19:59:41.892Z","6.2.9-renovate-major-json-schema-org-20c63cc38b7d1d9df66c2cafc5aff20677d91fce":"2021-10-10T20:02:58.561Z","6.2.9-renovate-major-external-major-521bb9fc5305cc44be79414f2007066e61b8c7ce":"2021-10-14T18:50:01.758Z","6.2.9-renovate-external-non-major-f5e0e26161ba8ceeff2e56def9bc7c999adaa4bb":"2021-10-16T00:51:58.309Z","6.2.9-renovate-external-non-major-990ba3afd9f8048c348a02b4b18fae8f4bad5c2d":"2021-10-16T02:40:29.227Z","6.2.9-renovate-major-external-major-ad806310bd4a9ca5bfaec783ae27844101fb1272":"2021-10-16T10:25:08.679Z","6.2.9-renovate-internal-patch-d05fb5ffd71764a74b12f95401f680c61e76c726":"2021-10-16T11:55:00.304Z","6.2.9-renovate-major-external-major-14df77c3bf4a176c47a3a8c0a0944b9a0afb427a":"2021-10-17T23:51:15.323Z","6.2.9-repo-sync-secrets-default-dd19973c4ec3c7f3028ecedec84c99de8a00f8a0":"2021-10-18T02:01:53.974Z","6.2.9":"2021-10-18T02:13:57.323Z","6.2.10-renovate-internal-patch-c0a49147f2d80fba51ea24a0de6867a618194f40":"2021-10-18T02:43:08.451Z","6.2.10-renovate-major-external-major-f3c943f15e88ea99656c3cd724d4660cea21b633":"2021-10-18T02:44:12.640Z","6.2.10":"2021-10-18T02:50:45.447Z","6.2.11-renovate-external-non-major-a61c04e2f407c06c75c1917d0830c2b678090a5b":"2021-10-18T02:55:16.786Z","6.2.11-renovate-major-external-major-5b86032518faa433dce0458d5efc92a720758868":"2021-10-18T02:58:38.470Z","6.2.11":"2021-10-18T03:06:08.561Z","6.2.12-renovate-major-json-schema-org-7e2553b5c41db84f6c5f3b9882441daa94957ad5":"2021-10-18T03:10:36.920Z","6.2.12-renovate-major-external-major-4ab7334ba7b789d5b741a64adcb8f7e50d2eb888":"2021-10-18T03:10:59.182Z","6.2.12-renovate-major-external-major-c3f5fd067581cc0d649acaa9b51419b79416b6cc":"2021-10-18T06:18:14.845Z","6.2.12-renovate-major-external-major-a7146a48fbf25190d5bfd61ba8514c62efef0246":"2021-10-18T06:50:10.231Z","6.2.12":"2021-10-18T07:06:06.389Z","6.2.13-renovate-major-json-schema-org-232543a51a63c95692c80ff079d2fcc580f22353":"2021-10-18T07:10:12.786Z","7.0.0-h32-simplify-patch-validation-7312cbd752aa118cd7365c352c2c5ffee0c31289":"2021-10-18T13:57:52.347Z","6.2.13-renovate-internal-patch-3009eb7ac2368cefebc34af7c6eaea077dacdb6d":"2021-10-18T23:46:51.510Z","6.2.13":"2021-10-18T23:53:55.382Z","6.2.14-renovate-major-json-schema-org-dae227d78648ca38b4321289e4e78d4830d55ffc":"2021-10-18T23:58:06.203Z","7.0.0-h32-simplify-patch-validation-f6343443e8bfc32d5c1a3b0908805e46513afebe":"2021-10-19T15:06:26.823Z","7.0.0":"2021-10-19T15:14:03.958Z","7.0.1-renovate-major-json-schema-org-d40b386ed538b0ec8a75480e2380b5ebb0ca1f9e":"2021-10-19T15:21:30.796Z","7.0.1-renovate-internal-patch-f763638cb13d1039b947c08163b2a2c74bce5e93":"2021-10-20T09:08:19.692Z","7.0.1":"2021-10-20T09:14:46.778Z","7.0.2-renovate-major-json-schema-org-bf7371631582ca0bbe4b01833ed7b4e28251d669":"2021-10-20T09:21:59.575Z","7.0.2-renovate-internal-patch-e9623770c12535499a49ef8fc3f6cc1e7f3c953b":"2021-10-20T09:27:50.715Z","7.0.2":"2021-10-20T09:34:59.091Z","7.0.3-renovate-major-json-schema-org-ce15d5daf6eb05d40a7f3778f520242d941a0526":"2021-10-20T09:40:01.030Z","8.0.0-joshbwlng-index-locks-a1a70527ce64507603c3f7c151ef987fd15ac836":"2021-10-22T04:02:01.837Z","8.0.0-joshbwlng-index-locks-a2ea7cdc1630e65cb6a04892fc4eaada356cc1f7":"2021-10-22T04:26:51.010Z","7.0.3-repo-sync-jellyfish-config-default-3ee62d67e80d29ec83c39b963e16ad333b40cd82":"2021-10-23T01:10:42.115Z","7.0.3-renovate-internal-patch-cdb8b825d894d847c3723af7ddf1034a54b4a43b":"2021-10-23T02:26:09.910Z","7.0.3":"2021-10-23T02:33:07.143Z","7.0.4-renovate-internal-patch-f6c5c3141b0b8972d3d80429c6433eb6a9acd57c":"2021-10-23T02:38:10.506Z","7.0.4-renovate-external-non-major-3ae80e793e1cdf53204cdfa9cfa36f5881699685":"2021-10-23T02:39:22.698Z","7.0.4-renovate-major-json-schema-org-3e0df8414e65af8c3de9f751f45ff305a9b0f195":"2021-10-23T02:41:45.870Z","7.0.4":"2021-10-23T02:48:12.057Z","7.0.5-renovate-external-non-major-b1549ab311a7d83b4770ce20f2bf09be4ebfdd4c":"2021-10-23T02:53:15.512Z","7.0.5-renovate-major-json-schema-org-817dce5a11fca527f4149c6f11dd9ecf2c098182":"2021-10-23T02:53:28.560Z","7.0.5":"2021-10-23T03:01:49.952Z","7.0.6-renovate-major-json-schema-org-bdcdc44ef2d4c8cbc7f208c3e781667775eb75e1":"2021-10-23T03:07:18.781Z","7.0.6-renovate-internal-patch-a8ad495b7ae069251d8c5279e5209226b7715456":"2021-10-23T04:48:05.632Z","7.0.6":"2021-10-23T04:56:46.162Z","7.0.7-renovate-major-json-schema-org-942d00c3e920a357516cd6b46b5a2e4ca6c8dddb":"2021-10-23T05:00:36.950Z","7.0.7-renovate-internal-patch-e9c9d12f5d15f5dc15392044286c8bb43795eb8b":"2021-10-23T07:40:52.239Z","7.0.7":"2021-10-23T07:47:46.768Z","7.0.8-renovate-major-json-schema-org-7f2fc071646b340243054b07b7e56ed05682bbc1":"2021-10-23T07:55:15.254Z","7.0.8-repo-sync-jellyfish-config-default-42de0586e3a4ec437b794f8bde03872af967e10f":"2021-10-23T08:06:20.294Z","7.0.8":"2021-10-23T08:12:48.867Z","7.0.9-renovate-major-json-schema-org-48ba4c8267e72db9b193b7771e6e574e66170448":"2021-10-23T08:19:43.541Z","7.0.9-renovate-internal-patch-d1b1769d82a0c009c471aa523bca9cc42d94024b":"2021-10-23T10:31:22.172Z","7.0.9":"2021-10-23T10:38:40.717Z","7.0.10-renovate-major-json-schema-org-23a08f23529fe2c72f563a0a0f67bb0ae332bc81":"2021-10-23T10:42:39.704Z","8.0.0-joshbwlng-index-locks-0a40c95b628a0332a8d6f24b6c8675904dce85a0":"2021-10-23T12:29:52.587Z","7.0.10-renovate-external-non-major-2e1a22ec19c855fab1a4f621657b2fbf7292e4dd":"2021-10-23T18:38:40.674Z","7.0.10":"2021-10-23T18:46:45.182Z","7.0.11-renovate-internal-patch-52335d8111d9f8703dd254e6954302cba4192100":"2021-10-23T18:51:55.048Z","7.0.11-renovate-internal-patch-0fa3bb92cc7371b02be31bd4ebffddeee201699c":"2021-10-23T18:54:41.582Z","7.0.11-renovate-major-json-schema-org-664b9af311715404e4c37a19b841f07e354c5a76":"2021-10-23T18:55:03.432Z","7.0.11-renovate-internal-patch-bba77d2d120b84b81488ebd93dd5874611472706":"2021-10-23T20:36:58.496Z","7.0.11":"2021-10-23T20:43:49.107Z","7.0.12-renovate-major-json-schema-org-9c971e37312ab8f13f10cebe7522dfd94a3aaece":"2021-10-23T20:47:09.020Z","7.0.12-renovate-internal-patch-5089c2b53b47063c49cb080a9961139642636d27":"2021-10-23T22:37:23.023Z","7.0.12":"2021-10-23T22:43:39.983Z","7.0.13-renovate-major-json-schema-org-8e3de53a5f4febabf3c7b800f64031fb00586525":"2021-10-23T22:47:34.826Z","8.0.0-joshbwlng-index-locks-cf62e9efb7cb8d43d661b0c45488d56155d6ac75":"2021-10-24T00:23:57.666Z","7.0.13-h32-updated-at-fix-6444ac747f7d1b224ed0037eba38fc5e5e81c969":"2021-10-25T12:01:53.180Z","7.0.13":"2021-10-25T12:08:18.303Z","7.0.14-renovate-major-json-schema-org-b1db9f4e658f22f42e92d0281be0e112d5b48655":"2021-10-25T12:14:36.440Z","8.0.0-joshbwlng-index-locks-984fb21dd5ff6caa488dbbeeab529d375c164ae7":"2021-10-26T03:55:50.809Z","8.0.0-joshbwlng-index-locks-6e584a56d8e2b726b150aadf9248c20de270b13a":"2021-10-26T04:37:44.590Z","8.0.0-joshbwlng-index-locks-220826cf028fe7550f9da45ca84314487d44d668":"2021-10-26T05:09:10.856Z","8.0.0-joshbwlng-index-locks-f5cae14846a6e304ec798f0646432cbcaeecf99e":"2021-10-26T13:14:51.874Z","8.0.0-joshbwlng-index-locks-bf7c7c6ecbdc833e0e1c5ebdf0be4462c6947f47":"2021-10-27T01:16:39.381Z","8.0.0-joshbwlng-index-locks-429c33f83299b0de0d292de9d760ae4d3aec1672":"2021-10-27T01:20:08.253Z","7.0.14-renovate-internal-patch-b85d0790e09de8de20940f798099fafbd65628d5":"2021-10-28T01:22:06.079Z","7.0.14":"2021-10-28T01:30:31.071Z","7.0.15-renovate-major-json-schema-org-ed7d1d359e9c6c6cd75c2c6b55bd05775924862b":"2021-10-28T01:38:28.718Z","7.0.15-renovate-internal-patch-3ab810aefb9844d86e1733237e0abdb27c1037c3":"2021-10-28T01:41:03.251Z","7.0.15":"2021-10-28T01:49:44.813Z","7.0.16-renovate-major-json-schema-org-c04e3ea13a58fb2357bee4f8ac11cddefe6f7dce":"2021-10-28T01:53:45.971Z","8.0.0-joshbwlng-index-locks-18a46e2076b61987f5f7fe835ac7e3709d315a8a":"2021-10-28T02:09:38.830Z","8.0.0-joshbwlng-index-locks-bdff904af0ef9e51d46505404ff5b1b3c949e4cd":"2021-10-28T03:26:07.385Z","7.0.16-renovate-internal-patch-25775ad973c41ff26537e505a3b9a37193a7fbd4":"2021-10-28T03:35:52.249Z","7.0.16":"2021-10-28T03:42:44.938Z","7.0.17-renovate-major-json-schema-org-1fcbcabfbd3fbf9e7b84ab0fcea0633cca5a79fa":"2021-10-28T03:47:28.648Z","7.0.17-renovate-internal-patch-0e8275f436680b7c7ec96919e4bd9a27ee183409":"2021-10-28T03:51:44.718Z","7.0.17":"2021-10-28T03:59:20.562Z","7.0.18-renovate-major-json-schema-org-9cbb48f3865e942e14fb1d37b72227882d51ba56":"2021-10-28T04:03:10.394Z","7.0.18-renovate-internal-patch-5dc3d840feb4eaf863a0c98af83c4ab00c484208":"2021-10-28T05:57:48.030Z","7.0.18":"2021-10-28T06:04:11.837Z","7.0.19-renovate-internal-patch-7f1c032091b8a21213544b46accb8e592a4988f2":"2021-10-28T06:11:36.071Z","7.0.19-renovate-major-json-schema-org-068f0a47fb9429679e8705038153c655e375cab1":"2021-10-28T06:12:04.576Z","7.0.19":"2021-10-28T06:18:39.351Z","7.0.20-renovate-major-json-schema-org-9e6b82d064146cba60c67b651c14399371ec090a":"2021-10-28T06:23:44.822Z","7.0.20-lucianbuzzo-test-one-of-text-search-22c0a99b86e7253981d9576309a974e2068596f8":"2021-10-28T08:12:38.482Z","7.0.20":"2021-10-28T09:29:29.762Z","7.0.21-renovate-internal-patch-7c64103ad50d0fc178149368ef753d8ed0f3f6fd":"2021-10-28T09:37:01.617Z","7.0.21":"2021-10-28T09:44:24.068Z","7.0.22-renovate-major-json-schema-org-97d521de44e92c427cafe84ea5804357119890c7":"2021-10-28T09:48:57.103Z","7.0.22-renovate-internal-patch-c0a82f163cd1d7d74fd1be317dc8e2d596045b58":"2021-10-28T11:56:50.642Z","7.0.22":"2021-10-28T12:03:37.227Z","7.0.23-renovate-major-json-schema-org-acb3972d3438372c74f057fbffc83a1d39ceb29d":"2021-10-28T12:09:29.041Z","7.0.23-renovate-internal-patch-b04355f84d0316907158ebbf74f688eb87c22712":"2021-10-28T14:04:43.838Z","7.0.23":"2021-10-28T14:12:01.942Z","7.0.24-renovate-major-json-schema-org-5b4f2b382644107cda40ec7135435921bd586f9e":"2021-10-28T14:19:17.038Z","8.0.0-joshbwlng-index-locks-6c183e632a097d029d62b6d37eba252ec3ff99c3":"2021-10-29T01:52:52.757Z","7.0.24-joshbwlng-search-index-version-1e9bbe5b7ec3dcf9337a1f5ba3853e767bff10a3":"2021-10-29T02:33:45.013Z","7.0.24-joshbwlng-search-index-version-931218cca9888182c920da922e4b962f0c82f90b":"2021-10-29T02:38:27.582Z","7.0.24-joshbwlng-search-index-version-07559c424e7fc85a94f2ce55c7cd1bd19bf37be1":"2021-10-29T02:49:01.737Z","7.0.24-joshbwlng-search-index-version-fadd9ff3775ffd14853c3b0471a5e571dfde6e06":"2021-10-29T04:04:11.989Z","7.0.24-renovate-external-non-major-057cce9c234b37e58c7c3d2979e85b9dffc6a8f6":"2021-10-30T00:44:03.617Z","7.0.24":"2021-10-30T00:50:52.153Z","7.0.25-renovate-major-json-schema-org-2ac84d51b836f1e24bba028814a74309e2d8b410":"2021-10-30T00:54:51.531Z","7.0.25-repo-sync-secrets-default-7879c857d4b4a347495c59e1f7626b5b9b650bec":"2021-10-30T04:01:23.079Z","7.0.25-renovate-internal-patch-e899cdf0ac7042d0715a61c575bcb2db996ae45f":"2021-10-30T04:59:04.727Z","7.0.25-renovate-internal-patch-6d019a1e7af09e0bdf741e5c2f0f888bf16c69f8":"2021-10-30T10:17:56.327Z","7.0.25":"2021-10-30T10:25:08.203Z","7.0.26-renovate-major-json-schema-org-de05a5f524620e54e152c1157ffb31e8d12c8dd4":"2021-10-30T10:30:21.729Z","7.0.26-repo-sync-secrets-default-bc54e6578a1ccff7249f9da400b96a188cab1f63":"2021-10-30T12:49:34.068Z","7.0.26":"2021-10-30T13:06:42.648Z","7.0.27-renovate-major-json-schema-org-142eed7c5e44213e009681780de6a06261b53180":"2021-10-30T13:13:13.072Z","8.0.0-joshbwlng-index-locks-5250c6c8c88f8ae4cbb72bb81e908dc06c135d30":"2021-11-04T03:23:22.298Z","8.0.0-joshbwlng-index-locks-17b1dc6f44fddd06ecc32aeb8d43d7be41e857f6":"2021-11-04T03:51:18.880Z","8.0.0-joshbwlng-index-locks-f585d5740fa6c5151059760304274a048438d5d0":"2021-11-04T09:22:07.039Z","7.0.27-joshbwlng-search-index-version-9ee46bca7cc5d38981c7f2b726ac38c14e877033":"2021-11-05T04:28:55.577Z","7.0.27":"2021-11-05T05:19:06.111Z","7.0.28-renovate-major-json-schema-org-9203f29501d03f998c3ea4a48278d2cc60a3f3c4":"2021-11-05T05:22:54.365Z","8.0.0-joshbwlng-index-locks-9fb7b9e5d3a262746604a9214d51676773fa3737":"2021-11-05T05:33:19.707Z","8.0.0":"2021-11-05T08:40:00.439Z","8.0.1-renovate-major-json-schema-org-15c7d8afcff7a23ff557f2a80fb07cc80fb2c489":"2021-11-05T08:44:01.405Z","8.0.1-renovate-internal-patch-55fefc7c66f3f527d036674c07345cc2f01a8dfd":"2021-11-06T00:01:54.896Z","8.0.1":"2021-11-06T00:09:52.231Z","8.0.1-renovate-major-json-schema-org-b2506df40be4367dd6e6e15525d22cac770cfdc4":"2021-11-06T00:13:33.003Z","8.0.2-renovate-major-json-schema-org-5eb07b455ad7686b2f61ba4a016a06146e8640e2":"2021-11-06T00:14:57.797Z","8.0.2-renovate-internal-patch-5ffd0c85bd4625ffb95ccabc02ac4b98143e7cb5":"2021-11-06T03:16:30.989Z","8.0.2":"2021-11-06T03:22:57.713Z","8.0.3-renovate-major-json-schema-org-f4ef331d94edba4cebbed6aed2ec7df2779866a7":"2021-11-06T03:27:32.441Z","8.0.3-renovate-internal-patch-4e9955004d9503e4e8236ffd489477c5de449614":"2021-11-06T06:07:09.991Z","8.0.3":"2021-11-06T06:13:55.561Z","8.0.4-renovate-major-json-schema-org-86c4f11e4f10a6cd09e2cbcf0f0c8e3feb79b689":"2021-11-06T06:18:34.825Z","8.0.4-renovate-external-non-major-b96632a698e106b933f5368f4a70788b51c81cba":"2021-11-07T01:20:35.284Z","8.0.4-repo-sync-jellyfish-config-default-d3d63012d5265a7975df11c2e3a4516276f7d6b2":"2021-11-07T05:15:27.185Z","8.0.4-renovate-internal-patch-c36137e77c0d737923b1f7783cbad821e8851e1e":"2021-11-08T22:32:31.090Z","8.0.4-renovate-external-non-major-6097df070da764e5df74dc87663f6c7dbbc7de81":"2021-11-09T12:49:40.419Z","8.0.4":"2021-11-09T12:56:15.219Z","8.0.5-renovate-internal-patch-a3fe381a2bb48c7e0e9e66ee66c60fc6efe76f0f":"2021-11-09T13:01:21.974Z","8.0.5-renovate-major-json-schema-org-47522b044866fa8ae8574bec6dc59a9632ed4683":"2021-11-09T13:02:16.990Z","8.0.5-renovate-internal-patch-d987c0fd226e22102d9f0fef153984ce547f8053":"2021-11-09T14:40:00.755Z","8.0.5":"2021-11-09T14:51:22.594Z","8.0.6-renovate-major-json-schema-org-837ef2e2f3473e2250e38861f2d90428a1e702b1":"2021-11-09T14:58:24.654Z","8.0.6-renovate-balena-jellyfish-environment-5-x-ac767498e0cc8dca7cd33de43b2f8bc77ee02af0":"2021-11-10T04:30:23.506Z","8.0.6-renovate-internal-patch-aae73e3add7c65320073e7bad888b763181c3792":"2021-11-10T04:38:04.121Z","8.1.0-new-permissions-4d8596899904a023f46156c5af0f8bb39a9cdb93":"2021-11-10T17:50:21.750Z","8.0.6-renovate-internal-patch-cd88eca8eb2778a3feee97acf7f1e4a6eea7c1be":"2021-11-10T23:42:36.189Z","8.0.6":"2021-11-10T23:51:11.829Z","8.0.7-renovate-major-json-schema-org-2e99fa72b963160a8799edeb4719497751fab597":"2021-11-10T23:54:05.382Z","8.0.6-renovate-balena-jellyfish-environment-5-x-ccef330be62327013520d2cd23a4b13c040ad3cb":"2021-11-10T23:54:24.495Z","8.0.7-renovate-internal-patch-cefb0f7220e9607038ea0443f08d40cd499030b8":"2021-11-11T05:33:59.718Z","8.0.7-renovate-internal-patch-395a694341c317105a9503e7783cd36ca8375b6f":"2021-11-11T09:32:26.388Z","8.1.0-new-permissions-f8db081ad91e7625618d983cb9a863893738c728":"2021-11-11T12:44:33.593Z","8.0.7-repo-sync-secrets-default-6a8be04c3a51b71a2797be5b2cf4727ddc44ab4c":"2021-11-12T01:28:07.473Z","8.0.7-renovate-balena-jellyfish-environment-5-x-2348c2ceed3feabebd9237cc030781c5326d36a6":"2021-11-12T01:48:29.862Z","8.0.7":"2021-11-12T01:55:37.740Z","8.0.7-repo-sync-secrets-default-fc3ca1e0e43ed3b0a56103db5b71376c0021370e":"2021-11-12T01:57:12.346Z","8.0.8-renovate-major-json-schema-org-d6c363a7847bb0381e80c4ed0e07c9b790bcb621":"2021-11-12T02:02:07.966Z","8.0.8-repo-sync-secrets-default-b47bc180eac925d72381ac4f6d78c86591ec91d9":"2021-11-12T02:19:38.680Z","8.0.8-repo-sync-secrets-default-91dbcbe25afb2ed1d05d5c82c4c78a648b48d362":"2021-11-12T02:45:53.222Z","8.0.8-renovate-internal-patch-78e53cd49fcf825f3a5c78ff84f51ed358fab98e":"2021-11-12T03:37:52.230Z","8.0.8":"2021-11-12T03:43:58.226Z","8.0.9-renovate-major-json-schema-org-de37df428a28f6fa6c5c78061b03642143a7f593":"2021-11-12T03:47:32.878Z","8.0.9-renovate-internal-patch-c99b3a7237d9a0a00f49974d2c0a464bea16b94e":"2021-11-12T05:21:19.915Z","8.0.9":"2021-11-12T05:29:07.792Z","8.0.10-renovate-major-json-schema-org-f29070d1e469e03e77df582714ea1a694d33a9c6":"2021-11-12T05:31:40.579Z","8.0.10-renovate-internal-patch-e23e77aebd9b921c9fb27d1953cb7cac4aaeea45":"2021-11-12T09:51:20.704Z","8.0.10":"2021-11-12T09:57:43.435Z","8.0.11-renovate-major-json-schema-org-bd699a9b3feb39b2120bca38f4164b9ca22692be":"2021-11-12T10:00:09.542Z","8.0.11-renovate-internal-patch-4f517780ccb87a3664471347f8fd0f29de1d822a":"2021-11-13T01:51:40.570Z","8.0.11":"2021-11-13T01:58:07.308Z","8.0.12-renovate-major-json-schema-org-f890dd6b87ac3a8c6c65e88353544b1b58dac6fa":"2021-11-13T02:01:35.016Z","8.0.12-renovate-internal-patch-d7eb62da154d0fbb89a3c1186586b0a851035f89":"2021-11-13T03:36:55.459Z","8.0.12":"2021-11-13T03:44:16.133Z","8.0.13-renovate-major-json-schema-org-2f49d0a72ffd4a944f47722b36c961580e50d3f8":"2021-11-13T03:46:05.082Z","8.0.13-renovate-internal-patch-5bf38292561b5c429480362728ea8e6867e53119":"2021-11-13T05:33:56.995Z","8.0.13":"2021-11-13T05:41:41.208Z","8.0.14-renovate-major-json-schema-org-b5c018cb4c13229b0dee5ba4c068f8244d99ebf3":"2021-11-13T05:43:39.365Z","8.0.14-renovate-internal-patch-45ca3924a731bfdc944c90b290e2d46a1519dbd0":"2021-11-13T10:39:15.840Z","8.0.14":"2021-11-13T10:46:50.039Z","8.0.14-renovate-major-json-schema-org-e5b10c04129b7cf4cacc696d29739fae0f29984a":"2021-11-13T10:48:39.206Z","8.0.15-repo-sync-secrets-default-fb96ed466b002b08378f581bb21eefac1bb057d5":"2021-11-14T00:04:08.493Z","8.0.15-renovate-internal-patch-4f901a046ab5a54c0858972088f93722be2289b3":"2021-11-14T12:58:37.480Z","8.0.15-renovate-major-external-major-47ae748c69ae4cb0ba3f6b0c6cc3971bc30a52e5":"2021-11-14T12:59:10.481Z","8.0.15-renovate-internal-patch-f30213283d00644aa8d11e919036e59d1f5a9d67":"2021-11-14T14:51:20.909Z","8.0.15-renovate-internal-patch-6787164f5daa7a7f34ed355bb65bbae1ddee4505":"2021-11-14T16:09:47.826Z","8.0.15-renovate-external-non-major-024677f5ae6eab4e8d0a92c109a5d8653cfe2688":"2021-11-14T23:35:49.145Z","8.0.15-renovate-internal-patch-40a1eb01761618f839b30ef87b708360bfbf4210":"2021-11-15T01:55:42.501Z","8.0.15-joshbwlng-set-license-9cd0168f505b9893bd3d8b961d7ac97db9697506":"2021-11-15T03:52:08.363Z","8.0.15-renovate-internal-patch-f644961246d3ce4e7355c28a35e3c9bb5232a6d8":"2021-11-15T04:29:21.801Z","8.0.15-renovate-internal-patch-37c73bd8c8dd3037420e032f47722f994557d057":"2021-11-15T11:21:55.118Z","8.0.15":"2021-11-15T11:27:26.871Z","8.0.16-renovate-major-external-major-19b0f0cec8cb59e2acb0a000228d62c3e6fcadcd":"2021-11-15T11:31:16.087Z","8.0.16-renovate-external-non-major-6dae037beef8d341899837b0ffd2dcc565a29bf5":"2021-11-15T11:32:37.730Z","8.0.16-renovate-major-json-schema-org-67fc21ce4e6f0c5755209ec9f2382eeb11e1ef8b":"2021-11-15T11:33:21.142Z","8.0.16":"2021-11-15T11:39:37.635Z","8.0.17-renovate-major-json-schema-org-52c4421a5dceaa57d6c07c0842a98fb1a29df836":"2021-11-15T11:43:05.226Z","8.0.17-renovate-major-external-major-be7c8fc89893562a0e236401c6d8f23b53cb1154":"2021-11-15T11:44:33.329Z","8.0.17-renovate-internal-patch-e3b83ad231a62583fb29f5ec502102c7cf565098":"2021-11-15T13:52:06.518Z","8.0.17":"2021-11-15T23:05:42.656Z","8.0.17-renovate-internal-patch-c61910b075bc771e5ac7e04c316b6fb9896d693b":"2021-11-15T23:07:54.325Z","8.0.17-renovate-major-json-schema-org-20a8b7cbb4b634a3c46208b9f4cb163ae5a99a8d":"2021-11-15T23:09:12.818Z","8.0.18-renovate-internal-patch-f60a744092140420111576c695d76f6647f1d72d":"2021-11-15T23:10:48.700Z","8.0.18-renovate-major-json-schema-org-50e3b6b8f3900ac2eec353b88ca56061ee55fd56":"2021-11-15T23:11:44.587Z","8.0.18":"2021-11-15T23:21:32.288Z","8.0.19-renovate-major-json-schema-org-85c4fec348c45bfd9169196b6ae032b4e7f34282":"2021-11-15T23:26:47.060Z","8.0.19-joshbwlng-set-license-d94ac3d05cd9108dd424b4e4462f0d4e5fae363d":"2021-11-16T00:22:34.424Z","8.0.19":"2021-11-16T01:04:44.838Z","8.0.20-renovate-internal-patch-8972de131473c361d38280e222c7031f470ba828":"2021-11-16T01:07:43.391Z","8.0.20-repo-sync-jellyfish-config-default-9e82820058da7f82da43568e3425cffc6b736635":"2021-11-16T02:00:33.277Z","8.0.20-renovate-internal-patch-756ea8245c544d07c7386454edf9f29606ed80f1":"2021-11-16T02:41:10.228Z","8.0.20-renovate-internal-patch-4c591bd14e941c759a91db0891375090a831ce99":"2021-11-16T04:34:00.778Z","8.0.20-renovate-internal-patch-0245af36b24aafb7937925add53bb865a93fc92a":"2021-11-16T08:56:50.533Z","8.0.20-repo-sync-secrets-default-f8e9e613e325f42f57629cfc545be142c09e8a48":"2021-11-16T13:38:39.477Z","8.0.20-repo-sync-secrets-default-58688a43f3a2feb3d26e77ac76409966051531ab":"2021-11-17T08:57:47.564Z","8.0.20-repo-sync-secrets-default-0fb8bac49574163556fa3a725d40d57cd5bb7cd5":"2021-11-17T09:03:22.094Z","8.0.20-renovate-internal-patch-67926a3675cabeb24bc0c64ea01285027351a053":"2021-11-17T09:11:39.396Z","8.0.20-renovate-major-json-schema-org-896fe169ac04d4b1e3ec15c267b6b45d531f49dc":"2021-11-17T09:12:46.678Z","8.0.20-renovate-internal-patch-09be4807700b3dc00b3ec35f47a6b661cbb8d654":"2021-11-19T10:51:50.049Z","8.0.20":"2021-11-19T10:58:18.121Z","8.0.21-renovate-major-json-schema-org-749a54084582595f501507ac039abeaa884a6e80":"2021-11-19T11:02:22.659Z","8.0.21-renovate-internal-patch-a8313f3841ad03df73a030c1812aa5883d23f826":"2021-11-19T12:47:25.430Z","8.0.21-renovate-major-json-schema-org-c9d313cfd0b8d21bbccdd4fd2d69e5f712a72c81":"2021-11-19T12:56:43.097Z","8.1.0-new-permissions-dd7cd2a165ee85c0cd368d1785b41c26018fcda7":"2021-11-19T19:31:00.684Z","8.1.0":"2021-11-19T19:37:30.415Z","8.1.1-renovate-major-json-schema-org-aacc521943aa9054e11e3391c985dc52651b68c7":"2021-11-19T19:42:43.142Z","8.1.1-renovate-balena-jellycheck-1-x-64c5d436015e0aa271f1dc2c965c2b74a858af2e":"2021-11-20T01:48:38.596Z","8.1.1-renovate-internal-patch-e364830db62a62520bb5a097f6dc16904cce563d":"2021-11-20T09:29:22.808Z","8.1.1-renovate-internal-patch-77942482ee47fd9d5a99c56c5823f20fcd3b508e":"2021-11-20T11:53:31.083Z","8.1.1-renovate-internal-patch-8b4f5e15b49aa364a846883b7e0b9f2ce726f99f":"2021-11-20T13:19:15.612Z","8.1.1-renovate-internal-patch-2efe143c704e175a80176c372064561b38ff8538":"2021-11-20T13:24:50.010Z","8.1.1":"2021-11-20T13:31:49.692Z","8.1.2-renovate-major-json-schema-org-4e337698aad59f1ef65be8237de3c73e6f40edc6":"2021-11-20T13:35:03.091Z","8.1.2-renovate-internal-patch-edb974a71daed990b3b1928be26a493af1f073f1":"2021-11-20T15:41:15.716Z","8.1.2":"2021-11-20T15:47:47.593Z","8.1.3-renovate-major-json-schema-org-c2b4d9de8718a715b26fe8764c2a35bae30bce45":"2021-11-20T15:51:17.773Z","8.1.3-joshbwlng-remove-license-headers-0b9b2dd1c62356a087bc0bfecbcc6471f0c6e94d":"2021-11-21T03:37:41.940Z","8.1.3":"2021-11-21T07:57:57.952Z","8.1.4-renovate-internal-patch-209524cfded2afc36969234f536c36b698a8b6f0":"2021-11-21T08:02:30.410Z","8.1.4-renovate-internal-patch-aea1c86dd2d27ac2b17e5a4e1577a6a467115473":"2021-11-21T14:50:33.570Z","8.1.4":"2021-11-21T14:57:00.085Z","8.1.5-renovate-major-json-schema-org-8f17e7c346cdf91e7a7111195afdd1779b0861cf":"2021-11-21T15:01:34.057Z","8.1.5-renovate-external-non-major-9358a7dad24a4ed48b1bb215c8f6692a89fa357c":"2021-11-21T17:49:20.032Z","8.1.5-renovate-internal-patch-cc35a5df61557c6c690d8a2226bf7a99574f0a3e":"2021-11-21T23:43:32.523Z","8.1.5":"2021-11-21T23:50:19.007Z","8.1.6-renovate-external-non-major-7ee516bdd61611b765f3775ecb6418a04cf0a3b8":"2021-11-21T23:52:14.745Z","8.1.6-renovate-major-json-schema-org-f721bc806f83135343020012e00e3ff3f9463a07":"2021-11-21T23:53:12.118Z","8.1.6-renovate-internal-patch-8c5fbac47218a815192bfdb3c7bac685ad1047b5":"2021-11-22T07:11:47.324Z","8.1.6-renovate-external-non-major-9ef68eccac5a86a4c4bfe297ead2f7c26f6230f1":"2021-11-22T10:59:16.220Z","8.1.6-joshbwlng-bump-deps-b3e7e64866acab2709bedf7fd5d92b8095623983":"2021-11-23T01:26:50.102Z","8.1.6":"2021-11-23T01:33:08.657Z","8.1.7-renovate-major-json-schema-org-d37974bb73188920a909696ddeb27084c80836f8":"2021-11-23T01:37:21.660Z","8.1.7-renovate-internal-patch-8ffff0fffe2b729bb71dd049fa7162da47541000":"2021-11-23T01:39:26.178Z","8.1.7-renovate-internal-patch-49743fbae83a388c1c0dd820a52fc2e5eab19d97":"2021-11-23T05:00:27.805Z","8.1.7":"2021-11-23T05:06:19.207Z","8.1.8-renovate-typescript-4-x-44fed10faa261e8bc34554bbd72c4dc6890acc65":"2021-11-23T05:12:09.811Z","8.1.8-renovate-internal-patch-da2887ae40c5700f2c18880f2834b57c17ac6cda":"2021-11-24T02:45:31.706Z","8.1.8":"2021-11-24T02:51:58.075Z","8.1.9-renovate-typescript-4-x-c5057df83b5e1c8ebf247740fa50fceea5318f47":"2021-11-24T02:55:46.398Z","8.1.9-renovate-internal-patch-b374796ee4c4888807f7ad131d262d370bc88d51":"2021-11-24T02:58:47.947Z","8.1.9":"2021-11-24T03:04:54.874Z","8.1.10-renovate-typescript-4-x-cd6322e6773b9f5a15e3ce31e94f328836d9d99e":"2021-11-24T03:14:10.795Z","8.1.10-renovate-internal-patch-21a16f7778833c14b8f64f52b78d9206c4cfcffc":"2021-11-24T05:23:25.583Z","8.1.10-renovate-internal-patch-c860f60136e792db45929f1afbd8d825fdd6d5d9":"2021-11-24T06:03:27.344Z","8.1.10-renovate-internal-patch-4a99fed01d81bc1c557eb3a5ae9fa1c4fab85ba8":"2021-11-24T09:22:49.752Z","8.1.10-renovate-internal-patch-4b1e719ef8d44f3add40be461aa6ffb7b5acaa0a":"2021-11-24T09:29:08.658Z","8.1.10":"2021-11-24T09:38:11.772Z","8.1.11-renovate-major-json-schema-org-d1cac17cfba7a27e0f0566623b7dc51cc3e8aad1":"2021-11-24T09:41:31.241Z","8.1.11-renovate-internal-patch-d0a08a0ca69efddb88b1980b8865540b5a3dc2d4":"2021-11-24T09:43:29.965Z","8.1.11":"2021-11-24T09:49:25.123Z","8.1.12-renovate-typescript-4-x-7b5191d2ed666e9c5cffae3a1e374d711394eb7f":"2021-11-24T09:52:36.919Z","8.1.12-renovate-major-json-schema-org-b50fc1a9ecd3f36bbe2a9b4ccb7837308a832855":"2021-11-24T09:53:25.031Z","8.1.12-renovate-internal-patch-3434db0a3733560b7a9765bcb9d718b623003c0c":"2021-11-24T13:04:56.864Z","8.1.12-renovate-internal-patch-ca8fcc618a13133acf73b37ddc42a42892fd8444":"2021-11-24T13:09:30.126Z","8.1.12-renovate-internal-patch-71bfb9921028e8a5d85fe4f5d28389986af2e8c6":"2021-11-24T13:13:18.440Z","8.1.12":"2021-11-24T13:20:24.571Z","8.1.13-renovate-typescript-4-x-387bb19c06e9db531545f7082a73a39e7d349d33":"2021-11-24T13:23:43.264Z","8.1.13-renovate-major-json-schema-org-34c634bddf06d0a8dd88fe494af20b49b3d5c8d2":"2021-11-24T13:24:30.281Z","8.1.13-renovate-internal-patch-47329d0a3a672b34046f533d912e2549d0461f0a":"2021-11-24T15:09:57.091Z","8.1.13":"2021-11-24T15:15:59.924Z","8.1.14-renovate-major-json-schema-org-ec5ebee50d842c9f8381d628d06d307e3524f3f8":"2021-11-24T15:19:38.211Z","8.1.14-repo-sync-jellyfish-config-default-5ea2c0476067f0bf1b94880a7bcaee1e7ab7d7fc":"2021-11-25T01:36:57.235Z","8.1.14":"2021-11-25T01:43:53.436Z","8.1.15-joshbwlng-remove-jellycheck-072bc0771a1419e5702493dd4ad63115f9658ed6":"2021-11-25T02:12:40.139Z","8.1.15":"2021-11-25T02:19:19.745Z","8.1.16-renovate-internal-patch-a4589da2fc99b3f6b846dc1ad515d62685b40e71":"2021-11-25T08:04:01.597Z","8.1.16-renovate-typescript-4-x-256ef6b969c9572673698bdcd691a9798aa3b32e":"2021-11-25T08:04:56.547Z","8.1.16-renovate-major-json-schema-org-b70b240f68077fbaf1cb9c6166ea5cae300f48c8":"2021-11-25T08:05:34.214Z","8.1.16":"2021-11-25T08:12:29.896Z","8.1.17-renovate-typescript-4-x-d29c0d7ce7c5263e591b963f46cafef32e167ee6":"2021-11-25T08:15:34.085Z","8.1.17-renovate-major-json-schema-org-971a400ba8e71a52fecb9e980caf01f6fdc15935":"2021-11-25T08:17:08.161Z","8.1.17-renovate-internal-patch-428026c36d58e3a1bbfd83c9491ef34cd5ec019b":"2021-11-25T10:12:15.893Z","8.1.17":"2021-11-25T10:19:34.182Z","8.1.18-renovate-typescript-4-x-1859c6f0c29c5597c9e86b2543693cdfb13e7c4a":"2021-11-25T10:22:36.214Z","8.1.18-joshbwlng-use-renovate-config-88f9dd1e027e9fdbf16718590ee06d2c3dff7dfe":"2021-11-26T04:40:19.052Z","8.1.18":"2021-11-26T04:46:57.787Z","8.1.19-renovate-internal-patch-84ea563e2e0bc2e1c79eecc1fd2259bd87d8f10b":"2021-11-26T10:45:51.408Z","8.1.19-renovate-major-json-schema-org-8c086d191f6af508df32e315701c163be8691954":"2021-11-26T10:46:38.851Z","8.1.19-renovate-internal-patch-4c3e160738c9501566ee1276e01a505b3f70dd89":"2021-11-26T16:55:15.554Z","8.1.19":"2021-11-26T17:03:16.354Z","8.1.20-renovate-typescript-4-x-65bf4cb1b697f597ae76fb59b2b9ec8b10935655":"2021-11-26T17:07:23.710Z","8.1.20-renovate-balena-jellyfish-config-2-x-4021ecf54b499d198d34e00c266334f14315e06f":"2021-11-26T17:07:45.885Z","8.1.20-renovate-internal-patch-8c840e6251f45b3bb27cea31d6a5d05f8c391022":"2021-11-26T17:10:56.435Z","8.1.20-renovate-internal-patch-be0af9a0c39976bc6fd67dc60e5807a15a500ecf":"2021-11-26T21:29:01.064Z","8.1.20":"2021-11-26T21:39:01.612Z","8.1.21-renovate-balena-lint-6-x-2bc9d4ae9103678e71be6a42c6717a6f15d62372":"2021-11-26T21:45:09.350Z","8.1.21-renovate-typescript-4-x-c4cda2539f5070c58bd4d463e3fbdb864a9769ff":"2021-11-26T21:47:35.580Z","8.1.21-renovate-major-json-schema-org-67c64c7fcd276ea87c7f3a6297ff188d02621170":"2021-11-26T21:51:20.999Z","8.1.21":"2021-11-26T21:55:02.190Z","8.1.22-renovate-typescript-4-x-23d82116e4ddc7b34278a7843625a6f0f53cbf4a":"2021-11-26T21:59:35.403Z","8.1.22-renovate-major-json-schema-org-ce87fba76fde04499419b35513a4f480c9cc9d60":"2021-11-26T22:01:08.111Z","8.1.22-repo-sync-jellyfish-config-default-252b832f8c7bce7a2db54eaa51983bd09427910b":"2021-11-27T00:36:59.638Z","8.1.22-renovate-external-non-major-2ad0af8bbbedc0ce72d1054c844f2dd2faf94203":"2021-11-27T00:40:16.031Z","8.1.22-renovate-balena-jellyfish-config-2-x-a4985c382204f02a7a354231156a39585b5de82a":"2021-11-27T00:40:45.260Z","8.1.22":"2021-11-27T00:46:44.900Z","8.1.22-renovate-typescript-4-x-42ca459cf8b0bbd1edc0851b6934f302b218891f":"2021-11-27T00:49:24.512Z","8.1.23-renovate-balena-jellyfish-config-2-x-593a0c78dc38ab80f104871d1fd80af6bbc43035":"2021-11-27T00:50:13.897Z","8.1.23-renovate-major-json-schema-org-6911f871b062509f9184feec12cc6c89e05190c6":"2021-11-27T00:51:31.460Z","8.1.23-renovate-typescript-4-x-1b27b6e3cb99c8aa164c67b9169ce70aafa219d9":"2021-11-27T00:53:20.435Z","8.1.23-renovate-internal-patch-bce6793191dfdb8869b25078ac556a6dd18070c0":"2021-11-27T03:38:21.228Z","8.1.23":"2021-11-27T03:44:10.799Z","8.1.24-renovate-balena-jellyfish-config-2-x-4280b59198dcefb005db8245e54f1c9113d9210b":"2021-11-27T03:47:50.641Z","8.1.24-renovate-typescript-4-x-610e2c48b57b40e99cadfe8d0747272c6a2aff75":"2021-11-27T03:49:39.500Z","8.1.24-renovate-internal-patch-2c318284424187da14a7c3e5ee7e15a178d97259":"2021-11-27T15:54:50.453Z","8.1.24-renovate-internal-patch-7d40c39756c6d75f983b9a6f80c24f7dea27d9e6":"2021-11-27T17:35:03.428Z","8.1.24":"2021-11-27T17:44:19.479Z","8.1.25-renovate-typescript-4-x-c3ca89f4f4e5894ac812928407388f783c9e5e4b":"2021-11-27T17:47:37.453Z","8.1.25-renovate-major-json-schema-org-4ad2102464df8510fbafe8b80c0542b7f4dac0c6":"2021-11-27T17:48:57.904Z","8.1.25-renovate-balena-jellyfish-config-2-x-0b96e15a5901fa6bc7cfbacad582a5442fbf28a2":"2021-11-27T17:51:16.904Z","8.1.25-renovate-internal-patch-725533cf3c8dcadbe190c2f055986c14aa1db661":"2021-11-27T19:19:06.468Z","8.1.25":"2021-11-28T02:20:26.140Z","8.1.26-renovate-typescript-4-x-8a59fe7afe151bf84ccd7586f6704e78e9e99966":"2021-11-28T06:14:03.920Z","8.1.26":"2021-11-28T06:20:02.720Z","8.1.27-renovate-major-json-schema-org-6b56145c1ed25a0bb36bb12707541dda34448402":"2021-11-28T07:13:07.831Z","8.1.27-joshbwlng-remove-sync-comments-18b09b73ff9e9ba6bebf4f75a887bf2ee8be1677":"2021-11-28T07:17:47.769Z","8.1.27":"2021-11-28T07:46:01.058Z","8.1.28-renovate-internal-patch-baab38f1c1547ca1c85c70177aba30177f870c8d":"2021-11-28T11:27:39.885Z","8.1.28":"2021-11-28T12:13:44.120Z","8.1.29-renovate-internal-patch-922307996c8d704fb078720a596f69e56e7d5587":"2021-11-29T07:33:39.823Z","8.1.29":"2021-11-29T07:41:00.026Z","8.1.29-renovate-major-json-schema-org-b09418c635447dd1beb6d1c25fe8ac2b8aaa4ba3":"2021-11-29T07:43:18.991Z","8.1.30-renovate-major-json-schema-org-bae04cd0657efc8d8c1decbfcf0914a9964459b6":"2021-11-29T07:47:51.051Z","8.1.30-renovate-internal-patch-98ece36cd70b92565f5125641c47aead7da05a01":"2021-11-29T08:54:50.616Z","8.1.30":"2021-11-29T09:00:59.663Z","8.1.31-renovate-major-json-schema-org-a9cc8c9908e1e00b18917afd85285f1b9f58c714":"2021-11-29T09:07:23.284Z","8.1.31-renovate-internal-patch-4847e06acefc5a9ba6b4396c9421a1e70e8f8dcf":"2021-11-29T14:51:16.697Z","8.1.31":"2021-11-29T14:57:45.546Z","8.1.31-renovate-major-json-schema-org-11fa6e484579706e8823823220c86f9097ea1694":"2021-11-29T15:00:28.881Z","8.1.32-repo-sync-secrets-default-ebc1de642f99b15a5119a271a98d5abde3a47c1d":"2021-12-01T00:39:56.799Z","8.1.32":"2021-12-01T01:07:28.510Z","8.1.33-repo-sync-secrets-default-b801c8437c7e7a25aae50ceae03b7f88c53f5aa1":"2021-12-01T04:14:55.649Z","8.1.33-repo-sync-secrets-default-3682dce9c0c976ec4befcbac45ff5a54ccd1de19":"2021-12-01T07:26:01.460Z","8.1.33":"2021-12-01T07:33:26.221Z","8.1.34-renovate-major-json-schema-org-7db833b4893bd3f4992d2ecbf47173212cde279e":"2021-12-01T10:26:34.767Z","8.1.34-renovate-internal-patch-30130e2988add0f0efe77f989db963271b5d7c03":"2021-12-01T12:17:34.990Z","8.1.34":"2021-12-01T12:24:59.767Z","8.1.34-renovate-major-json-schema-org-34cf4d3af22c250695a1fcfbe9f759010d9d09e5":"2021-12-01T12:27:41.465Z","8.1.35-joshbwlng-update-readme-6a48566e682e456dbbc30f8afc5bcde1053d687b":"2021-12-02T00:41:11.418Z","8.1.35":"2021-12-02T00:48:04.542Z","8.1.36-joshbwlng-remove-secrets-6d5a5545573b7b2a9a9fdac369bf2aeb97c4a77c":"2021-12-02T03:33:57.988Z","8.1.36":"2021-12-02T03:40:08.648Z","8.1.37-renovate-balena-jellyfish-environment-5-x-286ce1681ea8adfe06627b43559e260e0b0a9b8f":"2021-12-02T04:45:00.337Z","8.1.37-renovate-major-json-schema-org-9abd75a3efcad8e4480e180da26a3fd5d71a0ff0":"2021-12-02T10:04:35.336Z","8.1.37-joshbwlng-update-readme-07c731b7231adef5659e71b0b5c43ff8e9c91ff7":"2021-12-03T02:03:50.174Z","8.1.37":"2021-12-03T02:10:01.561Z","8.1.38-renovate-balena-jellyfish-environment-5-x-b451c7da3c48246b73640248540e67a4ce0831d6":"2021-12-03T02:15:12.249Z","8.1.38-renovate-major-json-schema-org-50dc4d2dbd39f1d52d840cf6bdcea083665ca0c0":"2021-12-03T02:16:00.301Z","8.1.38":"2021-12-04T04:39:07.987Z","8.1.39-renovate-internal-patch-3d30b40c84a7ce5495b612d8ca92e89ecd8d6a6f":"2021-12-04T18:11:16.154Z","8.1.39-renovate-external-non-major-4f944bee203bcdef20000b427531849e29115d0a":"2021-12-04T18:12:15.954Z","8.1.39":"2021-12-04T18:17:59.958Z","8.1.40-renovate-major-json-schema-org-8fcdca46b96478c8b932402f5fa9a52cc4a45bd1":"2021-12-04T18:21:14.488Z","8.1.39-renovate-external-non-major-10ab6f773073f866987cb58a7d18ccc0d23279b1":"2021-12-04T18:21:46.289Z","8.1.40-renovate-external-non-major-a050891f4154f5ce8852f9d149ef0d691922ed30":"2021-12-05T09:26:21.747Z","8.1.40":"2021-12-05T09:32:21.071Z","8.1.41-renovate-internal-patch-be985397f516542c6766e840001f0f91dd141437":"2021-12-05T09:37:30.565Z","8.1.41-renovate-major-json-schema-org-922fd448d0ee1a36a118ae1a9bf7fd7c126ea674":"2021-12-05T09:38:32.890Z","8.1.41":"2021-12-05T09:45:12.337Z","8.1.42-renovate-major-json-schema-org-cb191288cc47af4987fea602c0b5237deb20798c":"2021-12-05T09:48:14.773Z","8.1.42-renovate-internal-patch-24ef2e0e08597f1e985dbf25837fa48b29cc513f":"2021-12-05T11:45:09.481Z","8.1.42":"2021-12-05T11:53:49.252Z","8.1.42-renovate-major-json-schema-org-7926134f3515f87caa44e0ea78160f0817f5d637":"2021-12-05T11:56:06.723Z","8.1.43-renovate-major-json-schema-org-af9aeb88a862c659e5185273b900b9cd5e4b8cc1":"2021-12-05T12:01:01.035Z","8.1.43-renovate-internal-patch-08854fca928356acc4a264fdcfcf665f0717bd6b":"2021-12-05T16:59:04.988Z","8.1.43":"2021-12-05T17:05:46.025Z","8.1.44-renovate-major-json-schema-org-98a32a2e8d1db941c3dde5a63fcb8acdaf0aba47":"2021-12-05T17:10:43.704Z","8.1.44-renovate-internal-patch-f1c16688c27aa54f48358648435f745d808e6ad6":"2021-12-06T00:34:53.621Z","8.1.44":"2021-12-06T00:41:27.346Z","8.1.45-renovate-major-json-schema-org-50895845d49c9a96f389c4145fd33bf007d1f7cf":"2021-12-06T00:44:30.334Z","8.1.45-renovate-balena-jellyfish-environment-6-x-405be500a48497cb8d2884e6dc9d0a7da600bacb":"2021-12-09T01:29:47.727Z","8.1.45-renovate-internal-patch-2c51ba5ee7cbe7f9260e88c54ea38171e5250bb6":"2021-12-09T19:39:09.684Z","8.1.45-renovate-typescript-4-x-a666a75481bef828beb569ba83c01cc17d7842c8":"2021-12-10T00:24:11.441Z","8.1.45":"2021-12-10T02:14:37.917Z","8.1.45-renovate-major-json-schema-org-5a2437700e169e8b7afe863326e24d0ea4cf3ed7":"2021-12-10T02:18:25.034Z","8.1.46-renovate-internal-patch-4dc905430b1083b807eceba5b361a76d87274a50":"2021-12-10T02:22:12.768Z","8.1.46":"2021-12-10T02:26:19.752Z","8.1.47-renovate-major-json-schema-org-0f5af6176417850790bd2fcf7a1d3a2ead51bb4d":"2021-12-10T02:32:17.142Z","8.1.47-renovate-balena-jellyfish-environment-6-x-98aed2f78ed4254ec3bbd14fc1e85c7c9c8b7fd9":"2021-12-10T02:32:54.927Z","8.1.47":"2021-12-10T02:40:00.068Z","8.1.48-renovate-major-json-schema-org-bf985a2dbdcc0840521d12480f9e68f79a5ac764":"2021-12-10T02:43:11.943Z","8.1.48-renovate-internal-patch-e82c0221f230b147e365cc133e3d73f3820fe052":"2021-12-10T04:17:08.021Z","8.1.48":"2021-12-10T04:23:23.454Z","8.1.49-renovate-major-json-schema-org-eb3d0a1300a52d0b3620226cbbbab8a11e0c038b":"2021-12-10T04:29:11.075Z","8.2.0-h32-error-ui-schema-5d00d8fc9713b9062ccc8def92c85ebbcce8002e":"2021-12-10T12:31:24.748Z","8.2.0-h32-error-ui-schema-7777f2be309316ecdfbaac857011ea089238ebe3":"2021-12-10T12:37:02.219Z","8.1.49-renovate-internal-patch-58847bbd272c183b467bd40ed8a1c15427887182":"2021-12-11T00:38:16.805Z","8.1.49":"2021-12-11T00:45:26.656Z","8.1.50-renovate-external-non-major-f770b55431f83b85d1b0cc955f419ad2a4a86c04":"2021-12-11T00:49:25.209Z","8.1.50-renovate-major-json-schema-org-14506aeb19702bf2296300dad8c5075d7ca18576":"2021-12-11T00:50:32.955Z","8.1.50-joshbwlng-bump-deps-d3192879ed8f1a1e86c2b10bfd680504ad37c45c":"2021-12-11T04:39:22.346Z","8.1.50":"2021-12-11T04:46:54.978Z","8.1.51-renovate-internal-patch-52802b62052a21e0a1f308a6cd0bf004d9d4df5c":"2021-12-11T10:32:54.042Z","8.1.51-renovate-external-non-major-3287a77ba5f9ebac8550e4d9e41478df73100081":"2021-12-11T10:35:24.173Z","8.1.51-renovate-external-non-major-19f18e8d5b8ffda99c80842a0df27c74c1696933":"2021-12-13T20:47:58.371Z","8.1.51-renovate-external-non-major-afa00dc7cfb841888861e1152f057ec86e60693c":"2021-12-13T22:39:21.049Z","8.1.51-renovate-internal-patch-e959e228408fad839552c663d5ef4e2f0f76224d":"2021-12-14T01:24:12.891Z","8.1.51":"2021-12-14T01:31:35.319Z","8.1.52-renovate-external-non-major-6e252068bab8bad0921948b6202b9a4189eeef66":"2021-12-14T02:38:28.459Z","8.1.52":"2021-12-14T02:46:05.843Z","8.1.53-renovate-internal-patch-a29e6d741c6d732251389c725ab3c99d09b912f8":"2021-12-14T09:42:08.126Z","8.1.53-renovate-major-json-schema-org-1c93e21f53dfe6d3419120a382845f0a4ba60e7d":"2021-12-14T09:43:11.683Z","8.1.53-renovate-internal-patch-1751105668a66a0faedf5e94e32f1063a7e857cf":"2021-12-14T09:46:55.809Z","8.1.53":"2021-12-14T09:53:13.940Z","8.1.54-renovate-major-json-schema-org-c15eed885b524bb35982575797e3b83a40406d7a":"2021-12-14T09:57:51.392Z","8.1.54-renovate-balena-jellyfish-assert-1-x-d3ef3372cd00a96b138c13f6786841ad9b63ee04":"2021-12-16T18:46:05.606Z","8.1.54-renovate-balena-jellyfish-logger-3-x-a380ff651383ecd0c3977d69f5fec037c86f40a1":"2021-12-16T19:39:25.127Z","8.1.54":"2021-12-16T19:48:15.409Z","8.1.55-renovate-major-json-schema-org-e48da5d2589edcbb1a8084d3e6a2645d7758fac9":"2021-12-16T19:51:45.375Z","8.1.55-renovate-balena-jellyfish-assert-1-x-1625e88b622866682e4243613990146bdb8d09c6":"2021-12-16T19:53:16.086Z","8.1.55":"2021-12-16T20:01:10.209Z","8.1.55-renovate-internal-patch-84ebbb75a37208d9ec106f9104f37bf6db07abc7":"2021-12-16T20:04:43.698Z","8.1.56-renovate-internal-patch-d0f57de2b2302d8e76c04e16778ccb6f87de9c8a":"2021-12-16T20:09:16.631Z","8.1.56":"2021-12-16T20:21:06.400Z","8.1.57-renovate-major-json-schema-org-844fc0fa80f0f7ff6f139e967057da26d618024b":"2021-12-16T20:44:21.022Z","8.1.57-rationalize-context-91d79f6badaa549277de6ff1b0092b61f0f27a68":"2021-12-16T20:44:39.053Z","8.1.57-rationalize-context-73c6a9b03bb9c960b3a025d6515568664eaffee7":"2021-12-17T08:34:19.806Z","8.2.0-h32-error-ui-schema-c1f5e3ceb6e7768dca134f1216b869014aeb2b5d":"2021-12-17T09:00:03.992Z","8.2.0":"2021-12-17T09:06:08.869Z","8.2.1-renovate-major-json-schema-org-8c1881f33eea4b1566cd595af70d4dcf2ea9e77f":"2021-12-17T09:10:30.310Z","8.2.1-rationalize-context-522f21e00e63929766c8a1a105484c049975331f":"2021-12-17T09:43:47.054Z","8.2.1":"2021-12-17T10:17:50.015Z","8.2.2-renovate-major-json-schema-org-9078f80288c78a55992702df47d4270e5caceced":"2021-12-17T10:22:57.472Z","8.2.2-renovate-balena-jellyfish-logger-4-x-7df6d348df9ab2903604efde36aeccfc7a94e543":"2021-12-17T10:51:05.055Z","8.2.2":"2021-12-17T10:59:03.650Z","8.2.3-renovate-major-json-schema-org-96a4c3e5041833fc8cdfdd4da738ec3d1012d8de":"2021-12-17T11:04:38.853Z","8.2.3-renovate-internal-patch-f816615aa9b722aa729716bc42daba1e5e145736":"2021-12-17T16:45:14.551Z","8.2.3-renovate-external-non-major-a7dcddadd8318f46f133954ae7d6deb04a44611b":"2021-12-18T00:13:16.619Z","8.2.3-renovate-internal-patch-0189a504a523dc8588786abf98a7393031b4b080":"2021-12-18T00:13:47.058Z","8.2.3-renovate-internal-patch-cf67d991d79e0029d1fc20efc4ab8aa60ce14979":"2021-12-18T01:56:18.375Z","8.2.3-renovate-internal-patch-89b2d99eb417729b29942e17d13a84e4942045e1":"2021-12-18T09:35:17.910Z","8.2.3-renovate-external-non-major-377963ec3313962a0ae916e4079809e95d14603b":"2021-12-18T10:54:53.955Z","8.2.3":"2021-12-18T13:38:05.325Z","8.2.4-renovate-internal-patch-dbb7a6916661dbcda8bad1ff4ac5a35c4e574f17":"2021-12-18T13:41:49.268Z","8.2.4-renovate-external-non-major-6ee3e372266608cdd573d448289ba141032d07a4":"2021-12-18T13:43:50.508Z","8.2.4":"2021-12-18T13:53:52.413Z","8.2.5-renovate-major-json-schema-org-1ce0c43e75fe22728c6064986f7063315e62fcc2":"2021-12-18T13:57:21.140Z","8.2.5-renovate-internal-patch-d5eeee914e438a515cbeda86865aecb73ca967c3":"2021-12-18T14:00:47.213Z","8.2.5-renovate-internal-patch-1baa199da991d4ccec8628b3768b41a9c7a9c245":"2021-12-18T14:04:28.224Z","8.2.5":"2021-12-18T14:11:07.029Z","8.2.6-issue-1128-82d78dd87e93d386900d4e385aac7a620c0a42c8":"2021-12-20T17:13:41.130Z","8.2.6":"2021-12-20T17:21:13.826Z","8.2.7-renovate-major-json-schema-org-9bd7d35259b2c2c5f01070388e4decca09cd6b8d":"2021-12-20T17:23:56.118Z","8.2.7-restrict-jellyfish-types-f4e200f7e865fb460ffed05f47f5122e843ad016":"2021-12-20T18:07:43.699Z","8.2.7":"2021-12-20T18:16:20.371Z","8.2.8-renovate-major-json-schema-org-2be6e6a1098ad1bd2028ac241a123598a2ea4681":"2021-12-20T18:21:36.979Z","9.0.0-export-types-662a2787e7ec97c8b0bfc3826c22708fcf135e0f":"2021-12-20T18:34:29.530Z","9.0.0":"2021-12-20T18:42:06.784Z","10.0.0-more-interface-changes-17432d595f1d02ce838e6c57b36039e82107e21a":"2021-12-20T19:36:13.256Z","10.0.0":"2021-12-20T19:42:40.127Z","10.0.1-renovate-major-json-schema-org-e256a8dfb36145581d88219ead51e8f16e5eb81d":"2021-12-20T19:47:03.032Z","10.1.0-add-kernel-drop-0b03286282e4e7715728cee4c7622d94b9d4356f":"2021-12-20T20:05:43.367Z","10.1.0":"2021-12-20T20:13:12.965Z","10.2.0-kernel-reset-1edfbcfd379fd4874c94ea0e5eabf85f7ec4bfd0":"2021-12-22T20:46:26.189Z","10.1.1-use-truncate-e2e4050e271962ed0259a169e25a2d7ebf0e0285":"2021-12-22T20:48:04.359Z","10.1.1":"2021-12-22T20:54:19.028Z","10.2.0-kernel-reset-aed69d7afeeaa1f2c8cfa2888548703dcb8fc21b":"2021-12-22T21:04:07.453Z","10.2.0":"2021-12-22T21:25:49.780Z","10.2.1-renovate-major-json-schema-org-0e23e3ae27d2aecfd523f898da37882864f976f4":"2021-12-22T21:31:33.914Z","10.2.1-use-import-type-6e435de82b0b64646fd11a8385b08622d1b9c583":"2021-12-23T16:04:47.122Z","10.2.1":"2021-12-23T16:11:25.780Z","10.2.2-renovate-external-non-major-8f8dcbd92f2543ef4bef17083f6ee6856f58f713":"2021-12-25T00:32:30.977Z","10.2.2":"2021-12-25T00:39:25.431Z","10.2.3-renovate-internal-patch-f3526e849dfc787608bea74fb162a1b0d5e63350":"2021-12-25T00:42:59.458Z","10.2.3-renovate-major-json-schema-org-9ac505f37ca4dd841cb8c7e77ab6726b2198d052":"2021-12-25T00:47:42.236Z","10.2.3-renovate-internal-patch-c19678b6f47d67e4dcb973f6e8284610f1284a66":"2021-12-25T02:33:55.389Z","10.2.3":"2021-12-25T02:48:22.720Z","11.0.0-update-interface-24eb2981f78518a404b6ec6cb2e205230fef4b14":"2021-12-27T17:27:48.377Z","11.0.0-update-interface-8ff74b0ab9c47855d74c3c3f39b830e537d5d31f":"2021-12-27T17:51:35.457Z","11.0.0-update-interface-4e89c3c081abb1ae407e54bb2aebf96938b478e3":"2021-12-27T18:11:36.898Z","11.0.0-update-interface-dea38774700ced45dd05ded5359240f0132bf681":"2021-12-27T18:16:18.053Z","11.0.0":"2021-12-27T18:23:59.986Z","11.1.0-add-test-utils-44660dbbbbeb90eeab16d7a881acc999a2919a36":"2021-12-27T19:03:42.088Z","11.1.0-add-test-utils-1cfe4ff0a630295974a37da41b6c15c67d1155df":"2021-12-27T19:09:38.419Z","11.1.0-add-test-utils-92fefe6b037088a00868d58e454a1c20356d4b27":"2021-12-28T09:45:08.805Z","11.1.0":"2021-12-28T13:37:59.920Z","11.1.1-renovate-internal-patch-4ffda376b640dfd2eb3cc710831977e485084f6b":"2022-01-01T02:02:38.284Z","11.1.1-renovate-external-non-major-c29a7e547d98b21477180c10bb47fb5331e8de7a":"2022-01-01T02:03:35.671Z","11.1.1":"2022-01-01T02:11:24.856Z","11.1.2-renovate-internal-patch-59541ed03ca0d3ea6942be951e4efbdeaf8ed949":"2022-01-01T02:15:58.781Z","11.1.2-renovate-major-json-schema-org-235594c2b72f5a5fe24fe8afe475a2f527c22ef3":"2022-01-01T02:18:40.775Z","11.1.2-renovate-external-non-major-abef2160b33ddbe7d294d88f52ea35d17a931599":"2022-01-02T21:26:35.424Z","11.1.2":"2022-01-02T21:33:45.740Z","11.1.3-renovate-internal-patch-d6af82132b84c4294c99a81b2c47ad150f424ea3":"2022-01-02T21:37:52.486Z","11.1.3":"2022-01-02T21:50:54.893Z","11.1.4-renovate-internal-patch-57fd3b1bac83c866e7922e8bf25f223210f42010":"2022-01-02T23:19:05.832Z","11.1.4":"2022-01-03T05:02:43.758Z","11.1.5-renovate-balena-jellyfish-metrics-2-x-e272cb7829cdef1337e1b0428b2117e661a7e9d7":"2022-01-03T05:19:11.755Z","11.1.5-renovate-balena-jellyfish-metrics-2-x-54742fe1d53b8d1381df461947cb07358c02b845":"2022-01-03T07:11:53.241Z","11.1.5-renovate-balena-jellyfish-metrics-2-x-7c12456a4e342dfd667d26b95adc1104874721d7":"2022-01-03T07:51:44.210Z","11.1.5":"2022-01-03T07:58:22.377Z","11.1.6-renovate-internal-patch-4853aba76d00e653e5c72738d1bed2d25a0e4238":"2022-01-03T15:05:53.158Z","11.1.6":"2022-01-03T15:14:31.852Z","12.0.0-rationalize-db-connections-037493bc656c3290d0e13bfe0538ca1a557b3f60":"2022-01-03T20:10:28.811Z","12.0.0-rationalize-db-connections-0d7acfa37f83a41993efe6e596bcb922b209b20d":"2022-01-04T18:43:26.369Z","12.0.0-rationalize-db-connections-7dc741fb3eb79661042631baf027a63c0149fc60":"2022-01-04T19:06:31.955Z","12.0.0-rationalize-db-connections-f2b5a3a0d140e9e126781820b1baaa535a30fc00":"2022-01-05T14:24:21.662Z","12.0.0-joshbwlng-remove-actions-e2fc8524d0ce37f158a870c1f51474c3dc5e28cd":"2022-01-06T00:59:07.395Z","11.1.7-joshbwlng-fix-reset-193ceaeae4516a58c5054bcdf7bc1df0db4c28d3":"2022-01-06T03:15:50.537Z","11.1.7-joshbwlng-fix-reset-4565e933cfbd13e8de355a510393cdf018ef0fd1":"2022-01-06T04:41:58.729Z","11.1.7":"2022-01-06T06:47:50.280Z","11.1.8-renovate-major-json-schema-org-67a5cbc68daf744ae8efdf19a783fb587cd5a17c":"2022-01-06T11:42:42.937Z","12.0.0-rationalize-db-connections-0e7ec6f5d249b83b41e084eea43f30b215475f8a":"2022-01-07T19:19:32.006Z","12.0.0-rationalize-db-connections-a112328cecc5b5bb5d020261f0cb6ef607ca2b17":"2022-01-07T21:33:47.646Z","11.1.8-renovate-external-non-major-b56c7a567c9b5f4d2a3117238403967f0fbaba02":"2022-01-08T02:05:42.976Z","11.1.8":"2022-01-08T02:14:01.875Z","11.1.9-renovate-internal-patch-d8d710f501ca436784bb2afe29f4181135f3754f":"2022-01-08T02:23:26.966Z","11.1.9":"2022-01-08T02:32:00.711Z","11.1.10-renovate-major-json-schema-org-7203a9d2701e7fe6af182cb06bb34cee875d44dc":"2022-01-08T02:39:29.464Z","11.1.10-renovate-internal-patch-8397f5643a2c44d16bb5bfb36f0ed5bbee2f09d1":"2022-01-08T05:11:36.377Z","11.1.10-renovate-internal-patch-33f34b581b8e4af9a80d35b4a0a6d0b31bc6f5d5":"2022-01-08T12:31:15.118Z","11.1.10":"2022-01-08T12:42:49.695Z","11.1.10-renovate-major-json-schema-org-81df527daf5acb541454c924051a18744f2178ea":"2022-01-08T12:45:42.240Z","11.1.11-renovate-major-json-schema-org-d51c5ce2ff0f60b3dafea9117df420c1e4ba16ee":"2022-01-08T12:48:56.434Z","11.1.11-renovate-internal-patch-c9d4586d682724316c8feae15ce2f22f63abe6c7":"2022-01-08T14:14:37.114Z","11.1.11":"2022-01-08T14:21:08.214Z","11.1.8-card-to-contract-ed7ea8ab29d0e786124aa4d610c04f94b911b4df":"2022-01-10T16:19:30.825Z","12.0.0-rationalize-db-connections-c188b81de6f5c14dab3912e5fe9175276e3480a8":"2022-01-10T20:07:13.216Z","12.0.0-rationalize-db-connections-128d785ef0df69c3b0c482d2b8546c0ec1a6422c":"2022-01-10T20:16:00.070Z","12.0.0-rationalize-db-connections-bf563b7f06ce9dea78f409a50e39ecdd39bc9cf1":"2022-01-10T21:22:39.237Z","11.1.12-card-to-contract-26fcc8a6ec26d34370a0841822be4f718ffcc72c":"2022-01-11T11:23:39.762Z","12.0.0-rationalize-db-connections-69134e4eb641b1f52cbed2f7fa865717ff71a231":"2022-01-11T13:33:15.368Z","12.0.0-rationalize-db-connections-e56b4a8f7a561ffac61ac5474d3a751b5c5c9497":"2022-01-11T13:55:00.735Z","12.0.0-rationalize-db-connections-d5445244710f92e735a9ef9a549bd4366d86feb9":"2022-01-11T14:09:47.961Z","12.0.0-rationalize-db-connections-f7802eb14acb700cf73fb1e9f08eb8281bb4ee8e":"2022-01-11T14:22:59.780Z","12.0.0-rationalize-db-connections-41f458b874db826bc9f94ad00a70e35d8eb101f5":"2022-01-11T14:35:20.769Z","12.0.0":"2022-01-11T14:41:25.445Z","12.1.0-pool-test-utils-a6df7233954e2c068b151223e0b9d28bf90c93b7":"2022-01-11T16:54:38.953Z","12.1.0":"2022-01-11T17:02:07.938Z","11.1.12-card-to-contract-00b8de434ecd873b902984eecb28aaf7771225be":"2022-01-11T17:39:46.722Z","11.1.12-card-to-contract-2e3116b67e5999d4c9f75a8b87ac45e86a0a6569":"2022-01-11T18:09:34.213Z","12.1.1-card-to-contract-e5f1b30618dfc6f103b2e8577e7e4d0facff557c":"2022-01-11T19:10:45.851Z","12.1.1-card-to-contract-b270245786b6e7b4ab32e3929655ec7916c5847e":"2022-01-12T10:07:06.246Z","12.1.1":"2022-01-12T12:28:54.501Z","12.1.2-renovate-major-json-schema-org-2facc93423802e5e633119e1770caceed3d16950":"2022-01-12T14:10:10.438Z","12.1.2-alias-public-interfaces-card-to-contract-7f9cee9d3f23b14586e42dec62e395ec95529e24":"2022-01-14T17:10:03.479Z","12.1.2-alias-public-interfaces-card-to-contract-cd8e2d905131bc2513e8a1db2934f138f0518220":"2022-01-14T17:38:55.253Z","12.1.2-renovate-external-non-major-e7bb4f1cf9033611911b383e9fd418bcc998a20b":"2022-01-15T00:24:30.963Z","12.1.2":"2022-01-15T00:30:44.174Z","12.1.3-renovate-major-json-schema-org-44674aa1d965ae75f33a86db9684e6dffe77711e":"2022-01-15T00:34:02.132Z","12.1.3-renovate-internal-patch-878476e50a75b35686e0803dee220eea81c7b722":"2022-01-15T00:37:48.612Z","12.1.3":"2022-01-15T00:45:12.790Z","12.1.4-renovate-major-json-schema-org-e39d7bc287c45514ea9b674a68683adbd287694b":"2022-01-15T00:51:55.956Z","12.1.4-renovate-internal-patch-1fd4fbc3b8b1d857a97b1626a28ac76f08114dd2":"2022-01-15T03:12:14.698Z","12.1.4":"2022-01-15T03:19:06.411Z","12.1.5-renovate-internal-patch-1f59a51b41d0215d800b73be354f08e9450ca5aa":"2022-01-15T03:22:46.784Z","12.1.5":"2022-01-15T03:31:04.856Z","12.1.6-joshbwlng-bump-skhema-6da3c0e996c3462e33a5003c54029f63f0786467":"2022-01-16T08:57:08.990Z","12.1.6":"2022-01-16T09:03:38.847Z","13.0.0-joshbwlng-action-request-context-02798feea07faa7f3fde13348c3c3653263b2378":"2022-01-17T05:59:20.703Z","13.0.0-joshbwlng-action-request-context-440059bbbd0bcbd09e2878c143da5cfe3bd369b6":"2022-01-17T06:16:28.121Z","12.1.7-renovate-internal-patch-7000e25f0f43782200588200ee20b407eaef8ab4":"2022-01-17T10:29:26.011Z","12.1.7":"2022-01-17T10:36:09.720Z","12.1.8-renovate-major-json-schema-org-d205003eae867e441d216dfc6faac0ecc522a2b7":"2022-01-17T10:41:41.205Z","12.2.0-alias-public-interfaces-card-to-contract-86d2a81e4da41dd5b1a56ac6a0d5321a934487f5":"2022-01-17T14:17:17.604Z","12.2.0-alias-public-interfaces-card-to-contract-5e877d17669cc45abc07dd110820b29bf0cb3a85":"2022-01-17T14:56:53.719Z","12.2.0":"2022-01-17T15:03:03.165Z","12.2.1-renovate-major-json-schema-org-d682858bf04296d525fd6f7d37c3ed8afefeb517":"2022-01-17T15:08:13.901Z","12.2.1-fix-typo-insert-conctract-5982cfb63638b48f2a00ef37022d4487510f0515":"2022-01-17T17:08:04.736Z","12.2.1":"2022-01-17T17:15:32.692Z","12.2.2-renovate-major-json-schema-org-1d7e2e1d0f0f7adb2bf9f89af3644b7c0efcdee4":"2022-01-17T17:20:11.255Z","12.2.2-use-typescript--deprecated-718a5eea94706aaf3212d4327505dc1b6071ef00":"2022-01-17T19:01:39.073Z","12.2.2":"2022-01-17T19:54:08.857Z","12.2.3-renovate-typedoc-0-x-0e3fa280d319fd54915c1f5637b46737bd904e4d":"2022-01-18T10:06:51.469Z","12.2.3-renovate-resinci-jellyfish-test-1-x-3df26e29a5f2586d3890905b28949bc64e997362":"2022-01-18T10:08:01.784Z","12.2.3-lucianbuzzo-stop-using-json-schema-ref-f48a3792d16f1d7972a95d5235542ad58e4a469d":"2022-01-18T10:39:40.470Z","12.2.3":"2022-01-18T11:09:07.395Z","12.2.4-renovate-resinci-jellyfish-test-1-x-ea18934ed6938aea284d771b9b12a4c3bb4063c9":"2022-01-18T11:14:56.856Z","12.2.4":"2022-01-19T03:25:43.280Z","12.2.5-renovate-typedoc-0-x-b6cd39ce858eabd6f6a1fc1614acf3c22736ccd4":"2022-01-19T05:01:56.881Z","12.2.5":"2022-01-19T05:10:22.073Z","13.0.0-joshbwlng-remove-action-contracts-92098221205075a5db44ad6a4f8ee5dfb7d992e9":"2022-01-19T05:37:13.606Z","13.0.0":"2022-01-19T05:47:46.976Z","13.1.0-joshbwlng-role-guest-d626c0eafbf22674ad379fa781545133a9933e42":"2022-01-19T06:48:05.806Z","13.1.0-joshbwlng-role-guest-d40eb822451a3a64b25d0e1edd26e886b5d37592":"2022-01-19T07:13:39.816Z","13.1.0":"2022-01-19T07:31:54.312Z","13.1.1-renovate-internal-non-major-310ad6dcb8005c474b1db89e89697a43cfc843e6":"2022-01-19T14:12:05.222Z","13.1.1":"2022-01-19T14:18:19.614Z","13.1.2-renovate-major-json-schema-org-40d2d92b2539e2a26ea467c90ca981ee7e4a217b":"2022-01-19T14:21:33.253Z","13.2.0-joshbwlng-move-roles-eafcdd96cd036f523db8c7d181ff56a7e3f53094":"2022-01-20T05:23:25.200Z","13.2.0":"2022-01-20T05:30:33.714Z","13.3.0-joshbwlng-community-role-test-a42731e0cd6bf270e026961b0ab9053220a21e3d":"2022-01-20T07:08:05.533Z","13.3.0-joshbwlng-community-role-test-60ea23c8a211950928f01e62f81e863bf4e1abc1":"2022-01-20T07:17:13.786Z","13.3.0":"2022-01-20T07:23:23.528Z","14.0.0-fix-defaults-83cdba9f77941d7cbbd42ec73e40ea84a109e6eb":"2022-01-20T21:26:24.806Z","14.0.0":"2022-01-20T21:32:30.780Z","14.0.1-renovate-major-json-schema-org-7e5690d854d0f892714cb7c11d24ac729b84fb07":"2022-01-20T21:37:06.873Z","14.0.1-edufschmidt-move-types-into-core-8d998111aa63471cb241b10cd2142dca33d21ed4":"2022-01-21T17:10:09.593Z","14.0.1-renovate-external-non-major-0a00877898255fa3331416e5e3ff0cf90834b2fa":"2022-01-22T00:22:02.684Z","14.0.1-renovate-internal-non-major-a5a707c7023968315fb5dc7272b869562524f771":"2022-01-22T00:34:05.805Z","14.0.1-renovate-major-json-schema-org-8442e8b0faded1dbe0292d94485f86c088fbe13e":"2022-01-22T00:34:43.898Z","14.0.1":"2022-01-22T00:41:24.116Z","14.0.2-renovate-major-json-schema-org-c4001fc267fc90648b22fe59b57c69d1bd09f9a4":"2022-01-22T00:44:45.222Z","14.0.2-renovate-internal-non-major-954739e13c0e3f078ef19650851038ba7ee6ee99":"2022-01-22T02:50:14.511Z","14.0.2":"2022-01-22T02:56:52.856Z","14.0.3-renovate-major-json-schema-org-06f8115c54823b8d11e81064fdf92130bd0435b8":"2022-01-22T03:00:15.188Z","14.0.3-renovate-internal-non-major-eb00c09639a41d9f555bc61c991e5682083bdc9a":"2022-01-22T05:12:30.063Z","14.0.3":"2022-01-22T05:20:25.567Z","14.0.4-renovate-major-json-schema-org-bc592c18c172f289b4bca86490ae025ea5826451":"2022-01-22T05:23:44.820Z","14.0.4-renovate-internal-non-major-800840ba3186a22e1a6d6e5e03ed5f5d32f1cd41":"2022-01-22T09:39:33.609Z","14.0.4":"2022-01-23T02:09:29.901Z","14.0.4-renovate-major-json-schema-org-b2b219fabc8008880fa24c923f6ee133ed68f2e8":"2022-01-23T02:12:25.999Z","14.0.5-renovate-external-non-major-c0c532d16a2746f658f2de1b5f08c7a97bb4f989":"2022-01-23T11:29:13.655Z","14.0.5":"2022-01-23T11:35:49.738Z","14.0.6-renovate-major-json-schema-org-5f9eb2acb6ed26f76ae1e7253c87d1462ec15aea":"2022-01-23T11:39:35.848Z","14.0.6-renovate-internal-non-major-2c80a0439f59d8d63106a75c7e8199efc4a4ca87":"2022-01-23T14:19:14.927Z","14.0.6":"2022-01-23T14:25:06.452Z","14.0.7-renovate-major-json-schema-org-92496ea67951ed75ae06f8360c1446911469ffb7":"2022-01-23T14:31:32.078Z","14.0.7-renovate-internal-non-major-8f8c530fcac4f52994ab2b2d52b83be84287519c":"2022-01-23T16:26:04.679Z","14.0.7":"2022-01-23T16:35:58.482Z","14.0.8-renovate-external-non-major-b9d6386590a9e5f3fec6a8cccc0b10b9a3a1d1ba":"2022-01-23T16:40:40.823Z","14.0.8-renovate-internal-non-major-cdaf7d803f3fb664c951546036b02950e610fdf5":"2022-01-23T18:56:14.223Z","14.0.8":"2022-01-23T19:01:41.394Z","14.0.9-renovate-external-non-major-b4d1fc190e7eff4c361d69f02f04bfdf99a83245":"2022-01-23T19:05:40.016Z","14.0.9":"2022-01-23T19:13:59.089Z","14.0.9-renovate-major-json-schema-org-415b987dd1963497a572fec87f82b0b77f0c9acf":"2022-01-23T19:16:12.325Z","14.0.10-renovate-major-json-schema-org-1df37de2428b29977b80cc3667fbc607e0d31dc6":"2022-01-23T19:19:25.334Z","14.0.10-renovate-internal-non-major-188ac1cf955a6a129f2e8de7ee27ff360df75d01":"2022-01-23T21:17:58.949Z","14.0.10-renovate-internal-non-major-e51af3bd9d2bf6a7d46172ebf33a737bb477aae9":"2022-01-24T14:12:14.182Z","14.0.10-renovate-internal-non-major-cd86823f83e4ba7c368c609ae4a223799e86ab3a":"2022-01-24T16:06:57.204Z","14.0.10":"2022-01-24T16:14:35.230Z","14.0.10-renovate-major-json-schema-org-6c52baf6ceb547c707f47f1bb5c344c0004ed82d":"2022-01-24T16:16:37.939Z","14.0.11-renovate-internal-non-major-2121310033983518fd267ad9757d50773e0b528f":"2022-01-24T18:47:55.734Z","14.0.11":"2022-01-24T18:54:34.108Z","14.0.12-renovate-major-json-schema-org-31b825c75d747411c456159ef476530182d3c781":"2022-01-24T18:57:02.962Z","14.0.12-renovate-resinci-jellyfish-test-1-x-bd42d5d358533551b2573b6a354b6d7b4b4e33b7":"2022-01-25T11:10:34.790Z","14.0.12":"2022-01-25T18:52:53.132Z","14.0.13-renovate-major-json-schema-org-9c9e65ccaf83f247ccca4cb4c4162598fbba1f56":"2022-01-25T18:57:19.399Z","14.0.13-joshbwlng-export-ui-schema-def-901d5ce1e5bcfb2aae74609e03d70384267bc046":"2022-01-26T00:33:40.846Z","14.0.13-joshbwlng-export-ui-schema-def-2489e5c1f43f4c698e6ad2e3ff486f77201ce9bd":"2022-01-26T00:42:20.657Z","14.0.13":"2022-01-26T01:17:01.178Z","14.1.0-joshbwlng-add-mixins-35cff6c7c843c7b3b847a77db524fa829371a1b4":"2022-01-26T01:47:46.665Z","14.1.0-joshbwlng-add-mixins-239f2dca7c683e701973c0f89c03cbbaabd93981":"2022-01-26T02:34:06.672Z","14.1.0-joshbwlng-add-mixins-fad6415f3b66290434438d99a5a448f821a64cac":"2022-01-26T03:34:42.314Z","14.1.0-joshbwlng-add-mixins-fbe1b95ac4f01b8fdae7a5db2af29b08d2dab6d9":"2022-01-26T03:40:43.706Z","14.1.0":"2022-01-26T04:14:51.820Z","14.0.1-edufschmidt-move-types-into-core-c9ed3ab0f6a2c92c6646bbfe76e8217ccbf1af16":"2022-01-26T14:15:50.684Z","14.1.1-renovate-balena-balena-redis-0-x-786067a4ef0da24d9da335db280ab1ac92391366":"2022-01-28T00:58:43.600Z","14.1.1":"2022-01-28T02:37:37.168Z","14.1.2-renovate-major-json-schema-org-b99967c426309562551c93afb83f313ae5bc5a20":"2022-01-28T02:49:52.894Z","14.1.2-renovate-external-non-major-3624e0e73236881673786cc9362938221d8fe3fa":"2022-01-29T01:00:18.303Z","14.1.2":"2022-01-29T01:07:23.911Z","14.1.3-renovate-internal-patch-45f3940c2b6f906c92063ba240a10a5bc9823b84":"2022-01-29T01:11:05.526Z","14.1.3-renovate-major-json-schema-org-eec9a004f68dcdec248cbd0f4a8db8efea68d785":"2022-01-29T01:12:07.974Z","14.1.3":"2022-01-29T01:21:53.922Z","14.1.4-renovate-internal-patch-97f566aec734b298fb52475ef48c191823c3168f":"2022-01-29T04:00:26.425Z","14.1.4-renovate-internal-patch-bc1ff757068fc858c82b9e3edc32ec55ed558346":"2022-01-29T05:40:34.949Z","14.1.4":"2022-01-29T05:46:37.854Z","14.1.5-renovate-major-json-schema-org-c5cb3d53f0124ed0e005e144ad22f3ef601faa6a":"2022-01-29T05:51:03.050Z","14.1.5-renovate-resinci-jellyfish-test-1-x-2553d08a2e6e6e4cff25b5b6d7bef5e9d82eb4f4":"2022-01-31T12:06:20.275Z","14.1.5":"2022-01-31T12:12:28.237Z","14.1.6-renovate-major-json-schema-org-cdd219e4caaf288588c3415948ecb9d595d27f89":"2022-01-31T12:17:01.252Z","14.0.1-edufschmidt-move-types-into-core-ab091f439574f28c259cb3584d252ec7e57e8f94":"2022-01-31T14:38:15.711Z","14.2.0-export-query-options-7b127336e187bf1f551c9f1afdee868d792d07b0":"2022-02-01T18:49:04.207Z","14.2.0-export-query-options-daae8112f216177dff5817e22cf26097d2d3b491":"2022-02-01T18:52:26.227Z","14.2.0-export-query-options-59acaff98e1429a918f2cad78686732d294cae29":"2022-02-01T19:05:12.340Z","14.2.0":"2022-02-01T19:10:58.998Z","14.2.1-renovate-resinci-jellyfish-test-1-x-9040e71ae9a02b4f6b02e06e95eb6b4dab9e6942":"2022-02-04T00:55:36.382Z","14.2.1":"2022-02-04T12:13:52.940Z","14.2.2-renovate-major-json-schema-org-6af605b3e83e6d69cb28d33d2f65cc29d86742a8":"2022-02-04T12:16:48.967Z","14.2.2-renovate-external-non-major-bf955f43fa621342224c578c991132052db2c748":"2022-02-05T02:11:21.769Z","14.2.2":"2022-02-05T02:17:35.873Z","14.2.3-renovate-major-json-schema-org-20612ecd385f24512a22d861807060ed531a52ee":"2022-02-05T02:21:25.097Z","14.2.3-renovate-internal-patch-c5927df596939279e84a765435c2c3f90ba9d026":"2022-02-05T11:16:24.242Z","14.2.3":"2022-02-05T11:23:24.292Z","14.2.4-renovate-major-json-schema-org-5875b27696e9e3199d5151bd1c3d6e738437e800":"2022-02-05T11:27:38.969Z","14.2.4-joshbwlng-postgres-df2c183848b9278f609779af7f89c02fb5f0f9dc":"2022-02-06T12:31:16.307Z","14.2.4":"2022-02-06T12:38:26.388Z","14.3.0-joshbwlng-stream-change-07b60ffed64bab940a3723fa8d0c1b073ff60a0b":"2022-02-08T00:43:09.588Z","14.3.0":"2022-02-09T00:21:57.638Z","14.3.1-renovate-major-json-schema-org-d34b435a7f9b6c5aa990a6b3f745033ab9e44956":"2022-02-09T00:28:15.945Z","14.3.1-renovate-resinci-jellyfish-test-1-x-1eac1f2d739794b80ee361f480ae46858cbfad88":"2022-02-09T20:06:51.932Z","14.3.1":"2022-02-10T01:43:28.372Z","14.3.1-renovate-major-json-schema-org-b668240c365f490dad7d52518e660e3ec2f2acbd":"2022-02-10T01:46:46.674Z","14.3.2-renovate-major-json-schema-org-38175ed9c60e4fc7d71332dab3c4a718e36d1c0b":"2022-02-10T09:46:43.812Z","14.3.2-renovate-resinci-jellyfish-test-1-x-e92e01fd63dcf3189601bf0bfc795d55e93fe114":"2022-02-10T17:43:46.412Z","14.3.2":"2022-02-11T12:25:07.401Z","14.3.3-renovate-major-json-schema-org-2598189d971596afa02cc13ac99dbb366d7390ba":"2022-02-11T12:28:43.903Z","14.3.3-renovate-balena-jellyfish-environment-6-x-d15ff577f48c8b1de3766944bf00c4b9f0157ab4":"2022-02-13T05:18:56.427Z","14.3.3-renovate-external-non-major-95c1ced0b50c16851962b6e2a085c11bedd8d449":"2022-02-13T18:08:05.774Z","14.3.3":"2022-02-13T18:12:40.710Z","14.3.3-renovate-balena-jellyfish-environment-6-x-4e1565c1517a052462fefd7f98cf4bd9c8046243":"2022-02-13T18:14:47.068Z","14.3.4-renovate-balena-jellyfish-environment-6-x-4e5ab76e4b2759d5dac51137472b2890a86a27c4":"2022-02-13T18:17:25.681Z","14.3.4-renovate-internal-patch-d29d2a4d70c446f594873868e014085091458786":"2022-02-13T20:22:22.742Z","14.3.4":"2022-02-13T20:26:29.538Z","14.3.5-renovate-balena-jellyfish-environment-6-x-e592e2b23559a7fe2d794fff4692d83fbee07df4":"2022-02-13T20:29:45.422Z","14.3.5-move-unit-e89be343de790f0f03e0210acb36675fec58d337":"2022-02-14T12:01:21.031Z","14.3.5":"2022-02-14T12:06:32.499Z","14.3.5-renovate-balena-jellyfish-environment-6-x-7879f8a166c6e32fa36f7a5a81bf2ed47ff32d3c":"2022-02-14T12:08:53.387Z","14.3.6-renovate-resinci-jellyfish-test-1-x-f578e35570f46b65fc448b8ff065d2a8dd68bca0":"2022-02-14T15:10:11.106Z","14.4.0-edufschmidt-refactor-permissions-filter-4668b0f1eb491c806285dd2092e493bb00a4a561":"2022-02-14T18:16:06.421Z","14.4.0-edufschmidt-refactor-permissions-filter-8b8d0afd6fd734d1371355632151e5c71908ac5f":"2022-02-14T22:28:56.821Z","14.4.0-edufschmidt-refactor-permissions-filter-1cb920da7647a528d82ea8904bbdedc77a44335a":"2022-02-14T22:43:31.020Z","14.3.6-renovate-balena-jellyfish-environment-6-x-37a5d6edce1a48bdf490c72f96660e8178d6d82c":"2022-02-15T01:09:41.048Z","14.3.6":"2022-02-15T02:27:39.425Z","14.3.7-renovate-major-json-schema-org-bf1507f99bd2dea1cf42640db2cb4ab000696c5d":"2022-02-15T02:34:11.768Z","14.3.7-renovate-resinci-jellyfish-test-1-x-310cf6c2fb35fcce93c9ab6e4d361059948adf32":"2022-02-15T22:33:27.595Z","14.3.7":"2022-02-15T22:51:14.047Z","14.3.8-renovate-major-json-schema-org-e23d3704f3e792df5832658fa5e71d1119ada484":"2022-02-15T22:58:33.061Z","14.3.8-renovate-balena-jellyfish-environment-7-x-0397e4e84023aefeaa31ddce5f50dbb603ea7a4a":"2022-02-16T09:32:48.932Z","14.3.8":"2022-02-16T12:38:06.065Z","14.3.8-renovate-major-json-schema-org-0853a919062ca7fa92315ddc7bad1031a169d795":"2022-02-16T12:40:51.536Z","14.3.9-renovate-major-json-schema-org-27c113318b5caafc2c684abc5e4495fa4a600728":"2022-02-16T12:43:50.434Z","14.4.0-edufschmidt-refactor-permissions-filter-8ffee2372510e8a35cb83af12877f463ce692148":"2022-02-16T14:54:58.053Z","14.4.0-edufschmidt-refactor-permissions-filter-821ad0198edfb77d8cc45683b383a9afb15b0a82":"2022-02-17T18:28:51.806Z","14.4.0":"2022-02-17T18:48:25.705Z","14.4.1-renovate-major-json-schema-org-fc4e8f10368751918a09f6878388854d4c84cc0b":"2022-02-17T18:58:51.330Z","14.4.1-dont-merge-23eefb30244cc78cb1ffdc667519d2fe7166abd8":"2022-02-18T17:33:22.722Z","14.4.1-dont-merge-eee07c234e5da3391dfaafe06f6865368fa0ab9b":"2022-02-18T19:16:44.084Z","14.4.1":"2022-02-18T19:20:30.888Z","14.4.2-renovate-major-json-schema-org-04dec2273a40306304b74248b93b6096edba390e":"2022-02-18T19:24:41.311Z","14.4.2-renovate-typedoc-0-x-1ef67195ca9c703c61ea6b7e14bd011473c5ee11":"2022-02-20T04:05:37.412Z","14.4.2-renovate-major-json-schema-org-7a63080d971e2bbbb9838bdd9916eb24f0abb93f":"2022-02-20T06:44:44.926Z","14.4.2-renovate-internal-patch-58de6b483a49ab55467c83a82a10bac2671ef2c0":"2022-02-20T06:49:42.358Z","14.4.2":"2022-02-20T06:54:58.197Z","14.4.3-renovate-major-json-schema-org-d33e6819769be13926ac247bfbaa51bfcc316e8a":"2022-02-20T07:01:33.183Z","14.4.3-joshbwlng-fix-org-query-8bdf59543abee36ca0f5abfd26ea1345a434ba2b":"2022-02-22T06:24:03.059Z","14.4.3":"2022-02-22T06:30:19.652Z","14.4.4-renovate-major-json-schema-org-affff30d4d01d8ff6199cb3d23edf0057c4f62af":"2022-02-22T06:35:17.844Z","14.5.0-joshbwlng-add-marker-tests-f8e06caa0993cf42c6b85da66f86eaae4393ae7c":"2022-02-22T10:10:32.563Z","14.5.0-joshbwlng-add-marker-tests-f8dfab006ef6f12b89ed5ad07eb2b70075fb0af2":"2022-02-22T10:16:51.939Z","14.4.4-apply-mask-a10430ad05ccb68e61234dc9460b1555db6b89d9":"2022-02-23T16:30:15.845Z","14.4.4":"2022-02-23T16:50:50.821Z","14.4.5-renovate-major-json-schema-org-0956e1012d5146f10a5a60b035af72aca9d6c781":"2022-02-23T16:54:40.825Z","14.4.5-renovate-balena-jellyfish-environment-8-x-760ef4156dce923552efb1ff12c935d96f8c618e":"2022-02-23T19:17:54.239Z","15.0.0-update-redis-daace13eecc294eb29e90dbb6a4fe86441f8347b":"2022-02-23T21:05:51.275Z","15.0.0":"2022-02-23T21:12:42.020Z","15.0.0-renovate-major-json-schema-org-e0a7b6aefb17fa5a3e8264545d3226395a0da784":"2022-02-23T21:14:35.642Z","15.0.1-renovate-major-external-major-8cc4720f0882914747191bdd911b66c08d339c0e":"2022-02-23T21:15:49.541Z","15.0.1-renovate-major-json-schema-org-c9b89744602adfd220f52eb19bb7f79e11d68e5f":"2022-02-23T22:58:52.380Z","15.1.0-mask-test-cea9d4924bb888d8e1c063edee4b00e46ebdc2d1":"2022-02-24T08:48:10.402Z","15.1.0-mask-test-24be3323a51a3fd1cdb5f4a0cf0de9d1f42ea1b8":"2022-02-24T09:05:16.969Z","15.1.0-mask-test-7abdd05595eea18e511451cbf787e6d42bad5e0f":"2022-02-24T09:18:42.807Z","15.1.0-mask-test-ee8da7205fe71ca4d65b978809ead2855d4c4258":"2022-02-24T09:36:48.935Z","15.0.1-renovate-major-external-major-c3f6eb5df02a2fc50a545a7e395a9aa0631bd791":"2022-02-24T19:37:44.409Z","15.0.1-joshbwlng-bump-image-6d075210ee14a67fe5fa4bb01880a61fb6952146":"2022-02-25T03:25:23.835Z","15.0.1":"2022-02-25T03:29:49.867Z","15.0.2-renovate-major-json-schema-org-eb27ca26fc936c2ecd52c0540590c5598bb6b3b1":"2022-02-25T03:33:24.550Z","15.0.2-renovate-major-external-major-675b3ea148cdb27a8976d041c7e796388542466e":"2022-02-25T03:35:59.536Z","15.0.2-renovate-balena-jellyfish-environment-9-x-cf033c51519eb65a39da51f25a17429f3bfa91f3":"2022-02-25T06:00:05.341Z","15.0.2":"2022-02-25T06:18:21.585Z","15.0.3-renovate-major-json-schema-org-6273d5dc550d87705e63b03504106f81e81cbbd4":"2022-02-25T06:21:56.836Z","15.0.3-LucianBuzzo-patch-1-0c3ebee45e71a56fde8255cf14106b8b4a7b5b00":"2022-02-25T14:52:34.699Z","15.0.3":"2022-02-25T14:57:49.113Z","15.0.4-renovate-major-external-major-cc354c17db4c656d28f6e573770ad3601f347006":"2022-02-25T15:01:48.893Z","15.0.4-renovate-internal-patch-9b055a5ac5945a25d93fbf659653fc9a5864d3d6":"2022-02-26T02:09:27.868Z","15.0.4":"2022-02-26T02:13:48.800Z","15.0.5-renovate-major-json-schema-org-575a4ee4699b260a45a77df73d72d6a49f8a914a":"2022-02-26T02:18:11.265Z","15.0.5-renovate-major-external-major-38d3ad9d6030447bac5b8c54512f9488360f8bf2":"2022-02-26T02:20:09.571Z","15.0.5-renovate-internal-patch-ff84bfa7c3fbcb53204ee4bec9726448674bfced":"2022-02-26T05:07:08.927Z","15.0.5":"2022-02-26T05:11:17.673Z","15.0.6-renovate-major-external-major-301d3a8ee61846e38de14eeccb9360af3a7e807f":"2022-02-26T05:15:06.678Z","15.0.6-revert-allof-c9397b95678cffe804a3bea9d89bcd316044f7b1":"2022-02-28T13:03:18.012Z","15.0.6-revert-allof-d8880301dbc68f143958a244b514236fa6480458":"2022-02-28T13:21:56.894Z","15.0.6":"2022-02-28T13:25:58.014Z","15.0.7-renovate-major-external-major-8ba839cfddcd8c472a92e1741df7286501be2446":"2022-02-28T13:32:09.116Z","15.0.7-merge-workaround-82362bdc62a232effd1e4fd5479607aa3b294caf":"2022-02-28T13:33:36.208Z","15.0.7":"2022-02-28T13:38:06.194Z","15.0.8-renovate-major-external-major-1768d495c55737e649ce87fbc9347ca8c8e968b5":"2022-02-28T13:43:50.096Z","15.0.8-renovate-major-json-schema-org-19426ed642f6a5f54d4bd8b45e9d4e47fbe2fbea":"2022-02-28T13:44:32.251Z","15.0.8-merge-workaroud-a423bb692d0455a6862617432b9dbcd9b011896a":"2022-02-28T17:44:31.619Z","15.0.8":"2022-02-28T17:48:19.320Z","15.0.9-renovate-major-json-schema-org-a80c7c7f5c5b9669a69e8552f23fc3492df49daf":"2022-02-28T17:54:20.129Z","15.1.0-joshbwlng-add-marker-tests-7c5e7f1629c07ccc5ec20a5f30cfc0eea91f55bd":"2022-03-01T05:03:26.220Z","15.1.0":"2022-03-01T05:18:03.581Z","15.1.1-renovate-major-json-schema-org-ae7b670d1dd9009b04568f386be82c1b3694d151":"2022-03-01T05:23:48.385Z","15.1.1-renovate-major-external-major-ba7884b43615016685c6816f8a0f051c823c0a01":"2022-03-01T05:24:05.746Z","16.0.0-lucianbuzzo-restrict-guest-user-role-8388f93dfa8eca0fab82015771844f77b8ab6237":"2022-03-01T11:14:57.820Z","16.0.0":"2022-03-01T14:45:06.986Z","16.0.1-renovate-major-external-major-a2a7667433fa90dbebeb67128b1f8dbe3c91596e":"2022-03-01T14:51:19.436Z","16.0.1-renovate-major-external-major-5fbade75ae30d403cd65f2cccab93b946ccead43":"2022-03-02T17:44:43.981Z","16.0.1-joshbwlng-test-skhema-d5690e54d90af6b919b7698a4823a124cbda6a31":"2022-03-03T05:30:14.091Z","16.0.1-joshbwlng-test-skhema-d9834ff10945f049ec0f661e1bd72d193122ec5b":"2022-03-03T06:20:30.509Z","17.0.0-joshbwlng-update-error-checks-31dd351aef0241471e099aa6445de27ccc76fc4a":"2022-03-04T06:12:09.002Z","16.0.1-joshbwlng-update-error-checks-f0013aecc61f8770ef816d97ef9febe7335938ae":"2022-03-04T06:55:05.189Z","16.0.1-joshbwlng-update-error-checks-8c4e342019b82b2cde291ba053452dc1d1708e67":"2022-03-04T07:00:26.723Z","16.0.1":"2022-03-04T07:27:55.622Z","16.0.2-renovate-major-json-schema-org-fe1c952e6b203f06449b7b3e6f7d81f320d12360":"2022-03-04T07:31:13.405Z","16.0.2-renovate-major-external-major-12201dbe25666afe28ea93dc661dee472ce7916d":"2022-03-04T07:32:03.525Z","16.0.2-joshbwlng-bump-skhema-7384982d0852a741c156cb5cc07dd7908557f10f":"2022-03-05T03:24:54.042Z","16.0.2":"2022-03-05T08:30:37.249Z","16.0.3-renovate-major-external-major-f5b87ad196b9b1e0c05acc5e912a1bccc13552ea":"2022-03-05T09:16:02.734Z","16.0.3-renovate-internal-patch-37dfb8602c6692c4848c391d7555556389d1b4d9":"2022-03-05T16:27:47.952Z","16.0.3-renovate-internal-patch-83cde9b86dccae88ba377777be3706661ea23fd7":"2022-03-05T16:33:21.595Z","16.0.3":"2022-03-05T16:37:17.816Z","16.0.3-renovate-external-non-major-1676b51a51c784add64ebacae9c4146f2b30582b":"2022-03-05T16:40:33.664Z","16.0.4-renovate-external-non-major-bf826596bc71a510fa37c46e6a44647371d10f2a":"2022-03-05T16:44:30.908Z","16.0.4-renovate-major-json-schema-org-2ba62ed7cc747b50e07290f10183efd34689eb90":"2022-03-05T16:45:24.246Z","16.0.4-joshbwlng-bump-external-32045f2116f3a516fcf6290cc29241e76daadb21":"2022-03-06T02:20:29.629Z","16.0.4":"2022-03-06T02:24:21.127Z","16.0.5-renovate-major-external-major-ba45fcfb36e4a6dd83cf4f6e3327b3577e3e7a9c":"2022-03-06T02:29:42.932Z","16.0.5-renovate-typescript-4-x-c04cedc39baa995528b1e97f5c50d82e346ada87":"2022-03-06T02:30:30.282Z","16.0.5-renovate-internal-patch-75c1bf3e4ad26f40af537ceb1383b1fa26d0af92":"2022-03-06T04:30:33.231Z","16.0.5":"2022-03-06T04:34:47.635Z","16.0.6-renovate-typescript-4-x-48d74e2d38bceed25f5ef7ec6e06c4c287c08ebf":"2022-03-06T04:37:18.656Z","16.0.6-renovate-major-json-schema-org-c6b1a8c76d1b9397523afc979019a6396c9635e1":"2022-03-06T04:38:52.805Z","16.0.6-renovate-typedoc-0-x-cb3a201cb0831ea8538b29ef6d1dd3e1e79ad26c":"2022-03-07T02:24:57.396Z","16.0.6-renovate-typescript-4-x-b2d61dd76fd8f74c71f903f722c24814483cd15d":"2022-03-07T02:43:41.868Z","16.0.6":"2022-03-07T02:48:30.355Z","16.0.7-renovate-major-json-schema-org-e4574cfb5ddf565c89ea6a1a850ff062f32e1f54":"2022-03-07T02:54:27.797Z","16.0.7-renovate-major-external-major-748402b58d390d8d402c2a7d632ddda526cb268d":"2022-03-07T02:58:19.556Z","16.0.7-renovate-internal-patch-6a60f8ee0d662cd1fa9cae03d901712c249d54f3":"2022-03-07T05:59:16.702Z","16.0.7":"2022-03-07T06:03:12.353Z","16.0.8-renovate-major-external-major-443ed851f25b9443c544ce7822b966789a234d01":"2022-03-07T06:07:03.370Z","16.0.8-lucianbuzzo-stop-using-events-33d1e545c34cbc0f099a106592f5a6f96d1e230b":"2022-03-07T15:12:56.111Z","16.0.8-lucianbuzzo-stop-using-events-93cf56665bf0369ae7aa7ef5a0928848090cb4fa":"2022-03-08T11:04:30.694Z","16.0.8-lucianbuzzo-stop-using-events-adf28a130e8292d31269ff7bc63b6060efd6e782":"2022-03-08T11:45:41.964Z","16.0.8":"2022-03-08T12:31:35.537Z","16.0.9-renovate-major-json-schema-org-d6a2b7fc2224a69efd39382e44643abad8747eac":"2022-03-08T12:38:05.945Z","16.0.9-renovate-major-external-major-9818ddd10769b7f38e9f934bb3b6999cc1ad5f5f":"2022-03-08T12:38:24.602Z","16.0.9-joshbwlng-fix-events-partial-2612206d68a09875af8be4adca73a88d5f22329a":"2022-03-09T07:03:41.478Z","16.0.9":"2022-03-09T07:41:38.497Z","16.0.10-renovate-major-json-schema-org-b049e6c8839b178c374ebe76425d0385606b3a99":"2022-03-09T07:47:22.424Z","16.0.10-lucianbuzzo-unit-test-formulas-a30e09e3a502d37251e9f146baa326ce00212f6c":"2022-03-09T10:59:05.808Z","16.0.10":"2022-03-09T11:12:27.480Z","16.0.11-renovate-major-json-schema-org-365a8bf7810af55a242aadf856d11d56b9c1fa4c":"2022-03-09T11:15:50.045Z","16.0.11-renovate-major-external-major-8794e1157d1f54fd2567832193914822a6bbeed2":"2022-03-09T11:16:29.444Z","16.0.11-renovate-balena-jellyfish-jellyscript-5-x-3178964e95309b930477127ee3836d977fc4557a":"2022-03-09T13:56:18.954Z","16.0.11":"2022-03-09T15:00:24.151Z","16.0.12-renovate-major-json-schema-org-86d45b23e4c0411305a423dce82e51609b4f240a":"2022-03-09T15:05:38.832Z","16.0.12-renovate-major-external-major-783dfaf8a526b558177997c33d72dcd49e9a62cb":"2022-03-09T15:06:19.357Z","16.0.12-lucianbuzzo-aggregate-initial-value-63545856b2dab2c15df0220dcc04cb35c7bd884d":"2022-03-11T11:03:27.526Z","16.0.12":"2022-03-11T11:18:08.714Z","16.0.13-renovate-major-external-major-3eff9aa3eb767ed804395d4477b50b212303af31":"2022-03-11T11:22:00.959Z","16.0.13-lucianbuzzo-jellyscript-6-x-64677214f1f0600981e3c511c962afd5a68a16a1":"2022-03-14T14:14:58.537Z","16.0.13":"2022-03-14T14:35:27.912Z","16.0.13-renovate-major-json-schema-org-e993f43d3529c835a57b48e6251c768dd1db5a6f":"2022-03-14T14:38:21.050Z","16.0.14-renovate-major-external-major-98a3ec3f7e9d2fdf7ebb67e5ac08936443c9e9e1":"2022-03-14T19:40:44.059Z","16.0.14-renovate-major-external-major-84ac7e3cadfc8d0fb229b0a916aef804637c15f4":"2022-03-15T02:07:22.362Z"},"bugs":{"url":"https://github.com/product-os/jellyfish-core/issues"},"author":{"name":"Balena.io.","email":"hello@balena.io"},"license":"Apache-2.0","homepage":"https://github.com/product-os/jellyfish-core#readme","repository":{"url":"git+https://github.com/product-os/jellyfish-core.git","type":"git"},"description":"Core library for Jellyfish","maintainers":[{"name":"balena.io","email":"accounts+npm@balena.io"},{"name":"page","email":"page@balena.io"}],"readme":"","readmeFilename":""}